loadout-ai 0.7.0 → 0.9.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.
Files changed (108) hide show
  1. package/CHANGELOG.md +148 -1
  2. package/README.md +160 -315
  3. package/SECURITY.md +21 -1
  4. package/catalog/discovered.json +31269 -26003
  5. package/catalog/packages.json +4 -4
  6. package/dist/src/cli.js +13 -0
  7. package/dist/src/commands/agents.js +3 -1
  8. package/dist/src/commands/catalog-candidate.js +150 -0
  9. package/dist/src/commands/catalog-workflows.js +147 -0
  10. package/dist/src/commands/catalog.js +9 -367
  11. package/dist/src/commands/coordinate.js +586 -0
  12. package/dist/src/commands/coordination-discussions.js +194 -0
  13. package/dist/src/commands/coordination-sessions.js +197 -0
  14. package/dist/src/commands/inventory.js +4 -2
  15. package/dist/src/core/agents/agent-inspection.js +26 -4
  16. package/dist/src/core/{routing → agents}/model-config.js +7 -2
  17. package/dist/src/core/catalog/catalog.js +1 -0
  18. package/dist/src/core/catalog/registry.js +44 -10
  19. package/dist/src/core/catalog/safety.js +36 -7
  20. package/dist/src/core/coordination/adapters/claude-code.js +154 -0
  21. package/dist/src/core/coordination/adapters/codex.js +137 -0
  22. package/dist/src/core/coordination/adapters/types.js +64 -0
  23. package/dist/src/core/coordination/auth.js +59 -0
  24. package/dist/src/core/coordination/bridge-lease.js +79 -0
  25. package/dist/src/core/coordination/conflict-preview.js +167 -0
  26. package/dist/src/core/coordination/contract-diff.js +106 -0
  27. package/dist/src/core/coordination/coordinator.js +552 -0
  28. package/dist/src/core/coordination/crash-recovery.js +169 -0
  29. package/dist/src/core/coordination/daemon.js +667 -0
  30. package/dist/src/core/coordination/discussion.js +340 -0
  31. package/dist/src/core/coordination/events.js +161 -0
  32. package/dist/src/core/coordination/http-api.js +118 -0
  33. package/dist/src/core/coordination/interrupt-policy.js +89 -0
  34. package/dist/src/core/coordination/lock.js +110 -0
  35. package/dist/src/core/coordination/mcp-server.js +424 -0
  36. package/dist/src/core/coordination/redaction.js +85 -0
  37. package/dist/src/core/coordination/replay.js +188 -0
  38. package/dist/src/core/coordination/retention.js +128 -0
  39. package/dist/src/core/coordination/runtime.js +21 -0
  40. package/dist/src/core/coordination/session-manager.js +366 -0
  41. package/dist/src/core/coordination/watcher.js +128 -0
  42. package/dist/src/core/{routing → delegation}/first-party-skills.js +25 -13
  43. package/dist/src/core/{routing → delegation}/handoff.js +130 -63
  44. package/dist/src/core/discovery/candidate-intelligence-evidence.js +108 -0
  45. package/dist/src/core/discovery/candidate-intelligence-types.js +1 -0
  46. package/dist/src/core/discovery/candidate-intelligence-validation.js +108 -0
  47. package/dist/src/core/discovery/candidate-intelligence.js +3 -214
  48. package/dist/src/core/discovery/community.js +12 -4
  49. package/dist/src/core/discovery/github-discovery.js +6 -2
  50. package/dist/src/core/discovery/private-discovery.js +6 -2
  51. package/dist/src/core/install/reconcile.js +1 -1
  52. package/dist/src/core/install/source.js +21 -7
  53. package/dist/src/core/install/uninstall.js +39 -3
  54. package/dist/src/core/reporting/cli-guide.js +10 -6
  55. package/dist/src/core/reporting/completion.js +61 -109
  56. package/dist/src/core/reporting/doctor.js +4 -6
  57. package/dist/src/core/runtime/bounded-json.js +65 -0
  58. package/dist/src/core/runtime/github.js +30 -16
  59. package/dist/src/core/runtime/mcp-recipes.js +1 -1
  60. package/dist/src/core/workspace/active-policy.js +1 -1
  61. package/docs/CANDIDATE_INTELLIGENCE.md +9 -2
  62. package/docs/CATALOG.md +1 -1
  63. package/docs/CREDENTIAL_AND_UPDATE_POLICY.md +1 -1
  64. package/docs/DEMO_SCRIPT.md +19 -23
  65. package/docs/DISCOVERED.md +251 -250
  66. package/docs/FEATURE_TEST_MATRIX.md +10 -263
  67. package/docs/GITHUB_AUTHORIZATION.md +5 -0
  68. package/docs/LIVE_COLLABORATION.md +234 -0
  69. package/docs/PROVENANCE_AND_COMPARISON.md +1 -1
  70. package/docs/REFERENCE.md +163 -0
  71. package/docs/RELEASE_REVIEW.md +1 -2
  72. package/docs/TESTING.md +17 -1
  73. package/docs/USER_TEST_GUIDE.md +138 -3
  74. package/docs/assets/loadout-discover-activate.webp +0 -0
  75. package/docs/assets/loadout-handoff-coordinate.webp +0 -0
  76. package/docs/assets/loadout-social-preview.png +0 -0
  77. package/docs/decisions/001-coordination-jsonl-locking.md +35 -0
  78. package/docs/decisions/002-local-daemon-authentication.md +32 -0
  79. package/docs/decisions/003-bounded-agent-discussions.md +91 -0
  80. package/docs/specs/BOUNDED_AGENT_DISCUSSIONS.md +169 -0
  81. package/docs/superpowers/plans/2026-09-03-coordination-hardening.md +231 -0
  82. package/docs/superpowers/plans/2026-09-03-release-readiness.md +232 -0
  83. package/docs/superpowers/plans/2026-09-04-bounded-agent-discussions.md +121 -0
  84. package/package.json +18 -7
  85. package/skills/loadout-handoff/SKILL.md +238 -0
  86. package/MASTER_PLAN.md +0 -2207
  87. package/dist/src/core/routing/route.js +0 -539
  88. package/docs/ACTIVE_SET.md +0 -53
  89. package/docs/COMPATIBILITY_POLICY.md +0 -22
  90. package/docs/CONVERSION_AND_SANDBOX.md +0 -27
  91. package/docs/EVALUATION_PROTOCOL_V1.md +0 -300
  92. package/docs/HEAD_TO_HEAD_EVALUATION.md +0 -79
  93. package/docs/PROVIDER_CONFIGURATION.md +0 -45
  94. package/docs/README_RESEARCH.md +0 -36
  95. package/docs/REPOSITORY_STABILIZATION.md +0 -190
  96. package/docs/SAFE_UPDATE_DEMO.md +0 -25
  97. package/docs/SCHEMA_DECISIONS.md +0 -25
  98. package/docs/SUBMISSION_COPY.md +0 -90
  99. package/docs/TEAM_POLICY.md +0 -18
  100. package/docs/assets/loadout-workflow.png +0 -0
  101. package/docs/superpowers/plans/2026-07-19-relatable-readme-hero.md +0 -283
  102. package/docs/superpowers/plans/2026-07-20-loadout-readme-explainer.md +0 -116
  103. package/docs/superpowers/plans/2026-07-20-project-activation-safety.md +0 -469
  104. package/docs/superpowers/specs/2026-07-19-relatable-readme-hero-design.md +0 -80
  105. package/docs/superpowers/specs/2026-07-20-loadout-readme-explainer-design.md +0 -55
  106. package/docs/superpowers/specs/2026-07-20-project-activation-safety-design.md +0 -228
  107. package/skills/loadout-router/SKILL.md +0 -120
  108. /package/dist/src/core/{routing → agents}/credentials.js +0 -0
