@agentworkforce/cli 0.15.1 → 0.17.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,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.17.0] - 2026-05-08
11
+
12
+ ### Added
13
+
14
+ - **Add optional defaultTier to PersonaSpec**
15
+
16
+ ### Changed
17
+
18
+ - Agent: consult routingProfiles before defaultTier on no-@<tier>
19
+
20
+ ## [0.16.0] - 2026-05-08
21
+
22
+ ### Added
23
+
24
+ - **Add --dry-run flag to agentworkforce agent**
25
+
10
26
  ## [0.15.1] - 2026-05-08
11
27
 
12
28
  ### Added
package/dist/cli.d.ts CHANGED
@@ -166,6 +166,7 @@ export declare function main(): Promise<void>;
166
166
  export interface AgentFlags {
167
167
  installInRepo: boolean;
168
168
  noLaunchMetadata: boolean;
169
+ dryRun: boolean;
169
170
  }
170
171
  export interface CreateFlags extends AgentFlags {
171
172
  saveInDirectory?: string;
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn, spawnSync } from 'node:child_process';
3
3
  import { randomBytes } from 'node:crypto';
4
- import { appendFileSync, existsSync, mkdirSync, readFileSync, readSync, rmSync, statSync, writeFileSync } from 'node:fs';
5
- import { constants, homedir } from 'node:os';
4
+ import { appendFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readSync, rmSync, statSync, writeFileSync } from 'node:fs';
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 { HARNESS_VALUES, PERSONA_TAGS, PERSONA_TIERS, listBuiltInPersonas, personaCatalog, resolveSidecar, routingProfiles, useSelection } from '@agentworkforce/workload-router';
8
+ import { HARNESS_VALUES, materializeSkills, PERSONA_TAGS, PERSONA_TIERS, listBuiltInPersonas, personaCatalog, resolveSidecar, routingProfiles, useSelection } from '@agentworkforce/workload-router';
9
9
  import { buildInteractiveSpec, detectHarnesses, formatDropWarnings, MissingPersonaInputError, renderPersonaInputs, resolvePersonaInputs, resolveMcpServersLenient, resolveStringMapLenient } from '@agentworkforce/harness-kit';
10
10
  import { launchOnMount, readAgentDotfiles } from '@relayfile/local-mount';
11
11
  import ora from 'ora';
@@ -34,9 +34,11 @@ Commands:
34
34
  --no-launch-metadata
35
35
  Same behavior as agent.
36
36
  agent [flags] <persona>[@<tier>]
37
- Run a persona. Tier one of: ${PERSONA_TIERS.join(' | ')}
38
- (default: best-value). Drops into an interactive harness
39
- session.
37
+ Run a persona. Tier one of: ${PERSONA_TIERS.join(' | ')}.
38
+ With no @<tier>, the resolution order is:
39
+ routingProfiles.default.intents (built-in personas only)
40
+ → persona.defaultTier (when set) → best-value. Drops into
41
+ an interactive harness session.
40
42
 
41
43
  Flags:
42
44
  --install-in-repo Disengage the sandbox mount and
@@ -57,6 +59,26 @@ Commands:
57
59
  Disable launch metadata recording.
58
60
  Also disabled by
59
61
  AGENTWORKFORCE_LAUNCH_METADATA=0.
