@lasso-ai/cli 1.3.0 → 1.3.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/dist/cli/agent.js +75 -16
- package/dist/cli/bridge.d.ts +5 -0
- package/dist/cli/bridge.js +116 -17
- package/dist/cli/oneshot.js +42 -31
- package/dist/overlay.js +49 -1
- package/package.json +1 -1
package/dist/cli/agent.js
CHANGED
|
@@ -14,13 +14,57 @@ exports.runLocalAgentPrompt = runLocalAgentPrompt;
|
|
|
14
14
|
exports.proposeChanges = proposeChanges;
|
|
15
15
|
exports.answerQuestion = answerQuestion;
|
|
16
16
|
exports.generateCommitMessage = generateCommitMessage;
|
|
17
|
-
const
|
|
17
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
18
18
|
const node_path_1 = __importDefault(require("node:path"));
|
|
19
19
|
const node_child_process_1 = require("node:child_process");
|
|
20
20
|
const node_util_1 = require("node:util");
|
|
21
21
|
const auth_1 = require("./auth");
|
|
22
22
|
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
23
23
|
const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
|
|
24
|
+
function detectProjectStructure(cwd) {
|
|
25
|
+
const pkgPath = node_path_1.default.join(cwd, "package.json");
|
|
26
|
+
let framework = "unknown";
|
|
27
|
+
const directories = [];
|
|
28
|
+
const appDirs = [];
|
|
29
|
+
if (node_fs_1.default.existsSync(pkgPath)) {
|
|
30
|
+
try {
|
|
31
|
+
const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgPath, "utf8"));
|
|
32
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
33
|
+
if (deps.next)
|
|
34
|
+
framework = "Next.js";
|
|
35
|
+
else if (deps.vite)
|
|
36
|
+
framework = "Vite";
|
|
37
|
+
else if (deps.react)
|
|
38
|
+
framework = "React";
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Ignore errors
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Scan for important directories
|
|
45
|
+
const scanDirs = ["src", "app", "pages", "web", "lib", "components"];
|
|
46
|
+
for (const dir of scanDirs) {
|
|
47
|
+
if (node_fs_1.default.existsSync(node_path_1.default.join(cwd, dir))) {
|
|
48
|
+
directories.push(dir);
|
|
49
|
+
if (dir === "app")
|
|
50
|
+
appDirs.push(dir);
|
|
51
|
+
// Check for src/app structure
|
|
52
|
+
if (dir === "src" && node_fs_1.default.existsSync(node_path_1.default.join(cwd, "src", "app"))) {
|
|
53
|
+
directories.push("src/app");
|
|
54
|
+
appDirs.push("src/app");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Determine source root
|
|
59
|
+
let sourceRoot = ".";
|
|
60
|
+
if (directories.includes("src/app"))
|
|
61
|
+
sourceRoot = "src";
|
|
62
|
+
else if (directories.includes("app"))
|
|
63
|
+
sourceRoot = ".";
|
|
64
|
+
else if (directories.includes("src"))
|
|
65
|
+
sourceRoot = "src";
|
|
66
|
+
return { framework, sourceRoot, directories, multipleAppDirs: appDirs };
|
|
67
|
+
}
|
|
24
68
|
async function installPackages(cwd, packages, onProgress) {
|
|
25
69
|
const validPkgs = [...new Set(packages
|
|
26
70
|
.map((p) => p.trim().replace(/^['"`]|['"`]$/g, ""))
|
|
@@ -31,7 +75,7 @@ async function installPackages(cwd, packages, onProgress) {
|
|
|
31
75
|
let existingDeps = {};
|
|
32
76
|
try {
|
|
33
77
|
const pkgJsonPath = node_path_1.default.join(cwd, "package.json");
|
|
34
|
-
const raw =
|
|
78
|
+
const raw = node_fs_1.default.readFileSync(pkgJsonPath, "utf8");
|
|
35
79
|
const parsed = JSON.parse(raw);
|
|
36
80
|
existingDeps = { ...(parsed.dependencies || {}), ...(parsed.devDependencies || {}) };
|
|
37
81
|
}
|
|
@@ -42,7 +86,7 @@ async function installPackages(cwd, packages, onProgress) {
|
|
|
42
86
|
// Detect package manager
|
|
43
87
|
let cmd = "npm install";
|
|
44
88
|
try {
|
|
45
|
-
const rootFiles =
|
|
89
|
+
const rootFiles = node_fs_1.default.readdirSync(cwd);
|
|
46
90
|
if (rootFiles.includes("bun.lockb") || rootFiles.includes("bun.lock"))
|
|
47
91
|
cmd = "bun add";
|
|
48
92
|
else if (rootFiles.includes("pnpm-lock.yaml"))
|
|
@@ -165,7 +209,7 @@ const ignored = new Set(["node_modules", ".git", ".next", "dist", "build", ".tur
|
|
|
165
209
|
const sourceExtensions = /\.(tsx?|jsx?|vue|svelte|css|scss|html)$/i;
|
|
166
210
|
const layoutFiles = /^(layout|_layout|_app|app|_document|document|template|_template)\.(tsx?|jsx?)$/i;
|
|
167
211
|
const sourceFileCache = new Map();
|
|
168
|
-
|
|
212
|
+
function sourceFiles(directory, budget = { remaining: 25 }) {
|
|
169
213
|
if (budget.remaining <= 0)
|
|
170
214
|
return [];
|
|
171
215
|
const cached = sourceFileCache.get(directory);
|
|
@@ -174,7 +218,7 @@ async function sourceFiles(directory, budget = { remaining: 25 }) {
|
|
|
174
218
|
budget.remaining -= files.length;
|
|
175
219
|
return files;
|
|
176
220
|
}
|
|
177
|
-
const entries =
|
|
221
|
+
const entries = node_fs_1.default.readdirSync(directory, { withFileTypes: true });
|
|
178
222
|
const files = [];
|
|
179
223
|
const layoutFilesList = [];
|
|
180
224
|
const otherFiles = [];
|
|
@@ -185,7 +229,7 @@ async function sourceFiles(directory, budget = { remaining: 25 }) {
|
|
|
185
229
|
if (budget.remaining <= 0)
|
|
186
230
|
break;
|
|
187
231
|
if (entry.isDirectory()) {
|
|
188
|
-
const nested =
|
|
232
|
+
const nested = sourceFiles(fullPath, budget);
|
|
189
233
|
files.push(...nested);
|
|
190
234
|
}
|
|
191
235
|
else if (sourceExtensions.test(entry.name)) {
|
|
@@ -203,10 +247,21 @@ async function sourceFiles(directory, budget = { remaining: 25 }) {
|
|
|
203
247
|
sourceFileCache.set(directory, { expiresAt: Date.now() + 30000, files });
|
|
204
248
|
return files;
|
|
205
249
|
}
|
|
206
|
-
|
|
250
|
+
function contextFor(cwd, element) {
|
|
207
251
|
const needle = element.label.replace(/^[^.#]+[.#]?/, "");
|
|
208
252
|
const hintedPath = element.sourceHint?.split(":")[0];
|
|
209
253
|
const sourceFile = hintedPath ? node_path_1.default.basename(hintedPath) : "";
|
|
254
|
+
// Add project structure information at the top
|
|
255
|
+
const structure = detectProjectStructure(cwd);
|
|
256
|
+
let projectStructureInfo = `PROJECT STRUCTURE:
|
|
257
|
+
- Framework: ${structure.framework}
|
|
258
|
+
- Source root: ${structure.sourceRoot}
|
|
259
|
+
- Directories found: ${structure.directories.join(", ") || "root"}
|
|
260
|
+
- Multiple app directories: ${structure.multipleAppDirs.length > 0 ? structure.multipleAppDirs.join(", ") : "none"}
|
|
261
|
+
- IMPORTANT: When creating files, use the existing directory structure. Do not create duplicate directories (e.g., if "src/app/" exists, use it instead of creating a new "app/").
|
|
262
|
+
- Path aliases: Check tsconfig.json or jsconfig.json for path aliases like "@/components"
|
|
263
|
+
|
|
264
|
+
`;
|
|
210
265
|
// Extract unique class names and attributes from the element for better matching
|
|
211
266
|
const classMatches = element.html?.match(/class="([^"]+)"/);
|
|
212
267
|
const classNames = classMatches ? classMatches[1].split(/\s+/).filter((c) => c.length > 3) : [];
|
|
@@ -223,20 +278,20 @@ async function contextFor(cwd, element) {
|
|
|
223
278
|
candidates.push(node_path_1.default.join(cwd, normalizedHint.slice(srcIndex + 1)));
|
|
224
279
|
for (const candidate of candidates) {
|
|
225
280
|
try {
|
|
226
|
-
const content =
|
|
227
|
-
return
|
|
281
|
+
const content = node_fs_1.default.readFileSync(candidate, "utf8");
|
|
282
|
+
return `${projectStructureInfo}FILE: ${node_path_1.default.relative(cwd, candidate)}\n${content.slice(0, 16000)}`;
|
|
228
283
|
}
|
|
229
284
|
catch {
|
|
230
285
|
// The runtime source hint can point to a different checkout.
|
|
231
286
|
}
|
|
232
287
|
}
|
|
233
288
|
}
|
|
234
|
-
const files =
|
|
289
|
+
const files = sourceFiles(cwd);
|
|
235
290
|
const snippets = [];
|
|
236
291
|
const scoredFiles = [];
|
|
237
|
-
const results =
|
|
292
|
+
const results = files.map((file) => {
|
|
238
293
|
try {
|
|
239
|
-
const content =
|
|
294
|
+
const content = node_fs_1.default.readFileSync(file, "utf8");
|
|
240
295
|
let score = 0;
|
|
241
296
|
// Score files based on relevance
|
|
242
297
|
if (sourceFile && file.endsWith(sourceFile))
|
|
@@ -264,7 +319,7 @@ async function contextFor(cwd, element) {
|
|
|
264
319
|
// A file can disappear while a dev server is rebuilding; skip it.
|
|
265
320
|
}
|
|
266
321
|
return null;
|
|
267
|
-
}));
|
|
322
|
+
}).filter((r) => r !== null);
|
|
268
323
|
// Sort by score and take top files
|
|
269
324
|
const sortedResults = results.filter((r) => r !== null).sort((a, b) => b.score - a.score);
|
|
270
325
|
for (const result of sortedResults) {
|
|
@@ -272,7 +327,7 @@ async function contextFor(cwd, element) {
|
|
|
272
327
|
snippets.push(`FILE: ${node_path_1.default.relative(cwd, result.file)}\n${result.content.slice(0, 6000)}`);
|
|
273
328
|
}
|
|
274
329
|
}
|
|
275
|
-
return snippets.join("\n\n---\n\n");
|
|
330
|
+
return projectStructureInfo + snippets.join("\n\n---\n\n");
|
|
276
331
|
}
|
|
277
332
|
function jsonObjectCandidates(text) {
|
|
278
333
|
const candidates = [];
|
|
@@ -934,7 +989,7 @@ async function runLocalAgentPrompt(cwd, config, prompt, signal, onProgress, task
|
|
|
934
989
|
return extractLocalAgentText(result.stdout, provider);
|
|
935
990
|
}
|
|
936
991
|
async function proposeChanges(cwd, input, config, signal, onProgress, onPrompt) {
|
|
937
|
-
const context =
|
|
992
|
+
const context = contextFor(cwd, input.element);
|
|
938
993
|
const runtimeErrors = input.context?.runtimeErrors || [];
|
|
939
994
|
const visualContext = input.context ? { ...input.context, screenshots: undefined, runtimeErrors: undefined } : undefined;
|
|
940
995
|
const history = input.messages?.map((message) => `${message.role}: ${message.content}`).join("\n") || input.instruction;
|
|
@@ -945,6 +1000,8 @@ async function proposeChanges(cwd, input, config, signal, onProgress, onPrompt)
|
|
|
945
1000
|
const runtimeErrorHint = runtimeErrors.length
|
|
946
1001
|
? `\n\nLIVE CONSOLE/RUNTIME ERRORS (reported by the user's browser — the page is currently broken by these):\n${runtimeErrors.map((error) => `! ${error}`).join("\n")}`
|
|
947
1002
|
: "";
|
|
1003
|
+
// Detect project structure for instruction
|
|
1004
|
+
const structure = detectProjectStructure(cwd);
|
|
948
1005
|
const instruction = `Selection context:
|
|
949
1006
|
${JSON.stringify(input.element, null, 2)}
|
|
950
1007
|
${JSON.stringify(visualContext, null, 2)}
|
|
@@ -962,6 +1019,8 @@ Relevant source context:
|
|
|
962
1019
|
${context || "No matching source context was found. Ask for a more specific selection rather than inventing a file."}${dragHint}${runtimeErrorHint}
|
|
963
1020
|
|
|
964
1021
|
CRITICAL INSTRUCTIONS:
|
|
1022
|
+
0. PROJECT STRUCTURE AWARENESS: This project has specific directory structure. Before creating or moving files, verify the actual structure. Do not assume standard locations.
|
|
1023
|
+
${structure.multipleAppDirs.length > 0 ? ` WARNING: Multiple app directories detected: ${structure.multipleAppDirs.join(", ")}. Use the correct one for this project.\n` : ""}
|
|
965
1024
|
1. You are an autonomous agent with the ability to install npm packages and edit source code.
|
|
966
1025
|
2. If the user's request requires or asks for third-party libraries, icon packs, animation tools, or utility packages (e.g. icon libraries, motion, charts, UI primitives, etc.), determine the best npm package for this project and declare them in the "packages" array (e.g. ["@iconify/react", "@hugeicons/react"]).
|
|
967
1026
|
3. In the "changes" array, propose concrete, minimal edits to the source code to implement the request. You may freely import and use the packages you specified in "packages".
|
|
@@ -1256,7 +1315,7 @@ RULES:
|
|
|
1256
1315
|
return finalProposal;
|
|
1257
1316
|
}
|
|
1258
1317
|
async function answerQuestion(cwd, input, config, signal, onProgress, onPrompt) {
|
|
1259
|
-
const context =
|
|
1318
|
+
const context = contextFor(cwd, input.element);
|
|
1260
1319
|
const history = input.messages?.map((message) => `${message.role}: ${message.content}`).join("\n") || "None";
|
|
1261
1320
|
const prompt = `Answer the user's question conversationally and directly. Do not propose file changes and do not return JSON. If the question is about the selected UI, use the selection and source context below.\n\nUser question:\n${input.question}\n\nSelected element:\n${JSON.stringify(input.element, null, 2)}\n\nVisual context:\n${JSON.stringify({ ...input.context, screenshots: undefined }, null, 2)}\n\nConversation:\n${history}\n\nRelevant source context:\n${context || "No matching source context was found."}`;
|
|
1262
1321
|
if (config.provider === "claude-code" || config.provider === "codex" || config.provider === "opencode" || config.provider === "cursor") {
|
package/dist/cli/bridge.d.ts
CHANGED
package/dist/cli/bridge.js
CHANGED
|
@@ -147,6 +147,91 @@ function proposalMatchesCurrentSource(cwd, changes) {
|
|
|
147
147
|
return false;
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
|
+
async function validateBuild(cwd) {
|
|
151
|
+
const pkgPath = node_path_1.default.join(cwd, "package.json");
|
|
152
|
+
if (!node_fs_1.default.existsSync(pkgPath)) {
|
|
153
|
+
return { passed: true, errors: [], command: "none" };
|
|
154
|
+
}
|
|
155
|
+
const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgPath, "utf8"));
|
|
156
|
+
const scripts = pkg.scripts || {};
|
|
157
|
+
// Determine which build command to run
|
|
158
|
+
let buildCommand = "";
|
|
159
|
+
if (scripts.build) {
|
|
160
|
+
buildCommand = "build";
|
|
161
|
+
}
|
|
162
|
+
else if (scripts["build:cli"] || scripts["build:overlay"]) {
|
|
163
|
+
// Monorepo with separate build scripts
|
|
164
|
+
buildCommand = scripts["build:cli"] ? "build:cli" : "build:overlay";
|
|
165
|
+
}
|
|
166
|
+
else if (scripts.compile) {
|
|
167
|
+
buildCommand = "compile";
|
|
168
|
+
}
|
|
169
|
+
else if (scripts.check) {
|
|
170
|
+
buildCommand = "check";
|
|
171
|
+
}
|
|
172
|
+
else if (scripts.lint) {
|
|
173
|
+
buildCommand = "lint";
|
|
174
|
+
}
|
|
175
|
+
else if (scripts.typecheck) {
|
|
176
|
+
buildCommand = "typecheck";
|
|
177
|
+
}
|
|
178
|
+
else if (node_fs_1.default.existsSync(node_path_1.default.join(cwd, "tsconfig.json"))) {
|
|
179
|
+
buildCommand = "tsc"; // Run TypeScript compiler directly
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
// No suitable build command found
|
|
183
|
+
return { passed: true, errors: [], command: "none" };
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
const result = await execFileAsync(buildCommand === "tsc" ? "tsc" : "npm", buildCommand === "tsc" ? [] : ["run", buildCommand], { cwd, maxBuffer: 1024 * 1024 * 10, timeout: 60000 });
|
|
187
|
+
const output = result.stdout + result.stderr;
|
|
188
|
+
const errors = [];
|
|
189
|
+
// Parse TypeScript errors
|
|
190
|
+
if (output.includes("error TS")) {
|
|
191
|
+
const tsErrors = output.match(/error TS\d+.*$/gm) || [];
|
|
192
|
+
errors.push(...tsErrors);
|
|
193
|
+
}
|
|
194
|
+
// Parse general build errors
|
|
195
|
+
if (output.includes("Error:") || output.includes("error:")) {
|
|
196
|
+
const lines = output.split("\n");
|
|
197
|
+
for (const line of lines) {
|
|
198
|
+
if (line.includes("Error:") || line.includes("error:")) {
|
|
199
|
+
errors.push(line.trim());
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
passed: errors.length === 0,
|
|
205
|
+
errors,
|
|
206
|
+
command: buildCommand === "tsc" ? "tsc" : `npm run ${buildCommand}`
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
const stderr = error.stderr || "";
|
|
211
|
+
const errors = [];
|
|
212
|
+
if (stderr.includes("error TS")) {
|
|
213
|
+
const tsErrors = stderr.match(/error TS\d+.*$/gm) || [];
|
|
214
|
+
errors.push(...tsErrors);
|
|
215
|
+
}
|
|
216
|
+
if (stderr.includes("Error:") || stderr.includes("error:")) {
|
|
217
|
+
const lines = stderr.split("\n");
|
|
218
|
+
for (const line of lines) {
|
|
219
|
+
if (line.includes("Error:") || line.includes("error:")) {
|
|
220
|
+
errors.push(line.trim());
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// If no specific errors found, include the full stderr
|
|
225
|
+
if (errors.length === 0 && stderr) {
|
|
226
|
+
errors.push(stderr);
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
passed: false,
|
|
230
|
+
errors,
|
|
231
|
+
command: buildCommand === "tsc" ? "tsc" : `npm run ${buildCommand}`
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
150
235
|
function readEnvFile(cwd, filename) {
|
|
151
236
|
try {
|
|
152
237
|
return node_fs_1.default.readFileSync(node_path_1.default.join(cwd, filename), "utf8").split(/\r?\n/).reduce((values, line) => {
|
|
@@ -349,38 +434,45 @@ async function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort
|
|
|
349
434
|
...dynamicModels.nvidia.map(m => ({ id: m.id, label: m.name, provider: m.provider })),
|
|
350
435
|
// Fallback hardcoded models (only if dynamic fetch failed)
|
|
351
436
|
...(dynamicModels.anthropic.length === 0 ? [
|
|
437
|
+
{ id: "claude-opus-4-5-20251101", label: "Claude Opus 4.5", provider: "anthropic" },
|
|
352
438
|
{ id: "claude-sonnet-4-5-20250929", label: "Claude Sonnet 4.5", provider: "anthropic" },
|
|
353
|
-
{ id: "claude-
|
|
439
|
+
{ id: "claude-haiku-4-5-20251015", label: "Claude Haiku 4.5", provider: "anthropic" },
|
|
354
440
|
] : []),
|
|
355
441
|
...(dynamicModels.openai.length === 0 ? [
|
|
356
|
-
{ id: "gpt-
|
|
357
|
-
{ id: "gpt-
|
|
442
|
+
{ id: "gpt-5.1", label: "GPT-5.1", provider: "openai" },
|
|
443
|
+
{ id: "gpt-5.1-codex", label: "GPT-5.1 Codex", provider: "openai" },
|
|
444
|
+
{ id: "gpt-5.1-codex-mini", label: "GPT-5.1 Codex Mini", provider: "openai" },
|
|
445
|
+
{ id: "o3", label: "OpenAI o3", provider: "openai" },
|
|
446
|
+
{ id: "o3-mini", label: "OpenAI o3-mini", provider: "openai" },
|
|
358
447
|
] : []),
|
|
359
448
|
...(dynamicModels.google.length === 0 ? [
|
|
360
|
-
{ id: "gemini-
|
|
361
|
-
{ id: "gemini-
|
|
449
|
+
{ id: "gemini-3-pro-preview", label: "Gemini 3 Pro", provider: "google" },
|
|
450
|
+
{ id: "gemini-3-flash-preview", label: "Gemini 3 Flash", provider: "google" },
|
|
451
|
+
{ id: "gemini-2.5-flash-preview-09-2025", label: "Gemini 2.5 Flash", provider: "google" },
|
|
452
|
+
{ id: "gemini-2.5-flash-lite-preview-09-2025", label: "Gemini 2.5 Flash Lite", provider: "google" },
|
|
362
453
|
] : []),
|
|
363
454
|
...(dynamicModels.nvidia.length === 0 ? [
|
|
364
|
-
{ id: "
|
|
365
|
-
{ id: "
|
|
366
|
-
{ id: "deepseek-ai/deepseek-
|
|
367
|
-
{ id: "google/gemma-
|
|
455
|
+
{ id: "nvidia/llama-3.3-nemotron-super-49b-v1", label: "Llama 3.3 Nemotron Super 49B", provider: "nvidia" },
|
|
456
|
+
{ id: "deepseek-ai/deepseek-r1-distill-llama-70b", label: "DeepSeek R1 Distill Llama 70B", provider: "nvidia" },
|
|
457
|
+
{ id: "deepseek-ai/deepseek-r1-distill-llama-8b", label: "DeepSeek R1 Distill Llama 8B", provider: "nvidia" },
|
|
458
|
+
{ id: "google/gemma-3-1b-it", label: "Gemma 3 1B Instruct", provider: "nvidia" },
|
|
459
|
+
{ id: "openai/gpt-oss-120b", label: "GPT-OSS 120B", provider: "nvidia" },
|
|
368
460
|
] : []),
|
|
369
461
|
// Local CLI agents (always included)
|
|
370
462
|
...(localAgents.has("claude-code") ? [
|
|
371
|
-
{ id: "claude-code:sonnet", label: "Claude Code · Sonnet", provider: "cli" },
|
|
372
|
-
{ id: "claude-code:opus", label: "Claude Code · Opus", provider: "cli" },
|
|
373
|
-
{ id: "claude-code:haiku", label: "Claude Code · Haiku", provider: "cli" },
|
|
463
|
+
{ id: "claude-code:sonnet", label: "Claude Code · Sonnet 4.5", provider: "cli" },
|
|
464
|
+
{ id: "claude-code:opus", label: "Claude Code · Opus 4.5", provider: "cli" },
|
|
465
|
+
{ id: "claude-code:haiku", label: "Claude Code · Haiku 4.5", provider: "cli" },
|
|
374
466
|
] : []),
|
|
375
467
|
...(localAgents.has("codex") ? [
|
|
376
|
-
{ id: "codex:gpt-5", label: "Codex · GPT-5", provider: "cli" },
|
|
377
|
-
{ id: "codex:gpt-5-codex", label: "Codex · GPT-5-Codex", provider: "cli" },
|
|
468
|
+
{ id: "codex:gpt-5.1", label: "Codex · GPT-5.1", provider: "cli" },
|
|
469
|
+
{ id: "codex:gpt-5.1-codex", label: "Codex · GPT-5.1-Codex", provider: "cli" },
|
|
378
470
|
] : []),
|
|
379
471
|
...(localAgents.has("opencode") ? [
|
|
380
472
|
{ id: "opencode:opencode/big-pickle", label: "OpenCode · Big Pickle", provider: "cli" },
|
|
381
473
|
{ id: "opencode:deepseek/deepseek-chat", label: "OpenCode · DeepSeek Chat", provider: "cli" },
|
|
382
|
-
{ id: "opencode:anthropic/claude-sonnet-4-5", label: "OpenCode · Claude Sonnet", provider: "cli" },
|
|
383
|
-
{ id: "opencode:openai/gpt-5", label: "OpenCode · GPT-5", provider: "cli" },
|
|
474
|
+
{ id: "opencode:anthropic/claude-sonnet-4-5", label: "OpenCode · Claude Sonnet 4.5", provider: "cli" },
|
|
475
|
+
{ id: "opencode:openai/gpt-5.1", label: "OpenCode · GPT-5.1", provider: "cli" },
|
|
384
476
|
] : []),
|
|
385
477
|
...(localAgents.has("cursor") ? [
|
|
386
478
|
{ id: "cursor:claude-sonnet-4.5", label: "Cursor · Claude Sonnet 4.5", provider: "cli" },
|
|
@@ -902,7 +994,14 @@ async function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort
|
|
|
902
994
|
node_fs_1.default.mkdirSync(node_path_1.default.dirname(filePath), { recursive: true });
|
|
903
995
|
node_fs_1.default.writeFileSync(filePath, nextContent);
|
|
904
996
|
}
|
|
905
|
-
|
|
997
|
+
// Run build validation after applying changes
|
|
998
|
+
const validation = await validateBuild(cwd);
|
|
999
|
+
socket.send(JSON.stringify({
|
|
1000
|
+
type: "applied",
|
|
1001
|
+
taskId: msg.taskId,
|
|
1002
|
+
message: `${msg.changes.length} file${msg.changes.length === 1 ? "" : "s"} updated. Your dev server will reload.`,
|
|
1003
|
+
validation
|
|
1004
|
+
}));
|
|
906
1005
|
}
|
|
907
1006
|
catch (error) {
|
|
908
1007
|
// Validation happens before writes, but restore this task's snapshot
|
package/dist/cli/oneshot.js
CHANGED
|
@@ -10,6 +10,7 @@ const node_child_process_1 = require("node:child_process");
|
|
|
10
10
|
const node_util_1 = require("node:util");
|
|
11
11
|
const agent_1 = require("./agent");
|
|
12
12
|
const auth_1 = require("./auth");
|
|
13
|
+
const framework_1 = require("./utils/framework");
|
|
13
14
|
const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
|
|
14
15
|
const CLI_PROVIDERS = new Set(["claude-code", "codex", "opencode", "cursor"]);
|
|
15
16
|
function isCliProvider(provider) {
|
|
@@ -225,7 +226,7 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
|
|
|
225
226
|
}
|
|
226
227
|
// Skip project structure analysis for simple conversational queries
|
|
227
228
|
const projectStructure = isConversational ? null : await analyzeProject(cwd);
|
|
228
|
-
const framework = isConversational ? null : detectFramework(cwd);
|
|
229
|
+
const framework = isConversational ? null : (0, framework_1.detectFramework)(cwd);
|
|
229
230
|
if (!isConversational && !pageContext) {
|
|
230
231
|
onProgress("thinking", "Inspecting application...", `Detected ${framework} framework`);
|
|
231
232
|
}
|
|
@@ -314,6 +315,11 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
|
|
|
314
315
|
dependencies: projectStructure?.dependencies,
|
|
315
316
|
devDependencies: projectStructure?.devDependencies,
|
|
316
317
|
files: projectStructure?.files.slice(0, 100) || [],
|
|
318
|
+
directories: projectStructure?.directories.slice(0, 50) || [],
|
|
319
|
+
sourceRoot: projectStructure?.sourceRoot,
|
|
320
|
+
appRouter: projectStructure?.appRouter,
|
|
321
|
+
pagesRouter: projectStructure?.pagesRouter,
|
|
322
|
+
multipleAppDirs: projectStructure?.multipleAppDirs,
|
|
317
323
|
sourceHints: Object.keys(sourceHints).length > 0 ? sourceHints : undefined,
|
|
318
324
|
currentRoute: pageContext?.route || "/",
|
|
319
325
|
pageContext,
|
|
@@ -498,7 +504,7 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
|
|
|
498
504
|
}
|
|
499
505
|
// Phase 2: Local agent generation fallback for code changes
|
|
500
506
|
onProgress("thinking", "Generating implementation plan...", `Planning components for ${prompt}`);
|
|
501
|
-
const plan = await generatePlan(cwd, prompt, scope, projectStructure || { framework: "", hasTypeScript: false, hasTailwind: false, hasReact: false, hasNext: false, hasVite: false, dependencies: [], devDependencies: [], files: [] }, framework || "", config, signal, messages, pageContext);
|
|
507
|
+
const plan = await generatePlan(cwd, prompt, scope, projectStructure || { framework: "", hasTypeScript: false, hasTailwind: false, hasReact: false, hasNext: false, hasVite: false, dependencies: [], devDependencies: [], files: [], directories: [], sourceRoot: ".", appRouter: false, pagesRouter: false, multipleAppDirs: [] }, framework || "", config, signal, messages, pageContext);
|
|
502
508
|
if (signal.aborted) {
|
|
503
509
|
return { ok: false, error: "Operation was cancelled during planning" };
|
|
504
510
|
}
|
|
@@ -593,24 +599,33 @@ async function analyzeProject(cwd) {
|
|
|
593
599
|
const hasReact = dependencies.includes("react") || devDependencies.includes("react");
|
|
594
600
|
const hasNext = dependencies.includes("next") || devDependencies.includes("next");
|
|
595
601
|
const hasVite = dependencies.includes("vite") || devDependencies.includes("vite");
|
|
596
|
-
// Scan
|
|
602
|
+
// Scan directory structure
|
|
597
603
|
const files = [];
|
|
598
|
-
const
|
|
604
|
+
const directories = [];
|
|
605
|
+
const appDirs = [];
|
|
606
|
+
const pagesDirs = [];
|
|
607
|
+
const roots = ["src", "app", "pages", ".", "web", "lib", "components"];
|
|
599
608
|
for (const root of roots) {
|
|
600
609
|
const rootPath = node_path_1.default.join(cwd, root);
|
|
601
610
|
if (!node_fs_1.default.existsSync(rootPath))
|
|
602
611
|
continue;
|
|
603
612
|
const scanDir = (dir, base = "", depth = 0) => {
|
|
604
|
-
if (depth >
|
|
613
|
+
if (depth > 5)
|
|
605
614
|
return;
|
|
606
615
|
try {
|
|
607
616
|
const entries = node_fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
608
617
|
for (const entry of entries) {
|
|
609
|
-
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build") {
|
|
618
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === ".next") {
|
|
610
619
|
continue;
|
|
611
620
|
}
|
|
612
621
|
const rel = base ? node_path_1.default.join(base, entry.name) : entry.name;
|
|
613
622
|
if (entry.isDirectory()) {
|
|
623
|
+
directories.push(rel);
|
|
624
|
+
// Track app and pages directories
|
|
625
|
+
if (entry.name === "app")
|
|
626
|
+
appDirs.push(rel);
|
|
627
|
+
if (entry.name === "pages")
|
|
628
|
+
pagesDirs.push(rel);
|
|
614
629
|
scanDir(node_path_1.default.join(dir, entry.name), rel, depth + 1);
|
|
615
630
|
}
|
|
616
631
|
else if (/\.(tsx?|jsx?|vue|svelte|css|html|json)$/i.test(entry.name)) {
|
|
@@ -623,11 +638,25 @@ async function analyzeProject(cwd) {
|
|
|
623
638
|
}
|
|
624
639
|
};
|
|
625
640
|
scanDir(rootPath);
|
|
626
|
-
if (files.length >
|
|
641
|
+
if (files.length > 100)
|
|
627
642
|
break;
|
|
628
643
|
}
|
|
644
|
+
// Determine source root
|
|
645
|
+
let sourceRoot = ".";
|
|
646
|
+
if (directories.includes("src/app"))
|
|
647
|
+
sourceRoot = "src";
|
|
648
|
+
else if (directories.includes("app"))
|
|
649
|
+
sourceRoot = ".";
|
|
650
|
+
else if (directories.includes("src"))
|
|
651
|
+
sourceRoot = "src";
|
|
652
|
+
// Detect App Router vs Pages Router
|
|
653
|
+
const appRouter = appDirs.length > 0 && appDirs.some(dir => {
|
|
654
|
+
const appPath = node_path_1.default.join(cwd, dir);
|
|
655
|
+
return node_fs_1.default.existsSync(node_path_1.default.join(appPath, "layout.tsx")) || node_fs_1.default.existsSync(node_path_1.default.join(appPath, "page.tsx"));
|
|
656
|
+
});
|
|
657
|
+
const pagesRouter = pagesDirs.length > 0 && node_fs_1.default.existsSync(node_path_1.default.join(cwd, "pages", "_app.tsx")) || node_fs_1.default.existsSync(node_path_1.default.join(cwd, "pages", "_document.tsx"));
|
|
629
658
|
return {
|
|
630
|
-
framework: detectFramework(cwd),
|
|
659
|
+
framework: (0, framework_1.detectFramework)(cwd),
|
|
631
660
|
hasTypeScript,
|
|
632
661
|
hasTailwind,
|
|
633
662
|
hasReact,
|
|
@@ -636,31 +665,13 @@ async function analyzeProject(cwd) {
|
|
|
636
665
|
dependencies: dependencies,
|
|
637
666
|
devDependencies: devDependencies,
|
|
638
667
|
files,
|
|
668
|
+
directories,
|
|
669
|
+
sourceRoot,
|
|
670
|
+
appRouter,
|
|
671
|
+
pagesRouter,
|
|
672
|
+
multipleAppDirs: appDirs,
|
|
639
673
|
};
|
|
640
674
|
}
|
|
641
|
-
function detectFramework(cwd) {
|
|
642
|
-
const pkgPath = node_path_1.default.join(cwd, "package.json");
|
|
643
|
-
if (node_fs_1.default.existsSync(pkgPath)) {
|
|
644
|
-
try {
|
|
645
|
-
const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgPath, "utf8"));
|
|
646
|
-
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
647
|
-
if (deps.next)
|
|
648
|
-
return "next";
|
|
649
|
-
if (deps.vite)
|
|
650
|
-
return "vite";
|
|
651
|
-
if (deps.react)
|
|
652
|
-
return "react";
|
|
653
|
-
if (deps.vue)
|
|
654
|
-
return "vue";
|
|
655
|
-
if (deps.svelte)
|
|
656
|
-
return "svelte";
|
|
657
|
-
}
|
|
658
|
-
catch {
|
|
659
|
-
// fallback
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
return "unknown";
|
|
663
|
-
}
|
|
664
675
|
async function generatePlan(cwd, prompt, _scope, structure, framework, config, signal, _messages, pageContext) {
|
|
665
676
|
const steps = [];
|
|
666
677
|
// Identify relevant files to supply as context.
|
package/dist/overlay.js
CHANGED
|
@@ -7848,6 +7848,8 @@
|
|
|
7848
7848
|
return `${Math.floor(seconds / 86400)}d`;
|
|
7849
7849
|
}
|
|
7850
7850
|
var modelSessionKey = "lasso:selected-model";
|
|
7851
|
+
var chatHistoryKey = "lasso:chat-history";
|
|
7852
|
+
var changesHistoryKey = "lasso:changes-history";
|
|
7851
7853
|
function storedModelId() {
|
|
7852
7854
|
try {
|
|
7853
7855
|
return window.sessionStorage.getItem(modelSessionKey);
|
|
@@ -7861,6 +7863,22 @@
|
|
|
7861
7863
|
} catch {
|
|
7862
7864
|
}
|
|
7863
7865
|
}
|
|
7866
|
+
function loadChatHistory() {
|
|
7867
|
+
try {
|
|
7868
|
+
const chatHistory = JSON.parse(localStorage.getItem(chatHistoryKey) || "[]");
|
|
7869
|
+
const changesHistory = JSON.parse(localStorage.getItem(changesHistoryKey) || "[]");
|
|
7870
|
+
return { chatHistory, changesHistory };
|
|
7871
|
+
} catch {
|
|
7872
|
+
return { chatHistory: [], changesHistory: [] };
|
|
7873
|
+
}
|
|
7874
|
+
}
|
|
7875
|
+
function saveChatHistory(chatHistory, changesHistory) {
|
|
7876
|
+
try {
|
|
7877
|
+
localStorage.setItem(chatHistoryKey, JSON.stringify(chatHistory));
|
|
7878
|
+
localStorage.setItem(changesHistoryKey, JSON.stringify(changesHistory));
|
|
7879
|
+
} catch {
|
|
7880
|
+
}
|
|
7881
|
+
}
|
|
7864
7882
|
var OverlayState = class {
|
|
7865
7883
|
// Mode & Selection
|
|
7866
7884
|
selectMode = false;
|
|
@@ -21611,7 +21629,24 @@
|
|
|
21611
21629
|
recordTaskActivity(task.id, message.message || "Done.");
|
|
21612
21630
|
}
|
|
21613
21631
|
const isPromptTask = !taskId2 || taskId2 === state.promptTaskId;
|
|
21614
|
-
|
|
21632
|
+
let result = message.message || "Done.";
|
|
21633
|
+
if (message.type === "applied" && message.validation) {
|
|
21634
|
+
const { passed, errors, command } = message.validation;
|
|
21635
|
+
if (!passed && errors.length > 0) {
|
|
21636
|
+
result = `${message.message}
|
|
21637
|
+
|
|
21638
|
+
\u26A0\uFE0F Build validation failed after running \`${command}\`:
|
|
21639
|
+
|
|
21640
|
+
${errors.slice(0, 5).join("\n")}${errors.length > 5 ? `
|
|
21641
|
+
...and ${errors.length - 5} more errors` : ""}
|
|
21642
|
+
|
|
21643
|
+
You may need to manually fix these errors or undo the change.`;
|
|
21644
|
+
} else if (passed) {
|
|
21645
|
+
result = `${message.message}
|
|
21646
|
+
|
|
21647
|
+
\u2705 Build validation passed.`;
|
|
21648
|
+
}
|
|
21649
|
+
}
|
|
21615
21650
|
void notifyAgent(message.type === "applied" ? "Changes applied" : "Change undone", result);
|
|
21616
21651
|
appendChat("assistant", result, taskId2);
|
|
21617
21652
|
if (task?.element) releaseHeldLock(elementKey(task.element));
|
|
@@ -21743,6 +21778,8 @@
|
|
|
21743
21778
|
updateAgentTask(id, {
|
|
21744
21779
|
changesHistory: [...task.changesHistory, { summary, changes: changes.map((change) => ({ ...change })), createdAt: (/* @__PURE__ */ new Date()).toISOString() }]
|
|
21745
21780
|
});
|
|
21781
|
+
state.changesHistory = task.changesHistory;
|
|
21782
|
+
saveChatHistory(state.chatHistory, state.changesHistory);
|
|
21746
21783
|
}
|
|
21747
21784
|
function appendTaskMessage(id, role, text) {
|
|
21748
21785
|
const task = getAgentTask(id);
|
|
@@ -21757,6 +21794,8 @@
|
|
|
21757
21794
|
contextId: task.selectionId || void 0
|
|
21758
21795
|
};
|
|
21759
21796
|
updateAgentTask(task.id, { messages: [...task.messages, message] });
|
|
21797
|
+
state.chatHistory = task.messages;
|
|
21798
|
+
saveChatHistory(state.chatHistory, state.changesHistory);
|
|
21760
21799
|
return message;
|
|
21761
21800
|
}
|
|
21762
21801
|
function selectAgentTask(id) {
|
|
@@ -21916,6 +21955,7 @@
|
|
|
21916
21955
|
state.selectionId = selectionId;
|
|
21917
21956
|
state.chatHistory = conversation.messages;
|
|
21918
21957
|
state.changesHistory = conversation.changesHistory;
|
|
21958
|
+
saveChatHistory(state.chatHistory, state.changesHistory);
|
|
21919
21959
|
}
|
|
21920
21960
|
function buildPrompt() {
|
|
21921
21961
|
const dom = getDOM();
|
|
@@ -22101,6 +22141,12 @@
|
|
|
22101
22141
|
if (task) restoreTaskPrompt(task);
|
|
22102
22142
|
else resetPromptSession();
|
|
22103
22143
|
});
|
|
22144
|
+
const { chatHistory, changesHistory } = loadChatHistory();
|
|
22145
|
+
if (chatHistory.length > 0) {
|
|
22146
|
+
state.chatHistory = chatHistory;
|
|
22147
|
+
state.changesHistory = changesHistory;
|
|
22148
|
+
renderChatThread();
|
|
22149
|
+
}
|
|
22104
22150
|
onAgentTaskReviewRequested((task) => {
|
|
22105
22151
|
state.promptTaskId = task.id;
|
|
22106
22152
|
state.pendingChanges = [...task.pendingChanges.length ? task.pendingChanges : task.changes || []];
|
|
@@ -22610,6 +22656,7 @@
|
|
|
22610
22656
|
contextId: state.selectionId || void 0
|
|
22611
22657
|
});
|
|
22612
22658
|
}
|
|
22659
|
+
saveChatHistory(state.chatHistory, state.changesHistory);
|
|
22613
22660
|
renderChatThread();
|
|
22614
22661
|
}
|
|
22615
22662
|
function renderChatText(container, text) {
|
|
@@ -22696,6 +22743,7 @@
|
|
|
22696
22743
|
state.chatHistory = [];
|
|
22697
22744
|
state.changesHistory = [];
|
|
22698
22745
|
state.selectionId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
22746
|
+
saveChatHistory(state.chatHistory, state.changesHistory);
|
|
22699
22747
|
}
|
|
22700
22748
|
state.screenshotPromise = Promise.resolve({});
|
|
22701
22749
|
hideReview();
|
package/package.json
CHANGED