@mastra/code-sdk 1.0.3-alpha.0 → 1.1.0-alpha.2

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 (258) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/dist/acp/agent.js +155 -167
  3. package/dist/acp/agent.js.map +1 -1
  4. package/dist/acp/event-mapper.js +193 -168
  5. package/dist/acp/event-mapper.js.map +1 -1
  6. package/dist/acp/index.js +60 -49
  7. package/dist/acp/index.js.map +1 -1
  8. package/dist/acp/server.js +31 -30
  9. package/dist/acp/server.js.map +1 -1
  10. package/dist/acp.js +1 -4
  11. package/dist/agents/credential-resolver.js +40 -29
  12. package/dist/agents/credential-resolver.js.map +1 -1
  13. package/dist/agents/custom-provider-source.js +16 -9
  14. package/dist/agents/custom-provider-source.js.map +1 -1
  15. package/dist/agents/instructions.js +27 -36
  16. package/dist/agents/instructions.js.map +1 -1
  17. package/dist/agents/mastracode-gateway.js +355 -428
  18. package/dist/agents/mastracode-gateway.js.map +1 -1
  19. package/dist/agents/memory.js +87 -78
  20. package/dist/agents/memory.js.map +1 -1
  21. package/dist/agents/model.js +114 -119
  22. package/dist/agents/model.js.map +1 -1
  23. package/dist/agents/modes/build.js +12 -13
  24. package/dist/agents/modes/build.js.map +1 -1
  25. package/dist/agents/modes/explore.js +14 -13
  26. package/dist/agents/modes/explore.js.map +1 -1
  27. package/dist/agents/modes/plan.js +14 -15
  28. package/dist/agents/modes/plan.js.map +1 -1
  29. package/dist/agents/prompts/agent-instructions.js +78 -69
  30. package/dist/agents/prompts/agent-instructions.js.map +1 -1
  31. package/dist/agents/prompts/build.js +19 -16
  32. package/dist/agents/prompts/build.js.map +1 -1
  33. package/dist/agents/prompts/fast.js +8 -4
  34. package/dist/agents/prompts/fast.js.map +1 -1
  35. package/dist/agents/prompts/index.js +48 -47
  36. package/dist/agents/prompts/index.js.map +1 -1
  37. package/dist/agents/prompts/model.js +6 -5
  38. package/dist/agents/prompts/model.js.map +1 -1
  39. package/dist/agents/prompts/plan.js +20 -16
  40. package/dist/agents/prompts/plan.js.map +1 -1
  41. package/dist/agents/prompts/tool-guidance.js +77 -109
  42. package/dist/agents/prompts/tool-guidance.js.map +1 -1
  43. package/dist/agents/sandbox-filesystem.js +245 -259
  44. package/dist/agents/sandbox-filesystem.js.map +1 -1
  45. package/dist/agents/sandbox-reattach.js +9 -11
  46. package/dist/agents/sandbox-reattach.js.map +1 -1
  47. package/dist/agents/thread-caveman-state.js +92 -81
  48. package/dist/agents/thread-caveman-state.js.map +1 -1
  49. package/dist/agents/tool-availability.js +62 -80
  50. package/dist/agents/tool-availability.js.map +1 -1
  51. package/dist/agents/tools.js +99 -139
  52. package/dist/agents/tools.js.map +1 -1
  53. package/dist/agents/workspace.js +232 -225
  54. package/dist/agents/workspace.js.map +1 -1
  55. package/dist/analytics.js +139 -128
  56. package/dist/analytics.js.map +1 -1
  57. package/dist/auth/authorization-input.js +39 -25
  58. package/dist/auth/authorization-input.js.map +1 -1
  59. package/dist/auth/device-code.js +112 -80
  60. package/dist/auth/device-code.js.map +1 -1
  61. package/dist/auth/index.js +2 -9
  62. package/dist/auth/pkce.js +27 -16
  63. package/dist/auth/pkce.js.map +1 -1
  64. package/dist/auth/providers/anthropic.js +107 -93
  65. package/dist/auth/providers/anthropic.js.map +1 -1
  66. package/dist/auth/providers/github-copilot.js +304 -293
  67. package/dist/auth/providers/github-copilot.js.map +1 -1
  68. package/dist/auth/providers/openai-codex.js +442 -460
  69. package/dist/auth/providers/openai-codex.js.map +1 -1
  70. package/dist/auth/providers/xai.js +179 -145
  71. package/dist/auth/providers/xai.js.map +1 -1
  72. package/dist/auth/storage.js +182 -175
  73. package/dist/auth/storage.js.map +1 -1
  74. package/dist/auth/types.js +1 -1
  75. package/dist/clipboard/index.js +206 -172
  76. package/dist/clipboard/index.js.map +1 -1
  77. package/dist/constants.js +11 -16
  78. package/dist/constants.js.map +1 -1
  79. package/dist/error-classification.js +19 -11
  80. package/dist/error-classification.js.map +1 -1
  81. package/dist/evals/context-builder.js +145 -104
  82. package/dist/evals/context-builder.js.map +1 -1
  83. package/dist/evals/index.js +4 -7
  84. package/dist/evals/scorers/classify-command.js +62 -41
  85. package/dist/evals/scorers/classify-command.js.map +1 -1
  86. package/dist/evals/scorers/efficiency.js +239 -181
  87. package/dist/evals/scorers/efficiency.js.map +1 -1
  88. package/dist/evals/scorers/extract-tools.js +51 -47
  89. package/dist/evals/scorers/extract-tools.js.map +1 -1
  90. package/dist/evals/scorers/index.js +1 -5
  91. package/dist/evals/scorers/outcome.js +321 -212
  92. package/dist/evals/scorers/outcome.js.map +1 -1
  93. package/dist/goal-manager.js +266 -261
  94. package/dist/goal-manager.js.map +1 -1
  95. package/dist/goal-signal.js +14 -13
  96. package/dist/goal-signal.js.map +1 -1
  97. package/dist/headless/cli.js +153 -154
  98. package/dist/headless/cli.js.map +1 -1
  99. package/dist/headless/flags.js +180 -178
  100. package/dist/headless/flags.js.map +1 -1
  101. package/dist/headless/format.js +72 -71
  102. package/dist/headless/format.js.map +1 -1
  103. package/dist/headless/index.js +5 -31
  104. package/dist/headless/policy.js +35 -31
  105. package/dist/headless/policy.js.map +1 -1
  106. package/dist/headless/run-mc.js +332 -334
  107. package/dist/headless/run-mc.js.map +1 -1
  108. package/dist/headless/types.js +16 -7
  109. package/dist/headless/types.js.map +1 -1
  110. package/dist/hooks/config.js +56 -61
  111. package/dist/hooks/config.js.map +1 -1
  112. package/dist/hooks/executor.js +118 -116
  113. package/dist/hooks/executor.js.map +1 -1
  114. package/dist/hooks/index.js +4 -15
  115. package/dist/hooks/manager.js +221 -280
  116. package/dist/hooks/manager.js.map +1 -1
  117. package/dist/hooks/types.js +5 -4
  118. package/dist/hooks/types.js.map +1 -1
  119. package/dist/index.d.ts.map +1 -1
  120. package/dist/index.js +632 -712
  121. package/dist/index.js.map +1 -1
  122. package/dist/ipc/ipc-reporter.js +19 -22
  123. package/dist/ipc/ipc-reporter.js.map +1 -1
  124. package/dist/lsp/client.js +248 -306
  125. package/dist/lsp/client.js.map +1 -1
  126. package/dist/lsp/index.js +1 -6
  127. package/dist/lsp/language.js +39 -43
  128. package/dist/lsp/language.js.map +1 -1
  129. package/dist/lsp/manager.js +78 -93
  130. package/dist/lsp/manager.js.map +1 -1
  131. package/dist/lsp/server.js +151 -120
  132. package/dist/lsp/server.js.map +1 -1
  133. package/dist/lsp/workspace.js +50 -47
  134. package/dist/lsp/workspace.js.map +1 -1
  135. package/dist/mcp/config.js +210 -199
  136. package/dist/mcp/config.js.map +1 -1
  137. package/dist/mcp/index.js +2 -9
  138. package/dist/mcp/manager.js +482 -475
  139. package/dist/mcp/manager.js.map +1 -1
  140. package/dist/mcp/types.js +1 -1
  141. package/dist/onboarding/custom-packs.js +14 -17
  142. package/dist/onboarding/custom-packs.js.map +1 -1
  143. package/dist/onboarding/custom-providers.js +22 -20
  144. package/dist/onboarding/custom-providers.js.map +1 -1
  145. package/dist/onboarding/index.d.ts +1 -1
  146. package/dist/onboarding/index.d.ts.map +1 -1
  147. package/dist/onboarding/index.js +3 -21
  148. package/dist/onboarding/om-settings.d.ts +5 -0
  149. package/dist/onboarding/om-settings.d.ts.map +1 -1
  150. package/dist/onboarding/om-settings.js +41 -20
  151. package/dist/onboarding/om-settings.js.map +1 -1
  152. package/dist/onboarding/packs.d.ts +4 -6
  153. package/dist/onboarding/packs.d.ts.map +1 -1
  154. package/dist/onboarding/packs.js +154 -105
  155. package/dist/onboarding/packs.js.map +1 -1
  156. package/dist/onboarding/settings.js +575 -517
  157. package/dist/onboarding/settings.js.map +1 -1
  158. package/dist/permissions.js +112 -99
  159. package/dist/permissions.js.map +1 -1
  160. package/dist/plugin.js +21 -23
  161. package/dist/plugin.js.map +1 -1
  162. package/dist/plugins/dependencies.js +64 -87
  163. package/dist/plugins/dependencies.js.map +1 -1
  164. package/dist/plugins/install.js +159 -169
  165. package/dist/plugins/install.js.map +1 -1
  166. package/dist/plugins/loader.js +170 -185
  167. package/dist/plugins/loader.js.map +1 -1
  168. package/dist/plugins/manager.js +374 -380
  169. package/dist/plugins/manager.js.map +1 -1
  170. package/dist/plugins/manifest.js +38 -63
  171. package/dist/plugins/manifest.js.map +1 -1
  172. package/dist/plugins/package-link.js +48 -64
  173. package/dist/plugins/package-link.js.map +1 -1
  174. package/dist/plugins/paths.js +18 -19
  175. package/dist/plugins/paths.js.map +1 -1
  176. package/dist/plugins/registry.js +97 -81
  177. package/dist/plugins/registry.js.map +1 -1
  178. package/dist/plugins/scaffold.js +62 -98
  179. package/dist/plugins/scaffold.js.map +1 -1
  180. package/dist/plugins/types.js +1 -1
  181. package/dist/processors/plan-rejection-abort.js +45 -44
  182. package/dist/processors/plan-rejection-abort.js.map +1 -1
  183. package/dist/providers/amazon-bedrock-gateway.js +134 -125
  184. package/dist/providers/amazon-bedrock-gateway.js.map +1 -1
  185. package/dist/providers/amazon-bedrock.js +68 -47
  186. package/dist/providers/amazon-bedrock.js.map +1 -1
  187. package/dist/providers/claude-max.js +153 -133
  188. package/dist/providers/claude-max.js.map +1 -1
  189. package/dist/providers/github-copilot.js +232 -230
  190. package/dist/providers/github-copilot.js.map +1 -1
  191. package/dist/providers/openai-codex.js +271 -260
  192. package/dist/providers/openai-codex.js.map +1 -1
  193. package/dist/providers/xai.js +57 -50
  194. package/dist/providers/xai.js.map +1 -1
  195. package/dist/schema.js +91 -89
  196. package/dist/schema.js.map +1 -1
  197. package/dist/theme-palette.js +18 -14
  198. package/dist/theme-palette.js.map +1 -1
  199. package/dist/tool-names.js +46 -37
  200. package/dist/tool-names.js.map +1 -1
  201. package/dist/tools/index.js +2 -8
  202. package/dist/tools/request-sandbox-access.js +71 -80
  203. package/dist/tools/request-sandbox-access.js.map +1 -1
  204. package/dist/tools/utils.js +26 -16
  205. package/dist/tools/utils.js.map +1 -1
  206. package/dist/tools/web-search.js +51 -55
  207. package/dist/tools/web-search.js.map +1 -1
  208. package/dist/utils/binaries.js +65 -61
  209. package/dist/utils/binaries.js.map +1 -1
  210. package/dist/utils/debug-log.js +51 -47
  211. package/dist/utils/debug-log.js.map +1 -1
  212. package/dist/utils/errors.js +157 -192
  213. package/dist/utils/errors.js.map +1 -1
  214. package/dist/utils/gateway-sync.js +49 -37
  215. package/dist/utils/gateway-sync.js.map +1 -1
  216. package/dist/utils/path-security.js +7 -6
  217. package/dist/utils/path-security.js.map +1 -1
  218. package/dist/utils/plan-diff.js +58 -39
  219. package/dist/utils/plan-diff.js.map +1 -1
  220. package/dist/utils/plans.js +92 -74
  221. package/dist/utils/plans.js.map +1 -1
  222. package/dist/utils/project.js +318 -256
  223. package/dist/utils/project.js.map +1 -1
  224. package/dist/utils/signals-pubsub.js +117 -99
  225. package/dist/utils/signals-pubsub.js.map +1 -1
  226. package/dist/utils/slash-command-loader.js +118 -154
  227. package/dist/utils/slash-command-loader.js.map +1 -1
  228. package/dist/utils/slash-command-processor.js +92 -83
  229. package/dist/utils/slash-command-processor.js.map +1 -1
  230. package/dist/utils/stdin-pipe.js +87 -63
  231. package/dist/utils/stdin-pipe.js.map +1 -1
  232. package/dist/utils/storage-factory.js +104 -84
  233. package/dist/utils/storage-factory.js.map +1 -1
  234. package/dist/utils/storage-maintenance.js +249 -211
  235. package/dist/utils/storage-maintenance.js.map +1 -1
  236. package/dist/utils/thread-lock.js +96 -92
  237. package/dist/utils/thread-lock.js.map +1 -1
  238. package/dist/utils/token-estimator.js +12 -12
  239. package/dist/utils/token-estimator.js.map +1 -1
  240. package/dist/utils/update-check.d.ts +1 -1
  241. package/dist/utils/update-check.js +341 -259
  242. package/dist/utils/update-check.js.map +1 -1
  243. package/dist/voice/stt-registry.js +131 -113
  244. package/dist/voice/stt-registry.js.map +1 -1
  245. package/package.json +11 -11
  246. package/dist/acp.js.map +0 -1
  247. package/dist/auth/index.js.map +0 -1
  248. package/dist/auth/types.js.map +0 -1
  249. package/dist/evals/index.js.map +0 -1
  250. package/dist/evals/scorers/index.js.map +0 -1
  251. package/dist/headless/index.js.map +0 -1
  252. package/dist/hooks/index.js.map +0 -1
  253. package/dist/lsp/index.js.map +0 -1
  254. package/dist/mcp/index.js.map +0 -1
  255. package/dist/mcp/types.js.map +0 -1
  256. package/dist/onboarding/index.js.map +0 -1
  257. package/dist/plugins/types.js.map +0 -1
  258. package/dist/tools/index.js.map +0 -1
