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,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `magus install [profile] [--check] [--yes]`
|
|
3
|
+
*
|
|
4
|
+
* Materialize a committed team profile from .claude/profiles.json onto this
|
|
5
|
+
* machine: register marketplaces, install pinned plugins, install the binaries
|
|
6
|
+
* plugins declare (requires.bin) plus the profile's cliTools, install skills,
|
|
7
|
+
* collect required env, then activate the profile via symlinks.
|
|
8
|
+
*
|
|
9
|
+
* With no argument it installs the union of every profile's deps and
|
|
10
|
+
* materializes them all (so `profile switch` needs no reinstall), then
|
|
11
|
+
* activates one. `--check` is a read-only drift gate for CI. `--yes` skips
|
|
12
|
+
* the confirmation prompt.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { installPlugin, isClaudeAvailable } from "../services/claude-cli.js";
|
|
17
|
+
import {
|
|
18
|
+
readInstalledPluginsRegistry,
|
|
19
|
+
readLocalSettings,
|
|
20
|
+
writeLocalSettings,
|
|
21
|
+
} from "../services/claude-settings.js";
|
|
22
|
+
import { ensureGitignoreEntries } from "../services/conventions-manager.js";
|
|
23
|
+
import {
|
|
24
|
+
computeEnvStatus,
|
|
25
|
+
computeVersionDrift,
|
|
26
|
+
summarizeClosure,
|
|
27
|
+
} from "../services/install-plan.js";
|
|
28
|
+
import { validateManifest } from "../services/manifest.js";
|
|
29
|
+
import { registerClosureMarketplaces } from "../services/marketplace-sync.js";
|
|
30
|
+
import { materializeProfile } from "../services/profile-materializer.js";
|
|
31
|
+
import { detectProfileDrift } from "../services/profile-sync.js";
|
|
32
|
+
import { resolveAllProfiles, resolveProfile } from "../services/resolver.js";
|
|
33
|
+
import { installSkill } from "../services/skills-manager.js";
|
|
34
|
+
import {
|
|
35
|
+
PROFILE_GITIGNORE_ENTRIES,
|
|
36
|
+
activateProfile,
|
|
37
|
+
activeProfile,
|
|
38
|
+
} from "../services/symlink-manager.js";
|
|
39
|
+
import {
|
|
40
|
+
TOOLCHAIN_BOOTSTRAP,
|
|
41
|
+
binInstallCommand,
|
|
42
|
+
detectToolchains,
|
|
43
|
+
} from "../services/toolchain.js";
|
|
44
|
+
import type {
|
|
45
|
+
ProfileSkillRef,
|
|
46
|
+
ResolvedBin,
|
|
47
|
+
ResolvedClosure,
|
|
48
|
+
SkillInfo,
|
|
49
|
+
} from "../types/index.js";
|
|
50
|
+
import { formatCommand, resolveExecutable } from "../utils/command-utils.js";
|
|
51
|
+
import { runCommand, runShellScript } from "../utils/run.js";
|
|
52
|
+
import { ensureManifest } from "./bootstrap.js";
|
|
53
|
+
import { confirm, promptValue } from "./prompt.js";
|
|
54
|
+
|
|
55
|
+
interface InstallFlags {
|
|
56
|
+
check: boolean;
|
|
57
|
+
yes: boolean;
|
|
58
|
+
force: boolean;
|
|
59
|
+
profile?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parseArgs(args: string[]): InstallFlags {
|
|
63
|
+
return {
|
|
64
|
+
check: args.includes("--check"),
|
|
65
|
+
yes: args.includes("--yes") || args.includes("-y"),
|
|
66
|
+
force: args.includes("--force"),
|
|
67
|
+
profile: args.find((a) => !a.startsWith("-")),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── --check: read-only drift gate ───────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
async function runCheck(
|
|
74
|
+
closure: ResolvedClosure,
|
|
75
|
+
strict: boolean,
|
|
76
|
+
): Promise<number> {
|
|
77
|
+
const registry = await readInstalledPluginsRegistry();
|
|
78
|
+
const drift = computeVersionDrift(closure, registry);
|
|
79
|
+
if (drift.length === 0) {
|
|
80
|
+
console.log("✓ No plugin version drift.");
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
console.log("Plugin version drift:");
|
|
84
|
+
for (const d of drift) {
|
|
85
|
+
console.log(
|
|
86
|
+
` ✗ ${d.pluginId} pinned ${d.pinned}, installed ${d.installed ?? "none"}`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
if (strict) {
|
|
90
|
+
console.error("\nstrictVersions is on — failing.");
|
|
91
|
+
return 1;
|
|
92
|
+
}
|
|
93
|
+
console.log("\n(strictVersions is off — reporting only.)");
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ── install steps ───────────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
async function ensureToolchains(
|
|
100
|
+
closure: ResolvedClosure,
|
|
101
|
+
yes: boolean,
|
|
102
|
+
): Promise<void> {
|
|
103
|
+
// Only the binaries actually missing can need a toolchain. Checking all of
|
|
104
|
+
// them warns "pip isn't installed" on a machine whose pip-provided binaries
|
|
105
|
+
// are already on PATH — an alarming message about work that will not happen.
|
|
106
|
+
const missing: ResolvedBin[] = [];
|
|
107
|
+
for (const bin of closure.bins) {
|
|
108
|
+
if (!(await resolveExecutable(bin.name))) missing.push(bin);
|
|
109
|
+
}
|
|
110
|
+
if (missing.length === 0) return;
|
|
111
|
+
|
|
112
|
+
const toolchains = await detectToolchains(missing);
|
|
113
|
+
for (const tc of toolchains) {
|
|
114
|
+
if (tc.present) continue;
|
|
115
|
+
const bootstrap = TOOLCHAIN_BOOTSTRAP[tc.name];
|
|
116
|
+
if (!bootstrap) {
|
|
117
|
+
console.warn(
|
|
118
|
+
`⚠ ${tc.name} is required by ${tc.requiredBy.join(", ")} but isn't installed and can't be auto-installed. Install it and re-run.`,
|
|
119
|
+
);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const ok =
|
|
123
|
+
yes ||
|
|
124
|
+
(await confirm(
|
|
125
|
+
`${tc.name} is missing (needed by ${tc.requiredBy.join(", ")}). Install it?`,
|
|
126
|
+
));
|
|
127
|
+
if (ok) {
|
|
128
|
+
console.log(`Installing ${tc.name}…`);
|
|
129
|
+
// A bootstrap line IS shell text — bun's contains a pipe, Homebrew's a
|
|
130
|
+
// command substitution — and it is a constant in this repo, not
|
|
131
|
+
// profile data. This is the only legitimate caller.
|
|
132
|
+
if (!(await runShellScript(bootstrap))) {
|
|
133
|
+
console.warn(`⚠ Failed to install ${tc.name}. Continuing.`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Install the closure's plugins at PROJECT scope.
|
|
141
|
+
*
|
|
142
|
+
* A profile is a property of one repo, so its plugins must be too. At user
|
|
143
|
+
* scope, adopting profiles in a single repo would enable that repo's plugins in
|
|
144
|
+
* every other project on the machine — and since Claude Code resolves
|
|
145
|
+
* enabledPlugins per id with fall-through, those would stay on everywhere.
|
|
146
|
+
* Project scope keeps the blast radius to this repo. The plugin *cache* is
|
|
147
|
+
* global either way, so `profile switch` stays offline.
|
|
148
|
+
*
|
|
149
|
+
* Whatever the CLI writes into .claude/settings.json here is transient:
|
|
150
|
+
* materialization rewrites that file from the manifest, and activation replaces
|
|
151
|
+
* it with a symlink. The manifest stays authoritative.
|
|
152
|
+
*/
|
|
153
|
+
async function installPlugins(closure: ResolvedClosure): Promise<void> {
|
|
154
|
+
for (const pluginId of Object.keys(closure.plugins)) {
|
|
155
|
+
try {
|
|
156
|
+
console.log(`+ plugin ${pluginId}`);
|
|
157
|
+
await installPlugin(pluginId, "project");
|
|
158
|
+
} catch (e) {
|
|
159
|
+
console.warn(`⚠ plugin ${pluginId}: ${(e as Error).message}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function installBins(bins: ResolvedBin[]): Promise<void> {
|
|
165
|
+
for (const bin of bins) {
|
|
166
|
+
if (await resolveExecutable(bin.name)) {
|
|
167
|
+
continue; // already installed and runnable (not a dangling link)
|
|
168
|
+
}
|
|
169
|
+
const cmd = binInstallCommand(bin);
|
|
170
|
+
// Printed rendered, executed as argv. The package name in `cmd.args` came
|
|
171
|
+
// from a profile or a plugin manifest, so it must never reach a shell.
|
|
172
|
+
console.log(`+ ${bin.name} (${formatCommand(cmd)})`);
|
|
173
|
+
if (!(await runCommand(cmd))) {
|
|
174
|
+
console.warn(`⚠ failed to install ${bin.name} via ${bin.via}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Map a manifest skill ref to the SkillInfo shape installSkill expects. */
|
|
180
|
+
function toSkillInfo(ref: ProfileSkillRef): SkillInfo {
|
|
181
|
+
return {
|
|
182
|
+
id: `${ref.repo}/${ref.path}`,
|
|
183
|
+
name: ref.name,
|
|
184
|
+
source: {
|
|
185
|
+
label: ref.repo,
|
|
186
|
+
repo: ref.repo,
|
|
187
|
+
skillsPath: path.dirname(ref.path),
|
|
188
|
+
},
|
|
189
|
+
repoPath: ref.path,
|
|
190
|
+
gitBlobSha: "",
|
|
191
|
+
frontmatter: null,
|
|
192
|
+
installed: false,
|
|
193
|
+
installedScope: null,
|
|
194
|
+
hasUpdate: false,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function installSkills(
|
|
199
|
+
skills: ProfileSkillRef[],
|
|
200
|
+
projectPath: string,
|
|
201
|
+
): Promise<void> {
|
|
202
|
+
for (const ref of skills) {
|
|
203
|
+
try {
|
|
204
|
+
console.log(`+ skill ${ref.name}`);
|
|
205
|
+
await installSkill(toSkillInfo(ref), "project", projectPath);
|
|
206
|
+
} catch (e) {
|
|
207
|
+
console.warn(`⚠ skill ${ref.name}: ${(e as Error).message}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function collectEnv(
|
|
213
|
+
closure: ResolvedClosure,
|
|
214
|
+
projectPath: string,
|
|
215
|
+
yes: boolean,
|
|
216
|
+
): Promise<void> {
|
|
217
|
+
const status = computeEnvStatus(closure);
|
|
218
|
+
if (status.missingRequired.length === 0) return;
|
|
219
|
+
|
|
220
|
+
console.log("\nMissing required environment variables:");
|
|
221
|
+
if (yes) {
|
|
222
|
+
for (const name of status.missingRequired)
|
|
223
|
+
console.log(` ! ${name} (unset)`);
|
|
224
|
+
console.log(
|
|
225
|
+
"Run without --yes to enter values, or set them in .claude/settings.local.json.",
|
|
226
|
+
);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const local = await readLocalSettings(projectPath);
|
|
231
|
+
local.env = local.env ?? {};
|
|
232
|
+
for (const name of status.missingRequired) {
|
|
233
|
+
const value = await promptValue(` ${name} = `);
|
|
234
|
+
if (value) local.env[name] = value;
|
|
235
|
+
}
|
|
236
|
+
await writeLocalSettings(local, projectPath);
|
|
237
|
+
console.log("Saved to .claude/settings.local.json (personal, gitignored).");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ── entry point ──────────────────────────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
export async function runInstallCommand(
|
|
243
|
+
args: string[],
|
|
244
|
+
projectPath: string = process.cwd(),
|
|
245
|
+
): Promise<number> {
|
|
246
|
+
const flags = parseArgs(args);
|
|
247
|
+
|
|
248
|
+
// A repo with no manifest is offered adoption rather than an error — see
|
|
249
|
+
// cli/bootstrap.ts for why there is no manifest-free install path.
|
|
250
|
+
// `--yes` is deliberately NOT forwarded: it means "do not ask me about the
|
|
251
|
+
// thing I invoked", and someone running `magus install --yes` in CI
|
|
252
|
+
// invoked an install, not the authoring of their team's committed manifest.
|
|
253
|
+
// Creating that file needs a human at the prompt, or `profile init`.
|
|
254
|
+
const manifest = await ensureManifest(projectPath, {
|
|
255
|
+
check: flags.check,
|
|
256
|
+
});
|
|
257
|
+
if (!manifest) return 1;
|
|
258
|
+
|
|
259
|
+
const errors = validateManifest(manifest);
|
|
260
|
+
if (errors.length > 0) {
|
|
261
|
+
console.error("Invalid .claude/profiles.json:");
|
|
262
|
+
for (const e of errors) console.error(` ${e.path}: ${e.message}`);
|
|
263
|
+
return 1;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const profileIds = Object.keys(manifest.profiles);
|
|
267
|
+
if (flags.profile && !manifest.profiles[flags.profile]) {
|
|
268
|
+
console.error(
|
|
269
|
+
`Profile "${flags.profile}" not found. Available: ${profileIds.join(", ")}`,
|
|
270
|
+
);
|
|
271
|
+
return 1;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Closure: a single profile, or the union of all.
|
|
275
|
+
const closure = flags.profile
|
|
276
|
+
? await resolveProfile(manifest, flags.profile)
|
|
277
|
+
: await resolveAllProfiles(manifest);
|
|
278
|
+
|
|
279
|
+
if (flags.check) {
|
|
280
|
+
return runCheck(closure, manifest.strictVersions === true);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Drift guard: refuse to clobber unsynced local edits to the active profile
|
|
284
|
+
// (materialize would overwrite them) unless --force is given.
|
|
285
|
+
if (!flags.force) {
|
|
286
|
+
const active = await activeProfile(projectPath);
|
|
287
|
+
if (active && (flags.profile === undefined || flags.profile === active)) {
|
|
288
|
+
const drift = await detectProfileDrift(active, projectPath);
|
|
289
|
+
if (drift.length > 0) {
|
|
290
|
+
console.error(
|
|
291
|
+
`Active profile "${active}" has unsynced local edits that install would overwrite:`,
|
|
292
|
+
);
|
|
293
|
+
for (const c of drift) console.error(` ${c.kind}: ${c.detail}`);
|
|
294
|
+
console.error(
|
|
295
|
+
"\nRun `magus profile sync` to promote them into .claude/profiles.json,\n" +
|
|
296
|
+
"or re-run with --force to discard them.",
|
|
297
|
+
);
|
|
298
|
+
return 1;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Plan
|
|
304
|
+
console.log(`\nPlan for ${flags.profile ?? "all profiles"}:`);
|
|
305
|
+
for (const line of summarizeClosure(closure)) console.log(` ${line}`);
|
|
306
|
+
if (closure.conflicts?.length) {
|
|
307
|
+
console.log(
|
|
308
|
+
"\n⚠ Version conflicts across profiles (resolved to the higher pin):",
|
|
309
|
+
);
|
|
310
|
+
for (const c of closure.conflicts) console.log(` ${c}`);
|
|
311
|
+
}
|
|
312
|
+
console.log();
|
|
313
|
+
|
|
314
|
+
if (!flags.yes && !(await confirm("Apply?"))) {
|
|
315
|
+
console.log("Aborted.");
|
|
316
|
+
return 0;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (!(await isClaudeAvailable())) {
|
|
320
|
+
console.error(
|
|
321
|
+
"claude CLI not found on PATH — cannot install plugins/marketplaces.",
|
|
322
|
+
);
|
|
323
|
+
return 1;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
await ensureToolchains(closure, flags.yes);
|
|
327
|
+
await registerClosureMarketplaces(closure.marketplaces);
|
|
328
|
+
await installPlugins(closure);
|
|
329
|
+
await installBins(closure.bins);
|
|
330
|
+
|
|
331
|
+
// Materialize every profile (so `profile switch` needs no reinstall), then
|
|
332
|
+
// activate the target (or the sole/first profile).
|
|
333
|
+
//
|
|
334
|
+
// Every profile is materialized even when one was named: a profile arg scopes
|
|
335
|
+
// what gets *installed* and which becomes active, not which settings files
|
|
336
|
+
// exist. It also keeps the union below consistent across all of them — a
|
|
337
|
+
// profile left holding an older union would stop disabling plugins added
|
|
338
|
+
// since, silently making `switch` additive again.
|
|
339
|
+
const unionPluginIds = Object.keys(
|
|
340
|
+
(flags.profile ? await resolveAllProfiles(manifest) : closure).plugins,
|
|
341
|
+
);
|
|
342
|
+
for (const id of profileIds) {
|
|
343
|
+
const profileClosure = await resolveProfile(manifest, id);
|
|
344
|
+
await materializeProfile(id, profileClosure, projectPath, unionPluginIds);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Keep the generated dir + active-profile symlinks out of git before
|
|
348
|
+
// activating (source of truth is .claude/profiles.json).
|
|
349
|
+
await ensureGitignoreEntries(
|
|
350
|
+
path.join(projectPath, ".gitignore"),
|
|
351
|
+
PROFILE_GITIGNORE_ENTRIES,
|
|
352
|
+
"# magus: generated profile build output + active-profile symlinks",
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
const activeId =
|
|
356
|
+
flags.profile ?? (await pickActiveProfile(profileIds, flags.yes));
|
|
357
|
+
if (activeId) {
|
|
358
|
+
await activateProfile(activeId, projectPath);
|
|
359
|
+
console.log(`\n✓ Active profile: ${activeId}`);
|
|
360
|
+
// Skills route through the now-active .claude/skills symlink.
|
|
361
|
+
const activeClosure = await resolveProfile(manifest, activeId);
|
|
362
|
+
await installSkills(activeClosure.skills, projectPath);
|
|
363
|
+
await collectEnv(activeClosure, projectPath, flags.yes);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
console.log("\nDone.");
|
|
367
|
+
return 0;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async function pickActiveProfile(
|
|
371
|
+
profileIds: string[],
|
|
372
|
+
yes: boolean,
|
|
373
|
+
): Promise<string | undefined> {
|
|
374
|
+
if (profileIds.length === 0) return undefined;
|
|
375
|
+
if (profileIds.length === 1) return profileIds[0];
|
|
376
|
+
if (yes) return profileIds[0];
|
|
377
|
+
console.log("\nWhich profile should be active?");
|
|
378
|
+
profileIds.forEach((id, i) => console.log(` ${i + 1}) ${id}`));
|
|
379
|
+
const answer = await promptValue("Choose [1]: ");
|
|
380
|
+
const idx = answer ? Number.parseInt(answer, 10) - 1 : 0;
|
|
381
|
+
return profileIds[idx] ?? profileIds[0];
|
|
382
|
+
}
|