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.
Files changed (140) hide show
  1. package/README.md +1 -1
  2. package/package.json +4 -4
  3. package/src/__tests__/blackbox-parity.test.ts +45 -26
  4. package/src/__tests__/blackbox-plugin-state.test.ts +25 -6
  5. package/src/__tests__/blind/plugin-deps/doctor.test.ts +622 -0
  6. package/src/__tests__/blind/plugin-deps/helpers.ts +340 -0
  7. package/src/__tests__/blind/plugin-deps/hook.test.ts +328 -0
  8. package/src/__tests__/blind/plugin-deps/probe.test.ts +694 -0
  9. package/src/__tests__/blind/plugin-deps/read-only.test.ts +96 -0
  10. package/src/__tests__/blind/plugin-deps/schema.test.ts +390 -0
  11. package/src/__tests__/blind-780-cli-tools.test.ts +796 -0
  12. package/src/__tests__/blind-780-deprecated.test.ts +631 -0
  13. package/src/__tests__/blind-780-failure-reasons.test.ts +1004 -0
  14. package/src/__tests__/blind-780-plugin-resolution.test.ts +431 -0
  15. package/src/__tests__/blind-780-review2.test.ts +1178 -0
  16. package/src/__tests__/cli-apply-seams.test.ts +2 -67
  17. package/src/__tests__/cli-tool-commands.test.ts +131 -6
  18. package/src/__tests__/cli-tools-adapter.test.ts +52 -2
  19. package/src/__tests__/cli-tools-plugin-managed.test.tsx +83 -0
  20. package/src/__tests__/cli-update-view.test.ts +3 -3
  21. package/src/__tests__/deprecated-remove.test.ts +827 -0
  22. package/src/__tests__/deps-adapter.test.ts +191 -0
  23. package/src/__tests__/deps-check-hook.test.ts +220 -0
  24. package/src/__tests__/deps-plan.test.ts +896 -0
  25. package/src/__tests__/deps-report.test.ts +197 -0
  26. package/src/__tests__/doctor-bins.test.ts +134 -36
  27. package/src/__tests__/doctor-json.test.ts +261 -0
  28. package/src/__tests__/doctor.test.ts +159 -1
  29. package/src/__tests__/failure-popup-review2.test.tsx +896 -0
  30. package/src/__tests__/failure-popup.test.tsx +419 -0
  31. package/src/__tests__/final-fixes-780.test.tsx +740 -0
  32. package/src/__tests__/fixtures/bins.ts +55 -0
  33. package/src/__tests__/format-update-outcome.test.ts +6 -32
  34. package/src/__tests__/gitignore-profile.test.ts +1 -9
  35. package/src/__tests__/install-plan.test.ts +15 -1
  36. package/src/__tests__/marketplace-refresh.test.ts +9 -3
  37. package/src/__tests__/models-screen-state.test.ts +18 -40
  38. package/src/__tests__/no-shell-spawn.test.ts +77 -0
  39. package/src/__tests__/npm-package-name.test.ts +6 -9
  40. package/src/__tests__/plugin-requires.test.ts +138 -153
  41. package/src/__tests__/plugin-resolution.test.ts +52 -13
  42. package/src/__tests__/plugin-row-text.test.ts +30 -37
  43. package/src/__tests__/profile-dependencies.test.ts +7 -0
  44. package/src/__tests__/profile-startup-old-format.test.ts +0 -3
  45. package/src/__tests__/profile-startup.test.ts +40 -12
  46. package/src/__tests__/requires-probe.test.ts +858 -0
  47. package/src/__tests__/resolver.test.ts +129 -52
  48. package/src/__tests__/run.test.ts +108 -0
  49. package/src/__tests__/stale-load-repair.test.ts +31 -23
  50. package/src/__tests__/tool-probe.test.ts +1 -1
  51. package/src/__tests__/toolchain.test.ts +123 -52
  52. package/src/__tests__/update-apply.test.ts +13 -29
  53. package/src/__tests__/update-plan.test.ts +24 -18
  54. package/src/__tests__/useStaleLoadModal.test.ts +1 -57
  55. package/src/__tests__/version-unknown-state.test.ts +15 -17
  56. package/src/cli/ansi.ts +2 -7
  57. package/src/cli/deps-view.ts +213 -0
  58. package/src/cli/doctor.ts +320 -54
  59. package/src/cli/install.ts +55 -71
  60. package/src/cli/profile.ts +20 -2
  61. package/src/cli/router.ts +6 -2
  62. package/src/cli/update-view.ts +58 -15
  63. package/src/cli/update.ts +55 -12
  64. package/src/data/cli-tools.ts +24 -9
  65. package/src/services/active-closure.ts +3 -3
  66. package/src/services/catalog-cache-store.ts +8 -6
  67. package/src/services/claude-cli.ts +106 -19
  68. package/src/services/claude-settings.ts +88 -384
  69. package/src/services/cli-tool-commands.ts +104 -3
  70. package/src/services/deps-plan.ts +962 -0
  71. package/src/services/deps-report.ts +265 -0
  72. package/src/services/doctor-bins.ts +115 -43
  73. package/src/services/install-plan.ts +11 -1
  74. package/src/services/local-marketplace.ts +12 -206
  75. package/src/services/marketplace-refresh.ts +4 -4
  76. package/src/services/plugin-env-needs.ts +69 -0
  77. package/src/services/plugin-manager.ts +35 -37
  78. package/src/services/plugin-requires.ts +93 -162
  79. package/src/services/plugin-resolution.ts +34 -42
  80. package/src/services/profile-activate.ts +1 -2
  81. package/src/services/profile-startup.ts +18 -43
  82. package/src/services/profile-state.ts +72 -2
  83. package/src/services/resolver.ts +263 -59
  84. package/src/services/tool-probe.ts +18 -25
  85. package/src/services/toolchain.ts +117 -111
  86. package/src/services/update-engine.ts +345 -45
  87. package/src/services/update-plan.ts +296 -36
  88. package/src/shared/plugin/hooks/bun-missing.sh +39 -0
  89. package/src/shared/plugin/hooks/deps-check.ts +224 -0
  90. package/src/shared/plugin/hooks/lib/requires-probe.ts +1183 -0
  91. package/src/types/index.ts +60 -16
  92. package/src/ui/App.tsx +81 -171
  93. package/src/ui/adapters/cliToolsAdapter.ts +252 -12
  94. package/src/ui/adapters/depsAdapter.ts +129 -0
  95. package/src/ui/adapters/failureReport.ts +137 -0
  96. package/src/ui/adapters/pluginsAdapter.ts +297 -45
  97. package/src/ui/adapters/startupReports.ts +260 -0
  98. package/src/ui/components/modals/FailureListModal.tsx +231 -0
  99. package/src/ui/components/modals/MessageModal.tsx +7 -12
  100. package/src/ui/components/modals/ModalContainer.tsx +10 -5
  101. package/src/ui/components/modals/index.ts +1 -0
  102. package/src/ui/hooks/startupQueue.ts +56 -0
  103. package/src/ui/hooks/useGitignoreModal.ts +30 -52
  104. package/src/ui/hooks/useProfileStartupModal.ts +119 -142
  105. package/src/ui/hooks/useStaleLoadModal.ts +35 -65
  106. package/src/ui/keymap.ts +14 -1
  107. package/src/ui/renderers/cliToolRenderers.tsx +135 -2
  108. package/src/ui/renderers/mcpRenderers.tsx +110 -3
  109. package/src/ui/renderers/pluginRenderers.tsx +77 -6
  110. package/src/ui/renderers/pluginRowText.ts +25 -25
  111. package/src/ui/renderers/pluginStatusText.ts +18 -1
  112. package/src/ui/screens/AliasScreen.tsx +38 -45
  113. package/src/ui/screens/CliToolsScreen.tsx +199 -98
  114. package/src/ui/screens/EnvVarsScreen.tsx +17 -5
  115. package/src/ui/screens/GitignoreScreen.tsx +48 -67
  116. package/src/ui/screens/McpRegistryScreen.tsx +67 -38
  117. package/src/ui/screens/McpScreen.tsx +46 -19
  118. package/src/ui/screens/ModelsScreen.tsx +46 -86
  119. package/src/ui/screens/PluginsScreen.tsx +315 -259
  120. package/src/ui/screens/ProfilesScreen.tsx +79 -40
  121. package/src/ui/screens/SkillsScreen.tsx +83 -80
  122. package/src/ui/screens/StylesScreen.tsx +211 -275
  123. package/src/ui/state/AppContext.tsx +66 -44
  124. package/src/ui/state/DimensionsContext.tsx +3 -22
  125. package/src/ui/state/reducer.ts +12 -100
  126. package/src/ui/state/types.ts +48 -69
  127. package/src/utils/command-utils.ts +4 -5
  128. package/src/utils/run.ts +123 -52
  129. package/src/utils/string-utils.ts +9 -0
  130. package/src/utils/user-path.ts +60 -0
  131. package/src/__tests__/go-module.test.ts +0 -43
  132. package/src/__tests__/notices-rules.test.ts +0 -476
  133. package/src/__tests__/notices.test.tsx +0 -604
  134. package/src/__tests__/plugin-setup.test.ts +0 -1016
  135. package/src/__tests__/shell-script-callers.test.ts +0 -92
  136. package/src/__tests__/styles-status-line.test.ts +0 -72
  137. package/src/__tests__/version-snapshot.test.ts +0 -212
  138. package/src/services/plugin-setup.ts +0 -965
  139. package/src/services/version-snapshot.ts +0 -189
  140. 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
- }
@@ -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
- }