@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
@@ -2,6 +2,7 @@ export type SourceChange = {
2
2
  filePath: string;
3
3
  oldString: string;
4
4
  newString: string;
5
+ operation?: "created" | "modified" | "deleted";
5
6
  };
6
7
  export type AgentConfig = {
7
8
  provider: "anthropic" | "openai" | "google" | "ollama" | "nvidia";
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Local coding agent discovery for the Lasso CLI.
3
+ *
4
+ * Detects which agent CLIs are installed on THIS machine and whether they are
5
+ * authenticated, using each agent's own structured status command (never
6
+ * human-readable output scraping):
7
+ *
8
+ * claude → claude --version / claude auth status (JSON: loggedIn)
9
+ * codex → codex --version / codex login status (exit code)
10
+ * opencode → opencode --version / opencode providers list / opencode models
11
+ * cursor-agent→ cursor-agent -v / cursor-agent status --format json (JSON: isAuthenticated)
12
+ *
13
+ * Auth failures do not mark a backend as missing — it is reported with
14
+ * fix instructions so the overlay can show why it is locked.
15
+ */
16
+ export type LocalAgentProvider = "claude_code" | "opencode" | "codex_cli" | "cursor";
17
+ export interface DetectedBackendModel {
18
+ /** Model option id sent by the overlay (e.g. "opencode:big-pickle") */
19
+ id: string;
20
+ label: string;
21
+ /**
22
+ * Value for backendConfig.modelId — already normalized for the agent's CLI
23
+ * ("" means the agent's configured default).
24
+ */
25
+ modelId: string;
26
+ }
27
+ export interface DetectedBackend {
28
+ provider: LocalAgentProvider;
29
+ installed: boolean;
30
+ authenticated: boolean;
31
+ version?: string;
32
+ models: DetectedBackendModel[];
33
+ error?: string;
34
+ fixInstructions?: string;
35
+ }
36
+ /**
37
+ * Probe all supported local agents in parallel.
38
+ * Fault-tolerant: a missing or broken CLI never rejects the whole list.
39
+ */
40
+ export declare function detectBackends(): Promise<DetectedBackend[]>;
41
+ /**
42
+ * Whether a model option id belongs to a local agent (provider prefix match).
43
+ */
44
+ export declare const LOCAL_MODEL_PREFIXES: string[];
45
+ /**
46
+ * Extract the modelId for backendConfig from an overlay model option id.
47
+ * Examples:
48
+ * "claude_code:sonnet" → "sonnet"
49
+ * "opencode:big-pickle" → "opencode/big-pickle"
50
+ * "codex_cli:default" → "" (agent's configured default)
51
+ * "claude-3-5-sonnet" → "claude-3-5-sonnet" (hosted ids pass through)
52
+ */
53
+ export declare function modelIdForBackend(optionId: string, provider: string): string;
@@ -0,0 +1,267 @@
1
+ "use strict";
2
+ /**
3
+ * Local coding agent discovery for the Lasso CLI.
4
+ *
5
+ * Detects which agent CLIs are installed on THIS machine and whether they are
6
+ * authenticated, using each agent's own structured status command (never
7
+ * human-readable output scraping):
8
+ *
9
+ * claude → claude --version / claude auth status (JSON: loggedIn)
10
+ * codex → codex --version / codex login status (exit code)
11
+ * opencode → opencode --version / opencode providers list / opencode models
12
+ * cursor-agent→ cursor-agent -v / cursor-agent status --format json (JSON: isAuthenticated)
13
+ *
14
+ * Auth failures do not mark a backend as missing — it is reported with
15
+ * fix instructions so the overlay can show why it is locked.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.LOCAL_MODEL_PREFIXES = void 0;
19
+ exports.detectBackends = detectBackends;
20
+ exports.modelIdForBackend = modelIdForBackend;
21
+ const node_child_process_1 = require("node:child_process");
22
+ const node_util_1 = require("node:util");
23
+ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
24
+ async function probe(bin, args, timeout = 10000) {
25
+ try {
26
+ const { stdout, stderr } = await execFileAsync(bin, args, { timeout, windowsHide: true });
27
+ return { ok: true, stdout, stderr, code: 0, failed: false };
28
+ }
29
+ catch (error) {
30
+ const e = error;
31
+ if (e.killed) {
32
+ return { ok: false, stdout: e.stdout || "", stderr: "timeout", code: undefined, failed: true };
33
+ }
34
+ return {
35
+ ok: false,
36
+ stdout: e.stdout || "",
37
+ stderr: e.stderr || String(error),
38
+ code: typeof e.code === "number" ? e.code : undefined,
39
+ failed: true,
40
+ };
41
+ }
42
+ }
43
+ function stripAnsi(text) {
44
+ return text.replace(/\u001b\[[0-9;]*m/g, "").replace(/\u001b\][^\u0007]*\u0007/g, "");
45
+ }
46
+ function parseJson(stdout) {
47
+ const start = stdout.indexOf("{");
48
+ if (start < 0)
49
+ return null;
50
+ try {
51
+ return JSON.parse(stdout.slice(start));
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ }
57
+ async function detectClaudeCode() {
58
+ const base = {
59
+ provider: "claude_code",
60
+ installed: false,
61
+ authenticated: false,
62
+ models: [
63
+ { id: "claude_code:sonnet", label: "Sonnet", modelId: "sonnet" },
64
+ { id: "claude_code:opus", label: "Opus", modelId: "opus" },
65
+ { id: "claude_code:haiku", label: "Haiku", modelId: "haiku" },
66
+ ],
67
+ };
68
+ const version = await probe("claude", ["--version"]);
69
+ if (version.failed) {
70
+ return {
71
+ ...base,
72
+ error: "Claude Code CLI not found.",
73
+ fixInstructions: "Install: npm install -g @anthropic-ai/claude-code",
74
+ };
75
+ }
76
+ base.installed = true;
77
+ base.version = version.stdout.trim().split("\n")[0];
78
+ const auth = await probe("claude", ["auth", "status"], 15000);
79
+ const parsed = parseJson(auth.stdout);
80
+ if (parsed && typeof parsed.loggedIn === "boolean") {
81
+ base.authenticated = parsed.loggedIn;
82
+ if (!parsed.loggedIn) {
83
+ base.error = "Claude Code is not logged in.";
84
+ base.fixInstructions = "Run 'claude' once and complete the login flow.";
85
+ }
86
+ return base;
87
+ }
88
+ // Fallback: exit code decides
89
+ base.authenticated = auth.ok;
90
+ if (!auth.ok) {
91
+ base.error = "Claude Code authentication could not be verified.";
92
+ base.fixInstructions = "Run 'claude' once and complete the login flow.";
93
+ }
94
+ return base;
95
+ }
96
+ async function detectOpenCode() {
97
+ const base = {
98
+ provider: "opencode",
99
+ installed: false,
100
+ authenticated: false,
101
+ models: [],
102
+ };
103
+ const version = await probe("opencode", ["--version"]);
104
+ if (version.failed) {
105
+ return {
106
+ ...base,
107
+ error: "OpenCode CLI not found.",
108
+ fixInstructions: "Install: npm install -g opencode-ai",
109
+ };
110
+ }
111
+ base.installed = true;
112
+ base.version = version.stdout.trim().split("\n")[0];
113
+ const providers = await probe("opencode", ["providers", "list"], 15000);
114
+ if (providers.failed) {
115
+ base.error = "OpenCode authentication could not be verified.";
116
+ base.fixInstructions = "Run 'opencode providers list' and configure credentials with 'opencode auth login'.";
117
+ return base;
118
+ }
119
+ const text = stripAnsi(`${providers.stdout}\n${providers.stderr}`);
120
+ const credStart = text.indexOf("Credentials");
121
+ const envStart = text.indexOf("Environment");
122
+ if (credStart >= 0) {
123
+ const section = text.slice(credStart, envStart > credStart ? envStart : undefined);
124
+ if (!section.includes("●")) {
125
+ base.error = "OpenCode has no stored credentials.";
126
+ base.fixInstructions = "Run 'opencode auth login' or set a provider API key.";
127
+ return base;
128
+ }
129
+ }
130
+ base.authenticated = true;
131
+ // Model list (may fail without breaking detection)
132
+ const models = await probe("opencode", ["models"], 15000);
133
+ if (models.ok) {
134
+ const ids = models.stdout
135
+ .split("\n")
136
+ .map((line) => line.trim())
137
+ .filter((line) => /^[a-z0-9-]+\/[a-z0-9._-]+$/i.test(line))
138
+ .slice(0, 30);
139
+ base.models = ids.map((full) => {
140
+ const name = full.split("/")[1];
141
+ return {
142
+ id: `opencode:${name}`,
143
+ label: name,
144
+ modelId: full,
145
+ };
146
+ });
147
+ }
148
+ if (base.models.length === 0) {
149
+ base.models = [
150
+ { id: "opencode:big-pickle", label: "Big Pickle", modelId: "opencode/big-pickle" },
151
+ { id: "opencode:deepseek-v4-flash", label: "DeepSeek V4 Flash", modelId: "opencode/deepseek-v4-flash" },
152
+ ];
153
+ }
154
+ return base;
155
+ }
156
+ async function detectCodex() {
157
+ const base = {
158
+ provider: "codex_cli",
159
+ installed: false,
160
+ authenticated: false,
161
+ models: [{ id: "codex_cli:default", label: "Default model (codex config)", modelId: "" }],
162
+ };
163
+ const version = await probe("codex", ["--version"]);
164
+ if (version.failed) {
165
+ return {
166
+ ...base,
167
+ error: "Codex CLI not found.",
168
+ fixInstructions: "Install: npm install -g @openai/codex",
169
+ };
170
+ }
171
+ base.installed = true;
172
+ base.version = version.stdout.trim().split("\n")[0];
173
+ const auth = await probe("codex", ["login", "status"], 15000);
174
+ if (!auth.ok) {
175
+ base.error = "Codex CLI is not logged in.";
176
+ base.fixInstructions = "Run 'codex login' and sign in with ChatGPT or an API key.";
177
+ return base;
178
+ }
179
+ base.authenticated = true;
180
+ return base;
181
+ }
182
+ async function detectCursor() {
183
+ const base = {
184
+ provider: "cursor",
185
+ installed: false,
186
+ authenticated: false,
187
+ models: [
188
+ { id: "cursor:sonnet-4", label: "Sonnet 4", modelId: "sonnet-4" },
189
+ { id: "cursor:gpt-5", label: "GPT-5", modelId: "gpt-5" },
190
+ ],
191
+ };
192
+ const version = await probe("cursor-agent", ["-v"]);
193
+ if (version.failed) {
194
+ return {
195
+ ...base,
196
+ error: "Cursor Agent CLI not found.",
197
+ fixInstructions: "Install: curl https://cursor.com/install -fsS | bash",
198
+ };
199
+ }
200
+ base.installed = true;
201
+ base.version = version.stdout.trim().split("\n")[0];
202
+ const auth = await probe("cursor-agent", ["status", "--format", "json"], 15000);
203
+ const parsed = parseJson(auth.stdout);
204
+ if (parsed && typeof parsed.isAuthenticated === "boolean") {
205
+ base.authenticated = parsed.isAuthenticated;
206
+ if (!parsed.isAuthenticated) {
207
+ base.error = "Cursor Agent is not authenticated.";
208
+ base.fixInstructions = "Run 'cursor-agent login'.";
209
+ return base;
210
+ }
211
+ }
212
+ else if (auth.failed) {
213
+ base.error = "Cursor Agent is not authenticated.";
214
+ base.fixInstructions = "Run 'cursor-agent login'.";
215
+ return base;
216
+ }
217
+ else {
218
+ base.authenticated = true;
219
+ }
220
+ const models = await probe("cursor-agent", ["models"], 15000);
221
+ if (models.ok) {
222
+ const ids = models.stdout
223
+ .split("\n")
224
+ .map((line) => line.trim())
225
+ .filter((line) => /^[a-z0-9][a-z0-9.-]*$/i.test(line))
226
+ .slice(0, 20);
227
+ if (ids.length > 0) {
228
+ base.models = ids.map((id) => ({ id: `cursor:${id}`, label: id, modelId: id }));
229
+ }
230
+ }
231
+ return base;
232
+ }
233
+ /**
234
+ * Probe all supported local agents in parallel.
235
+ * Fault-tolerant: a missing or broken CLI never rejects the whole list.
236
+ */
237
+ async function detectBackends() {
238
+ return Promise.all([detectClaudeCode(), detectOpenCode(), detectCodex(), detectCursor()]);
239
+ }
240
+ /**
241
+ * Whether a model option id belongs to a local agent (provider prefix match).
242
+ */
243
+ exports.LOCAL_MODEL_PREFIXES = ["claude_code:", "claude-code:", "opencode:", "codex:", "codex_cli:", "cursor:"];
244
+ /**
245
+ * Extract the modelId for backendConfig from an overlay model option id.
246
+ * Examples:
247
+ * "claude_code:sonnet" → "sonnet"
248
+ * "opencode:big-pickle" → "opencode/big-pickle"
249
+ * "codex_cli:default" → "" (agent's configured default)
250
+ * "claude-3-5-sonnet" → "claude-3-5-sonnet" (hosted ids pass through)
251
+ */
252
+ function modelIdForBackend(optionId, provider) {
253
+ if (!exports.LOCAL_MODEL_PREFIXES.some((p) => optionId.startsWith(p)) && !["claude_code", "opencode", "codex_cli", "cursor"].includes(provider)) {
254
+ return optionId;
255
+ }
256
+ const colon = optionId.indexOf(":");
257
+ const raw = colon >= 0 ? optionId.slice(colon + 1) : optionId;
258
+ if (provider === "opencode") {
259
+ if (raw === "default")
260
+ return "";
261
+ return raw.includes("/") ? raw : `opencode/${raw}`;
262
+ }
263
+ if (provider === "codex_cli") {
264
+ return raw === "default" ? "" : raw;
265
+ }
266
+ return raw;
267
+ }
@@ -9,6 +9,7 @@ type GitState = {
9
9
  hasRemote?: boolean;
10
10
  remote?: string;
11
11
  };
