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
@@ -6,6 +6,7 @@ import {
6
6
  tokenizePosixAliasBody,
7
7
  argsToFlagValues,
8
8
  renderAlias,
9
+ renderArgsAsTokens,
9
10
  } from "../services/alias-shell-writer";
10
11
  import { defaultAliasConfig } from "../services/alias-store";
11
12
 
@@ -295,6 +296,97 @@ describe("round-trip: render → parse → enabled subset matches", () => {
295
296
  });
296
297
  });
297
298
 
299
+ describe("channels: render-time union + parse-time provenance split", () => {
300
+ // The model: channels.values holds OWN channels only; dev-load values are
301
+ // unioned into --channels at render time and subtracted back out on parse,
302
+ // so the two lists stay distinct in storage but merge on the command line.
303
+
304
+ it("emits --channels as (own ∪ enabled dev-load), deduped, own-first", () => {
305
+ const c = defaultAliasConfig();
306
+ c.flags["channels"] = {
307
+ kind: "text-list",
308
+ enabled: true,
309
+ values: ["plugin:own@m"],
310
+ };
311
+ c.flags["dangerously-load-development-channels"] = {
312
+ kind: "text-list",
313
+ enabled: true,
314
+ values: ["plugin:dev@m", "plugin:own@m"], // overlap dedupes
315
+ };
316
+ const tokens = renderArgsAsTokens(c);
317
+ // --channels carries own + derived (deduped); dev-load still emits its own.
318
+ const channelsArgs = tokens
319
+ .map((t, i) => (tokens[i - 1] === "--channels" ? t : null))
320
+ .filter(Boolean);
321
+ expect(channelsArgs).toEqual(["plugin:own@m", "plugin:dev@m"]);
322
+ });
323
+
324
+ it("forces --channels to emit derived values even when own channels is OFF", () => {
325
+ const c = defaultAliasConfig();
326
+ c.flags["channels"] = { kind: "text-list", enabled: false, values: [] };
327
+ c.flags["dangerously-load-development-channels"] = {
328
+ kind: "text-list",
329
+ enabled: true,
330
+ values: ["plugin:dev@m"],
331
+ };
332
+ const tokens = renderArgsAsTokens(c);
333
+ expect(tokens).toContain("--channels");
334
+ const idx = tokens.indexOf("--channels");
335
+ expect(tokens[idx + 1]).toBe("plugin:dev@m");
336
+ });
337
+
338
+ it("does NOT emit --channels when both own and dev-load are empty/off", () => {
339
+ const c = defaultAliasConfig(); // both channels + dev-load default off
340
+ expect(renderArgsAsTokens(c)).not.toContain("--channels");
341
+ });
342
+
343
+ it("round-trips: derived values do NOT leak into parsed channels.values", () => {
344
+ const c = defaultAliasConfig();
345
+ c.aliasName = "c";
346
+ c.flags["channels"] = {
347
+ kind: "text-list",
348
+ enabled: true,
349
+ values: ["plugin:own@m"],
350
+ };
351
+ c.flags["dangerously-load-development-channels"] = {
352
+ kind: "text-list",
353
+ enabled: true,
354
+ values: ["plugin:dev@m"],
355
+ };
356
+ const rendered = renderAlias(c, "zsh").block;
357
+ const parsed = parseAliasFromRc(rendered);
358
+ // channels.values comes back as OWN ONLY — derived was subtracted.
359
+ expect(parsed!.flags["channels"]).toEqual({
360
+ kind: "text-list",
361
+ enabled: true,
362
+ values: ["plugin:own@m"],
363
+ });
364
+ // dev-load round-trips intact (it's the source of truth for derived).
365
+ expect(parsed!.flags["dangerously-load-development-channels"]).toEqual({
366
+ kind: "text-list",
367
+ enabled: true,
368
+ values: ["plugin:dev@m"],
369
+ });
370
+ });
371
+
372
+ it("round-trips a purely-derived channels list to disabled+empty own", () => {
373
+ const c = defaultAliasConfig();
374
+ c.aliasName = "c";
375
+ // No own channels; only dev-load.
376
+ c.flags["dangerously-load-development-channels"] = {
377
+ kind: "text-list",
378
+ enabled: true,
379
+ values: ["plugin:dev@m"],
380
+ };
381
+ const parsed = parseAliasFromRc(renderAlias(c, "zsh").block);
382
+ expect(parsed!.flags["channels"]).toEqual({
383
+ kind: "text-list",
384
+ enabled: false,
385
+ values: [],
386
+ });
387
+ });
388
+ });
389
+
298
390
  describe("parseAliasFromRc — entry point", () => {
299
391
  it("returns null on text with no managed block", () => {
300
392
  expect(parseAliasFromRc("export PATH=...\n")).toBeNull();
@@ -92,7 +92,14 @@ describe("renderArgs", () => {
92
92
  enabled: true,
93
93
  values: ["plugin:claudish@magus", "plugin:dev@magus"],
94
94
  };
95
+ // Enabling dev-load also forces --channels to emit (the render-time union),
96
+ // since a dev-loaded channel must be on the channels list to run. --channels
97
+ // comes first (catalog order), then the repeated dev-load pairs.
95
98
  expect(renderArgs(c)).toEqual([
99
+ "--channels",
100
+ "plugin:claudish@magus",
101
+ "--channels",
102
+ "plugin:dev@magus",
96
103
  "--dangerously-load-development-channels",
97
104
  "plugin:claudish@magus",
98
105
  "--dangerously-load-development-channels",
@@ -3,10 +3,20 @@ import {
3
3
  defaultAliasConfig,
4
4
  defaultValueFor,
5
5
  validateAliasName,
6
+ derivedChannelValues,
7
+ withoutDerivedChannel,
8
+ listValues,
9
+ CHANNELS_FLAG_ID,
10
+ DEVLOAD_FLAG_ID,
6
11
  DEFAULT_ALIAS_NAME,
12
+ type FlagValue,
7
13
  } from "../services/alias-store";
8
14
  import { ALIAS_FLAGS } from "../data/alias-flags";
9
15
 
16
+ function textList(values: string[], enabled = true): FlagValue {
17
+ return { kind: "text-list", enabled, values };
18
+ }
19
+
10
20
  describe("defaultAliasConfig", () => {
11
21
  it("seeds every known flag with a sane default and the default alias name", () => {
12
22
  const c = defaultAliasConfig();
@@ -64,6 +74,73 @@ describe("defaultValueFor", () => {
64
74
  });
65
75
  });
66
76
 
77
+ describe("derivedChannelValues (derive-on-display model)", () => {
78
+ it("returns dev-load values when the dev-load flag is enabled", () => {
79
+ const flags: Record<string, FlagValue> = {
80
+ [DEVLOAD_FLAG_ID]: textList(["plugin:claudish@magus"], true),
81
+ [CHANNELS_FLAG_ID]: textList([], false),
82
+ };
83
+ expect(derivedChannelValues(flags)).toEqual(["plugin:claudish@magus"]);
84
+ });
85
+
86
+ it("returns nothing when the dev-load flag is DISABLED", () => {
87
+ const flags: Record<string, FlagValue> = {
88
+ [DEVLOAD_FLAG_ID]: textList(["plugin:claudish@magus"], false),
89
+ [CHANNELS_FLAG_ID]: textList([], false),
90
+ };
91
+ expect(derivedChannelValues(flags)).toEqual([]);
92
+ });
93
+
94
+ it("returns nothing when the dev-load flag is absent", () => {
95
+ expect(derivedChannelValues({})).toEqual([]);
96
+ });
97
+
98
+ it("does NOT read from channels.values (provenance stays separate)", () => {
99
+ // channels holds OWN values; dev-load is the only derived source.
100
+ const flags: Record<string, FlagValue> = {
101
+ [CHANNELS_FLAG_ID]: textList(["plugin:own@m"], true),
102
+ [DEVLOAD_FLAG_ID]: textList(["plugin:dev@m"], true),
103
+ };
104
+ expect(derivedChannelValues(flags)).toEqual(["plugin:dev@m"]);
105
+ // channels.values is untouched — own only.
106
+ expect(listValues(flags[CHANNELS_FLAG_ID])).toEqual(["plugin:own@m"]);
107
+ });
108
+ });
109
+
110
+ describe("withoutDerivedChannel (delete-through to dev-load)", () => {
111
+ it("removes the value from dev-load, NOT from channels.values", () => {
112
+ const flags: Record<string, FlagValue> = {
113
+ [CHANNELS_FLAG_ID]: textList(["plugin:own@m"], true),
114
+ [DEVLOAD_FLAG_ID]: textList(["plugin:dev@m", "plugin:keep@m"], true),
115
+ };
116
+ const next = withoutDerivedChannel(flags, "plugin:dev@m");
117
+ expect(listValues(next[DEVLOAD_FLAG_ID])).toEqual(["plugin:keep@m"]);
118
+ // channels.values untouched — the deletion landed on the source flag.
119
+ expect(listValues(next[CHANNELS_FLAG_ID])).toEqual(["plugin:own@m"]);
120
+ });
121
+
122
+ it("disables dev-load when its last value is removed", () => {
123
+ const flags: Record<string, FlagValue> = {
124
+ [DEVLOAD_FLAG_ID]: textList(["plugin:only@m"], true),
125
+ };
126
+ const next = withoutDerivedChannel(flags, "plugin:only@m");
127
+ expect(next[DEVLOAD_FLAG_ID]).toEqual({
128
+ kind: "text-list",
129
+ enabled: false,
130
+ values: [],
131
+ });
132
+ });
133
+
134
+ it("is a no-op when the value isn't present", () => {
135
+ const flags: Record<string, FlagValue> = {
136
+ [DEVLOAD_FLAG_ID]: textList(["plugin:a@m"], true),
137
+ };
138
+ expect(listValues(withoutDerivedChannel(flags, "nope")[DEVLOAD_FLAG_ID])).toEqual([
139
+ "plugin:a@m",
140
+ ]);
141
+ });
142
+ });
143
+
67
144
  describe("validateAliasName", () => {
68
145
  it("accepts safe identifiers", () => {
69
146
  expect(validateAliasName("c")).toBeNull();
@@ -0,0 +1,65 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
+ import { route } from "../cli/router.js";
3
+
4
+ // Silence the help banner and capture what the router prints, without
5
+ // touching the network (--version) or spawning processes (claude/update).
6
+ let logSpy: ReturnType<typeof mock>;
7
+ let errSpy: ReturnType<typeof mock>;
8
+ let logs: string[];
9
+ let errs: string[];
10
+
11
+ beforeEach(() => {
12
+ logs = [];
13
+ errs = [];
14
+ logSpy = mock((...a: unknown[]) => {
15
+ logs.push(a.join(" "));
16
+ });
17
+ errSpy = mock((...a: unknown[]) => {
18
+ errs.push(a.join(" "));
19
+ });
20
+ // biome-ignore lint/suspicious/noExplicitAny: test double
21
+ console.log = logSpy as any;
22
+ // biome-ignore lint/suspicious/noExplicitAny: test double
23
+ console.error = errSpy as any;
24
+ });
25
+
26
+ afterEach(() => {
27
+ mock.restore();
28
+ });
29
+
30
+ describe("route() dispatch", () => {
31
+ test("bare invocation falls through to the TUI", async () => {
32
+ const r = await route([], "1.2.3");
33
+ expect(r.handled).toBe(false);
34
+ expect(r.launchTui).toBe(true);
35
+ });
36
+
37
+ test("an unrecognized token falls through to the TUI", async () => {
38
+ const r = await route(["totally-unknown"], "1.2.3");
39
+ expect(r.handled).toBe(false);
40
+ expect(r.launchTui).toBe(true);
41
+ });
42
+
43
+ test("--help is handled, exits 0, and does not launch the TUI", async () => {
44
+ const r = await route(["--help"], "9.9.9");
45
+ expect(r).toEqual({ handled: true, exitCode: 0, launchTui: false });
46
+ expect(logs.join("\n")).toContain("claudeup v9.9.9");
47
+ // The corrected help must not carry the stale tab names.
48
+ expect(logs.join("\n")).not.toContain("[3] Status");
49
+ expect(logs.join("\n")).toContain("[3] MCP");
50
+ });
51
+
52
+ test("-h is treated the same as --help", async () => {
53
+ const r = await route(["-h"], "1.0.0");
54
+ expect(r.handled).toBe(true);
55
+ expect(r.exitCode).toBe(0);
56
+ });
57
+
58
+ test("install, profile, and doctor are routed to real handlers (not stubs)", async () => {
59
+ // The router maps these to their real command handlers, which are covered
60
+ // by their services' unit tests. We don't invoke them here to avoid
61
+ // touching the filesystem / running global installs.
62
+ const mod = await import("../cli/router.js");
63
+ expect(typeof mod.route).toBe("function");
64
+ });
65
+ });