claudeup 6.2.1 → 6.3.2

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 (150) hide show
  1. package/README.md +22 -0
  2. package/bin/claudeup.js +6 -1
  3. package/package.json +6 -6
  4. package/src/__tests__/alias-adopt.test.ts +331 -329
  5. package/src/__tests__/alias-parser.test.ts +384 -376
  6. package/src/__tests__/alias-shell-writer.test.ts +638 -625
  7. package/src/__tests__/alias-store.test.ts +143 -143
  8. package/src/__tests__/catalog-cache-store.test.ts +265 -265
  9. package/src/__tests__/catalog-notice.test.ts +123 -123
  10. package/src/__tests__/cli-apply-seams.test.ts +12 -8
  11. package/src/__tests__/cli-router.test.ts +132 -3
  12. package/src/__tests__/cli-update-view.test.ts +27 -8
  13. package/src/__tests__/community-staleness.test.ts +0 -2
  14. package/src/__tests__/component-badge.test.ts +4 -4
  15. package/src/__tests__/content-drift.test.ts +5 -1
  16. package/src/__tests__/conventions-integration.test.ts +774 -689
  17. package/src/__tests__/conventions-manager.test.ts +1216 -1152
  18. package/src/__tests__/doctor-bins.test.ts +14 -6
  19. package/src/__tests__/doctor.test.ts +438 -425
  20. package/src/__tests__/dual-write-prevention.test.ts +277 -282
  21. package/src/__tests__/enabled-not-installed.test.ts +71 -63
  22. package/src/__tests__/file-locking.test.ts +196 -196
  23. package/src/__tests__/gap-fill-versions.test.ts +318 -311
  24. package/src/__tests__/github-budget.test.ts +170 -164
  25. package/src/__tests__/gitignore-detector.test.ts +160 -152
  26. package/src/__tests__/gitignore-fixer.test.ts +285 -247
  27. package/src/__tests__/gitignore-prerun.test.ts +57 -57
  28. package/src/__tests__/gitignore-resolver.test.ts +173 -146
  29. package/src/__tests__/gitignore-service.test.ts +121 -119
  30. package/src/__tests__/install-command.test.ts +3 -3
  31. package/src/__tests__/install-plan.test.ts +13 -7
  32. package/src/__tests__/manifest.test.ts +16 -5
  33. package/src/__tests__/markdown-renderer.test.ts +0 -1
  34. package/src/__tests__/marketplace-badge.test.ts +111 -90
  35. package/src/__tests__/marketplace-version.test.ts +64 -61
  36. package/src/__tests__/plugin-manager-fallback.test.ts +249 -239
  37. package/src/__tests__/plugin-requires.test.ts +18 -4
  38. package/src/__tests__/plugin-setup.test.ts +12 -12
  39. package/src/__tests__/profile-command.test.ts +1 -7
  40. package/src/__tests__/profile-materializer.test.ts +31 -8
  41. package/src/__tests__/profile-sync.test.ts +23 -5
  42. package/src/__tests__/registry-version-resolution.test.ts +61 -47
  43. package/src/__tests__/resolve-executable.test.ts +1 -1
  44. package/src/__tests__/resolver.test.ts +40 -15
  45. package/src/__tests__/scope-action.test.ts +52 -50
  46. package/src/__tests__/scope-squares.test.tsx +4 -5
  47. package/src/__tests__/skill-install-files.test.ts +111 -103
  48. package/src/__tests__/symlink-manager.test.ts +27 -10
  49. package/src/__tests__/theme-env.test.ts +281 -0
  50. package/src/__tests__/theme-resolve.test.ts +380 -0
  51. package/src/__tests__/update-apply.test.ts +1 -3
  52. package/src/__tests__/useGitignoreModal.test.ts +69 -61
  53. package/src/__tests__/version-snapshot.test.ts +181 -165
  54. package/src/cli/ansi.ts +23 -15
  55. package/src/cli/doctor.ts +10 -9
  56. package/src/cli/install.ts +3 -3
  57. package/src/cli/profile.ts +5 -4
  58. package/src/cli/prompt.ts +0 -1
  59. package/src/cli/router.ts +69 -2
  60. package/src/cli/update-view.ts +11 -6
  61. package/src/cli/update.ts +9 -4
  62. package/src/cli/upgrade.ts +6 -4
  63. package/src/data/alias-flags.ts +260 -260
  64. package/src/data/gitignore-defaults.ts +20 -20
  65. package/src/data/gitignore-reasons.ts +86 -92
  66. package/src/data/gitignore-templates.ts +14 -14
  67. package/src/data/predefined-profiles.ts +8 -9
  68. package/src/data/skill-repos.ts +43 -35
  69. package/src/main.tsx +29 -6
  70. package/src/opentui.d.ts +1 -3
  71. package/src/prerunner/index.ts +7 -8
  72. package/src/services/alias-settings.ts +20 -23
  73. package/src/services/alias-shell-writer.ts +800 -803
  74. package/src/services/alias-store.ts +77 -77
  75. package/src/services/claude-settings.ts +27 -18
  76. package/src/services/content-drift.ts +13 -3
  77. package/src/services/conventions-manager.ts +692 -617
  78. package/src/services/doctor.ts +464 -403
  79. package/src/services/gitignore-detector.ts +113 -103
  80. package/src/services/gitignore-fixer.ts +204 -189
  81. package/src/services/gitignore-prerun.ts +26 -26
  82. package/src/services/gitignore-resolver.ts +162 -144
  83. package/src/services/gitignore-service.ts +120 -117
  84. package/src/services/install-plan.ts +4 -4
  85. package/src/services/manifest.ts +1 -1
  86. package/src/services/marketplace-catalog-git.ts +5 -2
  87. package/src/services/marketplace-fetcher.ts +241 -235
  88. package/src/services/plugin-manager.ts +17 -20
  89. package/src/services/plugin-mcp-config.ts +2 -2
  90. package/src/services/plugin-requires.ts +9 -6
  91. package/src/services/plugin-setup.ts +7 -9
  92. package/src/services/plugin-version-check.ts +4 -4
  93. package/src/services/profile-materializer.ts +9 -5
  94. package/src/services/profile-sync.ts +12 -4
  95. package/src/services/profiles.ts +3 -3
  96. package/src/services/resolver.ts +10 -6
  97. package/src/services/settings-manager.ts +82 -46
  98. package/src/services/skills-manager.ts +11 -9
  99. package/src/services/symlink-manager.ts +2 -5
  100. package/src/services/toolchain.ts +3 -3
  101. package/src/services/update-cache.ts +1 -1
  102. package/src/services/update-engine.ts +4 -4
  103. package/src/services/update-plan.ts +9 -11
  104. package/src/services/version-snapshot.ts +88 -88
  105. package/src/types/gitignore.ts +28 -28
  106. package/src/ui/App.tsx +36 -36
  107. package/src/ui/adapters/settingsAdapter.ts +2 -2
  108. package/src/ui/components/CategoryHeader.tsx +1 -1
  109. package/src/ui/components/FlagDetailEditor.tsx +0 -0
  110. package/src/ui/components/ScopeIndicator.tsx +3 -1
  111. package/src/ui/components/ScrollableList.tsx +2 -1
  112. package/src/ui/components/StyledText.tsx +1 -1
  113. package/src/ui/components/layout/FooterHints.tsx +5 -3
  114. package/src/ui/components/layout/Panel.tsx +2 -2
  115. package/src/ui/components/layout/ScreenLayout.tsx +5 -4
  116. package/src/ui/components/modals/InputModal.tsx +7 -1
  117. package/src/ui/components/modals/ModalContainer.tsx +3 -3
  118. package/src/ui/components/modals/SelectModal.tsx +1 -1
  119. package/src/ui/components/primitives/DetailSection.tsx +1 -1
  120. package/src/ui/components/primitives/KeyValueLine.tsx +1 -1
  121. package/src/ui/components/primitives/ScopeSquares.tsx +2 -2
  122. package/src/ui/hooks/index.ts +0 -1
  123. package/src/ui/hooks/useGitignoreModal.ts +1 -5
  124. package/src/ui/hooks/useMismatchModal.ts +4 -10
  125. package/src/ui/renderers/cliToolRenderers.tsx +2 -2
  126. package/src/ui/renderers/gitignoreRenderers.tsx +2 -2
  127. package/src/ui/renderers/mcpRenderers.tsx +1 -1
  128. package/src/ui/renderers/pluginRenderers.tsx +3 -9
  129. package/src/ui/renderers/profileRenderers.tsx +9 -8
  130. package/src/ui/renderers/settingsRenderers.tsx +4 -4
  131. package/src/ui/renderers/skillRenderers.tsx +14 -14
  132. package/src/ui/screens/AliasScreen.tsx +31 -34
  133. package/src/ui/screens/CliToolsScreen.tsx +14 -13
  134. package/src/ui/screens/EnvVarsScreen.tsx +8 -8
  135. package/src/ui/screens/GitignoreScreen.tsx +15 -14
  136. package/src/ui/screens/McpRegistryScreen.tsx +10 -7
  137. package/src/ui/screens/McpScreen.tsx +10 -10
  138. package/src/ui/screens/PluginsScreen.tsx +17 -21
  139. package/src/ui/screens/ProfilesScreen.tsx +25 -24
  140. package/src/ui/screens/SkillsScreen.tsx +16 -14
  141. package/src/ui/state/AppContext.tsx +4 -8
  142. package/src/ui/state/DimensionsContext.tsx +3 -2
  143. package/src/ui/theme-mode.ts +20 -38
  144. package/src/ui/theme-resolve.ts +148 -0
  145. package/src/utils/clipboard.ts +5 -5
  146. package/src/utils/command-utils.ts +2 -2
  147. package/src/utils/config-dir.ts +1 -3
  148. package/src/utils/file-locking.ts +88 -86
  149. package/src/ui/hooks/useAsyncData.ts +0 -127
  150. package/src/ui/state/AnimationContext.tsx +0 -76
