@agentworkforce/cli 0.6.1 → 0.8.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/dist/cli.js CHANGED
@@ -1,18 +1,31 @@
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, rmSync, statSync, writeFileSync } from 'node:fs';
4
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, 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';
8
8
  import { HARNESS_VALUES, PERSONA_TAGS, PERSONA_TIERS, personaCatalog, routingProfiles, useSelection } from '@agentworkforce/workload-router';
9
- import { buildInteractiveSpec, detectHarnesses, formatDropWarnings, resolveMcpServersLenient, resolveStringMapLenient } from '@agentworkforce/harness-kit';
9
+ import { buildInteractiveSpec, detectHarnesses, formatDropWarnings, MissingPersonaInputError, renderPersonaInputs, resolvePersonaInputs, resolveMcpServersLenient, resolveStringMapLenient } from '@agentworkforce/harness-kit';
10
10
  import { launchOnMount } from '@relayfile/local-mount';
11
11
  import ora from 'ora';
12
- import { buildPersonaSourceDirectories, loadLocalPersonas, loadPersonaSourceConfig, normalizePersonaDir, savePersonaSourceConfig } from './local-personas.js';
12
+ import { buildPersonaSourceDirectories, defaultCwdPersonaDir, loadLocalPersonas, loadPersonaSourceConfig, normalizePersonaDir, savePersonaSourceConfig } from './local-personas.js';
13
+ import { installPersonas } from './persona-install.js';
13
14
  const USAGE = `Usage: agentworkforce <command> [args...]
14
15
 
15
16
  Commands:
17
+ create [flags] Opens persona-maker@best for creating a new
18
+ persona, with target path passed as persona inputs.
19
+ Flags:
20
+ --to <target> Storage target: cwd, user, dir:n,
21
+ library, or an explicit path.
22
+ Default: cwd when
23
+ .agentworkforce/workforce exists,
24
+ otherwise config defaultCreateTarget,
25
+ otherwise user.
26
+ --save-default Persist --to as defaultCreateTarget in
27
+ ~/.agentworkforce/workforce/config.json.
28
+ --install-in-repo Same behavior as agent.
16
29
  agent [flags] <persona>[@<tier>]
17
30
  Run a persona. Tier one of: ${PERSONA_TIERS.join(' | ')}
18
31
  (default: best-value). Drops into an interactive harness
@@ -55,6 +68,13 @@ Commands:
55
68
  or --all to see every tier. Flags:
56
69
  --all include every tier (overrides default)
57
70
  --json emit the resolved PersonaSpec as JSON
71
+ install [flags] <pkg|path>
72
+ Copy persona JSON files from an npm package or local
73
+ package directory into
74
+ <cwd>/.agentworkforce/workforce/personas/. Flags:
75
+ --persona <id> install only the matching persona id;
76
+ repeat to install multiple
77
+ --overwrite replace existing target files
58
78
  sources list [--json]
59
79
  List persona source directories in cascade order.
60
80
  sources add <dir> [--position <n>]
@@ -66,6 +86,10 @@ Commands:
66
86
  harness check Probe which harnesses (claude, codex, opencode) are
67
87
  installed and runnable on this machine.
68
88
 
89
+ Options:
90
+ -h, --help Show this help text.
91
+ -v, --version Print the agentworkforce version.
92
+
69
93
  Local personas cascade: <cwd>/.agentworkforce/workforce/personas/*.json → configured persona dirs → repo library.
70
94
  Each layer only needs to specify fields it overrides; everything else inherits
71
95
  from the next lower layer. "extends" explicitly names a base; omit it and the
@@ -73,11 +97,15 @@ loader implicitly inherits from the same-id persona below. By default the only
73
97
  configured persona dir is ~/.agentworkforce/workforce/personas.
74
98
 
75
99
  Examples:
100
+ agentworkforce create
101
+ agentworkforce create --to user
76
102
  agentworkforce agent npm-provenance-publisher@best
77
103
  agentworkforce agent my-posthog@best
78
104
  agentworkforce agent review@best-value
79
105
  agentworkforce list
80
106
  agentworkforce show posthog
107
+ agentworkforce install @agentrelay/personas --persona relay-orchestrator
108
+ agentworkforce install ./local-personas --overwrite
81
109
  agentworkforce sources list
82
110
  agentworkforce sources add ../my-personas --position 1
83
111
  agentworkforce harness check
@@ -88,6 +116,17 @@ function die(msg, withUsage = true) {
88
116
  process.stderr.write(`\n${USAGE}`);
89
117
  process.exit(1);
90
118
  }
119
+ function readPackageVersion() {
120
+ const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
121
+ if (typeof pkg.version !== 'string' || !pkg.version) {
122
+ throw new Error('Could not read @agentworkforce/cli package version.');
123
+ }
124
+ return pkg.version;
125
+ }
126
+ export const CLI_VERSION = readPackageVersion();
127
+ export const CREATE_SELECTOR = 'persona-maker@best';
128
+ const CREATE_INPUT_TARGET_DIR = 'TARGET_DIR';
129
+ const CREATE_INPUT_CREATE_MODE = 'CREATE_MODE';
91
130
  const local = loadLocalPersonas();
92
131
  for (const warning of local.warnings) {
93
132
  process.stderr.write(`warning: ${warning}\n`);
@@ -154,6 +193,7 @@ function buildSelection(spec, tier, kind) {
154
193
  runtime,
155
194
  skills: spec.skills,
156
195
  rationale: kind === 'local' ? `local-override: ${spec.id}` : `cli-tier-override: ${tier}`,
196
+ ...(spec.inputs ? { inputs: spec.inputs } : {}),
157
197
  ...(spec.env ? { env: spec.env } : {}),
158
198
  ...(spec.mcpServers ? { mcpServers: spec.mcpServers } : {}),
159
199
  ...(spec.permissions ? { permissions: spec.permissions } : {})
@@ -443,7 +483,16 @@ export function decideCleanMode(harness, installInRepo = false) {
443
483
  return { useClean: false };
444
484
  }
445
485
  async function runInteractive(selection, options = {}) {
446
- const { runtime, personaId, tier } = selection;
486
+ const inputResolution = resolvePersonaInputs(selection.inputs, selection.inputValues, process.env);
487
+ const renderedSystemPrompt = renderPersonaInputs(selection.runtime.systemPrompt, inputResolution.values);
488
+ const effectiveSelection = {
489
+ ...selection,
490
+ runtime: {
491
+ ...selection.runtime,
492
+ systemPrompt: renderedSystemPrompt
493
+ }
494
+ };
495
+ const { runtime, personaId, tier } = effectiveSelection;
447
496
  // `installRoot` (out-of-repo skill staging via `--plugin-dir`) is currently
448
497
  // claude-only; the workload-router SDK throws if it's set for other
449
498
  // harnesses. For opencode, we instead keep installs out of the repo by
@@ -459,13 +508,17 @@ async function runInteractive(selection, options = {}) {
459
508
  const installRoot = sessionRoot && runtime.harness === 'claude'
460
509
  ? sessionInstallRoot(sessionRoot)
461
510
  : undefined;
462
- const ctx = useSelection(selection, installRoot !== undefined ? { installRoot } : {});
511
+ const ctx = useSelection(effectiveSelection, installRoot !== undefined ? { installRoot } : {});
463
512
  const { install } = ctx;
464
513
  process.stderr.write(`→ ${personaId} [${tier}] via ${runtime.harness} (${runtime.model})\n`);
465
- const envResolution = resolveStringMapLenient(selection.env, process.env, 'env');
466
- const mcpResolution = resolveMcpServersLenient(selection.mcpServers, process.env);
514
+ const inputEnv = inputResolution.values;
515
+ const callerEnv = { ...process.env, ...inputEnv };
516
+ const envResolution = resolveStringMapLenient(effectiveSelection.env, callerEnv, 'env');
517
+ const mcpResolution = resolveMcpServersLenient(effectiveSelection.mcpServers, callerEnv);
467
518
  emitDropWarnings(formatDropWarnings(envResolution.dropped, mcpResolution.dropped, mcpResolution.droppedServers));
468
- const resolvedEnv = envResolution.value;
519
+ const resolvedEnv = Object.keys(inputEnv).length > 0 || envResolution.value
520
+ ? { ...(envResolution.value ?? {}), ...inputEnv }
521
+ : undefined;
469
522
  const resolvedMcp = mcpResolution.servers;
470
523
  // In session mode the install command is never `:` — it at minimum runs
471
524
  // the plugin scaffold (mkdir + manifest + symlink) so `--plugin-dir` has a
@@ -489,7 +542,7 @@ async function runInteractive(selection, options = {}) {
489
542
  model: runtime.model,
490
543
  systemPrompt: runtime.systemPrompt,
491
544
  mcpServers: resolvedMcp,
492
- permissions: selection.permissions,
545
+ permissions: effectiveSelection.permissions,
493
546
  ...(installRoot !== undefined ? { pluginDirs: [installRoot] } : {})
494
547
  });
495
548
  for (const w of spec.warnings)
@@ -524,14 +577,14 @@ async function runInteractive(selection, options = {}) {
524
577
  if (runtime.harness === 'claude') {
525
578
  const servers = Object.keys(resolvedMcp ?? {});
526
579
  summary.push(`mcp-strict=${servers.length ? servers.join(',') : '(none)'}`);
527
- if (selection.permissions?.allow?.length) {
528
- summary.push(`allow=${selection.permissions.allow.length} rule(s)`);
580
+ if (effectiveSelection.permissions?.allow?.length) {
581
+ summary.push(`allow=${effectiveSelection.permissions.allow.length} rule(s)`);
529
582
  }
530
- if (selection.permissions?.deny?.length) {
531
- summary.push(`deny=${selection.permissions.deny.length} rule(s)`);
583
+ if (effectiveSelection.permissions?.deny?.length) {
584
+ summary.push(`deny=${effectiveSelection.permissions.deny.length} rule(s)`);
532
585
  }
533
- if (selection.permissions?.mode) {
534
- summary.push(`mode=${selection.permissions.mode}`);
586
+ if (effectiveSelection.permissions?.mode) {
587
+ summary.push(`mode=${effectiveSelection.permissions.mode}`);
535
588
  }
536
589
  }
537
590
  if (spec.initialPrompt)
@@ -802,9 +855,14 @@ function collectSourceDirRows() {
802
855
  exists: 'yes',
803
856
  dir: '(built-in)'
804
857
  });
805
- return { configPath: config.configPath, personaDirs: config.personaDirs, rows };
858
+ return {
859
+ configPath: config.configPath,
860
+ personaDirs: config.personaDirs,
861
+ ...(config.defaultCreateTarget ? { defaultCreateTarget: config.defaultCreateTarget } : {}),
862
+ rows
863
+ };
806
864
  }
807
- function formatSourcesTable(rows, configPath) {
865
+ function formatSourcesTable(rows, configPath, defaultCreateTarget) {
808
866
  const headers = {
809
867
  cascade: 'CASCADE',
810
868
  config: 'CONFIG',
@@ -817,6 +875,7 @@ function formatSourcesTable(rows, configPath) {
817
875
  const line = (row) => cols.map((c) => row[c].padEnd(widths[c])).join(' ').trimEnd();
818
876
  return [
819
877
  `Config: ${configPath}`,
878
+ `Default create target: ${defaultCreateTarget ?? '(auto)'}`,
820
879
  [line(headers), ...rows.map(line)].join('\n'),
821
880
  ''
822
881
  ].join('\n');
@@ -839,12 +898,12 @@ function parseSourcesListArgs(args) {
839
898
  }
840
899
  function runSourcesList(args) {
841
900
  const { json } = parseSourcesListArgs(args);
842
- const { configPath, personaDirs, rows } = collectSourceDirRows();
901
+ const { configPath, personaDirs, defaultCreateTarget, rows } = collectSourceDirRows();
843
902
  if (json) {
844
- process.stdout.write(JSON.stringify({ configPath, personaDirs, sources: rows }, null, 2) + '\n');
903
+ process.stdout.write(JSON.stringify({ configPath, personaDirs, defaultCreateTarget, sources: rows }, null, 2) + '\n');
845
904
  }
846
905
  else {
847
- process.stdout.write(formatSourcesTable(rows, configPath));
906
+ process.stdout.write(formatSourcesTable(rows, configPath, defaultCreateTarget));
848
907
  }
849
908
  process.exit(0);
850
909
  }
@@ -977,6 +1036,86 @@ function runSources(args) {
977
1036
  runSourcesRemove(rest);
978
1037
  die(`sources: unknown action "${action}". Expected: list, add, remove.`);
979
1038
  }
1039
+ export function parseInstallArgs(args) {
1040
+ let source;
1041
+ const personaIds = [];
1042
+ let overwrite = false;
1043
+ const valueOf = (i, flag) => {
1044
+ const v = args[i + 1];
1045
+ if (v === undefined || v.startsWith('--')) {
1046
+ throw new Error(`install: ${flag} requires a value.`);
1047
+ }
1048
+ return v;
1049
+ };
1050
+ for (let i = 0; i < args.length; i++) {
1051
+ const arg = args[i];
1052
+ if (arg === '--overwrite') {
1053
+ overwrite = true;
1054
+ }
1055
+ else if (arg === '--persona') {
1056
+ const value = valueOf(i++, arg);
1057
+ if (!value.trim())
1058
+ throw new Error('install: --persona requires a non-empty value.');
1059
+ personaIds.push(value);
1060
+ }
1061
+ else if (arg.startsWith('--')) {
1062
+ throw new Error(`install: unexpected flag "${arg}".`);
1063
+ }
1064
+ else if (source === undefined) {
1065
+ source = arg;
1066
+ }
1067
+ else {
1068
+ throw new Error(`install: unexpected argument "${arg}".`);
1069
+ }
1070
+ }
1071
+ if (!source)
1072
+ throw new Error('install: missing package or local path.');
1073
+ return { source, personaIds, overwrite };
1074
+ }
1075
+ function formatPersonaInstallSummary(result) {
1076
+ const lines = [];
1077
+ for (const persona of result.installed) {
1078
+ lines.push(`installed ${persona.id} -> ${persona.targetPath}`);
1079
+ }
1080
+ if (result.installed.length > 0) {
1081
+ lines.push(`Installed ${result.installed.length} persona(s) into ${result.targetDir}.`);
1082
+ }
1083
+ return lines.length > 0 ? lines.join('\n') + '\n' : '';
1084
+ }
1085
+ function formatPersonaInstallConflicts(result) {
1086
+ if (result.conflicts.length === 0)
1087
+ return '';
1088
+ const lines = result.conflicts.map((conflict) => `conflict ${conflict.id}: ${conflict.targetPath} already exists (use --overwrite to replace it)`);
1089
+ lines.push(`Skipped ${result.conflicts.length} existing persona file(s); re-run with --overwrite to replace.`);
1090
+ return lines.join('\n') + '\n';
1091
+ }
1092
+ function runPersonaInstall(args) {
1093
+ if (args.includes('-h') || args.includes('--help')) {
1094
+ process.stdout.write('Usage: agentworkforce install <pkg|path> [--persona <id> ...] [--overwrite]\n');
1095
+ process.exit(0);
1096
+ }
1097
+ let parsed;
1098
+ try {
1099
+ parsed = parseInstallArgs(args);
1100
+ }
1101
+ catch (err) {
1102
+ die(err.message);
1103
+ }
1104
+ let result;
1105
+ try {
1106
+ result = installPersonas({
1107
+ source: parsed.source,
1108
+ personaIds: parsed.personaIds,
1109
+ overwrite: parsed.overwrite
1110
+ });
1111
+ }
1112
+ catch (err) {
1113
+ die(err.message, false);
1114
+ }
1115
+ process.stdout.write(formatPersonaInstallSummary(result));
1116
+ process.stderr.write(formatPersonaInstallConflicts(result));
1117
+ process.exit(result.conflicts.length > 0 ? 1 : 0);
1118
+ }
980
1119
  function collectPersonaRows() {
981
1120
  const rows = [];
982
1121
  const pushSpec = (spec, source) => {
@@ -1263,6 +1402,21 @@ function formatPersonaShow(spec, source, tiers, tierNote) {
1263
1402
  }
1264
1403
  }
1265
1404
  lines.push('');
1405
+ lines.push('INPUTS');
1406
+ const inputs = Object.entries(spec.inputs ?? {});
1407
+ if (inputs.length === 0) {
1408
+ lines.push(' (none)');
1409
+ }
1410
+ else {
1411
+ for (const [name, input] of inputs) {
1412
+ lines.push(` - ${name}`);
1413
+ if (input.description)
1414
+ lines.push(` description: ${input.description}`);
1415
+ lines.push(` env: ${input.env ?? name}`);
1416
+ lines.push(` default: ${input.default ?? '(required)'}`);
1417
+ }
1418
+ }
1419
+ lines.push('');
1266
1420
  lines.push('MCP SERVERS');
1267
1421
  const servers = Object.entries(spec.mcpServers ?? {});
1268
1422
  if (servers.length === 0) {
@@ -1347,6 +1501,93 @@ function runHarnessCheck() {
1347
1501
  process.stdout.write(`\n${available}/${results.length} harness(es) available.\n`);
1348
1502
  process.exit(0);
1349
1503
  }
1504
+ function defaultCreateTargetSelector() {
1505
+ if (existsSync(join(process.cwd(), '.agentworkforce', 'workforce'))) {
1506
+ return 'cwd';
1507
+ }
1508
+ return loadPersonaSourceConfig().defaultCreateTarget ?? 'user';
1509
+ }
1510
+ function resolveCreateTarget(rawTarget) {
1511
+ const raw = rawTarget?.trim() || defaultCreateTargetSelector();
1512
+ const config = loadPersonaSourceConfig();
1513
+ if (raw === 'cwd') {
1514
+ return {
1515
+ raw,
1516
+ kind: 'cwd',
1517
+ dir: defaultCwdPersonaDir(process.cwd()),
1518
+ createMode: 'local'
1519
+ };
1520
+ }
1521
+ if (raw === 'user') {
1522
+ return {
1523
+ raw,
1524
+ kind: 'user',
1525
+ dir: config.userPersonaDir,
1526
+ createMode: 'local'
1527
+ };
1528
+ }
1529
+ if (raw === 'library') {
1530
+ const dir = resolvePath(process.cwd(), 'personas');
1531
+ if (!existsSync(dir)) {
1532
+ die('create: --to library requires running from the AgentWorkforce repo root, where ./personas exists.');
1533
+ }
1534
+ return {
1535
+ raw,
1536
+ kind: 'library',
1537
+ dir,
1538
+ createMode: 'built-in'
1539
+ };
1540
+ }
1541
+ const dirMatch = /^dir:([1-9]\d*)$/.exec(raw);
1542
+ if (dirMatch) {
1543
+ const idx = Number(dirMatch[1]) - 1;
1544
+ const dir = config.personaDirs[idx];
1545
+ if (!dir) {
1546
+ die(`create: ${raw} does not exist. Run "agentworkforce sources list" to see configured dirs.`);
1547
+ }
1548
+ return {
1549
+ raw,
1550
+ kind: raw,
1551
+ dir,
1552
+ createMode: 'local'
1553
+ };
1554
+ }
1555
+ return {
1556
+ raw: normalizePersonaDir(raw),
1557
+ kind: 'path',
1558
+ dir: normalizePersonaDir(raw),
1559
+ createMode: 'local'
1560
+ };
1561
+ }
1562
+ function buildCreateInputValues(target) {
1563
+ return {
1564
+ [CREATE_INPUT_TARGET_DIR]: target.dir,
1565
+ [CREATE_INPUT_CREATE_MODE]: target.createMode
1566
+ };
1567
+ }
1568
+ function ensureCreateTargetDir(target) {
1569
+ if (target.createMode === 'built-in')
1570
+ return;
1571
+ mkdirSync(target.dir, { recursive: true });
1572
+ }
1573
+ function saveDefaultCreateTarget(target) {
1574
+ const config = loadPersonaSourceConfig();
1575
+ const raw = target.kind === 'path' ? target.dir : target.raw;
1576
+ const saved = savePersonaSourceConfig(config.personaDirs, { defaultCreateTarget: raw });
1577
+ process.stderr.write(`• default create target saved: ${raw}\n`);
1578
+ process.stderr.write(`• config: ${saved.configPath}\n`);
1579
+ }
1580
+ async function runAgentSelector(selector, flags, inputValues) {
1581
+ const target = parseSelector(selector);
1582
+ const selection = {
1583
+ ...buildSelection(target.spec, target.tier, target.kind),
1584
+ ...(inputValues ? { inputValues } : {})
1585
+ };
1586
+ const code = await runInteractive(selection, {
1587
+ installInRepo: flags.installInRepo
1588
+ });
1589
+ process.exit(code);
1590
+ }
1350
1591
  export async function main() {
1351
1592
  const argv = process.argv.slice(2);
1352
1593
  const [subcommand, ...rest] = argv;
@@ -1354,12 +1595,19 @@ export async function main() {
1354
1595
  process.stdout.write(USAGE);
1355
1596
  process.exit(subcommand ? 0 : 1);
1356
1597
  }
1598
+ if (subcommand === '-v' || subcommand === '--version') {
1599
+ process.stdout.write(`${CLI_VERSION}\n`);
1600
+ process.exit(0);
1601
+ }
1357
1602
  if (subcommand === 'list') {
1358
1603
  runList(rest);
1359
1604
  }
1360
1605
  if (subcommand === 'show') {
1361
1606
  runShow(rest);
1362
1607
  }
1608
+ if (subcommand === 'install') {
1609
+ runPersonaInstall(rest);
1610
+ }
1363
1611
  if (subcommand === 'sources') {
1364
1612
  runSources(rest);
1365
1613
  }
@@ -1376,6 +1624,10 @@ export async function main() {
1376
1624
  }
1377
1625
  runHarnessCheck();
1378
1626
  }
1627
+ if (subcommand === 'create') {
1628
+ const { flags, selector, inputValues } = parseCreateArgs(rest);
1629
+ await runAgentSelector(selector, flags, inputValues);
1630
+ }
1379
1631
  if (subcommand !== 'agent') {
1380
1632
  die(`Unknown subcommand "${subcommand}".`);
1381
1633
  }
@@ -1386,12 +1638,7 @@ export async function main() {
1386
1638
  if (extra.length > 0) {
1387
1639
  die(`agent: unexpected argument "${extra[0]}". The agent subcommand only takes a persona selector.`);
1388
1640
  }
1389
- const target = parseSelector(selector);
1390
- const selection = buildSelection(target.spec, target.tier, target.kind);
1391
- const code = await runInteractive(selection, {
1392
- installInRepo: flags.installInRepo
1393
- });
1394
- process.exit(code);
1641
+ await runAgentSelector(selector, flags);
1395
1642
  }
1396
1643
  export function parseAgentArgs(args) {
1397
1644
  const flags = { installInRepo: false };
@@ -1418,6 +1665,60 @@ export function parseAgentArgs(args) {
1418
1665
  }
1419
1666
  return { flags, positional };
1420
1667
  }
1668
+ export function parseCreateArgs(args) {
1669
+ const flags = { installInRepo: false, saveDefault: false };
1670
+ let seenDoubleDash = false;
1671
+ const positional = [];
1672
+ const valueOf = (i, flag) => {
1673
+ const v = args[i + 1];
1674
+ if (v === undefined || v.startsWith('--')) {
1675
+ die(`create: ${flag} requires a value.`);
1676
+ }
1677
+ return v;
1678
+ };
1679
+ for (let i = 0; i < args.length; i += 1) {
1680
+ const arg = args[i];
1681
+ if (seenDoubleDash) {
1682
+ positional.push(arg);
1683
+ continue;
1684
+ }
1685
+ if (arg === '--') {
1686
+ seenDoubleDash = true;
1687
+ continue;
1688
+ }
1689
+ if (arg === '--install-in-repo') {
1690
+ flags.installInRepo = true;
1691
+ continue;
1692
+ }
1693
+ if (arg === '--to') {
1694
+ flags.to = valueOf(i, arg);
1695
+ i += 1;
1696
+ continue;
1697
+ }
1698
+ if (arg === '--save-default') {
1699
+ flags.saveDefault = true;
1700
+ continue;
1701
+ }
1702
+ if (arg === '-h' || arg === '--help') {
1703
+ process.stdout.write('Usage: agentworkforce create [--to <cwd|user|dir:n|library|path>] [--save-default] [--install-in-repo]\n');
1704
+ process.exit(0);
1705
+ }
1706
+ positional.push(arg);
1707
+ }
1708
+ const [unexpected] = positional;
1709
+ if (unexpected) {
1710
+ die(`create: unexpected argument "${unexpected}". The create command always runs ${CREATE_SELECTOR}; use "agentworkforce agent <persona>[@<tier>]" to run another persona.`);
1711
+ }
1712
+ const target = resolveCreateTarget(flags.to);
1713
+ ensureCreateTargetDir(target);
1714
+ if (flags.saveDefault)
1715
+ saveDefaultCreateTarget(target);
1716
+ return {
1717
+ flags,
1718
+ selector: CREATE_SELECTOR,
1719
+ inputValues: buildCreateInputValues(target)
1720
+ };
1721
+ }
1421
1722
  // Only run main when invoked as the CLI entry, not when imported by tests.
1422
1723
  // Node ESM: import.meta.url is the module URL; argv[1] is the entry script
1423
1724
  // path, which may be relative (e.g. `node ./dist/cli.js`) and pathToFileURL
@@ -1435,6 +1736,10 @@ const isCliEntry = (() => {
1435
1736
  })();
1436
1737
  if (isCliEntry) {
1437
1738
  main().catch((err) => {
1739
+ if (err instanceof MissingPersonaInputError) {
1740
+ process.stderr.write(`${err.message}\n`);
1741
+ process.exit(1);
1742
+ }
1438
1743
  process.stderr.write(`${err?.stack ?? String(err)}\n`);
1439
1744
  process.exit(1);
1440
1745
  });