@alook/cli 0.0.51 → 0.0.53

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
@@ -20,7 +20,7 @@ import { Command as Command11 } from "commander";
20
20
  // commands/register.ts
21
21
  import { Command } from "commander";
22
22
  import { execSync } from "child_process";
23
- import { hostname } from "os";
23
+ import { hostname as hostname2 } from "os";
24
24
 
25
25
  // lib/client.ts
26
26
  class APIClient {
@@ -164,6 +164,256 @@ function saveCLIConfigForProfile(profile, profileConfig) {
164
164
  saveCLIConfig(cfg);
165
165
  }
166
166
 
167
+ // daemon/pidfile.ts
168
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
169
+ import { dirname as dirname2 } from "path";
170
+
171
+ // daemon/config.ts
172
+ import { hostname } from "os";
173
+ import { join as join3 } from "path";
174
+
175
+ // lib/version.ts
176
+ import { readFileSync as readFileSync2 } from "fs";
177
+ import { join as join2, dirname } from "path";
178
+ import { fileURLToPath } from "url";
179
+ function getCurrentVersion() {
180
+ const __dirname2 = dirname(fileURLToPath(import.meta.url));
181
+ const candidates = [
182
+ join2(__dirname2, "..", "package.json"),
183
+ join2(__dirname2, "..", "..", "package.json")
184
+ ];
185
+ for (const candidate of candidates) {
186
+ try {
187
+ const pkg = JSON.parse(readFileSync2(candidate, "utf-8"));
188
+ if (typeof pkg.version === "string")
189
+ return pkg.version;
190
+ } catch {}
191
+ }
192
+ return "unknown";
193
+ }
194
+
195
+ // daemon/config.ts
196
+ function pidFilePath(profile) {
197
+ const name = profile ? `daemon_${profile}.pid` : "daemon.pid";
198
+ return join3(configDir(), name);
199
+ }
200
+ function lastUpdateMarkerPath(profile) {
201
+ const name = profile ? `last_update_${profile}` : "last_update";
202
+ return join3(configDir(), name);
203
+ }
204
+ function daemonLogDir() {
205
+ return join3(configDir(), "daemon", "logs");
206
+ }
207
+ function sessionRunnerLogDir() {
208
+ return join3(configDir(), "daemon", "session-runners");
209
+ }
210
+ function daemonLogFilePath(date = new Date) {
211
+ const y = date.getFullYear();
212
+ const m = String(date.getMonth() + 1).padStart(2, "0");
213
+ const d = String(date.getDate()).padStart(2, "0");
214
+ return join3(daemonLogDir(), `${y}-${m}-${d}.log`);
215
+ }
216
+ function parseDuration(s) {
217
+ if (!s)
218
+ return 0;
219
+ let total = 0;
220
+ const regex = /(\d+(?:\.\d+)?)(ns|us|µs|ms|s|m|h)/g;
221
+ let match;
222
+ while ((match = regex.exec(s)) !== null) {
223
+ const val = parseFloat(match[1]);
224
+ switch (match[2]) {
225
+ case "ns":
226
+ total += val / 1e6;
227
+ break;
228
+ case "us":
229
+ case "µs":
230
+ total += val / 1000;
231
+ break;
232
+ case "ms":
233
+ total += val;
234
+ break;
235
+ case "s":
236
+ total += val * 1000;
237
+ break;
238
+ case "m":
239
+ total += val * 60000;
240
+ break;
241
+ case "h":
242
+ total += val * 3600000;
243
+ break;
244
+ }
245
+ }
246
+ return total;
247
+ }
248
+ function loadDaemonConfig(profile) {
249
+ const h = hostname();
250
+ let daemonId = process.env.ALOOK_DAEMON_ID || h;
251
+ if (profile && !daemonId.endsWith(`-${profile}`)) {
252
+ daemonId = `${daemonId}-${profile}`;
253
+ }
254
+ const defaultRoot = join3(configDir(), profile ? `workspaces_${profile}` : "workspaces");
255
+ const workspacesRoot = process.env.ALOOK_WORKSPACES_ROOT || defaultRoot;
256
+ return {
257
+ serverURL: normalizeServerBaseURL(process.env.ALOOK_SERVER_URL || "https://alook.ai"),
258
+ claudePath: process.env.ALOOK_CLAUDE_PATH || "claude",
259
+ codexPath: process.env.ALOOK_CODEX_PATH || "codex",
260
+ opencodePath: process.env.ALOOK_OPENCODE_PATH || "opencode",
261
+ claudeModel: process.env.ALOOK_CLAUDE_MODEL || "",
262
+ codexModel: process.env.ALOOK_CODEX_MODEL || "",
263
+ opencodeModel: process.env.ALOOK_OPENCODE_MODEL || "",
264
+ pollInterval: parseDuration(process.env.ALOOK_DAEMON_POLL_INTERVAL || "3s"),
265
+ agentTimeout: parseDuration(process.env.ALOOK_AGENT_TIMEOUT || "12h"),
266
+ messageInactivityTimeout: parseDuration(process.env.ALOOK_MESSAGE_INACTIVITY_TIMEOUT || "20m"),
267
+ maxConcurrentTasks: parseInt(process.env.ALOOK_DAEMON_MAX_CONCURRENT_TASKS || "20"),
268
+ daemonId,
269
+ deviceName: process.env.ALOOK_DAEMON_DEVICE_NAME || h,
270
+ workspacesRoot,
271
+ cliVersion: getCurrentVersion()
272
+ };
273
+ }
274
+ function normalizeServerBaseURL(url) {
275
+ return url.replace(/^ws:\/\//, "http://").replace(/^wss:\/\//, "https://").replace(/\/ws$/, "");
276
+ }
277
+
278
+ // lib/logger.ts
279
+ var LEVELS = {
280
+ debug: 0,
281
+ info: 1,
282
+ warn: 2,
283
+ error: 3,
284
+ silent: 4
285
+ };
286
+ var LABELS = {
287
+ debug: "DEBUG",
288
+ info: "INFO ",
289
+ warn: "WARN ",
290
+ error: "ERROR"
291
+ };
292
+ var COLORS = {
293
+ debug: "\x1B[90m",
294
+ info: "\x1B[36m",
295
+ warn: "\x1B[33m",
296
+ error: "\x1B[31m"
297
+ };
298
+ var RESET = "\x1B[0m";
299
+ var DIM = "\x1B[2m";
300
+ function useColor() {
301
+ if (process.env.NO_COLOR !== undefined)
302
+ return false;
303
+ if (process.env.FORCE_COLOR !== undefined)
304
+ return true;
305
+ return process.stdout.isTTY === true;
306
+ }
307
+ function timestamp() {
308
+ const d = new Date;
309
+ const Y = d.getFullYear();
310
+ const M = String(d.getMonth() + 1).padStart(2, "0");
311
+ const D = String(d.getDate()).padStart(2, "0");
312
+ const h = String(d.getHours()).padStart(2, "0");
313
+ const m = String(d.getMinutes()).padStart(2, "0");
314
+ const s = String(d.getSeconds()).padStart(2, "0");
315
+ return `${Y}-${M}-${D} ${h}:${m}:${s}`;
316
+ }
317
+
318
+ class Logger {
319
+ level;
320
+ color;
321
+ constructor(level = "info") {
322
+ this.level = LEVELS[level];
323
+ this.color = useColor();
324
+ }
325
+ setLevel(level) {
326
+ this.level = LEVELS[level];
327
+ }
328
+ debug(msg, ...args) {
329
+ this.write("debug", msg, args);
330
+ }
331
+ info(msg, ...args) {
332
+ this.write("info", msg, args);
333
+ }
334
+ warn(msg, ...args) {
335
+ this.write("warn", msg, args);
336
+ }
337
+ error(msg, ...args) {
338
+ this.write("error", msg, args);
339
+ }
340
+ write(level, msg, args) {
341
+ if (LEVELS[level] < this.level)
342
+ return;
343
+ const ts = timestamp();
344
+ const label = LABELS[level];
345
+ let line;
346
+ if (this.color) {
347
+ const c = COLORS[level];
348
+ line = `${DIM}${ts}${RESET} ${c}${label}${RESET} ${msg}`;
349
+ } else {
350
+ line = `${ts} ${label} ${msg}`;
351
+ }
352
+ const dest = level === "error" ? process.stderr : process.stdout;
353
+ dest.write(line + `
354
+ `);
355
+ for (const a of args) {
356
+ if (a instanceof Error) {
357
+ dest.write(` ${a.message}
358
+ `);
359
+ } else if (a !== undefined) {
360
+ dest.write(` ${String(a)}
361
+ `);
362
+ }
363
+ }
364
+ }
365
+ }
366
+ function createLogger(level) {
367
+ const envLevel = process.env.ALOOK_LOG_LEVEL;
368
+ return new Logger(level ?? envLevel ?? "info");
369
+ }
370
+ var log = createLogger();
371
+
372
+ // daemon/pidfile.ts
373
+ function isProcessAlive(pid) {
374
+ try {
375
+ process.kill(pid, 0);
376
+ return true;
377
+ } catch {
378
+ return false;
379
+ }
380
+ }
381
+ function readDaemonPid(profile) {
382
+ try {
383
+ const content = readFileSync3(pidFilePath(profile), "utf-8").trim();
384
+ const pid = parseInt(content, 10);
385
+ return Number.isNaN(pid) ? null : pid;
386
+ } catch {
387
+ return null;
388
+ }
389
+ }
390
+ function acquireDaemonPid(profile) {
391
+ const pidPath = pidFilePath(profile);
392
+ try {
393
+ const content = readFileSync3(pidPath, "utf-8").trim();
394
+ const existingPid = parseInt(content, 10);
395
+ if (!isNaN(existingPid) && isProcessAlive(existingPid)) {
396
+ log.error(`Another daemon is already running (PID ${existingPid}). ` + `Remove ${pidPath} if this is stale.`);
397
+ return false;
398
+ }
399
+ } catch {}
400
+ mkdirSync2(dirname2(pidPath), { recursive: true, mode: 448 });
401
+ writeFileSync2(pidPath, String(process.pid), { mode: 384 });
402
+ return true;
403
+ }
404
+ function removePidFileIfMatches(pid, profile) {
405
+ const pidPath = pidFilePath(profile);
406
+ const onDisk = readDaemonPid(profile);
407
+ if (onDisk !== pid)
408
+ return;
409
+ try {
410
+ unlinkSync(pidPath);
411
+ } catch {}
412
+ }
413
+ function releaseDaemonPid(profile) {
414
+ removePidFileIfMatches(process.pid, profile);
415
+ }
416
+
167
417
  // commands/register.ts
168
418
  function isCommandAvailable(cmd) {
169
419
  try {
@@ -227,7 +477,7 @@ Usage: ${cmdPrefix()} register --token <token>`);
227
477
  process.exit(1);
228
478
  }
229
479
  console.log(`Found: ${runtimes.map((r) => r.type).join(", ")}`);
230
- const host = hostname();
480
+ const host = hostname2();
231
481
  console.log("Registering runtime...");
232
482
  let activateResp;
233
483
  try {
@@ -269,8 +519,20 @@ Usage: ${cmdPrefix()} register --token <token>`);
269
519
  Registered as ${me.email}`);
270
520
  console.log(`Workspace: ${ws.name} (${ws.id})`);
271
521
  console.log(`Runtimes: ${activateResp.runtimes.map((r) => r.provider).join(", ")}`);
272
- console.log();
273
- console.log(`Run '${cmdPrefix()} daemon start --foreground' to start the daemon.`);
522
+ const daemonPid = readDaemonPid(profile);
523
+ if (daemonPid && isProcessAlive(daemonPid)) {
524
+ try {
525
+ process.kill(daemonPid, "SIGHUP");
526
+ console.log(`
527
+ Daemon (pid ${daemonPid}) notified — workspace will be active shortly.`);
528
+ } catch {
529
+ console.log(`
530
+ Daemon is running but could not be notified. Restart it to pick up the new workspace.`);
531
+ }
532
+ } else {
533
+ console.log();
534
+ console.log(`Run '${cmdPrefix()} daemon start --foreground' to start the daemon.`);
535
+ }
274
536
  });
275
537
  return cmd;
276
538
  }
@@ -471,7 +733,7 @@ __export(exports_external, {
471
733
  instanceof: () => _instanceof,
472
734
  includes: () => _includes,
473
735
  httpUrl: () => httpUrl,
474
- hostname: () => hostname3,
736
+ hostname: () => hostname4,
475
737
  hex: () => hex2,
476
738
  hash: () => hash,
477
739
  guid: () => guid2,
@@ -1922,7 +2184,7 @@ __export(exports_regexes, {
1922
2184
  idnEmail: () => idnEmail,
1923
2185
  httpProtocol: () => httpProtocol,
1924
2186
  html5Email: () => html5Email,
1925
- hostname: () => hostname2,
2187
+ hostname: () => hostname3,
1926
2188
  hex: () => hex,
1927
2189
  guid: () => guid,
1928
2190
  extendedDuration: () => extendedDuration,
@@ -1980,7 +2242,7 @@ var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]
1980
2242
  var cidrv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/;
1981
2243
  var base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;
1982
2244
  var base64url = /^[A-Za-z0-9_-]*$/;
1983
- var hostname2 = /^(?=.{1,253}\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\.?$/;
2245
+ var hostname3 = /^(?=.{1,253}\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\.?$/;
1984
2246
  var domain = /^([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
1985
2247
  var httpProtocol = /^https?$/;
1986
2248
  var e164 = /^\+[1-9]\d{6,14}$/;
@@ -12604,7 +12866,7 @@ __export(exports_schemas2, {
12604
12866
  int: () => int,
12605
12867
  instanceof: () => _instanceof,
12606
12868
  httpUrl: () => httpUrl,
12607
- hostname: () => hostname3,
12869
+ hostname: () => hostname4,
12608
12870
  hex: () => hex2,
12609
12871
  hash: () => hash,
12610
12872
  guid: () => guid2,
@@ -13256,7 +13518,7 @@ var ZodCustomStringFormat = /* @__PURE__ */ $constructor("ZodCustomStringFormat"
13256
13518
  function stringFormat(format, fnOrRegex, _params = {}) {
13257
13519
  return _stringFormat(ZodCustomStringFormat, format, fnOrRegex, _params);
13258
13520
  }
13259
- function hostname3(_params) {
13521
+ function hostname4(_params) {
13260
13522
  return _stringFormat(ZodCustomStringFormat, "hostname", exports_regexes.hostname, _params);
13261
13523
  }
13262
13524
  function hex2(_params) {
@@ -14853,7 +15115,7 @@ var IssueStatusSchema = exports_external.enum([
14853
15115
  IssueStatus.FAILED
14854
15116
  ]);
14855
15117
  var CreateIssueRequestSchema = exports_external.object({
14856
- agent_id: exports_external.string().min(1, "agent_id is required"),
15118
+ agent_id: exports_external.string().min(1).optional(),
14857
15119
  title: exports_external.string().min(1, "title is required").max(200),
14858
15120
  description: exports_external.string().max(20000).optional().default("")
14859
15121
  });
@@ -14877,9 +15139,9 @@ var IssueCommentApiSchema = exports_external.object({
14877
15139
  var IssueApiSchema = exports_external.object({
14878
15140
  id: exports_external.string(),
14879
15141
  workspace_id: exports_external.string(),
14880
- agent_id: exports_external.string(),
15142
+ agent_id: exports_external.string().nullable(),
14881
15143
  creator_user_id: exports_external.string(),
14882
- conversation_id: exports_external.string(),
15144
+ conversation_id: exports_external.string().nullable(),
14883
15145
  latest_task_id: exports_external.string().nullable(),
14884
15146
  title: exports_external.string(),
14885
15147
  description: exports_external.string(),
@@ -15655,7 +15917,7 @@ function sql(strings, ...params) {
15655
15917
  return new SQL([new StringChunk(str)]);
15656
15918
  }
15657
15919
  sql2.raw = raw;
15658
- function join2(chunks, separator) {
15920
+ function join4(chunks, separator) {
15659
15921
  const result = [];
15660
15922
  for (const [i, chunk] of chunks.entries()) {
15661
15923
  if (i > 0 && separator !== undefined) {
@@ -15665,7 +15927,7 @@ function sql(strings, ...params) {
15665
15927
  }
15666
15928
  return new SQL(result);
15667
15929
  }
15668
- sql2.join = join2;
15930
+ sql2.join = join4;
15669
15931
  function identifier(value) {
15670
15932
  return new Name(value);
15671
15933
  }
@@ -16689,9 +16951,9 @@ var agentTaskQueue = sqliteTable("agent_task_queue", {
16689
16951
  var issue2 = sqliteTable("issue", {
16690
16952
  id: text("id").primaryKey().$defaultFn(() => "iss_" + nanoid3()),
16691
16953
  workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
16692
- agentId: text("agent_id").notNull(),
16954
+ agentId: text("agent_id"),
16693
16955
  creatorUserId: text("creator_user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
16694
- conversationId: text("conversation_id").notNull().references(() => conversation.id, { onDelete: "cascade" }),
16956
+ conversationId: text("conversation_id").references(() => conversation.id, { onDelete: "cascade" }),
16695
16957
  latestTaskId: text("latest_task_id").references(() => agentTaskQueue.id, {
16696
16958
  onDelete: "set null"
16697
16959
  }),
@@ -17073,113 +17335,6 @@ class DaemonClient {
17073
17335
  }
17074
17336
  }
17075
17337
 
17076
- // daemon/config.ts
17077
- import { hostname as hostname4 } from "os";
17078
- import { join as join3 } from "path";
17079
-
17080
- // lib/version.ts
17081
- import { readFileSync as readFileSync2 } from "fs";
17082
- import { join as join2, dirname } from "path";
17083
- import { fileURLToPath } from "url";
17084
- function getCurrentVersion() {
17085
- const __dirname2 = dirname(fileURLToPath(import.meta.url));
17086
- const candidates = [
17087
- join2(__dirname2, "..", "package.json"),
17088
- join2(__dirname2, "..", "..", "package.json")
17089
- ];
17090
- for (const candidate of candidates) {
17091
- try {
17092
- const pkg = JSON.parse(readFileSync2(candidate, "utf-8"));
17093
- if (typeof pkg.version === "string")
17094
- return pkg.version;
17095
- } catch {}
17096
- }
17097
- return "unknown";
17098
- }
17099
-
17100
- // daemon/config.ts
17101
- function pidFilePath(profile) {
17102
- const name = profile ? `daemon_${profile}.pid` : "daemon.pid";
17103
- return join3(configDir(), name);
17104
- }
17105
- function lastUpdateMarkerPath(profile) {
17106
- const name = profile ? `last_update_${profile}` : "last_update";
17107
- return join3(configDir(), name);
17108
- }
17109
- function daemonLogDir() {
17110
- return join3(configDir(), "daemon", "logs");
17111
- }
17112
- function sessionRunnerLogDir() {
17113
- return join3(configDir(), "daemon", "session-runners");
17114
- }
17115
- function daemonLogFilePath(date5 = new Date) {
17116
- const y = date5.getFullYear();
17117
- const m = String(date5.getMonth() + 1).padStart(2, "0");
17118
- const d = String(date5.getDate()).padStart(2, "0");
17119
- return join3(daemonLogDir(), `${y}-${m}-${d}.log`);
17120
- }
17121
- function parseDuration(s) {
17122
- if (!s)
17123
- return 0;
17124
- let total = 0;
17125
- const regex = /(\d+(?:\.\d+)?)(ns|us|µs|ms|s|m|h)/g;
17126
- let match;
17127
- while ((match = regex.exec(s)) !== null) {
17128
- const val = parseFloat(match[1]);
17129
- switch (match[2]) {
17130
- case "ns":
17131
- total += val / 1e6;
17132
- break;
17133
- case "us":
17134
- case "µs":
17135
- total += val / 1000;
17136
- break;
17137
- case "ms":
17138
- total += val;
17139
- break;
17140
- case "s":
17141
- total += val * 1000;
17142
- break;
17143
- case "m":
17144
- total += val * 60000;
17145
- break;
17146
- case "h":
17147
- total += val * 3600000;
17148
- break;
17149
- }
17150
- }
17151
- return total;
17152
- }
17153
- function loadDaemonConfig(profile) {
17154
- const h = hostname4();
17155
- let daemonId = process.env.ALOOK_DAEMON_ID || h;
17156
- if (profile && !daemonId.endsWith(`-${profile}`)) {
17157
- daemonId = `${daemonId}-${profile}`;
17158
- }
17159
- const defaultRoot = join3(configDir(), profile ? `workspaces_${profile}` : "workspaces");
17160
- const workspacesRoot = process.env.ALOOK_WORKSPACES_ROOT || defaultRoot;
17161
- return {
17162
- serverURL: normalizeServerBaseURL(process.env.ALOOK_SERVER_URL || "https://alook.ai"),
17163
- claudePath: process.env.ALOOK_CLAUDE_PATH || "claude",
17164
- codexPath: process.env.ALOOK_CODEX_PATH || "codex",
17165
- opencodePath: process.env.ALOOK_OPENCODE_PATH || "opencode",
17166
- claudeModel: process.env.ALOOK_CLAUDE_MODEL || "",
17167
- codexModel: process.env.ALOOK_CODEX_MODEL || "",
17168
- opencodeModel: process.env.ALOOK_OPENCODE_MODEL || "",
17169
- pollInterval: parseDuration(process.env.ALOOK_DAEMON_POLL_INTERVAL || "3s"),
17170
- agentTimeout: parseDuration(process.env.ALOOK_AGENT_TIMEOUT || "12h"),
17171
- messageInactivityTimeout: parseDuration(process.env.ALOOK_MESSAGE_INACTIVITY_TIMEOUT || "20m"),
17172
- maxConcurrentTasks: parseInt(process.env.ALOOK_DAEMON_MAX_CONCURRENT_TASKS || "20"),
17173
- daemonId,
17174
- deviceName: process.env.ALOOK_DAEMON_DEVICE_NAME || h,
17175
- workspacesRoot,
17176
- cliVersion: getCurrentVersion()
17177
- };
17178
- }
17179
- function normalizeServerBaseURL(url2) {
17180
- return url2.replace(/^ws:\/\//, "http://").replace(/^wss:\/\//, "https://").replace(/\/ws$/, "");
17181
- }
17182
-
17183
17338
  // daemon/health.ts
17184
17339
  import { createServer } from "http";
17185
17340
  var DEFAULT_HEALTH_PORT = Number(process.env.ALOOK_HEALTH_PORT) || 19514;
@@ -17267,147 +17422,6 @@ function fromApiTask(api2) {
17267
17422
  };
17268
17423
  }
17269
17424
 
17270
- // lib/logger.ts
17271
- var LEVELS = {
17272
- debug: 0,
17273
- info: 1,
17274
- warn: 2,
17275
- error: 3,
17276
- silent: 4
17277
- };
17278
- var LABELS = {
17279
- debug: "DEBUG",
17280
- info: "INFO ",
17281
- warn: "WARN ",
17282
- error: "ERROR"
17283
- };
17284
- var COLORS = {
17285
- debug: "\x1B[90m",
17286
- info: "\x1B[36m",
17287
- warn: "\x1B[33m",
17288
- error: "\x1B[31m"
17289
- };
17290
- var RESET = "\x1B[0m";
17291
- var DIM = "\x1B[2m";
17292
- function useColor() {
17293
- if (process.env.NO_COLOR !== undefined)
17294
- return false;
17295
- if (process.env.FORCE_COLOR !== undefined)
17296
- return true;
17297
- return process.stdout.isTTY === true;
17298
- }
17299
- function timestamp() {
17300
- const d = new Date;
17301
- const Y = d.getFullYear();
17302
- const M = String(d.getMonth() + 1).padStart(2, "0");
17303
- const D = String(d.getDate()).padStart(2, "0");
17304
- const h = String(d.getHours()).padStart(2, "0");
17305
- const m = String(d.getMinutes()).padStart(2, "0");
17306
- const s = String(d.getSeconds()).padStart(2, "0");
17307
- return `${Y}-${M}-${D} ${h}:${m}:${s}`;
17308
- }
17309
-
17310
- class Logger2 {
17311
- level;
17312
- color;
17313
- constructor(level = "info") {
17314
- this.level = LEVELS[level];
17315
- this.color = useColor();
17316
- }
17317
- setLevel(level) {
17318
- this.level = LEVELS[level];
17319
- }
17320
- debug(msg, ...args) {
17321
- this.write("debug", msg, args);
17322
- }
17323
- info(msg, ...args) {
17324
- this.write("info", msg, args);
17325
- }
17326
- warn(msg, ...args) {
17327
- this.write("warn", msg, args);
17328
- }
17329
- error(msg, ...args) {
17330
- this.write("error", msg, args);
17331
- }
17332
- write(level, msg, args) {
17333
- if (LEVELS[level] < this.level)
17334
- return;
17335
- const ts = timestamp();
17336
- const label = LABELS[level];
17337
- let line;
17338
- if (this.color) {
17339
- const c = COLORS[level];
17340
- line = `${DIM}${ts}${RESET} ${c}${label}${RESET} ${msg}`;
17341
- } else {
17342
- line = `${ts} ${label} ${msg}`;
17343
- }
17344
- const dest = level === "error" ? process.stderr : process.stdout;
17345
- dest.write(line + `
17346
- `);
17347
- for (const a of args) {
17348
- if (a instanceof Error) {
17349
- dest.write(` ${a.message}
17350
- `);
17351
- } else if (a !== undefined) {
17352
- dest.write(` ${String(a)}
17353
- `);
17354
- }
17355
- }
17356
- }
17357
- }
17358
- function createLogger2(level) {
17359
- const envLevel = process.env.ALOOK_LOG_LEVEL;
17360
- return new Logger2(level ?? envLevel ?? "info");
17361
- }
17362
- var log = createLogger2();
17363
-
17364
- // daemon/pidfile.ts
17365
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
17366
- import { dirname as dirname2 } from "path";
17367
- function isProcessAlive(pid) {
17368
- try {
17369
- process.kill(pid, 0);
17370
- return true;
17371
- } catch {
17372
- return false;
17373
- }
17374
- }
17375
- function readDaemonPid(profile) {
17376
- try {
17377
- const content = readFileSync3(pidFilePath(profile), "utf-8").trim();
17378
- const pid = parseInt(content, 10);
17379
- return Number.isNaN(pid) ? null : pid;
17380
- } catch {
17381
- return null;
17382
- }
17383
- }
17384
- function acquireDaemonPid(profile) {
17385
- const pidPath = pidFilePath(profile);
17386
- try {
17387
- const content = readFileSync3(pidPath, "utf-8").trim();
17388
- const existingPid = parseInt(content, 10);
17389
- if (!isNaN(existingPid) && isProcessAlive(existingPid)) {
17390
- log.error(`Another daemon is already running (PID ${existingPid}). ` + `Remove ${pidPath} if this is stale.`);
17391
- return false;
17392
- }
17393
- } catch {}
17394
- mkdirSync2(dirname2(pidPath), { recursive: true, mode: 448 });
17395
- writeFileSync2(pidPath, String(process.pid), { mode: 384 });
17396
- return true;
17397
- }
17398
- function removePidFileIfMatches(pid, profile) {
17399
- const pidPath = pidFilePath(profile);
17400
- const onDisk = readDaemonPid(profile);
17401
- if (onDisk !== pid)
17402
- return;
17403
- try {
17404
- unlinkSync(pidPath);
17405
- } catch {}
17406
- }
17407
- function releaseDaemonPid(profile) {
17408
- removePidFileIfMatches(process.pid, profile);
17409
- }
17410
-
17411
17425
  // daemon/update-handler.ts
17412
17426
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "fs";
17413
17427
 
@@ -18217,6 +18231,50 @@ async function startDaemon(profile, serverUrl) {
18217
18231
  };
18218
18232
  process.on("SIGTERM", shutdown);
18219
18233
  process.on("SIGINT", shutdown);
18234
+ process.on("SIGHUP", async () => {
18235
+ if (shuttingDown)
18236
+ return;
18237
+ log.info("SIGHUP received — reloading config...");
18238
+ try {
18239
+ const freshConfig = loadCLIConfigForProfile(profile);
18240
+ const freshWorkspaces = freshConfig.watched_workspaces || [];
18241
+ const existingIds = new Set(workspaceStates.map((ws) => ws.workspaceId));
18242
+ const newWorkspaces = freshWorkspaces.filter((ws) => ws.token && !existingIds.has(ws.id));
18243
+ for (const ws of newWorkspaces) {
18244
+ const runtimes = providers.map((p) => ({ type: p.type, version: p.version }));
18245
+ log.info(`Registering new workspace ${ws.id} (${ws.name ?? "unnamed"})...`);
18246
+ try {
18247
+ const resp = await client.register(ws.token, {
18248
+ workspace_id: ws.id,
18249
+ daemon_id: config2.daemonId,
18250
+ device_name: config2.deviceName,
18251
+ cli_version: config2.cliVersion,
18252
+ runtimes
18253
+ });
18254
+ const runtimeIds = resp.runtimes.map((r) => r.id);
18255
+ workspaceStates.push({ workspaceId: ws.id, token: ws.token, runtimeIds });
18256
+ for (let i = 0;i < runtimeIds.length; i++) {
18257
+ runtimeIndex.set(runtimeIds[i], {
18258
+ id: runtimeIds[i],
18259
+ workspaceId: ws.id,
18260
+ provider: providers[i].type
18261
+ });
18262
+ }
18263
+ log.info(`Workspace ${ws.id} added — ${runtimeIds.length} runtime(s)`);
18264
+ } catch (e) {
18265
+ log.error(`Failed to register new workspace ${ws.id}`, e);
18266
+ }
18267
+ }
18268
+ if (newWorkspaces.length > 0) {
18269
+ health.setRuntimeCount(workspaceStates.reduce((sum, w) => sum + w.runtimeIds.length, 0));
18270
+ log.info(`Reload complete — now polling ${workspaceStates.length} workspace(s)`);
18271
+ } else {
18272
+ log.info("Reload complete — no new workspaces found");
18273
+ }
18274
+ } catch (e) {
18275
+ log.error("Failed to reload config", e);
18276
+ }
18277
+ });
18220
18278
  await pollCycle();
18221
18279
  }
18222
18280
  function spawnSessionRunner(input) {
@@ -14570,7 +14570,7 @@ var IssueStatusSchema = exports_external.enum([
14570
14570
  IssueStatus.FAILED
14571
14571
  ]);
14572
14572
  var CreateIssueRequestSchema = exports_external.object({
14573
- agent_id: exports_external.string().min(1, "agent_id is required"),
14573
+ agent_id: exports_external.string().min(1).optional(),
14574
14574
  title: exports_external.string().min(1, "title is required").max(200),
14575
14575
  description: exports_external.string().max(20000).optional().default("")
14576
14576
  });
@@ -14594,9 +14594,9 @@ var IssueCommentApiSchema = exports_external.object({
14594
14594
  var IssueApiSchema = exports_external.object({
14595
14595
  id: exports_external.string(),
14596
14596
  workspace_id: exports_external.string(),
14597
- agent_id: exports_external.string(),
14597
+ agent_id: exports_external.string().nullable(),
14598
14598
  creator_user_id: exports_external.string(),
14599
- conversation_id: exports_external.string(),
14599
+ conversation_id: exports_external.string().nullable(),
14600
14600
  latest_task_id: exports_external.string().nullable(),
14601
14601
  title: exports_external.string(),
14602
14602
  description: exports_external.string(),
@@ -16406,9 +16406,9 @@ var agentTaskQueue = sqliteTable("agent_task_queue", {
16406
16406
  var issue2 = sqliteTable("issue", {
16407
16407
  id: text("id").primaryKey().$defaultFn(() => "iss_" + nanoid3()),
16408
16408
  workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
16409
- agentId: text("agent_id").notNull(),
16409
+ agentId: text("agent_id"),
16410
16410
  creatorUserId: text("creator_user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
16411
- conversationId: text("conversation_id").notNull().references(() => conversation.id, { onDelete: "cascade" }),
16411
+ conversationId: text("conversation_id").references(() => conversation.id, { onDelete: "cascade" }),
16412
16412
  latestTaskId: text("latest_task_id").references(() => agentTaskQueue.id, {
16413
16413
  onDelete: "set null"
16414
16414
  }),
@@ -18380,7 +18380,7 @@ function clearKillIntent(baseDir, taskId) {
18380
18380
  // daemon/prompt.ts
18381
18381
  var DM_RESPONSE_NOTICE = "IMPORTANT: Only your final text response is visible to the user." + " Tool calls, intermediate reasoning, and mid-process outputs are NOT displayed." + " Put all key information, answers, and conclusions in your final response — that is the only thing the user will read.";
18382
18382
  var EMAIL_NOTICE = "This task was triggered automatically by an incoming email. There is no human in this session." + " If you need to communicate with a human, you MUST send an email using the email sending tool." + " If you need more information or confirmation from the human, send them an email asking for it and then exit." + " Do not wait — when the human replies, a new task will be triggered automatically and you will be woken up with their response.";
18383
- var ISSUE_NOTICE = "This task was triggered by an assigned issue. The issue_id is provided in this message." + " Use `alook issue show --agent_id <your_agent_id> --issue_id <issue_id>` to read full context." + " Use `alook issue update --agent_id <your_agent_id> --issue_id <issue_id> --status <status>` to change status." + " Use `alook issue comment --agent_id <your_agent_id> --issue_id <issue_id> --body <text>` to leave a comment." + " You are responsible for setting the issue status: move to in_progress when working, review when awaiting user feedback, done/closed when finished." + " Always leave a comment summarizing what you did before changing status.";
18383
+ var ISSUE_NOTICE = "This task was triggered by an assigned issue. The issue_id is provided in this message." + " Use `alook issue show --agent_id <your_agent_id> --issue_id <issue_id>` to read full context." + " Use `alook issue update --agent_id <your_agent_id> --issue_id <issue_id> --status <status>` to change status." + " Use `alook issue comment --agent_id <your_agent_id> --issue_id <issue_id> --body <text>` to leave a comment." + " You are responsible for setting the issue status: move to in_progress when you start working on the task." + " IMPORTANT: You MUST move the status to 'review' when your task is fully complete — this signals to the owner that your work is ready for review." + " Only set 'review' when the task is done, never prematurely. Do not set 'review' for partial work or while still in progress." + " Always leave a comment summarizing what you did before changing status.";
18384
18384
  function buildDmNotice(name, email3) {
18385
18385
  return `This task was triggered by an incoming email on a conversation with ${name} (${email3}).` + ` ${name} is present in this session — reply to them directly.` + ` If you need to communicate with anyone else, use the email sending tool.`;
18386
18386
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alook/cli",
3
- "version": "0.0.51",
3
+ "version": "0.0.53",
4
4
  "description": "Alook CLI — Enable Your Person Colleague",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/alookai/alook#readme",