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.
- package/README.md +181 -121
- package/catalog/discovered.json +28066 -23196
- package/catalog/packages.json +2 -2
- package/dist/src/cli.js +2 -2
- package/dist/src/commands/agents.js +6 -23
- package/dist/src/commands/catalog.js +254 -27
- package/dist/src/commands/health.js +6 -6
- package/dist/src/commands/inventory.js +21 -121
- 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 +42 -11
- package/dist/src/core/{adapters.js → agents/adapters.js} +2 -2
- package/dist/src/core/{agent-inspection.js → agents/agent-inspection.js} +26 -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} +18 -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} +1 -1
- 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} +9 -4
- 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} +1 -1
- 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/reporting/cli-guide.js +103 -0
- package/dist/src/core/reporting/doctor.js +141 -0
- 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 +278 -0
- package/dist/src/core/{model-config.js → routing/model-config.js} +4 -4
- package/dist/src/core/routing/route.js +539 -0
- 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/CATALOG.md +2 -2
- package/docs/DISCOVERED.md +249 -247
- package/docs/UPSTREAM_LICENSE_DECISIONS.md +19 -12
- package/docs/evidence/readme-claims.json +22 -19
- package/package.json +2 -1
- package/skills/loadout-curator/SKILL.md +105 -0
- package/skills/loadout-router/SKILL.md +120 -0
- package/dist/src/core/cli-guide.js +0 -118
- package/dist/src/core/doctor.js +0 -95
- /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/{safety.js → catalog/safety.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/{completion.js → reporting/completion.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,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { cp, lstat, mkdir, readFile, readdir, rm, writeFile, } from "node:fs/promises";
|
|
3
3
|
import { join, relative, resolve, sep } from "node:path";
|
|
4
|
-
import { loadoutHome } from "
|
|
4
|
+
import { loadoutHome } from "../agents/paths.js";
|
|
5
5
|
import { analyzeUpdateSafety } from "./safety.js";
|
|
6
6
|
const NAME = /^[a-z0-9][a-z0-9._-]*$/;
|
|
7
7
|
const VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?$/;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { lstat, readFile, readdir } from "node:fs/promises";
|
|
3
3
|
import { join, relative, resolve, sep } from "node:path";
|
|
4
|
-
import { DEFAULT_ACTIVE_SKILL_LIMIT } from "
|
|
5
|
-
import { readInstallState } from "
|
|
6
|
-
import { listInstalledRuntimeToolSkillTargets, } from "
|
|
4
|
+
import { DEFAULT_ACTIVE_SKILL_LIMIT } from "../workspace/active-limit.js";
|
|
5
|
+
import { readInstallState } from "../workspace/state.js";
|
|
6
|
+
import { listInstalledRuntimeToolSkillTargets, } from "../agents/runtime-tools.js";
|
|
7
7
|
const MAX_SCAN_DEPTH = 6;
|
|
8
8
|
function cleanFrontmatterValue(value) {
|
|
9
9
|
if (!value)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cp, lstat, readdir, readFile, rm } from "node:fs/promises";
|
|
2
2
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
3
|
-
import { ensureDirectory } from "
|
|
3
|
+
import { ensureDirectory } from "../agents/paths.js";
|
|
4
4
|
import { assertSkillSecurity, scanSkillSecurity } from "./skill-security.js";
|
|
5
5
|
export async function discoverSkillDirectories(root, options = {}) {
|
|
6
6
|
const result = [];
|
|
@@ -4,13 +4,13 @@ import { readFile, stat } from "node:fs/promises";
|
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { isDeepStrictEqual } from "node:util";
|
|
7
|
-
import { writeFileAtomically } from "
|
|
8
|
-
import { loadEffectiveCatalog, validateCatalog } from "
|
|
7
|
+
import { writeFileAtomically } from "../install/atomic-file.js";
|
|
8
|
+
import { loadEffectiveCatalog, validateCatalog } from "../catalog/catalog.js";
|
|
9
9
|
import { evaluatePackage } from "./evaluate.js";
|
|
10
|
-
import { inspectPackage } from "
|
|
11
|
-
import { ensureDirectory, loadoutHome } from "
|
|
12
|
-
import { fetchRepositorySnapshot, normalizeRepository, } from "
|
|
13
|
-
import { safeTerminalText } from "
|
|
10
|
+
import { inspectPackage } from "../install/package.js";
|
|
11
|
+
import { ensureDirectory, loadoutHome } from "../agents/paths.js";
|
|
12
|
+
import { fetchRepositorySnapshot, normalizeRepository, } from "../install/source.js";
|
|
13
|
+
import { safeTerminalText } from "../reporting/terminal.js";
|
|
14
14
|
const dossierDirectory = () => join(loadoutHome(), "candidates");
|
|
15
15
|
const sourceVerifiedDossiers = new WeakMap();
|
|
16
16
|
function installabilityFor(components) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFile, stat } from "node:fs/promises";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
-
import { writeFileAtomically } from "
|
|
4
|
-
import { ensureDirectory } from "
|
|
3
|
+
import { writeFileAtomically } from "../install/atomic-file.js";
|
|
4
|
+
import { ensureDirectory } from "../agents/paths.js";
|
|
5
5
|
export class ConnectorRequestError extends Error {
|
|
6
6
|
status;
|
|
7
7
|
retryAfterSeconds;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { inspectPackage } from "
|
|
2
|
-
import { analyzeUpdateSafety } from "
|
|
1
|
+
import { inspectPackage } from "../install/package.js";
|
|
2
|
+
import { analyzeUpdateSafety } from "../catalog/safety.js";
|
|
3
3
|
function statusFor(findings) {
|
|
4
4
|
if (findings.some((finding) => finding.category === "secret" || finding.category === "instruction"))
|
|
5
5
|
return "blocked";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { ConnectorRequestError, fetchBoundedJson, rateLimitFromHeaders, readDiscoveryConnectorCache, writeDiscoveryConnectorCache, } from "./discovery-connector.js";
|
|
3
|
-
import { loadoutHome } from "
|
|
3
|
+
import { loadoutHome } from "../agents/paths.js";
|
|
4
4
|
const SOURCE = "official-mcp-registry";
|
|
5
5
|
const API = "https://registry.modelcontextprotocol.io/v0.1/servers";
|
|
6
6
|
const ATTRIBUTION = "https://registry.modelcontextprotocol.io/docs";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { fetchGitHubReleaseMetadata, } from "
|
|
4
|
-
import { writeFileAtomically } from "
|
|
5
|
-
import { loadoutHome } from "
|
|
3
|
+
import { fetchGitHubReleaseMetadata, } from "../runtime/github.js";
|
|
4
|
+
import { writeFileAtomically } from "../install/atomic-file.js";
|
|
5
|
+
import { loadoutHome } from "../agents/paths.js";
|
|
6
6
|
const MAX_OBSERVATIONS_PER_REPOSITORY = 90;
|
|
7
7
|
function observationPath(repository) {
|
|
8
8
|
return join(loadoutHome(), "observations", `${repository.replace("/", "__")}.json`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
-
import { writeFileAtomically } from "
|
|
4
|
-
import { ensureDirectory, loadoutHome } from "
|
|
3
|
+
import { writeFileAtomically } from "../install/atomic-file.js";
|
|
4
|
+
import { ensureDirectory, loadoutHome } from "../agents/paths.js";
|
|
5
5
|
const queuePath = () => join(loadoutHome(), "review-queue.json");
|
|
6
6
|
function isQueue(value) {
|
|
7
7
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
@@ -13,7 +13,7 @@ function isQueue(value) {
|
|
|
13
13
|
record.items.every((item) => item &&
|
|
14
14
|
typeof item === "object" &&
|
|
15
15
|
typeof item.repository === "string" &&
|
|
16
|
-
["pending", "shortlisted", "ignored"].includes(item.decision)));
|
|
16
|
+
["pending", "shortlisted", "ignored", "promoted"].includes(item.decision)));
|
|
17
17
|
}
|
|
18
18
|
export async function readReviewQueue() {
|
|
19
19
|
try {
|
|
@@ -102,7 +102,9 @@ function decisionPriority(decision) {
|
|
|
102
102
|
return 0;
|
|
103
103
|
if (decision === "pending")
|
|
104
104
|
return 1;
|
|
105
|
-
|
|
105
|
+
if (decision === "ignored")
|
|
106
|
+
return 2;
|
|
107
|
+
return 3;
|
|
106
108
|
}
|
|
107
109
|
function compareQueueItems(left, right) {
|
|
108
110
|
return (Number(left.alreadyCataloged) - Number(right.alreadyCataloged) ||
|
|
@@ -191,6 +193,9 @@ export async function setReviewDecision(repository, decision) {
|
|
|
191
193
|
await writeFileAtomically(queuePath(), `${JSON.stringify(queue, null, 2)}\n`);
|
|
192
194
|
return item;
|
|
193
195
|
}
|
|
196
|
+
export async function markPromoted(repository) {
|
|
197
|
+
return setReviewDecision(repository, "promoted");
|
|
198
|
+
}
|
|
194
199
|
export function formatReviewQueue(queue) {
|
|
195
200
|
const visible = queue.items.filter((item) => !item.alreadyCataloged);
|
|
196
201
|
return [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { ConnectorRequestError, fetchBoundedJson, rateLimitFromHeaders, readDiscoveryConnectorCache, writeDiscoveryConnectorCache, } from "./discovery-connector.js";
|
|
3
|
-
import { loadoutHome } from "
|
|
3
|
+
import { loadoutHome } from "../agents/paths.js";
|
|
4
4
|
const SOURCE = "skills-sh";
|
|
5
5
|
const API = "https://skills.sh/api/v1/skills";
|
|
6
6
|
const ATTRIBUTION = "https://skills.sh/docs/api";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { lstat, readFile, readdir } from "node:fs/promises";
|
|
3
3
|
import { basename, isAbsolute, join, posix, relative, sep } from "node:path";
|
|
4
|
-
import { enrichInventoryWithProvenance } from "
|
|
5
|
-
import { scanInstalledSkills } from "
|
|
6
|
-
import { installStatePath, recordInstall } from "
|
|
4
|
+
import { enrichInventoryWithProvenance } from "../catalog/provenance.js";
|
|
5
|
+
import { scanInstalledSkills } from "../catalog/skill-inventory.js";
|
|
6
|
+
import { installStatePath, recordInstall } from "../workspace/state.js";
|
|
7
7
|
import { runMutationTransaction } from "./transaction.js";
|
|
8
8
|
const issuedAdoptionPlans = new WeakMap();
|
|
9
9
|
function safeRelativePath(root, path) {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { DEFAULT_ACTIVE_SKILL_LIMIT } from "
|
|
2
|
-
import { loadEffectiveCatalog } from "
|
|
3
|
-
import { detectAgents } from "
|
|
4
|
-
import { isStableSkillSelected, isPowerSkillSelected, resolveCatalogProfile, } from "
|
|
1
|
+
import { DEFAULT_ACTIVE_SKILL_LIMIT } from "../workspace/active-limit.js";
|
|
2
|
+
import { loadEffectiveCatalog, } from "../catalog/catalog.js";
|
|
3
|
+
import { detectAgents } from "../agents/paths.js";
|
|
4
|
+
import { isStableSkillSelected, isPowerSkillSelected, resolveCatalogProfile, } from "../catalog/profiles.js";
|
|
5
5
|
import { applySkillLibraryBatch, applySkillInstallBatch, buildSkillPlan, installedAgents, planManagedProfileReconciliation, } from "./install.js";
|
|
6
6
|
import { fetchRepositorySnapshot, } from "./source.js";
|
|
7
|
-
import { analyzeInstallPlanSafety, } from "
|
|
8
|
-
import { formatModelApiAccess } from "
|
|
9
|
-
import { recordInstalledProfile } from "
|
|
10
|
-
import { readInstallState } from "
|
|
7
|
+
import { analyzeInstallPlanSafety, } from "../catalog/safety.js";
|
|
8
|
+
import { formatModelApiAccess, } from "../routing/access.js";
|
|
9
|
+
import { recordInstalledProfile } from "../workspace/profile-state.js";
|
|
10
|
+
import { readInstallState } from "../workspace/state.js";
|
|
11
11
|
async function parallelMap(values, concurrency, worker) {
|
|
12
12
|
const results = new Array(values.length);
|
|
13
13
|
let cursor = 0;
|
|
@@ -240,11 +240,11 @@ export function formatPreparedCatalogInstall(prepared, options = {}) {
|
|
|
240
240
|
const quarantined = prepared.skipped.filter((item) => item.kind === "quarantined");
|
|
241
241
|
const retired = prepared.reconciliation?.obsoleteUnits ?? [];
|
|
242
242
|
const modeLabel = prepared.selection.mode === "maximum"
|
|
243
|
-
? "Maximum
|
|
243
|
+
? "Maximum"
|
|
244
244
|
: prepared.selection.mode === "power"
|
|
245
|
-
? "Power
|
|
245
|
+
? "Power"
|
|
246
246
|
: prepared.selection.mode === "stable"
|
|
247
|
-
? "Stable
|
|
247
|
+
? "Stable"
|
|
248
248
|
: "Custom";
|
|
249
249
|
const lines = [
|
|
250
250
|
`Loadout: ${modeLabel}`,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { mkdir, open, readFile, rename, rm, stat, utimes, } from "node:fs/promises";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
|
-
import { ensureDirectory } from "
|
|
4
|
+
import { ensureDirectory } from "../agents/paths.js";
|
|
5
5
|
const wait = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
6
6
|
function parseOwner(content) {
|
|
7
7
|
try {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { cp, lstat, rm } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, isAbsolute, join, posix, relative, win32, } from "node:path";
|
|
4
|
-
import { ensureDirectory, loadoutHome } from "
|
|
5
|
-
import { planAdapterSkillInstall } from "
|
|
6
|
-
import { applySkillPlan, detectInstallConflicts } from "
|
|
7
|
-
import { activationLibraryPath, installStatePath, recordInstall, recordInstallBatch, recordLibraryInstallBatch, readInstallState, hashDirectory, writeInstallState, } from "
|
|
4
|
+
import { ensureDirectory, loadoutHome } from "../agents/paths.js";
|
|
5
|
+
import { planAdapterSkillInstall } from "../agents/adapters.js";
|
|
6
|
+
import { applySkillPlan, detectInstallConflicts } from "../catalog/skills.js";
|
|
7
|
+
import { activationLibraryPath, installStatePath, recordInstall, recordInstallBatch, recordLibraryInstallBatch, readInstallState, hashDirectory, writeInstallState, } from "../workspace/state.js";
|
|
8
8
|
import { runMutationTransaction } from "./transaction.js";
|
|
9
|
-
import { inspectTargetOccupancy } from "
|
|
9
|
+
import { inspectTargetOccupancy } from "../workspace/target-occupancy.js";
|
|
10
10
|
export function installedAgents(agents, requested) {
|
|
11
11
|
const available = agents.filter((agent) => agent.installed);
|
|
12
12
|
if (!requested || requested.length === 0)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFile, readdir } from "node:fs/promises";
|
|
2
2
|
import { dirname, join, relative, resolve } from "node:path";
|
|
3
|
-
import { discoverSkillDirectories } from "
|
|
4
|
-
import { discoverMcpManifests } from "
|
|
5
|
-
import { discoverResources } from "
|
|
3
|
+
import { discoverSkillDirectories } from "../catalog/skills.js";
|
|
4
|
+
import { discoverMcpManifests } from "../runtime/mcp.js";
|
|
5
|
+
import { discoverResources } from "../catalog/components.js";
|
|
6
6
|
const portableRelative = (root, path) => relative(root, path).split(/[\\/]/).join("/") || ".";
|
|
7
7
|
function frontmatter(path) {
|
|
8
8
|
return readFile(join(path, "SKILL.md"), "utf8").then((content) => {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { cp, lstat, readFile, readdir, rm } from "node:fs/promises";
|
|
3
3
|
import { join, relative, sep } from "node:path";
|
|
4
|
-
import { analyzeUpdateSafety } from "
|
|
5
|
-
import { enrichInventoryWithProvenance, } from "
|
|
6
|
-
import { scanInstalledSkills } from "
|
|
7
|
-
import { installStatePath, recordInstallBatch } from "
|
|
4
|
+
import { analyzeUpdateSafety } from "../catalog/safety.js";
|
|
5
|
+
import { enrichInventoryWithProvenance, } from "../catalog/provenance.js";
|
|
6
|
+
import { scanInstalledSkills } from "../catalog/skill-inventory.js";
|
|
7
|
+
import { installStatePath, recordInstallBatch } from "../workspace/state.js";
|
|
8
8
|
import { runMutationTransaction } from "./transaction.js";
|
|
9
9
|
import { repositoryCachePath } from "./source.js";
|
|
10
|
-
import { validateSkillDirectory } from "
|
|
10
|
+
import { validateSkillDirectory } from "../catalog/skills.js";
|
|
11
11
|
const issuedPlans = new WeakMap();
|
|
12
12
|
const IGNORED_TREE_NAMES = new Set([".git", ".cache", "node_modules"]);
|
|
13
13
|
function slug(value) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { readFile, rm } from "node:fs/promises";
|
|
3
|
-
import { forgetInstall, installStatePath, readInstallState } from "
|
|
4
|
-
import { writeMcpConfigPlan } from "
|
|
3
|
+
import { forgetInstall, installStatePath, readInstallState, } from "../workspace/state.js";
|
|
4
|
+
import { writeMcpConfigPlan } from "../runtime/mcp.js";
|
|
5
5
|
import { runMutationTransaction } from "./transaction.js";
|
|
6
|
-
import { managedFileReadPath } from "
|
|
7
|
-
import { codexMcpServerFingerprint, removeCodexMcpServerBlock, writeCodexMcpConfigContent, } from "
|
|
6
|
+
import { managedFileReadPath } from "../workspace/active-set.js";
|
|
7
|
+
import { codexMcpServerFingerprint, removeCodexMcpServerBlock, writeCodexMcpConfigContent, } from "../agents/codex-mcp.js";
|
|
8
8
|
export async function planRemove(packageId) {
|
|
9
9
|
const state = await readInstallState();
|
|
10
10
|
const record = state.installs.find((entry) => entry.packageId === packageId);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { readFile, writeFile, mkdir, readdir, rename, rm, lstat, } from "node:fs/promises";
|
|
3
3
|
import { dirname, join, resolve, sep } from "node:path";
|
|
4
|
-
import { loadoutHome, ensureDirectory, userHome } from "
|
|
4
|
+
import { loadoutHome, ensureDirectory, userHome } from "../agents/paths.js";
|
|
5
5
|
export async function createSnapshot(paths, options = {}) {
|
|
6
6
|
const normalizedRoots = [...new Set(paths.map((path) => resolve(path)))];
|
|
7
7
|
const snapshot = {
|
|
@@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { execFile } from "node:child_process";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
|
-
import { loadoutHome, ensureDirectory } from "
|
|
7
|
+
import { loadoutHome, ensureDirectory } from "../agents/paths.js";
|
|
8
8
|
const execFileAsync = promisify(execFile);
|
|
9
9
|
async function enforceRepositoryBounds(root, options) {
|
|
10
10
|
if (options.maxBytes === undefined && options.maxFiles === undefined)
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, resolve, sep } from "node:path";
|
|
4
|
-
import { loadEffectiveCatalog } from "
|
|
4
|
+
import { loadEffectiveCatalog } from "../catalog/catalog.js";
|
|
5
5
|
import { installedAgents } from "./install.js";
|
|
6
|
-
import { orderManifestPackages, readManifest, writeLockfile, } from "
|
|
7
|
-
import { detectAgents, userHome } from "
|
|
6
|
+
import { orderManifestPackages, readManifest, writeLockfile, } from "../workspace/manifest.js";
|
|
7
|
+
import { detectAgents, userHome } from "../agents/paths.js";
|
|
8
8
|
import { fetchGitSnapshot, fetchRepositorySnapshot } from "./source.js";
|
|
9
|
-
import { addRootFileExports, buildUniversalPackagePlan } from "
|
|
10
|
-
import { analyzeInstallPlanSafety, } from "
|
|
11
|
-
import { fetchRemoteRegistryPackage, packPackage, resolveRegistryPackage, } from "
|
|
12
|
-
import { discoverMcpManifests, planMcpConfigBatch, writeMcpConfigPlan, } from "
|
|
13
|
-
import { applySkillPlan, detectInstallConflicts } from "
|
|
14
|
-
import { installStatePath, readInstallState, recordInstallTransaction, } from "
|
|
9
|
+
import { addRootFileExports, buildUniversalPackagePlan, } from "../catalog/components.js";
|
|
10
|
+
import { analyzeInstallPlanSafety, } from "../catalog/safety.js";
|
|
11
|
+
import { fetchRemoteRegistryPackage, packPackage, resolveRegistryPackage, } from "../catalog/registry.js";
|
|
12
|
+
import { discoverMcpManifests, planMcpConfigBatch, writeMcpConfigPlan, } from "../runtime/mcp.js";
|
|
13
|
+
import { applySkillPlan, detectInstallConflicts } from "../catalog/skills.js";
|
|
14
|
+
import { installStatePath, readInstallState, recordInstallTransaction, } from "../workspace/state.js";
|
|
15
15
|
import { runMutationTransaction } from "./transaction.js";
|
|
16
16
|
function registrySourceKey(source) {
|
|
17
17
|
if (source.type === "registry")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { lstat, mkdir, readdir, readFile, rename, rm, writeFile, } from "node:fs/promises";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { ensureDirectory, loadoutHome } from "
|
|
4
|
+
import { ensureDirectory, loadoutHome } from "../agents/paths.js";
|
|
5
5
|
import { createSnapshot, readSnapshot, recordSnapshotPostMutationState, restoreSnapshot, } from "./snapshot.js";
|
|
6
6
|
import { acquireFileLock, withFileLock, } from "./file-lock.js";
|
|
7
7
|
export const transactionRoot = () => join(loadoutHome(), "staging");
|
|
@@ -2,11 +2,11 @@ import { execFile } from "node:child_process";
|
|
|
2
2
|
import { readdir, rm, rmdir } from "node:fs/promises";
|
|
3
3
|
import { dirname, parse, resolve } from "node:path";
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
|
-
import { loadoutHome } from "
|
|
5
|
+
import { loadoutHome } from "../agents/paths.js";
|
|
6
6
|
import { applyRemove, planRemove } from "./remove.js";
|
|
7
|
-
import { applyRuntimeToolPlan, listInstalledRuntimeTools, planRuntimeTool, } from "
|
|
8
|
-
import { applyNativeSchedulerBundle, planNativeScheduler, } from "
|
|
9
|
-
import { readInstallState } from "
|
|
7
|
+
import { applyRuntimeToolPlan, listInstalledRuntimeTools, planRuntimeTool, } from "../agents/runtime-tools.js";
|
|
8
|
+
import { applyNativeSchedulerBundle, planNativeScheduler, } from "../runtime/scheduler.js";
|
|
9
|
+
import { readInstallState } from "../workspace/state.js";
|
|
10
10
|
export async function buildUninstallPlan(dependencies = {}) {
|
|
11
11
|
const stateHome = loadoutHome();
|
|
12
12
|
const state = await readInstallState();
|
|
@@ -3,11 +3,11 @@ import { basename, join } from "node:path";
|
|
|
3
3
|
import { fetchRepositorySnapshot, resolveRepositoryHead } from "./source.js";
|
|
4
4
|
import { repositoryCachePath } from "./source.js";
|
|
5
5
|
import { diffRepositorySnapshots } from "./diff.js";
|
|
6
|
-
import { hashDirectory, readInstallState } from "
|
|
7
|
-
import { analyzeUpdateSafety } from "
|
|
8
|
-
import { detectAgents, loadoutHome } from "
|
|
6
|
+
import { hashDirectory, readInstallState } from "../workspace/state.js";
|
|
7
|
+
import { analyzeUpdateSafety } from "../catalog/safety.js";
|
|
8
|
+
import { detectAgents, loadoutHome } from "../agents/paths.js";
|
|
9
9
|
import { applySkillInstall, buildSkillPlan, installedAgents, } from "./install.js";
|
|
10
|
-
import { discoverSkillDirectories, validateSkillDirectory, } from "
|
|
10
|
+
import { discoverSkillDirectories, validateSkillDirectory, } from "../catalog/skills.js";
|
|
11
11
|
function managedUnitIds(state, packageId) {
|
|
12
12
|
return [
|
|
13
13
|
...new Set((state.activations ?? [])
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/** Plain-language entry points for people using the CLI, not maintaining it. */
|
|
2
|
+
export const BEGINNER_GUIDE = `
|
|
3
|
+
Loadout — manage the skills, tools, and MCP servers your AI agents use.
|
|
4
|
+
|
|
5
|
+
FIRST TIME
|
|
6
|
+
|
|
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
|
|
10
|
+
|
|
11
|
+
IN A PROJECT
|
|
12
|
+
|
|
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
|
|
16
|
+
|
|
17
|
+
WHILE YOU WORK
|
|
18
|
+
|
|
19
|
+
loadout route <describe a task> which model tier fits, and what it costs
|
|
20
|
+
loadout route --conserve <task> cheaper tiers when you are low on quota
|
|
21
|
+
loadout handoff send codex "..." give a task to your other agent
|
|
22
|
+
|
|
23
|
+
USE IT INSIDE YOUR AGENT
|
|
24
|
+
|
|
25
|
+
loadout skills install loadout-router --yes
|
|
26
|
+
|
|
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
|
|
39
|
+
`.trim();
|
|
40
|
+
export const ADVANCED_GUIDE = [
|
|
41
|
+
"ADVANCED COMMANDS",
|
|
42
|
+
"",
|
|
43
|
+
"Hidden from the first screen so daily use stays focused.",
|
|
44
|
+
"",
|
|
45
|
+
"Discovery: candidate, discover, review-queue, evaluate, skill-audit",
|
|
46
|
+
"Sharing: init, lock, export, import, share, card",
|
|
47
|
+
"Integrations: mcp-recipe, mcp-config, credentials, models, handoff, skills, convert",
|
|
48
|
+
"Lifecycle: plan, adopt, uninstall, profiles, autopilot, tool, watch",
|
|
49
|
+
"",
|
|
50
|
+
"`loadout <command> --help` for options. Mutations preview first or require --yes.",
|
|
51
|
+
].join("\n");
|
|
52
|
+
/**
|
|
53
|
+
* The first-screen help shows only the everyday core (~24 commands). Everything
|
|
54
|
+
* else stays fully available and runnable, just omitted from the default help so
|
|
55
|
+
* the surface reads as a focused product rather than a tool dump. `loadout
|
|
56
|
+
* advanced` lists the hidden surface; `loadout <command> --help` documents each.
|
|
57
|
+
*/
|
|
58
|
+
export const HIDDEN_FROM_FIRST_SCREEN = new Set([
|
|
59
|
+
// manifests, authoring, sharing
|
|
60
|
+
"init",
|
|
61
|
+
"lock",
|
|
62
|
+
"export",
|
|
63
|
+
"import",
|
|
64
|
+
"audit",
|
|
65
|
+
"create",
|
|
66
|
+
"add",
|
|
67
|
+
"unadd",
|
|
68
|
+
"share",
|
|
69
|
+
"card",
|
|
70
|
+
"search",
|
|
71
|
+
// analytics / evidence / discovery
|
|
72
|
+
"report",
|
|
73
|
+
"versions",
|
|
74
|
+
"skill-audit",
|
|
75
|
+
"candidate",
|
|
76
|
+
"discover",
|
|
77
|
+
"review-queue",
|
|
78
|
+
"inspect",
|
|
79
|
+
"propose",
|
|
80
|
+
"evaluate",
|
|
81
|
+
// integrations / credentials / runtime
|
|
82
|
+
"credentials",
|
|
83
|
+
"models",
|
|
84
|
+
"handoff",
|
|
85
|
+
"completion",
|
|
86
|
+
"mcp-recipe",
|
|
87
|
+
"mcp-config",
|
|
88
|
+
"convert",
|
|
89
|
+
// lifecycle extras
|
|
90
|
+
"plan",
|
|
91
|
+
"adopt",
|
|
92
|
+
"uninstall",
|
|
93
|
+
"profiles",
|
|
94
|
+
"autopilot",
|
|
95
|
+
"tool",
|
|
96
|
+
"watch",
|
|
97
|
+
"schedule",
|
|
98
|
+
"unschedule",
|
|
99
|
+
"alert-ignore",
|
|
100
|
+
"alert-pin",
|
|
101
|
+
"alert-unpin",
|
|
102
|
+
"alert-pins",
|
|
103
|
+
]);
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { access, constants } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { detectAgents, directoryExists, loadoutHome, userHome, } from "../agents/paths.js";
|
|
4
|
+
import { inspectAgents } from "../agents/agent-inspection.js";
|
|
5
|
+
async function writable(path) {
|
|
6
|
+
// A not-yet-created directory is healthy when any existing ancestor is
|
|
7
|
+
// writable; this handles first-run paths such as ~/.agents/skills.
|
|
8
|
+
let candidate = path;
|
|
9
|
+
while (true) {
|
|
10
|
+
try {
|
|
11
|
+
await access(candidate, constants.W_OK);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
const parent = dirname(candidate);
|
|
16
|
+
if (parent === candidate)
|
|
17
|
+
return false;
|
|
18
|
+
candidate = parent;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export async function runDoctor() {
|
|
23
|
+
const home = userHome();
|
|
24
|
+
const stateHome = loadoutHome();
|
|
25
|
+
const loadoutHomeExists = await directoryExists(stateHome);
|
|
26
|
+
const loadoutHomeWritable = await writable(stateHome);
|
|
27
|
+
const agents = await detectAgents();
|
|
28
|
+
const inventories = await inspectAgents(agents);
|
|
29
|
+
const diagnosedAgents = await Promise.all(agents.map(async (agent, index) => {
|
|
30
|
+
const exists = await directoryExists(agent.skillsDirectory);
|
|
31
|
+
const canWrite = await writable(agent.skillsDirectory);
|
|
32
|
+
const issues = [];
|
|
33
|
+
if (!agent.installed)
|
|
34
|
+
issues.push(`Install or configure ${agent.displayName} to enable it.`);
|
|
35
|
+
if (!exists)
|
|
36
|
+
issues.push("Skills directory does not exist yet; Loadout will create it during install.");
|
|
37
|
+
if (!canWrite)
|
|
38
|
+
issues.push("Skills directory is not writable; check permissions or choose another home.");
|
|
39
|
+
return {
|
|
40
|
+
agent,
|
|
41
|
+
inventory: inventories[index],
|
|
42
|
+
skillsRootExists: exists,
|
|
43
|
+
writable: canWrite,
|
|
44
|
+
issues,
|
|
45
|
+
};
|
|
46
|
+
}));
|
|
47
|
+
const issues = [];
|
|
48
|
+
if (!loadoutHomeExists)
|
|
49
|
+
issues.push("Loadout state directory does not exist yet; it will be created on first install.");
|
|
50
|
+
if (!loadoutHomeWritable)
|
|
51
|
+
issues.push("Loadout state directory is not writable; check LOADOUT_HOME permissions.");
|
|
52
|
+
if (!diagnosedAgents.some(({ agent }) => agent.installed)) {
|
|
53
|
+
issues.push("No supported agent executable was detected on PATH.");
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
platform: process.platform,
|
|
57
|
+
userHome: home,
|
|
58
|
+
loadoutHome: stateHome,
|
|
59
|
+
loadoutHomeExists,
|
|
60
|
+
loadoutHomeWritable,
|
|
61
|
+
agents: diagnosedAgents,
|
|
62
|
+
issues,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function formatDoctorReport(report, options = {}) {
|
|
66
|
+
const installed = report.agents.filter((a) => a.agent.installed);
|
|
67
|
+
const notInstalled = report.agents.filter((a) => !a.agent.installed);
|
|
68
|
+
const allIssues = [...report.issues, ...installed.flatMap((a) => a.issues)];
|
|
69
|
+
const grade = allIssues.length === 0 && installed.length > 0
|
|
70
|
+
? "HEALTHY"
|
|
71
|
+
: allIssues.length <= 2
|
|
72
|
+
? "OK"
|
|
73
|
+
: "NEEDS ATTENTION";
|
|
74
|
+
const lines = [
|
|
75
|
+
`loadout doctor — ${grade}`,
|
|
76
|
+
"",
|
|
77
|
+
`Platform: ${report.platform}`,
|
|
78
|
+
`State: ${report.loadoutHome} ${report.loadoutHomeExists ? "✓" : "⚠ not created"} ${report.loadoutHomeWritable ? "writable" : "⚠ not writable"}`,
|
|
79
|
+
`Agents: ${installed.length} detected, ${notInstalled.length} available`,
|
|
80
|
+
"",
|
|
81
|
+
];
|
|
82
|
+
// --- Detected agents: compact summary ---
|
|
83
|
+
if (installed.length) {
|
|
84
|
+
lines.push("DETECTED AGENTS");
|
|
85
|
+
for (const entry of installed) {
|
|
86
|
+
const skillCount = entry.inventory.components
|
|
87
|
+
.filter((c) => c.scanned && c.directoryExists)
|
|
88
|
+
.reduce((sum, c) => sum + c.entries.length, 0);
|
|
89
|
+
const supported = entry.inventory.components
|
|
90
|
+
.filter((c) => c.compatibility === "native" || c.compatibility === "adapted")
|
|
91
|
+
.map((c) => c.type);
|
|
92
|
+
lines.push(` ✓ ${entry.agent.displayName}`, ` ${entry.agent.skillsDirectory}`, ` ${skillCount} items | supports: ${supported.join(", ")}`);
|
|
93
|
+
if (entry.issues.length) {
|
|
94
|
+
for (const issue of entry.issues)
|
|
95
|
+
lines.push(` ⚠ ${issue}`);
|
|
96
|
+
}
|
|
97
|
+
if (options.verbose) {
|
|
98
|
+
for (const component of entry.inventory.components) {
|
|
99
|
+
if (component.compatibility === "unsupported")
|
|
100
|
+
continue;
|
|
101
|
+
const detail = component.scanned
|
|
102
|
+
? component.directoryExists
|
|
103
|
+
? `${component.entries.length} items`
|
|
104
|
+
: "not created"
|
|
105
|
+
: (component.note ?? "—");
|
|
106
|
+
lines.push(` ${component.type}: ${detail}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
lines.push("");
|
|
111
|
+
}
|
|
112
|
+
// --- Not installed: one-liner ---
|
|
113
|
+
if (notInstalled.length) {
|
|
114
|
+
lines.push(`NOT DETECTED: ${notInstalled.map((a) => a.agent.displayName).join(", ")}`, "");
|
|
115
|
+
}
|
|
116
|
+
// --- Issues ---
|
|
117
|
+
if (allIssues.length) {
|
|
118
|
+
lines.push("ISSUES");
|
|
119
|
+
for (const issue of allIssues)
|
|
120
|
+
lines.push(` ⚠ ${issue}`);
|
|
121
|
+
lines.push("");
|
|
122
|
+
}
|
|
123
|
+
// --- Next steps ---
|
|
124
|
+
lines.push("NEXT STEPS");
|
|
125
|
+
if (allIssues.length) {
|
|
126
|
+
lines.push(" Fix the issues above, then run `loadout doctor` again.");
|
|
127
|
+
}
|
|
128
|
+
else if (installed.length === 0) {
|
|
129
|
+
lines.push(" Install an AI coding agent (Claude Code, Codex, Cursor, etc.),", " then run `loadout doctor` again.");
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
lines.push(" loadout status see what's managed", " loadout setup install a curated skill set", " loadout route find the right model for your task", " loadout health check for updates");
|
|
133
|
+
}
|
|
134
|
+
return lines.join("\n");
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Legacy-compatible verbose format. Used when --verbose is passed.
|
|
138
|
+
*/
|
|
139
|
+
export function formatDoctorReportVerbose(report) {
|
|
140
|
+
return formatDoctorReport(report, { verbose: true });
|
|
141
|
+
}
|