@f5xc-salesdemos/xcsh 18.91.3 → 18.92.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.
Files changed (34) hide show
  1. package/package.json +8 -8
  2. package/scripts/generate-branding-index.ts +4 -1
  3. package/scripts/generate-terraform-index.ts +1 -0
  4. package/src/capability/context-file.ts +1 -1
  5. package/src/capability/types.ts +2 -2
  6. package/src/config/settings-schema.ts +5 -5
  7. package/src/config/settings.ts +1 -1
  8. package/src/config.ts +4 -5
  9. package/src/discovery/claude-plugins.ts +4 -4
  10. package/src/discovery/claude.ts +15 -15
  11. package/src/discovery/helpers.ts +12 -67
  12. package/src/extensibility/plugins/marketplace/fetcher.ts +2 -2
  13. package/src/extensibility/plugins/marketplace/manager.ts +4 -4
  14. package/src/extensibility/plugins/marketplace/source-resolver.ts +1 -1
  15. package/src/internal-urls/branding-index.generated.ts +42 -53
  16. package/src/internal-urls/build-info.generated.ts +8 -8
  17. package/src/internal-urls/terraform-index.generated.ts +1380 -1906
  18. package/src/internal-urls/terraform-resolve.ts +26 -41
  19. package/src/lsp/config.ts +4 -4
  20. package/src/main.ts +0 -1
  21. package/src/modes/components/plugins/index.ts +5 -0
  22. package/src/modes/components/plugins/plugin-dashboard.ts +449 -0
  23. package/src/modes/components/plugins/plugin-inspector-pane.ts +84 -0
  24. package/src/modes/components/plugins/plugin-list-pane.ts +104 -0
  25. package/src/modes/components/plugins/state-manager.ts +211 -0
  26. package/src/modes/components/plugins/types.ts +42 -0
  27. package/src/modes/controllers/mcp-command-controller.ts +1 -1
  28. package/src/modes/controllers/selector-controller.ts +15 -1
  29. package/src/modes/interactive-mode.ts +4 -0
  30. package/src/modes/types.ts +1 -0
  31. package/src/sdk.ts +2 -2
  32. package/src/slash-commands/builtin-registry.ts +18 -5
  33. package/src/task/commands.ts +1 -1
  34. 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.3",
