cray-code 1.0.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 (252) hide show
  1. package/README.md +316 -0
  2. package/dist/Tool.d.ts +217 -0
  3. package/dist/Tool.d.ts.map +1 -0
  4. package/dist/Tool.js +89 -0
  5. package/dist/Tool.js.map +1 -0
  6. package/dist/branding/logo.d.ts +8 -0
  7. package/dist/branding/logo.d.ts.map +1 -0
  8. package/dist/branding/logo.js +26 -0
  9. package/dist/branding/logo.js.map +1 -0
  10. package/dist/branding/theme.d.ts +27 -0
  11. package/dist/branding/theme.d.ts.map +1 -0
  12. package/dist/branding/theme.js +28 -0
  13. package/dist/branding/theme.js.map +1 -0
  14. package/dist/commands/registry.d.ts +32 -0
  15. package/dist/commands/registry.d.ts.map +1 -0
  16. package/dist/commands/registry.js +759 -0
  17. package/dist/commands/registry.js.map +1 -0
  18. package/dist/components/MessageView.d.ts +12 -0
  19. package/dist/components/MessageView.d.ts.map +1 -0
  20. package/dist/components/MessageView.js +35 -0
  21. package/dist/components/MessageView.js.map +1 -0
  22. package/dist/components/PermissionPrompt.d.ts +11 -0
  23. package/dist/components/PermissionPrompt.d.ts.map +1 -0
  24. package/dist/components/PermissionPrompt.js +6 -0
  25. package/dist/components/PermissionPrompt.js.map +1 -0
  26. package/dist/components/PluginManager.d.ts +27 -0
  27. package/dist/components/PluginManager.d.ts.map +1 -0
  28. package/dist/components/PluginManager.js +391 -0
  29. package/dist/components/PluginManager.js.map +1 -0
  30. package/dist/components/ThinkingBlock.d.ts +27 -0
  31. package/dist/components/ThinkingBlock.d.ts.map +1 -0
  32. package/dist/components/ThinkingBlock.js +29 -0
  33. package/dist/components/ThinkingBlock.js.map +1 -0
  34. package/dist/components/ToolCallBlock.d.ts +14 -0
  35. package/dist/components/ToolCallBlock.d.ts.map +1 -0
  36. package/dist/components/ToolCallBlock.js +83 -0
  37. package/dist/components/ToolCallBlock.js.map +1 -0
  38. package/dist/components/TrustDialog.d.ts +20 -0
  39. package/dist/components/TrustDialog.d.ts.map +1 -0
  40. package/dist/components/TrustDialog.js +80 -0
  41. package/dist/components/TrustDialog.js.map +1 -0
  42. package/dist/context.d.ts +25 -0
  43. package/dist/context.d.ts.map +1 -0
  44. package/dist/context.js +268 -0
  45. package/dist/context.js.map +1 -0
  46. package/dist/cray.d.ts +114 -0
  47. package/dist/cray.d.ts.map +1 -0
  48. package/dist/cray.js +338 -0
  49. package/dist/cray.js.map +1 -0
  50. package/dist/index.d.ts +16 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +122 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/plugins/registry.d.ts +106 -0
  55. package/dist/plugins/registry.d.ts.map +1 -0
  56. package/dist/plugins/registry.js +695 -0
  57. package/dist/plugins/registry.js.map +1 -0
  58. package/dist/query.d.ts +31 -0
  59. package/dist/query.d.ts.map +1 -0
  60. package/dist/query.js +637 -0
  61. package/dist/query.js.map +1 -0
  62. package/dist/queryStream.d.ts +36 -0
  63. package/dist/queryStream.d.ts.map +1 -0
  64. package/dist/queryStream.js +704 -0
  65. package/dist/queryStream.js.map +1 -0
  66. package/dist/screens/ReplScreen.d.ts +22 -0
  67. package/dist/screens/ReplScreen.d.ts.map +1 -0
  68. package/dist/screens/ReplScreen.js +763 -0
  69. package/dist/screens/ReplScreen.js.map +1 -0
  70. package/dist/services/agentRunner.d.ts +39 -0
  71. package/dist/services/agentRunner.d.ts.map +1 -0
  72. package/dist/services/agentRunner.js +115 -0
  73. package/dist/services/agentRunner.js.map +1 -0
  74. package/dist/services/compact.d.ts +34 -0
  75. package/dist/services/compact.d.ts.map +1 -0
  76. package/dist/services/compact.js +179 -0
  77. package/dist/services/compact.js.map +1 -0
  78. package/dist/services/loadPluginCommands.d.ts +55 -0
  79. package/dist/services/loadPluginCommands.d.ts.map +1 -0
  80. package/dist/services/loadPluginCommands.js +219 -0
  81. package/dist/services/loadPluginCommands.js.map +1 -0
  82. package/dist/services/mcp/index.d.ts +3 -0
  83. package/dist/services/mcp/index.d.ts.map +1 -0
  84. package/dist/services/mcp/index.js +3 -0
  85. package/dist/services/mcp/index.js.map +1 -0
  86. package/dist/services/mcp/manager.d.ts +24 -0
  87. package/dist/services/mcp/manager.d.ts.map +1 -0
  88. package/dist/services/mcp/manager.js +138 -0
  89. package/dist/services/mcp/manager.js.map +1 -0
  90. package/dist/services/mcp/types.d.ts +52 -0
  91. package/dist/services/mcp/types.d.ts.map +1 -0
  92. package/dist/services/mcp/types.js +5 -0
  93. package/dist/services/mcp/types.js.map +1 -0
  94. package/dist/services/memory.d.ts +38 -0
  95. package/dist/services/memory.d.ts.map +1 -0
  96. package/dist/services/memory.js +181 -0
  97. package/dist/services/memory.js.map +1 -0
  98. package/dist/services/permissionPrompt.d.ts +38 -0
  99. package/dist/services/permissionPrompt.d.ts.map +1 -0
  100. package/dist/services/permissionPrompt.js +83 -0
  101. package/dist/services/permissionPrompt.js.map +1 -0
  102. package/dist/services/permissions.d.ts +15 -0
  103. package/dist/services/permissions.d.ts.map +1 -0
  104. package/dist/services/permissions.js +237 -0
  105. package/dist/services/permissions.js.map +1 -0
  106. package/dist/services/sessionStorage.d.ts +51 -0
  107. package/dist/services/sessionStorage.d.ts.map +1 -0
  108. package/dist/services/sessionStorage.js +266 -0
  109. package/dist/services/sessionStorage.js.map +1 -0
  110. package/dist/setup.d.ts +22 -0
  111. package/dist/setup.d.ts.map +1 -0
  112. package/dist/setup.js +160 -0
  113. package/dist/setup.js.map +1 -0
  114. package/dist/skills/bundledSkills.d.ts +18 -0
  115. package/dist/skills/bundledSkills.d.ts.map +1 -0
  116. package/dist/skills/bundledSkills.js +277 -0
  117. package/dist/skills/bundledSkills.js.map +1 -0
  118. package/dist/skills/index.d.ts +4 -0
  119. package/dist/skills/index.d.ts.map +1 -0
  120. package/dist/skills/index.js +3 -0
  121. package/dist/skills/index.js.map +1 -0
  122. package/dist/skills/loadSkillsDir.d.ts +45 -0
  123. package/dist/skills/loadSkillsDir.d.ts.map +1 -0
  124. package/dist/skills/loadSkillsDir.js +233 -0
  125. package/dist/skills/loadSkillsDir.js.map +1 -0
  126. package/dist/state/AppState.d.ts +70 -0
  127. package/dist/state/AppState.d.ts.map +1 -0
  128. package/dist/state/AppState.js +106 -0
  129. package/dist/state/AppState.js.map +1 -0
  130. package/dist/tools/AgentTool.d.ts +62 -0
  131. package/dist/tools/AgentTool.d.ts.map +1 -0
  132. package/dist/tools/AgentTool.js +133 -0
  133. package/dist/tools/AgentTool.js.map +1 -0
  134. package/dist/tools/AskUserQuestionTool.d.ts +60 -0
  135. package/dist/tools/AskUserQuestionTool.d.ts.map +1 -0
  136. package/dist/tools/AskUserQuestionTool.js +52 -0
  137. package/dist/tools/AskUserQuestionTool.js.map +1 -0
  138. package/dist/tools/BashTool.d.ts +33 -0
  139. package/dist/tools/BashTool.d.ts.map +1 -0
  140. package/dist/tools/BashTool.js +211 -0
  141. package/dist/tools/BashTool.js.map +1 -0
  142. package/dist/tools/EditTool.d.ts +24 -0
  143. package/dist/tools/EditTool.d.ts.map +1 -0
  144. package/dist/tools/EditTool.js +102 -0
  145. package/dist/tools/EditTool.js.map +1 -0
  146. package/dist/tools/GlobTool.d.ts +17 -0
  147. package/dist/tools/GlobTool.d.ts.map +1 -0
  148. package/dist/tools/GlobTool.js +65 -0
  149. package/dist/tools/GlobTool.js.map +1 -0
  150. package/dist/tools/GrepTool.d.ts +30 -0
  151. package/dist/tools/GrepTool.d.ts.map +1 -0
  152. package/dist/tools/GrepTool.js +140 -0
  153. package/dist/tools/GrepTool.js.map +1 -0
  154. package/dist/tools/MCPTool.d.ts +24 -0
  155. package/dist/tools/MCPTool.d.ts.map +1 -0
  156. package/dist/tools/MCPTool.js +67 -0
  157. package/dist/tools/MCPTool.js.map +1 -0
  158. package/dist/tools/NotebookEditTool.d.ts +28 -0
  159. package/dist/tools/NotebookEditTool.d.ts.map +1 -0
  160. package/dist/tools/NotebookEditTool.js +213 -0
  161. package/dist/tools/NotebookEditTool.js.map +1 -0
  162. package/dist/tools/NotebookReadTool.d.ts +19 -0
  163. package/dist/tools/NotebookReadTool.d.ts.map +1 -0
  164. package/dist/tools/NotebookReadTool.js +191 -0
  165. package/dist/tools/NotebookReadTool.js.map +1 -0
  166. package/dist/tools/PlanTools.d.ts +17 -0
  167. package/dist/tools/PlanTools.d.ts.map +1 -0
  168. package/dist/tools/PlanTools.js +65 -0
  169. package/dist/tools/PlanTools.js.map +1 -0
  170. package/dist/tools/ReadTool.d.ts +21 -0
  171. package/dist/tools/ReadTool.d.ts.map +1 -0
  172. package/dist/tools/ReadTool.js +202 -0
  173. package/dist/tools/ReadTool.js.map +1 -0
  174. package/dist/tools/SkillTool.d.ts +32 -0
  175. package/dist/tools/SkillTool.d.ts.map +1 -0
  176. package/dist/tools/SkillTool.js +217 -0
  177. package/dist/tools/SkillTool.js.map +1 -0
  178. package/dist/tools/TodoWriteTool.d.ts +35 -0
  179. package/dist/tools/TodoWriteTool.d.ts.map +1 -0
  180. package/dist/tools/TodoWriteTool.js +58 -0
  181. package/dist/tools/TodoWriteTool.js.map +1 -0
  182. package/dist/tools/WebFetchTool.d.ts +17 -0
  183. package/dist/tools/WebFetchTool.d.ts.map +1 -0
  184. package/dist/tools/WebFetchTool.js +97 -0
  185. package/dist/tools/WebFetchTool.js.map +1 -0
  186. package/dist/tools/WebSearchTool.d.ts +18 -0
  187. package/dist/tools/WebSearchTool.d.ts.map +1 -0
  188. package/dist/tools/WebSearchTool.js +76 -0
  189. package/dist/tools/WebSearchTool.js.map +1 -0
  190. package/dist/tools/WriteTool.d.ts +17 -0
  191. package/dist/tools/WriteTool.d.ts.map +1 -0
  192. package/dist/tools/WriteTool.js +84 -0
  193. package/dist/tools/WriteTool.js.map +1 -0
  194. package/dist/tools/index.d.ts +21 -0
  195. package/dist/tools/index.d.ts.map +1 -0
  196. package/dist/tools/index.js +20 -0
  197. package/dist/tools/index.js.map +1 -0
  198. package/dist/tools.d.ts +34 -0
  199. package/dist/tools.d.ts.map +1 -0
  200. package/dist/tools.js +102 -0
  201. package/dist/tools.js.map +1 -0
  202. package/dist/types/events.d.ts +85 -0
  203. package/dist/types/events.d.ts.map +1 -0
  204. package/dist/types/events.js +12 -0
  205. package/dist/types/events.js.map +1 -0
  206. package/dist/types/index.d.ts +4 -0
  207. package/dist/types/index.d.ts.map +1 -0
  208. package/dist/types/index.js +4 -0
  209. package/dist/types/index.js.map +1 -0
  210. package/dist/types/message.d.ts +71 -0
  211. package/dist/types/message.d.ts.map +1 -0
  212. package/dist/types/message.js +5 -0
  213. package/dist/types/message.js.map +1 -0
  214. package/dist/types/permission.d.ts +56 -0
  215. package/dist/types/permission.d.ts.map +1 -0
  216. package/dist/types/permission.js +46 -0
  217. package/dist/types/permission.js.map +1 -0
  218. package/dist/types/processUserInput.d.ts +18 -0
  219. package/dist/types/processUserInput.d.ts.map +1 -0
  220. package/dist/types/processUserInput.js +8 -0
  221. package/dist/types/processUserInput.js.map +1 -0
  222. package/dist/types/tool.d.ts +32 -0
  223. package/dist/types/tool.d.ts.map +1 -0
  224. package/dist/types/tool.js +5 -0
  225. package/dist/types/tool.js.map +1 -0
  226. package/dist/utils/compactBoundary.d.ts +11 -0
  227. package/dist/utils/compactBoundary.d.ts.map +1 -0
  228. package/dist/utils/compactBoundary.js +26 -0
  229. package/dist/utils/compactBoundary.js.map +1 -0
  230. package/dist/utils/configStore.d.ts +41 -0
  231. package/dist/utils/configStore.d.ts.map +1 -0
  232. package/dist/utils/configStore.js +111 -0
  233. package/dist/utils/configStore.js.map +1 -0
  234. package/dist/utils/forkedAgent.d.ts +40 -0
  235. package/dist/utils/forkedAgent.d.ts.map +1 -0
  236. package/dist/utils/forkedAgent.js +231 -0
  237. package/dist/utils/forkedAgent.js.map +1 -0
  238. package/dist/utils/messages.d.ts +14 -0
  239. package/dist/utils/messages.d.ts.map +1 -0
  240. package/dist/utils/messages.js +29 -0
  241. package/dist/utils/messages.js.map +1 -0
  242. package/dist/utils/sandbox.d.ts +22 -0
  243. package/dist/utils/sandbox.d.ts.map +1 -0
  244. package/dist/utils/sandbox.js +59 -0
  245. package/dist/utils/sandbox.js.map +1 -0
  246. package/dist/utils/sideQuestion.d.ts +29 -0
  247. package/dist/utils/sideQuestion.d.ts.map +1 -0
  248. package/dist/utils/sideQuestion.js +81 -0
  249. package/dist/utils/sideQuestion.js.map +1 -0
  250. package/install.ps1 +86 -0
  251. package/install.sh +92 -0
  252. package/package.json +68 -0
