sparkecoder 0.1.87 → 0.1.93
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/README.md +1 -1
- package/dist/agent/index.d.ts +3 -3
- package/dist/agent/index.js +158 -68
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +416 -108
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/{index-BvIissiB.d.ts → index-Bn0Zox8f.d.ts} +23 -23
- package/dist/index.d.ts +5 -5
- package/dist/index.js +272 -97
- package/dist/index.js.map +1 -1
- package/dist/{schema-CohdIL13.d.ts → schema-EmpbnQeQ.d.ts} +3 -3
- package/dist/{search-CCffrVJE.d.ts → search-BRnGaIl-.d.ts} +7 -7
- package/dist/server/index.js +272 -97
- package/dist/server/index.js.map +1 -1
- package/dist/tools/index.d.ts +30 -3
- package/dist/tools/index.js +38 -4
- package/dist/tools/index.js.map +1 -1
- package/package.json +5 -5
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/embed/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__36edac7c._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__be5e2967._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_page_tsx_5ac4794b._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_components_sessions-sidebar_tsx_92510070._.js +1 -1
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/{static/chunks/26eb5fab5216f3cc.js → standalone/web/.next/static/chunks/58fd0aaa2746b444.js} +1 -1
- package/web/.next/standalone/web/.next/static/chunks/9fce2ce79c4c834e.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/{8d3efc76109d2efc.js → a888d448ceab1abe.js} +1 -1
- package/web/.next/standalone/web/.next/static/static/chunks/{26eb5fab5216f3cc.js → 58fd0aaa2746b444.js} +1 -1
- package/web/.next/standalone/web/.next/static/static/chunks/9fce2ce79c4c834e.js +1 -0
- package/web/.next/{static/chunks/8d3efc76109d2efc.js → standalone/web/.next/static/static/chunks/a888d448ceab1abe.js} +1 -1
- package/web/.next/standalone/web/package-lock.json +27 -27
- package/web/.next/standalone/web/package.json +1 -1
- package/web/.next/standalone/web/src/app/(main)/page.tsx +2 -2
- package/web/.next/standalone/web/src/app/api/config/route.ts +3 -2
- package/web/.next/standalone/web/src/components/sessions-sidebar.tsx +1 -1
- package/web/.next/standalone/web/src/lib/config.ts +2 -1
- package/web/.next/{standalone/web/.next/static/chunks/26eb5fab5216f3cc.js → static/chunks/58fd0aaa2746b444.js} +1 -1
- package/web/.next/static/chunks/9fce2ce79c4c834e.js +1 -0
- package/web/.next/{standalone/web/.next/static/static/chunks/8d3efc76109d2efc.js → static/chunks/a888d448ceab1abe.js} +1 -1
- package/web/package.json +1 -1
- package/web/.next/standalone/web/.next/static/chunks/b31b0765abe0c427.js +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/b31b0765abe0c427.js +0 -1
- package/web/.next/static/chunks/b31b0765abe0c427.js +0 -1
- /package/web/.next/standalone/web/.next/static/{static/uUaN7Xe5kF_pP6zhfaeYi → -ax3tJNqBGukss29jpu41}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{static/uUaN7Xe5kF_pP6zhfaeYi → -ax3tJNqBGukss29jpu41}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{static/uUaN7Xe5kF_pP6zhfaeYi → -ax3tJNqBGukss29jpu41}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{uUaN7Xe5kF_pP6zhfaeYi → static/-ax3tJNqBGukss29jpu41}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{uUaN7Xe5kF_pP6zhfaeYi → static/-ax3tJNqBGukss29jpu41}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{uUaN7Xe5kF_pP6zhfaeYi → static/-ax3tJNqBGukss29jpu41}/_ssgManifest.js +0 -0
- /package/web/.next/static/{uUaN7Xe5kF_pP6zhfaeYi → -ax3tJNqBGukss29jpu41}/_buildManifest.js +0 -0
- /package/web/.next/static/{uUaN7Xe5kF_pP6zhfaeYi → -ax3tJNqBGukss29jpu41}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{uUaN7Xe5kF_pP6zhfaeYi → -ax3tJNqBGukss29jpu41}/_ssgManifest.js +0 -0
package/dist/cli.js
CHANGED
|
@@ -29,9 +29,9 @@ __export(remote_exports, {
|
|
|
29
29
|
remoteToolExecutionQueries: () => remoteToolExecutionQueries,
|
|
30
30
|
storageQueries: () => storageQueries
|
|
31
31
|
});
|
|
32
|
-
function initRemoteDatabase(serverUrl,
|
|
32
|
+
function initRemoteDatabase(serverUrl, key2) {
|
|
33
33
|
remoteServerUrl = serverUrl.replace(/\/$/, "");
|
|
34
|
-
authKey =
|
|
34
|
+
authKey = key2;
|
|
35
35
|
}
|
|
36
36
|
function closeRemoteDatabase() {
|
|
37
37
|
remoteServerUrl = null;
|
|
@@ -45,14 +45,14 @@ function parseDates(obj) {
|
|
|
45
45
|
if (Array.isArray(obj)) return obj.map(parseDates);
|
|
46
46
|
if (typeof obj !== "object" || obj instanceof Date) return obj;
|
|
47
47
|
const result = { ...obj };
|
|
48
|
-
for (const
|
|
49
|
-
if (MODEL_MESSAGE_FIELDS.includes(
|
|
48
|
+
for (const key2 of Object.keys(result)) {
|
|
49
|
+
if (MODEL_MESSAGE_FIELDS.includes(key2)) {
|
|
50
50
|
continue;
|
|
51
51
|
}
|
|
52
|
-
if (DATE_FIELDS.includes(
|
|
53
|
-
result[
|
|
54
|
-
} else if (typeof result[
|
|
55
|
-
result[
|
|
52
|
+
if (DATE_FIELDS.includes(key2) && typeof result[key2] === "string") {
|
|
53
|
+
result[key2] = new Date(result[key2]);
|
|
54
|
+
} else if (typeof result[key2] === "object") {
|
|
55
|
+
result[key2] = parseDates(result[key2]);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
return result;
|
|
@@ -598,7 +598,7 @@ var init_types = __esm({
|
|
|
598
598
|
}).optional();
|
|
599
599
|
SparkcoderConfigSchema = z.object({
|
|
600
600
|
// Default model to use (Vercel AI Gateway format)
|
|
601
|
-
defaultModel: z.string().default("anthropic/claude-opus-4
|
|
601
|
+
defaultModel: z.string().default("anthropic/claude-opus-4.7"),
|
|
602
602
|
// Working directory for file operations
|
|
603
603
|
workingDirectory: z.string().optional(),
|
|
604
604
|
// Tool approval settings
|
|
@@ -872,7 +872,7 @@ function requiresApproval(toolName, sessionConfig) {
|
|
|
872
872
|
}
|
|
873
873
|
function createDefaultConfig() {
|
|
874
874
|
return {
|
|
875
|
-
defaultModel: "anthropic/claude-opus-4
|
|
875
|
+
defaultModel: "anthropic/claude-opus-4.7",
|
|
876
876
|
// workingDirectory is intentionally not set - defaults to where CLI is run
|
|
877
877
|
toolApprovals: {
|
|
878
878
|
bash: true,
|
|
@@ -984,6 +984,14 @@ function loadApiKeysIntoEnv() {
|
|
|
984
984
|
}
|
|
985
985
|
}
|
|
986
986
|
}
|
|
987
|
+
function isRemoteInferenceConfigured() {
|
|
988
|
+
try {
|
|
989
|
+
const config = getConfig();
|
|
990
|
+
return config.resolvedRemoteServer.isConfigured;
|
|
991
|
+
} catch {
|
|
992
|
+
return false;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
987
995
|
function setApiKey(provider, apiKey) {
|
|
988
996
|
const normalizedProvider = provider.toLowerCase();
|
|
989
997
|
const envVar = PROVIDER_ENV_MAP[normalizedProvider];
|
|
@@ -1033,11 +1041,11 @@ function getApiKeyStatus() {
|
|
|
1033
1041
|
};
|
|
1034
1042
|
});
|
|
1035
1043
|
}
|
|
1036
|
-
function maskApiKey(
|
|
1037
|
-
if (
|
|
1038
|
-
return "****" +
|
|
1044
|
+
function maskApiKey(key2) {
|
|
1045
|
+
if (key2.length <= 12) {
|
|
1046
|
+
return "****" + key2.slice(-4);
|
|
1039
1047
|
}
|
|
1040
|
-
return
|
|
1048
|
+
return key2.slice(0, 4) + "..." + key2.slice(-4);
|
|
1041
1049
|
}
|
|
1042
1050
|
var CONFIG_FILE_NAMES, cachedConfig, AUTH_KEY_FILE, API_KEYS_FILE, PROVIDER_ENV_MAP, SUPPORTED_PROVIDERS;
|
|
1043
1051
|
var init_config = __esm({
|
|
@@ -1491,10 +1499,10 @@ function parseSkillFrontmatter(content) {
|
|
|
1491
1499
|
}
|
|
1492
1500
|
const colonIndex = line.indexOf(":");
|
|
1493
1501
|
if (colonIndex > 0) {
|
|
1494
|
-
const
|
|
1502
|
+
const key2 = line.slice(0, colonIndex).trim();
|
|
1495
1503
|
let value = line.slice(colonIndex + 1).trim();
|
|
1496
1504
|
if (value === "" || value === "[]") {
|
|
1497
|
-
currentArrayKey =
|
|
1505
|
+
currentArrayKey = key2;
|
|
1498
1506
|
currentArray = [];
|
|
1499
1507
|
continue;
|
|
1500
1508
|
}
|
|
@@ -1507,18 +1515,18 @@ function parseSkillFrontmatter(content) {
|
|
|
1507
1515
|
}
|
|
1508
1516
|
return trimmed;
|
|
1509
1517
|
}).filter((item) => item.length > 0);
|
|
1510
|
-
data[
|
|
1518
|
+
data[key2] = items;
|
|
1511
1519
|
continue;
|
|
1512
1520
|
}
|
|
1513
1521
|
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
1514
1522
|
value = value.slice(1, -1);
|
|
1515
1523
|
}
|
|
1516
1524
|
if (value === "true") {
|
|
1517
|
-
data[
|
|
1525
|
+
data[key2] = true;
|
|
1518
1526
|
} else if (value === "false") {
|
|
1519
|
-
data[
|
|
1527
|
+
data[key2] = false;
|
|
1520
1528
|
} else {
|
|
1521
|
-
data[
|
|
1529
|
+
data[key2] = value;
|
|
1522
1530
|
}
|
|
1523
1531
|
}
|
|
1524
1532
|
}
|
|
@@ -2256,9 +2264,9 @@ var init_chunker = __esm({
|
|
|
2256
2264
|
});
|
|
2257
2265
|
|
|
2258
2266
|
// src/semantic/client.ts
|
|
2259
|
-
function initVectorClient(serverUrl,
|
|
2267
|
+
function initVectorClient(serverUrl, key2) {
|
|
2260
2268
|
remoteServerUrl2 = serverUrl.replace(/\/$/, "");
|
|
2261
|
-
authKey2 =
|
|
2269
|
+
authKey2 = key2;
|
|
2262
2270
|
}
|
|
2263
2271
|
function isVectorClientConfigured() {
|
|
2264
2272
|
return !!remoteServerUrl2 && !!authKey2;
|
|
@@ -2948,7 +2956,7 @@ import { promisify as promisify5 } from "util";
|
|
|
2948
2956
|
import { mkdirSync as mkdirSync5, existsSync as existsSync15, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
|
|
2949
2957
|
import { join as join8 } from "path";
|
|
2950
2958
|
import { tmpdir } from "os";
|
|
2951
|
-
import { nanoid as
|
|
2959
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
2952
2960
|
function isMacOs() {
|
|
2953
2961
|
return process.platform === "darwin";
|
|
2954
2962
|
}
|
|
@@ -3069,9 +3077,9 @@ async function runScroll(dx, dy) {
|
|
|
3069
3077
|
{ timeout: 5e3 }
|
|
3070
3078
|
);
|
|
3071
3079
|
}
|
|
3072
|
-
function translateKeyForCliclick(
|
|
3073
|
-
if (!
|
|
3074
|
-
const parts =
|
|
3080
|
+
function translateKeyForCliclick(key2) {
|
|
3081
|
+
if (!key2) return [];
|
|
3082
|
+
const parts = key2.split("+").map((p) => p.trim()).filter(Boolean);
|
|
3075
3083
|
if (parts.length === 0) return [];
|
|
3076
3084
|
const modMap = {
|
|
3077
3085
|
ctrl: "ctrl",
|
|
@@ -3167,7 +3175,7 @@ function createComputerUseTool(options) {
|
|
|
3167
3175
|
try {
|
|
3168
3176
|
switch (input.action) {
|
|
3169
3177
|
case "screenshot": {
|
|
3170
|
-
const path = join8(tmpdir(), `cu-${
|
|
3178
|
+
const path = join8(tmpdir(), `cu-${nanoid4(8)}.png`);
|
|
3171
3179
|
await runScreencapture(path);
|
|
3172
3180
|
const resized = await resizeScreenshotToPoints(path, displayWidth, displayHeight);
|
|
3173
3181
|
try {
|
|
@@ -3298,7 +3306,7 @@ function createComputerUseTool(options) {
|
|
|
3298
3306
|
case "zoom": {
|
|
3299
3307
|
const region = input.region ?? [0, 0, displayWidth, displayHeight];
|
|
3300
3308
|
const [x1, y1, x2, y2] = region;
|
|
3301
|
-
const tmpPath = join8(tmpdir(), `cu-zoom-${
|
|
3309
|
+
const tmpPath = join8(tmpdir(), `cu-zoom-${nanoid4(8)}.png`);
|
|
3302
3310
|
await runScreencapture(tmpPath);
|
|
3303
3311
|
const sharpModule = await import("sharp");
|
|
3304
3312
|
const sharp2 = sharpModule.default || sharpModule;
|
|
@@ -3620,7 +3628,7 @@ import { promisify as promisify6 } from "util";
|
|
|
3620
3628
|
import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
|
|
3621
3629
|
import { join as join9 } from "path";
|
|
3622
3630
|
import { tmpdir as tmpdir2 } from "os";
|
|
3623
|
-
import { nanoid as
|
|
3631
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
3624
3632
|
async function checkFfmpeg() {
|
|
3625
3633
|
try {
|
|
3626
3634
|
await execAsync6("ffmpeg -version", { timeout: 5e3 });
|
|
@@ -3675,7 +3683,7 @@ var init_recorder = __esm({
|
|
|
3675
3683
|
*/
|
|
3676
3684
|
async encode() {
|
|
3677
3685
|
if (this.frames.length === 0) return null;
|
|
3678
|
-
const workDir = join9(tmpdir2(), `sparkecoder-recording-${
|
|
3686
|
+
const workDir = join9(tmpdir2(), `sparkecoder-recording-${nanoid5(8)}`);
|
|
3679
3687
|
await mkdir4(workDir, { recursive: true });
|
|
3680
3688
|
try {
|
|
3681
3689
|
for (let i = 0; i < this.frames.length; i++) {
|
|
@@ -4023,9 +4031,9 @@ function startPersonalAgent(cfg) {
|
|
|
4023
4031
|
console.log(`${cfg.dashboardUrl.replace(/\/$/, "")}/admin/devices`);
|
|
4024
4032
|
console.log("");
|
|
4025
4033
|
const warned = /* @__PURE__ */ new Set();
|
|
4026
|
-
function warnOnce(
|
|
4027
|
-
if (warned.has(
|
|
4028
|
-
warned.add(
|
|
4034
|
+
function warnOnce(key2, msg) {
|
|
4035
|
+
if (warned.has(key2)) return;
|
|
4036
|
+
warned.add(key2);
|
|
4029
4037
|
console.warn(msg);
|
|
4030
4038
|
}
|
|
4031
4039
|
async function tick() {
|
|
@@ -4109,6 +4117,9 @@ import chalk from "chalk";
|
|
|
4109
4117
|
import ora from "ora";
|
|
4110
4118
|
import "dotenv/config";
|
|
4111
4119
|
import { createInterface } from "readline";
|
|
4120
|
+
import { dirname as dirname8 } from "path";
|
|
4121
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
4122
|
+
import { hostname as hostname4 } from "os";
|
|
4112
4123
|
|
|
4113
4124
|
// src/server/index.ts
|
|
4114
4125
|
import "dotenv/config";
|
|
@@ -4130,7 +4141,7 @@ import { z as z16 } from "zod";
|
|
|
4130
4141
|
import { existsSync as existsSync16, mkdirSync as mkdirSync6, writeFileSync as writeFileSync3, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
4131
4142
|
import { readdir as readdir6 } from "fs/promises";
|
|
4132
4143
|
import { join as join10, basename as basename5, extname as extname8, relative as relative9 } from "path";
|
|
4133
|
-
import { nanoid as
|
|
4144
|
+
import { nanoid as nanoid7 } from "nanoid";
|
|
4134
4145
|
|
|
4135
4146
|
// src/agent/index.ts
|
|
4136
4147
|
import {
|
|
@@ -4305,6 +4316,23 @@ function isAnthropicModel(modelId) {
|
|
|
4305
4316
|
const normalized = modelId.trim().toLowerCase();
|
|
4306
4317
|
return normalized.startsWith(ANTHROPIC_PREFIX) || normalized.startsWith("claude-");
|
|
4307
4318
|
}
|
|
4319
|
+
function requiresAdaptiveThinking(modelId) {
|
|
4320
|
+
const m = modelId.toLowerCase().match(/claude-(?:opus|sonnet|haiku)-(\d+)[.-](\d{1,2})(?!\d)/);
|
|
4321
|
+
if (!m) return false;
|
|
4322
|
+
const major = Number(m[1]);
|
|
4323
|
+
const minor = Number(m[2]);
|
|
4324
|
+
if (Number.isNaN(major) || Number.isNaN(minor)) return false;
|
|
4325
|
+
if (major > 4) return true;
|
|
4326
|
+
if (major === 4 && minor >= 6) return true;
|
|
4327
|
+
return false;
|
|
4328
|
+
}
|
|
4329
|
+
function getAnthropicProviderOptions(modelId, opts = {}) {
|
|
4330
|
+
const { toolStreaming, budgetTokens = 1e4 } = opts;
|
|
4331
|
+
const thinking = requiresAdaptiveThinking(modelId) ? { type: "adaptive" } : { type: "enabled", budgetTokens };
|
|
4332
|
+
const out = { thinking };
|
|
4333
|
+
if (toolStreaming) out.toolStreaming = true;
|
|
4334
|
+
return out;
|
|
4335
|
+
}
|
|
4308
4336
|
function resolveModel(modelId) {
|
|
4309
4337
|
try {
|
|
4310
4338
|
const config = getConfig();
|
|
@@ -4328,7 +4356,7 @@ var SUBAGENT_MODELS = {
|
|
|
4328
4356
|
init_db();
|
|
4329
4357
|
init_config();
|
|
4330
4358
|
import { z as z15 } from "zod";
|
|
4331
|
-
import { nanoid as
|
|
4359
|
+
import { nanoid as nanoid6 } from "nanoid";
|
|
4332
4360
|
|
|
4333
4361
|
// src/tools/bash.ts
|
|
4334
4362
|
import { tool } from "ai";
|
|
@@ -4661,11 +4689,11 @@ async function sendInput(terminalId, input, options = {}) {
|
|
|
4661
4689
|
return false;
|
|
4662
4690
|
}
|
|
4663
4691
|
}
|
|
4664
|
-
async function sendKey(terminalId,
|
|
4692
|
+
async function sendKey(terminalId, key2) {
|
|
4665
4693
|
const session = getSessionName(terminalId);
|
|
4666
4694
|
try {
|
|
4667
4695
|
await execAsync(`tmux has-session -t ${session}`, { timeout: 1e3 });
|
|
4668
|
-
await execAsync(`tmux send-keys -t ${session} ${
|
|
4696
|
+
await execAsync(`tmux send-keys -t ${session} ${key2}`, { timeout: 1e3 });
|
|
4669
4697
|
return true;
|
|
4670
4698
|
} catch {
|
|
4671
4699
|
return false;
|
|
@@ -4804,7 +4832,7 @@ bash({ id: "abc123", input: "my text" }) // send text input
|
|
|
4804
4832
|
Terminal output is stored in the global SparkECoder data directory. Use the \`tail\` option to read recent output.`,
|
|
4805
4833
|
inputSchema: bashInputSchema,
|
|
4806
4834
|
execute: async (inputArgs) => {
|
|
4807
|
-
const { command, background, id, kill, tail, input: textInput, key } = inputArgs;
|
|
4835
|
+
const { command, background, id, kill, tail, input: textInput, key: key2 } = inputArgs;
|
|
4808
4836
|
if (id) {
|
|
4809
4837
|
if (kill) {
|
|
4810
4838
|
const success = await killTerminal(id);
|
|
@@ -4835,8 +4863,8 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
4835
4863
|
message: `Sent input "${textInput}" to terminal`
|
|
4836
4864
|
};
|
|
4837
4865
|
}
|
|
4838
|
-
if (
|
|
4839
|
-
const success = await sendKey(id,
|
|
4866
|
+
if (key2) {
|
|
4867
|
+
const success = await sendKey(id, key2);
|
|
4840
4868
|
if (!success) {
|
|
4841
4869
|
return {
|
|
4842
4870
|
success: false,
|
|
@@ -4852,7 +4880,7 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
4852
4880
|
id,
|
|
4853
4881
|
output: truncatedOutput2,
|
|
4854
4882
|
status: status2,
|
|
4855
|
-
message: `Sent key "${
|
|
4883
|
+
message: `Sent key "${key2}" to terminal`
|
|
4856
4884
|
};
|
|
4857
4885
|
}
|
|
4858
4886
|
const { output, status } = await getLogs(id, options.workingDirectory, { tail, sessionId: options.sessionId });
|
|
@@ -4994,13 +5022,13 @@ async function resizeImageIfNeeded(buffer, mediaType) {
|
|
|
4994
5022
|
const needsResize = longEdge > MAX_LONG_EDGE;
|
|
4995
5023
|
const needsShrink = buffer.length > MAX_FILE_BYTES;
|
|
4996
5024
|
if (!needsResize && !needsShrink) return { buffer, mediaType: inputMediaType };
|
|
4997
|
-
const
|
|
5025
|
+
const key2 = cacheKey(buffer);
|
|
4998
5026
|
const cacheDir = getCacheDir();
|
|
4999
5027
|
const isPng = inputMediaType.includes("png");
|
|
5000
5028
|
const willConvertToJpeg = isPng && (needsShrink || buffer.length > 2 * 1024 * 1024);
|
|
5001
5029
|
const outputMediaType = willConvertToJpeg || !isPng ? "image/jpeg" : "image/png";
|
|
5002
5030
|
const ext = outputMediaType === "image/png" ? ".png" : ".jpg";
|
|
5003
|
-
const cachePath = join3(cacheDir,
|
|
5031
|
+
const cachePath = join3(cacheDir, key2 + ext);
|
|
5004
5032
|
if (existsSync3(cachePath)) {
|
|
5005
5033
|
console.log(`[image-resize] Cache hit for ${width}x${height} image`);
|
|
5006
5034
|
return { buffer: readFileSync2(cachePath), mediaType: outputMediaType };
|
|
@@ -5891,31 +5919,31 @@ async function getClientForFile(filePath) {
|
|
|
5891
5919
|
return null;
|
|
5892
5920
|
}
|
|
5893
5921
|
const root = dirname4(normalized);
|
|
5894
|
-
const
|
|
5895
|
-
const existing = state.clients.get(
|
|
5922
|
+
const key2 = `${serverDef.id}:${root}`;
|
|
5923
|
+
const existing = state.clients.get(key2);
|
|
5896
5924
|
if (existing) {
|
|
5897
5925
|
return existing;
|
|
5898
5926
|
}
|
|
5899
|
-
if (state.broken.has(
|
|
5927
|
+
if (state.broken.has(key2)) {
|
|
5900
5928
|
return null;
|
|
5901
5929
|
}
|
|
5902
5930
|
try {
|
|
5903
5931
|
const handle = await serverDef.spawn(root);
|
|
5904
5932
|
if (!handle) {
|
|
5905
|
-
state.broken.add(
|
|
5933
|
+
state.broken.add(key2);
|
|
5906
5934
|
return null;
|
|
5907
5935
|
}
|
|
5908
5936
|
console.log(`[lsp] Started ${serverDef.name} for ${root}`);
|
|
5909
5937
|
const client = await createClient(serverDef.id, handle, root);
|
|
5910
|
-
state.clients.set(
|
|
5938
|
+
state.clients.set(key2, client);
|
|
5911
5939
|
handle.process.on("exit", (code) => {
|
|
5912
5940
|
console.log(`[lsp] ${serverDef.name} exited with code ${code}`);
|
|
5913
|
-
state.clients.delete(
|
|
5941
|
+
state.clients.delete(key2);
|
|
5914
5942
|
});
|
|
5915
5943
|
return client;
|
|
5916
5944
|
} catch (error) {
|
|
5917
5945
|
console.error(`[lsp] Failed to start ${serverDef.name}:`, error);
|
|
5918
|
-
state.broken.add(
|
|
5946
|
+
state.broken.add(key2);
|
|
5919
5947
|
return null;
|
|
5920
5948
|
}
|
|
5921
5949
|
}
|
|
@@ -7647,6 +7675,7 @@ init_semantic_search();
|
|
|
7647
7675
|
import { tool as tool11 } from "ai";
|
|
7648
7676
|
import { z as z12 } from "zod";
|
|
7649
7677
|
import Ajv from "ajv";
|
|
7678
|
+
import { nanoid as nanoid3 } from "nanoid";
|
|
7650
7679
|
var ajv = new Ajv({ allErrors: true });
|
|
7651
7680
|
function createCompleteTaskTool(options) {
|
|
7652
7681
|
const validate = ajv.compile(options.outputSchema);
|
|
@@ -7693,6 +7722,37 @@ function createTaskFailedTool(options) {
|
|
|
7693
7722
|
}
|
|
7694
7723
|
});
|
|
7695
7724
|
}
|
|
7725
|
+
function createAskQuestionToUserTool(options) {
|
|
7726
|
+
return tool11({
|
|
7727
|
+
description: "Ask the user a blocking clarification question when you cannot safely continue without more information. Use this only after trying to infer the answer from the available context. The task will pause until the orchestrator or user answers.",
|
|
7728
|
+
inputSchema: z12.object({
|
|
7729
|
+
question: z12.string().min(1).describe("The concise question you need answered."),
|
|
7730
|
+
context: z12.string().optional().describe("Brief context explaining why this answer is needed and what you already tried."),
|
|
7731
|
+
choices: z12.array(z12.string().min(1)).max(10).optional().describe("Optional suggested answer choices when the question is multiple choice.")
|
|
7732
|
+
}),
|
|
7733
|
+
execute: async (input) => {
|
|
7734
|
+
if (!options.onQuestion) {
|
|
7735
|
+
return {
|
|
7736
|
+
status: "unavailable",
|
|
7737
|
+
message: "Question routing is not configured for this task. Continue with the best safe assumption or call task_failed if blocked."
|
|
7738
|
+
};
|
|
7739
|
+
}
|
|
7740
|
+
const questionId = `q_${nanoid3(12)}`;
|
|
7741
|
+
const answer = await options.onQuestion({
|
|
7742
|
+
questionId,
|
|
7743
|
+
question: input.question,
|
|
7744
|
+
context: input.context,
|
|
7745
|
+
choices: input.choices
|
|
7746
|
+
});
|
|
7747
|
+
return {
|
|
7748
|
+
status: "answered",
|
|
7749
|
+
questionId,
|
|
7750
|
+
answer: answer.answer,
|
|
7751
|
+
answeredBy: answer.answeredBy ?? "unknown"
|
|
7752
|
+
};
|
|
7753
|
+
}
|
|
7754
|
+
});
|
|
7755
|
+
}
|
|
7696
7756
|
|
|
7697
7757
|
// src/tools/upload-file.ts
|
|
7698
7758
|
import { tool as tool12 } from "ai";
|
|
@@ -8027,6 +8087,7 @@ async function createTools(options) {
|
|
|
8027
8087
|
if (options.taskTools) {
|
|
8028
8088
|
tools.complete_task = createCompleteTaskTool(options.taskTools);
|
|
8029
8089
|
tools.task_failed = createTaskFailedTool(options.taskTools);
|
|
8090
|
+
tools.ask_question_to_user = createAskQuestionToUserTool(options.taskTools);
|
|
8030
8091
|
}
|
|
8031
8092
|
return tools;
|
|
8032
8093
|
}
|
|
@@ -8434,9 +8495,10 @@ If you need to give the user a downloadable file (report, image, export, etc.),
|
|
|
8434
8495
|
### Rules
|
|
8435
8496
|
1. Work independently \u2014 no human will approve tool calls. All tools run without approval.
|
|
8436
8497
|
2. Keep working until the task is fully complete \u2014 and then VERIFY it is complete before finishing.
|
|
8437
|
-
3.
|
|
8438
|
-
4.
|
|
8439
|
-
5.
|
|
8498
|
+
3. If you are blocked by missing information, call \`ask_question_to_user\` with a concise question. The run will pause until the orchestrator or user answers, then you should continue from that answer.
|
|
8499
|
+
4. When done, call the \`complete_task\` tool with a JSON result matching the output schema below.
|
|
8500
|
+
5. If you determine the task is impossible or encounter an unrecoverable error, call the \`task_failed\` tool with a clear reason.
|
|
8501
|
+
6. Do NOT stop without calling \`complete_task\`, \`task_failed\`, or \`ask_question_to_user\` when blocked.
|
|
8440
8502
|
|
|
8441
8503
|
### Verification \u2014 BE EXTREMELY THOROUGH
|
|
8442
8504
|
Before calling \`complete_task\`, you MUST verify your work completely. Do not just assume it worked. Actually check.
|
|
@@ -8507,6 +8569,7 @@ ${JSON.stringify(outputSchema, null, 2)}
|
|
|
8507
8569
|
### Completion Tools
|
|
8508
8570
|
- **\`complete_task({ result: ... })\`** \u2014 Call ONLY after thorough verification. The result is validated against the schema above. If validation fails you will get errors back \u2014 fix and retry.
|
|
8509
8571
|
- **\`task_failed({ reason: "..." })\`** \u2014 Call only if the task truly cannot be completed.
|
|
8572
|
+
- **\`ask_question_to_user({ question, context?, choices? })\`** \u2014 Call only when you need information that is not available in the repo, task prompt, files, logs, or tools. Ask one clear question; after the answer is returned, continue working.
|
|
8510
8573
|
`;
|
|
8511
8574
|
}
|
|
8512
8575
|
function createSummaryPrompt(conversationHistory) {
|
|
@@ -8662,6 +8725,7 @@ function sanitizeModelMessages(messages) {
|
|
|
8662
8725
|
|
|
8663
8726
|
// src/agent/model-limits.ts
|
|
8664
8727
|
var MODEL_LIMITS = {
|
|
8728
|
+
"anthropic/claude-opus-4.7": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
8665
8729
|
"anthropic/claude-opus-4-6": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
8666
8730
|
"anthropic/claude-sonnet-4": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
8667
8731
|
"anthropic/claude-3.5-sonnet": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
@@ -8998,17 +9062,65 @@ function repairToolPairing(messages) {
|
|
|
8998
9062
|
|
|
8999
9063
|
// src/agent/index.ts
|
|
9000
9064
|
init_webhook();
|
|
9065
|
+
|
|
9066
|
+
// src/tasks/questions.ts
|
|
9067
|
+
var pendingQuestions = /* @__PURE__ */ new Map();
|
|
9068
|
+
var answeredQuestions = /* @__PURE__ */ new Map();
|
|
9069
|
+
function key(taskId, questionId) {
|
|
9070
|
+
return `${taskId}:${questionId}`;
|
|
9071
|
+
}
|
|
9072
|
+
function waitForTaskQuestionAnswer(question) {
|
|
9073
|
+
const k = key(question.taskId, question.questionId);
|
|
9074
|
+
if (pendingQuestions.has(k)) {
|
|
9075
|
+
return Promise.reject(new Error(`Question already pending: ${question.questionId}`));
|
|
9076
|
+
}
|
|
9077
|
+
return new Promise((resolve12, reject) => {
|
|
9078
|
+
pendingQuestions.set(k, {
|
|
9079
|
+
...question,
|
|
9080
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
9081
|
+
resolve: resolve12,
|
|
9082
|
+
reject
|
|
9083
|
+
});
|
|
9084
|
+
});
|
|
9085
|
+
}
|
|
9086
|
+
function answerTaskQuestion(taskId, questionId, answer) {
|
|
9087
|
+
const k = key(taskId, questionId);
|
|
9088
|
+
const pending = pendingQuestions.get(k);
|
|
9089
|
+
if (!pending) return answeredQuestions.has(k) ? "already_answered" : "not_found";
|
|
9090
|
+
pendingQuestions.delete(k);
|
|
9091
|
+
answeredQuestions.set(k, answer);
|
|
9092
|
+
pending.resolve(answer);
|
|
9093
|
+
return "answered";
|
|
9094
|
+
}
|
|
9095
|
+
function rejectTaskQuestions(taskId, reason) {
|
|
9096
|
+
for (const [k, pending] of pendingQuestions) {
|
|
9097
|
+
if (pending.taskId !== taskId) continue;
|
|
9098
|
+
pendingQuestions.delete(k);
|
|
9099
|
+
pending.reject(new Error(reason));
|
|
9100
|
+
}
|
|
9101
|
+
}
|
|
9102
|
+
function getPendingTaskQuestion(taskId, questionId) {
|
|
9103
|
+
const pending = pendingQuestions.get(key(taskId, questionId));
|
|
9104
|
+
if (!pending) return null;
|
|
9105
|
+
const { resolve: _resolve, reject: _reject, ...question } = pending;
|
|
9106
|
+
return question;
|
|
9107
|
+
}
|
|
9108
|
+
function getAnsweredTaskQuestion(taskId, questionId) {
|
|
9109
|
+
return answeredQuestions.get(key(taskId, questionId)) ?? null;
|
|
9110
|
+
}
|
|
9111
|
+
|
|
9112
|
+
// src/agent/index.ts
|
|
9001
9113
|
var MAX_SSE_FIELD_LENGTH = 8 * 1024;
|
|
9002
9114
|
var SSE_PREVIEW_LENGTH = 2 * 1024;
|
|
9003
9115
|
function truncateWriteFileInput(input) {
|
|
9004
9116
|
const out = { ...input };
|
|
9005
|
-
for (const
|
|
9006
|
-
const val = out[
|
|
9117
|
+
for (const key2 of ["content", "old_string", "new_string"]) {
|
|
9118
|
+
const val = out[key2];
|
|
9007
9119
|
if (typeof val === "string" && val.length > MAX_SSE_FIELD_LENGTH) {
|
|
9008
|
-
out[
|
|
9120
|
+
out[key2] = `${val.slice(0, SSE_PREVIEW_LENGTH)}
|
|
9009
9121
|
... (truncated)`;
|
|
9010
|
-
out[`${
|
|
9011
|
-
out[`${
|
|
9122
|
+
out[`${key2}Truncated`] = true;
|
|
9123
|
+
out[`${key2}Length`] = val.length;
|
|
9012
9124
|
}
|
|
9013
9125
|
}
|
|
9014
9126
|
return out;
|
|
@@ -9178,13 +9290,7 @@ ${prompt}` });
|
|
|
9178
9290
|
abortSignal: options.abortSignal,
|
|
9179
9291
|
// Enable extended thinking/reasoning for models that support it
|
|
9180
9292
|
providerOptions: useAnthropic ? {
|
|
9181
|
-
anthropic: {
|
|
9182
|
-
toolStreaming: true,
|
|
9183
|
-
thinking: {
|
|
9184
|
-
type: "enabled",
|
|
9185
|
-
budgetTokens: 1e4
|
|
9186
|
-
}
|
|
9187
|
-
}
|
|
9293
|
+
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
9188
9294
|
} : void 0,
|
|
9189
9295
|
onStepFinish: async (step) => {
|
|
9190
9296
|
options.onStepFinish?.(step);
|
|
@@ -9231,12 +9337,7 @@ ${prompt}` });
|
|
|
9231
9337
|
stopWhen: stepCountIs2(500),
|
|
9232
9338
|
// Enable extended thinking/reasoning for models that support it
|
|
9233
9339
|
providerOptions: useAnthropic ? {
|
|
9234
|
-
anthropic:
|
|
9235
|
-
thinking: {
|
|
9236
|
-
type: "enabled",
|
|
9237
|
-
budgetTokens: 1e4
|
|
9238
|
-
}
|
|
9239
|
-
}
|
|
9340
|
+
anthropic: getAnthropicProviderOptions(this.session.model)
|
|
9240
9341
|
} : void 0
|
|
9241
9342
|
});
|
|
9242
9343
|
const responseMessages = result.response.messages;
|
|
@@ -9323,7 +9424,38 @@ ${prompt}` });
|
|
|
9323
9424
|
},
|
|
9324
9425
|
taskTools: {
|
|
9325
9426
|
outputSchema: options.taskConfig.outputSchema,
|
|
9326
|
-
onComplete
|
|
9427
|
+
onComplete,
|
|
9428
|
+
onQuestion: async (question) => {
|
|
9429
|
+
const payload = {
|
|
9430
|
+
questionId: question.questionId,
|
|
9431
|
+
question: question.question,
|
|
9432
|
+
context: question.context,
|
|
9433
|
+
choices: question.choices,
|
|
9434
|
+
status: "pending"
|
|
9435
|
+
};
|
|
9436
|
+
const answerPromise = waitForTaskQuestionAnswer({
|
|
9437
|
+
taskId: this.session.id,
|
|
9438
|
+
questionId: question.questionId,
|
|
9439
|
+
question: question.question,
|
|
9440
|
+
context: question.context,
|
|
9441
|
+
choices: question.choices
|
|
9442
|
+
});
|
|
9443
|
+
fireWebhook("task.question", payload);
|
|
9444
|
+
if (emit) {
|
|
9445
|
+
await emit(JSON.stringify({ type: "task-question", data: payload }));
|
|
9446
|
+
}
|
|
9447
|
+
const answer = await answerPromise;
|
|
9448
|
+
const answeredPayload = {
|
|
9449
|
+
questionId: question.questionId,
|
|
9450
|
+
answer: answer.answer,
|
|
9451
|
+
answeredBy: answer.answeredBy
|
|
9452
|
+
};
|
|
9453
|
+
fireWebhook("task.question_answered", answeredPayload);
|
|
9454
|
+
if (emit) {
|
|
9455
|
+
await emit(JSON.stringify({ type: "task-question-answered", data: answeredPayload }));
|
|
9456
|
+
}
|
|
9457
|
+
return answer;
|
|
9458
|
+
}
|
|
9327
9459
|
}
|
|
9328
9460
|
});
|
|
9329
9461
|
const baseSystemPrompt = await buildSystemPrompt({
|
|
@@ -9368,10 +9500,7 @@ ${taskAddendum}`;
|
|
|
9368
9500
|
stopWhen: stepCountIs2(500),
|
|
9369
9501
|
abortSignal: options.abortSignal,
|
|
9370
9502
|
providerOptions: useAnthropic ? {
|
|
9371
|
-
anthropic: {
|
|
9372
|
-
toolStreaming: true,
|
|
9373
|
-
thinking: { type: "enabled", budgetTokens: 1e4 }
|
|
9374
|
-
}
|
|
9503
|
+
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
9375
9504
|
} : void 0,
|
|
9376
9505
|
onStepFinish: async (step) => {
|
|
9377
9506
|
options.onStepFinish?.(step);
|
|
@@ -9658,7 +9787,7 @@ ${taskAddendum}`;
|
|
|
9658
9787
|
description: originalTool.description || "",
|
|
9659
9788
|
inputSchema: originalTool.inputSchema || z15.object({}),
|
|
9660
9789
|
execute: async (input, toolOptions) => {
|
|
9661
|
-
const toolCallId = toolOptions.toolCallId ||
|
|
9790
|
+
const toolCallId = toolOptions.toolCallId || nanoid6();
|
|
9662
9791
|
const execution = toolExecutionQueries.create({
|
|
9663
9792
|
sessionId: this.session.id,
|
|
9664
9793
|
toolName: name,
|
|
@@ -10308,7 +10437,7 @@ sessions.post("/:id/attachments", async (c) => {
|
|
|
10308
10437
|
return c.json({ error: "No file provided" }, 400);
|
|
10309
10438
|
}
|
|
10310
10439
|
const dir = ensureAttachmentsDir(sessionId);
|
|
10311
|
-
const id =
|
|
10440
|
+
const id = nanoid7(10);
|
|
10312
10441
|
const ext = extname8(file.name) || "";
|
|
10313
10442
|
const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
10314
10443
|
const filePath = join10(dir, safeFilename);
|
|
@@ -10334,7 +10463,7 @@ sessions.post("/:id/attachments", async (c) => {
|
|
|
10334
10463
|
return c.json({ error: "Missing filename or data" }, 400);
|
|
10335
10464
|
}
|
|
10336
10465
|
const dir = ensureAttachmentsDir(sessionId);
|
|
10337
|
-
const id =
|
|
10466
|
+
const id = nanoid7(10);
|
|
10338
10467
|
const ext = extname8(body.filename) || "";
|
|
10339
10468
|
const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
10340
10469
|
const filePath = join10(dir, safeFilename);
|
|
@@ -10626,9 +10755,9 @@ var store = /* @__PURE__ */ new Map();
|
|
|
10626
10755
|
var channels = /* @__PURE__ */ new Map();
|
|
10627
10756
|
var cleanupInterval = setInterval(() => {
|
|
10628
10757
|
const now = Date.now();
|
|
10629
|
-
for (const [
|
|
10758
|
+
for (const [key2, data] of store.entries()) {
|
|
10630
10759
|
if (data.expiresAt && data.expiresAt < now) {
|
|
10631
|
-
store.delete(
|
|
10760
|
+
store.delete(key2);
|
|
10632
10761
|
}
|
|
10633
10762
|
}
|
|
10634
10763
|
}, 6e4);
|
|
@@ -10652,27 +10781,27 @@ var publisher = {
|
|
|
10652
10781
|
}
|
|
10653
10782
|
}
|
|
10654
10783
|
},
|
|
10655
|
-
set: async (
|
|
10784
|
+
set: async (key2, value, options) => {
|
|
10656
10785
|
const expiresAt = options?.EX ? Date.now() + options.EX * 1e3 : void 0;
|
|
10657
|
-
store.set(
|
|
10786
|
+
store.set(key2, { value, expiresAt });
|
|
10658
10787
|
if (options?.EX) {
|
|
10659
|
-
setTimeout(() => store.delete(
|
|
10788
|
+
setTimeout(() => store.delete(key2), options.EX * 1e3);
|
|
10660
10789
|
}
|
|
10661
10790
|
},
|
|
10662
|
-
get: async (
|
|
10663
|
-
const data = store.get(
|
|
10791
|
+
get: async (key2) => {
|
|
10792
|
+
const data = store.get(key2);
|
|
10664
10793
|
if (!data) return null;
|
|
10665
10794
|
if (data.expiresAt && data.expiresAt < Date.now()) {
|
|
10666
|
-
store.delete(
|
|
10795
|
+
store.delete(key2);
|
|
10667
10796
|
return null;
|
|
10668
10797
|
}
|
|
10669
10798
|
return data.value;
|
|
10670
10799
|
},
|
|
10671
|
-
incr: async (
|
|
10672
|
-
const data = store.get(
|
|
10800
|
+
incr: async (key2) => {
|
|
10801
|
+
const data = store.get(key2);
|
|
10673
10802
|
const current = data ? parseInt(data.value, 10) : 0;
|
|
10674
10803
|
const next = (isNaN(current) ? 0 : current) + 1;
|
|
10675
|
-
store.set(
|
|
10804
|
+
store.set(key2, { value: String(next), expiresAt: data?.expiresAt });
|
|
10676
10805
|
return next;
|
|
10677
10806
|
}
|
|
10678
10807
|
};
|
|
@@ -10704,7 +10833,7 @@ var streamContext = createResumableStreamContext({
|
|
|
10704
10833
|
});
|
|
10705
10834
|
|
|
10706
10835
|
// src/server/routes/agents.ts
|
|
10707
|
-
import { nanoid as
|
|
10836
|
+
import { nanoid as nanoid8 } from "nanoid";
|
|
10708
10837
|
init_stream_proxy();
|
|
10709
10838
|
init_recorder();
|
|
10710
10839
|
init_remote();
|
|
@@ -11263,7 +11392,7 @@ ${prompt}` });
|
|
|
11263
11392
|
userMessageContent = prompt;
|
|
11264
11393
|
}
|
|
11265
11394
|
await messageQueries.create(id, { role: "user", content: userMessageContent });
|
|
11266
|
-
const streamId = `stream_${id}_${
|
|
11395
|
+
const streamId = `stream_${id}_${nanoid8(10)}`;
|
|
11267
11396
|
console.log(`[STREAM] Creating stream ${streamId} for session ${id}`);
|
|
11268
11397
|
await activeStreamQueries.create(id, streamId);
|
|
11269
11398
|
const stream = await streamContext.resumableStream(
|
|
@@ -11468,7 +11597,7 @@ agents.post(
|
|
|
11468
11597
|
});
|
|
11469
11598
|
const session = agent.getSession();
|
|
11470
11599
|
const enrichedPrompt = enrichPromptWithDevtoolsContext(session.id, body.prompt);
|
|
11471
|
-
const streamId = `stream_${session.id}_${
|
|
11600
|
+
const streamId = `stream_${session.id}_${nanoid8(10)}`;
|
|
11472
11601
|
await createCheckpoint(session.id, session.workingDirectory, 0);
|
|
11473
11602
|
await activeStreamQueries.create(session.id, streamId);
|
|
11474
11603
|
const createQuickStreamProducer = () => {
|
|
@@ -11821,7 +11950,8 @@ health.get("/", async (c) => {
|
|
|
11821
11950
|
const config = getConfig();
|
|
11822
11951
|
const apiKeyStatus = getApiKeyStatus();
|
|
11823
11952
|
const gatewayKey = apiKeyStatus.find((s) => s.provider === "ai-gateway");
|
|
11824
|
-
const
|
|
11953
|
+
const remoteInference = isRemoteInferenceConfigured();
|
|
11954
|
+
const hasApiKey = remoteInference || (gatewayKey?.configured ?? false);
|
|
11825
11955
|
let hwid;
|
|
11826
11956
|
try {
|
|
11827
11957
|
hwid = getHardwareIdCached();
|
|
@@ -11832,6 +11962,7 @@ health.get("/", async (c) => {
|
|
|
11832
11962
|
version: currentVersion,
|
|
11833
11963
|
uptime: process.uptime(),
|
|
11834
11964
|
apiKeyConfigured: hasApiKey,
|
|
11965
|
+
inferenceMode: remoteInference ? "remote" : "local",
|
|
11835
11966
|
hwid,
|
|
11836
11967
|
config: {
|
|
11837
11968
|
workingDirectory: config.resolvedWorkingDirectory,
|
|
@@ -12258,7 +12389,7 @@ init_db();
|
|
|
12258
12389
|
import { Hono as Hono5 } from "hono";
|
|
12259
12390
|
import { zValidator as zValidator5 } from "@hono/zod-validator";
|
|
12260
12391
|
import { z as z20 } from "zod";
|
|
12261
|
-
import { nanoid as
|
|
12392
|
+
import { nanoid as nanoid9 } from "nanoid";
|
|
12262
12393
|
init_config();
|
|
12263
12394
|
var tasks = new Hono5();
|
|
12264
12395
|
var taskAbortControllers = /* @__PURE__ */ new Map();
|
|
@@ -12332,7 +12463,7 @@ tasks.post(
|
|
|
12332
12463
|
const taskId = agent.sessionId;
|
|
12333
12464
|
const abortController = new AbortController();
|
|
12334
12465
|
taskAbortControllers.set(taskId, abortController);
|
|
12335
|
-
const streamId = `stream_${taskId}_${
|
|
12466
|
+
const streamId = `stream_${taskId}_${nanoid9(10)}`;
|
|
12336
12467
|
await activeStreamQueries.create(taskId, streamId);
|
|
12337
12468
|
const taskStreamProducer = () => {
|
|
12338
12469
|
const { readable, writable } = new TransformStream();
|
|
@@ -12459,6 +12590,7 @@ tasks.post("/:id/cancel", async (c) => {
|
|
|
12459
12590
|
abortController.abort();
|
|
12460
12591
|
taskAbortControllers.delete(id);
|
|
12461
12592
|
}
|
|
12593
|
+
rejectTaskQuestions(id, "Task cancelled by user");
|
|
12462
12594
|
const cancelledTask = {
|
|
12463
12595
|
...task,
|
|
12464
12596
|
status: "failed",
|
|
@@ -12480,6 +12612,52 @@ tasks.post("/:id/cancel", async (c) => {
|
|
|
12480
12612
|
}
|
|
12481
12613
|
return c.json({ taskId: id, status: "failed", error: "Task cancelled by user" });
|
|
12482
12614
|
});
|
|
12615
|
+
var answerQuestionSchema = z20.object({
|
|
12616
|
+
answer: z20.string().min(1),
|
|
12617
|
+
answeredBy: z20.enum(["orchestrator", "user", "system"]).optional()
|
|
12618
|
+
});
|
|
12619
|
+
tasks.post(
|
|
12620
|
+
"/:id/questions/:questionId/answer",
|
|
12621
|
+
zValidator5("json", answerQuestionSchema),
|
|
12622
|
+
async (c) => {
|
|
12623
|
+
const id = c.req.param("id");
|
|
12624
|
+
const questionId = c.req.param("questionId");
|
|
12625
|
+
const body = c.req.valid("json");
|
|
12626
|
+
const session = await sessionQueries.getById(id);
|
|
12627
|
+
if (!session) {
|
|
12628
|
+
return c.json({ error: "Task not found" }, 404);
|
|
12629
|
+
}
|
|
12630
|
+
const task = session.config?.task;
|
|
12631
|
+
if (!task?.enabled) {
|
|
12632
|
+
return c.json({ error: "Session is not a task" }, 400);
|
|
12633
|
+
}
|
|
12634
|
+
const pending = getPendingTaskQuestion(id, questionId);
|
|
12635
|
+
if (!pending) {
|
|
12636
|
+
if (getAnsweredTaskQuestion(id, questionId)) {
|
|
12637
|
+
return c.json({
|
|
12638
|
+
taskId: id,
|
|
12639
|
+
questionId,
|
|
12640
|
+
status: "answered",
|
|
12641
|
+
alreadyAnswered: true
|
|
12642
|
+
});
|
|
12643
|
+
}
|
|
12644
|
+
return c.json({ error: "Question is not pending" }, 404);
|
|
12645
|
+
}
|
|
12646
|
+
const answerStatus = answerTaskQuestion(id, questionId, {
|
|
12647
|
+
answer: body.answer,
|
|
12648
|
+
answeredBy: body.answeredBy
|
|
12649
|
+
});
|
|
12650
|
+
if (answerStatus === "not_found") {
|
|
12651
|
+
return c.json({ error: "Question is not pending" }, 404);
|
|
12652
|
+
}
|
|
12653
|
+
return c.json({
|
|
12654
|
+
taskId: id,
|
|
12655
|
+
questionId,
|
|
12656
|
+
status: "answered",
|
|
12657
|
+
alreadyAnswered: answerStatus === "already_answered"
|
|
12658
|
+
});
|
|
12659
|
+
}
|
|
12660
|
+
);
|
|
12483
12661
|
var tasks_default = tasks;
|
|
12484
12662
|
|
|
12485
12663
|
// src/server/routes/system.ts
|
|
@@ -12575,15 +12753,15 @@ function loadPublicKey(input) {
|
|
|
12575
12753
|
if (!input.includes("BEGIN") && existsSync18(input)) {
|
|
12576
12754
|
pem = readFileSync11(input, "utf8");
|
|
12577
12755
|
}
|
|
12578
|
-
const
|
|
12579
|
-
if (
|
|
12756
|
+
const key2 = createPublicKey({ key: pem, format: "pem" });
|
|
12757
|
+
if (key2.asymmetricKeyType !== "ed25519") {
|
|
12580
12758
|
throw new Error(
|
|
12581
|
-
`expected an ed25519 public key, got ${
|
|
12759
|
+
`expected an ed25519 public key, got ${key2.asymmetricKeyType}. Generate with personal-agents/scripts/generate-signing-keys.mjs.`
|
|
12582
12760
|
);
|
|
12583
12761
|
}
|
|
12584
|
-
_cachedKey =
|
|
12762
|
+
_cachedKey = key2;
|
|
12585
12763
|
_cachedFromInput = input;
|
|
12586
|
-
return
|
|
12764
|
+
return key2;
|
|
12587
12765
|
}
|
|
12588
12766
|
function bodyHashB64(body) {
|
|
12589
12767
|
const hash = createHash3("sha256");
|
|
@@ -14097,8 +14275,30 @@ function generateOpenAPISpec() {
|
|
|
14097
14275
|
init_config();
|
|
14098
14276
|
init_semantic();
|
|
14099
14277
|
init_db();
|
|
14100
|
-
import { writeFileSync as writeFileSync6, readFileSync as readFileSync12, existsSync as existsSync20 } from "fs";
|
|
14278
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync6, readFileSync as readFileSync12, existsSync as existsSync20, chmodSync } from "fs";
|
|
14101
14279
|
import { resolve as resolve11, join as join14 } from "path";
|
|
14280
|
+
function getCliVersion() {
|
|
14281
|
+
const here = dirname8(fileURLToPath5(import.meta.url));
|
|
14282
|
+
const candidates = [
|
|
14283
|
+
join14(here, "..", "package.json"),
|
|
14284
|
+
join14(here, "..", "..", "package.json"),
|
|
14285
|
+
join14(process.cwd(), "package.json")
|
|
14286
|
+
];
|
|
14287
|
+
for (const p of candidates) {
|
|
14288
|
+
try {
|
|
14289
|
+
const pkg = JSON.parse(readFileSync12(p, "utf8"));
|
|
14290
|
+
if (pkg.name === "sparkecoder" && pkg.version) return pkg.version;
|
|
14291
|
+
} catch {
|
|
14292
|
+
}
|
|
14293
|
+
}
|
|
14294
|
+
return "0.0.0";
|
|
14295
|
+
}
|
|
14296
|
+
function shellExport(name, value) {
|
|
14297
|
+
return `export ${name}='${value.replace(/'/g, `'\\''`)}'`;
|
|
14298
|
+
}
|
|
14299
|
+
function xmlEscape(value) {
|
|
14300
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
14301
|
+
}
|
|
14102
14302
|
async function apiRequest(baseUrl, path, options = {}) {
|
|
14103
14303
|
const url = `${baseUrl}${path}`;
|
|
14104
14304
|
const init = {
|
|
@@ -14661,7 +14861,7 @@ Unexpected error: ${outerError.message}`));
|
|
|
14661
14861
|
}
|
|
14662
14862
|
}
|
|
14663
14863
|
var program = new Command();
|
|
14664
|
-
program.name("sparkecoder").description("AI coding agent - just type sparkecoder to start chatting").version(
|
|
14864
|
+
program.name("sparkecoder").description("AI coding agent - just type sparkecoder to start chatting").version(getCliVersion()).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("--public-url <url>", "Public URL for web UI to connect to API (for Docker/remote access)").option("-v, --verbose", "Enable verbose logging for web server").option("--dangerously-skip-approvals", "Auto-approve all tool calls (no confirmation prompts)").option("--enable-computer-use", "Enable the Anthropic computer use tool for all new sessions by default (macOS only)").action(async (options) => {
|
|
14665
14865
|
if (options.enableComputerUse) {
|
|
14666
14866
|
process.env.SPARKECODER_COMPUTER_USE = "1";
|
|
14667
14867
|
console.log(chalk.cyan("\u2192 Computer use enabled by default for new sessions."));
|
|
@@ -14842,6 +15042,114 @@ program.command("init").description("Create a sparkecoder.config.json file").opt
|
|
|
14842
15042
|
console.log(chalk.dim(` ${configPath}`));
|
|
14843
15043
|
console.log(chalk.dim("Set AI_GATEWAY_API_KEY and run sparkecoder to start"));
|
|
14844
15044
|
});
|
|
15045
|
+
program.command("personal-agent-setup").description("Create a personal-agent setup folder with env, key, start script, and launchd plist template").option("-d, --dir <path>", "Setup directory", "~/sparkecoder-personal-agent").option("--dashboard <url>", "Personal Agents dashboard URL").option("--device-name <name>", "Friendly device name").option("--device-api-url <url>", "Public API URL for this device (port 3141 tunnel)").option("--device-web-url <url>", "Public web UI URL for this device (port 6969 tunnel)").option("--remote-url <url>", "SparkECoder remote server URL (optional; defaults to production config)").option("--auth-key <key>", "SparkECoder remote server auth key (optional; auto-registers if omitted)").option("--ingest-token <token>", "Dashboard PERSONAL_AGENT_INGEST_TOKEN").option("--public-key <pem>", "Dashboard signing public key PEM string").option("--accept-signed-only", "Include --accept-signed-only in generated commands", true).option("--enable-computer-use", "Include --enable-computer-use in generated commands", true).action(async (options) => {
|
|
15046
|
+
const expandHome = (p) => p.startsWith("~/") ? join14(process.env.HOME || process.cwd(), p.slice(2)) : p;
|
|
15047
|
+
const setupDir = resolve11(expandHome(options.dir));
|
|
15048
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
15049
|
+
const ask = (label, fallback = "") => new Promise((resolve12) => {
|
|
15050
|
+
const suffix = fallback ? ` (${fallback})` : "";
|
|
15051
|
+
rl.question(`${label}${suffix}: `, (answer) => resolve12(answer.trim() || fallback));
|
|
15052
|
+
});
|
|
15053
|
+
try {
|
|
15054
|
+
const dashboard = options.dashboard || await ask("Dashboard URL", "https://personal-agents.studyfetchcms.com");
|
|
15055
|
+
const name = options.deviceName || await ask("Device name", hostname4().replace(/\.local$/, ""));
|
|
15056
|
+
const publicUrl = options.deviceApiUrl || await ask("Public API URL (3141 tunnel)");
|
|
15057
|
+
const webUrl = options.deviceWebUrl || await ask("Public web URL (6969 tunnel)", publicUrl.replace(/3141/g, "6969"));
|
|
15058
|
+
const remoteUrl = options.remoteUrl || await ask("SparkECoder remote server URL (blank = default)", "");
|
|
15059
|
+
const authKey3 = options.authKey || "";
|
|
15060
|
+
const ingestToken = options.ingestToken || await ask("PERSONAL_AGENT_INGEST_TOKEN");
|
|
15061
|
+
const publicKey = options.publicKey || await ask("Signing public key PEM (paste single-line with \\n escapes, or leave blank and edit file)");
|
|
15062
|
+
rl.close();
|
|
15063
|
+
mkdirSync9(setupDir, { recursive: true });
|
|
15064
|
+
const normalizedPublicKey = publicKey.replace(/\\n/g, "\n").trim();
|
|
15065
|
+
const keyPath = join14(setupDir, "personal-agent-public-key.pem");
|
|
15066
|
+
const envPath = join14(setupDir, "personal-agent.env");
|
|
15067
|
+
const startPath = join14(setupDir, "start-personal-agent.sh");
|
|
15068
|
+
const plistPath = join14(setupDir, "com.studyfetch.personal-agent.plist");
|
|
15069
|
+
const logsDir = join14(process.env.HOME || setupDir, "Library", "Logs", "PersonalAgents");
|
|
15070
|
+
const sparkecoderBin = process.env.SPARKECODER_BIN || process.argv[1] || "sparkecoder";
|
|
15071
|
+
if (normalizedPublicKey) {
|
|
15072
|
+
writeFileSync6(keyPath, normalizedPublicKey + "\n", { mode: 384 });
|
|
15073
|
+
} else if (!existsSync20(keyPath)) {
|
|
15074
|
+
writeFileSync6(keyPath, "PASTE_PUBLIC_KEY_HERE\n", { mode: 384 });
|
|
15075
|
+
}
|
|
15076
|
+
writeFileSync6(envPath, [
|
|
15077
|
+
shellExport("PERSONAL_AGENT_DASHBOARD", dashboard),
|
|
15078
|
+
shellExport("PERSONAL_AGENT_NAME", name),
|
|
15079
|
+
shellExport("PERSONAL_AGENT_PUBLIC_URL", publicUrl),
|
|
15080
|
+
shellExport("PERSONAL_AGENT_WEB_URL", webUrl),
|
|
15081
|
+
shellExport("PERSONAL_AGENT_INGEST_TOKEN", ingestToken),
|
|
15082
|
+
shellExport("PERSONAL_AGENT_PUBLIC_KEY", keyPath),
|
|
15083
|
+
...remoteUrl ? [shellExport("SPARKECODER_REMOTE_URL", remoteUrl)] : [],
|
|
15084
|
+
...authKey3 ? [shellExport("SPARKECODER_AUTH_KEY", authKey3)] : [],
|
|
15085
|
+
""
|
|
15086
|
+
].join("\n"), { mode: 384 });
|
|
15087
|
+
const flags = [
|
|
15088
|
+
"server",
|
|
15089
|
+
"--personal-agent-mode",
|
|
15090
|
+
'--personal-agent-dashboard "$PERSONAL_AGENT_DASHBOARD"',
|
|
15091
|
+
'--personal-agent-name "$PERSONAL_AGENT_NAME"',
|
|
15092
|
+
'--personal-agent-public-url "$PERSONAL_AGENT_PUBLIC_URL"',
|
|
15093
|
+
'--personal-agent-web-url "$PERSONAL_AGENT_WEB_URL"',
|
|
15094
|
+
'--personal-agent-ingest-token "$PERSONAL_AGENT_INGEST_TOKEN"',
|
|
15095
|
+
'--personal-agent-public-key "$PERSONAL_AGENT_PUBLIC_KEY"',
|
|
15096
|
+
...options.acceptSignedOnly ? ["--accept-signed-only"] : [],
|
|
15097
|
+
...options.enableComputerUse ? ["--enable-computer-use"] : []
|
|
15098
|
+
];
|
|
15099
|
+
writeFileSync6(startPath, [
|
|
15100
|
+
"#!/usr/bin/env bash",
|
|
15101
|
+
"set -euo pipefail",
|
|
15102
|
+
`source "${envPath}"`,
|
|
15103
|
+
`exec '${sparkecoderBin.replace(/'/g, `'\\''`)}' ${flags.join(" \\\n ")}`,
|
|
15104
|
+
""
|
|
15105
|
+
].join("\n"), { mode: 493 });
|
|
15106
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
15107
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
15108
|
+
<plist version="1.0">
|
|
15109
|
+
<dict>
|
|
15110
|
+
<key>Label</key><string>com.studyfetch.personal-agent</string>
|
|
15111
|
+
<key>ProgramArguments</key>
|
|
15112
|
+
<array>
|
|
15113
|
+
<string>${xmlEscape(startPath)}</string>
|
|
15114
|
+
</array>
|
|
15115
|
+
<key>EnvironmentVariables</key>
|
|
15116
|
+
<dict>
|
|
15117
|
+
<key>PATH</key><string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
|
|
15118
|
+
<key>HOME</key><string>${xmlEscape(process.env.HOME || "")}</string>
|
|
15119
|
+
</dict>
|
|
15120
|
+
<key>RunAtLoad</key><true/>
|
|
15121
|
+
<key>KeepAlive</key><true/>
|
|
15122
|
+
<key>ThrottleInterval</key><integer>10</integer>
|
|
15123
|
+
<key>StandardOutPath</key><string>${xmlEscape(join14(logsDir, "sparkecoder.log"))}</string>
|
|
15124
|
+
<key>StandardErrorPath</key><string>${xmlEscape(join14(logsDir, "sparkecoder.err.log"))}</string>
|
|
15125
|
+
</dict>
|
|
15126
|
+
</plist>
|
|
15127
|
+
`;
|
|
15128
|
+
writeFileSync6(plistPath, plist, { mode: 384 });
|
|
15129
|
+
mkdirSync9(logsDir, { recursive: true });
|
|
15130
|
+
try {
|
|
15131
|
+
chmodSync(envPath, 384);
|
|
15132
|
+
chmodSync(keyPath, 384);
|
|
15133
|
+
chmodSync(plistPath, 384);
|
|
15134
|
+
chmodSync(startPath, 493);
|
|
15135
|
+
} catch {
|
|
15136
|
+
}
|
|
15137
|
+
console.log(chalk.green("\n\u2713 Personal Agent setup folder created"));
|
|
15138
|
+
console.log(chalk.dim(` ${setupDir}`));
|
|
15139
|
+
console.log("");
|
|
15140
|
+
console.log(chalk.bold("Smoke test:"));
|
|
15141
|
+
console.log(chalk.cyan(` "${startPath}"`));
|
|
15142
|
+
console.log("");
|
|
15143
|
+
console.log(chalk.bold("Install launchd unit:"));
|
|
15144
|
+
console.log(chalk.cyan(` cp "${plistPath}" "$HOME/Library/LaunchAgents/com.studyfetch.personal-agent.plist"`));
|
|
15145
|
+
console.log(chalk.cyan(` launchctl bootstrap "gui/$UID" "$HOME/Library/LaunchAgents/com.studyfetch.personal-agent.plist"`));
|
|
15146
|
+
console.log(chalk.cyan(` launchctl kickstart -k "gui/$UID/com.studyfetch.personal-agent"`));
|
|
15147
|
+
} catch (error) {
|
|
15148
|
+
rl.close();
|
|
15149
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
15150
|
+
process.exit(1);
|
|
15151
|
+
}
|
|
15152
|
+
});
|
|
14845
15153
|
program.command("sessions").description("List all sessions").option("-l, --limit <limit>", "Number of sessions to show", "20").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").action(async (options) => {
|
|
14846
15154
|
const baseUrl = `http://${options.host}:${options.port}`;
|
|
14847
15155
|
try {
|
|
@@ -15089,10 +15397,10 @@ program.command("search").description("Search indexed repository using semantic
|
|
|
15089
15397
|
process.exit(1);
|
|
15090
15398
|
}
|
|
15091
15399
|
});
|
|
15092
|
-
program.command("api-key").description("Manage API keys for AI providers").argument("[provider]", "Provider name (anthropic, openai, google, xai, ai-gateway)").argument("[key]", "API key to set (if not provided, shows status)").option("-l, --list", "List all API key statuses").action((provider,
|
|
15400
|
+
program.command("api-key").description("Manage API keys for AI providers").argument("[provider]", "Provider name (anthropic, openai, google, xai, ai-gateway)").argument("[key]", "API key to set (if not provided, shows status)").option("-l, --list", "List all API key statuses").action((provider, key2, options) => {
|
|
15093
15401
|
try {
|
|
15094
15402
|
ensureAppDataDirectory();
|
|
15095
|
-
if (options.list || !provider && !
|
|
15403
|
+
if (options.list || !provider && !key2) {
|
|
15096
15404
|
console.log(chalk.bold("\nAPI Key Status:\n"));
|
|
15097
15405
|
const status = getApiKeyStatus();
|
|
15098
15406
|
for (const s of status) {
|
|
@@ -15112,8 +15420,8 @@ program.command("api-key").description("Manage API keys for AI providers").argum
|
|
|
15112
15420
|
console.log(chalk.dim(`Usage: sparkecoder api-key <provider> <key>`));
|
|
15113
15421
|
return;
|
|
15114
15422
|
}
|
|
15115
|
-
if (provider &&
|
|
15116
|
-
setApiKey(provider,
|
|
15423
|
+
if (provider && key2) {
|
|
15424
|
+
setApiKey(provider, key2);
|
|
15117
15425
|
const status = getApiKeyStatus();
|
|
15118
15426
|
const providerStatus = status.find((s) => s.provider === provider.toLowerCase());
|
|
15119
15427
|
console.log(chalk.green(`
|
|
@@ -15125,7 +15433,7 @@ program.command("api-key").description("Manage API keys for AI providers").argum
|
|
|
15125
15433
|
The key is stored securely and will be loaded automatically on startup.`));
|
|
15126
15434
|
return;
|
|
15127
15435
|
}
|
|
15128
|
-
if (provider && !
|
|
15436
|
+
if (provider && !key2) {
|
|
15129
15437
|
const status = getApiKeyStatus();
|
|
15130
15438
|
const providerStatus = status.find((s) => s.provider === provider.toLowerCase());
|
|
15131
15439
|
if (!providerStatus) {
|