@alook/cli 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ var __export = (target, all) => {
15
15
  };
16
16
 
17
17
  // src/index.ts
18
- import { Command as Command8 } from "commander";
18
+ import { Command as Command9 } from "commander";
19
19
 
20
20
  // commands/register.ts
21
21
  import { Command } from "commander";
@@ -295,9 +295,9 @@ function statusCommand() {
295
295
 
296
296
  // commands/daemon.ts
297
297
  import { Command as Command3 } from "commander";
298
- import { spawn as spawn2 } from "child_process";
298
+ import { spawn as spawn3 } from "child_process";
299
299
  import { openSync as openSync2, closeSync as closeSync2, mkdirSync as mkdirSync4 } from "fs";
300
- import { dirname as dirname3 } from "path";
300
+ import { dirname as dirname4 } from "path";
301
301
 
302
302
  // ../shared/src/constants.ts
303
303
  var TASK_TYPES = {
@@ -13900,11 +13900,13 @@ var TaskApiSchema = TaskApiBaseSchema.extend({
13900
13900
  });
13901
13901
  var PollRequestSchema = exports_external.object({
13902
13902
  daemon_id: exports_external.string().min(1),
13903
- max_tasks: exports_external.number().int().min(1).default(1)
13903
+ max_tasks: exports_external.number().int().min(1).default(1),
13904
+ cli_version: exports_external.string().optional()
13904
13905
  });
13905
13906
  var PollResponseSchema = exports_external.object({
13906
13907
  tasks: exports_external.array(TaskApiSchema),
13907
- evicted: exports_external.boolean().optional()
13908
+ evicted: exports_external.boolean().optional(),
13909
+ pending_update: exports_external.object({ version: exports_external.string() }).optional()
13908
13910
  });
13909
13911
  var RegisterResponseSchema = exports_external.object({
13910
13912
  runtimes: exports_external.array(exports_external.object({ id: exports_external.string() }))
@@ -14007,6 +14009,9 @@ var CalendarEventApiSchema = exports_external.object({
14007
14009
  created_at: exports_external.string(),
14008
14010
  updated_at: exports_external.string()
14009
14011
  });
14012
+ var AddWhitelistRequestSchema = exports_external.object({
14013
+ email: exports_external.string().email()
14014
+ });
14010
14015
  // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@4.20260418.1_@opentelemetry+api@1.9.1_bun-types@1.3.12_kysely@0.28.16/node_modules/drizzle-orm/entity.js
14011
14016
  var entityKind = Symbol.for("drizzle:entityKind");
14012
14017
  var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
@@ -15443,6 +15448,7 @@ var machine = sqliteTable("machine", {
15443
15448
  deviceInfo: text("device_info").notNull().default(""),
15444
15449
  lastSeenAt: text("last_seen_at"),
15445
15450
  createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString()),
15451
+ pendingUpdateVersion: text("pending_update_version"),
15446
15452
  updatedAt: text("updated_at").notNull().$defaultFn(() => new Date().toISOString())
15447
15453
  }, (t) => [primaryKey({ columns: [t.workspaceId, t.daemonId] })]);
15448
15454
  var agentRuntime = sqliteTable("agent_runtime", {
@@ -15626,6 +15632,20 @@ var RESERVED_HANDLES = new Set([
15626
15632
  "system",
15627
15633
  "alook"
15628
15634
  ]);
15635
+ // ../shared/src/semver.ts
15636
+ function semverGte(a, b) {
15637
+ const pa = a.split(".").map(Number);
15638
+ const pb = b.split(".").map(Number);
15639
+ for (let i = 0;i < Math.max(pa.length, pb.length); i++) {
15640
+ const sa = pa[i] ?? 0;
15641
+ const sb = pb[i] ?? 0;
15642
+ if (sa > sb)
15643
+ return true;
15644
+ if (sa < sb)
15645
+ return false;
15646
+ }
15647
+ return true;
15648
+ }
15629
15649
  // daemon/client.ts
15630
15650
  class DaemonClient {
15631
15651
  baseURL;
@@ -15657,10 +15677,14 @@ class DaemonClient {
15657
15677
  daemon_id: daemonId
15658
15678
  });
15659
15679
  }
15660
- async poll(token, daemonId, maxTasks) {
15661
- const raw = await this.request("POST", "/api/daemon/tasks/poll", token, { daemon_id: daemonId, max_tasks: maxTasks });
15680
+ async poll(token, daemonId, maxTasks, cliVersion) {
15681
+ const raw = await this.request("POST", "/api/daemon/tasks/poll", token, { daemon_id: daemonId, max_tasks: maxTasks, ...cliVersion && { cli_version: cliVersion } });
15662
15682
  const resp = PollResponseSchema.parse(raw);
15663
- return { tasks: resp.tasks, evicted: resp.evicted ?? false };
15683
+ return {
15684
+ tasks: resp.tasks,
15685
+ evicted: resp.evicted ?? false,
15686
+ pending_update: resp.pending_update
15687
+ };
15664
15688
  }
15665
15689
  startTask(token, taskId) {
15666
15690
  return this.request("POST", `/api/daemon/tasks/${taskId}/start`, token);
@@ -15680,22 +15704,44 @@ class DaemonClient {
15680
15704
 
15681
15705
  // daemon/config.ts
15682
15706
  import { hostname as hostname4 } from "os";
15683
- import { join as join2 } from "path";
15707
+ import { join as join3 } from "path";
15708
+
15709
+ // lib/version.ts
15710
+ import { readFileSync as readFileSync2 } from "fs";
15711
+ import { join as join2, dirname } from "path";
15712
+ import { fileURLToPath } from "url";
15713
+ function getCurrentVersion() {
15714
+ const __dirname2 = dirname(fileURLToPath(import.meta.url));
15715
+ const candidates = [
15716
+ join2(__dirname2, "..", "package.json"),
15717
+ join2(__dirname2, "..", "..", "package.json")
15718
+ ];
15719
+ for (const candidate of candidates) {
15720
+ try {
15721
+ const pkg = JSON.parse(readFileSync2(candidate, "utf-8"));
15722
+ if (typeof pkg.version === "string")
15723
+ return pkg.version;
15724
+ } catch {}
15725
+ }
15726
+ return "unknown";
15727
+ }
15728
+
15729
+ // daemon/config.ts
15684
15730
  function pidFilePath(profile) {
15685
15731
  const name = profile ? `daemon_${profile}.pid` : "daemon.pid";
15686
- return join2(configDir(), name);
15732
+ return join3(configDir(), name);
15687
15733
  }
15688
15734
  function daemonLogDir() {
15689
- return join2(configDir(), "daemon", "logs");
15735
+ return join3(configDir(), "daemon", "logs");
15690
15736
  }
15691
15737
  function sessionRunnerLogDir() {
15692
- return join2(configDir(), "daemon", "session-runners");
15738
+ return join3(configDir(), "daemon", "session-runners");
15693
15739
  }
15694
15740
  function daemonLogFilePath(date5 = new Date) {
15695
15741
  const y = date5.getFullYear();
15696
15742
  const m = String(date5.getMonth() + 1).padStart(2, "0");
15697
15743
  const d = String(date5.getDate()).padStart(2, "0");
15698
- return join2(daemonLogDir(), `${y}-${m}-${d}.log`);
15744
+ return join3(daemonLogDir(), `${y}-${m}-${d}.log`);
15699
15745
  }
15700
15746
  function parseDuration(s) {
15701
15747
  if (!s)
@@ -15735,7 +15781,7 @@ function loadDaemonConfig(profile) {
15735
15781
  if (profile && !daemonId.endsWith(`-${profile}`)) {
15736
15782
  daemonId = `${daemonId}-${profile}`;
15737
15783
  }
15738
- const defaultRoot = join2(configDir(), profile ? `workspaces_${profile}` : "workspaces");
15784
+ const defaultRoot = join3(configDir(), profile ? `workspaces_${profile}` : "workspaces");
15739
15785
  const workspacesRoot = process.env.ALOOK_WORKSPACES_ROOT || defaultRoot;
15740
15786
  return {
15741
15787
  serverURL: normalizeServerBaseURL(process.env.ALOOK_SERVER_URL || "https://alook.ai"),
@@ -15752,7 +15798,7 @@ function loadDaemonConfig(profile) {
15752
15798
  deviceName: process.env.ALOOK_DAEMON_DEVICE_NAME || h,
15753
15799
  runtimeName: process.env.ALOOK_AGENT_RUNTIME_NAME || "Local Agent",
15754
15800
  workspacesRoot,
15755
- cliVersion: "0.1.0"
15801
+ cliVersion: getCurrentVersion()
15756
15802
  };
15757
15803
  }
15758
15804
  function normalizeServerBaseURL(url2) {
@@ -15918,8 +15964,8 @@ function createLogger2(level) {
15918
15964
  var log = createLogger2();
15919
15965
 
15920
15966
  // daemon/pidfile.ts
15921
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
15922
- import { dirname } from "path";
15967
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
15968
+ import { dirname as dirname2 } from "path";
15923
15969
  function isProcessAlive(pid) {
15924
15970
  try {
15925
15971
  process.kill(pid, 0);
@@ -15930,7 +15976,7 @@ function isProcessAlive(pid) {
15930
15976
  }
15931
15977
  function readDaemonPid(profile) {
15932
15978
  try {
15933
- const content = readFileSync2(pidFilePath(profile), "utf-8").trim();
15979
+ const content = readFileSync3(pidFilePath(profile), "utf-8").trim();
15934
15980
  const pid = parseInt(content, 10);
15935
15981
  return Number.isNaN(pid) ? null : pid;
15936
15982
  } catch {
@@ -15940,14 +15986,14 @@ function readDaemonPid(profile) {
15940
15986
  function acquireDaemonPid(profile) {
15941
15987
  const pidPath = pidFilePath(profile);
15942
15988
  try {
15943
- const content = readFileSync2(pidPath, "utf-8").trim();
15989
+ const content = readFileSync3(pidPath, "utf-8").trim();
15944
15990
  const existingPid = parseInt(content, 10);
15945
15991
  if (!isNaN(existingPid) && isProcessAlive(existingPid)) {
15946
15992
  log.error(`Another daemon is already running (PID ${existingPid}). ` + `Remove ${pidPath} if this is stale.`);
15947
15993
  return false;
15948
15994
  }
15949
15995
  } catch {}
15950
- mkdirSync2(dirname(pidPath), { recursive: true, mode: 448 });
15996
+ mkdirSync2(dirname2(pidPath), { recursive: true, mode: 448 });
15951
15997
  writeFileSync2(pidPath, String(process.pid), { mode: 384 });
15952
15998
  return true;
15953
15999
  }
@@ -15964,13 +16010,71 @@ function releaseDaemonPid(profile) {
15964
16010
  removePidFileIfMatches(process.pid, profile);
15965
16011
  }
15966
16012
 
16013
+ // lib/update.ts
16014
+ import { spawn } from "child_process";
16015
+ function fetchLatestVersion() {
16016
+ return fetch("https://registry.npmjs.org/@alook/cli/latest").then((res) => {
16017
+ if (!res.ok)
16018
+ return null;
16019
+ return res.json();
16020
+ }).then((data) => data?.version ?? null).catch(() => null);
16021
+ }
16022
+ function runNpmUpdate(targetVersion) {
16023
+ return new Promise((resolve) => {
16024
+ const chunks = [];
16025
+ const child = spawn("npm", ["install", "-g", `@alook/cli@${targetVersion}`], {
16026
+ stdio: ["ignore", "pipe", "pipe"]
16027
+ });
16028
+ child.stdout?.on("data", (d) => chunks.push(d));
16029
+ child.stderr?.on("data", (d) => chunks.push(d));
16030
+ child.on("error", (err) => {
16031
+ resolve({ success: false, output: err.message });
16032
+ });
16033
+ child.on("close", (code) => {
16034
+ const output = Buffer.concat(chunks).toString();
16035
+ resolve({ success: code === 0, output });
16036
+ });
16037
+ });
16038
+ }
16039
+
16040
+ // daemon/update-handler.ts
16041
+ var updating = false;
16042
+ var retryCount = 0;
16043
+ var MAX_RETRIES = 3;
16044
+ function isUpdating() {
16045
+ return updating;
16046
+ }
16047
+ async function handleCliUpdate(version3, onSuccess) {
16048
+ if (updating)
16049
+ return;
16050
+ if (retryCount >= MAX_RETRIES)
16051
+ return;
16052
+ updating = true;
16053
+ try {
16054
+ log.info(`Updating CLI to v${version3}...`);
16055
+ const result = await runNpmUpdate(version3);
16056
+ if (result.success) {
16057
+ log.info(`CLI updated to v${version3} — restarting`);
16058
+ onSuccess();
16059
+ } else {
16060
+ retryCount++;
16061
+ log.error(`CLI update failed (attempt ${retryCount}/${MAX_RETRIES}): ${result.output}`);
16062
+ }
16063
+ } catch (e) {
16064
+ retryCount++;
16065
+ log.error(`CLI update error (attempt ${retryCount}/${MAX_RETRIES})`, e);
16066
+ } finally {
16067
+ updating = false;
16068
+ }
16069
+ }
16070
+
15967
16071
  // daemon/daemon.ts
15968
16072
  import { existsSync, mkdirSync as mkdirSync3, openSync, closeSync, renameSync, readdirSync, statSync, unlinkSync as unlinkSync2 } from "fs";
15969
- import { execSync as execSync3, spawn } from "child_process";
15970
- import { fileURLToPath } from "url";
15971
- import { dirname as dirname2, join as join3 } from "path";
15972
- var _dir = dirname2(fileURLToPath(import.meta.url));
15973
- var sessionRunnerPath = existsSync(join3(_dir, "session-runner.js")) ? join3(_dir, "session-runner.js") : join3(_dir, "session-runner.ts");
16073
+ import { execSync as execSync3, spawn as spawn2 } from "child_process";
16074
+ import { fileURLToPath as fileURLToPath2 } from "url";
16075
+ import { dirname as dirname3, join as join4 } from "path";
16076
+ var _dir = dirname3(fileURLToPath2(import.meta.url));
16077
+ var sessionRunnerPath = existsSync(join4(_dir, "session-runner.js")) ? join4(_dir, "session-runner.js") : join4(_dir, "session-runner.ts");
15974
16078
  function isCommandAvailable2(cmd) {
15975
16079
  try {
15976
16080
  execSync3(`which ${cmd}`, { stdio: "ignore" });
@@ -15991,7 +16095,7 @@ function pruneSessionRunnerLogs() {
15991
16095
  if (entries.length <= MAX_SESSION_RUNNER_LOGS)
15992
16096
  return;
15993
16097
  const withMtime = entries.map((name) => {
15994
- const full = join3(logDir, name);
16098
+ const full = join4(logDir, name);
15995
16099
  try {
15996
16100
  return { name, mtime: statSync(full).mtimeMs };
15997
16101
  } catch {
@@ -16001,7 +16105,7 @@ function pruneSessionRunnerLogs() {
16001
16105
  withMtime.sort((a, b) => b.mtime - a.mtime);
16002
16106
  for (const entry of withMtime.slice(MAX_SESSION_RUNNER_LOGS)) {
16003
16107
  try {
16004
- unlinkSync2(join3(logDir, entry.name));
16108
+ unlinkSync2(join4(logDir, entry.name));
16005
16109
  } catch {}
16006
16110
  }
16007
16111
  }
@@ -16147,11 +16251,14 @@ async function startDaemon(profile, serverUrl) {
16147
16251
  await new Promise((r) => setTimeout(r, staggerMs));
16148
16252
  }
16149
16253
  try {
16150
- const { tasks: apiTasks, evicted } = await client.poll(ws.token, config2.daemonId, remaining);
16254
+ const { tasks: apiTasks, evicted, pending_update } = await client.poll(ws.token, config2.daemonId, remaining, config2.cliVersion);
16151
16255
  if (evicted) {
16152
16256
  evictedIds.push(ws.workspaceId);
16153
16257
  continue;
16154
16258
  }
16259
+ if (pending_update && !isUpdating()) {
16260
+ handleCliUpdate(pending_update.version, () => requestRestart());
16261
+ }
16155
16262
  for (const apiTask of apiTasks) {
16156
16263
  const task = fromApiTask(apiTask);
16157
16264
  syncAgentId(task.agentId, ws.workspaceId);
@@ -16176,23 +16283,42 @@ async function startDaemon(profile, serverUrl) {
16176
16283
  };
16177
16284
  const pollTimer = setInterval(pollCycle, config2.pollInterval);
16178
16285
  let shuttingDown = false;
16286
+ let restartRequested = false;
16287
+ const requestRestart = () => {
16288
+ restartRequested = true;
16289
+ shutdown();
16290
+ };
16179
16291
  const shutdown = async () => {
16180
16292
  if (shuttingDown)
16181
16293
  return;
16182
16294
  shuttingDown = true;
16183
- log.info("Shutting down...");
16295
+ log.info(restartRequested ? "Restarting..." : "Shutting down...");
16184
16296
  clearInterval(pollTimer);
16185
- const shutdownMs = Number(process.env.ALOOK_SHUTDOWN_TIMEOUT_MS) || 5000;
16297
+ const shutdownMs = restartRequested ? 30000 : Number(process.env.ALOOK_SHUTDOWN_TIMEOUT_MS) || 5000;
16186
16298
  const timeout = setTimeout(() => process.exit(1), shutdownMs);
16187
16299
  try {
16188
16300
  for (const ws of workspaceStates) {
16189
16301
  await client.deregister(ws.token, config2.daemonId);
16190
16302
  }
16191
16303
  } catch {}
16192
- clearTimeout(timeout);
16193
16304
  releaseDaemonPid(profile);
16194
- health.server.close();
16195
- process.exit(0);
16305
+ health.server.close(() => {
16306
+ if (restartRequested) {
16307
+ const args = ["daemon", "start", "--foreground"];
16308
+ if (profile)
16309
+ args.push("--profile", profile);
16310
+ if (serverUrl)
16311
+ args.push("--server", serverUrl);
16312
+ const child = spawn2("alook", args, {
16313
+ detached: true,
16314
+ stdio: ["ignore", "ignore", "ignore"]
16315
+ });
16316
+ child.unref();
16317
+ log.info(`Spawned new daemon (pid=${child.pid})`);
16318
+ }
16319
+ clearTimeout(timeout);
16320
+ process.exit(0);
16321
+ });
16196
16322
  };
16197
16323
  process.on("SIGTERM", shutdown);
16198
16324
  process.on("SIGINT", shutdown);
@@ -16202,16 +16328,16 @@ function spawnSessionRunner(input) {
16202
16328
  const encoded = Buffer.from(JSON.stringify(input)).toString("base64");
16203
16329
  const logDir = sessionRunnerLogDir();
16204
16330
  mkdirSync3(logDir, { recursive: true });
16205
- const tmpLogPath = join3(logDir, `${input.task.id}.log`);
16331
+ const tmpLogPath = join4(logDir, `${input.task.id}.log`);
16206
16332
  const fd = openSync(tmpLogPath, "a");
16207
- const child = spawn(process.execPath, [sessionRunnerPath, encoded], {
16333
+ const child = spawn2(process.execPath, [sessionRunnerPath, encoded], {
16208
16334
  detached: true,
16209
16335
  stdio: ["ignore", fd, fd]
16210
16336
  });
16211
16337
  child.unref();
16212
16338
  closeSync(fd);
16213
16339
  if (child.pid) {
16214
- const pidLogPath = join3(logDir, `${child.pid}.log`);
16340
+ const pidLogPath = join4(logDir, `${child.pid}.log`);
16215
16341
  renameSync(tmpLogPath, pidLogPath);
16216
16342
  }
16217
16343
  return child;
@@ -16286,9 +16412,9 @@ async function startInBackground(profile, serverUrl) {
16286
16412
  return;
16287
16413
  }
16288
16414
  const logPath = daemonLogFilePath();
16289
- mkdirSync4(dirname3(logPath), { recursive: true, mode: 448 });
16415
+ mkdirSync4(dirname4(logPath), { recursive: true, mode: 448 });
16290
16416
  const logFd = openSync2(logPath, "a", 384);
16291
- const child = spawn2(process.execPath, buildChildArgs(profile, serverUrl), {
16417
+ const child = spawn3(process.execPath, buildChildArgs(profile, serverUrl), {
16292
16418
  detached: true,
16293
16419
  stdio: ["ignore", logFd, logFd]
16294
16420
  });
@@ -16396,8 +16522,8 @@ function configCommand() {
16396
16522
 
16397
16523
  // commands/email.ts
16398
16524
  import { Command as Command5 } from "commander";
16399
- import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
16400
- import { basename, join as join4 } from "path";
16525
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
16526
+ import { basename, join as join5 } from "path";
16401
16527
  import PostalMime from "postal-mime";
16402
16528
  var VALID_STATUSES = ["unread", "read", "archived"];
16403
16529
  var EMAIL_DIR = "/tmp/alook-emails";
@@ -16473,7 +16599,7 @@ function emailCommand() {
16473
16599
  mkdirSync5(EMAIL_DIR, { recursive: true });
16474
16600
  const downloadedPaths = [];
16475
16601
  for (const email3 of emails2) {
16476
- const emailDir = join4(EMAIL_DIR, email3.id);
16602
+ const emailDir = join5(EMAIL_DIR, email3.id);
16477
16603
  mkdirSync5(emailDir, { recursive: true });
16478
16604
  const metadata = {
16479
16605
  id: email3.id,
@@ -16486,7 +16612,7 @@ function emailCommand() {
16486
16612
  in_reply_to: email3.in_reply_to || "",
16487
16613
  references: email3.references || ""
16488
16614
  };
16489
- const metadataPath = join4(emailDir, "metadata.json");
16615
+ const metadataPath = join5(emailDir, "metadata.json");
16490
16616
  writeFileSync3(metadataPath, JSON.stringify(metadata, null, 2));
16491
16617
  downloadedPaths.push(metadataPath);
16492
16618
  let rawMime;
@@ -16502,17 +16628,17 @@ function emailCommand() {
16502
16628
  }
16503
16629
  const parsed = await new PostalMime().parse(rawMime);
16504
16630
  if (parsed.text) {
16505
- const bodyPath = join4(emailDir, "body.txt");
16631
+ const bodyPath = join5(emailDir, "body.txt");
16506
16632
  writeFileSync3(bodyPath, parsed.text);
16507
16633
  downloadedPaths.push(bodyPath);
16508
16634
  }
16509
16635
  if (parsed.html) {
16510
- const htmlPath = join4(emailDir, "body.html");
16636
+ const htmlPath = join5(emailDir, "body.html");
16511
16637
  writeFileSync3(htmlPath, parsed.html);
16512
16638
  downloadedPaths.push(htmlPath);
16513
16639
  }
16514
16640
  if (parsed.attachments && parsed.attachments.length > 0) {
16515
- const attDir = join4(emailDir, "attachments");
16641
+ const attDir = join5(emailDir, "attachments");
16516
16642
  mkdirSync5(attDir, { recursive: true });
16517
16643
  const usedFilenames = new Set;
16518
16644
  for (let i = 0;i < parsed.attachments.length; i++) {
@@ -16522,7 +16648,7 @@ function emailCommand() {
16522
16648
  filename = `${i}-${filename}`;
16523
16649
  }
16524
16650
  usedFilenames.add(filename);
16525
- const attPath = join4(attDir, filename);
16651
+ const attPath = join5(attDir, filename);
16526
16652
  const content = att.content;
16527
16653
  let buf;
16528
16654
  if (typeof content === "string") {
@@ -16571,7 +16697,7 @@ function emailCommand() {
16571
16697
  const client = new APIClient(serverUrl, token, workspaceId);
16572
16698
  let htmlBody;
16573
16699
  try {
16574
- htmlBody = readFileSync3(opts.bodyFile, "utf-8");
16700
+ htmlBody = readFileSync4(opts.bodyFile, "utf-8");
16575
16701
  } catch (err) {
16576
16702
  console.error(`Error: cannot read body file "${opts.bodyFile}": ${err instanceof Error ? err.message : err}`);
16577
16703
  process.exit(1);
@@ -16587,7 +16713,7 @@ function emailCommand() {
16587
16713
  let bytes;
16588
16714
  let size;
16589
16715
  try {
16590
- bytes = readFileSync3(path);
16716
+ bytes = readFileSync4(path);
16591
16717
  size = statSync2(path).size;
16592
16718
  } catch (err) {
16593
16719
  console.error(`Error: cannot read attachment "${path}": ${err instanceof Error ? err.message : err}`);
@@ -16846,28 +16972,6 @@ function calendarCommand() {
16846
16972
 
16847
16973
  // commands/version.ts
16848
16974
  import { Command as Command7 } from "commander";
16849
-
16850
- // lib/version.ts
16851
- import { readFileSync as readFileSync4 } from "fs";
16852
- import { join as join5, dirname as dirname4 } from "path";
16853
- import { fileURLToPath as fileURLToPath2 } from "url";
16854
- function getCurrentVersion() {
16855
- const __dirname2 = dirname4(fileURLToPath2(import.meta.url));
16856
- const candidates = [
16857
- join5(__dirname2, "..", "package.json"),
16858
- join5(__dirname2, "..", "..", "package.json")
16859
- ];
16860
- for (const candidate of candidates) {
16861
- try {
16862
- const pkg = JSON.parse(readFileSync4(candidate, "utf-8"));
16863
- if (typeof pkg.version === "string")
16864
- return pkg.version;
16865
- } catch {}
16866
- }
16867
- return "unknown";
16868
- }
16869
-
16870
- // commands/version.ts
16871
16975
  function versionCommand() {
16872
16976
  const cmd = new Command7("version").description("Show CLI version").action(() => {
16873
16977
  console.log(`alook version ${getCurrentVersion()}`);
@@ -16875,8 +16979,44 @@ function versionCommand() {
16875
16979
  return cmd;
16876
16980
  }
16877
16981
 
16982
+ // commands/update.ts
16983
+ import { Command as Command8 } from "commander";
16984
+ function updateCommand() {
16985
+ const cmd = new Command8("update").description("Update CLI to the latest version").action(async () => {
16986
+ const current = getCurrentVersion();
16987
+ console.log(`Current version: ${current}`);
16988
+ const latest = await fetchLatestVersion();
16989
+ if (!latest) {
16990
+ console.error("Failed to fetch latest version from npm registry.");
16991
+ process.exit(1);
16992
+ return;
16993
+ }
16994
+ if (semverGte(current, latest)) {
16995
+ console.log(`Already up to date (v${current}).`);
16996
+ return;
16997
+ }
16998
+ const healthPort = Number(process.env.ALOOK_HEALTH_PORT) || 19514;
16999
+ try {
17000
+ const res = await fetch(`http://127.0.0.1:${healthPort}/health`);
17001
+ if (res.ok) {
17002
+ console.warn("Warning: daemon is running on the old version. After update, restart with: alook daemon restart");
17003
+ }
17004
+ } catch {}
17005
+ console.log(`Updating to v${latest}...`);
17006
+ const result = await runNpmUpdate(latest);
17007
+ if (result.success) {
17008
+ console.log(`Updated successfully: v${current} → v${latest}`);
17009
+ } else {
17010
+ console.error(`Update failed:
17011
+ ${result.output}`);
17012
+ process.exit(1);
17013
+ }
17014
+ });
17015
+ return cmd;
17016
+ }
17017
+
16878
17018
  // src/index.ts
16879
- var program = new Command8;
17019
+ var program = new Command9;
16880
17020
  program.name("alook").description("Alook CLI").option("--server <url>", "Server URL").option("--profile <name>", "Profile name");
16881
17021
  program.addCommand(registerCommand());
16882
17022
  program.addCommand(statusCommand());
@@ -16885,4 +17025,5 @@ program.addCommand(emailCommand());
16885
17025
  program.addCommand(calendarCommand());
16886
17026
  program.addCommand(configCommand());
16887
17027
  program.addCommand(versionCommand());
17028
+ program.addCommand(updateCommand());
16888
17029
  program.parse();
@@ -13617,11 +13617,13 @@ var TaskApiSchema = TaskApiBaseSchema.extend({
13617
13617
  });
13618
13618
  var PollRequestSchema = exports_external.object({
13619
13619
  daemon_id: exports_external.string().min(1),
13620
- max_tasks: exports_external.number().int().min(1).default(1)
13620
+ max_tasks: exports_external.number().int().min(1).default(1),
13621
+ cli_version: exports_external.string().optional()
13621
13622
  });
13622
13623
  var PollResponseSchema = exports_external.object({
13623
13624
  tasks: exports_external.array(TaskApiSchema),
13624
- evicted: exports_external.boolean().optional()
13625
+ evicted: exports_external.boolean().optional(),
13626
+ pending_update: exports_external.object({ version: exports_external.string() }).optional()
13625
13627
  });
13626
13628
  var RegisterResponseSchema = exports_external.object({
13627
13629
  runtimes: exports_external.array(exports_external.object({ id: exports_external.string() }))
@@ -13724,6 +13726,9 @@ var CalendarEventApiSchema = exports_external.object({
13724
13726
  created_at: exports_external.string(),
13725
13727
  updated_at: exports_external.string()
13726
13728
  });
13729
+ var AddWhitelistRequestSchema = exports_external.object({
13730
+ email: exports_external.string().email()
13731
+ });
13727
13732
  // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@4.20260418.1_@opentelemetry+api@1.9.1_bun-types@1.3.12_kysely@0.28.16/node_modules/drizzle-orm/entity.js
13728
13733
  var entityKind = Symbol.for("drizzle:entityKind");
13729
13734
  var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
@@ -15160,6 +15165,7 @@ var machine = sqliteTable("machine", {
15160
15165
  deviceInfo: text("device_info").notNull().default(""),
15161
15166
  lastSeenAt: text("last_seen_at"),
15162
15167
  createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString()),
15168
+ pendingUpdateVersion: text("pending_update_version"),
15163
15169
  updatedAt: text("updated_at").notNull().$defaultFn(() => new Date().toISOString())
15164
15170
  }, (t) => [primaryKey({ columns: [t.workspaceId, t.daemonId] })]);
15165
15171
  var agentRuntime = sqliteTable("agent_runtime", {
@@ -15377,10 +15383,14 @@ class DaemonClient {
15377
15383
  daemon_id: daemonId
15378
15384
  });
15379
15385
  }
15380
- async poll(token, daemonId, maxTasks) {
15381
- const raw = await this.request("POST", "/api/daemon/tasks/poll", token, { daemon_id: daemonId, max_tasks: maxTasks });
15386
+ async poll(token, daemonId, maxTasks, cliVersion) {
15387
+ const raw = await this.request("POST", "/api/daemon/tasks/poll", token, { daemon_id: daemonId, max_tasks: maxTasks, ...cliVersion && { cli_version: cliVersion } });
15382
15388
  const resp = PollResponseSchema.parse(raw);
15383
- return { tasks: resp.tasks, evicted: resp.evicted ?? false };
15389
+ return {
15390
+ tasks: resp.tasks,
15391
+ evicted: resp.evicted ?? false,
15392
+ pending_update: resp.pending_update
15393
+ };
15384
15394
  }
15385
15395
  startTask(token, taskId) {
15386
15396
  return this.request("POST", `/api/daemon/tasks/${taskId}/start`, token);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alook/cli",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "Alook CLI — register and run always-on AI coding agents.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/alookai/alook#readme",