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
@@ -1,20 +1,18 @@
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 { readFile } from "node:fs/promises";
4
4
  import { resolve } from "node:path";
5
- import { buildSkillPlan, applySkillInstall, installedAgents, } from "../core/install.js";
6
- import { listSnapshotIds, readSnapshot, restoreSnapshot, summarizeSnapshot, } from "../core/snapshot.js";
7
- import { fetchRepositorySnapshot } from "../core/source.js";
8
- import { applyPackageUpdate, buildUpdatePlan, formatUpdatePlan, selectSafeAutomaticUpdates, } from "../core/update.js";
9
- import { startApiServer } from "../core/api.js";
10
- import { readInstallState } from "../core/state.js";
11
- import { evaluateInstalledProfile, formatInstalledProfileStatus, } from "../core/profile-state.js";
12
- import { resolveCatalogProfile } from "../core/profiles.js";
13
- import { compileConversion, } from "../core/conversion.js";
14
- import { writeFileAtomically } from "../core/atomic-file.js";
15
- import { formatCanaryResult, runCanary } from "../core/canary.js";
16
- import { applyPreparedCatalogInstall, formatCatalogApplyGuidance, formatPreparedCatalogInstall, prepareCatalogInstall, } from "../core/catalog-install.js";
17
- import { withMutationLock } from "../core/transaction.js";
5
+ import { buildSkillPlan, applySkillInstall, installedAgents, } from "../core/install/install.js";
6
+ import { listSnapshotIds, readSnapshot, restoreSnapshot, summarizeSnapshot, } from "../core/install/snapshot.js";
7
+ import { fetchRepositorySnapshot } from "../core/install/source.js";
8
+ import { applyPackageUpdate, buildUpdatePlan, formatUpdatePlan, selectSafeAutomaticUpdates, } from "../core/install/update.js";
9
+ import { readInstallState } from "../core/workspace/state.js";
10
+ import { evaluateInstalledProfile, formatInstalledProfileStatus, } from "../core/workspace/profile-state.js";
11
+ import { resolveCatalogProfile } from "../core/catalog/profiles.js";
12
+ import { compileConversion, } from "../core/agents/conversion.js";
13
+ import { writeFileAtomically } from "../core/install/atomic-file.js";
14
+ import { applyPreparedCatalogInstall, formatCatalogApplyGuidance, formatPreparedCatalogInstall, prepareCatalogInstall, } from "../core/install/catalog-install.js";
15
+ import { withMutationLock } from "../core/install/transaction.js";
18
16
  import { setupSelection, printSetupProgress } from "./support.js";
19
17
  export function registerLifecycle(program) {
20
18
  program
@@ -372,40 +370,4 @@ export function registerLifecycle(program) {
372
370
  ? "Manual approval is required before using this artifact."
373
371
  : "No additional approval is required."));
374
372
  });
375
- program
376
- .command("canary")
377
- .description("Run a static canary policy gate for a candidate without installing it")
378
- .requiredOption("--source <directory>", "candidate package directory")
379
- .requiredOption("--package <id>", "candidate package id")
380
- .option("--repository <owner/repo>", "candidate repository")
381
- .option("--commit <sha>", "candidate immutable commit")
382
- .option("--approve", "approve promotion if a promotion callback is supplied")
383
- .option("--allow-unready", "allow review findings in the static gate")
384
- .option("--json", "emit machine-readable JSON")
385
- .action(async (options) => {
386
- const result = await runCanary({
387
- packageId: options.package,
388
- root: options.source,
389
- ...(options.repository ? { repository: options.repository } : {}),
390
- ...(options.commit ? { commit: options.commit } : {}),
391
- }, { enabled: true, requireStaticReady: !options.allowUnready }, { approve: options.approve });
392
- console.log(options.json
393
- ? JSON.stringify(result, null, 2)
394
- : formatCanaryResult(result));
395
- if (result.status === "blocked")
396
- process.exitCode = 1;
397
- });
398
- program
399
- .command("serve")
400
- .description("Start a loopback-only read-only API for status, health, catalog, and updates")
401
- .option("--port <port>", "TCP port (0 selects an available port)", "0")
402
- .action(async (options) => {
403
- const handle = await startApiServer({ port: Number(options.port) });
404
- console.log(`Loadout API listening at http://${handle.host}:${handle.port}`);
405
- await new Promise((resolve) => {
406
- process.once("SIGINT", resolve);
407
- process.once("SIGTERM", resolve);
408
- });
409
- await handle.close();
410
- });
411
373
  }
