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,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `magus profile <list|show|switch|sync> [name]`
|
|
3
|
+
*
|
|
4
|
+
* Inspect and switch between team profiles materialized under
|
|
5
|
+
* `.claude/_profiles/`. Switching is cheap and offline — it just repoints the
|
|
6
|
+
* shared-config symlinks. `sync` (Phase 5) promotes local drift back into the
|
|
7
|
+
* committed manifest.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import fs from "fs-extra";
|
|
12
|
+
import { ensureGitignoreEntries } from "../services/conventions-manager.js";
|
|
13
|
+
import { checkBinaries, missingBinaries } from "../services/doctor-bins.js";
|
|
14
|
+
import { summarizeClosure } from "../services/install-plan.js";
|
|
15
|
+
import { getManifestPath, readManifest } from "../services/manifest.js";
|
|
16
|
+
import { syncProfile } from "../services/profile-sync.js";
|
|
17
|
+
import { resolveProfile } from "../services/resolver.js";
|
|
18
|
+
import {
|
|
19
|
+
PROFILE_GITIGNORE_ENTRIES,
|
|
20
|
+
activateProfile,
|
|
21
|
+
activeProfile,
|
|
22
|
+
profileDir,
|
|
23
|
+
} from "../services/symlink-manager.js";
|
|
24
|
+
import { ensureManifest } from "./bootstrap.js";
|
|
25
|
+
|
|
26
|
+
export async function runProfileCommand(args: string[]): Promise<number> {
|
|
27
|
+
const [sub, ...rest] = args;
|
|
28
|
+
const projectPath = process.cwd();
|
|
29
|
+
|
|
30
|
+
switch (sub) {
|
|
31
|
+
case undefined:
|
|
32
|
+
case "list":
|
|
33
|
+
return listProfiles(projectPath);
|
|
34
|
+
case "show":
|
|
35
|
+
return showProfile(rest[0], projectPath);
|
|
36
|
+
case "switch":
|
|
37
|
+
return switchProfile(rest[0], projectPath);
|
|
38
|
+
case "init":
|
|
39
|
+
return initProfile(
|
|
40
|
+
args.includes("--yes") || args.includes("-y"),
|
|
41
|
+
projectPath,
|
|
42
|
+
);
|
|
43
|
+
case "sync":
|
|
44
|
+
return syncActiveProfile(rest[0], projectPath);
|
|
45
|
+
default:
|
|
46
|
+
console.error(
|
|
47
|
+
`Unknown profile command "${sub}". Use: list | show <name> | switch <name> | init | sync`,
|
|
48
|
+
);
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* `profile init` — write a manifest describing this project as it stands.
|
|
55
|
+
*
|
|
56
|
+
* The same adoption `install`/`update` offer, available on its own so a repo
|
|
57
|
+
* can be brought under profile management without installing anything.
|
|
58
|
+
*/
|
|
59
|
+
export async function initProfile(
|
|
60
|
+
yes: boolean,
|
|
61
|
+
projectPath: string,
|
|
62
|
+
): Promise<number> {
|
|
63
|
+
const existing = await readManifest(projectPath);
|
|
64
|
+
if (Object.keys(existing.profiles).length > 0) {
|
|
65
|
+
console.error(
|
|
66
|
+
`${getManifestPath(projectPath)} already defines: ${Object.keys(existing.profiles).join(", ")}`,
|
|
67
|
+
);
|
|
68
|
+
console.error("Use `magus profile sync` to promote local edits into it.");
|
|
69
|
+
return 1;
|
|
70
|
+
}
|
|
71
|
+
// `profile init` exists to create the manifest, so it is the one caller that
|
|
72
|
+
// may do so unattended.
|
|
73
|
+
return (await ensureManifest(projectPath, { allowAdopt: yes })) ? 0 : 1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function listProfiles(projectPath: string): Promise<number> {
|
|
77
|
+
const manifest = await readManifest(projectPath);
|
|
78
|
+
const ids = Object.keys(manifest.profiles);
|
|
79
|
+
if (ids.length === 0) {
|
|
80
|
+
console.log("No profiles defined in .claude/profiles.json.");
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
const active = await activeProfile(projectPath);
|
|
84
|
+
console.log("Profiles:");
|
|
85
|
+
for (const id of ids) {
|
|
86
|
+
const entry = manifest.profiles[id];
|
|
87
|
+
const marker = id === active ? "●" : " ";
|
|
88
|
+
const materialized = (await fs.pathExists(profileDir(id, projectPath)))
|
|
89
|
+
? ""
|
|
90
|
+
: " (not installed)";
|
|
91
|
+
console.log(` ${marker} ${id} — ${entry.name}${materialized}`);
|
|
92
|
+
}
|
|
93
|
+
if (active) console.log(`\nActive: ${active}`);
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function showProfile(
|
|
98
|
+
name: string | undefined,
|
|
99
|
+
projectPath: string,
|
|
100
|
+
): Promise<number> {
|
|
101
|
+
if (!name) {
|
|
102
|
+
console.error("Usage: magus profile show <name>");
|
|
103
|
+
return 1;
|
|
104
|
+
}
|
|
105
|
+
const manifest = await readManifest(projectPath);
|
|
106
|
+
const entry = manifest.profiles[name];
|
|
107
|
+
if (!entry) {
|
|
108
|
+
console.error(
|
|
109
|
+
`Profile "${name}" not found. Available: ${Object.keys(manifest.profiles).join(", ")}`,
|
|
110
|
+
);
|
|
111
|
+
return 1;
|
|
112
|
+
}
|
|
113
|
+
const closure = await resolveProfile(manifest, name);
|
|
114
|
+
console.log(`Profile: ${name} (${entry.name})`);
|
|
115
|
+
for (const line of summarizeClosure(closure)) console.log(` ${line}`);
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function switchProfile(
|
|
120
|
+
name: string | undefined,
|
|
121
|
+
projectPath: string,
|
|
122
|
+
): Promise<number> {
|
|
123
|
+
if (!name) {
|
|
124
|
+
console.error("Usage: magus profile switch <name>");
|
|
125
|
+
return 1;
|
|
126
|
+
}
|
|
127
|
+
const manifest = await readManifest(projectPath);
|
|
128
|
+
if (!manifest.profiles[name]) {
|
|
129
|
+
console.error(
|
|
130
|
+
`Profile "${name}" not found. Available: ${Object.keys(manifest.profiles).join(", ")}`,
|
|
131
|
+
);
|
|
132
|
+
return 1;
|
|
133
|
+
}
|
|
134
|
+
if (!(await fs.pathExists(profileDir(name, projectPath)))) {
|
|
135
|
+
console.error(
|
|
136
|
+
`Profile "${name}" isn't installed yet. Run: magus install ${name}`,
|
|
137
|
+
);
|
|
138
|
+
return 1;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// In profile mode the source of truth is .claude/profiles.json; the
|
|
142
|
+
// generated dir AND the per-developer symlinks must stay out of git so
|
|
143
|
+
// switching profiles causes no churn.
|
|
144
|
+
await ensureGitignoreEntries(
|
|
145
|
+
path.join(projectPath, ".gitignore"),
|
|
146
|
+
PROFILE_GITIGNORE_ENTRIES,
|
|
147
|
+
"# magus: generated profile build output + active-profile symlinks",
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
await activateProfile(name, projectPath);
|
|
151
|
+
console.log(`✓ Switched to profile: ${name}`);
|
|
152
|
+
|
|
153
|
+
// Verify the profile's binary deps are actually usable — the switch itself
|
|
154
|
+
// succeeds (symlinks are valid), but warn so "switched" isn't misleading.
|
|
155
|
+
try {
|
|
156
|
+
const closure = await resolveProfile(manifest, name);
|
|
157
|
+
const missing = missingBinaries(await checkBinaries(closure.bins));
|
|
158
|
+
if (missing.length > 0) {
|
|
159
|
+
console.log(
|
|
160
|
+
`\n⚠ ${missing.length} binary dependenc${missing.length === 1 ? "y is" : "ies are"} missing:`,
|
|
161
|
+
);
|
|
162
|
+
for (const m of missing)
|
|
163
|
+
console.log(` ✗ ${m.name} — run: ${m.installCommand}`);
|
|
164
|
+
console.log(`Run \`magus install ${name}\` to install them.`);
|
|
165
|
+
}
|
|
166
|
+
} catch {
|
|
167
|
+
// Verification is best-effort — never fail a valid switch on it.
|
|
168
|
+
}
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export async function syncActiveProfile(
|
|
173
|
+
nameArg: string | undefined,
|
|
174
|
+
projectPath: string,
|
|
175
|
+
): Promise<number> {
|
|
176
|
+
const name = nameArg ?? (await activeProfile(projectPath));
|
|
177
|
+
if (!name) {
|
|
178
|
+
console.error(
|
|
179
|
+
"No active profile to sync. Pass a name: magus profile sync <name>",
|
|
180
|
+
);
|
|
181
|
+
return 1;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
const { changes, wrote } = await syncProfile(name, projectPath);
|
|
185
|
+
if (!wrote) {
|
|
186
|
+
console.log(`Profile "${name}" is already in sync with the manifest.`);
|
|
187
|
+
return 0;
|
|
188
|
+
}
|
|
189
|
+
console.log(`Synced "${name}" → .claude/profiles.json:`);
|
|
190
|
+
for (const c of changes) console.log(` ${c.kind}: ${c.detail}`);
|
|
191
|
+
console.log("\nReview the diff and commit .claude/profiles.json.");
|
|
192
|
+
return 0;
|
|
193
|
+
} catch (e) {
|
|
194
|
+
console.error((e as Error).message);
|
|
195
|
+
return 1;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal prompting shared by the non-interactive commands.
|
|
3
|
+
*
|
|
4
|
+
* These lived inside cli/install.ts until `update` and the adopt flow needed
|
|
5
|
+
* the same three. One copy, so a fix to (say) the Y/n default reaches every
|
|
6
|
+
* command at once.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createInterface } from "node:readline/promises";
|
|
10
|
+
|
|
11
|
+
/** Y/n prompt. Empty input means yes. */
|
|
12
|
+
export async function confirm(question: string): Promise<boolean> {
|
|
13
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
14
|
+
try {
|
|
15
|
+
const answer = (await rl.question(`${question} [Y/n] `))
|
|
16
|
+
.trim()
|
|
17
|
+
.toLowerCase();
|
|
18
|
+
return answer === "" || answer === "y" || answer === "yes";
|
|
19
|
+
} finally {
|
|
20
|
+
rl.close();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Free-text prompt, trimmed. */
|
|
25
|
+
export async function promptValue(question: string): Promise<string> {
|
|
26
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
27
|
+
try {
|
|
28
|
+
return (await rl.question(question)).trim();
|
|
29
|
+
} finally {
|
|
30
|
+
rl.close();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* magus subcommand router.
|
|
3
|
+
*
|
|
4
|
+
* Parses argv and dispatches to a command handler. Commands run to completion
|
|
5
|
+
* and return an exit code; bare invocation (or an unknown token) falls through
|
|
6
|
+
* to the interactive TUI, preserving magus's historical behavior.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ThemeMode } from "@opentui/core";
|
|
10
|
+
import { checkForUpdates } from "../services/version-check.js";
|
|
11
|
+
import { type ThemeEnv, parseThemeWord } from "../ui/theme-resolve.js";
|
|
12
|
+
import { runDoctorCommand } from "./doctor.js";
|
|
13
|
+
import { runInstallCommand } from "./install.js";
|
|
14
|
+
import { runModelsCommand } from "./models.js";
|
|
15
|
+
import { runProfileCommand } from "./profile.js";
|
|
16
|
+
import { runUpdateCommand } from "./update.js";
|
|
17
|
+
import { runUpgradeCommand } from "./upgrade.js";
|
|
18
|
+
|
|
19
|
+
export interface RouteOutcome {
|
|
20
|
+
/** A subcommand (or top-level flag) handled the invocation. */
|
|
21
|
+
handled: boolean;
|
|
22
|
+
/** Exit code to use when `handled` is true. */
|
|
23
|
+
exitCode?: number;
|
|
24
|
+
/** No subcommand matched — the caller should launch the TUI. */
|
|
25
|
+
launchTui: boolean;
|
|
26
|
+
/** From --theme, validated. Only meaningful when launchTui is true. */
|
|
27
|
+
theme?: ThemeMode;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type ThemeFlagParse =
|
|
31
|
+
| { ok: true; theme?: ThemeMode; rest: string[] }
|
|
32
|
+
| { ok: false; message: string };
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Pull `--theme light|dark` (or `--theme=…`) out of argv.
|
|
36
|
+
*
|
|
37
|
+
* Exact words only, the same rule every other theme source follows — so
|
|
38
|
+
* `--theme Dark` errors instead of silently differing from `TERM_THEME=Dark`.
|
|
39
|
+
* A flag is typed on purpose, so a bad value is an error rather than the "no
|
|
40
|
+
* opinion" an inherited variable gets. `--theme` as the last token, or followed
|
|
41
|
+
* by another `--flag`, is the same error.
|
|
42
|
+
*/
|
|
43
|
+
export function parseThemeFlag(argv: string[]): ThemeFlagParse {
|
|
44
|
+
const rest: string[] = [];
|
|
45
|
+
let theme: ThemeMode | undefined;
|
|
46
|
+
|
|
47
|
+
const invalid = (got: string | undefined): ThemeFlagParse => ({
|
|
48
|
+
ok: false,
|
|
49
|
+
message:
|
|
50
|
+
got === undefined
|
|
51
|
+
? 'magus: --theme must be "light" or "dark" (no value given)'
|
|
52
|
+
: `magus: --theme must be "light" or "dark" (got "${got}")`,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
56
|
+
const token = argv[i] as string;
|
|
57
|
+
let value: string | undefined;
|
|
58
|
+
|
|
59
|
+
if (token === "--theme") {
|
|
60
|
+
const next = argv[i + 1];
|
|
61
|
+
if (next === undefined || next.startsWith("--"))
|
|
62
|
+
return invalid(undefined);
|
|
63
|
+
value = next;
|
|
64
|
+
i += 1;
|
|
65
|
+
} else if (token.startsWith("--theme=")) {
|
|
66
|
+
value = token.slice("--theme=".length);
|
|
67
|
+
} else {
|
|
68
|
+
rest.push(token);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const parsed = parseThemeWord(value);
|
|
73
|
+
if (parsed === null) return invalid(value);
|
|
74
|
+
theme = parsed;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return theme === undefined ? { ok: true, rest } : { ok: true, theme, rest };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const HANDLED = (exitCode: number): RouteOutcome => ({
|
|
81
|
+
handled: true,
|
|
82
|
+
exitCode,
|
|
83
|
+
launchTui: false,
|
|
84
|
+
});
|
|
85
|
+
const LAUNCH_TUI: RouteOutcome = { handled: false, launchTui: true };
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Every name the switch below handles.
|
|
89
|
+
*
|
|
90
|
+
* Declared once so the theme resolution and the dispatch cannot disagree about
|
|
91
|
+
* what counts as a subcommand — a name in one and not the other would print an
|
|
92
|
+
* unthemed report, which is the bug this set exists to prevent.
|
|
93
|
+
*/
|
|
94
|
+
const SUBCOMMANDS = new Set([
|
|
95
|
+
"update",
|
|
96
|
+
"upgrade",
|
|
97
|
+
"install",
|
|
98
|
+
"profile",
|
|
99
|
+
"models",
|
|
100
|
+
"doctor",
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
export async function route(
|
|
104
|
+
argv: string[],
|
|
105
|
+
version: string,
|
|
106
|
+
/**
|
|
107
|
+
* The theme environment, snapshotted by main.tsx BEFORE it loads a project's
|
|
108
|
+
* .env. Optional only so a test may call `route` without one; production
|
|
109
|
+
* always passes it, and omitting it falls back to "no opinion" (dark) rather
|
|
110
|
+
* than reading a process.env that a project may by then have written into.
|
|
111
|
+
*/
|
|
112
|
+
themeEnv?: ThemeEnv,
|
|
113
|
+
): Promise<RouteOutcome> {
|
|
114
|
+
// Top-level flags take precedence over any command token.
|
|
115
|
+
if (argv.includes("--version") || argv.includes("-v")) {
|
|
116
|
+
console.log(`magus v${version}`);
|
|
117
|
+
const result = await checkForUpdates();
|
|
118
|
+
if (result.updateAvailable) {
|
|
119
|
+
console.log(`\nUpdate available: v${result.latestVersion}`);
|
|
120
|
+
console.log("Run: magus upgrade");
|
|
121
|
+
}
|
|
122
|
+
return HANDLED(0);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
126
|
+
printHelp(version);
|
|
127
|
+
return HANDLED(0);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const themeFlag = parseThemeFlag(argv);
|
|
131
|
+
if (!themeFlag.ok) {
|
|
132
|
+
console.error(themeFlag.message);
|
|
133
|
+
return HANDLED(1);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Dispatch on argv with the theme tokens removed, so `--theme dark update`
|
|
137
|
+
// still runs `update`.
|
|
138
|
+
const [command, ...rest] = themeFlag.rest;
|
|
139
|
+
|
|
140
|
+
// Every subcommand paints, and until now only the TUI ever asked which page it
|
|
141
|
+
// was painting on: `setThemeMode` was called in tui.tsx alone, so `magus
|
|
142
|
+
// update` kept the "dark" default and printed dark-page fills on a cream
|
|
143
|
+
// terminal. That is why the report read harsh next to the Models screen.
|
|
144
|
+
//
|
|
145
|
+
// No probe here. There is no renderer to ask, and a report must not block on a
|
|
146
|
+
// terminal reply — so this resolves from the flag, MAGUS_THEME, TERM_THEME
|
|
147
|
+
// and COLORFGBG, and still falls back to dark when nothing answers, exactly as
|
|
148
|
+
// before. The TUI keeps its own probing resolution in tui.tsx.
|
|
149
|
+
//
|
|
150
|
+
// `themeEnv` is HANDED IN, never re-snapshotted here. main.tsx takes it as
|
|
151
|
+
// its very first act, before `loadProjectDotenv()` merges a project's .env
|
|
152
|
+
// into process.env — so the theme comes from the environment magus was
|
|
153
|
+
// started with and never from the project being inspected. Calling
|
|
154
|
+
// `snapshotThemeEnv()` at this point would run after that merge and quietly
|
|
155
|
+
// hand a checked-out repo control of the palette.
|
|
156
|
+
if (command !== undefined && SUBCOMMANDS.has(command)) {
|
|
157
|
+
const [{ resolveTheme }, { setThemeMode }] = await Promise.all([
|
|
158
|
+
import("../ui/theme-resolve.js"),
|
|
159
|
+
import("../ui/theme-mode.js"),
|
|
160
|
+
]);
|
|
161
|
+
const { mode } = await resolveTheme({
|
|
162
|
+
flag: themeFlag.theme,
|
|
163
|
+
env: themeEnv ?? {},
|
|
164
|
+
// A report is not an interactive session: it may be piped, redirected
|
|
165
|
+
// or read by CI. `isInteractive: false` is what stops the probe being
|
|
166
|
+
// consulted at all, and the null probe satisfies the signature without
|
|
167
|
+
// ever writing an OSC query into someone's scrollback.
|
|
168
|
+
isInteractive: false,
|
|
169
|
+
probe: async () => null,
|
|
170
|
+
});
|
|
171
|
+
setThemeMode(mode);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
switch (command) {
|
|
175
|
+
case "update":
|
|
176
|
+
return HANDLED(await runUpdateCommand(rest));
|
|
177
|
+
case "upgrade":
|
|
178
|
+
return HANDLED(await runUpgradeCommand());
|
|
179
|
+
case "install":
|
|
180
|
+
return HANDLED(await runInstallCommand(rest));
|
|
181
|
+
case "profile":
|
|
182
|
+
return HANDLED(await runProfileCommand(rest));
|
|
183
|
+
case "models":
|
|
184
|
+
return HANDLED(await runModelsCommand(rest));
|
|
185
|
+
case "doctor":
|
|
186
|
+
return HANDLED(await runDoctorCommand(rest));
|
|
187
|
+
default:
|
|
188
|
+
// Bare `magus` or an unrecognized token → interactive TUI.
|
|
189
|
+
return themeFlag.theme === undefined
|
|
190
|
+
? LAUNCH_TUI
|
|
191
|
+
: { ...LAUNCH_TUI, theme: themeFlag.theme };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function printHelp(version: string): void {
|
|
196
|
+
console.log(`magus v${version}
|
|
197
|
+
|
|
198
|
+
TUI tool for managing Claude Code plugins, MCPs, and configuration.
|
|
199
|
+
|
|
200
|
+
Usage: magus Open the interactive TUI
|
|
201
|
+
magus <command> [args]
|
|
202
|
+
|
|
203
|
+
Options:
|
|
204
|
+
-v, --version Show version and check for updates
|
|
205
|
+
-h, --help Show this help message
|
|
206
|
+
--no-refresh Skip auto-refresh of marketplaces on startup
|
|
207
|
+
--theme light|dark Force the palette. Otherwise: MAGUS_THEME, then
|
|
208
|
+
TERM_THEME, then a 250 ms terminal query, then COLORFGBG,
|
|
209
|
+
then dark. Only the exact words "light"/"dark" count.
|
|
210
|
+
|
|
211
|
+
Dependency management — keep this project's plugins, CLI tools and skills
|
|
212
|
+
installed and current. The manifest is .claude/profiles.json, committed; a repo
|
|
213
|
+
without one is offered adoption on first run. See docs/team-configuration.md.
|
|
214
|
+
install [profile] Install the profile's plugins, binaries, skills and env at
|
|
215
|
+
the manifest's PINNED versions, then activate it.
|
|
216
|
+
No argument installs every profile.
|
|
217
|
+
--check Report drift only, write nothing (exits 1 in strict mode)
|
|
218
|
+
--yes, -y Skip the confirmation prompt
|
|
219
|
+
--force Discard unsynced local edits instead of refusing
|
|
220
|
+
update [profile] Keep the ACTIVE profile current: install whatever it
|
|
221
|
+
declares but this machine lacks, and advance anything
|
|
222
|
+
behind the version its marketplace publishes. Only the
|
|
223
|
+
newest version is installable, so a pin naming an older
|
|
224
|
+
one is reported, not chased.
|
|
225
|
+
APPLIES WITHOUT ASKING. It can only move forward — there
|
|
226
|
+
is no downgrade and no delete — so the plan is printed as
|
|
227
|
+
it runs rather than gated behind a prompt. This includes
|
|
228
|
+
running a profile's CLI-tool installers (brew, bun).
|
|
229
|
+
--dry-run Show the plan and stop. Writes nothing, exits 0.
|
|
230
|
+
--check Report only, write nothing. Exits 1 if anything is
|
|
231
|
+
missing or known to be behind — the CI gate.
|
|
232
|
+
profile list Show every profile; ● marks the active one
|
|
233
|
+
profile show <n> Print a profile's fully resolved closure
|
|
234
|
+
profile switch <n> Repoint the active profile — offline, no reinstall
|
|
235
|
+
profile init Write .claude/profiles.json from this project's setup
|
|
236
|
+
profile sync Promote local edits back into .claude/profiles.json
|
|
237
|
+
doctor Check binary deps, profile symlinks, and conventions
|
|
238
|
+
--fix Apply the repairs it can make itself
|
|
239
|
+
|
|
240
|
+
Per-subagent model routing — give each subagent its own model and effort. The
|
|
241
|
+
routing lives on the active profile (committed in .claude/profiles.json) and is
|
|
242
|
+
applied by a PreToolUse hook magus registers at user scope.
|
|
243
|
+
models list Show the built-in presets; ● marks the active one
|
|
244
|
+
models use <p> Apply a preset, record it on the active profile, and
|
|
245
|
+
register the hook
|
|
246
|
+
models status What is routed now, plus any drift from the config
|
|
247
|
+
models off Stop routing; subagents inherit the session model again
|
|
248
|
+
|
|
249
|
+
Other commands:
|
|
250
|
+
upgrade Update magus ITSELF to the latest version
|
|
251
|
+
|
|
252
|
+
Navigation (TUI):
|
|
253
|
+
[1] Plugins [4] Settings [7] Git State
|
|
254
|
+
[2] Skills [5] Profiles [8] Alias
|
|
255
|
+
[3] MCP [6] CLI Tools
|
|
256
|
+
|
|
257
|
+
Keys:
|
|
258
|
+
↑/↓ or j/k Navigate
|
|
259
|
+
Enter Select / Toggle
|
|
260
|
+
r Refresh current screen
|
|
261
|
+
? Show help
|
|
262
|
+
q / Escape Back / Quit
|
|
263
|
+
`);
|
|
264
|
+
}
|