@agentworkforce/cli 0.10.0 → 0.12.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
@@ -9,11 +9,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Added
11
11
 
12
+ - `agentworkforce agent` now records launch metadata for direct harness
13
+ launches; opt out with `--no-launch-metadata` or
14
+ `AGENTWORKFORCE_LAUNCH_METADATA=0`.
12
15
  - `agentworkforce create` now opens `persona-maker@best`, supports `--to` and
13
16
  `--save-default`, and passes `TARGET_DIR` / `CREATE_MODE` persona inputs.
14
17
  - Persona source config supports `defaultCreateTarget` for the implicit create target.
15
18
  - `--version`/`-v` now prints the CLI package version.
16
19
 
20
+ ## [0.12.0] - 2026-05-08
21
+
22
+ ### Released
23
+
24
+ - v0.12.0
25
+
26
+ ## [0.11.0] - 2026-05-08
27
+
28
+ ### Released
29
+
30
+ - v0.11.0
31
+
17
32
  ## [0.10.0] - 2026-05-08
18
33
 
19
34
  ### Released
package/README.md CHANGED
@@ -6,8 +6,8 @@ built-in one from `/personas/`, or an installed/local one that extends a lower
6
6
  source.
7
7
 
8
8
  ```
9
- agentworkforce create [--to <target>] [--save-default]
10
- agentworkforce agent <persona>[@<tier>]
9
+ agentworkforce create [--to <target>] [--save-default] [--install-in-repo] [--no-launch-metadata]
10
+ agentworkforce agent [--install-in-repo] [--no-launch-metadata] <persona>[@<tier>]
11
11
  agentworkforce list [flags]
12
12
  agentworkforce show <persona>[@<tier>]
13
13
  agentworkforce install [flags] <pkg|path>
@@ -68,7 +68,7 @@ Unknown persona prints the full catalog with each entry's origin.
68
68
  ## Create
69
69
 
70
70
  ```
71
- agentworkforce create [--to <target>] [--save-default] [--install-in-repo]
71
+ agentworkforce create [--to <target>] [--save-default] [--install-in-repo] [--no-launch-metadata]
72
72
  ```
73
73
 
74
74
  `create` is the persona-authoring entry point. It runs `persona-maker@best`
@@ -860,7 +860,7 @@ persona session, add it to the persona's `mcpServers` block.
860
860
  ## Interactive
861
861
 
862
862
  ```sh
