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.
- package/dist/agent/index.js +768 -34
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +850 -120
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +831 -106
- package/dist/index.js.map +1 -1
- package/dist/server/index.js +831 -106
- package/dist/server/index.js.map +1 -1
- package/dist/tools/index.d.ts +56 -34
- package/dist/tools/index.js +866 -127
- package/dist/tools/index.js.map +1 -1
- package/package.json +2 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/build-manifest.json +2 -2
- package/web/.next/cache/.previewinfo +1 -1
- package/web/.next/cache/.rscinfo +1 -1
- package/web/.next/cache/.tsbuildinfo +1 -1
- package/web/.next/cache/config.json +3 -3
- package/web/.next/dev/cache/.rscinfo +1 -1
- package/web/.next/dev/cache/config.json +3 -3
- package/web/.next/dev/cache/turbopack/731ace46/00000001.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000002.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000004.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000005.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000007.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000008.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000010.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000011.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000012.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000014.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000015.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000017.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000018.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000019.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000020.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000021.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000022.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000023.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/{00000029.meta → 00000025.meta} +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000026.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000027.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000028.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000029.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000030.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/{00000039.meta → 00000031.meta} +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/{00000040.meta → 00000032.meta} +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000033.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000034.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000035.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000036.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000037.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000038.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000039.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000040.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000041.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000042.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/{00000045.meta → 00000043.meta} +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000044.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000045.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000046.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000047.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000048.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000049.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000050.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000051.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000052.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000053.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000054.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000055.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000056.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000057.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000058.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000059.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000060.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000061.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000062.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000063.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/{00000042.sst → 00000064.sst} +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000065.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000066.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000067.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000068.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000069.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000070.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000071.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000072.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000073.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000074.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000075.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000076.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000077.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000078.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000079.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000080.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/CURRENT +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/LOG +68 -30
- package/web/.next/dev/prerender-manifest.json +3 -3
- package/web/.next/dev/server/server-reference-manifest.js +1 -1
- package/web/.next/dev/server/server-reference-manifest.json +1 -1
- package/web/.next/dev/trace +1 -1
- package/web/.next/fallback-build-manifest.json +2 -2
- package/web/.next/prerender-manifest.json +3 -3
- package/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
- package/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/server/app/_not-found.rsc +2 -2
- package/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/web/.next/server/app/index.html +1 -1
- package/web/.next/server/app/index.rsc +4 -4
- package/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
- package/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
- package/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/web/.next/server/chunks/[root-of-the-server]__4e36aa92._.js +1 -1
- package/web/.next/server/chunks/[root-of-the-server]__4e36aa92._.js.map +1 -1
- package/web/.next/server/chunks/ssr/{2374f__pnpm_aa2ce93b._.js → 2374f__pnpm_43c403e8._.js} +1 -1
- package/web/.next/server/chunks/ssr/{2374f__pnpm_2f0d7c45._.js → 2374f__pnpm_5f7087cf._.js} +1 -1
- package/web/.next/server/chunks/ssr/{2374f__pnpm_f4abb025._.js → 2374f__pnpm_7cd13576._.js} +1 -1
- package/web/.next/server/chunks/ssr/{2374f__pnpm_c7feb274._.js → 2374f__pnpm_9dd8b322._.js} +1 -1
- package/web/.next/server/chunks/ssr/{2374f__pnpm_eb87394d._.js → 2374f__pnpm_ca8b3ccf._.js} +2 -2
- package/web/.next/server/chunks/ssr/{2374f__pnpm_e04ff5e3._.js → 2374f__pnpm_ce00fd2a._.js} +1 -1
- package/web/.next/server/chunks/ssr/{2374f__pnpm_d7f11ceb._.js → 2374f__pnpm_e395e0b5._.js} +1 -1
- package/web/.next/server/chunks/ssr/{2374f__pnpm_c03e1359._.js → 2374f__pnpm_f1702ecf._.js} +1 -1
- package/web/.next/server/chunks/ssr/{[root-of-the-server]__2dfcd951._.js → [root-of-the-server]__805216cc._.js} +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__805216cc._.js.map +1 -0
- package/web/.next/server/chunks/ssr/web_1658d276._.js +1 -1
- package/web/.next/server/chunks/ssr/web_1658d276._.js.map +1 -1
- package/web/.next/server/chunks/ssr/web_38b8ac39._.js +1 -1
- package/web/.next/server/chunks/ssr/web_38b8ac39._.js.map +1 -1
- package/web/.next/server/chunks/ssr/web_aaaa7872._.js +1 -1
- package/web/.next/server/chunks/ssr/web_aaaa7872._.js.map +1 -1
- package/web/.next/server/pages/404.html +1 -1
- package/web/.next/server/pages/500.html +2 -2
- package/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/static/chunks/33150aab64afe27a.css +1 -0
- package/web/.next/static/chunks/{64d14a57d96d13ab.js → 62358f9ae3ca4fc3.js} +1 -1
- package/web/.next/static/chunks/{f7a8e0e717eeadf2.js → 7441934619c44e53.js} +1 -1
- package/web/.next/static/chunks/{dd0466c07795b288.js → 75df31f521796a31.js} +1 -1
- package/web/.next/static/chunks/{bf853305cd7a9765.js → fa06da28948df001.js} +3 -3
- package/web/.next/trace +1 -1
- package/web/.next/trace-build +1 -1
- package/web/.next/dev/cache/turbopack/731ace46/00000024.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000025.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000028.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000031.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000032.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000043.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000046.meta +0 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__2dfcd951._.js.map +0 -1
- package/web/.next/static/chunks/7b140db694582cde.css +0 -1
- /package/web/.next/dev/cache/turbopack/731ace46/{00000027.meta → 00000024.meta} +0 -0
- /package/web/.next/server/chunks/ssr/{2374f__pnpm_aa2ce93b._.js.map → 2374f__pnpm_43c403e8._.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{2374f__pnpm_2f0d7c45._.js.map → 2374f__pnpm_5f7087cf._.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{2374f__pnpm_f4abb025._.js.map → 2374f__pnpm_7cd13576._.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{2374f__pnpm_c7feb274._.js.map → 2374f__pnpm_9dd8b322._.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{2374f__pnpm_eb87394d._.js.map → 2374f__pnpm_ca8b3ccf._.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{2374f__pnpm_e04ff5e3._.js.map → 2374f__pnpm_ce00fd2a._.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{2374f__pnpm_d7f11ceb._.js.map → 2374f__pnpm_e395e0b5._.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{2374f__pnpm_c03e1359._.js.map → 2374f__pnpm_f1702ecf._.js.map} +0 -0
- /package/web/.next/static/{O9K30qQQXDEvnesMUBQxo → szyAyOMgs48yag2cwDbXh}/_buildManifest.js +0 -0
- /package/web/.next/static/{O9K30qQQXDEvnesMUBQxo → szyAyOMgs48yag2cwDbXh}/_clientMiddlewareManifest.json +0 -0
- /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
|
|
22
|
-
import { resolve as
|
|
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
|
|
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
|
|
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
|
|
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 = "
|
|
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
|
|
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(
|
|
1053
|
+
return join2(baseDir, LOG_BASE_DIR, sessionId, "terminals", terminalId);
|
|
1046
1054
|
}
|
|
1047
|
-
return join2(
|
|
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:
|
|
1261
|
-
const entries = await
|
|
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
|
-
|
|
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
|
|
1651
|
-
import { resolve as
|
|
1652
|
-
import { existsSync as
|
|
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 :
|
|
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 =
|
|
1887
|
-
if (!
|
|
2389
|
+
const dir = dirname5(absolutePath);
|
|
2390
|
+
if (!existsSync7(dir)) {
|
|
1888
2391
|
await mkdir3(dir, { recursive: true });
|
|
1889
2392
|
}
|
|
1890
|
-
const existed =
|
|
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 (!
|
|
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
|
|
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
|
|
2094
|
-
import { resolve as
|
|
2095
|
-
import { existsSync as
|
|
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,
|
|
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 (!
|
|
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 =
|
|
2134
|
-
const content = await
|
|
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
|
|
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
|
-
|
|
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
|
-
-
|
|
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] =
|
|
3492
|
+
wrappedTools[name] = tool7({
|
|
2770
3493
|
description: originalTool.description || "",
|
|
2771
|
-
inputSchema: originalTool.inputSchema ||
|
|
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((
|
|
2786
|
-
approvalResolvers.set(toolCallId, { resolve:
|
|
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 =
|
|
2882
|
-
name:
|
|
2883
|
-
workingDirectory:
|
|
2884
|
-
model:
|
|
2885
|
-
toolApprovals:
|
|
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 =
|
|
2888
|
-
limit:
|
|
2889
|
-
offset:
|
|
3610
|
+
var paginationQuerySchema = z9.object({
|
|
3611
|
+
limit: z9.string().optional(),
|
|
3612
|
+
offset: z9.string().optional()
|
|
2890
3613
|
});
|
|
2891
|
-
var messagesQuerySchema =
|
|
2892
|
-
limit:
|
|
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 =
|
|
3032
|
-
model:
|
|
3033
|
-
name:
|
|
3034
|
-
toolApprovals:
|
|
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
|
|
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 =
|
|
3313
|
-
prompt:
|
|
4035
|
+
var runPromptSchema = z10.object({
|
|
4036
|
+
prompt: z10.string().min(1)
|
|
3314
4037
|
});
|
|
3315
|
-
var quickStartSchema =
|
|
3316
|
-
prompt:
|
|
3317
|
-
name:
|
|
3318
|
-
workingDirectory:
|
|
3319
|
-
model:
|
|
3320
|
-
toolApprovals:
|
|
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 =
|
|
3323
|
-
reason:
|
|
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
|
|
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 =
|
|
3931
|
-
provider:
|
|
3932
|
-
apiKey:
|
|
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
|
|
4694
|
+
import { z as z12 } from "zod";
|
|
3972
4695
|
var terminals2 = new Hono4();
|
|
3973
|
-
var spawnSchema =
|
|
3974
|
-
command:
|
|
3975
|
-
cwd:
|
|
3976
|
-
name:
|
|
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 =
|
|
4058
|
-
tail:
|
|
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 =
|
|
4083
|
-
signal:
|
|
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 =
|
|
4098
|
-
input:
|
|
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 =
|
|
4419
|
-
const webDir =
|
|
4420
|
-
if (
|
|
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 =
|
|
4424
|
-
if (
|
|
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((
|
|
5170
|
+
return new Promise((resolve10) => {
|
|
4448
5171
|
const server = createNetServer();
|
|
4449
5172
|
server.once("error", (err) => {
|
|
4450
5173
|
if (err.code === "EADDRINUSE") {
|
|
4451
|
-
|
|
5174
|
+
resolve10(true);
|
|
4452
5175
|
} else {
|
|
4453
|
-
|
|
5176
|
+
resolve10(false);
|
|
4454
5177
|
}
|
|
4455
5178
|
});
|
|
4456
5179
|
server.once("listening", () => {
|
|
4457
5180
|
server.close();
|
|
4458
|
-
|
|
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
|
|
5206
|
+
return existsSync10(buildIdPath);
|
|
4484
5207
|
}
|
|
4485
5208
|
function runCommand(command, args, cwd, env) {
|
|
4486
|
-
return new Promise((
|
|
4487
|
-
const child =
|
|
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
|
-
|
|
5224
|
+
resolve10({ success: code === 0, output });
|
|
4502
5225
|
});
|
|
4503
5226
|
child.on("error", (err) => {
|
|
4504
|
-
|
|
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 =
|
|
4520
|
-
const useNpm = !usePnpm &&
|
|
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 =
|
|
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((
|
|
5283
|
+
const startedPromise = new Promise((resolve10) => {
|
|
4559
5284
|
const timeout = setTimeout(() => {
|
|
4560
5285
|
if (!started && !exited) {
|
|
4561
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
4674
|
-
|
|
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
|
|
5174
|
-
import { resolve as
|
|
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((
|
|
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
|
-
|
|
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:
|
|
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((
|
|
6121
|
+
const apiKey = await new Promise((resolve10) => {
|
|
5397
6122
|
keyRl.question(chalk.cyan("Enter your AI Gateway API key: "), (answer) => {
|
|
5398
|
-
|
|
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 =
|
|
6455
|
+
configPath = resolve9(process.cwd(), "sparkecoder.config.json");
|
|
5726
6456
|
configLocation = "local";
|
|
5727
6457
|
}
|
|
5728
|
-
if (
|
|
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;
|