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
package/dist/cli.js CHANGED
@@ -18,16 +18,16 @@ import { Hono as Hono5 } from "hono";
18
18
  import { serve } from "@hono/node-server";
19
19
  import { cors } from "hono/cors";
20
20
  import { logger } from "hono/logger";
21
- import { existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
22
- import { resolve as resolve6, dirname as dirname4, join as join3 } from "path";
23
- import { spawn } from "child_process";
21
+ import { existsSync as existsSync10, mkdirSync as mkdirSync3 } from "fs";
22
+ import { resolve as resolve8, dirname as dirname6, join as join3 } from "path";
23
+ import { spawn as spawn2 } from "child_process";
24
24
  import { createServer as createNetServer } from "net";
25
- import { fileURLToPath } from "url";
25
+ import { fileURLToPath as fileURLToPath2 } from "url";
26
26
 
27
27
  // src/server/routes/sessions.ts
28
28
  import { Hono } from "hono";
29
29
  import { zValidator } from "@hono/zod-validator";
30
- import { z as z8 } from "zod";
30
+ import { z as z9 } from "zod";
31
31
 
32
32
  // src/db/index.ts
33
33
  import Database from "better-sqlite3";
@@ -658,11 +658,11 @@ var fileBackupQueries = {
658
658
  import {
659
659
  streamText,
660
660
  generateText as generateText2,
661
- tool as tool6,
661
+ tool as tool7,
662
662
  stepCountIs
663
663
  } from "ai";
664
664
  import { gateway as gateway2 } from "@ai-sdk/gateway";
665
- import { z as z7 } from "zod";
665
+ import { z as z8 } from "zod";
666
666
  import { nanoid as nanoid3 } from "nanoid";
667
667
 
668
668
  // src/config/index.ts
@@ -1013,12 +1013,12 @@ function calculateContextSize(messages2) {
1013
1013
  import { exec } from "child_process";
1014
1014
  import { promisify } from "util";
1015
1015
  import { mkdir, writeFile, readFile } from "fs/promises";
1016
- import { existsSync as existsSync2 } from "fs";
1016
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
1017
1017
  import { join as join2 } from "path";
1018
1018
  import { nanoid as nanoid2 } from "nanoid";
1019
1019
  var execAsync = promisify(exec);
1020
1020
  var SESSION_PREFIX = "spark_";
1021
- var LOG_BASE_DIR = ".sparkecoder/sessions";
1021
+ var LOG_BASE_DIR = "sessions";
1022
1022
  var tmuxAvailableCache = null;
1023
1023
  async function isTmuxAvailable() {
1024
1024
  if (tmuxAvailableCache !== null) {
@@ -1040,11 +1040,19 @@ function generateTerminalId() {
1040
1040
  function getSessionName(terminalId) {
1041
1041
  return `${SESSION_PREFIX}${terminalId}`;
1042
1042
  }
1043
- function getLogDir(terminalId, workingDirectory, sessionId) {
1043
+ function getTerminalDataDir() {
1044
+ const appDataDir = getAppDataDirectory();
1045
+ if (!existsSync2(appDataDir)) {
1046
+ mkdirSync2(appDataDir, { recursive: true });
1047
+ }
1048
+ return appDataDir;
1049
+ }
1050
+ function getLogDir(terminalId, _workingDirectory, sessionId) {
1051
+ const baseDir = getTerminalDataDir();
1044
1052
  if (sessionId) {
1045
- return join2(workingDirectory, LOG_BASE_DIR, sessionId, "terminals", terminalId);
1053
+ return join2(baseDir, LOG_BASE_DIR, sessionId, "terminals", terminalId);
1046
1054
  }
1047
- return join2(workingDirectory, ".sparkecoder/terminals", terminalId);
1055
+ return join2(baseDir, "terminals", terminalId);
1048
1056
  }
1049
1057
  function shellEscape(str) {
1050
1058
  return `'${str.replace(/'/g, "'\\''")}'`;
@@ -1257,8 +1265,8 @@ async function listSessionTerminals(sessionId, workingDirectory) {
1257
1265
  const terminalsDir = join2(workingDirectory, LOG_BASE_DIR, sessionId, "terminals");
1258
1266
  const terminals3 = [];
1259
1267
  try {
1260
- const { readdir: readdir2 } = await import("fs/promises");
1261
- const entries = await readdir2(terminalsDir, { withFileTypes: true });
1268
+ const { readdir: readdir3 } = await import("fs/promises");
1269
+ const entries = await readdir3(terminalsDir, { withFileTypes: true });
1262
1270
  for (const entry of entries) {
1263
1271
  if (entry.isDirectory()) {
1264
1272
  const meta = await getMeta(entry.name, workingDirectory, sessionId);
@@ -1408,7 +1416,7 @@ bash({ id: "abc123", input: "my text" }) // send text input
1408
1416
  - For npm: add --yes or -y to skip confirmation
1409
1417
  - If prompts are unavoidable, run in background mode and use input/key to respond
1410
1418
 
1411
- Logs are saved to .sparkecoder/terminals/{id}/output.log`,
1419
+ Terminal output is stored in the global SparkECoder data directory. Use the \`tail\` option to read recent output.`,
1412
1420
  inputSchema: bashInputSchema,
1413
1421
  execute: async (inputArgs) => {
1414
1422
  const { command, background, id, kill, tail, input: textInput, key } = inputArgs;
@@ -1647,9 +1655,9 @@ Use this to understand existing code, check file contents, or gather context.`,
1647
1655
  // src/tools/write-file.ts
1648
1656
  import { tool as tool3 } from "ai";
1649
1657
  import { z as z4 } from "zod";
1650
- import { readFile as readFile4, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
1651
- import { resolve as resolve4, relative as relative3, isAbsolute as isAbsolute2, dirname as dirname3 } from "path";
1652
- import { existsSync as existsSync5 } from "fs";
1658
+ import { readFile as readFile5, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
1659
+ import { resolve as resolve5, relative as relative3, isAbsolute as isAbsolute2, dirname as dirname5 } from "path";
1660
+ import { existsSync as existsSync7 } from "fs";
1653
1661
 
1654
1662
  // src/checkpoints/index.ts
1655
1663
  import { readFile as readFile3, writeFile as writeFile2, unlink, mkdir as mkdir2 } from "fs/promises";
@@ -1838,6 +1846,501 @@ function clearCheckpointManager(sessionId) {
1838
1846
  activeManagers.delete(sessionId);
1839
1847
  }
1840
1848
 
1849
+ // src/lsp/index.ts
1850
+ import { extname as extname2, dirname as dirname4 } from "path";
1851
+
1852
+ // src/lsp/servers.ts
1853
+ import { spawn } from "child_process";
1854
+ import { existsSync as existsSync5 } from "fs";
1855
+ import { resolve as resolve4, dirname as dirname3 } from "path";
1856
+ function findNearestRoot(startDir, markers) {
1857
+ let dir = startDir;
1858
+ const root = "/";
1859
+ while (dir !== root) {
1860
+ for (const marker of markers) {
1861
+ if (existsSync5(resolve4(dir, marker))) {
1862
+ return dir;
1863
+ }
1864
+ }
1865
+ const parent = dirname3(dir);
1866
+ if (parent === dir) break;
1867
+ dir = parent;
1868
+ }
1869
+ return null;
1870
+ }
1871
+ async function commandExists(cmd) {
1872
+ try {
1873
+ const { exec: exec5 } = await import("child_process");
1874
+ const { promisify: promisify5 } = await import("util");
1875
+ const execAsync5 = promisify5(exec5);
1876
+ const isWindows = process.platform === "win32";
1877
+ const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
1878
+ await execAsync5(checkCmd);
1879
+ return true;
1880
+ } catch {
1881
+ return false;
1882
+ }
1883
+ }
1884
+ var TypeScriptServer = {
1885
+ id: "typescript",
1886
+ name: "TypeScript Language Server",
1887
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
1888
+ async spawn(root) {
1889
+ const projectRoot = findNearestRoot(root, [
1890
+ "package-lock.json",
1891
+ "pnpm-lock.yaml",
1892
+ "yarn.lock",
1893
+ "bun.lockb",
1894
+ "bun.lock"
1895
+ ]) || root;
1896
+ const hasNpx = await commandExists("npx");
1897
+ const hasBunx = await commandExists("bunx");
1898
+ const hasPnpx = await commandExists("pnpx");
1899
+ let cmd;
1900
+ if (hasPnpx) {
1901
+ cmd = ["pnpx", "typescript-language-server", "--stdio"];
1902
+ } else if (hasBunx) {
1903
+ cmd = ["bunx", "typescript-language-server", "--stdio"];
1904
+ } else if (hasNpx) {
1905
+ cmd = ["npx", "typescript-language-server", "--stdio"];
1906
+ } else {
1907
+ console.warn("[lsp] No package runner (npx/bunx/pnpx) found for typescript-language-server");
1908
+ return null;
1909
+ }
1910
+ try {
1911
+ const proc = spawn(cmd[0], cmd.slice(1), {
1912
+ cwd: projectRoot,
1913
+ stdio: ["pipe", "pipe", "pipe"],
1914
+ env: {
1915
+ ...process.env,
1916
+ // Suppress some noisy output
1917
+ TSS_LOG: "-level none"
1918
+ }
1919
+ });
1920
+ proc.stderr?.on("data", (data) => {
1921
+ const msg = data.toString().trim();
1922
+ if (msg && !msg.includes("deprecated")) {
1923
+ console.debug("[lsp:typescript:stderr]", msg);
1924
+ }
1925
+ });
1926
+ return {
1927
+ process: proc,
1928
+ initialization: {
1929
+ // TypeScript-specific initialization options
1930
+ preferences: {
1931
+ includeInlayParameterNameHints: "none",
1932
+ includeInlayPropertyDeclarationTypeHints: false,
1933
+ includeInlayFunctionLikeReturnTypeHints: false
1934
+ }
1935
+ }
1936
+ };
1937
+ } catch (error) {
1938
+ console.error("[lsp] Failed to spawn typescript-language-server:", error);
1939
+ return null;
1940
+ }
1941
+ }
1942
+ };
1943
+ var servers = [
1944
+ TypeScriptServer
1945
+ ];
1946
+ function getServerForExtension(ext) {
1947
+ for (const server of servers) {
1948
+ if (server.extensions.includes(ext)) {
1949
+ return server;
1950
+ }
1951
+ }
1952
+ return null;
1953
+ }
1954
+ function getSupportedExtensions() {
1955
+ const extensions = /* @__PURE__ */ new Set();
1956
+ for (const server of servers) {
1957
+ for (const ext of server.extensions) {
1958
+ extensions.add(ext);
1959
+ }
1960
+ }
1961
+ return Array.from(extensions);
1962
+ }
1963
+
1964
+ // src/lsp/client.ts
1965
+ import {
1966
+ createMessageConnection,
1967
+ StreamMessageReader,
1968
+ StreamMessageWriter
1969
+ } from "vscode-jsonrpc/node";
1970
+ import { pathToFileURL, fileURLToPath } from "url";
1971
+ import { readFile as readFile4 } from "fs/promises";
1972
+ import { existsSync as existsSync6 } from "fs";
1973
+ import { extname, normalize } from "path";
1974
+ function getLanguageId(filePath) {
1975
+ const ext = extname(filePath).toLowerCase();
1976
+ const map = {
1977
+ ".ts": "typescript",
1978
+ ".tsx": "typescriptreact",
1979
+ ".js": "javascript",
1980
+ ".jsx": "javascriptreact",
1981
+ ".mjs": "javascript",
1982
+ ".cjs": "javascript",
1983
+ ".mts": "typescript",
1984
+ ".cts": "typescript",
1985
+ ".json": "json",
1986
+ ".jsonc": "jsonc"
1987
+ };
1988
+ return map[ext] || "plaintext";
1989
+ }
1990
+ function normalizePath(filePath) {
1991
+ return normalize(filePath);
1992
+ }
1993
+ async function createClient(serverId, handle, root) {
1994
+ const { process: proc } = handle;
1995
+ if (!proc.stdout || !proc.stdin) {
1996
+ throw new Error("LSP server process has no stdout/stdin");
1997
+ }
1998
+ const connection = createMessageConnection(
1999
+ new StreamMessageReader(proc.stdout),
2000
+ new StreamMessageWriter(proc.stdin)
2001
+ );
2002
+ const diagnostics = /* @__PURE__ */ new Map();
2003
+ const fileVersions = /* @__PURE__ */ new Map();
2004
+ const diagnosticListeners = /* @__PURE__ */ new Map();
2005
+ connection.onNotification("textDocument/publishDiagnostics", (params) => {
2006
+ const filePath = normalizePath(fileURLToPath(params.uri));
2007
+ diagnostics.set(filePath, params.diagnostics || []);
2008
+ const listeners = diagnosticListeners.get(filePath);
2009
+ if (listeners) {
2010
+ for (const listener of listeners) {
2011
+ listener();
2012
+ }
2013
+ }
2014
+ });
2015
+ connection.onRequest("workspace/configuration", async (params) => {
2016
+ return params.items.map(() => handle.initialization || {});
2017
+ });
2018
+ connection.onRequest("client/registerCapability", async () => {
2019
+ return null;
2020
+ });
2021
+ connection.onRequest("window/workDoneProgress/create", async () => {
2022
+ return null;
2023
+ });
2024
+ connection.onNotification("window/logMessage", (params) => {
2025
+ if (params.type <= 2) {
2026
+ console.debug(`[lsp:${serverId}]`, params.message);
2027
+ }
2028
+ });
2029
+ connection.listen();
2030
+ const initResult = await connection.sendRequest("initialize", {
2031
+ processId: process.pid,
2032
+ rootUri: pathToFileURL(root).href,
2033
+ rootPath: root,
2034
+ workspaceFolders: [
2035
+ {
2036
+ name: "workspace",
2037
+ uri: pathToFileURL(root).href
2038
+ }
2039
+ ],
2040
+ capabilities: {
2041
+ textDocument: {
2042
+ synchronization: {
2043
+ dynamicRegistration: true,
2044
+ willSave: false,
2045
+ willSaveWaitUntil: false,
2046
+ didSave: true
2047
+ },
2048
+ publishDiagnostics: {
2049
+ relatedInformation: true,
2050
+ versionSupport: true,
2051
+ codeDescriptionSupport: true
2052
+ },
2053
+ completion: {
2054
+ dynamicRegistration: true,
2055
+ completionItem: {
2056
+ snippetSupport: true,
2057
+ documentationFormat: ["markdown", "plaintext"]
2058
+ }
2059
+ },
2060
+ hover: {
2061
+ dynamicRegistration: true,
2062
+ contentFormat: ["markdown", "plaintext"]
2063
+ },
2064
+ definition: {
2065
+ dynamicRegistration: true
2066
+ },
2067
+ references: {
2068
+ dynamicRegistration: true
2069
+ },
2070
+ documentSymbol: {
2071
+ dynamicRegistration: true
2072
+ }
2073
+ },
2074
+ workspace: {
2075
+ configuration: true,
2076
+ didChangeConfiguration: {
2077
+ dynamicRegistration: true
2078
+ },
2079
+ didChangeWatchedFiles: {
2080
+ dynamicRegistration: true
2081
+ },
2082
+ workspaceFolders: true
2083
+ }
2084
+ },
2085
+ initializationOptions: handle.initialization
2086
+ });
2087
+ await connection.sendNotification("initialized", {});
2088
+ const client = {
2089
+ serverId,
2090
+ root,
2091
+ diagnostics,
2092
+ async notifyOpen(filePath) {
2093
+ const normalized = normalizePath(filePath);
2094
+ if (!existsSync6(normalized)) {
2095
+ return;
2096
+ }
2097
+ try {
2098
+ const content = await readFile4(normalized, "utf-8");
2099
+ const version = (fileVersions.get(normalized) ?? -1) + 1;
2100
+ fileVersions.set(normalized, version);
2101
+ if (version === 0) {
2102
+ await connection.sendNotification("textDocument/didOpen", {
2103
+ textDocument: {
2104
+ uri: pathToFileURL(normalized).href,
2105
+ languageId: getLanguageId(normalized),
2106
+ version,
2107
+ text: content
2108
+ }
2109
+ });
2110
+ } else {
2111
+ await connection.sendNotification("textDocument/didChange", {
2112
+ textDocument: {
2113
+ uri: pathToFileURL(normalized).href,
2114
+ version
2115
+ },
2116
+ contentChanges: [{ text: content }]
2117
+ });
2118
+ }
2119
+ } catch (error) {
2120
+ console.error("[lsp] Error notifying open:", error);
2121
+ }
2122
+ },
2123
+ async notifyChange(filePath) {
2124
+ const normalized = normalizePath(filePath);
2125
+ if (!existsSync6(normalized)) {
2126
+ return;
2127
+ }
2128
+ try {
2129
+ const content = await readFile4(normalized, "utf-8");
2130
+ const version = (fileVersions.get(normalized) ?? 0) + 1;
2131
+ fileVersions.set(normalized, version);
2132
+ await connection.sendNotification("textDocument/didChange", {
2133
+ textDocument: {
2134
+ uri: pathToFileURL(normalized).href,
2135
+ version
2136
+ },
2137
+ contentChanges: [{ text: content }]
2138
+ });
2139
+ } catch (error) {
2140
+ console.error("[lsp] Error notifying change:", error);
2141
+ }
2142
+ },
2143
+ async notifyClose(filePath) {
2144
+ const normalized = normalizePath(filePath);
2145
+ fileVersions.delete(normalized);
2146
+ diagnostics.delete(normalized);
2147
+ try {
2148
+ await connection.sendNotification("textDocument/didClose", {
2149
+ textDocument: {
2150
+ uri: pathToFileURL(normalized).href
2151
+ }
2152
+ });
2153
+ } catch (error) {
2154
+ console.error("[lsp] Error notifying close:", error);
2155
+ }
2156
+ },
2157
+ async notifyWatchedFilesChanged(changes) {
2158
+ try {
2159
+ await connection.sendNotification("workspace/didChangeWatchedFiles", {
2160
+ changes
2161
+ });
2162
+ } catch (error) {
2163
+ console.error("[lsp] Error notifying watched files:", error);
2164
+ }
2165
+ },
2166
+ async waitForDiagnostics(filePath, timeoutMs = 5e3) {
2167
+ const normalized = normalizePath(filePath);
2168
+ return new Promise((resolve10) => {
2169
+ const startTime = Date.now();
2170
+ let debounceTimer;
2171
+ let resolved = false;
2172
+ const cleanup = () => {
2173
+ if (debounceTimer) clearTimeout(debounceTimer);
2174
+ const listeners = diagnosticListeners.get(normalized);
2175
+ if (listeners) {
2176
+ const idx = listeners.indexOf(onDiagnostic);
2177
+ if (idx >= 0) listeners.splice(idx, 1);
2178
+ if (listeners.length === 0) {
2179
+ diagnosticListeners.delete(normalized);
2180
+ }
2181
+ }
2182
+ };
2183
+ const finish = () => {
2184
+ if (resolved) return;
2185
+ resolved = true;
2186
+ cleanup();
2187
+ resolve10(diagnostics.get(normalized) || []);
2188
+ };
2189
+ const onDiagnostic = () => {
2190
+ if (debounceTimer) clearTimeout(debounceTimer);
2191
+ debounceTimer = setTimeout(finish, 150);
2192
+ };
2193
+ if (!diagnosticListeners.has(normalized)) {
2194
+ diagnosticListeners.set(normalized, []);
2195
+ }
2196
+ diagnosticListeners.get(normalized).push(onDiagnostic);
2197
+ setTimeout(() => {
2198
+ if (!resolved) {
2199
+ finish();
2200
+ }
2201
+ }, timeoutMs);
2202
+ if (diagnostics.has(normalized)) {
2203
+ onDiagnostic();
2204
+ }
2205
+ });
2206
+ },
2207
+ getDiagnostics(filePath) {
2208
+ return diagnostics.get(normalizePath(filePath)) || [];
2209
+ },
2210
+ getAllDiagnostics() {
2211
+ return new Map(diagnostics);
2212
+ },
2213
+ async shutdown() {
2214
+ try {
2215
+ await connection.sendRequest("shutdown");
2216
+ await connection.sendNotification("exit");
2217
+ connection.end();
2218
+ connection.dispose();
2219
+ proc.kill();
2220
+ } catch (error) {
2221
+ proc.kill("SIGKILL");
2222
+ }
2223
+ }
2224
+ };
2225
+ return client;
2226
+ }
2227
+
2228
+ // src/lsp/types.ts
2229
+ function formatDiagnostic(diagnostic) {
2230
+ const severity = {
2231
+ [1 /* Error */]: "ERROR",
2232
+ [2 /* Warning */]: "WARN",
2233
+ [3 /* Information */]: "INFO",
2234
+ [4 /* Hint */]: "HINT"
2235
+ }[diagnostic.severity ?? 1 /* Error */];
2236
+ const line = diagnostic.range.start.line + 1;
2237
+ const col = diagnostic.range.start.character + 1;
2238
+ const source = diagnostic.source ? ` [${diagnostic.source}]` : "";
2239
+ return `${severity} [${line}:${col}]${source} ${diagnostic.message}`;
2240
+ }
2241
+ function formatDiagnosticsForAgent(filePath, diagnostics, options = {}) {
2242
+ const { maxDiagnostics = 20, errorsOnly = true } = options;
2243
+ const filtered = errorsOnly ? diagnostics.filter((d) => d.severity === 1 /* Error */) : diagnostics;
2244
+ if (filtered.length === 0) return "";
2245
+ const limited = filtered.slice(0, maxDiagnostics);
2246
+ const suffix = filtered.length > maxDiagnostics ? `
2247
+ ... and ${filtered.length - maxDiagnostics} more` : "";
2248
+ const formatted = limited.map(formatDiagnostic).join("\n");
2249
+ return `
2250
+
2251
+ LSP errors detected in this file, please fix:
2252
+ <diagnostics file="${filePath}">
2253
+ ${formatted}${suffix}
2254
+ </diagnostics>`;
2255
+ }
2256
+
2257
+ // src/lsp/index.ts
2258
+ var state = {
2259
+ clients: /* @__PURE__ */ new Map(),
2260
+ broken: /* @__PURE__ */ new Set(),
2261
+ initialized: false
2262
+ };
2263
+ async function getClientForFile(filePath) {
2264
+ const normalized = normalizePath(filePath);
2265
+ const ext = extname2(normalized);
2266
+ const serverDef = getServerForExtension(ext);
2267
+ if (!serverDef) {
2268
+ return null;
2269
+ }
2270
+ const root = dirname4(normalized);
2271
+ const key = `${serverDef.id}:${root}`;
2272
+ const existing = state.clients.get(key);
2273
+ if (existing) {
2274
+ return existing;
2275
+ }
2276
+ if (state.broken.has(key)) {
2277
+ return null;
2278
+ }
2279
+ try {
2280
+ const handle = await serverDef.spawn(root);
2281
+ if (!handle) {
2282
+ state.broken.add(key);
2283
+ return null;
2284
+ }
2285
+ console.log(`[lsp] Started ${serverDef.name} for ${root}`);
2286
+ const client = await createClient(serverDef.id, handle, root);
2287
+ state.clients.set(key, client);
2288
+ handle.process.on("exit", (code) => {
2289
+ console.log(`[lsp] ${serverDef.name} exited with code ${code}`);
2290
+ state.clients.delete(key);
2291
+ });
2292
+ return client;
2293
+ } catch (error) {
2294
+ console.error(`[lsp] Failed to start ${serverDef.name}:`, error);
2295
+ state.broken.add(key);
2296
+ return null;
2297
+ }
2298
+ }
2299
+ async function getClientsForFile(filePath) {
2300
+ const client = await getClientForFile(filePath);
2301
+ return client ? [client] : [];
2302
+ }
2303
+ async function touchFile(filePath, waitForDiagnostics = false) {
2304
+ const clients = await getClientsForFile(filePath);
2305
+ if (clients.length === 0) {
2306
+ return;
2307
+ }
2308
+ await Promise.all(clients.map((client) => client.notifyOpen(filePath)));
2309
+ if (waitForDiagnostics) {
2310
+ await Promise.all(clients.map((client) => client.waitForDiagnostics(filePath)));
2311
+ }
2312
+ }
2313
+ async function getDiagnostics(filePath) {
2314
+ const normalized = normalizePath(filePath);
2315
+ const clients = await getClientsForFile(normalized);
2316
+ const allDiagnostics = [];
2317
+ for (const client of clients) {
2318
+ const diags = client.getDiagnostics(normalized);
2319
+ allDiagnostics.push(...diags);
2320
+ }
2321
+ return allDiagnostics;
2322
+ }
2323
+ async function getAllDiagnostics() {
2324
+ const results = {};
2325
+ for (const client of state.clients.values()) {
2326
+ const clientDiags = client.getAllDiagnostics();
2327
+ for (const [path, diagnostics] of clientDiags.entries()) {
2328
+ const existing = results[path] || [];
2329
+ existing.push(...diagnostics);
2330
+ results[path] = existing;
2331
+ }
2332
+ }
2333
+ return results;
2334
+ }
2335
+ async function formatDiagnosticsOutput(filePath, options = {}) {
2336
+ const diagnostics = await getDiagnostics(filePath);
2337
+ return formatDiagnosticsForAgent(filePath, diagnostics, options);
2338
+ }
2339
+ function isSupported(filePath) {
2340
+ const ext = extname2(filePath);
2341
+ return getServerForExtension(ext) !== null;
2342
+ }
2343
+
1841
2344
  // src/tools/write-file.ts
1842
2345
  var writeFileInputSchema = z4.object({
1843
2346
  path: z4.string().describe("The path to the file. Can be relative to working directory or absolute."),
@@ -1867,7 +2370,7 @@ Working directory: ${options.workingDirectory}`,
1867
2370
  inputSchema: writeFileInputSchema,
1868
2371
  execute: async ({ path, mode, content, old_string, new_string }) => {
1869
2372
  try {
1870
- const absolutePath = isAbsolute2(path) ? path : resolve4(options.workingDirectory, path);
2373
+ const absolutePath = isAbsolute2(path) ? path : resolve5(options.workingDirectory, path);
1871
2374
  const relativePath = relative3(options.workingDirectory, absolutePath);
1872
2375
  if (relativePath.startsWith("..") && !isAbsolute2(path)) {
1873
2376
  return {
@@ -1883,12 +2386,17 @@ Working directory: ${options.workingDirectory}`,
1883
2386
  };
1884
2387
  }
1885
2388
  await backupFile(options.sessionId, options.workingDirectory, absolutePath);
1886
- const dir = dirname3(absolutePath);
1887
- if (!existsSync5(dir)) {
2389
+ const dir = dirname5(absolutePath);
2390
+ if (!existsSync7(dir)) {
1888
2391
  await mkdir3(dir, { recursive: true });
1889
2392
  }
1890
- const existed = existsSync5(absolutePath);
2393
+ const existed = existsSync7(absolutePath);
1891
2394
  await writeFile3(absolutePath, content, "utf-8");
2395
+ let diagnosticsOutput = "";
2396
+ if (options.enableLSP !== false && isSupported(absolutePath)) {
2397
+ await touchFile(absolutePath, true);
2398
+ diagnosticsOutput = await formatDiagnosticsOutput(absolutePath);
2399
+ }
1892
2400
  return {
1893
2401
  success: true,
1894
2402
  path: absolutePath,
@@ -1896,7 +2404,8 @@ Working directory: ${options.workingDirectory}`,
1896
2404
  mode: "full",
1897
2405
  action: existed ? "replaced" : "created",
1898
2406
  bytesWritten: Buffer.byteLength(content, "utf-8"),
1899
- lineCount: content.split("\n").length
2407
+ lineCount: content.split("\n").length,
2408
+ ...diagnosticsOutput && { diagnostics: diagnosticsOutput }
1900
2409
  };
1901
2410
  } else if (mode === "str_replace") {
1902
2411
  if (old_string === void 0 || new_string === void 0) {
@@ -1905,14 +2414,14 @@ Working directory: ${options.workingDirectory}`,
1905
2414
  error: 'Both old_string and new_string are required for "str_replace" mode'
1906
2415
  };
1907
2416
  }
1908
- if (!existsSync5(absolutePath)) {
2417
+ if (!existsSync7(absolutePath)) {
1909
2418
  return {
1910
2419
  success: false,
1911
2420
  error: `File not found: ${path}. Use "full" mode to create new files.`
1912
2421
  };
1913
2422
  }
1914
2423
  await backupFile(options.sessionId, options.workingDirectory, absolutePath);
1915
- const currentContent = await readFile4(absolutePath, "utf-8");
2424
+ const currentContent = await readFile5(absolutePath, "utf-8");
1916
2425
  if (!currentContent.includes(old_string)) {
1917
2426
  const lines = currentContent.split("\n");
1918
2427
  const preview = lines.slice(0, 20).join("\n");
@@ -1936,6 +2445,11 @@ Working directory: ${options.workingDirectory}`,
1936
2445
  await writeFile3(absolutePath, newContent, "utf-8");
1937
2446
  const oldLines = old_string.split("\n").length;
1938
2447
  const newLines = new_string.split("\n").length;
2448
+ let diagnosticsOutput = "";
2449
+ if (options.enableLSP !== false && isSupported(absolutePath)) {
2450
+ await touchFile(absolutePath, true);
2451
+ diagnosticsOutput = await formatDiagnosticsOutput(absolutePath);
2452
+ }
1939
2453
  return {
1940
2454
  success: true,
1941
2455
  path: absolutePath,
@@ -1943,7 +2457,8 @@ Working directory: ${options.workingDirectory}`,
1943
2457
  mode: "str_replace",
1944
2458
  linesRemoved: oldLines,
1945
2459
  linesAdded: newLines,
1946
- lineDelta: newLines - oldLines
2460
+ lineDelta: newLines - oldLines,
2461
+ ...diagnosticsOutput && { diagnostics: diagnosticsOutput }
1947
2462
  };
1948
2463
  }
1949
2464
  return {
@@ -2090,9 +2605,9 @@ import { tool as tool5 } from "ai";
2090
2605
  import { z as z6 } from "zod";
2091
2606
 
2092
2607
  // src/skills/index.ts
2093
- import { readFile as readFile5, readdir } from "fs/promises";
2094
- import { resolve as resolve5, basename, extname } from "path";
2095
- import { existsSync as existsSync6 } from "fs";
2608
+ import { readFile as readFile6, readdir } from "fs/promises";
2609
+ import { resolve as resolve6, basename, extname as extname3 } from "path";
2610
+ import { existsSync as existsSync8 } from "fs";
2096
2611
  function parseSkillFrontmatter(content) {
2097
2612
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
2098
2613
  if (!frontmatterMatch) {
@@ -2120,18 +2635,18 @@ function parseSkillFrontmatter(content) {
2120
2635
  }
2121
2636
  }
2122
2637
  function getSkillNameFromPath(filePath) {
2123
- return basename(filePath, extname(filePath)).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2638
+ return basename(filePath, extname3(filePath)).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2124
2639
  }
2125
2640
  async function loadSkillsFromDirectory(directory) {
2126
- if (!existsSync6(directory)) {
2641
+ if (!existsSync8(directory)) {
2127
2642
  return [];
2128
2643
  }
2129
2644
  const skills = [];
2130
2645
  const files = await readdir(directory);
2131
2646
  for (const file of files) {
2132
2647
  if (!file.endsWith(".md")) continue;
2133
- const filePath = resolve5(directory, file);
2134
- const content = await readFile5(filePath, "utf-8");
2648
+ const filePath = resolve6(directory, file);
2649
+ const content = await readFile6(filePath, "utf-8");
2135
2650
  const parsed = parseSkillFrontmatter(content);
2136
2651
  if (parsed) {
2137
2652
  skills.push({
@@ -2173,7 +2688,7 @@ async function loadSkillContent(skillName, directories) {
2173
2688
  if (!skill) {
2174
2689
  return null;
2175
2690
  }
2176
- const content = await readFile5(skill.filePath, "utf-8");
2691
+ const content = await readFile6(skill.filePath, "utf-8");
2177
2692
  const parsed = parseSkillFrontmatter(content);
2178
2693
  return {
2179
2694
  ...skill,
@@ -2271,6 +2786,199 @@ Once loaded, a skill's content will be available in the conversation context.`,
2271
2786
  });
2272
2787
  }
2273
2788
 
2789
+ // src/tools/linter.ts
2790
+ import { tool as tool6 } from "ai";
2791
+ import { z as z7 } from "zod";
2792
+ import { resolve as resolve7, relative as relative4, isAbsolute as isAbsolute3, extname as extname4 } from "path";
2793
+ import { existsSync as existsSync9 } from "fs";
2794
+ import { readdir as readdir2, stat as stat2 } from "fs/promises";
2795
+ var linterInputSchema = z7.object({
2796
+ 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."),
2797
+ fix: z7.boolean().optional().default(false).describe("Reserved for future use: auto-fix lint errors (not yet implemented)")
2798
+ });
2799
+ async function findSupportedFiles(dir, workingDirectory, maxFiles = 50) {
2800
+ const files = [];
2801
+ const supportedExtensions = getSupportedExtensions();
2802
+ async function walk(currentDir) {
2803
+ if (files.length >= maxFiles) return;
2804
+ try {
2805
+ const entries = await readdir2(currentDir, { withFileTypes: true });
2806
+ for (const entry of entries) {
2807
+ if (files.length >= maxFiles) break;
2808
+ const fullPath = resolve7(currentDir, entry.name);
2809
+ if (entry.isDirectory()) {
2810
+ if (["node_modules", ".git", "dist", "build", ".next", "coverage"].includes(entry.name)) {
2811
+ continue;
2812
+ }
2813
+ await walk(fullPath);
2814
+ } else if (entry.isFile()) {
2815
+ const ext = extname4(entry.name);
2816
+ if (supportedExtensions.includes(ext)) {
2817
+ files.push(fullPath);
2818
+ }
2819
+ }
2820
+ }
2821
+ } catch {
2822
+ }
2823
+ }
2824
+ await walk(dir);
2825
+ return files;
2826
+ }
2827
+ function createLinterTool(options) {
2828
+ return tool6({
2829
+ description: `Check files for linting and type errors using the Language Server Protocol (LSP).
2830
+
2831
+ Supports TypeScript, JavaScript, TSX, JSX files.
2832
+
2833
+ Usage:
2834
+ - \`linter({})\` - Get diagnostics for all recently edited files
2835
+ - \`linter({ paths: ["src/app.ts"] })\` - Check specific files
2836
+ - \`linter({ paths: ["src/"] })\` - Check all supported files in a directory
2837
+
2838
+ Returns detailed error information including line numbers, error messages, and severity.
2839
+ Use this after making changes to verify your code is correct, or proactively to find issues.
2840
+
2841
+ Working directory: ${options.workingDirectory}`,
2842
+ inputSchema: linterInputSchema,
2843
+ execute: async ({ paths }) => {
2844
+ try {
2845
+ if (!paths || paths.length === 0) {
2846
+ const allDiagnostics = await getAllDiagnostics();
2847
+ if (Object.keys(allDiagnostics).length === 0) {
2848
+ return {
2849
+ success: true,
2850
+ message: "No lint errors found. No files have been analyzed yet - specify paths to check specific files.",
2851
+ files: [],
2852
+ totalErrors: 0,
2853
+ totalWarnings: 0
2854
+ };
2855
+ }
2856
+ return formatDiagnosticsResult(allDiagnostics, options.workingDirectory);
2857
+ }
2858
+ const filesToCheck = [];
2859
+ for (const path of paths) {
2860
+ const absolutePath = isAbsolute3(path) ? path : resolve7(options.workingDirectory, path);
2861
+ if (!existsSync9(absolutePath)) {
2862
+ continue;
2863
+ }
2864
+ const stats = await stat2(absolutePath);
2865
+ if (stats.isDirectory()) {
2866
+ const dirFiles = await findSupportedFiles(absolutePath, options.workingDirectory);
2867
+ filesToCheck.push(...dirFiles);
2868
+ } else if (stats.isFile()) {
2869
+ if (isSupported(absolutePath)) {
2870
+ filesToCheck.push(absolutePath);
2871
+ }
2872
+ }
2873
+ }
2874
+ if (filesToCheck.length === 0) {
2875
+ return {
2876
+ success: true,
2877
+ message: "No supported files found to check. Supported extensions: " + getSupportedExtensions().join(", "),
2878
+ files: [],
2879
+ totalErrors: 0,
2880
+ totalWarnings: 0
2881
+ };
2882
+ }
2883
+ await Promise.all(
2884
+ filesToCheck.map((file) => touchFile(file, true))
2885
+ );
2886
+ const diagnosticsMap = {};
2887
+ for (const file of filesToCheck) {
2888
+ const diagnostics = await getDiagnostics(file);
2889
+ if (diagnostics.length > 0) {
2890
+ diagnosticsMap[file] = diagnostics;
2891
+ }
2892
+ }
2893
+ return formatDiagnosticsResult(diagnosticsMap, options.workingDirectory);
2894
+ } catch (error) {
2895
+ return {
2896
+ success: false,
2897
+ error: error.message
2898
+ };
2899
+ }
2900
+ }
2901
+ });
2902
+ }
2903
+ function formatDiagnosticsResult(diagnosticsMap, workingDirectory) {
2904
+ let totalErrors = 0;
2905
+ let totalWarnings = 0;
2906
+ let totalInfo = 0;
2907
+ const files = [];
2908
+ for (const [filePath, diagnostics] of Object.entries(diagnosticsMap)) {
2909
+ const relativePath = relative4(workingDirectory, filePath);
2910
+ let fileErrors = 0;
2911
+ let fileWarnings = 0;
2912
+ const formattedDiagnostics = diagnostics.map((d) => {
2913
+ const severity = getSeverityString(d.severity);
2914
+ if (d.severity === 1 /* Error */) {
2915
+ fileErrors++;
2916
+ totalErrors++;
2917
+ } else if (d.severity === 2 /* Warning */) {
2918
+ fileWarnings++;
2919
+ totalWarnings++;
2920
+ } else {
2921
+ totalInfo++;
2922
+ }
2923
+ return {
2924
+ severity,
2925
+ line: d.range.start.line + 1,
2926
+ column: d.range.start.character + 1,
2927
+ message: d.message,
2928
+ source: d.source,
2929
+ code: d.code
2930
+ };
2931
+ });
2932
+ files.push({
2933
+ path: filePath,
2934
+ relativePath,
2935
+ errors: fileErrors,
2936
+ warnings: fileWarnings,
2937
+ diagnostics: formattedDiagnostics
2938
+ });
2939
+ }
2940
+ files.sort((a, b) => b.errors - a.errors);
2941
+ const hasIssues = totalErrors > 0 || totalWarnings > 0;
2942
+ return {
2943
+ success: true,
2944
+ 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).`,
2945
+ files,
2946
+ totalErrors,
2947
+ totalWarnings,
2948
+ totalInfo,
2949
+ summary: hasIssues ? formatSummary(files) : void 0
2950
+ };
2951
+ }
2952
+ function getSeverityString(severity) {
2953
+ switch (severity) {
2954
+ case 1 /* Error */:
2955
+ return "error";
2956
+ case 2 /* Warning */:
2957
+ return "warning";
2958
+ case 3 /* Information */:
2959
+ return "info";
2960
+ case 4 /* Hint */:
2961
+ return "hint";
2962
+ default:
2963
+ return "error";
2964
+ }
2965
+ }
2966
+ function formatSummary(files) {
2967
+ const lines = [];
2968
+ for (const file of files) {
2969
+ lines.push(`
2970
+ ${file.relativePath}:`);
2971
+ for (const d of file.diagnostics.slice(0, 10)) {
2972
+ const prefix = d.severity === "error" ? "\u274C" : d.severity === "warning" ? "\u26A0\uFE0F" : "\u2139\uFE0F";
2973
+ lines.push(` ${prefix} [${d.line}:${d.column}] ${d.message}`);
2974
+ }
2975
+ if (file.diagnostics.length > 10) {
2976
+ lines.push(` ... and ${file.diagnostics.length - 10} more`);
2977
+ }
2978
+ }
2979
+ return lines.join("\n");
2980
+ }
2981
+
2274
2982
  // src/tools/index.ts
2275
2983
  function createTools(options) {
2276
2984
  return {
@@ -2285,7 +2993,8 @@ function createTools(options) {
2285
2993
  }),
2286
2994
  write_file: createWriteFileTool({
2287
2995
  workingDirectory: options.workingDirectory,
2288
- sessionId: options.sessionId
2996
+ sessionId: options.sessionId,
2997
+ enableLSP: options.enableLSP ?? true
2289
2998
  }),
2290
2999
  todo: createTodoTool({
2291
3000
  sessionId: options.sessionId
@@ -2293,6 +3002,9 @@ function createTools(options) {
2293
3002
  load_skill: createLoadSkillTool({
2294
3003
  sessionId: options.sessionId,
2295
3004
  skillsDirectories: options.skillsDirectories
3005
+ }),
3006
+ linter: createLinterTool({
3007
+ workingDirectory: options.workingDirectory
2296
3008
  })
2297
3009
  };
2298
3010
  }
@@ -2340,6 +3052,7 @@ You have access to powerful tools for:
2340
3052
  - **bash**: Execute commands in the terminal (see below for details)
2341
3053
  - **read_file**: Read file contents to understand code and context
2342
3054
  - **write_file**: Create new files or edit existing ones (supports targeted string replacement)
3055
+ - **linter**: Check files for type errors and lint issues (TypeScript, JavaScript, TSX, JSX)
2343
3056
  - **todo**: Manage your task list to track progress on complex operations
2344
3057
  - **load_skill**: Load specialized knowledge documents for specific tasks
2345
3058
 
@@ -2396,7 +3109,7 @@ bash({ id: "abc123", input: "my text" }) // send text input
2396
3109
  - Use \`key: "y"\` or \`key: "n"\` for yes/no prompts
2397
3110
  - Use \`input: "text"\` for text input prompts
2398
3111
 
2399
- Logs are saved to \`.sparkecoder/terminals/{id}/output.log\` and can be read with \`read_file\` if needed.
3112
+ Terminal output is stored in the global SparkECoder data directory. Use the \`tail\` option to read recent output.
2400
3113
 
2401
3114
  ## Guidelines
2402
3115
 
@@ -2416,7 +3129,17 @@ Logs are saved to \`.sparkecoder/terminals/{id}/output.log\` and can be read wit
2416
3129
  - Use \`read_file\` to understand code before modifying
2417
3130
  - Use \`write_file\` with mode "str_replace" for targeted edits to existing files
2418
3131
  - Use \`write_file\` with mode "full" only for new files or complete rewrites
2419
- - Always verify changes by reading files after modifications
3132
+ - After making changes, use the \`linter\` tool to check for type errors and lint issues
3133
+ - The \`write_file\` tool automatically shows lint errors in its output for TypeScript/JavaScript files
3134
+
3135
+ ### Linter Tool
3136
+ The linter tool uses Language Server Protocol (LSP) to detect type errors and lint issues:
3137
+ \`\`\`
3138
+ linter({}) // Check all recently edited files
3139
+ linter({ paths: ["src/app.ts"] }) // Check specific files
3140
+ linter({ paths: ["src/"] }) // Check all files in a directory
3141
+ \`\`\`
3142
+ Use this proactively after making code changes to catch errors early.
2420
3143
 
2421
3144
  ### Searching and Exploration
2422
3145
  ${searchInstructions}
@@ -2766,9 +3489,9 @@ var Agent = class _Agent {
2766
3489
  wrappedTools[name] = originalTool;
2767
3490
  continue;
2768
3491
  }
2769
- wrappedTools[name] = tool6({
3492
+ wrappedTools[name] = tool7({
2770
3493
  description: originalTool.description || "",
2771
- inputSchema: originalTool.inputSchema || z7.object({}),
3494
+ inputSchema: originalTool.inputSchema || z8.object({}),
2772
3495
  execute: async (input, toolOptions) => {
2773
3496
  const toolCallId = toolOptions.toolCallId || nanoid3();
2774
3497
  const execution = toolExecutionQueries.create({
@@ -2782,8 +3505,8 @@ var Agent = class _Agent {
2782
3505
  this.pendingApprovals.set(toolCallId, execution);
2783
3506
  options.onApprovalRequired?.(execution);
2784
3507
  sessionQueries.updateStatus(this.session.id, "waiting");
2785
- const approved = await new Promise((resolve8) => {
2786
- approvalResolvers.set(toolCallId, { resolve: resolve8, sessionId: this.session.id });
3508
+ const approved = await new Promise((resolve10) => {
3509
+ approvalResolvers.set(toolCallId, { resolve: resolve10, sessionId: this.session.id });
2787
3510
  });
2788
3511
  const resolverData = approvalResolvers.get(toolCallId);
2789
3512
  approvalResolvers.delete(toolCallId);
@@ -2878,18 +3601,18 @@ var Agent = class _Agent {
2878
3601
 
2879
3602
  // src/server/routes/sessions.ts
2880
3603
  var sessions2 = new Hono();
2881
- var createSessionSchema = z8.object({
2882
- name: z8.string().optional(),
2883
- workingDirectory: z8.string().optional(),
2884
- model: z8.string().optional(),
2885
- toolApprovals: z8.record(z8.string(), z8.boolean()).optional()
3604
+ var createSessionSchema = z9.object({
3605
+ name: z9.string().optional(),
3606
+ workingDirectory: z9.string().optional(),
3607
+ model: z9.string().optional(),
3608
+ toolApprovals: z9.record(z9.string(), z9.boolean()).optional()
2886
3609
  });
2887
- var paginationQuerySchema = z8.object({
2888
- limit: z8.string().optional(),
2889
- offset: z8.string().optional()
3610
+ var paginationQuerySchema = z9.object({
3611
+ limit: z9.string().optional(),
3612
+ offset: z9.string().optional()
2890
3613
  });
2891
- var messagesQuerySchema = z8.object({
2892
- limit: z8.string().optional()
3614
+ var messagesQuerySchema = z9.object({
3615
+ limit: z9.string().optional()
2893
3616
  });
2894
3617
  sessions2.get(
2895
3618
  "/",
@@ -3028,10 +3751,10 @@ sessions2.get("/:id/tools", async (c) => {
3028
3751
  count: executions.length
3029
3752
  });
3030
3753
  });
3031
- var updateSessionSchema = z8.object({
3032
- model: z8.string().optional(),
3033
- name: z8.string().optional(),
3034
- toolApprovals: z8.record(z8.string(), z8.boolean()).optional()
3754
+ var updateSessionSchema = z9.object({
3755
+ model: z9.string().optional(),
3756
+ name: z9.string().optional(),
3757
+ toolApprovals: z9.record(z9.string(), z9.boolean()).optional()
3035
3758
  });
3036
3759
  sessions2.patch(
3037
3760
  "/:id",
@@ -3232,7 +3955,7 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
3232
3955
  // src/server/routes/agents.ts
3233
3956
  import { Hono as Hono2 } from "hono";
3234
3957
  import { zValidator as zValidator2 } from "@hono/zod-validator";
3235
- import { z as z9 } from "zod";
3958
+ import { z as z10 } from "zod";
3236
3959
 
3237
3960
  // src/server/resumable-stream.ts
3238
3961
  import { createResumableStreamContext } from "resumable-stream/generic";
@@ -3309,18 +4032,18 @@ var streamContext = createResumableStreamContext({
3309
4032
  // src/server/routes/agents.ts
3310
4033
  import { nanoid as nanoid4 } from "nanoid";
3311
4034
  var agents = new Hono2();
3312
- var runPromptSchema = z9.object({
3313
- prompt: z9.string().min(1)
4035
+ var runPromptSchema = z10.object({
4036
+ prompt: z10.string().min(1)
3314
4037
  });
3315
- var quickStartSchema = z9.object({
3316
- prompt: z9.string().min(1),
3317
- name: z9.string().optional(),
3318
- workingDirectory: z9.string().optional(),
3319
- model: z9.string().optional(),
3320
- toolApprovals: z9.record(z9.string(), z9.boolean()).optional()
4038
+ var quickStartSchema = z10.object({
4039
+ prompt: z10.string().min(1),
4040
+ name: z10.string().optional(),
4041
+ workingDirectory: z10.string().optional(),
4042
+ model: z10.string().optional(),
4043
+ toolApprovals: z10.record(z10.string(), z10.boolean()).optional()
3321
4044
  });
3322
- var rejectSchema = z9.object({
3323
- reason: z9.string().optional()
4045
+ var rejectSchema = z10.object({
4046
+ reason: z10.string().optional()
3324
4047
  }).optional();
3325
4048
  var streamAbortControllers = /* @__PURE__ */ new Map();
3326
4049
  function createAgentStreamProducer(sessionId, prompt, streamId) {
@@ -3881,7 +4604,7 @@ agents.post(
3881
4604
  // src/server/routes/health.ts
3882
4605
  import { Hono as Hono3 } from "hono";
3883
4606
  import { zValidator as zValidator3 } from "@hono/zod-validator";
3884
- import { z as z10 } from "zod";
4607
+ import { z as z11 } from "zod";
3885
4608
  var health = new Hono3();
3886
4609
  health.get("/", async (c) => {
3887
4610
  const config = getConfig();
@@ -3927,9 +4650,9 @@ health.get("/api-keys", async (c) => {
3927
4650
  supportedProviders: SUPPORTED_PROVIDERS
3928
4651
  });
3929
4652
  });
3930
- var setApiKeySchema = z10.object({
3931
- provider: z10.string(),
3932
- apiKey: z10.string().min(1)
4653
+ var setApiKeySchema = z11.object({
4654
+ provider: z11.string(),
4655
+ apiKey: z11.string().min(1)
3933
4656
  });
3934
4657
  health.post(
3935
4658
  "/api-keys",
@@ -3968,12 +4691,12 @@ health.delete("/api-keys/:provider", async (c) => {
3968
4691
  // src/server/routes/terminals.ts
3969
4692
  import { Hono as Hono4 } from "hono";
3970
4693
  import { zValidator as zValidator4 } from "@hono/zod-validator";
3971
- import { z as z11 } from "zod";
4694
+ import { z as z12 } from "zod";
3972
4695
  var terminals2 = new Hono4();
3973
- var spawnSchema = z11.object({
3974
- command: z11.string(),
3975
- cwd: z11.string().optional(),
3976
- name: z11.string().optional()
4696
+ var spawnSchema = z12.object({
4697
+ command: z12.string(),
4698
+ cwd: z12.string().optional(),
4699
+ name: z12.string().optional()
3977
4700
  });
3978
4701
  terminals2.post(
3979
4702
  "/:sessionId/terminals",
@@ -4054,8 +4777,8 @@ terminals2.get("/:sessionId/terminals/:terminalId", async (c) => {
4054
4777
  // We don't track exit codes in tmux mode
4055
4778
  });
4056
4779
  });
4057
- var logsQuerySchema = z11.object({
4058
- tail: z11.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
4780
+ var logsQuerySchema = z12.object({
4781
+ tail: z12.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
4059
4782
  });
4060
4783
  terminals2.get(
4061
4784
  "/:sessionId/terminals/:terminalId/logs",
@@ -4079,8 +4802,8 @@ terminals2.get(
4079
4802
  });
4080
4803
  }
4081
4804
  );
4082
- var killSchema = z11.object({
4083
- signal: z11.enum(["SIGTERM", "SIGKILL"]).optional()
4805
+ var killSchema = z12.object({
4806
+ signal: z12.enum(["SIGTERM", "SIGKILL"]).optional()
4084
4807
  });
4085
4808
  terminals2.post(
4086
4809
  "/:sessionId/terminals/:terminalId/kill",
@@ -4094,8 +4817,8 @@ terminals2.post(
4094
4817
  return c.json({ success: true, message: "Terminal killed" });
4095
4818
  }
4096
4819
  );
4097
- var writeSchema = z11.object({
4098
- input: z11.string()
4820
+ var writeSchema = z12.object({
4821
+ input: z12.string()
4099
4822
  });
4100
4823
  terminals2.post(
4101
4824
  "/:sessionId/terminals/:terminalId/write",
@@ -4415,13 +5138,13 @@ var DEFAULT_WEB_PORT = 6969;
4415
5138
  var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
4416
5139
  function getWebDirectory() {
4417
5140
  try {
4418
- const currentDir = dirname4(fileURLToPath(import.meta.url));
4419
- const webDir = resolve6(currentDir, "..", "web");
4420
- if (existsSync7(webDir) && existsSync7(join3(webDir, "package.json"))) {
5141
+ const currentDir = dirname6(fileURLToPath2(import.meta.url));
5142
+ const webDir = resolve8(currentDir, "..", "web");
5143
+ if (existsSync10(webDir) && existsSync10(join3(webDir, "package.json"))) {
4421
5144
  return webDir;
4422
5145
  }
4423
- const altWebDir = resolve6(currentDir, "..", "..", "web");
4424
- if (existsSync7(altWebDir) && existsSync7(join3(altWebDir, "package.json"))) {
5146
+ const altWebDir = resolve8(currentDir, "..", "..", "web");
5147
+ if (existsSync10(altWebDir) && existsSync10(join3(altWebDir, "package.json"))) {
4425
5148
  return altWebDir;
4426
5149
  }
4427
5150
  return null;
@@ -4444,18 +5167,18 @@ async function isSparkcoderWebRunning(port) {
4444
5167
  }
4445
5168
  }
4446
5169
  function isPortInUse(port) {
4447
- return new Promise((resolve8) => {
5170
+ return new Promise((resolve10) => {
4448
5171
  const server = createNetServer();
4449
5172
  server.once("error", (err) => {
4450
5173
  if (err.code === "EADDRINUSE") {
4451
- resolve8(true);
5174
+ resolve10(true);
4452
5175
  } else {
4453
- resolve8(false);
5176
+ resolve10(false);
4454
5177
  }
4455
5178
  });
4456
5179
  server.once("listening", () => {
4457
5180
  server.close();
4458
- resolve8(false);
5181
+ resolve10(false);
4459
5182
  });
4460
5183
  server.listen(port, "0.0.0.0");
4461
5184
  });
@@ -4480,11 +5203,11 @@ async function findWebPort(preferredPort) {
4480
5203
  }
4481
5204
  function hasProductionBuild(webDir) {
4482
5205
  const buildIdPath = join3(webDir, ".next", "BUILD_ID");
4483
- return existsSync7(buildIdPath);
5206
+ return existsSync10(buildIdPath);
4484
5207
  }
4485
5208
  function runCommand(command, args, cwd, env) {
4486
- return new Promise((resolve8) => {
4487
- const child = spawn(command, args, {
5209
+ return new Promise((resolve10) => {
5210
+ const child = spawn2(command, args, {
4488
5211
  cwd,
4489
5212
  stdio: ["ignore", "pipe", "pipe"],
4490
5213
  env,
@@ -4498,10 +5221,10 @@ function runCommand(command, args, cwd, env) {
4498
5221
  output += data.toString();
4499
5222
  });
4500
5223
  child.on("close", (code) => {
4501
- resolve8({ success: code === 0, output });
5224
+ resolve10({ success: code === 0, output });
4502
5225
  });
4503
5226
  child.on("error", (err) => {
4504
- resolve8({ success: false, output: err.message });
5227
+ resolve10({ success: false, output: err.message });
4505
5228
  });
4506
5229
  });
4507
5230
  }
@@ -4516,13 +5239,15 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false) {
4516
5239
  if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
4517
5240
  return { process: null, port: actualPort };
4518
5241
  }
4519
- const usePnpm = existsSync7(join3(webDir, "pnpm-lock.yaml"));
4520
- const useNpm = !usePnpm && existsSync7(join3(webDir, "package-lock.json"));
5242
+ const usePnpm = existsSync10(join3(webDir, "pnpm-lock.yaml"));
5243
+ const useNpm = !usePnpm && existsSync10(join3(webDir, "package-lock.json"));
4521
5244
  const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
4522
5245
  const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
4523
5246
  const webEnv = {
4524
5247
  ...cleanEnv,
4525
- NEXT_PUBLIC_API_URL: `http://127.0.0.1:${apiPort}`
5248
+ NEXT_PUBLIC_API_URL: `http://127.0.0.1:${apiPort}`,
5249
+ PORT: String(actualPort)
5250
+ // Next.js respects PORT env var
4526
5251
  };
4527
5252
  const isProduction = process.env.NODE_ENV === "production";
4528
5253
  let command;
@@ -4544,7 +5269,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false) {
4544
5269
  command = pkgManager;
4545
5270
  args = pkgManager === "npx" ? ["next", "dev", "-p", String(actualPort)] : ["run", "dev", "-p", String(actualPort)];
4546
5271
  }
4547
- const child = spawn(command, args, {
5272
+ const child = spawn2(command, args, {
4548
5273
  cwd: webDir,
4549
5274
  stdio: ["ignore", "pipe", "pipe"],
4550
5275
  env: webEnv,
@@ -4555,10 +5280,10 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false) {
4555
5280
  let started = false;
4556
5281
  let exited = false;
4557
5282
  let exitCode = null;
4558
- const startedPromise = new Promise((resolve8) => {
5283
+ const startedPromise = new Promise((resolve10) => {
4559
5284
  const timeout = setTimeout(() => {
4560
5285
  if (!started && !exited) {
4561
- resolve8(false);
5286
+ resolve10(false);
4562
5287
  }
4563
5288
  }, startupTimeout);
4564
5289
  child.stdout?.on("data", (data) => {
@@ -4566,7 +5291,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false) {
4566
5291
  if (!started && (output.includes("Ready") || output.includes("started") || output.includes("localhost"))) {
4567
5292
  started = true;
4568
5293
  clearTimeout(timeout);
4569
- resolve8(true);
5294
+ resolve10(true);
4570
5295
  }
4571
5296
  });
4572
5297
  child.stderr?.on("data", (data) => {
@@ -4578,14 +5303,14 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false) {
4578
5303
  child.on("error", (err) => {
4579
5304
  if (!quiet) console.error(` \u274C Web UI spawn error: ${err.message}`);
4580
5305
  clearTimeout(timeout);
4581
- resolve8(false);
5306
+ resolve10(false);
4582
5307
  });
4583
5308
  child.on("exit", (code) => {
4584
5309
  exited = true;
4585
5310
  exitCode = code;
4586
5311
  if (!started) {
4587
5312
  clearTimeout(timeout);
4588
- resolve8(false);
5313
+ resolve10(false);
4589
5314
  }
4590
5315
  webUIProcess = null;
4591
5316
  });
@@ -4670,8 +5395,8 @@ async function startServer(options = {}) {
4670
5395
  if (options.workingDirectory) {
4671
5396
  config.resolvedWorkingDirectory = options.workingDirectory;
4672
5397
  }
4673
- if (!existsSync7(config.resolvedWorkingDirectory)) {
4674
- mkdirSync2(config.resolvedWorkingDirectory, { recursive: true });
5398
+ if (!existsSync10(config.resolvedWorkingDirectory)) {
5399
+ mkdirSync3(config.resolvedWorkingDirectory, { recursive: true });
4675
5400
  if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
4676
5401
  }
4677
5402
  initDatabase(config.resolvedDatabasePath);
@@ -5170,8 +5895,8 @@ function generateOpenAPISpec() {
5170
5895
  }
5171
5896
 
5172
5897
  // src/cli.ts
5173
- import { writeFileSync as writeFileSync2, existsSync as existsSync8 } from "fs";
5174
- import { resolve as resolve7, join as join4 } from "path";
5898
+ import { writeFileSync as writeFileSync2, existsSync as existsSync11 } from "fs";
5899
+ import { resolve as resolve9, join as join4 } from "path";
5175
5900
  async function apiRequest(baseUrl, path, options = {}) {
5176
5901
  const url = `${baseUrl}${path}`;
5177
5902
  const init = {
@@ -5206,13 +5931,13 @@ async function getActiveStream(baseUrl, sessionId) {
5206
5931
  return { hasActiveStream: false };
5207
5932
  }
5208
5933
  function promptApproval(rl, toolName, input) {
5209
- return new Promise((resolve8) => {
5934
+ return new Promise((resolve10) => {
5210
5935
  const inputStr = JSON.stringify(input);
5211
5936
  const truncatedInput = inputStr.length > 100 ? inputStr.slice(0, 100) + "..." : inputStr;
5212
5937
  console.log(chalk.dim(` Command: ${truncatedInput}`));
5213
5938
  rl.question(chalk.yellow(` Approve? [y/n]: `), (answer) => {
5214
5939
  const approved = answer.toLowerCase().startsWith("y");
5215
- resolve8(approved);
5940
+ resolve10(approved);
5216
5941
  });
5217
5942
  });
5218
5943
  }
@@ -5357,8 +6082,8 @@ async function runChat(options) {
5357
6082
  host: options.host,
5358
6083
  configPath: options.config,
5359
6084
  workingDirectory: options.workingDir,
5360
- quiet: true,
5361
- // Clean chat output
6085
+ quiet: !options.verbose,
6086
+ // Clean chat output unless verbose
5362
6087
  webUI: options.web !== false,
5363
6088
  // Server handles web UI
5364
6089
  webPort: parseInt(options.webPort || "6969")
@@ -5393,9 +6118,9 @@ async function runChat(options) {
5393
6118
  input: process.stdin,
5394
6119
  output: process.stdout
5395
6120
  });
5396
- const apiKey = await new Promise((resolve8) => {
6121
+ const apiKey = await new Promise((resolve10) => {
5397
6122
  keyRl.question(chalk.cyan("Enter your AI Gateway API key: "), (answer) => {
5398
- resolve8(answer.trim());
6123
+ resolve10(answer.trim());
5399
6124
  });
5400
6125
  });
5401
6126
  keyRl.close();
@@ -5462,8 +6187,12 @@ async function runChat(options) {
5462
6187
  const data = await response.json();
5463
6188
  sessionId = data.id;
5464
6189
  }
6190
+ const sessionInfo = await apiRequest(baseUrl, `/sessions/${sessionId}`);
6191
+ const sessionData = sessionInfo.ok ? await sessionInfo.json() : null;
6192
+ const workingDir = sessionData?.workingDirectory || process.cwd();
5465
6193
  console.log("");
5466
6194
  console.log(chalk.bold.cyan("\u{1F436} SparkECoder"));
6195
+ console.log(chalk.dim(`Working directory: ${workingDir}`));
5467
6196
  console.log(chalk.dim("Commands: /quit, /clear, /session, /tools, /help"));
5468
6197
  console.log("");
5469
6198
  const prompt = () => {
@@ -5674,10 +6403,10 @@ Unexpected error: ${outerError.message}`));
5674
6403
  }
5675
6404
  }
5676
6405
  var program = new Command();
5677
- program.name("sparkecoder").description("AI coding agent - just type sparkecoder to start chatting").version("0.1.0").option("-s, --session <id>", "Resume an existing session").option("-n, --name <name>", "Name for the new session").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-c, --config <path>", "Path to config file").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("--no-auto-start", "Do not auto-start server if not running").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI when auto-starting server").action(async (options) => {
6406
+ program.name("sparkecoder").description("AI coding agent - just type sparkecoder to start chatting").version("0.1.0").option("-s, --session <id>", "Resume an existing session").option("-n, --name <name>", "Name for the new session").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-c, --config <path>", "Path to config file").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("--no-auto-start", "Do not auto-start server if not running").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI when auto-starting server").option("-v, --verbose", "Enable verbose logging for web server").action(async (options) => {
5678
6407
  await runChat(options);
5679
6408
  });
5680
- program.command("server").description("Start the SparkECoder server (API + Web UI)").option("-p, --port <port>", "API server port", "3141").option("-h, --host <host>", "Server host", "127.0.0.1").option("-c, --config <path>", "Path to config file").option("-w, --working-dir <path>", "Working directory").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI").action(async (options) => {
6409
+ program.command("server").description("Start the SparkECoder server (API + Web UI)").option("-p, --port <port>", "API server port", "3141").option("-h, --host <host>", "Server host", "127.0.0.1").option("-c, --config <path>", "Path to config file").option("-w, --working-dir <path>", "Working directory").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI").option("-v, --verbose", "Enable verbose logging for web server").action(async (options) => {
5681
6410
  await ensureDependencies({ quiet: false });
5682
6411
  const spinner = ora("Starting SparkECoder server...").start();
5683
6412
  try {
@@ -5687,7 +6416,8 @@ program.command("server").description("Start the SparkECoder server (API + Web U
5687
6416
  configPath: options.config,
5688
6417
  workingDirectory: options.workingDir,
5689
6418
  webUI: options.web !== false,
5690
- webPort: parseInt(options.webPort) || 6969
6419
+ webPort: parseInt(options.webPort) || 6969,
6420
+ quiet: !options.verbose
5691
6421
  });
5692
6422
  if (webPort) {
5693
6423
  spinner.succeed(chalk.green(`SparkECoder running at http://localhost:${webPort}`));
@@ -5711,7 +6441,7 @@ program.command("server").description("Start the SparkECoder server (API + Web U
5711
6441
  process.exit(1);
5712
6442
  }
5713
6443
  });
5714
- program.command("chat").description("Start an interactive chat session").option("-s, --session <id>", "Resume an existing session").option("-n, --name <name>", "Name for the new session").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-c, --config <path>", "Path to config file").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("--no-auto-start", "Do not auto-start server if not running").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI when auto-starting server").action(async (options) => {
6444
+ program.command("chat").description("Start an interactive chat session").option("-s, --session <id>", "Resume an existing session").option("-n, --name <name>", "Name for the new session").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-c, --config <path>", "Path to config file").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("--no-auto-start", "Do not auto-start server if not running").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI when auto-starting server").option("-v, --verbose", "Enable verbose logging for web server").action(async (options) => {
5715
6445
  await runChat(options);
5716
6446
  });
5717
6447
  program.command("init").description("Create a sparkecoder.config.json file").option("-f, --force", "Overwrite existing config").option("-g, --global", "Create global config in app data directory").action((options) => {
@@ -5722,10 +6452,10 @@ program.command("init").description("Create a sparkecoder.config.json file").opt
5722
6452
  configPath = join4(appDataDir, "sparkecoder.config.json");
5723
6453
  configLocation = "global";
5724
6454
  } else {
5725
- configPath = resolve7(process.cwd(), "sparkecoder.config.json");
6455
+ configPath = resolve9(process.cwd(), "sparkecoder.config.json");
5726
6456
  configLocation = "local";
5727
6457
  }
5728
- if (existsSync8(configPath) && !options.force) {
6458
+ if (existsSync11(configPath) && !options.force) {
5729
6459
  console.log(chalk.yellow("Config file already exists. Use --force to overwrite."));
5730
6460
  console.log(chalk.dim(` ${configPath}`));
5731
6461
  return;