@phnx-labs/agents-cli 1.20.5 → 1.20.7
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 +13 -0
- package/README.md +1 -1
- package/dist/commands/browser.js +31 -4
- package/dist/commands/computer-actions.d.ts +36 -0
- package/dist/commands/computer-actions.js +328 -0
- package/dist/commands/computer.js +74 -55
- package/dist/commands/defaults.d.ts +7 -0
- package/dist/commands/defaults.js +89 -0
- package/dist/commands/exec.js +24 -6
- package/dist/commands/inspect.d.ts +38 -7
- package/dist/commands/inspect.js +194 -24
- package/dist/commands/rules.js +3 -3
- package/dist/commands/secrets.js +46 -9
- package/dist/commands/sessions.js +9 -12
- package/dist/commands/setup.js +2 -2
- package/dist/commands/teams.js +108 -11
- package/dist/commands/view.d.ts +12 -1
- package/dist/commands/view.js +121 -38
- package/dist/index.js +61 -22
- package/dist/lib/agents.d.ts +10 -6
- package/dist/lib/agents.js +23 -14
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +84 -3
- package/dist/lib/daemon.js +4 -7
- package/dist/lib/exec.d.ts +9 -0
- package/dist/lib/exec.js +85 -9
- package/dist/lib/migrate.js +6 -4
- package/dist/lib/permissions.d.ts +23 -0
- package/dist/lib/permissions.js +89 -7
- package/dist/lib/platform/exec.d.ts +9 -0
- package/dist/lib/platform/exec.js +24 -0
- package/dist/lib/platform/index.d.ts +20 -0
- package/dist/lib/platform/index.js +20 -0
- package/dist/lib/platform/paths.d.ts +22 -0
- package/dist/lib/platform/paths.js +49 -0
- package/dist/lib/platform/process.d.ts +12 -0
- package/dist/lib/platform/process.js +22 -0
- package/dist/lib/plugin-marketplace.js +1 -1
- package/dist/lib/project-launch.d.ts +5 -0
- package/dist/lib/project-launch.js +37 -0
- package/dist/lib/pty-client.js +13 -5
- package/dist/lib/pty-server.d.ts +24 -1
- package/dist/lib/pty-server.js +109 -29
- package/dist/lib/resources/rules.js +1 -1
- package/dist/lib/resources/skills.js +1 -1
- package/dist/lib/resources.d.ts +2 -0
- package/dist/lib/resources.js +2 -1
- package/dist/lib/rotate.js +6 -18
- package/dist/lib/run-config.d.ts +9 -0
- package/dist/lib/run-config.js +35 -0
- package/dist/lib/run-defaults.d.ts +42 -0
- package/dist/lib/run-defaults.js +180 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/install-helper.d.ts +11 -3
- package/dist/lib/secrets/install-helper.js +48 -6
- package/dist/lib/secrets/linux.d.ts +12 -0
- package/dist/lib/secrets/linux.js +30 -16
- package/dist/lib/session/artifacts.js +8 -2
- package/dist/lib/shims.d.ts +9 -1
- package/dist/lib/shims.js +80 -3
- package/dist/lib/staleness/detectors/hooks.js +1 -1
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/teams/agents.js +5 -7
- package/dist/lib/teams/api.d.ts +67 -0
- package/dist/lib/teams/api.js +78 -0
- package/dist/lib/types.d.ts +15 -6
- package/dist/lib/versions.js +4 -4
- package/package.json +5 -2
- package/scripts/postinstall.js +18 -1
package/dist/index.js
CHANGED
|
@@ -76,6 +76,7 @@ import { registerDaemonCommands } from './commands/daemon.js';
|
|
|
76
76
|
import { registerRoutinesCommands } from './commands/routines.js';
|
|
77
77
|
import { registerRunCommand } from './commands/exec.js';
|
|
78
78
|
import { registerModelsCommand } from './commands/models.js';
|
|
79
|
+
import { registerDefaultsCommands } from './commands/defaults.js';
|
|
79
80
|
import { registerPruneCommand } from './commands/prune.js';
|
|
80
81
|
import { registerTrashCommands } from './commands/trash.js';
|
|
81
82
|
import { registerDoctorCommand } from './commands/doctor.js';
|
|
@@ -102,6 +103,21 @@ import { isInteractiveTerminal, isPromptCancelled } from './commands/utils.js';
|
|
|
102
103
|
import { AGENTS } from './lib/agents.js';
|
|
103
104
|
import { getGlobalDefault, listInstalledVersions } from './lib/versions.js';
|
|
104
105
|
import { addShimsToPath, ensureShimCurrent, ensureVersionedAliasCurrent, getPathShadowingExecutable, getPathSetupInstructions, getShimsDir, isShimsInPath, listAgentsWithInstalledVersions, removeLegacyUserShim, } from './lib/shims.js';
|
|
106
|
+
import { IS_WINDOWS } from './lib/platform/index.js';
|
|
107
|
+
// Transparent shim delegate: the generated Windows `.cmd` shims invoke
|
|
108
|
+
// `agents __shim <agent>[@version] <raw args>`. Intercept here, before commander
|
|
109
|
+
// parses anything, so the agent's own flags (`--help`, `--version`, etc.) pass
|
|
110
|
+
// through completely untouched and we skip registering the full command tree.
|
|
111
|
+
if (process.argv[2] === '__shim') {
|
|
112
|
+
const spec = process.argv[3] || '';
|
|
113
|
+
const rawArgs = process.argv.slice(4);
|
|
114
|
+
const atIndex = spec.indexOf('@');
|
|
115
|
+
const agent = atIndex === -1 ? spec : spec.slice(0, atIndex);
|
|
116
|
+
const pinned = atIndex === -1 ? undefined : spec.slice(atIndex + 1);
|
|
117
|
+
const { execShimPassthrough } = await import('./lib/exec.js');
|
|
118
|
+
const code = await execShimPassthrough(agent, rawArgs, process.cwd(), pinned || undefined);
|
|
119
|
+
process.exit(code);
|
|
120
|
+
}
|
|
105
121
|
const program = new Command();
|
|
106
122
|
program
|
|
107
123
|
.name('agents')
|
|
@@ -133,7 +149,7 @@ Agent versions:
|
|
|
133
149
|
prune cleanup [target] Remove orphan resources and older duplicate version installs
|
|
134
150
|
trash Inspect and restore soft-deleted version directories
|
|
135
151
|
view [agent[@version]] List versions, or inspect one in detail
|
|
136
|
-
inspect <
|
|
152
|
+
inspect <target> Deep details for one agent+version, or a DotAgents repo (user|system|project|alias|path)
|
|
137
153
|
|
|
138
154
|
Agent configuration (synced across versions):
|
|
139
155
|
rules Instructions given to agents (CLAUDE.md, etc.)
|
|
@@ -151,6 +167,7 @@ Packages:
|
|
|
151
167
|
|
|
152
168
|
Run and dispatch:
|
|
153
169
|
run <agent|profile> [prompt] Run an agent. Omit prompt for interactive mode.
|
|
170
|
+
defaults Configure run defaults by agent/version selector
|
|
154
171
|
teams Coordinate multiple agents on shared work
|
|
155
172
|
routines Run agents on a cron schedule (scheduler auto-starts)
|
|
156
173
|
sessions Browse, search, and replay past runs (live-search in TTY; grouped by workspace)
|
|
@@ -217,9 +234,12 @@ async function showWhatsNew(fromVersion, toVersion) {
|
|
|
217
234
|
const versionMatch = line.match(/^## (\d+\.\d+\.\d+)/);
|
|
218
235
|
if (versionMatch) {
|
|
219
236
|
currentVersion = versionMatch[1];
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
237
|
+
// Only the range the user actually moved through: (fromVersion, toVersion].
|
|
238
|
+
// Bounding the top end matters when upgrading to a specific older
|
|
239
|
+
// version, and guards against a changelog that lists unreleased entries.
|
|
240
|
+
const inRange = compareVersions(currentVersion, fromVersion) > 0 &&
|
|
241
|
+
compareVersions(currentVersion, toVersion) <= 0;
|
|
242
|
+
inRelevantSection = inRange;
|
|
223
243
|
if (inRelevantSection) {
|
|
224
244
|
relevantChanges.push('');
|
|
225
245
|
relevantChanges.push(chalk.bold(`v${currentVersion}`));
|
|
@@ -279,11 +299,14 @@ function saveUpdateCheck(latestVersion) {
|
|
|
279
299
|
}
|
|
280
300
|
}
|
|
281
301
|
/** Fetch the exact latest npm version plus its registry integrity hash. */
|
|
282
|
-
async function
|
|
283
|
-
const response = await fetch(`https://registry.npmjs.org/${NPM_PACKAGE_NAME}
|
|
302
|
+
async function fetchNpmPackageMetadata(versionOrTag = 'latest', timeoutMs = 5000) {
|
|
303
|
+
const response = await fetch(`https://registry.npmjs.org/${NPM_PACKAGE_NAME}/${versionOrTag}`, {
|
|
284
304
|
signal: AbortSignal.timeout(timeoutMs),
|
|
285
305
|
});
|
|
286
306
|
if (!response.ok) {
|
|
307
|
+
if (response.status === 404) {
|
|
308
|
+
throw new Error(`${NPM_PACKAGE_NAME}@${versionOrTag} not found on npm`);
|
|
309
|
+
}
|
|
287
310
|
throw new Error('Could not reach npm registry');
|
|
288
311
|
}
|
|
289
312
|
const data = await response.json();
|
|
@@ -338,7 +361,7 @@ async function promptUpgrade(latestVersion) {
|
|
|
338
361
|
const { spawnSync } = await import('child_process');
|
|
339
362
|
let spinner = ora('Resolving package metadata...').start();
|
|
340
363
|
try {
|
|
341
|
-
const metadata = await
|
|
364
|
+
const metadata = await fetchNpmPackageMetadata();
|
|
342
365
|
spinner.succeed(`Resolved ${NPM_PACKAGE_NAME}@${metadata.version}`);
|
|
343
366
|
printResolvedPackage(metadata);
|
|
344
367
|
const approved = await confirm({
|
|
@@ -453,6 +476,13 @@ async function maybeBootstrapShimIntegration(requestedCommand, helpOrVersionRequ
|
|
|
453
476
|
for (const agent of installedAgents) {
|
|
454
477
|
removeLegacyUserShim(agent);
|
|
455
478
|
}
|
|
479
|
+
// The remaining flow is rc-file PATH repair, which is POSIX-only. On Windows
|
|
480
|
+
// the shims were just regenerated (incl. `.cmd` companions) above; PATH setup
|
|
481
|
+
// is covered by the install-time guidance, so stop here rather than printing
|
|
482
|
+
// shell-rc instructions that don't apply.
|
|
483
|
+
if (IS_WINDOWS) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
456
486
|
const defaultAgents = installedAgents.filter((agent) => getGlobalDefault(agent));
|
|
457
487
|
const shadowed = defaultAgents
|
|
458
488
|
.map((agent) => ({ agent, shadowedBy: getPathShadowingExecutable(agent) }))
|
|
@@ -583,6 +613,7 @@ registerPackagesCommands(program);
|
|
|
583
613
|
registerDaemonCommands(program);
|
|
584
614
|
registerRoutinesCommands(program);
|
|
585
615
|
registerRunCommand(program);
|
|
616
|
+
registerDefaultsCommands(program);
|
|
586
617
|
registerModelsCommand(program);
|
|
587
618
|
registerPruneCommand(program);
|
|
588
619
|
registerTrashCommands(program);
|
|
@@ -627,26 +658,31 @@ for (const alias of ['jobs', 'cron']) {
|
|
|
627
658
|
}
|
|
628
659
|
program
|
|
629
660
|
.command('upgrade')
|
|
630
|
-
.description('Upgrade agents-cli to the latest version')
|
|
661
|
+
.description('Upgrade agents-cli to the latest version (or a specific [version])')
|
|
662
|
+
.argument('[version]', 'Target version or dist-tag to install (default: latest)')
|
|
631
663
|
.option('-y, --yes', 'Install without an interactive confirmation prompt')
|
|
632
|
-
.action(async (options) => {
|
|
633
|
-
|
|
664
|
+
.action(async (version, options) => {
|
|
665
|
+
const target = version ?? 'latest';
|
|
666
|
+
let spinner = ora(version ? `Resolving ${NPM_PACKAGE_NAME}@${target}...` : 'Checking for updates...').start();
|
|
634
667
|
try {
|
|
635
|
-
const metadata = await
|
|
636
|
-
const
|
|
637
|
-
if (
|
|
638
|
-
spinner.succeed(`Already on
|
|
668
|
+
const metadata = await fetchNpmPackageMetadata(target);
|
|
669
|
+
const resolvedVersion = metadata.version;
|
|
670
|
+
if (resolvedVersion === VERSION) {
|
|
671
|
+
spinner.succeed(`Already on ${VERSION}`);
|
|
639
672
|
return;
|
|
640
673
|
}
|
|
641
|
-
|
|
642
|
-
|
|
674
|
+
// For `latest` (no explicit version) skip when already ahead. When a
|
|
675
|
+
// version is named explicitly, honor it even if it's a downgrade.
|
|
676
|
+
if (!version && compareVersions(resolvedVersion, VERSION) <= 0) {
|
|
677
|
+
spinner.succeed(`Already ahead of latest (${VERSION} >= ${resolvedVersion})`);
|
|
643
678
|
return;
|
|
644
679
|
}
|
|
645
|
-
|
|
680
|
+
const direction = compareVersions(resolvedVersion, VERSION) < 0 ? 'Downgrade' : 'Upgrade';
|
|
681
|
+
spinner.succeed(`Resolved ${NPM_PACKAGE_NAME}@${resolvedVersion}`);
|
|
646
682
|
printResolvedPackage(metadata);
|
|
647
683
|
if (isInteractiveTerminal() && !options.yes) {
|
|
648
684
|
const approved = await confirm({
|
|
649
|
-
message: `Install ${NPM_PACKAGE_NAME}@${
|
|
685
|
+
message: `Install ${NPM_PACKAGE_NAME}@${resolvedVersion}?`,
|
|
650
686
|
default: false,
|
|
651
687
|
});
|
|
652
688
|
if (!approved) {
|
|
@@ -654,14 +690,17 @@ program
|
|
|
654
690
|
return;
|
|
655
691
|
}
|
|
656
692
|
}
|
|
657
|
-
spinner = ora(
|
|
693
|
+
spinner = ora(`${direction === 'Downgrade' ? 'Downgrading' : 'Upgrading'} ${VERSION} -> ${resolvedVersion}...`).start();
|
|
658
694
|
await installResolvedPackage(metadata);
|
|
659
|
-
spinner.succeed(
|
|
660
|
-
|
|
695
|
+
spinner.succeed(`${direction}d to ${resolvedVersion}`);
|
|
696
|
+
// Only show the changelog for a genuine upgrade range.
|
|
697
|
+
if (compareVersions(resolvedVersion, VERSION) > 0) {
|
|
698
|
+
await showWhatsNew(VERSION, resolvedVersion);
|
|
699
|
+
}
|
|
661
700
|
}
|
|
662
701
|
catch (err) {
|
|
663
702
|
spinner.fail('Upgrade failed');
|
|
664
|
-
console.log(chalk.gray(
|
|
703
|
+
console.log(chalk.gray(`Run manually: agents upgrade ${version ? version + ' ' : ''}--yes`));
|
|
665
704
|
}
|
|
666
705
|
});
|
|
667
706
|
registerPullCommand(program);
|
package/dist/lib/agents.d.ts
CHANGED
|
@@ -69,12 +69,16 @@ export declare function ensureSkillsDir(agentId: AgentId): void;
|
|
|
69
69
|
* The agent's config-dir name relative to $HOME — e.g. '.claude',
|
|
70
70
|
* '.gemini/antigravity-cli', '.config/amp', '.kimi-code'.
|
|
71
71
|
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
72
|
+
* Path segment to join onto a (version) home root when locating an agent's
|
|
73
|
+
* commands/skills/plugins. Do NOT hardcode `.${agentId}`: it is wrong for
|
|
74
|
+
* every agent whose config dir is nested or under ~/.config — antigravity
|
|
75
|
+
* (~/.gemini/antigravity-cli), amp (~/.config/amp), goose (~/.config/goose),
|
|
76
|
+
* kimi (~/.kimi-code). Mirrors the shim configDirName derivation in shims.ts.
|
|
77
|
+
*
|
|
78
|
+
* Relativized against the module-level HOME constant (the same value used to
|
|
79
|
+
* build every `configDir`), NOT a fresh `os.homedir()` — so the result stays a
|
|
80
|
+
* clean relative name even when HOME is overridden after module load (tests,
|
|
81
|
+
* sandboxes). Using `os.homedir()` here would yield `../../real/home/.claude`.
|
|
78
82
|
*/
|
|
79
83
|
export declare function agentConfigDirName(agentId: AgentId): string;
|
|
80
84
|
/** Account identity and billing information extracted from an agent's auth config. */
|
package/dist/lib/agents.js
CHANGED
|
@@ -446,13 +446,18 @@ export const AGENTS = {
|
|
|
446
446
|
rulesImports: true,
|
|
447
447
|
},
|
|
448
448
|
},
|
|
449
|
+
// Kimi Code CLI (`kimi`) — Moonshot AI coding agent.
|
|
450
|
+
// Install: `curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash`
|
|
451
|
+
// or: `npm install -g @moonshot-ai/kimi-code`
|
|
452
|
+
// Config: `~/.kimi-code/config.toml`, `~/.kimi-code/mcp.json`,
|
|
453
|
+
// `~/.kimi-code/skills/`, `~/.kimi-code/hooks/`
|
|
449
454
|
kimi: {
|
|
450
455
|
id: 'kimi',
|
|
451
456
|
name: 'Kimi',
|
|
452
457
|
color: 'magentaBright',
|
|
453
|
-
cliCommand: 'kimi
|
|
454
|
-
npmPackage: '',
|
|
455
|
-
installScript: '',
|
|
458
|
+
cliCommand: 'kimi',
|
|
459
|
+
npmPackage: '@moonshot-ai/kimi-code',
|
|
460
|
+
installScript: 'curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash',
|
|
456
461
|
configDir: path.join(HOME, '.kimi-code'),
|
|
457
462
|
commandsDir: '',
|
|
458
463
|
commandsSubdir: '',
|
|
@@ -465,14 +470,14 @@ export const AGENTS = {
|
|
|
465
470
|
capabilities: {
|
|
466
471
|
hooks: true,
|
|
467
472
|
mcp: true,
|
|
468
|
-
allowlist:
|
|
469
|
-
skills:
|
|
473
|
+
allowlist: true,
|
|
474
|
+
skills: true,
|
|
470
475
|
commands: false,
|
|
471
|
-
plugins:
|
|
476
|
+
plugins: true,
|
|
472
477
|
subagents: false,
|
|
473
478
|
rules: { file: 'AGENTS.md' },
|
|
474
479
|
workflows: false,
|
|
475
|
-
modes: ['plan', 'edit', 'skip'],
|
|
480
|
+
modes: ['plan', 'edit', 'auto', 'skip'],
|
|
476
481
|
rulesImports: false,
|
|
477
482
|
},
|
|
478
483
|
},
|
|
@@ -690,15 +695,19 @@ export function ensureSkillsDir(agentId) {
|
|
|
690
695
|
* The agent's config-dir name relative to $HOME — e.g. '.claude',
|
|
691
696
|
* '.gemini/antigravity-cli', '.config/amp', '.kimi-code'.
|
|
692
697
|
*
|
|
693
|
-
*
|
|
694
|
-
*
|
|
695
|
-
*
|
|
696
|
-
*
|
|
697
|
-
*
|
|
698
|
-
*
|
|
698
|
+
* Path segment to join onto a (version) home root when locating an agent's
|
|
699
|
+
* commands/skills/plugins. Do NOT hardcode `.${agentId}`: it is wrong for
|
|
700
|
+
* every agent whose config dir is nested or under ~/.config — antigravity
|
|
701
|
+
* (~/.gemini/antigravity-cli), amp (~/.config/amp), goose (~/.config/goose),
|
|
702
|
+
* kimi (~/.kimi-code). Mirrors the shim configDirName derivation in shims.ts.
|
|
703
|
+
*
|
|
704
|
+
* Relativized against the module-level HOME constant (the same value used to
|
|
705
|
+
* build every `configDir`), NOT a fresh `os.homedir()` — so the result stays a
|
|
706
|
+
* clean relative name even when HOME is overridden after module load (tests,
|
|
707
|
+
* sandboxes). Using `os.homedir()` here would yield `../../real/home/.claude`.
|
|
699
708
|
*/
|
|
700
709
|
export function agentConfigDirName(agentId) {
|
|
701
|
-
return path.relative(
|
|
710
|
+
return path.relative(HOME, AGENTS[agentId].configDir);
|
|
702
711
|
}
|
|
703
712
|
/** Return the email address associated with the agent's auth config, or null. */
|
|
704
713
|
export async function getAccountEmail(agentId, home) {
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import type { ChromeOptions } from './types.js';
|
|
2
2
|
import type { BrowserType } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* True when `binaryPath` is a shebang script rather than a native browser
|
|
5
|
+
* executable. The Linux distro launchers (`/usr/bin/brave-browser`, …) are such
|
|
6
|
+
* scripts; `launchBrowser` can't drive one over `--remote-debugging-pipe` (see
|
|
7
|
+
* resolveBrowserBinary). `profiles doctor` uses this to flag a profile whose
|
|
8
|
+
* binary resolves to a wrapper we couldn't unwrap. Shebang scripts are a
|
|
9
|
+
* Linux/Unix concept — returns false on Windows/macOS app bundles.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isLauncherScript(binaryPath: string): boolean;
|
|
12
|
+
export declare function resolveBrowserBinary(binaryPath: string): string;
|
|
3
13
|
export declare function findBrowserPath(browserType: BrowserType, customBinary?: string): string;
|
|
4
14
|
/**
|
|
5
15
|
* Walk the per-platform priority list and return the first browser that's
|
|
@@ -42,12 +42,90 @@ const BROWSER_PATHS = {
|
|
|
42
42
|
custom: [],
|
|
43
43
|
},
|
|
44
44
|
};
|
|
45
|
+
/**
|
|
46
|
+
* On Debian/Ubuntu the canonical launchers under `/usr/bin`
|
|
47
|
+
* (`brave-browser`, `google-chrome`, `chromium`) are not the browser ELF —
|
|
48
|
+
* they're `#!/bin/bash` wrapper scripts (the upstream Chromium wrapper) that,
|
|
49
|
+
* as their final step, run the real binary as a NON-exec child:
|
|
50
|
+
*
|
|
51
|
+
* exec < /dev/null
|
|
52
|
+
* exec > >(exec cat)
|
|
53
|
+
* exec 2> >(exec cat >&2)
|
|
54
|
+
* "$HERE/brave" "$@" || true
|
|
55
|
+
*
|
|
56
|
+
* That breaks `launchBrowser`'s `--remote-debugging-pipe` transport two ways:
|
|
57
|
+
* the std-fd sanitization (and the extra `cat` process-substitution children)
|
|
58
|
+
* disturbs the inherited CDP pipe on fd 3/4, and the pid we record is the
|
|
59
|
+
* wrapper's, not the browser's. The symptom is `read ECONNRESET` /
|
|
60
|
+
* `CDP connection closed` right after spawn (issue #229).
|
|
61
|
+
*
|
|
62
|
+
* Follow the wrapper to the ELF it execs. The wrapper sets
|
|
63
|
+
* `HERE="dirname(readlink -f "$0")"` and invokes `"$HERE/<name>"`, so we
|
|
64
|
+
* resolve the script path, scan for that invocation line, and join the two.
|
|
65
|
+
* Returns the original path untouched when it's already an ELF, when it's not
|
|
66
|
+
* a resolvable wrapper, or on any non-Linux platform.
|
|
67
|
+
*/
|
|
68
|
+
function readsAsShebangScript(binaryPath) {
|
|
69
|
+
let fd;
|
|
70
|
+
try {
|
|
71
|
+
fd = fs.openSync(binaryPath, 'r');
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const head = Buffer.alloc(2);
|
|
78
|
+
fs.readSync(fd, head, 0, 2, 0);
|
|
79
|
+
// ELF binaries start with 0x7f 'E'; shebang scripts with '#!'.
|
|
80
|
+
return head[0] === 0x23 && head[1] === 0x21;
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
fs.closeSync(fd);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* True when `binaryPath` is a shebang script rather than a native browser
|
|
88
|
+
* executable. The Linux distro launchers (`/usr/bin/brave-browser`, …) are such
|
|
89
|
+
* scripts; `launchBrowser` can't drive one over `--remote-debugging-pipe` (see
|
|
90
|
+
* resolveBrowserBinary). `profiles doctor` uses this to flag a profile whose
|
|
91
|
+
* binary resolves to a wrapper we couldn't unwrap. Shebang scripts are a
|
|
92
|
+
* Linux/Unix concept — returns false on Windows/macOS app bundles.
|
|
93
|
+
*/
|
|
94
|
+
export function isLauncherScript(binaryPath) {
|
|
95
|
+
if (os.platform() === 'win32')
|
|
96
|
+
return false;
|
|
97
|
+
return readsAsShebangScript(binaryPath);
|
|
98
|
+
}
|
|
99
|
+
export function resolveBrowserBinary(binaryPath) {
|
|
100
|
+
if (os.platform() !== 'linux')
|
|
101
|
+
return binaryPath;
|
|
102
|
+
// Only shebang scripts need unwrapping; a real ELF passes straight through.
|
|
103
|
+
if (!readsAsShebangScript(binaryPath))
|
|
104
|
+
return binaryPath;
|
|
105
|
+
let script;
|
|
106
|
+
let realScriptPath;
|
|
107
|
+
try {
|
|
108
|
+
realScriptPath = fs.realpathSync(binaryPath);
|
|
109
|
+
script = fs.readFileSync(realScriptPath, 'utf8');
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return binaryPath;
|
|
113
|
+
}
|
|
114
|
+
// Match the Chromium wrapper's launch line: `"$HERE/<name>" "$@"`, optionally
|
|
115
|
+
// prefixed with `exec -a "$0"`. The captured name is the real ELF, sitting in
|
|
116
|
+
// the same directory as the resolved wrapper.
|
|
117
|
+
const match = script.match(/"\$HERE\/([A-Za-z0-9._-]+)"\s+"\$@"/);
|
|
118
|
+
if (!match)
|
|
119
|
+
return binaryPath;
|
|
120
|
+
const realBinary = path.join(path.dirname(realScriptPath), match[1]);
|
|
121
|
+
return fs.existsSync(realBinary) ? realBinary : binaryPath;
|
|
122
|
+
}
|
|
45
123
|
export function findBrowserPath(browserType, customBinary) {
|
|
46
124
|
if (customBinary) {
|
|
47
125
|
if (!fs.existsSync(customBinary)) {
|
|
48
126
|
throw new Error(`Custom binary not found: ${customBinary}`);
|
|
49
127
|
}
|
|
50
|
-
return customBinary;
|
|
128
|
+
return resolveBrowserBinary(customBinary);
|
|
51
129
|
}
|
|
52
130
|
if (browserType === 'custom') {
|
|
53
131
|
throw new Error('browser: custom requires a binary path in the profile');
|
|
@@ -60,9 +138,12 @@ export function findBrowserPath(browserType, customBinary) {
|
|
|
60
138
|
const candidates = platformPaths[browserType] || [];
|
|
61
139
|
for (const p of candidates) {
|
|
62
140
|
if (fs.existsSync(p)) {
|
|
63
|
-
return p;
|
|
141
|
+
return resolveBrowserBinary(p);
|
|
64
142
|
}
|
|
65
143
|
}
|
|
144
|
+
if (browserType === 'comet' && platform !== 'darwin') {
|
|
145
|
+
throw new Error('Browser "comet" is macOS-only (Comet is a macOS Chromium fork). Use chrome, chromium, brave, or edge on this platform.');
|
|
146
|
+
}
|
|
66
147
|
throw new Error(`Browser "${browserType}" not found. Install it first.`);
|
|
67
148
|
}
|
|
68
149
|
// Per-platform Chromium-family priority list for "no --profile" auto-pick.
|
|
@@ -98,7 +179,7 @@ export function findFirstInstalledBrowser(platform = os.platform()) {
|
|
|
98
179
|
const candidates = platformPaths[browserType] || [];
|
|
99
180
|
for (const p of candidates) {
|
|
100
181
|
if (fs.existsSync(p)) {
|
|
101
|
-
return { browserType, binary: p };
|
|
182
|
+
return { browserType, binary: resolveBrowserBinary(p) };
|
|
102
183
|
}
|
|
103
184
|
}
|
|
104
185
|
}
|
package/dist/lib/daemon.js
CHANGED
|
@@ -11,6 +11,7 @@ import * as fs from 'fs';
|
|
|
11
11
|
import * as path from 'path';
|
|
12
12
|
import * as os from 'os';
|
|
13
13
|
import { getDaemonDir as getDaemonDirRoot } from './state.js';
|
|
14
|
+
import { isAlive } from './platform/index.js';
|
|
14
15
|
import { listJobs as listAllJobs } from './routines.js';
|
|
15
16
|
import { JobScheduler } from './scheduler.js';
|
|
16
17
|
import { executeJobDetached, monitorRunningJobs } from './runner.js';
|
|
@@ -114,14 +115,10 @@ export function isDaemonRunning() {
|
|
|
114
115
|
const pid = readDaemonPid();
|
|
115
116
|
if (!pid)
|
|
116
117
|
return false;
|
|
117
|
-
|
|
118
|
-
process.kill(pid, 0);
|
|
118
|
+
if (isAlive(pid))
|
|
119
119
|
return true;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
removeDaemonPid();
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
120
|
+
removeDaemonPid();
|
|
121
|
+
return false;
|
|
125
122
|
}
|
|
126
123
|
/** Redact values that look like tokens or credentials in a log message. */
|
|
127
124
|
function redactSecrets(message) {
|
package/dist/lib/exec.d.ts
CHANGED
|
@@ -95,6 +95,15 @@ export declare const AGENT_COMMANDS: Record<AgentId, AgentCommandTemplate>;
|
|
|
95
95
|
export declare function buildExecCommand(options: ExecOptions): string[];
|
|
96
96
|
/** Spawn an agent and return its exit code. Convenience wrapper over spawnAgent. */
|
|
97
97
|
export declare function execAgent(options: ExecOptions): Promise<number>;
|
|
98
|
+
/**
|
|
99
|
+
* Transparent passthrough exec for generated shims — the node-side delegate that
|
|
100
|
+
* Windows `.cmd` shims call. Resolves the active version (explicit pin, else
|
|
101
|
+
* project/default) and execs the real binary with the user's RAW args and the
|
|
102
|
+
* per-version env isolation, WITHOUT injecting mode/model/reasoning flags. This
|
|
103
|
+
* mirrors what the POSIX bash shim does inline (`exec $BINARY $launchArgs "$@"`),
|
|
104
|
+
* keeping version resolution in one place instead of reimplementing it in batch.
|
|
105
|
+
*/
|
|
106
|
+
export declare function execShimPassthrough(agent: AgentId, rawArgs: string[], cwd: string, pinnedVersion?: string): Promise<number>;
|
|
98
107
|
/**
|
|
99
108
|
* Patterns that indicate a rate/usage limit. Matching is intentionally broad
|
|
100
109
|
* because providers phrase these differently -- Anthropic uses "5-hour limit"
|
package/dist/lib/exec.js
CHANGED
|
@@ -11,7 +11,7 @@ import * as path from 'path';
|
|
|
11
11
|
import { ALL_MODES } from './types.js';
|
|
12
12
|
import { AGENTS } from './agents.js';
|
|
13
13
|
import { parseTimeout } from './routines.js';
|
|
14
|
-
import { getVersionHomePath, isVersionInstalled, resolveVersion } from './versions.js';
|
|
14
|
+
import { getBinaryPath, getVersionHomePath, isVersionInstalled, resolveVersion } from './versions.js';
|
|
15
15
|
import { resolveModel, buildReasoningFlags } from './models.js';
|
|
16
16
|
import { maybeRotate, createTimer, redactPrompt, redactArgs } from './events.js';
|
|
17
17
|
import { sanitizeProcessEnv } from './secrets/bundles.js';
|
|
@@ -118,6 +118,7 @@ export function buildExecEnv(options) {
|
|
|
118
118
|
}
|
|
119
119
|
delete result.CODEX_HOME;
|
|
120
120
|
delete result.COPILOT_HOME;
|
|
121
|
+
delete result.KIMI_CODE_HOME;
|
|
121
122
|
}
|
|
122
123
|
else if (options.agent === 'codex') {
|
|
123
124
|
const cwd = options.cwd || process.cwd();
|
|
@@ -130,6 +131,7 @@ export function buildExecEnv(options) {
|
|
|
130
131
|
}
|
|
131
132
|
delete result.CLAUDE_CONFIG_DIR;
|
|
132
133
|
delete result.COPILOT_HOME;
|
|
134
|
+
delete result.KIMI_CODE_HOME;
|
|
133
135
|
}
|
|
134
136
|
else if (options.agent === 'copilot') {
|
|
135
137
|
// Copilot honors COPILOT_HOME (relocates ~/.copilot, including settings,
|
|
@@ -145,11 +147,28 @@ export function buildExecEnv(options) {
|
|
|
145
147
|
}
|
|
146
148
|
delete result.CLAUDE_CONFIG_DIR;
|
|
147
149
|
delete result.CODEX_HOME;
|
|
150
|
+
delete result.KIMI_CODE_HOME;
|
|
151
|
+
}
|
|
152
|
+
else if (options.agent === 'kimi') {
|
|
153
|
+
// Kimi honors KIMI_CODE_HOME (relocates ~/.kimi-code, including config,
|
|
154
|
+
// skills, hooks, sessions). Pin it at the per-version home.
|
|
155
|
+
const cwd = options.cwd || process.cwd();
|
|
156
|
+
const resolvedVersion = options.version ?? resolveVersion('kimi', cwd);
|
|
157
|
+
const version = options.version
|
|
158
|
+
? resolvedVersion
|
|
159
|
+
: (resolvedVersion && isVersionInstalled('kimi', resolvedVersion) ? resolvedVersion : null);
|
|
160
|
+
if (version) {
|
|
161
|
+
result.KIMI_CODE_HOME = path.join(getVersionHomePath('kimi', version), '.kimi-code');
|
|
162
|
+
}
|
|
163
|
+
delete result.CLAUDE_CONFIG_DIR;
|
|
164
|
+
delete result.CODEX_HOME;
|
|
165
|
+
delete result.COPILOT_HOME;
|
|
148
166
|
}
|
|
149
167
|
else {
|
|
150
168
|
delete result.CLAUDE_CONFIG_DIR;
|
|
151
169
|
delete result.CODEX_HOME;
|
|
152
170
|
delete result.COPILOT_HOME;
|
|
171
|
+
delete result.KIMI_CODE_HOME;
|
|
153
172
|
}
|
|
154
173
|
return {
|
|
155
174
|
...result,
|
|
@@ -322,14 +341,15 @@ export const AGENT_COMMANDS = {
|
|
|
322
341
|
modelFlag: '--model',
|
|
323
342
|
},
|
|
324
343
|
kimi: {
|
|
325
|
-
base: ['kimi
|
|
344
|
+
base: ['kimi'],
|
|
326
345
|
promptFlag: '-p',
|
|
327
346
|
modeFlags: {
|
|
328
|
-
plan: [],
|
|
347
|
+
plan: ['--plan'],
|
|
329
348
|
edit: [],
|
|
330
|
-
|
|
349
|
+
auto: ['--auto'],
|
|
350
|
+
skip: ['--yolo'],
|
|
331
351
|
},
|
|
332
|
-
jsonFlags: [],
|
|
352
|
+
jsonFlags: ['--output-format', 'stream-json'],
|
|
333
353
|
modelFlag: '--model',
|
|
334
354
|
},
|
|
335
355
|
};
|
|
@@ -347,10 +367,27 @@ export function buildExecCommand(options) {
|
|
|
347
367
|
// Resolve to the absolute path of the shim so spawn doesn't depend on PATH —
|
|
348
368
|
// on Linux installs where the shims dir isn't on PATH, spawning the bare
|
|
349
369
|
// versioned name fails with ENOENT even though `agents view` shows the agent.
|
|
370
|
+
//
|
|
371
|
+
// On Windows, shims are bash scripts and cannot be executed by spawn() directly.
|
|
372
|
+
// buildExecEnv() already sets the isolation env vars (CLAUDE_CONFIG_DIR, CODEX_HOME,
|
|
373
|
+
// etc.) that the bash shim would set, so we can skip the shim entirely and resolve
|
|
374
|
+
// straight to the real binary via getBinaryPath.
|
|
350
375
|
if (options.version && cmd.length > 0) {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
376
|
+
if (process.platform === 'win32') {
|
|
377
|
+
const binaryPath = getBinaryPath(options.agent, options.version);
|
|
378
|
+
const binaryPathCmd = binaryPath + '.cmd';
|
|
379
|
+
if (fs.existsSync(binaryPathCmd)) {
|
|
380
|
+
cmd[0] = binaryPathCmd;
|
|
381
|
+
}
|
|
382
|
+
else if (fs.existsSync(binaryPath)) {
|
|
383
|
+
cmd[0] = binaryPath;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
const versionedName = `${cmd[0]}@${options.version}`;
|
|
388
|
+
const absPath = path.join(getShimsDir(), versionedName);
|
|
389
|
+
cmd[0] = fs.existsSync(absPath) ? absPath : versionedName;
|
|
390
|
+
}
|
|
354
391
|
}
|
|
355
392
|
// Add reasoning effort flags (before mode flags for codex -c positioning)
|
|
356
393
|
// For codex, -c must come before 'exec' subcommand, so we insert at position 1
|
|
@@ -438,6 +475,42 @@ export async function execAgent(options) {
|
|
|
438
475
|
const { exitCode } = await spawnAgent(options);
|
|
439
476
|
return exitCode;
|
|
440
477
|
}
|
|
478
|
+
/**
|
|
479
|
+
* Transparent passthrough exec for generated shims — the node-side delegate that
|
|
480
|
+
* Windows `.cmd` shims call. Resolves the active version (explicit pin, else
|
|
481
|
+
* project/default) and execs the real binary with the user's RAW args and the
|
|
482
|
+
* per-version env isolation, WITHOUT injecting mode/model/reasoning flags. This
|
|
483
|
+
* mirrors what the POSIX bash shim does inline (`exec $BINARY $launchArgs "$@"`),
|
|
484
|
+
* keeping version resolution in one place instead of reimplementing it in batch.
|
|
485
|
+
*/
|
|
486
|
+
export async function execShimPassthrough(agent, rawArgs, cwd, pinnedVersion) {
|
|
487
|
+
const version = pinnedVersion ?? resolveVersion(agent, cwd) ?? undefined;
|
|
488
|
+
if (!version || !isVersionInstalled(agent, version)) {
|
|
489
|
+
process.stderr.write(`agents: no installed default for ${agent}. Set one with: agents use ${agent} <version>\n`);
|
|
490
|
+
return 127;
|
|
491
|
+
}
|
|
492
|
+
let binary = getBinaryPath(agent, version);
|
|
493
|
+
if (process.platform === 'win32') {
|
|
494
|
+
// npm ships <cmd>.cmd alongside the bare script on Windows; that's the runnable form.
|
|
495
|
+
const cmdPath = binary + '.cmd';
|
|
496
|
+
if (fs.existsSync(cmdPath))
|
|
497
|
+
binary = cmdPath;
|
|
498
|
+
}
|
|
499
|
+
// The only flag the bash shim injects (codex); everything else is transparent.
|
|
500
|
+
const launchArgs = agent === 'codex' ? ['-c', 'check_for_update_on_startup=false'] : [];
|
|
501
|
+
// mode/effort are required by ExecOptions but unused by buildExecEnv (which only
|
|
502
|
+
// derives the per-version config-dir env); pass the agent's default to satisfy the type.
|
|
503
|
+
const env = buildExecEnv({ agent, version, cwd, mode: defaultModeFor(agent), effort: 'auto' });
|
|
504
|
+
const useShell = process.platform === 'win32' && (!path.isAbsolute(binary) || binary.endsWith('.cmd'));
|
|
505
|
+
return new Promise((resolve) => {
|
|
506
|
+
const child = spawn(binary, [...launchArgs, ...rawArgs], { cwd, stdio: 'inherit', env, shell: useShell });
|
|
507
|
+
child.on('exit', (code, signal) => resolve(code ?? (signal ? 1 : 0)));
|
|
508
|
+
child.on('error', (err) => {
|
|
509
|
+
process.stderr.write(`agents: failed to launch ${agent}: ${err.message}\n`);
|
|
510
|
+
resolve(127);
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
}
|
|
441
514
|
/**
|
|
442
515
|
* Spawn an agent process and return its exit code plus a tee'd copy of stderr.
|
|
443
516
|
*
|
|
@@ -475,11 +548,14 @@ async function spawnAgent(options) {
|
|
|
475
548
|
const stdio = interactive
|
|
476
549
|
? ['inherit', 'inherit', 'inherit']
|
|
477
550
|
: ['inherit', piped ? 'pipe' : 'inherit', 'pipe'];
|
|
551
|
+
// On Windows, .cmd batch wrappers (npm-installed CLIs) require shell:true
|
|
552
|
+
// whether addressed by name or absolute path.
|
|
553
|
+
const useShell = process.platform === 'win32' && (!path.isAbsolute(executable) || executable.endsWith('.cmd'));
|
|
478
554
|
const child = spawn(executable, args, {
|
|
479
555
|
cwd: options.cwd || process.cwd(),
|
|
480
556
|
stdio,
|
|
481
557
|
env: buildExecEnv(options),
|
|
482
|
-
shell:
|
|
558
|
+
shell: useShell,
|
|
483
559
|
});
|
|
484
560
|
// Mark startup time (time from function call to process spawn)
|
|
485
561
|
timer.mark('startup');
|
package/dist/lib/migrate.js
CHANGED
|
@@ -8,6 +8,7 @@ import * as fs from 'fs';
|
|
|
8
8
|
import * as path from 'path';
|
|
9
9
|
import * as os from 'os';
|
|
10
10
|
import * as yaml from 'yaml';
|
|
11
|
+
import { AGENTS, agentConfigDirName } from './agents.js';
|
|
11
12
|
const HOME = process.env.HOME ?? os.homedir();
|
|
12
13
|
const USER_DIR = path.join(HOME, '.agents');
|
|
13
14
|
/** Canonical system-repo location (post-fold). */
|
|
@@ -672,12 +673,13 @@ function repairAgentConfigSymlinks() {
|
|
|
672
673
|
}
|
|
673
674
|
let repaired = 0;
|
|
674
675
|
for (const { agent, version } of defaults) {
|
|
675
|
-
const
|
|
676
|
-
|
|
677
|
-
|
|
676
|
+
const configDirName = agent in AGENTS ? agentConfigDirName(agent) : `.${agent}`;
|
|
677
|
+
const userTarget = fs.existsSync(path.join(HISTORY_DIR, 'versions', agent, version, 'home', configDirName))
|
|
678
|
+
? path.join(HISTORY_DIR, 'versions', agent, version, 'home', configDirName)
|
|
679
|
+
: path.join(USER_DIR, 'versions', agent, version, 'home', configDirName);
|
|
678
680
|
if (!fs.existsSync(userTarget))
|
|
679
681
|
continue;
|
|
680
|
-
const symlinkPath = path.join(HOME,
|
|
682
|
+
const symlinkPath = path.join(HOME, configDirName);
|
|
681
683
|
let stat = null;
|
|
682
684
|
try {
|
|
683
685
|
stat = fs.lstatSync(symlinkPath);
|