loadout-ai 0.5.9 → 0.7.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/README.md +181 -121
  2. package/catalog/discovered.json +28066 -23196
  3. package/catalog/packages.json +2 -2
  4. package/dist/src/cli.js +2 -2
  5. package/dist/src/commands/agents.js +6 -23
  6. package/dist/src/commands/catalog.js +254 -27
  7. package/dist/src/commands/health.js +6 -6
  8. package/dist/src/commands/inventory.js +21 -121
  9. package/dist/src/commands/lifecycle.js +13 -51
  10. package/dist/src/commands/mcp.js +10 -73
  11. package/dist/src/commands/setup.js +5 -5
  12. package/dist/src/commands/sharing.js +11 -108
  13. package/dist/src/commands/support.js +42 -11
  14. package/dist/src/core/{adapters.js → agents/adapters.js} +2 -2
  15. package/dist/src/core/{agent-inspection.js → agents/agent-inspection.js} +26 -6
  16. package/dist/src/core/{codex-mcp.js → agents/codex-mcp.js} +1 -1
  17. package/dist/src/core/{health-score-evidence.js → agents/health-score-evidence.js} +8 -8
  18. package/dist/src/core/{runtime-tools.js → agents/runtime-tools.js} +2 -2
  19. package/dist/src/core/{audit.js → catalog/audit.js} +4 -4
  20. package/dist/src/core/{catalog.js → catalog/catalog.js} +18 -5
  21. package/dist/src/core/{components.js → catalog/components.js} +2 -2
  22. package/dist/src/core/{conformance.js → catalog/conformance.js} +2 -2
  23. package/dist/src/core/{profiles.js → catalog/profiles.js} +2 -2
  24. package/dist/src/core/{provenance.js → catalog/provenance.js} +3 -3
  25. package/dist/src/core/{registry.js → catalog/registry.js} +1 -1
  26. package/dist/src/core/{skill-compare.js → catalog/skill-compare.js} +1 -1
  27. package/dist/src/core/{skill-inventory.js → catalog/skill-inventory.js} +3 -3
  28. package/dist/src/core/{skills.js → catalog/skills.js} +1 -1
  29. package/dist/src/core/{candidate-intelligence.js → discovery/candidate-intelligence.js} +6 -6
  30. package/dist/src/core/{discovery-connector.js → discovery/discovery-connector.js} +2 -2
  31. package/dist/src/core/{evaluate.js → discovery/evaluate.js} +2 -2
  32. package/dist/src/core/{mcp-registry-discovery.js → discovery/mcp-registry-discovery.js} +1 -1
  33. package/dist/src/core/{observations.js → discovery/observations.js} +3 -3
  34. package/dist/src/core/{review-queue.js → discovery/review-queue.js} +9 -4
  35. package/dist/src/core/{skills-sh-discovery.js → discovery/skills-sh-discovery.js} +1 -1
  36. package/dist/src/core/{adopt.js → install/adopt.js} +3 -3
  37. package/dist/src/core/{catalog-install.js → install/catalog-install.js} +11 -11
  38. package/dist/src/core/{file-lock.js → install/file-lock.js} +1 -1
  39. package/dist/src/core/{install.js → install/install.js} +5 -5
  40. package/dist/src/core/{package.js → install/package.js} +3 -3
  41. package/dist/src/core/{reconcile.js → install/reconcile.js} +5 -5
  42. package/dist/src/core/{remove.js → install/remove.js} +4 -4
  43. package/dist/src/core/{snapshot.js → install/snapshot.js} +1 -1
  44. package/dist/src/core/{source.js → install/source.js} +1 -1
  45. package/dist/src/core/{sync.js → install/sync.js} +9 -9
  46. package/dist/src/core/{transaction.js → install/transaction.js} +1 -1
  47. package/dist/src/core/{uninstall.js → install/uninstall.js} +4 -4
  48. package/dist/src/core/{update.js → install/update.js} +4 -4
  49. package/dist/src/core/reporting/cli-guide.js +103 -0
  50. package/dist/src/core/reporting/doctor.js +141 -0
  51. package/dist/src/core/{freshness-alerts.js → reporting/freshness-alerts.js} +5 -5
  52. package/dist/src/core/{health.js → reporting/health.js} +6 -6
  53. package/dist/src/core/{loadout-card.js → reporting/loadout-card.js} +1 -1
  54. package/dist/src/core/{readme-claims.js → reporting/readme-claims.js} +1 -1
  55. package/dist/src/core/{readme-facts.js → reporting/readme-facts.js} +2 -2
  56. package/dist/src/core/{share-report.js → reporting/share-report.js} +3 -3
  57. package/dist/src/core/{upgrade.js → reporting/upgrade.js} +7 -7
  58. package/dist/src/core/routing/first-party-skills.js +118 -0
  59. package/dist/src/core/routing/handoff.js +278 -0
  60. package/dist/src/core/{model-config.js → routing/model-config.js} +4 -4
  61. package/dist/src/core/routing/route.js +539 -0
  62. package/dist/src/core/{api.js → runtime/api.js} +4 -4
  63. package/dist/src/core/{canary.js → runtime/canary.js} +1 -1
  64. package/dist/src/core/{github.js → runtime/github.js} +1 -1
  65. package/dist/src/core/{mcp-recipes.js → runtime/mcp-recipes.js} +1 -1
  66. package/dist/src/core/{mcp.js → runtime/mcp.js} +2 -2
  67. package/dist/src/core/{scheduler.js → runtime/scheduler.js} +4 -4
  68. package/dist/src/core/{active-policy.js → workspace/active-policy.js} +2 -2
  69. package/dist/src/core/{active-set.js → workspace/active-set.js} +2 -2
  70. package/dist/src/core/{improve.js → workspace/improve.js} +3 -3
  71. package/dist/src/core/{manifest.js → workspace/manifest.js} +2 -2
  72. package/dist/src/core/{outcomes.js → workspace/outcomes.js} +3 -3
  73. package/dist/src/core/{portable.js → workspace/portable.js} +3 -3
  74. package/dist/src/core/{profile-state.js → workspace/profile-state.js} +1 -1
  75. package/dist/src/core/{recommend.js → workspace/recommend.js} +1 -1
  76. package/dist/src/core/{state.js → workspace/state.js} +3 -3
  77. package/docs/CATALOG.md +2 -2
  78. package/docs/DISCOVERED.md +249 -247
  79. package/docs/UPSTREAM_LICENSE_DECISIONS.md +19 -12
  80. package/docs/evidence/readme-claims.json +22 -19
  81. package/package.json +2 -1
  82. package/skills/loadout-curator/SKILL.md +105 -0
  83. package/skills/loadout-router/SKILL.md +120 -0
  84. package/dist/src/core/cli-guide.js +0 -118
  85. package/dist/src/core/doctor.js +0 -95
  86. /package/dist/src/core/{agent-health-score.js → agents/agent-health-score.js} +0 -0
  87. /package/dist/src/core/{agent-versions.js → agents/agent-versions.js} +0 -0
  88. /package/dist/src/core/{conversion.js → agents/conversion.js} +0 -0
  89. /package/dist/src/core/{paths.js → agents/paths.js} +0 -0
  90. /package/dist/src/core/{runtime-tool-recipe.js → agents/runtime-tool-recipe.js} +0 -0
  91. /package/dist/src/core/{catalog-coverage.js → catalog/catalog-coverage.js} +0 -0
  92. /package/dist/src/core/{safety.js → catalog/safety.js} +0 -0
  93. /package/dist/src/core/{skill-security.js → catalog/skill-security.js} +0 -0
  94. /package/dist/src/core/{community.js → discovery/community.js} +0 -0
  95. /package/dist/src/core/{github-discovery.js → discovery/github-discovery.js} +0 -0
  96. /package/dist/src/core/{private-discovery.js → discovery/private-discovery.js} +0 -0
  97. /package/dist/src/core/{ranking.js → discovery/ranking.js} +0 -0
  98. /package/dist/src/core/{atomic-file.js → install/atomic-file.js} +0 -0
  99. /package/dist/src/core/{diff.js → install/diff.js} +0 -0
  100. /package/dist/src/core/{update-watch.js → install/update-watch.js} +0 -0
  101. /package/dist/src/core/{completion.js → reporting/completion.js} +0 -0
  102. /package/dist/src/core/{loadout-badge.js → reporting/loadout-badge.js} +0 -0
  103. /package/dist/src/core/{terminal.js → reporting/terminal.js} +0 -0
  104. /package/dist/src/core/{access.js → routing/access.js} +0 -0
  105. /package/dist/src/core/{credentials.js → routing/credentials.js} +0 -0
  106. /package/dist/src/core/{sandbox.js → runtime/sandbox.js} +0 -0
  107. /package/dist/src/core/{active-limit.js → workspace/active-limit.js} +0 -0
  108. /package/dist/src/core/{target-occupancy.js → workspace/target-occupancy.js} +0 -0
