loadout-ai 0.2.2 → 0.3.1

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 CHANGED
@@ -1,5 +1,42 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.1 - 2026-07-18
4
+
5
+ ### Fixed
6
+
7
+ - Distinguish separately billed AI/model API keys from unrelated service credentials.
8
+ The no-model-key MCP view now correctly includes GitHub read-only while clearly
9
+ disclosing its GitHub token requirement; `--credential-free` retains the stricter
10
+ zero-credential filter.
11
+
12
+ ## 0.3.0 - 2026-07-18
13
+
14
+ ### Added
15
+
16
+ - Add a preview-first `loadout uninstall` that removes managed packages, runtime
17
+ tools, native daily jobs, library/cache/state, and optionally the global npm CLI.
18
+ - Save the installed Stable, Power, Maximum, or Custom profile and evaluate it on
19
+ every `loadout update` check.
20
+ - Add explicit whole-profile `loadout update --yes` for reviewed profile drift and
21
+ safe active-package updates, holding disabled or risky changes for review.
22
+ - Add a pinned Chrome DevTools MCP recipe and `mcp-recipe --no-key` alongside
23
+ Playwright MCP.
24
+
25
+ ### Changed
26
+
27
+ - Daily update jobs use the unified profile-and-package update check while remaining
28
+ read-only.
29
+ - Bound large update checks to four repositories at a time and 30 seconds per network
30
+ operation, with readable progress in interactive CLI output.
31
+
32
+ ## 0.2.3 - 2026-07-17
33
+
34
+ ### Fixed
35
+
36
+ - Validate large rollback snapshots in linear, constant-stack time so project-aware activation can safely snapshot large reviewed skills.
37
+ - Preserve strict malformed-base64 rejection without relying on a stack-intensive regular expression.
38
+ - Rewrite every generated Graphify top-level lookup, repair, and optional Gemini install to the reviewed hashed artifact instead of leaving unpinned package fallbacks.
39
+
3
40
  ## 0.2.2 - 2026-07-17
4
41
 
5
42
  ### Fixed
package/MASTER_PLAN.md CHANGED
@@ -6,6 +6,66 @@ Category: Developer Tools
6
6
  Team size: 3
7
7
  Target submission: July 21, 2026 at 5:00 PM Pacific / July 22 at 4:00 AM Dubai
8
8
 
9
+ ## Current remaining work (July 18, 2026)
10
+
11
+ This is the authoritative active list. The long phase history below is retained as
12
+ an engineering record, not a command to build every speculative system before users
13
+ can test Loadout.
14
+
15
+ ### Required before calling the CLI polished for public testing
16
+
17
+ - [x] `P18-01 [TERRA]` Make the first CLI screen beginner-readable: a read-only
18
+ `loadout guide`, concise default library summary, focused first-screen help,
19
+ and retained access to every advanced command through `loadout advanced` or
20
+ `<command> --help`.
21
+ - [x] `P18-02 [TERRA]` Make core machine output honest: `catalog --json` returns
22
+ JSON, and `update --package <id>` checks only that package rather than every
23
+ tracked installation.
24
+ - [ ] `P18-03 [HUMAN+TERRA]` Run the founder acceptance path in
25
+ `docs/USER_TEST_GUIDE.md` on the real Codex and Claude profiles. Record each
26
+ observed failure and turn reproducible ones into regression tests.
27
+ - [x] `P18-04 [TERRA]` Bound and explain live network checks in `health --updates`
28
+ and `watch --once`; they must show progress, a clear timeout, and an actionable
29
+ result even when a large Maximum library is present.
30
+ - [x] `P18-05 [TERRA]` Make Agent Health distinguish active skills from disabled
31
+ Maximum-library copies, so a broad download is not presented as a broken active
32
+ configuration.
33
+ - [x] `P18-06 [TERRA+LUNA]` Audit the local dashboard with real founder state at
34
+ desktop and mobile widths. Keep it optional, reduce jargon, and only add UI
35
+ actions that retain preview, explicit acknowledgement, snapshot, and rollback.
36
+ - [x] `P18-07 [TERRA]` Add a concise beginner section to README that links to the
37
+ testing guide and explains Stable, Power, Maximum, project recommendations,
38
+ daily discovery, and rollback in plain language.
39
+ - [ ] `P18-08 [HUMAN+TERRA]` Run the full verification gate, publish the next npm
40
+ patch/minor only after founder acceptance, and test that exact tarball in a
41
+ fresh terminal.
42
+
43
+ ### Release 0.3 lifecycle hardening
44
+
45
+ - [x] `P18-09 [SOL+TERRA]` Add preview-first complete uninstall with modified-file
46
+ protection, native-job cleanup, runtime restoration, guarded state deletion,
47
+ and optional global npm removal.
48
+ - [x] `P18-10 [TERRA]` Persist the installed profile and make `loadout update`
49
+ evaluate both profile drift and every managed repository.
50
+ - [x] `P18-11 [SOL+TERRA]` Add explicit bulk safe updates while holding disabled,
51
+ risky, and failed packages for review; scheduled checks remain read-only.
52
+ - [x] `P18-12 [TERRA]` Add a pinned Chrome DevTools MCP recipe and distinguish
53
+ separately billed AI/model API keys from unrelated service credentials. The
54
+ no-model-key inventory includes GitHub read-only with its token disclosed.
55
+ - [ ] `P18-13 [HUMAN+TERRA]` Publish `0.3.1`, then run Stable -> rollback -> Power ->
56
+ rollback -> Maximum -> project optimization -> dashboard -> complete uninstall
57
+ against the exact npm package.
58
+
59
+ ### Explicitly deferred (do not expand during this usability pass)
60
+
61
+ - Hosted accounts, GitHub OAuth, cloud sync, analytics, and enterprise policy.
62
+ - Required API keys, automatic provider spending, or treating chat subscriptions as
63
+ API access.
64
+ - Automatically installing newly discovered repositories or executing arbitrary
65
+ third-party installers.
66
+ - A universal quality score, social-network scraping, or support claims for an agent
67
+ that has not passed a real adapter test.
68
+
9
69
  ## 1. Executive summary