@@ -1,16 +1,15 @@
1
- import { userHome } from "../core/paths.js";
1
+ import { userHome } from "../core/agents/paths.js";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
- import { fetchRepositorySnapshot } from "../core/source.js";
5
- import { discoverMcpManifests, summarizeMcpManifest, planMcpConfig, summarizeMcpConfigPlan, applyMcpConfigPlan, } from "../core/mcp.js";
6
- import { REVIEWED_MCP_RECIPES, buildMcpRecipeServer, findMcpRecipe, formatMcpRecipePlan, planMcpRecipe, verifyMcpRecipe, verifyMcpRecipeConnection, } from "../core/mcp-recipes.js";
7
- import { inspectPackage, formatPackageInspection } from "../core/package.js";
8
- import { installStatePath, recordInstallTransaction, recordMcpInstall, } from "../core/state.js";
9
- import { applyCodexMcpConfigPlan, codexMcpServerFingerprint, defaultCodexMcpConfigPath, planCodexMcpConfig, } from "../core/codex-mcp.js";
10
- import { evaluatePackage, formatPackageEvaluation } from "../core/evaluate.js";
11
- import { checkForUpdates, startUpdateWatcher } from "../core/update-watch.js";
12
- import { runDisposableSandbox } from "../core/sandbox.js";
13
- import { applyNativeScheduler, formatNativeScheduler, planNativeScheduler, } from "../core/scheduler.js";
4
+ import { fetchRepositorySnapshot } from "../core/install/source.js";
5
+ import { discoverMcpManifests, summarizeMcpManifest, planMcpConfig, summarizeMcpConfigPlan, applyMcpConfigPlan, } from "../core/runtime/mcp.js";
6
+ import { REVIEWED_MCP_RECIPES, buildMcpRecipeServer, findMcpRecipe, formatMcpRecipePlan, planMcpRecipe, verifyMcpRecipe, verifyMcpRecipeConnection, } from "../core/runtime/mcp-recipes.js";
7
+ import { inspectPackage, formatPackageInspection, } from "../core/install/package.js";
8
+ import { installStatePath, recordInstallTransaction, recordMcpInstall, } from "../core/workspace/state.js";
9
+ import { applyCodexMcpConfigPlan, codexMcpServerFingerprint, defaultCodexMcpConfigPath, planCodexMcpConfig, } from "../core/agents/codex-mcp.js";
10
+ import { evaluatePackage, formatPackageEvaluation, } from "../core/discovery/evaluate.js";
11
+ import { checkForUpdates, startUpdateWatcher, } from "../core/install/update-watch.js";
12
+ import { applyNativeScheduler, formatNativeScheduler, planNativeScheduler, } from "../core/runtime/scheduler.js";
14
13
  import { collectOption, parseMcpCredentialMappings, durableSchedulerLauncher, } from "./support.js";
15
14
  export function registerMcp(program) {
16
15
  program
@@ -318,29 +317,6 @@ export function registerMcp(program) {
318
317
  : `${formatNativeScheduler(plan)}\nApplied. Snapshot: ${snapshotId}`);
319
318
  });
320
319
  }
321
- program
322
- .command("sandbox-run")
323
- .description("Run an explicitly approved command in a disposable networkless Docker sandbox")
324
- .requiredOption("--source <directory>", "read-only source directory")
325
- .requiredOption("--image <image>", "reviewed/pinned Docker image reference")
326
- .requiredOption("--command <argument>", "command argument (repeatable; first is executable)", (value, previous = []) => [...previous, value], [])
327
- .requiredOption("--approve-risk", "explicitly approve sandbox execution")
328
- .option("--timeout <milliseconds>", "execution timeout", "120000")
329
- .option("--json", "emit result JSON")
330
- .action(async (options) => {
331
- const result = await runDisposableSandbox({
332
- sourceDirectory: options.source,
333
- image: options.image,
334
- command: options.command,
335
- approveRisk: options.approveRisk,
336
- timeoutMs: Number(options.timeout),
337
- });
338
- console.log(options.json
339
- ? JSON.stringify(result, null, 2)
340
- : `Sandbox exited ${result.exitCode}\n${result.stdout}${result.stderr ? `\n${result.stderr}` : ""}`);
341
- if (result.exitCode !== 0)
342
- process.exitCode = result.exitCode;
343
- });
344
320
  program
345
321
  .command("mcp-config")
346
322
  .description("Plan or apply a safe MCP server configuration change (dry-run by default)")
@@ -382,43 +358,4 @@ export function registerMcp(program) {
382
358
  const snapshot = await applyMcpConfigPlan(plan);
383
359
  console.log(`Applied successfully. Snapshot: ${snapshot.rollbackSnapshotId}`);
384
360
  });
