@lasso-ai/cli 1.2.5 → 1.2.6

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 CHANGED
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Fixed
11
+
12
+ - **One-shot generation no longer demands a provider API key when a local agent is
13
+ selected.** Picking Claude Code, Codex, OpenCode, or Cursor for a one-shot request
14
+ used to bail out with *"Please configure an AI provider API key or model in settings
15
+ to enable code generation."* Those agents authenticate themselves, so the request now
16
+ runs through the local CLI first. If the local CLI cannot run (not installed, not
17
+ signed in, out of credits) and a Lasso key is available, the request falls back to the
18
+ Lasso Agent Gateway with a hosted provider/model it understands (`claude-code`/`cursor`
19
+ → Anthropic, `codex` → OpenAI, `opencode` by model prefix). Without a key, the error
20
+ now names the CLI command and `lasso auth login` instead of pointing at settings.
21
+ - **Local agents no longer hang on stdin.** `codex exec` and `opencode run` read stdin
22
+ until EOF, so every local-agent request stalled until the 5-minute timeout. Stdin is
23
+ closed as soon as the process spawns (none of these command lines are interactive).
24
+ - **Ollama requests stay local.** One-shot requests for Ollama are no longer routed to
25
+ the gateway, which cannot reach the user's `localhost`.
26
+
10
27
  ### Changed
11
28
 
12
29
  - **`lasso init` registers against the Lasso API, not the realtime server.**
@@ -3,7 +3,7 @@ export type SourceChange = {
3
3
  oldString: string;
4
4
  newString: string;
5
5
  };
