@lasso-ai/cli 1.0.7 → 1.0.9
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 +6 -0
- package/dist/cli/host/client.d.ts +4 -0
- package/dist/cli/host/client.js +15 -0
- package/dist/cli/host/daemon.js +16 -0
- package/dist/cli/host/next-host-entry.js +26 -5
- package/dist/cli/index.js +41 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -197,6 +197,10 @@ The command closes the current overlay WebSocket, cancels any active agent
|
|
|
197
197
|
request, and lets the overlay reconnect to the existing bridge. Use
|
|
198
198
|
`--port <port>` when the bridge is running on a custom port.
|
|
199
199
|
|
|
200
|
+
When the project is served through Lasso Host, the command automatically
|
|
201
|
+
restarts the current registered project's runtime because Host assigns each
|
|
202
|
+
project its own bridge port.
|
|
203
|
+
|
|
200
204
|
---
|
|
201
205
|
|
|
202
206
|
## Local domains with Lasso Host
|
|
@@ -211,6 +215,8 @@ project is served at `http://app.lasso:<port>` or the secure
|
|
|
211
215
|
`*.lasso` domains, loopback-only, single instance).
|
|
212
216
|
- `lasso register [domain]` — register the current directory under a `.lasso`
|
|
213
217
|
domain (reuse, generate, or change one); never duplicates.
|
|
218
|
+
- `lasso unregister [domain]` — remove the current directory’s `.lasso`
|
|
219
|
+
registration, or pass a domain explicitly.
|
|
214
220
|
- `lasso projects` — list registered domains and running state.
|
|
215
221
|
- `lasso daemon install/uninstall` — attach a macOS LaunchAgent (auto-start on
|
|
216
222
|
login) and configure the system DNS resolver so bare `app.lasso` works.
|
package/dist/cli/host/client.js
CHANGED
|
@@ -11,6 +11,7 @@ exports.unregisterFromHost = unregisterFromHost;
|
|
|
11
11
|
exports.listHostProjects = listHostProjects;
|
|
12
12
|
exports.stopHostProject = stopHostProject;
|
|
13
13
|
exports.restartHost = restartHost;
|
|
14
|
+
exports.restartHostProject = restartHostProject;
|
|
14
15
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
15
16
|
const node_path_1 = __importDefault(require("node:path"));
|
|
16
17
|
const auth_1 = require("../auth");
|
|
@@ -139,3 +140,17 @@ async function restartHost(port) {
|
|
|
139
140
|
return { ok: false, error: "The host rejected the restart request." };
|
|
140
141
|
return { ok: true };
|
|
141
142
|
}
|
|
143
|
+
async function restartHostProject(domain, port) {
|
|
144
|
+
const health = await hostAlive(port);
|
|
145
|
+
if (!health)
|
|
146
|
+
return { ok: false, error: "Lasso Host is not running." };
|
|
147
|
+
const response = await (0, auth_1.fetchWithTimeout)(`http://127.0.0.1:${port}/_host/restart-project`, {
|
|
148
|
+
method: "POST",
|
|
149
|
+
headers: { "content-type": "application/json" },
|
|
150
|
+
body: JSON.stringify({ domain }),
|
|
151
|
+
});
|
|
152
|
+
const body = (await response.json());
|
|
153
|
+
if (!response.ok || !body.ok)
|
|
154
|
+
return { ok: false, error: body.error || "The Host rejected the project restart." };
|
|
155
|
+
return { ok: true };
|
|
156
|
+
}
|
package/dist/cli/host/daemon.js
CHANGED
|
@@ -216,6 +216,22 @@ class LassoHost {
|
|
|
216
216
|
writeJson(res, 200, { ok: true, stopped });
|
|
217
217
|
},
|
|
218
218
|
},
|
|
219
|
+
{
|
|
220
|
+
route: "POST /_host/restart-project",
|
|
221
|
+
handler: async (req, res) => {
|
|
222
|
+
const body = await readBody(req);
|
|
223
|
+
const domain = String(body.domain || "").trim().toLowerCase();
|
|
224
|
+
if (!domain)
|
|
225
|
+
return writeJson(res, 400, { ok: false, error: "A project domain is required." });
|
|
226
|
+
const runtime = this.running.get(domain);
|
|
227
|
+
if (runtime) {
|
|
228
|
+
runtime.child.kill("SIGTERM");
|
|
229
|
+
this.running.delete(domain);
|
|
230
|
+
this.log(`stopped ${domain} for bridge restart`);
|
|
231
|
+
}
|
|
232
|
+
writeJson(res, 200, { ok: true, domain, status: "stopped" });
|
|
233
|
+
},
|
|
234
|
+
},
|
|
219
235
|
];
|
|
220
236
|
}
|
|
221
237
|
health(res) {
|
|
@@ -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);
|
|
@@ -60,7 +81,7 @@ void (async () => {
|
|
|
60
81
|
env: { ...process.env, PORT: String(nextPort) },
|
|
61
82
|
stdio: "inherit",
|
|
62
83
|
});
|
|
63
|
-
const proxy = http_proxy_1.default.createProxyServer({ target: `http://127.0.0.1:${nextPort}`, selfHandleResponse: true, ws: true });
|
|
84
|
+
const proxy = http_proxy_1.default.createProxyServer({ target: `http://127.0.0.1:${nextPort}`, changeOrigin: true, selfHandleResponse: true, ws: true });
|
|
64
85
|
const bundlePath = node_path_1.default.resolve(__dirname, "../../overlay.js");
|
|
65
86
|
proxy.on("proxyRes", (proxyRes, _req, res) => {
|
|
66
87
|
const chunks = [];
|
|
@@ -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
|
@@ -33,8 +33,19 @@ bridge.command("restart")
|
|
|
33
33
|
.action(async (options) => {
|
|
34
34
|
const result = await (0, bridge_1.restartBridge)(Number(options.port));
|
|
35
35
|
if (!result.ok) {
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
const project = (0, registry_1.findByDirectory)((0, registry_1.loadRegistry)(), process.cwd());
|
|
37
|
+
if (!project) {
|
|
38
|
+
console.error(chalk_1.default.red("✗") + ` ${result.error}`);
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const hostResult = await (0, client_1.restartHostProject)(project.domain, (0, paths_1.hostProxyPort)(projectEnv()));
|
|
43
|
+
if (!hostResult.ok) {
|
|
44
|
+
console.error(chalk_1.default.red("✗") + ` ${hostResult.error}`);
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
console.log(chalk_1.default.green("✓") + ` Restarted the Host runtime for ${chalk_1.default.cyan(project.domain)}. Its bridge will start again on the next page request.`);
|
|
38
49
|
return;
|
|
39
50
|
}
|
|
40
51
|
console.log(chalk_1.default.green("✓") + " Lasso bridge restarted. The overlay will reconnect automatically.");
|
|
@@ -270,6 +281,34 @@ program.command("register [domain]")
|
|
|
270
281
|
}
|
|
271
282
|
});
|
|
272
283
|
// prettier-ignore
|
|
284
|
+
program.command("unregister [domain]")
|
|
285
|
+
.description("Remove the current project (or a named project) from Lasso Host")
|
|
286
|
+
.action(async (domainArg) => {
|
|
287
|
+
const cwd = process.cwd();
|
|
288
|
+
const config = (0, project_1.readProjectConfig)(cwd);
|
|
289
|
+
const registered = (0, registry_1.findByDirectory)((0, registry_1.loadRegistry)(), cwd);
|
|
290
|
+
const domain = (domainArg || config?.domain || registered?.domain || "").trim().toLowerCase();
|
|
291
|
+
if (!domain) {
|
|
292
|
+
console.error(chalk_1.default.red("✗") + " No .lasso project is registered for this directory. Pass a domain, for example: lasso unregister app.lasso");
|
|
293
|
+
process.exitCode = 1;
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (!(0, registry_1.validateDomain)(domain)) {
|
|
297
|
+
console.error(chalk_1.default.red("✗") + ` "${domain}" is not a valid .lasso domain.`);
|
|
298
|
+
process.exitCode = 1;
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const result = await (0, client_1.unregisterFromHost)(domain, (0, paths_1.hostProxyPort)(projectEnv()));
|
|
302
|
+
if (!result.ok) {
|
|
303
|
+
console.error(chalk_1.default.red("✗") + ` ${result.error}`);
|
|
304
|
+
process.exitCode = 1;
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
console.log(result.removed === false
|
|
308
|
+
? chalk_1.default.dim(`No registration found for ${domain}.`)
|
|
309
|
+
: chalk_1.default.green("✓") + ` Unregistered ${chalk_1.default.cyan(domain)}`);
|
|
310
|
+
});
|
|
311
|
+
// prettier-ignore
|
|
273
312
|
const auth = program.command("auth").description("Sign this machine into your Lasso account (the `lasso auth` OAuth flow)");
|
|
274
313
|
// prettier-ignore
|
|
275
314
|
auth.command("login").description("Sign in with the browser OAuth flow and store a Lasso credential locally").action(async () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lasso-ai/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "Select any part of your running app, describe a change, and let AI edit the real source code.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/cli/index.js",
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
"@iconify-icons/logos": "^2.0.1",
|
|
77
77
|
"@iconify-json/logos": "^1.2.14",
|
|
78
78
|
"@iconify/utils": "^3.1.7",
|
|
79
|
+
"@lasso-ai/cli": "link:",
|
|
79
80
|
"chalk": "^6.0.0",
|
|
80
81
|
"commander": "^15.0.0",
|
|
81
82
|
"http-proxy": "^1.18.1",
|