@agentworkforce/cli 0.5.4 → 0.6.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 +17 -0
- package/README.md +95 -56
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +227 -39
- package/dist/cli.js.map +1 -1
- package/dist/cli.test.js +46 -6
- package/dist/cli.test.js.map +1 -1
- package/dist/local-personas.d.ts +35 -5
- package/dist/local-personas.d.ts.map +1 -1
- package/dist/local-personas.js +145 -35
- package/dist/local-personas.js.map +1 -1
- package/dist/local-personas.test.js +83 -15
- package/dist/local-personas.test.js.map +1 -1
- package/package.json +3 -6
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.0] - 2026-05-06
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Support installable persona sources**
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Isolate cwd personas under personas dir
|
|
19
|
+
- Honor source config with legacy persona dir
|
|
20
|
+
|
|
21
|
+
### Dependencies
|
|
22
|
+
|
|
23
|
+
- Inline command name in help
|
|
24
|
+
- Use fixed command name in help
|
|
25
|
+
- Publish only agentworkforce bin
|
|
26
|
+
|
|
10
27
|
## [0.5.4] - 2026-05-01
|
|
11
28
|
|
|
12
29
|
### Added
|
package/README.md
CHANGED
|
@@ -1,59 +1,54 @@
|
|
|
1
|
-
#
|
|
1
|
+
# agentworkforce CLI
|
|
2
2
|
|
|
3
3
|
A thin command-line front end for the workload-router. Spawns the harness CLI
|
|
4
4
|
(`claude`, `codex`, `opencode`) configured by a selected **persona** — either a
|
|
5
|
-
built-in one from `/personas/`, or
|
|
5
|
+
built-in one from `/personas/`, or an installed/local one that extends a lower
|
|
6
|
+
source.
|
|
6
7
|
|
|
7
8
|
```
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
agentworkforce agent <persona>[@<tier>]
|
|
10
|
+
agentworkforce list [flags]
|
|
11
|
+
agentworkforce show <persona>[@<tier>]
|
|
12
|
+
agentworkforce sources <list|add|remove>
|
|
13
|
+
agentworkforce harness check
|
|
11
14
|
```
|
|
12
15
|
|
|
13
16
|
- `agent` — drops you into an interactive session with the harness.
|
|
14
17
|
- `list` — print the persona catalog as a table (or JSON). See
|
|
15
18
|
[`## List`](#list) below for every flag.
|
|
19
|
+
- `show` — print the resolved spec for one persona.
|
|
20
|
+
- `sources` — list, add, or remove persona source directories.
|
|
16
21
|
- `harness check` — probe which harnesses (`claude`, `codex`, `opencode`)
|
|
17
22
|
are installed. See [`## Harness check`](#harness-check) below.
|
|
18
23
|
|
|
19
24
|
## Install
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
- **`@agentworkforce/cli`** — the scoped package; installs the
|
|
24
|
-
`agent-workforce` bin.
|
|
25
|
-
- **`agentworkforce`** — a thin top-level wrapper; installs the
|
|
26
|
-
`agentworkforce` bin so the global install command and command name match
|
|
27
|
-
(`npm i -g agentworkforce`).
|
|
28
|
-
|
|
29
|
-
Both depend on `@agentworkforce/workload-router` and `@agentworkforce/harness-kit`
|
|
30
|
-
via the pnpm workspace. The CLI derives its help-text bin name from
|
|
31
|
-
`process.argv[1]`, so `--help` shows whichever name you invoked.
|
|
26
|
+
Install the top-level `agentworkforce` package. It provides the
|
|
27
|
+
`agentworkforce` bin and depends on this internal CLI package.
|
|
32
28
|
|
|
33
29
|
From npm:
|
|
34
30
|
|
|
35
31
|
```sh
|
|
36
|
-
npm i -g agentworkforce
|
|
37
|
-
# or
|
|
38
|
-
npm i -g @agentworkforce/cli # provides `agent-workforce`
|
|
32
|
+
npm i -g agentworkforce
|
|
39
33
|
```
|
|
40
34
|
|
|
41
35
|
From the repo checkout:
|
|
42
36
|
|
|
43
37
|
```sh
|
|
44
38
|
corepack pnpm -r build
|
|
45
|
-
corepack pnpm --filter
|
|
39
|
+
corepack pnpm --filter agentworkforce link --global
|
|
46
40
|
```
|
|
47
41
|
|
|
48
42
|
## Selectors
|
|
49
43
|
|
|
50
44
|
```
|
|
51
|
-
|
|
45
|
+
agentworkforce agent <persona>[@<tier>]
|
|
52
46
|
```
|
|
53
47
|
|
|
54
48
|
- `<persona>` — matches, in order:
|
|
55
|
-
1. A **
|
|
56
|
-
2. A
|
|
49
|
+
1. A **cwd-local** id (files in `<cwd>/.agentworkforce/workforce/personas/*.json`)
|
|
50
|
+
2. A configured persona source dir, in order. The default is
|
|
51
|
+
`~/.agentworkforce/workforce/personas/*.json`.
|
|
57
52
|
3. A **library** persona — by intent first (e.g. `review`), then by id
|
|
58
53
|
(e.g. `code-reviewer`)
|
|
59
54
|
- `<tier>` — `best` | `best-value` | `minimum`. Defaults to `best-value`.
|
|
@@ -64,23 +59,24 @@ Unknown persona prints the full catalog with each entry's origin.
|
|
|
64
59
|
|
|
65
60
|
```sh
|
|
66
61
|
# Interactive code reviewer
|
|
67
|
-
|
|
62
|
+
agentworkforce agent review@best-value
|
|
68
63
|
|
|
69
64
|
# Interactive PostHog session (library persona, needs POSTHOG_API_KEY)
|
|
70
|
-
|
|
65
|
+
agentworkforce agent posthog@best
|
|
71
66
|
|
|
72
67
|
# Interactive against a local override
|
|
73
|
-
|
|
68
|
+
agentworkforce agent my-posthog@best
|
|
74
69
|
```
|
|
75
70
|
|
|
76
71
|
## List
|
|
77
72
|
|
|
78
73
|
```
|
|
79
|
-
|
|
74
|
+
agentworkforce list [flags]
|
|
80
75
|
```
|
|
81
76
|
|
|
82
77
|
Prints the merged persona catalog — everything the `agent` subcommand would
|
|
83
|
-
accept as a selector — including cascade source (`library`, `
|
|
78
|
+
accept as a selector — including cascade source (`library`, `user`, `cwd`,
|
|
79
|
+
or `dir:<n>`),
|
|
84
80
|
harness, model, description, and tier ("rating"). One row per persona-tier
|
|
85
81
|
combination; by default only the **recommended tier per intent** is shown
|
|
86
82
|
(as declared in
|
|
@@ -105,28 +101,66 @@ the allowed values.
|
|
|
105
101
|
|
|
106
102
|
```sh
|
|
107
103
|
# Default: one row per persona, recommended tier only
|
|
108
|
-
|
|
104
|
+
agentworkforce list
|
|
109
105
|
|
|
110
106
|
# See every tier
|
|
111
|
-
|
|
107
|
+
agentworkforce list --all
|
|
112
108
|
|
|
113
109
|
# Only the top tier across the catalog — independent of recommendations
|
|
114
|
-
|
|
110
|
+
agentworkforce list --filter-rating best
|
|
115
111
|
|
|
116
112
|
# All claude-harness personas (any tier)
|
|
117
|
-
|
|
113
|
+
agentworkforce list --all --filter-harness claude
|
|
118
114
|
|
|
119
115
|
# Compact table for a narrow terminal
|
|
120
|
-
|
|
116
|
+
agentworkforce list --no-display-description
|
|
121
117
|
|
|
122
118
|
# Machine-readable
|
|
123
|
-
|
|
119
|
+
agentworkforce list --json --filter-harness claude
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Sources
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
agentworkforce sources list [--json]
|
|
126
|
+
agentworkforce sources add <dir> [--position <n>]
|
|
127
|
+
agentworkforce sources remove <dir|config-position>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The fixed project source is always first:
|
|
131
|
+
`<cwd>/.agentworkforce/workforce/personas/*.json`.
|
|
132
|
+
|
|
133
|
+
After that, the CLI reads an ordered list of configurable persona directories
|
|
134
|
+
from `~/.agentworkforce/workforce/config.json`. If no config exists, the list
|
|
135
|
+
defaults to `~/.agentworkforce/workforce/personas`. This makes installed
|
|
136
|
+
personas work as plain JSON files in the default user location, or from any
|
|
137
|
+
checked-out repo you add as a source directory.
|
|
138
|
+
|
|
139
|
+
`sources add` appends by default. `--position <n>` inserts at the 1-based
|
|
140
|
+
position among configurable directories, so `--position 1` gives that directory
|
|
141
|
+
the highest priority after the fixed cwd source. `sources remove` accepts either
|
|
142
|
+
that configurable position or an exact path.
|
|
143
|
+
|
|
144
|
+
Examples:
|
|
145
|
+
|
|
146
|
+
```sh
|
|
147
|
+
# Show the full source cascade, including fixed cwd and library entries
|
|
148
|
+
agentworkforce sources list
|
|
149
|
+
|
|
150
|
+
# Install personas from another checkout, below the default user persona dir
|
|
151
|
+
agentworkforce sources add ~/src/company-personas/personas
|
|
152
|
+
|
|
153
|
+
# Give a checked-out persona repo priority over the default user dir
|
|
154
|
+
agentworkforce sources add ~/src/company-personas/personas --position 1
|
|
155
|
+
|
|
156
|
+
# Remove the first configurable persona source
|
|
157
|
+
agentworkforce sources remove 1
|
|
124
158
|
```
|
|
125
159
|
|
|
126
160
|
## Harness check
|
|
127
161
|
|
|
128
162
|
```
|
|
129
|
-
|
|
163
|
+
agentworkforce harness check
|
|
130
164
|
```
|
|
131
165
|
|
|
132
166
|
Probes your PATH for each supported harness binary (`claude`, `codex`,
|
|
@@ -169,17 +203,21 @@ See `/personas/*.json` for all built-ins.
|
|
|
169
203
|
Local persona files layer on top of the library. Resolution precedence (highest
|
|
170
204
|
wins):
|
|
171
205
|
|
|
172
|
-
1. `<cwd>/.
|
|
173
|
-
2.
|
|
174
|
-
`
|
|
206
|
+
1. `<cwd>/.agentworkforce/workforce/personas/*.json` — **cwd**
|
|
207
|
+
2. Configurable persona source dirs, in order. Default:
|
|
208
|
+
`~/.agentworkforce/workforce/personas/*.json` — **user**
|
|
175
209
|
3. Built-in personas in `/personas/` — **library**
|
|
176
210
|
|
|
177
211
|
Local files are **partial overlays**: only the fields you set replace the
|
|
178
212
|
inherited value. Everything else cascades through from below.
|
|
179
213
|
|
|
214
|
+
Set `AGENT_WORKFORCE_HOME` to move the `~/.agentworkforce/workforce` config
|
|
215
|
+
root. The legacy `AGENT_WORKFORCE_CONFIG_DIR` env var is still honored as a
|
|
216
|
+
direct override for the default user persona directory.
|
|
217
|
+
|
|
180
218
|
### Minimal override: add your API key
|
|
181
219
|
|
|
182
|
-
`~/.
|
|
220
|
+
`~/.agentworkforce/workforce/personas/my-posthog.json`:
|
|
183
221
|
|
|
184
222
|
```json
|
|
185
223
|
{
|
|
@@ -190,7 +228,7 @@ inherited value. Everything else cascades through from below.
|
|
|
190
228
|
```
|
|
191
229
|
|
|
192
230
|
That inherits every field from the library `posthog` persona, then layers your
|
|
193
|
-
`env` on top. `
|
|
231
|
+
`env` on top. `agentworkforce agent my-posthog@best` now works as long as
|
|
194
232
|
`POSTHOG_API_KEY` is exported in your shell.
|
|
195
233
|
|
|
196
234
|
### Same-id override (implicit extends)
|
|
@@ -198,7 +236,7 @@ That inherits every field from the library `posthog` persona, then layers your
|
|
|
198
236
|
If your file's `id` matches a persona in a lower layer and you omit `extends`,
|
|
199
237
|
the loader implicitly inherits from that same-id base:
|
|
200
238
|
|
|
201
|
-
`<cwd>/.
|
|
239
|
+
`<cwd>/.agentworkforce/workforce/personas/posthog.json`:
|
|
202
240
|
|
|
203
241
|
```json
|
|
204
242
|
{
|
|
@@ -207,29 +245,30 @@ the loader implicitly inherits from that same-id base:
|
|
|
207
245
|
}
|
|
208
246
|
```
|
|
209
247
|
|
|
210
|
-
Resolving `posthog` now hits this
|
|
248
|
+
Resolving `posthog` now hits this cwd override first; it inherits the rest
|
|
211
249
|
(MCP, tiers, description, etc.) from the library `posthog`.
|
|
212
250
|
|
|
213
251
|
### Cascade chain
|
|
214
252
|
|
|
215
|
-
A
|
|
253
|
+
A cwd file can extend a user or configured-dir file, which extends the library:
|
|
216
254
|
|
|
217
255
|
```
|
|
218
|
-
~/.
|
|
256
|
+
~/.agentworkforce/workforce/personas/ph-base.json:
|
|
219
257
|
{ "id": "ph-base", "extends": "posthog", "env": { "POSTHOG_ORG": "acme" } }
|
|
220
258
|
|
|
221
|
-
<cwd>/.
|
|
259
|
+
<cwd>/.agentworkforce/workforce/personas/ph-prod.json:
|
|
222
260
|
{ "id": "ph-prod", "extends": "ph-base", "env": { "POSTHOG_API_KEY": "$PROD_KEY" } }
|
|
223
261
|
```
|
|
224
262
|
|
|
225
263
|
Resolving `ph-prod`:
|
|
226
264
|
|
|
227
265
|
- Start with library `posthog` (MCP, tiers, prompt, …)
|
|
228
|
-
- Layer
|
|
229
|
-
- Layer
|
|
266
|
+
- Layer user `ph-base` on top (adds `POSTHOG_ORG=acme`)
|
|
267
|
+
- Layer cwd `ph-prod` on top (adds `POSTHOG_API_KEY`)
|
|
230
268
|
|
|
231
|
-
`extends` is resolved **strictly against lower layers** —
|
|
232
|
-
library,
|
|
269
|
+
`extends` is resolved **strictly against lower layers** — cwd extends configured
|
|
270
|
+
dirs or library, configured dirs extend lower configured dirs or library, and
|
|
271
|
+
library has no `extends`.
|
|
233
272
|
|
|
234
273
|
### Override shape (all fields except `id` optional)
|
|
235
274
|
|
|
@@ -320,9 +359,9 @@ eyeball.
|
|
|
320
359
|
warning and fall back to their defaults when `permissions` is set.
|
|
321
360
|
- **Cascade merge:** `allow` and `deny` are unions across layers (deduped on
|
|
322
361
|
merge); `mode` is replaced by the topmost layer that sets it. So the
|
|
323
|
-
library can declare the minimum-viable allow list,
|
|
324
|
-
|
|
325
|
-
compose.
|
|
362
|
+
library can declare the minimum-viable allow list, a user or configured
|
|
363
|
+
persona source can layer shared denies, and cwd can add per-project patterns
|
|
364
|
+
— they all compose.
|
|
326
365
|
|
|
327
366
|
### Example: PostHog with auto-approve
|
|
328
367
|
|
|
@@ -392,7 +431,7 @@ persona session, add it to the persona's `mcpServers` block.
|
|
|
392
431
|
## Interactive
|
|
393
432
|
|
|
394
433
|
```sh
|
|
395
|
-
|
|
434
|
+
agentworkforce agent [--install-in-repo] <persona>[@<tier>]
|
|
396
435
|
```
|
|
397
436
|
|
|
398
437
|
By default, claude and opencode sessions run inside a sandbox mount — see
|
|
@@ -467,7 +506,7 @@ Pass `--install-in-repo` to fall back to the legacy behavior (skills land in
|
|
|
467
506
|
the repo's `.claude/skills/` directory, cleaned on exit):
|
|
468
507
|
|
|
469
508
|
```sh
|
|
470
|
-
|
|
509
|
+
agentworkforce agent --install-in-repo code-reviewer@best
|
|
471
510
|
```
|
|
472
511
|
|
|
473
512
|
Useful when you want to inspect the installed skills on disk, or when the
|
|
@@ -555,7 +594,7 @@ is generated once and both paths are derived from it:
|
|
|
555
594
|
|
|
556
595
|
`@relayfile/local-mount` handles mount creation, process spawn,
|
|
557
596
|
SIGINT/SIGTERM forwarding, write syncback, and cleanup on exit. The
|
|
558
|
-
|
|
597
|
+
agentworkforce CLI just wires the paths and passes the persona's argv.
|
|
559
598
|
|
|
560
599
|
### Example
|
|
561
600
|
|
|
@@ -564,7 +603,7 @@ agent-workforce CLI just wires the paths and passes the persona's argv.
|
|
|
564
603
|
# .mcp.json hidden — session sees the persona's staged skills plus your
|
|
565
604
|
# user-level ~/.claude/CLAUDE.md, nothing else from this repo.
|
|
566
605
|
export POSTHOG_API_KEY=phx_…
|
|
567
|
-
|
|
606
|
+
agentworkforce agent posthog@best
|
|
568
607
|
```
|
|
569
608
|
|
|
570
609
|
On exit: mount is synced back to the real repo, then torn down; skill
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAQA,OAAO,EAOL,KAAK,OAAO,EAMb,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAQA,OAAO,EAOL,KAAK,OAAO,EAMb,MAAM,iCAAiC,CAAC;AA+IzC;;;;;;;;;;GAUG;AACH,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAExF;AA4ID;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAUhE;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAe5D;AAED;;;kDAGkD;AAClD,eAAO,MAAM,sBAAsB,mEAKzB,CAAC;AAEX;;;;;;;;;GASG;AACH,eAAO,MAAM,8BAA8B,8IAajC,CAAC;AAEX;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAU7E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAuCxF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,EAChB,aAAa,UAAQ,GACpB;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,CAKvB;AAu+BD,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAqD1C;AAED,MAAM,WAAW,UAAU;IACzB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG;IACvD,KAAK,EAAE,UAAU,CAAC;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB,CAwBA"}
|
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, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { appendFileSync, existsSync, mkdirSync, 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,24 +9,8 @@ import { HARNESS_VALUES, PERSONA_TAGS, PERSONA_TIERS, personaCatalog, routingPro
|
|
|
9
9
|
import { buildInteractiveSpec, detectHarnesses, formatDropWarnings, resolveMcpServersLenient, resolveStringMapLenient } from '@agentworkforce/harness-kit';
|
|
10
10
|
import { launchOnMount } from '@relayfile/local-mount';
|
|
11
11
|
import ora from 'ora';
|
|
12
|
-
import { loadLocalPersonas } from './local-personas.js';
|
|
13
|
-
|
|
14
|
-
// drive multiple npm bins (`agent-workforce` from @agentworkforce/cli,
|
|
15
|
-
// `agentworkforce` from the top-level wrapper package) without baking either
|
|
16
|
-
// name into the help text. Falls back to `agent-workforce` for the bare
|
|
17
|
-
// `node dist/cli.js` case so the test suite, which spawns cli.js directly,
|
|
18
|
-
// still sees the historical name.
|
|
19
|
-
const BIN_NAME = (() => {
|
|
20
|
-
const arg1 = process.argv[1];
|
|
21
|
-
if (!arg1)
|
|
22
|
-
return 'agent-workforce';
|
|
23
|
-
const base = arg1.split(/[/\\]/).pop() ?? '';
|
|
24
|
-
const stripped = base.replace(/\.(m?js|cjs)$/, '');
|
|
25
|
-
if (!stripped || stripped === 'cli')
|
|
26
|
-
return 'agent-workforce';
|
|
27
|
-
return stripped;
|
|
28
|
-
})();
|
|
29
|
-
const USAGE = `Usage: ${BIN_NAME} <command> [args...]
|
|
12
|
+
import { buildPersonaSourceDirectories, loadLocalPersonas, loadPersonaSourceConfig, normalizePersonaDir, savePersonaSourceConfig } from './local-personas.js';
|
|
13
|
+
const USAGE = `Usage: agentworkforce <command> [args...]
|
|
30
14
|
|
|
31
15
|
Commands:
|
|
32
16
|
agent [flags] <persona>[@<tier>]
|
|
@@ -49,10 +33,10 @@ Commands:
|
|
|
49
33
|
are hidden from the session. Codex
|
|
50
34
|
sessions never mount and ignore
|
|
51
35
|
this flag.
|
|
52
|
-
list [flags] List available personas from the cascade (
|
|
53
|
-
library). By default shows
|
|
54
|
-
|
|
55
|
-
tier. Flags:
|
|
36
|
+
list [flags] List available personas from the cascade (cwd →
|
|
37
|
+
configured persona dirs → library). By default shows
|
|
38
|
+
one row per persona at the recommended tier for its
|
|
39
|
+
intent; pass --all to see every tier. Flags:
|
|
56
40
|
--all show every tier (overrides default)
|
|
57
41
|
--json emit JSON instead of a table
|
|
58
42
|
--filter-rating <tier> only show this tier; disables
|
|
@@ -65,28 +49,38 @@ Commands:
|
|
|
65
49
|
--no-display-description hide the DESCRIPTION column
|
|
66
50
|
show <persona>[@<tier>]
|
|
67
51
|
Print the fully-resolved spec for a single persona,
|
|
68
|
-
including which cascade layer defined it (
|
|
69
|
-
library). By default shows only the recommended
|
|
70
|
-
the persona's intent; pass @<tier> to pick one,
|
|
71
|
-
to see every tier. Flags:
|
|
52
|
+
including which cascade layer defined it (cwd, user,
|
|
53
|
+
dir:<n>, library). By default shows only the recommended
|
|
54
|
+
tier for the persona's intent; pass @<tier> to pick one,
|
|
55
|
+
or --all to see every tier. Flags:
|
|
72
56
|
--all include every tier (overrides default)
|
|
73
57
|
--json emit the resolved PersonaSpec as JSON
|
|
58
|
+
sources list [--json]
|
|
59
|
+
List persona source directories in cascade order.
|
|
60
|
+
sources add <dir> [--position <n>]
|
|
61
|
+
Add a configurable persona source directory. Position is
|
|
62
|
+
1-based among configurable dirs after the fixed cwd dir.
|
|
63
|
+
sources remove <dir|n>
|
|
64
|
+
Remove a configurable persona source directory by path or
|
|
65
|
+
1-based configurable position.
|
|
74
66
|
harness check Probe which harnesses (claude, codex, opencode) are
|
|
75
67
|
installed and runnable on this machine.
|
|
76
68
|
|
|
77
|
-
Local personas cascade: <cwd>/.
|
|
69
|
+
Local personas cascade: <cwd>/.agentworkforce/workforce/personas/*.json → configured persona dirs → repo library.
|
|
78
70
|
Each layer only needs to specify fields it overrides; everything else inherits
|
|
79
71
|
from the next lower layer. "extends" explicitly names a base; omit it and the
|
|
80
|
-
loader implicitly inherits from the same-id persona below.
|
|
81
|
-
|
|
72
|
+
loader implicitly inherits from the same-id persona below. By default the only
|
|
73
|
+
configured persona dir is ~/.agentworkforce/workforce/personas.
|
|
82
74
|
|
|
83
75
|
Examples:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
76
|
+
agentworkforce agent npm-provenance-publisher@best
|
|
77
|
+
agentworkforce agent my-posthog@best
|
|
78
|
+
agentworkforce agent review@best-value
|
|
79
|
+
agentworkforce list
|
|
80
|
+
agentworkforce show posthog
|
|
81
|
+
agentworkforce sources list
|
|
82
|
+
agentworkforce sources add ../my-personas --position 1
|
|
83
|
+
agentworkforce harness check
|
|
90
84
|
`;
|
|
91
85
|
function die(msg, withUsage = true) {
|
|
92
86
|
process.stderr.write(`${msg}\n`);
|
|
@@ -792,6 +786,197 @@ function formatAvailabilityTable(results) {
|
|
|
792
786
|
const line = (row) => cols.map((c) => row[c].padEnd(widths[c])).join(' ').trimEnd();
|
|
793
787
|
return [line(headers), ...rows.map(line)].join('\n') + '\n';
|
|
794
788
|
}
|
|
789
|
+
function collectSourceDirRows() {
|
|
790
|
+
const { directories, config } = buildPersonaSourceDirectories();
|
|
791
|
+
const rows = directories.map((sourceDir, idx) => ({
|
|
792
|
+
cascade: String(idx + 1),
|
|
793
|
+
config: sourceDir.configurable ? String(config.personaDirs.indexOf(sourceDir.dir) + 1) : '-',
|
|
794
|
+
source: sourceDir.source,
|
|
795
|
+
exists: existsSync(sourceDir.dir) ? 'yes' : 'no',
|
|
796
|
+
dir: sourceDir.dir
|
|
797
|
+
}));
|
|
798
|
+
rows.push({
|
|
799
|
+
cascade: String(rows.length + 1),
|
|
800
|
+
config: '-',
|
|
801
|
+
source: 'library',
|
|
802
|
+
exists: 'yes',
|
|
803
|
+
dir: '(built-in)'
|
|
804
|
+
});
|
|
805
|
+
return { configPath: config.configPath, personaDirs: config.personaDirs, rows };
|
|
806
|
+
}
|
|
807
|
+
function formatSourcesTable(rows, configPath) {
|
|
808
|
+
const headers = {
|
|
809
|
+
cascade: 'CASCADE',
|
|
810
|
+
config: 'CONFIG',
|
|
811
|
+
source: 'SOURCE',
|
|
812
|
+
exists: 'EXISTS',
|
|
813
|
+
dir: 'DIR'
|
|
814
|
+
};
|
|
815
|
+
const cols = ['cascade', 'config', 'source', 'exists', 'dir'];
|
|
816
|
+
const widths = Object.fromEntries(cols.map((c) => [c, Math.max(headers[c].length, ...rows.map((r) => r[c].length))]));
|
|
817
|
+
const line = (row) => cols.map((c) => row[c].padEnd(widths[c])).join(' ').trimEnd();
|
|
818
|
+
return [
|
|
819
|
+
`Config: ${configPath}`,
|
|
820
|
+
[line(headers), ...rows.map(line)].join('\n'),
|
|
821
|
+
''
|
|
822
|
+
].join('\n');
|
|
823
|
+
}
|
|
824
|
+
function parseSourcesListArgs(args) {
|
|
825
|
+
let json = false;
|
|
826
|
+
for (const arg of args) {
|
|
827
|
+
if (arg === '--json') {
|
|
828
|
+
json = true;
|
|
829
|
+
}
|
|
830
|
+
else if (arg === '-h' || arg === '--help') {
|
|
831
|
+
process.stdout.write('Usage: agentworkforce sources list [--json]\n');
|
|
832
|
+
process.exit(0);
|
|
833
|
+
}
|
|
834
|
+
else {
|
|
835
|
+
die(`sources list: unexpected argument "${arg}".`);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return { json };
|
|
839
|
+
}
|
|
840
|
+
function runSourcesList(args) {
|
|
841
|
+
const { json } = parseSourcesListArgs(args);
|
|
842
|
+
const { configPath, personaDirs, rows } = collectSourceDirRows();
|
|
843
|
+
if (json) {
|
|
844
|
+
process.stdout.write(JSON.stringify({ configPath, personaDirs, sources: rows }, null, 2) + '\n');
|
|
845
|
+
}
|
|
846
|
+
else {
|
|
847
|
+
process.stdout.write(formatSourcesTable(rows, configPath));
|
|
848
|
+
}
|
|
849
|
+
process.exit(0);
|
|
850
|
+
}
|
|
851
|
+
function assertDirectoryForAdd(dir) {
|
|
852
|
+
try {
|
|
853
|
+
if (!existsSync(dir)) {
|
|
854
|
+
die(`sources add: directory does not exist: ${dir}`);
|
|
855
|
+
}
|
|
856
|
+
if (!statSync(dir).isDirectory()) {
|
|
857
|
+
die(`sources add: path is not a directory: ${dir}`);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
catch (err) {
|
|
861
|
+
if (err.message.startsWith('sources add:'))
|
|
862
|
+
throw err;
|
|
863
|
+
die(`sources add: could not inspect ${dir}: ${err.message}`);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
function parseSourcesAddArgs(args) {
|
|
867
|
+
let dir;
|
|
868
|
+
let position;
|
|
869
|
+
const valueOf = (i, flag) => {
|
|
870
|
+
const v = args[i + 1];
|
|
871
|
+
if (v === undefined || v.startsWith('--')) {
|
|
872
|
+
die(`sources add: ${flag} requires a value.`);
|
|
873
|
+
}
|
|
874
|
+
return v;
|
|
875
|
+
};
|
|
876
|
+
for (let i = 0; i < args.length; i++) {
|
|
877
|
+
const arg = args[i];
|
|
878
|
+
if (arg === '-h' || arg === '--help') {
|
|
879
|
+
process.stdout.write('Usage: agentworkforce sources add <dir> [--position <n>]\n');
|
|
880
|
+
process.exit(0);
|
|
881
|
+
}
|
|
882
|
+
else if (arg === '--position') {
|
|
883
|
+
const raw = valueOf(i++, arg);
|
|
884
|
+
const parsed = Number(raw);
|
|
885
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
886
|
+
die(`sources add: --position must be a positive integer.`);
|
|
887
|
+
}
|
|
888
|
+
position = parsed;
|
|
889
|
+
}
|
|
890
|
+
else if (arg.startsWith('--')) {
|
|
891
|
+
die(`sources add: unexpected flag "${arg}".`);
|
|
892
|
+
}
|
|
893
|
+
else if (dir === undefined) {
|
|
894
|
+
dir = arg;
|
|
895
|
+
}
|
|
896
|
+
else {
|
|
897
|
+
die(`sources add: unexpected argument "${arg}".`);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
if (!dir)
|
|
901
|
+
die('sources add: missing directory.');
|
|
902
|
+
return { dir, position };
|
|
903
|
+
}
|
|
904
|
+
function runSourcesAdd(args) {
|
|
905
|
+
const { dir: rawDir, position } = parseSourcesAddArgs(args);
|
|
906
|
+
const dir = normalizePersonaDir(rawDir);
|
|
907
|
+
assertDirectoryForAdd(dir);
|
|
908
|
+
const config = loadPersonaSourceConfig();
|
|
909
|
+
if (config.personaDirs.includes(dir)) {
|
|
910
|
+
die(`sources add: directory is already configured: ${dir}`);
|
|
911
|
+
}
|
|
912
|
+
const insertAt = position === undefined ? config.personaDirs.length : position - 1;
|
|
913
|
+
if (insertAt < 0 || insertAt > config.personaDirs.length) {
|
|
914
|
+
die(`sources add: --position must be between 1 and ${config.personaDirs.length + 1}.`);
|
|
915
|
+
}
|
|
916
|
+
const nextDirs = [...config.personaDirs];
|
|
917
|
+
nextDirs.splice(insertAt, 0, dir);
|
|
918
|
+
const saved = savePersonaSourceConfig(nextDirs);
|
|
919
|
+
process.stdout.write(`Added persona source directory at configurable position ${insertAt + 1}: ${dir}\nConfig: ${saved.configPath}\n`);
|
|
920
|
+
process.exit(0);
|
|
921
|
+
}
|
|
922
|
+
function parseSourcesRemoveArgs(args) {
|
|
923
|
+
let target;
|
|
924
|
+
for (const arg of args) {
|
|
925
|
+
if (arg === '-h' || arg === '--help') {
|
|
926
|
+
process.stdout.write('Usage: agentworkforce sources remove <dir|config-position>\n');
|
|
927
|
+
process.exit(0);
|
|
928
|
+
}
|
|
929
|
+
else if (arg.startsWith('--')) {
|
|
930
|
+
die(`sources remove: unexpected flag "${arg}".`);
|
|
931
|
+
}
|
|
932
|
+
else if (target === undefined) {
|
|
933
|
+
target = arg;
|
|
934
|
+
}
|
|
935
|
+
else {
|
|
936
|
+
die(`sources remove: unexpected argument "${arg}".`);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
if (!target)
|
|
940
|
+
die('sources remove: missing directory or config-position.');
|
|
941
|
+
return { target };
|
|
942
|
+
}
|
|
943
|
+
function runSourcesRemove(args) {
|
|
944
|
+
const { target } = parseSourcesRemoveArgs(args);
|
|
945
|
+
const config = loadPersonaSourceConfig();
|
|
946
|
+
let idx;
|
|
947
|
+
if (/^[1-9]\d*$/.test(target)) {
|
|
948
|
+
idx = Number(target) - 1;
|
|
949
|
+
}
|
|
950
|
+
else {
|
|
951
|
+
const dir = normalizePersonaDir(target);
|
|
952
|
+
idx = config.personaDirs.indexOf(dir);
|
|
953
|
+
}
|
|
954
|
+
if (idx < 0 || idx >= config.personaDirs.length) {
|
|
955
|
+
die(`sources remove: no configurable persona source matched "${target}".`);
|
|
956
|
+
}
|
|
957
|
+
const nextDirs = [...config.personaDirs];
|
|
958
|
+
const [removed] = nextDirs.splice(idx, 1);
|
|
959
|
+
const saved = savePersonaSourceConfig(nextDirs);
|
|
960
|
+
process.stdout.write(`Removed persona source directory from configurable position ${idx + 1}: ${removed}\nConfig: ${saved.configPath}\n`);
|
|
961
|
+
process.exit(0);
|
|
962
|
+
}
|
|
963
|
+
function runSources(args) {
|
|
964
|
+
const [action, ...rest] = args;
|
|
965
|
+
if (!action || action === '-h' || action === '--help') {
|
|
966
|
+
process.stdout.write('Usage: agentworkforce sources <list|add|remove> [args...]\n' +
|
|
967
|
+
' agentworkforce sources list [--json]\n' +
|
|
968
|
+
' agentworkforce sources add <dir> [--position <n>]\n' +
|
|
969
|
+
' agentworkforce sources remove <dir|config-position>\n');
|
|
970
|
+
process.exit(action ? 0 : 1);
|
|
971
|
+
}
|
|
972
|
+
if (action === 'list')
|
|
973
|
+
runSourcesList(rest);
|
|
974
|
+
if (action === 'add')
|
|
975
|
+
runSourcesAdd(rest);
|
|
976
|
+
if (action === 'remove')
|
|
977
|
+
runSourcesRemove(rest);
|
|
978
|
+
die(`sources: unknown action "${action}". Expected: list, add, remove.`);
|
|
979
|
+
}
|
|
795
980
|
function collectPersonaRows() {
|
|
796
981
|
const rows = [];
|
|
797
982
|
const pushSpec = (spec, source) => {
|
|
@@ -897,7 +1082,7 @@ function parseListArgs(args) {
|
|
|
897
1082
|
json = true;
|
|
898
1083
|
}
|
|
899
1084
|
else if (arg === '-h' || arg === '--help') {
|
|
900
|
-
process.stdout.write(
|
|
1085
|
+
process.stdout.write('Usage: agentworkforce list [--all] [--json] [--filter-rating <tier>] [--filter-harness <harness>] [--filter-tag <tag>] [--no-display-description]\n');
|
|
901
1086
|
process.exit(0);
|
|
902
1087
|
}
|
|
903
1088
|
else if (arg === '--all' || arg === '--no-recommended') {
|
|
@@ -981,7 +1166,7 @@ function parseShowArgs(args) {
|
|
|
981
1166
|
all = true;
|
|
982
1167
|
}
|
|
983
1168
|
else if (arg === '-h' || arg === '--help') {
|
|
984
|
-
process.stdout.write(
|
|
1169
|
+
process.stdout.write('Usage: agentworkforce show <persona>[@<tier>] [--all] [--json]\n');
|
|
985
1170
|
process.exit(0);
|
|
986
1171
|
}
|
|
987
1172
|
else if (arg.startsWith('--')) {
|
|
@@ -1019,7 +1204,7 @@ function resolveShowTarget(selector, all) {
|
|
|
1019
1204
|
let source = 'library';
|
|
1020
1205
|
if (localSpec) {
|
|
1021
1206
|
spec = localSpec;
|
|
1022
|
-
source = local.sources.get(key) ?? '
|
|
1207
|
+
source = local.sources.get(key) ?? 'cwd';
|
|
1023
1208
|
}
|
|
1024
1209
|
else {
|
|
1025
1210
|
const byIntent = personaCatalog[key];
|
|
@@ -1175,6 +1360,9 @@ export async function main() {
|
|
|
1175
1360
|
if (subcommand === 'show') {
|
|
1176
1361
|
runShow(rest);
|
|
1177
1362
|
}
|
|
1363
|
+
if (subcommand === 'sources') {
|
|
1364
|
+
runSources(rest);
|
|
1365
|
+
}
|
|
1178
1366
|
if (subcommand === 'harness') {
|
|
1179
1367
|
const [action, ...extra] = rest;
|
|
1180
1368
|
if (!action || action === '-h' || action === '--help') {
|