@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 +15 -0
- package/README.md +29 -5
- package/dist/cli.d.ts +21 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +178 -9
- package/dist/cli.js.map +1 -1
- package/dist/cli.test.js +94 -5
- package/dist/cli.test.js.map +1 -1
- package/dist/launch-metadata.d.ts +53 -0
- package/dist/launch-metadata.d.ts.map +1 -0
- package/dist/launch-metadata.js +192 -0
- package/dist/launch-metadata.js.map +1 -0
- package/dist/launch-metadata.test.d.ts +2 -0
- package/dist/launch-metadata.test.d.ts.map +1 -0
- package/dist/launch-metadata.test.js +152 -0
- package/dist/launch-metadata.test.js.map +1 -0
- package/dist/persona-picker.d.ts +66 -0
- package/dist/persona-picker.d.ts.map +1 -0
- package/dist/persona-picker.js +152 -0
- package/dist/persona-picker.js.map +1 -0
- package/dist/persona-picker.test.d.ts +2 -0
- package/dist/persona-picker.test.d.ts.map +1 -0
- package/dist/persona-picker.test.js +184 -0
- package/dist/persona-picker.test.js.map +1 -0
- package/package.json +4 -3
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.
|
|
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":";
|
|
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
|
-
|
|
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);
|