@alook/cli 0.0.119 → 0.0.121

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
@@ -14561,7 +14561,8 @@ 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() })
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() })
14565
14566
  ]);
14566
14567
  var RegisterResponseSchema = exports_external.object({
14567
14568
  runtimes: exports_external.array(exports_external.object({ id: exports_external.string() }))
@@ -14591,6 +14592,9 @@ var RegisterDaemonRequestSchema = exports_external.object({
14591
14592
  workspaces_root: exports_external.string().optional().default(""),
14592
14593
  runtimes: exports_external.array(DaemonRuntimeItemSchema).min(1)
14593
14594
  });
14595
+ var BindWorkspaceRequestSchema = exports_external.object({
14596
+ workspace_id: exports_external.string().min(1)
14597
+ });
14594
14598
  var DeregisterRequestSchema = exports_external.object({
14595
14599
  daemon_id: exports_external.string().min(1)
14596
14600
  });
@@ -14752,7 +14756,8 @@ var CreateConversationRequestSchema = exports_external.object({
14752
14756
  channel: exports_external.string().optional()
14753
14757
  });
14754
14758
  var CreateMessageRequestSchema = exports_external.object({
14755
- content: exports_external.string().min(1, "content is required")
14759
+ content: exports_external.string().min(1, "content is required"),
14760
+ metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
14756
14761
  });
14757
14762
  var AgentDmRequestSchema = exports_external.object({
14758
14763
  content: exports_external.string().min(1, "content is required"),
@@ -16735,6 +16740,8 @@ var machineToken = sqliteTable("machine_token", {
16735
16740
  token: text("token").unique().notNull(),
16736
16741
  name: text("name").notNull().default(""),
16737
16742
  status: text("status").notNull().default("active"),
16743
+ hostname: text("hostname"),
16744
+ runtimesJson: text("runtimes_json"),
16738
16745
  lastUsedAt: text("last_used_at"),
16739
16746
  createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
16740
16747
  }, (t) => [index("idx_machine_token").on(t.token)]);
@@ -16883,7 +16890,24 @@ function isLocalUrl(url2) {
16883
16890
  return false;
16884
16891
  }
16885
16892
  }
16893
+ function hasWindow() {
16894
+ return typeof globalThis !== "undefined" && "window" in globalThis;
16895
+ }
16896
+ function isTauri() {
16897
+ return hasWindow() && typeof window !== "undefined" && "__TAURI__" in window;
16898
+ }
16899
+ function isMobile() {
16900
+ if (!isTauri())
16901
+ return false;
16902
+ const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
16903
+ return /(android|iphone|ipad|ipod)/i.test(ua);
16904
+ }
16886
16905
  function resolveMode(signals) {
16906
+ if (signals.tauri || isTauri()) {
16907
+ if (signals.tauriPlatform === "mobile" || isMobile())
16908
+ return "mobile";
16909
+ return "desktop";
16910
+ }
16887
16911
  if (signals.nodeEnv === "development" && !signals.cmdPrefix)
16888
16912
  return "dev";
16889
16913
  if (signals.serverUrl && !signals.cmdPrefix && signals.nodeEnv !== "production" && isLocalUrl(signals.serverUrl))
@@ -16900,6 +16924,8 @@ function cliCommand(mode) {
16900
16924
  return "pnpm dev:cli";
16901
16925
  case "app":
16902
16926
  return "npx @alook/app cli";
16927
+ case "desktop":
16928
+ case "mobile":
16903
16929
  case "production":
16904
16930
  return "npx @alook/cli";
16905
16931
  }
@@ -16958,10 +16984,19 @@ function loadCLIConfigForProfile(profile) {
16958
16984
  if (profileName && cfg.profiles?.[profileName]) {
16959
16985
  return cfg.profiles[profileName];
16960
16986
  }
16961
- return {
16987
+ const result = {
16962
16988
  server_url: cfg.server_url || "",
16963
16989
  watched_workspaces: cfg.watched_workspaces || []
16964
16990
  };
16991
+ const legacy = cfg.machine_token;
16992
+ if (legacy && !result.watched_workspaces.some((w) => w.token === legacy)) {
16993
+ result.watched_workspaces.push({ id: null, name: null, token: legacy, status: "registered", agent_ids: [] });
16994
+ }
16995
+ for (const ws of result.watched_workspaces) {
16996
+ if (!ws.status)
16997
+ ws.status = ws.id ? "active" : "registered";
16998
+ }
16999
+ return result;
16965
17000
  }
16966
17001
  function saveCLIConfig(cfg) {
16967
17002
  mkdirSync(configDir(), { recursive: true, mode: 448 });
@@ -16976,6 +17011,7 @@ function saveCLIConfigForProfile(profile, profileConfig) {
16976
17011
  } else {
16977
17012
  cfg.server_url = profileConfig.server_url;
16978
17013
  cfg.watched_workspaces = profileConfig.watched_workspaces;
17014
+ delete cfg.machine_token;
16979
17015
  }
16980
17016
  saveCLIConfig(cfg);
16981
17017
  }
@@ -17296,7 +17332,7 @@ async function activateAndSave(opts) {
17296
17332
  }
17297
17333
  console.log(`Found: ${runtimes.map((r) => r.type).join(", ")}`);
17298
17334
  const host = hostname4();
17299
- console.log("Registering runtime...");
17335
+ console.log("Registering machine...");
17300
17336
  let activateResp;
17301
17337
  try {
17302
17338
  const res = await fetch(`${serverUrl}/api/machine-tokens/activate`, {
@@ -17314,32 +17350,10 @@ async function activateAndSave(opts) {
17314
17350
  console.error(`Error: failed to activate: ${err instanceof Error ? err.message : err}`);
17315
17351
  process.exit(1);
17316
17352
  }
17317
- const client = new APIClient(serverUrl, token);
17318
- let workspaces;
17319
- try {
17320
- workspaces = await client.getJSON("/api/workspaces");
17321
- } catch (err) {
17322
- console.error(`Error: failed to fetch workspaces: ${err instanceof Error ? err.message : err}`);
17323
- process.exit(1);
17324
- }
17325
- if (!workspaces.length) {
17326
- console.error("Error: no workspaces found for this user");
17327
- process.exit(1);
17328
- }
17329
- const ws = workspaces.find((w) => w.id === activateResp.workspace_id) || workspaces[0];
17330
- const wsClient = new APIClient(serverUrl, token, ws.id);
17331
- let agentIds = [];
17332
- try {
17333
- const agents = await wsClient.getJSON(`/api/agents?workspace_id=${ws.id}`);
17334
- agentIds = agents.map((a) => a.id);
17335
- } catch {}
17336
17353
  const existing = loadCLIConfigForProfile(profile);
17337
17354
  const watched = existing.watched_workspaces || [];
17338
- const idx = watched.findIndex((w) => w.id === ws.id);
17339
- if (idx >= 0) {
17340
- watched[idx] = { id: ws.id, name: ws.name, token, agent_ids: agentIds };
17341
- } else {
17342
- watched.push({ id: ws.id, name: ws.name, token, agent_ids: agentIds });
17355
+ if (!watched.some((w) => w.token === token)) {
17356
+ watched.push({ id: null, name: null, token, status: "registered", agent_ids: [] });
17343
17357
  }
17344
17358
  saveCLIConfigForProfile(profile, {
17345
17359
  server_url: serverUrl,
@@ -17350,10 +17364,10 @@ async function activateAndSave(opts) {
17350
17364
  try {
17351
17365
  process.kill(daemonPid, "SIGHUP");
17352
17366
  console.log(`
17353
- Daemon (pid ${daemonPid}) notified — workspace will be active shortly.`);
17367
+ Daemon (pid ${daemonPid}) notified — machine registered, awaiting workspace binding.`);
17354
17368
  } catch {
17355
17369
  console.log(`
17356
- Daemon is running but could not be notified. Restart it to pick up the new workspace.`);
17370
+ Daemon is running but could not be notified. Restart it to pick up the new token.`);
17357
17371
  }
17358
17372
  } else {
17359
17373
  const startCmd = isDev() ? `${cmdPrefix()} daemon start --foreground` : `${cmdPrefix()} daemon start`;
@@ -17361,9 +17375,8 @@ Daemon is running but could not be notified. Restart it to pick up the new works
17361
17375
  console.log(`Run '${startCmd}' to start the daemon.`);
17362
17376
  }
17363
17377
  return {
17364
- workspaceId: ws.id,
17365
- workspaceName: ws.name,
17366
- runtimeProviders: activateResp.runtimes.map((r) => r.provider)
17378
+ daemonId: activateResp.daemon_id,
17379
+ tokenStatus: activateResp.token_status
17367
17380
  };
17368
17381
  }
17369
17382
 
@@ -17393,8 +17406,8 @@ Usage: ${cmdPrefix()} register --token <token>`);
17393
17406
  const result = await activateAndSave({ token, serverUrl, profile });
17394
17407
  console.log(`
17395
17408
  Registered as ${me.email}`);
17396
- console.log(`Workspace: ${result.workspaceName} (${result.workspaceId})`);
17397
- console.log(`Runtimes: ${result.runtimeProviders.join(", ")}`);
17409
+ console.log(`Machine: ${result.daemonId} (status: ${result.tokenStatus})`);
17410
+ console.log(`Workspace binding will happen when you launch a company.`);
17398
17411
  });
17399
17412
  return cmd;
17400
17413
  }
@@ -17486,8 +17499,8 @@ async function pollAndActivate(opts) {
17486
17499
  console.log(`
17487
17500
  Logged in as ${email3}`);
17488
17501
  }
17489
- console.log(`Workspace: ${result.workspaceName} (${result.workspaceId})`);
17490
- console.log(`Runtimes: ${result.runtimeProviders.join(", ")}`);
17502
+ console.log(`Machine: ${result.daemonId} (status: ${result.tokenStatus})`);
17503
+ console.log(`Workspace binding will happen when you launch a company.`);
17491
17504
  }
17492
17505
  if (process.argv.includes("--__login-poll")) {
17493
17506
  const idx = process.argv.indexOf("--__login-poll");
@@ -17527,7 +17540,7 @@ async function checkExistingAuth(serverUrl, profile) {
17527
17540
  email3 = me.email;
17528
17541
  }
17529
17542
  } catch {}
17530
- return { valid: true, email: email3, workspaceName: ws.name };
17543
+ return { valid: true, email: email3, workspaceName: ws.name ?? undefined };
17531
17544
  } catch {
17532
17545
  return { valid: false };
17533
17546
  }
@@ -17752,6 +17765,9 @@ class DaemonClient {
17752
17765
  syncSkills(token, body) {
17753
17766
  return this.request("POST", "/api/daemon/skills/sync", token, body);
17754
17767
  }
17768
+ async checkStandby(token, body) {
17769
+ return this.request("POST", "/api/daemon/register", token, body);
17770
+ }
17755
17771
  }
17756
17772
 
17757
17773
  // daemon/health.ts
@@ -19199,6 +19215,8 @@ Don't bundle everything into one giant message at the end. The user shouldn't ha
19199
19215
 
19200
19216
  \`${cmdPrefix()} sync send-dm --message "…"\` for short messages. For longer or markdown-rich messages, write to a file first and use \`--message-file <path>\` — this preserves formatting and avoids shell escaping issues. The conversation is in $ALOOK_CONVERSATION_ID, so you usually need no flags. You can send several times in one task.
19201
19217
 
19218
+ Your messages are rendered as **markdown** in the user's app. Use formatting to make your responses clear and scannable — headers, bullet lists, code blocks, bold for key points. Don't send a wall of plain text when structure would help the reader. For anything beyond a one-liner, prefer \`--message-file\` so you can write proper markdown without fighting shell escaping.
19219
+
19202
19220
  ### Attachments
19203
19221
  When your task includes attachments, their local paths are listed in the prompt JSON under "attachments".
19204
19222
  Use your Read tool to open them. Images and PDFs are read visually.
@@ -19681,6 +19699,13 @@ function buildTaskObject(task, attachments) {
19681
19699
  };
19682
19700
  if (task.type === "user_dm_message") {
19683
19701
  obj.notice = DM_RESPONSE_NOTICE;
19702
+ const ctx = task.context;
19703
+ if (ctx?.message_id) {
19704
+ obj.message_id = ctx.message_id;
19705
+ }
19706
+ if (ctx?.quoted_message) {
19707
+ obj.quoted_message = ctx.quoted_message;
19708
+ }
19684
19709
  }
19685
19710
  if (task.type === "email_notification") {
19686
19711
  const ctx = task.context;
@@ -21027,11 +21052,14 @@ async function startDaemon(profile, serverUrl) {
21027
21052
  }
21028
21053
  }
21029
21054
  const cliConfig = loadCLIConfigForProfile(profile);
21030
- const workspaces = cliConfig.watched_workspaces || [];
21031
- if (workspaces.length === 0) {
21032
- log10.error("No watched workspaces configured.");
21033
- process.exit(1);
21034
- return;
21055
+ const allEntries = cliConfig.watched_workspaces || [];
21056
+ const workspaces = allEntries.filter((ws) => ws.status !== "registered" && !!ws.id);
21057
+ const registeredEntries = allEntries.filter((ws) => ws.status === "registered" && !ws.id);
21058
+ const standbyToken = registeredEntries[0]?.token ?? null;
21059
+ if (workspaces.length === 0 && standbyToken) {
21060
+ log10.info("No workspaces configured — daemon starting in standby mode with machine token. Awaiting workspace binding.");
21061
+ } else if (workspaces.length === 0) {
21062
+ log10.info("No workspaces configured — daemon starting in standby mode. Register a workspace to begin.");
21035
21063
  }
21036
21064
  const hasPerWorkspaceTokens = workspaces.every((ws) => !!ws.token);
21037
21065
  if (!hasPerWorkspaceTokens) {
@@ -21062,6 +21090,7 @@ async function startDaemon(profile, serverUrl) {
21062
21090
  log10.info(`Detected providers: ${providers.map((p) => `${p.type}@${p.version}`).join(", ")}`);
21063
21091
  const workspaceStates = [];
21064
21092
  const runtimeIndex = new Map;
21093
+ let hadWorkspaces = workspaces.length > 0;
21065
21094
  for (const ws of workspaces) {
21066
21095
  const runtimes = providers.map((p) => ({
21067
21096
  type: p.type,
@@ -21097,7 +21126,7 @@ async function startDaemon(profile, serverUrl) {
21097
21126
  });
21098
21127
  }
21099
21128
  }
21100
- if (workspaceStates.length === 0) {
21129
+ if (workspaceStates.length === 0 && workspaces.length > 0) {
21101
21130
  log10.error("No workspaces registered successfully.");
21102
21131
  process.exit(1);
21103
21132
  return;
@@ -21142,6 +21171,50 @@ async function startDaemon(profile, serverUrl) {
21142
21171
  } catch {}
21143
21172
  log10.info(`Workspace ${workspaceId} deleted server-side — removed from config`);
21144
21173
  }
21174
+ async function handleWorkspaceAdded(workspaceId, workspaceName, token) {
21175
+ if (workspaceStates.some((ws) => ws.workspaceId === workspaceId)) {
21176
+ log10.info(`Workspace ${workspaceId} already registered — ignoring workspace_added`);
21177
+ return;
21178
+ }
21179
+ log10.info(`Workspace ${workspaceId} bound — registering...`);
21180
+ const runtimes = providers.map((p) => ({ type: p.type, version: p.version }));
21181
+ try {
21182
+ const resp = await client.register(token, {
21183
+ workspace_id: workspaceId,
21184
+ daemon_id: config2.daemonId,
21185
+ device_name: config2.deviceName,
21186
+ cli_version: config2.cliVersion,
21187
+ workspaces_root: config2.workspacesRoot,
21188
+ runtimes
21189
+ });
21190
+ const runtimeIds = resp.runtimes.map((r) => r.id);
21191
+ workspaceStates.push({ workspaceId, token, runtimeIds });
21192
+ for (let i = 0;i < runtimeIds.length; i++) {
21193
+ runtimeIndex.set(runtimeIds[i], {
21194
+ id: runtimeIds[i],
21195
+ workspaceId,
21196
+ provider: providers[i].type
21197
+ });
21198
+ }
21199
+ hadWorkspaces = true;
21200
+ health.setRuntimeCount(workspaceStates.reduce((sum, w) => sum + w.runtimeIds.length, 0));
21201
+ try {
21202
+ const cfg = loadCLIConfigForProfile(profile);
21203
+ const watched = cfg.watched_workspaces || [];
21204
+ const registeredIdx = watched.findIndex((w) => w.token === token && w.status === "registered" && !w.id);
21205
+ if (registeredIdx !== -1) {
21206
+ watched[registeredIdx] = { id: workspaceId, name: workspaceName, token, status: "active", agent_ids: [] };
21207
+ } else if (!watched.some((w) => w.id === workspaceId)) {
21208
+ watched.push({ id: workspaceId, name: workspaceName, token, status: "active" });
21209
+ }
21210
+ cfg.watched_workspaces = watched;
21211
+ saveCLIConfigForProfile(profile, cfg);
21212
+ } catch {}
21213
+ log10.info(`Workspace ${workspaceId} added via WS push — ${runtimeIds.length} runtime(s)`);
21214
+ } catch (e) {
21215
+ log10.error(`Failed to register workspace ${workspaceId} from WS push`, e);
21216
+ }
21217
+ }
21145
21218
  const pollCycle = async () => {
21146
21219
  let remaining = config2.maxConcurrentTasks - activeTasks.size;
21147
21220
  if (remaining <= 0)
@@ -21217,7 +21290,7 @@ async function startDaemon(profile, serverUrl) {
21217
21290
  for (const id of evictedIds) {
21218
21291
  evictWorkspace(id);
21219
21292
  }
21220
- if (workspaceStates.length === 0) {
21293
+ if (workspaceStates.length === 0 && hadWorkspaces) {
21221
21294
  log10.info("All workspaces evicted — shutting down");
21222
21295
  shutdown();
21223
21296
  }
@@ -21330,23 +21403,69 @@ async function startDaemon(profile, serverUrl) {
21330
21403
  }
21331
21404
  break;
21332
21405
  }
21406
+ case "daemon.workspace_added": {
21407
+ handleWorkspaceAdded(msg.workspaceId, msg.workspaceName, msg.token);
21408
+ break;
21409
+ }
21333
21410
  }
21334
21411
  }
21335
- const wsClient = firstToken ? new DaemonWsClient({
21412
+ const wsToken = firstToken || standbyToken;
21413
+ let wsClient = wsToken ? new DaemonWsClient({
21336
21414
  serverURL: config2.serverURL,
21337
21415
  daemonId: config2.daemonId,
21338
- machineToken: firstToken,
21416
+ machineToken: wsToken,
21339
21417
  onMessage: handleWsPush,
21340
21418
  onConnected: () => {
21341
- log10.info("WS connected switching to low-frequency poll");
21342
- updatePollInterval(config2.wsPollInterval);
21419
+ if (workspaceStates.length > 0) {
21420
+ log10.info("WS connected — switching to low-frequency poll");
21421
+ updatePollInterval(config2.wsPollInterval);
21422
+ } else {
21423
+ log10.info("WS connected in standby mode — awaiting workspace binding");
21424
+ }
21343
21425
  },
21344
21426
  onDisconnected: () => {
21345
- log10.info("WS disconnected reverting to high-frequency poll");
21346
- updatePollInterval(config2.pollInterval);
21427
+ if (workspaceStates.length > 0) {
21428
+ log10.info("WS disconnected — reverting to high-frequency poll");
21429
+ updatePollInterval(config2.pollInterval);
21430
+ }
21347
21431
  }
21348
21432
  }) : null;
21349
21433
  wsClient?.connect();
21434
+ const STANDBY_POLL_MS = 30000;
21435
+ let standbyPollTimer = null;
21436
+ if (standbyToken && workspaceStates.length === 0) {
21437
+ const standbyPollTick = async () => {
21438
+ if (workspaceStates.length > 0) {
21439
+ if (standbyPollTimer) {
21440
+ clearInterval(standbyPollTimer);
21441
+ standbyPollTimer = null;
21442
+ }
21443
+ return;
21444
+ }
21445
+ try {
21446
+ const runtimes = providers.map((p) => ({ type: p.type, version: p.version }));
21447
+ const resp = await client.checkStandby(standbyToken, {
21448
+ daemon_id: config2.daemonId,
21449
+ device_name: config2.deviceName,
21450
+ cli_version: config2.cliVersion,
21451
+ workspaces_root: config2.workspacesRoot,
21452
+ runtimes
21453
+ });
21454
+ if (!resp.standby && resp.runtimes.length > 0 && resp.workspaceId) {
21455
+ log10.info(`Standby poll: workspace ${resp.workspaceId} discovered via fallback`);
21456
+ await handleWorkspaceAdded(resp.workspaceId, "", standbyToken);
21457
+ if (standbyPollTimer) {
21458
+ clearInterval(standbyPollTimer);
21459
+ standbyPollTimer = null;
21460
+ }
21461
+ }
21462
+ } catch (e) {
21463
+ log10.debug("standby poll failed", { err: e instanceof Error ? e.message : String(e) });
21464
+ }
21465
+ };
21466
+ standbyPollTick();
21467
+ standbyPollTimer = setInterval(standbyPollTick, STANDBY_POLL_MS);
21468
+ }
21350
21469
  const sweepTick = async () => {
21351
21470
  for (const ws of workspaceStates) {
21352
21471
  client.sweep(ws.token, config2.daemonId).catch((e) => {
@@ -21384,6 +21503,8 @@ async function startDaemon(profile, serverUrl) {
21384
21503
  clearInterval(pollTimer);
21385
21504
  clearInterval(heartbeatTimer);
21386
21505
  clearInterval(sweepTimer);
21506
+ if (standbyPollTimer)
21507
+ clearInterval(standbyPollTimer);
21387
21508
  stopSkillScanner();
21388
21509
  wsClient?.close();
21389
21510
  const shutdownMs = restartRequested ? 30000 : Number(process.env.ALOOK_SHUTDOWN_TIMEOUT_MS) || 5000;
@@ -21432,7 +21553,7 @@ async function startDaemon(profile, serverUrl) {
21432
21553
  log10.info("SIGHUP received — reloading config...");
21433
21554
  try {
21434
21555
  const freshConfig = loadCLIConfigForProfile(profile);
21435
- const freshWorkspaces = freshConfig.watched_workspaces || [];
21556
+ const freshWorkspaces = (freshConfig.watched_workspaces || []).filter((ws) => ws.status !== "registered" && !!ws.id);
21436
21557
  const existingIds = new Set(workspaceStates.map((ws) => ws.workspaceId));
21437
21558
  const newWorkspaces = freshWorkspaces.filter((ws) => ws.token && !existingIds.has(ws.id));
21438
21559
  for (const ws of newWorkspaces) {
@@ -21462,7 +21583,27 @@ async function startDaemon(profile, serverUrl) {
21462
21583
  }
21463
21584
  }
21464
21585
  if (newWorkspaces.length > 0) {
21586
+ hadWorkspaces = true;
21465
21587
  health.setRuntimeCount(workspaceStates.reduce((sum, w) => sum + w.runtimeIds.length, 0));
21588
+ if (!wsClient && workspaceStates.length > 0) {
21589
+ const token = workspaceStates[0].token;
21590
+ wsClient = new DaemonWsClient({
21591
+ serverURL: config2.serverURL,
21592
+ daemonId: config2.daemonId,
21593
+ machineToken: token,
21594
+ onMessage: handleWsPush,
21595
+ onConnected: () => {
21596
+ log10.info("WS connected — switching to low-frequency poll");
21597
+ updatePollInterval(config2.wsPollInterval);
21598
+ },
21599
+ onDisconnected: () => {
21600
+ log10.info("WS disconnected — reverting to high-frequency poll");
21601
+ updatePollInterval(config2.pollInterval);
21602
+ }
21603
+ });
21604
+ wsClient.connect();
21605
+ log10.info("WS push client initialized after SIGHUP reload");
21606
+ }
21466
21607
  log10.info(`Reload complete — now polling ${workspaceStates.length} workspace(s)`);
21467
21608
  } else {
21468
21609
  log10.info("Reload complete — no new workspaces found");
@@ -22979,7 +23120,7 @@ function versionCommand() {
22979
23120
 
22980
23121
  // commands/update.ts
22981
23122
  import { Command as Command11 } from "commander";
22982
- function updateCommand() {
23123
+ function updateCommand2() {
22983
23124
  const cmd = new Command11("update").description("Update CLI to the latest version").action(async () => {
22984
23125
  const current = getCurrentVersion();
22985
23126
  console.log(`Current version: ${current}`);
@@ -23207,7 +23348,7 @@ program.addCommand(issueCommand());
23207
23348
  program.addCommand(agentCommand());
23208
23349
  program.addCommand(configCommand());
23209
23350
  program.addCommand(versionCommand());
23210
- program.addCommand(updateCommand());
23351
+ program.addCommand(updateCommand2());
23211
23352
  program.addCommand(syncCommand());
23212
23353
  program.addCommand(workspaceCommand());
23213
23354
  program.parse();
@@ -14472,7 +14472,8 @@ var DaemonPushMessageSchema = exports_external.discriminatedUnion("type", [
14472
14472
  exports_external.object({ type: exports_external.literal("daemon.evict"), workspaceId: exports_external.string() }),
14473
14473
  exports_external.object({ type: exports_external.literal("daemon.update"), version: exports_external.string() }),
14474
14474
  exports_external.object({ type: exports_external.literal("daemon.rescan") }),
14475
- 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() })
14475
+ 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() }),
14476
+ exports_external.object({ type: exports_external.literal("daemon.workspace_added"), workspaceId: exports_external.string(), workspaceName: exports_external.string(), token: exports_external.string() })
14476
14477
  ]);
14477
14478
  var RegisterResponseSchema = exports_external.object({
14478
14479
  runtimes: exports_external.array(exports_external.object({ id: exports_external.string() }))
@@ -14502,6 +14503,9 @@ var RegisterDaemonRequestSchema = exports_external.object({
14502
14503
  workspaces_root: exports_external.string().optional().default(""),
14503
14504
  runtimes: exports_external.array(DaemonRuntimeItemSchema).min(1)
14504
14505
  });
14506
+ var BindWorkspaceRequestSchema = exports_external.object({
14507
+ workspace_id: exports_external.string().min(1)
14508
+ });
14505
14509
  var DeregisterRequestSchema = exports_external.object({
14506
14510
  daemon_id: exports_external.string().min(1)
14507
14511
  });
@@ -14663,7 +14667,8 @@ var CreateConversationRequestSchema = exports_external.object({
14663
14667
  channel: exports_external.string().optional()
14664
14668
  });
14665
14669
  var CreateMessageRequestSchema = exports_external.object({
14666
- content: exports_external.string().min(1, "content is required")
14670
+ content: exports_external.string().min(1, "content is required"),
14671
+ metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
14667
14672
  });
14668
14673
  var AgentDmRequestSchema = exports_external.object({
14669
14674
  content: exports_external.string().min(1, "content is required"),
@@ -16646,6 +16651,8 @@ var machineToken = sqliteTable("machine_token", {
16646
16651
  token: text("token").unique().notNull(),
16647
16652
  name: text("name").notNull().default(""),
16648
16653
  status: text("status").notNull().default("active"),
16654
+ hostname: text("hostname"),
16655
+ runtimesJson: text("runtimes_json"),
16649
16656
  lastUsedAt: text("last_used_at"),
16650
16657
  createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
16651
16658
  }, (t) => [index("idx_machine_token").on(t.token)]);
@@ -16780,7 +16787,24 @@ function isLocalUrl(url2) {
16780
16787
  return false;
16781
16788
  }
16782
16789
  }
16790
+ function hasWindow() {
16791
+ return typeof globalThis !== "undefined" && "window" in globalThis;
16792
+ }
16793
+ function isTauri() {
16794
+ return hasWindow() && typeof window !== "undefined" && "__TAURI__" in window;
16795
+ }
16796
+ function isMobile() {
16797
+ if (!isTauri())
16798
+ return false;
16799
+ const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
16800
+ return /(android|iphone|ipad|ipod)/i.test(ua);
16801
+ }
16783
16802
  function resolveMode(signals) {
16803
+ if (signals.tauri || isTauri()) {
16804
+ if (signals.tauriPlatform === "mobile" || isMobile())
16805
+ return "mobile";
16806
+ return "desktop";
16807
+ }
16784
16808
  if (signals.nodeEnv === "development" && !signals.cmdPrefix)
16785
16809
  return "dev";
16786
16810
  if (signals.serverUrl && !signals.cmdPrefix && signals.nodeEnv !== "production" && isLocalUrl(signals.serverUrl))
@@ -16797,6 +16821,8 @@ function cliCommand(mode) {
16797
16821
  return "pnpm dev:cli";
16798
16822
  case "app":
16799
16823
  return "npx @alook/app cli";
16824
+ case "desktop":
16825
+ case "mobile":
16800
16826
  case "production":
16801
16827
  return "npx @alook/cli";
16802
16828
  }
@@ -16921,6 +16947,9 @@ class DaemonClient {
16921
16947
  syncSkills(token, body) {
16922
16948
  return this.request("POST", "/api/daemon/skills/sync", token, body);
16923
16949
  }
16950
+ async checkStandby(token, body) {
16951
+ return this.request("POST", "/api/daemon/register", token, body);
16952
+ }
16924
16953
  }
16925
16954
 
16926
16955
  // daemon/agent/claude.ts
@@ -18418,6 +18447,8 @@ Don't bundle everything into one giant message at the end. The user shouldn't ha
18418
18447
 
18419
18448
  \`${cmdPrefix()} sync send-dm --message "…"\` for short messages. For longer or markdown-rich messages, write to a file first and use \`--message-file <path>\` — this preserves formatting and avoids shell escaping issues. The conversation is in $ALOOK_CONVERSATION_ID, so you usually need no flags. You can send several times in one task.
18420
18449
 
18450
+ Your messages are rendered as **markdown** in the user's app. Use formatting to make your responses clear and scannable — headers, bullet lists, code blocks, bold for key points. Don't send a wall of plain text when structure would help the reader. For anything beyond a one-liner, prefer \`--message-file\` so you can write proper markdown without fighting shell escaping.
18451
+
18421
18452
  ### Attachments
18422
18453
  When your task includes attachments, their local paths are listed in the prompt JSON under "attachments".
18423
18454
  Use your Read tool to open them. Images and PDFs are read visually.
@@ -18817,6 +18848,13 @@ function buildTaskObject(task, attachments) {
18817
18848
  };
18818
18849
  if (task.type === "user_dm_message") {
18819
18850
  obj.notice = DM_RESPONSE_NOTICE;
18851
+ const ctx = task.context;
18852
+ if (ctx?.message_id) {
18853
+ obj.message_id = ctx.message_id;
18854
+ }
18855
+ if (ctx?.quoted_message) {
18856
+ obj.quoted_message = ctx.quoted_message;
18857
+ }
18820
18858
  }
18821
18859
  if (task.type === "email_notification") {
18822
18860
  const ctx = task.context;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alook/cli",
3
- "version": "0.0.119",
3
+ "version": "0.0.121",
4
4
  "description": "Alook CLI — Enable Your Person Colleague",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/alookai/alook#readme",