@@ -1,24 +1,18 @@
1
- import { explainCatalogScore, loadEffectiveCatalog, loadCatalog, promoteCatalogCandidate, rankCatalog, refreshCatalog, validateCatalog, } from "../core/catalog/catalog.js";
2
- import { detectAgents, parseAgentSelection } from "../core/agents/paths.js";
3
- import { resolve } from "node:path";
4
- import { readFile } from "node:fs/promises";
5
- import { allPhaseRoutes, formatCostTable, formatModelCatalog, formatRouteRecommendation, formatRoutingTable, routePhase, routeTask, } from "../core/routing/route.js";
6
- import { applyFirstPartySkill, FIRST_PARTY_SKILLS, formatFirstPartySkillList, formatFirstPartySkillPlan, installedFirstPartySkills, planFirstPartySkill, removeFirstPartySkill, } from "../core/routing/first-party-skills.js";
7
- import { applyPickup, formatHandoffStatus, formatInbox, formatPickupPlan, getHandoffState, initHandoff, isHandoffInitialized, markDone, planPickup, readInbox, sendHandoff, } from "../core/routing/handoff.js";
1
+ import { explainCatalogScore, loadEffectiveCatalog, loadCatalog, rankCatalog, refreshCatalog, } from "../core/catalog/catalog.js";
2
+ import { parseAgentSelection } from "../core/agents/paths.js";
8
3
  import { catalogTrustStage, formatCatalogTrustStage, } from "../core/catalog/profiles.js";
