@lasso-ai/cli 1.0.6 → 1.0.8
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/README.md +13 -0
- package/dist/cli/bridge.d.ts +5 -0
- package/dist/cli/bridge.js +26 -2
- package/dist/cli/host/daemon.d.ts +1 -0
- package/dist/cli/host/daemon.js +27 -13
- package/dist/cli/host/next-host-entry.js +25 -4
- package/dist/cli/index.js +41 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -186,6 +186,17 @@ Point the overlay at a different realtime server with any of
|
|
|
186
186
|
`LASSO_REALTIME_URL`, `REALTIME_URL`, or `NEXT_PUBLIC_REALTIME_URL`
|
|
187
187
|
(default: `http://localhost:3007`).
|
|
188
188
|
|
|
189
|
+
If the overlay connection gets stuck while `lasso dev` is running, reset it
|
|
190
|
+
without restarting your framework with:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
lasso bridge restart
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The command closes the current overlay WebSocket, cancels any active agent
|
|
197
|
+
request, and lets the overlay reconnect to the existing bridge. Use
|
|
198
|
+
`--port <port>` when the bridge is running on a custom port.
|
|
199
|
+
|
|
189
200
|
---
|
|
190
201
|
|
|
191
202
|
## Local domains with Lasso Host
|
|
@@ -200,6 +211,8 @@ project is served at `http://app.lasso:<port>` or the secure
|
|
|
200
211
|
`*.lasso` domains, loopback-only, single instance).
|
|
201
212
|
- `lasso register [domain]` — register the current directory under a `.lasso`
|
|
202
213
|
domain (reuse, generate, or change one); never duplicates.
|
|
214
|
+
- `lasso unregister [domain]` — remove the current directory’s `.lasso`
|
|
215
|
+
registration, or pass a domain explicitly.
|
|
203
216
|
- `lasso projects` — list registered domains and running state.
|
|
204
217
|
- `lasso daemon install/uninstall` — attach a macOS LaunchAgent (auto-start on
|
|
205
218
|
login) and configure the system DNS resolver so bare `app.lasso` works.
|
package/dist/cli/bridge.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type SourceChange } from "./agent";
|
|
2
2
|
import type { CollabConfig } from "./project";
|
|
3
|
+
export declare const DEFAULT_BRIDGE_PORT = 3056;
|
|
3
4
|
type GitState = {
|
|
4
5
|
isRepo: boolean;
|
|
5
6
|
branch?: string;
|
|
@@ -153,6 +154,10 @@ export type ServerBridgeMessage = {
|
|
|
153
154
|
provider?: string;
|
|
154
155
|
error?: string;
|
|
155
156
|
};
|
|
157
|
+
export declare function restartBridge(port?: number): Promise<{
|
|
158
|
+
ok: boolean;
|
|
159
|
+
error?: string;
|
|
160
|
+
}>;
|
|
156
161
|
export declare function startBridge(cwd?: string, collabConfig?: CollabConfig | null, bridgePort?: number): {
|
|
157
162
|
send: (msg: ServerBridgeMessage) => void;
|
|
158
163
|
close(): void;
|
package/dist/cli/bridge.js
CHANGED
|
@@ -3,6 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DEFAULT_BRIDGE_PORT = void 0;
|
|
7
|
+
exports.restartBridge = restartBridge;
|
|
6
8
|
exports.startBridge = startBridge;
|
|
7
9
|
// src/cli/bridge.ts
|
|
8
10
|
const ws_1 = require("ws");
|
|
@@ -14,8 +16,20 @@ const node_util_1 = require("node:util");
|
|
|
14
16
|
const chalk_1 = __importDefault(require("chalk"));
|
|
15
17
|
const agent_1 = require("./agent");
|
|
16
18
|
const auth_1 = require("./auth");
|
|
17
|
-
|
|
19
|
+
exports.DEFAULT_BRIDGE_PORT = 3056;
|
|
18
20
|
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
21
|
+
async function restartBridge(port = exports.DEFAULT_BRIDGE_PORT) {
|
|
22
|
+
try {
|
|
23
|
+
const response = await fetch(`http://127.0.0.1:${port}/__lasso/bridge/restart`, { method: "POST" });
|
|
24
|
+
const body = await response.json().catch(() => ({}));
|
|
25
|
+
if (!response.ok || !body.ok)
|
|
26
|
+
return { ok: false, error: body.error || "The bridge rejected the restart request." };
|
|
27
|
+
return { ok: true };
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return { ok: false, error: `No Lasso bridge is listening on 127.0.0.1:${port}. Start your project with ${chalk_1.default.cyan("lasso dev")} first.` };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
19
33
|
async function gitCommand(cwd, args) {
|
|
20
34
|
const result = await execFileAsync("git", args, { cwd, maxBuffer: 1024 * 1024 });
|
|
21
35
|
return result.stdout.trim();
|
|
@@ -82,7 +96,7 @@ function readEnvFile(cwd, filename) {
|
|
|
82
96
|
return {};
|
|
83
97
|
}
|
|
84
98
|
}
|
|
85
|
-
function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = DEFAULT_BRIDGE_PORT) {
|
|
99
|
+
function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = exports.DEFAULT_BRIDGE_PORT) {
|
|
86
100
|
const bridgeServer = node_http_1.default.createServer(); // dedicated, empty HTTP server
|
|
87
101
|
const wss = new ws_1.WebSocketServer({ server: bridgeServer });
|
|
88
102
|
let overlaySocket = null;
|
|
@@ -116,6 +130,16 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = DEFA
|
|
|
116
130
|
})();
|
|
117
131
|
let activeAgentController = null;
|
|
118
132
|
let localAgents = new Set();
|
|
133
|
+
bridgeServer.on("request", (req, res) => {
|
|
134
|
+
if (req.method !== "POST" || req.url?.split("?", 1)[0] !== "/__lasso/bridge/restart")
|
|
135
|
+
return;
|
|
136
|
+
activeAgentController?.abort();
|
|
137
|
+
activeAgentController = null;
|
|
138
|
+
for (const socket of wss.clients)
|
|
139
|
+
socket.close(1000, "Bridge restarted by the CLI");
|
|
140
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
141
|
+
res.end(JSON.stringify({ ok: true }));
|
|
142
|
+
});
|
|
119
143
|
async function localModels() {
|
|
120
144
|
const base = process.env.OLLAMA_BASE_URL || fileEnv.OLLAMA_BASE_URL || "http://localhost:11434/api";
|
|
121
145
|
try {
|
package/dist/cli/host/daemon.js
CHANGED
|
@@ -52,6 +52,7 @@ class LassoHost {
|
|
|
52
52
|
resolver;
|
|
53
53
|
registry;
|
|
54
54
|
running = new Map();
|
|
55
|
+
starting = new Map();
|
|
55
56
|
crashLog = new Map();
|
|
56
57
|
proxies = new Map();
|
|
57
58
|
constructor(opts) {
|
|
@@ -106,6 +107,9 @@ class LassoHost {
|
|
|
106
107
|
const existing = this.running.get(domain);
|
|
107
108
|
if (existing)
|
|
108
109
|
return existing;
|
|
110
|
+
const pending = this.starting.get(domain);
|
|
111
|
+
if (pending)
|
|
112
|
+
return pending;
|
|
109
113
|
const project = this.registry[domain];
|
|
110
114
|
if (!project)
|
|
111
115
|
return null;
|
|
@@ -114,20 +118,30 @@ class LassoHost {
|
|
|
114
118
|
throw new Error(`${domain} kept crashing on startup; not auto-restarting. Fix the project or run "lasso daemon restart".`);
|
|
115
119
|
}
|
|
116
120
|
this.crashLog.set(domain, crashes);
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const runtime = result.runtime;
|
|
123
|
-
this.running.set(domain, runtime);
|
|
124
|
-
runtime.child.on("exit", (code, signal) => {
|
|
125
|
-
if (this.running.get(domain) === runtime) {
|
|
126
|
-
this.running.delete(domain);
|
|
127
|
-
this.log(`project ${domain} exited (code=${code} signal=${signal})`);
|
|
121
|
+
const startup = (async () => {
|
|
122
|
+
const result = await (0, runtime_1.startProjectRuntime)(project.directory);
|
|
123
|
+
if (!result.ok || !result.runtime) {
|
|
124
|
+
this.crashLog.set(domain, [...crashes, Date.now()]);
|
|
125
|
+
throw new Error(result.error || `Could not start ${domain}.`);
|
|
128
126
|
}
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
const runtime = result.runtime;
|
|
128
|
+
this.running.set(domain, runtime);
|
|
129
|
+
runtime.child.on("exit", (code, signal) => {
|
|
130
|
+
if (this.running.get(domain) === runtime) {
|
|
131
|
+
this.running.delete(domain);
|
|
132
|
+
this.log(`project ${domain} exited (code=${code} signal=${signal})`);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
return runtime;
|
|
136
|
+
})();
|
|
137
|
+
this.starting.set(domain, startup);
|
|
138
|
+
try {
|
|
139
|
+
return await startup;
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
if (this.starting.get(domain) === startup)
|
|
143
|
+
this.starting.delete(domain);
|
|
144
|
+
}
|
|
131
145
|
}
|
|
132
146
|
// ---- control surface ---------------------------------------------
|
|
133
147
|
routes() {
|
|
@@ -25,10 +25,29 @@ function freePort() {
|
|
|
25
25
|
});
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
|
-
function waitForNext(nextPort, timeoutMs = 60_000) {
|
|
28
|
+
function waitForNext(nextPort, nextProcess, timeoutMs = 60_000) {
|
|
29
29
|
const deadline = Date.now() + timeoutMs;
|
|
30
30
|
return new Promise((resolve, reject) => {
|
|
31
|
+
let settled = false;
|
|
32
|
+
const finish = (error) => {
|
|
33
|
+
if (settled)
|
|
34
|
+
return;
|
|
35
|
+
settled = true;
|
|
36
|
+
clearTimeout(timeout);
|
|
37
|
+
nextProcess.off("exit", onExit);
|
|
38
|
+
if (error)
|
|
39
|
+
reject(error);
|
|
40
|
+
else
|
|
41
|
+
resolve();
|
|
42
|
+
};
|
|
43
|
+
const onExit = (code, signal) => {
|
|
44
|
+
finish(new Error(`Next.js exited before becoming ready${code === null ? ` (${signal || "unknown signal"})` : ` with code ${code}`}.`));
|
|
45
|
+
};
|
|
46
|
+
const timeout = setTimeout(() => finish(new Error(`Next.js did not become ready on port ${nextPort} within ${timeoutMs / 1000}s.`)), timeoutMs);
|
|
47
|
+
nextProcess.once("exit", onExit);
|
|
31
48
|
const check = () => {
|
|
49
|
+
if (settled)
|
|
50
|
+
return;
|
|
32
51
|
const request = node_http_1.default.get({
|
|
33
52
|
hostname: "127.0.0.1",
|
|
34
53
|
port: nextPort,
|
|
@@ -36,11 +55,13 @@ function waitForNext(nextPort, timeoutMs = 60_000) {
|
|
|
36
55
|
headers: { connection: "close" },
|
|
37
56
|
}, (response) => {
|
|
38
57
|
response.resume();
|
|
39
|
-
response.once("end",
|
|
58
|
+
response.once("end", () => finish());
|
|
40
59
|
});
|
|
41
60
|
request.once("error", () => {
|
|
61
|
+
if (settled)
|
|
62
|
+
return;
|
|
42
63
|
if (Date.now() >= deadline) {
|
|
43
|
-
|
|
64
|
+
finish(new Error(`Next.js did not become ready on port ${nextPort} within ${timeoutMs / 1000}s.`));
|
|
44
65
|
return;
|
|
45
66
|
}
|
|
46
67
|
setTimeout(check, 250);
|
|
@@ -92,7 +113,7 @@ void (async () => {
|
|
|
92
113
|
});
|
|
93
114
|
server.on("upgrade", (req, socket, head) => proxy.ws(req, socket, head));
|
|
94
115
|
try {
|
|
95
|
-
await waitForNext(nextPort);
|
|
116
|
+
await waitForNext(nextPort, nextProcess);
|
|
96
117
|
}
|
|
97
118
|
catch (error) {
|
|
98
119
|
bridge.close();
|
package/dist/cli/index.js
CHANGED
|
@@ -26,6 +26,19 @@ program.name("lasso").description("Select UI in your running app, describe a cha
|
|
|
26
26
|
function projectEnv() {
|
|
27
27
|
return (0, project_1.readProjectEnv)(process.cwd());
|
|
28
28
|
}
|
|
29
|
+
const bridge = program.command("bridge").description("Manage the Lasso overlay bridge");
|
|
30
|
+
bridge.command("restart")
|
|
31
|
+
.description("Reset the active bridge connection and reconnect the overlay")
|
|
32
|
+
.option("--port <port>", "Bridge port", "3056")
|
|
33
|
+
.action(async (options) => {
|
|
34
|
+
const result = await (0, bridge_1.restartBridge)(Number(options.port));
|
|
35
|
+
if (!result.ok) {
|
|
36
|
+
console.error(chalk_1.default.red("✗") + ` ${result.error}`);
|
|
37
|
+
process.exitCode = 1;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
console.log(chalk_1.default.green("✓") + " Lasso bridge restarted. The overlay will reconnect automatically.");
|
|
41
|
+
});
|
|
29
42
|
// prettier-ignore
|
|
30
43
|
program.command("init")
|
|
31
44
|
.description("Register this app with your Lasso workspace, create lasso.config.json, and register a local .lasso domain with Lasso Host")
|
|
@@ -257,6 +270,34 @@ program.command("register [domain]")
|
|
|
257
270
|
}
|
|
258
271
|
});
|
|
259
272
|
// prettier-ignore
|
|
273
|
+
program.command("unregister [domain]")
|
|
274
|
+
.description("Remove the current project (or a named project) from Lasso Host")
|
|
275
|
+
.action(async (domainArg) => {
|
|
276
|
+
const cwd = process.cwd();
|
|
277
|
+
const config = (0, project_1.readProjectConfig)(cwd);
|
|
278
|
+
const registered = (0, registry_1.findByDirectory)((0, registry_1.loadRegistry)(), cwd);
|
|
279
|
+
const domain = (domainArg || config?.domain || registered?.domain || "").trim().toLowerCase();
|
|
280
|
+
if (!domain) {
|
|
281
|
+
console.error(chalk_1.default.red("✗") + " No .lasso project is registered for this directory. Pass a domain, for example: lasso unregister app.lasso");
|
|
282
|
+
process.exitCode = 1;
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (!(0, registry_1.validateDomain)(domain)) {
|
|
286
|
+
console.error(chalk_1.default.red("✗") + ` "${domain}" is not a valid .lasso domain.`);
|
|
287
|
+
process.exitCode = 1;
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const result = await (0, client_1.unregisterFromHost)(domain, (0, paths_1.hostProxyPort)(projectEnv()));
|
|
291
|
+
if (!result.ok) {
|
|
292
|
+
console.error(chalk_1.default.red("✗") + ` ${result.error}`);
|
|
293
|
+
process.exitCode = 1;
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
console.log(result.removed === false
|
|
297
|
+
? chalk_1.default.dim(`No registration found for ${domain}.`)
|
|
298
|
+
: chalk_1.default.green("✓") + ` Unregistered ${chalk_1.default.cyan(domain)}`);
|
|
299
|
+
});
|
|
300
|
+
// prettier-ignore
|
|
260
301
|
const auth = program.command("auth").description("Sign this machine into your Lasso account (the `lasso auth` OAuth flow)");
|
|
261
302
|
// prettier-ignore
|
|
262
303
|
auth.command("login").description("Sign in with the browser OAuth flow and store a Lasso credential locally").action(async () => {
|
package/package.json
CHANGED