@@ -1,177 +1,141 @@
1
- import { promises as fs } from "node:fs";
2
- import * as path from "node:path";
3
- import { parse as parseYaml } from "yaml";
4
1
  import { DEFAULT_CONFIG_DIR } from "../constants.js";
5
2
  import { isPathWithinRoot } from "./path-security.js";
3
+ import { promises } from "fs";
4
+ import * as path$1 from "path";
5
+ import { parse } from "yaml";
6
+ //#region src/utils/slash-command-loader.ts
7
+ /**
8
+ * Parse a command file and extract metadata and template
9
+ * Supports both frontmatter-based and plain markdown files
10
+ */
6
11
  async function parseCommandFile(filePath, baseDir) {
7
- try {
8
- const content = await fs.readFile(filePath, "utf-8");
9
- const trimmedContent = content.trim();
10
- if (!trimmedContent.startsWith("---")) {
11
- const name2 = baseDir ? extractCommandName(filePath, baseDir) : path.basename(filePath, ".md");
12
- return {
13
- name: name2,
14
- description: "",
15
- template: content,
16
- sourcePath: filePath
17
- };
18
- }
19
- const parts = content.split("---");
20
- if (parts.length < 3) {
21
- return null;
22
- }
23
- const frontmatter = parts[1].trim();
24
- const template = parts.slice(2).join("---").trim();
25
- const metadata = parseYaml(frontmatter);
26
- let name;
27
- if (typeof metadata?.name === "string" && metadata.name) {
28
- name = metadata.name;
29
- } else if (baseDir) {
30
- name = extractCommandName(filePath, baseDir);
31
- } else {
32
- name = path.basename(filePath, ".md");
33
- }
34
- return {
35
- name,
36
- description: typeof metadata?.description === "string" ? metadata.description : "",
37
- template,
38
- sourcePath: filePath,
39
- namespace: typeof metadata?.namespace === "string" ? metadata.namespace : void 0,
40
- goal: metadata?.goal === true
41
- };
42
- } catch (error) {
43
- console.error(`Error parsing command file ${filePath}:`, error);
44
- return null;
45
- }
12
+ try {
13
+ const content = await promises.readFile(filePath, "utf-8");
14
+ if (!content.trim().startsWith("---")) return {
15
+ name: baseDir ? extractCommandName(filePath, baseDir) : path$1.basename(filePath, ".md"),
16
+ description: "",
17
+ template: content,
18
+ sourcePath: filePath
19
+ };
20
+ const parts = content.split("---");
21
+ if (parts.length < 3) return null;
22
+ const frontmatter = parts[1].trim();
23
+ const template = parts.slice(2).join("---").trim();
24
+ const metadata = parse(frontmatter);
25
+ let name;
26
+ if (typeof metadata?.name === "string" && metadata.name) name = metadata.name;
27
+ else if (baseDir) name = extractCommandName(filePath, baseDir);
28
+ else name = path$1.basename(filePath, ".md");
29
+ return {
30
+ name,
31
+ description: typeof metadata?.description === "string" ? metadata.description : "",
32
+ template,
33
+ sourcePath: filePath,
34
+ namespace: typeof metadata?.namespace === "string" ? metadata.namespace : void 0,
35
+ goal: metadata?.goal === true
36
+ };
37
+ } catch (error) {
38
+ console.error(`Error parsing command file ${filePath}:`, error);
39
+ return null;
40
+ }
46
41
  }
