mishkan-harness 0.1.0 → 0.2.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 (47) hide show
  1. package/README.md +75 -24
  2. package/bin/mishkan.js +145 -22
  3. package/docs/design/MISHKAN_decisions.md +400 -0
  4. package/docs/design/MISHKAN_observability.md +1023 -0
  5. package/docs/usage/01-installation.md +40 -12
  6. package/docs/usage/10-observability.md +149 -0
  7. package/docs/usage/README.md +1 -0
  8. package/package.json +1 -1
  9. package/payload/install/settings.hooks.json +9 -0
  10. package/payload/mishkan/hooks/model-route.py +32 -0
  11. package/payload/mishkan/hooks/post-tool-observe.sh +222 -14
  12. package/payload/mishkan/hooks/pre-tool-security.sh +16 -0
  13. package/payload/mishkan/hooks/pre-tool-trace.sh +43 -0
  14. package/payload/mishkan/observability/README.md +100 -0
  15. package/payload/mishkan/observability/bus.py +89 -0
  16. package/payload/mishkan/observability/bus.sh +117 -0
  17. package/payload/mishkan/observability/schema.json +209 -0
  18. package/payload/mishkan/observability/usage_parser.py +211 -0
  19. package/payload/mishkan/observability/watch/README.md +62 -0
  20. package/payload/mishkan/observability/watch/pyproject.toml +22 -0
  21. package/payload/mishkan/observability/watch/src/mishkan_watch/__init__.py +3 -0
  22. package/payload/mishkan/observability/watch/src/mishkan_watch/__main__.py +29 -0
  23. package/payload/mishkan/observability/watch/src/mishkan_watch/app.py +235 -0
  24. package/payload/mishkan/observability/watch/src/mishkan_watch/client.py +134 -0
  25. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/__init__.py +1 -0
  26. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/activity.py +100 -0
  27. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/agents.py +278 -0
  28. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/knowledge.py +188 -0
  29. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/live.py +307 -0
  30. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/workflows.py +189 -0
  31. package/payload/mishkan/observability/watch/src/mishkan_watch/theme.tcss +254 -0
  32. package/payload/mishkan/observability/watchd/README.md +59 -0
  33. package/payload/mishkan/observability/watchd/pyproject.toml +23 -0
  34. package/payload/mishkan/observability/watchd/src/mishkan_watchd/__init__.py +3 -0
  35. package/payload/mishkan/observability/watchd/src/mishkan_watchd/__main__.py +230 -0
  36. package/payload/mishkan/observability/watchd/src/mishkan_watchd/lifecycle.py +68 -0
  37. package/payload/mishkan/observability/watchd/src/mishkan_watchd/server.py +134 -0
  38. package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/__init__.py +6 -0
  39. package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/bus_tail.py +97 -0
  40. package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/cognee_poll.py +228 -0
  41. package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/mcp_probe.py +230 -0
  42. package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/session_discover.py +74 -0
  43. package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/session_tail.py +161 -0
  44. package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/subagent_tail.py +195 -0
  45. package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/worktree_poll.py +107 -0
  46. package/payload/mishkan/observability/watchd/src/mishkan_watchd/state.py +288 -0
  47. package/payload/mishkan/observability/watchd/tests/test_state.py +77 -0
package/README.md CHANGED
@@ -22,14 +22,19 @@ It's personal, opinionated infrastructure built around one engineer's standards
22
22
  and shared openly for anyone who wants to try working this way. To make it yours,
23
23
  replace one file (your profile) and re-sync; nothing else hardcodes the author.
24
24
 
25
- > **Status:** early and evolving. Used daily, not yet battle-hardened for everyone.
26
- > The agent fleet, rules, hooks, and installer are complete; the Cognee knowledge
27
- > graph is opt-in (bring up a local container) and some integration points are
28
- > marked as such below.
25
+ > **Status:** v0.2.0 — early and evolving. The agent fleet, rules, hooks, and
26
+ > installer are complete. The Cognee knowledge stack (work + curated stores) is
27
+ > ready to bring up locally. Live observability (daemon + Textual TUI) ships as
28
+ > an opt-in stack of two `uv tool install`-able Python packages.
29
29
 
30
30
  ---
31
31
 
