@lasso-ai/cli 1.5.0 → 2.0.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.
Files changed (52) hide show
  1. package/dist/cli/agent.d.ts +1 -0
  2. package/dist/cli/backends.d.ts +53 -0
  3. package/dist/cli/backends.js +267 -0
  4. package/dist/cli/bridge.d.ts +8 -5
  5. package/dist/cli/bridge.js +357 -151
  6. package/dist/cli/host/install.js +12 -1
  7. package/dist/cli/local-runtime.d.ts +28 -0
  8. package/dist/cli/local-runtime.js +294 -0
  9. package/dist/cli/oneshot.js +28 -10
  10. package/dist/cli/runtime-client.d.ts +91 -10
  11. package/dist/cli/runtime-client.js +38 -3
  12. package/dist/overlay.js +44 -28
  13. package/dist/runtime/__tests__/adapter-normalization.test.d.ts +1 -0
  14. package/dist/runtime/__tests__/adapter-normalization.test.js +392 -0
  15. package/dist/runtime/__tests__/state-machine.test.d.ts +1 -0
  16. package/dist/runtime/__tests__/state-machine.test.js +107 -0
  17. package/dist/runtime/__tests__/verification-engine.test.d.ts +1 -0
  18. package/dist/runtime/__tests__/verification-engine.test.js +91 -0
  19. package/dist/runtime/__tests__/workspace-guard.test.d.ts +1 -0
  20. package/dist/runtime/__tests__/workspace-guard.test.js +104 -0
  21. package/dist/runtime/adapters/claude-code-adapter.d.ts +46 -0
  22. package/dist/runtime/adapters/claude-code-adapter.js +230 -0
  23. package/dist/runtime/adapters/codex-cli-adapter.d.ts +44 -0
  24. package/dist/runtime/adapters/codex-cli-adapter.js +202 -0
  25. package/dist/runtime/adapters/cursor-adapter.d.ts +47 -0
  26. package/dist/runtime/adapters/cursor-adapter.js +214 -0
  27. package/dist/runtime/adapters/factory.d.ts +19 -0
  28. package/dist/runtime/adapters/factory.js +48 -0
  29. package/dist/runtime/adapters/local-agent-adapter.d.ts +143 -0
  30. package/dist/runtime/adapters/local-agent-adapter.js +408 -0
  31. package/dist/runtime/adapters/opencode-adapter.d.ts +43 -0
  32. package/dist/runtime/adapters/opencode-adapter.js +210 -0
  33. package/dist/runtime/agent-runtime.d.ts +286 -0
  34. package/dist/runtime/agent-runtime.js +8 -0
  35. package/dist/runtime/agent-state-machine.d.ts +67 -0
  36. package/dist/runtime/agent-state-machine.js +234 -0
  37. package/dist/runtime/backend-interface.d.ts +272 -0
  38. package/dist/runtime/backend-interface.js +38 -0
  39. package/dist/runtime/event-mapper.d.ts +11 -0
  40. package/dist/runtime/event-mapper.js +40 -0
  41. package/dist/runtime/index.d.ts +20 -0
  42. package/dist/runtime/index.js +43 -0
  43. package/dist/runtime/local-session-runner.d.ts +60 -0
  44. package/dist/runtime/local-session-runner.js +185 -0
  45. package/dist/runtime/review.d.ts +24 -0
  46. package/dist/runtime/review.js +30 -0
  47. package/dist/runtime/verification-engine.d.ts +71 -0
  48. package/dist/runtime/verification-engine.js +374 -0
  49. package/dist/runtime/workspace-guard.d.ts +70 -0
  50. package/dist/runtime/workspace-guard.js +321 -0
  51. package/docs/local-coding-agents.md +183 -0
  52. package/package.json +22 -3