@@ -208,7 +208,7 @@
208
208
  "description": "The open agent skills tool - npx skills.",
209
209
  "category": "skill-discovery",
210
210
  "tier": "stable",
211
- "license": "NOASSERTION",
211
+ "license": "MIT",
212
212
  "components": ["skill"],
213
213
  "operatingSystems": ["windows", "macos", "linux"],
214
214
  "stars": 26124,
@@ -271,7 +271,7 @@
271
271
  "description": "An MCP server for interacting with Sentry via LLMs.",
272
272
  "category": "observability",
273
273
  "tier": "official",
274
- "license": "NOASSERTION",
274
+ "license": "FSL-1.1-ALv2",
275
275
  "components": ["skill", "mcp"],
276
276
  "operatingSystems": ["windows", "macos", "linux"],
277
277
  "stars": 771,
package/dist/src/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command, CommanderError } from "commander";
3
- import { HIDDEN_FROM_FIRST_SCREEN } from "./core/cli-guide.js";
4
- import { recoverPendingTransactions } from "./core/transaction.js";
3
+ import { HIDDEN_FROM_FIRST_SCREEN } from "./core/reporting/cli-guide.js";
4
+ import { recoverPendingTransactions } from "./core/install/transaction.js";
5
5
  import { LOADOUT_VERSION, printBeginnerGuide, runWizard, } from "./commands/support.js";
