@phnx-labs/agents-cli 1.20.50 → 1.20.52
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 +40 -0
- package/dist/commands/browser-picker.js +1 -18
- package/dist/commands/browser.js +215 -7
- package/dist/commands/cloud.js +7 -25
- package/dist/commands/computer.d.ts +1 -0
- package/dist/commands/computer.js +129 -8
- package/dist/commands/events.d.ts +1 -1
- package/dist/commands/events.js +2 -3
- package/dist/commands/exec.js +66 -8
- package/dist/commands/factory.js +9 -4
- package/dist/commands/feed.d.ts +9 -0
- package/dist/commands/feed.js +69 -0
- package/dist/commands/inspect.js +1 -11
- package/dist/commands/logs.d.ts +5 -1
- package/dist/commands/logs.js +248 -3
- package/dist/commands/mcp.js +9 -6
- package/dist/commands/message.js +1 -4
- package/dist/commands/profiles.js +1 -18
- package/dist/commands/repo.js +33 -14
- package/dist/commands/resource-view.d.ts +1 -0
- package/dist/commands/resource-view.js +5 -17
- package/dist/commands/secrets.d.ts +23 -0
- package/dist/commands/secrets.js +174 -70
- package/dist/commands/sessions-picker.js +1 -18
- package/dist/commands/sessions.js +6 -8
- package/dist/commands/teams-picker.js +1 -32
- package/dist/commands/teams.js +5 -27
- package/dist/commands/tmux.js +1 -3
- package/dist/commands/view.js +1 -9
- package/dist/commands/worktree.js +1 -4
- package/dist/index.js +6 -2
- package/dist/lib/agents.d.ts +0 -4
- package/dist/lib/agents.js +20 -33
- package/dist/lib/auto-dispatch-linear.d.ts +18 -0
- package/dist/lib/auto-dispatch-linear.js +107 -0
- package/dist/lib/auto-dispatch-provider.d.ts +10 -0
- package/dist/lib/auto-dispatch-provider.js +25 -0
- package/dist/lib/auto-dispatch.d.ts +87 -0
- package/dist/lib/auto-dispatch.js +142 -0
- package/dist/lib/browser/cdp.js +11 -2
- package/dist/lib/browser/drivers/ssh.d.ts +28 -10
- package/dist/lib/browser/drivers/ssh.js +57 -18
- 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/refs.js +1 -5
- 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/cli-resources.d.ts +0 -2
- package/dist/lib/cli-resources.js +30 -13
- package/dist/lib/cloud/rush.d.ts +15 -24
- package/dist/lib/cloud/rush.js +7 -32
- package/dist/lib/crabbox/cli.js +4 -1
- package/dist/lib/crabbox/lease.d.ts +6 -0
- package/dist/lib/crabbox/lease.js +40 -10
- 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 +73 -17
- package/dist/lib/events.d.ts +31 -5
- package/dist/lib/events.js +288 -101
- package/dist/lib/exec.js +32 -14
- package/dist/lib/feed.d.ts +56 -0
- package/dist/lib/feed.js +251 -0
- package/dist/lib/format.d.ts +38 -0
- package/dist/lib/format.js +108 -0
- package/dist/lib/git.d.ts +21 -0
- package/dist/lib/git.js +92 -0
- package/dist/lib/hooks/cache.d.ts +9 -2
- package/dist/lib/hooks/cache.js +220 -8
- package/dist/lib/hooks.js +24 -10
- package/dist/lib/hosts/passthrough.js +1 -0
- package/dist/lib/platform/exec.d.ts +4 -1
- package/dist/lib/platform/exec.js +8 -2
- package/dist/lib/resources.d.ts +0 -8
- package/dist/lib/resources.js +0 -10
- package/dist/lib/rotate.js +2 -0
- package/dist/lib/runner.js +10 -2
- 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 +38 -8
- package/dist/lib/secrets/icloud-import.d.ts +70 -0
- package/dist/lib/secrets/icloud-import.js +173 -0
- package/dist/lib/secrets/index.d.ts +36 -0
- package/dist/lib/secrets/index.js +99 -9
- package/dist/lib/secrets/remote.js +1 -1
- package/dist/lib/secrets/sync.js +1 -1
- package/dist/lib/session/active.d.ts +11 -1
- package/dist/lib/session/active.js +3 -0
- package/dist/lib/session/db.d.ts +1 -4
- package/dist/lib/session/db.js +20 -25
- package/dist/lib/session/discover.d.ts +2 -2
- package/dist/lib/session/discover.js +60 -48
- package/dist/lib/session/parse.js +35 -34
- package/dist/lib/session/render.d.ts +7 -3
- package/dist/lib/session/render.js +15 -9
- package/dist/lib/session/state.d.ts +55 -0
- package/dist/lib/session/state.js +100 -11
- package/dist/lib/session/types.d.ts +9 -0
- package/dist/lib/shims.d.ts +9 -3
- package/dist/lib/shims.js +44 -8
- package/dist/lib/ssh-tunnel.d.ts +33 -2
- package/dist/lib/ssh-tunnel.js +94 -7
- package/dist/lib/staleness/types.d.ts +0 -1
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +2 -0
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.js +25 -8
- package/dist/lib/teams/agents.js +6 -3
- package/dist/lib/types.d.ts +24 -1
- package/dist/lib/versions.d.ts +0 -26
- package/dist/lib/versions.js +2 -145
- package/dist/lib/warn-unpushed.d.ts +40 -0
- package/dist/lib/warn-unpushed.js +128 -0
- package/dist/lib/whats-new.d.ts +5 -3
- package/dist/lib/whats-new.js +25 -5
- package/package.json +3 -1
- package/dist/lib/resources/index.d.ts +0 -53
- package/dist/lib/resources/index.js +0 -76
package/dist/commands/secrets.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { Option } from 'commander';
|
|
9
9
|
import chalk from 'chalk';
|
|
10
|
+
import { padVisible, readStdinSync } from '../lib/format.js';
|
|
10
11
|
import { terminalWidth, truncateToWidth, stringWidth } from '../lib/session/width.js';
|
|
11
12
|
import * as fs from 'fs';
|
|
12
13
|
import { SSH_TARGET_RE, assertValidSshTarget, sshExec } from '../lib/ssh-exec.js';
|
|
@@ -23,6 +24,7 @@ import { parseDuration } from '../lib/hooks/cache.js';
|
|
|
23
24
|
import { emit } from '../lib/events.js';
|
|
24
25
|
import { registerCommandGroups, setHelpSections } from '../lib/help.js';
|
|
25
26
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
27
|
+
import { discoverSyncedBundles, importSyncedBundle, } from '../lib/secrets/icloud-import.js';
|
|
26
28
|
import { registerSecretsSyncCommands } from './secrets-sync.js';
|
|
27
29
|
import { registerSecretsMigrateAclCommand } from './secrets-migrate.js';
|
|
28
30
|
import { registerSecretsImportKeyringCommand } from './secrets-import.js';
|
|
@@ -125,23 +127,6 @@ async function resolveVault(vaultOpt) {
|
|
|
125
127
|
});
|
|
126
128
|
}
|
|
127
129
|
/** Read all available data from stdin synchronously, trimmed. */
|
|
128
|
-
function readStdinSync() {
|
|
129
|
-
const chunks = [];
|
|
130
|
-
const buf = Buffer.alloc(65536);
|
|
131
|
-
while (true) {
|
|
132
|
-
let bytesRead;
|
|
133
|
-
try {
|
|
134
|
-
bytesRead = fs.readSync(0, buf, 0, buf.length, null);
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
if (bytesRead === 0)
|
|
140
|
-
break;
|
|
141
|
-
chunks.push(Buffer.from(buf.subarray(0, bytesRead)));
|
|
142
|
-
}
|
|
143
|
-
return Buffer.concat(chunks).toString('utf-8').trim();
|
|
144
|
-
}
|
|
145
130
|
/**
|
|
146
131
|
* Read the raw `.env` text for `import --from <path|->`. A `-` reads the .env
|
|
147
132
|
* from stdin (the SSH push path: `export --host` pipes the resolved dotenv over
|
|
@@ -151,6 +136,101 @@ function readStdinSync() {
|
|
|
151
136
|
export function readImportDotenv(from) {
|
|
152
137
|
return from === '-' ? readStdinSync() : fs.readFileSync(from, 'utf-8');
|
|
153
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 (opts.purge)
|
|
212
|
+
parts.push(`purged ${result.purged} iCloud item(s)`);
|
|
213
|
+
const line = `${candidate.name}: ${parts.join(', ')}`;
|
|
214
|
+
console.log(result.missing.length ? chalk.yellow(line) : chalk.green(line));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Printed under a "bundle not found" failure: if the name matches a bundle
|
|
219
|
+
* stranded in the iCloud Keychain (pre-device-local-cutover era), point at the
|
|
220
|
+
* recovery command instead of leaving a dead end.
|
|
221
|
+
*/
|
|
222
|
+
function maybePrintSyncedHint(name) {
|
|
223
|
+
if (process.platform !== 'darwin')
|
|
224
|
+
return;
|
|
225
|
+
try {
|
|
226
|
+
if (discoverSyncedBundles().some((c) => c.name === name)) {
|
|
227
|
+
console.error(chalk.yellow(`A legacy iCloud Keychain copy of '${name}' exists. Recover it with: agents secrets import ${name} --from icloud`));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// Hint only — never mask the original error.
|
|
232
|
+
}
|
|
233
|
+
}
|
|
154
234
|
/**
|
|
155
235
|
* Build the remote `agents secrets unlock` argv for `unlock --host`. `--all`
|
|
156
236
|
* forwards verbatim; otherwise the explicit bundle names. A `--ttl` is passed
|
|
@@ -257,17 +337,6 @@ async function browseRemote(targets, args, tty) {
|
|
|
257
337
|
process.exit(1);
|
|
258
338
|
}
|
|
259
339
|
/** Strip ANSI escape sequences so padding can be computed on visible width. */
|
|
260
|
-
function visibleWidth(s) {
|
|
261
|
-
// eslint-disable-next-line no-control-regex
|
|
262
|
-
return s.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
263
|
-
}
|
|
264
|
-
/** padEnd that respects ANSI color codes (chalk-wrapped strings have invisible bytes). */
|
|
265
|
-
function padVisible(s, n) {
|
|
266
|
-
const w = visibleWidth(s);
|
|
267
|
-
if (w >= n)
|
|
268
|
-
return s;
|
|
269
|
-
return s + ' '.repeat(n - w);
|
|
270
|
-
}
|
|
271
340
|
/** Render an ISO-8601 timestamp as a compact relative age: "now", "5m", "1h", "3d", "2w", "4mo", "1y". */
|
|
272
341
|
function relativeAge(iso) {
|
|
273
342
|
const t = Date.parse(iso);
|
|
@@ -552,7 +621,8 @@ export function registerSecretsCommands(program) {
|
|
|
552
621
|
agents secrets status show held bundles + when they lock
|
|
553
622
|
agents secrets rotate <bundle> <key> rotate value, preserve metadata
|
|
554
623
|
agents secrets import <bundle> --from .env bulk import from .env
|
|
555
|
-
agents secrets import <bundle> --from
|
|
624
|
+
agents secrets import <bundle> --from 1password:<vault>
|
|
625
|
+
agents secrets import --from icloud recover legacy iCloud Keychain bundles
|
|
556
626
|
agents secrets generate [length] generate a random password / PIN / hex
|
|
557
627
|
agents secrets migrate-acl upgrade legacy items to the biometry ACL
|
|
558
628
|
`,
|
|
@@ -637,7 +707,15 @@ export function registerSecretsCommands(program) {
|
|
|
637
707
|
return;
|
|
638
708
|
}
|
|
639
709
|
const resolvedName = name ?? (await pickBundleName('view'));
|
|
640
|
-
|
|
710
|
+
let bundle;
|
|
711
|
+
try {
|
|
712
|
+
bundle = readBundle(resolvedName);
|
|
713
|
+
}
|
|
714
|
+
catch (err) {
|
|
715
|
+
console.error(chalk.red(err.message));
|
|
716
|
+
maybePrintSyncedHint(resolvedName);
|
|
717
|
+
process.exit(1);
|
|
718
|
+
}
|
|
641
719
|
const entries = describeBundle(bundle);
|
|
642
720
|
console.log(chalk.bold(bundle.name));
|
|
643
721
|
if (bundle.description)
|
|
@@ -699,7 +777,7 @@ export function registerSecretsCommands(program) {
|
|
|
699
777
|
emit('secrets.get', {
|
|
700
778
|
module: 'secrets',
|
|
701
779
|
bundle: bundle.name,
|
|
702
|
-
|
|
780
|
+
operation: 'view --reveal',
|
|
703
781
|
source: 'reveal',
|
|
704
782
|
status: 'success',
|
|
705
783
|
keyCount: exposedCount,
|
|
@@ -740,24 +818,48 @@ export function registerSecretsCommands(program) {
|
|
|
740
818
|
}
|
|
741
819
|
});
|
|
742
820
|
cmd
|
|
743
|
-
.command('get <item>')
|
|
744
|
-
.description('Print a raw keychain item by name (
|
|
745
|
-
.action((item) => {
|
|
821
|
+
.command('get <item> [key]')
|
|
822
|
+
.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.')
|
|
823
|
+
.action((item, key) => {
|
|
824
|
+
if (key === undefined) {
|
|
825
|
+
// Raw keychain item path — unchanged.
|
|
826
|
+
try {
|
|
827
|
+
// Routes through the platform keychain layer: macOS reads bare items
|
|
828
|
+
// via /usr/bin/security (no Touch ID), Linux via secret-tool with the
|
|
829
|
+
// encrypted-file fallback. The value goes to stdout (newline-terminated
|
|
830
|
+
// so `$(agents secrets get NAME)` captures it cleanly); diagnostics go
|
|
831
|
+
// to stderr so they never pollute the captured value.
|
|
832
|
+
const value = getKeychainToken(item);
|
|
833
|
+
// Raw item reads bypass readAndResolveBundleEnv, so audit here too.
|
|
834
|
+
// `item` is the keychain service name, never the value.
|
|
835
|
+
emit('secrets.get', { module: 'secrets', item, source: 'raw-item', status: 'success' });
|
|
836
|
+
process.stdout.write(value.endsWith('\n') ? value : `${value}\n`);
|
|
837
|
+
}
|
|
838
|
+
catch {
|
|
839
|
+
// Missing item is a normal, quiet outcome for a hook probe: exit 1,
|
|
840
|
+
// print nothing to stdout. Callers test the exit code / empty capture.
|
|
841
|
+
process.exit(1);
|
|
842
|
+
}
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
// Bundle-key path: `get <bundle> <KEY>` prints exactly one resolved value.
|
|
846
|
+
// Ungated like the raw path (it IS the automation primitive); the
|
|
847
|
+
// `secrets.get` audit event is emitted inside readAndResolveBundleEnv.
|
|
746
848
|
try {
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
849
|
+
if (!bundleExists(item)) {
|
|
850
|
+
console.error(chalk.red(`Secrets bundle '${item}' not found.`));
|
|
851
|
+
process.exit(1);
|
|
852
|
+
}
|
|
853
|
+
const { env } = readAndResolveBundleEnv(item, { caller: 'secrets get', keys: [key] });
|
|
854
|
+
if (!(key in env)) {
|
|
855
|
+
console.error(chalk.red(`Key '${key}' not in bundle '${item}'.`));
|
|
856
|
+
process.exit(1);
|
|
857
|
+
}
|
|
858
|
+
const value = env[key];
|
|
756
859
|
process.stdout.write(value.endsWith('\n') ? value : `${value}\n`);
|
|
757
860
|
}
|
|
758
|
-
catch {
|
|
759
|
-
|
|
760
|
-
// print nothing to stdout. Callers test the exit code / empty capture.
|
|
861
|
+
catch (err) {
|
|
862
|
+
console.error(chalk.red(err.message));
|
|
761
863
|
process.exit(1);
|
|
762
864
|
}
|
|
763
865
|
});
|
|
@@ -1187,23 +1289,34 @@ Examples:
|
|
|
1187
1289
|
});
|
|
1188
1290
|
cmd
|
|
1189
1291
|
.command('import [bundle]')
|
|
1190
|
-
.description('Import keys from a .env file
|
|
1191
|
-
.option('--from <
|
|
1192
|
-
.
|
|
1193
|
-
.
|
|
1292
|
+
.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).')
|
|
1293
|
+
.option('--from <source>', "Source: a .env path (- reads stdin), '1password:<vault>', or 'icloud' (legacy iCloud Keychain bundles)")
|
|
1294
|
+
.addOption(new Option('--from-1password', 'deprecated alias for --from 1password:<vault>').hideHelp())
|
|
1295
|
+
.addOption(new Option('--vault <name>', 'deprecated: name the vault in --from 1password:<vault>').hideHelp())
|
|
1194
1296
|
.option('--all-plaintext', 'Store every imported value as a literal in the bundle metadata (skip keychain item creation)')
|
|
1195
1297
|
.option('--backend <backend>', 'When creating the bundle: keychain (default) or file (passphrase-encrypted, headless-readable)', 'keychain')
|
|
1196
1298
|
.option('--force', 'Overwrite an existing key in the bundle')
|
|
1299
|
+
.option('--purge', 'With --from icloud: delete the iCloud copies after a successful import (iCloud propagates the deletion to your other devices)')
|
|
1197
1300
|
.action(async (bundleName, opts) => {
|
|
1198
1301
|
try {
|
|
1199
|
-
|
|
1200
|
-
|
|
1302
|
+
const source = parseImportSource(opts);
|
|
1303
|
+
if (opts.purge && source.kind !== 'icloud') {
|
|
1304
|
+
throw new Error('--purge only applies to --from icloud.');
|
|
1201
1305
|
}
|
|
1202
|
-
if (opts.
|
|
1203
|
-
|
|
1306
|
+
if (opts.from1password) {
|
|
1307
|
+
console.log(chalk.yellow('--from-1password is deprecated; use --from 1password:<vault>.'));
|
|
1204
1308
|
}
|
|
1205
|
-
const resolvedBundleName = bundleName ?? (await pickBundleName('import into'));
|
|
1206
1309
|
const requestedBackend = parseBackendOpt(opts.backend);
|
|
1310
|
+
if (source.kind === 'icloud') {
|
|
1311
|
+
await importFromICloud(bundleName, {
|
|
1312
|
+
force: opts.force,
|
|
1313
|
+
allPlaintext: opts.allPlaintext,
|
|
1314
|
+
backend: requestedBackend === 'file' ? 'file' : undefined,
|
|
1315
|
+
purge: opts.purge,
|
|
1316
|
+
});
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
const resolvedBundleName = bundleName ?? (await pickBundleName('import into'));
|
|
1207
1320
|
// Read the bundle if it exists (inheriting its backend); otherwise
|
|
1208
1321
|
// create it with the requested backend so a single `import --backend
|
|
1209
1322
|
// file` works (this is what `export --host ... --remote-backend file`
|
|
@@ -1226,9 +1339,9 @@ Examples:
|
|
|
1226
1339
|
const store = bundleItemStore(bundle.backend, { noAcl: bundlePolicy(bundle) === 'never' });
|
|
1227
1340
|
let added = 0;
|
|
1228
1341
|
let skipped = 0;
|
|
1229
|
-
if (
|
|
1342
|
+
if (source.kind === '1password') {
|
|
1230
1343
|
assertOpAvailable();
|
|
1231
|
-
const vault = await resolveVault(
|
|
1344
|
+
const vault = await resolveVault(source.vault);
|
|
1232
1345
|
const items = listItems(vault);
|
|
1233
1346
|
const { secrets, skipped: opSkipped } = extractSecrets(items, vault);
|
|
1234
1347
|
for (const { envKey, value } of secrets) {
|
|
@@ -1253,7 +1366,7 @@ Examples:
|
|
|
1253
1366
|
console.log(chalk.green(`Imported ${added} key(s) from 1Password vault '${vault}'${skipped ? `, skipped ${skipped} (already set, pass --force)` : ''}.`));
|
|
1254
1367
|
}
|
|
1255
1368
|
else {
|
|
1256
|
-
const raw = readImportDotenv(
|
|
1369
|
+
const raw = readImportDotenv(source.path);
|
|
1257
1370
|
const pairs = parseDotenv(raw);
|
|
1258
1371
|
for (const [key, value] of Object.entries(pairs)) {
|
|
1259
1372
|
if (!opts.force && key in bundle.vars) {
|
|
@@ -1747,20 +1860,11 @@ Examples:
|
|
|
1747
1860
|
console.error(chalk.yellow('Aborted.'));
|
|
1748
1861
|
return;
|
|
1749
1862
|
}
|
|
1750
|
-
const wasDaily = bundlePolicy(bundle) === 'daily';
|
|
1751
1863
|
bundle.policy = next;
|
|
1864
|
+
// writeBundle evicts any broker-held copy, so tightening daily ->
|
|
1865
|
+
// always/never takes effect NOW: the next read re-prompts (`always`)
|
|
1866
|
+
// or reads its no-ACL item directly (`never`).
|
|
1752
1867
|
writeBundle(bundle);
|
|
1753
|
-
// Tightening daily -> always/never must take effect NOW, not up to the
|
|
1754
|
-
// ~7d hold later. If the broker is already serving this bundle silently
|
|
1755
|
-
// (auto-cached under the old `daily` policy), evict it so the next read
|
|
1756
|
-
// re-prompts (`always`) or reads its no-ACL item directly (`never`).
|
|
1757
|
-
// macOS-only + best-effort; agentLock no-ops off darwin / with no broker.
|
|
1758
|
-
if (wasDaily && next !== 'daily') {
|
|
1759
|
-
try {
|
|
1760
|
-
await agentLock(bundle.name);
|
|
1761
|
-
}
|
|
1762
|
-
catch { /* broker down — nothing held */ }
|
|
1763
|
-
}
|
|
1764
1868
|
console.log(chalk.green(`${bundle.name} policy set to ${next}.`));
|
|
1765
1869
|
if (next === 'daily') {
|
|
1766
1870
|
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).'));
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import fs from 'node:fs';
|
|
9
9
|
import chalk from 'chalk';
|
|
10
|
+
import { truncate, humanDuration } from '../lib/format.js';
|
|
10
11
|
import { parseSession, sanitizeForTerminal } from '../lib/session/parse.js';
|
|
11
12
|
import { cleanSessionPrompt, extractSessionTopic } from '../lib/session/prompt.js';
|
|
12
13
|
import { linkPath, relativeToCwd } from '../lib/session/render.js';
|
|
@@ -154,21 +155,6 @@ function extractTiming(events) {
|
|
|
154
155
|
const dur = Number.isNaN(lastMs) ? undefined : humanDuration(Math.max(0, lastMs - firstMs));
|
|
155
156
|
return { startedAgo: ago, duration: dur };
|
|
156
157
|
}
|
|
157
|
-
function humanDuration(ms) {
|
|
158
|
-
const s = Math.floor(ms / 1000);
|
|
159
|
-
if (s < 60)
|
|
160
|
-
return `${s}s`;
|
|
161
|
-
const m = Math.floor(s / 60);
|
|
162
|
-
if (m < 60)
|
|
163
|
-
return `${m}m`;
|
|
164
|
-
const h = Math.floor(m / 60);
|
|
165
|
-
const mm = m % 60;
|
|
166
|
-
if (h < 24)
|
|
167
|
-
return mm ? `${h}h ${mm}m` : `${h}h`;
|
|
168
|
-
const d = Math.floor(h / 24);
|
|
169
|
-
const hh = h % 24;
|
|
170
|
-
return hh ? `${d}d ${hh}h` : `${d}d`;
|
|
171
|
-
}
|
|
172
158
|
function countMessages(events) {
|
|
173
159
|
return events.filter(e => e.type === 'message').length;
|
|
174
160
|
}
|
|
@@ -364,9 +350,6 @@ function renderTodos(todos, termWidth) {
|
|
|
364
350
|
}
|
|
365
351
|
return out;
|
|
366
352
|
}
|
|
367
|
-
function truncate(s, max) {
|
|
368
|
-
return s.length > max ? s.slice(0, max - 1) + '…' : s;
|
|
369
|
-
}
|
|
370
353
|
/** Show an interactive session picker and return the selected session with its action (resume or view). */
|
|
371
354
|
export async function sessionPicker(config) {
|
|
372
355
|
const picked = await itemPicker({
|
|
@@ -12,6 +12,7 @@ import * as os from 'os';
|
|
|
12
12
|
import * as path from 'path';
|
|
13
13
|
import { spawn } from 'child_process';
|
|
14
14
|
import chalk from 'chalk';
|
|
15
|
+
import { truncate, padRight } from '../lib/format.js';
|
|
15
16
|
import ora from 'ora';
|
|
16
17
|
import { SESSION_AGENTS } from '../lib/session/types.js';
|
|
17
18
|
import { discoverArtifacts, readArtifact, resolveArtifact } from '../lib/session/artifacts.js';
|
|
@@ -1349,8 +1350,11 @@ async function renderSession(session, mode, filters, options = {}) {
|
|
|
1349
1350
|
process.stdout.write(renderMarkdown(renderConversationMarkdown(events, { redact: options.noRedact !== true })));
|
|
1350
1351
|
return;
|
|
1351
1352
|
}
|
|
1352
|
-
// json —
|
|
1353
|
-
|
|
1353
|
+
// json — normalized events plus the durable session signals from the state
|
|
1354
|
+
// engine (plan text, PR, worktree, ticket). Pre-1.20.51 emitted a bare event
|
|
1355
|
+
// array; consumers that JSON.parse this now read `output.events` for the
|
|
1356
|
+
// array. See issue #743 (plan surfaced) and CHANGELOG for the shape change.
|
|
1357
|
+
process.stdout.write(renderJson(events, session));
|
|
1354
1358
|
}
|
|
1355
1359
|
function renderTopicCell(label, topic, query, visibleWidth, paddedWidth) {
|
|
1356
1360
|
const lbl = (label ?? '').trim();
|
|
@@ -2289,9 +2293,3 @@ function formatAbsoluteTime(isoTimestamp) {
|
|
|
2289
2293
|
const mm = String(d.getMinutes()).padStart(2, '0');
|
|
2290
2294
|
return `${months[d.getMonth()]} ${d.getDate()} ${hh}:${mm}`;
|
|
2291
2295
|
}
|
|
2292
|
-
function padRight(s, width) {
|
|
2293
|
-
return s.length >= width ? s : s + ' '.repeat(width - s.length);
|
|
2294
|
-
}
|
|
2295
|
-
function truncate(s, max) {
|
|
2296
|
-
return s.length > max ? s.slice(0, max - 1) + '.' : s;
|
|
2297
|
-
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* activity for each team.
|
|
8
8
|
*/
|
|
9
9
|
import chalk from 'chalk';
|
|
10
|
+
import { relTime, truncate, humanDuration } from '../lib/format.js';
|
|
10
11
|
import { itemPicker } from '../lib/picker.js';
|
|
11
12
|
const AGENT_LABEL = {
|
|
12
13
|
claude: 'Claude',
|
|
@@ -26,23 +27,6 @@ function statusColor(status) {
|
|
|
26
27
|
default: return chalk.white;
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
|
-
function relTime(iso) {
|
|
30
|
-
const secs = Math.floor((Date.now() - new Date(iso).getTime()) / 1000);
|
|
31
|
-
if (secs < 10)
|
|
32
|
-
return 'just now';
|
|
33
|
-
if (secs < 60)
|
|
34
|
-
return `${secs}s ago`;
|
|
35
|
-
if (secs < 3600)
|
|
36
|
-
return `${Math.floor(secs / 60)}m ago`;
|
|
37
|
-
if (secs < 86400)
|
|
38
|
-
return `${Math.floor(secs / 3600)}h ago`;
|
|
39
|
-
return `${Math.floor(secs / 86400)}d ago`;
|
|
40
|
-
}
|
|
41
|
-
function truncate(s, n) {
|
|
42
|
-
if (s.length <= n)
|
|
43
|
-
return s;
|
|
44
|
-
return s.slice(0, n - 1) + '…';
|
|
45
|
-
}
|
|
46
30
|
function firstLine(s) {
|
|
47
31
|
// Collapse to the first non-empty line so multi-line messages (markdown,
|
|
48
32
|
// code blocks) render cleanly inside a one-line preview slot.
|
|
@@ -120,21 +104,6 @@ function formatComposition(agents) {
|
|
|
120
104
|
.map(([type, n]) => (n === 1 ? type : `${type}×${n}`));
|
|
121
105
|
return parts.join(' ');
|
|
122
106
|
}
|
|
123
|
-
function humanDuration(ms) {
|
|
124
|
-
const s = Math.floor(ms / 1000);
|
|
125
|
-
if (s < 60)
|
|
126
|
-
return `${s}s`;
|
|
127
|
-
const m = Math.floor(s / 60);
|
|
128
|
-
if (m < 60)
|
|
129
|
-
return `${m}m`;
|
|
130
|
-
const h = Math.floor(m / 60);
|
|
131
|
-
const mm = m % 60;
|
|
132
|
-
if (h < 24)
|
|
133
|
-
return mm ? `${h}h${mm}m` : `${h}h`;
|
|
134
|
-
const d = Math.floor(h / 24);
|
|
135
|
-
const hh = h % 24;
|
|
136
|
-
return hh ? `${d}d${hh}h` : `${d}d`;
|
|
137
|
-
}
|
|
138
107
|
function runtimeSpan(t) {
|
|
139
108
|
const startMs = new Date(t.created_at).getTime();
|
|
140
109
|
const endMs = new Date(t.modified_at).getTime();
|
package/dist/commands/teams.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import { die, relTime, truncate, isJsonMode, padRight } from '../lib/format.js';
|
|
2
3
|
import * as fs from 'fs/promises';
|
|
3
4
|
import { addHostOption } from '../lib/hosts/option.js';
|
|
4
5
|
import * as path from 'path';
|
|
5
6
|
import { AgentManager, checkCliSignedIn, collectTeamsDoctorData, getAgentsDir, VALID_TASK_TYPES, } from '../lib/teams/agents.js';
|
|
6
7
|
import { resolveProvider } from '../lib/cloud/registry.js';
|
|
8
|
+
import { emit } from '../lib/events.js';
|
|
7
9
|
import { runSupervisor } from '../lib/teams/supervisor.js';
|
|
8
10
|
import { debug } from '../lib/teams/debug.js';
|
|
9
11
|
import { runPrWatch, DEFAULT_MAX_WAVES, } from '../lib/teams/pr-watch.js';
|
|
@@ -48,13 +50,6 @@ const VALID_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max', 'auto'];
|
|
|
48
50
|
const VALID_CLOUD_PROVIDERS = ['rush', 'codex', 'factory'];
|
|
49
51
|
// Auto-enable JSON mode when piped / not a TTY so AI agent consumers get
|
|
50
52
|
// parseable output by default.
|
|
51
|
-
function isJsonMode(opts) {
|
|
52
|
-
return Boolean(opts.json) || !process.stdout.isTTY;
|
|
53
|
-
}
|
|
54
|
-
function die(msg, code = 1) {
|
|
55
|
-
console.error(chalk.red(msg));
|
|
56
|
-
process.exit(code);
|
|
57
|
-
}
|
|
58
53
|
function statusColor(status) {
|
|
59
54
|
switch (status) {
|
|
60
55
|
case 'pending': return chalk.blue;
|
|
@@ -65,23 +60,6 @@ function statusColor(status) {
|
|
|
65
60
|
default: return chalk.white;
|
|
66
61
|
}
|
|
67
62
|
}
|
|
68
|
-
function relTime(iso) {
|
|
69
|
-
const secs = Math.floor((Date.now() - new Date(iso).getTime()) / 1000);
|
|
70
|
-
if (secs < 10)
|
|
71
|
-
return 'just now';
|
|
72
|
-
if (secs < 60)
|
|
73
|
-
return `${secs}s ago`;
|
|
74
|
-
if (secs < 3600)
|
|
75
|
-
return `${Math.floor(secs / 60)}m ago`;
|
|
76
|
-
if (secs < 86400)
|
|
77
|
-
return `${Math.floor(secs / 3600)}h ago`;
|
|
78
|
-
return `${Math.floor(secs / 86400)}d ago`;
|
|
79
|
-
}
|
|
80
|
-
function truncate(s, n) {
|
|
81
|
-
if (s.length <= n)
|
|
82
|
-
return s;
|
|
83
|
-
return s.slice(0, n - 1) + '…';
|
|
84
|
-
}
|
|
85
63
|
function compactPrompt(s, n = 160) {
|
|
86
64
|
return truncate(s.replace(/\s+/g, ' ').trim(), n);
|
|
87
65
|
}
|
|
@@ -98,9 +76,6 @@ function formatTimestamp(iso) {
|
|
|
98
76
|
minute: '2-digit',
|
|
99
77
|
});
|
|
100
78
|
}
|
|
101
|
-
function padRight(s, width) {
|
|
102
|
-
return s.length >= width ? s : s + ' '.repeat(width - s.length);
|
|
103
|
-
}
|
|
104
79
|
function fullName(type, version) {
|
|
105
80
|
const name = AGENT_NAMES[type];
|
|
106
81
|
return version ? `${name} ${version}` : name;
|
|
@@ -1343,6 +1318,7 @@ export function registerTeamsCommands(program) {
|
|
|
1343
1318
|
}
|
|
1344
1319
|
try {
|
|
1345
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 });
|
|
1346
1322
|
if (isJsonMode(opts)) {
|
|
1347
1323
|
console.log(JSON.stringify(result, null, 2));
|
|
1348
1324
|
return;
|
|
@@ -1513,6 +1489,7 @@ export function registerTeamsCommands(program) {
|
|
|
1513
1489
|
await warnUnsignedTeammates(mgr, team);
|
|
1514
1490
|
await warnThrottledTeammates(mgr, team);
|
|
1515
1491
|
}
|
|
1492
|
+
emit('teams.start', { module: 'teams', team, watch: Boolean(opts.watch) });
|
|
1516
1493
|
if (!opts.watch) {
|
|
1517
1494
|
await runOneWave(mgr, team, Boolean(opts.json));
|
|
1518
1495
|
return;
|
|
@@ -1552,6 +1529,7 @@ export function registerTeamsCommands(program) {
|
|
|
1552
1529
|
},
|
|
1553
1530
|
});
|
|
1554
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 });
|
|
1555
1533
|
if (result.stoppedBy === 'drained') {
|
|
1556
1534
|
console.log(chalk.green(`Factory drained in ${elapsed}s (${result.waves} waves).`));
|
|
1557
1535
|
}
|
package/dist/commands/tmux.js
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* agents tmux kill-all [--yes]
|
|
22
22
|
*/
|
|
23
23
|
import chalk from 'chalk';
|
|
24
|
+
import { truncate } from '../lib/format.js';
|
|
24
25
|
import * as path from 'path';
|
|
25
26
|
import { setHelpSections } from '../lib/help.js';
|
|
26
27
|
import { assertTmuxAvailable, attachTmux, capturePane, createSession, getDefaultSocketPath, getTmuxVersion, hasSession, isTmuxInstalled, killAll, killSession, listSessions, readSessionMeta, sendKeys, splitPane, TmuxCommandError, TmuxSessionError, TmuxUnavailableError, } from '../lib/tmux/index.js';
|
|
@@ -395,9 +396,6 @@ function formatAge(secs) {
|
|
|
395
396
|
const h = Math.floor(m / 60);
|
|
396
397
|
return `${h}h ${m % 60}m`;
|
|
397
398
|
}
|
|
398
|
-
function truncate(s, n) {
|
|
399
|
-
return s.length > n ? `${s.slice(0, n - 1)}…` : s;
|
|
400
|
-
}
|
|
401
399
|
function printMeta(m) {
|
|
402
400
|
console.log(` ${chalk.bold('name')} ${m.name}`);
|
|
403
401
|
console.log(` ${chalk.bold('socket')} ${m.socket}`);
|
package/dist/commands/view.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { addHostOption } from '../lib/hosts/option.js';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
+
import { visibleWidth, termLink } from '../lib/format.js';
|
|
3
4
|
import ora from 'ora';
|
|
4
5
|
import * as fs from 'fs';
|
|
5
6
|
import * as path from 'path';
|
|
@@ -67,12 +68,6 @@ function profileKindAndModel(model, planWidth) {
|
|
|
67
68
|
const kind = 'profile'.padEnd(Math.max(planWidth, 'profile'.length));
|
|
68
69
|
return `${kind} ${model}`;
|
|
69
70
|
}
|
|
70
|
-
function termLink(text, filePath) {
|
|
71
|
-
if (!process.stdout.isTTY)
|
|
72
|
-
return text;
|
|
73
|
-
const url = `file://${filePath}`;
|
|
74
|
-
return `\x1b]8;;${url}\x1b\\${text}\x1b]8;;\x1b\\`;
|
|
75
|
-
}
|
|
76
71
|
/**
|
|
77
72
|
* Resolve a resource path to something the IDE can open inline. When `p` is a
|
|
78
73
|
* directory, OSC 8 file:// links cause IDEs (Cursor/VS Code) to open it as a
|
|
@@ -112,9 +107,6 @@ function formatLastActive(date) {
|
|
|
112
107
|
return chalk.gray(`${days}d ago`);
|
|
113
108
|
return chalk.gray(`${days}d ago`);
|
|
114
109
|
}
|
|
115
|
-
function visibleWidth(s) {
|
|
116
|
-
return s.replace(/\u001b\[[0-9;]*m/g, '').length;
|
|
117
|
-
}
|
|
118
110
|
function compareVersions(a, b) {
|
|
119
111
|
const partsA = a.split('.').map(Number);
|
|
120
112
|
const partsB = b.split('.').map(Number);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import { die } from '../lib/format.js';
|
|
2
3
|
import { execFile } from 'child_process';
|
|
3
4
|
import { promisify } from 'util';
|
|
4
5
|
import * as fs from 'fs/promises';
|
|
@@ -8,10 +9,6 @@ import { setHelpSections } from '../lib/help.js';
|
|
|
8
9
|
const execFileAsync = promisify(execFile);
|
|
9
10
|
const WORKTREE_SUBDIR = path.join('.agents', 'worktrees');
|
|
10
11
|
const BRANCH_PREFIX = 'agents/';
|
|
11
|
-
function die(msg, code = 1) {
|
|
12
|
-
console.error(chalk.red(msg));
|
|
13
|
-
process.exit(code);
|
|
14
|
-
}
|
|
15
12
|
function isValidTerminalId(id) {
|
|
16
13
|
// Allow letters, digits, dot, dash, underscore. Reject anything else so a
|
|
17
14
|
// hostile or buggy caller can't inject path traversal or shell metachars.
|
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, 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';
|
|
@@ -101,7 +101,10 @@ program.hook('preAction', (_thisCommand, actionCommand) => {
|
|
|
101
101
|
emit('command.start', {
|
|
102
102
|
module: parts[0],
|
|
103
103
|
command: parts.join(' '),
|
|
104
|
-
|
|
104
|
+
// Commander exposes positional operands in actionCommand.args but omits
|
|
105
|
+
// parsed option values. Audit the real argv so sensitive flags are seen
|
|
106
|
+
// and redacted instead of silently bypassing the policy.
|
|
107
|
+
args: redactArgs(process.argv.slice(2, 22)),
|
|
105
108
|
cwd: process.cwd(),
|
|
106
109
|
});
|
|
107
110
|
}
|
|
@@ -704,6 +707,7 @@ async function registerAllEagerCommands() {
|
|
|
704
707
|
await reg(loadLogs);
|
|
705
708
|
await reg(loadEvents);
|
|
706
709
|
await reg(loadAudit);
|
|
710
|
+
await reg(loadFeed);
|
|
707
711
|
await reg(loadSsh);
|
|
708
712
|
registerJobsCronAliasCommand(program, 'jobs');
|
|
709
713
|
registerJobsCronAliasCommand(program, 'cron');
|