@f5xc-salesdemos/xcsh 18.91.2 → 18.91.4
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 +7 -7
- package/scripts/generate-branding-index.ts +1 -0
- package/scripts/generate-terraform-index.ts +1 -0
- package/src/capability/context-file.ts +1 -1
- package/src/capability/index.ts +3 -1
- package/src/capability/types.ts +2 -2
- package/src/config/settings-schema.ts +24 -6
- package/src/config/settings.ts +1 -1
- package/src/config.ts +4 -5
- package/src/discovery/claude-plugins.ts +4 -4
- package/src/discovery/claude.ts +15 -15
- package/src/discovery/helpers.ts +12 -67
- package/src/extensibility/plugins/marketplace/fetcher.ts +2 -2
- package/src/extensibility/plugins/marketplace/manager.ts +4 -4
- package/src/extensibility/plugins/marketplace/source-resolver.ts +1 -1
- package/src/internal-urls/branding-index.generated.ts +42 -53
- package/src/internal-urls/build-info.generated.ts +8 -8
- package/src/internal-urls/terraform-index.generated.ts +1380 -1906
- package/src/lsp/config.ts +4 -4
- package/src/main.ts +0 -1
- package/src/modes/controllers/mcp-command-controller.ts +1 -1
- package/src/modes/controllers/selector-controller.ts +0 -1
- package/src/sdk.ts +2 -2
- package/src/slash-commands/builtin-registry.ts +0 -3
- package/src/task/commands.ts +1 -1
- package/src/task/discovery.ts +4 -4
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5xc-salesdemos/xcsh",
|
|
4
|
-
"version": "18.91.
|
|
4
|
+
"version": "18.91.4",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/f5xc-salesdemos/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -50,12 +50,12 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
52
52
|
"@mozilla/readability": "^0.6",
|
|
53
|
-
"@f5xc-salesdemos/xcsh-stats": "18.91.
|
|
54
|
-
"@f5xc-salesdemos/pi-agent-core": "18.91.
|
|
55
|
-
"@f5xc-salesdemos/pi-ai": "18.91.
|
|
56
|
-
"@f5xc-salesdemos/pi-natives": "18.91.
|
|
57
|
-
"@f5xc-salesdemos/pi-tui": "18.91.
|
|
58
|
-
"@f5xc-salesdemos/pi-utils": "18.91.
|
|
53
|
+
"@f5xc-salesdemos/xcsh-stats": "18.91.4",
|
|
54
|
+
"@f5xc-salesdemos/pi-agent-core": "18.91.4",
|
|
55
|
+
"@f5xc-salesdemos/pi-ai": "18.91.4",
|
|
56
|
+
"@f5xc-salesdemos/pi-natives": "18.91.4",
|
|
57
|
+
"@f5xc-salesdemos/pi-tui": "18.91.4",
|
|
58
|
+
"@f5xc-salesdemos/pi-utils": "18.91.4",
|
|
59
59
|
"@sinclair/typebox": "^0.34",
|
|
60
60
|
"@xterm/headless": "^6.0",
|
|
61
61
|
"ajv": "^8.18",
|
|
@@ -75,4 +75,5 @@ function generateTypeScript(config: BrandingConfig): string {
|
|
|
75
75
|
const config = await loadBrandingYaml();
|
|
76
76
|
const output = generateTypeScript(config);
|
|
77
77
|
await fs.writeFile(OUTPUT_FILE, output, "utf-8");
|
|
78
|
+
await Bun.$`bunx biome format --write ${OUTPUT_FILE}`.quiet();
|
|
78
79
|
console.log(`Generated ${OUTPUT_FILE}`);
|
|
@@ -53,4 +53,5 @@ function generateTypeScript(data: unknown): string {
|
|
|
53
53
|
const data = await loadTerraformIndex();
|
|
54
54
|
const output = generateTypeScript(data);
|
|
55
55
|
await fs.writeFile(OUTPUT_FILE, output, "utf-8");
|
|
56
|
+
await Bun.$`bunx biome format --write ${OUTPUT_FILE}`.quiet();
|
|
56
57
|
console.log(`Generated ${OUTPUT_FILE}`);
|
|
@@ -31,7 +31,7 @@ export const contextFileCapability = defineCapability<ContextFile>({
|
|
|
31
31
|
// Deduplicate by scope: one user-level file, and one project-level file per directory depth.
|
|
32
32
|
// Within each depth level, higher-priority providers shadow lower-priority ones.
|
|
33
33
|
// This supports monorepo hierarchies where AGENTS.md exists at multiple ancestor levels.
|
|
34
|
-
// Clamp depth >= 0: files inside config subdirectories of an ancestor (e.g. .
|
|
34
|
+
// Clamp depth >= 0: files inside config subdirectories of an ancestor (e.g. .xcsh/, .github/)
|
|
35
35
|
// are same-scope as the ancestor itself.
|
|
36
36
|
key: file => (file.level === "user" ? "user" : `project:${Math.max(0, file.depth ?? 0)}`),
|
|
37
37
|
toExtensionId: file => `context-file:${file.level}:${path.basename(file.path)}`,
|
package/src/capability/index.ts
CHANGED
|
@@ -208,11 +208,13 @@ async function loadImpl<T>(
|
|
|
208
208
|
* Filter providers based on options and disabled state.
|
|
209
209
|
*/
|
|
210
210
|
function filterProviders<T>(capability: Capability<T>, options: LoadOptions): Provider<T>[] {
|
|
211
|
-
let providers =
|
|
211
|
+
let providers = capability.providers as Provider<T>[];
|
|
212
212
|
|
|
213
213
|
if (options.providers) {
|
|
214
214
|
const allowed = new Set(options.providers);
|
|
215
215
|
providers = providers.filter(p => allowed.has(p.id));
|
|
216
|
+
} else {
|
|
217
|
+
providers = providers.filter(p => !disabledProviders.has(p.id));
|
|
216
218
|
}
|
|
217
219
|
if (options.excludeProviders) {
|
|
218
220
|
const excluded = new Set(options.excludeProviders);
|
package/src/capability/types.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Core types for the capability-based config discovery system.
|
|
3
3
|
*
|
|
4
4
|
* This architecture inverts control: instead of callers knowing about paths like
|
|
5
|
-
* `.
|
|
5
|
+
* `.xcsh`, `.codex`, `.gemini`, they simply ask for `load("mcps")` and get back
|
|
6
6
|
* a unified array of MCP servers.
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -37,7 +37,7 @@ export interface Provider<T> {
|
|
|
37
37
|
/** Human-readable name for UI display (e.g., "Claude Code", "OpenAI Codex") */
|
|
38
38
|
displayName: string;
|
|
39
39
|
|
|
40
|
-
/** Short description for settings UI (e.g., "Load config from ~/.
|
|
40
|
+
/** Short description for settings UI (e.g., "Load config from ~/.xcsh/ and .xcsh/") */
|
|
41
41
|
description: string;
|
|
42
42
|
|
|
43
43
|
/**
|
|
@@ -224,7 +224,25 @@ export const SETTINGS_SCHEMA = {
|
|
|
224
224
|
|
|
225
225
|
enabledModels: { type: "array", default: EMPTY_STRING_ARRAY },
|
|
226
226
|
|
|
227
|
-
disabledProviders: {
|
|
227
|
+
disabledProviders: {
|
|
228
|
+
type: "array",
|
|
229
|
+
default: [
|
|
230
|
+
"claude",
|
|
231
|
+
"claude-plugins",
|
|
232
|
+
"codex",
|
|
233
|
+
"agents",
|
|
234
|
+
"agents-md",
|
|
235
|
+
"gemini",
|
|
236
|
+
"opencode",
|
|
237
|
+
"cursor",
|
|
238
|
+
"windsurf",
|
|
239
|
+
"cline",
|
|
240
|
+
"github",
|
|
241
|
+
"vscode",
|
|
242
|
+
"mcp-json",
|
|
243
|
+
"ssh-json",
|
|
244
|
+
] as string[],
|
|
245
|
+
},
|
|
228
246
|
|
|
229
247
|
disabledExtensions: { type: "array", default: EMPTY_STRING_ARRAY },
|
|
230
248
|
|
|
@@ -1627,7 +1645,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
1627
1645
|
ui: {
|
|
1628
1646
|
tab: "tasks",
|
|
1629
1647
|
label: "Claude User Skills",
|
|
1630
|
-
description: "Load skills from ~/.
|
|
1648
|
+
description: "Load skills from ~/.xcsh/agent/skills/ (legacy Claude Code compatibility)",
|
|
1631
1649
|
},
|
|
1632
1650
|
},
|
|
1633
1651
|
|
|
@@ -1637,7 +1655,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
1637
1655
|
ui: {
|
|
1638
1656
|
tab: "tasks",
|
|
1639
1657
|
label: "Claude Project Skills",
|
|
1640
|
-
description: "Load skills from .
|
|
1658
|
+
description: "Load skills from .xcsh/skills/ (legacy Claude Code compatibility)",
|
|
1641
1659
|
},
|
|
1642
1660
|
},
|
|
1643
1661
|
|
|
@@ -1647,7 +1665,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
1647
1665
|
ui: {
|
|
1648
1666
|
tab: "tasks",
|
|
1649
1667
|
label: "Claude Marketplace Skills",
|
|
1650
|
-
description: "Load skills from
|
|
1668
|
+
description: "Load skills from marketplace plugins (~/.xcsh/plugins/cache/)",
|
|
1651
1669
|
},
|
|
1652
1670
|
},
|
|
1653
1671
|
|
|
@@ -1681,13 +1699,13 @@ export const SETTINGS_SCHEMA = {
|
|
|
1681
1699
|
"commands.enableClaudeUser": {
|
|
1682
1700
|
type: "boolean",
|
|
1683
1701
|
default: true,
|
|
1684
|
-
ui: { tab: "tasks", label: "Claude User Commands", description: "Load commands from ~/.
|
|
1702
|
+
ui: { tab: "tasks", label: "Claude User Commands", description: "Load commands from ~/.xcsh/agent/commands/" },
|
|
1685
1703
|
},
|
|
1686
1704
|
|
|
1687
1705
|
"commands.enableClaudeProject": {
|
|
1688
1706
|
type: "boolean",
|
|
1689
1707
|
default: true,
|
|
1690
|
-
ui: { tab: "tasks", label: "Claude Project Commands", description: "Load commands from .
|
|
1708
|
+
ui: { tab: "tasks", label: "Claude Project Commands", description: "Load commands from .xcsh/commands/" },
|
|
1691
1709
|
},
|
|
1692
1710
|
|
|
1693
1711
|
// ────────────────────────────────────────────────────────────────────────
|
package/src/config/settings.ts
CHANGED
|
@@ -116,7 +116,7 @@ export class Settings {
|
|
|
116
116
|
|
|
117
117
|
/** Global settings from config.yml */
|
|
118
118
|
#global: RawSettings = {};
|
|
119
|
-
/** Project settings from .
|
|
119
|
+
/** Project settings from .xcsh/settings.yml etc */
|
|
120
120
|
#project: RawSettings = {};
|
|
121
121
|
/** Runtime overrides (not persisted) */
|
|
122
122
|
#overrides: RawSettings = {};
|
package/src/config.ts
CHANGED
|
@@ -17,7 +17,6 @@ import { expandTilde } from "./tools/path-utils";
|
|
|
17
17
|
|
|
18
18
|
const priorityList = [
|
|
19
19
|
{ dir: CONFIG_DIR_NAME, globalAgentDir: getConfigAgentDirName },
|
|
20
|
-
{ dir: ".claude" },
|
|
21
20
|
{ dir: ".codex" },
|
|
22
21
|
{ dir: ".gemini" },
|
|
23
22
|
];
|
|
@@ -254,8 +253,8 @@ export class ConfigFile<T> implements IConfigFile<T> {
|
|
|
254
253
|
|
|
255
254
|
/**
|
|
256
255
|
* Config directory bases in priority order (highest first).
|
|
257
|
-
* User-level: ~/.xcsh/agent, ~/.
|
|
258
|
-
* Project-level: .xcsh, .
|
|
256
|
+
* User-level: ~/.xcsh/agent, ~/.codex, ~/.gemini
|
|
257
|
+
* Project-level: .xcsh, .codex, .gemini
|
|
259
258
|
*/
|
|
260
259
|
const USER_CONFIG_BASES = priorityList.map(({ dir, globalAgentDir }) => ({
|
|
261
260
|
base: () => path.join(os.homedir(), globalAgentDir ? globalAgentDir() : dir),
|
|
@@ -269,7 +268,7 @@ const PROJECT_CONFIG_BASES = priorityList.map(({ dir }) => ({
|
|
|
269
268
|
|
|
270
269
|
export interface ConfigDirEntry {
|
|
271
270
|
path: string;
|
|
272
|
-
source: string; // e.g., ".xcsh", ".
|
|
271
|
+
source: string; // e.g., ".xcsh", ".codex"
|
|
273
272
|
level: "user" | "project";
|
|
274
273
|
}
|
|
275
274
|
|
|
@@ -384,7 +383,7 @@ export function findConfigFileWithMeta(
|
|
|
384
383
|
|
|
385
384
|
/**
|
|
386
385
|
* Find all nearest config directories by walking up from cwd.
|
|
387
|
-
* Returns one entry per config base (.xcsh, .
|
|
386
|
+
* Returns one entry per config base (.xcsh, .codex, .gemini) - the nearest one found.
|
|
388
387
|
* Results are in priority order (highest first).
|
|
389
388
|
*/
|
|
390
389
|
export function findAllNearestProjectConfigDirs(subpath: string, cwd: string = getProjectDir()): ConfigDirEntry[] {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Claude Code Marketplace Plugin Provider
|
|
3
3
|
*
|
|
4
|
-
* Loads configuration from ~/.
|
|
5
|
-
* Priority: 70 (below claude.ts at 80, so user overrides in .
|
|
4
|
+
* Loads configuration from ~/.xcsh/plugins/cache/ based on installed_plugins.json registry.
|
|
5
|
+
* Priority: 70 (below claude.ts at 80, so user overrides in .xcsh/ take precedence)
|
|
6
6
|
*/
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import { logger } from "@f5xc-salesdemos/pi-utils";
|
|
@@ -26,7 +26,7 @@ import { substitutePluginRoot } from "./substitute-plugin-root";
|
|
|
26
26
|
|
|
27
27
|
const PROVIDER_ID = "claude-plugins";
|
|
28
28
|
const DISPLAY_NAME = "Claude Code Marketplace";
|
|
29
|
-
const PRIORITY = 70; // Below claude.ts (80) so user .
|
|
29
|
+
const PRIORITY = 70; // Below claude.ts (80) so user .xcsh/ overrides win
|
|
30
30
|
|
|
31
31
|
// =============================================================================
|
|
32
32
|
// Skills
|
|
@@ -258,7 +258,7 @@ async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>>
|
|
|
258
258
|
registerProvider<Skill>(skillCapability.id, {
|
|
259
259
|
id: PROVIDER_ID,
|
|
260
260
|
displayName: DISPLAY_NAME,
|
|
261
|
-
description: "Load skills from Claude Code marketplace plugins (~/.
|
|
261
|
+
description: "Load skills from Claude Code marketplace plugins (~/.xcsh/plugins/cache/)",
|
|
262
262
|
priority: PRIORITY,
|
|
263
263
|
load: loadSkills,
|
|
264
264
|
});
|
package/src/discovery/claude.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Claude Code Provider
|
|
3
3
|
*
|
|
4
|
-
* Loads configuration from .
|
|
4
|
+
* Loads configuration from .xcsh directories.
|
|
5
5
|
* Priority: 80 (tool-specific, below builtin but above shared standards)
|
|
6
6
|
*/
|
|
7
7
|
import * as path from "node:path";
|
|
@@ -31,17 +31,17 @@ import {
|
|
|
31
31
|
const PROVIDER_ID = "claude";
|
|
32
32
|
const DISPLAY_NAME = "Claude Code";
|
|
33
33
|
const PRIORITY = 80;
|
|
34
|
-
const CONFIG_DIR = ".
|
|
34
|
+
const CONFIG_DIR = ".xcsh";
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
|
-
* Get user-level .
|
|
37
|
+
* Get user-level .xcsh path.
|
|
38
38
|
*/
|
|
39
39
|
function getUserClaude(ctx: LoadContext): string {
|
|
40
40
|
return path.join(ctx.home, CONFIG_DIR);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
* Get project-level .
|
|
44
|
+
* Get project-level .xcsh path (cwd only).
|
|
45
45
|
*/
|
|
46
46
|
function getProjectClaude(ctx: LoadContext): string {
|
|
47
47
|
return path.join(ctx.cwd, CONFIG_DIR);
|
|
@@ -60,7 +60,7 @@ async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>>
|
|
|
60
60
|
const warnings: string[] = [];
|
|
61
61
|
|
|
62
62
|
const userBase = getUserClaude(ctx);
|
|
63
|
-
const userClaudeJson = path.join(ctx.home, ".
|
|
63
|
+
const userClaudeJson = path.join(ctx.home, ".xcsh.json");
|
|
64
64
|
const userMcpJson = path.join(userBase, "mcp.json");
|
|
65
65
|
|
|
66
66
|
const projectBase = path.join(ctx.cwd, CONFIG_DIR);
|
|
@@ -166,7 +166,7 @@ async function loadContextFiles(ctx: LoadContext): Promise<LoadResult<ContextFil
|
|
|
166
166
|
async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
|
|
167
167
|
const userSkillsDir = path.join(getUserClaude(ctx), "skills");
|
|
168
168
|
|
|
169
|
-
// Walk up from cwd finding .
|
|
169
|
+
// Walk up from cwd finding .xcsh/skills/ in ancestors
|
|
170
170
|
const projectScans: Promise<LoadResult<Skill>>[] = [];
|
|
171
171
|
let current = ctx.cwd;
|
|
172
172
|
while (true) {
|
|
@@ -473,7 +473,7 @@ async function loadSettings(ctx: LoadContext): Promise<LoadResult<Settings>> {
|
|
|
473
473
|
registerProvider<MCPServer>(mcpCapability.id, {
|
|
474
474
|
id: PROVIDER_ID,
|
|
475
475
|
displayName: DISPLAY_NAME,
|
|
476
|
-
description: "Load MCP servers from .
|
|
476
|
+
description: "Load MCP servers from .xcsh.json and .xcsh/mcp.json",
|
|
477
477
|
priority: PRIORITY,
|
|
478
478
|
load: loadMCPServers,
|
|
479
479
|
});
|
|
@@ -481,7 +481,7 @@ registerProvider<MCPServer>(mcpCapability.id, {
|
|
|
481
481
|
registerProvider<ContextFile>(contextFileCapability.id, {
|
|
482
482
|
id: PROVIDER_ID,
|
|
483
483
|
displayName: DISPLAY_NAME,
|
|
484
|
-
description: "Load CLAUDE.md files from .
|
|
484
|
+
description: "Load CLAUDE.md files from .xcsh/ directories",
|
|
485
485
|
priority: PRIORITY,
|
|
486
486
|
load: loadContextFiles,
|
|
487
487
|
});
|
|
@@ -489,7 +489,7 @@ registerProvider<ContextFile>(contextFileCapability.id, {
|
|
|
489
489
|
registerProvider<Skill>(skillCapability.id, {
|
|
490
490
|
id: PROVIDER_ID,
|
|
491
491
|
displayName: DISPLAY_NAME,
|
|
492
|
-
description: "Load skills from .
|
|
492
|
+
description: "Load skills from .xcsh/skills/*/SKILL.md",
|
|
493
493
|
priority: PRIORITY,
|
|
494
494
|
load: loadSkills,
|
|
495
495
|
});
|
|
@@ -497,7 +497,7 @@ registerProvider<Skill>(skillCapability.id, {
|
|
|
497
497
|
registerProvider<ExtensionModule>(extensionModuleCapability.id, {
|
|
498
498
|
id: PROVIDER_ID,
|
|
499
499
|
displayName: DISPLAY_NAME,
|
|
500
|
-
description: "Load extension modules from .
|
|
500
|
+
description: "Load extension modules from .xcsh/extensions",
|
|
501
501
|
priority: PRIORITY,
|
|
502
502
|
load: loadExtensionModules,
|
|
503
503
|
});
|
|
@@ -505,7 +505,7 @@ registerProvider<ExtensionModule>(extensionModuleCapability.id, {
|
|
|
505
505
|
registerProvider<SlashCommand>(slashCommandCapability.id, {
|
|
506
506
|
id: PROVIDER_ID,
|
|
507
507
|
displayName: DISPLAY_NAME,
|
|
508
|
-
description: "Load slash commands from .
|
|
508
|
+
description: "Load slash commands from .xcsh/commands/*.md",
|
|
509
509
|
priority: PRIORITY,
|
|
510
510
|
load: loadSlashCommands,
|
|
511
511
|
});
|
|
@@ -513,7 +513,7 @@ registerProvider<SlashCommand>(slashCommandCapability.id, {
|
|
|
513
513
|
registerProvider<Hook>(hookCapability.id, {
|
|
514
514
|
id: PROVIDER_ID,
|
|
515
515
|
displayName: DISPLAY_NAME,
|
|
516
|
-
description: "Load hooks from .
|
|
516
|
+
description: "Load hooks from .xcsh/hooks/pre/ and .xcsh/hooks/post/",
|
|
517
517
|
priority: PRIORITY,
|
|
518
518
|
load: loadHooks,
|
|
519
519
|
});
|
|
@@ -521,7 +521,7 @@ registerProvider<Hook>(hookCapability.id, {
|
|
|
521
521
|
registerProvider<CustomTool>(toolCapability.id, {
|
|
522
522
|
id: PROVIDER_ID,
|
|
523
523
|
displayName: DISPLAY_NAME,
|
|
524
|
-
description: "Load custom tools from .
|
|
524
|
+
description: "Load custom tools from .xcsh/tools/",
|
|
525
525
|
priority: PRIORITY,
|
|
526
526
|
load: loadTools,
|
|
527
527
|
});
|
|
@@ -529,7 +529,7 @@ registerProvider<CustomTool>(toolCapability.id, {
|
|
|
529
529
|
registerProvider<Settings>(settingsCapability.id, {
|
|
530
530
|
id: PROVIDER_ID,
|
|
531
531
|
displayName: DISPLAY_NAME,
|
|
532
|
-
description: "Load settings from .
|
|
532
|
+
description: "Load settings from .xcsh/settings.json",
|
|
533
533
|
priority: PRIORITY,
|
|
534
534
|
load: loadSettings,
|
|
535
535
|
});
|
|
@@ -537,7 +537,7 @@ registerProvider<Settings>(settingsCapability.id, {
|
|
|
537
537
|
registerProvider<SystemPrompt>(systemPromptCapability.id, {
|
|
538
538
|
id: PROVIDER_ID,
|
|
539
539
|
displayName: DISPLAY_NAME,
|
|
540
|
-
description: "Load system prompt from .
|
|
540
|
+
description: "Load system prompt from .xcsh/SYSTEM.md",
|
|
541
541
|
priority: PRIORITY,
|
|
542
542
|
load: loadSystemPrompts,
|
|
543
543
|
});
|
package/src/discovery/helpers.ts
CHANGED
|
@@ -31,11 +31,6 @@ export const SOURCE_PATHS = {
|
|
|
31
31
|
},
|
|
32
32
|
projectDir: CONFIG_DIR_NAME,
|
|
33
33
|
},
|
|
34
|
-
claude: {
|
|
35
|
-
userBase: ".claude",
|
|
36
|
-
userAgent: ".claude",
|
|
37
|
-
projectDir: ".claude",
|
|
38
|
-
},
|
|
39
34
|
codex: {
|
|
40
35
|
userBase: ".codex",
|
|
41
36
|
userAgent: ".codex",
|
|
@@ -719,9 +714,9 @@ export async function resolveOrDefaultProjectRegistryPath(cwd: string): Promise<
|
|
|
719
714
|
const pluginRootsCache = new Map<string, { roots: ClaudePluginRoot[]; warnings: string[] }>();
|
|
720
715
|
|
|
721
716
|
/**
|
|
722
|
-
* List all installed
|
|
723
|
-
* Reads ~/.
|
|
724
|
-
*
|
|
717
|
+
* List all installed marketplace plugin roots from the plugin cache.
|
|
718
|
+
* Reads ~/.xcsh/plugins/installed_plugins.json and optionally the nearest
|
|
719
|
+
* project-scoped registry resolved from `cwd`.
|
|
725
720
|
*
|
|
726
721
|
* Results are cached per `home:resolvedProjectPath` key to avoid repeated parsing.
|
|
727
722
|
*/
|
|
@@ -738,81 +733,30 @@ export async function listClaudePluginRoots(
|
|
|
738
733
|
const warnings: string[] = [];
|
|
739
734
|
const projectRoots: ClaudePluginRoot[] = [];
|
|
740
735
|
|
|
741
|
-
// ──
|
|
742
|
-
|
|
736
|
+
// ── Installed plugins registry ───────────────────────────────────────────
|
|
737
|
+
// Path derived from `home` (not os.homedir()) so test isolation works when home is overridden.
|
|
738
|
+
const registryPath = path.join(home, getConfigDirName(), "plugins", "installed_plugins.json");
|
|
743
739
|
const content = await readFile(registryPath);
|
|
744
|
-
|
|
745
740
|
if (content) {
|
|
746
741
|
const registry = parseClaudePluginsRegistry(content);
|
|
747
|
-
if (
|
|
748
|
-
warnings.push(`Failed to parse Claude Code plugin registry: ${registryPath}`);
|
|
749
|
-
} else {
|
|
742
|
+
if (registry) {
|
|
750
743
|
for (const [pluginId, entries] of Object.entries(registry.plugins)) {
|
|
751
744
|
if (!Array.isArray(entries) || entries.length === 0) continue;
|
|
752
745
|
|
|
753
|
-
// Parse plugin ID format: "plugin-name@marketplace"
|
|
754
746
|
const atIndex = pluginId.lastIndexOf("@");
|
|
755
747
|
if (atIndex === -1) {
|
|
756
748
|
warnings.push(`Invalid plugin ID format (missing @marketplace): ${pluginId}`);
|
|
757
749
|
continue;
|
|
758
750
|
}
|
|
759
|
-
|
|
760
751
|
const pluginName = pluginId.slice(0, atIndex);
|
|
761
752
|
const marketplace = pluginId.slice(atIndex + 1);
|
|
762
753
|
|
|
763
|
-
// Process all valid entries, not just the first one.
|
|
764
|
-
// This handles plugins with multiple installs (different scopes/versions).
|
|
765
|
-
for (const entry of entries) {
|
|
766
|
-
if (!entry.installPath || typeof entry.installPath !== "string") {
|
|
767
|
-
warnings.push(`Plugin ${pluginId} entry has no installPath`);
|
|
768
|
-
continue;
|
|
769
|
-
}
|
|
770
|
-
if (entry.enabled === false) continue;
|
|
771
|
-
|
|
772
|
-
roots.push({
|
|
773
|
-
id: pluginId,
|
|
774
|
-
marketplace,
|
|
775
|
-
plugin: pluginName,
|
|
776
|
-
version: entry.version || "unknown",
|
|
777
|
-
path: entry.installPath,
|
|
778
|
-
scope: entry.scope || "user",
|
|
779
|
-
});
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
// ── OMP installed plugins registry ───────────────────────────────────────
|
|
786
|
-
// OMP registry is authoritative: its entries replace Claude's entries for the same plugin ID.
|
|
787
|
-
// Path derived from `home` (not os.homedir()) so test isolation works when home is overridden.
|
|
788
|
-
const ompRegistryPath = path.join(home, getConfigDirName(), "plugins", "installed_plugins.json");
|
|
789
|
-
const ompContent = await readFile(ompRegistryPath);
|
|
790
|
-
if (ompContent) {
|
|
791
|
-
const ompRegistry = parseClaudePluginsRegistry(ompContent);
|
|
792
|
-
if (ompRegistry) {
|
|
793
|
-
for (const [pluginId, entries] of Object.entries(ompRegistry.plugins)) {
|
|
794
|
-
if (!Array.isArray(entries) || entries.length === 0) continue;
|
|
795
|
-
|
|
796
|
-
const atIndex = pluginId.lastIndexOf("@");
|
|
797
|
-
if (atIndex === -1) {
|
|
798
|
-
warnings.push(`Invalid plugin ID format (missing @marketplace): ${pluginId}`);
|
|
799
|
-
continue;
|
|
800
|
-
}
|
|
801
|
-
const pluginName = pluginId.slice(0, atIndex);
|
|
802
|
-
const marketplace = pluginId.slice(atIndex + 1);
|
|
803
|
-
|
|
804
|
-
// OMP is authoritative: drop all Claude-sourced entries for this plugin ID
|
|
805
|
-
const filtered = roots.filter(r => r.id !== pluginId);
|
|
806
|
-
roots.length = 0;
|
|
807
|
-
roots.push(...filtered);
|
|
808
|
-
|
|
809
754
|
for (const entry of entries) {
|
|
810
755
|
if (!entry.installPath || typeof entry.installPath !== "string") {
|
|
811
756
|
warnings.push(`Plugin ${pluginId} entry has no installPath`);
|
|
812
757
|
continue;
|
|
813
758
|
}
|
|
814
759
|
if (entry.enabled === false) continue;
|
|
815
|
-
// Deduplicate by installPath within same ID
|
|
816
760
|
if (roots.some(r => r.id === pluginId && r.path === entry.installPath)) continue;
|
|
817
761
|
|
|
818
762
|
roots.push({
|
|
@@ -826,14 +770,15 @@ export async function listClaudePluginRoots(
|
|
|
826
770
|
}
|
|
827
771
|
}
|
|
828
772
|
} else {
|
|
829
|
-
warnings.push(`Failed to parse
|
|
773
|
+
warnings.push(`Failed to parse plugin registry: ${registryPath}`);
|
|
830
774
|
}
|
|
831
775
|
}
|
|
832
776
|
|
|
833
|
-
// ── Project-scoped
|
|
777
|
+
// ── Project-scoped registry ──────────────────────────────────────────
|
|
834
778
|
// Loaded from the nearest .xcsh/plugins/installed_plugins.json relative to cwd.
|
|
835
779
|
// Project entries take precedence over user entries for the same plugin ID.
|
|
836
|
-
if (
|
|
780
|
+
// Skip if the project registry is the same file as the user registry (home === cwd).
|
|
781
|
+
if (resolvedProjectPath && resolvedProjectPath !== registryPath) {
|
|
837
782
|
const projectContent = await readFile(resolvedProjectPath);
|
|
838
783
|
if (projectContent) {
|
|
839
784
|
const projectRegistry = parseClaudePluginsRegistry(projectContent);
|
|
@@ -943,7 +888,7 @@ export async function injectPluginDirRoots(home: string, dirs: string[], cwd?: s
|
|
|
943
888
|
// Read plugin name from manifest
|
|
944
889
|
let pluginName = path.basename(resolved);
|
|
945
890
|
try {
|
|
946
|
-
const manifestPath = path.join(resolved, ".
|
|
891
|
+
const manifestPath = path.join(resolved, ".xcsh-plugin", "plugin.json");
|
|
947
892
|
const content = await Bun.file(manifestPath).text();
|
|
948
893
|
const manifest = JSON.parse(content);
|
|
949
894
|
if (typeof manifest.name === "string" && manifest.name) {
|
|
@@ -193,7 +193,7 @@ export function parseMarketplaceCatalog(content: string, filePath: string): Mark
|
|
|
193
193
|
// ── fetchMarketplace ──────────────────────────────────────────────────
|
|
194
194
|
|
|
195
195
|
/** Relative path from a marketplace root to its catalog file. */
|
|
196
|
-
const CATALOG_RELATIVE_PATH = path.join(".
|
|
196
|
+
const CATALOG_RELATIVE_PATH = path.join(".xcsh-plugin", "marketplace.json");
|
|
197
197
|
|
|
198
198
|
/**
|
|
199
199
|
* Expand a `~/...` path to an absolute path using os.homedir().
|
|
@@ -229,7 +229,7 @@ export async function fetchMarketplace(source: string, cacheDir: string): Promis
|
|
|
229
229
|
if (isEnoent(err)) {
|
|
230
230
|
throw new Error(
|
|
231
231
|
`Marketplace catalog not found at "${catalogPath}". ` +
|
|
232
|
-
`Ensure the directory exists and contains a .
|
|
232
|
+
`Ensure the directory exists and contains a .xcsh-plugin/marketplace.json file.`,
|
|
233
233
|
);
|
|
234
234
|
}
|
|
235
235
|
throw err;
|
|
@@ -257,7 +257,7 @@ export class MarketplaceManager {
|
|
|
257
257
|
}
|
|
258
258
|
|
|
259
259
|
// 4. Resolve source path.
|
|
260
|
-
// marketplaceClonePath is the marketplace root — the directory containing .
|
|
260
|
+
// marketplaceClonePath is the marketplace root — the directory containing .xcsh-plugin/
|
|
261
261
|
// catalogPath is <marketplacesCacheDir>/<name>/marketplace.json, so the root is two levels up.
|
|
262
262
|
// For local sources the content was fetched from a local path; the stored catalog is a copy
|
|
263
263
|
// under marketplacesCacheDir. We need the original source root for resolving relative paths.
|
|
@@ -345,7 +345,7 @@ export class MarketplaceManager {
|
|
|
345
345
|
/**
|
|
346
346
|
* Resolve plugin version from multiple sources:
|
|
347
347
|
* 1. Catalog entry version (if set)
|
|
348
|
-
* 2. Plugin manifest (.
|
|
348
|
+
* 2. Plugin manifest (.xcsh-plugin/plugin.json or package.json)
|
|
349
349
|
* 3. Git SHA from source (truncated to 7 chars)
|
|
350
350
|
* 4. Fallback "0.0.0"
|
|
351
351
|
*/
|
|
@@ -355,7 +355,7 @@ export class MarketplaceManager {
|
|
|
355
355
|
|
|
356
356
|
// 2. Plugin manifest
|
|
357
357
|
for (const manifestPath of [
|
|
358
|
-
path.join(sourcePath, ".
|
|
358
|
+
path.join(sourcePath, ".xcsh-plugin", "plugin.json"),
|
|
359
359
|
path.join(sourcePath, "package.json"),
|
|
360
360
|
]) {
|
|
361
361
|
try {
|
|
@@ -730,7 +730,7 @@ export class MarketplaceManager {
|
|
|
730
730
|
* Compute the marketplace root directory for source resolution.
|
|
731
731
|
*
|
|
732
732
|
* For local sources: sourceUri IS the local path, so resolve it directly.
|
|
733
|
-
* This gives the directory containing `.
|
|
733
|
+
* This gives the directory containing `.xcsh-plugin/marketplace.json`,
|
|
734
734
|
* which is what resolvePluginSource expects as `marketplaceClonePath`.
|
|
735
735
|
*
|
|
736
736
|
* For remote sources (git/github/url): the catalog was cloned into
|
|
@@ -63,7 +63,7 @@ async function resolveRelativeSource(
|
|
|
63
63
|
const pluginRoot = context.catalogMetadata?.pluginRoot;
|
|
64
64
|
const relativePath = pluginRoot ? `./${path.join(pluginRoot, source.slice(2))}` : source;
|
|
65
65
|
|
|
66
|
-
// Resolve against marketplace root (not the .
|
|
66
|
+
// Resolve against marketplace root (not the .xcsh-plugin/ catalog subdirectory)
|
|
67
67
|
const resolved = path.resolve(context.marketplaceClonePath, relativePath);
|
|
68
68
|
|
|
69
69
|
if (!pathIsWithin(context.marketplaceClonePath, resolved)) {
|