niahere 0.2.62 → 0.2.63

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "niahere",
3
- "version": "0.2.62",
3
+ "version": "0.2.63",
4
4
  "description": "A personal AI assistant daemon — chat, scheduled jobs, persona system, extensible via skills.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -8,9 +8,10 @@
8
8
  "stop": "bun run src/cli/index.ts stop",
9
9
  "status": "bun run src/cli/index.ts status",
10
10
  "dev": "bun run src/cli/index.ts run",
11
- "test": "tsc --noEmit && LOG_LEVEL=silent bun test --reporter=dots",
11
+ "test": "tsc --noEmit && bun run check:cycles && LOG_LEVEL=silent bun test --reporter=dots",
12
12
  "test:bun": "bun test",
13
13
  "typecheck": "tsc --noEmit",
14
+ "check:cycles": "madge --circular --extensions ts src/",
14
15
  "seed": "bun run src/db/seed.ts",
15
16
  "release": "npm version patch && npm publish && git push"
16
17
  },
@@ -44,6 +45,7 @@
44
45
  "private": false,
45
46
  "dependencies": {
46
47
  "@anthropic-ai/claude-agent-sdk": "^0.2.97",
48
+ "@anthropic-ai/sdk": "^0.88.0",
47
49
  "@modelcontextprotocol/sdk": "^1.27.1",
48
50
  "@slack/bolt": "^4.6.0",
49
51
  "cron-parser": "^5.5.0",
@@ -51,11 +53,13 @@
51
53
  "js-yaml": "^4.1.1",
52
54
  "pino": "^10.3.1",
53
55
  "postgres": "^3.4.8",
54
- "sharp": "^0.34.5"
56
+ "sharp": "^0.34.5",
57
+ "zod": "^4.3.6"
55
58
  },
56
59
  "devDependencies": {
57
60
  "@types/bun": "^1.3.10",
58
61
  "@types/js-yaml": "^4.0.9",
62
+ "madge": "^8.0.0",
59
63
  "typescript": "^5.9.3"
60
64
  }
61
65
  }
package/src/cli/index.ts CHANGED
@@ -1,17 +1,11 @@
1
1
  #!/usr/bin/env bun
2
2
  import { existsSync, mkdirSync } from "fs";
3
- import {
4
- isRunning,
5
- readPid,
6
- runDaemon,
7
- startDaemon,
8
- stopDaemon,
9
- } from "../core/daemon";
3
+ import { isRunning, readPid, runDaemon, startDaemon, stopDaemon } from "../core/daemon";
10
4
  import { getConfig } from "../utils/config";
11
5
  import { localTime } from "../utils/time";
12
6
  import { startRepl } from "../chat/repl";
13
7
  import { Message } from "../db/models";
14
- import { withDb } from "../db/connection";
8
+ import { withDb } from "../db/with-db";
15
9
  import { getNiaHome, getPaths } from "../utils/paths";
16
10
  import { errMsg } from "../utils/errors";
17
11
  import { fail, ICON_PASS, ICON_WARN } from "../utils/cli";
