@lakitu/sdk 0.1.0
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 +166 -0
- package/convex/_generated/api.d.ts +45 -0
- package/convex/_generated/api.js +23 -0
- package/convex/_generated/dataModel.d.ts +58 -0
- package/convex/_generated/server.d.ts +143 -0
- package/convex/_generated/server.js +93 -0
- package/convex/cloud/CLAUDE.md +238 -0
- package/convex/cloud/_generated/api.ts +84 -0
- package/convex/cloud/_generated/component.ts +861 -0
- package/convex/cloud/_generated/dataModel.ts +60 -0
- package/convex/cloud/_generated/server.ts +156 -0
- package/convex/cloud/convex.config.ts +16 -0
- package/convex/cloud/index.ts +29 -0
- package/convex/cloud/intentSchema/generate.ts +447 -0
- package/convex/cloud/intentSchema/index.ts +16 -0
- package/convex/cloud/intentSchema/types.ts +418 -0
- package/convex/cloud/ksaPolicy.ts +554 -0
- package/convex/cloud/mail.ts +92 -0
- package/convex/cloud/schema.ts +322 -0
- package/convex/cloud/utils/kanbanContext.ts +229 -0
- package/convex/cloud/workflows/agentBoard.ts +451 -0
- package/convex/cloud/workflows/agentPrompt.ts +272 -0
- package/convex/cloud/workflows/agentThread.ts +374 -0
- package/convex/cloud/workflows/compileSandbox.ts +146 -0
- package/convex/cloud/workflows/crudBoard.ts +217 -0
- package/convex/cloud/workflows/crudKSAs.ts +262 -0
- package/convex/cloud/workflows/crudLorobeads.ts +371 -0
- package/convex/cloud/workflows/crudSkills.ts +205 -0
- package/convex/cloud/workflows/crudThreads.ts +708 -0
- package/convex/cloud/workflows/lifecycleSandbox.ts +1396 -0
- package/convex/cloud/workflows/sandboxConvex.ts +1046 -0
- package/convex/sandbox/README.md +90 -0
- package/convex/sandbox/_generated/api.d.ts +2934 -0
- package/convex/sandbox/_generated/api.js +23 -0
- package/convex/sandbox/_generated/dataModel.d.ts +60 -0
- package/convex/sandbox/_generated/server.d.ts +143 -0
- package/convex/sandbox/_generated/server.js +93 -0
- package/convex/sandbox/actions/bash.ts +130 -0
- package/convex/sandbox/actions/browser.ts +282 -0
- package/convex/sandbox/actions/file.ts +336 -0
- package/convex/sandbox/actions/lsp.ts +325 -0
- package/convex/sandbox/actions/pdf.ts +119 -0
- package/convex/sandbox/agent/codeExecLoop.ts +535 -0
- package/convex/sandbox/agent/decisions.ts +284 -0
- package/convex/sandbox/agent/index.ts +515 -0
- package/convex/sandbox/agent/subagents.ts +651 -0
- package/convex/sandbox/brandResearch/index.ts +417 -0
- package/convex/sandbox/context/index.ts +7 -0
- package/convex/sandbox/context/session.ts +402 -0
- package/convex/sandbox/convex.config.ts +17 -0
- package/convex/sandbox/index.ts +51 -0
- package/convex/sandbox/nodeActions/codeExec.ts +130 -0
- package/convex/sandbox/planning/beads.ts +187 -0
- package/convex/sandbox/planning/index.ts +8 -0
- package/convex/sandbox/planning/sync.ts +194 -0
- package/convex/sandbox/prompts/codeExec.ts +852 -0
- package/convex/sandbox/prompts/modes.ts +231 -0
- package/convex/sandbox/prompts/system.ts +142 -0
- package/convex/sandbox/schema.ts +510 -0
- package/convex/sandbox/state/artifacts.ts +99 -0
- package/convex/sandbox/state/checkpoints.ts +341 -0
- package/convex/sandbox/state/files.ts +383 -0
- package/convex/sandbox/state/index.ts +10 -0
- package/convex/sandbox/state/verification.actions.ts +268 -0
- package/convex/sandbox/state/verification.ts +101 -0
- package/convex/sandbox/tsconfig.json +25 -0
- package/convex/sandbox/utils/codeExecHelpers.ts +52 -0
- package/dist/cli/commands/build.d.ts +19 -0
- package/dist/cli/commands/build.d.ts.map +1 -0
- package/dist/cli/commands/build.js +223 -0
- package/dist/cli/commands/init.d.ts +16 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +148 -0
- package/dist/cli/commands/publish.d.ts +12 -0
- package/dist/cli/commands/publish.d.ts.map +1 -0
- package/dist/cli/commands/publish.js +33 -0
- package/dist/cli/index.d.ts +14 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +40 -0
- package/dist/sdk/builders.d.ts +104 -0
- package/dist/sdk/builders.d.ts.map +1 -0
- package/dist/sdk/builders.js +214 -0
- package/dist/sdk/index.d.ts +29 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +38 -0
- package/dist/sdk/types.d.ts +107 -0
- package/dist/sdk/types.d.ts.map +1 -0
- package/dist/sdk/types.js +6 -0
- package/ksa/README.md +263 -0
- package/ksa/_generated/REFERENCE.md +2954 -0
- package/ksa/_generated/registry.ts +257 -0
- package/ksa/_shared/configReader.ts +302 -0
- package/ksa/_shared/configSchemas.ts +649 -0
- package/ksa/_shared/gateway.ts +175 -0
- package/ksa/_shared/ksaBehaviors.ts +411 -0
- package/ksa/_shared/ksaProxy.ts +248 -0
- package/ksa/_shared/localDb.ts +302 -0
- package/ksa/index.ts +134 -0
- package/package.json +93 -0
- package/runtime/browser/agent-browser.ts +330 -0
- package/runtime/entrypoint.ts +194 -0
- package/runtime/lsp/manager.ts +366 -0
- package/runtime/pdf/pdf-generator.ts +50 -0
- package/runtime/pdf/renderer.ts +357 -0
- package/runtime/pdf/schema.ts +97 -0
- package/runtime/services/file-watcher.ts +191 -0
- package/template/build.ts +307 -0
- package/template/e2b/Dockerfile +69 -0
- package/template/e2b/e2b.toml +13 -0
- package/template/e2b/prebuild.sh +68 -0
- package/template/e2b/start.sh +14 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"use node";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Browser Actions
|
|
5
|
+
*
|
|
6
|
+
* Internal actions for browser automation using agent-browser CLI.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { internalAction } from "../_generated/server";
|
|
10
|
+
import { v } from "convex/values";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Navigate to a URL
|
|
14
|
+
*/
|
|
15
|
+
export const open = internalAction({
|
|
16
|
+
args: {
|
|
17
|
+
url: v.string(),
|
|
18
|
+
},
|
|
19
|
+
handler: async (ctx, args) => {
|
|
20
|
+
try {
|
|
21
|
+
const { execSync } = await import("child_process");
|
|
22
|
+
|
|
23
|
+
execSync(`agent-browser open "${args.url}"`, {
|
|
24
|
+
encoding: "utf8",
|
|
25
|
+
timeout: 30000,
|
|
26
|
+
env: {
|
|
27
|
+
...process.env,
|
|
28
|
+
HOME: "/home/user",
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return { success: true, url: args.url };
|
|
33
|
+
} catch (error: any) {
|
|
34
|
+
return { success: false, error: error.message };
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get page snapshot with interactive elements
|
|
41
|
+
*/
|
|
42
|
+
export const snapshot = internalAction({
|
|
43
|
+
args: {
|
|
44
|
+
interactive: v.optional(v.boolean()),
|
|
45
|
+
},
|
|
46
|
+
handler: async (ctx, args) => {
|
|
47
|
+
try {
|
|
48
|
+
const { execSync } = await import("child_process");
|
|
49
|
+
|
|
50
|
+
const cmdArgs = ["snapshot"];
|
|
51
|
+
if (args.interactive !== false) {
|
|
52
|
+
cmdArgs.push("--interactive");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const result = execSync(`agent-browser ${cmdArgs.join(" ")}`, {
|
|
56
|
+
encoding: "utf8",
|
|
57
|
+
timeout: 15000,
|
|
58
|
+
env: {
|
|
59
|
+
...process.env,
|
|
60
|
+
HOME: "/home/user",
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Parse snapshot output
|
|
65
|
+
const snapshot = parseSnapshot(result);
|
|
66
|
+
return { success: true, ...snapshot };
|
|
67
|
+
} catch (error: any) {
|
|
68
|
+
return { success: false, error: error.message, elements: [] };
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Click an element by ref
|
|
75
|
+
*/
|
|
76
|
+
export const click = internalAction({
|
|
77
|
+
args: {
|
|
78
|
+
ref: v.string(),
|
|
79
|
+
},
|
|
80
|
+
handler: async (ctx, args) => {
|
|
81
|
+
try {
|
|
82
|
+
const { execSync } = await import("child_process");
|
|
83
|
+
|
|
84
|
+
execSync(`agent-browser click "${args.ref}"`, {
|
|
85
|
+
encoding: "utf8",
|
|
86
|
+
timeout: 10000,
|
|
87
|
+
env: {
|
|
88
|
+
...process.env,
|
|
89
|
+
HOME: "/home/user",
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return { success: true };
|
|
94
|
+
} catch (error: any) {
|
|
95
|
+
return { success: false, error: error.message };
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Type text into focused element
|
|
102
|
+
*/
|
|
103
|
+
export const type = internalAction({
|
|
104
|
+
args: {
|
|
105
|
+
text: v.string(),
|
|
106
|
+
},
|
|
107
|
+
handler: async (ctx, args) => {
|
|
108
|
+
try {
|
|
109
|
+
const { execSync } = await import("child_process");
|
|
110
|
+
|
|
111
|
+
// Escape text for shell
|
|
112
|
+
const escaped = args.text.replace(/"/g, '\\"');
|
|
113
|
+
|
|
114
|
+
execSync(`agent-browser type "${escaped}"`, {
|
|
115
|
+
encoding: "utf8",
|
|
116
|
+
timeout: 10000,
|
|
117
|
+
env: {
|
|
118
|
+
...process.env,
|
|
119
|
+
HOME: "/home/user",
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
return { success: true };
|
|
124
|
+
} catch (error: any) {
|
|
125
|
+
return { success: false, error: error.message };
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Press a keyboard key
|
|
132
|
+
*/
|
|
133
|
+
export const press = internalAction({
|
|
134
|
+
args: {
|
|
135
|
+
key: v.string(),
|
|
136
|
+
},
|
|
137
|
+
handler: async (ctx, args) => {
|
|
138
|
+
try {
|
|
139
|
+
const { execSync } = await import("child_process");
|
|
140
|
+
|
|
141
|
+
execSync(`agent-browser press "${args.key}"`, {
|
|
142
|
+
encoding: "utf8",
|
|
143
|
+
timeout: 5000,
|
|
144
|
+
env: {
|
|
145
|
+
...process.env,
|
|
146
|
+
HOME: "/home/user",
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return { success: true };
|
|
151
|
+
} catch (error: any) {
|
|
152
|
+
return { success: false, error: error.message };
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Scroll the page
|
|
159
|
+
*/
|
|
160
|
+
export const scroll = internalAction({
|
|
161
|
+
args: {
|
|
162
|
+
direction: v.union(
|
|
163
|
+
v.literal("up"),
|
|
164
|
+
v.literal("down"),
|
|
165
|
+
v.literal("top"),
|
|
166
|
+
v.literal("bottom")
|
|
167
|
+
),
|
|
168
|
+
},
|
|
169
|
+
handler: async (ctx, args) => {
|
|
170
|
+
try {
|
|
171
|
+
const { execSync } = await import("child_process");
|
|
172
|
+
|
|
173
|
+
execSync(`agent-browser scroll ${args.direction}`, {
|
|
174
|
+
encoding: "utf8",
|
|
175
|
+
timeout: 5000,
|
|
176
|
+
env: {
|
|
177
|
+
...process.env,
|
|
178
|
+
HOME: "/home/user",
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return { success: true };
|
|
183
|
+
} catch (error: any) {
|
|
184
|
+
return { success: false, error: error.message };
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Take a screenshot
|
|
191
|
+
*/
|
|
192
|
+
export const screenshot = internalAction({
|
|
193
|
+
args: {},
|
|
194
|
+
handler: async (ctx) => {
|
|
195
|
+
try {
|
|
196
|
+
const { execSync } = await import("child_process");
|
|
197
|
+
|
|
198
|
+
const result = execSync(`agent-browser screenshot --format base64`, {
|
|
199
|
+
encoding: "utf8",
|
|
200
|
+
timeout: 10000,
|
|
201
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
202
|
+
env: {
|
|
203
|
+
...process.env,
|
|
204
|
+
HOME: "/home/user",
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
return { success: true, screenshot: result.trim() };
|
|
209
|
+
} catch (error: any) {
|
|
210
|
+
return { success: false, error: error.message };
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Close browser session
|
|
217
|
+
*/
|
|
218
|
+
export const close = internalAction({
|
|
219
|
+
args: {},
|
|
220
|
+
handler: async (ctx) => {
|
|
221
|
+
try {
|
|
222
|
+
const { execSync } = await import("child_process");
|
|
223
|
+
|
|
224
|
+
execSync("agent-browser close", {
|
|
225
|
+
encoding: "utf8",
|
|
226
|
+
timeout: 5000,
|
|
227
|
+
env: {
|
|
228
|
+
...process.env,
|
|
229
|
+
HOME: "/home/user",
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
return { success: true };
|
|
234
|
+
} catch (error: any) {
|
|
235
|
+
// Ignore errors on close
|
|
236
|
+
return { success: true };
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// ============================================
|
|
242
|
+
// Helpers
|
|
243
|
+
// ============================================
|
|
244
|
+
|
|
245
|
+
function parseSnapshot(output: string): {
|
|
246
|
+
url: string;
|
|
247
|
+
title: string;
|
|
248
|
+
elements: Array<{
|
|
249
|
+
ref: string;
|
|
250
|
+
tag: string;
|
|
251
|
+
text?: string;
|
|
252
|
+
}>;
|
|
253
|
+
} {
|
|
254
|
+
const lines = output.split("\n");
|
|
255
|
+
const elements: Array<{ ref: string; tag: string; text?: string }> = [];
|
|
256
|
+
let url = "";
|
|
257
|
+
let title = "";
|
|
258
|
+
|
|
259
|
+
for (const line of lines) {
|
|
260
|
+
if (line.startsWith("URL:")) {
|
|
261
|
+
url = line.slice(4).trim();
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (line.startsWith("Title:")) {
|
|
266
|
+
title = line.slice(6).trim();
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Parse element refs like "@e1 button[Login]"
|
|
271
|
+
const refMatch = line.match(/^(@e\d+)\s+(\w+)(?:\[(.+?)\])?/);
|
|
272
|
+
if (refMatch) {
|
|
273
|
+
elements.push({
|
|
274
|
+
ref: refMatch[1],
|
|
275
|
+
tag: refMatch[2],
|
|
276
|
+
text: refMatch[3],
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return { url, title, elements };
|
|
282
|
+
}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
"use node";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* File Actions
|
|
5
|
+
*
|
|
6
|
+
* Internal actions for file system operations.
|
|
7
|
+
* These run in Node.js context and can use fs, child_process, etc.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { internalAction } from "../_generated/server";
|
|
11
|
+
import { v } from "convex/values";
|
|
12
|
+
|
|
13
|
+
// ============================================
|
|
14
|
+
// Read File
|
|
15
|
+
// ============================================
|
|
16
|
+
|
|
17
|
+
export const readFile = internalAction({
|
|
18
|
+
args: {
|
|
19
|
+
path: v.string(),
|
|
20
|
+
encoding: v.optional(v.string()),
|
|
21
|
+
},
|
|
22
|
+
handler: async (ctx, args) => {
|
|
23
|
+
const { readFile: fsReadFile, stat } = await import("fs/promises");
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const encoding = (args.encoding || "utf8") as BufferEncoding;
|
|
27
|
+
const content = await fsReadFile(args.path, encoding);
|
|
28
|
+
const stats = await stat(args.path);
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
success: true,
|
|
32
|
+
content,
|
|
33
|
+
path: args.path,
|
|
34
|
+
size: stats.size,
|
|
35
|
+
};
|
|
36
|
+
} catch (error: any) {
|
|
37
|
+
return {
|
|
38
|
+
success: false,
|
|
39
|
+
error: error.message,
|
|
40
|
+
path: args.path,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// ============================================
|
|
47
|
+
// Write File
|
|
48
|
+
// ============================================
|
|
49
|
+
|
|
50
|
+
export const writeFile = internalAction({
|
|
51
|
+
args: {
|
|
52
|
+
path: v.string(),
|
|
53
|
+
content: v.string(),
|
|
54
|
+
createDirs: v.optional(v.boolean()),
|
|
55
|
+
},
|
|
56
|
+
handler: async (ctx, args) => {
|
|
57
|
+
const { writeFile: fsWriteFile, mkdir, stat } = await import("fs/promises");
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
// Check if file exists
|
|
61
|
+
try {
|
|
62
|
+
await stat(args.path);
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
error: "File already exists. Use editFile to modify existing files.",
|
|
66
|
+
path: args.path,
|
|
67
|
+
};
|
|
68
|
+
} catch {
|
|
69
|
+
// File doesn't exist, good to proceed
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Create parent directories if needed
|
|
73
|
+
if (args.createDirs !== false) {
|
|
74
|
+
const dir = args.path.substring(0, args.path.lastIndexOf("/"));
|
|
75
|
+
if (dir) {
|
|
76
|
+
await mkdir(dir, { recursive: true });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Write file
|
|
81
|
+
await fsWriteFile(args.path, args.content, "utf8");
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
success: true,
|
|
85
|
+
path: args.path,
|
|
86
|
+
size: args.content.length,
|
|
87
|
+
};
|
|
88
|
+
} catch (error: any) {
|
|
89
|
+
return {
|
|
90
|
+
success: false,
|
|
91
|
+
error: error.message,
|
|
92
|
+
path: args.path,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// ============================================
|
|
99
|
+
// Edit File
|
|
100
|
+
// ============================================
|
|
101
|
+
|
|
102
|
+
export const editFile = internalAction({
|
|
103
|
+
args: {
|
|
104
|
+
path: v.string(),
|
|
105
|
+
oldContent: v.string(),
|
|
106
|
+
newContent: v.string(),
|
|
107
|
+
},
|
|
108
|
+
handler: async (ctx, args) => {
|
|
109
|
+
const { readFile: fsReadFile, writeFile: fsWriteFile } = await import("fs/promises");
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
// Read current content
|
|
113
|
+
const currentContent = await fsReadFile(args.path, "utf8");
|
|
114
|
+
|
|
115
|
+
// Validate precondition
|
|
116
|
+
if (!currentContent.includes(args.oldContent)) {
|
|
117
|
+
return {
|
|
118
|
+
success: false,
|
|
119
|
+
error: "Precondition failed: old_content not found in file.",
|
|
120
|
+
path: args.path,
|
|
121
|
+
hint: "Read the file first to get current content",
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Apply edit
|
|
126
|
+
const updatedContent = currentContent.replace(args.oldContent, args.newContent);
|
|
127
|
+
|
|
128
|
+
// Generate simple diff
|
|
129
|
+
const diff = generateDiff(currentContent, updatedContent, args.path);
|
|
130
|
+
|
|
131
|
+
// Write updated content
|
|
132
|
+
await fsWriteFile(args.path, updatedContent, "utf8");
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
success: true,
|
|
136
|
+
path: args.path,
|
|
137
|
+
diff,
|
|
138
|
+
previousContent: currentContent,
|
|
139
|
+
newContent: updatedContent,
|
|
140
|
+
};
|
|
141
|
+
} catch (error: any) {
|
|
142
|
+
return {
|
|
143
|
+
success: false,
|
|
144
|
+
error: error.message,
|
|
145
|
+
path: args.path,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// ============================================
|
|
152
|
+
// Glob Files
|
|
153
|
+
// ============================================
|
|
154
|
+
|
|
155
|
+
export const globFiles = internalAction({
|
|
156
|
+
args: {
|
|
157
|
+
pattern: v.string(),
|
|
158
|
+
cwd: v.optional(v.string()),
|
|
159
|
+
maxResults: v.optional(v.number()),
|
|
160
|
+
},
|
|
161
|
+
handler: async (ctx, args) => {
|
|
162
|
+
try {
|
|
163
|
+
const { execSync } = await import("child_process");
|
|
164
|
+
const cwd = args.cwd || "/home/user/workspace";
|
|
165
|
+
const max = args.maxResults || 100;
|
|
166
|
+
|
|
167
|
+
// Use shell find with pattern matching
|
|
168
|
+
// Convert glob pattern to find-compatible pattern
|
|
169
|
+
const pattern = args.pattern;
|
|
170
|
+
let cmd: string;
|
|
171
|
+
|
|
172
|
+
if (pattern.includes("**")) {
|
|
173
|
+
// Recursive glob - use find with -name
|
|
174
|
+
const name = pattern.split("/").pop() || "*";
|
|
175
|
+
cmd = `find "${cwd}" -type f -name "${name}" 2>/dev/null | head -${max + 1}`;
|
|
176
|
+
} else if (pattern.includes("*")) {
|
|
177
|
+
// Simple glob - use find with -name
|
|
178
|
+
cmd = `find "${cwd}" -maxdepth 1 -type f -name "${pattern}" 2>/dev/null | head -${max + 1}`;
|
|
179
|
+
} else {
|
|
180
|
+
// Exact path
|
|
181
|
+
cmd = `find "${cwd}" -type f -path "*${pattern}" 2>/dev/null | head -${max + 1}`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const output = execSync(cmd, {
|
|
185
|
+
encoding: "utf8",
|
|
186
|
+
cwd,
|
|
187
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
188
|
+
}).trim();
|
|
189
|
+
|
|
190
|
+
const files = output ? output.split("\n").filter(Boolean) : [];
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
success: true,
|
|
194
|
+
files: files.slice(0, max),
|
|
195
|
+
count: files.length,
|
|
196
|
+
truncated: files.length > max,
|
|
197
|
+
};
|
|
198
|
+
} catch (error: any) {
|
|
199
|
+
return {
|
|
200
|
+
success: false,
|
|
201
|
+
error: error.message,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// ============================================
|
|
208
|
+
// Grep Files
|
|
209
|
+
// ============================================
|
|
210
|
+
|
|
211
|
+
export const grepFiles = internalAction({
|
|
212
|
+
args: {
|
|
213
|
+
pattern: v.string(),
|
|
214
|
+
path: v.optional(v.string()),
|
|
215
|
+
fileGlob: v.optional(v.string()),
|
|
216
|
+
maxMatches: v.optional(v.number()),
|
|
217
|
+
},
|
|
218
|
+
handler: async (ctx, args) => {
|
|
219
|
+
try {
|
|
220
|
+
const { execSync } = await import("child_process");
|
|
221
|
+
const searchPath = args.path || "/home/user/workspace";
|
|
222
|
+
const max = args.maxMatches || 50;
|
|
223
|
+
|
|
224
|
+
let cmd = `grep -rn "${args.pattern}" "${searchPath}"`;
|
|
225
|
+
if (args.fileGlob) {
|
|
226
|
+
cmd += ` --include="${args.fileGlob}"`;
|
|
227
|
+
}
|
|
228
|
+
cmd += ` 2>/dev/null | head -${max}`;
|
|
229
|
+
|
|
230
|
+
const output = execSync(cmd, {
|
|
231
|
+
encoding: "utf8",
|
|
232
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
233
|
+
}).trim();
|
|
234
|
+
|
|
235
|
+
const matches = output
|
|
236
|
+
.split("\n")
|
|
237
|
+
.filter(Boolean)
|
|
238
|
+
.map((line) => {
|
|
239
|
+
const [filePath, lineNum, ...rest] = line.split(":");
|
|
240
|
+
return {
|
|
241
|
+
file: filePath,
|
|
242
|
+
line: parseInt(lineNum, 10),
|
|
243
|
+
content: rest.join(":").trim(),
|
|
244
|
+
};
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
success: true,
|
|
249
|
+
matches,
|
|
250
|
+
count: matches.length,
|
|
251
|
+
};
|
|
252
|
+
} catch (error: any) {
|
|
253
|
+
// grep returns exit code 1 if no matches
|
|
254
|
+
if (error.status === 1) {
|
|
255
|
+
return {
|
|
256
|
+
success: true,
|
|
257
|
+
matches: [],
|
|
258
|
+
count: 0,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
success: false,
|
|
263
|
+
error: error.message,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// ============================================
|
|
270
|
+
// List Directory
|
|
271
|
+
// ============================================
|
|
272
|
+
|
|
273
|
+
export const listDir = internalAction({
|
|
274
|
+
args: {
|
|
275
|
+
path: v.string(),
|
|
276
|
+
showHidden: v.optional(v.boolean()),
|
|
277
|
+
},
|
|
278
|
+
handler: async (ctx, args) => {
|
|
279
|
+
const { readdir } = await import("fs/promises");
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
const entries = await readdir(args.path, { withFileTypes: true });
|
|
283
|
+
|
|
284
|
+
const items = entries
|
|
285
|
+
.filter((e) => args.showHidden || !e.name.startsWith("."))
|
|
286
|
+
.map((e) => ({
|
|
287
|
+
name: e.name,
|
|
288
|
+
type: e.isDirectory() ? "directory" : "file",
|
|
289
|
+
path: `${args.path}/${e.name}`,
|
|
290
|
+
}));
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
success: true,
|
|
294
|
+
items,
|
|
295
|
+
count: items.length,
|
|
296
|
+
};
|
|
297
|
+
} catch (error: any) {
|
|
298
|
+
return {
|
|
299
|
+
success: false,
|
|
300
|
+
error: error.message,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// ============================================
|
|
307
|
+
// Helpers
|
|
308
|
+
// ============================================
|
|
309
|
+
|
|
310
|
+
function generateDiff(oldContent: string, newContent: string, path: string): string {
|
|
311
|
+
const oldLines = oldContent.split("\n");
|
|
312
|
+
const newLines = newContent.split("\n");
|
|
313
|
+
|
|
314
|
+
let diff = `--- a/${path}\n+++ b/${path}\n`;
|
|
315
|
+
|
|
316
|
+
// Simple line-by-line diff
|
|
317
|
+
const maxLines = Math.max(oldLines.length, newLines.length);
|
|
318
|
+
for (let i = 0; i < maxLines; i++) {
|
|
319
|
+
const oldLine = oldLines[i];
|
|
320
|
+
const newLine = newLines[i];
|
|
321
|
+
|
|
322
|
+
if (oldLine === newLine) {
|
|
323
|
+
continue; // Skip unchanged lines for brevity
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (oldLine !== undefined && newLine !== undefined && oldLine !== newLine) {
|
|
327
|
+
diff += `-${oldLine}\n+${newLine}\n`;
|
|
328
|
+
} else if (oldLine !== undefined) {
|
|
329
|
+
diff += `-${oldLine}\n`;
|
|
330
|
+
} else if (newLine !== undefined) {
|
|
331
|
+
diff += `+${newLine}\n`;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return diff;
|
|
336
|
+
}
|