magus-cli 0.0.0-placeholder → 7.1.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 +309 -0
- package/bin/magus.js +170 -0
- package/package.json +67 -3
- package/scripts/build-binaries.ts +113 -0
- package/scripts/capture-builtin.ts +412 -0
- package/scripts/check-optional-deps.ts +49 -0
- package/scripts/test-isolated.ts +49 -0
- package/scripts/verify-binary.ts +95 -0
- package/scripts/verify-community-registry.ts +272 -0
- package/src/__tests__/active-closure.test.ts +184 -0
- package/src/__tests__/alias-adopt.test.ts +365 -0
- package/src/__tests__/alias-parser.test.ts +417 -0
- package/src/__tests__/alias-shell-writer.test.ts +681 -0
- package/src/__tests__/alias-store.test.ts +163 -0
- package/src/__tests__/binary-signing.test.ts +120 -0
- package/src/__tests__/blackbox-active-closure.test.ts +119 -0
- package/src/__tests__/blackbox-convergence.test.ts +334 -0
- package/src/__tests__/blackbox-parity.test.ts +725 -0
- package/src/__tests__/blackbox-plugin-state.test.ts +545 -0
- package/src/__tests__/blackbox-tool-state.test.ts +669 -0
- package/src/__tests__/cache-provenance.test.ts +84 -0
- package/src/__tests__/capture-builtin.test.ts +92 -0
- package/src/__tests__/catalog-cache-store.test.ts +309 -0
- package/src/__tests__/catalog-notice.test.ts +155 -0
- package/src/__tests__/cli-ansi.test.ts +297 -0
- package/src/__tests__/cli-apply-seams.test.ts +288 -0
- package/src/__tests__/cli-live.test.ts +391 -0
- package/src/__tests__/cli-router.test.ts +240 -0
- package/src/__tests__/cli-tool-commands.test.ts +239 -0
- package/src/__tests__/cli-tools-adapter.test.ts +294 -0
- package/src/__tests__/cli-update-view.test.ts +524 -0
- package/src/__tests__/community-fetch.test.ts +545 -0
- package/src/__tests__/community-registry.test.ts +269 -0
- package/src/__tests__/community-staleness.test.ts +720 -0
- package/src/__tests__/component-badge.test.ts +98 -0
- package/src/__tests__/component-icons.test.ts +94 -0
- package/src/__tests__/content-drift.test.ts +353 -0
- package/src/__tests__/conventions-integration.test.ts +830 -0
- package/src/__tests__/conventions-manager.test.ts +1236 -0
- package/src/__tests__/doctor-bins.test.ts +43 -0
- package/src/__tests__/doctor.test.ts +463 -0
- package/src/__tests__/dotenv.test.ts +195 -0
- package/src/__tests__/enabled-not-installed.test.ts +94 -0
- package/src/__tests__/file-locking.test.ts +208 -0
- package/src/__tests__/fixtures/tool-probe/brew-list-versions-tmux.txt +2 -0
- package/src/__tests__/fixtures/tool-probe/brew-outdated-behind-homebrew-spec.txt +29 -0
- package/src/__tests__/fixtures/tool-probe/brew-outdated-tmux-current.txt +7 -0
- package/src/__tests__/fixtures/tool-probe/brew-outdated-unknown-formula.txt +6 -0
- package/src/__tests__/fixtures/tool-probe/go-version-m-tmux-mcp.txt +27 -0
- package/src/__tests__/fixtures/tool-probe/npm-dist-tags-missing-scoped.txt +4 -0
- package/src/__tests__/fixtures/tool-probe/npm-dist-tags-openai-codex.txt +4 -0
- package/src/__tests__/fixtures/tool-probe/pypi-cowsay.txt +3 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-browser-use.toml +10 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-cowsay-git.toml +8 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-cowsay-pinned.toml +8 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-graphifyy.toml +7 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-mcp.toml +6 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-serena-agent.toml +9 -0
- package/src/__tests__/fixtures/tool-probe/uv-tool-list-outdated-pinned.txt +5 -0
- package/src/__tests__/fixtures/tool-probe/uv-tool-list-outdated.txt +11 -0
- package/src/__tests__/fixtures/tool-probe/uv-tool-list.txt +22 -0
- package/src/__tests__/footer-hints.test.ts +40 -0
- package/src/__tests__/format-command.test.ts +78 -0
- package/src/__tests__/git-worktree.test.ts +108 -0
- package/src/__tests__/github-budget.test.ts +206 -0
- package/src/__tests__/gitignore-check.test.ts +71 -0
- package/src/__tests__/gitignore-detector.test.ts +176 -0
- package/src/__tests__/gitignore-fixer.test.ts +317 -0
- package/src/__tests__/gitignore-resolver.test.ts +193 -0
- package/src/__tests__/gitignore-service.test.ts +137 -0
- package/src/__tests__/go-module.test.ts +43 -0
- package/src/__tests__/hook-import-policy.test.ts +90 -0
- package/src/__tests__/hook-process.test.ts +256 -0
- package/src/__tests__/hook-registration.test.ts +224 -0
- package/src/__tests__/install-command.test.ts +81 -0
- package/src/__tests__/install-plan.test.ts +104 -0
- package/src/__tests__/launcher-signal.test.ts +308 -0
- package/src/__tests__/local-marketplace-scan.test.ts +248 -0
- package/src/__tests__/manifest.test.ts +281 -0
- package/src/__tests__/markdown-renderer.test.ts +142 -0
- package/src/__tests__/marketplace-badge.test.ts +140 -0
- package/src/__tests__/marketplace-refresh.test.ts +340 -0
- package/src/__tests__/marketplace-version.test.ts +84 -0
- package/src/__tests__/marketplaces.test.ts +128 -0
- package/src/__tests__/mate-availability.test.ts +156 -0
- package/src/__tests__/mate-catalog.test.ts +295 -0
- package/src/__tests__/mcp-registry.test.ts +158 -0
- package/src/__tests__/model-visuals.test.tsx +2466 -0
- package/src/__tests__/models-adapter.test.ts +332 -0
- package/src/__tests__/models-cli.test.ts +273 -0
- package/src/__tests__/models-core.test.ts +802 -0
- package/src/__tests__/models-manager.test.ts +500 -0
- package/src/__tests__/models-presets-marketplace.test.ts +168 -0
- package/src/__tests__/models-screen-state.test.ts +315 -0
- package/src/__tests__/open-file.test.ts +59 -0
- package/src/__tests__/owned-marketplaces.test.ts +93 -0
- package/src/__tests__/plugin-checkout.test.ts +66 -0
- package/src/__tests__/plugin-cli-argv.test.ts +109 -0
- package/src/__tests__/plugin-contents.test.ts +371 -0
- package/src/__tests__/plugin-manager-fallback.test.ts +320 -0
- package/src/__tests__/plugin-releases.test.ts +98 -0
- package/src/__tests__/plugin-requires.test.ts +187 -0
- package/src/__tests__/plugin-row-text.test.ts +345 -0
- package/src/__tests__/plugin-setup.test.ts +1016 -0
- package/src/__tests__/profile-adopt.test.ts +177 -0
- package/src/__tests__/profile-command.test.ts +110 -0
- package/src/__tests__/profile-materializer.test.ts +238 -0
- package/src/__tests__/profile-sync.test.ts +154 -0
- package/src/__tests__/registry-version-resolution.test.ts +88 -0
- package/src/__tests__/resolve-executable.test.ts +42 -0
- package/src/__tests__/resolver.test.ts +351 -0
- package/src/__tests__/scope-action.test.ts +134 -0
- package/src/__tests__/scope-squares.test.tsx +164 -0
- package/src/__tests__/selection-scope.test.ts +100 -0
- package/src/__tests__/settings-file.test.ts +179 -0
- package/src/__tests__/shell-script-callers.test.ts +92 -0
- package/src/__tests__/skill-install-files.test.ts +136 -0
- package/src/__tests__/skills-catalog-client.test.ts +48 -0
- package/src/__tests__/style-wrap.test.ts +220 -0
- package/src/__tests__/styles-manager.test.ts +1098 -0
- package/src/__tests__/styles-origins.test.ts +414 -0
- package/src/__tests__/styles-screen-state.test.ts +457 -0
- package/src/__tests__/styles-status-line.test.ts +72 -0
- package/src/__tests__/styles-sync.test.ts +452 -0
- package/src/__tests__/symlink-manager.test.ts +180 -0
- package/src/__tests__/tabbar-layout.test.ts +100 -0
- package/src/__tests__/terminology-filler.test.ts +214 -0
- package/src/__tests__/theme-adaptive-colors.test.ts +354 -0
- package/src/__tests__/theme-env.test.ts +310 -0
- package/src/__tests__/theme-resolve.test.ts +380 -0
- package/src/__tests__/tool-probe.test.ts +1400 -0
- package/src/__tests__/toolchain.test.ts +232 -0
- package/src/__tests__/ui-version-writers.test.ts +88 -0
- package/src/__tests__/update-apply.test.ts +637 -0
- package/src/__tests__/update-plan.test.ts +1396 -0
- package/src/__tests__/uppercase-keybindings.test.ts +101 -0
- package/src/__tests__/useGitignoreModal.test.ts +79 -0
- package/src/__tests__/version-check-refresh.test.ts +45 -0
- package/src/__tests__/version-snapshot.test.ts +212 -0
- package/src/__tests__/version-unknown-state.test.ts +403 -0
- package/src/__tests__/worktree-registry-resolution.test.ts +107 -0
- package/src/cli/ansi.ts +528 -0
- package/src/cli/bootstrap.ts +134 -0
- package/src/cli/doctor.ts +218 -0
- package/src/cli/hook.ts +129 -0
- package/src/cli/install.ts +382 -0
- package/src/cli/live.ts +374 -0
- package/src/cli/models.ts +297 -0
- package/src/cli/profile.ts +197 -0
- package/src/cli/prompt.ts +32 -0
- package/src/cli/router.ts +264 -0
- package/src/cli/update-view.ts +717 -0
- package/src/cli/update.ts +600 -0
- package/src/cli/upgrade.ts +97 -0
- package/src/data/alias-flags.ts +301 -0
- package/src/data/cli-tools.ts +146 -0
- package/src/data/community-styles.ts +531 -0
- package/src/data/gitignore-defaults.ts +33 -0
- package/src/data/gitignore-reasons.ts +93 -0
- package/src/data/gitignore-templates.ts +25 -0
- package/src/data/marketplaces.ts +278 -0
- package/src/data/mcp-servers.ts +51 -0
- package/src/data/models-presets.ts +329 -0
- package/src/data/predefined-profiles.ts +252 -0
- package/src/data/settings-catalog.ts +714 -0
- package/src/data/skill-repos.ts +91 -0
- package/src/data/styles/asd-ste100.md +38 -0
- package/src/data/styles/calibrated.md +24 -0
- package/src/data/styles/direct.md +23 -0
- package/src/data/styles/evidence-first.md +22 -0
- package/src/data/styles/explanatory.md +23 -0
- package/src/data/styles/index.ts +57 -0
- package/src/data/styles/no-slop.md +32 -0
- package/src/data/styles/plain-language.md +23 -0
- package/src/data/styles/structured.md +30 -0
- package/src/data/styles/terminology.md +28 -0
- package/src/data/styles/terse.md +20 -0
- package/src/main.tsx +110 -0
- package/src/markdown.d.ts +13 -0
- package/src/opentui.d.ts +203 -0
- package/src/services/active-closure.ts +117 -0
- package/src/services/alias-settings.ts +60 -0
- package/src/services/alias-shell-writer.ts +1244 -0
- package/src/services/alias-store.ts +172 -0
- package/src/services/binary-signing.ts +78 -0
- package/src/services/catalog-cache-store.ts +316 -0
- package/src/services/claude-cli.ts +304 -0
- package/src/services/claude-runner.ts +31 -0
- package/src/services/claude-settings.ts +1564 -0
- package/src/services/cli-tool-commands.ts +133 -0
- package/src/services/community-fetcher.ts +90 -0
- package/src/services/community-styles.ts +1194 -0
- package/src/services/content-drift.ts +208 -0
- package/src/services/conventions-manager.ts +945 -0
- package/src/services/doctor-bins.ts +63 -0
- package/src/services/doctor.ts +570 -0
- package/src/services/dotenv.ts +181 -0
- package/src/services/git-worktree.ts +129 -0
- package/src/services/github-budget.ts +274 -0
- package/src/services/gitignore-check.ts +63 -0
- package/src/services/gitignore-detector.ts +167 -0
- package/src/services/gitignore-fixer.ts +280 -0
- package/src/services/gitignore-resolver.ts +208 -0
- package/src/services/gitignore-service.ts +167 -0
- package/src/services/hook-registration.ts +218 -0
- package/src/services/install-plan.ts +107 -0
- package/src/services/local-marketplace.ts +657 -0
- package/src/services/manifest.ts +250 -0
- package/src/services/marketplace-catalog-git.ts +173 -0
- package/src/services/marketplace-catalog.ts +129 -0
- package/src/services/marketplace-fetcher.ts +400 -0
- package/src/services/marketplace-refresh.ts +245 -0
- package/src/services/marketplace-sync.ts +145 -0
- package/src/services/mate-availability.ts +133 -0
- package/src/services/mate-catalog.ts +265 -0
- package/src/services/mcp-registry.ts +171 -0
- package/src/services/models-core.ts +969 -0
- package/src/services/models-manager.ts +606 -0
- package/src/services/plugin-checkout.ts +186 -0
- package/src/services/plugin-manager.ts +847 -0
- package/src/services/plugin-mcp-config.ts +242 -0
- package/src/services/plugin-releases.ts +47 -0
- package/src/services/plugin-requires.ts +227 -0
- package/src/services/plugin-setup.ts +965 -0
- package/src/services/profile-adopt.ts +206 -0
- package/src/services/profile-materializer.ts +133 -0
- package/src/services/profile-sync.ts +158 -0
- package/src/services/profiles.ts +225 -0
- package/src/services/resolver.ts +377 -0
- package/src/services/settings-file.ts +69 -0
- package/src/services/settings-manager.ts +307 -0
- package/src/services/skills-manager.ts +1111 -0
- package/src/services/skillsmp-client.ts +193 -0
- package/src/services/styles-manager.ts +1294 -0
- package/src/services/symlink-manager.ts +189 -0
- package/src/services/terminology-filler.ts +266 -0
- package/src/services/tool-probe.ts +1637 -0
- package/src/services/toolchain.ts +431 -0
- package/src/services/update-engine.ts +691 -0
- package/src/services/update-plan.ts +820 -0
- package/src/services/version-check.ts +127 -0
- package/src/services/version-snapshot.ts +189 -0
- package/src/tui.tsx +112 -0
- package/src/types/bun.d.ts +27 -0
- package/src/types/gitignore.ts +68 -0
- package/src/types/index.ts +447 -0
- package/src/ui/App.tsx +535 -0
- package/src/ui/adapters/catalogNotice.ts +122 -0
- package/src/ui/adapters/cliToolsAdapter.ts +176 -0
- package/src/ui/adapters/modelsAdapter.ts +213 -0
- package/src/ui/adapters/pluginsAdapter.ts +413 -0
- package/src/ui/adapters/settingsAdapter.ts +168 -0
- package/src/ui/adapters/skillsAdapter.ts +229 -0
- package/src/ui/adapters/stylesAdapter.ts +403 -0
- package/src/ui/components/CategoryHeader.tsx +64 -0
- package/src/ui/components/EmptyFilterState.tsx +32 -0
- package/src/ui/components/FlagDetailEditor.tsx +0 -0
- package/src/ui/components/ScopeIndicator.tsx +63 -0
- package/src/ui/components/ScrollableList.tsx +95 -0
- package/src/ui/components/SearchInput.tsx +57 -0
- package/src/ui/components/StyledText.tsx +75 -0
- package/src/ui/components/TabBar.tsx +101 -0
- package/src/ui/components/layout/FooterHints.tsx +78 -0
- package/src/ui/components/layout/Panel.tsx +63 -0
- package/src/ui/components/layout/ProgressBar.tsx +44 -0
- package/src/ui/components/layout/ScopeTabs.tsx +54 -0
- package/src/ui/components/layout/ScreenLayout.tsx +321 -0
- package/src/ui/components/layout/index.ts +9 -0
- package/src/ui/components/modals/ConfirmModal.tsx +62 -0
- package/src/ui/components/modals/InputModal.tsx +75 -0
- package/src/ui/components/modals/LoadingModal.tsx +51 -0
- package/src/ui/components/modals/MessageModal.tsx +66 -0
- package/src/ui/components/modals/ModalContainer.tsx +159 -0
- package/src/ui/components/modals/SelectModal.tsx +90 -0
- package/src/ui/components/modals/index.ts +6 -0
- package/src/ui/components/primitives/ActionHints.tsx +44 -0
- package/src/ui/components/primitives/DetailSection.tsx +23 -0
- package/src/ui/components/primitives/KeyValueLine.tsx +26 -0
- package/src/ui/components/primitives/ListCategoryRow.tsx +45 -0
- package/src/ui/components/primitives/MetaText.tsx +40 -0
- package/src/ui/components/primitives/ScopeDetail.tsx +67 -0
- package/src/ui/components/primitives/ScopeSquares.tsx +58 -0
- package/src/ui/components/primitives/SelectableRow.tsx +30 -0
- package/src/ui/components/primitives/index.ts +9 -0
- package/src/ui/hooks/index.ts +2 -0
- package/src/ui/hooks/useGitignoreModal.ts +95 -0
- package/src/ui/hooks/useKeyboard.ts +24 -0
- package/src/ui/hooks/useKeyboardHandler.ts +63 -0
- package/src/ui/registry.ts +33 -0
- package/src/ui/renderers/cliToolRenderers.tsx +326 -0
- package/src/ui/renderers/gitignoreRenderers.tsx +80 -0
- package/src/ui/renderers/markdownRenderer.tsx +302 -0
- package/src/ui/renderers/mcpRenderers.tsx +149 -0
- package/src/ui/renderers/modelRenderers.tsx +1309 -0
- package/src/ui/renderers/modelVisuals.tsx +1402 -0
- package/src/ui/renderers/pluginRenderers.tsx +1089 -0
- package/src/ui/renderers/pluginRowText.ts +70 -0
- package/src/ui/renderers/pluginStatusText.ts +56 -0
- package/src/ui/renderers/profileRenderers.tsx +406 -0
- package/src/ui/renderers/settingsRenderers.tsx +218 -0
- package/src/ui/renderers/skillRenderers.tsx +479 -0
- package/src/ui/renderers/styleRenderers.tsx +813 -0
- package/src/ui/screens/AliasScreen.tsx +1558 -0
- package/src/ui/screens/CliToolsScreen.tsx +367 -0
- package/src/ui/screens/EnvVarsScreen.tsx +221 -0
- package/src/ui/screens/GitignoreScreen.tsx +562 -0
- package/src/ui/screens/McpRegistryScreen.tsx +366 -0
- package/src/ui/screens/McpScreen.tsx +256 -0
- package/src/ui/screens/ModelsScreen.tsx +542 -0
- package/src/ui/screens/PluginsScreen.tsx +1423 -0
- package/src/ui/screens/ProfilesScreen.tsx +438 -0
- package/src/ui/screens/SkillsScreen.tsx +853 -0
- package/src/ui/screens/StylesScreen.tsx +1064 -0
- package/src/ui/screens/index.ts +11 -0
- package/src/ui/state/AppContext.tsx +233 -0
- package/src/ui/state/DimensionsContext.tsx +154 -0
- package/src/ui/state/reducer.ts +810 -0
- package/src/ui/state/types.ts +503 -0
- package/src/ui/theme-mode.ts +440 -0
- package/src/ui/theme-resolve.ts +147 -0
- package/src/ui/theme.ts +175 -0
- package/src/utils/clipboard.ts +58 -0
- package/src/utils/command-utils.ts +149 -0
- package/src/utils/config-dir.ts +45 -0
- package/src/utils/file-locking.ts +146 -0
- package/src/utils/fuzzy-search.ts +138 -0
- package/src/utils/go-module.ts +36 -0
- package/src/utils/open-file.ts +84 -0
- package/src/utils/run.ts +67 -0
- package/src/utils/string-utils.ts +88 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* content-drift.ts — detect a plugin whose files changed without its version.
|
|
3
|
+
*
|
|
4
|
+
* The whole update system keys off one signal: the version string.
|
|
5
|
+
* `hasUpdate` is `compareVersions(catalogVersion, installedVersion) > 0`, so a
|
|
6
|
+
* marketplace that republishes different content under an unchanged version is
|
|
7
|
+
* invisible. No update is offered, and the only cure is a reinstall — which is
|
|
8
|
+
* exactly the "plugin says installed but its skills are missing" report.
|
|
9
|
+
*
|
|
10
|
+
* This is not a hypothetical. `publish-dist.sh` pushes a rebuilt tree, so
|
|
11
|
+
* same-version-different-content is a normal outcome of the release process.
|
|
12
|
+
* Measured on 2026-08-06: `cache/magus/dev/3.0.1/skills` held
|
|
13
|
+
* {audit, tui-progress} while `marketplaces/magus/plugins/dev/skills` held
|
|
14
|
+
* {security-audit} — both stamped 3.0.1.
|
|
15
|
+
*
|
|
16
|
+
* The fix uses a signal already present in `installed_plugins.json`: each entry
|
|
17
|
+
* records `gitCommitSha`, the marketplace commit it was installed from. Asking
|
|
18
|
+
* git whether THIS PLUGIN'S SUBTREE changed between that commit and the
|
|
19
|
+
* marketplace's current HEAD answers the question exactly.
|
|
20
|
+
*
|
|
21
|
+
* Comparing whole-marketplace SHAs would be wrong: any commit to any plugin
|
|
22
|
+
* would flag every plugin as drifted. The subtree scope is what makes this
|
|
23
|
+
* precise enough to act on.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { spawn } from "node:child_process";
|
|
27
|
+
import os from "node:os";
|
|
28
|
+
import path from "node:path";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Resolved per call, honouring CLAUDE_CONFIG_DIR — the same override Claude
|
|
32
|
+
* Code uses. A module-level constant would bake in `os.homedir()` at import
|
|
33
|
+
* time, which on macOS ignores $HOME and makes the service impossible to test
|
|
34
|
+
* without touching the operator's real marketplaces.
|
|
35
|
+
*/
|
|
36
|
+
function marketplacesDir(): string {
|
|
37
|
+
const configDir =
|
|
38
|
+
process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
|
|
39
|
+
return path.join(configDir, "plugins", "marketplaces");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Session cache. Keyed by marketplace + installed sha + plugin, because the
|
|
44
|
+
* answer cannot change while magus is open — the marketplace clone is only
|
|
45
|
+
* refreshed by Claude Code, out of process.
|
|
46
|
+
*/
|
|
47
|
+
const cache = new Map<string, boolean>();
|
|
48
|
+
|
|
49
|
+
export function clearContentDriftCache(): void {
|
|
50
|
+
cache.clear();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function git(
|
|
54
|
+
cwd: string,
|
|
55
|
+
args: string[],
|
|
56
|
+
): Promise<{ code: number; out: string }> {
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
const child = spawn("git", args, {
|
|
59
|
+
cwd,
|
|
60
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
61
|
+
});
|
|
62
|
+
let out = "";
|
|
63
|
+
child.stdout.on("data", (d) => {
|
|
64
|
+
out += String(d);
|
|
65
|
+
});
|
|
66
|
+
child.on("error", () => resolve({ code: -1, out: "" }));
|
|
67
|
+
child.on("close", (code) => resolve({ code: code ?? -1, out: out.trim() }));
|
|
68
|
+
// Never let a wedged git block the UI.
|
|
69
|
+
setTimeout(() => {
|
|
70
|
+
child.kill();
|
|
71
|
+
resolve({ code: -1, out: "" });
|
|
72
|
+
}, 5000).unref?.();
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* HEAD of the git repo containing `dir`, or undefined when there is none.
|
|
78
|
+
*
|
|
79
|
+
* This is what stamps provenance onto a cache copy: `copyPluginToCache` reads
|
|
80
|
+
* the plugin's files out of the marketplace clone, so that clone's HEAD is the
|
|
81
|
+
* commit those files came from. Undefined is a real answer — a directory-type
|
|
82
|
+
* marketplace has no commit to name, and inventing one would assert a
|
|
83
|
+
* provenance that is false.
|
|
84
|
+
*/
|
|
85
|
+
export async function repoHeadSha(dir: string): Promise<string | undefined> {
|
|
86
|
+
const r = await git(dir, ["rev-parse", "HEAD"]);
|
|
87
|
+
if (r.code !== 0) return undefined;
|
|
88
|
+
return /^[0-9a-f]{40}$/.test(r.out) ? r.out : undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface DriftQuery {
|
|
92
|
+
marketplace: string;
|
|
93
|
+
/** Plugin name without the @marketplace suffix. */
|
|
94
|
+
pluginName: string;
|
|
95
|
+
/** `gitCommitSha` from the installed_plugins.json entry. */
|
|
96
|
+
installedSha: string | undefined;
|
|
97
|
+
/**
|
|
98
|
+
* Version currently installed. Required, not optional: the sha alone cannot
|
|
99
|
+
* be trusted, and a caller that omitted this would silently get the old
|
|
100
|
+
* always-drifted behaviour back.
|
|
101
|
+
*/
|
|
102
|
+
installedVersion: string | undefined;
|
|
103
|
+
/**
|
|
104
|
+
* Path of the plugin inside the marketplace repo, from the catalog `source`
|
|
105
|
+
* field (e.g. "./plugins/dev"). Defaults to `plugins/<name>`.
|
|
106
|
+
*/
|
|
107
|
+
sourcePath?: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Strip a leading "v" so "v1.2.3" and "1.2.3" compare equal. */
|
|
111
|
+
const normalize = (v: string) => v.trim().replace(/^v/, "");
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The version a plugin's manifest declared at a given commit.
|
|
115
|
+
*
|
|
116
|
+
* Both layouts are tried because both are real: magus lays plugins out as
|
|
117
|
+
* `plugins/<name>/plugin.json`, while Claude Code documents
|
|
118
|
+
* `.claude-plugin/plugin.json`. Reading only one would silence drift detection
|
|
119
|
+
* for every plugin using the other.
|
|
120
|
+
*/
|
|
121
|
+
async function versionAtSha(
|
|
122
|
+
repo: string,
|
|
123
|
+
sha: string,
|
|
124
|
+
rel: string,
|
|
125
|
+
): Promise<string | undefined> {
|
|
126
|
+
for (const manifest of [
|
|
127
|
+
`${rel}/plugin.json`,
|
|
128
|
+
`${rel}/.claude-plugin/plugin.json`,
|
|
129
|
+
]) {
|
|
130
|
+
const r = await git(repo, ["show", `${sha}:${manifest}`]);
|
|
131
|
+
if (r.code !== 0 || !r.out) continue;
|
|
132
|
+
try {
|
|
133
|
+
const v = JSON.parse(r.out)?.version;
|
|
134
|
+
if (typeof v === "string" && v) return v;
|
|
135
|
+
} catch {
|
|
136
|
+
// Unparseable manifest is the same as no manifest: unverifiable.
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* True when the plugin's files in the marketplace differ from the commit it was
|
|
144
|
+
* installed from — i.e. a reinstall would deliver different content.
|
|
145
|
+
*
|
|
146
|
+
* Returns false whenever the question cannot be answered honestly: no recorded
|
|
147
|
+
* sha, no clone, git unavailable, the recorded commit is absent locally
|
|
148
|
+
* (force-push, shallow clone), or that commit declares a DIFFERENT version than
|
|
149
|
+
* the one installed. A false negative leaves today's behaviour; a false
|
|
150
|
+
* positive would nag the user to reinstall for no reason.
|
|
151
|
+
*/
|
|
152
|
+
export async function hasContentDrift(q: DriftQuery): Promise<boolean> {
|
|
153
|
+
if (!q.installedSha) return false;
|
|
154
|
+
|
|
155
|
+
const key = `${q.marketplace}\0${q.pluginName}\0${q.installedSha}\0${q.installedVersion}`;
|
|
156
|
+
const hit = cache.get(key);
|
|
157
|
+
if (hit !== undefined) return hit;
|
|
158
|
+
|
|
159
|
+
const repo = path.join(marketplacesDir(), q.marketplace);
|
|
160
|
+
const rel = (q.sourcePath ?? `plugins/${q.pluginName}`).replace(/^\.\//, "");
|
|
161
|
+
|
|
162
|
+
// The recorded commit must exist locally, or the diff is meaningless.
|
|
163
|
+
const known = await git(repo, [
|
|
164
|
+
"cat-file",
|
|
165
|
+
"-e",
|
|
166
|
+
`${q.installedSha}^{commit}`,
|
|
167
|
+
]);
|
|
168
|
+
if (known.code !== 0) {
|
|
169
|
+
cache.set(key, false);
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// The recorded sha must describe the version actually installed, or the diff
|
|
174
|
+
// answers a different question than the one asked.
|
|
175
|
+
//
|
|
176
|
+
// This is the whole reason the badge fired on every plugin at once:
|
|
177
|
+
// `updateInstalledPluginsRegistry` bumps `version` on an in-place update but
|
|
178
|
+
// carries the pre-update `gitCommitSha` forward, so the sha names whichever
|
|
179
|
+
// commit the plugin was FIRST installed from. Measured 2026-08-22 on one
|
|
180
|
+
// machine: dev recorded 4.3.0 against a sha holding 3.3.0, browser-use 1.4.1
|
|
181
|
+
// against 1.1.3. Diffing across those releases is trivially non-empty, so
|
|
182
|
+
// every plugin rendered "stale — reinstall" while its cached files were in
|
|
183
|
+
// fact byte-identical to the marketplace.
|
|
184
|
+
const shaVersion = q.installedVersion
|
|
185
|
+
? await versionAtSha(repo, q.installedSha, rel)
|
|
186
|
+
: undefined;
|
|
187
|
+
if (
|
|
188
|
+
!q.installedVersion ||
|
|
189
|
+
!shaVersion ||
|
|
190
|
+
normalize(shaVersion) !== normalize(q.installedVersion)
|
|
191
|
+
) {
|
|
192
|
+
cache.set(key, false);
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Exit 0 = no difference, 1 = differs, anything else = could not tell.
|
|
197
|
+
const diff = await git(repo, [
|
|
198
|
+
"diff",
|
|
199
|
+
"--quiet",
|
|
200
|
+
q.installedSha,
|
|
201
|
+
"HEAD",
|
|
202
|
+
"--",
|
|
203
|
+
rel,
|
|
204
|
+
]);
|
|
205
|
+
const drifted = diff.code === 1;
|
|
206
|
+
cache.set(key, drifted);
|
|
207
|
+
return drifted;
|
|
208
|
+
}
|