loadout-ai 0.6.0 → 0.8.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 (133) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +129 -26
  3. package/catalog/discovered.json +28469 -24524
  4. package/dist/src/cli.js +7 -2
  5. package/dist/src/commands/agents.js +6 -23
  6. package/dist/src/commands/catalog.js +186 -102
  7. package/dist/src/commands/health.js +6 -6
  8. package/dist/src/commands/inventory.js +19 -120
  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 +20 -9
  14. package/dist/src/core/{adapters.js → agents/adapters.js} +2 -2
  15. package/dist/src/core/{agent-inspection.js → agents/agent-inspection.js} +48 -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} +5 -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} +59 -11
  26. package/dist/src/core/{safety.js → catalog/safety.js} +36 -7
  27. package/dist/src/core/{skill-compare.js → catalog/skill-compare.js} +1 -1
  28. package/dist/src/core/{skill-inventory.js → catalog/skill-inventory.js} +3 -3
  29. package/dist/src/core/{skills.js → catalog/skills.js} +1 -1
  30. package/dist/src/core/{candidate-intelligence.js → discovery/candidate-intelligence.js} +6 -6
  31. package/dist/src/core/{discovery-connector.js → discovery/discovery-connector.js} +2 -2
  32. package/dist/src/core/{evaluate.js → discovery/evaluate.js} +2 -2
  33. package/dist/src/core/{mcp-registry-discovery.js → discovery/mcp-registry-discovery.js} +1 -1
  34. package/dist/src/core/{observations.js → discovery/observations.js} +3 -3
  35. package/dist/src/core/{review-queue.js → discovery/review-queue.js} +2 -2
  36. package/dist/src/core/{skills-sh-discovery.js → discovery/skills-sh-discovery.js} +1 -1
  37. package/dist/src/core/{adopt.js → install/adopt.js} +3 -3
  38. package/dist/src/core/{catalog-install.js → install/catalog-install.js} +11 -11
  39. package/dist/src/core/{file-lock.js → install/file-lock.js} +1 -1
  40. package/dist/src/core/{install.js → install/install.js} +5 -5
  41. package/dist/src/core/{package.js → install/package.js} +3 -3
  42. package/dist/src/core/{reconcile.js → install/reconcile.js} +5 -5
  43. package/dist/src/core/{remove.js → install/remove.js} +4 -4
  44. package/dist/src/core/{snapshot.js → install/snapshot.js} +1 -1
  45. package/dist/src/core/{source.js → install/source.js} +22 -8
  46. package/dist/src/core/{sync.js → install/sync.js} +9 -9
  47. package/dist/src/core/{transaction.js → install/transaction.js} +1 -1
  48. package/dist/src/core/{uninstall.js → install/uninstall.js} +4 -4
  49. package/dist/src/core/{update.js → install/update.js} +4 -4
  50. package/dist/src/core/{cli-guide.js → reporting/cli-guide.js} +29 -45
  51. package/dist/src/core/reporting/completion.js +90 -0
  52. package/dist/src/core/{doctor.js → reporting/doctor.js} +5 -10
  53. package/dist/src/core/{freshness-alerts.js → reporting/freshness-alerts.js} +5 -5
  54. package/dist/src/core/{health.js → reporting/health.js} +6 -6
  55. package/dist/src/core/{loadout-card.js → reporting/loadout-card.js} +1 -1
  56. package/dist/src/core/{readme-claims.js → reporting/readme-claims.js} +1 -1
  57. package/dist/src/core/{readme-facts.js → reporting/readme-facts.js} +2 -2
  58. package/dist/src/core/{share-report.js → reporting/share-report.js} +3 -3
  59. package/dist/src/core/{upgrade.js → reporting/upgrade.js} +7 -7
  60. package/dist/src/core/routing/first-party-skills.js +118 -0
  61. package/dist/src/core/routing/handoff.js +314 -0
  62. package/dist/src/core/{model-config.js → routing/model-config.js} +4 -4
  63. package/dist/src/core/routing/policy.js +147 -0
  64. package/dist/src/core/{route.js → routing/route.js} +202 -39
  65. package/dist/src/core/{api.js → runtime/api.js} +4 -4
  66. package/dist/src/core/{canary.js → runtime/canary.js} +1 -1
  67. package/dist/src/core/{github.js → runtime/github.js} +1 -1
  68. package/dist/src/core/{mcp-recipes.js → runtime/mcp-recipes.js} +1 -1
  69. package/dist/src/core/{mcp.js → runtime/mcp.js} +2 -2
  70. package/dist/src/core/{scheduler.js → runtime/scheduler.js} +4 -4
  71. package/dist/src/core/{active-policy.js → workspace/active-policy.js} +2 -2
  72. package/dist/src/core/{active-set.js → workspace/active-set.js} +2 -2
  73. package/dist/src/core/{improve.js → workspace/improve.js} +3 -3
  74. package/dist/src/core/{manifest.js → workspace/manifest.js} +2 -2
  75. package/dist/src/core/{outcomes.js → workspace/outcomes.js} +3 -3
  76. package/dist/src/core/{portable.js → workspace/portable.js} +3 -3
  77. package/dist/src/core/{profile-state.js → workspace/profile-state.js} +1 -1
  78. package/dist/src/core/{recommend.js → workspace/recommend.js} +1 -1
  79. package/dist/src/core/{state.js → workspace/state.js} +3 -3
  80. package/docs/CANDIDATE_INTELLIGENCE.md +9 -2
  81. package/docs/CATALOG.md +1 -1
  82. package/docs/CREDENTIAL_AND_UPDATE_POLICY.md +1 -1
  83. package/docs/DISCOVERED.md +250 -248
  84. package/docs/FEATURE_TEST_MATRIX.md +7 -260
  85. package/docs/GITHUB_AUTHORIZATION.md +5 -0
  86. package/docs/PROVENANCE_AND_COMPARISON.md +1 -1
  87. package/docs/RELEASE_REVIEW.md +0 -1
  88. package/docs/evidence/readme-claims.json +22 -19
  89. package/package.json +7 -4
  90. package/skills/loadout-curator/SKILL.md +105 -0
  91. package/skills/loadout-router/SKILL.md +85 -0
  92. package/MASTER_PLAN.md +0 -2207
  93. package/dist/src/core/completion.js +0 -143
  94. package/dist/src/core/handoff.js +0 -161
  95. package/docs/ACTIVE_SET.md +0 -53
  96. package/docs/COMPATIBILITY_POLICY.md +0 -22
  97. package/docs/CONVERSION_AND_SANDBOX.md +0 -27
  98. package/docs/EVALUATION_PROTOCOL_V1.md +0 -300
  99. package/docs/HEAD_TO_HEAD_EVALUATION.md +0 -79
  100. package/docs/PROVIDER_CONFIGURATION.md +0 -45
  101. package/docs/README_RESEARCH.md +0 -36
  102. package/docs/REPOSITORY_STABILIZATION.md +0 -190
  103. package/docs/SAFE_UPDATE_DEMO.md +0 -25
  104. package/docs/SCHEMA_DECISIONS.md +0 -25
  105. package/docs/SUBMISSION_COPY.md +0 -90
  106. package/docs/TEAM_POLICY.md +0 -18
  107. package/docs/superpowers/plans/2026-07-19-relatable-readme-hero.md +0 -283
  108. package/docs/superpowers/plans/2026-07-20-loadout-readme-explainer.md +0 -116
  109. package/docs/superpowers/plans/2026-07-20-project-activation-safety.md +0 -469
  110. package/docs/superpowers/specs/2026-07-19-relatable-readme-hero-design.md +0 -80
  111. package/docs/superpowers/specs/2026-07-20-loadout-readme-explainer-design.md +0 -55
  112. package/docs/superpowers/specs/2026-07-20-project-activation-safety-design.md +0 -228
  113. /package/dist/src/core/{agent-health-score.js → agents/agent-health-score.js} +0 -0
  114. /package/dist/src/core/{agent-versions.js → agents/agent-versions.js} +0 -0
  115. /package/dist/src/core/{conversion.js → agents/conversion.js} +0 -0
  116. /package/dist/src/core/{paths.js → agents/paths.js} +0 -0
  117. /package/dist/src/core/{runtime-tool-recipe.js → agents/runtime-tool-recipe.js} +0 -0
  118. /package/dist/src/core/{catalog-coverage.js → catalog/catalog-coverage.js} +0 -0
  119. /package/dist/src/core/{skill-security.js → catalog/skill-security.js} +0 -0
  120. /package/dist/src/core/{community.js → discovery/community.js} +0 -0
  121. /package/dist/src/core/{github-discovery.js → discovery/github-discovery.js} +0 -0
  122. /package/dist/src/core/{private-discovery.js → discovery/private-discovery.js} +0 -0
  123. /package/dist/src/core/{ranking.js → discovery/ranking.js} +0 -0
  124. /package/dist/src/core/{atomic-file.js → install/atomic-file.js} +0 -0
  125. /package/dist/src/core/{diff.js → install/diff.js} +0 -0
  126. /package/dist/src/core/{update-watch.js → install/update-watch.js} +0 -0
  127. /package/dist/src/core/{loadout-badge.js → reporting/loadout-badge.js} +0 -0
  128. /package/dist/src/core/{terminal.js → reporting/terminal.js} +0 -0
  129. /package/dist/src/core/{access.js → routing/access.js} +0 -0
  130. /package/dist/src/core/{credentials.js → routing/credentials.js} +0 -0
  131. /package/dist/src/core/{sandbox.js → runtime/sandbox.js} +0 -0
  132. /package/dist/src/core/{active-limit.js → workspace/active-limit.js} +0 -0
  133. /package/dist/src/core/{target-occupancy.js → workspace/target-occupancy.js} +0 -0
