claudeup 4.18.0 → 4.22.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.
Files changed (191) hide show
  1. package/bin/claudeup.js +43 -19
  2. package/package.json +14 -19
  3. package/scripts/build-binaries.ts +77 -0
  4. package/scripts/check-optional-deps.ts +49 -0
  5. package/scripts/test-isolated.ts +43 -0
  6. package/src/__tests__/alias-adopt.test.ts +364 -0
  7. package/src/__tests__/alias-parser.test.ts +92 -0
  8. package/src/__tests__/alias-shell-writer.test.ts +7 -0
  9. package/src/__tests__/alias-store.test.ts +77 -0
  10. package/src/__tests__/cli-router.test.ts +65 -0
  11. package/src/__tests__/conventions-integration.test.ts +745 -0
  12. package/src/__tests__/conventions-manager.test.ts +1172 -0
  13. package/src/__tests__/doctor-bins.test.ts +35 -0
  14. package/src/__tests__/doctor.test.ts +450 -0
  15. package/src/__tests__/dual-write-prevention.test.ts +14 -4
  16. package/src/__tests__/file-locking.test.ts +208 -0
  17. package/src/__tests__/gitignore-prerun.test.ts +1 -0
  18. package/src/__tests__/install-command.test.ts +81 -0
  19. package/src/__tests__/install-plan.test.ts +98 -0
  20. package/src/__tests__/manifest.test.ts +136 -0
  21. package/src/__tests__/marketplace-refresh.test.ts +267 -0
  22. package/src/__tests__/plugin-requires.test.ts +133 -0
  23. package/src/__tests__/plugin-setup.test.ts +118 -0
  24. package/src/__tests__/profile-command.test.ts +116 -0
  25. package/src/__tests__/profile-materializer.test.ts +82 -0
  26. package/src/__tests__/profile-sync.test.ts +136 -0
  27. package/src/__tests__/registry-version-resolution.test.ts +74 -0
  28. package/src/__tests__/resolve-executable.test.ts +42 -0
  29. package/src/__tests__/resolver.test.ts +218 -0
  30. package/src/__tests__/symlink-manager.test.ts +99 -0
  31. package/src/__tests__/toolchain.test.ts +56 -0
  32. package/src/cli/claude.ts +21 -0
  33. package/src/cli/doctor.ts +132 -0
  34. package/src/cli/install.ts +360 -0
  35. package/src/cli/profile.ts +166 -0
  36. package/src/cli/router.ts +107 -0
  37. package/src/cli/update.ts +85 -0
  38. package/src/data/alias-flags.ts +11 -1
  39. package/src/data/cli-tools.ts +7 -7
  40. package/src/data/gitignore-defaults.ts +4 -0
  41. package/src/data/marketplaces.ts +12 -0
  42. package/src/data/predefined-profiles.ts +3 -4
  43. package/src/main.tsx +11 -154
  44. package/src/prerunner/index.ts +62 -27
  45. package/src/services/alias-shell-writer.ts +382 -8
  46. package/src/services/alias-store.ts +60 -0
  47. package/src/services/claude-cli.ts +19 -22
  48. package/src/services/claude-settings.ts +99 -26
  49. package/src/services/conventions-manager.ts +865 -0
  50. package/src/services/doctor-bins.ts +49 -0
  51. package/src/services/doctor.ts +509 -0
  52. package/src/services/install-plan.ts +107 -0
  53. package/src/services/manifest.ts +166 -0
  54. package/src/services/marketplace-refresh.ts +173 -0
  55. package/src/services/plugin-manager.ts +7 -1
  56. package/src/services/plugin-requires.ts +215 -0
  57. package/src/services/plugin-setup.ts +61 -4
  58. package/src/services/plugin-version-check.ts +9 -4
  59. package/src/services/profile-materializer.ts +61 -0
  60. package/src/services/profile-sync.ts +150 -0
  61. package/src/services/resolver.ts +293 -0
  62. package/src/services/symlink-manager.ts +146 -0
  63. package/src/services/toolchain.ts +97 -0
  64. package/src/services/version-check.ts +10 -11
  65. package/src/types/index.ts +138 -31
  66. package/src/ui/App.tsx +16 -11
  67. package/src/ui/components/FlagDetailEditor.tsx +0 -0
  68. package/src/ui/components/modals/ConfirmModal.tsx +1 -1
  69. package/src/ui/screens/AliasScreen.tsx +491 -359
  70. package/src/ui/screens/CliToolsScreen.tsx +11 -3
  71. package/src/ui/screens/PluginsScreen.tsx +3 -1
  72. package/src/ui/screens/index.ts +0 -1
  73. package/src/ui/state/reducer.ts +6 -102
  74. package/src/ui/state/types.ts +8 -35
  75. package/src/utils/command-utils.ts +23 -0
  76. package/src/utils/file-locking.ts +144 -0
  77. package/src/data/alias-flags.js +0 -196
  78. package/src/data/cli-tools.js +0 -123
  79. package/src/data/gitignore-defaults.js +0 -24
  80. package/src/data/gitignore-reasons.js +0 -97
  81. package/src/data/gitignore-templates.js +0 -21
  82. package/src/data/marketplaces.js +0 -138
  83. package/src/data/mcp-servers.js +0 -509
  84. package/src/data/predefined-profiles.js +0 -248
  85. package/src/data/settings-catalog.js +0 -625
  86. package/src/data/skill-repos.js +0 -160
  87. package/src/data/statuslines.js +0 -159
  88. package/src/data/statuslines.ts +0 -188
  89. package/src/index.js +0 -4
  90. package/src/index.ts +0 -5
  91. package/src/main.js +0 -170
  92. package/src/prerunner/index.js +0 -252
  93. package/src/services/alias-settings.js +0 -51
  94. package/src/services/alias-shell-writer.js +0 -764
  95. package/src/services/alias-store.js +0 -77
  96. package/src/services/claude-cli.js +0 -189
  97. package/src/services/claude-runner.js +0 -28
  98. package/src/services/claude-settings.js +0 -1223
  99. package/src/services/gitignore-detector.js +0 -155
  100. package/src/services/gitignore-fixer.js +0 -231
  101. package/src/services/gitignore-prerun.js +0 -46
  102. package/src/services/gitignore-resolver.js +0 -143
  103. package/src/services/gitignore-service.js +0 -117
  104. package/src/services/local-marketplace.js +0 -339
  105. package/src/services/marketplace-fetcher.js +0 -96
  106. package/src/services/marketplace-sync.js +0 -94
  107. package/src/services/mcp-registry.js +0 -87
  108. package/src/services/plugin-manager.js +0 -473
  109. package/src/services/plugin-mcp-config.js +0 -177
  110. package/src/services/plugin-setup.js +0 -444
  111. package/src/services/plugin-version-check.js +0 -262
  112. package/src/services/profiles.js +0 -161
  113. package/src/services/settings-manager.js +0 -243
  114. package/src/services/skills-manager.js +0 -393
  115. package/src/services/skillsmp-client.js +0 -87
  116. package/src/services/update-cache.js +0 -52
  117. package/src/services/version-check.js +0 -99
  118. package/src/types/gitignore.js +0 -6
  119. package/src/types/index.js +0 -1
  120. package/src/ui/App.js +0 -346
  121. package/src/ui/adapters/pluginsAdapter.js +0 -139
  122. package/src/ui/adapters/settingsAdapter.js +0 -111
  123. package/src/ui/adapters/skillsAdapter.js +0 -154
  124. package/src/ui/components/CategoryHeader.js +0 -9
  125. package/src/ui/components/EmptyFilterState.js +0 -4
  126. package/src/ui/components/ScopeIndicator.js +0 -30
  127. package/src/ui/components/ScrollableList.js +0 -36
  128. package/src/ui/components/SearchInput.js +0 -19
  129. package/src/ui/components/StyledText.js +0 -39
  130. package/src/ui/components/TabBar.js +0 -19
  131. package/src/ui/components/layout/FooterHints.js +0 -29
  132. package/src/ui/components/layout/Panel.js +0 -6
  133. package/src/ui/components/layout/ProgressBar.js +0 -14
  134. package/src/ui/components/layout/ScopeTabs.js +0 -6
  135. package/src/ui/components/layout/ScreenLayout.js +0 -16
  136. package/src/ui/components/layout/index.js +0 -5
  137. package/src/ui/components/modals/ConfirmModal.js +0 -14
  138. package/src/ui/components/modals/InputModal.js +0 -5
  139. package/src/ui/components/modals/LoadingModal.js +0 -17
  140. package/src/ui/components/modals/MessageModal.js +0 -16
  141. package/src/ui/components/modals/ModalContainer.js +0 -137
  142. package/src/ui/components/modals/SelectModal.js +0 -18
  143. package/src/ui/components/modals/VersionMismatchModal.js +0 -36
  144. package/src/ui/components/modals/index.js +0 -6
  145. package/src/ui/components/primitives/ActionHints.js +0 -13
  146. package/src/ui/components/primitives/DetailSection.js +0 -7
  147. package/src/ui/components/primitives/KeyValueLine.js +0 -8
  148. package/src/ui/components/primitives/ListCategoryRow.js +0 -8
  149. package/src/ui/components/primitives/MetaText.js +0 -8
  150. package/src/ui/components/primitives/ScopeDetail.js +0 -32
  151. package/src/ui/components/primitives/ScopeSquares.js +0 -11
  152. package/src/ui/components/primitives/SelectableRow.js +0 -5
  153. package/src/ui/components/primitives/index.js +0 -8
  154. package/src/ui/hooks/index.js +0 -4
  155. package/src/ui/hooks/useAsyncData.js +0 -77
  156. package/src/ui/hooks/useGitignoreModal.js +0 -74
  157. package/src/ui/hooks/useKeyboard.js +0 -13
  158. package/src/ui/hooks/useKeyboardHandler.js +0 -39
  159. package/src/ui/hooks/useMismatchModal.js +0 -77
  160. package/src/ui/registry.js +0 -1
  161. package/src/ui/renderers/cliToolRenderers.js +0 -54
  162. package/src/ui/renderers/gitignoreRenderers.js +0 -46
  163. package/src/ui/renderers/mcpRenderers.js +0 -26
  164. package/src/ui/renderers/pluginRenderers.js +0 -124
  165. package/src/ui/renderers/profileRenderers.js +0 -177
  166. package/src/ui/renderers/settingsRenderers.js +0 -73
  167. package/src/ui/renderers/skillRenderers.js +0 -138
  168. package/src/ui/screens/AliasScreen.js +0 -1008
  169. package/src/ui/screens/CliToolsScreen.js +0 -338
  170. package/src/ui/screens/EnvVarsScreen.js +0 -152
  171. package/src/ui/screens/GitignoreScreen.js +0 -328
  172. package/src/ui/screens/McpRegistryScreen.js +0 -238
  173. package/src/ui/screens/McpScreen.js +0 -176
  174. package/src/ui/screens/ModelSelectorScreen.js +0 -296
  175. package/src/ui/screens/ModelSelectorScreen.tsx +0 -444
  176. package/src/ui/screens/PluginsScreen.js +0 -766
  177. package/src/ui/screens/ProfilesScreen.js +0 -298
  178. package/src/ui/screens/SkillsScreen.js +0 -549
  179. package/src/ui/screens/StatusLineScreen.js +0 -206
  180. package/src/ui/screens/StatusLineScreen.tsx +0 -416
  181. package/src/ui/screens/index.js +0 -10
  182. package/src/ui/state/AnimationContext.js +0 -34
  183. package/src/ui/state/AppContext.js +0 -162
  184. package/src/ui/state/DimensionsContext.js +0 -71
  185. package/src/ui/state/reducer.js +0 -543
  186. package/src/ui/state/types.js +0 -1
  187. package/src/ui/theme.js +0 -47
  188. package/src/utils/clipboard.js +0 -56
  189. package/src/utils/command-utils.js +0 -19
  190. package/src/utils/fuzzy-search.js +0 -101
  191. package/src/utils/string-utils.js +0 -62
