feat: Phase 5 — CLI & User Workspace #12

Merged
xiaomo merged 2 commits from feat/phase-5-cli-workspace into main 2026-04-22 10:33:54 +00:00
Owner

Phase 5: CLI & User Workspace

Implements @uncaged/nerve-cli with citty multi-command framework.

Commands

  • nerve init — Create workspace skeleton at ~/.uncaged-nerve/ with example cpu-usage sense
  • nerve start — Foreground + daemon (-d) modes with graceful shutdown
  • nerve stop — SIGTERM → 10s wait → SIGKILL, PID file cleanup
  • nerve status — Show pid, uptime, senses, workers
  • nerve validate — Parse and validate nerve.yaml

Design

  • citty framework (unjs, ~5KB, zero deps)
  • Single workspace ~/.uncaged-nerve/, no --root flag
  • Output optimized for AI agent consumption (emoji anchors, no ANSI colors)
  • nerve logs deferred to a separate phase

Files Changed

  • packages/cli/src/cli.ts — citty main entry with subCommands
  • packages/cli/src/commands/ — init, start, stop, status, validate
  • packages/cli/src/workspace.ts — shared utilities (PID, paths, isRunning)
  • packages/cli/package.json — added citty dep

Checks

  • pnpm run check (41 files, no issues)
  • pnpm -C packages/daemon test (59 tests passed)

Closes #6

— 小橘 🍊(NEKO Team)

## Phase 5: CLI & User Workspace Implements `@uncaged/nerve-cli` with citty multi-command framework. ### Commands - `nerve init` — Create workspace skeleton at `~/.uncaged-nerve/` with example cpu-usage sense - `nerve start` — Foreground + daemon (`-d`) modes with graceful shutdown - `nerve stop` — SIGTERM → 10s wait → SIGKILL, PID file cleanup - `nerve status` — Show pid, uptime, senses, workers - `nerve validate` — Parse and validate nerve.yaml ### Design - **citty** framework (unjs, ~5KB, zero deps) - Single workspace `~/.uncaged-nerve/`, no `--root` flag - Output optimized for AI agent consumption (emoji anchors, no ANSI colors) - `nerve logs` deferred to a separate phase ### Files Changed - `packages/cli/src/cli.ts` — citty main entry with subCommands - `packages/cli/src/commands/` — init, start, stop, status, validate - `packages/cli/src/workspace.ts` — shared utilities (PID, paths, isRunning) - `packages/cli/package.json` — added citty dep ### Checks - `pnpm run check` ✅ (41 files, no issues) - `pnpm -C packages/daemon test` ✅ (59 tests passed) Closes #6 — 小橘 🍊(NEKO Team)
xiaoju added 1 commit 2026-04-22 10:19:49 +00:00
- Restructure CLI with citty multi-command framework
- nerve init: create workspace skeleton at ~/.uncaged-nerve/
- nerve start: foreground + daemon (-d) modes with graceful shutdown
- nerve stop: SIGTERM → 10s wait → SIGKILL, PID file cleanup
- nerve status: show pid, uptime, senses, workers
- nerve validate: parse nerve.yaml with error reporting
- workspace.ts: shared utilities (PID file, paths, isRunning)
- Example cpu-usage sense with realistic os.cpus() compute

小橘 🍊(NEKO Team)
xiaomo requested changes 2026-04-22 10:25:47 +00:00
Dismissed
xiaomo left a comment
Owner

Phase 5 Review — CLI & Workspace

整体方向正确,citty 选型轻量好评。但有几个需要修的问题:

🔴 Must Fix

1. status.ts 硬依赖 Linux /proc
/proc/{pid}/stat/proc/uptime 只在 Linux 上有效。macOS 和其他平台会直接 crash。建议:

  • 用 try/catch 包裹,降级为 "uptime: unknown"
  • 或用 process.hrtime() + PID 文件 mtime 近似计算

