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 +7 -3
- package/src/cli/index.ts +23 -73
- package/src/cli/job.ts +25 -92
- package/src/cli/status.ts +17 -9
- package/src/core/agents.ts +6 -19
- package/src/core/consolidator.ts +14 -28
- package/src/core/daemon.ts +4 -41
- package/src/core/finalizer.ts +31 -3
- package/src/core/health.ts +5 -17
- package/src/core/runner.ts +0 -6
- package/src/core/scheduler.ts +12 -49
- package/src/core/skills.ts +4 -11
- package/src/core/summarizer.ts +7 -21
- package/src/db/connection.ts +0 -11
- package/src/db/models/job.ts +23 -22
- package/src/db/with-db.ts +11 -0
- package/src/mcp/server.ts +1 -1
- package/src/prompts/environment.md +44 -41
- package/src/utils/pid.ts +44 -0
- package/src/utils/schedule.ts +39 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "niahere",
|
|
3
|
-
"version": "0.2.
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
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,20 @@ 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
|
-
console.log(
|
|
261
|
-
|
|
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");
|
package/src/core/agents.ts
CHANGED
|
@@ -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] = {
|