10
70
 
11
71
  Loadout is a universal extension manager for AI coding agents. It detects the agents
@@ -91,12 +151,11 @@ Loadout wins through:
91
151
  - Optional local-folder scan for project-aware recommendations.
92
152
  - Clear display of `native`, `adapted`, and `unsupported` components.
93
153
 
94
- ### 3.2 Committed full-product scope
154
+ ### 3.2 Deferred product exploration (not current launch scope)
95
155
 
96
- These are committed capabilities, not disposable ideas. The team attempts them after
97
- the submission-critical vertical slice is integrated and passing. Any capability that
98
- is incomplete at submission must remain behind an experimental flag rather than being
99
- presented as production-ready.
156
+ These are ideas worth revisiting after the core CLI has passed real founder and
157
+ external user testing. They are not commitments for this hackathon release and must
158
+ not be presented as production-ready simply because a prototype or command exists.
100
159
 
101
160
  - GitHub OAuth for private repositories and personalized discovery, using minimal
102
161
  read-only scopes by default.
@@ -1773,31 +1832,7 @@ small PRs, no long-lived branches.
1773
1832
 
1774
1833
  ## 25. Immediate next tasks
1775
1834
 
1776
- The next engineering wave deliberately precedes npm, Devpost, and repository-visibility
1777
- work. Preserve the Sol/Terra/Luna routing and credit policy above.
1778
-
1779
- 1. `P16-02/03`: add a concrete disposable fixture executor and one explicit provider
1780
- adapter only after the user supplies separate API credit and approves the exact
1781
- maximum spend. Run provider reconciliation and the hosted Linux/Windows matrix; do
1782
- not turn Codex subscription or hackathon credit into an assumed API budget.
1783
- 2. `P16-04/05`: execute at least five real paired trials per compared candidate, review
1784
- failures and contamination, sign the resulting evidence, and obtain genuine human
1785
- license/trust approval before any `benchmarked` or `recommended` transition.
1786
- 3. `P16-12`: generate the production Ed25519 key outside the repository, configure the
1787
- public feed location and secret-backed daily publisher, test first-pin/rotation/
1788
- recovery operationally, and publish no private or unsigned artifact.
1789
- 4. `P16-14`: run the beginner comprehension session for card/compare/badge wording and
1790
- convert every reproducible misunderstanding into a fixture or copy regression.
1791
- 5. `P16-15`: independently review usefulness, license, security, dependencies,
1792
- Windows behavior, rollback, and removal for a second runtime tool before admission.
1793
- 6. `P16-16`: run founder testing first, then implement only observed adapter gaps with
1794
- official-source evidence, preservation fixtures, transaction coverage, and a real
1795
- disposable smoke test.
1796
- 7. `P16-18`: run the full founder matrix on disposable profiles before the real Codex
1797
- and Claude profiles, followed by ten external sessions across experience levels and
1798
- Windows/macOS/Linux. Convert reproducible failures into regression tests.
1799
- 8. Rerun `npm run verify`, the complete disposable feature matrix, npm dry-run, hosted
1800
- cross-platform jobs, and clean external tarball installs after those external gates.
1801
- 9. Human release work remains last: P12-30 license decisions, P15-06 public visibility
1802
- and npm publication, P15-07B clean external installs, branch protection, demo/video,
1803
- feedback, Devpost fields, and final review.
1835
+ Use the `Current remaining work` section at the top of this document. The next action
1836
+ is founder acceptance testing with `docs/USER_TEST_GUIDE.md`; do not add a new hosted
1837
+ service, provider dependency, or frontend surface until an observed user-testing issue
1838
+ justifies it.
package/README.md CHANGED
@@ -11,7 +11,7 @@ It solves a simple problem: useful skills and MCP tools are scattered across hun
11
11
  You need Node.js 20 or newer and Git.
