Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 0 additions & 15 deletions .github/workflows/ci-macos-compat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,21 +97,6 @@ jobs:
path: GhosttyKit.xcframework
key: ghosttykit-v3-${{ steps.ghostty-revision.outputs.revision }}

- name: Resolve zig version
id: zig-version
run: printf 'version=%s\n' "$(./scripts/required-zig-version.sh)" >> "$GITHUB_OUTPUT"

# install-zig.sh re-checks the tarball against scripts/zig-macos-aarch64.sha256
# on every run, so a cache hit never skips the checksum.
- name: Cache zig toolchain tarball
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
with:
path: ~/.cache/programa-zig-dist
key: zig-dist-macos-arm64-${{ steps.zig-version.outputs.version }}

- name: Set up required Zig
run: ./scripts/install-zig.sh

- name: Download pre-built GhosttyKit.xcframework
if: steps.cache-ghosttykit.outputs.cache-hit != 'true'
run: |
Expand Down
76 changes: 3 additions & 73 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -389,38 +389,6 @@ jobs:
run: |
./scripts/download-prebuilt-ghosttykit.sh

- name: Resolve zig version
id: zig-version
run: printf 'version=%s\n' "$(./scripts/required-zig-version.sh)" >> "$GITHUB_OUTPUT"

# install-zig.sh re-checks the tarball against scripts/zig-macos-aarch64.sha256
# on every run, so a cache hit never skips the checksum.
- name: Cache zig toolchain tarball
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
with:
path: ~/.cache/programa-zig-dist
key: zig-dist-macos-arm64-${{ steps.zig-version.outputs.version }}

- name: Install zig
run: ./scripts/install-zig.sh

- name: Cache Zig packages
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
with:
path: ~/.cache/zig
key: zig-packages-${{ hashFiles('ghostty/build.zig.zon', 'ghostty/build.zig.zon.json') }}
restore-keys: zig-packages-

# The "Copy Ghostty Resources" build phase compiles the Ghostty CLI helper
# (Resources/bin/ghostty) with zig. zig's local cache turns that into a
# no-op while the ghostty revision and zig version stay the same. No
# restore-keys: an exact key keeps the cache from accumulating old builds.
- name: Cache Ghostty CLI helper build
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
with:
path: ghostty/.zig-cache
key: ghostty-cli-helper-${{ steps.ghostty-revision.outputs.revision }}-zig${{ steps.zig-version.outputs.version }}

- name: Cache DerivedData
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
with:
Expand Down Expand Up @@ -504,11 +472,6 @@ jobs:
PROGRAMA_DERIVED_DATA_DIR: ${{ github.workspace }}/.ci-derived-data-unit-${{ matrix.shard }}
PROGRAMA_UNIT_TEST_SHARD: ${{ matrix.shard }}
PROGRAMA_UNIT_TEST_SHARD_COUNT: 2
# No unit test runs the Ghostty CLI helper (Resources/bin/ghostty), so the
# shards take the build script's stub instead of compiling it with zig.
# build-app still builds the real helper; its artifact feeds the jobs that
# do run it (theme-picker-helper-regression, socket and UI tests).
PROGRAMA_SKIP_ZIG_BUILD: "1"
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
Expand Down Expand Up @@ -675,6 +638,7 @@ jobs:
result=0
for t in \
tests/test_cli_noninteractive_install_regression.py \
tests/test_cli_themes.py \
tests/test_cli_socket_no_autodiscovery_regression.py \
tests/test_cli_wait_surface_agent_state.py \
tests/test_cli_claude_settings_preservation.py \
Expand Down Expand Up @@ -778,41 +742,6 @@ jobs:
echo "unit-tests-shard: $SHARD_RESULT (change-detection: $DETECTION_RESULT, run_app_jobs: $RUN_APP_JOBS)" >&2
exit 1

# Independent of unit-tests: same "programa" scheme app build-app already
# produces, so this reuses that artifact instead of paying for its own
# ~3 min build. Split out so it does not serialize behind the unit-test
# shards.
theme-picker-helper-regression:
needs:
- build-app
- change-detection
if: needs.change-detection.outputs.run_app_jobs == 'true'
runs-on: macos-26
timeout-minutes: 15
env:
PROGRAMA_DERIVED_DATA_DIR: ${{ github.workspace }}/.ci-derived-data-theme-picker
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: Download build artifact
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
with:
name: programa-app-build
path: .

- name: Extract build artifact
run: |
set -euo pipefail
mkdir -p "$PROGRAMA_DERIVED_DATA_DIR"
tar -xzf app-build.tar.gz -C "$PROGRAMA_DERIVED_DATA_DIR"

- name: Run bundled Ghostty theme picker helper regression
run: |
set -euo pipefail
PROGRAMA_PREBUILT_APP_PATH="$PROGRAMA_DERIVED_DATA_DIR/Build/Products/Debug/Programa DEV.app" \
./tests/test_bundled_ghostty_theme_picker_helper.sh

