sparkecoder 0.1.9 → 0.1.11

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 (180) hide show
  1. package/dist/agent/index.js +768 -34
  2. package/dist/agent/index.js.map +1 -1
  3. package/dist/cli.js +850 -120
  4. package/dist/cli.js.map +1 -1
  5. package/dist/index.d.ts +2 -2
  6. package/dist/index.js +831 -106
  7. package/dist/index.js.map +1 -1
  8. package/dist/server/index.js +831 -106
  9. package/dist/server/index.js.map +1 -1
  10. package/dist/tools/index.d.ts +56 -34
  11. package/dist/tools/index.js +866 -127
  12. package/dist/tools/index.js.map +1 -1
  13. package/package.json +2 -1
  14. package/web/.next/BUILD_ID +1 -1
  15. package/web/.next/build-manifest.json +2 -2
  16. package/web/.next/cache/.previewinfo +1 -1
  17. package/web/.next/cache/.rscinfo +1 -1
  18. package/web/.next/cache/.tsbuildinfo +1 -1
  19. package/web/.next/cache/config.json +3 -3
  20. package/web/.next/dev/cache/.rscinfo +1 -1
  21. package/web/.next/dev/cache/config.json +3 -3
  22. package/web/.next/dev/cache/turbopack/731ace46/00000001.sst +0 -0
  23. package/web/.next/dev/cache/turbopack/731ace46/00000002.sst +0 -0
  24. package/web/.next/dev/cache/turbopack/731ace46/00000004.sst +0 -0
  25. package/web/.next/dev/cache/turbopack/731ace46/00000005.sst +0 -0
  26. package/web/.next/dev/cache/turbopack/731ace46/00000007.meta +0 -0
  27. package/web/.next/dev/cache/turbopack/731ace46/00000008.meta +0 -0
  28. package/web/.next/dev/cache/turbopack/731ace46/00000010.meta +0 -0
  29. package/web/.next/dev/cache/turbopack/731ace46/00000011.sst +0 -0
  30. package/web/.next/dev/cache/turbopack/731ace46/00000012.sst +0 -0
  31. package/web/.next/dev/cache/turbopack/731ace46/00000014.sst +0 -0
  32. package/web/.next/dev/cache/turbopack/731ace46/00000015.sst +0 -0
  33. package/web/.next/dev/cache/turbopack/731ace46/00000017.meta +0 -0
  34. package/web/.next/dev/cache/turbopack/731ace46/00000018.meta +0 -0
  35. package/web/.next/dev/cache/turbopack/731ace46/00000019.meta +0 -0
  36. package/web/.next/dev/cache/turbopack/731ace46/00000020.meta +0 -0
  37. package/web/.next/dev/cache/turbopack/731ace46/00000021.sst +0 -0
  38. package/web/.next/dev/cache/turbopack/731ace46/00000022.sst +0 -0
  39. package/web/.next/dev/cache/turbopack/731ace46/00000023.sst +0 -0
  40. package/web/.next/dev/cache/turbopack/731ace46/{00000029.meta → 00000025.meta} +0 -0
  41. package/web/.next/dev/cache/turbopack/731ace46/00000026.meta +0 -0
  42. package/web/.next/dev/cache/turbopack/731ace46/00000027.sst +0 -0
  43. package/web/.next/dev/cache/turbopack/731ace46/00000028.sst +0 -0
  44. package/web/.next/dev/cache/turbopack/731ace46/00000029.sst +0 -0
  45. package/web/.next/dev/cache/turbopack/731ace46/00000030.meta +0 -0
  46. package/web/.next/dev/cache/turbopack/731ace46/{00000039.meta → 00000031.meta} +0 -0
  47. package/web/.next/dev/cache/turbopack/731ace46/{00000040.meta → 00000032.meta} +0 -0
  48. package/web/.next/dev/cache/turbopack/731ace46/00000033.sst +0 -0
  49. package/web/.next/dev/cache/turbopack/731ace46/00000034.sst +0 -0
  50. package/web/.next/dev/cache/turbopack/731ace46/00000035.sst +0 -0
  51. package/web/.next/dev/cache/turbopack/731ace46/00000036.meta +0 -0
  52. package/web/.next/dev/cache/turbopack/731ace46/00000037.meta +0 -0
  53. package/web/.next/dev/cache/turbopack/731ace46/00000038.meta +0 -0
  54. package/web/.next/dev/cache/turbopack/731ace46/00000039.sst +0 -0
  55. package/web/.next/dev/cache/turbopack/731ace46/00000040.sst +0 -0
  56. package/web/.next/dev/cache/turbopack/731ace46/00000041.sst +0 -0
  57. package/web/.next/dev/cache/turbopack/731ace46/00000042.meta +0 -0
  58. package/web/.next/dev/cache/turbopack/731ace46/{00000045.meta → 00000043.meta} +0 -0
  59. package/web/.next/dev/cache/turbopack/731ace46/00000044.meta +0 -0
  60. package/web/.next/dev/cache/turbopack/731ace46/00000045.sst +0 -0
  61. package/web/.next/dev/cache/turbopack/731ace46/00000046.sst +0 -0
  62. package/web/.next/dev/cache/turbopack/731ace46/00000047.sst +0 -0
  63. package/web/.next/dev/cache/turbopack/731ace46/00000048.meta +0 -0
  64. package/web/.next/dev/cache/turbopack/731ace46/00000049.meta +0 -0
  65. package/web/.next/dev/cache/turbopack/731ace46/00000050.meta +0 -0
  66. package/web/.next/dev/cache/turbopack/731ace46/00000051.sst +0 -0
  67. package/web/.next/dev/cache/turbopack/731ace46/00000052.sst +0 -0
  68. package/web/.next/dev/cache/turbopack/731ace46/00000053.sst +0 -0
  69. package/web/.next/dev/cache/turbopack/731ace46/00000054.meta +0 -0
  70. package/web/.next/dev/cache/turbopack/731ace46/00000055.meta +0 -0
  71. package/web/.next/dev/cache/turbopack/731ace46/00000056.meta +0 -0
  72. package/web/.next/dev/cache/turbopack/731ace46/00000057.sst +0 -0
  73. package/web/.next/dev/cache/turbopack/731ace46/00000058.sst +0 -0
  74. package/web/.next/dev/cache/turbopack/731ace46/00000059.sst +0 -0
  75. package/web/.next/dev/cache/turbopack/731ace46/00000060.meta +0 -0
  76. package/web/.next/dev/cache/turbopack/731ace46/00000061.meta +0 -0
  77. package/web/.next/dev/cache/turbopack/731ace46/00000062.meta +0 -0
  78. package/web/.next/dev/cache/turbopack/731ace46/00000063.sst +0 -0
  79. package/web/.next/dev/cache/turbopack/731ace46/{00000042.sst → 00000064.sst} +0 -0
  80. package/web/.next/dev/cache/turbopack/731ace46/00000065.sst +0 -0
  81. package/web/.next/dev/cache/turbopack/731ace46/00000066.meta +0 -0
  82. package/web/.next/dev/cache/turbopack/731ace46/00000067.meta +0 -0
  83. package/web/.next/dev/cache/turbopack/731ace46/00000068.meta +0 -0
  84. package/web/.next/dev/cache/turbopack/731ace46/00000069.sst +0 -0
  85. package/web/.next/dev/cache/turbopack/731ace46/00000070.sst +0 -0
  86. package/web/.next/dev/cache/turbopack/731ace46/00000071.sst +0 -0
  87. package/web/.next/dev/cache/turbopack/731ace46/00000072.meta +0 -0
  88. package/web/.next/dev/cache/turbopack/731ace46/00000073.meta +0 -0
  89. package/web/.next/dev/cache/turbopack/731ace46/00000074.meta +0 -0
  90. package/web/.next/dev/cache/turbopack/731ace46/00000075.sst +0 -0
  91. package/web/.next/dev/cache/turbopack/731ace46/00000076.sst +0 -0
  92. package/web/.next/dev/cache/turbopack/731ace46/00000077.sst +0 -0
  93. package/web/.next/dev/cache/turbopack/731ace46/00000078.meta +0 -0
  94. package/web/.next/dev/cache/turbopack/731ace46/00000079.meta +0 -0
  95. package/web/.next/dev/cache/turbopack/731ace46/00000080.meta +0 -0
  96. package/web/.next/dev/cache/turbopack/731ace46/CURRENT +0 -0
  97. package/web/.next/dev/cache/turbopack/731ace46/LOG +68 -30
  98. package/web/.next/dev/prerender-manifest.json +3 -3
  99. package/web/.next/dev/server/server-reference-manifest.js +1 -1
  100. package/web/.next/dev/server/server-reference-manifest.json +1 -1
  101. package/web/.next/dev/trace +1 -1
  102. package/web/.next/fallback-build-manifest.json +2 -2
  103. package/web/.next/prerender-manifest.json +3 -3
  104. package/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  105. package/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
  106. package/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  107. package/web/.next/server/app/_global-error.html +2 -2
  108. package/web/.next/server/app/_global-error.rsc +1 -1
  109. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  110. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  111. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  112. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  113. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  114. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  115. package/web/.next/server/app/_not-found.html +1 -1
  116. package/web/.next/server/app/_not-found.rsc +2 -2
  117. package/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  118. package/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  119. package/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  120. package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  121. package/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  122. package/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  123. package/web/.next/server/app/index.html +1 -1
  124. package/web/.next/server/app/index.rsc +4 -4
  125. package/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
  126. package/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
  127. package/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
  128. package/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  129. package/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
  130. package/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  131. package/web/.next/server/chunks/[root-of-the-server]__4e36aa92._.js +1 -1
  132. package/web/.next/server/chunks/[root-of-the-server]__4e36aa92._.js.map +1 -1
  133. package/web/.next/server/chunks/ssr/{2374f__pnpm_aa2ce93b._.js → 2374f__pnpm_43c403e8._.js} +1 -1
  134. package/web/.next/server/chunks/ssr/{2374f__pnpm_2f0d7c45._.js → 2374f__pnpm_5f7087cf._.js} +1 -1
  135. package/web/.next/server/chunks/ssr/{2374f__pnpm_f4abb025._.js → 2374f__pnpm_7cd13576._.js} +1 -1
  136. package/web/.next/server/chunks/ssr/{2374f__pnpm_c7feb274._.js → 2374f__pnpm_9dd8b322._.js} +1 -1
  137. package/web/.next/server/chunks/ssr/{2374f__pnpm_eb87394d._.js → 2374f__pnpm_ca8b3ccf._.js} +2 -2
  138. package/web/.next/server/chunks/ssr/{2374f__pnpm_e04ff5e3._.js → 2374f__pnpm_ce00fd2a._.js} +1 -1
  139. package/web/.next/server/chunks/ssr/{2374f__pnpm_d7f11ceb._.js → 2374f__pnpm_e395e0b5._.js} +1 -1
  140. package/web/.next/server/chunks/ssr/{2374f__pnpm_c03e1359._.js → 2374f__pnpm_f1702ecf._.js} +1 -1
  141. package/web/.next/server/chunks/ssr/{[root-of-the-server]__2dfcd951._.js → [root-of-the-server]__805216cc._.js} +2 -2
  142. package/web/.next/server/chunks/ssr/[root-of-the-server]__805216cc._.js.map +1 -0
  143. package/web/.next/server/chunks/ssr/web_1658d276._.js +1 -1
  144. package/web/.next/server/chunks/ssr/web_1658d276._.js.map +1 -1
  145. package/web/.next/server/chunks/ssr/web_38b8ac39._.js +1 -1
  146. package/web/.next/server/chunks/ssr/web_38b8ac39._.js.map +1 -1
  147. package/web/.next/server/chunks/ssr/web_aaaa7872._.js +1 -1
  148. package/web/.next/server/chunks/ssr/web_aaaa7872._.js.map +1 -1
  149. package/web/.next/server/pages/404.html +1 -1
  150. package/web/.next/server/pages/500.html +2 -2
  151. package/web/.next/server/server-reference-manifest.js +1 -1
  152. package/web/.next/server/server-reference-manifest.json +1 -1
  153. package/web/.next/static/chunks/33150aab64afe27a.css +1 -0
  154. package/web/.next/static/chunks/{64d14a57d96d13ab.js → 62358f9ae3ca4fc3.js} +1 -1
  155. package/web/.next/static/chunks/{f7a8e0e717eeadf2.js → 7441934619c44e53.js} +1 -1
  156. package/web/.next/static/chunks/{dd0466c07795b288.js → 75df31f521796a31.js} +1 -1
  157. package/web/.next/static/chunks/{bf853305cd7a9765.js → fa06da28948df001.js} +3 -3
  158. package/web/.next/trace +1 -1
  159. package/web/.next/trace-build +1 -1
  160. package/web/.next/dev/cache/turbopack/731ace46/00000024.sst +0 -0
  161. package/web/.next/dev/cache/turbopack/731ace46/00000025.sst +0 -0
  162. package/web/.next/dev/cache/turbopack/731ace46/00000028.meta +0 -0
  163. package/web/.next/dev/cache/turbopack/731ace46/00000031.sst +0 -0
  164. package/web/.next/dev/cache/turbopack/731ace46/00000032.sst +0 -0
  165. package/web/.next/dev/cache/turbopack/731ace46/00000043.sst +0 -0
  166. package/web/.next/dev/cache/turbopack/731ace46/00000046.meta +0 -0
  167. package/web/.next/server/chunks/ssr/[root-of-the-server]__2dfcd951._.js.map +0 -1
  168. package/web/.next/static/chunks/7b140db694582cde.css +0 -1
  169. /package/web/.next/dev/cache/turbopack/731ace46/{00000027.meta → 00000024.meta} +0 -0
  170. /package/web/.next/server/chunks/ssr/{2374f__pnpm_aa2ce93b._.js.map → 2374f__pnpm_43c403e8._.js.map} +0 -0
  171. /package/web/.next/server/chunks/ssr/{2374f__pnpm_2f0d7c45._.js.map → 2374f__pnpm_5f7087cf._.js.map} +0 -0
  172. /package/web/.next/server/chunks/ssr/{2374f__pnpm_f4abb025._.js.map → 2374f__pnpm_7cd13576._.js.map} +0 -0
  173. /package/web/.next/server/chunks/ssr/{2374f__pnpm_c7feb274._.js.map → 2374f__pnpm_9dd8b322._.js.map} +0 -0
  174. /package/web/.next/server/chunks/ssr/{2374f__pnpm_eb87394d._.js.map → 2374f__pnpm_ca8b3ccf._.js.map} +0 -0
  175. /package/web/.next/server/chunks/ssr/{2374f__pnpm_e04ff5e3._.js.map → 2374f__pnpm_ce00fd2a._.js.map} +0 -0
  176. /package/web/.next/server/chunks/ssr/{2374f__pnpm_d7f11ceb._.js.map → 2374f__pnpm_e395e0b5._.js.map} +0 -0
  177. /package/web/.next/server/chunks/ssr/{2374f__pnpm_c03e1359._.js.map → 2374f__pnpm_f1702ecf._.js.map} +0 -0
  178. /package/web/.next/static/{O9K30qQQXDEvnesMUBQxo → szyAyOMgs48yag2cwDbXh}/_buildManifest.js +0 -0
  179. /package/web/.next/static/{O9K30qQQXDEvnesMUBQxo → szyAyOMgs48yag2cwDbXh}/_clientMiddlewareManifest.json +0 -0
  180. /package/web/.next/static/{O9K30qQQXDEvnesMUBQxo → szyAyOMgs48yag2cwDbXh}/_ssgManifest.js +0 -0