12
+ type Provider = "anthropic" | "openai" | "google" | "nvidia" | "ollama" | "claude_code" | "opencode" | "codex_cli" | "cursor";
12
13
  export type BridgeMessage = {
13
14
  type: "hello";
14
15
  from: "overlay" | "cli";
@@ -17,7 +18,7 @@ export type BridgeMessage = {
17
18
  taskId: string;
18
19
  instruction: string;
19
20
  model: string;
20
- provider?: "anthropic" | "openai" | "google" | "nvidia" | "ollama";
21
+ provider?: "anthropic" | "openai" | "google" | "nvidia" | "ollama" | "claude_code" | "opencode" | "codex_cli" | "cursor";
21
22
  messages?: Array<{
22
23
  role: string;
23
24
  content: string;
@@ -52,7 +53,7 @@ export type BridgeMessage = {
52
53
  taskId: string;
53
54
  question: string;
54
55
  model: string;
55
- provider?: "anthropic" | "openai" | "google" | "nvidia" | "ollama";
56
+ provider?: "anthropic" | "openai" | "google" | "nvidia" | "ollama" | "claude_code" | "opencode" | "codex_cli" | "cursor";
56
57
  messages?: Array<{
57
58
  role: string;
58
59
  content: string;
@@ -112,7 +113,9 @@ export type BridgeMessage = {
112
113
  } | {
113
114
  type: "git_generate_message";
114
115
  model: string;
115
- provider?: "anthropic" | "openai" | "google" | "nvidia" | "ollama";
116
+ provider?: "anthropic" | "openai" | "google" | "nvidia" | "ollama" | "claude_code" | "opencode" | "codex_cli" | "cursor";
117
+ taskId?: string;
118
+ pageContext?: any;
116
119
  } | {
117
120
  type: "git_init";
118
121
  } | {
@@ -136,7 +139,7 @@ export type BridgeMessage = {
136
139
  prompt: string;
137
140
  scope?: "project" | "component";
138
141
  model?: string;
139
- provider?: "anthropic" | "openai" | "google" | "nvidia" | "ollama";
142
+ provider?: "anthropic" | "openai" | "google" | "nvidia" | "ollama" | "claude_code" | "opencode" | "codex_cli" | "cursor";
140
143
  messages?: Array<{
141
144
  role: "user" | "assistant";
142
145
  content: string;
@@ -160,7 +163,7 @@ export type BridgeMessage = {
160
163
  type ModelOption = {
161
164
  id: string;
162
165
  label: string;
163
- provider: "anthropic" | "openai" | "google" | "nvidia" | "ollama";
166
+ provider: Provider;
164
167
  locked?: boolean;
165
168
  lockedReason?: string;
166
169
  };