12
12
 
13
13
  ```bash
14
- npm install --global loadout-ai@0.2.0
14
+ npm install --global loadout-ai@0.3.1
15
15
  loadout --version
16
16
  loadout upgrade
17
17
  ```
@@ -30,6 +30,19 @@ Every applied change creates a snapshot. Undo the latest change with:
30
30
  loadout rollback
31
31
  ```
32
32
 
33
+ Remove one package with `loadout remove <package>`. Remove everything Loadout owns
34
+ with a preview-first command:
35
+
36
+ ```bash
37
+ loadout uninstall
38
+ loadout uninstall --yes
39
+ loadout uninstall --yes --remove-cli
40
+ ```
41
+
42
+ The first command changes nothing. Complete uninstall preserves unmanaged files and
43
+ stops if a Loadout-managed file contains your edits unless you explicitly add
44
+ `--force`.
45
+
33
46
  ## Choose how much you want
34
47
 
35
48
  | Mode | Best for | What happens |
@@ -126,6 +139,11 @@ loadout alerts --updates
126
139
  loadout update
127
140
  ```
128
141
 
142
+ `loadout update` checks the saved Stable, Power, Maximum, or Custom profile plus every
143
+ managed repository. It is read-only. `loadout update --yes` applies reviewed profile
144
+ changes and safe screened updates; anything disabled, risky, or failed is held for
145
+ review. Daily checks never include `--yes`.
146
+
129
147
  The wording matters:
130
148
 
131
149
  - A **new lead** is interesting enough to inspect, not “must install.”
@@ -166,6 +184,18 @@ loadout setup --mode maximum --api-access openai,anthropic
166
184
 
167
185
  Loadout never treats API access as permission to install an MCP server. Credentialed MCP tools remain explicit setup steps, and configuration stores an environment-variable or OS-keychain reference rather than the secret value.
168
186
 
