@lasso-ai/cli 2.1.14 → 2.1.15
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/auth.js +1 -7
- package/dist/cli/bridge.js +137 -117
- package/dist/cli/host/certificates.js +5 -7
- package/dist/cli/host/daemon.js +16 -5
- package/dist/cli/host/dns.js +4 -1
- package/dist/cli/host/install.js +1 -1
- package/dist/cli/host/next-host-entry.js +1 -3
- package/dist/cli/host/vite-host-entry.js +1 -7
- package/dist/cli/index.js +2 -1
- package/dist/cli/local-runtime.js +3 -8
- package/dist/cli/project.js +8 -12
- package/dist/cli/runtime-client.d.ts +2 -0
- package/dist/cli/runtime-client.js +73 -65
- package/dist/cli/server/vite.js +1 -1
- package/dist/cli/snapshot.js +13 -3
- package/dist/overlay.js +139 -282
- package/dist/runtime/__tests__/adapter-normalization.test.js +7 -1
- package/dist/runtime/action-executor.js +3 -15
- package/dist/runtime/adapters/claude-code-adapter.js +6 -13
- package/dist/runtime/adapters/codex-cli-adapter.js +1 -5
- package/dist/runtime/adapters/local-agent-adapter.js +7 -14
- package/dist/runtime/adapters/opencode-adapter.js +3 -12
- package/dist/runtime/agent-state-machine.js +2 -5
- package/dist/runtime/review.js +3 -7
- package/dist/runtime/workspace-guard.js +1 -6
- package/package.json +4 -3
|
@@ -212,12 +212,7 @@ class ActionExecutor {
|
|
|
212
212
|
};
|
|
213
213
|
}
|
|
214
214
|
// Safety check: prevent dangerous commands
|
|
215
|
-
const dangerousPatterns = [
|
|
216
|
-
/\brm\s+-rf\b/,
|
|
217
|
-
/\bdd\s+if=/,
|
|
218
|
-
/>\s*\/dev\/null/,
|
|
219
|
-
/>\s*\/dev\/sd/,
|
|
220
|
-
];
|
|
215
|
+
const dangerousPatterns = [/\brm\s+-rf\b/, /\bdd\s+if=/, />\s*\/dev\/null/, />\s*\/dev\/sd/];
|
|
221
216
|
for (const pattern of dangerousPatterns) {
|
|
222
217
|
if (pattern.test(command)) {
|
|
223
218
|
return {
|
|
@@ -366,9 +361,7 @@ class ActionExecutor {
|
|
|
366
361
|
* boundary: every resolved path must stay inside projectPath.
|
|
367
362
|
*/
|
|
368
363
|
resolvePath(filePath) {
|
|
369
|
-
const resolved = node_path_1.default.isAbsolute(filePath)
|
|
370
|
-
? node_path_1.default.resolve(filePath)
|
|
371
|
-
: node_path_1.default.resolve(this.projectPath, filePath);
|
|
364
|
+
const resolved = node_path_1.default.isAbsolute(filePath) ? node_path_1.default.resolve(filePath) : node_path_1.default.resolve(this.projectPath, filePath);
|
|
372
365
|
const root = node_path_1.default.resolve(this.projectPath);
|
|
373
366
|
if (resolved !== root && !resolved.startsWith(root + node_path_1.default.sep)) {
|
|
374
367
|
throw new Error(`Path escapes workspace: ${filePath}`);
|
|
@@ -388,12 +381,7 @@ class ActionExecutor {
|
|
|
388
381
|
for (const entry of entries) {
|
|
389
382
|
const fullPath = node_path_1.default.join(currentDir, entry.name);
|
|
390
383
|
// Skip common ignored directories
|
|
391
|
-
if (entry.name === "node_modules" ||
|
|
392
|
-
entry.name === ".git" ||
|
|
393
|
-
entry.name === "dist" ||
|
|
394
|
-
entry.name === "build" ||
|
|
395
|
-
entry.name === ".next" ||
|
|
396
|
-
entry.name === ".lasso") {
|
|
384
|
+
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist" || entry.name === "build" || entry.name === ".next" || entry.name === ".lasso") {
|
|
397
385
|
continue;
|
|
398
386
|
}
|
|
399
387
|
if (entry.isDirectory()) {
|
|
@@ -67,9 +67,7 @@ class ClaudeCodeAdapter extends local_agent_adapter_1.LocalAgentAdapter {
|
|
|
67
67
|
try {
|
|
68
68
|
const parsed = JSON.parse(stdout.slice(jsonStart));
|
|
69
69
|
if (parsed && typeof parsed.loggedIn === "boolean") {
|
|
70
|
-
return parsed.loggedIn
|
|
71
|
-
? { authenticated: true }
|
|
72
|
-
: { authenticated: false, error: "Claude Code is not logged in.", fixInstructions: "Run 'claude' once and log in, or run 'claude auth login'." };
|
|
70
|
+
return parsed.loggedIn ? { authenticated: true } : { authenticated: false, error: "Claude Code is not logged in.", fixInstructions: "Run 'claude' once and log in, or run 'claude auth login'." };
|
|
73
71
|
}
|
|
74
72
|
}
|
|
75
73
|
catch {
|
|
@@ -91,15 +89,7 @@ class ClaudeCodeAdapter extends local_agent_adapter_1.LocalAgentAdapter {
|
|
|
91
89
|
const cfg = this.config;
|
|
92
90
|
const resumeId = this.resumeSessionId(context);
|
|
93
91
|
const sessionId = node_crypto_1.default.randomUUID();
|
|
94
|
-
const args = [
|
|
95
|
-
"-p",
|
|
96
|
-
this.buildTaskPrompt(context),
|
|
97
|
-
"--output-format",
|
|
98
|
-
"stream-json",
|
|
99
|
-
"--verbose",
|
|
100
|
-
"--permission-mode",
|
|
101
|
-
cfg.permissionMode || "acceptEdits",
|
|
102
|
-
];
|
|
92
|
+
const args = ["-p", this.buildTaskPrompt(context), "--output-format", "stream-json", "--verbose", "--permission-mode", cfg.permissionMode || "acceptEdits"];
|
|
103
93
|
if (this.modelId)
|
|
104
94
|
args.push("--model", this.modelId);
|
|
105
95
|
if (resumeId)
|
|
@@ -160,7 +150,10 @@ class ClaudeCodeAdapter extends local_agent_adapter_1.LocalAgentAdapter {
|
|
|
160
150
|
handleAssistant(event, sink) {
|
|
161
151
|
const content = event.message?.content || [];
|
|
162
152
|
if (event.error) {
|
|
163
|
-
const text = content
|
|
153
|
+
const text = content
|
|
154
|
+
.map((c) => c.text || "")
|
|
155
|
+
.filter(Boolean)
|
|
156
|
+
.join(" ");
|
|
164
157
|
sink.state.failureMessage = text || `Claude Code error: ${event.error}`;
|
|
165
158
|
if (/billing|auth|unauthorized|credit/i.test(event.error) || /credit balance|not logged in|unauthorized/i.test(sink.state.failureMessage)) {
|
|
166
159
|
sink.state.authFailure = true;
|
|
@@ -199,11 +199,7 @@ class CodexCLIAdapter extends local_agent_adapter_1.LocalAgentAdapter {
|
|
|
199
199
|
return error;
|
|
200
200
|
if (typeof error === "object") {
|
|
201
201
|
const value = error;
|
|
202
|
-
const nested = [
|
|
203
|
-
value.message,
|
|
204
|
-
value.data?.message,
|
|
205
|
-
value.cause?.message,
|
|
206
|
-
].find((item) => typeof item === "string" && item.trim().length > 0);
|
|
202
|
+
const nested = [value.message, value.data?.message, value.cause?.message].find((item) => typeof item === "string" && item.trim().length > 0);
|
|
207
203
|
if (nested)
|
|
208
204
|
return nested;
|
|
209
205
|
try {
|
|
@@ -230,11 +230,7 @@ class LocalAgentAdapter {
|
|
|
230
230
|
sink.emit("task_completed", { summary: state.summary });
|
|
231
231
|
}
|
|
232
232
|
else {
|
|
233
|
-
const message = state.failureMessage ||
|
|
234
|
-
inspected?.message ||
|
|
235
|
-
(exitCode === 0
|
|
236
|
-
? `${this.provider} finished without reporting a successful result.`
|
|
237
|
-
: `${this.provider} exited with code ${exitCode}${stderrBuffer ? `: ${stderrBuffer.trim().slice(-500)}` : ""}`);
|
|
233
|
+
const message = state.failureMessage || inspected?.message || (exitCode === 0 ? `${this.provider} finished without reporting a successful result.` : `${this.provider} exited with code ${exitCode}${stderrBuffer ? `: ${stderrBuffer.trim().slice(-500)}` : ""}`);
|
|
238
234
|
if (!state.errors.some((e) => e.message === message)) {
|
|
239
235
|
sink.error(message, { recoverable: false, authFailure: state.authFailure });
|
|
240
236
|
}
|
|
@@ -277,14 +273,8 @@ class LocalAgentAdapter {
|
|
|
277
273
|
}
|
|
278
274
|
async switchBackend(newBackend, context) {
|
|
279
275
|
const lostContext = this.supportsResume
|
|
280
|
-
? [
|
|
281
|
-
|
|
282
|
-
"Tool call history and per-file context held by the previous agent are not portable.",
|
|
283
|
-
]
|
|
284
|
-
: [
|
|
285
|
-
`${this.provider} does not expose resumable session state for transfer.`,
|
|
286
|
-
"Tool call history and per-file context held by the previous agent are not portable.",
|
|
287
|
-
];
|
|
276
|
+
? [`${this.provider} maintains its own internal session state that cannot be transferred to ${newBackend.provider}.`, "Tool call history and per-file context held by the previous agent are not portable."]
|
|
277
|
+
: [`${this.provider} does not expose resumable session state for transfer.`, "Tool call history and per-file context held by the previous agent are not portable."];
|
|
288
278
|
return {
|
|
289
279
|
transferableContext: {
|
|
290
280
|
conversation: context.conversation,
|
|
@@ -314,7 +304,10 @@ class LocalAgentAdapter {
|
|
|
314
304
|
// Retry/continuation replies ("try again", "continue", …) must resume
|
|
315
305
|
// the previous request instead of being treated as a literal task —
|
|
316
306
|
// otherwise the agent greps for words like "again".
|
|
317
|
-
const normalizedPrompt = context.userPrompt
|
|
307
|
+
const normalizedPrompt = context.userPrompt
|
|
308
|
+
.trim()
|
|
309
|
+
.replace(/[.!]+$/, "")
|
|
310
|
+
.toLowerCase();
|
|
318
311
|
const isContinuation = /^(continue|proceed|go on|go ahead|keep going|yes|yep|yup|sure|ok|okay|alright|do it|carry on|resume|next|try again|again|retry|re-try|do it again|run again|run it again|one more time|same again)$/.test(normalizedPrompt) ||
|
|
319
312
|
/^(try|do|run|go)\s+(it\s+|that\s+)?again$|^one more time$|^retry(\s+it)?$/i.test(normalizedPrompt);
|
|
320
313
|
if (isContinuation) {
|
|
@@ -57,9 +57,7 @@ class OpenCodeAdapter extends local_agent_adapter_1.LocalAgentAdapter {
|
|
|
57
57
|
timeout: 12000,
|
|
58
58
|
windowsHide: true,
|
|
59
59
|
}));
|
|
60
|
-
const text = `${stdout}\n${stderr}
|
|
61
|
-
.replace(/\u001b\[[0-9;]*m/g, "")
|
|
62
|
-
.replace(/\u001b\][^\u0007]*\u0007/g, "");
|
|
60
|
+
const text = `${stdout}\n${stderr}`.replace(/\u001b\[[0-9;]*m/g, "").replace(/\u001b\][^\u0007]*\u0007/g, "");
|
|
63
61
|
// The credentials section lists a "●" bullet per configured credential.
|
|
64
62
|
const credentialsStart = text.indexOf("Credentials");
|
|
65
63
|
const environmentStart = text.indexOf("Environment");
|
|
@@ -95,9 +93,7 @@ class OpenCodeAdapter extends local_agent_adapter_1.LocalAgentAdapter {
|
|
|
95
93
|
if (resumeId)
|
|
96
94
|
args.push("--session", resumeId);
|
|
97
95
|
args.push(this.buildTaskPrompt(context));
|
|
98
|
-
const env = context.mode === "conversation"
|
|
99
|
-
? { OPENCODE_PERMISSION: JSON.stringify({ "*": "deny" }) }
|
|
100
|
-
: undefined;
|
|
96
|
+
const env = context.mode === "conversation" ? { OPENCODE_PERMISSION: JSON.stringify({ "*": "deny" }) } : undefined;
|
|
101
97
|
return { bin: "opencode", args, cwd: this.config.projectPath, env };
|
|
102
98
|
}
|
|
103
99
|
handleLine(line, context, sink) {
|
|
@@ -172,12 +168,7 @@ class OpenCodeAdapter extends local_agent_adapter_1.LocalAgentAdapter {
|
|
|
172
168
|
return error;
|
|
173
169
|
if (typeof error === "object") {
|
|
174
170
|
const e = error;
|
|
175
|
-
const nested = [
|
|
176
|
-
e.message,
|
|
177
|
-
e.data?.message,
|
|
178
|
-
e.data?.error,
|
|
179
|
-
e.cause?.message,
|
|
180
|
-
].find((item) => typeof item === "string" && item.trim().length > 0);
|
|
171
|
+
const nested = [e.message, e.data?.message, e.data?.error, e.cause?.message].find((item) => typeof item === "string" && item.trim().length > 0);
|
|
181
172
|
if (nested)
|
|
182
173
|
return nested;
|
|
183
174
|
try {
|
|
@@ -185,9 +185,7 @@ class AgentStateMachine {
|
|
|
185
185
|
* Check if the session can accept new actions
|
|
186
186
|
*/
|
|
187
187
|
static canAcceptActions(session) {
|
|
188
|
-
return
|
|
189
|
-
session.phase !== "complete" &&
|
|
190
|
-
session.phase !== "understand");
|
|
188
|
+
return session.status === "running" && session.phase !== "complete" && session.phase !== "understand";
|
|
191
189
|
}
|
|
192
190
|
/**
|
|
193
191
|
* Check if the session should trigger verification
|
|
@@ -203,8 +201,7 @@ class AgentStateMachine {
|
|
|
203
201
|
* Check if the session should trigger recovery
|
|
204
202
|
*/
|
|
205
203
|
static shouldRecover(session) {
|
|
206
|
-
return
|
|
207
|
-
session.errors.filter((e) => e.phase === "verify").length > 0);
|
|
204
|
+
return session.phase === "verify" && session.errors.filter((e) => e.phase === "verify").length > 0;
|
|
208
205
|
}
|
|
209
206
|
/**
|
|
210
207
|
* Check if the session can switch models
|
package/dist/runtime/review.js
CHANGED
|
@@ -13,18 +13,14 @@ function diffToReview(rawChanges) {
|
|
|
13
13
|
const applyable = rawChanges.filter((c) => !c.binary);
|
|
14
14
|
const changes = applyable.map((change) => ({
|
|
15
15
|
filePath: change.filePath,
|
|
16
|
-
oldString: change.operation === "created" ? "" : change.oldContent ?? "",
|
|
17
|
-
newString: change.operation === "deleted" ? "" : change.newContent ?? "",
|
|
16
|
+
oldString: change.operation === "created" ? "" : (change.oldContent ?? ""),
|
|
17
|
+
newString: change.operation === "deleted" ? "" : (change.newContent ?? ""),
|
|
18
18
|
operation: change.operation,
|
|
19
19
|
}));
|
|
20
20
|
const actions = applyable.map((change) => ({
|
|
21
21
|
id: crypto.randomUUID(),
|
|
22
22
|
type: change.operation === "created" ? "create_file" : change.operation === "deleted" ? "delete_file" : "edit_file",
|
|
23
|
-
data: change.operation === "created"
|
|
24
|
-
? { filePath: change.filePath, content: change.newContent }
|
|
25
|
-
: change.operation === "deleted"
|
|
26
|
-
? { filePath: change.filePath }
|
|
27
|
-
: { filePath: change.filePath, oldString: change.oldContent ?? "", newString: change.newContent ?? "" },
|
|
23
|
+
data: change.operation === "created" ? { filePath: change.filePath, content: change.newContent } : change.operation === "deleted" ? { filePath: change.filePath } : { filePath: change.filePath, oldString: change.oldContent ?? "", newString: change.newContent ?? "" },
|
|
28
24
|
}));
|
|
29
25
|
return { changes, binaryPaths, actions };
|
|
30
26
|
}
|
|
@@ -49,12 +49,7 @@ const DEFAULT_IGNORE_DIRS = new Set([
|
|
|
49
49
|
".gradle",
|
|
50
50
|
".terraform",
|
|
51
51
|
]);
|
|
52
|
-
const DEFAULT_IGNORE_FILES = new Set([
|
|
53
|
-
"next-env.d.ts",
|
|
54
|
-
"tsconfig.tsbuildinfo",
|
|
55
|
-
".DS_Store",
|
|
56
|
-
"Thumbs.db",
|
|
57
|
-
]);
|
|
52
|
+
const DEFAULT_IGNORE_FILES = new Set(["next-env.d.ts", "tsconfig.tsbuildinfo", ".DS_Store", "Thumbs.db"]);
|
|
58
53
|
function shouldIgnoreFile(name) {
|
|
59
54
|
return DEFAULT_IGNORE_FILES.has(name) || name.endsWith(".tsbuildinfo");
|
|
60
55
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lasso-ai/cli",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.15",
|
|
4
4
|
"description": "Select any part of your running app, describe a change, and let AI edit the real source code.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/cli/index.js",
|
|
@@ -46,9 +46,10 @@
|
|
|
46
46
|
"typecheck:overlay": "tsc -p config/tsconfig.overlay.json --noEmit",
|
|
47
47
|
"start": "node dist/cli/index.js",
|
|
48
48
|
"prepublishOnly": "npm run build",
|
|
49
|
-
"format": "prettier --write
|
|
49
|
+
"format": "prettier --write src config",
|
|
50
50
|
"deploy": "pnpm build && npm publish --access public",
|
|
51
|
-
"format:check": "prettier --check
|
|
51
|
+
"format:check": "prettier --check src config",
|
|
52
|
+
"lint": "npm run typecheck && npm run format:check",
|
|
52
53
|
"test": "node --import tsx --test src/runtime/__tests__/*.test.ts"
|
|
53
54
|
},
|
|
54
55
|
"keywords": [
|