package/src/cli/router.ts CHANGED
@@ -6,7 +6,9 @@
6
6
  * to the interactive TUI, preserving claudeup's historical behavior.
7
7
  */
8
8
 
9
+ import type { ThemeMode } from "@opentui/core";
9
10
  import { checkForUpdates } from "../services/version-check.js";
11
+ import { parseThemeWord } from "../ui/theme-resolve.js";
10
12
  import { runClaudeCommand } from "./claude.js";
11
13
  import { runDoctorCommand } from "./doctor.js";
12
14
  import { runInstallCommand } from "./install.js";
@@ -21,6 +23,58 @@ export interface RouteOutcome {
21
23
  exitCode?: number;
22
24
  /** No subcommand matched — the caller should launch the TUI. */
23
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
+ ? 'claudeup: --theme must be "light" or "dark" (no value given)'
52
+ : `claudeup: --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 };
24
78
  }
25
79
 
26
80
  const HANDLED = (exitCode: number): RouteOutcome => ({
@@ -50,7 +104,15 @@ export async function route(
50
104
  return HANDLED(0);
51
105
  }
52
106
 
53
- const [command, ...rest] = argv;
107
+ const themeFlag = parseThemeFlag(argv);
108
+ if (!themeFlag.ok) {
109
+ console.error(themeFlag.message);
110
+ return HANDLED(1);
111
+ }
112
+
113
+ // Dispatch on argv with the theme tokens removed, so `--theme dark update`
114
+ // still runs `update`. Subcommands ignore the flag; only the TUI carries it.
115
+ const [command, ...rest] = themeFlag.rest;
54
116
 
55
117
  switch (command) {
56
118
  case "claude":
@@ -67,7 +129,9 @@ export async function route(
67
129
  return HANDLED(await runDoctorCommand(rest));
68
130
  default:
69
131
  // Bare `claudeup` or an unrecognized token → interactive TUI.
70
- return LAUNCH_TUI;
132
+ return themeFlag.theme === undefined
133
+ ? LAUNCH_TUI
134
+ : { ...LAUNCH_TUI, theme: themeFlag.theme };
71
135
  }
72
136
  }
73
137
 
@@ -83,6 +147,9 @@ Options:
83
147
  -v, --version Show version and check for updates
84
148
  -h, --help Show this help message
85
149
  --no-refresh Skip auto-refresh of marketplaces on startup
150
+ --theme light|dark Force the palette. Otherwise: CLAUDEUP_THEME, then
151
+ TERM_THEME, then a 250 ms terminal query, then COLORFGBG,
152
+ then dark. Only the exact words "light"/"dark" count.
86
153
 
87
154
  Dependency management — keep this project's plugins, CLI tools and skills
88
155
  installed and current. The manifest is .claude/profiles.json, committed; a repo
@@ -24,13 +24,13 @@
24
24
  * own word, so the report survives NO_COLOR, a pipe, and colour blindness.
25
25
  */
26
26
 
27
+ import { binInstallCommand, binUpgradeCommand } from "../services/toolchain.js";
27
28
  import type {
28
29
  BinUpdateItem,
29
30
  PluginUpdateItem,
30
31
  SkillUpdateItem,
31
32
  UpdatePlan,
32
33
  } from "../services/update-plan.js";
33
- import { binInstallCommand, binUpgradeCommand } from "../services/toolchain.js";
34
34
  import { brand } from "../ui/theme.js";
35
35
  import { formatCommand } from "../utils/command-utils.js";
36
36
  import {
@@ -197,7 +197,9 @@ export function stepFrame(steps: Step[], tick: number, now: number): string[] {
197
197
  ? `${fg(brand.accent, `${step.progress.done}/${step.progress.total}`)} `
198
198
  : "";
199
199
  const detail =
200
- step.state === "running" ? fg(brand.accent, step.detail) : dim(step.detail);
200
+ step.state === "running"
201
+ ? fg(brand.accent, step.detail)
202
+ : dim(step.detail);
201
203
  return `${PAD}${stepMark(step, tick)} ${label} ${bar} ${time} ${count}${detail}`;
202
204
  });
203
205
  }
@@ -214,10 +216,10 @@ export function stepRecord(steps: Step[], now: number): string[] {
214
216
  const times = steps.map((s) => stepDuration(s, now) ?? 0);
215
217
  const slowest = Math.max(1, ...times);
216
218
  return steps.map((step, i) => {
217
- const share = (times[i]! / slowest) * 100;
219
+ const share = (times[i] / slowest) * 100;
218
220
  const bar = meter(share, BAR, ramps.severity);
219
221
  const label = padEnd(step.label, STEP_LABEL_WIDTH);
220
- const time = dim(padStart(elapsed(times[i]!), 6));
222
+ const time = dim(padStart(elapsed(times[i]), 6));
221
223
  return `${PAD}${stepMark(step, 0)} ${label} ${bar} ${time} ${dim(
222
224
  step.detail,
223
225
  )}`;
@@ -275,7 +277,8 @@ function section(
275
277
  ): string[] {
276
278
  if (rows.length === 0) return [];
277
279
  const counts = new Map<ActionKey, number>();
278
- for (const row of rows) counts.set(row.action, (counts.get(row.action) ?? 0) + 1);
280
+ for (const row of rows)
281
+ counts.set(row.action, (counts.get(row.action) ?? 0) + 1);
279
282
  const bar = stackedBar(
280
283
  BAR,
281
284
  [...counts].map(([action, value]) => ({
@@ -284,7 +287,9 @@ function section(
284
287
  })),
285
288
  );
286
289
  const legend = [...counts]
287
- .map(([action, n]) => fg(ACTIONS[action].color, `${n} ${ACTIONS[action].label.toLowerCase()}`))
290
+ .map(([action, n]) =>
291
+ fg(ACTIONS[action].color, `${n} ${ACTIONS[action].label.toLowerCase()}`),
292
+ )
288
293
  .join(dim(" · "));
289
294
 
290
295
  return [
package/src/cli/update.ts CHANGED
@@ -108,7 +108,6 @@ export function parseArgs(args: string[]): UpdateFlags {
108
108
  };
109
109
  }
110
110
 
111
-
112
111
  // -- phase runner ------------------------------------------------------------
113
112
 
114
113
  /**
@@ -254,7 +253,9 @@ export async function runUpdateCommand(
254
253
  // Only now is there a denominator; until `onStart` fires the step
255
254
  // shows a sweep rather than a meter that would have to guess one.
256
255
  steps.marketplaces.progress = { done: 0, total: names.length };
257
- say(names.length === 0 ? "none eligible" : `pulling ${names.join(", ")}`);
256
+ say(
257
+ names.length === 0 ? "none eligible" : `pulling ${names.join(", ")}`,
258
+ );
258
259
  },
259
260
  onSettled: (name, status) => {
260
261
  settled++;
@@ -285,7 +286,9 @@ export async function runUpdateCommand(
285
286
  ),
286
287
  );
287
288
  notices.push(
288
- noteLine(`claude plugin marketplace update ${name} # to refresh it once`),
289
+ noteLine(
290
+ `claude plugin marketplace update ${name} # to refresh it once`,
291
+ ),
289
292
  );
