niahere 0.2.58 → 0.2.59
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/README.md +9 -3
- package/package.json +2 -2
- package/skills/beads-tasks/SKILL.md +91 -13
- package/src/chat/engine.ts +1 -0
- package/src/cli/index.ts +124 -38
- package/src/cli/job.ts +160 -43
- package/src/core/runner.ts +74 -15
- package/src/db/migrations/013_jobs_model.ts +7 -0
- package/src/db/models/job.ts +40 -13
- package/src/mcp/server.ts +202 -37
- package/src/mcp/tools.ts +116 -29
- package/src/types/audit.ts +1 -0
- package/src/types/job.ts +2 -0
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# nia
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/niahere)
|
|
4
|
+
[](https://www.npmjs.com/package/niahere)
|
|
5
|
+
[](https://github.com/onlyoneaman/niahere/blob/main/LICENSE)
|
|
6
|
+
|
|
3
7
|
A personal AI agent you fork and make your own. Small enough to understand, built for one user. Powered by Claude Agent SDK.
|
|
4
8
|
|
|
5
9
|
- npm package: [`niahere`](https://www.npmjs.com/package/niahere)
|
|
@@ -33,7 +37,7 @@ nia start # starts daemon + registers OS service
|
|
|
33
37
|
- **Telegram** — message your agent from your phone, typing indicator while processing
|
|
34
38
|
- **Slack** — Socket Mode bot with thread awareness, thinking emoji, watch channels for proactive monitoring
|
|
35
39
|
- **Terminal chat** — REPL with session resume support
|
|
36
|
-
- **Scheduled jobs** — recurring jobs and crons that run Claude and can message you back
|
|
40
|
+
- **Scheduled jobs** — recurring jobs and crons that run Claude and can message you back. Stateful by default (working memory), per-job model routing for cost savings
|
|
37
41
|
- **Persona system** — customizable identity, soul, owner profile, rules, and memory (preloaded every session)
|
|
38
42
|
- **Agents** — domain specialists (marketer, senior-dev) via Claude Agent SDK subagents
|
|
39
43
|
- **Skills** — loads skills from multiple directories, invokable as slash commands
|
|
@@ -63,8 +67,8 @@ nia update — update to latest version (auto-backup + resta
|
|
|
63
67
|
nia job list — list all jobs
|
|
64
68
|
nia job show [name] — full details + recent runs
|
|
65
69
|
nia job status [name] — quick status check
|
|
66
|
-
nia job add <n> <s> <p> — add a job (--type, --always, --agent, --stateless, --prompt-file)
|
|
67
|
-
nia job update <name> — update a job (--schedule, --prompt, --prompt-file, --type, --always, --agent, --stateless)
|
|
70
|
+
nia job add <n> <s> <p> — add a job (--type, --always, --agent, --model, --stateless, --prompt-file)
|
|
71
|
+
nia job update <name> — update a job (--schedule, --prompt, --prompt-file, --type, --always, --agent, --model, --stateless)
|
|
68
72
|
nia job remove <name> — delete a job
|
|
69
73
|
nia job enable / disable <n> — toggle a job
|
|
70
74
|
nia job run <name> — run a job once
|
|
@@ -106,6 +110,8 @@ All config and data lives in `~/.niahere/`:
|
|
|
106
110
|
soul.md — how the agent works
|
|
107
111
|
rules.md — behavioral instructions (loaded every session)
|
|
108
112
|
memory.md — persistent facts and context (loaded every session)
|
|
113
|
+
jobs/ — per-job working memory and state (auto-created)
|
|
114
|
+
optimizations/ — optimization loop run workspaces
|
|
109
115
|
images/
|
|
110
116
|
reference.webp — visual identity reference image
|
|
111
117
|
profile.webp — profile picture for Telegram/Slack
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "niahere",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.59",
|
|
4
4
|
"description": "A personal AI assistant daemon — chat, scheduled jobs, persona system, extensible via skills.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"license": "MIT",
|
|
44
44
|
"private": false,
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@anthropic-ai/claude-agent-sdk": "^0.2.
|
|
46
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.97",
|
|
47
47
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
48
48
|
"@slack/bolt": "^4.6.0",
|
|
49
49
|
"cron-parser": "^5.5.0",
|
|
@@ -3,9 +3,8 @@ name: beads-tasks
|
|
|
3
3
|
description: >
|
|
4
4
|
Persistent task management via Beads CLI (bd). Use when user mentions tasks, todos, issues, or tracking work.
|
|
5
5
|
Check `which bd` first — if missing, offer: `npm install -g @beads/bd`.
|
|
6
|
-
|
|
7
|
-
`
|
|
8
|
-
Run `cd "$BEATS_DIR" && bd help-all` for available commands. Not for ephemeral in-conversation tracking.
|
|
6
|
+
All commands: run from `$BEATS_DIR` (for example `~/.niahere/beads`) and use `bd <command>`. Always label: `--label project:<project-name>`.
|
|
7
|
+
Run `bd help-all` for available commands. Not for ephemeral in-conversation tracking.
|
|
9
8
|
---
|
|
10
9
|
|
|
11
10
|
## Overview
|
|
@@ -19,7 +18,63 @@ Global task manager powered by [Beads](https://github.com/steveyegge/beads). Sto
|
|
|
19
18
|
2. Ensure `~/.niahere/beads/.beads` exists — `bd init` if not.
|
|
20
19
|
3. Set `BEATS_DIR` to your Beads workspace (for example `~/.niahere/beads`).
|
|
21
20
|
4. All commands: `cd "$BEATS_DIR" && bd <command>`.
|
|
22
|
-
5. Always label with `--label project:<
|
|
21
|
+
5. Always label with `--label project:<name>`.
|
|
22
|
+
6. Run `cd "$BEATS_DIR" && bd help-all` for available commands.
|
|
23
|
+
|
|
24
|
+
## Core Commands
|
|
25
|
+
|
|
26
|
+
### Creating tasks
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Basic
|
|
30
|
+
bd create --title "Fix auth token refresh" --priority P2 --type bug
|
|
31
|
+
|
|
32
|
+
# With parent (subtask)
|
|
33
|
+
bd create --title "Extract shared logic" --priority P2 --type task --parent <parent-id>
|
|
34
|
+
|
|
35
|
+
# With description — ALWAYS add context: what's broken, links, references
|
|
36
|
+
bd create --title "Chat fails on long docs" --type bug --description "Fails on docs >500 pages. Ref: https://..."
|
|
37
|
+
|
|
38
|
+
# Epic (container for related tasks)
|
|
39
|
+
bd create --title "API performance improvements" --type epic --priority P2
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Updating tasks
|
|
43
|
+
|
|
44
|
+
`bd update` is the workhorse — use it for reparenting, reprioritizing, retyping, renaming:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
bd update <id> --parent <new-parent-id> # Reparent / move under epic
|
|
48
|
+
bd update <id> --parent "" # Remove parent (make top-level)
|
|
49
|
+
bd update <id> --priority P1 # Change priority
|
|
50
|
+
bd update <id> --type bug # Change type
|
|
51
|
+
bd update <id> --title "Better title" # Rename
|
|
52
|
+
bd update <id> --status in_progress # Start work
|
|
53
|
+
bd update <id> --description "..." # Add/replace description
|
|
54
|
+
bd update <id> --add-label personal # Add label
|
|
55
|
+
bd update <id> --set-labels bug,urgent # Replace all labels
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Chain multiple updates: `bd update <id> --priority P1 --type bug --parent <parent-id>`
|
|
59
|
+
|
|
60
|
+
### Viewing tasks
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
bd list # Open tasks (tree view)
|
|
64
|
+
bd list --all # Include closed/deferred tasks
|
|
65
|
+
bd list --label project:<name> # Filter by project
|
|
66
|
+
bd show <id> # Full details of a task
|
|
67
|
+
bd children <id> # List children of a parent
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Closing tasks
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
bd close <id> # Close a task
|
|
74
|
+
bd reopen <id> # Reopen if closed prematurely
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Warning:** Closing a parent does NOT close or reparent its children. If a parent epic is done but children remain open, reparent them first or they become orphaned top-level items.
|
|
23
78
|
|
|
24
79
|
## Decision Points
|
|
25
80
|
|
|
@@ -29,22 +84,45 @@ Global task manager powered by [Beads](https://github.com/steveyegge/beads). Sto
|
|
|
29
84
|
- User says "done with X" / "finished" → `bd close <id>`
|
|
30
85
|
- User wants to see cross-project work → `bd list` (no project filter)
|
|
31
86
|
- User wants project-specific view → `bd list --label project:<name>`
|
|
32
|
-
- User asks for task in-progress state → use task status:
|
|
33
|
-
- `bd update <task-id> --status in_progress`
|
|
34
|
-
- `bd set-state ... state=...` is for operational metadata only and does not
|
|
35
|
-
change list-visible task status.
|
|
36
87
|
- bd not installed → offer install, don't silently fail
|
|
37
88
|
- Ephemeral/conversation-only tracking → use conversation context, not beads
|
|
89
|
+
- `bd set-state ... state=...` is for operational metadata only; it does not change the task status shown in list.
|
|
90
|
+
|
|
91
|
+
## Hierarchy & Organization
|
|
92
|
+
|
|
93
|
+
### When to use parent-child vs labels
|
|
94
|
+
|
|
95
|
+
- **Parent-child** (`--parent`): for structural grouping — epics containing subtasks, features broken into steps.
|
|
96
|
+
- **Labels** (`--add-label`): for cross-cutting tags — `personal`, `urgent`, `project:<name>`. A task can have multiple labels but only one parent.
|
|
97
|
+
|
|
98
|
+
### Epic patterns
|
|
99
|
+
|
|
100
|
+
- Use `--type epic` for containers that group related work.
|
|
101
|
+
- Epics can nest: epic > sub-epic > tasks.
|
|
102
|
+
- Keep epic titles broad ("API improvements"), subtask titles specific ("Reduce /search latency from 2s to 200ms").
|
|
103
|
+
|
|
104
|
+
### Cleanup & auditing
|
|
105
|
+
|
|
106
|
+
Periodically review with `bd list` and look for:
|
|
107
|
+
- **Orphaned tasks** — top-level items that should be under an epic.
|
|
108
|
+
- **Similar ungrouped tasks** — multiple tasks on the same topic that should share a parent.
|
|
109
|
+
- **Misplaced tasks** — bugs under improvement epics or vice versa.
|
|
110
|
+
- **Stale tasks** — open tasks that are actually done or no longer relevant.
|
|
111
|
+
|
|
112
|
+
When reorganizing, reparent with `bd update <id> --parent <new-parent>` — don't delete and recreate.
|
|
38
113
|
|
|
39
114
|
## Conventions
|
|
40
115
|
|
|
41
|
-
- Titles
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
116
|
+
- **Titles:** descriptive, actionable (e.g. "Fix auth token refresh in niahere")
|
|
117
|
+
- **Descriptions:** always include context — what's broken, why it matters, links to references (Canny, threads, logs). Future you needs enough to start working without asking questions.
|
|
118
|
+
- **Types:** `epic`, `bug`, `feature`, `task`, `chore`, `decision`
|
|
119
|
+
- **Priority:** P0 (critical) → P4 (nice-to-have). Default P2 unless user specifies.
|
|
120
|
+
- **Labels:** `project:<name>`, `personal`, `bug`, `feature`, `chore`, `urgent`
|
|
121
|
+
- **Status flow:** `open` → `in_progress` → `closed`
|
|
45
122
|
|
|
46
123
|
## Validation
|
|
47
124
|
|
|
48
|
-
- `
|
|
125
|
+
- `bd list` returns results after creating a task
|
|
49
126
|
- Labels appear correctly in list output
|
|
127
|
+
- Parent-child relationships show as indented tree in `bd list`
|
|
50
128
|
- Dependencies show in `bd dep tree`
|
package/src/chat/engine.ts
CHANGED
|
@@ -382,6 +382,7 @@ export async function createChatEngine(
|
|
|
382
382
|
duration_ms: msg.duration_ms,
|
|
383
383
|
duration_api_ms: msg.duration_api_ms,
|
|
384
384
|
stop_reason: msg.stop_reason,
|
|
385
|
+
terminal_reason: msg.terminal_reason,
|
|
385
386
|
session_id: msg.session_id,
|
|
386
387
|
subtype: msg.subtype,
|
|
387
388
|
usage: msg.usage,
|
package/src/cli/index.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { existsSync, mkdirSync } from "fs";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
isRunning,
|
|
5
|
+
readPid,
|
|
6
|
+
runDaemon,
|
|
7
|
+
startDaemon,
|
|
8
|
+
stopDaemon,
|
|
9
|
+
} from "../core/daemon";
|
|
4
10
|
import { getConfig } from "../utils/config";
|
|
5
11
|
import { localTime } from "../utils/time";
|
|
6
12
|
import { startRepl } from "../chat/repl";
|
|
@@ -29,7 +35,12 @@ try {
|
|
|
29
35
|
const command = process.argv[2];
|
|
30
36
|
|
|
31
37
|
// Ensure ~/.niahere/ exists for commands that need it
|
|
32
|
-
if (
|
|
38
|
+
if (
|
|
39
|
+
command &&
|
|
40
|
+
!["init", "help", "version", "-v", "--version", "-h", "--help"].includes(
|
|
41
|
+
command,
|
|
42
|
+
)
|
|
43
|
+
) {
|
|
33
44
|
mkdirSync(getNiaHome(), { recursive: true });
|
|
34
45
|
}
|
|
35
46
|
|
|
@@ -45,7 +56,8 @@ async function awaitStartup(timeout = 60_000): Promise<void> {
|
|
|
45
56
|
const expecting = new Set<string>();
|
|
46
57
|
if (config.channels.enabled) {
|
|
47
58
|
if (config.channels.telegram.bot_token) expecting.add("telegram");
|
|
48
|
-
if (config.channels.slack.bot_token && config.channels.slack.app_token)
|
|
59
|
+
if (config.channels.slack.bot_token && config.channels.slack.app_token)
|
|
60
|
+
expecting.add("slack");
|
|
49
61
|
}
|
|
50
62
|
expecting.add("scheduler");
|
|
51
63
|
|
|
@@ -54,13 +66,19 @@ async function awaitStartup(timeout = 60_000): Promise<void> {
|
|
|
54
66
|
const { readFileSync } = await import("fs");
|
|
55
67
|
const ready = new Set<string>();
|
|
56
68
|
let logOffset = 0;
|
|
57
|
-
try {
|
|
69
|
+
try {
|
|
70
|
+
logOffset = readFileSync(daemonLog, "utf8").length;
|
|
71
|
+
} catch {}
|
|
58
72
|
|
|
59
73
|
const startTime = Date.now();
|
|
60
74
|
while (ready.size < expecting.size && Date.now() - startTime < timeout) {
|
|
61
75
|
await new Promise((r) => setTimeout(r, 500));
|
|
62
76
|
let content = "";
|
|
63
|
-
try {
|
|
77
|
+
try {
|
|
78
|
+
content = readFileSync(daemonLog, "utf8").slice(logOffset);
|
|
79
|
+
} catch {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
64
82
|
|
|
65
83
|
for (const name of expecting) {
|
|
66
84
|
if (ready.has(name)) continue;
|
|
@@ -125,7 +143,8 @@ switch (command) {
|
|
|
125
143
|
}
|
|
126
144
|
|
|
127
145
|
case "restart": {
|
|
128
|
-
const { isServiceInstalled, restartService } =
|
|
146
|
+
const { isServiceInstalled, restartService } =
|
|
147
|
+
await import("../commands/service");
|
|
129
148
|
if (isServiceInstalled()) {
|
|
130
149
|
// Service-aware: unload (stops KeepAlive respawn), kill, then reload
|
|
131
150
|
await restartService();
|
|
@@ -134,7 +153,9 @@ switch (command) {
|
|
|
134
153
|
startDaemon();
|
|
135
154
|
}
|
|
136
155
|
const restartPid = readPid();
|
|
137
|
-
console.log(
|
|
156
|
+
console.log(
|
|
157
|
+
`nia restarting${restartPid ? ` (pid: ${restartPid})` : ""}...`,
|
|
158
|
+
);
|
|
138
159
|
await awaitStartup();
|
|
139
160
|
console.log("nia restarted");
|
|
140
161
|
break;
|
|
@@ -145,7 +166,12 @@ switch (command) {
|
|
|
145
166
|
if (prompt) {
|
|
146
167
|
const { createChatEngine } = await import("../chat/engine");
|
|
147
168
|
const { getMcpServers } = await import("../mcp");
|
|
148
|
-
const {
|
|
169
|
+
const {
|
|
170
|
+
DIM,
|
|
171
|
+
RESET: RST,
|
|
172
|
+
CLEAR_LINE,
|
|
173
|
+
SPINNER: FRAMES,
|
|
174
|
+
} = await import("../utils/cli");
|
|
149
175
|
let frame = 0;
|
|
150
176
|
let statusText = "thinking";
|
|
151
177
|
let spinTimer: ReturnType<typeof setInterval> | null = null;
|
|
@@ -153,31 +179,47 @@ switch (command) {
|
|
|
153
179
|
let streaming = false;
|
|
154
180
|
|
|
155
181
|
const renderSpinner = () => {
|
|
156
|
-
process.stderr.write(
|
|
182
|
+
process.stderr.write(
|
|
183
|
+
`${CLEAR_LINE}${DIM} ${FRAMES[frame]} ${statusText}${RST}`,
|
|
184
|
+
);
|
|
157
185
|
frame = (frame + 1) % FRAMES.length;
|
|
158
186
|
};
|
|
159
187
|
|
|
160
188
|
await withDb(async () => {
|
|
161
|
-
const engine = await createChatEngine({
|
|
189
|
+
const engine = await createChatEngine({
|
|
190
|
+
room: "cli-run",
|
|
191
|
+
channel: "terminal",
|
|
192
|
+
resume: false,
|
|
193
|
+
mcpServers: getMcpServers(),
|
|
194
|
+
});
|
|
162
195
|
spinTimer = setInterval(renderSpinner, 80);
|
|
163
196
|
renderSpinner();
|
|
164
197
|
|
|
165
198
|
const { result, costUsd, turns } = await engine.send(prompt, {
|
|
166
199
|
onStream(textSoFar) {
|
|
167
200
|
if (!streaming) {
|
|
168
|
-
if (spinTimer) {
|
|
201
|
+
if (spinTimer) {
|
|
202
|
+
clearInterval(spinTimer);
|
|
203
|
+
spinTimer = null;
|
|
204
|
+
}
|
|
169
205
|
process.stderr.write("\x1b[2K\r");
|
|
170
206
|
streaming = true;
|
|
171
207
|
}
|
|
172
208
|
const chunk = textSoFar.slice(streamedLen);
|
|
173
|
-
if (chunk) {
|
|
209
|
+
if (chunk) {
|
|
210
|
+
process.stdout.write(chunk);
|
|
211
|
+
streamedLen = textSoFar.length;
|
|
212
|
+
}
|
|
174
213
|
},
|
|
175
214
|
onActivity(text) {
|
|
176
215
|
if (!streaming) statusText = text;
|
|
177
216
|
},
|
|
178
217
|
});
|
|
179
218
|
|
|
180
|
-
if (spinTimer) {
|
|
219
|
+
if (spinTimer) {
|
|
220
|
+
clearInterval(spinTimer);
|
|
221
|
+
spinTimer = null;
|
|
222
|
+
}
|
|
181
223
|
|
|
182
224
|
if (!streaming && result.trim()) {
|
|
183
225
|
process.stderr.write("\x1b[2K\r");
|
|
@@ -190,13 +232,15 @@ switch (command) {
|
|
|
190
232
|
}
|
|
191
233
|
|
|
192
234
|
const costStr = costUsd > 0 ? `$${costUsd.toFixed(4)}` : "";
|
|
193
|
-
const turnsStr =
|
|
235
|
+
const turnsStr =
|
|
236
|
+
turns > 0 ? `${turns} turn${turns !== 1 ? "s" : ""}` : "";
|
|
194
237
|
const meta = [costStr, turnsStr].filter(Boolean).join(" · ");
|
|
195
238
|
if (meta) process.stderr.write(`\n${DIM}${meta}${RST}`);
|
|
196
239
|
process.stdout.write("\n");
|
|
197
240
|
|
|
198
241
|
engine.close();
|
|
199
242
|
});
|
|
243
|
+
process.exit(0);
|
|
200
244
|
} else {
|
|
201
245
|
await runDaemon();
|
|
202
246
|
}
|
|
@@ -235,8 +279,13 @@ switch (command) {
|
|
|
235
279
|
const time = localTime(new Date(m.createdAt));
|
|
236
280
|
const prefix = m.sender === "user" ? "you" : m.sender;
|
|
237
281
|
const roomTag = room ? "" : `[${m.room}] `;
|
|
238
|
-
const snippet =
|
|
239
|
-
|
|
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
|
+
);
|
|
240
289
|
}
|
|
241
290
|
}
|
|
242
291
|
});
|
|
@@ -253,16 +302,25 @@ switch (command) {
|
|
|
253
302
|
const follow = logArgs.includes("-f") || logArgs.includes("--follow");
|
|
254
303
|
// --channel <name> filters logs by channel/component via grep
|
|
255
304
|
const chIdx = logArgs.indexOf("--channel");
|
|
256
|
-
const channelFilter =
|
|
305
|
+
const channelFilter =
|
|
306
|
+
chIdx !== -1 && logArgs[chIdx + 1] ? logArgs[chIdx + 1] : null;
|
|
257
307
|
|
|
258
308
|
if (channelFilter) {
|
|
259
309
|
// Pipe through grep to filter by channel name in structured logs
|
|
260
|
-
const tailArgs = follow
|
|
261
|
-
|
|
262
|
-
|
|
310
|
+
const tailArgs = follow
|
|
311
|
+
? ["tail", "-f", daemonLog]
|
|
312
|
+
: ["tail", "-200", daemonLog];
|
|
313
|
+
const tail = Bun.spawn(tailArgs, {
|
|
314
|
+
stdio: ["ignore", "pipe", "inherit"],
|
|
315
|
+
});
|
|
316
|
+
const grep = Bun.spawn(["grep", "-i", channelFilter], {
|
|
317
|
+
stdio: [tail.stdout, "inherit", "inherit"],
|
|
318
|
+
});
|
|
263
319
|
await grep.exited;
|
|
264
320
|
} else {
|
|
265
|
-
const args = follow
|
|
321
|
+
const args = follow
|
|
322
|
+
? ["tail", "-f", daemonLog]
|
|
323
|
+
: ["tail", "-50", daemonLog];
|
|
266
324
|
const proc = Bun.spawn(args, { stdio: ["ignore", "inherit", "inherit"] });
|
|
267
325
|
await proc.exited;
|
|
268
326
|
}
|
|
@@ -276,13 +334,15 @@ switch (command) {
|
|
|
276
334
|
|
|
277
335
|
case "chat": {
|
|
278
336
|
const chatArgs = process.argv.slice(3);
|
|
279
|
-
const mode =
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
337
|
+
const mode =
|
|
338
|
+
chatArgs.includes("--continue") || chatArgs.includes("-c")
|
|
339
|
+
? ("continue" as const)
|
|
340
|
+
: chatArgs.includes("--resume") || chatArgs.includes("-r")
|
|
341
|
+
? ("pick" as const)
|
|
342
|
+
: ("new" as const);
|
|
284
343
|
const chIdx = chatArgs.indexOf("--channel");
|
|
285
|
-
const simChannel =
|
|
344
|
+
const simChannel =
|
|
345
|
+
chIdx !== -1 && chatArgs[chIdx + 1] ? chatArgs[chIdx + 1] : undefined;
|
|
286
346
|
await startRepl(mode, simChannel);
|
|
287
347
|
break;
|
|
288
348
|
}
|
|
@@ -300,7 +360,9 @@ switch (command) {
|
|
|
300
360
|
skills = skills.filter((s) => s.source === filter);
|
|
301
361
|
}
|
|
302
362
|
if (skills.length === 0) {
|
|
303
|
-
console.log(
|
|
363
|
+
console.log(
|
|
364
|
+
filter ? `No skills found in "${filter}".` : "No skills found.",
|
|
365
|
+
);
|
|
304
366
|
} else {
|
|
305
367
|
for (const s of skills) {
|
|
306
368
|
const tag = filter ? "" : ` [${s.source}]`;
|
|
@@ -354,8 +416,12 @@ switch (command) {
|
|
|
354
416
|
const parts = configKey.split(".");
|
|
355
417
|
let val: unknown = raw;
|
|
356
418
|
for (const p of parts) {
|
|
357
|
-
if (val && typeof val === "object")
|
|
358
|
-
|
|
419
|
+
if (val && typeof val === "object")
|
|
420
|
+
val = (val as Record<string, unknown>)[p];
|
|
421
|
+
else {
|
|
422
|
+
val = undefined;
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
359
425
|
}
|
|
360
426
|
if (val === undefined) {
|
|
361
427
|
console.log(`${configKey}: (not set)`);
|
|
@@ -389,7 +455,9 @@ switch (command) {
|
|
|
389
455
|
process.kill(pid, "SIGHUP");
|
|
390
456
|
console.log(`channels ${enabled ? "enabled" : "disabled"}`);
|
|
391
457
|
} else {
|
|
392
|
-
console.log(
|
|
458
|
+
console.log(
|
|
459
|
+
`channels ${enabled ? "enabled" : "disabled"} — start nia to apply`,
|
|
460
|
+
);
|
|
393
461
|
}
|
|
394
462
|
} else {
|
|
395
463
|
console.log(`channels: ${getConfig().channels.enabled ? "on" : "off"}`);
|
|
@@ -404,8 +472,11 @@ switch (command) {
|
|
|
404
472
|
}
|
|
405
473
|
|
|
406
474
|
case "test": {
|
|
407
|
-
const verbose =
|
|
408
|
-
|
|
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");
|
|
409
480
|
const proc = Bun.spawn(["bun", "test", ...extraArgs], {
|
|
410
481
|
stdio: ["ignore", "pipe", "pipe"],
|
|
411
482
|
cwd: import.meta.dir + "/../..",
|
|
@@ -423,7 +494,12 @@ switch (command) {
|
|
|
423
494
|
process.stdout.write(output);
|
|
424
495
|
} else {
|
|
425
496
|
for (const line of output.split("\n")) {
|
|
426
|
-
if (
|
|
497
|
+
if (
|
|
498
|
+
/^\s*\d+ pass/.test(line) ||
|
|
499
|
+
/^\s*\d+ fail/.test(line) ||
|
|
500
|
+
/^Ran \d+ tests/.test(line) ||
|
|
501
|
+
/expect\(\) calls/.test(line)
|
|
502
|
+
) {
|
|
427
503
|
console.log(line);
|
|
428
504
|
} else if (/^✗|FAIL|error:/i.test(line.trim())) {
|
|
429
505
|
console.log(line);
|
|
@@ -460,13 +536,18 @@ switch (command) {
|
|
|
460
536
|
console.log(`⚠ backup skipped: ${errMsg(err)}`);
|
|
461
537
|
}
|
|
462
538
|
console.log("Updating...");
|
|
463
|
-
const install = Bun.spawn(["npm", "i", "-g", "niahere@latest"], {
|
|
539
|
+
const install = Bun.spawn(["npm", "i", "-g", "niahere@latest"], {
|
|
540
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
541
|
+
});
|
|
464
542
|
const installExit = await install.exited;
|
|
465
543
|
if (installExit !== 0) {
|
|
466
544
|
fail("Update failed.");
|
|
467
545
|
}
|
|
468
546
|
// Get new version
|
|
469
|
-
const check = Bun.spawn(["npm", "view", "niahere", "version"], {
|
|
547
|
+
const check = Bun.spawn(["npm", "view", "niahere", "version"], {
|
|
548
|
+
stdout: "pipe",
|
|
549
|
+
stderr: "pipe",
|
|
550
|
+
});
|
|
470
551
|
const newVersion = (await new Response(check.stdout).text()).trim();
|
|
471
552
|
await check.exited;
|
|
472
553
|
if (newVersion === currentVersion) {
|
|
@@ -475,7 +556,8 @@ switch (command) {
|
|
|
475
556
|
console.log(`Updated: v${currentVersion} → v${newVersion}`);
|
|
476
557
|
if (isRunning()) {
|
|
477
558
|
console.log("Restarting daemon...");
|
|
478
|
-
const { isServiceInstalled, restartService } =
|
|
559
|
+
const { isServiceInstalled, restartService } =
|
|
560
|
+
await import("../commands/service");
|
|
479
561
|
if (isServiceInstalled()) {
|
|
480
562
|
await restartService();
|
|
481
563
|
} else {
|
|
@@ -540,7 +622,11 @@ System:
|
|
|
540
622
|
|
|
541
623
|
console.log(HELP);
|
|
542
624
|
// Unknown command → exit 1, help/no command → exit 0
|
|
543
|
-
const isHelp =
|
|
625
|
+
const isHelp =
|
|
626
|
+
!command ||
|
|
627
|
+
command === "help" ||
|
|
628
|
+
command === "--help" ||
|
|
629
|
+
command === "-h";
|
|
544
630
|
if (!isHelp) console.error(`\nUnknown command: ${command}`);
|
|
545
631
|
process.exit(isHelp ? 0 : 1);
|
|
546
632
|
}
|