@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 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
- # agent-workforce CLI
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 a user-local one that extends a built-in.
5
+ built-in one from `/personas/`, or an installed/local one that extends a lower
6
+ source.
6
7
 
7
8
  ```
8
- agent-workforce agent <persona>[@<tier>]
9
- agent-workforce list [flags]
10
- agent-workforce harness check
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
- The CLI ships under two npm names that point at the same code:
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 # provides `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 @agentworkforce/cli link --global
39
+ corepack pnpm --filter agentworkforce link --global
46
40
  ```
47
41
 
48
42
  ## Selectors
49
43
 
50
44
  ```
51
- agent-workforce agent <persona>[@<tier>]
45
+ agentworkforce agent <persona>[@<tier>]
52
46
  ```
53
47
 
54
48
  - `<persona>` — matches, in order:
55
- 1. A **pwd-local** id (files in `<cwd>/.agent-workforce/*.json`)
56
- 2. A **home-local** id (files in `~/.agent-workforce/*.json`)
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
- agent-workforce agent review@best-value
62
+ agentworkforce agent review@best-value
68
63
 
69
64
  # Interactive PostHog session (library persona, needs POSTHOG_API_KEY)
70
- agent-workforce agent posthog@best
65
+ agentworkforce agent posthog@best
71
66
 
72
67
  # Interactive against a local override
73
- agent-workforce agent my-posthog@best
68
+ agentworkforce agent my-posthog@best
74
69
  ```
75
70
 
76
71
  ## List
77
72
 
78
73
  ```
79
- agent-workforce list [flags]
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`, `home`, `pwd`),
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
- agent-workforce list
104
+ agentworkforce list
109
105
 
110
106
  # See every tier
111
- agent-workforce list --all
107
+ agentworkforce list --all
112
108
 
113
109
  # Only the top tier across the catalog — independent of recommendations
114
- agent-workforce list --filter-rating best
110
+ agentworkforce list --filter-rating best
115
111
 
116
112
  # All claude-harness personas (any tier)
117
- agent-workforce list --all --filter-harness claude
113
+ agentworkforce list --all --filter-harness claude
118
114
 
119
115
  # Compact table for a narrow terminal
120
- agent-workforce list --no-display-description
116
+ agentworkforce list --no-display-description
121
117
 
122
118
  # Machine-readable
123
- agent-workforce list --json --filter-harness claude
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
- agent-workforce harness check
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>/.agent-workforce/*.json` — **pwd**
173
- 2. `~/.agent-workforce/*.json` **home** (override path via
174
- `AGENT_WORKFORCE_CONFIG_DIR`)
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
- `~/.agent-workforce/my-posthog.json`:
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. `agent-workforce agent my-posthog@best` now works as long as
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>/.agent-workforce/posthog.json`:
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 pwd override first; it inherits the rest
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 pwd file can extend a home file, which extends the library:
253
+ A cwd file can extend a user or configured-dir file, which extends the library:
216
254
 
217
255
  ```
218
- ~/.agent-workforce/ph-base.json:
256
+ ~/.agentworkforce/workforce/personas/ph-base.json:
219
257
  { "id": "ph-base", "extends": "posthog", "env": { "POSTHOG_ORG": "acme" } }
220
258
 
221
- <cwd>/.agent-workforce/ph-prod.json:
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 home `ph-base` on top (adds `POSTHOG_ORG=acme`)
229
- - Layer pwd `ph-prod` on top (adds `POSTHOG_API_KEY`)
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** — pwd extends home or
232
- library, home extends library, library has no `extends`.
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, home can layer on
324
- project-wide denies, and pwd can add per-project patterns — they all
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
- agent-workforce agent [--install-in-repo] <persona>[@<tier>]
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
- agent-workforce agent --install-in-repo code-reviewer@best
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
- agent-workforce CLI just wires the paths and passes the persona's argv.
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
- agent-workforce agent posthog@best
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;AA6IzC;;;;;;;;;;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;AAwxBD,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAiD1C;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"}
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
- // Derived from the binary the user actually invoked. Lets the same dist file
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 (pwd home →
53
- library). By default shows one row per persona at the
54
- recommended tier for its intent; pass --all to see every
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 (pwd, home,
69
- library). By default shows only the recommended tier for
70
- the persona's intent; pass @<tier> to pick one, or --all
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>/.agent-workforce/*.json → ~/.agent-workforce/*.json → repo library.
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. (Override the home
81
- layer path via AGENT_WORKFORCE_CONFIG_DIR.)
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
- ${BIN_NAME} agent npm-provenance-publisher@best
85
- ${BIN_NAME} agent my-posthog@best
86
- ${BIN_NAME} agent review@best-value
87
- ${BIN_NAME} list
88
- ${BIN_NAME} show posthog
89
- ${BIN_NAME} harness check
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(`Usage: ${BIN_NAME} list [--all] [--json] [--filter-rating <tier>] [--filter-harness <harness>] [--filter-tag <tag>] [--no-display-description]\n`);
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(`Usage: ${BIN_NAME} show <persona>[@<tier>] [--all] [--json]\n`);
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) ?? 'pwd';
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') {