4
+ "version": "18.92.0",
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,15 +50,15 @@
50
50
  "dependencies": {
51
51
  "@agentclientprotocol/sdk": "0.16.1",
52
52
  "@mozilla/readability": "^0.6",
53
- "@f5xc-salesdemos/xcsh-stats": "18.91.3",
54
- "@f5xc-salesdemos/pi-agent-core": "18.91.3",
55
- "@f5xc-salesdemos/pi-ai": "18.91.3",
56
- "@f5xc-salesdemos/pi-natives": "18.91.3",
57
- "@f5xc-salesdemos/pi-tui": "18.91.3",
58
- "@f5xc-salesdemos/pi-utils": "18.91.3",
53
+ "@f5xc-salesdemos/xcsh-stats": "18.92.0",
54
+ "@f5xc-salesdemos/pi-agent-core": "18.92.0",
55
+ "@f5xc-salesdemos/pi-ai": "18.92.0",
56
+ "@f5xc-salesdemos/pi-natives": "18.92.0",
57
+ "@f5xc-salesdemos/pi-tui": "18.92.0",
58
+ "@f5xc-salesdemos/pi-utils": "18.92.0",
59
59
  "@sinclair/typebox": "^0.34",
60
60
  "@xterm/headless": "^6.0",
61
- "ajv": "^8.18",
61
+ "ajv": "^8.20",
62
62
  "chalk": "^5.6",
63
63
  "diff": "^8.0",
64
64
  "fflate": "0.8.2",
@@ -61,7 +61,9 @@ function generateTypeScript(config: BrandingConfig): string {
61
61
  "",
62
62
  `export const BRANDING_CANONICAL = ${JSON.stringify(config.canonical, null, 2)} as const;`,
63
63
  "",
64
- `export const BRANDING_DEPRECATIONS = ${JSON.stringify(config.deprecations, null, 2)} as const;`,
64
+ config.deprecations
65
+ ? `export const BRANDING_DEPRECATIONS = ${JSON.stringify(config.deprecations, null, 2)} as const;`
66
+ : "export const BRANDING_DEPRECATIONS = null;",
65
67
  "",
66
68
  `export const BRANDING_GLOSSARY = ${JSON.stringify(config.glossary, null, 2)} as const;`,
67
69
  "",
@@ -75,4 +77,5 @@ function generateTypeScript(config: BrandingConfig): string {
75
77
  const config = await loadBrandingYaml();
76
78
  const output = generateTypeScript(config);
77
79
  await fs.writeFile(OUTPUT_FILE, output, "utf-8");
80
+ await Bun.$`bunx biome format --write ${OUTPUT_FILE}`.quiet();
78
81
  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. .claude/, .github/)
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)}`,
@@ -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
- * `.claude`, `.codex`, `.gemini`, they simply ask for `load("mcps")` and get back
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 ~/.claude and .claude/") */
40
+ /** Short description for settings UI (e.g., "Load config from ~/.xcsh/ and .xcsh/") */
41
41
  description: string;
42
42
 
43
43
  /**
@@ -1645,7 +1645,7 @@ export const SETTINGS_SCHEMA = {
1645
1645
  ui: {
1646
1646
  tab: "tasks",
1647
1647
  label: "Claude User Skills",
1648
- description: "Load skills from ~/.claude/skills/",
1648
+ description: "Load skills from ~/.xcsh/agent/skills/ (legacy Claude Code compatibility)",
1649
1649
  },
1650
1650
  },
1651
1651
 
@@ -1655,7 +1655,7 @@ export const SETTINGS_SCHEMA = {
1655
1655
  ui: {
1656
1656
  tab: "tasks",
1657
1657
  label: "Claude Project Skills",
1658
- description: "Load skills from .claude/skills/",
1658
+ description: "Load skills from .xcsh/skills/ (legacy Claude Code compatibility)",
1659
1659
  },
1660
1660
  },
1661
1661
 
@@ -1665,7 +1665,7 @@ export const SETTINGS_SCHEMA = {
1665
1665
  ui: {
1666
1666
  tab: "tasks",
1667
1667
  label: "Claude Marketplace Skills",
1668
- description: "Load skills from Claude Code marketplace plugins (~/.claude/plugins/cache/)",
1668
+ description: "Load skills from marketplace plugins (~/.xcsh/plugins/cache/)",
1669
1669
  },
1670
1670
  },
1671
1671
 
@@ -1699,13 +1699,13 @@ export const SETTINGS_SCHEMA = {
1699
1699
  "commands.enableClaudeUser": {
1700
1700
  type: "boolean",
1701
1701
  default: true,
1702
- ui: { tab: "tasks", label: "Claude User Commands", description: "Load commands from ~/.claude/commands/" },
1702
+ ui: { tab: "tasks", label: "Claude User Commands", description: "Load commands from ~/.xcsh/agent/commands/" },
1703
1703
  },
1704
1704
 
1705
1705
  "commands.enableClaudeProject": {
1706
1706
  type: "boolean",
1707
1707
  default: true,
1708
- ui: { tab: "tasks", label: "Claude Project Commands", description: "Load commands from .claude/commands/" },
1708
+ ui: { tab: "tasks", label: "Claude Project Commands", description: "Load commands from .xcsh/commands/" },
1709
1709
  },
1710
1710
 
1711
1711
  // ────────────────────────────────────────────────────────────────────────
@@ -116,7 +116,7 @@ export class Settings {
116
116
 
117
117
  /** Global settings from config.yml */
118
118
  #global: RawSettings = {};
119
- /** Project settings from .claude/settings.yml etc */
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, ~/.claude, ~/.codex, ~/.gemini
258
- * Project-level: .xcsh, .claude, .codex, .gemini
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", ".claude"
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, .claude) - the nearest one found.
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 ~/.claude/plugins/cache/ based on installed_plugins.json registry.
5
- * Priority: 70 (below claude.ts at 80, so user overrides in .claude/ take precedence)
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 .claude/ overrides win
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 (~/.claude/plugins/cache/)",
261
+ description: "Load skills from Claude Code marketplace plugins (~/.xcsh/plugins/cache/)",
262
262
  priority: PRIORITY,
263
263
  load: loadSkills,
264
264
  });
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Claude Code Provider
3
3
  *
4
- * Loads configuration from .claude directories.
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 = ".claude";
34
+ const CONFIG_DIR = ".xcsh";
35
35
 
36
36
  /**
37
- * Get user-level .claude path.
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 .claude path (cwd only).
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, ".claude.json");
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 .claude/skills/ in ancestors
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 .claude.json and .claude/mcp.json",
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 .claude/ directories",
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 .claude/skills/*/SKILL.md",
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 .claude/extensions",
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 .claude/commands/*.md",
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 .claude/hooks/pre/ and .claude/hooks/post/",
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 .claude/tools/",
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 .claude/settings.json",
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 .claude/SYSTEM.md",
540
+ description: "Load system prompt from .xcsh/SYSTEM.md",
541
541
  priority: PRIORITY,
542
542
  load: loadSystemPrompts,
543
543
  });
@@ -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 Claude Code plugin roots from the plugin cache.
723
- * Reads ~/.claude/plugins/installed_plugins.json and ~/.xcsh/plugins/installed_plugins.json,
724
- * and optionally the nearest project-scoped registry resolved from `cwd`.
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
- // ── Claude Code registry ──────────────────────────────────────────────────
742
- const registryPath = path.join(home, ".claude", "plugins", "installed_plugins.json");
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 (!registry) {
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 OMP plugin registry: ${ompRegistryPath}`);
773
+ warnings.push(`Failed to parse plugin registry: ${registryPath}`);
830
774
  }
831
775
  }
832
776
 
833
- // ── Project-scoped OMP registry ────────────────────────────────────────
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 (resolvedProjectPath) {
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, ".claude-plugin", "plugin.json");
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(".claude-plugin", "marketplace.json");
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 .claude-plugin/marketplace.json file.`,
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 .claude-plugin/
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 (.claude-plugin/plugin.json or package.json)
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, ".claude-plugin", "plugin.json"),
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 `.claude-plugin/marketplace.json`,
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 .claude-plugin/ catalog subdirectory)
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)) {