sparkecoder 0.1.117 → 0.1.119

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/dist/agent/index.d.ts +2 -2
  2. package/dist/agent/index.js +117 -698
  3. package/dist/agent/index.js.map +1 -1
  4. package/dist/cli.js +639 -1042
  5. package/dist/cli.js.map +1 -1
  6. package/dist/db/index.d.ts +2 -2
  7. package/dist/{index-Bi8Ek02A.d.ts → index-Bcz0aCAR.d.ts} +1 -10
  8. package/dist/index.d.ts +4 -4
  9. package/dist/index.js +406 -944
  10. package/dist/index.js.map +1 -1
  11. package/dist/{schema-ecQSnCMz.d.ts → schema-BWbWmfDQ.d.ts} +0 -2
  12. package/dist/server/index.js +406 -944
  13. package/dist/server/index.js.map +1 -1
  14. package/dist/skills/default/desktop-automation.md +290 -0
  15. package/dist/skills/default/recording.md +3 -3
  16. package/dist/tools/index.d.ts +1 -167
  17. package/dist/tools/index.js +5 -590
  18. package/dist/tools/index.js.map +1 -1
  19. package/package.json +1 -1
  20. package/src/skills/default/desktop-automation.md +290 -0
  21. package/src/skills/default/recording.md +3 -3
  22. package/web/.next/BUILD_ID +1 -1
  23. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  24. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  25. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  26. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  27. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  28. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  29. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  30. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  31. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  32. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  34. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +1 -1
  35. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  36. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  37. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  38. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  39. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  40. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  41. package/web/.next/standalone/web/.next/server/app/agents.html +1 -1
  42. package/web/.next/standalone/web/.next/server/app/agents.rsc +1 -1
  43. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents/__PAGE__.segment.rsc +1 -1
  44. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents.segment.rsc +1 -1
  45. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p.segment.rsc +1 -1
  46. package/web/.next/standalone/web/.next/server/app/agents.segments/_full.segment.rsc +1 -1
  47. package/web/.next/standalone/web/.next/server/app/agents.segments/_head.segment.rsc +1 -1
  48. package/web/.next/standalone/web/.next/server/app/agents.segments/_index.segment.rsc +1 -1
  49. package/web/.next/standalone/web/.next/server/app/agents.segments/_tree.segment.rsc +1 -1
  50. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  51. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +1 -1
  52. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +1 -1
  53. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  54. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +1 -1
  55. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
  56. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  57. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  58. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  59. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  60. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +1 -1
  61. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +1 -1
  62. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  63. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +1 -1
  64. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
  65. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  66. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  67. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  68. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  69. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +1 -1
  70. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +1 -1
  71. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  72. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +1 -1
  73. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
  74. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  75. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  76. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  77. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  78. package/web/.next/standalone/web/.next/server/app/docs.rsc +1 -1
  79. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +1 -1
  80. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  81. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +1 -1
  82. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
  83. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  84. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  85. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  86. package/web/.next/standalone/web/.next/server/app/index.rsc +1 -1
  87. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
  88. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
  89. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
  90. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  91. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
  92. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  93. package/web/.next/standalone/web/.next/server/app/settings.html +1 -1
  94. package/web/.next/standalone/web/.next/server/app/settings.rsc +1 -1
  95. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings/__PAGE__.segment.rsc +1 -1
  96. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings.segment.rsc +1 -1
  97. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p.segment.rsc +1 -1
  98. package/web/.next/standalone/web/.next/server/app/settings.segments/_full.segment.rsc +1 -1
  99. package/web/.next/standalone/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  100. package/web/.next/standalone/web/.next/server/app/settings.segments/_index.segment.rsc +1 -1
  101. package/web/.next/standalone/web/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  102. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  103. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  104. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  105. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  106. package/dist/skills/default/computer-use.md +0 -225
  107. package/src/skills/default/computer-use.md +0 -225
  108. /package/web/.next/standalone/web/.next/static/{static/vLqK4jK7EKdLCpQ-D6-qL → Bt00m8W4k5F79ALhN700F}/_buildManifest.js +0 -0
  109. /package/web/.next/standalone/web/.next/static/{static/vLqK4jK7EKdLCpQ-D6-qL → Bt00m8W4k5F79ALhN700F}/_clientMiddlewareManifest.json +0 -0
  110. /package/web/.next/standalone/web/.next/static/{static/vLqK4jK7EKdLCpQ-D6-qL → Bt00m8W4k5F79ALhN700F}/_ssgManifest.js +0 -0
  111. /package/web/.next/standalone/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → static/Bt00m8W4k5F79ALhN700F}/_buildManifest.js +0 -0
  112. /package/web/.next/standalone/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → static/Bt00m8W4k5F79ALhN700F}/_clientMiddlewareManifest.json +0 -0
  113. /package/web/.next/standalone/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → static/Bt00m8W4k5F79ALhN700F}/_ssgManifest.js +0 -0
  114. /package/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → Bt00m8W4k5F79ALhN700F}/_buildManifest.js +0 -0
  115. /package/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → Bt00m8W4k5F79ALhN700F}/_clientMiddlewareManifest.json +0 -0
  116. /package/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → Bt00m8W4k5F79ALhN700F}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -725,12 +725,6 @@ var init_types = __esm({
725
725
  skillsDirectory: z.string().optional(),
726
726
  maxContextChars: z.number().optional().default(2e5),
727
727
  task: TaskConfigSchema.optional(),
728
- // Anthropic computer use tool — opt-in. When true, the `computer` tool is
729
- // included in the toolset for Anthropic models. Default false.
730
- computerUseEnabled: z.boolean().optional(),
731
- // Display dimensions for the computer use tool (defaults: 1280x800).
732
- computerUseDisplayWidth: z.number().int().positive().optional(),
733
- computerUseDisplayHeight: z.number().int().positive().optional(),
734
728
  // 'orchestrator' = supervisor session; 'worker' = task spawned by an orchestrator.
735
729
  role: z.enum(["orchestrator", "worker", "chat"]).optional(),
736
730
  // Optional persona / extra system-prompt text appended to the orchestrator's
@@ -1166,7 +1160,7 @@ function loadConfig(configPath, workingDirectory) {
1166
1160
  ...config,
1167
1161
  server: {
1168
1162
  port: config.server.port,
1169
- host: config.server.host ?? "127.0.0.1",
1163
+ host: config.server.host ?? "0.0.0.0",
1170
1164
  publicUrl: config.server.publicUrl
1171
1165
  },
1172
1166
  resolvedWorkingDirectory,
@@ -1333,7 +1327,7 @@ function createDefaultConfig() {
1333
1327
  },
1334
1328
  server: {
1335
1329
  port: 3141,
1336
- host: "127.0.0.1"
1330
+ host: "0.0.0.0"
1337
1331
  },
1338
1332
  databasePath: "./sparkecoder.db"
1339
1333
  };
@@ -5386,7 +5380,7 @@ function isPathExcluded(relativePath, exclude) {
5386
5380
  }
5387
5381
  async function walkDirectory(dir, include, exclude, baseDir) {
5388
5382
  const { readdirSync: readdirSync4 } = await import("fs");
5389
- const { join: join18, relative: relative10 } = await import("path");
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 = join18(currentDir, entry2.name);
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 platform3 = process.platform === "darwin" ? "darwin" : "other";
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.${computerUseAvailable ? `
8382
- 3. **Computer use** 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).
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 computer use 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 computer use for browser work."*
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 / computer-use tasks
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 need the **\`computer\` tool** (clicking, typing, taking desktop screenshots, 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.
7776
+ There is exactly **one** desktop, mouse, and keyboard on the host. If two or more workers both drive the desktop (clicking with \`cliclick\`, taking screenshots with \`screencapture\`, opening apps, switching windows), they will **fight over the same screen** \u2014 windows will steal focus from each other, screenshots will catch the wrong app, mouse clicks will land on the wrong target.
8389
7777
 
8390
7778
  **Rule**: when spawning workers, look at each one's goal:
8391
7779
 
@@ -8406,7 +7794,7 @@ Example: *"Take a screenshot of Calculator AND run the test suite AND open Syste
8406
7794
 
8407
7795
  Headless workers never interfere with desktop workers (they don't touch the screen), so they always run in parallel.
8408
7796
 
8409
- When you spawn a **desktop worker**, include a one-liner in the goal asking it to **record the session by default** (\`screencapture -v -V <seconds> -C\` on macOS) and return the recording path in its result, *unless* the task is long-running / boring / contains sensitive content. The recording lets you (and the user) replay what actually happened on screen. When the worker reports back, mention the recording path in your reply via the original channel.` : ""}
7797
+ When you spawn a **desktop worker**, tell it to bracket the work with \`sparkecoder record start\` / \`sparkecoder record stop\` (per the \`recording\` skill) so the user can replay what happened on screen, unless the task is long-running / boring / contains sensitive content. When the worker reports back, mention the recording path in your reply via the original channel.` : ""}
8410
7798
 
8411
7799
  Default bias: **when in doubt, decompose**. Two workers running in parallel and reporting independently is almost always better UX than one worker doing things sequentially.
8412
7800
 
@@ -8437,7 +7825,7 @@ You delegate; the worker executes. Stay at the **what** level, not the **how**.
8437
7825
  **DO** put in the goal:
8438
7826
 
8439
7827
  - The end objective ("open the macOS Weather app and capture the forecast for Anchorage as a screen recording").
8440
- - Which skills the worker should load up-front (\`load_skill recording\`, \`load_skill computer-use\`).
7828
+ - Which skills the worker should load up-front (\`load_skill recording\`, \`load_skill desktop-automation\`).
8441
7829
  - Acceptance criteria ("verify the city name is visible in the screenshot before reporting done").
8442
7830
  - Routing back ("post the recording URL to Slack channel C0123 thread 1700.001").
8443
7831
 
@@ -8470,7 +7858,7 @@ Bad goal (don't do this):
8470
7858
  > "Start a screen recording with \`screencapture -v -V 45 -C /tmp/weather.mov &\`, then open Weather with \`open -a Weather\`, then click the search bar with cliclick, type 'Anchorage'..."
8471
7859
 
8472
7860
  Good goal (do this):
8473
- > "Capture a 30\u201360s screen recording of opening the macOS Weather app and viewing the Anchorage, AK forecast. \`load_skill recording\` and \`load_skill computer-use\` 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."
7861
+ > "Capture a 30\u201360s screen recording of opening the macOS Weather app and viewing the Anchorage, AK forecast. \`load_skill recording\` and \`load_skill desktop-automation\` first; use the canonical commands from those skills. Verify the Anchorage temperature is visible in a final screenshot before completing. Return the recording path + a one-line summary of the forecast."
8474
7862
  `;
8475
7863
  }
8476
7864
  function createSummaryPrompt(conversationHistory) {
@@ -8689,17 +8077,17 @@ __export(conversation_archive_exports, {
8689
8077
  getHistoryDir: () => getHistoryDir,
8690
8078
  listSessionArchives: () => listSessionArchives
8691
8079
  });
8692
- import { existsSync as existsSync16, mkdirSync as mkdirSync6, appendFileSync as appendFileSync2, readdirSync as readdirSync2 } from "fs";
8693
- import { join as join9 } from "path";
8080
+ import { existsSync as existsSync15, mkdirSync as mkdirSync5, appendFileSync as appendFileSync2, readdirSync as readdirSync2 } from "fs";
8081
+ import { join as join8 } from "path";
8694
8082
  function getHistoryDir() {
8695
- const dir = join9(ensureAppDataDirectory(), "history");
8696
- if (!existsSync16(dir)) mkdirSync6(dir, { recursive: true });
8083
+ const dir = join8(ensureAppDataDirectory(), "history");
8084
+ if (!existsSync15(dir)) mkdirSync5(dir, { recursive: true });
8697
8085
  return dir;
8698
8086
  }
8699
8087
  function appendTurn(turn) {
8700
8088
  try {
8701
8089
  const dir = getHistoryDir();
8702
- const path = join9(dir, `${turn.sessionId}.jsonl`);
8090
+ const path = join8(dir, `${turn.sessionId}.jsonl`);
8703
8091
  const line = JSON.stringify({
8704
8092
  ts: turn.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
8705
8093
  sessionId: turn.sessionId,
@@ -8728,7 +8116,7 @@ function flattenContent(content) {
8728
8116
  }
8729
8117
  function listSessionArchives() {
8730
8118
  const dir = getHistoryDir();
8731
- if (!existsSync16(dir)) return [];
8119
+ if (!existsSync15(dir)) return [];
8732
8120
  return readdirSync2(dir).filter((f) => f.endsWith(".jsonl"));
8733
8121
  }
8734
8122
  var init_conversation_archive = __esm({
@@ -8800,6 +8188,18 @@ function repairToolPairing(messages) {
8800
8188
  }
8801
8189
  return repaired;
8802
8190
  }
8191
+ function ensureEndsWithUserOrTool(messages) {
8192
+ if (!Array.isArray(messages) || messages.length === 0) return messages;
8193
+ const last = messages[messages.length - 1];
8194
+ if (last?.role !== "assistant") return messages;
8195
+ console.warn(
8196
+ "[context] Trailing assistant message detected \u2014 appending synthetic user turn to satisfy prefill restrictions"
8197
+ );
8198
+ return [
8199
+ ...messages,
8200
+ { role: "user", content: [{ type: "text", text: "Please continue." }] }
8201
+ ];
8202
+ }
8803
8203
  var TOOL_OUTPUT_TRIM_CHARS, COMPACTABLE_TOOLS, ContextManager;
8804
8204
  var init_context = __esm({
8805
8205
  "src/agent/context.ts"() {
@@ -8857,6 +8257,7 @@ ${summaryContent}`
8857
8257
  ];
8858
8258
  }
8859
8259
  messages = repairToolPairing(messages);
8260
+ messages = ensureEndsWithUserOrTool(messages);
8860
8261
  return messages;
8861
8262
  }
8862
8263
  // ---------------------------------------------------------------------------
@@ -9050,7 +8451,8 @@ ${summaryContent}`
9050
8451
  }
9051
8452
  }
9052
8453
  async addResponseMessages(messages) {
9053
- await messageQueries.addMany(this.sessionId, messages);
8454
+ const safe = repairToolPairing(messages);
8455
+ await messageQueries.addMany(this.sessionId, safe);
9054
8456
  try {
9055
8457
  const { appendTurn: appendTurn2, flattenContent: flattenContent2 } = await Promise.resolve().then(() => (init_conversation_archive(), conversation_archive_exports));
9056
8458
  const { sessionQueries: sessionQueries2 } = await Promise.resolve().then(() => (init_db(), db_exports));
@@ -9146,6 +8548,44 @@ function getSlackSigningSecret() {
9146
8548
  function getDefaultOrchestratorName() {
9147
8549
  return readSlackConfig()?.defaultOrchestratorName ?? null;
9148
8550
  }
8551
+ function getCachedSlackSelfIdentity() {
8552
+ const cfg = readSlackConfig();
8553
+ if (!cfg) return null;
8554
+ if (cachedSelf && cachedSelf.token === cfg.botToken) return cachedSelf.identity;
8555
+ return null;
8556
+ }
8557
+ async function ensureSlackSelfIdentity() {
8558
+ const cfg = readSlackConfig();
8559
+ if (!cfg) return null;
8560
+ if (cachedSelf && cachedSelf.token === cfg.botToken) return cachedSelf.identity;
8561
+ if (selfInflight) return selfInflight;
8562
+ selfInflight = (async () => {
8563
+ try {
8564
+ const res = await fetch("https://slack.com/api/auth.test", {
8565
+ method: "POST",
8566
+ headers: { Authorization: `Bearer ${cfg.botToken}` }
8567
+ });
8568
+ const data = await res.json().catch(() => ({}));
8569
+ if (!data?.ok) {
8570
+ console.warn(`[slack] auth.test failed: ${data?.error || `HTTP ${res.status}`}`);
8571
+ return null;
8572
+ }
8573
+ const identity = {
8574
+ botUserId: String(data.user_id || ""),
8575
+ botId: String(data.bot_id || ""),
8576
+ teamId: data.team_id ? String(data.team_id) : void 0
8577
+ };
8578
+ cachedSelf = { token: cfg.botToken, identity };
8579
+ return identity;
8580
+ } catch (err) {
8581
+ console.warn("[slack] auth.test error:", err?.message || err);
8582
+ return null;
8583
+ } finally {
8584
+ selfInflight = null;
8585
+ }
8586
+ })();
8587
+ return selfInflight;
8588
+ }
9149
8589
  function getSlackAllowlistPolicy() {
9150
8590
  try {
9151
8591
  const cfg = getConfig();
@@ -9171,11 +8611,13 @@ function getSlackDeniedReplyPolicy() {
9171
8611
  return { enabled: true, template: DEFAULT_DENIED_TEMPLATE };
9172
8612
  }
9173
8613
  }
9174
- var DEFAULT_DENIED_TEMPLATE;
8614
+ var cachedSelf, selfInflight, DEFAULT_DENIED_TEMPLATE;
9175
8615
  var init_client3 = __esm({
9176
8616
  "src/integrations/slack/client.ts"() {
9177
8617
  "use strict";
9178
8618
  init_config();
8619
+ cachedSelf = null;
8620
+ selfInflight = null;
9179
8621
  DEFAULT_DENIED_TEMPLATE = "Sorry, you don't have permission to use this bot. (Contact the bot owner if you think this is a mistake.)";
9180
8622
  }
9181
8623
  });
@@ -9193,9 +8635,19 @@ function isThreadOwned(channel, threadTs) {
9193
8635
  function stripMention(text) {
9194
8636
  return String(text || "").replace(/<@[^>]+>/g, "").trim();
9195
8637
  }
9196
- function slackEventToInboundResult(event) {
8638
+ function isSelfAuthored(event, self) {
8639
+ if (!self) return true;
8640
+ if (self.botId && event.bot_id && event.bot_id === self.botId) return true;
8641
+ if (self.botUserId && event.user && event.user === self.botUserId) return true;
8642
+ return false;
8643
+ }
8644
+ function slackEventToInboundResult(event, opts = {}) {
9197
8645
  if (!event) return { event: null, dropReason: "empty_text" };
9198
- if (event.bot_id) return { event: null, dropReason: "bot_message" };
8646
+ const self = opts.self ?? getCachedSlackSelfIdentity();
8647
+ const isBotAuthored = !!event.bot_id || event.type === "message" && event.subtype === "bot_message";
8648
+ if (isBotAuthored && isSelfAuthored(event, self)) {
8649
+ return { event: null, dropReason: "bot_message" };
8650
+ }
9199
8651
  if (event.type === "message" && event.subtype && IGNORED_MESSAGE_SUBTYPES.has(event.subtype)) {
9200
8652
  return { event: null, dropReason: "bot_message" };
9201
8653
  }
@@ -9272,7 +8724,6 @@ var init_slack = __esm({
9272
8724
  }
9273
8725
  };
9274
8726
  IGNORED_MESSAGE_SUBTYPES = /* @__PURE__ */ new Set([
9275
- "bot_message",
9276
8727
  "message_changed",
9277
8728
  "message_deleted",
9278
8729
  "channel_join",
@@ -9491,7 +8942,7 @@ var init_messenger = __esm({
9491
8942
  });
9492
8943
 
9493
8944
  // src/orchestrator/schedules-store.ts
9494
- import { nanoid as nanoid5 } from "nanoid";
8945
+ import { nanoid as nanoid4 } from "nanoid";
9495
8946
  async function readOrch(orchestratorSessionId) {
9496
8947
  const s = await sessionQueries.getById(orchestratorSessionId);
9497
8948
  if (!s) return null;
@@ -9506,7 +8957,7 @@ async function createSchedule(orchestratorSessionId, input) {
9506
8957
  const data = await readOrch(orchestratorSessionId);
9507
8958
  if (!data) throw new Error("orchestrator session not found");
9508
8959
  const row = {
9509
- id: `sch_${nanoid5(10)}`,
8960
+ id: `sch_${nanoid4(10)}`,
9510
8961
  name: input.name,
9511
8962
  cron: input.cron,
9512
8963
  prompt: input.prompt,
@@ -9543,7 +8994,7 @@ var init_schedules_store = __esm({
9543
8994
 
9544
8995
  // src/orchestrator/webhooks-store.ts
9545
8996
  import { randomBytes } from "crypto";
9546
- import { nanoid as nanoid6 } from "nanoid";
8997
+ import { nanoid as nanoid5 } from "nanoid";
9547
8998
  function newToken() {
9548
8999
  return randomBytes(24).toString("base64url");
9549
9000
  }
@@ -9560,7 +9011,7 @@ async function createWebhook(orchestratorSessionId, input) {
9560
9011
  const data = await readOrch2(orchestratorSessionId);
9561
9012
  if (!data) throw new Error("orchestrator session not found");
9562
9013
  const row = {
9563
- id: `whk_${nanoid6(10)}`,
9014
+ id: `whk_${nanoid5(10)}`,
9564
9015
  name: input.name,
9565
9016
  token: newToken(),
9566
9017
  wake: input.wake ?? "now",
@@ -9616,8 +9067,8 @@ var init_webhooks_store = __esm({
9616
9067
  });
9617
9068
 
9618
9069
  // src/tools/orchestrator-actions.ts
9619
- import { tool as tool14 } from "ai";
9620
- import { z as z15 } from "zod";
9070
+ import { tool as tool13 } from "ai";
9071
+ import { z as z14 } from "zod";
9621
9072
  async function api2(baseUrl, path, init = {}) {
9622
9073
  const res = await fetch(`${baseUrl}${path}`, {
9623
9074
  method: init.method || "GET",
@@ -9643,7 +9094,7 @@ function previewMessageContent(content) {
9643
9094
  return "";
9644
9095
  }
9645
9096
  function buildAgentTool(opts) {
9646
- return tool14({
9097
+ return tool13({
9647
9098
  description: "Manage worker agents. Actions: list (browse with optional status filter), get (deep dive: status, todos, pending question, recent messages, final result; required: id), spawn (start a new worker; required: name, goal), message (post a message to a running worker; force=true to soft-interrupt; required: id, text), answer_question (resolve a worker's ask_question_to_user prompt; required: id, questionId, answer), stop (hard-cancel a running worker; required: id).",
9648
9099
  inputSchema: agentInputSchema,
9649
9100
  execute: async (input) => {
@@ -9746,7 +9197,7 @@ function buildAgentTool(opts) {
9746
9197
  });
9747
9198
  }
9748
9199
  function buildMessengerTool() {
9749
- return tool14({
9200
+ return tool13({
9750
9201
  description: "Send messages on configured external channels. Actions: list_channels (no args; returns which integrations are configured), post (required: channel, to, text). Use this to ping the user on Slack when a worker finishes, when a schedule fires, etc.",
9751
9202
  inputSchema: messengerInputSchema,
9752
9203
  execute: async (input) => {
@@ -9768,7 +9219,7 @@ function buildMessengerTool() {
9768
9219
  });
9769
9220
  }
9770
9221
  function buildScheduleTool(opts) {
9771
- return tool14({
9222
+ return tool13({
9772
9223
  description: "Recurring prompts. Actions: create (required: name, cron, prompt), list, update (required: id; any of name/cron/prompt/enabled/replyChannel), delete (required: id), pause (required: id), resume (required: id). Cron is standard 5-field syntax.",
9773
9224
  inputSchema: scheduleInputSchema,
9774
9225
  execute: async (input) => {
@@ -9811,7 +9262,7 @@ function buildWebhookUrl(opts, token) {
9811
9262
  return `${base}${webhookPrefix2}/inbox/${token}`;
9812
9263
  }
9813
9264
  function buildWebhookTool(opts) {
9814
- return tool14({
9265
+ return tool13({
9815
9266
  description: "Custom token-protected inbound URLs. Anyone POSTing JSON to the URL pings the orchestrator. Actions: create (required: name), list, update (required: id; optional: name, wake, template, rotateToken), delete (required: id). Use these to wire up GitHub, IFTTT, n8n, etc.",
9816
9267
  inputSchema: webhookInputSchema,
9817
9268
  execute: async (input) => {
@@ -9860,66 +9311,66 @@ var init_orchestrator_actions = __esm({
9860
9311
  init_schedules_store();
9861
9312
  init_config();
9862
9313
  init_webhooks_store();
9863
- AGENT_STATUS_ENUM = z15.enum(["running", "needs_attention", "completed", "failed", "idle"]);
9864
- agentInputSchema = z15.object({
9865
- action: z15.enum(["list", "get", "spawn", "message", "answer_question", "stop"]).describe("Which agent operation to perform."),
9314
+ AGENT_STATUS_ENUM = z14.enum(["running", "needs_attention", "completed", "failed", "idle"]);
9315
+ agentInputSchema = z14.object({
9316
+ action: z14.enum(["list", "get", "spawn", "message", "answer_question", "stop"]).describe("Which agent operation to perform."),
9866
9317
  // list
9867
9318
  status: AGENT_STATUS_ENUM.optional().describe("list only: filter to one status."),
9868
- limit: z15.number().int().min(1).max(100).optional().describe("list only: max rows."),
9319
+ limit: z14.number().int().min(1).max(100).optional().describe("list only: max rows."),
9869
9320
  // get / message / answer_question / stop
9870
- id: z15.string().optional().describe("get | message | answer_question | stop: the agent (session) id."),
9871
- recentMessages: z15.number().int().min(0).max(50).optional().describe("get only: how many recent messages to include."),
9321
+ id: z14.string().optional().describe("get | message | answer_question | stop: the agent (session) id."),
9322
+ recentMessages: z14.number().int().min(0).max(50).optional().describe("get only: how many recent messages to include."),
9872
9323
  // spawn
9873
- name: z15.string().optional().describe("spawn only: short human-readable label."),
9874
- goal: z15.string().optional().describe("spawn only: the worker's self-contained instruction."),
9875
- outputSchema: z15.record(z15.string(), z15.unknown()).optional().describe(
9324
+ name: z14.string().optional().describe("spawn only: short human-readable label."),
9325
+ goal: z14.string().optional().describe("spawn only: the worker's self-contained instruction."),
9326
+ outputSchema: z14.record(z14.string(), z14.unknown()).optional().describe(
9876
9327
  'spawn only: JSON Schema for the worker result. Defaults to {type:"object", properties:{summary:{type:"string"}}, required:["summary"]}.'
9877
9328
  ),
9878
- model: z15.string().optional().describe("spawn only: model override."),
9879
- workingDirectory: z15.string().optional().describe("spawn only: working directory override."),
9880
- maxIterations: z15.number().int().min(1).max(500).optional().describe("spawn only."),
9329
+ model: z14.string().optional().describe("spawn only: model override."),
9330
+ workingDirectory: z14.string().optional().describe("spawn only: working directory override."),
9331
+ maxIterations: z14.number().int().min(1).max(500).optional().describe("spawn only."),
9881
9332
  // message
9882
- text: z15.string().optional().describe("message only: the text to deliver to the worker."),
9883
- force: z15.boolean().optional().describe("message only: soft-interrupt the current step."),
9333
+ text: z14.string().optional().describe("message only: the text to deliver to the worker."),
9334
+ force: z14.boolean().optional().describe("message only: soft-interrupt the current step."),
9884
9335
  // answer_question
9885
- questionId: z15.string().optional().describe("answer_question only: pending question id (e.g. q_abc123)."),
9886
- answer: z15.string().optional().describe("answer_question only: your answer.")
9336
+ questionId: z14.string().optional().describe("answer_question only: pending question id (e.g. q_abc123)."),
9337
+ answer: z14.string().optional().describe("answer_question only: your answer.")
9887
9338
  });
9888
- messengerInputSchema = z15.object({
9889
- action: z15.enum(["list_channels", "post"]),
9339
+ messengerInputSchema = z14.object({
9340
+ action: z14.enum(["list_channels", "post"]),
9890
9341
  // post
9891
- channel: z15.string().optional().describe('post only: channel id (e.g. "slack").'),
9892
- to: z15.string().optional().describe('post only: destination. Slack: channel id (C0123), user id (U0123), or "#channel-name".'),
9893
- text: z15.string().optional().describe("post only: message body."),
9894
- threadTs: z15.string().optional().describe("post + slack: reply in this thread."),
9895
- subject: z15.string().optional().describe("post + email: subject (future).")
9342
+ channel: z14.string().optional().describe('post only: channel id (e.g. "slack").'),
9343
+ to: z14.string().optional().describe('post only: destination. Slack: channel id (C0123), user id (U0123), or "#channel-name".'),
9344
+ text: z14.string().optional().describe("post only: message body."),
9345
+ threadTs: z14.string().optional().describe("post + slack: reply in this thread."),
9346
+ subject: z14.string().optional().describe("post + email: subject (future).")
9896
9347
  });
9897
- scheduleInputSchema = z15.object({
9898
- action: z15.enum(["create", "list", "update", "delete", "pause", "resume"]),
9348
+ scheduleInputSchema = z14.object({
9349
+ action: z14.enum(["create", "list", "update", "delete", "pause", "resume"]),
9899
9350
  // create / update
9900
- name: z15.string().optional().describe("create | update"),
9901
- cron: z15.string().optional().describe('create | update: 5-field cron (e.g. "0 9 * * 1-5" = weekdays at 9am).'),
9902
- prompt: z15.string().optional().describe("create | update: the prompt injected when the schedule fires."),
9903
- replyChannel: z15.string().optional().describe("create | update: default channel id for orchestrator replies."),
9351
+ name: z14.string().optional().describe("create | update"),
9352
+ cron: z14.string().optional().describe('create | update: 5-field cron (e.g. "0 9 * * 1-5" = weekdays at 9am).'),
9353
+ prompt: z14.string().optional().describe("create | update: the prompt injected when the schedule fires."),
9354
+ replyChannel: z14.string().optional().describe("create | update: default channel id for orchestrator replies."),
9904
9355
  // update / delete / pause / resume
9905
- id: z15.string().optional().describe("update | delete | pause | resume: schedule id."),
9906
- enabled: z15.boolean().optional().describe("update only.")
9356
+ id: z14.string().optional().describe("update | delete | pause | resume: schedule id."),
9357
+ enabled: z14.boolean().optional().describe("update only.")
9907
9358
  });
9908
- webhookInputSchema = z15.object({
9909
- action: z15.enum(["create", "list", "update", "delete"]),
9910
- name: z15.string().optional().describe("create | update."),
9911
- wake: z15.enum(["now", "next"]).optional().describe("create | update: now = wake orchestrator immediately; next = add as context."),
9912
- template: z15.string().optional().describe("create | update: mustache-style template ({{path.to.field}}). Defaults to pretty-printed JSON."),
9913
- id: z15.string().optional().describe("update | delete: webhook id."),
9914
- rotateToken: z15.boolean().optional().describe("update only: regenerate the URL token.")
9359
+ webhookInputSchema = z14.object({
9360
+ action: z14.enum(["create", "list", "update", "delete"]),
9361
+ name: z14.string().optional().describe("create | update."),
9362
+ wake: z14.enum(["now", "next"]).optional().describe("create | update: now = wake orchestrator immediately; next = add as context."),
9363
+ template: z14.string().optional().describe("create | update: mustache-style template ({{path.to.field}}). Defaults to pretty-printed JSON."),
9364
+ id: z14.string().optional().describe("update | delete: webhook id."),
9365
+ rotateToken: z14.boolean().optional().describe("update only: regenerate the URL token.")
9915
9366
  });
9916
9367
  }
9917
9368
  });
9918
9369
 
9919
9370
  // src/integrations/mcp/store.ts
9920
- import { nanoid as nanoid7 } from "nanoid";
9921
- import { existsSync as existsSync17, readFileSync as readFileSync8 } from "fs";
9922
- import { resolve as resolve10, join as join10 } from "path";
9371
+ import { nanoid as nanoid6 } from "nanoid";
9372
+ import { existsSync as existsSync16, readFileSync as readFileSync7 } from "fs";
9373
+ import { resolve as resolve10, join as join9 } from "path";
9923
9374
  function readServers() {
9924
9375
  try {
9925
9376
  const cfg = getConfig();
@@ -9931,12 +9382,12 @@ function readServers() {
9931
9382
  function refreshMcpServersFromDisk() {
9932
9383
  const candidates = [
9933
9384
  resolve10(process.cwd(), "sparkecoder.config.json"),
9934
- join10(ensureAppDataDirectory(), "sparkecoder.config.json")
9385
+ join9(ensureAppDataDirectory(), "sparkecoder.config.json")
9935
9386
  ];
9936
9387
  for (const path of candidates) {
9937
- if (!existsSync17(path)) continue;
9388
+ if (!existsSync16(path)) continue;
9938
9389
  try {
9939
- const raw = JSON.parse(readFileSync8(path, "utf-8"));
9390
+ const raw = JSON.parse(readFileSync7(path, "utf-8"));
9940
9391
  const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
9941
9392
  setMcpServers(servers2);
9942
9393
  return servers2;
@@ -9955,7 +9406,7 @@ function createMcpServer(input) {
9955
9406
  const all = readServers();
9956
9407
  validateInput(input);
9957
9408
  const row = {
9958
- id: `mcp_${nanoid7(10)}`,
9409
+ id: `mcp_${nanoid6(10)}`,
9959
9410
  name: sanitizeName(input.name),
9960
9411
  transport: input.transport,
9961
9412
  url: input.url,
@@ -10546,15 +9997,15 @@ var recorder_exports = {};
10546
9997
  __export(recorder_exports, {
10547
9998
  FrameRecorder: () => FrameRecorder
10548
9999
  });
10549
- import { exec as exec6 } from "child_process";
10550
- import { promisify as promisify6 } from "util";
10000
+ import { exec as exec5 } from "child_process";
10001
+ import { promisify as promisify5 } from "util";
10551
10002
  import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
10552
- import { join as join11 } from "path";
10553
- import { tmpdir as tmpdir2 } from "os";
10554
- import { nanoid as nanoid8 } from "nanoid";
10003
+ import { join as join10 } from "path";
10004
+ import { tmpdir } from "os";
10005
+ import { nanoid as nanoid7 } from "nanoid";
10555
10006
  async function checkFfmpeg() {
10556
10007
  try {
10557
- await execAsync6("ffmpeg -version", { timeout: 5e3 });
10008
+ await execAsync5("ffmpeg -version", { timeout: 5e3 });
10558
10009
  return true;
10559
10010
  } catch {
10560
10011
  return false;
@@ -10566,11 +10017,11 @@ async function cleanup(dir) {
10566
10017
  } catch {
10567
10018
  }
10568
10019
  }
10569
- var execAsync6, FrameRecorder;
10020
+ var execAsync5, FrameRecorder;
10570
10021
  var init_recorder = __esm({
10571
10022
  "src/browser/recorder.ts"() {
10572
10023
  "use strict";
10573
- execAsync6 = promisify6(exec6);
10024
+ execAsync5 = promisify5(exec5);
10574
10025
  FrameRecorder = class {
10575
10026
  frames = [];
10576
10027
  startTime = null;
@@ -10606,21 +10057,21 @@ var init_recorder = __esm({
10606
10057
  */
10607
10058
  async encode() {
10608
10059
  if (this.frames.length === 0) return null;
10609
- const workDir = join11(tmpdir2(), `sparkecoder-recording-${nanoid8(8)}`);
10060
+ const workDir = join10(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
10610
10061
  await mkdir4(workDir, { recursive: true });
10611
10062
  try {
10612
10063
  for (let i = 0; i < this.frames.length; i++) {
10613
- const framePath = join11(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
10064
+ const framePath = join10(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
10614
10065
  await writeFile5(framePath, this.frames[i].data);
10615
10066
  }
10616
10067
  const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
10617
10068
  const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
10618
10069
  const clampedFps = Math.max(1, Math.min(fps, 30));
10619
- const outputPath = join11(workDir, `recording_${this.sessionId}.mp4`);
10070
+ const outputPath = join10(workDir, `recording_${this.sessionId}.mp4`);
10620
10071
  const hasFfmpeg = await checkFfmpeg();
10621
10072
  if (hasFfmpeg) {
10622
- await execAsync6(
10623
- `ffmpeg -y -framerate ${clampedFps} -i "${join11(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
10073
+ await execAsync5(
10074
+ `ffmpeg -y -framerate ${clampedFps} -i "${join10(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
10624
10075
  { timeout: 12e4 }
10625
10076
  );
10626
10077
  } else {
@@ -10632,7 +10083,7 @@ var init_recorder = __esm({
10632
10083
  const files = await readdir5(workDir);
10633
10084
  for (const f of files) {
10634
10085
  if (f.startsWith("frame_")) {
10635
- await unlink2(join11(workDir, f)).catch(() => {
10086
+ await unlink2(join10(workDir, f)).catch(() => {
10636
10087
  });
10637
10088
  }
10638
10089
  }
@@ -10657,11 +10108,11 @@ var init_recorder = __esm({
10657
10108
  import {
10658
10109
  streamText as streamText2,
10659
10110
  generateText as generateText3,
10660
- tool as tool15,
10111
+ tool as tool14,
10661
10112
  stepCountIs as stepCountIs2
10662
10113
  } from "ai";
10663
- import { z as z16 } from "zod";
10664
- import { nanoid as nanoid9 } from "nanoid";
10114
+ import { z as z15 } from "zod";
10115
+ import { nanoid as nanoid8 } from "nanoid";
10665
10116
  function anySignal(signals) {
10666
10117
  const ctrl = new AbortController();
10667
10118
  for (const s of signals) {
@@ -10730,14 +10181,10 @@ var init_agent = __esm({
10730
10181
  */
10731
10182
  async createToolsWithCallbacks(options) {
10732
10183
  const config = getConfig();
10733
- const sessionConfig = this.session.config || {};
10734
10184
  const tools = await createTools({
10735
10185
  sessionId: this.session.id,
10736
10186
  workingDirectory: this.session.workingDirectory,
10737
10187
  skillsDirectories: config.resolvedSkillsDirectories,
10738
- enableComputerUse: sessionConfig.computerUseEnabled === true,
10739
- computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
10740
- computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight,
10741
10188
  onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
10742
10189
  onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
10743
10190
  onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0
@@ -10787,14 +10234,10 @@ var init_agent = __esm({
10787
10234
  keepRecentMessages: config.context?.keepRecentMessages || 10,
10788
10235
  autoSummarize: config.context?.autoSummarize ?? true
10789
10236
  });
10790
- const sessionConfig = session.config || {};
10791
10237
  const tools = await createTools({
10792
10238
  sessionId: session.id,
10793
10239
  workingDirectory: session.workingDirectory,
10794
- skillsDirectories: config.resolvedSkillsDirectories,
10795
- enableComputerUse: sessionConfig.computerUseEnabled === true,
10796
- computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
10797
- computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight
10240
+ skillsDirectories: config.resolvedSkillsDirectories
10798
10241
  });
10799
10242
  if (session.config?.role === "orchestrator") {
10800
10243
  const baseUrl = `http://127.0.0.1:${config.server?.port ?? 3141}`;
@@ -11032,14 +10475,10 @@ ${personality.trim()}`;
11032
10475
  });
11033
10476
  }
11034
10477
  };
11035
- const taskSessionConfig = this.session.config || {};
11036
10478
  const taskTools = await createTools({
11037
10479
  sessionId: this.session.id,
11038
10480
  workingDirectory: this.session.workingDirectory,
11039
10481
  skillsDirectories: config.resolvedSkillsDirectories,
11040
- enableComputerUse: taskSessionConfig.computerUseEnabled === true,
11041
- computerUseDisplayWidth: taskSessionConfig.computerUseDisplayWidth,
11042
- computerUseDisplayHeight: taskSessionConfig.computerUseDisplayHeight,
11043
10482
  onBashProgress: bashProgressHandler,
11044
10483
  onWriteFileProgress: (progress) => {
11045
10484
  options.onToolProgress?.({ toolName: "write_file", data: progress });
@@ -11387,11 +10826,11 @@ ${p.text}` : p.text;
11387
10826
  const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
11388
10827
  if (!isRemoteConfigured2()) return [];
11389
10828
  const { readFile: readFile12 } = await import("fs/promises");
11390
- const { join: join18, basename: basename6 } = await import("path");
10829
+ const { join: join17, basename: basename6 } = await import("path");
11391
10830
  const urls = [];
11392
10831
  for (const filePath of filePaths) {
11393
10832
  try {
11394
- const fullPath = filePath.startsWith("/") ? filePath : join18(this.session.workingDirectory, filePath);
10833
+ const fullPath = filePath.startsWith("/") ? filePath : join17(this.session.workingDirectory, filePath);
11395
10834
  const fileName = basename6(fullPath);
11396
10835
  const ext = fileName.split(".").pop()?.toLowerCase() || "";
11397
10836
  const mimeMap = {
@@ -11449,11 +10888,11 @@ ${p.text}` : p.text;
11449
10888
  wrappedTools[name] = originalTool;
11450
10889
  continue;
11451
10890
  }
11452
- wrappedTools[name] = tool15({
10891
+ wrappedTools[name] = tool14({
11453
10892
  description: originalTool.description || "",
11454
- inputSchema: originalTool.inputSchema || z16.object({}),
10893
+ inputSchema: originalTool.inputSchema || z15.object({}),
11455
10894
  execute: async (input, toolOptions) => {
11456
- const toolCallId = toolOptions.toolCallId || nanoid9();
10895
+ const toolCallId = toolOptions.toolCallId || nanoid8();
11457
10896
  const execution = toolExecutionQueries.create({
11458
10897
  sessionId: this.session.id,
11459
10898
  toolName: name,
@@ -11596,19 +11035,19 @@ var init_session_lock = __esm({
11596
11035
  });
11597
11036
 
11598
11037
  // src/orchestrator/webhook-events.ts
11599
- import { existsSync as existsSync18, readFileSync as readFileSync9, appendFileSync as appendFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync7 } from "fs";
11600
- import { dirname as dirname6, join as join12 } from "path";
11601
- import { nanoid as nanoid10 } from "nanoid";
11038
+ import { existsSync as existsSync17, readFileSync as readFileSync8, appendFileSync as appendFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync6 } from "fs";
11039
+ import { dirname as dirname6, join as join11 } from "path";
11040
+ import { nanoid as nanoid9 } from "nanoid";
11602
11041
  function logFilePath() {
11603
- return join12(getAppDataDirectory(), "webhook-events.jsonl");
11042
+ return join11(getAppDataDirectory(), "webhook-events.jsonl");
11604
11043
  }
11605
11044
  function ensureLoaded() {
11606
11045
  if (cache !== null) return cache;
11607
11046
  cache = [];
11608
11047
  try {
11609
11048
  const p = logFilePath();
11610
- if (!existsSync18(p)) return cache;
11611
- const lines = readFileSync9(p, "utf-8").split("\n").filter(Boolean);
11049
+ if (!existsSync17(p)) return cache;
11050
+ const lines = readFileSync8(p, "utf-8").split("\n").filter(Boolean);
11612
11051
  for (const line of lines) {
11613
11052
  try {
11614
11053
  cache.push(JSON.parse(line));
@@ -11632,14 +11071,14 @@ function appendEvent(ev) {
11632
11071
  if (list.length > MAX_EVENTS) list.shift();
11633
11072
  try {
11634
11073
  const p = logFilePath();
11635
- mkdirSync7(dirname6(p), { recursive: true });
11074
+ mkdirSync6(dirname6(p), { recursive: true });
11636
11075
  appendFileSync3(p, JSON.stringify(ev) + "\n");
11637
11076
  } catch {
11638
11077
  }
11639
11078
  }
11640
11079
  function recordEvent(ev) {
11641
11080
  const full = {
11642
- id: ev.id ?? nanoid10(),
11081
+ id: ev.id ?? nanoid9(),
11643
11082
  ts: ev.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
11644
11083
  source: ev.source,
11645
11084
  status: ev.status,
@@ -11663,7 +11102,7 @@ function updateEvent(id, patch) {
11663
11102
  list[i] = { ...list[i], ...patch };
11664
11103
  try {
11665
11104
  const p = logFilePath();
11666
- mkdirSync7(dirname6(p), { recursive: true });
11105
+ mkdirSync6(dirname6(p), { recursive: true });
11667
11106
  writeFileSync3(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
11668
11107
  } catch {
11669
11108
  }
@@ -12038,6 +11477,148 @@ var init_api = __esm({
12038
11477
  }
12039
11478
  });
12040
11479
 
11480
+ // src/utils/desktop-permissions.ts
11481
+ var desktop_permissions_exports = {};
11482
+ __export(desktop_permissions_exports, {
11483
+ detectScreenSize: () => detectScreenSize,
11484
+ getResponsibleAppName: () => getResponsibleAppName,
11485
+ hasAccessibilityPermissions: () => hasAccessibilityPermissions,
11486
+ hasScreenRecordingPermissions: () => hasScreenRecordingPermissions,
11487
+ isCliclickInstalled: () => isCliclickInstalled,
11488
+ isMacOs: () => isMacOs,
11489
+ openSystemSettings: () => openSystemSettings
11490
+ });
11491
+ import { exec as exec7 } from "child_process";
11492
+ import { promisify as promisify7 } from "util";
11493
+ function isMacOs() {
11494
+ return process.platform === "darwin";
11495
+ }
11496
+ async function isCliclickInstalled() {
11497
+ try {
11498
+ await execAsync7("command -v cliclick", { timeout: 2e3 });
11499
+ return true;
11500
+ } catch {
11501
+ return false;
11502
+ }
11503
+ }
11504
+ async function runJxa(script) {
11505
+ try {
11506
+ const escaped = script.replace(/'/g, `'\\''`);
11507
+ const { stdout } = await execAsync7(`osascript -l JavaScript -e '${escaped}'`, {
11508
+ timeout: 5e3
11509
+ });
11510
+ return JSON.parse(stdout.trim());
11511
+ } catch {
11512
+ return null;
11513
+ }
11514
+ }
11515
+ async function hasAccessibilityPermissions() {
11516
+ try {
11517
+ const { stderr } = await execAsync7("cliclick p:.", { timeout: 3e3 });
11518
+ if (stderr && ACCESSIBILITY_DENIED_RE.test(stderr)) {
11519
+ return { ok: false, error: stderr.trim().split("\n")[0] };
11520
+ }
11521
+ return { ok: true };
11522
+ } catch (err) {
11523
+ return { ok: false, error: err?.message || String(err) };
11524
+ }
11525
+ }
11526
+ async function hasScreenRecordingPermissions() {
11527
+ const result = await runJxa(
11528
+ `ObjC.import("Cocoa");
11529
+ ObjC.import("CoreGraphics");
11530
+ ObjC.bindFunction("CGPreflightScreenCaptureAccess", ["bool", []]);
11531
+ JSON.stringify({ hasAccess: !!$.CGPreflightScreenCaptureAccess() });`
11532
+ );
11533
+ return result?.hasAccess ?? false;
11534
+ }
11535
+ async function openSystemSettings(pane) {
11536
+ const url = pane === "accessibility" ? "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility" : "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
11537
+ try {
11538
+ await execAsync7(`open '${url}'`, { timeout: 3e3 });
11539
+ } catch {
11540
+ }
11541
+ }
11542
+ async function detectScreenSize() {
11543
+ try {
11544
+ const { stdout } = await execAsync7("system_profiler SPDisplaysDataType -json", {
11545
+ timeout: 5e3,
11546
+ maxBuffer: 4 * 1024 * 1024
11547
+ });
11548
+ const data = JSON.parse(stdout);
11549
+ const cards = data.SPDisplaysDataType ?? [];
11550
+ const all = cards.flatMap((c) => c.spdisplays_ndrvs ?? []);
11551
+ if (all.length === 0) return null;
11552
+ const primary = all.find((d) => d.spdisplays_main === "spdisplays_yes") ?? all[0];
11553
+ const pixels = parseDim(primary._spdisplays_pixels) ?? parseDim(primary._spdisplays_resolution);
11554
+ const points = parseDim(primary.spdisplays_resolution) ?? pixels;
11555
+ if (!points) return null;
11556
+ return {
11557
+ width: points.w,
11558
+ height: points.h,
11559
+ pixelWidth: pixels?.w,
11560
+ pixelHeight: pixels?.h,
11561
+ displayName: primary._name
11562
+ };
11563
+ } catch {
11564
+ return null;
11565
+ }
11566
+ }
11567
+ function parseDim(s) {
11568
+ if (!s) return null;
11569
+ const m = s.match(/(\d+)\s*x\s*(\d+)/i);
11570
+ if (!m) return null;
11571
+ const w = parseInt(m[1], 10);
11572
+ const h = parseInt(m[2], 10);
11573
+ if (!Number.isFinite(w) || !Number.isFinite(h)) return null;
11574
+ return { w, h };
11575
+ }
11576
+ async function getResponsibleAppName() {
11577
+ const termProgram = process.env.TERM_PROGRAM;
11578
+ if (termProgram) {
11579
+ const pretty = TERM_PROGRAM_NAMES[termProgram] ?? termProgram;
11580
+ return pretty;
11581
+ }
11582
+ try {
11583
+ let pid = process.ppid;
11584
+ for (let i = 0; i < 12 && pid && pid !== 1; i++) {
11585
+ const { stdout } = await execAsync7(`ps -o ppid=,comm= -p ${pid}`, { timeout: 1e3 });
11586
+ const trimmed = stdout.trim();
11587
+ if (!trimmed) break;
11588
+ const m = trimmed.match(/^\s*(\d+)\s+(.+)$/);
11589
+ if (!m) break;
11590
+ const parentPid = parseInt(m[1], 10);
11591
+ const comm = m[2];
11592
+ const bundle = comm.match(/\/([^/]+)\.app\//);
11593
+ if (bundle) return bundle[1];
11594
+ pid = parentPid;
11595
+ }
11596
+ } catch {
11597
+ }
11598
+ return "the terminal app you launched the agent from";
11599
+ }
11600
+ var execAsync7, ACCESSIBILITY_DENIED_RE, TERM_PROGRAM_NAMES;
11601
+ var init_desktop_permissions = __esm({
11602
+ "src/utils/desktop-permissions.ts"() {
11603
+ "use strict";
11604
+ execAsync7 = promisify7(exec7);
11605
+ ACCESSIBILITY_DENIED_RE = /(not allowed|not trusted|accessibility (privilege|client)|control your computer|privilege.{0,20}required|enable.{0,20}accessibility)/i;
11606
+ TERM_PROGRAM_NAMES = {
11607
+ Apple_Terminal: "Terminal",
11608
+ "iTerm.app": "iTerm",
11609
+ iTerm: "iTerm",
11610
+ vscode: "Visual Studio Code (or Cursor, if that's what you're using)",
11611
+ WarpTerminal: "Warp",
11612
+ ghostty: "Ghostty",
11613
+ Hyper: "Hyper",
11614
+ Tabby: "Tabby",
11615
+ Alacritty: "Alacritty",
11616
+ WezTerm: "WezTerm",
11617
+ kitty: "kitty"
11618
+ };
11619
+ }
11620
+ });
11621
+
12041
11622
  // src/cli.ts
12042
11623
  import { Command } from "commander";
12043
11624
  import chalk from "chalk";
@@ -12053,8 +11634,8 @@ import { Hono as Hono9 } from "hono";
12053
11634
  import { serve } from "@hono/node-server";
12054
11635
  import { cors } from "hono/cors";
12055
11636
  import { logger } from "hono/logger";
12056
- import { existsSync as existsSync21, mkdirSync as mkdirSync10, writeFileSync as writeFileSync6 } from "fs";
12057
- import { resolve as resolve11, dirname as dirname8, join as join16 } from "path";
11637
+ import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
11638
+ import { resolve as resolve11, dirname as dirname8, join as join15 } from "path";
12058
11639
  import { spawn as spawn2 } from "child_process";
12059
11640
  import { createServer as createNetServer } from "net";
12060
11641
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -12067,11 +11648,11 @@ init_tmux();
12067
11648
  init_checkpoints();
12068
11649
  import { Hono } from "hono";
12069
11650
  import { zValidator } from "@hono/zod-validator";
12070
- import { z as z17 } from "zod";
12071
- import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync4, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync3 } from "fs";
11651
+ import { z as z16 } from "zod";
11652
+ import { existsSync as existsSync18, mkdirSync as mkdirSync7, writeFileSync as writeFileSync4, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
12072
11653
  import { readdir as readdir6 } from "fs/promises";
12073
- import { join as join13, basename as basename5, extname as extname8, relative as relative9 } from "path";
12074
- import { nanoid as nanoid11 } from "nanoid";
11654
+ import { join as join12, basename as basename5, extname as extname8, relative as relative9 } from "path";
11655
+ import { nanoid as nanoid10 } from "nanoid";
12075
11656
 
12076
11657
  // src/tasks/agent-status.ts
12077
11658
  init_questions();
@@ -12129,22 +11710,22 @@ function cleanupPendingInputs() {
12129
11710
  }
12130
11711
  }
12131
11712
  }
12132
- var createSessionSchema = z17.object({
12133
- name: z17.string().optional(),
12134
- workingDirectory: z17.string().optional(),
12135
- model: z17.string().optional(),
12136
- toolApprovals: z17.record(z17.string(), z17.boolean()).optional(),
12137
- // Optional full session-config passthrough (computerUseEnabled, etc.)
12138
- config: z17.record(z17.string(), z17.unknown()).optional(),
12139
- role: z17.enum(["orchestrator", "worker", "chat"]).optional()
11713
+ var createSessionSchema = z16.object({
11714
+ name: z16.string().optional(),
11715
+ workingDirectory: z16.string().optional(),
11716
+ model: z16.string().optional(),
11717
+ toolApprovals: z16.record(z16.string(), z16.boolean()).optional(),
11718
+ // Optional full session-config passthrough (role, persona, etc.)
11719
+ config: z16.record(z16.string(), z16.unknown()).optional(),
11720
+ role: z16.enum(["orchestrator", "worker", "chat"]).optional()
12140
11721
  });
12141
- var paginationQuerySchema = z17.object({
12142
- limit: z17.string().optional(),
12143
- offset: z17.string().optional(),
12144
- role: z17.enum(["orchestrator", "worker", "chat", "all"]).optional()
11722
+ var paginationQuerySchema = z16.object({
11723
+ limit: z16.string().optional(),
11724
+ offset: z16.string().optional(),
11725
+ role: z16.enum(["orchestrator", "worker", "chat", "all"]).optional()
12145
11726
  });
12146
- var messagesQuerySchema = z17.object({
12147
- limit: z17.string().optional()
11727
+ var messagesQuerySchema = z16.object({
11728
+ limit: z16.string().optional()
12148
11729
  });
12149
11730
  sessions2.get(
12150
11731
  "/",
@@ -12213,15 +11794,11 @@ sessions2.post(
12213
11794
  async (c) => {
12214
11795
  const body = c.req.valid("json");
12215
11796
  const config = getConfig();
12216
- const cuDefault = process.env.SPARKECODER_COMPUTER_USE === "1";
12217
11797
  const baseConfig = body.config || {};
12218
11798
  const mergedConfig = {
12219
11799
  ...baseConfig,
12220
11800
  ...body.toolApprovals ? { toolApprovals: body.toolApprovals } : {},
12221
- ...body.role ? { role: body.role } : {},
12222
- // Turn on computer use by default if the server was launched with --enable-computer-use,
12223
- // unless the client explicitly provided a value.
12224
- ...cuDefault && baseConfig.computerUseEnabled === void 0 ? { computerUseEnabled: true } : {}
11801
+ ...body.role ? { role: body.role } : {}
12225
11802
  };
12226
11803
  const agent = await Agent.create({
12227
11804
  name: body.name,
@@ -12398,10 +11975,10 @@ sessions2.get("/:id/tools", async (c) => {
12398
11975
  count: executions.length
12399
11976
  });
12400
11977
  });
12401
- var updateSessionSchema = z17.object({
12402
- model: z17.string().optional(),
12403
- name: z17.string().optional(),
12404
- toolApprovals: z17.record(z17.string(), z17.boolean()).optional()
11978
+ var updateSessionSchema = z16.object({
11979
+ model: z16.string().optional(),
11980
+ name: z16.string().optional(),
11981
+ toolApprovals: z16.record(z16.string(), z16.boolean()).optional()
12405
11982
  });
12406
11983
  sessions2.patch(
12407
11984
  "/:id",
@@ -12471,10 +12048,10 @@ sessions2.post("/:id/clear", async (c) => {
12471
12048
  await agent.clearContext();
12472
12049
  return c.json({ success: true, sessionId: id });
12473
12050
  });
12474
- var injectMessageSchema = z17.object({
12475
- text: z17.string().min(1),
12476
- source: z17.enum(["orchestrator", "user", "system"]).optional(),
12477
- force: z17.boolean().optional()
12051
+ var injectMessageSchema = z16.object({
12052
+ text: z16.string().min(1),
12053
+ source: z16.enum(["orchestrator", "user", "system"]).optional(),
12054
+ force: z16.boolean().optional()
12478
12055
  });
12479
12056
  sessions2.post(
12480
12057
  "/:id/messages",
@@ -12488,8 +12065,8 @@ sessions2.post(
12488
12065
  return c.json({ success: true, sessionId: id, queued: true, force });
12489
12066
  }
12490
12067
  );
12491
- var pendingInputSchema = z17.object({
12492
- text: z17.string()
12068
+ var pendingInputSchema = z16.object({
12069
+ text: z16.string()
12493
12070
  });
12494
12071
  sessions2.post(
12495
12072
  "/:id/pending-input",
@@ -12520,13 +12097,13 @@ sessions2.get("/:id/pending-input", async (c) => {
12520
12097
  createdAt: pending.createdAt.toISOString()
12521
12098
  });
12522
12099
  });
12523
- var devtoolsContextSchema = z17.object({
12524
- url: z17.string(),
12525
- path: z17.string(),
12526
- pageName: z17.string().optional(),
12527
- screenWidth: z17.number().optional(),
12528
- screenHeight: z17.number().optional(),
12529
- devicePixelRatio: z17.number().optional()
12100
+ var devtoolsContextSchema = z16.object({
12101
+ url: z16.string(),
12102
+ path: z16.string(),
12103
+ pageName: z16.string().optional(),
12104
+ screenWidth: z16.number().optional(),
12105
+ screenHeight: z16.number().optional(),
12106
+ devicePixelRatio: z16.number().optional()
12530
12107
  });
12531
12108
  sessions2.post(
12532
12109
  "/:id/devtools-context",
@@ -12712,12 +12289,12 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
12712
12289
  });
12713
12290
  function getAttachmentsDir(sessionId) {
12714
12291
  const appDataDir = getAppDataDirectory();
12715
- return join13(appDataDir, "attachments", sessionId);
12292
+ return join12(appDataDir, "attachments", sessionId);
12716
12293
  }
12717
12294
  function ensureAttachmentsDir(sessionId) {
12718
12295
  const dir = getAttachmentsDir(sessionId);
12719
- if (!existsSync19(dir)) {
12720
- mkdirSync8(dir, { recursive: true });
12296
+ if (!existsSync18(dir)) {
12297
+ mkdirSync7(dir, { recursive: true });
12721
12298
  }
12722
12299
  return dir;
12723
12300
  }
@@ -12728,12 +12305,12 @@ sessions2.get("/:id/attachments", async (c) => {
12728
12305
  return c.json({ error: "Session not found" }, 404);
12729
12306
  }
12730
12307
  const dir = getAttachmentsDir(sessionId);
12731
- if (!existsSync19(dir)) {
12308
+ if (!existsSync18(dir)) {
12732
12309
  return c.json({ sessionId, attachments: [], count: 0 });
12733
12310
  }
12734
12311
  const files = readdirSync3(dir);
12735
12312
  const attachments = files.map((filename) => {
12736
- const filePath = join13(dir, filename);
12313
+ const filePath = join12(dir, filename);
12737
12314
  const stats = statSync2(filePath);
12738
12315
  return {
12739
12316
  id: filename.split("_")[0],
@@ -12765,10 +12342,10 @@ sessions2.post("/:id/attachments", async (c) => {
12765
12342
  return c.json({ error: "No file provided" }, 400);
12766
12343
  }
12767
12344
  const dir = ensureAttachmentsDir(sessionId);
12768
- const id = nanoid11(10);
12345
+ const id = nanoid10(10);
12769
12346
  const ext = extname8(file.name) || "";
12770
12347
  const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
12771
- const filePath = join13(dir, safeFilename);
12348
+ const filePath = join12(dir, safeFilename);
12772
12349
  const arrayBuffer = await file.arrayBuffer();
12773
12350
  writeFileSync4(filePath, Buffer.from(arrayBuffer));
12774
12351
  return c.json({
@@ -12791,10 +12368,10 @@ sessions2.post("/:id/attachments", async (c) => {
12791
12368
  return c.json({ error: "Missing filename or data" }, 400);
12792
12369
  }
12793
12370
  const dir = ensureAttachmentsDir(sessionId);
12794
- const id = nanoid11(10);
12371
+ const id = nanoid10(10);
12795
12372
  const ext = extname8(body.filename) || "";
12796
12373
  const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
12797
- const filePath = join13(dir, safeFilename);
12374
+ const filePath = join12(dir, safeFilename);
12798
12375
  let base64Data = body.data;
12799
12376
  if (base64Data.includes(",")) {
12800
12377
  base64Data = base64Data.split(",")[1];
@@ -12823,7 +12400,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
12823
12400
  return c.json({ error: "Session not found" }, 404);
12824
12401
  }
12825
12402
  const dir = getAttachmentsDir(sessionId);
12826
- if (!existsSync19(dir)) {
12403
+ if (!existsSync18(dir)) {
12827
12404
  return c.json({ error: "Attachment not found" }, 404);
12828
12405
  }
12829
12406
  const files = readdirSync3(dir);
@@ -12831,14 +12408,14 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
12831
12408
  if (!file) {
12832
12409
  return c.json({ error: "Attachment not found" }, 404);
12833
12410
  }
12834
- const filePath = join13(dir, file);
12835
- unlinkSync3(filePath);
12411
+ const filePath = join12(dir, file);
12412
+ unlinkSync2(filePath);
12836
12413
  return c.json({ success: true, id: attachmentId });
12837
12414
  });
12838
- var filesQuerySchema = z17.object({
12839
- query: z17.string().optional(),
12415
+ var filesQuerySchema = z16.object({
12416
+ query: z16.string().optional(),
12840
12417
  // Filter query (e.g., "src/com" to match "src/components")
12841
- limit: z17.string().optional()
12418
+ limit: z16.string().optional()
12842
12419
  // Max results (default 50)
12843
12420
  });
12844
12421
  var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
@@ -12914,7 +12491,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
12914
12491
  const entries = await readdir6(currentDir, { withFileTypes: true });
12915
12492
  for (const entry2 of entries) {
12916
12493
  if (results.length >= limit * 2) break;
12917
- const fullPath = join13(currentDir, entry2.name);
12494
+ const fullPath = join12(currentDir, entry2.name);
12918
12495
  const relativePath = relative9(baseDir, fullPath);
12919
12496
  if (entry2.isDirectory() && IGNORED_DIRECTORIES.has(entry2.name)) {
12920
12497
  continue;
@@ -12962,7 +12539,7 @@ sessions2.get(
12962
12539
  return c.json({ error: "Session not found" }, 404);
12963
12540
  }
12964
12541
  const workingDirectory = session.workingDirectory;
12965
- if (!existsSync19(workingDirectory)) {
12542
+ if (!existsSync18(workingDirectory)) {
12966
12543
  return c.json({
12967
12544
  sessionId,
12968
12545
  workingDirectory,
@@ -13075,9 +12652,9 @@ init_session_lock();
13075
12652
  init_config();
13076
12653
  import { Hono as Hono2 } from "hono";
13077
12654
  import { zValidator as zValidator2 } from "@hono/zod-validator";
13078
- import { z as z18 } from "zod";
13079
- import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync5 } from "fs";
13080
- import { join as join14 } from "path";
12655
+ import { z as z17 } from "zod";
12656
+ import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5 } from "fs";
12657
+ import { join as join13 } from "path";
13081
12658
 
13082
12659
  // src/server/resumable-stream.ts
13083
12660
  import { createResumableStreamContext } from "resumable-stream/generic";
@@ -13164,7 +12741,7 @@ var streamContext = createResumableStreamContext({
13164
12741
 
13165
12742
  // src/server/routes/agents.ts
13166
12743
  init_checkpoints();
13167
- import { nanoid as nanoid12 } from "nanoid";
12744
+ import { nanoid as nanoid11 } from "nanoid";
13168
12745
  init_stream_proxy();
13169
12746
  init_recorder();
13170
12747
  init_remote();
@@ -13256,40 +12833,40 @@ function enrichPromptWithDevtoolsContext(sessionId, prompt) {
13256
12833
  ${prompt}`;
13257
12834
  }
13258
12835
  var agents = new Hono2();
13259
- var attachmentSchema = z18.object({
13260
- type: z18.enum(["image", "file"]),
13261
- data: z18.string(),
12836
+ var attachmentSchema = z17.object({
12837
+ type: z17.enum(["image", "file"]),
12838
+ data: z17.string(),
13262
12839
  // base64 data URL or raw base64
13263
- mediaType: z18.string().optional(),
13264
- filename: z18.string().optional()
12840
+ mediaType: z17.string().optional(),
12841
+ filename: z17.string().optional()
13265
12842
  });
13266
- var runPromptSchema = z18.object({
13267
- prompt: z18.string(),
12843
+ var runPromptSchema = z17.object({
12844
+ prompt: z17.string(),
13268
12845
  // Can be empty if attachments are provided
13269
- attachments: z18.array(attachmentSchema).optional()
12846
+ attachments: z17.array(attachmentSchema).optional()
13270
12847
  }).refine(
13271
12848
  (data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
13272
12849
  { message: "Either prompt or attachments must be provided" }
13273
12850
  );
13274
- var quickStartSchema = z18.object({
13275
- prompt: z18.string().min(1),
13276
- name: z18.string().optional(),
13277
- workingDirectory: z18.string().optional(),
13278
- model: z18.string().optional(),
13279
- toolApprovals: z18.record(z18.string(), z18.boolean()).optional()
12851
+ var quickStartSchema = z17.object({
12852
+ prompt: z17.string().min(1),
12853
+ name: z17.string().optional(),
12854
+ workingDirectory: z17.string().optional(),
12855
+ model: z17.string().optional(),
12856
+ toolApprovals: z17.record(z17.string(), z17.boolean()).optional()
13280
12857
  });
13281
- var rejectSchema = z18.object({
13282
- reason: z18.string().optional()
12858
+ var rejectSchema = z17.object({
12859
+ reason: z17.string().optional()
13283
12860
  }).optional();
13284
12861
  var streamAbortControllers = /* @__PURE__ */ new Map();
13285
12862
  function getAttachmentsDirectory(sessionId) {
13286
12863
  const appDataDir = getAppDataDirectory();
13287
- return join14(appDataDir, "attachments", sessionId);
12864
+ return join13(appDataDir, "attachments", sessionId);
13288
12865
  }
13289
12866
  async function saveAttachmentToDisk(sessionId, attachment, index) {
13290
12867
  const attachmentsDir = getAttachmentsDirectory(sessionId);
13291
- if (!existsSync20(attachmentsDir)) {
13292
- mkdirSync9(attachmentsDir, { recursive: true });
12868
+ if (!existsSync19(attachmentsDir)) {
12869
+ mkdirSync8(attachmentsDir, { recursive: true });
13293
12870
  }
13294
12871
  let filename = attachment.filename;
13295
12872
  if (!filename) {
@@ -13307,7 +12884,7 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
13307
12884
  attachment.mediaType = resized.mediaType;
13308
12885
  attachment.data = buffer.toString("base64");
13309
12886
  }
13310
- const filePath = join14(attachmentsDir, filename);
12887
+ const filePath = join13(attachmentsDir, filename);
13311
12888
  writeFileSync5(filePath, buffer);
13312
12889
  return filePath;
13313
12890
  }
@@ -13744,7 +13321,7 @@ ${prompt}` });
13744
13321
  });
13745
13322
  } catch {
13746
13323
  }
13747
- const streamId = `stream_${id}_${nanoid12(10)}`;
13324
+ const streamId = `stream_${id}_${nanoid11(10)}`;
13748
13325
  console.log(`[STREAM] Creating stream ${streamId} for session ${id}`);
13749
13326
  await activeStreamQueries.create(id, streamId);
13750
13327
  const stream = await streamContext.resumableStream(
@@ -13949,7 +13526,7 @@ agents.post(
13949
13526
  });
13950
13527
  const session = agent.getSession();
13951
13528
  const enrichedPrompt = enrichPromptWithDevtoolsContext(session.id, body.prompt);
13952
- const streamId = `stream_${session.id}_${nanoid12(10)}`;
13529
+ const streamId = `stream_${session.id}_${nanoid11(10)}`;
13953
13530
  await createCheckpoint(session.id, session.workingDirectory, 0);
13954
13531
  await activeStreamQueries.create(session.id, streamId);
13955
13532
  const createQuickStreamProducer = () => {
@@ -14216,23 +13793,23 @@ agents.post(
14216
13793
  });
14217
13794
  }
14218
13795
  );
14219
- var browserInputSchema = z18.object({
14220
- type: z18.enum(["input_mouse", "input_keyboard", "input_touch"]),
14221
- eventType: z18.string(),
14222
- x: z18.number().optional(),
14223
- y: z18.number().optional(),
14224
- button: z18.string().optional(),
14225
- clickCount: z18.number().optional(),
14226
- deltaX: z18.number().optional(),
14227
- deltaY: z18.number().optional(),
14228
- key: z18.string().optional(),
14229
- code: z18.string().optional(),
14230
- text: z18.string().optional(),
14231
- modifiers: z18.number().optional(),
14232
- touchPoints: z18.array(z18.object({
14233
- x: z18.number(),
14234
- y: z18.number(),
14235
- id: z18.number().optional()
13796
+ var browserInputSchema = z17.object({
13797
+ type: z17.enum(["input_mouse", "input_keyboard", "input_touch"]),
13798
+ eventType: z17.string(),
13799
+ x: z17.number().optional(),
13800
+ y: z17.number().optional(),
13801
+ button: z17.string().optional(),
13802
+ clickCount: z17.number().optional(),
13803
+ deltaX: z17.number().optional(),
13804
+ deltaY: z17.number().optional(),
13805
+ key: z17.string().optional(),
13806
+ code: z17.string().optional(),
13807
+ text: z17.string().optional(),
13808
+ modifiers: z17.number().optional(),
13809
+ touchPoints: z17.array(z17.object({
13810
+ x: z17.number(),
13811
+ y: z17.number(),
13812
+ id: z17.number().optional()
14236
13813
  })).optional()
14237
13814
  });
14238
13815
  agents.post(
@@ -14267,27 +13844,27 @@ agents.get("/:id/browser-stream", async (c) => {
14267
13844
  init_config();
14268
13845
  import { Hono as Hono3 } from "hono";
14269
13846
  import { zValidator as zValidator3 } from "@hono/zod-validator";
14270
- import { z as z19 } from "zod";
14271
- import { readFileSync as readFileSync10 } from "fs";
13847
+ import { z as z18 } from "zod";
13848
+ import { readFileSync as readFileSync9 } from "fs";
14272
13849
  import { fileURLToPath as fileURLToPath3 } from "url";
14273
- import { dirname as dirname7, join as join15 } from "path";
13850
+ import { dirname as dirname7, join as join14 } from "path";
14274
13851
  var __filename = fileURLToPath3(import.meta.url);
14275
13852
  var __dirname = dirname7(__filename);
14276
13853
  var possiblePaths = [
14277
- join15(__dirname, "../package.json"),
13854
+ join14(__dirname, "../package.json"),
14278
13855
  // From dist/server -> dist/../package.json
14279
- join15(__dirname, "../../package.json"),
13856
+ join14(__dirname, "../../package.json"),
14280
13857
  // From dist/server (if nested differently)
14281
- join15(__dirname, "../../../package.json"),
13858
+ join14(__dirname, "../../../package.json"),
14282
13859
  // From src/server/routes (development)
14283
- join15(process.cwd(), "package.json")
13860
+ join14(process.cwd(), "package.json")
14284
13861
  // From current working directory
14285
13862
  ];
14286
13863
  var currentVersion = "0.0.0";
14287
13864
  var packageName = "sparkecoder";
14288
13865
  for (const packageJsonPath of possiblePaths) {
14289
13866
  try {
14290
- const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
13867
+ const packageJson = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
14291
13868
  if (packageJson.name === "sparkecoder") {
14292
13869
  currentVersion = packageJson.version || "0.0.0";
14293
13870
  packageName = packageJson.name || "sparkecoder";
@@ -14379,9 +13956,9 @@ health.get("/api-keys", async (c) => {
14379
13956
  supportedProviders: SUPPORTED_PROVIDERS
14380
13957
  });
14381
13958
  });
14382
- var setApiKeySchema = z19.object({
14383
- provider: z19.string(),
14384
- apiKey: z19.string().min(1)
13959
+ var setApiKeySchema = z18.object({
13960
+ provider: z18.string(),
13961
+ apiKey: z18.string().min(1)
14385
13962
  });
14386
13963
  health.post(
14387
13964
  "/api-keys",
@@ -14422,12 +13999,12 @@ init_tmux();
14422
13999
  init_db();
14423
14000
  import { Hono as Hono4 } from "hono";
14424
14001
  import { zValidator as zValidator4 } from "@hono/zod-validator";
14425
- import { z as z20 } from "zod";
14002
+ import { z as z19 } from "zod";
14426
14003
  var terminals = new Hono4();
14427
- var spawnSchema = z20.object({
14428
- command: z20.string(),
14429
- cwd: z20.string().optional(),
14430
- name: z20.string().optional()
14004
+ var spawnSchema = z19.object({
14005
+ command: z19.string(),
14006
+ cwd: z19.string().optional(),
14007
+ name: z19.string().optional()
14431
14008
  });
14432
14009
  terminals.post(
14433
14010
  "/:sessionId/terminals",
@@ -14508,8 +14085,8 @@ terminals.get("/:sessionId/terminals/:terminalId", async (c) => {
14508
14085
  // We don't track exit codes in tmux mode
14509
14086
  });
14510
14087
  });
14511
- var logsQuerySchema = z20.object({
14512
- tail: z20.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
14088
+ var logsQuerySchema = z19.object({
14089
+ tail: z19.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
14513
14090
  });
14514
14091
  terminals.get(
14515
14092
  "/:sessionId/terminals/:terminalId/logs",
@@ -14533,8 +14110,8 @@ terminals.get(
14533
14110
  });
14534
14111
  }
14535
14112
  );
14536
- var killSchema = z20.object({
14537
- signal: z20.enum(["SIGTERM", "SIGKILL"]).optional()
14113
+ var killSchema = z19.object({
14114
+ signal: z19.enum(["SIGTERM", "SIGKILL"]).optional()
14538
14115
  });
14539
14116
  terminals.post(
14540
14117
  "/:sessionId/terminals/:terminalId/kill",
@@ -14548,8 +14125,8 @@ terminals.post(
14548
14125
  return c.json({ success: true, message: "Terminal killed" });
14549
14126
  }
14550
14127
  );
14551
- var writeSchema = z20.object({
14552
- input: z20.string()
14128
+ var writeSchema = z19.object({
14129
+ input: z19.string()
14553
14130
  });
14554
14131
  terminals.post(
14555
14132
  "/:sessionId/terminals/:terminalId/write",
@@ -14736,23 +14313,23 @@ init_agent();
14736
14313
  init_config();
14737
14314
  import { Hono as Hono5 } from "hono";
14738
14315
  import { zValidator as zValidator5 } from "@hono/zod-validator";
14739
- import { z as z21 } from "zod";
14740
- import { nanoid as nanoid13 } from "nanoid";
14316
+ import { z as z20 } from "zod";
14317
+ import { nanoid as nanoid12 } from "nanoid";
14741
14318
  init_questions();
14742
14319
  var tasks = new Hono5();
14743
14320
  var taskAbortControllers = /* @__PURE__ */ new Map();
14744
- var createTaskSchema = z21.object({
14745
- prompt: z21.string().min(1),
14746
- outputSchema: z21.record(z21.string(), z21.unknown()),
14747
- webhookUrl: z21.string().url().optional(),
14748
- model: z21.string().optional(),
14749
- workingDirectory: z21.string().optional(),
14750
- name: z21.string().optional(),
14751
- maxIterations: z21.number().int().min(1).max(500).optional(),
14752
- parentTaskId: z21.string().optional(),
14321
+ var createTaskSchema = z20.object({
14322
+ prompt: z20.string().min(1),
14323
+ outputSchema: z20.record(z20.string(), z20.unknown()),
14324
+ webhookUrl: z20.string().url().optional(),
14325
+ model: z20.string().optional(),
14326
+ workingDirectory: z20.string().optional(),
14327
+ name: z20.string().optional(),
14328
+ maxIterations: z20.number().int().min(1).max(500).optional(),
14329
+ parentTaskId: z20.string().optional(),
14753
14330
  /** When set, the spawning orchestrator's session id. Stamped on the
14754
14331
  * worker's config so terminal events can wake the orchestrator. */
14755
- orchestratorSessionId: z21.string().optional()
14332
+ orchestratorSessionId: z20.string().optional()
14756
14333
  });
14757
14334
  tasks.post(
14758
14335
  "/",
@@ -14818,7 +14395,7 @@ tasks.post(
14818
14395
  const taskId = agent.sessionId;
14819
14396
  const abortController = new AbortController();
14820
14397
  taskAbortControllers.set(taskId, abortController);
14821
- const streamId = `stream_${taskId}_${nanoid13(10)}`;
14398
+ const streamId = `stream_${taskId}_${nanoid12(10)}`;
14822
14399
  await activeStreamQueries.create(taskId, streamId);
14823
14400
  const taskStreamProducer = () => {
14824
14401
  const { readable, writable } = new TransformStream();
@@ -14967,9 +14544,9 @@ tasks.post("/:id/cancel", async (c) => {
14967
14544
  }
14968
14545
  return c.json({ taskId: id, status: "failed", error: "Task cancelled by user" });
14969
14546
  });
14970
- var answerQuestionSchema = z21.object({
14971
- answer: z21.string().min(1),
14972
- answeredBy: z21.enum(["orchestrator", "user", "system"]).optional()
14547
+ var answerQuestionSchema = z20.object({
14548
+ answer: z20.string().min(1),
14549
+ answeredBy: z20.enum(["orchestrator", "user", "system"]).optional()
14973
14550
  });
14974
14551
  tasks.post(
14975
14552
  "/:id/questions/:questionId/answer",
@@ -15098,7 +14675,8 @@ slack.post("/events", async (c) => {
15098
14675
  updateEvent(auditId, { status: "dropped", dropReason: "duplicate_delivery" });
15099
14676
  return c.json({ ok: true });
15100
14677
  }
15101
- const { event: inbound, dropReason } = slackEventToInboundResult(ev);
14678
+ const self = await ensureSlackSelfIdentity();
14679
+ const { event: inbound, dropReason } = slackEventToInboundResult(ev, { self });
15102
14680
  if (inbound) {
15103
14681
  const isThreadReply = ev.type === "message" && ev.channel_type !== "im" && typeof ev.thread_ts === "string" && ev.thread_ts !== ev.ts;
15104
14682
  if (isThreadReply) {
@@ -15253,7 +14831,7 @@ init_pool();
15253
14831
  init_webhook_events();
15254
14832
  import { Hono as Hono8 } from "hono";
15255
14833
  import { zValidator as zValidator6 } from "@hono/zod-validator";
15256
- import { z as z22 } from "zod";
14834
+ import { z as z21 } from "zod";
15257
14835
  var integrations = new Hono8();
15258
14836
  var orchestratorRouter = new Hono8();
15259
14837
  async function getOrchestratorId() {
@@ -15276,9 +14854,9 @@ orchestratorRouter.get("/", async (c) => {
15276
14854
  });
15277
14855
  orchestratorRouter.patch(
15278
14856
  "/",
15279
- zValidator6("json", z22.object({
15280
- name: z22.string().min(1).optional(),
15281
- personality: z22.string().optional()
14857
+ zValidator6("json", z21.object({
14858
+ name: z21.string().min(1).optional(),
14859
+ personality: z21.string().optional()
15282
14860
  })),
15283
14861
  async (c) => {
15284
14862
  const id = await getOrchestratorId();
@@ -15354,15 +14932,15 @@ integrations.get("/", async (c) => {
15354
14932
  }
15355
14933
  });
15356
14934
  });
15357
- var slackConfigSchema = z22.object({
15358
- botToken: z22.string().optional(),
15359
- signingSecret: z22.string().optional(),
15360
- defaultOrchestratorName: z22.string().optional(),
15361
- allowedUsers: z22.array(z22.string()).optional(),
15362
- allowedChannels: z22.array(z22.string()).optional(),
15363
- allowDmsFromAnyone: z22.boolean().optional(),
15364
- deniedReplyEnabled: z22.boolean().optional(),
15365
- deniedReplyTemplate: z22.string().optional()
14935
+ var slackConfigSchema = z21.object({
14936
+ botToken: z21.string().optional(),
14937
+ signingSecret: z21.string().optional(),
14938
+ defaultOrchestratorName: z21.string().optional(),
14939
+ allowedUsers: z21.array(z21.string()).optional(),
14940
+ allowedChannels: z21.array(z21.string()).optional(),
14941
+ allowDmsFromAnyone: z21.boolean().optional(),
14942
+ deniedReplyEnabled: z21.boolean().optional(),
14943
+ deniedReplyTemplate: z21.string().optional()
15366
14944
  });
15367
14945
  integrations.post("/slack", zValidator6("json", slackConfigSchema), async (c) => {
15368
14946
  const body = c.req.valid("json");
@@ -15391,11 +14969,11 @@ schedulesRouter.get("/", async (c) => {
15391
14969
  });
15392
14970
  schedulesRouter.post(
15393
14971
  "/",
15394
- zValidator6("json", z22.object({
15395
- name: z22.string().min(1),
15396
- cron: z22.string().min(1),
15397
- prompt: z22.string().min(1),
15398
- replyChannel: z22.string().optional()
14972
+ zValidator6("json", z21.object({
14973
+ name: z21.string().min(1),
14974
+ cron: z21.string().min(1),
14975
+ prompt: z21.string().min(1),
14976
+ replyChannel: z21.string().optional()
15399
14977
  })),
15400
14978
  async (c) => {
15401
14979
  const orcId = await getOrchestratorId();
@@ -15406,12 +14984,12 @@ schedulesRouter.post(
15406
14984
  );
15407
14985
  schedulesRouter.patch(
15408
14986
  "/:id",
15409
- zValidator6("json", z22.object({
15410
- name: z22.string().optional(),
15411
- cron: z22.string().optional(),
15412
- prompt: z22.string().optional(),
15413
- enabled: z22.boolean().optional(),
15414
- replyChannel: z22.string().optional()
14987
+ zValidator6("json", z21.object({
14988
+ name: z21.string().optional(),
14989
+ cron: z21.string().optional(),
14990
+ prompt: z21.string().optional(),
14991
+ enabled: z21.boolean().optional(),
14992
+ replyChannel: z21.string().optional()
15415
14993
  })),
15416
14994
  async (c) => {
15417
14995
  const orcId = await getOrchestratorId();
@@ -15439,10 +15017,10 @@ webhooksRouter.get("/", async (c) => {
15439
15017
  });
15440
15018
  webhooksRouter.post(
15441
15019
  "/",
15442
- zValidator6("json", z22.object({
15443
- name: z22.string().min(1),
15444
- wake: z22.enum(["now", "next"]).optional(),
15445
- template: z22.string().optional()
15020
+ zValidator6("json", z21.object({
15021
+ name: z21.string().min(1),
15022
+ wake: z21.enum(["now", "next"]).optional(),
15023
+ template: z21.string().optional()
15446
15024
  })),
15447
15025
  async (c) => {
15448
15026
  const orcId = await getOrchestratorId();
@@ -15453,11 +15031,11 @@ webhooksRouter.post(
15453
15031
  );
15454
15032
  webhooksRouter.patch(
15455
15033
  "/:id",
15456
- zValidator6("json", z22.object({
15457
- name: z22.string().optional(),
15458
- wake: z22.enum(["now", "next"]).optional(),
15459
- template: z22.string().optional(),
15460
- rotateToken: z22.boolean().optional()
15034
+ zValidator6("json", z21.object({
15035
+ name: z21.string().optional(),
15036
+ wake: z21.enum(["now", "next"]).optional(),
15037
+ template: z21.string().optional(),
15038
+ rotateToken: z21.boolean().optional()
15461
15039
  })),
15462
15040
  async (c) => {
15463
15041
  const orcId = await getOrchestratorId();
@@ -15474,22 +15052,22 @@ webhooksRouter.delete("/:id", async (c) => {
15474
15052
  return c.json({ deleted: ok });
15475
15053
  });
15476
15054
  var mcpRouter = new Hono8();
15477
- var mcpServerSchema = z22.object({
15478
- name: z22.string().min(1),
15479
- transport: z22.enum(["http", "sse", "stdio"]),
15480
- url: z22.string().optional(),
15481
- headers: z22.record(z22.string(), z22.string()).optional(),
15482
- command: z22.string().optional(),
15483
- args: z22.array(z22.string()).optional(),
15484
- enabled: z22.boolean().optional()
15055
+ var mcpServerSchema = z21.object({
15056
+ name: z21.string().min(1),
15057
+ transport: z21.enum(["http", "sse", "stdio"]),
15058
+ url: z21.string().optional(),
15059
+ headers: z21.record(z21.string(), z21.string()).optional(),
15060
+ command: z21.string().optional(),
15061
+ args: z21.array(z21.string()).optional(),
15062
+ enabled: z21.boolean().optional()
15485
15063
  });
15486
- var mcpPatchSchema = z22.object({
15487
- name: z22.string().optional(),
15488
- url: z22.string().optional(),
15489
- headers: z22.record(z22.string(), z22.string()).optional(),
15490
- command: z22.string().optional(),
15491
- args: z22.array(z22.string()).optional(),
15492
- enabled: z22.boolean().optional()
15064
+ var mcpPatchSchema = z21.object({
15065
+ name: z21.string().optional(),
15066
+ url: z21.string().optional(),
15067
+ headers: z21.record(z21.string(), z21.string()).optional(),
15068
+ command: z21.string().optional(),
15069
+ args: z21.array(z21.string()).optional(),
15070
+ enabled: z21.boolean().optional()
15493
15071
  });
15494
15072
  mcpRouter.get("/", async (c) => {
15495
15073
  const rows = listMcpServers().map((s) => ({
@@ -15606,10 +15184,10 @@ init_config();
15606
15184
  init_db();
15607
15185
 
15608
15186
  // src/utils/dependencies.ts
15609
- import { exec as exec7 } from "child_process";
15610
- import { promisify as promisify7 } from "util";
15187
+ import { exec as exec6 } from "child_process";
15188
+ import { promisify as promisify6 } from "util";
15611
15189
  import { platform as platform2 } from "os";
15612
- var execAsync7 = promisify7(exec7);
15190
+ var execAsync6 = promisify6(exec6);
15613
15191
  function getInstallInstructions() {
15614
15192
  const os2 = platform2();
15615
15193
  if (os2 === "darwin") {
@@ -15642,7 +15220,7 @@ Install tmux:
15642
15220
  }
15643
15221
  async function checkTmux() {
15644
15222
  try {
15645
- const { stdout } = await execAsync7("tmux -V", { timeout: 5e3 });
15223
+ const { stdout } = await execAsync6("tmux -V", { timeout: 5e3 });
15646
15224
  const version = stdout.trim();
15647
15225
  return {
15648
15226
  available: true,
@@ -15683,7 +15261,7 @@ async function checkDependencies(options = {}) {
15683
15261
  }
15684
15262
  async function checkAgentBrowser() {
15685
15263
  try {
15686
- const { stdout } = await execAsync7("agent-browser --version", { timeout: 1e4 });
15264
+ const { stdout } = await execAsync6("agent-browser --version", { timeout: 1e4 });
15687
15265
  const version = stdout.trim();
15688
15266
  return { available: true, version };
15689
15267
  } catch {
@@ -15699,12 +15277,12 @@ async function tryInstallAgentBrowser(options = {}) {
15699
15277
  if (!options.quiet) {
15700
15278
  console.log("\u{1F4E6} Installing agent-browser globally...");
15701
15279
  }
15702
- await execAsync7("npm install -g agent-browser", { timeout: 12e4 });
15280
+ await execAsync6("npm install -g agent-browser", { timeout: 12e4 });
15703
15281
  try {
15704
15282
  if (!options.quiet) {
15705
15283
  console.log("\u{1F4E6} Installing Chromium for browser automation...");
15706
15284
  }
15707
- await execAsync7("agent-browser install", { timeout: 12e4 });
15285
+ await execAsync6("agent-browser install", { timeout: 12e4 });
15708
15286
  } catch {
15709
15287
  }
15710
15288
  if (!options.quiet) {
@@ -15723,21 +15301,21 @@ async function tryAutoInstallTmux() {
15723
15301
  try {
15724
15302
  if (os2 === "darwin") {
15725
15303
  try {
15726
- await execAsync7("which brew", { timeout: 5e3 });
15304
+ await execAsync6("which brew", { timeout: 5e3 });
15727
15305
  } catch {
15728
15306
  return false;
15729
15307
  }
15730
15308
  console.log("\u{1F4E6} Installing tmux via Homebrew...");
15731
- await execAsync7("brew install tmux", { timeout: 3e5 });
15309
+ await execAsync6("brew install tmux", { timeout: 3e5 });
15732
15310
  console.log("\u2705 tmux installed successfully");
15733
15311
  return true;
15734
15312
  }
15735
15313
  if (os2 === "linux") {
15736
15314
  try {
15737
- await execAsync7("which apt-get", { timeout: 5e3 });
15315
+ await execAsync6("which apt-get", { timeout: 5e3 });
15738
15316
  console.log("\u{1F4E6} Installing tmux via apt-get...");
15739
15317
  console.log(" (This may require sudo password)");
15740
- await execAsync7("sudo apt-get update && sudo apt-get install -y tmux", {
15318
+ await execAsync6("sudo apt-get update && sudo apt-get install -y tmux", {
15741
15319
  timeout: 3e5
15742
15320
  });
15743
15321
  console.log("\u2705 tmux installed successfully");
@@ -15745,9 +15323,9 @@ async function tryAutoInstallTmux() {
15745
15323
  } catch {
15746
15324
  }
15747
15325
  try {
15748
- await execAsync7("which dnf", { timeout: 5e3 });
15326
+ await execAsync6("which dnf", { timeout: 5e3 });
15749
15327
  console.log("\u{1F4E6} Installing tmux via dnf...");
15750
- await execAsync7("sudo dnf install -y tmux", { timeout: 3e5 });
15328
+ await execAsync6("sudo dnf install -y tmux", { timeout: 3e5 });
15751
15329
  console.log("\u2705 tmux installed successfully");
15752
15330
  return true;
15753
15331
  } catch {
@@ -15787,11 +15365,11 @@ function getWebDirectory() {
15787
15365
  try {
15788
15366
  const currentDir = dirname8(fileURLToPath4(import.meta.url));
15789
15367
  const webDir = resolve11(currentDir, "..", "web");
15790
- if (existsSync21(webDir) && existsSync21(join16(webDir, "package.json"))) {
15368
+ if (existsSync20(webDir) && existsSync20(join15(webDir, "package.json"))) {
15791
15369
  return webDir;
15792
15370
  }
15793
15371
  const altWebDir = resolve11(currentDir, "..", "..", "web");
15794
- if (existsSync21(altWebDir) && existsSync21(join16(altWebDir, "package.json"))) {
15372
+ if (existsSync20(altWebDir) && existsSync20(join15(altWebDir, "package.json"))) {
15795
15373
  return altWebDir;
15796
15374
  }
15797
15375
  return null;
@@ -15849,23 +15427,23 @@ async function findWebPort(preferredPort) {
15849
15427
  return { port: preferredPort, alreadyRunning: false };
15850
15428
  }
15851
15429
  function hasProductionBuild(webDir) {
15852
- const buildIdPath = join16(webDir, ".next", "BUILD_ID");
15853
- return existsSync21(buildIdPath);
15430
+ const buildIdPath = join15(webDir, ".next", "BUILD_ID");
15431
+ return existsSync20(buildIdPath);
15854
15432
  }
15855
15433
  function hasSourceFiles(webDir) {
15856
- const appDir = join16(webDir, "src", "app");
15857
- const pagesDir = join16(webDir, "src", "pages");
15858
- const rootAppDir = join16(webDir, "app");
15859
- const rootPagesDir = join16(webDir, "pages");
15860
- return existsSync21(appDir) || existsSync21(pagesDir) || existsSync21(rootAppDir) || existsSync21(rootPagesDir);
15434
+ const appDir = join15(webDir, "src", "app");
15435
+ const pagesDir = join15(webDir, "src", "pages");
15436
+ const rootAppDir = join15(webDir, "app");
15437
+ const rootPagesDir = join15(webDir, "pages");
15438
+ return existsSync20(appDir) || existsSync20(pagesDir) || existsSync20(rootAppDir) || existsSync20(rootPagesDir);
15861
15439
  }
15862
15440
  function getStandaloneServerPath(webDir) {
15863
15441
  const possiblePaths2 = [
15864
- join16(webDir, ".next", "standalone", "server.js"),
15865
- join16(webDir, ".next", "standalone", "web", "server.js")
15442
+ join15(webDir, ".next", "standalone", "server.js"),
15443
+ join15(webDir, ".next", "standalone", "web", "server.js")
15866
15444
  ];
15867
15445
  for (const serverPath of possiblePaths2) {
15868
- if (existsSync21(serverPath)) {
15446
+ if (existsSync20(serverPath)) {
15869
15447
  return serverPath;
15870
15448
  }
15871
15449
  }
@@ -15905,13 +15483,13 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
15905
15483
  if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
15906
15484
  return { process: null, port: actualPort };
15907
15485
  }
15908
- const usePnpm = existsSync21(join16(webDir, "pnpm-lock.yaml"));
15909
- const useNpm = !usePnpm && existsSync21(join16(webDir, "package-lock.json"));
15486
+ const usePnpm = existsSync20(join15(webDir, "pnpm-lock.yaml"));
15487
+ const useNpm = !usePnpm && existsSync20(join15(webDir, "package-lock.json"));
15910
15488
  const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
15911
15489
  const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
15912
15490
  const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
15913
15491
  const runtimeConfig = { apiBaseUrl: apiUrl };
15914
- const runtimeConfigPath = join16(webDir, "runtime-config.json");
15492
+ const runtimeConfigPath = join15(webDir, "runtime-config.json");
15915
15493
  try {
15916
15494
  writeFileSync6(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
15917
15495
  if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
@@ -16126,8 +15704,8 @@ async function startServer(options = {}) {
16126
15704
  if (options.workingDirectory) {
16127
15705
  config.resolvedWorkingDirectory = options.workingDirectory;
16128
15706
  }
16129
- if (!existsSync21(config.resolvedWorkingDirectory)) {
16130
- mkdirSync10(config.resolvedWorkingDirectory, { recursive: true });
15707
+ if (!existsSync20(config.resolvedWorkingDirectory)) {
15708
+ mkdirSync9(config.resolvedWorkingDirectory, { recursive: true });
16131
15709
  if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
16132
15710
  }
16133
15711
  if (!config.resolvedRemoteServer.url) {
@@ -16168,13 +15746,15 @@ async function startServer(options = {}) {
16168
15746
  if (!options.quiet) console.warn(`[scheduler] start skipped: ${err.message}`);
16169
15747
  }
16170
15748
  const port = options.port || config.server.port;
16171
- const host = options.host || config.server.host || "0.0.0.0";
15749
+ const envHost = process.env.SPARKECODER_API_HOST || process.env.SPARKECODER_HOST;
15750
+ const host = envHost || options.host || config.server.host || "0.0.0.0";
15751
+ const hostSource = envHost ? process.env.SPARKECODER_API_HOST ? "env SPARKECODER_API_HOST" : "env SPARKECODER_HOST" : options.host ? "--host flag" : config.server.host ? "config.server.host" : "default";
16172
15752
  const publicUrl = options.publicUrl || config.server.publicUrl;
16173
15753
  const app = await createApp({ quiet: options.quiet });
16174
15754
  if (!options.quiet) {
16175
15755
  console.log(`
16176
15756
  \u{1F680} SparkECoder API Server`);
16177
- console.log(` \u2192 Running at http://${host}:${port}`);
15757
+ console.log(` \u2192 Binding to ${host}:${port} (source: ${hostSource})`);
16178
15758
  if (publicUrl) {
16179
15759
  console.log(` \u2192 Public URL: ${publicUrl}`);
16180
15760
  }
@@ -16183,10 +15763,22 @@ async function startServer(options = {}) {
16183
15763
  console.log(` \u2192 OpenAPI spec: http://${host}:${port}/openapi.json
16184
15764
  `);
16185
15765
  }
15766
+ if (host === "127.0.0.1" || host === "localhost") {
15767
+ console.log(`[sparkecoder] \u26A0 API bound to ${host} only \u2014 not reachable from outside the machine.`);
15768
+ console.log(`[sparkecoder] For tunnels/reverse proxies (Modal/Fly/Docker), set --host 0.0.0.0 or SPARKECODER_API_HOST=0.0.0.0.`);
15769
+ }
16186
15770
  serverInstance = serve({
16187
15771
  fetch: app.fetch,
16188
15772
  port,
16189
15773
  hostname: host
15774
+ }, (info) => {
15775
+ const actual = `${info.address}:${info.port}`;
15776
+ const requested = `${host}:${port}`;
15777
+ if (info.address === "127.0.0.1" && host !== "127.0.0.1" && host !== "localhost") {
15778
+ console.warn(`[sparkecoder] \u2717 API listener bound to ${actual} but ${requested} was requested. External requests will be refused.`);
15779
+ } else {
15780
+ console.log(`[sparkecoder] \u2713 API listening on ${actual}`);
15781
+ }
16190
15782
  });
16191
15783
  let webPort;
16192
15784
  let webStarted;
@@ -16671,18 +16263,18 @@ function generateOpenAPISpec() {
16671
16263
  init_config();
16672
16264
  init_semantic();
16673
16265
  init_db();
16674
- import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync7, readFileSync as readFileSync11, existsSync as existsSync22, statSync as statSync3 } from "fs";
16675
- import { resolve as resolve12, join as join17 } from "path";
16266
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync7, readFileSync as readFileSync10, existsSync as existsSync21, statSync as statSync3 } from "fs";
16267
+ import { resolve as resolve12, join as join16 } from "path";
16676
16268
  function getCliVersion() {
16677
16269
  const here = dirname9(fileURLToPath5(import.meta.url));
16678
16270
  const candidates = [
16679
- join17(here, "..", "package.json"),
16680
- join17(here, "..", "..", "package.json"),
16681
- join17(process.cwd(), "package.json")
16271
+ join16(here, "..", "package.json"),
16272
+ join16(here, "..", "..", "package.json"),
16273
+ join16(process.cwd(), "package.json")
16682
16274
  ];
16683
16275
  for (const p of candidates) {
16684
16276
  try {
16685
- const pkg = JSON.parse(readFileSync11(p, "utf8"));
16277
+ const pkg = JSON.parse(readFileSync10(p, "utf8"));
16686
16278
  if (pkg.name === "sparkecoder" && pkg.version) return pkg.version;
16687
16279
  } catch {
16688
16280
  }
@@ -17251,20 +16843,10 @@ Unexpected error: ${outerError.message}`));
17251
16843
  }
17252
16844
  }
17253
16845
  var program = new Command();
17254
- program.name("sparkecoder").description("AI coding agent - just type sparkecoder to start chatting").version(getCliVersion()).option("-s, --session <id>", "Resume an existing session").option("-n, --name <name>", "Name for the new session").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-c, --config <path>", "Path to config file").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("--no-auto-start", "Do not auto-start server if not running").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI when auto-starting server").option("--public-url <url>", "Public URL for web UI to connect to API (for Docker/remote access)").option("-v, --verbose", "Enable verbose logging for web server").option("--dangerously-skip-approvals", "Auto-approve all tool calls (no confirmation prompts)").option("--enable-computer-use", "Enable the Anthropic computer use tool for all new sessions by default (macOS only)").action(async (options) => {
17255
- if (options.enableComputerUse) {
17256
- process.env.SPARKECODER_COMPUTER_USE = "1";
17257
- console.log(chalk.cyan("\u2192 Computer use enabled by default for new sessions."));
17258
- }
16846
+ program.name("sparkecoder").description("AI coding agent - just type sparkecoder to start chatting").version(getCliVersion()).option("-s, --session <id>", "Resume an existing session").option("-n, --name <name>", "Name for the new session").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-c, --config <path>", "Path to config file").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("--no-auto-start", "Do not auto-start server if not running").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI when auto-starting server").option("--public-url <url>", "Public URL for web UI to connect to API (for Docker/remote access)").option("-v, --verbose", "Enable verbose logging for web server").option("--dangerously-skip-approvals", "Auto-approve all tool calls (no confirmation prompts)").action(async (options) => {
17259
16847
  await runChat(options);
17260
16848
  });
17261
- program.command("server").description("Start the SparkECoder server (API + Web UI)").option("-p, --port <port>", "API server port", "3141").option("-h, --host <host>", "Server 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("--enable-computer-use", "Enable the Anthropic computer use tool for all new sessions by default (macOS only)").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) => {
17262
- const globalOpts = program.opts();
17263
- const enableCU = options.enableComputerUse || globalOpts.enableComputerUse;
17264
- if (enableCU) {
17265
- process.env.SPARKECODER_COMPUTER_USE = "1";
17266
- console.log(chalk.cyan("\u2192 Computer use enabled by default for new sessions."));
17267
- }
16849
+ program.command("server").description("Start the SparkECoder server (API + Web UI)").option("-p, --port <port>", "API server port", "3141").option("-h, --host <host>", "Server bind address (use 127.0.0.1 to restrict to loopback). Overridable via SPARKECODER_API_HOST / SPARKECODER_HOST env.", "0.0.0.0").option("-c, --config <path>", "Path to config file").option("-w, --working-dir <path>", "Working directory").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI").option("--public-url <url>", "Public URL for web UI to connect to API (for Docker/remote access)").option("-v, --verbose", "Enable verbose logging for web server").option("--setup-secret <secret>", "Setup secret (or short-lived JWT) used for /auth/register and /tunnels when the remote server has SETUP_SECRET configured. Equivalent to setting SPARKECODER_SETUP_SECRET env.").action(async (options) => {
17268
16850
  if (options.setupSecret) {
17269
16851
  process.env.SPARKECODER_SETUP_SECRET = options.setupSecret;
17270
16852
  if (!process.env.SPARKECODER_TUNNEL_SECRET) {
@@ -17307,13 +16889,7 @@ program.command("server").description("Start the SparkECoder server (API + Web U
17307
16889
  process.exit(1);
17308
16890
  }
17309
16891
  });
17310
- program.command("chat").description("Start an interactive chat session").option("-s, --session <id>", "Resume an existing session").option("-n, --name <name>", "Name for the new session").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-c, --config <path>", "Path to config file").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("--no-auto-start", "Do not auto-start server if not running").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI when auto-starting server").option("--public-url <url>", "Public URL for web UI to connect to API (for Docker/remote access)").option("-v, --verbose", "Enable verbose logging for web server").option("--dangerously-skip-approvals", "Auto-approve all tool calls (no confirmation prompts)").option("--enable-computer-use", "Enable the Anthropic computer use tool for all new sessions by default (macOS only)").action(async (options) => {
17311
- const globalOpts = program.opts();
17312
- const enableCU = options.enableComputerUse || globalOpts.enableComputerUse;
17313
- if (enableCU) {
17314
- process.env.SPARKECODER_COMPUTER_USE = "1";
17315
- console.log(chalk.cyan("\u2192 Computer use enabled by default for new sessions."));
17316
- }
16892
+ program.command("chat").description("Start an interactive chat session").option("-s, --session <id>", "Resume an existing session").option("-n, --name <name>", "Name for the new session").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-c, --config <path>", "Path to config file").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("--no-auto-start", "Do not auto-start server if not running").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI when auto-starting server").option("--public-url <url>", "Public URL for web UI to connect to API (for Docker/remote access)").option("-v, --verbose", "Enable verbose logging for web server").option("--dangerously-skip-approvals", "Auto-approve all tool calls (no confirmation prompts)").action(async (options) => {
17317
16893
  await runChat(options);
17318
16894
  });
17319
16895
  program.command("task").description("Run an autonomous task that completes without human interaction").requiredOption("--prompt <prompt>", "Task prompt describing what to do").requiredOption("--schema <schema>", "JSON Schema for the output (file path or inline JSON string)").option("--webhook <url>", "Webhook URL to receive task events").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-n, --name <name>", "Name for the task").option("--max-iterations <n>", "Maximum agent loop iterations", "50").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("-c, --config <path>", "Path to config file").option("--no-auto-start", "Do not auto-start server if not running").option("--parent-task <id>", "Continue from a previous task (inherits its full conversation context)").option("--wait", "Block and poll until task completes").option("-v, --verbose", "Enable verbose logging").action(async (options) => {
@@ -17345,8 +16921,8 @@ program.command("task").description("Run an autonomous task that completes witho
17345
16921
  let outputSchema;
17346
16922
  try {
17347
16923
  const schemaStr = options.schema;
17348
- if (existsSync22(schemaStr)) {
17349
- outputSchema = JSON.parse(readFileSync11(schemaStr, "utf-8"));
16924
+ if (existsSync21(schemaStr)) {
16925
+ outputSchema = JSON.parse(readFileSync10(schemaStr, "utf-8"));
17350
16926
  } else {
17351
16927
  outputSchema = JSON.parse(schemaStr);
17352
16928
  }
@@ -17413,13 +16989,13 @@ program.command("init").description("Create a sparkecoder.config.json file").opt
17413
16989
  let configLocation;
17414
16990
  if (options.global) {
17415
16991
  const appDataDir = ensureAppDataDirectory();
17416
- configPath = join17(appDataDir, "sparkecoder.config.json");
16992
+ configPath = join16(appDataDir, "sparkecoder.config.json");
17417
16993
  configLocation = "global";
17418
16994
  } else {
17419
16995
  configPath = resolve12(process.cwd(), "sparkecoder.config.json");
17420
16996
  configLocation = "local";
17421
16997
  }
17422
- if (existsSync22(configPath) && !options.force) {
16998
+ if (existsSync21(configPath) && !options.force) {
17423
16999
  console.log(chalk.yellow("Config file already exists. Use --force to overwrite."));
17424
17000
  console.log(chalk.dim(` ${configPath}`));
17425
17001
  return;
@@ -17444,11 +17020,11 @@ program.command("slack-setup").description("Interactively configure Slack integr
17444
17020
  console.error(chalk.red("Both bot token and signing secret are required."));
17445
17021
  process.exit(1);
17446
17022
  }
17447
- const configPath = options.global ? join17(ensureAppDataDirectory(), "sparkecoder.config.json") : resolve12(process.cwd(), "sparkecoder.config.json");
17023
+ const configPath = options.global ? join16(ensureAppDataDirectory(), "sparkecoder.config.json") : resolve12(process.cwd(), "sparkecoder.config.json");
17448
17024
  let existing = {};
17449
- if (existsSync22(configPath)) {
17025
+ if (existsSync21(configPath)) {
17450
17026
  try {
17451
- existing = JSON.parse(readFileSync11(configPath, "utf-8"));
17027
+ existing = JSON.parse(readFileSync10(configPath, "utf-8"));
17452
17028
  } catch {
17453
17029
  }
17454
17030
  } else {
@@ -17712,9 +17288,9 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
17712
17288
  }
17713
17289
  const verOut = run("cloudflared", ["--version"]).stdout?.toString().split("\n")[0] || "installed";
17714
17290
  console.log(chalk.green("\u2713"), "cloudflared:", chalk.dim(verOut));
17715
- const cfDir = join17(homedir2(), ".cloudflared");
17716
- const certPath = join17(cfDir, "cert.pem");
17717
- if (!existsSync22(certPath)) {
17291
+ const cfDir = join16(homedir2(), ".cloudflared");
17292
+ const certPath = join16(cfDir, "cert.pem");
17293
+ if (!existsSync21(certPath)) {
17718
17294
  console.log(chalk.yellow("No Cloudflare login cert found."));
17719
17295
  if (await confirm("Run `cloudflared tunnel login` now (opens a browser)?", true)) {
17720
17296
  run("cloudflared", ["tunnel", "login"], { inheritIO: true, check: true });
@@ -17758,8 +17334,8 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
17758
17334
  return;
17759
17335
  }
17760
17336
  }
17761
- const credsFile = tunnel.credentials_file || join17(cfDir, `${tunnel.id}.json`);
17762
- if (!existsSync22(credsFile)) {
17337
+ const credsFile = tunnel.credentials_file || join16(cfDir, `${tunnel.id}.json`);
17338
+ if (!existsSync21(credsFile)) {
17763
17339
  console.log(chalk.yellow(`Credentials file not found at ${credsFile}. The tunnel may still work via cert.pem.`));
17764
17340
  }
17765
17341
  let hostname = options.hostname;
@@ -17782,7 +17358,7 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
17782
17358
  console.log(chalk.yellow("DNS route warning:"), err.trim().split("\n").slice(-2).join(" "));
17783
17359
  }
17784
17360
  }
17785
- const configPath = join17(cfDir, "config.yml");
17361
+ const configPath = join16(cfDir, "config.yml");
17786
17362
  const configBody = `tunnel: ${tunnel.id}
17787
17363
  credentials-file: ${credsFile}
17788
17364
  ingress:
@@ -17793,8 +17369,8 @@ ingress:
17793
17369
  - service: http_status:404
17794
17370
  `;
17795
17371
  let wroteConfig = false;
17796
- if (existsSync22(configPath)) {
17797
- const existing = readFileSync11(configPath, "utf8");
17372
+ if (existsSync21(configPath)) {
17373
+ const existing = readFileSync10(configPath, "utf8");
17798
17374
  if (existing.trim() === configBody.trim()) {
17799
17375
  console.log(chalk.green("\u2713"), `config.yml already up to date: ${configPath}`);
17800
17376
  wroteConfig = true;
@@ -18168,18 +17744,27 @@ ${providerName} API Key:
18168
17744
  process.exit(1);
18169
17745
  }
18170
17746
  });
18171
- program.command("check-permissions").description("Check macOS permissions required for the computer use tool (Accessibility + Screen Recording)").action(async () => {
17747
+ function formatDisplaySize(size) {
17748
+ const points = `${size.width}\xD7${size.height} points`;
17749
+ const px = size.pixelWidth && size.pixelHeight && (size.pixelWidth !== size.width || size.pixelHeight !== size.height) ? ` (${size.pixelWidth}\xD7${size.pixelHeight} pixels, Retina)` : "";
17750
+ const name = size.displayName ? `${size.displayName} \u2014 ` : "";
17751
+ return `${name}${points}${px}`;
17752
+ }
17753
+ program.command("check-permissions").description("Check macOS prerequisites for desktop automation (cliclick + Accessibility + Screen Recording)").action(async () => {
18172
17754
  if (process.platform !== "darwin") {
18173
- console.log(chalk.yellow(`Computer use is only supported on macOS. Current platform: ${process.platform}`));
17755
+ console.log(chalk.yellow(`Desktop automation prerequisites only apply on macOS. Current platform: ${process.platform}`));
18174
17756
  process.exit(0);
18175
17757
  }
18176
17758
  const {
18177
17759
  isCliclickInstalled: isCliclickInstalled2,
18178
17760
  hasAccessibilityPermissions: hasAccessibilityPermissions2,
18179
17761
  hasScreenRecordingPermissions: hasScreenRecordingPermissions2,
18180
- detectScreenSize: detectScreenSize2
18181
- } = await Promise.resolve().then(() => (init_computer_use(), computer_use_exports));
18182
- console.log(chalk.bold("\nComputer use prerequisites:\n"));
17762
+ detectScreenSize: detectScreenSize2,
17763
+ getResponsibleAppName: getResponsibleAppName2
17764
+ } = await Promise.resolve().then(() => (init_desktop_permissions(), desktop_permissions_exports));
17765
+ console.log(chalk.bold("\nDesktop-automation prerequisites:\n"));
17766
+ const responsibleApp = await getResponsibleAppName2();
17767
+ console.log(` ${chalk.dim("\u2022")} Responsible process (the app TCC tracks): ${chalk.cyan(responsibleApp)}`);
18183
17768
  const cliclick = await isCliclickInstalled2();
18184
17769
  if (cliclick) {
18185
17770
  console.log(` ${chalk.green("\u2713")} cliclick installed`);
@@ -18189,25 +17774,28 @@ program.command("check-permissions").description("Check macOS permissions requir
18189
17774
  }
18190
17775
  const acc = cliclick ? await hasAccessibilityPermissions2() : { ok: false, error: "cliclick not installed" };
18191
17776
  if (acc.ok) {
18192
- console.log(` ${chalk.green("\u2713")} Accessibility permissions granted`);
17777
+ console.log(` ${chalk.green("\u2713")} Accessibility permission granted (for ${responsibleApp})`);
18193
17778
  } else {
18194
- console.log(` ${chalk.red("\u2717")} Accessibility permissions missing`);
18195
- console.log(` ${chalk.dim(acc.error?.split("\n")[0] || "")}`);
17779
+ console.log(` ${chalk.red("\u2717")} Accessibility permission missing`);
17780
+ if (acc.error) console.log(` ${chalk.dim(acc.error.split("\n")[0])}`);
18196
17781
  }
18197
17782
  const screen = await hasScreenRecordingPermissions2();
18198
17783
  if (screen) {
18199
- console.log(` ${chalk.green("\u2713")} Screen Recording permissions granted`);
17784
+ console.log(` ${chalk.green("\u2713")} Screen Recording permission granted (for ${responsibleApp})`);
18200
17785
  } else {
18201
- console.log(` ${chalk.red("\u2717")} Screen Recording permissions missing`);
17786
+ console.log(` ${chalk.red("\u2717")} Screen Recording permission missing`);
17787
+ console.log(` ${chalk.dim("macOS 15 (Sequoia) re-prompts weekly even when granted \u2014 dismiss the dialog and the API still returns true.")}`);
18202
17788
  }
18203
17789
  const size = await detectScreenSize2();
18204
17790
  if (size) {
18205
- console.log(` ${chalk.dim("\u2022")} Detected display: ${size.width}x${size.height} points`);
17791
+ console.log(` ${chalk.dim("\u2022")} Main display: ${formatDisplaySize(size)}`);
17792
+ console.log(` ${chalk.dim("cliclick coordinates are POINTS, not pixels. (0,0) = top-left.")}`);
18206
17793
  }
18207
17794
  const allOk = cliclick && acc.ok && screen;
18208
17795
  console.log();
18209
17796
  if (allOk) {
18210
- console.log(chalk.green("All checks passed. The agent can use computer use."));
17797
+ console.log(chalk.green("All checks passed. The agent can drive the desktop via cliclick/screencapture/osascript."));
17798
+ console.log(chalk.dim("See the `desktop-automation` skill for recipes."));
18211
17799
  } else {
18212
17800
  console.log(chalk.yellow("Some prerequisites are missing."));
18213
17801
  console.log(chalk.dim("Run: sparkecoder request-permissions"));
@@ -18215,74 +17803,83 @@ program.command("check-permissions").description("Check macOS permissions requir
18215
17803
  console.log();
18216
17804
  process.exit(allOk ? 0 : 1);
18217
17805
  });
18218
- program.command("request-permissions").description("Request macOS permissions for the computer use tool \u2014 triggers system prompts and opens System Settings").action(async () => {
17806
+ program.command("request-permissions").description("Open System Settings to the right panes so you can grant macOS desktop-automation permissions to the responsible app").action(async () => {
18219
17807
  if (process.platform !== "darwin") {
18220
- console.log(chalk.yellow(`Computer use is only supported on macOS. Current platform: ${process.platform}`));
17808
+ console.log(chalk.yellow(`Desktop automation prerequisites only apply on macOS. Current platform: ${process.platform}`));
18221
17809
  process.exit(0);
18222
17810
  }
18223
17811
  const {
18224
17812
  isCliclickInstalled: isCliclickInstalled2,
18225
17813
  hasAccessibilityPermissions: hasAccessibilityPermissions2,
18226
17814
  hasScreenRecordingPermissions: hasScreenRecordingPermissions2,
18227
- requestAccessibilityPrompt: requestAccessibilityPrompt2,
18228
- requestScreenRecordingPrompt: requestScreenRecordingPrompt2,
18229
17815
  openSystemSettings: openSystemSettings2,
18230
- detectScreenSize: detectScreenSize2
18231
- } = await Promise.resolve().then(() => (init_computer_use(), computer_use_exports));
18232
- console.log(chalk.bold("\nRequesting computer use permissions...\n"));
17816
+ detectScreenSize: detectScreenSize2,
17817
+ getResponsibleAppName: getResponsibleAppName2
17818
+ } = await Promise.resolve().then(() => (init_desktop_permissions(), desktop_permissions_exports));
17819
+ console.log(chalk.bold("\nDesktop-automation setup\n"));
17820
+ const responsibleApp = await getResponsibleAppName2();
17821
+ console.log(` ${chalk.dim("\u2022")} Permissions need to be granted to: ${chalk.cyan(responsibleApp)}`);
17822
+ console.log(` ${chalk.dim("Not to cliclick, screencapture, osascript, or node \u2014 macOS attributes")}`);
17823
+ console.log(` ${chalk.dim("TCC entries to the responsible (parent GUI) process.")}`);
17824
+ console.log();
18233
17825
  if (!await isCliclickInstalled2()) {
18234
17826
  console.log(` ${chalk.red("\u2717")} cliclick is not installed`);
18235
17827
  console.log(` ${chalk.dim("Run: brew install cliclick")}`);
18236
17828
  console.log(` ${chalk.dim("Then re-run: sparkecoder request-permissions")}`);
18237
17829
  console.log();
18238
17830
  process.exit(1);
17831
+ } else {
17832
+ console.log(` ${chalk.green("\u2713")} cliclick installed`);
18239
17833
  }
18240
17834
  const acc = await hasAccessibilityPermissions2();
18241
17835
  const screen = await hasScreenRecordingPermissions2();
18242
- let needsRestart = false;
17836
+ let needsAction = false;
18243
17837
  if (!acc.ok) {
18244
- console.log(` ${chalk.yellow("\u26A0")} Accessibility permission missing \u2014 triggering prompt and opening System Settings...`);
18245
- await requestAccessibilityPrompt2();
17838
+ console.log();
17839
+ console.log(` ${chalk.yellow("\u26A0")} Accessibility permission missing \u2014 opening System Settings...`);
18246
17840
  await openSystemSettings2("accessibility");
18247
- console.log(` ${chalk.dim("1. In Privacy & Security \u2192 Accessibility, click +")}`);
18248
- console.log(` ${chalk.dim("2. Add the application running the agent (Terminal, iTerm, your IDE, or `node`)")}`);
18249
- console.log(` ${chalk.dim("3. Toggle the switch ON")}`);
18250
- needsRestart = true;
17841
+ console.log(` ${chalk.dim("1. In Privacy & Security \u2192 Accessibility, click the ")}+${chalk.dim(" button.")}`);
17842
+ console.log(` ${chalk.dim("2. Add ")}${chalk.cyan(responsibleApp)}${chalk.dim(".")}`);
17843
+ console.log(` ${chalk.dim("3. Make sure its toggle is ON.")}`);
17844
+ needsAction = true;
18251
17845
  } else {
18252
17846
  console.log(` ${chalk.green("\u2713")} Accessibility already granted`);
18253
17847
  }
18254
17848
  if (!screen) {
18255
- console.log(` ${chalk.yellow("\u26A0")} Screen Recording permission missing \u2014 triggering prompt and opening System Settings...`);
18256
- await requestScreenRecordingPrompt2();
17849
+ console.log();
17850
+ console.log(` ${chalk.yellow("\u26A0")} Screen Recording permission missing \u2014 opening System Settings...`);
18257
17851
  await openSystemSettings2("screen-recording");
18258
- console.log(` ${chalk.dim("1. In Privacy & Security \u2192 Screen Recording, click +")}`);
18259
- console.log(` ${chalk.dim("2. Add the application running the agent (Terminal, iTerm, your IDE, or `node`)")}`);
18260
- console.log(` ${chalk.dim("3. Toggle the switch ON")}`);
18261
- needsRestart = true;
17852
+ console.log(` ${chalk.dim("1. In Privacy & Security \u2192 Screen & System Audio Recording, click the ")}+${chalk.dim(" button.")}`);
17853
+ console.log(` ${chalk.dim("2. Add ")}${chalk.cyan(responsibleApp)}${chalk.dim(".")}`);
17854
+ console.log(` ${chalk.dim("3. Make sure its toggle is ON.")}`);
17855
+ console.log(` ${chalk.dim('Note: macOS 15 (Sequoia) will prompt weekly to re-confirm \u2014 just click "Allow".')}`);
17856
+ needsAction = true;
18262
17857
  } else {
18263
17858
  console.log(` ${chalk.green("\u2713")} Screen Recording already granted`);
18264
17859
  }
18265
17860
  const size = await detectScreenSize2();
18266
17861
  if (size) {
18267
- console.log(` ${chalk.dim("\u2022")} Detected display: ${size.width}x${size.height} points`);
17862
+ console.log();
17863
+ console.log(` ${chalk.dim("\u2022")} Main display: ${formatDisplaySize(size)}`);
18268
17864
  }
18269
17865
  console.log();
18270
- if (needsRestart) {
18271
- console.log(chalk.yellow("After granting permissions, RESTART the agent process for the new TCC entries to take effect."));
18272
- console.log(chalk.dim("Then run: sparkecoder check-permissions"));
17866
+ if (needsAction) {
17867
+ console.log(chalk.yellow(`After toggling on ${responsibleApp}, RESTART it (and the agent process inside it) so the new TCC entry takes effect.`));
17868
+ console.log(chalk.dim("Then re-run: sparkecoder check-permissions"));
18273
17869
  } else {
18274
- console.log(chalk.green("All permissions are already granted. Computer use is ready."));
17870
+ console.log(chalk.green("All permissions are already granted. Desktop automation is ready."));
17871
+ console.log(chalk.dim("See the `desktop-automation` skill for recipes."));
18275
17872
  }
18276
17873
  console.log();
18277
17874
  });
18278
17875
  {
18279
17876
  let stateFilePath = function() {
18280
- return join17(ensureAppDataDirectory(), "recordings.json");
17877
+ return join16(ensureAppDataDirectory(), "recordings.json");
18281
17878
  }, readState = function() {
18282
17879
  const p = stateFilePath();
18283
- if (!existsSync22(p)) return [];
17880
+ if (!existsSync21(p)) return [];
18284
17881
  try {
18285
- return JSON.parse(readFileSync11(p, "utf-8"));
17882
+ return JSON.parse(readFileSync10(p, "utf-8"));
18286
17883
  } catch {
18287
17884
  return [];
18288
17885
  }
@@ -18302,16 +17899,16 @@ program.command("request-permissions").description("Request macOS permissions fo
18302
17899
  const record = program.command("record").description("Start/stop screen recordings");
18303
17900
  record.command("start").description("Start a screen recording (returns id, path, pid as JSON)").option("--name <slug>", "Optional human-readable label for the recording").option("--dir <path>", "Output directory (default: ~/recordings)").action(async (opts) => {
18304
17901
  const { homedir: homedir2, platform: osPlatform } = await import("os");
18305
- const outDir = opts.dir ? resolve12(opts.dir.replace(/^~/, homedir2())) : join17(homedir2(), "recordings");
18306
- mkdirSync11(outDir, { recursive: true });
17902
+ const outDir = opts.dir ? resolve12(opts.dir.replace(/^~/, homedir2())) : join16(homedir2(), "recordings");
17903
+ mkdirSync10(outDir, { recursive: true });
18307
17904
  const id = `rec-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
18308
17905
  const ext = osPlatform() === "darwin" ? "mov" : "mp4";
18309
17906
  const filename = `${id}${opts.name ? `-${String(opts.name).replace(/[^a-z0-9-]+/gi, "-").toLowerCase()}` : ""}.${ext}`;
18310
- const path = join17(outDir, filename);
17907
+ const path = join16(outDir, filename);
18311
17908
  let cmd;
18312
17909
  let args;
18313
17910
  if (osPlatform() === "darwin") {
18314
- cmd = existsSync22("/usr/sbin/screencapture") ? "/usr/sbin/screencapture" : "screencapture";
17911
+ cmd = existsSync21("/usr/sbin/screencapture") ? "/usr/sbin/screencapture" : "screencapture";
18315
17912
  args = ["-v", "-C", "-k", path];
18316
17913
  } else if (osPlatform() === "linux") {
18317
17914
  const display = process.env.DISPLAY || ":0.0";
@@ -18414,7 +18011,7 @@ program.command("request-permissions").description("Request macOS permissions fo
18414
18011
  }
18415
18012
  }
18416
18013
  writeState(rows.filter((r) => r.id !== id));
18417
- const fileExists = existsSync22(row.path);
18014
+ const fileExists = existsSync21(row.path);
18418
18015
  const sizeMb = fileExists ? Math.round(statSync3(row.path).size / (1024 * 1024) * 10) / 10 : 0;
18419
18016
  console.log(JSON.stringify({
18420
18017
  id,
@@ -18449,7 +18046,7 @@ program.command("request-permissions").description("Request macOS permissions fo
18449
18046
  }
18450
18047
  }
18451
18048
  }
18452
- stopped.push({ id: r.id, path: r.path, ok: existsSync22(r.path) });
18049
+ stopped.push({ id: r.id, path: r.path, ok: existsSync21(r.path) });
18453
18050
  }
18454
18051
  writeState([]);
18455
18052
  console.log(JSON.stringify({ stopped }));