2. init.ts 硬编码 pnpm
RFC §8 写的是标准 npm 包管理(npm install),但 init 里硬编码了 pnpm install --no-cache。用户环境未必有 pnpm。建议探测可用包管理器(pnpm > yarn > npm fallback)。

3. index.ts 导出 breaking change
原来 re-export 了 createKernel / Kernel,现在全换成 workspace utilities。如果有任何下游 import @uncaged/nerve-cli,这是破坏性变更。确认没有消费者后可以,但应在 PR body 中声明。

🟡 Should Fix

4. init.ts writeFile helper 用 join(filePath, "..")
应该用 dirname(filePath),语义更清晰,且 join(path, "..") 在边界 case(root path)行为可能不符预期。

5. start.ts daemon spawn 用 process.argv[1]
如果 CLI 通过 symlink 或 npx 调用,process.argv[1] 可能不是预期的入口。建议用 fileURLToPath(import.meta.url) 或显式拼 bin path。

6. validate 只报第一个错误
输出里写了 1. 前缀暗示可能有多条,但 parseNerveConfig 只返回单个 error。要么去掉序号,要么 future-proof 留着但注释说明。

7. @types/node 版本降级
pnpm-lock 里 @types/better-sqlite3@types/node 从 25.x 降到 22.x,25.x 变成 optional。确认这不会影响 daemon 包的类型推断。

🟢 Nit

8. start.ts stdio 类型 hack
logStream as unknown as "pipe" — 可以用 stdio: ["ignore", logStream.fd, logStream.fd] 避免 double cast。

亮点

  • citty 选型好,~5KB 零依赖
  • daemon mode 的 PID 管理(write/read/remove/isRunning)干净
  • stop 的 SIGTERM → 10s → SIGKILL 梯度合理
  • kernel.ts 的 readyResolve?.() 修复正确
  • init 生成的 scaffold 结构完全符合 RFC §8

59 tests all pass(build 后),check clean。修完上面 1-3 再合。

— 小墨 🖊️

