sparkecoder 0.1.86 → 0.1.93
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/agent/index.d.ts +3 -3
- package/dist/agent/index.js +809 -97
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +2395 -316
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/{index-OhuTM4a0.d.ts → index-Bn0Zox8f.d.ts} +32 -23
- package/dist/index.d.ts +5 -5
- package/dist/index.js +1932 -273
- package/dist/index.js.map +1 -1
- package/dist/{schema-CohdIL13.d.ts → schema-EmpbnQeQ.d.ts} +3 -3
- package/dist/{search-CCffrVJE.d.ts → search-BRnGaIl-.d.ts} +7 -7
- package/dist/server/index.js +1932 -273
- package/dist/server/index.js.map +1 -1
- package/dist/skills/default/computer-use.md +150 -0
- package/dist/tools/index.d.ts +196 -3
- package/dist/tools/index.js +639 -11
- package/dist/tools/index.js.map +1 -1
- package/package.json +6 -5
- 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/(main)/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/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/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/embed/[id]/page.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 +4 -4
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__36edac7c._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{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/chunks/ssr/[root-of-the-server]__be5e2967._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_page_tsx_5ac4794b._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_components_sessions-sidebar_tsx_92510070._.js +1 -1
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/standalone/web/.next/static/chunks/275e8268daf318b2.js +7 -0
- package/web/.next/standalone/web/.next/static/{static/chunks/26eb5fab5216f3cc.js → chunks/58fd0aaa2746b444.js} +1 -1
- package/web/.next/standalone/web/.next/static/chunks/9fce2ce79c4c834e.js +1 -0
- package/web/.next/{static/chunks/8d3efc76109d2efc.js → standalone/web/.next/static/chunks/a888d448ceab1abe.js} +1 -1
- package/web/.next/standalone/web/.next/static/static/chunks/275e8268daf318b2.js +7 -0
- package/web/.next/{static/chunks/26eb5fab5216f3cc.js → standalone/web/.next/static/static/chunks/58fd0aaa2746b444.js} +1 -1
- package/web/.next/standalone/web/.next/static/static/chunks/9fce2ce79c4c834e.js +1 -0
- package/web/.next/standalone/web/.next/static/{chunks/8d3efc76109d2efc.js → static/chunks/a888d448ceab1abe.js} +1 -1
- package/web/.next/standalone/web/package-lock.json +27 -27
- package/web/.next/standalone/web/package.json +1 -1
- package/web/.next/standalone/web/src/app/(main)/page.tsx +2 -2
- package/web/.next/standalone/web/src/app/api/config/route.ts +3 -2
- package/web/.next/standalone/web/src/app/embed/[id]/page.tsx +12 -0
- package/web/.next/standalone/web/src/components/sessions-sidebar.tsx +1 -1
- package/web/.next/standalone/web/src/lib/config.ts +2 -1
- package/web/.next/standalone/web/src/lib/embed-bootstrap.ts +108 -0
- package/web/.next/static/chunks/275e8268daf318b2.js +7 -0
- package/web/.next/{standalone/web/.next/static/chunks/26eb5fab5216f3cc.js → static/chunks/58fd0aaa2746b444.js} +1 -1
- package/web/.next/static/chunks/9fce2ce79c4c834e.js +1 -0
- package/web/.next/{standalone/web/.next/static/static/chunks/8d3efc76109d2efc.js → static/chunks/a888d448ceab1abe.js} +1 -1
- package/web/package.json +1 -1
- package/web/.next/standalone/web/.next/static/chunks/5383c5717758f575.js +0 -7
- package/web/.next/standalone/web/.next/static/chunks/b31b0765abe0c427.js +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/5383c5717758f575.js +0 -7
- package/web/.next/standalone/web/.next/static/static/chunks/b31b0765abe0c427.js +0 -1
- package/web/.next/static/chunks/5383c5717758f575.js +0 -7
- package/web/.next/static/chunks/b31b0765abe0c427.js +0 -1
- /package/web/.next/standalone/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_ssgManifest.js +0 -0
- /package/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_buildManifest.js +0 -0
- /package/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_ssgManifest.js +0 -0
package/dist/tools/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)
|
|
@@ -96,7 +107,7 @@ var init_types = __esm({
|
|
|
96
107
|
}).optional();
|
|
97
108
|
SparkcoderConfigSchema = z.object({
|
|
98
109
|
// Default model to use (Vercel AI Gateway format)
|
|
99
|
-
defaultModel: z.string().default("anthropic/claude-opus-4
|
|
110
|
+
defaultModel: z.string().default("anthropic/claude-opus-4.7"),
|
|
100
111
|
// Working directory for file operations
|
|
101
112
|
workingDirectory: z.string().optional(),
|
|
102
113
|
// Tool approval settings
|
|
@@ -639,12 +650,13 @@ function getDb() {
|
|
|
639
650
|
}
|
|
640
651
|
return {};
|
|
641
652
|
}
|
|
642
|
-
var initialized, todoQueries, skillQueries, fileBackupQueries, subagentQueries, indexStatusQueries;
|
|
653
|
+
var initialized, sessionQueries, todoQueries, skillQueries, fileBackupQueries, subagentQueries, indexStatusQueries;
|
|
643
654
|
var init_db = __esm({
|
|
644
655
|
"src/db/index.ts"() {
|
|
645
656
|
"use strict";
|
|
646
657
|
init_remote();
|
|
647
658
|
initialized = false;
|
|
659
|
+
sessionQueries = remoteSessionQueries;
|
|
648
660
|
todoQueries = remoteTodoQueries;
|
|
649
661
|
skillQueries = remoteSkillQueries;
|
|
650
662
|
fileBackupQueries = remoteFileBackupQueries;
|
|
@@ -1932,12 +1944,12 @@ function findNearestRoot(startDir, markers) {
|
|
|
1932
1944
|
}
|
|
1933
1945
|
async function commandExists(cmd) {
|
|
1934
1946
|
try {
|
|
1935
|
-
const { exec:
|
|
1936
|
-
const { promisify:
|
|
1937
|
-
const
|
|
1947
|
+
const { exec: exec6 } = await import("child_process");
|
|
1948
|
+
const { promisify: promisify6 } = await import("util");
|
|
1949
|
+
const execAsync6 = promisify6(exec6);
|
|
1938
1950
|
const isWindows = process.platform === "win32";
|
|
1939
1951
|
const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
|
|
1940
|
-
await
|
|
1952
|
+
await execAsync6(checkCmd);
|
|
1941
1953
|
return true;
|
|
1942
1954
|
} catch {
|
|
1943
1955
|
return false;
|
|
@@ -3206,7 +3218,8 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
3206
3218
|
globs: parsed.metadata.globs,
|
|
3207
3219
|
loadType,
|
|
3208
3220
|
priority,
|
|
3209
|
-
sourceDir: directory
|
|
3221
|
+
sourceDir: directory,
|
|
3222
|
+
platforms: parsed.metadata.platforms
|
|
3210
3223
|
});
|
|
3211
3224
|
} else {
|
|
3212
3225
|
const name = getSkillNameFromPath(filePath);
|
|
@@ -3219,11 +3232,14 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
3219
3232
|
globs: [],
|
|
3220
3233
|
loadType: forceAlwaysApply ? "always" : defaultLoadType,
|
|
3221
3234
|
priority,
|
|
3222
|
-
sourceDir: directory
|
|
3235
|
+
sourceDir: directory,
|
|
3236
|
+
platforms: []
|
|
3223
3237
|
});
|
|
3224
3238
|
}
|
|
3225
3239
|
}
|
|
3226
|
-
return skills
|
|
3240
|
+
return skills.filter(
|
|
3241
|
+
(s) => s.platforms.length === 0 || s.platforms.includes(process.platform)
|
|
3242
|
+
);
|
|
3227
3243
|
}
|
|
3228
3244
|
async function loadAllSkills(directories) {
|
|
3229
3245
|
const allSkills = [];
|
|
@@ -4897,6 +4913,7 @@ init_semantic_search();
|
|
|
4897
4913
|
import { tool as tool11 } from "ai";
|
|
4898
4914
|
import { z as z12 } from "zod";
|
|
4899
4915
|
import Ajv from "ajv";
|
|
4916
|
+
import { nanoid as nanoid3 } from "nanoid";
|
|
4900
4917
|
var ajv = new Ajv({ allErrors: true });
|
|
4901
4918
|
function createCompleteTaskTool(options) {
|
|
4902
4919
|
const validate = ajv.compile(options.outputSchema);
|
|
@@ -4943,6 +4960,37 @@ function createTaskFailedTool(options) {
|
|
|
4943
4960
|
}
|
|
4944
4961
|
});
|
|
4945
4962
|
}
|
|
4963
|
+
function createAskQuestionToUserTool(options) {
|
|
4964
|
+
return tool11({
|
|
4965
|
+
description: "Ask the user a blocking clarification question when you cannot safely continue without more information. Use this only after trying to infer the answer from the available context. The task will pause until the orchestrator or user answers.",
|
|
4966
|
+
inputSchema: z12.object({
|
|
4967
|
+
question: z12.string().min(1).describe("The concise question you need answered."),
|
|
4968
|
+
context: z12.string().optional().describe("Brief context explaining why this answer is needed and what you already tried."),
|
|
4969
|
+
choices: z12.array(z12.string().min(1)).max(10).optional().describe("Optional suggested answer choices when the question is multiple choice.")
|
|
4970
|
+
}),
|
|
4971
|
+
execute: async (input) => {
|
|
4972
|
+
if (!options.onQuestion) {
|
|
4973
|
+
return {
|
|
4974
|
+
status: "unavailable",
|
|
4975
|
+
message: "Question routing is not configured for this task. Continue with the best safe assumption or call task_failed if blocked."
|
|
4976
|
+
};
|
|
4977
|
+
}
|
|
4978
|
+
const questionId = `q_${nanoid3(12)}`;
|
|
4979
|
+
const answer = await options.onQuestion({
|
|
4980
|
+
questionId,
|
|
4981
|
+
question: input.question,
|
|
4982
|
+
context: input.context,
|
|
4983
|
+
choices: input.choices
|
|
4984
|
+
});
|
|
4985
|
+
return {
|
|
4986
|
+
status: "answered",
|
|
4987
|
+
questionId,
|
|
4988
|
+
answer: answer.answer,
|
|
4989
|
+
answeredBy: answer.answeredBy ?? "unknown"
|
|
4990
|
+
};
|
|
4991
|
+
}
|
|
4992
|
+
});
|
|
4993
|
+
}
|
|
4946
4994
|
|
|
4947
4995
|
// src/tools/upload-file.ts
|
|
4948
4996
|
import { tool as tool12 } from "ai";
|
|
@@ -5041,6 +5089,568 @@ function createUploadFileTool(options) {
|
|
|
5041
5089
|
});
|
|
5042
5090
|
}
|
|
5043
5091
|
|
|
5092
|
+
// src/tools/computer-use.ts
|
|
5093
|
+
import { anthropic } from "@ai-sdk/anthropic";
|
|
5094
|
+
import { exec as exec5 } from "child_process";
|
|
5095
|
+
import { promisify as promisify5 } from "util";
|
|
5096
|
+
import { mkdirSync as mkdirSync5, existsSync as existsSync15, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
|
|
5097
|
+
import { join as join8 } from "path";
|
|
5098
|
+
import { tmpdir } from "os";
|
|
5099
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
5100
|
+
var execAsync5 = promisify5(exec5);
|
|
5101
|
+
var DEFAULT_WIDTH = 1280;
|
|
5102
|
+
var DEFAULT_HEIGHT = 800;
|
|
5103
|
+
function isMacOs() {
|
|
5104
|
+
return process.platform === "darwin";
|
|
5105
|
+
}
|
|
5106
|
+
async function isCliclickInstalled() {
|
|
5107
|
+
try {
|
|
5108
|
+
await execAsync5("command -v cliclick", { timeout: 2e3 });
|
|
5109
|
+
return true;
|
|
5110
|
+
} catch {
|
|
5111
|
+
return false;
|
|
5112
|
+
}
|
|
5113
|
+
}
|
|
5114
|
+
async function runJxa(script) {
|
|
5115
|
+
try {
|
|
5116
|
+
const escaped = script.replace(/'/g, `'\\''`);
|
|
5117
|
+
const { stdout } = await execAsync5(`osascript -l JavaScript -e '${escaped}'`, {
|
|
5118
|
+
timeout: 5e3
|
|
5119
|
+
});
|
|
5120
|
+
return JSON.parse(stdout.trim());
|
|
5121
|
+
} catch {
|
|
5122
|
+
return null;
|
|
5123
|
+
}
|
|
5124
|
+
}
|
|
5125
|
+
async function hasAccessibilityPermissions() {
|
|
5126
|
+
try {
|
|
5127
|
+
const { stderr } = await execAsync5("cliclick p:.", { timeout: 3e3 });
|
|
5128
|
+
if (/accessibility privileges not enabled/i.test(stderr)) {
|
|
5129
|
+
return { ok: false, error: stderr.trim().split("\n")[0] };
|
|
5130
|
+
}
|
|
5131
|
+
return { ok: true };
|
|
5132
|
+
} catch (err) {
|
|
5133
|
+
return { ok: false, error: err?.message || String(err) };
|
|
5134
|
+
}
|
|
5135
|
+
}
|
|
5136
|
+
async function hasScreenRecordingPermissions() {
|
|
5137
|
+
const result = await runJxa(
|
|
5138
|
+
`ObjC.import("Cocoa");
|
|
5139
|
+
ObjC.import("CoreGraphics");
|
|
5140
|
+
ObjC.bindFunction("CGPreflightScreenCaptureAccess", ["bool", []]);
|
|
5141
|
+
JSON.stringify({ hasAccess: !!$.CGPreflightScreenCaptureAccess() });`
|
|
5142
|
+
);
|
|
5143
|
+
return result?.hasAccess ?? false;
|
|
5144
|
+
}
|
|
5145
|
+
async function requestAccessibilityPrompt() {
|
|
5146
|
+
const result = await runJxa(
|
|
5147
|
+
`ObjC.import("ApplicationServices");
|
|
5148
|
+
var key = $.kAXTrustedCheckOptionPrompt;
|
|
5149
|
+
var dict = $.NSDictionary.dictionaryWithObjectForKey($.kCFBooleanTrue, key);
|
|
5150
|
+
var trusted = $.AXIsProcessTrustedWithOptions(dict);
|
|
5151
|
+
JSON.stringify({ trusted: !!trusted });`
|
|
5152
|
+
);
|
|
5153
|
+
return result?.trusted ?? false;
|
|
5154
|
+
}
|
|
5155
|
+
async function requestScreenRecordingPrompt() {
|
|
5156
|
+
const result = await runJxa(
|
|
5157
|
+
`ObjC.import("Cocoa");
|
|
5158
|
+
ObjC.import("CoreGraphics");
|
|
5159
|
+
ObjC.bindFunction("CGRequestScreenCaptureAccess", ["bool", []]);
|
|
5160
|
+
JSON.stringify({ granted: !!$.CGRequestScreenCaptureAccess() });`
|
|
5161
|
+
);
|
|
5162
|
+
return result?.granted ?? false;
|
|
5163
|
+
}
|
|
5164
|
+
async function openSystemSettings(pane) {
|
|
5165
|
+
const url = pane === "accessibility" ? "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility" : "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
|
|
5166
|
+
try {
|
|
5167
|
+
await execAsync5(`open '${url}'`, { timeout: 3e3 });
|
|
5168
|
+
} catch {
|
|
5169
|
+
}
|
|
5170
|
+
}
|
|
5171
|
+
async function detectScreenSize() {
|
|
5172
|
+
try {
|
|
5173
|
+
const { stdout } = await execAsync5(
|
|
5174
|
+
`osascript -e 'tell application "Finder" to get bounds of window of desktop'`,
|
|
5175
|
+
{ timeout: 3e3 }
|
|
5176
|
+
);
|
|
5177
|
+
const parts = stdout.trim().split(",").map((s) => parseInt(s.trim(), 10));
|
|
5178
|
+
if (parts.length >= 4 && parts.every((n) => Number.isFinite(n))) {
|
|
5179
|
+
const [x1, y1, x2, y2] = parts;
|
|
5180
|
+
return { width: x2 - x1, height: y2 - y1 };
|
|
5181
|
+
}
|
|
5182
|
+
} catch {
|
|
5183
|
+
}
|
|
5184
|
+
return null;
|
|
5185
|
+
}
|
|
5186
|
+
async function runCliclick(args) {
|
|
5187
|
+
const quoted = args.map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(" ");
|
|
5188
|
+
const { stdout, stderr } = await execAsync5(`cliclick ${quoted}`, {
|
|
5189
|
+
timeout: 15e3,
|
|
5190
|
+
maxBuffer: 1024 * 1024
|
|
5191
|
+
});
|
|
5192
|
+
if (/accessibility privileges not enabled/i.test(stderr)) {
|
|
5193
|
+
throw new Error(
|
|
5194
|
+
"Accessibility permissions not granted to cliclick. Open System Settings \u2192 Privacy & Security \u2192 Accessibility, add cliclick (or the agent runtime), and toggle it on."
|
|
5195
|
+
);
|
|
5196
|
+
}
|
|
5197
|
+
if (stderr && !stdout) throw new Error(stderr.trim());
|
|
5198
|
+
return (stdout || "").trim();
|
|
5199
|
+
}
|
|
5200
|
+
async function runScreencapture(path) {
|
|
5201
|
+
await execAsync5(`screencapture -x -t png '${path.replace(/'/g, `'\\''`)}'`, {
|
|
5202
|
+
timeout: 5e3
|
|
5203
|
+
});
|
|
5204
|
+
}
|
|
5205
|
+
async function resizeScreenshotToPoints(path, targetWidth, targetHeight) {
|
|
5206
|
+
const sharpModule = await import("sharp");
|
|
5207
|
+
const sharp2 = sharpModule.default || sharpModule;
|
|
5208
|
+
const meta = await sharp2(path).metadata();
|
|
5209
|
+
if (meta.width === targetWidth && meta.height === targetHeight) {
|
|
5210
|
+
return readFileSync7(path);
|
|
5211
|
+
}
|
|
5212
|
+
return await sharp2(path).resize(targetWidth, targetHeight, { fit: "fill" }).png().toBuffer();
|
|
5213
|
+
}
|
|
5214
|
+
async function runScroll(dx, dy) {
|
|
5215
|
+
const wheelY = -Math.round(dy);
|
|
5216
|
+
const wheelX = -Math.round(dx);
|
|
5217
|
+
const script = `ObjC.import('CoreGraphics');var ev = $.CGEventCreateScrollWheelEvent(null, 0, 2, ${wheelY}, ${wheelX});$.CGEventPost(0, ev);`;
|
|
5218
|
+
await execAsync5(
|
|
5219
|
+
`osascript -l JavaScript -e '${script.replace(/'/g, `'\\''`)}'`,
|
|
5220
|
+
{ timeout: 5e3 }
|
|
5221
|
+
);
|
|
5222
|
+
}
|
|
5223
|
+
function translateKeyForCliclick(key) {
|
|
5224
|
+
if (!key) return [];
|
|
5225
|
+
const parts = key.split("+").map((p) => p.trim()).filter(Boolean);
|
|
5226
|
+
if (parts.length === 0) return [];
|
|
5227
|
+
const modMap = {
|
|
5228
|
+
ctrl: "ctrl",
|
|
5229
|
+
control: "ctrl",
|
|
5230
|
+
alt: "alt",
|
|
5231
|
+
option: "alt",
|
|
5232
|
+
shift: "shift",
|
|
5233
|
+
cmd: "cmd",
|
|
5234
|
+
super: "cmd",
|
|
5235
|
+
meta: "cmd",
|
|
5236
|
+
win: "cmd",
|
|
5237
|
+
fn: "fn"
|
|
5238
|
+
};
|
|
5239
|
+
const keyMap = {
|
|
5240
|
+
return: "enter",
|
|
5241
|
+
enter: "enter",
|
|
5242
|
+
esc: "esc",
|
|
5243
|
+
escape: "esc",
|
|
5244
|
+
backspace: "delete",
|
|
5245
|
+
back_space: "delete",
|
|
5246
|
+
delete: "fwd-delete",
|
|
5247
|
+
fwd_delete: "fwd-delete",
|
|
5248
|
+
forward_delete: "fwd-delete",
|
|
5249
|
+
tab: "tab",
|
|
5250
|
+
space: "space",
|
|
5251
|
+
up: "arrow-up",
|
|
5252
|
+
arrow_up: "arrow-up",
|
|
5253
|
+
down: "arrow-down",
|
|
5254
|
+
arrow_down: "arrow-down",
|
|
5255
|
+
left: "arrow-left",
|
|
5256
|
+
arrow_left: "arrow-left",
|
|
5257
|
+
right: "arrow-right",
|
|
5258
|
+
arrow_right: "arrow-right",
|
|
5259
|
+
page_up: "page-up",
|
|
5260
|
+
pageup: "page-up",
|
|
5261
|
+
page_down: "page-down",
|
|
5262
|
+
pagedown: "page-down",
|
|
5263
|
+
home: "home",
|
|
5264
|
+
end: "end",
|
|
5265
|
+
f1: "f1",
|
|
5266
|
+
f2: "f2",
|
|
5267
|
+
f3: "f3",
|
|
5268
|
+
f4: "f4",
|
|
5269
|
+
f5: "f5",
|
|
5270
|
+
f6: "f6",
|
|
5271
|
+
f7: "f7",
|
|
5272
|
+
f8: "f8",
|
|
5273
|
+
f9: "f9",
|
|
5274
|
+
f10: "f10",
|
|
5275
|
+
f11: "f11",
|
|
5276
|
+
f12: "f12"
|
|
5277
|
+
};
|
|
5278
|
+
const modifiers = [];
|
|
5279
|
+
let mainKey = null;
|
|
5280
|
+
for (let i = 0; i < parts.length; i++) {
|
|
5281
|
+
const lower = parts[i].toLowerCase().replace(/-/g, "_");
|
|
5282
|
+
if (i < parts.length - 1 && modMap[lower]) {
|
|
5283
|
+
modifiers.push(modMap[lower]);
|
|
5284
|
+
} else {
|
|
5285
|
+
mainKey = keyMap[lower] || lower;
|
|
5286
|
+
}
|
|
5287
|
+
}
|
|
5288
|
+
const args = [];
|
|
5289
|
+
if (modifiers.length > 0) args.push(`kd:${modifiers.join(",")}`);
|
|
5290
|
+
if (mainKey) {
|
|
5291
|
+
const isNamedKey = Object.values(keyMap).includes(mainKey) || /^f([1-9]|1[0-9]|20)$/.test(mainKey) || /^num-/.test(mainKey);
|
|
5292
|
+
if (isNamedKey) {
|
|
5293
|
+
args.push(`kp:${mainKey}`);
|
|
5294
|
+
} else {
|
|
5295
|
+
args.push(`t:${mainKey}`);
|
|
5296
|
+
}
|
|
5297
|
+
}
|
|
5298
|
+
if (modifiers.length > 0) args.push(`ku:${modifiers.join(",")}`);
|
|
5299
|
+
return args;
|
|
5300
|
+
}
|
|
5301
|
+
function modifierStringToCliclick(text) {
|
|
5302
|
+
return text.split("+").map((p) => p.trim().toLowerCase()).map((p) => {
|
|
5303
|
+
if (p === "ctrl" || p === "control") return "ctrl";
|
|
5304
|
+
if (p === "alt" || p === "option") return "alt";
|
|
5305
|
+
if (p === "shift") return "shift";
|
|
5306
|
+
if (p === "super" || p === "meta" || p === "cmd") return "cmd";
|
|
5307
|
+
return "";
|
|
5308
|
+
}).filter(Boolean);
|
|
5309
|
+
}
|
|
5310
|
+
function createComputerUseTool(options) {
|
|
5311
|
+
const displayWidth = options.displayWidth ?? DEFAULT_WIDTH;
|
|
5312
|
+
const displayHeight = options.displayHeight ?? DEFAULT_HEIGHT;
|
|
5313
|
+
return anthropic.tools.computer_20251124({
|
|
5314
|
+
displayWidthPx: displayWidth,
|
|
5315
|
+
displayHeightPx: displayHeight,
|
|
5316
|
+
enableZoom: true,
|
|
5317
|
+
execute: async (input) => {
|
|
5318
|
+
try {
|
|
5319
|
+
switch (input.action) {
|
|
5320
|
+
case "screenshot": {
|
|
5321
|
+
const path = join8(tmpdir(), `cu-${nanoid4(8)}.png`);
|
|
5322
|
+
await runScreencapture(path);
|
|
5323
|
+
const resized = await resizeScreenshotToPoints(path, displayWidth, displayHeight);
|
|
5324
|
+
try {
|
|
5325
|
+
unlinkSync2(path);
|
|
5326
|
+
} catch {
|
|
5327
|
+
}
|
|
5328
|
+
return { type: "image", data: resized.toString("base64") };
|
|
5329
|
+
}
|
|
5330
|
+
case "left_click": {
|
|
5331
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5332
|
+
if (input.text) {
|
|
5333
|
+
const mods = modifierStringToCliclick(input.text);
|
|
5334
|
+
if (mods.length > 0) {
|
|
5335
|
+
await runCliclick([`kd:${mods.join(",")}`, `c:${x},${y}`, `ku:${mods.join(",")}`]);
|
|
5336
|
+
} else {
|
|
5337
|
+
await runCliclick([`c:${x},${y}`]);
|
|
5338
|
+
}
|
|
5339
|
+
} else {
|
|
5340
|
+
await runCliclick([`c:${x},${y}`]);
|
|
5341
|
+
}
|
|
5342
|
+
return `clicked at (${x}, ${y})${input.text ? ` with ${input.text}` : ""}`;
|
|
5343
|
+
}
|
|
5344
|
+
case "right_click": {
|
|
5345
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5346
|
+
await runCliclick([`rc:${x},${y}`]);
|
|
5347
|
+
return `right-clicked at (${x}, ${y})`;
|
|
5348
|
+
}
|
|
5349
|
+
case "middle_click": {
|
|
5350
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5351
|
+
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);`;
|
|
5352
|
+
await execAsync5(
|
|
5353
|
+
`osascript -l JavaScript -e '${script.replace(/'/g, `'\\''`)}'`,
|
|
5354
|
+
{ timeout: 3e3 }
|
|
5355
|
+
);
|
|
5356
|
+
return `middle-clicked at (${x}, ${y})`;
|
|
5357
|
+
}
|
|
5358
|
+
case "double_click": {
|
|
5359
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5360
|
+
await runCliclick([`dc:${x},${y}`]);
|
|
5361
|
+
return `double-clicked at (${x}, ${y})`;
|
|
5362
|
+
}
|
|
5363
|
+
case "triple_click": {
|
|
5364
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5365
|
+
await runCliclick([`tc:${x},${y}`]);
|
|
5366
|
+
return `triple-clicked at (${x}, ${y})`;
|
|
5367
|
+
}
|
|
5368
|
+
case "mouse_move": {
|
|
5369
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5370
|
+
await runCliclick([`m:${x},${y}`]);
|
|
5371
|
+
return `moved cursor to (${x}, ${y})`;
|
|
5372
|
+
}
|
|
5373
|
+
case "left_mouse_down": {
|
|
5374
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5375
|
+
await runCliclick([`dd:${x},${y}`]);
|
|
5376
|
+
return `left mouse button pressed at (${x}, ${y})`;
|
|
5377
|
+
}
|
|
5378
|
+
case "left_mouse_up": {
|
|
5379
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5380
|
+
await runCliclick([`du:${x},${y}`]);
|
|
5381
|
+
return `left mouse button released at (${x}, ${y})`;
|
|
5382
|
+
}
|
|
5383
|
+
case "left_click_drag": {
|
|
5384
|
+
const [sx, sy] = input.start_coordinate ?? [0, 0];
|
|
5385
|
+
const [ex, ey] = input.coordinate ?? [0, 0];
|
|
5386
|
+
await runCliclick([`dd:${sx},${sy}`, `m:${ex},${ey}`, `du:${ex},${ey}`]);
|
|
5387
|
+
return `dragged from (${sx}, ${sy}) to (${ex}, ${ey})`;
|
|
5388
|
+
}
|
|
5389
|
+
case "type": {
|
|
5390
|
+
const text = input.text ?? "";
|
|
5391
|
+
await runCliclick([`t:${text}`]);
|
|
5392
|
+
return `typed ${text.length} character(s)`;
|
|
5393
|
+
}
|
|
5394
|
+
case "key": {
|
|
5395
|
+
const args = translateKeyForCliclick(input.text ?? "");
|
|
5396
|
+
if (args.length === 0) return "no key specified";
|
|
5397
|
+
await runCliclick(args);
|
|
5398
|
+
return `pressed ${input.text}`;
|
|
5399
|
+
}
|
|
5400
|
+
case "hold_key": {
|
|
5401
|
+
const text = (input.text ?? "").toLowerCase();
|
|
5402
|
+
const duration = input.duration ?? 1;
|
|
5403
|
+
const modMap = {
|
|
5404
|
+
ctrl: "ctrl",
|
|
5405
|
+
control: "ctrl",
|
|
5406
|
+
alt: "alt",
|
|
5407
|
+
option: "alt",
|
|
5408
|
+
shift: "shift",
|
|
5409
|
+
cmd: "cmd",
|
|
5410
|
+
super: "cmd",
|
|
5411
|
+
meta: "cmd",
|
|
5412
|
+
fn: "fn"
|
|
5413
|
+
};
|
|
5414
|
+
const cliName = modMap[text] || text;
|
|
5415
|
+
await runCliclick([`kd:${cliName}`]);
|
|
5416
|
+
await new Promise((r) => setTimeout(r, duration * 1e3));
|
|
5417
|
+
await runCliclick([`ku:${cliName}`]);
|
|
5418
|
+
return `held ${text} for ${duration}s`;
|
|
5419
|
+
}
|
|
5420
|
+
case "scroll": {
|
|
5421
|
+
const direction = input.scroll_direction ?? "down";
|
|
5422
|
+
const amount = input.scroll_amount ?? 3;
|
|
5423
|
+
const px = amount * 100;
|
|
5424
|
+
const dx = direction === "left" ? -px : direction === "right" ? px : 0;
|
|
5425
|
+
const dy = direction === "up" ? -px : direction === "down" ? px : 0;
|
|
5426
|
+
if (input.coordinate) {
|
|
5427
|
+
const [x, y] = input.coordinate;
|
|
5428
|
+
await runCliclick([`m:${x},${y}`]);
|
|
5429
|
+
}
|
|
5430
|
+
const mods = input.text ? modifierStringToCliclick(input.text) : [];
|
|
5431
|
+
if (mods.length > 0) {
|
|
5432
|
+
await runCliclick([`kd:${mods.join(",")}`]);
|
|
5433
|
+
}
|
|
5434
|
+
await runScroll(dx, dy);
|
|
5435
|
+
if (mods.length > 0) {
|
|
5436
|
+
await runCliclick([`ku:${mods.join(",")}`]);
|
|
5437
|
+
}
|
|
5438
|
+
return `scrolled ${direction} by ${amount}`;
|
|
5439
|
+
}
|
|
5440
|
+
case "wait": {
|
|
5441
|
+
const duration = input.duration ?? 1;
|
|
5442
|
+
await new Promise((r) => setTimeout(r, duration * 1e3));
|
|
5443
|
+
return `waited ${duration}s`;
|
|
5444
|
+
}
|
|
5445
|
+
case "cursor_position": {
|
|
5446
|
+
const out = await runCliclick(["p:."]);
|
|
5447
|
+
return `cursor at ${out}`;
|
|
5448
|
+
}
|
|
5449
|
+
case "zoom": {
|
|
5450
|
+
const region = input.region ?? [0, 0, displayWidth, displayHeight];
|
|
5451
|
+
const [x1, y1, x2, y2] = region;
|
|
5452
|
+
const tmpPath = join8(tmpdir(), `cu-zoom-${nanoid4(8)}.png`);
|
|
5453
|
+
await runScreencapture(tmpPath);
|
|
5454
|
+
const sharpModule = await import("sharp");
|
|
5455
|
+
const sharp2 = sharpModule.default || sharpModule;
|
|
5456
|
+
const meta = await sharp2(tmpPath).metadata();
|
|
5457
|
+
const scaleX = (meta.width || displayWidth) / displayWidth;
|
|
5458
|
+
const scaleY = (meta.height || displayHeight) / displayHeight;
|
|
5459
|
+
const px = {
|
|
5460
|
+
left: Math.max(0, Math.round(x1 * scaleX)),
|
|
5461
|
+
top: Math.max(0, Math.round(y1 * scaleY)),
|
|
5462
|
+
width: Math.max(1, Math.round((x2 - x1) * scaleX)),
|
|
5463
|
+
height: Math.max(1, Math.round((y2 - y1) * scaleY))
|
|
5464
|
+
};
|
|
5465
|
+
const buf = await sharp2(tmpPath).extract(px).png().toBuffer();
|
|
5466
|
+
try {
|
|
5467
|
+
unlinkSync2(tmpPath);
|
|
5468
|
+
} catch {
|
|
5469
|
+
}
|
|
5470
|
+
return { type: "image", data: buf.toString("base64") };
|
|
5471
|
+
}
|
|
5472
|
+
default: {
|
|
5473
|
+
const exhaustive = input.action;
|
|
5474
|
+
return `unsupported action: ${String(exhaustive)}`;
|
|
5475
|
+
}
|
|
5476
|
+
}
|
|
5477
|
+
} catch (err) {
|
|
5478
|
+
const msg = err?.message || String(err);
|
|
5479
|
+
let hint = "";
|
|
5480
|
+
if (/accessibility|not authorized|tcc|operation not permitted/i.test(msg)) {
|
|
5481
|
+
hint = " (Hint: call enable_computer_use to (re-)check permissions and open System Settings)";
|
|
5482
|
+
} else if (/command not found/i.test(msg)) {
|
|
5483
|
+
hint = " (Hint: install cliclick with `brew install cliclick`)";
|
|
5484
|
+
}
|
|
5485
|
+
return `Error: ${msg}${hint}`;
|
|
5486
|
+
}
|
|
5487
|
+
},
|
|
5488
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5489
|
+
toModelOutput({ output }) {
|
|
5490
|
+
if (typeof output === "string") {
|
|
5491
|
+
return { type: "content", value: [{ type: "text", text: output }] };
|
|
5492
|
+
}
|
|
5493
|
+
return {
|
|
5494
|
+
type: "content",
|
|
5495
|
+
value: [{ type: "media", data: output.data, mediaType: "image/png" }]
|
|
5496
|
+
};
|
|
5497
|
+
}
|
|
5498
|
+
});
|
|
5499
|
+
}
|
|
5500
|
+
|
|
5501
|
+
// src/tools/enable-computer-use.ts
|
|
5502
|
+
init_db();
|
|
5503
|
+
import { tool as tool13 } from "ai";
|
|
5504
|
+
import { z as z14 } from "zod";
|
|
5505
|
+
var inputSchema = z14.object({
|
|
5506
|
+
display_width: z14.number().int().positive().optional().describe("Display width in pixels (defaults to detected primary display, fallback 1280)"),
|
|
5507
|
+
display_height: z14.number().int().positive().optional().describe("Display height in pixels (defaults to detected primary display, fallback 800)"),
|
|
5508
|
+
request_permissions: z14.boolean().optional().default(true).describe(
|
|
5509
|
+
"When true (default), proactively trigger macOS permission prompts and open System Settings panes for any missing permissions."
|
|
5510
|
+
)
|
|
5511
|
+
});
|
|
5512
|
+
var ACCESSIBILITY_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
|
|
5513
|
+
var SCREEN_RECORDING_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
|
|
5514
|
+
function createEnableComputerUseTool(options) {
|
|
5515
|
+
return tool13({
|
|
5516
|
+
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.",
|
|
5517
|
+
inputSchema,
|
|
5518
|
+
execute: async ({ display_width, display_height, request_permissions }) => {
|
|
5519
|
+
try {
|
|
5520
|
+
if (!isMacOs()) {
|
|
5521
|
+
return {
|
|
5522
|
+
success: false,
|
|
5523
|
+
error: "Computer use is currently only supported on macOS.",
|
|
5524
|
+
platform: process.platform
|
|
5525
|
+
};
|
|
5526
|
+
}
|
|
5527
|
+
if (!await isCliclickInstalled()) {
|
|
5528
|
+
return {
|
|
5529
|
+
success: false,
|
|
5530
|
+
error: "`cliclick` is not installed. It is required for mouse/keyboard control on macOS.",
|
|
5531
|
+
installCommand: "brew install cliclick",
|
|
5532
|
+
fixSteps: [
|
|
5533
|
+
"In a terminal on this Mac, run: brew install cliclick",
|
|
5534
|
+
"(If Homebrew is not installed, install it first from https://brew.sh)",
|
|
5535
|
+
"Then call enable_computer_use again"
|
|
5536
|
+
]
|
|
5537
|
+
};
|
|
5538
|
+
}
|
|
5539
|
+
const acc = await hasAccessibilityPermissions();
|
|
5540
|
+
const screen = await hasScreenRecordingPermissions();
|
|
5541
|
+
const missing = [];
|
|
5542
|
+
if (!acc.ok) {
|
|
5543
|
+
let prompted = false;
|
|
5544
|
+
let panelOpened = false;
|
|
5545
|
+
if (request_permissions) {
|
|
5546
|
+
prompted = await requestAccessibilityPrompt().then(() => true).catch(() => false);
|
|
5547
|
+
await openSystemSettings("accessibility").then(() => {
|
|
5548
|
+
panelOpened = true;
|
|
5549
|
+
}).catch(() => void 0);
|
|
5550
|
+
}
|
|
5551
|
+
missing.push({
|
|
5552
|
+
name: "Accessibility",
|
|
5553
|
+
reason: "cliclick failed: " + (acc.error?.split("\n")[0] || "no permission"),
|
|
5554
|
+
pane: "accessibility",
|
|
5555
|
+
settingsUrl: ACCESSIBILITY_URL,
|
|
5556
|
+
fixSteps: [
|
|
5557
|
+
"In the System Settings \u2192 Privacy & Security \u2192 Accessibility pane that opened",
|
|
5558
|
+
"Click the + button",
|
|
5559
|
+
"Add the application running the agent (Terminal, iTerm, your IDE, or `node`)",
|
|
5560
|
+
"Toggle the switch ON",
|
|
5561
|
+
"Restart the agent process so the new permission takes effect",
|
|
5562
|
+
"Then call enable_computer_use again"
|
|
5563
|
+
],
|
|
5564
|
+
prompted,
|
|
5565
|
+
panelOpened
|
|
5566
|
+
});
|
|
5567
|
+
}
|
|
5568
|
+
if (!screen) {
|
|
5569
|
+
let prompted = false;
|
|
5570
|
+
let panelOpened = false;
|
|
5571
|
+
if (request_permissions) {
|
|
5572
|
+
prompted = await requestScreenRecordingPrompt().then(() => true).catch(() => false);
|
|
5573
|
+
await openSystemSettings("screen-recording").then(() => {
|
|
5574
|
+
panelOpened = true;
|
|
5575
|
+
}).catch(() => void 0);
|
|
5576
|
+
}
|
|
5577
|
+
missing.push({
|
|
5578
|
+
name: "Screen Recording",
|
|
5579
|
+
reason: "CGPreflightScreenCaptureAccess returned false",
|
|
5580
|
+
pane: "screen-recording",
|
|
5581
|
+
settingsUrl: SCREEN_RECORDING_URL,
|
|
5582
|
+
fixSteps: [
|
|
5583
|
+
"In the System Settings \u2192 Privacy & Security \u2192 Screen Recording pane that opened",
|
|
5584
|
+
"Click the + button",
|
|
5585
|
+
"Add the application running the agent (Terminal, iTerm, your IDE, or `node`)",
|
|
5586
|
+
"Toggle the switch ON",
|
|
5587
|
+
"Restart the agent process so the new permission takes effect",
|
|
5588
|
+
"Then call enable_computer_use again"
|
|
5589
|
+
],
|
|
5590
|
+
prompted,
|
|
5591
|
+
panelOpened
|
|
5592
|
+
});
|
|
5593
|
+
}
|
|
5594
|
+
if (missing.length > 0) {
|
|
5595
|
+
return {
|
|
5596
|
+
success: false,
|
|
5597
|
+
error: `Missing permission${missing.length > 1 ? "s" : ""}: ` + missing.map((m) => m.name).join(" and ") + ".",
|
|
5598
|
+
missingPermissions: missing,
|
|
5599
|
+
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."
|
|
5600
|
+
};
|
|
5601
|
+
}
|
|
5602
|
+
let width = display_width;
|
|
5603
|
+
let height = display_height;
|
|
5604
|
+
let detected = null;
|
|
5605
|
+
if (width === void 0 || height === void 0) {
|
|
5606
|
+
detected = await detectScreenSize();
|
|
5607
|
+
width = width ?? detected?.width ?? 1280;
|
|
5608
|
+
height = height ?? detected?.height ?? 800;
|
|
5609
|
+
}
|
|
5610
|
+
const session = await sessionQueries.getById(options.sessionId);
|
|
5611
|
+
if (!session) {
|
|
5612
|
+
return { success: false, error: "Session not found" };
|
|
5613
|
+
}
|
|
5614
|
+
const config = session.config || {};
|
|
5615
|
+
if (config.computerUseEnabled === true && config.computerUseDisplayWidth === width && config.computerUseDisplayHeight === height) {
|
|
5616
|
+
return {
|
|
5617
|
+
success: true,
|
|
5618
|
+
alreadyEnabled: true,
|
|
5619
|
+
message: "Computer use was already enabled for this session.",
|
|
5620
|
+
displayWidth: width,
|
|
5621
|
+
displayHeight: height
|
|
5622
|
+
};
|
|
5623
|
+
}
|
|
5624
|
+
const updated = {
|
|
5625
|
+
...config,
|
|
5626
|
+
computerUseEnabled: true,
|
|
5627
|
+
computerUseDisplayWidth: width,
|
|
5628
|
+
computerUseDisplayHeight: height
|
|
5629
|
+
};
|
|
5630
|
+
await sessionQueries.update(options.sessionId, { config: updated });
|
|
5631
|
+
return {
|
|
5632
|
+
success: true,
|
|
5633
|
+
enabled: true,
|
|
5634
|
+
platform: "darwin",
|
|
5635
|
+
displayWidth: width,
|
|
5636
|
+
displayHeight: height,
|
|
5637
|
+
detectedScreenSize: detected || void 0,
|
|
5638
|
+
permissions: {
|
|
5639
|
+
accessibility: "granted",
|
|
5640
|
+
screenRecording: "granted"
|
|
5641
|
+
},
|
|
5642
|
+
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.`
|
|
5643
|
+
};
|
|
5644
|
+
} catch (err) {
|
|
5645
|
+
return {
|
|
5646
|
+
success: false,
|
|
5647
|
+
error: err?.message || String(err)
|
|
5648
|
+
};
|
|
5649
|
+
}
|
|
5650
|
+
}
|
|
5651
|
+
});
|
|
5652
|
+
}
|
|
5653
|
+
|
|
5044
5654
|
// src/tools/index.ts
|
|
5045
5655
|
init_semantic();
|
|
5046
5656
|
init_remote();
|
|
@@ -5088,6 +5698,20 @@ async function createTools(options) {
|
|
|
5088
5698
|
sessionId: options.sessionId
|
|
5089
5699
|
});
|
|
5090
5700
|
}
|
|
5701
|
+
if (process.platform === "darwin") {
|
|
5702
|
+
if (options.enableComputerUse) {
|
|
5703
|
+
tools.computer = createComputerUseTool({
|
|
5704
|
+
workingDirectory: options.workingDirectory,
|
|
5705
|
+
sessionId: options.sessionId,
|
|
5706
|
+
displayWidth: options.computerUseDisplayWidth,
|
|
5707
|
+
displayHeight: options.computerUseDisplayHeight
|
|
5708
|
+
});
|
|
5709
|
+
} else {
|
|
5710
|
+
tools.enable_computer_use = createEnableComputerUseTool({
|
|
5711
|
+
sessionId: options.sessionId
|
|
5712
|
+
});
|
|
5713
|
+
}
|
|
5714
|
+
}
|
|
5091
5715
|
if (options.enableSemanticSearch !== false) {
|
|
5092
5716
|
try {
|
|
5093
5717
|
if (isVectorGatewayConfigured()) {
|
|
@@ -5104,13 +5728,17 @@ async function createTools(options) {
|
|
|
5104
5728
|
if (options.taskTools) {
|
|
5105
5729
|
tools.complete_task = createCompleteTaskTool(options.taskTools);
|
|
5106
5730
|
tools.task_failed = createTaskFailedTool(options.taskTools);
|
|
5731
|
+
tools.ask_question_to_user = createAskQuestionToUserTool(options.taskTools);
|
|
5107
5732
|
}
|
|
5108
5733
|
return tools;
|
|
5109
5734
|
}
|
|
5110
5735
|
export {
|
|
5736
|
+
createAskQuestionToUserTool,
|
|
5111
5737
|
createBashTool,
|
|
5112
5738
|
createCodeGraphTool,
|
|
5113
5739
|
createCompleteTaskTool,
|
|
5740
|
+
createComputerUseTool,
|
|
5741
|
+
createEnableComputerUseTool,
|
|
5114
5742
|
createLinterTool,
|
|
5115
5743
|
createLoadSkillTool,
|
|
5116
5744
|
createReadFileTool,
|