6
6
  import { registerSetup } from "./commands/setup.js";
7
7
  import { registerSharing } from "./commands/sharing.js";
@@ -1,13 +1,9 @@
1
- import { detectAgents, parseAgentSelection } from "../core/paths.js";
2
- import { DEFAULT_ACTIVE_SKILL_LIMIT } from "../core/active-limit.js";
3
- import { resolve } from "node:path";
4
- import { writeFileAtomically } from "../core/atomic-file.js";
5
- import { applyActivationChange, formatActivationPlan, planActivationChange, } from "../core/active-set.js";
6
- import { applyProjectActivation, formatProjectActivation, planProjectActivation, } from "../core/active-policy.js";
7
- import { applyNativeSchedulerBundle, formatNativeScheduler, planNativeScheduler, } from "../core/scheduler.js";
8
- import { REVIEWED_RUNTIME_TOOLS, applyRuntimeToolPlan, formatRuntimeToolPlan, planRuntimeTool, } from "../core/runtime-tools.js";
9
- import { buildLoadoutCard } from "../core/loadout-card.js";
10
- import { buildLoadoutBadge, formatLoadoutBadgeUsage, parseLoadoutBadgeMetric, } from "../core/loadout-badge.js";
1
+ import { detectAgents, parseAgentSelection } from "../core/agents/paths.js";
2
+ import { DEFAULT_ACTIVE_SKILL_LIMIT } from "../core/workspace/active-limit.js";
3
+ import { applyActivationChange, formatActivationPlan, planActivationChange, } from "../core/workspace/active-set.js";
4
+ import { applyProjectActivation, formatProjectActivation, planProjectActivation, } from "../core/workspace/active-policy.js";
5
+ import { applyNativeSchedulerBundle, formatNativeScheduler, planNativeScheduler, } from "../core/runtime/scheduler.js";
6
+ import { REVIEWED_RUNTIME_TOOLS, applyRuntimeToolPlan, formatRuntimeToolPlan, planRuntimeTool, } from "../core/agents/runtime-tools.js";
11
7
  import { collectOption, durableSchedulerLauncher } from "./support.js";
