@alook/cli 0.0.8 → 0.0.9

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
@@ -167,7 +167,8 @@ function saveCLIConfigForProfile(profile, profileConfig) {
167
167
  // commands/register.ts
168
168
  function isCommandAvailable(cmd) {
169
169
  try {
170
- execSync(`which ${cmd}`, { stdio: "ignore" });
170
+ const check = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
171
+ execSync(check, { stdio: "ignore" });
171
172
  return true;
172
173
  } catch {
173
174
  return false;
@@ -13864,6 +13865,7 @@ var ClaimedTaskRowSchema = exports_external.object({
13864
13865
  result: exports_external.unknown().nullable(),
13865
13866
  context: exports_external.unknown().nullable(),
13866
13867
  type: exports_external.string().default(TASK_TYPES.USER_DM_MESSAGE),
13868
+ contextKey: exports_external.string().nullable().optional(),
13867
13869
  sessionId: exports_external.string().nullable(),
13868
13870
  createdAt: exports_external.coerce.date(),
13869
13871
  dispatchedAt: exports_external.coerce.date().nullable(),
@@ -13893,7 +13895,8 @@ var TaskApiBaseSchema = exports_external.object({
13893
13895
  result: exports_external.unknown().nullable(),
13894
13896
  error: exports_external.string().nullable(),
13895
13897
  created_at: exports_external.string(),
13896
- type: exports_external.string()
13898
+ type: exports_external.string(),
13899
+ context_key: exports_external.string().nullable().optional()
13897
13900
  });
13898
13901
  var TaskApiSchema = TaskApiBaseSchema.extend({
13899
13902
  agent: TaskAgentDataApiSchema.nullable().optional()
@@ -15528,6 +15531,7 @@ var agentTaskQueue = sqliteTable("agent_task_queue", {
15528
15531
  conversationId: text("conversation_id").notNull().references(() => conversation.id),
15529
15532
  prompt: text("prompt").notNull(),
15530
15533
  type: text("type").notNull().default(TASK_TYPES.USER_DM_MESSAGE),
15534
+ contextKey: text("context_key"),
15531
15535
  status: text("status").notNull().default("queued"),
15532
15536
  priority: integer2("priority").notNull().default(0),
15533
15537
  result: text("result", { mode: "json" }),
@@ -15866,6 +15870,7 @@ function fromApiTask(api2) {
15866
15870
  status: api2.status,
15867
15871
  priority: api2.priority,
15868
15872
  type: api2.type,
15873
+ contextKey: api2.context_key ?? null,
15869
15874
  agent: api2.agent ? { name: api2.agent.name, instructions: api2.agent.instructions, emailHandle: api2.agent.email_handle ?? undefined, userEmail: api2.agent.user_email ?? undefined, runtimeConfig: api2.agent.runtime_config ?? undefined } : undefined,
15870
15875
  repos: undefined,
15871
15876
  createdAt: api2.created_at
@@ -16077,7 +16082,8 @@ var _dir = dirname3(fileURLToPath2(import.meta.url));
16077
16082
  var sessionRunnerPath = existsSync(join4(_dir, "session-runner.js")) ? join4(_dir, "session-runner.js") : join4(_dir, "session-runner.ts");
16078
16083
  function isCommandAvailable2(cmd) {
16079
16084
  try {
16080
- execSync3(`which ${cmd}`, { stdio: "ignore" });
16085
+ const check2 = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
16086
+ execSync3(check2, { stdio: "ignore" });
16081
16087
  return true;
16082
16088
  } catch {
16083
16089
  return false;
@@ -16178,7 +16184,16 @@ async function startDaemon(profile, serverUrl) {
16178
16184
  runtimes
16179
16185
  });
16180
16186
  } catch (e) {
16181
- log.error(`Failed to register workspace ${ws.id}, skipping`, e);
16187
+ if (e instanceof Error && e.message.startsWith("HTTP 401")) {
16188
+ log.warn(`Workspace ${ws.id} token invalid — removing from config`);
16189
+ try {
16190
+ const cfg = loadCLIConfigForProfile(profile);
16191
+ cfg.watched_workspaces = (cfg.watched_workspaces || []).filter((w) => w.id !== ws.id);
16192
+ saveCLIConfigForProfile(profile, cfg);
16193
+ } catch {}
16194
+ } else {
16195
+ log.error(`Failed to register workspace ${ws.id}, skipping`, e);
16196
+ }
16182
16197
  continue;
16183
16198
  }
16184
16199
  log.info(`Workspace ${ws.id} registered — ${resp.runtimes.length} runtime(s)`);
@@ -16270,7 +16285,11 @@ async function startDaemon(profile, serverUrl) {
16270
16285
  });
16271
16286
  }
16272
16287
  } catch (e) {
16273
- log.debug("Poll error", e);
16288
+ if (e instanceof Error && e.message.startsWith("HTTP 401")) {
16289
+ evictedIds.push(ws.workspaceId);
16290
+ } else {
16291
+ log.debug("Poll error", e);
16292
+ }
16274
16293
  }
16275
16294
  }
16276
16295
  for (const id of evictedIds) {
@@ -13581,6 +13581,7 @@ var ClaimedTaskRowSchema = exports_external.object({
13581
13581
  result: exports_external.unknown().nullable(),
13582
13582
  context: exports_external.unknown().nullable(),
13583
13583
  type: exports_external.string().default(TASK_TYPES.USER_DM_MESSAGE),
13584
+ contextKey: exports_external.string().nullable().optional(),
13584
13585
  sessionId: exports_external.string().nullable(),
13585
13586
  createdAt: exports_external.coerce.date(),
13586
13587
  dispatchedAt: exports_external.coerce.date().nullable(),
@@ -13610,7 +13611,8 @@ var TaskApiBaseSchema = exports_external.object({
13610
13611
  result: exports_external.unknown().nullable(),
13611
13612
  error: exports_external.string().nullable(),
13612
13613
  created_at: exports_external.string(),
13613
- type: exports_external.string()
13614
+ type: exports_external.string(),
13615
+ context_key: exports_external.string().nullable().optional()
13614
13616
  });
13615
13617
  var TaskApiSchema = TaskApiBaseSchema.extend({
13616
13618
  agent: TaskAgentDataApiSchema.nullable().optional()
@@ -15245,6 +15247,7 @@ var agentTaskQueue = sqliteTable("agent_task_queue", {
15245
15247
  conversationId: text("conversation_id").notNull().references(() => conversation.id),
15246
15248
  prompt: text("prompt").notNull(),
15247
15249
  type: text("type").notNull().default(TASK_TYPES.USER_DM_MESSAGE),
15250
+ contextKey: text("context_key"),
15248
15251
  status: text("status").notNull().default("queued"),
15249
15252
  priority: integer2("priority").notNull().default(0),
15250
15253
  result: text("result", { mode: "json" }),
@@ -16350,8 +16353,8 @@ var SYSTEM_PROMPT_BODY = `## Memory Management
16350
16353
  - For SPECIFIC yet LONG rules or pattern, write to experiences/[NAME].md, and add index to ./memory.md for later recall.
16351
16354
  ### whats is ESSENTIAL and SHORT Memory?
16352
16355
  - basic user profile, e.g.:
16353
- - "user name is gus"
16354
- - "user is working on alook"
16356
+ - "user name is ..."
16357
+ - "user is working on ..."
16355
16358
  - certain local project mapping, e.g.:
16356
16359
  - "alook means the project under /user/home/alook/"
16357
16360
  - when to read certain stuff, e.g.:
@@ -16388,6 +16391,7 @@ those json are sorted by datetime in asc order.
16388
16391
  - When you start a new task, read the last ~10 lines of today's timeline to understand what has been asked and done recently.
16389
16392
  - if you don't know the current datetime, obtain the current datetime first.
16390
16393
  - When user ask you something you don't have in your current context, try to read the timeline jsonl files for answer (today or previous days).
16394
+ - When access other local projects, make sure you read the CLAUDE.md/AGENTS.md file under the project root dir to understand the requirements.
16391
16395
  `;
16392
16396
  function buildInstructionContent(task) {
16393
16397
  const displayName = task.agent?.name || "Alook Agent";
@@ -16441,18 +16445,22 @@ To reply to an email, add '--in-reply-to <EMAIL_ID>' to the send command. This s
16441
16445
  ### Calendar
16442
16446
  You have your own calendar to setup daily routines and reminders.
16443
16447
  Schedule future tasks for yourself. At the scheduled time, a new task is dispatched to you with the event as the prompt (task type 'calendar_event').
16448
+
16449
+ !USE Calendar when you think the tasks are recurring or it should be conducted in the future.
16450
+ !When scheduling calendar events relative to a weekday (e.g. "every Monday"), always run date '+%A' first to confirm today's weekday before calculating the target date
16444
16451
  ---
16452
+ Keep the event title informative and concise, less than 20 words.
16453
+ Place the event details in description.
16445
16454
  Create a one-off event:
16446
- - Run 'npx @alook/cli calendar set --agent_id ${task.agentId} --event_title "<PROMPT_TEXT>" --datetime <YYYY-MM-DDTHH:MM>'
16455
+ - Run 'npx @alook/cli calendar set --agent_id ${task.agentId} --event_title "<TASK_TITLE>" --description "<TASK_BODY>" --datetime <YYYY-MM-DDTHH:MM>'
16447
16456
  - '--datetime' is LOCAL time, format 'YYYY-MM-DDTHH:MM' (e.g. '2026-04-17T09:30'). Do NOT pass UTC / ISO strings with 'Z'.
16448
16457
  - '--event_title' becomes the task prompt when the event fires — write it as the instruction you want future-you to receive.
16449
- - Optional '--description "<text>"' — longer notes/context shown alongside the event in the web UI. Use it for anything that wouldn't fit cleanly in the title.
16450
16458
 
16451
16459
  Create a repeating event:
16452
16460
  - Add '--repeat <interval>' where interval is like '1day', '2hour', '1week', '1month'.
16453
16461
  - Optionally add '--repeat_stop_date <YYYY-MM-DD>' to stop the recurrence (local date).
16454
- - Example: 'npx @alook/cli calendar set --agent_id ${task.agentId} --event_title "daily standup summary" --datetime 2026-04-18T09:00 --repeat 1day --repeat_stop_date 2026-05-18'
16455
-
16462
+ - Example: 'npx @alook/cli calendar set --agent_id ${task.agentId} --event_title "<REPEAT_TASK_TITLE>" --description "<REPEAT_TASK_BODY>" --datetime 2026-04-18T09:00 --repeat 1day --repeat_stop_date 2026-05-18'
16463
+ ---
16456
16464
  List upcoming events:
16457
16465
  - Run 'npx @alook/cli calendar list --agent_id ${task.agentId}' (defaults: next 30 days, past 0 days).
16458
16466
  - Tune the window with '--future_days <N>' and '--past_days <N>'. Add '--json' for machine-readable output.
@@ -16785,9 +16793,10 @@ function updateEntry(timelineDir, taskId, updater) {
16785
16793
  }
16786
16794
  log.debug(`Timeline updateEntry: task_id ${taskId} not found in last 7 days`);
16787
16795
  }
16788
- function createTimelineEntry(taskId, prompt, type, sessionId, pid, provider) {
16796
+ function createTimelineEntry(taskId, prompt, type, sessionId, pid, provider, contextKey) {
16789
16797
  return {
16790
16798
  task_id: taskId,
16799
+ context_key: contextKey ?? null,
16791
16800
  session_id: sessionId || null,
16792
16801
  pid: pid ?? null,
16793
16802
  status: "running",
@@ -16800,7 +16809,9 @@ function createTimelineEntry(taskId, prompt, type, sessionId, pid, provider) {
16800
16809
  };
16801
16810
  }
16802
16811
  var DEFAULT_RESUME_MAX_AGE_MS = 3 * 60 * 60 * 1000;
16803
- function findResumableSessionId(timelineDir, type, provider, maxAgeMs = DEFAULT_RESUME_MAX_AGE_MS) {
16812
+ var EMAIL_RESUME_MAX_AGE_MS = 48 * 60 * 60 * 1000;
16813
+ function findResumableSessionByContextKey(timelineDir, contextKey, provider) {
16814
+ const maxAgeMs = contextKey.startsWith("email:") ? EMAIL_RESUME_MAX_AGE_MS : DEFAULT_RESUME_MAX_AGE_MS;
16804
16815
  const now = new Date;
16805
16816
  const cutoff = new Date(now.getTime() - maxAgeMs);
16806
16817
  const daysToScan = Math.ceil(maxAgeMs / 86400000) + 1;
@@ -16810,7 +16821,7 @@ function findResumableSessionId(timelineDir, type, provider, maxAgeMs = DEFAULT_
16810
16821
  }
16811
16822
  entries.sort((a, b) => new Date(b.datetime).getTime() - new Date(a.datetime).getTime());
16812
16823
  for (const entry of entries) {
16813
- if (entry.status === "completed" && entry.type === type && entry.provider === provider && entry.session_id && new Date(entry.datetime) >= cutoff) {
16824
+ if (entry.status === "completed" && entry.context_key === contextKey && entry.provider === provider && entry.session_id && new Date(entry.datetime) >= cutoff) {
16814
16825
  return entry.session_id;
16815
16826
  }
16816
16827
  }
@@ -16848,9 +16859,9 @@ async function runSession(input) {
16848
16859
  const backend = createBackend(provider, cliPath);
16849
16860
  const prompt = buildPrompt(task);
16850
16861
  const { workDir, logFile, timelineDir, env } = prepare({ workspacesRoot }, task);
16851
- const resumeSessionId = task.type === TASK_TYPES.USER_DM_MESSAGE ? findResumableSessionId(timelineDir, task.type, provider) ?? undefined : undefined;
16862
+ const resumeSessionId = task.contextKey ? findResumableSessionByContextKey(timelineDir, task.contextKey, provider) ?? undefined : undefined;
16852
16863
  if (resumeSessionId) {
16853
- log.info(`Task ${task.id} resuming session ${resumeSessionId}`);
16864
+ log.info(`Task ${task.id} resuming session ${resumeSessionId} (context_key: ${task.contextKey})`);
16854
16865
  }
16855
16866
  const session2 = backend.execute(prompt, {
16856
16867
  cwd: workDir,
@@ -16861,11 +16872,11 @@ async function runSession(input) {
16861
16872
  });
16862
16873
  const agentPid = session2.pid;
16863
16874
  const earlySessionId = await session2.sessionId;
16864
- await initEntryAsync(timelineDir, createTimelineEntry(task.id, task.prompt, task.type, earlySessionId, process.pid, provider));
16875
+ await initEntryAsync(timelineDir, createTimelineEntry(task.id, task.prompt, task.type, earlySessionId, process.pid, provider, task.contextKey));
16865
16876
  const pendingMessages = [];
16866
16877
  let seq = 0;
16867
16878
  const BATCH_SIZE = Number(process.env.ALOOK_MESSAGE_BATCH_SIZE) || 20;
16868
- const FLUSH_INTERVAL_MS = Number(process.env.ALOOK_MESSAGE_FLUSH_INTERVAL_MS) || 2000;
16879
+ const FLUSH_INTERVAL_MS = Number(process.env.ALOOK_MESSAGE_FLUSH_INTERVAL_MS) || 100;
16869
16880
  const flushMessages = async () => {
16870
16881
  if (pendingMessages.length === 0)
16871
16882
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alook/cli",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
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",