@phnx-labs/agents-cli 1.20.21 → 1.20.23

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/commands/cloud.js +142 -13
  3. package/dist/commands/exec.js +13 -1
  4. package/dist/commands/menubar.d.ts +10 -0
  5. package/dist/commands/menubar.js +83 -0
  6. package/dist/commands/routines.js +34 -1
  7. package/dist/commands/secrets.d.ts +1 -1
  8. package/dist/commands/secrets.js +95 -38
  9. package/dist/index.js +292 -225
  10. package/dist/lib/agents.js +8 -0
  11. package/dist/lib/cloud/antigravity.d.ts +70 -0
  12. package/dist/lib/cloud/antigravity.js +196 -0
  13. package/dist/lib/cloud/codex.d.ts +1 -0
  14. package/dist/lib/cloud/codex.js +8 -2
  15. package/dist/lib/cloud/factory.d.ts +79 -18
  16. package/dist/lib/cloud/factory.js +324 -26
  17. package/dist/lib/cloud/registry.d.ts +18 -2
  18. package/dist/lib/cloud/registry.js +28 -4
  19. package/dist/lib/cloud/types.d.ts +73 -2
  20. package/dist/lib/cloud/types.js +17 -0
  21. package/dist/lib/exec.d.ts +2 -0
  22. package/dist/lib/exec.js +5 -0
  23. package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +20 -0
  24. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  25. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +115 -0
  26. package/dist/lib/menubar/install-menubar.d.ts +57 -0
  27. package/dist/lib/menubar/install-menubar.js +291 -0
  28. package/dist/lib/secrets/agent.d.ts +9 -1
  29. package/dist/lib/secrets/agent.js +91 -10
  30. package/dist/lib/secrets/bundles.d.ts +19 -12
  31. package/dist/lib/secrets/bundles.js +22 -14
  32. package/dist/lib/self-update.d.ts +34 -0
  33. package/dist/lib/self-update.js +63 -2
  34. package/dist/lib/startup/command-registry.d.ts +99 -0
  35. package/dist/lib/startup/command-registry.js +136 -0
  36. package/dist/lib/types.d.ts +8 -0
  37. package/dist/lib/version.d.ts +11 -0
  38. package/dist/lib/version.js +20 -0
  39. package/package.json +5 -3
  40. package/scripts/postinstall.js +35 -0
@@ -5,10 +5,11 @@
5
5
  * and managing named bundles of environment variables backed by macOS
6
6
  * Keychain. Bundles are injected at run time via `agents run --secrets`.
7
7
  */
8
+ import { Option } from 'commander';
8
9
  import chalk from 'chalk';
9
10
  import * as fs from 'fs';
10
11
  import { spawnSync } from 'child_process';
11
- import { bundleExists, bundleItemStore, bundleTier, deleteBundle, describeBundle, keychainItemsForBundle, keychainRef, listBundles, migrateLegacyBundles, parseDotenv, readAndResolveBundleEnv, readBundle, renameBundle, rotateBundleSecret, validateBundleName, validateEnvKey, validateExpiresFutureDated, validateSecretType, writeBundle, } from '../lib/secrets/bundles.js';
12
+ import { bundleExists, bundleItemStore, bundlePolicy, deleteBundle, describeBundle, keychainItemsForBundle, keychainRef, listBundles, migrateLegacyBundles, parseDotenv, readAndResolveBundleEnv, readBundle, renameBundle, rotateBundleSecret, validateBundleName, validateEnvKey, validateExpiresFutureDated, validateSecretType, writeBundle, } from '../lib/secrets/bundles.js';
12
13
  import { getKeychainToken, getKeychainTokens, hasKeychainToken, secretsKeychainItem, setKeychainToken, } from '../lib/secrets/index.js';
13
14
  import { assertOpAvailable, createPasswordItem, deleteItemByTitle, extractSecrets, itemExistsByTitle, listItems, listVaults, } from '../lib/onepassword.js';
14
15
  import { DEFAULT_TTL_MS, agentLoad, agentLock, agentStatus, ensureAgentRunning, installSecretsAgentService, runAgentLoadFromStdin, runSecretsAgent, secretsAgentServiceInstalled, uninstallSecretsAgentService, } from '../lib/secrets/agent.js';
@@ -212,8 +213,30 @@ function humanAge(iso) {
212
213
  return age;
213
214
  return `${age} ago`;
214
215
  }
216
+ /** Compact remaining-time for the list POLICY column: "19h" / "45m" / "2d". */
217
+ function compactRemaining(expiresAt) {
218
+ const ms = expiresAt - Date.now();
219
+ if (ms <= 0)
220
+ return 'expired';
221
+ const mins = Math.round(ms / 60000);
222
+ if (mins < 60)
223
+ return `${mins}m`;
224
+ const hours = Math.round(mins / 60);
225
+ if (hours < 24)
226
+ return `${hours}h`;
227
+ return `${Math.round(hours / 24)}d`;
228
+ }
229
+ /** The POLICY column for `secrets list`: the prompt policy, plus a "Nh left"
230
+ * hint when a `daily` bundle is currently held by the secrets-agent. `held`
231
+ * maps bundle name → expiry epoch-ms (from agentStatus()). */
232
+ function renderPolicyCol(b, held) {
233
+ if (bundlePolicy(b) === 'always')
234
+ return chalk.yellow('always ask');
235
+ const exp = held?.get(b.name);
236
+ return exp ? chalk.green(`daily · ${compactRemaining(exp)} left`) : chalk.gray('daily');
237
+ }
215
238
  /** Format a single bundle as a table row for the `secrets list` output. */
