sparkecoder 0.1.117 → 0.1.119
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.d.ts +2 -2
- package/dist/agent/index.js +117 -698
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +639 -1042
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/{index-Bi8Ek02A.d.ts → index-Bcz0aCAR.d.ts} +1 -10
- package/dist/index.d.ts +4 -4
- package/dist/index.js +406 -944
- package/dist/index.js.map +1 -1
- package/dist/{schema-ecQSnCMz.d.ts → schema-BWbWmfDQ.d.ts} +0 -2
- package/dist/server/index.js +406 -944
- package/dist/server/index.js.map +1 -1
- package/dist/skills/default/desktop-automation.md +290 -0
- package/dist/skills/default/recording.md +3 -3
- package/dist/tools/index.d.ts +1 -167
- package/dist/tools/index.js +5 -590
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
- package/src/skills/default/desktop-automation.md +290 -0
- package/src/skills/default/recording.md +3 -3
- 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/_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.html +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- 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/agents.html +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_tree.segment.rsc +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 +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +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 +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- 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 +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 +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- 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 +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.html +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/_tree.segment.rsc +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/dist/skills/default/computer-use.md +0 -225
- package/src/skills/default/computer-use.md +0 -225
- /package/web/.next/standalone/web/.next/static/{static/vLqK4jK7EKdLCpQ-D6-qL → Bt00m8W4k5F79ALhN700F}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{static/vLqK4jK7EKdLCpQ-D6-qL → Bt00m8W4k5F79ALhN700F}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{static/vLqK4jK7EKdLCpQ-D6-qL → Bt00m8W4k5F79ALhN700F}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → static/Bt00m8W4k5F79ALhN700F}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → static/Bt00m8W4k5F79ALhN700F}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → static/Bt00m8W4k5F79ALhN700F}/_ssgManifest.js +0 -0
- /package/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → Bt00m8W4k5F79ALhN700F}/_buildManifest.js +0 -0
- /package/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → Bt00m8W4k5F79ALhN700F}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → Bt00m8W4k5F79ALhN700F}/_ssgManifest.js +0 -0
package/dist/cli.js
CHANGED
|
@@ -725,12 +725,6 @@ var init_types = __esm({
|
|
|
725
725
|
skillsDirectory: z.string().optional(),
|
|
726
726
|
maxContextChars: z.number().optional().default(2e5),
|
|
727
727
|
task: TaskConfigSchema.optional(),
|
|
728
|
-
// Anthropic computer use tool — opt-in. When true, the `computer` tool is
|
|
729
|
-
// included in the toolset for Anthropic models. Default false.
|
|
730
|
-
computerUseEnabled: z.boolean().optional(),
|
|
731
|
-
// Display dimensions for the computer use tool (defaults: 1280x800).
|
|
732
|
-
computerUseDisplayWidth: z.number().int().positive().optional(),
|
|
733
|
-
computerUseDisplayHeight: z.number().int().positive().optional(),
|
|
734
728
|
// 'orchestrator' = supervisor session; 'worker' = task spawned by an orchestrator.
|
|
735
729
|
role: z.enum(["orchestrator", "worker", "chat"]).optional(),
|
|
736
730
|
// Optional persona / extra system-prompt text appended to the orchestrator's
|
|
@@ -1166,7 +1160,7 @@ function loadConfig(configPath, workingDirectory) {
|
|
|
1166
1160
|
...config,
|
|
1167
1161
|
server: {
|
|
1168
1162
|
port: config.server.port,
|
|
1169
|
-
host: config.server.host ?? "
|
|
1163
|
+
host: config.server.host ?? "0.0.0.0",
|
|
1170
1164
|
publicUrl: config.server.publicUrl
|
|
1171
1165
|
},
|
|
1172
1166
|
resolvedWorkingDirectory,
|
|
@@ -1333,7 +1327,7 @@ function createDefaultConfig() {
|
|
|
1333
1327
|
},
|
|
1334
1328
|
server: {
|
|
1335
1329
|
port: 3141,
|
|
1336
|
-
host: "
|
|
1330
|
+
host: "0.0.0.0"
|
|
1337
1331
|
},
|
|
1338
1332
|
databasePath: "./sparkecoder.db"
|
|
1339
1333
|
};
|
|
@@ -5386,7 +5380,7 @@ function isPathExcluded(relativePath, exclude) {
|
|
|
5386
5380
|
}
|
|
5387
5381
|
async function walkDirectory(dir, include, exclude, baseDir) {
|
|
5388
5382
|
const { readdirSync: readdirSync4 } = await import("fs");
|
|
5389
|
-
const { join:
|
|
5383
|
+
const { join: join17, relative: relative10 } = await import("path");
|
|
5390
5384
|
const files = [];
|
|
5391
5385
|
function walk(currentDir) {
|
|
5392
5386
|
let entries;
|
|
@@ -5396,7 +5390,7 @@ async function walkDirectory(dir, include, exclude, baseDir) {
|
|
|
5396
5390
|
return;
|
|
5397
5391
|
}
|
|
5398
5392
|
for (const entry2 of entries) {
|
|
5399
|
-
const fullPath =
|
|
5393
|
+
const fullPath = join17(currentDir, entry2.name);
|
|
5400
5394
|
const relativePath = relative10(baseDir, fullPath);
|
|
5401
5395
|
if (isPathExcluded(relativePath, exclude)) {
|
|
5402
5396
|
continue;
|
|
@@ -7107,593 +7101,6 @@ var init_upload_file = __esm({
|
|
|
7107
7101
|
}
|
|
7108
7102
|
});
|
|
7109
7103
|
|
|
7110
|
-
// src/tools/computer-use.ts
|
|
7111
|
-
var computer_use_exports = {};
|
|
7112
|
-
__export(computer_use_exports, {
|
|
7113
|
-
createComputerUseTool: () => createComputerUseTool,
|
|
7114
|
-
detectScreenSize: () => detectScreenSize,
|
|
7115
|
-
hasAccessibilityPermissions: () => hasAccessibilityPermissions,
|
|
7116
|
-
hasScreenRecordingPermissions: () => hasScreenRecordingPermissions,
|
|
7117
|
-
isCliclickInstalled: () => isCliclickInstalled,
|
|
7118
|
-
isMacOs: () => isMacOs,
|
|
7119
|
-
openSystemSettings: () => openSystemSettings,
|
|
7120
|
-
requestAccessibilityPrompt: () => requestAccessibilityPrompt,
|
|
7121
|
-
requestScreenRecordingPrompt: () => requestScreenRecordingPrompt
|
|
7122
|
-
});
|
|
7123
|
-
import { anthropic } from "@ai-sdk/anthropic";
|
|
7124
|
-
import { exec as exec5 } from "child_process";
|
|
7125
|
-
import { promisify as promisify5 } from "util";
|
|
7126
|
-
import { mkdirSync as mkdirSync5, existsSync as existsSync15, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
|
|
7127
|
-
import { join as join8 } from "path";
|
|
7128
|
-
import { tmpdir } from "os";
|
|
7129
|
-
import { nanoid as nanoid4 } from "nanoid";
|
|
7130
|
-
function isMacOs() {
|
|
7131
|
-
return process.platform === "darwin";
|
|
7132
|
-
}
|
|
7133
|
-
async function isCliclickInstalled() {
|
|
7134
|
-
try {
|
|
7135
|
-
await execAsync5("command -v cliclick", { timeout: 2e3 });
|
|
7136
|
-
return true;
|
|
7137
|
-
} catch {
|
|
7138
|
-
return false;
|
|
7139
|
-
}
|
|
7140
|
-
}
|
|
7141
|
-
async function runJxa(script) {
|
|
7142
|
-
try {
|
|
7143
|
-
const escaped = script.replace(/'/g, `'\\''`);
|
|
7144
|
-
const { stdout } = await execAsync5(`osascript -l JavaScript -e '${escaped}'`, {
|
|
7145
|
-
timeout: 5e3
|
|
7146
|
-
});
|
|
7147
|
-
return JSON.parse(stdout.trim());
|
|
7148
|
-
} catch {
|
|
7149
|
-
return null;
|
|
7150
|
-
}
|
|
7151
|
-
}
|
|
7152
|
-
async function hasAccessibilityPermissions() {
|
|
7153
|
-
try {
|
|
7154
|
-
const { stderr } = await execAsync5("cliclick p:.", { timeout: 3e3 });
|
|
7155
|
-
if (/accessibility privileges not enabled/i.test(stderr)) {
|
|
7156
|
-
return { ok: false, error: stderr.trim().split("\n")[0] };
|
|
7157
|
-
}
|
|
7158
|
-
return { ok: true };
|
|
7159
|
-
} catch (err) {
|
|
7160
|
-
return { ok: false, error: err?.message || String(err) };
|
|
7161
|
-
}
|
|
7162
|
-
}
|
|
7163
|
-
async function hasScreenRecordingPermissions() {
|
|
7164
|
-
const result = await runJxa(
|
|
7165
|
-
`ObjC.import("Cocoa");
|
|
7166
|
-
ObjC.import("CoreGraphics");
|
|
7167
|
-
ObjC.bindFunction("CGPreflightScreenCaptureAccess", ["bool", []]);
|
|
7168
|
-
JSON.stringify({ hasAccess: !!$.CGPreflightScreenCaptureAccess() });`
|
|
7169
|
-
);
|
|
7170
|
-
return result?.hasAccess ?? false;
|
|
7171
|
-
}
|
|
7172
|
-
async function requestAccessibilityPrompt() {
|
|
7173
|
-
const result = await runJxa(
|
|
7174
|
-
`ObjC.import("ApplicationServices");
|
|
7175
|
-
var key = $.kAXTrustedCheckOptionPrompt;
|
|
7176
|
-
var dict = $.NSDictionary.dictionaryWithObjectForKey($.kCFBooleanTrue, key);
|
|
7177
|
-
var trusted = $.AXIsProcessTrustedWithOptions(dict);
|
|
7178
|
-
JSON.stringify({ trusted: !!trusted });`
|
|
7179
|
-
);
|
|
7180
|
-
return result?.trusted ?? false;
|
|
7181
|
-
}
|
|
7182
|
-
async function requestScreenRecordingPrompt() {
|
|
7183
|
-
const result = await runJxa(
|
|
7184
|
-
`ObjC.import("Cocoa");
|
|
7185
|
-
ObjC.import("CoreGraphics");
|
|
7186
|
-
ObjC.bindFunction("CGRequestScreenCaptureAccess", ["bool", []]);
|
|
7187
|
-
JSON.stringify({ granted: !!$.CGRequestScreenCaptureAccess() });`
|
|
7188
|
-
);
|
|
7189
|
-
return result?.granted ?? false;
|
|
7190
|
-
}
|
|
7191
|
-
async function openSystemSettings(pane) {
|
|
7192
|
-
const url = pane === "accessibility" ? "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility" : "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
|
|
7193
|
-
try {
|
|
7194
|
-
await execAsync5(`open '${url}'`, { timeout: 3e3 });
|
|
7195
|
-
} catch {
|
|
7196
|
-
}
|
|
7197
|
-
}
|
|
7198
|
-
async function detectScreenSize() {
|
|
7199
|
-
try {
|
|
7200
|
-
const { stdout } = await execAsync5(
|
|
7201
|
-
`osascript -e 'tell application "Finder" to get bounds of window of desktop'`,
|
|
7202
|
-
{ timeout: 3e3 }
|
|
7203
|
-
);
|
|
7204
|
-
const parts = stdout.trim().split(",").map((s) => parseInt(s.trim(), 10));
|
|
7205
|
-
if (parts.length >= 4 && parts.every((n) => Number.isFinite(n))) {
|
|
7206
|
-
const [x1, y1, x2, y2] = parts;
|
|
7207
|
-
return { width: x2 - x1, height: y2 - y1 };
|
|
7208
|
-
}
|
|
7209
|
-
} catch {
|
|
7210
|
-
}
|
|
7211
|
-
return null;
|
|
7212
|
-
}
|
|
7213
|
-
async function runCliclick(args) {
|
|
7214
|
-
const quoted = args.map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(" ");
|
|
7215
|
-
const { stdout, stderr } = await execAsync5(`cliclick ${quoted}`, {
|
|
7216
|
-
timeout: 15e3,
|
|
7217
|
-
maxBuffer: 1024 * 1024
|
|
7218
|
-
});
|
|
7219
|
-
if (/accessibility privileges not enabled/i.test(stderr)) {
|
|
7220
|
-
throw new Error(
|
|
7221
|
-
"Accessibility permissions not granted to cliclick. Open System Settings \u2192 Privacy & Security \u2192 Accessibility, add cliclick (or the agent runtime), and toggle it on."
|
|
7222
|
-
);
|
|
7223
|
-
}
|
|
7224
|
-
if (stderr && !stdout) throw new Error(stderr.trim());
|
|
7225
|
-
return (stdout || "").trim();
|
|
7226
|
-
}
|
|
7227
|
-
async function runScreencapture(path) {
|
|
7228
|
-
await execAsync5(`screencapture -x -t png '${path.replace(/'/g, `'\\''`)}'`, {
|
|
7229
|
-
timeout: 5e3
|
|
7230
|
-
});
|
|
7231
|
-
}
|
|
7232
|
-
async function resizeScreenshotToPoints(path, targetWidth, targetHeight) {
|
|
7233
|
-
const sharpModule = await import("sharp");
|
|
7234
|
-
const sharp2 = sharpModule.default || sharpModule;
|
|
7235
|
-
const meta = await sharp2(path).metadata();
|
|
7236
|
-
if (meta.width === targetWidth && meta.height === targetHeight) {
|
|
7237
|
-
return readFileSync7(path);
|
|
7238
|
-
}
|
|
7239
|
-
return await sharp2(path).resize(targetWidth, targetHeight, { fit: "fill" }).png().toBuffer();
|
|
7240
|
-
}
|
|
7241
|
-
async function runScroll(dx, dy) {
|
|
7242
|
-
const wheelY = -Math.round(dy);
|
|
7243
|
-
const wheelX = -Math.round(dx);
|
|
7244
|
-
const script = `ObjC.import('CoreGraphics');var ev = $.CGEventCreateScrollWheelEvent(null, 0, 2, ${wheelY}, ${wheelX});$.CGEventPost(0, ev);`;
|
|
7245
|
-
await execAsync5(
|
|
7246
|
-
`osascript -l JavaScript -e '${script.replace(/'/g, `'\\''`)}'`,
|
|
7247
|
-
{ timeout: 5e3 }
|
|
7248
|
-
);
|
|
7249
|
-
}
|
|
7250
|
-
function translateKeyForCliclick(key2) {
|
|
7251
|
-
if (!key2) return [];
|
|
7252
|
-
const parts = key2.split("+").map((p) => p.trim()).filter(Boolean);
|
|
7253
|
-
if (parts.length === 0) return [];
|
|
7254
|
-
const modMap = {
|
|
7255
|
-
ctrl: "ctrl",
|
|
7256
|
-
control: "ctrl",
|
|
7257
|
-
alt: "alt",
|
|
7258
|
-
option: "alt",
|
|
7259
|
-
shift: "shift",
|
|
7260
|
-
cmd: "cmd",
|
|
7261
|
-
super: "cmd",
|
|
7262
|
-
meta: "cmd",
|
|
7263
|
-
win: "cmd",
|
|
7264
|
-
fn: "fn"
|
|
7265
|
-
};
|
|
7266
|
-
const keyMap = {
|
|
7267
|
-
return: "enter",
|
|
7268
|
-
enter: "enter",
|
|
7269
|
-
esc: "esc",
|
|
7270
|
-
escape: "esc",
|
|
7271
|
-
backspace: "delete",
|
|
7272
|
-
back_space: "delete",
|
|
7273
|
-
delete: "fwd-delete",
|
|
7274
|
-
fwd_delete: "fwd-delete",
|
|
7275
|
-
forward_delete: "fwd-delete",
|
|
7276
|
-
tab: "tab",
|
|
7277
|
-
space: "space",
|
|
7278
|
-
up: "arrow-up",
|
|
7279
|
-
arrow_up: "arrow-up",
|
|
7280
|
-
down: "arrow-down",
|
|
7281
|
-
arrow_down: "arrow-down",
|
|
7282
|
-
left: "arrow-left",
|
|
7283
|
-
arrow_left: "arrow-left",
|
|
7284
|
-
right: "arrow-right",
|
|
7285
|
-
arrow_right: "arrow-right",
|
|
7286
|
-
page_up: "page-up",
|
|
7287
|
-
pageup: "page-up",
|
|
7288
|
-
page_down: "page-down",
|
|
7289
|
-
pagedown: "page-down",
|
|
7290
|
-
home: "home",
|
|
7291
|
-
end: "end",
|
|
7292
|
-
f1: "f1",
|
|
7293
|
-
f2: "f2",
|
|
7294
|
-
f3: "f3",
|
|
7295
|
-
f4: "f4",
|
|
7296
|
-
f5: "f5",
|
|
7297
|
-
f6: "f6",
|
|
7298
|
-
f7: "f7",
|
|
7299
|
-
f8: "f8",
|
|
7300
|
-
f9: "f9",
|
|
7301
|
-
f10: "f10",
|
|
7302
|
-
f11: "f11",
|
|
7303
|
-
f12: "f12"
|
|
7304
|
-
};
|
|
7305
|
-
const modifiers = [];
|
|
7306
|
-
let mainKey = null;
|
|
7307
|
-
for (let i = 0; i < parts.length; i++) {
|
|
7308
|
-
const lower = parts[i].toLowerCase().replace(/-/g, "_");
|
|
7309
|
-
if (i < parts.length - 1 && modMap[lower]) {
|
|
7310
|
-
modifiers.push(modMap[lower]);
|
|
7311
|
-
} else {
|
|
7312
|
-
mainKey = keyMap[lower] || lower;
|
|
7313
|
-
}
|
|
7314
|
-
}
|
|
7315
|
-
const args = [];
|
|
7316
|
-
if (modifiers.length > 0) args.push(`kd:${modifiers.join(",")}`);
|
|
7317
|
-
if (mainKey) {
|
|
7318
|
-
const isNamedKey = Object.values(keyMap).includes(mainKey) || /^f([1-9]|1[0-9]|20)$/.test(mainKey) || /^num-/.test(mainKey);
|
|
7319
|
-
if (isNamedKey) {
|
|
7320
|
-
args.push(`kp:${mainKey}`);
|
|
7321
|
-
} else {
|
|
7322
|
-
args.push(`t:${mainKey}`);
|
|
7323
|
-
}
|
|
7324
|
-
}
|
|
7325
|
-
if (modifiers.length > 0) args.push(`ku:${modifiers.join(",")}`);
|
|
7326
|
-
return args;
|
|
7327
|
-
}
|
|
7328
|
-
function modifierStringToCliclick(text) {
|
|
7329
|
-
return text.split("+").map((p) => p.trim().toLowerCase()).map((p) => {
|
|
7330
|
-
if (p === "ctrl" || p === "control") return "ctrl";
|
|
7331
|
-
if (p === "alt" || p === "option") return "alt";
|
|
7332
|
-
if (p === "shift") return "shift";
|
|
7333
|
-
if (p === "super" || p === "meta" || p === "cmd") return "cmd";
|
|
7334
|
-
return "";
|
|
7335
|
-
}).filter(Boolean);
|
|
7336
|
-
}
|
|
7337
|
-
function createComputerUseTool(options) {
|
|
7338
|
-
const displayWidth = options.displayWidth ?? DEFAULT_WIDTH;
|
|
7339
|
-
const displayHeight = options.displayHeight ?? DEFAULT_HEIGHT;
|
|
7340
|
-
return anthropic.tools.computer_20251124({
|
|
7341
|
-
displayWidthPx: displayWidth,
|
|
7342
|
-
displayHeightPx: displayHeight,
|
|
7343
|
-
enableZoom: true,
|
|
7344
|
-
execute: async (input) => {
|
|
7345
|
-
try {
|
|
7346
|
-
switch (input.action) {
|
|
7347
|
-
case "screenshot": {
|
|
7348
|
-
const path = join8(tmpdir(), `cu-${nanoid4(8)}.png`);
|
|
7349
|
-
await runScreencapture(path);
|
|
7350
|
-
const resized = await resizeScreenshotToPoints(path, displayWidth, displayHeight);
|
|
7351
|
-
try {
|
|
7352
|
-
unlinkSync2(path);
|
|
7353
|
-
} catch {
|
|
7354
|
-
}
|
|
7355
|
-
return { type: "image", data: resized.toString("base64") };
|
|
7356
|
-
}
|
|
7357
|
-
case "left_click": {
|
|
7358
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
7359
|
-
if (input.text) {
|
|
7360
|
-
const mods = modifierStringToCliclick(input.text);
|
|
7361
|
-
if (mods.length > 0) {
|
|
7362
|
-
await runCliclick([`kd:${mods.join(",")}`, `c:${x},${y}`, `ku:${mods.join(",")}`]);
|
|
7363
|
-
} else {
|
|
7364
|
-
await runCliclick([`c:${x},${y}`]);
|
|
7365
|
-
}
|
|
7366
|
-
} else {
|
|
7367
|
-
await runCliclick([`c:${x},${y}`]);
|
|
7368
|
-
}
|
|
7369
|
-
return `clicked at (${x}, ${y})${input.text ? ` with ${input.text}` : ""}`;
|
|
7370
|
-
}
|
|
7371
|
-
case "right_click": {
|
|
7372
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
7373
|
-
await runCliclick([`rc:${x},${y}`]);
|
|
7374
|
-
return `right-clicked at (${x}, ${y})`;
|
|
7375
|
-
}
|
|
7376
|
-
case "middle_click": {
|
|
7377
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
7378
|
-
const script = `ObjC.import('CoreGraphics');var loc = $.CGPointMake(${x}, ${y});var down = $.CGEventCreateMouseEvent(null, 25, loc, 2);var up = $.CGEventCreateMouseEvent(null, 26, loc, 2);$.CGEventPost(0, down); $.CGEventPost(0, up);`;
|
|
7379
|
-
await execAsync5(
|
|
7380
|
-
`osascript -l JavaScript -e '${script.replace(/'/g, `'\\''`)}'`,
|
|
7381
|
-
{ timeout: 3e3 }
|
|
7382
|
-
);
|
|
7383
|
-
return `middle-clicked at (${x}, ${y})`;
|
|
7384
|
-
}
|
|
7385
|
-
case "double_click": {
|
|
7386
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
7387
|
-
await runCliclick([`dc:${x},${y}`]);
|
|
7388
|
-
return `double-clicked at (${x}, ${y})`;
|
|
7389
|
-
}
|
|
7390
|
-
case "triple_click": {
|
|
7391
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
7392
|
-
await runCliclick([`tc:${x},${y}`]);
|
|
7393
|
-
return `triple-clicked at (${x}, ${y})`;
|
|
7394
|
-
}
|
|
7395
|
-
case "mouse_move": {
|
|
7396
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
7397
|
-
await runCliclick([`m:${x},${y}`]);
|
|
7398
|
-
return `moved cursor to (${x}, ${y})`;
|
|
7399
|
-
}
|
|
7400
|
-
case "left_mouse_down": {
|
|
7401
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
7402
|
-
await runCliclick([`dd:${x},${y}`]);
|
|
7403
|
-
return `left mouse button pressed at (${x}, ${y})`;
|
|
7404
|
-
}
|
|
7405
|
-
case "left_mouse_up": {
|
|
7406
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
7407
|
-
await runCliclick([`du:${x},${y}`]);
|
|
7408
|
-
return `left mouse button released at (${x}, ${y})`;
|
|
7409
|
-
}
|
|
7410
|
-
case "left_click_drag": {
|
|
7411
|
-
const [sx, sy] = input.start_coordinate ?? [0, 0];
|
|
7412
|
-
const [ex, ey] = input.coordinate ?? [0, 0];
|
|
7413
|
-
await runCliclick([`dd:${sx},${sy}`, `m:${ex},${ey}`, `du:${ex},${ey}`]);
|
|
7414
|
-
return `dragged from (${sx}, ${sy}) to (${ex}, ${ey})`;
|
|
7415
|
-
}
|
|
7416
|
-
case "type": {
|
|
7417
|
-
const text = input.text ?? "";
|
|
7418
|
-
await runCliclick([`t:${text}`]);
|
|
7419
|
-
return `typed ${text.length} character(s)`;
|
|
7420
|
-
}
|
|
7421
|
-
case "key": {
|
|
7422
|
-
const args = translateKeyForCliclick(input.text ?? "");
|
|
7423
|
-
if (args.length === 0) return "no key specified";
|
|
7424
|
-
await runCliclick(args);
|
|
7425
|
-
return `pressed ${input.text}`;
|
|
7426
|
-
}
|
|
7427
|
-
case "hold_key": {
|
|
7428
|
-
const text = (input.text ?? "").toLowerCase();
|
|
7429
|
-
const duration = input.duration ?? 1;
|
|
7430
|
-
const modMap = {
|
|
7431
|
-
ctrl: "ctrl",
|
|
7432
|
-
control: "ctrl",
|
|
7433
|
-
alt: "alt",
|
|
7434
|
-
option: "alt",
|
|
7435
|
-
shift: "shift",
|
|
7436
|
-
cmd: "cmd",
|
|
7437
|
-
super: "cmd",
|
|
7438
|
-
meta: "cmd",
|
|
7439
|
-
fn: "fn"
|
|
7440
|
-
};
|
|
7441
|
-
const cliName = modMap[text] || text;
|
|
7442
|
-
await runCliclick([`kd:${cliName}`]);
|
|
7443
|
-
await new Promise((r) => setTimeout(r, duration * 1e3));
|
|
7444
|
-
await runCliclick([`ku:${cliName}`]);
|
|
7445
|
-
return `held ${text} for ${duration}s`;
|
|
7446
|
-
}
|
|
7447
|
-
case "scroll": {
|
|
7448
|
-
const direction = input.scroll_direction ?? "down";
|
|
7449
|
-
const amount = input.scroll_amount ?? 3;
|
|
7450
|
-
const px = amount * 100;
|
|
7451
|
-
const dx = direction === "left" ? -px : direction === "right" ? px : 0;
|
|
7452
|
-
const dy = direction === "up" ? -px : direction === "down" ? px : 0;
|
|
7453
|
-
if (input.coordinate) {
|
|
7454
|
-
const [x, y] = input.coordinate;
|
|
7455
|
-
await runCliclick([`m:${x},${y}`]);
|
|
7456
|
-
}
|
|
7457
|
-
const mods = input.text ? modifierStringToCliclick(input.text) : [];
|
|
7458
|
-
if (mods.length > 0) {
|
|
7459
|
-
await runCliclick([`kd:${mods.join(",")}`]);
|
|
7460
|
-
}
|
|
7461
|
-
await runScroll(dx, dy);
|
|
7462
|
-
if (mods.length > 0) {
|
|
7463
|
-
await runCliclick([`ku:${mods.join(",")}`]);
|
|
7464
|
-
}
|
|
7465
|
-
return `scrolled ${direction} by ${amount}`;
|
|
7466
|
-
}
|
|
7467
|
-
case "wait": {
|
|
7468
|
-
const duration = input.duration ?? 1;
|
|
7469
|
-
await new Promise((r) => setTimeout(r, duration * 1e3));
|
|
7470
|
-
return `waited ${duration}s`;
|
|
7471
|
-
}
|
|
7472
|
-
case "cursor_position": {
|
|
7473
|
-
const out = await runCliclick(["p:."]);
|
|
7474
|
-
return `cursor at ${out}`;
|
|
7475
|
-
}
|
|
7476
|
-
case "zoom": {
|
|
7477
|
-
const region = input.region ?? [0, 0, displayWidth, displayHeight];
|
|
7478
|
-
const [x1, y1, x2, y2] = region;
|
|
7479
|
-
const tmpPath = join8(tmpdir(), `cu-zoom-${nanoid4(8)}.png`);
|
|
7480
|
-
await runScreencapture(tmpPath);
|
|
7481
|
-
const sharpModule = await import("sharp");
|
|
7482
|
-
const sharp2 = sharpModule.default || sharpModule;
|
|
7483
|
-
const meta = await sharp2(tmpPath).metadata();
|
|
7484
|
-
const scaleX = (meta.width || displayWidth) / displayWidth;
|
|
7485
|
-
const scaleY = (meta.height || displayHeight) / displayHeight;
|
|
7486
|
-
const px = {
|
|
7487
|
-
left: Math.max(0, Math.round(x1 * scaleX)),
|
|
7488
|
-
top: Math.max(0, Math.round(y1 * scaleY)),
|
|
7489
|
-
width: Math.max(1, Math.round((x2 - x1) * scaleX)),
|
|
7490
|
-
height: Math.max(1, Math.round((y2 - y1) * scaleY))
|
|
7491
|
-
};
|
|
7492
|
-
const buf = await sharp2(tmpPath).extract(px).png().toBuffer();
|
|
7493
|
-
try {
|
|
7494
|
-
unlinkSync2(tmpPath);
|
|
7495
|
-
} catch {
|
|
7496
|
-
}
|
|
7497
|
-
return { type: "image", data: buf.toString("base64") };
|
|
7498
|
-
}
|
|
7499
|
-
default: {
|
|
7500
|
-
const exhaustive = input.action;
|
|
7501
|
-
return `unsupported action: ${String(exhaustive)}`;
|
|
7502
|
-
}
|
|
7503
|
-
}
|
|
7504
|
-
} catch (err) {
|
|
7505
|
-
const msg = err?.message || String(err);
|
|
7506
|
-
let hint = "";
|
|
7507
|
-
if (/accessibility|not authorized|tcc|operation not permitted/i.test(msg)) {
|
|
7508
|
-
hint = " (Hint: call enable_computer_use to (re-)check permissions and open System Settings)";
|
|
7509
|
-
} else if (/command not found/i.test(msg)) {
|
|
7510
|
-
hint = " (Hint: install cliclick with `brew install cliclick`)";
|
|
7511
|
-
}
|
|
7512
|
-
return `Error: ${msg}${hint}`;
|
|
7513
|
-
}
|
|
7514
|
-
},
|
|
7515
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7516
|
-
toModelOutput({ output }) {
|
|
7517
|
-
if (typeof output === "string") {
|
|
7518
|
-
return { type: "content", value: [{ type: "text", text: output }] };
|
|
7519
|
-
}
|
|
7520
|
-
return {
|
|
7521
|
-
type: "content",
|
|
7522
|
-
value: [{ type: "media", data: output.data, mediaType: "image/png" }]
|
|
7523
|
-
};
|
|
7524
|
-
}
|
|
7525
|
-
});
|
|
7526
|
-
}
|
|
7527
|
-
var execAsync5, DEFAULT_WIDTH, DEFAULT_HEIGHT;
|
|
7528
|
-
var init_computer_use = __esm({
|
|
7529
|
-
"src/tools/computer-use.ts"() {
|
|
7530
|
-
"use strict";
|
|
7531
|
-
execAsync5 = promisify5(exec5);
|
|
7532
|
-
DEFAULT_WIDTH = 1280;
|
|
7533
|
-
DEFAULT_HEIGHT = 800;
|
|
7534
|
-
}
|
|
7535
|
-
});
|
|
7536
|
-
|
|
7537
|
-
// src/tools/enable-computer-use.ts
|
|
7538
|
-
import { tool as tool13 } from "ai";
|
|
7539
|
-
import { z as z14 } from "zod";
|
|
7540
|
-
function createEnableComputerUseTool(options) {
|
|
7541
|
-
return tool13({
|
|
7542
|
-
description: "Enable Anthropic's computer use beta tool for this session. macOS only. Drives the actual desktop (mouse, keyboard, screenshots) at pixel coordinates. Requires `cliclick` (brew install cliclick), Accessibility permissions, and Screen Recording permissions. When called, this tool will automatically request any missing permissions and open System Settings to the right pane. Only works on Anthropic Claude models. After this tool succeeds, you MUST stop the current turn and ask the user to send another message \u2014 the `computer` tool only becomes available on the NEXT message because the toolset is fixed for the current turn.",
|
|
7543
|
-
inputSchema,
|
|
7544
|
-
execute: async ({ display_width, display_height, request_permissions }) => {
|
|
7545
|
-
try {
|
|
7546
|
-
if (!isMacOs()) {
|
|
7547
|
-
return {
|
|
7548
|
-
success: false,
|
|
7549
|
-
error: "Computer use is currently only supported on macOS.",
|
|
7550
|
-
platform: process.platform
|
|
7551
|
-
};
|
|
7552
|
-
}
|
|
7553
|
-
if (!await isCliclickInstalled()) {
|
|
7554
|
-
return {
|
|
7555
|
-
success: false,
|
|
7556
|
-
error: "`cliclick` is not installed. It is required for mouse/keyboard control on macOS.",
|
|
7557
|
-
installCommand: "brew install cliclick",
|
|
7558
|
-
fixSteps: [
|
|
7559
|
-
"In a terminal on this Mac, run: brew install cliclick",
|
|
7560
|
-
"(If Homebrew is not installed, install it first from https://brew.sh)",
|
|
7561
|
-
"Then call enable_computer_use again"
|
|
7562
|
-
]
|
|
7563
|
-
};
|
|
7564
|
-
}
|
|
7565
|
-
const acc = await hasAccessibilityPermissions();
|
|
7566
|
-
const screen = await hasScreenRecordingPermissions();
|
|
7567
|
-
const missing = [];
|
|
7568
|
-
if (!acc.ok) {
|
|
7569
|
-
let prompted = false;
|
|
7570
|
-
let panelOpened = false;
|
|
7571
|
-
if (request_permissions) {
|
|
7572
|
-
prompted = await requestAccessibilityPrompt().then(() => true).catch(() => false);
|
|
7573
|
-
await openSystemSettings("accessibility").then(() => {
|
|
7574
|
-
panelOpened = true;
|
|
7575
|
-
}).catch(() => void 0);
|
|
7576
|
-
}
|
|
7577
|
-
missing.push({
|
|
7578
|
-
name: "Accessibility",
|
|
7579
|
-
reason: "cliclick failed: " + (acc.error?.split("\n")[0] || "no permission"),
|
|
7580
|
-
pane: "accessibility",
|
|
7581
|
-
settingsUrl: ACCESSIBILITY_URL,
|
|
7582
|
-
fixSteps: [
|
|
7583
|
-
"In the System Settings \u2192 Privacy & Security \u2192 Accessibility pane that opened",
|
|
7584
|
-
"Click the + button",
|
|
7585
|
-
"Add the application running the agent (Terminal, iTerm, your IDE, or `node`)",
|
|
7586
|
-
"Toggle the switch ON",
|
|
7587
|
-
"Restart the agent process so the new permission takes effect",
|
|
7588
|
-
"Then call enable_computer_use again"
|
|
7589
|
-
],
|
|
7590
|
-
prompted,
|
|
7591
|
-
panelOpened
|
|
7592
|
-
});
|
|
7593
|
-
}
|
|
7594
|
-
if (!screen) {
|
|
7595
|
-
let prompted = false;
|
|
7596
|
-
let panelOpened = false;
|
|
7597
|
-
if (request_permissions) {
|
|
7598
|
-
prompted = await requestScreenRecordingPrompt().then(() => true).catch(() => false);
|
|
7599
|
-
await openSystemSettings("screen-recording").then(() => {
|
|
7600
|
-
panelOpened = true;
|
|
7601
|
-
}).catch(() => void 0);
|
|
7602
|
-
}
|
|
7603
|
-
missing.push({
|
|
7604
|
-
name: "Screen Recording",
|
|
7605
|
-
reason: "CGPreflightScreenCaptureAccess returned false",
|
|
7606
|
-
pane: "screen-recording",
|
|
7607
|
-
settingsUrl: SCREEN_RECORDING_URL,
|
|
7608
|
-
fixSteps: [
|
|
7609
|
-
"In the System Settings \u2192 Privacy & Security \u2192 Screen Recording pane that opened",
|
|
7610
|
-
"Click the + button",
|
|
7611
|
-
"Add the application running the agent (Terminal, iTerm, your IDE, or `node`)",
|
|
7612
|
-
"Toggle the switch ON",
|
|
7613
|
-
"Restart the agent process so the new permission takes effect",
|
|
7614
|
-
"Then call enable_computer_use again"
|
|
7615
|
-
],
|
|
7616
|
-
prompted,
|
|
7617
|
-
panelOpened
|
|
7618
|
-
});
|
|
7619
|
-
}
|
|
7620
|
-
if (missing.length > 0) {
|
|
7621
|
-
return {
|
|
7622
|
-
success: false,
|
|
7623
|
-
error: `Missing permission${missing.length > 1 ? "s" : ""}: ` + missing.map((m) => m.name).join(" and ") + ".",
|
|
7624
|
-
missingPermissions: missing,
|
|
7625
|
-
note: request_permissions ? "System permission prompts have been triggered (best-effort) and System Settings has been opened to the relevant pane(s). After granting and restarting the agent, call enable_computer_use again." : "Re-run with request_permissions: true to auto-open System Settings."
|
|
7626
|
-
};
|
|
7627
|
-
}
|
|
7628
|
-
let width = display_width;
|
|
7629
|
-
let height = display_height;
|
|
7630
|
-
let detected = null;
|
|
7631
|
-
if (width === void 0 || height === void 0) {
|
|
7632
|
-
detected = await detectScreenSize();
|
|
7633
|
-
width = width ?? detected?.width ?? 1280;
|
|
7634
|
-
height = height ?? detected?.height ?? 800;
|
|
7635
|
-
}
|
|
7636
|
-
const session = await sessionQueries.getById(options.sessionId);
|
|
7637
|
-
if (!session) {
|
|
7638
|
-
return { success: false, error: "Session not found" };
|
|
7639
|
-
}
|
|
7640
|
-
const config = session.config || {};
|
|
7641
|
-
if (config.computerUseEnabled === true && config.computerUseDisplayWidth === width && config.computerUseDisplayHeight === height) {
|
|
7642
|
-
return {
|
|
7643
|
-
success: true,
|
|
7644
|
-
alreadyEnabled: true,
|
|
7645
|
-
message: "Computer use was already enabled for this session.",
|
|
7646
|
-
displayWidth: width,
|
|
7647
|
-
displayHeight: height
|
|
7648
|
-
};
|
|
7649
|
-
}
|
|
7650
|
-
const updated = {
|
|
7651
|
-
...config,
|
|
7652
|
-
computerUseEnabled: true,
|
|
7653
|
-
computerUseDisplayWidth: width,
|
|
7654
|
-
computerUseDisplayHeight: height
|
|
7655
|
-
};
|
|
7656
|
-
await sessionQueries.update(options.sessionId, { config: updated });
|
|
7657
|
-
return {
|
|
7658
|
-
success: true,
|
|
7659
|
-
enabled: true,
|
|
7660
|
-
platform: "darwin",
|
|
7661
|
-
displayWidth: width,
|
|
7662
|
-
displayHeight: height,
|
|
7663
|
-
detectedScreenSize: detected || void 0,
|
|
7664
|
-
permissions: {
|
|
7665
|
-
accessibility: "granted",
|
|
7666
|
-
screenRecording: "granted"
|
|
7667
|
-
},
|
|
7668
|
-
message: `Computer use is now enabled for this session. IMPORTANT: The \`computer\` tool is NOT yet available in this turn. Stop here, send a brief message to the user telling them computer use is enabled (display: ${width}x${height}), and ask them to send their next message to begin using it.`
|
|
7669
|
-
};
|
|
7670
|
-
} catch (err) {
|
|
7671
|
-
return {
|
|
7672
|
-
success: false,
|
|
7673
|
-
error: err?.message || String(err)
|
|
7674
|
-
};
|
|
7675
|
-
}
|
|
7676
|
-
}
|
|
7677
|
-
});
|
|
7678
|
-
}
|
|
7679
|
-
var inputSchema, ACCESSIBILITY_URL, SCREEN_RECORDING_URL;
|
|
7680
|
-
var init_enable_computer_use = __esm({
|
|
7681
|
-
"src/tools/enable-computer-use.ts"() {
|
|
7682
|
-
"use strict";
|
|
7683
|
-
init_db();
|
|
7684
|
-
init_computer_use();
|
|
7685
|
-
inputSchema = z14.object({
|
|
7686
|
-
display_width: z14.number().int().positive().optional().describe("Display width in pixels (defaults to detected primary display, fallback 1280)"),
|
|
7687
|
-
display_height: z14.number().int().positive().optional().describe("Display height in pixels (defaults to detected primary display, fallback 800)"),
|
|
7688
|
-
request_permissions: z14.boolean().optional().default(true).describe(
|
|
7689
|
-
"When true (default), proactively trigger macOS permission prompts and open System Settings panes for any missing permissions."
|
|
7690
|
-
)
|
|
7691
|
-
});
|
|
7692
|
-
ACCESSIBILITY_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
|
|
7693
|
-
SCREEN_RECORDING_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
|
|
7694
|
-
}
|
|
7695
|
-
});
|
|
7696
|
-
|
|
7697
7104
|
// src/tools/index.ts
|
|
7698
7105
|
async function createTools(options) {
|
|
7699
7106
|
const tools = {
|
|
@@ -7738,20 +7145,6 @@ async function createTools(options) {
|
|
|
7738
7145
|
sessionId: options.sessionId
|
|
7739
7146
|
});
|
|
7740
7147
|
}
|
|
7741
|
-
if (process.platform === "darwin") {
|
|
7742
|
-
if (options.enableComputerUse) {
|
|
7743
|
-
tools.computer = createComputerUseTool({
|
|
7744
|
-
workingDirectory: options.workingDirectory,
|
|
7745
|
-
sessionId: options.sessionId,
|
|
7746
|
-
displayWidth: options.computerUseDisplayWidth,
|
|
7747
|
-
displayHeight: options.computerUseDisplayHeight
|
|
7748
|
-
});
|
|
7749
|
-
} else {
|
|
7750
|
-
tools.enable_computer_use = createEnableComputerUseTool({
|
|
7751
|
-
sessionId: options.sessionId
|
|
7752
|
-
});
|
|
7753
|
-
}
|
|
7754
|
-
}
|
|
7755
7148
|
if (options.enableSemanticSearch !== false) {
|
|
7756
7149
|
try {
|
|
7757
7150
|
if (isVectorGatewayConfigured()) {
|
|
@@ -7786,8 +7179,6 @@ var init_tools = __esm({
|
|
|
7786
7179
|
init_code_graph();
|
|
7787
7180
|
init_task();
|
|
7788
7181
|
init_upload_file();
|
|
7789
|
-
init_computer_use();
|
|
7790
|
-
init_enable_computer_use();
|
|
7791
7182
|
init_semantic();
|
|
7792
7183
|
init_remote();
|
|
7793
7184
|
init_bash();
|
|
@@ -7801,8 +7192,6 @@ var init_tools = __esm({
|
|
|
7801
7192
|
init_code_graph();
|
|
7802
7193
|
init_task();
|
|
7803
7194
|
init_upload_file();
|
|
7804
|
-
init_computer_use();
|
|
7805
|
-
init_enable_computer_use();
|
|
7806
7195
|
}
|
|
7807
7196
|
});
|
|
7808
7197
|
|
|
@@ -8278,8 +7667,7 @@ ${JSON.stringify(outputSchema, null, 2)}
|
|
|
8278
7667
|
`;
|
|
8279
7668
|
}
|
|
8280
7669
|
function buildOrchestratorPromptAddendum() {
|
|
8281
|
-
const
|
|
8282
|
-
const computerUseAvailable = platform3 === "darwin";
|
|
7670
|
+
const desktopAvailable = process.platform === "darwin";
|
|
8283
7671
|
return `
|
|
8284
7672
|
## Orchestrator Mode
|
|
8285
7673
|
|
|
@@ -8378,14 +7766,14 @@ When NOT to split (keep as one worker):
|
|
|
8378
7766
|
When spawning a worker, push it toward the *cheapest tool that gets the job done*:
|
|
8379
7767
|
|
|
8380
7768
|
1. **Bash / file tools** for anything with a CLI (git, npm, brew, builds, tests, file editing, HTTP via curl, scripting).
|
|
8381
|
-
2. **agent-browser** (\`load_skill browser\`) for *anything* in a web browser \u2014 refs from \`snapshot -i\` are deterministic, ~100\xD7 cheaper in tokens than pixel coordinates, work cross-platform, and don't need any host permissions.${
|
|
8382
|
-
3. **
|
|
7769
|
+
2. **agent-browser** (\`load_skill browser\`) for *anything* in a web browser \u2014 refs from \`snapshot -i\` are deterministic, ~100\xD7 cheaper in tokens than pixel coordinates, work cross-platform, and don't need any host permissions.${desktopAvailable ? `
|
|
7770
|
+
3. **Desktop automation** (\`load_skill desktop-automation\`) is the last resort \u2014 only when the task genuinely requires a native macOS GUI app with no CLI / API equivalent (System Settings, Calculator, Finder operations that don't have CLI flags, complex cross-app drag/drop, demos where the user wants to *see* the screen). It's all shell \u2014 \`cliclick\`, \`screencapture\`, and \`osascript\` \u2014 invoked from \`bash\`. No special tool registration; no vendor lock-in.
|
|
8383
7771
|
|
|
8384
|
-
A common anti-pattern: a worker reaches for
|
|
7772
|
+
A common anti-pattern: a worker reaches for desktop automation because the user phrased the request visually ("open the website and click the button"). Almost always wrong \u2014 that's a job for the browser skill, not the desktop. Coach the worker in its goal text: *"Use the browser skill (\`load_skill browser\` + \`agent-browser\` with refs from \`snapshot -i\`) to open the site and click the button. Don't use desktop automation for browser work."*
|
|
8385
7773
|
|
|
8386
|
-
### Serialize desktop
|
|
7774
|
+
### Serialize desktop-automation tasks
|
|
8387
7775
|
|
|
8388
|
-
There is exactly **one** desktop, mouse, and keyboard on the host. If two or more workers both
|
|
7776
|
+
There is exactly **one** desktop, mouse, and keyboard on the host. If two or more workers both drive the desktop (clicking with \`cliclick\`, taking screenshots with \`screencapture\`, opening apps, switching windows), they will **fight over the same screen** \u2014 windows will steal focus from each other, screenshots will catch the wrong app, mouse clicks will land on the wrong target.
|
|
8389
7777
|
|
|
8390
7778
|
**Rule**: when spawning workers, look at each one's goal:
|
|
8391
7779
|
|
|
@@ -8406,7 +7794,7 @@ Example: *"Take a screenshot of Calculator AND run the test suite AND open Syste
|
|
|
8406
7794
|
|
|
8407
7795
|
Headless workers never interfere with desktop workers (they don't touch the screen), so they always run in parallel.
|
|
8408
7796
|
|
|
8409
|
-
When you spawn a **desktop worker**,
|
|
7797
|
+
When you spawn a **desktop worker**, tell it to bracket the work with \`sparkecoder record start\` / \`sparkecoder record stop\` (per the \`recording\` skill) so the user can replay what happened on screen, unless the task is long-running / boring / contains sensitive content. When the worker reports back, mention the recording path in your reply via the original channel.` : ""}
|
|
8410
7798
|
|
|
8411
7799
|
Default bias: **when in doubt, decompose**. Two workers running in parallel and reporting independently is almost always better UX than one worker doing things sequentially.
|
|
8412
7800
|
|
|
@@ -8437,7 +7825,7 @@ You delegate; the worker executes. Stay at the **what** level, not the **how**.
|
|
|
8437
7825
|
**DO** put in the goal:
|
|
8438
7826
|
|
|
8439
7827
|
- The end objective ("open the macOS Weather app and capture the forecast for Anchorage as a screen recording").
|
|
8440
|
-
- Which skills the worker should load up-front (\`load_skill recording\`, \`load_skill
|
|
7828
|
+
- Which skills the worker should load up-front (\`load_skill recording\`, \`load_skill desktop-automation\`).
|
|
8441
7829
|
- Acceptance criteria ("verify the city name is visible in the screenshot before reporting done").
|
|
8442
7830
|
- Routing back ("post the recording URL to Slack channel C0123 thread 1700.001").
|
|
8443
7831
|
|
|
@@ -8470,7 +7858,7 @@ Bad goal (don't do this):
|
|
|
8470
7858
|
> "Start a screen recording with \`screencapture -v -V 45 -C /tmp/weather.mov &\`, then open Weather with \`open -a Weather\`, then click the search bar with cliclick, type 'Anchorage'..."
|
|
8471
7859
|
|
|
8472
7860
|
Good goal (do this):
|
|
8473
|
-
> "Capture a 30\u201360s screen recording of opening the macOS Weather app and viewing the Anchorage, AK forecast. \`load_skill recording\` and \`load_skill
|
|
7861
|
+
> "Capture a 30\u201360s screen recording of opening the macOS Weather app and viewing the Anchorage, AK forecast. \`load_skill recording\` and \`load_skill desktop-automation\` first; use the canonical commands from those skills. Verify the Anchorage temperature is visible in a final screenshot before completing. Return the recording path + a one-line summary of the forecast."
|
|
8474
7862
|
`;
|
|
8475
7863
|
}
|
|
8476
7864
|
function createSummaryPrompt(conversationHistory) {
|
|
@@ -8689,17 +8077,17 @@ __export(conversation_archive_exports, {
|
|
|
8689
8077
|
getHistoryDir: () => getHistoryDir,
|
|
8690
8078
|
listSessionArchives: () => listSessionArchives
|
|
8691
8079
|
});
|
|
8692
|
-
import { existsSync as
|
|
8693
|
-
import { join as
|
|
8080
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync5, appendFileSync as appendFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
8081
|
+
import { join as join8 } from "path";
|
|
8694
8082
|
function getHistoryDir() {
|
|
8695
|
-
const dir =
|
|
8696
|
-
if (!
|
|
8083
|
+
const dir = join8(ensureAppDataDirectory(), "history");
|
|
8084
|
+
if (!existsSync15(dir)) mkdirSync5(dir, { recursive: true });
|
|
8697
8085
|
return dir;
|
|
8698
8086
|
}
|
|
8699
8087
|
function appendTurn(turn) {
|
|
8700
8088
|
try {
|
|
8701
8089
|
const dir = getHistoryDir();
|
|
8702
|
-
const path =
|
|
8090
|
+
const path = join8(dir, `${turn.sessionId}.jsonl`);
|
|
8703
8091
|
const line = JSON.stringify({
|
|
8704
8092
|
ts: turn.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
8705
8093
|
sessionId: turn.sessionId,
|
|
@@ -8728,7 +8116,7 @@ function flattenContent(content) {
|
|
|
8728
8116
|
}
|
|
8729
8117
|
function listSessionArchives() {
|
|
8730
8118
|
const dir = getHistoryDir();
|
|
8731
|
-
if (!
|
|
8119
|
+
if (!existsSync15(dir)) return [];
|
|
8732
8120
|
return readdirSync2(dir).filter((f) => f.endsWith(".jsonl"));
|
|
8733
8121
|
}
|
|
8734
8122
|
var init_conversation_archive = __esm({
|
|
@@ -8800,6 +8188,18 @@ function repairToolPairing(messages) {
|
|
|
8800
8188
|
}
|
|
8801
8189
|
return repaired;
|
|
8802
8190
|
}
|
|
8191
|
+
function ensureEndsWithUserOrTool(messages) {
|
|
8192
|
+
if (!Array.isArray(messages) || messages.length === 0) return messages;
|
|
8193
|
+
const last = messages[messages.length - 1];
|
|
8194
|
+
if (last?.role !== "assistant") return messages;
|
|
8195
|
+
console.warn(
|
|
8196
|
+
"[context] Trailing assistant message detected \u2014 appending synthetic user turn to satisfy prefill restrictions"
|
|
8197
|
+
);
|
|
8198
|
+
return [
|
|
8199
|
+
...messages,
|
|
8200
|
+
{ role: "user", content: [{ type: "text", text: "Please continue." }] }
|
|
8201
|
+
];
|
|
8202
|
+
}
|
|
8803
8203
|
var TOOL_OUTPUT_TRIM_CHARS, COMPACTABLE_TOOLS, ContextManager;
|
|
8804
8204
|
var init_context = __esm({
|
|
8805
8205
|
"src/agent/context.ts"() {
|
|
@@ -8857,6 +8257,7 @@ ${summaryContent}`
|
|
|
8857
8257
|
];
|
|
8858
8258
|
}
|
|
8859
8259
|
messages = repairToolPairing(messages);
|
|
8260
|
+
messages = ensureEndsWithUserOrTool(messages);
|
|
8860
8261
|
return messages;
|
|
8861
8262
|
}
|
|
8862
8263
|
// ---------------------------------------------------------------------------
|
|
@@ -9050,7 +8451,8 @@ ${summaryContent}`
|
|
|
9050
8451
|
}
|
|
9051
8452
|
}
|
|
9052
8453
|
async addResponseMessages(messages) {
|
|
9053
|
-
|
|
8454
|
+
const safe = repairToolPairing(messages);
|
|
8455
|
+
await messageQueries.addMany(this.sessionId, safe);
|
|
9054
8456
|
try {
|
|
9055
8457
|
const { appendTurn: appendTurn2, flattenContent: flattenContent2 } = await Promise.resolve().then(() => (init_conversation_archive(), conversation_archive_exports));
|
|
9056
8458
|
const { sessionQueries: sessionQueries2 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
@@ -9146,6 +8548,44 @@ function getSlackSigningSecret() {
|
|
|
9146
8548
|
function getDefaultOrchestratorName() {
|
|
9147
8549
|
return readSlackConfig()?.defaultOrchestratorName ?? null;
|
|
9148
8550
|
}
|
|
8551
|
+
function getCachedSlackSelfIdentity() {
|
|
8552
|
+
const cfg = readSlackConfig();
|
|
8553
|
+
if (!cfg) return null;
|
|
8554
|
+
if (cachedSelf && cachedSelf.token === cfg.botToken) return cachedSelf.identity;
|
|
8555
|
+
return null;
|
|
8556
|
+
}
|
|
8557
|
+
async function ensureSlackSelfIdentity() {
|
|
8558
|
+
const cfg = readSlackConfig();
|
|
8559
|
+
if (!cfg) return null;
|
|
8560
|
+
if (cachedSelf && cachedSelf.token === cfg.botToken) return cachedSelf.identity;
|
|
8561
|
+
if (selfInflight) return selfInflight;
|
|
8562
|
+
selfInflight = (async () => {
|
|
8563
|
+
try {
|
|
8564
|
+
const res = await fetch("https://slack.com/api/auth.test", {
|
|
8565
|
+
method: "POST",
|
|
8566
|
+
headers: { Authorization: `Bearer ${cfg.botToken}` }
|
|
8567
|
+
});
|
|
8568
|
+
const data = await res.json().catch(() => ({}));
|
|
8569
|
+
if (!data?.ok) {
|
|
8570
|
+
console.warn(`[slack] auth.test failed: ${data?.error || `HTTP ${res.status}`}`);
|
|
8571
|
+
return null;
|
|
8572
|
+
}
|
|
8573
|
+
const identity = {
|
|
8574
|
+
botUserId: String(data.user_id || ""),
|
|
8575
|
+
botId: String(data.bot_id || ""),
|
|
8576
|
+
teamId: data.team_id ? String(data.team_id) : void 0
|
|
8577
|
+
};
|
|
8578
|
+
cachedSelf = { token: cfg.botToken, identity };
|
|
8579
|
+
return identity;
|
|
8580
|
+
} catch (err) {
|
|
8581
|
+
console.warn("[slack] auth.test error:", err?.message || err);
|
|
8582
|
+
return null;
|
|
8583
|
+
} finally {
|
|
8584
|
+
selfInflight = null;
|
|
8585
|
+
}
|
|
8586
|
+
})();
|
|
8587
|
+
return selfInflight;
|
|
8588
|
+
}
|
|
9149
8589
|
function getSlackAllowlistPolicy() {
|
|
9150
8590
|
try {
|
|
9151
8591
|
const cfg = getConfig();
|
|
@@ -9171,11 +8611,13 @@ function getSlackDeniedReplyPolicy() {
|
|
|
9171
8611
|
return { enabled: true, template: DEFAULT_DENIED_TEMPLATE };
|
|
9172
8612
|
}
|
|
9173
8613
|
}
|
|
9174
|
-
var DEFAULT_DENIED_TEMPLATE;
|
|
8614
|
+
var cachedSelf, selfInflight, DEFAULT_DENIED_TEMPLATE;
|
|
9175
8615
|
var init_client3 = __esm({
|
|
9176
8616
|
"src/integrations/slack/client.ts"() {
|
|
9177
8617
|
"use strict";
|
|
9178
8618
|
init_config();
|
|
8619
|
+
cachedSelf = null;
|
|
8620
|
+
selfInflight = null;
|
|
9179
8621
|
DEFAULT_DENIED_TEMPLATE = "Sorry, you don't have permission to use this bot. (Contact the bot owner if you think this is a mistake.)";
|
|
9180
8622
|
}
|
|
9181
8623
|
});
|
|
@@ -9193,9 +8635,19 @@ function isThreadOwned(channel, threadTs) {
|
|
|
9193
8635
|
function stripMention(text) {
|
|
9194
8636
|
return String(text || "").replace(/<@[^>]+>/g, "").trim();
|
|
9195
8637
|
}
|
|
9196
|
-
function
|
|
8638
|
+
function isSelfAuthored(event, self) {
|
|
8639
|
+
if (!self) return true;
|
|
8640
|
+
if (self.botId && event.bot_id && event.bot_id === self.botId) return true;
|
|
8641
|
+
if (self.botUserId && event.user && event.user === self.botUserId) return true;
|
|
8642
|
+
return false;
|
|
8643
|
+
}
|
|
8644
|
+
function slackEventToInboundResult(event, opts = {}) {
|
|
9197
8645
|
if (!event) return { event: null, dropReason: "empty_text" };
|
|
9198
|
-
|
|
8646
|
+
const self = opts.self ?? getCachedSlackSelfIdentity();
|
|
8647
|
+
const isBotAuthored = !!event.bot_id || event.type === "message" && event.subtype === "bot_message";
|
|
8648
|
+
if (isBotAuthored && isSelfAuthored(event, self)) {
|
|
8649
|
+
return { event: null, dropReason: "bot_message" };
|
|
8650
|
+
}
|
|
9199
8651
|
if (event.type === "message" && event.subtype && IGNORED_MESSAGE_SUBTYPES.has(event.subtype)) {
|
|
9200
8652
|
return { event: null, dropReason: "bot_message" };
|
|
9201
8653
|
}
|
|
@@ -9272,7 +8724,6 @@ var init_slack = __esm({
|
|
|
9272
8724
|
}
|
|
9273
8725
|
};
|
|
9274
8726
|
IGNORED_MESSAGE_SUBTYPES = /* @__PURE__ */ new Set([
|
|
9275
|
-
"bot_message",
|
|
9276
8727
|
"message_changed",
|
|
9277
8728
|
"message_deleted",
|
|
9278
8729
|
"channel_join",
|
|
@@ -9491,7 +8942,7 @@ var init_messenger = __esm({
|
|
|
9491
8942
|
});
|
|
9492
8943
|
|
|
9493
8944
|
// src/orchestrator/schedules-store.ts
|
|
9494
|
-
import { nanoid as
|
|
8945
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
9495
8946
|
async function readOrch(orchestratorSessionId) {
|
|
9496
8947
|
const s = await sessionQueries.getById(orchestratorSessionId);
|
|
9497
8948
|
if (!s) return null;
|
|
@@ -9506,7 +8957,7 @@ async function createSchedule(orchestratorSessionId, input) {
|
|
|
9506
8957
|
const data = await readOrch(orchestratorSessionId);
|
|
9507
8958
|
if (!data) throw new Error("orchestrator session not found");
|
|
9508
8959
|
const row = {
|
|
9509
|
-
id: `sch_${
|
|
8960
|
+
id: `sch_${nanoid4(10)}`,
|
|
9510
8961
|
name: input.name,
|
|
9511
8962
|
cron: input.cron,
|
|
9512
8963
|
prompt: input.prompt,
|
|
@@ -9543,7 +8994,7 @@ var init_schedules_store = __esm({
|
|
|
9543
8994
|
|
|
9544
8995
|
// src/orchestrator/webhooks-store.ts
|
|
9545
8996
|
import { randomBytes } from "crypto";
|
|
9546
|
-
import { nanoid as
|
|
8997
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
9547
8998
|
function newToken() {
|
|
9548
8999
|
return randomBytes(24).toString("base64url");
|
|
9549
9000
|
}
|
|
@@ -9560,7 +9011,7 @@ async function createWebhook(orchestratorSessionId, input) {
|
|
|
9560
9011
|
const data = await readOrch2(orchestratorSessionId);
|
|
9561
9012
|
if (!data) throw new Error("orchestrator session not found");
|
|
9562
9013
|
const row = {
|
|
9563
|
-
id: `whk_${
|
|
9014
|
+
id: `whk_${nanoid5(10)}`,
|
|
9564
9015
|
name: input.name,
|
|
9565
9016
|
token: newToken(),
|
|
9566
9017
|
wake: input.wake ?? "now",
|
|
@@ -9616,8 +9067,8 @@ var init_webhooks_store = __esm({
|
|
|
9616
9067
|
});
|
|
9617
9068
|
|
|
9618
9069
|
// src/tools/orchestrator-actions.ts
|
|
9619
|
-
import { tool as
|
|
9620
|
-
import { z as
|
|
9070
|
+
import { tool as tool13 } from "ai";
|
|
9071
|
+
import { z as z14 } from "zod";
|
|
9621
9072
|
async function api2(baseUrl, path, init = {}) {
|
|
9622
9073
|
const res = await fetch(`${baseUrl}${path}`, {
|
|
9623
9074
|
method: init.method || "GET",
|
|
@@ -9643,7 +9094,7 @@ function previewMessageContent(content) {
|
|
|
9643
9094
|
return "";
|
|
9644
9095
|
}
|
|
9645
9096
|
function buildAgentTool(opts) {
|
|
9646
|
-
return
|
|
9097
|
+
return tool13({
|
|
9647
9098
|
description: "Manage worker agents. Actions: list (browse with optional status filter), get (deep dive: status, todos, pending question, recent messages, final result; required: id), spawn (start a new worker; required: name, goal), message (post a message to a running worker; force=true to soft-interrupt; required: id, text), answer_question (resolve a worker's ask_question_to_user prompt; required: id, questionId, answer), stop (hard-cancel a running worker; required: id).",
|
|
9648
9099
|
inputSchema: agentInputSchema,
|
|
9649
9100
|
execute: async (input) => {
|
|
@@ -9746,7 +9197,7 @@ function buildAgentTool(opts) {
|
|
|
9746
9197
|
});
|
|
9747
9198
|
}
|
|
9748
9199
|
function buildMessengerTool() {
|
|
9749
|
-
return
|
|
9200
|
+
return tool13({
|
|
9750
9201
|
description: "Send messages on configured external channels. Actions: list_channels (no args; returns which integrations are configured), post (required: channel, to, text). Use this to ping the user on Slack when a worker finishes, when a schedule fires, etc.",
|
|
9751
9202
|
inputSchema: messengerInputSchema,
|
|
9752
9203
|
execute: async (input) => {
|
|
@@ -9768,7 +9219,7 @@ function buildMessengerTool() {
|
|
|
9768
9219
|
});
|
|
9769
9220
|
}
|
|
9770
9221
|
function buildScheduleTool(opts) {
|
|
9771
|
-
return
|
|
9222
|
+
return tool13({
|
|
9772
9223
|
description: "Recurring prompts. Actions: create (required: name, cron, prompt), list, update (required: id; any of name/cron/prompt/enabled/replyChannel), delete (required: id), pause (required: id), resume (required: id). Cron is standard 5-field syntax.",
|
|
9773
9224
|
inputSchema: scheduleInputSchema,
|
|
9774
9225
|
execute: async (input) => {
|
|
@@ -9811,7 +9262,7 @@ function buildWebhookUrl(opts, token) {
|
|
|
9811
9262
|
return `${base}${webhookPrefix2}/inbox/${token}`;
|
|
9812
9263
|
}
|
|
9813
9264
|
function buildWebhookTool(opts) {
|
|
9814
|
-
return
|
|
9265
|
+
return tool13({
|
|
9815
9266
|
description: "Custom token-protected inbound URLs. Anyone POSTing JSON to the URL pings the orchestrator. Actions: create (required: name), list, update (required: id; optional: name, wake, template, rotateToken), delete (required: id). Use these to wire up GitHub, IFTTT, n8n, etc.",
|
|
9816
9267
|
inputSchema: webhookInputSchema,
|
|
9817
9268
|
execute: async (input) => {
|
|
@@ -9860,66 +9311,66 @@ var init_orchestrator_actions = __esm({
|
|
|
9860
9311
|
init_schedules_store();
|
|
9861
9312
|
init_config();
|
|
9862
9313
|
init_webhooks_store();
|
|
9863
|
-
AGENT_STATUS_ENUM =
|
|
9864
|
-
agentInputSchema =
|
|
9865
|
-
action:
|
|
9314
|
+
AGENT_STATUS_ENUM = z14.enum(["running", "needs_attention", "completed", "failed", "idle"]);
|
|
9315
|
+
agentInputSchema = z14.object({
|
|
9316
|
+
action: z14.enum(["list", "get", "spawn", "message", "answer_question", "stop"]).describe("Which agent operation to perform."),
|
|
9866
9317
|
// list
|
|
9867
9318
|
status: AGENT_STATUS_ENUM.optional().describe("list only: filter to one status."),
|
|
9868
|
-
limit:
|
|
9319
|
+
limit: z14.number().int().min(1).max(100).optional().describe("list only: max rows."),
|
|
9869
9320
|
// get / message / answer_question / stop
|
|
9870
|
-
id:
|
|
9871
|
-
recentMessages:
|
|
9321
|
+
id: z14.string().optional().describe("get | message | answer_question | stop: the agent (session) id."),
|
|
9322
|
+
recentMessages: z14.number().int().min(0).max(50).optional().describe("get only: how many recent messages to include."),
|
|
9872
9323
|
// spawn
|
|
9873
|
-
name:
|
|
9874
|
-
goal:
|
|
9875
|
-
outputSchema:
|
|
9324
|
+
name: z14.string().optional().describe("spawn only: short human-readable label."),
|
|
9325
|
+
goal: z14.string().optional().describe("spawn only: the worker's self-contained instruction."),
|
|
9326
|
+
outputSchema: z14.record(z14.string(), z14.unknown()).optional().describe(
|
|
9876
9327
|
'spawn only: JSON Schema for the worker result. Defaults to {type:"object", properties:{summary:{type:"string"}}, required:["summary"]}.'
|
|
9877
9328
|
),
|
|
9878
|
-
model:
|
|
9879
|
-
workingDirectory:
|
|
9880
|
-
maxIterations:
|
|
9329
|
+
model: z14.string().optional().describe("spawn only: model override."),
|
|
9330
|
+
workingDirectory: z14.string().optional().describe("spawn only: working directory override."),
|
|
9331
|
+
maxIterations: z14.number().int().min(1).max(500).optional().describe("spawn only."),
|
|
9881
9332
|
// message
|
|
9882
|
-
text:
|
|
9883
|
-
force:
|
|
9333
|
+
text: z14.string().optional().describe("message only: the text to deliver to the worker."),
|
|
9334
|
+
force: z14.boolean().optional().describe("message only: soft-interrupt the current step."),
|
|
9884
9335
|
// answer_question
|
|
9885
|
-
questionId:
|
|
9886
|
-
answer:
|
|
9336
|
+
questionId: z14.string().optional().describe("answer_question only: pending question id (e.g. q_abc123)."),
|
|
9337
|
+
answer: z14.string().optional().describe("answer_question only: your answer.")
|
|
9887
9338
|
});
|
|
9888
|
-
messengerInputSchema =
|
|
9889
|
-
action:
|
|
9339
|
+
messengerInputSchema = z14.object({
|
|
9340
|
+
action: z14.enum(["list_channels", "post"]),
|
|
9890
9341
|
// post
|
|
9891
|
-
channel:
|
|
9892
|
-
to:
|
|
9893
|
-
text:
|
|
9894
|
-
threadTs:
|
|
9895
|
-
subject:
|
|
9342
|
+
channel: z14.string().optional().describe('post only: channel id (e.g. "slack").'),
|
|
9343
|
+
to: z14.string().optional().describe('post only: destination. Slack: channel id (C0123), user id (U0123), or "#channel-name".'),
|
|
9344
|
+
text: z14.string().optional().describe("post only: message body."),
|
|
9345
|
+
threadTs: z14.string().optional().describe("post + slack: reply in this thread."),
|
|
9346
|
+
subject: z14.string().optional().describe("post + email: subject (future).")
|
|
9896
9347
|
});
|
|
9897
|
-
scheduleInputSchema =
|
|
9898
|
-
action:
|
|
9348
|
+
scheduleInputSchema = z14.object({
|
|
9349
|
+
action: z14.enum(["create", "list", "update", "delete", "pause", "resume"]),
|
|
9899
9350
|
// create / update
|
|
9900
|
-
name:
|
|
9901
|
-
cron:
|
|
9902
|
-
prompt:
|
|
9903
|
-
replyChannel:
|
|
9351
|
+
name: z14.string().optional().describe("create | update"),
|
|
9352
|
+
cron: z14.string().optional().describe('create | update: 5-field cron (e.g. "0 9 * * 1-5" = weekdays at 9am).'),
|
|
9353
|
+
prompt: z14.string().optional().describe("create | update: the prompt injected when the schedule fires."),
|
|
9354
|
+
replyChannel: z14.string().optional().describe("create | update: default channel id for orchestrator replies."),
|
|
9904
9355
|
// update / delete / pause / resume
|
|
9905
|
-
id:
|
|
9906
|
-
enabled:
|
|
9356
|
+
id: z14.string().optional().describe("update | delete | pause | resume: schedule id."),
|
|
9357
|
+
enabled: z14.boolean().optional().describe("update only.")
|
|
9907
9358
|
});
|
|
9908
|
-
webhookInputSchema =
|
|
9909
|
-
action:
|
|
9910
|
-
name:
|
|
9911
|
-
wake:
|
|
9912
|
-
template:
|
|
9913
|
-
id:
|
|
9914
|
-
rotateToken:
|
|
9359
|
+
webhookInputSchema = z14.object({
|
|
9360
|
+
action: z14.enum(["create", "list", "update", "delete"]),
|
|
9361
|
+
name: z14.string().optional().describe("create | update."),
|
|
9362
|
+
wake: z14.enum(["now", "next"]).optional().describe("create | update: now = wake orchestrator immediately; next = add as context."),
|
|
9363
|
+
template: z14.string().optional().describe("create | update: mustache-style template ({{path.to.field}}). Defaults to pretty-printed JSON."),
|
|
9364
|
+
id: z14.string().optional().describe("update | delete: webhook id."),
|
|
9365
|
+
rotateToken: z14.boolean().optional().describe("update only: regenerate the URL token.")
|
|
9915
9366
|
});
|
|
9916
9367
|
}
|
|
9917
9368
|
});
|
|
9918
9369
|
|
|
9919
9370
|
// src/integrations/mcp/store.ts
|
|
9920
|
-
import { nanoid as
|
|
9921
|
-
import { existsSync as
|
|
9922
|
-
import { resolve as resolve10, join as
|
|
9371
|
+
import { nanoid as nanoid6 } from "nanoid";
|
|
9372
|
+
import { existsSync as existsSync16, readFileSync as readFileSync7 } from "fs";
|
|
9373
|
+
import { resolve as resolve10, join as join9 } from "path";
|
|
9923
9374
|
function readServers() {
|
|
9924
9375
|
try {
|
|
9925
9376
|
const cfg = getConfig();
|
|
@@ -9931,12 +9382,12 @@ function readServers() {
|
|
|
9931
9382
|
function refreshMcpServersFromDisk() {
|
|
9932
9383
|
const candidates = [
|
|
9933
9384
|
resolve10(process.cwd(), "sparkecoder.config.json"),
|
|
9934
|
-
|
|
9385
|
+
join9(ensureAppDataDirectory(), "sparkecoder.config.json")
|
|
9935
9386
|
];
|
|
9936
9387
|
for (const path of candidates) {
|
|
9937
|
-
if (!
|
|
9388
|
+
if (!existsSync16(path)) continue;
|
|
9938
9389
|
try {
|
|
9939
|
-
const raw = JSON.parse(
|
|
9390
|
+
const raw = JSON.parse(readFileSync7(path, "utf-8"));
|
|
9940
9391
|
const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
|
|
9941
9392
|
setMcpServers(servers2);
|
|
9942
9393
|
return servers2;
|
|
@@ -9955,7 +9406,7 @@ function createMcpServer(input) {
|
|
|
9955
9406
|
const all = readServers();
|
|
9956
9407
|
validateInput(input);
|
|
9957
9408
|
const row = {
|
|
9958
|
-
id: `mcp_${
|
|
9409
|
+
id: `mcp_${nanoid6(10)}`,
|
|
9959
9410
|
name: sanitizeName(input.name),
|
|
9960
9411
|
transport: input.transport,
|
|
9961
9412
|
url: input.url,
|
|
@@ -10546,15 +9997,15 @@ var recorder_exports = {};
|
|
|
10546
9997
|
__export(recorder_exports, {
|
|
10547
9998
|
FrameRecorder: () => FrameRecorder
|
|
10548
9999
|
});
|
|
10549
|
-
import { exec as
|
|
10550
|
-
import { promisify as
|
|
10000
|
+
import { exec as exec5 } from "child_process";
|
|
10001
|
+
import { promisify as promisify5 } from "util";
|
|
10551
10002
|
import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
|
|
10552
|
-
import { join as
|
|
10553
|
-
import { tmpdir
|
|
10554
|
-
import { nanoid as
|
|
10003
|
+
import { join as join10 } from "path";
|
|
10004
|
+
import { tmpdir } from "os";
|
|
10005
|
+
import { nanoid as nanoid7 } from "nanoid";
|
|
10555
10006
|
async function checkFfmpeg() {
|
|
10556
10007
|
try {
|
|
10557
|
-
await
|
|
10008
|
+
await execAsync5("ffmpeg -version", { timeout: 5e3 });
|
|
10558
10009
|
return true;
|
|
10559
10010
|
} catch {
|
|
10560
10011
|
return false;
|
|
@@ -10566,11 +10017,11 @@ async function cleanup(dir) {
|
|
|
10566
10017
|
} catch {
|
|
10567
10018
|
}
|
|
10568
10019
|
}
|
|
10569
|
-
var
|
|
10020
|
+
var execAsync5, FrameRecorder;
|
|
10570
10021
|
var init_recorder = __esm({
|
|
10571
10022
|
"src/browser/recorder.ts"() {
|
|
10572
10023
|
"use strict";
|
|
10573
|
-
|
|
10024
|
+
execAsync5 = promisify5(exec5);
|
|
10574
10025
|
FrameRecorder = class {
|
|
10575
10026
|
frames = [];
|
|
10576
10027
|
startTime = null;
|
|
@@ -10606,21 +10057,21 @@ var init_recorder = __esm({
|
|
|
10606
10057
|
*/
|
|
10607
10058
|
async encode() {
|
|
10608
10059
|
if (this.frames.length === 0) return null;
|
|
10609
|
-
const workDir =
|
|
10060
|
+
const workDir = join10(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
|
|
10610
10061
|
await mkdir4(workDir, { recursive: true });
|
|
10611
10062
|
try {
|
|
10612
10063
|
for (let i = 0; i < this.frames.length; i++) {
|
|
10613
|
-
const framePath =
|
|
10064
|
+
const framePath = join10(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
10614
10065
|
await writeFile5(framePath, this.frames[i].data);
|
|
10615
10066
|
}
|
|
10616
10067
|
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
10617
10068
|
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
10618
10069
|
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
10619
|
-
const outputPath =
|
|
10070
|
+
const outputPath = join10(workDir, `recording_${this.sessionId}.mp4`);
|
|
10620
10071
|
const hasFfmpeg = await checkFfmpeg();
|
|
10621
10072
|
if (hasFfmpeg) {
|
|
10622
|
-
await
|
|
10623
|
-
`ffmpeg -y -framerate ${clampedFps} -i "${
|
|
10073
|
+
await execAsync5(
|
|
10074
|
+
`ffmpeg -y -framerate ${clampedFps} -i "${join10(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
|
|
10624
10075
|
{ timeout: 12e4 }
|
|
10625
10076
|
);
|
|
10626
10077
|
} else {
|
|
@@ -10632,7 +10083,7 @@ var init_recorder = __esm({
|
|
|
10632
10083
|
const files = await readdir5(workDir);
|
|
10633
10084
|
for (const f of files) {
|
|
10634
10085
|
if (f.startsWith("frame_")) {
|
|
10635
|
-
await unlink2(
|
|
10086
|
+
await unlink2(join10(workDir, f)).catch(() => {
|
|
10636
10087
|
});
|
|
10637
10088
|
}
|
|
10638
10089
|
}
|
|
@@ -10657,11 +10108,11 @@ var init_recorder = __esm({
|
|
|
10657
10108
|
import {
|
|
10658
10109
|
streamText as streamText2,
|
|
10659
10110
|
generateText as generateText3,
|
|
10660
|
-
tool as
|
|
10111
|
+
tool as tool14,
|
|
10661
10112
|
stepCountIs as stepCountIs2
|
|
10662
10113
|
} from "ai";
|
|
10663
|
-
import { z as
|
|
10664
|
-
import { nanoid as
|
|
10114
|
+
import { z as z15 } from "zod";
|
|
10115
|
+
import { nanoid as nanoid8 } from "nanoid";
|
|
10665
10116
|
function anySignal(signals) {
|
|
10666
10117
|
const ctrl = new AbortController();
|
|
10667
10118
|
for (const s of signals) {
|
|
@@ -10730,14 +10181,10 @@ var init_agent = __esm({
|
|
|
10730
10181
|
*/
|
|
10731
10182
|
async createToolsWithCallbacks(options) {
|
|
10732
10183
|
const config = getConfig();
|
|
10733
|
-
const sessionConfig = this.session.config || {};
|
|
10734
10184
|
const tools = await createTools({
|
|
10735
10185
|
sessionId: this.session.id,
|
|
10736
10186
|
workingDirectory: this.session.workingDirectory,
|
|
10737
10187
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
10738
|
-
enableComputerUse: sessionConfig.computerUseEnabled === true,
|
|
10739
|
-
computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
|
|
10740
|
-
computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight,
|
|
10741
10188
|
onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
|
|
10742
10189
|
onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
|
|
10743
10190
|
onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0
|
|
@@ -10787,14 +10234,10 @@ var init_agent = __esm({
|
|
|
10787
10234
|
keepRecentMessages: config.context?.keepRecentMessages || 10,
|
|
10788
10235
|
autoSummarize: config.context?.autoSummarize ?? true
|
|
10789
10236
|
});
|
|
10790
|
-
const sessionConfig = session.config || {};
|
|
10791
10237
|
const tools = await createTools({
|
|
10792
10238
|
sessionId: session.id,
|
|
10793
10239
|
workingDirectory: session.workingDirectory,
|
|
10794
|
-
skillsDirectories: config.resolvedSkillsDirectories
|
|
10795
|
-
enableComputerUse: sessionConfig.computerUseEnabled === true,
|
|
10796
|
-
computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
|
|
10797
|
-
computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight
|
|
10240
|
+
skillsDirectories: config.resolvedSkillsDirectories
|
|
10798
10241
|
});
|
|
10799
10242
|
if (session.config?.role === "orchestrator") {
|
|
10800
10243
|
const baseUrl = `http://127.0.0.1:${config.server?.port ?? 3141}`;
|
|
@@ -11032,14 +10475,10 @@ ${personality.trim()}`;
|
|
|
11032
10475
|
});
|
|
11033
10476
|
}
|
|
11034
10477
|
};
|
|
11035
|
-
const taskSessionConfig = this.session.config || {};
|
|
11036
10478
|
const taskTools = await createTools({
|
|
11037
10479
|
sessionId: this.session.id,
|
|
11038
10480
|
workingDirectory: this.session.workingDirectory,
|
|
11039
10481
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
11040
|
-
enableComputerUse: taskSessionConfig.computerUseEnabled === true,
|
|
11041
|
-
computerUseDisplayWidth: taskSessionConfig.computerUseDisplayWidth,
|
|
11042
|
-
computerUseDisplayHeight: taskSessionConfig.computerUseDisplayHeight,
|
|
11043
10482
|
onBashProgress: bashProgressHandler,
|
|
11044
10483
|
onWriteFileProgress: (progress) => {
|
|
11045
10484
|
options.onToolProgress?.({ toolName: "write_file", data: progress });
|
|
@@ -11387,11 +10826,11 @@ ${p.text}` : p.text;
|
|
|
11387
10826
|
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
11388
10827
|
if (!isRemoteConfigured2()) return [];
|
|
11389
10828
|
const { readFile: readFile12 } = await import("fs/promises");
|
|
11390
|
-
const { join:
|
|
10829
|
+
const { join: join17, basename: basename6 } = await import("path");
|
|
11391
10830
|
const urls = [];
|
|
11392
10831
|
for (const filePath of filePaths) {
|
|
11393
10832
|
try {
|
|
11394
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
10833
|
+
const fullPath = filePath.startsWith("/") ? filePath : join17(this.session.workingDirectory, filePath);
|
|
11395
10834
|
const fileName = basename6(fullPath);
|
|
11396
10835
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
11397
10836
|
const mimeMap = {
|
|
@@ -11449,11 +10888,11 @@ ${p.text}` : p.text;
|
|
|
11449
10888
|
wrappedTools[name] = originalTool;
|
|
11450
10889
|
continue;
|
|
11451
10890
|
}
|
|
11452
|
-
wrappedTools[name] =
|
|
10891
|
+
wrappedTools[name] = tool14({
|
|
11453
10892
|
description: originalTool.description || "",
|
|
11454
|
-
inputSchema: originalTool.inputSchema ||
|
|
10893
|
+
inputSchema: originalTool.inputSchema || z15.object({}),
|
|
11455
10894
|
execute: async (input, toolOptions) => {
|
|
11456
|
-
const toolCallId = toolOptions.toolCallId ||
|
|
10895
|
+
const toolCallId = toolOptions.toolCallId || nanoid8();
|
|
11457
10896
|
const execution = toolExecutionQueries.create({
|
|
11458
10897
|
sessionId: this.session.id,
|
|
11459
10898
|
toolName: name,
|
|
@@ -11596,19 +11035,19 @@ var init_session_lock = __esm({
|
|
|
11596
11035
|
});
|
|
11597
11036
|
|
|
11598
11037
|
// src/orchestrator/webhook-events.ts
|
|
11599
|
-
import { existsSync as
|
|
11600
|
-
import { dirname as dirname6, join as
|
|
11601
|
-
import { nanoid as
|
|
11038
|
+
import { existsSync as existsSync17, readFileSync as readFileSync8, appendFileSync as appendFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync6 } from "fs";
|
|
11039
|
+
import { dirname as dirname6, join as join11 } from "path";
|
|
11040
|
+
import { nanoid as nanoid9 } from "nanoid";
|
|
11602
11041
|
function logFilePath() {
|
|
11603
|
-
return
|
|
11042
|
+
return join11(getAppDataDirectory(), "webhook-events.jsonl");
|
|
11604
11043
|
}
|
|
11605
11044
|
function ensureLoaded() {
|
|
11606
11045
|
if (cache !== null) return cache;
|
|
11607
11046
|
cache = [];
|
|
11608
11047
|
try {
|
|
11609
11048
|
const p = logFilePath();
|
|
11610
|
-
if (!
|
|
11611
|
-
const lines =
|
|
11049
|
+
if (!existsSync17(p)) return cache;
|
|
11050
|
+
const lines = readFileSync8(p, "utf-8").split("\n").filter(Boolean);
|
|
11612
11051
|
for (const line of lines) {
|
|
11613
11052
|
try {
|
|
11614
11053
|
cache.push(JSON.parse(line));
|
|
@@ -11632,14 +11071,14 @@ function appendEvent(ev) {
|
|
|
11632
11071
|
if (list.length > MAX_EVENTS) list.shift();
|
|
11633
11072
|
try {
|
|
11634
11073
|
const p = logFilePath();
|
|
11635
|
-
|
|
11074
|
+
mkdirSync6(dirname6(p), { recursive: true });
|
|
11636
11075
|
appendFileSync3(p, JSON.stringify(ev) + "\n");
|
|
11637
11076
|
} catch {
|
|
11638
11077
|
}
|
|
11639
11078
|
}
|
|
11640
11079
|
function recordEvent(ev) {
|
|
11641
11080
|
const full = {
|
|
11642
|
-
id: ev.id ??
|
|
11081
|
+
id: ev.id ?? nanoid9(),
|
|
11643
11082
|
ts: ev.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
11644
11083
|
source: ev.source,
|
|
11645
11084
|
status: ev.status,
|
|
@@ -11663,7 +11102,7 @@ function updateEvent(id, patch) {
|
|
|
11663
11102
|
list[i] = { ...list[i], ...patch };
|
|
11664
11103
|
try {
|
|
11665
11104
|
const p = logFilePath();
|
|
11666
|
-
|
|
11105
|
+
mkdirSync6(dirname6(p), { recursive: true });
|
|
11667
11106
|
writeFileSync3(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
11668
11107
|
} catch {
|
|
11669
11108
|
}
|
|
@@ -12038,6 +11477,148 @@ var init_api = __esm({
|
|
|
12038
11477
|
}
|
|
12039
11478
|
});
|
|
12040
11479
|
|
|
11480
|
+
// src/utils/desktop-permissions.ts
|
|
11481
|
+
var desktop_permissions_exports = {};
|
|
11482
|
+
__export(desktop_permissions_exports, {
|
|
11483
|
+
detectScreenSize: () => detectScreenSize,
|
|
11484
|
+
getResponsibleAppName: () => getResponsibleAppName,
|
|
11485
|
+
hasAccessibilityPermissions: () => hasAccessibilityPermissions,
|
|
11486
|
+
hasScreenRecordingPermissions: () => hasScreenRecordingPermissions,
|
|
11487
|
+
isCliclickInstalled: () => isCliclickInstalled,
|
|
11488
|
+
isMacOs: () => isMacOs,
|
|
11489
|
+
openSystemSettings: () => openSystemSettings
|
|
11490
|
+
});
|
|
11491
|
+
import { exec as exec7 } from "child_process";
|
|
11492
|
+
import { promisify as promisify7 } from "util";
|
|
11493
|
+
function isMacOs() {
|
|
11494
|
+
return process.platform === "darwin";
|
|
11495
|
+
}
|
|
11496
|
+
async function isCliclickInstalled() {
|
|
11497
|
+
try {
|
|
11498
|
+
await execAsync7("command -v cliclick", { timeout: 2e3 });
|
|
11499
|
+
return true;
|
|
11500
|
+
} catch {
|
|
11501
|
+
return false;
|
|
11502
|
+
}
|
|
11503
|
+
}
|
|
11504
|
+
async function runJxa(script) {
|
|
11505
|
+
try {
|
|
11506
|
+
const escaped = script.replace(/'/g, `'\\''`);
|
|
11507
|
+
const { stdout } = await execAsync7(`osascript -l JavaScript -e '${escaped}'`, {
|
|
11508
|
+
timeout: 5e3
|
|
11509
|
+
});
|
|
11510
|
+
return JSON.parse(stdout.trim());
|
|
11511
|
+
} catch {
|
|
11512
|
+
return null;
|
|
11513
|
+
}
|
|
11514
|
+
}
|
|
11515
|
+
async function hasAccessibilityPermissions() {
|
|
11516
|
+
try {
|
|
11517
|
+
const { stderr } = await execAsync7("cliclick p:.", { timeout: 3e3 });
|
|
11518
|
+
if (stderr && ACCESSIBILITY_DENIED_RE.test(stderr)) {
|
|
11519
|
+
return { ok: false, error: stderr.trim().split("\n")[0] };
|
|
11520
|
+
}
|
|
11521
|
+
return { ok: true };
|
|
11522
|
+
} catch (err) {
|
|
11523
|
+
return { ok: false, error: err?.message || String(err) };
|
|
11524
|
+
}
|
|
11525
|
+
}
|
|
11526
|
+
async function hasScreenRecordingPermissions() {
|
|
11527
|
+
const result = await runJxa(
|
|
11528
|
+
`ObjC.import("Cocoa");
|
|
11529
|
+
ObjC.import("CoreGraphics");
|
|
11530
|
+
ObjC.bindFunction("CGPreflightScreenCaptureAccess", ["bool", []]);
|
|
11531
|
+
JSON.stringify({ hasAccess: !!$.CGPreflightScreenCaptureAccess() });`
|
|
11532
|
+
);
|
|
11533
|
+
return result?.hasAccess ?? false;
|
|
11534
|
+
}
|
|
11535
|
+
async function openSystemSettings(pane) {
|
|
11536
|
+
const url = pane === "accessibility" ? "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility" : "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
|
|
11537
|
+
try {
|
|
11538
|
+
await execAsync7(`open '${url}'`, { timeout: 3e3 });
|
|
11539
|
+
} catch {
|
|
11540
|
+
}
|
|
11541
|
+
}
|
|
11542
|
+
async function detectScreenSize() {
|
|
11543
|
+
try {
|
|
11544
|
+
const { stdout } = await execAsync7("system_profiler SPDisplaysDataType -json", {
|
|
11545
|
+
timeout: 5e3,
|
|
11546
|
+
maxBuffer: 4 * 1024 * 1024
|
|
11547
|
+
});
|
|
11548
|
+
const data = JSON.parse(stdout);
|
|
11549
|
+
const cards = data.SPDisplaysDataType ?? [];
|
|
11550
|
+
const all = cards.flatMap((c) => c.spdisplays_ndrvs ?? []);
|
|
11551
|
+
if (all.length === 0) return null;
|
|
11552
|
+
const primary = all.find((d) => d.spdisplays_main === "spdisplays_yes") ?? all[0];
|
|
11553
|
+
const pixels = parseDim(primary._spdisplays_pixels) ?? parseDim(primary._spdisplays_resolution);
|
|
11554
|
+
const points = parseDim(primary.spdisplays_resolution) ?? pixels;
|
|
11555
|
+
if (!points) return null;
|
|
11556
|
+
return {
|
|
11557
|
+
width: points.w,
|
|
11558
|
+
height: points.h,
|
|
11559
|
+
pixelWidth: pixels?.w,
|
|
11560
|
+
pixelHeight: pixels?.h,
|
|
11561
|
+
displayName: primary._name
|
|
11562
|
+
};
|
|
11563
|
+
} catch {
|
|
11564
|
+
return null;
|
|
11565
|
+
}
|
|
11566
|
+
}
|
|
11567
|
+
function parseDim(s) {
|
|
11568
|
+
if (!s) return null;
|
|
11569
|
+
const m = s.match(/(\d+)\s*x\s*(\d+)/i);
|
|
11570
|
+
if (!m) return null;
|
|
11571
|
+
const w = parseInt(m[1], 10);
|
|
11572
|
+
const h = parseInt(m[2], 10);
|
|
11573
|
+
if (!Number.isFinite(w) || !Number.isFinite(h)) return null;
|
|
11574
|
+
return { w, h };
|
|
11575
|
+
}
|
|
11576
|
+
async function getResponsibleAppName() {
|
|
11577
|
+
const termProgram = process.env.TERM_PROGRAM;
|
|
11578
|
+
if (termProgram) {
|
|
11579
|
+
const pretty = TERM_PROGRAM_NAMES[termProgram] ?? termProgram;
|
|
11580
|
+
return pretty;
|
|
11581
|
+
}
|
|
11582
|
+
try {
|
|
11583
|
+
let pid = process.ppid;
|
|
11584
|
+
for (let i = 0; i < 12 && pid && pid !== 1; i++) {
|
|
11585
|
+
const { stdout } = await execAsync7(`ps -o ppid=,comm= -p ${pid}`, { timeout: 1e3 });
|
|
11586
|
+
const trimmed = stdout.trim();
|
|
11587
|
+
if (!trimmed) break;
|
|
11588
|
+
const m = trimmed.match(/^\s*(\d+)\s+(.+)$/);
|
|
11589
|
+
if (!m) break;
|
|
11590
|
+
const parentPid = parseInt(m[1], 10);
|
|
11591
|
+
const comm = m[2];
|
|
11592
|
+
const bundle = comm.match(/\/([^/]+)\.app\//);
|
|
11593
|
+
if (bundle) return bundle[1];
|
|
11594
|
+
pid = parentPid;
|
|
11595
|
+
}
|
|
11596
|
+
} catch {
|
|
11597
|
+
}
|
|
11598
|
+
return "the terminal app you launched the agent from";
|
|
11599
|
+
}
|
|
11600
|
+
var execAsync7, ACCESSIBILITY_DENIED_RE, TERM_PROGRAM_NAMES;
|
|
11601
|
+
var init_desktop_permissions = __esm({
|
|
11602
|
+
"src/utils/desktop-permissions.ts"() {
|
|
11603
|
+
"use strict";
|
|
11604
|
+
execAsync7 = promisify7(exec7);
|
|
11605
|
+
ACCESSIBILITY_DENIED_RE = /(not allowed|not trusted|accessibility (privilege|client)|control your computer|privilege.{0,20}required|enable.{0,20}accessibility)/i;
|
|
11606
|
+
TERM_PROGRAM_NAMES = {
|
|
11607
|
+
Apple_Terminal: "Terminal",
|
|
11608
|
+
"iTerm.app": "iTerm",
|
|
11609
|
+
iTerm: "iTerm",
|
|
11610
|
+
vscode: "Visual Studio Code (or Cursor, if that's what you're using)",
|
|
11611
|
+
WarpTerminal: "Warp",
|
|
11612
|
+
ghostty: "Ghostty",
|
|
11613
|
+
Hyper: "Hyper",
|
|
11614
|
+
Tabby: "Tabby",
|
|
11615
|
+
Alacritty: "Alacritty",
|
|
11616
|
+
WezTerm: "WezTerm",
|
|
11617
|
+
kitty: "kitty"
|
|
11618
|
+
};
|
|
11619
|
+
}
|
|
11620
|
+
});
|
|
11621
|
+
|
|
12041
11622
|
// src/cli.ts
|
|
12042
11623
|
import { Command } from "commander";
|
|
12043
11624
|
import chalk from "chalk";
|
|
@@ -12053,8 +11634,8 @@ import { Hono as Hono9 } from "hono";
|
|
|
12053
11634
|
import { serve } from "@hono/node-server";
|
|
12054
11635
|
import { cors } from "hono/cors";
|
|
12055
11636
|
import { logger } from "hono/logger";
|
|
12056
|
-
import { existsSync as
|
|
12057
|
-
import { resolve as resolve11, dirname as dirname8, join as
|
|
11637
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
11638
|
+
import { resolve as resolve11, dirname as dirname8, join as join15 } from "path";
|
|
12058
11639
|
import { spawn as spawn2 } from "child_process";
|
|
12059
11640
|
import { createServer as createNetServer } from "net";
|
|
12060
11641
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -12067,11 +11648,11 @@ init_tmux();
|
|
|
12067
11648
|
init_checkpoints();
|
|
12068
11649
|
import { Hono } from "hono";
|
|
12069
11650
|
import { zValidator } from "@hono/zod-validator";
|
|
12070
|
-
import { z as
|
|
12071
|
-
import { existsSync as
|
|
11651
|
+
import { z as z16 } from "zod";
|
|
11652
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync7, writeFileSync as writeFileSync4, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
12072
11653
|
import { readdir as readdir6 } from "fs/promises";
|
|
12073
|
-
import { join as
|
|
12074
|
-
import { nanoid as
|
|
11654
|
+
import { join as join12, basename as basename5, extname as extname8, relative as relative9 } from "path";
|
|
11655
|
+
import { nanoid as nanoid10 } from "nanoid";
|
|
12075
11656
|
|
|
12076
11657
|
// src/tasks/agent-status.ts
|
|
12077
11658
|
init_questions();
|
|
@@ -12129,22 +11710,22 @@ function cleanupPendingInputs() {
|
|
|
12129
11710
|
}
|
|
12130
11711
|
}
|
|
12131
11712
|
}
|
|
12132
|
-
var createSessionSchema =
|
|
12133
|
-
name:
|
|
12134
|
-
workingDirectory:
|
|
12135
|
-
model:
|
|
12136
|
-
toolApprovals:
|
|
12137
|
-
// Optional full session-config passthrough (
|
|
12138
|
-
config:
|
|
12139
|
-
role:
|
|
11713
|
+
var createSessionSchema = z16.object({
|
|
11714
|
+
name: z16.string().optional(),
|
|
11715
|
+
workingDirectory: z16.string().optional(),
|
|
11716
|
+
model: z16.string().optional(),
|
|
11717
|
+
toolApprovals: z16.record(z16.string(), z16.boolean()).optional(),
|
|
11718
|
+
// Optional full session-config passthrough (role, persona, etc.)
|
|
11719
|
+
config: z16.record(z16.string(), z16.unknown()).optional(),
|
|
11720
|
+
role: z16.enum(["orchestrator", "worker", "chat"]).optional()
|
|
12140
11721
|
});
|
|
12141
|
-
var paginationQuerySchema =
|
|
12142
|
-
limit:
|
|
12143
|
-
offset:
|
|
12144
|
-
role:
|
|
11722
|
+
var paginationQuerySchema = z16.object({
|
|
11723
|
+
limit: z16.string().optional(),
|
|
11724
|
+
offset: z16.string().optional(),
|
|
11725
|
+
role: z16.enum(["orchestrator", "worker", "chat", "all"]).optional()
|
|
12145
11726
|
});
|
|
12146
|
-
var messagesQuerySchema =
|
|
12147
|
-
limit:
|
|
11727
|
+
var messagesQuerySchema = z16.object({
|
|
11728
|
+
limit: z16.string().optional()
|
|
12148
11729
|
});
|
|
12149
11730
|
sessions2.get(
|
|
12150
11731
|
"/",
|
|
@@ -12213,15 +11794,11 @@ sessions2.post(
|
|
|
12213
11794
|
async (c) => {
|
|
12214
11795
|
const body = c.req.valid("json");
|
|
12215
11796
|
const config = getConfig();
|
|
12216
|
-
const cuDefault = process.env.SPARKECODER_COMPUTER_USE === "1";
|
|
12217
11797
|
const baseConfig = body.config || {};
|
|
12218
11798
|
const mergedConfig = {
|
|
12219
11799
|
...baseConfig,
|
|
12220
11800
|
...body.toolApprovals ? { toolApprovals: body.toolApprovals } : {},
|
|
12221
|
-
...body.role ? { role: body.role } : {}
|
|
12222
|
-
// Turn on computer use by default if the server was launched with --enable-computer-use,
|
|
12223
|
-
// unless the client explicitly provided a value.
|
|
12224
|
-
...cuDefault && baseConfig.computerUseEnabled === void 0 ? { computerUseEnabled: true } : {}
|
|
11801
|
+
...body.role ? { role: body.role } : {}
|
|
12225
11802
|
};
|
|
12226
11803
|
const agent = await Agent.create({
|
|
12227
11804
|
name: body.name,
|
|
@@ -12398,10 +11975,10 @@ sessions2.get("/:id/tools", async (c) => {
|
|
|
12398
11975
|
count: executions.length
|
|
12399
11976
|
});
|
|
12400
11977
|
});
|
|
12401
|
-
var updateSessionSchema =
|
|
12402
|
-
model:
|
|
12403
|
-
name:
|
|
12404
|
-
toolApprovals:
|
|
11978
|
+
var updateSessionSchema = z16.object({
|
|
11979
|
+
model: z16.string().optional(),
|
|
11980
|
+
name: z16.string().optional(),
|
|
11981
|
+
toolApprovals: z16.record(z16.string(), z16.boolean()).optional()
|
|
12405
11982
|
});
|
|
12406
11983
|
sessions2.patch(
|
|
12407
11984
|
"/:id",
|
|
@@ -12471,10 +12048,10 @@ sessions2.post("/:id/clear", async (c) => {
|
|
|
12471
12048
|
await agent.clearContext();
|
|
12472
12049
|
return c.json({ success: true, sessionId: id });
|
|
12473
12050
|
});
|
|
12474
|
-
var injectMessageSchema =
|
|
12475
|
-
text:
|
|
12476
|
-
source:
|
|
12477
|
-
force:
|
|
12051
|
+
var injectMessageSchema = z16.object({
|
|
12052
|
+
text: z16.string().min(1),
|
|
12053
|
+
source: z16.enum(["orchestrator", "user", "system"]).optional(),
|
|
12054
|
+
force: z16.boolean().optional()
|
|
12478
12055
|
});
|
|
12479
12056
|
sessions2.post(
|
|
12480
12057
|
"/:id/messages",
|
|
@@ -12488,8 +12065,8 @@ sessions2.post(
|
|
|
12488
12065
|
return c.json({ success: true, sessionId: id, queued: true, force });
|
|
12489
12066
|
}
|
|
12490
12067
|
);
|
|
12491
|
-
var pendingInputSchema =
|
|
12492
|
-
text:
|
|
12068
|
+
var pendingInputSchema = z16.object({
|
|
12069
|
+
text: z16.string()
|
|
12493
12070
|
});
|
|
12494
12071
|
sessions2.post(
|
|
12495
12072
|
"/:id/pending-input",
|
|
@@ -12520,13 +12097,13 @@ sessions2.get("/:id/pending-input", async (c) => {
|
|
|
12520
12097
|
createdAt: pending.createdAt.toISOString()
|
|
12521
12098
|
});
|
|
12522
12099
|
});
|
|
12523
|
-
var devtoolsContextSchema =
|
|
12524
|
-
url:
|
|
12525
|
-
path:
|
|
12526
|
-
pageName:
|
|
12527
|
-
screenWidth:
|
|
12528
|
-
screenHeight:
|
|
12529
|
-
devicePixelRatio:
|
|
12100
|
+
var devtoolsContextSchema = z16.object({
|
|
12101
|
+
url: z16.string(),
|
|
12102
|
+
path: z16.string(),
|
|
12103
|
+
pageName: z16.string().optional(),
|
|
12104
|
+
screenWidth: z16.number().optional(),
|
|
12105
|
+
screenHeight: z16.number().optional(),
|
|
12106
|
+
devicePixelRatio: z16.number().optional()
|
|
12530
12107
|
});
|
|
12531
12108
|
sessions2.post(
|
|
12532
12109
|
"/:id/devtools-context",
|
|
@@ -12712,12 +12289,12 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
|
|
|
12712
12289
|
});
|
|
12713
12290
|
function getAttachmentsDir(sessionId) {
|
|
12714
12291
|
const appDataDir = getAppDataDirectory();
|
|
12715
|
-
return
|
|
12292
|
+
return join12(appDataDir, "attachments", sessionId);
|
|
12716
12293
|
}
|
|
12717
12294
|
function ensureAttachmentsDir(sessionId) {
|
|
12718
12295
|
const dir = getAttachmentsDir(sessionId);
|
|
12719
|
-
if (!
|
|
12720
|
-
|
|
12296
|
+
if (!existsSync18(dir)) {
|
|
12297
|
+
mkdirSync7(dir, { recursive: true });
|
|
12721
12298
|
}
|
|
12722
12299
|
return dir;
|
|
12723
12300
|
}
|
|
@@ -12728,12 +12305,12 @@ sessions2.get("/:id/attachments", async (c) => {
|
|
|
12728
12305
|
return c.json({ error: "Session not found" }, 404);
|
|
12729
12306
|
}
|
|
12730
12307
|
const dir = getAttachmentsDir(sessionId);
|
|
12731
|
-
if (!
|
|
12308
|
+
if (!existsSync18(dir)) {
|
|
12732
12309
|
return c.json({ sessionId, attachments: [], count: 0 });
|
|
12733
12310
|
}
|
|
12734
12311
|
const files = readdirSync3(dir);
|
|
12735
12312
|
const attachments = files.map((filename) => {
|
|
12736
|
-
const filePath =
|
|
12313
|
+
const filePath = join12(dir, filename);
|
|
12737
12314
|
const stats = statSync2(filePath);
|
|
12738
12315
|
return {
|
|
12739
12316
|
id: filename.split("_")[0],
|
|
@@ -12765,10 +12342,10 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
12765
12342
|
return c.json({ error: "No file provided" }, 400);
|
|
12766
12343
|
}
|
|
12767
12344
|
const dir = ensureAttachmentsDir(sessionId);
|
|
12768
|
-
const id =
|
|
12345
|
+
const id = nanoid10(10);
|
|
12769
12346
|
const ext = extname8(file.name) || "";
|
|
12770
12347
|
const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
12771
|
-
const filePath =
|
|
12348
|
+
const filePath = join12(dir, safeFilename);
|
|
12772
12349
|
const arrayBuffer = await file.arrayBuffer();
|
|
12773
12350
|
writeFileSync4(filePath, Buffer.from(arrayBuffer));
|
|
12774
12351
|
return c.json({
|
|
@@ -12791,10 +12368,10 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
12791
12368
|
return c.json({ error: "Missing filename or data" }, 400);
|
|
12792
12369
|
}
|
|
12793
12370
|
const dir = ensureAttachmentsDir(sessionId);
|
|
12794
|
-
const id =
|
|
12371
|
+
const id = nanoid10(10);
|
|
12795
12372
|
const ext = extname8(body.filename) || "";
|
|
12796
12373
|
const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
12797
|
-
const filePath =
|
|
12374
|
+
const filePath = join12(dir, safeFilename);
|
|
12798
12375
|
let base64Data = body.data;
|
|
12799
12376
|
if (base64Data.includes(",")) {
|
|
12800
12377
|
base64Data = base64Data.split(",")[1];
|
|
@@ -12823,7 +12400,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
12823
12400
|
return c.json({ error: "Session not found" }, 404);
|
|
12824
12401
|
}
|
|
12825
12402
|
const dir = getAttachmentsDir(sessionId);
|
|
12826
|
-
if (!
|
|
12403
|
+
if (!existsSync18(dir)) {
|
|
12827
12404
|
return c.json({ error: "Attachment not found" }, 404);
|
|
12828
12405
|
}
|
|
12829
12406
|
const files = readdirSync3(dir);
|
|
@@ -12831,14 +12408,14 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
12831
12408
|
if (!file) {
|
|
12832
12409
|
return c.json({ error: "Attachment not found" }, 404);
|
|
12833
12410
|
}
|
|
12834
|
-
const filePath =
|
|
12835
|
-
|
|
12411
|
+
const filePath = join12(dir, file);
|
|
12412
|
+
unlinkSync2(filePath);
|
|
12836
12413
|
return c.json({ success: true, id: attachmentId });
|
|
12837
12414
|
});
|
|
12838
|
-
var filesQuerySchema =
|
|
12839
|
-
query:
|
|
12415
|
+
var filesQuerySchema = z16.object({
|
|
12416
|
+
query: z16.string().optional(),
|
|
12840
12417
|
// Filter query (e.g., "src/com" to match "src/components")
|
|
12841
|
-
limit:
|
|
12418
|
+
limit: z16.string().optional()
|
|
12842
12419
|
// Max results (default 50)
|
|
12843
12420
|
});
|
|
12844
12421
|
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
@@ -12914,7 +12491,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
12914
12491
|
const entries = await readdir6(currentDir, { withFileTypes: true });
|
|
12915
12492
|
for (const entry2 of entries) {
|
|
12916
12493
|
if (results.length >= limit * 2) break;
|
|
12917
|
-
const fullPath =
|
|
12494
|
+
const fullPath = join12(currentDir, entry2.name);
|
|
12918
12495
|
const relativePath = relative9(baseDir, fullPath);
|
|
12919
12496
|
if (entry2.isDirectory() && IGNORED_DIRECTORIES.has(entry2.name)) {
|
|
12920
12497
|
continue;
|
|
@@ -12962,7 +12539,7 @@ sessions2.get(
|
|
|
12962
12539
|
return c.json({ error: "Session not found" }, 404);
|
|
12963
12540
|
}
|
|
12964
12541
|
const workingDirectory = session.workingDirectory;
|
|
12965
|
-
if (!
|
|
12542
|
+
if (!existsSync18(workingDirectory)) {
|
|
12966
12543
|
return c.json({
|
|
12967
12544
|
sessionId,
|
|
12968
12545
|
workingDirectory,
|
|
@@ -13075,9 +12652,9 @@ init_session_lock();
|
|
|
13075
12652
|
init_config();
|
|
13076
12653
|
import { Hono as Hono2 } from "hono";
|
|
13077
12654
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
13078
|
-
import { z as
|
|
13079
|
-
import { existsSync as
|
|
13080
|
-
import { join as
|
|
12655
|
+
import { z as z17 } from "zod";
|
|
12656
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
12657
|
+
import { join as join13 } from "path";
|
|
13081
12658
|
|
|
13082
12659
|
// src/server/resumable-stream.ts
|
|
13083
12660
|
import { createResumableStreamContext } from "resumable-stream/generic";
|
|
@@ -13164,7 +12741,7 @@ var streamContext = createResumableStreamContext({
|
|
|
13164
12741
|
|
|
13165
12742
|
// src/server/routes/agents.ts
|
|
13166
12743
|
init_checkpoints();
|
|
13167
|
-
import { nanoid as
|
|
12744
|
+
import { nanoid as nanoid11 } from "nanoid";
|
|
13168
12745
|
init_stream_proxy();
|
|
13169
12746
|
init_recorder();
|
|
13170
12747
|
init_remote();
|
|
@@ -13256,40 +12833,40 @@ function enrichPromptWithDevtoolsContext(sessionId, prompt) {
|
|
|
13256
12833
|
${prompt}`;
|
|
13257
12834
|
}
|
|
13258
12835
|
var agents = new Hono2();
|
|
13259
|
-
var attachmentSchema =
|
|
13260
|
-
type:
|
|
13261
|
-
data:
|
|
12836
|
+
var attachmentSchema = z17.object({
|
|
12837
|
+
type: z17.enum(["image", "file"]),
|
|
12838
|
+
data: z17.string(),
|
|
13262
12839
|
// base64 data URL or raw base64
|
|
13263
|
-
mediaType:
|
|
13264
|
-
filename:
|
|
12840
|
+
mediaType: z17.string().optional(),
|
|
12841
|
+
filename: z17.string().optional()
|
|
13265
12842
|
});
|
|
13266
|
-
var runPromptSchema =
|
|
13267
|
-
prompt:
|
|
12843
|
+
var runPromptSchema = z17.object({
|
|
12844
|
+
prompt: z17.string(),
|
|
13268
12845
|
// Can be empty if attachments are provided
|
|
13269
|
-
attachments:
|
|
12846
|
+
attachments: z17.array(attachmentSchema).optional()
|
|
13270
12847
|
}).refine(
|
|
13271
12848
|
(data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
|
|
13272
12849
|
{ message: "Either prompt or attachments must be provided" }
|
|
13273
12850
|
);
|
|
13274
|
-
var quickStartSchema =
|
|
13275
|
-
prompt:
|
|
13276
|
-
name:
|
|
13277
|
-
workingDirectory:
|
|
13278
|
-
model:
|
|
13279
|
-
toolApprovals:
|
|
12851
|
+
var quickStartSchema = z17.object({
|
|
12852
|
+
prompt: z17.string().min(1),
|
|
12853
|
+
name: z17.string().optional(),
|
|
12854
|
+
workingDirectory: z17.string().optional(),
|
|
12855
|
+
model: z17.string().optional(),
|
|
12856
|
+
toolApprovals: z17.record(z17.string(), z17.boolean()).optional()
|
|
13280
12857
|
});
|
|
13281
|
-
var rejectSchema =
|
|
13282
|
-
reason:
|
|
12858
|
+
var rejectSchema = z17.object({
|
|
12859
|
+
reason: z17.string().optional()
|
|
13283
12860
|
}).optional();
|
|
13284
12861
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
13285
12862
|
function getAttachmentsDirectory(sessionId) {
|
|
13286
12863
|
const appDataDir = getAppDataDirectory();
|
|
13287
|
-
return
|
|
12864
|
+
return join13(appDataDir, "attachments", sessionId);
|
|
13288
12865
|
}
|
|
13289
12866
|
async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
13290
12867
|
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
13291
|
-
if (!
|
|
13292
|
-
|
|
12868
|
+
if (!existsSync19(attachmentsDir)) {
|
|
12869
|
+
mkdirSync8(attachmentsDir, { recursive: true });
|
|
13293
12870
|
}
|
|
13294
12871
|
let filename = attachment.filename;
|
|
13295
12872
|
if (!filename) {
|
|
@@ -13307,7 +12884,7 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
|
13307
12884
|
attachment.mediaType = resized.mediaType;
|
|
13308
12885
|
attachment.data = buffer.toString("base64");
|
|
13309
12886
|
}
|
|
13310
|
-
const filePath =
|
|
12887
|
+
const filePath = join13(attachmentsDir, filename);
|
|
13311
12888
|
writeFileSync5(filePath, buffer);
|
|
13312
12889
|
return filePath;
|
|
13313
12890
|
}
|
|
@@ -13744,7 +13321,7 @@ ${prompt}` });
|
|
|
13744
13321
|
});
|
|
13745
13322
|
} catch {
|
|
13746
13323
|
}
|
|
13747
|
-
const streamId = `stream_${id}_${
|
|
13324
|
+
const streamId = `stream_${id}_${nanoid11(10)}`;
|
|
13748
13325
|
console.log(`[STREAM] Creating stream ${streamId} for session ${id}`);
|
|
13749
13326
|
await activeStreamQueries.create(id, streamId);
|
|
13750
13327
|
const stream = await streamContext.resumableStream(
|
|
@@ -13949,7 +13526,7 @@ agents.post(
|
|
|
13949
13526
|
});
|
|
13950
13527
|
const session = agent.getSession();
|
|
13951
13528
|
const enrichedPrompt = enrichPromptWithDevtoolsContext(session.id, body.prompt);
|
|
13952
|
-
const streamId = `stream_${session.id}_${
|
|
13529
|
+
const streamId = `stream_${session.id}_${nanoid11(10)}`;
|
|
13953
13530
|
await createCheckpoint(session.id, session.workingDirectory, 0);
|
|
13954
13531
|
await activeStreamQueries.create(session.id, streamId);
|
|
13955
13532
|
const createQuickStreamProducer = () => {
|
|
@@ -14216,23 +13793,23 @@ agents.post(
|
|
|
14216
13793
|
});
|
|
14217
13794
|
}
|
|
14218
13795
|
);
|
|
14219
|
-
var browserInputSchema =
|
|
14220
|
-
type:
|
|
14221
|
-
eventType:
|
|
14222
|
-
x:
|
|
14223
|
-
y:
|
|
14224
|
-
button:
|
|
14225
|
-
clickCount:
|
|
14226
|
-
deltaX:
|
|
14227
|
-
deltaY:
|
|
14228
|
-
key:
|
|
14229
|
-
code:
|
|
14230
|
-
text:
|
|
14231
|
-
modifiers:
|
|
14232
|
-
touchPoints:
|
|
14233
|
-
x:
|
|
14234
|
-
y:
|
|
14235
|
-
id:
|
|
13796
|
+
var browserInputSchema = z17.object({
|
|
13797
|
+
type: z17.enum(["input_mouse", "input_keyboard", "input_touch"]),
|
|
13798
|
+
eventType: z17.string(),
|
|
13799
|
+
x: z17.number().optional(),
|
|
13800
|
+
y: z17.number().optional(),
|
|
13801
|
+
button: z17.string().optional(),
|
|
13802
|
+
clickCount: z17.number().optional(),
|
|
13803
|
+
deltaX: z17.number().optional(),
|
|
13804
|
+
deltaY: z17.number().optional(),
|
|
13805
|
+
key: z17.string().optional(),
|
|
13806
|
+
code: z17.string().optional(),
|
|
13807
|
+
text: z17.string().optional(),
|
|
13808
|
+
modifiers: z17.number().optional(),
|
|
13809
|
+
touchPoints: z17.array(z17.object({
|
|
13810
|
+
x: z17.number(),
|
|
13811
|
+
y: z17.number(),
|
|
13812
|
+
id: z17.number().optional()
|
|
14236
13813
|
})).optional()
|
|
14237
13814
|
});
|
|
14238
13815
|
agents.post(
|
|
@@ -14267,27 +13844,27 @@ agents.get("/:id/browser-stream", async (c) => {
|
|
|
14267
13844
|
init_config();
|
|
14268
13845
|
import { Hono as Hono3 } from "hono";
|
|
14269
13846
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
14270
|
-
import { z as
|
|
14271
|
-
import { readFileSync as
|
|
13847
|
+
import { z as z18 } from "zod";
|
|
13848
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
14272
13849
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
14273
|
-
import { dirname as dirname7, join as
|
|
13850
|
+
import { dirname as dirname7, join as join14 } from "path";
|
|
14274
13851
|
var __filename = fileURLToPath3(import.meta.url);
|
|
14275
13852
|
var __dirname = dirname7(__filename);
|
|
14276
13853
|
var possiblePaths = [
|
|
14277
|
-
|
|
13854
|
+
join14(__dirname, "../package.json"),
|
|
14278
13855
|
// From dist/server -> dist/../package.json
|
|
14279
|
-
|
|
13856
|
+
join14(__dirname, "../../package.json"),
|
|
14280
13857
|
// From dist/server (if nested differently)
|
|
14281
|
-
|
|
13858
|
+
join14(__dirname, "../../../package.json"),
|
|
14282
13859
|
// From src/server/routes (development)
|
|
14283
|
-
|
|
13860
|
+
join14(process.cwd(), "package.json")
|
|
14284
13861
|
// From current working directory
|
|
14285
13862
|
];
|
|
14286
13863
|
var currentVersion = "0.0.0";
|
|
14287
13864
|
var packageName = "sparkecoder";
|
|
14288
13865
|
for (const packageJsonPath of possiblePaths) {
|
|
14289
13866
|
try {
|
|
14290
|
-
const packageJson = JSON.parse(
|
|
13867
|
+
const packageJson = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
|
|
14291
13868
|
if (packageJson.name === "sparkecoder") {
|
|
14292
13869
|
currentVersion = packageJson.version || "0.0.0";
|
|
14293
13870
|
packageName = packageJson.name || "sparkecoder";
|
|
@@ -14379,9 +13956,9 @@ health.get("/api-keys", async (c) => {
|
|
|
14379
13956
|
supportedProviders: SUPPORTED_PROVIDERS
|
|
14380
13957
|
});
|
|
14381
13958
|
});
|
|
14382
|
-
var setApiKeySchema =
|
|
14383
|
-
provider:
|
|
14384
|
-
apiKey:
|
|
13959
|
+
var setApiKeySchema = z18.object({
|
|
13960
|
+
provider: z18.string(),
|
|
13961
|
+
apiKey: z18.string().min(1)
|
|
14385
13962
|
});
|
|
14386
13963
|
health.post(
|
|
14387
13964
|
"/api-keys",
|
|
@@ -14422,12 +13999,12 @@ init_tmux();
|
|
|
14422
13999
|
init_db();
|
|
14423
14000
|
import { Hono as Hono4 } from "hono";
|
|
14424
14001
|
import { zValidator as zValidator4 } from "@hono/zod-validator";
|
|
14425
|
-
import { z as
|
|
14002
|
+
import { z as z19 } from "zod";
|
|
14426
14003
|
var terminals = new Hono4();
|
|
14427
|
-
var spawnSchema =
|
|
14428
|
-
command:
|
|
14429
|
-
cwd:
|
|
14430
|
-
name:
|
|
14004
|
+
var spawnSchema = z19.object({
|
|
14005
|
+
command: z19.string(),
|
|
14006
|
+
cwd: z19.string().optional(),
|
|
14007
|
+
name: z19.string().optional()
|
|
14431
14008
|
});
|
|
14432
14009
|
terminals.post(
|
|
14433
14010
|
"/:sessionId/terminals",
|
|
@@ -14508,8 +14085,8 @@ terminals.get("/:sessionId/terminals/:terminalId", async (c) => {
|
|
|
14508
14085
|
// We don't track exit codes in tmux mode
|
|
14509
14086
|
});
|
|
14510
14087
|
});
|
|
14511
|
-
var logsQuerySchema =
|
|
14512
|
-
tail:
|
|
14088
|
+
var logsQuerySchema = z19.object({
|
|
14089
|
+
tail: z19.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
|
|
14513
14090
|
});
|
|
14514
14091
|
terminals.get(
|
|
14515
14092
|
"/:sessionId/terminals/:terminalId/logs",
|
|
@@ -14533,8 +14110,8 @@ terminals.get(
|
|
|
14533
14110
|
});
|
|
14534
14111
|
}
|
|
14535
14112
|
);
|
|
14536
|
-
var killSchema =
|
|
14537
|
-
signal:
|
|
14113
|
+
var killSchema = z19.object({
|
|
14114
|
+
signal: z19.enum(["SIGTERM", "SIGKILL"]).optional()
|
|
14538
14115
|
});
|
|
14539
14116
|
terminals.post(
|
|
14540
14117
|
"/:sessionId/terminals/:terminalId/kill",
|
|
@@ -14548,8 +14125,8 @@ terminals.post(
|
|
|
14548
14125
|
return c.json({ success: true, message: "Terminal killed" });
|
|
14549
14126
|
}
|
|
14550
14127
|
);
|
|
14551
|
-
var writeSchema =
|
|
14552
|
-
input:
|
|
14128
|
+
var writeSchema = z19.object({
|
|
14129
|
+
input: z19.string()
|
|
14553
14130
|
});
|
|
14554
14131
|
terminals.post(
|
|
14555
14132
|
"/:sessionId/terminals/:terminalId/write",
|
|
@@ -14736,23 +14313,23 @@ init_agent();
|
|
|
14736
14313
|
init_config();
|
|
14737
14314
|
import { Hono as Hono5 } from "hono";
|
|
14738
14315
|
import { zValidator as zValidator5 } from "@hono/zod-validator";
|
|
14739
|
-
import { z as
|
|
14740
|
-
import { nanoid as
|
|
14316
|
+
import { z as z20 } from "zod";
|
|
14317
|
+
import { nanoid as nanoid12 } from "nanoid";
|
|
14741
14318
|
init_questions();
|
|
14742
14319
|
var tasks = new Hono5();
|
|
14743
14320
|
var taskAbortControllers = /* @__PURE__ */ new Map();
|
|
14744
|
-
var createTaskSchema =
|
|
14745
|
-
prompt:
|
|
14746
|
-
outputSchema:
|
|
14747
|
-
webhookUrl:
|
|
14748
|
-
model:
|
|
14749
|
-
workingDirectory:
|
|
14750
|
-
name:
|
|
14751
|
-
maxIterations:
|
|
14752
|
-
parentTaskId:
|
|
14321
|
+
var createTaskSchema = z20.object({
|
|
14322
|
+
prompt: z20.string().min(1),
|
|
14323
|
+
outputSchema: z20.record(z20.string(), z20.unknown()),
|
|
14324
|
+
webhookUrl: z20.string().url().optional(),
|
|
14325
|
+
model: z20.string().optional(),
|
|
14326
|
+
workingDirectory: z20.string().optional(),
|
|
14327
|
+
name: z20.string().optional(),
|
|
14328
|
+
maxIterations: z20.number().int().min(1).max(500).optional(),
|
|
14329
|
+
parentTaskId: z20.string().optional(),
|
|
14753
14330
|
/** When set, the spawning orchestrator's session id. Stamped on the
|
|
14754
14331
|
* worker's config so terminal events can wake the orchestrator. */
|
|
14755
|
-
orchestratorSessionId:
|
|
14332
|
+
orchestratorSessionId: z20.string().optional()
|
|
14756
14333
|
});
|
|
14757
14334
|
tasks.post(
|
|
14758
14335
|
"/",
|
|
@@ -14818,7 +14395,7 @@ tasks.post(
|
|
|
14818
14395
|
const taskId = agent.sessionId;
|
|
14819
14396
|
const abortController = new AbortController();
|
|
14820
14397
|
taskAbortControllers.set(taskId, abortController);
|
|
14821
|
-
const streamId = `stream_${taskId}_${
|
|
14398
|
+
const streamId = `stream_${taskId}_${nanoid12(10)}`;
|
|
14822
14399
|
await activeStreamQueries.create(taskId, streamId);
|
|
14823
14400
|
const taskStreamProducer = () => {
|
|
14824
14401
|
const { readable, writable } = new TransformStream();
|
|
@@ -14967,9 +14544,9 @@ tasks.post("/:id/cancel", async (c) => {
|
|
|
14967
14544
|
}
|
|
14968
14545
|
return c.json({ taskId: id, status: "failed", error: "Task cancelled by user" });
|
|
14969
14546
|
});
|
|
14970
|
-
var answerQuestionSchema =
|
|
14971
|
-
answer:
|
|
14972
|
-
answeredBy:
|
|
14547
|
+
var answerQuestionSchema = z20.object({
|
|
14548
|
+
answer: z20.string().min(1),
|
|
14549
|
+
answeredBy: z20.enum(["orchestrator", "user", "system"]).optional()
|
|
14973
14550
|
});
|
|
14974
14551
|
tasks.post(
|
|
14975
14552
|
"/:id/questions/:questionId/answer",
|
|
@@ -15098,7 +14675,8 @@ slack.post("/events", async (c) => {
|
|
|
15098
14675
|
updateEvent(auditId, { status: "dropped", dropReason: "duplicate_delivery" });
|
|
15099
14676
|
return c.json({ ok: true });
|
|
15100
14677
|
}
|
|
15101
|
-
const
|
|
14678
|
+
const self = await ensureSlackSelfIdentity();
|
|
14679
|
+
const { event: inbound, dropReason } = slackEventToInboundResult(ev, { self });
|
|
15102
14680
|
if (inbound) {
|
|
15103
14681
|
const isThreadReply = ev.type === "message" && ev.channel_type !== "im" && typeof ev.thread_ts === "string" && ev.thread_ts !== ev.ts;
|
|
15104
14682
|
if (isThreadReply) {
|
|
@@ -15253,7 +14831,7 @@ init_pool();
|
|
|
15253
14831
|
init_webhook_events();
|
|
15254
14832
|
import { Hono as Hono8 } from "hono";
|
|
15255
14833
|
import { zValidator as zValidator6 } from "@hono/zod-validator";
|
|
15256
|
-
import { z as
|
|
14834
|
+
import { z as z21 } from "zod";
|
|
15257
14835
|
var integrations = new Hono8();
|
|
15258
14836
|
var orchestratorRouter = new Hono8();
|
|
15259
14837
|
async function getOrchestratorId() {
|
|
@@ -15276,9 +14854,9 @@ orchestratorRouter.get("/", async (c) => {
|
|
|
15276
14854
|
});
|
|
15277
14855
|
orchestratorRouter.patch(
|
|
15278
14856
|
"/",
|
|
15279
|
-
zValidator6("json",
|
|
15280
|
-
name:
|
|
15281
|
-
personality:
|
|
14857
|
+
zValidator6("json", z21.object({
|
|
14858
|
+
name: z21.string().min(1).optional(),
|
|
14859
|
+
personality: z21.string().optional()
|
|
15282
14860
|
})),
|
|
15283
14861
|
async (c) => {
|
|
15284
14862
|
const id = await getOrchestratorId();
|
|
@@ -15354,15 +14932,15 @@ integrations.get("/", async (c) => {
|
|
|
15354
14932
|
}
|
|
15355
14933
|
});
|
|
15356
14934
|
});
|
|
15357
|
-
var slackConfigSchema =
|
|
15358
|
-
botToken:
|
|
15359
|
-
signingSecret:
|
|
15360
|
-
defaultOrchestratorName:
|
|
15361
|
-
allowedUsers:
|
|
15362
|
-
allowedChannels:
|
|
15363
|
-
allowDmsFromAnyone:
|
|
15364
|
-
deniedReplyEnabled:
|
|
15365
|
-
deniedReplyTemplate:
|
|
14935
|
+
var slackConfigSchema = z21.object({
|
|
14936
|
+
botToken: z21.string().optional(),
|
|
14937
|
+
signingSecret: z21.string().optional(),
|
|
14938
|
+
defaultOrchestratorName: z21.string().optional(),
|
|
14939
|
+
allowedUsers: z21.array(z21.string()).optional(),
|
|
14940
|
+
allowedChannels: z21.array(z21.string()).optional(),
|
|
14941
|
+
allowDmsFromAnyone: z21.boolean().optional(),
|
|
14942
|
+
deniedReplyEnabled: z21.boolean().optional(),
|
|
14943
|
+
deniedReplyTemplate: z21.string().optional()
|
|
15366
14944
|
});
|
|
15367
14945
|
integrations.post("/slack", zValidator6("json", slackConfigSchema), async (c) => {
|
|
15368
14946
|
const body = c.req.valid("json");
|
|
@@ -15391,11 +14969,11 @@ schedulesRouter.get("/", async (c) => {
|
|
|
15391
14969
|
});
|
|
15392
14970
|
schedulesRouter.post(
|
|
15393
14971
|
"/",
|
|
15394
|
-
zValidator6("json",
|
|
15395
|
-
name:
|
|
15396
|
-
cron:
|
|
15397
|
-
prompt:
|
|
15398
|
-
replyChannel:
|
|
14972
|
+
zValidator6("json", z21.object({
|
|
14973
|
+
name: z21.string().min(1),
|
|
14974
|
+
cron: z21.string().min(1),
|
|
14975
|
+
prompt: z21.string().min(1),
|
|
14976
|
+
replyChannel: z21.string().optional()
|
|
15399
14977
|
})),
|
|
15400
14978
|
async (c) => {
|
|
15401
14979
|
const orcId = await getOrchestratorId();
|
|
@@ -15406,12 +14984,12 @@ schedulesRouter.post(
|
|
|
15406
14984
|
);
|
|
15407
14985
|
schedulesRouter.patch(
|
|
15408
14986
|
"/:id",
|
|
15409
|
-
zValidator6("json",
|
|
15410
|
-
name:
|
|
15411
|
-
cron:
|
|
15412
|
-
prompt:
|
|
15413
|
-
enabled:
|
|
15414
|
-
replyChannel:
|
|
14987
|
+
zValidator6("json", z21.object({
|
|
14988
|
+
name: z21.string().optional(),
|
|
14989
|
+
cron: z21.string().optional(),
|
|
14990
|
+
prompt: z21.string().optional(),
|
|
14991
|
+
enabled: z21.boolean().optional(),
|
|
14992
|
+
replyChannel: z21.string().optional()
|
|
15415
14993
|
})),
|
|
15416
14994
|
async (c) => {
|
|
15417
14995
|
const orcId = await getOrchestratorId();
|
|
@@ -15439,10 +15017,10 @@ webhooksRouter.get("/", async (c) => {
|
|
|
15439
15017
|
});
|
|
15440
15018
|
webhooksRouter.post(
|
|
15441
15019
|
"/",
|
|
15442
|
-
zValidator6("json",
|
|
15443
|
-
name:
|
|
15444
|
-
wake:
|
|
15445
|
-
template:
|
|
15020
|
+
zValidator6("json", z21.object({
|
|
15021
|
+
name: z21.string().min(1),
|
|
15022
|
+
wake: z21.enum(["now", "next"]).optional(),
|
|
15023
|
+
template: z21.string().optional()
|
|
15446
15024
|
})),
|
|
15447
15025
|
async (c) => {
|
|
15448
15026
|
const orcId = await getOrchestratorId();
|
|
@@ -15453,11 +15031,11 @@ webhooksRouter.post(
|
|
|
15453
15031
|
);
|
|
15454
15032
|
webhooksRouter.patch(
|
|
15455
15033
|
"/:id",
|
|
15456
|
-
zValidator6("json",
|
|
15457
|
-
name:
|
|
15458
|
-
wake:
|
|
15459
|
-
template:
|
|
15460
|
-
rotateToken:
|
|
15034
|
+
zValidator6("json", z21.object({
|
|
15035
|
+
name: z21.string().optional(),
|
|
15036
|
+
wake: z21.enum(["now", "next"]).optional(),
|
|
15037
|
+
template: z21.string().optional(),
|
|
15038
|
+
rotateToken: z21.boolean().optional()
|
|
15461
15039
|
})),
|
|
15462
15040
|
async (c) => {
|
|
15463
15041
|
const orcId = await getOrchestratorId();
|
|
@@ -15474,22 +15052,22 @@ webhooksRouter.delete("/:id", async (c) => {
|
|
|
15474
15052
|
return c.json({ deleted: ok });
|
|
15475
15053
|
});
|
|
15476
15054
|
var mcpRouter = new Hono8();
|
|
15477
|
-
var mcpServerSchema =
|
|
15478
|
-
name:
|
|
15479
|
-
transport:
|
|
15480
|
-
url:
|
|
15481
|
-
headers:
|
|
15482
|
-
command:
|
|
15483
|
-
args:
|
|
15484
|
-
enabled:
|
|
15055
|
+
var mcpServerSchema = z21.object({
|
|
15056
|
+
name: z21.string().min(1),
|
|
15057
|
+
transport: z21.enum(["http", "sse", "stdio"]),
|
|
15058
|
+
url: z21.string().optional(),
|
|
15059
|
+
headers: z21.record(z21.string(), z21.string()).optional(),
|
|
15060
|
+
command: z21.string().optional(),
|
|
15061
|
+
args: z21.array(z21.string()).optional(),
|
|
15062
|
+
enabled: z21.boolean().optional()
|
|
15485
15063
|
});
|
|
15486
|
-
var mcpPatchSchema =
|
|
15487
|
-
name:
|
|
15488
|
-
url:
|
|
15489
|
-
headers:
|
|
15490
|
-
command:
|
|
15491
|
-
args:
|
|
15492
|
-
enabled:
|
|
15064
|
+
var mcpPatchSchema = z21.object({
|
|
15065
|
+
name: z21.string().optional(),
|
|
15066
|
+
url: z21.string().optional(),
|
|
15067
|
+
headers: z21.record(z21.string(), z21.string()).optional(),
|
|
15068
|
+
command: z21.string().optional(),
|
|
15069
|
+
args: z21.array(z21.string()).optional(),
|
|
15070
|
+
enabled: z21.boolean().optional()
|
|
15493
15071
|
});
|
|
15494
15072
|
mcpRouter.get("/", async (c) => {
|
|
15495
15073
|
const rows = listMcpServers().map((s) => ({
|
|
@@ -15606,10 +15184,10 @@ init_config();
|
|
|
15606
15184
|
init_db();
|
|
15607
15185
|
|
|
15608
15186
|
// src/utils/dependencies.ts
|
|
15609
|
-
import { exec as
|
|
15610
|
-
import { promisify as
|
|
15187
|
+
import { exec as exec6 } from "child_process";
|
|
15188
|
+
import { promisify as promisify6 } from "util";
|
|
15611
15189
|
import { platform as platform2 } from "os";
|
|
15612
|
-
var
|
|
15190
|
+
var execAsync6 = promisify6(exec6);
|
|
15613
15191
|
function getInstallInstructions() {
|
|
15614
15192
|
const os2 = platform2();
|
|
15615
15193
|
if (os2 === "darwin") {
|
|
@@ -15642,7 +15220,7 @@ Install tmux:
|
|
|
15642
15220
|
}
|
|
15643
15221
|
async function checkTmux() {
|
|
15644
15222
|
try {
|
|
15645
|
-
const { stdout } = await
|
|
15223
|
+
const { stdout } = await execAsync6("tmux -V", { timeout: 5e3 });
|
|
15646
15224
|
const version = stdout.trim();
|
|
15647
15225
|
return {
|
|
15648
15226
|
available: true,
|
|
@@ -15683,7 +15261,7 @@ async function checkDependencies(options = {}) {
|
|
|
15683
15261
|
}
|
|
15684
15262
|
async function checkAgentBrowser() {
|
|
15685
15263
|
try {
|
|
15686
|
-
const { stdout } = await
|
|
15264
|
+
const { stdout } = await execAsync6("agent-browser --version", { timeout: 1e4 });
|
|
15687
15265
|
const version = stdout.trim();
|
|
15688
15266
|
return { available: true, version };
|
|
15689
15267
|
} catch {
|
|
@@ -15699,12 +15277,12 @@ async function tryInstallAgentBrowser(options = {}) {
|
|
|
15699
15277
|
if (!options.quiet) {
|
|
15700
15278
|
console.log("\u{1F4E6} Installing agent-browser globally...");
|
|
15701
15279
|
}
|
|
15702
|
-
await
|
|
15280
|
+
await execAsync6("npm install -g agent-browser", { timeout: 12e4 });
|
|
15703
15281
|
try {
|
|
15704
15282
|
if (!options.quiet) {
|
|
15705
15283
|
console.log("\u{1F4E6} Installing Chromium for browser automation...");
|
|
15706
15284
|
}
|
|
15707
|
-
await
|
|
15285
|
+
await execAsync6("agent-browser install", { timeout: 12e4 });
|
|
15708
15286
|
} catch {
|
|
15709
15287
|
}
|
|
15710
15288
|
if (!options.quiet) {
|
|
@@ -15723,21 +15301,21 @@ async function tryAutoInstallTmux() {
|
|
|
15723
15301
|
try {
|
|
15724
15302
|
if (os2 === "darwin") {
|
|
15725
15303
|
try {
|
|
15726
|
-
await
|
|
15304
|
+
await execAsync6("which brew", { timeout: 5e3 });
|
|
15727
15305
|
} catch {
|
|
15728
15306
|
return false;
|
|
15729
15307
|
}
|
|
15730
15308
|
console.log("\u{1F4E6} Installing tmux via Homebrew...");
|
|
15731
|
-
await
|
|
15309
|
+
await execAsync6("brew install tmux", { timeout: 3e5 });
|
|
15732
15310
|
console.log("\u2705 tmux installed successfully");
|
|
15733
15311
|
return true;
|
|
15734
15312
|
}
|
|
15735
15313
|
if (os2 === "linux") {
|
|
15736
15314
|
try {
|
|
15737
|
-
await
|
|
15315
|
+
await execAsync6("which apt-get", { timeout: 5e3 });
|
|
15738
15316
|
console.log("\u{1F4E6} Installing tmux via apt-get...");
|
|
15739
15317
|
console.log(" (This may require sudo password)");
|
|
15740
|
-
await
|
|
15318
|
+
await execAsync6("sudo apt-get update && sudo apt-get install -y tmux", {
|
|
15741
15319
|
timeout: 3e5
|
|
15742
15320
|
});
|
|
15743
15321
|
console.log("\u2705 tmux installed successfully");
|
|
@@ -15745,9 +15323,9 @@ async function tryAutoInstallTmux() {
|
|
|
15745
15323
|
} catch {
|
|
15746
15324
|
}
|
|
15747
15325
|
try {
|
|
15748
|
-
await
|
|
15326
|
+
await execAsync6("which dnf", { timeout: 5e3 });
|
|
15749
15327
|
console.log("\u{1F4E6} Installing tmux via dnf...");
|
|
15750
|
-
await
|
|
15328
|
+
await execAsync6("sudo dnf install -y tmux", { timeout: 3e5 });
|
|
15751
15329
|
console.log("\u2705 tmux installed successfully");
|
|
15752
15330
|
return true;
|
|
15753
15331
|
} catch {
|
|
@@ -15787,11 +15365,11 @@ function getWebDirectory() {
|
|
|
15787
15365
|
try {
|
|
15788
15366
|
const currentDir = dirname8(fileURLToPath4(import.meta.url));
|
|
15789
15367
|
const webDir = resolve11(currentDir, "..", "web");
|
|
15790
|
-
if (
|
|
15368
|
+
if (existsSync20(webDir) && existsSync20(join15(webDir, "package.json"))) {
|
|
15791
15369
|
return webDir;
|
|
15792
15370
|
}
|
|
15793
15371
|
const altWebDir = resolve11(currentDir, "..", "..", "web");
|
|
15794
|
-
if (
|
|
15372
|
+
if (existsSync20(altWebDir) && existsSync20(join15(altWebDir, "package.json"))) {
|
|
15795
15373
|
return altWebDir;
|
|
15796
15374
|
}
|
|
15797
15375
|
return null;
|
|
@@ -15849,23 +15427,23 @@ async function findWebPort(preferredPort) {
|
|
|
15849
15427
|
return { port: preferredPort, alreadyRunning: false };
|
|
15850
15428
|
}
|
|
15851
15429
|
function hasProductionBuild(webDir) {
|
|
15852
|
-
const buildIdPath =
|
|
15853
|
-
return
|
|
15430
|
+
const buildIdPath = join15(webDir, ".next", "BUILD_ID");
|
|
15431
|
+
return existsSync20(buildIdPath);
|
|
15854
15432
|
}
|
|
15855
15433
|
function hasSourceFiles(webDir) {
|
|
15856
|
-
const appDir =
|
|
15857
|
-
const pagesDir =
|
|
15858
|
-
const rootAppDir =
|
|
15859
|
-
const rootPagesDir =
|
|
15860
|
-
return
|
|
15434
|
+
const appDir = join15(webDir, "src", "app");
|
|
15435
|
+
const pagesDir = join15(webDir, "src", "pages");
|
|
15436
|
+
const rootAppDir = join15(webDir, "app");
|
|
15437
|
+
const rootPagesDir = join15(webDir, "pages");
|
|
15438
|
+
return existsSync20(appDir) || existsSync20(pagesDir) || existsSync20(rootAppDir) || existsSync20(rootPagesDir);
|
|
15861
15439
|
}
|
|
15862
15440
|
function getStandaloneServerPath(webDir) {
|
|
15863
15441
|
const possiblePaths2 = [
|
|
15864
|
-
|
|
15865
|
-
|
|
15442
|
+
join15(webDir, ".next", "standalone", "server.js"),
|
|
15443
|
+
join15(webDir, ".next", "standalone", "web", "server.js")
|
|
15866
15444
|
];
|
|
15867
15445
|
for (const serverPath of possiblePaths2) {
|
|
15868
|
-
if (
|
|
15446
|
+
if (existsSync20(serverPath)) {
|
|
15869
15447
|
return serverPath;
|
|
15870
15448
|
}
|
|
15871
15449
|
}
|
|
@@ -15905,13 +15483,13 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15905
15483
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
15906
15484
|
return { process: null, port: actualPort };
|
|
15907
15485
|
}
|
|
15908
|
-
const usePnpm =
|
|
15909
|
-
const useNpm = !usePnpm &&
|
|
15486
|
+
const usePnpm = existsSync20(join15(webDir, "pnpm-lock.yaml"));
|
|
15487
|
+
const useNpm = !usePnpm && existsSync20(join15(webDir, "package-lock.json"));
|
|
15910
15488
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
15911
15489
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
15912
15490
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
15913
15491
|
const runtimeConfig = { apiBaseUrl: apiUrl };
|
|
15914
|
-
const runtimeConfigPath =
|
|
15492
|
+
const runtimeConfigPath = join15(webDir, "runtime-config.json");
|
|
15915
15493
|
try {
|
|
15916
15494
|
writeFileSync6(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
|
|
15917
15495
|
if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
|
|
@@ -16126,8 +15704,8 @@ async function startServer(options = {}) {
|
|
|
16126
15704
|
if (options.workingDirectory) {
|
|
16127
15705
|
config.resolvedWorkingDirectory = options.workingDirectory;
|
|
16128
15706
|
}
|
|
16129
|
-
if (!
|
|
16130
|
-
|
|
15707
|
+
if (!existsSync20(config.resolvedWorkingDirectory)) {
|
|
15708
|
+
mkdirSync9(config.resolvedWorkingDirectory, { recursive: true });
|
|
16131
15709
|
if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
|
|
16132
15710
|
}
|
|
16133
15711
|
if (!config.resolvedRemoteServer.url) {
|
|
@@ -16168,13 +15746,15 @@ async function startServer(options = {}) {
|
|
|
16168
15746
|
if (!options.quiet) console.warn(`[scheduler] start skipped: ${err.message}`);
|
|
16169
15747
|
}
|
|
16170
15748
|
const port = options.port || config.server.port;
|
|
16171
|
-
const
|
|
15749
|
+
const envHost = process.env.SPARKECODER_API_HOST || process.env.SPARKECODER_HOST;
|
|
15750
|
+
const host = envHost || options.host || config.server.host || "0.0.0.0";
|
|
15751
|
+
const hostSource = envHost ? process.env.SPARKECODER_API_HOST ? "env SPARKECODER_API_HOST" : "env SPARKECODER_HOST" : options.host ? "--host flag" : config.server.host ? "config.server.host" : "default";
|
|
16172
15752
|
const publicUrl = options.publicUrl || config.server.publicUrl;
|
|
16173
15753
|
const app = await createApp({ quiet: options.quiet });
|
|
16174
15754
|
if (!options.quiet) {
|
|
16175
15755
|
console.log(`
|
|
16176
15756
|
\u{1F680} SparkECoder API Server`);
|
|
16177
|
-
console.log(` \u2192
|
|
15757
|
+
console.log(` \u2192 Binding to ${host}:${port} (source: ${hostSource})`);
|
|
16178
15758
|
if (publicUrl) {
|
|
16179
15759
|
console.log(` \u2192 Public URL: ${publicUrl}`);
|
|
16180
15760
|
}
|
|
@@ -16183,10 +15763,22 @@ async function startServer(options = {}) {
|
|
|
16183
15763
|
console.log(` \u2192 OpenAPI spec: http://${host}:${port}/openapi.json
|
|
16184
15764
|
`);
|
|
16185
15765
|
}
|
|
15766
|
+
if (host === "127.0.0.1" || host === "localhost") {
|
|
15767
|
+
console.log(`[sparkecoder] \u26A0 API bound to ${host} only \u2014 not reachable from outside the machine.`);
|
|
15768
|
+
console.log(`[sparkecoder] For tunnels/reverse proxies (Modal/Fly/Docker), set --host 0.0.0.0 or SPARKECODER_API_HOST=0.0.0.0.`);
|
|
15769
|
+
}
|
|
16186
15770
|
serverInstance = serve({
|
|
16187
15771
|
fetch: app.fetch,
|
|
16188
15772
|
port,
|
|
16189
15773
|
hostname: host
|
|
15774
|
+
}, (info) => {
|
|
15775
|
+
const actual = `${info.address}:${info.port}`;
|
|
15776
|
+
const requested = `${host}:${port}`;
|
|
15777
|
+
if (info.address === "127.0.0.1" && host !== "127.0.0.1" && host !== "localhost") {
|
|
15778
|
+
console.warn(`[sparkecoder] \u2717 API listener bound to ${actual} but ${requested} was requested. External requests will be refused.`);
|
|
15779
|
+
} else {
|
|
15780
|
+
console.log(`[sparkecoder] \u2713 API listening on ${actual}`);
|
|
15781
|
+
}
|
|
16190
15782
|
});
|
|
16191
15783
|
let webPort;
|
|
16192
15784
|
let webStarted;
|
|
@@ -16671,18 +16263,18 @@ function generateOpenAPISpec() {
|
|
|
16671
16263
|
init_config();
|
|
16672
16264
|
init_semantic();
|
|
16673
16265
|
init_db();
|
|
16674
|
-
import { mkdirSync as
|
|
16675
|
-
import { resolve as resolve12, join as
|
|
16266
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync7, readFileSync as readFileSync10, existsSync as existsSync21, statSync as statSync3 } from "fs";
|
|
16267
|
+
import { resolve as resolve12, join as join16 } from "path";
|
|
16676
16268
|
function getCliVersion() {
|
|
16677
16269
|
const here = dirname9(fileURLToPath5(import.meta.url));
|
|
16678
16270
|
const candidates = [
|
|
16679
|
-
|
|
16680
|
-
|
|
16681
|
-
|
|
16271
|
+
join16(here, "..", "package.json"),
|
|
16272
|
+
join16(here, "..", "..", "package.json"),
|
|
16273
|
+
join16(process.cwd(), "package.json")
|
|
16682
16274
|
];
|
|
16683
16275
|
for (const p of candidates) {
|
|
16684
16276
|
try {
|
|
16685
|
-
const pkg = JSON.parse(
|
|
16277
|
+
const pkg = JSON.parse(readFileSync10(p, "utf8"));
|
|
16686
16278
|
if (pkg.name === "sparkecoder" && pkg.version) return pkg.version;
|
|
16687
16279
|
} catch {
|
|
16688
16280
|
}
|
|
@@ -17251,20 +16843,10 @@ Unexpected error: ${outerError.message}`));
|
|
|
17251
16843
|
}
|
|
17252
16844
|
}
|
|
17253
16845
|
var program = new Command();
|
|
17254
|
-
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)").
|
|
17255
|
-
if (options.enableComputerUse) {
|
|
17256
|
-
process.env.SPARKECODER_COMPUTER_USE = "1";
|
|
17257
|
-
console.log(chalk.cyan("\u2192 Computer use enabled by default for new sessions."));
|
|
17258
|
-
}
|
|
16846
|
+
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)").action(async (options) => {
|
|
17259
16847
|
await runChat(options);
|
|
17260
16848
|
});
|
|
17261
|
-
program.command("server").description("Start the SparkECoder server (API + Web UI)").option("-p, --port <port>", "API server port", "3141").option("-h, --host <host>", "Server
|
|
17262
|
-
const globalOpts = program.opts();
|
|
17263
|
-
const enableCU = options.enableComputerUse || globalOpts.enableComputerUse;
|
|
17264
|
-
if (enableCU) {
|
|
17265
|
-
process.env.SPARKECODER_COMPUTER_USE = "1";
|
|
17266
|
-
console.log(chalk.cyan("\u2192 Computer use enabled by default for new sessions."));
|
|
17267
|
-
}
|
|
16849
|
+
program.command("server").description("Start the SparkECoder server (API + Web UI)").option("-p, --port <port>", "API server port", "3141").option("-h, --host <host>", "Server bind address (use 127.0.0.1 to restrict to loopback). Overridable via SPARKECODER_API_HOST / SPARKECODER_HOST env.", "0.0.0.0").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("--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("--setup-secret <secret>", "Setup secret (or short-lived JWT) used for /auth/register and /tunnels when the remote server has SETUP_SECRET configured. Equivalent to setting SPARKECODER_SETUP_SECRET env.").action(async (options) => {
|
|
17268
16850
|
if (options.setupSecret) {
|
|
17269
16851
|
process.env.SPARKECODER_SETUP_SECRET = options.setupSecret;
|
|
17270
16852
|
if (!process.env.SPARKECODER_TUNNEL_SECRET) {
|
|
@@ -17307,13 +16889,7 @@ program.command("server").description("Start the SparkECoder server (API + Web U
|
|
|
17307
16889
|
process.exit(1);
|
|
17308
16890
|
}
|
|
17309
16891
|
});
|
|
17310
|
-
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("--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)").
|
|
17311
|
-
const globalOpts = program.opts();
|
|
17312
|
-
const enableCU = options.enableComputerUse || globalOpts.enableComputerUse;
|
|
17313
|
-
if (enableCU) {
|
|
17314
|
-
process.env.SPARKECODER_COMPUTER_USE = "1";
|
|
17315
|
-
console.log(chalk.cyan("\u2192 Computer use enabled by default for new sessions."));
|
|
17316
|
-
}
|
|
16892
|
+
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("--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)").action(async (options) => {
|
|
17317
16893
|
await runChat(options);
|
|
17318
16894
|
});
|
|
17319
16895
|
program.command("task").description("Run an autonomous task that completes without human interaction").requiredOption("--prompt <prompt>", "Task prompt describing what to do").requiredOption("--schema <schema>", "JSON Schema for the output (file path or inline JSON string)").option("--webhook <url>", "Webhook URL to receive task events").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-n, --name <name>", "Name for the task").option("--max-iterations <n>", "Maximum agent loop iterations", "50").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("-c, --config <path>", "Path to config file").option("--no-auto-start", "Do not auto-start server if not running").option("--parent-task <id>", "Continue from a previous task (inherits its full conversation context)").option("--wait", "Block and poll until task completes").option("-v, --verbose", "Enable verbose logging").action(async (options) => {
|
|
@@ -17345,8 +16921,8 @@ program.command("task").description("Run an autonomous task that completes witho
|
|
|
17345
16921
|
let outputSchema;
|
|
17346
16922
|
try {
|
|
17347
16923
|
const schemaStr = options.schema;
|
|
17348
|
-
if (
|
|
17349
|
-
outputSchema = JSON.parse(
|
|
16924
|
+
if (existsSync21(schemaStr)) {
|
|
16925
|
+
outputSchema = JSON.parse(readFileSync10(schemaStr, "utf-8"));
|
|
17350
16926
|
} else {
|
|
17351
16927
|
outputSchema = JSON.parse(schemaStr);
|
|
17352
16928
|
}
|
|
@@ -17413,13 +16989,13 @@ program.command("init").description("Create a sparkecoder.config.json file").opt
|
|
|
17413
16989
|
let configLocation;
|
|
17414
16990
|
if (options.global) {
|
|
17415
16991
|
const appDataDir = ensureAppDataDirectory();
|
|
17416
|
-
configPath =
|
|
16992
|
+
configPath = join16(appDataDir, "sparkecoder.config.json");
|
|
17417
16993
|
configLocation = "global";
|
|
17418
16994
|
} else {
|
|
17419
16995
|
configPath = resolve12(process.cwd(), "sparkecoder.config.json");
|
|
17420
16996
|
configLocation = "local";
|
|
17421
16997
|
}
|
|
17422
|
-
if (
|
|
16998
|
+
if (existsSync21(configPath) && !options.force) {
|
|
17423
16999
|
console.log(chalk.yellow("Config file already exists. Use --force to overwrite."));
|
|
17424
17000
|
console.log(chalk.dim(` ${configPath}`));
|
|
17425
17001
|
return;
|
|
@@ -17444,11 +17020,11 @@ program.command("slack-setup").description("Interactively configure Slack integr
|
|
|
17444
17020
|
console.error(chalk.red("Both bot token and signing secret are required."));
|
|
17445
17021
|
process.exit(1);
|
|
17446
17022
|
}
|
|
17447
|
-
const configPath = options.global ?
|
|
17023
|
+
const configPath = options.global ? join16(ensureAppDataDirectory(), "sparkecoder.config.json") : resolve12(process.cwd(), "sparkecoder.config.json");
|
|
17448
17024
|
let existing = {};
|
|
17449
|
-
if (
|
|
17025
|
+
if (existsSync21(configPath)) {
|
|
17450
17026
|
try {
|
|
17451
|
-
existing = JSON.parse(
|
|
17027
|
+
existing = JSON.parse(readFileSync10(configPath, "utf-8"));
|
|
17452
17028
|
} catch {
|
|
17453
17029
|
}
|
|
17454
17030
|
} else {
|
|
@@ -17712,9 +17288,9 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
17712
17288
|
}
|
|
17713
17289
|
const verOut = run("cloudflared", ["--version"]).stdout?.toString().split("\n")[0] || "installed";
|
|
17714
17290
|
console.log(chalk.green("\u2713"), "cloudflared:", chalk.dim(verOut));
|
|
17715
|
-
const cfDir =
|
|
17716
|
-
const certPath =
|
|
17717
|
-
if (!
|
|
17291
|
+
const cfDir = join16(homedir2(), ".cloudflared");
|
|
17292
|
+
const certPath = join16(cfDir, "cert.pem");
|
|
17293
|
+
if (!existsSync21(certPath)) {
|
|
17718
17294
|
console.log(chalk.yellow("No Cloudflare login cert found."));
|
|
17719
17295
|
if (await confirm("Run `cloudflared tunnel login` now (opens a browser)?", true)) {
|
|
17720
17296
|
run("cloudflared", ["tunnel", "login"], { inheritIO: true, check: true });
|
|
@@ -17758,8 +17334,8 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
17758
17334
|
return;
|
|
17759
17335
|
}
|
|
17760
17336
|
}
|
|
17761
|
-
const credsFile = tunnel.credentials_file ||
|
|
17762
|
-
if (!
|
|
17337
|
+
const credsFile = tunnel.credentials_file || join16(cfDir, `${tunnel.id}.json`);
|
|
17338
|
+
if (!existsSync21(credsFile)) {
|
|
17763
17339
|
console.log(chalk.yellow(`Credentials file not found at ${credsFile}. The tunnel may still work via cert.pem.`));
|
|
17764
17340
|
}
|
|
17765
17341
|
let hostname = options.hostname;
|
|
@@ -17782,7 +17358,7 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
17782
17358
|
console.log(chalk.yellow("DNS route warning:"), err.trim().split("\n").slice(-2).join(" "));
|
|
17783
17359
|
}
|
|
17784
17360
|
}
|
|
17785
|
-
const configPath =
|
|
17361
|
+
const configPath = join16(cfDir, "config.yml");
|
|
17786
17362
|
const configBody = `tunnel: ${tunnel.id}
|
|
17787
17363
|
credentials-file: ${credsFile}
|
|
17788
17364
|
ingress:
|
|
@@ -17793,8 +17369,8 @@ ingress:
|
|
|
17793
17369
|
- service: http_status:404
|
|
17794
17370
|
`;
|
|
17795
17371
|
let wroteConfig = false;
|
|
17796
|
-
if (
|
|
17797
|
-
const existing =
|
|
17372
|
+
if (existsSync21(configPath)) {
|
|
17373
|
+
const existing = readFileSync10(configPath, "utf8");
|
|
17798
17374
|
if (existing.trim() === configBody.trim()) {
|
|
17799
17375
|
console.log(chalk.green("\u2713"), `config.yml already up to date: ${configPath}`);
|
|
17800
17376
|
wroteConfig = true;
|
|
@@ -18168,18 +17744,27 @@ ${providerName} API Key:
|
|
|
18168
17744
|
process.exit(1);
|
|
18169
17745
|
}
|
|
18170
17746
|
});
|
|
18171
|
-
|
|
17747
|
+
function formatDisplaySize(size) {
|
|
17748
|
+
const points = `${size.width}\xD7${size.height} points`;
|
|
17749
|
+
const px = size.pixelWidth && size.pixelHeight && (size.pixelWidth !== size.width || size.pixelHeight !== size.height) ? ` (${size.pixelWidth}\xD7${size.pixelHeight} pixels, Retina)` : "";
|
|
17750
|
+
const name = size.displayName ? `${size.displayName} \u2014 ` : "";
|
|
17751
|
+
return `${name}${points}${px}`;
|
|
17752
|
+
}
|
|
17753
|
+
program.command("check-permissions").description("Check macOS prerequisites for desktop automation (cliclick + Accessibility + Screen Recording)").action(async () => {
|
|
18172
17754
|
if (process.platform !== "darwin") {
|
|
18173
|
-
console.log(chalk.yellow(`
|
|
17755
|
+
console.log(chalk.yellow(`Desktop automation prerequisites only apply on macOS. Current platform: ${process.platform}`));
|
|
18174
17756
|
process.exit(0);
|
|
18175
17757
|
}
|
|
18176
17758
|
const {
|
|
18177
17759
|
isCliclickInstalled: isCliclickInstalled2,
|
|
18178
17760
|
hasAccessibilityPermissions: hasAccessibilityPermissions2,
|
|
18179
17761
|
hasScreenRecordingPermissions: hasScreenRecordingPermissions2,
|
|
18180
|
-
detectScreenSize: detectScreenSize2
|
|
18181
|
-
|
|
18182
|
-
|
|
17762
|
+
detectScreenSize: detectScreenSize2,
|
|
17763
|
+
getResponsibleAppName: getResponsibleAppName2
|
|
17764
|
+
} = await Promise.resolve().then(() => (init_desktop_permissions(), desktop_permissions_exports));
|
|
17765
|
+
console.log(chalk.bold("\nDesktop-automation prerequisites:\n"));
|
|
17766
|
+
const responsibleApp = await getResponsibleAppName2();
|
|
17767
|
+
console.log(` ${chalk.dim("\u2022")} Responsible process (the app TCC tracks): ${chalk.cyan(responsibleApp)}`);
|
|
18183
17768
|
const cliclick = await isCliclickInstalled2();
|
|
18184
17769
|
if (cliclick) {
|
|
18185
17770
|
console.log(` ${chalk.green("\u2713")} cliclick installed`);
|
|
@@ -18189,25 +17774,28 @@ program.command("check-permissions").description("Check macOS permissions requir
|
|
|
18189
17774
|
}
|
|
18190
17775
|
const acc = cliclick ? await hasAccessibilityPermissions2() : { ok: false, error: "cliclick not installed" };
|
|
18191
17776
|
if (acc.ok) {
|
|
18192
|
-
console.log(` ${chalk.green("\u2713")} Accessibility
|
|
17777
|
+
console.log(` ${chalk.green("\u2713")} Accessibility permission granted (for ${responsibleApp})`);
|
|
18193
17778
|
} else {
|
|
18194
|
-
console.log(` ${chalk.red("\u2717")} Accessibility
|
|
18195
|
-
console.log(` ${chalk.dim(acc.error
|
|
17779
|
+
console.log(` ${chalk.red("\u2717")} Accessibility permission missing`);
|
|
17780
|
+
if (acc.error) console.log(` ${chalk.dim(acc.error.split("\n")[0])}`);
|
|
18196
17781
|
}
|
|
18197
17782
|
const screen = await hasScreenRecordingPermissions2();
|
|
18198
17783
|
if (screen) {
|
|
18199
|
-
console.log(` ${chalk.green("\u2713")} Screen Recording
|
|
17784
|
+
console.log(` ${chalk.green("\u2713")} Screen Recording permission granted (for ${responsibleApp})`);
|
|
18200
17785
|
} else {
|
|
18201
|
-
console.log(` ${chalk.red("\u2717")} Screen Recording
|
|
17786
|
+
console.log(` ${chalk.red("\u2717")} Screen Recording permission missing`);
|
|
17787
|
+
console.log(` ${chalk.dim("macOS 15 (Sequoia) re-prompts weekly even when granted \u2014 dismiss the dialog and the API still returns true.")}`);
|
|
18202
17788
|
}
|
|
18203
17789
|
const size = await detectScreenSize2();
|
|
18204
17790
|
if (size) {
|
|
18205
|
-
console.log(` ${chalk.dim("\u2022")}
|
|
17791
|
+
console.log(` ${chalk.dim("\u2022")} Main display: ${formatDisplaySize(size)}`);
|
|
17792
|
+
console.log(` ${chalk.dim("cliclick coordinates are POINTS, not pixels. (0,0) = top-left.")}`);
|
|
18206
17793
|
}
|
|
18207
17794
|
const allOk = cliclick && acc.ok && screen;
|
|
18208
17795
|
console.log();
|
|
18209
17796
|
if (allOk) {
|
|
18210
|
-
console.log(chalk.green("All checks passed. The agent can
|
|
17797
|
+
console.log(chalk.green("All checks passed. The agent can drive the desktop via cliclick/screencapture/osascript."));
|
|
17798
|
+
console.log(chalk.dim("See the `desktop-automation` skill for recipes."));
|
|
18211
17799
|
} else {
|
|
18212
17800
|
console.log(chalk.yellow("Some prerequisites are missing."));
|
|
18213
17801
|
console.log(chalk.dim("Run: sparkecoder request-permissions"));
|
|
@@ -18215,74 +17803,83 @@ program.command("check-permissions").description("Check macOS permissions requir
|
|
|
18215
17803
|
console.log();
|
|
18216
17804
|
process.exit(allOk ? 0 : 1);
|
|
18217
17805
|
});
|
|
18218
|
-
program.command("request-permissions").description("
|
|
17806
|
+
program.command("request-permissions").description("Open System Settings to the right panes so you can grant macOS desktop-automation permissions to the responsible app").action(async () => {
|
|
18219
17807
|
if (process.platform !== "darwin") {
|
|
18220
|
-
console.log(chalk.yellow(`
|
|
17808
|
+
console.log(chalk.yellow(`Desktop automation prerequisites only apply on macOS. Current platform: ${process.platform}`));
|
|
18221
17809
|
process.exit(0);
|
|
18222
17810
|
}
|
|
18223
17811
|
const {
|
|
18224
17812
|
isCliclickInstalled: isCliclickInstalled2,
|
|
18225
17813
|
hasAccessibilityPermissions: hasAccessibilityPermissions2,
|
|
18226
17814
|
hasScreenRecordingPermissions: hasScreenRecordingPermissions2,
|
|
18227
|
-
requestAccessibilityPrompt: requestAccessibilityPrompt2,
|
|
18228
|
-
requestScreenRecordingPrompt: requestScreenRecordingPrompt2,
|
|
18229
17815
|
openSystemSettings: openSystemSettings2,
|
|
18230
|
-
detectScreenSize: detectScreenSize2
|
|
18231
|
-
|
|
18232
|
-
|
|
17816
|
+
detectScreenSize: detectScreenSize2,
|
|
17817
|
+
getResponsibleAppName: getResponsibleAppName2
|
|
17818
|
+
} = await Promise.resolve().then(() => (init_desktop_permissions(), desktop_permissions_exports));
|
|
17819
|
+
console.log(chalk.bold("\nDesktop-automation setup\n"));
|
|
17820
|
+
const responsibleApp = await getResponsibleAppName2();
|
|
17821
|
+
console.log(` ${chalk.dim("\u2022")} Permissions need to be granted to: ${chalk.cyan(responsibleApp)}`);
|
|
17822
|
+
console.log(` ${chalk.dim("Not to cliclick, screencapture, osascript, or node \u2014 macOS attributes")}`);
|
|
17823
|
+
console.log(` ${chalk.dim("TCC entries to the responsible (parent GUI) process.")}`);
|
|
17824
|
+
console.log();
|
|
18233
17825
|
if (!await isCliclickInstalled2()) {
|
|
18234
17826
|
console.log(` ${chalk.red("\u2717")} cliclick is not installed`);
|
|
18235
17827
|
console.log(` ${chalk.dim("Run: brew install cliclick")}`);
|
|
18236
17828
|
console.log(` ${chalk.dim("Then re-run: sparkecoder request-permissions")}`);
|
|
18237
17829
|
console.log();
|
|
18238
17830
|
process.exit(1);
|
|
17831
|
+
} else {
|
|
17832
|
+
console.log(` ${chalk.green("\u2713")} cliclick installed`);
|
|
18239
17833
|
}
|
|
18240
17834
|
const acc = await hasAccessibilityPermissions2();
|
|
18241
17835
|
const screen = await hasScreenRecordingPermissions2();
|
|
18242
|
-
let
|
|
17836
|
+
let needsAction = false;
|
|
18243
17837
|
if (!acc.ok) {
|
|
18244
|
-
console.log(
|
|
18245
|
-
|
|
17838
|
+
console.log();
|
|
17839
|
+
console.log(` ${chalk.yellow("\u26A0")} Accessibility permission missing \u2014 opening System Settings...`);
|
|
18246
17840
|
await openSystemSettings2("accessibility");
|
|
18247
|
-
console.log(` ${chalk.dim("1. In Privacy & Security \u2192 Accessibility, click
|
|
18248
|
-
console.log(` ${chalk.dim("2. Add
|
|
18249
|
-
console.log(` ${chalk.dim("3.
|
|
18250
|
-
|
|
17841
|
+
console.log(` ${chalk.dim("1. In Privacy & Security \u2192 Accessibility, click the ")}+${chalk.dim(" button.")}`);
|
|
17842
|
+
console.log(` ${chalk.dim("2. Add ")}${chalk.cyan(responsibleApp)}${chalk.dim(".")}`);
|
|
17843
|
+
console.log(` ${chalk.dim("3. Make sure its toggle is ON.")}`);
|
|
17844
|
+
needsAction = true;
|
|
18251
17845
|
} else {
|
|
18252
17846
|
console.log(` ${chalk.green("\u2713")} Accessibility already granted`);
|
|
18253
17847
|
}
|
|
18254
17848
|
if (!screen) {
|
|
18255
|
-
console.log(
|
|
18256
|
-
|
|
17849
|
+
console.log();
|
|
17850
|
+
console.log(` ${chalk.yellow("\u26A0")} Screen Recording permission missing \u2014 opening System Settings...`);
|
|
18257
17851
|
await openSystemSettings2("screen-recording");
|
|
18258
|
-
console.log(` ${chalk.dim("1. In Privacy & Security \u2192 Screen Recording, click
|
|
18259
|
-
console.log(` ${chalk.dim("2. Add
|
|
18260
|
-
console.log(` ${chalk.dim("3.
|
|
18261
|
-
|
|
17852
|
+
console.log(` ${chalk.dim("1. In Privacy & Security \u2192 Screen & System Audio Recording, click the ")}+${chalk.dim(" button.")}`);
|
|
17853
|
+
console.log(` ${chalk.dim("2. Add ")}${chalk.cyan(responsibleApp)}${chalk.dim(".")}`);
|
|
17854
|
+
console.log(` ${chalk.dim("3. Make sure its toggle is ON.")}`);
|
|
17855
|
+
console.log(` ${chalk.dim('Note: macOS 15 (Sequoia) will prompt weekly to re-confirm \u2014 just click "Allow".')}`);
|
|
17856
|
+
needsAction = true;
|
|
18262
17857
|
} else {
|
|
18263
17858
|
console.log(` ${chalk.green("\u2713")} Screen Recording already granted`);
|
|
18264
17859
|
}
|
|
18265
17860
|
const size = await detectScreenSize2();
|
|
18266
17861
|
if (size) {
|
|
18267
|
-
console.log(
|
|
17862
|
+
console.log();
|
|
17863
|
+
console.log(` ${chalk.dim("\u2022")} Main display: ${formatDisplaySize(size)}`);
|
|
18268
17864
|
}
|
|
18269
17865
|
console.log();
|
|
18270
|
-
if (
|
|
18271
|
-
console.log(chalk.yellow(
|
|
18272
|
-
console.log(chalk.dim("Then run: sparkecoder check-permissions"));
|
|
17866
|
+
if (needsAction) {
|
|
17867
|
+
console.log(chalk.yellow(`After toggling on ${responsibleApp}, RESTART it (and the agent process inside it) so the new TCC entry takes effect.`));
|
|
17868
|
+
console.log(chalk.dim("Then re-run: sparkecoder check-permissions"));
|
|
18273
17869
|
} else {
|
|
18274
|
-
console.log(chalk.green("All permissions are already granted.
|
|
17870
|
+
console.log(chalk.green("All permissions are already granted. Desktop automation is ready."));
|
|
17871
|
+
console.log(chalk.dim("See the `desktop-automation` skill for recipes."));
|
|
18275
17872
|
}
|
|
18276
17873
|
console.log();
|
|
18277
17874
|
});
|
|
18278
17875
|
{
|
|
18279
17876
|
let stateFilePath = function() {
|
|
18280
|
-
return
|
|
17877
|
+
return join16(ensureAppDataDirectory(), "recordings.json");
|
|
18281
17878
|
}, readState = function() {
|
|
18282
17879
|
const p = stateFilePath();
|
|
18283
|
-
if (!
|
|
17880
|
+
if (!existsSync21(p)) return [];
|
|
18284
17881
|
try {
|
|
18285
|
-
return JSON.parse(
|
|
17882
|
+
return JSON.parse(readFileSync10(p, "utf-8"));
|
|
18286
17883
|
} catch {
|
|
18287
17884
|
return [];
|
|
18288
17885
|
}
|
|
@@ -18302,16 +17899,16 @@ program.command("request-permissions").description("Request macOS permissions fo
|
|
|
18302
17899
|
const record = program.command("record").description("Start/stop screen recordings");
|
|
18303
17900
|
record.command("start").description("Start a screen recording (returns id, path, pid as JSON)").option("--name <slug>", "Optional human-readable label for the recording").option("--dir <path>", "Output directory (default: ~/recordings)").action(async (opts) => {
|
|
18304
17901
|
const { homedir: homedir2, platform: osPlatform } = await import("os");
|
|
18305
|
-
const outDir = opts.dir ? resolve12(opts.dir.replace(/^~/, homedir2())) :
|
|
18306
|
-
|
|
17902
|
+
const outDir = opts.dir ? resolve12(opts.dir.replace(/^~/, homedir2())) : join16(homedir2(), "recordings");
|
|
17903
|
+
mkdirSync10(outDir, { recursive: true });
|
|
18307
17904
|
const id = `rec-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
18308
17905
|
const ext = osPlatform() === "darwin" ? "mov" : "mp4";
|
|
18309
17906
|
const filename = `${id}${opts.name ? `-${String(opts.name).replace(/[^a-z0-9-]+/gi, "-").toLowerCase()}` : ""}.${ext}`;
|
|
18310
|
-
const path =
|
|
17907
|
+
const path = join16(outDir, filename);
|
|
18311
17908
|
let cmd;
|
|
18312
17909
|
let args;
|
|
18313
17910
|
if (osPlatform() === "darwin") {
|
|
18314
|
-
cmd =
|
|
17911
|
+
cmd = existsSync21("/usr/sbin/screencapture") ? "/usr/sbin/screencapture" : "screencapture";
|
|
18315
17912
|
args = ["-v", "-C", "-k", path];
|
|
18316
17913
|
} else if (osPlatform() === "linux") {
|
|
18317
17914
|
const display = process.env.DISPLAY || ":0.0";
|
|
@@ -18414,7 +18011,7 @@ program.command("request-permissions").description("Request macOS permissions fo
|
|
|
18414
18011
|
}
|
|
18415
18012
|
}
|
|
18416
18013
|
writeState(rows.filter((r) => r.id !== id));
|
|
18417
|
-
const fileExists =
|
|
18014
|
+
const fileExists = existsSync21(row.path);
|
|
18418
18015
|
const sizeMb = fileExists ? Math.round(statSync3(row.path).size / (1024 * 1024) * 10) / 10 : 0;
|
|
18419
18016
|
console.log(JSON.stringify({
|
|
18420
18017
|
id,
|
|
@@ -18449,7 +18046,7 @@ program.command("request-permissions").description("Request macOS permissions fo
|
|
|
18449
18046
|
}
|
|
18450
18047
|
}
|
|
18451
18048
|
}
|
|
18452
|
-
stopped.push({ id: r.id, path: r.path, ok:
|
|
18049
|
+
stopped.push({ id: r.id, path: r.path, ok: existsSync21(r.path) });
|
|
18453
18050
|
}
|
|
18454
18051
|
writeState([]);
|
|
18455
18052
|
console.log(JSON.stringify({ stopped }));
|