@lasso-ai/cli 1.0.8 → 1.0.10
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 +4 -0
- package/dist/cli/host/client.d.ts +4 -0
- package/dist/cli/host/client.js +15 -0
- package/dist/cli/host/daemon.d.ts +1 -0
- package/dist/cli/host/daemon.js +22 -1
- package/dist/cli/host/next-host-entry.js +1 -1
- package/dist/cli/host/runtime.d.ts +3 -1
- package/dist/cli/host/runtime.js +42 -1
- package/dist/cli/index.js +13 -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
|
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
|
@@ -51,6 +51,7 @@ class LassoHost {
|
|
|
51
51
|
dnsHandle = null;
|
|
52
52
|
resolver;
|
|
53
53
|
registry;
|
|
54
|
+
cleanupOnStart = new Set();
|
|
54
55
|
running = new Map();
|
|
55
56
|
starting = new Map();
|
|
56
57
|
crashLog = new Map();
|
|
@@ -63,6 +64,8 @@ class LassoHost {
|
|
|
63
64
|
this.secureServer.on("upgrade", (req, socket, head) => this.handleUpgrade(req, socket, head));
|
|
64
65
|
this.resolver = (0, dns_1.createDomainResolver)();
|
|
65
66
|
this.registry = (0, registry_1.loadRegistry)();
|
|
67
|
+
for (const domain of Object.keys(this.registry))
|
|
68
|
+
this.cleanupOnStart.add(domain);
|
|
66
69
|
this.log(`host starting (version ${opts.version || "unknown"})`);
|
|
67
70
|
}
|
|
68
71
|
log(message) {
|
|
@@ -119,7 +122,7 @@ class LassoHost {
|
|
|
119
122
|
}
|
|
120
123
|
this.crashLog.set(domain, crashes);
|
|
121
124
|
const startup = (async () => {
|
|
122
|
-
const result = await (0, runtime_1.startProjectRuntime)(project.directory);
|
|
125
|
+
const result = await (0, runtime_1.startProjectRuntime)(project.directory, { cleanupExisting: this.cleanupOnStart.delete(domain) });
|
|
123
126
|
if (!result.ok || !result.runtime) {
|
|
124
127
|
this.crashLog.set(domain, [...crashes, Date.now()]);
|
|
125
128
|
throw new Error(result.error || `Could not start ${domain}.`);
|
|
@@ -210,12 +213,30 @@ class LassoHost {
|
|
|
210
213
|
for (const [domain, runtime] of this.running) {
|
|
211
214
|
runtime.child.kill("SIGTERM");
|
|
212
215
|
this.running.delete(domain);
|
|
216
|
+
this.cleanupOnStart.add(domain);
|
|
213
217
|
stopped.push(domain);
|
|
214
218
|
this.log(`stopped ${domain} for restart`);
|
|
215
219
|
}
|
|
216
220
|
writeJson(res, 200, { ok: true, stopped });
|
|
217
221
|
},
|
|
218
222
|
},
|
|
223
|
+
{
|
|
224
|
+
route: "POST /_host/restart-project",
|
|
225
|
+
handler: async (req, res) => {
|
|
226
|
+
const body = await readBody(req);
|
|
227
|
+
const domain = String(body.domain || "").trim().toLowerCase();
|
|
228
|
+
if (!domain)
|
|
229
|
+
return writeJson(res, 400, { ok: false, error: "A project domain is required." });
|
|
230
|
+
const runtime = this.running.get(domain);
|
|
231
|
+
this.cleanupOnStart.add(domain);
|
|
232
|
+
if (runtime) {
|
|
233
|
+
runtime.child.kill("SIGTERM");
|
|
234
|
+
this.running.delete(domain);
|
|
235
|
+
this.log(`stopped ${domain} for bridge restart`);
|
|
236
|
+
}
|
|
237
|
+
writeJson(res, 200, { ok: true, domain, status: "stopped" });
|
|
238
|
+
},
|
|
239
|
+
},
|
|
219
240
|
];
|
|
220
241
|
}
|
|
221
242
|
health(res) {
|
|
@@ -81,7 +81,7 @@ void (async () => {
|
|
|
81
81
|
env: { ...process.env, PORT: String(nextPort) },
|
|
82
82
|
stdio: "inherit",
|
|
83
83
|
});
|
|
84
|
-
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 });
|
|
85
85
|
const bundlePath = node_path_1.default.resolve(__dirname, "../../overlay.js");
|
|
86
86
|
proxy.on("proxyRes", (proxyRes, _req, res) => {
|
|
87
87
|
const chunks = [];
|
|
@@ -20,4 +20,6 @@ export interface StartRuntimeResult {
|
|
|
20
20
|
* answers HTTP before returning. Reuses Lasso's existing framework detection —
|
|
21
21
|
* the Host never guesses a package manager or hardcodes a runtime.
|
|
22
22
|
*/
|
|
23
|
-
export declare function startProjectRuntime(directory: string
|
|
23
|
+
export declare function startProjectRuntime(directory: string, options?: {
|
|
24
|
+
cleanupExisting?: boolean;
|
|
25
|
+
}): Promise<StartRuntimeResult>;
|
package/dist/cli/host/runtime.js
CHANGED
|
@@ -11,10 +11,12 @@ const node_net_1 = __importDefault(require("node:net"));
|
|
|
11
11
|
const node_http_1 = __importDefault(require("node:http"));
|
|
12
12
|
const node_path_1 = __importDefault(require("node:path"));
|
|
13
13
|
const node_child_process_1 = require("node:child_process");
|
|
14
|
+
const node_util_1 = require("node:util");
|
|
14
15
|
const framework_1 = require("../utils/framework");
|
|
15
16
|
const paths_1 = require("./paths");
|
|
16
17
|
const READY_TIMEOUT_MS = 60_000;
|
|
17
18
|
const READY_POLL_MS = 500;
|
|
19
|
+
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
18
20
|
function pickFreePort() {
|
|
19
21
|
return new Promise((resolve, reject) => {
|
|
20
22
|
const server = node_net_1.default.createServer();
|
|
@@ -88,18 +90,57 @@ async function waitUntilReady(port) {
|
|
|
88
90
|
}
|
|
89
91
|
return false;
|
|
90
92
|
}
|
|
93
|
+
async function stopExistingNextDev(directory) {
|
|
94
|
+
if (process.platform === "win32")
|
|
95
|
+
return;
|
|
96
|
+
const lockPath = node_path_1.default.join(directory, ".next", "dev", "lock");
|
|
97
|
+
let pid = 0;
|
|
98
|
+
try {
|
|
99
|
+
const lock = JSON.parse(node_fs_1.default.readFileSync(lockPath, "utf8"));
|
|
100
|
+
pid = Number(lock.pid);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (!Number.isInteger(pid) || pid <= 0 || pid === process.pid)
|
|
106
|
+
return;
|
|
107
|
+
try {
|
|
108
|
+
process.kill(pid, 0);
|
|
109
|
+
const { stdout } = await execFileAsync("ps", ["-p", String(pid), "-o", "command="], { maxBuffer: 16 * 1024 });
|
|
110
|
+
if (!/\bnext(?:-dev)?\b|next.*\bdev\b/i.test(stdout))
|
|
111
|
+
return;
|
|
112
|
+
process.kill(pid, "SIGTERM");
|
|
113
|
+
for (let attempt = 0; attempt < 20; attempt++) {
|
|
114
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
115
|
+
try {
|
|
116
|
+
process.kill(pid, 0);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
log(directory, `stopped previous Next.js process ${pid}`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
process.kill(pid, "SIGKILL");
|
|
124
|
+
log(directory, `force-stopped previous Next.js process ${pid}`);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// The lock may be stale or the process may have exited between checks.
|
|
128
|
+
}
|
|
129
|
+
}
|
|
91
130
|
/**
|
|
92
131
|
* Starts the project's development server on a free port and waits until it
|
|
93
132
|
* answers HTTP before returning. Reuses Lasso's existing framework detection —
|
|
94
133
|
* the Host never guesses a package manager or hardcodes a runtime.
|
|
95
134
|
*/
|
|
96
|
-
async function startProjectRuntime(directory) {
|
|
135
|
+
async function startProjectRuntime(directory, options = {}) {
|
|
97
136
|
const port = await pickFreePort();
|
|
98
137
|
const bridgePort = await pickFreePort();
|
|
99
138
|
const plan = spawnCommand(directory, port, bridgePort);
|
|
100
139
|
if (!plan) {
|
|
101
140
|
return { ok: false, error: `${node_path_1.default.basename(directory)} is not a Vite or Next.js project, so Lasso Host can't start it automatically. Run its dev server yourself.` };
|
|
102
141
|
}
|
|
142
|
+
if (options.cleanupExisting && plan.framework === "next")
|
|
143
|
+
await stopExistingNextDev(directory);
|
|
103
144
|
const child = (0, node_child_process_1.spawn)(plan.command, plan.args, {
|
|
104
145
|
cwd: directory,
|
|
105
146
|
env: { ...process.env, PORT: String(port) },
|
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.");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lasso-ai/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
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",
|