@alook/cli 0.0.3 → 0.0.4

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.
Files changed (2) hide show
  1. package/dist/index.js +391 -10
  2. package/package.json +1 -1
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" }),
@@ -16224,11 +16314,37 @@ function configCommand() {
16224
16314
 
16225
16315
  // commands/email.ts
16226
16316
  import { Command as Command5 } from "commander";
16227
- import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
16228
- import { join as join4 } from "path";
16317
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync3, statSync } from "fs";
16318
+ import { basename, join as join4 } from "path";
16229
16319
  import PostalMime from "postal-mime";
16230
16320
  var VALID_STATUSES = ["unread", "read", "archived"];
16231
16321
  var EMAIL_DIR = "/tmp/alook-emails";
16322
+ var MIME_BY_EXT = {
16323
+ ".pdf": "application/pdf",
16324
+ ".png": "image/png",
16325
+ ".jpg": "image/jpeg",
16326
+ ".jpeg": "image/jpeg",
16327
+ ".gif": "image/gif",
16328
+ ".webp": "image/webp",
16329
+ ".svg": "image/svg+xml",
16330
+ ".txt": "text/plain",
16331
+ ".html": "text/html",
16332
+ ".htm": "text/html",
16333
+ ".json": "application/json",
16334
+ ".csv": "text/csv",
16335
+ ".md": "text/markdown",
16336
+ ".zip": "application/zip"
16337
+ };
16338
+ function guessContentType(filename) {
16339
+ const idx = filename.lastIndexOf(".");
16340
+ if (idx < 0)
16341
+ return "application/octet-stream";
16342
+ const ext = filename.slice(idx).toLowerCase();
16343
+ return MIME_BY_EXT[ext] ?? "application/octet-stream";
16344
+ }
16345
+ function collectRepeated(value, previous) {
16346
+ return previous.concat([value]);
16347
+ }
16232
16348
  function resolveClientOpts(command, opts) {
16233
16349
  const parentOpts = command.parent?.parent?.opts() || {};
16234
16350
  const profile = parentOpts.profile;
@@ -16362,14 +16478,278 @@ function emailCommand() {
16362
16478
  process.exit(1);
16363
16479
  }
16364
16480
  });
16481
+ 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) => {
16482
+ const { serverUrl, token, workspaceId } = resolveClientOpts(command, {
16483
+ workspace: opts.workspace,
16484
+ agentId: opts.agent_id
16485
+ });
16486
+ const client = new APIClient(serverUrl, token, workspaceId);
16487
+ let htmlBody;
16488
+ try {
16489
+ htmlBody = readFileSync3(opts.bodyFile, "utf-8");
16490
+ } catch (err) {
16491
+ console.error(`Error: cannot read body file "${opts.bodyFile}": ${err instanceof Error ? err.message : err}`);
16492
+ process.exit(1);
16493
+ }
16494
+ if (!htmlBody) {
16495
+ console.error(`Error: body file "${opts.bodyFile}" is empty`);
16496
+ process.exit(1);
16497
+ }
16498
+ const attachmentPaths = opts.attachment ?? [];
16499
+ const attachments = [];
16500
+ try {
16501
+ for (const path of attachmentPaths) {
16502
+ let bytes;
16503
+ let size;
16504
+ try {
16505
+ bytes = readFileSync3(path);
16506
+ size = statSync(path).size;
16507
+ } catch (err) {
16508
+ console.error(`Error: cannot read attachment "${path}": ${err instanceof Error ? err.message : err}`);
16509
+ process.exit(1);
16510
+ }
16511
+ const filename = basename(path);
16512
+ const contentType = guessContentType(filename);
16513
+ const form = new FormData;
16514
+ form.append("file", new Blob([new Uint8Array(bytes)], { type: contentType }), filename);
16515
+ const uploaded = await client.postMultipart("/api/email/upload", form);
16516
+ attachments.push({
16517
+ key: uploaded.key,
16518
+ filename: uploaded.filename,
16519
+ size: uploaded.size ?? size,
16520
+ contentType: uploaded.contentType ?? contentType
16521
+ });
16522
+ }
16523
+ const res = await client.postJSON("/api/email/send", {
16524
+ agentId: opts.agent_id,
16525
+ to: opts.to,
16526
+ subject: opts.subject,
16527
+ htmlBody,
16528
+ attachments
16529
+ });
16530
+ console.log(`Sent email to ${res.to_email} (id: ${res.id})`);
16531
+ } catch (err) {
16532
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
16533
+ process.exit(1);
16534
+ }
16535
+ });
16365
16536
  return cmd;
