@phnx-labs/agents-cli 1.20.74 → 1.20.77
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 +1055 -0
- package/README.md +36 -10
- package/dist/bin/agents +0 -0
- package/dist/commands/apply.js +21 -3
- package/dist/commands/attach.d.ts +10 -0
- package/dist/commands/attach.js +41 -0
- package/dist/commands/computer.js +3 -3
- package/dist/commands/detach-core.d.ts +46 -0
- package/dist/commands/detach-core.js +61 -0
- package/dist/commands/detach.d.ts +5 -0
- package/dist/commands/detach.js +171 -0
- package/dist/commands/doctor.js +18 -0
- package/dist/commands/exec.js +83 -13
- package/dist/commands/feed.d.ts +7 -1
- package/dist/commands/feed.js +47 -4
- package/dist/commands/go.d.ts +9 -2
- package/dist/commands/go.js +16 -6
- package/dist/commands/harness.d.ts +14 -0
- package/dist/commands/harness.js +145 -0
- package/dist/commands/import.js +38 -8
- package/dist/commands/inspect.js +5 -2
- package/dist/commands/profiles.d.ts +20 -0
- package/dist/commands/profiles.js +68 -21
- package/dist/commands/repo.js +14 -0
- package/dist/commands/routines.js +286 -15
- package/dist/commands/secrets.js +102 -37
- package/dist/commands/sessions-browser.js +12 -2
- package/dist/commands/sessions-export.js +19 -5
- package/dist/commands/sessions-migrate.d.ts +29 -0
- package/dist/commands/sessions-migrate.js +596 -0
- package/dist/commands/sessions-picker.d.ts +14 -1
- package/dist/commands/sessions-picker.js +184 -16
- package/dist/commands/sessions.d.ts +56 -14
- package/dist/commands/sessions.js +322 -62
- package/dist/commands/setup-computer.js +2 -2
- package/dist/commands/ssh.d.ts +13 -0
- package/dist/commands/ssh.js +110 -37
- package/dist/commands/status.js +10 -2
- package/dist/commands/versions.js +15 -6
- package/dist/commands/view.d.ts +5 -0
- package/dist/commands/view.js +24 -4
- package/dist/commands/watchdog.d.ts +7 -2
- package/dist/commands/watchdog.js +73 -57
- package/dist/index.js +59 -7
- package/dist/lib/activity.d.ts +17 -6
- package/dist/lib/activity.js +466 -40
- package/dist/lib/actor.d.ts +50 -0
- package/dist/lib/actor.js +166 -0
- package/dist/lib/agent-spec/provider.js +2 -1
- package/dist/lib/agent-spec/resolve.js +19 -5
- package/dist/lib/agent-spec/types.d.ts +9 -1
- package/dist/lib/agents.d.ts +21 -0
- package/dist/lib/agents.js +70 -6
- package/dist/lib/cloud/codex.d.ts +2 -0
- package/dist/lib/cloud/codex.js +14 -3
- package/dist/lib/cloud/session-index.d.ts +32 -0
- package/dist/lib/cloud/session-index.js +58 -0
- package/dist/lib/cloud/store.d.ts +7 -0
- package/dist/lib/cloud/store.js +25 -0
- package/dist/lib/config-transfer.js +4 -0
- package/dist/lib/daemon.d.ts +10 -1
- package/dist/lib/daemon.js +114 -11
- package/dist/lib/devices/connect.d.ts +15 -1
- package/dist/lib/devices/connect.js +15 -1
- package/dist/lib/devices/fleet.d.ts +37 -1
- package/dist/lib/devices/fleet.js +36 -2
- package/dist/lib/devices/health-report.d.ts +38 -0
- package/dist/lib/devices/health-report.js +214 -0
- package/dist/lib/devices/health.js +4 -1
- package/dist/lib/devices/reachability.d.ts +31 -0
- package/dist/lib/devices/reachability.js +40 -0
- package/dist/lib/devices/registry.d.ts +33 -0
- package/dist/lib/devices/registry.js +37 -0
- package/dist/lib/devices/resolve-target.d.ts +15 -27
- package/dist/lib/devices/resolve-target.js +63 -102
- package/dist/lib/devices/sync.d.ts +18 -0
- package/dist/lib/devices/sync.js +23 -1
- package/dist/lib/devices/tailscale.d.ts +3 -0
- package/dist/lib/devices/tailscale.js +1 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +82 -0
- package/dist/lib/exec.js +218 -6
- package/dist/lib/feed-post.d.ts +63 -0
- package/dist/lib/feed-post.js +204 -0
- package/dist/lib/feed.d.ts +7 -1
- package/dist/lib/feed.js +96 -6
- package/dist/lib/fleet/apply.d.ts +32 -2
- package/dist/lib/fleet/apply.js +97 -10
- package/dist/lib/fleet/types.d.ts +11 -0
- package/dist/lib/fs-walk.d.ts +13 -0
- package/dist/lib/fs-walk.js +16 -7
- package/dist/lib/heal.d.ts +7 -4
- package/dist/lib/heal.js +10 -22
- package/dist/lib/hooks.d.ts +1 -0
- package/dist/lib/hooks.js +156 -0
- package/dist/lib/hosts/dispatch.d.ts +16 -0
- package/dist/lib/hosts/dispatch.js +26 -4
- package/dist/lib/hosts/passthrough.js +9 -2
- package/dist/lib/hosts/reconnect.d.ts +78 -0
- package/dist/lib/hosts/reconnect.js +127 -0
- package/dist/lib/hosts/registry.d.ts +75 -14
- package/dist/lib/hosts/registry.js +205 -30
- package/dist/lib/hosts/remote-cmd.d.ts +11 -0
- package/dist/lib/hosts/remote-cmd.js +32 -8
- package/dist/lib/hosts/remote-session-id.d.ts +45 -0
- package/dist/lib/hosts/remote-session-id.js +84 -0
- package/dist/lib/hosts/run-target.d.ts +17 -5
- package/dist/lib/hosts/run-target.js +30 -10
- package/dist/lib/hosts/session-index.d.ts +18 -1
- package/dist/lib/hosts/session-index.js +40 -5
- package/dist/lib/hosts/session-marker.d.ts +33 -0
- package/dist/lib/hosts/session-marker.js +51 -0
- package/dist/lib/hosts/tasks.d.ts +8 -4
- package/dist/lib/import.d.ts +2 -0
- package/dist/lib/import.js +35 -2
- package/dist/lib/menubar/install-menubar.d.ts +29 -0
- package/dist/lib/menubar/install-menubar.js +59 -1
- package/dist/lib/menubar/notify-desktop.d.ts +44 -0
- package/dist/lib/menubar/notify-desktop.js +78 -0
- package/dist/lib/migrate.d.ts +16 -0
- package/dist/lib/migrate.js +36 -0
- package/dist/lib/models.d.ts +27 -0
- package/dist/lib/models.js +54 -1
- package/dist/lib/overdue.d.ts +6 -2
- package/dist/lib/overdue.js +10 -36
- package/dist/lib/picker.js +4 -1
- package/dist/lib/platform/process.d.ts +17 -0
- package/dist/lib/platform/process.js +70 -0
- package/dist/lib/profiles-presets.js +9 -7
- package/dist/lib/profiles.d.ts +31 -0
- package/dist/lib/profiles.js +70 -0
- package/dist/lib/pty-server.d.ts +2 -10
- package/dist/lib/pty-server.js +4 -38
- package/dist/lib/registry.d.ts +1 -1
- package/dist/lib/registry.js +48 -8
- package/dist/lib/rotate.d.ts +18 -0
- package/dist/lib/rotate.js +28 -0
- package/dist/lib/routine-notify.d.ts +76 -0
- package/dist/lib/routine-notify.js +190 -0
- package/dist/lib/routines-placement.d.ts +38 -0
- package/dist/lib/routines-placement.js +79 -0
- package/dist/lib/routines-project.d.ts +97 -0
- package/dist/lib/routines-project.js +349 -0
- package/dist/lib/routines.d.ts +94 -0
- package/dist/lib/routines.js +93 -9
- package/dist/lib/runner.d.ts +12 -2
- package/dist/lib/runner.js +242 -20
- package/dist/lib/sandbox.js +10 -0
- package/dist/lib/secrets/account-token.d.ts +20 -0
- package/dist/lib/secrets/account-token.js +64 -0
- package/dist/lib/secrets/agent.d.ts +74 -4
- package/dist/lib/secrets/agent.js +181 -107
- package/dist/lib/secrets/bundles.d.ts +54 -6
- package/dist/lib/secrets/bundles.js +230 -34
- package/dist/lib/secrets/index.d.ts +26 -3
- package/dist/lib/secrets/index.js +42 -12
- package/dist/lib/secrets/rc-hygiene.d.ts +55 -0
- package/dist/lib/secrets/rc-hygiene.js +154 -0
- package/dist/lib/secrets/remote.d.ts +7 -4
- package/dist/lib/secrets/remote.js +7 -4
- package/dist/lib/secrets/session-store.d.ts +6 -2
- package/dist/lib/secrets/session-store.js +65 -15
- package/dist/lib/secrets/sync-commands.d.ts +21 -0
- package/dist/lib/secrets/sync-commands.js +21 -0
- package/dist/lib/secrets/unlock-hints.d.ts +27 -0
- package/dist/lib/secrets/unlock-hints.js +36 -0
- package/dist/lib/self-update.d.ts +23 -2
- package/dist/lib/self-update.js +86 -5
- package/dist/lib/session/active.d.ts +124 -7
- package/dist/lib/session/active.js +390 -56
- package/dist/lib/session/cloud.js +2 -1
- package/dist/lib/session/db.d.ts +54 -0
- package/dist/lib/session/db.js +320 -19
- package/dist/lib/session/detached.d.ts +30 -0
- package/dist/lib/session/detached.js +92 -0
- package/dist/lib/session/discover.d.ts +478 -3
- package/dist/lib/session/discover.js +1740 -472
- package/dist/lib/session/fork.js +2 -1
- package/dist/lib/session/hook-sessions.d.ts +43 -0
- package/dist/lib/session/hook-sessions.js +135 -0
- package/dist/lib/session/linear.d.ts +6 -0
- package/dist/lib/session/linear.js +55 -0
- package/dist/lib/session/migrate-targets.d.ts +65 -0
- package/dist/lib/session/migrate-targets.js +94 -0
- package/dist/lib/session/migrations.d.ts +37 -0
- package/dist/lib/session/migrations.js +60 -0
- package/dist/lib/session/parse.d.ts +18 -0
- package/dist/lib/session/parse.js +141 -32
- package/dist/lib/session/pid-registry.d.ts +34 -2
- package/dist/lib/session/pid-registry.js +49 -2
- package/dist/lib/session/prompt.d.ts +7 -0
- package/dist/lib/session/prompt.js +37 -0
- package/dist/lib/session/provenance.d.ts +7 -0
- package/dist/lib/session/render.d.ts +7 -2
- package/dist/lib/session/render.js +40 -26
- package/dist/lib/session/short-id.d.ts +17 -0
- package/dist/lib/session/short-id.js +20 -0
- package/dist/lib/session/state.d.ts +4 -0
- package/dist/lib/session/state.js +99 -13
- package/dist/lib/session/types.d.ts +25 -1
- package/dist/lib/session/types.js +8 -0
- package/dist/lib/shims.d.ts +1 -1
- package/dist/lib/shims.js +20 -6
- package/dist/lib/sqlite.d.ts +3 -2
- package/dist/lib/sqlite.js +27 -4
- package/dist/lib/staleness/detectors/commands.js +1 -1
- package/dist/lib/staleness/detectors/workflows.js +13 -2
- package/dist/lib/staleness/writers/commands.js +7 -7
- package/dist/lib/staleness/writers/workflows.d.ts +4 -2
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +3 -0
- package/dist/lib/state.d.ts +8 -4
- package/dist/lib/state.js +21 -37
- package/dist/lib/teams/agents.d.ts +19 -10
- package/dist/lib/teams/agents.js +40 -39
- package/dist/lib/types.d.ts +47 -2
- package/dist/lib/usage.d.ts +33 -1
- package/dist/lib/usage.js +172 -12
- package/dist/lib/versions.d.ts +9 -2
- package/dist/lib/versions.js +37 -8
- package/dist/lib/watchdog/log.d.ts +43 -0
- package/dist/lib/watchdog/log.js +69 -0
- package/dist/lib/watchdog/routine.d.ts +44 -0
- package/dist/lib/watchdog/routine.js +69 -0
- package/dist/lib/watchdog/runner.d.ts +51 -7
- package/dist/lib/watchdog/runner.js +239 -64
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdog.js +31 -16
- package/dist/lib/workflows.d.ts +16 -0
- package/dist/lib/workflows.js +110 -1
- package/package.json +1 -1
package/dist/commands/secrets.js
CHANGED
|
@@ -15,19 +15,20 @@ import * as path from 'path';
|
|
|
15
15
|
import { SSH_TARGET_RE, assertValidSshTarget, sshExec } from '../lib/ssh-exec.js';
|
|
16
16
|
import { quoteWin32ExecArg, composeWin32CommandLine } from '../lib/platform/index.js';
|
|
17
17
|
import { ensureDaemonStarted, isDaemonRunning } from '../lib/daemon.js';
|
|
18
|
-
import { parseHostsOption, remoteResolveEnv, remoteSecretsRaw, remoteSecretsStream,
|
|
18
|
+
import { parseHostsOption, remoteResolveEnv, remoteSecretsRaw, remoteSecretsStream, resolveHostSshTarget, } from '../lib/secrets/remote.js';
|
|
19
19
|
import { remoteShellFor, buildWindowsStdinImportCommand } from '../lib/hosts/remote-cmd.js';
|
|
20
20
|
import { resolveRemoteOsSync } from '../lib/hosts/remote-os.js';
|
|
21
|
-
import { bundleExists, bundleItemStore, bundlePolicy, deleteBundle, describeBundle, keychainItemsForBundle, keychainRef, listBundles, migrateLegacyBundles, parseDotenv, readAndResolveBundleEnv, isHeadlessSecretsContext, readBundle, renameBundle, rotateBundleSecret, sanitizeProcessEnv, validateBundleName, validateEnvKey, validateExpiresFutureDated, validateSecretType, writeBundle, writeBundleWithItems, } from '../lib/secrets/bundles.js';
|
|
21
|
+
import { bundleBackend, bundleExists, bundleItemStore, bundlePolicy, deleteBundle, describeBundle, keychainItemsForBundle, keychainRef, listBundles, migrateLegacyBundles, parseDotenv, readAndResolveBundleEnv, isHeadlessSecretsContext, readBundle, readBundleIfDecryptable, renameBundle, rotateBundleSecret, sanitizeProcessEnv, validateBundleName, validateEnvKey, validateExpiresFutureDated, validateSecretType, writeBundle, writeBundleWithItems, } from '../lib/secrets/bundles.js';
|
|
22
22
|
import { encryptForFallback, decryptForFallback } from '../lib/secrets/filestore.js';
|
|
23
23
|
import { getKeychainToken, getKeychainTokens, hasKeychainToken, secretsKeychainItem, setKeychainToken, } from '../lib/secrets/index.js';
|
|
24
24
|
import { assertOpAvailable, createPasswordItem, deleteItemByTitle, extractSecrets, itemExistsByTitle, listItems, listVaults, } from '../lib/onepassword.js';
|
|
25
25
|
import { secretsHoldMs, secretsAgentDurable, agentLoad, agentLock, agentPing, agentStatus, ensureAgentRunning, runAgentLoadFromStdin, runSecretsAgent, uninstallSecretsAgentService, } from '../lib/secrets/agent.js';
|
|
26
|
-
import { saveSession,
|
|
26
|
+
import { saveSession, deleteBundleSessions, deleteAllSessions } from '../lib/secrets/session-store.js';
|
|
27
27
|
import { getCliVersionFresh } from '../lib/version.js';
|
|
28
28
|
import { readMeta } from '../lib/state.js';
|
|
29
29
|
import { parseDuration } from '../lib/hooks/cache.js';
|
|
30
|
-
import { emit } from '../lib/events.js';
|
|
30
|
+
import { emit, query } from '../lib/events.js';
|
|
31
|
+
import { frequentlyPromptedBundles } from '../lib/secrets/unlock-hints.js';
|
|
31
32
|
import { registerCommandGroups, setHelpSections } from '../lib/help.js';
|
|
32
33
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
33
34
|
import { discoverSyncedBundles, importSyncedBundle, } from '../lib/secrets/icloud-import.js';
|
|
@@ -225,17 +226,29 @@ async function importFromICloud(bundleName, opts) {
|
|
|
225
226
|
}
|
|
226
227
|
}
|
|
227
228
|
/**
|
|
228
|
-
* Printed under a
|
|
229
|
-
*
|
|
230
|
-
*
|
|
229
|
+
* Printed under a read failure: if the name matches a bundle stranded in the
|
|
230
|
+
* iCloud Keychain (pre-device-local-cutover era), point at the recovery command
|
|
231
|
+
* instead of leaving a dead end.
|
|
232
|
+
*
|
|
233
|
+
* `stillPresent` distinguishes the two failures, because they need different
|
|
234
|
+
* commands. A MISSING bundle imports straight from iCloud. A bundle that is on
|
|
235
|
+
* disk but undecryptable does not: `import` reads the existing bundle before
|
|
236
|
+
* writing into it, so it fails the same way the read just did. That one has to
|
|
237
|
+
* be deleted first — which is why naming the wrong command here bricked the
|
|
238
|
+
* name entirely.
|
|
231
239
|
*/
|
|
232
|
-
function maybePrintSyncedHint(name) {
|
|
240
|
+
function maybePrintSyncedHint(name, stillPresent) {
|
|
233
241
|
if (process.platform !== 'darwin')
|
|
234
242
|
return;
|
|
235
243
|
try {
|
|
236
|
-
if (discoverSyncedBundles().some((c) => c.name === name))
|
|
237
|
-
|
|
238
|
-
|
|
244
|
+
if (!discoverSyncedBundles().some((c) => c.name === name))
|
|
245
|
+
return;
|
|
246
|
+
console.error(chalk.yellow(stillPresent
|
|
247
|
+
? `A legacy iCloud Keychain copy of '${name}' exists, but the local copy is unreadable and ` +
|
|
248
|
+
`import writes into it. Delete the local copy first, then recover:\n` +
|
|
249
|
+
` agents secrets delete ${name}\n` +
|
|
250
|
+
` agents secrets import ${name} --from icloud`
|
|
251
|
+
: `A legacy iCloud Keychain copy of '${name}' exists. Recover it with: agents secrets import ${name} --from icloud`));
|
|
239
252
|
}
|
|
240
253
|
catch {
|
|
241
254
|
// Hint only — never mask the original error.
|
|
@@ -383,12 +396,12 @@ async function browseRemote(targets, args, tty) {
|
|
|
383
396
|
};
|
|
384
397
|
if (tty) {
|
|
385
398
|
for (const t of targets) {
|
|
386
|
-
const target = await
|
|
399
|
+
const target = await resolveHostSshTarget(t);
|
|
387
400
|
render(t, remoteSecretsRaw(target, args, { tty: true, osLookupName: t }));
|
|
388
401
|
}
|
|
389
402
|
}
|
|
390
403
|
else {
|
|
391
|
-
const resolved = await Promise.all(targets.map(async (t) => ({ name: t, target: await
|
|
404
|
+
const resolved = await Promise.all(targets.map(async (t) => ({ name: t, target: await resolveHostSshTarget(t) })));
|
|
392
405
|
const results = resolved.map(({ name, target }) => remoteSecretsRaw(target, args, { osLookupName: name }));
|
|
393
406
|
targets.forEach((t, i) => render(t, results[i]));
|
|
394
407
|
}
|
|
@@ -871,7 +884,7 @@ export function registerSecretsCommands(program) {
|
|
|
871
884
|
}
|
|
872
885
|
catch (err) {
|
|
873
886
|
console.error(chalk.red(err.message));
|
|
874
|
-
maybePrintSyncedHint(resolvedName);
|
|
887
|
+
maybePrintSyncedHint(resolvedName, bundleExists(resolvedName));
|
|
875
888
|
process.exit(1);
|
|
876
889
|
}
|
|
877
890
|
const entries = describeBundle(bundle);
|
|
@@ -1084,8 +1097,9 @@ export function registerSecretsCommands(program) {
|
|
|
1084
1097
|
process.exit(1);
|
|
1085
1098
|
}
|
|
1086
1099
|
// `secrets get` is the scriptable automation primitive ($(agents secrets
|
|
1087
|
-
// get bundle KEY)); when embedded in a headless routine/CI script
|
|
1088
|
-
//
|
|
1100
|
+
// get bundle KEY)); when embedded in a headless routine/CI script — or run
|
|
1101
|
+
// beneath any agent, which inherits AGENTS_RUNTIME — it must not pop an
|
|
1102
|
+
// unwatched Touch ID prompt. Typed in a plain shell it still prompts.
|
|
1089
1103
|
const { env } = readAndResolveBundleEnv(item, { caller: 'secrets get', keys: [key], keyMode: 'storage', agentOnly: isHeadlessSecretsContext() });
|
|
1090
1104
|
if (!(key in env)) {
|
|
1091
1105
|
console.error(chalk.red(`Key '${key}' not in bundle '${item}'.`));
|
|
@@ -1465,16 +1479,27 @@ Examples:
|
|
|
1465
1479
|
.action(async (name, opts) => {
|
|
1466
1480
|
try {
|
|
1467
1481
|
const resolvedName = name ?? (await pickBundleName('delete'));
|
|
1468
|
-
|
|
1482
|
+
// An undecryptable bundle (lost/rotated passphrase) must still be
|
|
1483
|
+
// deletable: deletion is the ONLY way out of that state, and every other
|
|
1484
|
+
// verb — view, add, import — refuses to touch the name until it's gone.
|
|
1485
|
+
// Its key refs are unreadable, so its keychain items cannot be
|
|
1486
|
+
// enumerated for purging; say so rather than claiming a clean purge.
|
|
1487
|
+
const bundle = readBundleIfDecryptable(resolvedName);
|
|
1469
1488
|
if (!opts.yes) {
|
|
1470
1489
|
if (!isInteractiveTerminal()) {
|
|
1471
1490
|
console.error(chalk.red(`Refusing to delete '${resolvedName}' without --yes in a non-interactive shell.`));
|
|
1472
1491
|
process.exit(1);
|
|
1473
1492
|
}
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1493
|
+
let suffix;
|
|
1494
|
+
if (!bundle) {
|
|
1495
|
+
suffix = ' (unreadable — its keychain items cannot be enumerated and will be left in place)';
|
|
1496
|
+
}
|
|
1497
|
+
else {
|
|
1498
|
+
const keychainCount = describeBundle(bundle).filter((e) => e.kind === 'keychain').length;
|
|
1499
|
+
suffix = keychainCount && !opts.keepSecrets
|
|
1500
|
+
? ` and purge ${keychainCount} keychain item${keychainCount === 1 ? '' : 's'}`
|
|
1501
|
+
: '';
|
|
1502
|
+
}
|
|
1478
1503
|
const { confirm } = await import('@inquirer/prompts');
|
|
1479
1504
|
const proceed = await confirm({
|
|
1480
1505
|
message: `Delete bundle '${resolvedName}'${suffix}?`,
|
|
@@ -1485,7 +1510,7 @@ Examples:
|
|
|
1485
1510
|
return;
|
|
1486
1511
|
}
|
|
1487
1512
|
}
|
|
1488
|
-
if (!opts.keepSecrets) {
|
|
1513
|
+
if (bundle && !opts.keepSecrets) {
|
|
1489
1514
|
const store = bundleItemStore(bundle.backend);
|
|
1490
1515
|
for (const { item } of keychainItemsForBundle(bundle)) {
|
|
1491
1516
|
store.delete(item);
|
|
@@ -1583,7 +1608,7 @@ Examples:
|
|
|
1583
1608
|
}
|
|
1584
1609
|
assertValidSshTarget(opts.host);
|
|
1585
1610
|
const resolvedBundleName = bundleName ?? (await pickBundleName('import into'));
|
|
1586
|
-
const target = await
|
|
1611
|
+
const target = await resolveHostSshTarget(opts.host);
|
|
1587
1612
|
const env = await remoteResolveEnv(target, resolvedBundleName, { osLookupName: opts.host });
|
|
1588
1613
|
const bundle = resolveImportBundle(resolvedBundleName, opts.backend, opts.synced);
|
|
1589
1614
|
const { added, skipped } = applyEnvToBundle(bundle, env, opts);
|
|
@@ -1793,10 +1818,10 @@ Examples:
|
|
|
1793
1818
|
process.exit(1);
|
|
1794
1819
|
}
|
|
1795
1820
|
// `agents secrets export --plaintext` is what release/CI scripts eval.
|
|
1796
|
-
// When it runs detached (both stdio non-TTY) or
|
|
1797
|
-
// resolve broker-only so it can never pop a Touch
|
|
1798
|
-
// interactive user's screen. An
|
|
1799
|
-
//
|
|
1821
|
+
// When it runs detached (both stdio non-TTY) or beneath ANY agent — which
|
|
1822
|
+
// inherits AGENTS_RUNTIME — resolve broker-only so it can never pop a Touch
|
|
1823
|
+
// ID sheet on the interactive user's screen. An `eval "$(...)"` typed in a
|
|
1824
|
+
// plain shell carries no AGENTS_RUNTIME, so it is not headless and still prompts.
|
|
1800
1825
|
const { env } = readAndResolveBundleEnv(resolvedBundleName, {
|
|
1801
1826
|
caller: `export to shell`,
|
|
1802
1827
|
keyMode: 'process',
|
|
@@ -1848,7 +1873,7 @@ Examples:
|
|
|
1848
1873
|
// locally instead.
|
|
1849
1874
|
const { assertRemoteBundleFlagsUnsupported } = await import('../lib/secrets/bundles.js');
|
|
1850
1875
|
assertRemoteBundleFlagsUnsupported(bundleName, execOpts.host, { keys: keysSubset, allowExpired: execOpts.allowExpired }, { keysFlag: '--keys', allowExpiredFlag: '--allow-expired' });
|
|
1851
|
-
secretEnv = await remoteResolveEnv(await
|
|
1876
|
+
secretEnv = await remoteResolveEnv(await resolveHostSshTarget(execOpts.host), bundleName, { osLookupName: execOpts.host });
|
|
1852
1877
|
}
|
|
1853
1878
|
else {
|
|
1854
1879
|
const { readAndResolveBundleEnv } = await import('../lib/secrets/bundles.js');
|
|
@@ -2032,6 +2057,7 @@ Examples:
|
|
|
2032
2057
|
.description('Hold a bundle in the secrets-agent after one Touch ID, so concurrent runs read it without re-prompting (macOS). With --host, unlock FILE-backed bundle(s) on a remote (the passphrase prompt surfaces over the SSH TTY); keychain/biometry bundles are GUI-only and can\'t be remote-unlocked.')
|
|
2033
2058
|
.option('--ttl <duration>', 'How long to hold it (e.g. 30m, 8h, 3d). Default 7d.')
|
|
2034
2059
|
.option('--durable', 'Keep the unlock across sleep + reboot too (default: survives upgrade/restart but re-locks on sleep). Set secrets.agent.durable in agents.yaml to make this the default.')
|
|
2060
|
+
.option('--for <agent>', 'Scope the unlock to one harness type (for example claude, codex, or kimi).')
|
|
2035
2061
|
.option('--all', 'Unlock every configured bundle')
|
|
2036
2062
|
.option('--host <target>', 'Unlock the bundle(s) on this remote machine over SSH instead of locally (file-backed bundles only — the remote\'s passphrase prompt surfaces on your terminal over a -tt session). Single-valued (NOT variadic) so it never swallows the bundle name: `unlock <name> --host <machine>`.')
|
|
2037
2063
|
.action(async (names, opts) => {
|
|
@@ -2052,7 +2078,7 @@ Examples:
|
|
|
2052
2078
|
const unlockArgs = buildRemoteUnlockArgs(names, opts);
|
|
2053
2079
|
let failures = 0;
|
|
2054
2080
|
for (const h of hosts) {
|
|
2055
|
-
const target = await
|
|
2081
|
+
const target = await resolveHostSshTarget(h);
|
|
2056
2082
|
// FOREGROUND stream (stdio inherited), NOT the piped remoteSecretsRaw:
|
|
2057
2083
|
// the remote's passphrase prompt only surfaces if the remote process
|
|
2058
2084
|
// sees a real TTY, which requires our local terminal to pass straight
|
|
@@ -2104,12 +2130,19 @@ Examples:
|
|
|
2104
2130
|
// (single-instance start lock, #414) and best-effort — never blocks unlock.
|
|
2105
2131
|
ensureDaemonStarted();
|
|
2106
2132
|
let loaded = 0;
|
|
2133
|
+
const harness = opts.for || process.env.AGENTS_AGENT_NAME || 'cli';
|
|
2107
2134
|
for (const name of targets) {
|
|
2108
2135
|
try {
|
|
2109
2136
|
// noAgent: read the real keychain (one Touch ID) rather than the
|
|
2110
2137
|
// agent we're about to populate.
|
|
2111
|
-
const { bundle, env } = readAndResolveBundleEnv(name, {
|
|
2112
|
-
|
|
2138
|
+
const { bundle, env } = readAndResolveBundleEnv(name, {
|
|
2139
|
+
noAgent: true,
|
|
2140
|
+
caller: 'unlock secrets',
|
|
2141
|
+
agent: harness,
|
|
2142
|
+
duration: humanRemaining(Date.now() + ttlMs),
|
|
2143
|
+
keyMode: 'storage',
|
|
2144
|
+
});
|
|
2145
|
+
if (await agentLoad(name, bundle, env, ttlMs, harness)) {
|
|
2113
2146
|
loaded++;
|
|
2114
2147
|
// Persist a durable session snapshot so the unlock survives a daemon
|
|
2115
2148
|
// restart / upgrade (and sleep too, with --durable). session-store.ts.
|
|
@@ -2118,6 +2151,7 @@ Examples:
|
|
|
2118
2151
|
env,
|
|
2119
2152
|
expiresAt: Date.now() + ttlMs,
|
|
2120
2153
|
sleepPersist: opts.durable ?? secretsAgentDurable(),
|
|
2154
|
+
harness,
|
|
2121
2155
|
});
|
|
2122
2156
|
console.log(`${chalk.green('unlocked')} ${chalk.cyan(name)} ${chalk.gray(`(${Object.keys(env).length} keys, ${humanRemaining(Date.now() + ttlMs)})`)}`);
|
|
2123
2157
|
}
|
|
@@ -2147,7 +2181,7 @@ Examples:
|
|
|
2147
2181
|
let total = 0;
|
|
2148
2182
|
for (const name of names) {
|
|
2149
2183
|
total += await agentLock(name);
|
|
2150
|
-
|
|
2184
|
+
deleteBundleSessions(name); // drop every harness scope
|
|
2151
2185
|
}
|
|
2152
2186
|
console.log(total > 0 ? chalk.green(`Locked ${total} bundle(s).`) : chalk.gray('Nothing to lock.'));
|
|
2153
2187
|
}
|
|
@@ -2193,16 +2227,47 @@ Examples:
|
|
|
2193
2227
|
} })();
|
|
2194
2228
|
console.log(chalk.gray(`hold: ${holdStr}${configured ? ' (secrets.agent.holdMs)' : ' (default)'} — a daily bundle prompts once, then stays silent for this long or until sleep/logout.`));
|
|
2195
2229
|
const entries = await agentStatus();
|
|
2230
|
+
const held = new Set(entries.map((e) => e.name));
|
|
2196
2231
|
if (entries.length === 0) {
|
|
2197
2232
|
console.log(chalk.gray('No bundles held. The next read of each daily bundle will prompt once, then hold.'));
|
|
2198
2233
|
console.log(chalk.gray('Pre-warm now with: agents secrets unlock <bundle> (or --all)'));
|
|
2199
|
-
return;
|
|
2200
2234
|
}
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2235
|
+
else {
|
|
2236
|
+
console.log(chalk.bold(`${'BUNDLE'.padEnd(24)} ${'KEYS'.padEnd(5)} LOCKS IN`));
|
|
2237
|
+
for (const e of entries) {
|
|
2238
|
+
console.log(`${chalk.cyan(e.name.padEnd(24))} ${String(e.keyCount).padEnd(5)} ${humanRemaining(e.expiresAt)}`);
|
|
2239
|
+
}
|
|
2240
|
+
console.log(chalk.gray('Reads of held bundles are silent; any bundle not listed prompts once on its next read.'));
|
|
2241
|
+
}
|
|
2242
|
+
// Usage hint: bundles you keep getting a Touch ID prompt for — a keychain
|
|
2243
|
+
// read that fell through to biometry (not served by the broker/session)
|
|
2244
|
+
// >= a few times in the last week, and not currently held. Unlocking each
|
|
2245
|
+
// once silences it for the hold window. This runs even when NO bundle is
|
|
2246
|
+
// held — that user re-prompts on every read and most needs the nudge.
|
|
2247
|
+
// Excludes `never`/no-ACL bundles (never prompt) and file/vault bundles
|
|
2248
|
+
// (resolve by passphrase, not the keychain broker — `unlock` is a no-op).
|
|
2249
|
+
try {
|
|
2250
|
+
const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
|
|
2251
|
+
const reads = query({ eventTypes: ['secrets.get'], startDate: weekAgo });
|
|
2252
|
+
const hot = frequentlyPromptedBundles(reads, held, { minReads: 3 }).filter((h) => {
|
|
2253
|
+
try {
|
|
2254
|
+
return bundleBackend(h.name) === 'keychain' && bundlePolicy(readBundle(h.name)) !== 'never';
|
|
2255
|
+
}
|
|
2256
|
+
catch {
|
|
2257
|
+
return false; // bundle gone / unreadable metadata — nothing to suggest
|
|
2258
|
+
}
|
|
2259
|
+
});
|
|
2260
|
+
if (hot.length > 0) {
|
|
2261
|
+
console.log();
|
|
2262
|
+
console.log(chalk.bold('Prompted often — unlock once to silence:'));
|
|
2263
|
+
for (const h of hot) {
|
|
2264
|
+
console.log(`${chalk.cyan(h.name.padEnd(24))} ${chalk.gray(`${h.count}× in the last 7d`)} → ${chalk.green(`agents secrets unlock ${h.name}`)}`);
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
catch {
|
|
2269
|
+
// Best-effort hint — never let usage analysis break `secrets status`.
|
|
2204
2270
|
}
|
|
2205
|
-
console.log(chalk.gray('Reads of held bundles are silent; any bundle not listed prompts once on its next read.'));
|
|
2206
2271
|
});
|
|
2207
2272
|
cmd
|
|
2208
2273
|
.command('policy <bundle> [policy]')
|
|
@@ -197,6 +197,16 @@ function applyFilters(rows, live, f, self) {
|
|
|
197
197
|
out = out.filter((r) => live.has(r.id));
|
|
198
198
|
return out;
|
|
199
199
|
}
|
|
200
|
+
/** Derive the SSH-launch origin tag for a picker row from the live index. Set
|
|
201
|
+
* only when the live session's provenance is ssh transport; `device` is the
|
|
202
|
+
* resolved origin device (absent → the row shows a bare `ssh`). Rows without a
|
|
203
|
+
* live entry (the running filter off) get no tag — provenance is live-only. */
|
|
204
|
+
function sshOriginTagFor(live, id) {
|
|
205
|
+
const p = live?.get(id)?.provenance;
|
|
206
|
+
if (p?.transport !== 'ssh')
|
|
207
|
+
return undefined;
|
|
208
|
+
return p.origin?.device ? { device: p.origin.device } : {};
|
|
209
|
+
}
|
|
200
210
|
function headerFor(f) {
|
|
201
211
|
const bits = [
|
|
202
212
|
`device:${f.device ?? 'all'}`,
|
|
@@ -214,7 +224,7 @@ function helpFor(_f, mode) {
|
|
|
214
224
|
if (mode === 'search') {
|
|
215
225
|
return 'type to filter · ↑↓ navigate · esc exit search · ⏎ resume';
|
|
216
226
|
}
|
|
217
|
-
return 's search · r running · c teams · a agent · d device · p project · w window · y copy-cmd · ⏎ resume · esc quit';
|
|
227
|
+
return 's search · r running · c teams · a agent · d device · p project · w window · tab preview · y copy-cmd · ⏎ resume · esc quit';
|
|
218
228
|
}
|
|
219
229
|
/**
|
|
220
230
|
* Launch the interactive session browser. `initial` seeds the filter (e.g.
|
|
@@ -287,7 +297,7 @@ export async function runSessionBrowser(initial = {}, opts = {}) {
|
|
|
287
297
|
initialFilter,
|
|
288
298
|
load,
|
|
289
299
|
keyFor: (s) => s.id,
|
|
290
|
-
labelFor: (s, q) => formatPickerLabel(s, q, cols),
|
|
300
|
+
labelFor: (s, q) => formatPickerLabel(s, q, cols, sshOriginTagFor(liveCache, s.id)),
|
|
291
301
|
matches: sessionMatchesQuery,
|
|
292
302
|
buildPreview,
|
|
293
303
|
headerFor,
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
import * as fs from 'fs';
|
|
21
21
|
import * as path from 'path';
|
|
22
22
|
import chalk from 'chalk';
|
|
23
|
-
import { discoverSessions, resolveSessionById } from '../lib/session/discover.js';
|
|
23
|
+
import { discoverSessions, resolveSessionById, isCompleteSessionId } from '../lib/session/discover.js';
|
|
24
|
+
import { findSessionsById } from '../lib/session/db.js';
|
|
24
25
|
import { filterSessionsByQuery, parseAgentFilter } from './sessions.js';
|
|
25
26
|
import { listLocalTranscripts, SYNC_AGENTS } from '../lib/session/sync/agents.js';
|
|
26
27
|
import { machineId } from '../lib/machine-id.js';
|
|
@@ -228,16 +229,29 @@ function selectSessions(metas, selectors) {
|
|
|
228
229
|
const byId = [];
|
|
229
230
|
const unmatched = [];
|
|
230
231
|
for (const sel of selectors) {
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
232
|
+
const trimmed = sel.trim();
|
|
233
|
+
// Same reason as resolveSessionQuery: the discovered pool is a minority of
|
|
234
|
+
// the index, so a complete id absent from it may still be indexed here.
|
|
235
|
+
const hits = resolveSessionById(metas, trimmed);
|
|
236
|
+
const resolved = hits.length > 0 || !isCompleteSessionId(trimmed) ? hits : findSessionsById(trimmed);
|
|
237
|
+
if (resolved.length > 0)
|
|
238
|
+
byId.push(...resolved);
|
|
234
239
|
else
|
|
235
|
-
unmatched.push(
|
|
240
|
+
unmatched.push(trimmed);
|
|
236
241
|
}
|
|
237
242
|
if (byId.length > 0 && unmatched.length === 0) {
|
|
238
243
|
const seen = new Set();
|
|
239
244
|
return byId.filter(s => (seen.has(s.id) ? false : (seen.add(s.id), true)));
|
|
240
245
|
}
|
|
246
|
+
// A selector that is a COMPLETE session id and still missed cannot be widened:
|
|
247
|
+
// the id is unique, so the text query below could only bundle sessions that
|
|
248
|
+
// merely mention it. Bundling those would ship unrelated transcripts to
|
|
249
|
+
// whoever receives the export, so select nothing and let the caller report it.
|
|
250
|
+
const missingIds = unmatched.filter(isCompleteSessionId);
|
|
251
|
+
if (missingIds.length > 0) {
|
|
252
|
+
process.stderr.write(chalk.red(`No session with id ${missingIds.join(', ')} on this machine.\n`));
|
|
253
|
+
return [];
|
|
254
|
+
}
|
|
241
255
|
// Any selector that isn't an id → treat the whole thing as a text query.
|
|
242
256
|
return filterSessionsByQuery(metas, selectors.join(' '));
|
|
243
257
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import type { SessionMeta } from '../lib/session/types.js';
|
|
3
|
+
/** Agents whose sessions cannot be faithfully --resume'd (buildResumeCommand → null). */
|
|
4
|
+
type MigrateMode = 'resume' | 'rehydrate';
|
|
5
|
+
export declare function registerSessionsMigrateCommand(sessionsCmd: Command): void;
|
|
6
|
+
/**
|
|
7
|
+
* Harness-parity gate (pure, testable). `buildResumeCommand` returns null for the
|
|
8
|
+
* non-resumable agents (gemini, antigravity, openclaw, rush, hermes, grok, kimi,
|
|
9
|
+
* droid) — for those a faithful --resume is impossible, so a requested `resume`
|
|
10
|
+
* transparently becomes `rehydrate`. A resumable agent honors the request.
|
|
11
|
+
* Returns the effective mode plus whether it was downgraded, so the caller can
|
|
12
|
+
* print the notice.
|
|
13
|
+
*/
|
|
14
|
+
export declare function effectiveMode(source: SessionMeta, requested: MigrateMode): {
|
|
15
|
+
mode: MigrateMode;
|
|
16
|
+
downgraded: boolean;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Rehydrate command: launch a fresh agent that reads the transcript. For a
|
|
20
|
+
* non-resumable agent there is no faithful --resume, so we start the agent with
|
|
21
|
+
* a prompt pointing it at the imported transcript path.
|
|
22
|
+
*/
|
|
23
|
+
export declare function rehydrateCommand(source: SessionMeta): string[];
|
|
24
|
+
/**
|
|
25
|
+
* `agents sessions migrations` — the border tracker: the append-only ledger of
|
|
26
|
+
* every session handed off to/from another machine (RUSH-1977).
|
|
27
|
+
*/
|
|
28
|
+
export declare function registerSessionsMigrationsCommand(sessionsCmd: Command): void;
|
|
29
|
+
export {};
|