62
+ --dry-run Validate the persona without
63
+ spawning the harness or burning
64
+ tier-model tokens. Three checks:
65
+ (1) sidecar — claudeMd / agentsMd
66
+ filename refs are readable;
67
+ (2) harness spec — permissions /
68
+ mcpServers / harness-settings
69
+ shape is accepted by the harness
70
+ translator; (3) skills — each
71
+ \`skills[].source\` is run through
72
+ its real installer (npx skills
73
+ add / npx prpm install) inside a
74
+ fresh temp dir, with per-skill
75
+ pass/fail reporting. Use this in
76
+ the persona-author loop to catch
77
+ hallucinated skill names and
78
+ malformed config before a persona
79
+ ships. Temp dir is removed on
80
+ success, kept on failure for
81
+ inspection.
60
82
  list [flags] List available personas from the cascade (cwd →
61
83
  configured persona dirs → library). By default shows
62
84
  one row per persona at the recommended tier for its
@@ -212,14 +234,22 @@ function parseSelector(sel) {
212
234
  const tierRaw = at === -1 ? undefined : sel.slice(at + 1);
213
235
  if (!key)
214
236
  die('Missing persona name before "@"');
215
- const tier = (tierRaw ?? 'best-value');
216
- if (tierRaw !== undefined && !PERSONA_TIERS.includes(tier)) {
237
+ if (tierRaw !== undefined && !PERSONA_TIERS.includes(tierRaw)) {
217
238
  die(`Invalid tier "${tierRaw}". Must be one of: ${PERSONA_TIERS.join(', ')}`);
218
239
  }
219
240
  const result = resolveSpec(key);
220
241
  if ('error' in result)
221
242
  die(result.error, false);
222
243
  const kind = local.byId.has(key) ? 'local' : 'repo';
244
+ // Resolution order when no @<tier> is given: routingProfiles default for the
245
+ // persona's intent (built-ins only — local personas with custom intents miss
246
+ // the lookup and fall through), then the persona's own defaultTier, then
247
+ // 'best-value'. Mirrors `resolveShowTarget` and the `list` recommended-tier
248
+ // filter so all three commands agree on what "no tier" means.
249
+ const profileRule = kind === 'repo'
250
+ ? routingProfiles.default.intents[result.intent]
251
+ : undefined;
252
+ const tier = (tierRaw ?? profileRule?.tier ?? result.defaultTier ?? 'best-value');
223
253
  if (kind === 'local') {
224
254
  return { kind, source: local.sources.get(result.id) ?? 'cwd', spec: result, tier };
225
255
  }
@@ -677,6 +707,128 @@ export function decideCleanMode(harness, installInRepo = false) {
677
707
  }
678
708
  return { useClean: false };
679
709
  }
710
+ /**
711
+ * Persona authoring dry-run. Used by persona authors to verify a persona
712
+ * actually launches before it ships, without spawning the harness or
713
+ * running the agent's tier model. Three checks, in order:
714
+ *
715
+ * 1. Sidecar resolution — `claudeMd` / `agentsMd` filename references
716
+ * that point at unreadable files would brick the launch silently
717
+ * today (lenient warning); dry-run promotes them to failures.
718
+ * 2. Interactive spec build — runs `buildInteractiveSpec` on the
719
+ * resolved selection. Catches malformed `permissions` patterns,
720
+ * `mcpServers` shape errors, and missing required harness fields.
721
+ * Pure / no side effects.
722
+ * 3. Skill install — runs each `skills[].source` through its real
723
+ * installer (`npx skills add` / `npx prpm install`) inside a fresh
724
+ * temp dir and reports per-skill pass/fail.
725
+ *
726
+ * Temp dir is deleted on success so dry-run leaves no trace; on a skill
727
+ * failure it is left in place and its path printed so the author can
728
+ * inspect the installer's output. Checks 1 and 2 run before any temp
729
+ * dir is created so an early failure doesn't litter `/tmp`.
730
+ */
731
+ function runDryRun(selection) {
732
+ const inputResolution = resolvePersonaInputs(selection.inputs, selection.inputValues, process.env);
733
+ const renderedSystemPrompt = renderPersonaInputs(selection.runtime.systemPrompt, inputResolution.values);
734
+ const renderedClaudeContent = selection.claudeMdContent !== undefined
735
+ ? renderPersonaInputs(selection.claudeMdContent, inputResolution.values)
736
+ : undefined;
737
+ const renderedAgentsContent = selection.agentsMdContent !== undefined
738
+ ? renderPersonaInputs(selection.agentsMdContent, inputResolution.values)
739
+ : undefined;
740
+ const effectiveSelection = {
741
+ ...selection,
742
+ runtime: { ...selection.runtime, systemPrompt: renderedSystemPrompt },
743
+ ...(renderedClaudeContent !== undefined ? { claudeMdContent: renderedClaudeContent } : {}),
744
+ ...(renderedAgentsContent !== undefined ? { agentsMdContent: renderedAgentsContent } : {})
745
+ };
746
+ const { runtime, personaId, tier } = effectiveSelection;
747
+ process.stderr.write(`→ ${personaId} [${tier}] via ${runtime.harness} (${runtime.model}) [DRY-RUN]\n`);
748
+ // Check 1: sidecar resolution. A loadSidecarForSelection warning means
749
+ // the persona points `claudeMd` / `agentsMd` at a file we couldn't
750
+ // read; the launch path degrades to a warning today, which silently
751
+ // drops the persona's operating spec. In dry-run that's a failure.
752
+ const sidecarLookup = loadSidecarForSelection(effectiveSelection);
753
+ if (sidecarLookup.warning) {
754
+ process.stderr.write(`✗ sidecar: ${sidecarLookup.warning}\n`);
755
+ return 1;
756
+ }
757
+ process.stderr.write(`✓ sidecar: ${sidecarLookup.sidecar ? sidecarLookup.sidecar.mountFile : '(none)'}\n`);
758
+ // Check 2: harness-kit translation. buildInteractiveSpec validates
759
+ // permissions shape, mcpServers shape, and required runtime fields.
760
+ // We resolve env + mcp leniently (same as the live launch path) so
761
+ // the spec call sees the same inputs it would at runtime.
762
+ const callerEnv = { ...process.env, ...inputResolution.values };
763
+ const envResolution = resolveStringMapLenient(effectiveSelection.env, callerEnv, 'env');
764
+ const mcpResolution = resolveMcpServersLenient(effectiveSelection.mcpServers, callerEnv);
765
+ emitDropWarnings(formatDropWarnings(envResolution.dropped, mcpResolution.dropped, mcpResolution.droppedServers));
766
+ let spec;
767
+ try {
768
+ spec = buildInteractiveSpec({
769
+ harness: runtime.harness,
770
+ personaId,
771
+ model: runtime.model,
772
+ systemPrompt: runtime.systemPrompt,
773
+ harnessSettings: runtime.harnessSettings,
774
+ mcpServers: mcpResolution.servers,
775
+ permissions: effectiveSelection.permissions
776
+ });
777
+ }
778
+ catch (err) {
779
+ const msg = err instanceof Error ? err.message : String(err);
780
+ process.stderr.write(`✗ harness spec: ${msg}\n`);
781
+ return 1;
782
+ }
783
+ for (const w of spec.warnings)
784
+ process.stderr.write(`warning: ${w}\n`);
785
+ process.stderr.write(`✓ harness spec: ${spec.bin} (${spec.args.length} args)\n`);
786
+ // Check 3: skill installs.
787
+ const plan = materializeSkills(effectiveSelection.skills, runtime.harness);
788
+ if (plan.installs.length === 0) {
789
+ process.stderr.write('✓ skills: (none declared)\n');
790
+ process.stderr.write('✓ dry-run ok\n');
791
+ return 0;
792
+ }
793
+ const tempDir = mkdtempSync(join(tmpdir(), `agentworkforce-dryrun-${personaId}-`));
794
+ process.stderr.write(`• temp dir: ${tempDir}\n`);
795
+ process.stderr.write(`• installing ${plan.installs.length} skill(s)\n`);
796
+ const failures = [];
797
+ for (const inst of plan.installs) {
798
+ const [bin, ...args] = inst.installCommand;
799
+ if (!bin) {
800
+ process.stderr.write(` skipped ${inst.skillId}: empty install command\n`);
801
+ continue;
802
+ }
803
+ process.stderr.write(`• ${inst.skillId} (${inst.sourceKind}) → ${inst.packageRef}\n`);
804
+ const res = spawnSync(bin, args, { stdio: 'inherit', shell: false, cwd: tempDir });
805
+ const code = subprocessExitCode(res);
806
+ if (code === 0) {
807
+ process.stderr.write(` ✓ ${inst.skillId}\n`);
808
+ }
809
+ else {
810
+ process.stderr.write(` ✗ ${inst.skillId} (exit ${code})\n`);
811
+ failures.push({ skillId: inst.skillId, exitCode: code });
812
+ }
813
+ }
814
+ if (failures.length === 0) {
815
+ try {
816
+ rmSync(tempDir, { recursive: true, force: true });
817
+ }
818
+ catch (err) {
819
+ const msg = err instanceof Error ? err.message : String(err);
820
+ process.stderr.write(`warning: failed to clean up ${tempDir}: ${msg}\n`);
821
+ }
822
+ process.stderr.write(`✓ dry-run ok: ${plan.installs.length} skill(s) installed cleanly\n`);
823
+ return 0;
824
+ }
825
+ process.stderr.write(`✗ dry-run failed: ${failures.length} of ${plan.installs.length} skill(s) failed\n`);
826
+ process.stderr.write(` inspect ${tempDir} for installer output\n`);
827
+ for (const f of failures) {
828
+ process.stderr.write(` - ${f.skillId} (exit ${f.exitCode})\n`);
829
+ }
830
+ return failures[0]?.exitCode || 1;
831
+ }
680
832
  async function runInteractive(selection, options) {
681
833
  const inputResolution = resolvePersonaInputs(selection.inputs, selection.inputValues, process.env);
682
834
  const renderedSystemPrompt = renderPersonaInputs(selection.runtime.systemPrompt, inputResolution.values);
@@ -1368,7 +1520,8 @@ function collectPersonaRows() {
1368
1520
  intent: spec.intent,
1369
1521
  tags: spec.tags,
1370
1522
  description: spec.description,
1371
- rating: tier
1523
+ rating: tier,
1524
+ defaultTier: spec.defaultTier
1372
1525
  });
1373
1526
  }
1374
1527
  };
@@ -1517,7 +1670,7 @@ function runList(args) {
1517
1670
  return false;
1518
1671
  if (applyRecommended) {
1519
1672
  const rule = recommendedByIntent[r.intent];
1520
- if (r.rating !== (rule?.tier ?? 'best-value'))
1673
+ if (r.rating !== (rule?.tier ?? r.defaultTier ?? 'best-value'))
1521
1674
  return false;
1522
1675
  }
1523
1676
  return true;
@@ -1611,7 +1764,7 @@ function resolveShowTarget(selector, all) {
1611
1764
  }
1612
1765
  else {
1613
1766
  const rule = routingProfiles.default.intents[spec.intent];
1614
- tiers = [rule?.tier ?? 'best-value'];
1767
+ tiers = [rule?.tier ?? spec.defaultTier ?? 'best-value'];
1615
1768
  }
1616
1769
  return { spec, source, tiers, explicitTier };
1617
1770
  }
@@ -1628,6 +1781,9 @@ function formatPersonaShow(spec, source, tiers, tierNote) {
1628
1781
  lines.push(`INTENT ${spec.intent}`);
1629
1782
  lines.push(`TAGS ${spec.tags.length ? spec.tags.join(', ') : '(none)'}`);
1630
1783
  lines.push(`DESCRIPTION ${spec.description}`);
1784
+ if (spec.defaultTier) {
1785
+ lines.push(`DEFAULT TIER ${spec.defaultTier}`);
1786
+ }
1631
1787
  lines.push(`TIERS SHOWN ${tiers.join(', ')}${tierNote ? ` (${tierNote})` : ''}`);
1632
1788
  lines.push('');
1633
1789
  lines.push('SKILLS');
@@ -1846,6 +2002,10 @@ async function runAgentSelector(selector, flags, inputValues) {
1846
2002
  ...buildSelection(target.spec, target.tier, target.kind),
1847
2003
  ...(inputValues ? { inputValues } : {})
1848
2004
  };
2005
+ if (flags.dryRun) {
2006
+ const code = runDryRun(selection);
2007
+ process.exit(code);
2008
+ }
1849
2009
  const code = await runInteractive(selection, {
1850
2010
  installInRepo: flags.installInRepo,
1851
2011
  noLaunchMetadata: flags.noLaunchMetadata,
@@ -1972,7 +2132,7 @@ async function runPick(args) {
1972
2132
  ...buildCreateInputValues(target),
1973
2133
  TASK_DESCRIPTION: task
1974
2134
  };
1975
- await runAgentSelector(CREATE_SELECTOR, { installInRepo: false, noLaunchMetadata: false }, inputValues);
2135
+ await runAgentSelector(CREATE_SELECTOR, { installInRepo: false, noLaunchMetadata: false, dryRun: false }, inputValues);
1976
2136
  // runAgentSelector terminates via process.exit; this satisfies TS's
1977
2137
  // reachable-end-point check for the `Promise<never>` return type.
1978
2138
  process.exit(0);
@@ -2033,7 +2193,11 @@ export async function main() {
2033
2193
  await runAgentSelector(selector, flags);
2034
2194
  }
2035
2195
  export function parseAgentArgs(args) {
2036
- const flags = { installInRepo: false, noLaunchMetadata: false };
2196
+ const flags = {
2197
+ installInRepo: false,
2198
+ noLaunchMetadata: false,
2199
+ dryRun: false
2200
+ };
2037
2201
  const positional = [];
2038
2202
  let seenDoubleDash = false;
2039
2203
  for (const arg of args) {
@@ -2053,6 +2217,10 @@ export function parseAgentArgs(args) {
2053
2217
  flags.noLaunchMetadata = true;
2054
2218
  continue;
2055
2219
  }
2220
+ if (arg === '--dry-run') {
2221
+ flags.dryRun = true;
2222
+ continue;
2223
+ }
2056
2224
  if (arg === '-h' || arg === '--help') {
2057
2225
  process.stdout.write(USAGE);
2058
2226
  process.exit(0);
@@ -2062,7 +2230,12 @@ export function parseAgentArgs(args) {
2062
2230
  return { flags, positional };
2063
2231
  }
2064
2232
  export function parseCreateArgs(args) {
2065
- const flags = { installInRepo: false, noLaunchMetadata: false, saveDefault: false };
2233
+ const flags = {
2234
+ installInRepo: false,
2235
+ noLaunchMetadata: false,
2236
+ dryRun: false,
2237
+ saveDefault: false
2238
+ };
2066
2239
  let seenDoubleDash = false;
2067
2240
  const positional = [];
2068
2241
  const valueOf = (i, flag) => {