@alook/cli 0.0.135 → 0.0.137
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/dist/index.js +701 -756
- package/dist/session-runner.js +3 -9
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14561,8 +14561,7 @@ var DaemonPushMessageSchema = exports_external.discriminatedUnion("type", [
|
|
|
14561
14561
|
exports_external.object({ type: exports_external.literal("daemon.evict"), workspaceId: exports_external.string() }),
|
|
14562
14562
|
exports_external.object({ type: exports_external.literal("daemon.update"), version: exports_external.string() }),
|
|
14563
14563
|
exports_external.object({ type: exports_external.literal("daemon.rescan") }),
|
|
14564
|
-
exports_external.object({ type: exports_external.literal("daemon.kill"), workspaceId: exports_external.string(), agentId: exports_external.string().min(1), taskId: exports_external.string(), targetTaskId: exports_external.string() })
|
|
14565
|
-
exports_external.object({ type: exports_external.literal("daemon.workspace_added"), workspaceId: exports_external.string(), workspaceName: exports_external.string(), token: exports_external.string() })
|
|
14564
|
+
exports_external.object({ type: exports_external.literal("daemon.kill"), workspaceId: exports_external.string(), agentId: exports_external.string().min(1), taskId: exports_external.string(), targetTaskId: exports_external.string() })
|
|
14566
14565
|
]);
|
|
14567
14566
|
var RegisterResponseSchema = exports_external.object({
|
|
14568
14567
|
runtimes: exports_external.array(exports_external.object({ id: exports_external.string() }))
|
|
@@ -14592,9 +14591,6 @@ var RegisterDaemonRequestSchema = exports_external.object({
|
|
|
14592
14591
|
workspaces_root: exports_external.string().optional().default(""),
|
|
14593
14592
|
runtimes: exports_external.array(DaemonRuntimeItemSchema).min(1)
|
|
14594
14593
|
});
|
|
14595
|
-
var BindWorkspaceRequestSchema = exports_external.object({
|
|
14596
|
-
workspace_id: exports_external.string().min(1)
|
|
14597
|
-
});
|
|
14598
14594
|
var DeregisterRequestSchema = exports_external.object({
|
|
14599
14595
|
daemon_id: exports_external.string().min(1)
|
|
14600
14596
|
});
|
|
@@ -14860,7 +14856,7 @@ var UpdateMemberRequestSchema = exports_external.object({
|
|
|
14860
14856
|
});
|
|
14861
14857
|
var CreateWorkspaceRequestSchema = exports_external.object({
|
|
14862
14858
|
name: exports_external.string().min(1, "name is required"),
|
|
14863
|
-
slug: exports_external.string().
|
|
14859
|
+
slug: exports_external.string().optional().default("")
|
|
14864
14860
|
});
|
|
14865
14861
|
var UpdateWorkspaceRequestSchema = exports_external.object({
|
|
14866
14862
|
name: exports_external.string().min(1, "name is required").max(100).trim().optional(),
|
|
@@ -16358,6 +16354,7 @@ var workspace = sqliteTable("workspace", {
|
|
|
16358
16354
|
id: text("id").primaryKey().$defaultFn(() => "sp_" + nanoid3()),
|
|
16359
16355
|
name: text("name").notNull(),
|
|
16360
16356
|
slug: text("slug").unique().notNull(),
|
|
16357
|
+
onboarded: integer2("onboarded").notNull().default(0),
|
|
16361
16358
|
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString()),
|
|
16362
16359
|
updatedAt: text("updated_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
16363
16360
|
});
|
|
@@ -16971,6 +16968,9 @@ function cmdPrefix() {
|
|
|
16971
16968
|
|
|
16972
16969
|
// lib/activate.ts
|
|
16973
16970
|
import { hostname as hostname4 } from "os";
|
|
16971
|
+
import { spawn as spawn6 } from "child_process";
|
|
16972
|
+
import { openSync as openSync2, closeSync as closeSync2, mkdirSync as mkdirSync9 } from "fs";
|
|
16973
|
+
import { dirname as dirname4 } from "path";
|
|
16974
16974
|
|
|
16975
16975
|
// lib/config.ts
|
|
16976
16976
|
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
@@ -17000,13 +17000,9 @@ function loadCLIConfigForProfile(profile) {
|
|
|
17000
17000
|
session_token: cfg.session_token,
|
|
17001
17001
|
watched_workspaces: cfg.watched_workspaces || []
|
|
17002
17002
|
};
|
|
17003
|
-
const legacy = cfg.machine_token;
|
|
17004
|
-
if (legacy && !result.watched_workspaces.some((w) => w.token === legacy)) {
|
|
17005
|
-
result.watched_workspaces.push({ id: null, name: null, token: legacy, status: "registered", agent_ids: [] });
|
|
17006
|
-
}
|
|
17007
17003
|
for (const ws of result.watched_workspaces) {
|
|
17008
17004
|
if (!ws.status)
|
|
17009
|
-
ws.status = ws.id ? "active" : "
|
|
17005
|
+
ws.status = ws.id ? "active" : "deleted";
|
|
17010
17006
|
}
|
|
17011
17007
|
return result;
|
|
17012
17008
|
}
|
|
@@ -17309,556 +17305,176 @@ function releaseDaemonPid(profile) {
|
|
|
17309
17305
|
removePidFileIfMatches(process.pid, profile);
|
|
17310
17306
|
}
|
|
17311
17307
|
|
|
17312
|
-
//
|
|
17313
|
-
|
|
17314
|
-
|
|
17315
|
-
|
|
17316
|
-
|
|
17317
|
-
execSync(check2, { stdio: "ignore" });
|
|
17318
|
-
return true;
|
|
17319
|
-
} catch {
|
|
17320
|
-
return false;
|
|
17308
|
+
// daemon/client.ts
|
|
17309
|
+
class DaemonClient {
|
|
17310
|
+
baseURL;
|
|
17311
|
+
constructor(baseURL) {
|
|
17312
|
+
this.baseURL = baseURL;
|
|
17321
17313
|
}
|
|
17322
|
-
|
|
17323
|
-
|
|
17324
|
-
|
|
17325
|
-
|
|
17326
|
-
|
|
17327
|
-
|
|
17314
|
+
async request(method, path, token, body) {
|
|
17315
|
+
const headers = {
|
|
17316
|
+
"Content-Type": "application/json",
|
|
17317
|
+
Authorization: `Bearer ${token}`
|
|
17318
|
+
};
|
|
17319
|
+
const MAX_RETRIES = 3;
|
|
17320
|
+
const BASE_DELAY_MS = 500;
|
|
17321
|
+
let lastError;
|
|
17322
|
+
for (let attempt = 0;attempt <= MAX_RETRIES; attempt++) {
|
|
17328
17323
|
try {
|
|
17329
|
-
|
|
17330
|
-
|
|
17331
|
-
|
|
17324
|
+
const res = await fetch(this.baseURL + path, {
|
|
17325
|
+
method,
|
|
17326
|
+
headers,
|
|
17327
|
+
body: body ? JSON.stringify(body) : undefined
|
|
17328
|
+
});
|
|
17329
|
+
if (!res.ok)
|
|
17330
|
+
throw new Error(`HTTP ${res.status}: ${await res.text()}`);
|
|
17331
|
+
if (res.status === 204)
|
|
17332
|
+
return;
|
|
17333
|
+
return res.json();
|
|
17334
|
+
} catch (e) {
|
|
17335
|
+
if (e instanceof TypeError) {
|
|
17336
|
+
lastError = e;
|
|
17337
|
+
if (attempt < MAX_RETRIES) {
|
|
17338
|
+
await new Promise((r) => setTimeout(r, BASE_DELAY_MS * 2 ** attempt));
|
|
17339
|
+
continue;
|
|
17340
|
+
}
|
|
17341
|
+
}
|
|
17342
|
+
throw e;
|
|
17343
|
+
}
|
|
17332
17344
|
}
|
|
17345
|
+
throw lastError;
|
|
17333
17346
|
}
|
|
17334
|
-
|
|
17335
|
-
|
|
17336
|
-
|
|
17337
|
-
// lib/activate.ts
|
|
17338
|
-
async function activateAndSave(opts) {
|
|
17339
|
-
const { token, serverUrl, profile } = opts;
|
|
17340
|
-
console.log("Scanning for AI runtimes...");
|
|
17341
|
-
const runtimes = detectRuntimes();
|
|
17342
|
-
if (runtimes.length === 0) {
|
|
17343
|
-
console.error("Error: no runtimes found. Install claude, codex, or opencode first.");
|
|
17344
|
-
process.exit(1);
|
|
17347
|
+
async register(token, body) {
|
|
17348
|
+
const raw = await this.request("POST", "/api/daemon/register", token, body);
|
|
17349
|
+
return RegisterResponseSchema.parse(raw);
|
|
17345
17350
|
}
|
|
17346
|
-
|
|
17347
|
-
|
|
17348
|
-
|
|
17349
|
-
let activateResp;
|
|
17350
|
-
try {
|
|
17351
|
-
const res = await fetch(`${serverUrl}/api/machine-tokens/activate`, {
|
|
17352
|
-
method: "POST",
|
|
17353
|
-
headers: { "Content-Type": "application/json" },
|
|
17354
|
-
body: JSON.stringify({ token, hostname: host, runtimes })
|
|
17351
|
+
heartbeat(token, daemonId) {
|
|
17352
|
+
return this.request("POST", "/api/daemon/heartbeat", token, {
|
|
17353
|
+
daemon_id: daemonId
|
|
17355
17354
|
});
|
|
17356
|
-
if (!res.ok) {
|
|
17357
|
-
const text2 = await res.text();
|
|
17358
|
-
console.error(`Error: registration failed (${res.status}): ${text2}`);
|
|
17359
|
-
process.exit(1);
|
|
17360
|
-
}
|
|
17361
|
-
activateResp = await res.json();
|
|
17362
|
-
} catch (err) {
|
|
17363
|
-
console.error(`Error: failed to activate: ${err instanceof Error ? err.message : err}`);
|
|
17364
|
-
process.exit(1);
|
|
17365
17355
|
}
|
|
17366
|
-
|
|
17367
|
-
|
|
17368
|
-
|
|
17369
|
-
|
|
17356
|
+
sweep(token, daemonId) {
|
|
17357
|
+
return this.request("POST", "/api/daemon/sweep", token, {
|
|
17358
|
+
daemon_id: daemonId
|
|
17359
|
+
});
|
|
17370
17360
|
}
|
|
17371
|
-
|
|
17372
|
-
|
|
17373
|
-
|
|
17374
|
-
|
|
17375
|
-
|
|
17376
|
-
|
|
17377
|
-
|
|
17378
|
-
|
|
17379
|
-
|
|
17380
|
-
|
|
17381
|
-
|
|
17382
|
-
|
|
17383
|
-
|
|
17361
|
+
deregister(token, daemonId) {
|
|
17362
|
+
return this.request("POST", "/api/daemon/deregister", token, {
|
|
17363
|
+
daemon_id: daemonId
|
|
17364
|
+
});
|
|
17365
|
+
}
|
|
17366
|
+
async poll(token, daemonId, maxTasks, cliVersion) {
|
|
17367
|
+
const raw = await this.request("POST", "/api/daemon/tasks/poll", token, { daemon_id: daemonId, max_tasks: maxTasks, ...cliVersion && { cli_version: cliVersion } });
|
|
17368
|
+
const resp = PollResponseSchema.parse(raw);
|
|
17369
|
+
return {
|
|
17370
|
+
tasks: resp.tasks,
|
|
17371
|
+
evicted: resp.evicted ?? false,
|
|
17372
|
+
pending_update: resp.pending_update,
|
|
17373
|
+
pending_rescan: resp.pending_rescan,
|
|
17374
|
+
file_requests: resp.file_requests,
|
|
17375
|
+
meetings: resp.meetings
|
|
17376
|
+
};
|
|
17377
|
+
}
|
|
17378
|
+
startTask(token, taskId) {
|
|
17379
|
+
return this.request("POST", `/api/daemon/tasks/${taskId}/start`, token);
|
|
17380
|
+
}
|
|
17381
|
+
completeTask(token, taskId, body) {
|
|
17382
|
+
return this.request("POST", `/api/daemon/tasks/${taskId}/complete`, token, body);
|
|
17383
|
+
}
|
|
17384
|
+
failTask(token, taskId, error51) {
|
|
17385
|
+
return this.request("POST", `/api/daemon/tasks/${taskId}/fail`, token, {
|
|
17386
|
+
error: error51
|
|
17387
|
+
});
|
|
17388
|
+
}
|
|
17389
|
+
supersedeTask(token, taskId) {
|
|
17390
|
+
return this.request("POST", `/api/daemon/tasks/${taskId}/supersede`, token);
|
|
17391
|
+
}
|
|
17392
|
+
async getArtifactMeta(token, artifactId, workspaceId) {
|
|
17393
|
+
return this.request("GET", `/api/artifacts/${artifactId}?workspace_id=${encodeURIComponent(workspaceId)}`, token);
|
|
17394
|
+
}
|
|
17395
|
+
async downloadArtifact(token, artifactId, workspaceId) {
|
|
17396
|
+
const MAX_RETRIES = 3;
|
|
17397
|
+
const BASE_DELAY_MS = 500;
|
|
17398
|
+
let lastError;
|
|
17399
|
+
for (let attempt = 0;attempt <= MAX_RETRIES; attempt++) {
|
|
17400
|
+
try {
|
|
17401
|
+
const res = await fetch(`${this.baseURL}/api/artifacts/${artifactId}/content?workspace_id=${encodeURIComponent(workspaceId)}`, { headers: { Authorization: `Bearer ${token}` } });
|
|
17402
|
+
if (!res.ok) {
|
|
17403
|
+
throw new Error(`artifact download failed: HTTP ${res.status}`);
|
|
17404
|
+
}
|
|
17405
|
+
return res.arrayBuffer();
|
|
17406
|
+
} catch (e) {
|
|
17407
|
+
if (e instanceof TypeError) {
|
|
17408
|
+
lastError = e;
|
|
17409
|
+
if (attempt < MAX_RETRIES) {
|
|
17410
|
+
await new Promise((r) => setTimeout(r, BASE_DELAY_MS * 2 ** attempt));
|
|
17411
|
+
continue;
|
|
17412
|
+
}
|
|
17413
|
+
}
|
|
17414
|
+
throw e;
|
|
17415
|
+
}
|
|
17384
17416
|
}
|
|
17385
|
-
|
|
17386
|
-
|
|
17387
|
-
|
|
17388
|
-
|
|
17417
|
+
throw lastError;
|
|
17418
|
+
}
|
|
17419
|
+
reportMessages(token, taskId, messages) {
|
|
17420
|
+
return this.request("POST", `/api/daemon/tasks/${taskId}/messages`, token, { messages });
|
|
17421
|
+
}
|
|
17422
|
+
reportFileData(token, body) {
|
|
17423
|
+
return this.request("POST", "/api/daemon/workspace/report", token, body);
|
|
17424
|
+
}
|
|
17425
|
+
syncSkills(token, body) {
|
|
17426
|
+
return this.request("POST", "/api/daemon/skills/sync", token, body);
|
|
17389
17427
|
}
|
|
17390
|
-
return {
|
|
17391
|
-
daemonId: activateResp.daemon_id,
|
|
17392
|
-
tokenStatus: activateResp.token_status
|
|
17393
|
-
};
|
|
17394
17428
|
}
|
|
17395
17429
|
|
|
17396
|
-
//
|
|
17397
|
-
|
|
17398
|
-
|
|
17399
|
-
|
|
17400
|
-
|
|
17401
|
-
|
|
17402
|
-
|
|
17403
|
-
|
|
17404
|
-
|
|
17405
|
-
|
|
17406
|
-
|
|
17407
|
-
|
|
17408
|
-
|
|
17409
|
-
|
|
17410
|
-
|
|
17411
|
-
|
|
17412
|
-
|
|
17413
|
-
|
|
17414
|
-
me = await client.getJSON("/api/me");
|
|
17415
|
-
} catch (err) {
|
|
17416
|
-
console.error(`Error: failed to verify token: ${err instanceof Error ? err.message : err}`);
|
|
17417
|
-
process.exit(1);
|
|
17430
|
+
// daemon/health.ts
|
|
17431
|
+
import { createServer } from "http";
|
|
17432
|
+
var DEFAULT_HEALTH_PORT = Number(process.env.ALOOK_HEALTH_PORT) || 19514;
|
|
17433
|
+
function createHealthServer(port = DEFAULT_HEALTH_PORT) {
|
|
17434
|
+
let runtimeCount = 0;
|
|
17435
|
+
const startTime = Date.now();
|
|
17436
|
+
const server = createServer((req, res) => {
|
|
17437
|
+
if (req.url === "/health") {
|
|
17438
|
+
const uptimeSec = Math.floor((Date.now() - startTime) / 1000);
|
|
17439
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
17440
|
+
res.end(JSON.stringify({
|
|
17441
|
+
status: "ok",
|
|
17442
|
+
uptime: `${uptimeSec}s`,
|
|
17443
|
+
runtimes: runtimeCount
|
|
17444
|
+
}));
|
|
17445
|
+
} else {
|
|
17446
|
+
res.writeHead(404);
|
|
17447
|
+
res.end();
|
|
17418
17448
|
}
|
|
17419
|
-
const result = await activateAndSave({ token, serverUrl, profile });
|
|
17420
|
-
console.log(`
|
|
17421
|
-
Registered as ${me.email}`);
|
|
17422
|
-
console.log(`Machine: ${result.daemonId} (status: ${result.tokenStatus})`);
|
|
17423
|
-
console.log(`Workspace binding will happen when you launch a company.`);
|
|
17424
17449
|
});
|
|
17425
|
-
|
|
17450
|
+
server.listen(port, "127.0.0.1");
|
|
17451
|
+
return {
|
|
17452
|
+
server,
|
|
17453
|
+
setRuntimeCount(n) {
|
|
17454
|
+
runtimeCount = n;
|
|
17455
|
+
}
|
|
17456
|
+
};
|
|
17426
17457
|
}
|
|
17427
17458
|
|
|
17428
|
-
//
|
|
17429
|
-
import {
|
|
17430
|
-
import {
|
|
17431
|
-
|
|
17432
|
-
|
|
17433
|
-
|
|
17434
|
-
|
|
17435
|
-
|
|
17436
|
-
|
|
17437
|
-
const args = process.platform === "win32" ? ["", url2] : [url2];
|
|
17438
|
-
spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
|
|
17439
|
-
}
|
|
17440
|
-
} catch {}
|
|
17459
|
+
// daemon/agent/claude.ts
|
|
17460
|
+
import { spawn } from "child_process";
|
|
17461
|
+
import { createInterface } from "readline";
|
|
17462
|
+
|
|
17463
|
+
// daemon/kill-tree.ts
|
|
17464
|
+
import { execSync } from "child_process";
|
|
17465
|
+
var log3 = createLogger2({ module: "kill-tree" });
|
|
17466
|
+
function killGraceMs() {
|
|
17467
|
+
return Number(process.env.ALOOK_KILL_GRACE_MS) || 2000;
|
|
17441
17468
|
}
|
|
17442
|
-
|
|
17443
|
-
|
|
17444
|
-
|
|
17445
|
-
|
|
17446
|
-
|
|
17447
|
-
|
|
17448
|
-
|
|
17449
|
-
|
|
17450
|
-
|
|
17451
|
-
if (existing) {
|
|
17452
|
-
existing.status = "active";
|
|
17453
|
-
existing.name = sw.name;
|
|
17454
|
-
} else {
|
|
17455
|
-
watched.push({ id: sw.id, name: sw.name, token: "", status: "active", agent_ids: [] });
|
|
17456
|
-
}
|
|
17457
|
-
}
|
|
17458
|
-
for (const w of watched) {
|
|
17459
|
-
if (w.id && !serverIds.has(w.id) && w.status !== "registered") {
|
|
17460
|
-
w.status = "deleted";
|
|
17461
|
-
}
|
|
17462
|
-
}
|
|
17463
|
-
saveCLIConfigForProfile(profile, {
|
|
17464
|
-
server_url: cfg.server_url,
|
|
17465
|
-
session_token: sessionToken ?? cfg.session_token,
|
|
17466
|
-
watched_workspaces: watched
|
|
17467
|
-
});
|
|
17468
|
-
}
|
|
17469
|
-
async function pollAndActivate(opts) {
|
|
17470
|
-
const { deviceCode, expiresIn, serverUrl, profile } = opts;
|
|
17471
|
-
let interval = opts.interval;
|
|
17472
|
-
const expiresAt = Date.now() + expiresIn * 1000;
|
|
17473
|
-
let tokenResp;
|
|
17474
|
-
while (Date.now() < expiresAt) {
|
|
17475
|
-
await sleep(interval);
|
|
17476
|
-
try {
|
|
17477
|
-
const res = await fetch(`${serverUrl}/api/auth/device/token`, {
|
|
17478
|
-
method: "POST",
|
|
17479
|
-
headers: { "Content-Type": "application/json" },
|
|
17480
|
-
body: JSON.stringify({
|
|
17481
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
17482
|
-
device_code: deviceCode,
|
|
17483
|
-
client_id: DEVICE_CLIENT_ID
|
|
17484
|
-
})
|
|
17485
|
-
});
|
|
17486
|
-
if (res.ok) {
|
|
17487
|
-
tokenResp = await res.json();
|
|
17488
|
-
break;
|
|
17489
|
-
}
|
|
17490
|
-
const errBody = await res.json();
|
|
17491
|
-
if (errBody.error === "slow_down") {
|
|
17492
|
-
interval += 5000;
|
|
17493
|
-
} else if (errBody.error === "authorization_pending") {} else if (errBody.error === "expired_token") {
|
|
17494
|
-
console.error("Error: device code expired. Please run login again.");
|
|
17495
|
-
process.exit(1);
|
|
17496
|
-
} else if (errBody.error === "access_denied") {
|
|
17497
|
-
console.error("Error: authorization was denied.");
|
|
17498
|
-
process.exit(1);
|
|
17499
|
-
} else {
|
|
17500
|
-
console.error(`Error: unexpected error: ${errBody.error_description || errBody.error}`);
|
|
17501
|
-
process.exit(1);
|
|
17502
|
-
}
|
|
17503
|
-
} catch {
|
|
17504
|
-
console.error("Error: network request failed during polling.");
|
|
17505
|
-
process.exit(1);
|
|
17506
|
-
}
|
|
17507
|
-
}
|
|
17508
|
-
if (!tokenResp) {
|
|
17509
|
-
console.error("Error: device code expired (timed out). Please run login again.");
|
|
17510
|
-
process.exit(1);
|
|
17511
|
-
}
|
|
17512
|
-
const sessionToken = tokenResp.access_token;
|
|
17513
|
-
const client = new APIClient(serverUrl, sessionToken);
|
|
17514
|
-
let email3 = "";
|
|
17515
|
-
try {
|
|
17516
|
-
const me = await client.getJSON("/api/me");
|
|
17517
|
-
email3 = me.email;
|
|
17518
|
-
} catch {}
|
|
17519
|
-
let serverWorkspaces = [];
|
|
17520
|
-
try {
|
|
17521
|
-
serverWorkspaces = await client.getJSON("/api/workspaces");
|
|
17522
|
-
} catch {}
|
|
17523
|
-
syncWorkspacesToConfig(serverWorkspaces, profile, sessionToken);
|
|
17524
|
-
const existingWorkspaceId = serverWorkspaces.length > 0 ? serverWorkspaces[0].id : "";
|
|
17525
|
-
const mtUrl = existingWorkspaceId ? `/api/machine-tokens?workspace_id=${existingWorkspaceId}` : "/api/machine-tokens";
|
|
17526
|
-
let machineToken2;
|
|
17527
|
-
try {
|
|
17528
|
-
const mtResp = await client.postJSON(mtUrl);
|
|
17529
|
-
machineToken2 = mtResp.token;
|
|
17530
|
-
} catch {
|
|
17531
|
-
process.exit(1);
|
|
17532
|
-
}
|
|
17533
|
-
const result = await activateAndSave({ token: machineToken2, serverUrl, profile });
|
|
17534
|
-
if (email3) {
|
|
17535
|
-
console.log(`
|
|
17536
|
-
Logged in as ${email3}`);
|
|
17537
|
-
}
|
|
17538
|
-
console.log(`Machine: ${result.daemonId} (status: ${result.tokenStatus})`);
|
|
17539
|
-
console.log(`Workspace binding will happen when you launch a company.`);
|
|
17540
|
-
}
|
|
17541
|
-
if (process.argv.includes("--__login-poll")) {
|
|
17542
|
-
const idx = process.argv.indexOf("--__login-poll");
|
|
17543
|
-
let data;
|
|
17544
|
-
try {
|
|
17545
|
-
data = JSON.parse(process.argv[idx + 1]);
|
|
17546
|
-
} catch {
|
|
17547
|
-
console.error("Error: invalid poll data");
|
|
17548
|
-
process.exit(1);
|
|
17549
|
-
}
|
|
17550
|
-
pollAndActivate(data).catch(() => process.exit(1));
|
|
17551
|
-
}
|
|
17552
|
-
async function checkExistingAuth(serverUrl, profile) {
|
|
17553
|
-
const config2 = loadCLIConfigForProfile(profile);
|
|
17554
|
-
const sessionToken = config2.session_token;
|
|
17555
|
-
const workspaces = config2.watched_workspaces || [];
|
|
17556
|
-
const ws = workspaces[0];
|
|
17557
|
-
const authToken = sessionToken || ws?.token;
|
|
17558
|
-
if (!authToken) {
|
|
17559
|
-
return { valid: false };
|
|
17560
|
-
}
|
|
17561
|
-
try {
|
|
17562
|
-
const res = await fetch(`${serverUrl}/api/workspaces`, {
|
|
17563
|
-
headers: { Authorization: `Bearer ${authToken}` }
|
|
17564
|
-
});
|
|
17565
|
-
if (!res.ok) {
|
|
17566
|
-
return { valid: false };
|
|
17567
|
-
}
|
|
17568
|
-
const serverWorkspaces = await res.json();
|
|
17569
|
-
const hasValidWorkspace = workspaces.some((w) => w.id && w.status !== "deleted");
|
|
17570
|
-
if (!hasValidWorkspace && serverWorkspaces.length > 0) {
|
|
17571
|
-
syncWorkspacesToConfig(serverWorkspaces, profile);
|
|
17572
|
-
}
|
|
17573
|
-
let email3;
|
|
17574
|
-
try {
|
|
17575
|
-
const meRes = await fetch(`${serverUrl}/api/me`, {
|
|
17576
|
-
headers: { Authorization: `Bearer ${authToken}` }
|
|
17577
|
-
});
|
|
17578
|
-
if (meRes.ok) {
|
|
17579
|
-
const me = await meRes.json();
|
|
17580
|
-
email3 = me.email;
|
|
17581
|
-
}
|
|
17582
|
-
} catch {}
|
|
17583
|
-
const workspaceName = (serverWorkspaces.length > 0 ? serverWorkspaces[0].name : undefined) || ws?.name || undefined;
|
|
17584
|
-
return { valid: true, email: email3, workspaceName };
|
|
17585
|
-
} catch {
|
|
17586
|
-
return { valid: false };
|
|
17587
|
-
}
|
|
17588
|
-
}
|
|
17589
|
-
function loginCommand() {
|
|
17590
|
-
const cmd = new Command2("login").description("Log in to Alook via browser (device code flow)").option("--server <url>", "Server URL").option("--profile <name>", "Profile name").option("--force", "Re-authenticate even if already logged in").action(async (opts, command) => {
|
|
17591
|
-
const profile = opts.profile || command.parent?.opts().profile;
|
|
17592
|
-
const serverUrl = opts.server || command.parent?.opts().server || getServerUrl();
|
|
17593
|
-
if (!opts.force) {
|
|
17594
|
-
const existing = await checkExistingAuth(serverUrl, profile);
|
|
17595
|
-
if (existing.valid) {
|
|
17596
|
-
const parts = ["Already logged in"];
|
|
17597
|
-
if (existing.email)
|
|
17598
|
-
parts[0] += ` as ${existing.email}`;
|
|
17599
|
-
if (existing.workspaceName)
|
|
17600
|
-
parts[0] += ` (workspace: ${existing.workspaceName})`;
|
|
17601
|
-
parts[0] += ".";
|
|
17602
|
-
console.log(parts[0]);
|
|
17603
|
-
return;
|
|
17604
|
-
}
|
|
17605
|
-
}
|
|
17606
|
-
console.log("Requesting device code...");
|
|
17607
|
-
let deviceResp;
|
|
17608
|
-
try {
|
|
17609
|
-
const res = await fetch(`${serverUrl}/api/auth/device/code`, {
|
|
17610
|
-
method: "POST",
|
|
17611
|
-
headers: { "Content-Type": "application/json" },
|
|
17612
|
-
body: JSON.stringify({ client_id: DEVICE_CLIENT_ID })
|
|
17613
|
-
});
|
|
17614
|
-
if (!res.ok) {
|
|
17615
|
-
const text2 = await res.text();
|
|
17616
|
-
console.error(`Error: failed to get device code (${res.status}): ${text2}`);
|
|
17617
|
-
process.exit(1);
|
|
17618
|
-
}
|
|
17619
|
-
deviceResp = await res.json();
|
|
17620
|
-
} catch (err) {
|
|
17621
|
-
console.error(`Error: failed to request device code: ${err instanceof Error ? err.message : err}`);
|
|
17622
|
-
process.exit(1);
|
|
17623
|
-
}
|
|
17624
|
-
const verificationUrl = deviceResp.verification_uri_complete || deviceResp.verification_uri;
|
|
17625
|
-
console.log();
|
|
17626
|
-
console.log(` Open this URL in your browser:`);
|
|
17627
|
-
console.log(` ${verificationUrl}`);
|
|
17628
|
-
console.log();
|
|
17629
|
-
console.log(` Enter code: ${deviceResp.user_code}`);
|
|
17630
|
-
console.log();
|
|
17631
|
-
if (!process.stdout.isTTY) {
|
|
17632
|
-
const pollData = JSON.stringify({
|
|
17633
|
-
deviceCode: deviceResp.device_code,
|
|
17634
|
-
interval: (deviceResp.interval || 5) * 1000,
|
|
17635
|
-
expiresIn: deviceResp.expires_in,
|
|
17636
|
-
serverUrl,
|
|
17637
|
-
profile
|
|
17638
|
-
});
|
|
17639
|
-
const thisFile = fileURLToPath2(import.meta.url);
|
|
17640
|
-
const child = fork(thisFile, ["--__login-poll", pollData], {
|
|
17641
|
-
detached: true,
|
|
17642
|
-
stdio: "ignore"
|
|
17643
|
-
});
|
|
17644
|
-
child.unref();
|
|
17645
|
-
console.log(" Polling for authorization in the background (timeout: 5min).");
|
|
17646
|
-
console.log(` Once approved, run \`${cmdPrefix()} status\` to verify.`);
|
|
17647
|
-
return;
|
|
17648
|
-
}
|
|
17649
|
-
openBrowser(verificationUrl);
|
|
17650
|
-
console.log(" (Browser opened automatically)");
|
|
17651
|
-
console.log();
|
|
17652
|
-
console.log("Waiting for authorization...");
|
|
17653
|
-
await pollAndActivate({
|
|
17654
|
-
deviceCode: deviceResp.device_code,
|
|
17655
|
-
interval: (deviceResp.interval || 5) * 1000,
|
|
17656
|
-
expiresIn: deviceResp.expires_in,
|
|
17657
|
-
serverUrl,
|
|
17658
|
-
profile
|
|
17659
|
-
});
|
|
17660
|
-
});
|
|
17661
|
-
return cmd;
|
|
17662
|
-
}
|
|
17663
|
-
|
|
17664
|
-
// commands/status.ts
|
|
17665
|
-
import { Command as Command3 } from "commander";
|
|
17666
|
-
function statusCommand() {
|
|
17667
|
-
const cmd = new Command3("status").description("Show registration status").action((_opts, command) => {
|
|
17668
|
-
const profile = command.parent?.opts().profile;
|
|
17669
|
-
const cfg = loadCLIConfigForProfile(profile);
|
|
17670
|
-
const ws = cfg.watched_workspaces?.[0];
|
|
17671
|
-
if (!ws?.token) {
|
|
17672
|
-
console.log("Not registered");
|
|
17673
|
-
console.log(`Run '${cmdPrefix()} register --token <token>' to register.`);
|
|
17674
|
-
return;
|
|
17675
|
-
}
|
|
17676
|
-
console.log("Status: Registered");
|
|
17677
|
-
console.log(`Server: ${cfg.server_url}`);
|
|
17678
|
-
console.log(`Workspace: ${ws.name} (${ws.id})`);
|
|
17679
|
-
});
|
|
17680
|
-
return cmd;
|
|
17681
|
-
}
|
|
17682
|
-
|
|
17683
|
-
// commands/daemon.ts
|
|
17684
|
-
import { Command as Command4 } from "commander";
|
|
17685
|
-
import { spawn as spawn7 } from "child_process";
|
|
17686
|
-
import { openSync as openSync2, closeSync as closeSync2, mkdirSync as mkdirSync9 } from "fs";
|
|
17687
|
-
import { dirname as dirname4 } from "path";
|
|
17688
|
-
|
|
17689
|
-
// daemon/client.ts
|
|
17690
|
-
class DaemonClient {
|
|
17691
|
-
baseURL;
|
|
17692
|
-
constructor(baseURL) {
|
|
17693
|
-
this.baseURL = baseURL;
|
|
17694
|
-
}
|
|
17695
|
-
async request(method, path, token, body) {
|
|
17696
|
-
const headers = {
|
|
17697
|
-
"Content-Type": "application/json",
|
|
17698
|
-
Authorization: `Bearer ${token}`
|
|
17699
|
-
};
|
|
17700
|
-
const MAX_RETRIES = 3;
|
|
17701
|
-
const BASE_DELAY_MS = 500;
|
|
17702
|
-
let lastError;
|
|
17703
|
-
for (let attempt = 0;attempt <= MAX_RETRIES; attempt++) {
|
|
17704
|
-
try {
|
|
17705
|
-
const res = await fetch(this.baseURL + path, {
|
|
17706
|
-
method,
|
|
17707
|
-
headers,
|
|
17708
|
-
body: body ? JSON.stringify(body) : undefined
|
|
17709
|
-
});
|
|
17710
|
-
if (!res.ok)
|
|
17711
|
-
throw new Error(`HTTP ${res.status}: ${await res.text()}`);
|
|
17712
|
-
if (res.status === 204)
|
|
17713
|
-
return;
|
|
17714
|
-
return res.json();
|
|
17715
|
-
} catch (e) {
|
|
17716
|
-
if (e instanceof TypeError) {
|
|
17717
|
-
lastError = e;
|
|
17718
|
-
if (attempt < MAX_RETRIES) {
|
|
17719
|
-
await new Promise((r) => setTimeout(r, BASE_DELAY_MS * 2 ** attempt));
|
|
17720
|
-
continue;
|
|
17721
|
-
}
|
|
17722
|
-
}
|
|
17723
|
-
throw e;
|
|
17724
|
-
}
|
|
17725
|
-
}
|
|
17726
|
-
throw lastError;
|
|
17727
|
-
}
|
|
17728
|
-
async register(token, body) {
|
|
17729
|
-
const raw = await this.request("POST", "/api/daemon/register", token, body);
|
|
17730
|
-
return RegisterResponseSchema.parse(raw);
|
|
17731
|
-
}
|
|
17732
|
-
heartbeat(token, daemonId) {
|
|
17733
|
-
return this.request("POST", "/api/daemon/heartbeat", token, {
|
|
17734
|
-
daemon_id: daemonId
|
|
17735
|
-
});
|
|
17736
|
-
}
|
|
17737
|
-
sweep(token, daemonId) {
|
|
17738
|
-
return this.request("POST", "/api/daemon/sweep", token, {
|
|
17739
|
-
daemon_id: daemonId
|
|
17740
|
-
});
|
|
17741
|
-
}
|
|
17742
|
-
deregister(token, daemonId) {
|
|
17743
|
-
return this.request("POST", "/api/daemon/deregister", token, {
|
|
17744
|
-
daemon_id: daemonId
|
|
17745
|
-
});
|
|
17746
|
-
}
|
|
17747
|
-
async poll(token, daemonId, maxTasks, cliVersion) {
|
|
17748
|
-
const raw = await this.request("POST", "/api/daemon/tasks/poll", token, { daemon_id: daemonId, max_tasks: maxTasks, ...cliVersion && { cli_version: cliVersion } });
|
|
17749
|
-
const resp = PollResponseSchema.parse(raw);
|
|
17750
|
-
return {
|
|
17751
|
-
tasks: resp.tasks,
|
|
17752
|
-
evicted: resp.evicted ?? false,
|
|
17753
|
-
pending_update: resp.pending_update,
|
|
17754
|
-
pending_rescan: resp.pending_rescan,
|
|
17755
|
-
file_requests: resp.file_requests,
|
|
17756
|
-
meetings: resp.meetings
|
|
17757
|
-
};
|
|
17758
|
-
}
|
|
17759
|
-
startTask(token, taskId) {
|
|
17760
|
-
return this.request("POST", `/api/daemon/tasks/${taskId}/start`, token);
|
|
17761
|
-
}
|
|
17762
|
-
completeTask(token, taskId, body) {
|
|
17763
|
-
return this.request("POST", `/api/daemon/tasks/${taskId}/complete`, token, body);
|
|
17764
|
-
}
|
|
17765
|
-
failTask(token, taskId, error51) {
|
|
17766
|
-
return this.request("POST", `/api/daemon/tasks/${taskId}/fail`, token, {
|
|
17767
|
-
error: error51
|
|
17768
|
-
});
|
|
17769
|
-
}
|
|
17770
|
-
supersedeTask(token, taskId) {
|
|
17771
|
-
return this.request("POST", `/api/daemon/tasks/${taskId}/supersede`, token);
|
|
17772
|
-
}
|
|
17773
|
-
async getArtifactMeta(token, artifactId, workspaceId) {
|
|
17774
|
-
return this.request("GET", `/api/artifacts/${artifactId}?workspace_id=${encodeURIComponent(workspaceId)}`, token);
|
|
17775
|
-
}
|
|
17776
|
-
async downloadArtifact(token, artifactId, workspaceId) {
|
|
17777
|
-
const MAX_RETRIES = 3;
|
|
17778
|
-
const BASE_DELAY_MS = 500;
|
|
17779
|
-
let lastError;
|
|
17780
|
-
for (let attempt = 0;attempt <= MAX_RETRIES; attempt++) {
|
|
17781
|
-
try {
|
|
17782
|
-
const res = await fetch(`${this.baseURL}/api/artifacts/${artifactId}/content?workspace_id=${encodeURIComponent(workspaceId)}`, { headers: { Authorization: `Bearer ${token}` } });
|
|
17783
|
-
if (!res.ok) {
|
|
17784
|
-
throw new Error(`artifact download failed: HTTP ${res.status}`);
|
|
17785
|
-
}
|
|
17786
|
-
return res.arrayBuffer();
|
|
17787
|
-
} catch (e) {
|
|
17788
|
-
if (e instanceof TypeError) {
|
|
17789
|
-
lastError = e;
|
|
17790
|
-
if (attempt < MAX_RETRIES) {
|
|
17791
|
-
await new Promise((r) => setTimeout(r, BASE_DELAY_MS * 2 ** attempt));
|
|
17792
|
-
continue;
|
|
17793
|
-
}
|
|
17794
|
-
}
|
|
17795
|
-
throw e;
|
|
17796
|
-
}
|
|
17797
|
-
}
|
|
17798
|
-
throw lastError;
|
|
17799
|
-
}
|
|
17800
|
-
reportMessages(token, taskId, messages) {
|
|
17801
|
-
return this.request("POST", `/api/daemon/tasks/${taskId}/messages`, token, { messages });
|
|
17802
|
-
}
|
|
17803
|
-
reportFileData(token, body) {
|
|
17804
|
-
return this.request("POST", "/api/daemon/workspace/report", token, body);
|
|
17805
|
-
}
|
|
17806
|
-
syncSkills(token, body) {
|
|
17807
|
-
return this.request("POST", "/api/daemon/skills/sync", token, body);
|
|
17808
|
-
}
|
|
17809
|
-
async checkStandby(token, body) {
|
|
17810
|
-
return this.request("POST", "/api/daemon/register", token, body);
|
|
17811
|
-
}
|
|
17812
|
-
}
|
|
17813
|
-
|
|
17814
|
-
// daemon/health.ts
|
|
17815
|
-
import { createServer } from "http";
|
|
17816
|
-
var DEFAULT_HEALTH_PORT = Number(process.env.ALOOK_HEALTH_PORT) || 19514;
|
|
17817
|
-
function createHealthServer(port = DEFAULT_HEALTH_PORT) {
|
|
17818
|
-
let runtimeCount = 0;
|
|
17819
|
-
const startTime = Date.now();
|
|
17820
|
-
const server = createServer((req, res) => {
|
|
17821
|
-
if (req.url === "/health") {
|
|
17822
|
-
const uptimeSec = Math.floor((Date.now() - startTime) / 1000);
|
|
17823
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
17824
|
-
res.end(JSON.stringify({
|
|
17825
|
-
status: "ok",
|
|
17826
|
-
uptime: `${uptimeSec}s`,
|
|
17827
|
-
runtimes: runtimeCount
|
|
17828
|
-
}));
|
|
17829
|
-
} else {
|
|
17830
|
-
res.writeHead(404);
|
|
17831
|
-
res.end();
|
|
17832
|
-
}
|
|
17833
|
-
});
|
|
17834
|
-
server.listen(port, "127.0.0.1");
|
|
17835
|
-
return {
|
|
17836
|
-
server,
|
|
17837
|
-
setRuntimeCount(n) {
|
|
17838
|
-
runtimeCount = n;
|
|
17839
|
-
}
|
|
17840
|
-
};
|
|
17841
|
-
}
|
|
17842
|
-
|
|
17843
|
-
// daemon/agent/claude.ts
|
|
17844
|
-
import { spawn as spawn2 } from "child_process";
|
|
17845
|
-
import { createInterface } from "readline";
|
|
17846
|
-
|
|
17847
|
-
// daemon/kill-tree.ts
|
|
17848
|
-
import { execSync as execSync2 } from "child_process";
|
|
17849
|
-
var log3 = createLogger2({ module: "kill-tree" });
|
|
17850
|
-
function killGraceMs() {
|
|
17851
|
-
return Number(process.env.ALOOK_KILL_GRACE_MS) || 2000;
|
|
17852
|
-
}
|
|
17853
|
-
var POLL_MS = 100;
|
|
17854
|
-
var isPosix = process.platform !== "win32";
|
|
17855
|
-
function isAlive(pid) {
|
|
17856
|
-
try {
|
|
17857
|
-
process.kill(pid, 0);
|
|
17858
|
-
return true;
|
|
17859
|
-
} catch (e) {
|
|
17860
|
-
return e?.code === "EPERM";
|
|
17861
|
-
}
|
|
17469
|
+
var POLL_MS = 100;
|
|
17470
|
+
var isPosix = process.platform !== "win32";
|
|
17471
|
+
function isAlive(pid) {
|
|
17472
|
+
try {
|
|
17473
|
+
process.kill(pid, 0);
|
|
17474
|
+
return true;
|
|
17475
|
+
} catch (e) {
|
|
17476
|
+
return e?.code === "EPERM";
|
|
17477
|
+
}
|
|
17862
17478
|
}
|
|
17863
17479
|
function signalTree(pid, signal) {
|
|
17864
17480
|
if (isPosix) {
|
|
@@ -17873,7 +17489,7 @@ function signalTree(pid, signal) {
|
|
|
17873
17489
|
}
|
|
17874
17490
|
if (!isPosix) {
|
|
17875
17491
|
try {
|
|
17876
|
-
|
|
17492
|
+
execSync(`taskkill /PID ${pid} /T /F`, { stdio: "ignore" });
|
|
17877
17493
|
return;
|
|
17878
17494
|
} catch {}
|
|
17879
17495
|
return;
|
|
@@ -17931,7 +17547,7 @@ class ClaudeBackend {
|
|
|
17931
17547
|
if (options.resumeSessionId) {
|
|
17932
17548
|
args.push("--resume", options.resumeSessionId);
|
|
17933
17549
|
}
|
|
17934
|
-
const proc =
|
|
17550
|
+
const proc = spawn(this.cliPath, args, {
|
|
17935
17551
|
cwd: options.cwd,
|
|
17936
17552
|
stdio: ["pipe", "pipe", "pipe"],
|
|
17937
17553
|
env: { ...process.env, ...options.env },
|
|
@@ -18170,7 +17786,7 @@ function handleControlRequest(proc, event) {
|
|
|
18170
17786
|
}
|
|
18171
17787
|
|
|
18172
17788
|
// daemon/agent/codex.ts
|
|
18173
|
-
import { spawn as
|
|
17789
|
+
import { spawn as spawn2 } from "child_process";
|
|
18174
17790
|
import { createInterface as createInterface2 } from "readline";
|
|
18175
17791
|
var RAW_DETECTION_METHODS = new Set([
|
|
18176
17792
|
"turn/started",
|
|
@@ -18202,7 +17818,7 @@ class CodexBackend {
|
|
|
18202
17818
|
this.cliPath = cliPath;
|
|
18203
17819
|
}
|
|
18204
17820
|
execute(prompt, options) {
|
|
18205
|
-
const proc =
|
|
17821
|
+
const proc = spawn2(this.cliPath, ["app-server", "--listen", "stdio://", "--config", "sandbox_mode=danger-full-access"], {
|
|
18206
17822
|
cwd: options.cwd,
|
|
18207
17823
|
stdio: ["pipe", "pipe", "pipe"],
|
|
18208
17824
|
env: { ...process.env, ...options.env },
|
|
@@ -18676,7 +18292,7 @@ class CodexBackend {
|
|
|
18676
18292
|
}
|
|
18677
18293
|
|
|
18678
18294
|
// daemon/agent/opencode.ts
|
|
18679
|
-
import { spawn as
|
|
18295
|
+
import { spawn as spawn3 } from "child_process";
|
|
18680
18296
|
import { createInterface as createInterface3 } from "readline";
|
|
18681
18297
|
class OpenCodeBackend {
|
|
18682
18298
|
cliPath;
|
|
@@ -18693,7 +18309,7 @@ class OpenCodeBackend {
|
|
|
18693
18309
|
args.push("--session", options.resumeSessionId);
|
|
18694
18310
|
}
|
|
18695
18311
|
args.push(prompt);
|
|
18696
|
-
const proc =
|
|
18312
|
+
const proc = spawn3(this.cliPath, args, {
|
|
18697
18313
|
cwd: options.cwd,
|
|
18698
18314
|
stdio: ["ignore", "pipe", "pipe"],
|
|
18699
18315
|
env: { ...process.env, ...options.env, OPENCODE_PERMISSION: '{"*":"allow"}' },
|
|
@@ -18934,7 +18550,7 @@ class OpenCodeBackend {
|
|
|
18934
18550
|
}
|
|
18935
18551
|
|
|
18936
18552
|
// daemon/agent/index.ts
|
|
18937
|
-
import { execSync as
|
|
18553
|
+
import { execSync as execSync2 } from "child_process";
|
|
18938
18554
|
function createBackend(provider, cliPath) {
|
|
18939
18555
|
switch (provider) {
|
|
18940
18556
|
case "claude":
|
|
@@ -18949,7 +18565,7 @@ function createBackend(provider, cliPath) {
|
|
|
18949
18565
|
}
|
|
18950
18566
|
async function detectVersion2(cliPath) {
|
|
18951
18567
|
try {
|
|
18952
|
-
return
|
|
18568
|
+
return execSync2(`${cliPath} --version`, { encoding: "utf-8" }).trim();
|
|
18953
18569
|
} catch {
|
|
18954
18570
|
return "unknown";
|
|
18955
18571
|
}
|
|
@@ -19509,7 +19125,7 @@ function localISOString(now = new Date) {
|
|
|
19509
19125
|
function lockPathFor(timelineDir, filename) {
|
|
19510
19126
|
return join7(timelineDir, `.${filename}.lock`);
|
|
19511
19127
|
}
|
|
19512
|
-
function
|
|
19128
|
+
function sleep(ms) {
|
|
19513
19129
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
19514
19130
|
}
|
|
19515
19131
|
async function initEntryAsync(timelineDir, entry) {
|
|
@@ -19519,7 +19135,7 @@ async function initEntryAsync(timelineDir, entry) {
|
|
|
19519
19135
|
try {
|
|
19520
19136
|
let acquired = acquireLock(lockPath);
|
|
19521
19137
|
if (!acquired) {
|
|
19522
|
-
await
|
|
19138
|
+
await sleep(200);
|
|
19523
19139
|
acquired = acquireLock(lockPath);
|
|
19524
19140
|
}
|
|
19525
19141
|
if (!acquired) {
|
|
@@ -20329,7 +19945,7 @@ class DaemonWsClient {
|
|
|
20329
19945
|
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, unlinkSync as unlinkSync4 } from "fs";
|
|
20330
19946
|
|
|
20331
19947
|
// lib/update.ts
|
|
20332
|
-
import { spawn as
|
|
19948
|
+
import { spawn as spawn4 } from "child_process";
|
|
20333
19949
|
function fetchLatestVersion() {
|
|
20334
19950
|
return fetch("https://registry.npmjs.org/@alook/cli/latest").then((res) => {
|
|
20335
19951
|
if (!res.ok)
|
|
@@ -20340,7 +19956,7 @@ function fetchLatestVersion() {
|
|
|
20340
19956
|
function runNpmUpdate(targetVersion) {
|
|
20341
19957
|
return new Promise((resolve) => {
|
|
20342
19958
|
const chunks = [];
|
|
20343
|
-
const child =
|
|
19959
|
+
const child = spawn4("npm", ["install", "-g", `@alook/cli@${targetVersion}`], {
|
|
20344
19960
|
stdio: ["ignore", "pipe", "pipe"]
|
|
20345
19961
|
});
|
|
20346
19962
|
child.stdout?.on("data", (d) => chunks.push(d));
|
|
@@ -20878,7 +20494,7 @@ function stopSkillScanner() {
|
|
|
20878
20494
|
}
|
|
20879
20495
|
|
|
20880
20496
|
// lib/shell-env.ts
|
|
20881
|
-
import { execSync as
|
|
20497
|
+
import { execSync as execSync3 } from "child_process";
|
|
20882
20498
|
var PASSTHROUGH_VARS = ["ALOOK_PROJECT_ROOT", "ALOOK_SERVER_URL", "ALOOK_CMD_PREFIX", "ALOOK_HEALTH_PORT"];
|
|
20883
20499
|
function resolveLoginShellEnv() {
|
|
20884
20500
|
if (isWindows) {
|
|
@@ -20886,7 +20502,7 @@ function resolveLoginShellEnv() {
|
|
|
20886
20502
|
}
|
|
20887
20503
|
const shell = process.env.SHELL || "/bin/zsh";
|
|
20888
20504
|
try {
|
|
20889
|
-
const output =
|
|
20505
|
+
const output = execSync3(`${shell} -ilc 'env'`, {
|
|
20890
20506
|
encoding: "utf-8",
|
|
20891
20507
|
timeout: 5000,
|
|
20892
20508
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -20913,17 +20529,17 @@ function resolveLoginShellEnv() {
|
|
|
20913
20529
|
// daemon/daemon.ts
|
|
20914
20530
|
import { existsSync as existsSync3, mkdirSync as mkdirSync8, openSync, closeSync, readdirSync as readdirSync3, statSync as statSync4, unlinkSync as unlinkSync5 } from "fs";
|
|
20915
20531
|
import { readdir as readdir2, readFile as readFile2, unlink, stat as fsStat } from "fs/promises";
|
|
20916
|
-
import { execSync as
|
|
20917
|
-
import { fileURLToPath as
|
|
20532
|
+
import { execSync as execSync4, spawn as spawn5 } from "child_process";
|
|
20533
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
20918
20534
|
import { dirname as dirname3, join as join11 } from "path";
|
|
20919
20535
|
var log10 = createLogger2({ module: "daemon" });
|
|
20920
|
-
var _dir = dirname3(
|
|
20536
|
+
var _dir = dirname3(fileURLToPath2(import.meta.url));
|
|
20921
20537
|
var sessionRunnerPath = existsSync3(join11(_dir, "session-runner.js")) ? join11(_dir, "session-runner.js") : join11(_dir, "session-runner.ts");
|
|
20922
20538
|
var meetingRunnerPath = existsSync3(join11(_dir, "meeting-runner.js")) ? join11(_dir, "meeting-runner.js") : join11(_dir, "meeting-runner.ts");
|
|
20923
|
-
function
|
|
20539
|
+
function isCommandAvailable(cmd) {
|
|
20924
20540
|
try {
|
|
20925
20541
|
const check2 = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
|
|
20926
|
-
|
|
20542
|
+
execSync4(check2, { stdio: "ignore" });
|
|
20927
20543
|
return true;
|
|
20928
20544
|
} catch {
|
|
20929
20545
|
return false;
|
|
@@ -21098,13 +20714,11 @@ async function startDaemon(profile, serverUrl) {
|
|
|
21098
20714
|
}
|
|
21099
20715
|
const cliConfig = loadCLIConfigForProfile(profile);
|
|
21100
20716
|
const allEntries = cliConfig.watched_workspaces || [];
|
|
21101
|
-
const workspaces = allEntries.filter((ws) => ws.status !== "
|
|
21102
|
-
|
|
21103
|
-
|
|
21104
|
-
|
|
21105
|
-
|
|
21106
|
-
} else if (workspaces.length === 0) {
|
|
21107
|
-
log10.info("No workspaces configured — daemon starting in standby mode. Register a workspace to begin.");
|
|
20717
|
+
const workspaces = allEntries.filter((ws) => ws.status !== "deleted" && !!ws.id);
|
|
20718
|
+
if (workspaces.length === 0) {
|
|
20719
|
+
log10.error("No workspaces configured. Register a workspace first.");
|
|
20720
|
+
process.exit(1);
|
|
20721
|
+
return;
|
|
21108
20722
|
}
|
|
21109
20723
|
const hasPerWorkspaceTokens = workspaces.every((ws) => !!ws.token);
|
|
21110
20724
|
if (!hasPerWorkspaceTokens) {
|
|
@@ -21122,7 +20736,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
21122
20736
|
["codex", config2.codexPath],
|
|
21123
20737
|
["opencode", config2.opencodePath]
|
|
21124
20738
|
]) {
|
|
21125
|
-
if (
|
|
20739
|
+
if (isCommandAvailable(path2)) {
|
|
21126
20740
|
const version3 = await detectVersion2(path2);
|
|
21127
20741
|
providers.push({ type, path: path2, version: version3 });
|
|
21128
20742
|
}
|
|
@@ -21135,7 +20749,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
21135
20749
|
log10.info(`Detected providers: ${providers.map((p) => `${p.type}@${p.version}`).join(", ")}`);
|
|
21136
20750
|
const workspaceStates = [];
|
|
21137
20751
|
const runtimeIndex = new Map;
|
|
21138
|
-
|
|
20752
|
+
const hadWorkspaces = true;
|
|
21139
20753
|
for (const ws of workspaces) {
|
|
21140
20754
|
const runtimes = providers.map((p) => ({
|
|
21141
20755
|
type: p.type,
|
|
@@ -21202,63 +20816,19 @@ async function startDaemon(profile, serverUrl) {
|
|
|
21202
20816
|
function evictWorkspace(workspaceId) {
|
|
21203
20817
|
const idx = workspaceStates.findIndex((ws2) => ws2.workspaceId === workspaceId);
|
|
21204
20818
|
if (idx === -1)
|
|
21205
|
-
return;
|
|
21206
|
-
const ws = workspaceStates[idx];
|
|
21207
|
-
for (const rid of ws.runtimeIds) {
|
|
21208
|
-
runtimeIndex.delete(rid);
|
|
21209
|
-
}
|
|
21210
|
-
workspaceStates.splice(idx, 1);
|
|
21211
|
-
health.setRuntimeCount(workspaceStates.reduce((sum, w) => sum + w.runtimeIds.length, 0));
|
|
21212
|
-
try {
|
|
21213
|
-
const cfg = loadCLIConfigForProfile(profile);
|
|
21214
|
-
cfg.watched_workspaces = (cfg.watched_workspaces || []).filter((w) => w.id !== workspaceId);
|
|
21215
|
-
saveCLIConfigForProfile(profile, cfg);
|
|
21216
|
-
} catch {}
|
|
21217
|
-
log10.info(`Workspace ${workspaceId} deleted server-side — removed from config`);
|
|
21218
|
-
}
|
|
21219
|
-
async function handleWorkspaceAdded(workspaceId, workspaceName, token) {
|
|
21220
|
-
if (workspaceStates.some((ws) => ws.workspaceId === workspaceId)) {
|
|
21221
|
-
log10.info(`Workspace ${workspaceId} already registered — ignoring workspace_added`);
|
|
21222
|
-
return;
|
|
21223
|
-
}
|
|
21224
|
-
log10.info(`Workspace ${workspaceId} bound — registering...`);
|
|
21225
|
-
const runtimes = providers.map((p) => ({ type: p.type, version: p.version }));
|
|
21226
|
-
try {
|
|
21227
|
-
const resp = await client.register(token, {
|
|
21228
|
-
workspace_id: workspaceId,
|
|
21229
|
-
daemon_id: config2.daemonId,
|
|
21230
|
-
device_name: config2.deviceName,
|
|
21231
|
-
cli_version: config2.cliVersion,
|
|
21232
|
-
workspaces_root: config2.workspacesRoot,
|
|
21233
|
-
runtimes
|
|
21234
|
-
});
|
|
21235
|
-
const runtimeIds = resp.runtimes.map((r) => r.id);
|
|
21236
|
-
workspaceStates.push({ workspaceId, token, runtimeIds });
|
|
21237
|
-
for (let i = 0;i < runtimeIds.length; i++) {
|
|
21238
|
-
runtimeIndex.set(runtimeIds[i], {
|
|
21239
|
-
id: runtimeIds[i],
|
|
21240
|
-
workspaceId,
|
|
21241
|
-
provider: providers[i].type
|
|
21242
|
-
});
|
|
21243
|
-
}
|
|
21244
|
-
hadWorkspaces = true;
|
|
21245
|
-
health.setRuntimeCount(workspaceStates.reduce((sum, w) => sum + w.runtimeIds.length, 0));
|
|
21246
|
-
try {
|
|
21247
|
-
const cfg = loadCLIConfigForProfile(profile);
|
|
21248
|
-
const watched = cfg.watched_workspaces || [];
|
|
21249
|
-
const registeredIdx = watched.findIndex((w) => w.token === token && w.status === "registered" && !w.id);
|
|
21250
|
-
if (registeredIdx !== -1) {
|
|
21251
|
-
watched[registeredIdx] = { id: workspaceId, name: workspaceName, token, status: "active", agent_ids: [] };
|
|
21252
|
-
} else if (!watched.some((w) => w.id === workspaceId)) {
|
|
21253
|
-
watched.push({ id: workspaceId, name: workspaceName, token, status: "active" });
|
|
21254
|
-
}
|
|
21255
|
-
cfg.watched_workspaces = watched;
|
|
21256
|
-
saveCLIConfigForProfile(profile, cfg);
|
|
21257
|
-
} catch {}
|
|
21258
|
-
log10.info(`Workspace ${workspaceId} added via WS push — ${runtimeIds.length} runtime(s)`);
|
|
21259
|
-
} catch (e) {
|
|
21260
|
-
log10.error(`Failed to register workspace ${workspaceId} from WS push`, e);
|
|
20819
|
+
return;
|
|
20820
|
+
const ws = workspaceStates[idx];
|
|
20821
|
+
for (const rid of ws.runtimeIds) {
|
|
20822
|
+
runtimeIndex.delete(rid);
|
|
21261
20823
|
}
|
|
20824
|
+
workspaceStates.splice(idx, 1);
|
|
20825
|
+
health.setRuntimeCount(workspaceStates.reduce((sum, w) => sum + w.runtimeIds.length, 0));
|
|
20826
|
+
try {
|
|
20827
|
+
const cfg = loadCLIConfigForProfile(profile);
|
|
20828
|
+
cfg.watched_workspaces = (cfg.watched_workspaces || []).filter((w) => w.id !== workspaceId);
|
|
20829
|
+
saveCLIConfigForProfile(profile, cfg);
|
|
20830
|
+
} catch {}
|
|
20831
|
+
log10.info(`Workspace ${workspaceId} deleted server-side — removed from config`);
|
|
21262
20832
|
}
|
|
21263
20833
|
const pollCycle = async () => {
|
|
21264
20834
|
let remaining = config2.maxConcurrentTasks - activeTasks.size;
|
|
@@ -21448,69 +21018,24 @@ async function startDaemon(profile, serverUrl) {
|
|
|
21448
21018
|
}
|
|
21449
21019
|
break;
|
|
21450
21020
|
}
|
|
21451
|
-
case "daemon.workspace_added": {
|
|
21452
|
-
handleWorkspaceAdded(msg.workspaceId, msg.workspaceName, msg.token);
|
|
21453
|
-
break;
|
|
21454
|
-
}
|
|
21455
21021
|
}
|
|
21456
21022
|
}
|
|
21457
|
-
const wsToken = firstToken
|
|
21023
|
+
const wsToken = firstToken;
|
|
21458
21024
|
let wsClient = wsToken ? new DaemonWsClient({
|
|
21459
21025
|
serverURL: config2.serverURL,
|
|
21460
21026
|
daemonId: config2.daemonId,
|
|
21461
21027
|
machineToken: wsToken,
|
|
21462
21028
|
onMessage: handleWsPush,
|
|
21463
21029
|
onConnected: () => {
|
|
21464
|
-
|
|
21465
|
-
|
|
21466
|
-
updatePollInterval(config2.wsPollInterval);
|
|
21467
|
-
} else {
|
|
21468
|
-
log10.info("WS connected in standby mode — awaiting workspace binding");
|
|
21469
|
-
}
|
|
21030
|
+
log10.info("WS connected — switching to low-frequency poll");
|
|
21031
|
+
updatePollInterval(config2.wsPollInterval);
|
|
21470
21032
|
},
|
|
21471
21033
|
onDisconnected: () => {
|
|
21472
|
-
|
|
21473
|
-
|
|
21474
|
-
updatePollInterval(config2.pollInterval);
|
|
21475
|
-
}
|
|
21034
|
+
log10.info("WS disconnected — reverting to high-frequency poll");
|
|
21035
|
+
updatePollInterval(config2.pollInterval);
|
|
21476
21036
|
}
|
|
21477
21037
|
}) : null;
|
|
21478
21038
|
wsClient?.connect();
|
|
21479
|
-
const STANDBY_POLL_MS = 30000;
|
|
21480
|
-
let standbyPollTimer = null;
|
|
21481
|
-
if (standbyToken && workspaceStates.length === 0) {
|
|
21482
|
-
const standbyPollTick = async () => {
|
|
21483
|
-
if (workspaceStates.length > 0) {
|
|
21484
|
-
if (standbyPollTimer) {
|
|
21485
|
-
clearInterval(standbyPollTimer);
|
|
21486
|
-
standbyPollTimer = null;
|
|
21487
|
-
}
|
|
21488
|
-
return;
|
|
21489
|
-
}
|
|
21490
|
-
try {
|
|
21491
|
-
const runtimes = providers.map((p) => ({ type: p.type, version: p.version }));
|
|
21492
|
-
const resp = await client.checkStandby(standbyToken, {
|
|
21493
|
-
daemon_id: config2.daemonId,
|
|
21494
|
-
device_name: config2.deviceName,
|
|
21495
|
-
cli_version: config2.cliVersion,
|
|
21496
|
-
workspaces_root: config2.workspacesRoot,
|
|
21497
|
-
runtimes
|
|
21498
|
-
});
|
|
21499
|
-
if (!resp.standby && resp.runtimes.length > 0 && resp.workspaceId) {
|
|
21500
|
-
log10.info(`Standby poll: workspace ${resp.workspaceId} discovered via fallback`);
|
|
21501
|
-
await handleWorkspaceAdded(resp.workspaceId, "", standbyToken);
|
|
21502
|
-
if (standbyPollTimer) {
|
|
21503
|
-
clearInterval(standbyPollTimer);
|
|
21504
|
-
standbyPollTimer = null;
|
|
21505
|
-
}
|
|
21506
|
-
}
|
|
21507
|
-
} catch (e) {
|
|
21508
|
-
log10.debug("standby poll failed", { err: e instanceof Error ? e.message : String(e) });
|
|
21509
|
-
}
|
|
21510
|
-
};
|
|
21511
|
-
standbyPollTick();
|
|
21512
|
-
standbyPollTimer = setInterval(standbyPollTick, STANDBY_POLL_MS);
|
|
21513
|
-
}
|
|
21514
21039
|
const sweepTick = async () => {
|
|
21515
21040
|
for (const ws of workspaceStates) {
|
|
21516
21041
|
client.sweep(ws.token, config2.daemonId).catch((e) => {
|
|
@@ -21548,8 +21073,6 @@ async function startDaemon(profile, serverUrl) {
|
|
|
21548
21073
|
clearInterval(pollTimer);
|
|
21549
21074
|
clearInterval(heartbeatTimer);
|
|
21550
21075
|
clearInterval(sweepTimer);
|
|
21551
|
-
if (standbyPollTimer)
|
|
21552
|
-
clearInterval(standbyPollTimer);
|
|
21553
21076
|
stopSkillScanner();
|
|
21554
21077
|
wsClient?.close();
|
|
21555
21078
|
const shutdownMs = restartRequested ? 30000 : Number(process.env.ALOOK_SHUTDOWN_TIMEOUT_MS) || 5000;
|
|
@@ -21576,7 +21099,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
21576
21099
|
} catch (e) {
|
|
21577
21100
|
log10.error(`Failed to open daemon log file ${logPath}`, e);
|
|
21578
21101
|
}
|
|
21579
|
-
const child =
|
|
21102
|
+
const child = spawn5(process.execPath, args, {
|
|
21580
21103
|
detached: true,
|
|
21581
21104
|
stdio: logFd != null ? ["ignore", logFd, logFd] : ["ignore", "ignore", "ignore"],
|
|
21582
21105
|
env: resolveLoginShellEnv()
|
|
@@ -21598,7 +21121,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
21598
21121
|
log10.info("SIGHUP received — reloading config...");
|
|
21599
21122
|
try {
|
|
21600
21123
|
const freshConfig = loadCLIConfigForProfile(profile);
|
|
21601
|
-
const freshWorkspaces = (freshConfig.watched_workspaces || []).filter((ws) => ws.status !== "
|
|
21124
|
+
const freshWorkspaces = (freshConfig.watched_workspaces || []).filter((ws) => ws.status !== "deleted" && !!ws.id);
|
|
21602
21125
|
const existingIds = new Set(workspaceStates.map((ws) => ws.workspaceId));
|
|
21603
21126
|
const newWorkspaces = freshWorkspaces.filter((ws) => ws.token && !existingIds.has(ws.id));
|
|
21604
21127
|
for (const ws of newWorkspaces) {
|
|
@@ -21628,7 +21151,6 @@ async function startDaemon(profile, serverUrl) {
|
|
|
21628
21151
|
}
|
|
21629
21152
|
}
|
|
21630
21153
|
if (newWorkspaces.length > 0) {
|
|
21631
|
-
hadWorkspaces = true;
|
|
21632
21154
|
health.setRuntimeCount(workspaceStates.reduce((sum, w) => sum + w.runtimeIds.length, 0));
|
|
21633
21155
|
if (!wsClient && workspaceStates.length > 0) {
|
|
21634
21156
|
const token = workspaceStates[0].token;
|
|
@@ -21671,7 +21193,7 @@ function spawnSessionRunner(input) {
|
|
|
21671
21193
|
} catch (e) {
|
|
21672
21194
|
log10.error(`Failed to open log file ${logFilePath}`, e);
|
|
21673
21195
|
}
|
|
21674
|
-
const child =
|
|
21196
|
+
const child = spawn5(process.execPath, [sessionRunnerPath, encoded], {
|
|
21675
21197
|
detached: true,
|
|
21676
21198
|
stdio: fd != null ? ["ignore", fd, fd] : ["ignore", "ignore", "ignore"]
|
|
21677
21199
|
});
|
|
@@ -21691,7 +21213,7 @@ function spawnMeetingRunner(input) {
|
|
|
21691
21213
|
} catch (e) {
|
|
21692
21214
|
log10.error(`Failed to open meeting log file ${logFilePath}`, e);
|
|
21693
21215
|
}
|
|
21694
|
-
const child =
|
|
21216
|
+
const child = spawn5(process.execPath, [meetingRunnerPath, encoded], {
|
|
21695
21217
|
detached: true,
|
|
21696
21218
|
stdio: fd != null ? ["ignore", fd, fd] : ["ignore", "ignore", "ignore"]
|
|
21697
21219
|
});
|
|
@@ -21968,66 +21490,499 @@ async function handleTask(client, config2, runtimeIndex, task, token, activeTask
|
|
|
21968
21490
|
}
|
|
21969
21491
|
}
|
|
21970
21492
|
}
|
|
21971
|
-
const cliPath = provider === "claude" ? config2.claudePath : provider === "codex" ? config2.codexPath : config2.opencodePath;
|
|
21972
|
-
const configModel = provider === "claude" ? config2.claudeModel : provider === "codex" ? config2.codexModel : config2.opencodeModel;
|
|
21973
|
-
const agentModel = task.agent?.runtimeConfig?.model;
|
|
21974
|
-
const model = typeof agentModel === "string" && agentModel ? agentModel : configModel;
|
|
21975
|
-
const input = {
|
|
21976
|
-
task,
|
|
21977
|
-
provider,
|
|
21978
|
-
cliPath,
|
|
21979
|
-
model,
|
|
21980
|
-
serverURL: config2.serverURL,
|
|
21981
|
-
token,
|
|
21982
|
-
workspacesRoot: config2.workspacesRoot,
|
|
21983
|
-
agentTimeout: config2.agentTimeout,
|
|
21984
|
-
messageInactivityTimeout: config2.messageInactivityTimeout,
|
|
21985
|
-
...promptOverride && { promptOverride }
|
|
21986
|
-
};
|
|
21987
|
-
const child = spawnSessionRunner(input);
|
|
21988
|
-
child.on("close", async (code) => {
|
|
21989
|
-
activeTasks.delete(task.id);
|
|
21990
|
-
if (code !== 0) {
|
|
21991
|
-
const agentBaseDir = join11(config2.workspacesRoot, task.workspaceId, task.agentId, "workdir");
|
|
21992
|
-
const killIntent = readKillIntent(agentBaseDir, task.id);
|
|
21993
|
-
if (killIntent) {
|
|
21994
|
-
log10.info(`Task ${task.id} exited (${killIntent.reason}) — expected, skipping failTask`);
|
|
21995
|
-
clearKillIntent(agentBaseDir, task.id);
|
|
21493
|
+
const cliPath = provider === "claude" ? config2.claudePath : provider === "codex" ? config2.codexPath : config2.opencodePath;
|
|
21494
|
+
const configModel = provider === "claude" ? config2.claudeModel : provider === "codex" ? config2.codexModel : config2.opencodeModel;
|
|
21495
|
+
const agentModel = task.agent?.runtimeConfig?.model;
|
|
21496
|
+
const model = typeof agentModel === "string" && agentModel ? agentModel : configModel;
|
|
21497
|
+
const input = {
|
|
21498
|
+
task,
|
|
21499
|
+
provider,
|
|
21500
|
+
cliPath,
|
|
21501
|
+
model,
|
|
21502
|
+
serverURL: config2.serverURL,
|
|
21503
|
+
token,
|
|
21504
|
+
workspacesRoot: config2.workspacesRoot,
|
|
21505
|
+
agentTimeout: config2.agentTimeout,
|
|
21506
|
+
messageInactivityTimeout: config2.messageInactivityTimeout,
|
|
21507
|
+
...promptOverride && { promptOverride }
|
|
21508
|
+
};
|
|
21509
|
+
const child = spawnSessionRunner(input);
|
|
21510
|
+
child.on("close", async (code) => {
|
|
21511
|
+
activeTasks.delete(task.id);
|
|
21512
|
+
if (code !== 0) {
|
|
21513
|
+
const agentBaseDir = join11(config2.workspacesRoot, task.workspaceId, task.agentId, "workdir");
|
|
21514
|
+
const killIntent = readKillIntent(agentBaseDir, task.id);
|
|
21515
|
+
if (killIntent) {
|
|
21516
|
+
log10.info(`Task ${task.id} exited (${killIntent.reason}) — expected, skipping failTask`);
|
|
21517
|
+
clearKillIntent(agentBaseDir, task.id);
|
|
21518
|
+
return;
|
|
21519
|
+
}
|
|
21520
|
+
const errorMsg = code === null ? "killed by signal" : `session-runner exited with code ${code}`;
|
|
21521
|
+
try {
|
|
21522
|
+
await client.failTask(token, task.id, errorMsg);
|
|
21523
|
+
log10.warn(`session-runner crashed (${errorMsg}, task ${task.id})`);
|
|
21524
|
+
const timelineDir = join11(agentBaseDir, ".context_timeline");
|
|
21525
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
21526
|
+
entry.pid = null;
|
|
21527
|
+
entry.status = "failed";
|
|
21528
|
+
entry.errmsg = errorMsg;
|
|
21529
|
+
});
|
|
21530
|
+
} catch (e) {
|
|
21531
|
+
if (isClientError3(e)) {
|
|
21532
|
+
log10.info(`Task ${task.id} exited (already terminal) — session-runner handled cleanup`);
|
|
21533
|
+
return;
|
|
21534
|
+
}
|
|
21535
|
+
log10.error(`Failed to report crash for task ${task.id}`, e);
|
|
21536
|
+
try {
|
|
21537
|
+
await writeMarkerFile(config2.workspacesRoot, {
|
|
21538
|
+
taskId: task.id,
|
|
21539
|
+
type: "fail",
|
|
21540
|
+
payload: { error: errorMsg },
|
|
21541
|
+
token,
|
|
21542
|
+
serverURL: config2.serverURL,
|
|
21543
|
+
createdAt: new Date().toISOString()
|
|
21544
|
+
});
|
|
21545
|
+
} catch {}
|
|
21546
|
+
}
|
|
21547
|
+
}
|
|
21548
|
+
});
|
|
21549
|
+
log10.info(`Task ${task.id} dispatched to session-runner (pid=${child.pid})`);
|
|
21550
|
+
}
|
|
21551
|
+
|
|
21552
|
+
// lib/runtimes.ts
|
|
21553
|
+
import { execSync as execSync5 } from "child_process";
|
|
21554
|
+
function isCommandAvailable2(cmd) {
|
|
21555
|
+
try {
|
|
21556
|
+
const check2 = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
|
|
21557
|
+
execSync5(check2, { stdio: "ignore" });
|
|
21558
|
+
return true;
|
|
21559
|
+
} catch {
|
|
21560
|
+
return false;
|
|
21561
|
+
}
|
|
21562
|
+
}
|
|
21563
|
+
function detectRuntimes() {
|
|
21564
|
+
const found = [];
|
|
21565
|
+
for (const type of ["claude", "codex", "opencode"]) {
|
|
21566
|
+
if (isCommandAvailable2(type)) {
|
|
21567
|
+
let version3 = "";
|
|
21568
|
+
try {
|
|
21569
|
+
version3 = execSync5(`${type} --version`, { encoding: "utf-8" }).trim();
|
|
21570
|
+
} catch {}
|
|
21571
|
+
found.push({ type, version: version3 });
|
|
21572
|
+
}
|
|
21573
|
+
}
|
|
21574
|
+
return found;
|
|
21575
|
+
}
|
|
21576
|
+
|
|
21577
|
+
// lib/activate.ts
|
|
21578
|
+
async function activateAndSave(opts) {
|
|
21579
|
+
const { token, serverUrl, profile } = opts;
|
|
21580
|
+
console.log("Scanning for AI runtimes...");
|
|
21581
|
+
const runtimes = detectRuntimes();
|
|
21582
|
+
if (runtimes.length === 0) {
|
|
21583
|
+
console.error("Error: no runtimes found. Install claude, codex, or opencode first.");
|
|
21584
|
+
process.exit(1);
|
|
21585
|
+
}
|
|
21586
|
+
console.log(`Found: ${runtimes.map((r) => r.type).join(", ")}`);
|
|
21587
|
+
const host = hostname4();
|
|
21588
|
+
console.log("Registering machine...");
|
|
21589
|
+
let activateResp;
|
|
21590
|
+
try {
|
|
21591
|
+
const res = await fetch(`${serverUrl}/api/machine-tokens/activate`, {
|
|
21592
|
+
method: "POST",
|
|
21593
|
+
headers: { "Content-Type": "application/json" },
|
|
21594
|
+
body: JSON.stringify({ token, hostname: host, runtimes })
|
|
21595
|
+
});
|
|
21596
|
+
if (!res.ok) {
|
|
21597
|
+
const text2 = await res.text();
|
|
21598
|
+
console.error(`Error: registration failed (${res.status}): ${text2}`);
|
|
21599
|
+
process.exit(1);
|
|
21600
|
+
}
|
|
21601
|
+
activateResp = await res.json();
|
|
21602
|
+
} catch (err) {
|
|
21603
|
+
console.error(`Error: failed to activate: ${err instanceof Error ? err.message : err}`);
|
|
21604
|
+
process.exit(1);
|
|
21605
|
+
}
|
|
21606
|
+
const client = new APIClient(serverUrl, token);
|
|
21607
|
+
let workspaces;
|
|
21608
|
+
try {
|
|
21609
|
+
workspaces = await client.getJSON("/api/workspaces");
|
|
21610
|
+
} catch (err) {
|
|
21611
|
+
console.error(`Error: failed to fetch workspaces: ${err instanceof Error ? err.message : err}`);
|
|
21612
|
+
process.exit(1);
|
|
21613
|
+
}
|
|
21614
|
+
if (!workspaces.length) {
|
|
21615
|
+
console.error("Error: no workspaces found for this user");
|
|
21616
|
+
process.exit(1);
|
|
21617
|
+
}
|
|
21618
|
+
const ws = workspaces.find((w) => w.id === activateResp.workspace_id) || workspaces[0];
|
|
21619
|
+
const wsClient = new APIClient(serverUrl, token, ws.id);
|
|
21620
|
+
let agentIds = [];
|
|
21621
|
+
try {
|
|
21622
|
+
const agents = await wsClient.getJSON(`/api/agents?workspace_id=${ws.id}`);
|
|
21623
|
+
agentIds = agents.map((a) => a.id);
|
|
21624
|
+
} catch {}
|
|
21625
|
+
const existing = loadCLIConfigForProfile(profile);
|
|
21626
|
+
const watched = existing.watched_workspaces || [];
|
|
21627
|
+
const idx = watched.findIndex((w) => w.id === ws.id);
|
|
21628
|
+
if (idx >= 0) {
|
|
21629
|
+
watched[idx] = { id: ws.id, name: ws.name, token, status: "active", agent_ids: agentIds };
|
|
21630
|
+
} else {
|
|
21631
|
+
watched.push({ id: ws.id, name: ws.name, token, status: "active", agent_ids: agentIds });
|
|
21632
|
+
}
|
|
21633
|
+
saveCLIConfigForProfile(profile, {
|
|
21634
|
+
server_url: serverUrl,
|
|
21635
|
+
watched_workspaces: watched
|
|
21636
|
+
});
|
|
21637
|
+
const daemonPid = readDaemonPid(profile);
|
|
21638
|
+
if (daemonPid && isProcessAlive(daemonPid)) {
|
|
21639
|
+
try {
|
|
21640
|
+
process.kill(daemonPid, "SIGHUP");
|
|
21641
|
+
console.log(`
|
|
21642
|
+
Daemon (pid ${daemonPid}) notified — workspace will be active shortly.`);
|
|
21643
|
+
} catch {
|
|
21644
|
+
console.log(`
|
|
21645
|
+
Daemon is running but could not be notified. Restart it to pick up the new workspace.`);
|
|
21646
|
+
}
|
|
21647
|
+
} else if (isDev() && process.stdout.isTTY) {
|
|
21648
|
+
console.log(`
|
|
21649
|
+
Starting daemon in foreground...`);
|
|
21650
|
+
await startDaemon(profile, serverUrl);
|
|
21651
|
+
} else {
|
|
21652
|
+
console.log(`
|
|
21653
|
+
Starting daemon...`);
|
|
21654
|
+
try {
|
|
21655
|
+
const entry = process.argv[1];
|
|
21656
|
+
const args = [entry];
|
|
21657
|
+
if (profile)
|
|
21658
|
+
args.push("--profile", profile);
|
|
21659
|
+
args.push("daemon", "start", "--foreground");
|
|
21660
|
+
const logPath = daemonLogFilePath();
|
|
21661
|
+
mkdirSync9(dirname4(logPath), { recursive: true, mode: 448 });
|
|
21662
|
+
const logFd = openSync2(logPath, "a", 384);
|
|
21663
|
+
const child = spawn6(process.execPath, args, {
|
|
21664
|
+
detached: true,
|
|
21665
|
+
stdio: ["ignore", logFd, logFd],
|
|
21666
|
+
env: resolveLoginShellEnv()
|
|
21667
|
+
});
|
|
21668
|
+
child.unref();
|
|
21669
|
+
closeSync2(logFd);
|
|
21670
|
+
console.log("Daemon started in background.");
|
|
21671
|
+
console.log(`Logs: ${logPath}`);
|
|
21672
|
+
} catch {
|
|
21673
|
+
console.log(`Failed to auto-start daemon. Run '${cmdPrefix()} daemon start' manually.`);
|
|
21674
|
+
}
|
|
21675
|
+
}
|
|
21676
|
+
return {
|
|
21677
|
+
workspaceId: ws.id,
|
|
21678
|
+
workspaceName: ws.name,
|
|
21679
|
+
runtimeProviders: activateResp.runtimes.map((r) => r.provider)
|
|
21680
|
+
};
|
|
21681
|
+
}
|
|
21682
|
+
|
|
21683
|
+
// commands/register.ts
|
|
21684
|
+
function registerCommand() {
|
|
21685
|
+
const cmd = new Command("register").description("Register CLI with your Alook account").requiredOption("--token <token>", "API token (starts with al_)").option("--server <url>", "Server URL").option("--profile <name>", "Profile name").action(async (opts, command) => {
|
|
21686
|
+
const token = opts.token;
|
|
21687
|
+
const profile = opts.profile || command.parent?.opts().profile;
|
|
21688
|
+
const serverUrl = opts.server || command.parent?.opts().server || getServerUrl();
|
|
21689
|
+
if (!token) {
|
|
21690
|
+
console.error(`Error: --token is required
|
|
21691
|
+
Usage: ${cmdPrefix()} register --token <token>`);
|
|
21692
|
+
process.exit(1);
|
|
21693
|
+
}
|
|
21694
|
+
if (!token.startsWith("al_")) {
|
|
21695
|
+
console.error("Error: invalid token format: must start with 'al_'");
|
|
21696
|
+
process.exit(1);
|
|
21697
|
+
}
|
|
21698
|
+
const client = new APIClient(serverUrl, token);
|
|
21699
|
+
let me;
|
|
21700
|
+
try {
|
|
21701
|
+
me = await client.getJSON("/api/me");
|
|
21702
|
+
} catch (err) {
|
|
21703
|
+
console.error(`Error: failed to verify token: ${err instanceof Error ? err.message : err}`);
|
|
21704
|
+
process.exit(1);
|
|
21705
|
+
}
|
|
21706
|
+
const result = await activateAndSave({ token, serverUrl, profile });
|
|
21707
|
+
console.log(`
|
|
21708
|
+
Registered as ${me.email}`);
|
|
21709
|
+
console.log(`Workspace: ${result.workspaceName} (${result.workspaceId})`);
|
|
21710
|
+
console.log(`Runtimes: ${result.runtimeProviders.join(", ")}`);
|
|
21711
|
+
});
|
|
21712
|
+
return cmd;
|
|
21713
|
+
}
|
|
21714
|
+
|
|
21715
|
+
// commands/login.ts
|
|
21716
|
+
import { Command as Command2 } from "commander";
|
|
21717
|
+
import { fork, spawn as spawn7 } from "child_process";
|
|
21718
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
21719
|
+
var DEVICE_CLIENT_ID = process.env.ALOOK_DEVICE_CLIENT_ID || "alook-cli";
|
|
21720
|
+
function openBrowser(url2) {
|
|
21721
|
+
try {
|
|
21722
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "linux" ? "xdg-open" : process.platform === "win32" ? "start" : null;
|
|
21723
|
+
if (cmd) {
|
|
21724
|
+
const args = process.platform === "win32" ? ["", url2] : [url2];
|
|
21725
|
+
spawn7(cmd, args, { stdio: "ignore", detached: true }).unref();
|
|
21726
|
+
}
|
|
21727
|
+
} catch {}
|
|
21728
|
+
}
|
|
21729
|
+
function sleep2(ms) {
|
|
21730
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
21731
|
+
}
|
|
21732
|
+
function syncWorkspacesToConfig(serverWorkspaces, profile, sessionToken) {
|
|
21733
|
+
const cfg = loadCLIConfigForProfile(profile);
|
|
21734
|
+
const watched = cfg.watched_workspaces || [];
|
|
21735
|
+
const serverIds = new Set(serverWorkspaces.map((w) => w.id));
|
|
21736
|
+
for (const sw of serverWorkspaces) {
|
|
21737
|
+
const existing = watched.find((w) => w.id === sw.id);
|
|
21738
|
+
if (existing) {
|
|
21739
|
+
existing.status = "active";
|
|
21740
|
+
existing.name = sw.name;
|
|
21741
|
+
} else {
|
|
21742
|
+
watched.push({ id: sw.id, name: sw.name, token: "", status: "active", agent_ids: [] });
|
|
21743
|
+
}
|
|
21744
|
+
}
|
|
21745
|
+
for (const w of watched) {
|
|
21746
|
+
if (w.id && !serverIds.has(w.id)) {
|
|
21747
|
+
w.status = "deleted";
|
|
21748
|
+
}
|
|
21749
|
+
}
|
|
21750
|
+
saveCLIConfigForProfile(profile, {
|
|
21751
|
+
server_url: cfg.server_url,
|
|
21752
|
+
session_token: sessionToken ?? cfg.session_token,
|
|
21753
|
+
watched_workspaces: watched
|
|
21754
|
+
});
|
|
21755
|
+
}
|
|
21756
|
+
async function pollAndActivate(opts) {
|
|
21757
|
+
const { deviceCode, expiresIn, serverUrl, profile } = opts;
|
|
21758
|
+
let interval = opts.interval;
|
|
21759
|
+
const expiresAt = Date.now() + expiresIn * 1000;
|
|
21760
|
+
let tokenResp;
|
|
21761
|
+
while (Date.now() < expiresAt) {
|
|
21762
|
+
await sleep2(interval);
|
|
21763
|
+
try {
|
|
21764
|
+
const res = await fetch(`${serverUrl}/api/auth/device/token`, {
|
|
21765
|
+
method: "POST",
|
|
21766
|
+
headers: { "Content-Type": "application/json" },
|
|
21767
|
+
body: JSON.stringify({
|
|
21768
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
21769
|
+
device_code: deviceCode,
|
|
21770
|
+
client_id: DEVICE_CLIENT_ID
|
|
21771
|
+
})
|
|
21772
|
+
});
|
|
21773
|
+
if (res.ok) {
|
|
21774
|
+
tokenResp = await res.json();
|
|
21775
|
+
break;
|
|
21776
|
+
}
|
|
21777
|
+
const errBody = await res.json();
|
|
21778
|
+
if (errBody.error === "slow_down") {
|
|
21779
|
+
interval += 5000;
|
|
21780
|
+
} else if (errBody.error === "authorization_pending") {} else if (errBody.error === "expired_token") {
|
|
21781
|
+
console.error("Error: device code expired. Please run login again.");
|
|
21782
|
+
process.exit(1);
|
|
21783
|
+
} else if (errBody.error === "access_denied") {
|
|
21784
|
+
console.error("Error: authorization was denied.");
|
|
21785
|
+
process.exit(1);
|
|
21786
|
+
} else {
|
|
21787
|
+
console.error(`Error: unexpected error: ${errBody.error_description || errBody.error}`);
|
|
21788
|
+
process.exit(1);
|
|
21789
|
+
}
|
|
21790
|
+
} catch {
|
|
21791
|
+
console.error("Error: network request failed during polling.");
|
|
21792
|
+
process.exit(1);
|
|
21793
|
+
}
|
|
21794
|
+
}
|
|
21795
|
+
if (!tokenResp) {
|
|
21796
|
+
console.error("Error: device code expired (timed out). Please run login again.");
|
|
21797
|
+
process.exit(1);
|
|
21798
|
+
}
|
|
21799
|
+
const sessionToken = tokenResp.access_token;
|
|
21800
|
+
const client = new APIClient(serverUrl, sessionToken);
|
|
21801
|
+
let email3 = "";
|
|
21802
|
+
try {
|
|
21803
|
+
const me = await client.getJSON("/api/me");
|
|
21804
|
+
email3 = me.email;
|
|
21805
|
+
} catch {}
|
|
21806
|
+
let serverWorkspaces = [];
|
|
21807
|
+
try {
|
|
21808
|
+
serverWorkspaces = await client.getJSON("/api/workspaces");
|
|
21809
|
+
} catch {}
|
|
21810
|
+
syncWorkspacesToConfig(serverWorkspaces, profile, sessionToken);
|
|
21811
|
+
let workspaceId = serverWorkspaces.length > 0 ? serverWorkspaces[0].id : "";
|
|
21812
|
+
if (!workspaceId) {
|
|
21813
|
+
try {
|
|
21814
|
+
const newWs = await client.postJSON("/api/workspaces", {
|
|
21815
|
+
name: "Personal",
|
|
21816
|
+
slug: email3.split("@")[0]?.toLowerCase().replace(/[^a-z0-9-]/g, "-").slice(0, 48) || "personal"
|
|
21817
|
+
});
|
|
21818
|
+
workspaceId = newWs.id;
|
|
21819
|
+
} catch (err) {
|
|
21820
|
+
console.error(`Error: failed to create workspace: ${err instanceof Error ? err.message : err}`);
|
|
21821
|
+
process.exit(1);
|
|
21822
|
+
}
|
|
21823
|
+
}
|
|
21824
|
+
let machineToken2;
|
|
21825
|
+
try {
|
|
21826
|
+
const mtResp = await client.postJSON(`/api/machine-tokens?workspace_id=${workspaceId}`);
|
|
21827
|
+
machineToken2 = mtResp.token;
|
|
21828
|
+
} catch {
|
|
21829
|
+
process.exit(1);
|
|
21830
|
+
}
|
|
21831
|
+
const result = await activateAndSave({ token: machineToken2, serverUrl, profile });
|
|
21832
|
+
if (email3) {
|
|
21833
|
+
console.log(`
|
|
21834
|
+
Logged in as ${email3}`);
|
|
21835
|
+
}
|
|
21836
|
+
console.log(`Workspace: ${result.workspaceName} (${result.workspaceId})`);
|
|
21837
|
+
console.log(`Runtimes: ${result.runtimeProviders.join(", ")}`);
|
|
21838
|
+
}
|
|
21839
|
+
if (process.argv.includes("--__login-poll")) {
|
|
21840
|
+
const idx = process.argv.indexOf("--__login-poll");
|
|
21841
|
+
let data;
|
|
21842
|
+
try {
|
|
21843
|
+
data = JSON.parse(process.argv[idx + 1]);
|
|
21844
|
+
} catch {
|
|
21845
|
+
console.error("Error: invalid poll data");
|
|
21846
|
+
process.exit(1);
|
|
21847
|
+
}
|
|
21848
|
+
pollAndActivate(data).catch(() => process.exit(1));
|
|
21849
|
+
}
|
|
21850
|
+
async function checkExistingAuth(serverUrl, profile) {
|
|
21851
|
+
const config2 = loadCLIConfigForProfile(profile);
|
|
21852
|
+
const sessionToken = config2.session_token;
|
|
21853
|
+
const workspaces = config2.watched_workspaces || [];
|
|
21854
|
+
const ws = workspaces[0];
|
|
21855
|
+
const authToken = sessionToken || ws?.token;
|
|
21856
|
+
if (!authToken) {
|
|
21857
|
+
return { valid: false };
|
|
21858
|
+
}
|
|
21859
|
+
try {
|
|
21860
|
+
const res = await fetch(`${serverUrl}/api/workspaces`, {
|
|
21861
|
+
headers: { Authorization: `Bearer ${authToken}` }
|
|
21862
|
+
});
|
|
21863
|
+
if (!res.ok) {
|
|
21864
|
+
return { valid: false };
|
|
21865
|
+
}
|
|
21866
|
+
const serverWorkspaces = await res.json();
|
|
21867
|
+
const hasValidWorkspace = workspaces.some((w) => w.id && w.status !== "deleted");
|
|
21868
|
+
if (!hasValidWorkspace && serverWorkspaces.length > 0) {
|
|
21869
|
+
syncWorkspacesToConfig(serverWorkspaces, profile);
|
|
21870
|
+
}
|
|
21871
|
+
let email3;
|
|
21872
|
+
try {
|
|
21873
|
+
const meRes = await fetch(`${serverUrl}/api/me`, {
|
|
21874
|
+
headers: { Authorization: `Bearer ${authToken}` }
|
|
21875
|
+
});
|
|
21876
|
+
if (meRes.ok) {
|
|
21877
|
+
const me = await meRes.json();
|
|
21878
|
+
email3 = me.email;
|
|
21879
|
+
}
|
|
21880
|
+
} catch {}
|
|
21881
|
+
const workspaceName = (serverWorkspaces.length > 0 ? serverWorkspaces[0].name : undefined) || ws?.name || undefined;
|
|
21882
|
+
return { valid: true, email: email3, workspaceName };
|
|
21883
|
+
} catch {
|
|
21884
|
+
return { valid: false };
|
|
21885
|
+
}
|
|
21886
|
+
}
|
|
21887
|
+
function loginCommand() {
|
|
21888
|
+
const cmd = new Command2("login").description("Log in to Alook via browser (device code flow)").option("--server <url>", "Server URL").option("--profile <name>", "Profile name").option("--force", "Re-authenticate even if already logged in").action(async (opts, command) => {
|
|
21889
|
+
const profile = opts.profile || command.parent?.opts().profile;
|
|
21890
|
+
const serverUrl = opts.server || command.parent?.opts().server || getServerUrl();
|
|
21891
|
+
if (!opts.force) {
|
|
21892
|
+
const existing = await checkExistingAuth(serverUrl, profile);
|
|
21893
|
+
if (existing.valid) {
|
|
21894
|
+
const parts = ["Already logged in"];
|
|
21895
|
+
if (existing.email)
|
|
21896
|
+
parts[0] += ` as ${existing.email}`;
|
|
21897
|
+
if (existing.workspaceName)
|
|
21898
|
+
parts[0] += ` (workspace: ${existing.workspaceName})`;
|
|
21899
|
+
parts[0] += ".";
|
|
21900
|
+
console.log(parts[0]);
|
|
21996
21901
|
return;
|
|
21997
21902
|
}
|
|
21998
|
-
|
|
21999
|
-
|
|
22000
|
-
|
|
22001
|
-
|
|
22002
|
-
|
|
22003
|
-
|
|
22004
|
-
|
|
22005
|
-
|
|
22006
|
-
|
|
22007
|
-
|
|
22008
|
-
|
|
22009
|
-
|
|
22010
|
-
|
|
22011
|
-
return;
|
|
22012
|
-
}
|
|
22013
|
-
log10.error(`Failed to report crash for task ${task.id}`, e);
|
|
22014
|
-
try {
|
|
22015
|
-
await writeMarkerFile(config2.workspacesRoot, {
|
|
22016
|
-
taskId: task.id,
|
|
22017
|
-
type: "fail",
|
|
22018
|
-
payload: { error: errorMsg },
|
|
22019
|
-
token,
|
|
22020
|
-
serverURL: config2.serverURL,
|
|
22021
|
-
createdAt: new Date().toISOString()
|
|
22022
|
-
});
|
|
22023
|
-
} catch {}
|
|
21903
|
+
}
|
|
21904
|
+
console.log("Requesting device code...");
|
|
21905
|
+
let deviceResp;
|
|
21906
|
+
try {
|
|
21907
|
+
const res = await fetch(`${serverUrl}/api/auth/device/code`, {
|
|
21908
|
+
method: "POST",
|
|
21909
|
+
headers: { "Content-Type": "application/json" },
|
|
21910
|
+
body: JSON.stringify({ client_id: DEVICE_CLIENT_ID })
|
|
21911
|
+
});
|
|
21912
|
+
if (!res.ok) {
|
|
21913
|
+
const text2 = await res.text();
|
|
21914
|
+
console.error(`Error: failed to get device code (${res.status}): ${text2}`);
|
|
21915
|
+
process.exit(1);
|
|
22024
21916
|
}
|
|
21917
|
+
deviceResp = await res.json();
|
|
21918
|
+
} catch (err) {
|
|
21919
|
+
console.error(`Error: failed to request device code: ${err instanceof Error ? err.message : err}`);
|
|
21920
|
+
process.exit(1);
|
|
21921
|
+
}
|
|
21922
|
+
const verificationUrl = deviceResp.verification_uri_complete || deviceResp.verification_uri;
|
|
21923
|
+
console.log();
|
|
21924
|
+
console.log(` Open this URL in your browser:`);
|
|
21925
|
+
console.log(` ${verificationUrl}`);
|
|
21926
|
+
console.log();
|
|
21927
|
+
console.log(` Enter code: ${deviceResp.user_code}`);
|
|
21928
|
+
console.log();
|
|
21929
|
+
if (!process.stdout.isTTY) {
|
|
21930
|
+
const pollData = JSON.stringify({
|
|
21931
|
+
deviceCode: deviceResp.device_code,
|
|
21932
|
+
interval: (deviceResp.interval || 5) * 1000,
|
|
21933
|
+
expiresIn: deviceResp.expires_in,
|
|
21934
|
+
serverUrl,
|
|
21935
|
+
profile
|
|
21936
|
+
});
|
|
21937
|
+
const thisFile = fileURLToPath3(import.meta.url);
|
|
21938
|
+
const child = fork(thisFile, ["--__login-poll", pollData], {
|
|
21939
|
+
detached: true,
|
|
21940
|
+
stdio: "ignore"
|
|
21941
|
+
});
|
|
21942
|
+
child.unref();
|
|
21943
|
+
console.log(" Polling for authorization in the background (timeout: 5min).");
|
|
21944
|
+
console.log(` Once approved, run \`${cmdPrefix()} status\` to verify.`);
|
|
21945
|
+
return;
|
|
21946
|
+
}
|
|
21947
|
+
openBrowser(verificationUrl);
|
|
21948
|
+
console.log(" (Browser opened automatically)");
|
|
21949
|
+
console.log();
|
|
21950
|
+
console.log("Waiting for authorization...");
|
|
21951
|
+
await pollAndActivate({
|
|
21952
|
+
deviceCode: deviceResp.device_code,
|
|
21953
|
+
interval: (deviceResp.interval || 5) * 1000,
|
|
21954
|
+
expiresIn: deviceResp.expires_in,
|
|
21955
|
+
serverUrl,
|
|
21956
|
+
profile
|
|
21957
|
+
});
|
|
21958
|
+
});
|
|
21959
|
+
return cmd;
|
|
21960
|
+
}
|
|
21961
|
+
|
|
21962
|
+
// commands/status.ts
|
|
21963
|
+
import { Command as Command3 } from "commander";
|
|
21964
|
+
function statusCommand() {
|
|
21965
|
+
const cmd = new Command3("status").description("Show registration status").action((_opts, command) => {
|
|
21966
|
+
const profile = command.parent?.opts().profile;
|
|
21967
|
+
const cfg = loadCLIConfigForProfile(profile);
|
|
21968
|
+
const ws = cfg.watched_workspaces?.[0];
|
|
21969
|
+
if (!ws?.token) {
|
|
21970
|
+
console.log("Not registered");
|
|
21971
|
+
console.log(`Run '${cmdPrefix()} register --token <token>' to register.`);
|
|
21972
|
+
return;
|
|
22025
21973
|
}
|
|
21974
|
+
console.log("Status: Registered");
|
|
21975
|
+
console.log(`Server: ${cfg.server_url}`);
|
|
21976
|
+
console.log(`Workspace: ${ws.name} (${ws.id})`);
|
|
22026
21977
|
});
|
|
22027
|
-
|
|
21978
|
+
return cmd;
|
|
22028
21979
|
}
|
|
22029
21980
|
|
|
22030
21981
|
// commands/daemon.ts
|
|
21982
|
+
import { Command as Command4 } from "commander";
|
|
21983
|
+
import { spawn as spawn8 } from "child_process";
|
|
21984
|
+
import { openSync as openSync3, closeSync as closeSync3, mkdirSync as mkdirSync10 } from "fs";
|
|
21985
|
+
import { dirname as dirname5 } from "path";
|
|
22031
21986
|
var PID_POLL_INTERVAL_MS = 200;
|
|
22032
21987
|
var PID_POLL_TIMEOUT_MS = 2000;
|
|
22033
21988
|
var STOP_POLL_INTERVAL_MS = 200;
|
|
@@ -22062,15 +22017,15 @@ async function startInBackground(profile, serverUrl) {
|
|
|
22062
22017
|
return;
|
|
22063
22018
|
}
|
|
22064
22019
|
const logPath = daemonLogFilePath();
|
|
22065
|
-
|
|
22066
|
-
const logFd =
|
|
22067
|
-
const child =
|
|
22020
|
+
mkdirSync10(dirname5(logPath), { recursive: true, mode: 448 });
|
|
22021
|
+
const logFd = openSync3(logPath, "a", 384);
|
|
22022
|
+
const child = spawn8(process.execPath, buildChildArgs(profile, serverUrl), {
|
|
22068
22023
|
detached: true,
|
|
22069
22024
|
stdio: ["ignore", logFd, logFd],
|
|
22070
22025
|
env: resolveLoginShellEnv()
|
|
22071
22026
|
});
|
|
22072
22027
|
child.unref();
|
|
22073
|
-
|
|
22028
|
+
closeSync3(logFd);
|
|
22074
22029
|
const pid = await waitForPidFile(profile);
|
|
22075
22030
|
if (pid != null) {
|
|
22076
22031
|
console.log(`Daemon started (pid=${pid})`);
|
|
@@ -22183,7 +22138,7 @@ function configCommand() {
|
|
|
22183
22138
|
|
|
22184
22139
|
// commands/email.ts
|
|
22185
22140
|
import { Command as Command6 } from "commander";
|
|
22186
|
-
import { writeFileSync as writeFileSync8, mkdirSync as
|
|
22141
|
+
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync11, readFileSync as readFileSync11 } from "fs";
|
|
22187
22142
|
import { join as join12 } from "path";
|
|
22188
22143
|
import PostalMime from "postal-mime";
|
|
22189
22144
|
|
|
@@ -22400,11 +22355,11 @@ function emailCommand() {
|
|
|
22400
22355
|
printJSON(emails2);
|
|
22401
22356
|
return;
|
|
22402
22357
|
}
|
|
22403
|
-
|
|
22358
|
+
mkdirSync11(emailDir_base, { recursive: true });
|
|
22404
22359
|
const downloadedPaths = [];
|
|
22405
22360
|
for (const email3 of emails2) {
|
|
22406
22361
|
const emailDir = join12(emailDir_base, email3.id);
|
|
22407
|
-
|
|
22362
|
+
mkdirSync11(emailDir, { recursive: true });
|
|
22408
22363
|
const metadata = {
|
|
22409
22364
|
id: email3.id,
|
|
22410
22365
|
from: email3.from_email,
|
|
@@ -22443,7 +22398,7 @@ function emailCommand() {
|
|
|
22443
22398
|
}
|
|
22444
22399
|
if (parsed.attachments && parsed.attachments.length > 0) {
|
|
22445
22400
|
const attDir = join12(emailDir, "attachments");
|
|
22446
|
-
|
|
22401
|
+
mkdirSync11(attDir, { recursive: true });
|
|
22447
22402
|
const usedFilenames = new Set;
|
|
22448
22403
|
for (let i = 0;i < parsed.attachments.length; i++) {
|
|
22449
22404
|
const att = parsed.attachments[i];
|
|
@@ -23332,21 +23287,11 @@ function workspaceCommand() {
|
|
|
23332
23287
|
console.error("Error: JSON must contain a 'members' array with at least one member");
|
|
23333
23288
|
process.exit(1);
|
|
23334
23289
|
}
|
|
23290
|
+
const parentOpts = getRootOpts(command);
|
|
23335
23291
|
let targetWorkspaceId = resolvedWorkspaceId;
|
|
23336
23292
|
if (!targetWorkspaceId) {
|
|
23337
23293
|
const resolved = await resolveWorkspaceId(client, opts.name || config2.name);
|
|
23338
23294
|
targetWorkspaceId = resolved.workspaceId;
|
|
23339
|
-
const parentOpts = getRootOpts(command);
|
|
23340
|
-
const cfg = loadCLIConfigForProfile(parentOpts.profile);
|
|
23341
|
-
const registeredWs = cfg.watched_workspaces?.find((w) => w.status === "registered" && !w.id);
|
|
23342
|
-
if (registeredWs) {
|
|
23343
|
-
try {
|
|
23344
|
-
await client.postJSON("/api/machine-tokens/bind-workspace", { workspace_id: targetWorkspaceId });
|
|
23345
|
-
console.log("Machine token bound to workspace. Waiting for runtime registration...");
|
|
23346
|
-
} catch (err) {
|
|
23347
|
-
console.warn(`Warning: bind-workspace failed: ${err instanceof Error ? err.message : err}`);
|
|
23348
|
-
}
|
|
23349
|
-
}
|
|
23350
23295
|
let runtimes2 = [];
|
|
23351
23296
|
const wsClient = new APIClient(serverUrl, token, targetWorkspaceId);
|
|
23352
23297
|
for (let attempt = 0;attempt < 15; attempt++) {
|