12
8
  export function registerAgents(program) {
13
9
  for (const workflow of ["activate", "optimize"]) {
@@ -108,19 +104,6 @@ export function registerAgents(program) {
108
104
  ? JSON.stringify({ action, plans, snapshotId }, null, 2)
109
105
  : `Loadout Autopilot ${options.remove ? "removed" : "enabled"}: daily update radar + multi-source candidate discovery.\nNo scheduled command can install, promote, or execute a candidate. Snapshot: ${snapshotId}`);
110
106
  });
111
- program
112
- .command("badge")
113
- .description("Generate telemetry-free Shields endpoint JSON from the local privacy-safe card")
114
- .option("--metric <metric>", "evidence, active-skills, managed-packages, or mcp", "evidence")
115
- .option("--output <path>", "write endpoint JSON to this path")
116
- .action(async (options) => {
117
- const badge = buildLoadoutBadge(await buildLoadoutCard(), parseLoadoutBadgeMetric(options.metric));
118
- if (!options.output)
119
- return console.log(JSON.stringify(badge, null, 2));
120
- const output = resolve(options.output);
121
- await writeFileAtomically(output, `${JSON.stringify(badge, null, 2)}\n`);
122
- console.log(formatLoadoutBadgeUsage(output));
123
- });
124
107
  program
125
108
  .command("tool")
126
109
  .description("Preview, install, or remove an explicitly reviewed runtime-tool recipe")
@@ -1,20 +1,24 @@
1
- import { explainCatalogScore, loadEffectiveCatalog, loadCatalog, rankCatalog, refreshCatalog, } from "../core/catalog.js";
2
- import { parseAgentSelection } from "../core/paths.js";
1
+ import { explainCatalogScore, loadEffectiveCatalog, loadCatalog, promoteCatalogCandidate, rankCatalog, refreshCatalog, validateCatalog, } from "../core/catalog/catalog.js";
2
+ import { detectAgents, parseAgentSelection } from "../core/agents/paths.js";
3
3
  import { resolve } from "node:path";
4
- import { catalogTrustStage, formatCatalogTrustStage, } from "../core/profiles.js";
5
- import { discoverHackerNewsRepositories } from "../core/community.js";
6
- import { discoverPrivateRepositories } from "../core/private-discovery.js";
7
- import { defaultGitHubDiscoveryQueries, discoverGitHubRepositories, } from "../core/github-discovery.js";
8
- import { formatStarHistory, readCatalogObservations, } from "../core/observations.js";
9
- import { writeFileAtomically } from "../core/atomic-file.js";
10
- import { formatReviewQueue, mergeReviewQueue, readReviewQueue, setReviewDecision, } from "../core/review-queue.js";
11
- import { applyProviderModelSelection, defaultModelConfigurationPath, formatProviderModelConfiguration, planProviderModelSelection, readProviderModelConfiguration, requestOpenRouter, } from "../core/model-config.js";
12
- import { parseCompletionShell, renderShellCompletion, } from "../core/completion.js";
13
- import { buildCatalogCoverage, formatCatalogCoverage, } from "../core/catalog-coverage.js";
14
- import { createCredentialResolver, createOsCredentialStore, } from "../core/credentials.js";
15
- import { buildCandidateDossier, buildCatalogProposal, formatCandidateDossier, formatCandidateSummaries, listDiscoveryCandidates, readCandidateDossier, verifyCandidateDossierSource, writeCandidateDossier, } from "../core/candidate-intelligence.js";
16
- import { discoverSkillsSh } from "../core/skills-sh-discovery.js";
17
- import { discoverOfficialMcpRegistry } from "../core/mcp-registry-discovery.js";
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";
8
+ import { catalogTrustStage, formatCatalogTrustStage, } from "../core/catalog/profiles.js";
9
+ import { discoverHackerNewsRepositories } from "../core/discovery/community.js";
10
+ import { discoverPrivateRepositories } from "../core/discovery/private-discovery.js";
11
+ import { defaultGitHubDiscoveryQueries, discoverGitHubRepositories, } from "../core/discovery/github-discovery.js";
12
+ 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";
17
+ 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";
20
+ import { discoverSkillsSh } from "../core/discovery/skills-sh-discovery.js";
21
+ import { discoverOfficialMcpRegistry } from "../core/discovery/mcp-registry-discovery.js";
18
22
  import { readCredentialFromStdin } from "./support.js";
