claudeup 6.4.0 → 6.5.1
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 +4 -4
- package/src/__tests__/cli-update-view.test.ts +2 -2
- package/src/__tests__/gap-fill-versions.test.ts +24 -24
- package/src/__tests__/hook-process.test.ts +2 -2
- package/src/__tests__/model-visuals.test.tsx +8 -4
- package/src/__tests__/models-core.test.ts +5 -5
- package/src/__tests__/moved-marketplace.test.ts +7 -8
- package/src/__tests__/plugin-contents.test.ts +1 -1
- package/src/__tests__/profile-adopt.test.ts +1 -1
- package/src/__tests__/profile-materializer.test.ts +2 -2
- package/src/__tests__/resolver.test.ts +7 -6
- package/src/__tests__/version-snapshot.test.ts +4 -4
- package/src/data/gitignore-defaults.ts +0 -1
- package/src/data/gitignore-reasons.ts +0 -4
- package/src/data/marketplaces.ts +1 -2
- package/src/data/models-presets.ts +1 -12
- package/src/data/predefined-profiles.ts +3 -21
- package/src/services/models-core.ts +1 -1
- package/src/services/plugin-manager.ts +2 -3
- package/src/services/resolver.ts +2 -2
- package/src/ui/screens/PluginsScreen.tsx +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudeup",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.5.1",
|
|
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": "6.
|
|
68
|
-
"claudeup-darwin-x64": "6.
|
|
69
|
-
"claudeup-linux-x64": "6.
|
|
67
|
+
"claudeup-darwin-arm64": "6.5.1",
|
|
68
|
+
"claudeup-darwin-x64": "6.5.1",
|
|
69
|
+
"claudeup-linux-x64": "6.5.1"
|
|
70
70
|
}
|
|
71
71
|
}
|
|
@@ -62,7 +62,7 @@ function plan(over: Partial<UpdatePlan> = {}): UpdatePlan {
|
|
|
62
62
|
scopes: [],
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
|
-
pluginId: "
|
|
65
|
+
pluginId: "designer@magus",
|
|
66
66
|
pinned: "latest",
|
|
67
67
|
installed: "1.8.0",
|
|
68
68
|
available: null,
|
|
@@ -182,7 +182,7 @@ describe("NO_COLOR", () => {
|
|
|
182
182
|
|
|
183
183
|
test("a note rides along on the row it explains", () => {
|
|
184
184
|
setColorEnabled(false);
|
|
185
|
-
const row = planReport(plan()).find((l) => l.includes("
|
|
185
|
+
const row = planReport(plan()).find((l) => l.includes("designer@magus"))!;
|
|
186
186
|
expect(row).toContain("catalog unreachable");
|
|
187
187
|
});
|
|
188
188
|
});
|
|
@@ -117,20 +117,20 @@ describe("gapFillInstalledPluginVersions", () => {
|
|
|
117
117
|
await fs.remove(initTmpHome).catch(() => {});
|
|
118
118
|
});
|
|
119
119
|
|
|
120
|
-
// Scenario 1: "The
|
|
121
|
-
it("bumps global when project has a newer version (the
|
|
120
|
+
// Scenario 1: "The stale-global bug" — project has newer version, global is stale
|
|
121
|
+
it("bumps global when project has a newer version (the stale-global bug)", async () => {
|
|
122
122
|
await writeGlobalSettings({
|
|
123
|
-
installedPluginVersions: { "
|
|
123
|
+
installedPluginVersions: { "designer@magus": "1.0.0" },
|
|
124
124
|
});
|
|
125
125
|
await writeProjectSettings({
|
|
126
|
-
installedPluginVersions: { "
|
|
126
|
+
installedPluginVersions: { "designer@magus": "1.3.0" },
|
|
127
127
|
});
|
|
128
128
|
|
|
129
129
|
const result = await gapFillInstalledPluginVersions(tmpProject);
|
|
130
130
|
|
|
131
131
|
expect(result).toEqual([
|
|
132
132
|
{
|
|
133
|
-
pluginId: "
|
|
133
|
+
pluginId: "designer@magus",
|
|
134
134
|
oldVersion: "1.0.0",
|
|
135
135
|
newVersion: "1.3.0",
|
|
136
136
|
},
|
|
@@ -138,7 +138,7 @@ describe("gapFillInstalledPluginVersions", () => {
|
|
|
138
138
|
|
|
139
139
|
// Verify the file was actually updated
|
|
140
140
|
const global = await readGlobalSettingsFromDisk();
|
|
141
|
-
expect((global as any).installedPluginVersions["
|
|
141
|
+
expect((global as any).installedPluginVersions["designer@magus"]).toBe(
|
|
142
142
|
"1.3.0",
|
|
143
143
|
);
|
|
144
144
|
});
|
|
@@ -146,10 +146,10 @@ describe("gapFillInstalledPluginVersions", () => {
|
|
|
146
146
|
// Scenario 2: Global is already current — no changes needed
|
|
147
147
|
it("returns empty array when global is already at the highest version", async () => {
|
|
148
148
|
await writeGlobalSettings({
|
|
149
|
-
installedPluginVersions: { "
|
|
149
|
+
installedPluginVersions: { "designer@magus": "1.3.0" },
|
|
150
150
|
});
|
|
151
151
|
await writeProjectSettings({
|
|
152
|
-
installedPluginVersions: { "
|
|
152
|
+
installedPluginVersions: { "designer@magus": "1.3.0" },
|
|
153
153
|
});
|
|
154
154
|
|
|
155
155
|
const result = await gapFillInstalledPluginVersions(tmpProject);
|
|
@@ -158,7 +158,7 @@ describe("gapFillInstalledPluginVersions", () => {
|
|
|
158
158
|
|
|
159
159
|
// Verify global was NOT rewritten (check original content is intact)
|
|
160
160
|
const global = await readGlobalSettingsFromDisk();
|
|
161
|
-
expect((global as any).installedPluginVersions["
|
|
161
|
+
expect((global as any).installedPluginVersions["designer@magus"]).toBe(
|
|
162
162
|
"1.3.0",
|
|
163
163
|
);
|
|
164
164
|
});
|
|
@@ -298,10 +298,10 @@ describe("gapFillInstalledPluginVersions", () => {
|
|
|
298
298
|
// Scenario 9: Global version is higher than project — no downgrade
|
|
299
299
|
it("does not downgrade global when global is higher than project", async () => {
|
|
300
300
|
await writeGlobalSettings({
|
|
301
|
-
installedPluginVersions: { "
|
|
301
|
+
installedPluginVersions: { "designer@magus": "2.0.0" },
|
|
302
302
|
});
|
|
303
303
|
await writeProjectSettings({
|
|
304
|
-
installedPluginVersions: { "
|
|
304
|
+
installedPluginVersions: { "designer@magus": "1.0.0" },
|
|
305
305
|
});
|
|
306
306
|
|
|
307
307
|
const result = await gapFillInstalledPluginVersions(tmpProject);
|
|
@@ -310,7 +310,7 @@ describe("gapFillInstalledPluginVersions", () => {
|
|
|
310
310
|
|
|
311
311
|
// Verify global was NOT downgraded
|
|
312
312
|
const global = await readGlobalSettingsFromDisk();
|
|
313
|
-
expect((global as any).installedPluginVersions["
|
|
313
|
+
expect((global as any).installedPluginVersions["designer@magus"]).toBe(
|
|
314
314
|
"2.0.0",
|
|
315
315
|
);
|
|
316
316
|
});
|
|
@@ -318,12 +318,12 @@ describe("gapFillInstalledPluginVersions", () => {
|
|
|
318
318
|
// Scenario 10: Preserves unrelated settings fields during write
|
|
319
319
|
it("preserves other global settings fields when bumping versions", async () => {
|
|
320
320
|
await writeGlobalSettings({
|
|
321
|
-
enabledPlugins: { "
|
|
321
|
+
enabledPlugins: { "designer@magus": true, "dev@magus": true },
|
|
322
322
|
env: { SOME_VAR: "value" },
|
|
323
|
-
installedPluginVersions: { "
|
|
323
|
+
installedPluginVersions: { "designer@magus": "1.0.0" },
|
|
324
324
|
});
|
|
325
325
|
await writeProjectSettings({
|
|
326
|
-
installedPluginVersions: { "
|
|
326
|
+
installedPluginVersions: { "designer@magus": "2.0.0" },
|
|
327
327
|
});
|
|
328
328
|
|
|
329
329
|
const result = await gapFillInstalledPluginVersions(tmpProject);
|
|
@@ -333,11 +333,11 @@ describe("gapFillInstalledPluginVersions", () => {
|
|
|
333
333
|
// Verify other fields are preserved
|
|
334
334
|
const global = await readGlobalSettingsFromDisk();
|
|
335
335
|
expect((global as any).enabledPlugins).toEqual({
|
|
336
|
-
"
|
|
336
|
+
"designer@magus": true,
|
|
337
337
|
"dev@magus": true,
|
|
338
338
|
});
|
|
339
339
|
expect((global as any).env).toEqual({ SOME_VAR: "value" });
|
|
340
|
-
expect((global as any).installedPluginVersions["
|
|
340
|
+
expect((global as any).installedPluginVersions["designer@magus"]).toBe(
|
|
341
341
|
"2.0.0",
|
|
342
342
|
);
|
|
343
343
|
});
|
|
@@ -345,24 +345,24 @@ describe("gapFillInstalledPluginVersions", () => {
|
|
|
345
345
|
// Scenario 11: Invalid semver in GLOBAL scope gets overwritten
|
|
346
346
|
it("overwrites corrupted global version when project has valid semver", async () => {
|
|
347
347
|
await writeGlobalSettings({
|
|
348
|
-
installedPluginVersions: { "
|
|
348
|
+
installedPluginVersions: { "designer@magus": "not-a-version" },
|
|
349
349
|
});
|
|
350
350
|
await writeProjectSettings({
|
|
351
|
-
installedPluginVersions: { "
|
|
351
|
+
installedPluginVersions: { "designer@magus": "1.3.0" },
|
|
352
352
|
});
|
|
353
353
|
|
|
354
354
|
const result = await gapFillInstalledPluginVersions(tmpProject);
|
|
355
355
|
|
|
356
356
|
expect(result).toEqual([
|
|
357
357
|
{
|
|
358
|
-
pluginId: "
|
|
358
|
+
pluginId: "designer@magus",
|
|
359
359
|
oldVersion: "not-a-version",
|
|
360
360
|
newVersion: "1.3.0",
|
|
361
361
|
},
|
|
362
362
|
]);
|
|
363
363
|
|
|
364
364
|
const global = await readGlobalSettingsFromDisk();
|
|
365
|
-
expect((global as any).installedPluginVersions["
|
|
365
|
+
expect((global as any).installedPluginVersions["designer@magus"]).toBe(
|
|
366
366
|
"1.3.0",
|
|
367
367
|
);
|
|
368
368
|
});
|
|
@@ -370,10 +370,10 @@ describe("gapFillInstalledPluginVersions", () => {
|
|
|
370
370
|
// Scenario 12: Both global and project have invalid semver — no crash, no change
|
|
371
371
|
it("does nothing when all versions are invalid semver", async () => {
|
|
372
372
|
await writeGlobalSettings({
|
|
373
|
-
installedPluginVersions: { "
|
|
373
|
+
installedPluginVersions: { "designer@magus": "broken" },
|
|
374
374
|
});
|
|
375
375
|
await writeProjectSettings({
|
|
376
|
-
installedPluginVersions: { "
|
|
376
|
+
installedPluginVersions: { "designer@magus": "also-broken" },
|
|
377
377
|
});
|
|
378
378
|
|
|
379
379
|
const result = await gapFillInstalledPluginVersions(tmpProject);
|
|
@@ -382,7 +382,7 @@ describe("gapFillInstalledPluginVersions", () => {
|
|
|
382
382
|
|
|
383
383
|
// Global unchanged
|
|
384
384
|
const global = await readGlobalSettingsFromDisk();
|
|
385
|
-
expect((global as any).installedPluginVersions["
|
|
385
|
+
expect((global as any).installedPluginVersions["designer@magus"]).toBe(
|
|
386
386
|
"broken",
|
|
387
387
|
);
|
|
388
388
|
});
|
|
@@ -72,7 +72,7 @@ const CONFIG = {
|
|
|
72
72
|
agents: {
|
|
73
73
|
"dev:architect": "smart",
|
|
74
74
|
Explore: "cheap",
|
|
75
|
-
"
|
|
75
|
+
"dev:researcher": { model: "inherit" },
|
|
76
76
|
},
|
|
77
77
|
fallback: "normal",
|
|
78
78
|
};
|
|
@@ -158,7 +158,7 @@ describe("claudeup hook agent-model", () => {
|
|
|
158
158
|
|
|
159
159
|
test("`inherit` leaves the call alone", async () => {
|
|
160
160
|
writeConfig(dir);
|
|
161
|
-
const r = await runHook(agentPayload({ subagent_type: "
|
|
161
|
+
const r = await runHook(agentPayload({ subagent_type: "dev:researcher" }));
|
|
162
162
|
expect(r.stdout).toBe("");
|
|
163
163
|
});
|
|
164
164
|
|
|
@@ -317,7 +317,11 @@ describe("the agent table", () => {
|
|
|
317
317
|
});
|
|
318
318
|
|
|
319
319
|
test("sorted by grade in ROUTING order, then alphabetically inside it", () => {
|
|
320
|
-
|
|
320
|
+
// No built-in preset pins an agent to `inherit`, so add one to prove it trails.
|
|
321
|
+
const rows = agentRows({
|
|
322
|
+
...fableAdvisor,
|
|
323
|
+
agents: { ...fableAdvisor.agents, "x:pinned": { model: "inherit" } },
|
|
324
|
+
});
|
|
321
325
|
const rank = (key: string) => {
|
|
322
326
|
const index = (GRADES as readonly string[]).indexOf(key);
|
|
323
327
|
return index === -1 ? GRADES.length : index;
|
|
@@ -361,7 +365,7 @@ describe("the agent table", () => {
|
|
|
361
365
|
test("an inherit row sets nothing, so it reads as an em dash", () => {
|
|
362
366
|
const config: ModelsConfig = {
|
|
363
367
|
...fableAdvisor,
|
|
364
|
-
agents: { "
|
|
368
|
+
agents: { "dev:researcher": { model: "inherit" } },
|
|
365
369
|
};
|
|
366
370
|
const row = agentRows(config)[0];
|
|
367
371
|
expect(row?.inherit).toBe(true);
|
|
@@ -570,8 +574,8 @@ describe("the colour key under the bars", () => {
|
|
|
570
574
|
// NOT haiku: the presets no longer use it, and a key naming a colour that appears
|
|
571
575
|
// nowhere on screen is a legend for a thing that is not there.
|
|
572
576
|
expect(models).not.toContain("haiku");
|
|
573
|
-
//
|
|
574
|
-
expect(models).toContain("inherit");
|
|
577
|
+
// Nor inherit: no preset pins an agent to it, so there is no grey to explain.
|
|
578
|
+
expect(models).not.toContain("inherit");
|
|
575
579
|
expect(new Set(models).size).toBe(models.length);
|
|
576
580
|
});
|
|
577
581
|
|
|
@@ -39,7 +39,7 @@ const base = (): ModelsConfig => ({
|
|
|
39
39
|
agents: {
|
|
40
40
|
"dev:architect": "smart",
|
|
41
41
|
Explore: "cheap",
|
|
42
|
-
"
|
|
42
|
+
"dev:researcher": { model: "inherit" },
|
|
43
43
|
},
|
|
44
44
|
fallback: "normal",
|
|
45
45
|
});
|
|
@@ -113,9 +113,9 @@ describe("validateModelsConfig", () => {
|
|
|
113
113
|
|
|
114
114
|
test("rejects effort on `inherit`, which names no model to key it by", () => {
|
|
115
115
|
const c = base();
|
|
116
|
-
c.agents["
|
|
116
|
+
c.agents["dev:researcher"] = { model: "inherit", effort: "low" } as never;
|
|
117
117
|
expect(validateModelsConfig(c).map((e) => e.path)).toEqual([
|
|
118
|
-
"agents.
|
|
118
|
+
"agents.dev:researcher.effort",
|
|
119
119
|
]);
|
|
120
120
|
});
|
|
121
121
|
|
|
@@ -151,7 +151,7 @@ describe("resolveAgentRouting", () => {
|
|
|
151
151
|
});
|
|
152
152
|
|
|
153
153
|
test("a per-agent override beats the grade table", () => {
|
|
154
|
-
expect(resolveAgentRouting(base(), "
|
|
154
|
+
expect(resolveAgentRouting(base(), "dev:researcher")).toEqual({
|
|
155
155
|
model: "inherit",
|
|
156
156
|
source: "agent-override",
|
|
157
157
|
});
|
|
@@ -228,7 +228,7 @@ describe("evaluateAgentHook", () => {
|
|
|
228
228
|
|
|
229
229
|
test("`inherit` is the opt-out and touches nothing", () => {
|
|
230
230
|
expect(
|
|
231
|
-
evaluateAgentHook(agentCall({ subagent_type: "
|
|
231
|
+
evaluateAgentHook(agentCall({ subagent_type: "dev:researcher" }), base()),
|
|
232
232
|
).toEqual({
|
|
233
233
|
kind: "passthrough",
|
|
234
234
|
reason: "inherit",
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* An orphaned plugin that merely changed marketplace is not deprecated.
|
|
3
3
|
*
|
|
4
|
-
* Splitting `magus` into `magus` + `magus-marketing` left `
|
|
5
|
-
*
|
|
6
|
-
* under a namespace that no longer lists them. They rendered as bare
|
|
4
|
+
* Splitting `magus` into `magus` + `magus-marketing` left `video-editing@magus`
|
|
5
|
+
* and `nanobanana@magus` installed under a namespace that no longer lists them. They rendered as bare
|
|
7
6
|
* "deprecated", whose only action is deletion — which drops a plugin that is
|
|
8
7
|
* still published, just under a different id.
|
|
9
8
|
*/
|
|
@@ -45,19 +44,19 @@ describe("moved-marketplace detection", () => {
|
|
|
45
44
|
test("finds a plugin that moved to a sibling marketplace", () => {
|
|
46
45
|
const marketplaces = new Map([
|
|
47
46
|
mp("magus", ["dev", "terminal"]),
|
|
48
|
-
mp("magus-marketing", ["
|
|
47
|
+
mp("magus-marketing", ["image-generate", "video-editing"]),
|
|
49
48
|
]);
|
|
50
49
|
|
|
51
|
-
expect(
|
|
52
|
-
"
|
|
53
|
-
);
|
|
50
|
+
expect(
|
|
51
|
+
findPluginInOtherMarketplace("video-editing", "magus", marketplaces),
|
|
52
|
+
).toBe("magus-marketing");
|
|
54
53
|
});
|
|
55
54
|
|
|
56
55
|
test("a genuinely retired plugin reports no destination", () => {
|
|
57
56
|
// `conductor` was removed at magus v8.0.0 and republished nowhere.
|
|
58
57
|
const marketplaces = new Map([
|
|
59
58
|
mp("magus", ["dev"]),
|
|
60
|
-
mp("magus-marketing", ["
|
|
59
|
+
mp("magus-marketing", ["video-editing"]),
|
|
61
60
|
]);
|
|
62
61
|
|
|
63
62
|
expect(
|
|
@@ -90,7 +90,7 @@ describe("fetchPluginContents", () => {
|
|
|
90
90
|
stubTree([
|
|
91
91
|
"plugins/dev/skills/frontend/design-system/SKILL.md",
|
|
92
92
|
"plugins/dev/skills/backend/db-branching/SKILL.md",
|
|
93
|
-
"plugins/
|
|
93
|
+
"plugins/designer/skills/ui-analyse/SKILL.md",
|
|
94
94
|
]);
|
|
95
95
|
|
|
96
96
|
const skills = await fetchPluginContents("fake/multi", "./plugins/dev");
|
|
@@ -24,7 +24,7 @@ describe("buildAdoptedProfile", () => {
|
|
|
24
24
|
|
|
25
25
|
test("skips disabled plugins", () => {
|
|
26
26
|
const entry = buildAdoptedProfile({
|
|
27
|
-
enabledPlugins: { "dev@magus": true, "
|
|
27
|
+
enabledPlugins: { "dev@magus": true, "designer@magus": false },
|
|
28
28
|
settings: {},
|
|
29
29
|
mcpServers: {},
|
|
30
30
|
now: NOW,
|
|
@@ -61,12 +61,12 @@ describe("buildProfileSettings", () => {
|
|
|
61
61
|
test("explicitly disables union plugins the profile excludes", () => {
|
|
62
62
|
const s = buildProfileSettings(
|
|
63
63
|
closure({ plugins: { "designer@magus": "latest" } }),
|
|
64
|
-
["designer@magus", "terminal@magus", "
|
|
64
|
+
["designer@magus", "terminal@magus", "madbench@magus"],
|
|
65
65
|
);
|
|
66
66
|
expect(s.enabledPlugins).toEqual({
|
|
67
67
|
"designer@magus": true,
|
|
68
68
|
"terminal@magus": false,
|
|
69
|
-
"
|
|
69
|
+
"madbench@magus": false,
|
|
70
70
|
});
|
|
71
71
|
});
|
|
72
72
|
|
|
@@ -91,12 +91,11 @@ describe("resolveExtends", () => {
|
|
|
91
91
|
extends: "growth-marketer",
|
|
92
92
|
};
|
|
93
93
|
const merged = resolveExtends(entry);
|
|
94
|
-
//
|
|
94
|
+
// image-generate/video-editing ship on the magus-marketing channel and
|
|
95
95
|
// pin it explicitly — they must NOT be rewritten to @magus.
|
|
96
|
-
expect(merged.plugins!["seo@magus-marketing"]).toBe("latest");
|
|
97
96
|
expect(merged.plugins!["image-generate@magus-marketing"]).toBe("latest");
|
|
98
97
|
expect(merged.plugins!["video-editing@magus-marketing"]).toBe("latest");
|
|
99
|
-
expect(merged.plugins!["
|
|
98
|
+
expect(merged.plugins!["image-generate@magus"]).toBeUndefined();
|
|
100
99
|
// Bare names still default to @magus.
|
|
101
100
|
expect(merged.plugins!["browser-use@magus"]).toBe("latest");
|
|
102
101
|
});
|
|
@@ -303,8 +302,8 @@ describe("marketplaces derived from plugin ids", () => {
|
|
|
303
302
|
name: "Mixed",
|
|
304
303
|
plugins: {
|
|
305
304
|
"dev@magus": "latest",
|
|
306
|
-
"
|
|
307
|
-
"
|
|
305
|
+
"image-generate@magus-marketing": "latest",
|
|
306
|
+
"feature-dev@claude-plugins-official": "latest",
|
|
308
307
|
},
|
|
309
308
|
},
|
|
310
309
|
});
|
|
@@ -312,7 +311,9 @@ describe("marketplaces derived from plugin ids", () => {
|
|
|
312
311
|
expect(c.marketplaces["magus-marketing"]?.repo).toBe(
|
|
313
312
|
"MadAppGang/magus-marketing",
|
|
314
313
|
);
|
|
315
|
-
expect(c.marketplaces["
|
|
314
|
+
expect(c.marketplaces["claude-plugins-official"]?.repo).toBe(
|
|
315
|
+
"anthropics/claude-plugins-official",
|
|
316
|
+
);
|
|
316
317
|
});
|
|
317
318
|
|
|
318
319
|
test("an explicit declaration is never overridden", async () => {
|
|
@@ -159,13 +159,13 @@ describe("per-project baselines", () => {
|
|
|
159
159
|
|
|
160
160
|
test("saving one project preserves the others", async () => {
|
|
161
161
|
await withSandbox(async () => {
|
|
162
|
-
await saveSeenVersions("/proj/a", { "
|
|
163
|
-
await saveSeenVersions("/proj/b", { "
|
|
162
|
+
await saveSeenVersions("/proj/a", { "designer@magus": "2.0.0" });
|
|
163
|
+
await saveSeenVersions("/proj/b", { "designer@magus": "2.0.1" });
|
|
164
164
|
expect(await loadSeenVersions("/proj/a")).toEqual({
|
|
165
|
-
"
|
|
165
|
+
"designer@magus": "2.0.0",
|
|
166
166
|
});
|
|
167
167
|
expect(await loadSeenVersions("/proj/b")).toEqual({
|
|
168
|
-
"
|
|
168
|
+
"designer@magus": "2.0.1",
|
|
169
169
|
});
|
|
170
170
|
});
|
|
171
171
|
});
|
|
@@ -29,10 +29,6 @@ const RULE_REASONS = new Map<string, string>([
|
|
|
29
29
|
key("ignore", ".claudemem/"),
|
|
30
30
|
"Local claudemem indexes are generated from the repo and should not be committed.",
|
|
31
31
|
],
|
|
32
|
-
[
|
|
33
|
-
key("ignore", "gtd/sessions/"),
|
|
34
|
-
"Local GTD session artifacts are personal workflow state.",
|
|
35
|
-
],
|
|
36
32
|
[
|
|
37
33
|
key("ignore", ".agents/"),
|
|
38
34
|
"Local agent workspaces and runtime artifacts should stay out of team history.",
|
package/src/data/marketplaces.ts
CHANGED
|
@@ -47,8 +47,7 @@ export const defaultMarketplaces: Marketplace[] = [
|
|
|
47
47
|
source: "github",
|
|
48
48
|
repo: "MadAppGang/magus-marketing",
|
|
49
49
|
},
|
|
50
|
-
description:
|
|
51
|
-
"SEO, cold email outreach, AI image generation, and video editing plugins",
|
|
50
|
+
description: "AI image generation and video editing plugins",
|
|
52
51
|
official: false,
|
|
53
52
|
owned: true, // Ours: always listed even when uninstalled, sorted to the top
|
|
54
53
|
// Not featured: a niche channel, so keep it collapsed by default.
|
|
@@ -56,7 +56,6 @@ export const DEFAULT_AGENT_GRADES: Record<string, Grade> = {
|
|
|
56
56
|
"dev:stack-detector": "cheap",
|
|
57
57
|
"dev:docs": "cheap",
|
|
58
58
|
"terminal:tui-navigator": "cheap",
|
|
59
|
-
"gtd:gtd-reviewer": "cheap",
|
|
60
59
|
"statusline-setup": "cheap",
|
|
61
60
|
};
|
|
62
61
|
|
|
@@ -115,16 +114,6 @@ export const WORKFLOWS: { name: string; agents: string[] }[] = [
|
|
|
115
114
|
*/
|
|
116
115
|
export const VISION_AGENTS = ["designer:design-review", "designer:ui"] as const;
|
|
117
116
|
|
|
118
|
-
/**
|
|
119
|
-
* `seo:editor` is the one agent in this marketplace whose own frontmatter sets a model.
|
|
120
|
-
* Routing it to `inherit` keeps that authoring decision visible and working instead of
|
|
121
|
-
* silently overriding it — per-invocation beats frontmatter, so without this the author's
|
|
122
|
-
* choice would vanish with no error.
|
|
123
|
-
*/
|
|
124
|
-
const FRONTMATTER_RESPECTED: Record<string, { model: "inherit" }> = {
|
|
125
|
-
"seo:editor": { model: "inherit" },
|
|
126
|
-
};
|
|
127
|
-
|
|
128
117
|
function preset(
|
|
129
118
|
name: string,
|
|
130
119
|
main: ModelsConfig["main"],
|
|
@@ -135,7 +124,7 @@ function preset(
|
|
|
135
124
|
preset: name,
|
|
136
125
|
main,
|
|
137
126
|
grades,
|
|
138
|
-
agents: { ...DEFAULT_AGENT_GRADES
|
|
127
|
+
agents: { ...DEFAULT_AGENT_GRADES },
|
|
139
128
|
fallback: "normal",
|
|
140
129
|
};
|
|
141
130
|
}
|
|
@@ -57,7 +57,6 @@ export const PREDEFINED_PROFILES: PredefinedProfile[] = [
|
|
|
57
57
|
"browser-use",
|
|
58
58
|
"designer",
|
|
59
59
|
"terminal",
|
|
60
|
-
"kanban",
|
|
61
60
|
],
|
|
62
61
|
anthropicPlugins: [
|
|
63
62
|
"claude-code-setup",
|
|
@@ -130,14 +129,7 @@ export const PREDEFINED_PROFILES: PredefinedProfile[] = [
|
|
|
130
129
|
name: "Backend Forge",
|
|
131
130
|
description: "API development, debugging, code quality, data workflows",
|
|
132
131
|
icon: "★",
|
|
133
|
-
magusPlugins: [
|
|
134
|
-
"dev",
|
|
135
|
-
"code-analysis",
|
|
136
|
-
"terminal",
|
|
137
|
-
"setup",
|
|
138
|
-
"multimodel",
|
|
139
|
-
"gtd",
|
|
140
|
-
],
|
|
132
|
+
magusPlugins: ["dev", "code-analysis", "terminal", "setup", "multimodel"],
|
|
141
133
|
anthropicPlugins: [
|
|
142
134
|
"feature-dev",
|
|
143
135
|
"code-review",
|
|
@@ -171,7 +163,6 @@ export const PREDEFINED_PROFILES: PredefinedProfile[] = [
|
|
|
171
163
|
"terminal",
|
|
172
164
|
"setup",
|
|
173
165
|
"multimodel",
|
|
174
|
-
"gtd",
|
|
175
166
|
"browser-use",
|
|
176
167
|
],
|
|
177
168
|
anthropicPlugins: [
|
|
@@ -199,15 +190,13 @@ export const PREDEFINED_PROFILES: PredefinedProfile[] = [
|
|
|
199
190
|
{
|
|
200
191
|
id: "growth-marketer",
|
|
201
192
|
name: "Growth Marketer",
|
|
202
|
-
description:
|
|
203
|
-
"SEO, website audits, content production, marketing automation",
|
|
193
|
+
description: "Website audits, content production, marketing automation",
|
|
204
194
|
icon: "★",
|
|
205
195
|
magusPlugins: [
|
|
206
196
|
"dev",
|
|
207
197
|
"code-analysis",
|
|
208
198
|
"terminal",
|
|
209
199
|
"setup",
|
|
210
|
-
"seo@magus-marketing",
|
|
211
200
|
"browser-use",
|
|
212
201
|
"image-generate@magus-marketing",
|
|
213
202
|
"video-editing@magus-marketing",
|
|
@@ -237,14 +226,7 @@ export const PREDEFINED_PROFILES: PredefinedProfile[] = [
|
|
|
237
226
|
description:
|
|
238
227
|
"Planning, code review, coordination, and broad repo visibility",
|
|
239
228
|
icon: "★",
|
|
240
|
-
magusPlugins: [
|
|
241
|
-
"dev",
|
|
242
|
-
"code-analysis",
|
|
243
|
-
"terminal",
|
|
244
|
-
"setup",
|
|
245
|
-
"multimodel",
|
|
246
|
-
"gtd",
|
|
247
|
-
],
|
|
229
|
+
magusPlugins: ["dev", "code-analysis", "terminal", "setup", "multimodel"],
|
|
248
230
|
anthropicPlugins: [
|
|
249
231
|
"code-review",
|
|
250
232
|
"pr-review-toolkit",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* `hook-named` 5/5 (subagent on claude-haiku-4-5-20251001) vs `nohook` 0/5 (inherited
|
|
15
15
|
* claude-sonnet-5). One-sided Fisher p = 0.004.
|
|
16
16
|
* 2. `hookEventName` is REQUIRED. The payload without it ran on all five passes and changed
|
|
17
|
-
* nothing — so ALWAYS emit it.
|
|
17
|
+
* nothing — so ALWAYS emit it.
|
|
18
18
|
* 3. `permissionDecision: "allow"` is neither required nor harmful, so it is NOT emitted:
|
|
19
19
|
* it would widen permission for the call in exchange for nothing.
|
|
20
20
|
* 4. The value is SCHEMA-CHECKED. A non-alias string refused the spawn outright on all five
|
|
@@ -113,9 +113,8 @@ export interface PluginInfo {
|
|
|
113
113
|
* For an orphaned plugin: another configured marketplace that publishes a
|
|
114
114
|
* plugin of the same name — i.e. it did not disappear, it moved.
|
|
115
115
|
*
|
|
116
|
-
* Splitting `magus` into `magus` + `magus-marketing` left
|
|
117
|
-
*
|
|
118
|
-
* longer lists them. Rendered as bare "deprecated", the only offered action
|
|
116
|
+
* Splitting `magus` into `magus` + `magus-marketing` left video-editing and
|
|
117
|
+
* image-generate installed under a namespace that no longer lists them. Rendered as bare "deprecated", the only offered action
|
|
119
118
|
* was deletion, which silently drops a plugin that is still published.
|
|
120
119
|
*/
|
|
121
120
|
movedTo?: string;
|
package/src/services/resolver.ts
CHANGED
|
@@ -55,7 +55,7 @@ export function resolveExtends(
|
|
|
55
55
|
|
|
56
56
|
const inheritedPlugins: Record<string, string | boolean> = {};
|
|
57
57
|
for (const name of base.magusPlugins) {
|
|
58
|
-
// Entries may pin a marketplace explicitly (e.g. "
|
|
58
|
+
// Entries may pin a marketplace explicitly (e.g. "image-generate@magus-marketing")
|
|
59
59
|
// for plugins that ship on a channel other than magus. Bare names
|
|
60
60
|
// still default to @magus.
|
|
61
61
|
inheritedPlugins[name.includes("@") ? name : `${name}@magus`] = "latest";
|
|
@@ -158,7 +158,7 @@ async function resolveBins(
|
|
|
158
158
|
/**
|
|
159
159
|
* Add the marketplace every plugin id already names.
|
|
160
160
|
*
|
|
161
|
-
* A plugin id carries its marketplace — `dev@magus`, `
|
|
161
|
+
* A plugin id carries its marketplace — `dev@magus`, `image-generate@magus-marketing` —
|
|
162
162
|
* so requiring the manifest to ALSO declare that marketplace is redundant, and
|
|
163
163
|
* forgetting to is silent: the plugin resolves against a marketplace that was
|
|
164
164
|
* never registered. `extends` made this the default failure, because it
|
|
@@ -1200,7 +1200,7 @@ export function PluginsScreen() {
|
|
|
1200
1200
|
// A plugin that moved marketplace is not deprecated — it is still
|
|
1201
1201
|
// published under a new namespace. Offering only "remove" silently drops
|
|
1202
1202
|
// something the user still wants (the magus → magus-marketing split left
|
|
1203
|
-
//
|
|
1203
|
+
// video-editing and image-generate in this state).
|
|
1204
1204
|
let migrateTo: string | undefined;
|
|
1205
1205
|
if (plugin.movedTo) {
|
|
1206
1206
|
const choice = await modal.select(
|