magus-cli 7.7.0 → 7.9.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/README.md +1 -1
- package/package.json +4 -4
- package/src/__tests__/blackbox-parity.test.ts +45 -26
- package/src/__tests__/blackbox-plugin-state.test.ts +25 -6
- package/src/__tests__/blind/plugin-deps/doctor.test.ts +622 -0
- package/src/__tests__/blind/plugin-deps/helpers.ts +340 -0
- package/src/__tests__/blind/plugin-deps/hook.test.ts +328 -0
- package/src/__tests__/blind/plugin-deps/probe.test.ts +694 -0
- package/src/__tests__/blind/plugin-deps/read-only.test.ts +96 -0
- package/src/__tests__/blind/plugin-deps/schema.test.ts +390 -0
- package/src/__tests__/blind-780-cli-tools.test.ts +796 -0
- package/src/__tests__/blind-780-deprecated.test.ts +631 -0
- package/src/__tests__/blind-780-failure-reasons.test.ts +1004 -0
- package/src/__tests__/blind-780-plugin-resolution.test.ts +431 -0
- package/src/__tests__/blind-780-review2.test.ts +1178 -0
- package/src/__tests__/cli-apply-seams.test.ts +2 -67
- package/src/__tests__/cli-tool-commands.test.ts +131 -6
- package/src/__tests__/cli-tools-adapter.test.ts +52 -2
- package/src/__tests__/cli-tools-plugin-managed.test.tsx +83 -0
- package/src/__tests__/cli-update-view.test.ts +3 -3
- package/src/__tests__/deprecated-remove.test.ts +827 -0
- package/src/__tests__/deps-adapter.test.ts +191 -0
- package/src/__tests__/deps-check-hook.test.ts +220 -0
- package/src/__tests__/deps-plan.test.ts +896 -0
- package/src/__tests__/deps-report.test.ts +197 -0
- package/src/__tests__/doctor-bins.test.ts +134 -36
- package/src/__tests__/doctor-json.test.ts +261 -0
- package/src/__tests__/doctor.test.ts +159 -1
- package/src/__tests__/failure-popup-review2.test.tsx +896 -0
- package/src/__tests__/failure-popup.test.tsx +419 -0
- package/src/__tests__/final-fixes-780.test.tsx +740 -0
- package/src/__tests__/fixtures/bins.ts +55 -0
- package/src/__tests__/format-update-outcome.test.ts +6 -32
- package/src/__tests__/gitignore-profile.test.ts +1 -9
- package/src/__tests__/install-plan.test.ts +15 -1
- package/src/__tests__/marketplace-refresh.test.ts +9 -3
- package/src/__tests__/models-screen-state.test.ts +18 -40
- package/src/__tests__/no-shell-spawn.test.ts +77 -0
- package/src/__tests__/npm-package-name.test.ts +6 -9
- package/src/__tests__/plugin-requires.test.ts +138 -153
- package/src/__tests__/plugin-resolution.test.ts +52 -13
- package/src/__tests__/plugin-row-text.test.ts +30 -37
- package/src/__tests__/profile-dependencies.test.ts +7 -0
- package/src/__tests__/profile-startup-old-format.test.ts +0 -3
- package/src/__tests__/profile-startup.test.ts +40 -12
- package/src/__tests__/requires-probe.test.ts +858 -0
- package/src/__tests__/resolver.test.ts +129 -52
- package/src/__tests__/run.test.ts +108 -0
- package/src/__tests__/stale-load-repair.test.ts +31 -23
- package/src/__tests__/tool-probe.test.ts +1 -1
- package/src/__tests__/toolchain.test.ts +123 -52
- package/src/__tests__/update-apply.test.ts +13 -29
- package/src/__tests__/update-plan.test.ts +24 -18
- package/src/__tests__/useStaleLoadModal.test.ts +1 -57
- package/src/__tests__/version-unknown-state.test.ts +15 -17
- package/src/cli/ansi.ts +2 -7
- package/src/cli/deps-view.ts +213 -0
- package/src/cli/doctor.ts +320 -54
- package/src/cli/install.ts +55 -71
- package/src/cli/profile.ts +20 -2
- package/src/cli/router.ts +6 -2
- package/src/cli/update-view.ts +58 -15
- package/src/cli/update.ts +55 -12
- package/src/data/cli-tools.ts +24 -9
- package/src/services/active-closure.ts +3 -3
- package/src/services/catalog-cache-store.ts +8 -6
- package/src/services/claude-cli.ts +106 -19
- package/src/services/claude-settings.ts +88 -384
- package/src/services/cli-tool-commands.ts +104 -3
- package/src/services/deps-plan.ts +962 -0
- package/src/services/deps-report.ts +265 -0
- package/src/services/doctor-bins.ts +115 -43
- package/src/services/install-plan.ts +11 -1
- package/src/services/local-marketplace.ts +12 -206
- package/src/services/marketplace-refresh.ts +4 -4
- package/src/services/plugin-env-needs.ts +69 -0
- package/src/services/plugin-manager.ts +35 -37
- package/src/services/plugin-requires.ts +93 -162
- package/src/services/plugin-resolution.ts +34 -42
- package/src/services/profile-activate.ts +1 -2
- package/src/services/profile-startup.ts +18 -43
- package/src/services/profile-state.ts +72 -2
- package/src/services/resolver.ts +263 -59
- package/src/services/tool-probe.ts +18 -25
- package/src/services/toolchain.ts +117 -111
- package/src/services/update-engine.ts +345 -45
- package/src/services/update-plan.ts +296 -36
- package/src/shared/plugin/hooks/bun-missing.sh +39 -0
- package/src/shared/plugin/hooks/deps-check.ts +224 -0
- package/src/shared/plugin/hooks/lib/requires-probe.ts +1183 -0
- package/src/types/index.ts +60 -16
- package/src/ui/App.tsx +81 -171
- package/src/ui/adapters/cliToolsAdapter.ts +252 -12
- package/src/ui/adapters/depsAdapter.ts +129 -0
- package/src/ui/adapters/failureReport.ts +137 -0
- package/src/ui/adapters/pluginsAdapter.ts +297 -45
- package/src/ui/adapters/startupReports.ts +260 -0
- package/src/ui/components/modals/FailureListModal.tsx +231 -0
- package/src/ui/components/modals/MessageModal.tsx +7 -12
- package/src/ui/components/modals/ModalContainer.tsx +10 -5
- package/src/ui/components/modals/index.ts +1 -0
- package/src/ui/hooks/startupQueue.ts +56 -0
- package/src/ui/hooks/useGitignoreModal.ts +30 -52
- package/src/ui/hooks/useProfileStartupModal.ts +119 -142
- package/src/ui/hooks/useStaleLoadModal.ts +35 -65
- package/src/ui/keymap.ts +14 -1
- package/src/ui/renderers/cliToolRenderers.tsx +135 -2
- package/src/ui/renderers/mcpRenderers.tsx +110 -3
- package/src/ui/renderers/pluginRenderers.tsx +77 -6
- package/src/ui/renderers/pluginRowText.ts +25 -25
- package/src/ui/renderers/pluginStatusText.ts +18 -1
- package/src/ui/screens/AliasScreen.tsx +38 -45
- package/src/ui/screens/CliToolsScreen.tsx +199 -98
- package/src/ui/screens/EnvVarsScreen.tsx +17 -5
- package/src/ui/screens/GitignoreScreen.tsx +48 -67
- package/src/ui/screens/McpRegistryScreen.tsx +67 -38
- package/src/ui/screens/McpScreen.tsx +46 -19
- package/src/ui/screens/ModelsScreen.tsx +46 -86
- package/src/ui/screens/PluginsScreen.tsx +315 -259
- package/src/ui/screens/ProfilesScreen.tsx +79 -40
- package/src/ui/screens/SkillsScreen.tsx +83 -80
- package/src/ui/screens/StylesScreen.tsx +211 -275
- package/src/ui/state/AppContext.tsx +66 -44
- package/src/ui/state/DimensionsContext.tsx +3 -22
- package/src/ui/state/reducer.ts +12 -100
- package/src/ui/state/types.ts +48 -69
- package/src/utils/command-utils.ts +4 -5
- package/src/utils/run.ts +123 -52
- package/src/utils/string-utils.ts +9 -0
- package/src/utils/user-path.ts +60 -0
- package/src/__tests__/go-module.test.ts +0 -43
- package/src/__tests__/notices-rules.test.ts +0 -476
- package/src/__tests__/notices.test.tsx +0 -604
- package/src/__tests__/plugin-setup.test.ts +0 -1016
- package/src/__tests__/shell-script-callers.test.ts +0 -92
- package/src/__tests__/styles-status-line.test.ts +0 -72
- package/src/__tests__/version-snapshot.test.ts +0 -212
- package/src/services/plugin-setup.ts +0 -965
- package/src/services/version-snapshot.ts +0 -189
- package/src/utils/go-module.ts +0 -36
|
@@ -162,9 +162,6 @@ function getGitRemote(marketplacePath: string): string | undefined {
|
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
/**
|
|
166
|
-
* Scan a single marketplace directory and return marketplace info
|
|
167
|
-
*/
|
|
168
165
|
/**
|
|
169
166
|
* Names of the skills under `skillsDir`, recursing through grouping directories.
|
|
170
167
|
*
|
|
@@ -260,6 +257,11 @@ export async function scanMcpServers(pluginPath: string): Promise<string[]> {
|
|
|
260
257
|
return found;
|
|
261
258
|
}
|
|
262
259
|
|
|
260
|
+
/**
|
|
261
|
+
* Read one marketplace clone: its manifest, plus the component counts of each
|
|
262
|
+
* plugin whose source directory is on disk. Read-only — the manifest and the
|
|
263
|
+
* clone belong to Claude Code, and nothing here writes either.
|
|
264
|
+
*/
|
|
263
265
|
async function scanSingleMarketplace(
|
|
264
266
|
marketplacePath: string,
|
|
265
267
|
marketplaceName: string,
|
|
@@ -277,8 +279,6 @@ async function scanSingleMarketplace(
|
|
|
277
279
|
const gitRepo = getGitRemote(marketplacePath);
|
|
278
280
|
|
|
279
281
|
const plugins: LocalMarketplacePlugin[] = [];
|
|
280
|
-
const validManifestPlugins: typeof manifest.plugins = [];
|
|
281
|
-
let manifestModified = false;
|
|
282
282
|
|
|
283
283
|
if (manifest.plugins && Array.isArray(manifest.plugins)) {
|
|
284
284
|
for (const plugin of manifest.plugins) {
|
|
@@ -292,18 +292,13 @@ async function scanSingleMarketplace(
|
|
|
292
292
|
const sourceStr =
|
|
293
293
|
typeof plugin.source === "string" ? plugin.source : null;
|
|
294
294
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (!(await fs.pathExists(pluginPath))) {
|
|
303
|
-
manifestModified = true;
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
|
|
295
|
+
// A plugin whose source directory is not on disk is still listed: the
|
|
296
|
+
// manifest is Claude Code's, and a scan reads it, never rewrites it.
|
|
297
|
+
// Its component lists stay empty because there is nothing to count.
|
|
298
|
+
const pluginPath = sourceStr
|
|
299
|
+
? path.join(marketplacePath, sourceStr.replace("./", ""))
|
|
300
|
+
: null;
|
|
301
|
+
if (pluginPath && (await fs.pathExists(pluginPath))) {
|
|
307
302
|
try {
|
|
308
303
|
// Scan for agents
|
|
309
304
|
const agentsDir = path.join(pluginPath, "agents");
|
|
@@ -330,7 +325,6 @@ async function scanSingleMarketplace(
|
|
|
330
325
|
}
|
|
331
326
|
}
|
|
332
327
|
|
|
333
|
-
validManifestPlugins.push(plugin);
|
|
334
328
|
plugins.push({
|
|
335
329
|
name: plugin.name,
|
|
336
330
|
version: plugin.version || "0.0.0",
|
|
@@ -349,12 +343,6 @@ async function scanSingleMarketplace(
|
|
|
349
343
|
}
|
|
350
344
|
}
|
|
351
345
|
|
|
352
|
-
// Update marketplace.json if we removed invalid plugins
|
|
353
|
-
if (manifestModified) {
|
|
354
|
-
manifest.plugins = validManifestPlugins;
|
|
355
|
-
await fs.writeJson(manifestPath, manifest, { spaces: 2 });
|
|
356
|
-
}
|
|
357
|
-
|
|
358
346
|
return {
|
|
359
347
|
name: manifest.name || marketplaceName,
|
|
360
348
|
description: manifest.description || manifest.metadata?.description || "",
|
|
@@ -466,13 +454,6 @@ export async function hasLocalMarketplace(name: string): Promise<boolean> {
|
|
|
466
454
|
return fs.pathExists(manifestPath);
|
|
467
455
|
}
|
|
468
456
|
|
|
469
|
-
export interface RefreshResult {
|
|
470
|
-
name: string;
|
|
471
|
-
success: boolean;
|
|
472
|
-
updated: boolean;
|
|
473
|
-
error?: string;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
457
|
export interface RefreshProgress {
|
|
477
458
|
current: number;
|
|
478
459
|
total: number;
|
|
@@ -480,178 +461,3 @@ export interface RefreshProgress {
|
|
|
480
461
|
}
|
|
481
462
|
|
|
482
463
|
export type ProgressCallback = (progress: RefreshProgress) => void;
|
|
483
|
-
|
|
484
|
-
export interface RepairResult {
|
|
485
|
-
repaired: boolean;
|
|
486
|
-
pluginName: string;
|
|
487
|
-
added: {
|
|
488
|
-
agents: string[];
|
|
489
|
-
commands: string[];
|
|
490
|
-
skills: string[];
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
/**
|
|
495
|
-
* Repair a plugin's plugin.json by adding missing agents/commands/skills arrays
|
|
496
|
-
* based on what files exist on disk.
|
|
497
|
-
*
|
|
498
|
-
* This fixes plugins where the author forgot to add the arrays to plugin.json,
|
|
499
|
-
* causing Claude Code to not load the agents/commands even though files exist.
|
|
500
|
-
*/
|
|
501
|
-
export async function repairPluginJson(
|
|
502
|
-
pluginPath: string,
|
|
503
|
-
): Promise<RepairResult> {
|
|
504
|
-
const pluginJsonPath = path.join(pluginPath, ".claude-plugin", "plugin.json");
|
|
505
|
-
const pluginName = path.basename(pluginPath);
|
|
506
|
-
|
|
507
|
-
const result: RepairResult = {
|
|
508
|
-
repaired: false,
|
|
509
|
-
pluginName,
|
|
510
|
-
added: { agents: [], commands: [], skills: [] },
|
|
511
|
-
};
|
|
512
|
-
|
|
513
|
-
if (!(await fs.pathExists(pluginJsonPath))) {
|
|
514
|
-
return result;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
try {
|
|
518
|
-
const pluginJson = await fs.readJson(pluginJsonPath);
|
|
519
|
-
let modified = false;
|
|
520
|
-
|
|
521
|
-
// Scan and add agents if array is missing
|
|
522
|
-
const agentsDir = path.join(pluginPath, "agents");
|
|
523
|
-
if (await fs.pathExists(agentsDir)) {
|
|
524
|
-
const files = (await fs.readdir(agentsDir))
|
|
525
|
-
.filter((f) => f.endsWith(".md"))
|
|
526
|
-
.map((f) => `./agents/${f}`);
|
|
527
|
-
|
|
528
|
-
if (files.length > 0 && !pluginJson.agents) {
|
|
529
|
-
pluginJson.agents = files;
|
|
530
|
-
result.added.agents = files;
|
|
531
|
-
modified = true;
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// Scan and add commands if array is missing
|
|
536
|
-
const commandsDir = path.join(pluginPath, "commands");
|
|
537
|
-
if (await fs.pathExists(commandsDir)) {
|
|
538
|
-
const files = (await fs.readdir(commandsDir))
|
|
539
|
-
.filter((f) => f.endsWith(".md"))
|
|
540
|
-
.map((f) => `./commands/${f}`);
|
|
541
|
-
|
|
542
|
-
if (files.length > 0 && !pluginJson.commands) {
|
|
543
|
-
pluginJson.commands = files;
|
|
544
|
-
result.added.commands = files;
|
|
545
|
-
modified = true;
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// Scan and add skills if array is missing
|
|
550
|
-
const skillsDir = path.join(pluginPath, "skills");
|
|
551
|
-
if (await fs.pathExists(skillsDir)) {
|
|
552
|
-
const entries = await fs.readdir(skillsDir, { withFileTypes: true });
|
|
553
|
-
const skills = entries
|
|
554
|
-
.filter((e) => e.isDirectory() || e.name.endsWith(".md"))
|
|
555
|
-
.map((e) => `./skills/${e.name}`);
|
|
556
|
-
|
|
557
|
-
if (skills.length > 0 && !pluginJson.skills) {
|
|
558
|
-
pluginJson.skills = skills;
|
|
559
|
-
result.added.skills = skills;
|
|
560
|
-
modified = true;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
if (modified) {
|
|
565
|
-
await fs.writeJson(pluginJsonPath, pluginJson, { spaces: 2 });
|
|
566
|
-
result.repaired = true;
|
|
567
|
-
}
|
|
568
|
-
} catch {
|
|
569
|
-
// Ignore repair errors for individual plugins
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
return result;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
export interface RepairMarketplaceResult {
|
|
576
|
-
marketplace: string;
|
|
577
|
-
repaired: RepairResult[];
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
/**
|
|
581
|
-
* Repair all plugins in a marketplace
|
|
582
|
-
*/
|
|
583
|
-
export async function repairMarketplacePlugins(
|
|
584
|
-
marketplaceName: string,
|
|
585
|
-
): Promise<RepairMarketplaceResult> {
|
|
586
|
-
const result: RepairMarketplaceResult = {
|
|
587
|
-
marketplace: marketplaceName,
|
|
588
|
-
repaired: [],
|
|
589
|
-
};
|
|
590
|
-
|
|
591
|
-
const marketplacePath = path.join(CLAUDE_PLUGINS_DIR, marketplaceName);
|
|
592
|
-
const manifestPath = path.join(
|
|
593
|
-
marketplacePath,
|
|
594
|
-
".claude-plugin",
|
|
595
|
-
"marketplace.json",
|
|
596
|
-
);
|
|
597
|
-
|
|
598
|
-
if (!(await fs.pathExists(manifestPath))) {
|
|
599
|
-
return result;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
try {
|
|
603
|
-
const manifest = await fs.readJson(manifestPath);
|
|
604
|
-
|
|
605
|
-
if (manifest.plugins && Array.isArray(manifest.plugins)) {
|
|
606
|
-
for (const plugin of manifest.plugins) {
|
|
607
|
-
if (plugin.source) {
|
|
608
|
-
const pluginPath = path.join(
|
|
609
|
-
marketplacePath,
|
|
610
|
-
plugin.source.replace("./", ""),
|
|
611
|
-
);
|
|
612
|
-
const repairResult = await repairPluginJson(pluginPath);
|
|
613
|
-
|
|
614
|
-
if (repairResult.repaired) {
|
|
615
|
-
result.repaired.push(repairResult);
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
} catch {
|
|
621
|
-
// Ignore errors
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
return result;
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
/**
|
|
628
|
-
* Repair all plugins in all marketplaces
|
|
629
|
-
*/
|
|
630
|
-
export async function repairAllMarketplaces(): Promise<
|
|
631
|
-
RepairMarketplaceResult[]
|
|
632
|
-
> {
|
|
633
|
-
const results: RepairMarketplaceResult[] = [];
|
|
634
|
-
|
|
635
|
-
if (!(await fs.pathExists(CLAUDE_PLUGINS_DIR))) {
|
|
636
|
-
return results;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
try {
|
|
640
|
-
const entries = await fs.readdir(CLAUDE_PLUGINS_DIR, {
|
|
641
|
-
withFileTypes: true,
|
|
642
|
-
});
|
|
643
|
-
|
|
644
|
-
for (const entry of entries) {
|
|
645
|
-
if (!entry.isDirectory()) continue;
|
|
646
|
-
|
|
647
|
-
const repairResult = await repairMarketplacePlugins(entry.name);
|
|
648
|
-
if (repairResult.repaired.length > 0) {
|
|
649
|
-
results.push(repairResult);
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
} catch {
|
|
653
|
-
// Return empty on error
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
return results;
|
|
657
|
-
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Keep on-disk marketplace clones current — safely.
|
|
3
3
|
*
|
|
4
|
-
* The plugin *list* magus shows is fetched fresh from GitHub, but
|
|
5
|
-
* `
|
|
6
|
-
* clone (~/.claude/plugins/marketplaces/<name>). Nothing else keeps that clone
|
|
4
|
+
* The plugin *list* magus shows is fetched fresh from GitHub, but the
|
|
5
|
+
* `requires` of a plugin not installed yet and Claude Code's own installer read
|
|
6
|
+
* the on-disk marketplace clone (~/.claude/plugins/marketplaces/<name>). Nothing else keeps that clone
|
|
7
7
|
* current — recoverMarketplaceSettings only pulls entries it had to re-register —
|
|
8
8
|
* so a healthy marketplace's clone goes stale and its plugin metadata (e.g. a
|
|
9
|
-
*
|
|
9
|
+
* dependency pin in a plugin's `requires`) is never seen, even while the
|
|
10
10
|
* displayed version looks up to date.
|
|
11
11
|
*
|
|
12
12
|
* IMPORTANT: this refreshes the clone with a NON-DESTRUCTIVE `git pull --ff-only`,
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which variables a plugin's MCP server needs and does not have yet.
|
|
3
|
+
*
|
|
4
|
+
* DERIVED FROM CURRENT CONFIG, every time: the plugin's own MCP config names
|
|
5
|
+
* the `${VAR}` references, and a variable counts as set when this project's
|
|
6
|
+
* MCP variables hold it or magus's environment carries it. Nothing records
|
|
7
|
+
* that the user declined to set them — so the Plugins screen's "needs N
|
|
8
|
+
* settings" state disappears on its own the moment they are set, however
|
|
9
|
+
* they were set.
|
|
10
|
+
*
|
|
11
|
+
* One function for both the install flow (which asks for exactly these) and
|
|
12
|
+
* the detail panel (which states them), so the two cannot disagree.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { getMcpEnvVars } from "./claude-settings.js";
|
|
16
|
+
import {
|
|
17
|
+
type PluginEnvRequirement,
|
|
18
|
+
getPluginEnvRequirements,
|
|
19
|
+
getPluginSourcePath,
|
|
20
|
+
} from "./plugin-mcp-config.js";
|
|
21
|
+
|
|
22
|
+
/** The reads, injectable so a test needs no marketplace clone on disk. */
|
|
23
|
+
export interface PluginEnvNeedsDeps {
|
|
24
|
+
sourcePath: (
|
|
25
|
+
marketplace: string,
|
|
26
|
+
pluginName: string,
|
|
27
|
+
) => Promise<string | undefined>;
|
|
28
|
+
requirements: (
|
|
29
|
+
marketplace: string,
|
|
30
|
+
pluginSource: string,
|
|
31
|
+
) => Promise<PluginEnvRequirement[]>;
|
|
32
|
+
configured: (projectPath?: string) => Promise<Record<string, string>>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const REAL_DEPS: PluginEnvNeedsDeps = {
|
|
36
|
+
sourcePath: getPluginSourcePath,
|
|
37
|
+
requirements: getPluginEnvRequirements,
|
|
38
|
+
configured: getMcpEnvVars,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The variables `pluginName`'s MCP servers reference that are set nowhere —
|
|
43
|
+
* empty when it has no MCP server, needs nothing, or has everything.
|
|
44
|
+
*
|
|
45
|
+
* Throws when the project's variables cannot be read (a settings file that
|
|
46
|
+
* does not parse): "needs nothing" would be a guess.
|
|
47
|
+
*/
|
|
48
|
+
export async function missingPluginEnvVars(
|
|
49
|
+
marketplace: string,
|
|
50
|
+
pluginName: string,
|
|
51
|
+
projectPath?: string,
|
|
52
|
+
env: Readonly<Record<string, string | undefined>> = process.env,
|
|
53
|
+
deps: PluginEnvNeedsDeps = REAL_DEPS,
|
|
54
|
+
): Promise<PluginEnvRequirement[]> {
|
|
55
|
+
const source = await deps.sourcePath(marketplace, pluginName);
|
|
56
|
+
if (!source) return [];
|
|
57
|
+
const requirements = await deps.requirements(marketplace, source);
|
|
58
|
+
if (requirements.length === 0) return [];
|
|
59
|
+
const configured = await deps.configured(projectPath);
|
|
60
|
+
return requirements.filter((req) => !configured[req.name] && !env[req.name]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The detail panel's words for a plugin whose MCP server lacks variables:
|
|
65
|
+
* what is missing, then the one key that sets them.
|
|
66
|
+
*/
|
|
67
|
+
export function mcpSettingsNeededText(count: number): string {
|
|
68
|
+
return `its MCP server needs ${count} ${count === 1 ? "setting" : "settings"} — press e to set ${count === 1 ? "it" : "them"}`;
|
|
69
|
+
}
|
|
@@ -33,8 +33,6 @@ import { inheritablePaths } from "./git-worktree.js";
|
|
|
33
33
|
import {
|
|
34
34
|
type LocalMarketplace,
|
|
35
35
|
type ProgressCallback,
|
|
36
|
-
type RepairMarketplaceResult,
|
|
37
|
-
repairAllMarketplaces,
|
|
38
36
|
scanLocalMarketplaces,
|
|
39
37
|
} from "./local-marketplace.js";
|
|
40
38
|
import {
|
|
@@ -162,17 +160,6 @@ export interface PluginInfo {
|
|
|
162
160
|
* a reinstall delivers the current content. See content-drift.ts.
|
|
163
161
|
*/
|
|
164
162
|
contentStale?: boolean;
|
|
165
|
-
/**
|
|
166
|
-
* Set when this plugin's installed version changed since magus last
|
|
167
|
-
* rendered it — including updates made by Claude Code or a direct CLI call
|
|
168
|
-
* rather than by magus itself. Drives the "updated" badge.
|
|
169
|
-
*/
|
|
170
|
-
recentlyUpdatedFrom?: string;
|
|
171
|
-
/**
|
|
172
|
-
* Set when magus is seeing this plugin installed for the first time.
|
|
173
|
-
* Never set on the very first run, when there is no baseline to compare to.
|
|
174
|
-
*/
|
|
175
|
-
recentlyInstalled?: boolean;
|
|
176
163
|
/**
|
|
177
164
|
* Which catalog supplied `version` — i.e. the number this plugin's installed
|
|
178
165
|
* version was compared against.
|
|
@@ -389,6 +376,28 @@ export function resolveScopeAction(
|
|
|
389
376
|
return behind ? "update" : "uninstall";
|
|
390
377
|
}
|
|
391
378
|
|
|
379
|
+
/**
|
|
380
|
+
* The plugin ids the Plugins list may show as installed-but-unlisted rows.
|
|
381
|
+
*
|
|
382
|
+
* Installed means a key in this project's `enabledPlugins`, or registry rows.
|
|
383
|
+
* A version in `installedPluginVersions` with neither behind it is not an
|
|
384
|
+
* install: Claude Code never writes that field, so such a claim is a leftover
|
|
385
|
+
* of magus's own, and building a row from it showed a plugin nothing had
|
|
386
|
+
* installed — the settings key treated as authority, which it never is.
|
|
387
|
+
*/
|
|
388
|
+
export function orphanCandidateIds(
|
|
389
|
+
enabledPlugins: Readonly<Record<string, boolean>>,
|
|
390
|
+
installedVersions: Readonly<Record<string, string>>,
|
|
391
|
+
registryPlugins: InstalledPluginsRegistry["plugins"] | undefined,
|
|
392
|
+
): Set<string> {
|
|
393
|
+
return new Set([
|
|
394
|
+
...Object.keys(enabledPlugins),
|
|
395
|
+
...Object.keys(installedVersions).filter(
|
|
396
|
+
(id) => (registryPlugins?.[id]?.length ?? 0) > 0,
|
|
397
|
+
),
|
|
398
|
+
]);
|
|
399
|
+
}
|
|
400
|
+
|
|
392
401
|
export async function getAvailablePlugins(
|
|
393
402
|
projectPath?: string,
|
|
394
403
|
): Promise<PluginInfo[]> {
|
|
@@ -402,8 +411,9 @@ export async function getAvailablePlugins(
|
|
|
402
411
|
// ported in services/plugin-resolution.ts. Resolved once for the whole
|
|
403
412
|
// registry and carried alongside `installedVersion` on every row below; where
|
|
404
413
|
// the two disagree, the classifier calls it `stale-load`.
|
|
414
|
+
const registry = await readInstalledPluginsRegistry();
|
|
405
415
|
const loadedVersions = resolveLoadedVersions(
|
|
406
|
-
|
|
416
|
+
registry,
|
|
407
417
|
projectPath ?? process.cwd(),
|
|
408
418
|
);
|
|
409
419
|
const loadedVersionOf = (pluginId: string): string | null =>
|
|
@@ -568,13 +578,13 @@ export async function getAvailablePlugins(
|
|
|
568
578
|
}
|
|
569
579
|
}
|
|
570
580
|
|
|
571
|
-
// Add orphaned plugins (in this project's settings or registry, but not in
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
581
|
+
// Add orphaned plugins (in this project's settings or registry, but not in
|
|
582
|
+
// any cache) — see `orphanCandidateIds` for what counts as installed.
|
|
583
|
+
for (const pluginId of orphanCandidateIds(
|
|
584
|
+
enabledPlugins,
|
|
585
|
+
installedVersions,
|
|
586
|
+
registry.plugins,
|
|
587
|
+
)) {
|
|
578
588
|
if (seenPluginIds.has(pluginId)) continue;
|
|
579
589
|
|
|
580
590
|
const parsed = parsePluginId(pluginId);
|
|
@@ -1031,18 +1041,14 @@ async function autoSyncIfStale(
|
|
|
1031
1041
|
return localMarketplaces;
|
|
1032
1042
|
}
|
|
1033
1043
|
|
|
1034
|
-
export interface RefreshAndRepairResult {
|
|
1035
|
-
refresh: never[];
|
|
1036
|
-
repair: RepairMarketplaceResult[];
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
1044
|
/**
|
|
1040
|
-
* Refresh magus's internal cache
|
|
1041
|
-
*
|
|
1045
|
+
* Refresh magus's internal cache. Only magus's own caches are cleared: the
|
|
1046
|
+
* marketplace clones are Claude Code's, and refreshing them is
|
|
1047
|
+
* `claude plugin marketplace update`.
|
|
1042
1048
|
*/
|
|
1043
1049
|
export async function refreshAllMarketplaces(
|
|
1044
1050
|
onProgress?: ProgressCallback,
|
|
1045
|
-
): Promise<
|
|
1051
|
+
): Promise<void> {
|
|
1046
1052
|
onProgress?.({ current: 1, total: 1, name: "Clearing cache..." });
|
|
1047
1053
|
|
|
1048
1054
|
// Clear all caches to force fresh data. The on-disk catalog cache is awaited
|
|
@@ -1050,14 +1056,6 @@ export async function refreshAllMarketplaces(
|
|
|
1050
1056
|
// entries this is discarding.
|
|
1051
1057
|
clearMarketplaceCache();
|
|
1052
1058
|
await clearPersistedCatalogs();
|
|
1053
|
-
|
|
1054
|
-
// Auto-repair plugin.json files with missing agents/commands/skills
|
|
1055
|
-
const repairResults = await repairAllMarketplaces();
|
|
1056
|
-
|
|
1057
|
-
return {
|
|
1058
|
-
refresh: [],
|
|
1059
|
-
repair: repairResults,
|
|
1060
|
-
};
|
|
1061
1059
|
}
|
|
1062
1060
|
|
|
1063
1061
|
// Re-export types for consumers
|