187
+ See the reviewed MCP recipes that need no separately billed AI/model API key:
188
+
189
+ ```bash
190
+ loadout mcp-recipe --no-key
191
+ ```
192
+
193
+ This includes Playwright MCP, Chrome DevTools MCP, and GitHub's read-only MCP. Loadout
194
+ shows non-AI credentials separately: GitHub MCP needs a GitHub token, while the two
195
+ browser recipes need no credential. Use `loadout mcp-recipe --credential-free` for the
196
+ stricter zero-credential list. Browser control and service authorization remain
197
+ explicit.
198
+
169
199
  ```bash
170
200
  export LOADOUT_GITHUB_TOKEN="$GITHUB_PERSONAL_ACCESS_TOKEN"
171
201
 
@@ -206,7 +236,9 @@ Executable tools and MCP servers receive separate treatment because they can run
206
236
  | Read the discovery queue | `loadout review-queue` |
207
237
  | Check installed changes | `loadout alerts --updates` |
208
238
  | Preview updates | `loadout update` |
239
+ | Apply all safe reviewed updates | `loadout update --yes` |
209
240
  | Undo the latest applied change | `loadout rollback` |
241
+ | Completely remove Loadout | `loadout uninstall` |
210
242
  | Test safely without touching your profile | `loadout demo` |
211
243
  | See every command | `loadout --help` |
212
244
 
package/dist/src/cli.js CHANGED
@@ -12,13 +12,15 @@ import { fetchRepositorySnapshot } from "./core/source.js";
12
12
  import { discoverMcpManifests, summarizeMcpManifest, planMcpConfig, summarizeMcpConfigPlan, applyMcpConfigPlan, } from "./core/mcp.js";
13
13
  import { REVIEWED_MCP_RECIPES, findMcpRecipe, formatMcpRecipePlan, planMcpRecipe, verifyMcpRecipe, verifyMcpRecipeConnection, } from "./core/mcp-recipes.js";
14
14
  import { runDoctor, formatDoctorReport } from "./core/doctor.js";
15
- import { applyPackageUpdate, buildUpdatePlan, formatUpdatePlan, } from "./core/update.js";
15
+ import { applyPackageUpdate, buildUpdatePlan, formatUpdatePlan, selectSafeAutomaticUpdates, } from "./core/update.js";
16
16
  import { startApiServer } from "./core/api.js";
17
17
  import { inspectPackage, formatPackageInspection } from "./core/package.js";
18
18
  import { addManifestPackage, applyProfileToManifest, initManifest, readManifest, removeManifestPackage, writeLockfile, } from "./core/manifest.js";
19
19
  import { buildHealthReport, formatHealthReport } from "./core/health.js";
20
- import { readInstallState } from "./core/state.js";
20
+ import { readInstallState, recordInstallTransaction } from "./core/state.js";
21
21
  import { applyRemove, planRemove } from "./core/remove.js";
22
+ import { applyUninstall, buildUninstallPlan, formatUninstallPlan, uninstallGlobalCli, } from "./core/uninstall.js";
23
+ import { evaluateInstalledProfile, formatInstalledProfileStatus, recordInstalledProfile, } from "./core/profile-state.js";
22
24
  import { formatRecommendations, personalizeRecommendations, profileManifestPackages, recommendPackages, scanProject, TESTED_PROFILES, } from "./core/recommend.js";
23
25
  import { buildImprovementCycle, formatImprovementCycle, recordImprovementOutcome, writeImprovementCycle, } from "./core/improve.js";
24
26
  import { applySyncPlan, buildSyncPlan } from "./core/sync.js";
@@ -47,7 +49,7 @@ import { applyPreparedCatalogInstall, formatPreparedCatalogInstall, prepareCatal
47
49
  import { formatInstalledSkillInventory, scanInstalledSkills, } from "./core/skill-inventory.js";
48
50
  import { enrichInventoryWithProvenance, formatProvenanceSummary, resolveCatalogSkillIndex, } from "./core/provenance.js";
49
51
  import { compareSkill, formatSkillComparison } from "./core/skill-compare.js";
50
- import { applyActivationChange, buildLibraryStateReport, formatActivationPlan, formatLibraryStateReport, planActivationChange, } from "./core/active-set.js";
52
+ import { applyActivationChange, buildLibraryStateReport, formatActivationPlan, formatLibrarySummary, formatLibraryStateReport, planActivationChange, } from "./core/active-set.js";
51
53
  import { applyProjectActivation, formatProjectActivation, planProjectActivation, } from "./core/active-policy.js";
52
54
  import { applySkillAdoption, formatAdoptionPlan, planSkillAdoption, } from "./core/adopt.js";
53
55
  import { formatReviewQueue, mergeReviewQueue, readReviewQueue, setReviewDecision, } from "./core/review-queue.js";
@@ -79,6 +81,7 @@ import { applyIntelligenceFeed, previewIntelligenceFeed, readCachedIntelligenceF
79
81
  import { scanSkillSecurity } from "./core/skill-security.js";
80
82
  import { planApmImportFiles, planOpenPackageImportFiles, } from "./core/ecosystem-import.js";
81
83
  import { buildCompatibilityIntelligence, parseCompatibilityNoticeSet, } from "./core/compatibility-intelligence.js";
84
+ import { ADVANCED_GUIDE, BEGINNER_GUIDE, HIDDEN_FROM_FIRST_SCREEN, } from "./core/cli-guide.js";
82
85
  const collectOption = (value, previous = []) => [
83
86
  ...previous,
84
87
  value,
@@ -255,6 +258,7 @@ async function runSetup(options) {
255
258
  const snapshotId = await applyPreparedCatalogInstall(prepared, {
256
259
  approveRisk: riskApproved,
257
260
  });
261
+ await recordInstalledProfile(prepared);
258
262
  console.log(`\nLoadout installed ${prepared.entries.length} repositories for ${prepared.agents.length} agent(s). Snapshot: ${snapshotId}`);
259
263
  console.log("Next: `loadout status`, `loadout optimize --project .`, or `loadout autopilot --yes` for opt-in daily read-only discovery and update checks.");
260
264
  }
@@ -262,7 +266,7 @@ async function runSetup(options) {
262
266
  reader?.close();
263
267
  }
264
268
  }
265
- const LOADOUT_VERSION = "0.2.2";
269
+ const LOADOUT_VERSION = "0.3.1";
266
270
  function durableSchedulerLauncher() {
267
271
  return [
268
272
  join(dirname(process.execPath), process.platform === "win32" ? "npx.cmd" : "npx"),
@@ -271,6 +275,9 @@ function durableSchedulerLauncher() {
271
275
  ];
272
276
  }
273
277
  const program = new Command();
278
+ function printBeginnerGuide() {
279
+ console.log(BEGINNER_GUIDE);
280
+ }
274
281
  program
275
282
  .name("loadout")
276
283
  .description("The trusted upgrade layer for AI coding agents")
@@ -285,6 +292,14 @@ program
285
292
  // writes its own parse error first, producing two stderr documents and
286
293
  // breaking --json-errors consumers.
287
294
  .configureOutput({ writeErr: () => undefined });
295
+ program
296
+ .command("guide")
297
+ .description("Show the simple, read-only path for using Loadout")
298
+ .action(printBeginnerGuide);
299
+ program
300
+ .command("advanced")
301
+ .description("Explain where to find advanced and maintainer-only commands")
302
+ .action(() => console.log(ADVANCED_GUIDE));
288
303
  program
289
304
  .command("setup")
290
305
  .description("Preview and install a screened skill loadout for detected agents")
@@ -620,11 +635,14 @@ program
620
635
  .command("library")
621
636
  .description("Show separate cache, review, installation, and per-agent activation state")
622
637
  .option("--json", "emit machine-readable JSON")
638
+ .option("--all", "show every managed skill and its source package")
623
639
  .action(async (options) => {
624
640
  const report = await buildLibraryStateReport();
625
641
  console.log(options.json
626
642
  ? JSON.stringify(report, null, 2)
627
- : formatLibraryStateReport(report));
643
+ : options.all
644
+ ? formatLibraryStateReport(report)
645
+ : formatLibrarySummary(report));
628
646
  });
629
647
  program
630
648
  .command("report")
@@ -891,7 +909,17 @@ program
891
909
  .option("--explain", "add deterministic score dimensions, evidence, uncertainty, and remediation")
892
910
  .option("--agents <ids>", "limit explained scores to selected agent ids")
893
911
  .action(async (options) => {
894
- const report = await buildHealthReport({ checkUpdates: options.updates });
912
+ if (options.updates && !options.json)
913
+ console.error("Checking managed repositories (4 at a time, 30s per network operation)…");
914
+ const report = await buildHealthReport({
915
+ updates: options.updates
916
+ ? () => buildUpdatePlan(undefined, {
917
+ onProgress: options.json
918
+ ? undefined
919
+ : ({ completed, total, packageId }) => console.error(`✓ [${completed}/${total}] ${packageId}`),
920
+ })
921
+ : undefined,
922
+ });
895
923
  if (options.agents && !options.explain)
896
924
  throw new Error("--agents requires --explain");
897
925
  const selectedAgents = parseAgentSelection(options.agents);
@@ -985,6 +1013,29 @@ program
985
1013
  const snapshot = await applyRemove(plan, { force: options.force });
986
1014
  console.log(`Removed ${packageId}. Snapshot: ${snapshot}`);
987
1015
  });
1016
+ program
1017
+ .command("uninstall")
1018
+ .description("Preview or completely remove Loadout-managed data")
1019
+ .option("--yes", "remove managed data; otherwise show a preview")
1020
+ .option("--force", "also remove managed files changed outside Loadout")
1021
+ .option("--remove-cli", "also uninstall the global loadout-ai npm command")
1022
+ .action(async (options) => {
1023
+ const plan = await buildUninstallPlan();
1024
+ console.log(formatUninstallPlan(plan));
1025
+ if (!options.yes)
1026
+ return;
1027
+ const result = await applyUninstall(plan, undefined, {
1028
+ force: options.force,
1029
+ });
1030
+ console.log(`Removed ${result.removedPackages} managed package(s), ${result.removedRuntimeTools} runtime tool(s), daily jobs, cache, library, and Loadout state.`);
1031
+ if (options.removeCli) {
1032
+ await uninstallGlobalCli();
1033
+ console.log("Removed the global loadout-ai CLI. Open a new shell before checking the command again.");
1034
+ }
1035
+ else {
1036
+ console.log("The CLI is still installed. Remove it too with: npm uninstall -g loadout-ai");
1037
+ }
1038
+ });
988
1039
  program
989
1040
  .command("recommend")
990
1041
  .description("Recommend catalog packages from local project signals")
@@ -1520,6 +1571,10 @@ program
1520
1571
  : formatCatalogCoverage(coverage));
1521
1572
  return;
1522
1573
  }
1574
+ if (options.json) {
1575
+ console.log(JSON.stringify(rankCatalog(result.catalog), null, 2));
1576
+ return;
1577
+ }
1523
1578
  for (const pkg of rankCatalog(result.catalog)) {
1524
1579
  const topics = pkg.topics?.length ? ` — ${pkg.topics.join(", ")}` : "";
1525
1580
  const updated = pkg.lastUpdatedAt
@@ -2103,21 +2158,27 @@ program
2103
2158
  .option("--timeout <milliseconds>", "real connection timeout", "8000")
2104
2159
  .option("--approve-risk", "approve launching the reviewed pinned MCP artifact for --connect")
2105
2160
  .option("--json", "emit machine-readable JSON")
2161
+ .option("--no-key", "list recipes needing no separately billed AI/model API key")
2162
+ .option("--no-model-key", "clear alias for --no-key; service tokens may still be required")
2163
+ .option("--credential-free", "list recipes needing no credential of any kind")
2106
2164
  .action(async (id, options) => {
2107
2165
  if (!id) {
2108
2166
  if (options.connect || options.verify || options.yes)
2109
2167
  throw new Error("Select an MCP recipe id for this operation");
2110
- const listed = REVIEWED_MCP_RECIPES.map((recipe) => ({
2168
+ const selected = REVIEWED_MCP_RECIPES.filter((recipe) => !((options.key === false || options.modelKey === false) &&
2169
+ recipe.modelApiProviders.length > 0) && !(options.credentialFree && recipe.environment.length > 0));
2170
+ const listed = selected.map((recipe) => ({
2111
2171
  id: recipe.id,
2112
2172
  displayName: recipe.displayName,
2113
2173
  source: recipe.source,
2114
2174
  permissions: recipe.permissions,
2115
2175
  environment: recipe.environment,
2176
+ modelApiProviders: recipe.modelApiProviders,
2116
2177
  }));
2117
2178
  console.log(options.json
2118
2179
  ? JSON.stringify(listed, null, 2)
2119
2180
  : listed
2120
- .map((recipe) => `${recipe.id} — ${recipe.displayName} — env: ${recipe.environment.length ? recipe.environment.join(", ") : "none"}`)
2181
+ .map((recipe) => `${recipe.id} — ${recipe.displayName} — ${recipe.modelApiProviders.length ? `AI API required: ${recipe.modelApiProviders.join(", ")}` : "No AI API key required"} · ${recipe.environment.length ? `${recipe.environment.includes("GITHUB_PERSONAL_ACCESS_TOKEN") ? "GitHub token required" : `service credential required: ${recipe.environment.join(", ")}`}` : "no other credential"}`)
2121
2182
  .join("\n"));
2122
2183
  return;
2123
2184
  }
@@ -2174,6 +2235,7 @@ program
2174
2235
  return;
2175
2236
  }
2176
2237
  const snapshot = await applyMcpConfigPlan(plan.config);
2238
+ await recordInstallTransaction([], [{ packageId: `mcp-recipe:${id}`, plan: plan.config }], snapshot.id);
2177
2239
  console.log(options.json
2178
2240
  ? JSON.stringify({ plan, snapshot }, null, 2)
2179
2241
  : `${formatMcpRecipePlan(plan)}\nConfigured. Snapshot: ${snapshot.id}\nAuthorize the service separately, then run: loadout mcp-recipe ${id} --config ${plan.config.path} --verify`);
@@ -2534,6 +2596,7 @@ program
2534
2596
  const snapshotId = await applyPreparedCatalogInstall(prepared, {
2535
2597
  approveRisk: options.approveRisk,
2536
2598
  });
2599
+ await recordInstalledProfile(prepared);
2537
2600
  console.log(`Installed ${prepared.entries.length} repositories as one transaction. Snapshot: ${snapshotId}`);
2538
2601
  return;
2539
2602
  }
@@ -2615,23 +2678,104 @@ program
2615
2678
  });
2616
2679
  program
2617
2680
  .command("update")
2618
- .description("Plan updates, or apply one explicitly selected package update")
2681
+ .description("Check the saved profile and every managed package for updates")
2619
2682
  .option("--json", "emit machine-readable JSON")
2620
- .option("--apply", "apply the selected update")
2683
+ .option("--yes", "apply reviewed profile drift and safe screened package updates")
2684
+ .option("--apply", "legacy alias for --yes with --package")
2621
2685
  .option("--package <id>", "managed package id to update")
2622
2686
  .option("--approve-risk", "explicitly approve an update containing blocked safety findings")
2623
2687
  .action(async (options) => {
2624
- if (options.apply) {
2625
- if (!options.package)
2626
- throw new Error("--apply requires --package <id>");
2688
+ const applying = Boolean(options.apply || options.yes);
2689
+ if (options.apply && !options.package)
2690
+ throw new Error("--apply requires --package <id>");
2691
+ if (applying && options.package) {
2627
2692
  const result = await applyPackageUpdate(options.package, {
2628
2693
  approveRisk: options.approveRisk,
2629
2694
  });
2630
- console.log(`Updated ${options.package} to ${result.commit}. Snapshot: ${result.snapshotId}`);
2695
+ console.log(options.json
2696
+ ? JSON.stringify({ packageId: options.package, ...result }, null, 2)
2697
+ : `Updated ${options.package} to ${result.commit}. Snapshot: ${result.snapshotId}`);
2631
2698
  return;
2632
2699
  }
2633
- const plans = await buildUpdatePlan();
2634
- console.log(options.json ? JSON.stringify(plans, null, 2) : formatUpdatePlan(plans));
2700
+ const catalog = await loadEffectiveCatalog();
2701
+ let profile = await evaluateInstalledProfile(catalog);
2702
+ let profileSnapshotId;
2703
+ const appliedPackages = [];
2704
+ const failedPackages = [];
2705
+ if (applying && profile.installed && profile.needsRefresh) {
2706
+ const saved = (await readInstallState()).profile;
2707
+ const prepared = await prepareCatalogInstall({
2708
+ mode: saved.mode,
2709
+ ...(saved.packageIds ? { packageIds: saved.packageIds } : {}),
2710
+ }, {
2711
+ requestedAgents: saved.agents,
2712
+ catalog,
2713
+ access: { modelApis: [] },
2714
+ onProgress: printSetupProgress,
2715
+ });
2716
+ profileSnapshotId = await applyPreparedCatalogInstall(prepared, {
2717
+ approveRisk: options.approveRisk,
2718
+ });
2719
+ await recordInstalledProfile(prepared);
2720
+ if (!options.json)
2721
+ console.log(`Applied reviewed ${saved.mode} profile changes. Snapshot: ${profileSnapshotId}`);
2722
+ profile = await evaluateInstalledProfile(catalog);
2723
+ }
2724
+ if (!options.json)
2725
+ console.error("Checking managed repositories (4 at a time, 30s per network operation)…");
2726
+ let plans = await buildUpdatePlan(undefined, {
2727
+ packageId: options.package,
2728
+ onProgress: options.json
2729
+ ? undefined
2730
+ : ({ completed, total, packageId }) => console.error(`✓ [${completed}/${total}] ${packageId}`),
2731
+ });
2732
+ if (options.package && plans.length === 0)
2733
+ throw new Error(`No Loadout-managed installation named '${options.package}' was found. Run \`loadout list\` to see tracked packages.`);
2734
+ if (applying) {
2735
+ const safe = selectSafeAutomaticUpdates(plans);
2736
+ for (const plan of safe)
2737
+ try {
2738
+ await applyPackageUpdate(plan.packageId);
2739
+ appliedPackages.push(plan.packageId);
2740
+ }
2741
+ catch (error) {
2742
+ failedPackages.push({
2743
+ packageId: plan.packageId,
2744
+ error: error instanceof Error ? error.message : String(error),
2745
+ });
2746
+ }
2747
+ plans = await buildUpdatePlan(undefined, {
2748
+ onProgress: options.json
2749
+ ? undefined
2750
+ : ({ completed, total, packageId }) => console.error(`✓ [${completed}/${total}] ${packageId}`),
2751
+ });
2752
+ if (!options.json) {
2753
+ console.log(appliedPackages.length
2754
+ ? `Updated safely: ${appliedPackages.join(", ")}`
2755
+ : "No safe active package update needed applying.");
2756
+ for (const item of failedPackages)
2757
+ console.log(`Could not update ${item.packageId}: ${item.error}`);
2758
+ const held = plans.filter((plan) => plan.status === "update-available" &&
2759
+ (plan.approvalRequired || plan.disabledAgents?.length));
2760
+ if (held.length)
2761
+ console.log(`Held for explicit review: ${held.map((item) => item.packageId).join(", ")}`);
2762
+ }
2763
+ }
2764
+ console.log(options.json
2765
+ ? JSON.stringify({
2766
+ profile,
2767
+ packages: plans,
2768
+ ...(applying
2769
+ ? {
2770
+ applied: {
2771
+ profileSnapshotId,
2772
+ packages: appliedPackages,
2773
+ failures: failedPackages,
2774
+ },
2775
+ }
2776
+ : {}),
2777
+ }, null, 2)
2778
+ : `${formatInstalledProfileStatus(profile)}\n\n${formatUpdatePlan(plans)}`);
2635
2779
  });
