@travisennis/acai 0.0.12 → 0.0.14

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 (242) hide show
  1. package/README.md +5 -2
  2. package/dist/agent/index.d.ts.map +1 -1
  3. package/dist/agent/index.js +91 -5
  4. package/dist/commands/history/types.d.ts +3 -2
  5. package/dist/commands/history/types.d.ts.map +1 -1
  6. package/dist/commands/init-project/utils.d.ts +0 -1
  7. package/dist/commands/init-project/utils.d.ts.map +1 -1
  8. package/dist/commands/init-project/utils.js +1 -1
  9. package/dist/commands/manager.d.ts.map +1 -1
  10. package/dist/commands/manager.js +0 -18
  11. package/dist/commands/share/html-renderer.d.ts.map +1 -1
  12. package/dist/commands/share/html-renderer.js +54 -48
  13. package/dist/commands/tools/index.js +39 -38
  14. package/dist/config/index.d.ts +0 -2
  15. package/dist/config/index.d.ts.map +1 -1
  16. package/dist/config/index.js +3 -7
  17. package/dist/index.d.ts +14 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +94 -76
  20. package/dist/models/manager.d.ts +1 -10
  21. package/dist/models/manager.d.ts.map +1 -1
  22. package/dist/models/manager.js +2 -2
  23. package/dist/models/opencode-go-provider.d.ts +6 -4
  24. package/dist/models/opencode-go-provider.d.ts.map +1 -1
  25. package/dist/models/opencode-go-provider.js +55 -35
  26. package/dist/models/providers.d.ts +1 -1
  27. package/dist/models/providers.d.ts.map +1 -1
  28. package/dist/prompts/system-prompt.d.ts +0 -2
  29. package/dist/prompts/system-prompt.d.ts.map +1 -1
  30. package/dist/prompts/system-prompt.js +6 -10
  31. package/dist/repl/index.d.ts +27 -11
  32. package/dist/repl/index.d.ts.map +1 -1
  33. package/dist/repl/index.js +238 -254
  34. package/dist/sessions/manager.d.ts +41 -0
  35. package/dist/sessions/manager.d.ts.map +1 -1
  36. package/dist/sessions/manager.js +53 -0
  37. package/dist/sessions/summary.d.ts.map +1 -1
  38. package/dist/sessions/summary.js +18 -0
  39. package/dist/skills/index.d.ts +12 -2
  40. package/dist/skills/index.d.ts.map +1 -1
  41. package/dist/skills/index.js +123 -73
  42. package/dist/terminal/control.d.ts +1 -21
  43. package/dist/terminal/control.d.ts.map +1 -1
  44. package/dist/terminal/control.js +1 -32
  45. package/dist/terminal/formatting.d.ts +0 -33
  46. package/dist/terminal/formatting.d.ts.map +1 -1
  47. package/dist/terminal/formatting.js +0 -91
  48. package/dist/terminal/highlight/theme.d.ts +0 -37
  49. package/dist/terminal/highlight/theme.d.ts.map +1 -1
  50. package/dist/terminal/highlight/theme.js +1 -79
  51. package/dist/terminal/keys.d.ts +0 -97
  52. package/dist/terminal/keys.d.ts.map +1 -1
  53. package/dist/terminal/keys.js +0 -194
  54. package/dist/terminal/select-prompt.d.ts.map +1 -1
  55. package/dist/terminal/select-prompt.js +65 -51
  56. package/dist/terminal/style.d.ts +1 -1
  57. package/dist/terminal/style.d.ts.map +1 -1
  58. package/dist/terminal/supports-color.d.ts.map +1 -1
  59. package/dist/terminal/supports-color.js +38 -20
  60. package/dist/terminal/supports-hyperlinks.d.ts +3 -0
  61. package/dist/terminal/supports-hyperlinks.d.ts.map +1 -1
  62. package/dist/terminal/supports-hyperlinks.js +72 -31
  63. package/dist/terminal/table/layout-manager.d.ts +0 -20
  64. package/dist/terminal/table/layout-manager.d.ts.map +1 -1
  65. package/dist/terminal/table/layout-manager.js +68 -44
  66. package/dist/terminal/table/utils.d.ts +0 -1
  67. package/dist/terminal/table/utils.d.ts.map +1 -1
  68. package/dist/terminal/table/utils.js +2 -4
  69. package/dist/tools/apply-patch.d.ts +5 -3
  70. package/dist/tools/apply-patch.d.ts.map +1 -1
  71. package/dist/tools/apply-patch.js +154 -123
  72. package/dist/tools/bash.d.ts.map +1 -1
  73. package/dist/tools/bash.js +11 -8
  74. package/dist/tools/dynamic-tool-loader.d.ts +6 -1
  75. package/dist/tools/dynamic-tool-loader.d.ts.map +1 -1
  76. package/dist/tools/dynamic-tool-loader.js +154 -135
  77. package/dist/tools/index.d.ts +3 -133
  78. package/dist/tools/index.d.ts.map +1 -1
  79. package/dist/tools/index.js +0 -20
  80. package/dist/tools/read-file.d.ts.map +1 -1
  81. package/dist/tools/read-file.js +32 -13
  82. package/dist/tools/skill.d.ts.map +1 -1
  83. package/dist/tools/skill.js +20 -13
  84. package/dist/tools/web-fetch.d.ts +2 -6
  85. package/dist/tools/web-fetch.d.ts.map +1 -1
  86. package/dist/tools/web-fetch.js +59 -36
  87. package/dist/tools/web-search.d.ts +0 -4
  88. package/dist/tools/web-search.d.ts.map +1 -1
  89. package/dist/tools/web-search.js +40 -32
  90. package/dist/tui/autocomplete/utils.d.ts +0 -15
  91. package/dist/tui/autocomplete/utils.d.ts.map +1 -1
  92. package/dist/tui/autocomplete/utils.js +0 -85
  93. package/dist/tui/autocomplete.d.ts +1 -1
  94. package/dist/tui/autocomplete.d.ts.map +1 -1
  95. package/dist/tui/autocomplete.js +1 -1
  96. package/dist/tui/components/editor.d.ts +12 -0
  97. package/dist/tui/components/editor.d.ts.map +1 -1
  98. package/dist/tui/components/editor.js +181 -195
  99. package/dist/tui/components/input.d.ts +4 -0
  100. package/dist/tui/components/input.d.ts.map +1 -1
  101. package/dist/tui/components/input.js +33 -31
  102. package/dist/tui/components/markdown.d.ts +30 -0
  103. package/dist/tui/components/markdown.d.ts.map +1 -1
  104. package/dist/tui/components/markdown.js +274 -242
  105. package/dist/tui/components/modal.d.ts.map +1 -1
  106. package/dist/tui/components/modal.js +11 -11
  107. package/dist/tui/components/select-list.d.ts +5 -0
  108. package/dist/tui/components/select-list.d.ts.map +1 -1
  109. package/dist/tui/components/select-list.js +69 -98
  110. package/dist/tui/components/table.d.ts +6 -0
  111. package/dist/tui/components/table.d.ts.map +1 -1
  112. package/dist/tui/components/table.js +61 -52
  113. package/dist/tui/index.d.ts +1 -8
  114. package/dist/tui/index.d.ts.map +1 -1
  115. package/dist/tui/index.js +0 -8
  116. package/dist/tui/tui.d.ts +9 -0
  117. package/dist/tui/tui.d.ts.map +1 -1
  118. package/dist/tui/tui.js +78 -48
  119. package/dist/tui/utils.d.ts +7 -0
  120. package/dist/tui/utils.d.ts.map +1 -1
  121. package/dist/tui/utils.js +117 -53
  122. package/dist/utils/bash.d.ts.map +1 -1
  123. package/dist/utils/bash.js +156 -104
  124. package/dist/utils/command-protection.d.ts +3 -6
  125. package/dist/utils/command-protection.d.ts.map +1 -1
  126. package/dist/utils/command-protection.js +213 -100
  127. package/dist/utils/filesystem/operations.d.ts +0 -15
  128. package/dist/utils/filesystem/operations.d.ts.map +1 -1
  129. package/dist/utils/filesystem/operations.js +1 -38
  130. package/dist/utils/filesystem/security.d.ts.map +1 -1
  131. package/dist/utils/filesystem/security.js +22 -15
  132. package/dist/utils/filetype-detection.d.ts +0 -1
  133. package/dist/utils/filetype-detection.d.ts.map +1 -1
  134. package/dist/utils/filetype-detection.js +0 -12
  135. package/dist/utils/formatting.d.ts +0 -30
  136. package/dist/utils/formatting.d.ts.map +1 -1
  137. package/dist/utils/formatting.js +0 -44
  138. package/dist/utils/git.d.ts +4 -6
  139. package/dist/utils/git.d.ts.map +1 -1
  140. package/dist/utils/git.js +36 -46
  141. package/dist/utils/ignore.d.ts +1 -1
  142. package/dist/utils/ignore.d.ts.map +1 -1
  143. package/dist/utils/ignore.js +1 -1
  144. package/dist/utils/process.d.ts.map +1 -1
  145. package/dist/utils/process.js +77 -79
  146. package/dist/utils/yaml.d.ts +0 -1
  147. package/dist/utils/yaml.d.ts.map +1 -1
  148. package/dist/utils/yaml.js +80 -89
  149. package/dist/utils/zod.d.ts +0 -3
  150. package/dist/utils/zod.d.ts.map +1 -1
  151. package/dist/utils/zod.js +0 -7
  152. package/package.json +25 -24
  153. package/dist/agent/sub-agent.d.ts +0 -23
  154. package/dist/agent/sub-agent.d.ts.map +0 -1
  155. package/dist/agent/sub-agent.js +0 -109
  156. package/dist/commands/add-directory/index.d.ts +0 -3
  157. package/dist/commands/add-directory/index.d.ts.map +0 -1
  158. package/dist/commands/add-directory/index.js +0 -50
  159. package/dist/commands/add-directory/utils.d.ts +0 -3
  160. package/dist/commands/add-directory/utils.d.ts.map +0 -1
  161. package/dist/commands/add-directory/utils.js +0 -15
  162. package/dist/commands/clear/index.d.ts +0 -3
  163. package/dist/commands/clear/index.d.ts.map +0 -1
  164. package/dist/commands/clear/index.js +0 -13
  165. package/dist/commands/generate-rules/index.d.ts +0 -3
  166. package/dist/commands/generate-rules/index.d.ts.map +0 -1
  167. package/dist/commands/generate-rules/index.js +0 -206
  168. package/dist/commands/generate-rules/service.d.ts +0 -22
  169. package/dist/commands/generate-rules/service.d.ts.map +0 -1
  170. package/dist/commands/generate-rules/service.js +0 -103
  171. package/dist/commands/generate-rules/utils.d.ts +0 -5
  172. package/dist/commands/generate-rules/utils.d.ts.map +0 -1
  173. package/dist/commands/generate-rules/utils.js +0 -25
  174. package/dist/commands/handoff/index.d.ts +0 -3
  175. package/dist/commands/handoff/index.d.ts.map +0 -1
  176. package/dist/commands/handoff/index.js +0 -97
  177. package/dist/commands/handoff/utils.d.ts +0 -4
  178. package/dist/commands/handoff/utils.d.ts.map +0 -1
  179. package/dist/commands/handoff/utils.js +0 -123
  180. package/dist/commands/list-directories/index.d.ts +0 -3
  181. package/dist/commands/list-directories/index.d.ts.map +0 -1
  182. package/dist/commands/list-directories/index.js +0 -35
  183. package/dist/commands/pickup/index.d.ts +0 -3
  184. package/dist/commands/pickup/index.d.ts.map +0 -1
  185. package/dist/commands/pickup/index.js +0 -141
  186. package/dist/commands/pickup/types.d.ts +0 -6
  187. package/dist/commands/pickup/types.d.ts.map +0 -1
  188. package/dist/commands/pickup/types.js +0 -1
  189. package/dist/commands/pickup/utils.d.ts +0 -7
  190. package/dist/commands/pickup/utils.d.ts.map +0 -1
  191. package/dist/commands/pickup/utils.js +0 -56
  192. package/dist/commands/remove-directory/index.d.ts +0 -3
  193. package/dist/commands/remove-directory/index.d.ts.map +0 -1
  194. package/dist/commands/remove-directory/index.js +0 -55
  195. package/dist/commands/review/index.d.ts +0 -3
  196. package/dist/commands/review/index.d.ts.map +0 -1
  197. package/dist/commands/review/index.js +0 -12
  198. package/dist/commands/review/review-panel.d.ts +0 -3
  199. package/dist/commands/review/review-panel.d.ts.map +0 -1
  200. package/dist/commands/review/review-panel.js +0 -186
  201. package/dist/commands/review/utils.d.ts +0 -18
  202. package/dist/commands/review/utils.d.ts.map +0 -1
  203. package/dist/commands/review/utils.js +0 -146
  204. package/dist/commands/shell/index.d.ts +0 -3
  205. package/dist/commands/shell/index.d.ts.map +0 -1
  206. package/dist/commands/shell/index.js +0 -96
  207. package/dist/subagents/index.d.ts +0 -16
  208. package/dist/subagents/index.d.ts.map +0 -1
  209. package/dist/subagents/index.js +0 -231
  210. package/dist/terminal/index.d.ts +0 -9
  211. package/dist/terminal/index.d.ts.map +0 -1
  212. package/dist/terminal/index.js +0 -8
  213. package/dist/tools/agent.d.ts +0 -27
  214. package/dist/tools/agent.d.ts.map +0 -1
  215. package/dist/tools/agent.js +0 -81
  216. package/dist/tools/directory-tree.d.ts +0 -28
  217. package/dist/tools/directory-tree.d.ts.map +0 -1
  218. package/dist/tools/directory-tree.js +0 -154
  219. package/dist/tools/glob.d.ts +0 -35
  220. package/dist/tools/glob.d.ts.map +0 -1
  221. package/dist/tools/glob.js +0 -167
  222. package/dist/tools/grep.d.ts +0 -100
  223. package/dist/tools/grep.d.ts.map +0 -1
  224. package/dist/tools/grep.js +0 -608
  225. package/dist/tools/ls.d.ts +0 -26
  226. package/dist/tools/ls.d.ts.map +0 -1
  227. package/dist/tools/ls.js +0 -83
  228. package/dist/tui/components/header.d.ts +0 -21
  229. package/dist/tui/components/header.d.ts.map +0 -1
  230. package/dist/tui/components/header.js +0 -63
  231. package/dist/utils/bash/parse.d.ts +0 -19
  232. package/dist/utils/bash/parse.d.ts.map +0 -1
  233. package/dist/utils/bash/parse.js +0 -223
  234. package/dist/utils/bash/quote.d.ts +0 -6
  235. package/dist/utils/bash/quote.d.ts.map +0 -1
  236. package/dist/utils/bash/quote.js +0 -23
  237. package/dist/utils/generators.d.ts +0 -3
  238. package/dist/utils/generators.d.ts.map +0 -1
  239. package/dist/utils/generators.js +0 -25
  240. package/dist/utils/glob.d.ts +0 -52
  241. package/dist/utils/glob.d.ts.map +0 -1
  242. package/dist/utils/glob.js +0 -376
