@lasso-ai/cli 1.2.6 → 1.3.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.
@@ -3,13 +3,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.isConversationalPrompt = isConversationalPrompt;
7
6
  exports.runOneShotAgent = runOneShotAgent;
8
7
  const node_fs_1 = __importDefault(require("node:fs"));
9
8
  const node_path_1 = __importDefault(require("node:path"));
10
9
  const node_child_process_1 = require("node:child_process");
11
10
  const node_util_1 = require("node:util");
12
11
  const agent_1 = require("./agent");
12
+ const auth_1 = require("./auth");
13
+ const framework_1 = require("./utils/framework");
13
14
  const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
14
15
  const CLI_PROVIDERS = new Set(["claude-code", "codex", "opencode", "cursor"]);
15
16
  function isCliProvider(provider) {
@@ -40,28 +41,46 @@ function gatewayTargetForCli(provider, model) {
40
41
  return { provider: "google", model: GATEWAY_MODELS.google };
41
42
  return { provider: "anthropic", model: GATEWAY_MODELS.anthropic };
42
43
  }
43
- function isConversationalPrompt(prompt) {
44
- const p = prompt.trim().toLowerCase().replace(/[!?.,;:]+$/, "");
45
- const greetings = [
46
- "hi", "hello", "hey", "heya", "howdy", "sup", "yo",
47
- "good morning", "good afternoon", "good evening",
48
- "thanks", "thank you", "thx", "ty", "cool", "awesome", "great", "nice",
49
- "who are you", "what are you", "what can you do", "help", "help me",
50
- "what is lasso", "how do you work", "how does this work",
51
- ];
52
- if (greetings.includes(p))
53
- return true;
54
- if (/^(hi|hello|hey|howdy|yo)\b/i.test(p) && p.split(/\s+/).length <= 4) {
55
- const buildKeywords = /(build|create|add|make|fix|update|modify|change|refactor|implement|delete|remove|style|install|wire|code)/i;
56
- if (!buildKeywords.test(p))
57
- return true;
44
+ async function isConversationalPrompt(prompt, config, apiKey) {
45
+ // Use the user's selected model to intelligently detect if prompt is conversational vs. action-oriented
46
+ try {
47
+ // Use the gateway to classify - this respects the user's selected model
48
+ const response = await fetch(`${config.serverUrl || "https://api.lasso.byorello.space"}/api/v1/agent/generate`, {
49
+ method: "POST",
50
+ headers: {
51
+ "Content-Type": "application/json",
52
+ "Authorization": `Bearer ${apiKey || ""}`,
53
+ },
54
+ body: JSON.stringify({
55
+ model: {
56
+ id: config.model,
57
+ provider: config.provider,
58
+ },
59
+ prompt: `Classify this user prompt as either "conversational" or "action". Return ONLY the word "conversational" or "action" - nothing else.
60
+
61
+ Prompt: "${prompt}"`,
62
+ maxTokens: 10,
63
+ }),
64
+ });
65
+ if (response.ok) {
66
+ const data = await response.json();
67
+ const classification = data.reply?.toLowerCase().trim();
68
+ console.log('[Lasso] Classification result:', classification);
69
+ return classification === "conversational";
70
+ }
71
+ else {
72
+ console.log('[Lasso] Classification failed:', response.status);
73
+ }
58
74
  }
59
- const questionOnly = /^(what is|what's|how do i|why does|can you explain|tell me about)\b/i.test(p);
60
- const actionVerbs = /(build|create|add|make|fix|update|modify|change|refactor|implement|write code|code this|delete|remove|generate)/i;
61
- if (questionOnly && !actionVerbs.test(p)) {
62
- return true;
75
+ catch (err) {
76
+ console.log('[Lasso] Classification error:', err);
63
77
  }
64
- return false;
78
+ // Fallback: smarter heuristic
79
+ const p = prompt.trim().toLowerCase();
80
+ const actionKeywords = /(add|create|build|make|fix|update|modify|change|refactor|implement|delete|remove|style|install|wire|code|replace|insert|append|generate)/i;
81
+ const isAction = actionKeywords.test(p);
82
+ console.log('[Lasso] Fallback classification:', isAction ? 'action' : 'conversational');
83
+ return !isAction;
65
84
  }
66
85
  function runtimeErrorObservation(pageContext) {
67
86
  const errors = Array.isArray(pageContext?.runtimeErrors) ? pageContext.runtimeErrors : [];
@@ -113,7 +132,10 @@ Do not output code changes, file patches, or JSON schemas — just talk to the d
113
132
  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.";
114
133
  }
115
134
  try {
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");
135
+ const model = config.model;
136
+ if (!model) {
137
+ return "No model selected. Please select a model in settings.";
138
+ }
117
139
  if (config.provider === "openai" || config.provider === "ollama" || config.provider === "nvidia") {
118
140
  const baseUrl = config.baseUrl || (config.provider === "nvidia" ? "https://integrate.api.nvidia.com/v1" : "https://api.openai.com/v1");
119
141
  const res = await fetch(`${baseUrl}/chat/completions`, {
@@ -184,9 +206,10 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
184
206
  if (signal.aborted) {
185
207
  return { ok: false, error: "Operation was cancelled" };
186
208
  }
187
- const isConversational = isConversationalPrompt(prompt);
188
- // Browser inspection & snapshot steps
189
- if (pageContext) {
209
+ const isConversational = await isConversationalPrompt(prompt, config, apiKey);
210
+ console.log('[Lasso] Is conversational:', isConversational);
211
+ // Browser inspection & snapshot steps - skip for simple conversational queries
212
+ if (pageContext && !isConversational) {
190
213
  onProgress("thinking", "Inspecting browser page...", `Page: "${pageContext.title || "Landing page"}" (${pageContext.route || "/"})`);
191
214
  if (pageContext.viewport) {
192
215
  onProgress("thinking", "Capturing visual snapshot...", `Viewport: ${pageContext.viewport.width}x${pageContext.viewport.height} (Document height: ${pageContext.viewport.scrollHeight}px)`);
@@ -201,8 +224,9 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
201
224
  else {
202
225
  onProgress("thinking", isConversational ? "Thinking..." : "Analyzing project structure...", isConversational ? undefined : "Scanning files and dependencies");
203
226
  }
204
- const projectStructure = await analyzeProject(cwd);
205
- const framework = detectFramework(cwd);
227
+ // Skip project structure analysis for simple conversational queries
228
+ const projectStructure = isConversational ? null : await analyzeProject(cwd);
229
+ const framework = isConversational ? null : (0, framework_1.detectFramework)(cwd);
206
230
  if (!isConversational && !pageContext) {
207
231
  onProgress("thinking", "Inspecting application...", `Detected ${framework} framework`);
208
232
  }
@@ -216,15 +240,18 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
216
240
  }
217
241
  const targetServerUrl = rawServerUrl.replace(/\/$/, "").replace(/\/api\/v1$/, "");
218
242
  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 || "";
243
+ // Prioritize CLI auth credentials over env vars
244
+ const cliCredentials = (0, auth_1.loadCredentials)();
245
+ const gatewayKey = cliCredentials?.apiKey || apiKey || config.lassoKey || (config.apiKey && (config.apiKey.startsWith("lss_live_") || config.apiKey.startsWith("lss_")) ? config.apiKey : "") || process.env.LASSO_API_KEY || "";
220
246
  const hasLocalProviderKey = Boolean(config.apiKey &&
221
247
  config.apiKey !== "ollama" &&
222
248
  !config.apiKey.startsWith("lss_live_") &&
223
249
  !config.apiKey.startsWith("lss_"));
250
+ // If the user has CLI auth, always route to server (unless using local agents)
224
251
  // If the user does NOT have a local provider key in .env, route to Lasso Agent Gateway on server.
225
252
  // When they DO have a local key in .env, it runs locally to save server resources!
226
253
  // Ollama always runs locally (the server cannot reach the user's localhost).
227
- const shouldRouteToServer = !useCliAgent && config.provider !== "ollama" && !hasLocalProviderKey && Boolean(targetServerUrl);
254
+ const shouldRouteToServer = !useCliAgent && config.provider !== "ollama" && (Boolean(cliCredentials) || !hasLocalProviderKey) && Boolean(targetServerUrl);
228
255
  const callGateway = async (target) => {
229
256
  try {
230
257
  if (!isConversational && !pageContext) {
@@ -241,10 +268,15 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
241
268
  const controller = new AbortController();
242
269
  const onAbort = () => controller.abort();
243
270
  signal.addEventListener("abort", onAbort, { once: true });
271
+ // Add a 5-minute timeout for gateway requests
272
+ const timeout = setTimeout(() => {
273
+ controller.abort();
274
+ }, 5 * 60 * 1000);
275
+ timeout.unref();
244
276
  // Build sourceHints: read relevant file contents to give the AI real code context.
245
277
  // This is what enables code changes instead of "please share the file" responses.
246
278
  const sourceHints = {};
247
- if (!isConversational) {
279
+ if (!isConversational && projectStructure) {
248
280
  const promptLower = prompt.toLowerCase();
249
281
  const allCssFiles = projectStructure.files.filter((f) => /\.(css|scss)$/i.test(f));
250
282
  const entryFiles = projectStructure.files.filter((f) => /(App|page|index|layout|main|hero|header|landing)\.(tsx|jsx|js|ts|html)$/i.test(f));
@@ -274,30 +306,48 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
274
306
  provider: target.provider,
275
307
  },
276
308
  context: {
277
- framework,
278
- hasTypeScript: projectStructure.hasTypeScript,
279
- hasTailwind: projectStructure.hasTailwind,
280
- hasReact: projectStructure.hasReact,
281
- hasNext: projectStructure.hasNext,
282
- hasVite: projectStructure.hasVite,
283
- dependencies: projectStructure.dependencies,
284
- devDependencies: projectStructure.devDependencies,
285
- files: projectStructure.files.slice(0, 100),
309
+ framework: framework || undefined,
310
+ hasTypeScript: projectStructure?.hasTypeScript,
311
+ hasTailwind: projectStructure?.hasTailwind,
312
+ hasReact: projectStructure?.hasReact,
313
+ hasNext: projectStructure?.hasNext,
314
+ hasVite: projectStructure?.hasVite,
315
+ dependencies: projectStructure?.dependencies,
316
+ devDependencies: projectStructure?.devDependencies,
317
+ files: projectStructure?.files.slice(0, 100) || [],
318
+ directories: projectStructure?.directories.slice(0, 50) || [],
319
+ sourceRoot: projectStructure?.sourceRoot,
320
+ appRouter: projectStructure?.appRouter,
321
+ pagesRouter: projectStructure?.pagesRouter,
322
+ multipleAppDirs: projectStructure?.multipleAppDirs,
286
323
  sourceHints: Object.keys(sourceHints).length > 0 ? sourceHints : undefined,
287
324
  currentRoute: pageContext?.route || "/",
288
325
  pageContext,
289
326
  },
290
327
  }),
291
328
  });
329
+ clearTimeout(timeout);
292
330
  signal.removeEventListener("abort", onAbort);
331
+ if (signal.aborted) {
332
+ return { ok: false, error: "Request timed out after 5 minutes. Your plan quota may be exhausted. Please upgrade your plan or add credits to continue using this service." };
333
+ }
293
334
  if (!response.ok) {
294
335
  const errData = (await response.json().catch(() => ({})));
295
336
  const errorMessage = errData.error || `Agent request failed (${response.status})`;
337
+ // Check for quota/plan exhaustion
338
+ if (/quota exceeded|quota limit|out of quota|no quota|plan.*limit|plan.*exhausted|credit.*exhausted|billing.*quota|rate.*limit|429|too many requests|request.*limit|insufficient.*credits|no.*credits|credits.*exhausted|payment.*required|subscription.*required|upgrade.*required|upgrade.*plan|billing.*required/i.test(errorMessage)) {
339
+ return { ok: false, error: "Your plan quota has been exhausted. Please upgrade your plan or add credits to continue using this service." };
340
+ }
296
341
  return { ok: false, error: errorMessage };
297
342
  }
298
343
  const data = (await response.json());
299
344
  if (data.status === "failed" || data.error) {
300
- return { ok: false, error: data.error || data.summary || "Agent session failed on server." };
345
+ const error = data.error || data.summary || "Agent session failed on server.";
346
+ // Check for quota/plan exhaustion
347
+ if (/quota exceeded|quota limit|out of quota|no quota|plan.*limit|plan.*exhausted|credit.*exhausted|billing.*quota|rate.*limit|429|too many requests|request.*limit|insufficient.*credits|no.*credits|credits.*exhausted|payment.*required|subscription.*required|upgrade.*required|upgrade.*plan|billing.*required/i.test(error)) {
348
+ return { ok: false, error: "Your plan quota has been exhausted. Please upgrade your plan or add credits to continue using this service." };
349
+ }
350
+ return { ok: false, error };
301
351
  }
302
352
  if (data) {
303
353
  const changes = Array.isArray(data.changes) ? data.changes : [];
@@ -422,7 +472,7 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
422
472
  }
423
473
  if (useCliAgent && isConversational) {
424
474
  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 }));
475
+ 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
476
  return {
427
477
  ok: true,
428
478
  summary: reply,
@@ -443,7 +493,7 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
443
493
  }
444
494
  // If it's a conversational prompt and server was not used or failed, respond directly without modifying files!
445
495
  if (isConversational) {
446
- const reply = await generateConversationalReply(cwd, prompt, framework, config, signal, messages, pageContext);
496
+ const reply = await generateConversationalReply(cwd, prompt, framework || "", config, signal, messages, pageContext);
447
497
  return {
448
498
  ok: true,
449
499
  summary: reply,
@@ -454,7 +504,7 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
454
504
  }
455
505
  // Phase 2: Local agent generation fallback for code changes
456
506
  onProgress("thinking", "Generating implementation plan...", `Planning components for ${prompt}`);
457
- const plan = await generatePlan(cwd, prompt, scope, projectStructure, framework, config, signal, messages, pageContext);
507
+ const plan = await generatePlan(cwd, prompt, scope, projectStructure || { framework: "", hasTypeScript: false, hasTailwind: false, hasReact: false, hasNext: false, hasVite: false, dependencies: [], devDependencies: [], files: [], directories: [], sourceRoot: ".", appRouter: false, pagesRouter: false, multipleAppDirs: [] }, framework || "", config, signal, messages, pageContext);
458
508
  if (signal.aborted) {
459
509
  return { ok: false, error: "Operation was cancelled during planning" };
460
510
  }
@@ -549,24 +599,33 @@ async function analyzeProject(cwd) {
549
599
  const hasReact = dependencies.includes("react") || devDependencies.includes("react");
550
600
  const hasNext = dependencies.includes("next") || devDependencies.includes("next");
551
601
  const hasVite = dependencies.includes("vite") || devDependencies.includes("vite");
552
- // Scan src / app directory
602
+ // Scan directory structure
553
603
  const files = [];
554
- const roots = ["src", "app", "pages", "."];
604
+ const directories = [];
605
+ const appDirs = [];
606
+ const pagesDirs = [];
607
+ const roots = ["src", "app", "pages", ".", "web", "lib", "components"];
555
608
  for (const root of roots) {
556
609
  const rootPath = node_path_1.default.join(cwd, root);
557
610
  if (!node_fs_1.default.existsSync(rootPath))
558
611
  continue;
559
612
  const scanDir = (dir, base = "", depth = 0) => {
560
- if (depth > 4)
613
+ if (depth > 5)
561
614
  return;
562
615
  try {
563
616
  const entries = node_fs_1.default.readdirSync(dir, { withFileTypes: true });
564
617
  for (const entry of entries) {
565
- if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build") {
618
+ if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === ".next") {
566
619
  continue;
567
620
  }
568
621
  const rel = base ? node_path_1.default.join(base, entry.name) : entry.name;
569
622
  if (entry.isDirectory()) {
623
+ directories.push(rel);
624
+ // Track app and pages directories
625
+ if (entry.name === "app")
626
+ appDirs.push(rel);
627
+ if (entry.name === "pages")
628
+ pagesDirs.push(rel);
570
629
  scanDir(node_path_1.default.join(dir, entry.name), rel, depth + 1);
571
630
  }
572
631
  else if (/\.(tsx?|jsx?|vue|svelte|css|html|json)$/i.test(entry.name)) {
@@ -579,44 +638,40 @@ async function analyzeProject(cwd) {
579
638
  }
580
639
  };
581
640
  scanDir(rootPath);
582
- if (files.length > 60)
641
+ if (files.length > 100)
583
642
  break;
584
643
  }
644
+ // Determine source root
645
+ let sourceRoot = ".";
646
+ if (directories.includes("src/app"))
647
+ sourceRoot = "src";
648
+ else if (directories.includes("app"))
649
+ sourceRoot = ".";
650
+ else if (directories.includes("src"))
651
+ sourceRoot = "src";
652
+ // Detect App Router vs Pages Router
653
+ const appRouter = appDirs.length > 0 && appDirs.some(dir => {
654
+ const appPath = node_path_1.default.join(cwd, dir);
655
+ return node_fs_1.default.existsSync(node_path_1.default.join(appPath, "layout.tsx")) || node_fs_1.default.existsSync(node_path_1.default.join(appPath, "page.tsx"));
656
+ });
657
+ const pagesRouter = pagesDirs.length > 0 && node_fs_1.default.existsSync(node_path_1.default.join(cwd, "pages", "_app.tsx")) || node_fs_1.default.existsSync(node_path_1.default.join(cwd, "pages", "_document.tsx"));
585
658
  return {
586
- framework: detectFramework(cwd),
659
+ framework: (0, framework_1.detectFramework)(cwd),
587
660
  hasTypeScript,
588
661
  hasTailwind,
589
662
  hasReact,
590
663
  hasNext,
591
664
  hasVite,
592
- dependencies,
593
- devDependencies,
665
+ dependencies: dependencies,
666
+ devDependencies: devDependencies,
594
667
  files,
668
+ directories,
669
+ sourceRoot,
670
+ appRouter,
671
+ pagesRouter,
672
+ multipleAppDirs: appDirs,
595
673
  };
596
674
  }
597
- function detectFramework(cwd) {
598
- const pkgPath = node_path_1.default.join(cwd, "package.json");
599
- if (node_fs_1.default.existsSync(pkgPath)) {
600
- try {
601
- const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgPath, "utf8"));
602
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
603
- if (deps.next)
604
- return "next";
605
- if (deps.vite)
606
- return "vite";
607
- if (deps.react)
608
- return "react";
609
- if (deps.vue)
610
- return "vue";
611
- if (deps.svelte)
612
- return "svelte";
613
- }
614
- catch {
615
- // fallback
616
- }
617
- }
618
- return "unknown";
619
- }
620
675
  async function generatePlan(cwd, prompt, _scope, structure, framework, config, signal, _messages, pageContext) {
621
676
  const steps = [];
622
677
  // Identify relevant files to supply as context.
@@ -711,7 +766,14 @@ Analyze the existing code structure, then respond in JSON:`;
711
766
  };
712
767
  }
713
768
  try {
714
- 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");
769
+ const model = config.model;
770
+ if (!model) {
771
+ return {
772
+ steps: [],
773
+ isConversational: true,
774
+ reply: "No model selected. Please select a model in settings.",
775
+ };
776
+ }
715
777
  let text = "";
716
778
  if (config.provider === "openai" || config.provider === "ollama" || config.provider === "nvidia") {
717
779
  const baseUrl = config.baseUrl || (config.provider === "nvidia" ? "https://integrate.api.nvidia.com/v1" : "https://api.openai.com/v1");
@@ -15,6 +15,24 @@ export interface CollabConfig {
15
15
  plan?: string;
16
16
  configuredProviders?: string[];
17
17
  }
18
+ export interface WorkspaceConfig {
19
+ workspaceId?: string;
20
+ workspaceName?: string;
21
+ plan?: string;
22
+ configuredProviders?: string[];
23
+ }
24
+ export interface ModelInfo {
25
+ id: string;
26
+ name: string;
27
+ provider: string;
28
+ capabilities?: string[];
29
+ }
30
+ export interface ModelsResponse {
31
+ anthropic: ModelInfo[];
32
+ openai: ModelInfo[];
33
+ google: ModelInfo[];
34
+ nvidia: ModelInfo[];
35
+ }
18
36
  export interface InitResult {
19
37
  ok: boolean;
20
38
  error?: string;
@@ -57,3 +75,13 @@ export declare function initProject(cwd: string, fileEnv: Record<string, string>
57
75
  * safely degraded (no collaboration, local editing unaffected) on any failure.
58
76
  */
59
77
  export declare function resolveCollabSession(cwd: string, fileEnv: Record<string, string>, projectId: string): Promise<CollabConfig>;
78
+ /**
79
+ * Fetch workspace configuration from the server using CLI auth credentials.
80
+ * This allows users to see their dashboard-configured providers even without a project config.
81
+ */
82
+ export declare function resolveWorkspaceConfig(fileEnv: Record<string, string>): Promise<WorkspaceConfig>;
83
+ /**
84
+ * Fetch available models from the server's models endpoint.
85
+ * This fetches from provider APIs dynamically and caches results.
86
+ */
87
+ export declare function fetchModels(fileEnv: Record<string, string>): Promise<ModelsResponse>;
@@ -10,6 +10,8 @@ exports.resolveLassoApiKey = resolveLassoApiKey;
10
10
  exports.readProjectConfig = readProjectConfig;
11
11
  exports.initProject = initProject;
12
12
  exports.resolveCollabSession = resolveCollabSession;
13
+ exports.resolveWorkspaceConfig = resolveWorkspaceConfig;
14
+ exports.fetchModels = fetchModels;
13
15
  const node_fs_1 = __importDefault(require("node:fs"));
14
16
  const node_path_1 = __importDefault(require("node:path"));
15
17
  const node_readline_1 = __importDefault(require("node:readline"));
@@ -351,3 +353,58 @@ async function resolveCollabSession(cwd, fileEnv, projectId) {
351
353
  return config;
352
354
  }
353
355
  }
356
+ /**
357
+ * Fetch workspace configuration from the server using CLI auth credentials.
358
+ * This allows users to see their dashboard-configured providers even without a project config.
359
+ */
360
+ async function resolveWorkspaceConfig(fileEnv) {
361
+ const serverUrl = (0, auth_1.serverUrlFrom)(fileEnv).replace(/\/$/, "");
362
+ const apiUrl = `${serverUrl}/api/v1`;
363
+ const apiKey = resolveLassoApiKey(fileEnv);
364
+ if (!apiKey) {
365
+ return {};
366
+ }
367
+ try {
368
+ const response = await (0, auth_1.fetchWithTimeout)(`${apiUrl}/auth/workspace`, {
369
+ method: "GET",
370
+ headers: { authorization: `Bearer ${apiKey}` },
371
+ });
372
+ if (!response.ok) {
373
+ return {};
374
+ }
375
+ const body = (await response.json());
376
+ if (!body.success || !body.workspace) {
377
+ return {};
378
+ }
379
+ return {
380
+ workspaceId: body.workspace.id,
381
+ workspaceName: body.workspace.name,
382
+ plan: body.workspace.plan || "free",
383
+ configuredProviders: body.workspace.configuredProviders || [],
384
+ };
385
+ }
386
+ catch {
387
+ return {};
388
+ }
389
+ }
390
+ /**
391
+ * Fetch available models from the server's models endpoint.
392
+ * This fetches from provider APIs dynamically and caches results.
393
+ */
394
+ async function fetchModels(fileEnv) {
395
+ const serverUrl = (0, auth_1.serverUrlFrom)(fileEnv).replace(/\/$/, "");
396
+ const apiUrl = `${serverUrl}/api/v1`;
397
+ try {
398
+ const response = await (0, auth_1.fetchWithTimeout)(`${apiUrl}/models`, {
399
+ method: "GET",
400
+ });
401
+ if (!response.ok) {
402
+ return { anthropic: [], openai: [], google: [], nvidia: [] };
403
+ }
404
+ const body = await response.json();
405
+ return body;
406
+ }
407
+ catch {
408
+ return { anthropic: [], openai: [], google: [], nvidia: [] };
409
+ }
410
+ }
package/dist/overlay.js CHANGED
@@ -7848,6 +7848,8 @@
7848
7848
  return `${Math.floor(seconds / 86400)}d`;
7849
7849
  }
7850
7850
  var modelSessionKey = "lasso:selected-model";
7851
+ var chatHistoryKey = "lasso:chat-history";
7852
+ var changesHistoryKey = "lasso:changes-history";
7851
7853
  function storedModelId() {
7852
7854
  try {
7853
7855
  return window.sessionStorage.getItem(modelSessionKey);
@@ -7861,6 +7863,22 @@
7861
7863
  } catch {
7862
7864
  }
7863
7865
  }
7866
+ function loadChatHistory() {
7867
+ try {
7868
+ const chatHistory = JSON.parse(localStorage.getItem(chatHistoryKey) || "[]");
7869
+ const changesHistory = JSON.parse(localStorage.getItem(changesHistoryKey) || "[]");
7870
+ return { chatHistory, changesHistory };
7871
+ } catch {
7872
+ return { chatHistory: [], changesHistory: [] };
7873
+ }
7874
+ }
7875
+ function saveChatHistory(chatHistory, changesHistory) {
7876
+ try {
7877
+ localStorage.setItem(chatHistoryKey, JSON.stringify(chatHistory));
7878
+ localStorage.setItem(changesHistoryKey, JSON.stringify(changesHistory));
7879
+ } catch {
7880
+ }
7881
+ }
7864
7882
  var OverlayState = class {
7865
7883
  // Mode & Selection
7866
7884
  selectMode = false;
@@ -21611,7 +21629,24 @@
21611
21629
  recordTaskActivity(task.id, message.message || "Done.");
21612
21630
  }
21613
21631
  const isPromptTask = !taskId2 || taskId2 === state.promptTaskId;
21614
- const result = message.message || "Done.";
21632
+ let result = message.message || "Done.";
21633
+ if (message.type === "applied" && message.validation) {
21634
+ const { passed, errors, command } = message.validation;
21635
+ if (!passed && errors.length > 0) {
21636
+ result = `${message.message}
21637
+
21638
+ \u26A0\uFE0F Build validation failed after running \`${command}\`:
21639
+
21640
+ ${errors.slice(0, 5).join("\n")}${errors.length > 5 ? `
21641
+ ...and ${errors.length - 5} more errors` : ""}
21642
+
21643
+ You may need to manually fix these errors or undo the change.`;
21644
+ } else if (passed) {
21645
+ result = `${message.message}
21646
+
21647
+ \u2705 Build validation passed.`;
21648
+ }
21649
+ }
21615
21650
  void notifyAgent(message.type === "applied" ? "Changes applied" : "Change undone", result);
21616
21651
  appendChat("assistant", result, taskId2);
21617
21652
  if (task?.element) releaseHeldLock(elementKey(task.element));
@@ -21743,6 +21778,8 @@
21743
21778
  updateAgentTask(id, {
21744
21779
  changesHistory: [...task.changesHistory, { summary, changes: changes.map((change) => ({ ...change })), createdAt: (/* @__PURE__ */ new Date()).toISOString() }]
21745
21780
  });
21781
+ state.changesHistory = task.changesHistory;
21782
+ saveChatHistory(state.chatHistory, state.changesHistory);
21746
21783
  }
21747
21784
  function appendTaskMessage(id, role, text) {
21748
21785
  const task = getAgentTask(id);
@@ -21757,6 +21794,8 @@
21757
21794
  contextId: task.selectionId || void 0
21758
21795
  };
21759
21796
  updateAgentTask(task.id, { messages: [...task.messages, message] });
21797
+ state.chatHistory = task.messages;
21798
+ saveChatHistory(state.chatHistory, state.changesHistory);
21760
21799
  return message;
21761
21800
  }
21762
21801
  function selectAgentTask(id) {
@@ -21916,6 +21955,7 @@
21916
21955
  state.selectionId = selectionId;
21917
21956
  state.chatHistory = conversation.messages;
21918
21957
  state.changesHistory = conversation.changesHistory;
21958
+ saveChatHistory(state.chatHistory, state.changesHistory);
21919
21959
  }
21920
21960
  function buildPrompt() {
21921
21961
  const dom = getDOM();
@@ -22101,6 +22141,12 @@
22101
22141
  if (task) restoreTaskPrompt(task);
22102
22142
  else resetPromptSession();
22103
22143
  });
22144
+ const { chatHistory, changesHistory } = loadChatHistory();
22145
+ if (chatHistory.length > 0) {
22146
+ state.chatHistory = chatHistory;
22147
+ state.changesHistory = changesHistory;
22148
+ renderChatThread();
22149
+ }
22104
22150
  onAgentTaskReviewRequested((task) => {
22105
22151
  state.promptTaskId = task.id;
22106
22152
  state.pendingChanges = [...task.pendingChanges.length ? task.pendingChanges : task.changes || []];
@@ -22610,6 +22656,7 @@
22610
22656
  contextId: state.selectionId || void 0
22611
22657
  });
22612
22658
  }
22659
+ saveChatHistory(state.chatHistory, state.changesHistory);
22613
22660
  renderChatThread();
22614
22661
  }
22615
22662
  function renderChatText(container, text) {
@@ -22696,6 +22743,7 @@
22696
22743
  state.chatHistory = [];
22697
22744
  state.changesHistory = [];
22698
22745
  state.selectionId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
22746
+ saveChatHistory(state.chatHistory, state.changesHistory);
22699
22747
  }
22700
22748
  state.screenshotPromise = Promise.resolve({});
22701
22749
  hideReview();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lasso-ai/cli",
3
- "version": "1.2.6",
3
+ "version": "1.3.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",