@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 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
- * - Claude / opencode: mount engages by default. Hides CLAUDE.md / .claude
128
- * / .mcp.json (claude) and routes `npx prpm install` / `npx skills add`
129
- * writes (both) into the sandbox. Disengage with `--install-in-repo`.
130
- * - Codex: no sandbox mount support, always runs against the real cwd.
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":"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"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAiBA,OAAO,EAQL,KAAK,OAAO,EAEZ,KAAK,YAAY,EACjB,KAAK,gBAAgB,EAIrB,KAAK,aAAa,EACnB,MAAM,iCAAiC,CAAC;AA6BzC,OAAO,EAAe,KAAK,aAAa,EAAmB,MAAM,qBAAqB,CAAC;AAqIvF,eAAO,MAAM,WAAW,QAAuB,CAAC;AAChD,eAAO,MAAM,eAAe,uBAAuB,CAAC;AAmDpD;;;;;;;;;;GAUG;AACH,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAExF;AAyJD;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAUhE;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAe5D;AAED;;;kDAGkD;AAClD,eAAO,MAAM,sBAAsB,gFAUzB,CAAC;AAEX;;;;;;;;;GASG;AACH,eAAO,MAAM,8BAA8B,2JAiBjC,CAAC;AAEX,MAAM,WAAW,sBAAsB;IACrC,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC,GAAG,sBAAsB,CAqBzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAU7E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAuCxF;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,SAAS,EAAE,WAAW,GAAG,WAAW,CAAC;IACrC,uFAAuF;IACvF,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,aAAa,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,gBAAgB,GAC1B;IAAE,OAAO,CAAC,EAAE,eAAe,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAyDjD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,eAAe,EACxB,UAAU,EAAE,MAAM,GACjB,MAAM,CAkBR;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,EAChB,aAAa,UAAQ,GACpB;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,CAKvB;AA8pBD,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,kBAAkB,CA+B5E;AAqmBD;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,aAAa,EAAE,CAmBrD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE;IACJ,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;CAC5B,GACL,OAAO,CAWT;AAiGD,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAiE1C;AAED,MAAM,WAAW,UAAU;IACzB,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,WAAY,SAAQ,UAAU;IAC7C,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG;IACvD,KAAK,EAAE,UAAU,CAAC;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB,CA4BA;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG;IACxD,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC,CAgEA"}
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
- * - Claude / opencode: mount engages by default. Hides CLAUDE.md / .claude
614
- * / .mcp.json (claude) and routes `npx prpm install` / `npx skills add`
615
- * writes (both) into the sandbox. Disengage with `--install-in-repo`.
616
- * - Codex: no sandbox mount support, always runs against the real cwd.
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 (claude/
646
- // opencode default). Without a mount (codex, --install-in-repo) we
647
- // skip-and-warn writing into the real cwd would pollute the user's
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
- `${runtime.harness === 'codex' ? 'codex harness has no mount' : '--install-in-repo disengages the mount'})` +
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
  }