6
- type AgentInput = {
6
+ export type AgentInput = {
7
7
  taskId?: string;
8
8
  instruction: string;
9
9
  model: string;
@@ -97,6 +97,7 @@ export type AgentStepAction = {
97
97
  changes: SourceChange[];
98
98
  };
99
99
  export declare function parseAgentAction(text: string): AgentStepAction;
100
+ export declare function runLocalAgentPrompt(cwd: string, config: AgentConfig, prompt: string, signal?: AbortSignal, onProgress?: AgentProgress, taskId?: string, onPrompt?: AgentPromptHandler): Promise<string>;
100
101
  export declare function proposeChanges(cwd: string, input: AgentInput, config: AgentConfig, signal?: AbortSignal, onProgress?: AgentProgress, onPrompt?: AgentPromptHandler): Promise<{
101
102
  summary: string;
102
103
  packages?: string[];
@@ -104,4 +105,3 @@ export declare function proposeChanges(cwd: string, input: AgentInput, config: A
104
105
  }>;
105
106
  export declare function answerQuestion(cwd: string, input: AgentAnswer, config: AgentConfig, signal?: AbortSignal, onProgress?: AgentProgress, onPrompt?: AgentPromptHandler): Promise<string>;
106
107
  export declare function generateCommitMessage(cwd: string, status: string[], config: AgentConfig, signal?: AbortSignal, onProgress?: AgentProgress): Promise<string>;
107
- export {};
package/dist/cli/agent.js CHANGED
@@ -10,6 +10,7 @@ exports.executeWebSearch = executeWebSearch;
10
10
  exports.executeAgentCommand = executeAgentCommand;
11
11
  exports.detectLocalAgents = detectLocalAgents;
12
12
  exports.parseAgentAction = parseAgentAction;
13
+ exports.runLocalAgentPrompt = runLocalAgentPrompt;
13
14
  exports.proposeChanges = proposeChanges;
14
15
  exports.answerQuestion = answerQuestion;
15
16
  exports.generateCommitMessage = generateCommitMessage;
@@ -755,6 +756,12 @@ function localCommand(provider, model, prompt) {
755
756
  function runLocalCommand(cwd, command, args, provider, signal, onProgress, taskId, onPrompt) {
756
757
  return new Promise((resolve, reject) => {
757
758
  const child = (0, node_child_process_1.spawn)(command, args, { cwd, stdio: ["pipe", "pipe", "pipe"] });
759
+ // Headless invocations: codex/opencode/cursor read stdin until EOF and otherwise hang
760
+ // until our timeout, so close stdin right away. Prompt answers can no longer be piped
761
+ // back in, which is fine — none of these command lines are interactive. Swallow the
762
+ // EPIPE that a late write would raise so it cannot crash the CLI.
763
+ child.stdin?.on("error", () => { });
764
+ child.stdin?.end();
758
765
  const label = provider === "opencode" ? "OpenCode" : provider === "codex" ? "Codex" : provider === "cursor" ? "Cursor" : "Claude Code";
759
766
  let stdout = "";
760
767
  let stderr = "";
@@ -882,6 +889,16 @@ async function proposeWithLocalAgent(cwd, instruction, context, config, signal,
882
889
  throw new Error(localAgentError(command, result.stderr, result.exitCode));
883
890
  return extractLocalAgentProposal(result.stdout, config.provider);
884
891
  }
892
+ // Runs a local coding-agent CLI (claude/codex/opencode/cursor) and returns its final text.
893
+ // The CLI authenticates itself (its own login), so no provider API key is required.
894
+ async function runLocalAgentPrompt(cwd, config, prompt, signal, onProgress, taskId, onPrompt) {
895
+ const provider = config.provider;
896
+ const local = localCommand(provider, config.model, prompt);
897
+ const result = await runLocalCommand(cwd, local.command, local.args, provider, signal, onProgress, taskId, onPrompt);
898
+ if (result.exitCode !== 0)
899
+ throw new Error(localAgentError(local.command, result.stderr, result.exitCode));
900
+ return extractLocalAgentText(result.stdout, provider);
901
+ }
885
902
  async function proposeChanges(cwd, input, config, signal, onProgress, onPrompt) {
886
903
  const context = await contextFor(cwd, input.element);
887
904
  const runtimeErrors = input.context?.runtimeErrors || [];
@@ -1176,11 +1193,7 @@ async function answerQuestion(cwd, input, config, signal, onProgress, onPrompt)
1176
1193
  const history = input.messages?.map((message) => `${message.role}: ${message.content}`).join("\n") || "None";
1177
1194
  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."}`;
1178
1195
  if (config.provider === "claude-code" || config.provider === "codex" || config.provider === "opencode" || config.provider === "cursor") {
1179
- const local = localCommand(config.provider, config.model, prompt);
1180
- const result = await runLocalCommand(cwd, local.command, local.args, config.provider, signal, onProgress, input.taskId, onPrompt);
1181
- if (result.exitCode !== 0)
1182
- throw new Error(localAgentError(local.command, result.stderr, result.exitCode));
1183
- return extractLocalAgentText(result.stdout, config.provider).trim();
1196
+ return (await runLocalAgentPrompt(cwd, config, prompt, signal, onProgress, input.taskId, onPrompt)).trim();
1184
1197
  }
1185
1198
  const callServerGatewayAnswer = async (gatewayKey) => {
1186
1199
  let serverUrl = config.serverUrl || process.env.LASSO_SERVER_URL || process.env.NEXT_PUBLIC_LASSO_SERVER_URL || "https://api.lasso.byorello.space";
@@ -556,7 +556,7 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
556
556
  taskControllers.set(msg.taskId, controller);
557
557
  let selectedConfig;
558
558
  if (localProvider) {
559
- selectedConfig = { provider: cliProvider, model: msg.model };
559
+ selectedConfig = { provider: cliProvider, model: msg.model, lassoKey: resolvedLassoKey, serverUrl: collabConfig?.serverUrl || (0, auth_1.serverUrlFrom)(fileEnv) };
560
560
  }
561
561
  else {
562
562
  const requestedProvider = (msg.provider || agentConfig.provider);
@@ -608,7 +608,7 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
608
608
  }
609
609
  let selectedConfig;
610
610
  if (localProvider) {
611
- selectedConfig = { provider: cliProvider, model: msg.model };
611
+ selectedConfig = { provider: cliProvider, model: msg.model, lassoKey: resolvedLassoKey, serverUrl: collabConfig?.serverUrl || (0, auth_1.serverUrlFrom)(fileEnv) };
612
612
  }
613
613
  else {
614
614
  const requestedProvider = (msg.provider || agentConfig.provider);
@@ -765,21 +765,23 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
765
765
  const provider = (msg.provider || agentConfig?.provider || "google");
766
766
  const model = msg.model || agentConfig?.model || (provider === "nvidia" ? "nvidia/llama-3.1-nemotron-70b-instruct" : provider === "google" ? "gemini-2.5-flash" : "claude-3-7-sonnet");
767
767
  const localKey = localProviderKeys[provider];
768
+ const serverUrl = collabConfig?.serverUrl || (0, auth_1.serverUrlFrom)(fileEnv);
769
+ const targetApiUrl = (serverUrl.includes("collab.lasso.byorello.space") ? "https://api.lasso.byorello.space" : serverUrl).replace(/\/api\/v1\/?$/, "");
768
770
  const selectedConfig = localProvider
769
- ? { provider: cliProvider, model: msg.model || "claude-code:sonnet" }
771
+ ? { provider: cliProvider, model: msg.model || "claude-code:sonnet", lassoKey: resolvedLassoKey, serverUrl: targetApiUrl }
770
772
  : {
771
773
  ...(agentConfig || {}),
772
774
  provider,
773
775
  model,
774
776
  apiKey: localKey || apiKey,
777
+ lassoKey: resolvedLassoKey,
778
+ serverUrl: targetApiUrl,
775
779
  baseUrl: provider === "nvidia" ? "https://integrate.api.nvidia.com/v1" : provider === "ollama" ? `${(process.env.OLLAMA_BASE_URL || fileEnv.OLLAMA_BASE_URL || "http://localhost:11434/api").replace(/\/api\/?$/, "")}/v1` : undefined,
776
780
  };
777
781
  const controller = new AbortController();
778
782
  taskControllers.set(taskId, controller);
779
783
  socket.send(JSON.stringify({ type: "agent_status", taskId, status: "thinking", message: "Planning your request..." }));
780
784
  const { runOneShotAgent } = await import("./oneshot.js");
781
- const serverUrl = collabConfig?.serverUrl || (0, auth_1.serverUrlFrom)(fileEnv);
782
- const targetApiUrl = (serverUrl.includes("collab.lasso.byorello.space") ? "https://api.lasso.byorello.space" : serverUrl).replace(/\/api\/v1\/?$/, "");
783
785
  runOneShotAgent(cwd, msg.prompt, msg.scope || "project", selectedConfig, controller.signal, (status, message, detail) => {
784
786
  if (!controller.signal.aborted && socket.readyState === socket.OPEN) {
785
787
  socket.send(JSON.stringify({ type: "agent_status", taskId, status, message, detail }));
@@ -9,7 +9,37 @@ const node_fs_1 = __importDefault(require("node:fs"));
9
9
  const node_path_1 = __importDefault(require("node:path"));
10
10
  const node_child_process_1 = require("node:child_process");
11
11
  const node_util_1 = require("node:util");
12
+ const agent_1 = require("./agent");
12
13
  const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
14
+ const CLI_PROVIDERS = new Set(["claude-code", "codex", "opencode", "cursor"]);
15
+ function isCliProvider(provider) {
16
+ return Boolean(provider && CLI_PROVIDERS.has(provider));
17
+ }
18
+ function cliLabel(provider) {
19
+ return provider === "claude-code" ? "Claude Code" : provider === "opencode" ? "OpenCode" : provider === "cursor" ? "Cursor" : provider === "codex" ? "Codex" : "CLI agent";
20
+ }
21
+ function cliCommand(provider) {
22
+ return provider === "claude-code" ? "claude" : provider || "the agent CLI";
23
+ }
24
+ // The Lasso Agent Gateway only understands hosted providers, so a CLI selection is
25
+ // normalized to the closest hosted provider/model when we have to fall back to it.
26
+ const GATEWAY_MODELS = {
27
+ anthropic: "claude-sonnet-4-5-20250929",
28
+ openai: "gpt-4.1",
29
+ google: "gemini-2.5-flash",
30
+ };
31
+ function gatewayTargetForCli(provider, model) {
32
+ const bare = model && model.includes(":") ? model.slice(model.indexOf(":") + 1) : model || "";
33
+ if (provider === "codex")
34
+ return { provider: "openai", model: GATEWAY_MODELS.openai };
35
+ if (provider === "cursor")
36
+ return bare.includes("gpt") ? { provider: "openai", model: GATEWAY_MODELS.openai } : { provider: "anthropic", model: GATEWAY_MODELS.anthropic };
37
+ if (bare.startsWith("openai/") || /^gpt/i.test(bare))
38
+ return { provider: "openai", model: GATEWAY_MODELS.openai };
39
+ if (bare.startsWith("google/") || /^gemini/i.test(bare))
40
+ return { provider: "google", model: GATEWAY_MODELS.google };
41
+ return { provider: "anthropic", model: GATEWAY_MODELS.anthropic };
42
+ }
13
43
  function isConversationalPrompt(prompt) {
14
44
  const p = prompt.trim().toLowerCase().replace(/[!?.,;:]+$/, "");
15
45
  const greetings = [
@@ -39,7 +69,7 @@ function runtimeErrorObservation(pageContext) {
39
69
  return "- Console/Runtime Errors: None";
40
70
  return `- Console/Runtime Errors (the page is currently throwing these):\n${errors.map((e) => ` ! ${e}`).join("\n")}`;
41
71
  }
42
- async function generateConversationalReply(prompt, framework, config, signal, messages, pageContext) {
72
+ async function generateConversationalReply(cwd, prompt, framework, config, signal, messages, pageContext, onProgress, taskId, onPrompt) {
43
73
  const history = messages?.map((m) => `${m.role === "user" ? "User" : "Assistant"}: ${m.content}`).join("\n\n") || "";
44
74
  let pageObservation = "";
45
75
  if (pageContext) {
@@ -68,13 +98,19 @@ Respond conversationally, concisely, and helpfully.
68
98
  ${pageContext ? "You have live access to the browser page context. When the user asks about the page, design, layout, or components, reference the actual headings, sections, buttons, and text rendered on screen. Provide concrete, expert design and UX feedback. Never claim the page has no content or components when rendered headings and sections are present." : ""}
69
99
  Do not output code changes, file patches, or JSON schemas — just talk to the developer naturally and offer assistance.`;
70
100
  const userPrompt = `${history ? `Conversation History:\n${history}\n\n` : ""}${pageObservation}\nUser message: ${prompt}`;
101
+ // CLI agents (Claude Code, Codex, OpenCode, Cursor) authenticate themselves, so no
102
+ // provider API key is needed. Failures are thrown so the caller can fall back.
103
+ if (isCliProvider(config.provider)) {
104
+ const text = await (0, agent_1.runLocalAgentPrompt)(cwd, config, `${systemPrompt}\n\n${userPrompt}`, signal, onProgress, taskId, onPrompt);
105
+ return text.trim() || "Hello! I'm Lasso, your AI pair programmer. You can ask me questions about your project or tell me what to build, modify, or fix.";
106
+ }
71
107
  if (!config.apiKey && config.provider !== "ollama") {
72
108
  if (pageContext && (prompt.toLowerCase().includes("landing page") || prompt.toLowerCase().includes("page"))) {
73
109
  const headings = (pageContext.domSummary?.headings || []).map((h) => `"${h.text}"`).join(", ");
74
110
  const sections = (pageContext.domSummary?.sections || []).map((s) => s.name).join(", ");
75
- return `Looking at your current page ("${pageContext.title}" on ${pageContext.route}):\n\n- **Rendered Sections:** ${sections || "Standard container"}\n- **Key Headings:** ${headings || "None"}\n- **CTAs:** ${(pageContext.domSummary?.buttons || []).join(", ") || "None"}\n\nTo build or refine components, configure your AI API key in settings or ask me what to modify!`;
111
+ return `Looking at your current page ("${pageContext.title}" on ${pageContext.route}):\n\n- **Rendered Sections:** ${sections || "Standard container"}\n- **Key Headings:** ${headings || "None"}\n- **CTAs:** ${(pageContext.domSummary?.buttons || []).join(", ") || "None"}\n\nTo build or refine components, run \`lasso auth login\` or add an AI provider API key in settings, then ask me what to modify!`;
76
112
  }
77
- return "Hello! I'm Lasso, your AI pair programmer. You can ask me questions about your project or tell me what to build, modify, or fix.";
113
+ return "Hello! I'm Lasso, your AI pair programmer. You can ask me questions about your project or tell me what to build, modify, or fix. Run `lasso auth login` (or add a provider API key in settings) to unlock AI replies.";
78
114
  }
79
115
  try {
80
116
  const model = config.model || (config.provider === "google" ? "gemini-2.5-flash" : config.provider === "openai" ? "gpt-4.1-mini" : config.provider === "ollama" ? "llama3.2" : config.provider === "nvidia" ? "nvidia/llama-3.1-nemotron-70b-instruct" : "claude-3-7-sonnet-latest");
@@ -179,21 +215,20 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
179
215
  rawServerUrl = rawServerUrl.replace("collab.lasso.byorello.space", "api.lasso.byorello.space");
180
216
  }
181
217
  const targetServerUrl = rawServerUrl.replace(/\/$/, "").replace(/\/api\/v1$/, "");
182
- const isCliProvider = config.provider === "claude-code" ||
183
- config.provider === "codex" ||
184
- config.provider === "opencode" ||
185
- config.provider === "cursor";
218
+ const useCliAgent = isCliProvider(config.provider);
219
+ const gatewayKey = apiKey || config.lassoKey || (config.apiKey && (config.apiKey.startsWith("lss_live_") || config.apiKey.startsWith("lss_")) ? config.apiKey : "") || process.env.LASSO_API_KEY || "";
186
220
  const hasLocalProviderKey = Boolean(config.apiKey &&
187
221
  config.apiKey !== "ollama" &&
188
222
  !config.apiKey.startsWith("lss_live_") &&
189
223
  !config.apiKey.startsWith("lss_"));
190
224
  // If the user does NOT have a local provider key in .env, route to Lasso Agent Gateway on server.
191
225
  // When they DO have a local key in .env, it runs locally to save server resources!
192
- const shouldRouteToServer = !isCliProvider && !hasLocalProviderKey && Boolean(targetServerUrl);
193
- if (shouldRouteToServer) {
226
+ // Ollama always runs locally (the server cannot reach the user's localhost).
227
+ const shouldRouteToServer = !useCliAgent && config.provider !== "ollama" && !hasLocalProviderKey && Boolean(targetServerUrl);
228
+ const callGateway = async (target) => {
194
229
  try {
195
230
  if (!isConversational && !pageContext) {
196
- onProgress("thinking", "Connecting to Lasso Agent Gateway...", `Model: ${config.model || "claude-3-7-sonnet"}`);
231
+ onProgress("thinking", "Connecting to Lasso Agent Gateway...", `Model: ${target.model || "claude-3-7-sonnet"}`);
197
232
  }
198
233
  const endpoint = `${targetServerUrl.replace(/\/$/, "")}/api/v1/agent/session`;
199
234
  const headers = {
@@ -235,8 +270,8 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
235
270
  prompt,
236
271
  messages,
237
272
  model: {
238
- id: config.model,
239
- provider: config.provider,
273
+ id: target.model,
274
+ provider: target.provider,
240
275
  },
241
276
  context: {
242
277
  framework,
@@ -315,10 +350,100 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
315
350
  error: serverErr instanceof Error ? serverErr.message : "Failed to connect to Lasso Agent Gateway.",
316
351
  };
317
352
  }
353
+ return { ok: false, error: "The agent session returned no result." };
354
+ };
355
+ const gatewayFallback = async (reason) => {
356
+ if (!gatewayKey || !targetServerUrl) {
357
+ return {
358
+ ok: false,
359
+ error: `${reason} — run \`${cliCommand(config.provider)}\` once to authenticate it, or run \`lasso auth login\` to use the Lasso gateway.`,
360
+ };
361
+ }
362
+ onProgress("thinking", `${cliLabel(config.provider)} is unavailable; using the Lasso Agent Gateway...`, reason);
363
+ const target = gatewayTargetForCli(config.provider, config.model);
364
+ const result = await callGateway({ provider: target.provider, model: target.model });
365
+ if (!result.ok && result.error) {
366
+ return { ok: false, error: `${reason} — gateway fallback failed: ${result.error}` };
367
+ }
368
+ return result;
369
+ };
370
+ // CLI agents (Claude Code, Codex, OpenCode, Cursor) authenticate themselves, so they run
371
+ // locally first instead of demanding a provider API key from settings.
372
+ if (useCliAgent && !isConversational) {
373
+ onProgress("thinking", `Running ${cliLabel(config.provider)}...`, `Local CLI agent · ${config.model || ""}`);
374
+ try {
375
+ const selectedElement = pageContext?.selectedElement;
376
+ const input = {
377
+ taskId,
378
+ instruction: prompt,
379
+ model: config.model || "",
380
+ messages,
381
+ context: {
382
+ runtimeErrors: pageContext?.runtimeErrors,
383
+ screenshots: pageContext?.screenshot ? { full: pageContext.screenshot } : undefined,
384
+ viewport: pageContext?.viewport,
385
+ },
386
+ element: {
387
+ tag: selectedElement?.tag || "project",
388
+ group: scope === "component" ? "component" : "workspace",
389
+ label: selectedElement?.text || "project",
390
+ html: selectedElement ? `<${selectedElement.tag}> ${selectedElement.text || ""}` : undefined,
391
+ },
392
+ };
393
+ const proposal = await (0, agent_1.proposeChanges)(cwd, input, config, signal, (message, detail, level) => {
394
+ onProgress(level === "error" ? "error" : "working", message, detail);
395
+ }, (agentPrompt) => {
396
+ onPrompt({ question: agentPrompt.message, options: agentPrompt.options });
397
+ });
398
+ if (signal.aborted) {
399
+ return { ok: false, error: "Operation was cancelled" };
400
+ }
401
+ if (!proposal.changes.length) {
402
+ return { ok: true, summary: proposal.summary, changes: [], thinking: [], totalThinkingTimeMs: Date.now() - startTime };
403
+ }
404
+ onProgress("review", "Implementation complete", `${proposal.changes.length} files modified`);
405
+ return {
406
+ ok: true,
407
+ summary: proposal.summary,
408
+ changes: proposal.changes,
409
+ thinking: [
410
+ { title: "Analyzed project structure", detail: `Detected ${framework} framework` },
411
+ { title: `${cliLabel(config.provider)} generated a proposal`, detail: `${proposal.changes.length} file(s)` },
412
+ ],
413
+ totalThinkingTimeMs: Date.now() - startTime,
414
+ };
415
+ }
416
+ catch (err) {
417
+ if (signal.aborted) {
418
+ return { ok: false, error: "Operation was cancelled" };
419
+ }
420
+ return await gatewayFallback(err instanceof Error ? err.message : `${cliLabel(config.provider)} failed`);
421
+ }
422
+ }
423
+ if (useCliAgent && isConversational) {
424
+ try {
425
+ const reply = await generateConversationalReply(cwd, prompt, framework, config, signal, messages, pageContext, (message, detail) => onProgress("thinking", message, detail), taskId, (agentPrompt) => onPrompt({ question: agentPrompt.message, options: agentPrompt.options }));
426
+ return {
427
+ ok: true,
428
+ summary: reply,
429
+ changes: [],
430
+ thinking: [],
431
+ totalThinkingTimeMs: Date.now() - startTime,
432
+ };
433
+ }
434
+ catch (err) {
435
+ if (signal.aborted) {
436
+ return { ok: false, error: "Operation was cancelled" };
437
+ }
438
+ return await gatewayFallback(err instanceof Error ? err.message : `${cliLabel(config.provider)} failed`);
439
+ }
440
+ }
441
+ if (shouldRouteToServer) {
442
+ return await callGateway({ provider: config.provider, model: config.model });
318
443
  }
319
444
  // If it's a conversational prompt and server was not used or failed, respond directly without modifying files!
320
445
  if (isConversational) {
321
- const reply = await generateConversationalReply(prompt, framework, config, signal, messages, pageContext);
446
+ const reply = await generateConversationalReply(cwd, prompt, framework, config, signal, messages, pageContext);
322
447
  return {
323
448
  ok: true,
324
449
  summary: reply,
@@ -578,11 +703,11 @@ Existing code context (study these carefully before writing — especially the C
578
703
  ${fileSnippets || "No existing components found. Create appropriate files in src/."}
579
704
 
580
705
  Analyze the existing code structure, then respond in JSON:`;
581
- if (!config.apiKey && config.provider !== "ollama") {
706
+ if (!config.apiKey && config.provider !== "ollama" && !isCliProvider(config.provider)) {
582
707
  return {
583
708
  steps: [],
584
709
  isConversational: true,
585
- reply: "Please configure an AI provider API key or model in settings to enable code generation.",
710
+ reply: "I couldn't generate code because no AI provider is available. Run `lasso auth login` to use your Lasso account, or add an AI provider API key in settings, then try again.",
586
711
  };
587
712
  }
588
713
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lasso-ai/cli",
3
- "version": "1.2.5",
3
+ "version": "1.2.6",
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",