863
- agentworkforce agent [--install-in-repo] <persona>[@<tier>]
863
+ agentworkforce agent [--install-in-repo] [--no-launch-metadata] <persona>[@<tier>]
864
864
  ```
865
865
 
866
866
  By default, claude and opencode sessions run inside a sandbox mount — see
@@ -887,10 +887,34 @@ By default, claude and opencode sessions run inside a sandbox mount — see
887
887
  initial argument.
888
888
  5. Runs the skill cleanup command on exit, regardless of exit status. In
889
889
  stage-dir mode this is a single `rm -rf <stage-dir>`.
890
- 6. Propagates the harness's exit code.
890
+ 6. Records launch metadata for the session and refreshes harness session logs
891
+ while the child runs, then once more after exit. The harness is still
892
+ launched directly.
893
+ 7. Propagates the harness's exit code.
891
894
 
892
895
  Signals (SIGINT, SIGTERM) are forwarded to the child.
893
896
 
897
+ ### Launch Metadata
898
+
899
+ Persona launches record metadata by default when the installed backend supports
900
+ launcher metadata. AgentWorkforce records:
901
+ `agentworkforce=1`, `persona=<id>`, `personaTier=<tier>`,
902
+ `personaVersion=<sha256>`, and `personaSource=<cwd|user|dir:n|library>`.
903
+ `personaVersion` is the SHA-256 of the fully resolved persona spec after
904
+ cascade/extends merge and before prompt input substitution.
905
+
906
+ Opt out for one launch:
907
+
908
+ ```sh
909
+ agentworkforce agent --no-launch-metadata code-reviewer@best
910
+ ```
911
+
912
+ Opt out through the environment:
913
+
914
+ ```sh
915
+ AGENTWORKFORCE_LAUNCH_METADATA=0 agentworkforce agent code-reviewer@best
916
+ ```
917
+
894
918
  ## Skill staging
895
919
 
896
920
  By default, interactive `claude` sessions stage skills under the user's home
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
  /**
@@ -140,9 +141,29 @@ export interface PersonaInstallArgs {
140
141
  overwrite: boolean;
141
142
  }
142
143
  export declare function parseInstallArgs(args: readonly string[]): PersonaInstallArgs;
144
+ /**
145
+ * Enumerate persona candidates for the picker. Local overrides win over the
146
+ * built-in catalog when ids collide; the picker only needs the projection
147
+ * fields ({@link PickCandidate}), not full specs.
148
+ */
149
+ export declare function buildPickCandidates(): PickCandidate[];
150
+ /**
151
+ * Synchronous y/n prompt over /dev/tty-equivalent stdin. Default is "no" on
152
+ * empty input or non-y answer. Used by `pick` when the picker reports
153
+ * no-match in an interactive session.
154
+ *
155
+ * Test seam: callers can inject `read` so the prompt path is exercisable
156
+ * without a real TTY.
157
+ */
158
+ export declare function promptYesNoSync(question: string, opts?: {
159
+ isTTY?: boolean;
160
+ write?: (chunk: string) => void;
161
+ read?: () => string | undefined;
162
+ }): boolean;
143
163
  export declare function main(): Promise<void>;
144
164
  export interface AgentFlags {
145
165
  installInRepo: boolean;
166
+ noLaunchMetadata: boolean;
146
167
  }
147
168
  export interface CreateFlags extends AgentFlags {
148
169
  to?: string;
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":"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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';
@@ -9,8 +9,10 @@ import { HARNESS_VALUES, PERSONA_TAGS, PERSONA_TIERS, personaCatalog, resolveSid
9
9
  import { buildInteractiveSpec, detectHarnesses, formatDropWarnings, MissingPersonaInputError, renderPersonaInputs, resolvePersonaInputs, resolveMcpServersLenient, resolveStringMapLenient } from '@agentworkforce/harness-kit';
10
10
  import { launchOnMount, readAgentDotfiles } from '@relayfile/local-mount';
11
11
  import ora from 'ora';
12
+ import { startLaunchMetadataRecording } from './launch-metadata.js';
12
13
  import { buildPersonaSourceDirectories, defaultCwdPersonaDir, loadLocalPersonas, loadPersonaSourceConfig, normalizePersonaDir, savePersonaSourceConfig } from './local-personas.js';
13
14
  import { installPersonas } from './persona-install.js';
15
+ import { pickPersona } from './persona-picker.js';
14
16
  const USAGE = `Usage: agentworkforce <command> [args...]
15
17
 
16
18
  Commands:
@@ -26,6 +28,8 @@ Commands:
26
28
  --save-default Persist --to as defaultCreateTarget in
27
29
  ~/.agentworkforce/workforce/config.json.
28
30
  --install-in-repo Same behavior as agent.
31
+ --no-launch-metadata
32
+ Same behavior as agent.
29
33
  agent [flags] <persona>[@<tier>]
30
34
  Run a persona. Tier one of: ${PERSONA_TIERS.join(' | ')}
31
35
  (default: best-value). Drops into an interactive harness
@@ -46,6 +50,10 @@ Commands:
46
50
  are hidden from the session. Codex
47
51
  sessions never mount and ignore
48
52
  this flag.
53
+ --no-launch-metadata
54
+ Disable launch metadata recording.
55
+ Also disabled by
56
+ AGENTWORKFORCE_LAUNCH_METADATA=0.
49
57
  list [flags] List available personas from the cascade (cwd →
50
58
  configured persona dirs → library). By default shows
51
59
  one row per persona at the recommended tier for its
@@ -85,6 +93,13 @@ Commands:
85
93
  1-based configurable position.
86
94
  harness check Probe which harnesses (claude, codex, opencode) are
87
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.
88
103
 
89
104
  Options:
90
105
  -h, --help Show this help text.
@@ -109,6 +124,8 @@ Examples:
109
124
  agentworkforce sources list
110
125
  agentworkforce sources add ../my-personas --position 1
