@alook/cli 0.0.6 → 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,10 +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
- tasks: exports_external.array(TaskApiSchema)
13907
+ tasks: exports_external.array(TaskApiSchema),
13908
+ evicted: exports_external.boolean().optional(),
13909
+ pending_update: exports_external.object({ version: exports_external.string() }).optional()
13907
13910
  });
13908
13911
  var RegisterResponseSchema = exports_external.object({
13909
13912
  runtimes: exports_external.array(exports_external.object({ id: exports_external.string() }))
@@ -14006,6 +14009,9 @@ var CalendarEventApiSchema = exports_external.object({
14006
14009
  created_at: exports_external.string(),
14007
14010
  updated_at: exports_external.string()
14008
14011
  });
14012
+ var AddWhitelistRequestSchema = exports_external.object({
14013
+ email: exports_external.string().email()
14014
+ });
14009
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
14010
14016
  var entityKind = Symbol.for("drizzle:entityKind");
14011
14017
  var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
@@ -15442,6 +15448,7 @@ var machine = sqliteTable("machine", {
15442
15448
  deviceInfo: text("device_info").notNull().default(""),
15443
15449
  lastSeenAt: text("last_seen_at"),
15444
15450
  createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString()),
15451
+ pendingUpdateVersion: text("pending_update_version"),
15445
15452
  updatedAt: text("updated_at").notNull().$defaultFn(() => new Date().toISOString())
15446
15453
  }, (t) => [primaryKey({ columns: [t.workspaceId, t.daemonId] })]);
15447
15454
  var agentRuntime = sqliteTable("agent_runtime", {
@@ -15625,6 +15632,20 @@ var RESERVED_HANDLES = new Set([
15625
15632
  "system",
15626
15633
  "alook"
15627
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
+ }
15628
15649
  // daemon/client.ts
15629
15650
  class DaemonClient {
15630
15651
  baseURL;
@@ -15656,10 +15677,14 @@ class DaemonClient {
15656
15677
  daemon_id: daemonId
15657
15678
  });
15658
15679
  }
15659
- async poll(token, daemonId, maxTasks) {
15660
- 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 } });
15661
15682
  const resp = PollResponseSchema.parse(raw);
15662
- return resp.tasks;
15683
+ return {
15684
+ tasks: resp.tasks,
15685
+ evicted: resp.evicted ?? false,
15686
+ pending_update: resp.pending_update
15687
+ };
15663
15688
  }
