@phnx-labs/agents-cli 1.14.2 → 1.14.4
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/README.md +17 -7
- package/dist/browser.d.ts +2 -0
- package/dist/browser.js +7 -0
- package/dist/commands/browser.d.ts +3 -0
- package/dist/commands/browser.js +392 -0
- package/dist/commands/daemon.js +1 -1
- package/dist/commands/doctor.d.ts +16 -9
- package/dist/commands/doctor.js +248 -12
- package/dist/commands/prune.js +9 -3
- package/dist/commands/refresh-rules.d.ts +15 -0
- package/dist/commands/{refresh-memory.js → refresh-rules.js} +14 -14
- package/dist/commands/routines.js +1 -1
- package/dist/commands/rules.js +100 -4
- package/dist/commands/secrets.js +198 -11
- package/dist/commands/sync.js +19 -0
- package/dist/commands/teams.js +184 -22
- package/dist/commands/trash.d.ts +10 -0
- package/dist/commands/trash.js +187 -0
- package/dist/commands/view.js +47 -14
- package/dist/index.js +62 -4
- package/dist/lib/agents.js +2 -2
- package/dist/lib/browser/cdp.d.ts +24 -0
- package/dist/lib/browser/cdp.js +94 -0
- package/dist/lib/browser/chrome.d.ts +16 -0
- package/dist/lib/browser/chrome.js +157 -0
- package/dist/lib/browser/drivers/local.d.ts +8 -0
- package/dist/lib/browser/drivers/local.js +22 -0
- package/dist/lib/browser/drivers/ssh.d.ts +9 -0
- package/dist/lib/browser/drivers/ssh.js +129 -0
- package/dist/lib/browser/index.d.ts +5 -0
- package/dist/lib/browser/index.js +5 -0
- package/dist/lib/browser/input.d.ts +6 -0
- package/dist/lib/browser/input.js +52 -0
- package/dist/lib/browser/ipc.d.ts +12 -0
- package/dist/lib/browser/ipc.js +223 -0
- package/dist/lib/browser/profiles.d.ts +11 -0
- package/dist/lib/browser/profiles.js +61 -0
- package/dist/lib/browser/refs.d.ts +21 -0
- package/dist/lib/browser/refs.js +88 -0
- package/dist/lib/browser/service.d.ts +45 -0
- package/dist/lib/browser/service.js +404 -0
- package/dist/lib/browser/types.d.ts +73 -0
- package/dist/lib/browser/types.js +7 -0
- package/dist/lib/cloud/codex.js +1 -1
- package/dist/lib/cloud/registry.js +2 -2
- package/dist/lib/cloud/rush.js +2 -2
- package/dist/lib/cloud/store.js +2 -2
- package/dist/lib/daemon.d.ts +1 -1
- package/dist/lib/daemon.js +47 -11
- package/dist/lib/diff-text.d.ts +25 -0
- package/dist/lib/diff-text.js +47 -0
- package/dist/lib/doctor-diff.d.ts +64 -0
- package/dist/lib/doctor-diff.js +497 -0
- package/dist/lib/git.js +3 -3
- package/dist/lib/hooks.d.ts +6 -0
- package/dist/lib/hooks.js +6 -1
- package/dist/lib/migrate.js +123 -0
- package/dist/lib/pty-client.js +3 -3
- package/dist/lib/pty-server.js +36 -7
- package/dist/lib/resources/commands.d.ts +46 -0
- package/dist/lib/resources/commands.js +208 -0
- package/dist/lib/resources/hooks.d.ts +12 -0
- package/dist/lib/resources/hooks.js +136 -0
- package/dist/lib/resources/index.d.ts +36 -0
- package/dist/lib/resources/index.js +69 -0
- package/dist/lib/resources/mcp.d.ts +34 -0
- package/dist/lib/resources/mcp.js +483 -0
- package/dist/lib/resources/permissions.d.ts +13 -0
- package/dist/lib/resources/permissions.js +184 -0
- package/dist/lib/resources/rules.d.ts +43 -0
- package/dist/lib/resources/rules.js +146 -0
- package/dist/lib/resources/skills.d.ts +37 -0
- package/dist/lib/resources/skills.js +238 -0
- package/dist/lib/resources/subagents.d.ts +46 -0
- package/dist/lib/resources/subagents.js +198 -0
- package/dist/lib/resources/types.d.ts +82 -0
- package/dist/lib/resources/types.js +8 -0
- package/dist/lib/resources.js +1 -1
- package/dist/lib/rotate.d.ts +8 -1
- package/dist/lib/rotate.js +17 -4
- package/dist/lib/rules/compile.d.ts +104 -0
- package/dist/lib/{memory-compile.js → rules/compile.js} +160 -21
- package/dist/lib/rules/compose.d.ts +78 -0
- package/dist/lib/rules/compose.js +170 -0
- package/dist/lib/{memory.d.ts → rules/rules.d.ts} +5 -5
- package/dist/lib/{memory.js → rules/rules.js} +10 -10
- package/dist/lib/secrets/AgentsKeychain.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/AgentsKeychain.app/Contents/MacOS/AgentsKeychain +0 -0
- package/dist/lib/secrets/bundles.d.ts +61 -4
- package/dist/lib/secrets/bundles.js +222 -54
- package/dist/lib/secrets/index.d.ts +24 -5
- package/dist/lib/secrets/index.js +70 -41
- package/dist/lib/session/active.js +5 -5
- package/dist/lib/session/db.js +4 -4
- package/dist/lib/session/discover.js +2 -2
- package/dist/lib/session/render.js +21 -7
- package/dist/lib/shims.d.ts +28 -4
- package/dist/lib/shims.js +72 -14
- package/dist/lib/state.d.ts +22 -28
- package/dist/lib/state.js +83 -78
- package/dist/lib/sync-manifest.d.ts +2 -2
- package/dist/lib/sync-manifest.js +5 -5
- package/dist/lib/teams/agents.d.ts +4 -2
- package/dist/lib/teams/agents.js +11 -4
- package/dist/lib/teams/api.d.ts +1 -1
- package/dist/lib/teams/api.js +2 -2
- package/dist/lib/teams/index.d.ts +1 -0
- package/dist/lib/teams/index.js +1 -0
- package/dist/lib/teams/persistence.js +3 -3
- package/dist/lib/teams/registry.d.ts +12 -1
- package/dist/lib/teams/registry.js +12 -2
- package/dist/lib/teams/worktree.d.ts +30 -0
- package/dist/lib/teams/worktree.js +96 -0
- package/dist/lib/types.d.ts +12 -6
- package/dist/lib/types.js +3 -3
- package/dist/lib/versions.d.ts +32 -3
- package/dist/lib/versions.js +147 -119
- package/package.json +3 -2
- package/scripts/postinstall.js +29 -0
- package/dist/commands/refresh-memory.d.ts +0 -15
- package/dist/lib/memory-compile.d.ts +0 -66
package/dist/commands/secrets.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import * as fs from 'fs';
|
|
10
|
-
import { bundleExists, deleteBundle, describeBundle, keychainItemsForBundle, keychainRef, listBundles, parseDotenv, readBundle, validateBundleName, validateEnvKey, writeBundle, } from '../lib/secrets/bundles.js';
|
|
10
|
+
import { bundleExists, deleteBundle, describeBundle, keychainItemsForBundle, keychainRef, listBundles, migrateLegacyBundles, parseDotenv, readBundle, rotateBundleSecret, validateBundleName, validateEnvKey, validateExpiresFutureDated, validateSecretType, writeBundle, } from '../lib/secrets/bundles.js';
|
|
11
11
|
import { deleteKeychainToken, getKeychainToken, hasKeychainToken, secretsKeychainItem, setKeychainToken, } from '../lib/secrets/index.js';
|
|
12
12
|
import { registerCommandGroups } from '../lib/help.js';
|
|
13
13
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
@@ -114,7 +114,11 @@ function renderBundleRow(b) {
|
|
|
114
114
|
const entries = describeBundle(b);
|
|
115
115
|
const keys = entries.length;
|
|
116
116
|
const sensitive = entries.filter((e) => e.kind === 'keychain').length;
|
|
117
|
-
|
|
117
|
+
const expiring = countExpiringSoon(b.meta);
|
|
118
|
+
const expiringCol = expiring > 0
|
|
119
|
+
? chalk.yellow(String(expiring).padEnd(10))
|
|
120
|
+
: ''.padEnd(10);
|
|
121
|
+
return `${chalk.cyan(b.name.padEnd(20))} ${String(keys).padEnd(6)} ${chalk.yellow(String(sensitive).padEnd(10))} ${expiringCol} ${chalk.gray(b.description || '')}`;
|
|
118
122
|
}
|
|
119
123
|
/** Colorize a variable source kind (literal, keychain, env, file, exec). */
|
|
120
124
|
function kindLabel(kind) {
|
|
@@ -135,11 +139,102 @@ function redact(value, reveal) {
|
|
|
135
139
|
return '';
|
|
136
140
|
return '*'.repeat(Math.min(value.length, 8));
|
|
137
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Build a VarMeta patch from CLI flags. Validates each provided field. Returns
|
|
144
|
+
* undefined if no meta flag was passed (so callers know to skip meta updates).
|
|
145
|
+
*
|
|
146
|
+
* `--note -` reads the note from stdin so users can pass long/multi-line notes
|
|
147
|
+
* without shell-escaping. It's mutually exclusive with `--value-stdin`; both
|
|
148
|
+
* trying to consume stdin would race and silently corrupt one or the other.
|
|
149
|
+
*/
|
|
150
|
+
function buildMetaPatch(raw) {
|
|
151
|
+
if (raw.type === undefined && raw.expires === undefined && raw.note === undefined) {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
const patch = {};
|
|
155
|
+
if (raw.type !== undefined) {
|
|
156
|
+
validateSecretType(raw.type);
|
|
157
|
+
patch.type = raw.type;
|
|
158
|
+
}
|
|
159
|
+
if (raw.expires !== undefined) {
|
|
160
|
+
validateExpiresFutureDated(raw.expires);
|
|
161
|
+
patch.expires = raw.expires;
|
|
162
|
+
}
|
|
163
|
+
if (raw.note !== undefined) {
|
|
164
|
+
if (raw.note === '-') {
|
|
165
|
+
if (raw.valueStdin) {
|
|
166
|
+
throw new Error('--note - and --value-stdin both want stdin; only one can read it.');
|
|
167
|
+
}
|
|
168
|
+
const fromStdin = readStdinSync();
|
|
169
|
+
if (!fromStdin)
|
|
170
|
+
throw new Error('No note received on stdin.');
|
|
171
|
+
patch.note = fromStdin;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
patch.note = raw.note;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return patch;
|
|
178
|
+
}
|
|
179
|
+
/** Whole days from now until midnight-UTC of the given ISO date. Negative if past. */
|
|
180
|
+
function daysUntil(iso) {
|
|
181
|
+
const target = new Date(iso + 'T23:59:59Z').getTime();
|
|
182
|
+
const now = Date.now();
|
|
183
|
+
return Math.floor((target - now) / (24 * 60 * 60 * 1000));
|
|
184
|
+
}
|
|
185
|
+
/** Render the meta line under a var, indented. Returns empty string if nothing to show. */
|
|
186
|
+
function renderMetaLine(meta, reveal) {
|
|
187
|
+
if (!meta)
|
|
188
|
+
return '';
|
|
189
|
+
const parts = [];
|
|
190
|
+
if (meta.type)
|
|
191
|
+
parts.push(`type: ${meta.type}`);
|
|
192
|
+
if (meta.expires) {
|
|
193
|
+
const days = daysUntil(meta.expires);
|
|
194
|
+
const tail = `(in ${days} days)`;
|
|
195
|
+
let colored;
|
|
196
|
+
if (days < 0) {
|
|
197
|
+
colored = chalk.red(`expires: ${meta.expires} ${tail}`);
|
|
198
|
+
}
|
|
199
|
+
else if (days < 30) {
|
|
200
|
+
colored = chalk.yellow(`expires: ${meta.expires} ${tail}`);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
colored = chalk.gray(`expires: ${meta.expires} ${tail}`);
|
|
204
|
+
}
|
|
205
|
+
parts.push(colored);
|
|
206
|
+
}
|
|
207
|
+
if (meta.note) {
|
|
208
|
+
let note = meta.note;
|
|
209
|
+
if (!reveal && note.length > 80) {
|
|
210
|
+
note = note.slice(0, 79) + '\u2026';
|
|
211
|
+
}
|
|
212
|
+
parts.push(`note: ${note}`);
|
|
213
|
+
}
|
|
214
|
+
if (parts.length === 0)
|
|
215
|
+
return '';
|
|
216
|
+
return ` ${parts.join(' ')}`;
|
|
217
|
+
}
|
|
218
|
+
/** Count entries in `meta` whose `expires` falls in the next 30 days. */
|
|
219
|
+
function countExpiringSoon(meta) {
|
|
220
|
+
if (!meta)
|
|
221
|
+
return 0;
|
|
222
|
+
let n = 0;
|
|
223
|
+
for (const m of Object.values(meta)) {
|
|
224
|
+
if (!m.expires)
|
|
225
|
+
continue;
|
|
226
|
+
const d = daysUntil(m.expires);
|
|
227
|
+
if (d >= 0 && d < 30)
|
|
228
|
+
n++;
|
|
229
|
+
}
|
|
230
|
+
return n;
|
|
231
|
+
}
|
|
138
232
|
/** Register the `agents secrets` command tree. */
|
|
139
233
|
export function registerSecretsCommands(program) {
|
|
140
234
|
const cmd = program
|
|
141
235
|
.command('secrets')
|
|
142
236
|
.description('Named bundles of env variables backed by macOS Keychain (with optional iCloud sync). Inject into agents via `agents run --secrets <name>`.')
|
|
237
|
+
.hook('preAction', () => { migrateLegacyBundles(); })
|
|
143
238
|
.addHelpText('after', `
|
|
144
239
|
Workflow:
|
|
145
240
|
Bundles are containers; secrets are the variables inside them. Create a
|
|
@@ -164,6 +259,12 @@ Examples:
|
|
|
164
259
|
# Add a literal (non-sensitive) value
|
|
165
260
|
agents secrets add prod LOG_LEVEL --value info
|
|
166
261
|
|
|
262
|
+
# Add with metadata
|
|
263
|
+
agents secrets add prod STRIPE_API_KEY --type api-key --expires 2027-01-15 --note "Live key, owner: payments-team"
|
|
264
|
+
|
|
265
|
+
# Rotate a key (replaces the value, preserves metadata unless overridden)
|
|
266
|
+
agents secrets rotate prod STRIPE_API_KEY
|
|
267
|
+
|
|
167
268
|
# Import an entire .env file straight into keychain
|
|
168
269
|
agents secrets import prod --from .env.prod
|
|
169
270
|
|
|
@@ -187,7 +288,7 @@ Examples:
|
|
|
187
288
|
`);
|
|
188
289
|
registerCommandGroups(cmd, [
|
|
189
290
|
{ title: 'Bundle commands', names: ['list', 'view', 'create', 'delete'] },
|
|
190
|
-
{ title: 'Secret commands', names: ['add', 'remove', 'import', 'export'] },
|
|
291
|
+
{ title: 'Secret commands', names: ['add', 'rotate', 'remove', 'import', 'export'] },
|
|
191
292
|
]);
|
|
192
293
|
cmd
|
|
193
294
|
.command('list')
|
|
@@ -200,7 +301,7 @@ Examples:
|
|
|
200
301
|
console.log(chalk.gray('Try: agents secrets create <name>'));
|
|
201
302
|
return;
|
|
202
303
|
}
|
|
203
|
-
console.log(chalk.bold(`${'NAME'.padEnd(20)} ${'KEYS'.padEnd(6)} ${'SENSITIVE'.padEnd(10)} DESCRIPTION`));
|
|
304
|
+
console.log(chalk.bold(`${'NAME'.padEnd(20)} ${'KEYS'.padEnd(6)} ${'SENSITIVE'.padEnd(10)} ${'EXPIRING'.padEnd(10)} DESCRIPTION`));
|
|
204
305
|
for (const b of bundles) {
|
|
205
306
|
console.log(renderBundleRow(b));
|
|
206
307
|
}
|
|
@@ -259,6 +360,9 @@ Examples:
|
|
|
259
360
|
else {
|
|
260
361
|
console.log(` ${chalk.cyan(e.key.padEnd(28))} ${kindLabel(e.kind).padEnd(18)} ${e.detail}`);
|
|
261
362
|
}
|
|
363
|
+
const metaLine = renderMetaLine(bundle.meta?.[e.key], reveal);
|
|
364
|
+
if (metaLine)
|
|
365
|
+
console.log(metaLine);
|
|
262
366
|
}
|
|
263
367
|
}
|
|
264
368
|
catch (err) {
|
|
@@ -304,29 +408,45 @@ Examples:
|
|
|
304
408
|
cmd
|
|
305
409
|
.command('add [bundle] [key]')
|
|
306
410
|
.description('Add a variable to a bundle. Defaults to keychain-backed; pass --value for literal, --env/--file/--exec for refs.')
|
|
307
|
-
.option('--value <v>', 'Store as a plaintext literal in the
|
|
411
|
+
.option('--value <v>', 'Store as a plaintext literal in the bundle (non-sensitive values only)')
|
|
308
412
|
.option('--value-stdin', 'Read the value from stdin (stored in keychain unless combined with --value)')
|
|
309
413
|
.option('--env <VAR>', 'Store as an env: ref that reads from the parent process.env at run time')
|
|
310
414
|
.option('--file <path>', 'Store as a file: ref that reads from a file at run time')
|
|
311
415
|
.option('--exec <cmd>', 'Store as an exec: ref that runs a command at run time (requires allow_exec)')
|
|
416
|
+
.option('--type <kind>', 'Tag this secret with a type (api-key, token, password, url, database-url, ssh-key, certificate, webhook, note)')
|
|
417
|
+
.option('--expires <YYYY-MM-DD>', 'Mark when this secret expires (must be future-dated)')
|
|
418
|
+
.option('--note <text>', 'Attach a freeform note. Pass `-` to read from stdin (mutually exclusive with --value-stdin).')
|
|
312
419
|
.action(async (bundleName, key, opts) => {
|
|
313
420
|
try {
|
|
314
421
|
const resolvedBundleName = bundleName ?? (await pickBundleName('add to'));
|
|
315
422
|
const bundle = readBundle(resolvedBundleName);
|
|
316
423
|
const resolvedKey = key ?? (await promptKeyName(resolvedBundleName));
|
|
317
424
|
validateEnvKey(resolvedKey);
|
|
425
|
+
if (resolvedKey in bundle.vars) {
|
|
426
|
+
throw new Error(`Key '${resolvedKey}' already exists in bundle '${resolvedBundleName}'. Use 'agents secrets rotate' to refresh it.`);
|
|
427
|
+
}
|
|
318
428
|
const sources = [opts.value !== undefined, Boolean(opts.env), Boolean(opts.file), Boolean(opts.exec)].filter(Boolean).length;
|
|
319
429
|
if (sources > 1) {
|
|
320
430
|
throw new Error('Pick one of: --value, --env, --file, --exec.');
|
|
321
431
|
}
|
|
432
|
+
const metaPatch = buildMetaPatch(opts);
|
|
433
|
+
const applyMeta = () => {
|
|
434
|
+
if (!metaPatch)
|
|
435
|
+
return;
|
|
436
|
+
if (!bundle.meta)
|
|
437
|
+
bundle.meta = {};
|
|
438
|
+
bundle.meta[resolvedKey] = { ...(bundle.meta[resolvedKey] ?? {}), ...metaPatch };
|
|
439
|
+
};
|
|
322
440
|
if (opts.env) {
|
|
323
441
|
bundle.vars[resolvedKey] = `env:${opts.env}`;
|
|
442
|
+
applyMeta();
|
|
324
443
|
writeBundle(bundle);
|
|
325
444
|
console.log(chalk.green(`${resolvedBundleName}.${resolvedKey} -> env:${opts.env}`));
|
|
326
445
|
return;
|
|
327
446
|
}
|
|
328
447
|
if (opts.file) {
|
|
329
448
|
bundle.vars[resolvedKey] = `file:${opts.file}`;
|
|
449
|
+
applyMeta();
|
|
330
450
|
writeBundle(bundle);
|
|
331
451
|
console.log(chalk.green(`${resolvedBundleName}.${resolvedKey} -> file:${opts.file}`));
|
|
332
452
|
return;
|
|
@@ -336,12 +456,14 @@ Examples:
|
|
|
336
456
|
throw new Error(`Bundle '${resolvedBundleName}' does not allow exec refs. Re-create with --allow-exec.`);
|
|
337
457
|
}
|
|
338
458
|
bundle.vars[resolvedKey] = `exec:${opts.exec}`;
|
|
459
|
+
applyMeta();
|
|
339
460
|
writeBundle(bundle);
|
|
340
461
|
console.log(chalk.green(`${resolvedBundleName}.${resolvedKey} -> exec:${opts.exec}`));
|
|
341
462
|
return;
|
|
342
463
|
}
|
|
343
464
|
if (opts.value !== undefined) {
|
|
344
465
|
bundle.vars[resolvedKey] = { value: opts.value };
|
|
466
|
+
applyMeta();
|
|
345
467
|
writeBundle(bundle);
|
|
346
468
|
console.log(chalk.green(`${resolvedBundleName}.${resolvedKey} = <literal>`));
|
|
347
469
|
return;
|
|
@@ -359,6 +481,7 @@ Examples:
|
|
|
359
481
|
const item = secretsKeychainItem(resolvedBundleName, resolvedKey);
|
|
360
482
|
setKeychainToken(item, secretValue, bundle.icloud_sync);
|
|
361
483
|
bundle.vars[resolvedKey] = keychainRef(resolvedKey);
|
|
484
|
+
applyMeta();
|
|
362
485
|
writeBundle(bundle);
|
|
363
486
|
const where = bundle.icloud_sync ? 'iCloud Keychain' : 'keychain';
|
|
364
487
|
console.log(chalk.green(`${resolvedBundleName}.${resolvedKey} stored in ${where} (${item}).`));
|
|
@@ -370,10 +493,71 @@ Examples:
|
|
|
370
493
|
process.exit(1);
|
|
371
494
|
}
|
|
372
495
|
});
|
|
496
|
+
cmd
|
|
497
|
+
.command('rotate [bundle] [key]')
|
|
498
|
+
.description('Rotate an existing keychain-backed secret (replaces the value, preserves metadata unless overridden).')
|
|
499
|
+
.option('--value <v>', 'New value (non-secret cases). Prompts interactively if omitted.')
|
|
500
|
+
.option('--value-stdin', 'Read the new value from stdin (stored in keychain unless combined with --value)')
|
|
501
|
+
.option('--type <kind>', 'Update the type metadata (api-key, token, password, url, database-url, ssh-key, certificate, webhook, note)')
|
|
502
|
+
.option('--expires <YYYY-MM-DD>', 'Update the expiration date (must be future-dated)')
|
|
503
|
+
.option('--note <text>', 'Update the note. Pass `-` to read from stdin (mutually exclusive with --value-stdin).')
|
|
504
|
+
.option('--clear-meta', 'Wipe all metadata for this key while rotating')
|
|
505
|
+
.addHelpText('after', `
|
|
506
|
+
Examples:
|
|
507
|
+
# Rotate the value, preserve all metadata
|
|
508
|
+
agents secrets rotate prod STRIPE_API_KEY
|
|
509
|
+
|
|
510
|
+
# Rotate with a metadata refresh
|
|
511
|
+
agents secrets rotate prod STRIPE_API_KEY --type api-key --expires 2027-01-15 --note "rotated after employee offboarding"
|
|
512
|
+
`)
|
|
513
|
+
.action(async (bundleName, key, opts) => {
|
|
514
|
+
try {
|
|
515
|
+
const resolvedBundleName = bundleName ?? (await pickBundleName('rotate in'));
|
|
516
|
+
const bundle = readBundle(resolvedBundleName);
|
|
517
|
+
const resolvedKey = key ?? (await pickKey(bundle, 'rotate'));
|
|
518
|
+
if (!(resolvedKey in bundle.vars)) {
|
|
519
|
+
throw new Error(`Key '${resolvedKey}' not in bundle '${resolvedBundleName}'. Use 'agents secrets add' to add a new key.`);
|
|
520
|
+
}
|
|
521
|
+
const raw = bundle.vars[resolvedKey];
|
|
522
|
+
if (typeof raw !== 'string' || !raw.startsWith('keychain:')) {
|
|
523
|
+
throw new Error(`Key '${resolvedKey}' in bundle '${resolvedBundleName}' is not keychain-backed; cannot rotate.`);
|
|
524
|
+
}
|
|
525
|
+
const metaPatch = buildMetaPatch(opts);
|
|
526
|
+
if (opts.clearMeta && metaPatch) {
|
|
527
|
+
throw new Error('--clear-meta and --type/--expires/--note are mutually exclusive.');
|
|
528
|
+
}
|
|
529
|
+
// Resolve the new value: --value > --value-stdin > prompt.
|
|
530
|
+
let newValue;
|
|
531
|
+
if (opts.value !== undefined) {
|
|
532
|
+
newValue = opts.value;
|
|
533
|
+
}
|
|
534
|
+
else if (opts.valueStdin) {
|
|
535
|
+
newValue = readStdinSync();
|
|
536
|
+
if (!newValue)
|
|
537
|
+
throw new Error('No value received on stdin.');
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
newValue = await promptForSecret(`Enter new value for ${resolvedBundleName}.${resolvedKey}`);
|
|
541
|
+
}
|
|
542
|
+
rotateBundleSecret(bundle, resolvedKey, {
|
|
543
|
+
newValue,
|
|
544
|
+
clearMeta: opts.clearMeta,
|
|
545
|
+
meta: metaPatch,
|
|
546
|
+
});
|
|
547
|
+
const where = bundle.icloud_sync ? 'iCloud Keychain' : 'keychain';
|
|
548
|
+
console.log(chalk.green(`${resolvedBundleName}.${resolvedKey} rotated in ${where}.`));
|
|
549
|
+
}
|
|
550
|
+
catch (err) {
|
|
551
|
+
if (isPromptCancelled(err))
|
|
552
|
+
return;
|
|
553
|
+
console.error(chalk.red(err.message));
|
|
554
|
+
process.exit(1);
|
|
555
|
+
}
|
|
556
|
+
});
|
|
373
557
|
cmd
|
|
374
558
|
.command('remove [bundle] [key]')
|
|
375
559
|
.description('Remove a key from the bundle. Purges the keychain item if the ref was keychain:. Use --keep-secret to retain it.')
|
|
376
|
-
.option('--keep-secret', 'Leave the keychain item in place after removing the
|
|
560
|
+
.option('--keep-secret', 'Leave the keychain item in place after removing the ref from the bundle')
|
|
377
561
|
.action(async (bundleName, key, opts) => {
|
|
378
562
|
try {
|
|
379
563
|
const resolvedBundleName = bundleName ?? (await pickBundleName('remove from'));
|
|
@@ -406,7 +590,7 @@ Examples:
|
|
|
406
590
|
cmd
|
|
407
591
|
.command('delete [name]')
|
|
408
592
|
.description('Delete a bundle and purge all its keychain items (use --keep-secrets to retain them).')
|
|
409
|
-
.option('--keep-secrets', 'Leave keychain items in place after deleting the bundle
|
|
593
|
+
.option('--keep-secrets', 'Leave keychain items in place after deleting the bundle')
|
|
410
594
|
.option('-y, --yes', 'Skip the confirmation prompt')
|
|
411
595
|
.action(async (name, opts) => {
|
|
412
596
|
try {
|
|
@@ -454,7 +638,7 @@ Examples:
|
|
|
454
638
|
.command('import [bundle]')
|
|
455
639
|
.description('Import keys from a .env file into a bundle. By default every key is stored in keychain.')
|
|
456
640
|
.requiredOption('--from <path>', 'Path to a .env file')
|
|
457
|
-
.option('--all-plaintext', 'Store every imported value as a
|
|
641
|
+
.option('--all-plaintext', 'Store every imported value as a literal in the bundle metadata (skip keychain item creation)')
|
|
458
642
|
.option('--force', 'Overwrite an existing key in the bundle')
|
|
459
643
|
.action(async (bundleName, opts) => {
|
|
460
644
|
try {
|
|
@@ -495,7 +679,7 @@ Examples:
|
|
|
495
679
|
.option('--plaintext', 'Acknowledge that the resolved values will be printed in the clear')
|
|
496
680
|
.action(async (bundleName, opts) => {
|
|
497
681
|
try {
|
|
498
|
-
const { resolveBundleEnv } = await import('../lib/secrets/bundles.js');
|
|
682
|
+
const { resolveBundleEnv, bundleToEnvPrefix, isReservedEnvName } = await import('../lib/secrets/bundles.js');
|
|
499
683
|
const resolvedBundleName = bundleName ?? (await pickBundleName('export'));
|
|
500
684
|
const bundle = readBundle(resolvedBundleName);
|
|
501
685
|
if (isInteractiveTerminal() && !opts.plaintext) {
|
|
@@ -503,9 +687,12 @@ Examples:
|
|
|
503
687
|
process.exit(1);
|
|
504
688
|
}
|
|
505
689
|
const env = resolveBundleEnv(bundle);
|
|
690
|
+
const prefix = bundleToEnvPrefix(resolvedBundleName);
|
|
506
691
|
for (const [k, v] of Object.entries(env)) {
|
|
507
|
-
const
|
|
508
|
-
|
|
692
|
+
const exportKey = isReservedEnvName(k) ? `${prefix}_${k}` : k;
|
|
693
|
+
const needsQuotes = /[\s$`"'\\|&;<>(){}[\]!#~]/.test(v);
|
|
694
|
+
const output = needsQuotes ? `'${v.replace(/'/g, `'\\''`)}'` : v;
|
|
695
|
+
process.stdout.write(`export ${exportKey}=${output}\n`);
|
|
509
696
|
}
|
|
510
697
|
}
|
|
511
698
|
catch (err) {
|
package/dist/commands/sync.js
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
* synchronize resources (commands, skills, hooks, memory, MCP, etc.)
|
|
6
6
|
* into a specific agent version home before launch.
|
|
7
7
|
*/
|
|
8
|
+
import * as path from 'path';
|
|
8
9
|
import chalk from 'chalk';
|
|
9
10
|
import { AGENTS } from '../lib/agents.js';
|
|
10
11
|
import { isVersionInstalled, syncResourcesToVersion } from '../lib/versions.js';
|
|
12
|
+
import { compileRulesForProject } from '../lib/rules/compile.js';
|
|
11
13
|
/** Register the hidden `agents sync` command. */
|
|
12
14
|
export function registerSyncCommand(program) {
|
|
13
15
|
program
|
|
@@ -39,6 +41,14 @@ export function registerSyncCommand(program) {
|
|
|
39
41
|
return;
|
|
40
42
|
}
|
|
41
43
|
const result = syncResourcesToVersion(agentId, version, undefined, { projectDir, cwd });
|
|
44
|
+
// Compile project-scope rules into the workspace itself so each agent's
|
|
45
|
+
// native loader picks up cwd/<INSTRUCTIONS_FILE>. projectDir is the
|
|
46
|
+
// .agents/ directory; the workspace root is its parent.
|
|
47
|
+
let projectCompile = null;
|
|
48
|
+
if (projectDir) {
|
|
49
|
+
const projectRoot = path.dirname(projectDir);
|
|
50
|
+
projectCompile = compileRulesForProject(projectRoot);
|
|
51
|
+
}
|
|
42
52
|
if (quiet) {
|
|
43
53
|
return;
|
|
44
54
|
}
|
|
@@ -65,5 +75,14 @@ export function registerSyncCommand(program) {
|
|
|
65
75
|
else {
|
|
66
76
|
console.log(chalk.gray('No resources to sync'));
|
|
67
77
|
}
|
|
78
|
+
if (projectCompile?.compiled) {
|
|
79
|
+
const linkInfo = projectCompile.symlinks.length > 0
|
|
80
|
+
? ` (+ ${projectCompile.symlinks.join(', ')})`
|
|
81
|
+
: '';
|
|
82
|
+
console.log(chalk.gray(`Compiled project rules → ${projectCompile.agentsPath}${linkInfo}`));
|
|
83
|
+
}
|
|
84
|
+
if (projectCompile && projectCompile.skippedClobber.length > 0) {
|
|
85
|
+
console.log(chalk.yellow(`Skipped (user-authored, not overwritten): ${projectCompile.skippedClobber.join(', ')}`));
|
|
86
|
+
}
|
|
68
87
|
});
|
|
69
88
|
}
|