@@ -2,11 +2,11 @@
2
2
  import {
3
3
  streamText,
4
4
  generateText as generateText2,
5
- tool as tool6,
5
+ tool as tool7,
6
6
  stepCountIs
7
7
  } from "ai";
8
8
  import { gateway as gateway2 } from "@ai-sdk/gateway";
9
- import { z as z7 } from "zod";
9
+ import { z as z8 } from "zod";
10
10
  import { nanoid as nanoid3 } from "nanoid";
11
11
 
12
12
  // src/db/index.ts
@@ -480,6 +480,17 @@ var SparkcoderConfigSchema = z.object({
480
480
  });
481
481
 
482
482
  // src/config/index.ts
483
+ function getAppDataDirectory() {
484
+ const appName = "sparkecoder";
485
+ switch (platform()) {
486
+ case "darwin":
487
+ return join(homedir(), "Library", "Application Support", appName);
488
+ case "win32":
489
+ return join(process.env.APPDATA || join(homedir(), "AppData", "Roaming"), appName);
490
+ default:
491
+ return join(process.env.XDG_DATA_HOME || join(homedir(), ".local", "share"), appName);
492
+ }
493
+ }
483
494
  var cachedConfig = null;
484
495
  function getConfig() {
485
496
  if (!cachedConfig) {
@@ -541,12 +552,12 @@ function calculateContextSize(messages2) {
541
552
  import { exec } from "child_process";
542
553
  import { promisify } from "util";
543
554
  import { mkdir, writeFile, readFile } from "fs/promises";
544
- import { existsSync as existsSync2 } from "fs";
555
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
545
556
  import { join as join2 } from "path";
546
557
  import { nanoid as nanoid2 } from "nanoid";
547
558
  var execAsync = promisify(exec);
548
559
  var SESSION_PREFIX = "spark_";
549
- var LOG_BASE_DIR = ".sparkecoder/sessions";
560
+ var LOG_BASE_DIR = "sessions";
550
561
  var tmuxAvailableCache = null;
551
562
  async function isTmuxAvailable() {
552
563
  if (tmuxAvailableCache !== null) {
@@ -568,11 +579,19 @@ function generateTerminalId() {
568
579
  function getSessionName(terminalId) {
569
580
  return `${SESSION_PREFIX}${terminalId}`;
570
581
  }
571
- function getLogDir(terminalId, workingDirectory, sessionId) {
582
+ function getTerminalDataDir() {
583
+ const appDataDir = getAppDataDirectory();
584
+ if (!existsSync2(appDataDir)) {
585
+ mkdirSync2(appDataDir, { recursive: true });
586
+ }
587
+ return appDataDir;
588
+ }
589
+ function getLogDir(terminalId, _workingDirectory, sessionId) {
590
+ const baseDir = getTerminalDataDir();
572
591
  if (sessionId) {
573
- return join2(workingDirectory, LOG_BASE_DIR, sessionId, "terminals", terminalId);
592
+ return join2(baseDir, LOG_BASE_DIR, sessionId, "terminals", terminalId);
574
593
  }
575
- return join2(workingDirectory, ".sparkecoder/terminals", terminalId);
594
+ return join2(baseDir, "terminals", terminalId);
576
595
  }
577
596
  function shellEscape(str) {
578
597
  return `'${str.replace(/'/g, "'\\''")}'`;
@@ -888,7 +907,7 @@ bash({ id: "abc123", input: "my text" }) // send text input
888
907
  - For npm: add --yes or -y to skip confirmation
889
908
  - If prompts are unavoidable, run in background mode and use input/key to respond
890
909
 
891
- Logs are saved to .sparkecoder/terminals/{id}/output.log`,
910
+ Terminal output is stored in the global SparkECoder data directory. Use the \`tail\` option to read recent output.`,
892
911
  inputSchema: bashInputSchema,
893
912
  execute: async (inputArgs) => {
894
913
  const { command, background, id, kill, tail, input: textInput, key } = inputArgs;
@@ -1127,9 +1146,9 @@ Use this to understand existing code, check file contents, or gather context.`,
1127
1146
  // src/tools/write-file.ts
1128
1147
  import { tool as tool3 } from "ai";
1129
1148
  import { z as z4 } from "zod";
1130
- import { readFile as readFile4, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
1131
- import { resolve as resolve4, relative as relative3, isAbsolute as isAbsolute2, dirname as dirname3 } from "path";
1132
- import { existsSync as existsSync5 } from "fs";
1149
+ import { readFile as readFile5, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
1150
+ import { resolve as resolve5, relative as relative3, isAbsolute as isAbsolute2, dirname as dirname5 } from "path";
1151
+ import { existsSync as existsSync7 } from "fs";
1133
1152
 
1134
1153
  // src/checkpoints/index.ts
1135
1154
  import { readFile as readFile3, writeFile as writeFile2, unlink, mkdir as mkdir2 } from "fs/promises";
@@ -1182,6 +1201,501 @@ async function backupFile(sessionId, workingDirectory, filePath) {
1182
1201
  return backup;
1183
1202
  }
1184
1203
 
1204
+ // src/lsp/index.ts
1205
+ import { extname as extname2, dirname as dirname4 } from "path";
1206
+
1207
+ // src/lsp/servers.ts
1208
+ import { spawn } from "child_process";
1209
+ import { existsSync as existsSync5 } from "fs";
1210
+ import { resolve as resolve4, dirname as dirname3 } from "path";
1211
+ function findNearestRoot(startDir, markers) {
1212
+ let dir = startDir;
1213
+ const root = "/";
1214
+ while (dir !== root) {
1215
+ for (const marker of markers) {
1216
+ if (existsSync5(resolve4(dir, marker))) {
1217
+ return dir;
1218
+ }
1219
+ }
1220
+ const parent = dirname3(dir);
1221
+ if (parent === dir) break;
1222
+ dir = parent;
1223
+ }
1224
+ return null;
1225
+ }
1226
+ async function commandExists(cmd) {
1227
+ try {
1228
+ const { exec: exec4 } = await import("child_process");
1229
+ const { promisify: promisify4 } = await import("util");
1230
+ const execAsync4 = promisify4(exec4);
1231
+ const isWindows = process.platform === "win32";
1232
+ const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
1233
+ await execAsync4(checkCmd);
1234
+ return true;
1235
+ } catch {
1236
+ return false;
1237
+ }
1238
+ }
1239
+ var TypeScriptServer = {
1240
+ id: "typescript",
1241
+ name: "TypeScript Language Server",
1242
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
1243
+ async spawn(root) {
1244
+ const projectRoot = findNearestRoot(root, [
1245
+ "package-lock.json",
1246
+ "pnpm-lock.yaml",
1247
+ "yarn.lock",
1248
+ "bun.lockb",
1249
+ "bun.lock"
1250
+ ]) || root;
1251
+ const hasNpx = await commandExists("npx");
1252
+ const hasBunx = await commandExists("bunx");
1253
+ const hasPnpx = await commandExists("pnpx");
1254
+ let cmd;
1255
+ if (hasPnpx) {
1256
+ cmd = ["pnpx", "typescript-language-server", "--stdio"];
1257
+ } else if (hasBunx) {
1258
+ cmd = ["bunx", "typescript-language-server", "--stdio"];
1259
+ } else if (hasNpx) {
1260
+ cmd = ["npx", "typescript-language-server", "--stdio"];
1261
+ } else {
1262
+ console.warn("[lsp] No package runner (npx/bunx/pnpx) found for typescript-language-server");
1263
+ return null;
1264
+ }
1265
+ try {
1266
+ const proc = spawn(cmd[0], cmd.slice(1), {
1267
+ cwd: projectRoot,
1268
+ stdio: ["pipe", "pipe", "pipe"],
1269
+ env: {
1270
+ ...process.env,
1271
+ // Suppress some noisy output
1272
+ TSS_LOG: "-level none"
1273
+ }
1274
+ });
1275
+ proc.stderr?.on("data", (data) => {
1276
+ const msg = data.toString().trim();
1277
+ if (msg && !msg.includes("deprecated")) {
1278
+ console.debug("[lsp:typescript:stderr]", msg);
1279
+ }
1280
+ });
1281
+ return {
1282
+ process: proc,
1283
+ initialization: {
1284
+ // TypeScript-specific initialization options
1285
+ preferences: {
1286
+ includeInlayParameterNameHints: "none",
1287
+ includeInlayPropertyDeclarationTypeHints: false,
1288
+ includeInlayFunctionLikeReturnTypeHints: false
1289
+ }
1290
+ }
1291
+ };
1292
+ } catch (error) {
1293
+ console.error("[lsp] Failed to spawn typescript-language-server:", error);
1294
+ return null;
1295
+ }
1296
+ }
1297
+ };
1298
+ var servers = [
1299
+ TypeScriptServer
1300
+ ];
1301
+ function getServerForExtension(ext) {
1302
+ for (const server of servers) {
1303
+ if (server.extensions.includes(ext)) {
1304
+ return server;
1305
+ }
1306
+ }
1307
+ return null;
1308
+ }
1309
+ function getSupportedExtensions() {
1310
+ const extensions = /* @__PURE__ */ new Set();
1311
+ for (const server of servers) {
1312
+ for (const ext of server.extensions) {
1313
+ extensions.add(ext);
1314
+ }
1315
+ }
1316
+ return Array.from(extensions);
1317
+ }
1318
+
1319
+ // src/lsp/client.ts
1320
+ import {
1321
+ createMessageConnection,
1322
+ StreamMessageReader,
1323
+ StreamMessageWriter
1324
+ } from "vscode-jsonrpc/node";
1325
+ import { pathToFileURL, fileURLToPath } from "url";
1326
+ import { readFile as readFile4 } from "fs/promises";
1327
+ import { existsSync as existsSync6 } from "fs";
1328
+ import { extname, normalize } from "path";
1329
+ function getLanguageId(filePath) {
1330
+ const ext = extname(filePath).toLowerCase();
1331
+ const map = {
1332
+ ".ts": "typescript",
1333
+ ".tsx": "typescriptreact",
1334
+ ".js": "javascript",
1335
+ ".jsx": "javascriptreact",
1336
+ ".mjs": "javascript",
1337
+ ".cjs": "javascript",
1338
+ ".mts": "typescript",
1339
+ ".cts": "typescript",
1340
+ ".json": "json",
1341
+ ".jsonc": "jsonc"
1342
+ };
1343
+ return map[ext] || "plaintext";
1344
+ }
1345
+ function normalizePath(filePath) {
1346
+ return normalize(filePath);
1347
+ }
1348
+ async function createClient(serverId, handle, root) {
1349
+ const { process: proc } = handle;
1350
+ if (!proc.stdout || !proc.stdin) {
1351
+ throw new Error("LSP server process has no stdout/stdin");
1352
+ }
1353
+ const connection = createMessageConnection(
1354
+ new StreamMessageReader(proc.stdout),
1355
+ new StreamMessageWriter(proc.stdin)
1356
+ );
1357
+ const diagnostics = /* @__PURE__ */ new Map();
1358
+ const fileVersions = /* @__PURE__ */ new Map();
1359
+ const diagnosticListeners = /* @__PURE__ */ new Map();
1360
+ connection.onNotification("textDocument/publishDiagnostics", (params) => {
1361
+ const filePath = normalizePath(fileURLToPath(params.uri));
1362
+ diagnostics.set(filePath, params.diagnostics || []);
1363
+ const listeners = diagnosticListeners.get(filePath);
1364
+ if (listeners) {
1365
+ for (const listener of listeners) {
1366
+ listener();
1367
+ }
1368
+ }
1369
+ });
1370
+ connection.onRequest("workspace/configuration", async (params) => {
1371
+ return params.items.map(() => handle.initialization || {});
1372
+ });
1373
+ connection.onRequest("client/registerCapability", async () => {
1374
+ return null;
1375
+ });
1376
+ connection.onRequest("window/workDoneProgress/create", async () => {
1377
+ return null;
1378
+ });
1379
+ connection.onNotification("window/logMessage", (params) => {
1380
+ if (params.type <= 2) {
1381
+ console.debug(`[lsp:${serverId}]`, params.message);
1382
+ }
1383
+ });
1384
+ connection.listen();
1385
+ const initResult = await connection.sendRequest("initialize", {
1386
+ processId: process.pid,
1387
+ rootUri: pathToFileURL(root).href,
1388
+ rootPath: root,
1389
+ workspaceFolders: [
1390
+ {
1391
+ name: "workspace",
1392
+ uri: pathToFileURL(root).href
1393
+ }
1394
+ ],
1395
+ capabilities: {
1396
+ textDocument: {
1397
+ synchronization: {
1398
+ dynamicRegistration: true,
1399
+ willSave: false,
1400
+ willSaveWaitUntil: false,
1401
+ didSave: true
1402
+ },
1403
+ publishDiagnostics: {
1404
+ relatedInformation: true,
1405
+ versionSupport: true,
1406
+ codeDescriptionSupport: true
1407
+ },
1408
+ completion: {
1409
+ dynamicRegistration: true,
1410
+ completionItem: {
1411
+ snippetSupport: true,
1412
+ documentationFormat: ["markdown", "plaintext"]
1413
+ }
1414
+ },
1415
+ hover: {
1416
+ dynamicRegistration: true,
1417
+ contentFormat: ["markdown", "plaintext"]
1418
+ },
1419
+ definition: {
1420
+ dynamicRegistration: true
1421
+ },
1422
+ references: {
1423
+ dynamicRegistration: true
1424
+ },
1425
+ documentSymbol: {
1426
+ dynamicRegistration: true
1427
+ }
1428
+ },
1429
+ workspace: {
1430
+ configuration: true,
1431
+ didChangeConfiguration: {
1432
+ dynamicRegistration: true
1433
+ },
1434
+ didChangeWatchedFiles: {
1435
+ dynamicRegistration: true
1436
+ },
1437
+ workspaceFolders: true
1438
+ }
1439
+ },
1440
+ initializationOptions: handle.initialization
1441
+ });
1442
+ await connection.sendNotification("initialized", {});
1443
+ const client = {
1444
+ serverId,
1445
+ root,
1446
+ diagnostics,
1447
+ async notifyOpen(filePath) {
1448
+ const normalized = normalizePath(filePath);
1449
+ if (!existsSync6(normalized)) {
1450
+ return;
1451
+ }
1452
+ try {
1453
+ const content = await readFile4(normalized, "utf-8");
1454
+ const version = (fileVersions.get(normalized) ?? -1) + 1;
1455
+ fileVersions.set(normalized, version);
1456
+ if (version === 0) {
1457
+ await connection.sendNotification("textDocument/didOpen", {
1458
+ textDocument: {
1459
+ uri: pathToFileURL(normalized).href,
1460
+ languageId: getLanguageId(normalized),
1461
+ version,
1462
+ text: content
1463
+ }
1464
+ });
1465
+ } else {
1466
+ await connection.sendNotification("textDocument/didChange", {
1467
+ textDocument: {
1468
+ uri: pathToFileURL(normalized).href,
1469
+ version
1470
+ },
1471
+ contentChanges: [{ text: content }]
1472
+ });
1473
+ }
1474
+ } catch (error) {
1475
+ console.error("[lsp] Error notifying open:", error);
1476
+ }
1477
+ },
1478
+ async notifyChange(filePath) {
1479
+ const normalized = normalizePath(filePath);
1480
+ if (!existsSync6(normalized)) {
1481
+ return;
1482
+ }
1483
+ try {
1484
+ const content = await readFile4(normalized, "utf-8");
1485
+ const version = (fileVersions.get(normalized) ?? 0) + 1;
1486
+ fileVersions.set(normalized, version);
1487
+ await connection.sendNotification("textDocument/didChange", {
1488
+ textDocument: {
1489
+ uri: pathToFileURL(normalized).href,
1490
+ version
1491
+ },
1492
+ contentChanges: [{ text: content }]
1493
+ });
1494
+ } catch (error) {
1495
+ console.error("[lsp] Error notifying change:", error);
1496
+ }
1497
+ },
1498
+ async notifyClose(filePath) {
1499
+ const normalized = normalizePath(filePath);
1500
+ fileVersions.delete(normalized);
1501
+ diagnostics.delete(normalized);
1502
+ try {
1503
+ await connection.sendNotification("textDocument/didClose", {
1504
+ textDocument: {
1505
+ uri: pathToFileURL(normalized).href
1506
+ }
1507
+ });
1508
+ } catch (error) {
1509
+ console.error("[lsp] Error notifying close:", error);
1510
+ }
1511
+ },
1512
+ async notifyWatchedFilesChanged(changes) {
1513
+ try {
1514
+ await connection.sendNotification("workspace/didChangeWatchedFiles", {
1515
+ changes
1516
+ });
1517
+ } catch (error) {
1518
+ console.error("[lsp] Error notifying watched files:", error);
1519
+ }
1520
+ },
1521
+ async waitForDiagnostics(filePath, timeoutMs = 5e3) {
1522
+ const normalized = normalizePath(filePath);
1523
+ return new Promise((resolve8) => {
1524
+ const startTime = Date.now();
1525
+ let debounceTimer;
1526
+ let resolved = false;
1527
+ const cleanup = () => {
1528
+ if (debounceTimer) clearTimeout(debounceTimer);
1529
+ const listeners = diagnosticListeners.get(normalized);
1530
+ if (listeners) {
1531
+ const idx = listeners.indexOf(onDiagnostic);
1532
+ if (idx >= 0) listeners.splice(idx, 1);
1533
+ if (listeners.length === 0) {
1534
+ diagnosticListeners.delete(normalized);
1535
+ }
1536
+ }
1537
+ };
1538
+ const finish = () => {
1539
+ if (resolved) return;
1540
+ resolved = true;
1541
+ cleanup();
1542
+ resolve8(diagnostics.get(normalized) || []);
1543
+ };
1544
+ const onDiagnostic = () => {
1545
+ if (debounceTimer) clearTimeout(debounceTimer);
1546
+ debounceTimer = setTimeout(finish, 150);
1547
+ };
1548
+ if (!diagnosticListeners.has(normalized)) {
1549
+ diagnosticListeners.set(normalized, []);
1550
+ }
1551
+ diagnosticListeners.get(normalized).push(onDiagnostic);
1552
+ setTimeout(() => {
1553
+ if (!resolved) {
1554
+ finish();
1555
+ }
1556
+ }, timeoutMs);
1557
+ if (diagnostics.has(normalized)) {
1558
+ onDiagnostic();
1559
+ }
1560
+ });
1561
+ },
1562
+ getDiagnostics(filePath) {
1563
+ return diagnostics.get(normalizePath(filePath)) || [];
1564
+ },
1565
+ getAllDiagnostics() {
1566
+ return new Map(diagnostics);
1567
+ },
1568
+ async shutdown() {
1569
+ try {
1570
+ await connection.sendRequest("shutdown");
1571
+ await connection.sendNotification("exit");
1572
+ connection.end();
1573
+ connection.dispose();
1574
+ proc.kill();
1575
+ } catch (error) {
1576
+ proc.kill("SIGKILL");
1577
+ }
1578
+ }
1579
+ };
1580
+ return client;
1581
+ }
1582
+
1583
+ // src/lsp/types.ts
1584
+ function formatDiagnostic(diagnostic) {
1585
+ const severity = {
1586
+ [1 /* Error */]: "ERROR",
1587
+ [2 /* Warning */]: "WARN",
1588
+ [3 /* Information */]: "INFO",
1589
+ [4 /* Hint */]: "HINT"
1590
+ }[diagnostic.severity ?? 1 /* Error */];
1591
+ const line = diagnostic.range.start.line + 1;
1592
+ const col = diagnostic.range.start.character + 1;
1593
+ const source = diagnostic.source ? ` [${diagnostic.source}]` : "";
1594
+ return `${severity} [${line}:${col}]${source} ${diagnostic.message}`;
1595
+ }
1596
+ function formatDiagnosticsForAgent(filePath, diagnostics, options = {}) {
1597
+ const { maxDiagnostics = 20, errorsOnly = true } = options;
1598
+ const filtered = errorsOnly ? diagnostics.filter((d) => d.severity === 1 /* Error */) : diagnostics;
1599
+ if (filtered.length === 0) return "";
1600
+ const limited = filtered.slice(0, maxDiagnostics);
1601
+ const suffix = filtered.length > maxDiagnostics ? `
1602
+ ... and ${filtered.length - maxDiagnostics} more` : "";
1603
+ const formatted = limited.map(formatDiagnostic).join("\n");
1604
+ return `
1605
+
1606
+ LSP errors detected in this file, please fix:
1607
+ <diagnostics file="${filePath}">
1608
+ ${formatted}${suffix}
1609
+ </diagnostics>`;
1610
+ }
1611
+
1612
+ // src/lsp/index.ts
1613
+ var state = {
1614
+ clients: /* @__PURE__ */ new Map(),
1615
+ broken: /* @__PURE__ */ new Set(),
1616
+ initialized: false
1617
+ };
1618
+ async function getClientForFile(filePath) {
1619
+ const normalized = normalizePath(filePath);
1620
+ const ext = extname2(normalized);
1621
+ const serverDef = getServerForExtension(ext);
1622
+ if (!serverDef) {
1623
+ return null;
1624
+ }
1625
+ const root = dirname4(normalized);
1626
+ const key = `${serverDef.id}:${root}`;
1627
+ const existing = state.clients.get(key);
1628
+ if (existing) {
1629
+ return existing;
1630
+ }
1631
+ if (state.broken.has(key)) {
1632
+ return null;
1633
+ }
1634
+ try {
1635
+ const handle = await serverDef.spawn(root);
1636
+ if (!handle) {
1637
+ state.broken.add(key);
1638
+ return null;
1639
+ }
1640
+ console.log(`[lsp] Started ${serverDef.name} for ${root}`);
1641
+ const client = await createClient(serverDef.id, handle, root);
1642
+ state.clients.set(key, client);
1643
+ handle.process.on("exit", (code) => {
1644
+ console.log(`[lsp] ${serverDef.name} exited with code ${code}`);
1645
+ state.clients.delete(key);
1646
+ });
1647
+ return client;
1648
+ } catch (error) {
1649
+ console.error(`[lsp] Failed to start ${serverDef.name}:`, error);
1650
+ state.broken.add(key);
1651
+ return null;
1652
+ }
1653
+ }
1654
+ async function getClientsForFile(filePath) {
1655
+ const client = await getClientForFile(filePath);
1656
+ return client ? [client] : [];
1657
+ }
1658
+ async function touchFile(filePath, waitForDiagnostics = false) {
1659
+ const clients = await getClientsForFile(filePath);
1660
+ if (clients.length === 0) {
1661
+ return;
1662
+ }
1663
+ await Promise.all(clients.map((client) => client.notifyOpen(filePath)));
1664
+ if (waitForDiagnostics) {
1665
+ await Promise.all(clients.map((client) => client.waitForDiagnostics(filePath)));
1666
+ }
1667
+ }
1668
+ async function getDiagnostics(filePath) {
1669
+ const normalized = normalizePath(filePath);
1670
+ const clients = await getClientsForFile(normalized);
1671
+ const allDiagnostics = [];
1672
+ for (const client of clients) {
1673
+ const diags = client.getDiagnostics(normalized);
1674
+ allDiagnostics.push(...diags);
1675
+ }
1676
+ return allDiagnostics;
1677
+ }
1678
+ async function getAllDiagnostics() {
1679
+ const results = {};
1680
+ for (const client of state.clients.values()) {
1681
+ const clientDiags = client.getAllDiagnostics();
1682
+ for (const [path, diagnostics] of clientDiags.entries()) {
1683
+ const existing = results[path] || [];
1684
+ existing.push(...diagnostics);
1685
+ results[path] = existing;
1686
+ }
1687
+ }
1688
+ return results;
1689
+ }
1690
+ async function formatDiagnosticsOutput(filePath, options = {}) {
1691
+ const diagnostics = await getDiagnostics(filePath);
1692
+ return formatDiagnosticsForAgent(filePath, diagnostics, options);
1693
+ }
1694
+ function isSupported(filePath) {
1695
+ const ext = extname2(filePath);
1696
+ return getServerForExtension(ext) !== null;
1697
+ }
1698
+
1185
1699
  // src/tools/write-file.ts
1186
1700
  var writeFileInputSchema = z4.object({
1187
1701
  path: z4.string().describe("The path to the file. Can be relative to working directory or absolute."),
@@ -1211,7 +1725,7 @@ Working directory: ${options.workingDirectory}`,
1211
1725
  inputSchema: writeFileInputSchema,
1212
1726
  execute: async ({ path, mode, content, old_string, new_string }) => {
1213
1727
  try {
1214
- const absolutePath = isAbsolute2(path) ? path : resolve4(options.workingDirectory, path);
1728
+ const absolutePath = isAbsolute2(path) ? path : resolve5(options.workingDirectory, path);
1215
1729
  const relativePath = relative3(options.workingDirectory, absolutePath);
1216
1730
  if (relativePath.startsWith("..") && !isAbsolute2(path)) {
1217
1731
  return {
@@ -1227,12 +1741,17 @@ Working directory: ${options.workingDirectory}`,
1227
1741
  };
1228
1742
  }
1229
1743
  await backupFile(options.sessionId, options.workingDirectory, absolutePath);
1230
- const dir = dirname3(absolutePath);
1231
- if (!existsSync5(dir)) {
1744
+ const dir = dirname5(absolutePath);
1745
+ if (!existsSync7(dir)) {
1232
1746
  await mkdir3(dir, { recursive: true });
1233
1747
  }
1234
- const existed = existsSync5(absolutePath);
1748
+ const existed = existsSync7(absolutePath);
1235
1749
  await writeFile3(absolutePath, content, "utf-8");
1750
+ let diagnosticsOutput = "";
1751
+ if (options.enableLSP !== false && isSupported(absolutePath)) {
1752
+ await touchFile(absolutePath, true);
1753
+ diagnosticsOutput = await formatDiagnosticsOutput(absolutePath);
1754
+ }
1236
1755
  return {
1237
1756
  success: true,
1238
1757
  path: absolutePath,
@@ -1240,7 +1759,8 @@ Working directory: ${options.workingDirectory}`,
1240
1759
  mode: "full",
1241
1760
  action: existed ? "replaced" : "created",
1242
1761
  bytesWritten: Buffer.byteLength(content, "utf-8"),
1243
- lineCount: content.split("\n").length
1762
+ lineCount: content.split("\n").length,
1763
+ ...diagnosticsOutput && { diagnostics: diagnosticsOutput }
1244
1764
  };
1245
1765
  } else if (mode === "str_replace") {
1246
1766
  if (old_string === void 0 || new_string === void 0) {
@@ -1249,14 +1769,14 @@ Working directory: ${options.workingDirectory}`,
1249
1769
  error: 'Both old_string and new_string are required for "str_replace" mode'
1250
1770
  };
1251
1771
  }
1252
- if (!existsSync5(absolutePath)) {
1772
+ if (!existsSync7(absolutePath)) {
1253
1773
  return {
1254
1774
  success: false,
1255
1775
  error: `File not found: ${path}. Use "full" mode to create new files.`
1256
1776
  };
1257
1777
  }
1258
1778
  await backupFile(options.sessionId, options.workingDirectory, absolutePath);
1259
- const currentContent = await readFile4(absolutePath, "utf-8");
1779
+ const currentContent = await readFile5(absolutePath, "utf-8");
1260
1780
  if (!currentContent.includes(old_string)) {
1261
1781
  const lines = currentContent.split("\n");
1262
1782
  const preview = lines.slice(0, 20).join("\n");
@@ -1280,6 +1800,11 @@ Working directory: ${options.workingDirectory}`,
1280
1800
  await writeFile3(absolutePath, newContent, "utf-8");
1281
1801
  const oldLines = old_string.split("\n").length;
1282
1802
  const newLines = new_string.split("\n").length;
1803
+ let diagnosticsOutput = "";
1804
+ if (options.enableLSP !== false && isSupported(absolutePath)) {
1805
+ await touchFile(absolutePath, true);
1806
+ diagnosticsOutput = await formatDiagnosticsOutput(absolutePath);
1807
+ }
1283
1808
  return {
1284
1809
  success: true,
1285
1810
  path: absolutePath,
@@ -1287,7 +1812,8 @@ Working directory: ${options.workingDirectory}`,
1287
1812
  mode: "str_replace",
1288
1813
  linesRemoved: oldLines,
1289
1814
  linesAdded: newLines,
1290
- lineDelta: newLines - oldLines
1815
+ lineDelta: newLines - oldLines,
1816
+ ...diagnosticsOutput && { diagnostics: diagnosticsOutput }
1291
1817
  };
1292
1818
  }
1293
1819
  return {
@@ -1434,9 +1960,9 @@ import { tool as tool5 } from "ai";
1434
1960
  import { z as z6 } from "zod";
1435
1961
 
1436
1962
  // src/skills/index.ts
1437
- import { readFile as readFile5, readdir } from "fs/promises";
1438
- import { resolve as resolve5, basename, extname } from "path";
1439
- import { existsSync as existsSync6 } from "fs";
1963
+ import { readFile as readFile6, readdir } from "fs/promises";
1964
+ import { resolve as resolve6, basename, extname as extname3 } from "path";
1965
+ import { existsSync as existsSync8 } from "fs";
1440
1966
  function parseSkillFrontmatter(content) {
1441
1967
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
1442
1968
  if (!frontmatterMatch) {
@@ -1464,18 +1990,18 @@ function parseSkillFrontmatter(content) {
1464
1990
  }
1465
1991
  }
1466
1992
  function getSkillNameFromPath(filePath) {
1467
- return basename(filePath, extname(filePath)).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1993
+ return basename(filePath, extname3(filePath)).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1468
1994
  }
1469
1995
  async function loadSkillsFromDirectory(directory) {
1470
- if (!existsSync6(directory)) {
1996
+ if (!existsSync8(directory)) {
1471
1997
  return [];
1472
1998
  }
1473
1999
  const skills = [];
1474
2000
  const files = await readdir(directory);
1475
2001
  for (const file of files) {
1476
2002
  if (!file.endsWith(".md")) continue;
1477
- const filePath = resolve5(directory, file);
1478
- const content = await readFile5(filePath, "utf-8");
2003
+ const filePath = resolve6(directory, file);
2004
+ const content = await readFile6(filePath, "utf-8");
1479
2005
  const parsed = parseSkillFrontmatter(content);
1480
2006
  if (parsed) {
1481
2007
  skills.push({
@@ -1517,7 +2043,7 @@ async function loadSkillContent(skillName, directories) {
1517
2043
  if (!skill) {
1518
2044
  return null;
1519
2045
  }
1520
- const content = await readFile5(skill.filePath, "utf-8");
2046
+ const content = await readFile6(skill.filePath, "utf-8");
1521
2047
  const parsed = parseSkillFrontmatter(content);
1522
2048
  return {
1523
2049
  ...skill,
@@ -1615,6 +2141,199 @@ Once loaded, a skill's content will be available in the conversation context.`,
1615
2141
  });
1616
2142
  }
1617
2143
 
2144
+ // src/tools/linter.ts
2145
+ import { tool as tool6 } from "ai";
2146
+ import { z as z7 } from "zod";
2147
+ import { resolve as resolve7, relative as relative4, isAbsolute as isAbsolute3, extname as extname4 } from "path";
2148
+ import { existsSync as existsSync9 } from "fs";
2149
+ import { readdir as readdir2, stat as stat2 } from "fs/promises";
2150
+ var linterInputSchema = z7.object({
2151
+ paths: z7.array(z7.string()).optional().describe("File or directory paths to check for lint errors. If not provided, returns diagnostics for all recently touched files."),
2152
+ fix: z7.boolean().optional().default(false).describe("Reserved for future use: auto-fix lint errors (not yet implemented)")
2153
+ });
2154
+ async function findSupportedFiles(dir, workingDirectory, maxFiles = 50) {
2155
+ const files = [];
2156
+ const supportedExtensions = getSupportedExtensions();
2157
+ async function walk(currentDir) {
2158
+ if (files.length >= maxFiles) return;
2159
+ try {
2160
+ const entries = await readdir2(currentDir, { withFileTypes: true });
2161
+ for (const entry of entries) {
2162
+ if (files.length >= maxFiles) break;
2163
+ const fullPath = resolve7(currentDir, entry.name);
2164
+ if (entry.isDirectory()) {
2165
+ if (["node_modules", ".git", "dist", "build", ".next", "coverage"].includes(entry.name)) {
2166
+ continue;
2167
+ }
2168
+ await walk(fullPath);
2169
+ } else if (entry.isFile()) {
2170
+ const ext = extname4(entry.name);
2171
+ if (supportedExtensions.includes(ext)) {
2172
+ files.push(fullPath);
2173
+ }
2174
+ }
2175
+ }
2176
+ } catch {
2177
+ }
2178
+ }
2179
+ await walk(dir);
2180
+ return files;
2181
+ }
2182
+ function createLinterTool(options) {
2183
+ return tool6({
2184
+ description: `Check files for linting and type errors using the Language Server Protocol (LSP).
2185
+
2186
+ Supports TypeScript, JavaScript, TSX, JSX files.
2187
+
2188
+ Usage:
2189
+ - \`linter({})\` - Get diagnostics for all recently edited files
2190
+ - \`linter({ paths: ["src/app.ts"] })\` - Check specific files
2191
+ - \`linter({ paths: ["src/"] })\` - Check all supported files in a directory
2192
+
2193
+ Returns detailed error information including line numbers, error messages, and severity.
2194
+ Use this after making changes to verify your code is correct, or proactively to find issues.
2195
+
2196
+ Working directory: ${options.workingDirectory}`,
2197
+ inputSchema: linterInputSchema,
2198
+ execute: async ({ paths }) => {
2199
+ try {
2200
+ if (!paths || paths.length === 0) {
2201
+ const allDiagnostics = await getAllDiagnostics();
2202
+ if (Object.keys(allDiagnostics).length === 0) {
2203
+ return {
2204
+ success: true,
2205
+ message: "No lint errors found. No files have been analyzed yet - specify paths to check specific files.",
2206
+ files: [],
2207
+ totalErrors: 0,
2208
+ totalWarnings: 0
2209
+ };
2210
+ }
2211
+ return formatDiagnosticsResult(allDiagnostics, options.workingDirectory);
2212
+ }
2213
+ const filesToCheck = [];
2214
+ for (const path of paths) {
2215
+ const absolutePath = isAbsolute3(path) ? path : resolve7(options.workingDirectory, path);
2216
+ if (!existsSync9(absolutePath)) {
2217
+ continue;
2218
+ }
2219
+ const stats = await stat2(absolutePath);
2220
+ if (stats.isDirectory()) {
2221
+ const dirFiles = await findSupportedFiles(absolutePath, options.workingDirectory);
2222
+ filesToCheck.push(...dirFiles);
2223
+ } else if (stats.isFile()) {
2224
+ if (isSupported(absolutePath)) {
2225
+ filesToCheck.push(absolutePath);
2226
+ }
2227
+ }
2228
+ }
2229
+ if (filesToCheck.length === 0) {
2230
+ return {
2231
+ success: true,
2232
+ message: "No supported files found to check. Supported extensions: " + getSupportedExtensions().join(", "),
2233
+ files: [],
2234
+ totalErrors: 0,
2235
+ totalWarnings: 0
2236
+ };
2237
+ }
2238
+ await Promise.all(
2239
+ filesToCheck.map((file) => touchFile(file, true))
2240
+ );
2241
+ const diagnosticsMap = {};
2242
+ for (const file of filesToCheck) {
2243
+ const diagnostics = await getDiagnostics(file);
2244
+ if (diagnostics.length > 0) {
2245
+ diagnosticsMap[file] = diagnostics;
2246
+ }
2247
+ }
2248
+ return formatDiagnosticsResult(diagnosticsMap, options.workingDirectory);
2249
+ } catch (error) {
2250
+ return {
2251
+ success: false,
2252
+ error: error.message
2253
+ };
2254
+ }
2255
+ }
2256
+ });
2257
+ }
2258
+ function formatDiagnosticsResult(diagnosticsMap, workingDirectory) {
2259
+ let totalErrors = 0;
2260
+ let totalWarnings = 0;
2261
+ let totalInfo = 0;
2262
+ const files = [];
2263
+ for (const [filePath, diagnostics] of Object.entries(diagnosticsMap)) {
2264
+ const relativePath = relative4(workingDirectory, filePath);
2265
+ let fileErrors = 0;
2266
+ let fileWarnings = 0;
2267
+ const formattedDiagnostics = diagnostics.map((d) => {
2268
+ const severity = getSeverityString(d.severity);
2269
+ if (d.severity === 1 /* Error */) {
2270
+ fileErrors++;
2271
+ totalErrors++;
2272
+ } else if (d.severity === 2 /* Warning */) {
2273
+ fileWarnings++;
2274
+ totalWarnings++;
2275
+ } else {
2276
+ totalInfo++;
2277
+ }
2278
+ return {
2279
+ severity,
2280
+ line: d.range.start.line + 1,
2281
+ column: d.range.start.character + 1,
2282
+ message: d.message,
2283
+ source: d.source,
2284
+ code: d.code
2285
+ };
2286
+ });
2287
+ files.push({
2288
+ path: filePath,
2289
+ relativePath,
2290
+ errors: fileErrors,
2291
+ warnings: fileWarnings,
2292
+ diagnostics: formattedDiagnostics
2293
+ });
2294
+ }
2295
+ files.sort((a, b) => b.errors - a.errors);
2296
+ const hasIssues = totalErrors > 0 || totalWarnings > 0;
2297
+ return {
2298
+ success: true,
2299
+ message: hasIssues ? `Found ${totalErrors} error(s) and ${totalWarnings} warning(s) in ${files.length} file(s).` : `No lint errors found in ${Object.keys(diagnosticsMap).length || "any"} file(s).`,
2300
+ files,
2301
+ totalErrors,
2302
+ totalWarnings,
2303
+ totalInfo,
2304
+ summary: hasIssues ? formatSummary(files) : void 0
2305
+ };
2306
+ }
2307
+ function getSeverityString(severity) {
2308
+ switch (severity) {
2309
+ case 1 /* Error */:
2310
+ return "error";
2311
+ case 2 /* Warning */:
2312
+ return "warning";
2313
+ case 3 /* Information */:
2314
+ return "info";
2315
+ case 4 /* Hint */:
2316
+ return "hint";
2317
+ default:
2318
+ return "error";
2319
+ }
2320
+ }
2321
+ function formatSummary(files) {
2322
+ const lines = [];
2323
+ for (const file of files) {
2324
+ lines.push(`
2325
+ ${file.relativePath}:`);
2326
+ for (const d of file.diagnostics.slice(0, 10)) {
2327
+ const prefix = d.severity === "error" ? "\u274C" : d.severity === "warning" ? "\u26A0\uFE0F" : "\u2139\uFE0F";
2328
+ lines.push(` ${prefix} [${d.line}:${d.column}] ${d.message}`);
2329
+ }
2330
+ if (file.diagnostics.length > 10) {
2331
+ lines.push(` ... and ${file.diagnostics.length - 10} more`);
2332
+ }
2333
+ }
2334
+ return lines.join("\n");
2335
+ }
2336
+
1618
2337
  // src/tools/index.ts
1619
2338
  function createTools(options) {
1620
2339
  return {
@@ -1629,7 +2348,8 @@ function createTools(options) {
1629
2348
  }),
1630
2349
  write_file: createWriteFileTool({
1631
2350
  workingDirectory: options.workingDirectory,
1632
- sessionId: options.sessionId
2351
+ sessionId: options.sessionId,
2352
+ enableLSP: options.enableLSP ?? true
1633
2353
  }),
1634
2354
  todo: createTodoTool({
1635
2355
  sessionId: options.sessionId
@@ -1637,6 +2357,9 @@ function createTools(options) {
1637
2357
  load_skill: createLoadSkillTool({
1638
2358
  sessionId: options.sessionId,
1639
2359
  skillsDirectories: options.skillsDirectories
2360
+ }),
2361
+ linter: createLinterTool({
2362
+ workingDirectory: options.workingDirectory
1640
2363
  })
1641
2364
  };
1642
2365
  }
@@ -1684,6 +2407,7 @@ You have access to powerful tools for:
1684
2407
  - **bash**: Execute commands in the terminal (see below for details)
1685
2408
  - **read_file**: Read file contents to understand code and context
1686
2409
  - **write_file**: Create new files or edit existing ones (supports targeted string replacement)
2410
+ - **linter**: Check files for type errors and lint issues (TypeScript, JavaScript, TSX, JSX)
1687
2411
  - **todo**: Manage your task list to track progress on complex operations
1688
2412
  - **load_skill**: Load specialized knowledge documents for specific tasks
1689
2413
 
@@ -1740,7 +2464,7 @@ bash({ id: "abc123", input: "my text" }) // send text input
1740
2464
  - Use \`key: "y"\` or \`key: "n"\` for yes/no prompts
1741
2465
  - Use \`input: "text"\` for text input prompts
1742
2466
 
1743
- Logs are saved to \`.sparkecoder/terminals/{id}/output.log\` and can be read with \`read_file\` if needed.
2467
+ Terminal output is stored in the global SparkECoder data directory. Use the \`tail\` option to read recent output.
1744
2468
 
1745
2469
  ## Guidelines
1746
2470
 
@@ -1760,7 +2484,17 @@ Logs are saved to \`.sparkecoder/terminals/{id}/output.log\` and can be read wit
1760
2484
  - Use \`read_file\` to understand code before modifying
1761
2485
  - Use \`write_file\` with mode "str_replace" for targeted edits to existing files
1762
2486
  - Use \`write_file\` with mode "full" only for new files or complete rewrites
1763
- - Always verify changes by reading files after modifications
2487
+ - After making changes, use the \`linter\` tool to check for type errors and lint issues
2488
+ - The \`write_file\` tool automatically shows lint errors in its output for TypeScript/JavaScript files
2489
+
2490
+ ### Linter Tool
2491
+ The linter tool uses Language Server Protocol (LSP) to detect type errors and lint issues:
2492
+ \`\`\`
2493
+ linter({}) // Check all recently edited files
2494
+ linter({ paths: ["src/app.ts"] }) // Check specific files
2495
+ linter({ paths: ["src/"] }) // Check all files in a directory
2496
+ \`\`\`
2497
+ Use this proactively after making code changes to catch errors early.
1764
2498
 
1765
2499
  ### Searching and Exploration
1766
2500
  ${searchInstructions}
@@ -2110,9 +2844,9 @@ var Agent = class _Agent {
2110
2844
  wrappedTools[name] = originalTool;
2111
2845
  continue;
2112
2846
  }
2113
- wrappedTools[name] = tool6({
2847
+ wrappedTools[name] = tool7({
2114
2848
  description: originalTool.description || "",
2115
- inputSchema: originalTool.inputSchema || z7.object({}),
2849
+ inputSchema: originalTool.inputSchema || z8.object({}),
2116
2850
  execute: async (input, toolOptions) => {
2117
2851
  const toolCallId = toolOptions.toolCallId || nanoid3();
2118
2852
  const execution = toolExecutionQueries.create({
@@ -2126,8 +2860,8 @@ var Agent = class _Agent {
2126
2860
  this.pendingApprovals.set(toolCallId, execution);
2127
2861
  options.onApprovalRequired?.(execution);
2128
2862
  sessionQueries.updateStatus(this.session.id, "waiting");
2129
- const approved = await new Promise((resolve6) => {
2130
- approvalResolvers.set(toolCallId, { resolve: resolve6, sessionId: this.session.id });
2863
+ const approved = await new Promise((resolve8) => {
2864
+ approvalResolvers.set(toolCallId, { resolve: resolve8, sessionId: this.session.id });
2131
2865
  });
2132
2866
  const resolverData = approvalResolvers.get(toolCallId);
2133
2867
  approvalResolvers.delete(toolCallId);