@coralai/nps-cli 0.1.0

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.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +459 -0
  3. package/dist/audit-log.d.ts +26 -0
  4. package/dist/audit-log.d.ts.map +1 -0
  5. package/dist/audit-log.js +120 -0
  6. package/dist/audit-log.js.map +1 -0
  7. package/dist/bindings/bindings-loader.d.ts +19 -0
  8. package/dist/bindings/bindings-loader.d.ts.map +1 -0
  9. package/dist/bindings/bindings-loader.js +84 -0
  10. package/dist/bindings/bindings-loader.js.map +1 -0
  11. package/dist/cli/agent-cmd.d.ts +2 -0
  12. package/dist/cli/agent-cmd.d.ts.map +1 -0
  13. package/dist/cli/agent-cmd.js +125 -0
  14. package/dist/cli/agent-cmd.js.map +1 -0
  15. package/dist/cli/bind-cmd.d.ts +2 -0
  16. package/dist/cli/bind-cmd.d.ts.map +1 -0
  17. package/dist/cli/bind-cmd.js +127 -0
  18. package/dist/cli/bind-cmd.js.map +1 -0
  19. package/dist/cli/daemon-cmd.d.ts +4 -0
  20. package/dist/cli/daemon-cmd.d.ts.map +1 -0
  21. package/dist/cli/daemon-cmd.js +137 -0
  22. package/dist/cli/daemon-cmd.js.map +1 -0
  23. package/dist/cli/index.d.ts +2 -0
  24. package/dist/cli/index.d.ts.map +1 -0
  25. package/dist/cli/index.js +62 -0
  26. package/dist/cli/index.js.map +1 -0
  27. package/dist/config/loader.d.ts +5 -0
  28. package/dist/config/loader.d.ts.map +1 -0
  29. package/dist/config/loader.js +54 -0
  30. package/dist/config/loader.js.map +1 -0
  31. package/dist/config/paths.d.ts +14 -0
  32. package/dist/config/paths.d.ts.map +1 -0
  33. package/dist/config/paths.js +27 -0
  34. package/dist/config/paths.js.map +1 -0
  35. package/dist/config/schema.d.ts +48 -0
  36. package/dist/config/schema.d.ts.map +1 -0
  37. package/dist/config/schema.js +52 -0
  38. package/dist/config/schema.js.map +1 -0
  39. package/dist/daemon/control-client.d.ts +2 -0
  40. package/dist/daemon/control-client.d.ts.map +1 -0
  41. package/dist/daemon/control-client.js +51 -0
  42. package/dist/daemon/control-client.js.map +1 -0
  43. package/dist/daemon/control-socket.d.ts +19 -0
  44. package/dist/daemon/control-socket.d.ts.map +1 -0
  45. package/dist/daemon/control-socket.js +192 -0
  46. package/dist/daemon/control-socket.js.map +1 -0
  47. package/dist/daemon/daemon.d.ts +20 -0
  48. package/dist/daemon/daemon.d.ts.map +1 -0
  49. package/dist/daemon/daemon.js +166 -0
  50. package/dist/daemon/daemon.js.map +1 -0
  51. package/dist/dispatch/dispatch-pipeline.d.ts +39 -0
  52. package/dist/dispatch/dispatch-pipeline.d.ts.map +1 -0
  53. package/dist/dispatch/dispatch-pipeline.js +219 -0
  54. package/dist/dispatch/dispatch-pipeline.js.map +1 -0
  55. package/dist/dispatch/slash-commands.d.ts +35 -0
  56. package/dist/dispatch/slash-commands.d.ts.map +1 -0
  57. package/dist/dispatch/slash-commands.js +67 -0
  58. package/dist/dispatch/slash-commands.js.map +1 -0
  59. package/dist/errors.d.ts +22 -0
  60. package/dist/errors.d.ts.map +1 -0
  61. package/dist/errors.js +17 -0
  62. package/dist/errors.js.map +1 -0
  63. package/dist/gateway/matrix-gateway.d.ts +24 -0
  64. package/dist/gateway/matrix-gateway.d.ts.map +1 -0
  65. package/dist/gateway/matrix-gateway.js +166 -0
  66. package/dist/gateway/matrix-gateway.js.map +1 -0
  67. package/dist/logger.d.ts +13 -0
  68. package/dist/logger.d.ts.map +1 -0
  69. package/dist/logger.js +19 -0
  70. package/dist/logger.js.map +1 -0
  71. package/dist/main.d.ts +3 -0
  72. package/dist/main.d.ts.map +1 -0
  73. package/dist/main.js +7 -0
  74. package/dist/main.js.map +1 -0
  75. package/dist/profile/profile-registry.d.ts +24 -0
  76. package/dist/profile/profile-registry.d.ts.map +1 -0
  77. package/dist/profile/profile-registry.js +114 -0
  78. package/dist/profile/profile-registry.js.map +1 -0
  79. package/dist/runtime-check.d.ts +18 -0
  80. package/dist/runtime-check.d.ts.map +1 -0
  81. package/dist/runtime-check.js +62 -0
  82. package/dist/runtime-check.js.map +1 -0
  83. package/dist/session/compat-hash.d.ts +21 -0
  84. package/dist/session/compat-hash.d.ts.map +1 -0
  85. package/dist/session/compat-hash.js +115 -0
  86. package/dist/session/compat-hash.js.map +1 -0
  87. package/dist/session/session-store.d.ts +29 -0
  88. package/dist/session/session-store.d.ts.map +1 -0
  89. package/dist/session/session-store.js +123 -0
  90. package/dist/session/session-store.js.map +1 -0
  91. package/dist/version.d.ts +2 -0
  92. package/dist/version.d.ts.map +1 -0
  93. package/dist/version.js +2 -0
  94. package/dist/version.js.map +1 -0
  95. package/package.json +59 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Coral / wykj
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,459 @@
1
+ # nps-cli — Vision Doc (rev 5, aligned with CEO plan)
2
+
3
+ > **Status:** v0.1.0-alpha.0 skeleton landed; engineering plan rev 5 ready to implement.
4
+ > Last updated 2026-05-14 by Coral + Claude after CEO + Eng review cycles.
5
+
6
+ > **Authoritative engineering plan:**
7
+ > `~/.gstack/projects/coral-jarvis-skills/ceo-plans/2026-05-14-nps-cli-v0-1.md` (rev 5)
8
+ >
9
+ > This README captures the **what + why**. The CEO plan captures the **how + effort**.
10
+
11
+ ---
12
+
13
+ ## 1. What is nps-cli
14
+
15
+ **nps** is a **Linux-only local daemon** that hosts ephemeral Claude Code agent processes accessible via IM (Matrix DM in v0.1). Each agent is defined by a **profile** — a directory with its own personality, skills, and an external workspace pointer. Per message, nps spawns a Claude binary, dispatches the message, persists the result, and stops — no long-lived per-channel cache (codex F2 design lesson from rev 3 → rev 4 pivot).
16
+
17
+ Think of it as:
18
+
19
+ - **Discord-bot framework**, but: agent-first, ephemeral-per-message, project-skill-scoped.
20
+ - **openclaw**, but: agent profile abstraction with workspace routing + safe session resume.
21
+ - **sps-cli**, but: NOT a worker pipeline. Conversational, IM-driven.
22
+
23
+ The core runtime primitive is **`@coralai/claude-code-agent`** (Phase 1 package, v0.2+). nps-cli is the first true consumer of its public `ClaudeCodeAgent` class.
24
+
25
+ ---
26
+
27
+ ## 2. Why nps-cli (the gap)
28
+
29
+ After Phase 1, `@coralai/claude-code-agent` exists as a clean library. Beyond proving its API, the actual product value:
30
+
31
+ - Coral wants a **personal AI presence** reachable through Matrix DM, backed by skills he can iterate locally.
32
+ - Different conversational contexts (work, finance, study) deserve **different agents** with their own personalities and skills.
33
+ - sps-cli is a **build pipeline**, not a conversational front-end. Two different products with one shared runtime.
34
+
35
+ **v0.1 is a validation vehicle, not a finished product.** It proves three things before any platform feature is built on top:
36
+
37
+ 1. **Workspace routing** — agent persona/skills at A, task workspace at B, never conflated.
38
+ 2. **Safe session resume** — `resumeSession` works AND has compatibility invalidation done BEFORE resume.
39
+ 3. **One real transport loop** — Matrix DM in, reply out, no crash, no leaked process, with event-id dedupe.
40
+
41
+ Everything not directly serving those goals is deferred.
42
+
43
+ ---
44
+
45
+ ## 3. Relationship to other projects
46
+
47
+ ```
48
+ ┌───────────────────────────────────────────────────────────────┐
49
+ │ nps-cli │
50
+ │ (this project — Linux daemon + Matrix DM gateway + profiles) │
51
+ └─────────────────────────────┬─────────────────────────────────┘
52
+
53
+
54
+ @coralai/claude-code-agent ^0.2.0 (Phase 1 package)
55
+ • v0.1: start / stop / prompt / cancelAll
56
+ • v0.2: + start({ resumeSession }) ← nps prereq, shipped
57
+
58
+
59
+ @agentclientprotocol/sdk ^0.21
60
+
61
+
62
+ claude-agent-acp shim 0.33.1
63
+
64
+
65
+ claude binary (OAuth subscription)
66
+ ```
67
+
68
+ - **sps-cli** stays a separate consumer of `@coralai/claude-code-agent`. nps-cli is a sibling, not a fork.
69
+ - **Multi-vendor LLM**: NOT via an `LLMBackend` abstraction inside nps-cli (decision D2). Instead, profiles can point `ANTHROPIC_BASE_URL` at an API-compat proxy (e.g. CLIProxyAPI, litellm) — the claude binary still thinks it's talking to Anthropic. Backend abstraction = zero code in nps-cli.
70
+
71
+ ---
72
+
73
+ ## 4. Platform target
74
+
75
+ **Linux only in v0.1.** The implementation depends on systemd user units, journald for log aggregation, `flock` for file locking, Unix domain sockets, and `loginctl enable-linger` for user-mode service supervision. macOS launchd and Windows support are deferred to v0.1.x.
76
+
77
+ ---
78
+
79
+ ## 5. User scenario (concrete)
80
+
81
+ Coral runs `nps daemon start` as a systemd user service. He has three agent profiles:
82
+
83
+ ```
84
+ ~/.nps/
85
+ ├── config.yaml # global: Matrix homeserver, admin user ids
86
+ ├── bindings.yaml # channel → profile mapping (CLI-managed in v0.1)
87
+ └── agents/
88
+ ├── work-companion/
89
+ │ ├── nps.yaml # workspace: /home/coral/jarvis-skills, env: ANTHROPIC_BASE_URL=...
90
+ │ ├── CLAUDE.md # "你是 Coral 的工作助手..."
91
+ │ └── .claude/skills/
92
+ │ ├── code-review/SKILL.md
93
+ │ └── architecture-sketch/SKILL.md
94
+ ├── personal-finance/
95
+ │ ├── nps.yaml # workspace: /home/coral/.finance, env: ...
96
+ │ ├── CLAUDE.md
97
+ │ └── .claude/skills/...
98
+ └── study-buddy/
99
+ └── ...
100
+ ```
101
+
102
+ In Matrix DM with `@nps-work-companion:matrix.org`, Coral types "review the latest PR on jarvis-skills." nps daemon:
103
+
104
+ 1. Receives the message (Matrix event with `event_id`).
105
+ 2. Checks event-id dedupe (drop if already processed).
106
+ 3. Looks up channel binding → `work-companion`.
107
+ 4. Computes `compatHash` from profile artifacts.
108
+ 5. Checks stored hash; if mismatch → drop stored sessionId (fresh start); else use it for resume.
109
+ 6. Spawns `ClaudeCodeAgent({ cwd: /home/coral/jarvis-skills, env: profile.env })`.
110
+ 7. `agent.start({ resumeSession: validId })` — or no resume on fresh start.
111
+ 8. `agent.prompt(text)` — claude does its work in the actual repo.
112
+ 9. Atomically persists `{ sessionId, compatHash, lastEventId }`.
113
+ 10. `agent.stop()` — process gone, baseline RAM.
114
+ 11. Sends reply (Matrix `m.text` final, no streaming in v0.1).
115
+
116
+ Tomorrow Coral restarts the daemon. Next message in the DM resumes from the stored sessionId because `compatHash` matches (no profile edits).
117
+
118
+ ---
119
+
120
+ ## 6. Architecture
121
+
122
+ ### 6.1 Process model
123
+
124
+ Single Node.js daemon on Linux. Spawns a fresh Claude binary per message, then exits.
125
+
126
+ ```
127
+ nps daemon (one Node process)
128
+ ├── ProfileRegistry — scan + chokidar watch ~/.nps/agents/
129
+ ├── BindingsLoader — atomic read of ~/.nps/bindings.yaml
130
+ ├── DispatchPipeline — dedupe → lookup → compat check → resume/fresh → prompt → persist → stop → send
131
+ ├── MatrixGateway — DM-only client (unencrypted, final-only replies, event-id dedupe)
132
+ ├── SessionStore — atomic-write JSON per channel: { sessionId, compatHash, lastEventId }
133
+ ├── ControlSocket — JSON-RPC 2.0 on Unix socket + PID file + stale-socket detection
134
+ └── pino logger — stdout → journald
135
+ ```
136
+
137
+ **Notably absent** (deferred to v0.2+ if dogfooding shows need):
138
+ - No AgentSupervisor (no per-channel cache)
139
+ - No idle eviction (nothing to evict)
140
+ - No health auto-restart of agents (ephemeral processes can't hang)
141
+ - No LLMBackend abstraction (D2)
142
+
143
+ ### 6.2 DispatchPipeline — corrected order (codex F1, F11)
144
+
145
+ ```
146
+ on MatrixGateway emits Message{ channelId, eventId, text, sender }:
147
+ 1. SessionStore.hasSeenEvent(eventId)? → silent drop on duplicate
148
+ 2. BindingsLoader.lookup(channelId) → profile → /help reply on miss
149
+ 3. Compute currentCompatHash from profile artifacts (spec below)
150
+ 4. SessionStore.get(channelId) → { storedHash?, sessionId? }
151
+ 5. ── COMPAT CHECK BEFORE RESUME ── ← codex F1: NOT after
152
+ if storedHash !== currentCompatHash:
153
+ resumeId = undefined # fresh start
154
+ IM reply hint: "session reset (config changed)"
155
+ else:
156
+ resumeId = sessionId
157
+ 6. agent = new ClaudeCodeAgent({ cwd: profile.workspace, env: profile.env, ... })
158
+ 7. await agent.start({ resumeSession: resumeId })
159
+ 8. result = await agent.prompt(text)
160
+ 9. ── PERSIST BEFORE SEND ── ← codex F11
161
+ await SessionStore.atomicWrite(channelId, {
162
+ sessionId: result.sessionId,
163
+ compatHash: currentCompatHash,
164
+ lastEventId: eventId,
165
+ })
166
+ 10. await agent.stop()
167
+ 11. await gateway.sendReply(channelId, result.text) (retry once on transient failure)
168
+ ```
169
+
170
+ ### 6.3 compatHash specification (codex F3, F4)
171
+
172
+ ```
173
+ Inputs (in canonical order, each ending with separator "\n--SEP--\n"):
174
+ 1. profile.yaml — yaml.dump with sorted keys, LF line endings
175
+ 2. CLAUDE.md — utf-8, LF line endings
176
+ 3. skills/*.SKILL.md — sorted by lexicographic filename
177
+ 4. claude-code-agent npm version
178
+ 5. claude binary version — `claude --version` cached at daemon boot
179
+ 6. claude-agent-acp version — `claude-agent-acp --version` cached at boot
180
+
181
+ Algorithm:
182
+ sha256(canonical_bytes)
183
+
184
+ Edge cases:
185
+ - Missing file → include literal "<MISSING>" + path marker
186
+ - Symlinks → resolve to target's content
187
+ - File read error → throw COMPAT_HASH_FAILED, do NOT silently fresh-start
188
+ - Binary missing on boot → daemon refuses to start with actionable error
189
+
190
+ NOT in hash (deliberately):
191
+ - workspace directory contents (workspace is task layer, not identity layer)
192
+ - .claude/settings.json (per-run preference, not agent identity)
193
+ ```
194
+
195
+ ### 6.4 Atomic write contracts (codex F7, F8)
196
+
197
+ **bindings.yaml** (CLI-only writer):
198
+ 1. CLI acquires `~/.nps/.bindings.lock` (file lock).
199
+ 2. Reads current bindings.yaml.
200
+ 3. Mutates in-memory.
201
+ 4. Writes to `bindings.yaml.tmp` mode 0600.
202
+ 5. `rename(2)` for atomic replace.
203
+ 6. Releases lock.
204
+
205
+ Daemon reader retries on transient `EBUSY` (max 3 attempts).
206
+
207
+ **SessionStore** (daemon writer):
208
+ - File per channel: `~/.nps/state/<channelId>.json` mode 0600.
209
+ - Write → `.tmp` → `fsync` → `rename`.
210
+ - Corrupt JSON on read → log warn + treat as empty (fresh start).
211
+
212
+ **Audit log**:
213
+ - `~/.nps/audit.jsonl` mode 0600, schema `{ v: 1, ts, sender, transport, channel, command_or_event, decision, profile? }`.
214
+ - Rotation at 50MB → atomic rename to `.0`; shift `.0..3` up; delete `.4`. Retention 5 × 50MB = 250MB max.
215
+ - ENOSPC → log `[CRITICAL] audit log unavailable` via pino, daemon continues.
216
+
217
+ ---
218
+
219
+ ## 7. Configuration model
220
+
221
+ ### 7.1 Global config (`~/.nps/config.yaml`)
222
+
223
+ ```yaml
224
+ daemon:
225
+ controlSocket: ~/.nps/daemon.sock
226
+ pidFile: ~/.nps/daemon.pid
227
+ logLevel: info
228
+ adminUserIds:
229
+ - matrix:@coral:matrix.org
230
+
231
+ im:
232
+ matrix:
233
+ homeserver: https://matrix.org
234
+ user: '@coral:matrix.org'
235
+ accessToken: ${MATRIX_TOKEN} # env var indirection supported
236
+ ```
237
+
238
+ ### 7.2 Per-agent profile (`~/.nps/agents/<name>/`)
239
+
240
+ ```
241
+ <name>/
242
+ ├── nps.yaml # profile metadata (workspace, env, recording, idle settings)
243
+ ├── CLAUDE.md # personality / rules (read by claude binary natively)
244
+ └── .claude/skills/
245
+ └── <skill>/SKILL.md
246
+ ```
247
+
248
+ `nps.yaml` schema:
249
+
250
+ ```yaml
251
+ workspace: /home/coral/jarvis-skills # SINGULAR — exactly one path per profile (D3=A)
252
+ # claude binary cwd; need multiple? create multiple profiles.
253
+
254
+ env: # passed through to claude binary via AgentOptions.env
255
+ ANTHROPIC_BASE_URL: https://my-proxy.example/anthropic # optional API-compat proxy
256
+ # ANTHROPIC_API_KEY: ${MY_KEY} # optional; omit to use OAuth subscription
257
+
258
+ recording: ~/.nps/recordings/work-companion.jsonl # optional, ClaudeCodeAgent recording
259
+
260
+ permissionMode: auto # auto | readonly | manual
261
+ ```
262
+
263
+ **Skill loading is free**: claude binary reads `cwd/CLAUDE.md` + `cwd/.claude/skills/` natively. But here `cwd = workspace` (task layer), so we set `CLAUDE.md` + skills INSIDE the profile dir AND we set workspace as cwd. Solution: nps daemon **copies / symlinks** profile's CLAUDE.md + .claude/skills/ into the workspace before spawn? Or: claude binary supports a separate `--system-prompt-file` flag? **TBD in M-WS implementation.**
264
+
265
+ ### 7.3 Channel bindings (`~/.nps/bindings.yaml`)
266
+
267
+ ```yaml
268
+ matrix:
269
+ '!abc123:matrix.org': work-companion
270
+ '!def456:matrix.org': personal-finance
271
+ ```
272
+
273
+ **CLI-managed only in v0.1** (codex F5 dodge — daemon doesn't write):
274
+ ```
275
+ nps bind add <channel-id> <profile>
276
+ nps bind remove <channel-id>
277
+ nps bind list
278
+ ```
279
+
280
+ IM `/bind /unbind /persona /reset` deferred to v0.1.x.
281
+
282
+ ---
283
+
284
+ ## 8. v0.1 milestones
285
+
286
+ See CEO plan rev 5 for "done when" criteria + effort estimates. Summary:
287
+
288
+ ```
289
+ PROTOTYPE GATES (parallel, can run before main impl):
290
+ M0 Lifecycle gate — 3 concurrent claude sessions, distinct sessionIds, no auth collision (1-2d)
291
+ M0a Resume gate — resume sessionId, history retained, cleanly stop (0.5d)
292
+ M5a Matrix transport spike — DM detect, encrypted-room refuse, send msg (0.5-1d)
293
+ M-WS Workspace routing — singular workspace per profile, agent gets correct cwd (1d)
294
+
295
+ LANE A (daemon core, sequential):
296
+ M1 Daemon + control socket + JSON-RPC + PID file (2-3d)
297
+ M2 ProfileRegistry (scan + chokidar watch) (1d)
298
+ M3 BindingsLoader (atomic read + retry-on-EBUSY) (1d)
299
+ M4 DispatchPipeline (corrected order, codex F1+F11) (2d)
300
+ M6 SessionStore + compatHash + lastEventId dedupe (2d)
301
+
302
+ LANE B (gateway, after M5a + M6):
303
+ M5b Matrix gateway production (final-only, dedupe, reconnect backoff) (2-3d)
304
+
305
+ LANE C (CLI + ops, parallel with A):
306
+ M7 nps CLI (agent/daemon/bind subcommands) (2d)
307
+ M8 Slash commands (/help open, /status admin only) (1d)
308
+ M10 Boot runtime check (claude + shim presence) (0.5d)
309
+ M11 systemd user unit + install-service (1-1.5d)
310
+ M12 Audit log (rotation + 0600 + schema v=1) (1.5d)
311
+ M13 Structured logs (pino → journald) (0.5d)
312
+ ```
313
+
314
+ **Effort total**: 18-23 human-days + 20% buffer = **22-28 human-days** (3-4.5 CC-days, 3-5 weeks calendar).
315
+
316
+ ### Slash command contract (minimal — D6=A, D7-resolved)
317
+
318
+ | Command | Args | Side effect | Auth |
319
+ |---------|------|-------------|------|
320
+ | `/help` | — | Reply with command list + binding info | **Open** (any sender, including unbound DM first message) |
321
+ | `/status` | — | Reply with profile, lastActivity, sessionId presence | **Admin only** |
322
+
323
+ `/bind /unbind /reset /persona` → CLI only in v0.1.
324
+
325
+ ---
326
+
327
+ ## 9. Hard prerequisite
328
+
329
+ **`@coralai/claude-code-agent` v0.2** — **shipped 2026-05-14**. Provides `start({ resumeSession?: string })`. nps-cli depends on `^0.2.0`.
330
+
331
+ What we use:
332
+ - `start({ resumeSession })` — public option, calls ACP `loadSession` internally
333
+ - `prompt(text)` + `stop()` — v0.1.0 unchanged
334
+
335
+ What we DON'T need from claude-code-agent v0.2:
336
+ - Public `loadSession` method (codex F9 clarified — internal only)
337
+ - Replay protection idempotency keys (shim replay is informational, no tool re-execution observed)
338
+ - `fork()` (still v1.1)
339
+
340
+ ---
341
+
342
+ ## 10. Risks & posture
343
+
344
+ ### R1 — Matrix protocol complexity
345
+ E2EE adds a fully separate code path. **v0.1 ships with E2EE disabled** — encrypted rooms refused at gateway with explicit error. Multi-participant rooms also refused (v0.1 is DM-only).
346
+
347
+ ### R2 — ~~Per-channel memory pressure~~ — **DISSOLVED**
348
+ Ephemeral spawn-per-message means baseline daemon RAM is ~50MB; each turn briefly spikes ~200MB during claude binary's lifetime, then back to baseline. No N-concurrent-claude-processes problem.
349
+
350
+ ### R3 — Single-user assumption
351
+ v0.1 is single-user: `daemon.adminUserIds` lists Coral's transport-prefixed IDs (e.g. `matrix:@coral:matrix.org`). Non-admin senders in a bound DM get rejected with audit log entry. Multi-user with roles → v0.1.x.
352
+
353
+ ### R4 — Skill conflicts across profiles
354
+ Non-risk (isolated profile dirs).
355
+
356
+ ### R5 — ~~LLM backend over-design~~ — **dissolved**
357
+ D2: no LLMBackend abstraction. Multi-vendor via `ANTHROPIC_BASE_URL` proxy at deployment, not at code level.
358
+
359
+ ### Cold-start UX (D1=A)
360
+ Ephemeral mode: 5-10s per message before first token. Gateway sends Matrix `m.typing` indicator during. Coral's IM rate (~human-paced) makes this acceptable. If dogfooding reveals pain, v0.2 may add a warm hot-pool (NOT residency — pool-of-1).
361
+
362
+ ### M0 prototype gate is the hinge
363
+ Concurrent claude binaries on a single OAuth subscription must work. If M0 reveals auth collision or rate-limit pathology, plan rev 5 is invalidated; rev 6 designs a queue-based serialization model. **No daemon code lands before M0 passes.**
364
+
365
+ ---
366
+
367
+ ## 11. Open decisions
368
+
369
+ | # | Question | Status |
370
+ |---|----------|--------|
371
+ | Q1 | LLM backend abstraction timing | **Resolved (D2)**: never. Multi-vendor via `ANTHROPIC_BASE_URL` proxy. |
372
+ | Q2 | Streaming UX (Matrix m.replace vs final-only) | **Resolved**: final-only in v0.1 on both transports. Streaming → v0.1.x. |
373
+ | Q3 | resumeSession hard block vs stateless v0.1 | **Resolved (D10)**: hard block; claude-code-agent v0.2 shipped 2026-05-14. |
374
+ | Q4 | Project name (does "nps" stand for something?) | **Open. Owner: Coral. Deadline: before npm publish.** Fallback codename: `nps-cli`. |
375
+
376
+ ---
377
+
378
+ ## 12. Comparison with sps-cli (so we don't accidentally rebuild it)
379
+
380
+ | Dimension | sps-cli | nps-cli |
381
+ |---|---|---|
382
+ | Form factor | Pipeline orchestrator | Conversational daemon |
383
+ | Concurrency | Single worker, serial (v0.37.2 deliberate) | Ephemeral process per message; no parallelism within a channel |
384
+ | Trigger | File changes in `~/.coral/projects/...` | IM messages |
385
+ | Output | Card state machine + worker artifacts | Reply messages in IM channel |
386
+ | Skill scope | sps global + project | nps profile-local (cwd = workspace, with system prompt + skills resolved by M-WS) |
387
+ | Web UI | Yes (console) | Deferred to v0.1.x |
388
+ | Platform | Cross-platform | Linux-only (v0.1) |
389
+ | Persistence | Card state in files | Per-channel SessionStore + audit log |
390
+
391
+ Two different products with one shared runtime (`@coralai/claude-code-agent`). The Phase 1 extraction is what makes both possible.
392
+
393
+ ---
394
+
395
+ ## 13. Distribution
396
+
397
+ ```json
398
+ {
399
+ "name": "@coralai/nps-cli",
400
+ "version": "0.1.0",
401
+ "type": "module",
402
+ "bin": { "nps": "dist/main.js" },
403
+ "files": ["dist", "systemd/", "README.md"],
404
+ "engines": { "node": ">=18" },
405
+ "os": ["linux"]
406
+ }
407
+ ```
408
+
409
+ CI/CD (GitHub Actions, to be added in M7):
410
+ - `on: push to main` → `npm test && tsc --noEmit`
411
+ - `on: tag v*.*.*` → `npm publish`
412
+ - CI smoke: `npm install -g <local pack>` + `nps --version`
413
+
414
+ systemd `ExecStart` pattern (documented by `nps daemon install-service`):
415
+ ```
416
+ ExecStart=/usr/bin/env node /home/<user>/.npm-global/bin/nps daemon start --foreground
417
+ ```
418
+
419
+ ---
420
+
421
+ ## 14. NOT in scope (v0.1, rev 5 final)
422
+
423
+ - nps-cli's own `LLMBackend` abstraction (D2)
424
+ - Plugin extension model for nps-cli (D3)
425
+ - sps-cli chat-worker integration
426
+ - Pure library release without daemon
427
+ - Per-channel residency / process cache (codex F2)
428
+ - Multi-participant Matrix rooms (codex F8)
429
+ - Telegram gateway (codex F6 — hidden work; own milestone in v0.1.x)
430
+ - IM-driven binding commands `/bind /unbind /persona /reset` (codex F5)
431
+ - Web admin console (codex F7)
432
+ - Cross-agent context handoff (D8)
433
+ - Voice messages STT/TTS (D9)
434
+ - Cost tracking (D6)
435
+ - Streaming UX (Matrix m.replace) (Q2)
436
+ - macOS launchd / Windows (codex F20 — Linux-only)
437
+ - Public `loadSession` method in claude-code-agent (codex F9 — internal only)
438
+
439
+ ---
440
+
441
+ ## 15. Implementation status
442
+
443
+ | Component | Status |
444
+ |-----------|--------|
445
+ | `@coralai/claude-code-agent` v0.2 (resumeSession) | ✅ Shipped 2026-05-14 |
446
+ | Skeleton `jarvis-skills/nps-cli/` | ✅ Landed 2026-05-14 |
447
+ | **M0 lifecycle prototype gate** | ✅ **PASSED 2026-05-14** (solo=4847ms, parallel=4712ms, ratio 0.97x; all 6 criteria green) |
448
+ | **M0a resume prototype gate** | ✅ **PASSED 2026-05-14** (stored "42" → resumed → recalled "42"; bogus sessionId → SESSION_LOST; 5/5 criteria) |
449
+ | **M-WS workspace prototype gate** | ✅ **PASSED 2026-05-14** (profile-as-cwd loads CLAUDE.md + skills; workspace via absolute path also works without `additionalDirectories`) |
450
+ | **M5a Matrix transport spike** | ✅ **PASSED 2026-05-14** (im.wymsn.com sync 218ms, DM classification works, test send succeeded; lib decision: `matrix-js-sdk@41 direct`) |
451
+ | **M1-M13 implementation** | ✅ **DONE 2026-05-14** (62/62 unit tests; agent invoke smoke green; daemon RPC works) |
452
+ | **Real-world Matrix DM E2E** | ✅ **PASSED 2026-05-15** (dedicated `@nps-bot:wymsn.com` account registered via /register API; DM rooms with both @elon and @yuguo; T1 /help / T2 prompt / T3 /status all replied correctly; sessions persisted with compatHash) |
453
+ | **systemd deployment** | ✅ **ACTIVE** (`nps daemon install-service` writes wrapper at `~/.nps/run-daemon.sh` + unit at `~/.config/systemd/user/nps.service`; wrapper sources `~/.coral/env` to handle `export KEY=value` lines + inherits PATH so claude + claude-agent-acp resolve; `systemctl --user enable --now nps` works; `loginctl enable-linger` set) |
454
+ | Project name (Q4) | ✅ **Locked: nps-cli** (D2=A 2026-05-15); package `private` flag removed; LICENSE + publishConfig + repository added |
455
+ | npm publish | ⏳ Pending dogfood validation (D3=B, target 0.1.0 stable after a few days of use) |
456
+
457
+ ---
458
+
459
+ *Vision rev 5 · 2026-05-14 · aligned with CEO plan rev 5 + eng plan rev 5*
@@ -0,0 +1,26 @@
1
+ import type { Logger } from 'pino';
2
+ export interface AuditEntry {
3
+ ts: string;
4
+ sender: string;
5
+ transport: 'matrix' | 'telegram' | 'cli';
6
+ channel?: string;
7
+ command_or_event: string;
8
+ decision: 'accepted' | 'denied' | 'dropped' | 'error';
9
+ profile?: string;
10
+ detail?: string;
11
+ }
12
+ export declare class AuditLog {
13
+ private readonly path;
14
+ private logger;
15
+ private fd;
16
+ private bytesWritten;
17
+ constructor(path: string, logger?: Logger);
18
+ private open;
19
+ append(entry: Omit<AuditEntry, 'ts'> & {
20
+ ts?: string;
21
+ }): void;
22
+ /** Atomic rotation: close current → shift .3→.4, .2→.3 ... → rename current to .0 → reopen */
23
+ private rotate;
24
+ close(): void;
25
+ }
26
+ //# sourceMappingURL=audit-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-log.d.ts","sourceRoot":"","sources":["../src/audit-log.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAMnC,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,QAAQ,GAAG,UAAU,GAAG,KAAK,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;IACtD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,QAAQ;IAKjB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAJvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,EAAE,CAAqB;IAC/B,OAAO,CAAC,YAAY,CAAK;gBAEN,IAAI,EAAE,MAAM,EAC7B,MAAM,CAAC,EAAE,MAAM;IAMjB,OAAO,CAAC,IAAI;IAmBZ,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAsB7D,8FAA8F;IAC9F,OAAO,CAAC,MAAM;IA6Bd,KAAK,IAAI,IAAI;CAMd"}
@@ -0,0 +1,120 @@
1
+ /**
2
+ * M12 — audit log.
3
+ *
4
+ * jsonl append at `~/.nps/audit.jsonl`, mode 0600. Each entry is `{ v: 1, ts, ... }`
5
+ * for forward compatibility. Rotation at 50MB → `.0`; existing `.0..3` shift up;
6
+ * `.4` deleted. On ENOSPC: log to pino as [CRITICAL], do NOT block daemon.
7
+ */
8
+ import { existsSync, mkdirSync, openSync, fstatSync, writeSync, closeSync, renameSync, unlinkSync, chmodSync } from 'node:fs';
9
+ import { dirname } from 'node:path';
10
+ import { getLogger } from './logger.js';
11
+ const ROTATE_BYTES = 50 * 1024 * 1024; // 50 MB
12
+ const RETAIN = 5; // .0..4
13
+ export class AuditLog {
14
+ path;
15
+ logger;
16
+ fd;
17
+ bytesWritten = 0;
18
+ constructor(path, logger) {
19
+ this.path = path;
20
+ this.logger = logger ?? getLogger('audit');
21
+ this.open();
22
+ }
23
+ open() {
24
+ try {
25
+ mkdirSync(dirname(this.path), { recursive: true });
26
+ // O_APPEND is implicit by writing at end; openSync('a') opens append-only.
27
+ this.fd = openSync(this.path, 'a', 0o600);
28
+ // Ensure perms even if file pre-existed with looser mode.
29
+ try {
30
+ chmodSync(this.path, 0o600);
31
+ }
32
+ catch { /* best-effort */ }
33
+ // Estimate current size for rotation budget
34
+ try {
35
+ this.bytesWritten = fstatSync(this.fd).size;
36
+ }
37
+ catch {
38
+ this.bytesWritten = 0;
39
+ }
40
+ }
41
+ catch (e) {
42
+ this.logger.error({ err: e instanceof Error ? e.message : e }, '[CRITICAL] audit log open failed');
43
+ this.fd = undefined;
44
+ }
45
+ }
46
+ append(entry) {
47
+ if (this.fd === undefined) {
48
+ // Failed open — skip silently per plan (audit must never block daemon).
49
+ return;
50
+ }
51
+ const line = JSON.stringify({ v: 1, ts: entry.ts ?? new Date().toISOString(), ...entry }) + '\n';
52
+ try {
53
+ writeSync(this.fd, line);
54
+ this.bytesWritten += Buffer.byteLength(line, 'utf8');
55
+ if (this.bytesWritten >= ROTATE_BYTES) {
56
+ this.rotate();
57
+ }
58
+ }
59
+ catch (e) {
60
+ const msg = e instanceof Error ? e.message : String(e);
61
+ if (msg.includes('ENOSPC') || msg.includes('no space')) {
62
+ this.logger.error('[CRITICAL] audit log unavailable: disk full');
63
+ }
64
+ else {
65
+ this.logger.error({ err: msg }, '[CRITICAL] audit log write failed');
66
+ }
67
+ }
68
+ }
69
+ /** Atomic rotation: close current → shift .3→.4, .2→.3 ... → rename current to .0 → reopen */
70
+ rotate() {
71
+ if (this.fd === undefined)
72
+ return;
73
+ try {
74
+ closeSync(this.fd);
75
+ this.fd = undefined;
76
+ // Delete oldest if exists
77
+ const oldest = `${this.path}.${RETAIN - 1}`;
78
+ if (existsSync(oldest)) {
79
+ try {
80
+ unlinkSync(oldest);
81
+ }
82
+ catch { /* ignore */ }
83
+ }
84
+ // Shift remaining
85
+ for (let i = RETAIN - 2; i >= 0; i--) {
86
+ const src = `${this.path}.${i}`;
87
+ const dst = `${this.path}.${i + 1}`;
88
+ if (existsSync(src)) {
89
+ try {
90
+ renameSync(src, dst);
91
+ }
92
+ catch { /* ignore */ }
93
+ }
94
+ }
95
+ // Move current → .0
96
+ if (existsSync(this.path)) {
97
+ try {
98
+ renameSync(this.path, `${this.path}.0`);
99
+ }
100
+ catch { /* ignore */ }
101
+ }
102
+ }
103
+ catch (e) {
104
+ this.logger.error({ err: e instanceof Error ? e.message : e }, '[CRITICAL] audit rotation failed');
105
+ }
106
+ finally {
107
+ this.open();
108
+ }
109
+ }
110
+ close() {
111
+ if (this.fd !== undefined) {
112
+ try {
113
+ closeSync(this.fd);
114
+ }
115
+ catch { /* ignore */ }
116
+ this.fd = undefined;
117
+ }
118
+ }
119
+ }
120
+ //# sourceMappingURL=audit-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-log.js","sourceRoot":"","sources":["../src/audit-log.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import type { Logger } from 'pino';
3
+ import type { BindingsFile } from '../config/schema.js';
4
+ export type Transport = 'matrix' | 'telegram';
5
+ export declare class BindingsLoader extends EventEmitter {
6
+ private readonly path;
7
+ private logger;
8
+ private bindings;
9
+ private watcher;
10
+ constructor(path: string, logger?: Logger);
11
+ start(): Promise<void>;
12
+ stop(): Promise<void>;
13
+ private reload;
14
+ /** Look up profile name for a Matrix channel id; undefined if unbound. */
15
+ lookupMatrix(channelId: string): string | undefined;
16
+ /** Snapshot of current bindings (read-only). */
17
+ snapshot(): BindingsFile;
18
+ }
19
+ //# sourceMappingURL=bindings-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bindings-loader.d.ts","sourceRoot":"","sources":["../../src/bindings/bindings-loader.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAGnC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE9C,qBAAa,cAAe,SAAQ,YAAY;IAM5C,OAAO,CAAC,QAAQ,CAAC,IAAI;IALvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,OAAO,CAAwB;gBAGpB,IAAI,EAAE,MAAM,EAC7B,MAAM,CAAC,EAAE,MAAM;IAMX,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAO3B,OAAO,CAAC,MAAM;IA4Bd,0EAA0E;IAC1E,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAInD,gDAAgD;IAChD,QAAQ,IAAI,YAAY;CAGzB"}