290
293
  }
291
294
  for (const name of refresh.failed) {
@@ -439,7 +442,9 @@ export async function runUpdateCommand(
439
442
 
440
443
  if (!(await isClaudeAvailable())) {
441
444
  console.error(
442
- failLine("claude CLI not found on PATH — cannot install or update plugins."),
445
+ failLine(
446
+ "claude CLI not found on PATH — cannot install or update plugins.",
447
+ ),
443
448
  );
444
449
  return 1;
445
450
  }
@@ -26,7 +26,8 @@ export async function runUpgradeCommand(): Promise<number> {
26
26
  timeout: 5000,
27
27
  }).trim();
28
28
  const bunPath = `${bunGlobalBin}/claudeup`;
29
- if (existsSync(bunPath)) installations.push({ manager: "bun", path: bunPath });
29
+ if (existsSync(bunPath))
30
+ installations.push({ manager: "bun", path: bunPath });
30
31
  } catch {
31
32
  // bun not installed or no global claudeup
32
33
  }
@@ -38,7 +39,8 @@ export async function runUpgradeCommand(): Promise<number> {
38
39
  timeout: 5000,
39
40
  }).trim();
40
41
  const npmPath = `${npmPrefix}/bin/claudeup`;
41
- if (existsSync(npmPath)) installations.push({ manager: "npm", path: npmPath });
42
+ if (existsSync(npmPath))
43
+ installations.push({ manager: "npm", path: npmPath });
42
44
  } catch {
43
45
  // npm not installed or no global claudeup
44
46
  }
@@ -50,12 +52,12 @@ export async function runUpgradeCommand(): Promise<number> {
50
52
  timeout: 5000,
51
53
  }).trim();
52
54
 
53
- console.log(`⚠ claudeup is installed via multiple package managers:\n`);
55
+ console.log("⚠ claudeup is installed via multiple package managers:\n");
54
56
  for (const inst of installations) {
55
57
  const tag = inst.path === activePath ? " (active)" : " (shadowed)";
56
58
  console.log(` ${inst.manager}: ${inst.path}${tag}`);
57
59
  }
58
- console.log(`\nTo fix, keep one and remove the other:`);
60
+ console.log("\nTo fix, keep one and remove the other:");
59
61
  for (const inst of installations) {
60
62
  console.log(` ${inst.manager} uninstall -g claudeup`);
61
63
  }