385
- program
386
- .command("codex-mcp-config")
387
- .description("Plan or add a Codex TOML MCP server (dry-run by default)")
388
- .option("--config <path>", "Codex config.toml path", defaultCodexMcpConfigPath())
389
- .requiredOption("--name <name>", "server name")
390
- .option("--command <command>", "local server command")
391
- .option("--url <url>", "remote MCP server URL")
392
- .option("--arg <value>", "server argument (repeatable)", (value, previous = []) => [...previous, value], [])
393
- .option("--env <NAME=VALUE>", "environment variable (repeatable; values are never printed)", (value, previous = []) => [...previous, value], [])
394
- .option("--yes", "apply the change; without this flag only a plan is shown")
395
- .action(async (options) => {
396
- if ((options.command ? 1 : 0) + (options.url ? 1 : 0) !== 1)
397
- throw new Error("Provide exactly one of --command or --url");
398
- const env = {};
399
- for (const item of options.env) {
400
- const separator = item.indexOf("=");
401
- if (separator <= 0)
402
- throw new Error(`Invalid --env '${item}'; expected NAME=VALUE`);
403
- const key = item.slice(0, separator);
404
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
405
- throw new Error(`Invalid environment variable name '${key}'`);
406
- env[key] = item.slice(separator + 1);
407
- }
408
- const server = {
409
- name: options.name,
410
- command: options.command,
411
- url: options.url,
412
- args: options.arg,
413
- env,
414
- sourcePath: options.config,
415
- warnings: [],
416
- };
417
- const plan = await planCodexMcpConfig(options.config, server);
418
- console.log(`Codex config: ${plan.path}\n - ${plan.summary}`);
419
- if (!options.yes)
420
- return console.log("Dry run only. Re-run with --yes to add this Codex MCP server.");
421
- const snapshot = await applyCodexMcpConfigPlan(plan);
422
- console.log(`Applied successfully. Snapshot: ${snapshot.rollbackSnapshotId}`);
423
- });
424
361
  }
@@ -1,8 +1,8 @@
1
- import { parseAgentSelection } from "../core/paths.js";
2
- import { formatHealthReport } from "../core/health.js";
3
- import { applyUpgrade, formatUpgradePlan, planUpgrade, summarizeUpgradePlan, } from "../core/upgrade.js";
4
- import { parseModelApiAccess } from "../core/access.js";
5
- import { ADVANCED_GUIDE } from "../core/cli-guide.js";
1
+ import { parseAgentSelection } from "../core/agents/paths.js";
2
+ import { formatHealthReport } from "../core/reporting/health.js";
3
+ import { applyUpgrade, formatUpgradePlan, planUpgrade, summarizeUpgradePlan, } from "../core/reporting/upgrade.js";
4
+ import { parseModelApiAccess } from "../core/routing/access.js";
5
+ import { ADVANCED_GUIDE } from "../core/reporting/cli-guide.js";
6
6
  import { collectOption, setupSelection, printSetupProgress, runSetup, printBeginnerGuide, } from "./support.js";
