@alook/cli 0.0.10 → 0.0.12

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
@@ -13878,6 +13878,7 @@ var TaskAgentDataApiSchema = exports_external.object({
13878
13878
  name: exports_external.string(),
13879
13879
  runtime_config: exports_external.record(exports_external.string(), exports_external.unknown()).default({}),
13880
13880
  email_handle: exports_external.string().nullable().optional(),
13881
+ email_addresses: exports_external.array(exports_external.string()).default([]),
13881
13882
  user_email: exports_external.string().nullable().optional()
13882
13883
  });
13883
13884
  var TaskApiBaseSchema = exports_external.object({
@@ -14052,7 +14053,9 @@ var SendEmailRequestSchema = exports_external.object({
14052
14053
  htmlBody: exports_external.string().default(""),
14053
14054
  inReplyTo: exports_external.string().optional(),
14054
14055
  references: exports_external.string().optional(),
14055
- attachments: exports_external.array(EmailAttachmentSchema).optional()
14056
+ attachments: exports_external.array(EmailAttachmentSchema).optional(),
14057
+ customAccountId: exports_external.string().optional(),
14058
+ from: exports_external.string().email().optional()
14056
14059
  });
14057
14060
  var UpdateEmailStatusRequestSchema = exports_external.object({
14058
14061
  status: exports_external.enum(["unread", "read", "archived"])
@@ -14070,6 +14073,48 @@ var EmailNotifyRequestSchema = exports_external.object({
14070
14073
  inReplyTo: exports_external.string().optional().default(""),
14071
14074
  references: exports_external.string().optional().default("")
14072
14075
  });
14076
+ var CreateEmailAccountSchema = exports_external.object({
14077
+ emailAddress: exports_external.string().email("valid email required"),
14078
+ displayName: exports_external.string().default(""),
14079
+ imapHost: exports_external.string().min(1, "IMAP host is required"),
14080
+ imapPort: exports_external.number().int().min(1).max(65535).default(993),
14081
+ imapUsername: exports_external.string().min(1, "IMAP username is required"),
14082
+ imapPassword: exports_external.string().min(1, "IMAP password is required"),
14083
+ imapTls: exports_external.boolean().default(true),
14084
+ smtpHost: exports_external.string().min(1, "SMTP host is required"),
14085
+ smtpPort: exports_external.number().int().min(1).max(65535).default(587),
14086
+ smtpUsername: exports_external.string().min(1, "SMTP username is required"),
14087
+ smtpPassword: exports_external.string().min(1, "SMTP password is required"),
14088
+ smtpTls: exports_external.number().int().min(0).max(2).default(1),
14089
+ pollIntervalSeconds: exports_external.number().int().min(30).max(3600).default(60)
14090
+ });
14091
+ var UpdateEmailAccountSchema = exports_external.object({
14092
+ emailAddress: exports_external.string().email().optional(),
14093
+ displayName: exports_external.string().optional(),
14094
+ imapHost: exports_external.string().min(1).optional(),
14095
+ imapPort: exports_external.number().int().min(1).max(65535).optional(),
14096
+ imapUsername: exports_external.string().min(1).optional(),
14097
+ imapPassword: exports_external.string().min(1).optional(),
14098
+ imapTls: exports_external.boolean().optional(),
14099
+ smtpHost: exports_external.string().min(1).optional(),
14100
+ smtpPort: exports_external.number().int().min(1).max(65535).optional(),
14101
+ smtpUsername: exports_external.string().min(1).optional(),
14102
+ smtpPassword: exports_external.string().min(1).optional(),
14103
+ smtpTls: exports_external.number().int().min(0).max(2).optional(),
14104
+ pollIntervalSeconds: exports_external.number().int().min(30).max(3600).optional()
14105
+ });
14106
+ var TestEmailConnectionSchema = exports_external.object({
14107
+ imapHost: exports_external.string().min(1),
14108
+ imapPort: exports_external.number().int().min(1).max(65535).default(993),
14109
+ imapUsername: exports_external.string().min(1),
14110
+ imapPassword: exports_external.string().min(1),
14111
+ imapTls: exports_external.boolean().default(true),
14112
+ smtpHost: exports_external.string().min(1),
14113
+ smtpPort: exports_external.number().int().min(1).max(65535).default(587),
14114
+ smtpUsername: exports_external.string().min(1),
14115
+ smtpPassword: exports_external.string().min(1),
14116
+ smtpTls: exports_external.number().int().min(0).max(2).default(1)
14117
+ });
14073
14118
  var CreateWorkspaceRequestSchema = exports_external.object({
14074
14119
  name: exports_external.string().min(1, "name is required"),
14075
14120
  slug: exports_external.string().min(1, "slug is required")
@@ -15683,6 +15728,37 @@ var artifact = sqliteTable("artifact", {
15683
15728
  foreignColumns: [agent.id, agent.workspaceId]
15684
15729
  }).onDelete("cascade")
15685
15730
  ]);
15731
+ var agentEmailAccount = sqliteTable("agent_email_account", {
15732
+ id: text("id").primaryKey().$defaultFn(() => "aea_" + nanoid3()),
15733
+ agentId: text("agent_id").notNull(),
15734
+ workspaceId: text("workspace_id").notNull(),
15735
+ emailAddress: text("email_address").notNull(),
15736
+ displayName: text("display_name").notNull().default(""),
15737
+ imapHost: text("imap_host").notNull(),
15738
+ imapPort: integer2("imap_port").notNull().default(993),
15739
+ imapUsername: text("imap_username").notNull(),
15740
+ imapPassword: text("imap_password").notNull(),
15741
+ imapTls: integer2("imap_tls", { mode: "boolean" }).notNull().default(true),
15742
+ smtpHost: text("smtp_host").notNull(),
15743
+ smtpPort: integer2("smtp_port").notNull().default(587),
15744
+ smtpUsername: text("smtp_username").notNull(),
15745
+ smtpPassword: text("smtp_password").notNull(),
15746
+ smtpTls: integer2("smtp_tls").notNull().default(1),
15747
+ pollIntervalSeconds: integer2("poll_interval_seconds").notNull().default(60),
15748
+ lastSyncedUid: text("last_synced_uid").notNull().default("0"),
15749
+ lastSyncedAt: text("last_synced_at"),
15750
+ status: text("status").notNull().default("active"),
15751
+ errorMessage: text("error_message").notNull().default(""),
15752
+ createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString()),
15753
+ updatedAt: text("updated_at").notNull().$defaultFn(() => new Date().toISOString())
15754
+ }, (t) => [
15755
+ index("idx_email_account_agent_ws").on(t.agentId, t.workspaceId),
15756
+ unique("email_account_agent_email").on(t.agentId, t.emailAddress),
15757
+ foreignKey({
15758
+ columns: [t.agentId, t.workspaceId],
15759
+ foreignColumns: [agent.id, agent.workspaceId]
15760
+ }).onDelete("cascade")
15761
+ ]);
15686
15762
  var machineToken = sqliteTable("machine_token", {
15687
15763
  id: text("id").primaryKey().$defaultFn(() => nanoid3()),
15688
15764
  userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
@@ -15823,6 +15899,10 @@ function pidFilePath(profile) {
15823
15899
  const name = profile ? `daemon_${profile}.pid` : "daemon.pid";
15824
15900
  return join3(configDir(), name);
15825
15901
  }
15902
+ function lastUpdateMarkerPath(profile) {
15903
+ const name = profile ? `last_update_${profile}` : "last_update";
15904
+ return join3(configDir(), name);
15905
+ }
15826
15906
  function daemonLogDir() {
15827
15907
  return join3(configDir(), "daemon", "logs");
15828
15908
  }
@@ -15884,7 +15964,7 @@ function loadDaemonConfig(profile) {
15884
15964
  codexModel: process.env.ALOOK_CODEX_MODEL || "",
15885
15965
  opencodeModel: process.env.ALOOK_OPENCODE_MODEL || "",
15886
15966
  pollInterval: parseDuration(process.env.ALOOK_DAEMON_POLL_INTERVAL || "3s"),
15887
- agentTimeout: parseDuration(process.env.ALOOK_AGENT_TIMEOUT || "2h"),
15967
+ agentTimeout: parseDuration(process.env.ALOOK_AGENT_TIMEOUT || "12h"),
15888
15968
  maxConcurrentTasks: parseInt(process.env.ALOOK_DAEMON_MAX_CONCURRENT_TASKS || "20"),
15889
15969
  daemonId,
15890
15970
  deviceName: process.env.ALOOK_DAEMON_DEVICE_NAME || h,
@@ -15960,7 +16040,7 @@ function fromApiTask(api2) {
15960
16040
  type: api2.type,
15961
16041
  contextKey: api2.context_key ?? null,
15962
16042
  context: api2.context ?? undefined,
15963
- 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,
16043
+ agent: api2.agent ? { name: api2.agent.name, instructions: api2.agent.instructions, emailHandle: api2.agent.email_handle ?? undefined, emailAddresses: api2.agent.email_addresses ?? [], userEmail: api2.agent.user_email ?? undefined, runtimeConfig: api2.agent.runtime_config ?? undefined } : undefined,
15964
16044
  repos: undefined,
15965
16045
  createdAt: api2.created_at
15966
16046
  };
@@ -15997,10 +16077,13 @@ function useColor() {
15997
16077
  }
15998
16078
  function timestamp() {
15999
16079
  const d = new Date;
16080
+ const Y = d.getFullYear();
16081
+ const M = String(d.getMonth() + 1).padStart(2, "0");
16082
+ const D = String(d.getDate()).padStart(2, "0");
16000
16083
  const h = String(d.getHours()).padStart(2, "0");
16001
16084
  const m = String(d.getMinutes()).padStart(2, "0");
16002
16085
  const s = String(d.getSeconds()).padStart(2, "0");
16003
- return `${h}:${m}:${s}`;
16086
+ return `${Y}-${M}-${D} ${h}:${m}:${s}`;
16004
16087
  }
16005
16088
 
16006
16089
  class Logger2 {
@@ -16104,6 +16187,9 @@ function releaseDaemonPid(profile) {
16104
16187
  removePidFileIfMatches(process.pid, profile);
16105
16188
  }
16106
16189
 
16190
+ // daemon/update-handler.ts
16191
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "fs";
16192
+
16107
16193
  // lib/update.ts
16108
16194
  import { spawn } from "child_process";
16109
16195
  function fetchLatestVersion() {
@@ -16138,16 +16224,39 @@ var MAX_RETRIES = 3;
16138
16224
  function isUpdating() {
16139
16225
  return updating;
16140
16226
  }
16141
- async function handleCliUpdate(version3, onSuccess) {
16227
+ function readUpdateMarker(profile) {
16228
+ try {
16229
+ return readFileSync4(lastUpdateMarkerPath(profile), "utf-8").trim() || null;
16230
+ } catch {
16231
+ return null;
16232
+ }
16233
+ }
16234
+ function writeUpdateMarker(version3, profile) {
16235
+ try {
16236
+ writeFileSync3(lastUpdateMarkerPath(profile), version3, { mode: 384 });
16237
+ } catch {}
16238
+ }
16239
+ function clearUpdateMarker(profile) {
16240
+ try {
16241
+ unlinkSync2(lastUpdateMarkerPath(profile));
16242
+ } catch {}
16243
+ }
16244
+ async function handleCliUpdate(version3, onSuccess, profile) {
16142
16245
  if (updating)
16143
16246
  return;
16144
16247
  if (retryCount >= MAX_RETRIES)
16145
16248
  return;
16249
+ const marker = readUpdateMarker(profile);
16250
+ if (marker === version3) {
16251
+ log.info(`Skipping update to v${version3} — already attempted (marker exists)`);
16252
+ return;
16253
+ }
16146
16254
  updating = true;
16147
16255
  try {
16148
16256
  log.info(`Updating CLI to v${version3}...`);
16149
16257
  const result = await runNpmUpdate(version3);
16150
16258
  if (result.success) {
16259
+ writeUpdateMarker(version3, profile);
16151
16260
  log.info(`CLI updated to v${version3} — restarting`);
16152
16261
  onSuccess();
16153
16262
  } else {
@@ -16163,7 +16272,7 @@ async function handleCliUpdate(version3, onSuccess) {
16163
16272
  }
16164
16273
 
16165
16274
  // daemon/daemon.ts
16166
- import { existsSync, mkdirSync as mkdirSync3, openSync, closeSync, renameSync, readdirSync, statSync, unlinkSync as unlinkSync2 } from "fs";
16275
+ import { existsSync, mkdirSync as mkdirSync3, openSync, closeSync, readdirSync, statSync, unlinkSync as unlinkSync3 } from "fs";
16167
16276
  import { execSync as execSync3, spawn as spawn2 } from "child_process";
16168
16277
  import { fileURLToPath as fileURLToPath2 } from "url";
16169
16278
  import { dirname as dirname3, join as join4 } from "path";
@@ -16200,7 +16309,7 @@ function pruneSessionRunnerLogs() {
16200
16309
  withMtime.sort((a, b) => b.mtime - a.mtime);
16201
16310
  for (const entry of withMtime.slice(MAX_SESSION_RUNNER_LOGS)) {
16202
16311
  try {
16203
- unlinkSync2(join4(logDir, entry.name));
16312
+ unlinkSync3(join4(logDir, entry.name));
16204
16313
  } catch {}
16205
16314
  }
16206
16315
  }
@@ -16220,6 +16329,11 @@ async function startDaemon(profile, serverUrl) {
16220
16329
  const config2 = loadDaemonConfig(profile);
16221
16330
  if (serverUrl)
16222
16331
  config2.serverURL = serverUrl;
16332
+ const marker = readUpdateMarker(profile);
16333
+ if (marker && marker === config2.cliVersion) {
16334
+ clearUpdateMarker(profile);
16335
+ log.info(`Cleared update marker — now running v${config2.cliVersion}`);
16336
+ }
16223
16337
  const cliConfig = loadCLIConfigForProfile(profile);
16224
16338
  const workspaces = cliConfig.watched_workspaces || [];
16225
16339
  if (workspaces.length === 0) {
@@ -16360,8 +16474,8 @@ async function startDaemon(profile, serverUrl) {
16360
16474
  evictedIds.push(ws.workspaceId);
16361
16475
  continue;
16362
16476
  }
16363
- if (pending_update && !isUpdating()) {
16364
- handleCliUpdate(pending_update.version, () => requestRestart());
16477
+ if (pending_update && !isUpdating() && pending_update.version !== config2.cliVersion) {
16478
+ handleCliUpdate(pending_update.version, () => requestRestart(), profile);
16365
16479
  }
16366
16480
  for (const apiTask of apiTasks) {
16367
16481
  const task = fromApiTask(apiTask);
@@ -16433,21 +16547,18 @@ async function startDaemon(profile, serverUrl) {
16433
16547
  await pollCycle();
16434
16548
  }
16435
16549
  function spawnSessionRunner(input) {
16436
- const encoded = Buffer.from(JSON.stringify(input)).toString("base64");
16437
16550
  const logDir = sessionRunnerLogDir();
16438
16551
  mkdirSync3(logDir, { recursive: true });
16439
- const tmpLogPath = join4(logDir, `${input.task.id}.log`);
16440
- const fd = openSync(tmpLogPath, "a");
16552
+ const logFilePath = join4(logDir, `${input.task.id}.log`);
16553
+ input.logFilePath = logFilePath;
16554
+ const encoded = Buffer.from(JSON.stringify(input)).toString("base64");
16555
+ const fd = openSync(logFilePath, "a");
16441
16556
  const child = spawn2(process.execPath, [sessionRunnerPath, encoded], {
16442
16557
  detached: true,
16443
16558
  stdio: ["ignore", fd, fd]
16444
16559
  });
16445
16560
  child.unref();
16446
16561
  closeSync(fd);
16447
- if (child.pid) {
16448
- const pidLogPath = join4(logDir, `${child.pid}.log`);
16449
- renameSync(tmpLogPath, pidLogPath);
16450
- }
16451
16562
  return child;
16452
16563
  }
16453
16564
  async function handleTask(client, config2, runtimeIndex, task, token, activeTasks) {
@@ -16630,7 +16741,7 @@ function configCommand() {
16630
16741
 
16631
16742
  // commands/email.ts
16632
16743
  import { Command as Command5 } from "commander";
16633
- import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
16744
+ import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
16634
16745
  import { basename, join as join5 } from "path";
16635
16746
  import PostalMime from "postal-mime";
16636
16747
  var VALID_STATUSES = ["unread", "read", "archived"];
@@ -16721,7 +16832,7 @@ function emailCommand() {
16721
16832
  references: email3.references || ""
16722
16833
  };
16723
16834
  const metadataPath = join5(emailDir, "metadata.json");
16724
- writeFileSync3(metadataPath, JSON.stringify(metadata, null, 2));
16835
+ writeFileSync4(metadataPath, JSON.stringify(metadata, null, 2));
16725
16836
  downloadedPaths.push(metadataPath);
16726
16837
  let rawMime;
16727
16838
  try {
@@ -16737,12 +16848,12 @@ function emailCommand() {
16737
16848
  const parsed = await new PostalMime().parse(rawMime);
16738
16849
  if (parsed.text) {
16739
16850
  const bodyPath = join5(emailDir, "body.txt");
16740
- writeFileSync3(bodyPath, parsed.text);
16851
+ writeFileSync4(bodyPath, parsed.text);
16741
16852
  downloadedPaths.push(bodyPath);
16742
16853
  }
16743
16854
  if (parsed.html) {
16744
16855
  const htmlPath = join5(emailDir, "body.html");
16745
- writeFileSync3(htmlPath, parsed.html);
16856
+ writeFileSync4(htmlPath, parsed.html);
16746
16857
  downloadedPaths.push(htmlPath);
16747
16858
  }
16748
16859
  if (parsed.attachments && parsed.attachments.length > 0) {
@@ -16766,7 +16877,7 @@ function emailCommand() {
16766
16877
  } else {
16767
16878
  buf = Buffer.from(content);
16768
16879
  }
16769
- writeFileSync3(attPath, buf);
16880
+ writeFileSync4(attPath, buf);
16770
16881
  downloadedPaths.push(attPath);
16771
16882
  }
16772
16883
  }
@@ -16797,7 +16908,7 @@ function emailCommand() {
16797
16908
  process.exit(1);
16798
16909
  }
16799
16910
  });
16800
- cmd.command("send").description("Send an email from the agent").requiredOption("--agent_id <id>", "Agent ID").requiredOption("--to <addr>", "Recipient email address").requiredOption("--subject <s>", "Subject line").requiredOption("--body-file <path>", "Path to HTML body file").option("--in-reply-to <emailId>", "Email ID to reply to (sets threading headers)").option("--attachment <path>", "Path to a file to attach (repeatable)", collectRepeated, []).option("--workspace <id>", "Workspace ID").action(async (opts, command) => {
16911
+ cmd.command("send").description("Send an email from the agent").requiredOption("--agent_id <id>", "Agent ID").requiredOption("--to <addr>", "Recipient email address").requiredOption("--subject <s>", "Subject line").requiredOption("--body-file <path>", "Path to HTML body file").option("--from <addr>", "Send from a specific email address (custom mailbox)").option("--in-reply-to <emailId>", "Email ID to reply to (sets threading headers)").option("--attachment <path>", "Path to a file to attach (repeatable)", collectRepeated, []).option("--workspace <id>", "Workspace ID").action(async (opts, command) => {
16801
16912
  const { serverUrl, token, workspaceId } = resolveClientOpts(command, {
16802
16913
  workspace: opts.workspace,
16803
16914
  agentId: opts.agent_id
@@ -16805,7 +16916,7 @@ function emailCommand() {
16805
16916
  const client = new APIClient(serverUrl, token, workspaceId);
16806
16917
  let htmlBody;
16807
16918
  try {
16808
- htmlBody = readFileSync4(opts.bodyFile, "utf-8");
16919
+ htmlBody = readFileSync5(opts.bodyFile, "utf-8");
16809
16920
  } catch (err) {
16810
16921
  console.error(`Error: cannot read body file "${opts.bodyFile}": ${err instanceof Error ? err.message : err}`);
16811
16922
  process.exit(1);
@@ -16821,7 +16932,7 @@ function emailCommand() {
16821
16932
  let bytes;
16822
16933
  let size;
16823
16934
  try {
16824
- bytes = readFileSync4(path);
16935
+ bytes = readFileSync5(path);
16825
16936
  size = statSync2(path).size;
16826
16937
  } catch (err) {
16827
16938
  console.error(`Error: cannot read attachment "${path}": ${err instanceof Error ? err.message : err}`);
@@ -16858,7 +16969,8 @@ function emailCommand() {
16858
16969
  subject: opts.subject,
16859
16970
  htmlBody,
16860
16971
  attachments,
16861
- ...inReplyTo ? { inReplyTo, references } : {}
16972
+ ...inReplyTo ? { inReplyTo, references } : {},
16973
+ ...opts.from ? { from: opts.from } : {}
16862
16974
  });
16863
16975
  console.log(`Sent email to ${res.to_email} (id: ${res.id})`);
16864
16976
  } catch (err) {
@@ -17125,7 +17237,7 @@ ${result.output}`);
17125
17237
 
17126
17238
  // commands/sync.ts
17127
17239
  import { Command as Command9 } from "commander";
17128
- import { readFileSync as readFileSync5, statSync as statSync3 } from "fs";
17240
+ import { readFileSync as readFileSync6, statSync as statSync3 } from "fs";
17129
17241
  import { basename as basename2 } from "path";
17130
17242
  var MIME_BY_EXT2 = {
17131
17243
  ".pdf": "application/pdf",
@@ -17176,7 +17288,7 @@ function syncCommand() {
17176
17288
  try {
17177
17289
  const stat = statSync3(opts.file);
17178
17290
  size = stat.size;
17179
- bytes = readFileSync5(opts.file);
17291
+ bytes = readFileSync6(opts.file);
17180
17292
  } catch (err) {
17181
17293
  console.error(`Error: cannot read file "${opts.file}": ${err.message}`);
17182
17294
  process.exit(1);
@@ -14,7 +14,6 @@ var __export = (target, all) => {
14
14
  };
15
15
 
16
16
  // daemon/session-runner.ts
17
- import { createWriteStream } from "fs";
18
17
  import { mkdir, writeFile, rm } from "fs/promises";
19
18
  import path from "path";
20
19
 
@@ -13596,6 +13595,7 @@ var TaskAgentDataApiSchema = exports_external.object({
13596
13595
  name: exports_external.string(),
13597
13596
  runtime_config: exports_external.record(exports_external.string(), exports_external.unknown()).default({}),
13598
13597
  email_handle: exports_external.string().nullable().optional(),
13598
+ email_addresses: exports_external.array(exports_external.string()).default([]),
13599
13599
  user_email: exports_external.string().nullable().optional()
13600
13600
  });
13601
13601
  var TaskApiBaseSchema = exports_external.object({
@@ -13770,7 +13770,9 @@ var SendEmailRequestSchema = exports_external.object({
13770
13770
  htmlBody: exports_external.string().default(""),
13771
13771
  inReplyTo: exports_external.string().optional(),
13772
13772
  references: exports_external.string().optional(),
13773
- attachments: exports_external.array(EmailAttachmentSchema).optional()
13773
+ attachments: exports_external.array(EmailAttachmentSchema).optional(),
13774
+ customAccountId: exports_external.string().optional(),
13775
+ from: exports_external.string().email().optional()
13774
13776
  });
13775
13777
  var UpdateEmailStatusRequestSchema = exports_external.object({
13776
13778
  status: exports_external.enum(["unread", "read", "archived"])
@@ -13788,6 +13790,48 @@ var EmailNotifyRequestSchema = exports_external.object({
13788
13790
  inReplyTo: exports_external.string().optional().default(""),
13789
13791
  references: exports_external.string().optional().default("")
13790
13792
  });
13793
+ var CreateEmailAccountSchema = exports_external.object({
13794
+ emailAddress: exports_external.string().email("valid email required"),
13795
+ displayName: exports_external.string().default(""),
13796
+ imapHost: exports_external.string().min(1, "IMAP host is required"),
13797
+ imapPort: exports_external.number().int().min(1).max(65535).default(993),
13798
+ imapUsername: exports_external.string().min(1, "IMAP username is required"),
13799
+ imapPassword: exports_external.string().min(1, "IMAP password is required"),
13800
+ imapTls: exports_external.boolean().default(true),
13801
+ smtpHost: exports_external.string().min(1, "SMTP host is required"),
13802
+ smtpPort: exports_external.number().int().min(1).max(65535).default(587),
13803
+ smtpUsername: exports_external.string().min(1, "SMTP username is required"),
13804
+ smtpPassword: exports_external.string().min(1, "SMTP password is required"),
13805
+ smtpTls: exports_external.number().int().min(0).max(2).default(1),
13806
+ pollIntervalSeconds: exports_external.number().int().min(30).max(3600).default(60)
13807
+ });
13808
+ var UpdateEmailAccountSchema = exports_external.object({
13809
+ emailAddress: exports_external.string().email().optional(),
13810
+ displayName: exports_external.string().optional(),
13811
+ imapHost: exports_external.string().min(1).optional(),
13812
+ imapPort: exports_external.number().int().min(1).max(65535).optional(),
13813
+ imapUsername: exports_external.string().min(1).optional(),
13814
+ imapPassword: exports_external.string().min(1).optional(),
13815
+ imapTls: exports_external.boolean().optional(),
13816
+ smtpHost: exports_external.string().min(1).optional(),
13817
+ smtpPort: exports_external.number().int().min(1).max(65535).optional(),
13818
+ smtpUsername: exports_external.string().min(1).optional(),
13819
+ smtpPassword: exports_external.string().min(1).optional(),
13820
+ smtpTls: exports_external.number().int().min(0).max(2).optional(),
13821
+ pollIntervalSeconds: exports_external.number().int().min(30).max(3600).optional()
13822
+ });
13823
+ var TestEmailConnectionSchema = exports_external.object({
13824
+ imapHost: exports_external.string().min(1),
13825
+ imapPort: exports_external.number().int().min(1).max(65535).default(993),
13826
+ imapUsername: exports_external.string().min(1),
13827
+ imapPassword: exports_external.string().min(1),
13828
+ imapTls: exports_external.boolean().default(true),
13829
+ smtpHost: exports_external.string().min(1),
13830
+ smtpPort: exports_external.number().int().min(1).max(65535).default(587),
13831
+ smtpUsername: exports_external.string().min(1),
13832
+ smtpPassword: exports_external.string().min(1),
13833
+ smtpTls: exports_external.number().int().min(0).max(2).default(1)
13834
+ });
13791
13835
  var CreateWorkspaceRequestSchema = exports_external.object({
13792
13836
  name: exports_external.string().min(1, "name is required"),
13793
13837
  slug: exports_external.string().min(1, "slug is required")
@@ -15401,6 +15445,37 @@ var artifact = sqliteTable("artifact", {
15401
15445
  foreignColumns: [agent.id, agent.workspaceId]
15402
15446
  }).onDelete("cascade")
15403
15447
  ]);
15448
+ var agentEmailAccount = sqliteTable("agent_email_account", {
15449
+ id: text("id").primaryKey().$defaultFn(() => "aea_" + nanoid3()),
15450
+ agentId: text("agent_id").notNull(),
15451
+ workspaceId: text("workspace_id").notNull(),
15452
+ emailAddress: text("email_address").notNull(),
15453
+ displayName: text("display_name").notNull().default(""),
15454
+ imapHost: text("imap_host").notNull(),
15455
+ imapPort: integer2("imap_port").notNull().default(993),
15456
+ imapUsername: text("imap_username").notNull(),
15457
+ imapPassword: text("imap_password").notNull(),
15458
+ imapTls: integer2("imap_tls", { mode: "boolean" }).notNull().default(true),
15459
+ smtpHost: text("smtp_host").notNull(),
15460
+ smtpPort: integer2("smtp_port").notNull().default(587),
15461
+ smtpUsername: text("smtp_username").notNull(),
15462
+ smtpPassword: text("smtp_password").notNull(),
15463
+ smtpTls: integer2("smtp_tls").notNull().default(1),
15464
+ pollIntervalSeconds: integer2("poll_interval_seconds").notNull().default(60),
15465
+ lastSyncedUid: text("last_synced_uid").notNull().default("0"),
15466
+ lastSyncedAt: text("last_synced_at"),
15467
+ status: text("status").notNull().default("active"),
15468
+ errorMessage: text("error_message").notNull().default(""),
15469
+ createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString()),
15470
+ updatedAt: text("updated_at").notNull().$defaultFn(() => new Date().toISOString())
15471
+ }, (t) => [
15472
+ index("idx_email_account_agent_ws").on(t.agentId, t.workspaceId),
15473
+ unique("email_account_agent_email").on(t.agentId, t.emailAddress),
15474
+ foreignKey({
15475
+ columns: [t.agentId, t.workspaceId],
15476
+ foreignColumns: [agent.id, agent.workspaceId]
15477
+ }).onDelete("cascade")
15478
+ ]);
15404
15479
  var machineToken = sqliteTable("machine_token", {
15405
15480
  id: text("id").primaryKey().$defaultFn(() => nanoid3()),
15406
15481
  userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
@@ -16421,7 +16496,7 @@ function createBackend(provider, cliPath) {
16421
16496
 
16422
16497
  // daemon/execenv/index.ts
16423
16498
  import { mkdirSync as mkdirSync2 } from "fs";
16424
- import { join as join3, dirname } from "path";
16499
+ import { join as join3 } from "path";
16425
16500
 
16426
16501
  // daemon/execenv/context.ts
16427
16502
  import { createHash } from "crypto";
@@ -16499,9 +16574,18 @@ ${task.agent.instructions}
16499
16574
  You can communicate with the world through Alook CLI.
16500
16575
  Your alook agent id is '${task.agentId}'. remember this, most of alook cli will requires you input your agent id.
16501
16576
  `;
16502
- if (task.agent?.emailHandle) {
16503
- content += `Your email address is '${toAlookAddress(task.agent.emailHandle)}'.
16504
- ${task.agent.userEmail ? `Your owner's email address is '${task.agent.userEmail}'.` : ""}
16577
+ const alookAddr = task.agent?.emailHandle ? toAlookAddress(task.agent.emailHandle) : null;
16578
+ const customAddrs = (task.agent?.emailAddresses ?? []).filter((a) => a !== alookAddr);
16579
+ if (alookAddr || customAddrs.length > 0) {
16580
+ const lines = [];
16581
+ if (alookAddr)
16582
+ lines.push(`- '${alookAddr}' (default, Alook platform address)`);
16583
+ for (const a of customAddrs)
16584
+ lines.push(`- '${a}' (custom IMAP/SMTP mailbox)`);
16585
+ content += `Your email addresses:
16586
+ ${lines.join(`
16587
+ `)}
16588
+ ${task.agent?.userEmail ? `Your owner's email address is '${task.agent.userEmail}'.` : ""}
16505
16589
 
16506
16590
  ### Emails
16507
16591
  ---
@@ -16519,8 +16603,9 @@ Before starting to process an email, mark it as read:
16519
16603
  #### Sending a new email
16520
16604
  Write the HTML body to a file first, then send it. The body is forwarded as-is (HTML).
16521
16605
  - Run 'npx @alook/cli email send --agent_id ${task.agentId} --to <ADDRESS> --subject "<SUBJECT>" --body-file <PATH_TO_HTML>'
16606
+ - To send from a specific mailbox, add '--from <YOUR_EMAIL_ADDRESS>'. Without '--from', the default Alook address is used.
16522
16607
  - Attach files with '--attachment <PATH>' — repeat the flag for multiple attachments. Each file is uploaded before sending.
16523
- - Example: 'npx @alook/cli email send --agent_id ${task.agentId} --to foo@bar.com --subject "Weekly report" --body-file /tmp/body.html --attachment /tmp/report.pdf --attachment /tmp/chart.png'
16608
+ - Example: 'npx @alook/cli email send --agent_id ${task.agentId} --to foo@bar.com --subject "Weekly report" --body-file /tmp/body.html --from alice@company.com --attachment /tmp/report.pdf'
16524
16609
 
16525
16610
  #### Replying to an email
16526
16611
  To reply to an email, add '--in-reply-to <EMAIL_ID>' to the send command. This sets the correct email threading headers so the recipient's email client groups the reply into the same conversation thread.
@@ -16708,10 +16793,13 @@ function useColor() {
16708
16793
  }
16709
16794
  function timestamp() {
16710
16795
  const d = new Date;
16796
+ const Y = d.getFullYear();
16797
+ const M = String(d.getMonth() + 1).padStart(2, "0");
16798
+ const D = String(d.getDate()).padStart(2, "0");
16711
16799
  const h = String(d.getHours()).padStart(2, "0");
16712
16800
  const m = String(d.getMinutes()).padStart(2, "0");
16713
16801
  const s = String(d.getSeconds()).padStart(2, "0");
16714
- return `${h}:${m}:${s}`;
16802
+ return `${Y}-${M}-${D} ${h}:${m}:${s}`;
16715
16803
  }
16716
16804
 
16717
16805
  class Logger2 {
@@ -16896,7 +16984,7 @@ function updateEntry(timelineDir, taskId, updater) {
16896
16984
  }
16897
16985
  log.debug(`Timeline updateEntry: task_id ${taskId} not found in last 7 days`);
16898
16986
  }
16899
- function createTimelineEntry(taskId, prompt, type, sessionId, pid, provider, contextKey) {
16987
+ function createTimelineEntry(taskId, prompt, type, sessionId, pid, provider, contextKey, detailedLog) {
16900
16988
  return {
16901
16989
  task_id: taskId,
16902
16990
  context_key: contextKey ?? null,
@@ -16908,7 +16996,8 @@ function createTimelineEntry(taskId, prompt, type, sessionId, pid, provider, con
16908
16996
  prompt,
16909
16997
  agent_responses: [],
16910
16998
  errmsg: null,
16911
- provider: provider ?? null
16999
+ provider: provider ?? null,
17000
+ detailed_log: detailedLog ?? null
16912
17001
  };
16913
17002
  }
16914
17003
  var DEFAULT_RESUME_MAX_AGE_MS = 3 * 60 * 60 * 1000;
@@ -16938,8 +17027,6 @@ function prepare(config2, task) {
16938
17027
  const timelineDir = join3(workDir, ".context_timeline");
16939
17028
  mkdirSync2(timelineDir, { recursive: true });
16940
17029
  writeInstructionFileIfChanged(workDir, task);
16941
- const logFile = join3(config2.workspacesRoot, task.workspaceId, task.agentId, "agent.log");
16942
- mkdirSync2(dirname(logFile), { recursive: true });
16943
17030
  const env = {
16944
17031
  ALOOK_WORKSPACE_ID: task.workspaceId,
16945
17032
  ALOOK_AGENT_ID: task.agentId,
@@ -16947,7 +17034,7 @@ function prepare(config2, task) {
16947
17034
  ALOOK_CONVERSATION_ID: task.conversationId,
16948
17035
  ALOOK_HEALTH_PORT: process.env.ALOOK_HEALTH_PORT || "19514"
16949
17036
  };
16950
- return { workDir, logFile, timelineDir, env };
17037
+ return { workDir, timelineDir, env };
16951
17038
  }
16952
17039
 
16953
17040
  // daemon/prompt.ts
@@ -16993,18 +17080,21 @@ async function downloadAttachments(client, token, workspaceId, taskId, attachmen
16993
17080
  }
16994
17081
  async function runSession(input) {
16995
17082
  const { task, provider, cliPath, model, serverURL, token, workspacesRoot, agentTimeout } = input;
17083
+ log.info(`starting (task=${task.id}, type=${task.type}, agent=${task.agentId}, provider=${provider}, model=${model || "default"})`);
16996
17084
  const client = new DaemonClient(serverURL);
16997
17085
  const backend = createBackend(provider, cliPath);
16998
- const { workDir, logFile, timelineDir, env } = prepare({ workspacesRoot }, task);
17086
+ const { workDir, timelineDir, env } = prepare({ workspacesRoot }, task);
16999
17087
  const attachmentIds = task.context?.attachment_ids ?? [];
17000
17088
  let attachments;
17001
17089
  if (attachmentIds.length > 0) {
17090
+ log.info(`downloading ${attachmentIds.length} attachment(s)`);
17002
17091
  try {
17003
17092
  attachments = await downloadAttachments(client, token, task.workspaceId, task.id, attachmentIds);
17093
+ log.info(`attachments ready (${attachments.length} file(s))`);
17004
17094
  } catch (e) {
17005
17095
  await cleanupAttachments(task.id);
17006
17096
  const errMsg = `failed to download attachments: ${e}`;
17007
- log.error(`Task ${task.id} ${errMsg}`);
17097
+ log.error(errMsg);
17008
17098
  await client.failTask(token, task.id, errMsg);
17009
17099
  return;
17010
17100
  }
@@ -17012,7 +17102,7 @@ async function runSession(input) {
17012
17102
  const prompt = buildPrompt(task, attachments);
17013
17103
  const resumeSessionId = task.contextKey ? findResumableSessionByContextKey(timelineDir, task.contextKey, provider) ?? undefined : undefined;
17014
17104
  if (resumeSessionId) {
17015
- log.info(`Task ${task.id} resuming session ${resumeSessionId} (context_key: ${task.contextKey})`);
17105
+ log.info(`resuming session ${resumeSessionId} (context_key: ${task.contextKey})`);
17016
17106
  }
17017
17107
  const session2 = backend.execute(prompt, {
17018
17108
  cwd: workDir,
@@ -17023,9 +17113,12 @@ async function runSession(input) {
17023
17113
  });
17024
17114
  const agentPid = session2.pid;
17025
17115
  const earlySessionId = await session2.sessionId;
17026
- await initEntryAsync(timelineDir, createTimelineEntry(task.id, task.prompt, task.type, earlySessionId, process.pid, provider, task.contextKey));
17116
+ log.info(`agent started (pid=${agentPid ?? "unknown"}, session=${earlySessionId})`);
17117
+ log.info(JSON.stringify({ role: "user", type: "text", content: prompt }));
17118
+ await initEntryAsync(timelineDir, createTimelineEntry(task.id, task.prompt, task.type, earlySessionId, process.pid, provider, task.contextKey, input.logFilePath));
17027
17119
  const pendingMessages = [];
17028
17120
  let seq = 0;
17121
+ let toolCount = 0;
17029
17122
  const BATCH_SIZE = Number(process.env.ALOOK_MESSAGE_BATCH_SIZE) || 20;
17030
17123
  const FLUSH_INTERVAL_MS = Number(process.env.ALOOK_MESSAGE_FLUSH_INTERVAL_MS) || 100;
17031
17124
  const flushMessages = async () => {
@@ -17035,29 +17128,16 @@ async function runSession(input) {
17035
17128
  try {
17036
17129
  await client.reportMessages(token, task.id, batch);
17037
17130
  } catch (e) {
17038
- log.debug(`Task ${task.id} message report failed`, e);
17131
+ log.debug("message report failed", e);
17039
17132
  }
17040
17133
  };
17041
17134
  const flushTimer = setInterval(flushMessages, FLUSH_INTERVAL_MS);
17042
- let logStream;
17043
- try {
17044
- logStream = createWriteStream(logFile, { flags: "a" });
17045
- logStream.write(JSON.stringify({
17046
- ts: localISOString(),
17047
- type: "text",
17048
- role: "user",
17049
- content: prompt
17050
- }) + `
17051
- `);
17052
- } catch {
17053
- logStream = undefined;
17054
- }
17055
17135
  let killed = false;
17056
17136
  const onKill = async () => {
17057
17137
  if (killed)
17058
17138
  return;
17059
17139
  killed = true;
17060
- log.info(`Task ${task.id} killed by signal`);
17140
+ log.info(`killed by signal (messages=${seq}, tools=${toolCount})`);
17061
17141
  if (agentPid) {
17062
17142
  try {
17063
17143
  process.kill(agentPid, "SIGTERM");
@@ -17067,7 +17147,6 @@ async function runSession(input) {
17067
17147
  try {
17068
17148
  await flushMessages();
17069
17149
  } catch {}
17070
- logStream?.end();
17071
17150
  await cleanupAttachments(task.id);
17072
17151
  updateEntry(timelineDir, task.id, (entry) => {
17073
17152
  entry.pid = null;
@@ -17095,21 +17174,14 @@ async function runSession(input) {
17095
17174
  input: msg.input,
17096
17175
  output: msg.output
17097
17176
  });
17177
+ if (msg.type === "tool-use")
17178
+ toolCount++;
17179
+ log.info(JSON.stringify({ role: "assistant", ...msg }));
17098
17180
  if (msg.type === "text" && msg.content) {
17099
17181
  updateEntry(timelineDir, task.id, (entry) => {
17100
17182
  entry.agent_responses.push(msg.content);
17101
17183
  });
17102
17184
  }
17103
- if (logStream) {
17104
- try {
17105
- logStream.write(JSON.stringify({
17106
- ts: localISOString(),
17107
- role: "assistant",
17108
- ...msg
17109
- }) + `
17110
- `);
17111
- } catch {}
17112
- }
17113
17185
  if (pendingMessages.length >= BATCH_SIZE) {
17114
17186
  await flushMessages();
17115
17187
  }
@@ -17118,7 +17190,6 @@ async function runSession(input) {
17118
17190
  await flushMessages();
17119
17191
  } finally {
17120
17192
  clearInterval(flushTimer);
17121
- logStream?.end();
17122
17193
  process.removeListener("SIGTERM", onKill);
17123
17194
  process.removeListener("SIGINT", onKill);
17124
17195
  }
@@ -17152,10 +17223,12 @@ async function runSession(input) {
17152
17223
  if (result.sessionId)
17153
17224
  body.session_id = result.sessionId;
17154
17225
  await client.completeTask(token, task.id, body);
17155
- log.info(`Task ${task.id} completed`);
17226
+ const dur = (result.durationMs / 1000).toFixed(1);
17227
+ log.info(`completed (duration=${dur}s, messages=${seq}, tools=${toolCount})`);
17156
17228
  } else {
17157
17229
  await client.failTask(token, task.id, result.error || "unknown error");
17158
- log.info(`Task ${task.id} failed — ${result.error}`);
17230
+ const dur = (result.durationMs / 1000).toFixed(1);
17231
+ log.info(`failed (duration=${dur}s, messages=${seq}, tools=${toolCount}) — ${result.error}`);
17159
17232
  }
17160
17233
  }
17161
17234
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alook/cli",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
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",