sparkecoder 0.1.85 → 0.1.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/index.js +666 -40
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +2001 -226
- package/dist/cli.js.map +1 -1
- package/dist/db/index.js.map +1 -1
- package/dist/{index-OhuTM4a0.d.ts → index-BvIissiB.d.ts} +9 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1688 -200
- package/dist/index.js.map +1 -1
- package/dist/server/index.js +1688 -200
- package/dist/server/index.js.map +1 -1
- package/dist/skills/default/computer-use.md +150 -0
- package/dist/tools/index.d.ts +167 -1
- package/dist/tools/index.js +609 -11
- package/dist/tools/index.js.map +1 -1
- package/package.json +2 -1
- package/src/skills/default/computer-use.md +150 -0
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/api/config/route.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/api/health/route.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/embed/[id]/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/embed/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_ecd2bdca._.js → 2374f_317b1fef._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_9adc1edb._.js → 2374f_37dd9702._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_8dc0f9aa._.js → 2374f_4d44e4ed._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_cc6c6363._.js → 2374f_54ac917f._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_00f7fe07._.js → 2374f_86585101._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_369747ce._.js → 2374f_a383a4d9._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d58d0276._.js → 2374f_c59a35bb._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__25b25c9d._.js → [root-of-the-server]__9a826344._.js} +2 -2
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/standalone/web/.next/static/chunks/275e8268daf318b2.js +7 -0
- package/web/.next/standalone/web/.next/static/static/chunks/275e8268daf318b2.js +7 -0
- package/web/.next/standalone/web/package-lock.json +3 -3
- package/web/.next/standalone/web/src/app/embed/[id]/page.tsx +12 -0
- package/web/.next/standalone/web/src/lib/embed-bootstrap.ts +108 -0
- package/web/.next/static/chunks/275e8268daf318b2.js +7 -0
- package/web/.next/standalone/web/.next/static/chunks/5383c5717758f575.js +0 -7
- package/web/.next/standalone/web/.next/static/static/chunks/5383c5717758f575.js +0 -7
- package/web/.next/static/chunks/5383c5717758f575.js +0 -7
- /package/web/.next/standalone/web/.next/static/{J0gen1p9aNjUNIU1NDO5h → static/uUaN7Xe5kF_pP6zhfaeYi}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{J0gen1p9aNjUNIU1NDO5h → static/uUaN7Xe5kF_pP6zhfaeYi}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{J0gen1p9aNjUNIU1NDO5h → static/uUaN7Xe5kF_pP6zhfaeYi}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{static/J0gen1p9aNjUNIU1NDO5h → uUaN7Xe5kF_pP6zhfaeYi}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{static/J0gen1p9aNjUNIU1NDO5h → uUaN7Xe5kF_pP6zhfaeYi}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{static/J0gen1p9aNjUNIU1NDO5h → uUaN7Xe5kF_pP6zhfaeYi}/_ssgManifest.js +0 -0
- /package/web/.next/static/{J0gen1p9aNjUNIU1NDO5h → uUaN7Xe5kF_pP6zhfaeYi}/_buildManifest.js +0 -0
- /package/web/.next/static/{J0gen1p9aNjUNIU1NDO5h → uUaN7Xe5kF_pP6zhfaeYi}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{J0gen1p9aNjUNIU1NDO5h → uUaN7Xe5kF_pP6zhfaeYi}/_ssgManifest.js +0 -0
package/dist/agent/index.js
CHANGED
|
@@ -27,7 +27,12 @@ var init_types = __esm({
|
|
|
27
27
|
// Whether to always inject this skill into context (vs on-demand loading)
|
|
28
28
|
alwaysApply: z.boolean().optional().default(false),
|
|
29
29
|
// Glob patterns - auto-inject when working with matching files
|
|
30
|
-
globs: z.array(z.string()).optional().default([])
|
|
30
|
+
globs: z.array(z.string()).optional().default([]),
|
|
31
|
+
// Platform requirements — skill is hidden from the model on platforms
|
|
32
|
+
// not listed here. Values match `process.platform`
|
|
33
|
+
// (darwin, linux, win32, freebsd, ...). If omitted or empty, the skill is
|
|
34
|
+
// available on all platforms.
|
|
35
|
+
platforms: z.array(z.string()).optional().default([])
|
|
31
36
|
});
|
|
32
37
|
TaskConfigSchema = z.object({
|
|
33
38
|
enabled: z.boolean(),
|
|
@@ -45,7 +50,13 @@ var init_types = __esm({
|
|
|
45
50
|
approvalWebhook: z.string().url().optional(),
|
|
46
51
|
skillsDirectory: z.string().optional(),
|
|
47
52
|
maxContextChars: z.number().optional().default(2e5),
|
|
48
|
-
task: TaskConfigSchema.optional()
|
|
53
|
+
task: TaskConfigSchema.optional(),
|
|
54
|
+
// Anthropic computer use tool — opt-in. When true, the `computer` tool is
|
|
55
|
+
// included in the toolset for Anthropic models. Default false.
|
|
56
|
+
computerUseEnabled: z.boolean().optional(),
|
|
57
|
+
// Display dimensions for the computer use tool (defaults: 1280x800).
|
|
58
|
+
computerUseDisplayWidth: z.number().int().positive().optional(),
|
|
59
|
+
computerUseDisplayHeight: z.number().int().positive().optional()
|
|
49
60
|
});
|
|
50
61
|
VectorGatewayConfigSchema = z.object({
|
|
51
62
|
// Redis cluster nodes URL for Vector Gateway (or use REDIS_CLUSTER_NODES env var)
|
|
@@ -633,7 +644,11 @@ var init_remote = __esm({
|
|
|
633
644
|
return result.files;
|
|
634
645
|
},
|
|
635
646
|
async getDownloadUrl(fileId) {
|
|
636
|
-
|
|
647
|
+
const result = await storageApi(`/download/${fileId}`);
|
|
648
|
+
return {
|
|
649
|
+
downloadUrl: result.shortUrl || result.downloadUrl,
|
|
650
|
+
expiresAt: result.expiresAt
|
|
651
|
+
};
|
|
637
652
|
},
|
|
638
653
|
async deleteFile(fileId) {
|
|
639
654
|
await storageApi(`/files/${fileId}`, { method: "DELETE" });
|
|
@@ -798,7 +813,8 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
798
813
|
globs: parsed.metadata.globs,
|
|
799
814
|
loadType,
|
|
800
815
|
priority,
|
|
801
|
-
sourceDir: directory
|
|
816
|
+
sourceDir: directory,
|
|
817
|
+
platforms: parsed.metadata.platforms
|
|
802
818
|
});
|
|
803
819
|
} else {
|
|
804
820
|
const name = getSkillNameFromPath(filePath);
|
|
@@ -811,11 +827,14 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
811
827
|
globs: [],
|
|
812
828
|
loadType: forceAlwaysApply ? "always" : defaultLoadType,
|
|
813
829
|
priority,
|
|
814
|
-
sourceDir: directory
|
|
830
|
+
sourceDir: directory,
|
|
831
|
+
platforms: []
|
|
815
832
|
});
|
|
816
833
|
}
|
|
817
834
|
}
|
|
818
|
-
return skills
|
|
835
|
+
return skills.filter(
|
|
836
|
+
(s) => s.platforms.length === 0 || s.platforms.includes(process.platform)
|
|
837
|
+
);
|
|
819
838
|
}
|
|
820
839
|
async function loadAllSkills(directories) {
|
|
821
840
|
const allSkills = [];
|
|
@@ -1612,15 +1631,15 @@ var recorder_exports = {};
|
|
|
1612
1631
|
__export(recorder_exports, {
|
|
1613
1632
|
FrameRecorder: () => FrameRecorder
|
|
1614
1633
|
});
|
|
1615
|
-
import { exec as
|
|
1616
|
-
import { promisify as
|
|
1634
|
+
import { exec as exec6 } from "child_process";
|
|
1635
|
+
import { promisify as promisify6 } from "util";
|
|
1617
1636
|
import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
|
|
1618
|
-
import { join as
|
|
1619
|
-
import { tmpdir } from "os";
|
|
1620
|
-
import { nanoid as
|
|
1637
|
+
import { join as join9 } from "path";
|
|
1638
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
1639
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
1621
1640
|
async function checkFfmpeg() {
|
|
1622
1641
|
try {
|
|
1623
|
-
await
|
|
1642
|
+
await execAsync6("ffmpeg -version", { timeout: 5e3 });
|
|
1624
1643
|
return true;
|
|
1625
1644
|
} catch {
|
|
1626
1645
|
return false;
|
|
@@ -1632,11 +1651,11 @@ async function cleanup(dir) {
|
|
|
1632
1651
|
} catch {
|
|
1633
1652
|
}
|
|
1634
1653
|
}
|
|
1635
|
-
var
|
|
1654
|
+
var execAsync6, FrameRecorder;
|
|
1636
1655
|
var init_recorder = __esm({
|
|
1637
1656
|
"src/browser/recorder.ts"() {
|
|
1638
1657
|
"use strict";
|
|
1639
|
-
|
|
1658
|
+
execAsync6 = promisify6(exec6);
|
|
1640
1659
|
FrameRecorder = class {
|
|
1641
1660
|
frames = [];
|
|
1642
1661
|
startTime = null;
|
|
@@ -1672,21 +1691,21 @@ var init_recorder = __esm({
|
|
|
1672
1691
|
*/
|
|
1673
1692
|
async encode() {
|
|
1674
1693
|
if (this.frames.length === 0) return null;
|
|
1675
|
-
const workDir =
|
|
1694
|
+
const workDir = join9(tmpdir2(), `sparkecoder-recording-${nanoid4(8)}`);
|
|
1676
1695
|
await mkdir4(workDir, { recursive: true });
|
|
1677
1696
|
try {
|
|
1678
1697
|
for (let i = 0; i < this.frames.length; i++) {
|
|
1679
|
-
const framePath =
|
|
1698
|
+
const framePath = join9(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
1680
1699
|
await writeFile5(framePath, this.frames[i].data);
|
|
1681
1700
|
}
|
|
1682
1701
|
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
1683
1702
|
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
1684
1703
|
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
1685
|
-
const outputPath =
|
|
1704
|
+
const outputPath = join9(workDir, `recording_${this.sessionId}.mp4`);
|
|
1686
1705
|
const hasFfmpeg = await checkFfmpeg();
|
|
1687
1706
|
if (hasFfmpeg) {
|
|
1688
|
-
await
|
|
1689
|
-
`ffmpeg -y -framerate ${clampedFps} -i "${
|
|
1707
|
+
await execAsync6(
|
|
1708
|
+
`ffmpeg -y -framerate ${clampedFps} -i "${join9(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
|
|
1690
1709
|
{ timeout: 12e4 }
|
|
1691
1710
|
);
|
|
1692
1711
|
} else {
|
|
@@ -1698,7 +1717,7 @@ var init_recorder = __esm({
|
|
|
1698
1717
|
const files = await readdir5(workDir);
|
|
1699
1718
|
for (const f of files) {
|
|
1700
1719
|
if (f.startsWith("frame_")) {
|
|
1701
|
-
await unlink2(
|
|
1720
|
+
await unlink2(join9(workDir, f)).catch(() => {
|
|
1702
1721
|
});
|
|
1703
1722
|
}
|
|
1704
1723
|
}
|
|
@@ -1723,7 +1742,7 @@ var init_recorder = __esm({
|
|
|
1723
1742
|
import {
|
|
1724
1743
|
streamText as streamText2,
|
|
1725
1744
|
generateText as generateText3,
|
|
1726
|
-
tool as
|
|
1745
|
+
tool as tool14,
|
|
1727
1746
|
stepCountIs as stepCountIs2
|
|
1728
1747
|
} from "ai";
|
|
1729
1748
|
|
|
@@ -1914,8 +1933,8 @@ var SUBAGENT_MODELS = {
|
|
|
1914
1933
|
// src/agent/index.ts
|
|
1915
1934
|
init_db();
|
|
1916
1935
|
init_config();
|
|
1917
|
-
import { z as
|
|
1918
|
-
import { nanoid as
|
|
1936
|
+
import { z as z15 } from "zod";
|
|
1937
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
1919
1938
|
|
|
1920
1939
|
// src/tools/bash.ts
|
|
1921
1940
|
import { tool } from "ai";
|
|
@@ -2798,12 +2817,12 @@ function findNearestRoot(startDir, markers) {
|
|
|
2798
2817
|
}
|
|
2799
2818
|
async function commandExists(cmd) {
|
|
2800
2819
|
try {
|
|
2801
|
-
const { exec:
|
|
2802
|
-
const { promisify:
|
|
2803
|
-
const
|
|
2820
|
+
const { exec: exec7 } = await import("child_process");
|
|
2821
|
+
const { promisify: promisify7 } = await import("util");
|
|
2822
|
+
const execAsync7 = promisify7(exec7);
|
|
2804
2823
|
const isWindows = process.platform === "win32";
|
|
2805
2824
|
const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
|
|
2806
|
-
await
|
|
2825
|
+
await execAsync7(checkCmd);
|
|
2807
2826
|
return true;
|
|
2808
2827
|
} catch {
|
|
2809
2828
|
return false;
|
|
@@ -5562,6 +5581,568 @@ function createUploadFileTool(options) {
|
|
|
5562
5581
|
});
|
|
5563
5582
|
}
|
|
5564
5583
|
|
|
5584
|
+
// src/tools/computer-use.ts
|
|
5585
|
+
import { anthropic } from "@ai-sdk/anthropic";
|
|
5586
|
+
import { exec as exec5 } from "child_process";
|
|
5587
|
+
import { promisify as promisify5 } from "util";
|
|
5588
|
+
import { mkdirSync as mkdirSync5, existsSync as existsSync15, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
|
|
5589
|
+
import { join as join8 } from "path";
|
|
5590
|
+
import { tmpdir } from "os";
|
|
5591
|
+
import { nanoid as nanoid3 } from "nanoid";
|
|
5592
|
+
var execAsync5 = promisify5(exec5);
|
|
5593
|
+
var DEFAULT_WIDTH = 1280;
|
|
5594
|
+
var DEFAULT_HEIGHT = 800;
|
|
5595
|
+
function isMacOs() {
|
|
5596
|
+
return process.platform === "darwin";
|
|
5597
|
+
}
|
|
5598
|
+
async function isCliclickInstalled() {
|
|
5599
|
+
try {
|
|
5600
|
+
await execAsync5("command -v cliclick", { timeout: 2e3 });
|
|
5601
|
+
return true;
|
|
5602
|
+
} catch {
|
|
5603
|
+
return false;
|
|
5604
|
+
}
|
|
5605
|
+
}
|
|
5606
|
+
async function runJxa(script) {
|
|
5607
|
+
try {
|
|
5608
|
+
const escaped = script.replace(/'/g, `'\\''`);
|
|
5609
|
+
const { stdout } = await execAsync5(`osascript -l JavaScript -e '${escaped}'`, {
|
|
5610
|
+
timeout: 5e3
|
|
5611
|
+
});
|
|
5612
|
+
return JSON.parse(stdout.trim());
|
|
5613
|
+
} catch {
|
|
5614
|
+
return null;
|
|
5615
|
+
}
|
|
5616
|
+
}
|
|
5617
|
+
async function hasAccessibilityPermissions() {
|
|
5618
|
+
try {
|
|
5619
|
+
const { stderr } = await execAsync5("cliclick p:.", { timeout: 3e3 });
|
|
5620
|
+
if (/accessibility privileges not enabled/i.test(stderr)) {
|
|
5621
|
+
return { ok: false, error: stderr.trim().split("\n")[0] };
|
|
5622
|
+
}
|
|
5623
|
+
return { ok: true };
|
|
5624
|
+
} catch (err) {
|
|
5625
|
+
return { ok: false, error: err?.message || String(err) };
|
|
5626
|
+
}
|
|
5627
|
+
}
|
|
5628
|
+
async function hasScreenRecordingPermissions() {
|
|
5629
|
+
const result = await runJxa(
|
|
5630
|
+
`ObjC.import("Cocoa");
|
|
5631
|
+
ObjC.import("CoreGraphics");
|
|
5632
|
+
ObjC.bindFunction("CGPreflightScreenCaptureAccess", ["bool", []]);
|
|
5633
|
+
JSON.stringify({ hasAccess: !!$.CGPreflightScreenCaptureAccess() });`
|
|
5634
|
+
);
|
|
5635
|
+
return result?.hasAccess ?? false;
|
|
5636
|
+
}
|
|
5637
|
+
async function requestAccessibilityPrompt() {
|
|
5638
|
+
const result = await runJxa(
|
|
5639
|
+
`ObjC.import("ApplicationServices");
|
|
5640
|
+
var key = $.kAXTrustedCheckOptionPrompt;
|
|
5641
|
+
var dict = $.NSDictionary.dictionaryWithObjectForKey($.kCFBooleanTrue, key);
|
|
5642
|
+
var trusted = $.AXIsProcessTrustedWithOptions(dict);
|
|
5643
|
+
JSON.stringify({ trusted: !!trusted });`
|
|
5644
|
+
);
|
|
5645
|
+
return result?.trusted ?? false;
|
|
5646
|
+
}
|
|
5647
|
+
async function requestScreenRecordingPrompt() {
|
|
5648
|
+
const result = await runJxa(
|
|
5649
|
+
`ObjC.import("Cocoa");
|
|
5650
|
+
ObjC.import("CoreGraphics");
|
|
5651
|
+
ObjC.bindFunction("CGRequestScreenCaptureAccess", ["bool", []]);
|
|
5652
|
+
JSON.stringify({ granted: !!$.CGRequestScreenCaptureAccess() });`
|
|
5653
|
+
);
|
|
5654
|
+
return result?.granted ?? false;
|
|
5655
|
+
}
|
|
5656
|
+
async function openSystemSettings(pane) {
|
|
5657
|
+
const url = pane === "accessibility" ? "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility" : "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
|
|
5658
|
+
try {
|
|
5659
|
+
await execAsync5(`open '${url}'`, { timeout: 3e3 });
|
|
5660
|
+
} catch {
|
|
5661
|
+
}
|
|
5662
|
+
}
|
|
5663
|
+
async function detectScreenSize() {
|
|
5664
|
+
try {
|
|
5665
|
+
const { stdout } = await execAsync5(
|
|
5666
|
+
`osascript -e 'tell application "Finder" to get bounds of window of desktop'`,
|
|
5667
|
+
{ timeout: 3e3 }
|
|
5668
|
+
);
|
|
5669
|
+
const parts = stdout.trim().split(",").map((s) => parseInt(s.trim(), 10));
|
|
5670
|
+
if (parts.length >= 4 && parts.every((n) => Number.isFinite(n))) {
|
|
5671
|
+
const [x1, y1, x2, y2] = parts;
|
|
5672
|
+
return { width: x2 - x1, height: y2 - y1 };
|
|
5673
|
+
}
|
|
5674
|
+
} catch {
|
|
5675
|
+
}
|
|
5676
|
+
return null;
|
|
5677
|
+
}
|
|
5678
|
+
async function runCliclick(args) {
|
|
5679
|
+
const quoted = args.map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(" ");
|
|
5680
|
+
const { stdout, stderr } = await execAsync5(`cliclick ${quoted}`, {
|
|
5681
|
+
timeout: 15e3,
|
|
5682
|
+
maxBuffer: 1024 * 1024
|
|
5683
|
+
});
|
|
5684
|
+
if (/accessibility privileges not enabled/i.test(stderr)) {
|
|
5685
|
+
throw new Error(
|
|
5686
|
+
"Accessibility permissions not granted to cliclick. Open System Settings \u2192 Privacy & Security \u2192 Accessibility, add cliclick (or the agent runtime), and toggle it on."
|
|
5687
|
+
);
|
|
5688
|
+
}
|
|
5689
|
+
if (stderr && !stdout) throw new Error(stderr.trim());
|
|
5690
|
+
return (stdout || "").trim();
|
|
5691
|
+
}
|
|
5692
|
+
async function runScreencapture(path) {
|
|
5693
|
+
await execAsync5(`screencapture -x -t png '${path.replace(/'/g, `'\\''`)}'`, {
|
|
5694
|
+
timeout: 5e3
|
|
5695
|
+
});
|
|
5696
|
+
}
|
|
5697
|
+
async function resizeScreenshotToPoints(path, targetWidth, targetHeight) {
|
|
5698
|
+
const sharpModule = await import("sharp");
|
|
5699
|
+
const sharp2 = sharpModule.default || sharpModule;
|
|
5700
|
+
const meta = await sharp2(path).metadata();
|
|
5701
|
+
if (meta.width === targetWidth && meta.height === targetHeight) {
|
|
5702
|
+
return readFileSync7(path);
|
|
5703
|
+
}
|
|
5704
|
+
return await sharp2(path).resize(targetWidth, targetHeight, { fit: "fill" }).png().toBuffer();
|
|
5705
|
+
}
|
|
5706
|
+
async function runScroll(dx, dy) {
|
|
5707
|
+
const wheelY = -Math.round(dy);
|
|
5708
|
+
const wheelX = -Math.round(dx);
|
|
5709
|
+
const script = `ObjC.import('CoreGraphics');var ev = $.CGEventCreateScrollWheelEvent(null, 0, 2, ${wheelY}, ${wheelX});$.CGEventPost(0, ev);`;
|
|
5710
|
+
await execAsync5(
|
|
5711
|
+
`osascript -l JavaScript -e '${script.replace(/'/g, `'\\''`)}'`,
|
|
5712
|
+
{ timeout: 5e3 }
|
|
5713
|
+
);
|
|
5714
|
+
}
|
|
5715
|
+
function translateKeyForCliclick(key) {
|
|
5716
|
+
if (!key) return [];
|
|
5717
|
+
const parts = key.split("+").map((p) => p.trim()).filter(Boolean);
|
|
5718
|
+
if (parts.length === 0) return [];
|
|
5719
|
+
const modMap = {
|
|
5720
|
+
ctrl: "ctrl",
|
|
5721
|
+
control: "ctrl",
|
|
5722
|
+
alt: "alt",
|
|
5723
|
+
option: "alt",
|
|
5724
|
+
shift: "shift",
|
|
5725
|
+
cmd: "cmd",
|
|
5726
|
+
super: "cmd",
|
|
5727
|
+
meta: "cmd",
|
|
5728
|
+
win: "cmd",
|
|
5729
|
+
fn: "fn"
|
|
5730
|
+
};
|
|
5731
|
+
const keyMap = {
|
|
5732
|
+
return: "enter",
|
|
5733
|
+
enter: "enter",
|
|
5734
|
+
esc: "esc",
|
|
5735
|
+
escape: "esc",
|
|
5736
|
+
backspace: "delete",
|
|
5737
|
+
back_space: "delete",
|
|
5738
|
+
delete: "fwd-delete",
|
|
5739
|
+
fwd_delete: "fwd-delete",
|
|
5740
|
+
forward_delete: "fwd-delete",
|
|
5741
|
+
tab: "tab",
|
|
5742
|
+
space: "space",
|
|
5743
|
+
up: "arrow-up",
|
|
5744
|
+
arrow_up: "arrow-up",
|
|
5745
|
+
down: "arrow-down",
|
|
5746
|
+
arrow_down: "arrow-down",
|
|
5747
|
+
left: "arrow-left",
|
|
5748
|
+
arrow_left: "arrow-left",
|
|
5749
|
+
right: "arrow-right",
|
|
5750
|
+
arrow_right: "arrow-right",
|
|
5751
|
+
page_up: "page-up",
|
|
5752
|
+
pageup: "page-up",
|
|
5753
|
+
page_down: "page-down",
|
|
5754
|
+
pagedown: "page-down",
|
|
5755
|
+
home: "home",
|
|
5756
|
+
end: "end",
|
|
5757
|
+
f1: "f1",
|
|
5758
|
+
f2: "f2",
|
|
5759
|
+
f3: "f3",
|
|
5760
|
+
f4: "f4",
|
|
5761
|
+
f5: "f5",
|
|
5762
|
+
f6: "f6",
|
|
5763
|
+
f7: "f7",
|
|
5764
|
+
f8: "f8",
|
|
5765
|
+
f9: "f9",
|
|
5766
|
+
f10: "f10",
|
|
5767
|
+
f11: "f11",
|
|
5768
|
+
f12: "f12"
|
|
5769
|
+
};
|
|
5770
|
+
const modifiers = [];
|
|
5771
|
+
let mainKey = null;
|
|
5772
|
+
for (let i = 0; i < parts.length; i++) {
|
|
5773
|
+
const lower = parts[i].toLowerCase().replace(/-/g, "_");
|
|
5774
|
+
if (i < parts.length - 1 && modMap[lower]) {
|
|
5775
|
+
modifiers.push(modMap[lower]);
|
|
5776
|
+
} else {
|
|
5777
|
+
mainKey = keyMap[lower] || lower;
|
|
5778
|
+
}
|
|
5779
|
+
}
|
|
5780
|
+
const args = [];
|
|
5781
|
+
if (modifiers.length > 0) args.push(`kd:${modifiers.join(",")}`);
|
|
5782
|
+
if (mainKey) {
|
|
5783
|
+
const isNamedKey = Object.values(keyMap).includes(mainKey) || /^f([1-9]|1[0-9]|20)$/.test(mainKey) || /^num-/.test(mainKey);
|
|
5784
|
+
if (isNamedKey) {
|
|
5785
|
+
args.push(`kp:${mainKey}`);
|
|
5786
|
+
} else {
|
|
5787
|
+
args.push(`t:${mainKey}`);
|
|
5788
|
+
}
|
|
5789
|
+
}
|
|
5790
|
+
if (modifiers.length > 0) args.push(`ku:${modifiers.join(",")}`);
|
|
5791
|
+
return args;
|
|
5792
|
+
}
|
|
5793
|
+
function modifierStringToCliclick(text) {
|
|
5794
|
+
return text.split("+").map((p) => p.trim().toLowerCase()).map((p) => {
|
|
5795
|
+
if (p === "ctrl" || p === "control") return "ctrl";
|
|
5796
|
+
if (p === "alt" || p === "option") return "alt";
|
|
5797
|
+
if (p === "shift") return "shift";
|
|
5798
|
+
if (p === "super" || p === "meta" || p === "cmd") return "cmd";
|
|
5799
|
+
return "";
|
|
5800
|
+
}).filter(Boolean);
|
|
5801
|
+
}
|
|
5802
|
+
function createComputerUseTool(options) {
|
|
5803
|
+
const displayWidth = options.displayWidth ?? DEFAULT_WIDTH;
|
|
5804
|
+
const displayHeight = options.displayHeight ?? DEFAULT_HEIGHT;
|
|
5805
|
+
return anthropic.tools.computer_20251124({
|
|
5806
|
+
displayWidthPx: displayWidth,
|
|
5807
|
+
displayHeightPx: displayHeight,
|
|
5808
|
+
enableZoom: true,
|
|
5809
|
+
execute: async (input) => {
|
|
5810
|
+
try {
|
|
5811
|
+
switch (input.action) {
|
|
5812
|
+
case "screenshot": {
|
|
5813
|
+
const path = join8(tmpdir(), `cu-${nanoid3(8)}.png`);
|
|
5814
|
+
await runScreencapture(path);
|
|
5815
|
+
const resized = await resizeScreenshotToPoints(path, displayWidth, displayHeight);
|
|
5816
|
+
try {
|
|
5817
|
+
unlinkSync2(path);
|
|
5818
|
+
} catch {
|
|
5819
|
+
}
|
|
5820
|
+
return { type: "image", data: resized.toString("base64") };
|
|
5821
|
+
}
|
|
5822
|
+
case "left_click": {
|
|
5823
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5824
|
+
if (input.text) {
|
|
5825
|
+
const mods = modifierStringToCliclick(input.text);
|
|
5826
|
+
if (mods.length > 0) {
|
|
5827
|
+
await runCliclick([`kd:${mods.join(",")}`, `c:${x},${y}`, `ku:${mods.join(",")}`]);
|
|
5828
|
+
} else {
|
|
5829
|
+
await runCliclick([`c:${x},${y}`]);
|
|
5830
|
+
}
|
|
5831
|
+
} else {
|
|
5832
|
+
await runCliclick([`c:${x},${y}`]);
|
|
5833
|
+
}
|
|
5834
|
+
return `clicked at (${x}, ${y})${input.text ? ` with ${input.text}` : ""}`;
|
|
5835
|
+
}
|
|
5836
|
+
case "right_click": {
|
|
5837
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5838
|
+
await runCliclick([`rc:${x},${y}`]);
|
|
5839
|
+
return `right-clicked at (${x}, ${y})`;
|
|
5840
|
+
}
|
|
5841
|
+
case "middle_click": {
|
|
5842
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5843
|
+
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);`;
|
|
5844
|
+
await execAsync5(
|
|
5845
|
+
`osascript -l JavaScript -e '${script.replace(/'/g, `'\\''`)}'`,
|
|
5846
|
+
{ timeout: 3e3 }
|
|
5847
|
+
);
|
|
5848
|
+
return `middle-clicked at (${x}, ${y})`;
|
|
5849
|
+
}
|
|
5850
|
+
case "double_click": {
|
|
5851
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5852
|
+
await runCliclick([`dc:${x},${y}`]);
|
|
5853
|
+
return `double-clicked at (${x}, ${y})`;
|
|
5854
|
+
}
|
|
5855
|
+
case "triple_click": {
|
|
5856
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5857
|
+
await runCliclick([`tc:${x},${y}`]);
|
|
5858
|
+
return `triple-clicked at (${x}, ${y})`;
|
|
5859
|
+
}
|
|
5860
|
+
case "mouse_move": {
|
|
5861
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5862
|
+
await runCliclick([`m:${x},${y}`]);
|
|
5863
|
+
return `moved cursor to (${x}, ${y})`;
|
|
5864
|
+
}
|
|
5865
|
+
case "left_mouse_down": {
|
|
5866
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5867
|
+
await runCliclick([`dd:${x},${y}`]);
|
|
5868
|
+
return `left mouse button pressed at (${x}, ${y})`;
|
|
5869
|
+
}
|
|
5870
|
+
case "left_mouse_up": {
|
|
5871
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5872
|
+
await runCliclick([`du:${x},${y}`]);
|
|
5873
|
+
return `left mouse button released at (${x}, ${y})`;
|
|
5874
|
+
}
|
|
5875
|
+
case "left_click_drag": {
|
|
5876
|
+
const [sx, sy] = input.start_coordinate ?? [0, 0];
|
|
5877
|
+
const [ex, ey] = input.coordinate ?? [0, 0];
|
|
5878
|
+
await runCliclick([`dd:${sx},${sy}`, `m:${ex},${ey}`, `du:${ex},${ey}`]);
|
|
5879
|
+
return `dragged from (${sx}, ${sy}) to (${ex}, ${ey})`;
|
|
5880
|
+
}
|
|
5881
|
+
case "type": {
|
|
5882
|
+
const text = input.text ?? "";
|
|
5883
|
+
await runCliclick([`t:${text}`]);
|
|
5884
|
+
return `typed ${text.length} character(s)`;
|
|
5885
|
+
}
|
|
5886
|
+
case "key": {
|
|
5887
|
+
const args = translateKeyForCliclick(input.text ?? "");
|
|
5888
|
+
if (args.length === 0) return "no key specified";
|
|
5889
|
+
await runCliclick(args);
|
|
5890
|
+
return `pressed ${input.text}`;
|
|
5891
|
+
}
|
|
5892
|
+
case "hold_key": {
|
|
5893
|
+
const text = (input.text ?? "").toLowerCase();
|
|
5894
|
+
const duration = input.duration ?? 1;
|
|
5895
|
+
const modMap = {
|
|
5896
|
+
ctrl: "ctrl",
|
|
5897
|
+
control: "ctrl",
|
|
5898
|
+
alt: "alt",
|
|
5899
|
+
option: "alt",
|
|
5900
|
+
shift: "shift",
|
|
5901
|
+
cmd: "cmd",
|
|
5902
|
+
super: "cmd",
|
|
5903
|
+
meta: "cmd",
|
|
5904
|
+
fn: "fn"
|
|
5905
|
+
};
|
|
5906
|
+
const cliName = modMap[text] || text;
|
|
5907
|
+
await runCliclick([`kd:${cliName}`]);
|
|
5908
|
+
await new Promise((r) => setTimeout(r, duration * 1e3));
|
|
5909
|
+
await runCliclick([`ku:${cliName}`]);
|
|
5910
|
+
return `held ${text} for ${duration}s`;
|
|
5911
|
+
}
|
|
5912
|
+
case "scroll": {
|
|
5913
|
+
const direction = input.scroll_direction ?? "down";
|
|
5914
|
+
const amount = input.scroll_amount ?? 3;
|
|
5915
|
+
const px = amount * 100;
|
|
5916
|
+
const dx = direction === "left" ? -px : direction === "right" ? px : 0;
|
|
5917
|
+
const dy = direction === "up" ? -px : direction === "down" ? px : 0;
|
|
5918
|
+
if (input.coordinate) {
|
|
5919
|
+
const [x, y] = input.coordinate;
|
|
5920
|
+
await runCliclick([`m:${x},${y}`]);
|
|
5921
|
+
}
|
|
5922
|
+
const mods = input.text ? modifierStringToCliclick(input.text) : [];
|
|
5923
|
+
if (mods.length > 0) {
|
|
5924
|
+
await runCliclick([`kd:${mods.join(",")}`]);
|
|
5925
|
+
}
|
|
5926
|
+
await runScroll(dx, dy);
|
|
5927
|
+
if (mods.length > 0) {
|
|
5928
|
+
await runCliclick([`ku:${mods.join(",")}`]);
|
|
5929
|
+
}
|
|
5930
|
+
return `scrolled ${direction} by ${amount}`;
|
|
5931
|
+
}
|
|
5932
|
+
case "wait": {
|
|
5933
|
+
const duration = input.duration ?? 1;
|
|
5934
|
+
await new Promise((r) => setTimeout(r, duration * 1e3));
|
|
5935
|
+
return `waited ${duration}s`;
|
|
5936
|
+
}
|
|
5937
|
+
case "cursor_position": {
|
|
5938
|
+
const out = await runCliclick(["p:."]);
|
|
5939
|
+
return `cursor at ${out}`;
|
|
5940
|
+
}
|
|
5941
|
+
case "zoom": {
|
|
5942
|
+
const region = input.region ?? [0, 0, displayWidth, displayHeight];
|
|
5943
|
+
const [x1, y1, x2, y2] = region;
|
|
5944
|
+
const tmpPath = join8(tmpdir(), `cu-zoom-${nanoid3(8)}.png`);
|
|
5945
|
+
await runScreencapture(tmpPath);
|
|
5946
|
+
const sharpModule = await import("sharp");
|
|
5947
|
+
const sharp2 = sharpModule.default || sharpModule;
|
|
5948
|
+
const meta = await sharp2(tmpPath).metadata();
|
|
5949
|
+
const scaleX = (meta.width || displayWidth) / displayWidth;
|
|
5950
|
+
const scaleY = (meta.height || displayHeight) / displayHeight;
|
|
5951
|
+
const px = {
|
|
5952
|
+
left: Math.max(0, Math.round(x1 * scaleX)),
|
|
5953
|
+
top: Math.max(0, Math.round(y1 * scaleY)),
|
|
5954
|
+
width: Math.max(1, Math.round((x2 - x1) * scaleX)),
|
|
5955
|
+
height: Math.max(1, Math.round((y2 - y1) * scaleY))
|
|
5956
|
+
};
|
|
5957
|
+
const buf = await sharp2(tmpPath).extract(px).png().toBuffer();
|
|
5958
|
+
try {
|
|
5959
|
+
unlinkSync2(tmpPath);
|
|
5960
|
+
} catch {
|
|
5961
|
+
}
|
|
5962
|
+
return { type: "image", data: buf.toString("base64") };
|
|
5963
|
+
}
|
|
5964
|
+
default: {
|
|
5965
|
+
const exhaustive = input.action;
|
|
5966
|
+
return `unsupported action: ${String(exhaustive)}`;
|
|
5967
|
+
}
|
|
5968
|
+
}
|
|
5969
|
+
} catch (err) {
|
|
5970
|
+
const msg = err?.message || String(err);
|
|
5971
|
+
let hint = "";
|
|
5972
|
+
if (/accessibility|not authorized|tcc|operation not permitted/i.test(msg)) {
|
|
5973
|
+
hint = " (Hint: call enable_computer_use to (re-)check permissions and open System Settings)";
|
|
5974
|
+
} else if (/command not found/i.test(msg)) {
|
|
5975
|
+
hint = " (Hint: install cliclick with `brew install cliclick`)";
|
|
5976
|
+
}
|
|
5977
|
+
return `Error: ${msg}${hint}`;
|
|
5978
|
+
}
|
|
5979
|
+
},
|
|
5980
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5981
|
+
toModelOutput({ output }) {
|
|
5982
|
+
if (typeof output === "string") {
|
|
5983
|
+
return { type: "content", value: [{ type: "text", text: output }] };
|
|
5984
|
+
}
|
|
5985
|
+
return {
|
|
5986
|
+
type: "content",
|
|
5987
|
+
value: [{ type: "media", data: output.data, mediaType: "image/png" }]
|
|
5988
|
+
};
|
|
5989
|
+
}
|
|
5990
|
+
});
|
|
5991
|
+
}
|
|
5992
|
+
|
|
5993
|
+
// src/tools/enable-computer-use.ts
|
|
5994
|
+
init_db();
|
|
5995
|
+
import { tool as tool13 } from "ai";
|
|
5996
|
+
import { z as z14 } from "zod";
|
|
5997
|
+
var inputSchema = z14.object({
|
|
5998
|
+
display_width: z14.number().int().positive().optional().describe("Display width in pixels (defaults to detected primary display, fallback 1280)"),
|
|
5999
|
+
display_height: z14.number().int().positive().optional().describe("Display height in pixels (defaults to detected primary display, fallback 800)"),
|
|
6000
|
+
request_permissions: z14.boolean().optional().default(true).describe(
|
|
6001
|
+
"When true (default), proactively trigger macOS permission prompts and open System Settings panes for any missing permissions."
|
|
6002
|
+
)
|
|
6003
|
+
});
|
|
6004
|
+
var ACCESSIBILITY_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
|
|
6005
|
+
var SCREEN_RECORDING_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
|
|
6006
|
+
function createEnableComputerUseTool(options) {
|
|
6007
|
+
return tool13({
|
|
6008
|
+
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.",
|
|
6009
|
+
inputSchema,
|
|
6010
|
+
execute: async ({ display_width, display_height, request_permissions }) => {
|
|
6011
|
+
try {
|
|
6012
|
+
if (!isMacOs()) {
|
|
6013
|
+
return {
|
|
6014
|
+
success: false,
|
|
6015
|
+
error: "Computer use is currently only supported on macOS.",
|
|
6016
|
+
platform: process.platform
|
|
6017
|
+
};
|
|
6018
|
+
}
|
|
6019
|
+
if (!await isCliclickInstalled()) {
|
|
6020
|
+
return {
|
|
6021
|
+
success: false,
|
|
6022
|
+
error: "`cliclick` is not installed. It is required for mouse/keyboard control on macOS.",
|
|
6023
|
+
installCommand: "brew install cliclick",
|
|
6024
|
+
fixSteps: [
|
|
6025
|
+
"In a terminal on this Mac, run: brew install cliclick",
|
|
6026
|
+
"(If Homebrew is not installed, install it first from https://brew.sh)",
|
|
6027
|
+
"Then call enable_computer_use again"
|
|
6028
|
+
]
|
|
6029
|
+
};
|
|
6030
|
+
}
|
|
6031
|
+
const acc = await hasAccessibilityPermissions();
|
|
6032
|
+
const screen = await hasScreenRecordingPermissions();
|
|
6033
|
+
const missing = [];
|
|
6034
|
+
if (!acc.ok) {
|
|
6035
|
+
let prompted = false;
|
|
6036
|
+
let panelOpened = false;
|
|
6037
|
+
if (request_permissions) {
|
|
6038
|
+
prompted = await requestAccessibilityPrompt().then(() => true).catch(() => false);
|
|
6039
|
+
await openSystemSettings("accessibility").then(() => {
|
|
6040
|
+
panelOpened = true;
|
|
6041
|
+
}).catch(() => void 0);
|
|
6042
|
+
}
|
|
6043
|
+
missing.push({
|
|
6044
|
+
name: "Accessibility",
|
|
6045
|
+
reason: "cliclick failed: " + (acc.error?.split("\n")[0] || "no permission"),
|
|
6046
|
+
pane: "accessibility",
|
|
6047
|
+
settingsUrl: ACCESSIBILITY_URL,
|
|
6048
|
+
fixSteps: [
|
|
6049
|
+
"In the System Settings \u2192 Privacy & Security \u2192 Accessibility pane that opened",
|
|
6050
|
+
"Click the + button",
|
|
6051
|
+
"Add the application running the agent (Terminal, iTerm, your IDE, or `node`)",
|
|
6052
|
+
"Toggle the switch ON",
|
|
6053
|
+
"Restart the agent process so the new permission takes effect",
|
|
6054
|
+
"Then call enable_computer_use again"
|
|
6055
|
+
],
|
|
6056
|
+
prompted,
|
|
6057
|
+
panelOpened
|
|
6058
|
+
});
|
|
6059
|
+
}
|
|
6060
|
+
if (!screen) {
|
|
6061
|
+
let prompted = false;
|
|
6062
|
+
let panelOpened = false;
|
|
6063
|
+
if (request_permissions) {
|
|
6064
|
+
prompted = await requestScreenRecordingPrompt().then(() => true).catch(() => false);
|
|
6065
|
+
await openSystemSettings("screen-recording").then(() => {
|
|
6066
|
+
panelOpened = true;
|
|
6067
|
+
}).catch(() => void 0);
|
|
6068
|
+
}
|
|
6069
|
+
missing.push({
|
|
6070
|
+
name: "Screen Recording",
|
|
6071
|
+
reason: "CGPreflightScreenCaptureAccess returned false",
|
|
6072
|
+
pane: "screen-recording",
|
|
6073
|
+
settingsUrl: SCREEN_RECORDING_URL,
|
|
6074
|
+
fixSteps: [
|
|
6075
|
+
"In the System Settings \u2192 Privacy & Security \u2192 Screen Recording pane that opened",
|
|
6076
|
+
"Click the + button",
|
|
6077
|
+
"Add the application running the agent (Terminal, iTerm, your IDE, or `node`)",
|
|
6078
|
+
"Toggle the switch ON",
|
|
6079
|
+
"Restart the agent process so the new permission takes effect",
|
|
6080
|
+
"Then call enable_computer_use again"
|
|
6081
|
+
],
|
|
6082
|
+
prompted,
|
|
6083
|
+
panelOpened
|
|
6084
|
+
});
|
|
6085
|
+
}
|
|
6086
|
+
if (missing.length > 0) {
|
|
6087
|
+
return {
|
|
6088
|
+
success: false,
|
|
6089
|
+
error: `Missing permission${missing.length > 1 ? "s" : ""}: ` + missing.map((m) => m.name).join(" and ") + ".",
|
|
6090
|
+
missingPermissions: missing,
|
|
6091
|
+
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."
|
|
6092
|
+
};
|
|
6093
|
+
}
|
|
6094
|
+
let width = display_width;
|
|
6095
|
+
let height = display_height;
|
|
6096
|
+
let detected = null;
|
|
6097
|
+
if (width === void 0 || height === void 0) {
|
|
6098
|
+
detected = await detectScreenSize();
|
|
6099
|
+
width = width ?? detected?.width ?? 1280;
|
|
6100
|
+
height = height ?? detected?.height ?? 800;
|
|
6101
|
+
}
|
|
6102
|
+
const session = await sessionQueries.getById(options.sessionId);
|
|
6103
|
+
if (!session) {
|
|
6104
|
+
return { success: false, error: "Session not found" };
|
|
6105
|
+
}
|
|
6106
|
+
const config = session.config || {};
|
|
6107
|
+
if (config.computerUseEnabled === true && config.computerUseDisplayWidth === width && config.computerUseDisplayHeight === height) {
|
|
6108
|
+
return {
|
|
6109
|
+
success: true,
|
|
6110
|
+
alreadyEnabled: true,
|
|
6111
|
+
message: "Computer use was already enabled for this session.",
|
|
6112
|
+
displayWidth: width,
|
|
6113
|
+
displayHeight: height
|
|
6114
|
+
};
|
|
6115
|
+
}
|
|
6116
|
+
const updated = {
|
|
6117
|
+
...config,
|
|
6118
|
+
computerUseEnabled: true,
|
|
6119
|
+
computerUseDisplayWidth: width,
|
|
6120
|
+
computerUseDisplayHeight: height
|
|
6121
|
+
};
|
|
6122
|
+
await sessionQueries.update(options.sessionId, { config: updated });
|
|
6123
|
+
return {
|
|
6124
|
+
success: true,
|
|
6125
|
+
enabled: true,
|
|
6126
|
+
platform: "darwin",
|
|
6127
|
+
displayWidth: width,
|
|
6128
|
+
displayHeight: height,
|
|
6129
|
+
detectedScreenSize: detected || void 0,
|
|
6130
|
+
permissions: {
|
|
6131
|
+
accessibility: "granted",
|
|
6132
|
+
screenRecording: "granted"
|
|
6133
|
+
},
|
|
6134
|
+
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.`
|
|
6135
|
+
};
|
|
6136
|
+
} catch (err) {
|
|
6137
|
+
return {
|
|
6138
|
+
success: false,
|
|
6139
|
+
error: err?.message || String(err)
|
|
6140
|
+
};
|
|
6141
|
+
}
|
|
6142
|
+
}
|
|
6143
|
+
});
|
|
6144
|
+
}
|
|
6145
|
+
|
|
5565
6146
|
// src/tools/index.ts
|
|
5566
6147
|
init_semantic();
|
|
5567
6148
|
init_remote();
|
|
@@ -5609,6 +6190,20 @@ async function createTools(options) {
|
|
|
5609
6190
|
sessionId: options.sessionId
|
|
5610
6191
|
});
|
|
5611
6192
|
}
|
|
6193
|
+
if (process.platform === "darwin") {
|
|
6194
|
+
if (options.enableComputerUse) {
|
|
6195
|
+
tools.computer = createComputerUseTool({
|
|
6196
|
+
workingDirectory: options.workingDirectory,
|
|
6197
|
+
sessionId: options.sessionId,
|
|
6198
|
+
displayWidth: options.computerUseDisplayWidth,
|
|
6199
|
+
displayHeight: options.computerUseDisplayHeight
|
|
6200
|
+
});
|
|
6201
|
+
} else {
|
|
6202
|
+
tools.enable_computer_use = createEnableComputerUseTool({
|
|
6203
|
+
sessionId: options.sessionId
|
|
6204
|
+
});
|
|
6205
|
+
}
|
|
6206
|
+
}
|
|
5612
6207
|
if (options.enableSemanticSearch !== false) {
|
|
5613
6208
|
try {
|
|
5614
6209
|
if (isVectorGatewayConfigured()) {
|
|
@@ -6594,7 +7189,14 @@ function repairToolPairing(messages) {
|
|
|
6594
7189
|
}
|
|
6595
7190
|
|
|
6596
7191
|
// src/utils/webhook.ts
|
|
7192
|
+
var TERMINAL_EVENTS = /* @__PURE__ */ new Set([
|
|
7193
|
+
"task.started",
|
|
7194
|
+
"task.completed",
|
|
7195
|
+
"task.failed"
|
|
7196
|
+
]);
|
|
7197
|
+
var QUIET = process.env.SPARKECODER_QUIET_WEBHOOKS === "1";
|
|
6597
7198
|
async function sendWebhook(url, event) {
|
|
7199
|
+
const t0 = Date.now();
|
|
6598
7200
|
try {
|
|
6599
7201
|
const controller = new AbortController();
|
|
6600
7202
|
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
@@ -6608,12 +7210,24 @@ async function sendWebhook(url, event) {
|
|
|
6608
7210
|
signal: controller.signal
|
|
6609
7211
|
});
|
|
6610
7212
|
clearTimeout(timeout);
|
|
7213
|
+
const ms = Date.now() - t0;
|
|
6611
7214
|
if (!response.ok) {
|
|
6612
|
-
|
|
7215
|
+
const body = await response.text().catch(() => "");
|
|
7216
|
+
console.warn(
|
|
7217
|
+
`[WEBHOOK] ${event.type} task=${event.taskId} -> HTTP ${response.status} in ${ms}ms${body ? ` (${body.slice(0, 200)})` : ""}`
|
|
7218
|
+
);
|
|
7219
|
+
return;
|
|
7220
|
+
}
|
|
7221
|
+
if (!QUIET || TERMINAL_EVENTS.has(event.type)) {
|
|
7222
|
+
console.log(
|
|
7223
|
+
`[WEBHOOK] ${event.type} task=${event.taskId} -> 200 in ${ms}ms`
|
|
7224
|
+
);
|
|
6613
7225
|
}
|
|
6614
7226
|
} catch (err) {
|
|
6615
7227
|
const reason = err.name === "AbortError" ? "timeout (5s)" : err.message;
|
|
6616
|
-
console.warn(
|
|
7228
|
+
console.warn(
|
|
7229
|
+
`[WEBHOOK] ${event.type} task=${event.taskId} -> failed: ${reason}`
|
|
7230
|
+
);
|
|
6617
7231
|
}
|
|
6618
7232
|
}
|
|
6619
7233
|
|
|
@@ -6656,10 +7270,14 @@ var Agent = class _Agent {
|
|
|
6656
7270
|
*/
|
|
6657
7271
|
async createToolsWithCallbacks(options) {
|
|
6658
7272
|
const config = getConfig();
|
|
7273
|
+
const sessionConfig = this.session.config || {};
|
|
6659
7274
|
return createTools({
|
|
6660
7275
|
sessionId: this.session.id,
|
|
6661
7276
|
workingDirectory: this.session.workingDirectory,
|
|
6662
7277
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
7278
|
+
enableComputerUse: sessionConfig.computerUseEnabled === true,
|
|
7279
|
+
computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
|
|
7280
|
+
computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight,
|
|
6663
7281
|
onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
|
|
6664
7282
|
onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
|
|
6665
7283
|
onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0
|
|
@@ -6692,10 +7310,14 @@ var Agent = class _Agent {
|
|
|
6692
7310
|
keepRecentMessages: config.context?.keepRecentMessages || 10,
|
|
6693
7311
|
autoSummarize: config.context?.autoSummarize ?? true
|
|
6694
7312
|
});
|
|
7313
|
+
const sessionConfig = session.config || {};
|
|
6695
7314
|
const tools = await createTools({
|
|
6696
7315
|
sessionId: session.id,
|
|
6697
7316
|
workingDirectory: session.workingDirectory,
|
|
6698
|
-
skillsDirectories: config.resolvedSkillsDirectories
|
|
7317
|
+
skillsDirectories: config.resolvedSkillsDirectories,
|
|
7318
|
+
enableComputerUse: sessionConfig.computerUseEnabled === true,
|
|
7319
|
+
computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
|
|
7320
|
+
computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight
|
|
6699
7321
|
});
|
|
6700
7322
|
return new _Agent(session, context, tools);
|
|
6701
7323
|
}
|
|
@@ -6914,10 +7536,14 @@ ${prompt}` });
|
|
|
6914
7536
|
});
|
|
6915
7537
|
}
|
|
6916
7538
|
};
|
|
7539
|
+
const taskSessionConfig = this.session.config || {};
|
|
6917
7540
|
const taskTools = await createTools({
|
|
6918
7541
|
sessionId: this.session.id,
|
|
6919
7542
|
workingDirectory: this.session.workingDirectory,
|
|
6920
7543
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
7544
|
+
enableComputerUse: taskSessionConfig.computerUseEnabled === true,
|
|
7545
|
+
computerUseDisplayWidth: taskSessionConfig.computerUseDisplayWidth,
|
|
7546
|
+
computerUseDisplayHeight: taskSessionConfig.computerUseDisplayHeight,
|
|
6921
7547
|
onBashProgress: bashProgressHandler,
|
|
6922
7548
|
onWriteFileProgress: (progress) => {
|
|
6923
7549
|
options.onToolProgress?.({ toolName: "write_file", data: progress });
|
|
@@ -7200,11 +7826,11 @@ ${taskAddendum}`;
|
|
|
7200
7826
|
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
7201
7827
|
if (!isRemoteConfigured2()) return [];
|
|
7202
7828
|
const { readFile: readFile12 } = await import("fs/promises");
|
|
7203
|
-
const { join:
|
|
7829
|
+
const { join: join10, basename: basename5 } = await import("path");
|
|
7204
7830
|
const urls = [];
|
|
7205
7831
|
for (const filePath of filePaths) {
|
|
7206
7832
|
try {
|
|
7207
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
7833
|
+
const fullPath = filePath.startsWith("/") ? filePath : join10(this.session.workingDirectory, filePath);
|
|
7208
7834
|
const fileName = basename5(fullPath);
|
|
7209
7835
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
7210
7836
|
const mimeMap = {
|
|
@@ -7262,11 +7888,11 @@ ${taskAddendum}`;
|
|
|
7262
7888
|
wrappedTools[name] = originalTool;
|
|
7263
7889
|
continue;
|
|
7264
7890
|
}
|
|
7265
|
-
wrappedTools[name] =
|
|
7891
|
+
wrappedTools[name] = tool14({
|
|
7266
7892
|
description: originalTool.description || "",
|
|
7267
|
-
inputSchema: originalTool.inputSchema ||
|
|
7893
|
+
inputSchema: originalTool.inputSchema || z15.object({}),
|
|
7268
7894
|
execute: async (input, toolOptions) => {
|
|
7269
|
-
const toolCallId = toolOptions.toolCallId ||
|
|
7895
|
+
const toolCallId = toolOptions.toolCallId || nanoid5();
|
|
7270
7896
|
const execution = toolExecutionQueries.create({
|
|
7271
7897
|
sessionId: this.session.id,
|
|
7272
7898
|
toolName: name,
|
|
@@ -7284,10 +7910,10 @@ ${taskAddendum}`;
|
|
|
7284
7910
|
const resolverData = approvalResolvers.get(toolCallId);
|
|
7285
7911
|
approvalResolvers.delete(toolCallId);
|
|
7286
7912
|
this.pendingApprovals.delete(toolCallId);
|
|
7287
|
-
const
|
|
7913
|
+
const exec7 = await execution;
|
|
7288
7914
|
if (!approved) {
|
|
7289
7915
|
const reason = resolverData?.reason || "User rejected the tool execution";
|
|
7290
|
-
await toolExecutionQueries.reject(
|
|
7916
|
+
await toolExecutionQueries.reject(exec7.id);
|
|
7291
7917
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
7292
7918
|
return {
|
|
7293
7919
|
status: "rejected",
|
|
@@ -7297,14 +7923,14 @@ ${taskAddendum}`;
|
|
|
7297
7923
|
message: `Tool "${name}" was rejected by the user. Reason: ${reason}`
|
|
7298
7924
|
};
|
|
7299
7925
|
}
|
|
7300
|
-
await toolExecutionQueries.approve(
|
|
7926
|
+
await toolExecutionQueries.approve(exec7.id);
|
|
7301
7927
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
7302
7928
|
try {
|
|
7303
7929
|
const result = await originalTool.execute(input, toolOptions);
|
|
7304
|
-
await toolExecutionQueries.complete(
|
|
7930
|
+
await toolExecutionQueries.complete(exec7.id, result);
|
|
7305
7931
|
return result;
|
|
7306
7932
|
} catch (error) {
|
|
7307
|
-
await toolExecutionQueries.complete(
|
|
7933
|
+
await toolExecutionQueries.complete(exec7.id, null, error.message);
|
|
7308
7934
|
throw error;
|
|
7309
7935
|
}
|
|
7310
7936
|
}
|