19
23
  export function registerCatalog(program) {
20
24
  program
@@ -199,6 +203,35 @@ export function registerCatalog(program) {
199
203
  else
200
204
  console.log(`Approved proposal written to ${output}.`);
201
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
+ });
202
235
  program
203
236
  .command("discover")
204
237
  .description("Find public community leads; discovery never installs anything")
@@ -412,17 +445,6 @@ export function registerCatalog(program) {
412
445
  ? JSON.stringify(filtered, null, 2)
413
446
  : formatReviewQueue(filtered));
414
447
  });
415
- program
416
- .command("review")
417
- .description("Record a human queue decision; shortlisting still does not promote or install")
418
- .argument("<repository>", "owner/repository")
419
- .requiredOption("--decision <value>", "pending, shortlisted, or ignored")
420
- .action(async (repository, options) => {
421
- if (!["pending", "shortlisted", "ignored"].includes(options.decision))
422
- throw new Error("--decision must be pending, shortlisted, or ignored");
423
- const item = await setReviewDecision(repository, options.decision);
424
- console.log(`${item.repository}: ${item.decision}. No catalog or agent files changed.`);
425
- });
426
448
  const credentials = program
427
449
  .command("credentials")
428
450
  .description("Store, inspect, or remove secrets in the native OS credential store");
@@ -568,6 +590,211 @@ export function registerCatalog(program) {
568
590
  });
569
591
  console.log(`Verified model selection '${id}'. No credential value was stored or printed.`);
570
592
  });
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
+ });
571
798
  program
572
799
  .command("completion")
573
800
  .description("Print a shell-completion script; redirect it to your shell profile")
