claudeup 6.2.1 → 6.3.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 (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 +38 -13
  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 +183 -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 +1 -2
  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
@@ -1,163 +1,163 @@
1
- import { describe, it, expect } from "bun:test";
1
+ import { describe, expect, it } from "bun:test";
2
+ import { ALIAS_FLAGS } from "../data/alias-flags";
2
3
  import {
3
- defaultAliasConfig,
4
- defaultValueFor,
5
- validateAliasName,
6
- derivedChannelValues,
7
- withoutDerivedChannel,
8
- listValues,
9
- CHANNELS_FLAG_ID,
10
- DEVLOAD_FLAG_ID,
11
- DEFAULT_ALIAS_NAME,
12
- type FlagValue,
4
+ CHANNELS_FLAG_ID,
5
+ DEFAULT_ALIAS_NAME,
6
+ DEVLOAD_FLAG_ID,
7
+ type FlagValue,
8
+ defaultAliasConfig,
9
+ defaultValueFor,
10
+ derivedChannelValues,
11
+ listValues,
12
+ validateAliasName,
13
+ withoutDerivedChannel,
13
14
  } from "../services/alias-store";
14
- import { ALIAS_FLAGS } from "../data/alias-flags";
15
15
 
16
16
  function textList(values: string[], enabled = true): FlagValue {
17
- return { kind: "text-list", enabled, values };
17
+ return { kind: "text-list", enabled, values };
18
18
  }
19
19
 
20
20
  describe("defaultAliasConfig", () => {
21
- it("seeds every known flag with a sane default and the default alias name", () => {
22
- const c = defaultAliasConfig();
23
- expect(c.aliasName).toBe(DEFAULT_ALIAS_NAME);
24
- expect(c.aliasName).toBe("c");
25
- expect(c.flags["dangerously-skip-permissions"]).toEqual({
26
- kind: "boolean",
27
- enabled: false,
28
- });
29
- expect(c.flags["chrome"]).toEqual({ kind: "tri-state", state: "unset" });
30
- expect(c.flags["effort"]).toEqual({
31
- kind: "select",
32
- enabled: false,
33
- value: "low",
34
- });
35
- expect(c.flags["dangerously-load-development-channels"]).toEqual({
36
- kind: "text-list",
37
- enabled: false,
38
- values: ["plugin:claudish@magus"],
39
- });
40
- expect(c.flags["debug"]).toEqual({
41
- kind: "multi-with-custom",
42
- enabled: false,
43
- picked: [],
44
- custom: [],
45
- });
46
- });
47
-
48
- it("covers every flag in the catalog", () => {
49
- const c = defaultAliasConfig();
50
- for (const flag of ALIAS_FLAGS) {
51
- expect(c.flags[flag.id]).toBeDefined();
52
- }
53
- });
21
+ it("seeds every known flag with a sane default and the default alias name", () => {
22
+ const c = defaultAliasConfig();
23
+ expect(c.aliasName).toBe(DEFAULT_ALIAS_NAME);
24
+ expect(c.aliasName).toBe("c");
25
+ expect(c.flags["dangerously-skip-permissions"]).toEqual({
26
+ kind: "boolean",
27
+ enabled: false,
28
+ });
29
+ expect(c.flags.chrome).toEqual({ kind: "tri-state", state: "unset" });
30
+ expect(c.flags.effort).toEqual({
31
+ kind: "select",
32
+ enabled: false,
33
+ value: "low",
34
+ });
35
+ expect(c.flags["dangerously-load-development-channels"]).toEqual({
36
+ kind: "text-list",
37
+ enabled: false,
38
+ values: ["plugin:claudish@magus"],
39
+ });
40
+ expect(c.flags.debug).toEqual({
41
+ kind: "multi-with-custom",
42
+ enabled: false,
43
+ picked: [],
44
+ custom: [],
45
+ });
46
+ });
47
+
48
+ it("covers every flag in the catalog", () => {
49
+ const c = defaultAliasConfig();
50
+ for (const flag of ALIAS_FLAGS) {
51
+ expect(c.flags[flag.id]).toBeDefined();
52
+ }
53
+ });
54
54
  });
55
55
 
56
56
  describe("defaultValueFor", () => {
57
- it("returns the right default for each kind", () => {
58
- const ide = ALIAS_FLAGS.find((f) => f.id === "ide")!;
59
- expect(defaultValueFor(ide)).toEqual({ kind: "boolean", enabled: false });
60
-
61
- const chrome = ALIAS_FLAGS.find((f) => f.id === "chrome")!;
62
- expect(defaultValueFor(chrome)).toEqual({
63
- kind: "tri-state",
64
- state: "unset",
65
- });
66
-
67
- const debug = ALIAS_FLAGS.find((f) => f.id === "debug")!;
68
- expect(defaultValueFor(debug)).toEqual({
69
- kind: "multi-with-custom",
70
- enabled: false,
71
- picked: [],
72
- custom: [],
73
- });
74
- });
57
+ it("returns the right default for each kind", () => {
58
+ const ide = ALIAS_FLAGS.find((f) => f.id === "ide")!;
59
+ expect(defaultValueFor(ide)).toEqual({ kind: "boolean", enabled: false });
60
+
61
+ const chrome = ALIAS_FLAGS.find((f) => f.id === "chrome")!;
62
+ expect(defaultValueFor(chrome)).toEqual({
63
+ kind: "tri-state",
64
+ state: "unset",
65
+ });
66
+
67
+ const debug = ALIAS_FLAGS.find((f) => f.id === "debug")!;
68
+ expect(defaultValueFor(debug)).toEqual({
69
+ kind: "multi-with-custom",
70
+ enabled: false,
71
+ picked: [],
72
+ custom: [],
73
+ });
74
+ });
75
75
  });
76
76
 
77
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
- });
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
108
  });