@@ -2,6 +2,7 @@
2
2
  * Command Protection Module
3
3
  * Detects and blocks destructive commands that could cause data loss
4
4
  */
5
+ import path from "node:path";
5
6
  /**
6
7
  * Check if a git subcommand appears as an actual command (not inside quoted strings).
7
8
  * This helps avoid false positives when destructive command text appears in
@@ -91,6 +92,34 @@ export function detectDestructiveCommand(command) {
91
92
  }
92
93
  return { blocked: false };
93
94
  }
95
+ /**
96
+ * Check if a git restore command will discard uncommitted changes
97
+ * (restore without --staged and not creating a branch with -b)
98
+ */
99
+ function isDestructiveGitRestore(lowerCommand) {
100
+ if (!lowerCommand.startsWith("git restore"))
101
+ return false;
102
+ if (lowerCommand.includes(" --staged"))
103
+ return false;
104
+ const afterRestore = lowerCommand.substring("git restore".length).trim();
105
+ return !!afterRestore && !afterRestore.startsWith("-b");
106
+ }
107
+ /**
108
+ * Check if a git branch command uses -D (force delete without merge check)
109
+ */
110
+ function isDestructiveGitBranchD(command, lowerCommand) {
111
+ const branchMatch = lowerCommand.match(/git\s+branch\s+-([a-z])/);
112
+ if (!branchMatch)
113
+ return false;
114
+ // Check if the flag in the original command is uppercase
115
+ const flagInOriginal = command.match(/git\s+branch\s+-([A-Za-z])/i);
116
+ if (!flagInOriginal)
117
+ return false;
118
+ if (flagInOriginal[1] !== flagInOriginal[1].toUpperCase())
119
+ return false;
120
+ // Verify this is an actual git branch command, not text inside quotes
121
+ return isActualGitCommand(command, "branch");
122
+ }
94
123
  /**
95
124
  * Detect destructive git commands
96
125
  */
