@phnx-labs/agents-cli 1.22.47 → 1.22.50
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 +503 -17
- package/README.md +76 -32
- package/dist/bootstrap.js +4 -2
- package/dist/cli/command-registry.d.ts +1 -0
- package/dist/cli/command-registry.js +2 -0
- package/dist/commands/accounts.d.ts +58 -0
- package/dist/commands/accounts.js +201 -39
- package/dist/commands/artifacts.js +7 -6
- package/dist/commands/attach.js +1 -1
- package/dist/commands/browser.js +501 -103
- package/dist/commands/config.d.ts +0 -1
- package/dist/commands/config.js +0 -20
- package/dist/commands/daemon-test-harness.d.ts +42 -0
- package/dist/commands/daemon-test-harness.js +90 -0
- package/dist/commands/daemon.d.ts +21 -0
- package/dist/commands/daemon.js +147 -12
- package/dist/commands/device-config-test-harness.d.ts +11 -0
- package/dist/commands/device-config-test-harness.js +59 -0
- package/dist/commands/exec.js +76 -16
- 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/inspect.js +2 -2
- package/dist/commands/lease.d.ts +1 -1
- package/dist/commands/lease.js +4 -3
- 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 +3 -2
- package/dist/commands/run-broadcast.d.ts +1 -1
- package/dist/commands/run-broadcast.js +1 -1
- package/dist/commands/sessions-picker.d.ts +22 -0
- package/dist/commands/sessions-picker.js +48 -9
- package/dist/commands/sessions-trace.js +1 -1
- package/dist/commands/sessions.js +1 -0
- package/dist/commands/setup-secrets.js +23 -0
- package/dist/commands/share.d.ts +20 -5
- package/dist/commands/share.js +158 -53
- package/dist/commands/ssh.js +54 -7
- package/dist/commands/teams.d.ts +12 -0
- package/dist/commands/teams.js +44 -67
- package/dist/commands/traces.d.ts +2 -0
- package/dist/commands/traces.js +188 -0
- package/dist/commands/view.d.ts +1 -1
- package/dist/commands/view.js +17 -34
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/lib/account-provider-registry.d.ts +9 -0
- package/dist/lib/account-provider-registry.js +20 -0
- package/dist/lib/accounting/account-pool-collect.d.ts +34 -0
- package/dist/lib/accounting/account-pool-collect.js +71 -0
- package/dist/lib/accounting/account-pool.d.ts +46 -0
- package/dist/lib/accounting/account-pool.js +33 -0
- package/dist/lib/accounting/capacity.d.ts +24 -0
- package/dist/lib/accounting/capacity.js +30 -0
- package/dist/lib/accounting/rotate.d.ts +11 -18
- package/dist/lib/accounting/rotate.js +6 -24
- package/dist/lib/accounting/usage.d.ts +14 -3
- package/dist/lib/accounting/usage.js +64 -18
- package/dist/lib/agent-spec/agents.js +11 -9
- package/dist/lib/browser/drivers/ssh.d.ts +16 -2
- package/dist/lib/browser/drivers/ssh.js +36 -9
- package/dist/lib/browser/ipc.js +8 -3
- package/dist/lib/browser/profiles.d.ts +35 -101
- package/dist/lib/browser/profiles.js +130 -301
- 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 +8 -6
- package/dist/lib/browser/remote-control.js +8 -6
- 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 +40 -16
- package/dist/lib/browser/runtime-state.js +92 -25
- package/dist/lib/browser/service.d.ts +28 -9
- package/dist/lib/browser/service.js +139 -62
- package/dist/lib/browser/task-index.d.ts +70 -0
- package/dist/lib/browser/task-index.js +180 -0
- 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/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 +2 -8
- package/dist/lib/config-keys.js +2 -13
- 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/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 +20 -18
- package/dist/lib/device-config.d.ts +0 -2
- package/dist/lib/device-config.js +1 -23
- package/dist/lib/devices/registry.d.ts +1 -1
- package/dist/lib/devices/registry.js +1 -1
- package/dist/lib/devices/stats-cache.d.ts +20 -0
- package/dist/lib/devices/stats-cache.js +49 -5
- 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 +67 -13
- package/dist/lib/exec.js +85 -29
- 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 +11 -0
- package/dist/lib/hooks/install.js +2 -2
- 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 +19 -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/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/store.js +6 -3
- package/dist/lib/installations/versions.js +73 -48
- 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 +31 -7
- package/dist/lib/menubar/install-menubar.js +79 -21
- 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/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/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.js +17 -1
- package/dist/lib/session/db.d.ts +11 -1
- package/dist/lib/session/db.js +138 -6
- package/dist/lib/session/discover.d.ts +21 -1
- package/dist/lib/session/discover.js +33 -5
- 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/remote/remote-list.d.ts +9 -0
- package/dist/lib/session/remote/remote-list.js +19 -2
- package/dist/lib/session/remote/watch.js +11 -2
- package/dist/lib/session/session-cache.d.ts +53 -0
- package/dist/lib/session/session-cache.js +88 -0
- 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-html.d.ts +5 -1
- package/dist/lib/session/trajectory-html.js +42 -22
- package/dist/lib/session/trajectory-text.js +9 -2
- package/dist/lib/session/trajectory.js +53 -4
- 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 +44 -15
- package/dist/lib/share/publish.js +58 -26
- package/dist/lib/share/worker-template.js +402 -25
- 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.js +1 -1
- package/dist/lib/startup/dev-build.js +3 -3
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +7 -3
- package/dist/lib/subagents-registry.js +1 -1
- package/dist/lib/teams/agents.d.ts +14 -12
- package/dist/lib/teams/agents.js +188 -73
- package/dist/lib/teams/api.d.ts +6 -1
- package/dist/lib/teams/api.js +2 -0
- package/dist/lib/teams/scheduler.d.ts +7 -0
- package/dist/lib/teams/scheduler.js +26 -8
- package/dist/lib/terminal/index.d.ts +1 -1
- 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.js +31 -0
- 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 +459 -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 +18 -19
- package/dist/lib/types.js +14 -0
- package/dist/lib/usage-refresh.d.ts +1 -1
- package/dist/lib/usage-refresh.js +11 -8
- package/dist/lib/workflows.d.ts +1 -1
- package/package.json +4 -6
- 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 -26
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +0 -135
- 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
|
@@ -25,9 +25,25 @@ import { getCliVersion, resolveAgentsBin, resolveInstalledLayout } from '../vers
|
|
|
25
25
|
import { copyAppBundle, withInstallLock } from '../app-bundle-install.js';
|
|
26
26
|
import { compareVersions } from '../agent-spec/primitives.js';
|
|
27
27
|
import { namespacedServiceLabel, serviceManifestHomeEnv, serviceManagerRegistrationAllowed } from '../service-manifest.js';
|
|
28
|
+
import { downloadMenubarHelperApp } from './download-menubar.js';
|
|
29
|
+
import { helperFloor } from '../helper-versions.js';
|
|
28
30
|
const APP_BUNDLE_NAME = 'MenubarHelper.app';
|
|
29
31
|
const INSTALL_DIR_NAME = 'agents-cli';
|
|
30
32
|
const SERVICE_LABEL_BASE = 'com.phnx-labs.agents-menubar';
|
|
33
|
+
/**
|
|
34
|
+
* The bundled executable's basename (RUSH-3101) — what launchd execs directly
|
|
35
|
+
* inside `Contents/MacOS/`, bypassing LaunchServices name resolution. Before
|
|
36
|
+
* this it was `MenubarHelper`, so that raw Mach-O basename is exactly what
|
|
37
|
+
* macOS showed in System Settings > Privacy & Security > Accessibility and in
|
|
38
|
+
* the "would like to control this computer" prompt. Renaming this constant
|
|
39
|
+
* alone does NOT touch the bundle id, Team ID, or designated requirement
|
|
40
|
+
* (`SERVICE_LABEL_BASE`, `MENUBAR_HELPER_BUNDLE_ID` in download-menubar.ts) —
|
|
41
|
+
* those are what keep the existing Accessibility grant alive across upgrades.
|
|
42
|
+
* Every basename-matching check reads this constant rather than re-deriving
|
|
43
|
+
* the string; the Swift side has its own equal in
|
|
44
|
+
* `menubar/Sources/MenubarHelper/HelperIdentity.swift`.
|
|
45
|
+
*/
|
|
46
|
+
export const MENUBAR_HELPER_EXECUTABLE_NAME = 'AGI Menu';
|
|
31
47
|
/**
|
|
32
48
|
* launchd Label for this process's helper — the production identifier for a
|
|
33
49
|
* real invocation, namespaced under a redirected HOME (RUSH-2639). launchd
|
|
@@ -83,12 +99,12 @@ function readInstalledMenubarVersion() {
|
|
|
83
99
|
}
|
|
84
100
|
/** Executable inside the installed bundle. */
|
|
85
101
|
function installedExecutablePath() {
|
|
86
|
-
return path.join(installedAppPath(), 'Contents', 'MacOS',
|
|
102
|
+
return path.join(installedAppPath(), 'Contents', 'MacOS', MENUBAR_HELPER_EXECUTABLE_NAME);
|
|
87
103
|
}
|
|
88
104
|
/**
|
|
89
|
-
* Absolute path to the installed
|
|
90
|
-
* else null. The desktop notifier (notify-desktop.ts) routes daemon
|
|
91
|
-
* notifications through this one-shot (`
|
|
105
|
+
* Absolute path to the installed menu-bar helper executable if it exists on
|
|
106
|
+
* disk, else null. The desktop notifier (notify-desktop.ts) routes daemon
|
|
107
|
+
* notifications through this one-shot (`"AGI Menu" --notify ...`) so they
|
|
92
108
|
* carry the agents-cli mark rather than the generic osascript icon. Null on
|
|
93
109
|
* non-darwin or when the helper is not installed (menu bar disabled, a Linux
|
|
94
110
|
* package, or a dev checkout without a built bundle).
|
|
@@ -119,7 +135,7 @@ export function menubarServiceInstalled() {
|
|
|
119
135
|
* Locate the source `.app` shipped alongside the compiled JS.
|
|
120
136
|
* 1. dist/lib/menubar/MenubarHelper.app — npm install layout (sibling of this file)
|
|
121
137
|
* 2. <repo>/bin/MenubarHelper.app — raw working tree (tsx/dev)
|
|
122
|
-
* 3.
|
|
138
|
+
* 3. cli/menubar/dist/MenubarHelper.app — fresh local build
|
|
123
139
|
* 4. <on-disk install>/dist/lib/menubar/MenubarHelper.app — Bun single-file
|
|
124
140
|
* binary: `import.meta.url` is a virtual `/$bunfs/` path, so the sibling
|
|
125
141
|
* candidates above can't see the on-disk bundle; recover it via the
|
|
@@ -229,7 +245,12 @@ export function hasDeveloperIdSignature(appPath) {
|
|
|
229
245
|
export function ensureMenubarAppInstalled(opts = {}) {
|
|
230
246
|
if (!onDarwin())
|
|
231
247
|
return null;
|
|
232
|
-
|
|
248
|
+
// An explicit `sourceAppPath` (a pre-downloaded cache path from the explicit
|
|
249
|
+
// enable/setup path) overrides local discovery. This function itself does NO
|
|
250
|
+
// network — the download happens in the async callers before they hand a path
|
|
251
|
+
// here, so the sync startup self-heal that calls it with no override stays
|
|
252
|
+
// network-free and simply no-ops when `sourceAppPath()` finds nothing.
|
|
253
|
+
const src = opts.sourceAppPath ?? sourceAppPath();
|
|
233
254
|
if (!src)
|
|
234
255
|
return null;
|
|
235
256
|
const dest = installedAppPath();
|
|
@@ -393,15 +414,18 @@ export function restartMenubarHelperAfterSwap(uid, ownProcesses, exec = execFile
|
|
|
393
414
|
}
|
|
394
415
|
}
|
|
395
416
|
/**
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
*
|
|
417
|
+
* Sync install + start core: install the given (already-on-disk) source `.app`
|
|
418
|
+
* and start the launchd service. NO network — the source `.app` must already be
|
|
419
|
+
* local (a bundled/local copy for the sync startup self-heal, or a pre-downloaded
|
|
420
|
+
* cache path for the explicit async enable/setup path). Returns false on
|
|
421
|
+
* non-darwin, when no source resolves, or when the bundle fails the Gatekeeper
|
|
422
|
+
* check. Shared by the sync self-heal (`installMenubarLaunchAgentOnUpgrade`) and
|
|
423
|
+
* the async `enableMenubarService`.
|
|
400
424
|
*/
|
|
401
|
-
|
|
425
|
+
function startMenubarServiceFromSource(opts = {}) {
|
|
402
426
|
if (!onDarwin())
|
|
403
427
|
return false;
|
|
404
|
-
const exec = ensureMenubarAppInstalled({ forceReinstall: true });
|
|
428
|
+
const exec = ensureMenubarAppInstalled({ forceReinstall: true, sourceAppPath: opts.sourceAppPath });
|
|
405
429
|
if (!exec)
|
|
406
430
|
return false;
|
|
407
431
|
// Never bootstrap a helper macOS will reject at launch: an invalid signature
|
|
@@ -420,6 +444,29 @@ export function enableMenubarService(opts = { clearOptOut: true }) {
|
|
|
420
444
|
installAndStartService(exec);
|
|
421
445
|
return true;
|
|
422
446
|
}
|
|
447
|
+
/**
|
|
448
|
+
* Install + start the menu-bar helper as a launchd user service (idempotent).
|
|
449
|
+
* Clears the sticky opt-out, installs the .app, writes the plist, and
|
|
450
|
+
* bootstraps it into the GUI domain. Returns false on non-darwin or when no
|
|
451
|
+
* helper bundle can be resolved for this install.
|
|
452
|
+
*
|
|
453
|
+
* ASYNC and download-capable: this is the EXPLICIT user-initiated path
|
|
454
|
+
* (`agents menubar enable`). When no bundled/local `.app` ships (a fresh
|
|
455
|
+
* `npm i -g` machine whose tarball lacks the bundle), it fetches the signed +
|
|
456
|
+
* notarized release asset for the running CLI version — verified (sha256 +
|
|
457
|
+
* codesign + Team + designated-requirement pin + notarization) before install
|
|
458
|
+
* — and starts the service from that cached copy. The startup self-heal never
|
|
459
|
+
* routes here: it calls `startMenubarServiceFromSource` and stays sync +
|
|
460
|
+
* network-free, no-opping when no local source exists.
|
|
461
|
+
*/
|
|
462
|
+
export async function enableMenubarService(opts = { clearOptOut: true }) {
|
|
463
|
+
if (!onDarwin())
|
|
464
|
+
return false;
|
|
465
|
+
let src = sourceAppPath();
|
|
466
|
+
if (!src)
|
|
467
|
+
src = await downloadMenubarHelperApp(helperFloor('menubar'));
|
|
468
|
+
return startMenubarServiceFromSource({ ...opts, sourceAppPath: src });
|
|
469
|
+
}
|
|
423
470
|
/** Drop the sticky `agents menubar disable` sentinel. */
|
|
424
471
|
function clearMenubarOptOut() {
|
|
425
472
|
try {
|
|
@@ -714,7 +761,7 @@ export function installMenubarLaunchAgentOnUpgrade() {
|
|
|
714
761
|
if (!sourceAppPath())
|
|
715
762
|
return;
|
|
716
763
|
if (!menubarServiceInstalled()) {
|
|
717
|
-
|
|
764
|
+
startMenubarServiceFromSource({ clearOptOut: false });
|
|
718
765
|
return;
|
|
719
766
|
}
|
|
720
767
|
// Re-enable (recopy helper + rewrite plist) when the version drifted OR the
|
|
@@ -735,7 +782,7 @@ export function installMenubarLaunchAgentOnUpgrade() {
|
|
|
735
782
|
// false without installing when the bundle fails the Gatekeeper check, and
|
|
736
783
|
// stamping first would spend the shared cooldown on a no-op — locking every
|
|
737
784
|
// non-owner out for another hour while nothing had been fixed.
|
|
738
|
-
if (
|
|
785
|
+
if (startMenubarServiceFromSource({ clearOptOut: false })) {
|
|
739
786
|
stampMenubarHeal();
|
|
740
787
|
// One-time: the ad-hoc -> Developer ID transition leaves a dead TCC row
|
|
741
788
|
// under the old identity (tccutil on zion reset it 11 times across
|
|
@@ -839,7 +886,7 @@ function endProcess(pid) {
|
|
|
839
886
|
* 4. login item — write the plist (RunAtLoad + KeepAlive) and bootstrap it
|
|
840
887
|
* 5. single — verify exactly one helper came back up
|
|
841
888
|
*/
|
|
842
|
-
export function runMenubarSetup() {
|
|
889
|
+
export async function runMenubarSetup() {
|
|
843
890
|
const steps = [];
|
|
844
891
|
const step = (name, outcome, detail) => {
|
|
845
892
|
steps.push({ name, outcome, detail });
|
|
@@ -849,9 +896,20 @@ export function runMenubarSetup() {
|
|
|
849
896
|
return { steps, configured: false, status: getMenubarStatus() };
|
|
850
897
|
}
|
|
851
898
|
const before = getMenubarStatus();
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
899
|
+
// Explicit user-initiated path: when no bundled/local `.app` ships (a fresh
|
|
900
|
+
// `npm i -g` machine whose tarball lacks the bundle), fetch the signed +
|
|
901
|
+
// notarized release asset for this CLI version. Verified (sha256 + codesign +
|
|
902
|
+
// Team + designated-requirement pin + notarization) before install; the
|
|
903
|
+
// cached copy is the source for `ensureMenubarAppInstalled` below.
|
|
904
|
+
let src = sourceAppPath();
|
|
905
|
+
if (!src) {
|
|
906
|
+
try {
|
|
907
|
+
src = await downloadMenubarHelperApp(helperFloor('menubar'));
|
|
908
|
+
}
|
|
909
|
+
catch (e) {
|
|
910
|
+
step('bundle', 'failed', `no AGI Menu bundle ships with this install, and the release-asset download failed: ${e.message}`);
|
|
911
|
+
return { steps, configured: false, status: before };
|
|
912
|
+
}
|
|
855
913
|
}
|
|
856
914
|
// 3 before 1: end the running copies BEFORE swapping the bundle underneath
|
|
857
915
|
// them, so no helper keeps a status item alive on a binary that no longer
|
|
@@ -868,7 +926,7 @@ export function runMenubarSetup() {
|
|
|
868
926
|
else {
|
|
869
927
|
step('duplicates', 'ok', 'no helper was running');
|
|
870
928
|
}
|
|
871
|
-
const exec = ensureMenubarAppInstalled({ forceReinstall: true });
|
|
929
|
+
const exec = ensureMenubarAppInstalled({ forceReinstall: true, sourceAppPath: src });
|
|
872
930
|
if (!exec) {
|
|
873
931
|
step('bundle', 'failed', 'could not install the helper bundle');
|
|
874
932
|
return { steps, configured: false, status: getMenubarStatus() };
|
|
@@ -946,14 +1004,14 @@ function parsePsLines(psOutput) {
|
|
|
946
1004
|
*
|
|
947
1005
|
* Identity comes from `comm` (the resolved executable), never from a substring
|
|
948
1006
|
* of the command line: matching the latter flags any shell that merely mentions
|
|
949
|
-
*
|
|
1007
|
+
* the helper's name. `command` is consulted only to drop `--notify` one-shots.
|
|
950
1008
|
*/
|
|
951
1009
|
export function classifyMenubarProcesses(commOutput, commandOutput, installedExec) {
|
|
952
1010
|
const commands = parsePsLines(commandOutput);
|
|
953
1011
|
const own = [];
|
|
954
1012
|
const foreign = [];
|
|
955
1013
|
for (const [pid, executable] of parsePsLines(commOutput)) {
|
|
956
|
-
if (path.basename(executable) !==
|
|
1014
|
+
if (path.basename(executable) !== MENUBAR_HELPER_EXECUTABLE_NAME)
|
|
957
1015
|
continue;
|
|
958
1016
|
// `--notify` is a one-shot that posts a notification and exits; it runs the
|
|
959
1017
|
// installed binary but never claims the status item or the chords.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The one place the daemon (overdue routines, heal, routine start/finish/output)
|
|
5
5
|
* emits a native desktop notification. On macOS it routes through the installed
|
|
6
|
-
* `MenubarHelper.app` companion — a one-shot `
|
|
6
|
+
* `MenubarHelper.app` companion — a one-shot `"AGI Menu" --notify` invocation —
|
|
7
7
|
* so the notification is attributed to that bundle and carries the agents-cli
|
|
8
8
|
* mark instead of the generic AppleScript icon. A banner carries two images: the
|
|
9
9
|
* agents-cli app icon on the LEFT (the sender) and, when the event belongs to one
|
|
@@ -48,7 +48,7 @@ export interface DesktopNotification {
|
|
|
48
48
|
*/
|
|
49
49
|
agent?: string;
|
|
50
50
|
}
|
|
51
|
-
/** Argv for the
|
|
51
|
+
/** Argv for the "AGI Menu" one-shot notify mode. Exported for tests. */
|
|
52
52
|
export declare function buildMenubarNotifyArgs(n: DesktopNotification): string[];
|
|
53
53
|
/** AppleScript for the osascript degradation path. Exported for tests. */
|
|
54
54
|
export declare function buildOsascriptNotifyArgs(n: DesktopNotification): string[];
|
|
@@ -67,7 +67,7 @@ export declare function buildOsascriptNotifyArgs(n: DesktopNotification): string
|
|
|
67
67
|
*/
|
|
68
68
|
export declare function spawnDetachedQuiet(command: string, args: string[], timeoutMs?: number): ChildProcess;
|
|
69
69
|
/**
|
|
70
|
-
* Fire a native desktop notification, branded via the
|
|
70
|
+
* Fire a native desktop notification, branded via the "AGI Menu" companion on
|
|
71
71
|
* macOS. Best-effort — any failure is swallowed so a notification hiccup never
|
|
72
72
|
* blocks or crashes the daemon.
|
|
73
73
|
*/
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The one place the daemon (overdue routines, heal, routine start/finish/output)
|
|
5
5
|
* emits a native desktop notification. On macOS it routes through the installed
|
|
6
|
-
* `MenubarHelper.app` companion — a one-shot `
|
|
6
|
+
* `MenubarHelper.app` companion — a one-shot `"AGI Menu" --notify` invocation —
|
|
7
7
|
* so the notification is attributed to that bundle and carries the agents-cli
|
|
8
8
|
* mark instead of the generic AppleScript icon. A banner carries two images: the
|
|
9
9
|
* agents-cli app icon on the LEFT (the sender) and, when the event belongs to one
|
|
@@ -32,7 +32,7 @@ import { resolveInstalledMenubarExecutable } from './install-menubar.js';
|
|
|
32
32
|
* only if the child never self-exits.
|
|
33
33
|
*/
|
|
34
34
|
const NOTIFY_TIMEOUT_MS = 4000;
|
|
35
|
-
/** Argv for the
|
|
35
|
+
/** Argv for the "AGI Menu" one-shot notify mode. Exported for tests. */
|
|
36
36
|
export function buildMenubarNotifyArgs(n) {
|
|
37
37
|
const args = ['--notify', '--title', n.title, '--body', n.body];
|
|
38
38
|
if (n.subtitle)
|
|
@@ -88,7 +88,7 @@ export function spawnDetachedQuiet(command, args, timeoutMs = NOTIFY_TIMEOUT_MS)
|
|
|
88
88
|
return child;
|
|
89
89
|
}
|
|
90
90
|
/**
|
|
91
|
-
* Fire a native desktop notification, branded via the
|
|
91
|
+
* Fire a native desktop notification, branded via the "AGI Menu" companion on
|
|
92
92
|
* macOS. Best-effort — any failure is swallowed so a notification hiccup never
|
|
93
93
|
* blocks or crashes the daemon.
|
|
94
94
|
*/
|
|
@@ -98,9 +98,9 @@ export function notifyDesktop(n) {
|
|
|
98
98
|
if (platform === 'darwin') {
|
|
99
99
|
const exec = resolveInstalledMenubarExecutable();
|
|
100
100
|
if (exec) {
|
|
101
|
-
// Branded path: the notification is attributed to MenubarHelper.app
|
|
102
|
-
// shows the agents-cli mark and its click
|
|
103
|
-
// helper's UNUserNotificationCenter delegate.
|
|
101
|
+
// Branded path: the notification is attributed to MenubarHelper.app
|
|
102
|
+
// ("AGI Menu" on disk), so it shows the agents-cli mark and its click
|
|
103
|
+
// action is handled by the running helper's UNUserNotificationCenter delegate.
|
|
104
104
|
spawnDetachedQuiet(exec, buildMenubarNotifyArgs(n));
|
|
105
105
|
return;
|
|
106
106
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* actually ships — so `--model cheap|default|best|ultra` works on `agents run`
|
|
8
8
|
* and `agents teams add` alike, funnelling through `resolveModel()`.
|
|
9
9
|
*
|
|
10
|
-
* Ranking signal, in priority (see
|
|
10
|
+
* Ranking signal, in priority (see cli/docs — model ranking mechanisms):
|
|
11
11
|
* 1. Provider-declared lineup — the catalog's own family names / descriptions
|
|
12
12
|
* (opus/sonnet/haiku/fable; Codex "frontier / balanced / fast"). Most
|
|
13
13
|
* drift-proof: the provider tells us its own ranking.
|
|
@@ -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)
|
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();
|
|
@@ -2068,10 +2068,26 @@ export function foldHostLink(rows) {
|
|
|
2068
2068
|
// disproves. A stored record never yields `attached` (it is background or
|
|
2069
2069
|
// parked), so clearing only that value drops the derived lie and leaves a
|
|
2070
2070
|
// real detach record untouched.
|
|
2071
|
-
|
|
2071
|
+
// Only a POSITIVE loss signal disproves a derived `attached`. `unknown` means
|
|
2072
|
+
// we never looked — a bare terminal with no IDE window and no tmux has
|
|
2073
|
+
// neither input — so it must not clear presence, or every plain-terminal
|
|
2074
|
+
// session the user is sitting in would lose its `attached` marker.
|
|
2075
|
+
if ((link === 'no-client' || link === 'host-gone') && s.presence === 'attached') {
|
|
2072
2076
|
s.presence = undefined;
|
|
2077
|
+
}
|
|
2073
2078
|
if (link === 'host-gone' && s.status === 'closed')
|
|
2074
2079
|
s.status = 'crashed';
|
|
2080
|
+
// Only idle/input_required are promoted — a `running` session keeps its
|
|
2081
|
+
// status (SES-18a). Extending this to a running agent with no client was
|
|
2082
|
+
// tried and reverted: since RUSH-3125 wraps every remote interactive run in
|
|
2083
|
+
// a detached tmux pane, "running with zero attached clients" is the NORMAL
|
|
2084
|
+
// steady state between check-ins, and that path writes no detach record, so
|
|
2085
|
+
// `deliberatelyDetached` is false for it. Promoting it would relabel every
|
|
2086
|
+
// remote agent as orphaned whenever nobody is looking — the over-reporting
|
|
2087
|
+
// this file's header calls worthless. Telling a stranded agent from a
|
|
2088
|
+
// healthy unattended one needs to know a client was EXPECTED and LOST, which
|
|
2089
|
+
// no signal available here carries; that belongs with the peer-side pane
|
|
2090
|
+
// ownership work, not this function.
|
|
2075
2091
|
else if (link === 'no-client' && (s.status === 'idle' || s.status === 'input_required')) {
|
|
2076
2092
|
s.status = 'orphaned';
|
|
2077
2093
|
}
|