magus-cli 7.7.0 → 7.9.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 +1 -1
- package/package.json +4 -4
- package/src/__tests__/blackbox-parity.test.ts +45 -26
- package/src/__tests__/blackbox-plugin-state.test.ts +25 -6
- package/src/__tests__/blind/plugin-deps/doctor.test.ts +622 -0
- package/src/__tests__/blind/plugin-deps/helpers.ts +340 -0
- package/src/__tests__/blind/plugin-deps/hook.test.ts +328 -0
- package/src/__tests__/blind/plugin-deps/probe.test.ts +694 -0
- package/src/__tests__/blind/plugin-deps/read-only.test.ts +96 -0
- package/src/__tests__/blind/plugin-deps/schema.test.ts +390 -0
- package/src/__tests__/blind-780-cli-tools.test.ts +796 -0
- package/src/__tests__/blind-780-deprecated.test.ts +631 -0
- package/src/__tests__/blind-780-failure-reasons.test.ts +1004 -0
- package/src/__tests__/blind-780-plugin-resolution.test.ts +431 -0
- package/src/__tests__/blind-780-review2.test.ts +1178 -0
- package/src/__tests__/cli-apply-seams.test.ts +2 -67
- package/src/__tests__/cli-tool-commands.test.ts +131 -6
- package/src/__tests__/cli-tools-adapter.test.ts +52 -2
- package/src/__tests__/cli-tools-plugin-managed.test.tsx +83 -0
- package/src/__tests__/cli-update-view.test.ts +3 -3
- package/src/__tests__/deprecated-remove.test.ts +827 -0
- package/src/__tests__/deps-adapter.test.ts +191 -0
- package/src/__tests__/deps-check-hook.test.ts +220 -0
- package/src/__tests__/deps-plan.test.ts +896 -0
- package/src/__tests__/deps-report.test.ts +197 -0
- package/src/__tests__/doctor-bins.test.ts +134 -36
- package/src/__tests__/doctor-json.test.ts +261 -0
- package/src/__tests__/doctor.test.ts +159 -1
- package/src/__tests__/failure-popup-review2.test.tsx +896 -0
- package/src/__tests__/failure-popup.test.tsx +419 -0
- package/src/__tests__/final-fixes-780.test.tsx +740 -0
- package/src/__tests__/fixtures/bins.ts +55 -0
- package/src/__tests__/format-update-outcome.test.ts +6 -32
- package/src/__tests__/gitignore-profile.test.ts +1 -9
- package/src/__tests__/install-plan.test.ts +15 -1
- package/src/__tests__/marketplace-refresh.test.ts +9 -3
- package/src/__tests__/models-screen-state.test.ts +18 -40
- package/src/__tests__/no-shell-spawn.test.ts +77 -0
- package/src/__tests__/npm-package-name.test.ts +6 -9
- package/src/__tests__/plugin-requires.test.ts +138 -153
- package/src/__tests__/plugin-resolution.test.ts +52 -13
- package/src/__tests__/plugin-row-text.test.ts +30 -37
- package/src/__tests__/profile-dependencies.test.ts +7 -0
- package/src/__tests__/profile-startup-old-format.test.ts +0 -3
- package/src/__tests__/profile-startup.test.ts +40 -12
- package/src/__tests__/requires-probe.test.ts +858 -0
- package/src/__tests__/resolver.test.ts +129 -52
- package/src/__tests__/run.test.ts +108 -0
- package/src/__tests__/stale-load-repair.test.ts +31 -23
- package/src/__tests__/tool-probe.test.ts +1 -1
- package/src/__tests__/toolchain.test.ts +123 -52
- package/src/__tests__/update-apply.test.ts +13 -29
- package/src/__tests__/update-plan.test.ts +24 -18
- package/src/__tests__/useStaleLoadModal.test.ts +1 -57
- package/src/__tests__/version-unknown-state.test.ts +15 -17
- package/src/cli/ansi.ts +2 -7
- package/src/cli/deps-view.ts +213 -0
- package/src/cli/doctor.ts +320 -54
- package/src/cli/install.ts +55 -71
- package/src/cli/profile.ts +20 -2
- package/src/cli/router.ts +6 -2
- package/src/cli/update-view.ts +58 -15
- package/src/cli/update.ts +55 -12
- package/src/data/cli-tools.ts +24 -9
- package/src/services/active-closure.ts +3 -3
- package/src/services/catalog-cache-store.ts +8 -6
- package/src/services/claude-cli.ts +106 -19
- package/src/services/claude-settings.ts +88 -384
- package/src/services/cli-tool-commands.ts +104 -3
- package/src/services/deps-plan.ts +962 -0
- package/src/services/deps-report.ts +265 -0
- package/src/services/doctor-bins.ts +115 -43
- package/src/services/install-plan.ts +11 -1
- package/src/services/local-marketplace.ts +12 -206
- package/src/services/marketplace-refresh.ts +4 -4
- package/src/services/plugin-env-needs.ts +69 -0
- package/src/services/plugin-manager.ts +35 -37
- package/src/services/plugin-requires.ts +93 -162
- package/src/services/plugin-resolution.ts +34 -42
- package/src/services/profile-activate.ts +1 -2
- package/src/services/profile-startup.ts +18 -43
- package/src/services/profile-state.ts +72 -2
- package/src/services/resolver.ts +263 -59
- package/src/services/tool-probe.ts +18 -25
- package/src/services/toolchain.ts +117 -111
- package/src/services/update-engine.ts +345 -45
- package/src/services/update-plan.ts +296 -36
- package/src/shared/plugin/hooks/bun-missing.sh +39 -0
- package/src/shared/plugin/hooks/deps-check.ts +224 -0
- package/src/shared/plugin/hooks/lib/requires-probe.ts +1183 -0
- package/src/types/index.ts +60 -16
- package/src/ui/App.tsx +81 -171
- package/src/ui/adapters/cliToolsAdapter.ts +252 -12
- package/src/ui/adapters/depsAdapter.ts +129 -0
- package/src/ui/adapters/failureReport.ts +137 -0
- package/src/ui/adapters/pluginsAdapter.ts +297 -45
- package/src/ui/adapters/startupReports.ts +260 -0
- package/src/ui/components/modals/FailureListModal.tsx +231 -0
- package/src/ui/components/modals/MessageModal.tsx +7 -12
- package/src/ui/components/modals/ModalContainer.tsx +10 -5
- package/src/ui/components/modals/index.ts +1 -0
- package/src/ui/hooks/startupQueue.ts +56 -0
- package/src/ui/hooks/useGitignoreModal.ts +30 -52
- package/src/ui/hooks/useProfileStartupModal.ts +119 -142
- package/src/ui/hooks/useStaleLoadModal.ts +35 -65
- package/src/ui/keymap.ts +14 -1
- package/src/ui/renderers/cliToolRenderers.tsx +135 -2
- package/src/ui/renderers/mcpRenderers.tsx +110 -3
- package/src/ui/renderers/pluginRenderers.tsx +77 -6
- package/src/ui/renderers/pluginRowText.ts +25 -25
- package/src/ui/renderers/pluginStatusText.ts +18 -1
- package/src/ui/screens/AliasScreen.tsx +38 -45
- package/src/ui/screens/CliToolsScreen.tsx +199 -98
- package/src/ui/screens/EnvVarsScreen.tsx +17 -5
- package/src/ui/screens/GitignoreScreen.tsx +48 -67
- package/src/ui/screens/McpRegistryScreen.tsx +67 -38
- package/src/ui/screens/McpScreen.tsx +46 -19
- package/src/ui/screens/ModelsScreen.tsx +46 -86
- package/src/ui/screens/PluginsScreen.tsx +315 -259
- package/src/ui/screens/ProfilesScreen.tsx +79 -40
- package/src/ui/screens/SkillsScreen.tsx +83 -80
- package/src/ui/screens/StylesScreen.tsx +211 -275
- package/src/ui/state/AppContext.tsx +66 -44
- package/src/ui/state/DimensionsContext.tsx +3 -22
- package/src/ui/state/reducer.ts +12 -100
- package/src/ui/state/types.ts +48 -69
- package/src/utils/command-utils.ts +4 -5
- package/src/utils/run.ts +123 -52
- package/src/utils/string-utils.ts +9 -0
- package/src/utils/user-path.ts +60 -0
- package/src/__tests__/go-module.test.ts +0 -43
- package/src/__tests__/notices-rules.test.ts +0 -476
- package/src/__tests__/notices.test.tsx +0 -604
- package/src/__tests__/plugin-setup.test.ts +0 -1016
- package/src/__tests__/shell-script-callers.test.ts +0 -92
- package/src/__tests__/styles-status-line.test.ts +0 -72
- package/src/__tests__/version-snapshot.test.ts +0 -212
- package/src/services/plugin-setup.ts +0 -965
- package/src/services/version-snapshot.ts +0 -189
- package/src/utils/go-module.ts +0 -36
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* version-snapshot.ts — detect plugin changes that happened outside magus.
|
|
3
|
-
*
|
|
4
|
-
* Plugins get updated by things magus does not control: Claude Code's own
|
|
5
|
-
* `plugin install`/`update`, or a direct CLI
|
|
6
|
-
* call. All of them write `installed_plugins.json`; none of them tell the user
|
|
7
|
-
* anything the next time magus opens, so versions move underfoot silently.
|
|
8
|
-
*
|
|
9
|
-
* Rather than log what magus itself did — which misses every other source —
|
|
10
|
-
* this records what magus last *saw* installed and diffs against reality on
|
|
11
|
-
* load. Any change is reported, regardless of who made it.
|
|
12
|
-
*
|
|
13
|
-
* The snapshot is advisory UI state, kept separate from the update-check cache
|
|
14
|
-
* so a TTL expiry or a corrupt cache can never make plugins look changed.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { promises as fs } from "node:fs";
|
|
18
|
-
import os from "node:os";
|
|
19
|
-
import path from "node:path";
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Sentinel recorded by Claude Code for a plugin that is installed but whose
|
|
23
|
-
* version is unknown — every plugin in the official Anthropic marketplace uses
|
|
24
|
-
* it. It is not a real version and must never be rendered as one.
|
|
25
|
-
*/
|
|
26
|
-
export const UNKNOWN_VERSION = "0.0.0";
|
|
27
|
-
|
|
28
|
-
/** True when a version string carries no usable information. */
|
|
29
|
-
export function isKnownVersion(v: string | undefined): v is string {
|
|
30
|
-
return !!v && v !== UNKNOWN_VERSION;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export type PluginChangeKind = "installed" | "updated";
|
|
34
|
-
|
|
35
|
-
export interface PluginChange {
|
|
36
|
-
pluginId: string;
|
|
37
|
-
kind: PluginChangeKind;
|
|
38
|
-
/** Previous version. Only set for "updated", and always a known version. */
|
|
39
|
-
from?: string;
|
|
40
|
-
/** Current version. May be UNKNOWN_VERSION — callers must not render it raw. */
|
|
41
|
-
to: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface SnapshotFile {
|
|
45
|
-
/**
|
|
46
|
-
* Legacy flat baseline: pluginId -> version, with no record of which project
|
|
47
|
-
* it was observed from. Kept only so an existing file can be migrated.
|
|
48
|
-
*/
|
|
49
|
-
seen?: Record<string, string>;
|
|
50
|
-
/**
|
|
51
|
-
* projectPath -> (pluginId -> installedVersion) as of the last time magus
|
|
52
|
-
* rendered that project.
|
|
53
|
-
*
|
|
54
|
-
* Versions are per-project (installed_plugins.json keys installs by
|
|
55
|
-
* projectPath), but this file is global. A single flat map therefore let two
|
|
56
|
-
* projects legitimately on different versions overwrite each other's
|
|
57
|
-
* baseline, and every differing plugin was badged "updated" on the next
|
|
58
|
-
* render — a change that never happened. Keying by project removes the
|
|
59
|
-
* cross-talk entirely.
|
|
60
|
-
*/
|
|
61
|
-
byProject?: Record<string, Record<string, string>>;
|
|
62
|
-
/** Set on the run that first created the baseline. */
|
|
63
|
-
seededAt: string;
|
|
64
|
-
updatedAt: string;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Honour CLAUDE_CONFIG_DIR — the same override Claude Code itself uses.
|
|
69
|
-
*
|
|
70
|
-
* `os.homedir()` reads the passwd database on macOS and ignores $HOME, so a
|
|
71
|
-
* test that only overrides HOME writes to the operator's real snapshot file.
|
|
72
|
-
* That is not hypothetical: it happened while building the tests for this fix.
|
|
73
|
-
*/
|
|
74
|
-
function snapshotPath(): string {
|
|
75
|
-
const configDir =
|
|
76
|
-
process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
|
|
77
|
-
return path.join(configDir, "magus-version-snapshot.json");
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async function read(): Promise<SnapshotFile | null> {
|
|
81
|
-
try {
|
|
82
|
-
const raw = await fs.readFile(snapshotPath(), "utf-8");
|
|
83
|
-
const data = JSON.parse(raw) as SnapshotFile;
|
|
84
|
-
if (!data) return null;
|
|
85
|
-
const hasFlat = typeof data.seen === "object" && data.seen !== null;
|
|
86
|
-
const hasByProject =
|
|
87
|
-
typeof data.byProject === "object" && data.byProject !== null;
|
|
88
|
-
if (!hasFlat && !hasByProject) return null;
|
|
89
|
-
return data;
|
|
90
|
-
} catch {
|
|
91
|
-
return null; // absent or corrupt — treated as "no baseline yet"
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Diff what is installed now against the previous snapshot.
|
|
97
|
-
*
|
|
98
|
-
* Three cases are deliberately silent, because each reflects magus's own
|
|
99
|
-
* knowledge changing rather than anything happening to the user's plugins:
|
|
100
|
-
*
|
|
101
|
-
* 1. No baseline (first run). Every plugin would otherwise be reported as
|
|
102
|
-
* newly installed on the very first launch — a guaranteed false flood that
|
|
103
|
-
* trains the user to ignore the badge. The first run seeds and says nothing.
|
|
104
|
-
*
|
|
105
|
-
* 2. A version moving to or from UNKNOWN_VERSION. That is magus learning
|
|
106
|
-
* (or losing) a version it could not previously resolve — notably after
|
|
107
|
-
* v4.22.0 taught it to read the registry — not the plugin changing.
|
|
108
|
-
*
|
|
109
|
-
* 3. A plugin disappearing. Uninstalls are visible elsewhere; nothing to badge.
|
|
110
|
-
*
|
|
111
|
-
* A downgrade IS reported: rolling back is a real change worth surfacing.
|
|
112
|
-
*/
|
|
113
|
-
export function diffVersions(
|
|
114
|
-
previous: Record<string, string> | null,
|
|
115
|
-
current: Record<string, string>,
|
|
116
|
-
): PluginChange[] {
|
|
117
|
-
if (!previous) return [];
|
|
118
|
-
|
|
119
|
-
const changes: PluginChange[] = [];
|
|
120
|
-
for (const [pluginId, to] of Object.entries(current)) {
|
|
121
|
-
const from = previous[pluginId];
|
|
122
|
-
|
|
123
|
-
// Never seen before → newly installed, not an update. There is no
|
|
124
|
-
// meaningful "from", and `to` may legitimately be UNKNOWN_VERSION.
|
|
125
|
-
if (from === undefined) {
|
|
126
|
-
changes.push({ pluginId, kind: "installed", to });
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (from === to) continue;
|
|
131
|
-
|
|
132
|
-
// One side carries no real version: we learned or lost it, nothing moved.
|
|
133
|
-
if (!isKnownVersion(from) || !isKnownVersion(to)) continue;
|
|
134
|
-
|
|
135
|
-
changes.push({ pluginId, kind: "updated", from, to });
|
|
136
|
-
}
|
|
137
|
-
return changes;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Load the baseline for one project, or null when that project has none.
|
|
142
|
-
*
|
|
143
|
-
* A project seen for the first time returns null, so `diffVersions` stays
|
|
144
|
-
* silent — the same rule as a first ever run. That is deliberate: opening
|
|
145
|
-
* magus in a new project must not badge every plugin as "updated" just
|
|
146
|
-
* because this file has never seen that path before.
|
|
147
|
-
*
|
|
148
|
-
* A pre-migration flat file is adopted as the baseline for whichever project
|
|
149
|
-
* asks first, which is the closest thing to the truth it contains.
|
|
150
|
-
*/
|
|
151
|
-
export async function loadSeenVersions(
|
|
152
|
-
projectPath: string,
|
|
153
|
-
): Promise<Record<string, string> | null> {
|
|
154
|
-
const data = await read();
|
|
155
|
-
if (!data) return null;
|
|
156
|
-
const scoped = data.byProject?.[projectPath];
|
|
157
|
-
if (scoped) return scoped;
|
|
158
|
-
// Legacy flat baseline, project unknown — use it once, then it is replaced
|
|
159
|
-
// by per-project entries on save.
|
|
160
|
-
if (data.seen && !data.byProject) return data.seen;
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Persist what is installed now, so the same change is not reported twice.
|
|
166
|
-
* Best-effort: a failure here must never break the UI — it only means the badge
|
|
167
|
-
* shows again next launch.
|
|
168
|
-
*/
|
|
169
|
-
export async function saveSeenVersions(
|
|
170
|
-
projectPath: string,
|
|
171
|
-
current: Record<string, string>,
|
|
172
|
-
): Promise<void> {
|
|
173
|
-
try {
|
|
174
|
-
const file = snapshotPath();
|
|
175
|
-
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
176
|
-
const existing = await read();
|
|
177
|
-
const now = new Date().toISOString();
|
|
178
|
-
const payload: SnapshotFile = {
|
|
179
|
-
// Other projects' baselines are preserved — writing only this project's
|
|
180
|
-
// slot is the entire point of the fix.
|
|
181
|
-
byProject: { ...(existing?.byProject ?? {}), [projectPath]: current },
|
|
182
|
-
seededAt: existing?.seededAt ?? now,
|
|
183
|
-
updatedAt: now,
|
|
184
|
-
};
|
|
185
|
-
await fs.writeFile(file, JSON.stringify(payload, null, 2), "utf-8");
|
|
186
|
-
} catch {
|
|
187
|
-
// ignore
|
|
188
|
-
}
|
|
189
|
-
}
|
package/src/utils/go-module.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Go module path → installed binary name.
|
|
3
|
-
*
|
|
4
|
-
* `go install` names the binary after the last path element of the module
|
|
5
|
-
* path, EXCEPT when that element is a major-version suffix: the module
|
|
6
|
-
* `github.com/MadAppGang/tmux-mcp/v2` installs `tmux-mcp`, not `v2`. Taking a
|
|
7
|
-
* plain basename therefore points every later check at a binary that never
|
|
8
|
-
* exists — presence probes fail, so the module is reinstalled on every run,
|
|
9
|
-
* doctor reports it missing, and the pin comparison never runs at all.
|
|
10
|
-
*
|
|
11
|
-
* Only the Go rule lives here. npm and cargo share the `name@version` syntax
|
|
12
|
-
* but not the suffix convention — an npm scoped package can legitimately be
|
|
13
|
-
* called `@scope/v2` — so this must not be folded into the generic spec parser.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Trailing major-version suffix per the Go modules spec: `/vN` with N >= 2.
|
|
18
|
-
* Anchored to the END of the path with nothing after `vN`, so a segment that
|
|
19
|
-
* merely starts with "v2" (`github.com/x/v2tool`) is a real name, not a suffix.
|
|
20
|
-
* v0 and v1 are never written as a path suffix. The alternation, not a plain
|
|
21
|
-
* `[2-9][0-9]*`, because that shorthand rejects v10–v19 — a first digit of 1
|
|
22
|
-
* is only excluded when it is the ONLY digit.
|
|
23
|
-
*/
|
|
24
|
-
const MAJOR_VERSION_SUFFIX = /\/v(?:[2-9]|[1-9][0-9]+)$/;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* The binary `go install <modulePath>` produces.
|
|
28
|
-
*
|
|
29
|
-
* Takes the MODULE PATH — the part before any `@version` — so callers that
|
|
30
|
-
* hold a full `path@version` spec split it first. An empty or single-segment
|
|
31
|
-
* input is returned unchanged.
|
|
32
|
-
*/
|
|
33
|
-
export function goBinaryName(modulePath: string): string {
|
|
34
|
-
const withoutSuffix = modulePath.replace(MAJOR_VERSION_SUFFIX, "");
|
|
35
|
-
return withoutSuffix.split("/").pop() || withoutSuffix;
|
|
36
|
-
}
|