2636
2780
  program
2637
2781
  .command("convert")
@@ -2732,7 +2876,14 @@ program
2732
2876
  });
2733
2877
  await handle.close();
2734
2878
  });
2735
- program.action(() => runSetup({ package: [] }));
2879
+ for (const command of program.commands)
2880
+ // Commander supports `hidden` when a command is created. These commands are
2881
+ // registered by separate feature blocks, so use the same runtime flag here
2882
+ // rather than `Option#hideHelp`, which does not exist on Command objects.
2883
+ if (HIDDEN_FROM_FIRST_SCREEN.has(command.name()))
2884
+ command._hidden = true;
2885
+ program.addHelpText("after", "\nStart here: `loadout guide` shows the safe everyday path. `loadout advanced` lists the retained maintainer and integration commands.\n");
2886
+ program.action(printBeginnerGuide);
2736
2887
  try {
2737
2888
  await recoverPendingTransactions();
2738
2889
  await program.parseAsync();
@@ -156,9 +156,13 @@ async function activeTreeFiles(targets) {
156
156
  export async function buildLibraryStateReport() {
157
157
  const state = await readInstallState();
158
158
  const records = allActivationRecords(state.installs, state.activations ?? []);
159
+ const repositories = Object.fromEntries(state.installs
160
+ .filter((install) => Boolean(install.repository))
161
+ .map((install) => [install.packageId, install.repository]));
159
162
  return {
160
163
  generatedAt: new Date().toISOString(),
161
164
  records,
165
+ repositories,
162
166
  counts: {
163
167
  packages: new Set(records.map((record) => record.packageId)).size,
164
168
  downloaded: records.filter((record) => record.cacheState === "downloaded")
@@ -357,17 +361,48 @@ export async function applyActivationChange(plan) {
357
361
  });
358
362
  return applied.snapshotId;
359
363
  }
364
+ function formatLibraryHeadline(report) {
365
+ return `Managed library: ${report.counts.packages} package(s), ${report.counts.active} active, ${report.counts.disabled} disabled, ${report.counts.downloaded} cached`;
366
+ }
367
+ function countByAgent(report, activationState) {
368
+ const counts = new Map();
369
+ for (const record of report.records)
370
+ if (record.activationState === activationState)
371
+ counts.set(record.agent, (counts.get(record.agent) ?? 0) + 1);
372
+ return [...counts]
373
+ .sort(([left], [right]) => left.localeCompare(right))
374
+ .map(([agent, count]) => `${agent} ${count}`)
375
+ .join(", ");
376
+ }
377
+ /** Concise default for people with a broad Maximum library. */
378
+ export function formatLibrarySummary(report) {
379
+ if (!report.records.length)
380
+ return "No Loadout-managed skill activations exist.";
381
+ return [
382
+ formatLibraryHeadline(report),
383
+ `Active now: ${countByAgent(report, "active") || "none"}`,
384
+ `Disabled library: ${countByAgent(report, "disabled") || "none"}`,
385
+ "Disabled library skills are reviewed copies, not extra agent context.",
386
+ ...(report.migrationPending
387
+ ? [
388
+ `Migration pending: ${report.migrationPending} active installation(s) will be cached on first disable.`,
389
+ ]
390
+ : []),
391
+ "Use `loadout library --all` for every skill, source package, and activation state.",
392
+ ].join("\n");
393
+ }
394
+ /** Full per-skill provenance report for audits and explicit investigation. */
360
395
  export function formatLibraryStateReport(report) {
361
396
  if (!report.records.length)
362
397
  return "No Loadout-managed skill activations exist.";
363
398
  return [
364
- `Managed library: ${report.counts.packages} package(s), ${report.counts.active} active, ${report.counts.disabled} disabled, ${report.counts.downloaded} cached`,
399
+ formatLibraryHeadline(report),
365
400
  ...(report.migrationPending
366
401
  ? [
367
402
  `Migration pending: ${report.migrationPending} active installation(s) will be cached on first disable.`,
368
403
  ]
369
404
  : []),
370
- ...report.records.map((record) => `${record.packageId}${record.unitId ? `/${record.unitId}` : ""} — ${record.agent} — cache:${record.cacheState} review:${record.reviewState} install:${record.installationState} activation:${record.activationState} — ${record.targets.length} target(s)`),
405
+ ...report.records.map((record) => `${record.packageId}${record.unitId ? `/${record.unitId}` : ""}${report.repositories[record.packageId] ? ` (${report.repositories[record.packageId]})` : ""} — ${record.agent} — cache:${record.cacheState} review:${record.reviewState} install:${record.installationState} activation:${record.activationState} — ${record.targets.length} target(s)`),
371
406
  ].join("\n");
372
407
  }
373
408
  export function formatActivationPlan(plan) {