@@ -1,9 +1,9 @@
1
- import { parseAgentSelection } from "../core/paths.js";
2
- import { buildUpdatePlan } from "../core/update.js";
3
- import { buildHealthReport, formatHealthReport } from "../core/health.js";
4
- import { buildFreshnessAlerts, formatFreshnessAlerts, ignoreFreshnessAlert, pinReplacement, readReplacementPins, unpinReplacement, } from "../core/freshness-alerts.js";
5
- import { formatAgentHealthScore } from "../core/agent-health-score.js";
6
- import { buildLocalAgentHealthScores } from "../core/health-score-evidence.js";
1
+ import { parseAgentSelection } from "../core/agents/paths.js";
2
+ import { buildUpdatePlan } from "../core/install/update.js";
3
+ import { buildHealthReport, formatHealthReport, } from "../core/reporting/health.js";
4
+ import { buildFreshnessAlerts, formatFreshnessAlerts, ignoreFreshnessAlert, pinReplacement, readReplacementPins, unpinReplacement, } from "../core/reporting/freshness-alerts.js";
5
+ import { formatAgentHealthScore } from "../core/agents/agent-health-score.js";
6
+ import { buildLocalAgentHealthScores } from "../core/agents/health-score-evidence.js";
7
7
  export function registerHealth(program) {
8
8
  program
9
9
  .command("health")
@@ -1,24 +1,21 @@
1
- import { loadEffectiveCatalog } from "../core/catalog.js";
2
- import { detectAgents, parseAgentSelection } from "../core/paths.js";
1
+ import { loadEffectiveCatalog } from "../core/catalog/catalog.js";
2
+ import { detectAgents, parseAgentSelection } from "../core/agents/paths.js";
3
3
  import { resolve } from "node:path";
4
- import { runDoctor, formatDoctorReport } from "../core/doctor.js";
5
- import { applyProfileToManifest } from "../core/manifest.js";
6
- import { buildHealthReport, formatStatusScreen, gradeHealth, } from "../core/health.js";
7
- import { applyRemove, planRemove } from "../core/remove.js";
8
- import { applyUninstall, buildUninstallPlan, formatUninstallPlan, uninstallGlobalCli, } from "../core/uninstall.js";
9
- import { formatRecommendations, personalizeRecommendations, profileManifestPackages, recommendPackages, RECOMMENDATION_BOUNDARY, scanProject, TESTED_PROFILES, } from "../core/recommend.js";
10
- import { buildImprovementCycle, formatImprovementCycle, recordImprovementOutcome, writeImprovementCycle, } from "../core/improve.js";
11
- import { applySyncPlan, buildSyncPlan } from "../core/sync.js";
12
- import { ADAPTER_CAPABILITIES, buildAdapterCapabilityGaps, formatAdapterCapabilityGaps, formatCapabilityMatrix, } from "../core/adapters.js";
13
- import { formatAgentInventory, inspectAgents, } from "../core/agent-inspection.js";
14
- import { formatInstalledSkillInventory, scanInstalledSkills, } from "../core/skill-inventory.js";
15
- import { enrichInventoryWithProvenance, formatProvenanceSummary, resolveCatalogSkillIndex, } from "../core/provenance.js";
16
- import { compareSkill, formatSkillComparison } from "../core/skill-compare.js";
17
- import { applySkillAdoption, formatAdoptionPlan, planSkillAdoption, } from "../core/adopt.js";
18
- import { applyReconcilePlan, buildReconcilePlan, formatReconcilePlan, } from "../core/reconcile.js";
19
- import { readLocalOutcomes } from "../core/outcomes.js";
20
- import { formatAgentVersions, inspectAgentVersions, } from "../core/agent-versions.js";
21
- import { scanSkillSecurity } from "../core/skill-security.js";
4
+ import { runDoctor, formatDoctorReport } from "../core/reporting/doctor.js";
5
+ import { applyProfileToManifest } from "../core/workspace/manifest.js";
6
+ import { buildHealthReport, formatStatusScreen, gradeHealth, } from "../core/reporting/health.js";
7
+ import { applyRemove, planRemove } from "../core/install/remove.js";
8
+ import { applyUninstall, buildUninstallPlan, formatUninstallPlan, uninstallGlobalCli, } from "../core/install/uninstall.js";
9
+ import { formatRecommendations, personalizeRecommendations, profileManifestPackages, recommendPackages, RECOMMENDATION_BOUNDARY, scanProject, TESTED_PROFILES, } from "../core/workspace/recommend.js";
10
+ import { applySyncPlan, buildSyncPlan } from "../core/install/sync.js";
11
+ import { formatAgentInventory, inspectAgents, } from "../core/agents/agent-inspection.js";
12
+ import { formatInstalledSkillInventory, scanInstalledSkills, } from "../core/catalog/skill-inventory.js";
13
+ import { enrichInventoryWithProvenance, formatProvenanceSummary, resolveCatalogSkillIndex, } from "../core/catalog/provenance.js";
14
+ import { applySkillAdoption, formatAdoptionPlan, planSkillAdoption, } from "../core/install/adopt.js";
15
+ import { applyReconcilePlan, buildReconcilePlan, formatReconcilePlan, } from "../core/install/reconcile.js";
16
+ import { readLocalOutcomes } from "../core/workspace/outcomes.js";
17
+ import { formatAgentVersions, inspectAgentVersions, } from "../core/agents/agent-versions.js";
18
+ import { scanSkillSecurity } from "../core/catalog/skill-security.js";
22
19
  import { printProvenanceProgress } from "./support.js";
23
20
  export function registerInventory(program) {
24
21
  program
@@ -117,35 +114,6 @@ export function registerInventory(program) {
117
114
  ? JSON.stringify({ name, ...TESTED_PROFILES[name], packages }, null, 2)
118
115
  : `${name}: ${TESTED_PROFILES[name].description}\n${packages.map((pkg) => ` ${pkg.id} — ${pkg.repository}`).join("\n")}`);
119
116
  });