@@ -116,18 +145,13 @@ function detectDestructiveGitCommands(command) {
116
145
  };
117
146
  }
118
147
  // Block git restore without --staged (discards uncommitted changes)
119
- if (lowerCommand.startsWith("git restore") &&
120
- !lowerCommand.includes(" --staged")) {
121
- // Check if it's restoring files (not branches)
122
- const afterRestore = lowerCommand.substring("git restore".length).trim();
123
- if (afterRestore && !afterRestore.startsWith("-b")) {
124
- return {
125
- blocked: true,
126
- reason: "git restore <file> (without --staged) discards uncommitted changes",
127
- command,
128
- tip: "Use 'git restore --staged <file>' to only unstage, or 'git stash' to save changes.",
129
- };
130
- }
148
+ if (isDestructiveGitRestore(lowerCommand)) {
149
+ return {
150
+ blocked: true,
151
+ reason: "git restore <file> (without --staged) discards uncommitted changes",
152
+ command,
153
+ tip: "Use 'git restore --staged <file>' to only unstage, or 'git stash' to save changes.",
154
+ };
131
155
  }
132
156
  // Block git clean -f (force deletes untracked files)
133
157
  if (lowerCommand.includes("git clean -f") ||
@@ -151,27 +175,13 @@ function detectDestructiveGitCommands(command) {
151
175
  };
152
176
  }
153
177
  // Block git branch -D (force delete without merge check)
154
- // Check for uppercase flag on original command (lowercase -d is safe)
155
- // Match 'git' case-insensitively, but preserve case for the flag
156
- // Only match if git branch appears as an actual command (not inside quoted strings)
157
- const branchMatch = lowerCommand.match(/git\s+branch\s+-([a-z])/);
158
- if (branchMatch) {
159
- // Check if the flag in the original command is uppercase
160
- const flagInOriginal = command.match(/git\s+branch\s+-([A-Za-z])/i);
161
- if (flagInOriginal &&
162
- flagInOriginal[1] === flagInOriginal[1].toUpperCase()) {
163
- // Verify this is an actual git branch command, not text inside quotes
164
- // Check if git branch appears at start or after command separators
165
- const isActualCommand = isActualGitCommand(command, "branch");
166
- if (isActualCommand) {
167
- return {
168
- blocked: true,
169
- reason: "git branch -D force-deletes branches without checking if they're merged",
170
- command,
171
- tip: "Use 'git branch -d' (lowercase) to safely delete branches that are merged.",
172
- };
173
- }
174
- }
178
+ if (isDestructiveGitBranchD(command, lowerCommand)) {
179
+ return {
180
+ blocked: true,
181
+ reason: "git branch -D force-deletes branches without checking if they're merged",
182
+ command,
183
+ tip: "Use 'git branch -d' (lowercase) to safely delete branches that are merged.",
184
+ };
175
185
  }
176
186
  // Block git stash drop and git stash clear
177
187
  if (lowerCommand.includes("git stash drop") ||
@@ -189,49 +199,168 @@ function detectDestructiveGitCommands(command) {
189
199
  * Detect dangerous rm -rf commands outside of temporary directories
190
200
  */
191
201
  function detectDangerousRmRf(command) {
192
- // Check for rm -rf pattern (case insensitive)
193
- const rmMatch = command.match(/rm\s+-rf\s+/i);
194
- if (!rmMatch || rmMatch.index === undefined) {
195
- return { blocked: false };
202
+ const tokens = tokenizeShellCommand(command);
203
+ for (let i = 0; i < tokens.length; i++) {
204
+ const token = tokens[i];
205
+ if (!token || token.separator || token.value !== "rm")
206
+ continue;
207
+ const rmArgs = collectCommandArguments(tokens, i + 1);
208
+ if (!isRecursiveForceRm(rmArgs))
209
+ continue;
210
+ const paths = getRmPathArguments(rmArgs);
211
+ if (paths.length === 0)
212
+ continue;
213
+ if (paths.every(isTempDirectoryOnly))
214
+ continue;
215
+ return {
216
+ blocked: true,
217
+ reason: "rm -rf outside of temporary directories can cause permanent data loss",
218
+ command,
219
+ tip: "Only rm -rf is allowed for /tmp/*, /var/tmp/*, or $TMPDIR/* to clean temporary files.",
220
+ };
196
221
  }
197
- // Extract the path argument
198
- const matchIndex = rmMatch.index + rmMatch[0].length;
199
- const afterRmRf = command.substring(matchIndex).trim();
200
- // If no path specified, don't block (will fail naturally)
201
- if (!afterRmRf) {
202
- return { blocked: false };
222
+ return { blocked: false };
223
+ }
224
+ function tokenizeShellCommand(command) {
225
+ const state = {
226
+ tokens: [],
227
+ current: "",
228
+ inSingleQuote: false,
229
+ inDoubleQuote: false,
230
+ escaped: false,
231
+ };
232
+ for (let i = 0; i < command.length; i++) {
233
+ consumeShellChar(state, command[i] ?? "");
234
+ }
235
+ pushShellToken(state);
236
+ return state.tokens;
237
+ }
238
+ function consumeShellChar(state, char) {
239
+ if (state.escaped) {
240
+ state.current += char;
241
+ state.escaped = false;
242
+ return;
243
+ }
244
+ if (char === "\\" && !state.inSingleQuote) {
245
+ state.escaped = true;
246
+ return;
247
+ }
248
+ if (char === "'" && !state.inDoubleQuote) {
249
+ state.inSingleQuote = !state.inSingleQuote;
250
+ return;
251
+ }
252
+ if (char === '"' && !state.inSingleQuote) {
253
+ state.inDoubleQuote = !state.inDoubleQuote;
254
+ return;
203
255
  }
204
- // Get temporary directory paths
205
- const tempDirs = ["/tmp", "/var/tmp", process.env["TMPDIR"] || "/tmp"];
206
- // Check if the path is explicitly targeting only temp directories
207
- // This handles: rm -rf /tmp/*, rm -rf /var/tmp/*, rm -rf $TMPDIR/*, and any subpaths
208
- const isTempDirectoryOnly = tempDirs.some((tempDir) => {
209
- // Check if path starts with temp directory (e.g., /tmp/foo, /tmp/*)
210
- if (afterRmRf === tempDir || afterRmRf.startsWith(`${tempDir}/`)) {
211
- return true;
256
+ if (isTokenBoundary(state, char)) {
257
+ pushShellToken(state);
258
+ if (char === "\n" || isShellSeparator(char)) {
259
+ state.tokens.push({ value: char, separator: true });
212
260
  }
213
- return false;
214
- });
215
- // Also check for $TMPDIR/* pattern (literal strings)
216
- // Using string concatenation to avoid lint warnings about template-like strings
261
+ return;
262
+ }
263
+ state.current += char;
264
+ }
265
+ function isTokenBoundary(state, char) {
266
+ return (!state.inSingleQuote &&
267
+ !state.inDoubleQuote &&
268
+ (/\s/.test(char) || isShellSeparator(char)));
269
+ }
270
+ function pushShellToken(state) {
271
+ if (!state.current)
272
+ return;
273
+ state.tokens.push({ value: state.current, separator: false });
274
+ state.current = "";
275
+ }
276
+ function isShellSeparator(char) {
277
+ return char === ";" || char === "|" || char === "&";
278
+ }
279
+ function collectCommandArguments(tokens, startIndex) {
280
+ const args = [];
281
+ for (let i = startIndex; i < tokens.length; i++) {
282
+ const token = tokens[i];
283
+ if (!token || token.separator)
284
+ break;
285
+ args.push(token.value);
286
+ }
287
+ return args;
288
+ }
289
+ function isRecursiveForceRm(args) {
290
+ const flags = { recursive: false, force: false };
291
+ for (const arg of args) {
292
+ if (arg === "--")
293
+ break;
294
+ updateRmFlagState(flags, arg);
295
+ }
296
+ return flags.recursive && flags.force;
297
+ }
298
+ function updateRmFlagState(flags, arg) {
299
+ if (arg === "--recursive") {
300
+ flags.recursive = true;
301
+ return;
302
+ }
303
+ if (arg === "--force") {
304
+ flags.force = true;
305
+ return;
306
+ }
307
+ if (!/^-[^-]+$/.test(arg))
308
+ return;
309
+ if (arg.includes("r") || arg.includes("R"))
310
+ flags.recursive = true;
311
+ if (arg.includes("f"))
312
+ flags.force = true;
313
+ }
314
+ function getRmPathArguments(args) {
315
+ const paths = [];
316
+ let parsingOptions = true;
317
+ for (const arg of args) {
318
+ if (parsingOptions && arg === "--") {
319
+ parsingOptions = false;
320
+ continue;
321
+ }
322
+ if (parsingOptions && isRmOption(arg))
323
+ continue;
324
+ paths.push(arg);
325
+ }
326
+ return paths;
327
+ }
328
+ function isRmOption(arg) {
329
+ return arg.startsWith("-") && arg !== "-";
330
+ }
331
+ function isTempDirectoryOnly(inputPath) {
217
332
  const tmpDirVar = "$" + "TMPDIR";
218
333
  const tmpDirVarBraces = "$" + "{TMPDIR}";
219
- if (afterRmRf === tmpDirVar ||
220
- afterRmRf.startsWith(`${tmpDirVar}/`) ||
221
- afterRmRf === tmpDirVarBraces ||
222
- afterRmRf.startsWith(`${tmpDirVarBraces}/`)) {
223
- return { blocked: false };
334
+ if (isTempVariablePath(inputPath, tmpDirVar) ||
335
+ isTempVariablePath(inputPath, tmpDirVarBraces)) {
336
+ return true;
224
337
  }
225
- if (isTempDirectoryOnly) {
226
- return { blocked: false };
338
+ if (!inputPath.startsWith("/"))
339
+ return false;
340
+ const normalizedPath = normalizeAbsolutePath(inputPath);
341
+ const tempDirs = ["/tmp", "/var/tmp", process.env["TMPDIR"] || "/tmp"].map(normalizeAbsolutePath);
342
+ return tempDirs.some((tempDir) => normalizedPath === tempDir || normalizedPath.startsWith(`${tempDir}/`));
343
+ }
344
+ function isTempVariablePath(inputPath, variable) {
345
+ if (inputPath !== variable && !inputPath.startsWith(`${variable}/`)) {
346
+ return false;
227
347
  }
228
- // Block any other rm -rf
229
- return {
230
- blocked: true,
231
- reason: "rm -rf outside of temporary directories can cause permanent data loss",
232
- command,
233
- tip: "Only rm -rf is allowed for /tmp/*, /var/tmp/*, or $TMPDIR/* to clean temporary files.",
234
- };
348
+ const suffix = inputPath.slice(variable.length);
349
+ return !suffix.split("/").includes("..");
350
+ }
351
+ function normalizeAbsolutePath(inputPath) {
352
+ return path.posix.normalize(inputPath.replace(/\/+$/, "") || "/");
353
+ }
354
+ function detectDangerousRmRfInText(content) {
355
+ const rmCommandPattern = /\brm\b[^\n;&|)]*/gi;
356
+ let match = rmCommandPattern.exec(content);
357
+ while (match !== null) {
358
+ const rmResult = detectDangerousRmRf(match[0]);
359
+ if (rmResult.blocked)
360
+ return rmResult;
361
+ match = rmCommandPattern.exec(content);
362
+ }
363
+ return { blocked: false };
235
364
  }
236
365
  /**
237
366
  * Detect dangerous patterns in inline scripts (-c flags)
@@ -259,9 +388,6 @@ function detectDangerousInlineScripts(command) {
259
388
  /git\s+push\s+(-f|--force)/i,
260
389
  /git\s+branch\s+-[A-Z]/i,
261
390
  /git\s+stash\s+(drop|clear)/i,
262
- /rm\s+-rf\s+\/home/i,
263
- /rm\s+-rf\s+\/usr/i,
264
- /rm\s+-rf\s+~/i,
265
391
  ];
266
392
  // Check if any destructive pattern is present in the command
267
393
  for (const destructivePattern of destructivePatterns) {
@@ -274,6 +400,15 @@ function detectDangerousInlineScripts(command) {
274
400
  };
275
401
  }
276
402
  }
403
+ const rmResult = detectDangerousRmRfInText(command);
404
+ if (rmResult.blocked) {
405
+ return {
406
+ blocked: true,
407
+ reason: `Inline ${language} script contains destructive operation`,
408
+ command,
409
+ tip: "Review the script content for destructive commands.",
410
+ };
411
+ }
277
412
  }
278
413
  }
279
414
  return { blocked: false };
@@ -362,34 +497,12 @@ function detectDangerousScriptContent(content) {
362
497
  };
363
498
  }
364
499
  }
365
- // Check for dangerous rm -rf patterns (more permissive for scripts, block obvious dangers)
366
- const dangerousRmPatterns = [
367
- /\brm\s+-rf\s+\/[^\s*]*[a-z]/i, // rm -rf /something (but allow /tmp/* patterns)
368
- /\brm\s+-rf\s+\/home/i,
369
- /\brm\s+-rf\s+\/usr/i,
370
- /\brm\s+-rf\s+\/etc/i,
371
- /\brm\s+-rf\s+\/var\s*$/i, // Block /var alone but allow /var/tmp
372
- /\brm\s+-rf\s+~(?!\/)/i, // rm -rf ~ but allow ~/tmp
373
- ];
374
- for (const pattern of dangerousRmPatterns) {
375
- if (pattern.test(content)) {
376
- return {
377
- blocked: true,
378
- tip: "The script contains a dangerous rm -rf command.",
379
- };
380
- }
381
- }
382
- // Check for format string attacks and other dangerous patterns
383
- const dangerousPatterns = [
384
- /\brm\s+-rf\s+\$\w+/i, // rm -rf $VAR (variable expansion could be dangerous)
385
- ];
386
- for (const pattern of dangerousPatterns) {
387
- if (pattern.test(content)) {
388
- return {
389
- blocked: true,
390
- tip: "The script contains a potentially dangerous rm command with variable expansion.",
391
- };
392
- }
500
+ const rmResult = detectDangerousRmRfInText(content);
501
+ if (rmResult.blocked) {
502
+ return {
503
+ blocked: true,
504
+ tip: "The script contains a dangerous rm -rf command.",
505
+ };
393
506
  }
394
507
  return { blocked: false };
395
508
  }
@@ -1,18 +1,3 @@
1
- export declare function toPath(urlOrPath: URL | string): string;
2
- /**
3
- * Converts Windows backslashes to POSIX forward slashes.
4
- * Preserves UNC paths and extended-length paths.
5
- * @param inputPath - Path to convert
6
- * @returns Path with forward slashes
7
- */
8
- export declare function slash(inputPath: string): string;
9
- /**
10
- * Checks if a path exists and is a directory.
11
- * @param filePath - Path to check (URL or string)
12
- * @returns Promise resolving to true if path is a directory
13
- * @throws TypeError if input is invalid
14
- */
15
- export declare function isDirectory(filePath: URL | string): Promise<boolean>;
16
1
  /**
17
2
  * Safely clears all contents of a directory.
18
3
  * @param directoryPath - Path to the directory to clear (URL or string)
@@ -1 +1 @@
1
- {"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../../../source/utils/filesystem/operations.ts"],"names":[],"mappings":"AAIA,wBAAgB,MAAM,CAAC,SAAS,EAAE,GAAG,GAAG,MAAM,GAAG,MAAM,CAEtD;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAS/C;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,QAAQ,EAAE,GAAG,GAAG,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAmB1E;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,aAAa,EAAE,GAAG,GAAG,MAAM,GAC1B,OAAO,CAAC,IAAI,CAAC,CAuGf"}
1
+ {"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../../../source/utils/filesystem/operations.ts"],"names":[],"mappings":"AAQA;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,aAAa,EAAE,GAAG,GAAG,MAAM,GAC1B,OAAO,CAAC,IAAI,CAAC,CAuGf"}
@@ -1,46 +1,9 @@
1
1
  import * as fsPromises from "node:fs/promises";
2
2
  import * as path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- export function toPath(urlOrPath) {
4
+ function toPath(urlOrPath) {
5
5
  return urlOrPath instanceof URL ? fileURLToPath(urlOrPath) : urlOrPath;
6
6
  }
7
- /**
8
- * Converts Windows backslashes to POSIX forward slashes.
9
- * Preserves UNC paths and extended-length paths.
10
- * @param inputPath - Path to convert
11
- * @returns Path with forward slashes
12
- */
13
- export function slash(inputPath) {
14
- const isExtendedLengthPath = inputPath.startsWith("\\\\?\\");
15
- const isUncPath = inputPath.startsWith("\\\\");
16
- if (isExtendedLengthPath || isUncPath) {
17
- return inputPath;
18
- }
19
- return inputPath.replace(/\\/g, "/");
20
- }
21
- /**
22
- * Checks if a path exists and is a directory.
23
- * @param filePath - Path to check (URL or string)
24
- * @returns Promise resolving to true if path is a directory
25
- * @throws TypeError if input is invalid
26
- */
27
- export async function isDirectory(filePath) {
28
- const resolvedPath = path.resolve(toPath(filePath));
29
- if (typeof resolvedPath !== "string" || !resolvedPath.trim()) {
30
- throw new TypeError(`Expected a non-empty string or URL, got ${typeof filePath}`);
31
- }
32
- try {
33
- const stats = await fsPromises.stat(resolvedPath);
34
- return stats.isDirectory();
35
- }
36
- catch (error) {
37
- const err = error;
38
- if (err.code === "ENOENT") {
39
- return false;
40
- }
41
- throw error;
42
- }
43
- }
44
7
  /**
45
8
  * Safely clears all contents of a directory.
46
9
  * @param directoryPath - Path to the directory to clear (URL or string)
@@ -1 +1 @@
1
- {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../../source/utils/filesystem/security.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAsCpD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAK3E;AAoCD,wBAAgB,uBAAuB,CACrC,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,EAAE,GACpB,OAAO,CAIT;AAsCD,wBAAsB,YAAY,CAChC,aAAa,EAAE,MAAM,EACrB,gBAAgB,EAAE,MAAM,GAAG,MAAM,EAAE,EACnC,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,WAAW,CAAA;CAAO,GACtE,OAAO,CAAC,MAAM,CAAC,CAmGjB;AA8BD,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GACjB,IAAI,CAIN"}
1
+ {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../../source/utils/filesystem/security.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAsCpD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAK3E;AAoCD,wBAAgB,uBAAuB,CACrC,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,EAAE,GACpB,OAAO,CAKT;AA2DD,wBAAsB,YAAY,CAChC,aAAa,EAAE,MAAM,EACrB,gBAAgB,EAAE,MAAM,GAAG,MAAM,EAAE,EACnC,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,WAAW,CAAA;CAAO,GACtE,OAAO,CAAC,MAAM,CAAC,CAuFjB;AA8BD,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GACjB,IAAI,CAIN"}
@@ -75,7 +75,10 @@ function isPathWithinBaseDir(requestedPath, baseDir) {
75
75
  }
76
76
  // Check if path is within any of the allowed directories
77
77
  export function isPathWithinAllowedDirs(requestedPath, allowedDirs) {
78
- return allowedDirs.some((allowedDir) => isPathWithinBaseDir(requestedPath, allowedDir));
78
+ return allowedDirs.some((allowedDir) => {
79
+ const expandedDir = expandHome(allowedDir);
80
+ return isPathWithinBaseDir(requestedPath, expandedDir);
81
+ });
79
82
  }
80
83
  async function resolveValidAncestor(startDir, isWithinAllowed) {
81
84
  let current = startDir;
@@ -104,6 +107,21 @@ async function resolveValidAncestor(startDir, isWithinAllowed) {
104
107
  }
105
108
  }
106
109
  }
110
+ async function ensurePathExists(validatedPath, requestedPath, abortSignal) {
111
+ if (abortSignal?.aborted) {
112
+ throw new Error("Path validation aborted during existence check");
113
+ }
114
+ try {
115
+ await fs.stat(validatedPath);
116
+ }
117
+ catch (err) {
118
+ const error = err;
119
+ if (error.code === "ENOENT") {
120
+ throw new Error(`The specified path does not exist: ${requestedPath} (${validatedPath})`);
121
+ }
122
+ throw error;
123
+ }
124
+ }
107
125
  // Security utilities
108
126
  export async function validatePath(requestedPath, allowedDirectory, options = {}) {
109
127
  const { requireExistence = true, abortSignal } = options;
@@ -121,7 +139,8 @@ export async function validatePath(requestedPath, allowedDirectory, options = {}
121
139
  : [allowedDirectory];
122
140
  // Resolve and normalize all allowed directories
123
141
  const normalizedAllowedDirs = await Promise.all(allowedDirectories.map(async (dir) => {
124
- let normalizedDir = normalizePath(path.resolve(dir));
142
+ const expandedDir = expandHome(dir);
143
+ let normalizedDir = normalizePath(path.resolve(expandedDir));
125
144
  // Try to resolve real path for allowedDirectory when it exists to handle symlinked roots
126
145
  try {
127
146
  const stats = await fs.stat(normalizedDir);
@@ -171,19 +190,7 @@ export async function validatePath(requestedPath, allowedDirectory, options = {}
171
190
  }
172
191
  // Now, if requireExistence, check if the path exists
173
192
  if (requireExistence) {
174
- if (abortSignal?.aborted) {
175
- throw new Error("Path validation aborted during existence check");
176
- }
177
- try {
178
- await fs.stat(validatedPath);
179
- }
180
- catch (err) {
181
- const error = err;
182
- if (error.code === "ENOENT") {
183
- throw new Error(`The specified path does not exist: ${requestedPath} (${validatedPath})`);
184
- }
185
- throw error;
186
- }
193
+ await ensurePathExists(validatedPath, requestedPath, abortSignal);
187
194
  }
188
195
  return validatedPath;
189
196
  }
@@ -1,3 +1,2 @@
1
- export declare function getLanguageFromFilePath(filePath: string): string | undefined;
2
1
  export declare function getCodeblockFromFilePath(filePath: string): string | undefined;
3
2
  //# sourceMappingURL=filetype-detection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"filetype-detection.d.ts","sourceRoot":"","sources":["../../source/utils/filetype-detection.ts"],"names":[],"mappings":"AA+FA,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAW5E;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAW7E"}
1
+ {"version":3,"file":"filetype-detection.d.ts","sourceRoot":"","sources":["../../source/utils/filetype-detection.ts"],"names":[],"mappings":"AA+FA,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAW7E"}
@@ -86,18 +86,6 @@ const extensionToLanguageMap = {
86
86
  ".proto": { name: "Protocol Buffers", codeblock: "protobuf" },
87
87
  ".txt": { name: "Text", codeblock: "text" },
88
88
  };
89
- export function getLanguageFromFilePath(filePath) {
90
- const extension = extname(filePath).toLowerCase();
91
- const languageInfo = extension
92
- ? extensionToLanguageMap[extension]
93
- : undefined;
94
- if (languageInfo) {
95
- return languageInfo.name;
96
- }
97
- const filename = basename(filePath).toLowerCase();
98
- const filenameLanguageInfo = extensionToLanguageMap[`.${filename}`];
99
- return filenameLanguageInfo?.name;
100
- }
101
89
  export function getCodeblockFromFilePath(filePath) {
102
90
  const extension = extname(filePath).toLowerCase();
103
91
  const languageInfo = extension
@@ -24,36 +24,6 @@ export type FormatType = "xml" | "markdown" | "bracket";
24
24
  * ```
25
25
  */
26
26
  export declare function formatFile(file: string, content: string, format: FormatType): string;
27
- /**
28
- * Formats URL content with metadata in the specified format.
29
- *
30
- * @param siteUrl - The URL to format
31
- * @param content - The URL content to format
32
- * @param format - The output format type
33
- * @returns Formatted URL content with metadata
34
- * @throws {Error} When an unsupported format is provided
35
- *
36
- * @example
37
- * ```typescript
38
- * formatUrl("https://example.com", "<html>...</html>", "xml")
39
- * // Returns: "<webpage>\n<url>https://example.com</url>\n<content>\n<html>...</html>\n</content>\n</webpage>"
40
- * ```
41
- */
42
- export declare function formatUrl(siteUrl: string, content: string, format: FormatType): string;
43
- /**
44
- * Formats content as a code block with appropriate language identifier.
45
- *
46
- * @param file - The file path used to determine the code block language
47
- * @param content - The code content to format
48
- * @returns Formatted code block with language identifier
49
- *
50
- * @example
51
- * ```typescript
52
- * formatCodeBlock("example.js", "console.log('hello');")
53
- * // Returns: "``` javascript\nconsole.log('hello');\n```"
54
- * ```
55
- */
56
- export declare function formatCodeBlock(file: string, content: string): string;
57
27
  /**
58
28
  * Formats generic content blocks with a name in the specified format.
59
29
  *
@@ -1 +1 @@
1
- {"version":3,"file":"formatting.d.ts","sourceRoot":"","sources":["../../source/utils/formatting.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,UAAU,GAAG,SAAS,CAAC;AAExD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,UAAU,GACjB,MAAM,CAaR;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CACvB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,UAAU,GACjB,MAAM,CAWR;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAIrE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,UAAU,GACjB,MAAM,CAWR;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,KAAG,MACG,CAAC;AAE7C;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,MAAM,KAAG,MASjD,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,cAAc,GAAI,cAAc,MAAM,KAAG,MAuCrD,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,KAAG,MAwC1C,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,UAAU,GAAI,MAAM,IAAI,KAAG,MASvC,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gBAAgB,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MAGvD,CAAC"}
1
+ {"version":3,"file":"formatting.d.ts","sourceRoot":"","sources":["../../source/utils/formatting.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,UAAU,GAAG,SAAS,CAAC;AAExD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,UAAU,GACjB,MAAM,CAaR;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,UAAU,GACjB,MAAM,CAWR;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,KAAG,MACG,CAAC;AAE7C;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,MAAM,KAAG,MASjD,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,cAAc,GAAI,cAAc,MAAM,KAAG,MAuCrD,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,KAAG,MAwC1C,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,UAAU,GAAI,MAAM,IAAI,KAAG,MASvC,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gBAAgB,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MAGvD,CAAC"}
@@ -35,50 +35,6 @@ export function formatFile(file, content, format) {
35
35
  throw new Error(`Unsupported format: ${format}`);
36
36
  }
37
37
  }
38
- /**
39
- * Formats URL content with metadata in the specified format.
40
- *
41
- * @param siteUrl - The URL to format
42
- * @param content - The URL content to format
43
- * @param format - The output format type
44
- * @returns Formatted URL content with metadata
45
- * @throws {Error} When an unsupported format is provided
46
- *
47
- * @example
48
- * ```typescript
49
- * formatUrl("https://example.com", "<html>...</html>", "xml")
50
- * // Returns: "<webpage>\n<url>https://example.com</url>\n<content>\n<html>...</html>\n</content>\n</webpage>"
51
- * ```
52
- */
53
- export function formatUrl(siteUrl, content, format) {
54
- switch (format) {
55
- case "xml":
56
- return `<webpage>\n<url>${siteUrl}</url>\n<content>\n${content}\n</content>\n</webpage>`;
57
- case "markdown":
58
- return `## URL: ${siteUrl}\n${MD_TRIPLE_QUOTE}\n${content}\n${MD_TRIPLE_QUOTE}`;
59
- case "bracket":
60
- return `[url]: ${siteUrl}\n[url content begin]\n${content}\n[url content end]`;
61
- default:
62
- throw new Error(`Unsupported format: ${format}`);
63
- }
64
- }
65
- /**
66
- * Formats content as a code block with appropriate language identifier.
67
- *
68
- * @param file - The file path used to determine the code block language
69
- * @param content - The code content to format
70
- * @returns Formatted code block with language identifier
71
- *
72
- * @example
73
- * ```typescript
74
- * formatCodeBlock("example.js", "console.log('hello');")
75
- * // Returns: "``` javascript\nconsole.log('hello');\n```"
76
- * ```
77
- */
78
- export function formatCodeBlock(file, content) {
79
- const codeBlockName = getCodeblockFromFilePath(file) || path.extname(file).slice(1);
80
- return `${MD_TRIPLE_QUOTE} ${codeBlockName}\n${content}\n${MD_TRIPLE_QUOTE}`;
81
- }
82
38
  /**
83
39
  * Formats generic content blocks with a name in the specified format.
84
40
  *