@alook/cli 0.0.3 → 0.0.5

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 Command7 } from "commander";
18
+ import { Command as Command8 } from "commander";
19
19
 
20
20
  // commands/register.ts
21
21
  import { Command } from "commander";
@@ -64,6 +64,25 @@ class APIClient {
64
64
  patchJSON(path, body) {
65
65
  return this.request("PATCH", path, body);
66
66
  }
67
+ async postMultipart(path, form) {
68
+ const headers = {
69
+ Authorization: `Bearer ${this.token}`
70
+ };
71
+ if (this.workspaceId)
72
+ headers["X-Workspace-ID"] = this.workspaceId;
73
+ const res = await fetch(this.baseURL + path, {
74
+ method: "POST",
75
+ headers,
76
+ body: form
77
+ });
78
+ if (!res.ok) {
79
+ const text = await res.text();
80
+ throw new Error(`HTTP ${res.status}: ${text}`);
81
+ }
82
+ if (res.status === 204)
83
+ return;
84
+ return res.json();
85
+ }
67
86
  async getText(path) {
68
87
  const headers = {
69
88
  Authorization: `Bearer ${this.token}`
@@ -281,6 +300,11 @@ import { openSync, closeSync, mkdirSync as mkdirSync3 } from "fs";
281
300
  import { dirname as dirname3 } from "path";
282
301
 
283
302
  // ../shared/src/constants.ts
303
+ var TASK_TYPES = {
304
+ USER_DM_MESSAGE: "user_dm_message",
305
+ EMAIL_NOTIFICATION: "email_notification",
306
+ CALENDAR_EVENT: "calendar_event"
307
+ };
284
308
  var POLL_INTERVAL_MS = Number(process.env.POLL_INTERVAL_MS) || 3000;
285
309
  var OFFLINE_THRESHOLD_MS = Number(process.env.OFFLINE_THRESHOLD_MS) || 9000;
286
310
  var EVENT_POLL_INTERVAL_MS = Number(process.env.EVENT_POLL_INTERVAL_MS) || 2000;
@@ -13839,7 +13863,7 @@ var ClaimedTaskRowSchema = exports_external.object({
13839
13863
  priority: exports_external.coerce.number(),
13840
13864
  result: exports_external.unknown().nullable(),
13841
13865
  context: exports_external.unknown().nullable(),
13842
- type: exports_external.string().default("user_dm_message"),
13866
+ type: exports_external.string().default(TASK_TYPES.USER_DM_MESSAGE),
13843
13867
  sessionId: exports_external.string().nullable(),
13844
13868
  createdAt: exports_external.coerce.date(),
13845
13869
  dispatchedAt: exports_external.coerce.date().nullable(),
@@ -13932,6 +13956,50 @@ var MessageItemSchema = exports_external.object({
13932
13956
  var ReportMessagesRequestSchema = exports_external.object({
13933
13957
  messages: exports_external.array(MessageItemSchema)
13934
13958
  });
13959
+ var RepeatIntervalSchema = exports_external.string().regex(/^\d+(min|hour|day|week|month)$/, {
13960
+ message: "repeat_interval must match <positive_integer><min|hour|day|week|month>"
13961
+ });
13962
+ var CreateCalendarEventRequestSchema = exports_external.object({
13963
+ agent_id: exports_external.string().min(1),
13964
+ title: exports_external.string().min(1),
13965
+ description: exports_external.string().max(20000).optional(),
13966
+ scheduled_at: exports_external.string().min(1).refine((s) => !Number.isNaN(Date.parse(s)), {
13967
+ message: "scheduled_at must be a valid ISO datetime"
13968
+ }),
13969
+ repeat_interval: RepeatIntervalSchema.optional(),
13970
+ repeat_stop_date: exports_external.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional()
13971
+ }).refine((data) => !data.repeat_stop_date || !!data.repeat_interval, {
13972
+ message: "repeat_stop_date requires repeat_interval",
13973
+ path: ["repeat_stop_date"]
13974
+ });
13975
+ var UpdateCalendarEventRequestSchema = exports_external.object({
13976
+ title: exports_external.string().min(1).optional(),
13977
+ description: exports_external.string().max(20000).nullable().optional(),
13978
+ agent_id: exports_external.string().min(1).optional(),
13979
+ scheduled_at: exports_external.string().min(1).refine((s) => !Number.isNaN(Date.parse(s)), {
13980
+ message: "scheduled_at must be a valid ISO datetime"
13981
+ }).optional(),
13982
+ repeat_interval: RepeatIntervalSchema.nullable().optional(),
13983
+ repeat_stop_date: exports_external.string().regex(/^\d{4}-\d{2}-\d{2}$/).nullable().optional(),
13984
+ scope: exports_external.enum(["this", "following"]).optional(),
13985
+ occurrence_at: exports_external.string().min(1).refine((s) => !Number.isNaN(Date.parse(s)), {
13986
+ message: "occurrence_at must be a valid ISO datetime"
13987
+ }).optional()
13988
+ }).refine((v) => v.title !== undefined || v.description !== undefined || v.agent_id !== undefined || v.scheduled_at !== undefined || v.repeat_interval !== undefined || v.repeat_stop_date !== undefined, { message: "at least one field is required" });
13989
+ var CalendarEventApiSchema = exports_external.object({
13990
+ id: exports_external.string(),
13991
+ agent_id: exports_external.string(),
13992
+ workspace_id: exports_external.string(),
13993
+ title: exports_external.string(),
13994
+ description: exports_external.string().nullable(),
13995
+ scheduled_at: exports_external.string(),
13996
+ occurrence_at: exports_external.string(),
13997
+ repeat_interval: exports_external.string().nullable(),
13998
+ repeat_stop_at: exports_external.string().nullable(),
13999
+ last_triggered_at: exports_external.string().nullable(),
14000
+ created_at: exports_external.string(),
14001
+ updated_at: exports_external.string()
14002
+ });
13935
14003
  // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@4.20260414.1_@opentelemetry+api@1.9.1_bun-types@1.3.12_kysely@0.28.15/node_modules/drizzle-orm/entity.js
13936
14004
  var entityKind = Symbol.for("drizzle:entityKind");
13937
14005
  var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
@@ -15424,6 +15492,7 @@ var conversation = sqliteTable("conversation", {
15424
15492
  agentId: text("agent_id").notNull(),
15425
15493
  userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
15426
15494
  title: text("title").notNull().default(""),
15495
+ type: text("type").notNull().default(TASK_TYPES.USER_DM_MESSAGE),
15427
15496
  createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
15428
15497
  }, (t) => [
15429
15498
  foreignKey({
@@ -15446,7 +15515,7 @@ var agentTaskQueue = sqliteTable("agent_task_queue", {
15446
15515
  workspaceId: text("workspace_id").notNull().references(() => workspace.id),
15447
15516
  conversationId: text("conversation_id").notNull().references(() => conversation.id),
15448
15517
  prompt: text("prompt").notNull(),
15449
- type: text("type").notNull().default("user_dm_message"),
15518
+ type: text("type").notNull().default(TASK_TYPES.USER_DM_MESSAGE),
15450
15519
  status: text("status").notNull().default("queued"),
15451
15520
  priority: integer2("priority").notNull().default(0),
15452
15521
  result: text("result", { mode: "json" }),
@@ -15496,6 +15565,27 @@ var emails = sqliteTable("emails", {
15496
15565
  foreignColumns: [agent.id, agent.workspaceId]
15497
15566
  }).onDelete("cascade")
15498
15567
  ]);
15568
+ var calendarEvent = sqliteTable("calendar_event", {
15569
+ id: text("id").primaryKey().$defaultFn(() => "ce_" + nanoid3()),
15570
+ agentId: text("agent_id").notNull(),
15571
+ workspaceId: text("workspace_id").notNull(),
15572
+ title: text("title").notNull(),
15573
+ description: text("description"),
15574
+ scheduledAt: text("scheduled_at").notNull(),
15575
+ repeatInterval: text("repeat_interval"),
15576
+ repeatStopAt: text("repeat_stop_at"),
15577
+ lastTriggeredAt: text("last_triggered_at"),
15578
+ exceptions: text("exceptions", { mode: "json" }).$type().notNull().default(sql`'[]'`),
15579
+ createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString()),
15580
+ updatedAt: text("updated_at").notNull().$defaultFn(() => new Date().toISOString())
15581
+ }, (t) => [
15582
+ index("idx_calendar_event_agent_ws").on(t.agentId, t.workspaceId),
15583
+ index("idx_calendar_event_ws_scheduled").on(t.workspaceId, t.scheduledAt),
15584
+ foreignKey({
15585
+ columns: [t.agentId, t.workspaceId],
15586
+ foreignColumns: [agent.id, agent.workspaceId]
15587
+ }).onDelete("cascade")
15588
+ ]);
15499
15589
  var machineToken = sqliteTable("machine_token", {
15500
15590
  id: text("id").primaryKey().$defaultFn(() => nanoid3()),
15501
15591
  userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
@@ -15862,9 +15952,12 @@ function releaseDaemonPid(profile) {
15862
15952
  }
15863
15953
 
15864
15954
  // daemon/daemon.ts
15955
+ import { existsSync } from "fs";
15865
15956
  import { execSync as execSync3, spawn } from "child_process";
15866
15957
  import { fileURLToPath } from "url";
15867
15958
  import { dirname as dirname2, join as join3 } from "path";
15959
+ var _dir = dirname2(fileURLToPath(import.meta.url));
15960
+ var sessionRunnerPath = existsSync(join3(_dir, "session-runner.js")) ? join3(_dir, "session-runner.js") : join3(_dir, "session-runner.ts");
15868
15961
  function isCommandAvailable2(cmd) {
15869
15962
  try {
15870
15963
  execSync3(`which ${cmd}`, { stdio: "ignore" });
@@ -16036,10 +16129,9 @@ async function startDaemon(profile, serverUrl) {
16036
16129
  process.on("SIGINT", shutdown);
16037
16130
  await pollCycle();
16038
16131
  }
16039
- var SESSION_RUNNER_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "session-runner.ts");
16040
16132
  function spawnSessionRunner(input) {
16041
16133
  const encoded = Buffer.from(JSON.stringify(input)).toString("base64");
16042
- const child = spawn("bun", ["run", SESSION_RUNNER_PATH, encoded], {
16134
+ const child = spawn(process.execPath, [sessionRunnerPath, encoded], {
16043
16135
  detached: true,
16044
16136
  stdio: "ignore"
16045
16137
  });
@@ -16224,11 +16316,37 @@ function configCommand() {
16224
16316
 
16225
16317
  // commands/email.ts
16226
16318
  import { Command as Command5 } from "commander";
16227
- import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
16228
- import { join as join4 } from "path";
16319
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync3, statSync } from "fs";
16320
+ import { basename, join as join4 } from "path";
16229
16321
  import PostalMime from "postal-mime";
16230
16322
  var VALID_STATUSES = ["unread", "read", "archived"];
16231
16323
  var EMAIL_DIR = "/tmp/alook-emails";
16324
+ var MIME_BY_EXT = {
16325
+ ".pdf": "application/pdf",
16326
+ ".png": "image/png",
16327
+ ".jpg": "image/jpeg",
16328
+ ".jpeg": "image/jpeg",
16329
+ ".gif": "image/gif",
16330
+ ".webp": "image/webp",
16331
+ ".svg": "image/svg+xml",
16332
+ ".txt": "text/plain",
16333
+ ".html": "text/html",
16334
+ ".htm": "text/html",
16335
+ ".json": "application/json",
16336
+ ".csv": "text/csv",
16337
+ ".md": "text/markdown",
16338
+ ".zip": "application/zip"
16339
+ };
16340
+ function guessContentType(filename) {
16341
+ const idx = filename.lastIndexOf(".");
16342
+ if (idx < 0)
16343
+ return "application/octet-stream";
16344
+ const ext = filename.slice(idx).toLowerCase();
16345
+ return MIME_BY_EXT[ext] ?? "application/octet-stream";
16346
+ }
16347
+ function collectRepeated(value, previous) {
16348
+ return previous.concat([value]);
16349
+ }
16232
16350
  function resolveClientOpts(command, opts) {
16233
16351
  const parentOpts = command.parent?.parent?.opts() || {};
16234
16352
  const profile = parentOpts.profile;
@@ -16362,14 +16480,278 @@ function emailCommand() {
16362
16480
  process.exit(1);
16363
16481
  }
16364
16482
  });
16483
+ 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("--attachment <path>", "Path to a file to attach (repeatable)", collectRepeated, []).option("--workspace <id>", "Workspace ID").action(async (opts, command) => {
16484
+ const { serverUrl, token, workspaceId } = resolveClientOpts(command, {
16485
+ workspace: opts.workspace,
16486
+ agentId: opts.agent_id
16487
+ });
16488
+ const client = new APIClient(serverUrl, token, workspaceId);
16489
+ let htmlBody;
16490
+ try {
16491
+ htmlBody = readFileSync3(opts.bodyFile, "utf-8");
16492
+ } catch (err) {
16493
+ console.error(`Error: cannot read body file "${opts.bodyFile}": ${err instanceof Error ? err.message : err}`);
16494
+ process.exit(1);
16495
+ }
16496
+ if (!htmlBody) {
16497
+ console.error(`Error: body file "${opts.bodyFile}" is empty`);
16498
+ process.exit(1);
16499
+ }
16500
+ const attachmentPaths = opts.attachment ?? [];
16501
+ const attachments = [];
16502
+ try {
16503
+ for (const path of attachmentPaths) {
16504
+ let bytes;
16505
+ let size;
16506
+ try {
16507
+ bytes = readFileSync3(path);
16508
+ size = statSync(path).size;
16509
+ } catch (err) {
16510
+ console.error(`Error: cannot read attachment "${path}": ${err instanceof Error ? err.message : err}`);
16511
+ process.exit(1);
16512
+ }
16513
+ const filename = basename(path);
16514
+ const contentType = guessContentType(filename);
16515
+ const form = new FormData;
16516
+ form.append("file", new Blob([new Uint8Array(bytes)], { type: contentType }), filename);
16517
+ const uploaded = await client.postMultipart("/api/email/upload", form);
16518
+ attachments.push({
16519
+ key: uploaded.key,
16520
+ filename: uploaded.filename,
16521
+ size: uploaded.size ?? size,
16522
+ contentType: uploaded.contentType ?? contentType
16523
+ });
16524
+ }
16525
+ const res = await client.postJSON("/api/email/send", {
16526
+ agentId: opts.agent_id,
16527
+ to: opts.to,
16528
+ subject: opts.subject,
16529
+ htmlBody,
16530
+ attachments
16531
+ });
16532
+ console.log(`Sent email to ${res.to_email} (id: ${res.id})`);
16533
+ } catch (err) {
16534
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
16535
+ process.exit(1);
16536
+ }
16537
+ });
16365
16538
  return cmd;
16366
16539
  }
16367
16540
 
16368
- // commands/version.ts
16541
+ // commands/calendar.ts
16369
16542
  import { Command as Command6 } from "commander";
16543
+ function resolveClientOpts2(command, agentId) {
16544
+ const parentOpts = command.parent?.parent?.opts() || {};
16545
+ const profile = parentOpts.profile;
16546
+ const cfg = loadCLIConfigForProfile(profile);
16547
+ const serverUrl = parentOpts.server || cfg.server_url;
16548
+ const workspaces = cfg.watched_workspaces || [];
16549
+ const ws = workspaces.find((w) => w.agent_ids?.includes(agentId));
16550
+ if (!ws || !ws.token) {
16551
+ console.error(`Error: no registered workspace contains agent ${agentId}. Run '${cmdPrefix()} register --token <token>' first.`);
16552
+ process.exit(1);
16553
+ }
16554
+ return { serverUrl, token: ws.token, workspaceId: ws.id };
16555
+ }
16556
+ function parseLocalDatetime(input) {
16557
+ const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?$/.exec(input);
16558
+ if (!match) {
16559
+ throw new Error(`invalid --datetime "${input}" — expected YYYY-MM-DDTHH:MM`);
16560
+ }
16561
+ const [, y, mo, d, h, mi, s] = match;
16562
+ const date5 = new Date(Number(y), Number(mo) - 1, Number(d), Number(h), Number(mi), s ? Number(s) : 0);
16563
+ return date5.toISOString();
16564
+ }
16565
+ function formatLocalDatetime(iso) {
16566
+ const d = new Date(iso);
16567
+ const pad = (n) => String(n).padStart(2, "0");
16568
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
16569
+ }
16570
+ function printEventDetail(ev) {
16571
+ console.log(`id: ${ev.id}`);
16572
+ console.log(`agent_id: ${ev.agent_id}`);
16573
+ console.log(`title: ${ev.title}`);
16574
+ console.log(`scheduled_at: ${formatLocalDatetime(ev.scheduled_at)}`);
16575
+ if (ev.repeat_interval) {
16576
+ const until = ev.repeat_stop_at ? ` until ${formatLocalDatetime(ev.repeat_stop_at)}` : "";
16577
+ console.log(`repeat: every ${ev.repeat_interval}${until}`);
16578
+ } else {
16579
+ console.log(`repeat: (none)`);
16580
+ }
16581
+ console.log(`last_fired_at: ${ev.last_triggered_at ? formatLocalDatetime(ev.last_triggered_at) : "(never)"}`);
16582
+ console.log("description:");
16583
+ console.log(ev.description ?? "(no description)");
16584
+ }
16585
+ function calendarCommand() {
16586
+ const cmd = new Command6("calendar").description("Manage scheduled agent events");
16587
+ cmd.command("set").description("Create a calendar event").requiredOption("--agent_id <id>", "Agent ID").requiredOption("--event_title <title>", "Event title (used as the task prompt)").requiredOption("--datetime <iso>", "Scheduled datetime (YYYY-MM-DDTHH:MM, local time)").option("--description <text>", "Optional longer-form notes for the event").option("--repeat <interval>", "Repeat interval, e.g. 1day, 2hour, 1month").option("--repeat_stop_date <date>", "Stop repeating on or after this date (YYYY-MM-DD, local time)").option("--json", "Output as JSON").action(async (opts, command) => {
16588
+ const { serverUrl, token, workspaceId } = resolveClientOpts2(command, opts.agent_id);
16589
+ const client = new APIClient(serverUrl, token, workspaceId);
16590
+ let scheduledAt;
16591
+ try {
16592
+ scheduledAt = parseLocalDatetime(opts.datetime);
16593
+ } catch (err) {
16594
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
16595
+ process.exit(1);
16596
+ }
16597
+ if (opts.repeat_stop_date && !opts.repeat) {
16598
+ console.error("Error: --repeat_stop_date requires --repeat");
16599
+ process.exit(1);
16600
+ }
16601
+ if (opts.repeat_stop_date && !/^\d{4}-\d{2}-\d{2}$/.test(opts.repeat_stop_date)) {
16602
+ console.error("Error: --repeat_stop_date must be YYYY-MM-DD");
16603
+ process.exit(1);
16604
+ }
16605
+ const body = {
16606
+ agent_id: opts.agent_id,
16607
+ title: opts.event_title,
16608
+ scheduled_at: scheduledAt
16609
+ };
16610
+ if (opts.description)
16611
+ body.description = opts.description;
16612
+ if (opts.repeat)
16613
+ body.repeat_interval = opts.repeat;
16614
+ if (opts.repeat_stop_date)
16615
+ body.repeat_stop_date = opts.repeat_stop_date;
16616
+ try {
16617
+ const created = await client.postJSON("/api/calendar", body);
16618
+ if (opts.json) {
16619
+ printJSON(created);
16620
+ return;
16621
+ }
16622
+ console.log(`Created ${created.id} — ${created.title} @ ${formatLocalDatetime(created.scheduled_at)}${created.repeat_interval ? ` (every ${created.repeat_interval})` : ""}`);
16623
+ } catch (err) {
16624
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
16625
+ process.exit(1);
16626
+ }
16627
+ });
16628
+ cmd.command("list").description("List calendar events for an agent").requiredOption("--agent_id <id>", "Agent ID").option("--future_days <n>", "Include events scheduled in the next N days", "30").option("--past_days <n>", "Include events scheduled in the past N days", "0").option("--json", "Output as JSON").action(async (opts, command) => {
16629
+ const { serverUrl, token, workspaceId } = resolveClientOpts2(command, opts.agent_id);
16630
+ const client = new APIClient(serverUrl, token, workspaceId);
16631
+ const now = Date.now();
16632
+ const from = new Date(now - Number(opts.past_days) * 86400000).toISOString();
16633
+ const to = new Date(now + Number(opts.future_days) * 86400000).toISOString();
16634
+ try {
16635
+ const events = await client.getJSON(`/api/calendar?agentId=${encodeURIComponent(opts.agent_id)}&from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`);
16636
+ if (opts.json) {
16637
+ printJSON(events);
16638
+ return;
16639
+ }
16640
+ if (events.length === 0) {
16641
+ console.log("No calendar events.");
16642
+ return;
16643
+ }
16644
+ for (const ev of events) {
16645
+ const repeatBadge = ev.repeat_interval ? ` [every ${ev.repeat_interval}${ev.repeat_stop_at ? ` until ${formatLocalDatetime(ev.repeat_stop_at)}` : ""}]` : "";
16646
+ const descBadge = ev.description ? " [has description]" : "";
16647
+ console.log(`${ev.id} ${formatLocalDatetime(ev.scheduled_at)} ${ev.title}${repeatBadge}${descBadge}`);
16648
+ }
16649
+ } catch (err) {
16650
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
16651
+ process.exit(1);
16652
+ }
16653
+ });
16654
+ cmd.command("show").description("Show the full detail of a single calendar event").requiredOption("--agent_id <id>", "Agent ID").requiredOption("--event_id <id>", "Event ID").option("--json", "Output as JSON").action(async (opts, command) => {
16655
+ const { serverUrl, token, workspaceId } = resolveClientOpts2(command, opts.agent_id);
16656
+ const client = new APIClient(serverUrl, token, workspaceId);
16657
+ try {
16658
+ const ev = await client.getJSON(`/api/calendar/${opts.event_id}`);
16659
+ if (ev.agent_id !== opts.agent_id) {
16660
+ console.error(`Error: event ${ev.id} does not belong to agent ${opts.agent_id}`);
16661
+ process.exit(1);
16662
+ }
16663
+ if (opts.json) {
16664
+ printJSON(ev);
16665
+ return;
16666
+ }
16667
+ printEventDetail(ev);
16668
+ } catch (err) {
16669
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
16670
+ process.exit(1);
16671
+ }
16672
+ });
16673
+ cmd.command("update").description("Update fields on an existing calendar event").requiredOption("--agent_id <id>", "Agent ID").requiredOption("--event_id <id>", "Event ID").option("--event_title <title>", "New event title (task prompt)").option("--description <text>", "New description text").option("--clear_description", "Remove the description (sets to null)").option("--datetime <iso>", "New scheduled datetime (YYYY-MM-DDTHH:MM, local time)").option("--repeat <interval>", "New repeat interval, e.g. 1day, 2hour").option("--clear_repeat", "Convert a repeating event into a one-off").option("--repeat_stop_date <date>", "New stop date (YYYY-MM-DD, local time)").option("--clear_repeat_stop_date", "Remove the repeat stop date").option("--json", "Output as JSON").action(async (opts, command) => {
16674
+ if (opts.description && opts.clear_description) {
16675
+ console.error("Error: --description and --clear_description are mutually exclusive");
16676
+ process.exit(1);
16677
+ }
16678
+ if (opts.repeat && opts.clear_repeat) {
16679
+ console.error("Error: --repeat and --clear_repeat are mutually exclusive");
16680
+ process.exit(1);
16681
+ }
16682
+ if (opts.repeat_stop_date && opts.clear_repeat_stop_date) {
16683
+ console.error("Error: --repeat_stop_date and --clear_repeat_stop_date are mutually exclusive");
16684
+ process.exit(1);
16685
+ }
16686
+ const body = {};
16687
+ if (opts.event_title)
16688
+ body.title = opts.event_title;
16689
+ if (opts.description)
16690
+ body.description = opts.description;
16691
+ if (opts.clear_description)
16692
+ body.description = null;
16693
+ if (opts.datetime) {
16694
+ try {
16695
+ body.scheduled_at = parseLocalDatetime(opts.datetime);
16696
+ } catch (err) {
16697
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
16698
+ process.exit(1);
16699
+ }
16700
+ }
16701
+ if (opts.repeat)
16702
+ body.repeat_interval = opts.repeat;
16703
+ if (opts.clear_repeat)
16704
+ body.repeat_interval = null;
16705
+ if (opts.repeat_stop_date) {
16706
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(opts.repeat_stop_date)) {
16707
+ console.error("Error: --repeat_stop_date must be YYYY-MM-DD");
16708
+ process.exit(1);
16709
+ }
16710
+ body.repeat_stop_date = opts.repeat_stop_date;
16711
+ }
16712
+ if (opts.clear_repeat_stop_date)
16713
+ body.repeat_stop_date = null;
16714
+ if (Object.keys(body).length === 0) {
16715
+ console.error("Error: no fields to update — pass at least one of --event_title, --description, --clear_description, --datetime, --repeat, --clear_repeat, --repeat_stop_date, --clear_repeat_stop_date");
16716
+ process.exit(1);
16717
+ }
16718
+ const { serverUrl, token, workspaceId } = resolveClientOpts2(command, opts.agent_id);
16719
+ const client = new APIClient(serverUrl, token, workspaceId);
16720
+ try {
16721
+ const updated = await client.patchJSON(`/api/calendar/${opts.event_id}`, body);
16722
+ if (updated.agent_id !== opts.agent_id) {
16723
+ console.error(`Error: event ${updated.id} does not belong to agent ${opts.agent_id}`);
16724
+ process.exit(1);
16725
+ }
16726
+ if (opts.json) {
16727
+ printJSON(updated);
16728
+ return;
16729
+ }
16730
+ printEventDetail(updated);
16731
+ } catch (err) {
16732
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
16733
+ process.exit(1);
16734
+ }
16735
+ });
16736
+ cmd.command("delete").description("Delete a calendar event").requiredOption("--agent_id <id>", "Agent ID").requiredOption("--event_id <id>", "Event ID").action(async (opts, command) => {
16737
+ const { serverUrl, token, workspaceId } = resolveClientOpts2(command, opts.agent_id);
16738
+ const client = new APIClient(serverUrl, token, workspaceId);
16739
+ try {
16740
+ await client.deleteJSON(`/api/calendar/${opts.event_id}`);
16741
+ console.log(`Deleted ${opts.event_id}`);
16742
+ } catch (err) {
16743
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
16744
+ process.exit(1);
16745
+ }
16746
+ });
16747
+ return cmd;
16748
+ }
16749
+
16750
+ // commands/version.ts
16751
+ import { Command as Command7 } from "commander";
16370
16752
 
16371
16753
  // lib/version.ts
16372
- import { readFileSync as readFileSync3 } from "fs";
16754
+ import { readFileSync as readFileSync4 } from "fs";
16373
16755
  import { join as join5, dirname as dirname4 } from "path";
16374
16756
  import { fileURLToPath as fileURLToPath2 } from "url";
16375
16757
  function getCurrentVersion() {
@@ -16380,7 +16762,7 @@ function getCurrentVersion() {
16380
16762
  ];
16381
16763
  for (const candidate of candidates) {
16382
16764
  try {
16383
- const pkg = JSON.parse(readFileSync3(candidate, "utf-8"));
16765
+ const pkg = JSON.parse(readFileSync4(candidate, "utf-8"));
16384
16766
  if (typeof pkg.version === "string")
16385
16767
  return pkg.version;
16386
16768
  } catch {}
@@ -16390,19 +16772,20 @@ function getCurrentVersion() {
16390
16772
 
16391
16773
  // commands/version.ts
16392
16774
  function versionCommand() {
16393
- const cmd = new Command6("version").description("Show CLI version").action(() => {
16775
+ const cmd = new Command7("version").description("Show CLI version").action(() => {
16394
16776
  console.log(`alook version ${getCurrentVersion()}`);
16395
16777
  });
16396
16778
  return cmd;
16397
16779
  }
16398
16780
 
16399
16781
  // src/index.ts
16400
- var program = new Command7;
16782
+ var program = new Command8;
16401
16783
  program.name("alook").description("Alook CLI").option("--server <url>", "Server URL").option("--profile <name>", "Profile name");
16402
16784
  program.addCommand(registerCommand());
16403
16785
  program.addCommand(statusCommand());
16404
16786
  program.addCommand(daemonCommand());
16405
16787
  program.addCommand(emailCommand());
16788
+ program.addCommand(calendarCommand());
16406
16789
  program.addCommand(configCommand());
16407
16790
  program.addCommand(versionCommand());
16408
16791
  program.parse();