7
7
  export function registerSetup(program) {
8
8
  program
@@ -1,18 +1,15 @@
1
- import { loadEffectiveCatalog } from "../core/catalog.js";
2
- import { detectAgents, parseAgentSelection } from "../core/paths.js";
3
- import { readFile } from "node:fs/promises";
1
+ import { loadEffectiveCatalog } from "../core/catalog/catalog.js";
2
+ import { parseAgentSelection } from "../core/agents/paths.js";
4
3
  import { resolve } from "node:path";
5
- import { addManifestPackage, initManifest, readManifest, removeManifestPackage, writeLockfile, } from "../core/manifest.js";
6
- import { readInstallState } from "../core/state.js";
7
- import { createPackage, packPackage, publishLocalPackage, publishRemotePackage, searchLocalRegistry, } from "../core/registry.js";
8
- import { auditLoadout, formatAuditReport } from "../core/audit.js";
9
- import { applyPortableImport, exportPortableLoadout, planPortableImport, } from "../core/portable.js";
10
- import { writeFileAtomically } from "../core/atomic-file.js";
11
- import { buildLibraryStateReport, formatLibrarySummary, formatLibraryStateReport, } from "../core/active-set.js";
12
- import { buildPrivacySafeReport, formatPrivacySafeReport, parsePrivacySafeLoadoutReport, writePrivacySafeReport, } from "../core/share-report.js";
13
- import { readLocalOutcomes, recordLocalOutcome, } from "../core/outcomes.js";
14
- import { createCredentialResolver } from "../core/credentials.js";
15
- import { buildLoadoutCard, compareLoadoutReports, formatLoadoutCard, formatLoadoutComparison, } from "../core/loadout-card.js";
4
+ import { addManifestPackage, initManifest, readManifest, removeManifestPackage, writeLockfile, } from "../core/workspace/manifest.js";
5
+ import { readInstallState } from "../core/workspace/state.js";
6
+ import { createPackage, searchLocalRegistry, } from "../core/catalog/registry.js";
7
+ import { auditLoadout, formatAuditReport } from "../core/catalog/audit.js";
8
+ import { applyPortableImport, exportPortableLoadout, planPortableImport, } from "../core/workspace/portable.js";
9
+ import { writeFileAtomically } from "../core/install/atomic-file.js";
10
+ import { buildLibraryStateReport, formatLibrarySummary, formatLibraryStateReport, } from "../core/workspace/active-set.js";
11
+ import { buildPrivacySafeReport, formatPrivacySafeReport, writePrivacySafeReport, } from "../core/reporting/share-report.js";
12
+ import { buildLoadoutCard, formatLoadoutCard, } from "../core/reporting/loadout-card.js";
16
13
  export function registerSharing(program) {
17
14
  program
18
15
  .command("init")
@@ -89,54 +86,6 @@ export function registerSharing(program) {
89
86
  const descriptor = await createPackage(directory, options);
90
87
  console.log(`Created ${descriptor.name}@${descriptor.version} in ${directory}.`);
91
88
  });
92
- program
93
- .command("pack")
94
- .description("Validate a package and print its deterministic inventory digest")
95
- .argument("[directory]", "package directory", ".")
96
- .option("--json", "emit machine-readable JSON")
97
- .action(async (directory, options) => {
98
- const packed = await packPackage(directory);
99
- console.log(options.json
100
- ? JSON.stringify(packed, null, 2)
101
- : `${packed.descriptor.name}@${packed.descriptor.version} — ${packed.files.length} file(s) — sha256:${packed.digest}`);
102
- });
103
- program
104
- .command("publish")
105
- .description("Publish an immutable package version to a local or remote Loadout registry")
106
- .argument("[directory]", "package directory", ".")
107
- .option("--local", "publish to the local registry")
108
- .option("--registry-url <url>", "remote registry base URL")
109
- .option("--credential-keychain <service>", "resolve the remote registry token from the OS credential store")
110
- .option("--credential-account <account>", "OS credential account")
111
- .option("--approve-risk", "explicitly approve publishing scripts, hooks, or binaries")
112
- .action(async (directory, options) => {
113
- if (Number(Boolean(options.local)) +
114
- Number(Boolean(options.registryUrl)) !==
115
- 1)
116
- throw new Error("Choose exactly one destination: --local or --registry-url");
117
- if (options.local) {
118
- if (options.credentialKeychain)
119
- throw new Error("Local publishing does not require a credential");
120
- const packed = await publishLocalPackage(directory, {
121
- approveRisk: options.approveRisk,
122
- });
123
- console.log(`Published ${packed.descriptor.name}@${packed.descriptor.version} with digest ${packed.digest}.`);
124
- return;
125
- }
126
- const token = await createCredentialResolver()(options.credentialKeychain
127
- ? {
128
- kind: "os-keychain",
129
- service: options.credentialKeychain,
130
- ...(options.credentialAccount
131
- ? { account: options.credentialAccount }
132
- : {}),
133
- }
134
- : { kind: "environment", name: "LOADOUT_REGISTRY_TOKEN" });
135
- if (!token)
136
- throw new Error("Remote publishing credential did not resolve; set LOADOUT_REGISTRY_TOKEN or use --credential-keychain");
137
- const published = await publishRemotePackage(directory, options.registryUrl, token, { approveRisk: options.approveRisk });
138
- console.log(`Published ${published.name}@${published.version} with digest ${published.digest}.`);
139
- });
140
89
  program
141
90
  .command("search")
142
91
  .description("Search the bundled catalog and local registry")
@@ -283,39 +232,6 @@ export function registerSharing(program) {
283
232
  ? JSON.stringify(report, null, 2)
284
233
  : formatPrivacySafeReport(report));
285
234
  });
286
- program
287
- .command("outcomes")
288
- .description("Show privacy-safe local outcome signals; never uploads project or prompt data")
289
- .option("--json", "emit machine-readable JSON")
290
- .action(async (options) => {
291
- const store = await readLocalOutcomes();
292
- console.log(options.json
293
- ? JSON.stringify(store, null, 2)
294
- : [
295
- `Local outcomes: ${store.events.length}`,
296
- ...store.events.map((event) => `${event.recordedAt} — ${event.selector} — ${event.agent}/${event.taskFamily} — ${event.result}`),
297
- "Privacy: local only; no project names, paths, prompts, code, filenames, or secrets.",
298
- ].join("\n"));
299
- });
300
- program
301
- .command("outcome")
302
- .description("Record an explicit local, agent/task-scoped skill outcome")
303
- .argument("<selector>", "exact package/skill selector")
304
- .requiredOption("--agent <id>", "agent id")
305
- .requiredOption("--task <family>", "general, frontend, testing, javascript, python, backend, security, or documentation")
306
- .requiredOption("--result <value>", "accept, reject, success, failure, activation, disable, or rollback")
307
- .action(async (selector, options) => {
308
- const knownAgents = new Set((await detectAgents()).map((agent) => agent.id));
309
- if (!knownAgents.has(options.agent))
310
- throw new Error(`Unknown agent id: ${options.agent}`);
311
- const event = await recordLocalOutcome({
312
- selector,
313
- agent: options.agent,
314
- taskFamily: options.task,
315
- result: options.result,
316
- });
317
- console.log(`Recorded local outcome ${event.id}. No project, prompt, or source data was stored.`);
318
- });
319
235
  program