9
4
  import { discoverHackerNewsRepositories } from "../core/discovery/community.js";
10
5
  import { discoverPrivateRepositories } from "../core/discovery/private-discovery.js";
11
6
  import { defaultGitHubDiscoveryQueries, discoverGitHubRepositories, } from "../core/discovery/github-discovery.js";
12
7
  import { formatStarHistory, readCatalogObservations, } from "../core/discovery/observations.js";
13
- import { writeFileAtomically } from "../core/install/atomic-file.js";
14
- import { formatReviewQueue, markPromoted, mergeReviewQueue, readReviewQueue, } from "../core/discovery/review-queue.js";
15
- import { applyProviderModelSelection, defaultModelConfigurationPath, formatProviderModelConfiguration, planProviderModelSelection, readProviderModelConfiguration, requestOpenRouter, } from "../core/routing/model-config.js";
16
- import { parseCompletionShell, renderShellCompletion, } from "../core/reporting/completion.js";
8
+ import { formatReviewQueue, mergeReviewQueue, readReviewQueue, } from "../core/discovery/review-queue.js";
9
+ import { applyProviderModelSelection, defaultModelConfigurationPath, formatProviderModelConfiguration, planProviderModelSelection, readProviderModelConfiguration, requestOpenRouter, } from "../core/agents/model-config.js";
17
10
  import { buildCatalogCoverage, formatCatalogCoverage, } from "../core/catalog/catalog-coverage.js";
18
- import { createCredentialResolver, createOsCredentialStore, } from "../core/routing/credentials.js";
19
- import { buildCandidateDossier, buildCatalogProposal, formatCandidateDossier, formatCandidateSummaries, listDiscoveryCandidates, readCandidateDossier, verifyCandidateDossierSource, writeCandidateDossier, } from "../core/discovery/candidate-intelligence.js";
11
+ import { createCredentialResolver, createOsCredentialStore, } from "../core/agents/credentials.js";
20
12
  import { discoverSkillsSh } from "../core/discovery/skills-sh-discovery.js";
21
13
  import { discoverOfficialMcpRegistry } from "../core/discovery/mcp-registry-discovery.js";
14
+ import { registerCandidateCommands } from "./catalog-candidate.js";
15
+ import { registerWorkflowCommands } from "./catalog-workflows.js";
22
16
  import { readCredentialFromStdin } from "./support.js";
23
17
  export function registerCatalog(program) {
24
18
  program
@@ -90,148 +84,7 @@ export function registerCatalog(program) {
90
84
  for (const failure of result.observationFailures)
91
85
  console.error(`Warning: could not record release observation for ${failure.repository}: ${failure.error}`);
92
86
  });
