@phnx-labs/agents-cli 1.20.51 → 1.20.53
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 +71 -0
- package/README.md +12 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/browser.js +215 -7
- package/dist/commands/cloud.d.ts +3 -0
- package/dist/commands/cloud.js +8 -1
- package/dist/commands/events.d.ts +1 -1
- package/dist/commands/events.js +2 -3
- package/dist/commands/exec.js +82 -3
- package/dist/commands/factory.js +8 -0
- package/dist/commands/feed.d.ts +38 -0
- package/dist/commands/feed.js +274 -0
- package/dist/commands/logs.d.ts +5 -1
- package/dist/commands/logs.js +248 -3
- package/dist/commands/mcp.js +7 -0
- package/dist/commands/memory.d.ts +9 -0
- package/dist/commands/memory.js +164 -0
- package/dist/commands/message.d.ts +11 -6
- package/dist/commands/message.js +140 -5
- package/dist/commands/routines.js +12 -0
- package/dist/commands/secrets-migrate.d.ts +2 -1
- package/dist/commands/secrets-migrate.js +88 -13
- package/dist/commands/secrets.d.ts +22 -0
- package/dist/commands/secrets.js +176 -42
- package/dist/commands/sessions.js +10 -1
- package/dist/commands/teams.js +4 -0
- package/dist/commands/worktree.js +4 -2
- package/dist/index.js +21 -22
- package/dist/lib/agents.js +249 -17
- package/dist/lib/answer-router.d.ts +75 -0
- package/dist/lib/answer-router.js +149 -0
- package/dist/lib/ask-classifier.d.ts +71 -0
- package/dist/lib/ask-classifier.js +197 -0
- package/dist/lib/browser/login-detection.d.ts +94 -0
- package/dist/lib/browser/login-detection.js +274 -0
- package/dist/lib/browser/profiles.d.ts +17 -8
- package/dist/lib/browser/profiles.js +27 -8
- package/dist/lib/browser/secret-ref.d.ts +10 -0
- package/dist/lib/browser/secret-ref.js +14 -0
- package/dist/lib/browser/service.js +14 -12
- package/dist/lib/cloud/antigravity.d.ts +0 -2
- package/dist/lib/cloud/antigravity.js +2 -17
- package/dist/lib/cloud/codex.js +3 -18
- package/dist/lib/cloud/rush.d.ts +15 -0
- package/dist/lib/cloud/rush.js +10 -16
- package/dist/lib/cloud/stream.js +2 -0
- package/dist/lib/cloud/types.d.ts +21 -0
- package/dist/lib/cloud/types.js +81 -0
- package/dist/lib/crabbox/cli.d.ts +1 -1
- package/dist/lib/crabbox/cli.js +12 -2
- package/dist/lib/crabbox/lease.d.ts +19 -0
- package/dist/lib/crabbox/lease.js +22 -11
- package/dist/lib/crabbox/progress.d.ts +62 -0
- package/dist/lib/crabbox/progress.js +129 -0
- package/dist/lib/crabbox/runtimes.d.ts +38 -1
- package/dist/lib/crabbox/runtimes.js +98 -5
- package/dist/lib/daemon.d.ts +12 -9
- package/dist/lib/daemon.js +32 -17
- package/dist/lib/events.d.ts +31 -5
- package/dist/lib/events.js +291 -101
- package/dist/lib/exec.js +20 -1
- package/dist/lib/feed-outcome.d.ts +101 -0
- package/dist/lib/feed-outcome.js +244 -0
- package/dist/lib/feed-policy.d.ts +30 -0
- package/dist/lib/feed-policy.js +133 -0
- package/dist/lib/feed.d.ts +180 -0
- package/dist/lib/feed.js +627 -0
- package/dist/lib/git.d.ts +17 -1
- package/dist/lib/git.js +20 -1
- package/dist/lib/hooks.js +529 -14
- package/dist/lib/hosts/passthrough.d.ts +3 -3
- package/dist/lib/hosts/passthrough.js +3 -3
- package/dist/lib/mailbox-gc.d.ts +22 -0
- package/dist/lib/mailbox-gc.js +161 -0
- package/dist/lib/mailbox.d.ts +26 -2
- package/dist/lib/mailbox.js +80 -5
- package/dist/lib/mcp.js +82 -0
- package/dist/lib/memory.d.ts +55 -0
- package/dist/lib/memory.js +274 -0
- package/dist/lib/notify.d.ts +16 -0
- package/dist/lib/notify.js +61 -0
- package/dist/lib/operator.d.ts +26 -0
- package/dist/lib/operator.js +107 -0
- package/dist/lib/plugins.d.ts +35 -0
- package/dist/lib/plugins.js +217 -0
- package/dist/lib/remote-agents-json.d.ts +14 -0
- package/dist/lib/remote-agents-json.js +94 -0
- package/dist/lib/resources/mcp.js +44 -0
- package/dist/lib/resources/memory.d.ts +15 -0
- package/dist/lib/resources/memory.js +46 -0
- package/dist/lib/resources/types.d.ts +2 -2
- package/dist/lib/rotate.js +2 -0
- package/dist/lib/runner.d.ts +43 -0
- package/dist/lib/runner.js +323 -74
- package/dist/lib/sandbox.js +6 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/agent.d.ts +21 -0
- package/dist/lib/secrets/agent.js +63 -1
- package/dist/lib/secrets/bundles.d.ts +33 -1
- package/dist/lib/secrets/bundles.js +76 -21
- package/dist/lib/secrets/icloud-import.d.ts +79 -0
- package/dist/lib/secrets/icloud-import.js +203 -0
- package/dist/lib/secrets/index.d.ts +142 -2
- package/dist/lib/secrets/index.js +695 -30
- package/dist/lib/secrets/remote.js +1 -1
- package/dist/lib/secrets/sync.js +1 -1
- package/dist/lib/session/active.d.ts +18 -0
- package/dist/lib/session/active.js +47 -17
- package/dist/lib/session/db.d.ts +9 -1
- package/dist/lib/session/db.js +18 -3
- package/dist/lib/session/discover.d.ts +13 -0
- package/dist/lib/session/discover.js +32 -2
- package/dist/lib/session/parse.d.ts +8 -0
- package/dist/lib/session/parse.js +42 -21
- package/dist/lib/session/remote-active.js +8 -89
- package/dist/lib/session/state.d.ts +11 -0
- package/dist/lib/session/state.js +50 -1
- package/dist/lib/session/tail.d.ts +23 -4
- package/dist/lib/session/tail.js +34 -16
- package/dist/lib/session/throughput.d.ts +30 -0
- package/dist/lib/session/throughput.js +86 -0
- package/dist/lib/shim-heal.d.ts +12 -3
- package/dist/lib/shim-heal.js +12 -6
- package/dist/lib/staleness/detectors/subagents.js +57 -3
- package/dist/lib/staleness/writers/hooks.js +7 -3
- package/dist/lib/staleness/writers/subagents.js +37 -6
- package/dist/lib/startup/command-registry.d.ts +2 -0
- package/dist/lib/startup/command-registry.js +4 -0
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.js +25 -8
- package/dist/lib/subagents.d.ts +52 -0
- package/dist/lib/subagents.js +315 -12
- package/dist/lib/teams/agents.js +6 -3
- package/dist/lib/teams/worktree.d.ts +8 -0
- package/dist/lib/teams/worktree.js +8 -0
- package/dist/lib/types.d.ts +20 -2
- package/dist/lib/versions.js +38 -48
- package/dist/lib/whats-new.d.ts +5 -3
- package/dist/lib/whats-new.js +25 -5
- package/package.json +4 -3
- package/scripts/postinstall.js +61 -1
package/dist/commands/secrets.js
CHANGED
|
@@ -24,6 +24,7 @@ import { parseDuration } from '../lib/hooks/cache.js';
|
|
|
24
24
|
import { emit } from '../lib/events.js';
|
|
25
25
|
import { registerCommandGroups, setHelpSections } from '../lib/help.js';
|
|
26
26
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
27
|
+
import { discoverSyncedBundles, importSyncedBundle, } from '../lib/secrets/icloud-import.js';
|
|
27
28
|
import { registerSecretsSyncCommands } from './secrets-sync.js';
|
|
28
29
|
import { registerSecretsMigrateAclCommand } from './secrets-migrate.js';
|
|
29
30
|
import { registerSecretsImportKeyringCommand } from './secrets-import.js';
|
|
@@ -135,6 +136,104 @@ async function resolveVault(vaultOpt) {
|
|
|
135
136
|
export function readImportDotenv(from) {
|
|
136
137
|
return from === '-' ? readStdinSync() : fs.readFileSync(from, 'utf-8');
|
|
137
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Parse the unified `--from <source>` value: a .env path (`-` reads stdin),
|
|
141
|
+
* `1password:<vault>` (bare `1password` prompts for the vault), or `icloud`
|
|
142
|
+
* (legacy iCloud Keychain bundles). The deprecated `--from-1password --vault`
|
|
143
|
+
* pair maps onto the 1password source. A file literally named `icloud` or
|
|
144
|
+
* `1password` can still be imported via an explicit path (`./icloud`).
|
|
145
|
+
*/
|
|
146
|
+
export function parseImportSource(opts) {
|
|
147
|
+
if (opts.from && opts.from1password) {
|
|
148
|
+
throw new Error('--from and --from-1password are mutually exclusive.');
|
|
149
|
+
}
|
|
150
|
+
if (opts.from1password)
|
|
151
|
+
return { kind: '1password', vault: opts.vault };
|
|
152
|
+
if (!opts.from) {
|
|
153
|
+
throw new Error("Pass --from <source>: a .env path (- reads stdin), '1password:<vault>', or 'icloud'.");
|
|
154
|
+
}
|
|
155
|
+
if (opts.from === 'icloud')
|
|
156
|
+
return { kind: 'icloud' };
|
|
157
|
+
if (opts.from === '1password')
|
|
158
|
+
return { kind: '1password', vault: opts.vault };
|
|
159
|
+
if (opts.from.startsWith('1password:')) {
|
|
160
|
+
const vault = opts.from.slice('1password:'.length);
|
|
161
|
+
return { kind: '1password', vault: vault || opts.vault };
|
|
162
|
+
}
|
|
163
|
+
return { kind: 'dotenv', path: opts.from };
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* `secrets import --from icloud` — recover bundles stranded in the iCloud
|
|
167
|
+
* Keychain by the device-local cutover. With a bundle name, imports exactly
|
|
168
|
+
* that bundle; without one, interactively multi-selects from everything
|
|
169
|
+
* discovered (all pre-checked — the common case is "bring them all back").
|
|
170
|
+
*/
|
|
171
|
+
async function importFromICloud(bundleName, opts) {
|
|
172
|
+
if (process.platform !== 'darwin') {
|
|
173
|
+
throw new Error('--from icloud reads the macOS iCloud Keychain and is only available on macOS.');
|
|
174
|
+
}
|
|
175
|
+
const candidates = discoverSyncedBundles();
|
|
176
|
+
if (candidates.length === 0) {
|
|
177
|
+
console.log('No legacy iCloud Keychain bundles found.');
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const describe = (c) => `${c.name} (${c.keys.length} key${c.keys.length === 1 ? '' : 's'}${c.hasMeta ? '' : ', no metadata'})`;
|
|
181
|
+
let chosen;
|
|
182
|
+
if (bundleName) {
|
|
183
|
+
const hit = candidates.find((c) => c.name === bundleName);
|
|
184
|
+
if (!hit) {
|
|
185
|
+
throw new Error(`No iCloud Keychain bundle named '${bundleName}'. Found: ${candidates.map((c) => c.name).join(', ')}`);
|
|
186
|
+
}
|
|
187
|
+
chosen = [hit];
|
|
188
|
+
}
|
|
189
|
+
else if (!isInteractiveTerminal()) {
|
|
190
|
+
throw new Error(`Found ${candidates.length} iCloud Keychain bundle(s): ${candidates.map((c) => c.name).join(', ')}. ` +
|
|
191
|
+
'Pass a bundle name to import non-interactively.');
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
const { checkbox } = await import('@inquirer/prompts');
|
|
195
|
+
chosen = await checkbox({
|
|
196
|
+
message: 'Which iCloud Keychain bundles to import?',
|
|
197
|
+
choices: candidates.map((c) => ({ name: describe(c), value: c, checked: true })),
|
|
198
|
+
});
|
|
199
|
+
if (chosen.length === 0) {
|
|
200
|
+
console.log('Nothing selected.');
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
for (const candidate of chosen) {
|
|
205
|
+
const result = importSyncedBundle(candidate, opts);
|
|
206
|
+
const parts = [`imported ${result.added} key(s)`];
|
|
207
|
+
if (result.skipped)
|
|
208
|
+
parts.push(`skipped ${result.skipped} (already set, pass --force)`);
|
|
209
|
+
if (result.missing.length)
|
|
210
|
+
parts.push(`unreadable (left in iCloud): ${result.missing.join(', ')}`);
|
|
211
|
+
if (result.unimportable.length)
|
|
212
|
+
parts.push(`reserved, not importable (left in iCloud): ${result.unimportable.join(', ')}`);
|
|
213
|
+
if (opts.purge)
|
|
214
|
+
parts.push(`purged ${result.purged} iCloud item(s)`);
|
|
215
|
+
const line = `${candidate.name}: ${parts.join(', ')}`;
|
|
216
|
+
const warn = result.missing.length > 0 || result.unimportable.length > 0;
|
|
217
|
+
console.log(warn ? chalk.yellow(line) : chalk.green(line));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Printed under a "bundle not found" failure: if the name matches a bundle
|
|
222
|
+
* stranded in the iCloud Keychain (pre-device-local-cutover era), point at the
|
|
223
|
+
* recovery command instead of leaving a dead end.
|
|
224
|
+
*/
|
|
225
|
+
function maybePrintSyncedHint(name) {
|
|
226
|
+
if (process.platform !== 'darwin')
|
|
227
|
+
return;
|
|
228
|
+
try {
|
|
229
|
+
if (discoverSyncedBundles().some((c) => c.name === name)) {
|
|
230
|
+
console.error(chalk.yellow(`A legacy iCloud Keychain copy of '${name}' exists. Recover it with: agents secrets import ${name} --from icloud`));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
// Hint only — never mask the original error.
|
|
235
|
+
}
|
|
236
|
+
}
|
|
138
237
|
/**
|
|
139
238
|
* Build the remote `agents secrets unlock` argv for `unlock --host`. `--all`
|
|
140
239
|
* forwards verbatim; otherwise the explicit bundle names. A `--ttl` is passed
|
|
@@ -525,7 +624,8 @@ export function registerSecretsCommands(program) {
|
|
|
525
624
|
agents secrets status show held bundles + when they lock
|
|
526
625
|
agents secrets rotate <bundle> <key> rotate value, preserve metadata
|
|
527
626
|
agents secrets import <bundle> --from .env bulk import from .env
|
|
528
|
-
agents secrets import <bundle> --from
|
|
627
|
+
agents secrets import <bundle> --from 1password:<vault>
|
|
628
|
+
agents secrets import --from icloud recover legacy iCloud Keychain bundles
|
|
529
629
|
agents secrets generate [length] generate a random password / PIN / hex
|
|
530
630
|
agents secrets migrate-acl upgrade legacy items to the biometry ACL
|
|
531
631
|
`,
|
|
@@ -610,7 +710,15 @@ export function registerSecretsCommands(program) {
|
|
|
610
710
|
return;
|
|
611
711
|
}
|
|
612
712
|
const resolvedName = name ?? (await pickBundleName('view'));
|
|
613
|
-
|
|
713
|
+
let bundle;
|
|
714
|
+
try {
|
|
715
|
+
bundle = readBundle(resolvedName);
|
|
716
|
+
}
|
|
717
|
+
catch (err) {
|
|
718
|
+
console.error(chalk.red(err.message));
|
|
719
|
+
maybePrintSyncedHint(resolvedName);
|
|
720
|
+
process.exit(1);
|
|
721
|
+
}
|
|
614
722
|
const entries = describeBundle(bundle);
|
|
615
723
|
console.log(chalk.bold(bundle.name));
|
|
616
724
|
if (bundle.description)
|
|
@@ -672,7 +780,7 @@ export function registerSecretsCommands(program) {
|
|
|
672
780
|
emit('secrets.get', {
|
|
673
781
|
module: 'secrets',
|
|
674
782
|
bundle: bundle.name,
|
|
675
|
-
|
|
783
|
+
operation: 'view --reveal',
|
|
676
784
|
source: 'reveal',
|
|
677
785
|
status: 'success',
|
|
678
786
|
keyCount: exposedCount,
|
|
@@ -713,24 +821,48 @@ export function registerSecretsCommands(program) {
|
|
|
713
821
|
}
|
|
714
822
|
});
|
|
715
823
|
cmd
|
|
716
|
-
.command('get <item>')
|
|
717
|
-
.description('Print a raw keychain item by name (
|
|
718
|
-
.action((item) => {
|
|
824
|
+
.command('get <item> [key]')
|
|
825
|
+
.description('Print one secret value for shell hooks/automation. One arg = a raw keychain item by name; two args = one KEY out of a bundle (`get <bundle> <KEY>`). Cross-platform.')
|
|
826
|
+
.action((item, key) => {
|
|
827
|
+
if (key === undefined) {
|
|
828
|
+
// Raw keychain item path — unchanged.
|
|
829
|
+
try {
|
|
830
|
+
// Routes through the platform keychain layer: macOS reads bare items
|
|
831
|
+
// via /usr/bin/security (no Touch ID), Linux via secret-tool with the
|
|
832
|
+
// encrypted-file fallback. The value goes to stdout (newline-terminated
|
|
833
|
+
// so `$(agents secrets get NAME)` captures it cleanly); diagnostics go
|
|
834
|
+
// to stderr so they never pollute the captured value.
|
|
835
|
+
const value = getKeychainToken(item);
|
|
836
|
+
// Raw item reads bypass readAndResolveBundleEnv, so audit here too.
|
|
837
|
+
// `item` is the keychain service name, never the value.
|
|
838
|
+
emit('secrets.get', { module: 'secrets', item, source: 'raw-item', status: 'success' });
|
|
839
|
+
process.stdout.write(value.endsWith('\n') ? value : `${value}\n`);
|
|
840
|
+
}
|
|
841
|
+
catch {
|
|
842
|
+
// Missing item is a normal, quiet outcome for a hook probe: exit 1,
|
|
843
|
+
// print nothing to stdout. Callers test the exit code / empty capture.
|
|
844
|
+
process.exit(1);
|
|
845
|
+
}
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
// Bundle-key path: `get <bundle> <KEY>` prints exactly one resolved value.
|
|
849
|
+
// Ungated like the raw path (it IS the automation primitive); the
|
|
850
|
+
// `secrets.get` audit event is emitted inside readAndResolveBundleEnv.
|
|
719
851
|
try {
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
852
|
+
if (!bundleExists(item)) {
|
|
853
|
+
console.error(chalk.red(`Secrets bundle '${item}' not found.`));
|
|
854
|
+
process.exit(1);
|
|
855
|
+
}
|
|
856
|
+
const { env } = readAndResolveBundleEnv(item, { caller: 'secrets get', keys: [key] });
|
|
857
|
+
if (!(key in env)) {
|
|
858
|
+
console.error(chalk.red(`Key '${key}' not in bundle '${item}'.`));
|
|
859
|
+
process.exit(1);
|
|
860
|
+
}
|
|
861
|
+
const value = env[key];
|
|
729
862
|
process.stdout.write(value.endsWith('\n') ? value : `${value}\n`);
|
|
730
863
|
}
|
|
731
|
-
catch {
|
|
732
|
-
|
|
733
|
-
// print nothing to stdout. Callers test the exit code / empty capture.
|
|
864
|
+
catch (err) {
|
|
865
|
+
console.error(chalk.red(err.message));
|
|
734
866
|
process.exit(1);
|
|
735
867
|
}
|
|
736
868
|
});
|
|
@@ -1160,23 +1292,34 @@ Examples:
|
|
|
1160
1292
|
});
|
|
1161
1293
|
cmd
|
|
1162
1294
|
.command('import [bundle]')
|
|
1163
|
-
.description('Import keys from a .env file
|
|
1164
|
-
.option('--from <
|
|
1165
|
-
.
|
|
1166
|
-
.
|
|
1295
|
+
.description('Import keys into a bundle from a .env file, a 1Password vault, or legacy iCloud Keychain bundles. The bundle is created if it does not exist. Values are stored in the bundle\'s backend (keychain by default).')
|
|
1296
|
+
.option('--from <source>', "Source: a .env path (- reads stdin), '1password:<vault>', or 'icloud' (legacy iCloud Keychain bundles)")
|
|
1297
|
+
.addOption(new Option('--from-1password', 'deprecated alias for --from 1password:<vault>').hideHelp())
|
|
1298
|
+
.addOption(new Option('--vault <name>', 'deprecated: name the vault in --from 1password:<vault>').hideHelp())
|
|
1167
1299
|
.option('--all-plaintext', 'Store every imported value as a literal in the bundle metadata (skip keychain item creation)')
|
|
1168
1300
|
.option('--backend <backend>', 'When creating the bundle: keychain (default) or file (passphrase-encrypted, headless-readable)', 'keychain')
|
|
1169
1301
|
.option('--force', 'Overwrite an existing key in the bundle')
|
|
1302
|
+
.option('--purge', 'With --from icloud: delete the iCloud copies after a successful import (iCloud propagates the deletion to your other devices)')
|
|
1170
1303
|
.action(async (bundleName, opts) => {
|
|
1171
1304
|
try {
|
|
1172
|
-
|
|
1173
|
-
|
|
1305
|
+
const source = parseImportSource(opts);
|
|
1306
|
+
if (opts.purge && source.kind !== 'icloud') {
|
|
1307
|
+
throw new Error('--purge only applies to --from icloud.');
|
|
1174
1308
|
}
|
|
1175
|
-
if (opts.
|
|
1176
|
-
|
|
1309
|
+
if (opts.from1password) {
|
|
1310
|
+
console.log(chalk.yellow('--from-1password is deprecated; use --from 1password:<vault>.'));
|
|
1177
1311
|
}
|
|
1178
|
-
const resolvedBundleName = bundleName ?? (await pickBundleName('import into'));
|
|
1179
1312
|
const requestedBackend = parseBackendOpt(opts.backend);
|
|
1313
|
+
if (source.kind === 'icloud') {
|
|
1314
|
+
await importFromICloud(bundleName, {
|
|
1315
|
+
force: opts.force,
|
|
1316
|
+
allPlaintext: opts.allPlaintext,
|
|
1317
|
+
backend: requestedBackend === 'file' ? 'file' : undefined,
|
|
1318
|
+
purge: opts.purge,
|
|
1319
|
+
});
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
const resolvedBundleName = bundleName ?? (await pickBundleName('import into'));
|
|
1180
1323
|
// Read the bundle if it exists (inheriting its backend); otherwise
|
|
1181
1324
|
// create it with the requested backend so a single `import --backend
|
|
1182
1325
|
// file` works (this is what `export --host ... --remote-backend file`
|
|
@@ -1199,9 +1342,9 @@ Examples:
|
|
|
1199
1342
|
const store = bundleItemStore(bundle.backend, { noAcl: bundlePolicy(bundle) === 'never' });
|
|
1200
1343
|
let added = 0;
|
|
1201
1344
|
let skipped = 0;
|
|
1202
|
-
if (
|
|
1345
|
+
if (source.kind === '1password') {
|
|
1203
1346
|
assertOpAvailable();
|
|
1204
|
-
const vault = await resolveVault(
|
|
1347
|
+
const vault = await resolveVault(source.vault);
|
|
1205
1348
|
const items = listItems(vault);
|
|
1206
1349
|
const { secrets, skipped: opSkipped } = extractSecrets(items, vault);
|
|
1207
1350
|
for (const { envKey, value } of secrets) {
|
|
@@ -1226,7 +1369,7 @@ Examples:
|
|
|
1226
1369
|
console.log(chalk.green(`Imported ${added} key(s) from 1Password vault '${vault}'${skipped ? `, skipped ${skipped} (already set, pass --force)` : ''}.`));
|
|
1227
1370
|
}
|
|
1228
1371
|
else {
|
|
1229
|
-
const raw = readImportDotenv(
|
|
1372
|
+
const raw = readImportDotenv(source.path);
|
|
1230
1373
|
const pairs = parseDotenv(raw);
|
|
1231
1374
|
for (const [key, value] of Object.entries(pairs)) {
|
|
1232
1375
|
if (!opts.force && key in bundle.vars) {
|
|
@@ -1720,20 +1863,11 @@ Examples:
|
|
|
1720
1863
|
console.error(chalk.yellow('Aborted.'));
|
|
1721
1864
|
return;
|
|
1722
1865
|
}
|
|
1723
|
-
const wasDaily = bundlePolicy(bundle) === 'daily';
|
|
1724
1866
|
bundle.policy = next;
|
|
1867
|
+
// writeBundle evicts any broker-held copy, so tightening daily ->
|
|
1868
|
+
// always/never takes effect NOW: the next read re-prompts (`always`)
|
|
1869
|
+
// or reads its no-ACL item directly (`never`).
|
|
1725
1870
|
writeBundle(bundle);
|
|
1726
|
-
// Tightening daily -> always/never must take effect NOW, not up to the
|
|
1727
|
-
// ~7d hold later. If the broker is already serving this bundle silently
|
|
1728
|
-
// (auto-cached under the old `daily` policy), evict it so the next read
|
|
1729
|
-
// re-prompts (`always`) or reads its no-ACL item directly (`never`).
|
|
1730
|
-
// macOS-only + best-effort; agentLock no-ops off darwin / with no broker.
|
|
1731
|
-
if (wasDaily && next !== 'daily') {
|
|
1732
|
-
try {
|
|
1733
|
-
await agentLock(bundle.name);
|
|
1734
|
-
}
|
|
1735
|
-
catch { /* broker down — nothing held */ }
|
|
1736
|
-
}
|
|
1737
1871
|
console.log(chalk.green(`${bundle.name} policy set to ${next}.`));
|
|
1738
1872
|
if (next === 'daily') {
|
|
1739
1873
|
console.log(chalk.gray('Held by the secrets-agent for ~7 days after one unlock (auto-cache is on by default; disable with `secrets.agent.auto: false` in agents.yaml).'));
|
|
@@ -25,7 +25,7 @@ import { machineId, normalizeHost } from '../lib/session/sync/config.js';
|
|
|
25
25
|
import { gatherRemoteActive, NO_FANOUT_ENV } from '../lib/session/remote-active.js';
|
|
26
26
|
import { gatherRemoteList, runOnPeer } from '../lib/session/remote-list.js';
|
|
27
27
|
import { stringWidth, truncateToWidth, padToWidth, terminalWidth } from '../lib/session/width.js';
|
|
28
|
-
import { discoverSessions, countSessionsInScope, resolveSessionById, searchContentIndex } from '../lib/session/discover.js';
|
|
28
|
+
import { discoverSessions, countSessionsInScope, resolveSessionById, searchContentIndex, getSessionRoots } from '../lib/session/discover.js';
|
|
29
29
|
import { filterTeamSessions } from '../lib/session/team-filter.js';
|
|
30
30
|
import { parseSession } from '../lib/session/parse.js';
|
|
31
31
|
import { runRemoteSessions, buildForwardedArgs } from '../lib/session/remote.js';
|
|
@@ -796,6 +796,14 @@ async function sessionsAction(query, options) {
|
|
|
796
796
|
if (options.device && options.device.length > 0) {
|
|
797
797
|
options.host = [...(options.host ?? []), ...options.device];
|
|
798
798
|
}
|
|
799
|
+
// --roots: emit the local session-scan directories, per agent, as JSON. A pure
|
|
800
|
+
// machine-readable query (no listing/render) — external watchers (the Factory
|
|
801
|
+
// extension's fs.watch) read it to track the same dirs the CLI scans, instead
|
|
802
|
+
// of hardcoding `~/.claude|.codex|.gemini`. Always local; ignores other flags.
|
|
803
|
+
if (options.roots) {
|
|
804
|
+
process.stdout.write(JSON.stringify(getSessionRoots(), null, 2) + '\n');
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
799
807
|
// --host WITHOUT --active. `--json` fans the recent listing out and emits ONE
|
|
800
808
|
// clean merged SessionMeta[] array (same shape as the local --json path), for
|
|
801
809
|
// scripts/extensions that JSON.parse a remote's history. Without --json it
|
|
@@ -2047,6 +2055,7 @@ export function registerSessionsCommands(program) {
|
|
|
2047
2055
|
.option('--artifacts', 'List all files written or edited during a session')
|
|
2048
2056
|
.option('--artifact <name>', 'Read a specific artifact by filename or path (outputs to stdout)')
|
|
2049
2057
|
.option('--active', 'Show only sessions running right now across terminals, teams, cloud, and headless agents')
|
|
2058
|
+
.option('--roots', 'With --json: emit the on-disk directories scanned for session transcripts, per agent (for external watchers)')
|
|
2050
2059
|
.option('--local', 'Only this machine — skip the cross-machine SSH fan-out (default listing and --active)')
|
|
2051
2060
|
.option('--waiting', 'With --active: show only sessions waiting on your input (exits non-zero if any)')
|
|
2052
2061
|
.option('--tree', 'Group the listing by directory; drops the id/version columns for readability')
|
package/dist/commands/teams.js
CHANGED
|
@@ -5,6 +5,7 @@ import { addHostOption } from '../lib/hosts/option.js';
|
|
|
5
5
|
import * as path from 'path';
|
|
6
6
|
import { AgentManager, checkCliSignedIn, collectTeamsDoctorData, getAgentsDir, VALID_TASK_TYPES, } from '../lib/teams/agents.js';
|
|
7
7
|
import { resolveProvider } from '../lib/cloud/registry.js';
|
|
8
|
+
import { emit } from '../lib/events.js';
|
|
8
9
|
import { runSupervisor } from '../lib/teams/supervisor.js';
|
|
9
10
|
import { debug } from '../lib/teams/debug.js';
|
|
10
11
|
import { runPrWatch, DEFAULT_MAX_WAVES, } from '../lib/teams/pr-watch.js';
|
|
@@ -1317,6 +1318,7 @@ export function registerTeamsCommands(program) {
|
|
|
1317
1318
|
}
|
|
1318
1319
|
try {
|
|
1319
1320
|
const result = await handleSpawn(mgr, team, agent, effectiveTask, cwd, opts.mode, opts.effort, null, cwd, version, opts.name ?? null, after, opts.model ?? null, envOverrides ?? null, taskType, cloudProviderId, cloudSessionId, opts.repo ?? null, opts.branch ?? null, worktreeName, worktreePath, profileName, hostName, hostTarget, hostRepoPath);
|
|
1321
|
+
emit('teams.add', { module: 'teams', team, agent, name: result.name, agent_id: result.agent_id, status: result.status });
|
|
1320
1322
|
if (isJsonMode(opts)) {
|
|
1321
1323
|
console.log(JSON.stringify(result, null, 2));
|
|
1322
1324
|
return;
|
|
@@ -1487,6 +1489,7 @@ export function registerTeamsCommands(program) {
|
|
|
1487
1489
|
await warnUnsignedTeammates(mgr, team);
|
|
1488
1490
|
await warnThrottledTeammates(mgr, team);
|
|
1489
1491
|
}
|
|
1492
|
+
emit('teams.start', { module: 'teams', team, watch: Boolean(opts.watch) });
|
|
1490
1493
|
if (!opts.watch) {
|
|
1491
1494
|
await runOneWave(mgr, team, Boolean(opts.json));
|
|
1492
1495
|
return;
|
|
@@ -1526,6 +1529,7 @@ export function registerTeamsCommands(program) {
|
|
|
1526
1529
|
},
|
|
1527
1530
|
});
|
|
1528
1531
|
const elapsed = Math.floor(result.elapsed_ms / 1000);
|
|
1532
|
+
emit('teams.complete', { module: 'teams', team, stoppedBy: result.stoppedBy, waves: result.waves, durationMs: result.elapsed_ms });
|
|
1529
1533
|
if (result.stoppedBy === 'drained') {
|
|
1530
1534
|
console.log(chalk.green(`Factory drained in ${elapsed}s (${result.waves} waves).`));
|
|
1531
1535
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { die } from '../lib/format.js';
|
|
3
|
+
import { getGitRoot } from '../lib/git.js';
|
|
3
4
|
import { execFile } from 'child_process';
|
|
4
5
|
import { promisify } from 'util';
|
|
5
6
|
import * as fs from 'fs/promises';
|
|
@@ -14,10 +15,11 @@ function isValidTerminalId(id) {
|
|
|
14
15
|
// hostile or buggy caller can't inject path traversal or shell metachars.
|
|
15
16
|
return /^[A-Za-z0-9._-]+$/.test(id) && id.length > 0 && id.length <= 128;
|
|
16
17
|
}
|
|
18
|
+
// Thin command-layer wrapper over the shared lib/git.ts `getGitRoot`: turns its
|
|
19
|
+
// throw into a clean `die()` message. The git plumbing lives in one place now.
|
|
17
20
|
async function gitRoot(cwd) {
|
|
18
21
|
try {
|
|
19
|
-
|
|
20
|
-
return stdout.trim();
|
|
22
|
+
return await getGitRoot(cwd);
|
|
21
23
|
}
|
|
22
24
|
catch {
|
|
23
25
|
die(`Not inside a git repo: ${cwd}`);
|
package/dist/index.js
CHANGED
|
@@ -50,7 +50,7 @@ if (IS_DEV_BUILD) {
|
|
|
50
50
|
// module on each invocation (which loaded the whole ~50-module tree before the
|
|
51
51
|
// first byte of output), the registry maps a command name to a thunk that
|
|
52
52
|
// imports only what that command needs. See src/lib/startup/command-registry.ts.
|
|
53
|
-
import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadPackages, loadDaemon, loadRoutines, loadRun, loadDefaults, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadCheck, loadStatus, loadProfiles, loadSecrets, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadLock, loadRefreshRules, loadDrive, loadFactory, loadUsage, loadCost, loadBudget, loadAlias, loadPty, loadTmux, loadWatchdog, loadBrowser, loadComputer, loadHosts, loadLogs, loadEvents, loadAudit, loadSsh, loadPull, loadPush, loadRepo, loadSetup, } from './lib/startup/command-registry.js';
|
|
53
|
+
import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadMemory, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadPackages, loadDaemon, loadRoutines, loadRun, loadDefaults, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadCheck, loadStatus, loadProfiles, loadSecrets, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadLock, loadRefreshRules, loadDrive, loadFactory, loadUsage, loadCost, loadBudget, loadAlias, loadPty, loadTmux, loadWatchdog, loadBrowser, loadComputer, loadHosts, loadLogs, loadEvents, loadAudit, loadSsh, loadPull, loadPush, loadRepo, loadSetup, loadFeed, } from './lib/startup/command-registry.js';
|
|
54
54
|
import { applyGlobalHelpConventions } from './lib/help.js';
|
|
55
55
|
import { renderWhatsNew } from './lib/whats-new.js';
|
|
56
56
|
import { emit, redactArgs } from './lib/events.js';
|
|
@@ -73,6 +73,7 @@ program
|
|
|
73
73
|
.name('agents')
|
|
74
74
|
.description('Environment manager for AI agents')
|
|
75
75
|
.version(VERSION)
|
|
76
|
+
.option('--verbose', 'Show startup self-heal details on stderr')
|
|
76
77
|
.helpOption('-h, --help', 'Show help')
|
|
77
78
|
.addHelpCommand(false);
|
|
78
79
|
// ─── Audit backbone ────────────────────────────────────────────────────────────
|
|
@@ -101,7 +102,10 @@ program.hook('preAction', (_thisCommand, actionCommand) => {
|
|
|
101
102
|
emit('command.start', {
|
|
102
103
|
module: parts[0],
|
|
103
104
|
command: parts.join(' '),
|
|
104
|
-
|
|
105
|
+
// Commander exposes positional operands in actionCommand.args but omits
|
|
106
|
+
// parsed option values. Audit the real argv so sensitive flags are seen
|
|
107
|
+
// and redacted instead of silently bypassing the policy.
|
|
108
|
+
args: redactArgs(process.argv.slice(2, 22)),
|
|
105
109
|
cwd: process.cwd(),
|
|
106
110
|
});
|
|
107
111
|
}
|
|
@@ -204,6 +208,7 @@ Automation tips:
|
|
|
204
208
|
Options:
|
|
205
209
|
-V, --version Show version number
|
|
206
210
|
-h, --help Show help
|
|
211
|
+
--verbose Show startup self-heal details on stderr
|
|
207
212
|
|
|
208
213
|
System config lives in ~/.agents/.system/. Run 'agents <command> --help' for details.
|
|
209
214
|
`;
|
|
@@ -461,8 +466,8 @@ async function checkForUpdates() {
|
|
|
461
466
|
}
|
|
462
467
|
}
|
|
463
468
|
}
|
|
464
|
-
async function maybeBootstrapShimIntegration(requestedCommand, helpOrVersionRequested) {
|
|
465
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
469
|
+
async function maybeBootstrapShimIntegration(requestedCommand, helpOrVersionRequested, verboseStartup) {
|
|
470
|
+
if (!verboseStartup && (!process.stdin.isTTY || !process.stdout.isTTY)) {
|
|
466
471
|
return;
|
|
467
472
|
}
|
|
468
473
|
// Pure documentation paths must never trigger interactive repair — mirrors
|
|
@@ -484,8 +489,12 @@ async function maybeBootstrapShimIntegration(requestedCommand, helpOrVersionRequ
|
|
|
484
489
|
// (a real native binary shadowing the shim) or is worth saying once (a PATH entry
|
|
485
490
|
// just added). Suppression is persistent and keyed to the condition — a new
|
|
486
491
|
// terminal no longer re-nags (the old per-PPID sentinel did, every shell).
|
|
487
|
-
const {
|
|
488
|
-
const
|
|
492
|
+
const { runInteractiveShimHeal } = await import('./lib/shim-heal.js');
|
|
493
|
+
const { summarizeSelfHeal } = await import('./lib/self-heal/registry.js');
|
|
494
|
+
const { noticeLines, report } = await runInteractiveShimHeal();
|
|
495
|
+
if (verboseStartup) {
|
|
496
|
+
process.stderr.write(`[agents] startup self-heal: ${summarizeSelfHeal(report)}\n`);
|
|
497
|
+
}
|
|
489
498
|
if (noticeLines) {
|
|
490
499
|
for (const line of noticeLines)
|
|
491
500
|
console.log(chalk.gray(line));
|
|
@@ -496,17 +505,8 @@ async function maybeBootstrapShimIntegration(requestedCommand, helpOrVersionRequ
|
|
|
496
505
|
// close over entry-point-local state (program re-parsing, VERSION, the npm
|
|
497
506
|
// upgrade helpers). The lazy registrar and the all-commands fallback below both
|
|
498
507
|
// call them, so the behavior is identical to the old eager registration.
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
p.command('memory', { hidden: true })
|
|
502
|
-
.allowUnknownOption()
|
|
503
|
-
.allowExcessArguments()
|
|
504
|
-
.action(() => {
|
|
505
|
-
console.error(chalk.red('"agents memory" has been renamed to "agents rules".'));
|
|
506
|
-
console.error(chalk.gray('Run "agents rules --help" for usage.\n'));
|
|
507
|
-
process.exit(1);
|
|
508
|
-
});
|
|
509
|
-
}
|
|
508
|
+
// memory is a first-class resource command (see commands/memory.ts via
|
|
509
|
+
// COMMAND_LOADERS). The old memory→rules tombstone was removed in RUSH-1330.
|
|
510
510
|
/** Deprecated `perms` alias — re-parses as `permissions`. */
|
|
511
511
|
function registerPermsAliasCommand(p) {
|
|
512
512
|
p.command('perms', { hidden: true })
|
|
@@ -613,9 +613,6 @@ async function reg(loader) {
|
|
|
613
613
|
*/
|
|
614
614
|
async function registerEagerForRequest(name) {
|
|
615
615
|
switch (name) {
|
|
616
|
-
case 'memory':
|
|
617
|
-
registerMemoryCommand(program);
|
|
618
|
-
return true;
|
|
619
616
|
case 'perms':
|
|
620
617
|
// The action re-parses as `permissions`, so that target must exist too.
|
|
621
618
|
registerPermsAliasCommand(program);
|
|
@@ -656,7 +653,7 @@ async function registerAllEagerCommands() {
|
|
|
656
653
|
await reg(loadHooks);
|
|
657
654
|
await reg(loadSkills);
|
|
658
655
|
await reg(loadRules);
|
|
659
|
-
|
|
656
|
+
await reg(loadMemory);
|
|
660
657
|
await reg(loadPermissions);
|
|
661
658
|
registerPermsAliasCommand(program);
|
|
662
659
|
await reg(loadMcp);
|
|
@@ -704,6 +701,7 @@ async function registerAllEagerCommands() {
|
|
|
704
701
|
await reg(loadLogs);
|
|
705
702
|
await reg(loadEvents);
|
|
706
703
|
await reg(loadAudit);
|
|
704
|
+
await reg(loadFeed);
|
|
707
705
|
await reg(loadSsh);
|
|
708
706
|
registerJobsCronAliasCommand(program, 'jobs');
|
|
709
707
|
registerJobsCronAliasCommand(program, 'cron');
|
|
@@ -764,6 +762,7 @@ program.on('command:*', (operands) => {
|
|
|
764
762
|
// and whether the update check + background sync run at all.
|
|
765
763
|
const passedArgs = process.argv.slice(2);
|
|
766
764
|
const requestedCommand = passedArgs.find((arg) => !arg.startsWith('-'));
|
|
765
|
+
const verboseStartup = passedArgs.includes('--verbose');
|
|
767
766
|
// Help and version output are pure documentation — they must never gate on
|
|
768
767
|
// setup, otherwise `agents <cmd> --help` becomes useless on a fresh box.
|
|
769
768
|
const helpOrVersionRequested = passedArgs.some((arg) => arg === '--help' || arg === '-h' || arg === '--version' || arg === '-V');
|
|
@@ -918,7 +917,7 @@ if (passedArgs.length === 0) {
|
|
|
918
917
|
process.exit(0);
|
|
919
918
|
}
|
|
920
919
|
try {
|
|
921
|
-
await maybeBootstrapShimIntegration(requestedCommand, helpOrVersionRequested);
|
|
920
|
+
await maybeBootstrapShimIntegration(requestedCommand, helpOrVersionRequested, verboseStartup);
|
|
922
921
|
await program.parseAsync();
|
|
923
922
|
}
|
|
924
923
|
catch (err) {
|