socket-integration-tests:
needs:
- build-app
Expand Down Expand Up @@ -1038,10 +967,11 @@ jobs:
fi
rm -f /tmp/create-virtual-display

# Independent of unit-tests: it reuses the build-app artifact, so waiting
# on the unit-test shards only lengthens the main-push CI tail.
ui-regressions:
needs:
- build-app
- unit-tests
- change-detection
if: ((github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'workflow_dispatch' && inputs.notification_ui)) && needs.change-detection.outputs.run_app_jobs == 'true'
runs-on: macos-26
Expand Down
16 changes: 3 additions & 13 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,7 @@ jobs:
bun-version: 1.3.14

- name: Install build deps
run: |
./scripts/install-zig.sh
./scripts/install-create-dmg.sh
run: ./scripts/install-create-dmg.sh

# Always download and checksum GhosttyKit here instead of restoring the CI
# cache: the pinned SHA-256 covers the archive, not an extracted tree, so a
Expand Down Expand Up @@ -263,8 +261,7 @@ jobs:
run: |
./scripts/verify-release-architectures.sh \
"build-universal/Build/Products/Release/Programa.app/Contents/MacOS/Programa" \
"build-universal/Build/Products/Release/Programa.app/Contents/Resources/bin/programa" \
"build-universal/Build/Products/Release/Programa.app/Contents/Resources/bin/ghostty"
"build-universal/Build/Products/Release/Programa.app/Contents/Resources/bin/programa"

- name: Archive dSYMs
run: |
Expand Down Expand Up @@ -302,12 +299,6 @@ jobs:
[ -x "$CLI_BINARY" ] || { echo "programa CLI binary not found at $CLI_BINARY" >&2; exit 1; }
PROGRAMA_CLI_BIN="$CLI_BINARY" python3 tests/test_cli_version_memory_guard.py

- name: Verify bundled Ghostty theme picker helper
run: |
set -euo pipefail
HELPER_BINARY="build-universal/Build/Products/Release/Programa.app/Contents/Resources/bin/ghostty"
[ -x "$HELPER_BINARY" ] || { echo "Ghostty theme picker helper not found at $HELPER_BINARY" >&2; exit 1; }

- name: Inject Sparkle keys into Info.plist
run: |
APP_PLIST="build-universal/Build/Products/Release/Programa.app/Contents/Info.plist"
Expand Down Expand Up @@ -373,10 +364,9 @@ jobs:
fi
APP_PATH="build-universal/Build/Products/Release/Programa.app"
CLI_PATH="$APP_PATH/Contents/Resources/bin/programa"
HELPER_PATH="$APP_PATH/Contents/Resources/bin/ghostty"
MCP_PATH="$APP_PATH/Contents/Resources/bin/programa-mcp"
./scripts/sign-release-app.sh "$APP_PATH" "$APPLE_SIGNING_IDENTITY" programa.entitlements
./scripts/verify-release-entitlements.sh "$APP_PATH" "$CLI_PATH" "$HELPER_PATH" "$MCP_PATH"
./scripts/verify-release-entitlements.sh "$APP_PATH" "$CLI_PATH" "$MCP_PATH"
./scripts/verify-provision-profile.sh "$APP_PATH"

- name: Verify embedded Sparkle artifact
Expand Down
3 changes: 0 additions & 3 deletions .github/workflows/test-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,6 @@ jobs:
GHOSTTYKIT_DOWNLOAD_RETRY_DELAY: 20
run: ./scripts/download-prebuilt-ghosttykit.sh

- name: Install zig
run: ./scripts/install-zig.sh