93
- const candidate = program
94
- .command("candidate")
95
- .description("Triage and statically inspect daily discovery candidates; never auto-promotes");
96
- candidate
97
- .command("list")
98
- .allowExcessArguments(false)
99
- .description("Rank discovery leads for human triage, not as universal quality")
100
- .option("--limit <count>", "maximum candidates", "20")
101
- .option("--query <words>", "require all search words")
102
- .option("--include-reviewed", "include repositories already in the catalog")
103
- .option("--feed <path>", "alternate discovered.json evidence feed")
104
- .option("--json", "emit machine-readable JSON")
105
- .action(async (options) => {
106
- const limit = Number(options.limit);
107
- if (!Number.isInteger(limit) || limit < 1 || limit > 500)
108
- throw new Error("--limit must be an integer from 1 to 500");
109
- const result = await listDiscoveryCandidates({
110
- limit,
111
- query: options.query,
112
- includeReviewed: options.includeReviewed,
113
- path: options.feed,
114
- });
115
- console.log(options.json
116
- ? JSON.stringify(result, null, 2)
117
- : formatCandidateSummaries(result));
118
- });
119
- candidate
120
- .command("inspect")
121
- .allowExcessArguments(false)
122
- .description("Clone one lead at an immutable commit and build a static evidence dossier")
123
- .argument("<repository>", "owner/repository present in the discovery feed")
124
- .option("--feed <path>", "alternate discovered.json evidence feed")
125
- .option("--write", "persist the dossier in private Loadout state")
126
- .option("--output <path>", "persist at an explicit path (implies --write)")
127
- .option("--json", "emit the complete dossier as JSON")
128
- .action(async (repository, options) => {
129
- const dossier = await buildCandidateDossier(repository, {
130
- discoveryPath: options.feed,
131
- });
132
- const path = options.write || options.output
133
- ? await writeCandidateDossier(dossier, options.output)
134
- : undefined;
135
- if (options.json)
136
- return console.log(JSON.stringify({
137
- dossier,
138
- persisted: Boolean(path),
139
- ...(path ? { path } : {}),
140
- }, null, 2));
141
- console.log(formatCandidateDossier(dossier));
142
- if (path)
143
- console.log(`Dossier: ${path}`);
144
- else {
145
- console.log("Preview only. Re-run with --write to persist this dossier.");
146
- }
147
- });
148
- candidate
149
- .command("propose")
150
- .allowExcessArguments(false)
151
- .description("Convert a reviewed dossier into a catalog-record proposal; never edits the catalog")
152
- .argument("<dossier>", "persisted candidate dossier JSON")
153
- .requiredOption("--id <id>", "lowercase kebab-case catalog id")
154
- .requiredOption("--category <category>", "inspected catalog category")
155
- .requiredOption("--platforms <ids>", "explicitly reviewed comma-separated platforms: windows,macos,linux")
156
- .option("--display-name <name>", "reviewed display name")
157
- .option("--description <text>", "reviewed description")
158
- .option("--license <spdx>", "human-reviewed license override")
159
- .option("--tier <tier>", "official, stable, trending, or community", "community")
160
- .option("--approve", "confirm human review and write the proposal")
161
- .option("--output <path>", "proposal JSON output path; required with --approve")
162
- .option("--json", "emit machine-readable JSON")
163
- .action(async (dossierPath, options) => {
164
- const platforms = options.platforms
165
- .split(",")
166
- .map((item) => item.trim())
167
- .filter(Boolean);
168
- const knownPlatforms = new Set(["windows", "macos", "linux"]);
169
- if (platforms.some((item) => !knownPlatforms.has(item)))
170
- throw new Error("--platforms supports only windows, macos, and linux");
171
- const knownTiers = new Set([
172
- "official",
173
- "stable",
174
- "trending",
175
- "community",
176
- ]);
177
- if (!knownTiers.has(options.tier))
178
- throw new Error("--tier is invalid");
179
- if (options.approve && !options.output)
180
- throw new Error("--approve requires --output so catalog mutation stays separate");
181
- const proposal = buildCatalogProposal(await verifyCandidateDossierSource(await readCandidateDossier(dossierPath)), {
182
- id: options.id,
183
- category: options.category,
184
- operatingSystems: platforms,
185
- tier: options.tier,
186
- displayName: options.displayName,
187
- description: options.description,
188
- license: options.license,
189
- }, await loadEffectiveCatalog());
190
- const output = options.approve ? resolve(options.output) : undefined;
191
- if (output)
192
- await writeFileAtomically(output, `${JSON.stringify(proposal, null, 2)}\n`);
193
- if (options.json)
194
- return console.log(JSON.stringify({
195
- proposal,
196
- approved: Boolean(output),
197
- catalogMutated: false,
198
- ...(output ? { output } : {}),
199
- }, null, 2));
200
- console.log(JSON.stringify(proposal, null, 2));
201
- if (!output)
202
- console.log("Proposal preview only. Human review is still required; use --approve --output <path> to persist it.");
203
- else
204
- console.log(`Approved proposal written to ${output}.`);
205
- });
206
- candidate
207
- .command("promote")
208
- .allowExcessArguments(false)
209
- .description("Merge a reviewed proposal into the local catalog; requires --approve")
210
- .argument("<proposal>", "proposal JSON file from `candidate propose --approve`")
211
- .option("--approve", "confirm human review and write to catalog")
212
- .option("--json", "emit machine-readable JSON")
213
- .action(async (proposalPath, options) => {
214
- const proposal = JSON.parse(await readFile(resolve(proposalPath), "utf8"));
215
- validateCatalog([proposal], { requireEvidence: true });
216
- if (!options.approve) {
217
- if (options.json)
218
- return console.log(JSON.stringify({ proposal, promoted: false, catalogMutated: false }, null, 2));
219
- console.log(JSON.stringify(proposal, null, 2));
220
- console.log("Preview only. Re-run with --approve to merge into the catalog.");
221
- return;
222
- }
223
- const result = await promoteCatalogCandidate(proposal);
224
- const item = await markPromoted(proposal.repository);
225
- if (options.json)
226
- return console.log(JSON.stringify({
227
- proposal,
228
- promoted: true,
229
- catalogMutated: true,
230
- ...result,
231
- reviewQueue: item,
232
- }, null, 2));
233
- console.log(`Promoted '${proposal.id}' into ${result.catalogPath} (${result.totalRecords} records). Review queue: ${item.repository} marked promoted.`);
234
- });
87
+ registerCandidateCommands(program);
235
88
  program
