niahere 0.2.62 → 0.2.64

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/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";
@@ -21,6 +15,7 @@ import { sendCommand, telegramCommand, slackCommand } from "./channels";
21
15
  import { rulesCommand, memoryCommand } from "./self";
22
16
  import { watchCommand } from "./watch";
23
17
  import { agentCommand } from "./agent";
18
+ import { employeeCommand } from "./employee";
24
19
 
25
20
  // Set LOG_LEVEL from config before anything else logs
26
21
  try {
@@ -35,12 +30,7 @@ try {
35
30
  const command = process.argv[2];
36
31
 
37
32
  // 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
- ) {
33
+ if (command && !["init", "help", "version", "-v", "--version", "-h", "--help"].includes(command)) {
44
34
  mkdirSync(getNiaHome(), { recursive: true });
45
35
  }
46
36
 
@@ -56,8 +46,7 @@ async function awaitStartup(timeout = 60_000): Promise<void> {
56
46
  const expecting = new Set<string>();
57
47
  if (config.channels.enabled) {
58
48
  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");
49
+ if (config.channels.slack.bot_token && config.channels.slack.app_token) expecting.add("slack");
61
50
  }
62
51
  expecting.add("scheduler");
63
52
 
@@ -143,8 +132,7 @@ switch (command) {
143
132
  }
144
133
 
145
134
  case "restart": {
146
- const { isServiceInstalled, restartService } =
147
- await import("../commands/service");
135
+ const { isServiceInstalled, restartService } = await import("../commands/service");
148
136
  if (isServiceInstalled()) {
149
137
  // Service-aware: unload (stops KeepAlive respawn), kill, then reload
150
138
  await restartService();
@@ -153,9 +141,7 @@ switch (command) {
153
141
  startDaemon();
154
142
  }
155
143
  const restartPid = readPid();
156
- console.log(
157
- `nia restarting${restartPid ? ` (pid: ${restartPid})` : ""}...`,
158
- );
144
+ console.log(`nia restarting${restartPid ? ` (pid: ${restartPid})` : ""}...`);
159
145
  await awaitStartup();
160
146
  console.log("nia restarted");
161
147
  break;
@@ -166,12 +152,7 @@ switch (command) {
166
152
  if (prompt) {
167
153
  const { createChatEngine } = await import("../chat/engine");
168
154
  const { getMcpServers } = await import("../mcp");
169
- const {
170
- DIM,
171
- RESET: RST,
172
- CLEAR_LINE,
173
- SPINNER: FRAMES,
174
- } = await import("../utils/cli");
155
+ const { DIM, RESET: RST, CLEAR_LINE, SPINNER: FRAMES } = await import("../utils/cli");
175
156
  let frame = 0;
176
157
  let statusText = "thinking";
177
158
  let spinTimer: ReturnType<typeof setInterval> | null = null;
@@ -179,9 +160,7 @@ switch (command) {
179
160
  let streaming = false;
180
161
 
181
162
  const renderSpinner = () => {
182
- process.stderr.write(
183
- `${CLEAR_LINE}${DIM} ${FRAMES[frame]} ${statusText}${RST}`,
184
- );
163
+ process.stderr.write(`${CLEAR_LINE}${DIM} ${FRAMES[frame]} ${statusText}${RST}`);
185
164
  frame = (frame + 1) % FRAMES.length;
186
165
  };
187
166
 
@@ -232,13 +211,12 @@ switch (command) {
232
211
  }
233
212
 
234
213
  const costStr = costUsd > 0 ? `$${costUsd.toFixed(4)}` : "";
235
- const turnsStr =
236
- turns > 0 ? `${turns} turn${turns !== 1 ? "s" : ""}` : "";
214
+ const turnsStr = turns > 0 ? `${turns} turn${turns !== 1 ? "s" : ""}` : "";
237
215
  const meta = [costStr, turnsStr].filter(Boolean).join(" · ");
238
216
  if (meta) process.stderr.write(`\n${DIM}${meta}${RST}`);
239
217
  process.stdout.write("\n");
240
218
 
241
- engine.close();
219
+ await engine.close();
242
220
  });
243
221
  process.exit(0);
244
222
  } else {
@@ -279,13 +257,8 @@ switch (command) {
279
257
  const time = localTime(new Date(m.createdAt));
280
258
  const prefix = m.sender === "user" ? "you" : m.sender;
281
259
  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
- );
260
+ const snippet = m.content.length > 120 ? m.content.slice(0, 120) + "..." : m.content;
261
+ console.log(` ${roomTag}${time} ${prefix} > ${snippet.replace(/\n/g, " ")}`);
289
262
  }
290
263
  }
291
264
  });
@@ -302,14 +275,11 @@ switch (command) {
302
275
  const follow = logArgs.includes("-f") || logArgs.includes("--follow");
303
276
  // --channel <name> filters logs by channel/component via grep
304
277
  const chIdx = logArgs.indexOf("--channel");
305
- const channelFilter =
306
- chIdx !== -1 && logArgs[chIdx + 1] ? logArgs[chIdx + 1] : null;
278
+ const channelFilter = chIdx !== -1 && logArgs[chIdx + 1] ? logArgs[chIdx + 1] : null;
307
279
 
308
280
  if (channelFilter) {
309
281
  // Pipe through grep to filter by channel name in structured logs
310
- const tailArgs = follow
311
- ? ["tail", "-f", daemonLog]
312
- : ["tail", "-200", daemonLog];
282
+ const tailArgs = follow ? ["tail", "-f", daemonLog] : ["tail", "-200", daemonLog];
313
283
  const tail = Bun.spawn(tailArgs, {
314
284
  stdio: ["ignore", "pipe", "inherit"],
315
285
  });
@@ -318,9 +288,7 @@ switch (command) {
318
288
  });
319
289
  await grep.exited;
320
290
  } else {
321
- const args = follow
322
- ? ["tail", "-f", daemonLog]
323
- : ["tail", "-50", daemonLog];
291
+ const args = follow ? ["tail", "-f", daemonLog] : ["tail", "-50", daemonLog];
324
292
  const proc = Bun.spawn(args, { stdio: ["ignore", "inherit", "inherit"] });
325
293
  await proc.exited;
326
294
  }
@@ -340,10 +308,18 @@ switch (command) {
340
308
  : chatArgs.includes("--resume") || chatArgs.includes("-r")
341
309
  ? ("pick" as const)
342
310
  : ("new" as const);
343
- const chIdx = chatArgs.indexOf("--channel");
344
- const simChannel =
345
- chIdx !== -1 && chatArgs[chIdx + 1] ? chatArgs[chIdx + 1] : undefined;
346
- await startRepl(mode, simChannel);
311
+ const flagVal = (flag: string) => {
312
+ const idx = chatArgs.indexOf(flag);
313
+ return idx !== -1 && chatArgs[idx + 1] ? chatArgs[idx + 1] : undefined;
314
+ };
315
+ const simChannel = flagVal("--channel");
316
+ const context = {
317
+ employee: flagVal("--employee"),
318
+ agent: flagVal("--agent"),
319
+ job: flagVal("--job"),
320
+ };
321
+ const hasContext = context.employee || context.agent || context.job;
322
+ await startRepl(mode, simChannel, hasContext ? context : undefined);
347
323
  break;
348
324
  }
349
325
 
@@ -352,6 +328,11 @@ switch (command) {
352
328
  break;
353
329
  }
354
330
 
331
+ case "employee": {
332
+ await employeeCommand();
333
+ break;
334
+ }
335
+
355
336
  case "skills": {
356
337
  const { scanSkills: loadSkills } = await import("../core/skills");
357
338
  const filter = process.argv[3]; // e.g. "project", "nia", "shared", "claude"
@@ -360,9 +341,7 @@ switch (command) {
360
341
  skills = skills.filter((s) => s.source === filter);
361
342
  }
362
343
  if (skills.length === 0) {
363
- console.log(
364
- filter ? `No skills found in "${filter}".` : "No skills found.",
365
- );
344
+ console.log(filter ? `No skills found in "${filter}".` : "No skills found.");
366
345
  } else {
367
346
  for (const s of skills) {
368
347
  const tag = filter ? "" : ` [${s.source}]`;
@@ -416,8 +395,7 @@ switch (command) {
416
395
  const parts = configKey.split(".");
417
396
  let val: unknown = raw;
418
397
  for (const p of parts) {
419
- if (val && typeof val === "object")
420
- val = (val as Record<string, unknown>)[p];
398
+ if (val && typeof val === "object") val = (val as Record<string, unknown>)[p];
421
399
  else {
422
400
  val = undefined;
423
401
  break;
@@ -455,9 +433,7 @@ switch (command) {
455
433
  process.kill(pid, "SIGHUP");
456
434
  console.log(`channels ${enabled ? "enabled" : "disabled"}`);
457
435
  } else {
458
- console.log(
459
- `channels ${enabled ? "enabled" : "disabled"} — start nia to apply`,
460
- );
436
+ console.log(`channels ${enabled ? "enabled" : "disabled"} — start nia to apply`);
461
437
  }
462
438
  } else {
463
439
  console.log(`channels: ${getConfig().channels.enabled ? "on" : "off"}`);
@@ -472,21 +448,15 @@ switch (command) {
472
448
  }
473
449
 
474
450
  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");
451
+ const verbose = process.argv.includes("-v") || process.argv.includes("--verbose");
452
+ const extraArgs = process.argv.slice(3).filter((a) => a !== "-v" && a !== "--verbose");
480
453
  const proc = Bun.spawn(["bun", "test", ...extraArgs], {
481
454
  stdio: ["ignore", "pipe", "pipe"],
482
455
  cwd: import.meta.dir + "/../..",
483
456
  env: { ...process.env, LOG_LEVEL: "silent" },
484
457
  });
485
458
 
486
- const [stdout, stderr] = await Promise.all([
487
- new Response(proc.stdout).text(),
488
- new Response(proc.stderr).text(),
489
- ]);
459
+ const [stdout, stderr] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text()]);
490
460
  const exitCode = await proc.exited;
491
461
  const output = stdout + stderr;
492
462
 
@@ -556,8 +526,7 @@ switch (command) {
556
526
  console.log(`Updated: v${currentVersion} → v${newVersion}`);
557
527
  if (isRunning()) {
558
528
  console.log("Restarting daemon...");
559
- const { isServiceInstalled, restartService } =
560
- await import("../commands/service");
529
+ const { isServiceInstalled, restartService } = await import("../commands/service");
561
530
  if (isServiceInstalled()) {
562
531
  await restartService();
563
532
  } else {
@@ -591,7 +560,7 @@ Daemon:
591
560
  logs [-f] [--channel ch] Daemon logs (filter by channel)
592
561
 
593
562
  Chat:
594
- chat [-c] [-r] [--channel ch] Interactive chat (new session by default)
563
+ chat [-c] [-r] [--employee|--agent|--job name] Interactive chat
595
564
  run <prompt> One-shot execution
596
565
  history [room] Recent messages
597
566
  send [-c ch] <msg> Send a message via channel
@@ -603,6 +572,7 @@ Persona:
603
572
  rules [show|reset] View or reset rules.md
604
573
  memory [show|reset] View or reset memory.md
605
574
  agent <sub> List/show agents
575
+ employee <sub> Manage employees
606
576
  skills [source] List available skills
607
577
 
608
578
  Channels:
@@ -622,11 +592,7 @@ System:
622
592
 
623
593
  console.log(HELP);
624
594
  // Unknown command → exit 1, help/no command → exit 0
625
- const isHelp =
626
- !command ||
627
- command === "help" ||
628
- command === "--help" ||
629
- command === "-h";
595
+ const isHelp = !command || command === "help" || command === "--help" || command === "-h";
630
596
  if (!isHelp) console.error(`\nUnknown command: ${command}`);
631
597
  process.exit(isHelp ? 0 : 1);
632
598
  }
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>
@@ -29,6 +23,7 @@ Commands:
29
23
  --type cron|interval|once Schedule type (default: cron)
30
24
  --always Run 24/7 regardless of active hours
31
25
  --agent <name> Assign an agent to the job
26
+ --employee <name> Assign an employee to the job
32
27
  --model <model> Model override (e.g. haiku, sonnet, opus)
33
28
  --stateless yes|no Disable working memory for this job
34
29
  update <name> Update a job
@@ -38,6 +33,7 @@ Commands:
38
33
  --type cron|interval|once Change schedule type
39
34
  --always / --no-always Toggle 24/7 mode
40
35
  --agent <name> Assign agent (--no-agent to remove)
36
+ --employee <name> Assign employee (--no-employee to remove)
41
37
  --model <model> Model override (--no-model to remove)
42
38
  --stateless yes|no Toggle working memory
43
39
  remove <name> Delete a job
@@ -85,12 +81,7 @@ async function pickJob(prompt = "Pick a job"): Promise<string> {
85
81
  export async function jobCommand(): Promise<void> {
86
82
  const subcommand = process.argv[3];
87
83
 
88
- if (
89
- !subcommand ||
90
- subcommand === "help" ||
91
- subcommand === "--help" ||
92
- subcommand === "-h"
93
- ) {
84
+ if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
94
85
  console.log(HELP);
95
86
  process.exit(subcommand ? 0 : 0);
96
87
  }
@@ -101,18 +92,14 @@ export async function jobCommand(): Promise<void> {
101
92
  await withDb(async () => {
102
93
  const jobs = await Job.list();
103
94
  if (jobs.length === 0) {
104
- console.log(
105
- "No jobs configured. Use `nia job add` or `nia job import`.",
106
- );
95
+ console.log("No jobs configured. Use `nia job add` or `nia job import`.");
107
96
  } else {
108
97
  for (const job of jobs) {
109
98
  const tag = job.always ? " always" : "";
110
- const type =
111
- job.scheduleType !== "cron" ? ` (${job.scheduleType})` : "";
99
+ const type = job.scheduleType !== "cron" ? ` (${job.scheduleType})` : "";
112
100
  const agentTag = job.agent ? ` [${job.agent}]` : "";
113
- console.log(
114
- ` ${job.enabled ? "●" : "○"} ${job.name} ${job.schedule}${type}${tag}${agentTag}`,
115
- );
101
+ const empTag = job.employee ? ` [emp:${job.employee}]` : "";
102
+ console.log(` ${job.enabled ? "●" : "○"} ${job.name} ${job.schedule}${type}${tag}${agentTag}${empTag}`);
116
103
  }
117
104
  }
118
105
  });
@@ -131,17 +118,14 @@ export async function jobCommand(): Promise<void> {
131
118
 
132
119
  const scheduleType = (args.getString("type") || "cron") as ScheduleType;
133
120
  if (!["cron", "interval", "once"].includes(scheduleType)) {
134
- fail(
135
- `Invalid --type: "${scheduleType}". Must be cron, interval, or once.`,
136
- );
121
+ fail(`Invalid --type: "${scheduleType}". Must be cron, interval, or once.`);
137
122
  }
138
123
 
139
124
  const always = args.getBool("always") ?? false;
140
125
  const statelessRaw = args.getString("stateless");
141
- const stateless = statelessRaw
142
- ? ["yes", "y", "true", "t", "1"].includes(statelessRaw.toLowerCase())
143
- : false;
126
+ const stateless = statelessRaw ? ["yes", "y", "true", "t", "1"].includes(statelessRaw.toLowerCase()) : false;
144
127
  const agent = args.getString("agent");
128
+ const employee = args.getString("employee");
145
129
  const model = args.getString("model");
146
130
 
147
131
  const [name, schedule, ...promptParts] = args.positional;
@@ -162,33 +146,15 @@ export async function jobCommand(): Promise<void> {
162
146
  console.error(
163
147
  "Usage: nia job add <name> <schedule> <prompt> [--always] [--type cron|interval|once] [--agent <name>]",
164
148
  );
165
- fail(
166
- 'Example: nia job add heartbeat "*/10 * * * *" Check system health --always',
167
- );
149
+ fail('Example: nia job add heartbeat "*/10 * * * *" Check system health --always');
168
150
  }
169
151
 
170
152
  try {
171
153
  const config = getConfig();
172
- const nextRunAt = computeInitialNextRun(
173
- scheduleType,
174
- schedule,
175
- config.timezone,
176
- );
154
+ const nextRunAt = computeInitialNextRun(scheduleType, schedule, config.timezone);
177
155
  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
- );
156
+ await Job.create(name, schedule, prompt, always, scheduleType, nextRunAt, agent, stateless, model, employee);
157
+ console.log(`Job "${name}" added (${scheduleType}: ${schedule}).${always ? " (runs 24/7)" : ""}`);
192
158
  });
193
159
  } catch (err) {
194
160
  fail(`Failed to add job: ${errMsg(err)}`);
@@ -203,9 +169,7 @@ export async function jobCommand(): Promise<void> {
203
169
  try {
204
170
  await withDb(async () => {
205
171
  const removed = await Job.remove(name);
206
- console.log(
207
- removed ? `Job "${name}" removed.` : `Job not found: ${name}`,
208
- );
172
+ console.log(removed ? `Job "${name}" removed.` : `Job not found: ${name}`);
209
173
  });
210
174
  } catch (err) {
211
175
  fail(`Failed to remove job: ${errMsg(err)}`);
@@ -222,11 +186,7 @@ export async function jobCommand(): Promise<void> {
222
186
  try {
223
187
  await withDb(async () => {
224
188
  const updated = await Job.update(name, { enabled });
225
- console.log(
226
- updated
227
- ? `Job "${name}" ${subcommand}d.`
228
- : `Job not found: ${name}`,
229
- );
189
+ console.log(updated ? `Job "${name}" ${subcommand}d.` : `Job not found: ${name}`);
230
190
  });
231
191
  } catch (err) {
232
192
  fail(`Failed: ${errMsg(err)}`);
@@ -246,9 +206,7 @@ export async function jobCommand(): Promise<void> {
246
206
  console.error(
247
207
  "Usage: nia job update <name> [--schedule <s>] [--prompt <p>] [--type <t>] [--always] [--no-always]",
248
208
  );
249
- fail(
250
- 'Example: nia job update curator --schedule "4h" --prompt "New prompt"',
251
- );
209
+ fail('Example: nia job update curator --schedule "4h" --prompt "New prompt"');
252
210
  }
253
211
 
254
212
  const fields: Partial<{
@@ -259,6 +217,7 @@ export async function jobCommand(): Promise<void> {
259
217
  model: string | null;
260
218
  scheduleType: ScheduleType;
261
219
  agent: string | null;
220
+ employee: string | null;
262
221
  }> = {};
263
222
  const schedule = args.getString("schedule");
264
223
  const promptFile = args.getString("prompt-file");
@@ -273,24 +232,23 @@ export async function jobCommand(): Promise<void> {
273
232
  const statelessRaw = args.getString("stateless");
274
233
  const agent = args.getString("agent");
275
234
  const noAgent = args.getBool("agent");
235
+ const employeeFlag = args.getString("employee");
236
+ const noEmployee = args.getBool("employee");
276
237
 
277
238
  if (schedule) fields.schedule = schedule;
278
239
  if (prompt) fields.prompt = prompt;
279
240
  if (scheduleType) {
280
241
  if (!["cron", "interval", "once"].includes(scheduleType)) {
281
- fail(
282
- `Invalid --type: "${scheduleType}". Must be cron, interval, or once.`,
283
- );
242
+ fail(`Invalid --type: "${scheduleType}". Must be cron, interval, or once.`);
284
243
  }
285
244
  fields.scheduleType = scheduleType;
286
245
  }
287
246
  if (always !== undefined) fields.always = always;
288
- if (statelessRaw)
289
- fields.stateless = ["yes", "y", "true", "t", "1"].includes(
290
- statelessRaw.toLowerCase(),
291
- );
247
+ if (statelessRaw) fields.stateless = ["yes", "y", "true", "t", "1"].includes(statelessRaw.toLowerCase());
292
248
  if (agent) fields.agent = agent;
293
249
  if (noAgent === false) fields.agent = null;
250
+ if (employeeFlag) fields.employee = employeeFlag;
251
+ if (noEmployee === false) fields.employee = null;
294
252
  const modelFlag = args.getString("model");
295
253
  const noModel = args.getBool("model");
296
254
  if (modelFlag) fields.model = modelFlag;
@@ -298,17 +256,14 @@ export async function jobCommand(): Promise<void> {
298
256
 
299
257
  if (Object.keys(fields).length === 0) {
300
258
  fail(
301
- "Nothing to update. Pass at least one flag (--schedule, --prompt, --type, --always, --stateless, --model, --agent).",
259
+ "Nothing to update. Pass at least one flag (--schedule, --prompt, --type, --always, --stateless, --model, --agent, --employee).",
302
260
  );
303
261
  }
304
262
 
305
263
  try {
306
264
  await withDb(async () => {
307
265
  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
- );
266
+ if (!updated) fail(`Job not found: "${name}". Use \`nia job list\` to see available jobs.`);
312
267
  console.log(`Job "${name}" updated.`);
313
268
  });
314
269
  } catch (err) {
@@ -330,6 +285,7 @@ export async function jobCommand(): Promise<void> {
330
285
  console.log(` enabled: ${job.enabled}`);
331
286
  console.log(` always: ${job.always}`);
332
287
  if (job.agent) console.log(` agent: ${job.agent}`);
288
+ if (job.employee) console.log(` employee: ${job.employee}`);
333
289
  if (job.model) console.log(` model: ${job.model}`);
334
290
  if (job.stateless) console.log(` stateless: true`);
335
291
  console.log(` prompt: ${job.prompt}`);
@@ -352,11 +308,8 @@ export async function jobCommand(): Promise<void> {
352
308
  const time = localTime(new Date(e.timestamp));
353
309
  const dur = `${formatDuration(e.duration_ms)}`;
354
310
  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
- );
311
+ const summary = e.error || e.result.slice(0, 60).replace(/\n/g, " ") || "-";
312
+ console.log(` ${icon} ${time} ${dur.padStart(8)} ${summary}`);
360
313
  }
361
314
  }
362
315
  });
@@ -380,9 +333,7 @@ export async function jobCommand(): Promise<void> {
380
333
  ? `${info.status} (${localTime(new Date(info.lastRun))}, ${formatDuration(info.duration_ms)})`
381
334
  : "never run";
382
335
  const tag = job.always ? " always" : "";
383
- console.log(
384
- ` ${job.enabled ? "●" : "○"} ${job.name} [${job.schedule}]${tag} ${status}`,
385
- );
336
+ console.log(` ${job.enabled ? "●" : "○"} ${job.name} [${job.schedule}]${tag} ${status}`);
386
337
  if (info?.error) console.log(` error: ${info.error}`);
387
338
  });
388
339
  } catch (err) {
@@ -395,8 +346,7 @@ export async function jobCommand(): Promise<void> {
395
346
  const name = process.argv[4];
396
347
  if (!name) fail("Usage: nia job run <name>");
397
348
 
398
- let found: { name: string; schedule: string; prompt: string } | null =
399
- null;
349
+ let found: { name: string; schedule: string; prompt: string } | null = null;
400
350
  try {
401
351
  await withDb(async () => {
402
352
  found = await Job.get(name);
@@ -457,22 +407,15 @@ export async function jobCommand(): Promise<void> {
457
407
  const logName = process.argv[4];
458
408
  const entries = readAudit(logName, 20);
459
409
  if (entries.length === 0) {
460
- console.log(
461
- logName
462
- ? `No runs found for ${logName}`
463
- : "No job runs recorded yet.",
464
- );
410
+ console.log(logName ? `No runs found for ${logName}` : "No job runs recorded yet.");
465
411
  break;
466
412
  }
467
413
  for (const e of entries) {
468
414
  const time = localTime(new Date(e.timestamp));
469
415
  const dur = `${formatDuration(e.duration_ms)}`;
470
416
  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
- );
417
+ const summary = e.error || e.result.slice(0, 80).replace(/\n/g, " ") || "-";
418
+ console.log(` ${status} ${time} ${dur.padStart(8)} ${e.job} ${summary}`);
476
419
  }
477
420
  break;
478
421
  }
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,21 @@ 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
+ const empTag = job.employee ? ` [emp:${job.employee}]` : "";
265
+ console.log(
266
+ ` ${job.enabled ? "\u25cf" : "\u25cb"} ${job.name.padEnd(20)} ${job.enabled ? "enabled" : "disabled"}${agentTag}${empTag}`,
267
+ );
268
+ console.log(
269
+ ` ${statusIcon} ${status} last: ${lastText} next: ${nextText} duration: ${durationText}${staleText}`,
270
+ );
262
271
  }
263
272
  } else {
264
273
  console.log("\nJobs: none");