15664
15689
  startTask(token, taskId) {
15665
15690
  return this.request("POST", `/api/daemon/tasks/${taskId}/start`, token);
@@ -15679,22 +15704,44 @@ class DaemonClient {
15679
15704
 
15680
15705
  // daemon/config.ts
15681
15706
  import { hostname as hostname4 } from "os";
15682
- 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
15683
15730
  function pidFilePath(profile) {
15684
15731
  const name = profile ? `daemon_${profile}.pid` : "daemon.pid";
15685
- return join2(configDir(), name);
15732
+ return join3(configDir(), name);
15686
15733
  }
15687
15734
  function daemonLogDir() {
15688
- return join2(configDir(), "daemon", "logs");
15735
+ return join3(configDir(), "daemon", "logs");
15689
15736
  }
15690
15737
  function sessionRunnerLogDir() {
15691
- return join2(configDir(), "daemon", "session-runners");
15738
+ return join3(configDir(), "daemon", "session-runners");
15692
15739
  }
15693
15740
  function daemonLogFilePath(date5 = new Date) {
15694
15741
  const y = date5.getFullYear();
15695
15742
  const m = String(date5.getMonth() + 1).padStart(2, "0");
15696
15743
  const d = String(date5.getDate()).padStart(2, "0");
15697
- return join2(daemonLogDir(), `${y}-${m}-${d}.log`);
15744
+ return join3(daemonLogDir(), `${y}-${m}-${d}.log`);
15698
15745
  }
15699
15746
  function parseDuration(s) {
15700
15747
  if (!s)
@@ -15734,7 +15781,7 @@ function loadDaemonConfig(profile) {
15734
15781
  if (profile && !daemonId.endsWith(`-${profile}`)) {
15735
15782
  daemonId = `${daemonId}-${profile}`;
15736
15783
  }
15737
- const defaultRoot = join2(configDir(), profile ? `workspaces_${profile}` : "workspaces");
15784
+ const defaultRoot = join3(configDir(), profile ? `workspaces_${profile}` : "workspaces");
15738
15785
  const workspacesRoot = process.env.ALOOK_WORKSPACES_ROOT || defaultRoot;
15739
15786
  return {
15740
15787
  serverURL: normalizeServerBaseURL(process.env.ALOOK_SERVER_URL || "https://alook.ai"),
@@ -15751,7 +15798,7 @@ function loadDaemonConfig(profile) {
15751
15798
  deviceName: process.env.ALOOK_DAEMON_DEVICE_NAME || h,
15752
15799
  runtimeName: process.env.ALOOK_AGENT_RUNTIME_NAME || "Local Agent",
15753
15800
  workspacesRoot,
15754
- cliVersion: "0.1.0"
15801
+ cliVersion: getCurrentVersion()
15755
15802
  };
15756
15803
  }
15757
15804
  function normalizeServerBaseURL(url2) {
@@ -15917,8 +15964,8 @@ function createLogger2(level) {
15917
15964
  var log = createLogger2();
15918
15965
 
15919
15966
  // daemon/pidfile.ts
15920
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
15921
- 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";
15922
15969
  function isProcessAlive(pid) {
15923
15970
  try {
15924
15971
  process.kill(pid, 0);
@@ -15929,7 +15976,7 @@ function isProcessAlive(pid) {
15929
15976
  }
15930
15977
  function readDaemonPid(profile) {
15931
15978
  try {
15932
- const content = readFileSync2(pidFilePath(profile), "utf-8").trim();
15979
+ const content = readFileSync3(pidFilePath(profile), "utf-8").trim();
15933
15980
  const pid = parseInt(content, 10);
15934
15981
  return Number.isNaN(pid) ? null : pid;
15935
15982
  } catch {
@@ -15939,14 +15986,14 @@ function readDaemonPid(profile) {
15939
15986
  function acquireDaemonPid(profile) {
15940
15987
  const pidPath = pidFilePath(profile);
15941
15988
  try {
15942
- const content = readFileSync2(pidPath, "utf-8").trim();
15989
+ const content = readFileSync3(pidPath, "utf-8").trim();
15943
15990
  const existingPid = parseInt(content, 10);
15944
15991
  if (!isNaN(existingPid) && isProcessAlive(existingPid)) {
15945
15992
  log.error(`Another daemon is already running (PID ${existingPid}). ` + `Remove ${pidPath} if this is stale.`);
15946
15993
  return false;
15947
15994
  }
15948
15995
  } catch {}
15949
- mkdirSync2(dirname(pidPath), { recursive: true, mode: 448 });
15996
+ mkdirSync2(dirname2(pidPath), { recursive: true, mode: 448 });
15950
15997
  writeFileSync2(pidPath, String(process.pid), { mode: 384 });
15951
15998
  return true;
15952
15999
  }
@@ -15963,13 +16010,71 @@ function releaseDaemonPid(profile) {
15963
16010
  removePidFileIfMatches(process.pid, profile);
15964
16011
  }
15965
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
+
15966
16071
  // daemon/daemon.ts
15967
16072
  import { existsSync, mkdirSync as mkdirSync3, openSync, closeSync, renameSync, readdirSync, statSync, unlinkSync as unlinkSync2 } from "fs";
15968
- import { execSync as execSync3, spawn } from "child_process";
15969
- import { fileURLToPath } from "url";
15970
- import { dirname as dirname2, join as join3 } from "path";
15971
- var _dir = dirname2(fileURLToPath(import.meta.url));
15972
- 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");
15973
16078
  function isCommandAvailable2(cmd) {
15974
16079
  try {
15975
16080
  execSync3(`which ${cmd}`, { stdio: "ignore" });
@@ -15990,7 +16095,7 @@ function pruneSessionRunnerLogs() {
15990
16095
  if (entries.length <= MAX_SESSION_RUNNER_LOGS)
15991
16096
  return;
15992
16097
  const withMtime = entries.map((name) => {
15993
- const full = join3(logDir, name);
16098
+ const full = join4(logDir, name);
15994
16099
  try {
15995
16100
  return { name, mtime: statSync(full).mtimeMs };
15996
16101
  } catch {
@@ -16000,7 +16105,7 @@ function pruneSessionRunnerLogs() {
16000
16105
  withMtime.sort((a, b) => b.mtime - a.mtime);
16001
16106
  for (const entry of withMtime.slice(MAX_SESSION_RUNNER_LOGS)) {
16002
16107
  try {
16003
- unlinkSync2(join3(logDir, entry.name));
16108
+ unlinkSync2(join4(logDir, entry.name));
16004
16109
  } catch {}
16005
16110
  }
16006
16111
  }
@@ -16114,12 +16219,30 @@ async function startDaemon(profile, serverUrl) {
16114
16219
  }
16115
16220
  } catch {}
16116
16221
  }
16222
+ function evictWorkspace(workspaceId) {
16223
+ const idx = workspaceStates.findIndex((ws2) => ws2.workspaceId === workspaceId);
16224
+ if (idx === -1)
16225
+ return;
16226
+ const ws = workspaceStates[idx];
16227
+ for (const rid of ws.runtimeIds) {
16228
+ runtimeIndex.delete(rid);
16229
+ }
16230
+ workspaceStates.splice(idx, 1);
16231
+ health.setRuntimeCount(workspaceStates.reduce((sum, w) => sum + w.runtimeIds.length, 0));
16232
+ try {
16233
+ const cfg = loadCLIConfigForProfile(profile);
16234
+ cfg.watched_workspaces = (cfg.watched_workspaces || []).filter((w) => w.id !== workspaceId);
16235
+ saveCLIConfigForProfile(profile, cfg);
16236
+ } catch {}
16237
+ log.info(`Workspace ${workspaceId} evicted — runtimes removed server-side`);
16238
+ }
16117
16239
  const pollCycle = async () => {
16118
16240
  let remaining = config2.maxConcurrentTasks - activeTasks.size;
16119
16241
  if (remaining <= 0)
16120
16242
  return;
16121
16243
  const N = workspaceStates.length;
16122
16244
  const staggerMs = N > 1 ? Math.floor(config2.pollInterval / N) : 0;
16245
+ const evictedIds = [];
16123
16246
  for (let i = 0;i < N; i++) {
16124
16247
  if (remaining <= 0)
16125
16248
  break;
@@ -16128,8 +16251,15 @@ async function startDaemon(profile, serverUrl) {
16128
16251
  await new Promise((r) => setTimeout(r, staggerMs));
16129
16252
  }
16130
16253
  try {
16131
- const tasks = await client.poll(ws.token, config2.daemonId, remaining);
16132
- for (const apiTask of tasks) {
16254
+ const { tasks: apiTasks, evicted, pending_update } = await client.poll(ws.token, config2.daemonId, remaining, config2.cliVersion);
16255
+ if (evicted) {
16256
+ evictedIds.push(ws.workspaceId);
16257
+ continue;
16258
+ }
16259
+ if (pending_update && !isUpdating()) {
16260
+ handleCliUpdate(pending_update.version, () => requestRestart());
16261
+ }
16262
+ for (const apiTask of apiTasks) {
16133
16263
  const task = fromApiTask(apiTask);
16134
16264
  syncAgentId(task.agentId, ws.workspaceId);
16135
16265
  activeTasks.add(task.id);
@@ -16143,26 +16273,52 @@ async function startDaemon(profile, serverUrl) {
16143
16273
  log.debug("Poll error", e);
16144
16274
  }
16145
16275
  }
16276
+ for (const id of evictedIds) {
16277
+ evictWorkspace(id);
16278
+ }
16279
+ if (workspaceStates.length === 0) {
16280
+ log.info("All workspaces evicted — shutting down");
16281
+ shutdown();
16282
+ }
16146
16283
  };
16147
16284
  const pollTimer = setInterval(pollCycle, config2.pollInterval);
16148
16285
  let shuttingDown = false;
16286
+ let restartRequested = false;
16287
+ const requestRestart = () => {
16288
+ restartRequested = true;
16289
+ shutdown();
16290
+ };
16149
16291
  const shutdown = async () => {
16150
16292
  if (shuttingDown)
16151
16293
  return;
16152
16294
  shuttingDown = true;
16153
- log.info("Shutting down...");
16295
+ log.info(restartRequested ? "Restarting..." : "Shutting down...");
16154
16296
  clearInterval(pollTimer);
16155
- const shutdownMs = Number(process.env.ALOOK_SHUTDOWN_TIMEOUT_MS) || 5000;
16297
+ const shutdownMs = restartRequested ? 30000 : Number(process.env.ALOOK_SHUTDOWN_TIMEOUT_MS) || 5000;
16156
16298
  const timeout = setTimeout(() => process.exit(1), shutdownMs);
16157
16299
  try {
16158
16300
  for (const ws of workspaceStates) {
16159
16301
  await client.deregister(ws.token, config2.daemonId);
16160
16302
  }
16161
16303
  } catch {}
16162
- clearTimeout(timeout);
16163
16304
  releaseDaemonPid(profile);
16164
- health.server.close();
16165
- 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
+ });
16166
16322
  };
16167
16323
  process.on("SIGTERM", shutdown);
16168
16324
  process.on("SIGINT", shutdown);
@@ -16172,16 +16328,16 @@ function spawnSessionRunner(input) {
16172
16328
  const encoded = Buffer.from(JSON.stringify(input)).toString("base64");
16173
16329
  const logDir = sessionRunnerLogDir();
16174
16330
  mkdirSync3(logDir, { recursive: true });
16175
- const tmpLogPath = join3(logDir, `${input.task.id}.log`);
16331
+ const tmpLogPath = join4(logDir, `${input.task.id}.log`);
16176
16332
  const fd = openSync(tmpLogPath, "a");
16177
- const child = spawn(process.execPath, [sessionRunnerPath, encoded], {
16333
+ const child = spawn2(process.execPath, [sessionRunnerPath, encoded], {
16178
16334
  detached: true,
16179
16335
  stdio: ["ignore", fd, fd]
16180
16336
  });
16181
16337
  child.unref();
16182
16338
  closeSync(fd);
16183
16339
  if (child.pid) {
16184
- const pidLogPath = join3(logDir, `${child.pid}.log`);
16340
+ const pidLogPath = join4(logDir, `${child.pid}.log`);
16185
16341
  renameSync(tmpLogPath, pidLogPath);
16186
16342
  }
16187
16343
  return child;
@@ -16256,9 +16412,9 @@ async function startInBackground(profile, serverUrl) {
16256
16412
  return;
16257
16413
  }
16258
16414
  const logPath = daemonLogFilePath();
16259
- mkdirSync4(dirname3(logPath), { recursive: true, mode: 448 });
16415
+ mkdirSync4(dirname4(logPath), { recursive: true, mode: 448 });
16260
16416
  const logFd = openSync2(logPath, "a", 384);
16261
- const child = spawn2(process.execPath, buildChildArgs(profile, serverUrl), {
16417
+ const child = spawn3(process.execPath, buildChildArgs(profile, serverUrl), {
16262
16418
  detached: true,
16263
16419
  stdio: ["ignore", logFd, logFd]
16264
16420
  });
@@ -16366,8 +16522,8 @@ function configCommand() {
16366
16522
 
16367
16523
  // commands/email.ts
16368
16524
  import { Command as Command5 } from "commander";
16369
- import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
16370
- 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";
16371
16527
  import PostalMime from "postal-mime";
16372
16528
  var VALID_STATUSES = ["unread", "read", "archived"];
16373
16529
  var EMAIL_DIR = "/tmp/alook-emails";
@@ -16443,7 +16599,7 @@ function emailCommand() {
16443
16599
  mkdirSync5(EMAIL_DIR, { recursive: true });
16444
16600
  const downloadedPaths = [];
16445
16601
  for (const email3 of emails2) {
16446
- const emailDir = join4(EMAIL_DIR, email3.id);
16602
+ const emailDir = join5(EMAIL_DIR, email3.id);
16447
16603
  mkdirSync5(emailDir, { recursive: true });
16448
16604
  const metadata = {
16449
16605
  id: email3.id,
@@ -16456,7 +16612,7 @@ function emailCommand() {
16456
16612
  in_reply_to: email3.in_reply_to || "",
16457
16613
  references: email3.references || ""
16458
16614
  };
16459
- const metadataPath = join4(emailDir, "metadata.json");
16615
+ const metadataPath = join5(emailDir, "metadata.json");
16460
16616
  writeFileSync3(metadataPath, JSON.stringify(metadata, null, 2));
16461
16617
  downloadedPaths.push(metadataPath);
16462
16618
  let rawMime;
@@ -16472,17 +16628,17 @@ function emailCommand() {
16472
16628
  }
16473
16629
  const parsed = await new PostalMime().parse(rawMime);
16474
16630
  if (parsed.text) {
16475
- const bodyPath = join4(emailDir, "body.txt");
16631
+ const bodyPath = join5(emailDir, "body.txt");
16476
16632
  writeFileSync3(bodyPath, parsed.text);
16477
16633
  downloadedPaths.push(bodyPath);
16478
16634
  }
16479
16635
  if (parsed.html) {
16480
- const htmlPath = join4(emailDir, "body.html");
16636
+ const htmlPath = join5(emailDir, "body.html");
16481
16637
  writeFileSync3(htmlPath, parsed.html);
16482
16638
  downloadedPaths.push(htmlPath);
16483
16639
  }
16484
16640
  if (parsed.attachments && parsed.attachments.length > 0) {
16485
- const attDir = join4(emailDir, "attachments");
16641
+ const attDir = join5(emailDir, "attachments");
16486
16642
  mkdirSync5(attDir, { recursive: true });
16487
16643
  const usedFilenames = new Set;
16488
16644
  for (let i = 0;i < parsed.attachments.length; i++) {
@@ -16492,7 +16648,7 @@ function emailCommand() {
16492
16648
  filename = `${i}-${filename}`;
16493
16649
  }
16494
16650
  usedFilenames.add(filename);
16495
- const attPath = join4(attDir, filename);
16651
+ const attPath = join5(attDir, filename);
16496
16652
  const content = att.content;
16497
16653
  let buf;
16498
16654
  if (typeof content === "string") {
@@ -16541,7 +16697,7 @@ function emailCommand() {
16541
16697
  const client = new APIClient(serverUrl, token, workspaceId);
16542
16698
  let htmlBody;
16543
16699
  try {
16544
- htmlBody = readFileSync3(opts.bodyFile, "utf-8");
16700
+ htmlBody = readFileSync4(opts.bodyFile, "utf-8");
16545
16701
  } catch (err) {
16546
16702
  console.error(`Error: cannot read body file "${opts.bodyFile}": ${err instanceof Error ? err.message : err}`);
16547
16703
  process.exit(1);
@@ -16557,7 +16713,7 @@ function emailCommand() {
16557
16713
  let bytes;
16558
16714
  let size;
16559
16715
  try {
16560
- bytes = readFileSync3(path);
16716
+ bytes = readFileSync4(path);
16561
16717
  size = statSync2(path).size;
16562
16718
  } catch (err) {
16563
16719
  console.error(`Error: cannot read attachment "${path}": ${err instanceof Error ? err.message : err}`);
@@ -16816,28 +16972,6 @@ function calendarCommand() {
16816
16972
 
16817
16973
  // commands/version.ts
16818
16974
  import { Command as Command7 } from "commander";
16819
-
16820
- // lib/version.ts
16821
- import { readFileSync as readFileSync4 } from "fs";
16822
- import { join as join5, dirname as dirname4 } from "path";
16823
- import { fileURLToPath as fileURLToPath2 } from "url";
16824
- function getCurrentVersion() {
16825
- const __dirname2 = dirname4(fileURLToPath2(import.meta.url));
16826
- const candidates = [
16827
- join5(__dirname2, "..", "package.json"),
16828
- join5(__dirname2, "..", "..", "package.json")
16829
- ];
16830
- for (const candidate of candidates) {
16831
- try {
16832
- const pkg = JSON.parse(readFileSync4(candidate, "utf-8"));
16833
- if (typeof pkg.version === "string")
16834
- return pkg.version;
16835
- } catch {}
16836
- }
16837
- return "unknown";
16838
- }
16839
-
16840
- // commands/version.ts
16841
16975
  function versionCommand() {
16842
16976
  const cmd = new Command7("version").description("Show CLI version").action(() => {
16843
16977
  console.log(`alook version ${getCurrentVersion()}`);
@@ -16845,8 +16979,44 @@ function versionCommand() {
16845
16979
  return cmd;
16846
16980
  }
16847
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
+
16848
17018
  // src/index.ts
16849
- var program = new Command8;
17019
+ var program = new Command9;
16850
17020
  program.name("alook").description("Alook CLI").option("--server <url>", "Server URL").option("--profile <name>", "Profile name");
16851
17021
  program.addCommand(registerCommand());
16852
17022
  program.addCommand(statusCommand());
@@ -16855,4 +17025,5 @@ program.addCommand(emailCommand());
16855
17025
  program.addCommand(calendarCommand());
16856
17026
  program.addCommand(configCommand());
16857
17027
  program.addCommand(versionCommand());
17028
+ program.addCommand(updateCommand());
16858
17029
  program.parse();
@@ -13617,10 +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
- tasks: exports_external.array(TaskApiSchema)
13624
+ tasks: exports_external.array(TaskApiSchema),
13625
+ evicted: exports_external.boolean().optional(),
13626
+ pending_update: exports_external.object({ version: exports_external.string() }).optional()
13624
13627
  });
13625
13628
  var RegisterResponseSchema = exports_external.object({
13626
13629
  runtimes: exports_external.array(exports_external.object({ id: exports_external.string() }))
@@ -13723,6 +13726,9 @@ var CalendarEventApiSchema = exports_external.object({
13723
13726
  created_at: exports_external.string(),
13724
13727
  updated_at: exports_external.string()
13725
13728
  });
13729
+ var AddWhitelistRequestSchema = exports_external.object({
13730
+ email: exports_external.string().email()
13731
+ });
13726
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
13727
13733
  var entityKind = Symbol.for("drizzle:entityKind");
13728
13734
  var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
@@ -15159,6 +15165,7 @@ var machine = sqliteTable("machine", {
15159
15165
  deviceInfo: text("device_info").notNull().default(""),
15160
15166
  lastSeenAt: text("last_seen_at"),
15161
15167
  createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString()),
15168
+ pendingUpdateVersion: text("pending_update_version"),
15162
15169
  updatedAt: text("updated_at").notNull().$defaultFn(() => new Date().toISOString())
15163
15170
  }, (t) => [primaryKey({ columns: [t.workspaceId, t.daemonId] })]);
15164
15171
  var agentRuntime = sqliteTable("agent_runtime", {
@@ -15376,10 +15383,14 @@ class DaemonClient {
15376
15383
  daemon_id: daemonId
15377
15384
  });
15378
15385
  }
15379
- async poll(token, daemonId, maxTasks) {
15380
- 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 } });
15381
15388
  const resp = PollResponseSchema.parse(raw);
15382
- return resp.tasks;
15389
+ return {
15390
+ tasks: resp.tasks,
15391
+ evicted: resp.evicted ?? false,
15392
+ pending_update: resp.pending_update
15393
+ };
15383
15394
  }
15384
15395
  startTask(token, taskId) {
15385
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.6",
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",