32
- ## Why
32
+ ## Three pillars
33
+
34
+ MISHKAN rests on three things that work together. They are independent — you
35
+ can use each without the others — but they compound:
36
+
37
+ ### 1. A disciplined multi-agent organisation
33
38
 
34
39
  Most "AI coding" is one model in one chat doing everything at once — generating,
35
40
  judging its own output, skipping the spec, drifting scope. MISHKAN refuses that:
@@ -38,12 +43,36 @@ judging its own output, skipping the spec, drifting scope. MISHKAN refuses that:
38
43
  Modeling → implementation. Agents won't jump to code without the upstream artifacts.
39
44
  - **Generation ≠ evaluation.** The agent that writes is never the agent that reviews.
40
45
  QA and reporters are structurally separate roles.
41
- - **Deterministic guardrails.** Secrets, injection, unsafe deserialization, disabled
42
- TLS, `:latest` tags, weak hashing — blocked by a hook *before* the write lands,
43
- not flagged after.
44
46
  - **Security & dependencies are first-class**, not an afterthought audit.
45
- - **It learns.** Resolved research and decisions become nodes in a knowledge graph
46
- (Cognee) that grows through use.
47
+
48
+ ### 2. Deterministic guardrails
49
+
50
+ Quality and security aren't *requested* of the model — they're **enforced by
51
+ the environment**: path-scoped rules, pre-write security hooks, structural
52
+ separation of generation from review. Secrets, injection, unsafe
53
+ deserialization, disabled TLS, `:latest` tags, weak hashing — blocked by a
54
+ hook *before* the write lands, not flagged after.
55
+
56
+ ### 3. A knowledge stack that grows
57
+
58
+ MISHKAN is built around **persisting what you learn**, so future sessions
59
+ start where the last one stopped instead of from zero. Three complementary
60
+ layers (see [D-008](docs/design/MISHKAN_decisions.md)):
61
+
62
+ - **[Cognee work](payload/mishkan/cognee/) (`:7777`)** — per-project semantic
63
+ graph. ADRs, runbooks, decisions, resolved research — all ingested
64
+ selectively, then queryable by any agent that hits an unknown.
65
+ - **[Cognee curated](payload/mishkan/cognee/) (`:7730`)** — cross-project
66
+ reference library. Read-mostly, seeded once, shared across every project on
67
+ the host.
68
+ - **[Graphify](https://github.com/safishamsi/graphify) (planned, D-008
69
+ signed)** — deterministic code-structure graph for "who calls X, who depends
70
+ on Y", complementing Cognee's semantic layer.
71
+
72
+ Plus a **live observability TUI** ([mishkan-watch](payload/mishkan/observability/))
73
+ that shows every running agent, workflow, tool call, hook fire, token spend,
74
+ and MCP/Cognee status — in real time, across every session, with no overload.
75
+ See [`docs/design/MISHKAN_observability.md`](docs/design/MISHKAN_observability.md).
47
76
 
48
77
  ## The teams
49
78
 
@@ -81,10 +110,15 @@ install time — nothing machine-specific is baked in.
81
110
 
82
111
  ```bash
83
112
  npx mishkan-harness status # what's installed
113
+ npx mishkan-harness observability # install/refresh just the daemon + TUI (needs `uv`)
84
114
  npx mishkan-harness uninstall # remove harness; keep your CLAUDE.md & rules
85
115
  npx mishkan-harness uninstall --purge # also remove the user-level rule
86
116
  ```
87
117
 
118
+ The installer walks 7 phases with clear progress, asks once whether to install
119
+ the observability stack at the end, and skips that step cleanly if `uv` isn't
120
+ installed.
121
+
88
122
  ## A first session
89
123
 
90
124
  1. Run `claude` anywhere. You're talking to **Nehemiah** and **Bezalel** in
@@ -129,14 +163,14 @@ org-layer agent) owns re-deriving anything drawn from it. See
129
163
  workspace (discovery-based, no hardcoded paths), aggregates shared CVEs and
130
164
  version drift, and produces a coordinated, vetted update plan.
131
165
 
132
- ## Knowledge graph (optional)
166
+ ## Knowledge stack
133
167
 
