@phnx-labs/agents-cli 1.22.46 → 1.22.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +663 -0
- package/LICENSE +102 -182
- package/README.md +96 -49
- package/dist/bootstrap.js +4 -2
- package/dist/cli/command-registry.d.ts +2 -1
- package/dist/cli/command-registry.js +6 -2
- package/dist/commands/accounts.d.ts +58 -0
- package/dist/commands/accounts.js +243 -25
- package/dist/commands/artifacts.js +7 -6
- package/dist/commands/attach.js +1 -1
- package/dist/commands/auth.js +7 -5
- package/dist/commands/browser-sessions-picker.js +9 -7
- package/dist/commands/browser.js +711 -62
- package/dist/commands/config.d.ts +0 -1
- package/dist/commands/config.js +26 -24
- package/dist/commands/cost.d.ts +1 -1
- package/dist/commands/daemon.d.ts +21 -0
- package/dist/commands/daemon.js +147 -12
- package/dist/commands/detach.d.ts +13 -0
- package/dist/commands/detach.js +11 -8
- package/dist/commands/exec.js +107 -53
- package/dist/commands/feed-watch.d.ts +2 -0
- package/dist/commands/feed-watch.js +28 -0
- package/dist/commands/feed.js +27 -0
- package/dist/commands/feedback.js +3 -19
- package/dist/commands/focus.d.ts +14 -0
- package/dist/commands/focus.js +48 -5
- package/dist/commands/go.d.ts +3 -0
- package/dist/commands/go.js +16 -3
- package/dist/commands/insights.d.ts +1 -1
- package/dist/commands/insights.js +2 -2
- package/dist/commands/inspect.js +2 -2
- package/dist/commands/lease.d.ts +1 -1
- package/dist/commands/lease.js +7 -6
- package/dist/commands/mailboxes.js +4 -3
- package/dist/commands/menubar.js +11 -4
- package/dist/commands/projects.d.ts +16 -1
- package/dist/commands/projects.js +58 -64
- package/dist/commands/resume.js +1 -1
- package/dist/commands/route.js +4 -4
- package/dist/commands/routines.js +4 -3
- package/dist/commands/run-broadcast.d.ts +1 -1
- package/dist/commands/run-broadcast.js +1 -1
- package/dist/commands/sessions-picker-factory.d.ts +2 -1
- package/dist/commands/sessions-picker-factory.js +1 -1
- package/dist/commands/sessions-picker.d.ts +22 -0
- package/dist/commands/sessions-picker.js +48 -9
- package/dist/commands/sessions-stop.d.ts +24 -0
- package/dist/commands/sessions-stop.js +66 -0
- package/dist/commands/sessions-trace.d.ts +82 -0
- package/dist/commands/sessions-trace.js +387 -0
- package/dist/commands/sessions.js +5 -0
- package/dist/commands/setup-secrets.js +23 -0
- package/dist/commands/share.d.ts +20 -5
- package/dist/commands/share.js +152 -52
- package/dist/commands/ssh.d.ts +15 -0
- package/dist/commands/ssh.js +243 -18
- package/dist/commands/teams.js +34 -2
- package/dist/commands/traces.d.ts +2 -0
- package/dist/commands/traces.js +184 -0
- package/dist/commands/view.d.ts +2 -0
- package/dist/commands/view.js +32 -62
- package/dist/commands/watchdog.js +9 -14
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/lib/account-capabilities.d.ts +4 -0
- package/dist/lib/account-capabilities.js +13 -9
- package/dist/lib/account-registry.d.ts +2 -0
- package/dist/lib/account-registry.js +30 -2
- package/dist/lib/accounting/rotate.d.ts +9 -0
- package/dist/lib/accounting/rotate.js +12 -2
- package/dist/lib/accounting/usage.d.ts +84 -6
- package/dist/lib/accounting/usage.js +293 -75
- package/dist/lib/agent-modes.js +29 -1
- package/dist/lib/agent-spec/agents.d.ts +4 -3
- package/dist/lib/agent-spec/agents.js +25 -15
- package/dist/lib/analytics/mix-commands.d.ts +1 -1
- package/dist/lib/analytics/mix-commands.js +2 -2
- package/dist/lib/auth-health.d.ts +13 -3
- package/dist/lib/auth-health.js +50 -8
- package/dist/lib/browser/devices.d.ts +19 -0
- package/dist/lib/browser/devices.js +23 -0
- package/dist/lib/browser/drivers/ssh.d.ts +16 -2
- package/dist/lib/browser/drivers/ssh.js +36 -9
- package/dist/lib/browser/ipc.d.ts +1 -1
- package/dist/lib/browser/ipc.js +40 -9
- package/dist/lib/browser/profiles.d.ts +104 -65
- package/dist/lib/browser/profiles.js +277 -165
- package/dist/lib/browser/registry.d.ts +39 -0
- package/dist/lib/browser/registry.js +149 -0
- package/dist/lib/browser/remote-control.d.ts +28 -2
- package/dist/lib/browser/remote-control.js +35 -3
- package/dist/lib/browser/resolve-target.d.ts +68 -0
- package/dist/lib/browser/resolve-target.js +250 -0
- package/dist/lib/browser/runtime-state.d.ts +44 -11
- package/dist/lib/browser/runtime-state.js +98 -15
- package/dist/lib/browser/service.d.ts +65 -9
- package/dist/lib/browser/service.js +196 -61
- package/dist/lib/browser/sessions-list.d.ts +1 -3
- package/dist/lib/browser/sessions-list.js +3 -16
- package/dist/lib/browser/task-index.d.ts +70 -0
- package/dist/lib/browser/task-index.js +180 -0
- package/dist/lib/browser/types.d.ts +12 -1
- package/dist/lib/byok-usage.js +2 -2
- package/dist/lib/claude-statusline.d.ts +29 -0
- package/dist/lib/claude-statusline.js +139 -0
- package/dist/lib/cloud/host.d.ts +1 -1
- package/dist/lib/cloud/host.js +1 -1
- package/dist/lib/cloud/rush.js +1 -1
- package/dist/lib/codex-policy.d.ts +11 -1
- package/dist/lib/codex-policy.js +20 -5
- package/dist/lib/computer/computer-rpc.js +2 -2
- package/dist/lib/computer/download.d.ts +15 -15
- package/dist/lib/computer/download.js +35 -98
- package/dist/lib/computer/ssh-tunnel.d.ts +0 -3
- package/dist/lib/computer/ssh-tunnel.js +12 -22
- package/dist/lib/config-keys.d.ts +6 -11
- package/dist/lib/config-keys.js +18 -19
- package/dist/lib/crabbox/cli.d.ts +1 -1
- package/dist/lib/crabbox/cli.js +2 -2
- package/dist/lib/crabbox/lease.d.ts +9 -0
- package/dist/lib/crabbox/lease.js +70 -15
- package/dist/lib/crabbox/runtimes.js +1 -1
- package/dist/lib/daemon/account-state-daemon-service.d.ts +16 -0
- package/dist/lib/daemon/account-state-daemon-service.js +26 -0
- package/dist/lib/daemon/browser-ipc-service.d.ts +20 -0
- package/dist/lib/daemon/browser-ipc-service.js +46 -0
- package/dist/lib/daemon/daemon.d.ts +3 -0
- package/dist/lib/daemon/daemon.js +212 -444
- package/dist/lib/daemon/device-probe-service.d.ts +26 -0
- package/dist/lib/daemon/device-probe-service.js +47 -0
- package/dist/lib/daemon/keychain-reap-service.d.ts +17 -0
- package/dist/lib/daemon/keychain-reap-service.js +32 -0
- package/dist/lib/daemon/monitor-engine-service.d.ts +22 -0
- package/dist/lib/daemon/monitor-engine-service.js +29 -0
- package/dist/lib/daemon/runner.js +15 -2
- package/dist/lib/daemon/secrets-broker-service.d.ts +20 -0
- package/dist/lib/daemon/secrets-broker-service.js +66 -0
- package/dist/lib/daemon/self-heal-service.d.ts +27 -0
- package/dist/lib/daemon/self-heal-service.js +48 -0
- package/dist/lib/daemon/service.d.ts +102 -0
- package/dist/lib/daemon/service.js +98 -0
- package/dist/lib/daemon/session-index-service.d.ts +22 -0
- package/dist/lib/daemon/session-index-service.js +40 -0
- package/dist/lib/daemon/state-dir-check-service.d.ts +43 -0
- package/dist/lib/daemon/state-dir-check-service.js +63 -0
- package/dist/lib/daemon/supervisor.d.ts +87 -0
- package/dist/lib/daemon/supervisor.js +298 -0
- package/dist/lib/daemon/watchdog-service.d.ts +19 -0
- package/dist/lib/daemon/watchdog-service.js +40 -0
- package/dist/lib/daemon-health.d.ts +15 -0
- package/dist/lib/daemon-health.js +29 -5
- package/dist/lib/daemon-services.d.ts +8 -1
- package/dist/lib/daemon-services.js +55 -0
- package/dist/lib/daemon-ticks.d.ts +13 -2
- package/dist/lib/daemon-ticks.js +22 -20
- package/dist/lib/device-config.d.ts +25 -10
- package/dist/lib/device-config.js +90 -39
- package/dist/lib/devices/config-migration.d.ts +7 -0
- package/dist/lib/devices/config-migration.js +67 -3
- package/dist/lib/devices/health.d.ts +27 -2
- package/dist/lib/devices/health.js +39 -9
- package/dist/lib/devices/interactive-host.d.ts +15 -0
- package/dist/lib/devices/interactive-host.js +53 -0
- package/dist/lib/devices/registry.d.ts +61 -4
- package/dist/lib/devices/registry.js +94 -38
- package/dist/lib/devices/stats-cache.d.ts +26 -0
- package/dist/lib/devices/stats-cache.js +59 -7
- package/dist/lib/devices/worker-pick.d.ts +48 -0
- package/dist/lib/devices/worker-pick.js +113 -0
- package/dist/lib/exec.d.ts +70 -14
- package/dist/lib/exec.js +110 -38
- package/dist/lib/feed/answer.d.ts +29 -0
- package/dist/lib/feed/answer.js +145 -0
- package/dist/lib/feed/attention.d.ts +83 -0
- package/dist/lib/feed/attention.js +0 -0
- package/dist/lib/feed/feed.d.ts +109 -0
- package/dist/lib/feed/feed.js +154 -3
- package/dist/lib/feed/pr-status.d.ts +14 -0
- package/dist/lib/feed/pr-status.js +47 -0
- package/dist/lib/feed/watch.d.ts +59 -0
- package/dist/lib/feed/watch.js +180 -0
- package/dist/lib/fleet/capture.js +6 -0
- package/dist/lib/fleet/remote-login.js +8 -3
- package/dist/lib/fleet/types.d.ts +17 -0
- package/dist/lib/harness/adapter.d.ts +11 -0
- package/dist/lib/harness/adapters/claude.js +38 -24
- package/dist/lib/harness/adapters/codex.js +2 -4
- package/dist/lib/harness/adapters/cursor.js +8 -11
- package/dist/lib/helper-download.d.ts +108 -0
- package/dist/lib/helper-download.js +194 -0
- package/dist/lib/helper-versions.d.ts +52 -0
- package/dist/lib/helper-versions.js +58 -0
- package/dist/lib/hooks/cache.js +19 -0
- package/dist/lib/hooks/install.js +2 -2
- package/dist/lib/hooks/match.d.ts +3 -0
- package/dist/lib/hooks/match.js +25 -0
- package/dist/lib/hosts/credentials.d.ts +1 -1
- package/dist/lib/hosts/credentials.js +1 -1
- package/dist/lib/hosts/dispatch.d.ts +7 -1
- package/dist/lib/hosts/dispatch.js +24 -5
- package/dist/lib/hosts/passthrough.d.ts +8 -15
- package/dist/lib/hosts/passthrough.js +35 -16
- package/dist/lib/hosts/providers/devices.d.ts +1 -1
- package/dist/lib/hosts/providers/devices.js +1 -1
- package/dist/lib/hosts/reconnect.d.ts +179 -21
- package/dist/lib/hosts/reconnect.js +266 -45
- package/dist/lib/hosts/registry.js +7 -0
- package/dist/lib/installations/migrate.d.ts +4 -5
- package/dist/lib/installations/migrate.js +6 -7
- package/dist/lib/installations/shims.d.ts +1 -1
- package/dist/lib/installations/shims.js +23 -13
- package/dist/lib/installations/versions.js +25 -2
- package/dist/lib/linear-autoclose.d.ts +1 -1
- package/dist/lib/linear-autoclose.js +1 -1
- package/dist/lib/loop.d.ts +2 -2
- package/dist/lib/loop.js +1 -1
- package/dist/lib/menubar/download-menubar.d.ts +47 -0
- package/dist/lib/menubar/download-menubar.js +60 -0
- package/dist/lib/menubar/install-menubar.d.ts +44 -7
- package/dist/lib/menubar/install-menubar.js +105 -23
- package/dist/lib/menubar/notify-desktop.d.ts +3 -3
- package/dist/lib/menubar/notify-desktop.js +6 -6
- package/dist/lib/model-tiers.d.ts +1 -1
- package/dist/lib/open-url.d.ts +60 -2
- package/dist/lib/open-url.js +167 -12
- package/dist/lib/project-probe.d.ts +1 -1
- package/dist/lib/project-probe.js +1 -1
- package/dist/lib/project-pull.d.ts +1 -1
- package/dist/lib/project-pull.js +2 -2
- package/dist/lib/projects.d.ts +1 -1
- package/dist/lib/projects.js +1 -1
- package/dist/lib/refresh-coordinator.d.ts +1 -1
- package/dist/lib/refresh-coordinator.js +1 -1
- package/dist/lib/sandbox.js +6 -7
- package/dist/lib/secrets/download-keychain.d.ts +47 -0
- package/dist/lib/secrets/download-keychain.js +70 -0
- package/dist/lib/secrets/install-helper.d.ts +20 -1
- package/dist/lib/secrets/install-helper.js +44 -3
- package/dist/lib/session/active.d.ts +67 -0
- package/dist/lib/session/active.js +86 -2
- package/dist/lib/session/db.d.ts +11 -1
- package/dist/lib/session/db.js +142 -8
- package/dist/lib/session/discover.d.ts +23 -1
- package/dist/lib/session/discover.js +44 -10
- package/dist/lib/session/highlights.d.ts +49 -1
- package/dist/lib/session/highlights.js +95 -0
- package/dist/lib/session/host-link.d.ts +16 -2
- package/dist/lib/session/host-link.js +7 -1
- package/dist/lib/session/parse.js +33 -1
- package/dist/lib/session/prompt.d.ts +35 -0
- package/dist/lib/session/prompt.js +56 -0
- package/dist/lib/session/remote/remote-list.d.ts +9 -0
- package/dist/lib/session/remote/remote-list.js +19 -2
- package/dist/lib/session/remote/watch.d.ts +1 -1
- package/dist/lib/session/remote/watch.js +17 -3
- package/dist/lib/session/render.js +7 -3
- package/dist/lib/session/session-cache.d.ts +53 -0
- package/dist/lib/session/session-cache.js +93 -1
- package/dist/lib/session/state.js +1 -1
- package/dist/lib/session/tool-calls.d.ts +2 -0
- package/dist/lib/session/tool-calls.js +1 -1
- package/dist/lib/session/trajectory-compare.d.ts +47 -0
- package/dist/lib/session/trajectory-compare.js +116 -0
- package/dist/lib/session/trajectory-html.d.ts +19 -0
- package/dist/lib/session/trajectory-html.js +770 -0
- package/dist/lib/session/trajectory-lineage.d.ts +92 -0
- package/dist/lib/session/trajectory-lineage.js +195 -0
- package/dist/lib/session/trajectory-text.d.ts +37 -0
- package/dist/lib/session/trajectory-text.js +284 -0
- package/dist/lib/session/trajectory.d.ts +102 -0
- package/dist/lib/session/trajectory.js +384 -0
- package/dist/lib/session/types.d.ts +15 -0
- package/dist/lib/sha256-asset.d.ts +26 -0
- package/dist/lib/sha256-asset.js +41 -0
- package/dist/lib/share/backend.d.ts +95 -0
- package/dist/lib/share/backend.js +139 -0
- package/dist/lib/share/delete.d.ts +6 -2
- package/dist/lib/share/delete.js +8 -9
- package/dist/lib/share/html.d.ts +22 -0
- package/dist/lib/share/html.js +88 -0
- package/dist/lib/share/provision.d.ts +18 -4
- package/dist/lib/share/provision.js +38 -4
- package/dist/lib/share/publish.d.ts +39 -15
- package/dist/lib/share/publish.js +55 -26
- package/dist/lib/share/worker-template.js +171 -11
- package/dist/lib/sqlite.js +16 -3
- package/dist/lib/ssh-exec.d.ts +65 -0
- package/dist/lib/ssh-exec.js +120 -4
- package/dist/lib/startup/command-registry.d.ts +3 -1
- package/dist/lib/startup/command-registry.js +6 -3
- package/dist/lib/startup/dev-build.js +3 -3
- package/dist/lib/state.d.ts +2 -2
- package/dist/lib/state.js +11 -5
- package/dist/lib/subagents-registry.js +1 -1
- package/dist/lib/teams/agents.js +9 -0
- package/dist/lib/terminal/index.d.ts +1 -1
- package/dist/lib/terminal/inject.d.ts +19 -0
- package/dist/lib/terminal/inject.js +19 -9
- package/dist/lib/terminal/types.d.ts +1 -1
- package/dist/lib/terminal/types.js +1 -1
- package/dist/lib/tmux/orphan-reap.js +1 -1
- package/dist/lib/tmux/session.d.ts +46 -13
- package/dist/lib/tmux/session.js +204 -27
- package/dist/lib/traces/backend.d.ts +29 -0
- package/dist/lib/traces/backend.js +46 -0
- package/dist/lib/traces/classify.d.ts +52 -0
- package/dist/lib/traces/classify.js +72 -0
- package/dist/lib/traces/config.d.ts +10 -0
- package/dist/lib/traces/config.js +2 -0
- package/dist/lib/traces/provision.d.ts +19 -0
- package/dist/lib/traces/provision.js +30 -0
- package/dist/lib/traces/sync.d.ts +150 -0
- package/dist/lib/traces/sync.js +447 -0
- package/dist/lib/traces/worker-template.d.ts +4 -0
- package/dist/lib/traces/worker-template.js +159 -0
- package/dist/lib/types.d.ts +34 -19
- package/dist/lib/types.js +14 -0
- package/dist/lib/usage-backoff.d.ts +2 -1
- package/dist/lib/usage-backoff.js +60 -19
- package/dist/lib/usage-refresh.d.ts +18 -4
- package/dist/lib/usage-refresh.js +68 -19
- package/dist/lib/watchdog/log.d.ts +1 -1
- package/dist/lib/watchdog/log.js +1 -1
- package/dist/lib/watchdog/runner.d.ts +36 -45
- package/dist/lib/watchdog/runner.js +150 -119
- package/dist/lib/watchdog/service.d.ts +0 -1
- package/dist/lib/watchdog/service.js +0 -1
- package/dist/lib/watchdog/watchdog-agent.d.ts +38 -0
- package/dist/lib/watchdog/watchdog-agent.js +59 -0
- package/dist/lib/watchdog/watchdog.d.ts +11 -2
- package/dist/lib/watchdog/watchdog.js +37 -104
- package/dist/lib/workflows.d.ts +1 -1
- package/package.json +5 -7
- package/dist/commands/usage.d.ts +0 -27
- package/dist/commands/usage.js +0 -96
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +0 -24
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +0 -128
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -24
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +0 -123
- package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
- package/dist/lib/usage-fleet.d.ts +0 -32
- package/dist/lib/usage-fleet.js +0 -125
package/dist/lib/open-url.d.ts
CHANGED
|
@@ -1,2 +1,60 @@
|
|
|
1
|
-
/**
|
|
2
|
-
export
|
|
1
|
+
/** Where a "show the human this" call actually landed. */
|
|
2
|
+
export type ShowOutcome = {
|
|
3
|
+
via: 'profile';
|
|
4
|
+
profile: string;
|
|
5
|
+
tabId?: string;
|
|
6
|
+
} | {
|
|
7
|
+
via: 'os';
|
|
8
|
+
command: string;
|
|
9
|
+
} | {
|
|
10
|
+
via: 'none';
|
|
11
|
+
reason: string;
|
|
12
|
+
};
|
|
13
|
+
export interface ShowOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Force the OS default handler, ignoring `browser.viewer`. This is the
|
|
16
|
+
* The programmatic escape hatch, for a caller that must use the user's own
|
|
17
|
+
* browser regardless of configuration. There is deliberately no CLI flag for
|
|
18
|
+
* it: `agents config set browser.viewer os` is the user-facing control.
|
|
19
|
+
*/
|
|
20
|
+
osBrowser?: boolean;
|
|
21
|
+
/** Explicit profile override, ahead of `browser.viewer`. */
|
|
22
|
+
profile?: string;
|
|
23
|
+
/** Injected opener so the OS branch is testable without spawning anything. */
|
|
24
|
+
spawnOpen?: (cmd: string, args: string[]) => boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Launch a detached opener and report whether it actually started.
|
|
28
|
+
*
|
|
29
|
+
* Detection without blocking, which is the whole trick here. A bare detached
|
|
30
|
+
* `spawn` cannot tell success from "xdg-open is not installed" — it does not
|
|
31
|
+
* throw for a missing binary, it emits `error` asynchronously — so the failure
|
|
32
|
+
* branches of every caller were dead. But `spawnSync` is not the answer either:
|
|
33
|
+
* it waits for the child's whole lifetime, and `devices lease` opens a console
|
|
34
|
+
* and then immediately prompts for a pasted key, so a blocking open would stall
|
|
35
|
+
* that prompt behind the browser.
|
|
36
|
+
*
|
|
37
|
+
* Racing `spawn` against `error` gives both: Node emits `spawn` as soon as the
|
|
38
|
+
* child is successfully created (measured: 1ms, and it does NOT wait for exit),
|
|
39
|
+
* and `error` for ENOENT. We unref on success so the opener outlives us.
|
|
40
|
+
*/
|
|
41
|
+
export declare function trySpawn(cmd: string, args: string[]): Promise<boolean>;
|
|
42
|
+
/**
|
|
43
|
+
* Decide the viewer for this call. Exported for its own test — this is the one
|
|
44
|
+
* place the policy lives.
|
|
45
|
+
*
|
|
46
|
+
* Every fall back to the OS handler prints one stderr line naming why. A silent
|
|
47
|
+
* downgrade here is what made the original bug invisible for so long: the user
|
|
48
|
+
* had configured a profile and had no way to see that it was being ignored.
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveViewer(opts?: ShowOptions): Promise<'os' | {
|
|
51
|
+
profile: string;
|
|
52
|
+
}>;
|
|
53
|
+
/** Show a URL to the human at this machine. Never throws. */
|
|
54
|
+
export declare function showUrl(url: string, opts?: ShowOptions): Promise<ShowOutcome>;
|
|
55
|
+
/**
|
|
56
|
+
* Show a local file. Browser-renderable kinds go through {@link showUrl}; every
|
|
57
|
+
* other kind goes to the OS default APP, which for a screenshot or a recording
|
|
58
|
+
* is the right viewer.
|
|
59
|
+
*/
|
|
60
|
+
export declare function showFile(filePath: string, opts?: ShowOptions): Promise<ShowOutcome>;
|
package/dist/lib/open-url.js
CHANGED
|
@@ -1,19 +1,174 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* The one place that decides WHERE a URL or file is shown to the human.
|
|
3
|
+
*
|
|
4
|
+
* Two browsers exist on a machine like this: the OS default handler, and the
|
|
5
|
+
* profile `agents browser` drives. They are not interchangeable. The configured
|
|
6
|
+
* profile is where the fleet's logins accumulate — `agents browser profiles
|
|
7
|
+
* logins` lists them — so a page opened there is a page the user is already
|
|
8
|
+
* signed in for, and a login acquired there is inherited by every later agent.
|
|
9
|
+
* The OS handler has none of that.
|
|
10
|
+
*
|
|
11
|
+
* Before this seam existed, `agents browser navigate` honoured the configured
|
|
12
|
+
* profile and nothing else did: `fleet login`, `devices lease`, `feedback`, and
|
|
13
|
+
* the browser-session artifact opener each shelled straight to `open`/`xdg-open`,
|
|
14
|
+
* so every one of them landed in whatever the OS handler happened to be. This
|
|
15
|
+
* module replaces all of those call sites; do not add a sixth raw `open`.
|
|
16
|
+
*
|
|
17
|
+
* Never throws. A viewer that cannot be reached degrades to the OS handler with
|
|
18
|
+
* one stderr line naming the reason, and a total failure returns `via: 'none'`
|
|
19
|
+
* so the caller can print the URL rather than silently doing nothing.
|
|
5
20
|
*/
|
|
6
21
|
import { spawn } from 'child_process';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
22
|
+
import * as path from 'path';
|
|
23
|
+
import { pathToFileURL } from 'url';
|
|
24
|
+
/**
|
|
25
|
+
* Extensions a CDP tab renders at least as well as the OS default app.
|
|
26
|
+
*
|
|
27
|
+
* Deliberately narrow. `sessions-list.ts` EXT_KIND covers .png/.jpg/.webp/.pdf/
|
|
28
|
+
* .webm, and for those Preview and QuickTime are the better viewer — routing a
|
|
29
|
+
* screenshot into a browser tab is a downgrade, not a fix.
|
|
30
|
+
*/
|
|
31
|
+
const BROWSER_RENDERABLE = new Set(['.html', '.htm', '.svg', '.xhtml']);
|
|
32
|
+
async function osOpen(target, spawnOpen) {
|
|
33
|
+
const candidates = process.platform === 'darwin'
|
|
34
|
+
? [['open', [target]]]
|
|
35
|
+
: process.platform === 'win32'
|
|
36
|
+
? // `start` treats a lone quoted first argument as the window TITLE, so the
|
|
37
|
+
// empty title placeholder is required before the target. The three copies
|
|
38
|
+
// of this that predated the seam disagreed on it ('' vs '""').
|
|
39
|
+
[['cmd', ['/c', 'start', '', target]]]
|
|
40
|
+
: [
|
|
41
|
+
['xdg-open', [target]],
|
|
42
|
+
['gnome-open', [target]],
|
|
43
|
+
];
|
|
44
|
+
for (const [cmd, args] of candidates) {
|
|
45
|
+
if (spawnOpen) {
|
|
46
|
+
if (spawnOpen(cmd, args))
|
|
47
|
+
return { via: 'os', command: cmd };
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (await trySpawn(cmd, args))
|
|
51
|
+
return { via: 'os', command: cmd };
|
|
52
|
+
}
|
|
53
|
+
return { via: 'none', reason: 'no working OS opener on this platform' };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Launch a detached opener and report whether it actually started.
|
|
57
|
+
*
|
|
58
|
+
* Detection without blocking, which is the whole trick here. A bare detached
|
|
59
|
+
* `spawn` cannot tell success from "xdg-open is not installed" — it does not
|
|
60
|
+
* throw for a missing binary, it emits `error` asynchronously — so the failure
|
|
61
|
+
* branches of every caller were dead. But `spawnSync` is not the answer either:
|
|
62
|
+
* it waits for the child's whole lifetime, and `devices lease` opens a console
|
|
63
|
+
* and then immediately prompts for a pasted key, so a blocking open would stall
|
|
64
|
+
* that prompt behind the browser.
|
|
65
|
+
*
|
|
66
|
+
* Racing `spawn` against `error` gives both: Node emits `spawn` as soon as the
|
|
67
|
+
* child is successfully created (measured: 1ms, and it does NOT wait for exit),
|
|
68
|
+
* and `error` for ENOENT. We unref on success so the opener outlives us.
|
|
69
|
+
*/
|
|
70
|
+
export function trySpawn(cmd, args) {
|
|
71
|
+
return new Promise((resolve) => {
|
|
72
|
+
let settled = false;
|
|
73
|
+
const done = (ok) => {
|
|
74
|
+
if (settled)
|
|
75
|
+
return;
|
|
76
|
+
settled = true;
|
|
77
|
+
resolve(ok);
|
|
78
|
+
};
|
|
79
|
+
try {
|
|
80
|
+
const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
|
|
81
|
+
child.on('error', () => done(false));
|
|
82
|
+
child.on('spawn', () => {
|
|
83
|
+
child.unref();
|
|
84
|
+
done(true);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
done(false);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Decide the viewer for this call. Exported for its own test — this is the one
|
|
94
|
+
* place the policy lives.
|
|
95
|
+
*
|
|
96
|
+
* Every fall back to the OS handler prints one stderr line naming why. A silent
|
|
97
|
+
* downgrade here is what made the original bug invisible for so long: the user
|
|
98
|
+
* had configured a profile and had no way to see that it was being ignored.
|
|
99
|
+
*/
|
|
100
|
+
export async function resolveViewer(opts = {}) {
|
|
101
|
+
if (opts.osBrowser)
|
|
102
|
+
return 'os';
|
|
103
|
+
const { getConfigValue } = await import('./device-config.js');
|
|
104
|
+
const configured = opts.profile ?? (getConfigValue('browser.viewer').value || undefined);
|
|
105
|
+
// Unset means "follow the profile agents drive" — the whole point is that a
|
|
106
|
+
// machine with a configured browser stops leaking pages to the OS handler.
|
|
107
|
+
const { getConfiguredDefaultProfileName, resolveProfileRef, getProfile, isProfileLaunchableHere } = await import('./browser/profiles.js');
|
|
108
|
+
const name = configured ?? getConfiguredDefaultProfileName();
|
|
109
|
+
if (!name)
|
|
110
|
+
return 'os';
|
|
111
|
+
if (name === 'os')
|
|
112
|
+
return 'os';
|
|
113
|
+
let resolved;
|
|
11
114
|
try {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
115
|
+
resolved = await resolveProfileRef(name);
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
console.error(`[viewer] ${name}: ${err instanceof Error ? err.message : String(err)} — using the OS browser.`);
|
|
119
|
+
return 'os';
|
|
120
|
+
}
|
|
121
|
+
if (!resolved) {
|
|
122
|
+
console.error(`[viewer] "${name}" does not resolve to a profile — using the OS browser.`);
|
|
123
|
+
return 'os';
|
|
15
124
|
}
|
|
16
|
-
|
|
17
|
-
|
|
125
|
+
const profile = await getProfile(resolved);
|
|
126
|
+
if (!profile) {
|
|
127
|
+
console.error(`[viewer] profile "${resolved}" is not configured — using the OS browser.`);
|
|
128
|
+
return 'os';
|
|
129
|
+
}
|
|
130
|
+
if (profile.browser === 'arc') {
|
|
131
|
+
// Arc exposes no CDP page targets and crashes on tab creation, so it can be
|
|
132
|
+
// a configured profile but never a drivable viewer.
|
|
133
|
+
console.error(`[viewer] "${resolved}" is Arc, which cannot be driven — using the OS browser.`);
|
|
134
|
+
return 'os';
|
|
135
|
+
}
|
|
136
|
+
if (!isProfileLaunchableHere(profile)) {
|
|
137
|
+
console.error(`[viewer] "${resolved}" cannot launch on this machine — using the OS browser.`);
|
|
138
|
+
return 'os';
|
|
139
|
+
}
|
|
140
|
+
return { profile: resolved };
|
|
141
|
+
}
|
|
142
|
+
/** Show a URL to the human at this machine. Never throws. */
|
|
143
|
+
export async function showUrl(url, opts = {}) {
|
|
144
|
+
const viewer = await resolveViewer(opts);
|
|
145
|
+
if (viewer === 'os')
|
|
146
|
+
return osOpen(url, opts.spawnOpen);
|
|
147
|
+
try {
|
|
148
|
+
const { sendIPCRequest } = await import('./browser/ipc.js');
|
|
149
|
+
// Deliberately does NOT auto-start the browser daemon. Showing a page is a
|
|
150
|
+
// side errand — `devices lease` opens a console and immediately prompts for
|
|
151
|
+
// a pasted key — so blocking that on a daemon cold start is a surprising
|
|
152
|
+
// multi-second stall. Daemon already up: use the viewer. Not up: the OS
|
|
153
|
+
// handler is the fast, correct answer.
|
|
154
|
+
const response = await sendIPCRequest({ action: 'show', url, profile: viewer.profile }, { autoStartDaemon: false });
|
|
155
|
+
if (response.ok)
|
|
156
|
+
return { via: 'profile', profile: viewer.profile, tabId: response.tabId };
|
|
157
|
+
console.error(`[viewer] ${viewer.profile}: ${response.error} — using the OS browser.`);
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
console.error(`[viewer] ${viewer.profile}: ${err instanceof Error ? err.message : String(err)} — using the OS browser.`);
|
|
161
|
+
}
|
|
162
|
+
return osOpen(url, opts.spawnOpen);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Show a local file. Browser-renderable kinds go through {@link showUrl}; every
|
|
166
|
+
* other kind goes to the OS default APP, which for a screenshot or a recording
|
|
167
|
+
* is the right viewer.
|
|
168
|
+
*/
|
|
169
|
+
export async function showFile(filePath, opts = {}) {
|
|
170
|
+
if (!BROWSER_RENDERABLE.has(path.extname(filePath).toLowerCase())) {
|
|
171
|
+
return osOpen(filePath, opts.spawnOpen);
|
|
18
172
|
}
|
|
173
|
+
return showUrl(pathToFileURL(filePath).href, opts);
|
|
19
174
|
}
|
|
@@ -58,7 +58,7 @@ export declare function probeProjectWorkspaces(paths: string[]): RepoWorkspaceSt
|
|
|
58
58
|
*/
|
|
59
59
|
export declare function workspaceTargetsForDef(def: ProjectDef): string[];
|
|
60
60
|
/**
|
|
61
|
-
* Parse a peer's `projects probe
|
|
61
|
+
* Parse a peer's `projects probe` stdout, tagging each row with the
|
|
62
62
|
* machine that answered. Defensive against version skew / partial output, the
|
|
63
63
|
* same boundary contract as `parseRemoteActive`: non-JSON or a non-array
|
|
64
64
|
* yields `[]`, and rows without a `path`/`present` core are dropped.
|
|
@@ -96,7 +96,7 @@ export function workspaceTargetsForDef(def) {
|
|
|
96
96
|
return projectProbeTargets(def);
|
|
97
97
|
}
|
|
98
98
|
/**
|
|
99
|
-
* Parse a peer's `projects probe
|
|
99
|
+
* Parse a peer's `projects probe` stdout, tagging each row with the
|
|
100
100
|
* machine that answered. Defensive against version skew / partial output, the
|
|
101
101
|
* same boundary contract as `parseRemoteActive`: non-JSON or a non-array
|
|
102
102
|
* yields `[]`, and rows without a `path`/`present` core are dropped.
|
|
@@ -93,7 +93,7 @@ export declare function decodePullTargets(raw: string): ProjectRepoTarget[];
|
|
|
93
93
|
export declare function pullProjectTargets(targets: ProjectRepoTarget[], host?: string): Promise<ProjectPullResult[]>;
|
|
94
94
|
export declare function buildPullEnvelope(results: ProjectPullResult[], targets: ProjectRepoTarget[]): ProjectPullEnvelope;
|
|
95
95
|
/**
|
|
96
|
-
* Parse a peer's `projects pull-local
|
|
96
|
+
* Parse a peer's `projects pull-local` stdout. Fails CLOSED **and
|
|
97
97
|
* LOUD**: any structural anomaly (wrong schema version, wrong kind, machine
|
|
98
98
|
* mismatch, fingerprint mismatch, non-array results, malformed rows) returns
|
|
99
99
|
* `{ items: [], valid: false }` rather than silently accepting a partial or
|
package/dist/lib/project-pull.js
CHANGED
|
@@ -66,7 +66,7 @@ export function encodePullTargets(targets) {
|
|
|
66
66
|
* test can exercise the real caller-side arguments rather than a retyped copy.
|
|
67
67
|
*/
|
|
68
68
|
export function pullLocalArgs(targets) {
|
|
69
|
-
return ['projects', 'pull-local', '--
|
|
69
|
+
return ['projects', 'pull-local', '--targets', encodePullTargets(targets)];
|
|
70
70
|
}
|
|
71
71
|
/**
|
|
72
72
|
* Decode the `--targets` argument back into targets on the peer. THROWS on any
|
|
@@ -226,7 +226,7 @@ export function buildPullEnvelope(results, targets) {
|
|
|
226
226
|
// Envelope parser (fail-closed — every validation failure returns [])
|
|
227
227
|
// ---------------------------------------------------------------------------
|
|
228
228
|
/**
|
|
229
|
-
* Parse a peer's `projects pull-local
|
|
229
|
+
* Parse a peer's `projects pull-local` stdout. Fails CLOSED **and
|
|
230
230
|
* LOUD**: any structural anomaly (wrong schema version, wrong kind, machine
|
|
231
231
|
* mismatch, fingerprint mismatch, non-array results, malformed rows) returns
|
|
232
232
|
* `{ items: [], valid: false }` rather than silently accepting a partial or
|
package/dist/lib/projects.d.ts
CHANGED
|
@@ -204,7 +204,7 @@ export declare function projectDirsAbs(def: ProjectDef, opts: {
|
|
|
204
204
|
* with `expandLocalHome` and resolved, so this matches sessions whose cwd shares
|
|
205
205
|
* this machine's home layout. A session recorded on a different-home machine
|
|
206
206
|
* (`/Users/x/…` vs `/home/x/…`) will not match until the fleet-wide,
|
|
207
|
-
* home-relative variant lands (see
|
|
207
|
+
* home-relative variant lands (see docs/concepts.md).
|
|
208
208
|
*/
|
|
209
209
|
export declare function projectNameForCwd(cwd: string | undefined, defs: ProjectDef[]): string | undefined;
|
|
210
210
|
/**
|
package/dist/lib/projects.js
CHANGED
|
@@ -429,7 +429,7 @@ function isUnder(child, parent) {
|
|
|
429
429
|
* with `expandLocalHome` and resolved, so this matches sessions whose cwd shares
|
|
430
430
|
* this machine's home layout. A session recorded on a different-home machine
|
|
431
431
|
* (`/Users/x/…` vs `/home/x/…`) will not match until the fleet-wide,
|
|
432
|
-
* home-relative variant lands (see
|
|
432
|
+
* home-relative variant lands (see docs/concepts.md).
|
|
433
433
|
*/
|
|
434
434
|
export function projectNameForCwd(cwd, defs) {
|
|
435
435
|
if (!cwd)
|
|
@@ -15,7 +15,7 @@ export interface RefreshLeaseOptions<T> {
|
|
|
15
15
|
* Serialize refresh work across every agents-cli process on this device.
|
|
16
16
|
*
|
|
17
17
|
* In-process promise maps only protect one Node process. Factory, the daemon,
|
|
18
|
-
*
|
|
18
|
+
* and `agents view` are separate processes, so they need an
|
|
19
19
|
* OS-visible lease. The result is re-read after lock acquisition: a waiter
|
|
20
20
|
* consumes the winner's publication and never calls the provider a second
|
|
21
21
|
* time. `proper-lockfile` supplies heartbeat + stale-owner recovery while an
|
|
@@ -18,7 +18,7 @@ function lockTarget(scope, key) {
|
|
|
18
18
|
* Serialize refresh work across every agents-cli process on this device.
|
|
19
19
|
*
|
|
20
20
|
* In-process promise maps only protect one Node process. Factory, the daemon,
|
|
21
|
-
*
|
|
21
|
+
* and `agents view` are separate processes, so they need an
|
|
22
22
|
* OS-visible lease. The result is re-read after lock acquisition: a waiter
|
|
23
23
|
* consumes the winner's publication and never calls the provider a second
|
|
24
24
|
* time. `proper-lockfile` supplies heartbeat + stale-owner recovery while an
|
package/dist/lib/sandbox.js
CHANGED
|
@@ -209,11 +209,11 @@ export function assertSandboxForwardsHostGhAuth(spawnEnv) {
|
|
|
209
209
|
}
|
|
210
210
|
/** Link this host's Cursor login and CLI config into the disposable overlay. */
|
|
211
211
|
export function generateCursorConfig(overlayHome) {
|
|
212
|
-
const
|
|
213
|
-
const realAuth = path.join(
|
|
212
|
+
const realCursorDir = path.join(process.env.AGENTS_REAL_HOME || resolveRealHome(), '.cursor');
|
|
213
|
+
const realAuth = path.join(realCursorDir, 'auth.json');
|
|
214
214
|
if (!fs.existsSync(realAuth))
|
|
215
215
|
return;
|
|
216
|
-
const overlayCursorDir = path.join(overlayHome, '.
|
|
216
|
+
const overlayCursorDir = path.join(overlayHome, '.cursor');
|
|
217
217
|
fs.mkdirSync(overlayCursorDir, { recursive: true });
|
|
218
218
|
const overlayAuth = path.join(overlayCursorDir, 'auth.json');
|
|
219
219
|
if (process.platform === 'win32') {
|
|
@@ -227,10 +227,9 @@ export function generateCursorConfig(overlayHome) {
|
|
|
227
227
|
else {
|
|
228
228
|
fs.symlinkSync(realAuth, overlayAuth);
|
|
229
229
|
}
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
const realCliConfig = path.join(resolveRealHome(), '.cursor', 'cli-config.json');
|
|
230
|
+
// cli-config.json carries account identity alongside the file-store token, so
|
|
231
|
+
// it must remain linked rather than copied.
|
|
232
|
+
const realCliConfig = path.join(realCursorDir, 'cli-config.json');
|
|
234
233
|
if (fs.existsSync(realCliConfig)) {
|
|
235
234
|
const overlayCliConfig = path.join(overlayCursorDir, 'cli-config.json');
|
|
236
235
|
try {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On-demand download + verification of the macOS keychain broker helper
|
|
3
|
+
* ("Agents CLI.app").
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the ComputerHelper / MenubarHelper download model
|
|
6
|
+
* (`../helper-download.ts`): the helper ships as a signed + notarized `.app`
|
|
7
|
+
* zipped as a GitHub release asset on the helper's own `keychain/v<x.y.z>` tag,
|
|
8
|
+
* NOT the CLI's tag (see `helper-versions.ts`). A fresh `npm i -g`
|
|
9
|
+
* machine whose tarball lacks a bundled copy fetches the asset for the running
|
|
10
|
+
* CLI version, verifies its sha256 + code signature before install.
|
|
11
|
+
*
|
|
12
|
+
* Unlike the menu-bar helper, the keychain helper pins NO designated
|
|
13
|
+
* requirement (no `expectedBundleId`). The menu-bar `expectedBundleId` exists
|
|
14
|
+
* because macOS keys the Accessibility (TCC) grant to the bundle's designated
|
|
15
|
+
* requirement, so a substituted bundle whose DR does not pin (id, team) would
|
|
16
|
+
* silently revoke that upgrade-stable grant. The keychain helper has no such
|
|
17
|
+
* DR-keyed grant to protect: its items are gated by the access-group
|
|
18
|
+
* entitlement (`keychain-helper.swift` → `kSecAttrAccessGroup`, the
|
|
19
|
+
* `keychain-access-groups` entitlement) plus a biometry `SecAccessControl`
|
|
20
|
+
* (user presence) — NOT a `SecTrustedApplication` ACL keyed to a bundle-id/DR.
|
|
21
|
+
* The release-time gate `scripts/verify-keychain-helper.sh` correspondingly
|
|
22
|
+
* pins the executable's sha256, not a designated requirement. So Team +
|
|
23
|
+
* notarization is the verification boundary here, exactly like ComputerHelper.
|
|
24
|
+
*/
|
|
25
|
+
import { type HelperSpec } from '../helper-download.js';
|
|
26
|
+
/** The zipped `.app` release asset name (the bundle name has a space). */
|
|
27
|
+
export declare const KEYCHAIN_HELPER_ASSET = "Agents_CLI.app.zip";
|
|
28
|
+
/** The bundle directory name once extracted. */
|
|
29
|
+
export declare const KEYCHAIN_HELPER_APP_NAME = "Agents CLI.app";
|
|
30
|
+
/** Keychain-helper identity + verification policy — Team + notarization only,
|
|
31
|
+
* no DR pin (see docblock). */
|
|
32
|
+
export declare const KEYCHAIN_HELPER_SPEC: HelperSpec;
|
|
33
|
+
/** Release-asset URLs for the keychain helper zip + its checksum at `v<version>`. */
|
|
34
|
+
export declare function keychainHelperAssetUrls(version: string): {
|
|
35
|
+
zip: string;
|
|
36
|
+
sha256: string;
|
|
37
|
+
};
|
|
38
|
+
/** Cache dir for the downloaded keychain helper, one subdir per release tag. */
|
|
39
|
+
export declare function keychainHelperCacheDir(version: string): string;
|
|
40
|
+
/** Verify a keychain helper `.app`: codesign + Team + notarization (no DR pin). */
|
|
41
|
+
export declare function verifyKeychainHelper(appPath: string): void;
|
|
42
|
+
/**
|
|
43
|
+
* Download the keychain helper release asset for `version`, verify sha256 +
|
|
44
|
+
* signature, and return the path to the extracted `Agents CLI.app` in the
|
|
45
|
+
* cache. A missing asset is a hard error naming the exact tag.
|
|
46
|
+
*/
|
|
47
|
+
export declare function downloadKeychainHelperApp(version: string): Promise<string>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On-demand download + verification of the macOS keychain broker helper
|
|
3
|
+
* ("Agents CLI.app").
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the ComputerHelper / MenubarHelper download model
|
|
6
|
+
* (`../helper-download.ts`): the helper ships as a signed + notarized `.app`
|
|
7
|
+
* zipped as a GitHub release asset on the helper's own `keychain/v<x.y.z>` tag,
|
|
8
|
+
* NOT the CLI's tag (see `helper-versions.ts`). A fresh `npm i -g`
|
|
9
|
+
* machine whose tarball lacks a bundled copy fetches the asset for the running
|
|
10
|
+
* CLI version, verifies its sha256 + code signature before install.
|
|
11
|
+
*
|
|
12
|
+
* Unlike the menu-bar helper, the keychain helper pins NO designated
|
|
13
|
+
* requirement (no `expectedBundleId`). The menu-bar `expectedBundleId` exists
|
|
14
|
+
* because macOS keys the Accessibility (TCC) grant to the bundle's designated
|
|
15
|
+
* requirement, so a substituted bundle whose DR does not pin (id, team) would
|
|
16
|
+
* silently revoke that upgrade-stable grant. The keychain helper has no such
|
|
17
|
+
* DR-keyed grant to protect: its items are gated by the access-group
|
|
18
|
+
* entitlement (`keychain-helper.swift` → `kSecAttrAccessGroup`, the
|
|
19
|
+
* `keychain-access-groups` entitlement) plus a biometry `SecAccessControl`
|
|
20
|
+
* (user presence) — NOT a `SecTrustedApplication` ACL keyed to a bundle-id/DR.
|
|
21
|
+
* The release-time gate `scripts/verify-keychain-helper.sh` correspondingly
|
|
22
|
+
* pins the executable's sha256, not a designated requirement. So Team +
|
|
23
|
+
* notarization is the verification boundary here, exactly like ComputerHelper.
|
|
24
|
+
*/
|
|
25
|
+
import { EXPECTED_TEAM_ID, downloadHelperApp, helperAssetUrls, helperCacheDir, verifyHelperApp, } from '../helper-download.js';
|
|
26
|
+
/** The zipped `.app` release asset name (the bundle name has a space). */
|
|
27
|
+
// Underscore, and NOT a space. GitHub rewrites a space in a release-asset name
|
|
28
|
+
// to a dot on upload, so an asset staged as `Agents CLI.app.zip` is SERVED as
|
|
29
|
+
// `Agents.CLI.app.zip` and a client asking for the spaced name 404s forever --
|
|
30
|
+
// which is exactly what shipped. The fix is to CHOOSE a name GitHub leaves
|
|
31
|
+
// alone rather than to mirror its rewrite: `Agents.CLI.app.zip` would work by
|
|
32
|
+
// matching an artifact of that behavior, and would silently break if the
|
|
33
|
+
// normalization rule ever changed. Underscores are preserved verbatim (verified
|
|
34
|
+
// against a real upload).
|
|
35
|
+
//
|
|
36
|
+
// The bundle DIRECTORY inside the zip keeps its space (`appName` below) -- that
|
|
37
|
+
// is the on-disk bundle name macOS and the TCC grant key on, and it is unchanged.
|
|
38
|
+
export const KEYCHAIN_HELPER_ASSET = 'Agents_CLI.app.zip';
|
|
39
|
+
/** The bundle directory name once extracted. */
|
|
40
|
+
export const KEYCHAIN_HELPER_APP_NAME = 'Agents CLI.app';
|
|
41
|
+
/** Keychain-helper identity + verification policy — Team + notarization only,
|
|
42
|
+
* no DR pin (see docblock). */
|
|
43
|
+
export const KEYCHAIN_HELPER_SPEC = {
|
|
44
|
+
helper: 'keychain',
|
|
45
|
+
assetName: KEYCHAIN_HELPER_ASSET,
|
|
46
|
+
appName: KEYCHAIN_HELPER_APP_NAME,
|
|
47
|
+
cacheSubdir: ['secrets', 'mac-helper'],
|
|
48
|
+
expectedTeamId: EXPECTED_TEAM_ID,
|
|
49
|
+
localBuildHint: 'cli/scripts/build-keychain-helper.sh',
|
|
50
|
+
};
|
|
51
|
+
/** Release-asset URLs for the keychain helper zip + its checksum at `v<version>`. */
|
|
52
|
+
export function keychainHelperAssetUrls(version) {
|
|
53
|
+
return helperAssetUrls(KEYCHAIN_HELPER_SPEC, version);
|
|
54
|
+
}
|
|
55
|
+
/** Cache dir for the downloaded keychain helper, one subdir per release tag. */
|
|
56
|
+
export function keychainHelperCacheDir(version) {
|
|
57
|
+
return helperCacheDir(KEYCHAIN_HELPER_SPEC, version);
|
|
58
|
+
}
|
|
59
|
+
/** Verify a keychain helper `.app`: codesign + Team + notarization (no DR pin). */
|
|
60
|
+
export function verifyKeychainHelper(appPath) {
|
|
61
|
+
verifyHelperApp(appPath, KEYCHAIN_HELPER_SPEC);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Download the keychain helper release asset for `version`, verify sha256 +
|
|
65
|
+
* signature, and return the path to the extracted `Agents CLI.app` in the
|
|
66
|
+
* cache. A missing asset is a hard error naming the exact tag.
|
|
67
|
+
*/
|
|
68
|
+
export function downloadKeychainHelperApp(version) {
|
|
69
|
+
return downloadHelperApp(KEYCHAIN_HELPER_SPEC, version);
|
|
70
|
+
}
|
|
@@ -29,6 +29,20 @@ export declare function setInstallRootForTest(dir: string | null): string | null
|
|
|
29
29
|
export declare function ensureKeychainHelperInstalled(opts?: {
|
|
30
30
|
forceReinstall?: boolean;
|
|
31
31
|
}): void;
|
|
32
|
+
/**
|
|
33
|
+
* ASYNC, download-capable install — the EXPLICIT onboarding/upgrade path
|
|
34
|
+
* (`agents setup secrets`, the bootstrap upgrade self-heal). When a bundled or
|
|
35
|
+
* locally-built `.app` ships (the common case, and always true this release
|
|
36
|
+
* stage), it installs that copy with NO network. When the tarball lacks the
|
|
37
|
+
* bundle (a fresh `npm i -g` whose tarball dropped it), it fetches the signed +
|
|
38
|
+
* notarized release asset for the running CLI version — sha256 + codesign +
|
|
39
|
+
* Team + notarization verified before install — and installs from the cached
|
|
40
|
+
* copy. The sync `getKeychainHelperPath()` never routes here: it stays sync +
|
|
41
|
+
* network-free and fails loud toward this command when no source exists.
|
|
42
|
+
*/
|
|
43
|
+
export declare function ensureKeychainHelperInstalledAsync(opts?: {
|
|
44
|
+
forceReinstall?: boolean;
|
|
45
|
+
}): Promise<void>;
|
|
32
46
|
/**
|
|
33
47
|
* Return the absolute path to the helper executable. If the installed bundle
|
|
34
48
|
* is missing, or is stale relative to the bundled source helper, performs a
|
|
@@ -37,7 +51,12 @@ export declare function ensureKeychainHelperInstalled(opts?: {
|
|
|
37
51
|
* `npm i -g`, so this call site is the only one every machine is guaranteed to
|
|
38
52
|
* pass through.
|
|
39
53
|
*
|
|
40
|
-
*
|
|
54
|
+
* SYNC and network-free by design: this is the secrets hot path, called by
|
|
55
|
+
* every keychain op. It never downloads. When no bundled/local source exists
|
|
56
|
+
* (a tarball that dropped the bundle), the reinstall's `sourceAppPath()` throws
|
|
57
|
+
* loud, directing the user to `agents setup secrets` — the explicit, async,
|
|
58
|
+
* download-capable path (`ensureKeychainHelperInstalledAsync`). Throws on
|
|
59
|
+
* non-darwin.
|
|
41
60
|
*/
|
|
42
61
|
export declare function getKeychainHelperPath(): string;
|
|
43
62
|
/** Diagnostic snapshot of the installed Keychain helper. */
|
|
@@ -19,6 +19,8 @@ import * as fs from 'fs';
|
|
|
19
19
|
import * as os from 'os';
|
|
20
20
|
import * as path from 'path';
|
|
21
21
|
import { copyAppBundle, withInstallLock } from '../app-bundle-install.js';
|
|
22
|
+
import { downloadKeychainHelperApp } from './download-keychain.js';
|
|
23
|
+
import { helperFloor } from '../helper-versions.js';
|
|
22
24
|
const APP_BUNDLE_NAME = 'Agents CLI.app';
|
|
23
25
|
const INSTALL_DIR_NAME = 'agents-cli';
|
|
24
26
|
let installRootOverride = null;
|
|
@@ -60,7 +62,18 @@ function sourceAppPath() {
|
|
|
60
62
|
return c;
|
|
61
63
|
}
|
|
62
64
|
throw new Error(`Source ${APP_BUNDLE_NAME} not found. Looked in:\n ${candidates.join('\n ')}\n` +
|
|
63
|
-
'The npm package
|
|
65
|
+
'The npm package shipped without the bundled keychain helper. Run `agents setup secrets` to ' +
|
|
66
|
+
'download the signed helper for this version, or reinstall agents-cli.');
|
|
67
|
+
}
|
|
68
|
+
/** The bundled/local source `.app`, or null when absent (no throw) — the async
|
|
69
|
+
* download path resolves the missing bundle instead of failing. */
|
|
70
|
+
function bundledSourceAppPathOrNull() {
|
|
71
|
+
try {
|
|
72
|
+
return sourceAppPath();
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
64
77
|
}
|
|
65
78
|
function assertDarwin() {
|
|
66
79
|
if (process.platform !== 'darwin') {
|
|
@@ -122,6 +135,30 @@ function spctlAssess(appPath) {
|
|
|
122
135
|
*/
|
|
123
136
|
export function ensureKeychainHelperInstalled(opts = {}) {
|
|
124
137
|
assertDarwin();
|
|
138
|
+
installKeychainHelperFromSource(sourceAppPath(), opts);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* ASYNC, download-capable install — the EXPLICIT onboarding/upgrade path
|
|
142
|
+
* (`agents setup secrets`, the bootstrap upgrade self-heal). When a bundled or
|
|
143
|
+
* locally-built `.app` ships (the common case, and always true this release
|
|
144
|
+
* stage), it installs that copy with NO network. When the tarball lacks the
|
|
145
|
+
* bundle (a fresh `npm i -g` whose tarball dropped it), it fetches the signed +
|
|
146
|
+
* notarized release asset for the running CLI version — sha256 + codesign +
|
|
147
|
+
* Team + notarization verified before install — and installs from the cached
|
|
148
|
+
* copy. The sync `getKeychainHelperPath()` never routes here: it stays sync +
|
|
149
|
+
* network-free and fails loud toward this command when no source exists.
|
|
150
|
+
*/
|
|
151
|
+
export async function ensureKeychainHelperInstalledAsync(opts = {}) {
|
|
152
|
+
assertDarwin();
|
|
153
|
+
const bundled = bundledSourceAppPathOrNull();
|
|
154
|
+
const src = bundled ?? (await downloadKeychainHelperApp(helperFloor('keychain')));
|
|
155
|
+
installKeychainHelperFromSource(src, opts);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Copy `src` (a verified `.app` bundle) to the stable user path, then verify.
|
|
159
|
+
* Shared by the sync (bundled-source) and async (download-capable) installers.
|
|
160
|
+
*/
|
|
161
|
+
function installKeychainHelperFromSource(src, opts = {}) {
|
|
125
162
|
const dest = installedAppPath();
|
|
126
163
|
const upToDate = () => fs.existsSync(dest) && codesignVerify(dest).ok && !installedHelperIsStale();
|
|
127
164
|
// Fast path: already installed, valid, and current — no lock, no copy.
|
|
@@ -134,7 +171,6 @@ export function ensureKeychainHelperInstalled(opts = {}) {
|
|
|
134
171
|
withInstallLock(dest, (heartbeat) => {
|
|
135
172
|
if (!opts.forceReinstall && upToDate())
|
|
136
173
|
return;
|
|
137
|
-
const src = sourceAppPath();
|
|
138
174
|
copyAppBundle(src, dest);
|
|
139
175
|
heartbeat(); // cp -R done; keep the lock fresh across the codesign/spctl spawns
|
|
140
176
|
const verify = codesignVerify(dest);
|
|
@@ -160,7 +196,12 @@ export function ensureKeychainHelperInstalled(opts = {}) {
|
|
|
160
196
|
* `npm i -g`, so this call site is the only one every machine is guaranteed to
|
|
161
197
|
* pass through.
|
|
162
198
|
*
|
|
163
|
-
*
|
|
199
|
+
* SYNC and network-free by design: this is the secrets hot path, called by
|
|
200
|
+
* every keychain op. It never downloads. When no bundled/local source exists
|
|
201
|
+
* (a tarball that dropped the bundle), the reinstall's `sourceAppPath()` throws
|
|
202
|
+
* loud, directing the user to `agents setup secrets` — the explicit, async,
|
|
203
|
+
* download-capable path (`ensureKeychainHelperInstalledAsync`). Throws on
|
|
204
|
+
* non-darwin.
|
|
164
205
|
*/
|
|
165
206
|
export function getKeychainHelperPath() {
|
|
166
207
|
assertDarwin();
|