@@ -0,0 +1,695 @@
1
+ /**
2
+ * Plugin System for Cray Code.
3
+ *
4
+ * Mirrors Claude Code's plugin architecture: plugins are directories under
5
+ * .cray/plugins/<name>/ containing:
6
+ * manifest.json — required: { name, version, description }
7
+ * commands/ — optional: *.md files = plugin commands
8
+ * skills/ — optional: <skill-name>/SKILL.md = plugin skills
9
+ * mcp.json — optional: { servers: { ... } }
10
+ * hooks.js — optional: { onSessionStart, onSessionEnd, ... }
11
+ *
12
+ * Plugins are auto-discovered at startup. Plugin skills/commands are
13
+ * converted to SkillInfo objects and merged into the global skill list.
14
+ *
15
+ * Commands:
16
+ * /plugin list — show installed plugins
17
+ * /plugin install <path> — install a plugin from a local directory
18
+ * /plugin remove <name> — remove an installed plugin
19
+ * /plugin enable <name> — enable a disabled plugin
20
+ * /plugin disable <name> — disable a plugin
21
+ * /plugin info <name> — show plugin details
22
+ */
23
+ import { existsSync, readdirSync, statSync, readFileSync, mkdirSync, writeFileSync, unlinkSync, rmdirSync } from 'fs';
24
+ import { join, basename } from 'path';
25
+ import { homedir } from 'os';
26
+ import { execFileSync } from 'child_process';
27
+ import { pathToFileURL } from 'url';
28
+ import { discoverAllPlugins } from '../services/loadPluginCommands.js';
29
+ // Re-export for external consumers
30
+ export { discoverAllPlugins };
31
+ const plugins = new Map();
32
+ const loadedFromPaths = new Set();
33
+ // ─── Path Helpers ─────────────────────────────────────────────────────
34
+ function getUserPluginDir() {
35
+ return join(homedir(), '.cray', 'plugins');
36
+ }
37
+ function getProjectPluginDir(cwd) {
38
+ return join(cwd, '.cray', 'plugins');
39
+ }
40
+ function getAllPluginSearchDirs(projectRoot) {
41
+ const dirs = [getUserPluginDir()];
42
+ if (projectRoot)
43
+ dirs.push(getProjectPluginDir(projectRoot));
44
+ return dirs;
45
+ }
46
+ // ─── Registration ─────────────────────────────────────────────────────
47
+ export function registerPlugin(plugin) {
48
+ plugins.set(plugin.manifest.name, plugin);
49
+ loadedFromPaths.add(plugin.dir);
50
+ }
51
+ export function unregisterPlugin(name) {
52
+ const p = plugins.get(name);
53
+ if (p)
54
+ loadedFromPaths.delete(p.dir);
55
+ return plugins.delete(name);
56
+ }
57
+ export function getEnabledPlugins() {
58
+ return [...plugins.values()].filter(p => p.isEnabled());
59
+ }
60
+ export function getAllPlugins() {
61
+ return [...plugins.values()];
62
+ }
63
+ export function getPlugin(name) {
64
+ return plugins.get(name);
65
+ }
66
+ // ─── Skill/Command/MCP Extraction ─────────────────────────────────────
67
+ /**
68
+ * Get all skills from enabled plugins.
69
+ * Plugin skills follow the same SkillInfo type as bundled/disk skills,
70
+ * so they automatically appear in the SkillTool's available list and
71
+ * the system prompt's skill listing.
72
+ */
73
+ export function getPluginSkills() {
74
+ return getEnabledPlugins().flatMap(p => p.skills);
75
+ }
76
+ /**
77
+ * Get all commands from enabled plugins.
78
+ */
79
+ export function getPluginCommands() {
80
+ return getEnabledPlugins().flatMap(p => p.commands);
81
+ }
82
+ /**
83
+ * Get MCP server configs from enabled plugins.
84
+ * Prefixed with plugin name to avoid collisions: e.g. "myplugin::myserver"
85
+ */
86
+ export function getPluginMCPServers() {
87
+ const map = new Map();
88
+ for (const p of getEnabledPlugins()) {
89
+ for (const [name, cfg] of Object.entries(p.mcpServers)) {
90
+ map.set(`${p.manifest.name}::${name}`, cfg);
91
+ }
92
+ }
93
+ return map;
94
+ }
95
+ // ─── Hooks ────────────────────────────────────────────────────────────
96
+ export async function executeSessionStartHooks() {
97
+ for (const p of getEnabledPlugins()) {
98
+ try {
99
+ await p.hooks?.onSessionStart?.();
100
+ }
101
+ catch { /* non-fatal */ }
102
+ }
103
+ }
104
+ export async function executeSessionEndHooks() {
105
+ for (const p of getEnabledPlugins()) {
106
+ try {
107
+ await p.hooks?.onSessionEnd?.();
108
+ }
109
+ catch { /* non-fatal */ }
110
+ }
111
+ }
112
+ // ─── Discovery and Loading ────────────────────────────────────────────
113
+ /**
114
+ * Load all installed plugins into the registry.
115
+ * Called during CrayCode.initialize().
116
+ */
117
+ export async function loadAllPlugins(projectRoot) {
118
+ const dirs = getAllPluginSearchDirs(projectRoot);
119
+ const found = discoverAllPlugins(dirs);
120
+ const results = [];
121
+ for (const plugin of found) {
122
+ // Skip duplicates
123
+ if (loadedFromPaths.has(plugin.dir))
124
+ continue;
125
+ if (plugins.has(plugin.manifest.name))
126
+ continue;
127
+ // Load hooks
128
+ let hooks;
129
+ const hooksPath = join(plugin.dir, 'hooks.js');
130
+ if (existsSync(hooksPath)) {
131
+ try {
132
+ const hooksUrl = pathToFileURL(hooksPath).href;
133
+ const mod = await import(hooksUrl);
134
+ hooks = { ...mod };
135
+ }
136
+ catch { /* hooks.js load error */ }
137
+ }
138
+ const registered = { ...plugin, hooks };
139
+ registerPlugin(registered);
140
+ results.push(registered);
141
+ }
142
+ return results;
143
+ }
144
+ /**
145
+ * Reload all plugins (use after install/remove/enable/disable).
146
+ */
147
+ export async function reloadAllPlugins(projectRoot) {
148
+ plugins.clear();
149
+ loadedFromPaths.clear();
150
+ await loadAllPlugins(projectRoot);
151
+ }
152
+ // ─── Plugin Management ────────────────────────────────────────────────
153
+ /**
154
+ * Parse a plugin identifier string.
155
+ * Supports three formats:
156
+ * - Local path: ./my-plugin or /absolute/path
157
+ * - Marketplace ref: chrome-devtools-mcp@claude-plugins-official
158
+ * - Marketplace slash: claude-plugins-official/chrome-devtools-mcp
159
+ *
160
+ * Returns { type: 'local' | 'marketplace', name?, marketplace?, source, refPath? }
161
+ */
162
+ export function parsePluginRef(source) {
163
+ // Check for marketplace format: name@marketplace
164
+ const atMatch = source.match(/^(.+)@(.+)$/);
165
+ if (atMatch && !source.includes('/') && !source.includes('\\')) {
166
+ return {
167
+ type: 'marketplace',
168
+ name: atMatch[1],
169
+ marketplace: atMatch[2],
170
+ displayName: `${atMatch[2]} / ${atMatch[1]}`,
171
+ localPath: atMatch[1],
172
+ };
173
+ }
174
+ // Check for marketplace format: marketplace/name (with only one slash, no path)
175
+ const slashMatch = source.match(/^([^@\/\\]+)\/([^@\/\\]+)$/);
176
+ if (slashMatch) {
177
+ return {
178
+ type: 'marketplace',
179
+ name: slashMatch[2],
180
+ marketplace: slashMatch[1],
181
+ displayName: `${slashMatch[1]} / ${slashMatch[2]}`,
182
+ localPath: slashMatch[2],
183
+ };
184
+ }
185
+ // Treat as local path
186
+ return {
187
+ type: 'local',
188
+ displayName: source,
189
+ localPath: basename(source),
190
+ };
191
+ }
192
+ export function installPlugin(source, projectRoot) {
193
+ const destDir = projectRoot ? getProjectPluginDir(projectRoot) : getUserPluginDir();
194
+ mkdirSync(destDir, { recursive: true });
195
+ const ref = parsePluginRef(source);
196
+ // ─── Local path install ──────────────────────────────────────
197
+ if (ref.type === 'local') {
198
+ // Check if it's a URL — git clone and install
199
+ if (source.startsWith('http://') || source.startsWith('https://')) {
200
+ return installPluginFromUrl(source, destDir);
201
+ }
202
+ const abs = join(process.cwd(), source);
203
+ // If the name looks like a plugin name (no path separators, no file extension),
204
+ // and the local directory doesn't exist, treat it as a marketplace plugin.
205
+ const looksLikePluginName = !source.includes('/') && !source.includes('\\') && !source.includes('.');
206
+ if (!existsSync(abs) && looksLikePluginName) {
207
+ // Fall through to marketplace install — resolve against known marketplaces
208
+ return installFromMarketplace(source, destDir);
209
+ }
210
+ if (!existsSync(abs)) {
211
+ return { success: false, message: `Source not found: ${abs}${looksLikePluginName ? `\n\nDid you mean a marketplace plugin? Try:\n /plugin install claude-plugins-official/${source}` : ''}` };
212
+ }
213
+ if (!statSync(abs).isDirectory()) {
214
+ return { success: false, message: `Source must be a directory: ${abs}` };
215
+ }
216
+ if (!existsSync(join(abs, 'manifest.json'))) {
217
+ return { success: false, message: `No manifest.json found in ${abs}. Plugins must have a manifest.` };
218
+ }
219
+ const name = ref.localPath;
220
+ const dest = join(destDir, name);
221
+ if (existsSync(dest)) {
222
+ return { success: false, message: `Plugin "${name}" is already installed. Remove it first (/plugin remove ${name}).` };
223
+ }
224
+ copyDirSync(abs, dest);
225
+ return { success: true, message: `Plugin "${name}" installed to ${dest}\nReload Cray Code to activate.`, pluginName: name };
226
+ }
227
+ // ─── Marketplace install ─────────────────────────────────────
228
+ const { name, marketplace, displayName } = ref;
229
+ const pkgName = ref.localPath; // Guaranteed string for marketplace refs
230
+ const marketplaceId = marketplace ?? '';
231
+ const skillName = name ?? pkgName;
232
+ const dest = join(destDir, pkgName);
233
+ if (existsSync(dest)) {
234
+ return { success: false, message: `Plugin "${pkgName}" is already installed. Remove it first (/plugin remove ${pkgName}).` };
235
+ }
236
+ // Try known marketplaces
237
+ const resolved = resolveMarketplacePlugin(marketplaceId, skillName);
238
+ if (resolved) {
239
+ // Create the plugin with skill stubs
240
+ mkdirSync(dest, { recursive: true });
241
+ writeFileSync(join(dest, 'manifest.json'), JSON.stringify({
242
+ name: skillName,
243
+ version: '1.0.0',
244
+ description: `Marketplace plugin: ${displayName}`,
245
+ author: marketplaceId,
246
+ repository: resolved,
247
+ }, null, 2), 'utf-8');
248
+ writeFileSync(join(dest, 'README.md'), `# ${skillName}\n\nInstalled from ${displayName}.\nSource: ${resolved}`, 'utf-8');
249
+ // Create basic skill stubs if it's a known plugin type
250
+ mkdirSync(join(dest, 'commands'), { recursive: true });
251
+ mkdirSync(join(dest, 'skills'), { recursive: true });
252
+ mkdirSync(join(dest, 'skills', skillName), { recursive: true });
253
+ writeFileSync(join(dest, 'skills', skillName, 'SKILL.md'), [
254
+ '---',
255
+ `description: "${skillName} — ${displayName}"`,
256
+ 'allowed-tools: [read, glob, grep, bash, web_fetch]',
257
+ 'context: inline',
258
+ '---',
259
+ '',
260
+ `# ${skillName}`,
261
+ '',
262
+ 'This skill was installed from the marketplace.',
263
+ '',
264
+ 'To customize: edit this SKILL.md, then reload Cray Code.',
265
+ ].join('\n'), 'utf-8');
266
+ return {
267
+ success: true,
268
+ message: [
269
+ `Plugin "${pkgName}" installed from ${displayName}!`,
270
+ `Directory: ${dest}`,
271
+ '',
272
+ `Template files created:`,
273
+ ` ${dest}/manifest.json`,
274
+ ` ${dest}/skills/${skillName}/SKILL.md`,
275
+ '',
276
+ `Edit the SKILL.md to add your skill instructions,`,
277
+ `then add commands/*.md files for additional features.`,
278
+ '',
279
+ `Reload Cray Code to activate.`,
280
+ ].join('\n'),
281
+ pluginName: pkgName,
282
+ };
283
+ }
284
+ // Marketplace not recognized — scaffold an empty plugin for the user
285
+ mkdirSync(dest, { recursive: true });
286
+ writeFileSync(join(dest, 'manifest.json'), JSON.stringify({
287
+ name: skillName,
288
+ version: '1.0.0',
289
+ description: `Plugin: ${displayName}`,
290
+ author: marketplaceId,
291
+ }, null, 2), 'utf-8');
292
+ mkdirSync(join(dest, 'commands'), { recursive: true });
293
+ mkdirSync(join(dest, 'skills'), { recursive: true });
294
+ mkdirSync(join(dest, 'skills', skillName), { recursive: true });
295
+ writeFileSync(join(dest, 'skills', skillName, 'SKILL.md'), [
296
+ '---',
297
+ `description: "${skillName} skill"`,
298
+ 'allowed-tools: [read, glob, grep]',
299
+ 'context: inline',
300
+ '---',
301
+ '',
302
+ `# ${skillName}`,
303
+ '',
304
+ `Marketplace: ${displayName}`,
305
+ '',
306
+ '## When to Use',
307
+ '',
308
+ 'Describe when this skill should be invoked.',
309
+ '',
310
+ '## Instructions',
311
+ '',
312
+ 'Detailed instructions go here.',
313
+ ].join('\n'), 'utf-8');
314
+ return {
315
+ success: true,
316
+ message: [
317
+ `Plugin "${pkgName}" scaffolded from ${displayName}.`,
318
+ `Directory: ${dest}`,
319
+ '',
320
+ `Template files created:`,
321
+ ` ${dest}/manifest.json`,
322
+ ` ${dest}/skills/${skillName}/SKILL.md`,
323
+ '',
324
+ `Edit the SKILL.md and commands/*.md to add your plugin logic.`,
325
+ `Reload Cray Code to activate: restart cray.`,
326
+ ].join('\n'),
327
+ pluginName: pkgName,
328
+ };
329
+ }
330
+ /**
331
+ * Install a plugin from a git URL by cloning into the plugins directory.
332
+ */
333
+ function installPluginFromUrl(gitUrl, destDir) {
334
+ // Check if git is available
335
+ try {
336
+ execFileSync('git', ['--version'], { stdio: 'pipe' });
337
+ }
338
+ catch {
339
+ return { success: false, message: 'Git is required for URL-based plugin installation.\nInstall it from: https://git-scm.com/' };
340
+ }
341
+ // Extract a safe directory name from the URL
342
+ const urlForName = gitUrl.replace(/\.git$/, '').replace(/\/$/, '');
343
+ const name = basename(urlForName) || 'installed-plugin';
344
+ const dest = join(destDir, name);
345
+ if (existsSync(dest)) {
346
+ return { success: false, message: `Plugin "${name}" already exists. Remove it first: /plugin remove ${name}` };
347
+ }
348
+ try {
349
+ // Clone the repo (shallow to save bandwidth)
350
+ // Use execFileSync with argument array — no shell, no injection
351
+ execFileSync('git', ['clone', '--depth', '1', gitUrl, dest], {
352
+ cwd: destDir,
353
+ stdio: 'pipe',
354
+ timeout: 60_000,
355
+ });
356
+ // Check for manifest.json
357
+ if (!existsSync(join(dest, 'manifest.json'))) {
358
+ // Generate a manifest from repo metadata
359
+ let desc = `Plugin from ${gitUrl}`;
360
+ try {
361
+ const readme = join(dest, 'README.md');
362
+ if (existsSync(readme)) {
363
+ const firstLine = readFileSync(readme, 'utf-8').split('\n')[0];
364
+ if (firstLine)
365
+ desc = firstLine.replace(/^#+\s*/, '').trim();
366
+ }
367
+ }
368
+ catch { /* use default */ }
369
+ writeFileSync(join(dest, 'manifest.json'), JSON.stringify({
370
+ name,
371
+ version: '1.0.0',
372
+ description: desc,
373
+ repository: gitUrl,
374
+ }, null, 2), 'utf-8');
375
+ }
376
+ // If no skills/ directory, create a default one
377
+ if (!existsSync(join(dest, 'skills'))) {
378
+ mkdirSync(join(dest, 'skills', name), { recursive: true });
379
+ writeFileSync(join(dest, 'skills', name, 'SKILL.md'), [
380
+ '---',
381
+ `description: "${name} — installed from ${gitUrl}"`,
382
+ 'allowed-tools: [read, glob, grep, bash]',
383
+ 'context: inline',
384
+ '---',
385
+ '',
386
+ `# ${name}`,
387
+ '',
388
+ 'This plugin was installed from:',
389
+ ` ${gitUrl}`,
390
+ '',
391
+ 'Add your skill instructions to this file and reload Cray Code.',
392
+ ].join('\n'), 'utf-8');
393
+ }
394
+ return {
395
+ success: true,
396
+ message: [
397
+ `Plugin "${name}" installed from git!`,
398
+ `Directory: ${dest}`,
399
+ '',
400
+ `Reload Cray Code to activate.`,
401
+ ].join('\n'),
402
+ pluginName: name,
403
+ };
404
+ }
405
+ catch (err) {
406
+ // Clean up failed clone
407
+ if (existsSync(dest)) {
408
+ try {
409
+ rmDirSync(dest);
410
+ }
411
+ catch { /* best-effort */ }
412
+ }
413
+ const msg = err.stderr ? String(err.stderr) : err.message;
414
+ return { success: false, message: `Git clone failed: ${msg.slice(0, 300)}` };
415
+ }
416
+ }
417
+ /**
418
+ * Resolve a marketplace plugin to a known source URL.
419
+ * Returns the URL/fetch string, or null if not found.
420
+ */
421
+ function resolveMarketplacePlugin(marketplace, pluginName) {
422
+ const key = `${marketplace}/${pluginName}`;
423
+ const registry = {
424
+ 'claude-plugins-official/chrome-devtools-mcp': 'https://github.com/anthropics/claude-code/tree/main/plugins',
425
+ 'claude-plugins-official/code-review': 'https://github.com/anthropics/claude-code/tree/main/plugins',
426
+ 'claude-plugins-official/mcp-server-context7': 'https://github.com/upstash/context7',
427
+ 'context7/mcp-server-context7': 'https://github.com/upstash/context7',
428
+ 'context7/context7': 'https://github.com/upstash/context7',
429
+ 'claude-plugins-official/gitlab': 'https://gitlab.com',
430
+ 'claude-plugins-official/playwright': 'https://github.com/microsoft/playwright',
431
+ 'claude-plugins-official/linear': 'https://linear.app',
432
+ 'claude-plugins-official/slack': 'https://slack.com',
433
+ 'claude-plugins-official/notion': 'https://notion.so',
434
+ 'claude-plugins-official/figma': 'https://figma.com',
435
+ 'claude-plugins-official/sentry': 'https://sentry.io',
436
+ 'claude-plugins-official/vercel': 'https://vercel.com',
437
+ };
438
+ return registry[key] ?? null;
439
+ }
440
+ /**
441
+ * Try to install a bare plugin name by searching known marketplaces.
442
+ * When the user types `/plugin install context7`, we search for it.
443
+ */
444
+ function installFromMarketplace(bareName, destDir) {
445
+ // Try exact matches first across known marketplaces
446
+ const knownMarketplaces = [
447
+ 'claude-plugins-official',
448
+ 'context7',
449
+ 'upstash',
450
+ ];
451
+ // Exact match: name == known plugin
452
+ const exactMatches = [];
453
+ for (const mp of knownMarketplaces) {
454
+ const resolved = resolveMarketplacePlugin(mp, bareName);
455
+ if (resolved)
456
+ exactMatches.push(mp);
457
+ }
458
+ // Also check fuzzy matches — if the name contains or is contained by known plugins
459
+ const allKnownPlugins = [
460
+ 'chrome-devtools-mcp', 'code-review', 'mcp-server-context7', 'context7',
461
+ 'gitlab', 'playwright', 'linear', 'slack', 'notion', 'figma', 'sentry', 'vercel',
462
+ ];
463
+ const fuzzyMatches = allKnownPlugins.filter(p => p.toLowerCase().includes(bareName.toLowerCase()) ||
464
+ bareName.toLowerCase().includes(p.toLowerCase()));
465
+ // Resolve each fuzzy match
466
+ const fuzzyResolved = [];
467
+ for (const pn of fuzzyMatches) {
468
+ for (const mp of knownMarketplaces) {
469
+ const resolved = resolveMarketplacePlugin(mp, pn);
470
+ if (resolved) {
471
+ fuzzyResolved.push({ name: pn, marketplace: mp });
472
+ break;
473
+ }
474
+ }
475
+ }
476
+ // If we found exact matches, install the first one
477
+ if (exactMatches.length > 0) {
478
+ const mp = exactMatches[0];
479
+ return finishMarketplaceInstall(mp, bareName, destDir);
480
+ }
481
+ // If we have one fuzzy match, use it
482
+ if (fuzzyResolved.length === 1) {
483
+ const match = fuzzyResolved[0];
484
+ return finishMarketplaceInstall(match.marketplace, match.name, destDir);
485
+ }
486
+ // If we have multiple matches, list them
487
+ if (fuzzyResolved.length > 1) {
488
+ const suggestions = fuzzyResolved
489
+ .map(f => ` /plugin install ${f.marketplace}/${f.name}`)
490
+ .join('\n');
491
+ return {
492
+ success: false,
493
+ message: [
494
+ `No exact match for "${bareName}". Did you mean one of these?`,
495
+ '',
496
+ suggestions,
497
+ '',
498
+ `Or install from a specific marketplace: /plugin install <marketplace>/<name>`,
499
+ ].join('\n'),
500
+ };
501
+ }
502
+ // No match — scaffold a new plugin with the given name
503
+ const dest = join(destDir, bareName);
504
+ if (existsSync(dest)) {
505
+ return { success: false, message: `Plugin "${bareName}" is already installed.` };
506
+ }
507
+ mkdirSync(dest, { recursive: true });
508
+ writeFileSync(join(dest, 'manifest.json'), JSON.stringify({
509
+ name: bareName,
510
+ version: '1.0.0',
511
+ description: `Plugin: ${bareName}`,
512
+ }, null, 2), 'utf-8');
513
+ mkdirSync(join(dest, 'commands'), { recursive: true });
514
+ mkdirSync(join(dest, 'skills'), { recursive: true });
515
+ mkdirSync(join(dest, 'skills', bareName), { recursive: true });
516
+ writeFileSync(join(dest, 'skills', bareName, 'SKILL.md'), [
517
+ '---',
518
+ `description: "${bareName} skill"`,
519
+ 'allowed-tools: [read, glob, grep]',
520
+ 'context: inline',
521
+ '---',
522
+ '',
523
+ `# ${bareName}`,
524
+ '',
525
+ '## When to Use',
526
+ '',
527
+ 'Describe when this skill should be invoked.',
528
+ '',
529
+ '## Instructions',
530
+ '',
531
+ 'Describe your plugin instructions here.',
532
+ ].join('\n'), 'utf-8');
533
+ return {
534
+ success: true,
535
+ message: [
536
+ `Plugin "${bareName}" scaffolded (not found in known marketplaces).`,
537
+ `Directory: ${dest}`,
538
+ '',
539
+ `Template files created:`,
540
+ ` ${dest}/manifest.json`,
541
+ ` ${dest}/skills/${bareName}/SKILL.md`,
542
+ '',
543
+ `Edit the SKILL.md and commands/*.md to add your plugin logic.`,
544
+ `Restart cray to activate.`,
545
+ ].join('\n'),
546
+ pluginName: bareName,
547
+ };
548
+ }
549
+ /**
550
+ * Finish a marketplace install: scaffold the plugin directory with templates.
551
+ */
552
+ function finishMarketplaceInstall(marketplace, pluginName, destDir) {
553
+ const dest = join(destDir, pluginName);
554
+ if (existsSync(dest)) {
555
+ return { success: false, message: `Plugin "${pluginName}" is already installed. Remove it first (/plugin remove ${pluginName}).` };
556
+ }
557
+ const displayName = `${marketplace} / ${pluginName}`;
558
+ mkdirSync(dest, { recursive: true });
559
+ writeFileSync(join(dest, 'manifest.json'), JSON.stringify({
560
+ name: pluginName,
561
+ version: '1.0.0',
562
+ description: `Marketplace plugin: ${displayName}`,
563
+ author: marketplace,
564
+ }, null, 2), 'utf-8');
565
+ mkdirSync(join(dest, 'commands'), { recursive: true });
566
+ mkdirSync(join(dest, 'skills'), { recursive: true });
567
+ mkdirSync(join(dest, 'skills', pluginName), { recursive: true });
568
+ writeFileSync(join(dest, 'skills', pluginName, 'SKILL.md'), [
569
+ '---',
570
+ `description: "${pluginName} — ${displayName}"`,
571
+ 'allowed-tools: [read, glob, grep, bash, web_fetch]',
572
+ 'context: inline',
573
+ '---',
574
+ '',
575
+ `# ${pluginName}`,
576
+ '',
577
+ `This skill was installed from the marketplace (${marketplace}).`,
578
+ '',
579
+ 'To customize: edit this SKILL.md, then reload Cray Code.',
580
+ ].join('\n'), 'utf-8');
581
+ return {
582
+ success: true,
583
+ message: [
584
+ `Plugin "${pluginName}" installed from ${displayName}!`,
585
+ `Directory: ${dest}`,
586
+ '',
587
+ `Template files created:`,
588
+ ` ${dest}/manifest.json`,
589
+ ` ${dest}/skills/${pluginName}/SKILL.md`,
590
+ '',
591
+ `Edit the SKILL.md to add your plugin's instructions.`,
592
+ `Restart cray to activate.`,
593
+ ].join('\n'),
594
+ pluginName,
595
+ };
596
+ }
597
+ export function removePlugin(name, projectRoot) {
598
+ const dirs = getAllPluginSearchDirs(projectRoot);
599
+ for (const d of dirs) {
600
+ const target = join(d, name);
601
+ if (existsSync(target)) {
602
+ rmDirSync(target);
603
+ plugins.delete(name);
604
+ loadedFromPaths.delete(target);
605
+ return { success: true, message: `Plugin "${name}" removed.` };
606
+ }
607
+ }
608
+ return { success: false, message: `Plugin "${name}" not found in any plugin directory.` };
609
+ }
610
+ export function enablePlugin(name) {
611
+ const p = plugins.get(name);
612
+ if (!p)
613
+ return { success: false, message: `Plugin "${name}" not registered.` };
614
+ if (p.enabled)
615
+ return { success: true, message: `Plugin "${name}" is already enabled.` };
616
+ p.enabled = true;
617
+ persistPluginState(name, true);
618
+ return { success: true, message: `Plugin "${name}" enabled. Reload to activate.` };
619
+ }
620
+ export function disablePlugin(name) {
621
+ const p = plugins.get(name);
622
+ if (!p)
623
+ return { success: false, message: `Plugin "${name}" not registered.` };
624
+ if (!p.enabled)
625
+ return { success: true, message: `Plugin "${name}" is already disabled.` };
626
+ p.enabled = false;
627
+ persistPluginState(name, false);
628
+ return { success: true, message: `Plugin "${name}" disabled.` };
629
+ }
630
+ // ─── State Persistence ────────────────────────────────────────────────
631
+ function persistPluginState(name, enabled) {
632
+ try {
633
+ const statePath = join(homedir(), '.cray', 'plugin_state.json');
634
+ let state = {};
635
+ if (existsSync(statePath)) {
636
+ try {
637
+ state = JSON.parse(readFileSync(statePath, 'utf-8'));
638
+ }
639
+ catch { /* reset */ }
640
+ }
641
+ state[name] = enabled;
642
+ mkdirSync(join(statePath, '..'), { recursive: true });
643
+ writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf-8');
644
+ }
645
+ catch { /* non-fatal */ }
646
+ }
647
+ function loadPluginState() {
648
+ try {
649
+ const statePath = join(homedir(), '.cray', 'plugin_state.json');
650
+ if (existsSync(statePath)) {
651
+ return JSON.parse(readFileSync(statePath, 'utf-8'));
652
+ }
653
+ }
654
+ catch { /* use defaults */ }
655
+ return {};
656
+ }
657
+ /**
658
+ * Apply saved enable/disable state on startup.
659
+ */
660
+ export function applyPluginState() {
661
+ const state = loadPluginState();
662
+ for (const [name, enabled] of Object.entries(state)) {
663
+ const p = plugins.get(name);
664
+ if (p)
665
+ p.enabled = enabled;
666
+ }
667
+ }
668
+ // ─── File System Helpers ──────────────────────────────────────────────
669
+ function copyDirSync(src, dest) {
670
+ mkdirSync(dest, { recursive: true });
671
+ for (const entry of readdirSync(src)) {
672
+ const sp = join(src, entry), dp = join(dest, entry);
673
+ if (statSync(sp).isDirectory()) {
674
+ copyDirSync(sp, dp);
675
+ }
676
+ else {
677
+ writeFileSync(dp, readFileSync(sp));
678
+ }
679
+ }
680
+ }
681
+ function rmDirSync(dir) {
682
+ if (!existsSync(dir))
683
+ return;
684
+ for (const entry of readdirSync(dir)) {
685
+ const p = join(dir, entry);
686
+ if (statSync(p).isDirectory()) {
687
+ rmDirSync(p);
688
+ }
689
+ else {
690
+ unlinkSync(p);
691
+ }
692
+ }
693
+ rmdirSync(dir);
694
+ }
695
+ //# sourceMappingURL=registry.js.map