@lasso-ai/cli 1.1.5 → 1.2.0
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 +15 -0
- package/README.md +57 -0
- package/dist/cli/agent.js +3 -3
- package/dist/cli/bridge.d.ts +6 -0
- package/dist/cli/bridge.js +43 -0
- package/dist/cli/create.d.ts +16 -0
- package/dist/cli/create.js +251 -0
- package/dist/cli/index.js +64 -0
- package/dist/cli/oneshot.d.ts +17 -0
- package/dist/cli/oneshot.js +233 -0
- package/dist/cli/server/next.js +3 -3
- package/dist/cli/server/vite.js +3 -3
- package/dist/overlay.js +447 -2
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
+
- **One-shot mode with global command bar**: Press ⌘K to open a command interface
|
|
13
|
+
anywhere in your app. Describe what you want to build (e.g., "Build a complete billing
|
|
14
|
+
system with invoices and payment tracking") and Lasso will plan, execute, and
|
|
15
|
+
validate the changes automatically. The agent runs through understand → plan →
|
|
16
|
+
execute → validate, with real-time progress visibility.
|
|
17
|
+
- **`lasso create` command**: Create brand-new Lasso projects with framework scaffolding.
|
|
18
|
+
Supports Next.js, React, Vue, Svelte, and Solid with TypeScript and package manager
|
|
19
|
+
selection. Optionally describe your app during creation to trigger one-shot mode
|
|
20
|
+
automatically.
|
|
21
|
+
- **`lasso doctor` command**: Diagnose configuration and integration problems.
|
|
22
|
+
Checks for `lasso.config.json`, API key configuration, framework detection, and
|
|
23
|
+
package.json presence.
|
|
24
|
+
- **Scope management**: One-shot mode supports project-level (broad changes across
|
|
25
|
+
entire codebase) and component-level (targeted changes) scopes to prevent agents
|
|
26
|
+
from wandering through unrelated files.
|
|
12
27
|
- **Agent task workflow**: concurrent prompts now appear in In progress and
|
|
13
28
|
Completed tabs. Review-ready tasks expose a Review action, and accepted changes
|
|
14
29
|
move the task to Completed.
|
package/README.md
CHANGED
|
@@ -78,6 +78,25 @@ Lasso works differently.
|
|
|
78
78
|
|
|
79
79
|
## Features
|
|
80
80
|
|
|
81
|
+
### Two interaction modes
|
|
82
|
+
|
|
83
|
+
Lasso now supports two equally powerful ways to build:
|
|
84
|
+
|
|
85
|
+
**Visual Mode** (point-and-select)
|
|
86
|
+
- Select a component in your running app
|
|
87
|
+
- Describe the change you want
|
|
88
|
+
- Review the diff
|
|
89
|
+
- Accept to apply
|
|
90
|
+
|
|
91
|
+
**One-Shot Mode** (⌘K command bar)
|
|
92
|
+
- Press ⌘K anywhere to open the command bar
|
|
93
|
+
- Describe what you want to build (e.g., "Build a complete billing system")
|
|
94
|
+
- Lasso plans, executes, and validates automatically
|
|
95
|
+
- Real-time progress visibility as the agent works
|
|
96
|
+
|
|
97
|
+
These modes work together naturally: use one-shot to generate large features, then
|
|
98
|
+
visual mode to refine specific parts.
|
|
99
|
+
|
|
81
100
|
### Visual selection
|
|
82
101
|
|
|
83
102
|
Turn on Lasso Mode and interact with your application normally.
|
|
@@ -87,6 +106,17 @@ Turn on Lasso Mode and interact with your application normally.
|
|
|
87
106
|
- Hover to see what will be selected
|
|
88
107
|
- Select empty space to insert new UI
|
|
89
108
|
|
|
109
|
+
### One-shot agent
|
|
110
|
+
|
|
111
|
+
The one-shot agent follows a plan-execute-validate loop:
|
|
112
|
+
|
|
113
|
+
1. **Understand**: Analyzes project structure, framework, and dependencies
|
|
114
|
+
2. **Plan**: Generates execution steps (install, create, modify, delete, command)
|
|
115
|
+
3. **Execute**: Runs each step with real-time progress updates
|
|
116
|
+
4. **Validate**: Runs typecheck and build to verify changes
|
|
117
|
+
|
|
118
|
+
Progress is visible throughout: "Step 1/5: Creating auth configuration", "Step 2/5: Adding login page", etc.
|
|
119
|
+
|
|
90
120
|
### Overlay toolbars
|
|
91
121
|
|
|
92
122
|
The overlay includes a primary editing toolbar and a compact secondary toolbar.
|
|
@@ -333,6 +363,33 @@ and set `LASSO_API_KEY` — see [Team collaboration](#team-collaboration-realtim
|
|
|
333
363
|
|
|
334
364
|
Your existing configuration files are **not modified**.
|
|
335
365
|
|
|
366
|
+
### 2a. Create a new project
|
|
367
|
+
|
|
368
|
+
Starting from scratch? Lasso can scaffold a new project for you:
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
lasso create my-app
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
You'll be prompted to choose:
|
|
375
|
+
- Framework (Next.js, React, Vue, Svelte, Solid)
|
|
376
|
+
- TypeScript preference
|
|
377
|
+
- Package manager (pnpm, npm, yarn)
|
|
378
|
+
|
|
379
|
+
Lasso then:
|
|
380
|
+
- Creates the application scaffold
|
|
381
|
+
- Installs dependencies
|
|
382
|
+
- Connects Lasso automatically
|
|
383
|
+
- Registers the project
|
|
384
|
+
|
|
385
|
+
Optionally describe your app during creation to trigger one-shot mode:
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
lasso create my-app --prompt "A SaaS dashboard for managing customer invoices with billing and analytics"
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Or describe it interactively after the scaffold is complete.
|
|
392
|
+
|
|
336
393
|
### 3. Select something
|
|
337
394
|
|
|
338
395
|
Enable **Lasso Mode** from the floating toolbar.
|
package/dist/cli/agent.js
CHANGED
|
@@ -659,7 +659,7 @@ function runLocalCommand(cwd, command, args, provider, signal, onProgress, taskI
|
|
|
659
659
|
});
|
|
660
660
|
}
|
|
661
661
|
async function proposeWithLocalAgent(cwd, instruction, context, config, signal, onProgress, taskId, onPrompt) {
|
|
662
|
-
const outputContract = `
|
|
662
|
+
const outputContract = `CRITICAL: You MUST return ONLY valid JSON with actual file changes. NEVER explain, describe, or analyze code without proposing edits. ALWAYS return JSON with at least one change when given a modification instruction. Format: {"summary":"brief action taken","changes":[{"filePath":"relative/path","oldString":"exact existing text","newString":"replacement text"}]}. Treat the supplied source context as read-only. Before returning, verify every oldString against that context. Use project-relative paths only. Do not edit files, run write commands, commit, or produce markdown fences. If you cannot find the exact text to change, search the provided context more carefully - do not give up and explain instead.`;
|
|
663
663
|
const prompt = `${instruction}\n\n${outputContract}\n\nLasso has already assembled this source context:\n${context || "No matching source context was found."}`;
|
|
664
664
|
const local = localCommand(config.provider, config.model, prompt);
|
|
665
665
|
const command = local.command;
|
|
@@ -677,11 +677,11 @@ async function proposeChanges(cwd, input, config, signal, onProgress, onPrompt)
|
|
|
677
677
|
const dragHint = input.context?.drag
|
|
678
678
|
? `\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
679
|
: "";
|
|
680
|
-
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\
|
|
680
|
+
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
681
|
if (config.provider === "claude-code" || config.provider === "codex" || config.provider === "opencode") {
|
|
682
682
|
return proposeWithLocalAgent(cwd, instruction, context, config, signal, onProgress, input.taskId, onPrompt);
|
|
683
683
|
}
|
|
684
|
-
const system = "You are Lasso, a
|
|
684
|
+
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.";
|
|
685
685
|
const model = config.model || (config.provider === "google" ? "gemini-2.5-flash" : config.provider === "openai" ? "gpt-4.1-mini" : config.provider === "ollama" ? "llama3.2" : "claude-sonnet-4-20250514");
|
|
686
686
|
const image = input.context?.screenshots?.element || input.context?.screenshots?.full;
|
|
687
687
|
const imageData = image?.replace(/^data:image\/[^;]+;base64,/, "");
|
package/dist/cli/bridge.d.ts
CHANGED
|
@@ -131,6 +131,12 @@ export type BridgeMessage = {
|
|
|
131
131
|
audio: string;
|
|
132
132
|
mimeType?: string;
|
|
133
133
|
language?: string;
|
|
134
|
+
} | {
|
|
135
|
+
type: "oneshot";
|
|
136
|
+
prompt: string;
|
|
137
|
+
scope: "project" | "component";
|
|
138
|
+
model?: string;
|
|
139
|
+
provider?: "anthropic" | "openai" | "google" | "ollama" | "cli";
|
|
134
140
|
};
|
|
135
141
|
type ModelOption = {
|
|
136
142
|
id: string;
|
package/dist/cli/bridge.js
CHANGED
|
@@ -598,6 +598,49 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
598
598
|
}));
|
|
599
599
|
}
|
|
600
600
|
}
|
|
601
|
+
else if (msg.type === "oneshot") {
|
|
602
|
+
console.log("[lasso] one-shot request:", msg.prompt, msg.scope);
|
|
603
|
+
if (!lassoKeyConfigured && !agentConfig) {
|
|
604
|
+
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." }));
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
const localProvider = msg.provider === "cli";
|
|
608
|
+
const cliProvider = msg.model?.startsWith("claude-code:") ? "claude-code" : msg.model?.startsWith("opencode:") ? "opencode" : "codex";
|
|
609
|
+
const selectedConfig = localProvider
|
|
610
|
+
? { provider: cliProvider, model: msg.model || "claude-code:sonnet" }
|
|
611
|
+
: { ...agentConfig, provider: (msg.provider || agentConfig.provider), model: msg.model || agentConfig.model };
|
|
612
|
+
const taskId = `oneshot-${Date.now()}`;
|
|
613
|
+
const controller = new AbortController();
|
|
614
|
+
taskControllers.set(taskId, controller);
|
|
615
|
+
socket.send(JSON.stringify({ type: "agent_status", taskId, status: "thinking", message: "Planning your request..." }));
|
|
616
|
+
// Import the one-shot agent function
|
|
617
|
+
const { runOneShotAgent } = await import("./oneshot.js");
|
|
618
|
+
runOneShotAgent(cwd, msg.prompt, msg.scope, selectedConfig, controller.signal, (status, message, detail) => {
|
|
619
|
+
if (!controller.signal.aborted && socket.readyState === socket.OPEN) {
|
|
620
|
+
socket.send(JSON.stringify({ type: "agent_status", taskId, status, message, detail }));
|
|
621
|
+
}
|
|
622
|
+
}, (prompt) => {
|
|
623
|
+
if (!controller.signal.aborted && socket.readyState === socket.OPEN) {
|
|
624
|
+
socket.send(JSON.stringify({ type: "agent_prompt", taskId, prompt }));
|
|
625
|
+
}
|
|
626
|
+
}).then((result) => {
|
|
627
|
+
if (!controller.signal.aborted && socket.readyState === socket.OPEN) {
|
|
628
|
+
if (result.ok) {
|
|
629
|
+
socket.send(JSON.stringify({ type: "agent_status", taskId, status: "review", message: result.summary, changes: result.changes }));
|
|
630
|
+
}
|
|
631
|
+
else {
|
|
632
|
+
socket.send(JSON.stringify({ type: "agent_status", taskId, status: "error", message: result.error || "One-shot agent failed" }));
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}).catch((error) => {
|
|
636
|
+
if (!controller.signal.aborted && socket.readyState === socket.OPEN) {
|
|
637
|
+
socket.send(JSON.stringify({ type: "agent_status", taskId, status: "error", message: error instanceof Error ? error.message : "One-shot agent encountered an error" }));
|
|
638
|
+
}
|
|
639
|
+
}).finally(() => {
|
|
640
|
+
if (taskControllers.get(taskId) === controller)
|
|
641
|
+
taskControllers.delete(taskId);
|
|
642
|
+
});
|
|
643
|
+
}
|
|
601
644
|
else if (msg.type === "apply") {
|
|
602
645
|
try {
|
|
603
646
|
const snapshots = [];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type Framework = "next" | "react" | "vue" | "svelte" | "solid";
|
|
2
|
+
export type PackageManager = "pnpm" | "npm" | "yarn";
|
|
3
|
+
interface CreateOptions {
|
|
4
|
+
name?: string;
|
|
5
|
+
framework?: Framework;
|
|
6
|
+
typescript?: boolean;
|
|
7
|
+
packageManager?: PackageManager;
|
|
8
|
+
prompt?: string;
|
|
9
|
+
}
|
|
10
|
+
interface CreateResult {
|
|
11
|
+
ok: boolean;
|
|
12
|
+
error?: string;
|
|
13
|
+
projectPath?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function createProject(options: CreateOptions): Promise<CreateResult>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createProject = createProject;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const node_child_process_1 = require("node:child_process");
|
|
10
|
+
const node_util_1 = require("node:util");
|
|
11
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
12
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
13
|
+
const project_1 = require("./project");
|
|
14
|
+
const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
|
|
15
|
+
async function detectPackageManager() {
|
|
16
|
+
const hasCommand = (cmd) => {
|
|
17
|
+
try {
|
|
18
|
+
(0, node_child_process_1.execSync)(cmd, { stdio: "ignore" });
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
if (hasCommand("pnpm --version"))
|
|
26
|
+
return "pnpm";
|
|
27
|
+
if (hasCommand("yarn --version"))
|
|
28
|
+
return "yarn";
|
|
29
|
+
return "npm";
|
|
30
|
+
}
|
|
31
|
+
function getFrameworkCommand(framework, typescript, packageManager) {
|
|
32
|
+
const tsFlag = typescript ? "--typescript" : "--js";
|
|
33
|
+
const baseCommands = {
|
|
34
|
+
next: `npx create-next-app@latest`,
|
|
35
|
+
react: `npx create-react-app@latest`,
|
|
36
|
+
vue: `npx create-vite@latest`,
|
|
37
|
+
svelte: `npx create-svelte@latest`,
|
|
38
|
+
solid: `npx degit solidjs/templates/js`,
|
|
39
|
+
};
|
|
40
|
+
let command = baseCommands[framework];
|
|
41
|
+
if (framework === "next") {
|
|
42
|
+
command += ` ${tsFlag} --tailwind --eslint --app --src-dir --import-alias "@/*" --no-git`;
|
|
43
|
+
}
|
|
44
|
+
else if (framework === "react") {
|
|
45
|
+
command += ` ${typescript ? "--template typescript" : ""}`;
|
|
46
|
+
}
|
|
47
|
+
else if (framework === "vue") {
|
|
48
|
+
command += ` ${typescript ? "--template vue-ts" : "--template vanilla"}`;
|
|
49
|
+
}
|
|
50
|
+
else if (framework === "svelte") {
|
|
51
|
+
command += ` ${typescript ? "--template skeleton" : "--template skeleton"}`;
|
|
52
|
+
}
|
|
53
|
+
return command;
|
|
54
|
+
}
|
|
55
|
+
async function runCreateCommand(projectName, framework, typescript, packageManager, cwd) {
|
|
56
|
+
const projectPath = node_path_1.default.join(cwd, projectName);
|
|
57
|
+
if (node_fs_1.default.existsSync(projectPath)) {
|
|
58
|
+
return { ok: false, error: `Directory "${projectName}" already exists.` };
|
|
59
|
+
}
|
|
60
|
+
console.log(chalk_1.default.dim(`Creating ${chalk_1.default.cyan(projectName)} with ${chalk_1.default.cyan(framework)}...`));
|
|
61
|
+
try {
|
|
62
|
+
let command;
|
|
63
|
+
let args;
|
|
64
|
+
if (framework === "next") {
|
|
65
|
+
const tsFlag = typescript ? "--ts" : "--js";
|
|
66
|
+
command = "npx";
|
|
67
|
+
args = ["create-next-app@latest", projectName, tsFlag, "--tailwind", "--eslint", "--app", "--src-dir", "--import-alias", "@/*", "--no-git"];
|
|
68
|
+
}
|
|
69
|
+
else if (framework === "react") {
|
|
70
|
+
command = "npx";
|
|
71
|
+
args = ["create-react-app@latest", projectName, ...(typescript ? ["--template", "typescript"] : [])];
|
|
72
|
+
}
|
|
73
|
+
else if (framework === "vue") {
|
|
74
|
+
command = "npx";
|
|
75
|
+
args = ["create-vue@latest", projectName, ...(typescript ? ["--typescript"] : []), "--no-git", "--no-install"];
|
|
76
|
+
}
|
|
77
|
+
else if (framework === "svelte") {
|
|
78
|
+
command = "npx";
|
|
79
|
+
args = ["create-svelte@latest", projectName, ...(typescript ? ["--types", "typescript"] : ["--types", "checkjs"]), "--no-eslint", "--no-prettier", "--no-playwright", "--no-vitest", "--template", "skeleton"];
|
|
80
|
+
}
|
|
81
|
+
else if (framework === "solid") {
|
|
82
|
+
command = "npx";
|
|
83
|
+
args = ["degit", `solidjs/templates/${typescript ? "ts" : "js"}`, projectName];
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
return { ok: false, error: `Unsupported framework: ${framework}` };
|
|
87
|
+
}
|
|
88
|
+
await new Promise((resolve, reject) => {
|
|
89
|
+
const proc = (0, node_child_process_1.spawn)(command, args, { cwd, stdio: "inherit" });
|
|
90
|
+
proc.on("close", (code) => {
|
|
91
|
+
if (code === 0)
|
|
92
|
+
resolve();
|
|
93
|
+
else
|
|
94
|
+
reject(new Error(`Command exited with code ${code}`));
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
if (framework === "vue" || framework === "svelte") {
|
|
98
|
+
console.log(chalk_1.default.dim(`Installing dependencies with ${chalk_1.default.cyan(packageManager)}...`));
|
|
99
|
+
const installCommand = packageManager === "pnpm" ? "pnpm" : packageManager === "yarn" ? "yarn" : "npm";
|
|
100
|
+
const installArgs = packageManager === "pnpm" ? ["install"] : packageManager === "yarn" ? [] : ["install"];
|
|
101
|
+
await new Promise((resolve, reject) => {
|
|
102
|
+
const proc = (0, node_child_process_1.spawn)(installCommand, installArgs, { cwd: projectPath, stdio: "inherit" });
|
|
103
|
+
proc.on("close", (code) => {
|
|
104
|
+
if (code === 0)
|
|
105
|
+
resolve();
|
|
106
|
+
else
|
|
107
|
+
reject(new Error(`Install command exited with code ${code}`));
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return { ok: true, projectPath };
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
const reason = error instanceof Error ? error.message : "unknown error";
|
|
115
|
+
return { ok: false, error: `Failed to create project: ${reason}` };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function createProject(options) {
|
|
119
|
+
const cwd = process.cwd();
|
|
120
|
+
let { name, framework, typescript, packageManager } = options;
|
|
121
|
+
if (!name) {
|
|
122
|
+
const response = await (0, prompts_1.default)({
|
|
123
|
+
type: "text",
|
|
124
|
+
name: "name",
|
|
125
|
+
message: "Project name:",
|
|
126
|
+
initial: "my-app",
|
|
127
|
+
validate: (value) => {
|
|
128
|
+
if (!value.trim())
|
|
129
|
+
return "Project name is required";
|
|
130
|
+
if (!/^[a-z0-9-]+$/.test(value))
|
|
131
|
+
return "Project name must contain only lowercase letters, numbers, and hyphens";
|
|
132
|
+
return true;
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
name = response.name;
|
|
136
|
+
}
|
|
137
|
+
if (!name) {
|
|
138
|
+
return { ok: false, error: "Project name is required" };
|
|
139
|
+
}
|
|
140
|
+
if (!framework) {
|
|
141
|
+
const response = await (0, prompts_1.default)({
|
|
142
|
+
type: "select",
|
|
143
|
+
name: "framework",
|
|
144
|
+
message: "Choose a framework:",
|
|
145
|
+
choices: [
|
|
146
|
+
{ title: "Next.js", value: "next" },
|
|
147
|
+
{ title: "React", value: "react" },
|
|
148
|
+
{ title: "Vue", value: "vue" },
|
|
149
|
+
{ title: "Svelte", value: "svelte" },
|
|
150
|
+
{ title: "Solid", value: "solid" },
|
|
151
|
+
],
|
|
152
|
+
});
|
|
153
|
+
framework = response.framework;
|
|
154
|
+
}
|
|
155
|
+
if (!framework) {
|
|
156
|
+
return { ok: false, error: "Framework selection is required" };
|
|
157
|
+
}
|
|
158
|
+
if (typescript === undefined) {
|
|
159
|
+
const response = await (0, prompts_1.default)({
|
|
160
|
+
type: "select",
|
|
161
|
+
name: "typescript",
|
|
162
|
+
message: "Use TypeScript?",
|
|
163
|
+
choices: [
|
|
164
|
+
{ title: "Yes", value: true },
|
|
165
|
+
{ title: "No", value: false },
|
|
166
|
+
],
|
|
167
|
+
});
|
|
168
|
+
typescript = response.typescript;
|
|
169
|
+
}
|
|
170
|
+
if (typescript === undefined) {
|
|
171
|
+
typescript = true;
|
|
172
|
+
}
|
|
173
|
+
if (!packageManager) {
|
|
174
|
+
const detected = await detectPackageManager();
|
|
175
|
+
const response = await (0, prompts_1.default)({
|
|
176
|
+
type: "select",
|
|
177
|
+
name: "packageManager",
|
|
178
|
+
message: "Package manager:",
|
|
179
|
+
choices: [
|
|
180
|
+
{ title: "pnpm", value: "pnpm" },
|
|
181
|
+
{ title: "npm", value: "npm" },
|
|
182
|
+
{ title: "yarn", value: "yarn" },
|
|
183
|
+
],
|
|
184
|
+
initial: detected,
|
|
185
|
+
});
|
|
186
|
+
packageManager = response.packageManager;
|
|
187
|
+
}
|
|
188
|
+
if (!packageManager) {
|
|
189
|
+
packageManager = "npm";
|
|
190
|
+
}
|
|
191
|
+
const createResult = await runCreateCommand(name, framework, typescript, packageManager, cwd);
|
|
192
|
+
if (!createResult.ok) {
|
|
193
|
+
return createResult;
|
|
194
|
+
}
|
|
195
|
+
const projectPath = createResult.projectPath;
|
|
196
|
+
console.log(chalk_1.default.green("✓") + " Created application");
|
|
197
|
+
console.log(chalk_1.default.green("✓") + " Installed dependencies");
|
|
198
|
+
const fileEnv = (0, project_1.readProjectEnv)(projectPath);
|
|
199
|
+
const initResult = await (0, project_1.initProject)(projectPath, fileEnv);
|
|
200
|
+
if (!initResult.ok) {
|
|
201
|
+
console.error(chalk_1.default.yellow("!") + ` Could not initialize Lasso: ${initResult.error}`);
|
|
202
|
+
console.log(chalk_1.default.dim(`Run ${chalk_1.default.cyan("lasso init")} in the project directory to complete setup.`));
|
|
203
|
+
return { ok: true, projectPath };
|
|
204
|
+
}
|
|
205
|
+
console.log(chalk_1.default.green("✓") + " Connected Lasso");
|
|
206
|
+
console.log(chalk_1.default.green("✓") + " Registered project");
|
|
207
|
+
// Ask if user wants to describe their app for one-shot building
|
|
208
|
+
if (!options.prompt) {
|
|
209
|
+
const describeResponse = await (0, prompts_1.default)({
|
|
210
|
+
type: "select",
|
|
211
|
+
name: "describe",
|
|
212
|
+
message: "Do you want to describe your app for Lasso to build initial features?",
|
|
213
|
+
choices: [
|
|
214
|
+
{ title: "Yes, describe my app", value: true },
|
|
215
|
+
{ title: "No, I'll start from scratch", value: false },
|
|
216
|
+
],
|
|
217
|
+
});
|
|
218
|
+
if (describeResponse.describe) {
|
|
219
|
+
const promptResponse = await (0, prompts_1.default)({
|
|
220
|
+
type: "text",
|
|
221
|
+
name: "appPrompt",
|
|
222
|
+
message: "Describe what you're building (e.g., A SaaS dashboard for managing customer invoices with billing and analytics):",
|
|
223
|
+
validate: (value) => {
|
|
224
|
+
if (!value.trim())
|
|
225
|
+
return "Please describe your app";
|
|
226
|
+
return true;
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
if (promptResponse.appPrompt) {
|
|
230
|
+
options.prompt = promptResponse.appPrompt;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (options.prompt) {
|
|
235
|
+
console.log("");
|
|
236
|
+
console.log(chalk_1.default.dim(`✦ Lasso will build: ${chalk_1.default.cyan(options.prompt)}`));
|
|
237
|
+
console.log(chalk_1.default.dim(` Open your app and press ${chalk_1.default.cyan("⌘K")} to track progress.`));
|
|
238
|
+
}
|
|
239
|
+
console.log("");
|
|
240
|
+
console.log(chalk_1.default.bold("Your app is ready!"));
|
|
241
|
+
console.log("");
|
|
242
|
+
console.log(chalk_1.default.dim(`Next steps:`));
|
|
243
|
+
console.log(chalk_1.default.dim(` cd ${chalk_1.default.cyan(name)}`));
|
|
244
|
+
console.log(chalk_1.default.dim(` ${chalk_1.default.cyan("lasso dev")}`));
|
|
245
|
+
if (options.prompt) {
|
|
246
|
+
console.log(chalk_1.default.dim(` Then use ${chalk_1.default.cyan("⌘K")} to trigger one-shot mode`));
|
|
247
|
+
}
|
|
248
|
+
console.log("");
|
|
249
|
+
console.log(chalk_1.default.dim(`Your app will be available at ${chalk_1.default.cyan("http://localhost:3000")}`));
|
|
250
|
+
return { ok: true, projectPath };
|
|
251
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -6,6 +6,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
const node_os_1 = __importDefault(require("node:os"));
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
10
11
|
const commander_1 = require("commander");
|
|
11
12
|
const bridge_1 = require("./bridge");
|
|
@@ -20,6 +21,7 @@ const install_1 = require("./host/install");
|
|
|
20
21
|
const client_1 = require("./host/client");
|
|
21
22
|
const registry_1 = require("./host/registry");
|
|
22
23
|
const next_config_1 = require("./next-config");
|
|
24
|
+
const create_1 = require("./create");
|
|
23
25
|
const package_json_1 = __importDefault(require("../../package.json"));
|
|
24
26
|
const VERSION = package_json_1.default.version;
|
|
25
27
|
const program = new commander_1.Command();
|
|
@@ -81,6 +83,17 @@ program.command("init")
|
|
|
81
83
|
console.log(chalk_1.default.dim(` ${project_1.LASSO_CONFIG_FILE} — commit it so teammates share this project.`));
|
|
82
84
|
});
|
|
83
85
|
// prettier-ignore
|
|
86
|
+
program.command("create [name]")
|
|
87
|
+
.description("Create a brand-new Lasso project with a framework scaffold")
|
|
88
|
+
.option("--prompt <prompt>", "Describe your app for one-shot building")
|
|
89
|
+
.action(async (name, options) => {
|
|
90
|
+
const result = await (0, create_1.createProject)({ name, prompt: options.prompt });
|
|
91
|
+
if (!result.ok) {
|
|
92
|
+
console.error(chalk_1.default.red("✗") + ` ${result.error}`);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
// prettier-ignore
|
|
84
97
|
const daemon = program.command("daemon [action]")
|
|
85
98
|
.description("Start/stop Lasso Host — the local .lasso domain server (start|status|stop|restart|install|uninstall)")
|
|
86
99
|
.usage("[action]")
|
|
@@ -353,6 +366,57 @@ auth.command("logout").description("Remove the Lasso credential from this machin
|
|
|
353
366
|
console.log(chalk_1.default.green("✓") + " Signed out. Revoke the key from the dashboard if you no longer need it.");
|
|
354
367
|
});
|
|
355
368
|
// prettier-ignore
|
|
369
|
+
program.command("doctor")
|
|
370
|
+
.description("Diagnose configuration and integration problems")
|
|
371
|
+
.action(async () => {
|
|
372
|
+
const cwd = process.cwd();
|
|
373
|
+
console.log(chalk_1.default.bold("Lasso Doctor"));
|
|
374
|
+
console.log("");
|
|
375
|
+
// Check for lasso.config.json
|
|
376
|
+
const configPath = node_path_1.default.join(cwd, project_1.LASSO_CONFIG_FILE);
|
|
377
|
+
if (node_fs_1.default.existsSync(configPath)) {
|
|
378
|
+
console.log(chalk_1.default.green("✓") + ` ${project_1.LASSO_CONFIG_FILE} exists`);
|
|
379
|
+
try {
|
|
380
|
+
const config = JSON.parse(node_fs_1.default.readFileSync(configPath, "utf8"));
|
|
381
|
+
console.log(chalk_1.default.dim(` Project ID: ${config.id}`));
|
|
382
|
+
if (config.domain)
|
|
383
|
+
console.log(chalk_1.default.dim(` Domain: ${config.domain}`));
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
console.log(chalk_1.default.red("✗") + ` ${project_1.LASSO_CONFIG_FILE} is invalid`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
console.log(chalk_1.default.yellow("!") + ` ${project_1.LASSO_CONFIG_FILE} not found. Run ${chalk_1.default.cyan("lasso init")} to create it.`);
|
|
391
|
+
}
|
|
392
|
+
// Check for API key
|
|
393
|
+
const credentials = (0, auth_1.loadCredentials)();
|
|
394
|
+
if (credentials) {
|
|
395
|
+
console.log(chalk_1.default.green("✓") + " Lasso API key configured");
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
console.log(chalk_1.default.yellow("!") + " No Lasso API key found. Run " + chalk_1.default.cyan("lasso auth login"));
|
|
399
|
+
}
|
|
400
|
+
// Check framework
|
|
401
|
+
const framework = (0, framework_1.detectFramework)(cwd);
|
|
402
|
+
if (framework !== "unknown") {
|
|
403
|
+
console.log(chalk_1.default.green("✓") + ` Detected framework: ${framework}`);
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
console.log(chalk_1.default.yellow("!") + " Could not detect a supported framework");
|
|
407
|
+
}
|
|
408
|
+
// Check package.json
|
|
409
|
+
const pkgPath = node_path_1.default.join(cwd, "package.json");
|
|
410
|
+
if (node_fs_1.default.existsSync(pkgPath)) {
|
|
411
|
+
console.log(chalk_1.default.green("✓") + " package.json exists");
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
console.log(chalk_1.default.red("✗") + " package.json not found");
|
|
415
|
+
}
|
|
416
|
+
console.log("");
|
|
417
|
+
console.log(chalk_1.default.dim("Run ") + chalk_1.default.cyan("lasso dev") + chalk_1.default.dim(" to start development with Lasso."));
|
|
418
|
+
});
|
|
419
|
+
// prettier-ignore
|
|
356
420
|
program.command("dev", { isDefault: true }).description("Start your dev server with the Lasso overlay attached").action(async () => {
|
|
357
421
|
const cwd = process.cwd();
|
|
358
422
|
const framework = (0, framework_1.detectFramework)(cwd);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AgentConfig, SourceChange } from "./agent";
|
|
2
|
+
export interface OneShotResult {
|
|
3
|
+
ok: boolean;
|
|
4
|
+
summary?: string;
|
|
5
|
+
changes?: SourceChange[];
|
|
6
|
+
error?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ProgressCallback {
|
|
9
|
+
(status: "thinking" | "working" | "review" | "error" | "stopped", message: string, detail?: string): void;
|
|
10
|
+
}
|
|
11
|
+
export interface PromptCallback {
|
|
12
|
+
(prompt: {
|
|
13
|
+
question: string;
|
|
14
|
+
options?: string[];
|
|
15
|
+
}): void;
|
|
16
|
+
}
|
|
17
|
+
export declare function runOneShotAgent(cwd: string, prompt: string, scope: "project" | "component", config: AgentConfig, signal: AbortSignal, onProgress: ProgressCallback, onPrompt: PromptCallback): Promise<OneShotResult>;
|