@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,85 @@
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { dirname, join } from 'node:path';
4
+ /**
5
+ * Location of the config file. `NOCVILLE_CONFIG` overrides (used by tests);
6
+ * otherwise `$XDG_CONFIG_HOME/nocville/config.json`, else `~/.config/nocville/config.json`.
7
+ */
8
+ export function userConfigPath() {
9
+ const override = process.env['NOCVILLE_CONFIG'];
10
+ if (override)
11
+ return override;
12
+ const xdg = process.env['XDG_CONFIG_HOME'];
13
+ const base = xdg && xdg.trim() ? xdg : join(homedir(), '.config');
14
+ return join(base, 'nocville', 'config.json');
15
+ }
16
+ /** Read the user config; missing or unreadable files yield `{}` (never throws). */
17
+ export function loadUserConfig(path = userConfigPath()) {
18
+ try {
19
+ const raw = JSON.parse(readFileSync(path, 'utf-8'));
20
+ const out = {};
21
+ if (typeof raw['apiUrl'] === 'string' && raw['apiUrl'])
22
+ out.apiUrl = raw['apiUrl'];
23
+ if (typeof raw['apiKey'] === 'string' && raw['apiKey'])
24
+ out.apiKey = raw['apiKey'];
25
+ if (typeof raw['map'] === 'string' && raw['map'])
26
+ out.map = raw['map'];
27
+ return out;
28
+ }
29
+ catch {
30
+ return {};
31
+ }
32
+ }
33
+ /**
34
+ * Merge `patch` into the stored config and write it with owner-only perms
35
+ * (dir 0700, file 0600) via an atomic rename. Undefined fields are left as-is;
36
+ * an empty string clears a field. Returns the written path.
37
+ */
38
+ export function saveUserConfig(patch, path = userConfigPath()) {
39
+ const current = loadUserConfig(path);
40
+ const next = { ...current };
41
+ for (const [k, v] of Object.entries(patch)) {
42
+ if (v === undefined)
43
+ continue;
44
+ if (v === '')
45
+ delete next[k];
46
+ else
47
+ next[k] = v;
48
+ }
49
+ const dir = dirname(path);
50
+ if (!existsSync(dir))
51
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
52
+ const tmp = `${path}.${process.pid}.tmp`;
53
+ writeFileSync(tmp, JSON.stringify(next, null, 2) + '\n', { encoding: 'utf-8', mode: 0o600 });
54
+ renameSync(tmp, path);
55
+ try {
56
+ chmodSync(path, 0o600);
57
+ }
58
+ catch {
59
+ // best effort (e.g. Windows)
60
+ }
61
+ return path;
62
+ }
63
+ /** Mask an API key for display: keep a short prefix/suffix only. */
64
+ export function maskKey(key) {
65
+ if (!key)
66
+ return '(none)';
67
+ if (key.length <= 10)
68
+ return '****';
69
+ return `${key.slice(0, 4)}…${key.slice(-4)}`;
70
+ }
71
+ /**
72
+ * Save credentials (explicit flags win; else migrate any from a legacy hook
73
+ * command) and return the effective config. Shared by claude-code/codex setup.
74
+ */
75
+ export function persistCredentials(explicit, legacy = {}) {
76
+ const saved = loadUserConfig();
77
+ const patch = {
78
+ apiUrl: explicit.apiUrl ?? (saved.apiUrl ? undefined : legacy.apiUrl),
79
+ apiKey: explicit.apiKey ?? (saved.apiKey ? undefined : legacy.apiKey),
80
+ map: explicit.map,
81
+ };
82
+ const path = patch.apiUrl || patch.apiKey || patch.map ? saveUserConfig(patch) : userConfigPath();
83
+ return { ...loadUserConfig(), path };
84
+ }
85
+ //# sourceMappingURL=user-config.js.map
@@ -0,0 +1,8 @@
1
+ import type { NpcConfig } from './core/types.js';
2
+ /**
3
+ * Diagnose connectivity to the Nocville API: hit `/api/v1/health/ready`, then POST a single
4
+ * offline status push and report HTTP status, latency, and rate-limit headroom.
5
+ */
6
+ export declare function runDoctor(config: NpcConfig): Promise<void>;
7
+ export declare function hasCodexHookWatcherOverlap(dir?: string): boolean;
8
+ //# sourceMappingURL=doctor.d.ts.map
package/dist/doctor.js ADDED
@@ -0,0 +1,126 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { readdirSync } from 'node:fs';
3
+ import { isAgentAlive } from './npcs/agent-common/liveness.js';
4
+ import { CODEX_DEFAULTS } from './npcs/codex/defaults.js';
5
+ /**
6
+ * Diagnose connectivity to the Nocville API: hit `/api/v1/health/ready`, then POST a single
7
+ * offline status push and report HTTP status, latency, and rate-limit headroom.
8
+ */
9
+ export async function runDoctor(config) {
10
+ const apiUrl = config.apiUrl.replace(/\/+$/, '');
11
+ console.log(`nocville-npc doctor — checking ${apiUrl}`);
12
+ console.log('');
13
+ if (hasCodexHookWatcherOverlap())
14
+ console.warn(' [WARN] Codex hooks and codex watch are both active. Stop the fallback watcher; hooks own these sessions.');
15
+ // 1) Health check
16
+ let healthOk = false;
17
+ {
18
+ const start = Date.now();
19
+ const t = withTimeout(5000);
20
+ try {
21
+ const res = await fetch(`${apiUrl}/api/v1/health/ready`, { method: 'GET', signal: t.signal });
22
+ const ms = Date.now() - start;
23
+ const body = await res.text();
24
+ healthOk = res.ok;
25
+ console.log(` [${res.ok ? 'OK' : 'FAIL'}] GET /api/v1/health/ready -> ${res.status} (${ms}ms)`);
26
+ if (config.debug)
27
+ console.log(` ${body.slice(0, 200)}`);
28
+ }
29
+ catch (err) {
30
+ console.log(` [FAIL] GET /api/v1/health/ready -> ${err instanceof Error ? err.message : String(err)}`);
31
+ }
32
+ finally {
33
+ t.done();
34
+ }
35
+ }
36
+ // 2) Test status push
37
+ let pushOk = false;
38
+ const hasKey = Boolean(config.apiKey);
39
+ if (!hasKey) {
40
+ console.log(' [SKIP] POST /api/v1/status — no --api-key / NOCVILLE_API_KEY set');
41
+ }
42
+ else {
43
+ const start = Date.now();
44
+ const t = withTimeout(5000);
45
+ try {
46
+ const res = await fetch(`${apiUrl}/api/v1/status`, {
47
+ method: 'POST',
48
+ headers: {
49
+ 'Content-Type': 'application/json',
50
+ Authorization: `Bearer ${config.apiKey}`,
51
+ },
52
+ signal: t.signal,
53
+ body: JSON.stringify({
54
+ // Unique offline probe: personal-key ownership must not reserve a shared doctor ID.
55
+ serviceId: `${(config.serviceId || 'npc').slice(0, 60)}-doctor-${randomUUID().slice(0, 16)}`,
56
+ status: 'unknown',
57
+ mood: 'idle',
58
+ offline: true,
59
+ subStatus: 'offline',
60
+ ...(config.map ? { map: config.map } : {}),
61
+ }),
62
+ });
63
+ const ms = Date.now() - start;
64
+ const text = await res.text();
65
+ pushOk = res.ok;
66
+ let remaining = res.headers.get('x-ratelimit-remaining') ?? res.headers.get('ratelimit-remaining');
67
+ if (!remaining) {
68
+ try {
69
+ const parsed = JSON.parse(text);
70
+ if (typeof parsed.remaining === 'number')
71
+ remaining = String(parsed.remaining);
72
+ }
73
+ catch {
74
+ // non-JSON body
75
+ }
76
+ }
77
+ console.log(` [${res.ok ? 'OK' : 'FAIL'}] POST /api/v1/status -> ${res.status} ` +
78
+ `(${ms}ms, rate-limit remaining: ${remaining ?? 'n/a'})`);
79
+ if (config.map)
80
+ console.log(` target map: ${config.map}`);
81
+ if (!res.ok)
82
+ console.log(` ${text.slice(0, 200)}`);
83
+ }
84
+ catch (err) {
85
+ console.log(` [FAIL] POST /api/v1/status -> ${err instanceof Error ? err.message : String(err)}`);
86
+ }
87
+ finally {
88
+ t.done();
89
+ }
90
+ }
91
+ // Verdict
92
+ console.log('');
93
+ if (healthOk && pushOk) {
94
+ console.log('Verdict: OK — API reachable and status push accepted.');
95
+ console.log('The offline probe does not create a coworker in your office.');
96
+ }
97
+ else if (!healthOk) {
98
+ console.log('Verdict: FAIL — API health check is unreachable. Check --api-url and that the server is running.');
99
+ }
100
+ else if (!hasKey) {
101
+ console.log('Verdict: PARTIAL — API is up, but no API key was provided to test a push.');
102
+ }
103
+ else {
104
+ console.log('Verdict: FAIL — API is up but the push was rejected. Check your API key / team settings.');
105
+ }
106
+ process.exitCode = healthOk && (pushOk || !hasKey) ? 0 : 1;
107
+ }
108
+ function withTimeout(timeoutMs) {
109
+ const controller = new AbortController();
110
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
111
+ return { signal: controller.signal, done: () => clearTimeout(timer) };
112
+ }
113
+ export function hasCodexHookWatcherOverlap(dir = CODEX_DEFAULTS.stateDir) {
114
+ try {
115
+ const files = readdirSync(dir);
116
+ return (files.some((file) => /^session-.*\.json$/.test(file)) &&
117
+ files.some((file) => {
118
+ const match = file.match(/^watcher-(\d+)\.pid$/);
119
+ return !!match && isAgentAlive({ pid: Number(match[1]) });
120
+ }));
121
+ }
122
+ catch {
123
+ return false;
124
+ }
125
+ }
126
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1,39 @@
1
+ export { BaseNPC } from './core/base-npc.js';
2
+ export { PollingNPC } from './core/polling-npc.js';
3
+ export type { PollingNpcConfig } from './core/polling-npc.js';
4
+ export { EventNPC } from './core/event-npc.js';
5
+ export { Logger } from './core/logger.js';
6
+ export { parseArgs, resolveConfig, stringFlag, boolFlag, numberFlag } from './core/config.js';
7
+ export type { ParsedArgs } from './core/config.js';
8
+ export { evaluateThreshold, worstStatus } from './core/thresholds.js';
9
+ export { truncate } from './core/text.js';
10
+ export { statusColor, pushHistory } from './core/screen.js';
11
+ export type { ScreenColor } from './core/screen.js';
12
+ export type { Status, Mood, NpcConfig, CollectedMetrics, ThresholdConfig, NpcState, LogLevelName, Archetype, BehaviorHints, CompanionSpec, CompanionDecl, ScreenData, ScreenValue, } from './core/types.js';
13
+ export { SystemMonitorNPC } from './npcs/system-monitor/index.js';
14
+ export type { SystemMonitorConfig } from './npcs/system-monitor/index.js';
15
+ export { ClaudeCodeNPC, handleClaudeCodeCommand, handleHookEvent, setupHooks, uninstallHooks, processHookEvent, claudeTransition, } from './npcs/claude-code/index.js';
16
+ export type { ClaudeCodeSubcommandOptions, SetupOptions, SessionState, HookEvent, StatusOutput, SessionMachineState, } from './npcs/claude-code/index.js';
17
+ export { handleCodexCommand, handleCodexHookEvent, codexTransition, setupCodexHooks, uninstallCodexHooks, parseRolloutLine, CodexWatcher, } from './npcs/codex/index.js';
18
+ export type { CodexSetupOptions, CodexWatcherOptions, RolloutSignal } from './npcs/codex/index.js';
19
+ export { describeToolUse, shortCommand, shortPath, redactSecrets, } from './npcs/agent-common/activity.js';
20
+ export { loadUserConfig, saveUserConfig, userConfigPath } from './core/user-config.js';
21
+ export type { UserConfig } from './core/user-config.js';
22
+ export { InternetConnectivityNPC } from './npcs/internet-connectivity/index.js';
23
+ export type { InternetConnectivityConfig } from './npcs/internet-connectivity/index.js';
24
+ export { ProcessWatcherNPC } from './npcs/process-watcher/index.js';
25
+ export type { ProcessWatcherConfig } from './npcs/process-watcher/index.js';
26
+ export { LogWatcherNPC } from './npcs/log-watcher/index.js';
27
+ export type { LogWatcherConfig } from './npcs/log-watcher/index.js';
28
+ export { ReceptionistNPC } from './npcs/receptionist/index.js';
29
+ export type { ReceptionistConfig } from './npcs/receptionist/index.js';
30
+ export { GitHubWatcherNPC } from './npcs/github-watcher/index.js';
31
+ export type { GitHubWatcherConfig } from './npcs/github-watcher/index.js';
32
+ export { WebPageWatcherNPC } from './npcs/web-page-watcher/index.js';
33
+ export type { WebPageWatcherConfig } from './npcs/web-page-watcher/index.js';
34
+ export { GEMINI_DEFAULTS, GEMINI_ADAPTER, geminiTransition, geminiServiceId, handleGeminiHookEvent, setupGeminiHooks, uninstallGeminiHooks, installGeminiHooksInto, GEMINI_HOOK_EVENTS, } from './npcs/gemini/index.js';
35
+ export type { GeminiSetupOptions } from './npcs/gemini/index.js';
36
+ export { CURSOR_DEFAULTS, CURSOR_ADAPTER, cursorServiceId, handleCursorHookEvent, setupCursorHooks, uninstallCursorHooks, installCursorHooksInto, CURSOR_HOOK_EVENTS, } from './npcs/cursor/index.js';
37
+ export type { CursorSetupOptions } from './npcs/cursor/index.js';
38
+ export { isCursorInvocation, normalizeCursorEvent } from './npcs/cursor/normalize.js';
39
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ // Core
2
+ export { BaseNPC } from './core/base-npc.js';
3
+ export { PollingNPC } from './core/polling-npc.js';
4
+ export { EventNPC } from './core/event-npc.js';
5
+ export { Logger } from './core/logger.js';
6
+ export { parseArgs, resolveConfig, stringFlag, boolFlag, numberFlag } from './core/config.js';
7
+ export { evaluateThreshold, worstStatus } from './core/thresholds.js';
8
+ export { truncate } from './core/text.js';
9
+ export { statusColor, pushHistory } from './core/screen.js';
10
+ // NPCs
11
+ export { SystemMonitorNPC } from './npcs/system-monitor/index.js';
12
+ export { ClaudeCodeNPC, handleClaudeCodeCommand, handleHookEvent, setupHooks, uninstallHooks, processHookEvent, claudeTransition, } from './npcs/claude-code/index.js';
13
+ export { handleCodexCommand, handleCodexHookEvent, codexTransition, setupCodexHooks, uninstallCodexHooks, parseRolloutLine, CodexWatcher, } from './npcs/codex/index.js';
14
+ export { describeToolUse, shortCommand, shortPath, redactSecrets, } from './npcs/agent-common/activity.js';
15
+ export { loadUserConfig, saveUserConfig, userConfigPath } from './core/user-config.js';
16
+ export { InternetConnectivityNPC } from './npcs/internet-connectivity/index.js';
17
+ export { ProcessWatcherNPC } from './npcs/process-watcher/index.js';
18
+ export { LogWatcherNPC } from './npcs/log-watcher/index.js';
19
+ export { ReceptionistNPC } from './npcs/receptionist/index.js';
20
+ export { GitHubWatcherNPC } from './npcs/github-watcher/index.js';
21
+ export { WebPageWatcherNPC } from './npcs/web-page-watcher/index.js';
22
+ export { GEMINI_DEFAULTS, GEMINI_ADAPTER, geminiTransition, geminiServiceId, handleGeminiHookEvent, setupGeminiHooks, uninstallGeminiHooks, installGeminiHooksInto, GEMINI_HOOK_EVENTS, } from './npcs/gemini/index.js';
23
+ export { CURSOR_DEFAULTS, CURSOR_ADAPTER, cursorServiceId, handleCursorHookEvent, setupCursorHooks, uninstallCursorHooks, installCursorHooksInto, CURSOR_HOOK_EVENTS, } from './npcs/cursor/index.js';
24
+ export { isCursorInvocation, normalizeCursorEvent } from './npcs/cursor/normalize.js';
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Human-friendly activity strings for AI coding agents (Claude Code, Codex).
3
+ *
4
+ * These end up on the NPC's speech bubble and desk-sign ticker, which teammates
5
+ * see — so they must be short, readable, and never leak secrets or prompt text.
6
+ */
7
+ /** Max length of a full activity line ("Running pnpm test", "Editing src/x.ts"). */
8
+ export declare const ACTIVITY_MAX = 60;
9
+ export declare const THINKING = "Thinking\u2026";
10
+ /**
11
+ * Make a path short and relative: strip the project cwd (or the home dir to
12
+ * `~`), then keep only the trailing segments if it is still long.
13
+ */
14
+ export declare function shortPath(p: string, cwd?: string): string;
15
+ /**
16
+ * Redact anything secret-looking from a shell command: env assignments,
17
+ * `--token=…`-style flags, bearer headers, URL credentials, and long opaque
18
+ * strings (API keys, hashes). Conservative by design — false positives are fine.
19
+ */
20
+ export declare function redactSecrets(cmd: string): string;
21
+ /**
22
+ * Shorten a shell command for display: first line only, drop a leading
23
+ * `cd … &&`, drop leading `VAR=value` env assignments (often secrets), unwrap
24
+ * `bash -lc "…"` wrappers, redact secrets, and truncate.
25
+ */
26
+ export declare function shortCommand(raw: unknown, max?: number): string;
27
+ /** Files touched by an `apply_patch` envelope ("*** Update File: path"). */
28
+ export declare function patchFiles(patch: string): string[];
29
+ /** "Editing a.ts" / "Editing a.ts +2 more" for a list of files. */
30
+ export declare function editingFiles(files: string[], cwd?: string): string;
31
+ /**
32
+ * Describe a tool invocation in a few words. Covers Claude Code tool names and
33
+ * Codex tool names (Bash/shell/exec_command, apply_patch, update_plan, …);
34
+ * unknown tools fall back to "Using <tool>". Never includes file contents,
35
+ * edit strings, or prompts.
36
+ */
37
+ export declare function describeToolUse(toolName: string | undefined, rawInput: unknown, cwd?: string): string;
38
+ /** Tools whose PreToolUse means the agent is now waiting on the human. */
39
+ export declare function isQuestionTool(toolName: string | undefined): boolean;
40
+ /** Short failure label: "A command failed", "Edit failed". */
41
+ export declare function failureText(toolName: string | undefined): string;
42
+ //# sourceMappingURL=activity.d.ts.map
@@ -0,0 +1,280 @@
1
+ import { homedir } from 'node:os';
2
+ import { truncate } from '../../core/text.js';
3
+ /**
4
+ * Human-friendly activity strings for AI coding agents (Claude Code, Codex).
5
+ *
6
+ * These end up on the NPC's speech bubble and desk-sign ticker, which teammates
7
+ * see — so they must be short, readable, and never leak secrets or prompt text.
8
+ */
9
+ /** Max length of a full activity line ("Running pnpm test", "Editing src/x.ts"). */
10
+ export const ACTIVITY_MAX = 60;
11
+ /** Max length of an embedded shell command. */
12
+ const COMMAND_MAX = 44;
13
+ /** Max length of an embedded path. */
14
+ const PATH_MAX = 40;
15
+ /** Max length of an embedded search pattern / query. */
16
+ const PATTERN_MAX = 28;
17
+ export const THINKING = 'Thinking…';
18
+ function str(input, ...keys) {
19
+ for (const k of keys) {
20
+ const v = input[k];
21
+ if (typeof v === 'string' && v.trim())
22
+ return v.trim();
23
+ }
24
+ return undefined;
25
+ }
26
+ function asInput(raw) {
27
+ if (raw && typeof raw === 'object' && !Array.isArray(raw))
28
+ return raw;
29
+ if (typeof raw === 'string') {
30
+ // Some agents pass JSON-encoded arguments.
31
+ try {
32
+ const parsed = JSON.parse(raw);
33
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
34
+ return parsed;
35
+ }
36
+ catch {
37
+ // Not JSON — treat as a bare command string.
38
+ }
39
+ return { command: raw };
40
+ }
41
+ if (Array.isArray(raw))
42
+ return { command: raw };
43
+ return {};
44
+ }
45
+ /**
46
+ * Make a path short and relative: strip the project cwd (or the home dir to
47
+ * `~`), then keep only the trailing segments if it is still long.
48
+ */
49
+ export function shortPath(p, cwd) {
50
+ let out = p.trim().replace(/^file:\/\//, '');
51
+ const norm = (s) => s.replace(/\\/g, '/').replace(/\/+$/, '');
52
+ out = norm(out);
53
+ const base = cwd ? norm(cwd) : '';
54
+ if (base && (out === base || out.startsWith(base + '/'))) {
55
+ out = out.slice(base.length + 1) || '.';
56
+ }
57
+ else {
58
+ const home = norm(homedir());
59
+ if (home && out.startsWith(home + '/'))
60
+ out = '~' + out.slice(home.length);
61
+ }
62
+ if (out.length <= PATH_MAX)
63
+ return out;
64
+ const parts = out.split('/');
65
+ // Keep the last two segments; fall back to truncating the filename.
66
+ const tail = parts.slice(-2).join('/');
67
+ if (tail.length + 2 <= PATH_MAX)
68
+ return '…/' + tail;
69
+ return truncate(parts[parts.length - 1] ?? out, PATH_MAX);
70
+ }
71
+ const SECRET_FLAG = /(--?[\w-]*(?:token|key|secret|password|passwd|pwd|auth|credential)[\w-]*)(=|\s+)("[^"]*"|'[^']*'|\S+)/gi;
72
+ /**
73
+ * Redact anything secret-looking from a shell command: env assignments,
74
+ * `--token=…`-style flags, bearer headers, URL credentials, and long opaque
75
+ * strings (API keys, hashes). Conservative by design — false positives are fine.
76
+ */
77
+ export function redactSecrets(cmd) {
78
+ return (cmd
79
+ // URL credentials: scheme://user:pass@host
80
+ .replace(/([a-z][a-z0-9+.-]*:\/\/)[^\s/@:]+:[^\s/@]+@/gi, '$1***@')
81
+ // Authorization headers
82
+ .replace(/(bearer|basic|token)\s+[A-Za-z0-9._~+/=-]{8,}/gi, '$1 ***')
83
+ // --api-key=…, --password …, -token …
84
+ .replace(SECRET_FLAG, (_m, flag, sep) => `${flag}${sep}***`)
85
+ // Well-known key prefixes
86
+ .replace(/\b(sk|pk|rk)-[A-Za-z0-9_-]{8,}/g, '***')
87
+ .replace(/\b(ghp|gho|ghs|ghu|github_pat|xox[abprs]|glpat|AKIA)[A-Za-z0-9_-]{8,}/g, '***')
88
+ // Long opaque tokens (≥32 chars of key-ish alphabet, no path separators)
89
+ .replace(/\b[A-Za-z0-9_+=-]{32,}\b/g, '***'));
90
+ }
91
+ /**
92
+ * Shorten a shell command for display: first line only, drop a leading
93
+ * `cd … &&`, drop leading `VAR=value` env assignments (often secrets), unwrap
94
+ * `bash -lc "…"` wrappers, redact secrets, and truncate.
95
+ */
96
+ export function shortCommand(raw, max = COMMAND_MAX) {
97
+ let cmd;
98
+ if (Array.isArray(raw)) {
99
+ const parts = raw.filter((x) => typeof x === 'string');
100
+ // Unwrap ["bash", "-lc", "<script>"] style argv.
101
+ const shellIdx = parts.findIndex((p) => p === '-lc' || p === '-c');
102
+ cmd = shellIdx >= 0 && parts[shellIdx + 1] ? parts[shellIdx + 1] : parts.join(' ');
103
+ }
104
+ else if (typeof raw === 'string') {
105
+ cmd = raw;
106
+ }
107
+ else {
108
+ return '';
109
+ }
110
+ cmd = cmd.trim();
111
+ // Unwrap a string-form shell wrapper: bash -lc 'pnpm test'
112
+ const wrapped = cmd.match(/^(?:\/\S*\/)?(?:ba|z)?sh\s+-l?c\s+(['"])([\s\S]*)\1$/);
113
+ if (wrapped)
114
+ cmd = wrapped[2].trim();
115
+ cmd = cmd.split('\n')[0].trim();
116
+ // Drop leading "cd <dir> &&" / "cd <dir>;" hops.
117
+ cmd = cmd.replace(/^(?:cd\s+("[^"]*"|'[^']*'|\S+)\s*(?:&&|;)\s*)+/, '');
118
+ // Drop leading env assignments.
119
+ cmd = cmd.replace(/^(?:[A-Za-z_][A-Za-z0-9_]*=("[^"]*"|'[^']*'|\S*)\s+)+/, '');
120
+ cmd = redactSecrets(cmd).replace(/\s+/g, ' ').trim();
121
+ return truncate(cmd, max);
122
+ }
123
+ function quote(s, max = PATTERN_MAX) {
124
+ return `'${truncate(s.replace(/\s+/g, ' '), max)}'`;
125
+ }
126
+ function hostOf(url) {
127
+ try {
128
+ return new URL(url).hostname.replace(/^www\./, '');
129
+ }
130
+ catch {
131
+ return truncate(url, PATTERN_MAX);
132
+ }
133
+ }
134
+ /** Files touched by an `apply_patch` envelope ("*** Update File: path"). */
135
+ export function patchFiles(patch) {
136
+ const files = [];
137
+ const re = /^\*\*\* (?:Update|Add|Delete) File:\s*(.+)$/gm;
138
+ let m;
139
+ while ((m = re.exec(patch)) !== null) {
140
+ const f = m[1].trim();
141
+ if (f && !files.includes(f))
142
+ files.push(f);
143
+ }
144
+ return files;
145
+ }
146
+ /** "Editing a.ts" / "Editing a.ts +2 more" for a list of files. */
147
+ export function editingFiles(files, cwd) {
148
+ if (files.length === 0)
149
+ return 'Editing files';
150
+ const first = shortPath(files[0], cwd);
151
+ return files.length === 1 ? `Editing ${first}` : `Editing ${first} +${files.length - 1} more`;
152
+ }
153
+ /**
154
+ * Describe a tool invocation in a few words. Covers Claude Code tool names and
155
+ * Codex tool names (Bash/shell/exec_command, apply_patch, update_plan, …);
156
+ * unknown tools fall back to "Using <tool>". Never includes file contents,
157
+ * edit strings, or prompts.
158
+ */
159
+ export function describeToolUse(toolName, rawInput, cwd) {
160
+ const name = (toolName ?? '').trim();
161
+ const input = asInput(rawInput);
162
+ const key = name.toLowerCase();
163
+ // MCP tools: mcp__server__tool → "Using server (tool)"
164
+ if (key.startsWith('mcp__')) {
165
+ const [, server = 'mcp', tool = ''] = name.split('__');
166
+ return truncate(tool ? `Using ${server} (${tool})` : `Using ${server}`, ACTIVITY_MAX);
167
+ }
168
+ switch (key) {
169
+ case 'replace':
170
+ case 'write_file':
171
+ case 'edit':
172
+ case 'multiedit':
173
+ case 'write':
174
+ case 'notebookedit': {
175
+ const file = str(input, 'file_path', 'absolute_path', 'notebook_path', 'path');
176
+ if (file)
177
+ return truncate(`Editing ${shortPath(file, cwd)}`, ACTIVITY_MAX);
178
+ // Codex aliases Edit/Write to apply_patch; fall through to patch parsing.
179
+ const patch = str(input, 'command', 'input', 'patch');
180
+ return truncate(editingFiles(patch ? patchFiles(patch) : [], cwd), ACTIVITY_MAX);
181
+ }
182
+ case 'delete': {
183
+ const file = str(input, 'file_path', 'path');
184
+ return file ? truncate(`Deleting ${shortPath(file, cwd)}`, ACTIVITY_MAX) : 'Deleting a file';
185
+ }
186
+ case 'apply_patch':
187
+ case 'applypatch': {
188
+ const patch = str(input, 'command', 'input', 'patch') ?? '';
189
+ return truncate(editingFiles(patchFiles(patch), cwd), ACTIVITY_MAX);
190
+ }
191
+ case 'run_shell_command':
192
+ case 'bash':
193
+ case 'shell':
194
+ case 'local_shell':
195
+ case 'exec_command':
196
+ case 'container.exec':
197
+ case 'unified_exec': {
198
+ const cmd = shortCommand(input['command'] ?? input['cmd']);
199
+ return cmd ? `Running ${cmd}` : 'Running a command';
200
+ }
201
+ case 'bashoutput':
202
+ case 'write_stdin':
203
+ return 'Checking a running command';
204
+ case 'killshell':
205
+ case 'killbash':
206
+ return 'Stopping a command';
207
+ case 'read_file':
208
+ case 'read':
209
+ case 'notebookread':
210
+ case 'view': {
211
+ const file = str(input, 'file_path', 'absolute_path', 'notebook_path', 'path');
212
+ return file ? truncate(`Reading ${shortPath(file, cwd)}`, ACTIVITY_MAX) : 'Reading files';
213
+ }
214
+ case 'view_image': {
215
+ const file = str(input, 'path', 'file_path');
216
+ return file ? truncate(`Viewing ${shortPath(file, cwd)}`, ACTIVITY_MAX) : 'Viewing an image';
217
+ }
218
+ case 'ls': {
219
+ const dir = str(input, 'path');
220
+ return dir ? truncate(`Listing ${shortPath(dir, cwd)}`, ACTIVITY_MAX) : 'Listing files';
221
+ }
222
+ case 'grep': {
223
+ const pattern = str(input, 'pattern', 'query');
224
+ return pattern ? `Searching for ${quote(pattern)}` : 'Searching the code';
225
+ }
226
+ case 'glob': {
227
+ const pattern = str(input, 'pattern');
228
+ return pattern ? `Finding files ${quote(pattern)}` : 'Finding files';
229
+ }
230
+ case 'toolsearch':
231
+ return 'Looking up tools';
232
+ case 'webfetch': {
233
+ const url = str(input, 'url');
234
+ return url ? `Browsing ${hostOf(url)}` : 'Browsing the web';
235
+ }
236
+ case 'websearch':
237
+ case 'web_search':
238
+ case 'web_search_preview': {
239
+ const q = str(input, 'query', 'q');
240
+ return q ? `Searching the web for ${quote(q)}` : 'Searching the web';
241
+ }
242
+ case 'task':
243
+ case 'agent':
244
+ case 'spawn_agent':
245
+ return 'Delegating to a subagent';
246
+ case 'todowrite':
247
+ case 'update_plan':
248
+ case 'taskcreate':
249
+ case 'taskupdate':
250
+ return 'Planning';
251
+ case 'exitplanmode':
252
+ return 'Plan ready for review';
253
+ case 'askuserquestion':
254
+ case 'request_user_input':
255
+ return 'Has a question for you';
256
+ case 'skill': {
257
+ const skill = str(input, 'skill', 'name');
258
+ return skill ? truncate(`Using skill ${skill}`, ACTIVITY_MAX) : 'Using a skill';
259
+ }
260
+ case '':
261
+ return 'Working';
262
+ default:
263
+ return truncate(`Using ${name}`, ACTIVITY_MAX);
264
+ }
265
+ }
266
+ /** Tools whose PreToolUse means the agent is now waiting on the human. */
267
+ export function isQuestionTool(toolName) {
268
+ const k = (toolName ?? '').toLowerCase();
269
+ return k === 'askuserquestion' || k === 'exitplanmode' || k === 'request_user_input';
270
+ }
271
+ /** Short failure label: "A command failed", "Edit failed". */
272
+ export function failureText(toolName) {
273
+ const k = (toolName ?? '').toLowerCase();
274
+ if (k === 'bash' || k === 'shell' || k === 'exec_command')
275
+ return 'A command failed';
276
+ if (!toolName)
277
+ return 'Hit an error';
278
+ return truncate(`${toolName} failed`, ACTIVITY_MAX);
279
+ }
280
+ //# sourceMappingURL=activity.js.map
@@ -0,0 +1,11 @@
1
+ import type { AgentStats } from '@nocville/node';
2
+ export type { AgentStats } from '@nocville/node';
3
+ /** Parse NUL-delimited numstat, including binary files and rename path pairs. */
4
+ export declare function parseNumstat(raw: string): Array<{
5
+ path: string;
6
+ insertions: number;
7
+ deletions: number;
8
+ }>;
9
+ /** Both commands share one wall-clock budget; call only from detached flushes. */
10
+ export declare function computeGitStats(cwd: string, timeoutMs?: number): AgentStats | null;
11
+ //# sourceMappingURL=git-stats.d.ts.map