Skip to content

feat: add shellInteractive flag for PTY-based shell commands#326

Merged
bguidolim merged 5 commits intomainfrom
bruno/shell-interactive-stdin
Apr 12, 2026
Merged

feat: add shellInteractive flag for PTY-based shell commands#326
bguidolim merged 5 commits intomainfrom
bruno/shell-interactive-stdin

Conversation

@bguidolim
Copy link
Copy Markdown
Collaborator

Summary

Adds a shellInteractive YAML key for tech pack shell: components that need terminal access (e.g. install scripts requiring sudo). When enabled, mcs uses forkpty() to allocate a real pseudo-terminal so commands can prompt for passwords securely with proper echo control.

Changes

  • Add interactive: Bool associated value to ComponentInstallAction.shellCommand and ExternalInstallAction.shellCommand (defaults to false — fully backward-compatible)
  • Add shellInteractive shorthand key to ExternalComponentDefinition for YAML packs
  • Thread interactive through ShellRunning protocol, all sync strategies, PackInstaller, and ManifestBuilder export
  • Add runInteractive() to ShellRunner using forkpty() + select() I/O bridge with raw-mode terminal, EINTR handling, proper WIFEXITED/WIFSIGNALED exit status, and defer-based terminal restoration
  • Show "may prompt for your password" message before interactive commands; suppress empty stderr warnings for interactive failures

Test plan

  • swift test passes locally (978 tests)
  • swiftformat --lint . and swiftlint pass without violations
  • Tested mcs sync --global with a pack using shellInteractive: true — sudo prompt works, password is hidden, install completes successfully
  • Verified non-interactive shell commands are unaffected (default interactive: false)
  • Verified mcs doctor does not trigger side effects from interactive components
Checklist for engine changes
  • .shellCommand components have supplementaryDoctorChecks defined (deriveDoctorCheck() returns nil for shell actions)
  • Docs updated if behavior changed (CLAUDE.md, docs/, techpack.yaml schema in ExternalPackManifest.swift)

Tech pack YAML syntax

- id: my-tool
  type: configuration
  shell: "curl -fsSL https://example.com/install.sh | sh"
  shellInteractive: true  # allocates PTY for sudo prompts

- Add `shellInteractive` YAML key and `interactive` parameter to
  shellCommand install actions, enabling commands that need terminal
  access (e.g. sudo prompts) to run via forkpty() with a real PTY
- Thread interactive flag through ShellRunning protocol, all sync
  strategies, PackInstaller, and ManifestBuilder export
- Show "may prompt for your password" message before interactive
  commands; suppress empty stderr warnings for interactive failures
- Document shellInteractive in CLAUDE.md, techpack-schema.md,
  creating-tech-packs.md, and architecture.md
- Add manifest decode tests for interactive flag (verbose + shorthand)
- Add adapter passthrough test for interactive flag
- Add lifecycle integration tests for shellCommand components
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an interactive flag to shell-based install actions so tech pack shell components can optionally run inside a PTY (via forkpty) to support terminal-driven prompts such as sudo, along with YAML/docs/test updates.

Changes:

  • Extend .shellCommand actions (internal + external/YAML) with interactive: Bool = false and add shellInteractive YAML shorthand.
  • Thread the interactive flag through sync/install/export paths and implement PTY-backed execution in ShellRunner.
  • Update docs and add/adjust tests for decoding and lifecycle behavior.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
Tests/MCSTests/TestHelpers.swift Updates MockShellRunner to match the expanded ShellRunning API.
Tests/MCSTests/LifecycleIntegrationTests.swift Adds lifecycle coverage for shellCommand components and the new interactive flag.
Tests/MCSTests/ExternalPackManifestTests.swift Adds/updates YAML decode tests for interactive and shellInteractive.
Tests/MCSTests/ExternalPackAdapterTests.swift Verifies adapter passthrough of the interactive flag.
Sources/mcs/TechPack/Component.swift Extends ComponentInstallAction.shellCommand with interactive.
Sources/mcs/Sync/ProjectSyncStrategy.swift Threads interactive execution through project sync shell commands.
Sources/mcs/Sync/PackInstaller.swift Threads interactive execution through dependency installer shell commands.
Sources/mcs/Sync/GlobalSyncStrategy.swift Adds interactive messaging and routes interactive shell commands through the runner.
Sources/mcs/ExternalPack/PackTrustManager.swift Updates trust manager pattern-matching for the new shellCommand associated value shape.
Sources/mcs/ExternalPack/ExternalPackManifest.swift Adds shellInteractive shorthand + verbose interactive decoding/encoding for shellCommand.
Sources/mcs/ExternalPack/ExternalPackAdapter.swift Maps external shellCommand interactive flag into internal component actions.
Sources/mcs/Export/ManifestBuilder.swift Exports shellInteractive: true when an action is interactive.
Sources/mcs/Core/ShellRunner.swift Implements PTY-backed interactive execution and updates ShellRunning API.
docs/techpack-schema.md Documents shellInteractive and the verbose interactive field.
docs/creating-tech-packs.md Adds authoring guidance/examples for shellInteractive: true.
docs/architecture.md Updates architecture docs for the new shellCommand signature.
CLAUDE.md Updates external pack shorthand documentation to mention shellInteractive.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Sources/mcs/Core/ShellRunner.swift Outdated
Comment thread Sources/mcs/Core/ShellRunner.swift
Comment thread Sources/mcs/Core/ShellRunner.swift Outdated
Comment thread Sources/mcs/Core/ShellRunner.swift
Comment thread Sources/mcs/Sync/ProjectSyncStrategy.swift Outdated
Comment thread Tests/MCSTests/LifecycleIntegrationTests.swift Outdated
- Unify interactive failure messaging: all three call sites now show
  "failed (see output above)" instead of silent suppression or
  misleading "requires manual installation"
- Add writeAll helper to handle partial writes and EINTR in I/O bridge
- Handle stdin EOF by removing stdin from select set
- Re-enable ISIG in raw mode so Ctrl-C/Ctrl-Z can interrupt stuck commands
- Add chdir/execve failure diagnostics in child process
- Retry waitpid on EINTR, retry tcsetattr on failure
- Fix misleading ShorthandKeys comment and stale docstrings
- Replace select() + fd_set helpers with poll() — removes 25 lines
  of manual bit manipulation and eliminates FD_SETSIZE bounds concern
- Fix fork safety: use only async-signal-safe C calls in child process
  (no Swift String interpolation or ARC after forkpty)
- Record interactive flag in MockShellRunner RunCall/ShellCall structs
- Remove unnecessary inout on writeAll buf parameter
- Simplify terminal restore to single TCSADRAIN call
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Sources/mcs/Core/ShellRunner.swift Outdated
Comment thread Sources/mcs/Core/ShellRunner.swift Outdated
…branch

- Pre-convert workingDirectory to a C string before forkpty() so the
  child process doesn't invoke Swift's String-to-CString bridge
- Remove unreachable else-if POLLHUP branch (already covered by
  the POLLIN|POLLHUP check above)
@bguidolim bguidolim enabled auto-merge (squash) April 12, 2026 12:29
@bguidolim bguidolim merged commit be2e270 into main Apr 12, 2026
4 checks passed
@bguidolim bguidolim deleted the bruno/shell-interactive-stdin branch April 12, 2026 12:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants