@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.
- package/CHANGELOG.md +14 -0
- package/dist/commands/cloud.js +142 -13
- package/dist/commands/exec.js +13 -1
- package/dist/commands/menubar.d.ts +10 -0
- package/dist/commands/menubar.js +83 -0
- package/dist/commands/routines.js +34 -1
- package/dist/commands/secrets.d.ts +1 -1
- package/dist/commands/secrets.js +95 -38
- package/dist/index.js +292 -225
- package/dist/lib/agents.js +8 -0
- package/dist/lib/cloud/antigravity.d.ts +70 -0
- package/dist/lib/cloud/antigravity.js +196 -0
- package/dist/lib/cloud/codex.d.ts +1 -0
- package/dist/lib/cloud/codex.js +8 -2
- package/dist/lib/cloud/factory.d.ts +79 -18
- package/dist/lib/cloud/factory.js +324 -26
- package/dist/lib/cloud/registry.d.ts +18 -2
- package/dist/lib/cloud/registry.js +28 -4
- package/dist/lib/cloud/types.d.ts +73 -2
- package/dist/lib/cloud/types.js +17 -0
- package/dist/lib/exec.d.ts +2 -0
- package/dist/lib/exec.js +5 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +20 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +115 -0
- package/dist/lib/menubar/install-menubar.d.ts +57 -0
- package/dist/lib/menubar/install-menubar.js +291 -0
- package/dist/lib/secrets/agent.d.ts +9 -1
- package/dist/lib/secrets/agent.js +91 -10
- package/dist/lib/secrets/bundles.d.ts +19 -12
- package/dist/lib/secrets/bundles.js +22 -14
- package/dist/lib/self-update.d.ts +34 -0
- package/dist/lib/self-update.js +63 -2
- package/dist/lib/startup/command-registry.d.ts +99 -0
- package/dist/lib/startup/command-registry.js +136 -0
- package/dist/lib/types.d.ts +8 -0
- package/dist/lib/version.d.ts +11 -0
- package/dist/lib/version.js +20 -0
- package/package.json +5 -3
- package/scripts/postinstall.js +35 -0
package/dist/commands/secrets.js
CHANGED
|
@@ -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,
|
|
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.
|
|
402
|
-
|
|
403
|
-
|
|
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', '
|
|
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
|
-
|
|
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
|
-
|
|
459
|
-
|
|
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('--
|
|
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
|
|
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
|
-
|
|
645
|
+
policy,
|
|
600
646
|
vars: {},
|
|
601
647
|
};
|
|
602
648
|
writeBundle(bundle);
|
|
603
|
-
const tags = [
|
|
604
|
-
|
|
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('
|
|
1383
|
-
.
|
|
1384
|
-
.
|
|
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 (
|
|
1388
|
-
console.log(`${chalk.cyan(bundle.name)}
|
|
1437
|
+
if (policyArg === undefined) {
|
|
1438
|
+
console.log(`${chalk.cyan(bundle.name)} policy: ${chalk.bold(bundlePolicy(bundle))}`);
|
|
1389
1439
|
return;
|
|
1390
1440
|
}
|
|
1391
|
-
const next =
|
|
1392
|
-
bundle.
|
|
1441
|
+
const next = parsePolicyOpt(policyArg);
|
|
1442
|
+
bundle.policy = next;
|
|
1393
1443
|
writeBundle(bundle);
|
|
1394
|
-
console.log(chalk.green(`${bundle.name}
|
|
1395
|
-
if (next === '
|
|
1396
|
-
console.log(chalk.gray('
|
|
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
|
|
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
|
|
1447
|
-
*
|
|
1448
|
-
*
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
if (v === '
|
|
1454
|
-
|
|
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
|
|
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. */
|