magus-cli 0.0.0-placeholder → 7.0.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 +684 -0
- package/src/__tests__/blackbox-plugin-state.test.ts +523 -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 +445 -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 +174 -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 +335 -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__/moved-marketplace.test.ts +86 -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 +336 -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 +281 -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 +1373 -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 +388 -0
- package/src/__tests__/worktree-registry-resolution.test.ts +107 -0
- package/src/cli/ansi.ts +520 -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 +207 -0
- package/src/cli/update-view.ts +526 -0
- package/src/cli/update.ts +613 -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 +1589 -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 +587 -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 +253 -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 +878 -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 +344 -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 +863 -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 +538 -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 +1464 -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 +352 -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 +144 -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,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude CLI wrapper service
|
|
3
|
+
*
|
|
4
|
+
* Delegates plugin and marketplace management to the `claude` CLI
|
|
5
|
+
* instead of manipulating settings JSON files directly.
|
|
6
|
+
*
|
|
7
|
+
* CLI commands used:
|
|
8
|
+
* - claude plugin install/uninstall/enable/disable/update
|
|
9
|
+
* - claude marketplace add
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { execFile } from "node:child_process";
|
|
13
|
+
import { promisify } from "node:util";
|
|
14
|
+
import { defaultMarketplaces } from "../data/marketplaces.js";
|
|
15
|
+
import { which } from "../utils/command-utils.js";
|
|
16
|
+
import {
|
|
17
|
+
isMarketplaceRegistered,
|
|
18
|
+
removeGlobalInstalledPluginVersion,
|
|
19
|
+
removeLocalInstalledPluginVersion,
|
|
20
|
+
} from "./claude-settings.js";
|
|
21
|
+
import { removeInstalledPluginVersion } from "./plugin-manager.js";
|
|
22
|
+
|
|
23
|
+
const execFileAsync = promisify(execFile);
|
|
24
|
+
|
|
25
|
+
export type PluginScope = "user" | "project" | "local";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get the path to the claude CLI binary
|
|
29
|
+
* @throws Error if claude CLI is not found in PATH
|
|
30
|
+
*/
|
|
31
|
+
async function getClaudePath(): Promise<string> {
|
|
32
|
+
const claudePath = await which("claude");
|
|
33
|
+
if (!claudePath) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
"claude CLI not found in PATH. Install with: npm install -g @anthropic-ai/claude-code",
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return claudePath;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Execute a claude CLI command and return stdout
|
|
43
|
+
* @param args - Arguments to pass to claude
|
|
44
|
+
* @param timeoutMs - Timeout in milliseconds (default: 30s)
|
|
45
|
+
* @returns stdout from the command
|
|
46
|
+
*/
|
|
47
|
+
async function execClaude(args: string[], timeoutMs = 30000): Promise<string> {
|
|
48
|
+
const claudePath = await getClaudePath();
|
|
49
|
+
try {
|
|
50
|
+
const { stdout } = await execFileAsync(claudePath, args, {
|
|
51
|
+
timeout: timeoutMs,
|
|
52
|
+
});
|
|
53
|
+
return stdout.trim();
|
|
54
|
+
} catch (error: unknown) {
|
|
55
|
+
const execError = error as {
|
|
56
|
+
stderr?: string;
|
|
57
|
+
stdout?: string;
|
|
58
|
+
message?: string;
|
|
59
|
+
code?: number;
|
|
60
|
+
};
|
|
61
|
+
const msg =
|
|
62
|
+
execError.stderr?.trim() ||
|
|
63
|
+
execError.stdout?.trim() ||
|
|
64
|
+
execError.message ||
|
|
65
|
+
"claude command failed";
|
|
66
|
+
throw new Error(msg);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Recover from a "not found in marketplace" error.
|
|
72
|
+
*
|
|
73
|
+
* Two distinct failure modes land in the same error string:
|
|
74
|
+
* 1. Marketplace is registered locally but stale (or uses the old
|
|
75
|
+
* "directory" source) — fix the registry and refresh the clone.
|
|
76
|
+
* 2. Marketplace is not registered at all — look up its repo in
|
|
77
|
+
* defaultMarketplaces and add it. This is the "new employee clones
|
|
78
|
+
* a project whose .claude/settings.json enables @magus plugins but
|
|
79
|
+
* magus was never registered on this machine" case.
|
|
80
|
+
*
|
|
81
|
+
* Returns true if recovery was attempted and a retry is worth trying.
|
|
82
|
+
*/
|
|
83
|
+
async function recoverMissingMarketplace(
|
|
84
|
+
marketplace: string,
|
|
85
|
+
): Promise<boolean> {
|
|
86
|
+
if (await isMarketplaceRegistered(marketplace)) {
|
|
87
|
+
// Registered but the plugin wasn't found → the registry may use a stale or
|
|
88
|
+
// "directory" source. Fix the registry, then refresh the clone
|
|
89
|
+
// NON-DESTRUCTIVELY (git pull --ff-only) — never `marketplace update`, which
|
|
90
|
+
// deletes the directory before re-cloning and can leave it permanently gone
|
|
91
|
+
// on failure (see marketplace-refresh.ts). If the clone is absent, fall
|
|
92
|
+
// through to the safe `marketplace add` below to create it.
|
|
93
|
+
const { recoverMarketplaceSettings } = await import("./claude-settings.js");
|
|
94
|
+
await recoverMarketplaceSettings();
|
|
95
|
+
const { fastForwardCloneIfPresent } = await import(
|
|
96
|
+
"./marketplace-refresh.js"
|
|
97
|
+
);
|
|
98
|
+
if ((await fastForwardCloneIfPresent(marketplace)) !== "absent") {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
// Clone missing → fall through to `marketplace add`.
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const defaultMp = defaultMarketplaces.find((m) => m.name === marketplace);
|
|
105
|
+
if (!defaultMp?.source.repo) return false;
|
|
106
|
+
|
|
107
|
+
await addMarketplace(defaultMp.source.repo);
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Run a `claude plugin` subcommand, recovering once from a missing or stale
|
|
113
|
+
* marketplace.
|
|
114
|
+
*
|
|
115
|
+
* Shared by install and update because the recovery is identical and the two
|
|
116
|
+
* had already been written twice; a third copy for `update` is how the next
|
|
117
|
+
* divergence starts.
|
|
118
|
+
*/
|
|
119
|
+
async function execPluginCommand(
|
|
120
|
+
verb: "install" | "update",
|
|
121
|
+
pluginId: string,
|
|
122
|
+
scope: PluginScope,
|
|
123
|
+
timeoutMs?: number,
|
|
124
|
+
): Promise<void> {
|
|
125
|
+
const argv = ["plugin", verb, pluginId, "--scope", scope];
|
|
126
|
+
try {
|
|
127
|
+
await execClaude(argv, timeoutMs);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
130
|
+
if (msg.includes("not found in marketplace")) {
|
|
131
|
+
const marketplace = pluginId.split("@")[1];
|
|
132
|
+
if (marketplace && (await recoverMissingMarketplace(marketplace))) {
|
|
133
|
+
await execClaude(argv, timeoutMs);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Install a plugin using claude CLI
|
|
143
|
+
* Handles enabling + version tracking + cache copy in one shot.
|
|
144
|
+
*
|
|
145
|
+
* ONLY for a plugin that is not installed at this scope yet. On one already
|
|
146
|
+
* installed there, `claude plugin install` is a no-op that ignores the version
|
|
147
|
+
* — see {@link updatePlugin}.
|
|
148
|
+
*
|
|
149
|
+
* If the install fails because the plugin is "not found in marketplace",
|
|
150
|
+
* attempts to recover (add the marketplace if missing, or refresh it if
|
|
151
|
+
* stale) and retries once.
|
|
152
|
+
*/
|
|
153
|
+
export async function installPlugin(
|
|
154
|
+
pluginId: string,
|
|
155
|
+
scope: PluginScope = "user",
|
|
156
|
+
): Promise<void> {
|
|
157
|
+
// 60s, matching update and repair. Before the install/update split this path
|
|
158
|
+
// ran through `updatePlugin`'s 60s budget; leaving it on the 30s default
|
|
159
|
+
// would have quietly halved the time a fresh install — the SLOWEST of the
|
|
160
|
+
// three, since it downloads and copies — is allowed to take.
|
|
161
|
+
await execPluginCommand("install", pluginId, scope, 60000);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Uninstall a plugin using claude CLI.
|
|
166
|
+
* Falls back to direct settings removal if CLI uninstall fails
|
|
167
|
+
* (e.g., for plugins installed via old JSON method).
|
|
168
|
+
* @param projectPath - Required for project/local scope fallback
|
|
169
|
+
*/
|
|
170
|
+
export async function uninstallPlugin(
|
|
171
|
+
pluginId: string,
|
|
172
|
+
scope: PluginScope = "user",
|
|
173
|
+
projectPath?: string,
|
|
174
|
+
): Promise<void> {
|
|
175
|
+
try {
|
|
176
|
+
await execClaude(["plugin", "uninstall", pluginId, "--scope", scope]);
|
|
177
|
+
} catch {
|
|
178
|
+
// Fallback: directly remove from settings JSON
|
|
179
|
+
if (scope === "user") {
|
|
180
|
+
await removeGlobalInstalledPluginVersion(pluginId);
|
|
181
|
+
} else if (scope === "local" && projectPath) {
|
|
182
|
+
await removeLocalInstalledPluginVersion(pluginId, projectPath);
|
|
183
|
+
} else if (projectPath) {
|
|
184
|
+
await removeInstalledPluginVersion(pluginId, projectPath);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Repair a plugin whose files drifted from the marketplace under an unchanged
|
|
191
|
+
* version — uninstall, then install again at the same scope.
|
|
192
|
+
*
|
|
193
|
+
* `claude plugin install` cannot do this alone. Measured against the real
|
|
194
|
+
* `MadAppGang/magus` marketplace (autotest/plugin-system/probe-05): with the
|
|
195
|
+
* version unchanged it reports *"Plugin is already installed"* and copies
|
|
196
|
+
* nothing — cache content and the recorded `gitCommitSha` both stay put.
|
|
197
|
+
* Uninstall-then-install refreshes both. That is why the only cure users ever
|
|
198
|
+
* found was removing the plugin and adding it back.
|
|
199
|
+
*
|
|
200
|
+
* The window between the two calls is real: if the install fails, the plugin is
|
|
201
|
+
* left uninstalled. The caller must surface that rather than swallow it, which
|
|
202
|
+
* is why the install error is rethrown with the state spelled out.
|
|
203
|
+
*/
|
|
204
|
+
export async function repairPlugin(
|
|
205
|
+
pluginId: string,
|
|
206
|
+
scope: PluginScope = "user",
|
|
207
|
+
projectPath?: string,
|
|
208
|
+
): Promise<void> {
|
|
209
|
+
await uninstallPlugin(pluginId, scope, projectPath);
|
|
210
|
+
try {
|
|
211
|
+
await execClaude(["plugin", "install", pluginId, "--scope", scope], 60000);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
214
|
+
throw new Error(
|
|
215
|
+
`${pluginId} was uninstalled from ${scope} scope but could not be reinstalled: ${msg}. Reinstall it manually with: claude plugin install ${pluginId} --scope ${scope}`,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Enable a previously disabled plugin
|
|
222
|
+
*/
|
|
223
|
+
export async function enablePlugin(
|
|
224
|
+
pluginId: string,
|
|
225
|
+
scope: PluginScope = "user",
|
|
226
|
+
): Promise<void> {
|
|
227
|
+
await execClaude(["plugin", "enable", pluginId, "--scope", scope]);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Disable a plugin without uninstalling it
|
|
232
|
+
*/
|
|
233
|
+
export async function disablePlugin(
|
|
234
|
+
pluginId: string,
|
|
235
|
+
scope: PluginScope = "user",
|
|
236
|
+
): Promise<void> {
|
|
237
|
+
await execClaude(["plugin", "disable", pluginId, "--scope", scope]);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Update an ALREADY-INSTALLED plugin to the latest version.
|
|
242
|
+
*
|
|
243
|
+
* Runs `claude plugin update`, not `claude plugin install`.
|
|
244
|
+
*
|
|
245
|
+
* This used to run `install`, on the documented premise that it "handles both
|
|
246
|
+
* fresh installs and re-installs (upgrades) of existing plugins". That premise
|
|
247
|
+
* is false, and every magus update path was silently doing nothing because
|
|
248
|
+
* of it. Measured against Claude Code 2.1.252, with designer@magus at 0.6.0 and
|
|
249
|
+
* 0.6.1 published:
|
|
250
|
+
*
|
|
251
|
+
* $ claude plugin install designer@magus --scope project
|
|
252
|
+
* Installing plugin "designer@magus"...
|
|
253
|
+
* ✔ Plugin "designer@magus" is already installed (scope: project)
|
|
254
|
+
* EXIT: 0
|
|
255
|
+
*
|
|
256
|
+
* $ claude plugin update designer@magus --scope project
|
|
257
|
+
* Checking for updates for plugin "designer@magus" at project scope…
|
|
258
|
+
* ✔ Plugin "designer" updated from 0.6.0 to 0.6.1 for scope project. Restart to apply.
|
|
259
|
+
* EXIT: 0
|
|
260
|
+
*
|
|
261
|
+
* `install` is idempotent on (plugin, scope) and never looks at the version, so
|
|
262
|
+
* it exits 0 having done nothing — indistinguishable from success to every
|
|
263
|
+
* caller. The registry keeps the old version, the read-back in `update-engine`
|
|
264
|
+
* records that old version as "what landed", and the same update is offered
|
|
265
|
+
* again on the next run, for ever.
|
|
266
|
+
*
|
|
267
|
+
* The two commands are NOT interchangeable in the other direction either:
|
|
268
|
+
* `claude plugin update` on a plugin absent from this scope reports "already at
|
|
269
|
+
* the latest version" and installs nothing. A fresh install must go through
|
|
270
|
+
* {@link installPlugin}, and content drift under an unchanged version still
|
|
271
|
+
* needs {@link repairPlugin} — `update` has no version bump to act on there.
|
|
272
|
+
*
|
|
273
|
+
* Retries with marketplace update on "not found" errors (same as installPlugin).
|
|
274
|
+
*/
|
|
275
|
+
export async function updatePlugin(
|
|
276
|
+
pluginId: string,
|
|
277
|
+
scope: PluginScope = "user",
|
|
278
|
+
): Promise<void> {
|
|
279
|
+
await execPluginCommand("update", pluginId, scope, 60000);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Add a marketplace by GitHub repo (e.g., "MadAppGang/magus")
|
|
284
|
+
* Uses longer timeout since this involves cloning a git repo
|
|
285
|
+
*/
|
|
286
|
+
export async function addMarketplace(repo: string): Promise<void> {
|
|
287
|
+
await execClaude(["plugin", "marketplace", "add", repo], 60000);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Update marketplace cache by running git pull via Claude CLI
|
|
292
|
+
* Uses longer timeout since this involves git network operations
|
|
293
|
+
*/
|
|
294
|
+
export async function updateMarketplace(name: string): Promise<void> {
|
|
295
|
+
await execClaude(["plugin", "marketplace", "update", name], 60000);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Check if the claude CLI is available
|
|
300
|
+
* @returns true if claude CLI is found in PATH
|
|
301
|
+
*/
|
|
302
|
+
export async function isClaudeAvailable(): Promise<boolean> {
|
|
303
|
+
return (await which("claude")) !== null;
|
|
304
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { which } from "../utils/command-utils.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Execute claude CLI with provided arguments
|
|
6
|
+
* @param args - Arguments to pass to claude
|
|
7
|
+
* @returns Exit code from claude process
|
|
8
|
+
*/
|
|
9
|
+
export async function runClaude(args: string[]): Promise<number> {
|
|
10
|
+
// Check if claude exists
|
|
11
|
+
const claudePath = await which("claude");
|
|
12
|
+
if (!claudePath) {
|
|
13
|
+
console.error("Error: claude CLI not found in PATH");
|
|
14
|
+
console.error("Install with: npm install -g @anthropic-ai/claude-code");
|
|
15
|
+
return 1;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Spawn claude with full TTY inheritance using the resolved path
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
const proc = spawn(claudePath, args, {
|
|
21
|
+
stdio: "inherit", // Pass through stdin, stdout, stderr
|
|
22
|
+
shell: false, // Use full path, no shell needed (fixes DEP0190)
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
proc.on("exit", (code) => resolve(code || 0));
|
|
26
|
+
proc.on("error", (err) => {
|
|
27
|
+
console.error("Failed to run claude:", err.message);
|
|
28
|
+
resolve(1);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|