@@ -0,0 +1,321 @@
1
+ "use strict";
2
+ /**
3
+ * Workspace Guard - Capture, diff, and roll back project state.
4
+ *
5
+ * Lasso's non-negotiable review workflow requires:
6
+ * 1. Capture the original project state before an agent runs.
7
+ * 2. Collect resulting file changes after the agent runs.
8
+ * 3. Roll back to the original state so nothing is applied before review.
9
+ * 4. On approval, re-apply the changes; on rejection, leave the project untouched.
10
+ *
11
+ * The guard backs up every non-ignored file (in memory, or on disk for
12
+ * oversized files), computes a normalized diff against the captured state,
13
+ * and can restore the workspace exactly (content + permission bits + deletions).
14
+ */
15
+ var __importDefault = (this && this.__importDefault) || function (mod) {
16
+ return (mod && mod.__esModule) ? mod : { "default": mod };
17
+ };
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.WorkspaceGuard = void 0;
20
+ const node_fs_1 = __importDefault(require("node:fs"));
21
+ const node_path_1 = __importDefault(require("node:path"));
22
+ const node_os_1 = __importDefault(require("node:os"));
23
+ const node_crypto_1 = __importDefault(require("node:crypto"));
24
+ const DEFAULT_IGNORE_DIRS = new Set([
25
+ "node_modules",
26
+ ".git",
27
+ ".hg",
28
+ ".svn",
29
+ ".next",
30
+ ".nuxt",
31
+ ".output",
32
+ ".svelte-kit",
33
+ ".turbo",
34
+ ".parcel-cache",
35
+ ".cache",
36
+ ".vercel",
37
+ ".netlify",
38
+ ".lasso",
39
+ ".yarn",
40
+ ".pnpm-store",
41
+ "dist",
42
+ "build",
43
+ "out",
44
+ "coverage",
45
+ "__pycache__",
46
+ ".venv",
47
+ "venv",
48
+ "target",
49
+ ".gradle",
50
+ ".terraform",
51
+ ]);
52
+ function hashBuffer(buf) {
53
+ return node_crypto_1.default.createHash("sha256").update(buf).digest("hex");
54
+ }
55
+ function looksBinary(buf) {
56
+ const sample = buf.subarray(0, Math.min(buf.length, 8192));
57
+ return sample.includes(0);
58
+ }
59
+ class WorkspaceGuard {
60
+ root;
61
+ files = new Map();
62
+ skipped = [];
63
+ tempDir;
64
+ ignoreDirs;
65
+ ignoreFn;
66
+ tempDirCreated = false;
67
+ disposed = false;
68
+ constructor(root, ignoreDirs, ignoreFn) {
69
+ this.root = root;
70
+ this.ignoreDirs = ignoreDirs;
71
+ this.ignoreFn = ignoreFn;
72
+ this.tempDir = node_path_1.default.join(node_os_1.default.tmpdir(), `lasso-guard-${node_crypto_1.default.randomBytes(6).toString("hex")}`);
73
+ }
74
+ /**
75
+ * Capture the full current state of the workspace.
76
+ */
77
+ static async capture(projectPath, options = {}) {
78
+ const root = node_path_1.default.resolve(projectPath);
79
+ if (!node_fs_1.default.existsSync(root) || !node_fs_1.default.statSync(root).isDirectory()) {
80
+ throw new Error(`Project path does not exist or is not a directory: ${root}`);
81
+ }
82
+ const ignoreDirs = new Set([...DEFAULT_IGNORE_DIRS, ...(options.ignoreDirs || [])]);
83
+ const maxMemory = options.maxMemoryFileSize ?? 4 * 1024 * 1024;
84
+ const maxDepth = options.maxDepth ?? 32;
85
+ const ignoreFn = options.ignore;
86
+ const guard = new WorkspaceGuard(root, ignoreDirs, ignoreFn);
87
+ const walk = (dir, relBase, depth) => {
88
+ if (depth > maxDepth)
89
+ return;
90
+ let entries;
91
+ try {
92
+ entries = node_fs_1.default.readdirSync(dir, { withFileTypes: true });
93
+ }
94
+ catch {
95
+ return;
96
+ }
97
+ for (const entry of entries) {
98
+ const relPath = relBase ? node_path_1.default.join(relBase, entry.name) : entry.name;
99
+ const absPath = node_path_1.default.join(dir, entry.name);
100
+ if (entry.isSymbolicLink()) {
101
+ guard.skipped.push(relPath);
102
+ continue;
103
+ }
104
+ const isDir = entry.isDirectory();
105
+ if (isDir && ignoreDirs.has(entry.name))
106
+ continue;
107
+ if (ignoreFn && ignoreFn(relPath, isDir))
108
+ continue;
109
+ if (isDir) {
110
+ walk(absPath, relPath, depth + 1);
111
+ continue;
112
+ }
113
+ if (!entry.isFile()) {
114
+ guard.skipped.push(relPath);
115
+ continue;
116
+ }
117
+ try {
118
+ const stat = node_fs_1.default.statSync(absPath);
119
+ let content = node_fs_1.default.readFileSync(absPath);
120
+ let tempPath;
121
+ if (content.length > maxMemory) {
122
+ // Oversized: keep a disk copy so rollback stays possible without holding it in RAM.
123
+ guard.ensureTempDir();
124
+ tempPath = node_path_1.default.join(guard.tempDir, relPath);
125
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(tempPath), { recursive: true });
126
+ node_fs_1.default.copyFileSync(absPath, tempPath);
127
+ content = null;
128
+ }
129
+ guard.files.set(relPath, {
130
+ absPath,
131
+ content,
132
+ tempPath,
133
+ hash: hashBuffer(node_fs_1.default.readFileSync(absPath)),
134
+ size: stat.size,
135
+ mode: stat.mode,
136
+ binary: content ? looksBinary(content) : false,
137
+ });
138
+ }
139
+ catch {
140
+ guard.skipped.push(relPath);
141
+ }
142
+ }
143
+ };
144
+ walk(root, "", 0);
145
+ return guard;
146
+ }
147
+ /** Number of files captured (excludes ignored/skipped entries). */
148
+ get fileCount() {
149
+ return this.files.size;
150
+ }
151
+ /** Paths that could not be captured (symlinks, unreadable entries). */
152
+ get skippedPaths() {
153
+ return [...this.skipped];
154
+ }
155
+ /**
156
+ * Compare the current on-disk state against the captured state.
157
+ * Must be called BEFORE restore() when collecting an agent's changes.
158
+ */
159
+ diff() {
160
+ const changes = [];
161
+ const seen = new Set();
162
+ for (const [relPath, entry] of this.files) {
163
+ seen.add(relPath);
164
+ let stat = null;
165
+ try {
166
+ stat = node_fs_1.default.statSync(entry.absPath);
167
+ }
168
+ catch {
169
+ stat = null;
170
+ }
171
+ if (!stat || !stat.isFile()) {
172
+ changes.push({ filePath: relPath, operation: "deleted", oldContent: entry.binary ? undefined : entry.content?.toString("utf8") });
173
+ continue;
174
+ }
175
+ let current;
176
+ try {
177
+ current = node_fs_1.default.readFileSync(entry.absPath);
178
+ }
179
+ catch {
180
+ continue;
181
+ }
182
+ if (hashBuffer(current) === entry.hash)
183
+ continue;
184
+ if (entry.binary || looksBinary(current)) {
185
+ changes.push({ filePath: relPath, operation: "modified", binary: true });
186
+ }
187
+ else {
188
+ changes.push({
189
+ filePath: relPath,
190
+ operation: "modified",
191
+ oldContent: entry.content ? entry.content.toString("utf8") : undefined,
192
+ newContent: current.toString("utf8"),
193
+ });
194
+ }
195
+ }
196
+ // Files that did not exist at capture time.
197
+ for (const change of this.listNewFiles(seen)) {
198
+ changes.push(change);
199
+ }
200
+ return changes;
201
+ }
202
+ /**
203
+ * Restore the workspace to the captured state:
204
+ * modified files are reverted, created files are deleted, deleted files are recreated.
205
+ */
206
+ async restore() {
207
+ if (this.disposed)
208
+ return;
209
+ this.disposed = true;
210
+ for (const [relPath, entry] of this.files) {
211
+ let currentExists = false;
212
+ try {
213
+ currentExists = node_fs_1.default.statSync(entry.absPath).isFile();
214
+ }
215
+ catch {
216
+ currentExists = false;
217
+ }
218
+ const desired = entry.content ?? (entry.tempPath ? node_fs_1.default.readFileSync(entry.tempPath) : null);
219
+ if (!desired)
220
+ continue;
221
+ try {
222
+ if (currentExists) {
223
+ const current = node_fs_1.default.readFileSync(entry.absPath);
224
+ if (hashBuffer(current) === hashBuffer(desired)) {
225
+ // Unchanged — still normalize permissions below.
226
+ node_fs_1.default.chmodSync(entry.absPath, entry.mode);
227
+ continue;
228
+ }
229
+ }
230
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(entry.absPath), { recursive: true });
231
+ node_fs_1.default.writeFileSync(entry.absPath, desired);
232
+ node_fs_1.default.chmodSync(entry.absPath, entry.mode);
233
+ }
234
+ catch {
235
+ // Best effort: continue restoring remaining files.
236
+ }
237
+ }
238
+ // Delete files created during execution.
239
+ for (const change of this.listNewFiles(new Set(this.files.keys()))) {
240
+ try {
241
+ node_fs_1.default.unlinkSync(node_path_1.default.join(this.root, change.filePath));
242
+ }
243
+ catch {
244
+ // Ignore — file may already be gone.
245
+ }
246
+ }
247
+ this.cleanupTemp();
248
+ }
249
+ /** Restore without comparing (used by dispose paths). */
250
+ async dispose() {
251
+ if (!this.disposed) {
252
+ await this.restore();
253
+ }
254
+ else {
255
+ this.cleanupTemp();
256
+ }
257
+ }
258
+ listNewFiles(capturedPaths) {
259
+ const created = [];
260
+ const walk = (dir, relBase, depth) => {
261
+ if (depth > 40)
262
+ return;
263
+ let entries;
264
+ try {
265
+ entries = node_fs_1.default.readdirSync(dir, { withFileTypes: true });
266
+ }
267
+ catch {
268
+ return;
269
+ }
270
+ for (const entry of entries) {
271
+ const relPath = relBase ? node_path_1.default.join(relBase, entry.name) : entry.name;
272
+ const absPath = node_path_1.default.join(dir, entry.name);
273
+ if (entry.isSymbolicLink())
274
+ continue;
275
+ if (entry.isDirectory()) {
276
+ if (this.ignoreDirs.has(entry.name))
277
+ continue;
278
+ walk(absPath, relPath, depth + 1);
279
+ continue;
280
+ }
281
+ if (!entry.isFile())
282
+ continue;
283
+ if (this.ignoreFn && this.ignoreFn(relPath, false))
284
+ continue;
285
+ if (capturedPaths.has(relPath))
286
+ continue;
287
+ let binary = false;
288
+ let content;
289
+ try {
290
+ const buf = node_fs_1.default.readFileSync(absPath);
291
+ binary = looksBinary(buf);
292
+ content = binary ? undefined : buf.toString("utf8");
293
+ }
294
+ catch {
295
+ continue;
296
+ }
297
+ created.push(binary ? { filePath: relPath, operation: "created", binary: true } : { filePath: relPath, operation: "created", newContent: content });
298
+ }
299
+ };
300
+ walk(this.root, "", 0);
301
+ return created;
302
+ }
303
+ ensureTempDir() {
304
+ if (this.tempDirCreated)
305
+ return;
306
+ node_fs_1.default.mkdirSync(this.tempDir, { recursive: true });
307
+ this.tempDirCreated = true;
308
+ }
309
+ cleanupTemp() {
310
+ if (!this.tempDirCreated)
311
+ return;
312
+ try {
313
+ node_fs_1.default.rmSync(this.tempDir, { recursive: true, force: true });
314
+ }
315
+ catch {
316
+ // Temp cleanup is best effort.
317
+ }
318
+ this.tempDirCreated = false;
319
+ }
320
+ }
321
+ exports.WorkspaceGuard = WorkspaceGuard;
@@ -0,0 +1,183 @@
1
+ # Local Coding Agents
2
+
3
+ Lasso can drive local coding agents — Claude Code, OpenCode, Codex CLI, and
4
+ Cursor Agent — with the same session management, progress events, review flow,
5
+ verification, and corrective runs as hosted models. Local agents run
6
+ headlessly against your project, but **nothing is applied until you review and
7
+ approve the diff**.
8
+
9
+ ## How it works
10
+
11
+ Local agents execute **on your machine, inside the `lasso` CLI process**
12
+ (`LocalRuntime`). Prompts, project context, and source code never travel
13
+ through Lasso's server — you can point the CLI at any server URL (or none)
14
+ and local agents keep working. Hosted models are unchanged: they still run
15
+ server-side through the Lasso runtime API and the Model Gateway.
16
+
17
+ Local agent lifecycle:
18
+
19
+ 1. Before an agent runs, the CLI captures your workspace (a `WorkspaceGuard`
20
+ snapshot of every non-ignored file).
21
+ 2. The agent's CLI is spawned in your project directory and its structured
22
+ output is normalized into runtime events (progress, tool calls, file
23
+ operations, completion).
24
+ 3. After the run — success **or** failure — the workspace is rolled back to
25
+ the captured state. The guard's diff becomes the reviewable change set.
26
+ 4. Approving a change re-applies it; discarding leaves the project untouched.
27
+ 5. After apply, Lasso runs verification (typecheck/build, when configured).
28
+ Failures are fed back for an automatic corrective run (bounded attempts).
29
+
30
+ Because the agent writes into the real project directory, the guard's diff is
31
+ the single source of truth for what changed — agent-reported file lists are
32
+ never trusted on their own.
33
+
34
+ ## Installation
35
+
36
+ Local agents are detected per-machine by the CLI. Install only the ones you want:
37
+
38
+ | Agent | Provider id | Install |
39
+ | --- | --- | --- |
40
+ | Claude Code | `claude_code` | `npm install -g @anthropic-ai/claude-code` |
41
+ | OpenCode | `opencode` | `npm install -g opencode-ai` |
42
+ | Codex CLI | `codex_cli` | `npm install -g @openai/codex` |
43
+ | Cursor Agent | `cursor` | `curl https://cursor.com/install -fsS \| bash` |
44
+
45
+ The binary names the CLI probes: `claude`, `opencode`, `codex`, `cursor-agent`
46
+ (Cursor's CLI is `cursor-agent`, not `cursor`).
47
+
48
+ ## Authentication
49
+
50
+ Each agent is authenticated with its own login flow. Lasso only *reads*
51
+ authentication state via each agent's structured status command:
52
+
53
+ - **Claude Code** — `claude auth status` (JSON `loggedIn`). Login: run `claude`
54
+ once and complete the flow, or `claude auth login`.
55
+ - **OpenCode** — `opencode providers list` (stored credentials marker). Login:
56
+ `opencode auth login` or configure a provider API key.
57
+ - **Codex CLI** — `codex login status` (exit code). Login: `codex login`
58
+ (ChatGPT account or API key).
59
+ - **Cursor Agent** — `cursor-agent status --format json` (JSON
60
+ `isAuthenticated`). Login: `cursor-agent login`.
61
+
62
+ An agent that is installed but not authenticated stays visible in the model
63
+ menu as **locked**, with the fix instructions shown inline.
64
+
65
+ Account limits (for example Claude "credit balance is too low" or Codex "usage
66
+ limit") are surfaced as authentication-class failures with a clear message —
67
+ they are not treated as code errors.
68
+
69
+ ## Model selection
70
+
71
+ Model option ids are prefixed with the provider (`claude_code:sonnet`,
72
+ `opencode:big-pickle`, …). The CLI normalizes them for each agent:
73
+
74
+ - `claude_code:sonnet` → `sonnet` (also `opus`, `haiku`)
75
+ - `opencode:<name>` → `opencode/<name>` (lists real models via `opencode models`
76
+ when available)
77
+ - `codex_cli:default` → empty (the agent's own configured default model)
78
+ - `cursor:<id>` → passed through (e.g. `sonnet-4`)
79
+
80
+ An empty `modelId` means "use the agent's default".
81
+
82
+ ## Configuration
83
+
84
+ Local agents are selected via the backend config attached to a runtime session.
85
+ The CLI bridge builds this automatically from the overlay's model menu:
86
+
87
+ ```ts
88
+ {
89
+ type: "local_agent",
90
+ provider: "claude_code" | "opencode" | "codex_cli" | "cursor",
91
+ modelId: string, // normalized id, "" = agent default
92
+ projectPath: string, // must be the project root
93
+ permissionMode?: string // claude only, default "acceptEdits"
94
+ }
95
+ ```
96
+
97
+ For programmatic use, `LocalRuntime` (from `@lasso-ai/cli`) implements the
98
+ same session surface as `RuntimeClient` — `createSession`, `runSession`,
99
+ `getSession`, `verifySession`, `switchModel`, `cancelSession` — but executes
100
+ in-process:
101
+
102
+ ```ts
103
+ import { getLocalRuntime } from "@lasso-ai/cli/local-runtime";
104
+
105
+ const runtime = getLocalRuntime();
106
+ const session = await runtime.createSession({
107
+ userPrompt: "Add a loading state to the submit button",
108
+ project: { projectId: "app", hasTypeScript: true },
109
+ model: { provider: "claude_code", id: "sonnet" },
110
+ backendConfig: {
111
+ type: "local_agent",
112
+ provider: "claude_code",
113
+ modelId: "sonnet",
114
+ projectPath: "/path/to/project",
115
+ },
116
+ });
117
+ await runtime.runSession(session.id, "/path/to/project", (event) => {
118
+ console.log(event.type);
119
+ });
120
+ ```
121
+
122
+ The shared runtime core (`WorkspaceGuard`, `VerificationEngine`,
123
+ `LocalSessionRunner`, adapters) is published as `@lasso-ai/cli/runtime` and is
124
+ the same code the Lasso server uses for its colocated Mode B deployments.
125
+
126
+ ## Sessions
127
+
128
+ Local runtime sessions are **in-memory, per CLI process**: they live as long
129
+ as the `lasso` bridge process does and are gone when it exits. Review payloads
130
+ (`pendingChanges`) are persisted in session metadata and applied from the
131
+ review panel regardless. Hosted sessions continue to live server-side (and in
132
+ the server's MongoDB when configured).
133
+
134
+ ## Limitations
135
+
136
+ - **Review-first is mandatory.** There is no "apply silently" mode; every run
137
+ ends in a review (or a no-change completion).
138
+ - **Ephemeral local sessions.** Local agent sessions do not survive a CLI
139
+ restart; hosted sessions do.
140
+ - **Verification is best-effort.** Typecheck runs only when a `tsconfig.json`
141
+ exists; build only when `package.json` has a `build` script. Missing
142
+ tooling is reported as *skipped* (inconclusive), never as a pass or a
143
+ failure.
144
+ - **Session continuation varies.** Claude Code (`--resume`), Codex
145
+ (`exec resume`), and Cursor (`--resume`) can continue their own external
146
+ sessions; OpenCode is re-driven with conversation context each run. Agent
147
+ internal tool-call state is not portable across backends — switching agents
148
+ mid-task transfers conversation/observations/errors only.
149
+ - **Cancellation** sends SIGTERM (then SIGKILL after 3s) to the agent process.
150
+ In-flight tool calls are not undone by the agent itself; the workspace guard
151
+ rollback is what keeps the project clean.
152
+ - **Permissions.** Claude runs with `--permission-mode acceptEdits`; Codex runs
153
+ with `sandbox_mode="workspace-write"`; agents are instructed not to commit,
154
+ push, or act outside the project directory.
155
+
156
+ ## Troubleshooting
157
+
158
+ | Symptom | Likely cause | Fix |
159
+ | --- | --- | --- |
160
+ | Backend locked: "not installed" | Binary not on your `PATH` | Install the CLI globally; restart the `lasso` CLI so it picks up `PATH` changes |
161
+ | Backend locked: "not logged in" | Missing agent credentials | Run the agent's login command listed in the fix instructions |
162
+ | "Credit balance is too low" / "usage limit" | Provider account limits | Top up or wait for the limit window; the run is failed cleanly with no partial apply |
163
+ | "finished without reporting a successful result" | Agent exited 0 without a terminal success event | Usually a CLI version mismatch — update the agent |
164
+ | Changes appear, then vanish before review | Expected: workspace is rolled back until you approve | Approve in the review panel to apply |
165
+ | Local agent run works, hosted models don't | Hosted models need the Lasso server API | Check the CLI's server URL / API key; local agents are unaffected |
166
+
167
+ ## API
168
+
169
+ Local agents do not require any server endpoints — the CLI runs them
170
+ directly. The server API remains for hosted models and backend discovery:
171
+
172
+ - `GET /api/v1/agent-runtime/backends` — probe hosted providers and (for
173
+ colocated deployments) local agents.
174
+ - `POST /api/v1/agent-runtime/sessions` — create a hosted-model session (or a
175
+ colocated local-agent session on self-hosted deployments).
176
+ - `POST /api/v1/agent-runtime/sessions/:id/run` — run (also used for
177
+ corrective re-runs).
178
+ - `POST /api/v1/agent-runtime/sessions/:id/verify` — post-apply verification;
179
+ returns `{ passed, errors, observations, command, requiresFix }`.
180
+ - `POST /api/v1/agent-runtime/sessions/:id/model` — switch hosted model or
181
+ local agent mid-session; reports `transferableContext` / `lostContext`.
182
+ - `POST /api/v1/agent-runtime/sessions/:id/cancel` — abort the running agent
183
+ and restore the workspace.
package/package.json CHANGED
@@ -1,9 +1,24 @@
1
1
  {
2
2
  "name": "@lasso-ai/cli",
3
- "version": "1.5.0",
3
+ "version": "2.0.1",
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",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/cli/index.d.ts",
10
+ "default": "./dist/cli/index.js"
11
+ },
12
+ "./runtime": {
13
+ "types": "./dist/runtime/index.d.ts",
14
+ "default": "./dist/runtime/index.js"
15
+ },
16
+ "./local-runtime": {
17
+ "types": "./dist/cli/local-runtime.d.ts",
18
+ "default": "./dist/cli/local-runtime.js"
19
+ },
20
+ "./package.json": "./package.json"
21
+ },
7
22
  "bin": {
8
23
  "lasso": "dist/cli/index.js"
9
24
  },
@@ -15,7 +30,8 @@
15
30
  "CONTRIBUTING.md",
16
31
  "SECURITY.md",
17
32
  "SUPPORT.md",
18
- "docs/PUBLISHING.md"
33
+ "docs/PUBLISHING.md",
34
+ "docs/local-coding-agents.md"
19
35
  ],
20
36
  "engines": {
21
37
  "node": ">=18.0.0"
@@ -33,7 +49,7 @@
33
49
  "format": "prettier --write .",
34
50
  "deploy": "pnpm build && npm publish --access public",
35
51
  "format:check": "prettier --check .",
36
- "test": "echo \"Error: no test specified\" && exit 1"
52
+ "test": "node --import tsx --test src/runtime/__tests__/*.test.ts"
37
53
  },
38
54
  "keywords": [
39
55
  "ai",
@@ -105,5 +121,8 @@
105
121
  "tsx": "^4.23.13",
106
122
  "typescript": "^7.0.2",
107
123
  "vite": "^8.3.0"
124
+ },
125
+ "overrides": {
126
+ "@lasso-ai/cli": "link:"
108
127
  }
109
128
  }