@@ -0,0 +1,166 @@
1
+ /**
2
+ * `claudeup 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 { readManifest } from "../services/manifest.js";
12
+ import { resolveProfile } from "../services/resolver.js";
13
+ import { summarizeClosure } from "../services/install-plan.js";
14
+ import {
15
+ activateProfile,
16
+ activeProfile,
17
+ profileDir,
18
+ PROFILE_GITIGNORE_ENTRIES,
19
+ } from "../services/symlink-manager.js";
20
+ import { ensureGitignoreEntries } from "../services/conventions-manager.js";
21
+ import { syncProfile } from "../services/profile-sync.js";
22
+ import { checkBinaries, missingBinaries } from "../services/doctor-bins.js";
23
+ import fs from "fs-extra";
24
+
25
+ export async function runProfileCommand(args: string[]): Promise<number> {
26
+ const [sub, ...rest] = args;
27
+ const projectPath = process.cwd();
28
+
29
+ switch (sub) {
30
+ case undefined:
31
+ case "list":
32
+ return listProfiles(projectPath);
33
+ case "show":
34
+ return showProfile(rest[0], projectPath);
35
+ case "switch":
36
+ return switchProfile(rest[0], projectPath);
37
+ case "sync":
38
+ return syncActiveProfile(rest[0], projectPath);
39
+ default:
40
+ console.error(
41
+ `Unknown profile command "${sub}". Use: list | show <name> | switch <name> | sync`,
42
+ );
43
+ return 1;
44
+ }
45
+ }
46
+
47
+ export async function listProfiles(projectPath: string): Promise<number> {
48
+ const manifest = await readManifest(projectPath);
49
+ const ids = Object.keys(manifest.profiles);
50
+ if (ids.length === 0) {
51
+ console.log("No profiles defined in .claude/profiles.json.");
52
+ return 0;
53
+ }
54
+ const active = await activeProfile(projectPath);
55
+ console.log("Profiles:");
56
+ for (const id of ids) {
57
+ const entry = manifest.profiles[id]!;
58
+ const marker = id === active ? "●" : " ";
59
+ const materialized = (await fs.pathExists(profileDir(id, projectPath)))
60
+ ? ""
61
+ : " (not installed)";
62
+ console.log(` ${marker} ${id} — ${entry.name}${materialized}`);
63
+ }
64
+ if (active) console.log(`\nActive: ${active}`);
65
+ return 0;
66
+ }
67
+
68
+ export async function showProfile(
69
+ name: string | undefined,
70
+ projectPath: string,
71
+ ): Promise<number> {
72
+ if (!name) {
73
+ console.error("Usage: claudeup profile show <name>");
74
+ return 1;
75
+ }
76
+ const manifest = await readManifest(projectPath);
77
+ if (!manifest.profiles[name]) {
78
+ console.error(
79
+ `Profile "${name}" not found. Available: ${Object.keys(manifest.profiles).join(", ")}`,
80
+ );
81
+ return 1;
82
+ }
83
+ const closure = await resolveProfile(manifest, name);
84
+ console.log(`Profile: ${name} (${manifest.profiles[name]!.name})`);
85
+ for (const line of summarizeClosure(closure)) console.log(` ${line}`);
86
+ return 0;
87
+ }
88
+
89
+ export async function switchProfile(
90
+ name: string | undefined,
91
+ projectPath: string,
92
+ ): Promise<number> {
93
+ if (!name) {
94
+ console.error("Usage: claudeup profile switch <name>");
95
+ return 1;
96
+ }
97
+ const manifest = await readManifest(projectPath);
98
+ if (!manifest.profiles[name]) {
99
+ console.error(
100
+ `Profile "${name}" not found. Available: ${Object.keys(manifest.profiles).join(", ")}`,
101
+ );
102
+ return 1;
103
+ }
104
+ if (!(await fs.pathExists(profileDir(name, projectPath)))) {
105
+ console.error(
106
+ `Profile "${name}" isn't installed yet. Run: claudeup install ${name}`,
107
+ );
108
+ return 1;
109
+ }
110
+
111
+ // In profile mode the source of truth is .claude/profiles.json; the
112
+ // generated dir AND the per-developer symlinks must stay out of git so
113
+ // switching profiles causes no churn.
114
+ await ensureGitignoreEntries(
115
+ path.join(projectPath, ".gitignore"),
116
+ PROFILE_GITIGNORE_ENTRIES,
117
+ "# claudeup: generated profile build output + active-profile symlinks",
118
+ );
119
+
120
+ await activateProfile(name, projectPath);
121
+ console.log(`✓ Switched to profile: ${name}`);
122
+
123
+ // Verify the profile's binary deps are actually usable — the switch itself
124
+ // succeeds (symlinks are valid), but warn so "switched" isn't misleading.
125
+ try {
126
+ const closure = await resolveProfile(manifest, name);
127
+ const missing = missingBinaries(await checkBinaries(closure.bins));
128
+ if (missing.length > 0) {
129
+ console.log(
130
+ `\n⚠ ${missing.length} binary dependenc${missing.length === 1 ? "y is" : "ies are"} missing:`,
131
+ );
132
+ for (const m of missing) console.log(` ✗ ${m.name} — run: ${m.installCommand}`);
133
+ console.log(`Run \`claudeup install ${name}\` to install them.`);
134
+ }
135
+ } catch {
136
+ // Verification is best-effort — never fail a valid switch on it.
137
+ }
138
+ return 0;
139
+ }
140
+
141
+ export async function syncActiveProfile(
142
+ nameArg: string | undefined,
143
+ projectPath: string,
144
+ ): Promise<number> {
145
+ const name = nameArg ?? (await activeProfile(projectPath));
146
+ if (!name) {
147
+ console.error(
148
+ "No active profile to sync. Pass a name: claudeup profile sync <name>",
149
+ );
150
+ return 1;
151
+ }
152
+ try {
153
+ const { changes, wrote } = await syncProfile(name, projectPath);
154
+ if (!wrote) {
155
+ console.log(`Profile "${name}" is already in sync with the manifest.`);
156
+ return 0;
157
+ }
158
+ console.log(`Synced "${name}" → .claude/profiles.json:`);
159
+ for (const c of changes) console.log(` ${c.kind}: ${c.detail}`);
160
+ console.log("\nReview the diff and commit .claude/profiles.json.");
161
+ return 0;
162
+ } catch (e) {
163
+ console.error((e as Error).message);
164
+ return 1;
165
+ }
166
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * claudeup 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 claudeup's historical behavior.
7
+ */
8
+
9
+ import { checkForUpdates } from "../services/version-check.js";
10
+ import { runClaudeCommand } from "./claude.js";
11
+ import { runUpdateCommand } from "./update.js";
12
+ import { runInstallCommand } from "./install.js";
13
+ import { runProfileCommand } from "./profile.js";
14
+ import { runDoctorCommand } from "./doctor.js";
15
+
16
+ export interface RouteOutcome {
17
+ /** A subcommand (or top-level flag) handled the invocation. */
18
+ handled: boolean;
19
+ /** Exit code to use when `handled` is true. */
20
+ exitCode?: number;
21
+ /** No subcommand matched — the caller should launch the TUI. */
22
+ launchTui: boolean;
23
+ }
24
+
25
+ const HANDLED = (exitCode: number): RouteOutcome => ({
26
+ handled: true,
27
+ exitCode,
28
+ launchTui: false,
29
+ });
30
+ const LAUNCH_TUI: RouteOutcome = { handled: false, launchTui: true };
31
+
32
+ export async function route(
33
+ argv: string[],
34
+ version: string,
35
+ ): Promise<RouteOutcome> {
36
+ // Top-level flags take precedence over any command token.
37
+ if (argv.includes("--version") || argv.includes("-v")) {
38
+ console.log(`claudeup v${version}`);
39
+ const result = await checkForUpdates();
40
+ if (result.updateAvailable) {
41
+ console.log(`\nUpdate available: v${result.latestVersion}`);
42
+ console.log("Run: claudeup update");
43
+ }
44
+ return HANDLED(0);
45
+ }
46
+
47
+ if (argv.includes("--help") || argv.includes("-h")) {
48
+ printHelp(version);
49
+ return HANDLED(0);
50
+ }
51
+
52
+ const [command, ...rest] = argv;
53
+
54
+ switch (command) {
55
+ case "claude":
56
+ return HANDLED(await runClaudeCommand(rest));
57
+ case "update":
58
+ return HANDLED(await runUpdateCommand());
59
+ case "install":
60
+ return HANDLED(await runInstallCommand(rest));
61
+ case "profile":
62
+ return HANDLED(await runProfileCommand(rest));
63
+ case "doctor":
64
+ return HANDLED(await runDoctorCommand(rest));
65
+ default:
66
+ // Bare `claudeup` or an unrecognized token → interactive TUI.
67
+ return LAUNCH_TUI;
68
+ }
69
+ }
70
+
71
+ function printHelp(version: string): void {
72
+ console.log(`claudeup v${version}
73
+
74
+ TUI tool for managing Claude Code plugins, MCPs, and configuration.
75
+
76
+ Usage: claudeup [options]
77
+ claudeup claude [args...] Run claude with auto-update prerunner
78
+ claudeup update Update claudeup to latest version
79
+
80
+ Options:
81
+ -v, --version Show version and check for updates
82
+ -h, --help Show this help message
83
+ --no-refresh Skip auto-refresh of marketplaces on startup
84
+
85
+ Commands:
86
+ claude [args...] Check for plugin updates (1h cache), then run claude
87
+ -f, --force Force update check (bypass 1h cache)
88
+ update Update claudeup itself to latest version
89
+
90
+ Experimental (in development):
91
+ install [profile] Install a team profile from .claude/profiles.json
92
+ profile <cmd> list | show | switch | sync team profiles
93
+ doctor Diagnose & repair plugin/marketplace/profile state
94
+
95
+ Navigation (TUI):
96
+ [1] Plugins [4] Settings [7] Git State
97
+ [2] Skills [5] Profiles [8] Alias
98
+ [3] MCP [6] CLI Tools
99
+
100
+ Keys:
101
+ ↑/↓ or j/k Navigate
102
+ Enter Select / Toggle
103
+ r Refresh current screen
104
+ ? Show help
105
+ q / Escape Back / Quit
106
+ `);
107
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * `claudeup update` — self-update.
3
+ *
4
+ * Detects claudeup installs across bun + npm global, warns on duplicates
5
+ * (active vs shadowed), then re-installs `claudeup@latest` with whichever
6
+ * package manager owns the active binary. Resolves with the child's exit code.
7
+ */
8
+
9
+ import { spawn } from "node:child_process";
10
+
11
+ export async function runUpdateCommand(): Promise<number> {
12
+ const { execSync } = await import("node:child_process");
13
+ const { existsSync } = await import("node:fs");
14
+
15
+ // Detect all installations of claudeup across package managers
16
+ const installations: Array<{ manager: "bun" | "npm"; path: string }> = [];
17
+
18
+ // Check bun global
19
+ try {
20
+ const bunGlobalBin = execSync("bun pm -g bin", {
21
+ encoding: "utf-8",
22
+ timeout: 5000,
23
+ }).trim();
24
+ const bunPath = `${bunGlobalBin}/claudeup`;
25
+ if (existsSync(bunPath)) installations.push({ manager: "bun", path: bunPath });
26
+ } catch {
27
+ // bun not installed or no global claudeup
28
+ }
29
+
30
+ // Check npm global
31
+ try {
32
+ const npmPrefix = execSync("npm prefix -g", {
33
+ encoding: "utf-8",
34
+ timeout: 5000,
35
+ }).trim();
36
+ const npmPath = `${npmPrefix}/bin/claudeup`;
37
+ if (existsSync(npmPath)) installations.push({ manager: "npm", path: npmPath });
38
+ } catch {
39
+ // npm not installed or no global claudeup
40
+ }
41
+
42
+ // Warn about duplicate installations
43
+ if (installations.length > 1) {
44
+ const activePath = execSync("which claudeup", {
45
+ encoding: "utf-8",
46
+ timeout: 5000,
47
+ }).trim();
48
+
49
+ console.log(`⚠ claudeup is installed via multiple package managers:\n`);
50
+ for (const inst of installations) {
51
+ const tag = inst.path === activePath ? " (active)" : " (shadowed)";
52
+ console.log(` ${inst.manager}: ${inst.path}${tag}`);
53
+ }
54
+ console.log(`\nTo fix, keep one and remove the other:`);
55
+ for (const inst of installations) {
56
+ console.log(` ${inst.manager} uninstall -g claudeup`);
57
+ }
58
+ console.log();
59
+ }
60
+
61
+ // Determine which package manager to use for the update
62
+ let usesBun = false;
63
+ try {
64
+ const claudeupPath = execSync("which claudeup", {
65
+ encoding: "utf-8",
66
+ }).trim();
67
+ usesBun = claudeupPath.includes(".bun") || claudeupPath.includes("bun/bin");
68
+ } catch {
69
+ // If which fails, default to npm
70
+ }
71
+
72
+ const pkgManager = usesBun ? "bun" : "npm";
73
+ console.log(`Updating claudeup using ${pkgManager}...`);
74
+
75
+ const installArgs = ["install", "-g", "claudeup@latest"];
76
+
77
+ return new Promise<number>((resolve) => {
78
+ const proc = spawn(pkgManager, installArgs, {
79
+ stdio: "inherit",
80
+ shell: false, // Avoid shell for security (fixes DEP0190 warning)
81
+ });
82
+ proc.on("exit", (code) => resolve(code ?? 0));
83
+ proc.on("error", () => resolve(1));
84
+ });
85
+ }
@@ -222,6 +222,16 @@ export const ALIAS_FLAGS: AliasFlag[] = [
222
222
  description:
223
223
  "Path to a file whose contents are appended to the default system prompt.",
224
224
  },
225
+ {
226
+ id: "channels",
227
+ label: "Channels (approved)",
228
+ flag: "--channels",
229
+ kind: "text-list",
230
+ group: "channels",
231
+ description:
232
+ "Approved channel plugins/MCP servers to run this session (the allowlist). Channels derived from --dangerously-load appear in a separate section here and are unioned in on write; add your own approved channels on top.",
233
+ defaultValues: [],
234
+ },
225
235
  {
226
236
  id: "dangerously-load-development-channels",
227
237
  label: "Load development channels",
@@ -229,7 +239,7 @@ export const ALIAS_FLAGS: AliasFlag[] = [
229
239
  kind: "text-list",
230
240
  group: "channels",
231
241
  description:
232
- "Sideload development channels from magus plugins. Default seed: claudish (the only magus plugin currently shipping channels).",
242
+ "Sideload development channels not on the approved allowlist (local dev only). Also shown under --channels and unioned into it on write. Default seed: claudish (the only magus plugin currently shipping channels).",
233
243
  defaultValues: ["plugin:claudish@magus"],
234
244
  },
235
245
  {
@@ -6,7 +6,7 @@ export interface CliTool {
6
6
  checkCommand: string;
7
7
  website: string;
8
8
  category: "ai-coding" | "utility";
9
- packageManager: "npm" | "pip";
9
+ packageManager: "bun" | "npm" | "pip" | "brew" | "go";
10
10
  packageName: string;
11
11
  }
12
12
 
@@ -16,11 +16,11 @@ export const cliTools: CliTool[] = [
16
16
  displayName: "claudeup",
17
17
  description:
18
18
  "TUI tool for managing Claude Code plugins, MCPs, and configuration",
19
- installCommand: "npm install -g claudeup",
19
+ installCommand: "bun install -g claudeup",
20
20
  checkCommand: "claudeup --version",
21
21
  website: "https://github.com/MadAppGang/magus/tree/main/tools/claudeup",
22
22
  category: "ai-coding",
23
- packageManager: "npm",
23
+ packageManager: "bun",
24
24
  packageName: "claudeup",
25
25
  },
26
26
  {
@@ -28,22 +28,22 @@ export const cliTools: CliTool[] = [
28
28
  displayName: "Mnemex",
29
29
  description:
30
30
  "AST-aware code search with PageRank, callers/callees, and semantic embeddings",
31
- installCommand: "npm install -g mnemex",
31
+ installCommand: "bun install -g mnemex",
32
32
  checkCommand: "mnemex --version",
33
33
  website: "https://github.com/MadAppGang/mnemex",
34
34
  category: "ai-coding",
35
- packageManager: "npm",
35
+ packageManager: "bun",
36
36
  packageName: "mnemex",
37
37
  },
38
38
  {
39
39
  name: "claudish",
40
40
  displayName: "Claudish",
41
41
  description: "Run Claude Code with OpenRouter models (Grok, GPT-5, Gemini)",
42
- installCommand: "npm install -g claudish",
42
+ installCommand: "bun install -g claudish",
43
43
  checkCommand: "claudish --version",
44
44
  website: "https://github.com/MadAppGang/claudish",
45
45
  category: "ai-coding",
46
- packageManager: "npm",
46
+ packageManager: "bun",
47
47
  packageName: "claudish",
48
48
  },
49
49
  {
@@ -12,6 +12,8 @@ export const BUILTIN_DEFAULTS: GitignoreManifest = {
12
12
  ".claude/scheduled_tasks.lock",
13
13
  ".claude/cache/",
14
14
  ".claude/.statusline-worktree-*",
15
+ // Generated, per-developer profile build output (like node_modules).
16
+ ".claude/_profiles/",
15
17
  "ai-docs/sessions/",
16
18
  ".mnemex/",
17
19
  ".claudemem/",
@@ -19,6 +21,8 @@ export const BUILTIN_DEFAULTS: GitignoreManifest = {
19
21
  ".agents/",
20
22
  ],
21
23
  track: [
24
+ // The committed team source of truth when using profiles.
25
+ ".claude/profiles.json",
22
26
  ".claude/settings.json",
23
27
  ".mcp.json",
24
28
  "plugin.json",
@@ -39,6 +39,18 @@ export const defaultMarketplaces: Marketplace[] = [
39
39
  official: false,
40
40
  featured: true, // Always show plugins (expanded by default)
41
41
  },
42
+ {
43
+ name: "magus-marketing",
44
+ displayName: "Magus Marketing",
45
+ source: {
46
+ source: "github",
47
+ repo: "MadAppGang/magus-marketing",
48
+ },
49
+ description:
50
+ "SEO, cold email outreach, AI image generation, and video editing plugins",
51
+ official: false,
52
+ // Not featured: a niche channel, so keep it collapsed by default.
53
+ },
42
54
  {
43
55
  name: "claude-plugins-official",
44
56
  displayName: "Anthropic Official",
@@ -127,7 +127,6 @@ export const PREDEFINED_PROFILES: PredefinedProfile[] = [
127
127
  "code-analysis",
128
128
  "terminal",
129
129
  "statusline",
130
- "conductor",
131
130
  "multimodel",
132
131
  "gtd",
133
132
  ],
@@ -200,10 +199,10 @@ export const PREDEFINED_PROFILES: PredefinedProfile[] = [
200
199
  "code-analysis",
201
200
  "terminal",
202
201
  "statusline",
203
- "seo",
202
+ "seo@magus-marketing",
204
203
  "browser-use",
205
- "nanobanana",
206
- "video-editing",
204
+ "nanobanana@magus-marketing",
205
+ "video-editing@magus-marketing",
207
206
  ],
208
207
  anthropicPlugins: [
209
208
  "explanatory-output-style",
package/src/main.tsx CHANGED
@@ -2,15 +2,13 @@
2
2
 
3
3
  import { createCliRenderer } from "@opentui/core";
4
4
  import { createRoot } from "@opentui/react";
5
- import { spawn } from "node:child_process";
6
- import { createRequire } from "node:module";
7
5
  import { App } from "./ui/App.js";
8
- import { prerunClaude } from "./prerunner/index.js";
9
- import { checkForUpdates } from "./services/version-check.js";
6
+ import { route } from "./cli/router.js";
7
+ // Static import so `bun build --compile` embeds package.json into the binary;
8
+ // a dynamic require("../package.json") is not resolvable inside the bunfs root.
9
+ import pkg from "../package.json";
10
10
 
11
- const require = createRequire(import.meta.url);
12
- const pkg = require("../package.json") as { version: string };
13
- export const VERSION = pkg.version;
11
+ export const VERSION = (pkg as { version: string }).version;
14
12
 
15
13
  // Note: OpenTUI renderer handles alternate screen buffer automatically
16
14
  // No need for manual ANSI escape codes
@@ -18,153 +16,12 @@ export const VERSION = pkg.version;
18
16
  async function main(): Promise<void> {
19
17
  const args = process.argv.slice(2);
20
18
 
21
- // Handle "claudeup claude [args]" - prerunner mode
22
- if (args[0] === "claude") {
23
- const claudeArgs = args.slice(1);
24
-
25
- // Parse prerunner-specific flags
26
- const forceUpdate =
27
- claudeArgs.includes("--force") || claudeArgs.includes("-f");
28
-
29
- // Remove prerunner flags before passing to claude
30
- const filteredArgs = claudeArgs.filter(
31
- (arg) => arg !== "--force" && arg !== "-f",
32
- );
33
-
34
- const exitCode = await prerunClaude(filteredArgs, { force: forceUpdate });
35
- process.exit(exitCode);
36
- return;
37
- }
38
-
39
- // Handle "claudeup update" - self-update command
40
- if (args[0] === "update") {
41
- const { execSync } = await import("node:child_process");
42
- const { existsSync } = await import("node:fs");
43
-
44
- // Detect all installations of claudeup across package managers
45
- const installations: Array<{
46
- manager: "bun" | "npm";
47
- path: string;
48
- }> = [];
49
-
50
- // Check bun global
51
- try {
52
- const bunGlobalBin = execSync("bun pm -g bin", {
53
- encoding: "utf-8",
54
- timeout: 5000,
55
- }).trim();
56
- const bunPath = `${bunGlobalBin}/claudeup`;
57
- if (existsSync(bunPath)) installations.push({ manager: "bun", path: bunPath });
58
- } catch {
59
- // bun not installed or no global claudeup
60
- }
61
-
62
- // Check npm global
63
- try {
64
- const npmPrefix = execSync("npm prefix -g", {
65
- encoding: "utf-8",
66
- timeout: 5000,
67
- }).trim();
68
- const npmPath = `${npmPrefix}/bin/claudeup`;
69
- if (existsSync(npmPath)) installations.push({ manager: "npm", path: npmPath });
70
- } catch {
71
- // npm not installed or no global claudeup
72
- }
73
-
74
- // Warn about duplicate installations
75
- if (installations.length > 1) {
76
- const activePath = execSync("which claudeup", {
77
- encoding: "utf-8",
78
- timeout: 5000,
79
- }).trim();
80
-
81
- console.log(
82
- `⚠ claudeup is installed via multiple package managers:\n`,
83
- );
84
- for (const inst of installations) {
85
- const tag =
86
- inst.path === activePath ? " (active)" : " (shadowed)";
87
- console.log(` ${inst.manager}: ${inst.path}${tag}`);
88
- }
89
- console.log(`\nTo fix, keep one and remove the other:`);
90
- for (const inst of installations) {
91
- console.log(` ${inst.manager} uninstall -g claudeup`);
92
- }
93
- console.log();
94
- }
95
-
96
- // Determine which package manager to use for the update
97
- let usesBun = false;
98
- try {
99
- const claudeupPath = execSync("which claudeup", {
100
- encoding: "utf-8",
101
- }).trim();
102
- usesBun =
103
- claudeupPath.includes(".bun") || claudeupPath.includes("bun/bin");
104
- } catch {
105
- // If which fails, default to npm
106
- }
107
-
108
- const pkgManager = usesBun ? "bun" : "npm";
109
- console.log(`Updating claudeup using ${pkgManager}...`);
110
-
111
- const installArgs = ["install", "-g", "claudeup@latest"];
112
-
113
- const proc = spawn(pkgManager, installArgs, {
114
- stdio: "inherit",
115
- shell: false, // Avoid shell for security (fixes DEP0190 warning)
116
- });
117
- proc.on("exit", (code) => process.exit(code || 0));
118
- return;
119
- }
120
-
121
- // Handle --version flag - show version and check for updates
122
- if (args.includes("--version") || args.includes("-v")) {
123
- console.log(`claudeup v${VERSION}`);
124
- const result = await checkForUpdates();
125
- if (result.updateAvailable) {
126
- console.log(`\nUpdate available: v${result.latestVersion}`);
127
- console.log("Run: claudeup update");
128
- }
129
- process.exit(0);
130
- }
131
-
132
- // Handle --help flag
133
- if (args.includes("--help") || args.includes("-h")) {
134
- console.log(`claudeup v${VERSION}
135
-
136
- TUI tool for managing Claude Code plugins, MCPs, and configuration.
137
-
138
- Usage: claudeup [options]
139
- claudeup claude [args...] Run claude with auto-update prerunner
140
- claudeup update Update claudeup to latest version
141
-
142
- Options:
143
- -v, --version Show version and check for updates
144
- -h, --help Show this help message
145
- --no-refresh Skip auto-refresh of marketplaces on startup
146
-
147
- Commands:
148
- claude [args...] Check for plugin updates (1h cache), then run claude
149
- -f, --force Force update check (bypass 1h cache)
150
- update Update claudeup itself to latest version
151
-
152
- Navigation:
153
- [1] Plugins Manage plugin marketplaces and installed plugins
154
- [2] MCP Setup and manage MCP servers
155
- [3] Status Configure status line display
156
- [4] Env Environment variables for MCP servers
157
- [5] Tools Install and update AI coding CLI tools
158
-
159
- Keys:
160
- ↑/↓ or j/k Navigate
161
- Enter Select / Toggle
162
- g Toggle global/project scope (in Plugins)
163
- r Refresh current screen
164
- ? Show help
165
- q / Escape Back / Quit
166
- `);
167
- process.exit(0);
19
+ // Dispatch non-interactive subcommands (claude, update, install, …) and
20
+ // top-level flags (--version/--help). A bare invocation falls through to
21
+ // the interactive TUI below.
22
+ const outcome = await route(args, VERSION);
23
+ if (outcome.handled) {
24
+ process.exit(outcome.exitCode ?? 0);
168
25
  }
169
26
 
170
27
  // Create OpenTUI renderer (handles alternate screen buffer automatically)