claudeup 4.34.0 → 4.35.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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudeup",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.35.0",
|
|
4
4
|
"description": "TUI tool for managing Claude Code plugins, MCPs, and configuration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main.tsx",
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
"typescript": "^5.6.3"
|
|
65
65
|
},
|
|
66
66
|
"optionalDependencies": {
|
|
67
|
-
"claudeup-darwin-arm64": "4.
|
|
68
|
-
"claudeup-darwin-x64": "4.
|
|
69
|
-
"claudeup-linux-x64": "4.
|
|
67
|
+
"claudeup-darwin-arm64": "4.35.0",
|
|
68
|
+
"claudeup-darwin-x64": "4.35.0",
|
|
69
|
+
"claudeup-linux-x64": "4.35.0"
|
|
70
70
|
}
|
|
71
71
|
}
|
|
@@ -216,3 +216,74 @@ describe("resolveAllProfiles — union", () => {
|
|
|
216
216
|
expect(union.conflicts?.some((c) => c.includes("claudish"))).toBe(true);
|
|
217
217
|
});
|
|
218
218
|
});
|
|
219
|
+
|
|
220
|
+
describe("marketplaces derived from plugin ids", () => {
|
|
221
|
+
const noBins = { binResolver: fakeBinResolver({}) };
|
|
222
|
+
|
|
223
|
+
test("a plugin id registers the marketplace it names", async () => {
|
|
224
|
+
const manifest = manifestOf({
|
|
225
|
+
solo: { name: "Solo", plugins: { "dev@magus": "latest" } },
|
|
226
|
+
});
|
|
227
|
+
const c = await resolveProfile(manifest, "solo", noBins);
|
|
228
|
+
expect(c.marketplaces.magus).toEqual({
|
|
229
|
+
source: "github",
|
|
230
|
+
repo: "MadAppGang/magus",
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("extends alone is enough — the failure this fixes", async () => {
|
|
235
|
+
// A profile that only extends a predefined one inherits plugins like
|
|
236
|
+
// dev@magus and previously NO marketplaces, so install resolved every
|
|
237
|
+
// plugin against a marketplace that was never registered.
|
|
238
|
+
const manifest = manifestOf({
|
|
239
|
+
team: { name: "Team", extends: "developer-essentials" },
|
|
240
|
+
});
|
|
241
|
+
const c = await resolveProfile(manifest, "team", noBins);
|
|
242
|
+
expect(Object.keys(c.plugins).length).toBeGreaterThan(0);
|
|
243
|
+
expect(c.marketplaces.magus?.repo).toBe("MadAppGang/magus");
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("each distinct channel is derived, not just the first", async () => {
|
|
247
|
+
const manifest = manifestOf({
|
|
248
|
+
mixed: {
|
|
249
|
+
name: "Mixed",
|
|
250
|
+
plugins: {
|
|
251
|
+
"dev@magus": "latest",
|
|
252
|
+
"seo@magus-marketing": "latest",
|
|
253
|
+
"autolinear@magus-alpha": "latest",
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
const c = await resolveProfile(manifest, "mixed", noBins);
|
|
258
|
+
expect(c.marketplaces["magus-marketing"]?.repo).toBe("MadAppGang/magus-marketing");
|
|
259
|
+
expect(c.marketplaces["magus-alpha"]?.repo).toBe("MadAppGang/magus-alpha");
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("an explicit declaration is never overridden", async () => {
|
|
263
|
+
const manifest = manifestOf({
|
|
264
|
+
pinned: {
|
|
265
|
+
name: "Pinned",
|
|
266
|
+
marketplaces: { magus: { source: "github", repo: "MadAppGang/magus-fork" } },
|
|
267
|
+
plugins: { "dev@magus": "latest" },
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
const c = await resolveProfile(manifest, "pinned", noBins);
|
|
271
|
+
expect(c.marketplaces.magus?.repo).toBe("MadAppGang/magus-fork");
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test("an unknown marketplace is left for the manifest to declare", async () => {
|
|
275
|
+
const manifest = manifestOf({
|
|
276
|
+
third: { name: "Third", plugins: { "thing@someones-marketplace": "latest" } },
|
|
277
|
+
});
|
|
278
|
+
const c = await resolveProfile(manifest, "third", noBins);
|
|
279
|
+
expect(c.marketplaces["someones-marketplace"]).toBeUndefined();
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test("a bare plugin id contributes no marketplace", async () => {
|
|
283
|
+
const manifest = manifestOf({
|
|
284
|
+
bare: { name: "Bare", plugins: { local: "latest" } },
|
|
285
|
+
});
|
|
286
|
+
const c = await resolveProfile(manifest, "bare", noBins);
|
|
287
|
+
expect(Object.keys(c.marketplaces)).toHaveLength(0);
|
|
288
|
+
});
|
|
289
|
+
});
|
package/src/data/marketplaces.ts
CHANGED
|
@@ -53,6 +53,19 @@ export const defaultMarketplaces: Marketplace[] = [
|
|
|
53
53
|
owned: true, // Ours: always listed even when uninstalled, sorted to the top
|
|
54
54
|
// Not featured: a niche channel, so keep it collapsed by default.
|
|
55
55
|
},
|
|
56
|
+
{
|
|
57
|
+
name: "magus-alpha",
|
|
58
|
+
displayName: "Magus Alpha",
|
|
59
|
+
source: {
|
|
60
|
+
source: "github",
|
|
61
|
+
repo: "MadAppGang/magus-alpha",
|
|
62
|
+
},
|
|
63
|
+
description:
|
|
64
|
+
"Experimental plugins with evolving interfaces — may change or be withdrawn without notice",
|
|
65
|
+
official: false,
|
|
66
|
+
owned: true, // Ours: always listed even when uninstalled, sorted to the top
|
|
67
|
+
// Not featured: experimental, so keep it collapsed by default.
|
|
68
|
+
},
|
|
56
69
|
{
|
|
57
70
|
name: "claude-plugins-official",
|
|
58
71
|
displayName: "Anthropic Official",
|
package/src/services/resolver.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
ResolvedClosure,
|
|
18
18
|
} from "../types/index.js";
|
|
19
19
|
import { cliTools } from "../data/cli-tools.js";
|
|
20
|
+
import { getMarketplaceByName } from "../data/marketplaces.js";
|
|
20
21
|
import { PREDEFINED_PROFILES } from "../data/predefined-profiles.js";
|
|
21
22
|
import { resolvePluginBinRequirements } from "./plugin-requires.js";
|
|
22
23
|
|
|
@@ -147,6 +148,36 @@ async function resolveBins(
|
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
/** Resolve a single profile (applying `extends`) into an install closure. */
|
|
151
|
+
/**
|
|
152
|
+
* Add the marketplace every plugin id already names.
|
|
153
|
+
*
|
|
154
|
+
* A plugin id carries its marketplace — `dev@magus`, `seo@magus-marketing` —
|
|
155
|
+
* so requiring the manifest to ALSO declare that marketplace is redundant, and
|
|
156
|
+
* forgetting to is silent: the plugin resolves against a marketplace that was
|
|
157
|
+
* never registered. `extends` made this the default failure, because it
|
|
158
|
+
* inherits plugins from a predefined profile and no marketplaces at all.
|
|
159
|
+
*
|
|
160
|
+
* Only marketplaces claudeup already knows are derived, since a repo is needed
|
|
161
|
+
* to register one. An unknown suffix is left alone for the manifest to declare.
|
|
162
|
+
* An explicit entry always wins: deriving must never override a pin.
|
|
163
|
+
*/
|
|
164
|
+
function withDerivedMarketplaces(
|
|
165
|
+
declared: ProfileManifestEntry["marketplaces"],
|
|
166
|
+
plugins: Record<string, string>,
|
|
167
|
+
): NonNullable<ProfileManifestEntry["marketplaces"]> {
|
|
168
|
+
const out = { ...(declared ?? {}) };
|
|
169
|
+
for (const id of Object.keys(plugins)) {
|
|
170
|
+
const at = id.lastIndexOf("@");
|
|
171
|
+
if (at <= 0) continue;
|
|
172
|
+
const name = id.slice(at + 1);
|
|
173
|
+
if (out[name]) continue;
|
|
174
|
+
const known = getMarketplaceByName(name);
|
|
175
|
+
if (!known?.source.repo) continue;
|
|
176
|
+
out[name] = { source: "github", repo: known.source.repo };
|
|
177
|
+
}
|
|
178
|
+
return out;
|
|
179
|
+
}
|
|
180
|
+
|
|
150
181
|
export async function resolveProfile(
|
|
151
182
|
manifest: ProfileManifest,
|
|
152
183
|
profileId: string,
|
|
@@ -161,7 +192,7 @@ export async function resolveProfile(
|
|
|
161
192
|
const bins = await resolveBins(entry, Object.keys(plugins), binResolver);
|
|
162
193
|
|
|
163
194
|
return {
|
|
164
|
-
marketplaces:
|
|
195
|
+
marketplaces: withDerivedMarketplaces(entry.marketplaces, plugins),
|
|
165
196
|
plugins,
|
|
166
197
|
mcpServers: { ...(entry.mcpServers ?? {}) },
|
|
167
198
|
bins,
|
|
@@ -6,7 +6,7 @@ import { ScreenLayout } from "../components/layout/index.js";
|
|
|
6
6
|
import { ScrollableList } from "../components/ScrollableList.js";
|
|
7
7
|
import { EmptyFilterState } from "../components/EmptyFilterState.js";
|
|
8
8
|
import { fuzzyFilter } from "../../utils/fuzzy-search.js";
|
|
9
|
-
import { getAllMarketplaces } from "../../data/marketplaces.js";
|
|
9
|
+
import { defaultMarketplaces, getAllMarketplaces } from "../../data/marketplaces.js";
|
|
10
10
|
import { clearContentDriftCache } from "../../services/content-drift.js";
|
|
11
11
|
import {
|
|
12
12
|
diffVersions,
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
} from "../../services/claude-settings.js";
|
|
34
34
|
import { saveProfile } from "../../services/profiles.js";
|
|
35
35
|
import {
|
|
36
|
+
addMarketplace as cliAddMarketplace,
|
|
36
37
|
installPlugin as cliInstallPlugin,
|
|
37
38
|
repairPlugin as cliRepairPlugin,
|
|
38
39
|
uninstallPlugin as cliUninstallPlugin,
|
|
@@ -387,16 +388,60 @@ export function PluginsScreen() {
|
|
|
387
388
|
}
|
|
388
389
|
};
|
|
389
390
|
|
|
391
|
+
/**
|
|
392
|
+
* Add a marketplace.
|
|
393
|
+
*
|
|
394
|
+
* The known ones are offered as a menu and added here — that covers every
|
|
395
|
+
* marketplace claudeup ships with, including magus, so a first run does not
|
|
396
|
+
* have to leave the tool. An arbitrary owner/repo still needs the terminal:
|
|
397
|
+
* there is no text-input modal, only confirm/message/select.
|
|
398
|
+
*/
|
|
390
399
|
const handleShowAddMarketplaceInstructions = async () => {
|
|
400
|
+
const installed = new Set(
|
|
401
|
+
pluginsState.marketplaces.status === "success"
|
|
402
|
+
? pluginsState.marketplaces.data.map((m) => m.name)
|
|
403
|
+
: [],
|
|
404
|
+
);
|
|
405
|
+
const addable = defaultMarketplaces.filter(
|
|
406
|
+
(m) => !installed.has(m.name) && !m.deprecated && m.source.repo,
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
if (addable.length > 0) {
|
|
410
|
+
const choice = await modal.select(
|
|
411
|
+
"Add Marketplace",
|
|
412
|
+
"Pick one to add now, or choose Other for a repo not listed.",
|
|
413
|
+
[
|
|
414
|
+
...addable.map((m) => ({
|
|
415
|
+
label: m.displayName || m.name,
|
|
416
|
+
value: m.source.repo as string,
|
|
417
|
+
description: m.description,
|
|
418
|
+
})),
|
|
419
|
+
{ label: "Other…", value: "", description: "Any owner/repo" },
|
|
420
|
+
],
|
|
421
|
+
);
|
|
422
|
+
if (choice === null) return;
|
|
423
|
+
if (choice) {
|
|
424
|
+
try {
|
|
425
|
+
await cliAddMarketplace(choice);
|
|
426
|
+
await handleRefresh();
|
|
427
|
+
} catch (e) {
|
|
428
|
+
await modal.message(
|
|
429
|
+
"Could not add marketplace",
|
|
430
|
+
`${(e as Error).message}\n\nRun it yourself if this persists:\n\n` +
|
|
431
|
+
` claude plugin marketplace add ${choice}`,
|
|
432
|
+
"error",
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
391
439
|
await modal.message(
|
|
392
440
|
"Add Marketplace",
|
|
393
|
-
"
|
|
441
|
+
"For a marketplace that is not listed, run this in your terminal:\n\n" +
|
|
394
442
|
" claude plugin marketplace add owner/repo\n\n" +
|
|
395
|
-
"Examples:\n" +
|
|
396
|
-
" claude plugin marketplace add MadAppGang/magus\n" +
|
|
397
|
-
" claude plugin marketplace add anthropics/claude-plugins-official\n\n" +
|
|
398
443
|
"Auto-update is enabled by default for new marketplaces.\n\n" +
|
|
399
|
-
"After adding, refresh claudeup with 'r' to see
|
|
444
|
+
"After adding, refresh claudeup with 'r' to see it.",
|
|
400
445
|
"info",
|
|
401
446
|
);
|
|
402
447
|
};
|
|
@@ -593,14 +638,30 @@ export function PluginsScreen() {
|
|
|
593
638
|
);
|
|
594
639
|
}
|
|
595
640
|
} else {
|
|
596
|
-
|
|
641
|
+
// Add it here rather than printing the command for the user to run.
|
|
642
|
+
// Telling someone to leave the tool, paste a command, come back and
|
|
643
|
+
// press 'r' is a step claudeup can just take: it already knows the
|
|
644
|
+
// repo, and addMarketplace() is the same call they would make.
|
|
645
|
+
const repo = mp.source.repo || mp.name;
|
|
646
|
+
const wantAdd = await modal.confirm(
|
|
597
647
|
`Add ${mp.displayName}?`,
|
|
598
|
-
`
|
|
599
|
-
`
|
|
600
|
-
`Auto-update is enabled by default.\n\n` +
|
|
601
|
-
`After adding, refresh claudeup with 'r' to see it.`,
|
|
602
|
-
"info",
|
|
648
|
+
`Clone ${repo} and register it as a marketplace?\n\n` +
|
|
649
|
+
`Auto-update is enabled by default.`,
|
|
603
650
|
);
|
|
651
|
+
if (wantAdd) {
|
|
652
|
+
try {
|
|
653
|
+
await cliAddMarketplace(repo);
|
|
654
|
+
await handleRefresh();
|
|
655
|
+
} catch (e) {
|
|
656
|
+
await modal.message(
|
|
657
|
+
"Could not add marketplace",
|
|
658
|
+
`${(e as Error).message}\n\n` +
|
|
659
|
+
`Run it yourself if this persists:\n\n` +
|
|
660
|
+
` claude plugin marketplace add ${repo}`,
|
|
661
|
+
"error",
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
604
665
|
}
|
|
605
666
|
} else if (item.kind === "plugin") {
|
|
606
667
|
const plugin = item.plugin;
|