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.
- package/CHANGELOG.md +72 -0
- package/README.md +129 -26
- package/catalog/discovered.json +28469 -24524
- package/dist/src/cli.js +7 -2
- package/dist/src/commands/agents.js +6 -23
- package/dist/src/commands/catalog.js +186 -102
- package/dist/src/commands/health.js +6 -6
- package/dist/src/commands/inventory.js +19 -120
- package/dist/src/commands/lifecycle.js +13 -51
- package/dist/src/commands/mcp.js +10 -73
- package/dist/src/commands/setup.js +5 -5
- package/dist/src/commands/sharing.js +11 -108
- package/dist/src/commands/support.js +20 -9
- package/dist/src/core/{adapters.js → agents/adapters.js} +2 -2
- package/dist/src/core/{agent-inspection.js → agents/agent-inspection.js} +48 -6
- package/dist/src/core/{codex-mcp.js → agents/codex-mcp.js} +1 -1
- package/dist/src/core/{health-score-evidence.js → agents/health-score-evidence.js} +8 -8
- package/dist/src/core/{runtime-tools.js → agents/runtime-tools.js} +2 -2
- package/dist/src/core/{audit.js → catalog/audit.js} +4 -4
- package/dist/src/core/{catalog.js → catalog/catalog.js} +5 -5
- package/dist/src/core/{components.js → catalog/components.js} +2 -2
- package/dist/src/core/{conformance.js → catalog/conformance.js} +2 -2
- package/dist/src/core/{profiles.js → catalog/profiles.js} +2 -2
- package/dist/src/core/{provenance.js → catalog/provenance.js} +3 -3
- package/dist/src/core/{registry.js → catalog/registry.js} +59 -11
- package/dist/src/core/{safety.js → catalog/safety.js} +36 -7
- package/dist/src/core/{skill-compare.js → catalog/skill-compare.js} +1 -1
- package/dist/src/core/{skill-inventory.js → catalog/skill-inventory.js} +3 -3
- package/dist/src/core/{skills.js → catalog/skills.js} +1 -1
- package/dist/src/core/{candidate-intelligence.js → discovery/candidate-intelligence.js} +6 -6
- package/dist/src/core/{discovery-connector.js → discovery/discovery-connector.js} +2 -2
- package/dist/src/core/{evaluate.js → discovery/evaluate.js} +2 -2
- package/dist/src/core/{mcp-registry-discovery.js → discovery/mcp-registry-discovery.js} +1 -1
- package/dist/src/core/{observations.js → discovery/observations.js} +3 -3
- package/dist/src/core/{review-queue.js → discovery/review-queue.js} +2 -2
- package/dist/src/core/{skills-sh-discovery.js → discovery/skills-sh-discovery.js} +1 -1
- package/dist/src/core/{adopt.js → install/adopt.js} +3 -3
- package/dist/src/core/{catalog-install.js → install/catalog-install.js} +11 -11
- package/dist/src/core/{file-lock.js → install/file-lock.js} +1 -1
- package/dist/src/core/{install.js → install/install.js} +5 -5
- package/dist/src/core/{package.js → install/package.js} +3 -3
- package/dist/src/core/{reconcile.js → install/reconcile.js} +5 -5
- package/dist/src/core/{remove.js → install/remove.js} +4 -4
- package/dist/src/core/{snapshot.js → install/snapshot.js} +1 -1
- package/dist/src/core/{source.js → install/source.js} +22 -8
- package/dist/src/core/{sync.js → install/sync.js} +9 -9
- package/dist/src/core/{transaction.js → install/transaction.js} +1 -1
- package/dist/src/core/{uninstall.js → install/uninstall.js} +4 -4
- package/dist/src/core/{update.js → install/update.js} +4 -4
- package/dist/src/core/{cli-guide.js → reporting/cli-guide.js} +29 -45
- package/dist/src/core/reporting/completion.js +90 -0
- package/dist/src/core/{doctor.js → reporting/doctor.js} +5 -10
- package/dist/src/core/{freshness-alerts.js → reporting/freshness-alerts.js} +5 -5
- package/dist/src/core/{health.js → reporting/health.js} +6 -6
- package/dist/src/core/{loadout-card.js → reporting/loadout-card.js} +1 -1
- package/dist/src/core/{readme-claims.js → reporting/readme-claims.js} +1 -1
- package/dist/src/core/{readme-facts.js → reporting/readme-facts.js} +2 -2
- package/dist/src/core/{share-report.js → reporting/share-report.js} +3 -3
- package/dist/src/core/{upgrade.js → reporting/upgrade.js} +7 -7
- package/dist/src/core/routing/first-party-skills.js +118 -0
- package/dist/src/core/routing/handoff.js +314 -0
- package/dist/src/core/{model-config.js → routing/model-config.js} +4 -4
- package/dist/src/core/routing/policy.js +147 -0
- package/dist/src/core/{route.js → routing/route.js} +202 -39
- package/dist/src/core/{api.js → runtime/api.js} +4 -4
- package/dist/src/core/{canary.js → runtime/canary.js} +1 -1
- package/dist/src/core/{github.js → runtime/github.js} +1 -1
- package/dist/src/core/{mcp-recipes.js → runtime/mcp-recipes.js} +1 -1
- package/dist/src/core/{mcp.js → runtime/mcp.js} +2 -2
- package/dist/src/core/{scheduler.js → runtime/scheduler.js} +4 -4
- package/dist/src/core/{active-policy.js → workspace/active-policy.js} +2 -2
- package/dist/src/core/{active-set.js → workspace/active-set.js} +2 -2
- package/dist/src/core/{improve.js → workspace/improve.js} +3 -3
- package/dist/src/core/{manifest.js → workspace/manifest.js} +2 -2
- package/dist/src/core/{outcomes.js → workspace/outcomes.js} +3 -3
- package/dist/src/core/{portable.js → workspace/portable.js} +3 -3
- package/dist/src/core/{profile-state.js → workspace/profile-state.js} +1 -1
- package/dist/src/core/{recommend.js → workspace/recommend.js} +1 -1
- package/dist/src/core/{state.js → workspace/state.js} +3 -3
- package/docs/CANDIDATE_INTELLIGENCE.md +9 -2
- package/docs/CATALOG.md +1 -1
- package/docs/CREDENTIAL_AND_UPDATE_POLICY.md +1 -1
- package/docs/DISCOVERED.md +250 -248
- package/docs/FEATURE_TEST_MATRIX.md +7 -260
- package/docs/GITHUB_AUTHORIZATION.md +5 -0
- package/docs/PROVENANCE_AND_COMPARISON.md +1 -1
- package/docs/RELEASE_REVIEW.md +0 -1
- package/docs/evidence/readme-claims.json +22 -19
- package/package.json +7 -4
- package/skills/loadout-curator/SKILL.md +105 -0
- package/skills/loadout-router/SKILL.md +85 -0
- package/MASTER_PLAN.md +0 -2207
- package/dist/src/core/completion.js +0 -143
- package/dist/src/core/handoff.js +0 -161
- package/docs/ACTIVE_SET.md +0 -53
- package/docs/COMPATIBILITY_POLICY.md +0 -22
- package/docs/CONVERSION_AND_SANDBOX.md +0 -27
- package/docs/EVALUATION_PROTOCOL_V1.md +0 -300
- package/docs/HEAD_TO_HEAD_EVALUATION.md +0 -79
- package/docs/PROVIDER_CONFIGURATION.md +0 -45
- package/docs/README_RESEARCH.md +0 -36
- package/docs/REPOSITORY_STABILIZATION.md +0 -190
- package/docs/SAFE_UPDATE_DEMO.md +0 -25
- package/docs/SCHEMA_DECISIONS.md +0 -25
- package/docs/SUBMISSION_COPY.md +0 -90
- package/docs/TEAM_POLICY.md +0 -18
- package/docs/superpowers/plans/2026-07-19-relatable-readme-hero.md +0 -283
- package/docs/superpowers/plans/2026-07-20-loadout-readme-explainer.md +0 -116
- package/docs/superpowers/plans/2026-07-20-project-activation-safety.md +0 -469
- package/docs/superpowers/specs/2026-07-19-relatable-readme-hero-design.md +0 -80
- package/docs/superpowers/specs/2026-07-20-loadout-readme-explainer-design.md +0 -55
- package/docs/superpowers/specs/2026-07-20-project-activation-safety-design.md +0 -228
- /package/dist/src/core/{agent-health-score.js → agents/agent-health-score.js} +0 -0
- /package/dist/src/core/{agent-versions.js → agents/agent-versions.js} +0 -0
- /package/dist/src/core/{conversion.js → agents/conversion.js} +0 -0
- /package/dist/src/core/{paths.js → agents/paths.js} +0 -0
- /package/dist/src/core/{runtime-tool-recipe.js → agents/runtime-tool-recipe.js} +0 -0
- /package/dist/src/core/{catalog-coverage.js → catalog/catalog-coverage.js} +0 -0
- /package/dist/src/core/{skill-security.js → catalog/skill-security.js} +0 -0
- /package/dist/src/core/{community.js → discovery/community.js} +0 -0
- /package/dist/src/core/{github-discovery.js → discovery/github-discovery.js} +0 -0
- /package/dist/src/core/{private-discovery.js → discovery/private-discovery.js} +0 -0
- /package/dist/src/core/{ranking.js → discovery/ranking.js} +0 -0
- /package/dist/src/core/{atomic-file.js → install/atomic-file.js} +0 -0
- /package/dist/src/core/{diff.js → install/diff.js} +0 -0
- /package/dist/src/core/{update-watch.js → install/update-watch.js} +0 -0
- /package/dist/src/core/{loadout-badge.js → reporting/loadout-badge.js} +0 -0
- /package/dist/src/core/{terminal.js → reporting/terminal.js} +0 -0
- /package/dist/src/core/{access.js → routing/access.js} +0 -0
- /package/dist/src/core/{credentials.js → routing/credentials.js} +0 -0
- /package/dist/src/core/{sandbox.js → runtime/sandbox.js} +0 -0
- /package/dist/src/core/{active-limit.js → workspace/active-limit.js} +0 -0
- /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
|
-
|
|
3
|
+
Loadout — manage the skills, tools, and MCP servers your AI agents use.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
loadout library
|
|
7
|
-
loadout scan
|
|
8
|
-
loadout reconcile
|
|
5
|
+
FIRST TIME
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
15
|
-
loadout recommend --project .
|
|
16
|
-
loadout optimize --project .
|
|
11
|
+
IN A PROJECT
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
32
|
-
without rewriting them; outdated replacements remain a separate explicit choice.
|
|
23
|
+
USE IT INSIDE YOUR AGENT
|
|
33
24
|
|
|
34
|
-
|
|
35
|
-
retiring the current managed profile, use:
|
|
36
|
-
loadout install --mode custom --package <id>
|
|
25
|
+
loadout skills install loadout-router --yes
|
|
37
26
|
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
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 "
|
|
4
|
-
import { inspectAgents } from "
|
|
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}
|
|
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 "
|
|
5
|
-
import { loadEffectiveCatalog } from "
|
|
6
|
-
import { ensureDirectory, loadoutHome } from "
|
|
7
|
-
import { readInstallState } from "
|
|
8
|
-
import { buildUpdatePlan } from "
|
|
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 "
|
|
5
|
-
import { detectAgents } from "
|
|
6
|
-
import { readInstallState } from "
|
|
7
|
-
import { buildUpdatePlan } from "
|
|
8
|
-
import { codexMcpServerFingerprint } from "
|
|
9
|
-
import { listInstalledRuntimeToolSkillTargets, listInstalledRuntimeTools, } from "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
2
|
-
import { buildCatalogCoverage } from "
|
|
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 "
|
|
3
|
-
import { ensureDirectory } from "
|
|
4
|
-
import { readInstallState } from "
|
|
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 "
|
|
2
|
-
import { applyPreparedCatalogInstall, formatPreparedCatalogInstall, prepareCatalogInstall, } from "
|
|
3
|
-
import { DEFAULT_ACTIVE_SKILL_LIMIT } from "
|
|
4
|
-
import { loadEffectiveCatalog } from "
|
|
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 "
|
|
7
|
-
import { formatRecommendations, personalizeRecommendations, recommendPackages, scanProject, } from "
|
|
8
|
-
import { readLocalOutcomes } from "
|
|
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
|
+
}
|