@agentworkforce/cli 2.1.4 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -5,10 +5,11 @@ import { appendFileSync, closeSync, existsSync, mkdirSync, mkdtempSync, openSync
5
5
  import { constants, homedir, tmpdir } from 'node:os';
6
6
  import { dirname, isAbsolute, join, resolve as resolvePath } from 'node:path';
7
7
  import { pathToFileURL } from 'node:url';
8
- import { buildCleanupArtifacts, buildInstallArtifacts, buildInteractiveSpec, buildNonInteractiveSpec, detectHarnesses, formatDropWarnings, HARNESS_VALUES, materializeSkills, MissingPersonaInputError, PERSONA_TAGS, PERSONA_TIERS, renderPersonaInputs, resolveMcpServersLenient, resolvePersonaInputs, resolveSidecar, resolveStringMapLenient } from '@agentworkforce/persona-kit';
8
+ import { buildCleanupArtifacts, buildInstallArtifacts, buildInteractiveSpec, buildNonInteractiveSpec, detectHarnesses, formatDropWarnings, HARNESS_VALUES, materializeSkills, MissingPersonaInputError, PERSONA_TAGS, renderPersonaInputs, resolveMcpServersLenient, resolvePersonaInputs, resolveSidecar, resolveStringMapLenient } from '@agentworkforce/persona-kit';
9
9
  import { listBuiltInPersonas, personaCatalog, routingProfiles } from '@agentworkforce/workload-router';
10
10
  import { createMount, readAgentDotfiles } from '@relayfile/local-mount';
11
11
  import ora from 'ora';
12
+ import { runDeploy, runLogin } from './deploy-command.js';
12
13
  import { startLaunchMetadataRecording } from './launch-metadata.js';
13
14
  import { buildPersonaSourceDirectories, defaultCwdPersonaDir, formatPersonaSourceLabel, loadLocalPersonas, loadPersonaSourceConfig, normalizePersonaDir, savePersonaSourceConfig } from './local-personas.js';
14
15
  import { installPersonas } from './persona-install.js';
@@ -23,7 +24,7 @@ is one of: built-in (bundled), cwd (./.agentworkforce/workforce/personas),
23
24
  personal (~/.agentworkforce/workforce/personas), or dir:N (configured).
24
25
 
25
26
  Commands:
26
- create [flags] Opens persona-maker@best for creating a new
27
+ create [flags] Opens persona-maker for creating a new
27
28
  persona, with target path passed as persona inputs.
28
29
  Flags:
29
30
  --save-in-directory=<target>
@@ -40,12 +41,8 @@ Commands:
40
41
  --install-in-repo Same behavior as agent.
41
42
  --no-launch-metadata
42
43
  Same behavior as agent.
43
- agent [flags] <persona>[@<tier>]
44
- Run a persona. Tier one of: ${PERSONA_TIERS.join(' | ')}.
45
- With no @<tier>, the resolution order is:
46
- routingProfiles.default.intents (built-in personas only)
47
- → persona.defaultTier (when set) → best-value. Drops into
48
- an interactive harness session.
44
+ agent [flags] <persona>
45
+ Run a persona. Drops into an interactive harness session.
49
46
 
50
47
  Flags:
51
48
  --install-in-repo Disengage the sandbox mount and
@@ -87,26 +84,16 @@ Commands:
87
84
  success, kept on failure for
88
85
  inspection.
89
86
  list [flags] List available personas from the cascade (cwd →
90
- configured persona dirs → library). By default shows
91
- one row per persona at the recommended tier for its
92
- intent; pass --all to see every tier. Flags:
93
- --all show every tier (overrides default)
87
+ configured persona dirs → library). Flags:
94
88
  --json emit JSON instead of a table
95
- --filter-rating <tier> only show this tier; disables
96
- the recommended-only default
97
- (${PERSONA_TIERS.join(' | ')})
98
89
  --filter-harness <harness> only show this harness
99
90
  (${HARNESS_VALUES.join(' | ')})
100
91
  --filter-tag <tag> only show personas carrying this tag
101
92
  (${PERSONA_TAGS.join(' | ')})
102
93
  --no-display-description hide the DESCRIPTION column
103
- show <persona>[@<tier>]
104
- Print the fully-resolved spec for a single persona,
94
+ show <persona> Print the fully-resolved spec for a single persona,
105
95
  including which cascade layer defined it (cwd, user,
106
- dir:<n>, library). By default shows only the recommended
107
- tier for the persona's intent; pass @<tier> to pick one,
108
- or --all to see every tier. Flags:
109
- --all include every tier (overrides default)
96
+ dir:<n>, library). Flags:
110
97
  --json emit the resolved PersonaSpec as JSON
111
98
  install [flags] <pkg|path>
112
99
  Copy persona JSON files from an npm package or local
@@ -147,8 +134,8 @@ Examples:
147
134
  agentworkforce create
148
135
  agentworkforce create --save-in-directory=user
149
136
  agentworkforce install @agentworkforce/personas-core --persona code-reviewer
150
- agentworkforce agent code-reviewer@best-value
151
- agentworkforce agent my-reviewer@best
137
+ agentworkforce agent code-reviewer
138
+ agentworkforce agent my-reviewer
152
139
  agentworkforce list
153
140
  agentworkforce show code-reviewer
154
141
  agentworkforce install @agentrelay/personas --persona relay-orchestrator
@@ -173,7 +160,7 @@ function readPackageVersion() {
173
160
  return pkg.version;
174
161
  }
175
162
  export const CLI_VERSION = readPackageVersion();
176
- export const CREATE_SELECTOR = 'persona-maker@best';
163
+ export const CREATE_SELECTOR = 'persona-maker';
177
164
  const CREATE_INPUT_TARGET_DIR = 'TARGET_DIR';
178
165
  const CREATE_INPUT_CREATE_MODE = 'CREATE_MODE';
179
166
  const local = loadLocalPersonas();
@@ -236,31 +223,25 @@ function resolveSpec(key) {
236
223
  };
237
224
  }