320
236
  .command("share")
321
237
  .description("Write the privacy-safe Loadout report to a JSON artifact")
@@ -342,17 +258,4 @@ export function registerSharing(program) {
342
258
  }
343
259
  console.log(options.json ? JSON.stringify(card, null, 2) : markdown);
344
260
  });
345
- program
346
- .command("compare-loadouts")
347
- .description("Compare aggregate counts from two explicit privacy-safe Loadout reports")
348
- .argument("<left>", "earlier privacy-safe report JSON")
349
- .argument("<right>", "later privacy-safe report JSON")
350
- .option("--json", "emit machine-readable count deltas")
351
- .action(async (left, right, options) => {
352
- const [before, after] = await Promise.all([left, right].map(async (path) => parsePrivacySafeLoadoutReport(JSON.parse(await readFile(resolve(path), "utf8")))));
353
- const comparison = compareLoadoutReports(before, after);
354
- console.log(options.json
355
- ? JSON.stringify(comparison, null, 2)
356
- : formatLoadoutComparison(comparison));
357
- });
358
261
  }
@@ -1,10 +1,12 @@
1
1
  import { createInterface } from "node:readline/promises";
2
- import { detectAgents, parseAgentSelection } from "../core/paths.js";
3
- import { dirname, join } from "node:path";
4
- import { applyPreparedCatalogInstall, formatCatalogApplyGuidance, formatPreparedCatalogInstall, prepareCatalogInstall, } from "../core/catalog-install.js";
5
- import { formatInstalledSkillInventory, scanInstalledSkills, } from "../core/skill-inventory.js";
6
- import { interactiveModelApiAccess, parseModelApiAccess, } from "../core/access.js";
7
- import { BEGINNER_GUIDE } from "../core/cli-guide.js";
2
+ import { createRequire } from "node:module";
3
+ import { detectAgents, parseAgentSelection } from "../core/agents/paths.js";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { applyPreparedCatalogInstall, formatCatalogApplyGuidance, formatPreparedCatalogInstall, prepareCatalogInstall, } from "../core/install/catalog-install.js";
7
+ import { formatInstalledSkillInventory, scanInstalledSkills, } from "../core/catalog/skill-inventory.js";
8
+ import { interactiveModelApiAccess, parseModelApiAccess, } from "../core/routing/access.js";
9
+ import { BEGINNER_GUIDE } from "../core/reporting/cli-guide.js";
8
10
  export const collectOption = (value, previous = []) => [...previous, value];