236
89
  .command("discover")
237
90
  .description("Find public community leads; discovery never installs anything")
@@ -590,216 +443,5 @@ export function registerCatalog(program) {
590
443
  });
591
444
  console.log(`Verified model selection '${id}'. No credential value was stored or printed.`);
592
445
  });
593
- const skills = program
594
- .command("skills")
595
- .description("Install the skills Loadout ships so your agents can use Loadout from inside a conversation");
596
- skills
597
- .command("list", { isDefault: true })
598
- .description("Show the skills Loadout ships and whether they are installed")
599
- .option("--json", "emit machine-readable JSON")
600
- .action(async (options) => {
601
- const installed = await installedFirstPartySkills();
602
- console.log(options.json
603
- ? JSON.stringify(FIRST_PARTY_SKILLS.map((skill) => ({
604
- ...skill,
605
- installed: installed.has(skill.id),
606
- })), null, 2)
607
- : formatFirstPartySkillList(installed));
608
- });
609
- skills
610
- .command("install")
611
- .description("Preview or install one Loadout skill into detected agents")
612
- .argument("<id>", "skill id, for example loadout-router")
613
- .option("--agents <ids>", "comma-separated agent ids; defaults to all detected")
614
- .option("--yes", "apply after previewing")
615
- .action(async (id, options) => {
616
- const plan = await planFirstPartySkill(id, {
617
- ...(options.agents
618
- ? { agents: parseAgentSelection(options.agents) }
619
- : {}),
620
- });
621
- console.log(formatFirstPartySkillPlan(plan));
622
- if (!plan.targets.length)
623
- return;
624
- if (!options.yes) {
625
- console.log("\nPreview only. Re-run with --yes to install.");
626
- return;
627
- }
628
- await applyFirstPartySkill(plan);
629
- console.log(`\nInstalled '${plan.skill.id}' for ${plan.targets.length} agent(s). Start a new agent session to pick it up.`);
630
- });
631
- skills
632
- .command("remove")
633
- .description("Preview or remove one Loadout skill from detected agents")
634
- .argument("<id>", "skill id")
635
- .option("--agents <ids>", "comma-separated agent ids; defaults to all detected")
636
- .option("--yes", "apply after previewing")
637
- .action(async (id, options) => {
638
- const plan = await planFirstPartySkill(id, {
639
- ...(options.agents
640
- ? { agents: parseAgentSelection(options.agents) }
641
- : {}),
642
- });
643
- const present = plan.targets.filter((target) => target.replacing);
644
- if (!present.length) {
645
- console.log(`'${id}' is not installed for any detected agent.`);
646
- return;
647
- }
648
- for (const target of present)
649
- console.log(` remove → ${target.destination} [${target.displayName}]`);
650
- if (!options.yes) {
651
- console.log("\nPreview only. Re-run with --yes to remove.");
652
- return;
653
- }
654
- await removeFirstPartySkill(plan);
655
- console.log(`\nRemoved '${id}' from ${present.length} agent(s).`);
656
- });
657
- const handoff = program
658
- .command("handoff")
659
- .description("Cross-agent file-based task handoff — send work between Claude Code, Codex, and other agents");
660
- handoff
661
- .command("init")
662
- .description("Create the .handoff/ protocol directory in the current project")
663
- .action(async () => {
664
- const projectRoot = process.cwd();
665
- if (await isHandoffInitialized(projectRoot)) {
666
- console.log("Handoff already initialized at .handoff/");
667
- return;
668
- }
669
- const dir = await initHandoff(projectRoot);
670
- console.log(`Initialized handoff protocol at ${dir}`);
671
- console.log("Add .handoff/ to version control so both agents share the message log.");
672
- });
673
- handoff
674
- .command("send")
675
- .description("Send a task or message to another agent")
676
- .argument("<agent>", "target agent (e.g. codex, claude-code)")
677
- .argument("<description...>", "task description")
678
- .option("--from <agent>", "sending agent", "user")
679
- .option("--type <type>", "message type: task, handoff, question, status, error, cancel", "task")
680
- .option("--context <text>", "additional context for the receiving agent")
681
- .option("--json", "emit machine-readable JSON")
682
- .action(async (agent, descriptionWords, options) => {
683
- const message = await sendHandoff(process.cwd(), agent, descriptionWords.join(" "), {
684
- from: options.from,
685
- type: options.type,
686
- context: options.context,
687
- });
688
- console.log(options.json
689
- ? JSON.stringify(message, null, 2)
690
- : `Sent ${message.type} ${message.id} → ${message.to}: ${message.description}`);
691
- });
692
- handoff
693
- .command("done")
694
- .description("Mark a handoff task as completed")
695
- .argument("<id>", "message id to mark done")
696
- .option("--json", "emit machine-readable JSON")
697
- .action(async (id, options) => {
698
- const message = await markDone(process.cwd(), id);
699
- console.log(options.json
700
- ? JSON.stringify(message, null, 2)
701
- : `Marked ${id} as done`);
702
- });
703
- handoff
704
- .command("inbox")
705
- .description("Show pending tasks addressed to one agent, as instructions it can act on")
706
- .argument("<agent>", "agent reading its inbox (e.g. claude-code, codex)")
707
- .option("--json", "emit machine-readable JSON")
708
- .action(async (agent, options) => {
709
- const messages = await readInbox(process.cwd(), agent);
710
- console.log(options.json
711
- ? JSON.stringify(messages, null, 2)
712
- : formatInbox(agent, messages));
713
- });
714
- handoff
715
- .command("pickup")
716
- .description("Teach agents to check their handoff inbox by adding a managed block to CLAUDE.md / AGENTS.md")
717
- .option("--agents <ids>", "comma-separated agents to instruct", "claude-code,codex")
718
- .option("--yes", "write the files after previewing")
719
- .action(async (options) => {
720
- const agents = options.agents
721
- .split(",")
722
- .map((value) => value.trim())
723
- .filter(Boolean);
724
- const plans = [];
725
- for (const agent of agents)
726
- plans.push(await planPickup(process.cwd(), agent));
727
- console.log(formatPickupPlan(plans));
728
- if (!options.yes) {
729
- console.log("\nPreview only. Re-run with --yes to write these files.");
730
- return;
731
- }
732
- for (const plan of plans)
733
- await applyPickup(plan);
734
- console.log(`\nWrote pickup instructions for ${plans.length} agent(s). They will check their inbox on the next session.`);
735
- });
736
- handoff
737
- .command("status")
738
- .description("Show pending and completed handoff tasks")
739
- .option("--json", "emit machine-readable JSON")
740
- .action(async (options) => {
741
- const state = await getHandoffState(process.cwd());
742
- console.log(options.json
743
- ? JSON.stringify(state, null, 2)
744
- : formatHandoffStatus(state));
745
- });
746
- const route = program
747
- .command("route")
748
- .description("Recommend the right model tier and agent for a task — plan, implement, review, test, debug, or document");
749
- route
750
- .argument("[description...]", "natural-language task description")
751
- .option("--phase <phase>", "explicit phase instead of auto-classify")
752
- .option("--conserve", "recommend cheaper tiers to stretch remaining session quota")
753
- .option("--cost", "show cost comparison table across all phases")
754
- .option("--models", "list the full model catalog Loadout knows about")
755
- .option("--provider <name>", "filter models by provider (anthropic, openai, google, deepseek, meta)")
756
- .option("--tier <tier>", "filter models by tier (frontier, standard, fast)")
757
- .option("--json", "emit machine-readable JSON")
758
- .action(async (descriptionWords, options) => {
759
- if (options.models) {
760
- console.log(options.json
761
- ? JSON.stringify((await import("../core/routing/route.js")).MODEL_CATALOG, null, 2)
762
- : formatModelCatalog({
763
- provider: options.provider,
764
- tier: options.tier,
765
- }));
766
- return;
767
- }
768
- if (options.cost) {
769
- console.log(options.json
770
- ? JSON.stringify((await import("../core/routing/route.js")).estimateCostSavings(), null, 2)
771
- : formatCostTable());
772
- return;
773
- }
774
- const description = descriptionWords.join(" ").trim();
775
- if (!description && !options.phase) {
776
- console.log(options.json
777
- ? JSON.stringify(allPhaseRoutes(options.conserve), null, 2)
778
- : formatRoutingTable(options.conserve));
779
- return;
780
- }
781
- const rec = options.phase
782
- ? routePhase(options.phase, options.conserve)
783
- : routeTask(description, options.conserve);
784
- // Recommendations are only useful when they name agents this machine
785
- // actually has, so detection drives both the advice and the handoff line.
786
- const detected = await detectAgents();
787
- const installedAgents = detected
788
- .filter((agent) => agent.installed)
789
- .map((agent) => agent.id);
790
- console.log(options.json
791
- ? JSON.stringify({ ...rec, installedAgents }, null, 2)
792
- : formatRouteRecommendation(rec, {
793
- installedAgents,
794
- ...(description ? { description } : {}),
795
- handoffReady: await isHandoffInitialized(process.cwd()),
796
- }));
797
- });
798
- program
799
- .command("completion")
800
- .description("Print a shell-completion script; redirect it to your shell profile")
801
- .argument("<shell>", "bash, zsh, fish, or powershell")
802
- .action((shell) => {
803
- process.stdout.write(renderShellCompletion(parseCompletionShell(shell)));
804
- });
446
+ registerWorkflowCommands(program);
805
447
  }