@alook/cli 0.0.2 → 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.
- package/dist/index.js +436 -165
- 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
|
|
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}`
|
|
@@ -97,7 +116,7 @@ function isDev() {
|
|
|
97
116
|
return !!process.env.ALOOK_SERVER_URL;
|
|
98
117
|
}
|
|
99
118
|
function cmdPrefix() {
|
|
100
|
-
return isDev() ? "pnpm dev:cli" : "alook";
|
|
119
|
+
return isDev() ? "pnpm dev:cli" : "npx @alook/cli";
|
|
101
120
|
}
|
|
102
121
|
|
|
103
122
|
// lib/config.ts
|
|
@@ -145,137 +164,6 @@ function saveCLIConfigForProfile(profile, profileConfig) {
|
|
|
145
164
|
saveCLIConfig(cfg);
|
|
146
165
|
}
|
|
147
166
|
|
|
148
|
-
// lib/installer.ts
|
|
149
|
-
import { spawnSync } from "child_process";
|
|
150
|
-
|
|
151
|
-
// lib/version.ts
|
|
152
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
153
|
-
import { join as join2, dirname } from "path";
|
|
154
|
-
import { fileURLToPath } from "url";
|
|
155
|
-
function getCurrentVersion() {
|
|
156
|
-
const __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
157
|
-
const candidates = [
|
|
158
|
-
join2(__dirname2, "..", "package.json"),
|
|
159
|
-
join2(__dirname2, "..", "..", "package.json")
|
|
160
|
-
];
|
|
161
|
-
for (const candidate of candidates) {
|
|
162
|
-
try {
|
|
163
|
-
const pkg = JSON.parse(readFileSync2(candidate, "utf-8"));
|
|
164
|
-
if (typeof pkg.version === "string")
|
|
165
|
-
return pkg.version;
|
|
166
|
-
} catch {}
|
|
167
|
-
}
|
|
168
|
-
return "unknown";
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// lib/installer.ts
|
|
172
|
-
var PACKAGE = "@alook/cli";
|
|
173
|
-
var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE}/latest`;
|
|
174
|
-
var FETCH_TIMEOUT_MS = 3000;
|
|
175
|
-
async function fetchLatestVersion() {
|
|
176
|
-
const ctrl = new AbortController;
|
|
177
|
-
const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
|
|
178
|
-
try {
|
|
179
|
-
const res = await fetch(REGISTRY_URL, { signal: ctrl.signal });
|
|
180
|
-
if (!res.ok)
|
|
181
|
-
return null;
|
|
182
|
-
const data = await res.json();
|
|
183
|
-
return typeof data.version === "string" ? data.version : null;
|
|
184
|
-
} catch {
|
|
185
|
-
return null;
|
|
186
|
-
} finally {
|
|
187
|
-
clearTimeout(timer);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
function isNewer(current, latest) {
|
|
191
|
-
const parse = (v) => v.split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
192
|
-
const [cMaj = 0, cMin = 0, cPatch = 0] = parse(current);
|
|
193
|
-
const [lMaj = 0, lMin = 0, lPatch = 0] = parse(latest);
|
|
194
|
-
if (lMaj !== cMaj)
|
|
195
|
-
return lMaj > cMaj;
|
|
196
|
-
if (lMin !== cMin)
|
|
197
|
-
return lMin > cMin;
|
|
198
|
-
return lPatch > cPatch;
|
|
199
|
-
}
|
|
200
|
-
function isNpx() {
|
|
201
|
-
return process.env.npm_command === "exec" || !!process.env.npm_execpath?.includes("npx-cli");
|
|
202
|
-
}
|
|
203
|
-
function detectPackageManager() {
|
|
204
|
-
const ua = process.env.npm_config_user_agent || "";
|
|
205
|
-
if (ua.startsWith("pnpm/"))
|
|
206
|
-
return "pnpm";
|
|
207
|
-
if (ua.startsWith("yarn/"))
|
|
208
|
-
return "yarn";
|
|
209
|
-
return "npm";
|
|
210
|
-
}
|
|
211
|
-
function installArgs(pm) {
|
|
212
|
-
switch (pm) {
|
|
213
|
-
case "pnpm":
|
|
214
|
-
return ["pnpm", ["add", "-g", PACKAGE]];
|
|
215
|
-
case "yarn":
|
|
216
|
-
return ["yarn", ["global", "add", PACKAGE]];
|
|
217
|
-
case "npm":
|
|
218
|
-
return ["npm", ["install", "-g", `${PACKAGE}@latest`]];
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
function installCmdString(pm) {
|
|
222
|
-
const [bin, args] = installArgs(pm);
|
|
223
|
-
return `${bin} ${args.join(" ")}`;
|
|
224
|
-
}
|
|
225
|
-
function runInstall(pm) {
|
|
226
|
-
const [bin, args] = installArgs(pm);
|
|
227
|
-
const result = spawnSync(bin, args, { stdio: "inherit" });
|
|
228
|
-
return result.status === 0;
|
|
229
|
-
}
|
|
230
|
-
async function ensureInstalled(opts = {}, deps = {}) {
|
|
231
|
-
const fetchLatest = deps.fetchLatest ?? fetchLatestVersion;
|
|
232
|
-
const runInst = deps.runInstall ?? runInstall;
|
|
233
|
-
const getCurrent = deps.getCurrent ?? getCurrentVersion;
|
|
234
|
-
const isNpxFn = deps.isNpxFn ?? isNpx;
|
|
235
|
-
const isDevFn = deps.isDevFn ?? isDev;
|
|
236
|
-
const log = deps.log ?? ((m) => console.log(m));
|
|
237
|
-
const current = getCurrent();
|
|
238
|
-
const pm = detectPackageManager();
|
|
239
|
-
if (isDevFn() || opts.skip) {
|
|
240
|
-
return { skipped: true, action: "none", current, latest: null, pm };
|
|
241
|
-
}
|
|
242
|
-
const latest = await fetchLatest();
|
|
243
|
-
if (!latest) {
|
|
244
|
-
return { skipped: false, action: "none", current, latest: null, pm };
|
|
245
|
-
}
|
|
246
|
-
const runningViaNpx = isNpxFn();
|
|
247
|
-
const needsInstall = runningViaNpx;
|
|
248
|
-
const needsUpdate = !runningViaNpx && isNewer(current, latest);
|
|
249
|
-
if (!needsInstall && !needsUpdate) {
|
|
250
|
-
log(`
|
|
251
|
-
✓ ${PACKAGE} is up to date (${current})`);
|
|
252
|
-
return { skipped: false, action: "none", current, latest, pm };
|
|
253
|
-
}
|
|
254
|
-
const cmdStr = installCmdString(pm);
|
|
255
|
-
if (needsInstall) {
|
|
256
|
-
log(`
|
|
257
|
-
Installing ${PACKAGE} globally (${cmdStr})...`);
|
|
258
|
-
} else {
|
|
259
|
-
log(`
|
|
260
|
-
Updating ${PACKAGE} ${current} → ${latest} (${cmdStr})...`);
|
|
261
|
-
}
|
|
262
|
-
const ok = runInst(pm);
|
|
263
|
-
if (!ok) {
|
|
264
|
-
log(`
|
|
265
|
-
Could not ${needsInstall ? "install" : "update"} ${PACKAGE} automatically.`);
|
|
266
|
-
log(`Install it manually: ${cmdStr}`);
|
|
267
|
-
return { skipped: false, action: "failed", current, latest, pm };
|
|
268
|
-
}
|
|
269
|
-
log(`✓ ${needsInstall ? "Installed" : "Updated"} ${PACKAGE} ${latest}`);
|
|
270
|
-
return {
|
|
271
|
-
skipped: false,
|
|
272
|
-
action: needsInstall ? "installed" : "updated",
|
|
273
|
-
current,
|
|
274
|
-
latest,
|
|
275
|
-
pm
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
|
|
279
167
|
// commands/register.ts
|
|
280
168
|
function isCommandAvailable(cmd) {
|
|
281
169
|
try {
|
|
@@ -299,7 +187,7 @@ function detectRuntimes() {
|
|
|
299
187
|
return found;
|
|
300
188
|
}
|
|
301
189
|
function registerCommand() {
|
|
302
|
-
const cmd = new Command("register").description("Register CLI with your Alook account").requiredOption("--token <token>", "API token (starts with al_)").option("--server <url>", "Server URL").option("--profile <name>", "Profile name").
|
|
190
|
+
const cmd = new Command("register").description("Register CLI with your Alook account").requiredOption("--token <token>", "API token (starts with al_)").option("--server <url>", "Server URL").option("--profile <name>", "Profile name").action(async (opts, command) => {
|
|
303
191
|
const token = opts.token;
|
|
304
192
|
const profile = opts.profile || command.parent?.opts().profile;
|
|
305
193
|
const serverUrl = opts.server || command.parent?.opts().server || process.env.ALOOK_SERVER_URL || "https://alook.ai";
|
|
@@ -380,7 +268,6 @@ Usage: ${cmdPrefix()} register --token <token>`);
|
|
|
380
268
|
Registered as ${me.email}`);
|
|
381
269
|
console.log(`Workspace: ${ws.name} (${ws.id})`);
|
|
382
270
|
console.log(`Runtimes: ${activateResp.runtimes.map((r) => r.provider).join(", ")}`);
|
|
383
|
-
await ensureInstalled({ skip: opts.install === false });
|
|
384
271
|
console.log();
|
|
385
272
|
console.log(`Run '${cmdPrefix()} daemon start --foreground' to start the daemon.`);
|
|
386
273
|
});
|
|
@@ -410,9 +297,14 @@ function statusCommand() {
|
|
|
410
297
|
import { Command as Command3 } from "commander";
|
|
411
298
|
import { spawn as spawn2 } from "child_process";
|
|
412
299
|
import { openSync, closeSync, mkdirSync as mkdirSync3 } from "fs";
|
|
413
|
-
import { dirname as
|
|
300
|
+
import { dirname as dirname3 } from "path";
|
|
414
301
|
|
|
415
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
|
+
};
|
|
416
308
|
var POLL_INTERVAL_MS = Number(process.env.POLL_INTERVAL_MS) || 3000;
|
|
417
309
|
var OFFLINE_THRESHOLD_MS = Number(process.env.OFFLINE_THRESHOLD_MS) || 9000;
|
|
418
310
|
var EVENT_POLL_INTERVAL_MS = Number(process.env.EVENT_POLL_INTERVAL_MS) || 2000;
|
|
@@ -13971,7 +13863,7 @@ var ClaimedTaskRowSchema = exports_external.object({
|
|
|
13971
13863
|
priority: exports_external.coerce.number(),
|
|
13972
13864
|
result: exports_external.unknown().nullable(),
|
|
13973
13865
|
context: exports_external.unknown().nullable(),
|
|
13974
|
-
type: exports_external.string().default(
|
|
13866
|
+
type: exports_external.string().default(TASK_TYPES.USER_DM_MESSAGE),
|
|
13975
13867
|
sessionId: exports_external.string().nullable(),
|
|
13976
13868
|
createdAt: exports_external.coerce.date(),
|
|
13977
13869
|
dispatchedAt: exports_external.coerce.date().nullable(),
|
|
@@ -14064,6 +13956,50 @@ var MessageItemSchema = exports_external.object({
|
|
|
14064
13956
|
var ReportMessagesRequestSchema = exports_external.object({
|
|
14065
13957
|
messages: exports_external.array(MessageItemSchema)
|
|
14066
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
|
+
});
|
|
14067
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
|
|
14068
14004
|
var entityKind = Symbol.for("drizzle:entityKind");
|
|
14069
14005
|
var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
|
|
@@ -14650,7 +14586,7 @@ function sql(strings, ...params) {
|
|
|
14650
14586
|
return new SQL([new StringChunk(str)]);
|
|
14651
14587
|
}
|
|
14652
14588
|
sql2.raw = raw;
|
|
14653
|
-
function
|
|
14589
|
+
function join2(chunks, separator) {
|
|
14654
14590
|
const result = [];
|
|
14655
14591
|
for (const [i, chunk] of chunks.entries()) {
|
|
14656
14592
|
if (i > 0 && separator !== undefined) {
|
|
@@ -14660,7 +14596,7 @@ function sql(strings, ...params) {
|
|
|
14660
14596
|
}
|
|
14661
14597
|
return new SQL(result);
|
|
14662
14598
|
}
|
|
14663
|
-
sql2.join =
|
|
14599
|
+
sql2.join = join2;
|
|
14664
14600
|
function identifier(value) {
|
|
14665
14601
|
return new Name(value);
|
|
14666
14602
|
}
|
|
@@ -15556,6 +15492,7 @@ var conversation = sqliteTable("conversation", {
|
|
|
15556
15492
|
agentId: text("agent_id").notNull(),
|
|
15557
15493
|
userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
15558
15494
|
title: text("title").notNull().default(""),
|
|
15495
|
+
type: text("type").notNull().default(TASK_TYPES.USER_DM_MESSAGE),
|
|
15559
15496
|
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
15560
15497
|
}, (t) => [
|
|
15561
15498
|
foreignKey({
|
|
@@ -15578,7 +15515,7 @@ var agentTaskQueue = sqliteTable("agent_task_queue", {
|
|
|
15578
15515
|
workspaceId: text("workspace_id").notNull().references(() => workspace.id),
|
|
15579
15516
|
conversationId: text("conversation_id").notNull().references(() => conversation.id),
|
|
15580
15517
|
prompt: text("prompt").notNull(),
|
|
15581
|
-
type: text("type").notNull().default(
|
|
15518
|
+
type: text("type").notNull().default(TASK_TYPES.USER_DM_MESSAGE),
|
|
15582
15519
|
status: text("status").notNull().default("queued"),
|
|
15583
15520
|
priority: integer2("priority").notNull().default(0),
|
|
15584
15521
|
result: text("result", { mode: "json" }),
|
|
@@ -15628,6 +15565,27 @@ var emails = sqliteTable("emails", {
|
|
|
15628
15565
|
foreignColumns: [agent.id, agent.workspaceId]
|
|
15629
15566
|
}).onDelete("cascade")
|
|
15630
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
|
+
]);
|
|
15631
15589
|
var machineToken = sqliteTable("machine_token", {
|
|
15632
15590
|
id: text("id").primaryKey().$defaultFn(() => nanoid3()),
|
|
15633
15591
|
userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
@@ -15713,19 +15671,19 @@ class DaemonClient {
|
|
|
15713
15671
|
|
|
15714
15672
|
// daemon/config.ts
|
|
15715
15673
|
import { hostname as hostname4 } from "os";
|
|
15716
|
-
import { join as
|
|
15674
|
+
import { join as join2 } from "path";
|
|
15717
15675
|
function pidFilePath(profile) {
|
|
15718
15676
|
const name = profile ? `daemon_${profile}.pid` : "daemon.pid";
|
|
15719
|
-
return
|
|
15677
|
+
return join2(configDir(), name);
|
|
15720
15678
|
}
|
|
15721
15679
|
function daemonLogDir() {
|
|
15722
|
-
return
|
|
15680
|
+
return join2(configDir(), "daemon", "logs");
|
|
15723
15681
|
}
|
|
15724
15682
|
function daemonLogFilePath(date5 = new Date) {
|
|
15725
15683
|
const y = date5.getFullYear();
|
|
15726
15684
|
const m = String(date5.getMonth() + 1).padStart(2, "0");
|
|
15727
15685
|
const d = String(date5.getDate()).padStart(2, "0");
|
|
15728
|
-
return
|
|
15686
|
+
return join2(daemonLogDir(), `${y}-${m}-${d}.log`);
|
|
15729
15687
|
}
|
|
15730
15688
|
function parseDuration(s) {
|
|
15731
15689
|
if (!s)
|
|
@@ -15765,7 +15723,7 @@ function loadDaemonConfig(profile) {
|
|
|
15765
15723
|
if (profile && !daemonId.endsWith(`-${profile}`)) {
|
|
15766
15724
|
daemonId = `${daemonId}-${profile}`;
|
|
15767
15725
|
}
|
|
15768
|
-
const defaultRoot =
|
|
15726
|
+
const defaultRoot = join2(configDir(), profile ? `workspaces_${profile}` : "workspaces");
|
|
15769
15727
|
const workspacesRoot = process.env.ALOOK_WORKSPACES_ROOT || defaultRoot;
|
|
15770
15728
|
return {
|
|
15771
15729
|
serverURL: normalizeServerBaseURL(process.env.ALOOK_SERVER_URL || "https://alook.ai"),
|
|
@@ -15947,8 +15905,8 @@ function createLogger2(level) {
|
|
|
15947
15905
|
var log = createLogger2();
|
|
15948
15906
|
|
|
15949
15907
|
// daemon/pidfile.ts
|
|
15950
|
-
import { readFileSync as
|
|
15951
|
-
import { dirname
|
|
15908
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
|
|
15909
|
+
import { dirname } from "path";
|
|
15952
15910
|
function isProcessAlive(pid) {
|
|
15953
15911
|
try {
|
|
15954
15912
|
process.kill(pid, 0);
|
|
@@ -15959,7 +15917,7 @@ function isProcessAlive(pid) {
|
|
|
15959
15917
|
}
|
|
15960
15918
|
function readDaemonPid(profile) {
|
|
15961
15919
|
try {
|
|
15962
|
-
const content =
|
|
15920
|
+
const content = readFileSync2(pidFilePath(profile), "utf-8").trim();
|
|
15963
15921
|
const pid = parseInt(content, 10);
|
|
15964
15922
|
return Number.isNaN(pid) ? null : pid;
|
|
15965
15923
|
} catch {
|
|
@@ -15969,14 +15927,14 @@ function readDaemonPid(profile) {
|
|
|
15969
15927
|
function acquireDaemonPid(profile) {
|
|
15970
15928
|
const pidPath = pidFilePath(profile);
|
|
15971
15929
|
try {
|
|
15972
|
-
const content =
|
|
15930
|
+
const content = readFileSync2(pidPath, "utf-8").trim();
|
|
15973
15931
|
const existingPid = parseInt(content, 10);
|
|
15974
15932
|
if (!isNaN(existingPid) && isProcessAlive(existingPid)) {
|
|
15975
15933
|
log.error(`Another daemon is already running (PID ${existingPid}). ` + `Remove ${pidPath} if this is stale.`);
|
|
15976
15934
|
return false;
|
|
15977
15935
|
}
|
|
15978
15936
|
} catch {}
|
|
15979
|
-
mkdirSync2(
|
|
15937
|
+
mkdirSync2(dirname(pidPath), { recursive: true, mode: 448 });
|
|
15980
15938
|
writeFileSync2(pidPath, String(process.pid), { mode: 384 });
|
|
15981
15939
|
return true;
|
|
15982
15940
|
}
|
|
@@ -15995,8 +15953,8 @@ function releaseDaemonPid(profile) {
|
|
|
15995
15953
|
|
|
15996
15954
|
// daemon/daemon.ts
|
|
15997
15955
|
import { execSync as execSync3, spawn } from "child_process";
|
|
15998
|
-
import { fileURLToPath
|
|
15999
|
-
import { dirname as
|
|
15956
|
+
import { fileURLToPath } from "url";
|
|
15957
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
16000
15958
|
function isCommandAvailable2(cmd) {
|
|
16001
15959
|
try {
|
|
16002
15960
|
execSync3(`which ${cmd}`, { stdio: "ignore" });
|
|
@@ -16168,7 +16126,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
16168
16126
|
process.on("SIGINT", shutdown);
|
|
16169
16127
|
await pollCycle();
|
|
16170
16128
|
}
|
|
16171
|
-
var SESSION_RUNNER_PATH =
|
|
16129
|
+
var SESSION_RUNNER_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "session-runner.ts");
|
|
16172
16130
|
function spawnSessionRunner(input) {
|
|
16173
16131
|
const encoded = Buffer.from(JSON.stringify(input)).toString("base64");
|
|
16174
16132
|
const child = spawn("bun", ["run", SESSION_RUNNER_PATH, encoded], {
|
|
@@ -16246,7 +16204,7 @@ async function startInBackground(profile, serverUrl) {
|
|
|
16246
16204
|
return;
|
|
16247
16205
|
}
|
|
16248
16206
|
const logPath = daemonLogFilePath();
|
|
16249
|
-
mkdirSync3(
|
|
16207
|
+
mkdirSync3(dirname3(logPath), { recursive: true, mode: 448 });
|
|
16250
16208
|
const logFd = openSync(logPath, "a", 384);
|
|
16251
16209
|
const child = spawn2(process.execPath, buildChildArgs(profile, serverUrl), {
|
|
16252
16210
|
detached: true,
|
|
@@ -16356,11 +16314,37 @@ function configCommand() {
|
|
|
16356
16314
|
|
|
16357
16315
|
// commands/email.ts
|
|
16358
16316
|
import { Command as Command5 } from "commander";
|
|
16359
|
-
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
|
|
16360
|
-
import { join as
|
|
16317
|
+
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync3, statSync } from "fs";
|
|
16318
|
+
import { basename, join as join4 } from "path";
|
|
16361
16319
|
import PostalMime from "postal-mime";
|
|
16362
16320
|
var VALID_STATUSES = ["unread", "read", "archived"];
|
|
16363
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
|
+
}
|
|
16364
16348
|
function resolveClientOpts(command, opts) {
|
|
16365
16349
|
const parentOpts = command.parent?.parent?.opts() || {};
|
|
16366
16350
|
const profile = parentOpts.profile;
|
|
@@ -16407,7 +16391,7 @@ function emailCommand() {
|
|
|
16407
16391
|
mkdirSync4(EMAIL_DIR, { recursive: true });
|
|
16408
16392
|
const downloadedPaths = [];
|
|
16409
16393
|
for (const email3 of emails2) {
|
|
16410
|
-
const emailDir =
|
|
16394
|
+
const emailDir = join4(EMAIL_DIR, email3.id);
|
|
16411
16395
|
mkdirSync4(emailDir, { recursive: true });
|
|
16412
16396
|
const metadata = {
|
|
16413
16397
|
id: email3.id,
|
|
@@ -16417,7 +16401,7 @@ function emailCommand() {
|
|
|
16417
16401
|
date: email3.created_at,
|
|
16418
16402
|
status: email3.status
|
|
16419
16403
|
};
|
|
16420
|
-
const metadataPath =
|
|
16404
|
+
const metadataPath = join4(emailDir, "metadata.json");
|
|
16421
16405
|
writeFileSync3(metadataPath, JSON.stringify(metadata, null, 2));
|
|
16422
16406
|
downloadedPaths.push(metadataPath);
|
|
16423
16407
|
let rawMime;
|
|
@@ -16433,17 +16417,17 @@ function emailCommand() {
|
|
|
16433
16417
|
}
|
|
16434
16418
|
const parsed = await new PostalMime().parse(rawMime);
|
|
16435
16419
|
if (parsed.text) {
|
|
16436
|
-
const bodyPath =
|
|
16420
|
+
const bodyPath = join4(emailDir, "body.txt");
|
|
16437
16421
|
writeFileSync3(bodyPath, parsed.text);
|
|
16438
16422
|
downloadedPaths.push(bodyPath);
|
|
16439
16423
|
}
|
|
16440
16424
|
if (parsed.html) {
|
|
16441
|
-
const htmlPath =
|
|
16425
|
+
const htmlPath = join4(emailDir, "body.html");
|
|
16442
16426
|
writeFileSync3(htmlPath, parsed.html);
|
|
16443
16427
|
downloadedPaths.push(htmlPath);
|
|
16444
16428
|
}
|
|
16445
16429
|
if (parsed.attachments && parsed.attachments.length > 0) {
|
|
16446
|
-
const attDir =
|
|
16430
|
+
const attDir = join4(emailDir, "attachments");
|
|
16447
16431
|
mkdirSync4(attDir, { recursive: true });
|
|
16448
16432
|
const usedFilenames = new Set;
|
|
16449
16433
|
for (let i = 0;i < parsed.attachments.length; i++) {
|
|
@@ -16453,7 +16437,7 @@ function emailCommand() {
|
|
|
16453
16437
|
filename = `${i}-${filename}`;
|
|
16454
16438
|
}
|
|
16455
16439
|
usedFilenames.add(filename);
|
|
16456
|
-
const attPath =
|
|
16440
|
+
const attPath = join4(attDir, filename);
|
|
16457
16441
|
const content = att.content;
|
|
16458
16442
|
let buf;
|
|
16459
16443
|
if (typeof content === "string") {
|
|
@@ -16494,25 +16478,312 @@ function emailCommand() {
|
|
|
16494
16478
|
process.exit(1);
|
|
16495
16479
|
}
|
|
16496
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
|
+
});
|
|
16497
16536
|
return cmd;
|
|
16498
16537
|
}
|
|
16499
16538
|
|
|
16500
|
-
// commands/
|
|
16539
|
+
// commands/calendar.ts
|
|
16501
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";
|
|
16750
|
+
|
|
16751
|
+
// lib/version.ts
|
|
16752
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
16753
|
+
import { join as join5, dirname as dirname4 } from "path";
|
|
16754
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
16755
|
+
function getCurrentVersion() {
|
|
16756
|
+
const __dirname2 = dirname4(fileURLToPath2(import.meta.url));
|
|
16757
|
+
const candidates = [
|
|
16758
|
+
join5(__dirname2, "..", "package.json"),
|
|
16759
|
+
join5(__dirname2, "..", "..", "package.json")
|
|
16760
|
+
];
|
|
16761
|
+
for (const candidate of candidates) {
|
|
16762
|
+
try {
|
|
16763
|
+
const pkg = JSON.parse(readFileSync4(candidate, "utf-8"));
|
|
16764
|
+
if (typeof pkg.version === "string")
|
|
16765
|
+
return pkg.version;
|
|
16766
|
+
} catch {}
|
|
16767
|
+
}
|
|
16768
|
+
return "unknown";
|
|
16769
|
+
}
|
|
16770
|
+
|
|
16771
|
+
// commands/version.ts
|
|
16502
16772
|
function versionCommand() {
|
|
16503
|
-
const cmd = new
|
|
16773
|
+
const cmd = new Command7("version").description("Show CLI version").action(() => {
|
|
16504
16774
|
console.log(`alook version ${getCurrentVersion()}`);
|
|
16505
16775
|
});
|
|
16506
16776
|
return cmd;
|
|
16507
16777
|
}
|
|
16508
16778
|
|
|
16509
16779
|
// src/index.ts
|
|
16510
|
-
var program = new
|
|
16780
|
+
var program = new Command8;
|
|
16511
16781
|
program.name("alook").description("Alook CLI").option("--server <url>", "Server URL").option("--profile <name>", "Profile name");
|
|
16512
16782
|
program.addCommand(registerCommand());
|
|
16513
16783
|
program.addCommand(statusCommand());
|
|
16514
16784
|
program.addCommand(daemonCommand());
|
|
16515
16785
|
program.addCommand(emailCommand());
|
|
16786
|
+
program.addCommand(calendarCommand());
|
|
16516
16787
|
program.addCommand(configCommand());
|
|
16517
16788
|
program.addCommand(versionCommand());
|
|
16518
16789
|
program.parse();
|