@lasso-ai/cli 1.1.5 → 1.2.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/CHANGELOG.md +15 -0
- package/README.md +57 -0
- package/dist/cli/agent.js +3 -3
- package/dist/cli/bridge.d.ts +6 -0
- package/dist/cli/bridge.js +43 -0
- package/dist/cli/create.d.ts +16 -0
- package/dist/cli/create.js +251 -0
- package/dist/cli/index.js +64 -0
- package/dist/cli/oneshot.d.ts +17 -0
- package/dist/cli/oneshot.js +233 -0
- package/dist/cli/server/next.js +3 -3
- package/dist/cli/server/vite.js +3 -3
- package/dist/overlay.js +447 -2
- package/package.json +3 -1
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runOneShotAgent = runOneShotAgent;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const node_child_process_1 = require("node:child_process");
|
|
10
|
+
const node_util_1 = require("node:util");
|
|
11
|
+
const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
|
|
12
|
+
async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, onPrompt) {
|
|
13
|
+
try {
|
|
14
|
+
if (signal.aborted) {
|
|
15
|
+
return { ok: false, error: "Operation was cancelled" };
|
|
16
|
+
}
|
|
17
|
+
// Phase 1: Understand & Plan
|
|
18
|
+
onProgress("thinking", "Analyzing project structure...", "Scanning files and dependencies");
|
|
19
|
+
const projectStructure = await analyzeProject(cwd);
|
|
20
|
+
const framework = detectFramework(cwd);
|
|
21
|
+
onProgress("thinking", "Planning implementation...", `Detected ${framework} framework`);
|
|
22
|
+
// Phase 2: Generate execution plan
|
|
23
|
+
const plan = await generatePlan(prompt, scope, projectStructure, framework, config, signal);
|
|
24
|
+
if (signal.aborted) {
|
|
25
|
+
return { ok: false, error: "Operation was cancelled during planning" };
|
|
26
|
+
}
|
|
27
|
+
onProgress("thinking", "Execution plan ready", `${plan.steps.length} steps identified`);
|
|
28
|
+
// Phase 3: Execute plan step by step
|
|
29
|
+
const allChanges = [];
|
|
30
|
+
for (let i = 0; i < plan.steps.length; i++) {
|
|
31
|
+
if (signal.aborted) {
|
|
32
|
+
return { ok: false, error: "Operation was cancelled during execution" };
|
|
33
|
+
}
|
|
34
|
+
const step = plan.steps[i];
|
|
35
|
+
onProgress("working", `Step ${i + 1}/${plan.steps.length}: ${step.description}`, step.detail || "");
|
|
36
|
+
const stepResult = await executeStep(cwd, step, config, signal, onProgress, onPrompt);
|
|
37
|
+
if (!stepResult.ok) {
|
|
38
|
+
return { ok: false, error: `Failed at step ${i + 1}: ${stepResult.error}` };
|
|
39
|
+
}
|
|
40
|
+
if (stepResult.changes) {
|
|
41
|
+
allChanges.push(...stepResult.changes);
|
|
42
|
+
}
|
|
43
|
+
onProgress("working", `Step ${i + 1}/${plan.steps.length} complete`, step.description);
|
|
44
|
+
}
|
|
45
|
+
// Phase 4: Validate
|
|
46
|
+
onProgress("working", "Validating changes...", "Running typecheck and build");
|
|
47
|
+
const validationResult = await validateChanges(cwd, framework);
|
|
48
|
+
if (!validationResult.ok) {
|
|
49
|
+
onProgress("error", "Validation failed", validationResult.error);
|
|
50
|
+
// Still return the changes for review, but flag the error
|
|
51
|
+
return {
|
|
52
|
+
ok: true,
|
|
53
|
+
summary: `Completed with validation warnings: ${validationResult.error}`,
|
|
54
|
+
changes: allChanges
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
onProgress("review", "Implementation complete", `${allChanges.length} files modified`);
|
|
58
|
+
return {
|
|
59
|
+
ok: true,
|
|
60
|
+
summary: `Successfully implemented: ${prompt}`,
|
|
61
|
+
changes: allChanges,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const reason = error instanceof Error ? error.message : "Unknown error";
|
|
66
|
+
return { ok: false, error: `One-shot agent failed: ${reason}` };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async function analyzeProject(cwd) {
|
|
70
|
+
const pkgPath = node_path_1.default.join(cwd, "package.json");
|
|
71
|
+
const pkg = node_fs_1.default.existsSync(pkgPath) ? JSON.parse(node_fs_1.default.readFileSync(pkgPath, "utf8")) : {};
|
|
72
|
+
const dependencies = Object.keys(pkg.dependencies || {});
|
|
73
|
+
const devDependencies = Object.keys(pkg.devDependencies || {});
|
|
74
|
+
const hasTypeScript = dependencies.includes("typescript") || devDependencies.includes("typescript") || node_fs_1.default.existsSync(node_path_1.default.join(cwd, "tsconfig.json"));
|
|
75
|
+
const hasTailwind = dependencies.includes("tailwindcss") || devDependencies.includes("tailwindcss");
|
|
76
|
+
const hasReact = dependencies.includes("react") || devDependencies.includes("react");
|
|
77
|
+
const hasNext = dependencies.includes("next") || devDependencies.includes("next");
|
|
78
|
+
const hasVite = dependencies.includes("vite") || devDependencies.includes("vite");
|
|
79
|
+
// Scan src directory
|
|
80
|
+
const files = [];
|
|
81
|
+
const srcPath = node_path_1.default.join(cwd, "src");
|
|
82
|
+
if (node_fs_1.default.existsSync(srcPath)) {
|
|
83
|
+
const scanDir = (dir, base = "") => {
|
|
84
|
+
const entries = node_fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
85
|
+
for (const entry of entries) {
|
|
86
|
+
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
87
|
+
scanDir(node_path_1.default.join(dir, entry.name), node_path_1.default.join(base, entry.name));
|
|
88
|
+
}
|
|
89
|
+
else if (entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx") || entry.name.endsWith(".js") || entry.name.endsWith(".jsx"))) {
|
|
90
|
+
files.push(node_path_1.default.join(base, entry.name));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
scanDir(srcPath);
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
framework: detectFramework(cwd),
|
|
98
|
+
hasTypeScript,
|
|
99
|
+
hasTailwind,
|
|
100
|
+
hasReact,
|
|
101
|
+
hasNext,
|
|
102
|
+
hasVite,
|
|
103
|
+
dependencies,
|
|
104
|
+
devDependencies,
|
|
105
|
+
files,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function detectFramework(cwd) {
|
|
109
|
+
const pkgPath = node_path_1.default.join(cwd, "package.json");
|
|
110
|
+
if (node_fs_1.default.existsSync(pkgPath)) {
|
|
111
|
+
const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgPath, "utf8"));
|
|
112
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
113
|
+
if (deps.next)
|
|
114
|
+
return "next";
|
|
115
|
+
if (deps.vite)
|
|
116
|
+
return "vite";
|
|
117
|
+
if (deps.react)
|
|
118
|
+
return "react";
|
|
119
|
+
}
|
|
120
|
+
return "unknown";
|
|
121
|
+
}
|
|
122
|
+
async function generatePlan(prompt, scope, structure, framework, config, signal) {
|
|
123
|
+
// For now, return a simple plan. In a real implementation, this would use the LLM
|
|
124
|
+
// to analyze the prompt and generate a detailed execution plan.
|
|
125
|
+
const steps = [];
|
|
126
|
+
// This is a placeholder - in production, this would call the LLM to generate
|
|
127
|
+
// a detailed plan based on the user's prompt
|
|
128
|
+
steps.push({
|
|
129
|
+
description: "Analyze requirements",
|
|
130
|
+
detail: "Understanding what needs to be built",
|
|
131
|
+
type: "command",
|
|
132
|
+
command: "echo 'Planning phase'",
|
|
133
|
+
});
|
|
134
|
+
return { steps };
|
|
135
|
+
}
|
|
136
|
+
async function executeStep(cwd, step, config, signal, onProgress, onPrompt) {
|
|
137
|
+
if (signal.aborted) {
|
|
138
|
+
return { ok: false, error: "Cancelled" };
|
|
139
|
+
}
|
|
140
|
+
switch (step.type) {
|
|
141
|
+
case "install":
|
|
142
|
+
if (step.target) {
|
|
143
|
+
await execAsync(`npm install ${step.target}`, { cwd });
|
|
144
|
+
}
|
|
145
|
+
return { ok: true };
|
|
146
|
+
case "create":
|
|
147
|
+
if (step.target && step.content) {
|
|
148
|
+
const filePath = node_path_1.default.join(cwd, step.target);
|
|
149
|
+
const dir = node_path_1.default.dirname(filePath);
|
|
150
|
+
node_fs_1.default.mkdirSync(dir, { recursive: true });
|
|
151
|
+
node_fs_1.default.writeFileSync(filePath, step.content, "utf8");
|
|
152
|
+
return {
|
|
153
|
+
ok: true,
|
|
154
|
+
changes: [{
|
|
155
|
+
filePath: step.target,
|
|
156
|
+
oldString: "",
|
|
157
|
+
newString: step.content,
|
|
158
|
+
}]
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return { ok: false, error: "Missing target or content for create step" };
|
|
162
|
+
case "modify":
|
|
163
|
+
if (step.target && step.content) {
|
|
164
|
+
const filePath = node_path_1.default.join(cwd, step.target);
|
|
165
|
+
if (node_fs_1.default.existsSync(filePath)) {
|
|
166
|
+
const oldContent = node_fs_1.default.readFileSync(filePath, "utf8");
|
|
167
|
+
node_fs_1.default.writeFileSync(filePath, step.content, "utf8");
|
|
168
|
+
return {
|
|
169
|
+
ok: true,
|
|
170
|
+
changes: [{
|
|
171
|
+
filePath: step.target,
|
|
172
|
+
oldString: oldContent,
|
|
173
|
+
newString: step.content,
|
|
174
|
+
}]
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
return { ok: false, error: `File not found: ${step.target}` };
|
|
178
|
+
}
|
|
179
|
+
return { ok: false, error: "Missing target or content for modify step" };
|
|
180
|
+
case "delete":
|
|
181
|
+
if (step.target) {
|
|
182
|
+
const filePath = node_path_1.default.join(cwd, step.target);
|
|
183
|
+
if (node_fs_1.default.existsSync(filePath)) {
|
|
184
|
+
node_fs_1.default.unlinkSync(filePath);
|
|
185
|
+
return { ok: true };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return { ok: true }; // Don't fail if file doesn't exist
|
|
189
|
+
case "command":
|
|
190
|
+
if (step.command) {
|
|
191
|
+
await execAsync(step.command, { cwd });
|
|
192
|
+
return { ok: true };
|
|
193
|
+
}
|
|
194
|
+
return { ok: false, error: "Missing command for command step" };
|
|
195
|
+
default:
|
|
196
|
+
return { ok: false, error: `Unknown step type: ${step.type}` };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async function validateChanges(cwd, framework) {
|
|
200
|
+
try {
|
|
201
|
+
// Run typecheck if TypeScript is available
|
|
202
|
+
if (node_fs_1.default.existsSync(node_path_1.default.join(cwd, "tsconfig.json"))) {
|
|
203
|
+
try {
|
|
204
|
+
await execAsync("npx tsc --noEmit", { cwd, timeout: 30000 });
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
return { ok: false, error: "TypeScript typecheck failed" };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Run build if applicable
|
|
211
|
+
if (framework === "next") {
|
|
212
|
+
try {
|
|
213
|
+
await execAsync("npx next build", { cwd, timeout: 120000 });
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
return { ok: false, error: "Next.js build failed" };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else if (framework === "vite") {
|
|
220
|
+
try {
|
|
221
|
+
await execAsync("npm run build", { cwd, timeout: 120000 });
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
return { ok: false, error: "Vite build failed" };
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return { ok: true };
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
const reason = error instanceof Error ? error.message : "Unknown validation error";
|
|
231
|
+
return { ok: false, error: reason };
|
|
232
|
+
}
|
|
233
|
+
}
|
package/dist/cli/server/next.js
CHANGED
|
@@ -12,8 +12,8 @@ const node_path_1 = require("node:path");
|
|
|
12
12
|
const node_child_process_1 = require("node:child_process");
|
|
13
13
|
const PUBLIC_PORT = 3000; // what the user opens in the browser
|
|
14
14
|
const INTERNAL_PORT = 3055; // where the real `next dev` actually runs
|
|
15
|
-
function serveOverlayBundle(res) {
|
|
16
|
-
const bundlePath = (0, node_path_1.resolve)(
|
|
15
|
+
function serveOverlayBundle(res, cwd) {
|
|
16
|
+
const bundlePath = (0, node_path_1.resolve)(cwd, "node_modules/lasso/dist/overlay.js");
|
|
17
17
|
// when running from within the Lasso repo itself (not yet published),
|
|
18
18
|
// this resolves relative to Lasso's own dist folder instead — adjust
|
|
19
19
|
// the path to point at your actual dist/overlay.js location for now.
|
|
@@ -55,7 +55,7 @@ async function startNextServer(cwd) {
|
|
|
55
55
|
// proxy everything else through to Next
|
|
56
56
|
const server = node_http_1.default.createServer((req, res) => {
|
|
57
57
|
if (req.url === "/__lasso/overlay.js") {
|
|
58
|
-
serveOverlayBundle(res);
|
|
58
|
+
serveOverlayBundle(res, cwd);
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
61
|
proxy.web(req, res, { headers: { "accept-encoding": "identity" } }, (err) => {
|
package/dist/cli/server/vite.js
CHANGED
|
@@ -14,7 +14,7 @@ const node_module_1 = require("node:module");
|
|
|
14
14
|
// console.log("[lasso] overlay placeholder loaded");
|
|
15
15
|
// alert("Lasso overlay injected successfully!");
|
|
16
16
|
// `;
|
|
17
|
-
function lassoInjectPlugin(bridgePort = 3056) {
|
|
17
|
+
function lassoInjectPlugin(bridgePort = 3056, cwd) {
|
|
18
18
|
return {
|
|
19
19
|
name: "lasso-inject",
|
|
20
20
|
transformIndexHtml(html) {
|
|
@@ -22,7 +22,7 @@ function lassoInjectPlugin(bridgePort = 3056) {
|
|
|
22
22
|
},
|
|
23
23
|
configureServer(server) {
|
|
24
24
|
server.middlewares.use("/__lasso/overlay.js", (req, res) => {
|
|
25
|
-
const bundlePath = (0, node_path_2.resolve)(
|
|
25
|
+
const bundlePath = (0, node_path_2.resolve)(cwd, "node_modules/lasso/dist/overlay.js");
|
|
26
26
|
res.setHeader("Content-Type", "application/javascript");
|
|
27
27
|
res.end((0, node_fs_1.readFileSync)(bundlePath, "utf-8"));
|
|
28
28
|
});
|
|
@@ -42,7 +42,7 @@ async function startViteServer(cwd) {
|
|
|
42
42
|
const server = await createServer({
|
|
43
43
|
root: cwd,
|
|
44
44
|
server: { open: false },
|
|
45
|
-
plugins: [lassoInjectPlugin()],
|
|
45
|
+
plugins: [lassoInjectPlugin(3056, cwd)],
|
|
46
46
|
});
|
|
47
47
|
await server.listen();
|
|
48
48
|
console.log(chalk_1.default.green("✓") + " Vite dev server running with Lasso attached");
|