@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 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/workload-router';
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
- * (harness-kit's opencode branch) emits exactly one pair, so both behaviors
26
- * are equivalent today, but "remove all" is idempotent and safer if a future
27
- * caller ever appends a second `--agent` for any reason. A trailing `--agent`
28
- * with no following value is preserved so the malformed argv surfaces at the
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":"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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
@@ -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, listBuiltInPersonas, personaCatalog, resolveSidecar, routingProfiles, useSelection } from '@agentworkforce/workload-router';
9
- import { buildInteractiveSpec, detectHarnesses, formatDropWarnings, MissingPersonaInputError, renderPersonaInputs, resolvePersonaInputs, resolveMcpServersLenient, resolveStringMapLenient, useRunnableSelection } from '@agentworkforce/harness-kit';
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
- * (harness-kit's opencode branch) emits exactly one pair, so both behaviors
479
- * are equivalent today, but "remove all" is idempotent and safer if a future
480
- * caller ever appends a second `--agent` for any reason. A trailing `--agent`
481
- * with no following value is preserved so the malformed argv surfaces at the
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: harness-kit translation. buildInteractiveSpec validates
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 ctx = useSelection(effectiveSelection, installRoot !== undefined ? { installRoot } : {});
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 ctx = useRunnableSelection(selection);
2711
- const taskLines = [
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 result = await ctx.sendMessage(taskLines.join('\n'), {
2718
- inputs: {
2719
- PERSONA_FILE_PATH: args.personaFilePath,
2720
- SESSION_TRANSCRIPT_PATH: args.transcriptPath,
2721
- PROPOSALS_OUTPUT_PATH: args.proposalsOutputPath
2722
- },
2723
- timeoutSeconds: selection.runtime.harnessSettings.timeoutSeconds
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
- if (result.status !== 'completed' || (result.exitCode !== null && result.exitCode !== 0)) {
2726
- throw new Error(`improver exited with status=${result.status}, code=${result.exitCode ?? 'null'}.${result.stderr ? ` stderr: ${result.stderr.slice(0, 400)}` : ''}`);
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 (!subcommand || subcommand === '-h' || subcommand === '--help') {
3233
+ if (subcommand === '-h' || subcommand === '--help') {
3079
3234
  process.stdout.write(USAGE);
3080
- process.exit(subcommand ? 0 : 1);
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`);