42
+ /**
43
+ * Extract command name from file path
44
+ * Converts path like "git/commit.md" to "git:commit"
45
+ */
47
46
  function extractCommandName(filePath, baseDir) {
48
- const relativePath = path.relative(baseDir, filePath);
49
- const dirName = path.dirname(relativePath);
50
- const baseName = path.basename(relativePath, ".md");
51
- if (dirName === "." || dirName === "") {
52
- return baseName;
53
- }
54
- const namespace = dirName.replace(/[\\/]/g, ":");
55
- return `${namespace}:${baseName}`;
47
+ const relativePath = path$1.relative(baseDir, filePath);
48
+ const dirName = path$1.dirname(relativePath);
49
+ const baseName = path$1.basename(relativePath, ".md");
50
+ if (dirName === "." || dirName === "") return baseName;
51
+ return `${dirName.replace(/[\\/]/g, ":")}:${baseName}`;
56
52
  }
57
53
  async function scanCommandDirectory(dirPath, rootDir, options = {}) {
58
- const baseDir = rootDir ?? dirPath;
59
- const commands = [];
60
- const visitedDirectories = options.visitedDirectories ?? /* @__PURE__ */ new Set();
61
- try {
62
- const realDirectory = await fs.realpath(dirPath);
63
- const realAllowedRoot = options.allowedRoot ? await fs.realpath(options.allowedRoot) : void 0;
64
- if (realAllowedRoot && !isPathWithinRoot(realDirectory, realAllowedRoot)) return commands;
65
- if (visitedDirectories.has(realDirectory)) return commands;
66
- visitedDirectories.add(realDirectory);
67
- const entries = await fs.readdir(dirPath, { withFileTypes: true });
68
- for (const entry of entries) {
69
- const fullPath = path.join(dirPath, entry.name);
70
- const stats = entry.isSymbolicLink() ? await fs.stat(fullPath).catch(() => null) : entry;
71
- if (!stats) continue;
72
- if (stats.isDirectory()) {
73
- const subCommands = await scanCommandDirectory(fullPath, baseDir, {
74
- ...options,
75
- visitedDirectories
76
- });
77
- commands.push(...subCommands);
78
- } else if (entry.name.endsWith(".md") && stats.isFile()) {
79
- if (realAllowedRoot) {
80
- const realFile = await fs.realpath(fullPath).catch(() => null);
81
- if (!realFile || !isPathWithinRoot(realFile, realAllowedRoot)) continue;
82
- }
83
- const command = await parseCommandFile(fullPath, baseDir);
84
- if (command) {
85
- commands.push(command);
86
- }
87
- }
88
- }
89
- } catch {
90
- }
91
- return commands;
54
+ const baseDir = rootDir ?? dirPath;
55
+ const commands = [];
56
+ const visitedDirectories = options.visitedDirectories ?? /* @__PURE__ */ new Set();
57
+ try {
58
+ const realDirectory = await promises.realpath(dirPath);
59
+ const realAllowedRoot = options.allowedRoot ? await promises.realpath(options.allowedRoot) : void 0;
60
+ if (realAllowedRoot && !isPathWithinRoot(realDirectory, realAllowedRoot)) return commands;
61
+ if (visitedDirectories.has(realDirectory)) return commands;
62
+ visitedDirectories.add(realDirectory);
63
+ const entries = await promises.readdir(dirPath, { withFileTypes: true });
64
+ for (const entry of entries) {
65
+ const fullPath = path$1.join(dirPath, entry.name);
66
+ const stats = entry.isSymbolicLink() ? await promises.stat(fullPath).catch(() => null) : entry;
67
+ if (!stats) continue;
68
+ if (stats.isDirectory()) {
69
+ const subCommands = await scanCommandDirectory(fullPath, baseDir, {
70
+ ...options,
71
+ visitedDirectories
72
+ });
73
+ commands.push(...subCommands);
74
+ } else if (entry.name.endsWith(".md") && stats.isFile()) {
75
+ if (realAllowedRoot) {
76
+ const realFile = await promises.realpath(fullPath).catch(() => null);
77
+ if (!realFile || !isPathWithinRoot(realFile, realAllowedRoot)) continue;
78
+ }
79
+ const command = await parseCommandFile(fullPath, baseDir);
80
+ if (command) commands.push(command);
81
+ }
82
+ }
83
+ } catch {}
84
+ return commands;
92
85
  }
86
+ /**
87
+ * Load custom slash commands from all configured directories
88
+ * Priority: mastra project > claude project > opencode project > mastra user > claude user > opencode user
89
+ */
93
90
  async function loadCustomCommands(projectDir, configDirName = DEFAULT_CONFIG_DIR, extraCommandDirs = []) {
94
- const commandMap = /* @__PURE__ */ new Map();
95
- const addCommands = (newCommands) => {
96
- for (const cmd of newCommands) {
97
- commandMap.set(cmd.name, cmd);
98
- }
99
- };
100
- const homeDir = process.env.HOME || process.env.USERPROFILE;
101
- if (homeDir) {
102
- const opencodeUserDir = path.join(homeDir, ".opencode", "command");
103
- const opencodeUserCommands = await scanCommandDirectory(opencodeUserDir);
104
- addCommands(opencodeUserCommands);
105
- }
106
- if (homeDir) {
107
- const claudeUserDir = path.join(homeDir, ".claude", "commands");
108
- const claudeUserCommands = await scanCommandDirectory(claudeUserDir);
109
- addCommands(claudeUserCommands);
110
- }
111
- if (homeDir) {
112
- const mastraUserDir = path.join(homeDir, configDirName, "commands");
113
- const mastraUserCommands = await scanCommandDirectory(mastraUserDir);
114
- addCommands(mastraUserCommands);
115
- }
116
- if (projectDir) {
117
- const opencodeProjectDir = path.join(projectDir, ".opencode", "command");
118
- const opencodeProjectCommands = await scanCommandDirectory(opencodeProjectDir, void 0, {
119
- allowedRoot: projectDir
120
- });
121
- addCommands(opencodeProjectCommands);
122
- }
123
- if (projectDir) {
124
- const claudeProjectDir = path.join(projectDir, ".claude", "commands");
125
- const claudeProjectCommands = await scanCommandDirectory(claudeProjectDir, void 0, {
126
- allowedRoot: projectDir
127
- });
128
- addCommands(claudeProjectCommands);
129
- }
130
- if (projectDir) {
131
- const mastraProjectDir = path.join(projectDir, configDirName, "commands");
132
- const mastraProjectCommands = await scanCommandDirectory(mastraProjectDir, void 0, {
133
- allowedRoot: projectDir
134
- });
135
- addCommands(mastraProjectCommands);
136
- }
137
- for (const commandsDir of extraCommandDirs) {
138
- addCommands(await scanCommandDirectory(commandsDir, void 0, { allowedRoot: commandsDir }));
139
- }
140
- return Array.from(commandMap.values());
91
+ const commandMap = /* @__PURE__ */ new Map();
92
+ const addCommands = (newCommands) => {
93
+ for (const cmd of newCommands) commandMap.set(cmd.name, cmd);
94
+ };
95
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
96
+ if (homeDir) addCommands(await scanCommandDirectory(path$1.join(homeDir, ".opencode", "command")));
97
+ if (homeDir) addCommands(await scanCommandDirectory(path$1.join(homeDir, ".claude", "commands")));
98
+ if (homeDir) addCommands(await scanCommandDirectory(path$1.join(homeDir, configDirName, "commands")));
99
+ if (projectDir) addCommands(await scanCommandDirectory(path$1.join(projectDir, ".opencode", "command"), void 0, { allowedRoot: projectDir }));
100
+ if (projectDir) addCommands(await scanCommandDirectory(path$1.join(projectDir, ".claude", "commands"), void 0, { allowedRoot: projectDir }));
101
+ if (projectDir) addCommands(await scanCommandDirectory(path$1.join(projectDir, configDirName, "commands"), void 0, { allowedRoot: projectDir }));
102
+ for (const commandsDir of extraCommandDirs) addCommands(await scanCommandDirectory(commandsDir, void 0, { allowedRoot: commandsDir }));
103
+ return Array.from(commandMap.values());
141
104
  }
