@phnx-labs/agents-cli 1.20.49 → 1.20.51
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 +31 -0
- package/README.md +3 -0
- package/dist/commands/browser-picker.js +1 -18
- package/dist/commands/cloud.js +1 -25
- package/dist/commands/computer.d.ts +1 -0
- package/dist/commands/computer.js +129 -8
- package/dist/commands/doctor.js +133 -2
- package/dist/commands/exec.js +49 -6
- package/dist/commands/factory.js +1 -4
- package/dist/commands/inspect.js +1 -11
- package/dist/commands/mcp.js +2 -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 +1 -0
- package/dist/commands/secrets.js +1 -28
- 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 +218 -97
- package/dist/commands/tmux.js +1 -3
- package/dist/commands/view.js +1 -9
- package/dist/commands/worktree.js +1 -4
- 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/refs.js +1 -5
- package/dist/lib/cli-resources.d.ts +0 -2
- package/dist/lib/cli-resources.js +30 -13
- package/dist/lib/cloud/rush.d.ts +0 -24
- package/dist/lib/cloud/rush.js +0 -31
- package/dist/lib/crabbox/cli.js +4 -1
- package/dist/lib/crabbox/lease.js +29 -1
- package/dist/lib/daemon.js +41 -0
- package/dist/lib/exec.js +43 -18
- 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 +17 -8
- package/dist/lib/hosts/passthrough.js +30 -1
- package/dist/lib/hosts/progress.d.ts +31 -0
- package/dist/lib/hosts/progress.js +35 -0
- package/dist/lib/hosts/remote-cmd.d.ts +1 -1
- package/dist/lib/hosts/remote-cmd.js +13 -3
- 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/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/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 +61 -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 +87 -10
- package/dist/lib/session/types.d.ts +9 -0
- package/dist/lib/shims.d.ts +25 -2
- package/dist/lib/shims.js +73 -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/teams/agents.d.ts +108 -1
- package/dist/lib/teams/agents.js +511 -11
- package/dist/lib/teams/api.d.ts +7 -1
- package/dist/lib/teams/api.js +5 -2
- package/dist/lib/teams/registry.d.ts +17 -0
- package/dist/lib/teams/registry.js +2 -0
- package/dist/lib/teams/remoteWorktree.d.ts +57 -0
- package/dist/lib/teams/remoteWorktree.js +213 -0
- package/dist/lib/teams/scheduler.d.ts +29 -0
- package/dist/lib/teams/scheduler.js +78 -0
- package/dist/lib/teams/supervisor.js +7 -0
- package/dist/lib/types.d.ts +14 -1
- package/dist/lib/versions.d.ts +7 -26
- package/dist/lib/versions.js +44 -146
- package/dist/lib/warn-unpushed.d.ts +40 -0
- package/dist/lib/warn-unpushed.js +128 -0
- 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/exec.js
CHANGED
|
@@ -11,6 +11,7 @@ import { setHelpSections } from '../lib/help.js';
|
|
|
11
11
|
import { parseLoopInterval } from '../lib/loop.js';
|
|
12
12
|
import { AGENTS } from '../lib/agents.js';
|
|
13
13
|
import { recordDispatchedRun } from '../lib/audit/log.js';
|
|
14
|
+
import { warnUnpushedWork, shouldWarnUnpushed } from '../lib/warn-unpushed.js';
|
|
14
15
|
import * as fs from 'fs';
|
|
15
16
|
import * as path from 'path';
|
|
16
17
|
import * as os from 'os';
|
|
@@ -211,7 +212,7 @@ export function registerRunCommand(program) {
|
|
|
211
212
|
.option('--secrets-keys <keys>', 'Inject only this comma-separated subset of keys from --secrets bundles (e.g. KEY1,KEY2). Missing keys are an error. Applies to all --secrets bundles on this run.')
|
|
212
213
|
.option('--allow-expired', 'Inject secrets even if their expiry date has passed (overrides the pre-run expiry abort).')
|
|
213
214
|
.option('--cwd <dir>', 'Working directory for the agent (defaults to current directory)')
|
|
214
|
-
.option('--add-dir <dir>', 'Grant access to an additional directory outside the project (Claude
|
|
215
|
+
.option('--add-dir <dir>', 'Grant access to an additional directory outside the project (Claude and Codex, repeatable)', (val, prev) => [...prev, val], [])
|
|
215
216
|
.option('--json', 'Stream events as JSON lines (for parsing by other tools)')
|
|
216
217
|
.option('--quiet', 'Suppress preamble (rotation banner, "Running:" line). Useful when piping JSON events to a parser.', false)
|
|
217
218
|
.option('--headless', 'Force headless mode. Auto-enabled when a prompt is provided; pass explicitly to stay headless with no prompt (reads the prompt from stdin).', false)
|
|
@@ -245,6 +246,12 @@ export function registerRunCommand(program) {
|
|
|
245
246
|
// `--on` and `--computer` are hidden aliases of `--host` — same behavior.
|
|
246
247
|
runCmd.addOption(new Option('--on <name>', 'Alias of --host.').hideHelp());
|
|
247
248
|
runCmd.addOption(new Option('--computer <name>', 'Alias of --host.').hideHelp());
|
|
249
|
+
// Required for the documented `agents run <agent> [prompt] -- <native flags>`
|
|
250
|
+
// passthrough: commander >=13 rejects excess operands by default, so any
|
|
251
|
+
// post-`--` token died with "too many arguments" before the action ran. The
|
|
252
|
+
// action re-derives the `--` boundary from rawArgs and still errors on excess
|
|
253
|
+
// operands that are NOT behind `--`.
|
|
254
|
+
runCmd.allowExcessArguments(true);
|
|
248
255
|
setHelpSections(runCmd, {
|
|
249
256
|
examples: `
|
|
250
257
|
# Headless, read-only: investigate or summarize without writing files
|
|
@@ -293,10 +300,25 @@ export function registerRunCommand(program) {
|
|
|
293
300
|
`,
|
|
294
301
|
});
|
|
295
302
|
runCmd.action(async (agentSpec, prompt, options, command) => {
|
|
296
|
-
// Capture everything after -- as passthrough args forwarded verbatim to the
|
|
297
|
-
//
|
|
298
|
-
|
|
299
|
-
|
|
303
|
+
// Capture everything after -- as passthrough args forwarded verbatim to the
|
|
304
|
+
// underlying CLI. Commander strips the literal `--` and folds what follows
|
|
305
|
+
// into the positional operands (so `agents run codex -- --yolo` would parse
|
|
306
|
+
// `--yolo` as the PROMPT) — recover the boundary from rawArgs instead of
|
|
307
|
+
// operand counts. Excess operands not behind `--` are still an error (an
|
|
308
|
+
// unquoted prompt must not silently become agent flags).
|
|
309
|
+
const rawArgs = process.argv;
|
|
310
|
+
const separatorIdx = rawArgs.indexOf('--');
|
|
311
|
+
const passthroughArgs = separatorIdx === -1 ? [] : rawArgs.slice(separatorIdx + 1);
|
|
312
|
+
const operandsBeforeSeparator = command.args.length - passthroughArgs.length;
|
|
313
|
+
if (operandsBeforeSeparator > 2) {
|
|
314
|
+
console.error(chalk.red(`Too many arguments for 'run'. Quote the prompt ("fix the bug"), and put agent-native flags after -- (agents run codex -- --yolo).`));
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
317
|
+
if (prompt !== undefined && operandsBeforeSeparator < 2) {
|
|
318
|
+
// The token commander assigned to [prompt] came from behind `--` — it is
|
|
319
|
+
// a native flag, not a prompt. Run interactively.
|
|
320
|
+
prompt = undefined;
|
|
321
|
+
}
|
|
300
322
|
// --lease: invent a disposable cloud box for this run (via crabbox), run
|
|
301
323
|
// the agent there, then tear it down. Unlike --host, nothing is registered.
|
|
302
324
|
if (options.lease) {
|
|
@@ -342,7 +364,7 @@ export function registerRunCommand(program) {
|
|
|
342
364
|
secretsBundle: process.env.AGENTS_LEASE_SECRETS_BUNDLE,
|
|
343
365
|
keep: options.keepBox,
|
|
344
366
|
});
|
|
345
|
-
console.error(chalk.gray(toreDown ? `Box ${box.slug} destroyed.` : `Box ${box.slug} kept${box.ip ? ` (${box.ip})` : ''}. Stop it: crabbox stop
|
|
367
|
+
console.error(chalk.gray(toreDown ? `Box ${box.slug} destroyed.` : `Box ${box.slug} kept${box.ip ? ` (${box.ip})` : ''}. Stop it: crabbox stop ${box.slug}`));
|
|
346
368
|
process.exit(exitCode === null ? 1 : exitCode);
|
|
347
369
|
}
|
|
348
370
|
catch (err) {
|
|
@@ -568,6 +590,9 @@ export function registerRunCommand(program) {
|
|
|
568
590
|
cwd: resumeExec.cwd ?? process.cwd(),
|
|
569
591
|
exitCode: resumeExit,
|
|
570
592
|
});
|
|
593
|
+
// A resumed loop is always headless; surface any commits it left unpushed.
|
|
594
|
+
if (shouldWarnUnpushed(resumeExec.mode ?? 'auto', false))
|
|
595
|
+
await warnUnpushedWork(resumeExec.cwd ?? process.cwd());
|
|
571
596
|
process.exit(resumeExit);
|
|
572
597
|
}
|
|
573
598
|
const [{ buildExecCommand, parseExecEnv, execAgent, runWithFallback, normalizeMode, resolveMode, headlessPlanStallCommand, nativeResume, resolveInteractive }, { ALL_AGENT_IDS }, { profileExists, resolveProfileForRun }, { readAndResolveBundleEnv, describeBundle, assertRemoteBundleFlagsUnsupported }, { splitBundleRef, resolveSshTarget, remoteResolveEnv }, { getConfiguredRunStrategy, normalizeRunStrategy, resolveRunVersion, rotationFailoverChain, shouldArmRotationFailover, RUN_STRATEGIES }, { getGlobalDefault, getVersionHomePath, resolveVersion, resolveVersionAlias, ensureAgentRunnable }, { buildDiscoveredPlugin, loadPluginManifest, syncPluginToVersion }, { parseWorkflowFrontmatter, resolveWorkflowRef, resolveAllowedSubagents, pruneStaleWorkflowSubagents }, { resolveRunDefaults }, { getMcpServersByName, buildWorkflowMcpConfig }, { supports },] = await Promise.all([
|
|
@@ -1279,6 +1304,9 @@ export function registerRunCommand(program) {
|
|
|
1279
1304
|
// Governance chokepoint (#347): the --acp path exits here, bypassing
|
|
1280
1305
|
// the normal finalize below — record its one audit entry.
|
|
1281
1306
|
recordDispatchedRun({ agent, version: defaultVersion ?? 'unknown', mode, cwd, exitCode });
|
|
1307
|
+
// ACP headless run always has a prompt; surface any unpushed commits.
|
|
1308
|
+
if (shouldWarnUnpushed(mode, false))
|
|
1309
|
+
await warnUnpushedWork(cwd);
|
|
1282
1310
|
process.exit(exitCode);
|
|
1283
1311
|
}
|
|
1284
1312
|
catch (err) {
|
|
@@ -1435,6 +1463,9 @@ export function registerRunCommand(program) {
|
|
|
1435
1463
|
// the normal finalize below — record its one audit entry.
|
|
1436
1464
|
const loopExit = loopExitCode(result.stoppedBy);
|
|
1437
1465
|
recordDispatchedRun({ agent, version: defaultVersion ?? 'unknown', mode, cwd, exitCode: loopExit });
|
|
1466
|
+
// A loop is always headless; surface any commits it left unpushed.
|
|
1467
|
+
if (shouldWarnUnpushed(mode, false))
|
|
1468
|
+
await warnUnpushedWork(cwd);
|
|
1438
1469
|
process.exit(loopExit);
|
|
1439
1470
|
}
|
|
1440
1471
|
catch (err) {
|
|
@@ -1462,6 +1493,13 @@ export function registerRunCommand(program) {
|
|
|
1462
1493
|
}
|
|
1463
1494
|
cleanupWorkflowMcpConfig();
|
|
1464
1495
|
cleanupWorkflowSubagents();
|
|
1496
|
+
// Surface committed-but-unpushed work a headless writable run left
|
|
1497
|
+
// behind, so it isn't silently stranded in a worktree. Advisory only,
|
|
1498
|
+
// never throws; skipped for interactive runs (the human sees the shell)
|
|
1499
|
+
// and read-only plan mode (can't commit).
|
|
1500
|
+
if (shouldWarnUnpushed(mode, resolveInteractive(execOptions))) {
|
|
1501
|
+
await warnUnpushedWork(cwd);
|
|
1502
|
+
}
|
|
1465
1503
|
// Governance chokepoint (#347): every dispatched run finalizes here.
|
|
1466
1504
|
// ONE tamper-evident audit record per run — non-fatal by contract.
|
|
1467
1505
|
recordDispatchedRun({ agent: ranAgent, version: ranVersion ?? 'unknown', mode, cwd, exitCode });
|
|
@@ -1470,6 +1508,11 @@ export function registerRunCommand(program) {
|
|
|
1470
1508
|
catch (err) {
|
|
1471
1509
|
cleanupWorkflowMcpConfig();
|
|
1472
1510
|
cleanupWorkflowSubagents();
|
|
1511
|
+
// An agent that committed then crashed is the case where stranded work
|
|
1512
|
+
// matters most — warn before exiting the error path too.
|
|
1513
|
+
if (shouldWarnUnpushed(mode, resolveInteractive(execOptions))) {
|
|
1514
|
+
await warnUnpushedWork(cwd);
|
|
1515
|
+
}
|
|
1473
1516
|
console.error(chalk.red(`Failed to execute ${agent}: ${err.message}`));
|
|
1474
1517
|
process.exit(1);
|
|
1475
1518
|
}
|
package/dist/commands/factory.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import { die } from '../lib/format.js';
|
|
2
3
|
import * as fs from 'fs';
|
|
3
4
|
import * as path from 'path';
|
|
4
5
|
import { homedir } from 'os';
|
|
5
6
|
import { betaEnableHint, isBetaEnabled } from '../lib/beta.js';
|
|
6
7
|
import { insertTask } from '../lib/cloud/store.js';
|
|
7
|
-
function die(msg, code = 1) {
|
|
8
|
-
console.error(chalk.red(msg));
|
|
9
|
-
process.exit(code);
|
|
10
|
-
}
|
|
11
8
|
function requireFactoryUrl() {
|
|
12
9
|
const url = process.env.FACTORY_FLOOR_URL;
|
|
13
10
|
if (!url) {
|
package/dist/commands/inspect.js
CHANGED
|
@@ -17,6 +17,7 @@ import * as fs from 'fs';
|
|
|
17
17
|
import * as os from 'os';
|
|
18
18
|
import * as path from 'path';
|
|
19
19
|
import chalk from 'chalk';
|
|
20
|
+
import { truncate, termLink } from '../lib/format.js';
|
|
20
21
|
import * as yaml from 'yaml';
|
|
21
22
|
import { AGENTS, getCliState, resolveAgentName } from '../lib/agents.js';
|
|
22
23
|
import { supports } from '../lib/capabilities.js';
|
|
@@ -1216,12 +1217,6 @@ function readFirstProseLine(p) {
|
|
|
1216
1217
|
return '';
|
|
1217
1218
|
}
|
|
1218
1219
|
// ─── OSC-8 + path helpers ────────────────────────────────────────────────────
|
|
1219
|
-
function termLink(text, filePath) {
|
|
1220
|
-
if (!filePath || !process.stdout.isTTY)
|
|
1221
|
-
return text;
|
|
1222
|
-
const url = `file://${filePath}`;
|
|
1223
|
-
return `\x1b]8;;${url}\x1b\\${text}\x1b]8;;\x1b\\`;
|
|
1224
|
-
}
|
|
1225
1220
|
function linkTarget(p) {
|
|
1226
1221
|
if (!p)
|
|
1227
1222
|
return '';
|
|
@@ -1271,8 +1266,3 @@ function safeCountSessions(agent) {
|
|
|
1271
1266
|
return 0;
|
|
1272
1267
|
}
|
|
1273
1268
|
}
|
|
1274
|
-
function truncate(s, n) {
|
|
1275
|
-
if (s.length <= n)
|
|
1276
|
-
return s;
|
|
1277
|
-
return s.slice(0, n - 1) + '…';
|
|
1278
|
-
}
|
package/dist/commands/mcp.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import { truncate } from '../lib/format.js';
|
|
2
3
|
import ora from 'ora';
|
|
3
4
|
import { checkbox } from '@inquirer/prompts';
|
|
4
5
|
import { capableAgents, isCapable } from '../lib/capabilities.js';
|
|
@@ -764,7 +765,7 @@ function buildMcpRows(opts) {
|
|
|
764
765
|
const displayCommand = declaredCommand || firstCommand;
|
|
765
766
|
rows.push({
|
|
766
767
|
name,
|
|
767
|
-
description: displayCommand ?
|
|
768
|
+
description: displayCommand ? truncate(displayCommand, 60) : '',
|
|
768
769
|
extra: source,
|
|
769
770
|
targets,
|
|
770
771
|
buildDetail: () => formatMcpDetail(name, source, centralConfig, manifestConfig, displayCommand, targets),
|
|
@@ -811,8 +812,3 @@ function formatMcpDetail(name, source, centralConfig, manifestConfig, command, t
|
|
|
811
812
|
lines.push(buildTargetsSection(targets));
|
|
812
813
|
return lines.join('\n');
|
|
813
814
|
}
|
|
814
|
-
function truncateString(s, max) {
|
|
815
|
-
if (s.length <= max)
|
|
816
|
-
return s;
|
|
817
|
-
return s.slice(0, max - 1) + '…';
|
|
818
|
-
}
|
package/dist/commands/message.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import { die } from '../lib/format.js';
|
|
2
3
|
import { getActiveSessions } from '../lib/session/active.js';
|
|
3
4
|
import { getTaskById, updateTaskStatus } from '../lib/cloud/store.js';
|
|
4
5
|
import { resolveProvider } from '../lib/cloud/registry.js';
|
|
5
6
|
import { mailboxDir, enqueue } from '../lib/mailbox.js';
|
|
6
7
|
import { resolveMessageTarget } from '../lib/mailbox-target.js';
|
|
7
|
-
function die(msg, code = 1) {
|
|
8
|
-
console.error(chalk.red(msg));
|
|
9
|
-
process.exit(code);
|
|
10
|
-
}
|
|
11
8
|
export function registerMessageCommand(program) {
|
|
12
9
|
program
|
|
13
10
|
.command('message <target> <text>')
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* through a standard agent CLI with no local proxy.
|
|
8
8
|
*/
|
|
9
9
|
import chalk from 'chalk';
|
|
10
|
-
import
|
|
10
|
+
import { readStdinSync } from '../lib/format.js';
|
|
11
11
|
import { spawn } from 'child_process';
|
|
12
12
|
import { listProfiles, readProfile, writeProfile, deleteProfile, profileExists, profileFromPreset, validateProfileName, getPresetForProfile, } from '../lib/profiles.js';
|
|
13
13
|
import { getPreset, listPresets, expandPreset } from '../lib/profiles-presets.js';
|
|
@@ -41,23 +41,6 @@ async function promptForSecret(message) {
|
|
|
41
41
|
return await password({ message, mask: true });
|
|
42
42
|
}
|
|
43
43
|
/** Read all available data from stdin synchronously, trimmed. */
|
|
44
|
-
function readStdinSync() {
|
|
45
|
-
const chunks = [];
|
|
46
|
-
const buf = Buffer.alloc(65536);
|
|
47
|
-
while (true) {
|
|
48
|
-
let bytesRead;
|
|
49
|
-
try {
|
|
50
|
-
bytesRead = fs.readSync(0, buf, 0, buf.length, null);
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
break;
|
|
54
|
-
}
|
|
55
|
-
if (bytesRead === 0)
|
|
56
|
-
break;
|
|
57
|
-
chunks.push(Buffer.from(buf.subarray(0, bytesRead)));
|
|
58
|
-
}
|
|
59
|
-
return Buffer.concat(chunks).toString('utf-8').trim();
|
|
60
|
-
}
|
|
61
44
|
/** Ensure a provider API key exists in keychain, prompting or reading stdin if missing. */
|
|
62
45
|
async function ensureProviderToken(provider, signupUrl, fromStdin) {
|
|
63
46
|
const item = keychainItemName(provider);
|
package/dist/commands/repo.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import { visibleWidth, padVisible } from '../lib/format.js';
|
|
2
3
|
import ora from 'ora';
|
|
3
4
|
import * as fs from 'fs';
|
|
4
5
|
import * as path from 'path';
|
|
@@ -26,7 +27,7 @@ function resolveRepoPath(target) {
|
|
|
26
27
|
return path.join(HOME, `.agents-${trimmed}`);
|
|
27
28
|
}
|
|
28
29
|
import { applyExtraAliasToVersions, ensureAgentsDir, getExtraRepoDir, getSystemAgentsDir, getUserAgentsDir, readMeta, resolveExtraRepoDir, updateMeta, } from '../lib/state.js';
|
|
29
|
-
import { parseSource, pullRepo, commitAndPush, isGitRepo, isSystemRepoOrigin } from '../lib/git.js';
|
|
30
|
+
import { parseSource, pullRepo, commitAndPush, isGitRepo, isSystemRepoOrigin, adoptRepo } from '../lib/git.js';
|
|
30
31
|
import { DEFAULT_SYSTEM_REPO } from '../lib/types.js';
|
|
31
32
|
import { ALL_AGENT_IDS, isAgentName, resolveAgentName } from '../lib/agents.js';
|
|
32
33
|
import { refresh } from '../lib/refresh.js';
|
|
@@ -218,14 +219,6 @@ async function diffResourceEntries(git, range) {
|
|
|
218
219
|
}
|
|
219
220
|
return out;
|
|
220
221
|
}
|
|
221
|
-
/** Visible character width of a string with embedded ANSI color codes. */
|
|
222
|
-
function visibleWidth(s) {
|
|
223
|
-
return s.replace(/\[[0-9;]*m/g, '').length;
|
|
224
|
-
}
|
|
225
|
-
/** Pad string with trailing spaces to a target visible column width. */
|
|
226
|
-
function padVisible(s, width) {
|
|
227
|
-
return s + ' '.repeat(Math.max(0, width - visibleWidth(s)));
|
|
228
|
-
}
|
|
229
222
|
/**
|
|
230
223
|
* Read one repo's status into structured data: branch, resource-level sync (what
|
|
231
224
|
* a pull/push would move), resource-level local edits, and remote URL + commit.
|
|
@@ -752,9 +745,9 @@ export function registerRepoCommands(program) {
|
|
|
752
745
|
await toggle(alias, false);
|
|
753
746
|
});
|
|
754
747
|
repoCmd
|
|
755
|
-
.command('pull [alias]')
|
|
756
|
-
.description('Pull updates. Aliases: "system" (~/.agents/.system/), "user" (~/.agents/), or any registered extra. No arg pulls all.')
|
|
757
|
-
.action(async (alias) => {
|
|
748
|
+
.command('pull [alias] [url]')
|
|
749
|
+
.description('Pull updates. Aliases: "system" (~/.agents/.system/), "user" (~/.agents/), or any registered extra. No arg pulls all. Pass a git URL to git-back a not-yet-cloned user repo: "agents repo pull user <url>".')
|
|
750
|
+
.action(async (alias, url) => {
|
|
758
751
|
const targets = collectRepoTargets(alias);
|
|
759
752
|
if (!targets) {
|
|
760
753
|
process.exitCode = 1;
|
|
@@ -766,8 +759,34 @@ export function registerRepoCommands(program) {
|
|
|
766
759
|
}
|
|
767
760
|
for (const t of targets) {
|
|
768
761
|
if (!fs.existsSync(t.dir) || !isGitRepo(t.dir)) {
|
|
769
|
-
|
|
770
|
-
|
|
762
|
+
// A plain (never-cloned) user repo — setup makes ~/.agents a bare dir and
|
|
763
|
+
// never git-clones it (state.ts ensureAgentsDir), so a fresh/Windows box
|
|
764
|
+
// silently falls out of config sync. If a URL was passed, git-back it in
|
|
765
|
+
// place (keeping local files, backing up any that differ); else guide the
|
|
766
|
+
// user. Only the USER repo gets this — system is cloned by setup, extras
|
|
767
|
+
// by `repo add`.
|
|
768
|
+
if (t.alias === 'user' && url) {
|
|
769
|
+
const spinner = ora(`Git-backing ${t.dir} from ${url}...`).start();
|
|
770
|
+
const res = await adoptRepo(url, t.dir);
|
|
771
|
+
if (!res.success) {
|
|
772
|
+
spinner.fail(`user: could not git-back — ${res.error}`);
|
|
773
|
+
process.exitCode = 1;
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
spinner.succeed(`user: git-backed -> ${res.commit}`);
|
|
777
|
+
if (res.backedUp.length > 0) {
|
|
778
|
+
console.log(chalk.yellow(` backed up ${res.backedUp.length} locally-modified file(s) to ${res.backupDir}`));
|
|
779
|
+
console.log(chalk.gray(` (${res.backedUp.slice(0, 5).join(', ')}${res.backedUp.length > 5 ? ', …' : ''})`));
|
|
780
|
+
}
|
|
781
|
+
// Now a git repo — fall through to the normal pull below.
|
|
782
|
+
}
|
|
783
|
+
else {
|
|
784
|
+
console.log(chalk.yellow(` ${t.alias}: not a git repo, skipping`));
|
|
785
|
+
if (t.alias === 'user') {
|
|
786
|
+
console.log(chalk.gray(' git-back it from your config remote: agents repo pull user <git-url>'));
|
|
787
|
+
}
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
771
790
|
}
|
|
772
791
|
if (t.alias === 'system') {
|
|
773
792
|
// Skip system repo unless explicitly requested
|
|
@@ -58,3 +58,4 @@ export declare function resourceLayout(cols: number, o: {
|
|
|
58
58
|
}): ResourceLayout;
|
|
59
59
|
/** Build the sync targets section showing version pills grouped by agent. */
|
|
60
60
|
export declare function buildTargetsSection(targets: SyncTarget[]): string;
|
|
61
|
+
/** Pad a string to a fixed width, accounting for ANSI escape codes in length calculation. */
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* filtering, and paging.
|
|
8
8
|
*/
|
|
9
9
|
import chalk from 'chalk';
|
|
10
|
+
import { truncate, padVisible } from '../lib/format.js';
|
|
10
11
|
import { agentLabel } from '../lib/agents.js';
|
|
11
12
|
import { itemPicker } from '../lib/picker.js';
|
|
12
13
|
import { isInteractiveTerminal, isPromptCancelled, printWithPager } from './utils.js';
|
|
@@ -65,15 +66,15 @@ function filterRows(rows, query) {
|
|
|
65
66
|
}
|
|
66
67
|
/** Row label rendered inside the picker list. */
|
|
67
68
|
function formatPickerRow(row, opts) {
|
|
68
|
-
const name = chalk.cyan(
|
|
69
|
+
const name = chalk.cyan(padVisible(row.name, 22));
|
|
69
70
|
const extra = opts.extraLabel
|
|
70
|
-
? chalk.gray(
|
|
71
|
+
? chalk.gray(padVisible(row.extra ?? '-', 10))
|
|
71
72
|
: '';
|
|
72
73
|
const extra2 = opts.extra2Label
|
|
73
|
-
? chalk.gray(
|
|
74
|
+
? chalk.gray(padVisible(row.extra2 ?? '-', 16))
|
|
74
75
|
: '';
|
|
75
76
|
const descRaw = row.description ? truncate(row.description, 40) : '';
|
|
76
|
-
const desc =
|
|
77
|
+
const desc = padVisible(chalk.gray(descRaw), 42);
|
|
77
78
|
const sync = formatSyncSummary(row.targets, opts);
|
|
78
79
|
return `${name} ${extra}${extra2}${desc} ${sync}`;
|
|
79
80
|
}
|
|
@@ -252,16 +253,3 @@ function formatVersionPill(t) {
|
|
|
252
253
|
}
|
|
253
254
|
}
|
|
254
255
|
/** Pad a string to a fixed width, accounting for ANSI escape codes in length calculation. */
|
|
255
|
-
function padRight(s, width) {
|
|
256
|
-
// Strip ANSI for length calc
|
|
257
|
-
const raw = s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
258
|
-
if (raw.length >= width)
|
|
259
|
-
return s;
|
|
260
|
-
return s + ' '.repeat(width - raw.length);
|
|
261
|
-
}
|
|
262
|
-
/** Truncate a string to a maximum length, appending an ellipsis if needed. */
|
|
263
|
-
function truncate(s, max) {
|
|
264
|
-
if (s.length <= max)
|
|
265
|
-
return s;
|
|
266
|
-
return s.slice(0, max - 1) + '…';
|
|
267
|
-
}
|
|
@@ -9,6 +9,7 @@ import { type Command } from 'commander';
|
|
|
9
9
|
import { SSH_TARGET_RE, assertValidSshTarget } from '../lib/ssh-exec.js';
|
|
10
10
|
import { quoteWin32ExecArg } from '../lib/platform/index.js';
|
|
11
11
|
import { type SecretsBundle, type SecretsPolicy } from '../lib/secrets/bundles.js';
|
|
12
|
+
/** Read all available data from stdin synchronously, trimmed. */
|
|
12
13
|
/**
|
|
13
14
|
* Read the raw `.env` text for `import --from <path|->`. A `-` reads the .env
|
|
14
15
|
* from stdin (the SSH push path: `export --host` pipes the resolved dotenv over
|
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';
|
|
@@ -125,23 +126,6 @@ async function resolveVault(vaultOpt) {
|
|
|
125
126
|
});
|
|
126
127
|
}
|
|
127
128
|
/** 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
129
|
/**
|
|
146
130
|
* Read the raw `.env` text for `import --from <path|->`. A `-` reads the .env
|
|
147
131
|
* from stdin (the SSH push path: `export --host` pipes the resolved dotenv over
|
|
@@ -257,17 +241,6 @@ async function browseRemote(targets, args, tty) {
|
|
|
257
241
|
process.exit(1);
|
|
258
242
|
}
|
|
259
243
|
/** 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
244
|
/** Render an ISO-8601 timestamp as a compact relative age: "now", "5m", "1h", "3d", "2w", "4mo", "1y". */
|
|
272
245
|
function relativeAge(iso) {
|
|
273
246
|
const t = Date.parse(iso);
|
|
@@ -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();
|