@nocville/npc 0.0.0-stage → 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 (210) hide show
  1. package/README.md +243 -2
  2. package/dist/cjs/cli.js +533 -0
  3. package/dist/cjs/core/api-key.js +69 -0
  4. package/dist/cjs/core/base-npc.js +160 -0
  5. package/dist/cjs/core/config.js +108 -0
  6. package/dist/cjs/core/event-npc.js +14 -0
  7. package/dist/cjs/core/logger.js +39 -0
  8. package/dist/cjs/core/polling-npc.js +65 -0
  9. package/dist/cjs/core/screen.js +28 -0
  10. package/dist/cjs/core/text.js +15 -0
  11. package/dist/cjs/core/thresholds.js +48 -0
  12. package/dist/cjs/core/types.js +10 -0
  13. package/dist/cjs/core/user-config.js +92 -0
  14. package/dist/cjs/doctor.js +130 -0
  15. package/dist/cjs/index.js +90 -0
  16. package/dist/cjs/npcs/agent-common/activity.js +291 -0
  17. package/dist/cjs/npcs/agent-common/git-stats.js +71 -0
  18. package/dist/cjs/npcs/agent-common/hook-runner.js +252 -0
  19. package/dist/cjs/npcs/agent-common/hook-settings.js +279 -0
  20. package/dist/cjs/npcs/agent-common/liveness.js +112 -0
  21. package/dist/cjs/npcs/agent-common/lock.js +99 -0
  22. package/dist/cjs/npcs/agent-common/runtime.js +154 -0
  23. package/dist/cjs/npcs/agent-common/session.js +390 -0
  24. package/dist/cjs/npcs/agent-common/shim.js +96 -0
  25. package/dist/cjs/npcs/agent-common/watchdog.js +149 -0
  26. package/dist/cjs/npcs/claude-code/defaults.js +20 -0
  27. package/dist/cjs/npcs/claude-code/hook-handler.js +139 -0
  28. package/dist/cjs/npcs/claude-code/index.js +104 -0
  29. package/dist/cjs/npcs/claude-code/setup.js +133 -0
  30. package/dist/cjs/npcs/claude-code/state-machine.js +130 -0
  31. package/dist/cjs/npcs/codex/defaults.js +32 -0
  32. package/dist/cjs/npcs/codex/hook-handler.js +116 -0
  33. package/dist/cjs/npcs/codex/index.js +92 -0
  34. package/dist/cjs/npcs/codex/rollout.js +246 -0
  35. package/dist/cjs/npcs/codex/setup.js +104 -0
  36. package/dist/cjs/npcs/codex/watcher.js +346 -0
  37. package/dist/cjs/npcs/cursor/defaults.js +15 -0
  38. package/dist/cjs/npcs/cursor/hook-handler.js +65 -0
  39. package/dist/cjs/npcs/cursor/index.js +72 -0
  40. package/dist/cjs/npcs/cursor/normalize.js +54 -0
  41. package/dist/cjs/npcs/cursor/setup.js +98 -0
  42. package/dist/cjs/npcs/gemini/defaults.js +15 -0
  43. package/dist/cjs/npcs/gemini/hook-handler.js +93 -0
  44. package/dist/cjs/npcs/gemini/index.js +69 -0
  45. package/dist/cjs/npcs/gemini/setup.js +98 -0
  46. package/dist/cjs/npcs/github-watcher/defaults.js +11 -0
  47. package/dist/cjs/npcs/github-watcher/format.js +21 -0
  48. package/dist/cjs/npcs/github-watcher/github-api.js +126 -0
  49. package/dist/cjs/npcs/github-watcher/index.js +148 -0
  50. package/dist/cjs/npcs/internet-connectivity/checks.js +92 -0
  51. package/dist/cjs/npcs/internet-connectivity/defaults.js +22 -0
  52. package/dist/cjs/npcs/internet-connectivity/index.js +251 -0
  53. package/dist/cjs/npcs/log-watcher/buffer.js +40 -0
  54. package/dist/cjs/npcs/log-watcher/defaults.js +12 -0
  55. package/dist/cjs/npcs/log-watcher/index.js +161 -0
  56. package/dist/cjs/npcs/log-watcher/tail.js +81 -0
  57. package/dist/cjs/npcs/process-watcher/defaults.js +9 -0
  58. package/dist/cjs/npcs/process-watcher/index.js +140 -0
  59. package/dist/cjs/npcs/process-watcher/process-scanner.js +114 -0
  60. package/dist/cjs/npcs/receptionist/defaults.js +9 -0
  61. package/dist/cjs/npcs/receptionist/index.js +120 -0
  62. package/dist/cjs/npcs/receptionist/weather.js +129 -0
  63. package/dist/cjs/npcs/system-monitor/collectors.js +68 -0
  64. package/dist/cjs/npcs/system-monitor/defaults.js +13 -0
  65. package/dist/cjs/npcs/system-monitor/index.js +105 -0
  66. package/dist/cjs/npcs/web-page-watcher/basic-mode.js +58 -0
  67. package/dist/cjs/npcs/web-page-watcher/capture.js +80 -0
  68. package/dist/cjs/npcs/web-page-watcher/defaults.js +31 -0
  69. package/dist/cjs/npcs/web-page-watcher/index.js +234 -0
  70. package/dist/cjs/npcs/web-page-watcher/pixelate.js +181 -0
  71. package/dist/cjs/package.json +1 -0
  72. package/dist/cli.d.ts +3 -0
  73. package/dist/cli.js +498 -0
  74. package/dist/core/api-key.d.ts +34 -0
  75. package/dist/core/api-key.js +64 -0
  76. package/dist/core/base-npc.d.ts +41 -0
  77. package/dist/core/base-npc.js +156 -0
  78. package/dist/core/config.d.ts +22 -0
  79. package/dist/core/config.js +101 -0
  80. package/dist/core/event-npc.d.ts +8 -0
  81. package/dist/core/event-npc.js +10 -0
  82. package/dist/core/logger.d.ts +11 -0
  83. package/dist/core/logger.js +35 -0
  84. package/dist/core/polling-npc.d.ts +18 -0
  85. package/dist/core/polling-npc.js +61 -0
  86. package/dist/core/screen.d.ts +11 -0
  87. package/dist/core/screen.js +24 -0
  88. package/dist/core/text.d.ts +6 -0
  89. package/dist/core/text.js +12 -0
  90. package/dist/core/thresholds.d.ts +10 -0
  91. package/dist/core/thresholds.js +44 -0
  92. package/dist/core/types.d.ts +60 -0
  93. package/dist/core/types.js +7 -0
  94. package/dist/core/user-config.d.ts +43 -0
  95. package/dist/core/user-config.js +85 -0
  96. package/dist/doctor.d.ts +8 -0
  97. package/dist/doctor.js +126 -0
  98. package/dist/index.d.ts +39 -0
  99. package/dist/index.js +25 -0
  100. package/dist/npcs/agent-common/activity.d.ts +42 -0
  101. package/dist/npcs/agent-common/activity.js +280 -0
  102. package/dist/npcs/agent-common/git-stats.d.ts +11 -0
  103. package/dist/npcs/agent-common/git-stats.js +67 -0
  104. package/dist/npcs/agent-common/hook-runner.d.ts +62 -0
  105. package/dist/npcs/agent-common/hook-runner.js +245 -0
  106. package/dist/npcs/agent-common/hook-settings.d.ts +75 -0
  107. package/dist/npcs/agent-common/hook-settings.js +265 -0
  108. package/dist/npcs/agent-common/liveness.d.ts +12 -0
  109. package/dist/npcs/agent-common/liveness.js +105 -0
  110. package/dist/npcs/agent-common/lock.d.ts +6 -0
  111. package/dist/npcs/agent-common/lock.js +95 -0
  112. package/dist/npcs/agent-common/runtime.d.ts +16 -0
  113. package/dist/npcs/agent-common/runtime.js +149 -0
  114. package/dist/npcs/agent-common/session.d.ts +184 -0
  115. package/dist/npcs/agent-common/session.js +368 -0
  116. package/dist/npcs/agent-common/shim.d.ts +4 -0
  117. package/dist/npcs/agent-common/shim.js +90 -0
  118. package/dist/npcs/agent-common/watchdog.d.ts +16 -0
  119. package/dist/npcs/agent-common/watchdog.js +144 -0
  120. package/dist/npcs/claude-code/defaults.d.ts +15 -0
  121. package/dist/npcs/claude-code/defaults.js +17 -0
  122. package/dist/npcs/claude-code/hook-handler.d.ts +40 -0
  123. package/dist/npcs/claude-code/hook-handler.js +131 -0
  124. package/dist/npcs/claude-code/index.d.ts +36 -0
  125. package/dist/npcs/claude-code/index.js +92 -0
  126. package/dist/npcs/claude-code/setup.d.ts +58 -0
  127. package/dist/npcs/claude-code/setup.js +121 -0
  128. package/dist/npcs/claude-code/state-machine.d.ts +28 -0
  129. package/dist/npcs/claude-code/state-machine.js +125 -0
  130. package/dist/npcs/codex/defaults.d.ts +24 -0
  131. package/dist/npcs/codex/defaults.js +28 -0
  132. package/dist/npcs/codex/hook-handler.d.ts +24 -0
  133. package/dist/npcs/codex/hook-handler.js +110 -0
  134. package/dist/npcs/codex/index.d.ts +21 -0
  135. package/dist/npcs/codex/index.js +76 -0
  136. package/dist/npcs/codex/rollout.d.ts +39 -0
  137. package/dist/npcs/codex/rollout.js +242 -0
  138. package/dist/npcs/codex/setup.d.ts +37 -0
  139. package/dist/npcs/codex/setup.js +96 -0
  140. package/dist/npcs/codex/watcher.d.ts +62 -0
  141. package/dist/npcs/codex/watcher.js +341 -0
  142. package/dist/npcs/cursor/defaults.d.ts +9 -0
  143. package/dist/npcs/cursor/defaults.js +11 -0
  144. package/dist/npcs/cursor/hook-handler.d.ts +8 -0
  145. package/dist/npcs/cursor/hook-handler.js +60 -0
  146. package/dist/npcs/cursor/index.d.ts +18 -0
  147. package/dist/npcs/cursor/index.js +57 -0
  148. package/dist/npcs/cursor/normalize.d.ts +6 -0
  149. package/dist/npcs/cursor/normalize.js +49 -0
  150. package/dist/npcs/cursor/setup.d.ts +29 -0
  151. package/dist/npcs/cursor/setup.js +90 -0
  152. package/dist/npcs/gemini/defaults.d.ts +9 -0
  153. package/dist/npcs/gemini/defaults.js +11 -0
  154. package/dist/npcs/gemini/hook-handler.d.ts +10 -0
  155. package/dist/npcs/gemini/hook-handler.js +87 -0
  156. package/dist/npcs/gemini/index.d.ts +17 -0
  157. package/dist/npcs/gemini/index.js +56 -0
  158. package/dist/npcs/gemini/setup.d.ts +29 -0
  159. package/dist/npcs/gemini/setup.js +90 -0
  160. package/dist/npcs/github-watcher/defaults.d.ts +8 -0
  161. package/dist/npcs/github-watcher/defaults.js +8 -0
  162. package/dist/npcs/github-watcher/format.d.ts +3 -0
  163. package/dist/npcs/github-watcher/format.js +18 -0
  164. package/dist/npcs/github-watcher/github-api.d.ts +55 -0
  165. package/dist/npcs/github-watcher/github-api.js +120 -0
  166. package/dist/npcs/github-watcher/index.d.ts +35 -0
  167. package/dist/npcs/github-watcher/index.js +144 -0
  168. package/dist/npcs/internet-connectivity/checks.d.ts +24 -0
  169. package/dist/npcs/internet-connectivity/checks.js +84 -0
  170. package/dist/npcs/internet-connectivity/defaults.d.ts +20 -0
  171. package/dist/npcs/internet-connectivity/defaults.js +19 -0
  172. package/dist/npcs/internet-connectivity/index.d.ts +55 -0
  173. package/dist/npcs/internet-connectivity/index.js +246 -0
  174. package/dist/npcs/log-watcher/buffer.d.ts +18 -0
  175. package/dist/npcs/log-watcher/buffer.js +34 -0
  176. package/dist/npcs/log-watcher/defaults.d.ts +9 -0
  177. package/dist/npcs/log-watcher/defaults.js +9 -0
  178. package/dist/npcs/log-watcher/index.d.ts +45 -0
  179. package/dist/npcs/log-watcher/index.js +157 -0
  180. package/dist/npcs/log-watcher/tail.d.ts +9 -0
  181. package/dist/npcs/log-watcher/tail.js +78 -0
  182. package/dist/npcs/process-watcher/defaults.d.ts +6 -0
  183. package/dist/npcs/process-watcher/defaults.js +6 -0
  184. package/dist/npcs/process-watcher/index.d.ts +26 -0
  185. package/dist/npcs/process-watcher/index.js +136 -0
  186. package/dist/npcs/process-watcher/process-scanner.d.ts +19 -0
  187. package/dist/npcs/process-watcher/process-scanner.js +110 -0
  188. package/dist/npcs/receptionist/defaults.d.ts +6 -0
  189. package/dist/npcs/receptionist/defaults.js +6 -0
  190. package/dist/npcs/receptionist/index.d.ts +29 -0
  191. package/dist/npcs/receptionist/index.js +116 -0
  192. package/dist/npcs/receptionist/weather.d.ts +37 -0
  193. package/dist/npcs/receptionist/weather.js +121 -0
  194. package/dist/npcs/system-monitor/collectors.d.ts +45 -0
  195. package/dist/npcs/system-monitor/collectors.js +60 -0
  196. package/dist/npcs/system-monitor/defaults.d.ts +7 -0
  197. package/dist/npcs/system-monitor/defaults.js +10 -0
  198. package/dist/npcs/system-monitor/index.d.ts +27 -0
  199. package/dist/npcs/system-monitor/index.js +101 -0
  200. package/dist/npcs/web-page-watcher/basic-mode.d.ts +16 -0
  201. package/dist/npcs/web-page-watcher/basic-mode.js +52 -0
  202. package/dist/npcs/web-page-watcher/capture.d.ts +49 -0
  203. package/dist/npcs/web-page-watcher/capture.js +75 -0
  204. package/dist/npcs/web-page-watcher/defaults.d.ts +19 -0
  205. package/dist/npcs/web-page-watcher/defaults.js +27 -0
  206. package/dist/npcs/web-page-watcher/index.d.ts +42 -0
  207. package/dist/npcs/web-page-watcher/index.js +230 -0
  208. package/dist/npcs/web-page-watcher/pixelate.d.ts +36 -0
  209. package/dist/npcs/web-page-watcher/pixelate.js +172 -0
  210. package/package.json +73 -3
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RUNTIME_POINTER = exports.SHIM_PATH = void 0;
4
+ exports.installHookShim = installHookShim;
5
+ const node_fs_1 = require("node:fs");
6
+ const node_path_1 = require("node:path");
7
+ const user_config_js_1 = require("../../core/user-config.js");
8
+ const runtime_js_1 = require("./runtime.js");
9
+ const SHIM_PATH = () => (0, node_path_1.join)((0, node_path_1.dirname)((0, user_config_js_1.userConfigPath)()), 'bin', 'nocville-hook');
10
+ exports.SHIM_PATH = SHIM_PATH;
11
+ const RUNTIME_POINTER = () => (0, node_path_1.join)((0, node_path_1.dirname)((0, user_config_js_1.userConfigPath)()), 'runtime', 'current.json');
12
+ exports.RUNTIME_POINTER = RUNTIME_POINTER;
13
+ function writeChanged(path, content, mode) {
14
+ try {
15
+ if ((0, node_fs_1.readFileSync)(path, 'utf8') === content)
16
+ return;
17
+ }
18
+ catch {
19
+ /* first install */
20
+ }
21
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true, mode: 0o700 });
22
+ const tmp = `${path}.${process.pid}.tmp`;
23
+ (0, node_fs_1.writeFileSync)(tmp, content, { mode });
24
+ (0, node_fs_1.renameSync)(tmp, path);
25
+ }
26
+ // Resolve relative to the shim itself, including custom XDG/NOCVILLE_CONFIG installs.
27
+ // JSON is parsed by Node, never interpolated into shell code or decoded with sed.
28
+ const SHIM = `#!/bin/sh
29
+ # nocville-npc-hook: stable one-way hook launcher
30
+ DIR=$(CDPATH= cd -- "$(dirname -- "$0")" 2>/dev/null && pwd) || exit 0
31
+ IFS= read -r NODE < "$DIR/../runtime/node-path" 2>/dev/null || NODE=''
32
+ if [ ! -x "$NODE" ]; then NODE=$(command -v node 2>/dev/null); fi
33
+ for candidate in "$NODE" /opt/homebrew/bin/node /usr/local/bin/node "$HOME/.volta/bin/node"; do
34
+ if [ -x "$candidate" ]; then NODE="$candidate"; break; fi
35
+ done
36
+ if [ ! -x "$NODE" ]; then cat >/dev/null; if { [ "$1" = gemini ] || [ "$1" = cursor ] || [ "$1" = claude-code ]; }; then printf '{}\\n'; fi; exit 0; fi
37
+ exec "$NODE" "$DIR/launch.cjs" "$DIR/../runtime/current.json" "$@"
38
+ `;
39
+ // Without Node, return a neutral empty object for both Claude and imported Cursor.
40
+ // No payload is evaluated by either shell. Native Codex remains silent.
41
+ const WINDOWS_SHIM = String.raw `@echo off
42
+ setlocal DisableDelayedExpansion
43
+ set "NOCVILLE_NODE="
44
+ set /p "NOCVILLE_NODE="<"%~dp0..\runtime\node-path" 2>nul
45
+ if exist "%NOCVILLE_NODE%" goto launch
46
+ set "NOCVILLE_NODE="
47
+ for /f "delims=" %%N in ('where node.exe 2^>nul') do if not defined NOCVILLE_NODE set "NOCVILLE_NODE=%%N"
48
+ if not defined NOCVILLE_NODE goto no_node
49
+ :launch
50
+ "%NOCVILLE_NODE%" "%~dp0launch.cjs" "%~dp0..\runtime\current.json" %*
51
+ exit /b 0
52
+ :no_node
53
+ more >nul
54
+ if "%~1"=="cursor" echo {}
55
+ if "%~1"=="gemini" echo {}
56
+ if "%~1"=="claude-code" echo {}
57
+ exit /b 0
58
+ `;
59
+ const LAUNCHER = `// Stable bootstrap. Hooks never return decisions or emit launcher errors.
60
+ const fs = require('node:fs');
61
+ const { spawnSync } = require('node:child_process');
62
+ const agent = process.argv[3];
63
+ const hook = !process.argv[4] || process.argv[4] === 'hook';
64
+ let neutral = hook && (agent === 'gemini' || agent === 'cursor');
65
+ let input = Buffer.alloc(0);
66
+ try {
67
+ input = fs.readFileSync(0);
68
+ if (hook && agent === 'claude-code') {
69
+ try {
70
+ const event = JSON.parse(input.toString('utf8'));
71
+ const transcript = typeof event?.transcript_path === 'string' ? event.transcript_path.replace(/\\\\/g, '/') : '';
72
+ neutral = typeof event?.cursor_version === 'string' ||
73
+ (typeof event?.conversation_id === 'string' && !/(?:^|\\/)\\.claude(?:\\/|$)/.test(transcript));
74
+ } catch {}
75
+ }
76
+ const pointer = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
77
+ if (typeof pointer.cli !== 'string' || !fs.existsSync(pointer.cli)) throw new Error('missing runtime');
78
+ const node = typeof pointer.node === 'string' && fs.existsSync(pointer.node) ? pointer.node : process.execPath;
79
+ // Buffer stdin once for payload-based imported-Cursor detection, then replay it unchanged.
80
+ // Never forward child stdout, even in debug mode or on an unexpected failure.
81
+ spawnSync(node, [pointer.cli, ...process.argv.slice(3)], { input, stdio: ['pipe', 'ignore', 'inherit'], windowsHide: true });
82
+ } catch {}
83
+ if (neutral) process.stdout.write('{}\\n');
84
+ `;
85
+ function installHookShim(nodePath, cliPath) {
86
+ writeChanged((0, exports.RUNTIME_POINTER)(), JSON.stringify({ node: nodePath, cli: cliPath }, null, 2) + '\n', 0o600);
87
+ writeChanged((0, node_path_1.join)((0, node_path_1.dirname)((0, exports.RUNTIME_POINTER)()), 'node-path'), nodePath + '\n', 0o600);
88
+ writeChanged((0, node_path_1.join)((0, node_path_1.dirname)((0, exports.SHIM_PATH)()), 'launch.cjs'), LAUNCHER, 0o600);
89
+ writeChanged((0, exports.SHIM_PATH)(), SHIM, 0o755);
90
+ (0, node_fs_1.chmodSync)((0, exports.SHIM_PATH)(), 0o755);
91
+ // Both launchers use the same bootstrap, including imported-Cursor detection.
92
+ writeChanged((0, exports.SHIM_PATH)() + '.cmd', WINDOWS_SHIM.replace(/\n/g, '\r\n'), 0o755);
93
+ (0, runtime_js_1.pruneOldRuntimes)((0, node_path_1.dirname)((0, exports.RUNTIME_POINTER)()));
94
+ return (0, exports.SHIM_PATH)();
95
+ }
96
+ //# sourceMappingURL=shim.js.map
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WATCHDOG_OFFLINE_RETRY_MS = exports.WATCHDOG_HEARTBEAT_MS = exports.WATCHDOG_TICK_MS = void 0;
4
+ exports.ensureWatchdog = ensureWatchdog;
5
+ exports.runWatchdog = runWatchdog;
6
+ const liveness_js_1 = require("./liveness.js");
7
+ const lock_js_1 = require("./lock.js");
8
+ const session_js_1 = require("./session.js");
9
+ exports.WATCHDOG_TICK_MS = 15_000;
10
+ exports.WATCHDOG_HEARTBEAT_MS = 60_000;
11
+ exports.WATCHDOG_OFFLINE_RETRY_MS = 15 * 60_000;
12
+ async function ensureWatchdog(adapter, config, sessionId, cliPath, lockWaitMs) {
13
+ if (adapter.processNames?.length === 0 ||
14
+ !config.apiKey ||
15
+ process.env['NOCVILLE_NO_WATCHDOG'] === '1')
16
+ return;
17
+ await (0, lock_js_1.withStateLock)((0, session_js_1.stateFilePath)(adapter.stateDir, sessionId), () => {
18
+ const state = (0, session_js_1.readSessionState)(adapter.stateDir, sessionId);
19
+ // No heartbeat without proven liveness. Keep the long fallback on unsupported hosts.
20
+ if (!state || state.offline || !state.agentPid)
21
+ return;
22
+ if (state.watchdogPid && (0, liveness_js_1.isAgentAlive)({ pid: state.watchdogPid }))
23
+ return;
24
+ const pid = (0, session_js_1.spawnDetachedAgent)(adapter, config, ['watchdog', sessionId], cliPath);
25
+ if (pid) {
26
+ state.watchdogPid = pid;
27
+ (0, session_js_1.writeSessionState)(adapter.stateDir, state);
28
+ }
29
+ }, lockWaitMs);
30
+ }
31
+ /** Silent, read-only observer of the agent process; never sends input to it. */
32
+ async function runWatchdog(adapter, config, sessionId, deps = {}) {
33
+ if (!sessionId || adapter.processNames?.length === 0)
34
+ return;
35
+ const isAlive = deps.isAlive ?? liveness_js_1.isAgentAlive;
36
+ const now = deps.now ?? Date.now;
37
+ const wait = deps.wait ?? session_js_1.sleep;
38
+ const selfPid = deps.selfPid ?? process.pid;
39
+ let unknownTicks = 0;
40
+ let rejected = false;
41
+ let heartbeatRejected = false;
42
+ const delivery = {
43
+ ...deps,
44
+ onResult: (result) => {
45
+ if (!result.success && [400, 401, 403, 404].includes(result.statusCode ?? 0))
46
+ rejected = true;
47
+ deps.onResult?.(result);
48
+ },
49
+ };
50
+ // Do not clear a replacement observer's ownership. A failed write retries on the next tick.
51
+ const finish = async (terminal = false, abandoned = false) => {
52
+ await (0, lock_js_1.withStateLock)((0, session_js_1.stateFilePath)(adapter.stateDir, sessionId), () => {
53
+ const fresh = (0, session_js_1.readSessionState)(adapter.stateDir, sessionId);
54
+ if (!fresh || (fresh.watchdogPid && fresh.watchdogPid !== selfPid))
55
+ return;
56
+ if (terminal && !fresh.offline) {
57
+ fresh.offline = true;
58
+ fresh.rev = (fresh.rev ?? 0) + 1;
59
+ }
60
+ if (abandoned)
61
+ fresh.offlineDeliveryStopped = true;
62
+ fresh.watchdogPid = undefined;
63
+ (0, session_js_1.writeSessionState)(adapter.stateDir, fresh);
64
+ });
65
+ };
66
+ for (;;) {
67
+ await wait(exports.WATCHDOG_TICK_MS);
68
+ try {
69
+ const state = (0, session_js_1.readSessionState)(adapter.stateDir, sessionId);
70
+ if (!state)
71
+ return;
72
+ if (state.watchdogPid && state.watchdogPid !== selfPid)
73
+ return;
74
+ if (rejected || state.offlineDeliveryStopped) {
75
+ await finish(true, true);
76
+ return;
77
+ }
78
+ if (heartbeatRejected) {
79
+ await finish();
80
+ return;
81
+ }
82
+ if (!state.offline && !state.agentPid) {
83
+ if (++unknownTicks >= 3) {
84
+ await finish();
85
+ return;
86
+ }
87
+ continue;
88
+ }
89
+ const terminal = state.offline || !isAlive({ pid: state.agentPid, startedAt: state.agentStartedAt });
90
+ if (terminal) {
91
+ if (state.offline && (state.pushedRev ?? 0) >= (state.rev ?? 0)) {
92
+ await finish();
93
+ return;
94
+ }
95
+ const startedAt = await (0, lock_js_1.withStateLock)((0, session_js_1.stateFilePath)(adapter.stateDir, sessionId), () => {
96
+ const fresh = (0, session_js_1.readSessionState)(adapter.stateDir, sessionId);
97
+ if (!fresh ||
98
+ fresh.agentPid !== state.agentPid ||
99
+ (fresh.watchdogPid && fresh.watchdogPid !== selfPid))
100
+ return undefined;
101
+ if (!fresh.offline) {
102
+ fresh.offline = true;
103
+ fresh.rev = (fresh.rev ?? 0) + 1;
104
+ }
105
+ fresh.offlineRetryStartedAt ??= now();
106
+ (0, session_js_1.writeSessionState)(adapter.stateDir, fresh);
107
+ return fresh.offlineRetryStartedAt;
108
+ });
109
+ if (startedAt === undefined)
110
+ continue;
111
+ if (now() - startedAt >= exports.WATCHDOG_OFFLINE_RETRY_MS) {
112
+ await finish(true, true);
113
+ return;
114
+ }
115
+ if (await (0, session_js_1.flushSession)(adapter, config, sessionId, delivery)) {
116
+ await finish();
117
+ return;
118
+ }
119
+ }
120
+ else if ((state.rev ?? 0) > (state.pushedRev ?? 0) ||
121
+ now() - (state.lastPushAt ?? 0) >= exports.WATCHDOG_HEARTBEAT_MS) {
122
+ await (0, session_js_1.flushSession)(adapter, config, sessionId, {
123
+ ...deps,
124
+ force: true,
125
+ onResult: (result) => {
126
+ if (!result.success && [400, 401, 403, 404].includes(result.statusCode ?? 0))
127
+ heartbeatRejected = true;
128
+ deps.onResult?.(result);
129
+ },
130
+ });
131
+ if (heartbeatRejected) {
132
+ // A rejected heartbeat is not evidence the agent died. A later hook
133
+ // can restart observation after credentials/routing recover.
134
+ await finish();
135
+ return;
136
+ }
137
+ }
138
+ if (rejected) {
139
+ await finish(true, true);
140
+ return;
141
+ }
142
+ }
143
+ catch {
144
+ // A transient state-lock/filesystem error must not kill an otherwise live observer.
145
+ continue;
146
+ }
147
+ }
148
+ }
149
+ //# sourceMappingURL=watchdog.js.map
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULTS = void 0;
4
+ const node_os_1 = require("node:os");
5
+ const node_path_1 = require("node:path");
6
+ exports.DEFAULTS = {
7
+ stateDir: (0, node_path_1.join)((0, node_os_1.tmpdir)(), 'nocville-claude-code'),
8
+ stateFilePrefix: 'session-',
9
+ stateFileMaxAge: 24 * 60 * 60 * 1000, // 24 hours
10
+ httpTimeout: 4000,
11
+ /** Watchdog-backed sessions become unknown after three minutes without a heartbeat. */
12
+ heartbeatSeconds: 90,
13
+ /** Number of recent activity lines kept in the desk-sign ticker. */
14
+ activityBufferSize: 8,
15
+ /** Coalesce same-phase activity pushes closer together than this. */
16
+ minPushGapMs: 1500,
17
+ /** Per-hook timeout written into settings.json (seconds) — a safety net only. */
18
+ hookTimeoutSeconds: 10,
19
+ };
20
+ //# sourceMappingURL=defaults.js.map
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CLAUDE_HOOK_SPEC = exports.CLAUDE_ADAPTER = void 0;
4
+ exports.deriveServiceId = deriveServiceId;
5
+ exports.humanizeProjectName = humanizeProjectName;
6
+ exports.lookupSessionNameFromStore = lookupSessionNameFromStore;
7
+ exports.resolveSessionName = resolveSessionName;
8
+ exports.handleHookEvent = handleHookEvent;
9
+ const node_fs_1 = require("node:fs");
10
+ const node_os_1 = require("node:os");
11
+ const node_path_1 = require("node:path");
12
+ const hook_runner_js_1 = require("../agent-common/hook-runner.js");
13
+ const session_js_1 = require("../agent-common/session.js");
14
+ const normalize_js_1 = require("../cursor/normalize.js");
15
+ const hook_handler_js_1 = require("../cursor/hook-handler.js");
16
+ const defaults_js_1 = require("./defaults.js");
17
+ const state_machine_js_1 = require("./state-machine.js");
18
+ exports.CLAUDE_ADAPTER = {
19
+ command: 'claude-code',
20
+ label: 'Claude',
21
+ family: 'claude',
22
+ stateDir: defaults_js_1.DEFAULTS.stateDir,
23
+ heartbeatSeconds: defaults_js_1.DEFAULTS.heartbeatSeconds,
24
+ processNames: ['claude'],
25
+ };
26
+ /**
27
+ * Derive a serviceId from the session and project directory.
28
+ * Format: "claude-{project_name}-{session_id_prefix}" (≤100 chars).
29
+ */
30
+ function deriveServiceId(sessionId, cwd) {
31
+ return (0, session_js_1.sanitizeServiceId)(`claude-${(0, session_js_1.projectNameOf)(cwd)}-${sessionId.slice(0, 8)}`);
32
+ }
33
+ /**
34
+ * Humanize a project dir name into a banner label: split on punctuation and
35
+ * title-case each word ("gridbreak" -> "Gridbreak", "my-cool-app" -> "My Cool App").
36
+ */
37
+ function humanizeProjectName(cwd) {
38
+ const raw = (0, session_js_1.projectNameOf)(cwd);
39
+ const words = raw
40
+ .replace(/[^a-zA-Z0-9]+/g, ' ')
41
+ .trim()
42
+ .split(/\s+/)
43
+ .filter(Boolean);
44
+ if (words.length === 0)
45
+ return raw;
46
+ return words.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
47
+ }
48
+ /** Default location of Claude Code's per-session sidecar files. */
49
+ function sessionsStoreDir() {
50
+ return (0, node_path_1.join)((0, node_os_1.homedir)(), '.claude', 'sessions');
51
+ }
52
+ /**
53
+ * Correlate a session_id to its user-facing name via the local session store.
54
+ * Claude Code writes ~/.claude/sessions/<pid>.json — keyed by pid, each holding
55
+ * `{ sessionId, name, ... }` — so we scan for the file whose sessionId matches.
56
+ * `dir` is injectable for testing.
57
+ */
58
+ function lookupSessionNameFromStore(sessionId, dir = sessionsStoreDir()) {
59
+ if (!sessionId)
60
+ return null;
61
+ let files;
62
+ try {
63
+ files = (0, node_fs_1.readdirSync)(dir);
64
+ }
65
+ catch {
66
+ return null;
67
+ }
68
+ for (const file of files) {
69
+ if (!file.endsWith('.json'))
70
+ continue;
71
+ try {
72
+ const obj = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(dir, file), 'utf-8'));
73
+ if (obj.sessionId === sessionId && typeof obj.name === 'string' && obj.name.trim()) {
74
+ return obj.name.trim();
75
+ }
76
+ }
77
+ catch {
78
+ // Skip unreadable / partially-written session files.
79
+ }
80
+ }
81
+ return null;
82
+ }
83
+ /**
84
+ * Resolve the user-facing session name. Checked fresh only on the cheap,
85
+ * low-frequency events (SessionStart carries `session_title`; UserPromptSubmit
86
+ * falls back to the store scan) so renames propagate; otherwise the cached
87
+ * value is reused. Returns null when unknown (banner falls back to the project).
88
+ */
89
+ function resolveSessionName(event, cached, dir) {
90
+ const name = (0, state_machine_js_1.eventName)(event);
91
+ const shouldRefresh = name === 'SessionStart' || name === 'UserPromptSubmit' || !cached;
92
+ if (!shouldRefresh)
93
+ return cached;
94
+ const fromPayload = typeof event.session_title === 'string' ? event.session_title.trim() : '';
95
+ if (fromPayload)
96
+ return fromPayload;
97
+ return lookupSessionNameFromStore(event.session_id ?? '', dir) ?? cached;
98
+ }
99
+ exports.CLAUDE_HOOK_SPEC = {
100
+ adapter: exports.CLAUDE_ADAPTER,
101
+ serviceIdFor: deriveServiceId,
102
+ transition: (event, prev) => (0, state_machine_js_1.claudeTransition)(event, prev),
103
+ resolveName: (event, prev) => {
104
+ // Only scan the store on the low-frequency events; never on PreToolUse.
105
+ const name = (0, state_machine_js_1.eventName)(event);
106
+ if (prev && name !== 'SessionStart' && name !== 'UserPromptSubmit')
107
+ return undefined;
108
+ return resolveSessionName(event, prev?.name ?? null) ?? undefined;
109
+ },
110
+ };
111
+ /**
112
+ * Handle a single Claude Code hook event.
113
+ *
114
+ * Reads the JSON payload (from `rawInput` if provided, else stdin), updates the
115
+ * per-session state file, and pushes an NPC status update (banner
116
+ * "Claude · <session name or project>"). Defaults to an in-process push; the CLI
117
+ * passes `mode: 'detach'` so the push runs in a background process. Never throws.
118
+ */
119
+ async function handleHookEvent(config, rawInput, opts = {}) {
120
+ const input = rawInput ?? (0, hook_runner_js_1.readStdin)();
121
+ let event;
122
+ try {
123
+ event = JSON.parse(input);
124
+ }
125
+ catch {
126
+ return;
127
+ }
128
+ if (!event || typeof event !== 'object' || Array.isArray(event))
129
+ return;
130
+ if ((0, normalize_js_1.isCursorInvocation)(event)) {
131
+ await (0, hook_handler_js_1.handleCursorHookEvent)(config, event, opts, 'third-party');
132
+ return;
133
+ }
134
+ await (0, hook_runner_js_1.runAgentHook)(exports.CLAUDE_HOOK_SPEC, config, event, {
135
+ minPushGapMs: defaults_js_1.DEFAULTS.minPushGapMs,
136
+ ...opts,
137
+ });
138
+ }
139
+ //# sourceMappingURL=hook-handler.js.map
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ClaudeCodeNPC = exports.uninstallHooks = exports.setupHooks = exports.CLAUDE_ADAPTER = exports.handleHookEvent = exports.claudeTransition = exports.processHookEvent = exports.DEFAULTS = void 0;
4
+ exports.handleClaudeCodeCommand = handleClaudeCodeCommand;
5
+ const config_js_1 = require("../../core/config.js");
6
+ const hook_runner_js_1 = require("../agent-common/hook-runner.js");
7
+ const watchdog_js_1 = require("../agent-common/watchdog.js");
8
+ const session_js_1 = require("../agent-common/session.js");
9
+ const defaults_js_1 = require("./defaults.js");
10
+ const hook_handler_js_1 = require("./hook-handler.js");
11
+ const setup_js_1 = require("./setup.js");
12
+ var defaults_js_2 = require("./defaults.js");
13
+ Object.defineProperty(exports, "DEFAULTS", { enumerable: true, get: function () { return defaults_js_2.DEFAULTS; } });
14
+ var state_machine_js_1 = require("./state-machine.js");
15
+ Object.defineProperty(exports, "processHookEvent", { enumerable: true, get: function () { return state_machine_js_1.processHookEvent; } });
16
+ Object.defineProperty(exports, "claudeTransition", { enumerable: true, get: function () { return state_machine_js_1.claudeTransition; } });
17
+ var hook_handler_js_2 = require("./hook-handler.js");
18
+ Object.defineProperty(exports, "handleHookEvent", { enumerable: true, get: function () { return hook_handler_js_2.handleHookEvent; } });
19
+ Object.defineProperty(exports, "CLAUDE_ADAPTER", { enumerable: true, get: function () { return hook_handler_js_2.CLAUDE_ADAPTER; } });
20
+ var setup_js_2 = require("./setup.js");
21
+ Object.defineProperty(exports, "setupHooks", { enumerable: true, get: function () { return setup_js_2.setupHooks; } });
22
+ Object.defineProperty(exports, "uninstallHooks", { enumerable: true, get: function () { return setup_js_2.uninstallHooks; } });
23
+ /** Remove stale session state files. */
24
+ async function cleanupStateFiles() {
25
+ const removed = await (0, session_js_1.cleanupSessionStates)(defaults_js_1.DEFAULTS.stateDir, defaults_js_1.DEFAULTS.stateFileMaxAge);
26
+ console.log(`Cleanup complete: ${removed} stale file(s) removed`);
27
+ }
28
+ /**
29
+ * claude-code subcommands: setup | uninstall | hook | flush | cleanup.
30
+ * `hook` and `flush` are invoked by Claude Code / ourselves and must stay
31
+ * silent on stdout and never exit non-zero.
32
+ */
33
+ async function handleClaudeCodeCommand(opts) {
34
+ const { subcommand, config, flags } = opts;
35
+ switch (subcommand) {
36
+ case 'setup': {
37
+ if (flags['uninstall']) {
38
+ (0, setup_js_1.uninstallHooks)();
39
+ break;
40
+ }
41
+ const ok = (0, setup_js_1.setupHooks)({
42
+ apiUrl: (0, config_js_1.stringFlag)(flags, 'api-url') ?? process.env['NOCVILLE_API_URL'],
43
+ apiKey: (0, config_js_1.stringFlag)(flags, 'api-key') ?? process.env['NOCVILLE_API_KEY'],
44
+ map: (0, config_js_1.stringFlag)(flags, 'map'),
45
+ cliPath: process.argv[1],
46
+ debug: config.debug,
47
+ });
48
+ if (!ok)
49
+ process.exitCode = 1;
50
+ break;
51
+ }
52
+ case 'uninstall':
53
+ (0, setup_js_1.uninstallHooks)();
54
+ break;
55
+ case 'hook':
56
+ await (0, hook_handler_js_1.handleHookEvent)(config, (0, hook_runner_js_1.readStdin)(), { mode: 'detach' });
57
+ break;
58
+ case 'watchdog':
59
+ await (0, watchdog_js_1.runWatchdog)(hook_handler_js_1.CLAUDE_ADAPTER, config, opts.args?.[0]);
60
+ break;
61
+ case 'flush':
62
+ await (0, hook_runner_js_1.runFlushCommand)(hook_handler_js_1.CLAUDE_ADAPTER, config, opts.args?.[0], (0, config_js_1.numberFlag)(flags, 'delay'), flags['stats'] === true);
63
+ break;
64
+ case 'cleanup':
65
+ await cleanupStateFiles();
66
+ break;
67
+ default: {
68
+ console.error(`Unknown claude-code subcommand: ${subcommand}`);
69
+ console.error('Available subcommands: setup, uninstall, hook, cleanup');
70
+ process.exitCode = 1;
71
+ }
72
+ }
73
+ }
74
+ /**
75
+ * ClaudeCodeNPC class for programmatic use.
76
+ */
77
+ class ClaudeCodeNPC {
78
+ config;
79
+ constructor(config) {
80
+ this.config = config;
81
+ }
82
+ /** Process a hook event from stdin (in-process push). */
83
+ async handleHook() {
84
+ await (0, hook_handler_js_1.handleHookEvent)(this.config);
85
+ }
86
+ /** Install hooks into Claude Code settings. */
87
+ setup() {
88
+ (0, setup_js_1.setupHooks)({
89
+ apiUrl: this.config.apiUrl,
90
+ apiKey: this.config.apiKey,
91
+ debug: this.config.debug,
92
+ });
93
+ }
94
+ /** Remove all Nocville hooks from Claude Code settings. */
95
+ uninstall() {
96
+ (0, setup_js_1.uninstallHooks)();
97
+ }
98
+ /** Clean up stale session state files. */
99
+ async cleanup() {
100
+ await cleanupStateFiles();
101
+ }
102
+ }
103
+ exports.ClaudeCodeNPC = ClaudeCodeNPC;
104
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findExistingNocvilleCommand = exports.CLAUDE_HOOK_EVENTS = exports.extractFlag = void 0;
4
+ exports.claudeSettingsPath = claudeSettingsPath;
5
+ exports.installHooksInto = installHooksInto;
6
+ exports.removeHooksFrom = removeHooksFrom;
7
+ exports.buildHookCommand = buildHookCommand;
8
+ exports.legacyCredentials = legacyCredentials;
9
+ exports.setupHooks = setupHooks;
10
+ exports.uninstallHooks = uninstallHooks;
11
+ const node_fs_1 = require("node:fs");
12
+ const node_os_1 = require("node:os");
13
+ const node_path_1 = require("node:path");
14
+ const user_config_js_1 = require("../../core/user-config.js");
15
+ const hook_settings_js_1 = require("../agent-common/hook-settings.js");
16
+ const shim_js_1 = require("../agent-common/shim.js");
17
+ const runtime_js_1 = require("../agent-common/runtime.js");
18
+ const defaults_js_1 = require("./defaults.js");
19
+ var hook_settings_js_2 = require("../agent-common/hook-settings.js");
20
+ Object.defineProperty(exports, "extractFlag", { enumerable: true, get: function () { return hook_settings_js_2.extractFlag; } });
21
+ function claudeSettingsPath() {
22
+ return (0, node_path_1.join)((0, node_os_1.homedir)(), '.claude', 'settings.json');
23
+ }
24
+ /**
25
+ * Hook events to install. Verified against code.claude.com/docs/en/hooks —
26
+ * all exist; events without matcher support ignore the empty matcher.
27
+ */
28
+ exports.CLAUDE_HOOK_EVENTS = [
29
+ 'SessionStart',
30
+ 'SessionEnd',
31
+ 'UserPromptSubmit',
32
+ 'PreToolUse',
33
+ 'PostToolUse',
34
+ 'PostToolUseFailure',
35
+ 'PermissionRequest',
36
+ 'Notification',
37
+ 'Stop',
38
+ 'SubagentStart',
39
+ 'SubagentStop',
40
+ 'PreCompact',
41
+ ];
42
+ /** Install the nocville group into every Claude Code event. Mutates `settings`. */
43
+ function installHooksInto(settings, command, opts = {}) {
44
+ return (0, hook_settings_js_1.installHookGroups)(settings, command, exports.CLAUDE_HOOK_EVENTS, {
45
+ perEvent: Object.fromEntries(exports.CLAUDE_HOOK_EVENTS.map((event) => [
46
+ event,
47
+ {
48
+ ...((opts.platform ?? process.platform) === 'win32' ? { shell: 'powershell' } : {}),
49
+ ...(event === 'SessionEnd'
50
+ ? { timeout: 5 }
51
+ : { async: true, timeout: opts.timeout ?? 10 }),
52
+ },
53
+ ])),
54
+ });
55
+ }
56
+ /** Remove all nocville entries (nested or legacy flat). Mutates `settings`. */
57
+ function removeHooksFrom(settings) {
58
+ return (0, hook_settings_js_1.removeHookGroups)(settings);
59
+ }
60
+ var hook_settings_js_3 = require("../agent-common/hook-settings.js");
61
+ Object.defineProperty(exports, "findExistingNocvilleCommand", { enumerable: true, get: function () { return hook_settings_js_3.findNocvilleCommand; } });
62
+ /** `'<node>' '<cli.js>' claude-code hook # nocville-npc-hook` — no key on the command line. */
63
+ function buildHookCommand(_cliPath) {
64
+ return (0, hook_settings_js_1.buildShimHookCommand)('claude-code');
65
+ }
66
+ /**
67
+ * Credentials embedded in a legacy hook command (`--api-url=… --api-key=…`),
68
+ * so upgrading setup can move them into the user config.
69
+ */
70
+ function legacyCredentials(settings) {
71
+ const prev = (0, hook_settings_js_1.findNocvilleCommand)(settings);
72
+ if (!prev)
73
+ return {};
74
+ return { apiUrl: (0, hook_settings_js_1.extractFlag)(prev, 'api-url'), apiKey: (0, hook_settings_js_1.extractFlag)(prev, 'api-key') };
75
+ }
76
+ function out(opts, msg) {
77
+ if (!opts.quiet)
78
+ console.log(msg);
79
+ }
80
+ /**
81
+ * Install Nocville hooks into Claude Code settings (matcher-group format,
82
+ * absolute node/cli paths, 10s timeout safety net). Idempotent: migrates and
83
+ * de-duplicates legacy entries and moves any legacy inline credentials into
84
+ * ~/.config/nocville/config.json. Returns false if setup could not complete.
85
+ */
86
+ function setupHooks(opts) {
87
+ const settingsPath = opts.settingsPath ?? claudeSettingsPath();
88
+ const dir = (0, node_path_1.join)(settingsPath, '..');
89
+ if (!(0, node_fs_1.existsSync)(dir)) {
90
+ console.error(`${dir} does not exist — is Claude Code installed? Skipping Claude Code setup.`);
91
+ return false;
92
+ }
93
+ const settings = (0, hook_settings_js_1.readJsonFile)(settingsPath);
94
+ if (settings === null) {
95
+ console.error(`Could not parse ${settingsPath}; fix it (or move it aside) and re-run setup.`);
96
+ return false;
97
+ }
98
+ const creds = (0, user_config_js_1.persistCredentials)(opts, legacyCredentials(settings));
99
+ const resolved = (0, hook_settings_js_1.resolveCliPath)(opts.cliPath);
100
+ const cliPath = (0, runtime_js_1.ensureStableCliPath)(resolved);
101
+ (0, shim_js_1.installHookShim)((0, hook_settings_js_1.stableNodePath)(), cliPath);
102
+ const command = buildHookCommand(cliPath);
103
+ const { migrated, installed } = installHooksInto(settings, command, {
104
+ timeout: defaults_js_1.DEFAULTS.hookTimeoutSeconds,
105
+ });
106
+ (0, hook_settings_js_1.writeJsonFile)(settingsPath, settings);
107
+ out(opts, `✓ Claude Code: installed Nocville hooks for ${installed} events in ${settingsPath}`);
108
+ if (migrated > 0)
109
+ out(opts, ` (migrated ${migrated} legacy entr${migrated === 1 ? 'y' : 'ies'})`);
110
+ out(opts, ` Hook runs: ${process.execPath} ${cliPath}`);
111
+ out(opts, ` API: ${creds.apiUrl ?? 'https://nocville.com'} key: ${(0, user_config_js_1.maskKey)(creds.apiKey)}`);
112
+ if (!creds.apiKey) {
113
+ out(opts, ' ⚠ No API key saved yet — run: nocville-npc init --api-key=<your team key>');
114
+ }
115
+ out(opts, ' New Claude Code sessions will appear in your office. Restart running sessions.');
116
+ return true;
117
+ }
118
+ /** Remove all Nocville hooks from Claude Code settings. */
119
+ function uninstallHooks(opts = {}) {
120
+ const o = typeof opts === 'boolean' ? {} : opts;
121
+ const settingsPath = o.settingsPath ?? claudeSettingsPath();
122
+ const settings = (0, hook_settings_js_1.readJsonFile)(settingsPath);
123
+ if (!settings || !(0, node_fs_1.existsSync)(settingsPath)) {
124
+ out(o, 'Claude Code: no settings.json — nothing to remove.');
125
+ return 0;
126
+ }
127
+ const removed = removeHooksFrom(settings);
128
+ if (removed > 0)
129
+ (0, hook_settings_js_1.writeJsonFile)(settingsPath, settings);
130
+ out(o, `✓ Claude Code: removed ${removed} Nocville hook(s) from ${settingsPath}`);
131
+ return removed;
132
+ }
133
+ //# sourceMappingURL=setup.js.map