16366
16537
  }
16367
16538
 
16368
- // commands/version.ts
16539
+ // commands/calendar.ts
16369
16540
  import { Command as Command6 } from "commander";
16541
+ function resolveClientOpts2(command, agentId) {
16542
+ const parentOpts = command.parent?.parent?.opts() || {};
16543
+ const profile = parentOpts.profile;
16544
+ const cfg = loadCLIConfigForProfile(profile);
16545
+ const serverUrl = parentOpts.server || cfg.server_url;
16546
+ const workspaces = cfg.watched_workspaces || [];
16547
+ const ws = workspaces.find((w) => w.agent_ids?.includes(agentId));
16548
+ if (!ws || !ws.token) {
16549
+ console.error(`Error: no registered workspace contains agent ${agentId}. Run '${cmdPrefix()} register --token <token>' first.`);
16550
+ process.exit(1);
16551
+ }
16552
+ return { serverUrl, token: ws.token, workspaceId: ws.id };
16553
+ }
16554
+ function parseLocalDatetime(input) {
16555
+ const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?$/.exec(input);
16556
+ if (!match) {
16557
+ throw new Error(`invalid --datetime "${input}" — expected YYYY-MM-DDTHH:MM`);
16558
+ }
16559
+ const [, y, mo, d, h, mi, s] = match;
16560
+ const date5 = new Date(Number(y), Number(mo) - 1, Number(d), Number(h), Number(mi), s ? Number(s) : 0);
16561
+ return date5.toISOString();
16562
+ }
16563
+ function formatLocalDatetime(iso) {
16564
+ const d = new Date(iso);
16565
+ const pad = (n) => String(n).padStart(2, "0");
16566
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
16567
+ }
16568
+ function printEventDetail(ev) {
16569
+ console.log(`id: ${ev.id}`);
16570
+ console.log(`agent_id: ${ev.agent_id}`);
16571
+ console.log(`title: ${ev.title}`);
16572
+ console.log(`scheduled_at: ${formatLocalDatetime(ev.scheduled_at)}`);
16573
+ if (ev.repeat_interval) {
16574
+ const until = ev.repeat_stop_at ? ` until ${formatLocalDatetime(ev.repeat_stop_at)}` : "";
16575
+ console.log(`repeat: every ${ev.repeat_interval}${until}`);
16576
+ } else {
16577
+ console.log(`repeat: (none)`);
16578
+ }
16579
+ console.log(`last_fired_at: ${ev.last_triggered_at ? formatLocalDatetime(ev.last_triggered_at) : "(never)"}`);
16580
+ console.log("description:");
16581
+ console.log(ev.description ?? "(no description)");
16582
+ }
16583
+ function calendarCommand() {
16584
+ const cmd = new Command6("calendar").description("Manage scheduled agent events");
16585
+ 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) => {
16586
+ const { serverUrl, token, workspaceId } = resolveClientOpts2(command, opts.agent_id);
16587
+ const client = new APIClient(serverUrl, token, workspaceId);
16588
+ let scheduledAt;
16589
+ try {
16590
+ scheduledAt = parseLocalDatetime(opts.datetime);
16591
+ } catch (err) {
16592
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
16593
+ process.exit(1);
16594
+ }
16595
+ if (opts.repeat_stop_date && !opts.repeat) {
16596
+ console.error("Error: --repeat_stop_date requires --repeat");
16597
+ process.exit(1);
16598
+ }
16599
+ if (opts.repeat_stop_date && !/^\d{4}-\d{2}-\d{2}$/.test(opts.repeat_stop_date)) {
16600
+ console.error("Error: --repeat_stop_date must be YYYY-MM-DD");
16601
+ process.exit(1);
16602
+ }
16603
+ const body = {
16604
+ agent_id: opts.agent_id,
16605
+ title: opts.event_title,
16606
+ scheduled_at: scheduledAt
16607
+ };
16608
+ if (opts.description)
16609
+ body.description = opts.description;
16610
+ if (opts.repeat)
16611
+ body.repeat_interval = opts.repeat;
16612
+ if (opts.repeat_stop_date)
16613
+ body.repeat_stop_date = opts.repeat_stop_date;
16614
+ try {
16615
+ const created = await client.postJSON("/api/calendar", body);
16616
+ if (opts.json) {
16617
+ printJSON(created);
16618
+ return;
16619
+ }
16620
+ console.log(`Created ${created.id} — ${created.title} @ ${formatLocalDatetime(created.scheduled_at)}${created.repeat_interval ? ` (every ${created.repeat_interval})` : ""}`);
16621
+ } catch (err) {
16622
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
16623
+ process.exit(1);
16624
+ }
16625
+ });
16626
+ 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) => {
16627
+ const { serverUrl, token, workspaceId } = resolveClientOpts2(command, opts.agent_id);
16628
+ const client = new APIClient(serverUrl, token, workspaceId);
16629
+ const now = Date.now();
16630
+ const from = new Date(now - Number(opts.past_days) * 86400000).toISOString();
16631
+ const to = new Date(now + Number(opts.future_days) * 86400000).toISOString();
16632
+ try {
16633
+ const events = await client.getJSON(`/api/calendar?agentId=${encodeURIComponent(opts.agent_id)}&from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`);
16634
+ if (opts.json) {
16635
+ printJSON(events);
16636
+ return;
16637
+ }
16638
+ if (events.length === 0) {
16639
+ console.log("No calendar events.");
16640
+ return;
16641
+ }
16642
+ for (const ev of events) {
16643
+ const repeatBadge = ev.repeat_interval ? ` [every ${ev.repeat_interval}${ev.repeat_stop_at ? ` until ${formatLocalDatetime(ev.repeat_stop_at)}` : ""}]` : "";
16644
+ const descBadge = ev.description ? " [has description]" : "";
16645
+ console.log(`${ev.id} ${formatLocalDatetime(ev.scheduled_at)} ${ev.title}${repeatBadge}${descBadge}`);
16646
+ }
16647
+ } catch (err) {
16648
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
16649
+ process.exit(1);
16650
+ }
16651
+ });
16652
+ 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) => {
16653
+ const { serverUrl, token, workspaceId } = resolveClientOpts2(command, opts.agent_id);
16654
+ const client = new APIClient(serverUrl, token, workspaceId);
16655
+ try {
16656
+ const ev = await client.getJSON(`/api/calendar/${opts.event_id}`);
16657
+ if (ev.agent_id !== opts.agent_id) {
16658
+ console.error(`Error: event ${ev.id} does not belong to agent ${opts.agent_id}`);
16659
+ process.exit(1);
16660
+ }
16661
+ if (opts.json) {
16662
+ printJSON(ev);
16663
+ return;
16664
+ }
16665
+ printEventDetail(ev);
16666
+ } catch (err) {
16667
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
16668
+ process.exit(1);
16669
+ }
16670
+ });
16671
+ 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) => {
16672
+ if (opts.description && opts.clear_description) {
16673
+ console.error("Error: --description and --clear_description are mutually exclusive");
16674
+ process.exit(1);
16675
+ }
16676
+ if (opts.repeat && opts.clear_repeat) {
16677
+ console.error("Error: --repeat and --clear_repeat are mutually exclusive");
16678
+ process.exit(1);
16679
+ }
16680
+ if (opts.repeat_stop_date && opts.clear_repeat_stop_date) {
16681
+ console.error("Error: --repeat_stop_date and --clear_repeat_stop_date are mutually exclusive");
16682
+ process.exit(1);
16683
+ }
16684
+ const body = {};
16685
+ if (opts.event_title)
16686
+ body.title = opts.event_title;
16687
+ if (opts.description)
16688
+ body.description = opts.description;
16689
+ if (opts.clear_description)
16690
+ body.description = null;
16691
+ if (opts.datetime) {
16692
+ try {
16693
+ body.scheduled_at = parseLocalDatetime(opts.datetime);
16694
+ } catch (err) {
16695
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
16696
+ process.exit(1);
16697
+ }
16698
+ }
16699
+ if (opts.repeat)
16700
+ body.repeat_interval = opts.repeat;
16701
+ if (opts.clear_repeat)
16702
+ body.repeat_interval = null;
16703
+ if (opts.repeat_stop_date) {
16704
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(opts.repeat_stop_date)) {
16705
+ console.error("Error: --repeat_stop_date must be YYYY-MM-DD");
16706
+ process.exit(1);
16707
+ }
16708
+ body.repeat_stop_date = opts.repeat_stop_date;
16709
+ }
16710
+ if (opts.clear_repeat_stop_date)
16711
+ body.repeat_stop_date = null;
16712
+ if (Object.keys(body).length === 0) {
16713
+ 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");
16714
+ process.exit(1);
16715
+ }
16716
+ const { serverUrl, token, workspaceId } = resolveClientOpts2(command, opts.agent_id);
16717
+ const client = new APIClient(serverUrl, token, workspaceId);
16718
+ try {
16719
+ const updated = await client.patchJSON(`/api/calendar/${opts.event_id}`, body);
16720
+ if (updated.agent_id !== opts.agent_id) {
16721
+ console.error(`Error: event ${updated.id} does not belong to agent ${opts.agent_id}`);
16722
+ process.exit(1);
16723
+ }
16724
+ if (opts.json) {
16725
+ printJSON(updated);
16726
+ return;
16727
+ }
16728
+ printEventDetail(updated);
16729
+ } catch (err) {
16730
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
16731
+ process.exit(1);
16732
+ }
16733
+ });
16734
+ cmd.command("delete").description("Delete a calendar event").requiredOption("--agent_id <id>", "Agent ID").requiredOption("--event_id <id>", "Event ID").action(async (opts, command) => {
16735
+ const { serverUrl, token, workspaceId } = resolveClientOpts2(command, opts.agent_id);
16736
+ const client = new APIClient(serverUrl, token, workspaceId);
16737
+ try {
16738
+ await client.deleteJSON(`/api/calendar/${opts.event_id}`);
16739
+ console.log(`Deleted ${opts.event_id}`);
16740
+ } catch (err) {
16741
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
16742
+ process.exit(1);
16743
+ }
16744
+ });
16745
+ return cmd;
16746
+ }
16747
+
16748
+ // commands/version.ts
16749
+ import { Command as Command7 } from "commander";
16370
16750
 