105
+ /**
106
+ * Get the commands directory path for a project
107
+ */
142
108
  function getProjectCommandsDir(projectDir, configDirName = DEFAULT_CONFIG_DIR) {
143
- return path.join(projectDir, configDirName, "commands");
109
+ return path$1.join(projectDir, configDirName, "commands");
144
110
  }
111
+ /**
112
+ * Initialize a commands directory with an example command
113
+ */
145
114
  async function initCommandsDirectory(projectDir, configDirName = DEFAULT_CONFIG_DIR) {
146
- const commandsDir = getProjectCommandsDir(projectDir, configDirName);
147
- try {
148
- await fs.mkdir(commandsDir, { recursive: true });
149
- const examplePath = path.join(commandsDir, "example.md");
150
- const exampleContent = `---
115
+ const commandsDir = getProjectCommandsDir(projectDir, configDirName);
116
+ try {
117
+ await promises.mkdir(commandsDir, { recursive: true });
118
+ const examplePath = path$1.join(commandsDir, "example.md");
119
+ const exampleContent = `---
151
120
  name: example
152
121
  description: An example slash command
153
122
  ---
154
123
 
155
124
  This is an example slash command template.
156
- You can use variables like $ARGUMENTS or $1, $2 for positional args.
125
+ You can use variables like \$ARGUMENTS or \$1, \$2 for positional args.
157
126
  You can also include file content with @filename.
158
127
  Shell commands with !command will be executed and output included.
159
128
  `;
160
- try {
161
- await fs.access(examplePath);
162
- } catch {
163
- await fs.writeFile(examplePath, exampleContent, "utf-8");
164
- }
165
- } catch (error) {
166
- console.error("Error initializing commands directory:", error);
167
- }
129
+ try {
130
+ await promises.access(examplePath);
131
+ } catch {
132
+ await promises.writeFile(examplePath, exampleContent, "utf-8");
133
+ }
134
+ } catch (error) {
135
+ console.error("Error initializing commands directory:", error);
136
+ }
168
137
  }
169
- export {
170
- extractCommandName,
171
- getProjectCommandsDir,
172
- initCommandsDirectory,
173
- loadCustomCommands,
174
- parseCommandFile,
175
- scanCommandDirectory
176
- };
138
+ //#endregion
139
+ export { extractCommandName, getProjectCommandsDir, initCommandsDirectory, loadCustomCommands, parseCommandFile, scanCommandDirectory };
140
+
177
141
  //# sourceMappingURL=slash-command-loader.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/slash-command-loader.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport * as path from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport { DEFAULT_CONFIG_DIR } from '../constants.js';\nimport { isPathWithinRoot } from './path-security.js';\n\n/**\n * Metadata for a slash command\n */\nexport interface SlashCommandMetadata {\n /** Command name (e.g., \"git:commit\") */\n name: string;\n /** Human-readable description */\n description: string;\n /** The command template with variables */\n template: string;\n /** Source file path */\n sourcePath: string;\n /** Namespace derived from directory structure */\n namespace?: string;\n /** Whether this command should also be exposed as /goal/<name> */\n goal?: boolean;\n}\n\n/**\n * Parse a command file and extract metadata and template\n * Supports both frontmatter-based and plain markdown files\n */\nexport async function parseCommandFile(filePath: string, baseDir?: string): Promise<SlashCommandMetadata | null> {\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n const trimmedContent = content.trim();\n\n // Check if file has frontmatter (starts with ---)\n if (!trimmedContent.startsWith('---')) {\n // No frontmatter - treat entire file as template\n // Derive name from file path\n const name = baseDir ? extractCommandName(filePath, baseDir) : path.basename(filePath, '.md');\n\n return {\n name,\n description: '',\n template: content,\n sourcePath: filePath,\n };\n }\n\n // Split frontmatter and template\n const parts = content.split('---');\n if (parts.length < 3) {\n return null;\n }\n\n const frontmatter = parts[1]!.trim();\n const template = parts.slice(2).join('---').trim();\n\n // Parse YAML frontmatter\n const metadata = parseYaml(frontmatter) as Record<string, unknown>;\n\n // Derive name from file path if not specified in frontmatter\n let name: string;\n if (typeof metadata?.name === 'string' && metadata.name) {\n name = metadata.name;\n } else if (baseDir) {\n name = extractCommandName(filePath, baseDir);\n } else {\n name = path.basename(filePath, '.md');\n }\n\n return {\n name,\n description: typeof metadata?.description === 'string' ? metadata.description : '',\n template,\n sourcePath: filePath,\n namespace: typeof metadata?.namespace === 'string' ? metadata.namespace : undefined,\n goal: metadata?.goal === true,\n };\n } catch (error) {\n console.error(`Error parsing command file ${filePath}:`, error);\n return null;\n }\n}\n\n/**\n * Extract command name from file path\n * Converts path like \"git/commit.md\" to \"git:commit\"\n */\nexport function extractCommandName(filePath: string, baseDir: string): string {\n const relativePath = path.relative(baseDir, filePath);\n const dirName = path.dirname(relativePath);\n const baseName = path.basename(relativePath, '.md');\n\n if (dirName === '.' || dirName === '') {\n return baseName;\n }\n\n // Replace path separators with colons for namespacing\n const namespace = dirName.replace(/[\\\\/]/g, ':');\n return `${namespace}:${baseName}`;\n}\n\n/**\n * Recursively scan a directory for command files.\n * @param dirPath - Current directory to scan\n * @param rootDir - Original root commands directory (used for namespace derivation).\n * When omitted the first call sets it to dirPath.\n */\nexport interface ScanCommandDirectoryOptions {\n allowedRoot?: string;\n visitedDirectories?: Set<string>;\n}\n\nexport async function scanCommandDirectory(\n dirPath: string,\n rootDir?: string,\n options: ScanCommandDirectoryOptions = {},\n): Promise<SlashCommandMetadata[]> {\n const baseDir = rootDir ?? dirPath;\n const commands: SlashCommandMetadata[] = [];\n const visitedDirectories = options.visitedDirectories ?? new Set<string>();\n\n try {\n const realDirectory = await fs.realpath(dirPath);\n const realAllowedRoot = options.allowedRoot ? await fs.realpath(options.allowedRoot) : undefined;\n if (realAllowedRoot && !isPathWithinRoot(realDirectory, realAllowedRoot)) return commands;\n if (visitedDirectories.has(realDirectory)) return commands;\n visitedDirectories.add(realDirectory);\n\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dirPath, entry.name);\n const stats = entry.isSymbolicLink() ? await fs.stat(fullPath).catch(() => null) : entry;\n if (!stats) continue;\n\n if (stats.isDirectory()) {\n // Recursively scan subdirectories, preserving the root directory for namespace derivation\n const subCommands = await scanCommandDirectory(fullPath, baseDir, {\n ...options,\n visitedDirectories,\n });\n commands.push(...subCommands);\n } else if (entry.name.endsWith('.md') && stats.isFile()) {\n if (realAllowedRoot) {\n const realFile = await fs.realpath(fullPath).catch(() => null);\n if (!realFile || !isPathWithinRoot(realFile, realAllowedRoot)) continue;\n }\n\n // Parse markdown command files, passing the root commands dir as baseDir for name derivation\n const command = await parseCommandFile(fullPath, baseDir);\n if (command) {\n commands.push(command);\n }\n }\n }\n } catch {\n // Directory doesn't exist or can't be read - silently skip\n }\n\n return commands;\n}\n/**\n * Load custom slash commands from all configured directories\n * Priority: mastra project > claude project > opencode project > mastra user > claude user > opencode user\n */\nexport async function loadCustomCommands(\n projectDir?: string,\n configDirName = DEFAULT_CONFIG_DIR,\n extraCommandDirs: string[] = [],\n): Promise<SlashCommandMetadata[]> {\n // Use a Map so later (higher priority) sources override earlier ones with the same name\n const commandMap = new Map<string, SlashCommandMetadata>();\n\n const addCommands = (newCommands: SlashCommandMetadata[]) => {\n for (const cmd of newCommands) {\n commandMap.set(cmd.name, cmd);\n }\n };\n\n const homeDir = process.env.HOME || process.env.USERPROFILE;\n\n // 1. Load from opencode user directory ~/.opencode/command (lowest priority)\n if (homeDir) {\n const opencodeUserDir = path.join(homeDir, '.opencode', 'command');\n const opencodeUserCommands = await scanCommandDirectory(opencodeUserDir);\n addCommands(opencodeUserCommands);\n }\n\n // 2. Load from claude user directory ~/.claude/commands (Claude Code compat)\n if (homeDir) {\n const claudeUserDir = path.join(homeDir, '.claude', 'commands');\n const claudeUserCommands = await scanCommandDirectory(claudeUserDir);\n addCommands(claudeUserCommands);\n }\n\n // 3. Load from mastra user directory ~/<configDirName>/commands\n if (homeDir) {\n const mastraUserDir = path.join(homeDir, configDirName, 'commands');\n const mastraUserCommands = await scanCommandDirectory(mastraUserDir);\n addCommands(mastraUserCommands);\n }\n\n // 4. Load from opencode project directory .opencode/command\n if (projectDir) {\n const opencodeProjectDir = path.join(projectDir, '.opencode', 'command');\n const opencodeProjectCommands = await scanCommandDirectory(opencodeProjectDir, undefined, {\n allowedRoot: projectDir,\n });\n addCommands(opencodeProjectCommands);\n }\n\n // 5. Load from claude project directory .claude/commands (Claude Code compat)\n if (projectDir) {\n const claudeProjectDir = path.join(projectDir, '.claude', 'commands');\n const claudeProjectCommands = await scanCommandDirectory(claudeProjectDir, undefined, {\n allowedRoot: projectDir,\n });\n addCommands(claudeProjectCommands);\n }\n\n // 6. Load from mastra project directory <configDirName>/commands\n if (projectDir) {\n const mastraProjectDir = path.join(projectDir, configDirName, 'commands');\n const mastraProjectCommands = await scanCommandDirectory(mastraProjectDir, undefined, {\n allowedRoot: projectDir,\n });\n addCommands(mastraProjectCommands);\n }\n\n // 7. Load from active plugin command directories (highest priority)\n for (const commandsDir of extraCommandDirs) {\n addCommands(await scanCommandDirectory(commandsDir, undefined, { allowedRoot: commandsDir }));\n }\n\n return Array.from(commandMap.values());\n}\n\n/**\n * Get the commands directory path for a project\n */\nexport function getProjectCommandsDir(projectDir: string, configDirName = DEFAULT_CONFIG_DIR): string {\n return path.join(projectDir, configDirName, 'commands');\n}\n\n/**\n * Initialize a commands directory with an example command\n */\nexport async function initCommandsDirectory(projectDir: string, configDirName = DEFAULT_CONFIG_DIR): Promise<void> {\n const commandsDir = getProjectCommandsDir(projectDir, configDirName);\n\n try {\n await fs.mkdir(commandsDir, { recursive: true });\n\n // Create an example command\n const examplePath = path.join(commandsDir, 'example.md');\n const exampleContent = `---\nname: example\ndescription: An example slash command\n---\n\nThis is an example slash command template.\nYou can use variables like \\$ARGUMENTS or \\$1, \\$2 for positional args.\nYou can also include file content with @filename.\nShell commands with !command will be executed and output included.\n`;\n\n try {\n await fs.access(examplePath);\n // File already exists, don't overwrite\n } catch {\n await fs.writeFile(examplePath, exampleContent, 'utf-8');\n }\n } catch (error) {\n console.error('Error initializing commands directory:', error);\n }\n}\n"],"mappings":"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","names":["name"]}
1
+ {"version":3,"file":"slash-command-loader.js","names":["fs","path","parseYaml"],"sources":["../../src/utils/slash-command-loader.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport * as path from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport { DEFAULT_CONFIG_DIR } from '../constants.js';\nimport { isPathWithinRoot } from './path-security.js';\n\n/**\n * Metadata for a slash command\n */\nexport interface SlashCommandMetadata {\n /** Command name (e.g., \"git:commit\") */\n name: string;\n /** Human-readable description */\n description: string;\n /** The command template with variables */\n template: string;\n /** Source file path */\n sourcePath: string;\n /** Namespace derived from directory structure */\n namespace?: string;\n /** Whether this command should also be exposed as /goal/<name> */\n goal?: boolean;\n}\n\n/**\n * Parse a command file and extract metadata and template\n * Supports both frontmatter-based and plain markdown files\n */\nexport async function parseCommandFile(filePath: string, baseDir?: string): Promise<SlashCommandMetadata | null> {\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n const trimmedContent = content.trim();\n\n // Check if file has frontmatter (starts with ---)\n if (!trimmedContent.startsWith('---')) {\n // No frontmatter - treat entire file as template\n // Derive name from file path\n const name = baseDir ? extractCommandName(filePath, baseDir) : path.basename(filePath, '.md');\n\n return {\n name,\n description: '',\n template: content,\n sourcePath: filePath,\n };\n }\n\n // Split frontmatter and template\n const parts = content.split('---');\n if (parts.length < 3) {\n return null;\n }\n\n const frontmatter = parts[1]!.trim();\n const template = parts.slice(2).join('---').trim();\n\n // Parse YAML frontmatter\n const metadata = parseYaml(frontmatter) as Record<string, unknown>;\n\n // Derive name from file path if not specified in frontmatter\n let name: string;\n if (typeof metadata?.name === 'string' && metadata.name) {\n name = metadata.name;\n } else if (baseDir) {\n name = extractCommandName(filePath, baseDir);\n } else {\n name = path.basename(filePath, '.md');\n }\n\n return {\n name,\n description: typeof metadata?.description === 'string' ? metadata.description : '',\n template,\n sourcePath: filePath,\n namespace: typeof metadata?.namespace === 'string' ? metadata.namespace : undefined,\n goal: metadata?.goal === true,\n };\n } catch (error) {\n console.error(`Error parsing command file ${filePath}:`, error);\n return null;\n }\n}\n\n/**\n * Extract command name from file path\n * Converts path like \"git/commit.md\" to \"git:commit\"\n */\nexport function extractCommandName(filePath: string, baseDir: string): string {\n const relativePath = path.relative(baseDir, filePath);\n const dirName = path.dirname(relativePath);\n const baseName = path.basename(relativePath, '.md');\n\n if (dirName === '.' || dirName === '') {\n return baseName;\n }\n\n // Replace path separators with colons for namespacing\n const namespace = dirName.replace(/[\\\\/]/g, ':');\n return `${namespace}:${baseName}`;\n}\n\n/**\n * Recursively scan a directory for command files.\n * @param dirPath - Current directory to scan\n * @param rootDir - Original root commands directory (used for namespace derivation).\n * When omitted the first call sets it to dirPath.\n */\nexport interface ScanCommandDirectoryOptions {\n allowedRoot?: string;\n visitedDirectories?: Set<string>;\n}\n\nexport async function scanCommandDirectory(\n dirPath: string,\n rootDir?: string,\n options: ScanCommandDirectoryOptions = {},\n): Promise<SlashCommandMetadata[]> {\n const baseDir = rootDir ?? dirPath;\n const commands: SlashCommandMetadata[] = [];\n const visitedDirectories = options.visitedDirectories ?? new Set<string>();\n\n try {\n const realDirectory = await fs.realpath(dirPath);\n const realAllowedRoot = options.allowedRoot ? await fs.realpath(options.allowedRoot) : undefined;\n if (realAllowedRoot && !isPathWithinRoot(realDirectory, realAllowedRoot)) return commands;\n if (visitedDirectories.has(realDirectory)) return commands;\n visitedDirectories.add(realDirectory);\n\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dirPath, entry.name);\n const stats = entry.isSymbolicLink() ? await fs.stat(fullPath).catch(() => null) : entry;\n if (!stats) continue;\n\n if (stats.isDirectory()) {\n // Recursively scan subdirectories, preserving the root directory for namespace derivation\n const subCommands = await scanCommandDirectory(fullPath, baseDir, {\n ...options,\n visitedDirectories,\n });\n commands.push(...subCommands);\n } else if (entry.name.endsWith('.md') && stats.isFile()) {\n if (realAllowedRoot) {\n const realFile = await fs.realpath(fullPath).catch(() => null);\n if (!realFile || !isPathWithinRoot(realFile, realAllowedRoot)) continue;\n }\n\n // Parse markdown command files, passing the root commands dir as baseDir for name derivation\n const command = await parseCommandFile(fullPath, baseDir);\n if (command) {\n commands.push(command);\n }\n }\n }\n } catch {\n // Directory doesn't exist or can't be read - silently skip\n }\n\n return commands;\n}\n/**\n * Load custom slash commands from all configured directories\n * Priority: mastra project > claude project > opencode project > mastra user > claude user > opencode user\n */\nexport async function loadCustomCommands(\n projectDir?: string,\n configDirName = DEFAULT_CONFIG_DIR,\n extraCommandDirs: string[] = [],\n): Promise<SlashCommandMetadata[]> {\n // Use a Map so later (higher priority) sources override earlier ones with the same name\n const commandMap = new Map<string, SlashCommandMetadata>();\n\n const addCommands = (newCommands: SlashCommandMetadata[]) => {\n for (const cmd of newCommands) {\n commandMap.set(cmd.name, cmd);\n }\n };\n\n const homeDir = process.env.HOME || process.env.USERPROFILE;\n\n // 1. Load from opencode user directory ~/.opencode/command (lowest priority)\n if (homeDir) {\n const opencodeUserDir = path.join(homeDir, '.opencode', 'command');\n const opencodeUserCommands = await scanCommandDirectory(opencodeUserDir);\n addCommands(opencodeUserCommands);\n }\n\n // 2. Load from claude user directory ~/.claude/commands (Claude Code compat)\n if (homeDir) {\n const claudeUserDir = path.join(homeDir, '.claude', 'commands');\n const claudeUserCommands = await scanCommandDirectory(claudeUserDir);\n addCommands(claudeUserCommands);\n }\n\n // 3. Load from mastra user directory ~/<configDirName>/commands\n if (homeDir) {\n const mastraUserDir = path.join(homeDir, configDirName, 'commands');\n const mastraUserCommands = await scanCommandDirectory(mastraUserDir);\n addCommands(mastraUserCommands);\n }\n\n // 4. Load from opencode project directory .opencode/command\n if (projectDir) {\n const opencodeProjectDir = path.join(projectDir, '.opencode', 'command');\n const opencodeProjectCommands = await scanCommandDirectory(opencodeProjectDir, undefined, {\n allowedRoot: projectDir,\n });\n addCommands(opencodeProjectCommands);\n }\n\n // 5. Load from claude project directory .claude/commands (Claude Code compat)\n if (projectDir) {\n const claudeProjectDir = path.join(projectDir, '.claude', 'commands');\n const claudeProjectCommands = await scanCommandDirectory(claudeProjectDir, undefined, {\n allowedRoot: projectDir,\n });\n addCommands(claudeProjectCommands);\n }\n\n // 6. Load from mastra project directory <configDirName>/commands\n if (projectDir) {\n const mastraProjectDir = path.join(projectDir, configDirName, 'commands');\n const mastraProjectCommands = await scanCommandDirectory(mastraProjectDir, undefined, {\n allowedRoot: projectDir,\n });\n addCommands(mastraProjectCommands);\n }\n\n // 7. Load from active plugin command directories (highest priority)\n for (const commandsDir of extraCommandDirs) {\n addCommands(await scanCommandDirectory(commandsDir, undefined, { allowedRoot: commandsDir }));\n }\n\n return Array.from(commandMap.values());\n}\n\n/**\n * Get the commands directory path for a project\n */\nexport function getProjectCommandsDir(projectDir: string, configDirName = DEFAULT_CONFIG_DIR): string {\n return path.join(projectDir, configDirName, 'commands');\n}\n\n/**\n * Initialize a commands directory with an example command\n */\nexport async function initCommandsDirectory(projectDir: string, configDirName = DEFAULT_CONFIG_DIR): Promise<void> {\n const commandsDir = getProjectCommandsDir(projectDir, configDirName);\n\n try {\n await fs.mkdir(commandsDir, { recursive: true });\n\n // Create an example command\n const examplePath = path.join(commandsDir, 'example.md');\n const exampleContent = `---\nname: example\ndescription: An example slash command\n---\n\nThis is an example slash command template.\nYou can use variables like \\$ARGUMENTS or \\$1, \\$2 for positional args.\nYou can also include file content with @filename.\nShell commands with !command will be executed and output included.\n`;\n\n try {\n await fs.access(examplePath);\n // File already exists, don't overwrite\n } catch {\n await fs.writeFile(examplePath, exampleContent, 'utf-8');\n }\n } catch (error) {\n console.error('Error initializing commands directory:', error);\n }\n}\n"],"mappings":"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"}
@@ -1,96 +1,105 @@
1
- import { execSync } from "node:child_process";
2
- import { promises as fs } from "node:fs";
3
- import * as path from "node:path";
1
+ import { promises } from "fs";
2
+ import * as path$1 from "path";
3
+ import { execSync } from "child_process";
4
+ //#region src/utils/slash-command-processor.ts
5
+ /**
6
+ * Process a slash command by replacing variables and executing shell commands
7
+ */
4
8
  async function processSlashCommand(command, args, workingDir) {
5
- const { result: withArgs, shouldAppendRawArgs } = replaceArguments(command.template, args);
6
- let result = withArgs;
7
- result = await replaceShellOutput(result, workingDir);
8
- result = await replaceFileReferences(result, workingDir);
9
- if (shouldAppendRawArgs) {
10
- result = result.trimEnd() + `
11
-
12
- ARGUMENTS: ${args.join(" ")}`;
13
- }
14
- return result;
9
+ const { result: withArgs, shouldAppendRawArgs } = replaceArguments(command.template, args);
10
+ let result = withArgs;
11
+ result = await replaceShellOutput(result, workingDir);
12
+ result = await replaceFileReferences(result, workingDir);
13
+ if (shouldAppendRawArgs) result = result.trimEnd() + `\n\nARGUMENTS: ${args.join(" ")}`;
14
+ return result;
15
15
  }
16
+ /**
17
+ * Replace argument variables in template
18
+ * $ARGUMENTS - all arguments joined
19
+ * $1, $2, etc. - positional arguments
20
+ */
16
21
  function replaceArguments(template, args) {
17
- let result = template;
18
- const hasArgumentsVar = /\$ARGUMENTS/.test(template);
19
- const hasPositionalVar = /\$[1-9]\d*/.test(template);
20
- result = result.replace(/\$ARGUMENTS/g, args.join(" "));
21
- args.forEach((_, index) => {
22
- const argNumber = index + 1;
23
- const pattern = new RegExp(`\\$${argNumber}\\+`, "g");
24
- result = result.replace(pattern, args.slice(index).join(" "));
25
- });
26
- args.forEach((arg, index) => {
27
- const pattern = new RegExp(`\\$${index + 1}`, "g");
28
- result = result.replace(pattern, arg);
29
- });
30
- result = result.replace(/\$[1-9]\d*\+?/g, "");
31
- return {
32
- result,
33
- shouldAppendRawArgs: !hasArgumentsVar && !hasPositionalVar && args.length > 0
34
- };
22
+ let result = template;
23
+ const hasArgumentsVar = /\$ARGUMENTS/.test(template);
24
+ const hasPositionalVar = /\$[1-9]\d*/.test(template);
25
+ result = result.replace(/\$ARGUMENTS/g, args.join(" "));
26
+ args.forEach((_, index) => {
27
+ const argNumber = index + 1;
28
+ const pattern = new RegExp(`\\\$${argNumber}\\+`, "g");
29
+ result = result.replace(pattern, args.slice(index).join(" "));
30
+ });
31
+ args.forEach((arg, index) => {
32
+ const pattern = new RegExp(`\\\$${index + 1}`, "g");
33
+ result = result.replace(pattern, arg);
34
+ });
35
+ result = result.replace(/\$[1-9]\d*\+?/g, "");
36
+ return {
37
+ result,
38
+ shouldAppendRawArgs: !hasArgumentsVar && !hasPositionalVar && args.length > 0
39
+ };
35
40
  }
41
+ /**
42
+ * Replace shell command references with their output
43
+ * Format: !`command`
44
+ */
36
45
  async function replaceShellOutput(template, workingDir) {
37
- const shellPattern = /!`([^`]+)`/g;
38
- const matches = [...template.matchAll(shellPattern)];
39
- let result = template;
40
- for (const match of matches) {
41
- const [fullMatch, command] = match;
42
- try {
43
- const output = execSync(command, {
44
- cwd: workingDir,
45
- encoding: "utf-8",
46
- timeout: 3e4,
47
- maxBuffer: 1024 * 1024
48
- // 1MB buffer
49
- });
50
- result = result.replace(fullMatch, output.trim());
51
- } catch (error) {
52
- console.error(`Error executing shell command "${command}":`, error);
53
- result = result.replace(fullMatch, `[Error: Failed to execute "${command}"]`);
54
- }
55
- }
56
- return result;
46
+ const matches = [...template.matchAll(/!`([^`]+)`/g)];
47
+ let result = template;
48
+ for (const match of matches) {
49
+ const [fullMatch, command] = match;
50
+ try {
51
+ const output = execSync(command, {
52
+ cwd: workingDir,
53
+ encoding: "utf-8",
54
+ timeout: 3e4,
55
+ maxBuffer: 1024 * 1024
56
+ });
57
+ result = result.replace(fullMatch, output.trim());
58
+ } catch (error) {
59
+ console.error(`Error executing shell command "${command}":`, error);
60
+ result = result.replace(fullMatch, `[Error: Failed to execute "${command}"]`);
61
+ }
62
+ }
63
+ return result;
57
64
  }
65
+ /**
66
+ * Replace file references with file content
67
+ * Format: @filename or @path/to/file
68
+ */
58
69
  async function replaceFileReferences(template, workingDir) {
59
- const filePattern = /@([\w./-]+)/g;
60
- const matches = [...template.matchAll(filePattern)];
61
- let result = template;
62
- for (const match of matches) {
63
- const [fullMatch, filePath] = match;
64
- try {
65
- const fullPath = path.resolve(workingDir, filePath);
66
- const content = await fs.readFile(fullPath, "utf-8");
67
- result = result.replace(fullMatch, content);
68
- } catch {
69
- }
70
- }
71
- return result;
70
+ const matches = [...template.matchAll(/@([\w./-]+)/g)];
71
+ let result = template;
72
+ for (const match of matches) {
73
+ const [fullMatch, filePath] = match;
74
+ try {
75
+ const fullPath = path$1.resolve(workingDir, filePath);
76
+ const content = await promises.readFile(fullPath, "utf-8");
77
+ result = result.replace(fullMatch, content);
78
+ } catch {}
79
+ }
80
+ return result;
72
81
  }
82
+ /**
83
+ * Format a command for display in help/autocomplete
84
+ */
73
85
  function formatCommandForDisplay(command) {
74
- const parts = [command.name];
75
- if (command.description) {
76
- parts.push(`- ${command.description}`);
77
- }
78
- return parts.join(" ");
86
+ const parts = [command.name];
87
+ if (command.description) parts.push(`- ${command.description}`);
88
+ return parts.join(" ");
79
89
  }
90
+ /**
91
+ * Group commands by namespace for display
92
+ */
80
93
  function groupCommandsByNamespace(commands) {
81
- const groups = /* @__PURE__ */ new Map();
82
- for (const command of commands) {
83
- const namespace = command.namespace || command.name.split(":")[0] || "general";
84
- if (!groups.has(namespace)) {
85
- groups.set(namespace, []);
86
- }
87
- groups.get(namespace).push(command);
88
- }
89
- return groups;
94
+ const groups = /* @__PURE__ */ new Map();
95
+ for (const command of commands) {
96
+ const namespace = command.namespace || command.name.split(":")[0] || "general";
97
+ if (!groups.has(namespace)) groups.set(namespace, []);
98
+ groups.get(namespace).push(command);
99
+ }
100
+ return groups;
90
101
  }
91
- export {
92
- formatCommandForDisplay,
93
- groupCommandsByNamespace,
94
- processSlashCommand
95
- };
102
+ //#endregion
103
+ export { formatCommandForDisplay, groupCommandsByNamespace, processSlashCommand };
104
+
96
105
  //# sourceMappingURL=slash-command-processor.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/slash-command-processor.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { promises as fs } from 'node:fs';\nimport * as path from 'node:path';\nimport type { SlashCommandMetadata } from './slash-command-loader.js';\n\n/**\n * Process a slash command by replacing variables and executing shell commands\n */\nexport async function processSlashCommand(\n command: SlashCommandMetadata,\n args: string[],\n workingDir: string,\n): Promise<string> {\n const { result: withArgs, shouldAppendRawArgs } = replaceArguments(command.template, args);\n let result = withArgs;\n\n // Replace shell commands\n result = await replaceShellOutput(result, workingDir);\n\n // Replace file references\n result = await replaceFileReferences(result, workingDir);\n\n // Append raw args after shell/file processing to avoid executing user input\n if (shouldAppendRawArgs) {\n result = result.trimEnd() + `\\n\\nARGUMENTS: ${args.join(' ')}`;\n }\n\n return result;\n}\n\n/**\n * Replace argument variables in template\n * $ARGUMENTS - all arguments joined\n * $1, $2, etc. - positional arguments\n */\nfunction replaceArguments(template: string, args: string[]): { result: string; shouldAppendRawArgs: boolean } {\n let result = template;\n\n // Check if template references any argument variables\n const hasArgumentsVar = /\\$ARGUMENTS/.test(template);\n const hasPositionalVar = /\\$[1-9]\\d*/.test(template);\n\n // Replace $ARGUMENTS with all args joined\n result = result.replace(/\\$ARGUMENTS/g, args.join(' '));\n\n // Replace range arguments $1+, $2+, etc. before single positional arguments.\n args.forEach((_, index) => {\n const argNumber = index + 1;\n const pattern = new RegExp(`\\\\\\$${argNumber}\\\\+`, 'g');\n result = result.replace(pattern, args.slice(index).join(' '));\n });\n\n // Replace positional arguments $1, $2, etc.\n args.forEach((arg, index) => {\n const pattern = new RegExp(`\\\\\\$${index + 1}`, 'g');\n result = result.replace(pattern, arg);\n });\n\n // Clear unused positional and range arguments\n result = result.replace(/\\$[1-9]\\d*\\+?/g, '');\n\n return {\n result,\n shouldAppendRawArgs: !hasArgumentsVar && !hasPositionalVar && args.length > 0,\n };\n}\n\n/**\n * Replace shell command references with their output\n * Format: !`command`\n */\nasync function replaceShellOutput(template: string, workingDir: string): Promise<string> {\n const shellPattern = /!`([^`]+)`/g;\n const matches = [...template.matchAll(shellPattern)];\n\n let result = template;\n for (const match of matches) {\n const [fullMatch, command] = match;\n try {\n const output = execSync(command!, {\n cwd: workingDir,\n encoding: 'utf-8',\n timeout: 30000,\n maxBuffer: 1024 * 1024, // 1MB buffer\n });\n result = result.replace(fullMatch, output.trim());\n } catch (error) {\n console.error(`Error executing shell command \"${command}\":`, error);\n result = result.replace(fullMatch, `[Error: Failed to execute \"${command}\"]`);\n }\n }\n\n return result;\n}\n\n/**\n * Replace file references with file content\n * Format: @filename or @path/to/file\n */\nasync function replaceFileReferences(template: string, workingDir: string): Promise<string> {\n const filePattern = /@([\\w./-]+)/g;\n const matches = [...template.matchAll(filePattern)];\n\n let result = template;\n for (const match of matches) {\n const [fullMatch, filePath] = match;\n try {\n const fullPath = path.resolve(workingDir, filePath!);\n const content = await fs.readFile(fullPath, 'utf-8');\n result = result.replace(fullMatch, content);\n } catch {\n // Leave literal @mentions/search qualifiers such as @me intact when they do not resolve to files.\n }\n }\n\n return result;\n}\n\n/**\n * Format a command for display in help/autocomplete\n */\nexport function formatCommandForDisplay(command: SlashCommandMetadata): string {\n const parts = [command.name];\n\n if (command.description) {\n parts.push(`- ${command.description}`);\n }\n\n return parts.join(' ');\n}\n\n/**\n * Group commands by namespace for display\n */\nexport function groupCommandsByNamespace(commands: SlashCommandMetadata[]): Map<string, SlashCommandMetadata[]> {\n const groups = new Map<string, SlashCommandMetadata[]>();\n\n for (const command of commands) {\n const namespace = command.namespace || command.name.split(':')[0] || 'general';\n\n if (!groups.has(namespace)) {\n groups.set(namespace, []);\n }\n\n groups.get(namespace)!.push(command);\n }\n\n return groups;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"file":"slash-command-processor.js","names":["path","fs"],"sources":["../../src/utils/slash-command-processor.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { promises as fs } from 'node:fs';\nimport * as path from 'node:path';\nimport type { SlashCommandMetadata } from './slash-command-loader.js';\n\n/**\n * Process a slash command by replacing variables and executing shell commands\n */\nexport async function processSlashCommand(\n command: SlashCommandMetadata,\n args: string[],\n workingDir: string,\n): Promise<string> {\n const { result: withArgs, shouldAppendRawArgs } = replaceArguments(command.template, args);\n let result = withArgs;\n\n // Replace shell commands\n result = await replaceShellOutput(result, workingDir);\n\n // Replace file references\n result = await replaceFileReferences(result, workingDir);\n\n // Append raw args after shell/file processing to avoid executing user input\n if (shouldAppendRawArgs) {\n result = result.trimEnd() + `\\n\\nARGUMENTS: ${args.join(' ')}`;\n }\n\n return result;\n}\n\n/**\n * Replace argument variables in template\n * $ARGUMENTS - all arguments joined\n * $1, $2, etc. - positional arguments\n */\nfunction replaceArguments(template: string, args: string[]): { result: string; shouldAppendRawArgs: boolean } {\n let result = template;\n\n // Check if template references any argument variables\n const hasArgumentsVar = /\\$ARGUMENTS/.test(template);\n const hasPositionalVar = /\\$[1-9]\\d*/.test(template);\n\n // Replace $ARGUMENTS with all args joined\n result = result.replace(/\\$ARGUMENTS/g, args.join(' '));\n\n // Replace range arguments $1+, $2+, etc. before single positional arguments.\n args.forEach((_, index) => {\n const argNumber = index + 1;\n const pattern = new RegExp(`\\\\\\$${argNumber}\\\\+`, 'g');\n result = result.replace(pattern, args.slice(index).join(' '));\n });\n\n // Replace positional arguments $1, $2, etc.\n args.forEach((arg, index) => {\n const pattern = new RegExp(`\\\\\\$${index + 1}`, 'g');\n result = result.replace(pattern, arg);\n });\n\n // Clear unused positional and range arguments\n result = result.replace(/\\$[1-9]\\d*\\+?/g, '');\n\n return {\n result,\n shouldAppendRawArgs: !hasArgumentsVar && !hasPositionalVar && args.length > 0,\n };\n}\n\n/**\n * Replace shell command references with their output\n * Format: !`command`\n */\nasync function replaceShellOutput(template: string, workingDir: string): Promise<string> {\n const shellPattern = /!`([^`]+)`/g;\n const matches = [...template.matchAll(shellPattern)];\n\n let result = template;\n for (const match of matches) {\n const [fullMatch, command] = match;\n try {\n const output = execSync(command!, {\n cwd: workingDir,\n encoding: 'utf-8',\n timeout: 30000,\n maxBuffer: 1024 * 1024, // 1MB buffer\n });\n result = result.replace(fullMatch, output.trim());\n } catch (error) {\n console.error(`Error executing shell command \"${command}\":`, error);\n result = result.replace(fullMatch, `[Error: Failed to execute \"${command}\"]`);\n }\n }\n\n return result;\n}\n\n/**\n * Replace file references with file content\n * Format: @filename or @path/to/file\n */\nasync function replaceFileReferences(template: string, workingDir: string): Promise<string> {\n const filePattern = /@([\\w./-]+)/g;\n const matches = [...template.matchAll(filePattern)];\n\n let result = template;\n for (const match of matches) {\n const [fullMatch, filePath] = match;\n try {\n const fullPath = path.resolve(workingDir, filePath!);\n const content = await fs.readFile(fullPath, 'utf-8');\n result = result.replace(fullMatch, content);\n } catch {\n // Leave literal @mentions/search qualifiers such as @me intact when they do not resolve to files.\n }\n }\n\n return result;\n}\n\n/**\n * Format a command for display in help/autocomplete\n */\nexport function formatCommandForDisplay(command: SlashCommandMetadata): string {\n const parts = [command.name];\n\n if (command.description) {\n parts.push(`- ${command.description}`);\n }\n\n return parts.join(' ');\n}\n\n/**\n * Group commands by namespace for display\n */\nexport function groupCommandsByNamespace(commands: SlashCommandMetadata[]): Map<string, SlashCommandMetadata[]> {\n const groups = new Map<string, SlashCommandMetadata[]>();\n\n for (const command of commands) {\n const namespace = command.namespace || command.name.split(':')[0] || 'general';\n\n if (!groups.has(namespace)) {\n groups.set(namespace, []);\n }\n\n groups.get(namespace)!.push(command);\n }\n\n return groups;\n}\n"],"mappings":";;;;;;;AAQA,eAAsB,oBACpB,SACA,MACA,YACiB;CACjB,MAAM,EAAE,QAAQ,UAAU,wBAAwB,iBAAiB,QAAQ,UAAU,IAAI;CACzF,IAAI,SAAS;CAGb,SAAS,MAAM,mBAAmB,QAAQ,UAAU;CAGpD,SAAS,MAAM,sBAAsB,QAAQ,UAAU;CAGvD,IAAI,qBACF,SAAS,OAAO,QAAQ,IAAI,kBAAkB,KAAK,KAAK,GAAG;CAG7D,OAAO;AACT;;;;;;AAOA,SAAS,iBAAiB,UAAkB,MAAkE;CAC5G,IAAI,SAAS;CAGb,MAAM,kBAAkB,cAAc,KAAK,QAAQ;CACnD,MAAM,mBAAmB,aAAa,KAAK,QAAQ;CAGnD,SAAS,OAAO,QAAQ,gBAAgB,KAAK,KAAK,GAAG,CAAC;CAGtD,KAAK,SAAS,GAAG,UAAU;EACzB,MAAM,YAAY,QAAQ;EAC1B,MAAM,UAAU,IAAI,OAAO,OAAO,UAAU,MAAM,GAAG;EACrD,SAAS,OAAO,QAAQ,SAAS,KAAK,MAAM,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC;CAC9D,CAAC;CAGD,KAAK,SAAS,KAAK,UAAU;EAC3B,MAAM,UAAU,IAAI,OAAO,OAAO,QAAQ,KAAK,GAAG;EAClD,SAAS,OAAO,QAAQ,SAAS,GAAG;CACtC,CAAC;CAGD,SAAS,OAAO,QAAQ,kBAAkB,EAAE;CAE5C,OAAO;EACL;EACA,qBAAqB,CAAC,mBAAmB,CAAC,oBAAoB,KAAK,SAAS;CAC9E;AACF;;;;;AAMA,eAAe,mBAAmB,UAAkB,YAAqC;CAEvF,MAAM,UAAU,CAAC,GAAG,SAAS,SAAS,aAAY,CAAC;CAEnD,IAAI,SAAS;CACb,KAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,CAAC,WAAW,WAAW;EAC7B,IAAI;GACF,MAAM,SAAS,SAAS,SAAU;IAChC,KAAK;IACL,UAAU;IACV,SAAS;IACT,WAAW,OAAO;GACpB,CAAC;GACD,SAAS,OAAO,QAAQ,WAAW,OAAO,KAAK,CAAC;EAClD,SAAS,OAAO;GACd,QAAQ,MAAM,kCAAkC,QAAQ,KAAK,KAAK;GAClE,SAAS,OAAO,QAAQ,WAAW,8BAA8B,QAAQ,GAAG;EAC9E;CACF;CAEA,OAAO;AACT;;;;;AAMA,eAAe,sBAAsB,UAAkB,YAAqC;CAE1F,MAAM,UAAU,CAAC,GAAG,SAAS,SAAS,cAAW,CAAC;CAElD,IAAI,SAAS;CACb,KAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,CAAC,WAAW,YAAY;EAC9B,IAAI;GACF,MAAM,WAAWA,OAAK,QAAQ,YAAY,QAAS;GACnD,MAAM,UAAU,MAAMC,SAAG,SAAS,UAAU,OAAO;GACnD,SAAS,OAAO,QAAQ,WAAW,OAAO;EAC5C,QAAQ,CAER;CACF;CAEA,OAAO;AACT;;;;AAKA,SAAgB,wBAAwB,SAAuC;CAC7E,MAAM,QAAQ,CAAC,QAAQ,IAAI;CAE3B,IAAI,QAAQ,aACV,MAAM,KAAK,KAAK,QAAQ,aAAa;CAGvC,OAAO,MAAM,KAAK,GAAG;AACvB;;;;AAKA,SAAgB,yBAAyB,UAAuE;CAC9G,MAAM,yBAAS,IAAI,IAAoC;CAEvD,KAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,YAAY,QAAQ,aAAa,QAAQ,KAAK,MAAM,GAAG,CAAC,CAAC,MAAM;EAErE,IAAI,CAAC,OAAO,IAAI,SAAS,GACvB,OAAO,IAAI,WAAW,CAAC,CAAC;EAG1B,OAAO,IAAI,SAAS,CAAC,CAAE,KAAK,OAAO;CACrC;CAEA,OAAO;AACT"}