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.
- package/README.md +22 -0
- package/bin/claudeup.js +6 -1
- package/package.json +6 -6
- package/src/__tests__/alias-adopt.test.ts +331 -329
- package/src/__tests__/alias-parser.test.ts +384 -376
- package/src/__tests__/alias-shell-writer.test.ts +638 -625
- package/src/__tests__/alias-store.test.ts +143 -143
- package/src/__tests__/catalog-cache-store.test.ts +265 -265
- package/src/__tests__/catalog-notice.test.ts +123 -123
- package/src/__tests__/cli-apply-seams.test.ts +12 -8
- package/src/__tests__/cli-router.test.ts +132 -3
- package/src/__tests__/cli-update-view.test.ts +27 -8
- package/src/__tests__/community-staleness.test.ts +0 -2
- package/src/__tests__/component-badge.test.ts +4 -4
- package/src/__tests__/content-drift.test.ts +5 -1
- package/src/__tests__/conventions-integration.test.ts +774 -689
- package/src/__tests__/conventions-manager.test.ts +1216 -1152
- package/src/__tests__/doctor-bins.test.ts +14 -6
- package/src/__tests__/doctor.test.ts +438 -425
- package/src/__tests__/dual-write-prevention.test.ts +277 -282
- package/src/__tests__/enabled-not-installed.test.ts +71 -63
- package/src/__tests__/file-locking.test.ts +196 -196
- package/src/__tests__/gap-fill-versions.test.ts +318 -311
- package/src/__tests__/github-budget.test.ts +170 -164
- package/src/__tests__/gitignore-detector.test.ts +160 -152
- package/src/__tests__/gitignore-fixer.test.ts +285 -247
- package/src/__tests__/gitignore-prerun.test.ts +57 -57
- package/src/__tests__/gitignore-resolver.test.ts +173 -146
- package/src/__tests__/gitignore-service.test.ts +121 -119
- package/src/__tests__/install-command.test.ts +3 -3
- package/src/__tests__/install-plan.test.ts +13 -7
- package/src/__tests__/manifest.test.ts +16 -5
- package/src/__tests__/markdown-renderer.test.ts +0 -1
- package/src/__tests__/marketplace-badge.test.ts +111 -90
- package/src/__tests__/marketplace-version.test.ts +64 -61
- package/src/__tests__/plugin-manager-fallback.test.ts +249 -239
- package/src/__tests__/plugin-requires.test.ts +18 -4
- package/src/__tests__/plugin-setup.test.ts +12 -12
- package/src/__tests__/profile-command.test.ts +1 -7
- package/src/__tests__/profile-materializer.test.ts +31 -8
- package/src/__tests__/profile-sync.test.ts +23 -5
- package/src/__tests__/registry-version-resolution.test.ts +61 -47
- package/src/__tests__/resolve-executable.test.ts +1 -1
- package/src/__tests__/resolver.test.ts +40 -15
- package/src/__tests__/scope-action.test.ts +52 -50
- package/src/__tests__/scope-squares.test.tsx +4 -5
- package/src/__tests__/skill-install-files.test.ts +111 -103
- package/src/__tests__/symlink-manager.test.ts +27 -10
- package/src/__tests__/theme-env.test.ts +281 -0
- package/src/__tests__/theme-resolve.test.ts +380 -0
- package/src/__tests__/update-apply.test.ts +1 -3
- package/src/__tests__/useGitignoreModal.test.ts +69 -61
- package/src/__tests__/version-snapshot.test.ts +181 -165
- package/src/cli/ansi.ts +23 -15
- package/src/cli/doctor.ts +10 -9
- package/src/cli/install.ts +3 -3
- package/src/cli/profile.ts +5 -4
- package/src/cli/prompt.ts +0 -1
- package/src/cli/router.ts +69 -2
- package/src/cli/update-view.ts +11 -6
- package/src/cli/update.ts +9 -4
- package/src/cli/upgrade.ts +6 -4
- package/src/data/alias-flags.ts +260 -260
- package/src/data/gitignore-defaults.ts +20 -20
- package/src/data/gitignore-reasons.ts +86 -92
- package/src/data/gitignore-templates.ts +14 -14
- package/src/data/predefined-profiles.ts +8 -9
- package/src/data/skill-repos.ts +43 -35
- package/src/main.tsx +29 -6
- package/src/opentui.d.ts +1 -3
- package/src/prerunner/index.ts +7 -8
- package/src/services/alias-settings.ts +20 -23
- package/src/services/alias-shell-writer.ts +800 -803
- package/src/services/alias-store.ts +77 -77
- package/src/services/claude-settings.ts +27 -18
- package/src/services/content-drift.ts +13 -3
- package/src/services/conventions-manager.ts +692 -617
- package/src/services/doctor.ts +464 -403
- package/src/services/gitignore-detector.ts +113 -103
- package/src/services/gitignore-fixer.ts +204 -189
- package/src/services/gitignore-prerun.ts +26 -26
- package/src/services/gitignore-resolver.ts +162 -144
- package/src/services/gitignore-service.ts +120 -117
- package/src/services/install-plan.ts +4 -4
- package/src/services/manifest.ts +1 -1
- package/src/services/marketplace-catalog-git.ts +5 -2
- package/src/services/marketplace-fetcher.ts +241 -235
- package/src/services/plugin-manager.ts +17 -20
- package/src/services/plugin-mcp-config.ts +2 -2
- package/src/services/plugin-requires.ts +9 -6
- package/src/services/plugin-setup.ts +7 -9
- package/src/services/plugin-version-check.ts +4 -4
- package/src/services/profile-materializer.ts +9 -5
- package/src/services/profile-sync.ts +12 -4
- package/src/services/profiles.ts +3 -3
- package/src/services/resolver.ts +10 -6
- package/src/services/settings-manager.ts +82 -46
- package/src/services/skills-manager.ts +11 -9
- package/src/services/symlink-manager.ts +2 -5
- package/src/services/toolchain.ts +3 -3
- package/src/services/update-cache.ts +1 -1
- package/src/services/update-engine.ts +4 -4
- package/src/services/update-plan.ts +9 -11
- package/src/services/version-snapshot.ts +88 -88
- package/src/types/gitignore.ts +28 -28
- package/src/ui/App.tsx +36 -36
- package/src/ui/adapters/settingsAdapter.ts +2 -2
- package/src/ui/components/CategoryHeader.tsx +1 -1
- package/src/ui/components/FlagDetailEditor.tsx +0 -0
- package/src/ui/components/ScopeIndicator.tsx +3 -1
- package/src/ui/components/ScrollableList.tsx +2 -1
- package/src/ui/components/StyledText.tsx +1 -1
- package/src/ui/components/layout/FooterHints.tsx +5 -3
- package/src/ui/components/layout/Panel.tsx +2 -2
- package/src/ui/components/layout/ScreenLayout.tsx +5 -4
- package/src/ui/components/modals/InputModal.tsx +7 -1
- package/src/ui/components/modals/ModalContainer.tsx +3 -3
- package/src/ui/components/modals/SelectModal.tsx +1 -1
- package/src/ui/components/primitives/DetailSection.tsx +1 -1
- package/src/ui/components/primitives/KeyValueLine.tsx +1 -1
- package/src/ui/components/primitives/ScopeSquares.tsx +2 -2
- package/src/ui/hooks/index.ts +0 -1
- package/src/ui/hooks/useGitignoreModal.ts +1 -5
- package/src/ui/hooks/useMismatchModal.ts +4 -10
- package/src/ui/renderers/cliToolRenderers.tsx +2 -2
- package/src/ui/renderers/gitignoreRenderers.tsx +2 -2
- package/src/ui/renderers/mcpRenderers.tsx +1 -1
- package/src/ui/renderers/pluginRenderers.tsx +3 -9
- package/src/ui/renderers/profileRenderers.tsx +9 -8
- package/src/ui/renderers/settingsRenderers.tsx +4 -4
- package/src/ui/renderers/skillRenderers.tsx +14 -14
- package/src/ui/screens/AliasScreen.tsx +31 -34
- package/src/ui/screens/CliToolsScreen.tsx +14 -13
- package/src/ui/screens/EnvVarsScreen.tsx +8 -8
- package/src/ui/screens/GitignoreScreen.tsx +15 -14
- package/src/ui/screens/McpRegistryScreen.tsx +10 -7
- package/src/ui/screens/McpScreen.tsx +10 -10
- package/src/ui/screens/PluginsScreen.tsx +17 -21
- package/src/ui/screens/ProfilesScreen.tsx +25 -24
- package/src/ui/screens/SkillsScreen.tsx +16 -14
- package/src/ui/state/AppContext.tsx +4 -8
- package/src/ui/state/DimensionsContext.tsx +3 -2
- package/src/ui/theme-mode.ts +20 -38
- package/src/ui/theme-resolve.ts +148 -0
- package/src/utils/clipboard.ts +5 -5
- package/src/utils/command-utils.ts +2 -2
- package/src/utils/config-dir.ts +1 -3
- package/src/utils/file-locking.ts +88 -86
- package/src/ui/hooks/useAsyncData.ts +0 -127
- package/src/ui/state/AnimationContext.tsx +0 -76
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { beforeEach, describe, expect, it } from "bun:test";
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
activeCooldowns,
|
|
4
|
+
cooldownFor,
|
|
5
|
+
describeCooldown,
|
|
6
|
+
formatWait,
|
|
7
|
+
hostOf,
|
|
8
|
+
recordRateLimit,
|
|
9
|
+
recordSuccess,
|
|
10
|
+
remainingFromHeaders,
|
|
11
|
+
resetGitHubBudget,
|
|
12
|
+
waitMs,
|
|
13
13
|
} from "../services/github-budget";
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -26,175 +26,181 @@ import {
|
|
|
26
26
|
const NOW = 1_786_980_000_000;
|
|
27
27
|
|
|
28
28
|
beforeEach(() => {
|
|
29
|
-
|
|
29
|
+
resetGitHubBudget();
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
describe("hostOf", () => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
it("recognises the two budgeted hosts and nothing else", () => {
|
|
34
|
+
expect(hostOf("https://api.github.com/repos/x/y")).toBe("api.github.com");
|
|
35
|
+
expect(hostOf("https://raw.githubusercontent.com/x/y/main/f.json")).toBe(
|
|
36
|
+
"raw.githubusercontent.com",
|
|
37
|
+
);
|
|
38
|
+
expect(hostOf("https://github.com/x/y")).toBeNull();
|
|
39
|
+
expect(hostOf("not a url")).toBeNull();
|
|
40
|
+
});
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
describe("recordRateLimit — exact timing when GitHub provides it", () => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
44
|
+
it("honours retry-after in seconds", () => {
|
|
45
|
+
const cooldown = recordRateLimit(
|
|
46
|
+
"api.github.com",
|
|
47
|
+
new Headers({ "retry-after": "120" }),
|
|
48
|
+
NOW,
|
|
49
|
+
);
|
|
50
|
+
expect(cooldown.exact).toBe(true);
|
|
51
|
+
expect(cooldown.until).toBe(NOW + 120_000);
|
|
52
|
+
expect(describeCooldown(cooldown, NOW)).toBe(
|
|
53
|
+
"GitHub rate limit resets in 2m",
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("honours retry-after as an HTTP date", () => {
|
|
58
|
+
const when = new Date(NOW + 300_000).toUTCString();
|
|
59
|
+
const cooldown = recordRateLimit(
|
|
60
|
+
"api.github.com",
|
|
61
|
+
new Headers({ "retry-after": when }),
|
|
62
|
+
NOW,
|
|
63
|
+
);
|
|
64
|
+
expect(cooldown.exact).toBe(true);
|
|
65
|
+
// UTCString drops sub-second precision, so allow a second of slack.
|
|
66
|
+
expect(Math.abs(cooldown.until - (NOW + 300_000))).toBeLessThanOrEqual(
|
|
67
|
+
1000,
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("reads x-ratelimit-reset as epoch SECONDS, not milliseconds", () => {
|
|
72
|
+
// Treating it as ms lands in 1970, so the cooldown expires instantly and the
|
|
73
|
+
// UI reports "not rate limited" while GitHub is still refusing every call.
|
|
74
|
+
const resetEpochSeconds = Math.floor(NOW / 1000) + 600;
|
|
75
|
+
const cooldown = recordRateLimit(
|
|
76
|
+
"api.github.com",
|
|
77
|
+
new Headers({ "x-ratelimit-reset": String(resetEpochSeconds) }),
|
|
78
|
+
NOW,
|
|
79
|
+
);
|
|
80
|
+
expect(cooldown.exact).toBe(true);
|
|
81
|
+
expect(cooldown.until).toBe(resetEpochSeconds * 1000);
|
|
82
|
+
expect(waitMs("api.github.com", NOW)).toBeGreaterThan(0);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("caps an implausible server window so a bad header cannot wedge the UI", () => {
|
|
86
|
+
const cooldown = recordRateLimit(
|
|
87
|
+
"api.github.com",
|
|
88
|
+
new Headers({ "retry-after": String(60 * 60 * 24 * 30) }),
|
|
89
|
+
NOW,
|
|
90
|
+
);
|
|
91
|
+
expect(cooldown.until - NOW).toBeLessThanOrEqual(60 * 60_000);
|
|
92
|
+
});
|
|
91
93
|
});
|
|
92
94
|
|
|
93
95
|
describe("recordRateLimit — backoff when GitHub provides nothing", () => {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
96
|
+
it("does not claim a reset time it was never told", () => {
|
|
97
|
+
// This is the raw.githubusercontent.com case, measured: a bare 429.
|
|
98
|
+
const cooldown = recordRateLimit(
|
|
99
|
+
"raw.githubusercontent.com",
|
|
100
|
+
new Headers(),
|
|
101
|
+
NOW,
|
|
102
|
+
);
|
|
103
|
+
expect(cooldown.exact).toBe(false);
|
|
104
|
+
expect(describeCooldown(cooldown, NOW)).toContain("next attempt in");
|
|
105
|
+
expect(describeCooldown(cooldown, NOW)).not.toContain("resets in");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("backs off further on each consecutive failure", () => {
|
|
109
|
+
const first = recordRateLimit("raw.githubusercontent.com", undefined, NOW);
|
|
110
|
+
const second = recordRateLimit("raw.githubusercontent.com", undefined, NOW);
|
|
111
|
+
const third = recordRateLimit("raw.githubusercontent.com", undefined, NOW);
|
|
112
|
+
expect(second.until).toBeGreaterThan(first.until);
|
|
113
|
+
expect(third.until).toBeGreaterThan(second.until);
|
|
114
|
+
expect(third.strikes).toBe(3);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("ignores a retry-after that is garbage or already past", () => {
|
|
118
|
+
const garbage = recordRateLimit(
|
|
119
|
+
"raw.githubusercontent.com",
|
|
120
|
+
new Headers({ "retry-after": "soon-ish" }),
|
|
121
|
+
NOW,
|
|
122
|
+
);
|
|
123
|
+
expect(garbage.exact).toBe(false);
|
|
124
|
+
expect(garbage.until).toBeGreaterThan(NOW);
|
|
125
|
+
});
|
|
124
126
|
});
|
|
125
127
|
|
|
126
128
|
describe("the gate", () => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
129
|
+
it("blocks while cooling and clears when the window passes", () => {
|
|
130
|
+
const cooldown = recordRateLimit(
|
|
131
|
+
"raw.githubusercontent.com",
|
|
132
|
+
undefined,
|
|
133
|
+
NOW,
|
|
134
|
+
);
|
|
135
|
+
expect(cooldownFor("raw.githubusercontent.com", NOW)).not.toBeNull();
|
|
136
|
+
expect(
|
|
137
|
+
cooldownFor("raw.githubusercontent.com", cooldown.until + 1),
|
|
138
|
+
).toBeNull();
|
|
139
|
+
expect(waitMs("raw.githubusercontent.com", cooldown.until + 1)).toBe(0);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("keeps the two hosts on separate budgets", () => {
|
|
143
|
+
// They are separate buckets upstream. Blocking raw because the API is
|
|
144
|
+
// exhausted would stop work that would have succeeded.
|
|
145
|
+
recordRateLimit(
|
|
146
|
+
"api.github.com",
|
|
147
|
+
new Headers({ "retry-after": "60" }),
|
|
148
|
+
NOW,
|
|
149
|
+
);
|
|
150
|
+
expect(cooldownFor("api.github.com", NOW)).not.toBeNull();
|
|
151
|
+
expect(cooldownFor("raw.githubusercontent.com", NOW)).toBeNull();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("clears the cooldown and the strike count on success", () => {
|
|
155
|
+
// Without resetting strikes, one bad afternoon pins the backoff at ten
|
|
156
|
+
// minutes for the rest of the session even though calls are succeeding.
|
|
157
|
+
recordRateLimit("raw.githubusercontent.com", undefined, NOW);
|
|
158
|
+
recordRateLimit("raw.githubusercontent.com", undefined, NOW);
|
|
159
|
+
recordSuccess("raw.githubusercontent.com");
|
|
160
|
+
expect(cooldownFor("raw.githubusercontent.com", NOW)).toBeNull();
|
|
161
|
+
|
|
162
|
+
const afterRecovery = recordRateLimit(
|
|
163
|
+
"raw.githubusercontent.com",
|
|
164
|
+
undefined,
|
|
165
|
+
NOW,
|
|
166
|
+
);
|
|
167
|
+
expect(afterRecovery.strikes).toBe(1);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("lists active cooldowns soonest first", () => {
|
|
171
|
+
recordRateLimit(
|
|
172
|
+
"api.github.com",
|
|
173
|
+
new Headers({ "retry-after": "600" }),
|
|
174
|
+
NOW,
|
|
175
|
+
);
|
|
176
|
+
recordRateLimit(
|
|
177
|
+
"raw.githubusercontent.com",
|
|
178
|
+
new Headers({ "retry-after": "30" }),
|
|
179
|
+
NOW,
|
|
180
|
+
);
|
|
181
|
+
const active = activeCooldowns(NOW);
|
|
182
|
+
expect(active.map((c) => c.host)).toEqual([
|
|
183
|
+
"raw.githubusercontent.com",
|
|
184
|
+
"api.github.com",
|
|
185
|
+
]);
|
|
186
|
+
});
|
|
181
187
|
});
|
|
182
188
|
|
|
183
189
|
describe("remainingFromHeaders", () => {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
+
it("reads the budget so a caller can stop before tripping the limit", () => {
|
|
191
|
+
expect(
|
|
192
|
+
remainingFromHeaders(new Headers({ "x-ratelimit-remaining": "51" })),
|
|
193
|
+
).toBe(51);
|
|
194
|
+
expect(remainingFromHeaders(new Headers())).toBeNull();
|
|
195
|
+
});
|
|
190
196
|
});
|
|
191
197
|
|
|
192
198
|
describe("formatWait", () => {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
199
|
+
it("reads naturally at every scale", () => {
|
|
200
|
+
expect(formatWait(0)).toBe("0s");
|
|
201
|
+
expect(formatWait(45_000)).toBe("45s");
|
|
202
|
+
expect(formatWait(90_000)).toBe("2m");
|
|
203
|
+
expect(formatWait(3_600_000)).toBe("1h 0m");
|
|
204
|
+
expect(formatWait(4_320_000)).toBe("1h 12m");
|
|
205
|
+
});
|
|
200
206
|
});
|