@lasso-ai/cli 1.2.2 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/auth.js CHANGED
@@ -30,7 +30,9 @@ function serverUrlFrom(fileEnv) {
30
30
  fileEnv.LASSO_AUTH_URL ||
31
31
  process.env.LASSO_API_URL ||
32
32
  fileEnv.LASSO_API_URL ||
33
- "https://api.lasso.byorello.space").replace(/\/$/, "");
33
+ process.env.LASSO_SERVER_URL ||
34
+ fileEnv.LASSO_SERVER_URL ||
35
+ (process.env.NODE_ENV === "development" ? "http://localhost:3005" : "https://api.lasso.byorello.space")).replace(/\/$/, "");
34
36
  }
35
37
  function fetchWithTimeout(url, init, timeoutMs = 8000) {
36
38
  const controller = new AbortController();
@@ -17,7 +17,7 @@ export type BridgeMessage = {
17
17
  taskId: string;
18
18
  instruction: string;
19
19
  model: string;
20
- provider?: "anthropic" | "openai" | "google" | "ollama" | "cli";
20
+ provider?: "anthropic" | "openai" | "google" | "ollama" | "nvidia" | "cli";
21
21
  messages?: Array<{
22
22
  role: string;
23
23
  content: string;
@@ -52,7 +52,7 @@ export type BridgeMessage = {
52
52
  taskId: string;
53
53
  question: string;
54
54
  model: string;
55
- provider?: "anthropic" | "openai" | "google" | "ollama" | "cli";
55
+ provider?: "anthropic" | "openai" | "google" | "ollama" | "nvidia" | "cli";
56
56
  messages?: Array<{
57
57
  role: string;
58
58
  content: string;
@@ -112,7 +112,7 @@ export type BridgeMessage = {
112
112
  } | {
113
113
  type: "git_generate_message";
114
114
  model: string;
115
- provider?: "anthropic" | "openai" | "google" | "ollama" | "cli";
115
+ provider?: "anthropic" | "openai" | "google" | "ollama" | "nvidia" | "cli";
116
116
  } | {
117
117
  type: "git_init";
118
118
  } | {
@@ -134,14 +134,20 @@ export type BridgeMessage = {
134
134
  } | {
135
135
  type: "oneshot";
136
136
  prompt: string;
137
- scope: "project" | "component";
137
+ scope?: "project" | "component";
138
138
  model?: string;
139
- provider?: "anthropic" | "openai" | "google" | "ollama" | "cli";
139
+ provider?: "anthropic" | "openai" | "google" | "ollama" | "nvidia" | "cli";
140
+ messages?: Array<{
141
+ role: "user" | "assistant";
142
+ content: string;
143
+ }>;
144
+ taskId?: string;
145
+ pageContext?: any;
140
146
  };
141
147
  type ModelOption = {
142
148
  id: string;
143
149
  label: string;
144
- provider: "anthropic" | "openai" | "google" | "ollama" | "cli";
150
+ provider: "anthropic" | "openai" | "google" | "ollama" | "nvidia" | "cli";
145
151
  };
146
152
  type PageEntry = {
147
153
  name: string;
@@ -154,6 +160,10 @@ export type ServerBridgeMessage = {
154
160
  agentConfigured: boolean;
155
161
  models: ModelOption[];
156
162
  collab?: CollabConfig | null;
163
+ user?: {
164
+ name?: string;
165
+ email?: string;
166
+ } | null;
157
167
  } | {
158
168
  type: "git_state";
159
169
  git: GitState;
@@ -175,6 +185,12 @@ export type ServerBridgeMessage = {
175
185
  message: string;
176
186
  detail?: string;
177
187
  changes?: SourceChange[];
188
+ thinking?: Array<{
189
+ title: string;
190
+ detail?: string;
191
+ durationMs?: number;
192
+ }>;
193
+ totalThinkingTimeMs?: number;
178
194
  } | {
179
195
  type: "agent_prompt";
180
196
  taskId: string;
@@ -15,6 +15,7 @@ const node_child_process_1 = require("node:child_process");
15
15
  const node_util_1 = require("node:util");
16
16
  const chalk_1 = __importDefault(require("chalk"));
17
17
  const agent_1 = require("./agent");
18
+ const project_1 = require("./project");
18
19
  const auth_1 = require("./auth");
19
20
  exports.DEFAULT_BRIDGE_PORT = 3056;
20
21
  const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
@@ -52,7 +53,7 @@ function fileLineEnding(content) {
52
53
  function withLineEnding(value, lineEnding) {
53
54
  return value.replace(/\r\n?|\n/g, lineEnding);
54
55
  }
55
- function resolveProposedFile(cwd, proposedPath) {
56
+ function resolveProposedFile(cwd, proposedPath, allowNew = false) {
56
57
  const root = node_path_1.default.resolve(cwd);
57
58
  const candidate = node_path_1.default.resolve(root, proposedPath);
58
59
  if (candidate.startsWith(`${root}${node_path_1.default.sep}`) && node_fs_1.default.existsSync(candidate))
@@ -65,10 +66,15 @@ function resolveProposedFile(cwd, proposedPath) {
65
66
  const rebased = node_path_1.default.join(root, normalized.slice(srcIndex + 1));
66
67
  if (rebased.startsWith(`${root}${node_path_1.default.sep}`) && node_fs_1.default.existsSync(rebased))
67
68
  return rebased;
69
+ if (allowNew && (rebased.startsWith(`${root}${node_path_1.default.sep}`) || rebased === root))
70
+ return rebased;
71
+ }
72
+ if (candidate.startsWith(`${root}${node_path_1.default.sep}`) || candidate === root) {
73
+ if (allowNew)
74
+ return candidate;
75
+ throw new Error(`Could not find the proposed file in the current project: ${proposedPath}`);
68
76
  }
69
- if (!candidate.startsWith(`${root}${node_path_1.default.sep}`))
70
- throw new Error(`The proposed file is outside the current project: ${proposedPath}`);
71
- throw new Error(`Could not find the proposed file in the current project: ${proposedPath}`);
77
+ throw new Error(`The proposed file is outside the current project: ${proposedPath}`);
72
78
  }
73
79
  function occurrenceCount(content, needle) {
74
80
  if (!needle)
@@ -117,6 +123,8 @@ function prepareChange(content, change) {
117
123
  function proposalMatchesCurrentSource(cwd, changes) {
118
124
  try {
119
125
  for (const change of changes) {
126
+ if (!change.oldString)
127
+ continue;
120
128
  const filePath = resolveProposedFile(cwd, change.filePath);
121
129
  const content = node_fs_1.default.readFileSync(filePath, "utf8");
122
130
  // Try exact match first
@@ -171,24 +179,33 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
171
179
  ...readEnvFile(root, ".env.local"),
172
180
  ...readEnvFile(root, ".env.production"),
173
181
  }), {});
174
- const lassoKeyConfigured = Boolean(process.env.VITE_LASSO_API_KEY || process.env.NEXT_LASSO_API_KEY || fileEnv.VITE_LASSO_API_KEY || fileEnv.NEXT_LASSO_API_KEY);
182
+ const resolvedLassoKey = (0, project_1.resolveLassoApiKey)(fileEnv) ||
183
+ process.env.LASSO_API_KEY ||
184
+ fileEnv.LASSO_API_KEY ||
185
+ process.env.VITE_LASSO_API_KEY ||
186
+ process.env.NEXT_LASSO_API_KEY ||
187
+ fileEnv.VITE_LASSO_API_KEY ||
188
+ fileEnv.NEXT_LASSO_API_KEY ||
189
+ "";
190
+ const lassoKeyConfigured = Boolean(resolvedLassoKey);
191
+ const localProviderKeys = {
192
+ google: process.env.GOOGLE_GENERATIVE_AI_API_KEY || fileEnv.GOOGLE_GENERATIVE_AI_API_KEY || process.env.GEMINI_API_KEY || fileEnv.GEMINI_API_KEY,
193
+ openai: process.env.OPENAI_API_KEY || fileEnv.OPENAI_API_KEY,
194
+ anthropic: process.env.ANTHROPIC_API_KEY || fileEnv.ANTHROPIC_API_KEY,
195
+ nvidia: process.env.NVIDIA_API_KEY || fileEnv.NVIDIA_API_KEY,
196
+ ollama: process.env.OLLAMA_BASE_URL || fileEnv.OLLAMA_BASE_URL || process.env.OLLAMA_MODEL || fileEnv.OLLAMA_MODEL ? "ollama" : undefined,
197
+ };
175
198
  let agentConfig = (() => {
176
199
  const provider = (process.env.LASSO_AGENT_PROVIDER || fileEnv.LASSO_AGENT_PROVIDER || process.env.AI_PROVIDER || fileEnv.AI_PROVIDER || "").toLowerCase();
177
- const keys = {
178
- google: process.env.GOOGLE_GENERATIVE_AI_API_KEY || fileEnv.GOOGLE_GENERATIVE_AI_API_KEY || process.env.GEMINI_API_KEY || fileEnv.GEMINI_API_KEY,
179
- openai: process.env.OPENAI_API_KEY || fileEnv.OPENAI_API_KEY,
180
- anthropic: process.env.ANTHROPIC_API_KEY || fileEnv.ANTHROPIC_API_KEY,
181
- ollama: process.env.OLLAMA_BASE_URL || fileEnv.OLLAMA_BASE_URL || process.env.OLLAMA_MODEL || fileEnv.OLLAMA_MODEL,
182
- };
183
- const selectedProvider = provider === "google" || provider === "gemini" ? "google" : provider === "openai" ? "openai" : provider === "ollama" ? "ollama" : provider === "anthropic" ? "anthropic" : keys.ollama ? "ollama" : keys.google ? "google" : keys.openai ? "openai" : "anthropic";
184
- const apiKey = selectedProvider === "ollama" ? "ollama" : keys[selectedProvider];
185
- if (!apiKey)
200
+ const selectedProvider = provider === "google" || provider === "gemini" ? "google" : provider === "openai" ? "openai" : provider === "ollama" ? "ollama" : provider === "anthropic" ? "anthropic" : provider === "nvidia" ? "nvidia" : localProviderKeys.ollama ? "ollama" : localProviderKeys.nvidia ? "nvidia" : localProviderKeys.google ? "google" : localProviderKeys.openai ? "openai" : localProviderKeys.anthropic ? "anthropic" : "google";
201
+ const apiKey = selectedProvider === "ollama" ? "ollama" : localProviderKeys[selectedProvider];
202
+ if (!apiKey && !lassoKeyConfigured)
186
203
  return null;
187
204
  return {
188
205
  provider: selectedProvider,
189
- apiKey,
206
+ apiKey: apiKey || resolvedLassoKey,
190
207
  model: process.env.LASSO_AGENT_MODEL || fileEnv.LASSO_AGENT_MODEL || process.env.AI_MODEL || fileEnv.AI_MODEL,
191
- baseUrl: process.env.OLLAMA_BASE_URL || fileEnv.OLLAMA_BASE_URL ? `${(process.env.OLLAMA_BASE_URL || fileEnv.OLLAMA_BASE_URL || "http://localhost:11434/api").replace(/\/api\/?$/, "")}/v1` : undefined,
208
+ baseUrl: selectedProvider === "nvidia" ? "https://integrate.api.nvidia.com/v1" : process.env.OLLAMA_BASE_URL || fileEnv.OLLAMA_BASE_URL ? `${(process.env.OLLAMA_BASE_URL || fileEnv.OLLAMA_BASE_URL || "http://localhost:11434/api").replace(/\/api\/?$/, "")}/v1` : undefined,
192
209
  };
193
210
  })();
194
211
  let activeAgentController = null;
@@ -293,9 +310,26 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
293
310
  const base = process.env.OLLAMA_BASE_URL || fileEnv.OLLAMA_BASE_URL || "http://localhost:11434/api";
294
311
  agentConfig = { provider: "ollama", apiKey: "ollama", model: process.env.OLLAMA_MODEL || fileEnv.OLLAMA_MODEL, baseUrl: `${base.replace(/\/api\/?$/, "")}/v1` };
295
312
  }
296
- const isPaidPlan = collabConfig?.plan && collabConfig.plan !== "free";
313
+ const isPaidPlan = Boolean(collabConfig?.plan && collabConfig.plan.toLowerCase() !== "free");
297
314
  const configuredProviders = collabConfig?.configuredProviders || [];
298
315
  const allModels = [
316
+ // NVIDIA NIM models (confirmed working with server API key)
317
+ { id: "meta/llama-3.2-11b-vision-instruct", label: "Llama 3.2 11B Vision", provider: "nvidia" },
318
+ { id: "openai/gpt-oss-20b", label: "GPT-OSS 20B (NVIDIA)", provider: "nvidia" },
319
+ { id: "deepseek-ai/deepseek-coder-6.7b-instruct", label: "DeepSeek Coder 6.7B", provider: "nvidia" },
320
+ { id: "deepseek-ai/deepseek-v4.1-flash", label: "DeepSeek V4.1 Flash", provider: "nvidia" },
321
+ { id: "ibm/granite-34b-code-instruct", label: "Granite 34B Code", provider: "nvidia" },
322
+ { id: "ibm/granite-3.0-8b-instruct", label: "Granite 3.0 8B", provider: "nvidia" },
323
+ { id: "mistralai/mistral-large-2-instruct", label: "Mistral Large 2", provider: "nvidia" },
324
+ { id: "mistralai/codestral-22b-instruct-v0.1", label: "Codestral 22B", provider: "nvidia" },
325
+ { id: "mistralai/mistral-large", label: "Mistral Large", provider: "nvidia" },
326
+ { id: "nv-mistralai/mistral-nemo-12b-instruct", label: "Mistral Nemo 12B", provider: "nvidia" },
327
+ { id: "nvidia/nemotron-3.5-lightning-30b-a3b", label: "Nemotron Lightning 30B", provider: "nvidia" },
328
+ { id: "microsoft/phi-3.5-moe-instruct", label: "Phi-3.5 MoE", provider: "nvidia" },
329
+ { id: "google/gemma-4-31b-it", label: "Gemma 4 31B", provider: "nvidia" },
330
+ { id: "moonshotai/kimi-k2.6", label: "Kimi K2.6", provider: "nvidia" },
331
+ { id: "poolside/laguna-xs-2.1", label: "Laguna XS 2.1", provider: "nvidia" },
332
+ // Existing models
299
333
  { id: "claude-sonnet-4-5-20250929", label: "Claude Sonnet 4.5", provider: "anthropic" },
300
334
  { id: "claude-opus-4-1-20250805", label: "Claude Opus 4.1", provider: "anthropic" },
301
335
  { id: "gpt-4.1", label: "GPT-4.1", provider: "openai" },
@@ -340,6 +374,7 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
340
374
  anthropic: "ANTHROPIC_API_KEY",
341
375
  openai: "OPENAI_API_KEY",
342
376
  google: "GOOGLE_GENERATIVE_AI_API_KEY",
377
+ nvidia: "NVIDIA_API_KEY",
343
378
  ollama: "OLLAMA_BASE_URL",
344
379
  };
345
380
  return allModels.map((model) => {
@@ -355,7 +390,8 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
355
390
  const envKey = providerKeyMapping[model.provider];
356
391
  const isConfigured = configuredProviders.includes(model.provider) ||
357
392
  Boolean(process.env[envKey]) ||
358
- Boolean(fileEnv[envKey]);
393
+ Boolean(fileEnv[envKey]) ||
394
+ Boolean(localProviderKeys[model.provider]);
359
395
  if (!isConfigured) {
360
396
  return {
361
397
  ...model,
@@ -369,10 +405,14 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
369
405
  wss.on("connection", (socket) => {
370
406
  overlaySocket = socket;
371
407
  console.log(chalk_1.default.green("✓") + " Overlay connected");
372
- socket.send(JSON.stringify({ type: "config", apiKeyConfigured: lassoKeyConfigured, agentConfigured: Boolean(agentConfig) || localAgents.size > 0, models: [], collab: collabConfig?.registered ? { ...collabConfig, plan: collabConfig.plan, configuredProviders: collabConfig.configuredProviders } : null }));
408
+ const creds = (0, auth_1.loadCredentials)();
409
+ const userInfo = creds?.userName
410
+ ? { name: creds.userName, email: creds.userEmail }
411
+ : (process.env.USER ? { name: process.env.USER } : null);
412
+ socket.send(JSON.stringify({ type: "config", apiKeyConfigured: lassoKeyConfigured, agentConfigured: lassoKeyConfigured || Boolean(agentConfig) || localAgents.size > 0, models: [], collab: collabConfig?.registered ? { ...collabConfig, plan: collabConfig.plan, configuredProviders: collabConfig.configuredProviders } : null, user: userInfo }));
373
413
  void availableModels(collabConfig).then((models) => {
374
414
  if (socket.readyState === socket.OPEN)
375
- socket.send(JSON.stringify({ type: "config", apiKeyConfigured: lassoKeyConfigured, agentConfigured: Boolean(agentConfig) || localAgents.size > 0, models, collab: collabConfig?.registered ? { ...collabConfig, plan: collabConfig.plan, configuredProviders: collabConfig.configuredProviders } : null }));
415
+ socket.send(JSON.stringify({ type: "config", apiKeyConfigured: lassoKeyConfigured, agentConfigured: lassoKeyConfigured || Boolean(agentConfig) || localAgents.size > 0, models, collab: collabConfig?.registered ? { ...collabConfig, plan: collabConfig.plan, configuredProviders: collabConfig.configuredProviders } : null, user: userInfo }));
376
416
  });
377
417
  void getGitState(cwd).then((git) => {
378
418
  if (socket.readyState === socket.OPEN)
@@ -514,9 +554,29 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
514
554
  taskControllers.get(msg.taskId)?.abort();
515
555
  const controller = new AbortController();
516
556
  taskControllers.set(msg.taskId, controller);
517
- const selectedConfig = localProvider
518
- ? { provider: cliProvider, model: msg.model }
519
- : { ...agentConfig, provider: (msg.provider || agentConfig.provider), model: msg.model };
557
+ let selectedConfig;
558
+ if (localProvider) {
559
+ selectedConfig = { provider: cliProvider, model: msg.model };
560
+ }
561
+ else {
562
+ const requestedProvider = (msg.provider || agentConfig.provider);
563
+ const providerLocalKey = localProviderKeys[requestedProvider];
564
+ const apiKey = providerLocalKey || resolvedLassoKey;
565
+ const baseUrl = requestedProvider === "nvidia"
566
+ ? "https://integrate.api.nvidia.com/v1"
567
+ : requestedProvider === "ollama"
568
+ ? (agentConfig.baseUrl || "http://localhost:11434/v1")
569
+ : agentConfig.baseUrl;
570
+ selectedConfig = {
571
+ ...agentConfig,
572
+ provider: requestedProvider,
573
+ model: msg.model,
574
+ apiKey,
575
+ baseUrl,
576
+ lassoKey: resolvedLassoKey,
577
+ serverUrl: collabConfig?.serverUrl || (0, auth_1.serverUrlFrom)(fileEnv),
578
+ };
579
+ }
520
580
  socket.send(JSON.stringify({ type: "agent_status", taskId: msg.taskId, status: "working", message: "Working…" }));
521
581
  void (0, agent_1.answerQuestion)(cwd, { taskId: msg.taskId, question: msg.question, context: msg.context, element: msg.element, messages: msg.messages }, selectedConfig, controller.signal, (message, detail, level) => {
522
582
  if (!controller.signal.aborted && socket.readyState === socket.OPEN)
@@ -546,9 +606,29 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
546
606
  socket.send(JSON.stringify({ type: "agent_status", taskId: msg.taskId, status: "error", message: "Add a supported agent key: GOOGLE_GENERATIVE_AI_API_KEY, OPENAI_API_KEY, or ANTHROPIC_API_KEY." }));
547
607
  return;
548
608
  }
549
- const selectedConfig = localProvider
550
- ? { provider: cliProvider, model: msg.model }
551
- : { ...agentConfig, provider: (msg.provider || agentConfig.provider), model: msg.model };
609
+ let selectedConfig;
610
+ if (localProvider) {
611
+ selectedConfig = { provider: cliProvider, model: msg.model };
612
+ }
613
+ else {
614
+ const requestedProvider = (msg.provider || agentConfig.provider);
615
+ const providerLocalKey = localProviderKeys[requestedProvider];
616
+ const apiKey = providerLocalKey || resolvedLassoKey;
617
+ const baseUrl = requestedProvider === "nvidia"
618
+ ? "https://integrate.api.nvidia.com/v1"
619
+ : requestedProvider === "ollama"
620
+ ? (agentConfig.baseUrl || "http://localhost:11434/v1")
621
+ : agentConfig.baseUrl;
622
+ selectedConfig = {
623
+ ...agentConfig,
624
+ provider: requestedProvider,
625
+ model: msg.model,
626
+ apiKey,
627
+ baseUrl,
628
+ lassoKey: resolvedLassoKey,
629
+ serverUrl: collabConfig?.serverUrl || (0, auth_1.serverUrlFrom)(fileEnv),
630
+ };
631
+ }
552
632
  editRequests.set(msg.taskId, msg);
553
633
  editConfigs.set(msg.taskId, selectedConfig);
554
634
  reviewRefreshAttempts.set(msg.taskId, 0);
@@ -633,10 +713,15 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
633
713
  }
634
714
  else if (msg.type === "transcribe") {
635
715
  const serverUrl = (0, auth_1.serverUrlFrom)(fileEnv);
716
+ const creds = (0, auth_1.loadCredentials)();
717
+ const apiKey = resolvedLassoKey || agentConfig?.apiKey || collabConfig?.apiKey || creds?.apiKey;
636
718
  try {
637
719
  const resp = await fetch(`${serverUrl}/api/v1/transcribe`, {
638
720
  method: "POST",
639
- headers: { "Content-Type": "application/json" },
721
+ headers: {
722
+ "Content-Type": "application/json",
723
+ ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
724
+ },
640
725
  body: JSON.stringify({
641
726
  audio: msg.audio,
642
727
  mimeType: msg.mimeType,
@@ -664,22 +749,38 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
664
749
  }
665
750
  else if (msg.type === "oneshot") {
666
751
  console.log("[lasso] one-shot request:", msg.prompt, msg.scope);
667
- if (!lassoKeyConfigured && !agentConfig) {
668
- socket.send(JSON.stringify({ type: "agent_status", status: "error", message: "Set your Lasso API key or configure an AI provider before using one-shot mode." }));
752
+ const taskId = msg.taskId || `oneshot-${Date.now()}`;
753
+ const localProvider = msg.provider === "cli" || msg.model?.startsWith("claude-code:") || msg.model?.startsWith("opencode:") || msg.model?.startsWith("cursor:") || msg.model?.startsWith("codex:");
754
+ const cliProvider = msg.model?.startsWith("claude-code:") ? "claude-code" : msg.model?.startsWith("opencode:") ? "opencode" : msg.model?.startsWith("cursor:") ? "cursor" : "codex";
755
+ const apiKey = resolvedLassoKey || agentConfig?.apiKey || collabConfig?.apiKey;
756
+ if (!localProvider && !lassoKeyConfigured && !agentConfig && !apiKey) {
757
+ socket.send(JSON.stringify({
758
+ type: "agent_status",
759
+ taskId,
760
+ status: "error",
761
+ message: "No AI provider or Lasso key configured. Sign in with 'lasso auth login' or configure a provider key in .env."
762
+ }));
669
763
  return;
670
764
  }
671
- const localProvider = msg.provider === "cli";
672
- const cliProvider = msg.model?.startsWith("claude-code:") ? "claude-code" : msg.model?.startsWith("opencode:") ? "opencode" : msg.model?.startsWith("cursor:") ? "cursor" : "codex";
765
+ const provider = (msg.provider || agentConfig?.provider || "google");
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
+ const localKey = localProviderKeys[provider];
673
768
  const selectedConfig = localProvider
674
769
  ? { provider: cliProvider, model: msg.model || "claude-code:sonnet" }
675
- : { ...agentConfig, provider: (msg.provider || agentConfig.provider), model: msg.model || agentConfig.model };
676
- const taskId = `oneshot-${Date.now()}`;
770
+ : {
771
+ ...(agentConfig || {}),
772
+ provider,
773
+ model,
774
+ apiKey: localKey || apiKey,
775
+ 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
+ };
677
777
  const controller = new AbortController();
678
778
  taskControllers.set(taskId, controller);
679
779
  socket.send(JSON.stringify({ type: "agent_status", taskId, status: "thinking", message: "Planning your request..." }));
680
- // Import the one-shot agent function
681
780
  const { runOneShotAgent } = await import("./oneshot.js");
682
- runOneShotAgent(cwd, msg.prompt, msg.scope, selectedConfig, controller.signal, (status, message, detail) => {
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
+ runOneShotAgent(cwd, msg.prompt, msg.scope || "project", selectedConfig, controller.signal, (status, message, detail) => {
683
784
  if (!controller.signal.aborted && socket.readyState === socket.OPEN) {
684
785
  socket.send(JSON.stringify({ type: "agent_status", taskId, status, message, detail }));
685
786
  }
@@ -687,10 +788,20 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
687
788
  if (!controller.signal.aborted && socket.readyState === socket.OPEN) {
688
789
  socket.send(JSON.stringify({ type: "agent_prompt", taskId, prompt }));
689
790
  }
690
- }).then((result) => {
791
+ }, msg.messages, targetApiUrl, apiKey, taskId, msg.pageContext).then((result) => {
691
792
  if (!controller.signal.aborted && socket.readyState === socket.OPEN) {
692
793
  if (result.ok) {
693
- socket.send(JSON.stringify({ type: "agent_status", taskId, status: "review", message: result.summary, changes: result.changes }));
794
+ const hasChanges = Array.isArray(result.changes) && result.changes.length > 0;
795
+ socket.send(JSON.stringify({
796
+ type: "agent_status",
797
+ taskId,
798
+ status: hasChanges ? "review" : "complete",
799
+ message: result.summary,
800
+ todo: result.todo,
801
+ changes: result.changes,
802
+ thinking: result.thinking,
803
+ totalThinkingTimeMs: result.totalThinkingTimeMs,
804
+ }));
694
805
  }
695
806
  else {
696
807
  socket.send(JSON.stringify({ type: "agent_status", taskId, status: "error", message: result.error || "One-shot agent failed" }));
@@ -710,25 +821,33 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
710
821
  const snapshots = [];
711
822
  const planned = new Map();
712
823
  for (const change of msg.changes) {
713
- const filePath = resolveProposedFile(cwd, change.filePath);
714
- const content = node_fs_1.default.readFileSync(filePath, "utf8");
715
- const prepared = prepareChange(content, change);
824
+ const isNew = !change.oldString;
825
+ const filePath = resolveProposedFile(cwd, change.filePath, true);
826
+ const exists = node_fs_1.default.existsSync(filePath);
827
+ const content = exists ? node_fs_1.default.readFileSync(filePath, "utf8") : "";
828
+ const prepared = (!exists || isNew)
829
+ ? { start: 0, end: content.length, oldString: content, newString: change.newString }
830
+ : prepareChange(content, change);
716
831
  const fileChanges = planned.get(filePath) || [];
717
832
  if (fileChanges.some((item) => prepared.start < item.end && item.start < prepared.end)) {
718
833
  throw new Error(`Could not safely apply ${change.filePath}. Proposed changes overlap.`);
719
834
  }
720
- fileChanges.push({ filePath, content, ...prepared });
835
+ fileChanges.push({ filePath, content, exists, ...prepared });
721
836
  planned.set(filePath, fileChanges);
722
837
  }
723
838
  // Validate every change before writing any file, then apply each
724
839
  // file's replacements from the end toward the beginning.
725
840
  taskSnapshots.set(msg.taskId, snapshots);
726
841
  for (const [filePath, changes] of planned) {
842
+ const exists = changes[0].exists;
727
843
  const content = changes[0].content;
728
- snapshots.push({ filePath, content });
729
- const nextContent = [...changes]
730
- .sort((a, b) => b.start - a.start)
731
- .reduce((value, change) => value.slice(0, change.start) + change.newString + value.slice(change.end), content);
844
+ snapshots.push({ filePath, content: exists ? content : null });
845
+ const nextContent = exists
846
+ ? [...changes]
847
+ .sort((a, b) => b.start - a.start)
848
+ .reduce((value, change) => value.slice(0, change.start) + change.newString + value.slice(change.end), content)
849
+ : changes[0].newString;
850
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(filePath), { recursive: true });
732
851
  node_fs_1.default.writeFileSync(filePath, nextContent);
733
852
  }
734
853
  socket.send(JSON.stringify({ type: "applied", taskId: msg.taskId, message: `${msg.changes.length} file${msg.changes.length === 1 ? "" : "s"} updated. Your dev server will reload.` }));
@@ -736,8 +855,15 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
736
855
  catch (error) {
737
856
  // Validation happens before writes, but restore this task's snapshot
738
857
  // if a filesystem error occurs during the write phase.
739
- for (const snapshot of taskSnapshots.get(msg.taskId) || [])
740
- node_fs_1.default.writeFileSync(snapshot.filePath, snapshot.content);
858
+ for (const snapshot of taskSnapshots.get(msg.taskId) || []) {
859
+ if (snapshot.content === null) {
860
+ if (node_fs_1.default.existsSync(snapshot.filePath))
861
+ node_fs_1.default.unlinkSync(snapshot.filePath);
862
+ }
863
+ else {
864
+ node_fs_1.default.writeFileSync(snapshot.filePath, snapshot.content);
865
+ }
866
+ }
741
867
  taskSnapshots.delete(msg.taskId);
742
868
  const message = error instanceof Error ? error.message : "The change could not be applied.";
743
869
  const sourceChanged = message.includes("The source changed after the suggestion was generated");
@@ -755,8 +881,15 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
755
881
  }
756
882
  else if (msg.type === "undo") {
757
883
  const snapshots = taskSnapshots.get(msg.taskId || "") || [];
758
- for (const snapshot of snapshots)
759
- node_fs_1.default.writeFileSync(snapshot.filePath, snapshot.content);
884
+ for (const snapshot of snapshots) {
885
+ if (snapshot.content === null) {
886
+ if (node_fs_1.default.existsSync(snapshot.filePath))
887
+ node_fs_1.default.unlinkSync(snapshot.filePath);
888
+ }
889
+ else {
890
+ node_fs_1.default.writeFileSync(snapshot.filePath, snapshot.content);
891
+ }
892
+ }
760
893
  taskSnapshots.delete(msg.taskId || "");
761
894
  socket.send(JSON.stringify({ type: "undone", taskId: msg.taskId, message: snapshots.length ? "The accepted change was reverted." : "There is no accepted change to undo." }));
762
895
  }
package/dist/cli/index.js CHANGED
@@ -264,6 +264,16 @@ program.command("register [domain]")
264
264
  console.error(chalk_1.default.red("✗") + ` "${domain}" is not a valid .lasso domain (lowercase letters, numbers, and hyphens).`);
265
265
  process.exit(1);
266
266
  }
267
+ // Register with Lasso workspace & Host
268
+ console.log(chalk_1.default.dim("Registering project with Lasso workspace…"));
269
+ const initResult = await (0, project_1.initProject)(cwd, env, domain || undefined);
270
+ if (initResult.ok) {
271
+ console.log(chalk_1.default.green("✓") + ` Registered project: ${chalk_1.default.cyan(initResult.projectId)}`);
272
+ console.log(chalk_1.default.green("✓") + ` Local domain: ${chalk_1.default.cyan(initResult.domain || domain)}`);
273
+ console.log(chalk_1.default.green("✓") + ` Points to ${chalk_1.default.dim(cwd)}`);
274
+ return;
275
+ }
276
+ // If server registration was not possible (e.g. offline/no key), fall back to local host registration
267
277
  if (!domain) {
268
278
  if (config?.domain) {
269
279
  domain = config.domain;
@@ -1,8 +1,21 @@
1
1
  import type { AgentConfig, SourceChange } from "./agent";
2
+ export interface OneShotThinkingStep {
3
+ title: string;
4
+ detail?: string;
5
+ durationMs?: number;
6
+ }
7
+ export interface OneShotTodoItem {
8
+ file: string;
9
+ action: "modify" | "create" | "delete";
10
+ reason: string;
11
+ }
2
12
  export interface OneShotResult {
3
13
  ok: boolean;
4
14
  summary?: string;
15
+ todo?: OneShotTodoItem[];
5
16
  changes?: SourceChange[];
17
+ thinking?: OneShotThinkingStep[];
18
+ totalThinkingTimeMs?: number;
6
19
  error?: string;
7
20
  }
8
21
  export interface ProgressCallback {
@@ -14,4 +27,9 @@ export interface PromptCallback {
14
27
  options?: string[];
15
28
  }): void;
16
29
  }
17
- export declare function runOneShotAgent(cwd: string, prompt: string, scope: "project" | "component", config: AgentConfig, signal: AbortSignal, onProgress: ProgressCallback, onPrompt: PromptCallback): Promise<OneShotResult>;
30
+ export interface ConversationMessage {
31
+ role: "user" | "assistant";
32
+ content: string;
33
+ }
34
+ export declare function isConversationalPrompt(prompt: string): boolean;
35
+ export declare function runOneShotAgent(cwd: string, prompt: string, scope: "project" | "component", config: AgentConfig, signal: AbortSignal, onProgress: ProgressCallback, onPrompt: PromptCallback, messages?: ConversationMessage[], serverUrl?: string, apiKey?: string, taskId?: string, pageContext?: any): Promise<OneShotResult>;