- name: Create virtual display
if: ${{ inputs.test_filter != 'DisplayResolutionRegressionUITests' }}
run: |
Expand Down
10 changes: 5 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ Programa is a fork of [cmux](https://github.com/manaflow-ai/cmux); for history p

### Added
- Programs can report their status to Programa with the OSC 7501 Program Status Protocol. The sidebar shows working, blocked, done and error, an unfocused terminal gets a notification when a program finishes, fails or needs input, and `surface.list`, `system.tree` and `surface.wait` (`program_state`) expose it without ever returning the message text. Programa's own agent hooks emit it too, and it outranks hook and screen-detected state. See `docs/program-status.md`.
- Programa can be set as the default web browser. Links clicked in other apps open as a browser tab in the focused pane of the active window, including a link that launches Programa. Links Programa sends to an external browser skip Programa itself and go to the next installed browser.
- A ports button in the sidebar footer shows what is listening on a port in each workspace, plus dev servers left running after their terminal closed, with Open, Copy URL, Reveal and Stop. `programa list-ports` and `programa stop-port` and the `ports.list` and `ports.stop` socket methods do the same from the command line.
- New `agent.needs_input` socket method reports a surface as blocked on the user and posts its notification in one call, instead of three separate calls that could fail independently.
- New `surface.resolve_tty` socket method finds the terminal surface for a tty without changing focus. `agent.spawn` returns the new terminal's `pane_id` and `pane_ref`, and `agent.detection.list` reports manifests it rejected in a `rejected` list.
Expand All @@ -26,10 +25,9 @@ Programa is a fork of [cmux](https://github.com/manaflow-ai/cmux); for history p
- Every trusted `programa.json` asks for approval once more after updating. Trust is now recorded per config file, and the dialog says so when a config was trusted before that change.
- `programa claude`, `codex` and `opencode` `install-integration` run without a terminal now need `--yes`.
- The CLI no longer connects to a dev, staging or tagged app's socket on its own. Point it at one with `--socket` or `PROGRAMA_SOCKET_PATH`.
- A program that writes to the clipboard with OSC 52 asks before the clipboard changes.
- Browser cookie methods act on the current page's site by default; `all_domains: true` widens them, and a `domain` filter matches the exact host or its subdomains only. `browser.state.save` writes its file with `0600` permissions to an absolute path and exports only the current site's cookies unless asked for more.
- A program that writes to the clipboard with OSC 52 asks before the clipboard changes, showing a preview of the text. "Always allow for this terminal" stops the prompt for that terminal until it closes; a `clipboard-write` setting in your config still wins.
- Socket errors are consistent: a reference that does not resolve returns `not_found` (a malformed one stays `invalid_params`), splitting a workspace that already has four panes returns `limit_reached` before anything is created, and closing a window's last workspace returns `invalid_state`.
- `surface.send_text` and `agent.prompt` report `"queued": true` when the text waits for a terminal that is not attached yet. `surface.wait` resolves with `"outcome": "closed"` when its surface closes, caps `timeout_ms` at one hour, and rejects `exit` on a browser surface.
- `surface.send_text` and `agent.prompt` report `"queued": true` when the text waits for a terminal that is not attached yet. `surface.wait` resolves with `"outcome": "closed"` when its surface closes and caps `timeout_ms` at one hour.
- A workspace's `agent_state` counts only helpers that are still running; finished helpers are counted in the new `helper_outcomes` field of `workspace.list` and `system.tree`.
- Password mode closes a connection after five failed `auth.login` attempts and compares the password in constant time.
- Agent hooks fail open: outside Programa, or when the app is not reachable, they answer the agent normally and let it continue. The Codex installer keeps everything it writes to `config.toml` inside one `# BEGIN programa` block.
Expand All @@ -38,11 +36,13 @@ Programa is a fork of [cmux](https://github.com/manaflow-ai/cmux); for history p
- The tmux shim's `resize-pane` counts terminal cells, `capture-pane` shows the visible screen, and `send-keys` supports named keys.
- The Rust core is no longer part of the macOS app; it serves the Windows app only.
- Each workspace row now shows one agent indicator (Needs input, Working, or Idle) instead of the badge, status row, and notification text sometimes disagreeing with each other. It dims and shows "(stale)" after ten minutes with no update from the agent, and only clears when the agent itself reports it's resumed, not just from opening the workspace.
- `programa themes` lists and sets themes without the interactive picker, and the app no longer bundles a second copy of the ghostty command-line tool (about 6 MB smaller). Unknown theme names suggest close matches.

### Removed
- Browser socket methods that only ever answered `not_supported` (network routing, viewport emulation, raw input injection and similar) are gone; calling them is now an unknown-method error.
- The built-in browser, its panes, the `browser.*` socket methods and the matching CLI and MCP tools. Links open in your default browser. See `docs/removed/built-in-browser.md`.
- The library-validation and unsigned-executable-memory entitlements.
- The `programa codex install-hooks` and `uninstall-hooks` legacy aliases. Use `programa codex install-integration` and `uninstall-integration`.
- The `programa omo`, `omx` and `omc` wrappers for oh-my-openagent, Oh My Codex and Oh My Claude Code. The `programa claude`, `codex` and `opencode` integrations are unchanged. See `docs/removed/omo-omx-omc-wrappers.md` to restore them.

### Fixed
- With dozens of tabs in a pane, each tab keeps a width that shows its title instead of shrinking to one letter. The tab bar scrolls with a regular mouse wheel and keeps the selected tab in view.
Expand Down
2 changes: 1 addition & 1 deletion CLI-MCP/Tools/SurfaceTools.swift
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ enum SurfaceTools {
"workspace_id": ProgramaToolSchema.string("Workspace UUID or short ref."),
"surface_id": ProgramaToolSchema.string("Surface UUID or short ref."),
"branch": ProgramaToolSchema.string("Git branch name."),
"dirty": ProgramaToolSchema.boolean("Whether the working tree has uncommitted changes. Defaults to false."),
"dirty": ProgramaToolSchema.boolean("Whether the working tree has uncommitted changes. When omitted, the last value the app probed is kept (false if none)."),
],
required: ["workspace_id", "surface_id", "branch"]
)
Expand Down
Loading
Loading