fourmis-agents-sdk 0.3.1 → 0.4.1
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 +126 -198
- package/dist/agent-loop.d.ts +21 -3
- package/dist/agent-loop.d.ts.map +1 -1
- package/dist/agent-loop.js +279 -90
- package/dist/agents/index.js +1079 -124
- package/dist/agents/tools.d.ts.map +1 -1
- package/dist/agents/tools.js +1079 -124
- package/dist/agents/types.d.ts +4 -0
- package/dist/agents/types.d.ts.map +1 -1
- package/dist/api.d.ts +8 -5
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +1663 -430
- package/dist/hooks.d.ts +19 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +27 -2
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1671 -431
- package/dist/mcp/client.d.ts +8 -1
- package/dist/mcp/client.d.ts.map +1 -1
- package/dist/mcp/client.js +134 -13
- package/dist/mcp/index.js +134 -13
- package/dist/mcp/types.d.ts +21 -1
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +7 -3
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +41 -2
- package/dist/providers/openai.d.ts +6 -0
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +36 -6
- package/dist/providers/registry.js +76 -8
- package/dist/providers/types.d.ts +4 -1
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/query.d.ts +21 -2
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +69 -1
- package/dist/skills/index.js +23 -1
- package/dist/skills/skills.d.ts +16 -0
- package/dist/skills/skills.d.ts.map +1 -1
- package/dist/skills/skills.js +23 -1
- package/dist/tools/ask-user-question.d.ts +7 -0
- package/dist/tools/ask-user-question.d.ts.map +1 -0
- package/dist/tools/ask-user-question.js +48 -0
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +47 -2
- package/dist/tools/config.d.ts +7 -0
- package/dist/tools/config.d.ts.map +1 -0
- package/dist/tools/config.js +114 -0
- package/dist/tools/exit-plan-mode.d.ts +7 -0
- package/dist/tools/exit-plan-mode.d.ts.map +1 -0
- package/dist/tools/exit-plan-mode.js +34 -0
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +506 -9
- package/dist/tools/notebook-edit.d.ts +7 -0
- package/dist/tools/notebook-edit.d.ts.map +1 -0
- package/dist/tools/notebook-edit.js +83 -0
- package/dist/tools/presets.d.ts +2 -1
- package/dist/tools/presets.d.ts.map +1 -1
- package/dist/tools/presets.js +22 -4
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +12 -1
- package/dist/tools/registry.d.ts +2 -0
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +10 -0
- package/dist/tools/todo-write.d.ts +7 -0
- package/dist/tools/todo-write.d.ts.map +1 -0
- package/dist/tools/todo-write.js +69 -0
- package/dist/tools/web-fetch.d.ts +6 -0
- package/dist/tools/web-fetch.d.ts.map +1 -0
- package/dist/tools/web-fetch.js +85 -0
- package/dist/tools/web-search.d.ts +7 -0
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +78 -0
- package/dist/types.d.ts +344 -42
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/session-store.d.ts +1 -1
- package/dist/utils/session-store.d.ts.map +1 -1
- package/dist/utils/session-store.js +49 -2
- package/dist/utils/system-prompt.d.ts +2 -0
- package/dist/utils/system-prompt.d.ts.map +1 -1
- package/dist/utils/system-prompt.js +33 -4
- package/package.json +3 -2
package/dist/tools/index.js
CHANGED
|
@@ -18,6 +18,16 @@ class ToolRegistry {
|
|
|
18
18
|
register(tool) {
|
|
19
19
|
this.tools.set(tool.name, tool);
|
|
20
20
|
}
|
|
21
|
+
unregister(name) {
|
|
22
|
+
this.tools.delete(name);
|
|
23
|
+
}
|
|
24
|
+
clearByPrefix(prefix) {
|
|
25
|
+
for (const name of this.tools.keys()) {
|
|
26
|
+
if (name.startsWith(prefix)) {
|
|
27
|
+
this.tools.delete(name);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
21
31
|
get(name) {
|
|
22
32
|
return this.tools.get(name);
|
|
23
33
|
}
|
|
@@ -51,16 +61,34 @@ class ToolRegistry {
|
|
|
51
61
|
// src/tools/presets.ts
|
|
52
62
|
var PRESETS = {
|
|
53
63
|
coding: ["Bash", "Read", "Write", "Edit", "Glob", "Grep"],
|
|
64
|
+
claude_code: [
|
|
65
|
+
"Bash",
|
|
66
|
+
"Read",
|
|
67
|
+
"Write",
|
|
68
|
+
"Edit",
|
|
69
|
+
"Glob",
|
|
70
|
+
"Grep",
|
|
71
|
+
"NotebookEdit",
|
|
72
|
+
"WebFetch",
|
|
73
|
+
"WebSearch",
|
|
74
|
+
"TodoWrite",
|
|
75
|
+
"Config",
|
|
76
|
+
"AskUserQuestion",
|
|
77
|
+
"ExitPlanMode"
|
|
78
|
+
],
|
|
54
79
|
readonly: ["Read", "Glob", "Grep"],
|
|
55
80
|
minimal: ["Read", "Write", "Edit", "Glob", "Grep"]
|
|
56
81
|
};
|
|
57
82
|
function resolveToolNames(tools) {
|
|
58
83
|
if (!tools)
|
|
59
|
-
return PRESETS.
|
|
60
|
-
if (
|
|
61
|
-
return
|
|
84
|
+
return PRESETS.claude_code;
|
|
85
|
+
if (Array.isArray(tools)) {
|
|
86
|
+
return tools;
|
|
87
|
+
}
|
|
88
|
+
if (tools.type === "preset") {
|
|
89
|
+
return PRESETS[tools.preset] ?? PRESETS.claude_code;
|
|
62
90
|
}
|
|
63
|
-
|
|
91
|
+
throw new Error("Invalid tools option. Expected string[] or { type: 'preset', preset: 'claude_code' }.");
|
|
64
92
|
}
|
|
65
93
|
|
|
66
94
|
// src/tools/bash.ts
|
|
@@ -84,17 +112,58 @@ var BashTool = {
|
|
|
84
112
|
timeout: {
|
|
85
113
|
type: "number",
|
|
86
114
|
description: "Timeout in milliseconds (max 600000)"
|
|
115
|
+
},
|
|
116
|
+
run_in_background: {
|
|
117
|
+
type: "boolean",
|
|
118
|
+
description: "Run command asynchronously and return immediately."
|
|
119
|
+
},
|
|
120
|
+
dangerouslyDisableSandbox: {
|
|
121
|
+
type: "boolean",
|
|
122
|
+
description: "If true, explicitly request unsandboxed execution."
|
|
123
|
+
},
|
|
124
|
+
_simulatedSedEdit: {
|
|
125
|
+
type: "object",
|
|
126
|
+
properties: {
|
|
127
|
+
filePath: { type: "string" },
|
|
128
|
+
newContent: { type: "string" }
|
|
129
|
+
},
|
|
130
|
+
description: "Internal field for precomputed edit previews."
|
|
87
131
|
}
|
|
88
132
|
},
|
|
89
133
|
required: ["command"]
|
|
90
134
|
},
|
|
91
135
|
async execute(input, ctx) {
|
|
92
|
-
const {
|
|
136
|
+
const {
|
|
137
|
+
command,
|
|
138
|
+
timeout: timeoutMs,
|
|
139
|
+
run_in_background,
|
|
140
|
+
description,
|
|
141
|
+
dangerouslyDisableSandbox,
|
|
142
|
+
_simulatedSedEdit
|
|
143
|
+
} = input;
|
|
93
144
|
if (!command || typeof command !== "string") {
|
|
94
145
|
return { content: "Error: command is required", isError: true };
|
|
95
146
|
}
|
|
96
147
|
const timeout = Math.min(timeoutMs ?? DEFAULT_TIMEOUT, MAX_TIMEOUT);
|
|
97
148
|
try {
|
|
149
|
+
if (run_in_background) {
|
|
150
|
+
const proc2 = Bun.spawn(["bash", "-c", command], {
|
|
151
|
+
cwd: ctx.cwd,
|
|
152
|
+
stdout: "ignore",
|
|
153
|
+
stderr: "ignore",
|
|
154
|
+
stdin: "ignore",
|
|
155
|
+
env: { ...process.env, ...ctx.env }
|
|
156
|
+
});
|
|
157
|
+
return {
|
|
158
|
+
content: `Background command started (pid ${proc2.pid ?? "unknown"}).`,
|
|
159
|
+
metadata: {
|
|
160
|
+
pid: proc2.pid ?? null,
|
|
161
|
+
run_in_background: true,
|
|
162
|
+
dangerouslyDisableSandbox: dangerouslyDisableSandbox === true,
|
|
163
|
+
hasSimulatedSedEdit: !!_simulatedSedEdit
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
98
167
|
const proc = Bun.spawn(["bash", "-c", command], {
|
|
99
168
|
cwd: ctx.cwd,
|
|
100
169
|
stdout: "pipe",
|
|
@@ -126,7 +195,11 @@ var BashTool = {
|
|
|
126
195
|
return {
|
|
127
196
|
content: output,
|
|
128
197
|
isError: exitCode !== 0 ? true : undefined,
|
|
129
|
-
metadata: {
|
|
198
|
+
metadata: {
|
|
199
|
+
exitCode,
|
|
200
|
+
dangerouslyDisableSandbox: dangerouslyDisableSandbox === true,
|
|
201
|
+
hasSimulatedSedEdit: !!_simulatedSedEdit
|
|
202
|
+
}
|
|
130
203
|
};
|
|
131
204
|
} catch (err) {
|
|
132
205
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -155,12 +228,17 @@ var ReadTool = {
|
|
|
155
228
|
limit: {
|
|
156
229
|
type: "number",
|
|
157
230
|
description: "Number of lines to read"
|
|
231
|
+
},
|
|
232
|
+
pages: {
|
|
233
|
+
type: "array",
|
|
234
|
+
items: { type: "number" },
|
|
235
|
+
description: "Optional PDF page numbers (1-based)."
|
|
158
236
|
}
|
|
159
237
|
},
|
|
160
238
|
required: ["file_path"]
|
|
161
239
|
},
|
|
162
240
|
async execute(input, ctx) {
|
|
163
|
-
const { file_path, offset, limit } = input;
|
|
241
|
+
const { file_path, offset, limit, pages } = input;
|
|
164
242
|
if (!file_path) {
|
|
165
243
|
return { content: "Error: file_path is required", isError: true };
|
|
166
244
|
}
|
|
@@ -171,6 +249,12 @@ var ReadTool = {
|
|
|
171
249
|
if (!exists) {
|
|
172
250
|
return { content: `Error: File not found: ${resolvedPath}`, isError: true };
|
|
173
251
|
}
|
|
252
|
+
if (Array.isArray(pages) && pages.length > 0 && resolvedPath.toLowerCase().endsWith(".pdf")) {
|
|
253
|
+
return {
|
|
254
|
+
content: "Error: PDF page extraction is not implemented in this runtime.",
|
|
255
|
+
isError: true
|
|
256
|
+
};
|
|
257
|
+
}
|
|
174
258
|
const text = await file.text();
|
|
175
259
|
const lines = text.split(`
|
|
176
260
|
`);
|
|
@@ -558,6 +642,405 @@ async function collectFiles(dir, globPattern) {
|
|
|
558
642
|
}
|
|
559
643
|
return files;
|
|
560
644
|
}
|
|
645
|
+
|
|
646
|
+
// src/tools/notebook-edit.ts
|
|
647
|
+
import { readFile, writeFile } from "fs/promises";
|
|
648
|
+
function toSourceLines(text) {
|
|
649
|
+
const lines = text.split(`
|
|
650
|
+
`);
|
|
651
|
+
return lines.map((line, idx) => idx < lines.length - 1 ? `${line}
|
|
652
|
+
` : line);
|
|
653
|
+
}
|
|
654
|
+
var NotebookEditTool = {
|
|
655
|
+
name: "NotebookEdit",
|
|
656
|
+
description: "Edit a specific Jupyter notebook cell by id or index.",
|
|
657
|
+
inputSchema: {
|
|
658
|
+
type: "object",
|
|
659
|
+
properties: {
|
|
660
|
+
notebook_path: { type: "string", description: "Path to .ipynb file." },
|
|
661
|
+
cell_id: { type: "string", description: "Cell id to edit." },
|
|
662
|
+
cell_index: { type: "number", description: "Cell index to edit if id is not provided." },
|
|
663
|
+
new_source: { type: "string", description: "New cell source content." }
|
|
664
|
+
},
|
|
665
|
+
required: ["notebook_path", "new_source"]
|
|
666
|
+
},
|
|
667
|
+
async execute(input, ctx) {
|
|
668
|
+
const {
|
|
669
|
+
notebook_path,
|
|
670
|
+
cell_id,
|
|
671
|
+
cell_index,
|
|
672
|
+
new_source
|
|
673
|
+
} = input ?? {};
|
|
674
|
+
if (!notebook_path)
|
|
675
|
+
return { content: "Error: notebook_path is required", isError: true };
|
|
676
|
+
if (new_source === undefined)
|
|
677
|
+
return { content: "Error: new_source is required", isError: true };
|
|
678
|
+
const filePath = notebook_path.startsWith("/") ? notebook_path : `${ctx.cwd}/${notebook_path}`;
|
|
679
|
+
try {
|
|
680
|
+
const raw = await readFile(filePath, "utf-8");
|
|
681
|
+
const notebook = JSON.parse(raw);
|
|
682
|
+
if (!Array.isArray(notebook.cells)) {
|
|
683
|
+
return { content: "Error: notebook has no cells array", isError: true };
|
|
684
|
+
}
|
|
685
|
+
let targetIndex = -1;
|
|
686
|
+
if (cell_id) {
|
|
687
|
+
targetIndex = notebook.cells.findIndex((c) => c.id === cell_id);
|
|
688
|
+
} else if (typeof cell_index === "number") {
|
|
689
|
+
targetIndex = cell_index;
|
|
690
|
+
} else {
|
|
691
|
+
targetIndex = 0;
|
|
692
|
+
}
|
|
693
|
+
if (targetIndex < 0 || targetIndex >= notebook.cells.length) {
|
|
694
|
+
return {
|
|
695
|
+
content: `Error: cell not found (id=${cell_id ?? "n/a"}, index=${String(cell_index ?? "n/a")})`,
|
|
696
|
+
isError: true
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
const cell = notebook.cells[targetIndex];
|
|
700
|
+
cell.source = toSourceLines(new_source);
|
|
701
|
+
await writeFile(filePath, JSON.stringify(notebook, null, 2) + `
|
|
702
|
+
`, "utf-8");
|
|
703
|
+
return {
|
|
704
|
+
content: `Updated notebook cell ${targetIndex} in ${filePath}`
|
|
705
|
+
};
|
|
706
|
+
} catch (err) {
|
|
707
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
708
|
+
return { content: `Error editing notebook: ${message}`, isError: true };
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
// src/tools/web-fetch.ts
|
|
714
|
+
var DEFAULT_TIMEOUT_MS = 20000;
|
|
715
|
+
var MAX_OUTPUT = 80000;
|
|
716
|
+
var WebFetchTool = {
|
|
717
|
+
name: "WebFetch",
|
|
718
|
+
description: "Fetches a URL and returns response text.",
|
|
719
|
+
inputSchema: {
|
|
720
|
+
type: "object",
|
|
721
|
+
properties: {
|
|
722
|
+
url: {
|
|
723
|
+
type: "string",
|
|
724
|
+
description: "The URL to fetch."
|
|
725
|
+
},
|
|
726
|
+
prompt: {
|
|
727
|
+
type: "string",
|
|
728
|
+
description: "Optional fetch intent/instructions."
|
|
729
|
+
},
|
|
730
|
+
timeout_ms: {
|
|
731
|
+
type: "number",
|
|
732
|
+
description: "Timeout in milliseconds (default 20000)."
|
|
733
|
+
},
|
|
734
|
+
max_length: {
|
|
735
|
+
type: "number",
|
|
736
|
+
description: "Maximum output length (default 80000)."
|
|
737
|
+
}
|
|
738
|
+
},
|
|
739
|
+
required: ["url"]
|
|
740
|
+
},
|
|
741
|
+
async execute(input) {
|
|
742
|
+
const { url, timeout_ms, max_length } = input ?? {};
|
|
743
|
+
if (!url)
|
|
744
|
+
return { content: "Error: url is required", isError: true };
|
|
745
|
+
const timeout = Math.max(1000, timeout_ms ?? DEFAULT_TIMEOUT_MS);
|
|
746
|
+
const outLimit = Math.max(1000, max_length ?? MAX_OUTPUT);
|
|
747
|
+
const controller = new AbortController;
|
|
748
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
749
|
+
try {
|
|
750
|
+
const res = await fetch(url, {
|
|
751
|
+
method: "GET",
|
|
752
|
+
signal: controller.signal,
|
|
753
|
+
headers: {
|
|
754
|
+
"user-agent": "fourmis-agent-sdk/1.0"
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
const contentType = res.headers.get("content-type") ?? "unknown";
|
|
758
|
+
let body = await res.text();
|
|
759
|
+
if (body.length > outLimit) {
|
|
760
|
+
body = body.slice(0, outLimit) + `
|
|
761
|
+
... (truncated)`;
|
|
762
|
+
}
|
|
763
|
+
return {
|
|
764
|
+
content: [
|
|
765
|
+
`Status: ${res.status} ${res.statusText}`,
|
|
766
|
+
`Content-Type: ${contentType}`,
|
|
767
|
+
"",
|
|
768
|
+
body
|
|
769
|
+
].join(`
|
|
770
|
+
`),
|
|
771
|
+
isError: res.ok ? undefined : true
|
|
772
|
+
};
|
|
773
|
+
} catch (err) {
|
|
774
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
775
|
+
return { content: `Error fetching URL: ${message}`, isError: true };
|
|
776
|
+
} finally {
|
|
777
|
+
clearTimeout(timer);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
// src/tools/web-search.ts
|
|
783
|
+
var SEARCH_ENDPOINT = "https://duckduckgo.com/html/";
|
|
784
|
+
function stripTags(input) {
|
|
785
|
+
return input.replace(/<[^>]+>/g, "").replace(/&/g, "&").replace(/"/g, '"').replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">").trim();
|
|
786
|
+
}
|
|
787
|
+
var WebSearchTool = {
|
|
788
|
+
name: "WebSearch",
|
|
789
|
+
description: "Searches the web and returns top result links.",
|
|
790
|
+
inputSchema: {
|
|
791
|
+
type: "object",
|
|
792
|
+
properties: {
|
|
793
|
+
query: {
|
|
794
|
+
type: "string",
|
|
795
|
+
description: "Search query."
|
|
796
|
+
},
|
|
797
|
+
max_results: {
|
|
798
|
+
type: "number",
|
|
799
|
+
description: "Maximum results to return (default 5)."
|
|
800
|
+
}
|
|
801
|
+
},
|
|
802
|
+
required: ["query"]
|
|
803
|
+
},
|
|
804
|
+
async execute(input) {
|
|
805
|
+
const { query, max_results } = input ?? {};
|
|
806
|
+
if (!query) {
|
|
807
|
+
return { content: "Error: query is required", isError: true };
|
|
808
|
+
}
|
|
809
|
+
const limit = Math.max(1, Math.min(20, max_results ?? 5));
|
|
810
|
+
try {
|
|
811
|
+
const url = `${SEARCH_ENDPOINT}?q=${encodeURIComponent(query)}`;
|
|
812
|
+
const res = await fetch(url, {
|
|
813
|
+
headers: {
|
|
814
|
+
"user-agent": "fourmis-agent-sdk/1.0"
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
if (!res.ok) {
|
|
818
|
+
return {
|
|
819
|
+
content: `Error searching web: ${res.status} ${res.statusText}`,
|
|
820
|
+
isError: true
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
const html = await res.text();
|
|
824
|
+
const matches = [...html.matchAll(/<a[^>]*class="result__a"[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/g)];
|
|
825
|
+
if (matches.length === 0) {
|
|
826
|
+
return { content: "No search results found." };
|
|
827
|
+
}
|
|
828
|
+
const lines = [];
|
|
829
|
+
for (let i = 0;i < Math.min(limit, matches.length); i++) {
|
|
830
|
+
const href = stripTags(matches[i][1]);
|
|
831
|
+
const title = stripTags(matches[i][2]);
|
|
832
|
+
lines.push(`${i + 1}. ${title}
|
|
833
|
+
${href}`);
|
|
834
|
+
}
|
|
835
|
+
return { content: lines.join(`
|
|
836
|
+
`) };
|
|
837
|
+
} catch (err) {
|
|
838
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
839
|
+
return { content: `Error searching web: ${message}`, isError: true };
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
// src/tools/ask-user-question.ts
|
|
845
|
+
var AskUserQuestionTool = {
|
|
846
|
+
name: "AskUserQuestion",
|
|
847
|
+
description: "Ask the user a clarifying question and wait for their response.",
|
|
848
|
+
inputSchema: {
|
|
849
|
+
type: "object",
|
|
850
|
+
properties: {
|
|
851
|
+
question: {
|
|
852
|
+
type: "string",
|
|
853
|
+
description: "Question to ask the user."
|
|
854
|
+
},
|
|
855
|
+
options: {
|
|
856
|
+
type: "array",
|
|
857
|
+
items: { type: "string" },
|
|
858
|
+
description: "Optional fixed choices."
|
|
859
|
+
}
|
|
860
|
+
},
|
|
861
|
+
required: ["question"]
|
|
862
|
+
},
|
|
863
|
+
async execute(input) {
|
|
864
|
+
const { question, options } = input ?? {};
|
|
865
|
+
if (!question) {
|
|
866
|
+
return { content: "Error: question is required", isError: true };
|
|
867
|
+
}
|
|
868
|
+
const choices = Array.isArray(options) && options.length > 0 ? ` Choices: ${options.join(" | ")}` : "";
|
|
869
|
+
return {
|
|
870
|
+
content: `User interaction is not available in this runtime. Unanswered question: ${question}.${choices}`,
|
|
871
|
+
isError: true
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
|
|
876
|
+
// src/tools/todo-write.ts
|
|
877
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
878
|
+
import { dirname as dirname2, join } from "path";
|
|
879
|
+
var TodoWriteTool = {
|
|
880
|
+
name: "TodoWrite",
|
|
881
|
+
description: "Write/update task todo items for the current session.",
|
|
882
|
+
inputSchema: {
|
|
883
|
+
type: "object",
|
|
884
|
+
properties: {
|
|
885
|
+
todos: {
|
|
886
|
+
type: "array",
|
|
887
|
+
items: {
|
|
888
|
+
type: "object",
|
|
889
|
+
properties: {
|
|
890
|
+
content: { type: "string" },
|
|
891
|
+
status: { type: "string", enum: ["pending", "in_progress", "completed"] },
|
|
892
|
+
activeForm: { type: "string" }
|
|
893
|
+
},
|
|
894
|
+
required: ["content", "status"]
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
},
|
|
898
|
+
required: ["todos"]
|
|
899
|
+
},
|
|
900
|
+
async execute(input, ctx) {
|
|
901
|
+
const { todos } = input ?? {};
|
|
902
|
+
if (!Array.isArray(todos)) {
|
|
903
|
+
return { content: "Error: todos must be an array", isError: true };
|
|
904
|
+
}
|
|
905
|
+
for (const todo of todos) {
|
|
906
|
+
if (!todo?.content || !todo?.status) {
|
|
907
|
+
return { content: "Error: each todo requires content and status", isError: true };
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
const filePath = join(ctx.cwd, ".claude", "todos.json");
|
|
911
|
+
try {
|
|
912
|
+
await mkdir2(dirname2(filePath), { recursive: true });
|
|
913
|
+
const payload = {
|
|
914
|
+
updatedAt: new Date().toISOString(),
|
|
915
|
+
todos
|
|
916
|
+
};
|
|
917
|
+
await writeFile2(filePath, JSON.stringify(payload, null, 2) + `
|
|
918
|
+
`, "utf-8");
|
|
919
|
+
return {
|
|
920
|
+
content: `Saved ${todos.length} todo item(s) to ${filePath}`
|
|
921
|
+
};
|
|
922
|
+
} catch (err) {
|
|
923
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
924
|
+
return { content: `Error writing todos: ${message}`, isError: true };
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
|
|
929
|
+
// src/tools/config.ts
|
|
930
|
+
import { mkdir as mkdir3, readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
|
|
931
|
+
import { join as join2, dirname as dirname3 } from "path";
|
|
932
|
+
function scopePath(cwd, scope) {
|
|
933
|
+
if (scope === "project")
|
|
934
|
+
return join2(cwd, ".claude", "settings.json");
|
|
935
|
+
return join2(cwd, ".claude", "settings.local.json");
|
|
936
|
+
}
|
|
937
|
+
function setByPath(obj, keyPath, value) {
|
|
938
|
+
const keys = keyPath.split(".").filter(Boolean);
|
|
939
|
+
if (keys.length === 0)
|
|
940
|
+
return;
|
|
941
|
+
let current = obj;
|
|
942
|
+
for (let i = 0;i < keys.length - 1; i++) {
|
|
943
|
+
const key = keys[i];
|
|
944
|
+
const next = current[key];
|
|
945
|
+
if (!next || typeof next !== "object" || Array.isArray(next)) {
|
|
946
|
+
current[key] = {};
|
|
947
|
+
}
|
|
948
|
+
current = current[key];
|
|
949
|
+
}
|
|
950
|
+
current[keys[keys.length - 1]] = value;
|
|
951
|
+
}
|
|
952
|
+
function getByPath(obj, keyPath) {
|
|
953
|
+
const keys = keyPath.split(".").filter(Boolean);
|
|
954
|
+
let current = obj;
|
|
955
|
+
for (const key of keys) {
|
|
956
|
+
if (!current || typeof current !== "object" || Array.isArray(current))
|
|
957
|
+
return;
|
|
958
|
+
current = current[key];
|
|
959
|
+
}
|
|
960
|
+
return current;
|
|
961
|
+
}
|
|
962
|
+
var ConfigTool = {
|
|
963
|
+
name: "Config",
|
|
964
|
+
description: "Read or update .claude settings values.",
|
|
965
|
+
inputSchema: {
|
|
966
|
+
type: "object",
|
|
967
|
+
properties: {
|
|
968
|
+
action: {
|
|
969
|
+
type: "string",
|
|
970
|
+
enum: ["get", "set", "list"]
|
|
971
|
+
},
|
|
972
|
+
key: {
|
|
973
|
+
type: "string",
|
|
974
|
+
description: "Dot-path key (for get/set)."
|
|
975
|
+
},
|
|
976
|
+
value: {
|
|
977
|
+
description: "Value for set action."
|
|
978
|
+
},
|
|
979
|
+
scope: {
|
|
980
|
+
type: "string",
|
|
981
|
+
enum: ["local", "project"]
|
|
982
|
+
}
|
|
983
|
+
},
|
|
984
|
+
required: ["action"]
|
|
985
|
+
},
|
|
986
|
+
async execute(input, ctx) {
|
|
987
|
+
const {
|
|
988
|
+
action,
|
|
989
|
+
key,
|
|
990
|
+
value,
|
|
991
|
+
scope = "local"
|
|
992
|
+
} = input ?? {};
|
|
993
|
+
if (!action) {
|
|
994
|
+
return { content: "Error: action is required", isError: true };
|
|
995
|
+
}
|
|
996
|
+
const filePath = scopePath(ctx.cwd, scope);
|
|
997
|
+
let data = {};
|
|
998
|
+
try {
|
|
999
|
+
const raw = await readFile2(filePath, "utf-8");
|
|
1000
|
+
data = JSON.parse(raw);
|
|
1001
|
+
} catch {
|
|
1002
|
+
data = {};
|
|
1003
|
+
}
|
|
1004
|
+
if (action === "list") {
|
|
1005
|
+
return { content: JSON.stringify(data, null, 2) };
|
|
1006
|
+
}
|
|
1007
|
+
if (!key) {
|
|
1008
|
+
return { content: "Error: key is required for get/set", isError: true };
|
|
1009
|
+
}
|
|
1010
|
+
if (action === "get") {
|
|
1011
|
+
const out = getByPath(data, key);
|
|
1012
|
+
return { content: out === undefined ? "undefined" : JSON.stringify(out, null, 2) };
|
|
1013
|
+
}
|
|
1014
|
+
setByPath(data, key, value);
|
|
1015
|
+
try {
|
|
1016
|
+
await mkdir3(dirname3(filePath), { recursive: true });
|
|
1017
|
+
await writeFile3(filePath, JSON.stringify(data, null, 2) + `
|
|
1018
|
+
`, "utf-8");
|
|
1019
|
+
return { content: `Updated ${key} in ${filePath}` };
|
|
1020
|
+
} catch (err) {
|
|
1021
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1022
|
+
return { content: `Error writing config: ${message}`, isError: true };
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
|
|
1027
|
+
// src/tools/exit-plan-mode.ts
|
|
1028
|
+
var ExitPlanModeTool = {
|
|
1029
|
+
name: "ExitPlanMode",
|
|
1030
|
+
description: "Exit plan mode and resume normal execution permissions.",
|
|
1031
|
+
inputSchema: {
|
|
1032
|
+
type: "object",
|
|
1033
|
+
properties: {}
|
|
1034
|
+
},
|
|
1035
|
+
async execute() {
|
|
1036
|
+
return {
|
|
1037
|
+
content: "Exiting plan mode.",
|
|
1038
|
+
metadata: {
|
|
1039
|
+
setPermissionMode: "default"
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
561
1044
|
// src/tools/index.ts
|
|
562
1045
|
var ALL_TOOLS = {
|
|
563
1046
|
Bash: BashTool,
|
|
@@ -565,7 +1048,14 @@ var ALL_TOOLS = {
|
|
|
565
1048
|
Write: WriteTool,
|
|
566
1049
|
Edit: EditTool,
|
|
567
1050
|
Glob: GlobTool,
|
|
568
|
-
Grep: GrepTool
|
|
1051
|
+
Grep: GrepTool,
|
|
1052
|
+
NotebookEdit: NotebookEditTool,
|
|
1053
|
+
WebFetch: WebFetchTool,
|
|
1054
|
+
WebSearch: WebSearchTool,
|
|
1055
|
+
AskUserQuestion: AskUserQuestionTool,
|
|
1056
|
+
TodoWrite: TodoWriteTool,
|
|
1057
|
+
Config: ConfigTool,
|
|
1058
|
+
ExitPlanMode: ExitPlanModeTool
|
|
569
1059
|
};
|
|
570
1060
|
function buildToolRegistry(toolNames, allowedTools, disallowedTools) {
|
|
571
1061
|
const registry = new ToolRegistry;
|
|
@@ -583,11 +1073,18 @@ export {
|
|
|
583
1073
|
resolveToolNames,
|
|
584
1074
|
buildToolRegistry,
|
|
585
1075
|
WriteTool,
|
|
1076
|
+
WebSearchTool,
|
|
1077
|
+
WebFetchTool,
|
|
586
1078
|
ToolRegistry,
|
|
1079
|
+
TodoWriteTool,
|
|
587
1080
|
ReadTool,
|
|
588
1081
|
PRESETS,
|
|
1082
|
+
NotebookEditTool,
|
|
589
1083
|
GrepTool,
|
|
590
1084
|
GlobTool,
|
|
1085
|
+
ExitPlanModeTool,
|
|
591
1086
|
EditTool,
|
|
592
|
-
|
|
1087
|
+
ConfigTool,
|
|
1088
|
+
BashTool,
|
|
1089
|
+
AskUserQuestionTool
|
|
593
1090
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notebook-edit.d.ts","sourceRoot":"","sources":["../../src/tools/notebook-edit.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAA2B,MAAM,eAAe,CAAC;AAkBjF,eAAO,MAAM,gBAAgB,EAAE,kBAqE9B,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true,
|
|
8
|
+
configurable: true,
|
|
9
|
+
set: (newValue) => all[name] = () => newValue
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
13
|
+
var __require = import.meta.require;
|
|
14
|
+
|
|
15
|
+
// src/tools/notebook-edit.ts
|
|
16
|
+
import { readFile, writeFile } from "fs/promises";
|
|
17
|
+
function toSourceLines(text) {
|
|
18
|
+
const lines = text.split(`
|
|
19
|
+
`);
|
|
20
|
+
return lines.map((line, idx) => idx < lines.length - 1 ? `${line}
|
|
21
|
+
` : line);
|
|
22
|
+
}
|
|
23
|
+
var NotebookEditTool = {
|
|
24
|
+
name: "NotebookEdit",
|
|
25
|
+
description: "Edit a specific Jupyter notebook cell by id or index.",
|
|
26
|
+
inputSchema: {
|
|
27
|
+
type: "object",
|
|
28
|
+
properties: {
|
|
29
|
+
notebook_path: { type: "string", description: "Path to .ipynb file." },
|
|
30
|
+
cell_id: { type: "string", description: "Cell id to edit." },
|
|
31
|
+
cell_index: { type: "number", description: "Cell index to edit if id is not provided." },
|
|
32
|
+
new_source: { type: "string", description: "New cell source content." }
|
|
33
|
+
},
|
|
34
|
+
required: ["notebook_path", "new_source"]
|
|
35
|
+
},
|
|
36
|
+
async execute(input, ctx) {
|
|
37
|
+
const {
|
|
38
|
+
notebook_path,
|
|
39
|
+
cell_id,
|
|
40
|
+
cell_index,
|
|
41
|
+
new_source
|
|
42
|
+
} = input ?? {};
|
|
43
|
+
if (!notebook_path)
|
|
44
|
+
return { content: "Error: notebook_path is required", isError: true };
|
|
45
|
+
if (new_source === undefined)
|
|
46
|
+
return { content: "Error: new_source is required", isError: true };
|
|
47
|
+
const filePath = notebook_path.startsWith("/") ? notebook_path : `${ctx.cwd}/${notebook_path}`;
|
|
48
|
+
try {
|
|
49
|
+
const raw = await readFile(filePath, "utf-8");
|
|
50
|
+
const notebook = JSON.parse(raw);
|
|
51
|
+
if (!Array.isArray(notebook.cells)) {
|
|
52
|
+
return { content: "Error: notebook has no cells array", isError: true };
|
|
53
|
+
}
|
|
54
|
+
let targetIndex = -1;
|
|
55
|
+
if (cell_id) {
|
|
56
|
+
targetIndex = notebook.cells.findIndex((c) => c.id === cell_id);
|
|
57
|
+
} else if (typeof cell_index === "number") {
|
|
58
|
+
targetIndex = cell_index;
|
|
59
|
+
} else {
|
|
60
|
+
targetIndex = 0;
|
|
61
|
+
}
|
|
62
|
+
if (targetIndex < 0 || targetIndex >= notebook.cells.length) {
|
|
63
|
+
return {
|
|
64
|
+
content: `Error: cell not found (id=${cell_id ?? "n/a"}, index=${String(cell_index ?? "n/a")})`,
|
|
65
|
+
isError: true
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const cell = notebook.cells[targetIndex];
|
|
69
|
+
cell.source = toSourceLines(new_source);
|
|
70
|
+
await writeFile(filePath, JSON.stringify(notebook, null, 2) + `
|
|
71
|
+
`, "utf-8");
|
|
72
|
+
return {
|
|
73
|
+
content: `Updated notebook cell ${targetIndex} in ${filePath}`
|
|
74
|
+
};
|
|
75
|
+
} catch (err) {
|
|
76
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
77
|
+
return { content: `Error editing notebook: ${message}`, isError: true };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
export {
|
|
82
|
+
NotebookEditTool
|
|
83
|
+
};
|
package/dist/tools/presets.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Named tool presets for common use cases.
|
|
3
3
|
*/
|
|
4
|
+
import type { ToolsOption } from "../types.js";
|
|
4
5
|
export declare const PRESETS: Record<string, string[]>;
|
|
5
|
-
export declare function resolveToolNames(tools:
|
|
6
|
+
export declare function resolveToolNames(tools: ToolsOption | undefined): string[];
|
|
6
7
|
//# sourceMappingURL=presets.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../../src/tools/presets.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../../src/tools/presets.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAmB5C,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,GAAG,MAAM,EAAE,CASzE"}
|