@lasso-ai/cli 1.2.1 → 1.2.2
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 +9 -0
- package/README.md +3 -2
- package/dist/cli/agent.d.ts +2 -2
- package/dist/cli/agent.js +53 -16
- package/dist/cli/bridge.js +73 -9
- package/dist/cli/project.d.ts +2 -0
- package/dist/cli/project.js +2 -0
- package/dist/overlay.js +460 -235
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,8 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **One-shot mode as sidebar panel**: Changed one-shot mode from a centered modal to a right-side sidebar panel that only appears during one-shot mode. When users exit one-shot mode, they return to the normal edit mode with just the toolbar. This provides a cleaner, more integrated experience similar to modern AI editors.
|
|
13
|
+
- **Mini toolbar positioning**: Adjusted the mini toolbar position to `left: calc(50% + 140px)` (reduced from 280px) to reduce overlap with the main toolbar. Added smooth transition and centered positioning when the main toolbar is dismissed. Mini toolbar is now hidden when one-shot sidebar is open to avoid UI clutter.
|
|
14
|
+
|
|
10
15
|
### Added
|
|
11
16
|
|
|
17
|
+
- **Multiple access points for one-shot mode**: Added a Command button to the toolbar (⌘ icon) for opening the one-shot sidebar, in addition to the existing ⌘K keyboard shortcut. The sidebar includes a model selector dropdown allowing users to choose their AI model before submitting a one-shot request.
|
|
18
|
+
- **Model selector in command sidebar**: The one-shot sidebar now displays the currently selected model with a dropdown to switch between available models. The selector is organized by provider (Anthropic, OpenAI, Google, Local Agents) for easy navigation.
|
|
19
|
+
- **Cursor CLI support**: Added Cursor as a local agent option alongside Claude Code, Codex, and OpenCode. Users can now select Cursor models in the model dropdown if Cursor is installed on their system. Cursor uses similar output parsing to Claude Code with progress tracking and error handling.
|
|
20
|
+
- **Model locking based on API key configuration**: Models are now locked with a lock icon in the dropdown when the user hasn't configured the provider's API key in their dashboard. Free plan users can only use models from providers with configured keys. Paid plan users have all models unlocked. CLI agents (Claude Code, Codex, OpenCode, Cursor) are always unlocked since users manage their own credentials.
|
|
12
21
|
- **One-shot mode with global command bar**: Press ⌘K to open a command interface
|
|
13
22
|
anywhere in your app. Describe what you want to build (e.g., "Build a complete billing
|
|
14
23
|
system with invoices and payment tracking") and Lasso will plan, execute, and
|
package/README.md
CHANGED
|
@@ -200,7 +200,7 @@ Use:
|
|
|
200
200
|
|
|
201
201
|
Your selection and project context stay inside the Lasso pipeline while the actual coding agent can be swapped independently.
|
|
202
202
|
|
|
203
|
-
When installed, Lasso automatically detects the `claude`, `codex`, and
|
|
203
|
+
When installed, Lasso automatically detects the `claude`, `codex`, `cursor`, and
|
|
204
204
|
`opencode` commands on your PATH and adds the available local agents to the
|
|
205
205
|
prompt model menu. Local agents run in read-only/planning mode and return a
|
|
206
206
|
reviewable proposal; Lasso remains the only process that writes accepted
|
|
@@ -444,9 +444,10 @@ sees**, so install it before starting the dev server (or restart afterwards):
|
|
|
444
444
|
| ------------------------------------------------------------ | ---------- | ----------------------------------------------------------------------------- |
|
|
445
445
|
| [Claude Code](https://lasso.byorello.space/docs/claude-code) | `claude` | `claude -p … --permission-mode plan --output-format stream-json --max-turns 3` |
|
|
446
446
|
| [Codex](https://lasso.byorello.space/docs/codex) | `codex` | `codex exec --json --sandbox read-only --skip-git-repo-check` |
|
|
447
|
+
| [Cursor](https://lasso.byorello.space/docs/cursor) | `cursor` | `cursor agent run --non-interactive --model "<model>"` |
|
|
447
448
|
| [OpenCode](https://lasso.byorello.space/docs/opencode) | `opencode` | `opencode run --format json --print-logs --agent plan` |
|
|
448
449
|
|
|
449
|
-
All
|
|
450
|
+
All four run read-only, so the agent proposes a patch and never writes to your project.
|
|
450
451
|
Sign each CLI in once in a real terminal; Lasso inherits the credentials it stores for
|
|
451
452
|
your user.
|
|
452
453
|
|
package/dist/cli/agent.d.ts
CHANGED
|
@@ -39,12 +39,12 @@ type AgentInput = {
|
|
|
39
39
|
};
|
|
40
40
|
};
|
|
41
41
|
export type AgentConfig = {
|
|
42
|
-
provider: "anthropic" | "openai" | "google" | "ollama" | "claude-code" | "codex" | "opencode";
|
|
42
|
+
provider: "anthropic" | "openai" | "google" | "ollama" | "claude-code" | "codex" | "opencode" | "cursor";
|
|
43
43
|
apiKey?: string;
|
|
44
44
|
model?: string;
|
|
45
45
|
baseUrl?: string;
|
|
46
46
|
};
|
|
47
|
-
export type LocalAgent = "claude-code" | "codex" | "opencode";
|
|
47
|
+
export type LocalAgent = "claude-code" | "codex" | "opencode" | "cursor";
|
|
48
48
|
export type AgentProgressLevel = "working" | "error";
|
|
49
49
|
export type AgentProgress = (message: string, detail?: string, level?: AgentProgressLevel) => void;
|
|
50
50
|
export type AgentPrompt = {
|
package/dist/cli/agent.js
CHANGED
|
@@ -23,7 +23,7 @@ function respondToAgentPrompt(taskId, value) {
|
|
|
23
23
|
}
|
|
24
24
|
async function detectLocalAgents() {
|
|
25
25
|
const found = new Set();
|
|
26
|
-
for (const [name, command] of [["claude-code", "claude"], ["codex", "codex"], ["opencode", "opencode"]]) {
|
|
26
|
+
for (const [name, command] of [["claude-code", "claude"], ["codex", "codex"], ["opencode", "opencode"], ["cursor", "cursor"]]) {
|
|
27
27
|
try {
|
|
28
28
|
await execFileAsync(process.platform === "win32" ? "where.exe" : "which", [command]);
|
|
29
29
|
found.add(name);
|
|
@@ -229,6 +229,33 @@ function extractLocalAgentText(raw, provider) {
|
|
|
229
229
|
}
|
|
230
230
|
return results.at(-1) || raw;
|
|
231
231
|
}
|
|
232
|
+
if (provider === "cursor") {
|
|
233
|
+
// Cursor uses similar output format to claude-code
|
|
234
|
+
try {
|
|
235
|
+
const payload = JSON.parse(raw);
|
|
236
|
+
if (payload.result)
|
|
237
|
+
return payload.result;
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
// Stream-json output is handled below.
|
|
241
|
+
}
|
|
242
|
+
const results = [];
|
|
243
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
244
|
+
try {
|
|
245
|
+
const event = JSON.parse(line);
|
|
246
|
+
if (typeof event.result === "string")
|
|
247
|
+
results.push(event.result);
|
|
248
|
+
const message = event.message;
|
|
249
|
+
for (const part of message?.content || [])
|
|
250
|
+
if (part.type === "text" && part.text)
|
|
251
|
+
results.push(part.text);
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
// Ignore progress lines that are not JSON.
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return results.at(-1) || raw;
|
|
258
|
+
}
|
|
232
259
|
const texts = [];
|
|
233
260
|
for (const line of raw.split(/\r?\n/)) {
|
|
234
261
|
try {
|
|
@@ -294,7 +321,7 @@ function extractLocalAgentProposal(raw, provider) {
|
|
|
294
321
|
const event = JSON.parse(line);
|
|
295
322
|
const item = event.item;
|
|
296
323
|
const part = event.part;
|
|
297
|
-
const values = provider === "claude-code"
|
|
324
|
+
const values = provider === "claude-code" || provider === "cursor"
|
|
298
325
|
? [event.result, ...(event.message?.content || []).map((entry) => entry.text)]
|
|
299
326
|
: provider === "opencode"
|
|
300
327
|
? [...collectTextFields(event), part?.type === "text" ? part.text : undefined]
|
|
@@ -335,7 +362,7 @@ function extractLocalAgentProposal(raw, provider) {
|
|
|
335
362
|
// Keep looking; this output may only be a progress or tool event.
|
|
336
363
|
}
|
|
337
364
|
}
|
|
338
|
-
throw new Error(`${provider === "opencode" ? "OpenCode" : "The agent"} did not return a reviewable proposal. It produced progress/tool events but no final JSON containing file changes.`);
|
|
365
|
+
throw new Error(`${provider === "opencode" ? "OpenCode" : provider === "cursor" ? "Cursor" : "The agent"} did not return a reviewable proposal. It produced progress/tool events but no final JSON containing file changes.`);
|
|
339
366
|
}
|
|
340
367
|
function snippet(value, max = 80) {
|
|
341
368
|
const s = String(value ?? "").trim().replace(/\s+/g, " ");
|
|
@@ -413,9 +440,10 @@ function progressFromLine(raw, provider) {
|
|
|
413
440
|
try {
|
|
414
441
|
const event = JSON.parse(raw);
|
|
415
442
|
const item = event.item;
|
|
416
|
-
if (provider === "claude-code") {
|
|
443
|
+
if (provider === "claude-code" || provider === "cursor") {
|
|
444
|
+
const agentName = provider === "cursor" ? "Cursor" : "Claude Code";
|
|
417
445
|
if (event.type === "system")
|
|
418
|
-
return progressEvent(
|
|
446
|
+
return progressEvent(`${agentName} connected`);
|
|
419
447
|
// tool_use blocks inside assistant messages
|
|
420
448
|
const toolBlock = event.message?.content?.find?.((p) => p.type === "tool_use");
|
|
421
449
|
if (toolBlock) {
|
|
@@ -423,13 +451,13 @@ function progressFromLine(raw, provider) {
|
|
|
423
451
|
const inp = toolBlock.input;
|
|
424
452
|
const detail = inp?.file_path ?? inp?.path ?? inp?.command ?? inp?.query ?? inp?.url ?? "";
|
|
425
453
|
return progressEvent(detail
|
|
426
|
-
?
|
|
427
|
-
:
|
|
454
|
+
? `${agentName} · ${toolName} ${snippet(detail)}`
|
|
455
|
+
: `${agentName} · ${toolName}`, detail);
|
|
428
456
|
}
|
|
429
457
|
// thinking blocks inside assistant messages
|
|
430
458
|
const thinkBlock = event.message?.content?.find?.((p) => p.type === "thinking");
|
|
431
459
|
if (thinkBlock?.thinking) {
|
|
432
|
-
return progressEvent(
|
|
460
|
+
return progressEvent(`${agentName} · ${snippet(thinkBlock.thinking)}`, thinkBlock.thinking);
|
|
433
461
|
}
|
|
434
462
|
// top-level tool event fields (stream-json verbose format)
|
|
435
463
|
const tool = event.tool_name || event.name || event.tool?.name;
|
|
@@ -437,14 +465,14 @@ function progressFromLine(raw, provider) {
|
|
|
437
465
|
const inp = event.tool_input;
|
|
438
466
|
const detail = inp?.file_path ?? inp?.path ?? inp?.command ?? inp?.query ?? inp?.url ?? "";
|
|
439
467
|
return progressEvent(detail
|
|
440
|
-
?
|
|
441
|
-
:
|
|
468
|
+
? `${agentName} · ${tool} ${snippet(detail)}`
|
|
469
|
+
: `${agentName} · ${tool}`, detail);
|
|
442
470
|
}
|
|
443
471
|
if (event.type === "result" || event.result) {
|
|
444
|
-
return progressEvent(
|
|
472
|
+
return progressEvent(`${agentName} · preparing the proposal`);
|
|
445
473
|
}
|
|
446
474
|
if (event.type === "assistant")
|
|
447
|
-
return progressEvent(
|
|
475
|
+
return progressEvent(`${agentName} · reasoning about the change`);
|
|
448
476
|
}
|
|
449
477
|
else if (provider === "codex") {
|
|
450
478
|
const type = item?.type || event.type || "";
|
|
@@ -514,10 +542,13 @@ function localAgentError(command, stderr, exitCode) {
|
|
|
514
542
|
const output = stderr.trim();
|
|
515
543
|
const hint = agentFailureHint(output);
|
|
516
544
|
if (hint)
|
|
517
|
-
return `${command === "opencode" ? "OpenCode" : command} could not finish: ${hint}`;
|
|
545
|
+
return `${command === "opencode" ? "OpenCode" : command === "cursor" ? "Cursor" : command} could not finish: ${hint}`;
|
|
518
546
|
if (command === "opencode" && /waiting for permission or authentication/i.test(output)) {
|
|
519
547
|
return "OpenCode needs permission or authentication. Run OpenCode once in a terminal, approve the requested access or sign in, then retry in Lasso.";
|
|
520
548
|
}
|
|
549
|
+
if (command === "cursor" && /waiting for permission or authentication/i.test(output)) {
|
|
550
|
+
return "Cursor needs permission or authentication. Run Cursor once in a terminal, approve the requested access or sign in, then retry in Lasso.";
|
|
551
|
+
}
|
|
521
552
|
if (command === "codex" && output.includes("missing field `base_instructions`")) {
|
|
522
553
|
return "Codex could not read its models cache because it uses an older cache format. Update Codex, then retry. Lasso already bypasses the repository trust check.";
|
|
523
554
|
}
|
|
@@ -537,6 +568,9 @@ function localCommand(provider, model, prompt) {
|
|
|
537
568
|
if (provider === "claude-code") {
|
|
538
569
|
return { command: "claude", args: ["-p", prompt || "", "--output-format", "stream-json", "--verbose", "--permission-mode", "plan", "--max-turns", "3", ...(selectedModel ? ["--model", selectedModel] : [])] };
|
|
539
570
|
}
|
|
571
|
+
if (provider === "cursor") {
|
|
572
|
+
return { command: "cursor", args: ["agent", "run", "--non-interactive", ...(selectedModel ? ["--model", selectedModel] : []), prompt || ""] };
|
|
573
|
+
}
|
|
540
574
|
if (provider === "opencode") {
|
|
541
575
|
return { command: "opencode", args: ["run", "--format", "json", "--print-logs", "--log-level", "INFO", "--agent", "plan", ...(selectedModel ? ["--model", selectedModel] : []), prompt || ""] };
|
|
542
576
|
}
|
|
@@ -545,7 +579,7 @@ function localCommand(provider, model, prompt) {
|
|
|
545
579
|
function runLocalCommand(cwd, command, args, provider, signal, onProgress, taskId, onPrompt) {
|
|
546
580
|
return new Promise((resolve, reject) => {
|
|
547
581
|
const child = (0, node_child_process_1.spawn)(command, args, { cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
548
|
-
const label = provider === "opencode" ? "OpenCode" : provider === "codex" ? "Codex" : "Claude Code";
|
|
582
|
+
const label = provider === "opencode" ? "OpenCode" : provider === "codex" ? "Codex" : provider === "cursor" ? "Cursor" : "Claude Code";
|
|
549
583
|
let stdout = "";
|
|
550
584
|
let stderr = "";
|
|
551
585
|
let pendingStdout = "";
|
|
@@ -653,6 +687,9 @@ function runLocalCommand(cwd, command, args, provider, signal, onProgress, taskI
|
|
|
653
687
|
if (provider === "opencode" && /quota exceeded|authentication failed|invalid api key|unauthorized|forbidden/i.test(stderrTail)) {
|
|
654
688
|
onProgress?.("OpenCode · provider rejected the request; stopping this task…", undefined, "error");
|
|
655
689
|
}
|
|
690
|
+
if (provider === "cursor" && /quota exceeded|authentication failed|invalid api key|unauthorized|forbidden/i.test(stderrTail)) {
|
|
691
|
+
onProgress?.("Cursor · provider rejected the request; stopping this task…", undefined, "error");
|
|
692
|
+
}
|
|
656
693
|
}
|
|
657
694
|
resolve({ stdout, stderr, exitCode: code ?? 1 });
|
|
658
695
|
});
|
|
@@ -678,7 +715,7 @@ async function proposeChanges(cwd, input, config, signal, onProgress, onPrompt)
|
|
|
678
715
|
? `\n\nDRAG REPOSITIONING TASK:\nThe user dragged this element by dx: ${input.context.drag.delta?.dx ?? 0}px, dy: ${input.context.drag.delta?.dy ?? 0}px to target coordinates (left: ${input.context.drag.targetRect?.left ?? 0}px, top: ${input.context.drag.targetRect?.top ?? 0}px). Modify the source code (CSS classes, Tailwind classes, flex/grid alignment, margin offsets, or positioning properties) so the element is accurately rendered at this target position.`
|
|
679
716
|
: "";
|
|
680
717
|
const instruction = `Selection context:\n${JSON.stringify(input.element, null, 2)}\n${JSON.stringify(visualContext, null, 2)}\n\nConversation history:\n${history}\n\nPrevious change history for this selection:\n${priorChanges}\n\nRelevant source context:\n${context || "No matching source context was found. Ask for a more specific selection rather than inventing a file."}${dragHint}\n\nCRITICAL: You MUST return ONLY JSON with actual file changes. Do NOT explain what you see or describe the code. Always propose concrete edits when given an instruction. Return JSON format: {"summary":"brief action taken","changes":[{"filePath":"relative/path","oldString":"exact existing text","newString":"replacement text"}]}`;
|
|
681
|
-
if (config.provider === "claude-code" || config.provider === "codex" || config.provider === "opencode") {
|
|
718
|
+
if (config.provider === "claude-code" || config.provider === "codex" || config.provider === "opencode" || config.provider === "cursor") {
|
|
682
719
|
return proposeWithLocalAgent(cwd, instruction, context, config, signal, onProgress, input.taskId, onPrompt);
|
|
683
720
|
}
|
|
684
721
|
const system = "You are Lasso, a source-code editing agent. Your ONLY job is to propose concrete file changes. NEVER explain, describe, or analyze code without proposing edits. ALWAYS return valid JSON with at least one change when the user requests a modification. Each oldString must occur exactly once in its file. Never rewrite whole files. Keep changes focused and minimal. If you cannot find the exact text to change, look harder at the provided context - do not give up and explain instead.";
|
|
@@ -734,7 +771,7 @@ async function answerQuestion(cwd, input, config, signal, onProgress, onPrompt)
|
|
|
734
771
|
const context = await contextFor(cwd, input.element);
|
|
735
772
|
const history = input.messages?.map((message) => `${message.role}: ${message.content}`).join("\n") || "None";
|
|
736
773
|
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."}`;
|
|
737
|
-
if (config.provider === "claude-code" || config.provider === "codex" || config.provider === "opencode") {
|
|
774
|
+
if (config.provider === "claude-code" || config.provider === "codex" || config.provider === "opencode" || config.provider === "cursor") {
|
|
738
775
|
const local = localCommand(config.provider, config.model, prompt);
|
|
739
776
|
const result = await runLocalCommand(cwd, local.command, local.args, config.provider, signal, onProgress, input.taskId, onPrompt);
|
|
740
777
|
if (result.exitCode !== 0)
|
package/dist/cli/bridge.js
CHANGED
|
@@ -263,15 +263,39 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
263
263
|
return [];
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
|
-
|
|
266
|
+
async function cursorModels() {
|
|
267
|
+
if (!localAgents.has("cursor"))
|
|
268
|
+
return [];
|
|
269
|
+
try {
|
|
270
|
+
const result = await execFileAsync("cursor", ["model", "list"], { maxBuffer: 1024 * 1024 });
|
|
271
|
+
return result.stdout
|
|
272
|
+
.split(/\r?\n/)
|
|
273
|
+
.map((line) => line.trim())
|
|
274
|
+
.filter((line) => line && !line.startsWith("Available models"))
|
|
275
|
+
.map((line) => {
|
|
276
|
+
const parts = line.split(/\s+/);
|
|
277
|
+
const id = parts[0]?.trim();
|
|
278
|
+
const name = parts.slice(1).join(" ").trim();
|
|
279
|
+
return id ? { id: `cursor:${id}`, label: `Cursor · ${name}`, provider: "cli" } : null;
|
|
280
|
+
})
|
|
281
|
+
.filter((model) => model !== null);
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
return [];
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const availableModels = async (collabConfig = null) => {
|
|
267
288
|
localAgents = await (0, agent_1.detectLocalAgents)();
|
|
268
289
|
const locals = await localModels();
|
|
269
290
|
const discoveredOpenCodeModels = await openCodeModels();
|
|
291
|
+
const discoveredCursorModels = await cursorModels();
|
|
270
292
|
if (locals.length && !agentConfig) {
|
|
271
293
|
const base = process.env.OLLAMA_BASE_URL || fileEnv.OLLAMA_BASE_URL || "http://localhost:11434/api";
|
|
272
294
|
agentConfig = { provider: "ollama", apiKey: "ollama", model: process.env.OLLAMA_MODEL || fileEnv.OLLAMA_MODEL, baseUrl: `${base.replace(/\/api\/?$/, "")}/v1` };
|
|
273
295
|
}
|
|
274
|
-
|
|
296
|
+
const isPaidPlan = collabConfig?.plan && collabConfig.plan !== "free";
|
|
297
|
+
const configuredProviders = collabConfig?.configuredProviders || [];
|
|
298
|
+
const allModels = [
|
|
275
299
|
{ id: "claude-sonnet-4-5-20250929", label: "Claude Sonnet 4.5", provider: "anthropic" },
|
|
276
300
|
{ id: "claude-opus-4-1-20250805", label: "Claude Opus 4.1", provider: "anthropic" },
|
|
277
301
|
{ id: "gpt-4.1", label: "GPT-4.1", provider: "openai" },
|
|
@@ -298,17 +322,57 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
298
322
|
{ id: "opencode:anthropic/claude-sonnet-4-5", label: "OpenCode · Claude Sonnet", provider: "cli" },
|
|
299
323
|
{ id: "opencode:openai/gpt-5", label: "OpenCode · GPT-5", provider: "cli" },
|
|
300
324
|
] : []),
|
|
325
|
+
...(localAgents.has("cursor") ? [
|
|
326
|
+
{ id: "cursor:claude-sonnet-4.5", label: "Cursor · Claude Sonnet 4.5", provider: "cli" },
|
|
327
|
+
{ id: "cursor:claude-opus-4", label: "Cursor · Claude Opus 4", provider: "cli" },
|
|
328
|
+
{ id: "cursor:claude-3.5-sonnet", label: "Cursor · Claude 3.5 Sonnet", provider: "cli" },
|
|
329
|
+
{ id: "cursor:gpt-4o", label: "Cursor · GPT-4o", provider: "cli" },
|
|
330
|
+
{ id: "cursor:gpt-4o-mini", label: "Cursor · GPT-4o mini", provider: "cli" },
|
|
331
|
+
] : []),
|
|
301
332
|
...discoveredOpenCodeModels,
|
|
333
|
+
...discoveredCursorModels,
|
|
302
334
|
...locals,
|
|
303
335
|
];
|
|
336
|
+
// Lock models based on provider API key configuration
|
|
337
|
+
// Paid plans have all models unlocked
|
|
338
|
+
// Free plans only have models unlocked for providers with configured API keys
|
|
339
|
+
const providerKeyMapping = {
|
|
340
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
341
|
+
openai: "OPENAI_API_KEY",
|
|
342
|
+
google: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
343
|
+
ollama: "OLLAMA_BASE_URL",
|
|
344
|
+
};
|
|
345
|
+
return allModels.map((model) => {
|
|
346
|
+
if (model.provider === "cli") {
|
|
347
|
+
// CLI agents are always unlocked (user manages their own credentials)
|
|
348
|
+
return model;
|
|
349
|
+
}
|
|
350
|
+
if (isPaidPlan) {
|
|
351
|
+
// Paid plans have all hosted models unlocked
|
|
352
|
+
return model;
|
|
353
|
+
}
|
|
354
|
+
// Free plans: check if provider is configured
|
|
355
|
+
const envKey = providerKeyMapping[model.provider];
|
|
356
|
+
const isConfigured = configuredProviders.includes(model.provider) ||
|
|
357
|
+
Boolean(process.env[envKey]) ||
|
|
358
|
+
Boolean(fileEnv[envKey]);
|
|
359
|
+
if (!isConfigured) {
|
|
360
|
+
return {
|
|
361
|
+
...model,
|
|
362
|
+
locked: true,
|
|
363
|
+
lockedReason: "Configure your API key in the dashboard to use this model",
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
return model;
|
|
367
|
+
});
|
|
304
368
|
};
|
|
305
369
|
wss.on("connection", (socket) => {
|
|
306
370
|
overlaySocket = socket;
|
|
307
371
|
console.log(chalk_1.default.green("✓") + " Overlay connected");
|
|
308
|
-
socket.send(JSON.stringify({ type: "config", apiKeyConfigured: lassoKeyConfigured, agentConfigured: Boolean(agentConfig) || localAgents.size > 0, models: [], collab: collabConfig?.registered ? collabConfig : null }));
|
|
309
|
-
void availableModels().then((models) => {
|
|
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 }));
|
|
373
|
+
void availableModels(collabConfig).then((models) => {
|
|
310
374
|
if (socket.readyState === socket.OPEN)
|
|
311
|
-
socket.send(JSON.stringify({ type: "config", apiKeyConfigured: lassoKeyConfigured, agentConfigured: Boolean(agentConfig) || localAgents.size > 0, models, collab: collabConfig?.registered ? collabConfig : null }));
|
|
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 }));
|
|
312
376
|
});
|
|
313
377
|
void getGitState(cwd).then((git) => {
|
|
314
378
|
if (socket.readyState === socket.OPEN)
|
|
@@ -438,7 +502,7 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
438
502
|
}
|
|
439
503
|
if (msg.type === "ask") {
|
|
440
504
|
const localProvider = msg.provider === "cli";
|
|
441
|
-
const cliProvider = msg.model
|
|
505
|
+
const cliProvider = msg.model?.startsWith("claude-code:") ? "claude-code" : msg.model?.startsWith("opencode:") ? "opencode" : msg.model?.startsWith("cursor:") ? "cursor" : "codex";
|
|
442
506
|
if (!lassoKeyConfigured && !localProvider) {
|
|
443
507
|
socket.send(JSON.stringify({ type: "agent_status", taskId: msg.taskId, status: "error", message: "Set your Lasso API key before asking the hosted agent a question." }));
|
|
444
508
|
return;
|
|
@@ -473,7 +537,7 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
473
537
|
}
|
|
474
538
|
else if (msg.type === "edit") {
|
|
475
539
|
const localProvider = msg.provider === "cli";
|
|
476
|
-
const cliProvider = msg.model
|
|
540
|
+
const cliProvider = msg.model?.startsWith("claude-code:") ? "claude-code" : msg.model?.startsWith("opencode:") ? "opencode" : msg.model?.startsWith("cursor:") ? "cursor" : "codex";
|
|
477
541
|
if (!lassoKeyConfigured && !localProvider) {
|
|
478
542
|
socket.send(JSON.stringify({ type: "agent_status", taskId: msg.taskId, status: "error", message: "Set VITE_LASSO_API_KEY or NEXT_LASSO_API_KEY in your app environment before sending an edit." }));
|
|
479
543
|
return;
|
|
@@ -509,7 +573,7 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
509
573
|
}
|
|
510
574
|
else if (msg.type === "git_generate_message") {
|
|
511
575
|
const localProvider = msg.provider === "cli";
|
|
512
|
-
const cliProvider = msg.model
|
|
576
|
+
const cliProvider = msg.model?.startsWith("claude-code:") ? "claude-code" : msg.model?.startsWith("opencode:") ? "opencode" : msg.model?.startsWith("cursor:") ? "cursor" : "codex";
|
|
513
577
|
if (!agentConfig && !localProvider) {
|
|
514
578
|
socket.send(JSON.stringify({ type: "git_commit_message", error: "No AI provider is configured." }));
|
|
515
579
|
return;
|
|
@@ -605,7 +669,7 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
605
669
|
return;
|
|
606
670
|
}
|
|
607
671
|
const localProvider = msg.provider === "cli";
|
|
608
|
-
const cliProvider = msg.model?.startsWith("claude-code:") ? "claude-code" : msg.model?.startsWith("opencode:") ? "opencode" : "codex";
|
|
672
|
+
const cliProvider = msg.model?.startsWith("claude-code:") ? "claude-code" : msg.model?.startsWith("opencode:") ? "opencode" : msg.model?.startsWith("cursor:") ? "cursor" : "codex";
|
|
609
673
|
const selectedConfig = localProvider
|
|
610
674
|
? { provider: cliProvider, model: msg.model || "claude-code:sonnet" }
|
|
611
675
|
: { ...agentConfig, provider: (msg.provider || agentConfig.provider), model: msg.model || agentConfig.model };
|
package/dist/cli/project.d.ts
CHANGED
package/dist/cli/project.js
CHANGED
|
@@ -292,6 +292,8 @@ async function resolveCollabSession(cwd, fileEnv, projectId) {
|
|
|
292
292
|
config.registered = true;
|
|
293
293
|
config.claimed = Boolean(body.session?.id);
|
|
294
294
|
config.registeredAt = new Date().toISOString();
|
|
295
|
+
config.plan = body.project?.plan || "free";
|
|
296
|
+
config.configuredProviders = body.project?.configuredProviders || [];
|
|
295
297
|
console.log(chalk_1.default.green("✓") +
|
|
296
298
|
" Project session opened " +
|
|
297
299
|
chalk_1.default.cyan(config.name || projectId) +
|
package/dist/overlay.js
CHANGED
|
@@ -8094,7 +8094,7 @@
|
|
|
8094
8094
|
|
|
8095
8095
|
.lasso-toolbar-mini {
|
|
8096
8096
|
position: fixed;
|
|
8097
|
-
left: calc(50% +
|
|
8097
|
+
left: calc(50% + 140px);
|
|
8098
8098
|
bottom: 24px;
|
|
8099
8099
|
display: inline-flex;
|
|
8100
8100
|
align-items: center;
|
|
@@ -8108,8 +8108,21 @@
|
|
|
8108
8108
|
-webkit-backdrop-filter: blur(20px);
|
|
8109
8109
|
pointer-events: auto;
|
|
8110
8110
|
z-index: 10;
|
|
8111
|
+
transition: left 200ms ease, opacity 200ms ease;
|
|
8111
8112
|
}
|
|
8113
|
+
|
|
8114
|
+
.lasso-toolbar.dismissed + .lasso-toolbar-mini {
|
|
8115
|
+
left: 50%;
|
|
8116
|
+
transform: translateX(-50%);
|
|
8117
|
+
}
|
|
8118
|
+
|
|
8112
8119
|
.lasso-toolbar-mini.dismissed { display: none; }
|
|
8120
|
+
|
|
8121
|
+
/* Hide mini toolbar when one-shot sidebar is open */
|
|
8122
|
+
.lasso-command-sidebar:not([hidden]) ~ .lasso-toolbar-mini {
|
|
8123
|
+
opacity: 0;
|
|
8124
|
+
pointer-events: none;
|
|
8125
|
+
}
|
|
8113
8126
|
.lasso-new-page-btn {
|
|
8114
8127
|
display: inline-flex;
|
|
8115
8128
|
align-items: center;
|
|
@@ -8343,6 +8356,16 @@
|
|
|
8343
8356
|
color: var(--lo-error);
|
|
8344
8357
|
}
|
|
8345
8358
|
|
|
8359
|
+
/* Command button */
|
|
8360
|
+
.lasso-tool-btn.command-tool {
|
|
8361
|
+
color: var(--lo-text-2);
|
|
8362
|
+
}
|
|
8363
|
+
|
|
8364
|
+
.lasso-tool-btn.command-tool:hover {
|
|
8365
|
+
color: var(--lo-primary);
|
|
8366
|
+
background: rgba(129, 201, 149, 0.16);
|
|
8367
|
+
}
|
|
8368
|
+
|
|
8346
8369
|
/* Badge on comment button */
|
|
8347
8370
|
.lasso-comments-badge {
|
|
8348
8371
|
display: none;
|
|
@@ -9841,6 +9864,15 @@
|
|
|
9841
9864
|
background: var(--lo-surface-hover);
|
|
9842
9865
|
}
|
|
9843
9866
|
|
|
9867
|
+
.lasso-prompt-model-item.locked {
|
|
9868
|
+
cursor: not-allowed;
|
|
9869
|
+
opacity: 0.6;
|
|
9870
|
+
}
|
|
9871
|
+
|
|
9872
|
+
.lasso-prompt-model-item.locked:hover {
|
|
9873
|
+
background: transparent;
|
|
9874
|
+
}
|
|
9875
|
+
|
|
9844
9876
|
.lasso-prompt-model-item .lasso-prompt-model-check {
|
|
9845
9877
|
flex-shrink: 0;
|
|
9846
9878
|
color: var(--lo-primary);
|
|
@@ -9851,6 +9883,11 @@
|
|
|
9851
9883
|
opacity: 1;
|
|
9852
9884
|
}
|
|
9853
9885
|
|
|
9886
|
+
.lasso-prompt-model-item .lasso-prompt-model-lock {
|
|
9887
|
+
flex-shrink: 0;
|
|
9888
|
+
color: var(--lo-text-3);
|
|
9889
|
+
}
|
|
9890
|
+
|
|
9854
9891
|
.lasso-prompt-element {
|
|
9855
9892
|
min-width: 0;
|
|
9856
9893
|
margin-left: auto;
|
|
@@ -11080,47 +11117,19 @@
|
|
|
11080
11117
|
.lasso-agent-prompt-actions button:active { transform: scale(0.97); }
|
|
11081
11118
|
|
|
11082
11119
|
/* ==========================================================
|
|
11083
|
-
COMMAND
|
|
11120
|
+
COMMAND SIDEBAR (One-Shot Build Mode)
|
|
11084
11121
|
========================================================== */
|
|
11085
11122
|
|
|
11086
|
-
.lasso-command-
|
|
11123
|
+
.lasso-command-sidebar {
|
|
11087
11124
|
position: fixed;
|
|
11088
|
-
|
|
11089
|
-
|
|
11090
|
-
|
|
11091
|
-
|
|
11092
|
-
-
|
|
11093
|
-
pointer-events: auto;
|
|
11094
|
-
}
|
|
11095
|
-
|
|
11096
|
-
.lasso-command-backdrop[hidden] {
|
|
11097
|
-
display: none;
|
|
11098
|
-
}
|
|
11099
|
-
|
|
11100
|
-
.lasso-command-bar {
|
|
11101
|
-
position: fixed;
|
|
11102
|
-
top: 0;
|
|
11103
|
-
left: 0;
|
|
11104
|
-
right: 0;
|
|
11105
|
-
bottom: 0;
|
|
11106
|
-
z-index: 101;
|
|
11107
|
-
display: flex;
|
|
11108
|
-
align-items: center;
|
|
11109
|
-
justify-content: center;
|
|
11110
|
-
padding: 24px;
|
|
11111
|
-
pointer-events: none;
|
|
11112
|
-
}
|
|
11113
|
-
|
|
11114
|
-
.lasso-command-bar[hidden] {
|
|
11115
|
-
display: none;
|
|
11116
|
-
}
|
|
11117
|
-
|
|
11118
|
-
.lasso-command-card {
|
|
11119
|
-
width: min(600px, calc(100vw - 48px));
|
|
11120
|
-
max-height: calc(100vh - 48px);
|
|
11125
|
+
top: 16px;
|
|
11126
|
+
right: 16px;
|
|
11127
|
+
bottom: 16px;
|
|
11128
|
+
width: 380px;
|
|
11129
|
+
max-width: calc(100vw - 32px);
|
|
11121
11130
|
display: flex;
|
|
11122
11131
|
flex-direction: column;
|
|
11123
|
-
padding:
|
|
11132
|
+
padding: 0;
|
|
11124
11133
|
border: 1px solid var(--lo-border);
|
|
11125
11134
|
border-radius: var(--lo-radius-xl);
|
|
11126
11135
|
background: rgba(24, 25, 29, 0.98);
|
|
@@ -11130,12 +11139,17 @@
|
|
|
11130
11139
|
box-shadow: 0 24px 56px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(255, 255, 255, 0.08);
|
|
11131
11140
|
pointer-events: auto;
|
|
11132
11141
|
animation: lasso-command-in 200ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
11142
|
+
z-index: 100;
|
|
11143
|
+
}
|
|
11144
|
+
|
|
11145
|
+
.lasso-command-sidebar[hidden] {
|
|
11146
|
+
display: none;
|
|
11133
11147
|
}
|
|
11134
11148
|
|
|
11135
11149
|
@keyframes lasso-command-in {
|
|
11136
11150
|
from {
|
|
11137
11151
|
opacity: 0;
|
|
11138
|
-
transform:
|
|
11152
|
+
transform: translateX(12px);
|
|
11139
11153
|
}
|
|
11140
11154
|
to {
|
|
11141
11155
|
opacity: 1;
|
|
@@ -11143,11 +11157,12 @@
|
|
|
11143
11157
|
}
|
|
11144
11158
|
}
|
|
11145
11159
|
|
|
11146
|
-
.lasso-command-header {
|
|
11160
|
+
.lasso-command-sidebar-header {
|
|
11147
11161
|
display: flex;
|
|
11148
11162
|
align-items: center;
|
|
11149
11163
|
justify-content: space-between;
|
|
11150
|
-
|
|
11164
|
+
padding: 16px 20px;
|
|
11165
|
+
border-bottom: 1px solid var(--lo-border);
|
|
11151
11166
|
}
|
|
11152
11167
|
|
|
11153
11168
|
.lasso-command-brand {
|
|
@@ -11157,12 +11172,12 @@
|
|
|
11157
11172
|
}
|
|
11158
11173
|
|
|
11159
11174
|
.lasso-command-brand-logo {
|
|
11160
|
-
width:
|
|
11161
|
-
height:
|
|
11175
|
+
width: 24px;
|
|
11176
|
+
height: 24px;
|
|
11162
11177
|
}
|
|
11163
11178
|
|
|
11164
11179
|
.lasso-command-brand-title {
|
|
11165
|
-
font-size:
|
|
11180
|
+
font-size: 14px;
|
|
11166
11181
|
font-weight: 600;
|
|
11167
11182
|
letter-spacing: -0.01em;
|
|
11168
11183
|
}
|
|
@@ -11186,10 +11201,124 @@
|
|
|
11186
11201
|
color: var(--lo-text);
|
|
11187
11202
|
}
|
|
11188
11203
|
|
|
11189
|
-
.lasso-command-body {
|
|
11204
|
+
.lasso-command-sidebar-body {
|
|
11205
|
+
display: flex;
|
|
11206
|
+
flex-direction: column;
|
|
11207
|
+
gap: 16px;
|
|
11208
|
+
padding: 20px;
|
|
11209
|
+
flex: 1;
|
|
11210
|
+
overflow-y: auto;
|
|
11211
|
+
}
|
|
11212
|
+
|
|
11213
|
+
.lasso-command-model-select {
|
|
11214
|
+
position: relative;
|
|
11215
|
+
}
|
|
11216
|
+
|
|
11217
|
+
.lasso-command-model-btn {
|
|
11218
|
+
width: 100%;
|
|
11219
|
+
display: flex;
|
|
11220
|
+
align-items: center;
|
|
11221
|
+
gap: 8px;
|
|
11222
|
+
padding: 10px 12px;
|
|
11223
|
+
border: 1px solid var(--lo-border);
|
|
11224
|
+
border-radius: var(--lo-radius-md);
|
|
11225
|
+
background: var(--lo-surface-2);
|
|
11226
|
+
color: var(--lo-text);
|
|
11227
|
+
font-family: inherit;
|
|
11228
|
+
font-size: 12px;
|
|
11229
|
+
font-weight: 500;
|
|
11230
|
+
cursor: pointer;
|
|
11231
|
+
transition: border-color 140ms ease, background 140ms ease;
|
|
11232
|
+
}
|
|
11233
|
+
|
|
11234
|
+
.lasso-command-model-btn:hover {
|
|
11235
|
+
border-color: var(--lo-primary);
|
|
11236
|
+
background: var(--lo-surface-hover);
|
|
11237
|
+
}
|
|
11238
|
+
|
|
11239
|
+
.lasso-command-model-icon {
|
|
11240
|
+
flex: 0 0 auto;
|
|
11241
|
+
}
|
|
11242
|
+
|
|
11243
|
+
.lasso-command-model-label {
|
|
11244
|
+
flex: 1;
|
|
11245
|
+
min-width: 0;
|
|
11246
|
+
overflow: hidden;
|
|
11247
|
+
text-overflow: ellipsis;
|
|
11248
|
+
white-space: nowrap;
|
|
11249
|
+
}
|
|
11250
|
+
|
|
11251
|
+
.lasso-command-model-chevron {
|
|
11252
|
+
flex: 0 0 auto;
|
|
11253
|
+
transition: transform 200ms ease;
|
|
11254
|
+
}
|
|
11255
|
+
|
|
11256
|
+
.lasso-command-model-menu {
|
|
11257
|
+
position: absolute;
|
|
11258
|
+
top: calc(100% + 4px);
|
|
11259
|
+
left: 0;
|
|
11260
|
+
right: 0;
|
|
11261
|
+
max-height: 320px;
|
|
11262
|
+
overflow-y: auto;
|
|
11263
|
+
padding: 8px;
|
|
11264
|
+
border: 1px solid var(--lo-border);
|
|
11265
|
+
border-radius: var(--lo-radius-lg);
|
|
11266
|
+
background: rgba(24, 25, 29, 0.98);
|
|
11267
|
+
backdrop-filter: blur(24px);
|
|
11268
|
+
-webkit-backdrop-filter: blur(24px);
|
|
11269
|
+
color: var(--lo-text);
|
|
11270
|
+
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
|
|
11271
|
+
z-index: 10;
|
|
11272
|
+
}
|
|
11273
|
+
|
|
11274
|
+
.lasso-command-model-section {
|
|
11275
|
+
margin-bottom: 8px;
|
|
11276
|
+
}
|
|
11277
|
+
|
|
11278
|
+
.lasso-command-model-section:last-child {
|
|
11279
|
+
margin-bottom: 0;
|
|
11280
|
+
}
|
|
11281
|
+
|
|
11282
|
+
.lasso-command-model-section-header {
|
|
11283
|
+
padding: 4px 8px;
|
|
11284
|
+
font-size: 10px;
|
|
11285
|
+
font-weight: 600;
|
|
11286
|
+
text-transform: uppercase;
|
|
11287
|
+
letter-spacing: 0.05em;
|
|
11288
|
+
color: var(--lo-text-3);
|
|
11289
|
+
}
|
|
11290
|
+
|
|
11291
|
+
.lasso-command-model-item {
|
|
11292
|
+
width: 100%;
|
|
11293
|
+
display: flex;
|
|
11294
|
+
align-items: center;
|
|
11295
|
+
gap: 8px;
|
|
11296
|
+
padding: 6px 8px;
|
|
11297
|
+
border: 0;
|
|
11298
|
+
border-radius: var(--lo-radius-md);
|
|
11299
|
+
background: transparent;
|
|
11300
|
+
color: var(--lo-text);
|
|
11301
|
+
font-family: inherit;
|
|
11302
|
+
font-size: 12px;
|
|
11303
|
+
font-weight: 450;
|
|
11304
|
+
text-align: left;
|
|
11305
|
+
cursor: pointer;
|
|
11306
|
+
transition: background 120ms ease;
|
|
11307
|
+
}
|
|
11308
|
+
|
|
11309
|
+
.lasso-command-model-item:hover {
|
|
11310
|
+
background: var(--lo-surface-hover);
|
|
11311
|
+
}
|
|
11312
|
+
|
|
11313
|
+
.lasso-command-model-item.selected {
|
|
11314
|
+
background: rgba(129, 201, 149, 0.16);
|
|
11315
|
+
color: var(--lo-primary);
|
|
11316
|
+
}
|
|
11317
|
+
|
|
11318
|
+
.lasso-command-input-wrapper {
|
|
11319
|
+
flex: 1;
|
|
11190
11320
|
display: flex;
|
|
11191
11321
|
flex-direction: column;
|
|
11192
|
-
gap: 12px;
|
|
11193
11322
|
}
|
|
11194
11323
|
|
|
11195
11324
|
.lasso-command-input {
|
|
@@ -17378,8 +17507,8 @@
|
|
|
17378
17507
|
const pin = document.createElement("button");
|
|
17379
17508
|
pin.type = "button";
|
|
17380
17509
|
const isResolved = thread.root.status === "RESOLVED";
|
|
17381
|
-
const
|
|
17382
|
-
pin.className = `lasso-comment-pin${isResolved ? " resolved" : ""}${
|
|
17510
|
+
const isOpen = state.openThreadUid === thread.root.uid;
|
|
17511
|
+
pin.className = `lasso-comment-pin${isResolved ? " resolved" : ""}${isOpen ? " active" : ""}`;
|
|
17383
17512
|
pin.dataset.commentUid = thread.root.uid;
|
|
17384
17513
|
const authorColor = userNameColor(thread.root.author?.id || "");
|
|
17385
17514
|
const authorInitials = initials(thread.root.author?.name || "");
|
|
@@ -19019,8 +19148,17 @@
|
|
|
19019
19148
|
}
|
|
19020
19149
|
if (message.type === "config" && message.models?.length) {
|
|
19021
19150
|
state.MODELS = message.models;
|
|
19022
|
-
|
|
19023
|
-
|
|
19151
|
+
const currentLocked = state.selectedModel?.locked;
|
|
19152
|
+
if (currentLocked) {
|
|
19153
|
+
const firstUnlocked = state.MODELS.find((m) => !m.locked);
|
|
19154
|
+
if (firstUnlocked) {
|
|
19155
|
+
state.selectedModel = firstUnlocked;
|
|
19156
|
+
rememberModel(state.selectedModel);
|
|
19157
|
+
}
|
|
19158
|
+
} else {
|
|
19159
|
+
state.selectedModel = state.MODELS.find((m) => m.id === storedModelId()) || state.MODELS.find((m) => !m.locked) || state.MODELS[0];
|
|
19160
|
+
rememberModel(state.selectedModel);
|
|
19161
|
+
}
|
|
19024
19162
|
refreshModelMenu();
|
|
19025
19163
|
}
|
|
19026
19164
|
if (message.type === "git_state" && message.git) {
|
|
@@ -19649,7 +19787,7 @@
|
|
|
19649
19787
|
return;
|
|
19650
19788
|
}
|
|
19651
19789
|
const item = event.target.closest(".lasso-prompt-model-item");
|
|
19652
|
-
if (!item) return;
|
|
19790
|
+
if (!item || item.classList.contains("locked")) return;
|
|
19653
19791
|
const found = state.MODELS.find((m) => m.id === item.dataset.model);
|
|
19654
19792
|
if (!found) return;
|
|
19655
19793
|
state.selectedModel = found;
|
|
@@ -19889,6 +20027,7 @@
|
|
|
19889
20027
|
if (key === "claude-code") return "Claude Code";
|
|
19890
20028
|
if (key === "codex") return "Codex";
|
|
19891
20029
|
if (key === "opencode") return "OpenCode";
|
|
20030
|
+
if (key === "cursor") return "Cursor";
|
|
19892
20031
|
return key;
|
|
19893
20032
|
}
|
|
19894
20033
|
function modelItem(model) {
|
|
@@ -19896,7 +20035,15 @@
|
|
|
19896
20035
|
item.type = "button";
|
|
19897
20036
|
item.className = "lasso-prompt-model-item";
|
|
19898
20037
|
item.dataset.model = model.id;
|
|
19899
|
-
|
|
20038
|
+
if (model.locked) {
|
|
20039
|
+
item.classList.add("locked");
|
|
20040
|
+
item.disabled = true;
|
|
20041
|
+
item.setAttribute("aria-disabled", "true");
|
|
20042
|
+
item.setAttribute("title", model.lockedReason || "This model is locked");
|
|
20043
|
+
item.innerHTML = `${providerIcon(model.provider)}<span>${model.label}</span><svg class="lasso-prompt-model-lock" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>`;
|
|
20044
|
+
} else {
|
|
20045
|
+
item.innerHTML = `${providerIcon(model.provider)}<span>${model.label}</span><svg class="lasso-prompt-model-check" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>`;
|
|
20046
|
+
}
|
|
19900
20047
|
return item;
|
|
19901
20048
|
}
|
|
19902
20049
|
function syncModelMenu2() {
|
|
@@ -21640,6 +21787,254 @@
|
|
|
21640
21787
|
return panelEl ? !panelEl.hidden : false;
|
|
21641
21788
|
}
|
|
21642
21789
|
|
|
21790
|
+
// src/overlay/commandbar/commandbar.ts
|
|
21791
|
+
var commandBarEl = null;
|
|
21792
|
+
var commandInput = null;
|
|
21793
|
+
var commandSubmit = null;
|
|
21794
|
+
var commandVoice = null;
|
|
21795
|
+
var commandClose = null;
|
|
21796
|
+
var commandModelBtn = null;
|
|
21797
|
+
var commandModelMenu = null;
|
|
21798
|
+
var commandModelIcon = null;
|
|
21799
|
+
var commandModelLabel = null;
|
|
21800
|
+
var isOneShotMode = false;
|
|
21801
|
+
function buildCommandBar() {
|
|
21802
|
+
const dom = getDOM();
|
|
21803
|
+
const el = document.createElement("div");
|
|
21804
|
+
el.className = "lasso-command-sidebar";
|
|
21805
|
+
el.hidden = true;
|
|
21806
|
+
el.innerHTML = `
|
|
21807
|
+
<div class="lasso-command-sidebar-header">
|
|
21808
|
+
<div class="lasso-command-brand">
|
|
21809
|
+
<img class="lasso-command-brand-logo" src="${LASSO_ICON_DATA_URL}" alt="Lasso" />
|
|
21810
|
+
<span class="lasso-command-brand-title">Build with AI</span>
|
|
21811
|
+
</div>
|
|
21812
|
+
<button class="lasso-command-close" type="button" aria-label="Close">
|
|
21813
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
21814
|
+
<path d="M18 6L6 18M6 6l12 12"/>
|
|
21815
|
+
</svg>
|
|
21816
|
+
</button>
|
|
21817
|
+
</div>
|
|
21818
|
+
|
|
21819
|
+
<div class="lasso-command-sidebar-body">
|
|
21820
|
+
<div class="lasso-command-model-select">
|
|
21821
|
+
<button class="lasso-command-model-btn" type="button" aria-label="Select model">
|
|
21822
|
+
<span class="lasso-command-model-icon"></span>
|
|
21823
|
+
<span class="lasso-command-model-label"></span>
|
|
21824
|
+
<svg class="lasso-command-model-chevron" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
21825
|
+
<path d="M6 9l6 6 6-6"/>
|
|
21826
|
+
</svg>
|
|
21827
|
+
</button>
|
|
21828
|
+
<div class="lasso-command-model-menu" hidden></div>
|
|
21829
|
+
</div>
|
|
21830
|
+
|
|
21831
|
+
<div class="lasso-command-input-wrapper">
|
|
21832
|
+
<textarea
|
|
21833
|
+
class="lasso-command-input"
|
|
21834
|
+
placeholder="Describe what you want to build... Examples: \u2022 Build a settings page with profile, notifications and security sections \u2022 Add authentication with login and signup \u2022 Create a dashboard with customer table and analytics"
|
|
21835
|
+
rows="6"
|
|
21836
|
+
></textarea>
|
|
21837
|
+
</div>
|
|
21838
|
+
|
|
21839
|
+
<div class="lasso-command-actions">
|
|
21840
|
+
<button class="lasso-command-voice" type="button" aria-label="Voice input" title="Voice input (speech-to-text)">
|
|
21841
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
21842
|
+
<rect x="9" y="3" width="6" height="11" rx="3"/>
|
|
21843
|
+
<path d="M5 11a7 7 0 0 0 14 0M12 18v3M8 21h8"/>
|
|
21844
|
+
</svg>
|
|
21845
|
+
</button>
|
|
21846
|
+
|
|
21847
|
+
<button class="lasso-command-submit primary" type="button" aria-label="Build">
|
|
21848
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
21849
|
+
<path d="M5 12h14M12 5l7 7-7 7"/>
|
|
21850
|
+
</svg>
|
|
21851
|
+
<span>Build</span>
|
|
21852
|
+
</button>
|
|
21853
|
+
</div>
|
|
21854
|
+
|
|
21855
|
+
<div class="lasso-command-shortcuts">
|
|
21856
|
+
<span class="lasso-command-shortcut">
|
|
21857
|
+
<kbd>\u2318</kbd><kbd>K</kbd>
|
|
21858
|
+
<span>Toggle one-shot mode</span>
|
|
21859
|
+
</span>
|
|
21860
|
+
<span class="lasso-command-shortcut">
|
|
21861
|
+
<kbd>\u2318</kbd><kbd>\u21B5</kbd>
|
|
21862
|
+
<span>Submit</span>
|
|
21863
|
+
</span>
|
|
21864
|
+
</div>
|
|
21865
|
+
</div>
|
|
21866
|
+
`;
|
|
21867
|
+
dom.shadow.appendChild(el);
|
|
21868
|
+
commandBarEl = el;
|
|
21869
|
+
commandInput = el.querySelector(".lasso-command-input");
|
|
21870
|
+
commandSubmit = el.querySelector(".lasso-command-submit");
|
|
21871
|
+
commandVoice = el.querySelector(".lasso-command-voice");
|
|
21872
|
+
commandClose = el.querySelector(".lasso-command-close");
|
|
21873
|
+
commandModelBtn = el.querySelector(".lasso-command-model-btn");
|
|
21874
|
+
commandModelMenu = el.querySelector(".lasso-command-model-menu");
|
|
21875
|
+
commandModelIcon = el.querySelector(".lasso-command-model-icon");
|
|
21876
|
+
commandModelLabel = el.querySelector(".lasso-command-model-label");
|
|
21877
|
+
commandModelBtn.addEventListener("click", (event) => {
|
|
21878
|
+
event.preventDefault();
|
|
21879
|
+
event.stopPropagation();
|
|
21880
|
+
if (commandModelMenu) commandModelMenu.hidden = !commandModelMenu.hidden;
|
|
21881
|
+
});
|
|
21882
|
+
commandModelMenu.addEventListener("click", (event) => {
|
|
21883
|
+
event.stopPropagation();
|
|
21884
|
+
const item = event.target.closest(".lasso-command-model-item");
|
|
21885
|
+
if (!item) return;
|
|
21886
|
+
const found = state.MODELS.find((m) => m.id === item.dataset.model);
|
|
21887
|
+
if (!found) return;
|
|
21888
|
+
state.selectedModel = found;
|
|
21889
|
+
if (commandModelIcon && commandModelLabel) {
|
|
21890
|
+
commandModelIcon.innerHTML = providerIcon(found.provider, true);
|
|
21891
|
+
commandModelLabel.textContent = found.label;
|
|
21892
|
+
}
|
|
21893
|
+
if (commandModelMenu) commandModelMenu.hidden = true;
|
|
21894
|
+
});
|
|
21895
|
+
document.addEventListener("click", (event) => {
|
|
21896
|
+
if (!commandModelMenu || commandModelMenu.hidden) return;
|
|
21897
|
+
const path = event.composedPath ? event.composedPath() : [];
|
|
21898
|
+
if (path.includes(commandModelBtn) || path.includes(commandModelMenu)) return;
|
|
21899
|
+
const target = event.target;
|
|
21900
|
+
if (commandModelBtn?.contains(target) || commandModelMenu?.contains(target)) return;
|
|
21901
|
+
commandModelMenu.hidden = true;
|
|
21902
|
+
});
|
|
21903
|
+
commandClose.addEventListener("click", (event) => {
|
|
21904
|
+
event.preventDefault();
|
|
21905
|
+
event.stopPropagation();
|
|
21906
|
+
closeCommandBar();
|
|
21907
|
+
});
|
|
21908
|
+
commandVoice.addEventListener("click", async (event) => {
|
|
21909
|
+
event.preventDefault();
|
|
21910
|
+
event.stopPropagation();
|
|
21911
|
+
if (isRecordingVoice()) {
|
|
21912
|
+
commandVoice.classList.remove("recording");
|
|
21913
|
+
commandVoice.classList.add("transcribing");
|
|
21914
|
+
commandVoice.title = "Transcribing\u2026";
|
|
21915
|
+
try {
|
|
21916
|
+
const result = await stopVoiceRecording();
|
|
21917
|
+
if (result.text && commandInput) {
|
|
21918
|
+
const prev = commandInput.value.trim();
|
|
21919
|
+
commandInput.value = prev ? `${prev} ${result.text}` : result.text;
|
|
21920
|
+
commandInput.focus();
|
|
21921
|
+
autoResizeTextarea(commandInput);
|
|
21922
|
+
showActivity("Transcribed", "#81c995");
|
|
21923
|
+
} else {
|
|
21924
|
+
showActivity("No speech detected.", "#fdd663");
|
|
21925
|
+
}
|
|
21926
|
+
} catch (err) {
|
|
21927
|
+
showActivity(err?.message || "Transcription failed", "#f28b82");
|
|
21928
|
+
} finally {
|
|
21929
|
+
commandVoice.classList.remove("transcribing");
|
|
21930
|
+
commandVoice.title = "Voice input";
|
|
21931
|
+
}
|
|
21932
|
+
} else {
|
|
21933
|
+
try {
|
|
21934
|
+
await startVoiceRecording();
|
|
21935
|
+
commandVoice.classList.add("recording");
|
|
21936
|
+
commandVoice.title = "Recording\u2026 Click again to stop";
|
|
21937
|
+
showActivity("Listening\u2026 speak now", "#ea4335");
|
|
21938
|
+
} catch (err) {
|
|
21939
|
+
showActivity(err?.message || "Microphone access denied", "#f28b82");
|
|
21940
|
+
}
|
|
21941
|
+
}
|
|
21942
|
+
});
|
|
21943
|
+
commandInput.addEventListener("keydown", (event) => {
|
|
21944
|
+
if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
|
|
21945
|
+
event.preventDefault();
|
|
21946
|
+
handleSubmit();
|
|
21947
|
+
}
|
|
21948
|
+
});
|
|
21949
|
+
commandSubmit.addEventListener("click", (event) => {
|
|
21950
|
+
event.preventDefault();
|
|
21951
|
+
event.stopPropagation();
|
|
21952
|
+
handleSubmit();
|
|
21953
|
+
});
|
|
21954
|
+
commandInput.addEventListener("input", () => {
|
|
21955
|
+
if (commandInput) autoResizeTextarea(commandInput);
|
|
21956
|
+
});
|
|
21957
|
+
commandInput.addEventListener("keydown", (event) => {
|
|
21958
|
+
if (event.key === "Escape") {
|
|
21959
|
+
event.preventDefault();
|
|
21960
|
+
closeCommandBar();
|
|
21961
|
+
}
|
|
21962
|
+
});
|
|
21963
|
+
}
|
|
21964
|
+
function autoResizeTextarea(textarea) {
|
|
21965
|
+
textarea.style.height = "auto";
|
|
21966
|
+
textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px`;
|
|
21967
|
+
}
|
|
21968
|
+
function openCommandBar() {
|
|
21969
|
+
if (!commandBarEl) return;
|
|
21970
|
+
commandBarEl.hidden = false;
|
|
21971
|
+
isOneShotMode = true;
|
|
21972
|
+
if (commandInput) {
|
|
21973
|
+
commandInput.value = "";
|
|
21974
|
+
commandInput.focus();
|
|
21975
|
+
autoResizeTextarea(commandInput);
|
|
21976
|
+
}
|
|
21977
|
+
if (commandModelIcon && commandModelLabel && state.selectedModel) {
|
|
21978
|
+
commandModelIcon.innerHTML = providerIcon(state.selectedModel.provider, true);
|
|
21979
|
+
commandModelLabel.textContent = state.selectedModel.label;
|
|
21980
|
+
}
|
|
21981
|
+
populateModelMenu();
|
|
21982
|
+
}
|
|
21983
|
+
function closeCommandBar() {
|
|
21984
|
+
if (!commandBarEl) return;
|
|
21985
|
+
commandBarEl.hidden = true;
|
|
21986
|
+
isOneShotMode = false;
|
|
21987
|
+
if (commandInput) {
|
|
21988
|
+
commandInput.value = "";
|
|
21989
|
+
}
|
|
21990
|
+
}
|
|
21991
|
+
function isCommandBarOpen() {
|
|
21992
|
+
return isOneShotMode;
|
|
21993
|
+
}
|
|
21994
|
+
function populateModelMenu() {
|
|
21995
|
+
if (!commandModelMenu) return;
|
|
21996
|
+
commandModelMenu.innerHTML = "";
|
|
21997
|
+
const providers = ["all", ...Array.from(new Set(state.MODELS.map((m) => m.provider)))];
|
|
21998
|
+
for (const provider of providers) {
|
|
21999
|
+
const section = document.createElement("div");
|
|
22000
|
+
section.className = "lasso-command-model-section";
|
|
22001
|
+
if (provider !== "all") {
|
|
22002
|
+
const header = document.createElement("div");
|
|
22003
|
+
header.className = "lasso-command-model-section-header";
|
|
22004
|
+
header.textContent = provider === "anthropic" ? "Anthropic" : provider === "openai" ? "OpenAI" : provider === "google" ? "Google" : provider === "ollama" ? "Ollama" : provider === "cli" ? "Local Agents" : provider;
|
|
22005
|
+
section.appendChild(header);
|
|
22006
|
+
}
|
|
22007
|
+
const models = provider === "all" ? state.MODELS : state.MODELS.filter((m) => m.provider === provider);
|
|
22008
|
+
for (const model of models) {
|
|
22009
|
+
const item = document.createElement("button");
|
|
22010
|
+
item.type = "button";
|
|
22011
|
+
item.className = "lasso-command-model-item";
|
|
22012
|
+
item.dataset.model = model.id;
|
|
22013
|
+
item.innerHTML = `${providerIcon(model.provider)}<span>${model.label}</span>`;
|
|
22014
|
+
if (model.id === state.selectedModel.id) {
|
|
22015
|
+
item.classList.add("selected");
|
|
22016
|
+
}
|
|
22017
|
+
section.appendChild(item);
|
|
22018
|
+
}
|
|
22019
|
+
commandModelMenu.appendChild(section);
|
|
22020
|
+
}
|
|
22021
|
+
}
|
|
22022
|
+
async function handleSubmit() {
|
|
22023
|
+
if (!commandInput) return;
|
|
22024
|
+
const prompt = commandInput.value.trim();
|
|
22025
|
+
if (!prompt) return;
|
|
22026
|
+
closeCommandBar();
|
|
22027
|
+
if (state.bridgeSocket?.readyState === WebSocket.OPEN) {
|
|
22028
|
+
state.bridgeSocket.send(JSON.stringify({
|
|
22029
|
+
type: "oneshot",
|
|
22030
|
+
prompt,
|
|
22031
|
+
scope: "project",
|
|
22032
|
+
model: state.selectedModel.id,
|
|
22033
|
+
provider: state.selectedModel.provider === "cli" ? "cli" : void 0
|
|
22034
|
+
}));
|
|
22035
|
+
}
|
|
22036
|
+
}
|
|
22037
|
+
|
|
21643
22038
|
// src/overlay/toolbar/toolbar.ts
|
|
21644
22039
|
var COMMENT_CURSOR_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="28" height="32" viewBox="0 0 28 32"><defs><filter id="s" x="-30%" y="-30%" width="160%" height="160%"><feDropShadow dx="0" dy="1" stdDeviation="1.5" flood-color="rgba(0,0,0,0.5)"/></filter></defs><g filter="url(#s)"><path d="M4 2h16a3 3 0 0 1 3 3v13a3 3 0 0 1-3 3H10l-6 6V5a3 3 0 0 1 3-3z" fill="#7C3AED"/><path d="M4 2h16a3 3 0 0 1 3 3v13a3 3 0 0 1-3 3H10l-6 6V5a3 3 0 0 1 3-3z" fill="none" stroke="rgba(255,255,255,0.25)" stroke-width="0.8"/><line x1="12" y1="7" x2="12" y2="15" stroke="white" stroke-width="1.8" stroke-linecap="round"/><line x1="8" y1="11" x2="16" y2="11" stroke="white" stroke-width="1.8" stroke-linecap="round"/></g></svg>`;
|
|
21645
22040
|
var COMMENT_CURSOR_URL = `url("data:image/svg+xml,${encodeURIComponent(COMMENT_CURSOR_SVG)}") 4 2, crosshair`;
|
|
@@ -21767,6 +22162,14 @@
|
|
|
21767
22162
|
</svg>
|
|
21768
22163
|
</button>
|
|
21769
22164
|
|
|
22165
|
+
<button class="lasso-tool-btn command-tool" type="button" aria-label="Command bar" title="Command bar (\u2318K)">
|
|
22166
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
22167
|
+
<path d="M12 2v20M2 12h20"/>
|
|
22168
|
+
<circle cx="12" cy="12" r="9"/>
|
|
22169
|
+
</svg>
|
|
22170
|
+
<span class="lasso-tool-label">Command</span>
|
|
22171
|
+
</button>
|
|
22172
|
+
|
|
21770
22173
|
<div class="lasso-presence" style="display: none;">
|
|
21771
22174
|
<div class="lasso-presence-avatars"></div>
|
|
21772
22175
|
<span class="lasso-presence-count"></span>
|
|
@@ -21871,6 +22274,7 @@
|
|
|
21871
22274
|
const notepadBtn = toolbar.querySelector(".notepad-tool");
|
|
21872
22275
|
const clipboardBtn = toolbar.querySelector(".clipboard-tool");
|
|
21873
22276
|
const voiceBtn = toolbar.querySelector(".voice-tool");
|
|
22277
|
+
const commandBtn = toolbar.querySelector(".command-tool");
|
|
21874
22278
|
const dismissBtn = toolbar.querySelector(".lasso-toolbar-dismiss");
|
|
21875
22279
|
const newPageBtn = miniToolbar.querySelector(".lasso-new-page-btn");
|
|
21876
22280
|
newPageBtn.addEventListener("click", (event) => {
|
|
@@ -21934,6 +22338,11 @@
|
|
|
21934
22338
|
e.preventDefault();
|
|
21935
22339
|
if (state.voiceOn) setVoiceMuted(!state.voiceMuted);
|
|
21936
22340
|
});
|
|
22341
|
+
commandBtn.addEventListener("click", (event) => {
|
|
22342
|
+
event.preventDefault();
|
|
22343
|
+
event.stopPropagation();
|
|
22344
|
+
openCommandBar();
|
|
22345
|
+
});
|
|
21937
22346
|
dismissBtn.addEventListener("click", () => {
|
|
21938
22347
|
toolbar.classList.add("dismissed");
|
|
21939
22348
|
miniToolbar.classList.add("dismissed");
|
|
@@ -21953,190 +22362,6 @@
|
|
|
21953
22362
|
return { toolbar, voiceBar };
|
|
21954
22363
|
}
|
|
21955
22364
|
|
|
21956
|
-
// src/overlay/commandbar/commandbar.ts
|
|
21957
|
-
var commandBarEl = null;
|
|
21958
|
-
var commandInput = null;
|
|
21959
|
-
var commandBackdrop = null;
|
|
21960
|
-
var commandSubmit = null;
|
|
21961
|
-
var commandVoice = null;
|
|
21962
|
-
var commandClose = null;
|
|
21963
|
-
var isOpen = false;
|
|
21964
|
-
function buildCommandBar() {
|
|
21965
|
-
const dom = getDOM();
|
|
21966
|
-
const backdrop = document.createElement("div");
|
|
21967
|
-
backdrop.className = "lasso-command-backdrop";
|
|
21968
|
-
backdrop.hidden = true;
|
|
21969
|
-
backdrop.innerHTML = ``;
|
|
21970
|
-
dom.shadow.appendChild(backdrop);
|
|
21971
|
-
commandBackdrop = backdrop;
|
|
21972
|
-
const el = document.createElement("div");
|
|
21973
|
-
el.className = "lasso-command-bar";
|
|
21974
|
-
el.hidden = true;
|
|
21975
|
-
el.innerHTML = `
|
|
21976
|
-
<div class="lasso-command-card">
|
|
21977
|
-
<div class="lasso-command-header">
|
|
21978
|
-
<div class="lasso-command-brand">
|
|
21979
|
-
<img class="lasso-command-brand-logo" src="${LASSO_ICON_DATA_URL}" alt="Lasso" />
|
|
21980
|
-
<span class="lasso-command-brand-title">What do you want to build?</span>
|
|
21981
|
-
</div>
|
|
21982
|
-
<button class="lasso-command-close" type="button" aria-label="Close">
|
|
21983
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
21984
|
-
<path d="M18 6L6 18M6 6l12 12"/>
|
|
21985
|
-
</svg>
|
|
21986
|
-
</button>
|
|
21987
|
-
</div>
|
|
21988
|
-
|
|
21989
|
-
<div class="lasso-command-body">
|
|
21990
|
-
<textarea
|
|
21991
|
-
class="lasso-command-input"
|
|
21992
|
-
placeholder="Describe what you want to build... Examples: \u2022 Build a settings page with profile, notifications and security sections \u2022 Add authentication with login and signup \u2022 Create a dashboard with customer table and analytics"
|
|
21993
|
-
rows="3"
|
|
21994
|
-
></textarea>
|
|
21995
|
-
|
|
21996
|
-
<div class="lasso-command-actions">
|
|
21997
|
-
<div class="lasso-command-icon-group">
|
|
21998
|
-
<button class="lasso-command-voice" type="button" aria-label="Voice input" title="Voice input (speech-to-text)">
|
|
21999
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
22000
|
-
<rect x="9" y="3" width="6" height="11" rx="3"/>
|
|
22001
|
-
<path d="M5 11a7 7 0 0 0 14 0M12 18v3M8 21h8"/>
|
|
22002
|
-
</svg>
|
|
22003
|
-
</button>
|
|
22004
|
-
</div>
|
|
22005
|
-
|
|
22006
|
-
<button class="lasso-command-submit primary" type="button" aria-label="Build">
|
|
22007
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
22008
|
-
<path d="M5 12h14M12 5l7 7-7 7"/>
|
|
22009
|
-
</svg>
|
|
22010
|
-
<span>Build</span>
|
|
22011
|
-
</button>
|
|
22012
|
-
</div>
|
|
22013
|
-
|
|
22014
|
-
<div class="lasso-command-shortcuts">
|
|
22015
|
-
<span class="lasso-command-shortcut">
|
|
22016
|
-
<kbd>\u2318</kbd><kbd>K</kbd>
|
|
22017
|
-
<span>Open command bar</span>
|
|
22018
|
-
</span>
|
|
22019
|
-
<span class="lasso-command-shortcut">
|
|
22020
|
-
<kbd>\u2318</kbd><kbd>\u21B5</kbd>
|
|
22021
|
-
<span>Submit</span>
|
|
22022
|
-
</span>
|
|
22023
|
-
<span class="lasso-command-shortcut">
|
|
22024
|
-
<kbd>Esc</kbd>
|
|
22025
|
-
<span>Close</span>
|
|
22026
|
-
</span>
|
|
22027
|
-
</div>
|
|
22028
|
-
</div>
|
|
22029
|
-
</div>
|
|
22030
|
-
`;
|
|
22031
|
-
dom.shadow.appendChild(el);
|
|
22032
|
-
commandBarEl = el;
|
|
22033
|
-
commandInput = el.querySelector(".lasso-command-input");
|
|
22034
|
-
commandSubmit = el.querySelector(".lasso-command-submit");
|
|
22035
|
-
commandVoice = el.querySelector(".lasso-command-voice");
|
|
22036
|
-
commandClose = el.querySelector(".lasso-command-close");
|
|
22037
|
-
backdrop.addEventListener("click", closeCommandBar);
|
|
22038
|
-
commandClose.addEventListener("click", (event) => {
|
|
22039
|
-
event.preventDefault();
|
|
22040
|
-
event.stopPropagation();
|
|
22041
|
-
closeCommandBar();
|
|
22042
|
-
});
|
|
22043
|
-
commandVoice.addEventListener("click", async (event) => {
|
|
22044
|
-
event.preventDefault();
|
|
22045
|
-
event.stopPropagation();
|
|
22046
|
-
if (isRecordingVoice()) {
|
|
22047
|
-
commandVoice.classList.remove("recording");
|
|
22048
|
-
commandVoice.classList.add("transcribing");
|
|
22049
|
-
commandVoice.title = "Transcribing\u2026";
|
|
22050
|
-
try {
|
|
22051
|
-
const result = await stopVoiceRecording();
|
|
22052
|
-
if (result.text && commandInput) {
|
|
22053
|
-
const prev = commandInput.value.trim();
|
|
22054
|
-
commandInput.value = prev ? `${prev} ${result.text}` : result.text;
|
|
22055
|
-
commandInput.focus();
|
|
22056
|
-
autoResizeTextarea(commandInput);
|
|
22057
|
-
showActivity("Transcribed", "#81c995");
|
|
22058
|
-
} else {
|
|
22059
|
-
showActivity("No speech detected.", "#fdd663");
|
|
22060
|
-
}
|
|
22061
|
-
} catch (err) {
|
|
22062
|
-
showActivity(err?.message || "Transcription failed", "#f28b82");
|
|
22063
|
-
} finally {
|
|
22064
|
-
commandVoice.classList.remove("transcribing");
|
|
22065
|
-
commandVoice.title = "Voice input";
|
|
22066
|
-
}
|
|
22067
|
-
} else {
|
|
22068
|
-
try {
|
|
22069
|
-
await startVoiceRecording();
|
|
22070
|
-
commandVoice.classList.add("recording");
|
|
22071
|
-
commandVoice.title = "Recording\u2026 Click again to stop";
|
|
22072
|
-
showActivity("Listening\u2026 speak now", "#ea4335");
|
|
22073
|
-
} catch (err) {
|
|
22074
|
-
showActivity(err?.message || "Microphone access denied", "#f28b82");
|
|
22075
|
-
}
|
|
22076
|
-
}
|
|
22077
|
-
});
|
|
22078
|
-
commandInput.addEventListener("keydown", (event) => {
|
|
22079
|
-
if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
|
|
22080
|
-
event.preventDefault();
|
|
22081
|
-
handleSubmit();
|
|
22082
|
-
}
|
|
22083
|
-
});
|
|
22084
|
-
commandSubmit.addEventListener("click", (event) => {
|
|
22085
|
-
event.preventDefault();
|
|
22086
|
-
event.stopPropagation();
|
|
22087
|
-
handleSubmit();
|
|
22088
|
-
});
|
|
22089
|
-
commandInput.addEventListener("input", () => {
|
|
22090
|
-
if (commandInput) autoResizeTextarea(commandInput);
|
|
22091
|
-
});
|
|
22092
|
-
commandInput.addEventListener("keydown", (event) => {
|
|
22093
|
-
if (event.key === "Escape") {
|
|
22094
|
-
event.preventDefault();
|
|
22095
|
-
closeCommandBar();
|
|
22096
|
-
}
|
|
22097
|
-
});
|
|
22098
|
-
}
|
|
22099
|
-
function autoResizeTextarea(textarea) {
|
|
22100
|
-
textarea.style.height = "auto";
|
|
22101
|
-
textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px`;
|
|
22102
|
-
}
|
|
22103
|
-
function openCommandBar() {
|
|
22104
|
-
if (!commandBarEl || !commandBackdrop) return;
|
|
22105
|
-
commandBarEl.hidden = false;
|
|
22106
|
-
commandBackdrop.hidden = false;
|
|
22107
|
-
isOpen = true;
|
|
22108
|
-
if (commandInput) {
|
|
22109
|
-
commandInput.value = "";
|
|
22110
|
-
commandInput.focus();
|
|
22111
|
-
autoResizeTextarea(commandInput);
|
|
22112
|
-
}
|
|
22113
|
-
}
|
|
22114
|
-
function closeCommandBar() {
|
|
22115
|
-
if (!commandBarEl || !commandBackdrop) return;
|
|
22116
|
-
commandBarEl.hidden = true;
|
|
22117
|
-
commandBackdrop.hidden = true;
|
|
22118
|
-
isOpen = false;
|
|
22119
|
-
if (commandInput) {
|
|
22120
|
-
commandInput.value = "";
|
|
22121
|
-
}
|
|
22122
|
-
}
|
|
22123
|
-
function isCommandBarOpen() {
|
|
22124
|
-
return isOpen;
|
|
22125
|
-
}
|
|
22126
|
-
async function handleSubmit() {
|
|
22127
|
-
if (!commandInput) return;
|
|
22128
|
-
const prompt = commandInput.value.trim();
|
|
22129
|
-
if (!prompt) return;
|
|
22130
|
-
closeCommandBar();
|
|
22131
|
-
if (state.bridgeSocket?.readyState === WebSocket.OPEN) {
|
|
22132
|
-
state.bridgeSocket.send(JSON.stringify({
|
|
22133
|
-
type: "oneshot",
|
|
22134
|
-
prompt,
|
|
22135
|
-
scope: "project"
|
|
22136
|
-
}));
|
|
22137
|
-
}
|
|
22138
|
-
}
|
|
22139
|
-
|
|
22140
22365
|
// src/overlay/index.ts
|
|
22141
22366
|
console.log("[lasso] overlay initializing");
|
|
22142
22367
|
function init() {
|
package/package.json
CHANGED