sparkecoder 0.1.82 → 0.1.83
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.js +284 -71
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +370 -144
- package/dist/cli.js.map +1 -1
- package/dist/index.js +358 -132
- package/dist/index.js.map +1 -1
- package/dist/server/index.js +358 -132
- package/dist/server/index.js.map +1 -1
- package/dist/skills/default/browser.md +30 -0
- package/dist/tools/index.d.ts +117 -1
- package/dist/tools/index.js +183 -41
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
- package/src/skills/default/browser.md +30 -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/docs/installation.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/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/package-lock.json +3 -3
- /package/web/.next/standalone/web/.next/static/{Ne3ChQc_mw5oh4Y1Rr7qj → aCZCpTkVv_k-RisOFPegk}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{Ne3ChQc_mw5oh4Y1Rr7qj → aCZCpTkVv_k-RisOFPegk}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{Ne3ChQc_mw5oh4Y1Rr7qj → aCZCpTkVv_k-RisOFPegk}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{Ne3ChQc_mw5oh4Y1Rr7qj → aCZCpTkVv_k-RisOFPegk}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{Ne3ChQc_mw5oh4Y1Rr7qj → aCZCpTkVv_k-RisOFPegk}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/static/{Ne3ChQc_mw5oh4Y1Rr7qj → aCZCpTkVv_k-RisOFPegk}/_ssgManifest.js +0 -0
- /package/web/.next/static/{Ne3ChQc_mw5oh4Y1Rr7qj → aCZCpTkVv_k-RisOFPegk}/_buildManifest.js +0 -0
- /package/web/.next/static/{Ne3ChQc_mw5oh4Y1Rr7qj → aCZCpTkVv_k-RisOFPegk}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{Ne3ChQc_mw5oh4Y1Rr7qj → aCZCpTkVv_k-RisOFPegk}/_ssgManifest.js +0 -0
|
@@ -110,6 +110,36 @@ agent-browser wait --url "**/dashboard" # Wait for URL pattern
|
|
|
110
110
|
agent-browser wait 2000 # Wait milliseconds
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
+
### Video Recording
|
|
114
|
+
Record browser actions to a WebM video file. Useful for demos, bug reproductions, or proof of work.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
mkdir -p ".sparkecode-artifacts/recordings"
|
|
118
|
+
|
|
119
|
+
# Start recording (from current page — preserves login state)
|
|
120
|
+
agent-browser record start ".sparkecode-artifacts/recordings/demo.webm"
|
|
121
|
+
|
|
122
|
+
# Do your interactions...
|
|
123
|
+
agent-browser click @e3
|
|
124
|
+
agent-browser fill @e4 "test@example.com"
|
|
125
|
+
agent-browser click @e5
|
|
126
|
+
|
|
127
|
+
# Stop and save
|
|
128
|
+
agent-browser record stop
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
After recording, upload the video to get a shareable URL:
|
|
132
|
+
```
|
|
133
|
+
upload_file({ path: ".sparkecode-artifacts/recordings/demo.webm", name: "demo.webm" })
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
You can also restart recording into a new file without closing the browser:
|
|
137
|
+
```bash
|
|
138
|
+
agent-browser record restart ".sparkecode-artifacts/recordings/take2.webm"
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Note:** In task mode, browser sessions are also automatically recorded in the background. The MP4 recording is uploaded when the task completes and appears in the `GET /tasks/:id` response as `browserRecordings` and in the `task.completed` webhook as `browserRecordingUrls`. You do NOT need to manually record in task mode — it happens for free.
|
|
142
|
+
|
|
113
143
|
### Sessions
|
|
114
144
|
```bash
|
|
115
145
|
agent-browser sessions # List active sessions
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -80,15 +80,18 @@ declare function createReadFileTool(options: ReadFileToolOptions): ai.Tool<{
|
|
|
80
80
|
|
|
81
81
|
interface TodoToolOptions {
|
|
82
82
|
sessionId: string;
|
|
83
|
+
workingDirectory: string;
|
|
83
84
|
}
|
|
84
85
|
declare function createTodoTool(options: TodoToolOptions): ai.Tool<{
|
|
85
|
-
action: "add" | "list" | "mark" | "clear";
|
|
86
|
+
action: "add" | "list" | "mark" | "clear" | "save_plan" | "list_plans" | "get_plan" | "delete_plan";
|
|
86
87
|
status?: "completed" | "pending" | "in_progress" | "cancelled" | undefined;
|
|
87
88
|
items?: {
|
|
88
89
|
content: string;
|
|
89
90
|
order?: number | undefined;
|
|
90
91
|
}[] | undefined;
|
|
91
92
|
todoId?: string | undefined;
|
|
93
|
+
planName?: string | undefined;
|
|
94
|
+
planContent?: string | undefined;
|
|
92
95
|
}, {
|
|
93
96
|
success: boolean;
|
|
94
97
|
action: string;
|
|
@@ -103,6 +106,14 @@ declare function createTodoTool(options: TodoToolOptions): ai.Tool<{
|
|
|
103
106
|
stats?: undefined;
|
|
104
107
|
item?: undefined;
|
|
105
108
|
itemsRemoved?: undefined;
|
|
109
|
+
planName?: undefined;
|
|
110
|
+
filename?: undefined;
|
|
111
|
+
path?: undefined;
|
|
112
|
+
sizeChars?: undefined;
|
|
113
|
+
plans?: undefined;
|
|
114
|
+
count?: undefined;
|
|
115
|
+
content?: undefined;
|
|
116
|
+
deleted?: undefined;
|
|
106
117
|
error?: undefined;
|
|
107
118
|
} | {
|
|
108
119
|
success: boolean;
|
|
@@ -124,6 +135,14 @@ declare function createTodoTool(options: TodoToolOptions): ai.Tool<{
|
|
|
124
135
|
itemsAdded?: undefined;
|
|
125
136
|
item?: undefined;
|
|
126
137
|
itemsRemoved?: undefined;
|
|
138
|
+
planName?: undefined;
|
|
139
|
+
filename?: undefined;
|
|
140
|
+
path?: undefined;
|
|
141
|
+
sizeChars?: undefined;
|
|
142
|
+
plans?: undefined;
|
|
143
|
+
count?: undefined;
|
|
144
|
+
content?: undefined;
|
|
145
|
+
deleted?: undefined;
|
|
127
146
|
error?: undefined;
|
|
128
147
|
} | {
|
|
129
148
|
success: boolean;
|
|
@@ -139,6 +158,14 @@ declare function createTodoTool(options: TodoToolOptions): ai.Tool<{
|
|
|
139
158
|
items?: undefined;
|
|
140
159
|
stats?: undefined;
|
|
141
160
|
itemsRemoved?: undefined;
|
|
161
|
+
planName?: undefined;
|
|
162
|
+
filename?: undefined;
|
|
163
|
+
path?: undefined;
|
|
164
|
+
sizeChars?: undefined;
|
|
165
|
+
plans?: undefined;
|
|
166
|
+
count?: undefined;
|
|
167
|
+
content?: undefined;
|
|
168
|
+
deleted?: undefined;
|
|
142
169
|
error?: undefined;
|
|
143
170
|
} | {
|
|
144
171
|
success: boolean;
|
|
@@ -148,6 +175,87 @@ declare function createTodoTool(options: TodoToolOptions): ai.Tool<{
|
|
|
148
175
|
items?: undefined;
|
|
149
176
|
stats?: undefined;
|
|
150
177
|
item?: undefined;
|
|
178
|
+
planName?: undefined;
|
|
179
|
+
filename?: undefined;
|
|
180
|
+
path?: undefined;
|
|
181
|
+
sizeChars?: undefined;
|
|
182
|
+
plans?: undefined;
|
|
183
|
+
count?: undefined;
|
|
184
|
+
content?: undefined;
|
|
185
|
+
deleted?: undefined;
|
|
186
|
+
error?: undefined;
|
|
187
|
+
} | {
|
|
188
|
+
success: boolean;
|
|
189
|
+
action: string;
|
|
190
|
+
planName: string;
|
|
191
|
+
filename: string;
|
|
192
|
+
path: string;
|
|
193
|
+
sizeChars: number;
|
|
194
|
+
itemsAdded?: undefined;
|
|
195
|
+
items?: undefined;
|
|
196
|
+
stats?: undefined;
|
|
197
|
+
item?: undefined;
|
|
198
|
+
itemsRemoved?: undefined;
|
|
199
|
+
plans?: undefined;
|
|
200
|
+
count?: undefined;
|
|
201
|
+
content?: undefined;
|
|
202
|
+
deleted?: undefined;
|
|
203
|
+
error?: undefined;
|
|
204
|
+
} | {
|
|
205
|
+
success: boolean;
|
|
206
|
+
action: string;
|
|
207
|
+
plans: {
|
|
208
|
+
name: string;
|
|
209
|
+
title: string;
|
|
210
|
+
filename: string;
|
|
211
|
+
sizeChars: number;
|
|
212
|
+
}[];
|
|
213
|
+
count: number;
|
|
214
|
+
itemsAdded?: undefined;
|
|
215
|
+
items?: undefined;
|
|
216
|
+
stats?: undefined;
|
|
217
|
+
item?: undefined;
|
|
218
|
+
itemsRemoved?: undefined;
|
|
219
|
+
planName?: undefined;
|
|
220
|
+
filename?: undefined;
|
|
221
|
+
path?: undefined;
|
|
222
|
+
sizeChars?: undefined;
|
|
223
|
+
content?: undefined;
|
|
224
|
+
deleted?: undefined;
|
|
225
|
+
error?: undefined;
|
|
226
|
+
} | {
|
|
227
|
+
success: boolean;
|
|
228
|
+
action: string;
|
|
229
|
+
planName: string;
|
|
230
|
+
content: string;
|
|
231
|
+
sizeChars: number;
|
|
232
|
+
itemsAdded?: undefined;
|
|
233
|
+
items?: undefined;
|
|
234
|
+
stats?: undefined;
|
|
235
|
+
item?: undefined;
|
|
236
|
+
itemsRemoved?: undefined;
|
|
237
|
+
filename?: undefined;
|
|
238
|
+
path?: undefined;
|
|
239
|
+
plans?: undefined;
|
|
240
|
+
count?: undefined;
|
|
241
|
+
deleted?: undefined;
|
|
242
|
+
error?: undefined;
|
|
243
|
+
} | {
|
|
244
|
+
success: boolean;
|
|
245
|
+
action: string;
|
|
246
|
+
planName: string;
|
|
247
|
+
deleted: boolean;
|
|
248
|
+
itemsAdded?: undefined;
|
|
249
|
+
items?: undefined;
|
|
250
|
+
stats?: undefined;
|
|
251
|
+
item?: undefined;
|
|
252
|
+
itemsRemoved?: undefined;
|
|
253
|
+
filename?: undefined;
|
|
254
|
+
path?: undefined;
|
|
255
|
+
sizeChars?: undefined;
|
|
256
|
+
plans?: undefined;
|
|
257
|
+
count?: undefined;
|
|
258
|
+
content?: undefined;
|
|
151
259
|
error?: undefined;
|
|
152
260
|
} | {
|
|
153
261
|
success: boolean;
|
|
@@ -158,6 +266,14 @@ declare function createTodoTool(options: TodoToolOptions): ai.Tool<{
|
|
|
158
266
|
stats?: undefined;
|
|
159
267
|
item?: undefined;
|
|
160
268
|
itemsRemoved?: undefined;
|
|
269
|
+
planName?: undefined;
|
|
270
|
+
filename?: undefined;
|
|
271
|
+
path?: undefined;
|
|
272
|
+
sizeChars?: undefined;
|
|
273
|
+
plans?: undefined;
|
|
274
|
+
count?: undefined;
|
|
275
|
+
content?: undefined;
|
|
276
|
+
deleted?: undefined;
|
|
161
277
|
}>;
|
|
162
278
|
|
|
163
279
|
interface LoadSkillToolOptions {
|
package/dist/tools/index.js
CHANGED
|
@@ -848,7 +848,7 @@ var init_client = __esm({
|
|
|
848
848
|
});
|
|
849
849
|
|
|
850
850
|
// src/semantic/indexer.ts
|
|
851
|
-
import { readFileSync as
|
|
851
|
+
import { readFileSync as readFileSync5, statSync } from "fs";
|
|
852
852
|
import { relative as relative6 } from "path";
|
|
853
853
|
import { minimatch as minimatch2 } from "minimatch";
|
|
854
854
|
async function getIndexStatus(workingDirectory) {
|
|
@@ -933,8 +933,8 @@ __export(semantic_search_exports, {
|
|
|
933
933
|
});
|
|
934
934
|
import { tool as tool8 } from "ai";
|
|
935
935
|
import { z as z9 } from "zod";
|
|
936
|
-
import { existsSync as
|
|
937
|
-
import { join as
|
|
936
|
+
import { existsSync as existsSync13, readFileSync as readFileSync6 } from "fs";
|
|
937
|
+
import { join as join6 } from "path";
|
|
938
938
|
import { minimatch as minimatch3 } from "minimatch";
|
|
939
939
|
function createSemanticSearchTool(options) {
|
|
940
940
|
return tool8({
|
|
@@ -1001,13 +1001,13 @@ Returns matching code snippets with file paths, line numbers, and relevance scor
|
|
|
1001
1001
|
if (language && matchLanguage !== language.toLowerCase()) {
|
|
1002
1002
|
continue;
|
|
1003
1003
|
}
|
|
1004
|
-
const fullPath =
|
|
1005
|
-
if (!
|
|
1004
|
+
const fullPath = join6(options.workingDirectory, filePath);
|
|
1005
|
+
if (!existsSync13(fullPath)) {
|
|
1006
1006
|
continue;
|
|
1007
1007
|
}
|
|
1008
1008
|
let snippet = "";
|
|
1009
1009
|
try {
|
|
1010
|
-
const content =
|
|
1010
|
+
const content = readFileSync6(fullPath, "utf-8");
|
|
1011
1011
|
const lines = content.split("\n");
|
|
1012
1012
|
const snippetLines = lines.slice(
|
|
1013
1013
|
Math.max(0, startLine - 1),
|
|
@@ -2729,8 +2729,34 @@ Working directory: ${options.workingDirectory}`,
|
|
|
2729
2729
|
init_db();
|
|
2730
2730
|
import { tool as tool4 } from "ai";
|
|
2731
2731
|
import { z as z5 } from "zod";
|
|
2732
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync, unlinkSync, readFileSync as readFileSync3, appendFileSync } from "fs";
|
|
2733
|
+
import { readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
|
|
2734
|
+
import { join as join4 } from "path";
|
|
2735
|
+
function getPlansDir(workingDirectory, sessionId) {
|
|
2736
|
+
return join4(workingDirectory, ".sparkecoder", "plans", sessionId);
|
|
2737
|
+
}
|
|
2738
|
+
function ensurePlansDir(workingDirectory, sessionId) {
|
|
2739
|
+
const dir = getPlansDir(workingDirectory, sessionId);
|
|
2740
|
+
if (!existsSync9(dir)) {
|
|
2741
|
+
mkdirSync4(dir, { recursive: true });
|
|
2742
|
+
}
|
|
2743
|
+
const gitignorePath = join4(workingDirectory, ".gitignore");
|
|
2744
|
+
if (existsSync9(gitignorePath)) {
|
|
2745
|
+
try {
|
|
2746
|
+
const content = readFileSync3(gitignorePath, "utf-8");
|
|
2747
|
+
if (!content.includes(".sparkecoder")) {
|
|
2748
|
+
appendFileSync(gitignorePath, "\n.sparkecoder/\n");
|
|
2749
|
+
}
|
|
2750
|
+
} catch {
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
return dir;
|
|
2754
|
+
}
|
|
2755
|
+
function slugify(name) {
|
|
2756
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "plan";
|
|
2757
|
+
}
|
|
2732
2758
|
var todoInputSchema = z5.object({
|
|
2733
|
-
action: z5.enum(["add", "list", "mark", "clear"]).describe("The action to perform
|
|
2759
|
+
action: z5.enum(["add", "list", "mark", "clear", "save_plan", "list_plans", "get_plan", "delete_plan"]).describe("The action to perform"),
|
|
2734
2760
|
items: z5.array(
|
|
2735
2761
|
z5.object({
|
|
2736
2762
|
content: z5.string().describe("Description of the task"),
|
|
@@ -2738,27 +2764,67 @@ var todoInputSchema = z5.object({
|
|
|
2738
2764
|
})
|
|
2739
2765
|
).optional().describe('For "add" action: Array of todo items to add'),
|
|
2740
2766
|
todoId: z5.string().optional().describe('For "mark" action: The ID of the todo item to update'),
|
|
2741
|
-
status: z5.enum(["pending", "in_progress", "completed", "cancelled"]).optional().describe('For "mark" action: The new status for the todo item')
|
|
2767
|
+
status: z5.enum(["pending", "in_progress", "completed", "cancelled"]).optional().describe('For "mark" action: The new status for the todo item'),
|
|
2768
|
+
planName: z5.string().optional().describe('For plan actions: Name of the plan (e.g. "auth-system", "db-migration")'),
|
|
2769
|
+
planContent: z5.string().optional().describe('For "save_plan": Full plan content as markdown with hierarchical tasks using checkboxes')
|
|
2742
2770
|
});
|
|
2743
2771
|
function createTodoTool(options) {
|
|
2744
2772
|
return tool4({
|
|
2745
|
-
description: `Manage your task list for the current session.
|
|
2746
|
-
- Break down complex tasks into smaller steps
|
|
2747
|
-
- Track progress on multi-step operations
|
|
2748
|
-
- Organize your work systematically
|
|
2773
|
+
description: `Manage your task list and persistent plans for the current session.
|
|
2749
2774
|
|
|
2750
|
-
|
|
2775
|
+
## Todo Actions (for tracking current work)
|
|
2751
2776
|
- "add": Add one or more new todo items to the list
|
|
2752
2777
|
- "list": View all current todo items and their status
|
|
2753
2778
|
- "mark": Update the status of a todo item (pending, in_progress, completed, cancelled)
|
|
2754
2779
|
- "clear": Remove all todo items from the list
|
|
2755
2780
|
|
|
2756
|
-
|
|
2757
|
-
-
|
|
2758
|
-
-
|
|
2759
|
-
-
|
|
2781
|
+
## Plan Actions (for complex, multi-phase work)
|
|
2782
|
+
- "save_plan": Create or update a named plan \u2014 a persistent markdown document with hierarchical tasks, subtasks, and notes. Plans survive context compaction and are always available.
|
|
2783
|
+
- "list_plans": List all plans for this session
|
|
2784
|
+
- "get_plan": Read a specific plan by name
|
|
2785
|
+
- "delete_plan": Remove a plan
|
|
2786
|
+
|
|
2787
|
+
## Plans vs Todos
|
|
2788
|
+
- **Plans** are the big picture \u2014 the full spec with phases, subtasks, notes, and decisions. They persist on disk and are always injected into your context, even after old messages are summarized.
|
|
2789
|
+
- **Todos** are your current focus \u2014 the immediate steps you're working on right now.
|
|
2790
|
+
|
|
2791
|
+
## Workflow for complex tasks
|
|
2792
|
+
1. Create a plan with phases and subtasks (save_plan)
|
|
2793
|
+
2. Create todos from the first uncompleted phase (add)
|
|
2794
|
+
3. Work through the todos, marking them as you go
|
|
2795
|
+
4. When all current todos are done, update the plan (mark completed sections with [x]) and save it
|
|
2796
|
+
5. Create new todos from the next uncompleted phase
|
|
2797
|
+
6. Repeat until the plan is fully complete
|
|
2798
|
+
|
|
2799
|
+
## Plan format
|
|
2800
|
+
Plans should be markdown with this structure:
|
|
2801
|
+
\`\`\`markdown
|
|
2802
|
+
# Plan: [Title]
|
|
2803
|
+
|
|
2804
|
+
## Overview
|
|
2805
|
+
[What we're doing and why]
|
|
2806
|
+
|
|
2807
|
+
## Phase 1: [Name] [completed]
|
|
2808
|
+
- [x] Task 1
|
|
2809
|
+
- [x] Task 2
|
|
2810
|
+
|
|
2811
|
+
## Phase 2: [Name] [in_progress]
|
|
2812
|
+
- [x] Subtask 2.1
|
|
2813
|
+
- [ ] Subtask 2.2
|
|
2814
|
+
- [ ] Sub-subtask 2.2.1
|
|
2815
|
+
- [ ] Sub-subtask 2.2.2
|
|
2816
|
+
- [ ] Subtask 2.3
|
|
2817
|
+
|
|
2818
|
+
## Phase 3: [Name] [pending]
|
|
2819
|
+
- [ ] Task 1
|
|
2820
|
+
- [ ] Task 2
|
|
2821
|
+
|
|
2822
|
+
## Notes
|
|
2823
|
+
- Key decisions and context to preserve
|
|
2824
|
+
- Important file paths discovered
|
|
2825
|
+
\`\`\``,
|
|
2760
2826
|
inputSchema: todoInputSchema,
|
|
2761
|
-
execute: async ({ action, items, todoId, status }) => {
|
|
2827
|
+
execute: async ({ action, items, todoId, status, planName, planContent }) => {
|
|
2762
2828
|
try {
|
|
2763
2829
|
switch (action) {
|
|
2764
2830
|
case "add": {
|
|
@@ -2826,6 +2892,81 @@ Best practices:
|
|
|
2826
2892
|
itemsRemoved: count
|
|
2827
2893
|
};
|
|
2828
2894
|
}
|
|
2895
|
+
// ── Plan actions ─────────────────────────────────────────
|
|
2896
|
+
case "save_plan": {
|
|
2897
|
+
if (!planName) {
|
|
2898
|
+
return { success: false, error: 'planName is required for "save_plan"' };
|
|
2899
|
+
}
|
|
2900
|
+
if (!planContent) {
|
|
2901
|
+
return { success: false, error: 'planContent is required for "save_plan"' };
|
|
2902
|
+
}
|
|
2903
|
+
const dir = ensurePlansDir(options.workingDirectory, options.sessionId);
|
|
2904
|
+
const filename = `${slugify(planName)}.md`;
|
|
2905
|
+
const filePath = join4(dir, filename);
|
|
2906
|
+
await writeFile4(filePath, planContent, "utf-8");
|
|
2907
|
+
return {
|
|
2908
|
+
success: true,
|
|
2909
|
+
action: "save_plan",
|
|
2910
|
+
planName,
|
|
2911
|
+
filename,
|
|
2912
|
+
path: filePath,
|
|
2913
|
+
sizeChars: planContent.length
|
|
2914
|
+
};
|
|
2915
|
+
}
|
|
2916
|
+
case "list_plans": {
|
|
2917
|
+
const dir = getPlansDir(options.workingDirectory, options.sessionId);
|
|
2918
|
+
if (!existsSync9(dir)) {
|
|
2919
|
+
return { success: true, action: "list_plans", plans: [], count: 0 };
|
|
2920
|
+
}
|
|
2921
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
2922
|
+
const plans = [];
|
|
2923
|
+
for (const f of files) {
|
|
2924
|
+
try {
|
|
2925
|
+
const content = await readFile6(join4(dir, f), "utf-8");
|
|
2926
|
+
const titleMatch = content.match(/^#\s+(?:Plan:\s*)?(.+)/m);
|
|
2927
|
+
plans.push({
|
|
2928
|
+
name: f.replace(/\.md$/, ""),
|
|
2929
|
+
title: titleMatch?.[1]?.trim() || f.replace(/\.md$/, ""),
|
|
2930
|
+
filename: f,
|
|
2931
|
+
sizeChars: content.length
|
|
2932
|
+
});
|
|
2933
|
+
} catch {
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
return { success: true, action: "list_plans", plans, count: plans.length };
|
|
2937
|
+
}
|
|
2938
|
+
case "get_plan": {
|
|
2939
|
+
if (!planName) {
|
|
2940
|
+
return { success: false, error: 'planName is required for "get_plan"' };
|
|
2941
|
+
}
|
|
2942
|
+
const dir = getPlansDir(options.workingDirectory, options.sessionId);
|
|
2943
|
+
const filename = `${slugify(planName)}.md`;
|
|
2944
|
+
const filePath = join4(dir, filename);
|
|
2945
|
+
if (!existsSync9(filePath)) {
|
|
2946
|
+
return { success: false, error: `Plan not found: "${planName}" (looked for ${filename})` };
|
|
2947
|
+
}
|
|
2948
|
+
const content = await readFile6(filePath, "utf-8");
|
|
2949
|
+
return {
|
|
2950
|
+
success: true,
|
|
2951
|
+
action: "get_plan",
|
|
2952
|
+
planName,
|
|
2953
|
+
content,
|
|
2954
|
+
sizeChars: content.length
|
|
2955
|
+
};
|
|
2956
|
+
}
|
|
2957
|
+
case "delete_plan": {
|
|
2958
|
+
if (!planName) {
|
|
2959
|
+
return { success: false, error: 'planName is required for "delete_plan"' };
|
|
2960
|
+
}
|
|
2961
|
+
const dir = getPlansDir(options.workingDirectory, options.sessionId);
|
|
2962
|
+
const filename = `${slugify(planName)}.md`;
|
|
2963
|
+
const filePath = join4(dir, filename);
|
|
2964
|
+
if (!existsSync9(filePath)) {
|
|
2965
|
+
return { success: false, error: `Plan not found: "${planName}"` };
|
|
2966
|
+
}
|
|
2967
|
+
unlinkSync(filePath);
|
|
2968
|
+
return { success: true, action: "delete_plan", planName, deleted: true };
|
|
2969
|
+
}
|
|
2829
2970
|
default:
|
|
2830
2971
|
return {
|
|
2831
2972
|
success: false,
|
|
@@ -2857,9 +2998,9 @@ import { z as z6 } from "zod";
|
|
|
2857
2998
|
|
|
2858
2999
|
// src/skills/index.ts
|
|
2859
3000
|
init_types();
|
|
2860
|
-
import { readFile as
|
|
3001
|
+
import { readFile as readFile7, readdir } from "fs/promises";
|
|
2861
3002
|
import { resolve as resolve6, basename, extname as extname4, relative as relative4 } from "path";
|
|
2862
|
-
import { existsSync as
|
|
3003
|
+
import { existsSync as existsSync10 } from "fs";
|
|
2863
3004
|
import { minimatch } from "minimatch";
|
|
2864
3005
|
function parseSkillFrontmatter(content) {
|
|
2865
3006
|
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
@@ -2937,7 +3078,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
2937
3078
|
defaultLoadType = "on_demand",
|
|
2938
3079
|
forceAlwaysApply = false
|
|
2939
3080
|
} = options;
|
|
2940
|
-
if (!
|
|
3081
|
+
if (!existsSync10(directory)) {
|
|
2941
3082
|
return [];
|
|
2942
3083
|
}
|
|
2943
3084
|
const skills = [];
|
|
@@ -2947,7 +3088,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
2947
3088
|
let fileName;
|
|
2948
3089
|
if (entry.isDirectory()) {
|
|
2949
3090
|
const skillMdPath = resolve6(directory, entry.name, "SKILL.md");
|
|
2950
|
-
if (
|
|
3091
|
+
if (existsSync10(skillMdPath)) {
|
|
2951
3092
|
filePath = skillMdPath;
|
|
2952
3093
|
fileName = entry.name;
|
|
2953
3094
|
} else {
|
|
@@ -2959,7 +3100,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
2959
3100
|
} else {
|
|
2960
3101
|
continue;
|
|
2961
3102
|
}
|
|
2962
|
-
const content = await
|
|
3103
|
+
const content = await readFile7(filePath, "utf-8");
|
|
2963
3104
|
const parsed = parseSkillFrontmatter(content);
|
|
2964
3105
|
if (parsed) {
|
|
2965
3106
|
const alwaysApply = forceAlwaysApply || parsed.metadata.alwaysApply;
|
|
@@ -3013,7 +3154,7 @@ async function loadSkillContent(skillName, directories) {
|
|
|
3013
3154
|
if (!skill) {
|
|
3014
3155
|
return null;
|
|
3015
3156
|
}
|
|
3016
|
-
const content = await
|
|
3157
|
+
const content = await readFile7(skill.filePath, "utf-8");
|
|
3017
3158
|
const parsed = parseSkillFrontmatter(content);
|
|
3018
3159
|
return {
|
|
3019
3160
|
...skill,
|
|
@@ -3118,7 +3259,7 @@ Once loaded, a skill's content will be available in the conversation context.`,
|
|
|
3118
3259
|
import { tool as tool6 } from "ai";
|
|
3119
3260
|
import { z as z7 } from "zod";
|
|
3120
3261
|
import { resolve as resolve7, relative as relative5, isAbsolute as isAbsolute3, extname as extname5 } from "path";
|
|
3121
|
-
import { existsSync as
|
|
3262
|
+
import { existsSync as existsSync11 } from "fs";
|
|
3122
3263
|
import { readdir as readdir2, stat as stat2 } from "fs/promises";
|
|
3123
3264
|
var linterInputSchema = z7.object({
|
|
3124
3265
|
paths: z7.array(z7.string()).optional().describe("File or directory paths to check for lint errors. If not provided, returns diagnostics for all recently touched files."),
|
|
@@ -3186,7 +3327,7 @@ Working directory: ${options.workingDirectory}`,
|
|
|
3186
3327
|
const filesToCheck = [];
|
|
3187
3328
|
for (const path of paths) {
|
|
3188
3329
|
const absolutePath = isAbsolute3(path) ? path : resolve7(options.workingDirectory, path);
|
|
3189
|
-
if (!
|
|
3330
|
+
if (!existsSync11(absolutePath)) {
|
|
3190
3331
|
continue;
|
|
3191
3332
|
}
|
|
3192
3333
|
const stats = await stat2(absolutePath);
|
|
@@ -3679,17 +3820,17 @@ import { tool as tool9 } from "ai";
|
|
|
3679
3820
|
import { z as z10 } from "zod";
|
|
3680
3821
|
import { exec as exec4 } from "child_process";
|
|
3681
3822
|
import { promisify as promisify4 } from "util";
|
|
3682
|
-
import { readFile as
|
|
3823
|
+
import { readFile as readFile9, stat as stat3, readdir as readdir4 } from "fs/promises";
|
|
3683
3824
|
import { resolve as resolve9, relative as relative8, isAbsolute as isAbsolute5 } from "path";
|
|
3684
|
-
import { existsSync as
|
|
3825
|
+
import { existsSync as existsSync14 } from "fs";
|
|
3685
3826
|
init_semantic();
|
|
3686
3827
|
|
|
3687
3828
|
// src/tools/code-graph.ts
|
|
3688
3829
|
import { tool as tool7 } from "ai";
|
|
3689
3830
|
import { z as z8 } from "zod";
|
|
3690
3831
|
import { resolve as resolve8, relative as relative7, isAbsolute as isAbsolute4, basename as basename3 } from "path";
|
|
3691
|
-
import { readFile as
|
|
3692
|
-
import { existsSync as
|
|
3832
|
+
import { readFile as readFile8, readdir as readdir3 } from "fs/promises";
|
|
3833
|
+
import { existsSync as existsSync12 } from "fs";
|
|
3693
3834
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3694
3835
|
import { execFileSync } from "child_process";
|
|
3695
3836
|
var codeGraphInputSchema = z8.object({
|
|
@@ -3826,7 +3967,7 @@ async function grepForSymbol(symbol, workingDirectory) {
|
|
|
3826
3967
|
const ext = entry.name.substring(entry.name.lastIndexOf("."));
|
|
3827
3968
|
if (!SUPPORTED_EXTS.has(ext)) continue;
|
|
3828
3969
|
remaining--;
|
|
3829
|
-
const content = await
|
|
3970
|
+
const content = await readFile8(fullPath, "utf-8");
|
|
3830
3971
|
const lines = content.split("\n");
|
|
3831
3972
|
for (let i = 0; i < lines.length; i++) {
|
|
3832
3973
|
if (defPattern.test(lines[i])) {
|
|
@@ -3875,7 +4016,7 @@ Working directory: ${options.workingDirectory}`,
|
|
|
3875
4016
|
let defSymbol = null;
|
|
3876
4017
|
if (filePath) {
|
|
3877
4018
|
const absPath = isAbsolute4(filePath) ? filePath : resolve8(options.workingDirectory, filePath);
|
|
3878
|
-
if (!
|
|
4019
|
+
if (!existsSync12(absPath)) {
|
|
3879
4020
|
return { success: false, error: `File not found: ${filePath}` };
|
|
3880
4021
|
}
|
|
3881
4022
|
if (!isSupported(absPath)) {
|
|
@@ -3889,7 +4030,7 @@ Working directory: ${options.workingDirectory}`,
|
|
|
3889
4030
|
defLine = defSymbol.selectionRange.start.line;
|
|
3890
4031
|
defChar = defSymbol.selectionRange.start.character;
|
|
3891
4032
|
} else {
|
|
3892
|
-
const content = await
|
|
4033
|
+
const content = await readFile8(absPath, "utf-8");
|
|
3893
4034
|
const lines2 = content.split("\n");
|
|
3894
4035
|
const defPattern = new RegExp(
|
|
3895
4036
|
`(export|function|const|let|var|class|interface|type|enum)\\s+.*\\b${symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`
|
|
@@ -4302,7 +4443,7 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
|
|
|
4302
4443
|
execute: async ({ path, startLine, endLine }) => {
|
|
4303
4444
|
try {
|
|
4304
4445
|
const absolutePath = isAbsolute5(path) ? path : resolve9(workingDirectory, path);
|
|
4305
|
-
if (!
|
|
4446
|
+
if (!existsSync14(absolutePath)) {
|
|
4306
4447
|
return {
|
|
4307
4448
|
success: false,
|
|
4308
4449
|
error: `File not found: ${path}`
|
|
@@ -4315,7 +4456,7 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
|
|
|
4315
4456
|
error: `File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Use startLine/endLine to read portions.`
|
|
4316
4457
|
};
|
|
4317
4458
|
}
|
|
4318
|
-
let content = await
|
|
4459
|
+
let content = await readFile9(absolutePath, "utf-8");
|
|
4319
4460
|
if (startLine !== void 0 || endLine !== void 0) {
|
|
4320
4461
|
const lines = content.split("\n");
|
|
4321
4462
|
const start = (startLine ?? 1) - 1;
|
|
@@ -4346,7 +4487,7 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
|
|
|
4346
4487
|
execute: async ({ path, recursive, maxDepth }) => {
|
|
4347
4488
|
try {
|
|
4348
4489
|
const absolutePath = isAbsolute5(path) ? path : resolve9(workingDirectory, path);
|
|
4349
|
-
if (!
|
|
4490
|
+
if (!existsSync14(absolutePath)) {
|
|
4350
4491
|
return {
|
|
4351
4492
|
success: false,
|
|
4352
4493
|
error: `Directory not found: ${path}`
|
|
@@ -4713,8 +4854,8 @@ function createTaskFailedTool(options) {
|
|
|
4713
4854
|
// src/tools/upload-file.ts
|
|
4714
4855
|
import { tool as tool12 } from "ai";
|
|
4715
4856
|
import { z as z13 } from "zod";
|
|
4716
|
-
import { readFile as
|
|
4717
|
-
import { join as
|
|
4857
|
+
import { readFile as readFile10, stat as stat4 } from "fs/promises";
|
|
4858
|
+
import { join as join7, basename as basename4, extname as extname7 } from "path";
|
|
4718
4859
|
var MIME_TYPES = {
|
|
4719
4860
|
".txt": "text/plain",
|
|
4720
4861
|
".md": "text/markdown",
|
|
@@ -4756,7 +4897,7 @@ function createUploadFileTool(options) {
|
|
|
4756
4897
|
error: "File upload is not available \u2014 remote server with GCS is not configured."
|
|
4757
4898
|
};
|
|
4758
4899
|
}
|
|
4759
|
-
const fullPath = input.path.startsWith("/") ? input.path :
|
|
4900
|
+
const fullPath = input.path.startsWith("/") ? input.path : join7(options.workingDirectory, input.path);
|
|
4760
4901
|
try {
|
|
4761
4902
|
await stat4(fullPath);
|
|
4762
4903
|
} catch {
|
|
@@ -4774,7 +4915,7 @@ function createUploadFileTool(options) {
|
|
|
4774
4915
|
contentType,
|
|
4775
4916
|
"general"
|
|
4776
4917
|
);
|
|
4777
|
-
const fileData = await
|
|
4918
|
+
const fileData = await readFile10(fullPath);
|
|
4778
4919
|
const putRes = await fetch(uploadInfo.uploadUrl, {
|
|
4779
4920
|
method: "PUT",
|
|
4780
4921
|
headers: { "Content-Type": contentType },
|
|
@@ -4829,7 +4970,8 @@ async function createTools(options) {
|
|
|
4829
4970
|
onProgress: options.onWriteFileProgress
|
|
4830
4971
|
}),
|
|
4831
4972
|
todo: createTodoTool({
|
|
4832
|
-
sessionId: options.sessionId
|
|
4973
|
+
sessionId: options.sessionId,
|
|
4974
|
+
workingDirectory: options.workingDirectory
|
|
4833
4975
|
}),
|
|
4834
4976
|
load_skill: createLoadSkillTool({
|
|
4835
4977
|
sessionId: options.sessionId,
|