109
109
 
110
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
- });
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(
139
+ listValues(withoutDerivedChannel(flags, "nope")[DEVLOAD_FLAG_ID]),
140
+ ).toEqual(["plugin:a@m"]);
141
+ });
142
142
  });
143
143
 
144
144
  describe("validateAliasName", () => {
145
- it("accepts safe identifiers", () => {
146
- expect(validateAliasName("c")).toBeNull();
147
- expect(validateAliasName("claude")).toBeNull();
148
- expect(validateAliasName("cc-yolo_2")).toBeNull();
149
- expect(validateAliasName("_underscore")).toBeNull();
150
- });
151
-
152
- it("rejects empty, leading-digit, and special-char names", () => {
153
- expect(validateAliasName("")).toMatch(/empty/);
154
- expect(validateAliasName("9live")).toMatch(/letter/);
155
- expect(validateAliasName("c laude")).toMatch(/letters/);
156
- expect(validateAliasName("c'")).toMatch(/letters/);
157
- expect(validateAliasName("c=d")).toMatch(/letters/);
158
- });
159
-
160
- it("rejects pathologically long names", () => {
161
- expect(validateAliasName("a".repeat(64))).toMatch(/long/);
162
- });
145
+ it("accepts safe identifiers", () => {
146
+ expect(validateAliasName("c")).toBeNull();
147
+ expect(validateAliasName("claude")).toBeNull();
148
+ expect(validateAliasName("cc-yolo_2")).toBeNull();
149
+ expect(validateAliasName("_underscore")).toBeNull();
150
+ });
151
+
152
+ it("rejects empty, leading-digit, and special-char names", () => {
153
+ expect(validateAliasName("")).toMatch(/empty/);
154
+ expect(validateAliasName("9live")).toMatch(/letter/);
155
+ expect(validateAliasName("c laude")).toMatch(/letters/);
156
+ expect(validateAliasName("c'")).toMatch(/letters/);
157
+ expect(validateAliasName("c=d")).toMatch(/letters/);
158
+ });
159
+
160
+ it("rejects pathologically long names", () => {
161
+ expect(validateAliasName("a".repeat(64))).toMatch(/long/);
162
+ });
163
163
  });