claudeup 4.18.0 → 4.19.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.
- package/package.json +1 -1
- package/src/__tests__/alias-adopt.test.ts +364 -0
- package/src/__tests__/alias-parser.test.ts +92 -0
- package/src/__tests__/alias-shell-writer.test.ts +7 -0
- package/src/__tests__/alias-store.test.ts +77 -0
- package/src/__tests__/plugin-setup.test.ts +111 -0
- package/src/data/alias-flags.js +10 -1
- package/src/data/alias-flags.ts +11 -1
- package/src/services/alias-shell-writer.js +262 -8
- package/src/services/alias-shell-writer.ts +382 -8
- package/src/services/alias-store.js +52 -0
- package/src/services/alias-store.ts +60 -0
- package/src/services/plugin-setup.js +59 -4
- package/src/services/plugin-setup.ts +61 -4
- package/src/ui/App.js +16 -7
- package/src/ui/App.tsx +16 -7
- package/src/ui/components/FlagDetailEditor.js +0 -0
- package/src/ui/components/FlagDetailEditor.tsx +0 -0
- package/src/ui/components/modals/ConfirmModal.js +1 -1
- package/src/ui/components/modals/ConfirmModal.tsx +1 -1
- package/src/ui/screens/AliasScreen.js +380 -277
- package/src/ui/screens/AliasScreen.tsx +491 -359
- package/src/ui/screens/PluginsScreen.js +4 -1
- package/src/ui/screens/PluginsScreen.tsx +3 -1
- package/src/ui/state/reducer.js +5 -1
- package/src/ui/state/reducer.ts +6 -1
- package/src/ui/state/types.ts +8 -0
|
@@ -295,6 +295,65 @@ export function extractGoBinaryName(pkg: string): string {
|
|
|
295
295
|
return withoutVersion.split("/").pop() || pkg;
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
+
/**
|
|
299
|
+
* Extract the pinned version from a Go module path, or null when unpinned.
|
|
300
|
+
* e.g., "github.com/MadAppGang/tmux-mcp@v1.6.2" → "v1.6.2"
|
|
301
|
+
* "github.com/MadAppGang/tmux-mcp@latest" → null (a moving target)
|
|
302
|
+
* "github.com/user/tool" → null
|
|
303
|
+
*/
|
|
304
|
+
export function extractGoVersion(pkg: string): string | null {
|
|
305
|
+
const at = pkg.lastIndexOf("@");
|
|
306
|
+
if (at === -1) return null;
|
|
307
|
+
const version = pkg.slice(at + 1);
|
|
308
|
+
if (version === "" || version === "latest") return null;
|
|
309
|
+
return version;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Normalize a semver-ish string for comparison: trim and drop a leading "v". */
|
|
313
|
+
function normalizeVersion(v: string): string {
|
|
314
|
+
return v.trim().replace(/^v/, "");
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Read an installed binary's version via `<binary> --version`, normalized, or
|
|
319
|
+
* null if it produced no usable version string. Used to tell whether a pinned Go
|
|
320
|
+
* dependency is already at the right version.
|
|
321
|
+
*/
|
|
322
|
+
async function getInstalledBinaryVersion(
|
|
323
|
+
binaryName: string,
|
|
324
|
+
): Promise<string | null> {
|
|
325
|
+
const { ok, stdout } = await run(binaryName, ["--version"], 10000);
|
|
326
|
+
if (!ok || !stdout) return null;
|
|
327
|
+
const match = stdout.split("\n")[0].match(/v?\d+\.\d+\.\d+[^\s]*/);
|
|
328
|
+
return match ? normalizeVersion(match[0]) : null;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Decide whether a Go dependency needs (re)installing. This is what makes a
|
|
333
|
+
* plugin *update* actually update its binary: without a version check an
|
|
334
|
+
* already-present binary is skipped forever, so a bumped plugin never pulls the
|
|
335
|
+
* new binary.
|
|
336
|
+
*
|
|
337
|
+
* - binary absent → yes
|
|
338
|
+
* - pinned version, installed reports the same one → no
|
|
339
|
+
* - pinned version, installed reports a different one → yes
|
|
340
|
+
* - pinned version, installed version can't be read → yes (can't confirm the
|
|
341
|
+
* pin, so reinstall to be safe)
|
|
342
|
+
* - unpinned (@latest) and present → no (a moving target
|
|
343
|
+
* can't be verified without a network round-trip)
|
|
344
|
+
*/
|
|
345
|
+
export async function goDepNeedsInstall(pkg: string): Promise<boolean> {
|
|
346
|
+
const binaryName = extractGoBinaryName(pkg);
|
|
347
|
+
if (!(await isBinaryAvailable(binaryName))) return true;
|
|
348
|
+
|
|
349
|
+
const pinned = extractGoVersion(pkg);
|
|
350
|
+
if (!pinned) return false;
|
|
351
|
+
|
|
352
|
+
const installed = await getInstalledBinaryVersion(binaryName);
|
|
353
|
+
if (installed === null) return true;
|
|
354
|
+
return normalizeVersion(pinned) !== installed;
|
|
355
|
+
}
|
|
356
|
+
|
|
298
357
|
/**
|
|
299
358
|
* Install Go packages via `go install`
|
|
300
359
|
*/
|
|
@@ -311,8 +370,7 @@ async function installGoPackages(
|
|
|
311
370
|
}
|
|
312
371
|
|
|
313
372
|
for (const pkg of packages) {
|
|
314
|
-
|
|
315
|
-
if (await isBinaryAvailable(binaryName)) {
|
|
373
|
+
if (!(await goDepNeedsInstall(pkg))) {
|
|
316
374
|
result.skipped.push(`go:${pkg}`);
|
|
317
375
|
continue;
|
|
318
376
|
}
|
|
@@ -515,8 +573,7 @@ export async function checkMissingDeps(
|
|
|
515
573
|
if (setup.go?.length) {
|
|
516
574
|
const missingGo: string[] = [];
|
|
517
575
|
for (const pkg of setup.go) {
|
|
518
|
-
|
|
519
|
-
if (!(await isBinaryAvailable(bin))) {
|
|
576
|
+
if (await goDepNeedsInstall(pkg)) {
|
|
520
577
|
missingGo.push(pkg);
|
|
521
578
|
}
|
|
522
579
|
}
|
package/src/ui/App.js
CHANGED
|
@@ -53,7 +53,7 @@ function Router() {
|
|
|
53
53
|
}
|
|
54
54
|
/**
|
|
55
55
|
* GlobalKeyHandler Component
|
|
56
|
-
* Handles global keyboard shortcuts (1-
|
|
56
|
+
* Handles global keyboard shortcuts (1-8, Tab, Escape, q, Ctrl+C, ?, Shift+D)
|
|
57
57
|
* Does not render anything (returns null)
|
|
58
58
|
*/
|
|
59
59
|
function GlobalKeyHandler({ onDebugToggle, onExit, }) {
|
|
@@ -139,18 +139,26 @@ function GlobalKeyHandler({ onDebugToggle, onExit, }) {
|
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
|
-
//
|
|
143
|
-
if (key.
|
|
142
|
+
// q or Ctrl+C quits from anywhere — no need to bounce to home first.
|
|
143
|
+
if (input === "q" || (key.ctrl && input === "c")) {
|
|
144
|
+
onExit();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
// Escape goes back: registry → mcp, any other tab → plugins home,
|
|
148
|
+
// already-home → exit. But when a screen has a focused sub-view open
|
|
149
|
+
// (e.g. the Alias flag-detail editor), that sub-view owns Escape and
|
|
150
|
+
// backs out to itself — don't let the global handler jump to home.
|
|
151
|
+
// Scoped to the Escape branch ONLY so q/Ctrl+C still quit from anywhere.
|
|
152
|
+
if (key.escape) {
|
|
153
|
+
if (state.detailActive)
|
|
154
|
+
return;
|
|
144
155
|
if (state.currentRoute.screen === "plugins") {
|
|
145
|
-
// On home screen, exit immediately
|
|
146
156
|
onExit();
|
|
147
157
|
}
|
|
148
158
|
else if (state.currentRoute.screen === "mcp-registry") {
|
|
149
|
-
// Go back to MCP from registry
|
|
150
159
|
navigateToScreen("mcp");
|
|
151
160
|
}
|
|
152
161
|
else {
|
|
153
|
-
// Go back to plugins (home)
|
|
154
162
|
navigateToScreen("plugins");
|
|
155
163
|
}
|
|
156
164
|
}
|
|
@@ -159,7 +167,8 @@ function GlobalKeyHandler({ onDebugToggle, onExit, }) {
|
|
|
159
167
|
modal.message("claudeup Help", `Navigation
|
|
160
168
|
↑/↓ or j/k Move selection
|
|
161
169
|
Enter Select / Toggle
|
|
162
|
-
Escape
|
|
170
|
+
Escape Back (to home)
|
|
171
|
+
q / Ctrl+C Quit (from anywhere)
|
|
163
172
|
? This help
|
|
164
173
|
|
|
165
174
|
Quick Navigation
|
package/src/ui/App.tsx
CHANGED
|
@@ -89,7 +89,7 @@ function Router() {
|
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
91
|
* GlobalKeyHandler Component
|
|
92
|
-
* Handles global keyboard shortcuts (1-
|
|
92
|
+
* Handles global keyboard shortcuts (1-8, Tab, Escape, q, Ctrl+C, ?, Shift+D)
|
|
93
93
|
* Does not render anything (returns null)
|
|
94
94
|
*/
|
|
95
95
|
function GlobalKeyHandler({
|
|
@@ -180,16 +180,24 @@ function GlobalKeyHandler({
|
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
//
|
|
184
|
-
if (key.
|
|
183
|
+
// q or Ctrl+C quits from anywhere — no need to bounce to home first.
|
|
184
|
+
if (input === "q" || (key.ctrl && input === "c")) {
|
|
185
|
+
onExit();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Escape goes back: registry → mcp, any other tab → plugins home,
|
|
190
|
+
// already-home → exit. But when a screen has a focused sub-view open
|
|
191
|
+
// (e.g. the Alias flag-detail editor), that sub-view owns Escape and
|
|
192
|
+
// backs out to itself — don't let the global handler jump to home.
|
|
193
|
+
// Scoped to the Escape branch ONLY so q/Ctrl+C still quit from anywhere.
|
|
194
|
+
if (key.escape) {
|
|
195
|
+
if (state.detailActive) return;
|
|
185
196
|
if (state.currentRoute.screen === "plugins") {
|
|
186
|
-
// On home screen, exit immediately
|
|
187
197
|
onExit();
|
|
188
198
|
} else if (state.currentRoute.screen === "mcp-registry") {
|
|
189
|
-
// Go back to MCP from registry
|
|
190
199
|
navigateToScreen("mcp");
|
|
191
200
|
} else {
|
|
192
|
-
// Go back to plugins (home)
|
|
193
201
|
navigateToScreen("plugins");
|
|
194
202
|
}
|
|
195
203
|
}
|
|
@@ -201,7 +209,8 @@ function GlobalKeyHandler({
|
|
|
201
209
|
`Navigation
|
|
202
210
|
↑/↓ or j/k Move selection
|
|
203
211
|
Enter Select / Toggle
|
|
204
|
-
Escape
|
|
212
|
+
Escape Back (to home)
|
|
213
|
+
q / Ctrl+C Quit (from anywhere)
|
|
205
214
|
? This help
|
|
206
215
|
|
|
207
216
|
Quick Navigation
|
|
Binary file
|
|
Binary file
|
|
@@ -9,6 +9,6 @@ export function ConfirmModal({ title, message, onConfirm, onCancel, }) {
|
|
|
9
9
|
onCancel();
|
|
10
10
|
}
|
|
11
11
|
});
|
|
12
|
-
return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: "#525252", backgroundColor: "#1C1C1E", paddingLeft: 3, paddingRight: 3, paddingTop: 1, paddingBottom: 1, width: 60, children: [_jsx("box", { marginBottom: 1, children: _jsx("text", { fg: "#EDEDED", children: _jsx("strong", { children: title }) }) }), _jsx("box", { marginBottom: 1, children: _jsx("text", { fg: "#A1A1AA", children: message }) }), _jsxs("box", { marginTop: 1, children: [_jsx("text", { fg: "#71717A", children: "Press " }), _jsx("text", { fg: "#EDEDED", children: "Y" }), _jsx("text", { fg: "#71717A", children: " to confirm \u2022 " }), _jsx("text", { fg: "#EDEDED", children: "N" }), _jsx("text", { fg: "#71717A", children: " to cancel" })] })] }));
|
|
12
|
+
return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: "#525252", backgroundColor: "#1C1C1E", paddingLeft: 3, paddingRight: 3, paddingTop: 1, paddingBottom: 1, width: 60, children: [_jsx("box", { marginBottom: 1, children: _jsx("text", { fg: "#EDEDED", children: _jsx("strong", { children: title }) }) }), _jsx("box", { marginBottom: 1, children: _jsx("text", { fg: "#A1A1AA", children: message }) }), _jsxs("box", { marginTop: 1, flexDirection: "row", children: [_jsx("text", { fg: "#71717A", children: "Press " }), _jsx("text", { fg: "#EDEDED", children: "Y" }), _jsx("text", { fg: "#71717A", children: " to confirm \u2022 " }), _jsx("text", { fg: "#EDEDED", children: "N" }), _jsx("text", { fg: "#71717A", children: " to cancel" })] })] }));
|
|
13
13
|
}
|
|
14
14
|
export default ConfirmModal;
|
|
@@ -47,7 +47,7 @@ export function ConfirmModal({
|
|
|
47
47
|
<box marginBottom={1}>
|
|
48
48
|
<text fg="#A1A1AA">{message}</text>
|
|
49
49
|
</box>
|
|
50
|
-
<box marginTop={1}>
|
|
50
|
+
<box marginTop={1} flexDirection="row">
|
|
51
51
|
<text fg="#71717A">Press </text>
|
|
52
52
|
<text fg="#EDEDED">Y</text>
|
|
53
53
|
<text fg="#71717A"> to confirm • </text>
|