@phnx-labs/agents-cli 1.22.114 → 1.22.116
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 +245 -0
- package/README.md +22 -102
- package/dist/browser.js +0 -0
- package/dist/cli/command-registry.js +0 -7
- package/dist/commands/accounts.d.ts +3 -77
- package/dist/commands/accounts.js +13 -366
- package/dist/commands/audit.js +1 -1
- package/dist/commands/auth.js +0 -2
- package/dist/commands/browser.d.ts +53 -0
- package/dist/commands/browser.js +210 -9
- package/dist/commands/doctor.d.ts +2 -3
- package/dist/commands/doctor.js +0 -11
- package/dist/commands/events.d.ts +1 -3
- package/dist/commands/events.js +4 -7
- package/dist/commands/exec.d.ts +0 -1
- package/dist/commands/exec.js +214 -148
- package/dist/commands/feed-watch.js +52 -8
- package/dist/commands/feed.js +65 -14
- package/dist/commands/focus.js +1 -1
- package/dist/commands/go.d.ts +0 -17
- package/dist/commands/go.js +2 -19
- package/dist/commands/logs.js +1 -1
- package/dist/commands/mcp.js +8 -83
- package/dist/commands/memory.js +4 -47
- package/dist/commands/message.js +4 -4
- package/dist/commands/plugins.js +1 -93
- package/dist/commands/repo.js +0 -44
- package/dist/commands/resume.js +4 -1
- package/dist/commands/secrets-passthrough.js +2 -2
- package/dist/commands/send.d.ts +4 -4
- package/dist/commands/send.js +6 -51
- package/dist/commands/sessions-backup-setup.js +1 -1
- package/dist/commands/sessions-picker.d.ts +11 -0
- package/dist/commands/sessions-picker.js +88 -7
- package/dist/commands/sessions-resume.js +0 -1
- package/dist/commands/sessions-share.d.ts +5 -7
- package/dist/commands/sessions-share.js +98 -49
- package/dist/commands/sessions.d.ts +21 -2
- package/dist/commands/sessions.js +158 -11
- package/dist/commands/setup-browser.js +18 -2
- package/dist/commands/setup-computer.js +20 -6
- package/dist/commands/setup-secrets.d.ts +2 -2
- package/dist/commands/setup-secrets.js +24 -4
- package/dist/commands/setup-term.d.ts +24 -0
- package/dist/commands/setup-term.js +70 -0
- package/dist/commands/setup-terminal.d.ts +3 -0
- package/dist/commands/setup-terminal.js +22 -0
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +29 -11
- package/dist/commands/skills.js +0 -8
- package/dist/commands/ssh.d.ts +6 -0
- package/dist/commands/ssh.js +93 -302
- package/dist/commands/sync.js +14 -5
- package/dist/commands/teams.js +1 -1
- package/dist/commands/traces.js +1 -1
- package/dist/lib/accounting/rotate.d.ts +63 -1
- package/dist/lib/accounting/rotate.js +56 -0
- package/dist/lib/accounts/add.d.ts +0 -5
- package/dist/lib/accounts/add.js +3 -9
- package/dist/lib/accounts/slots.js +32 -2
- package/dist/lib/answer-router.d.ts +11 -2
- package/dist/lib/answer-router.js +26 -2
- package/dist/lib/artifacts-client.d.ts +20 -0
- package/dist/lib/artifacts-client.js +46 -0
- package/dist/lib/auth-mint.d.ts +5 -4
- package/dist/lib/auth-mint.js +6 -5
- package/dist/lib/browser/drivers/arc.d.ts +1 -1
- package/dist/lib/browser/runtime-state.d.ts +55 -0
- package/dist/lib/browser/runtime-state.js +99 -18
- package/dist/lib/browser/service.d.ts +10 -0
- package/dist/lib/browser/service.js +229 -37
- package/dist/lib/browser/types.d.ts +18 -0
- package/dist/lib/cli-resources.js +3 -1
- package/dist/lib/cloud/dispatch.js +1 -1
- package/dist/lib/cloudflare/creds.d.ts +10 -0
- package/dist/lib/cloudflare/creds.js +46 -0
- package/dist/lib/cloudflare/provision.d.ts +35 -0
- package/dist/lib/cloudflare/provision.js +144 -0
- package/dist/lib/computer/sessions-list.d.ts +55 -0
- package/dist/lib/computer/sessions-list.js +168 -1
- package/dist/lib/config-keys.d.ts +1 -1
- package/dist/lib/config-keys.js +5 -0
- package/dist/lib/daemon/daemon.js +8 -1
- package/dist/lib/daemon/feed-stream-service.d.ts +23 -0
- package/dist/lib/daemon/feed-stream-service.js +40 -0
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +5 -0
- package/dist/lib/device-config.js +61 -0
- package/dist/lib/devices/connect.d.ts +49 -5
- package/dist/lib/devices/connect.js +169 -21
- package/dist/lib/devices/doctor-findings.js +2 -6
- package/dist/lib/feed/answer.d.ts +153 -4
- package/dist/lib/feed/answer.js +716 -105
- package/dist/lib/feed/envelope.d.ts +79 -0
- package/dist/lib/feed/envelope.js +23 -0
- package/dist/lib/feed/events.d.ts +6 -0
- package/dist/lib/feed/events.js +8 -0
- package/dist/lib/feed/feed.d.ts +61 -1
- package/dist/lib/feed/feed.js +226 -14
- package/dist/lib/feed/hub-server.d.ts +141 -0
- package/dist/lib/feed/hub-server.js +586 -0
- package/dist/lib/feed/hub.d.ts +95 -0
- package/dist/lib/feed/hub.js +255 -0
- package/dist/lib/feed/pr-status.d.ts +8 -0
- package/dist/lib/feed/pr-status.js +9 -1
- package/dist/lib/feed/tool-activity.d.ts +108 -0
- package/dist/lib/feed/tool-activity.js +313 -0
- package/dist/lib/feed/tools.d.ts +198 -0
- package/dist/lib/feed/tools.js +265 -0
- package/dist/lib/feed/watch.d.ts +50 -50
- package/dist/lib/feed/watch.js +147 -16
- package/dist/lib/feed-outcome.d.ts +1 -1
- package/dist/lib/feed-outcome.js +9 -2
- package/dist/lib/feed-policy.js +9 -3
- package/dist/lib/fleet/auth-sync.d.ts +2 -55
- package/dist/lib/fleet/auth-sync.js +2 -89
- package/dist/lib/format.d.ts +1 -1
- package/dist/lib/format.js +1 -1
- package/dist/lib/git.d.ts +0 -16
- package/dist/lib/git.js +0 -58
- package/dist/lib/harness-auth-capabilities.js +7 -2
- package/dist/lib/helper-versions.js +1 -1
- package/dist/lib/hosts/dispatch.d.ts +20 -1
- package/dist/lib/hosts/dispatch.js +52 -30
- package/dist/lib/hosts/remote-cmd.d.ts +72 -1
- package/dist/lib/hosts/remote-cmd.js +151 -10
- package/dist/lib/hosts/remote-cmd.test-fixture.d.ts +2 -0
- package/dist/lib/hosts/remote-cmd.test-fixture.js +22 -0
- package/dist/lib/mailbox.d.ts +12 -0
- package/dist/lib/mailbox.js +16 -2
- package/dist/lib/mcp.js +17 -11
- package/dist/lib/menubar/snapshot.d.ts +51 -0
- package/dist/lib/menubar/snapshot.js +42 -3
- package/dist/lib/open-url.js +2 -2
- package/dist/lib/probe.d.ts +4 -1
- package/dist/lib/probe.js +5 -2
- package/dist/lib/projects.d.ts +23 -0
- package/dist/lib/projects.js +78 -0
- package/dist/lib/pwsh.d.ts +33 -0
- package/dist/lib/pwsh.js +56 -0
- package/dist/lib/redact.d.ts +8 -0
- package/dist/lib/redact.js +11 -0
- package/dist/lib/refresh.d.ts +6 -2
- package/dist/lib/refresh.js +92 -72
- package/dist/lib/secrets-cli.d.ts +11 -0
- package/dist/lib/secrets-cli.js +30 -0
- package/dist/lib/secrets-client.js +3 -2
- package/dist/lib/session/active.d.ts +1 -0
- package/dist/lib/session/active.js +8 -0
- package/dist/lib/session/db.d.ts +67 -3
- package/dist/lib/session/db.js +381 -126
- package/dist/lib/session/detached.d.ts +7 -0
- package/dist/lib/session/detached.js +29 -0
- package/dist/lib/session/prompt.d.ts +23 -7
- package/dist/lib/session/prompt.js +46 -8
- package/dist/lib/session/remote/peer-stream.d.ts +24 -2
- package/dist/lib/session/remote/peer-stream.js +33 -6
- package/dist/lib/session/remote/remote-list.d.ts +20 -0
- package/dist/lib/session/remote/remote-list.js +22 -6
- package/dist/lib/session/remote/watch.d.ts +12 -0
- package/dist/lib/session/remote/watch.js +9 -0
- package/dist/lib/session/remote-preview-cache.d.ts +29 -0
- package/dist/lib/session/remote-preview-cache.js +373 -0
- package/dist/lib/session/sync/backend.d.ts +3 -3
- package/dist/lib/session/sync/backend.js +3 -3
- package/dist/lib/session/sync/provision.d.ts +1 -1
- package/dist/lib/session/sync/provision.js +2 -2
- package/dist/lib/session/tail.d.ts +50 -0
- package/dist/lib/session/tail.js +219 -0
- package/dist/lib/sessions-client.js +0 -3
- package/dist/lib/setup-tool-install.d.ts +3 -0
- package/dist/lib/setup-tool-install.js +27 -0
- package/dist/lib/setup-tool-status.d.ts +22 -0
- package/dist/lib/setup-tool-status.js +220 -0
- package/dist/lib/share-runtime.d.ts +11 -0
- package/dist/lib/share-runtime.js +63 -0
- package/dist/lib/signin-badge.d.ts +19 -4
- package/dist/lib/signin-badge.js +29 -11
- package/dist/lib/smart-launch.d.ts +1 -5
- package/dist/lib/smart-launch.js +3 -11
- package/dist/lib/ssh-exec.d.ts +44 -0
- package/dist/lib/ssh-exec.js +119 -0
- package/dist/lib/startup/command-registry.d.ts +6 -4
- package/dist/lib/startup/command-registry.js +10 -7
- package/dist/lib/state.js +2 -2
- package/dist/lib/storage/selection.d.ts +2 -2
- package/dist/lib/storage/selection.js +2 -2
- package/dist/lib/sync-umbrella.d.ts +5 -0
- package/dist/lib/sync-umbrella.js +18 -10
- package/dist/lib/term-driver.d.ts +24 -0
- package/dist/lib/term-driver.js +36 -0
- package/dist/lib/terminal/index.d.ts +1 -1
- package/dist/lib/terminal/index.js +1 -1
- package/dist/lib/terminal/inject.d.ts +38 -0
- package/dist/lib/terminal/inject.js +55 -9
- package/dist/lib/terminal/transport.d.ts +15 -5
- package/dist/lib/terminal/transport.js +61 -11
- package/dist/lib/traces/backend.d.ts +1 -2
- package/dist/lib/traces/backend.js +1 -2
- package/dist/lib/traces/provision.d.ts +1 -1
- package/dist/lib/traces/provision.js +2 -2
- package/dist/lib/types.d.ts +8 -6
- package/package.json +2 -3
- package/dist/commands/artifacts-setup.d.ts +0 -53
- package/dist/commands/artifacts-setup.js +0 -161
- package/dist/commands/artifacts.d.ts +0 -18
- package/dist/commands/artifacts.js +0 -58
- package/dist/commands/attach.d.ts +0 -12
- package/dist/commands/attach.js +0 -86
- package/dist/commands/auth-mint.d.ts +0 -12
- package/dist/commands/auth-mint.js +0 -108
- package/dist/commands/reconnect.d.ts +0 -46
- package/dist/commands/reconnect.js +0 -115
- package/dist/commands/share.d.ts +0 -293
- package/dist/commands/share.js +0 -1424
- package/dist/lib/fleet/remote-login.d.ts +0 -170
- package/dist/lib/fleet/remote-login.js +0 -568
- package/dist/lib/share/analytics.d.ts +0 -13
- package/dist/lib/share/analytics.js +0 -45
- package/dist/lib/share/backend.d.ts +0 -120
- package/dist/lib/share/backend.js +0 -176
- package/dist/lib/share/capture.d.ts +0 -31
- package/dist/lib/share/capture.js +0 -174
- package/dist/lib/share/config.d.ts +0 -72
- package/dist/lib/share/config.js +0 -211
- package/dist/lib/share/delete.d.ts +0 -123
- package/dist/lib/share/delete.js +0 -173
- package/dist/lib/share/html.d.ts +0 -20
- package/dist/lib/share/html.js +0 -88
- package/dist/lib/share/http-error.d.ts +0 -53
- package/dist/lib/share/http-error.js +0 -65
- package/dist/lib/share/og.d.ts +0 -26
- package/dist/lib/share/og.js +0 -84
- package/dist/lib/share/provision.d.ts +0 -127
- package/dist/lib/share/provision.js +0 -285
- package/dist/lib/share/publish.d.ts +0 -379
- package/dist/lib/share/publish.js +0 -818
- package/dist/lib/share/worker-template.d.ts +0 -27
- package/dist/lib/share/worker-template.js +0 -2424
- package/dist/lib/storage/index.d.ts +0 -14
- package/dist/lib/storage/index.js +0 -14
- package/dist/lib/storage/visibility.d.ts +0 -82
- package/dist/lib/storage/visibility.js +0 -99
package/dist/commands/setup.js
CHANGED
|
@@ -20,9 +20,9 @@ import { ensureShimCurrent, switchHomeFileSymlinks, isShimsInPath, addShimsToPat
|
|
|
20
20
|
import { setHelpSections } from '../lib/help.js';
|
|
21
21
|
import { registerSetupBrowserCommand, runBrowserWizard } from './setup-browser.js';
|
|
22
22
|
import { registerSetupComputerCommand, runComputerWizard } from './setup-computer.js';
|
|
23
|
-
import { runShareWizard } from './artifacts-setup.js';
|
|
24
23
|
import { registerSetupMineCommand } from './setup-mine.js';
|
|
25
24
|
import { registerSetupSecretsCommand } from './setup-secrets.js';
|
|
25
|
+
import { registerSetupTermCommand, runTermWizard, isTermCliInstalled } from './setup-term.js';
|
|
26
26
|
import { registerSetupFleetCommand } from './setup-fleet.js';
|
|
27
27
|
import { registerSetupAccountsCommand, runAccountsSetupWizard } from './setup-accounts.js';
|
|
28
28
|
import { registerSetupWatchdogCommand, runWatchdogSetupWizard } from './setup-watchdog.js';
|
|
@@ -34,10 +34,10 @@ import { runPreferencesStep } from './setup-preferences.js';
|
|
|
34
34
|
import { getConfiguredDefaultProfileName, getProfile, getAutoDetectedProfile, isProfileLaunchableHere } from '../lib/browser/profiles.js';
|
|
35
35
|
import { listInstalledBrowsers } from '../lib/browser/chrome.js';
|
|
36
36
|
import { probeComputerTrust } from './computer.js';
|
|
37
|
-
import { readShareConfig } from '../lib/share/config.js';
|
|
38
37
|
import { loadDevices } from '../lib/devices/registry.js';
|
|
39
38
|
import { getConfigValue } from '../lib/device-config.js';
|
|
40
39
|
import { setupSecretsPrefsPath } from './setup-secrets.js';
|
|
40
|
+
import { SETUP_TOOLS, getCachedToolSetup, refreshToolSetup } from '../lib/setup-tool-status.js';
|
|
41
41
|
const HOME = os.homedir();
|
|
42
42
|
/**
|
|
43
43
|
* Import an existing unmanaged agent installation into agents-cli.
|
|
@@ -279,8 +279,8 @@ export async function getSetupStatus() {
|
|
|
279
279
|
const devices = await loadDevices();
|
|
280
280
|
const coreReady = isGitRepo(getAgentsDir());
|
|
281
281
|
const secretsReady = fs.existsSync(setupSecretsPrefsPath());
|
|
282
|
+
const termReady = isTermCliInstalled();
|
|
282
283
|
const minted = hasMintedSetupToken();
|
|
283
|
-
const shareConfig = readShareConfig();
|
|
284
284
|
const watchdogEnabled = getConfigValue('watchdog.enabled').value === true;
|
|
285
285
|
const interactiveHost = getConfigValue('interactive.host').value;
|
|
286
286
|
const defaultBrowser = getConfigValue('browser.profile').value;
|
|
@@ -289,9 +289,9 @@ export async function getSetupStatus() {
|
|
|
289
289
|
{ phase: 'browser', state: browserReady ? 'ready' : 'missing', detail: browserReady ? `profile ${browserProfile.name}` : browserProfile ? `profile ${browserProfile.name} cannot launch here` : installedBrowsers.length ? 'no default profile' : 'no supported browser found' },
|
|
290
290
|
{ phase: 'computer', state: computerState, detail: computerState === 'ready' ? 'helper trusted' : computerState === 'n/a' ? 'macOS local setup only' : 'helper not running or not trusted' },
|
|
291
291
|
{ phase: 'secrets', state: secretsReady ? 'ready' : 'missing', detail: secretsReady ? 'defaults chosen' : 'defaults not chosen' },
|
|
292
|
+
{ phase: 'term', state: termReady ? 'ready' : 'missing', detail: termReady ? 'installed' : 'not installed (fleet login / auth mint spawn it)' },
|
|
292
293
|
{ phase: 'accounts', state: minted.ready ? 'ready' : 'missing', detail: minted.detail },
|
|
293
294
|
{ phase: 'fleet', state: Object.keys(devices).length ? 'ready' : 'missing', detail: Object.keys(devices).length ? `${Object.keys(devices).length} device${Object.keys(devices).length === 1 ? '' : 's'} registered` : 'no devices registered' },
|
|
294
|
-
{ phase: 'share', state: shareConfig ? 'ready' : 'missing', detail: shareConfig?.baseUrl ?? 'endpoint not configured' },
|
|
295
295
|
{ phase: 'watchdog', state: watchdogEnabled ? 'ready' : 'missing', detail: watchdogEnabled ? 'enabled on this device' : 'disabled on this device' },
|
|
296
296
|
{ phase: 'preferences', state: interactiveHost || defaultBrowser ? 'ready' : 'missing', detail: [interactiveHost && `host ${interactiveHost}`, defaultBrowser && `browser ${defaultBrowser}`].filter(Boolean).join(' · ') || 'interactive host and browser unset' },
|
|
297
297
|
];
|
|
@@ -309,10 +309,10 @@ async function runSetupPhase(phase) {
|
|
|
309
309
|
await runBrowserWizard();
|
|
310
310
|
else if (phase === 'computer')
|
|
311
311
|
await runComputerWizard();
|
|
312
|
-
else if (phase === 'share')
|
|
313
|
-
await runShareWizard();
|
|
314
312
|
else if (phase === 'secrets')
|
|
315
313
|
await import('./setup-secrets.js').then((m) => m.runSecretsSetupWizard());
|
|
314
|
+
else if (phase === 'term')
|
|
315
|
+
await runTermWizard();
|
|
316
316
|
else if (phase === 'accounts')
|
|
317
317
|
await runAccountsSetupWizard();
|
|
318
318
|
else if (phase === 'fleet')
|
|
@@ -371,11 +371,12 @@ export function registerSetupCommand(program) {
|
|
|
371
371
|
.description('Set up agents-cli, or re-open the capability onboarding hub.')
|
|
372
372
|
.option('-f, --force', 'Re-run setup even if ~/.agents/.system/ already exists (use with caution)')
|
|
373
373
|
.option('--no-system-repo', 'Skip cloning the system repo (you must populate ~/.agents/.system/ yourself)');
|
|
374
|
-
// Capability subcommands: `agents setup browser|computer|mine|secrets|accounts|fleet|alias|beta`.
|
|
375
|
-
//
|
|
376
|
-
//
|
|
374
|
+
// Capability subcommands: `agents setup browser|computer|term|mine|secrets|accounts|fleet|alias|beta`.
|
|
375
|
+
// Artifact publishing is no longer set up here — it lives in the standalone
|
|
376
|
+
// `artifacts` CLI (`artifacts share setup`/`join`, PHNX-3992).
|
|
377
377
|
registerSetupBrowserCommand(setupCmd);
|
|
378
378
|
registerSetupComputerCommand(setupCmd);
|
|
379
|
+
registerSetupTermCommand(setupCmd);
|
|
379
380
|
registerSetupMineCommand(setupCmd);
|
|
380
381
|
registerSetupSecretsCommand(setupCmd);
|
|
381
382
|
registerSetupAccountsCommand(setupCmd);
|
|
@@ -404,9 +405,25 @@ export function registerSetupCommand(program) {
|
|
|
404
405
|
`,
|
|
405
406
|
});
|
|
406
407
|
setupCmd.command('status')
|
|
407
|
-
.description('Show setup readiness for core, browser, computer, secrets, accounts, fleet,
|
|
408
|
+
.description('Show setup readiness for core, browser, computer, secrets, term, accounts, fleet, watchdog, and preferences.')
|
|
408
409
|
.option('--json', 'print machine-readable JSON')
|
|
410
|
+
.option('--tool <name>', 'Read cached standalone tool status: browser, computer, secrets, term, or all')
|
|
411
|
+
.option('--refresh', 'Explicitly refresh only the selected standalone tool health checks')
|
|
409
412
|
.action(async (options) => {
|
|
413
|
+
if (options.tool || options.refresh) {
|
|
414
|
+
const selected = options.tool ?? 'all';
|
|
415
|
+
if (selected !== 'all' && !SETUP_TOOLS.includes(selected))
|
|
416
|
+
throw new Error('Unknown tool. Choose browser, computer, secrets, term, or all.');
|
|
417
|
+
const rows = options.refresh
|
|
418
|
+
? await refreshToolSetup(selected)
|
|
419
|
+
: getCachedToolSetup().filter((row) => selected === 'all' || row.tool === selected);
|
|
420
|
+
if (options.json)
|
|
421
|
+
console.log(JSON.stringify(rows));
|
|
422
|
+
else
|
|
423
|
+
for (const row of rows)
|
|
424
|
+
console.log(`${row.tool}: ${row.installed === false ? 'not installed' : row.readiness} · ${row.detail}`);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
410
427
|
const rows = await getSetupStatus();
|
|
411
428
|
if (options.json)
|
|
412
429
|
console.log(JSON.stringify(rows, null, 2));
|
|
@@ -427,6 +444,7 @@ export function registerSetupCommand(program) {
|
|
|
427
444
|
agents setup browser
|
|
428
445
|
agents setup computer
|
|
429
446
|
agents setup secrets
|
|
447
|
+
agents setup term
|
|
430
448
|
agents setup accounts
|
|
431
449
|
agents setup fleet
|
|
432
450
|
agents setup watchdog
|
|
@@ -443,8 +461,8 @@ export function registerSetupCommand(program) {
|
|
|
443
461
|
Capability setup can also be run any time on its own:
|
|
444
462
|
agents setup browser # detect a browser + create the default profile
|
|
445
463
|
agents setup computer # install the signed macOS helper + grant permissions
|
|
446
|
-
agents artifacts setup # provision or join a Cloudflare share endpoint
|
|
447
464
|
agents setup secrets # choose secrets backend/policy defaults + import
|
|
465
|
+
agents setup term # install the standalone term CLI (PTY engine for fleet login / auth mint)
|
|
448
466
|
agents setup accounts # mint a Claude setup-token for unattended usage/probe
|
|
449
467
|
agents setup fleet # discover Tailscale devices + configure SSH access
|
|
450
468
|
agents setup watchdog # choose which devices run the daemon watchdog pass
|
package/dist/commands/skills.js
CHANGED
|
@@ -511,14 +511,6 @@ Examples:
|
|
|
511
511
|
const output = lines.join('\n');
|
|
512
512
|
printWithPager(output, lines.length);
|
|
513
513
|
});
|
|
514
|
-
// Deprecated alias for 'view'
|
|
515
|
-
skillsCmd
|
|
516
|
-
.command('info [name]', { hidden: true })
|
|
517
|
-
.action(async (name) => {
|
|
518
|
-
console.log(chalk.yellow('Deprecated: Use "agents skills view" instead of "agents skills info"\n'));
|
|
519
|
-
// Re-execute view command logic
|
|
520
|
-
await skillsCmd.commands.find((c) => c.name() === 'view')?.parseAsync(['view', ...(name ? [name] : [])], { from: 'user' });
|
|
521
|
-
});
|
|
522
514
|
}
|
|
523
515
|
/**
|
|
524
516
|
* Build the row data for `agents skills list`. Each row = one central skill
|
package/dist/commands/ssh.d.ts
CHANGED
|
@@ -83,5 +83,11 @@ export interface HarnessInventoryOpts {
|
|
|
83
83
|
*/
|
|
84
84
|
export declare function collectFleetHarnesses(opts: HarnessInventoryOpts): Promise<HostHarnessResult[]>;
|
|
85
85
|
export declare function runDevicesAccounts(opts: HarnessInventoryOpts): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Parse `--argv`'s JSON array, or report exactly what was wrong and return
|
|
88
|
+
* undefined. Fails loud rather than coercing: a caller that meant to pass argv
|
|
89
|
+
* and typo'd the JSON must not silently get shell-string semantics instead.
|
|
90
|
+
*/
|
|
91
|
+
export declare function parseArgvJson(raw: string, log?: (line: string) => void): string[] | undefined;
|
|
86
92
|
/** Register both `agents ssh` and `agents devices`. */
|
|
87
93
|
export declare function registerSshCommands(program: Command): void;
|
package/dist/commands/ssh.js
CHANGED
|
@@ -58,7 +58,6 @@ import { crabboxList, crabboxFind, crabboxSshArgv } from '../lib/crabbox/cli.js'
|
|
|
58
58
|
import { boxAddress, boxStatus, fmtIdleShort, fmtExpiresShort, registerLeaseCommand } from './lease.js';
|
|
59
59
|
import { registerSnapshotCommand } from './snapshot.js';
|
|
60
60
|
import { authCellColor, formatCheckedAge, isDeadVerdict, readAuthHealthCache, summarizeHostAuth, summarizeVerdicts, verdictColor, verdictLabel, writeFleetAuthRows, } from '../lib/auth-health.js';
|
|
61
|
-
import { runFleetLogin } from '../lib/fleet/remote-login.js';
|
|
62
61
|
import { getConfigValue, listConfig, setConfigValue, unsetConfigValue, configKeySpec, autoPoolMode, configuredDeviceRole, listConfiguredDeviceRoles, setConfiguredDeviceRole, } from '../lib/device-config.js';
|
|
63
62
|
import { filterAutoPool, listWorkerDevices } from '../lib/devices/pool.js';
|
|
64
63
|
import { registerCommandGroups, setHelpSections } from '../lib/help.js';
|
|
@@ -1055,46 +1054,6 @@ function renderAuthMatrix(results, opts) {
|
|
|
1055
1054
|
}
|
|
1056
1055
|
return lines;
|
|
1057
1056
|
}
|
|
1058
|
-
/** Render the final `fleet login` result summary in the house auth-matrix style. */
|
|
1059
|
-
function renderLoginMatrix(results) {
|
|
1060
|
-
const lines = [chalk.bold('Fleet login')];
|
|
1061
|
-
if (results.length === 0) {
|
|
1062
|
-
lines.push(chalk.gray(' Nothing pending — every requested account is already logged in.'));
|
|
1063
|
-
return lines;
|
|
1064
|
-
}
|
|
1065
|
-
const nameW = Math.max(6, ...results.map((r) => `${r.agent}@${r.device}`.length));
|
|
1066
|
-
for (const r of results) {
|
|
1067
|
-
const who = `${r.agent}@${r.device}`.padEnd(nameW);
|
|
1068
|
-
let badge;
|
|
1069
|
-
switch (r.state) {
|
|
1070
|
-
case 'authorized':
|
|
1071
|
-
badge = chalk.green('authorized');
|
|
1072
|
-
break;
|
|
1073
|
-
case 'ready':
|
|
1074
|
-
badge = chalk.cyan('code ready');
|
|
1075
|
-
break;
|
|
1076
|
-
case 'driving':
|
|
1077
|
-
badge = chalk.yellow('driving');
|
|
1078
|
-
break;
|
|
1079
|
-
case 'skipped':
|
|
1080
|
-
badge = chalk.gray('not remotable');
|
|
1081
|
-
break;
|
|
1082
|
-
case 'error':
|
|
1083
|
-
badge = chalk.red('error');
|
|
1084
|
-
break;
|
|
1085
|
-
default:
|
|
1086
|
-
badge = chalk.gray(r.state);
|
|
1087
|
-
break;
|
|
1088
|
-
}
|
|
1089
|
-
const note = r.detail ? chalk.dim(` ${r.detail}`) : (r.reason && !r.remotable ? chalk.dim(` ${r.reason}`) : '');
|
|
1090
|
-
lines.push(` ${who} ${badge}${note}`);
|
|
1091
|
-
}
|
|
1092
|
-
const auth = results.filter((r) => r.state === 'authorized').length;
|
|
1093
|
-
const remotable = results.filter((r) => r.remotable).length;
|
|
1094
|
-
lines.push('');
|
|
1095
|
-
lines.push(chalk.gray(` ${auth}/${remotable} remotable logins authorized`));
|
|
1096
|
-
return lines;
|
|
1097
|
-
}
|
|
1098
1057
|
/** Register the `agents devices` command tree (also aliased as `fleet`). */
|
|
1099
1058
|
function registerDevicesCommands(program) {
|
|
1100
1059
|
const devicesCmd = program
|
|
@@ -1122,7 +1081,8 @@ function registerDevicesCommands(program) {
|
|
|
1122
1081
|
agents devices config mac-mini notes "runs the releases"
|
|
1123
1082
|
agents devices config win-mini ssh.auth password
|
|
1124
1083
|
agents devices config worker ssh.identity-file ~/.ssh/worker_ed25519
|
|
1125
|
-
agents devices
|
|
1084
|
+
agents devices disable mac-mini # leave the auto-placement pool (auto-launch.enabled off)
|
|
1085
|
+
agents devices enable mac-mini # rejoin it
|
|
1126
1086
|
agents devices describe mark-1 "gpu box — cuda 12.4" # one-line purpose, shown in the list
|
|
1127
1087
|
agents devices config mac-mini interactive.host zion # where agents show YOU artifacts
|
|
1128
1088
|
agents devices render --write # write ~/.ssh/config.d/agents include
|
|
@@ -1140,7 +1100,7 @@ function registerDevicesCommands(program) {
|
|
|
1140
1100
|
{ title: 'Inspect', names: ['list', 'show', 'status', 'ping', 'harnesses', 'accounts', 'snapshot'] },
|
|
1141
1101
|
{ title: 'Disposable devices', names: ['lease'] },
|
|
1142
1102
|
{ title: 'Configure a device', names: ['config', 'describe', 'render'] },
|
|
1143
|
-
{ title: 'Fleet operations', names: ['update', 'run', '
|
|
1103
|
+
{ title: 'Fleet operations', names: ['update', 'run', 'capture', 'apply', 'worktrees'] },
|
|
1144
1104
|
]);
|
|
1145
1105
|
devicesCmd
|
|
1146
1106
|
.command('sync')
|
|
@@ -1286,11 +1246,9 @@ function registerDevicesCommands(program) {
|
|
|
1286
1246
|
// ─── devices config (unified settings surface) ────────────────────────────
|
|
1287
1247
|
//
|
|
1288
1248
|
// ONE command for every per-device setting: `agents devices config <name>
|
|
1289
|
-
// [key] [value] [--unset] [--json]`.
|
|
1290
|
-
//
|
|
1291
|
-
//
|
|
1292
|
-
// consumer's stdout stays parseable) and delegates to this same engine,
|
|
1293
|
-
// preserving its old output shape and exit codes.
|
|
1249
|
+
// [key] [value] [--unset] [--json]`. configure, note, set-interactive, set,
|
|
1250
|
+
// prefer, and unprefer are deleted (PHNX-4051) — use this command directly.
|
|
1251
|
+
// enable/disable stay as first-class sugar over auto-launch.enabled.
|
|
1294
1252
|
/** Parse a raw CLI string into a config key's typed value (bool/int pass validation, strings verbatim). */
|
|
1295
1253
|
const parseConfigValueInput = (spec, raw) => {
|
|
1296
1254
|
switch (spec.type) {
|
|
@@ -1685,8 +1643,10 @@ function registerDevicesCommands(program) {
|
|
|
1685
1643
|
the device itself; --fleet still writes a fleet-wide default those boxes
|
|
1686
1644
|
inherit until they override.
|
|
1687
1645
|
|
|
1688
|
-
|
|
1689
|
-
|
|
1646
|
+
configure, note, set, set-interactive, prefer, and unprefer are deleted;
|
|
1647
|
+
use this command directly (auto-launch.preferred, interactive.host, or
|
|
1648
|
+
any other key). enable/disable stay as first-class sugar over
|
|
1649
|
+
auto-launch.enabled.
|
|
1690
1650
|
`,
|
|
1691
1651
|
});
|
|
1692
1652
|
const roleCmd = devicesCmd
|
|
@@ -1765,182 +1725,6 @@ function registerDevicesCommands(program) {
|
|
|
1765
1725
|
long-form scratch use 'agents devices config <name> notes "…"'.
|
|
1766
1726
|
`,
|
|
1767
1727
|
});
|
|
1768
|
-
/** Deprecation notice for a retired subcommand — STDERR only, so a --json consumer's stdout stays parseable. */
|
|
1769
|
-
const configTombstoneNotice = (retired, replacement) => {
|
|
1770
|
-
console.error(chalk.yellow(`Deprecated: "agents devices ${retired}" is now "agents devices ${replacement}". Running that for you.\n`));
|
|
1771
|
-
};
|
|
1772
|
-
devicesCmd
|
|
1773
|
-
.command('enable <name>', { hidden: true })
|
|
1774
|
-
.action(async (name) => {
|
|
1775
|
-
try {
|
|
1776
|
-
configTombstoneNotice('enable <name>', 'config <name> auto-launch.enabled on');
|
|
1777
|
-
await mustGetDevice(name);
|
|
1778
|
-
// Back to the default (enabled) = remove the key.
|
|
1779
|
-
await runDevicesConfig(name, 'auto-launch.enabled', [], { unset: true, quiet: true });
|
|
1780
|
-
console.log(chalk.green(`Enabled '${name}'`) + chalk.gray(' for AGI EXT auto-launch.'));
|
|
1781
|
-
}
|
|
1782
|
-
catch (err) {
|
|
1783
|
-
console.error(chalk.red(err.message));
|
|
1784
|
-
process.exit(1);
|
|
1785
|
-
}
|
|
1786
|
-
});
|
|
1787
|
-
devicesCmd
|
|
1788
|
-
.command('disable <name>', { hidden: true })
|
|
1789
|
-
.action(async (name) => {
|
|
1790
|
-
try {
|
|
1791
|
-
configTombstoneNotice('disable <name>', 'config <name> auto-launch.enabled off');
|
|
1792
|
-
await mustGetDevice(name);
|
|
1793
|
-
await runDevicesConfig(name, 'auto-launch.enabled', ['off'], { quiet: true });
|
|
1794
|
-
console.log(chalk.green(`Disabled '${name}'`) + chalk.gray(' for AGI EXT auto-launch.'));
|
|
1795
|
-
}
|
|
1796
|
-
catch (err) {
|
|
1797
|
-
console.error(chalk.red(err.message));
|
|
1798
|
-
process.exit(1);
|
|
1799
|
-
}
|
|
1800
|
-
});
|
|
1801
|
-
devicesCmd
|
|
1802
|
-
.command('prefer <name>', { hidden: true })
|
|
1803
|
-
.action(async (name) => {
|
|
1804
|
-
try {
|
|
1805
|
-
configTombstoneNotice('prefer <name>', 'config <name> auto-launch.preferred on');
|
|
1806
|
-
await mustGetDevice(name);
|
|
1807
|
-
await runDevicesConfig(name, 'auto-launch.preferred', ['on'], { quiet: true });
|
|
1808
|
-
console.log(chalk.green(`Preferred '${name}'`) + chalk.gray(' for AGI EXT auto-launch.'));
|
|
1809
|
-
}
|
|
1810
|
-
catch (err) {
|
|
1811
|
-
console.error(chalk.red(err.message));
|
|
1812
|
-
process.exit(1);
|
|
1813
|
-
}
|
|
1814
|
-
});
|
|
1815
|
-
devicesCmd
|
|
1816
|
-
.command('unprefer <name>', { hidden: true })
|
|
1817
|
-
.action(async (name) => {
|
|
1818
|
-
try {
|
|
1819
|
-
configTombstoneNotice('unprefer <name>', 'config <name> auto-launch.preferred off');
|
|
1820
|
-
await mustGetDevice(name);
|
|
1821
|
-
// Back to the default (not preferred) = remove the key.
|
|
1822
|
-
await runDevicesConfig(name, 'auto-launch.preferred', [], { unset: true, quiet: true });
|
|
1823
|
-
console.log(chalk.green(`No longer preferring '${name}'`) + chalk.gray(' for AGI EXT auto-launch.'));
|
|
1824
|
-
}
|
|
1825
|
-
catch (err) {
|
|
1826
|
-
console.error(chalk.red(err.message));
|
|
1827
|
-
process.exit(1);
|
|
1828
|
-
}
|
|
1829
|
-
});
|
|
1830
|
-
devicesCmd
|
|
1831
|
-
.command('set-interactive [name]', { hidden: true })
|
|
1832
|
-
.option('--unset', 'clear the interactive host')
|
|
1833
|
-
.option('--json', 'output machine-readable JSON')
|
|
1834
|
-
.action(async (name, opts) => {
|
|
1835
|
-
try {
|
|
1836
|
-
configTombstoneNotice('set-interactive [name]', 'config <name> interactive.host <name>');
|
|
1837
|
-
if (opts.unset) {
|
|
1838
|
-
unsetConfigValue('interactive.host');
|
|
1839
|
-
if (opts.json)
|
|
1840
|
-
writeJson({ interactiveHost: null });
|
|
1841
|
-
else
|
|
1842
|
-
console.log(chalk.green('Cleared the interactive host.'));
|
|
1843
|
-
return;
|
|
1844
|
-
}
|
|
1845
|
-
if (name) {
|
|
1846
|
-
await mustGetDevice(name);
|
|
1847
|
-
setConfigValue('interactive.host', name);
|
|
1848
|
-
if (opts.json)
|
|
1849
|
-
writeJson({ interactiveHost: name });
|
|
1850
|
-
else
|
|
1851
|
-
console.log(chalk.green(`Interactive host: '${name}'`) + chalk.gray(' — agents show you artifacts there. Clear with --unset.'));
|
|
1852
|
-
return;
|
|
1853
|
-
}
|
|
1854
|
-
const current = getConfigValue('interactive.host').value;
|
|
1855
|
-
if (opts.json) {
|
|
1856
|
-
writeJson({ interactiveHost: current ?? null });
|
|
1857
|
-
}
|
|
1858
|
-
else if (current) {
|
|
1859
|
-
console.log(`${chalk.bold('Interactive host:')} ${chalk.cyan(current)}`);
|
|
1860
|
-
}
|
|
1861
|
-
else {
|
|
1862
|
-
console.log(chalk.gray("No interactive host set. Set one with 'agents devices config <name> interactive.host <name>'."));
|
|
1863
|
-
}
|
|
1864
|
-
}
|
|
1865
|
-
catch (err) {
|
|
1866
|
-
console.error(chalk.red(err.message));
|
|
1867
|
-
process.exit(1);
|
|
1868
|
-
}
|
|
1869
|
-
});
|
|
1870
|
-
devicesCmd
|
|
1871
|
-
.command('configure <name>', { hidden: true })
|
|
1872
|
-
.option('--max-agents <n>', 'cap concurrent agents')
|
|
1873
|
-
.option('--scheduler <on|off>', 'allow the routines scheduler (daemon) to fire on this device')
|
|
1874
|
-
.option('--json', 'output machine-readable JSON')
|
|
1875
|
-
.action(async (name, opts) => {
|
|
1876
|
-
try {
|
|
1877
|
-
configTombstoneNotice('configure <name> [--max-agents N] [--scheduler on|off]', 'config <name> <key> <value>');
|
|
1878
|
-
await mustGetDevice(name);
|
|
1879
|
-
const writes = [];
|
|
1880
|
-
if (opts.maxAgents !== undefined)
|
|
1881
|
-
writes.push(['agents.max-concurrent', opts.maxAgents]);
|
|
1882
|
-
if (opts.scheduler !== undefined) {
|
|
1883
|
-
if (opts.scheduler !== 'on' && opts.scheduler !== 'off') {
|
|
1884
|
-
throw new Error(`--scheduler expects 'on' or 'off', got '${opts.scheduler}'.`);
|
|
1885
|
-
}
|
|
1886
|
-
writes.push(['scheduler.enabled', opts.scheduler]);
|
|
1887
|
-
}
|
|
1888
|
-
for (const [key, value] of writes) {
|
|
1889
|
-
await runDevicesConfig(name, key, [value], { quiet: Boolean(opts.json) });
|
|
1890
|
-
}
|
|
1891
|
-
if (opts.json)
|
|
1892
|
-
printDevicesConfig(name, true);
|
|
1893
|
-
else if (writes.length === 0)
|
|
1894
|
-
printDevicesConfig(name, false);
|
|
1895
|
-
}
|
|
1896
|
-
catch (err) {
|
|
1897
|
-
console.error(chalk.red(err.message));
|
|
1898
|
-
process.exit(1);
|
|
1899
|
-
}
|
|
1900
|
-
});
|
|
1901
|
-
devicesCmd
|
|
1902
|
-
.command('note <name> [text...]', { hidden: true })
|
|
1903
|
-
.option('--clear', 'remove all notes from the device')
|
|
1904
|
-
.option('--json', 'output machine-readable JSON')
|
|
1905
|
-
.action(async (name, text, opts) => {
|
|
1906
|
-
try {
|
|
1907
|
-
configTombstoneNotice('note <name> [text...]', 'config <name> notes <text>');
|
|
1908
|
-
await mustGetDevice(name);
|
|
1909
|
-
if (opts.clear) {
|
|
1910
|
-
await runDevicesConfig(name, 'notes', [], { unset: true, quiet: true });
|
|
1911
|
-
if (opts.json)
|
|
1912
|
-
writeJson({ device: name, notes: [] });
|
|
1913
|
-
else
|
|
1914
|
-
console.log(chalk.green(`Cleared notes on '${name}'.`));
|
|
1915
|
-
return;
|
|
1916
|
-
}
|
|
1917
|
-
if (text.length > 0) {
|
|
1918
|
-
await runDevicesConfig(name, 'notes', text, { quiet: true });
|
|
1919
|
-
const notes = getConfigValue('notes', { device: name }).value ?? [];
|
|
1920
|
-
if (opts.json)
|
|
1921
|
-
writeJson({ device: name, notes });
|
|
1922
|
-
else
|
|
1923
|
-
console.log(chalk.green(`Noted on '${name}':`) + ` ${text.join(' ')}`);
|
|
1924
|
-
return;
|
|
1925
|
-
}
|
|
1926
|
-
const notes = getConfigValue('notes', { device: name }).value ?? [];
|
|
1927
|
-
if (opts.json) {
|
|
1928
|
-
writeJson({ device: name, notes });
|
|
1929
|
-
}
|
|
1930
|
-
else if (notes.length > 0) {
|
|
1931
|
-
console.log(chalk.bold(`Notes for '${name}'`));
|
|
1932
|
-
for (const n of notes)
|
|
1933
|
-
console.log(` ${chalk.gray('•')} ${n}`);
|
|
1934
|
-
}
|
|
1935
|
-
else {
|
|
1936
|
-
console.log(chalk.gray(`No notes on '${name}'. Add one with 'agents devices config ${name} notes "..."'.`));
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
|
-
catch (err) {
|
|
1940
|
-
console.error(chalk.red(err.message));
|
|
1941
|
-
process.exit(1);
|
|
1942
|
-
}
|
|
1943
|
-
});
|
|
1944
1728
|
const runList = async (opts = {}) => {
|
|
1945
1729
|
const reg = await loadDevices();
|
|
1946
1730
|
const names = Object.keys(reg).sort();
|
|
@@ -2027,6 +1811,34 @@ function registerDevicesCommands(program) {
|
|
|
2027
1811
|
}
|
|
2028
1812
|
};
|
|
2029
1813
|
devicesCmd.action(runList);
|
|
1814
|
+
devicesCmd
|
|
1815
|
+
.command('enable <name>')
|
|
1816
|
+
.description('Put a device back in the automatic-placement pool (clears auto-launch.enabled)')
|
|
1817
|
+
.action(async (name) => {
|
|
1818
|
+
try {
|
|
1819
|
+
await mustGetDevice(name);
|
|
1820
|
+
await runDevicesConfig(name, 'auto-launch.enabled', [], { unset: true, quiet: true });
|
|
1821
|
+
console.log(chalk.green(`Enabled '${name}'`) + chalk.gray(' for automatic placement.'));
|
|
1822
|
+
}
|
|
1823
|
+
catch (err) {
|
|
1824
|
+
console.error(chalk.red(err.message));
|
|
1825
|
+
process.exit(1);
|
|
1826
|
+
}
|
|
1827
|
+
});
|
|
1828
|
+
devicesCmd
|
|
1829
|
+
.command('disable <name>')
|
|
1830
|
+
.description('Drop a device from every automatic-placement path (sets auto-launch.enabled off)')
|
|
1831
|
+
.action(async (name) => {
|
|
1832
|
+
try {
|
|
1833
|
+
await mustGetDevice(name);
|
|
1834
|
+
await runDevicesConfig(name, 'auto-launch.enabled', ['off'], { quiet: true });
|
|
1835
|
+
console.log(chalk.green(`Disabled '${name}'`) + chalk.gray(' for automatic placement.'));
|
|
1836
|
+
}
|
|
1837
|
+
catch (err) {
|
|
1838
|
+
console.error(chalk.red(err.message));
|
|
1839
|
+
process.exit(1);
|
|
1840
|
+
}
|
|
1841
|
+
});
|
|
2030
1842
|
devicesCmd
|
|
2031
1843
|
.command('list')
|
|
2032
1844
|
.alias('ls')
|
|
@@ -2155,33 +1967,6 @@ agents devices accounts --json # machine-readable, per-host account gr
|
|
|
2155
1967
|
Collapses the installs that share one account (e.g. five claude versions on one
|
|
2156
1968
|
email) into a single row. Use \`agents devices harnesses\` for the per-install view.`,
|
|
2157
1969
|
});
|
|
2158
|
-
devicesCmd
|
|
2159
|
-
.command('login')
|
|
2160
|
-
.description('Log agent CLIs into fleet boxes over SSH: drive each box\'s device-code OAuth, scrape the URL + code, and surface every pending login in one local browser page. Default drives all codes at once; --interactive walks one box at a time (codes requested just-in-time so they don\'t expire).')
|
|
2161
|
-
.option('--agents <csv>', 'only these agents (comma-separated); default: every agent with a device-code flow')
|
|
2162
|
-
.option('--devices <csv>', 'only these devices (comma-separated); default: every online box')
|
|
2163
|
-
.option('--all', 'target every device-code pair regardless of cached login state (cold cache / forced re-login)')
|
|
2164
|
-
.option('--interactive', 'guided one-box-at-a-time wizard (codes requested just-in-time)')
|
|
2165
|
-
.option('--json', 'output the final result matrix as JSON')
|
|
2166
|
-
.action(async (opts) => {
|
|
2167
|
-
const csv = (s) => (s ? s.split(',').map((x) => x.trim()).filter(Boolean) : undefined);
|
|
2168
|
-
const results = await runFleetLogin({
|
|
2169
|
-
agents: csv(opts.agents),
|
|
2170
|
-
devices: csv(opts.devices),
|
|
2171
|
-
all: opts.all,
|
|
2172
|
-
interactive: opts.interactive,
|
|
2173
|
-
json: opts.json,
|
|
2174
|
-
});
|
|
2175
|
-
if (opts.json) {
|
|
2176
|
-
console.log(JSON.stringify(results, null, 2));
|
|
2177
|
-
}
|
|
2178
|
-
else {
|
|
2179
|
-
for (const line of renderLoginMatrix(results))
|
|
2180
|
-
console.log(line);
|
|
2181
|
-
}
|
|
2182
|
-
if (results.some((r) => r.remotable && r.state !== 'authorized'))
|
|
2183
|
-
process.exitCode = 1;
|
|
2184
|
-
});
|
|
2185
1970
|
devicesCmd
|
|
2186
1971
|
.command('show <name>')
|
|
2187
1972
|
.description('Show the full profile for one device.')
|
|
@@ -2214,53 +1999,6 @@ email) into a single row. Use \`agents devices harnesses\` for the per-install v
|
|
|
2214
1999
|
process.exit(1);
|
|
2215
2000
|
}
|
|
2216
2001
|
});
|
|
2217
|
-
devicesCmd
|
|
2218
|
-
.command('set <name>', { hidden: true })
|
|
2219
|
-
.option('--platform <platform>', 'windows | linux | macos')
|
|
2220
|
-
.option('--user <user>', 'login user')
|
|
2221
|
-
.option('--auth <method>', 'key | password')
|
|
2222
|
-
.option('--bundle <bundle>', 'secrets bundle holding the password (for --auth password)')
|
|
2223
|
-
.option('--bundle-key <key>', "key within the bundle (default 'password')")
|
|
2224
|
-
.option('--identity-file <path>', 'private-key path for --auth key')
|
|
2225
|
-
.option('--clear-identity-file', 'return key auth to ssh-agent/default-key discovery')
|
|
2226
|
-
.action(async (name, opts) => {
|
|
2227
|
-
try {
|
|
2228
|
-
configTombstoneNotice('set <name> [--platform|--user|--auth|--bundle|--bundle-key|--identity-file …]', 'config <name> <ssh.*|platform> <value>');
|
|
2229
|
-
const existing = await mustGetDevice(name);
|
|
2230
|
-
const nextMethod = opts.auth ?? resolveDeviceProfile(existing).auth.method;
|
|
2231
|
-
if (opts.identityFile && nextMethod !== 'key') {
|
|
2232
|
-
throw new Error('--identity-file requires key auth; pass --auth key in the same command.');
|
|
2233
|
-
}
|
|
2234
|
-
const writes = [];
|
|
2235
|
-
if (opts.platform)
|
|
2236
|
-
writes.push({ key: 'platform', value: opts.platform });
|
|
2237
|
-
if (opts.user)
|
|
2238
|
-
writes.push({ key: 'ssh.user', value: opts.user });
|
|
2239
|
-
if (opts.auth)
|
|
2240
|
-
writes.push({ key: 'ssh.auth', value: opts.auth });
|
|
2241
|
-
if (opts.bundle)
|
|
2242
|
-
writes.push({ key: 'ssh.bundle', value: opts.bundle });
|
|
2243
|
-
if (opts.bundleKey)
|
|
2244
|
-
writes.push({ key: 'ssh.bundle-key', value: opts.bundleKey });
|
|
2245
|
-
if (opts.identityFile)
|
|
2246
|
-
writes.push({ key: 'ssh.identity-file', value: opts.identityFile });
|
|
2247
|
-
if (opts.clearIdentityFile)
|
|
2248
|
-
writes.push({ key: 'ssh.identity-file' }); // unset
|
|
2249
|
-
if (writes.length === 0) {
|
|
2250
|
-
printDevicesConfig(name, false);
|
|
2251
|
-
return;
|
|
2252
|
-
}
|
|
2253
|
-
for (const w of writes) {
|
|
2254
|
-
await runDevicesConfig(name, w.key, w.value !== undefined ? [w.value] : [], { unset: w.value === undefined, quiet: true });
|
|
2255
|
-
}
|
|
2256
|
-
const d = resolveDeviceProfile((await mustGetDevice(name)));
|
|
2257
|
-
console.log(chalk.green(`Updated device '${name}'`) + chalk.gray(` (auth: ${d.auth.method}${d.auth.bundle ? ` via ${d.auth.bundle}` : ''})`));
|
|
2258
|
-
}
|
|
2259
|
-
catch (err) {
|
|
2260
|
-
console.error(chalk.red(err.message));
|
|
2261
|
-
process.exit(1);
|
|
2262
|
-
}
|
|
2263
|
-
});
|
|
2264
2002
|
devicesCmd
|
|
2265
2003
|
.command('remove <name>')
|
|
2266
2004
|
.alias('rm')
|
|
@@ -2633,12 +2371,40 @@ async function doDeviceTaskStop(ref) {
|
|
|
2633
2371
|
process.exitCode = 1;
|
|
2634
2372
|
}
|
|
2635
2373
|
}
|
|
2374
|
+
/**
|
|
2375
|
+
* Parse `--argv`'s JSON array, or report exactly what was wrong and return
|
|
2376
|
+
* undefined. Fails loud rather than coercing: a caller that meant to pass argv
|
|
2377
|
+
* and typo'd the JSON must not silently get shell-string semantics instead.
|
|
2378
|
+
*/
|
|
2379
|
+
export function parseArgvJson(raw, log = (line) => console.error(line)) {
|
|
2380
|
+
let parsed;
|
|
2381
|
+
try {
|
|
2382
|
+
parsed = JSON.parse(raw);
|
|
2383
|
+
}
|
|
2384
|
+
catch (error) {
|
|
2385
|
+
log(`--argv is not valid JSON: ${error.message}`);
|
|
2386
|
+
log(`Expected a JSON array of strings, e.g. --argv '["uptime","-p"]'`);
|
|
2387
|
+
return undefined;
|
|
2388
|
+
}
|
|
2389
|
+
if (!Array.isArray(parsed)) {
|
|
2390
|
+
log('--argv must be a JSON ARRAY of strings.');
|
|
2391
|
+
return undefined;
|
|
2392
|
+
}
|
|
2393
|
+
const bad = parsed.findIndex((token) => typeof token !== 'string');
|
|
2394
|
+
if (bad >= 0) {
|
|
2395
|
+
log(`--argv element ${bad} is ${typeof parsed[bad]}, not a string.`);
|
|
2396
|
+
log('Every element is one argv token; numbers and objects have no argv meaning.');
|
|
2397
|
+
return undefined;
|
|
2398
|
+
}
|
|
2399
|
+
return parsed;
|
|
2400
|
+
}
|
|
2636
2401
|
/** Register the `agents ssh` smart wrapper. */
|
|
2637
2402
|
function registerSshWrapper(program) {
|
|
2638
2403
|
const sshCmd = program
|
|
2639
2404
|
.command('ssh <name> [cmd...]')
|
|
2640
2405
|
.description('Connect to a registered device. Preflights reachability, picks the right shell, and authenticates (key or password-from-bundle).')
|
|
2641
2406
|
.allowUnknownOption()
|
|
2407
|
+
.option('--argv <json>', 'run a JSON array of argv tokens with exact per-token fidelity (no shell splitting)')
|
|
2642
2408
|
.addHelpText('after', `
|
|
2643
2409
|
Examples:
|
|
2644
2410
|
agents ssh yosemite-s0 # interactive login (mirrors your project dir)
|
|
@@ -2646,6 +2412,8 @@ Examples:
|
|
|
2646
2412
|
agents ssh win-mini hostname # run a command (PowerShell on Windows)
|
|
2647
2413
|
agents ssh yosemite-s0 uptime # run a command (POSIX)
|
|
2648
2414
|
agents ssh auto # affinity-pick a device (same engine as 'agents run --device auto')
|
|
2415
|
+
agents ssh box --argv '["agents","feed","post","--title","two words","a & b"]'
|
|
2416
|
+
# exact tokens: spaces and metacharacters survive
|
|
2649
2417
|
|
|
2650
2418
|
Devices come from 'agents devices'. Password auth pulls the secret from a
|
|
2651
2419
|
secrets bundle via an askpass shim — the password never touches argv.
|
|
@@ -2660,8 +2428,31 @@ the target when it exists, else the remote home. Same portable-cwd rule as
|
|
|
2660
2428
|
An 'agents browser …', 'ag browser …', or standalone 'browser …' command is
|
|
2661
2429
|
stamped AGENTS_FLEET_REMOTE so the target's browser.remote-control consent
|
|
2662
2430
|
gate applies, same as 'agents browser <verb> --device <name>'.
|
|
2431
|
+
|
|
2432
|
+
--argv takes a JSON array of strings and delivers each element as exactly ONE
|
|
2433
|
+
token on the peer, so a token containing a space, '&', '|', '$' or a quote
|
|
2434
|
+
arrives intact. The positional form keeps its existing semantics — the remote
|
|
2435
|
+
shell parses it — so the two are mutually exclusive rather than interchangeable.
|
|
2663
2436
|
`)
|
|
2664
|
-
.action(async (name, cmd) => {
|
|
2437
|
+
.action(async (name, cmd, opts) => {
|
|
2438
|
+
// `--argv` is a DIFFERENT delivery contract, not a spelling of the
|
|
2439
|
+
// positional form, so accepting both would have to silently pick one.
|
|
2440
|
+
if (opts.argv !== undefined && cmd.length > 0) {
|
|
2441
|
+
console.error(chalk.red('Pass either --argv <json> or a positional command, not both.'));
|
|
2442
|
+
console.error(chalk.gray('--argv delivers exact tokens; the positional form is parsed by the remote shell.'));
|
|
2443
|
+
process.exit(1);
|
|
2444
|
+
}
|
|
2445
|
+
let argvTokens;
|
|
2446
|
+
if (opts.argv !== undefined) {
|
|
2447
|
+
argvTokens = parseArgvJson(opts.argv);
|
|
2448
|
+
if (!argvTokens)
|
|
2449
|
+
process.exit(1);
|
|
2450
|
+
if (argvTokens.length === 0) {
|
|
2451
|
+
console.error(chalk.red('--argv needs at least one token (the program to run).'));
|
|
2452
|
+
process.exit(1);
|
|
2453
|
+
}
|
|
2454
|
+
cmd = argvTokens;
|
|
2455
|
+
}
|
|
2665
2456
|
// Hidden askpass bridge: ssh execs the shim, which re-invokes us here.
|
|
2666
2457
|
if (name === '__askpass') {
|
|
2667
2458
|
await runAskpass();
|
|
@@ -2734,7 +2525,7 @@ gate applies, same as 'agents browser <verb> --device <name>'.
|
|
|
2734
2525
|
// `agents run --device` (deriveMirroredCwd). Best-effort — a missing dir
|
|
2735
2526
|
// falls back to the remote home. An explicit `cmd` keeps its cwd (RUSH-2412).
|
|
2736
2527
|
const mirrorCwd = cmd.length === 0 ? deriveMirroredCwd(process.cwd()) : undefined;
|
|
2737
|
-
const { args, env } = buildSshInvocation(device, cmd, shim, { pinned }, { interactiveCwd: mirrorCwd });
|
|
2528
|
+
const { args, env } = buildSshInvocation(device, cmd, shim, { pinned }, { interactiveCwd: mirrorCwd, ...(argvTokens ? { argv: true } : {}) });
|
|
2738
2529
|
// Interactive login: make the local terminal's terminfo (e.g.
|
|
2739
2530
|
// xterm-ghostty) available on the remote so backspace/colors/clear work.
|
|
2740
2531
|
// Best-effort + cached per host — never blocks the login (see terminfo.ts).
|