134
- MISHKAN uses [Cognee](https://docs.cognee.ai) as the knowledge graph that grows as
135
- you work. Cognee core is a Python library; the harness consumes it through the
136
- **`cognee-mcp`** server. Optional at first run — agents work without it; only
137
- persistence is deferred. Run the **cognee-quickstart** skill for guided setup, or
138
- the hardened Docker deployment under `~/.claude/mishkan/cognee/` (HTTP transport,
139
- port 7777):
168
+ MISHKAN uses [Cognee](https://docs.cognee.ai) as its **two-store knowledge
169
+ graph** that grows as you work. Cognee core is a Python library; the harness
170
+ consumes it through the **`cognee-mcp`** server. Agents can still work without
171
+ it — persistence is just deferred. Run the **cognee-quickstart** skill for
172
+ guided setup, or the hardened Docker deployment under
173
+ `~/.claude/mishkan/cognee/`:
140
174
 
141
175
  ```bash
142
176
  cd ~/.claude/mishkan/cognee
@@ -146,13 +180,30 @@ nc -z localhost 7777 && echo up
146
180
  ~/.claude/mishkan/scripts/seed-curated-library.sh # 96 curated references
147
181
  ```
148
182
 
149
- Agents reach it via each project's `.mcp.json` (seeded by `/mishkan-init`): the
150
- default is HTTP transport to `http://localhost:7777/mcp`, with a zero-container
151
- stdio alternative included. The deployment follows the infra rules — built from a
152
- pinned `Dockerfile` (no `:latest`), SOPS secrets, hardening overlay on every
153
- recreate, `127.0.0.1`-bound. Pin `COGNEE_MCP_REF` to a release and confirm details
154
- against [Cognee's docs](https://docs.cognee.ai/cognee-mcp/mcp-local-setup).
155
- Full guide: [`payload/mishkan/cognee/README.md`](payload/mishkan/cognee/README.md).
183
+ Agents reach the work store at `http://localhost:7777/mcp` and the curated
184
+ store at `:7730`, both seeded by `/mishkan-init` into each project's
185
+ `.mcp.json`. The deployment is pinned, SOPS-managed, hardening-overlay on
186
+ every recreate, `127.0.0.1`-bound. Full guide:
187
+ [`payload/mishkan/cognee/README.md`](payload/mishkan/cognee/README.md).
188
+
189
+ ## Live observability (opt-in)
190
+
191
+ See **everything** that runs across your MISHKAN sessions in a single Textual
192
+ TUI: active agents, workflows-in-flight, token spend ($), file changes, hook
193
+ decisions, Cognee node counts, MCP server status. Cross-session,
194
+ cross-project, near-zero overhead. Two `uv tool`-installable Python packages:
195
+
196
+ ```bash
197
+ # the installer offers this automatically; or run it standalone any time:
198
+ npx mishkan-harness observability
199
+
200
+ # then, in two tmux panes (or any two terminals):
201
+ mishkan-watchd start # daemon — aggregates the bus
202
+ mishkan-watch # TUI client — 5 tabs, status bar
203
+ ```
204
+
205
+ Full operator guide: [`docs/usage/10-observability.md`](docs/usage/10-observability.md).
206
+ Design and event schema: [`docs/design/MISHKAN_observability.md`](docs/design/MISHKAN_observability.md).
156
207
 
157
208
  ## Token & context management
158
209
 
package/bin/mishkan.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  // MISHKAN installer — dependency-free (Node >=18, built-ins only).
3
- // Commands: install | uninstall | status
3
+ // Commands: install | uninstall | status | observability
4
4
  //
5
5
  // Portability by design: every path is resolved from os.homedir() at runtime.
6
6
  // No machine-specific paths are baked in. Idempotent: re-running install updates
@@ -10,6 +10,9 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync,
10
10
  import { homedir } from "node:os";
11
11
  import { join, dirname, relative } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
+ import { spawnSync } from "node:child_process";
14
+ import { createInterface } from "node:readline";
15
+ import { stdin, stdout } from "node:process";
13
16
 
14
17
  const HERE = dirname(fileURLToPath(import.meta.url));
15
18
  const PKG = join(HERE, "..");
@@ -18,8 +21,30 @@ const CLAUDE = join(HOME, ".claude");
18
21
  const MISHKAN = join(CLAUDE, "mishkan");
19
22
  const STAMP = join(MISHKAN, ".install-stamp");
20
23
 
24
+ // ─── output ────────────────────────────────────────────────────────────────
25
+
26
+ const NO_COLOR = process.env.NO_COLOR || !process.stdout.isTTY;
27
+ const c = NO_COLOR
28
+ ? { dim: s => s, bold: s => s, cyan: s => s, green: s => s, yellow: s => s, red: s => s }
29
+ : {
30
+ dim: s => `\x1b[2m${s}\x1b[0m`,
31
+ bold: s => `\x1b[1m${s}\x1b[0m`,
32
+ cyan: s => `\x1b[36m${s}\x1b[0m`,
33
+ green: s => `\x1b[32m${s}\x1b[0m`,
34
+ yellow: s => `\x1b[33m${s}\x1b[0m`,
35
+ red: s => `\x1b[31m${s}\x1b[0m`,
36
+ };
37
+
21
38
  const log = (...a) => console.log("mishkan:", ...a);
22
- const warn = (...a) => console.warn("mishkan: WARN", ...a);
39
+ const warn = (...a) => console.warn("mishkan: " + c.yellow("WARN"), ...a);
40
+
41
+ // Print a phase header with a one-line "why" subtitle. Helps the engineer
42
+ // see what each step does and why, instead of an unstructured wall of logs.
43
+ function phase(n, total, title, why) {
44
+ console.log();
45
+ console.log(c.bold(c.cyan(`[${n}/${total}] ${title}`)));
46
+ if (why) console.log(c.dim(` ${why}`));
47
+ }
23
48
 
24
49
  function ensureDir(d) { mkdirSync(d, { recursive: true }); }
25
50
 
@@ -106,16 +131,85 @@ function removeHooks() {
106
131
  writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
107
132
  }
108
133
 
109
- function install() {
134
+ // ─── tool availability + interactive prompt ────────────────────────────────
135
+
136
+ function commandExists(cmd) {
137
+ const r = spawnSync(process.platform === "win32" ? "where" : "which",
138
+ [cmd], { stdio: "ignore" });
139
+ return r.status === 0;
140
+ }
141
+
142
+ async function promptYN(question, defaultYes = true) {
143
+ // Non-TTY (CI, piped install) -> use the default, never block.
144
+ if (!stdin.isTTY) return defaultYes;
145
+ const suffix = defaultYes ? " [Y/n] " : " [y/N] ";
146
+ const rl = createInterface({ input: stdin, output: stdout });
147
+ const ans = await new Promise(r => rl.question(question + suffix, r));
148
+ rl.close();
149
+ const a = ans.trim().toLowerCase();
150
+ if (!a) return defaultYes;
151
+ return a === "y" || a === "yes" || a === "o" || a === "oui";
152
+ }
153
+
154
+ // ─── observability opt-in (Phase 1.5 of the install contract, §10 of doc) ──
155
+
156
+ function installObservabilityStack() {
157
+ console.log();
158
+ console.log(c.bold(c.cyan("Observability stack")));
159
+ console.log(c.dim(
160
+ " Optional cross-session daemon + TUI client that aggregates the event\n" +
161
+ " bus into a live snapshot. Read docs/design/MISHKAN_observability.md.\n" +
162
+ " Requires `uv` (https://astral.sh/uv) and Python 3.11+."));
163
+
164
+ if (!commandExists("uv")) {
165
+ console.log(c.yellow(" uv not found — skipping observability install."));
166
+ console.log(c.dim(
167
+ " Install uv with:\n" +
168
+ " curl -LsSf https://astral.sh/uv/install.sh | sh\n" +
169
+ " Then re-run: npx mishkan-harness observability"));
170
+ return { installed: false, reason: "uv-missing" };
171
+ }
172
+
173
+ const watchdSrc = join(PKG, "payload", "mishkan", "observability", "watchd");
174
+ const watchSrc = join(PKG, "payload", "mishkan", "observability", "watch");
175
+ for (const dir of [watchdSrc, watchSrc]) {
176
+ if (!existsSync(dir)) {
177
+ warn(`observability source missing: ${tilde(dir)}`);
178
+ return { installed: false, reason: "payload-missing" };
179
+ }
180
+ }
181
+
182
+ console.log(c.dim(" Installing mishkan-watchd (daemon)…"));
183
+ const r1 = spawnSync("uv", ["tool", "install", "--from", watchdSrc, "mishkan-watchd"],
184
+ { stdio: "inherit" });
185
+ if (r1.status !== 0) { warn("mishkan-watchd install failed"); return { installed: false, reason: "install-failed" }; }
186
+
187
+ console.log(c.dim(" Installing mishkan-watch (TUI client)…"));
188
+ const r2 = spawnSync("uv", ["tool", "install", "--from", watchSrc, "mishkan-watch"],
189
+ { stdio: "inherit" });
190
+ if (r2.status !== 0) { warn("mishkan-watch install failed"); return { installed: false, reason: "install-failed" }; }
191
+
192
+ console.log(c.green(" ✓ observability stack installed"));
193
+ console.log(c.dim(
194
+ " Start the daemon: " + c.bold("mishkan-watchd start") + "\n" +
195
+ " Open the TUI: " + c.bold("mishkan-watch") + "\n" +
196
+ " Auto-start unit: mishkan-watchd install-service"));
197
+ return { installed: true };
198
+ }
199
+
200
+ async function install() {
201
+ const totalPhases = 7;
110
202
  log(`installing into ${tilde(CLAUDE)} (home resolved at runtime)`);
111
203
  ensureDir(CLAUDE);
204
+
205
+ phase(1, totalPhases, "Payload",
206
+ "copy agents, skills, commands, hooks, rules, cognee compose, observability sources");
112
207
  // 1. payload/mishkan -> ~/.claude/mishkan
113
208
  copyDir(join(PKG, "payload", "mishkan"), MISHKAN);
114
209
  ensureDir(join(MISHKAN, "logs"));
115
210
  ensureDir(join(MISHKAN, "cognee"));
116
- // 2. engineer profile -> runtime. Prefer a real (gitignored) profile.md if the
117
- // engineer made one; otherwise ship the sanitized example. Never overwrite an
118
- // existing runtime profile the engineer may have edited in place.
211
+ phase(2, totalPhases, "Engineer profile",
212
+ "place runtime profile never overwrites an existing edited one");
119
213
  const realProfile = join(PKG, "docs", "engineer", "profile.md");
120
214
  const exampleProfile = join(PKG, "docs", "engineer", "profile.example.md");
121
215
  const runtimeProfile = join(MISHKAN, "profile.md");
@@ -126,33 +220,59 @@ function install() {
126
220
  } else {
127
221
  log("preserved existing ~/.claude/mishkan/profile.md");
128
222
  }
129
- // 3. user-level files
223
+
224
+ phase(3, totalPhases, "User-level rules",
225
+ "refresh harness default; preserve your engineer-standards.md + CLAUDE.md");
130
226
  ensureDir(join(CLAUDE, "rules"));
131
- // y4nn-standards.md is the harness-maintained DEFAULT — always refreshed so
132
- // updates flow. Customisation lives in engineer-standards.md instead.
133
227
  copyFileSync(join(PKG, "payload", "user", "rules", "y4nn-standards.md"),
134
228
  join(CLAUDE, "rules", "y4nn-standards.md"));
135
229
  log("refreshed harness default ~/.claude/rules/y4nn-standards.md");
136
- // engineer-standards.md is the USER's layer — placed once, never overwritten.
137
230
  const engRule = join(CLAUDE, "rules", "engineer-standards.md");
138
231
  if (!existsSync(engRule)) copyFileSync(join(PKG, "payload", "user", "rules", "engineer-standards.md"), engRule);
139
232
  else log("preserved your ~/.claude/rules/engineer-standards.md");
140
- // user CLAUDE.md — placed once, never overwritten.
141
233
  const userClaude = join(CLAUDE, "CLAUDE.md");
142
234
  if (!existsSync(userClaude)) copyFileSync(join(PKG, "payload", "user", "CLAUDE.md"), userClaude);
143
235
  else log("preserved existing ~/.claude/CLAUDE.md");
144
- // 4. symlinks for discovery
236
+
237
+ phase(4, totalPhases, "Discovery symlinks",
238
+ "make agents, skills, commands visible to Claude Code");
145
239
  const a = linkInto("agents", join(CLAUDE, "agents"), false);
146
240
  const s = linkInto("skills", join(CLAUDE, "skills"), true);
147
- const c = linkInto("commands", join(CLAUDE, "commands"), false);
148
- log(`linked agents=${a.linked} (skipped ${a.skipped}), skills=${s.linked}, commands=${c.linked}`);
149
- // 5. hooks
241
+ const cm = linkInto("commands", join(CLAUDE, "commands"), false);
242
+ log(`linked agents=${a.linked} (skipped ${a.skipped}), skills=${s.linked}, commands=${cm.linked}`);
243
+
244
+ phase(5, totalPhases, "Hooks",
245
+ "merge MISHKAN hooks into ~/.claude/settings.json (preserves existing)");
150
246
  mergeHooks();
151
- log("hooks merged into settings.json (existing hooks preserved)");
152
- // 6. stamp
247
+ log("hooks merged into settings.json");
248
+
249
+ phase(6, totalPhases, "Stamp",
250
+ "record install version + timestamp for status / uninstall");
153
251
  const version = JSON.parse(readFileSync(join(PKG, "package.json"), "utf8")).version;
154
252
  writeFileSync(STAMP, JSON.stringify({ version, installedAt: new Date().toISOString() }, null, 2) + "\n");
155
- log(`installed v${version}. Run a Claude session and talk to Nehemiah, or /mishkan-init in a project.`);
253
+
254
+ phase(7, totalPhases, "Observability (opt-in)",
255
+ "Python daemon + Textual TUI for live cross-session monitoring");
256
+ if (await promptYN(" Install observability stack now?", true)) {
257
+ installObservabilityStack();
258
+ } else {
259
+ console.log(c.dim(" Skipped. Re-run later: npx mishkan-harness observability"));
260
+ }
261
+
262
+ console.log();
263
+ console.log(c.green(`✓ MISHKAN v${version} installed.`));
264
+ console.log(c.dim(
265
+ " Run a Claude session and talk to Nehemiah, or /mishkan-init in a project.\n" +
266
+ " Status: npx mishkan-harness status\n" +
267
+ " Re-add obs stack: npx mishkan-harness observability"));
268
+ }
269
+
270
+ function uninstallObservabilityHint() {
271
+ if (commandExists("uv")) {
272
+ console.log(c.dim(
273
+ " Observability stack installed via uv tool — remove manually if desired:\n" +
274
+ " uv tool uninstall mishkan-watch mishkan-watchd"));
275
+ }
156
276
  }
157
277
 
158
278
  function uninstall({ purge = false } = {}) {
@@ -180,6 +300,7 @@ function uninstall({ purge = false } = {}) {
180
300
  } else {
181
301
  log("kept user-level CLAUDE.md, y4nn-standards.md, and engineer-standards.md (use --purge to remove the default).");
182
302
  }
303
+ uninstallObservabilityHint();
183
304
  }
184
305
 
185
306
  function status() {
@@ -208,14 +329,16 @@ function status() {
208
329
  const cmd = process.argv[2];
209
330
  const flags = new Set(process.argv.slice(3));
210
331
  switch (cmd) {
211
- case "install": install(); break;
332
+ case "install": await install(); break;
212
333
  case "uninstall": uninstall({ purge: flags.has("--purge") }); break;
213
334
  case "status": status(); break;
335
+ case "observability": installObservabilityStack(); break;
214
336
  default:
215
337
  console.log(`MISHKAN harness installer
216
338
  Usage:
217
- npx mishkan-harness install Install/refresh into ~/.claude (idempotent)
218
- npx mishkan-harness status Show install state
219
- npx mishkan-harness uninstall Remove harness (keeps your CLAUDE.md & rules)
339
+ npx mishkan-harness install Install/refresh into ~/.claude (idempotent)
340
+ npx mishkan-harness observability Install only the observability stack (daemon + TUI, needs uv)
341
+ npx mishkan-harness status Show install state
342
+ npx mishkan-harness uninstall Remove harness (keeps your CLAUDE.md & rules)
220
343
  npx mishkan-harness uninstall --purge Also remove user-level rule`);
221
344
  }