216
- function renderBundleRow(b) {
239
+ function renderBundleRow(b, held) {
217
240
  const entries = describeBundle(b);
218
241
  const keys = entries.length;
219
242
  const expiringCount = countExpiringSoon(b.meta);
@@ -231,6 +254,7 @@ function renderBundleRow(b) {
231
254
  : (b.created_at ? chalk.gray('never') : chalk.gray('?'));
232
255
  const head = `${chalk.cyan(b.name.padEnd(20))} ` +
233
256
  `${String(keys).padEnd(5)} ` +
257
+ `${padVisible(renderPolicyCol(b, held), 18)} ` +
234
258
  `${padVisible(expiring, 9)} ` +
235
259
  `${padVisible(created, 9)} ` +
236
260
  `${padVisible(updated, 9)} ` +
@@ -380,9 +404,12 @@ export function registerSecretsCommands(program) {
380
404
  # Inject the bundle into an agent run
381
405
  agents run claude "deploy the worker" --secrets prod
382
406
 
383
- # See what's in the bundle (values masked)
407
+ # See what's in the bundle (values masked); shows its prompt policy
384
408
  agents secrets view prod
385
409
 
410
+ # Stop a noisy automation bundle from prompting every run: ask once a day
411
+ agents secrets policy prod daily
412
+
386
413
  # Eval the bundle into your current shell
387
414
  eval "$(agents secrets export prod --plaintext)"
388
415
 
@@ -398,11 +425,16 @@ export function registerSecretsCommands(program) {
398
425
  or device passcode; cross-machine sync is handled by 'agents secrets push/pull'.
399
426
 
400
427
  Touch ID noise: macOS pops a prompt per bundle per process, so concurrent
401
- agents each re-prompt. 'agents secrets unlock <bundle>' holds the resolved
402
- bundle in a local agent after one prompt; later runs read it silently until
403
- it expires (default 24h), you 'lock' it, or the screen locks. Nothing on disk.
428
+ agents each re-prompt. Each bundle has a prompt policy, shown in the POLICY
429
+ column of 'agents secrets list':
430
+ always (default) ask for Touch ID every time never auto-held.
431
+ daily ask once, then hold it silently in the local agent up
432
+ to ~24h, until screen-lock / sleep / logout or 'lock'.
433
+ Set it with 'agents secrets policy <bundle> daily'. 'agents secrets unlock
434
+ <bundle>' holds any bundle after one prompt regardless of policy. Nothing on disk.
404
435
 
405
436
  See also:
437
+ agents secrets policy <bundle> daily ask once a day, not every run
406
438
  agents secrets unlock <bundle> hold a bundle after one Touch ID
407
439
  agents secrets lock wipe held bundles (re-prompt next read)
408
440
  agents secrets status show held bundles + when they lock
@@ -416,7 +448,7 @@ export function registerSecretsCommands(program) {
416
448
  registerCommandGroups(cmd, [
417
449
  { title: 'Bundle commands', names: ['list', 'view', 'create', 'rename', 'describe', 'delete'] },
418
450
  { title: 'Secret commands', names: ['add', 'rotate', 'remove', 'import', 'export'] },
419
- { title: 'Agent commands', names: ['start', 'stop', 'unlock', 'lock', 'status', 'tier'] },
451
+ { title: 'Agent commands', names: ['start', 'stop', 'unlock', 'lock', 'status', 'policy'] },
420
452
  { title: 'Raw item commands', names: ['get', 'set'] },
421
453
  { title: 'Sync commands', names: ['push', 'pull', 'remote-list'] },
422
454
  { title: 'Utilities', names: ['exec', 'generate', 'migrate-acl'] },
@@ -425,16 +457,28 @@ export function registerSecretsCommands(program) {
425
457
  .command('list')
426
458
  .alias('ls')
427
459
  .description('List configured secrets bundles')
428
- .action(() => {
460
+ .action(async () => {
429
461
  const bundles = listBundles();
430
462
  if (bundles.length === 0) {
431
463
  console.log(chalk.gray('No secrets bundles configured.'));
432
464
  console.log(chalk.gray('Try: agents secrets create <name>'));
433
465
  return;
434
466
  }
435
- console.log(chalk.bold(`${'NAME'.padEnd(20)} ${'KEYS'.padEnd(5)} ${'EXPIRING'.padEnd(9)} ${'CREATED'.padEnd(9)} ${'UPDATED'.padEnd(9)} ${'USED'.padEnd(7)} DESCRIPTION`));
467
+ // Cross-reference the secrets-agent so `daily` bundles that are currently
468
+ // held can show "· Nh left". Soft-fails to no hint if the broker is down.
469
+ const held = new Map();
470
+ if (process.platform === 'darwin') {
471
+ try {
472
+ for (const e of await agentStatus())
473
+ held.set(e.name, e.expiresAt);
474
+ }
475
+ catch {
476
+ /* broker not running — render policy without the countdown */
477
+ }
478
+ }
479
+ console.log(chalk.bold(`${'NAME'.padEnd(20)} ${'KEYS'.padEnd(5)} ${'POLICY'.padEnd(18)} ${'EXPIRING'.padEnd(9)} ${'CREATED'.padEnd(9)} ${'UPDATED'.padEnd(9)} ${'USED'.padEnd(7)} DESCRIPTION`));
436
480
  for (const b of bundles) {
437
- console.log(renderBundleRow(b));
481
+ console.log(renderBundleRow(b, held));
438
482
  }
439
483
  });
440
484
  cmd
@@ -455,8 +499,9 @@ export function registerSecretsCommands(program) {
455
499
  console.log(chalk.yellow('allow_exec: true'));
456
500
  if (bundle.backend === 'file')
457
501
  console.log(chalk.gray('backend: file (passphrase-encrypted; reads need AGENTS_SECRETS_PASSPHRASE, no Touch ID)'));
458
- if (bundleTier(bundle) === 'session')
459
- console.log(chalk.gray('tier: session (secrets-agent eligible)'));
502
+ console.log(bundlePolicy(bundle) === 'daily'
503
+ ? chalk.gray('policy: daily (ask once, then held ~24h until screen-lock / sleep / logout)')
504
+ : chalk.gray('policy: always (asks for Touch ID every time — never auto-held)'));
460
505
  if (bundle.created_at)
461
506
  console.log(chalk.gray(`created_at: ${bundle.created_at} (${humanAge(bundle.created_at)})`));
462
507
  if (bundle.updated_at)
@@ -578,14 +623,15 @@ export function registerSecretsCommands(program) {
578
623
  .description('Create an empty bundle')
579
624
  .option('--description <text>', 'Free-form description')
580
625
  .option('--allow-exec', 'Allow exec: refs in this bundle (off by default)')
581
- .option('--tier <tier>', 'secrets-agent tier: biometry (default) or session', 'biometry')
626
+ .option('--policy <policy>', 'prompt policy: always (default, ask every time) or daily (ask once a day)')
627
+ .addOption(new Option('--tier <policy>', 'deprecated alias for --policy').hideHelp())
582
628
  .option('--backend <backend>', 'storage backend: keychain (default) or file (passphrase-encrypted, headless-readable)', 'keychain')
583
629
  .option('--force', 'Overwrite an existing bundle')
584
630
  .action(async (name, opts) => {
585
631
  try {
586
632
  const resolvedName = name ?? (await promptBundleName());
587
633
  validateBundleName(resolvedName);
588
- const tier = parseTierOpt(opts.tier);
634
+ const policy = parsePolicyOpt(opts.policy ?? opts.tier);
589
635
  const backend = parseBackendOpt(opts.backend);
590
636
  if (bundleExists(resolvedName) && !opts.force) {
591
637
  console.error(chalk.red(`Bundle '${resolvedName}' already exists. Use --force to overwrite.`));
@@ -596,12 +642,15 @@ export function registerSecretsCommands(program) {
596
642
  description: opts.description,
597
643
  allow_exec: opts.allowExec,
598
644
  backend: backend === 'file' ? 'file' : undefined,
599
- tier,
645
+ policy,
600
646
  vars: {},
601
647
  };
602
648
  writeBundle(bundle);
603
- const tags = [tier === 'session' ? 'tier: session' : null, backend === 'file' ? 'backend: file' : null].filter(Boolean);
604
- console.log(chalk.green(`Bundle '${resolvedName}' created${tags.length ? ` (${tags.join(', ')})` : ''}.`));
649
+ const tags = [
650
+ policy === 'daily' ? 'policy: daily' : 'policy: always ask',
651
+ backend === 'file' ? 'backend: file' : null,
652
+ ].filter(Boolean);
653
+ console.log(chalk.green(`Bundle '${resolvedName}' created (${tags.join(', ')}).`));
605
654
  if (backend === 'file') {
606
655
  console.log(chalk.gray('File-backed: items are AES-256-GCM encrypted under AGENTS_SECRETS_PASSPHRASE (no Touch ID).'));
607
656
  }
@@ -1379,21 +1428,25 @@ Examples:
1379
1428
  }
1380
1429
  });
1381
1430
  cmd
1382
- .command('tier <bundle> [tier]')
1383
- .description("Show or set a bundle's secrets-agent tier: biometry (default) or session.")
1384
- .action((bundleName, tier) => {
1431
+ .command('policy <bundle> [policy]')
1432
+ .alias('tier')
1433
+ .description("Show or set a bundle's prompt policy: always (default, ask every time) or daily (ask once a day).")
1434
+ .action((bundleName, policyArg) => {
1385
1435
  try {
1386
1436
  const bundle = readBundle(bundleName);
1387
- if (tier === undefined) {
1388
- console.log(`${chalk.cyan(bundle.name)} tier: ${chalk.bold(bundleTier(bundle))}`);
1437
+ if (policyArg === undefined) {
1438
+ console.log(`${chalk.cyan(bundle.name)} policy: ${chalk.bold(bundlePolicy(bundle))}`);
1389
1439
  return;
1390
1440
  }
1391
- const next = parseTierOpt(tier);
1392
- bundle.tier = next;
1441
+ const next = parsePolicyOpt(policyArg);
1442
+ bundle.policy = next;
1393
1443
  writeBundle(bundle);
1394
- console.log(chalk.green(`${bundle.name} tier set to ${next}.`));
1395
- if (next === 'session') {
1396
- console.log(chalk.gray('Eligible for the secrets-agent: unlock it, or enable auto-cache with `secrets.agent.auto: true` in agents.yaml.'));
1444
+ console.log(chalk.green(`${bundle.name} policy set to ${next}.`));
1445
+ if (next === 'daily') {
1446
+ console.log(chalk.gray('Held by the secrets-agent after one unlock: run `agents secrets unlock`, or enable auto-cache with `secrets.agent.auto: true` in agents.yaml.'));
1447
+ }
1448
+ else {
1449
+ console.log(chalk.gray('Asks for Touch ID every time — never auto-held.'));
1397
1450
  }
1398
1451
  }
1399
1452
  catch (err) {
@@ -1411,7 +1464,7 @@ Examples:
1411
1464
  }
1412
1465
  process.stdout.write(chalk.gray('Installing launchd service…\n'));
1413
1466
  if (await installSecretsAgentService()) {
1414
- console.log(chalk.green('secrets-agent service running.') + chalk.gray(' It stays up across the session; unlock/auto-cache now connect instantly.'));
1467
+ console.log(chalk.green('secrets-agent service running.') + chalk.gray(' It stays up across your macOS login session; unlock/auto-cache now connect instantly.'));
1415
1468
  }
1416
1469
  else {
1417
1470
  console.error(chalk.red('Service installed but did not become reachable in time (machine may be heavily loaded — launchd will keep retrying).'));
@@ -1443,18 +1496,22 @@ Examples:
1443
1496
  registerSecretsSyncCommands(cmd);
1444
1497
  registerSecretsMigrateAclCommand(cmd);
1445
1498
  }
1446
- /** Validate a --tier value, exiting with a clear message on a bad one. `none`
1447
- * is rejected explicitly: it would require storing items without the biometry
1448
- * ACL (a separate signed-helper change), so it isn't offered yet. */
1449
- function parseTierOpt(raw) {
1450
- const v = (raw ?? 'biometry').toLowerCase();
1451
- if (v === 'biometry' || v === 'session')
1452
- return v;
1453
- if (v === 'none') {
1454
- console.error(chalk.red("tier 'none' (no biometry ACL) is not available yet — use 'biometry' or 'session'."));
1499
+ /** Validate a prompt-policy value, exiting with a clear message on a bad one.
1500
+ * Accepts the legacy `biometry`/`session` tokens as aliases for `always`/`daily`
1501
+ * so older flags and scripts keep working. `never`/`none` (no biometry ACL) is
1502
+ * rejected explicitly — it needs a separate signed-helper change (see
1503
+ * https://github.com/phnx-labs/agents-cli/issues/421). */
1504
+ function parsePolicyOpt(raw) {
1505
+ const v = (raw ?? 'always').toLowerCase();
1506
+ if (v === 'always' || v === 'biometry')
1507
+ return 'always';
1508
+ if (v === 'daily' || v === 'session')
1509
+ return 'daily';
1510
+ if (v === 'never' || v === 'none') {
1511
+ console.error(chalk.red("policy 'never' (no biometry ACL) is not available yet — see https://github.com/phnx-labs/agents-cli/issues/421. Use 'always' or 'daily'."));
1455
1512
  process.exit(1);
1456
1513
  }
1457
- console.error(chalk.red(`Invalid --tier '${raw}'. Use 'biometry' or 'session'.`));
1514
+ console.error(chalk.red(`Invalid policy '${raw}'. Use 'always' or 'daily'.`));
1458
1515
  process.exit(1);
1459
1516
  }
1460
1517
  /** Validate a --backend value, exiting with a clear message on a bad one. */