@@ -1,42 +1,41 @@
1
1
  /** Plain-language entry points for people using the CLI, not maintaining it. */
2
2
  export const BEGINNER_GUIDE = `
3
- START HERE
3
+ Loadout — manage the skills, tools, and MCP servers your AI agents use.
4
4
 
5
- 1. See what Loadout currently manages
6
- loadout library
7
- loadout scan
8
- loadout reconcile
5
+ FIRST TIME
9
6
 
10
- 2. Preview the recommended everyday setup
11
- loadout setup --mode stable
12
- loadout profiles
7
+ loadout doctor which agents you have, and whether they are healthy
8
+ loadout setup --mode stable preview 30 reviewed skills (nothing changes)
9
+ loadout setup --mode stable --yes install them
13
10
 
14
- 3. Find additions that fit the project in this folder
15
- loadout recommend --project .
16
- loadout optimize --project .
11
+ IN A PROJECT
17
12
 
18
- 4. Check for safer updates and new discoveries
19
- loadout health
20
- loadout alerts
21
- loadout candidate list --limit 10
13
+ loadout recommend --project . what this codebase actually needs
14
+ loadout optimize --project . propose an active set for it
15
+ loadout status health grade and what is managed
22
16
 
23
- 5. See reviewed MCP integrations and their credential needs
24
- loadout mcp-recipe
25
- loadout mcp-recipe --credential-free
26
- loadout mcp-recipe playwright --agent claude-code
17
+ WHILE YOU WORK
27
18
 
28
- If you decide to install something, Loadout shows a preview first and creates a
29
- snapshot before changing managed files. Recover with: loadout rollback
19
+ loadout route your model policy: hard / normal / cheap
20
+ loadout route <describe a task> which of those it looks like, and what to use
21
+ loadout handoff codex "..." give a task to your other agent
30
22
 
31
- Reconcile is also read-only unless you add --yes. Exact matches can be managed
32
- without rewriting them; outdated replacements remain a separate explicit choice.
23
+ USE IT INSIDE YOUR AGENT
33
24
 
34
- Setup reconciles the complete selected profile. To add one catalog package without
35
- retiring the current managed profile, use:
36
- loadout install --mode custom --package <id>
25
+ loadout skills install loadout-router --yes
37
26
 
38
- Nothing above changes your agents. For the full maintainer/tooling surface, run:
39
- loadout advanced
27
+ Then just ask your agent "which model should I use for this?" instead of
28
+ coming back here.
29
+
30
+ KEEPING CURRENT
31
+
32
+ loadout health safer updates and local drift
33
+ loadout alerts archived, stale, or changed sources
34
+
35
+ Every command that writes previews first and snapshots before it changes
36
+ anything. Undo with: loadout rollback
37
+
38
+ More commands: loadout advanced
40
39
  `.trim();