238
225
  function parseSelector(sel) {
226
+ // Catch legacy `@<tier>` selectors and point users at the new selector form.
227
+ // Tiers were removed; a persona's runtime fields now live at the top level.
239
228
  const at = sel.indexOf('@');
240
- const key = at === -1 ? sel : sel.slice(0, at);
241
- const tierRaw = at === -1 ? undefined : sel.slice(at + 1);
242
- if (!key)
243
- die('Missing persona name before "@"');
244
- if (tierRaw !== undefined && !PERSONA_TIERS.includes(tierRaw)) {
245
- die(`Invalid tier "${tierRaw}". Must be one of: ${PERSONA_TIERS.join(', ')}`);
229
+ if (at !== -1) {
230
+ const suffix = sel.slice(at + 1);
231
+ die(`@<tier> selectors were removed; tiers are no longer part of the persona shape. ` +
232
+ `Use 'agentworkforce agent ${sel.slice(0, at)}' instead (drop "@${suffix}").`, false);
246
233
  }
234
+ const key = sel;
235
+ if (!key)
236
+ die('Missing persona name');
247
237
  const result = resolveSpec(key);
248
238
  if ('error' in result)
249
239
  die(result.error, false);
250
240
  const kind = local.byId.has(key) ? 'local' : 'repo';
251
- // Resolution order when no @<tier> is given: routingProfiles default for the
252
- // persona's intent (built-ins only — local personas with custom intents miss
253
- // the lookup and fall through), then the persona's own defaultTier, then
254
- // 'best-value'. Mirrors `resolveShowTarget` and the `list` recommended-tier
255
- // filter so all three commands agree on what "no tier" means.
256
- const profileRule = kind === 'repo'
257
- ? routingProfiles.default.intents[result.intent]
258
- : undefined;
259
- const tier = (tierRaw ?? profileRule?.tier ?? result.defaultTier ?? 'best-value');
260
241
  if (kind === 'local') {
261
- return { kind, source: local.sources.get(result.id) ?? 'cwd', spec: result, tier };
242
+ return { kind, source: local.sources.get(result.id) ?? 'cwd', spec: result };
262
243
  }
263
- return { kind, source: 'library', spec: result, tier };
244
+ return { kind, source: 'library', spec: result };
264
245
  }
265
246
  /**
266
247
  * Resolve the `<harness>` placeholder used in persona systemPrompts.
@@ -276,19 +257,22 @@ function parseSelector(sel) {
276
257
  export function resolveSystemPromptPlaceholders(prompt, harness) {
277
258
  return prompt.replaceAll('<harness>', harness);
278
259
  }
279
- function buildSelection(spec, tier, kind) {
280
- const rawRuntime = spec.tiers[tier];
281
- const runtime = {
282
- ...rawRuntime,
283
- systemPrompt: resolveSystemPromptPlaceholders(rawRuntime.systemPrompt, rawRuntime.harness)
284
- };
285
- const sidecar = resolveSidecar(spec, tier);
260
+ function buildSelection(spec, kind) {
261
+ const systemPrompt = resolveSystemPromptPlaceholders(spec.systemPrompt, spec.harness);
262
+ const sidecar = resolveSidecar(spec);
263
+ // Built-in personas: prefer the routing-profile rationale string so the
264
+ // selection carries the policy-explained "why" rather than a generic label.
265
+ const rationale = kind === 'local'
266
+ ? `local-override: ${spec.id}`
267
+ : routingProfiles.default.intents[spec.intent]?.rationale ?? `cli: ${spec.id}`;
286
268
  return {
287
269
  personaId: spec.id,
288
- tier,
289
- runtime,
270
+ harness: spec.harness,
271
+ model: spec.model,
272
+ systemPrompt,
273
+ harnessSettings: spec.harnessSettings,
290
274
  skills: spec.skills,
291
- rationale: kind === 'local' ? `local-override: ${spec.id}` : `cli-tier-override: ${tier}`,
275
+ rationale,
292
276
  ...(spec.inputs ? { inputs: spec.inputs } : {}),
293
277
  ...(spec.env ? { env: spec.env } : {}),
294
278
  ...(spec.mcpServers ? { mcpServers: spec.mcpServers } : {}),
@@ -426,7 +410,7 @@ async function runInstallOrThrow(command, label, cwd) {
426
410
  }
427
411
  }
428
412
  function buildInstallContext(selection, options = {}) {
429
- const plan = materializeSkills(selection.skills, selection.runtime.harness, {
413
+ const plan = materializeSkills(selection.skills, selection.harness, {
430
414
  ...(options.installRoot !== undefined ? { installRoot: options.installRoot } : {}),
431
415
  ...(options.repoRoot !== undefined ? { repoRoot: options.repoRoot } : {})
432
416
  });
@@ -676,7 +660,7 @@ export function configureGitForMount(mountDir, patterns) {
676
660
  * failing the whole session.
677
661
  */
678
662
  export function loadSidecarForSelection(selection) {
679
- const harness = selection.runtime.harness;
663
+ const harness = selection.harness;
680
664
  if (harness !== 'claude' && harness !== 'opencode' && harness !== 'codex')
681
665
  return {};
682
666
  if (harness === 'claude') {
@@ -803,7 +787,7 @@ export function decideCleanMode(harness, installInRepo = false) {
803
787
  */
804
788
  function runDryRun(selection) {
805
789
  const inputResolution = resolvePersonaInputs(selection.inputs, selection.inputValues, process.env);
806
- const renderedSystemPrompt = renderPersonaInputs(selection.runtime.systemPrompt, inputResolution.values);
790
+ const renderedSystemPrompt = renderPersonaInputs(selection.systemPrompt, inputResolution.values);
807
791
  const renderedClaudeContent = selection.claudeMdContent !== undefined
808
792
  ? renderPersonaInputs(selection.claudeMdContent, inputResolution.values)
809
793
  : undefined;
@@ -812,12 +796,12 @@ function runDryRun(selection) {
812
796
  : undefined;
813
797
  const effectiveSelection = {
814
798
  ...selection,
815
- runtime: { ...selection.runtime, systemPrompt: renderedSystemPrompt },
799
+ systemPrompt: renderedSystemPrompt,
816
800
  ...(renderedClaudeContent !== undefined ? { claudeMdContent: renderedClaudeContent } : {}),
817
801
  ...(renderedAgentsContent !== undefined ? { agentsMdContent: renderedAgentsContent } : {})
818
802
  };
819
- const { runtime, personaId, tier } = effectiveSelection;
820
- process.stderr.write(`→ ${personaId} [${tier}] via ${runtime.harness} (${runtime.model}) [DRY-RUN]\n`);
803
+ const { personaId, harness, model, harnessSettings, systemPrompt } = effectiveSelection;
804
+ process.stderr.write(`→ ${personaId} via ${harness} (${model}) [DRY-RUN]\n`);
821
805
  // Check 1: sidecar resolution. A loadSidecarForSelection warning means
822
806
  // the persona points `claudeMd` / `agentsMd` at a file we couldn't
823
807
  // read; the launch path degrades to a warning today, which silently
@@ -839,11 +823,11 @@ function runDryRun(selection) {
839
823
  let spec;
840
824
  try {
841
825
  spec = buildInteractiveSpec({
842
- harness: runtime.harness,
826
+ harness,
843
827
  personaId,
844
- model: runtime.model,
845
- systemPrompt: runtime.systemPrompt,
846
- harnessSettings: runtime.harnessSettings,
828
+ model,
829
+ systemPrompt,
830
+ harnessSettings,
847
831
  mcpServers: mcpResolution.servers,
848
832
  permissions: effectiveSelection.permissions
849
833
  });
@@ -860,7 +844,7 @@ function runDryRun(selection) {
860
844
  // Dry-run runs each install inside a fresh tempDir (see `cwd: tempDir` on
861
845
  // the spawnSync below). Pass repoRoot=process.cwd() so `local`-kind skills
862
846
  // resolve their relative source paths against the real repo, not the tmp.
863
- const plan = materializeSkills(effectiveSelection.skills, runtime.harness, {
847
+ const plan = materializeSkills(effectiveSelection.skills, harness, {
864
848
  repoRoot: process.cwd()
865
849
  });
866
850
  if (plan.installs.length === 0) {
@@ -909,7 +893,7 @@ function runDryRun(selection) {
909
893
  }
910
894
  async function runInteractive(selection, options) {
911
895
  const inputResolution = resolvePersonaInputs(selection.inputs, selection.inputValues, process.env);
912
- const renderedSystemPrompt = renderPersonaInputs(selection.runtime.systemPrompt, inputResolution.values);
896
+ const renderedSystemPrompt = renderPersonaInputs(selection.systemPrompt, inputResolution.values);
913
897
  // Render input placeholders ($TARGET_DIR, ${CREATE_MODE}, …) inside the
914
898
  // sidecar Content fields too. Personas that move heavy authoring guidance
915
899
  // out of the systemPrompt and into AGENTS.md / CLAUDE.md still want
@@ -923,21 +907,18 @@ async function runInteractive(selection, options) {
923
907
  : undefined;
924
908
  const effectiveSelection = {
925
909
  ...selection,
926
- runtime: {
927
- ...selection.runtime,
928
- systemPrompt: renderedSystemPrompt
929
- },
910
+ systemPrompt: renderedSystemPrompt,
930
911
  ...(renderedClaudeContent !== undefined ? { claudeMdContent: renderedClaudeContent } : {}),
931
912
  ...(renderedAgentsContent !== undefined ? { agentsMdContent: renderedAgentsContent } : {})
932
913
  };
933
- const { runtime, personaId, tier } = effectiveSelection;
914
+ const { personaId, harness, model, harnessSettings, systemPrompt } = effectiveSelection;
934
915
  // `installRoot` (out-of-repo skill staging via `--plugin-dir`) is currently
935
916
  // claude-only; the workload-router SDK throws if it's set for other
936
917
  // harnesses. For opencode, we instead keep installs out of the repo by
937
918
  // running them inside a @relayfile/local-mount sandbox (see `useClean`
938
919
  // below). The --install-in-repo flag forces legacy in-repo installs
939
920
  // across the board.
940
- const useClean = decideCleanMode(runtime.harness, options.installInRepo === true).useClean;
921
+ const useClean = decideCleanMode(harness, options.installInRepo === true).useClean;
941
922
  // Per-persona CLAUDE.md / AGENTS.md: load the author content if any. The
942
923
  // file is materialized into the mount inside onBeforeLaunch. Without a
943
924
  // mount (--install-in-repo) we skip-and-warn — writing into the real cwd
@@ -954,9 +935,9 @@ async function runInteractive(selection, options) {
954
935
  // A session dir is needed whenever we either (a) stage skills out-of-repo
955
936
  // via claude's installRoot, or (b) open a mount. Both engage for claude/
956
937
  // opencode by default; --install-in-repo disengages both.
957
- const useSessionDir = !options.installInRepo && (runtime.harness === 'claude' || useClean);
938
+ const useSessionDir = !options.installInRepo && (harness === 'claude' || useClean);
958
939
  const sessionRoot = useSessionDir ? generateSessionRoot(personaId) : undefined;
959
- const installRoot = sessionRoot && runtime.harness === 'claude'
940
+ const installRoot = sessionRoot && harness === 'claude'
960
941
  ? sessionInstallRoot(sessionRoot)
961
942
  : undefined;
962
943
  // `repoRoot` lets the local skill provider (kind: 'local') resolve
@@ -968,7 +949,7 @@ async function runInteractive(selection, options) {
968
949
  ...(installRoot !== undefined ? { installRoot } : {}),
969
950
  repoRoot: process.cwd()
970
951
  });
971
- process.stderr.write(`→ ${personaId} [${tier}] via ${runtime.harness} (${runtime.model})\n`);
952
+ process.stderr.write(`→ ${personaId} via ${harness} (${model})\n`);
972
953
  const startLaunchMetadataForLaunch = (cwd = process.cwd()) => startLaunchMetadataRecording({
973
954
  selection: effectiveSelection,
974
955
  personaSpec: options.personaSpec,
@@ -998,16 +979,16 @@ async function runInteractive(selection, options) {
998
979
  // INSIDE the mount so `.opencode/skills/`, `.agents/skills/`, prpm.lock,
999
980
  // etc. land in the sandbox rather than the real repo. We defer it to
1000
981
  // `onBeforeLaunch` below instead of pre-running here.
1001
- const deferInstallToMount = useClean && runtime.harness !== 'claude' && install.commandString !== ':';
982
+ const deferInstallToMount = useClean && harness !== 'claude' && install.commandString !== ':';
1002
983
  if (install.commandString !== ':' && !deferInstallToMount) {
1003
984
  await runInstall(install.command, installLabel);
1004
985
  }
1005
986
  const spec = buildInteractiveSpec({
1006
- harness: runtime.harness,
987
+ harness,
1007
988
  personaId,
1008
- model: runtime.model,
1009
- systemPrompt: runtime.systemPrompt,
1010
- harnessSettings: runtime.harnessSettings,
989
+ model,
990
+ systemPrompt,
991
+ harnessSettings,
1011
992
  mcpServers: resolvedMcp,
1012
993
  permissions: effectiveSelection.permissions,
1013
994
  ...(installRoot !== undefined ? { pluginDirs: [installRoot] } : {})
@@ -1040,8 +1021,8 @@ async function runInteractive(selection, options) {
1040
1021
  // env refs are interpolated. We show the bin, model, and the *names* of
1041
1022
  // the servers / permission fields so the user can verify the shape without
1042
1023
  // leaking credentials to stderr or CI logs.
1043
- const summary = [`model=${runtime.model}`];
1044
- if (runtime.harness === 'claude') {
1024
+ const summary = [`model=${model}`];
1025
+ if (harness === 'claude') {
1045
1026
  const servers = Object.keys(resolvedMcp ?? {});
1046
1027
  summary.push(`mcp-strict=${servers.length ? servers.join(',') : '(none)'}`);
1047
1028
  if (effectiveSelection.permissions?.allow?.length) {
@@ -1086,7 +1067,7 @@ async function runInteractive(selection, options) {
1086
1067
  const { ignoredPatterns, readonlyPatterns } = buildRelayfileMountPatterns({
1087
1068
  projectDir: process.cwd(),
1088
1069
  personaId,
1089
- harness: runtime.harness,
1070
+ harness,
1090
1071
  mount: effectiveSelection.mount,
1091
1072
  configFilePaths: spec.configFiles.map((file) => file.path)
1092
1073
  });
@@ -1225,7 +1206,7 @@ async function runInteractive(selection, options) {
1225
1206
  const childCwd = handle.mountDir;
1226
1207
  if (options.capture) {
1227
1208
  options.capture.sessionCwd = childCwd;
1228
- options.capture.harness = runtime.harness;
1209
+ options.capture.harness = harness;
1229
1210
  options.capture.startedAt = Date.now();
1230
1211
  }
1231
1212
  // Flip the SIGINT phase flag before spawn so a Ctrl-C arriving during
@@ -1294,7 +1275,7 @@ async function runInteractive(selection, options) {
1294
1275
  }
1295
1276
  const e = err;
1296
1277
  if (e.code === 'ENOENT') {
1297
- process.stderr.write(`Failed to spawn "${spec.bin}" inside sandbox mount: binary not found on PATH. Install the ${runtime.harness} CLI and retry.\n`);
1278
+ process.stderr.write(`Failed to spawn "${spec.bin}" inside sandbox mount: binary not found on PATH. Install the ${harness} CLI and retry.\n`);
1298
1279
  return 127;
1299
1280
  }
1300
1281
  process.stderr.write(`Failed to launch sandbox mount: ${e.message}\n`);
@@ -1337,7 +1318,7 @@ async function runInteractive(selection, options) {
1337
1318
  const launchMetadata = await startLaunchMetadataForLaunch();
1338
1319
  if (options.capture) {
1339
1320
  options.capture.sessionCwd = process.cwd();
1340
- options.capture.harness = runtime.harness;
1321
+ options.capture.harness = harness;
1341
1322
  options.capture.startedAt = Date.now();
1342
1323
  options.capture.stampEnrichment = { ...launchMetadata.metadata };
1343
1324
  options.capture.stampingEnabled = launchMetadata.enabled;
@@ -1367,7 +1348,7 @@ async function runInteractive(selection, options) {
1367
1348
  });
1368
1349
  child.on('error', (err) => {
1369
1350
  if (err.code === 'ENOENT') {
1370
- process.stderr.write(`Failed to spawn "${spec.bin}": binary not found on PATH. Install the ${runtime.harness} CLI and retry.\n`);
1351
+ process.stderr.write(`Failed to spawn "${spec.bin}": binary not found on PATH. Install the ${harness} CLI and retry.\n`);
1371
1352
  }
1372
1353
  else {
1373
1354
  process.stderr.write(`Failed to spawn "${spec.bin}": ${err.message}\n`);
@@ -1675,19 +1656,15 @@ function runPersonaInstall(args) {
1675
1656
  function collectPersonaRows() {
1676
1657
  const rows = [];
1677
1658
  const pushSpec = (spec, source) => {
1678
- for (const tier of PERSONA_TIERS) {
1679
- rows.push({
1680
- persona: spec.id,
1681
- source,
1682
- harness: spec.tiers[tier].harness,
1683
- model: spec.tiers[tier].model,
1684
- intent: spec.intent,
1685
- tags: spec.tags,
1686
- description: spec.description,
1687
- rating: tier,
1688
- defaultTier: spec.defaultTier
1689
- });
1690
- }
1659
+ rows.push({
1660
+ persona: spec.id,
1661
+ source,
1662
+ harness: spec.harness,
1663
+ model: spec.model,
1664
+ intent: spec.intent,
1665
+ tags: spec.tags,
1666
+ description: spec.description
1667
+ });
1691
1668
  };
1692
1669
  const seen = new Set();
1693
1670
  for (const [id, spec] of local.byId) {
@@ -1699,9 +1676,7 @@ function collectPersonaRows() {
1699
1676
  continue;
1700
1677
  pushSpec(spec, 'library');
1701
1678
  }
1702
- const tierOrder = new Map(PERSONA_TIERS.map((t, i) => [t, i]));
1703
- return rows.sort((a, b) => a.persona.localeCompare(b.persona) ||
1704
- (tierOrder.get(a.rating) - tierOrder.get(b.rating)));
1679
+ return rows.sort((a, b) => a.persona.localeCompare(b.persona));
1705
1680
  }
1706
1681
  function formatPersonaTable(rows, display) {
1707
1682
  const headers = {
@@ -1709,18 +1684,14 @@ function formatPersonaTable(rows, display) {
1709
1684
  source: 'SOURCE',
1710
1685
  harness: 'HARNESS',
1711
1686
  model: 'MODEL',
1712
- rating: 'RATING',
1713
1687
  tags: 'TAGS',
1714
1688
  description: 'DESCRIPTION'
1715
1689
  };
1716
1690
  const rendered = rows.map((r) => ({
1717
1691
  persona: r.persona,
1718
- // Show the user-facing label (`built-in` / `repo` / `personal` / `dir:N`).
1719
- // The internal cascade key is still in `--json` output for tooling.
1720
1692
  source: formatPersonaSourceLabel(r.source),
1721
1693
  harness: r.harness,
1722
1694
  model: r.model,
1723
- rating: r.rating,
1724
1695
  tags: r.tags.join(','),
1725
1696
  description: r.description
1726
1697
  }));
@@ -1729,7 +1700,6 @@ function formatPersonaTable(rows, display) {
1729
1700
  source: Math.max(headers.source.length, ...rendered.map((r) => r.source.length)),
1730
1701
  harness: Math.max(headers.harness.length, ...rendered.map((r) => r.harness.length)),
1731
1702
  model: Math.max(headers.model.length, ...rendered.map((r) => r.model.length)),
1732
- rating: Math.max(headers.rating.length, ...rendered.map((r) => r.rating.length)),
1733
1703
  tags: Math.max(headers.tags.length, ...rendered.map((r) => r.tags.length)),
1734
1704
  description: headers.description.length
1735
1705
  };
@@ -1738,9 +1708,8 @@ function formatPersonaTable(rows, display) {
1738
1708
  widths.source +
1739
1709
  widths.harness +
1740
1710
  widths.model +
1741
- widths.rating +
1742
1711
  widths.tags +
1743
- (6 + (display.description ? 1 : 0) - 1) * 2;
1712
+ (5 + (display.description ? 1 : 0) - 1) * 2;
1744
1713
  const descBudget = Math.max(20, termWidth - fixed - 1);
1745
1714
  const truncate = (s, n) => (s.length <= n ? s : s.slice(0, Math.max(1, n - 1)) + '…');
1746
1715
  const line = (row) => {
@@ -1749,7 +1718,6 @@ function formatPersonaTable(rows, display) {
1749
1718
  row.source.padEnd(widths.source),
1750
1719
  row.harness.padEnd(widths.harness),
1751
1720
  row.model.padEnd(widths.model),
1752
- row.rating.padEnd(widths.rating),
1753
1721
  row.tags.padEnd(widths.tags)
1754
1722
  ];
1755
1723
  if (display.description) {
@@ -1761,11 +1729,8 @@ function formatPersonaTable(rows, display) {
1761
1729
  }
1762
1730
  function parseListArgs(args) {
1763
1731
  let json = false;
1764
- let filterRating;
1765
- let filterRatingExplicit = false;
1766
1732
  let filterHarness;
1767
1733
  let filterTag;
1768
- let showAll = false;
1769
1734
  const display = { description: true };
1770
1735
  const valueOf = (i, flag) => {
1771
1736
  const v = args[i + 1];
@@ -1780,23 +1745,9 @@ function parseListArgs(args) {
1780
1745
  json = true;
1781
1746
  }
1782
1747
  else if (arg === '-h' || arg === '--help') {
1783
- process.stdout.write('Usage: agentworkforce list [--all] [--json] [--filter-rating <tier>] [--filter-harness <harness>] [--filter-tag <tag>] [--no-display-description]\n');
1748
+ process.stdout.write('Usage: agentworkforce list [--json] [--filter-harness <harness>] [--filter-tag <tag>] [--no-display-description]\n');
1784
1749
  process.exit(0);
1785
1750
  }
1786
- else if (arg === '--all' || arg === '--no-recommended') {
1787
- showAll = true;
1788
- }
1789
- else if (arg === '--recommended') {
1790
- showAll = false;
1791
- }
1792
- else if (arg === '--filter-rating') {
1793
- const v = valueOf(i++, arg);
1794
- if (!PERSONA_TIERS.includes(v)) {
1795
- die(`list: invalid --filter-rating "${v}". Must be one of: ${PERSONA_TIERS.join(', ')}`);
1796
- }
1797
- filterRating = v;
1798
- filterRatingExplicit = true;
1799
- }
1800
1751
  else if (arg === '--filter-harness') {
1801
1752
  const v = valueOf(i++, arg);
1802
1753
  if (!HARNESS_VALUES.includes(v)) {
@@ -1821,24 +1772,15 @@ function parseListArgs(args) {
1821
1772
  die(`list: unexpected argument "${arg}".`);
1822
1773
  }
1823
1774
  }
1824
- return { json, filterRating, filterHarness, filterTag, display, showAll, filterRatingExplicit };
1775
+ return { json, filterHarness, filterTag, display };
1825
1776
  }
1826
1777
  function runList(args) {
1827
- const { json, filterRating, filterHarness, filterTag, display, showAll, filterRatingExplicit } = parseListArgs(args);
1828
- const recommendedByIntent = routingProfiles.default.intents;
1829
- const applyRecommended = !showAll && !filterRatingExplicit;
1778
+ const { json, filterHarness, filterTag, display } = parseListArgs(args);
1830
1779
  const rows = collectPersonaRows().filter((r) => {
1831
- if (filterRating && r.rating !== filterRating)
1832
- return false;
1833
1780
  if (filterHarness && r.harness !== filterHarness)
1834
1781
  return false;
1835
1782
  if (filterTag && !r.tags.includes(filterTag))
1836
1783
  return false;
1837
- if (applyRecommended) {
1838
- const rule = recommendedByIntent[r.intent];
1839
- if (r.rating !== (rule?.tier ?? r.defaultTier ?? 'best-value'))
1840
- return false;
1841
- }
1842
1784
  return true;
1843
1785
  });
1844
1786
  if (json) {
@@ -1846,25 +1788,19 @@ function runList(args) {
1846
1788
  }
1847
1789
  else {
1848
1790
  process.stdout.write(formatPersonaTable(rows, display));
1849
- const uniq = new Set(rows.map((r) => r.persona)).size;
1850
- const suffix = applyRecommended ? ' (recommended tier per intent; pass --all to see every tier)' : '';
1851
- process.stdout.write(`\n${uniq} persona(s), ${rows.length} row(s)${suffix}.\n`);
1791
+ process.stdout.write(`\n${rows.length} persona(s).\n`);
1852
1792
  }
1853
1793
  process.exit(0);
1854
1794
  }
1855
1795
  function parseShowArgs(args) {
1856
1796
  let json = false;
1857
- let all = false;
1858
1797
  let selector;
1859
1798
  for (const arg of args) {
1860
1799
  if (arg === '--json') {
1861
1800
  json = true;
1862
1801
  }
1863
- else if (arg === '--all') {
1864
- all = true;
1865
- }
1866
1802
  else if (arg === '-h' || arg === '--help') {
1867
- process.stdout.write('Usage: agentworkforce show <persona>[@<tier>] [--all] [--json]\n');
1803
+ process.stdout.write('Usage: agentworkforce show <persona> [--json]\n');
1868
1804
  process.exit(0);
1869
1805
  }
1870
1806
  else if (arg.startsWith('--')) {
@@ -1879,24 +1815,16 @@ function parseShowArgs(args) {
1879
1815
  }
1880
1816
  if (!selector)
1881
1817
  die('show: missing persona name.');
1882
- return { selector, json, all };
1818
+ return { selector, json };
1883
1819
  }
1884
- function resolveShowTarget(selector, all) {
1885
- const at = selector.indexOf('@');
1886
- const key = at === -1 ? selector : selector.slice(0, at);
1887
- const tierRaw = at === -1 ? undefined : selector.slice(at + 1);
1888
- if (!key)
1889
- die('show: missing persona name before "@".');
1890
- let explicitTier;
1891
- if (tierRaw !== undefined) {
1892
- if (!PERSONA_TIERS.includes(tierRaw)) {
1893
- die(`show: invalid tier "${tierRaw}". Must be one of: ${PERSONA_TIERS.join(', ')}`);
1894
- }
1895
- explicitTier = tierRaw;
1896
- if (all) {
1897
- die('show: --all cannot be combined with an explicit @<tier> suffix.');
1898
- }
1820
+ function resolveShowTarget(selector) {
1821
+ if (selector.includes('@')) {
1822
+ die('show: @<tier> selectors were removed; tiers are no longer part of the persona shape. ' +
1823
+ `Use 'agentworkforce show ${selector.slice(0, selector.indexOf('@'))}' instead.`, false);
1899
1824
  }
1825
+ const key = selector;
1826
+ if (!key)
1827
+ die('show: missing persona name.');
1900
1828
  const localSpec = local.byId.get(key);
1901
1829
  let spec;
1902
1830
  let source = 'library';
@@ -1921,18 +1849,7 @@ function resolveShowTarget(selector, all) {
1921
1849
  die(result.error, false);
1922
1850
  spec = result;
1923
1851
  }
1924
- let tiers;
1925
- if (all) {
1926
- tiers = [...PERSONA_TIERS];
1927
- }
1928
- else if (explicitTier) {
1929
- tiers = [explicitTier];
1930
- }
1931
- else {
1932
- const rule = routingProfiles.default.intents[spec.intent];
1933
- tiers = [rule?.tier ?? spec.defaultTier ?? 'best-value'];
1934
- }
1935
- return { spec, source, tiers, explicitTier };
1852
+ return { spec, source };
1936
1853
  }
1937
1854
  function indent(text, prefix) {
1938
1855
  return text
@@ -1940,17 +1857,13 @@ function indent(text, prefix) {
1940
1857
  .map((line) => (line.length > 0 ? prefix + line : line))
1941
1858
  .join('\n');
1942
1859
  }
1943
- function formatPersonaShow(spec, source, tiers, tierNote) {
1860
+ function formatPersonaShow(spec, source) {
1944
1861
  const lines = [];
1945
1862
  lines.push(`PERSONA ${spec.id}`);
1946
1863
  lines.push(`SOURCE ${source}`);
1947
1864
  lines.push(`INTENT ${spec.intent}`);
1948
1865
  lines.push(`TAGS ${spec.tags.length ? spec.tags.join(', ') : '(none)'}`);
1949
1866
  lines.push(`DESCRIPTION ${spec.description}`);
1950
- if (spec.defaultTier) {
1951
- lines.push(`DEFAULT TIER ${spec.defaultTier}`);
1952
- }
1953
- lines.push(`TIERS SHOWN ${tiers.join(', ')}${tierNote ? ` (${tierNote})` : ''}`);
1954
1867
  lines.push('');
1955
1868
  lines.push('SKILLS');
1956
1869
  if (spec.skills.length === 0) {
@@ -2039,46 +1952,36 @@ function formatPersonaShow(spec, source, tiers, tierNote) {
2039
1952
  for (const k of envKeys)
2040
1953
  lines.push(` ${k}=${spec.env[k]}`);
2041
1954
  }
2042
- for (const tier of tiers) {
2043
- const rt = spec.tiers[tier];
2044
- lines.push('');
2045
- lines.push(`TIER: ${tier}`);
2046
- lines.push(` harness: ${rt.harness}`);
2047
- lines.push(` model: ${rt.model}`);
2048
- lines.push(` reasoning: ${rt.harnessSettings.reasoning}`);
2049
- lines.push(` timeout: ${rt.harnessSettings.timeoutSeconds}s`);
2050
- if (rt.harnessSettings.sandboxMode) {
2051
- lines.push(` sandbox: ${rt.harnessSettings.sandboxMode}`);
2052
- }
2053
- if (rt.harnessSettings.approvalPolicy) {
2054
- lines.push(` approvals: ${rt.harnessSettings.approvalPolicy}`);
2055
- }
2056
- if (rt.harnessSettings.workspaceWriteNetworkAccess !== undefined) {
2057
- lines.push(` network: ${rt.harnessSettings.workspaceWriteNetworkAccess}`);
2058
- }
2059
- if (rt.harnessSettings.webSearch !== undefined) {
2060
- lines.push(` webSearch: ${rt.harnessSettings.webSearch}`);
2061
- }
2062
- lines.push(' systemPrompt:');
2063
- lines.push(indent(rt.systemPrompt, ' '));
1955
+ lines.push('');
1956
+ lines.push('RUNTIME');
1957
+ lines.push(` harness: ${spec.harness}`);
1958
+ lines.push(` model: ${spec.model}`);
1959
+ lines.push(` reasoning: ${spec.harnessSettings.reasoning}`);
1960
+ lines.push(` timeout: ${spec.harnessSettings.timeoutSeconds}s`);
1961
+ if (spec.harnessSettings.sandboxMode) {
1962
+ lines.push(` sandbox: ${spec.harnessSettings.sandboxMode}`);
1963
+ }
1964
+ if (spec.harnessSettings.approvalPolicy) {
1965
+ lines.push(` approvals: ${spec.harnessSettings.approvalPolicy}`);
2064
1966
  }
1967
+ if (spec.harnessSettings.workspaceWriteNetworkAccess !== undefined) {
1968
+ lines.push(` network: ${spec.harnessSettings.workspaceWriteNetworkAccess}`);
1969
+ }
1970
+ if (spec.harnessSettings.webSearch !== undefined) {
1971
+ lines.push(` webSearch: ${spec.harnessSettings.webSearch}`);
1972
+ }
1973
+ lines.push(' systemPrompt:');
1974
+ lines.push(indent(spec.systemPrompt, ' '));
2065
1975
  return lines.join('\n') + '\n';
2066
1976
  }
2067
1977
  function runShow(args) {
2068
- const { selector, json, all } = parseShowArgs(args);
2069
- const { spec, source, tiers, explicitTier } = resolveShowTarget(selector, all);
2070
- const tierNote = all
2071
- ? 'all tiers'
2072
- : explicitTier
2073
- ? 'explicit @<tier>'
2074
- : 'recommended for intent; pass --all or @<tier> to override';
1978
+ const { selector, json } = parseShowArgs(args);
1979
+ const { spec, source } = resolveShowTarget(selector);
2075
1980
  if (json) {
2076
- const projectedTiers = Object.fromEntries(tiers.map((t) => [t, spec.tiers[t]]));
2077
- const projected = { ...spec, tiers: projectedTiers };
2078
- process.stdout.write(JSON.stringify({ source, spec: projected }, null, 2) + '\n');
1981
+ process.stdout.write(JSON.stringify({ source, spec }, null, 2) + '\n');
2079
1982
  }
2080
1983
  else {
2081
- process.stdout.write(formatPersonaShow(spec, source, tiers, tierNote));
1984
+ process.stdout.write(formatPersonaShow(spec, source));
2082
1985
  }
2083
1986
  process.exit(0);
2084
1987
  }
@@ -2165,7 +2068,7 @@ function saveDefaultCreateTarget(target) {
2165
2068
  async function runAgentSelector(selector, flags, inputValues) {
2166
2069
  const target = parseSelector(selector);
2167
2070
  const selection = {
2168
- ...buildSelection(target.spec, target.tier, target.kind),
2071
+ ...buildSelection(target.spec, target.kind),
2169
2072
  ...(inputValues ? { inputValues } : {})
2170
2073
  };
2171
2074
  if (flags.dryRun) {
@@ -2302,9 +2205,7 @@ const ALLOWED_SET_PATHS = [
2302
2205
  'agentsMdContent',
2303
2206
  'claudeMdContent',
2304
2207
  'tags',
2305
- 'tiers.best.systemPrompt',
2306
- 'tiers.best-value.systemPrompt',
2307
- 'tiers.minimum.systemPrompt'
2208
+ 'systemPrompt'
2308
2209
  ];
2309
2210
  /**
2310
2211
  * Allowlist of dot-paths the improver may rewrite via `op: "append"`.
@@ -2750,15 +2651,14 @@ async function runPersonaImprover(args) {
2750
2651
  if (!improverSpec) {
2751
2652
  throw new Error('built-in persona "persona-improver" is not registered in the catalog');
2752
2653
  }
2753
- const tier = 'best-value';
2754
- const selection = buildSelection(improverSpec, tier, 'repo');
2654
+ const selection = buildSelection(improverSpec, 'repo');
2755
2655
  const inputValues = {
2756
2656
  PERSONA_FILE_PATH: args.personaFilePath,
2757
2657
  SESSION_TRANSCRIPT_PATH: args.transcriptPath,
2758
2658
  PROPOSALS_OUTPUT_PATH: args.proposalsOutputPath
2759
2659
  };
2760
2660
  const inputResolution = resolvePersonaInputs(selection.inputs, inputValues, process.env);
2761
- const renderedSystemPrompt = renderPersonaInputs(selection.runtime.systemPrompt, inputResolution.values);
2661
+ const renderedSystemPrompt = renderPersonaInputs(selection.systemPrompt, inputResolution.values);
2762
2662
  const callerEnv = { ...process.env, ...inputResolution.values };
2763
2663
  const envResolution = resolveStringMapLenient(selection.env, callerEnv, 'env');
2764
2664
  const mcpResolution = resolveMcpServersLenient(selection.mcpServers, callerEnv);
@@ -2770,11 +2670,11 @@ async function runPersonaImprover(args) {
2770
2670
  ].join('\n');
2771
2671
  const task = `${taskBody}\n\nRun inputs:\n${JSON.stringify(inputValues, null, 2)}`;
2772
2672
  const spec = buildNonInteractiveSpec({
2773
- harness: selection.runtime.harness,
2673
+ harness: selection.harness,
2774
2674
  personaId: selection.personaId,
2775
- model: selection.runtime.model,
2675
+ model: selection.model,
2776
2676
  systemPrompt: renderedSystemPrompt,
2777
- harnessSettings: selection.runtime.harnessSettings,
2677
+ harnessSettings: selection.harnessSettings,
2778
2678
  mcpServers: mcpResolution.servers,
2779
2679
  permissions: selection.permissions,
2780
2680
  task
@@ -2801,8 +2701,8 @@ async function runPersonaImprover(args) {
2801
2701
  }
2802
2702
  }
2803
2703
  };
2804
- const timeoutMs = selection.runtime.harnessSettings.timeoutSeconds
2805
- ? selection.runtime.harnessSettings.timeoutSeconds * 1000
2704
+ const timeoutMs = selection.harnessSettings.timeoutSeconds
2705
+ ? selection.harnessSettings.timeoutSeconds * 1000
2806
2706
  : undefined;
2807
2707
  let captureResult;
2808
2708
  try {
@@ -2885,15 +2785,8 @@ export function parseProposals(raw) {
2885
2785
  throw new Error(`proposals[${idx}] must be an object`);
2886
2786
  }
2887
2787
  const p = item;
2888
- if (typeof p.id !== 'string' || !p.id.trim()) {
2889
- throw new Error(`proposals[${idx}].id must be a non-empty string`);
2890
- }
2891
- if (typeof p.summary !== 'string' || !p.summary.trim()) {
2892
- throw new Error(`proposals[${idx}].summary must be a non-empty string`);
2893
- }
2894
- if (typeof p.rationale !== 'string') {
2895
- throw new Error(`proposals[${idx}].rationale must be a string`);
2896
- }
2788
+ const id = normalizeNonEmptyString(p.id) ?? `proposal-${idx + 1}`;
2789
+ const rationale = typeof p.rationale === 'string' ? p.rationale.trim() : '';
2897
2790
  if (!Array.isArray(p.patches) || p.patches.length === 0) {
2898
2791
  throw new Error(`proposals[${idx}].patches must be a non-empty array`);
2899
2792
  }
@@ -2913,10 +2806,11 @@ export function parseProposals(raw) {
2913
2806
  assertAllowedImproverPatch(patch, `proposals[${idx}].patches[${pidx}]`);
2914
2807
  patches.push(patch);
2915
2808
  }
2809
+ const summary = normalizeProposalSummary(p.summary, id, patches);
2916
2810
  proposals.push({
2917
- id: p.id,
2918
- summary: p.summary,
2919
- rationale: p.rationale,
2811
+ id,
2812
+ summary,
2813
+ rationale,
2920
2814
  patches
2921
2815
  });
2922
2816
  }
@@ -2927,6 +2821,54 @@ export function parseProposals(raw) {
2927
2821
  proposals
2928
2822
  };
2929
2823
  }
2824
+ function normalizeNonEmptyString(value) {
2825
+ if (typeof value !== 'string')
2826
+ return undefined;
2827
+ const trimmed = value.trim();
2828
+ return trimmed.length > 0 ? trimmed : undefined;
2829
+ }
2830
+ function normalizeProposalSummary(value, id, patches) {
2831
+ const explicit = normalizeNonEmptyString(value);
2832
+ if (explicit)
2833
+ return explicit;
2834
+ const fromId = humanizeProposalId(id);
2835
+ if (fromId)
2836
+ return fromId;
2837
+ return summarizeProposalPatches(patches);
2838
+ }
2839
+ function humanizeProposalId(id) {
2840
+ const trimmed = id.trim();
2841
+ if (!trimmed || /^p\d+$/i.test(trimmed) || /^proposal-\d+$/i.test(trimmed)) {
2842
+ return undefined;
2843
+ }
2844
+ const words = trimmed.replace(/[-_]+/g, ' ').replace(/\s+/g, ' ').trim();
2845
+ if (!words)
2846
+ return undefined;
2847
+ return `${words.slice(0, 1).toUpperCase()}${words.slice(1)}`;
2848
+ }
2849
+ function summarizeProposalPatches(patches) {
2850
+ if (patches.length === 1)
2851
+ return summarizeSingleProposalPatch(patches[0]);
2852
+ return `Apply ${patches.length} persona updates`;
2853
+ }
2854
+ function summarizeSingleProposalPatch(patch) {
2855
+ if (patch.op === 'append' && patch.path === 'skills')
2856
+ return 'Add skill';
2857
+ if (patch.path.startsWith('inputs.')) {
2858
+ return `Add ${patch.path.slice('inputs.'.length)} input`;
2859
+ }
2860
+ if (patch.path === 'description')
2861
+ return 'Update description';
2862
+ if (patch.path === 'agentsMdContent')
2863
+ return 'Update AGENTS.md guidance';
2864
+ if (patch.path === 'claudeMdContent')
2865
+ return 'Update CLAUDE.md guidance';
2866
+ if (patch.path === 'tags')
2867
+ return 'Update tags';
2868
+ if (patch.path === 'systemPrompt')
2869
+ return 'Update system prompt';
2870
+ return patch.op === 'append' ? `Append to ${patch.path}` : `Update ${patch.path}`;
2871
+ }
2930
2872
  /**
2931
2873
  * Walk improver proposals one-by-one over the TTY. Returns only the
2932
2874
  * accepted proposals; the caller applies the patches. Supports:
@@ -3310,6 +3252,14 @@ export async function main() {
3310
3252
  if (subcommand === 'pick') {
3311
3253
  await runPick(rest);
3312
3254
  }
3255
+ if (subcommand === 'deploy') {
3256
+ await runDeploy(rest);
3257
+ return;
3258
+ }
3259
+ if (subcommand === 'login') {
3260
+ await runLogin(rest);
3261
+ return;
3262
+ }
3313
3263
  if (subcommand !== 'agent') {
3314
3264
  die(`Unknown subcommand "${subcommand}".`);
3315
3265
  }
@@ -3420,7 +3370,7 @@ export function parseCreateArgs(args) {
3420
3370
  }
3421
3371
  const [unexpected] = positional;
3422
3372
  if (unexpected) {
3423
- die(`create: unexpected argument "${unexpected}". The create command always runs ${CREATE_SELECTOR}; use "agentworkforce agent <persona>[@<tier>]" to run another persona.`);
3373
+ die(`create: unexpected argument "${unexpected}". The create command always runs ${CREATE_SELECTOR}; use "agentworkforce agent <persona>" to run another persona.`);
3424
3374
  }
3425
3375
  const target = resolveCreateTarget(flags.saveInDirectory);
3426
3376
  ensureCreateTargetDir(target);