120
- program
121
- .command("improve")
122
- .description("Propose the next evidence-backed improvement without changing anything")
123
- .option("--json", "emit machine-readable JSON")
124
- .option("--write", "persist the cycle record and reusable prompt locally")
125
- .option("--output <directory>", "output directory; defaults to private Loadout state")
126
- .action(async (options) => {
127
- const cycle = await buildImprovementCycle();
128
- console.log(options.json
129
- ? JSON.stringify(cycle, null, 2)
130
- : formatImprovementCycle(cycle));
131
- if (options.write) {
132
- const paths = await writeImprovementCycle(cycle, options.output);
133
- console.log(`Cycle record: ${paths.json}\nLoop prompt: ${paths.prompt}`);
134
- }
135
- });
136
- program
137
- .command("improve-feedback")
138
- .description("Record a human-reviewed outcome for a persisted improvement cycle")
139
- .requiredOption("--id <id>", "cycle id")
140
- .requiredOption("--outcome <outcome>", "success, failure, or partial")
141
- .option("--note <text>", "short non-secret lesson")
142
- .option("--directory <path>", "improvement history directory")
143
- .action(async (options) => {
144
- if (!["success", "failure", "partial"].includes(options.outcome))
145
- throw new Error("--outcome must be success, failure, or partial");
146
- await recordImprovementOutcome(options.id, options.outcome, options.note, options.directory);
147
- console.log(`Recorded ${options.outcome} outcome for ${options.id}.`);
148
- });
149
117
  program
150
118
  .command("sync")
151
119
  .description("Reproduce a loadout.json manifest as one safe transaction")
@@ -284,53 +252,10 @@ export function registerInventory(program) {
284
252
  ? JSON.stringify({ plan, snapshotId }, null, 2)
285
253
  : `${formatAdoptionPlan(plan)}\nAdopted without changing skill bytes. Snapshot: ${snapshotId}`);
286
254
  });
287
- program
288
- .command("compare")
289
- .description("Compare an installed or reviewed skill with evidence-related catalog alternatives without changing anything")
290
- .argument("<skill>", "installed skill name, directory name, or catalog package id")
291
- .option("--agent <id>", "select one installed agent when names are ambiguous")
292
- .option("--refresh", "rebuild the inspected catalog skill index")
293
- .option("--offline", "never fetch; use the existing local index only")
294
- .option("--limit <count>", "maximum alternatives to show", "10")
295
- .option("--json", "emit the complete machine-readable comparison")
296
- .action(async (skill, options) => {
297
- if (options.refresh && options.offline)
298
- throw new Error("--refresh and --offline cannot be used together");
299
- const limit = Number(options.limit);
300
- if (!Number.isInteger(limit) || limit < 1 || limit > 50)
301
- throw new Error("--limit must be an integer from 1 to 50");
302
- const detected = await detectAgents();
303
- const agent = options.agent;
304
- if (agent && !detected.some((item) => item.id === agent))
305
- throw new Error(`Unknown agent id: ${options.agent}`);
306
- const selected = detected.filter((item) => item.installed);
307
- if (!selected.length)
308
- throw new Error("No detected agent profile is available to compare");
309
- const catalog = await loadEffectiveCatalog();
310
- const resolved = await resolveCatalogSkillIndex({
311
- refresh: options.refresh,
312
- offline: options.offline,
313
- build: {
314
- catalog,
315
- onProgress: options.offline ? undefined : printProvenanceProgress,
316
- },
317
- });
318
- if (!resolved.index)
319
- throw new Error("No local catalog skill index exists. Re-run without --offline or use --refresh.");
320
- const inventory = enrichInventoryWithProvenance(await scanInstalledSkills(selected), resolved.index, resolved.source);
321
- const comparison = compareSkill(skill, inventory, resolved.index.records, catalog, {
322
- ...(agent ? { agent } : {}),
323
- limit,
324
- indexGeneratedAt: resolved.index.generatedAt,
325
- failures: resolved.index.failures,
326
- });
327
- console.log(options.json
328
- ? JSON.stringify(comparison, null, 2)
329
- : formatSkillComparison(comparison));
330
- });
331
255
  program