111
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")"
112
129
  `;
113
130
  function die(msg, withUsage = true) {
114
131
  process.stderr.write(`${msg}\n`);
@@ -165,7 +182,10 @@ function parseSelector(sel) {
165
182
  if ('error' in result)
166
183
  die(result.error, false);
167
184
  const kind = local.byId.has(key) ? 'local' : 'repo';
168
- return { kind, spec: result, tier };
185
+ if (kind === 'local') {
186
+ return { kind, source: local.sources.get(result.id) ?? 'cwd', spec: result, tier };
187
+ }
188
+ return { kind, source: 'library', spec: result, tier };
169
189
  }
170
190
  /**
171
191
  * Resolve the `<harness>` placeholder used in persona systemPrompts.
@@ -613,7 +633,7 @@ export function decideCleanMode(harness, installInRepo = false) {
613
633
  }
614
634
  return { useClean: false };
615
635
  }
616
- async function runInteractive(selection, options = {}) {
636
+ async function runInteractive(selection, options) {
617
637
  const inputResolution = resolvePersonaInputs(selection.inputs, selection.inputValues, process.env);
618
638
  const renderedSystemPrompt = renderPersonaInputs(selection.runtime.systemPrompt, inputResolution.values);
619
639
  const effectiveSelection = {
@@ -657,6 +677,14 @@ async function runInteractive(selection, options = {}) {
657
677
  const ctx = useSelection(effectiveSelection, installRoot !== undefined ? { installRoot } : {});
658
678
  const { install } = ctx;
659
679
  process.stderr.write(`→ ${personaId} [${tier}] via ${runtime.harness} (${runtime.model})\n`);
680
+ const startLaunchMetadataForLaunch = (cwd = process.cwd()) => startLaunchMetadataRecording({
681
+ selection: effectiveSelection,
682
+ personaSpec: options.personaSpec,
683
+ personaSource: options.personaSource,
684
+ cwd,
685
+ noLaunchMetadata: options.noLaunchMetadata,
686
+ env: process.env
687
+ });
660
688
  const inputEnv = inputResolution.values;
661
689
  const callerEnv = { ...process.env, ...inputEnv };
662
690
  const envResolution = resolveStringMapLenient(effectiveSelection.env, callerEnv, 'env');
@@ -754,6 +782,7 @@ async function runInteractive(selection, options = {}) {
754
782
  // copied in from the real repo nor synced back on exit.
755
783
  if (useClean && sessionRoot) {
756
784
  const mountDir = sessionMountDir(sessionRoot);
785
+ let launchMetadata;
757
786
  // Anything we materialize into the mount via onBeforeLaunch must be
758
787
  // hidden from the mount-mirror in both directions: without this, any
759
788
  // opencode.json already present in the real repo would be copied into
@@ -874,7 +903,7 @@ async function runInteractive(selection, options = {}) {
874
903
  process.stderr.write(`✓ ${message}\n`);
875
904
  }
876
905
  },
877
- onBeforeLaunch: (dir) => {
906
+ onBeforeLaunch: async (dir) => {
878
907
  // Run before install / configFile writes so the freshly written
879
908
  // files (e.g. `.opencode/`, `opencode.json`) aren't yet present
880
909
  // when we run `git ls-files` to pick skip-worktree candidates —
@@ -897,6 +926,7 @@ async function runInteractive(selection, options = {}) {
897
926
  const body = buildSidecarBody(resolvedSidecar, process.cwd());
898
927
  writeFileSync(join(dir, resolvedSidecar.mountFile), body, 'utf8');
899
928
  }
929
+ launchMetadata = await startLaunchMetadataForLaunch(dir);
900
930
  }
901
931
  });
902
932
  return result.exitCode;
@@ -931,6 +961,7 @@ async function runInteractive(selection, options = {}) {
931
961
  syncSpinner.stop();
932
962
  syncSpinner = undefined;
933
963
  }
964
+ await launchMetadata?.stop();
934
965
  process.removeListener('SIGINT', forceExitHandler);
935
966
  // When the install ran inside the mount, its cleanup paths are
936
967
  // mount-relative (e.g. `.skills/<name>`, `skills/<name>`) and
@@ -944,6 +975,7 @@ async function runInteractive(selection, options = {}) {
944
975
  removeSessionRoot(sessionRoot);
945
976
  }
946
977
  }
978
+ const launchMetadata = await startLaunchMetadataForLaunch();
947
979
  return new Promise((resolve) => {
948
980
  let settled = false;
949
981
  const finish = (code) => {
@@ -952,7 +984,7 @@ async function runInteractive(selection, options = {}) {
952
984
  settled = true;
953
985
  runCleanup(install.cleanupCommand, install.cleanupCommandString);
954
986
  removeSessionRoot(sessionRoot);
955
- resolve(code);
987
+ void launchMetadata.stop().finally(() => resolve(code));
956
988
  };
957
989
  const child = spawn(spec.bin, finalArgs, {
958
990
  stdio: 'inherit',
@@ -1750,10 +1782,136 @@ async function runAgentSelector(selector, flags, inputValues) {
1750
1782
  ...(inputValues ? { inputValues } : {})
1751
1783
  };
1752
1784
  const code = await runInteractive(selection, {
1753
- installInRepo: flags.installInRepo
1785
+ installInRepo: flags.installInRepo,
1786
+ noLaunchMetadata: flags.noLaunchMetadata,
1787
+ personaSpec: target.spec,
1788
+ personaSource: target.source
1754
1789
  });
1755
1790
  process.exit(code);
1756
1791
  }
1792
+ /**
1793
+ * Enumerate persona candidates for the picker. Local overrides win over the
1794
+ * built-in catalog when ids collide; the picker only needs the projection
1795
+ * fields ({@link PickCandidate}), not full specs.
1796
+ */
1797
+ export function buildPickCandidates() {
1798
+ const byId = new Map();
1799
+ for (const spec of Object.values(personaCatalog)) {
1800
+ byId.set(spec.id, {
1801
+ id: spec.id,
1802
+ intent: spec.intent,
1803
+ tags: [...spec.tags],
1804
+ description: spec.description
1805
+ });
1806
+ }
1807
+ for (const [id, spec] of local.byId.entries()) {
1808
+ byId.set(id, {
1809
+ id,
1810
+ intent: spec.intent,
1811
+ tags: [...spec.tags],
1812
+ description: spec.description
1813
+ });
1814
+ }
1815
+ return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
1816
+ }
1817
+ /**
1818
+ * Synchronous y/n prompt over /dev/tty-equivalent stdin. Default is "no" on
1819
+ * empty input or non-y answer. Used by `pick` when the picker reports
1820
+ * no-match in an interactive session.
1821
+ *
1822
+ * Test seam: callers can inject `read` so the prompt path is exercisable
1823
+ * without a real TTY.
1824
+ */
1825
+ export function promptYesNoSync(question, opts = {}) {
1826
+ const isTTY = opts.isTTY ?? Boolean(process.stdout.isTTY && process.stdin.isTTY);
1827
+ if (!isTTY)
1828
+ return false;
1829
+ const write = opts.write ?? ((chunk) => {
1830
+ process.stderr.write(chunk);
1831
+ });
1832
+ write(question);
1833
+ const answer = opts.read ? opts.read() : readLineFromStdinSync();
1834
+ if (!answer)
1835
+ return false;
1836
+ const normalized = answer.trim().toLowerCase();
1837
+ return normalized === 'y' || normalized === 'yes';
1838
+ }
1839
+ function readLineFromStdinSync() {
1840
+ const buf = Buffer.alloc(256);
1841
+ let line = '';
1842
+ for (;;) {
1843
+ let n;
1844
+ try {
1845
+ n = readSync(0, buf, 0, buf.length, null);
1846
+ }
1847
+ catch {
1848
+ return line || undefined;
1849
+ }
1850
+ if (n <= 0)
1851
+ return line || undefined;
1852
+ const chunk = buf.subarray(0, n).toString('utf8');
1853
+ const newlineIdx = chunk.indexOf('\n');
1854
+ if (newlineIdx === -1) {
1855
+ line += chunk;
1856
+ continue;
1857
+ }
1858
+ line += chunk.slice(0, newlineIdx);
1859
+ return line;
1860
+ }
1861
+ }
1862
+ async function runPick(args) {
1863
+ const positional = [];
1864
+ for (const arg of args) {
1865
+ if (arg === '-h' || arg === '--help') {
1866
+ process.stdout.write('Usage: agentworkforce pick "<task description>"\n' +
1867
+ ' Prints the best-fit persona id to stdout. On low confidence, prompts to\n' +
1868
+ ' open persona-maker (TTY) or exits 2 (non-TTY). Exits 3 if `claude` is\n' +
1869
+ ' not installed.\n');
1870
+ process.exit(0);
1871
+ }
1872
+ positional.push(arg);
1873
+ }
1874
+ if (positional.length === 0) {
1875
+ die('pick: missing task description. Usage: agentworkforce pick "<task>"');
1876
+ }
1877
+ if (positional.length > 1) {
1878
+ die(`pick: expected a single quoted task description, got ${positional.length} arguments. ` +
1879
+ `Did you forget to quote the task? Try: agentworkforce pick "${positional.join(' ')}"`);
1880
+ }
1881
+ const task = positional[0].trim();
1882
+ if (!task) {
1883
+ die('pick: task description is empty.');
1884
+ }
1885
+ const candidates = buildPickCandidates();
1886
+ const result = pickPersona(task, candidates);
1887
+ if (result.kind === 'match') {
1888
+ process.stderr.write(`• picked ${result.personaId} (${result.confidence}): ${result.reason}\n`);
1889
+ process.stdout.write(`${result.personaId}\n`);
1890
+ process.exit(0);
1891
+ }
1892
+ if (result.kind === 'picker-unavailable') {
1893
+ process.stderr.write(`pick: ${result.message}\n`);
1894
+ process.exit(3);
1895
+ }
1896
+ // no-match
1897
+ process.stderr.write(`pick: no close persona match — ${result.reason}\n`);
1898
+ const wantsCreate = promptYesNoSync('Open persona-maker to scaffold a new persona for this task? [y/N] ');
1899
+ if (!wantsCreate) {
1900
+ process.stderr.write('Try: agentworkforce list # to browse existing personas\n' +
1901
+ ' agentworkforce create # to author a new persona\n');
1902
+ process.exit(2);
1903
+ }
1904
+ const target = resolveCreateTarget(undefined);
1905
+ ensureCreateTargetDir(target);
1906
+ const inputValues = {
1907
+ ...buildCreateInputValues(target),
1908
+ TASK_DESCRIPTION: task
1909
+ };
1910
+ await runAgentSelector(CREATE_SELECTOR, { installInRepo: false, noLaunchMetadata: false }, inputValues);
1911
+ // runAgentSelector terminates via process.exit; this satisfies TS's
1912
+ // reachable-end-point check for the `Promise<never>` return type.
1913
+ process.exit(0);
1914
+ }
1757
1915
  export async function main() {
1758
1916
  const argv = process.argv.slice(2);
1759
1917
  const [subcommand, ...rest] = argv;
@@ -1794,6 +1952,9 @@ export async function main() {
1794
1952
  const { flags, selector, inputValues } = parseCreateArgs(rest);
1795
1953
  await runAgentSelector(selector, flags, inputValues);
1796
1954
  }
1955
+ if (subcommand === 'pick') {
1956
+ await runPick(rest);
1957
+ }
1797
1958
  if (subcommand !== 'agent') {
1798
1959
  die(`Unknown subcommand "${subcommand}".`);
1799
1960
  }
@@ -1807,7 +1968,7 @@ export async function main() {
1807
1968
  await runAgentSelector(selector, flags);
1808
1969
  }
1809
1970
  export function parseAgentArgs(args) {
1810
- const flags = { installInRepo: false };
1971
+ const flags = { installInRepo: false, noLaunchMetadata: false };
1811
1972
  const positional = [];
1812
1973
  let seenDoubleDash = false;
1813
1974
  for (const arg of args) {
@@ -1823,6 +1984,10 @@ export function parseAgentArgs(args) {
1823
1984
  flags.installInRepo = true;
1824
1985
  continue;
1825
1986
  }
1987
+ if (arg === '--no-launch-metadata') {
1988
+ flags.noLaunchMetadata = true;
1989
+ continue;
1990
+ }
1826
1991
  if (arg === '-h' || arg === '--help') {
1827
1992
  process.stdout.write(USAGE);
1828
1993
  process.exit(0);
@@ -1832,7 +1997,7 @@ export function parseAgentArgs(args) {
1832
1997
  return { flags, positional };
1833
1998
  }
1834
1999
  export function parseCreateArgs(args) {
1835
- const flags = { installInRepo: false, saveDefault: false };
2000
+ const flags = { installInRepo: false, noLaunchMetadata: false, saveDefault: false };
1836
2001
  let seenDoubleDash = false;
1837
2002
  const positional = [];
1838
2003
  const valueOf = (i, flag) => {
@@ -1856,6 +2021,10 @@ export function parseCreateArgs(args) {
1856
2021
  flags.installInRepo = true;
1857
2022
  continue;
1858
2023
  }
2024
+ if (arg === '--no-launch-metadata') {
2025
+ flags.noLaunchMetadata = true;
2026
+ continue;
2027
+ }
1859
2028
  if (arg === '--to') {
1860
2029
  flags.to = valueOf(i, arg);
1861
2030
  i += 1;
@@ -1866,7 +2035,7 @@ export function parseCreateArgs(args) {
1866
2035
  continue;
1867
2036
  }
1868
2037
  if (arg === '-h' || arg === '--help') {
1869
- process.stdout.write('Usage: agentworkforce create [--to <cwd|user|dir:n|library|path>] [--save-default] [--install-in-repo]\n');
2038
+ process.stdout.write('Usage: agentworkforce create [--to <cwd|user|dir:n|library|path>] [--save-default] [--install-in-repo] [--no-launch-metadata]\n');
1870
2039
  process.exit(0);
1871
2040
  }
1872
2041
  positional.push(arg);