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/package.json +7 -3
- package/src/chat/employee-prompt.ts +75 -0
- package/src/chat/engine.ts +40 -7
- package/src/chat/identity.ts +8 -1
- package/src/chat/repl.ts +52 -25
- package/src/cli/employee-add.ts +124 -0
- package/src/cli/employee.ts +167 -0
- package/src/cli/index.ts +43 -77
- package/src/cli/job.ts +36 -93
- package/src/cli/status.ts +18 -9
- package/src/commands/backup.ts +3 -7
- package/src/core/agents.ts +8 -20
- package/src/core/consolidator.ts +14 -28
- package/src/core/daemon.ts +4 -41
- package/src/core/employees.ts +116 -0
- package/src/core/finalizer.ts +31 -3
- package/src/core/health.ts +5 -17
- package/src/core/runner.ts +15 -9
- package/src/core/scheduler.ts +12 -49
- package/src/core/skills.ts +6 -12
- package/src/core/summarizer.ts +7 -21
- package/src/db/connection.ts +0 -11
- package/src/db/migrations/015_jobs_employee.ts +7 -0
- package/src/db/models/job.ts +34 -28
- package/src/db/with-db.ts +11 -0
- package/src/mcp/server.ts +15 -2
- package/src/mcp/tools.ts +13 -2
- package/src/prompts/environment.md +44 -41
- package/src/types/employee.ts +14 -0
- package/src/types/engine.ts +9 -2
- package/src/types/index.ts +1 -0
- package/src/types/job.ts +1 -0
- package/src/types/paths.ts +1 -0
- package/src/utils/paths.ts +1 -0
- package/src/utils/pid.ts +44 -0
- package/src/utils/schedule.ts +39 -0
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/
|
|
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
|
-
|
|
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
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
-
|
|
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] [--
|
|
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/
|
|
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
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
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
|
|
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
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
261
|
-
console.log(
|
|
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");
|