@@ -35,12 +29,7 @@ try {
35
29
  const command = process.argv[2];
36
30
 
37
31
  // Ensure ~/.niahere/ exists for commands that need it
38
- if (
39
- command &&
40
- !["init", "help", "version", "-v", "--version", "-h", "--help"].includes(
41
- command,
42
- )
43
- ) {
32
+ if (command && !["init", "help", "version", "-v", "--version", "-h", "--help"].includes(command)) {
44
33
  mkdirSync(getNiaHome(), { recursive: true });
45
34
  }
46
35
 
@@ -56,8 +45,7 @@ async function awaitStartup(timeout = 60_000): Promise<void> {
56
45
  const expecting = new Set<string>();
57
46
  if (config.channels.enabled) {
58
47
  if (config.channels.telegram.bot_token) expecting.add("telegram");
59
- if (config.channels.slack.bot_token && config.channels.slack.app_token)
60
- expecting.add("slack");
48
+ if (config.channels.slack.bot_token && config.channels.slack.app_token) expecting.add("slack");
61
49
  }
62
50
  expecting.add("scheduler");
63
51
 
@@ -143,8 +131,7 @@ switch (command) {
143
131
  }
144
132
 
145
133
  case "restart": {
146
- const { isServiceInstalled, restartService } =
147
- await import("../commands/service");
134
+ const { isServiceInstalled, restartService } = await import("../commands/service");
148
135
  if (isServiceInstalled()) {
149
136
  // Service-aware: unload (stops KeepAlive respawn), kill, then reload
150
137
  await restartService();
@@ -153,9 +140,7 @@ switch (command) {
153
140
  startDaemon();
154
141
  }
155
142
  const restartPid = readPid();
156
- console.log(
157
- `nia restarting${restartPid ? ` (pid: ${restartPid})` : ""}...`,
158
- );
143
+ console.log(`nia restarting${restartPid ? ` (pid: ${restartPid})` : ""}...`);
159
144
  await awaitStartup();
160
145
  console.log("nia restarted");
161
146
  break;
@@ -166,12 +151,7 @@ switch (command) {
166
151
  if (prompt) {
167
152
  const { createChatEngine } = await import("../chat/engine");
168
153
  const { getMcpServers } = await import("../mcp");
169
- const {
170
- DIM,
171
- RESET: RST,
172
- CLEAR_LINE,
173
- SPINNER: FRAMES,
174
- } = await import("../utils/cli");
154
+ const { DIM, RESET: RST, CLEAR_LINE, SPINNER: FRAMES } = await import("../utils/cli");
175
155
  let frame = 0;
176
156
  let statusText = "thinking";
177
157
  let spinTimer: ReturnType<typeof setInterval> | null = null;
@@ -179,9 +159,7 @@ switch (command) {
179
159
  let streaming = false;
180
160
 
181
161
  const renderSpinner = () => {
182
- process.stderr.write(
183
- `${CLEAR_LINE}${DIM} ${FRAMES[frame]} ${statusText}${RST}`,
184
- );
162
+ process.stderr.write(`${CLEAR_LINE}${DIM} ${FRAMES[frame]} ${statusText}${RST}`);
185
163
  frame = (frame + 1) % FRAMES.length;
186
164
  };
187
165
 
@@ -232,8 +210,7 @@ switch (command) {
232
210
  }
233
211
 
234
212
  const costStr = costUsd > 0 ? `$${costUsd.toFixed(4)}` : "";
235
- const turnsStr =
236
- turns > 0 ? `${turns} turn${turns !== 1 ? "s" : ""}` : "";
213
+ const turnsStr = turns > 0 ? `${turns} turn${turns !== 1 ? "s" : ""}` : "";
237
214
  const meta = [costStr, turnsStr].filter(Boolean).join(" · ");
238
215
  if (meta) process.stderr.write(`\n${DIM}${meta}${RST}`);
239
216
  process.stdout.write("\n");
@@ -279,13 +256,8 @@ switch (command) {
279
256
  const time = localTime(new Date(m.createdAt));
280
257
  const prefix = m.sender === "user" ? "you" : m.sender;
281
258
  const roomTag = room ? "" : `[${m.room}] `;
282
- const snippet =
283
- m.content.length > 120
284
- ? m.content.slice(0, 120) + "..."
285
- : m.content;
286
- console.log(
287
- ` ${roomTag}${time} ${prefix} > ${snippet.replace(/\n/g, " ")}`,
288
- );
259
+ const snippet = m.content.length > 120 ? m.content.slice(0, 120) + "..." : m.content;
260
+ console.log(` ${roomTag}${time} ${prefix} > ${snippet.replace(/\n/g, " ")}`);
289
261
  }
290
262
  }
291
263
  });
@@ -302,14 +274,11 @@ switch (command) {
302
274
  const follow = logArgs.includes("-f") || logArgs.includes("--follow");
303
275
  // --channel <name> filters logs by channel/component via grep
304
276
  const chIdx = logArgs.indexOf("--channel");
305
- const channelFilter =
306
- chIdx !== -1 && logArgs[chIdx + 1] ? logArgs[chIdx + 1] : null;
277
+ const channelFilter = chIdx !== -1 && logArgs[chIdx + 1] ? logArgs[chIdx + 1] : null;
307
278
 
308
279
  if (channelFilter) {
309
280
  // Pipe through grep to filter by channel name in structured logs
310
- const tailArgs = follow
311
- ? ["tail", "-f", daemonLog]
312
- : ["tail", "-200", daemonLog];
281
+ const tailArgs = follow ? ["tail", "-f", daemonLog] : ["tail", "-200", daemonLog];
313
282
  const tail = Bun.spawn(tailArgs, {
314
283
  stdio: ["ignore", "pipe", "inherit"],
315
284
  });
@@ -318,9 +287,7 @@ switch (command) {
318
287
  });
319
288
  await grep.exited;
320
289
  } else {
321
- const args = follow
322
- ? ["tail", "-f", daemonLog]
323
- : ["tail", "-50", daemonLog];
290
+ const args = follow ? ["tail", "-f", daemonLog] : ["tail", "-50", daemonLog];
324
291
  const proc = Bun.spawn(args, { stdio: ["ignore", "inherit", "inherit"] });
325
292
  await proc.exited;
326
293
  }
@@ -341,8 +308,7 @@ switch (command) {
341
308
  ? ("pick" as const)
342
309
  : ("new" as const);
343
310
  const chIdx = chatArgs.indexOf("--channel");
344
- const simChannel =
345
- chIdx !== -1 && chatArgs[chIdx + 1] ? chatArgs[chIdx + 1] : undefined;
311
+ const simChannel = chIdx !== -1 && chatArgs[chIdx + 1] ? chatArgs[chIdx + 1] : undefined;
346
312
  await startRepl(mode, simChannel);
347
313
  break;
348
314
  }
@@ -360,9 +326,7 @@ switch (command) {
360
326
  skills = skills.filter((s) => s.source === filter);
361
327
  }
362
328
  if (skills.length === 0) {
363
- console.log(
364
- filter ? `No skills found in "${filter}".` : "No skills found.",
365
- );
329
+ console.log(filter ? `No skills found in "${filter}".` : "No skills found.");
366
330
  } else {
367
331
  for (const s of skills) {
368
332
  const tag = filter ? "" : ` [${s.source}]`;
@@ -416,8 +380,7 @@ switch (command) {
416
380
  const parts = configKey.split(".");
417
381
  let val: unknown = raw;
418
382
  for (const p of parts) {
419
- if (val && typeof val === "object")
420
- val = (val as Record<string, unknown>)[p];
383
+ if (val && typeof val === "object") val = (val as Record<string, unknown>)[p];
421
384
  else {
422
385
  val = undefined;
423
386
  break;
@@ -455,9 +418,7 @@ switch (command) {
455
418
  process.kill(pid, "SIGHUP");
456
419
  console.log(`channels ${enabled ? "enabled" : "disabled"}`);
457
420
  } else {
458
- console.log(
459
- `channels ${enabled ? "enabled" : "disabled"} — start nia to apply`,
460
- );
421
+ console.log(`channels ${enabled ? "enabled" : "disabled"} — start nia to apply`);
461
422
  }
462
423
  } else {
463
424
  console.log(`channels: ${getConfig().channels.enabled ? "on" : "off"}`);
@@ -472,21 +433,15 @@ switch (command) {
472
433
  }
473
434
 
474
435
  case "test": {
475
- const verbose =
476
- process.argv.includes("-v") || process.argv.includes("--verbose");
477
- const extraArgs = process.argv
478
- .slice(3)
479
- .filter((a) => a !== "-v" && a !== "--verbose");
436
+ const verbose = process.argv.includes("-v") || process.argv.includes("--verbose");
437
+ const extraArgs = process.argv.slice(3).filter((a) => a !== "-v" && a !== "--verbose");
480
438
  const proc = Bun.spawn(["bun", "test", ...extraArgs], {
481
439
  stdio: ["ignore", "pipe", "pipe"],
482
440
  cwd: import.meta.dir + "/../..",
483
441
  env: { ...process.env, LOG_LEVEL: "silent" },
484
442
  });
485
443
 
486
- const [stdout, stderr] = await Promise.all([
487
- new Response(proc.stdout).text(),
488
- new Response(proc.stderr).text(),
489
- ]);
444
+ const [stdout, stderr] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text()]);
490
445
  const exitCode = await proc.exited;
491
446
  const output = stdout + stderr;
492
447
 
@@ -556,8 +511,7 @@ switch (command) {
556
511
  console.log(`Updated: v${currentVersion} → v${newVersion}`);
557
512
  if (isRunning()) {
558
513
  console.log("Restarting daemon...");
559
- const { isServiceInstalled, restartService } =
560
- await import("../commands/service");
514
+ const { isServiceInstalled, restartService } = await import("../commands/service");
561
515
  if (isServiceInstalled()) {
562
516
  await restartService();
563
517
  } else {
@@ -622,11 +576,7 @@ System:
622
576
 
623
577
  console.log(HELP);
624
578
  // Unknown command → exit 1, help/no command → exit 0
625
- const isHelp =
626
- !command ||
627
- command === "help" ||
628
- command === "--help" ||
629
- command === "-h";
579
+ const isHelp = !command || command === "help" || command === "--help" || command === "-h";
630
580
  if (!isHelp) console.error(`\nUnknown command: ${command}`);
631
581
  process.exit(isHelp ? 0 : 1);
632
582
  }
package/src/cli/job.ts CHANGED
@@ -5,16 +5,10 @@ import { runJob } from "../core/runner";
5
5
  import { localTime } from "../utils/time";
6
6
  import { formatDuration } from "../utils/format";
7
7
  import { Job } from "../db/models";
8
- import { withDb } from "../db/connection";
8
+ import { withDb } from "../db/with-db";
9
9
  import type { ScheduleType } from "../types";
10
10
  import { errMsg } from "../utils/errors";
11
- import {
12
- fail,
13
- parseArgs,
14
- pickFromList,
15
- ICON_PASS,
16
- ICON_FAIL,
17
- } from "../utils/cli";
11
+ import { fail, parseArgs, pickFromList, ICON_PASS, ICON_FAIL } from "../utils/cli";
18
12
  import { computeInitialNextRun } from "../core/scheduler";
19
13
 
20
14
  const HELP = `Usage: nia job <command>
@@ -85,12 +79,7 @@ async function pickJob(prompt = "Pick a job"): Promise<string> {
85
79
  export async function jobCommand(): Promise<void> {
86
80
  const subcommand = process.argv[3];
87
81
 
88
- if (
89
- !subcommand ||
90
- subcommand === "help" ||
91
- subcommand === "--help" ||
92
- subcommand === "-h"
93
- ) {
82
+ if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
94
83
  console.log(HELP);
95
84
  process.exit(subcommand ? 0 : 0);
96
85
  }
@@ -101,18 +90,13 @@ export async function jobCommand(): Promise<void> {
101
90
  await withDb(async () => {
102
91
  const jobs = await Job.list();
103
92
  if (jobs.length === 0) {
104
- console.log(
105
- "No jobs configured. Use `nia job add` or `nia job import`.",
106
- );
93
+ console.log("No jobs configured. Use `nia job add` or `nia job import`.");
107
94
  } else {
108
95
  for (const job of jobs) {
109
96
  const tag = job.always ? " always" : "";
110
- const type =
111
- job.scheduleType !== "cron" ? ` (${job.scheduleType})` : "";
97
+ const type = job.scheduleType !== "cron" ? ` (${job.scheduleType})` : "";
112
98
  const agentTag = job.agent ? ` [${job.agent}]` : "";
113
- console.log(
114
- ` ${job.enabled ? "●" : "○"} ${job.name} ${job.schedule}${type}${tag}${agentTag}`,
115
- );
99
+ console.log(` ${job.enabled ? "●" : "○"} ${job.name} ${job.schedule}${type}${tag}${agentTag}`);
116
100
  }
117
101
  }
118
102
  });
@@ -131,16 +115,12 @@ export async function jobCommand(): Promise<void> {
131
115
 
132
116
  const scheduleType = (args.getString("type") || "cron") as ScheduleType;
133
117
  if (!["cron", "interval", "once"].includes(scheduleType)) {
134
- fail(
135
- `Invalid --type: "${scheduleType}". Must be cron, interval, or once.`,
136
- );
118
+ fail(`Invalid --type: "${scheduleType}". Must be cron, interval, or once.`);
137
119
  }
138
120
 
139
121
  const always = args.getBool("always") ?? false;
140
122
  const statelessRaw = args.getString("stateless");
141
- const stateless = statelessRaw
142
- ? ["yes", "y", "true", "t", "1"].includes(statelessRaw.toLowerCase())
143
- : false;
123
+ const stateless = statelessRaw ? ["yes", "y", "true", "t", "1"].includes(statelessRaw.toLowerCase()) : false;
144
124
  const agent = args.getString("agent");
145
125
  const model = args.getString("model");
146
126
 
@@ -162,33 +142,15 @@ export async function jobCommand(): Promise<void> {
162
142
  console.error(
163
143
  "Usage: nia job add <name> <schedule> <prompt> [--always] [--type cron|interval|once] [--agent <name>]",
164
144
  );
165
- fail(
166
- 'Example: nia job add heartbeat "*/10 * * * *" Check system health --always',
167
- );
145
+ fail('Example: nia job add heartbeat "*/10 * * * *" Check system health --always');
168
146
  }
169
147
 
170
148
  try {
171
149
  const config = getConfig();
172
- const nextRunAt = computeInitialNextRun(
173
- scheduleType,
174
- schedule,
175
- config.timezone,
176
- );
150
+ const nextRunAt = computeInitialNextRun(scheduleType, schedule, config.timezone);
177
151
  await withDb(async () => {
178
- await Job.create(
179
- name,
180
- schedule,
181
- prompt,
182
- always,
183
- scheduleType,
184
- nextRunAt,
185
- agent,
186
- stateless,
187
- model,
188
- );
189
- console.log(
190
- `Job "${name}" added (${scheduleType}: ${schedule}).${always ? " (runs 24/7)" : ""}`,
191
- );
152
+ await Job.create(name, schedule, prompt, always, scheduleType, nextRunAt, agent, stateless, model);
153
+ console.log(`Job "${name}" added (${scheduleType}: ${schedule}).${always ? " (runs 24/7)" : ""}`);
192
154
  });
193
155
  } catch (err) {
194
156
  fail(`Failed to add job: ${errMsg(err)}`);
@@ -203,9 +165,7 @@ export async function jobCommand(): Promise<void> {
203
165
  try {
204
166
  await withDb(async () => {
205
167
  const removed = await Job.remove(name);
206
- console.log(
207
- removed ? `Job "${name}" removed.` : `Job not found: ${name}`,
208
- );
168
+ console.log(removed ? `Job "${name}" removed.` : `Job not found: ${name}`);
209
169
  });
210
170
  } catch (err) {
211
171
  fail(`Failed to remove job: ${errMsg(err)}`);
@@ -222,11 +182,7 @@ export async function jobCommand(): Promise<void> {
222
182
  try {
223
183
  await withDb(async () => {
224
184
  const updated = await Job.update(name, { enabled });
225
- console.log(
226
- updated
227
- ? `Job "${name}" ${subcommand}d.`
228
- : `Job not found: ${name}`,
229
- );
185
+ console.log(updated ? `Job "${name}" ${subcommand}d.` : `Job not found: ${name}`);
230
186
  });
231
187
  } catch (err) {
232
188
  fail(`Failed: ${errMsg(err)}`);
@@ -246,9 +202,7 @@ export async function jobCommand(): Promise<void> {
246
202
  console.error(
247
203
  "Usage: nia job update <name> [--schedule <s>] [--prompt <p>] [--type <t>] [--always] [--no-always]",
248
204
  );
249
- fail(
250
- 'Example: nia job update curator --schedule "4h" --prompt "New prompt"',
251
- );
205
+ fail('Example: nia job update curator --schedule "4h" --prompt "New prompt"');
252
206
  }
253
207
 
254
208
  const fields: Partial<{
@@ -278,17 +232,12 @@ export async function jobCommand(): Promise<void> {
278
232
  if (prompt) fields.prompt = prompt;
279
233
  if (scheduleType) {
280
234
  if (!["cron", "interval", "once"].includes(scheduleType)) {
281
- fail(
282
- `Invalid --type: "${scheduleType}". Must be cron, interval, or once.`,
283
- );
235
+ fail(`Invalid --type: "${scheduleType}". Must be cron, interval, or once.`);
284
236
  }
285
237
  fields.scheduleType = scheduleType;
286
238
  }
287
239
  if (always !== undefined) fields.always = always;
288
- if (statelessRaw)
289
- fields.stateless = ["yes", "y", "true", "t", "1"].includes(
290
- statelessRaw.toLowerCase(),
291
- );
240
+ if (statelessRaw) fields.stateless = ["yes", "y", "true", "t", "1"].includes(statelessRaw.toLowerCase());
292
241
  if (agent) fields.agent = agent;
293
242
  if (noAgent === false) fields.agent = null;
294
243
  const modelFlag = args.getString("model");
@@ -305,10 +254,7 @@ export async function jobCommand(): Promise<void> {
305
254
  try {
306
255
  await withDb(async () => {
307
256
  const updated = await Job.update(name, fields);
308
- if (!updated)
309
- fail(
310
- `Job not found: "${name}". Use \`nia job list\` to see available jobs.`,
311
- );
257
+ if (!updated) fail(`Job not found: "${name}". Use \`nia job list\` to see available jobs.`);
312
258
  console.log(`Job "${name}" updated.`);
313
259
  });
314
260
  } catch (err) {
@@ -352,11 +298,8 @@ export async function jobCommand(): Promise<void> {
352
298
  const time = localTime(new Date(e.timestamp));
353
299
  const dur = `${formatDuration(e.duration_ms)}`;
354
300
  const icon = e.status === "ok" ? ICON_PASS : ICON_FAIL;
355
- const summary =
356
- e.error || e.result.slice(0, 60).replace(/\n/g, " ") || "-";
357
- console.log(
358
- ` ${icon} ${time} ${dur.padStart(8)} ${summary}`,
359
- );
301
+ const summary = e.error || e.result.slice(0, 60).replace(/\n/g, " ") || "-";
302
+ console.log(` ${icon} ${time} ${dur.padStart(8)} ${summary}`);
360
303
  }
361
304
  }
362
305
  });
@@ -380,9 +323,7 @@ export async function jobCommand(): Promise<void> {
380
323
  ? `${info.status} (${localTime(new Date(info.lastRun))}, ${formatDuration(info.duration_ms)})`
381
324
  : "never run";
382
325
  const tag = job.always ? " always" : "";
383
- console.log(
384
- ` ${job.enabled ? "●" : "○"} ${job.name} [${job.schedule}]${tag} ${status}`,
385
- );
326
+ console.log(` ${job.enabled ? "●" : "○"} ${job.name} [${job.schedule}]${tag} ${status}`);
386
327
  if (info?.error) console.log(` error: ${info.error}`);
387
328
  });
388
329
  } catch (err) {
@@ -395,8 +336,7 @@ export async function jobCommand(): Promise<void> {
395
336
  const name = process.argv[4];
396
337
  if (!name) fail("Usage: nia job run <name>");
397
338
 
398
- let found: { name: string; schedule: string; prompt: string } | null =
399
- null;
339
+ let found: { name: string; schedule: string; prompt: string } | null = null;
400
340
  try {
401
341
  await withDb(async () => {
402
342
  found = await Job.get(name);
@@ -457,22 +397,15 @@ export async function jobCommand(): Promise<void> {
457
397
  const logName = process.argv[4];
458
398
  const entries = readAudit(logName, 20);
459
399
  if (entries.length === 0) {
460
- console.log(
461
- logName
462
- ? `No runs found for ${logName}`
463
- : "No job runs recorded yet.",
464
- );
400
+ console.log(logName ? `No runs found for ${logName}` : "No job runs recorded yet.");
465
401
  break;
466
402
  }
467
403
  for (const e of entries) {
468
404
  const time = localTime(new Date(e.timestamp));
469
405
  const dur = `${formatDuration(e.duration_ms)}`;
470
406
  const status = e.status === "ok" ? ICON_PASS : ICON_FAIL;
471
- const summary =
472
- e.error || e.result.slice(0, 80).replace(/\n/g, " ") || "-";
473
- console.log(
474
- ` ${status} ${time} ${dur.padStart(8)} ${e.job} ${summary}`,
475
- );
407
+ const summary = e.error || e.result.slice(0, 80).replace(/\n/g, " ") || "-";
408
+ console.log(` ${status} ${time} ${dur.padStart(8)} ${e.job} ${summary}`);
476
409
  }
477
410
  break;
478
411
  }
package/src/cli/status.ts CHANGED
@@ -5,7 +5,7 @@ import { localTime } from "../utils/time";
5
5
  import { maskToken, safeDate, dateSortValue, formatTimeLine } from "../utils/format";
6
6
  import { Message, ActiveEngine, Job } from "../db/models";
7
7
  import type { ScheduleType, JobStateStatus, RoomStats } from "../types";
8
- import { withDb } from "../db/connection";
8
+ import { withDb } from "../db/with-db";
9
9
  import { errMsg } from "../utils/errors";
10
10
  import { checkForUpdate } from "../utils/update";
11
11
  import { ICON_PASS, ICON_FAIL, ICON_RUNNING } from "../utils/cli";
@@ -56,7 +56,6 @@ function parseStatusArgs(argv: string[]): StatusOptions {
56
56
  return opts;
57
57
  }
58
58
 
59
-
60
59
  export async function statusCommand(argv: string[] = []): Promise<void> {
61
60
  const options = parseStatusArgs(argv);
62
61
  const now = new Date();
@@ -87,7 +86,9 @@ export async function statusCommand(argv: string[] = []): Promise<void> {
87
86
  ? "not configured"
88
87
  : !config.channels.enabled
89
88
  ? "disabled"
90
- : running ? "active" : "configured",
89
+ : running
90
+ ? "active"
91
+ : "configured",
91
92
  tokenSuffix: config.channels.telegram.bot_token ? maskToken(config.channels.telegram.bot_token) : null,
92
93
  },
93
94
  slack: {
@@ -98,7 +99,9 @@ export async function statusCommand(argv: string[] = []): Promise<void> {
98
99
  : !config.channels.enabled
99
100
  ? "disabled"
100
101
  : running
101
- ? config.channels.slack.app_token ? "active" : "configured (missing app token)"
102
+ ? config.channels.slack.app_token
103
+ ? "active"
104
+ : "configured (missing app token)"
102
105
  : "configured",
103
106
  tokenSuffix: config.channels.slack.bot_token ? maskToken(config.channels.slack.bot_token) : null,
104
107
  },
@@ -115,7 +118,7 @@ export async function statusCommand(argv: string[] = []): Promise<void> {
115
118
 
116
119
  const jobsPayload: JobStatusLine[] = sortedJobs.map((job) => {
117
120
  const stateInfo = state[job.name];
118
- const lastRun = stateInfo?.lastRun ? stateInfo.lastRun : job.lastRunAt ?? null;
121
+ const lastRun = stateInfo?.lastRun ? stateInfo.lastRun : (job.lastRunAt ?? null);
119
122
  return {
120
123
  name: job.name,
121
124
  schedule: job.schedule,
@@ -188,7 +191,7 @@ export async function statusCommand(argv: string[] = []): Promise<void> {
188
191
  activeEngines: engineRows,
189
192
  rooms: roomRows,
190
193
  counts: {
191
- jobs: (dbError ? fallbackJobs.length : jobsPayload.length),
194
+ jobs: dbError ? fallbackJobs.length : jobsPayload.length,
192
195
  activeEngines: engineRows.length,
193
196
  rooms: roomRows.length,
194
197
  },
@@ -250,15 +253,20 @@ export async function statusCommand(argv: string[] = []): Promise<void> {
250
253
  safeDate(nextRun)!.getTime() <= now.getTime() &&
251
254
  !stateInfo;
252
255
 
253
- const statusIcon = status === "ok" ? ICON_PASS : status === "error" ? ICON_FAIL : status === "running" ? ICON_RUNNING : "\u2217";
256
+ const statusIcon =
257
+ status === "ok" ? ICON_PASS : status === "error" ? ICON_FAIL : status === "running" ? ICON_RUNNING : "\u2217";
254
258
  const durationText = stateInfo?.duration_ms === undefined ? "n/a" : formatDuration(stateInfo.duration_ms);
255
259
  const nextText = nextRun ? formatTimeLine(nextRun, now) : "unknown";
256
260
  const lastText = lastRun ? formatTimeLine(lastRun, now) : "never";
257
261
  const staleText = stale ? " ⚠ stale" : "";
258
262
 
259
263
  const agentTag = job.agent ? ` [${job.agent}]` : "";
260
- console.log(` ${job.enabled ? "\u25cf" : "\u25cb"} ${job.name.padEnd(20)} ${job.enabled ? "enabled" : "disabled"}${agentTag}`);
261
- console.log(` ${statusIcon} ${status} last: ${lastText} next: ${nextText} duration: ${durationText}${staleText}`);
264
+ console.log(
265
+ ` ${job.enabled ? "\u25cf" : "\u25cb"} ${job.name.padEnd(20)} ${job.enabled ? "enabled" : "disabled"}${agentTag}`,
266
+ );
267
+ console.log(
268
+ ` ${statusIcon} ${status} last: ${lastText} next: ${nextText} duration: ${durationText}${staleText}`,
269
+ );
262
270
  }
263
271
  } else {
264
272
  console.log("\nJobs: none");
@@ -48,14 +48,10 @@ export function scanAgents(): AgentInfo[] {
48
48
  try {
49
49
  meta = (yaml.load(fmMatch[1]) as Record<string, unknown>) || {};
50
50
  } catch (err) {
51
- log.warn(
52
- { err, agent: entry.name, path: agentFile },
53
- "failed to parse agent metadata, skipping",
54
- );
51
+ log.warn({ err, agent: entry.name, path: agentFile }, "failed to parse agent metadata, skipping");
55
52
  continue;
56
53
  }
57
- const name =
58
- (typeof meta.name === "string" ? meta.name : "") || entry.name;
54
+ const name = (typeof meta.name === "string" ? meta.name : "") || entry.name;
59
55
 
60
56
  const key = name.toLowerCase();
61
57
  if (seen.has(key)) continue;
@@ -65,8 +61,7 @@ export function scanAgents(): AgentInfo[] {
65
61
 
66
62
  agents.push({
67
63
  name,
68
- description:
69
- typeof meta.description === "string" ? meta.description : "",
64
+ description: typeof meta.description === "string" ? meta.description : "",
70
65
  body,
71
66
  model: typeof meta.model === "string" ? meta.model : undefined,
72
67
  source,
@@ -80,21 +75,13 @@ export function scanAgents(): AgentInfo[] {
80
75
  export function getAgentsSummary(): string {
81
76
  const agents = scanAgents();
82
77
  if (agents.length === 0) return "";
83
- const lines = agents.map((a) =>
84
- a.description ? `- @${a.name}: ${a.description}` : `- @${a.name}`,
85
- );
78
+ const lines = agents.map((a) => (a.description ? `- @${a.name}: ${a.description}` : `- @${a.name}`));
86
79
  return `Available agents:\n${lines.join("\n")}`;
87
80
  }
88
81
 
89
- export function getAgentDefinitions(): Record<
90
- string,
91
- { description: string; prompt: string; model?: string }
92
- > {
82
+ export function getAgentDefinitions(): Record<string, { description: string; prompt: string; model?: string }> {
93
83
  const agents = scanAgents();
94
- const defs: Record<
95
- string,
96
- { description: string; prompt: string; model?: string }
97
- > = {};
84
+ const defs: Record<string, { description: string; prompt: string; model?: string }> = {};
98
85
 
99
86
  for (const agent of agents) {
100
87
  defs[agent.name] = {