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
package/README.md
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# magus
|
|
2
|
+
|
|
3
|
+
Manage a Claude Code setup — plugins, MCP servers, skills, CLI tools and the
|
|
4
|
+
binaries plugins depend on — from a TUI, a CLI, or a committed team manifest.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
bun add -g magus-cli # recommended
|
|
10
|
+
npm install -g magus-cli # also works
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Updates go through magus itself, not the package manager:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
magus upgrade
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`upgrade` advances **magus**. `update` advances **your dependencies** — the
|
|
20
|
+
plugins, CLI tools and skills your profile declares. Same split as every package
|
|
21
|
+
manager, so the two are never confused.
|
|
22
|
+
|
|
23
|
+
You get a self-contained binary for your platform (an `optionalDependencies`
|
|
24
|
+
package gated on `os`/`cpu`). On a platform without a prebuilt binary the
|
|
25
|
+
launcher falls back to running from source under Bun, so the install still works.
|
|
26
|
+
|
|
27
|
+
## Two ways to use it
|
|
28
|
+
|
|
29
|
+
**Interactive** — run `magus` with no arguments for the TUI. Good for
|
|
30
|
+
exploring what is available and toggling things on one machine.
|
|
31
|
+
|
|
32
|
+
**Declarative** — commit a `.claude/profiles.json` and run `magus install`.
|
|
33
|
+
Good for making a teammate's machine match yours. This is the
|
|
34
|
+
[team configuration guide](docs/team-configuration.md).
|
|
35
|
+
|
|
36
|
+
## Commands
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
magus Open the interactive TUI
|
|
40
|
+
|
|
41
|
+
install [profile] Install a profile's plugins, binaries, skills and
|
|
42
|
+
env at the manifest's PINNED versions, then activate
|
|
43
|
+
it. No argument = every profile.
|
|
44
|
+
--check Report drift only, write nothing (CI gate)
|
|
45
|
+
--yes, -y Skip the confirmation prompt
|
|
46
|
+
--force Discard unsynced local edits instead of refusing
|
|
47
|
+
update [profile] Bring the ACTIVE profile up to date: install what it
|
|
48
|
+
declares but this machine lacks, and advance every
|
|
49
|
+
"latest" pin to the newest published version.
|
|
50
|
+
--check Report only, write nothing (exits 1 if anything is
|
|
51
|
+
missing or known to be behind)
|
|
52
|
+
--yes, -y Skip the confirmation prompt
|
|
53
|
+
profile list Show every profile; ● marks the active one
|
|
54
|
+
profile show <name> Print a profile's fully resolved closure
|
|
55
|
+
profile switch <name> Repoint the active profile — offline, no reinstall
|
|
56
|
+
profile init Write .claude/profiles.json from this project's setup
|
|
57
|
+
profile sync Promote local edits back into .claude/profiles.json
|
|
58
|
+
doctor [--fix] Check binary deps, profile symlinks, conventions
|
|
59
|
+
|
|
60
|
+
claude [args...] Check for plugin updates (1h cache), then run claude
|
|
61
|
+
--force, -f Force the update check
|
|
62
|
+
upgrade Update magus itself
|
|
63
|
+
--version, -v Version + update check
|
|
64
|
+
--help, -h This list
|
|
65
|
+
--theme light|dark Force the palette. Otherwise: MAGUS_THEME, then
|
|
66
|
+
TERM_THEME, then a 250 ms terminal query, then
|
|
67
|
+
COLORFGBG, then dark. Only the exact words
|
|
68
|
+
"light"/"dark" count.
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Light or dark
|
|
72
|
+
|
|
73
|
+
magus decides once at startup, first answer wins: `--theme`, then the
|
|
74
|
+
`MAGUS_THEME` variable, then `TERM_THEME`, then a bounded (250 ms) wait for
|
|
75
|
+
the terminal's OSC 10/11 reply — only when stdin and stdout are both TTYs — then
|
|
76
|
+
`COLORFGBG`, then dark. A word that is not exactly `light` or `dark` (so `auto`,
|
|
77
|
+
`Light`, an empty value) is no opinion and the next step runs; when the flag is
|
|
78
|
+
given a bad word, that is an error naming both accepted values.
|
|
79
|
+
|
|
80
|
+
`TERM_THEME` and `MAGUS_THEME` are read from the process environment, never
|
|
81
|
+
from a project's `.env`. Running from source needs `--no-env-file`, because Bun
|
|
82
|
+
otherwise autoloads the cwd's `.env` before any code runs; the package scripts
|
|
83
|
+
and the `bin/magus.js` source fallback already pass it, and the compiled
|
|
84
|
+
binary never autoloads. The OSC 10/11 query itself is emitted by OpenTUI
|
|
85
|
+
(0.1.107) when the renderer is created, regardless of the outcome; when an
|
|
86
|
+
earlier step answered, magus never awaits or uses the reply, so first paint
|
|
87
|
+
is not delayed and the terminal's answer has no influence.
|
|
88
|
+
|
|
89
|
+
### install vs update
|
|
90
|
+
|
|
91
|
+
They differ in one thing: whether the pins are allowed to move.
|
|
92
|
+
|
|
93
|
+
| | `install` | `update` |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| Versions | exactly what the manifest pins | advances every `"latest"` pin |
|
|
96
|
+
| Scope | every profile (or the one named) | the **active** profile |
|
|
97
|
+
| Also does | materializes + activates the profile | nothing to the manifest |
|
|
98
|
+
|
|
99
|
+
Both install whatever the profile declares and this machine lacks, so `update`
|
|
100
|
+
covers the "I ticked it but never installed it" case on its own.
|
|
101
|
+
|
|
102
|
+
**This is dependency management, not a package manager.** There is no resolver,
|
|
103
|
+
no lockfile and no version negotiation. The job is: everything the profile
|
|
104
|
+
declares is installed, and current.
|
|
105
|
+
|
|
106
|
+
That matters for exact pins. `claude plugin install` takes no version argument —
|
|
107
|
+
it installs whatever the marketplace publishes now — so a pin naming anything
|
|
108
|
+
else cannot be satisfied. `update` plans against the version it can actually
|
|
109
|
+
get, notes the shortfall, and moves on:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
dev@magus = 9.9.9 (manifest pins 4.6.1; the marketplace offers only 9.9.9)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
It records what actually landed, never the pin, and it does **not** fail over
|
|
116
|
+
the difference — an unsatisfiable pin is a standing property of the manifest,
|
|
117
|
+
not something a given run did wrong. Pin drift is gated where it always was:
|
|
118
|
+
`magus install --check` with `strictVersions` in the manifest.
|
|
119
|
+
|
|
120
|
+
**`update` only ever moves forward.** Nothing here can downgrade — the installer
|
|
121
|
+
takes no version — so an install that is already ahead of what the marketplace
|
|
122
|
+
publishes is left alone rather than dragged back. A plan that tried would emit a
|
|
123
|
+
step that runs, reports success, changes nothing, and reappears on every
|
|
124
|
+
subsequent run.
|
|
125
|
+
|
|
126
|
+
`--check` fails on things that are **missing or known to be behind**. It
|
|
127
|
+
deliberately does not fail on the two items that get re-fetched every run
|
|
128
|
+
regardless: an already-installed skill (a manifest skill ref carries no content
|
|
129
|
+
hash, so there is nothing to compare) and a present unpinned binary (no version
|
|
130
|
+
probe exists, only present/absent). A gate that can never pass gets switched
|
|
131
|
+
off, so it only fires on real drift.
|
|
132
|
+
|
|
133
|
+
Neither command needs a manifest to exist first. In a repo without
|
|
134
|
+
`.claude/profiles.json`, both offer to write one describing the project as it
|
|
135
|
+
stands — that is `magus profile init`, which you can also run on its own.
|
|
136
|
+
Adoption reads the project's own `.claude/settings.json`; if that enables
|
|
137
|
+
nothing it falls back to your user-scope settings and says so, because the file
|
|
138
|
+
is meant to be committed and one machine's global set is not a team contract.
|
|
139
|
+
Machine-only keys are stripped, and any settings value naming a path under your
|
|
140
|
+
home directory is flagged for review.
|
|
141
|
+
|
|
142
|
+
`--yes` alone will **not** create a manifest. It means "don't ask me about the
|
|
143
|
+
command I ran", and someone running `magus update --yes` in CI did not ask to
|
|
144
|
+
author their team's committed contract. Creating it needs a human at the prompt,
|
|
145
|
+
or `magus profile init`.
|
|
146
|
+
|
|
147
|
+
`magus doctor` exits non-zero when it finds a problem, so it works as a CI
|
|
148
|
+
check. So does `magus install --check`.
|
|
149
|
+
|
|
150
|
+
## What the TUI covers
|
|
151
|
+
|
|
152
|
+
| Screen | What it manages |
|
|
153
|
+
|---|---|
|
|
154
|
+
| Plugins | Install, enable, disable; shows version changes and newly installed plugins |
|
|
155
|
+
| Skills | Browse and install skills from configured skill repos |
|
|
156
|
+
| MCP | Add MCP servers from a curated catalog |
|
|
157
|
+
| Settings | Claude Code settings, from a catalog of known keys |
|
|
158
|
+
| Profiles | Saved plugin sets (the TUI's own, older profile concept) |
|
|
159
|
+
| CLI Tools | Install claudish, mnemex and friends via the right package manager |
|
|
160
|
+
| Git State | Gitignore conventions and repo hygiene |
|
|
161
|
+
| Alias | Shell alias + flag management for launching `claude` |
|
|
162
|
+
| Styles | Communication style presets, composed into one native output style |
|
|
163
|
+
|
|
164
|
+
Navigate with `↑/↓` or `j/k`, `Enter` to select, `r` to refresh, `?` for help,
|
|
165
|
+
`q`/`Escape` to go back. Number keys `1`–`9` jump straight to a screen.
|
|
166
|
+
|
|
167
|
+
## Styles
|
|
168
|
+
|
|
169
|
+
Claude Code activates exactly one output style at a time. The Styles screen (`9`)
|
|
170
|
+
composes several rule blocks into that one style, so choices that would otherwise
|
|
171
|
+
compete for the single slot end up in one file.
|
|
172
|
+
|
|
173
|
+
One **verbosity axis** — pick exactly one of `direct`, `explanatory`, or `terse` —
|
|
174
|
+
plus any number of free modifiers:
|
|
175
|
+
|
|
176
|
+
| Preset | Effect |
|
|
177
|
+
|---|---|
|
|
178
|
+
| `direct` | Lead with the answer, minimal preamble |
|
|
179
|
+
| `explanatory` | Teach as you go, more context per step |
|
|
180
|
+
| `terse` | Shortest useful output |
|
|
181
|
+
| `no-slop` | Bans filler, hype, and AI vocabulary |
|
|
182
|
+
| `asd-ste100` | ASD-STE100 Simplified Technical English: sentences that survive one read |
|
|
183
|
+
| `evidence-first` | Claims must carry a citation, command output, or file reference |
|
|
184
|
+
| `calibrated` | State confidence honestly; no false certainty |
|
|
185
|
+
| `plain-language` | Prefer plain words over jargon |
|
|
186
|
+
| `structured` | Headings, tables, and lists over long paragraphs |
|
|
187
|
+
| `terminology` | Enforce project-specific vocabulary, filled in from your codebase |
|
|
188
|
+
|
|
189
|
+
Two verbosity presets is the one combination that does not work: `terse` says "do
|
|
190
|
+
not explain unless asked" and `explanatory` says "explain the specific choice", so
|
|
191
|
+
together they cancel and the model resolves the contradiction differently every
|
|
192
|
+
turn. Everything else stacks.
|
|
193
|
+
|
|
194
|
+
The presets live in `src/data/styles/*.md` and are compiled into the binary, so
|
|
195
|
+
they are always the version this magus was built from.
|
|
196
|
+
|
|
197
|
+
### What gets written
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
.claude/output-styles/composed.md the composed rules (generated — do not hand-edit)
|
|
201
|
+
.claude/settings.json "outputStyle": "composed"
|
|
202
|
+
.claude/style.json the declaration — commit this to share it
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
With a profile active the file is named `composed-<profile>` and the choice is
|
|
206
|
+
also recorded in `.claude/profiles.json`, because `magus install` rebuilds
|
|
207
|
+
`settings.json` from the manifest and would otherwise drop it.
|
|
208
|
+
|
|
209
|
+
`style.json` is the reviewable statement of what the project wants; `composed.md`
|
|
210
|
+
is the artifact Claude Code reads. A teammate who clones the repo gets the
|
|
211
|
+
declaration, and magus tells them when their local artifact does not match it.
|
|
212
|
+
|
|
213
|
+
### Styles you already have
|
|
214
|
+
|
|
215
|
+
Output styles you wrote yourself compose too — magus finds them in
|
|
216
|
+
`~/.claude/output-styles/` and `.claude/output-styles/` and lists them alongside
|
|
217
|
+
the presets. Community styles can be fetched from their source repository on an
|
|
218
|
+
explicit action, and are cached outside `~/.claude/output-styles/` so a re-fetch
|
|
219
|
+
never overwrites something you wrote.
|
|
220
|
+
|
|
221
|
+
Built-in styles ship inside the Claude Code binary rather than on disk, so there
|
|
222
|
+
is no file to import until you capture one. `--discover` names the set the
|
|
223
|
+
installed version actually has — it was `Concise`, `Explanatory`, `Learning` and
|
|
224
|
+
`Proactive` on Claude Code 2.1.239, and Anthropic adds to it:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
bun scripts/capture-builtin.ts --discover # what Anthropic ships today
|
|
228
|
+
bun scripts/capture-builtin.ts --all # capture every built-in
|
|
229
|
+
bun scripts/capture-builtin.ts --check # what fell behind after an upgrade
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Each capture runs one real `claude -p` round trip behind a transparent proxy and
|
|
233
|
+
records the system prompt Claude Code actually sent, writing
|
|
234
|
+
`~/.claude/output-styles/builtin-<name>.md`. Captures are per machine and stamped
|
|
235
|
+
with the Claude Code version they came from — **re-run `--check` after every
|
|
236
|
+
upgrade**. Nothing is committed to this repo: the text is Anthropic's, and it
|
|
237
|
+
changes on their release schedule, not ours.
|
|
238
|
+
|
|
239
|
+
### Coding rules stay on
|
|
240
|
+
|
|
241
|
+
Every generated style sets `keep-coding-instructions: true`. Without that flag
|
|
242
|
+
Claude Code drops its own coding-discipline rules from the system prompt, and a
|
|
243
|
+
setting about how to *communicate* has no business switching off how code gets
|
|
244
|
+
written. `force-for-plugin` is never set either — that would override your own
|
|
245
|
+
`/output-style` choice.
|
|
246
|
+
|
|
247
|
+
## Files magus touches
|
|
248
|
+
|
|
249
|
+
| Path | Owner | What magus does |
|
|
250
|
+
|---|---|---|
|
|
251
|
+
| `.claude/profiles.json` | you, committed | reads it; `profile sync` writes to it |
|
|
252
|
+
| `.claude/_profiles/<name>/` | magus, gitignored | generated build output |
|
|
253
|
+
| `.claude/settings.json` | Claude Code | in profile mode, a symlink into `_profiles/` |
|
|
254
|
+
| `.claude/settings.local.json` | you, gitignored | env values collected during install |
|
|
255
|
+
| `.claude/style.json` | you, committed | the project's declared style — written on apply |
|
|
256
|
+
| `.claude/output-styles/composed*.md` | magus, committed | the generated style Claude Code reads |
|
|
257
|
+
| `.mcp.json` | Claude Code | in profile mode, a symlink into `_profiles/` |
|
|
258
|
+
| `~/.claude/plugins/*` | Claude Code | never written directly — always via the `claude` CLI |
|
|
259
|
+
|
|
260
|
+
magus never hand-edits Claude Code's own registries
|
|
261
|
+
(`installed_plugins.json`, `known_marketplaces.json`, the plugin cache). Those go
|
|
262
|
+
through `claude plugin ...` so Claude Code stays the single writer.
|
|
263
|
+
|
|
264
|
+
## Development
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
bun install
|
|
268
|
+
bun test # full suite
|
|
269
|
+
bun run typecheck
|
|
270
|
+
bun run lint
|
|
271
|
+
bun run src/main.tsx # run from source
|
|
272
|
+
bun run build:binaries # cross-compile all platform binaries + their packages
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Tests run per-file isolated in CI (`bun run test:ci`) because `mock.module`
|
|
276
|
+
leaks across files in a shared process.
|
|
277
|
+
|
|
278
|
+
**Do not bump `@opentui` past 0.1.x.** 0.4.x breaks `bun build --compile`: it
|
|
279
|
+
resolves tree-sitter workers eagerly, bypassing the `OTUI_ASSET_ROOT` override,
|
|
280
|
+
so a bare `import "@opentui/core"` crashes at import time in the compiled
|
|
281
|
+
binary. Re-run the compile spike before changing that pin.
|
|
282
|
+
|
|
283
|
+
## Releasing
|
|
284
|
+
|
|
285
|
+
Tag-driven. `.github/workflows/magus-release.yml` fires on `tools/magus/v*`:
|
|
286
|
+
|
|
287
|
+
1. **verify** — install, typecheck, `test:ci`, version and optional-deps gates
|
|
288
|
+
2. **binaries** — a matrix building each target on its **native** runner
|
|
289
|
+
(`bun --compile` cannot cross-compile in CI because opentui ships a
|
|
290
|
+
per-platform native library)
|
|
291
|
+
3. **publish** — platform packages first, main package last, so its
|
|
292
|
+
`optionalDependencies` already resolve
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
# bump "version" AND the three optionalDependencies to match
|
|
296
|
+
bun run check:optional-deps
|
|
297
|
+
git commit -am "feat(magus): vX.Y.Z - description"
|
|
298
|
+
git tag -a tools/magus/vX.Y.Z -m "Release message"
|
|
299
|
+
git push origin main --tags
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Auth is npm OIDC trusted publishing — the workflow references no secrets. Adding
|
|
303
|
+
a **new** platform target needs a one-time manual publish to create the package
|
|
304
|
+
name (OIDC can only authenticate against a package that already exists); see
|
|
305
|
+
[`docs/npm-trusted-publishing-setup.md`](../../docs/npm-trusted-publishing-setup.md).
|
|
306
|
+
|
|
307
|
+
## License
|
|
308
|
+
|
|
309
|
+
MIT
|
package/bin/magus.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* magus launcher.
|
|
4
|
+
*
|
|
5
|
+
* Prefers a prebuilt, self-contained binary shipped as a per-platform
|
|
6
|
+
* optionalDependency (magus-cli-<platform>-<arch>) — that binary embeds the Bun
|
|
7
|
+
* runtime, so it runs without Bun installed. Falls back to running from source
|
|
8
|
+
* via Bun for dev checkouts and unsupported platforms.
|
|
9
|
+
*
|
|
10
|
+
* Set MAGUS_NO_BINARY=1 to skip the prebuilt binary and force the source
|
|
11
|
+
* path. That escape hatch exists because the launcher PREFERS the binary: when
|
|
12
|
+
* a shipped binary cannot start on a given machine, without it magus is a
|
|
13
|
+
* hard block rather than a slow start.
|
|
14
|
+
*
|
|
15
|
+
* This file must stay self-contained and import only node: builtins. Its tests
|
|
16
|
+
* copy it into a throwaway tree, where any relative import would not resolve.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { spawnSync } from "node:child_process";
|
|
20
|
+
import { createRequire } from "node:module";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { dirname, join } from "node:path";
|
|
23
|
+
import { existsSync } from "node:fs";
|
|
24
|
+
import { constants } from "node:os";
|
|
25
|
+
|
|
26
|
+
const require = createRequire(import.meta.url);
|
|
27
|
+
const args = process.argv.slice(2);
|
|
28
|
+
const { platform, arch } = process;
|
|
29
|
+
|
|
30
|
+
const ISSUES_URL = "https://github.com/MadAppGang/magus/issues";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The four signals a person sends on purpose. Everything else is a crash.
|
|
34
|
+
*
|
|
35
|
+
* The rule is deliberately inverted from the obvious one. An allowlist of
|
|
36
|
+
* crash signals leaves holes: SIGABRT is what a Bun/JSC panic raises, which is
|
|
37
|
+
* exactly the "this binary is broken, use source" case, and it would fall
|
|
38
|
+
* through the allowlist into the old masked path.
|
|
39
|
+
*/
|
|
40
|
+
const USER_SIGNALS = new Set(["SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT"]);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* How long after exec a crash still counts as "it never started".
|
|
44
|
+
*
|
|
45
|
+
* An invalid code signature kills the binary in single-digit milliseconds, at
|
|
46
|
+
* exec, before any output. A `kill -9` against a running TUI, or an OOM kill,
|
|
47
|
+
* lands seconds later. Without this gate the fix would trade a silent exit 0
|
|
48
|
+
* for a surprise relaunch of an app the user deliberately killed.
|
|
49
|
+
*/
|
|
50
|
+
const CRASH_FALLBACK_WINDOW_MS = 2000;
|
|
51
|
+
|
|
52
|
+
/** Shell convention: a signal death is 128 + the signal number. */
|
|
53
|
+
function exitCodeForSignal(signal) {
|
|
54
|
+
const number = constants.signals[signal];
|
|
55
|
+
return typeof number === "number" ? 128 + number : 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Set when a binary was found and tried but could not deliver a result. It
|
|
59
|
+
// changes what the source path says if IT then fails: "no prebuilt binary for
|
|
60
|
+
// this platform" is true when none was installed and a flat contradiction when
|
|
61
|
+
// one was just reported as killed.
|
|
62
|
+
let binaryFailed = false;
|
|
63
|
+
|
|
64
|
+
// 1. Prefer the prebuilt platform binary, unless the source path is forced.
|
|
65
|
+
const forceSource = process.env.MAGUS_NO_BINARY === "1";
|
|
66
|
+
const pkgName = `magus-cli-${platform}-${arch}`;
|
|
67
|
+
let binaryPath = null;
|
|
68
|
+
try {
|
|
69
|
+
const pkgJson = require.resolve(`${pkgName}/package.json`);
|
|
70
|
+
const candidate = join(dirname(pkgJson), "bin", "magus");
|
|
71
|
+
if (existsSync(candidate)) binaryPath = candidate;
|
|
72
|
+
} catch {
|
|
73
|
+
// optional dep not installed for this platform — fall through
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (binaryPath && !forceSource) {
|
|
77
|
+
const startedAt = Date.now();
|
|
78
|
+
const result = spawnSync(binaryPath, args, { stdio: "inherit" });
|
|
79
|
+
const elapsedMs = Date.now() - startedAt;
|
|
80
|
+
|
|
81
|
+
if (!result.error) {
|
|
82
|
+
// `status === null` is the authoritative "did not exit normally" marker,
|
|
83
|
+
// NOT the truthiness of `signal`. Node writes an empty string into
|
|
84
|
+
// `signal` for a signal it has no name for (Linux real-time signals,
|
|
85
|
+
// SIGRTMIN+n), and "" is falsy — so branching on the signal would fall
|
|
86
|
+
// straight back into the `status ?? 0` this release exists to remove.
|
|
87
|
+
if (result.status !== null) process.exit(result.status);
|
|
88
|
+
|
|
89
|
+
const signalName = result.signal || "an unnamed signal";
|
|
90
|
+
const deliberate =
|
|
91
|
+
USER_SIGNALS.has(result.signal) ||
|
|
92
|
+
elapsedMs >= CRASH_FALLBACK_WINDOW_MS;
|
|
93
|
+
if (deliberate) process.exit(exitCodeForSignal(result.signal));
|
|
94
|
+
|
|
95
|
+
binaryFailed = true;
|
|
96
|
+
// The elapsed time is measured; "before producing any output" is not —
|
|
97
|
+
// the child ran under stdio: "inherit", so this process never saw its
|
|
98
|
+
// output and must not claim there was none.
|
|
99
|
+
console.error(
|
|
100
|
+
`magus: the prebuilt binary was killed by ${signalName} ${elapsedMs}ms after it started.\n` +
|
|
101
|
+
"\n" +
|
|
102
|
+
` binary: ${binaryPath}\n` +
|
|
103
|
+
` platform: ${platform}-${arch}\n` +
|
|
104
|
+
"\n" +
|
|
105
|
+
"On macOS this almost always means the binary's code signature is invalid and the\n" +
|
|
106
|
+
"kernel refused to run it. Confirm with:\n" +
|
|
107
|
+
"\n" +
|
|
108
|
+
` codesign --verify --strict "${binaryPath}"\n` +
|
|
109
|
+
"\n" +
|
|
110
|
+
"Falling back to running from source via Bun. To skip the binary permanently:\n" +
|
|
111
|
+
" export MAGUS_NO_BINARY=1\n" +
|
|
112
|
+
"\n" +
|
|
113
|
+
`Please report this: ${ISSUES_URL}`,
|
|
114
|
+
);
|
|
115
|
+
// fall through to the source path
|
|
116
|
+
} else {
|
|
117
|
+
// A binary that could not be executed AT ALL (ENOENT, EACCES, bad arch)
|
|
118
|
+
// also leaves status null. Say so and fall through.
|
|
119
|
+
binaryFailed = true;
|
|
120
|
+
console.error(
|
|
121
|
+
`magus: prebuilt binary could not start (${result.error.message}); falling back to source.`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 2. Fallback: run from source via Bun.
|
|
127
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
128
|
+
const mainSrc = join(here, "..", "src", "main.tsx");
|
|
129
|
+
// --no-env-file: Bun autoloads the cwd's .env into process.env before any user
|
|
130
|
+
// code runs, and the theme snapshot in main.tsx must precede any .env merge
|
|
131
|
+
// (loadProjectDotenv is the only merger). The compiled binary never autoloads.
|
|
132
|
+
const bunRun = spawnSync("bun", ["--no-env-file", mainSrc, ...args], {
|
|
133
|
+
stdio: "inherit",
|
|
134
|
+
});
|
|
135
|
+
if (bunRun.error) {
|
|
136
|
+
// Two different states reach here, and one message for both is a lie in one
|
|
137
|
+
// of them. A user who installed magus from npm on macOS 27 and has no
|
|
138
|
+
// Bun hits the FIRST branch — telling them no binary exists for their
|
|
139
|
+
// platform contradicts the diagnostic printed seconds earlier, and
|
|
140
|
+
// "use a supported platform" is advice for a problem they do not have.
|
|
141
|
+
if (binaryFailed) {
|
|
142
|
+
console.error(
|
|
143
|
+
`magus: the prebuilt binary for ${platform}-${arch} is unusable on this machine, and Bun is not installed to fall back to.\n` +
|
|
144
|
+
"\n" +
|
|
145
|
+
"Reinstall to pick up a working binary:\n" +
|
|
146
|
+
" npm install -g magus-cli@latest\n" +
|
|
147
|
+
"\n" +
|
|
148
|
+
"Or install Bun (https://bun.sh) so the source path can run.\n" +
|
|
149
|
+
`Please report this: ${ISSUES_URL}`,
|
|
150
|
+
);
|
|
151
|
+
} else {
|
|
152
|
+
console.error(
|
|
153
|
+
`magus: no prebuilt binary for ${platform}-${arch}, and Bun is not installed.`,
|
|
154
|
+
);
|
|
155
|
+
console.error("Install Bun (https://bun.sh) or use a supported platform.");
|
|
156
|
+
}
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
// Same masking bug, second site, keyed the same way: `status === null` decides,
|
|
160
|
+
// not the signal. There is no binary here and nowhere left to fall back to, so
|
|
161
|
+
// this reports and exits — no codesign hint, which would be nonsense for a Bun
|
|
162
|
+
// crash.
|
|
163
|
+
if (bunRun.status !== null) process.exit(bunRun.status);
|
|
164
|
+
if (!USER_SIGNALS.has(bunRun.signal)) {
|
|
165
|
+
console.error(
|
|
166
|
+
`magus: running from source was killed by ${bunRun.signal || "an unnamed signal"}.\n` +
|
|
167
|
+
`Please report this: ${ISSUES_URL}`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
process.exit(exitCodeForSignal(bunRun.signal));
|
package/package.json
CHANGED
|
@@ -1,7 +1,71 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magus-cli",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
|
+
"description": "TUI tool for managing Claude Code plugins, MCPs, and configuration",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/main.tsx",
|
|
7
|
+
"bin": {
|
|
8
|
+
"magus": "bin/magus.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "bun --no-env-file src/main.tsx",
|
|
12
|
+
"dev": "bun --watch --no-env-file src/main.tsx",
|
|
13
|
+
"typecheck": "tsc --noEmit",
|
|
14
|
+
"lint": "biome check src/",
|
|
15
|
+
"format": "biome format --write src/",
|
|
16
|
+
"test": "bun test",
|
|
17
|
+
"test:ci": "bun scripts/test-isolated.ts",
|
|
18
|
+
"test:update": "bun test --update-snapshots",
|
|
19
|
+
"build:binaries": "bun scripts/build-binaries.ts",
|
|
20
|
+
"check:optional-deps": "bun scripts/check-optional-deps.ts"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"claude",
|
|
24
|
+
"claude-code",
|
|
25
|
+
"mcp",
|
|
26
|
+
"plugins",
|
|
27
|
+
"tui",
|
|
28
|
+
"cli",
|
|
29
|
+
"opentui",
|
|
30
|
+
"react"
|
|
31
|
+
],
|
|
32
|
+
"author": "Jack Rudenko <i@madappgang.com>",
|
|
5
33
|
"license": "MIT",
|
|
6
|
-
"repository": {
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/MadAppGang/magus"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/MadAppGang/magus/issues"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/MadAppGang/magus/tree/main/tools/magus#readme",
|
|
42
|
+
"engines": {
|
|
43
|
+
"bun": ">=1.0.0"
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"bin/",
|
|
47
|
+
"src/",
|
|
48
|
+
"scripts/"
|
|
49
|
+
],
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@opentui/core": "^0.1.75",
|
|
52
|
+
"@opentui/react": "^0.1.75",
|
|
53
|
+
"chalk": "^5.3.0",
|
|
54
|
+
"fs-extra": "^11.2.0",
|
|
55
|
+
"react": "^19.0.0",
|
|
56
|
+
"semver": "^7.6.3"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@biomejs/biome": "^1.9.4",
|
|
60
|
+
"@types/fs-extra": "^11.0.4",
|
|
61
|
+
"@types/node": "^22.9.0",
|
|
62
|
+
"@types/react": "^19.0.0",
|
|
63
|
+
"@types/semver": "^7.5.8",
|
|
64
|
+
"typescript": "^5.6.3"
|
|
65
|
+
},
|
|
66
|
+
"optionalDependencies": {
|
|
67
|
+
"magus-cli-darwin-arm64": "7.0.0",
|
|
68
|
+
"magus-cli-darwin-x64": "7.0.0",
|
|
69
|
+
"magus-cli-linux-x64": "7.0.0"
|
|
70
|
+
}
|
|
7
71
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Build per-platform, self-contained magus binaries and their npm packages.
|
|
4
|
+
*
|
|
5
|
+
* Each target is compiled with `bun build --compile --target=bun-<os>-<arch>`,
|
|
6
|
+
* producing a binary that embeds the Bun runtime (runs without Bun installed).
|
|
7
|
+
* The output is a publishable package per platform under dist-binaries/:
|
|
8
|
+
*
|
|
9
|
+
* dist-binaries/magus-cli-darwin-arm64/
|
|
10
|
+
* package.json (os/cpu restricted, no `bin` — the launcher execs it)
|
|
11
|
+
* bin/magus (the compiled binary)
|
|
12
|
+
*
|
|
13
|
+
* The main `magus` package lists these as optionalDependencies; npm installs
|
|
14
|
+
* only the one matching the host os/cpu, and bin/magus.js execs it.
|
|
15
|
+
*
|
|
16
|
+
* Hard constraint: magus pins @opentui 0.1.x — 0.4.x breaks --compile.
|
|
17
|
+
*
|
|
18
|
+
* Hard constraint: every darwin binary is re-signed and verified here. The
|
|
19
|
+
* signature --compile leaves behind does not cover the appended bundle, and
|
|
20
|
+
* macOS 27 kills such a binary at exec. See src/services/binary-signing.ts.
|
|
21
|
+
*
|
|
22
|
+
* Hard constraint: the two --no-compile-autoload flags below must stay. A Bun
|
|
23
|
+
* standalone executable autoloads .env and bunfig.toml from its CURRENT
|
|
24
|
+
* DIRECTORY by default, and magus reads neither on purpose. Bun 1.4.0 dies
|
|
25
|
+
* in the .env path when that file is a symlink pointing at a FIFO — exactly how
|
|
26
|
+
* 1Password serves secrets into a git worktree — exiting 1 with nothing on
|
|
27
|
+
* stdout or stderr, because the crash lands before any JS runs and therefore
|
|
28
|
+
* before any error handler exists. A FIFO also hands its bytes to whoever opens
|
|
29
|
+
* it first, so an autoloading magus can swallow the secrets a dev server was
|
|
30
|
+
* waiting for. Dropping the flags re-arms both failures.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { $ } from "bun";
|
|
34
|
+
import { mkdir, writeFile, rm } from "node:fs/promises";
|
|
35
|
+
import path from "node:path";
|
|
36
|
+
import pkg from "../package.json";
|
|
37
|
+
import { signAndVerify } from "../src/services/binary-signing.js";
|
|
38
|
+
|
|
39
|
+
interface Target {
|
|
40
|
+
os: "darwin" | "linux";
|
|
41
|
+
cpu: "arm64" | "x64";
|
|
42
|
+
bunTarget: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const TARGETS: Target[] = [
|
|
46
|
+
{ os: "darwin", cpu: "arm64", bunTarget: "bun-darwin-arm64" },
|
|
47
|
+
{ os: "darwin", cpu: "x64", bunTarget: "bun-darwin-x64" },
|
|
48
|
+
{ os: "linux", cpu: "x64", bunTarget: "bun-linux-x64" },
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const version = (pkg as { version: string }).version;
|
|
52
|
+
const root = path.join(import.meta.dir, "..");
|
|
53
|
+
const distRoot = path.join(root, "dist-binaries");
|
|
54
|
+
const entry = path.join(root, "src", "main.tsx");
|
|
55
|
+
|
|
56
|
+
// Optional filter: `bun scripts/build-binaries.ts darwin-arm64`
|
|
57
|
+
const only = process.argv[2];
|
|
58
|
+
|
|
59
|
+
// Only a full build purges the whole tree. Scoping a filtered build to its own
|
|
60
|
+
// target means `build:binaries linux-x64` on a Linux host does not first wipe
|
|
61
|
+
// dist-binaries/ and then throw on the darwin guard below, leaving the user
|
|
62
|
+
// with neither the old output nor the one target they could actually build.
|
|
63
|
+
if (!only) await rm(distRoot, { recursive: true, force: true });
|
|
64
|
+
|
|
65
|
+
for (const t of TARGETS) {
|
|
66
|
+
const id = `${t.os}-${t.cpu}`;
|
|
67
|
+
if (only && only !== id) continue;
|
|
68
|
+
|
|
69
|
+
const pkgName = `magus-cli-${id}`;
|
|
70
|
+
const outDir = path.join(distRoot, pkgName);
|
|
71
|
+
const binPath = path.join(outDir, "bin", "magus");
|
|
72
|
+
|
|
73
|
+
// `codesign` is macOS-only, and an unsigned darwin binary is killed at exec
|
|
74
|
+
// by macOS 27. Building one anywhere else produces an artifact that cannot
|
|
75
|
+
// be gated, which is precisely how the broken binaries shipped.
|
|
76
|
+
if (t.os === "darwin" && process.platform !== "darwin") {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`Cannot build ${pkgName} on ${process.platform}: darwin binaries must be signed, and codesign exists only on macOS. Build this target on a macOS runner, or name a buildable one: bun run build:binaries linux-x64`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
await rm(outDir, { recursive: true, force: true });
|
|
83
|
+
await mkdir(path.join(outDir, "bin"), { recursive: true });
|
|
84
|
+
|
|
85
|
+
console.log(`Building ${pkgName} (${t.bunTarget})…`);
|
|
86
|
+
// The --no-compile-autoload flags are load-bearing; see the header.
|
|
87
|
+
await $`bun build --compile --no-compile-autoload-dotenv --no-compile-autoload-bunfig --target=${t.bunTarget} ${entry} --outfile ${binPath}`;
|
|
88
|
+
|
|
89
|
+
// --compile signs the Mach-O, then appends the bundle after it, leaving a
|
|
90
|
+
// signature that does not cover the file. Re-sign and refuse to continue if
|
|
91
|
+
// the result does not verify.
|
|
92
|
+
signAndVerify(t, binPath);
|
|
93
|
+
|
|
94
|
+
// Platform package: os/cpu-restricted, ships only the binary, declares NO
|
|
95
|
+
// `bin` (the main package's launcher resolves and execs bin/magus).
|
|
96
|
+
const platformPkg = {
|
|
97
|
+
name: pkgName,
|
|
98
|
+
version,
|
|
99
|
+
description: `Prebuilt magus binary for ${id}`,
|
|
100
|
+
os: [t.os],
|
|
101
|
+
cpu: [t.cpu],
|
|
102
|
+
files: ["bin/"],
|
|
103
|
+
license: (pkg as { license: string }).license,
|
|
104
|
+
repository: (pkg as { repository: unknown }).repository,
|
|
105
|
+
};
|
|
106
|
+
await writeFile(
|
|
107
|
+
path.join(outDir, "package.json"),
|
|
108
|
+
`${JSON.stringify(platformPkg, null, 2)}\n`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(`\nDone. Packages in ${path.relative(process.cwd(), distRoot)}/`);
|
|
113
|
+
console.log("Publish each with `npm publish` (then the main package).");
|