332
256
  .command("status")
333
257
  .description("Home screen: a health grade, detected agents, and the next thing to do")
258
+ .option("--details", "show per-component adapter compatibility for each agent")
334
259
  .option("--json", "emit machine-readable status")
335
260
  .action(async (options) => {
336
261
  const agents = await detectAgents();
@@ -340,7 +265,7 @@ export function registerInventory(program) {
340
265
  ]);
341
266
  if (options.json)
342
267
  return console.log(JSON.stringify({ grade: gradeHealth(report), report, inventory }, null, 2));
343
- console.log(formatStatusScreen(report, inventory.map((item) => formatAgentInventory(item))));
268
+ console.log(formatStatusScreen(report, inventory.map((item) => formatAgentInventory(item, { details: options.details }))));
344
269
  });
345
270
  program
346
271
  .command("versions")
@@ -377,40 +302,15 @@ export function registerInventory(program) {
377
302
  if (report.verdict === "blocked")
378
303
  process.exitCode = 2;
379
304
  });
380
- program
381
- .command("capabilities")
382
- .description("Show honest native, adapted, and unsupported adapter capabilities")
383
- .option("--json", "emit machine-readable JSON")
384
- .option("--inspect", "also inspect managed component directories on this machine")
385
- .option("--gaps", "show the evidence-gated backlog for unsupported adapter combinations")
386
- .action(async (options) => {
387
- if (options.gaps) {
388
- if (options.inspect)
389
- throw new Error("Choose either --inspect or --gaps");
390
- const gaps = buildAdapterCapabilityGaps();
391
- return console.log(options.json
392
- ? JSON.stringify(gaps, null, 2)
393
- : formatAdapterCapabilityGaps());
394
- }
395
- if (!options.inspect)
396
- return console.log(options.json
397
- ? JSON.stringify(ADAPTER_CAPABILITIES, null, 2)
398
- : formatCapabilityMatrix());
399
- const inventory = await inspectAgents(await detectAgents());
400
- if (options.json)
401
- return console.log(JSON.stringify({ capabilities: ADAPTER_CAPABILITIES, inventory }, null, 2));
402
- console.log(`${formatCapabilityMatrix()}\n\nLocal managed-component inventory:`);
403
- for (const item of inventory)
404
- console.log(formatAgentInventory(item));
405
- });
406
305
  program
407
306
  .command("doctor")
408
307
  .description("Check agents, skill directories, permissions, and Loadout setup")
308
+ .option("--verbose", "show per-component detail for each agent")
409
309
  .option("--json", "print a machine-readable report")
410
310
  .action(async (options) => {
411
311
  const report = await runDoctor();
412
312
  console.log(options.json
413
313
  ? JSON.stringify(report, null, 2)
414
- : formatDoctorReport(report));
314
+ : formatDoctorReport(report, { verbose: options.verbose }));
415
315
  });
416
316
  }