loadout-ai 0.2.3 → 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 +29 -0
- package/MASTER_PLAN.md +68 -33
- package/README.md +33 -1
- package/dist/src/cli.js +168 -17
- package/dist/src/core/active-set.js +37 -2
- package/dist/src/core/cli-guide.js +101 -0
- package/dist/src/core/completion.js +3 -0
- package/dist/src/core/mcp-recipes.js +21 -0
- package/dist/src/core/profile-state.js +101 -0
- package/dist/src/core/remove.js +6 -7
- package/dist/src/core/runtime-tools.js +5 -0
- package/dist/src/core/scheduler.js +2 -2
- package/dist/src/core/state.js +5 -2
- package/dist/src/core/uninstall.js +109 -0
- package/dist/src/core/update.js +87 -58
- package/dist/src/shared/schemas.js +12 -0
- package/docs/TESTING.md +25 -2
- package/docs/USER_TEST_GUIDE.md +176 -0
- package/docs/plans/2026-07-18-release-0.3.md +42 -0
- package/docs/superpowers/plans/2026-07-18-cli-ux-polish.md +86 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
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
|
+
|
|
3
32
|
## 0.2.3 - 2026-07-17
|
|
4
33
|
|
|
5
34
|
### 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
|
|
154
|
+
### 3.2 Deferred product exploration (not current launch scope)
|
|
95
155
|
|
|
96
|
-
These are
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
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.
|
|
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.
|
|
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
|
-
:
|
|
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
|
-
|
|
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
|
|
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} —
|
|
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("
|
|
2681
|
+
.description("Check the saved profile and every managed package for updates")
|
|
2619
2682
|
.option("--json", "emit machine-readable JSON")
|
|
2620
|
-
.option("--
|
|
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
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
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(
|
|
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
|
|
2634
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|