@agentworkforce/cli 0.11.0 → 0.13.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 +12 -0
- package/dist/cli.d.ts +26 -4
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +168 -15
- package/dist/cli.js.map +1 -1
- package/dist/cli.test.js +92 -15
- package/dist/cli.test.js.map +1 -1
- package/dist/persona-picker.d.ts +66 -0
- package/dist/persona-picker.d.ts.map +1 -0
- package/dist/persona-picker.js +152 -0
- package/dist/persona-picker.js.map +1 -0
- package/dist/persona-picker.test.d.ts +2 -0
- package/dist/persona-picker.test.d.ts.map +1 -0
- package/dist/persona-picker.test.js +184 -0
- package/dist/persona-picker.test.js.map +1 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -17,6 +17,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
17
17
|
- Persona source config supports `defaultCreateTarget` for the implicit create target.
|
|
18
18
|
- `--version`/`-v` now prints the CLI package version.
|
|
19
19
|
|
|
20
|
+
## [0.13.0] - 2026-05-08
|
|
21
|
+
|
|
22
|
+
### Released
|
|
23
|
+
|
|
24
|
+
- v0.13.0
|
|
25
|
+
|
|
26
|
+
## [0.12.0] - 2026-05-08
|
|
27
|
+
|
|
28
|
+
### Released
|
|
29
|
+
|
|
30
|
+
- v0.12.0
|
|
31
|
+
|
|
20
32
|
## [0.11.0] - 2026-05-08
|
|
21
33
|
|
|
22
34
|
### Released
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { type Harness, type PersonaMount, type PersonaSelection, type SidecarMdMode } from '@agentworkforce/workload-router';
|
|
3
|
+
import { type PickCandidate } from './persona-picker.js';
|
|
3
4
|
export declare const CLI_VERSION: string;
|
|
4
5
|
export declare const CREATE_SELECTOR = "persona-maker@best";
|
|
5
6
|
/**
|
|
@@ -124,10 +125,12 @@ export declare function buildSidecarBody(sidecar: ResolvedSidecar, realCwdDir: s
|
|
|
124
125
|
* Decide whether to run the interactive session inside a
|
|
125
126
|
* `@relayfile/local-mount` sandbox.
|
|
126
127
|
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
128
|
+
* All three harnesses (claude, codex, opencode) default to the mount.
|
|
129
|
+
* The mount hides CLAUDE.md / .claude / .mcp.json (claude) or the
|
|
130
|
+
* skill-install patterns + AGENTS.md (codex / opencode) so persona-supplied
|
|
131
|
+
* sidecars and any per-session writes stay sandboxed and don't leak into
|
|
132
|
+
* the user's real repo. `--install-in-repo` is the single opt-out that
|
|
133
|
+
* disengages the mount across all harnesses.
|
|
131
134
|
*
|
|
132
135
|
* Pure — no side effects, trivially testable.
|
|
133
136
|
*/
|
|
@@ -140,6 +143,25 @@ export interface PersonaInstallArgs {
|
|
|
140
143
|
overwrite: boolean;
|
|
141
144
|
}
|
|
142
145
|
export declare function parseInstallArgs(args: readonly string[]): PersonaInstallArgs;
|
|
146
|
+
/**
|
|
147
|
+
* Enumerate persona candidates for the picker. Local overrides win over the
|
|
148
|
+
* built-in catalog when ids collide; the picker only needs the projection
|
|
149
|
+
* fields ({@link PickCandidate}), not full specs.
|
|
150
|
+
*/
|
|
151
|
+
export declare function buildPickCandidates(): PickCandidate[];
|
|
152
|
+
/**
|
|
153
|
+
* Synchronous y/n prompt over /dev/tty-equivalent stdin. Default is "no" on
|
|
154
|
+
* empty input or non-y answer. Used by `pick` when the picker reports
|
|
155
|
+
* no-match in an interactive session.
|
|
156
|
+
*
|
|
157
|
+
* Test seam: callers can inject `read` so the prompt path is exercisable
|
|
158
|
+
* without a real TTY.
|
|
159
|
+
*/
|
|
160
|
+
export declare function promptYesNoSync(question: string, opts?: {
|
|
161
|
+
isTTY?: boolean;
|
|
162
|
+
write?: (chunk: string) => void;
|
|
163
|
+
read?: () => string | undefined;
|
|
164
|
+
}): boolean;
|
|
143
165
|
export declare function main(): Promise<void>;
|
|
144
166
|
export interface AgentFlags {
|
|
145
167
|
installInRepo: boolean;
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn, spawnSync } from 'node:child_process';
|
|
3
3
|
import { randomBytes } from 'node:crypto';
|
|
4
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, readSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
5
5
|
import { constants, homedir } from 'node:os';
|
|
6
6
|
import { dirname, isAbsolute, join, resolve as resolvePath } from 'node:path';
|
|
7
7
|
import { pathToFileURL } from 'node:url';
|
|
@@ -12,6 +12,7 @@ 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
|
+
import { pickPersona } from './persona-picker.js';
|
|
15
16
|
const USAGE = `Usage: agentworkforce <command> [args...]
|
|
16
17
|
|
|
17
18
|
Commands:
|
|
@@ -92,6 +93,13 @@ Commands:
|
|
|
92
93
|
1-based configurable position.
|
|
93
94
|
harness check Probe which harnesses (claude, codex, opencode) are
|
|
94
95
|
installed and runnable on this machine.
|
|
96
|
+
pick "<task>" Pick the best-fit persona for a free-text task description
|
|
97
|
+
using a cheap LLM call (Claude Haiku via the local
|
|
98
|
+
\`claude\` CLI). Prints the matched persona id to stdout
|
|
99
|
+
on success. On low confidence, prompts (TTY) to launch
|
|
100
|
+
persona-maker with the task as input, or exits non-zero
|
|
101
|
+
(non-TTY) with a hint.
|
|
102
|
+
Exit codes: 0 match, 2 no match, 3 picker unavailable.
|
|
95
103
|
|
|
96
104
|
Options:
|
|
97
105
|
-h, --help Show this help text.
|
|
@@ -116,6 +124,8 @@ Examples:
|
|
|
116
124
|
agentworkforce sources list
|
|
117
125
|
agentworkforce sources add ../my-personas --position 1
|
|
118
126
|
agentworkforce harness check
|
|
127
|
+
agentworkforce pick "review this PR for security issues"
|
|
128
|
+
agentworkforce agent "$(agentworkforce pick "fix the flaky test in foo.test.ts")"
|
|
119
129
|
`;
|
|
120
130
|
function die(msg, withUsage = true) {
|
|
121
131
|
process.stderr.write(`${msg}\n`);
|
|
@@ -526,7 +536,7 @@ export function configureGitForMount(mountDir, patterns) {
|
|
|
526
536
|
*/
|
|
527
537
|
export function loadSidecarForSelection(selection) {
|
|
528
538
|
const harness = selection.runtime.harness;
|
|
529
|
-
if (harness !== 'claude' && harness !== 'opencode')
|
|
539
|
+
if (harness !== 'claude' && harness !== 'opencode' && harness !== 'codex')
|
|
530
540
|
return {};
|
|
531
541
|
if (harness === 'claude') {
|
|
532
542
|
if (selection.claudeMdContent) {
|
|
@@ -555,6 +565,10 @@ export function loadSidecarForSelection(selection) {
|
|
|
555
565
|
}
|
|
556
566
|
return {};
|
|
557
567
|
}
|
|
568
|
+
// opencode and codex both read AGENTS.md from cwd. For codex, the mount
|
|
569
|
+
// only engages when a sidecar is declared (see decideCleanMode); without
|
|
570
|
+
// a mount the materialization warning fires. The resolution rule is
|
|
571
|
+
// identical for both harnesses here.
|
|
558
572
|
if (selection.agentsMdContent) {
|
|
559
573
|
return {
|
|
560
574
|
sidecar: {
|
|
@@ -610,15 +624,17 @@ export function buildSidecarBody(sidecar, realCwdDir) {
|
|
|
610
624
|
* Decide whether to run the interactive session inside a
|
|
611
625
|
* `@relayfile/local-mount` sandbox.
|
|
612
626
|
*
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
627
|
+
* All three harnesses (claude, codex, opencode) default to the mount.
|
|
628
|
+
* The mount hides CLAUDE.md / .claude / .mcp.json (claude) or the
|
|
629
|
+
* skill-install patterns + AGENTS.md (codex / opencode) so persona-supplied
|
|
630
|
+
* sidecars and any per-session writes stay sandboxed and don't leak into
|
|
631
|
+
* the user's real repo. `--install-in-repo` is the single opt-out that
|
|
632
|
+
* disengages the mount across all harnesses.
|
|
617
633
|
*
|
|
618
634
|
* Pure — no side effects, trivially testable.
|
|
619
635
|
*/
|
|
620
636
|
export function decideCleanMode(harness, installInRepo = false) {
|
|
621
|
-
if (harness === 'claude' || harness === 'opencode') {
|
|
637
|
+
if (harness === 'claude' || harness === 'opencode' || harness === 'codex') {
|
|
622
638
|
return { useClean: !installInRepo };
|
|
623
639
|
}
|
|
624
640
|
return { useClean: false };
|
|
@@ -626,12 +642,25 @@ export function decideCleanMode(harness, installInRepo = false) {
|
|
|
626
642
|
async function runInteractive(selection, options) {
|
|
627
643
|
const inputResolution = resolvePersonaInputs(selection.inputs, selection.inputValues, process.env);
|
|
628
644
|
const renderedSystemPrompt = renderPersonaInputs(selection.runtime.systemPrompt, inputResolution.values);
|
|
645
|
+
// Render input placeholders ($TARGET_DIR, ${CREATE_MODE}, …) inside the
|
|
646
|
+
// sidecar Content fields too. Personas that move heavy authoring guidance
|
|
647
|
+
// out of the systemPrompt and into AGENTS.md / CLAUDE.md still want
|
|
648
|
+
// their declared inputs interpolated; without this the materialized
|
|
649
|
+
// sidecar would carry literal `$TARGET_DIR` strings.
|
|
650
|
+
const renderedClaudeContent = selection.claudeMdContent !== undefined
|
|
651
|
+
? renderPersonaInputs(selection.claudeMdContent, inputResolution.values)
|
|
652
|
+
: undefined;
|
|
653
|
+
const renderedAgentsContent = selection.agentsMdContent !== undefined
|
|
654
|
+
? renderPersonaInputs(selection.agentsMdContent, inputResolution.values)
|
|
655
|
+
: undefined;
|
|
629
656
|
const effectiveSelection = {
|
|
630
657
|
...selection,
|
|
631
658
|
runtime: {
|
|
632
659
|
...selection.runtime,
|
|
633
660
|
systemPrompt: renderedSystemPrompt
|
|
634
|
-
}
|
|
661
|
+
},
|
|
662
|
+
...(renderedClaudeContent !== undefined ? { claudeMdContent: renderedClaudeContent } : {}),
|
|
663
|
+
...(renderedAgentsContent !== undefined ? { agentsMdContent: renderedAgentsContent } : {})
|
|
635
664
|
};
|
|
636
665
|
const { runtime, personaId, tier } = effectiveSelection;
|
|
637
666
|
// `installRoot` (out-of-repo skill staging via `--plugin-dir`) is currently
|
|
@@ -642,19 +671,17 @@ async function runInteractive(selection, options) {
|
|
|
642
671
|
// across the board.
|
|
643
672
|
const useClean = decideCleanMode(runtime.harness, options.installInRepo === true).useClean;
|
|
644
673
|
// Per-persona CLAUDE.md / AGENTS.md: load the author content if any. The
|
|
645
|
-
// file is materialized into the mount inside onBeforeLaunch
|
|
646
|
-
//
|
|
647
|
-
//
|
|
648
|
-
// repo and is explicitly out of scope.
|
|
674
|
+
// file is materialized into the mount inside onBeforeLaunch. Without a
|
|
675
|
+
// mount (--install-in-repo) we skip-and-warn — writing into the real cwd
|
|
676
|
+
// would pollute the user's repo and is explicitly out of scope.
|
|
649
677
|
const sidecarLookup = loadSidecarForSelection(effectiveSelection);
|
|
650
678
|
if (sidecarLookup.warning) {
|
|
651
679
|
process.stderr.write(`warning: ${sidecarLookup.warning}\n`);
|
|
652
680
|
}
|
|
653
681
|
const resolvedSidecar = useClean ? sidecarLookup.sidecar : undefined;
|
|
654
682
|
if (sidecarLookup.sidecar && !useClean) {
|
|
655
|
-
process.stderr.write(`warning: persona declares ${sidecarLookup.sidecar.mountFile} but no sandbox mount is available
|
|
656
|
-
|
|
657
|
-
`; skipping sidecar materialization to avoid writing into your repo.\n`);
|
|
683
|
+
process.stderr.write(`warning: persona declares ${sidecarLookup.sidecar.mountFile} but no sandbox mount is available ` +
|
|
684
|
+
`(--install-in-repo disengages the mount); skipping sidecar materialization to avoid writing into your repo.\n`);
|
|
658
685
|
}
|
|
659
686
|
// A session dir is needed whenever we either (a) stage skills out-of-repo
|
|
660
687
|
// via claude's installRoot, or (b) open a mount. Both engage for claude/
|
|
@@ -1779,6 +1806,129 @@ async function runAgentSelector(selector, flags, inputValues) {
|
|
|
1779
1806
|
});
|
|
1780
1807
|
process.exit(code);
|
|
1781
1808
|
}
|
|
1809
|
+
/**
|
|
1810
|
+
* Enumerate persona candidates for the picker. Local overrides win over the
|
|
1811
|
+
* built-in catalog when ids collide; the picker only needs the projection
|
|
1812
|
+
* fields ({@link PickCandidate}), not full specs.
|
|
1813
|
+
*/
|
|
1814
|
+
export function buildPickCandidates() {
|
|
1815
|
+
const byId = new Map();
|
|
1816
|
+
for (const spec of Object.values(personaCatalog)) {
|
|
1817
|
+
byId.set(spec.id, {
|
|
1818
|
+
id: spec.id,
|
|
1819
|
+
intent: spec.intent,
|
|
1820
|
+
tags: [...spec.tags],
|
|
1821
|
+
description: spec.description
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1824
|
+
for (const [id, spec] of local.byId.entries()) {
|
|
1825
|
+
byId.set(id, {
|
|
1826
|
+
id,
|
|
1827
|
+
intent: spec.intent,
|
|
1828
|
+
tags: [...spec.tags],
|
|
1829
|
+
description: spec.description
|
|
1830
|
+
});
|
|
1831
|
+
}
|
|
1832
|
+
return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
1833
|
+
}
|
|
1834
|
+
/**
|
|
1835
|
+
* Synchronous y/n prompt over /dev/tty-equivalent stdin. Default is "no" on
|
|
1836
|
+
* empty input or non-y answer. Used by `pick` when the picker reports
|
|
1837
|
+
* no-match in an interactive session.
|
|
1838
|
+
*
|
|
1839
|
+
* Test seam: callers can inject `read` so the prompt path is exercisable
|
|
1840
|
+
* without a real TTY.
|
|
1841
|
+
*/
|
|
1842
|
+
export function promptYesNoSync(question, opts = {}) {
|
|
1843
|
+
const isTTY = opts.isTTY ?? Boolean(process.stdout.isTTY && process.stdin.isTTY);
|
|
1844
|
+
if (!isTTY)
|
|
1845
|
+
return false;
|
|
1846
|
+
const write = opts.write ?? ((chunk) => {
|
|
1847
|
+
process.stderr.write(chunk);
|
|
1848
|
+
});
|
|
1849
|
+
write(question);
|
|
1850
|
+
const answer = opts.read ? opts.read() : readLineFromStdinSync();
|
|
1851
|
+
if (!answer)
|
|
1852
|
+
return false;
|
|
1853
|
+
const normalized = answer.trim().toLowerCase();
|
|
1854
|
+
return normalized === 'y' || normalized === 'yes';
|
|
1855
|
+
}
|
|
1856
|
+
function readLineFromStdinSync() {
|
|
1857
|
+
const buf = Buffer.alloc(256);
|
|
1858
|
+
let line = '';
|
|
1859
|
+
for (;;) {
|
|
1860
|
+
let n;
|
|
1861
|
+
try {
|
|
1862
|
+
n = readSync(0, buf, 0, buf.length, null);
|
|
1863
|
+
}
|
|
1864
|
+
catch {
|
|
1865
|
+
return line || undefined;
|
|
1866
|
+
}
|
|
1867
|
+
if (n <= 0)
|
|
1868
|
+
return line || undefined;
|
|
1869
|
+
const chunk = buf.subarray(0, n).toString('utf8');
|
|
1870
|
+
const newlineIdx = chunk.indexOf('\n');
|
|
1871
|
+
if (newlineIdx === -1) {
|
|
1872
|
+
line += chunk;
|
|
1873
|
+
continue;
|
|
1874
|
+
}
|
|
1875
|
+
line += chunk.slice(0, newlineIdx);
|
|
1876
|
+
return line;
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
async function runPick(args) {
|
|
1880
|
+
const positional = [];
|
|
1881
|
+
for (const arg of args) {
|
|
1882
|
+
if (arg === '-h' || arg === '--help') {
|
|
1883
|
+
process.stdout.write('Usage: agentworkforce pick "<task description>"\n' +
|
|
1884
|
+
' Prints the best-fit persona id to stdout. On low confidence, prompts to\n' +
|
|
1885
|
+
' open persona-maker (TTY) or exits 2 (non-TTY). Exits 3 if `claude` is\n' +
|
|
1886
|
+
' not installed.\n');
|
|
1887
|
+
process.exit(0);
|
|
1888
|
+
}
|
|
1889
|
+
positional.push(arg);
|
|
1890
|
+
}
|
|
1891
|
+
if (positional.length === 0) {
|
|
1892
|
+
die('pick: missing task description. Usage: agentworkforce pick "<task>"');
|
|
1893
|
+
}
|
|
1894
|
+
if (positional.length > 1) {
|
|
1895
|
+
die(`pick: expected a single quoted task description, got ${positional.length} arguments. ` +
|
|
1896
|
+
`Did you forget to quote the task? Try: agentworkforce pick "${positional.join(' ')}"`);
|
|
1897
|
+
}
|
|
1898
|
+
const task = positional[0].trim();
|
|
1899
|
+
if (!task) {
|
|
1900
|
+
die('pick: task description is empty.');
|
|
1901
|
+
}
|
|
1902
|
+
const candidates = buildPickCandidates();
|
|
1903
|
+
const result = pickPersona(task, candidates);
|
|
1904
|
+
if (result.kind === 'match') {
|
|
1905
|
+
process.stderr.write(`• picked ${result.personaId} (${result.confidence}): ${result.reason}\n`);
|
|
1906
|
+
process.stdout.write(`${result.personaId}\n`);
|
|
1907
|
+
process.exit(0);
|
|
1908
|
+
}
|
|
1909
|
+
if (result.kind === 'picker-unavailable') {
|
|
1910
|
+
process.stderr.write(`pick: ${result.message}\n`);
|
|
1911
|
+
process.exit(3);
|
|
1912
|
+
}
|
|
1913
|
+
// no-match
|
|
1914
|
+
process.stderr.write(`pick: no close persona match — ${result.reason}\n`);
|
|
1915
|
+
const wantsCreate = promptYesNoSync('Open persona-maker to scaffold a new persona for this task? [y/N] ');
|
|
1916
|
+
if (!wantsCreate) {
|
|
1917
|
+
process.stderr.write('Try: agentworkforce list # to browse existing personas\n' +
|
|
1918
|
+
' agentworkforce create # to author a new persona\n');
|
|
1919
|
+
process.exit(2);
|
|
1920
|
+
}
|
|
1921
|
+
const target = resolveCreateTarget(undefined);
|
|
1922
|
+
ensureCreateTargetDir(target);
|
|
1923
|
+
const inputValues = {
|
|
1924
|
+
...buildCreateInputValues(target),
|
|
1925
|
+
TASK_DESCRIPTION: task
|
|
1926
|
+
};
|
|
1927
|
+
await runAgentSelector(CREATE_SELECTOR, { installInRepo: false, noLaunchMetadata: false }, inputValues);
|
|
1928
|
+
// runAgentSelector terminates via process.exit; this satisfies TS's
|
|
1929
|
+
// reachable-end-point check for the `Promise<never>` return type.
|
|
1930
|
+
process.exit(0);
|
|
1931
|
+
}
|
|
1782
1932
|
export async function main() {
|
|
1783
1933
|
const argv = process.argv.slice(2);
|
|
1784
1934
|
const [subcommand, ...rest] = argv;
|
|
@@ -1819,6 +1969,9 @@ export async function main() {
|
|
|
1819
1969
|
const { flags, selector, inputValues } = parseCreateArgs(rest);
|
|
1820
1970
|
await runAgentSelector(selector, flags, inputValues);
|
|
1821
1971
|
}
|
|
1972
|
+
if (subcommand === 'pick') {
|
|
1973
|
+
await runPick(rest);
|
|
1974
|
+
}
|
|
1822
1975
|
if (subcommand !== 'agent') {
|
|
1823
1976
|
die(`Unknown subcommand "${subcommand}".`);
|
|
1824
1977
|
}
|