@agentworkforce/cli 0.19.0 → 2.1.0
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 +18 -0
- package/dist/cli.d.ts +14 -6
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +189 -24
- package/dist/cli.js.map +1 -1
- package/dist/launch-metadata.d.ts +1 -1
- package/dist/launch-metadata.d.ts.map +1 -1
- package/dist/local-personas.d.ts +1 -1
- package/dist/local-personas.d.ts.map +1 -1
- package/dist/local-personas.js +2 -1
- package/dist/local-personas.js.map +1 -1
- package/dist/persona-tui.d.ts +85 -0
- package/dist/persona-tui.d.ts.map +1 -0
- package/dist/persona-tui.js +390 -0
- package/dist/persona-tui.js.map +1 -0
- package/dist/persona-tui.test.d.ts +2 -0
- package/dist/persona-tui.test.d.ts.map +1 -0
- package/dist/persona-tui.test.js +130 -0
- package/dist/persona-tui.test.js.map +1 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.1.0] - 2026-05-11
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Persona-tui: address review feedback
|
|
15
|
+
- Cli: open interactive persona picker on bare invocation
|
|
16
|
+
|
|
17
|
+
## [2.0.1] - 2026-05-11
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Migrate workforce CLI to consume persona-kit directly (#67)
|
|
22
|
+
- Move persona-shape code from harness-kit + workload-router into persona-kit (#65)
|
|
23
|
+
- Delete @agentworkforce/harness-kit from the monorepo (#65)
|
|
24
|
+
- Shrink workload-router: drop persona-domain re-exports (1.0.0) (#68)
|
|
25
|
+
- RunPersonaImprover: restore config files in try/finally
|
|
26
|
+
- RunPersonaImprover: escalate to SIGKILL after SIGTERM grace
|
|
27
|
+
|
|
10
28
|
## [0.19.0] - 2026-05-08
|
|
11
29
|
|
|
12
30
|
### Changed
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { type Harness, type PersonaMount, type PersonaSelection, type SidecarMdMode } from '@agentworkforce/
|
|
2
|
+
import { type Harness, type PersonaMount, type PersonaSelection, type SidecarMdMode } from '@agentworkforce/persona-kit';
|
|
3
3
|
import { type PickCandidate } from './persona-picker.js';
|
|
4
|
+
import { type TuiCandidate } from './persona-tui.js';
|
|
4
5
|
export declare const CLI_VERSION: string;
|
|
5
6
|
export declare const CREATE_SELECTOR = "persona-maker@best";
|
|
6
7
|
/**
|
|
@@ -22,11 +23,11 @@ export declare function resolveSystemPromptPlaceholders(prompt: string, harness:
|
|
|
22
23
|
* launching opencode without a persona-specific agent selection.
|
|
23
24
|
*
|
|
24
25
|
* Strips all occurrences rather than just the first — the current producer
|
|
25
|
-
* (
|
|
26
|
-
* are equivalent today, but "remove all" is idempotent and safer if
|
|
27
|
-
* caller ever appends a second `--agent` for any reason. A trailing
|
|
28
|
-
* with no following value is preserved so the malformed argv
|
|
29
|
-
* harness rather than getting silently swallowed here.
|
|
26
|
+
* (the opencode branch in persona-kit) emits exactly one pair, so both
|
|
27
|
+
* behaviors are equivalent today, but "remove all" is idempotent and safer if
|
|
28
|
+
* a future caller ever appends a second `--agent` for any reason. A trailing
|
|
29
|
+
* `--agent` with no following value is preserved so the malformed argv
|
|
30
|
+
* surfaces at the harness rather than getting silently swallowed here.
|
|
30
31
|
*/
|
|
31
32
|
export declare function stripAgentFlag(args: readonly string[]): string[];
|
|
32
33
|
/**
|
|
@@ -180,6 +181,13 @@ export declare function readSingleCharChoice(prompt: string, valid: readonly str
|
|
|
180
181
|
* unsupported patch op/path resolution.
|
|
181
182
|
*/
|
|
182
183
|
export declare function applyAcceptedPatches(personaFilePath: string, accepted: readonly ImproverProposal[]): void;
|
|
184
|
+
/**
|
|
185
|
+
* Enumerate personas for the interactive TUI. Source label mirrors the cascade
|
|
186
|
+
* shown by `agentworkforce list` so the picker tells the user *where* a
|
|
187
|
+
* persona is coming from (cwd, user, dir:n, library) without a separate
|
|
188
|
+
* lookup.
|
|
189
|
+
*/
|
|
190
|
+
export declare function buildTuiCandidates(): TuiCandidate[];
|
|
183
191
|
/**
|
|
184
192
|
* Enumerate persona candidates for the picker. Local overrides win over the
|
|
185
193
|
* built-in catalog when ids collide; the picker only needs the projection
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAsBA,OAAO,
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -5,16 +5,21 @@ import { appendFileSync, closeSync, existsSync, mkdirSync, mkdtempSync, openSync
|
|
|
5
5
|
import { constants, homedir, tmpdir } from 'node:os';
|
|
6
6
|
import { dirname, isAbsolute, join, resolve as resolvePath } from 'node:path';
|
|
7
7
|
import { pathToFileURL } from 'node:url';
|
|
8
|
-
import { HARNESS_VALUES, materializeSkills, PERSONA_TAGS, PERSONA_TIERS,
|
|
9
|
-
import {
|
|
8
|
+
import { buildCleanupArtifacts, buildInstallArtifacts, buildInteractiveSpec, buildNonInteractiveSpec, detectHarnesses, formatDropWarnings, HARNESS_VALUES, materializeSkills, MissingPersonaInputError, PERSONA_TAGS, PERSONA_TIERS, renderPersonaInputs, resolveMcpServersLenient, resolvePersonaInputs, resolveSidecar, resolveStringMapLenient } from '@agentworkforce/persona-kit';
|
|
9
|
+
import { listBuiltInPersonas, personaCatalog, routingProfiles } from '@agentworkforce/workload-router';
|
|
10
10
|
import { createMount, readAgentDotfiles } from '@relayfile/local-mount';
|
|
11
11
|
import ora from 'ora';
|
|
12
12
|
import { startLaunchMetadataRecording } from './launch-metadata.js';
|
|
13
13
|
import { buildPersonaSourceDirectories, defaultCwdPersonaDir, loadLocalPersonas, loadPersonaSourceConfig, normalizePersonaDir, savePersonaSourceConfig } from './local-personas.js';
|
|
14
14
|
import { installPersonas } from './persona-install.js';
|
|
15
15
|
import { pickPersona } from './persona-picker.js';
|
|
16
|
+
import { recordRecent, loadRecents, runPersonaPickerTui } from './persona-tui.js';
|
|
16
17
|
const USAGE = `Usage: agentworkforce <command> [args...]
|
|
17
18
|
|
|
19
|
+
Run with no arguments inside a TTY to open an interactive persona picker —
|
|
20
|
+
the top 3 most recently used personas are shown first, and typing fuzzy-
|
|
21
|
+
searches across persona names and descriptions.
|
|
22
|
+
|
|
18
23
|
Commands:
|
|
19
24
|
create [flags] Opens persona-maker@best for creating a new
|
|
20
25
|
persona, with target path passed as persona inputs.
|
|
@@ -418,6 +423,18 @@ async function runInstallOrThrow(command, label, cwd) {
|
|
|
418
423
|
throw new InstallCommandError(label, code);
|
|
419
424
|
}
|
|
420
425
|
}
|
|
426
|
+
function buildInstallContext(selection, options = {}) {
|
|
427
|
+
const plan = materializeSkills(selection.skills, selection.runtime.harness, options.installRoot !== undefined ? { installRoot: options.installRoot } : {});
|
|
428
|
+
const { installCommand, installCommandString } = buildInstallArtifacts(plan);
|
|
429
|
+
const { cleanupCommand, cleanupCommandString } = buildCleanupArtifacts(plan);
|
|
430
|
+
return {
|
|
431
|
+
plan,
|
|
432
|
+
command: installCommand,
|
|
433
|
+
commandString: installCommandString,
|
|
434
|
+
cleanupCommand,
|
|
435
|
+
cleanupCommandString
|
|
436
|
+
};
|
|
437
|
+
}
|
|
421
438
|
function runCleanup(command, commandString) {
|
|
422
439
|
if (commandString === ':')
|
|
423
440
|
return;
|
|
@@ -475,11 +492,11 @@ function sessionMountDir(sessionRoot) {
|
|
|
475
492
|
* launching opencode without a persona-specific agent selection.
|
|
476
493
|
*
|
|
477
494
|
* Strips all occurrences rather than just the first — the current producer
|
|
478
|
-
* (
|
|
479
|
-
* are equivalent today, but "remove all" is idempotent and safer if
|
|
480
|
-
* caller ever appends a second `--agent` for any reason. A trailing
|
|
481
|
-
* with no following value is preserved so the malformed argv
|
|
482
|
-
* harness rather than getting silently swallowed here.
|
|
495
|
+
* (the opencode branch in persona-kit) emits exactly one pair, so both
|
|
496
|
+
* behaviors are equivalent today, but "remove all" is idempotent and safer if
|
|
497
|
+
* a future caller ever appends a second `--agent` for any reason. A trailing
|
|
498
|
+
* `--agent` with no following value is preserved so the malformed argv
|
|
499
|
+
* surfaces at the harness rather than getting silently swallowed here.
|
|
483
500
|
*/
|
|
484
501
|
export function stripAgentFlag(args) {
|
|
485
502
|
const out = [];
|
|
@@ -806,7 +823,7 @@ function runDryRun(selection) {
|
|
|
806
823
|
return 1;
|
|
807
824
|
}
|
|
808
825
|
process.stderr.write(`✓ sidecar: ${sidecarLookup.sidecar ? sidecarLookup.sidecar.mountFile : '(none)'}\n`);
|
|
809
|
-
// Check 2:
|
|
826
|
+
// Check 2: persona-kit translation. buildInteractiveSpec validates
|
|
810
827
|
// permissions shape, mcpServers shape, and required runtime fields.
|
|
811
828
|
// We resolve env + mcp leniently (same as the live launch path) so
|
|
812
829
|
// the spec call sees the same inputs it would at runtime.
|
|
@@ -932,8 +949,7 @@ async function runInteractive(selection, options) {
|
|
|
932
949
|
const installRoot = sessionRoot && runtime.harness === 'claude'
|
|
933
950
|
? sessionInstallRoot(sessionRoot)
|
|
934
951
|
: undefined;
|
|
935
|
-
const
|
|
936
|
-
const { install } = ctx;
|
|
952
|
+
const install = buildInstallContext(effectiveSelection, installRoot !== undefined ? { installRoot } : {});
|
|
937
953
|
process.stderr.write(`→ ${personaId} [${tier}] via ${runtime.harness} (${runtime.model})\n`);
|
|
938
954
|
const startLaunchMetadataForLaunch = (cwd = process.cwd()) => startLaunchMetadataRecording({
|
|
939
955
|
selection: effectiveSelection,
|
|
@@ -2130,6 +2146,9 @@ async function runAgentSelector(selector, flags, inputValues) {
|
|
|
2130
2146
|
const code = runDryRun(selection);
|
|
2131
2147
|
process.exit(code);
|
|
2132
2148
|
}
|
|
2149
|
+
// Record only on real launches so `--dry-run` validations don't pollute the
|
|
2150
|
+
// MRU list used by the bare-invocation picker.
|
|
2151
|
+
recordRecent(target.spec.id);
|
|
2133
2152
|
const capture = {};
|
|
2134
2153
|
const code = await runInteractive(selection, {
|
|
2135
2154
|
installInRepo: flags.installInRepo,
|
|
@@ -2707,23 +2726,110 @@ async function runPersonaImprover(args) {
|
|
|
2707
2726
|
}
|
|
2708
2727
|
const tier = 'best-value';
|
|
2709
2728
|
const selection = buildSelection(improverSpec, tier, 'repo');
|
|
2710
|
-
const
|
|
2711
|
-
|
|
2729
|
+
const inputValues = {
|
|
2730
|
+
PERSONA_FILE_PATH: args.personaFilePath,
|
|
2731
|
+
SESSION_TRANSCRIPT_PATH: args.transcriptPath,
|
|
2732
|
+
PROPOSALS_OUTPUT_PATH: args.proposalsOutputPath
|
|
2733
|
+
};
|
|
2734
|
+
const inputResolution = resolvePersonaInputs(selection.inputs, inputValues, process.env);
|
|
2735
|
+
const renderedSystemPrompt = renderPersonaInputs(selection.runtime.systemPrompt, inputResolution.values);
|
|
2736
|
+
const callerEnv = { ...process.env, ...inputResolution.values };
|
|
2737
|
+
const envResolution = resolveStringMapLenient(selection.env, callerEnv, 'env');
|
|
2738
|
+
const mcpResolution = resolveMcpServersLenient(selection.mcpServers, callerEnv);
|
|
2739
|
+
const taskBody = [
|
|
2712
2740
|
'Improve this local persona from one finished session. The CLI will read your proposals JSON and walk the user through accept/deny.',
|
|
2713
2741
|
`PERSONA_FILE_PATH=${args.personaFilePath}`,
|
|
2714
2742
|
`SESSION_TRANSCRIPT_PATH=${args.transcriptPath}`,
|
|
2715
2743
|
`PROPOSALS_OUTPUT_PATH=${args.proposalsOutputPath}`
|
|
2716
|
-
];
|
|
2717
|
-
const
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2744
|
+
].join('\n');
|
|
2745
|
+
const task = `${taskBody}\n\nRun inputs:\n${JSON.stringify(inputValues, null, 2)}`;
|
|
2746
|
+
const spec = buildNonInteractiveSpec({
|
|
2747
|
+
harness: selection.runtime.harness,
|
|
2748
|
+
personaId: selection.personaId,
|
|
2749
|
+
model: selection.runtime.model,
|
|
2750
|
+
systemPrompt: renderedSystemPrompt,
|
|
2751
|
+
harnessSettings: selection.runtime.harnessSettings,
|
|
2752
|
+
mcpServers: mcpResolution.servers,
|
|
2753
|
+
permissions: selection.permissions,
|
|
2754
|
+
task
|
|
2724
2755
|
});
|
|
2725
|
-
|
|
2726
|
-
|
|
2756
|
+
const childEnv = { ...callerEnv, ...(envResolution.value ?? {}), ...inputResolution.values };
|
|
2757
|
+
const cwd = process.cwd();
|
|
2758
|
+
const configWrites = [];
|
|
2759
|
+
for (const file of spec.configFiles) {
|
|
2760
|
+
assertSafeRelativePath(file.path);
|
|
2761
|
+
const target = join(cwd, file.path);
|
|
2762
|
+
const existed = existsSync(target);
|
|
2763
|
+
const previous = existed ? readFileSync(target, 'utf8') : undefined;
|
|
2764
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
2765
|
+
writeFileSync(target, file.contents, 'utf8');
|
|
2766
|
+
configWrites.push({ path: target, existed, ...(previous !== undefined ? { previous } : {}) });
|
|
2767
|
+
}
|
|
2768
|
+
const restoreConfigWrites = () => {
|
|
2769
|
+
for (const write of [...configWrites].reverse()) {
|
|
2770
|
+
if (write.existed) {
|
|
2771
|
+
writeFileSync(write.path, write.previous ?? '', 'utf8');
|
|
2772
|
+
}
|
|
2773
|
+
else {
|
|
2774
|
+
rmSync(write.path, { force: true });
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
};
|
|
2778
|
+
const timeoutMs = selection.runtime.harnessSettings.timeoutSeconds
|
|
2779
|
+
? selection.runtime.harnessSettings.timeoutSeconds * 1000
|
|
2780
|
+
: undefined;
|
|
2781
|
+
let captureResult;
|
|
2782
|
+
try {
|
|
2783
|
+
captureResult = await new Promise((resolveResult) => {
|
|
2784
|
+
const child = spawn(spec.bin, [...spec.args], {
|
|
2785
|
+
cwd,
|
|
2786
|
+
env: childEnv,
|
|
2787
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
2788
|
+
shell: false
|
|
2789
|
+
});
|
|
2790
|
+
let stderrBuf = '';
|
|
2791
|
+
let forceKillTimeout;
|
|
2792
|
+
child.stdout?.setEncoding('utf8');
|
|
2793
|
+
child.stderr?.setEncoding('utf8');
|
|
2794
|
+
child.stderr?.on('data', (chunk) => {
|
|
2795
|
+
stderrBuf += chunk;
|
|
2796
|
+
});
|
|
2797
|
+
// SIGTERM first; if the harness traps or ignores it, escalate to
|
|
2798
|
+
// SIGKILL after a 1s grace so the timeout is actually enforced.
|
|
2799
|
+
const timeout = timeoutMs !== undefined
|
|
2800
|
+
? setTimeout(() => {
|
|
2801
|
+
child.kill('SIGTERM');
|
|
2802
|
+
forceKillTimeout = setTimeout(() => {
|
|
2803
|
+
if (!child.killed)
|
|
2804
|
+
child.kill('SIGKILL');
|
|
2805
|
+
}, 1000);
|
|
2806
|
+
}, timeoutMs)
|
|
2807
|
+
: undefined;
|
|
2808
|
+
const clearTimers = () => {
|
|
2809
|
+
if (timeout)
|
|
2810
|
+
clearTimeout(timeout);
|
|
2811
|
+
if (forceKillTimeout)
|
|
2812
|
+
clearTimeout(forceKillTimeout);
|
|
2813
|
+
};
|
|
2814
|
+
child.on('error', (err) => {
|
|
2815
|
+
clearTimers();
|
|
2816
|
+
resolveResult({ exitCode: 1, stderr: `${stderrBuf}${err.message}\n` });
|
|
2817
|
+
});
|
|
2818
|
+
child.on('close', (code, signal) => {
|
|
2819
|
+
clearTimers();
|
|
2820
|
+
const exitCode = typeof code === 'number' ? code : signal ? signalExitCode(signal) : null;
|
|
2821
|
+
resolveResult({ exitCode, stderr: stderrBuf });
|
|
2822
|
+
});
|
|
2823
|
+
});
|
|
2824
|
+
}
|
|
2825
|
+
finally {
|
|
2826
|
+
// Always restore — a synchronous spawn() throw or unexpected promise
|
|
2827
|
+
// rejection must not leave orphaned `opencode.json` (or any other
|
|
2828
|
+
// configFile) sitting in the user's working directory.
|
|
2829
|
+
restoreConfigWrites();
|
|
2830
|
+
}
|
|
2831
|
+
if (captureResult.exitCode !== 0) {
|
|
2832
|
+
throw new Error(`improver exited with code=${captureResult.exitCode ?? 'null'}.${captureResult.stderr ? ` stderr: ${captureResult.stderr.slice(0, 400)}` : ''}`);
|
|
2727
2833
|
}
|
|
2728
2834
|
let raw;
|
|
2729
2835
|
try {
|
|
@@ -2949,6 +3055,55 @@ function applyPatchInPlace(root, patch) {
|
|
|
2949
3055
|
// op === 'set'
|
|
2950
3056
|
cursor[finalSeg] = patch.value;
|
|
2951
3057
|
}
|
|
3058
|
+
/**
|
|
3059
|
+
* Enumerate personas for the interactive TUI. Source label mirrors the cascade
|
|
3060
|
+
* shown by `agentworkforce list` so the picker tells the user *where* a
|
|
3061
|
+
* persona is coming from (cwd, user, dir:n, library) without a separate
|
|
3062
|
+
* lookup.
|
|
3063
|
+
*/
|
|
3064
|
+
export function buildTuiCandidates() {
|
|
3065
|
+
const byId = new Map();
|
|
3066
|
+
for (const spec of listBuiltInPersonas()) {
|
|
3067
|
+
byId.set(spec.id, { id: spec.id, description: spec.description, source: 'library' });
|
|
3068
|
+
}
|
|
3069
|
+
for (const [id, spec] of local.byId.entries()) {
|
|
3070
|
+
byId.set(id, {
|
|
3071
|
+
id,
|
|
3072
|
+
description: spec.description,
|
|
3073
|
+
source: local.sources.get(id) ?? 'library'
|
|
3074
|
+
});
|
|
3075
|
+
}
|
|
3076
|
+
return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
3077
|
+
}
|
|
3078
|
+
/**
|
|
3079
|
+
* Bare-invocation flow: open the interactive TUI, then hand the chosen
|
|
3080
|
+
* persona to {@link runAgentSelector}. Quitting the picker (Esc / Ctrl-C)
|
|
3081
|
+
* exits with conventional 130 so shell pipelines see SIGINT-style failure.
|
|
3082
|
+
*
|
|
3083
|
+
* runAgentSelector terminates the process via process.exit; this function
|
|
3084
|
+
* only returns when the picker is dismissed without a selection.
|
|
3085
|
+
*/
|
|
3086
|
+
async function runInteractivePicker() {
|
|
3087
|
+
const candidates = buildTuiCandidates();
|
|
3088
|
+
if (candidates.length === 0) {
|
|
3089
|
+
process.stderr.write('No personas available. Try `agentworkforce install <pack>` or run with --help.\n');
|
|
3090
|
+
process.exit(1);
|
|
3091
|
+
}
|
|
3092
|
+
const selected = await runPersonaPickerTui({
|
|
3093
|
+
candidates,
|
|
3094
|
+
recentIds: loadRecents()
|
|
3095
|
+
});
|
|
3096
|
+
if (!selected) {
|
|
3097
|
+
process.exit(130);
|
|
3098
|
+
}
|
|
3099
|
+
await runAgentSelector(selected, {
|
|
3100
|
+
installInRepo: false,
|
|
3101
|
+
noLaunchMetadata: false,
|
|
3102
|
+
dryRun: false
|
|
3103
|
+
});
|
|
3104
|
+
// runAgentSelector has Promise<never> return type; this is unreachable.
|
|
3105
|
+
process.exit(0);
|
|
3106
|
+
}
|
|
2952
3107
|
/**
|
|
2953
3108
|
* Enumerate persona candidates for the picker. Local overrides win over the
|
|
2954
3109
|
* built-in catalog when ids collide; the picker only needs the projection
|
|
@@ -3075,9 +3230,19 @@ async function runPick(args) {
|
|
|
3075
3230
|
export async function main() {
|
|
3076
3231
|
const argv = process.argv.slice(2);
|
|
3077
3232
|
const [subcommand, ...rest] = argv;
|
|
3078
|
-
if (
|
|
3233
|
+
if (subcommand === '-h' || subcommand === '--help') {
|
|
3079
3234
|
process.stdout.write(USAGE);
|
|
3080
|
-
process.exit(
|
|
3235
|
+
process.exit(0);
|
|
3236
|
+
}
|
|
3237
|
+
if (!subcommand) {
|
|
3238
|
+
if (process.stdin.isTTY && process.stderr.isTTY) {
|
|
3239
|
+
await runInteractivePicker();
|
|
3240
|
+
// runInteractivePicker either runAgentSelector → process.exit, or
|
|
3241
|
+
// exits itself on quit / no-match. Satisfy TS's unreachable check.
|
|
3242
|
+
process.exit(0);
|
|
3243
|
+
}
|
|
3244
|
+
process.stdout.write(USAGE);
|
|
3245
|
+
process.exit(1);
|
|
3081
3246
|
}
|
|
3082
3247
|
if (subcommand === '-v' || subcommand === '--version') {
|
|
3083
3248
|
process.stdout.write(`${CLI_VERSION}\n`);
|