41
40
  export const ADVANCED_GUIDE = [
42
41
  "ADVANCED COMMANDS",
@@ -44,8 +43,8 @@ export const ADVANCED_GUIDE = [
44
43
  "Hidden from the first screen so daily use stays focused.",
45
44
  "",
46
45
  "Discovery: candidate, discover, review-queue, evaluate, skill-audit",
47
- "Sharing: init, lock, export, import, share, card, badge",
48
- "Integrations: mcp-recipe, mcp-config, credentials, models, handoff, convert",
46
+ "Sharing: init, lock, export, import, share, card",
47
+ "Integrations: mcp-recipe, mcp-config, credentials, models, handoff, skills, convert",
49
48
  "Lifecycle: plan, adopt, uninstall, profiles, autopilot, tool, watch",
50
49
  "",
51
50
  "`loadout <command> --help` for options. Mutations preview first or require --yes.",
@@ -64,29 +63,18 @@ export const HIDDEN_FROM_FIRST_SCREEN = new Set([
64
63
  "import",
65
64
  "audit",
66
65
  "create",
67
- "pack",
68
- "publish",
69
66
  "add",
70
67
  "unadd",
71
68
  "share",
72
69
  "card",
73
- "badge",
74
- "compare-loadouts",
75
70
  "search",
76
71
  // analytics / evidence / discovery
77
72
  "report",
78
- "outcomes",
79
- "outcome",
80
- "improve",
81
- "improve-feedback",
82
- "compare",
83
73
  "versions",
84
74
  "skill-audit",
85
- "capabilities",
86
75
  "candidate",
87
76
  "discover",
88
77
  "review-queue",
89
- "review",
90
78
  "inspect",
91
79
  "propose",
92
80
  "evaluate",
@@ -97,11 +85,7 @@ export const HIDDEN_FROM_FIRST_SCREEN = new Set([
97
85
  "completion",
98
86
  "mcp-recipe",
99
87
  "mcp-config",
100
- "codex-mcp-config",
101
- "sandbox-run",
102
88
  "convert",
103
- "canary",
104
- "serve",
105
89
  // lifecycle extras
106
90
  "plan",
107
91
  "adopt",
@@ -0,0 +1,90 @@
1
+ let registry = [];
2
+ /** Called once at startup with the assembled program. */
3
+ export function registerCompletionCommands(tree) {
4
+ registry = tree
5
+ .filter((entry) => entry.name && entry.name !== "help")
6
+ .sort((a, b) => a.name.localeCompare(b.name));
7
+ }
8
+ export function completionCommands() {
9
+ return registry.map((entry) => entry.name);
10
+ }
11
+ export function completionSubcommands(name) {
12
+ return registry.find((entry) => entry.name === name)?.subcommands ?? [];
13
+ }
14
+ export function renderShellCompletion(shell) {
15
+ const words = completionCommands().join(" ");
16
+ switch (shell) {
17
+ case "bash":
18
+ return `# Loadout command completion
19
+ _loadout() {
20
+ local current="\${COMP_WORDS[COMP_CWORD]}"
21
+ local commands="${words}"
22
+ if [[ COMP_CWORD -eq 1 ]]; then
23
+ COMPREPLY=( $(compgen -W "$commands" -- "$current") )
24
+ elif [[ COMP_CWORD -eq 2 && "\${COMP_WORDS[1]}" == "models" ]]; then
25
+ COMPREPLY=( $(compgen -W "${completionSubcommands("models").join(" ")}" -- "$current") )
26
+ elif [[ COMP_CWORD -eq 2 && "\${COMP_WORDS[1]}" == "credentials" ]]; then
27
+ COMPREPLY=( $(compgen -W "${completionSubcommands("credentials").join(" ")}" -- "$current") )
28
+ elif [[ COMP_CWORD -eq 2 && "\${COMP_WORDS[1]}" == "candidate" ]]; then
29
+ COMPREPLY=( $(compgen -W "${completionSubcommands("candidate").join(" ")}" -- "$current") )
30
+ fi
31
+ }
32
+ complete -F _loadout loadout
33
+ `;
34
+ case "zsh":
35
+ return `#compdef loadout
36
+ typeset -a commands model_commands credential_commands candidate_commands
37
+ commands=(${completionCommands()
38
+ .map((command) => `'${command}'`)
39
+ .join(" ")})
40
+ model_commands=(${completionSubcommands("models")
41
+ .map((command) => `'${command}'`)
42
+ .join(" ")})
43
+ credential_commands=(${completionSubcommands("credentials")
44
+ .map((command) => `'${command}'`)
45
+ .join(" ")})
46
+ candidate_commands=(${completionSubcommands("candidate")
47
+ .map((command) => `'${command}'`)
48
+ .join(" ")})
49
+ if (( CURRENT == 2 )); then
50
+ _describe 'command' commands
51
+ elif (( CURRENT == 3 )) && [[ $words[2] == models ]]; then
52
+ _describe 'models command' model_commands
53
+ elif (( CURRENT == 3 )) && [[ $words[2] == credentials ]]; then
54
+ _describe 'credentials command' credential_commands
55
+ elif (( CURRENT == 3 )) && [[ $words[2] == candidate ]]; then
56
+ _describe 'candidate command' candidate_commands
57
+ fi
58
+ `;
59
+ case "fish":
60
+ return [
61
+ ...completionCommands().map((command) => `complete -c loadout -f -n '__fish_use_subcommand' -a ${command}`),
62
+ ...completionSubcommands("models").map((command) => `complete -c loadout -f -n '__fish_seen_subcommand_from models' -a ${command}`),
63
+ ...completionSubcommands("credentials").map((command) => `complete -c loadout -f -n '__fish_seen_subcommand_from credentials' -a ${command}`),
64
+ ...completionSubcommands("candidate").map((command) => `complete -c loadout -f -n '__fish_seen_subcommand_from candidate' -a ${command}`),
65
+ ]
66
+ .join("\n")
67
+ .concat("\n");
68
+ case "powershell":
69
+ return `Register-ArgumentCompleter -Native -CommandName loadout -ScriptBlock {
70
+ param($wordToComplete, $commandAst, $cursorPosition)
71
+ $elements = @($commandAst.CommandElements | ForEach-Object { $_.Extent.Text })
72
+ $candidates = if ($elements.Count -ge 2 -and $elements[1] -eq 'models') { @(${completionSubcommands("models")
73
+ .map((command) => `'${command}'`)
74
+ .join(", ")}) } elseif ($elements.Count -ge 2 -and $elements[1] -eq 'credentials') { @(${completionSubcommands("credentials")
75
+ .map((command) => `'${command}'`)
76
+ .join(", ")}) } elseif ($elements.Count -ge 2 -and $elements[1] -eq 'candidate') { @(${completionSubcommands("candidate")
77
+ .map((command) => `'${command}'`)
78
+ .join(", ")}) } else { @(${completionCommands()
79
+ .map((command) => `'${command}'`)
80
+ .join(", ")}) }
81
+ $candidates | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
82
+ }
83
+ `;
84
+ }
85
+ }
86
+ export function parseCompletionShell(value) {
87
+ if (["bash", "zsh", "fish", "powershell"].includes(value))
88
+ return value;
89
+ throw new Error("Supported shells: bash, zsh, fish, powershell");
90
+ }
@@ -1,7 +1,7 @@
1
1
  import { access, constants } from "node:fs/promises";
2
2
  import { dirname } from "node:path";
3
- import { detectAgents, directoryExists, loadoutHome, userHome, } from "./paths.js";
4
- import { inspectAgents } from "./agent-inspection.js";
3
+ import { detectAgents, directoryExists, loadoutHome, userHome, } from "../agents/paths.js";
4
+ import { countComponentUnits, inspectAgents, } from "../agents/agent-inspection.js";
5
5
  async function writable(path) {
6
6
  // A not-yet-created directory is healthy when any existing ancestor is
7
7
  // writable; this handles first-run paths such as ~/.agents/skills.
@@ -65,10 +65,7 @@ export async function runDoctor() {
65
65
  export function formatDoctorReport(report, options = {}) {
66
66
  const installed = report.agents.filter((a) => a.agent.installed);
67
67
  const notInstalled = report.agents.filter((a) => !a.agent.installed);
68
- const allIssues = [
69
- ...report.issues,
70
- ...installed.flatMap((a) => a.issues),
71
- ];
68
+ const allIssues = [...report.issues, ...installed.flatMap((a) => a.issues)];
72
69
  const grade = allIssues.length === 0 && installed.length > 0
73
70
  ? "HEALTHY"
74
71
  : allIssues.length <= 2
@@ -86,13 +83,11 @@ export function formatDoctorReport(report, options = {}) {
86
83
  if (installed.length) {
87
84
  lines.push("DETECTED AGENTS");
88
85
  for (const entry of installed) {
89
- const skillCount = entry.inventory.components
90
- .filter((c) => c.scanned && c.directoryExists)
91
- .reduce((sum, c) => sum + c.entries.length, 0);
86
+ const skillCount = entry.inventory.components.reduce((sum, component) => sum + countComponentUnits(component), 0);
92
87
  const supported = entry.inventory.components
93
88
  .filter((c) => c.compatibility === "native" || c.compatibility === "adapted")
94
89
  .map((c) => c.type);
95
- lines.push(` ✓ ${entry.agent.displayName}`, ` ${entry.agent.skillsDirectory}`, ` ${skillCount} items | supports: ${supported.join(", ")}`);
90
+ lines.push(` ✓ ${entry.agent.displayName}`, ` ${entry.agent.skillsDirectory}`, ` ${skillCount} skills | supports: ${supported.join(", ")}`);
96
91
  if (entry.issues.length) {
97
92
  for (const issue of entry.issues)
98
93
  lines.push(` ⚠ ${issue}`);
@@ -1,11 +1,11 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { dirname, join } from "node:path";
4
- import { writeFileAtomically } from "./atomic-file.js";
5
- import { loadEffectiveCatalog } from "./catalog.js";
6
- import { ensureDirectory, loadoutHome } from "./paths.js";
7
- import { readInstallState } from "./state.js";
8
- import { buildUpdatePlan } from "./update.js";
4
+ import { writeFileAtomically } from "../install/atomic-file.js";
5
+ import { loadEffectiveCatalog } from "../catalog/catalog.js";
6
+ import { ensureDirectory, loadoutHome } from "../agents/paths.js";
7
+ import { readInstallState } from "../workspace/state.js";
8
+ import { buildUpdatePlan } from "../install/update.js";
9
9
  const decisionsPath = () => join(loadoutHome(), "alert-decisions.json");
10
10
  function alertId(packageId, kind, evidence) {
11
11
  return `${packageId}-${kind}-${createHash("sha256")
@@ -1,12 +1,12 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
- import { managedFileReadPath } from "./active-set.js";
5
- import { detectAgents } from "./paths.js";
6
- import { readInstallState } from "./state.js";
7
- import { buildUpdatePlan } from "./update.js";
8
- import { codexMcpServerFingerprint } from "./codex-mcp.js";
9
- import { listInstalledRuntimeToolSkillTargets, listInstalledRuntimeTools, } from "./runtime-tools.js";
4
+ import { managedFileReadPath } from "../workspace/active-set.js";
5
+ import { detectAgents } from "../agents/paths.js";
6
+ import { readInstallState } from "../workspace/state.js";
7
+ import { buildUpdatePlan } from "../install/update.js";
8
+ import { codexMcpServerFingerprint } from "../agents/codex-mcp.js";
9
+ import { listInstalledRuntimeToolSkillTargets, listInstalledRuntimeTools, } from "../agents/runtime-tools.js";
10
10
  async function drift(record, activations) {
11
11
  const changed = [];
12
12
  for (const file of record.files) {
@@ -1,4 +1,4 @@
1
- import { buildLocalAgentHealthScores } from "./health-score-evidence.js";
1
+ import { buildLocalAgentHealthScores } from "../agents/health-score-evidence.js";
2
2
  import { buildHealthReport, gradeHealth } from "./health.js";
3
3
  import { buildPrivacySafeReport, } from "./share-report.js";
4
4
  export async function buildLoadoutCard(options = {}) {
@@ -1,4 +1,4 @@
1
- import { formatSchemaError, readmeClaimManifestSchema, } from "../shared/schemas.js";
1
+ import { formatSchemaError, readmeClaimManifestSchema, } from "../../shared/schemas.js";
2
2
  export { readmeClaimManifestSchema };
3
3
  /** Parse the versioned index of evidence for material README statements. */
4
4
  export function parseReadmeClaimManifest(value) {
@@ -1,5 +1,5 @@
1
- import { supportedAdapterNames } from "./adapters.js";
2
- import { buildCatalogCoverage } from "./catalog-coverage.js";
1
+ import { supportedAdapterNames, } from "../agents/adapters.js";
2
+ import { buildCatalogCoverage } from "../catalog/catalog-coverage.js";
3
3
  function profileFacts(allowlist) {
4
4
  return {
5
5
  sources: Object.keys(allowlist).length,
@@ -1,7 +1,7 @@
1
1
  import { dirname } from "node:path";
2
- import { writeFileAtomically } from "./atomic-file.js";
3
- import { ensureDirectory } from "./paths.js";
4
- import { readInstallState } from "./state.js";
2
+ import { writeFileAtomically } from "../install/atomic-file.js";
3
+ import { ensureDirectory } from "../agents/paths.js";
4
+ import { readInstallState } from "../workspace/state.js";
5
5
  export function parsePrivacySafeLoadoutReport(value) {
6
6
  if (!value || typeof value !== "object" || Array.isArray(value))
7
7
  throw new Error("Privacy-safe Loadout report must be an object");
@@ -1,11 +1,11 @@
1
- import { buildAdapterCapabilityGaps, } from "./adapters.js";
2
- import { applyPreparedCatalogInstall, formatPreparedCatalogInstall, prepareCatalogInstall, } from "./catalog-install.js";
3
- import { DEFAULT_ACTIVE_SKILL_LIMIT } from "./active-limit.js";
4
- import { loadEffectiveCatalog } from "./catalog.js";
1
+ import { buildAdapterCapabilityGaps, } from "../agents/adapters.js";
2
+ import { applyPreparedCatalogInstall, formatPreparedCatalogInstall, prepareCatalogInstall, } from "../install/catalog-install.js";
3
+ import { DEFAULT_ACTIVE_SKILL_LIMIT } from "../workspace/active-limit.js";
4
+ import { loadEffectiveCatalog, } from "../catalog/catalog.js";
5
5
  import { buildHealthReport, formatHealthReport } from "./health.js";
6
- import { buildLocalAgentHealthScores } from "./health-score-evidence.js";
7
- import { formatRecommendations, personalizeRecommendations, recommendPackages, scanProject, } from "./recommend.js";
8
- import { readLocalOutcomes } from "./outcomes.js";
6
+ import { buildLocalAgentHealthScores } from "../agents/health-score-evidence.js";
7
+ import { formatRecommendations, personalizeRecommendations, recommendPackages, scanProject, } from "../workspace/recommend.js";
8
+ import { readLocalOutcomes, } from "../workspace/outcomes.js";
9
9
  /**
10
10
  * Builds the complete first-run preview without changing agent or Loadout state.
11
11
  * Network access is limited to the same immutable catalog preparation performed by
@@ -0,0 +1,118 @@
1
+ import { cp, rm, stat } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { detectAgents } from "../agents/paths.js";
5
+ export const FIRST_PARTY_SKILLS = [
6
+ {
7
+ id: "loadout-router",
8
+ displayName: "Loadout Router",
9
+ summary: "Lets an agent pick the right model tier for a task and hand work to another agent",
10
+ },
11
+ {
12
+ id: "loadout-curator",
13
+ displayName: "Loadout Curator",
14
+ summary: "Lets an agent decide which skills and MCP servers should be active for the current repository",
15
+ },
16
+ ];
17
+ /**
18
+ * Locate the bundled `skills/` directory. Resolution walks upward so it works
19
+ * both from `src/` under tsx and from `dist/src/` in the published package.
20
+ */
21
+ export async function bundledSkillsRoot() {
22
+ let dir = dirname(fileURLToPath(import.meta.url));
23
+ for (let depth = 0; depth < 5; depth += 1) {
24
+ const candidate = join(dir, "skills");
25
+ try {
26
+ if ((await stat(candidate)).isDirectory())
27
+ return candidate;
28
+ }
29
+ catch {
30
+ // Keep walking toward the package root.
31
+ }
32
+ dir = dirname(dir);
33
+ }
34
+ throw new Error("Bundled skills directory not found; this build may be incomplete.");
35
+ }
36
+ export function findFirstPartySkill(id) {
37
+ const skill = FIRST_PARTY_SKILLS.find((item) => item.id === id);
38
+ if (!skill)
39
+ throw new Error(`Unknown Loadout skill '${id}'. Available: ${FIRST_PARTY_SKILLS.map((s) => s.id).join(", ")}`);
40
+ return skill;
41
+ }
42
+ async function directoryExists(path) {
43
+ try {
44
+ return (await stat(path)).isDirectory();
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ }
50
+ /**
51
+ * Build a preview of where one first-party skill would be installed. Nothing is
52
+ * written; the caller decides whether to apply.
53
+ */
54
+ export async function planFirstPartySkill(id, options = {}) {
55
+ const skill = findFirstPartySkill(id);
56
+ const source = join(await bundledSkillsRoot(), skill.id);
57
+ if (!(await directoryExists(source)))
58
+ throw new Error(`Bundled skill '${skill.id}' is missing from this build.`);
59
+ const detected = options.detected ?? (await detectAgents());
60
+ const installed = detected.filter((agent) => agent.installed);
61
+ const requested = options.agents?.length
62
+ ? installed.filter((agent) => options.agents.includes(agent.id))
63
+ : installed;
64
+ const targets = [];
65
+ for (const agent of requested) {
66
+ const destination = join(agent.skillsDirectory, skill.id);
67
+ targets.push({
68
+ agent: agent.id,
69
+ displayName: agent.displayName,
70
+ destination,
71
+ replacing: await directoryExists(destination),
72
+ });
73
+ }
74
+ return { skill, source, targets };
75
+ }
76
+ export async function applyFirstPartySkill(plan) {
77
+ for (const target of plan.targets) {
78
+ // Replace wholesale so a reinstall cannot leave files from an older
79
+ // version of the skill behind.
80
+ if (target.replacing)
81
+ await rm(target.destination, { recursive: true });
82
+ await cp(plan.source, target.destination, { recursive: true });
83
+ }
84
+ }
85
+ export async function removeFirstPartySkill(plan) {
86
+ for (const target of plan.targets)
87
+ if (target.replacing)
88
+ await rm(target.destination, { recursive: true });
89
+ }
90
+ export function formatFirstPartySkillList(installedIds = new Set()) {
91
+ const lines = ["Skills that ship with Loadout:", ""];
92
+ for (const skill of FIRST_PARTY_SKILLS) {
93
+ const marker = installedIds.has(skill.id) ? "✓" : "○";
94
+ lines.push(` ${marker} ${skill.id} — ${skill.summary}`);
95
+ }
96
+ lines.push("", "Install with: loadout skills install <id> --yes", "These teach your agents to use Loadout from inside a conversation.");
97
+ return lines.join("\n");
98
+ }
99
+ export function formatFirstPartySkillPlan(plan) {
100
+ if (!plan.targets.length)
101
+ return `No installed agents detected, so '${plan.skill.id}' has nowhere to go. Install an agent first.`;
102
+ const lines = [`${plan.skill.displayName} (${plan.skill.id})`, ""];
103
+ for (const target of plan.targets)
104
+ lines.push(` ${target.replacing ? "replace" : "install"} → ${target.destination} [${target.displayName}]`);
105
+ return lines.join("\n");
106
+ }
107
+ /** Which first-party skills are already present for at least one agent. */
108
+ export async function installedFirstPartySkills(detected) {
109
+ const agents = (detected ?? (await detectAgents())).filter((agent) => agent.installed);
110
+ const found = new Set();
111
+ for (const skill of FIRST_PARTY_SKILLS)
112
+ for (const agent of agents)
113
+ if (await directoryExists(join(agent.skillsDirectory, skill.id))) {
114
+ found.add(skill.id);
115
+ break;
116
+ }
117
+ return found;
118
+ }