sparkecoder 0.1.116 → 0.1.118
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 +3 -3
- package/dist/agent/index.js +136 -697
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +621 -1038
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/{index-Biy5JTop.d.ts → index-Bcz0aCAR.d.ts} +104 -113
- package/dist/index.d.ts +5 -5
- package/dist/index.js +353 -935
- package/dist/index.js.map +1 -1
- package/dist/{schema-CYSKJZ3m.d.ts → schema-BWbWmfDQ.d.ts} +3 -5
- package/dist/{search-CVVfuBPZ.d.ts → search-DOzC4ojH.d.ts} +4 -4
- package/dist/server/index.js +353 -935
- 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 +4 -170
- 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/{7tYFi20tEUZNGXmy2DJ1K → T8x1J_CS0n9FaWBr5GhLe}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{7tYFi20tEUZNGXmy2DJ1K → T8x1J_CS0n9FaWBr5GhLe}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{7tYFi20tEUZNGXmy2DJ1K → T8x1J_CS0n9FaWBr5GhLe}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{7tYFi20tEUZNGXmy2DJ1K → T8x1J_CS0n9FaWBr5GhLe}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{7tYFi20tEUZNGXmy2DJ1K → T8x1J_CS0n9FaWBr5GhLe}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/static/{7tYFi20tEUZNGXmy2DJ1K → T8x1J_CS0n9FaWBr5GhLe}/_ssgManifest.js +0 -0
- /package/web/.next/static/{7tYFi20tEUZNGXmy2DJ1K → T8x1J_CS0n9FaWBr5GhLe}/_buildManifest.js +0 -0
- /package/web/.next/static/{7tYFi20tEUZNGXmy2DJ1K → T8x1J_CS0n9FaWBr5GhLe}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{7tYFi20tEUZNGXmy2DJ1K → T8x1J_CS0n9FaWBr5GhLe}/_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
|
|
@@ -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,10 +7794,30 @@ 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
|
|
|
7801
|
+
### How to TALK to the user (versus how you reason internally)
|
|
7802
|
+
|
|
7803
|
+
All of the rules below \u2014 decomposition, parallel spawning, "don't invent commands", load-the-skill, etc. \u2014 are **your internal operating procedure**. They are how you decide what to do. They are NOT something to recite back to the user.
|
|
7804
|
+
|
|
7805
|
+
When replying to the user (Slack, web, or any channel), be a normal helpful assistant:
|
|
7806
|
+
|
|
7807
|
+
- Tell them *what you're doing*, not *how you're doing it internally*.
|
|
7808
|
+
- Don't quote your own prompt language ("just the goal", "read the skill", "no step-by-step from me", "decomposing into parallel workers"). The user doesn't care about your delegation pattern.
|
|
7809
|
+
- Don't narrate "handing off to a worker now" unless it's actually relevant (e.g. they asked how you work, or the work is going to take a noticeably long time).
|
|
7810
|
+
- Skip ceremony. A short acknowledgement + a brief description of the actual task + (optionally) a heads-up if there's anything they need to do or avoid is plenty.
|
|
7811
|
+
|
|
7812
|
+
| What you might be tempted to say | What you should say instead |
|
|
7813
|
+
|---|---|
|
|
7814
|
+
| *"Got it \u2014 handing it off with just the goal + 'read the skills and figure it out.' No step-by-step from me."* | *"On it. Recording a screen capture of the Weather app showing Anchorage. Don't touch the keyboard while it's running \u2014 I'll post the video when it's done."* |
|
|
7815
|
+
| *"Decomposing into two parallel workers: one headless, one desktop."* | *"Running the tests and pulling the diff at the same time. Back in ~30s."* |
|
|
7816
|
+
| *"Spawning worker \`screenshot-calc\` with goal: \u2026"* | *"Taking a screenshot of Calculator now."* |
|
|
7817
|
+
| *"I'll relay the user's instructions to the worker verbatim."* | *(say nothing \u2014 just do it)* |
|
|
7818
|
+
|
|
7819
|
+
If the user explicitly asks how you work, *then* you can explain the orchestrator/worker split. Otherwise: less is more.
|
|
7820
|
+
|
|
8413
7821
|
### How to write a worker goal (and what NOT to put in it)
|
|
8414
7822
|
|
|
8415
7823
|
You delegate; the worker executes. Stay at the **what** level, not the **how**.
|
|
@@ -8417,7 +7825,7 @@ You delegate; the worker executes. Stay at the **what** level, not the **how**.
|
|
|
8417
7825
|
**DO** put in the goal:
|
|
8418
7826
|
|
|
8419
7827
|
- The end objective ("open the macOS Weather app and capture the forecast for Anchorage as a screen recording").
|
|
8420
|
-
- 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\`).
|
|
8421
7829
|
- Acceptance criteria ("verify the city name is visible in the screenshot before reporting done").
|
|
8422
7830
|
- Routing back ("post the recording URL to Slack channel C0123 thread 1700.001").
|
|
8423
7831
|
|
|
@@ -8450,7 +7858,7 @@ Bad goal (don't do this):
|
|
|
8450
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'..."
|
|
8451
7859
|
|
|
8452
7860
|
Good goal (do this):
|
|
8453
|
-
> "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."
|
|
8454
7862
|
`;
|
|
8455
7863
|
}
|
|
8456
7864
|
function createSummaryPrompt(conversationHistory) {
|
|
@@ -8669,17 +8077,17 @@ __export(conversation_archive_exports, {
|
|
|
8669
8077
|
getHistoryDir: () => getHistoryDir,
|
|
8670
8078
|
listSessionArchives: () => listSessionArchives
|
|
8671
8079
|
});
|
|
8672
|
-
import { existsSync as
|
|
8673
|
-
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";
|
|
8674
8082
|
function getHistoryDir() {
|
|
8675
|
-
const dir =
|
|
8676
|
-
if (!
|
|
8083
|
+
const dir = join8(ensureAppDataDirectory(), "history");
|
|
8084
|
+
if (!existsSync15(dir)) mkdirSync5(dir, { recursive: true });
|
|
8677
8085
|
return dir;
|
|
8678
8086
|
}
|
|
8679
8087
|
function appendTurn(turn) {
|
|
8680
8088
|
try {
|
|
8681
8089
|
const dir = getHistoryDir();
|
|
8682
|
-
const path =
|
|
8090
|
+
const path = join8(dir, `${turn.sessionId}.jsonl`);
|
|
8683
8091
|
const line = JSON.stringify({
|
|
8684
8092
|
ts: turn.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
8685
8093
|
sessionId: turn.sessionId,
|
|
@@ -8708,7 +8116,7 @@ function flattenContent(content) {
|
|
|
8708
8116
|
}
|
|
8709
8117
|
function listSessionArchives() {
|
|
8710
8118
|
const dir = getHistoryDir();
|
|
8711
|
-
if (!
|
|
8119
|
+
if (!existsSync15(dir)) return [];
|
|
8712
8120
|
return readdirSync2(dir).filter((f) => f.endsWith(".jsonl"));
|
|
8713
8121
|
}
|
|
8714
8122
|
var init_conversation_archive = __esm({
|
|
@@ -8780,6 +8188,18 @@ function repairToolPairing(messages) {
|
|
|
8780
8188
|
}
|
|
8781
8189
|
return repaired;
|
|
8782
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
|
+
}
|
|
8783
8203
|
var TOOL_OUTPUT_TRIM_CHARS, COMPACTABLE_TOOLS, ContextManager;
|
|
8784
8204
|
var init_context = __esm({
|
|
8785
8205
|
"src/agent/context.ts"() {
|
|
@@ -8837,6 +8257,7 @@ ${summaryContent}`
|
|
|
8837
8257
|
];
|
|
8838
8258
|
}
|
|
8839
8259
|
messages = repairToolPairing(messages);
|
|
8260
|
+
messages = ensureEndsWithUserOrTool(messages);
|
|
8840
8261
|
return messages;
|
|
8841
8262
|
}
|
|
8842
8263
|
// ---------------------------------------------------------------------------
|
|
@@ -9030,7 +8451,8 @@ ${summaryContent}`
|
|
|
9030
8451
|
}
|
|
9031
8452
|
}
|
|
9032
8453
|
async addResponseMessages(messages) {
|
|
9033
|
-
|
|
8454
|
+
const safe = repairToolPairing(messages);
|
|
8455
|
+
await messageQueries.addMany(this.sessionId, safe);
|
|
9034
8456
|
try {
|
|
9035
8457
|
const { appendTurn: appendTurn2, flattenContent: flattenContent2 } = await Promise.resolve().then(() => (init_conversation_archive(), conversation_archive_exports));
|
|
9036
8458
|
const { sessionQueries: sessionQueries2 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
@@ -9471,7 +8893,7 @@ var init_messenger = __esm({
|
|
|
9471
8893
|
});
|
|
9472
8894
|
|
|
9473
8895
|
// src/orchestrator/schedules-store.ts
|
|
9474
|
-
import { nanoid as
|
|
8896
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
9475
8897
|
async function readOrch(orchestratorSessionId) {
|
|
9476
8898
|
const s = await sessionQueries.getById(orchestratorSessionId);
|
|
9477
8899
|
if (!s) return null;
|
|
@@ -9486,7 +8908,7 @@ async function createSchedule(orchestratorSessionId, input) {
|
|
|
9486
8908
|
const data = await readOrch(orchestratorSessionId);
|
|
9487
8909
|
if (!data) throw new Error("orchestrator session not found");
|
|
9488
8910
|
const row = {
|
|
9489
|
-
id: `sch_${
|
|
8911
|
+
id: `sch_${nanoid4(10)}`,
|
|
9490
8912
|
name: input.name,
|
|
9491
8913
|
cron: input.cron,
|
|
9492
8914
|
prompt: input.prompt,
|
|
@@ -9523,7 +8945,7 @@ var init_schedules_store = __esm({
|
|
|
9523
8945
|
|
|
9524
8946
|
// src/orchestrator/webhooks-store.ts
|
|
9525
8947
|
import { randomBytes } from "crypto";
|
|
9526
|
-
import { nanoid as
|
|
8948
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
9527
8949
|
function newToken() {
|
|
9528
8950
|
return randomBytes(24).toString("base64url");
|
|
9529
8951
|
}
|
|
@@ -9540,7 +8962,7 @@ async function createWebhook(orchestratorSessionId, input) {
|
|
|
9540
8962
|
const data = await readOrch2(orchestratorSessionId);
|
|
9541
8963
|
if (!data) throw new Error("orchestrator session not found");
|
|
9542
8964
|
const row = {
|
|
9543
|
-
id: `whk_${
|
|
8965
|
+
id: `whk_${nanoid5(10)}`,
|
|
9544
8966
|
name: input.name,
|
|
9545
8967
|
token: newToken(),
|
|
9546
8968
|
wake: input.wake ?? "now",
|
|
@@ -9596,8 +9018,8 @@ var init_webhooks_store = __esm({
|
|
|
9596
9018
|
});
|
|
9597
9019
|
|
|
9598
9020
|
// src/tools/orchestrator-actions.ts
|
|
9599
|
-
import { tool as
|
|
9600
|
-
import { z as
|
|
9021
|
+
import { tool as tool13 } from "ai";
|
|
9022
|
+
import { z as z14 } from "zod";
|
|
9601
9023
|
async function api2(baseUrl, path, init = {}) {
|
|
9602
9024
|
const res = await fetch(`${baseUrl}${path}`, {
|
|
9603
9025
|
method: init.method || "GET",
|
|
@@ -9623,7 +9045,7 @@ function previewMessageContent(content) {
|
|
|
9623
9045
|
return "";
|
|
9624
9046
|
}
|
|
9625
9047
|
function buildAgentTool(opts) {
|
|
9626
|
-
return
|
|
9048
|
+
return tool13({
|
|
9627
9049
|
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).",
|
|
9628
9050
|
inputSchema: agentInputSchema,
|
|
9629
9051
|
execute: async (input) => {
|
|
@@ -9726,7 +9148,7 @@ function buildAgentTool(opts) {
|
|
|
9726
9148
|
});
|
|
9727
9149
|
}
|
|
9728
9150
|
function buildMessengerTool() {
|
|
9729
|
-
return
|
|
9151
|
+
return tool13({
|
|
9730
9152
|
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.",
|
|
9731
9153
|
inputSchema: messengerInputSchema,
|
|
9732
9154
|
execute: async (input) => {
|
|
@@ -9748,7 +9170,7 @@ function buildMessengerTool() {
|
|
|
9748
9170
|
});
|
|
9749
9171
|
}
|
|
9750
9172
|
function buildScheduleTool(opts) {
|
|
9751
|
-
return
|
|
9173
|
+
return tool13({
|
|
9752
9174
|
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.",
|
|
9753
9175
|
inputSchema: scheduleInputSchema,
|
|
9754
9176
|
execute: async (input) => {
|
|
@@ -9791,7 +9213,7 @@ function buildWebhookUrl(opts, token) {
|
|
|
9791
9213
|
return `${base}${webhookPrefix2}/inbox/${token}`;
|
|
9792
9214
|
}
|
|
9793
9215
|
function buildWebhookTool(opts) {
|
|
9794
|
-
return
|
|
9216
|
+
return tool13({
|
|
9795
9217
|
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.",
|
|
9796
9218
|
inputSchema: webhookInputSchema,
|
|
9797
9219
|
execute: async (input) => {
|
|
@@ -9840,66 +9262,66 @@ var init_orchestrator_actions = __esm({
|
|
|
9840
9262
|
init_schedules_store();
|
|
9841
9263
|
init_config();
|
|
9842
9264
|
init_webhooks_store();
|
|
9843
|
-
AGENT_STATUS_ENUM =
|
|
9844
|
-
agentInputSchema =
|
|
9845
|
-
action:
|
|
9265
|
+
AGENT_STATUS_ENUM = z14.enum(["running", "needs_attention", "completed", "failed", "idle"]);
|
|
9266
|
+
agentInputSchema = z14.object({
|
|
9267
|
+
action: z14.enum(["list", "get", "spawn", "message", "answer_question", "stop"]).describe("Which agent operation to perform."),
|
|
9846
9268
|
// list
|
|
9847
9269
|
status: AGENT_STATUS_ENUM.optional().describe("list only: filter to one status."),
|
|
9848
|
-
limit:
|
|
9270
|
+
limit: z14.number().int().min(1).max(100).optional().describe("list only: max rows."),
|
|
9849
9271
|
// get / message / answer_question / stop
|
|
9850
|
-
id:
|
|
9851
|
-
recentMessages:
|
|
9272
|
+
id: z14.string().optional().describe("get | message | answer_question | stop: the agent (session) id."),
|
|
9273
|
+
recentMessages: z14.number().int().min(0).max(50).optional().describe("get only: how many recent messages to include."),
|
|
9852
9274
|
// spawn
|
|
9853
|
-
name:
|
|
9854
|
-
goal:
|
|
9855
|
-
outputSchema:
|
|
9275
|
+
name: z14.string().optional().describe("spawn only: short human-readable label."),
|
|
9276
|
+
goal: z14.string().optional().describe("spawn only: the worker's self-contained instruction."),
|
|
9277
|
+
outputSchema: z14.record(z14.string(), z14.unknown()).optional().describe(
|
|
9856
9278
|
'spawn only: JSON Schema for the worker result. Defaults to {type:"object", properties:{summary:{type:"string"}}, required:["summary"]}.'
|
|
9857
9279
|
),
|
|
9858
|
-
model:
|
|
9859
|
-
workingDirectory:
|
|
9860
|
-
maxIterations:
|
|
9280
|
+
model: z14.string().optional().describe("spawn only: model override."),
|
|
9281
|
+
workingDirectory: z14.string().optional().describe("spawn only: working directory override."),
|
|
9282
|
+
maxIterations: z14.number().int().min(1).max(500).optional().describe("spawn only."),
|
|
9861
9283
|
// message
|
|
9862
|
-
text:
|
|
9863
|
-
force:
|
|
9284
|
+
text: z14.string().optional().describe("message only: the text to deliver to the worker."),
|
|
9285
|
+
force: z14.boolean().optional().describe("message only: soft-interrupt the current step."),
|
|
9864
9286
|
// answer_question
|
|
9865
|
-
questionId:
|
|
9866
|
-
answer:
|
|
9287
|
+
questionId: z14.string().optional().describe("answer_question only: pending question id (e.g. q_abc123)."),
|
|
9288
|
+
answer: z14.string().optional().describe("answer_question only: your answer.")
|
|
9867
9289
|
});
|
|
9868
|
-
messengerInputSchema =
|
|
9869
|
-
action:
|
|
9290
|
+
messengerInputSchema = z14.object({
|
|
9291
|
+
action: z14.enum(["list_channels", "post"]),
|
|
9870
9292
|
// post
|
|
9871
|
-
channel:
|
|
9872
|
-
to:
|
|
9873
|
-
text:
|
|
9874
|
-
threadTs:
|
|
9875
|
-
subject:
|
|
9293
|
+
channel: z14.string().optional().describe('post only: channel id (e.g. "slack").'),
|
|
9294
|
+
to: z14.string().optional().describe('post only: destination. Slack: channel id (C0123), user id (U0123), or "#channel-name".'),
|
|
9295
|
+
text: z14.string().optional().describe("post only: message body."),
|
|
9296
|
+
threadTs: z14.string().optional().describe("post + slack: reply in this thread."),
|
|
9297
|
+
subject: z14.string().optional().describe("post + email: subject (future).")
|
|
9876
9298
|
});
|
|
9877
|
-
scheduleInputSchema =
|
|
9878
|
-
action:
|
|
9299
|
+
scheduleInputSchema = z14.object({
|
|
9300
|
+
action: z14.enum(["create", "list", "update", "delete", "pause", "resume"]),
|
|
9879
9301
|
// create / update
|
|
9880
|
-
name:
|
|
9881
|
-
cron:
|
|
9882
|
-
prompt:
|
|
9883
|
-
replyChannel:
|
|
9302
|
+
name: z14.string().optional().describe("create | update"),
|
|
9303
|
+
cron: z14.string().optional().describe('create | update: 5-field cron (e.g. "0 9 * * 1-5" = weekdays at 9am).'),
|
|
9304
|
+
prompt: z14.string().optional().describe("create | update: the prompt injected when the schedule fires."),
|
|
9305
|
+
replyChannel: z14.string().optional().describe("create | update: default channel id for orchestrator replies."),
|
|
9884
9306
|
// update / delete / pause / resume
|
|
9885
|
-
id:
|
|
9886
|
-
enabled:
|
|
9307
|
+
id: z14.string().optional().describe("update | delete | pause | resume: schedule id."),
|
|
9308
|
+
enabled: z14.boolean().optional().describe("update only.")
|
|
9887
9309
|
});
|
|
9888
|
-
webhookInputSchema =
|
|
9889
|
-
action:
|
|
9890
|
-
name:
|
|
9891
|
-
wake:
|
|
9892
|
-
template:
|
|
9893
|
-
id:
|
|
9894
|
-
rotateToken:
|
|
9310
|
+
webhookInputSchema = z14.object({
|
|
9311
|
+
action: z14.enum(["create", "list", "update", "delete"]),
|
|
9312
|
+
name: z14.string().optional().describe("create | update."),
|
|
9313
|
+
wake: z14.enum(["now", "next"]).optional().describe("create | update: now = wake orchestrator immediately; next = add as context."),
|
|
9314
|
+
template: z14.string().optional().describe("create | update: mustache-style template ({{path.to.field}}). Defaults to pretty-printed JSON."),
|
|
9315
|
+
id: z14.string().optional().describe("update | delete: webhook id."),
|
|
9316
|
+
rotateToken: z14.boolean().optional().describe("update only: regenerate the URL token.")
|
|
9895
9317
|
});
|
|
9896
9318
|
}
|
|
9897
9319
|
});
|
|
9898
9320
|
|
|
9899
9321
|
// src/integrations/mcp/store.ts
|
|
9900
|
-
import { nanoid as
|
|
9901
|
-
import { existsSync as
|
|
9902
|
-
import { resolve as resolve10, join as
|
|
9322
|
+
import { nanoid as nanoid6 } from "nanoid";
|
|
9323
|
+
import { existsSync as existsSync16, readFileSync as readFileSync7 } from "fs";
|
|
9324
|
+
import { resolve as resolve10, join as join9 } from "path";
|
|
9903
9325
|
function readServers() {
|
|
9904
9326
|
try {
|
|
9905
9327
|
const cfg = getConfig();
|
|
@@ -9911,12 +9333,12 @@ function readServers() {
|
|
|
9911
9333
|
function refreshMcpServersFromDisk() {
|
|
9912
9334
|
const candidates = [
|
|
9913
9335
|
resolve10(process.cwd(), "sparkecoder.config.json"),
|
|
9914
|
-
|
|
9336
|
+
join9(ensureAppDataDirectory(), "sparkecoder.config.json")
|
|
9915
9337
|
];
|
|
9916
9338
|
for (const path of candidates) {
|
|
9917
|
-
if (!
|
|
9339
|
+
if (!existsSync16(path)) continue;
|
|
9918
9340
|
try {
|
|
9919
|
-
const raw = JSON.parse(
|
|
9341
|
+
const raw = JSON.parse(readFileSync7(path, "utf-8"));
|
|
9920
9342
|
const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
|
|
9921
9343
|
setMcpServers(servers2);
|
|
9922
9344
|
return servers2;
|
|
@@ -9935,7 +9357,7 @@ function createMcpServer(input) {
|
|
|
9935
9357
|
const all = readServers();
|
|
9936
9358
|
validateInput(input);
|
|
9937
9359
|
const row = {
|
|
9938
|
-
id: `mcp_${
|
|
9360
|
+
id: `mcp_${nanoid6(10)}`,
|
|
9939
9361
|
name: sanitizeName(input.name),
|
|
9940
9362
|
transport: input.transport,
|
|
9941
9363
|
url: input.url,
|
|
@@ -10526,15 +9948,15 @@ var recorder_exports = {};
|
|
|
10526
9948
|
__export(recorder_exports, {
|
|
10527
9949
|
FrameRecorder: () => FrameRecorder
|
|
10528
9950
|
});
|
|
10529
|
-
import { exec as
|
|
10530
|
-
import { promisify as
|
|
9951
|
+
import { exec as exec5 } from "child_process";
|
|
9952
|
+
import { promisify as promisify5 } from "util";
|
|
10531
9953
|
import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
|
|
10532
|
-
import { join as
|
|
10533
|
-
import { tmpdir
|
|
10534
|
-
import { nanoid as
|
|
9954
|
+
import { join as join10 } from "path";
|
|
9955
|
+
import { tmpdir } from "os";
|
|
9956
|
+
import { nanoid as nanoid7 } from "nanoid";
|
|
10535
9957
|
async function checkFfmpeg() {
|
|
10536
9958
|
try {
|
|
10537
|
-
await
|
|
9959
|
+
await execAsync5("ffmpeg -version", { timeout: 5e3 });
|
|
10538
9960
|
return true;
|
|
10539
9961
|
} catch {
|
|
10540
9962
|
return false;
|
|
@@ -10546,11 +9968,11 @@ async function cleanup(dir) {
|
|
|
10546
9968
|
} catch {
|
|
10547
9969
|
}
|
|
10548
9970
|
}
|
|
10549
|
-
var
|
|
9971
|
+
var execAsync5, FrameRecorder;
|
|
10550
9972
|
var init_recorder = __esm({
|
|
10551
9973
|
"src/browser/recorder.ts"() {
|
|
10552
9974
|
"use strict";
|
|
10553
|
-
|
|
9975
|
+
execAsync5 = promisify5(exec5);
|
|
10554
9976
|
FrameRecorder = class {
|
|
10555
9977
|
frames = [];
|
|
10556
9978
|
startTime = null;
|
|
@@ -10586,21 +10008,21 @@ var init_recorder = __esm({
|
|
|
10586
10008
|
*/
|
|
10587
10009
|
async encode() {
|
|
10588
10010
|
if (this.frames.length === 0) return null;
|
|
10589
|
-
const workDir =
|
|
10011
|
+
const workDir = join10(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
|
|
10590
10012
|
await mkdir4(workDir, { recursive: true });
|
|
10591
10013
|
try {
|
|
10592
10014
|
for (let i = 0; i < this.frames.length; i++) {
|
|
10593
|
-
const framePath =
|
|
10015
|
+
const framePath = join10(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
10594
10016
|
await writeFile5(framePath, this.frames[i].data);
|
|
10595
10017
|
}
|
|
10596
10018
|
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
10597
10019
|
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
10598
10020
|
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
10599
|
-
const outputPath =
|
|
10021
|
+
const outputPath = join10(workDir, `recording_${this.sessionId}.mp4`);
|
|
10600
10022
|
const hasFfmpeg = await checkFfmpeg();
|
|
10601
10023
|
if (hasFfmpeg) {
|
|
10602
|
-
await
|
|
10603
|
-
`ffmpeg -y -framerate ${clampedFps} -i "${
|
|
10024
|
+
await execAsync5(
|
|
10025
|
+
`ffmpeg -y -framerate ${clampedFps} -i "${join10(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
|
|
10604
10026
|
{ timeout: 12e4 }
|
|
10605
10027
|
);
|
|
10606
10028
|
} else {
|
|
@@ -10612,7 +10034,7 @@ var init_recorder = __esm({
|
|
|
10612
10034
|
const files = await readdir5(workDir);
|
|
10613
10035
|
for (const f of files) {
|
|
10614
10036
|
if (f.startsWith("frame_")) {
|
|
10615
|
-
await unlink2(
|
|
10037
|
+
await unlink2(join10(workDir, f)).catch(() => {
|
|
10616
10038
|
});
|
|
10617
10039
|
}
|
|
10618
10040
|
}
|
|
@@ -10637,11 +10059,11 @@ var init_recorder = __esm({
|
|
|
10637
10059
|
import {
|
|
10638
10060
|
streamText as streamText2,
|
|
10639
10061
|
generateText as generateText3,
|
|
10640
|
-
tool as
|
|
10062
|
+
tool as tool14,
|
|
10641
10063
|
stepCountIs as stepCountIs2
|
|
10642
10064
|
} from "ai";
|
|
10643
|
-
import { z as
|
|
10644
|
-
import { nanoid as
|
|
10065
|
+
import { z as z15 } from "zod";
|
|
10066
|
+
import { nanoid as nanoid8 } from "nanoid";
|
|
10645
10067
|
function anySignal(signals) {
|
|
10646
10068
|
const ctrl = new AbortController();
|
|
10647
10069
|
for (const s of signals) {
|
|
@@ -10710,14 +10132,10 @@ var init_agent = __esm({
|
|
|
10710
10132
|
*/
|
|
10711
10133
|
async createToolsWithCallbacks(options) {
|
|
10712
10134
|
const config = getConfig();
|
|
10713
|
-
const sessionConfig = this.session.config || {};
|
|
10714
10135
|
const tools = await createTools({
|
|
10715
10136
|
sessionId: this.session.id,
|
|
10716
10137
|
workingDirectory: this.session.workingDirectory,
|
|
10717
10138
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
10718
|
-
enableComputerUse: sessionConfig.computerUseEnabled === true,
|
|
10719
|
-
computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
|
|
10720
|
-
computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight,
|
|
10721
10139
|
onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
|
|
10722
10140
|
onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
|
|
10723
10141
|
onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0
|
|
@@ -10767,14 +10185,10 @@ var init_agent = __esm({
|
|
|
10767
10185
|
keepRecentMessages: config.context?.keepRecentMessages || 10,
|
|
10768
10186
|
autoSummarize: config.context?.autoSummarize ?? true
|
|
10769
10187
|
});
|
|
10770
|
-
const sessionConfig = session.config || {};
|
|
10771
10188
|
const tools = await createTools({
|
|
10772
10189
|
sessionId: session.id,
|
|
10773
10190
|
workingDirectory: session.workingDirectory,
|
|
10774
|
-
skillsDirectories: config.resolvedSkillsDirectories
|
|
10775
|
-
enableComputerUse: sessionConfig.computerUseEnabled === true,
|
|
10776
|
-
computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
|
|
10777
|
-
computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight
|
|
10191
|
+
skillsDirectories: config.resolvedSkillsDirectories
|
|
10778
10192
|
});
|
|
10779
10193
|
if (session.config?.role === "orchestrator") {
|
|
10780
10194
|
const baseUrl = `http://127.0.0.1:${config.server?.port ?? 3141}`;
|
|
@@ -11012,14 +10426,10 @@ ${personality.trim()}`;
|
|
|
11012
10426
|
});
|
|
11013
10427
|
}
|
|
11014
10428
|
};
|
|
11015
|
-
const taskSessionConfig = this.session.config || {};
|
|
11016
10429
|
const taskTools = await createTools({
|
|
11017
10430
|
sessionId: this.session.id,
|
|
11018
10431
|
workingDirectory: this.session.workingDirectory,
|
|
11019
10432
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
11020
|
-
enableComputerUse: taskSessionConfig.computerUseEnabled === true,
|
|
11021
|
-
computerUseDisplayWidth: taskSessionConfig.computerUseDisplayWidth,
|
|
11022
|
-
computerUseDisplayHeight: taskSessionConfig.computerUseDisplayHeight,
|
|
11023
10433
|
onBashProgress: bashProgressHandler,
|
|
11024
10434
|
onWriteFileProgress: (progress) => {
|
|
11025
10435
|
options.onToolProgress?.({ toolName: "write_file", data: progress });
|
|
@@ -11367,11 +10777,11 @@ ${p.text}` : p.text;
|
|
|
11367
10777
|
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
11368
10778
|
if (!isRemoteConfigured2()) return [];
|
|
11369
10779
|
const { readFile: readFile12 } = await import("fs/promises");
|
|
11370
|
-
const { join:
|
|
10780
|
+
const { join: join17, basename: basename6 } = await import("path");
|
|
11371
10781
|
const urls = [];
|
|
11372
10782
|
for (const filePath of filePaths) {
|
|
11373
10783
|
try {
|
|
11374
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
10784
|
+
const fullPath = filePath.startsWith("/") ? filePath : join17(this.session.workingDirectory, filePath);
|
|
11375
10785
|
const fileName = basename6(fullPath);
|
|
11376
10786
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
11377
10787
|
const mimeMap = {
|
|
@@ -11429,11 +10839,11 @@ ${p.text}` : p.text;
|
|
|
11429
10839
|
wrappedTools[name] = originalTool;
|
|
11430
10840
|
continue;
|
|
11431
10841
|
}
|
|
11432
|
-
wrappedTools[name] =
|
|
10842
|
+
wrappedTools[name] = tool14({
|
|
11433
10843
|
description: originalTool.description || "",
|
|
11434
|
-
inputSchema: originalTool.inputSchema ||
|
|
10844
|
+
inputSchema: originalTool.inputSchema || z15.object({}),
|
|
11435
10845
|
execute: async (input, toolOptions) => {
|
|
11436
|
-
const toolCallId = toolOptions.toolCallId ||
|
|
10846
|
+
const toolCallId = toolOptions.toolCallId || nanoid8();
|
|
11437
10847
|
const execution = toolExecutionQueries.create({
|
|
11438
10848
|
sessionId: this.session.id,
|
|
11439
10849
|
toolName: name,
|
|
@@ -11576,19 +10986,19 @@ var init_session_lock = __esm({
|
|
|
11576
10986
|
});
|
|
11577
10987
|
|
|
11578
10988
|
// src/orchestrator/webhook-events.ts
|
|
11579
|
-
import { existsSync as
|
|
11580
|
-
import { dirname as dirname6, join as
|
|
11581
|
-
import { nanoid as
|
|
10989
|
+
import { existsSync as existsSync17, readFileSync as readFileSync8, appendFileSync as appendFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync6 } from "fs";
|
|
10990
|
+
import { dirname as dirname6, join as join11 } from "path";
|
|
10991
|
+
import { nanoid as nanoid9 } from "nanoid";
|
|
11582
10992
|
function logFilePath() {
|
|
11583
|
-
return
|
|
10993
|
+
return join11(getAppDataDirectory(), "webhook-events.jsonl");
|
|
11584
10994
|
}
|
|
11585
10995
|
function ensureLoaded() {
|
|
11586
10996
|
if (cache !== null) return cache;
|
|
11587
10997
|
cache = [];
|
|
11588
10998
|
try {
|
|
11589
10999
|
const p = logFilePath();
|
|
11590
|
-
if (!
|
|
11591
|
-
const lines =
|
|
11000
|
+
if (!existsSync17(p)) return cache;
|
|
11001
|
+
const lines = readFileSync8(p, "utf-8").split("\n").filter(Boolean);
|
|
11592
11002
|
for (const line of lines) {
|
|
11593
11003
|
try {
|
|
11594
11004
|
cache.push(JSON.parse(line));
|
|
@@ -11612,14 +11022,14 @@ function appendEvent(ev) {
|
|
|
11612
11022
|
if (list.length > MAX_EVENTS) list.shift();
|
|
11613
11023
|
try {
|
|
11614
11024
|
const p = logFilePath();
|
|
11615
|
-
|
|
11025
|
+
mkdirSync6(dirname6(p), { recursive: true });
|
|
11616
11026
|
appendFileSync3(p, JSON.stringify(ev) + "\n");
|
|
11617
11027
|
} catch {
|
|
11618
11028
|
}
|
|
11619
11029
|
}
|
|
11620
11030
|
function recordEvent(ev) {
|
|
11621
11031
|
const full = {
|
|
11622
|
-
id: ev.id ??
|
|
11032
|
+
id: ev.id ?? nanoid9(),
|
|
11623
11033
|
ts: ev.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
11624
11034
|
source: ev.source,
|
|
11625
11035
|
status: ev.status,
|
|
@@ -11643,7 +11053,7 @@ function updateEvent(id, patch) {
|
|
|
11643
11053
|
list[i] = { ...list[i], ...patch };
|
|
11644
11054
|
try {
|
|
11645
11055
|
const p = logFilePath();
|
|
11646
|
-
|
|
11056
|
+
mkdirSync6(dirname6(p), { recursive: true });
|
|
11647
11057
|
writeFileSync3(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
11648
11058
|
} catch {
|
|
11649
11059
|
}
|
|
@@ -12018,6 +11428,148 @@ var init_api = __esm({
|
|
|
12018
11428
|
}
|
|
12019
11429
|
});
|
|
12020
11430
|
|
|
11431
|
+
// src/utils/desktop-permissions.ts
|
|
11432
|
+
var desktop_permissions_exports = {};
|
|
11433
|
+
__export(desktop_permissions_exports, {
|
|
11434
|
+
detectScreenSize: () => detectScreenSize,
|
|
11435
|
+
getResponsibleAppName: () => getResponsibleAppName,
|
|
11436
|
+
hasAccessibilityPermissions: () => hasAccessibilityPermissions,
|
|
11437
|
+
hasScreenRecordingPermissions: () => hasScreenRecordingPermissions,
|
|
11438
|
+
isCliclickInstalled: () => isCliclickInstalled,
|
|
11439
|
+
isMacOs: () => isMacOs,
|
|
11440
|
+
openSystemSettings: () => openSystemSettings
|
|
11441
|
+
});
|
|
11442
|
+
import { exec as exec7 } from "child_process";
|
|
11443
|
+
import { promisify as promisify7 } from "util";
|
|
11444
|
+
function isMacOs() {
|
|
11445
|
+
return process.platform === "darwin";
|
|
11446
|
+
}
|
|
11447
|
+
async function isCliclickInstalled() {
|
|
11448
|
+
try {
|
|
11449
|
+
await execAsync7("command -v cliclick", { timeout: 2e3 });
|
|
11450
|
+
return true;
|
|
11451
|
+
} catch {
|
|
11452
|
+
return false;
|
|
11453
|
+
}
|
|
11454
|
+
}
|
|
11455
|
+
async function runJxa(script) {
|
|
11456
|
+
try {
|
|
11457
|
+
const escaped = script.replace(/'/g, `'\\''`);
|
|
11458
|
+
const { stdout } = await execAsync7(`osascript -l JavaScript -e '${escaped}'`, {
|
|
11459
|
+
timeout: 5e3
|
|
11460
|
+
});
|
|
11461
|
+
return JSON.parse(stdout.trim());
|
|
11462
|
+
} catch {
|
|
11463
|
+
return null;
|
|
11464
|
+
}
|
|
11465
|
+
}
|
|
11466
|
+
async function hasAccessibilityPermissions() {
|
|
11467
|
+
try {
|
|
11468
|
+
const { stderr } = await execAsync7("cliclick p:.", { timeout: 3e3 });
|
|
11469
|
+
if (stderr && ACCESSIBILITY_DENIED_RE.test(stderr)) {
|
|
11470
|
+
return { ok: false, error: stderr.trim().split("\n")[0] };
|
|
11471
|
+
}
|
|
11472
|
+
return { ok: true };
|
|
11473
|
+
} catch (err) {
|
|
11474
|
+
return { ok: false, error: err?.message || String(err) };
|
|
11475
|
+
}
|
|
11476
|
+
}
|
|
11477
|
+
async function hasScreenRecordingPermissions() {
|
|
11478
|
+
const result = await runJxa(
|
|
11479
|
+
`ObjC.import("Cocoa");
|
|
11480
|
+
ObjC.import("CoreGraphics");
|
|
11481
|
+
ObjC.bindFunction("CGPreflightScreenCaptureAccess", ["bool", []]);
|
|
11482
|
+
JSON.stringify({ hasAccess: !!$.CGPreflightScreenCaptureAccess() });`
|
|
11483
|
+
);
|
|
11484
|
+
return result?.hasAccess ?? false;
|
|
11485
|
+
}
|
|
11486
|
+
async function openSystemSettings(pane) {
|
|
11487
|
+
const url = pane === "accessibility" ? "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility" : "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
|
|
11488
|
+
try {
|
|
11489
|
+
await execAsync7(`open '${url}'`, { timeout: 3e3 });
|
|
11490
|
+
} catch {
|
|
11491
|
+
}
|
|
11492
|
+
}
|
|
11493
|
+
async function detectScreenSize() {
|
|
11494
|
+
try {
|
|
11495
|
+
const { stdout } = await execAsync7("system_profiler SPDisplaysDataType -json", {
|
|
11496
|
+
timeout: 5e3,
|
|
11497
|
+
maxBuffer: 4 * 1024 * 1024
|
|
11498
|
+
});
|
|
11499
|
+
const data = JSON.parse(stdout);
|
|
11500
|
+
const cards = data.SPDisplaysDataType ?? [];
|
|
11501
|
+
const all = cards.flatMap((c) => c.spdisplays_ndrvs ?? []);
|
|
11502
|
+
if (all.length === 0) return null;
|
|
11503
|
+
const primary = all.find((d) => d.spdisplays_main === "spdisplays_yes") ?? all[0];
|
|
11504
|
+
const pixels = parseDim(primary._spdisplays_pixels) ?? parseDim(primary._spdisplays_resolution);
|
|
11505
|
+
const points = parseDim(primary.spdisplays_resolution) ?? pixels;
|
|
11506
|
+
if (!points) return null;
|
|
11507
|
+
return {
|
|
11508
|
+
width: points.w,
|
|
11509
|
+
height: points.h,
|
|
11510
|
+
pixelWidth: pixels?.w,
|
|
11511
|
+
pixelHeight: pixels?.h,
|
|
11512
|
+
displayName: primary._name
|
|
11513
|
+
};
|
|
11514
|
+
} catch {
|
|
11515
|
+
return null;
|
|
11516
|
+
}
|
|
11517
|
+
}
|
|
11518
|
+
function parseDim(s) {
|
|
11519
|
+
if (!s) return null;
|
|
11520
|
+
const m = s.match(/(\d+)\s*x\s*(\d+)/i);
|
|
11521
|
+
if (!m) return null;
|
|
11522
|
+
const w = parseInt(m[1], 10);
|
|
11523
|
+
const h = parseInt(m[2], 10);
|
|
11524
|
+
if (!Number.isFinite(w) || !Number.isFinite(h)) return null;
|
|
11525
|
+
return { w, h };
|
|
11526
|
+
}
|
|
11527
|
+
async function getResponsibleAppName() {
|
|
11528
|
+
const termProgram = process.env.TERM_PROGRAM;
|
|
11529
|
+
if (termProgram) {
|
|
11530
|
+
const pretty = TERM_PROGRAM_NAMES[termProgram] ?? termProgram;
|
|
11531
|
+
return pretty;
|
|
11532
|
+
}
|
|
11533
|
+
try {
|
|
11534
|
+
let pid = process.ppid;
|
|
11535
|
+
for (let i = 0; i < 12 && pid && pid !== 1; i++) {
|
|
11536
|
+
const { stdout } = await execAsync7(`ps -o ppid=,comm= -p ${pid}`, { timeout: 1e3 });
|
|
11537
|
+
const trimmed = stdout.trim();
|
|
11538
|
+
if (!trimmed) break;
|
|
11539
|
+
const m = trimmed.match(/^\s*(\d+)\s+(.+)$/);
|
|
11540
|
+
if (!m) break;
|
|
11541
|
+
const parentPid = parseInt(m[1], 10);
|
|
11542
|
+
const comm = m[2];
|
|
11543
|
+
const bundle = comm.match(/\/([^/]+)\.app\//);
|
|
11544
|
+
if (bundle) return bundle[1];
|
|
11545
|
+
pid = parentPid;
|
|
11546
|
+
}
|
|
11547
|
+
} catch {
|
|
11548
|
+
}
|
|
11549
|
+
return "the terminal app you launched the agent from";
|
|
11550
|
+
}
|
|
11551
|
+
var execAsync7, ACCESSIBILITY_DENIED_RE, TERM_PROGRAM_NAMES;
|
|
11552
|
+
var init_desktop_permissions = __esm({
|
|
11553
|
+
"src/utils/desktop-permissions.ts"() {
|
|
11554
|
+
"use strict";
|
|
11555
|
+
execAsync7 = promisify7(exec7);
|
|
11556
|
+
ACCESSIBILITY_DENIED_RE = /(not allowed|not trusted|accessibility (privilege|client)|control your computer|privilege.{0,20}required|enable.{0,20}accessibility)/i;
|
|
11557
|
+
TERM_PROGRAM_NAMES = {
|
|
11558
|
+
Apple_Terminal: "Terminal",
|
|
11559
|
+
"iTerm.app": "iTerm",
|
|
11560
|
+
iTerm: "iTerm",
|
|
11561
|
+
vscode: "Visual Studio Code (or Cursor, if that's what you're using)",
|
|
11562
|
+
WarpTerminal: "Warp",
|
|
11563
|
+
ghostty: "Ghostty",
|
|
11564
|
+
Hyper: "Hyper",
|
|
11565
|
+
Tabby: "Tabby",
|
|
11566
|
+
Alacritty: "Alacritty",
|
|
11567
|
+
WezTerm: "WezTerm",
|
|
11568
|
+
kitty: "kitty"
|
|
11569
|
+
};
|
|
11570
|
+
}
|
|
11571
|
+
});
|
|
11572
|
+
|
|
12021
11573
|
// src/cli.ts
|
|
12022
11574
|
import { Command } from "commander";
|
|
12023
11575
|
import chalk from "chalk";
|
|
@@ -12033,8 +11585,8 @@ import { Hono as Hono9 } from "hono";
|
|
|
12033
11585
|
import { serve } from "@hono/node-server";
|
|
12034
11586
|
import { cors } from "hono/cors";
|
|
12035
11587
|
import { logger } from "hono/logger";
|
|
12036
|
-
import { existsSync as
|
|
12037
|
-
import { resolve as resolve11, dirname as dirname8, join as
|
|
11588
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
11589
|
+
import { resolve as resolve11, dirname as dirname8, join as join15 } from "path";
|
|
12038
11590
|
import { spawn as spawn2 } from "child_process";
|
|
12039
11591
|
import { createServer as createNetServer } from "net";
|
|
12040
11592
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -12047,11 +11599,11 @@ init_tmux();
|
|
|
12047
11599
|
init_checkpoints();
|
|
12048
11600
|
import { Hono } from "hono";
|
|
12049
11601
|
import { zValidator } from "@hono/zod-validator";
|
|
12050
|
-
import { z as
|
|
12051
|
-
import { existsSync as
|
|
11602
|
+
import { z as z16 } from "zod";
|
|
11603
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync7, writeFileSync as writeFileSync4, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
12052
11604
|
import { readdir as readdir6 } from "fs/promises";
|
|
12053
|
-
import { join as
|
|
12054
|
-
import { nanoid as
|
|
11605
|
+
import { join as join12, basename as basename5, extname as extname8, relative as relative9 } from "path";
|
|
11606
|
+
import { nanoid as nanoid10 } from "nanoid";
|
|
12055
11607
|
|
|
12056
11608
|
// src/tasks/agent-status.ts
|
|
12057
11609
|
init_questions();
|
|
@@ -12109,22 +11661,22 @@ function cleanupPendingInputs() {
|
|
|
12109
11661
|
}
|
|
12110
11662
|
}
|
|
12111
11663
|
}
|
|
12112
|
-
var createSessionSchema =
|
|
12113
|
-
name:
|
|
12114
|
-
workingDirectory:
|
|
12115
|
-
model:
|
|
12116
|
-
toolApprovals:
|
|
12117
|
-
// Optional full session-config passthrough (
|
|
12118
|
-
config:
|
|
12119
|
-
role:
|
|
11664
|
+
var createSessionSchema = z16.object({
|
|
11665
|
+
name: z16.string().optional(),
|
|
11666
|
+
workingDirectory: z16.string().optional(),
|
|
11667
|
+
model: z16.string().optional(),
|
|
11668
|
+
toolApprovals: z16.record(z16.string(), z16.boolean()).optional(),
|
|
11669
|
+
// Optional full session-config passthrough (role, persona, etc.)
|
|
11670
|
+
config: z16.record(z16.string(), z16.unknown()).optional(),
|
|
11671
|
+
role: z16.enum(["orchestrator", "worker", "chat"]).optional()
|
|
12120
11672
|
});
|
|
12121
|
-
var paginationQuerySchema =
|
|
12122
|
-
limit:
|
|
12123
|
-
offset:
|
|
12124
|
-
role:
|
|
11673
|
+
var paginationQuerySchema = z16.object({
|
|
11674
|
+
limit: z16.string().optional(),
|
|
11675
|
+
offset: z16.string().optional(),
|
|
11676
|
+
role: z16.enum(["orchestrator", "worker", "chat", "all"]).optional()
|
|
12125
11677
|
});
|
|
12126
|
-
var messagesQuerySchema =
|
|
12127
|
-
limit:
|
|
11678
|
+
var messagesQuerySchema = z16.object({
|
|
11679
|
+
limit: z16.string().optional()
|
|
12128
11680
|
});
|
|
12129
11681
|
sessions2.get(
|
|
12130
11682
|
"/",
|
|
@@ -12193,15 +11745,11 @@ sessions2.post(
|
|
|
12193
11745
|
async (c) => {
|
|
12194
11746
|
const body = c.req.valid("json");
|
|
12195
11747
|
const config = getConfig();
|
|
12196
|
-
const cuDefault = process.env.SPARKECODER_COMPUTER_USE === "1";
|
|
12197
11748
|
const baseConfig = body.config || {};
|
|
12198
11749
|
const mergedConfig = {
|
|
12199
11750
|
...baseConfig,
|
|
12200
11751
|
...body.toolApprovals ? { toolApprovals: body.toolApprovals } : {},
|
|
12201
|
-
...body.role ? { role: body.role } : {}
|
|
12202
|
-
// Turn on computer use by default if the server was launched with --enable-computer-use,
|
|
12203
|
-
// unless the client explicitly provided a value.
|
|
12204
|
-
...cuDefault && baseConfig.computerUseEnabled === void 0 ? { computerUseEnabled: true } : {}
|
|
11752
|
+
...body.role ? { role: body.role } : {}
|
|
12205
11753
|
};
|
|
12206
11754
|
const agent = await Agent.create({
|
|
12207
11755
|
name: body.name,
|
|
@@ -12378,10 +11926,10 @@ sessions2.get("/:id/tools", async (c) => {
|
|
|
12378
11926
|
count: executions.length
|
|
12379
11927
|
});
|
|
12380
11928
|
});
|
|
12381
|
-
var updateSessionSchema =
|
|
12382
|
-
model:
|
|
12383
|
-
name:
|
|
12384
|
-
toolApprovals:
|
|
11929
|
+
var updateSessionSchema = z16.object({
|
|
11930
|
+
model: z16.string().optional(),
|
|
11931
|
+
name: z16.string().optional(),
|
|
11932
|
+
toolApprovals: z16.record(z16.string(), z16.boolean()).optional()
|
|
12385
11933
|
});
|
|
12386
11934
|
sessions2.patch(
|
|
12387
11935
|
"/:id",
|
|
@@ -12451,10 +11999,10 @@ sessions2.post("/:id/clear", async (c) => {
|
|
|
12451
11999
|
await agent.clearContext();
|
|
12452
12000
|
return c.json({ success: true, sessionId: id });
|
|
12453
12001
|
});
|
|
12454
|
-
var injectMessageSchema =
|
|
12455
|
-
text:
|
|
12456
|
-
source:
|
|
12457
|
-
force:
|
|
12002
|
+
var injectMessageSchema = z16.object({
|
|
12003
|
+
text: z16.string().min(1),
|
|
12004
|
+
source: z16.enum(["orchestrator", "user", "system"]).optional(),
|
|
12005
|
+
force: z16.boolean().optional()
|
|
12458
12006
|
});
|
|
12459
12007
|
sessions2.post(
|
|
12460
12008
|
"/:id/messages",
|
|
@@ -12468,8 +12016,8 @@ sessions2.post(
|
|
|
12468
12016
|
return c.json({ success: true, sessionId: id, queued: true, force });
|
|
12469
12017
|
}
|
|
12470
12018
|
);
|
|
12471
|
-
var pendingInputSchema =
|
|
12472
|
-
text:
|
|
12019
|
+
var pendingInputSchema = z16.object({
|
|
12020
|
+
text: z16.string()
|
|
12473
12021
|
});
|
|
12474
12022
|
sessions2.post(
|
|
12475
12023
|
"/:id/pending-input",
|
|
@@ -12500,13 +12048,13 @@ sessions2.get("/:id/pending-input", async (c) => {
|
|
|
12500
12048
|
createdAt: pending.createdAt.toISOString()
|
|
12501
12049
|
});
|
|
12502
12050
|
});
|
|
12503
|
-
var devtoolsContextSchema =
|
|
12504
|
-
url:
|
|
12505
|
-
path:
|
|
12506
|
-
pageName:
|
|
12507
|
-
screenWidth:
|
|
12508
|
-
screenHeight:
|
|
12509
|
-
devicePixelRatio:
|
|
12051
|
+
var devtoolsContextSchema = z16.object({
|
|
12052
|
+
url: z16.string(),
|
|
12053
|
+
path: z16.string(),
|
|
12054
|
+
pageName: z16.string().optional(),
|
|
12055
|
+
screenWidth: z16.number().optional(),
|
|
12056
|
+
screenHeight: z16.number().optional(),
|
|
12057
|
+
devicePixelRatio: z16.number().optional()
|
|
12510
12058
|
});
|
|
12511
12059
|
sessions2.post(
|
|
12512
12060
|
"/:id/devtools-context",
|
|
@@ -12692,12 +12240,12 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
|
|
|
12692
12240
|
});
|
|
12693
12241
|
function getAttachmentsDir(sessionId) {
|
|
12694
12242
|
const appDataDir = getAppDataDirectory();
|
|
12695
|
-
return
|
|
12243
|
+
return join12(appDataDir, "attachments", sessionId);
|
|
12696
12244
|
}
|
|
12697
12245
|
function ensureAttachmentsDir(sessionId) {
|
|
12698
12246
|
const dir = getAttachmentsDir(sessionId);
|
|
12699
|
-
if (!
|
|
12700
|
-
|
|
12247
|
+
if (!existsSync18(dir)) {
|
|
12248
|
+
mkdirSync7(dir, { recursive: true });
|
|
12701
12249
|
}
|
|
12702
12250
|
return dir;
|
|
12703
12251
|
}
|
|
@@ -12708,12 +12256,12 @@ sessions2.get("/:id/attachments", async (c) => {
|
|
|
12708
12256
|
return c.json({ error: "Session not found" }, 404);
|
|
12709
12257
|
}
|
|
12710
12258
|
const dir = getAttachmentsDir(sessionId);
|
|
12711
|
-
if (!
|
|
12259
|
+
if (!existsSync18(dir)) {
|
|
12712
12260
|
return c.json({ sessionId, attachments: [], count: 0 });
|
|
12713
12261
|
}
|
|
12714
12262
|
const files = readdirSync3(dir);
|
|
12715
12263
|
const attachments = files.map((filename) => {
|
|
12716
|
-
const filePath =
|
|
12264
|
+
const filePath = join12(dir, filename);
|
|
12717
12265
|
const stats = statSync2(filePath);
|
|
12718
12266
|
return {
|
|
12719
12267
|
id: filename.split("_")[0],
|
|
@@ -12745,10 +12293,10 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
12745
12293
|
return c.json({ error: "No file provided" }, 400);
|
|
12746
12294
|
}
|
|
12747
12295
|
const dir = ensureAttachmentsDir(sessionId);
|
|
12748
|
-
const id =
|
|
12296
|
+
const id = nanoid10(10);
|
|
12749
12297
|
const ext = extname8(file.name) || "";
|
|
12750
12298
|
const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
12751
|
-
const filePath =
|
|
12299
|
+
const filePath = join12(dir, safeFilename);
|
|
12752
12300
|
const arrayBuffer = await file.arrayBuffer();
|
|
12753
12301
|
writeFileSync4(filePath, Buffer.from(arrayBuffer));
|
|
12754
12302
|
return c.json({
|
|
@@ -12771,10 +12319,10 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
12771
12319
|
return c.json({ error: "Missing filename or data" }, 400);
|
|
12772
12320
|
}
|
|
12773
12321
|
const dir = ensureAttachmentsDir(sessionId);
|
|
12774
|
-
const id =
|
|
12322
|
+
const id = nanoid10(10);
|
|
12775
12323
|
const ext = extname8(body.filename) || "";
|
|
12776
12324
|
const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
12777
|
-
const filePath =
|
|
12325
|
+
const filePath = join12(dir, safeFilename);
|
|
12778
12326
|
let base64Data = body.data;
|
|
12779
12327
|
if (base64Data.includes(",")) {
|
|
12780
12328
|
base64Data = base64Data.split(",")[1];
|
|
@@ -12803,7 +12351,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
12803
12351
|
return c.json({ error: "Session not found" }, 404);
|
|
12804
12352
|
}
|
|
12805
12353
|
const dir = getAttachmentsDir(sessionId);
|
|
12806
|
-
if (!
|
|
12354
|
+
if (!existsSync18(dir)) {
|
|
12807
12355
|
return c.json({ error: "Attachment not found" }, 404);
|
|
12808
12356
|
}
|
|
12809
12357
|
const files = readdirSync3(dir);
|
|
@@ -12811,14 +12359,14 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
12811
12359
|
if (!file) {
|
|
12812
12360
|
return c.json({ error: "Attachment not found" }, 404);
|
|
12813
12361
|
}
|
|
12814
|
-
const filePath =
|
|
12815
|
-
|
|
12362
|
+
const filePath = join12(dir, file);
|
|
12363
|
+
unlinkSync2(filePath);
|
|
12816
12364
|
return c.json({ success: true, id: attachmentId });
|
|
12817
12365
|
});
|
|
12818
|
-
var filesQuerySchema =
|
|
12819
|
-
query:
|
|
12366
|
+
var filesQuerySchema = z16.object({
|
|
12367
|
+
query: z16.string().optional(),
|
|
12820
12368
|
// Filter query (e.g., "src/com" to match "src/components")
|
|
12821
|
-
limit:
|
|
12369
|
+
limit: z16.string().optional()
|
|
12822
12370
|
// Max results (default 50)
|
|
12823
12371
|
});
|
|
12824
12372
|
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
@@ -12894,7 +12442,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
12894
12442
|
const entries = await readdir6(currentDir, { withFileTypes: true });
|
|
12895
12443
|
for (const entry2 of entries) {
|
|
12896
12444
|
if (results.length >= limit * 2) break;
|
|
12897
|
-
const fullPath =
|
|
12445
|
+
const fullPath = join12(currentDir, entry2.name);
|
|
12898
12446
|
const relativePath = relative9(baseDir, fullPath);
|
|
12899
12447
|
if (entry2.isDirectory() && IGNORED_DIRECTORIES.has(entry2.name)) {
|
|
12900
12448
|
continue;
|
|
@@ -12942,7 +12490,7 @@ sessions2.get(
|
|
|
12942
12490
|
return c.json({ error: "Session not found" }, 404);
|
|
12943
12491
|
}
|
|
12944
12492
|
const workingDirectory = session.workingDirectory;
|
|
12945
|
-
if (!
|
|
12493
|
+
if (!existsSync18(workingDirectory)) {
|
|
12946
12494
|
return c.json({
|
|
12947
12495
|
sessionId,
|
|
12948
12496
|
workingDirectory,
|
|
@@ -13055,9 +12603,9 @@ init_session_lock();
|
|
|
13055
12603
|
init_config();
|
|
13056
12604
|
import { Hono as Hono2 } from "hono";
|
|
13057
12605
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
13058
|
-
import { z as
|
|
13059
|
-
import { existsSync as
|
|
13060
|
-
import { join as
|
|
12606
|
+
import { z as z17 } from "zod";
|
|
12607
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
12608
|
+
import { join as join13 } from "path";
|
|
13061
12609
|
|
|
13062
12610
|
// src/server/resumable-stream.ts
|
|
13063
12611
|
import { createResumableStreamContext } from "resumable-stream/generic";
|
|
@@ -13144,7 +12692,7 @@ var streamContext = createResumableStreamContext({
|
|
|
13144
12692
|
|
|
13145
12693
|
// src/server/routes/agents.ts
|
|
13146
12694
|
init_checkpoints();
|
|
13147
|
-
import { nanoid as
|
|
12695
|
+
import { nanoid as nanoid11 } from "nanoid";
|
|
13148
12696
|
init_stream_proxy();
|
|
13149
12697
|
init_recorder();
|
|
13150
12698
|
init_remote();
|
|
@@ -13236,40 +12784,40 @@ function enrichPromptWithDevtoolsContext(sessionId, prompt) {
|
|
|
13236
12784
|
${prompt}`;
|
|
13237
12785
|
}
|
|
13238
12786
|
var agents = new Hono2();
|
|
13239
|
-
var attachmentSchema =
|
|
13240
|
-
type:
|
|
13241
|
-
data:
|
|
12787
|
+
var attachmentSchema = z17.object({
|
|
12788
|
+
type: z17.enum(["image", "file"]),
|
|
12789
|
+
data: z17.string(),
|
|
13242
12790
|
// base64 data URL or raw base64
|
|
13243
|
-
mediaType:
|
|
13244
|
-
filename:
|
|
12791
|
+
mediaType: z17.string().optional(),
|
|
12792
|
+
filename: z17.string().optional()
|
|
13245
12793
|
});
|
|
13246
|
-
var runPromptSchema =
|
|
13247
|
-
prompt:
|
|
12794
|
+
var runPromptSchema = z17.object({
|
|
12795
|
+
prompt: z17.string(),
|
|
13248
12796
|
// Can be empty if attachments are provided
|
|
13249
|
-
attachments:
|
|
12797
|
+
attachments: z17.array(attachmentSchema).optional()
|
|
13250
12798
|
}).refine(
|
|
13251
12799
|
(data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
|
|
13252
12800
|
{ message: "Either prompt or attachments must be provided" }
|
|
13253
12801
|
);
|
|
13254
|
-
var quickStartSchema =
|
|
13255
|
-
prompt:
|
|
13256
|
-
name:
|
|
13257
|
-
workingDirectory:
|
|
13258
|
-
model:
|
|
13259
|
-
toolApprovals:
|
|
12802
|
+
var quickStartSchema = z17.object({
|
|
12803
|
+
prompt: z17.string().min(1),
|
|
12804
|
+
name: z17.string().optional(),
|
|
12805
|
+
workingDirectory: z17.string().optional(),
|
|
12806
|
+
model: z17.string().optional(),
|
|
12807
|
+
toolApprovals: z17.record(z17.string(), z17.boolean()).optional()
|
|
13260
12808
|
});
|
|
13261
|
-
var rejectSchema =
|
|
13262
|
-
reason:
|
|
12809
|
+
var rejectSchema = z17.object({
|
|
12810
|
+
reason: z17.string().optional()
|
|
13263
12811
|
}).optional();
|
|
13264
12812
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
13265
12813
|
function getAttachmentsDirectory(sessionId) {
|
|
13266
12814
|
const appDataDir = getAppDataDirectory();
|
|
13267
|
-
return
|
|
12815
|
+
return join13(appDataDir, "attachments", sessionId);
|
|
13268
12816
|
}
|
|
13269
12817
|
async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
13270
12818
|
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
13271
|
-
if (!
|
|
13272
|
-
|
|
12819
|
+
if (!existsSync19(attachmentsDir)) {
|
|
12820
|
+
mkdirSync8(attachmentsDir, { recursive: true });
|
|
13273
12821
|
}
|
|
13274
12822
|
let filename = attachment.filename;
|
|
13275
12823
|
if (!filename) {
|
|
@@ -13287,7 +12835,7 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
|
13287
12835
|
attachment.mediaType = resized.mediaType;
|
|
13288
12836
|
attachment.data = buffer.toString("base64");
|
|
13289
12837
|
}
|
|
13290
|
-
const filePath =
|
|
12838
|
+
const filePath = join13(attachmentsDir, filename);
|
|
13291
12839
|
writeFileSync5(filePath, buffer);
|
|
13292
12840
|
return filePath;
|
|
13293
12841
|
}
|
|
@@ -13724,7 +13272,7 @@ ${prompt}` });
|
|
|
13724
13272
|
});
|
|
13725
13273
|
} catch {
|
|
13726
13274
|
}
|
|
13727
|
-
const streamId = `stream_${id}_${
|
|
13275
|
+
const streamId = `stream_${id}_${nanoid11(10)}`;
|
|
13728
13276
|
console.log(`[STREAM] Creating stream ${streamId} for session ${id}`);
|
|
13729
13277
|
await activeStreamQueries.create(id, streamId);
|
|
13730
13278
|
const stream = await streamContext.resumableStream(
|
|
@@ -13929,7 +13477,7 @@ agents.post(
|
|
|
13929
13477
|
});
|
|
13930
13478
|
const session = agent.getSession();
|
|
13931
13479
|
const enrichedPrompt = enrichPromptWithDevtoolsContext(session.id, body.prompt);
|
|
13932
|
-
const streamId = `stream_${session.id}_${
|
|
13480
|
+
const streamId = `stream_${session.id}_${nanoid11(10)}`;
|
|
13933
13481
|
await createCheckpoint(session.id, session.workingDirectory, 0);
|
|
13934
13482
|
await activeStreamQueries.create(session.id, streamId);
|
|
13935
13483
|
const createQuickStreamProducer = () => {
|
|
@@ -14196,23 +13744,23 @@ agents.post(
|
|
|
14196
13744
|
});
|
|
14197
13745
|
}
|
|
14198
13746
|
);
|
|
14199
|
-
var browserInputSchema =
|
|
14200
|
-
type:
|
|
14201
|
-
eventType:
|
|
14202
|
-
x:
|
|
14203
|
-
y:
|
|
14204
|
-
button:
|
|
14205
|
-
clickCount:
|
|
14206
|
-
deltaX:
|
|
14207
|
-
deltaY:
|
|
14208
|
-
key:
|
|
14209
|
-
code:
|
|
14210
|
-
text:
|
|
14211
|
-
modifiers:
|
|
14212
|
-
touchPoints:
|
|
14213
|
-
x:
|
|
14214
|
-
y:
|
|
14215
|
-
id:
|
|
13747
|
+
var browserInputSchema = z17.object({
|
|
13748
|
+
type: z17.enum(["input_mouse", "input_keyboard", "input_touch"]),
|
|
13749
|
+
eventType: z17.string(),
|
|
13750
|
+
x: z17.number().optional(),
|
|
13751
|
+
y: z17.number().optional(),
|
|
13752
|
+
button: z17.string().optional(),
|
|
13753
|
+
clickCount: z17.number().optional(),
|
|
13754
|
+
deltaX: z17.number().optional(),
|
|
13755
|
+
deltaY: z17.number().optional(),
|
|
13756
|
+
key: z17.string().optional(),
|
|
13757
|
+
code: z17.string().optional(),
|
|
13758
|
+
text: z17.string().optional(),
|
|
13759
|
+
modifiers: z17.number().optional(),
|
|
13760
|
+
touchPoints: z17.array(z17.object({
|
|
13761
|
+
x: z17.number(),
|
|
13762
|
+
y: z17.number(),
|
|
13763
|
+
id: z17.number().optional()
|
|
14216
13764
|
})).optional()
|
|
14217
13765
|
});
|
|
14218
13766
|
agents.post(
|
|
@@ -14247,27 +13795,27 @@ agents.get("/:id/browser-stream", async (c) => {
|
|
|
14247
13795
|
init_config();
|
|
14248
13796
|
import { Hono as Hono3 } from "hono";
|
|
14249
13797
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
14250
|
-
import { z as
|
|
14251
|
-
import { readFileSync as
|
|
13798
|
+
import { z as z18 } from "zod";
|
|
13799
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
14252
13800
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
14253
|
-
import { dirname as dirname7, join as
|
|
13801
|
+
import { dirname as dirname7, join as join14 } from "path";
|
|
14254
13802
|
var __filename = fileURLToPath3(import.meta.url);
|
|
14255
13803
|
var __dirname = dirname7(__filename);
|
|
14256
13804
|
var possiblePaths = [
|
|
14257
|
-
|
|
13805
|
+
join14(__dirname, "../package.json"),
|
|
14258
13806
|
// From dist/server -> dist/../package.json
|
|
14259
|
-
|
|
13807
|
+
join14(__dirname, "../../package.json"),
|
|
14260
13808
|
// From dist/server (if nested differently)
|
|
14261
|
-
|
|
13809
|
+
join14(__dirname, "../../../package.json"),
|
|
14262
13810
|
// From src/server/routes (development)
|
|
14263
|
-
|
|
13811
|
+
join14(process.cwd(), "package.json")
|
|
14264
13812
|
// From current working directory
|
|
14265
13813
|
];
|
|
14266
13814
|
var currentVersion = "0.0.0";
|
|
14267
13815
|
var packageName = "sparkecoder";
|
|
14268
13816
|
for (const packageJsonPath of possiblePaths) {
|
|
14269
13817
|
try {
|
|
14270
|
-
const packageJson = JSON.parse(
|
|
13818
|
+
const packageJson = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
|
|
14271
13819
|
if (packageJson.name === "sparkecoder") {
|
|
14272
13820
|
currentVersion = packageJson.version || "0.0.0";
|
|
14273
13821
|
packageName = packageJson.name || "sparkecoder";
|
|
@@ -14359,9 +13907,9 @@ health.get("/api-keys", async (c) => {
|
|
|
14359
13907
|
supportedProviders: SUPPORTED_PROVIDERS
|
|
14360
13908
|
});
|
|
14361
13909
|
});
|
|
14362
|
-
var setApiKeySchema =
|
|
14363
|
-
provider:
|
|
14364
|
-
apiKey:
|
|
13910
|
+
var setApiKeySchema = z18.object({
|
|
13911
|
+
provider: z18.string(),
|
|
13912
|
+
apiKey: z18.string().min(1)
|
|
14365
13913
|
});
|
|
14366
13914
|
health.post(
|
|
14367
13915
|
"/api-keys",
|
|
@@ -14402,12 +13950,12 @@ init_tmux();
|
|
|
14402
13950
|
init_db();
|
|
14403
13951
|
import { Hono as Hono4 } from "hono";
|
|
14404
13952
|
import { zValidator as zValidator4 } from "@hono/zod-validator";
|
|
14405
|
-
import { z as
|
|
13953
|
+
import { z as z19 } from "zod";
|
|
14406
13954
|
var terminals = new Hono4();
|
|
14407
|
-
var spawnSchema =
|
|
14408
|
-
command:
|
|
14409
|
-
cwd:
|
|
14410
|
-
name:
|
|
13955
|
+
var spawnSchema = z19.object({
|
|
13956
|
+
command: z19.string(),
|
|
13957
|
+
cwd: z19.string().optional(),
|
|
13958
|
+
name: z19.string().optional()
|
|
14411
13959
|
});
|
|
14412
13960
|
terminals.post(
|
|
14413
13961
|
"/:sessionId/terminals",
|
|
@@ -14488,8 +14036,8 @@ terminals.get("/:sessionId/terminals/:terminalId", async (c) => {
|
|
|
14488
14036
|
// We don't track exit codes in tmux mode
|
|
14489
14037
|
});
|
|
14490
14038
|
});
|
|
14491
|
-
var logsQuerySchema =
|
|
14492
|
-
tail:
|
|
14039
|
+
var logsQuerySchema = z19.object({
|
|
14040
|
+
tail: z19.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
|
|
14493
14041
|
});
|
|
14494
14042
|
terminals.get(
|
|
14495
14043
|
"/:sessionId/terminals/:terminalId/logs",
|
|
@@ -14513,8 +14061,8 @@ terminals.get(
|
|
|
14513
14061
|
});
|
|
14514
14062
|
}
|
|
14515
14063
|
);
|
|
14516
|
-
var killSchema =
|
|
14517
|
-
signal:
|
|
14064
|
+
var killSchema = z19.object({
|
|
14065
|
+
signal: z19.enum(["SIGTERM", "SIGKILL"]).optional()
|
|
14518
14066
|
});
|
|
14519
14067
|
terminals.post(
|
|
14520
14068
|
"/:sessionId/terminals/:terminalId/kill",
|
|
@@ -14528,8 +14076,8 @@ terminals.post(
|
|
|
14528
14076
|
return c.json({ success: true, message: "Terminal killed" });
|
|
14529
14077
|
}
|
|
14530
14078
|
);
|
|
14531
|
-
var writeSchema =
|
|
14532
|
-
input:
|
|
14079
|
+
var writeSchema = z19.object({
|
|
14080
|
+
input: z19.string()
|
|
14533
14081
|
});
|
|
14534
14082
|
terminals.post(
|
|
14535
14083
|
"/:sessionId/terminals/:terminalId/write",
|
|
@@ -14716,23 +14264,23 @@ init_agent();
|
|
|
14716
14264
|
init_config();
|
|
14717
14265
|
import { Hono as Hono5 } from "hono";
|
|
14718
14266
|
import { zValidator as zValidator5 } from "@hono/zod-validator";
|
|
14719
|
-
import { z as
|
|
14720
|
-
import { nanoid as
|
|
14267
|
+
import { z as z20 } from "zod";
|
|
14268
|
+
import { nanoid as nanoid12 } from "nanoid";
|
|
14721
14269
|
init_questions();
|
|
14722
14270
|
var tasks = new Hono5();
|
|
14723
14271
|
var taskAbortControllers = /* @__PURE__ */ new Map();
|
|
14724
|
-
var createTaskSchema =
|
|
14725
|
-
prompt:
|
|
14726
|
-
outputSchema:
|
|
14727
|
-
webhookUrl:
|
|
14728
|
-
model:
|
|
14729
|
-
workingDirectory:
|
|
14730
|
-
name:
|
|
14731
|
-
maxIterations:
|
|
14732
|
-
parentTaskId:
|
|
14272
|
+
var createTaskSchema = z20.object({
|
|
14273
|
+
prompt: z20.string().min(1),
|
|
14274
|
+
outputSchema: z20.record(z20.string(), z20.unknown()),
|
|
14275
|
+
webhookUrl: z20.string().url().optional(),
|
|
14276
|
+
model: z20.string().optional(),
|
|
14277
|
+
workingDirectory: z20.string().optional(),
|
|
14278
|
+
name: z20.string().optional(),
|
|
14279
|
+
maxIterations: z20.number().int().min(1).max(500).optional(),
|
|
14280
|
+
parentTaskId: z20.string().optional(),
|
|
14733
14281
|
/** When set, the spawning orchestrator's session id. Stamped on the
|
|
14734
14282
|
* worker's config so terminal events can wake the orchestrator. */
|
|
14735
|
-
orchestratorSessionId:
|
|
14283
|
+
orchestratorSessionId: z20.string().optional()
|
|
14736
14284
|
});
|
|
14737
14285
|
tasks.post(
|
|
14738
14286
|
"/",
|
|
@@ -14798,7 +14346,7 @@ tasks.post(
|
|
|
14798
14346
|
const taskId = agent.sessionId;
|
|
14799
14347
|
const abortController = new AbortController();
|
|
14800
14348
|
taskAbortControllers.set(taskId, abortController);
|
|
14801
|
-
const streamId = `stream_${taskId}_${
|
|
14349
|
+
const streamId = `stream_${taskId}_${nanoid12(10)}`;
|
|
14802
14350
|
await activeStreamQueries.create(taskId, streamId);
|
|
14803
14351
|
const taskStreamProducer = () => {
|
|
14804
14352
|
const { readable, writable } = new TransformStream();
|
|
@@ -14947,9 +14495,9 @@ tasks.post("/:id/cancel", async (c) => {
|
|
|
14947
14495
|
}
|
|
14948
14496
|
return c.json({ taskId: id, status: "failed", error: "Task cancelled by user" });
|
|
14949
14497
|
});
|
|
14950
|
-
var answerQuestionSchema =
|
|
14951
|
-
answer:
|
|
14952
|
-
answeredBy:
|
|
14498
|
+
var answerQuestionSchema = z20.object({
|
|
14499
|
+
answer: z20.string().min(1),
|
|
14500
|
+
answeredBy: z20.enum(["orchestrator", "user", "system"]).optional()
|
|
14953
14501
|
});
|
|
14954
14502
|
tasks.post(
|
|
14955
14503
|
"/:id/questions/:questionId/answer",
|
|
@@ -15233,7 +14781,7 @@ init_pool();
|
|
|
15233
14781
|
init_webhook_events();
|
|
15234
14782
|
import { Hono as Hono8 } from "hono";
|
|
15235
14783
|
import { zValidator as zValidator6 } from "@hono/zod-validator";
|
|
15236
|
-
import { z as
|
|
14784
|
+
import { z as z21 } from "zod";
|
|
15237
14785
|
var integrations = new Hono8();
|
|
15238
14786
|
var orchestratorRouter = new Hono8();
|
|
15239
14787
|
async function getOrchestratorId() {
|
|
@@ -15256,9 +14804,9 @@ orchestratorRouter.get("/", async (c) => {
|
|
|
15256
14804
|
});
|
|
15257
14805
|
orchestratorRouter.patch(
|
|
15258
14806
|
"/",
|
|
15259
|
-
zValidator6("json",
|
|
15260
|
-
name:
|
|
15261
|
-
personality:
|
|
14807
|
+
zValidator6("json", z21.object({
|
|
14808
|
+
name: z21.string().min(1).optional(),
|
|
14809
|
+
personality: z21.string().optional()
|
|
15262
14810
|
})),
|
|
15263
14811
|
async (c) => {
|
|
15264
14812
|
const id = await getOrchestratorId();
|
|
@@ -15334,15 +14882,15 @@ integrations.get("/", async (c) => {
|
|
|
15334
14882
|
}
|
|
15335
14883
|
});
|
|
15336
14884
|
});
|
|
15337
|
-
var slackConfigSchema =
|
|
15338
|
-
botToken:
|
|
15339
|
-
signingSecret:
|
|
15340
|
-
defaultOrchestratorName:
|
|
15341
|
-
allowedUsers:
|
|
15342
|
-
allowedChannels:
|
|
15343
|
-
allowDmsFromAnyone:
|
|
15344
|
-
deniedReplyEnabled:
|
|
15345
|
-
deniedReplyTemplate:
|
|
14885
|
+
var slackConfigSchema = z21.object({
|
|
14886
|
+
botToken: z21.string().optional(),
|
|
14887
|
+
signingSecret: z21.string().optional(),
|
|
14888
|
+
defaultOrchestratorName: z21.string().optional(),
|
|
14889
|
+
allowedUsers: z21.array(z21.string()).optional(),
|
|
14890
|
+
allowedChannels: z21.array(z21.string()).optional(),
|
|
14891
|
+
allowDmsFromAnyone: z21.boolean().optional(),
|
|
14892
|
+
deniedReplyEnabled: z21.boolean().optional(),
|
|
14893
|
+
deniedReplyTemplate: z21.string().optional()
|
|
15346
14894
|
});
|
|
15347
14895
|
integrations.post("/slack", zValidator6("json", slackConfigSchema), async (c) => {
|
|
15348
14896
|
const body = c.req.valid("json");
|
|
@@ -15371,11 +14919,11 @@ schedulesRouter.get("/", async (c) => {
|
|
|
15371
14919
|
});
|
|
15372
14920
|
schedulesRouter.post(
|
|
15373
14921
|
"/",
|
|
15374
|
-
zValidator6("json",
|
|
15375
|
-
name:
|
|
15376
|
-
cron:
|
|
15377
|
-
prompt:
|
|
15378
|
-
replyChannel:
|
|
14922
|
+
zValidator6("json", z21.object({
|
|
14923
|
+
name: z21.string().min(1),
|
|
14924
|
+
cron: z21.string().min(1),
|
|
14925
|
+
prompt: z21.string().min(1),
|
|
14926
|
+
replyChannel: z21.string().optional()
|
|
15379
14927
|
})),
|
|
15380
14928
|
async (c) => {
|
|
15381
14929
|
const orcId = await getOrchestratorId();
|
|
@@ -15386,12 +14934,12 @@ schedulesRouter.post(
|
|
|
15386
14934
|
);
|
|
15387
14935
|
schedulesRouter.patch(
|
|
15388
14936
|
"/:id",
|
|
15389
|
-
zValidator6("json",
|
|
15390
|
-
name:
|
|
15391
|
-
cron:
|
|
15392
|
-
prompt:
|
|
15393
|
-
enabled:
|
|
15394
|
-
replyChannel:
|
|
14937
|
+
zValidator6("json", z21.object({
|
|
14938
|
+
name: z21.string().optional(),
|
|
14939
|
+
cron: z21.string().optional(),
|
|
14940
|
+
prompt: z21.string().optional(),
|
|
14941
|
+
enabled: z21.boolean().optional(),
|
|
14942
|
+
replyChannel: z21.string().optional()
|
|
15395
14943
|
})),
|
|
15396
14944
|
async (c) => {
|
|
15397
14945
|
const orcId = await getOrchestratorId();
|
|
@@ -15419,10 +14967,10 @@ webhooksRouter.get("/", async (c) => {
|
|
|
15419
14967
|
});
|
|
15420
14968
|
webhooksRouter.post(
|
|
15421
14969
|
"/",
|
|
15422
|
-
zValidator6("json",
|
|
15423
|
-
name:
|
|
15424
|
-
wake:
|
|
15425
|
-
template:
|
|
14970
|
+
zValidator6("json", z21.object({
|
|
14971
|
+
name: z21.string().min(1),
|
|
14972
|
+
wake: z21.enum(["now", "next"]).optional(),
|
|
14973
|
+
template: z21.string().optional()
|
|
15426
14974
|
})),
|
|
15427
14975
|
async (c) => {
|
|
15428
14976
|
const orcId = await getOrchestratorId();
|
|
@@ -15433,11 +14981,11 @@ webhooksRouter.post(
|
|
|
15433
14981
|
);
|
|
15434
14982
|
webhooksRouter.patch(
|
|
15435
14983
|
"/:id",
|
|
15436
|
-
zValidator6("json",
|
|
15437
|
-
name:
|
|
15438
|
-
wake:
|
|
15439
|
-
template:
|
|
15440
|
-
rotateToken:
|
|
14984
|
+
zValidator6("json", z21.object({
|
|
14985
|
+
name: z21.string().optional(),
|
|
14986
|
+
wake: z21.enum(["now", "next"]).optional(),
|
|
14987
|
+
template: z21.string().optional(),
|
|
14988
|
+
rotateToken: z21.boolean().optional()
|
|
15441
14989
|
})),
|
|
15442
14990
|
async (c) => {
|
|
15443
14991
|
const orcId = await getOrchestratorId();
|
|
@@ -15454,22 +15002,22 @@ webhooksRouter.delete("/:id", async (c) => {
|
|
|
15454
15002
|
return c.json({ deleted: ok });
|
|
15455
15003
|
});
|
|
15456
15004
|
var mcpRouter = new Hono8();
|
|
15457
|
-
var mcpServerSchema =
|
|
15458
|
-
name:
|
|
15459
|
-
transport:
|
|
15460
|
-
url:
|
|
15461
|
-
headers:
|
|
15462
|
-
command:
|
|
15463
|
-
args:
|
|
15464
|
-
enabled:
|
|
15005
|
+
var mcpServerSchema = z21.object({
|
|
15006
|
+
name: z21.string().min(1),
|
|
15007
|
+
transport: z21.enum(["http", "sse", "stdio"]),
|
|
15008
|
+
url: z21.string().optional(),
|
|
15009
|
+
headers: z21.record(z21.string(), z21.string()).optional(),
|
|
15010
|
+
command: z21.string().optional(),
|
|
15011
|
+
args: z21.array(z21.string()).optional(),
|
|
15012
|
+
enabled: z21.boolean().optional()
|
|
15465
15013
|
});
|
|
15466
|
-
var mcpPatchSchema =
|
|
15467
|
-
name:
|
|
15468
|
-
url:
|
|
15469
|
-
headers:
|
|
15470
|
-
command:
|
|
15471
|
-
args:
|
|
15472
|
-
enabled:
|
|
15014
|
+
var mcpPatchSchema = z21.object({
|
|
15015
|
+
name: z21.string().optional(),
|
|
15016
|
+
url: z21.string().optional(),
|
|
15017
|
+
headers: z21.record(z21.string(), z21.string()).optional(),
|
|
15018
|
+
command: z21.string().optional(),
|
|
15019
|
+
args: z21.array(z21.string()).optional(),
|
|
15020
|
+
enabled: z21.boolean().optional()
|
|
15473
15021
|
});
|
|
15474
15022
|
mcpRouter.get("/", async (c) => {
|
|
15475
15023
|
const rows = listMcpServers().map((s) => ({
|
|
@@ -15586,10 +15134,10 @@ init_config();
|
|
|
15586
15134
|
init_db();
|
|
15587
15135
|
|
|
15588
15136
|
// src/utils/dependencies.ts
|
|
15589
|
-
import { exec as
|
|
15590
|
-
import { promisify as
|
|
15137
|
+
import { exec as exec6 } from "child_process";
|
|
15138
|
+
import { promisify as promisify6 } from "util";
|
|
15591
15139
|
import { platform as platform2 } from "os";
|
|
15592
|
-
var
|
|
15140
|
+
var execAsync6 = promisify6(exec6);
|
|
15593
15141
|
function getInstallInstructions() {
|
|
15594
15142
|
const os2 = platform2();
|
|
15595
15143
|
if (os2 === "darwin") {
|
|
@@ -15622,7 +15170,7 @@ Install tmux:
|
|
|
15622
15170
|
}
|
|
15623
15171
|
async function checkTmux() {
|
|
15624
15172
|
try {
|
|
15625
|
-
const { stdout } = await
|
|
15173
|
+
const { stdout } = await execAsync6("tmux -V", { timeout: 5e3 });
|
|
15626
15174
|
const version = stdout.trim();
|
|
15627
15175
|
return {
|
|
15628
15176
|
available: true,
|
|
@@ -15663,7 +15211,7 @@ async function checkDependencies(options = {}) {
|
|
|
15663
15211
|
}
|
|
15664
15212
|
async function checkAgentBrowser() {
|
|
15665
15213
|
try {
|
|
15666
|
-
const { stdout } = await
|
|
15214
|
+
const { stdout } = await execAsync6("agent-browser --version", { timeout: 1e4 });
|
|
15667
15215
|
const version = stdout.trim();
|
|
15668
15216
|
return { available: true, version };
|
|
15669
15217
|
} catch {
|
|
@@ -15679,12 +15227,12 @@ async function tryInstallAgentBrowser(options = {}) {
|
|
|
15679
15227
|
if (!options.quiet) {
|
|
15680
15228
|
console.log("\u{1F4E6} Installing agent-browser globally...");
|
|
15681
15229
|
}
|
|
15682
|
-
await
|
|
15230
|
+
await execAsync6("npm install -g agent-browser", { timeout: 12e4 });
|
|
15683
15231
|
try {
|
|
15684
15232
|
if (!options.quiet) {
|
|
15685
15233
|
console.log("\u{1F4E6} Installing Chromium for browser automation...");
|
|
15686
15234
|
}
|
|
15687
|
-
await
|
|
15235
|
+
await execAsync6("agent-browser install", { timeout: 12e4 });
|
|
15688
15236
|
} catch {
|
|
15689
15237
|
}
|
|
15690
15238
|
if (!options.quiet) {
|
|
@@ -15703,21 +15251,21 @@ async function tryAutoInstallTmux() {
|
|
|
15703
15251
|
try {
|
|
15704
15252
|
if (os2 === "darwin") {
|
|
15705
15253
|
try {
|
|
15706
|
-
await
|
|
15254
|
+
await execAsync6("which brew", { timeout: 5e3 });
|
|
15707
15255
|
} catch {
|
|
15708
15256
|
return false;
|
|
15709
15257
|
}
|
|
15710
15258
|
console.log("\u{1F4E6} Installing tmux via Homebrew...");
|
|
15711
|
-
await
|
|
15259
|
+
await execAsync6("brew install tmux", { timeout: 3e5 });
|
|
15712
15260
|
console.log("\u2705 tmux installed successfully");
|
|
15713
15261
|
return true;
|
|
15714
15262
|
}
|
|
15715
15263
|
if (os2 === "linux") {
|
|
15716
15264
|
try {
|
|
15717
|
-
await
|
|
15265
|
+
await execAsync6("which apt-get", { timeout: 5e3 });
|
|
15718
15266
|
console.log("\u{1F4E6} Installing tmux via apt-get...");
|
|
15719
15267
|
console.log(" (This may require sudo password)");
|
|
15720
|
-
await
|
|
15268
|
+
await execAsync6("sudo apt-get update && sudo apt-get install -y tmux", {
|
|
15721
15269
|
timeout: 3e5
|
|
15722
15270
|
});
|
|
15723
15271
|
console.log("\u2705 tmux installed successfully");
|
|
@@ -15725,9 +15273,9 @@ async function tryAutoInstallTmux() {
|
|
|
15725
15273
|
} catch {
|
|
15726
15274
|
}
|
|
15727
15275
|
try {
|
|
15728
|
-
await
|
|
15276
|
+
await execAsync6("which dnf", { timeout: 5e3 });
|
|
15729
15277
|
console.log("\u{1F4E6} Installing tmux via dnf...");
|
|
15730
|
-
await
|
|
15278
|
+
await execAsync6("sudo dnf install -y tmux", { timeout: 3e5 });
|
|
15731
15279
|
console.log("\u2705 tmux installed successfully");
|
|
15732
15280
|
return true;
|
|
15733
15281
|
} catch {
|
|
@@ -15767,11 +15315,11 @@ function getWebDirectory() {
|
|
|
15767
15315
|
try {
|
|
15768
15316
|
const currentDir = dirname8(fileURLToPath4(import.meta.url));
|
|
15769
15317
|
const webDir = resolve11(currentDir, "..", "web");
|
|
15770
|
-
if (
|
|
15318
|
+
if (existsSync20(webDir) && existsSync20(join15(webDir, "package.json"))) {
|
|
15771
15319
|
return webDir;
|
|
15772
15320
|
}
|
|
15773
15321
|
const altWebDir = resolve11(currentDir, "..", "..", "web");
|
|
15774
|
-
if (
|
|
15322
|
+
if (existsSync20(altWebDir) && existsSync20(join15(altWebDir, "package.json"))) {
|
|
15775
15323
|
return altWebDir;
|
|
15776
15324
|
}
|
|
15777
15325
|
return null;
|
|
@@ -15829,23 +15377,23 @@ async function findWebPort(preferredPort) {
|
|
|
15829
15377
|
return { port: preferredPort, alreadyRunning: false };
|
|
15830
15378
|
}
|
|
15831
15379
|
function hasProductionBuild(webDir) {
|
|
15832
|
-
const buildIdPath =
|
|
15833
|
-
return
|
|
15380
|
+
const buildIdPath = join15(webDir, ".next", "BUILD_ID");
|
|
15381
|
+
return existsSync20(buildIdPath);
|
|
15834
15382
|
}
|
|
15835
15383
|
function hasSourceFiles(webDir) {
|
|
15836
|
-
const appDir =
|
|
15837
|
-
const pagesDir =
|
|
15838
|
-
const rootAppDir =
|
|
15839
|
-
const rootPagesDir =
|
|
15840
|
-
return
|
|
15384
|
+
const appDir = join15(webDir, "src", "app");
|
|
15385
|
+
const pagesDir = join15(webDir, "src", "pages");
|
|
15386
|
+
const rootAppDir = join15(webDir, "app");
|
|
15387
|
+
const rootPagesDir = join15(webDir, "pages");
|
|
15388
|
+
return existsSync20(appDir) || existsSync20(pagesDir) || existsSync20(rootAppDir) || existsSync20(rootPagesDir);
|
|
15841
15389
|
}
|
|
15842
15390
|
function getStandaloneServerPath(webDir) {
|
|
15843
15391
|
const possiblePaths2 = [
|
|
15844
|
-
|
|
15845
|
-
|
|
15392
|
+
join15(webDir, ".next", "standalone", "server.js"),
|
|
15393
|
+
join15(webDir, ".next", "standalone", "web", "server.js")
|
|
15846
15394
|
];
|
|
15847
15395
|
for (const serverPath of possiblePaths2) {
|
|
15848
|
-
if (
|
|
15396
|
+
if (existsSync20(serverPath)) {
|
|
15849
15397
|
return serverPath;
|
|
15850
15398
|
}
|
|
15851
15399
|
}
|
|
@@ -15885,13 +15433,13 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15885
15433
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
15886
15434
|
return { process: null, port: actualPort };
|
|
15887
15435
|
}
|
|
15888
|
-
const usePnpm =
|
|
15889
|
-
const useNpm = !usePnpm &&
|
|
15436
|
+
const usePnpm = existsSync20(join15(webDir, "pnpm-lock.yaml"));
|
|
15437
|
+
const useNpm = !usePnpm && existsSync20(join15(webDir, "package-lock.json"));
|
|
15890
15438
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
15891
15439
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
15892
15440
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
15893
15441
|
const runtimeConfig = { apiBaseUrl: apiUrl };
|
|
15894
|
-
const runtimeConfigPath =
|
|
15442
|
+
const runtimeConfigPath = join15(webDir, "runtime-config.json");
|
|
15895
15443
|
try {
|
|
15896
15444
|
writeFileSync6(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
|
|
15897
15445
|
if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
|
|
@@ -16106,8 +15654,8 @@ async function startServer(options = {}) {
|
|
|
16106
15654
|
if (options.workingDirectory) {
|
|
16107
15655
|
config.resolvedWorkingDirectory = options.workingDirectory;
|
|
16108
15656
|
}
|
|
16109
|
-
if (!
|
|
16110
|
-
|
|
15657
|
+
if (!existsSync20(config.resolvedWorkingDirectory)) {
|
|
15658
|
+
mkdirSync9(config.resolvedWorkingDirectory, { recursive: true });
|
|
16111
15659
|
if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
|
|
16112
15660
|
}
|
|
16113
15661
|
if (!config.resolvedRemoteServer.url) {
|
|
@@ -16651,18 +16199,18 @@ function generateOpenAPISpec() {
|
|
|
16651
16199
|
init_config();
|
|
16652
16200
|
init_semantic();
|
|
16653
16201
|
init_db();
|
|
16654
|
-
import { mkdirSync as
|
|
16655
|
-
import { resolve as resolve12, join as
|
|
16202
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync7, readFileSync as readFileSync10, existsSync as existsSync21, statSync as statSync3 } from "fs";
|
|
16203
|
+
import { resolve as resolve12, join as join16 } from "path";
|
|
16656
16204
|
function getCliVersion() {
|
|
16657
16205
|
const here = dirname9(fileURLToPath5(import.meta.url));
|
|
16658
16206
|
const candidates = [
|
|
16659
|
-
|
|
16660
|
-
|
|
16661
|
-
|
|
16207
|
+
join16(here, "..", "package.json"),
|
|
16208
|
+
join16(here, "..", "..", "package.json"),
|
|
16209
|
+
join16(process.cwd(), "package.json")
|
|
16662
16210
|
];
|
|
16663
16211
|
for (const p of candidates) {
|
|
16664
16212
|
try {
|
|
16665
|
-
const pkg = JSON.parse(
|
|
16213
|
+
const pkg = JSON.parse(readFileSync10(p, "utf8"));
|
|
16666
16214
|
if (pkg.name === "sparkecoder" && pkg.version) return pkg.version;
|
|
16667
16215
|
} catch {
|
|
16668
16216
|
}
|
|
@@ -17231,20 +16779,10 @@ Unexpected error: ${outerError.message}`));
|
|
|
17231
16779
|
}
|
|
17232
16780
|
}
|
|
17233
16781
|
var program = new Command();
|
|
17234
|
-
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)").
|
|
17235
|
-
if (options.enableComputerUse) {
|
|
17236
|
-
process.env.SPARKECODER_COMPUTER_USE = "1";
|
|
17237
|
-
console.log(chalk.cyan("\u2192 Computer use enabled by default for new sessions."));
|
|
17238
|
-
}
|
|
16782
|
+
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) => {
|
|
17239
16783
|
await runChat(options);
|
|
17240
16784
|
});
|
|
17241
|
-
program.command("server").description("Start the SparkECoder server (API + Web UI)").option("-p, --port <port>", "API server port", "3141").option("-h, --host <host>", "Server host", "127.0.0.1").option("-c, --config <path>", "Path to config file").option("-w, --working-dir <path>", "Working directory").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI").option("--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("--
|
|
17242
|
-
const globalOpts = program.opts();
|
|
17243
|
-
const enableCU = options.enableComputerUse || globalOpts.enableComputerUse;
|
|
17244
|
-
if (enableCU) {
|
|
17245
|
-
process.env.SPARKECODER_COMPUTER_USE = "1";
|
|
17246
|
-
console.log(chalk.cyan("\u2192 Computer use enabled by default for new sessions."));
|
|
17247
|
-
}
|
|
16785
|
+
program.command("server").description("Start the SparkECoder server (API + Web UI)").option("-p, --port <port>", "API server port", "3141").option("-h, --host <host>", "Server host", "127.0.0.1").option("-c, --config <path>", "Path to config file").option("-w, --working-dir <path>", "Working directory").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI").option("--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) => {
|
|
17248
16786
|
if (options.setupSecret) {
|
|
17249
16787
|
process.env.SPARKECODER_SETUP_SECRET = options.setupSecret;
|
|
17250
16788
|
if (!process.env.SPARKECODER_TUNNEL_SECRET) {
|
|
@@ -17287,13 +16825,7 @@ program.command("server").description("Start the SparkECoder server (API + Web U
|
|
|
17287
16825
|
process.exit(1);
|
|
17288
16826
|
}
|
|
17289
16827
|
});
|
|
17290
|
-
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)").
|
|
17291
|
-
const globalOpts = program.opts();
|
|
17292
|
-
const enableCU = options.enableComputerUse || globalOpts.enableComputerUse;
|
|
17293
|
-
if (enableCU) {
|
|
17294
|
-
process.env.SPARKECODER_COMPUTER_USE = "1";
|
|
17295
|
-
console.log(chalk.cyan("\u2192 Computer use enabled by default for new sessions."));
|
|
17296
|
-
}
|
|
16828
|
+
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) => {
|
|
17297
16829
|
await runChat(options);
|
|
17298
16830
|
});
|
|
17299
16831
|
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) => {
|
|
@@ -17325,8 +16857,8 @@ program.command("task").description("Run an autonomous task that completes witho
|
|
|
17325
16857
|
let outputSchema;
|
|
17326
16858
|
try {
|
|
17327
16859
|
const schemaStr = options.schema;
|
|
17328
|
-
if (
|
|
17329
|
-
outputSchema = JSON.parse(
|
|
16860
|
+
if (existsSync21(schemaStr)) {
|
|
16861
|
+
outputSchema = JSON.parse(readFileSync10(schemaStr, "utf-8"));
|
|
17330
16862
|
} else {
|
|
17331
16863
|
outputSchema = JSON.parse(schemaStr);
|
|
17332
16864
|
}
|
|
@@ -17393,13 +16925,13 @@ program.command("init").description("Create a sparkecoder.config.json file").opt
|
|
|
17393
16925
|
let configLocation;
|
|
17394
16926
|
if (options.global) {
|
|
17395
16927
|
const appDataDir = ensureAppDataDirectory();
|
|
17396
|
-
configPath =
|
|
16928
|
+
configPath = join16(appDataDir, "sparkecoder.config.json");
|
|
17397
16929
|
configLocation = "global";
|
|
17398
16930
|
} else {
|
|
17399
16931
|
configPath = resolve12(process.cwd(), "sparkecoder.config.json");
|
|
17400
16932
|
configLocation = "local";
|
|
17401
16933
|
}
|
|
17402
|
-
if (
|
|
16934
|
+
if (existsSync21(configPath) && !options.force) {
|
|
17403
16935
|
console.log(chalk.yellow("Config file already exists. Use --force to overwrite."));
|
|
17404
16936
|
console.log(chalk.dim(` ${configPath}`));
|
|
17405
16937
|
return;
|
|
@@ -17424,11 +16956,11 @@ program.command("slack-setup").description("Interactively configure Slack integr
|
|
|
17424
16956
|
console.error(chalk.red("Both bot token and signing secret are required."));
|
|
17425
16957
|
process.exit(1);
|
|
17426
16958
|
}
|
|
17427
|
-
const configPath = options.global ?
|
|
16959
|
+
const configPath = options.global ? join16(ensureAppDataDirectory(), "sparkecoder.config.json") : resolve12(process.cwd(), "sparkecoder.config.json");
|
|
17428
16960
|
let existing = {};
|
|
17429
|
-
if (
|
|
16961
|
+
if (existsSync21(configPath)) {
|
|
17430
16962
|
try {
|
|
17431
|
-
existing = JSON.parse(
|
|
16963
|
+
existing = JSON.parse(readFileSync10(configPath, "utf-8"));
|
|
17432
16964
|
} catch {
|
|
17433
16965
|
}
|
|
17434
16966
|
} else {
|
|
@@ -17692,9 +17224,9 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
17692
17224
|
}
|
|
17693
17225
|
const verOut = run("cloudflared", ["--version"]).stdout?.toString().split("\n")[0] || "installed";
|
|
17694
17226
|
console.log(chalk.green("\u2713"), "cloudflared:", chalk.dim(verOut));
|
|
17695
|
-
const cfDir =
|
|
17696
|
-
const certPath =
|
|
17697
|
-
if (!
|
|
17227
|
+
const cfDir = join16(homedir2(), ".cloudflared");
|
|
17228
|
+
const certPath = join16(cfDir, "cert.pem");
|
|
17229
|
+
if (!existsSync21(certPath)) {
|
|
17698
17230
|
console.log(chalk.yellow("No Cloudflare login cert found."));
|
|
17699
17231
|
if (await confirm("Run `cloudflared tunnel login` now (opens a browser)?", true)) {
|
|
17700
17232
|
run("cloudflared", ["tunnel", "login"], { inheritIO: true, check: true });
|
|
@@ -17738,8 +17270,8 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
17738
17270
|
return;
|
|
17739
17271
|
}
|
|
17740
17272
|
}
|
|
17741
|
-
const credsFile = tunnel.credentials_file ||
|
|
17742
|
-
if (!
|
|
17273
|
+
const credsFile = tunnel.credentials_file || join16(cfDir, `${tunnel.id}.json`);
|
|
17274
|
+
if (!existsSync21(credsFile)) {
|
|
17743
17275
|
console.log(chalk.yellow(`Credentials file not found at ${credsFile}. The tunnel may still work via cert.pem.`));
|
|
17744
17276
|
}
|
|
17745
17277
|
let hostname = options.hostname;
|
|
@@ -17762,7 +17294,7 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
17762
17294
|
console.log(chalk.yellow("DNS route warning:"), err.trim().split("\n").slice(-2).join(" "));
|
|
17763
17295
|
}
|
|
17764
17296
|
}
|
|
17765
|
-
const configPath =
|
|
17297
|
+
const configPath = join16(cfDir, "config.yml");
|
|
17766
17298
|
const configBody = `tunnel: ${tunnel.id}
|
|
17767
17299
|
credentials-file: ${credsFile}
|
|
17768
17300
|
ingress:
|
|
@@ -17773,8 +17305,8 @@ ingress:
|
|
|
17773
17305
|
- service: http_status:404
|
|
17774
17306
|
`;
|
|
17775
17307
|
let wroteConfig = false;
|
|
17776
|
-
if (
|
|
17777
|
-
const existing =
|
|
17308
|
+
if (existsSync21(configPath)) {
|
|
17309
|
+
const existing = readFileSync10(configPath, "utf8");
|
|
17778
17310
|
if (existing.trim() === configBody.trim()) {
|
|
17779
17311
|
console.log(chalk.green("\u2713"), `config.yml already up to date: ${configPath}`);
|
|
17780
17312
|
wroteConfig = true;
|
|
@@ -18148,18 +17680,27 @@ ${providerName} API Key:
|
|
|
18148
17680
|
process.exit(1);
|
|
18149
17681
|
}
|
|
18150
17682
|
});
|
|
18151
|
-
|
|
17683
|
+
function formatDisplaySize(size) {
|
|
17684
|
+
const points = `${size.width}\xD7${size.height} points`;
|
|
17685
|
+
const px = size.pixelWidth && size.pixelHeight && (size.pixelWidth !== size.width || size.pixelHeight !== size.height) ? ` (${size.pixelWidth}\xD7${size.pixelHeight} pixels, Retina)` : "";
|
|
17686
|
+
const name = size.displayName ? `${size.displayName} \u2014 ` : "";
|
|
17687
|
+
return `${name}${points}${px}`;
|
|
17688
|
+
}
|
|
17689
|
+
program.command("check-permissions").description("Check macOS prerequisites for desktop automation (cliclick + Accessibility + Screen Recording)").action(async () => {
|
|
18152
17690
|
if (process.platform !== "darwin") {
|
|
18153
|
-
console.log(chalk.yellow(`
|
|
17691
|
+
console.log(chalk.yellow(`Desktop automation prerequisites only apply on macOS. Current platform: ${process.platform}`));
|
|
18154
17692
|
process.exit(0);
|
|
18155
17693
|
}
|
|
18156
17694
|
const {
|
|
18157
17695
|
isCliclickInstalled: isCliclickInstalled2,
|
|
18158
17696
|
hasAccessibilityPermissions: hasAccessibilityPermissions2,
|
|
18159
17697
|
hasScreenRecordingPermissions: hasScreenRecordingPermissions2,
|
|
18160
|
-
detectScreenSize: detectScreenSize2
|
|
18161
|
-
|
|
18162
|
-
|
|
17698
|
+
detectScreenSize: detectScreenSize2,
|
|
17699
|
+
getResponsibleAppName: getResponsibleAppName2
|
|
17700
|
+
} = await Promise.resolve().then(() => (init_desktop_permissions(), desktop_permissions_exports));
|
|
17701
|
+
console.log(chalk.bold("\nDesktop-automation prerequisites:\n"));
|
|
17702
|
+
const responsibleApp = await getResponsibleAppName2();
|
|
17703
|
+
console.log(` ${chalk.dim("\u2022")} Responsible process (the app TCC tracks): ${chalk.cyan(responsibleApp)}`);
|
|
18163
17704
|
const cliclick = await isCliclickInstalled2();
|
|
18164
17705
|
if (cliclick) {
|
|
18165
17706
|
console.log(` ${chalk.green("\u2713")} cliclick installed`);
|
|
@@ -18169,25 +17710,28 @@ program.command("check-permissions").description("Check macOS permissions requir
|
|
|
18169
17710
|
}
|
|
18170
17711
|
const acc = cliclick ? await hasAccessibilityPermissions2() : { ok: false, error: "cliclick not installed" };
|
|
18171
17712
|
if (acc.ok) {
|
|
18172
|
-
console.log(` ${chalk.green("\u2713")} Accessibility
|
|
17713
|
+
console.log(` ${chalk.green("\u2713")} Accessibility permission granted (for ${responsibleApp})`);
|
|
18173
17714
|
} else {
|
|
18174
|
-
console.log(` ${chalk.red("\u2717")} Accessibility
|
|
18175
|
-
console.log(` ${chalk.dim(acc.error
|
|
17715
|
+
console.log(` ${chalk.red("\u2717")} Accessibility permission missing`);
|
|
17716
|
+
if (acc.error) console.log(` ${chalk.dim(acc.error.split("\n")[0])}`);
|
|
18176
17717
|
}
|
|
18177
17718
|
const screen = await hasScreenRecordingPermissions2();
|
|
18178
17719
|
if (screen) {
|
|
18179
|
-
console.log(` ${chalk.green("\u2713")} Screen Recording
|
|
17720
|
+
console.log(` ${chalk.green("\u2713")} Screen Recording permission granted (for ${responsibleApp})`);
|
|
18180
17721
|
} else {
|
|
18181
|
-
console.log(` ${chalk.red("\u2717")} Screen Recording
|
|
17722
|
+
console.log(` ${chalk.red("\u2717")} Screen Recording permission missing`);
|
|
17723
|
+
console.log(` ${chalk.dim("macOS 15 (Sequoia) re-prompts weekly even when granted \u2014 dismiss the dialog and the API still returns true.")}`);
|
|
18182
17724
|
}
|
|
18183
17725
|
const size = await detectScreenSize2();
|
|
18184
17726
|
if (size) {
|
|
18185
|
-
console.log(` ${chalk.dim("\u2022")}
|
|
17727
|
+
console.log(` ${chalk.dim("\u2022")} Main display: ${formatDisplaySize(size)}`);
|
|
17728
|
+
console.log(` ${chalk.dim("cliclick coordinates are POINTS, not pixels. (0,0) = top-left.")}`);
|
|
18186
17729
|
}
|
|
18187
17730
|
const allOk = cliclick && acc.ok && screen;
|
|
18188
17731
|
console.log();
|
|
18189
17732
|
if (allOk) {
|
|
18190
|
-
console.log(chalk.green("All checks passed. The agent can
|
|
17733
|
+
console.log(chalk.green("All checks passed. The agent can drive the desktop via cliclick/screencapture/osascript."));
|
|
17734
|
+
console.log(chalk.dim("See the `desktop-automation` skill for recipes."));
|
|
18191
17735
|
} else {
|
|
18192
17736
|
console.log(chalk.yellow("Some prerequisites are missing."));
|
|
18193
17737
|
console.log(chalk.dim("Run: sparkecoder request-permissions"));
|
|
@@ -18195,74 +17739,83 @@ program.command("check-permissions").description("Check macOS permissions requir
|
|
|
18195
17739
|
console.log();
|
|
18196
17740
|
process.exit(allOk ? 0 : 1);
|
|
18197
17741
|
});
|
|
18198
|
-
program.command("request-permissions").description("
|
|
17742
|
+
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 () => {
|
|
18199
17743
|
if (process.platform !== "darwin") {
|
|
18200
|
-
console.log(chalk.yellow(`
|
|
17744
|
+
console.log(chalk.yellow(`Desktop automation prerequisites only apply on macOS. Current platform: ${process.platform}`));
|
|
18201
17745
|
process.exit(0);
|
|
18202
17746
|
}
|
|
18203
17747
|
const {
|
|
18204
17748
|
isCliclickInstalled: isCliclickInstalled2,
|
|
18205
17749
|
hasAccessibilityPermissions: hasAccessibilityPermissions2,
|
|
18206
17750
|
hasScreenRecordingPermissions: hasScreenRecordingPermissions2,
|
|
18207
|
-
requestAccessibilityPrompt: requestAccessibilityPrompt2,
|
|
18208
|
-
requestScreenRecordingPrompt: requestScreenRecordingPrompt2,
|
|
18209
17751
|
openSystemSettings: openSystemSettings2,
|
|
18210
|
-
detectScreenSize: detectScreenSize2
|
|
18211
|
-
|
|
18212
|
-
|
|
17752
|
+
detectScreenSize: detectScreenSize2,
|
|
17753
|
+
getResponsibleAppName: getResponsibleAppName2
|
|
17754
|
+
} = await Promise.resolve().then(() => (init_desktop_permissions(), desktop_permissions_exports));
|
|
17755
|
+
console.log(chalk.bold("\nDesktop-automation setup\n"));
|
|
17756
|
+
const responsibleApp = await getResponsibleAppName2();
|
|
17757
|
+
console.log(` ${chalk.dim("\u2022")} Permissions need to be granted to: ${chalk.cyan(responsibleApp)}`);
|
|
17758
|
+
console.log(` ${chalk.dim("Not to cliclick, screencapture, osascript, or node \u2014 macOS attributes")}`);
|
|
17759
|
+
console.log(` ${chalk.dim("TCC entries to the responsible (parent GUI) process.")}`);
|
|
17760
|
+
console.log();
|
|
18213
17761
|
if (!await isCliclickInstalled2()) {
|
|
18214
17762
|
console.log(` ${chalk.red("\u2717")} cliclick is not installed`);
|
|
18215
17763
|
console.log(` ${chalk.dim("Run: brew install cliclick")}`);
|
|
18216
17764
|
console.log(` ${chalk.dim("Then re-run: sparkecoder request-permissions")}`);
|
|
18217
17765
|
console.log();
|
|
18218
17766
|
process.exit(1);
|
|
17767
|
+
} else {
|
|
17768
|
+
console.log(` ${chalk.green("\u2713")} cliclick installed`);
|
|
18219
17769
|
}
|
|
18220
17770
|
const acc = await hasAccessibilityPermissions2();
|
|
18221
17771
|
const screen = await hasScreenRecordingPermissions2();
|
|
18222
|
-
let
|
|
17772
|
+
let needsAction = false;
|
|
18223
17773
|
if (!acc.ok) {
|
|
18224
|
-
console.log(
|
|
18225
|
-
|
|
17774
|
+
console.log();
|
|
17775
|
+
console.log(` ${chalk.yellow("\u26A0")} Accessibility permission missing \u2014 opening System Settings...`);
|
|
18226
17776
|
await openSystemSettings2("accessibility");
|
|
18227
|
-
console.log(` ${chalk.dim("1. In Privacy & Security \u2192 Accessibility, click
|
|
18228
|
-
console.log(` ${chalk.dim("2. Add
|
|
18229
|
-
console.log(` ${chalk.dim("3.
|
|
18230
|
-
|
|
17777
|
+
console.log(` ${chalk.dim("1. In Privacy & Security \u2192 Accessibility, click the ")}+${chalk.dim(" button.")}`);
|
|
17778
|
+
console.log(` ${chalk.dim("2. Add ")}${chalk.cyan(responsibleApp)}${chalk.dim(".")}`);
|
|
17779
|
+
console.log(` ${chalk.dim("3. Make sure its toggle is ON.")}`);
|
|
17780
|
+
needsAction = true;
|
|
18231
17781
|
} else {
|
|
18232
17782
|
console.log(` ${chalk.green("\u2713")} Accessibility already granted`);
|
|
18233
17783
|
}
|
|
18234
17784
|
if (!screen) {
|
|
18235
|
-
console.log(
|
|
18236
|
-
|
|
17785
|
+
console.log();
|
|
17786
|
+
console.log(` ${chalk.yellow("\u26A0")} Screen Recording permission missing \u2014 opening System Settings...`);
|
|
18237
17787
|
await openSystemSettings2("screen-recording");
|
|
18238
|
-
console.log(` ${chalk.dim("1. In Privacy & Security \u2192 Screen Recording, click
|
|
18239
|
-
console.log(` ${chalk.dim("2. Add
|
|
18240
|
-
console.log(` ${chalk.dim("3.
|
|
18241
|
-
|
|
17788
|
+
console.log(` ${chalk.dim("1. In Privacy & Security \u2192 Screen & System Audio Recording, click the ")}+${chalk.dim(" button.")}`);
|
|
17789
|
+
console.log(` ${chalk.dim("2. Add ")}${chalk.cyan(responsibleApp)}${chalk.dim(".")}`);
|
|
17790
|
+
console.log(` ${chalk.dim("3. Make sure its toggle is ON.")}`);
|
|
17791
|
+
console.log(` ${chalk.dim('Note: macOS 15 (Sequoia) will prompt weekly to re-confirm \u2014 just click "Allow".')}`);
|
|
17792
|
+
needsAction = true;
|
|
18242
17793
|
} else {
|
|
18243
17794
|
console.log(` ${chalk.green("\u2713")} Screen Recording already granted`);
|
|
18244
17795
|
}
|
|
18245
17796
|
const size = await detectScreenSize2();
|
|
18246
17797
|
if (size) {
|
|
18247
|
-
console.log(
|
|
17798
|
+
console.log();
|
|
17799
|
+
console.log(` ${chalk.dim("\u2022")} Main display: ${formatDisplaySize(size)}`);
|
|
18248
17800
|
}
|
|
18249
17801
|
console.log();
|
|
18250
|
-
if (
|
|
18251
|
-
console.log(chalk.yellow(
|
|
18252
|
-
console.log(chalk.dim("Then run: sparkecoder check-permissions"));
|
|
17802
|
+
if (needsAction) {
|
|
17803
|
+
console.log(chalk.yellow(`After toggling on ${responsibleApp}, RESTART it (and the agent process inside it) so the new TCC entry takes effect.`));
|
|
17804
|
+
console.log(chalk.dim("Then re-run: sparkecoder check-permissions"));
|
|
18253
17805
|
} else {
|
|
18254
|
-
console.log(chalk.green("All permissions are already granted.
|
|
17806
|
+
console.log(chalk.green("All permissions are already granted. Desktop automation is ready."));
|
|
17807
|
+
console.log(chalk.dim("See the `desktop-automation` skill for recipes."));
|
|
18255
17808
|
}
|
|
18256
17809
|
console.log();
|
|
18257
17810
|
});
|
|
18258
17811
|
{
|
|
18259
17812
|
let stateFilePath = function() {
|
|
18260
|
-
return
|
|
17813
|
+
return join16(ensureAppDataDirectory(), "recordings.json");
|
|
18261
17814
|
}, readState = function() {
|
|
18262
17815
|
const p = stateFilePath();
|
|
18263
|
-
if (!
|
|
17816
|
+
if (!existsSync21(p)) return [];
|
|
18264
17817
|
try {
|
|
18265
|
-
return JSON.parse(
|
|
17818
|
+
return JSON.parse(readFileSync10(p, "utf-8"));
|
|
18266
17819
|
} catch {
|
|
18267
17820
|
return [];
|
|
18268
17821
|
}
|
|
@@ -18281,18 +17834,17 @@ program.command("request-permissions").description("Request macOS permissions fo
|
|
|
18281
17834
|
stateFilePath2 = stateFilePath, readState2 = readState, writeState2 = writeState, isAlive2 = isAlive, pruneDead2 = pruneDead;
|
|
18282
17835
|
const record = program.command("record").description("Start/stop screen recordings");
|
|
18283
17836
|
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) => {
|
|
18284
|
-
const { spawn: spawn3 } = await import("child_process");
|
|
18285
17837
|
const { homedir: homedir2, platform: osPlatform } = await import("os");
|
|
18286
|
-
const outDir = opts.dir ? resolve12(opts.dir.replace(/^~/, homedir2())) :
|
|
18287
|
-
|
|
17838
|
+
const outDir = opts.dir ? resolve12(opts.dir.replace(/^~/, homedir2())) : join16(homedir2(), "recordings");
|
|
17839
|
+
mkdirSync10(outDir, { recursive: true });
|
|
18288
17840
|
const id = `rec-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
18289
17841
|
const ext = osPlatform() === "darwin" ? "mov" : "mp4";
|
|
18290
17842
|
const filename = `${id}${opts.name ? `-${String(opts.name).replace(/[^a-z0-9-]+/gi, "-").toLowerCase()}` : ""}.${ext}`;
|
|
18291
|
-
const path =
|
|
17843
|
+
const path = join16(outDir, filename);
|
|
18292
17844
|
let cmd;
|
|
18293
17845
|
let args;
|
|
18294
17846
|
if (osPlatform() === "darwin") {
|
|
18295
|
-
cmd = "screencapture";
|
|
17847
|
+
cmd = existsSync21("/usr/sbin/screencapture") ? "/usr/sbin/screencapture" : "screencapture";
|
|
18296
17848
|
args = ["-v", "-C", "-k", path];
|
|
18297
17849
|
} else if (osPlatform() === "linux") {
|
|
18298
17850
|
const display = process.env.DISPLAY || ":0.0";
|
|
@@ -18320,12 +17872,43 @@ program.command("request-permissions").description("Request macOS permissions fo
|
|
|
18320
17872
|
console.error(JSON.stringify({ error: `Unsupported platform: ${osPlatform()}` }));
|
|
18321
17873
|
process.exit(1);
|
|
18322
17874
|
}
|
|
18323
|
-
const
|
|
18324
|
-
child
|
|
18325
|
-
|
|
18326
|
-
|
|
17875
|
+
const { spawn: spawnRec } = await import("child_process");
|
|
17876
|
+
const child = spawnRec(cmd, args, { detached: true, stdio: ["ignore", "ignore", "pipe"] });
|
|
17877
|
+
let stderrTail = "";
|
|
17878
|
+
child.stderr?.on("data", (b) => {
|
|
17879
|
+
stderrTail = (stderrTail + b.toString()).slice(-2e3);
|
|
17880
|
+
});
|
|
17881
|
+
let spawnError = null;
|
|
17882
|
+
child.on("error", (err) => {
|
|
17883
|
+
spawnError = err?.message ?? String(err);
|
|
17884
|
+
});
|
|
17885
|
+
let exited = false;
|
|
17886
|
+
let exitCode = null;
|
|
17887
|
+
child.on("exit", (code) => {
|
|
17888
|
+
exited = true;
|
|
17889
|
+
exitCode = code;
|
|
17890
|
+
});
|
|
17891
|
+
await new Promise((r) => setTimeout(r, 600));
|
|
17892
|
+
if (spawnError || exited || !child.pid) {
|
|
17893
|
+
const diagnosis = [
|
|
17894
|
+
spawnError && `spawn error: ${spawnError}`,
|
|
17895
|
+
exited && `recorder exited code=${exitCode} within 600ms`,
|
|
17896
|
+
stderrTail.trim() && `stderr: ${stderrTail.trim()}`,
|
|
17897
|
+
osPlatform() === "darwin" && `tried: ${cmd} ${args.join(" ")}`,
|
|
17898
|
+
osPlatform() === "darwin" && `if missing Screen Recording permission, run: sparkecoder request-permissions`,
|
|
17899
|
+
osPlatform() === "linux" && `display=${process.env.DISPLAY || ":0.0"} \u2014 ensure X server is running and ffmpeg is installed`
|
|
17900
|
+
].filter(Boolean).join(". ");
|
|
17901
|
+
console.error(JSON.stringify({
|
|
17902
|
+
error: `Failed to start ${cmd}`,
|
|
17903
|
+
detail: diagnosis || "(no diagnostic output)",
|
|
17904
|
+
cmd,
|
|
17905
|
+
args,
|
|
17906
|
+
pid: child.pid,
|
|
17907
|
+
exitCode
|
|
17908
|
+
}));
|
|
18327
17909
|
process.exit(1);
|
|
18328
17910
|
}
|
|
17911
|
+
child.unref();
|
|
18329
17912
|
const row = {
|
|
18330
17913
|
id,
|
|
18331
17914
|
name: opts.name,
|
|
@@ -18364,7 +17947,7 @@ program.command("request-permissions").description("Request macOS permissions fo
|
|
|
18364
17947
|
}
|
|
18365
17948
|
}
|
|
18366
17949
|
writeState(rows.filter((r) => r.id !== id));
|
|
18367
|
-
const fileExists =
|
|
17950
|
+
const fileExists = existsSync21(row.path);
|
|
18368
17951
|
const sizeMb = fileExists ? Math.round(statSync3(row.path).size / (1024 * 1024) * 10) / 10 : 0;
|
|
18369
17952
|
console.log(JSON.stringify({
|
|
18370
17953
|
id,
|
|
@@ -18399,7 +17982,7 @@ program.command("request-permissions").description("Request macOS permissions fo
|
|
|
18399
17982
|
}
|
|
18400
17983
|
}
|
|
18401
17984
|
}
|
|
18402
|
-
stopped.push({ id: r.id, path: r.path, ok:
|
|
17985
|
+
stopped.push({ id: r.id, path: r.path, ok: existsSync21(r.path) });
|
|
18403
17986
|
}
|
|
18404
17987
|
writeState([]);
|
|
18405
17988
|
console.log(JSON.stringify({ stopped }));
|