@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/CHANGELOG.md +30 -1
- package/README.md +396 -0
- package/dist/cli.d.ts +17 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +331 -26
- package/dist/cli.js.map +1 -1
- package/dist/cli.test.js +89 -1
- package/dist/cli.test.js.map +1 -1
- package/dist/local-personas.d.ts +13 -3
- package/dist/local-personas.d.ts.map +1 -1
- package/dist/local-personas.js +157 -10
- package/dist/local-personas.js.map +1 -1
- package/dist/local-personas.test.js +92 -0
- package/dist/local-personas.test.js.map +1 -1
- package/dist/persona-install.d.ts +32 -0
- package/dist/persona-install.d.ts.map +1 -0
- package/dist/persona-install.js +292 -0
- package/dist/persona-install.js.map +1 -0
- package/dist/persona-install.test.d.ts +2 -0
- package/dist/persona-install.test.d.ts.map +1 -0
- package/dist/persona-install.test.js +223 -0
- package/dist/persona-install.test.js.map +1 -0
- package/package.json +3 -3
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
|
|
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(
|
|
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
|
|
466
|
-
const
|
|
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:
|
|
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 (
|
|
528
|
-
summary.push(`allow=${
|
|
580
|
+
if (effectiveSelection.permissions?.allow?.length) {
|
|
581
|
+
summary.push(`allow=${effectiveSelection.permissions.allow.length} rule(s)`);
|
|
529
582
|
}
|
|
530
|
-
if (
|
|
531
|
-
summary.push(`deny=${
|
|
583
|
+
if (effectiveSelection.permissions?.deny?.length) {
|
|
584
|
+
summary.push(`deny=${effectiveSelection.permissions.deny.length} rule(s)`);
|
|
532
585
|
}
|
|
533
|
-
if (
|
|
534
|
-
summary.push(`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 {
|
|
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
|
-
|
|
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
|
});
|