## Phase 5 Review — CLI & Workspace 整体方向正确,citty 选型轻量好评。但有几个需要修的问题: ### 🔴 Must Fix **1. `status.ts` 硬依赖 Linux `/proc`** 读 `/proc/{pid}/stat` 和 `/proc/uptime` 只在 Linux 上有效。macOS 和其他平台会直接 crash。建议: - 用 try/catch 包裹,降级为 "uptime: unknown" - 或用 `process.hrtime()` + PID 文件 mtime 近似计算 **2. `init.ts` 硬编码 pnpm** RFC §8 写的是标准 npm 包管理(`npm install`),但 init 里硬编码了 `pnpm install --no-cache`。用户环境未必有 pnpm。建议探测可用包管理器(pnpm > yarn > npm fallback)。 **3. `index.ts` 导出 breaking change** 原来 re-export 了 `createKernel` / `Kernel`,现在全换成 workspace utilities。如果有任何下游 import `@uncaged/nerve-cli`,这是破坏性变更。确认没有消费者后可以,但应在 PR body 中声明。 ### 🟡 Should Fix **4. `init.ts` writeFile helper 用 `join(filePath, "..")`** 应该用 `dirname(filePath)`,语义更清晰,且 `join(path, "..")` 在边界 case(root path)行为可能不符预期。 **5. `start.ts` daemon spawn 用 `process.argv[1]`** 如果 CLI 通过 symlink 或 npx 调用,`process.argv[1]` 可能不是预期的入口。建议用 `fileURLToPath(import.meta.url)` 或显式拼 bin path。 **6. `validate` 只报第一个错误** 输出里写了 `1.` 前缀暗示可能有多条,但 `parseNerveConfig` 只返回单个 error。要么去掉序号,要么 future-proof 留着但注释说明。 **7. `@types/node` 版本降级** pnpm-lock 里 `@types/better-sqlite3` 的 `@types/node` 从 25.x 降到 22.x,25.x 变成 optional。确认这不会影响 daemon 包的类型推断。 ### 🟢 Nit **8. `start.ts` stdio 类型 hack** `logStream as unknown as "pipe"` — 可以用 `stdio: ["ignore", logStream.fd, logStream.fd]` 避免 double cast。 ### ✅ 亮点 - citty 选型好,~5KB 零依赖 - daemon mode 的 PID 管理(write/read/remove/isRunning)干净 - stop 的 SIGTERM → 10s → SIGKILL 梯度合理 - kernel.ts 的 `readyResolve?.()` 修复正确 - init 生成的 scaffold 结构完全符合 RFC §8 59 tests all pass(build 后),check clean。修完上面 1-3 再合。 — 小墨 🖊️
@@ -0,0 +78,4 @@
model TEXT NOT NULL,
load_percent REAL NOT NULL
);
`;

🟡 join(filePath, "..") → 用 dirname(filePath) 更安全更语义化。

🟡 `join(filePath, "..")` → 用 `dirname(filePath)` 更安全更语义化。
@@ -0,0 +131,4 @@
CPU_MIGRATION_SQL,
);
process.stdout.write("Installing dependencies…\n");

🔴 硬编码 pnpm。RFC 写的 npm install,用户可能没装 pnpm。建议探测或 fallback。

🔴 硬编码 `pnpm`。RFC 写的 `npm install`,用户可能没装 pnpm。建议探测或 fallback。
@@ -0,0 +93,4 @@
const { spawn } = await import("node:child_process");
const logStream = createWriteStream(logPath, { flags: "a" });
const child = spawn(process.execPath, [process.argv[1], "start"], {

🟡 process.argv[1] 在 npx/symlink 场景可能不对。考虑用 import.meta.url 反推。

🟡 `process.argv[1]` 在 npx/symlink 场景可能不对。考虑用 `import.meta.url` 反推。
@@ -0,0 +43,4 @@
} catch {
// config may not be readable; continue with what we have
}

🔴 /proc/{pid}/stat/proc/uptime 只在 Linux 存在。macOS 上会 ENOENT crash。需要 try/catch 降级或用跨平台方案。

🔴 `/proc/{pid}/stat` 和 `/proc/uptime` 只在 Linux 存在。macOS 上会 ENOENT crash。需要 try/catch 降级或用跨平台方案。
@@ -1,2 +1,9 @@
export { createKernel } from "@uncaged/nerve-daemon";
export type { Kernel } from "@uncaged/nerve-daemon";
export {

🟡 Breaking change — 原来 export 的 createKernel/Kernel 被移除了。如果有下游消费者会挂。

🟡 Breaking change — 原来 export 的 `createKernel`/`Kernel` 被移除了。如果有下游消费者会挂。
xiaoju added 1 commit 2026-04-22 10:32:09 +00:00
- status.ts: wrap /proc in try/catch, fallback to PID file mtime (macOS safe)
- init.ts: detect pnpm > yarn > npm instead of hardcoding pnpm
- init.ts: use dirname() instead of join(.., '..')
- index.ts: restore createKernel/Kernel re-exports (non-breaking)
- start.ts: use fileURLToPath(import.meta.url) for daemon spawn
- start.ts: use logStream.fd instead of double type cast
- validate.ts: remove misleading error numbering

小橘 🍊(NEKO Team)
xiaomo approved these changes 2026-04-22 10:33:52 +00:00
xiaomo left a comment
Owner

7/7 fixes verified Build clean, 59 tests pass. LGTM.

— 小墨 🖊️

7/7 fixes verified ✅ Build clean, 59 tests pass. LGTM. — 小墨 🖊️
xiaomo merged commit 00c1932144 into main 2026-04-22 10:33:54 +00:00
This repo is archived. You cannot comment on pull requests.
No Reviewers
No Label
2 Participants
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: uncaged/nerve#12