@phnx-labs/agents-cli 1.20.77 → 1.20.82
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 +589 -0
- package/README.md +27 -25
- package/dist/bin/agents +0 -0
- package/dist/commands/activity.d.ts +9 -0
- package/dist/commands/activity.js +153 -24
- package/dist/commands/apply.js +11 -4
- package/dist/commands/cli.js +1 -1
- package/dist/commands/commands.d.ts +1 -1
- package/dist/commands/commands.js +7 -6
- package/dist/commands/defaults.js +3 -11
- package/dist/commands/doctor.d.ts +74 -0
- package/dist/commands/doctor.js +718 -40
- package/dist/commands/events.js +1 -1
- package/dist/commands/exec.js +53 -8
- package/dist/commands/feed.d.ts +4 -0
- package/dist/commands/feed.js +87 -11
- package/dist/commands/harness.js +2 -2
- package/dist/commands/hooks.js +1 -1
- package/dist/commands/import.js +8 -4
- package/dist/commands/logs.js +6 -0
- package/dist/commands/models.js +1 -1
- package/dist/commands/packages.js +6 -6
- package/dist/commands/permissions.js +1 -1
- package/dist/commands/profiles.js +1 -1
- package/dist/commands/pull.d.ts +1 -1
- package/dist/commands/pull.js +4 -4
- package/dist/commands/repo.d.ts +78 -0
- package/dist/commands/repo.js +195 -11
- package/dist/commands/routines.js +16 -4
- package/dist/commands/rules.js +13 -12
- package/dist/commands/secrets.d.ts +3 -1
- package/dist/commands/secrets.js +108 -34
- package/dist/commands/sessions-browser.d.ts +45 -3
- package/dist/commands/sessions-browser.js +118 -12
- package/dist/commands/sessions-export.d.ts +3 -0
- package/dist/commands/sessions-export.js +13 -10
- package/dist/commands/sessions-inject.d.ts +7 -4
- package/dist/commands/sessions-inject.js +18 -18
- package/dist/commands/sessions-picker.d.ts +15 -1
- package/dist/commands/sessions-picker.js +90 -7
- package/dist/commands/sessions-resume.d.ts +16 -0
- package/dist/commands/sessions-resume.js +14 -13
- package/dist/commands/sessions.d.ts +126 -1
- package/dist/commands/sessions.js +365 -42
- package/dist/commands/set.d.ts +15 -0
- package/dist/commands/set.js +79 -0
- package/dist/commands/setup-fleet.d.ts +21 -0
- package/dist/commands/setup-fleet.js +201 -0
- package/dist/commands/setup-secrets.d.ts +20 -0
- package/dist/commands/setup-secrets.js +223 -0
- package/dist/commands/setup.js +17 -3
- package/dist/commands/skills.js +1 -1
- package/dist/commands/ssh.js +9 -1
- package/dist/commands/sync.js +7 -2
- package/dist/commands/teams-picker.js +0 -1
- package/dist/commands/teams.d.ts +33 -1
- package/dist/commands/teams.js +208 -44
- package/dist/commands/versions.js +10 -5
- package/dist/commands/view.d.ts +1 -0
- package/dist/commands/view.js +11 -0
- package/dist/index.js +8 -13
- package/dist/lib/acp/harnesses.js +0 -7
- package/dist/lib/activity.d.ts +156 -0
- package/dist/lib/activity.js +282 -0
- package/dist/lib/agents.d.ts +9 -23
- package/dist/lib/agents.js +72 -100
- package/dist/lib/auto-pull.d.ts +16 -8
- package/dist/lib/auto-pull.js +23 -28
- package/dist/lib/browser/service.js +3 -0
- package/dist/lib/browser/types.d.ts +7 -0
- package/dist/lib/capabilities.js +6 -2
- package/dist/lib/crabbox/lease.js +2 -2
- package/dist/lib/crabbox/setup-copy.d.ts +4 -4
- package/dist/lib/crabbox/setup-copy.js +4 -4
- package/dist/lib/daemon.d.ts +26 -40
- package/dist/lib/daemon.js +63 -123
- package/dist/lib/devices/fleet-divergence.d.ts +101 -0
- package/dist/lib/devices/fleet-divergence.js +188 -0
- package/dist/lib/devices/fleet-inventory.d.ts +19 -0
- package/dist/lib/devices/fleet-inventory.js +57 -0
- package/dist/lib/devices/fleet.d.ts +1 -1
- package/dist/lib/devices/fleet.js +1 -1
- package/dist/lib/devices/health-report.d.ts +10 -2
- package/dist/lib/devices/health-report.js +32 -1
- package/dist/lib/doctor-diff.d.ts +20 -0
- package/dist/lib/doctor-diff.js +6 -1
- package/dist/lib/drift.d.ts +31 -11
- package/dist/lib/drift.js +58 -7
- package/dist/lib/events.d.ts +8 -1
- package/dist/lib/events.js +10 -1
- package/dist/lib/exec.js +35 -8
- package/dist/lib/feed-post.d.ts +28 -1
- package/dist/lib/feed-post.js +110 -2
- package/dist/lib/fleet/apply.js +8 -1
- package/dist/lib/fleet/auth-sync.d.ts +18 -1
- package/dist/lib/fleet/auth-sync.js +25 -11
- package/dist/lib/fleet/remote-login.js +5 -0
- package/dist/lib/git.d.ts +27 -14
- package/dist/lib/git.js +138 -43
- package/dist/lib/hooks.d.ts +42 -0
- package/dist/lib/hooks.js +137 -93
- package/dist/lib/hosts/dispatch.d.ts +28 -1
- package/dist/lib/hosts/dispatch.js +33 -5
- package/dist/lib/hosts/option.js +2 -2
- package/dist/lib/hosts/passthrough.d.ts +22 -1
- package/dist/lib/hosts/passthrough.js +238 -13
- package/dist/lib/hosts/remote-cmd.js +2 -0
- package/dist/lib/hosts/run-target.d.ts +2 -0
- package/dist/lib/hosts/run-target.js +1 -0
- package/dist/lib/hq/floor.d.ts +1 -1
- package/dist/lib/hq/floor.js +6 -1
- package/dist/lib/mcp.js +3 -71
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +2 -0
- 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 +15 -2
- package/dist/lib/menubar/notify-desktop.d.ts +15 -0
- package/dist/lib/menubar/notify-desktop.js +41 -5
- package/dist/lib/merged-resources.d.ts +11 -0
- package/dist/{commands/resources.js → lib/merged-resources.js} +11 -19
- package/dist/lib/permissions.d.ts +0 -32
- package/dist/lib/permissions.js +4 -157
- package/dist/lib/picker.js +1 -1
- package/dist/lib/platform/process.d.ts +16 -5
- package/dist/lib/platform/process.js +54 -0
- package/dist/lib/plugins.d.ts +0 -8
- package/dist/lib/plugins.js +4 -110
- package/dist/lib/project-resources.js +4 -1
- package/dist/lib/refresh.d.ts +5 -3
- package/dist/lib/refresh.js +7 -5
- package/dist/lib/resources/commands.js +4 -1
- package/dist/lib/resources/mcp.js +0 -4
- package/dist/lib/resources/permissions.d.ts +1 -1
- package/dist/lib/resources/permissions.js +1 -5
- package/dist/lib/resources/rules.js +4 -1
- package/dist/lib/resources/skills.js +4 -1
- package/dist/lib/resources/subagents.js +4 -0
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/routines.d.ts +20 -0
- package/dist/lib/routines.js +6 -0
- package/dist/lib/run-defaults.d.ts +1 -0
- package/dist/lib/run-defaults.js +9 -0
- package/dist/lib/runner.d.ts +0 -12
- package/dist/lib/runner.js +71 -29
- package/dist/lib/sandbox.js +0 -16
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +2 -0
- 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 +13 -1
- package/dist/lib/secrets/agent.d.ts +18 -0
- package/dist/lib/secrets/agent.js +131 -17
- package/dist/lib/secrets/audit.d.ts +46 -0
- package/dist/lib/secrets/audit.js +56 -0
- package/dist/lib/secrets/bundles.d.ts +12 -9
- package/dist/lib/secrets/bundles.js +47 -31
- package/dist/lib/secrets/index.d.ts +2 -1
- package/dist/lib/secrets/index.js +8 -2
- package/dist/lib/secrets/remote.d.ts +75 -2
- package/dist/lib/secrets/remote.js +139 -5
- package/dist/lib/secrets/scope.d.ts +26 -0
- package/dist/lib/secrets/scope.js +29 -0
- package/dist/lib/secrets/session-store.d.ts +10 -0
- package/dist/lib/secrets/session-store.js +64 -11
- package/dist/lib/secrets/sync.js +3 -3
- package/dist/lib/session/active.d.ts +117 -26
- package/dist/lib/session/active.js +199 -53
- package/dist/lib/session/actor-sidecar.d.ts +23 -0
- package/dist/lib/session/actor-sidecar.js +101 -0
- package/dist/lib/session/db.d.ts +41 -1
- package/dist/lib/session/db.js +173 -8
- package/dist/lib/session/digest.js +126 -21
- package/dist/lib/session/discover.d.ts +19 -2
- package/dist/lib/session/discover.js +32 -11
- package/dist/lib/session/hook-sessions.d.ts +9 -0
- package/dist/lib/session/hook-sessions.js +57 -8
- package/dist/lib/session/origin-machine.d.ts +18 -0
- package/dist/lib/session/origin-machine.js +34 -0
- package/dist/lib/session/pid-registry.d.ts +13 -0
- package/dist/lib/session/render.d.ts +2 -0
- package/dist/lib/session/render.js +1 -1
- package/dist/lib/session/state.d.ts +1 -0
- package/dist/lib/session/state.js +1 -1
- package/dist/lib/session/sync/config.js +2 -2
- package/dist/lib/session/types.d.ts +16 -0
- package/dist/lib/smart-launch.d.ts +86 -0
- package/dist/lib/smart-launch.js +172 -0
- package/dist/lib/staleness/detectors/commands.js +2 -2
- package/dist/lib/staleness/detectors/permissions.js +1 -45
- package/dist/lib/staleness/writers/commands.js +4 -5
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +1 -2
- package/dist/lib/startup/command-registry.js +2 -4
- package/dist/lib/subagents-registry.js +5 -12
- package/dist/lib/subagents.d.ts +0 -10
- package/dist/lib/subagents.js +0 -12
- package/dist/lib/teams/agents.d.ts +0 -2
- package/dist/lib/teams/agents.js +10 -27
- package/dist/lib/teams/index.d.ts +1 -1
- package/dist/lib/teams/index.js +1 -1
- package/dist/lib/types.d.ts +17 -9
- package/dist/lib/usage.d.ts +28 -5
- package/dist/lib/usage.js +271 -8
- package/dist/lib/versions.js +7 -1
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdog.js +25 -13
- package/package.json +1 -2
- package/dist/commands/check.d.ts +0 -15
- package/dist/commands/check.js +0 -180
- package/dist/commands/resources.d.ts +0 -5
- package/dist/lib/secrets/account-token.d.ts +0 -20
- package/dist/lib/secrets/account-token.js +0 -64
|
@@ -19,6 +19,7 @@ import { saveTask, updateTask, terminalPatch } from './tasks.js';
|
|
|
19
19
|
import { followHostTask } from './progress.js';
|
|
20
20
|
import { wrapHostCommandWithCredentials } from './credentials.js';
|
|
21
21
|
import { hostKeyCheckingOpts } from '../devices/known-hosts.js';
|
|
22
|
+
import { toRemotePortable } from '../project-root.js';
|
|
22
23
|
// Use $HOME (not ~) so the path is correct whether or not it's quoted and
|
|
23
24
|
// regardless of the run's cwd. Task ids are 8 hex chars, so these paths are
|
|
24
25
|
// injection-safe to interpolate unquoted into remote commands.
|
|
@@ -39,6 +40,21 @@ function homeRemainder(p) {
|
|
|
39
40
|
return p.slice(6);
|
|
40
41
|
return null;
|
|
41
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Derive the remote directory to mirror from the local cwd, for a host run the
|
|
45
|
+
* caller gave no `--cwd`/`--remote-cwd`.
|
|
46
|
+
*
|
|
47
|
+
* Without this a `--host` run lands in the remote `$HOME`, so an agent launched
|
|
48
|
+
* from a repo starts with no project context and the user has to `cd` by hand.
|
|
49
|
+
* Only a cwd under the LOCAL home is mirrored — that is the part with a
|
|
50
|
+
* meaningful remote analogue (`~/src/x` re-roots onto the remote home). A path
|
|
51
|
+
* outside home returns undefined: `/opt/thing` on this box says nothing about
|
|
52
|
+
* the target's filesystem, so the run keeps the remote home.
|
|
53
|
+
*/
|
|
54
|
+
export function deriveMirroredCwd(localCwd) {
|
|
55
|
+
const portable = toRemotePortable(localCwd);
|
|
56
|
+
return homeRemainder(portable) === null ? undefined : portable;
|
|
57
|
+
}
|
|
42
58
|
/**
|
|
43
59
|
* Build a `cd <dir> && ` prefix that resolves on the REMOTE host.
|
|
44
60
|
*
|
|
@@ -46,15 +62,26 @@ function homeRemainder(p) {
|
|
|
46
62
|
* the local one (`/home/<me>` vs `/Users/<me>`). We emit an unquoted `"$HOME"`
|
|
47
63
|
* for that segment — the remote login shell expands it — and shell-quote the
|
|
48
64
|
* remainder. Any other path (absolute or relative) is quoted verbatim.
|
|
65
|
+
*
|
|
66
|
+
* `mirror` marks a directory the caller DERIVED from the local cwd rather than
|
|
67
|
+
* one the user asked for (see `deriveMirroredCwd`). The same repo checked out at
|
|
68
|
+
* the same home-relative path on both boxes is the common fleet layout, so
|
|
69
|
+
* mirroring lands the remote agent in the project instead of `$HOME`. It is a
|
|
70
|
+
* best-effort mirror by definition — the host may simply not have that checkout
|
|
71
|
+
* — so a missing directory falls back to the remote home instead of failing the
|
|
72
|
+
* run. An explicit `--cwd`/`--remote-cwd` is never mirrored: the user named that
|
|
73
|
+
* directory, so a missing one must surface as a `cd` error.
|
|
49
74
|
*/
|
|
50
|
-
export function remoteCdPrefix(remoteCwd) {
|
|
75
|
+
export function remoteCdPrefix(remoteCwd, opts = {}) {
|
|
51
76
|
if (!remoteCwd)
|
|
52
77
|
return '';
|
|
53
78
|
const rest = homeRemainder(remoteCwd);
|
|
54
79
|
if (rest === '')
|
|
55
80
|
return 'cd "$HOME" && ';
|
|
56
|
-
if (rest !== null)
|
|
57
|
-
|
|
81
|
+
if (rest !== null) {
|
|
82
|
+
const dir = `"$HOME"/${shellQuote(rest)}`;
|
|
83
|
+
return opts.mirror ? `{ cd ${dir} || cd "$HOME"; } && ` : `cd ${dir} && `;
|
|
84
|
+
}
|
|
58
85
|
return `cd ${shellQuote(remoteCwd)} && `;
|
|
59
86
|
}
|
|
60
87
|
/**
|
|
@@ -190,7 +217,7 @@ async function launchDetached(host, target, opts) {
|
|
|
190
217
|
// resolved actor provenance first so the detached remote run inherits it
|
|
191
218
|
// instead of re-resolving from this box's SSH_CONNECTION (RUSH-2028).
|
|
192
219
|
const invocation = ['agents', ...opts.forwardedArgs].map(shellQuote).join(' ');
|
|
193
|
-
const cwd = remoteCdPrefix(opts.remoteCwd);
|
|
220
|
+
const cwd = remoteCdPrefix(opts.remoteCwd, { mirror: opts.mirrorCwd });
|
|
194
221
|
const actorExports = posixEnvExports(withActorEnv());
|
|
195
222
|
const prelude = actorExports ? `${actorExports}; ` : '';
|
|
196
223
|
let inner = `${prelude}${cwd}${invocation} > ${remoteLog} 2>&1; echo $? > ${remoteExit}`;
|
|
@@ -385,7 +412,7 @@ export async function runInteractiveOnHost(host, opts) {
|
|
|
385
412
|
for (const w of warnings)
|
|
386
413
|
process.stderr.write(`[hosts] warning: ${w}\n`);
|
|
387
414
|
const invocation = ['agents', ...buildInteractiveRunForwardedArgs(opts)].map(shellQuote).join(' ');
|
|
388
|
-
const cwd = remoteCdPrefix(opts.remoteCwd);
|
|
415
|
+
const cwd = remoteCdPrefix(opts.remoteCwd, { mirror: opts.mirrorCwd });
|
|
389
416
|
// Forward actor provenance so the interactive remote run inherits it rather
|
|
390
417
|
// than re-resolving from this box's SSH_CONNECTION (RUSH-2028).
|
|
391
418
|
const actorExports = posixEnvExports(withActorEnv());
|
|
@@ -413,6 +440,7 @@ export async function dispatchToHost(host, opts) {
|
|
|
413
440
|
return launchDetached(host, target, {
|
|
414
441
|
forwardedArgs: buildRunForwardedArgs(opts),
|
|
415
442
|
remoteCwd: opts.remoteCwd,
|
|
443
|
+
mirrorCwd: opts.mirrorCwd,
|
|
416
444
|
follow: opts.follow,
|
|
417
445
|
timeoutMs: opts.timeoutMs,
|
|
418
446
|
agentLabel: opts.agent,
|
package/dist/lib/hosts/option.js
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
/** Attach the standard `--host` flag family to a command and return it (chainable). */
|
|
13
13
|
export function addHostOption(cmd) {
|
|
14
14
|
return cmd
|
|
15
|
-
.option('-H, --host <name>', 'Run this command on another machine over SSH instead of locally — a device, a registered host,
|
|
16
|
-
.option('--device <name>', 'Alias of --host: run this command on a registered device (from `agents devices`).')
|
|
15
|
+
.option('-H, --host <name>', 'Run this command on another machine over SSH instead of locally — a device, a registered host, user@host, or `all` to fan out across every registered device. See `agents devices` / `agents hosts`.')
|
|
16
|
+
.option('--device <name>', 'Alias of --host: run this command on a registered device (from `agents devices`), or `all` to run it across the whole fleet.')
|
|
17
17
|
.option('--remote-cwd <dir>', "Working directory on the host for --host runs. Resolves on the REMOTE host — pass a '$HOME'-relative path (single-quoted so your local shell doesn't expand it) or a valid remote absolute path; a local ~ expands here and won't exist there (/Users/you vs /home/you). No effect on 'teams add'.")
|
|
18
18
|
.option('--no-tty', 'Force non-interactive output for --host runs even from a terminal.')
|
|
19
19
|
.option('--any', 'With --host <cap> (a capability tag), pick any matching host instead of erroring when several match.');
|
|
@@ -16,8 +16,28 @@
|
|
|
16
16
|
* table or, when `--host`/`--device` is present, exits with a clear
|
|
17
17
|
* "not supported" message — never commander's raw `unknown option`.
|
|
18
18
|
*/
|
|
19
|
+
import { type DeviceRegistry } from '../devices/registry.js';
|
|
20
|
+
import { runLocalCommand, runOnDevice } from '../devices/fleet.js';
|
|
21
|
+
/** Per-command remote behaviour. Absence from this map = not host-routable here. */
|
|
22
|
+
interface RemoteSpec {
|
|
23
|
+
/** Flags appended when running non-interactively (no local TTY / `--no-tty`). */
|
|
24
|
+
nonInteractive?: string[];
|
|
25
|
+
}
|
|
19
26
|
/** Pull the value of `--host`/`-H`/`--remote-cwd` (any form) out of an argv. */
|
|
20
27
|
export declare function flagValue(args: string[], long: string, short?: string): string | undefined;
|
|
28
|
+
/** Injectable dependencies for {@link runFleetPassthrough} — used by tests. */
|
|
29
|
+
export interface FleetPassthroughOptions {
|
|
30
|
+
/** Override the device registry loader (tests). */
|
|
31
|
+
loadDevices?: () => Promise<DeviceRegistry>;
|
|
32
|
+
/** Override the per-device runner (tests). Defaults to `runOnDevice`. */
|
|
33
|
+
runner?: typeof runOnDevice;
|
|
34
|
+
/** Override the local runner for the self device (tests). Defaults to `runLocalCommand`. */
|
|
35
|
+
localRunner?: typeof runLocalCommand;
|
|
36
|
+
/** Override this machine's id (tests). Defaults to `machineId()`. */
|
|
37
|
+
self?: string;
|
|
38
|
+
}
|
|
39
|
+
/** Run `agents <command> …` across every registered device and render the roster. */
|
|
40
|
+
export declare function runFleetPassthrough(command: string, allArgs: string[], spec: RemoteSpec, opts?: FleetPassthroughOptions): Promise<boolean>;
|
|
21
41
|
/**
|
|
22
42
|
* Route `agents <command> … --host <name>` to a remote if the command is
|
|
23
43
|
* host-routable and a `--host` (or its `--device` alias) was given. Returns
|
|
@@ -30,4 +50,5 @@ export declare function flagValue(args: string[], long: string, short?: string):
|
|
|
30
50
|
* @param command the resolved subcommand name (`process.argv`'s first non-flag).
|
|
31
51
|
* @param allArgs `process.argv.slice(2)` — the command name followed by its args.
|
|
32
52
|
*/
|
|
33
|
-
export declare function maybeRunOnHost(command: string, allArgs: string[]): Promise<boolean>;
|
|
53
|
+
export declare function maybeRunOnHost(command: string, allArgs: string[], opts?: FleetPassthroughOptions): Promise<boolean>;
|
|
54
|
+
export {};
|
|
@@ -24,6 +24,9 @@ import { dispatchAgentsCommand, withActorEnv } from './dispatch.js';
|
|
|
24
24
|
import { stripRoutingFlags, buildRemoteAgentsInvocation, HOST_ROUTING_SPECS, } from './remote-cmd.js';
|
|
25
25
|
import { resolveRemoteOsSync } from './remote-os.js';
|
|
26
26
|
import { machineId } from '../session/sync/config.js';
|
|
27
|
+
import { loadDevices } from '../devices/registry.js';
|
|
28
|
+
import { fanOutDevices, planFleetTargets, runLocalCommand, runOnDevice, } from '../devices/fleet.js';
|
|
29
|
+
import { platformGroupLabel } from '../devices/health-report.js';
|
|
27
30
|
/**
|
|
28
31
|
* First-class groups that run transparently on a remote via SSH when
|
|
29
32
|
* `--host`/`--device` is present. Keep both canonical names and aliases
|
|
@@ -102,6 +105,7 @@ const OWN_HOST_COMMANDS = new Set([
|
|
|
102
105
|
'harnesses',
|
|
103
106
|
'sessions',
|
|
104
107
|
'feed',
|
|
108
|
+
'activity', // fans `--host`/`--device`/`--devices-all` out itself (feed-style)
|
|
105
109
|
'computer',
|
|
106
110
|
'secrets',
|
|
107
111
|
'logs',
|
|
@@ -112,8 +116,15 @@ const OWN_HOST_COMMANDS = new Set([
|
|
|
112
116
|
'apply', // `--device` scopes the fleet reconcile to one device (it targets devices itself)
|
|
113
117
|
'monitors', // `--device` names the OWNER machine (pin-to-one), not a routing target
|
|
114
118
|
]);
|
|
115
|
-
/** `--no-tty` is stripped like the routing flags but carries no value.
|
|
116
|
-
|
|
119
|
+
/** `--no-tty` is stripped like the routing flags but carries no value. The plural
|
|
120
|
+
* fleet flags are stripped only when we handle the `all` sentinel ourselves;
|
|
121
|
+
* otherwise they fall through to command-level aggregators. */
|
|
122
|
+
const STRIP_SPECS = [
|
|
123
|
+
...HOST_ROUTING_SPECS,
|
|
124
|
+
{ long: 'no-tty', takesValue: false },
|
|
125
|
+
{ long: 'hosts', takesValue: true },
|
|
126
|
+
{ long: 'devices', takesValue: true },
|
|
127
|
+
];
|
|
117
128
|
/** Pull the value of `--host`/`-H`/`--remote-cwd` (any form) out of an argv. */
|
|
118
129
|
export function flagValue(args, long, short) {
|
|
119
130
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -155,6 +166,201 @@ async function resolveTargetHost(name, any) {
|
|
|
155
166
|
assertValidSshTarget(name); // rejects injection / flag-smuggling before it reaches ssh
|
|
156
167
|
return syntheticHost(name);
|
|
157
168
|
}
|
|
169
|
+
/** Detect the `all` sentinel on any routing flag. */
|
|
170
|
+
function isFleetAllSentinel(hostFlag, deviceFlag, hostsFlag, devicesFlag) {
|
|
171
|
+
return (hostFlag?.toLowerCase() === 'all' ||
|
|
172
|
+
deviceFlag?.toLowerCase() === 'all' ||
|
|
173
|
+
hostsFlag?.toLowerCase() === 'all' ||
|
|
174
|
+
devicesFlag?.toLowerCase() === 'all');
|
|
175
|
+
}
|
|
176
|
+
/** Strip routing flags from the argv and ensure the per-device call emits JSON. */
|
|
177
|
+
function buildFleetForwardedArgs(allArgs) {
|
|
178
|
+
const stripped = stripRoutingFlags(allArgs, STRIP_SPECS);
|
|
179
|
+
if (!stripped.includes('--json'))
|
|
180
|
+
stripped.push('--json');
|
|
181
|
+
return stripped;
|
|
182
|
+
}
|
|
183
|
+
/** Parse stdout as JSON; on failure return an object describing the error. */
|
|
184
|
+
function safeJsonParse(stdout) {
|
|
185
|
+
try {
|
|
186
|
+
return JSON.parse(stdout);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return { parseError: 'invalid JSON', snippet: stdout.trim().slice(0, 200) };
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/** One-line summary of a per-device `agents view [agent] --json` payload. */
|
|
193
|
+
function summarizeViewResult(forwarded, json) {
|
|
194
|
+
// After routing flags are stripped, the agent argument is the first token that
|
|
195
|
+
// is not a flag (e.g. `kimi` in `agents view kimi --json`).
|
|
196
|
+
const agentArg = forwarded.find((a, i) => i > 0 && !a.startsWith('-'));
|
|
197
|
+
// `agents view --json` returns an array; `agents view <agent> --json` returns
|
|
198
|
+
// a single object. Normalize to the per-agent shape.
|
|
199
|
+
const agent = agentArg
|
|
200
|
+
? (Array.isArray(json) ? json[0] : json)
|
|
201
|
+
: undefined;
|
|
202
|
+
if (!agentArg) {
|
|
203
|
+
const rows = Array.isArray(json) ? json : [];
|
|
204
|
+
const count = rows.reduce((n, r) => n + (r.versions?.length ?? 0), 0);
|
|
205
|
+
return count === 0 ? 'no agents installed' : `${count} version${count === 1 ? '' : 's'}`;
|
|
206
|
+
}
|
|
207
|
+
if (!agent || !Array.isArray(agent.versions) || agent.versions.length === 0) {
|
|
208
|
+
return 'not installed';
|
|
209
|
+
}
|
|
210
|
+
const v = agent.versions.find((x) => x.isDefault) ?? agent.versions[0];
|
|
211
|
+
const parts = [chalk.cyan(String(v.version))];
|
|
212
|
+
if (v.signedIn) {
|
|
213
|
+
parts.push(chalk.green('active'));
|
|
214
|
+
if (v.email)
|
|
215
|
+
parts.push(chalk.gray(String(v.email)));
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
parts.push(chalk.gray('signed out'));
|
|
219
|
+
}
|
|
220
|
+
return parts.join(' · ');
|
|
221
|
+
}
|
|
222
|
+
function formatCompactUsd(usd) {
|
|
223
|
+
if (usd >= 1000)
|
|
224
|
+
return `$${(usd / 1000).toFixed(1)}k`;
|
|
225
|
+
if (usd >= 1)
|
|
226
|
+
return `$${usd.toFixed(2)}`;
|
|
227
|
+
return `$${usd.toFixed(3)}`;
|
|
228
|
+
}
|
|
229
|
+
function formatCompactTokens(n) {
|
|
230
|
+
const abs = Math.abs(n);
|
|
231
|
+
if (abs >= 1e9)
|
|
232
|
+
return `${(n / 1e9).toFixed(1)}B`;
|
|
233
|
+
if (abs >= 1e6)
|
|
234
|
+
return `${(n / 1e6).toFixed(1)}M`;
|
|
235
|
+
if (abs >= 1e3)
|
|
236
|
+
return `${(n / 1e3).toFixed(1)}K`;
|
|
237
|
+
return String(n);
|
|
238
|
+
}
|
|
239
|
+
/** One-line summary of a per-device `agents output --json` payload. */
|
|
240
|
+
function summarizeOutputResult(json) {
|
|
241
|
+
const p = json;
|
|
242
|
+
const burn = p?.burn;
|
|
243
|
+
if (!burn || typeof burn !== 'object')
|
|
244
|
+
return 'ok';
|
|
245
|
+
const parts = [];
|
|
246
|
+
if (typeof burn.costUsd === 'number')
|
|
247
|
+
parts.push(`${formatCompactUsd(burn.costUsd)} burned`);
|
|
248
|
+
if (typeof burn.outputTokens === 'number')
|
|
249
|
+
parts.push(`${formatCompactTokens(burn.outputTokens)} output tokens`);
|
|
250
|
+
const commits = p?.output?.commits;
|
|
251
|
+
if (typeof commits === 'number' && commits > 0)
|
|
252
|
+
parts.push(`${commits} commits`);
|
|
253
|
+
return parts.length ? parts.join(' · ') : 'ok';
|
|
254
|
+
}
|
|
255
|
+
/** Best-effort summary of any per-device JSON payload. */
|
|
256
|
+
function summarizeResult(command, forwarded, json) {
|
|
257
|
+
if (command === 'view')
|
|
258
|
+
return summarizeViewResult(forwarded, json);
|
|
259
|
+
if (command === 'output')
|
|
260
|
+
return summarizeOutputResult(json);
|
|
261
|
+
return 'ok';
|
|
262
|
+
}
|
|
263
|
+
const GROUP_ORDER = ['macOS', 'Linux', 'Windows', 'Other'];
|
|
264
|
+
/** Render the grouped-by-OS fleet roster from per-device results. */
|
|
265
|
+
function renderFleetRoster(command, forwarded, results, self) {
|
|
266
|
+
const agentArg = forwarded[1];
|
|
267
|
+
const installed = results.filter((r) => r.status === 'ok').length;
|
|
268
|
+
const skipped = results.filter((r) => r.status === 'skipped').length;
|
|
269
|
+
const failed = results.filter((r) => r.status === 'failed').length;
|
|
270
|
+
const title = agentArg ? `${command} ${agentArg}` : command;
|
|
271
|
+
const summaryParts = [];
|
|
272
|
+
if (installed)
|
|
273
|
+
summaryParts.push(`${installed} installed`);
|
|
274
|
+
if (failed)
|
|
275
|
+
summaryParts.push(`${failed} unreachable`);
|
|
276
|
+
if (skipped)
|
|
277
|
+
summaryParts.push(`${skipped} skipped`);
|
|
278
|
+
console.log(chalk.bold(title) + chalk.gray(` · ${results.length} device${results.length === 1 ? '' : 's'}`));
|
|
279
|
+
console.log('');
|
|
280
|
+
const nameW = Math.max(6, ...results.map((r) => r.name.length));
|
|
281
|
+
const grouped = new Map();
|
|
282
|
+
for (const r of results) {
|
|
283
|
+
const g = platformGroupLabel(r.device.platform);
|
|
284
|
+
(grouped.get(g) ?? grouped.set(g, []).get(g)).push(r);
|
|
285
|
+
}
|
|
286
|
+
for (const group of GROUP_ORDER) {
|
|
287
|
+
const members = grouped.get(group);
|
|
288
|
+
if (!members || members.length === 0)
|
|
289
|
+
continue;
|
|
290
|
+
members.sort((a, b) => {
|
|
291
|
+
const aSelf = a.name.toLowerCase() === self.toLowerCase();
|
|
292
|
+
const bSelf = b.name.toLowerCase() === self.toLowerCase();
|
|
293
|
+
return (aSelf ? -1 : bSelf ? 1 : 0) || a.name.localeCompare(b.name);
|
|
294
|
+
});
|
|
295
|
+
console.log(chalk.bold(group));
|
|
296
|
+
for (const r of members) {
|
|
297
|
+
const isSelf = r.name.toLowerCase() === self.toLowerCase();
|
|
298
|
+
const prefix = isSelf ? chalk.cyan('▸') : ' ';
|
|
299
|
+
let glyph;
|
|
300
|
+
let text;
|
|
301
|
+
if (r.status === 'skipped') {
|
|
302
|
+
glyph = chalk.gray('○');
|
|
303
|
+
text = chalk.gray(String(r.reason ?? 'skipped'));
|
|
304
|
+
}
|
|
305
|
+
else if (r.status === 'failed') {
|
|
306
|
+
glyph = chalk.red('✕');
|
|
307
|
+
text = chalk.red(String(r.error ?? 'unreachable').split('\n')[0].slice(0, 80));
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
glyph = chalk.green('●');
|
|
311
|
+
text = summarizeResult(command, forwarded, r.value);
|
|
312
|
+
}
|
|
313
|
+
const selfNote = isSelf ? chalk.cyan(' ← this machine') : '';
|
|
314
|
+
console.log(` ${prefix} ${r.name.padEnd(nameW)} ${glyph} ${text}${selfNote}`);
|
|
315
|
+
}
|
|
316
|
+
console.log('');
|
|
317
|
+
}
|
|
318
|
+
if (summaryParts.length) {
|
|
319
|
+
console.log(chalk.gray(summaryParts.join(' · ')));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/** Run `agents <command> …` across every registered device and render the roster. */
|
|
323
|
+
export async function runFleetPassthrough(command, allArgs, spec, opts = {}) {
|
|
324
|
+
const self = opts.self ?? machineId();
|
|
325
|
+
const registry = await (opts.loadDevices ?? loadDevices)();
|
|
326
|
+
const planned = planFleetTargets(registry);
|
|
327
|
+
const targets = planned.map((t) => ({
|
|
328
|
+
name: t.device.name,
|
|
329
|
+
device: t.device,
|
|
330
|
+
skip: t.skip,
|
|
331
|
+
}));
|
|
332
|
+
const forwarded = buildFleetForwardedArgs(allArgs);
|
|
333
|
+
const runner = opts.runner ?? runOnDevice;
|
|
334
|
+
const localRunner = opts.localRunner ?? runLocalCommand;
|
|
335
|
+
const results = await fanOutDevices(targets, async (target) => {
|
|
336
|
+
const cmd = ['agents', ...forwarded];
|
|
337
|
+
const isSelf = target.device.name.toLowerCase() === self.toLowerCase();
|
|
338
|
+
const res = isSelf ? localRunner(cmd) : runner(target.device, cmd);
|
|
339
|
+
if (res.code !== 0) {
|
|
340
|
+
const detail = (res.stderr || res.stdout || 'unreachable').trim().slice(0, 200);
|
|
341
|
+
throw new Error(detail || 'unreachable');
|
|
342
|
+
}
|
|
343
|
+
return safeJsonParse(res.stdout);
|
|
344
|
+
}, { perDeviceTimeoutMs: 120_000 });
|
|
345
|
+
// Map fan-out results back to typed results with device attached for rendering.
|
|
346
|
+
const typedResults = results.map((r, i) => ({
|
|
347
|
+
...r,
|
|
348
|
+
device: targets[i].device,
|
|
349
|
+
}));
|
|
350
|
+
if (allArgs.includes('--json')) {
|
|
351
|
+
const out = {};
|
|
352
|
+
for (const r of typedResults) {
|
|
353
|
+
out[r.name] = r.status === 'ok' ? r.value : { error: r.error ?? r.reason ?? 'unknown' };
|
|
354
|
+
}
|
|
355
|
+
console.log(JSON.stringify(out, null, 2));
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
renderFleetRoster(command, forwarded, typedResults, self);
|
|
359
|
+
}
|
|
360
|
+
const anyFailed = typedResults.some((r) => r.status === 'failed');
|
|
361
|
+
process.exitCode = anyFailed ? 1 : 0;
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
158
364
|
/**
|
|
159
365
|
* Route `agents <command> … --host <name>` to a remote if the command is
|
|
160
366
|
* host-routable and a `--host` (or its `--device` alias) was given. Returns
|
|
@@ -167,11 +373,16 @@ async function resolveTargetHost(name, any) {
|
|
|
167
373
|
* @param command the resolved subcommand name (`process.argv`'s first non-flag).
|
|
168
374
|
* @param allArgs `process.argv.slice(2)` — the command name followed by its args.
|
|
169
375
|
*/
|
|
170
|
-
export async function maybeRunOnHost(command, allArgs) {
|
|
376
|
+
export async function maybeRunOnHost(command, allArgs, opts) {
|
|
171
377
|
const hostFlag = flagValue(allArgs, 'host', 'H');
|
|
172
378
|
const deviceFlag = flagValue(allArgs, 'device');
|
|
379
|
+
const hostsFlag = flagValue(allArgs, 'hosts');
|
|
380
|
+
const devicesFlag = flagValue(allArgs, 'devices');
|
|
173
381
|
const hostName = hostFlag ?? deviceFlag;
|
|
174
|
-
|
|
382
|
+
const fleetAll = isFleetAllSentinel(hostFlag, deviceFlag, hostsFlag, devicesFlag);
|
|
383
|
+
// Proceed when any routing flag is present, including the plural fleet flags
|
|
384
|
+
// that may carry the `all` sentinel.
|
|
385
|
+
if (!hostName && !hostsFlag && !devicesFlag)
|
|
175
386
|
return false;
|
|
176
387
|
// Commands with their own richer --host semantics must reach local commander
|
|
177
388
|
// BEFORE any single-target conflict gate. sessions/feed merge --host and
|
|
@@ -193,14 +404,18 @@ export async function maybeRunOnHost(command, allArgs) {
|
|
|
193
404
|
return false;
|
|
194
405
|
}
|
|
195
406
|
}
|
|
196
|
-
// `--hosts`
|
|
197
|
-
//
|
|
198
|
-
// a
|
|
199
|
-
|
|
200
|
-
if (allArgs.includes('--hosts'))
|
|
201
|
-
return false;
|
|
202
|
-
if (allArgs.includes('--devices') && command !== 'routines')
|
|
407
|
+
// `--hosts` / `--devices` are command-level fleet flags unless their value is
|
|
408
|
+
// the `all` sentinel, which this module fans out generically. On `routines`,
|
|
409
|
+
// a non-all `--devices` value is placement (which devices may run the routine).
|
|
410
|
+
if (allArgs.includes('--hosts') && hostsFlag?.toLowerCase() !== 'all')
|
|
203
411
|
return false;
|
|
412
|
+
if (allArgs.includes('--devices')) {
|
|
413
|
+
if (devicesFlag === undefined)
|
|
414
|
+
return false; // malformed, let commander error
|
|
415
|
+
const isAll = devicesFlag.toLowerCase() === 'all';
|
|
416
|
+
if (!isAll && command !== 'routines')
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
204
419
|
const spec = REMOTE_PASSTHROUGH[command];
|
|
205
420
|
if (!spec) {
|
|
206
421
|
// Flag was accepted (no raw commander "unknown option") but this group has
|
|
@@ -210,13 +425,23 @@ export async function maybeRunOnHost(command, allArgs) {
|
|
|
210
425
|
process.exitCode = 1;
|
|
211
426
|
return true;
|
|
212
427
|
}
|
|
213
|
-
//
|
|
214
|
-
//
|
|
428
|
+
// Reject a conflicting --host/--device pair before either the fleet fan-out
|
|
429
|
+
// or single-target path runs. Equal values (e.g. both `all`) are allowed.
|
|
215
430
|
if (hostFlag && deviceFlag && hostFlag !== deviceFlag) {
|
|
216
431
|
console.error(chalk.red('Conflicting --host/--device values — pass just one.'));
|
|
217
432
|
process.exitCode = 1;
|
|
218
433
|
return true;
|
|
219
434
|
}
|
|
435
|
+
// Generic fleet fan-out for the `all` sentinel — before single-host resolution
|
|
436
|
+
// so `all` is never treated as a literal hostname.
|
|
437
|
+
if (fleetAll) {
|
|
438
|
+
return runFleetPassthrough(command, allArgs, spec, opts);
|
|
439
|
+
}
|
|
440
|
+
// After the bailouts and fleet fan-out above, the only remaining path is a
|
|
441
|
+
// single-target --host/--device. Guard for the type checker: plural non-all
|
|
442
|
+
// flags and bare flags were already handled.
|
|
443
|
+
if (!hostName)
|
|
444
|
+
return false;
|
|
220
445
|
// Running against your own machine is just a local run — skip the SSH round-trip.
|
|
221
446
|
// `machineId()` is the same self-identifier the device registry and session
|
|
222
447
|
// sync use (lowercased short hostname); compare case-insensitively.
|
|
@@ -116,6 +116,8 @@ export const RUN_OPTION_FORWARDING = {
|
|
|
116
116
|
tailscale: 'local-only', // --tailscale/--no-tailscale gate the lease net mode; never forwarded
|
|
117
117
|
copyCreds: 'local-only', // copies creds TO the host before dispatch — local concern only
|
|
118
118
|
authCheck: 'local-only', // --no-auth-check gates the local interactive login preflight; --host runs skip that preflight entirely
|
|
119
|
+
// Deprecated alias for --device auto; resolved on the launching box before SSH.
|
|
120
|
+
smart: 'local-only',
|
|
119
121
|
};
|
|
120
122
|
/** Actionable messages for value-aware rejections, keyed by attribute name. */
|
|
121
123
|
export const RUN_OPTION_REJECT_MESSAGES = {
|
|
@@ -49,6 +49,8 @@ export interface HostPromptRun {
|
|
|
49
49
|
sessionId?: string;
|
|
50
50
|
/** Working directory on the host, already made remote-portable by the caller. */
|
|
51
51
|
remoteCwd?: string;
|
|
52
|
+
/** `remoteCwd` was derived from the local cwd — mirror it, don't fail on it. */
|
|
53
|
+
mirrorCwd?: boolean;
|
|
52
54
|
/** Stream progress and block until completion (default true). */
|
|
53
55
|
follow?: boolean;
|
|
54
56
|
timeoutMs?: number;
|
package/dist/lib/hq/floor.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ActiveSession } from '../session/active.js';
|
|
|
2
2
|
import type { OpenBlock } from '../feed.js';
|
|
3
3
|
import type { AgentStatusDetail, TaskInfo } from '../teams/api.js';
|
|
4
4
|
export type HqRoomKind = 'team' | 'machine' | 'lobby';
|
|
5
|
-
export type HqAgentMood = 'working' | 'waiting' | 'blocked' | 'celebrating' | 'idle';
|
|
5
|
+
export type HqAgentMood = 'working' | 'waiting' | 'blocked' | 'celebrating' | 'done' | 'idle';
|
|
6
6
|
export type HqAmbientKind = 'needs_input' | 'error' | 'pr' | 'team_active' | 'idle_scene';
|
|
7
7
|
export interface HqAction {
|
|
8
8
|
id: string;
|
package/dist/lib/hq/floor.js
CHANGED
|
@@ -28,6 +28,10 @@ function teammateName(session, teammatesById) {
|
|
|
28
28
|
return session.agentId;
|
|
29
29
|
}
|
|
30
30
|
function moodForSession(session, hasOpenBlock) {
|
|
31
|
+
if (session.status === 'abandoned')
|
|
32
|
+
return 'blocked';
|
|
33
|
+
if (session.status === 'closed')
|
|
34
|
+
return 'done';
|
|
31
35
|
if (session.status === 'input_required' || session.activity === 'waiting_input' || hasOpenBlock)
|
|
32
36
|
return 'waiting';
|
|
33
37
|
if (session.rateLimited)
|
|
@@ -175,6 +179,7 @@ export function buildHqFloor(input) {
|
|
|
175
179
|
}
|
|
176
180
|
}
|
|
177
181
|
for (const room of rooms.values()) {
|
|
182
|
+
const roomAgents = agents.filter((agent) => agent.roomId === room.id);
|
|
178
183
|
if (room.kind === 'team' && room.counts.running > 0) {
|
|
179
184
|
ambientEvents.push({
|
|
180
185
|
id: `team-active:${room.id}`,
|
|
@@ -184,7 +189,7 @@ export function buildHqFloor(input) {
|
|
|
184
189
|
intensity: 'low',
|
|
185
190
|
});
|
|
186
191
|
}
|
|
187
|
-
if (
|
|
192
|
+
if (roomAgents.length > 0 && roomAgents.every((agent) => agent.mood === 'idle')) {
|
|
188
193
|
ambientEvents.push({
|
|
189
194
|
id: `idle:${room.id}`,
|
|
190
195
|
kind: 'idle_scene',
|
package/dist/lib/mcp.js
CHANGED
|
@@ -18,7 +18,6 @@ import { getBinaryPath, getVersionHomePath } from './versions.js';
|
|
|
18
18
|
import { IS_WINDOWS, execFileShellSpec } from './platform/index.js';
|
|
19
19
|
import { AGENTS, getMcpConfigPathForHome, getProjectMcpConfigPath } from './agents.js';
|
|
20
20
|
import { isCapable } from './capabilities.js';
|
|
21
|
-
import { setGeminiAutoUpdateDisabled, updateGeminiSettings } from './gemini-settings.js';
|
|
22
21
|
/**
|
|
23
22
|
* Parse an MCP server config from a YAML file.
|
|
24
23
|
*/
|
|
@@ -401,7 +400,7 @@ export async function registerMcpCommandToTargets(targets, name, commandSpec, sc
|
|
|
401
400
|
function registerMcpCommand(agentId, name, commandSpec, scope, transport, options = {}) {
|
|
402
401
|
try {
|
|
403
402
|
validateMcpServerName(name);
|
|
404
|
-
if (agentId === 'hermes'
|
|
403
|
+
if (agentId === 'hermes') {
|
|
405
404
|
const server = {
|
|
406
405
|
name,
|
|
407
406
|
path: '',
|
|
@@ -413,12 +412,7 @@ function registerMcpCommand(agentId, name, commandSpec, scope, transport, option
|
|
|
413
412
|
: { command: commandSpec.command, args: commandSpec.args }),
|
|
414
413
|
},
|
|
415
414
|
};
|
|
416
|
-
|
|
417
|
-
installMcpToHermesConfig(server, options.home || os.homedir());
|
|
418
|
-
}
|
|
419
|
-
else {
|
|
420
|
-
installMcpToForgeConfig(server, options.home || os.homedir());
|
|
421
|
-
}
|
|
415
|
+
installMcpToHermesConfig(server, options.home || os.homedir());
|
|
422
416
|
return { success: true };
|
|
423
417
|
}
|
|
424
418
|
const bin = options.binary || AGENTS[agentId].cliCommand;
|
|
@@ -436,31 +430,6 @@ function registerMcpCommand(agentId, name, commandSpec, scope, transport, option
|
|
|
436
430
|
return { success: false, error: err.message };
|
|
437
431
|
}
|
|
438
432
|
}
|
|
439
|
-
/**
|
|
440
|
-
* Install MCP server to Gemini config file.
|
|
441
|
-
*/
|
|
442
|
-
function installMcpToGeminiConfig(server, versionHome) {
|
|
443
|
-
const configPath = path.join(versionHome, '.gemini', 'settings.json');
|
|
444
|
-
updateGeminiSettings(configPath, (config) => {
|
|
445
|
-
setGeminiAutoUpdateDisabled(config);
|
|
446
|
-
if (!config.mcpServers || typeof config.mcpServers !== 'object') {
|
|
447
|
-
config.mcpServers = {};
|
|
448
|
-
}
|
|
449
|
-
const mcpServers = config.mcpServers;
|
|
450
|
-
if (server.config.transport === 'stdio') {
|
|
451
|
-
mcpServers[server.name] = {
|
|
452
|
-
command: server.config.command,
|
|
453
|
-
args: server.config.args || [],
|
|
454
|
-
env: server.config.env || {},
|
|
455
|
-
};
|
|
456
|
-
}
|
|
457
|
-
else {
|
|
458
|
-
mcpServers[server.name] = {
|
|
459
|
-
url: server.config.url,
|
|
460
|
-
};
|
|
461
|
-
}
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
433
|
/**
|
|
465
434
|
* Install MCP server to Cursor config file.
|
|
466
435
|
*/
|
|
@@ -574,31 +543,6 @@ function installMcpToHermesConfig(server, versionHome) {
|
|
|
574
543
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
575
544
|
fs.writeFileSync(configPath, yaml.stringify(config), 'utf-8');
|
|
576
545
|
}
|
|
577
|
-
function installMcpToForgeConfig(server, versionHome) {
|
|
578
|
-
const configPath = path.join(versionHome, '.forge', '.mcp.json');
|
|
579
|
-
let config = {};
|
|
580
|
-
if (fs.existsSync(configPath)) {
|
|
581
|
-
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
582
|
-
}
|
|
583
|
-
if (!config.mcpServers || typeof config.mcpServers !== 'object' || Array.isArray(config.mcpServers)) {
|
|
584
|
-
config.mcpServers = {};
|
|
585
|
-
}
|
|
586
|
-
const mcpServers = config.mcpServers;
|
|
587
|
-
if (server.config.transport === 'stdio') {
|
|
588
|
-
mcpServers[server.name] = {
|
|
589
|
-
command: server.config.command,
|
|
590
|
-
args: server.config.args || [],
|
|
591
|
-
...(server.config.env && { env: server.config.env }),
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
else {
|
|
595
|
-
mcpServers[server.name] = {
|
|
596
|
-
url: server.config.url,
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
|
-
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
600
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
601
|
-
}
|
|
602
546
|
function installMcpToOpenCodeConfig(server, versionHome) {
|
|
603
547
|
const configPath = path.join(versionHome, '.config', 'opencode', 'opencode.jsonc');
|
|
604
548
|
let config = {};
|
|
@@ -643,10 +587,8 @@ function writeMcpConfigSupportsAgent(agentId) {
|
|
|
643
587
|
switch (agentId) {
|
|
644
588
|
case 'claude':
|
|
645
589
|
case 'cursor':
|
|
646
|
-
case 'gemini':
|
|
647
590
|
case 'kimi':
|
|
648
591
|
case 'droid':
|
|
649
|
-
case 'forge':
|
|
650
592
|
case 'openclaw':
|
|
651
593
|
case 'codex':
|
|
652
594
|
case 'grok':
|
|
@@ -672,10 +614,8 @@ export function writeMcpConfig(agentId, configPath, servers, mode = 'overwrite')
|
|
|
672
614
|
switch (agentId) {
|
|
673
615
|
case 'claude':
|
|
674
616
|
case 'cursor':
|
|
675
|
-
case 'gemini':
|
|
676
617
|
case 'kimi':
|
|
677
|
-
case 'droid':
|
|
678
|
-
case 'forge': {
|
|
618
|
+
case 'droid': {
|
|
679
619
|
let config = {};
|
|
680
620
|
if (fs.existsSync(configPath)) {
|
|
681
621
|
try {
|
|
@@ -888,10 +828,6 @@ export function installMcpServers(agentId, version, versionHome, mcpNames, optio
|
|
|
888
828
|
installMcpViaCodex(binaryPath, server, versionHome);
|
|
889
829
|
handled = true;
|
|
890
830
|
}
|
|
891
|
-
else if (agentId === 'gemini') {
|
|
892
|
-
installMcpToGeminiConfig(server, versionHome);
|
|
893
|
-
handled = true;
|
|
894
|
-
}
|
|
895
831
|
else if (agentId === 'cursor') {
|
|
896
832
|
installMcpToCursorConfig(server, versionHome);
|
|
897
833
|
handled = true;
|
|
@@ -943,10 +879,6 @@ export function installMcpServers(agentId, version, versionHome, mcpNames, optio
|
|
|
943
879
|
installMcpToHermesConfig(server, versionHome);
|
|
944
880
|
handled = true;
|
|
945
881
|
}
|
|
946
|
-
else if (agentId === 'forge') {
|
|
947
|
-
installMcpToForgeConfig(server, versionHome);
|
|
948
|
-
handled = true;
|
|
949
|
-
}
|
|
950
882
|
// Project-layer servers also get merged into the agent's project-level
|
|
951
883
|
// config (e.g., .mcp.json, .codex/config.toml) so the CLI discovers them
|
|
952
884
|
// when run inside the repo.
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
<string>com.phnx-labs.agents-menubar</string>
|
|
9
9
|
<key>CFBundleName</key>
|
|
10
10
|
<string>Agents Menu Bar</string>
|
|
11
|
+
<key>CFBundleIconFile</key>
|
|
12
|
+
<string>AppIcon</string>
|
|
11
13
|
<key>CFBundlePackageType</key>
|
|
12
14
|
<string>APPL</string>
|
|
13
15
|
<key>CFBundleShortVersionString</key>
|
|
Binary file
|