9
11
  export function parseMcpCredentialMappings(recipe, mappings, account) {
10
12
  const references = {};
@@ -103,7 +105,16 @@ export async function runSetup(options) {
103
105
  try {
104
106
  if (!mode) {
105
107
  if (!interactive) {
106
- console.log("Pick a loadout to preview: `loadout setup --mode stable` (recommended), `--mode power` (broader), or `--mode maximum` (full library, activate per project). Nothing installs until you add --yes.");
108
+ console.log([
109
+ "Pick a loadout to preview:",
110
+ " --mode stable 4 reviewed sources, 30 skills, all active (recommended)",
111
+ " --mode power 8 sources, 56 skills, all active",
112
+ " --mode maximum every reviewed source, downloaded but DISABLED; a project",
113
+ " activates what it needs with `loadout optimize --project .`",
114
+ " --mode custom --package <id>, one or more",
115
+ "",
116
+ "Nothing installs until you add --yes.",
117
+ ].join("\n"));
107
118
  return;
108
119
  }
109
120
  reader = createInterface({
@@ -112,9 +123,11 @@ export async function runSetup(options) {
112
123
  });
113
124
  const answer = (await reader.question([
114
125
  "Choose a loadout:",
115
- " [1] Stable — a small, reviewed set of everyday skills (recommended)",
116
- " [2] Power — a broader daily-driver set",
117
- " [3] Maximum — download the full reviewed library, activate per project later",
126
+ " [1] Stable — 4 reviewed sources, 30 skills, all active (recommended)",
127
+ " [2] Power — 8 sources, 56 skills, all active",
128
+ " [3] Maximum — download every reviewed source, but leave it DISABLED.",
129
+ " Nothing reaches your prompts until a project activates",
130
+ " it with `loadout optimize --project .`. Costs disk, not context.",
118
131
  " [4] Custom — pick exact package ids",
119
132
  "> ",
120
133
  ].join("\n"))).trim();
@@ -213,7 +226,25 @@ export async function runWizard() {
213
226
  console.log("\nPreview first — nothing changes until you approve, and every change is snapshotted for rollback.\n");
214
227
  await runSetup({ package: [] });
215
228
  }
216
- export const LOADOUT_VERSION = "0.5.9";
229
+ const _require = createRequire(import.meta.url);
230
+ function findPackageJson() {
231
+ let dir = dirname(fileURLToPath(import.meta.url));
232
+ for (let i = 0; i < 5; i++) {
233
+ const candidate = join(dir, "package.json");
234
+ try {
235
+ const pkg = _require(candidate);
236
+ if (pkg.name === "loadout-ai")
237
+ return candidate;
238
+ }
239
+ catch {
240
+ // not found, keep walking
241
+ }
242
+ dir = dirname(dir);
243
+ }
244
+ // ponytail: fallback for dist layout — 3 levels up from dist/src/commands/
245
+ return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "package.json");
246
+ }
247
+ export const LOADOUT_VERSION = _require(findPackageJson()).version;
217
248
  export function durableSchedulerLauncher() {
218
249
  return [
219
250
  join(dirname(process.execPath), process.platform === "win32" ? "npx.cmd" : "npx"),
@@ -1,6 +1,6 @@
1
1
  import { dirname, join } from "node:path";
2
- import { planSkillInstall } from "./skills.js";
3
- import { formatTerminalTable } from "./terminal.js";
2
+ import { planSkillInstall } from "../catalog/skills.js";
3
+ import { formatTerminalTable } from "../reporting/terminal.js";
4
4
  /**
5
5
  * Rules shared by the CLI, planner, and documentation. A capability only
6
6
  * changes state when it is native or has this explicitly tested adaptation.
@@ -136,15 +136,35 @@ export async function inspectAgent(agent) {
136
136
  export async function inspectAgents(agents) {
137
137
  return Promise.all(agents.map(inspectAgent));
138
138
  }
139
- export function formatAgentInventory(inventory) {
139
+ /**
140
+ * One agent's inventory. The default is a two-line summary: what is installed
141
+ * and which component kinds the adapter supports. Adapter internals — the
142
+ * per-kind compatibility notes and the paths that do not exist yet — are only
143
+ * useful when diagnosing, so they stay behind `details`.
144
+ */
145
+ export function formatAgentInventory(inventory, options = {}) {
140
146
  const lines = [
141
147
  `${inventory.agent.installed ? "✓" : "○"} ${inventory.agent.displayName}`,
142
148
  ];
143
- for (const component of inventory.components) {
144
- const state = component.scanned
145
- ? `${component.directoryExists ? `${component.entries.length} filesystem item(s)` : "not created"} — ${component.directory}`
146
- : (component.note ?? "not inspected");
147
- lines.push(` ${component.type}: ${component.compatibility}; ${state}`);
149
+ if (options.details) {
150
+ for (const component of inventory.components) {
151
+ const state = component.scanned
152
+ ? `${component.directoryExists ? `${component.entries.length} filesystem item(s)` : "not created"} — ${component.directory}`
153
+ : (component.note ?? "not inspected");
154
+ lines.push(` ${component.type}: ${component.compatibility}; ${state}`);
155
+ }
156
+ }
157
+ else {
158
+ const items = inventory.components
159
+ .filter((component) => component.scanned && component.directoryExists)
160
+ .reduce((total, component) => total + component.entries.length, 0);
161
+ const supported = inventory.components
162
+ .filter((component) => component.compatibility !== "unsupported")
163
+ .map((component) => component.type);
164
+ const skills = inventory.components.find((component) => component.type === "skill");
165
+ if (skills?.directory)
166
+ lines.push(` ${skills.directory}`);
167
+ lines.push(` ${items} items | supports: ${supported.join(", ")}`);
148
168
  }
149
169
  for (const warning of inventory.warnings)
150
170
  lines.push(` ! ${warning}`);
@@ -2,7 +2,7 @@ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
2
2
  import { basename, dirname, join, resolve } from "node:path";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import { loadoutHome, userHome } from "./paths.js";
5
- import { runMutationTransaction } from "./transaction.js";
5
+ import { runMutationTransaction } from "../install/transaction.js";
6
6
  function safeName(name) {
7
7
  if (!/^[A-Za-z0-9._-]{1,128}$/.test(name))
8
8
  throw new Error("MCP server name may contain only letters, numbers, ., _, and -");
@@ -1,16 +1,16 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { readFile, readdir } from "node:fs/promises";
3
3
  import { buildAgentHealthScore, } from "./agent-health-score.js";
4
- import { DEFAULT_ACTIVE_SKILL_LIMIT } from "./active-limit.js";
5
- import { managedFileReadPath } from "./active-set.js";
4
+ import { DEFAULT_ACTIVE_SKILL_LIMIT } from "../workspace/active-limit.js";
5
+ import { managedFileReadPath } from "../workspace/active-set.js";
6
6
  import { adapterCapabilities } from "./adapters.js";
7
- import { loadEffectiveCatalog } from "./catalog.js";
7
+ import { loadEffectiveCatalog } from "../catalog/catalog.js";
8
8
  import { detectAgents } from "./paths.js";
9
- import { readLocalOutcomes } from "./outcomes.js";
10
- import { readSnapshot } from "./snapshot.js";
11
- import { scanInstalledSkills } from "./skill-inventory.js";
12
- import { readInstallState } from "./state.js";
13
- import { transactionRoot } from "./transaction.js";
9
+ import { readLocalOutcomes } from "../workspace/outcomes.js";
10
+ import { readSnapshot } from "../install/snapshot.js";
11
+ import { scanInstalledSkills } from "../catalog/skill-inventory.js";
12
+ import { readInstallState } from "../workspace/state.js";
13
+ import { transactionRoot } from "../install/transaction.js";
14
14
  function freshness(pkg, asOf) {
15
15
  const observed = pkg?.pushedAt ?? pkg?.lastUpdatedAt;
16
16
  if (!observed)
@@ -2,10 +2,10 @@ import { execFile } from "node:child_process";
2
2
  import { access, readFile, readdir, rm } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  import { promisify } from "node:util";
5
- import { writeFileAtomically } from "./atomic-file.js";
5
+ import { writeFileAtomically } from "../install/atomic-file.js";
6
6
  import { detectAgents, loadoutHome, userHome } from "./paths.js";
7
7
  import { parseRuntimeToolRecipe, renderRuntimeRecipeValue, resolveRuntimeRecipePath, } from "./runtime-tool-recipe.js";
8
- import { createSnapshot, readSnapshot, recordSnapshotPostMutationState, restoreSnapshot, } from "./snapshot.js";
8
+ import { createSnapshot, readSnapshot, recordSnapshotPostMutationState, restoreSnapshot, } from "../install/snapshot.js";
9
9
  const execFileAsync = promisify(execFile);
10
10
  function deepFreeze(value) {
11
11
  if (value && typeof value === "object" && !Object.isFrozen(value)) {
@@ -1,10 +1,10 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { resolve } from "node:path";
4
- import { formatSchemaError, loadoutLockfileSchema } from "../shared/schemas.js";
5
- import { readManifest } from "./manifest.js";
6
- import { readInstallState } from "./state.js";
7
- import { managedFileReadPath } from "./active-set.js";
4
+ import { formatSchemaError, loadoutLockfileSchema, } from "../../shared/schemas.js";
5
+ import { readManifest } from "../workspace/manifest.js";
6
+ import { readInstallState } from "../workspace/state.js";
7
+ import { managedFileReadPath } from "../workspace/active-set.js";
8
8
  export function parseLockfile(value) {
9
9
  const result = loadoutLockfileSchema.safeParse(value);
10
10
  if (!result.success)
@@ -2,12 +2,12 @@ import { existsSync } from "node:fs";
2
2
  import { mkdir, readFile, writeFile } from "node:fs/promises";
3
3
  import { dirname, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { catalogSchema, formatSchemaError } from "../shared/schemas.js";
6
- import { fetchGitHubMetadata } from "./github.js";
7
- import { loadoutHome } from "./paths.js";
8
- import { compareCatalogPackages, explainCatalogScore } from "./ranking.js";
5
+ import { catalogSchema, formatSchemaError } from "../../shared/schemas.js";
6
+ import { fetchGitHubMetadata, } from "../runtime/github.js";
7
+ import { loadoutHome } from "../agents/paths.js";
8
+ import { compareCatalogPackages, explainCatalogScore, } from "../discovery/ranking.js";
9
9
  import { resolveCatalogProfile } from "./profiles.js";
10
- import { recordCatalogObservations } from "./observations.js";
10
+ import { recordCatalogObservations } from "../discovery/observations.js";
11
11
  const cachedCatalogPath = () => join(loadoutHome(), "catalog.json");
12
12
  function bundledCatalogPath() {
13
13
  const moduleDirectory = dirname(fileURLToPath(import.meta.url));
@@ -218,6 +218,19 @@ export async function refreshCatalog(packages, options = {}) {
218
218
  observationFailures: observations.failures,
219
219
  };
220
220
  }
221
+ /** Merge a human-approved proposal into the bundled catalog. */
222
+ export async function promoteCatalogCandidate(proposal) {
223
+ const catalogPath = bundledCatalogPath();
224
+ const current = JSON.parse(await readFile(catalogPath, "utf8"));
225
+ if (current.some((pkg) => pkg.id === proposal.id))
226
+ throw new Error(`Catalog already contains id '${proposal.id}'`);
227
+ if (current.some((pkg) => pkg.repository.toLowerCase() === proposal.repository.toLowerCase()))
228
+ throw new Error(`Catalog already contains repository '${proposal.repository}'`);
229
+ const merged = [...current, proposal].sort((a, b) => a.id.localeCompare(b.id));
230
+ validateCatalog(merged, { requireEvidence: true });
231
+ await writeFile(catalogPath, `${JSON.stringify(merged, null, 2)}\n`);
232
+ return { catalogPath, totalRecords: merged.length };
233
+ }
221
234
  export function rankCatalog(packages) {
222
235
  return [...packages].sort(compareCatalogPackages);
223
236
  }
@@ -1,7 +1,7 @@
1
1
  import { lstat, readdir } from "node:fs/promises";
2
2
  import { isAbsolute, join, relative, resolve, sep } from "node:path";
3
- import { buildSkillPlan } from "./install.js";
4
- import { adapterCapabilities, agentComponentDirectory } from "./adapters.js";
3
+ import { buildSkillPlan } from "../install/install.js";
4
+ import { adapterCapabilities, agentComponentDirectory, } from "../agents/adapters.js";
5
5
  const RESOURCE_DIRECTORIES = new Map([
6
6
  ["rules", "rule"],
7
7
  ["commands", "command"],
@@ -1,5 +1,5 @@
1
- import { ADAPTER_CAPABILITIES, agentComponentDirectory } from "./adapters.js";
2
- import { AGENT_DEFINITIONS, agentSkillsDirectory } from "./paths.js";
1
+ import { ADAPTER_CAPABILITIES, agentComponentDirectory, } from "../agents/adapters.js";
2
+ import { AGENT_DEFINITIONS, agentSkillsDirectory } from "../agents/paths.js";
3
3
  const PLATFORM_BY_RUNNER = {
4
4
  "ubuntu-latest": "linux",
5
5
  "macos-latest": "macos",
@@ -1,4 +1,4 @@
1
- import { compareCatalogPackages } from "./ranking.js";
1
+ import { compareCatalogPackages } from "../discovery/ranking.js";
2
2
  /**
3
3
  * Stable is Loadout's bounded policy-selected daily driver: broad enough to improve normal
4
4
  * engineering work immediately, but bounded at skill granularity and restricted
@@ -247,7 +247,7 @@ export function resolveCatalogProfile(packages, selection, families = CATALOG_CO
247
247
  warnings.push(`Custom selection retains the soft overlap in ${family.label}. Review ${names} before installation.`);
248
248
  }
249
249
  else {
250
- warnings.push(`${selection.mode === "power" ? "Power Boost" : "Maximum Library"} retains the soft overlap in ${family.label}. Loadout policy orders ${primary.displayName} first; review each package before installation.`);
250
+ warnings.push(`${selection.mode === "power" ? "Power" : "Maximum"} retains the soft overlap in ${family.label}. Loadout policy orders ${primary.displayName} first; review each package before installation.`);
251
251
  }
252
252
  }
253
253
  return {
@@ -2,10 +2,10 @@ import { createHash } from "node:crypto";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { dirname, join, relative, resolve, sep } from "node:path";
4
4
  import { loadEffectiveCatalog } from "./catalog.js";
5
- import { ensureDirectory, loadoutHome } from "./paths.js";
5
+ import { ensureDirectory, loadoutHome } from "../agents/paths.js";
6
6
  import { discoverSkillDirectories } from "./skills.js";
7
- import { fetchRepositorySnapshot, } from "./source.js";
8
- import { writeFileAtomically } from "./atomic-file.js";
7
+ import { fetchRepositorySnapshot, } from "../install/source.js";
8
+ import { writeFileAtomically } from "../install/atomic-file.js";
9
9
  const catalogSkillIndexPath = () => join(loadoutHome(), "provenance", "catalog-skills.json");
10
10
  function immutableCatalogDigest(catalog) {
11
11
  const evidence = catalog