16371
16751
  // lib/version.ts
16372
- import { readFileSync as readFileSync3 } from "fs";
16752
+ import { readFileSync as readFileSync4 } from "fs";
16373
16753
  import { join as join5, dirname as dirname4 } from "path";
16374
16754
  import { fileURLToPath as fileURLToPath2 } from "url";
16375
16755
  function getCurrentVersion() {
@@ -16380,7 +16760,7 @@ function getCurrentVersion() {
16380
16760
  ];
16381
16761
  for (const candidate of candidates) {
16382
16762
  try {
16383
- const pkg = JSON.parse(readFileSync3(candidate, "utf-8"));
16763
+ const pkg = JSON.parse(readFileSync4(candidate, "utf-8"));
16384
16764
  if (typeof pkg.version === "string")
16385
16765
  return pkg.version;
16386
16766
  } catch {}
@@ -16390,19 +16770,20 @@ function getCurrentVersion() {
16390
16770
 
16391
16771
  // commands/version.ts
16392
16772
  function versionCommand() {
16393
- const cmd = new Command6("version").description("Show CLI version").action(() => {
16773
+ const cmd = new Command7("version").description("Show CLI version").action(() => {
16394
16774
  console.log(`alook version ${getCurrentVersion()}`);
16395
16775
  });
16396
16776
  return cmd;
16397
16777
  }
16398
16778
 
16399
16779
  // src/index.ts
16400
- var program = new Command7;
16780
+ var program = new Command8;
16401
16781
  program.name("alook").description("Alook CLI").option("--server <url>", "Server URL").option("--profile <name>", "Profile name");
16402
16782
  program.addCommand(registerCommand());
16403
16783
  program.addCommand(statusCommand());
16404
16784
  program.addCommand(daemonCommand());
16405
16785
  program.addCommand(emailCommand());
16786
+ program.addCommand(calendarCommand());
16406
16787
  program.addCommand(configCommand());
16407
16788
  program.addCommand(versionCommand());
16408
16789
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alook/cli",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
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",