@nocville/npc 0.0.0-stage → 0.2.1

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 (213) hide show
  1. package/README.md +268 -2
  2. package/dist/cjs/cli.js +584 -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/session-filter.js +150 -0
  11. package/dist/cjs/core/text.js +15 -0
  12. package/dist/cjs/core/thresholds.js +48 -0
  13. package/dist/cjs/core/types.js +10 -0
  14. package/dist/cjs/core/user-config.js +92 -0
  15. package/dist/cjs/doctor.js +130 -0
  16. package/dist/cjs/index.js +95 -0
  17. package/dist/cjs/npcs/agent-common/activity.js +291 -0
  18. package/dist/cjs/npcs/agent-common/git-stats.js +71 -0
  19. package/dist/cjs/npcs/agent-common/hook-runner.js +252 -0
  20. package/dist/cjs/npcs/agent-common/hook-settings.js +279 -0
  21. package/dist/cjs/npcs/agent-common/liveness.js +112 -0
  22. package/dist/cjs/npcs/agent-common/lock.js +99 -0
  23. package/dist/cjs/npcs/agent-common/runtime.js +154 -0
  24. package/dist/cjs/npcs/agent-common/session.js +412 -0
  25. package/dist/cjs/npcs/agent-common/shim.js +96 -0
  26. package/dist/cjs/npcs/agent-common/watchdog.js +149 -0
  27. package/dist/cjs/npcs/claude-code/defaults.js +20 -0
  28. package/dist/cjs/npcs/claude-code/hook-handler.js +139 -0
  29. package/dist/cjs/npcs/claude-code/index.js +104 -0
  30. package/dist/cjs/npcs/claude-code/setup.js +133 -0
  31. package/dist/cjs/npcs/claude-code/state-machine.js +130 -0
  32. package/dist/cjs/npcs/codex/defaults.js +32 -0
  33. package/dist/cjs/npcs/codex/hook-handler.js +116 -0
  34. package/dist/cjs/npcs/codex/index.js +92 -0
  35. package/dist/cjs/npcs/codex/rollout.js +246 -0
  36. package/dist/cjs/npcs/codex/setup.js +104 -0
  37. package/dist/cjs/npcs/codex/watcher.js +361 -0
  38. package/dist/cjs/npcs/cursor/defaults.js +15 -0
  39. package/dist/cjs/npcs/cursor/hook-handler.js +65 -0
  40. package/dist/cjs/npcs/cursor/index.js +72 -0
  41. package/dist/cjs/npcs/cursor/normalize.js +54 -0
  42. package/dist/cjs/npcs/cursor/setup.js +98 -0
  43. package/dist/cjs/npcs/gemini/defaults.js +15 -0
  44. package/dist/cjs/npcs/gemini/hook-handler.js +93 -0
  45. package/dist/cjs/npcs/gemini/index.js +69 -0
  46. package/dist/cjs/npcs/gemini/setup.js +98 -0
  47. package/dist/cjs/npcs/github-watcher/defaults.js +11 -0
  48. package/dist/cjs/npcs/github-watcher/format.js +21 -0
  49. package/dist/cjs/npcs/github-watcher/github-api.js +126 -0
  50. package/dist/cjs/npcs/github-watcher/index.js +148 -0
  51. package/dist/cjs/npcs/internet-connectivity/checks.js +92 -0
  52. package/dist/cjs/npcs/internet-connectivity/defaults.js +22 -0
  53. package/dist/cjs/npcs/internet-connectivity/index.js +251 -0
  54. package/dist/cjs/npcs/log-watcher/buffer.js +40 -0
  55. package/dist/cjs/npcs/log-watcher/defaults.js +12 -0
  56. package/dist/cjs/npcs/log-watcher/index.js +161 -0
  57. package/dist/cjs/npcs/log-watcher/tail.js +81 -0
  58. package/dist/cjs/npcs/process-watcher/defaults.js +9 -0
  59. package/dist/cjs/npcs/process-watcher/index.js +140 -0
  60. package/dist/cjs/npcs/process-watcher/process-scanner.js +114 -0
  61. package/dist/cjs/npcs/receptionist/defaults.js +9 -0
  62. package/dist/cjs/npcs/receptionist/index.js +120 -0
  63. package/dist/cjs/npcs/receptionist/weather.js +129 -0
  64. package/dist/cjs/npcs/system-monitor/collectors.js +68 -0
  65. package/dist/cjs/npcs/system-monitor/defaults.js +13 -0
  66. package/dist/cjs/npcs/system-monitor/index.js +105 -0
  67. package/dist/cjs/npcs/web-page-watcher/basic-mode.js +58 -0
  68. package/dist/cjs/npcs/web-page-watcher/capture.js +80 -0
  69. package/dist/cjs/npcs/web-page-watcher/defaults.js +31 -0
  70. package/dist/cjs/npcs/web-page-watcher/index.js +234 -0
  71. package/dist/cjs/npcs/web-page-watcher/pixelate.js +181 -0
  72. package/dist/cjs/package.json +1 -0
  73. package/dist/cli.d.ts +3 -0
  74. package/dist/cli.js +549 -0
  75. package/dist/core/api-key.d.ts +34 -0
  76. package/dist/core/api-key.js +64 -0
  77. package/dist/core/base-npc.d.ts +41 -0
  78. package/dist/core/base-npc.js +156 -0
  79. package/dist/core/config.d.ts +22 -0
  80. package/dist/core/config.js +101 -0
  81. package/dist/core/event-npc.d.ts +8 -0
  82. package/dist/core/event-npc.js +10 -0
  83. package/dist/core/logger.d.ts +11 -0
  84. package/dist/core/logger.js +35 -0
  85. package/dist/core/polling-npc.d.ts +18 -0
  86. package/dist/core/polling-npc.js +61 -0
  87. package/dist/core/screen.d.ts +11 -0
  88. package/dist/core/screen.js +24 -0
  89. package/dist/core/session-filter.d.ts +59 -0
  90. package/dist/core/session-filter.js +143 -0
  91. package/dist/core/text.d.ts +6 -0
  92. package/dist/core/text.js +12 -0
  93. package/dist/core/thresholds.d.ts +10 -0
  94. package/dist/core/thresholds.js +44 -0
  95. package/dist/core/types.d.ts +60 -0
  96. package/dist/core/types.js +7 -0
  97. package/dist/core/user-config.d.ts +43 -0
  98. package/dist/core/user-config.js +85 -0
  99. package/dist/doctor.d.ts +8 -0
  100. package/dist/doctor.js +126 -0
  101. package/dist/index.d.ts +41 -0
  102. package/dist/index.js +26 -0
  103. package/dist/npcs/agent-common/activity.d.ts +42 -0
  104. package/dist/npcs/agent-common/activity.js +280 -0
  105. package/dist/npcs/agent-common/git-stats.d.ts +11 -0
  106. package/dist/npcs/agent-common/git-stats.js +67 -0
  107. package/dist/npcs/agent-common/hook-runner.d.ts +62 -0
  108. package/dist/npcs/agent-common/hook-runner.js +245 -0
  109. package/dist/npcs/agent-common/hook-settings.d.ts +75 -0
  110. package/dist/npcs/agent-common/hook-settings.js +265 -0
  111. package/dist/npcs/agent-common/liveness.d.ts +12 -0
  112. package/dist/npcs/agent-common/liveness.js +105 -0
  113. package/dist/npcs/agent-common/lock.d.ts +6 -0
  114. package/dist/npcs/agent-common/lock.js +95 -0
  115. package/dist/npcs/agent-common/runtime.d.ts +16 -0
  116. package/dist/npcs/agent-common/runtime.js +149 -0
  117. package/dist/npcs/agent-common/session.d.ts +192 -0
  118. package/dist/npcs/agent-common/session.js +390 -0
  119. package/dist/npcs/agent-common/shim.d.ts +4 -0
  120. package/dist/npcs/agent-common/shim.js +90 -0
  121. package/dist/npcs/agent-common/watchdog.d.ts +16 -0
  122. package/dist/npcs/agent-common/watchdog.js +144 -0
  123. package/dist/npcs/claude-code/defaults.d.ts +15 -0
  124. package/dist/npcs/claude-code/defaults.js +17 -0
  125. package/dist/npcs/claude-code/hook-handler.d.ts +40 -0
  126. package/dist/npcs/claude-code/hook-handler.js +131 -0
  127. package/dist/npcs/claude-code/index.d.ts +36 -0
  128. package/dist/npcs/claude-code/index.js +92 -0
  129. package/dist/npcs/claude-code/setup.d.ts +58 -0
  130. package/dist/npcs/claude-code/setup.js +121 -0
  131. package/dist/npcs/claude-code/state-machine.d.ts +28 -0
  132. package/dist/npcs/claude-code/state-machine.js +125 -0
  133. package/dist/npcs/codex/defaults.d.ts +24 -0
  134. package/dist/npcs/codex/defaults.js +28 -0
  135. package/dist/npcs/codex/hook-handler.d.ts +24 -0
  136. package/dist/npcs/codex/hook-handler.js +110 -0
  137. package/dist/npcs/codex/index.d.ts +21 -0
  138. package/dist/npcs/codex/index.js +76 -0
  139. package/dist/npcs/codex/rollout.d.ts +39 -0
  140. package/dist/npcs/codex/rollout.js +242 -0
  141. package/dist/npcs/codex/setup.d.ts +37 -0
  142. package/dist/npcs/codex/setup.js +96 -0
  143. package/dist/npcs/codex/watcher.d.ts +65 -0
  144. package/dist/npcs/codex/watcher.js +356 -0
  145. package/dist/npcs/cursor/defaults.d.ts +9 -0
  146. package/dist/npcs/cursor/defaults.js +11 -0
  147. package/dist/npcs/cursor/hook-handler.d.ts +8 -0
  148. package/dist/npcs/cursor/hook-handler.js +60 -0
  149. package/dist/npcs/cursor/index.d.ts +18 -0
  150. package/dist/npcs/cursor/index.js +57 -0
  151. package/dist/npcs/cursor/normalize.d.ts +6 -0
  152. package/dist/npcs/cursor/normalize.js +49 -0
  153. package/dist/npcs/cursor/setup.d.ts +29 -0
  154. package/dist/npcs/cursor/setup.js +90 -0
  155. package/dist/npcs/gemini/defaults.d.ts +9 -0
  156. package/dist/npcs/gemini/defaults.js +11 -0
  157. package/dist/npcs/gemini/hook-handler.d.ts +10 -0
  158. package/dist/npcs/gemini/hook-handler.js +87 -0
  159. package/dist/npcs/gemini/index.d.ts +17 -0
  160. package/dist/npcs/gemini/index.js +56 -0
  161. package/dist/npcs/gemini/setup.d.ts +29 -0
  162. package/dist/npcs/gemini/setup.js +90 -0
  163. package/dist/npcs/github-watcher/defaults.d.ts +8 -0
  164. package/dist/npcs/github-watcher/defaults.js +8 -0
  165. package/dist/npcs/github-watcher/format.d.ts +3 -0
  166. package/dist/npcs/github-watcher/format.js +18 -0
  167. package/dist/npcs/github-watcher/github-api.d.ts +55 -0
  168. package/dist/npcs/github-watcher/github-api.js +120 -0
  169. package/dist/npcs/github-watcher/index.d.ts +35 -0
  170. package/dist/npcs/github-watcher/index.js +144 -0
  171. package/dist/npcs/internet-connectivity/checks.d.ts +24 -0
  172. package/dist/npcs/internet-connectivity/checks.js +84 -0
  173. package/dist/npcs/internet-connectivity/defaults.d.ts +20 -0
  174. package/dist/npcs/internet-connectivity/defaults.js +19 -0
  175. package/dist/npcs/internet-connectivity/index.d.ts +55 -0
  176. package/dist/npcs/internet-connectivity/index.js +246 -0
  177. package/dist/npcs/log-watcher/buffer.d.ts +18 -0
  178. package/dist/npcs/log-watcher/buffer.js +34 -0
  179. package/dist/npcs/log-watcher/defaults.d.ts +9 -0
  180. package/dist/npcs/log-watcher/defaults.js +9 -0
  181. package/dist/npcs/log-watcher/index.d.ts +45 -0
  182. package/dist/npcs/log-watcher/index.js +157 -0
  183. package/dist/npcs/log-watcher/tail.d.ts +9 -0
  184. package/dist/npcs/log-watcher/tail.js +78 -0
  185. package/dist/npcs/process-watcher/defaults.d.ts +6 -0
  186. package/dist/npcs/process-watcher/defaults.js +6 -0
  187. package/dist/npcs/process-watcher/index.d.ts +26 -0
  188. package/dist/npcs/process-watcher/index.js +136 -0
  189. package/dist/npcs/process-watcher/process-scanner.d.ts +19 -0
  190. package/dist/npcs/process-watcher/process-scanner.js +110 -0
  191. package/dist/npcs/receptionist/defaults.d.ts +6 -0
  192. package/dist/npcs/receptionist/defaults.js +6 -0
  193. package/dist/npcs/receptionist/index.d.ts +29 -0
  194. package/dist/npcs/receptionist/index.js +116 -0
  195. package/dist/npcs/receptionist/weather.d.ts +37 -0
  196. package/dist/npcs/receptionist/weather.js +121 -0
  197. package/dist/npcs/system-monitor/collectors.d.ts +45 -0
  198. package/dist/npcs/system-monitor/collectors.js +60 -0
  199. package/dist/npcs/system-monitor/defaults.d.ts +7 -0
  200. package/dist/npcs/system-monitor/defaults.js +10 -0
  201. package/dist/npcs/system-monitor/index.d.ts +27 -0
  202. package/dist/npcs/system-monitor/index.js +101 -0
  203. package/dist/npcs/web-page-watcher/basic-mode.d.ts +16 -0
  204. package/dist/npcs/web-page-watcher/basic-mode.js +52 -0
  205. package/dist/npcs/web-page-watcher/capture.d.ts +49 -0
  206. package/dist/npcs/web-page-watcher/capture.js +75 -0
  207. package/dist/npcs/web-page-watcher/defaults.d.ts +19 -0
  208. package/dist/npcs/web-page-watcher/defaults.js +27 -0
  209. package/dist/npcs/web-page-watcher/index.d.ts +42 -0
  210. package/dist/npcs/web-page-watcher/index.js +230 -0
  211. package/dist/npcs/web-page-watcher/pixelate.d.ts +36 -0
  212. package/dist/npcs/web-page-watcher/pixelate.js +172 -0
  213. package/package.json +73 -3
@@ -0,0 +1,6 @@
1
+ import type { AgentHookEvent } from '../agent-common/hook-runner.js';
2
+ export declare const CURSOR_TO_CANON: Record<string, string>;
3
+ export declare function isCursorInvocation(event: AgentHookEvent): boolean;
4
+ /** Copy only telemetry fields; never retain prompts, model responses or tool output. */
5
+ export declare function normalizeCursorEvent(event: AgentHookEvent): AgentHookEvent;
6
+ //# sourceMappingURL=normalize.d.ts.map
@@ -0,0 +1,49 @@
1
+ export const CURSOR_TO_CANON = {
2
+ sessionStart: 'SessionStart',
3
+ sessionEnd: 'SessionEnd',
4
+ beforeSubmitPrompt: 'UserPromptSubmit',
5
+ preToolUse: 'PreToolUse',
6
+ postToolUse: 'PostToolUse',
7
+ postToolUseFailure: 'PostToolUseFailure',
8
+ beforeShellExecution: 'PreToolUse',
9
+ afterFileEdit: 'PostToolUse',
10
+ stop: 'Stop',
11
+ subagentStart: 'SubagentStart',
12
+ subagentStop: 'SubagentStop',
13
+ preCompact: 'PreCompact',
14
+ };
15
+ export function isCursorInvocation(event) {
16
+ const transcript = typeof event['transcript_path'] === 'string'
17
+ ? event['transcript_path'].replace(/\\/g, '/')
18
+ : '';
19
+ return (typeof event['cursor_version'] === 'string' ||
20
+ (typeof event['conversation_id'] === 'string' && !/(?:^|\/)\.claude(?:\/|$)/.test(transcript)));
21
+ }
22
+ /** Copy only telemetry fields; never retain prompts, model responses or tool output. */
23
+ export function normalizeCursorEvent(event) {
24
+ const name = event.hook_event_name ?? event.event ?? '';
25
+ const roots = event['workspace_roots'];
26
+ const shell = name === 'beforeShellExecution';
27
+ const tools = {
28
+ Shell: 'Bash',
29
+ ReadFile: 'Read',
30
+ Write: 'Write',
31
+ StrReplace: 'Edit',
32
+ Grep: 'Grep',
33
+ Delete: 'Delete',
34
+ Task: 'Task',
35
+ };
36
+ return {
37
+ session_id: typeof event['conversation_id'] === 'string' ? event['conversation_id'] : event.session_id,
38
+ cwd: Array.isArray(roots) && typeof roots[0] === 'string' ? roots[0] : event.cwd,
39
+ hook_event_name: CURSOR_TO_CANON[name] ?? name,
40
+ tool_name: shell
41
+ ? 'Bash'
42
+ : event.tool_name?.startsWith('MCP:')
43
+ ? `mcp__cursor__${event.tool_name.slice(4)}`
44
+ : (tools[event.tool_name ?? ''] ?? event.tool_name),
45
+ tool_input: shell ? { command: event['command'] } : event.tool_input,
46
+ generation_id: typeof event['generation_id'] === 'string' ? event['generation_id'] : undefined,
47
+ };
48
+ }
49
+ //# sourceMappingURL=normalize.js.map
@@ -0,0 +1,29 @@
1
+ import type { HooksFile } from '../agent-common/hook-settings.js';
2
+ /** Telemetry hooks only; model output and prompt text never enter Nocville. */
3
+ export declare const CURSOR_HOOK_EVENTS: string[];
4
+ export declare function cursorSettingsPath(): string;
5
+ export interface CursorSetupOptions {
6
+ apiUrl?: string;
7
+ apiKey?: string;
8
+ map?: string;
9
+ cliPath?: string;
10
+ /** Override ~/.cursor/hooks.json (tests). */
11
+ settingsPath?: string;
12
+ quiet?: boolean;
13
+ }
14
+ /** Install the nocville group into every Cursor event. Mutates `file`. */
15
+ export declare function installCursorHooksInto(file: HooksFile, command: string, opts?: {
16
+ timeout?: number;
17
+ }): {
18
+ migrated: number;
19
+ installed: number;
20
+ };
21
+ export declare function buildCursorHookCommand(_cliPath: string): string;
22
+ /** Merge into Cursor settings, preserving user hooks and a one-time backup. */
23
+ export declare function setupCursorHooks(opts?: CursorSetupOptions): boolean;
24
+ /** Remove all Nocville hooks from `~/.cursor/hooks.json`. */
25
+ export declare function uninstallCursorHooks(opts?: {
26
+ settingsPath?: string;
27
+ quiet?: boolean;
28
+ }): number;
29
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1,90 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { maskKey, persistCredentials } from '../../core/user-config.js';
4
+ import { buildShimHookCommand, stableNodePath, readJsonFile, removeHookGroups, resolveCliPath, writeJsonFile, } from '../agent-common/hook-settings.js';
5
+ import { installHookShim } from '../agent-common/shim.js';
6
+ import { ensureStableCliPath } from '../agent-common/runtime.js';
7
+ import { CURSOR_DEFAULTS, cursorHome } from './defaults.js';
8
+ /** Telemetry hooks only; model output and prompt text never enter Nocville. */
9
+ export const CURSOR_HOOK_EVENTS = [
10
+ 'sessionStart',
11
+ 'sessionEnd',
12
+ 'beforeSubmitPrompt',
13
+ 'postToolUse',
14
+ 'postToolUseFailure',
15
+ 'afterFileEdit',
16
+ 'stop',
17
+ 'subagentStop',
18
+ 'preCompact',
19
+ ];
20
+ export function cursorSettingsPath() {
21
+ return join(cursorHome(), 'hooks.json');
22
+ }
23
+ /** Install the nocville group into every Cursor event. Mutates `file`. */
24
+ export function installCursorHooksInto(file, command, opts = {}) {
25
+ const migrated = removeHookGroups(file);
26
+ file.version = 1;
27
+ file.hooks ??= {};
28
+ for (const event of CURSOR_HOOK_EVENTS) {
29
+ const existing = Array.isArray(file.hooks[event]) ? file.hooks[event] : [];
30
+ file.hooks[event] = [
31
+ ...existing,
32
+ { command, timeout: opts.timeout ?? CURSOR_DEFAULTS.hookTimeoutSeconds },
33
+ ];
34
+ }
35
+ return { migrated, installed: CURSOR_HOOK_EVENTS.length };
36
+ }
37
+ export function buildCursorHookCommand(_cliPath) {
38
+ return buildShimHookCommand('cursor');
39
+ }
40
+ function out(opts, msg) {
41
+ if (!opts.quiet)
42
+ console.log(msg);
43
+ }
44
+ /** Merge into Cursor settings, preserving user hooks and a one-time backup. */
45
+ export function setupCursorHooks(opts = {}) {
46
+ const settingsPath = opts.settingsPath ?? cursorSettingsPath();
47
+ const dir = join(settingsPath, '..');
48
+ if (!existsSync(dir)) {
49
+ console.error(`${dir} does not exist — is Cursor installed? Skipping Cursor setup.`);
50
+ return false;
51
+ }
52
+ const file = readJsonFile(settingsPath);
53
+ if (file === null ||
54
+ (file.version !== undefined && file.version !== 1) ||
55
+ (file.hooks !== undefined &&
56
+ (!file.hooks || typeof file.hooks !== 'object' || Array.isArray(file.hooks)))) {
57
+ console.error(`Unsupported or invalid Cursor hook settings in ${settingsPath}; no settings changed.`);
58
+ return false;
59
+ }
60
+ const creds = persistCredentials(opts);
61
+ const resolved = resolveCliPath(opts.cliPath);
62
+ const cliPath = ensureStableCliPath(resolved);
63
+ installHookShim(stableNodePath(), cliPath);
64
+ const { installed } = installCursorHooksInto(file, buildCursorHookCommand(cliPath), {
65
+ timeout: CURSOR_DEFAULTS.hookTimeoutSeconds,
66
+ });
67
+ writeJsonFile(settingsPath, file);
68
+ out(opts, `✓ Cursor: installed Nocville hooks for ${installed} events in ${settingsPath}`);
69
+ out(opts, ` Hook runs: ${process.execPath} ${cliPath}`);
70
+ out(opts, ` API: ${creds.apiUrl ?? 'https://nocville.com'} key: ${maskKey(creds.apiKey)}`);
71
+ if (!creds.apiKey) {
72
+ out(opts, ' ⚠ No API key saved yet — run: nocville-npc init --api-key=<your team key>');
73
+ }
74
+ return true;
75
+ }
76
+ /** Remove all Nocville hooks from `~/.cursor/hooks.json`. */
77
+ export function uninstallCursorHooks(opts = {}) {
78
+ const settingsPath = opts.settingsPath ?? cursorSettingsPath();
79
+ const file = readJsonFile(settingsPath);
80
+ if (!file || !existsSync(settingsPath)) {
81
+ out(opts, 'Cursor: no hooks.json — nothing to remove.');
82
+ return 0;
83
+ }
84
+ const removed = removeHookGroups(file);
85
+ if (removed > 0)
86
+ writeJsonFile(settingsPath, file);
87
+ out(opts, `✓ Cursor: removed ${removed} Nocville hook(s) from ${settingsPath}`);
88
+ return removed;
89
+ }
90
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1,9 @@
1
+ export declare const GEMINI_DEFAULTS: {
2
+ stateDir: string;
3
+ stateFileMaxAge: number;
4
+ heartbeatSeconds: number;
5
+ minPushGapMs: number;
6
+ hookTimeoutMs: number;
7
+ };
8
+ export declare const geminiHome: () => string;
9
+ //# sourceMappingURL=defaults.d.ts.map
@@ -0,0 +1,11 @@
1
+ import { homedir, tmpdir } from 'node:os';
2
+ import { join } from 'node:path';
3
+ export const GEMINI_DEFAULTS = {
4
+ stateDir: join(tmpdir(), 'nocville-gemini'),
5
+ stateFileMaxAge: 24 * 60 * 60 * 1000,
6
+ heartbeatSeconds: 90,
7
+ minPushGapMs: 1500,
8
+ hookTimeoutMs: 5000,
9
+ };
10
+ export const geminiHome = () => join(homedir(), '.gemini');
11
+ //# sourceMappingURL=defaults.js.map
@@ -0,0 +1,10 @@
1
+ import type { NpcConfig } from '../../core/types.js';
2
+ import type { AgentHookEvent, AgentHookSpec, HookRunOptions } from '../agent-common/hook-runner.js';
3
+ import type { AgentAdapter, AgentSessionState, AgentTransition } from '../agent-common/session.js';
4
+ export declare const GEMINI_ADAPTER: AgentAdapter;
5
+ export declare function geminiServiceId(sessionId: string, cwd: string): string;
6
+ /** Only phase and tool metadata are used; prompts/responses/transcripts are never read. */
7
+ export declare function geminiTransition(event: AgentHookEvent, prev: AgentSessionState | null): AgentTransition;
8
+ export declare const GEMINI_HOOK_SPEC: AgentHookSpec;
9
+ export declare function handleGeminiHookEvent(config: NpcConfig, rawInput?: string, opts?: HookRunOptions): Promise<void>;
10
+ //# sourceMappingURL=hook-handler.d.ts.map
@@ -0,0 +1,87 @@
1
+ import { THINKING, describeToolUse, failureText } from '../agent-common/activity.js';
2
+ import { hookEventName, readStdin, runAgentHook } from '../agent-common/hook-runner.js';
3
+ import { projectNameOf, sanitizeServiceId } from '../agent-common/session.js';
4
+ import { GEMINI_DEFAULTS } from './defaults.js';
5
+ export const GEMINI_ADAPTER = {
6
+ command: 'gemini',
7
+ label: 'Gemini',
8
+ family: 'gemini',
9
+ stateDir: GEMINI_DEFAULTS.stateDir,
10
+ heartbeatSeconds: GEMINI_DEFAULTS.heartbeatSeconds,
11
+ processNames: ['gemini', 'node'],
12
+ nodeCommandPattern: /[\/\\](?:@google[\/\\])?gemini(?:-cli[\/\\]|(?:\.[cm]?js)?(?:\s|$))/i,
13
+ };
14
+ export function geminiServiceId(sessionId, cwd) {
15
+ return sanitizeServiceId(`gemini-${projectNameOf(cwd)}-${sessionId.slice(0, 8)}`);
16
+ }
17
+ const idle = (activity) => ({
18
+ state: 'IDLE',
19
+ activity,
20
+ status: 'healthy',
21
+ mood: 'idle',
22
+ });
23
+ const working = (activity, warning = false) => ({
24
+ state: 'WORKING',
25
+ activity,
26
+ status: warning ? 'warning' : 'healthy',
27
+ mood: warning ? 'concerned' : 'happy',
28
+ });
29
+ /** Only phase and tool metadata are used; prompts/responses/transcripts are never read. */
30
+ export function geminiTransition(event, prev) {
31
+ const cwd = typeof event.cwd === 'string' ? event.cwd : prev?.projectDir;
32
+ switch (hookEventName(event)) {
33
+ case 'SessionStart':
34
+ return idle('Ready for a task');
35
+ case 'BeforeAgent':
36
+ return working(THINKING);
37
+ case 'BeforeTool':
38
+ return working(describeToolUse(event.tool_name, event.tool_input, cwd));
39
+ case 'AfterTool': {
40
+ const response = event['tool_response'];
41
+ if (response && typeof response === 'object' && 'error' in response && response.error)
42
+ return working(failureText(event.tool_name), true);
43
+ return { ...working(), quiet: prev?.state === 'WORKING' };
44
+ }
45
+ case 'Notification':
46
+ if (event['notification_type'] === 'ToolPermission')
47
+ return {
48
+ state: 'WAITING',
49
+ status: 'warning',
50
+ mood: 'concerned',
51
+ activity: 'Needs your approval',
52
+ };
53
+ break;
54
+ case 'AfterAgent':
55
+ return idle('Done — waiting for next task');
56
+ case 'PreCompress':
57
+ return working('Compacting context');
58
+ case 'SessionEnd':
59
+ return {
60
+ state: 'IDLE',
61
+ activity: 'Session ended',
62
+ status: 'unknown',
63
+ mood: 'idle',
64
+ offline: true,
65
+ };
66
+ }
67
+ return {
68
+ state: prev?.state ?? 'IDLE',
69
+ status: prev?.status ?? 'healthy',
70
+ mood: prev?.mood ?? 'idle',
71
+ quiet: true,
72
+ };
73
+ }
74
+ export const GEMINI_HOOK_SPEC = {
75
+ adapter: GEMINI_ADAPTER,
76
+ serviceIdFor: geminiServiceId,
77
+ transition: geminiTransition,
78
+ stdoutOnSuccess: '{}',
79
+ statsEvents: ['AfterAgent'],
80
+ };
81
+ export async function handleGeminiHookEvent(config, rawInput, opts = {}) {
82
+ await runAgentHook(GEMINI_HOOK_SPEC, config, rawInput ?? readStdin(), {
83
+ minPushGapMs: GEMINI_DEFAULTS.minPushGapMs,
84
+ ...opts,
85
+ });
86
+ }
87
+ //# sourceMappingURL=hook-handler.js.map
@@ -0,0 +1,17 @@
1
+ import type { NpcConfig } from '../../core/types.js';
2
+ export { GEMINI_DEFAULTS } from './defaults.js';
3
+ export { GEMINI_ADAPTER, geminiTransition, geminiServiceId, handleGeminiHookEvent, } from './hook-handler.js';
4
+ export { setupGeminiHooks, uninstallGeminiHooks, installGeminiHooksInto, GEMINI_HOOK_EVENTS, } from './setup.js';
5
+ export type { GeminiSetupOptions } from './setup.js';
6
+ export interface GeminiSubcommandOptions {
7
+ subcommand: string;
8
+ config: NpcConfig;
9
+ flags: Record<string, string | boolean>;
10
+ args?: string[];
11
+ }
12
+ /**
13
+ * gemini subcommands: setup | uninstall | hook | flush | watchdog | cleanup.
14
+ * `hook` always emits {}; detached helpers stay silent.
15
+ */
16
+ export declare function handleGeminiCommand(opts: GeminiSubcommandOptions): Promise<void>;
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,56 @@
1
+ import { numberFlag, stringFlag } from '../../core/config.js';
2
+ import { readStdin, runFlushCommand } from '../agent-common/hook-runner.js';
3
+ import { runWatchdog } from '../agent-common/watchdog.js';
4
+ import { cleanupSessionStates } from '../agent-common/session.js';
5
+ import { GEMINI_DEFAULTS } from './defaults.js';
6
+ import { GEMINI_ADAPTER, handleGeminiHookEvent } from './hook-handler.js';
7
+ import { setupGeminiHooks, uninstallGeminiHooks } from './setup.js';
8
+ export { GEMINI_DEFAULTS } from './defaults.js';
9
+ export { GEMINI_ADAPTER, geminiTransition, geminiServiceId, handleGeminiHookEvent, } from './hook-handler.js';
10
+ export { setupGeminiHooks, uninstallGeminiHooks, installGeminiHooksInto, GEMINI_HOOK_EVENTS, } from './setup.js';
11
+ /**
12
+ * gemini subcommands: setup | uninstall | hook | flush | watchdog | cleanup.
13
+ * `hook` always emits {}; detached helpers stay silent.
14
+ */
15
+ export async function handleGeminiCommand(opts) {
16
+ const { subcommand, config, flags } = opts;
17
+ switch (subcommand) {
18
+ case 'setup': {
19
+ if (flags['uninstall']) {
20
+ uninstallGeminiHooks();
21
+ break;
22
+ }
23
+ const ok = setupGeminiHooks({
24
+ apiUrl: stringFlag(flags, 'api-url') ?? process.env['NOCVILLE_API_URL'],
25
+ apiKey: stringFlag(flags, 'api-key') ?? process.env['NOCVILLE_API_KEY'],
26
+ map: stringFlag(flags, 'map'),
27
+ cliPath: process.argv[1],
28
+ });
29
+ if (!ok)
30
+ process.exitCode = 1;
31
+ break;
32
+ }
33
+ case 'uninstall':
34
+ uninstallGeminiHooks();
35
+ break;
36
+ case 'hook':
37
+ await handleGeminiHookEvent(config, readStdin(), { mode: 'detach' });
38
+ break;
39
+ case 'watchdog':
40
+ await runWatchdog(GEMINI_ADAPTER, config, opts.args?.[0]);
41
+ break;
42
+ case 'flush':
43
+ await runFlushCommand(GEMINI_ADAPTER, config, opts.args?.[0], numberFlag(flags, 'delay'), flags['stats'] === true);
44
+ break;
45
+ case 'cleanup': {
46
+ const removed = await cleanupSessionStates(GEMINI_DEFAULTS.stateDir, GEMINI_DEFAULTS.stateFileMaxAge);
47
+ console.log(`Cleanup complete: ${removed} stale file(s) removed`);
48
+ break;
49
+ }
50
+ default:
51
+ console.error(`Unknown gemini subcommand: ${subcommand}`);
52
+ console.error('Available subcommands: setup, uninstall, hook, cleanup');
53
+ process.exitCode = 1;
54
+ }
55
+ }
56
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,29 @@
1
+ import type { HooksFile } from '../agent-common/hook-settings.js';
2
+ /** Telemetry hooks only; model output and prompt text never enter Nocville. */
3
+ export declare const GEMINI_HOOK_EVENTS: readonly ["SessionStart", "SessionEnd", "BeforeAgent", "BeforeTool", "AfterTool", "Notification", "AfterAgent", "PreCompress"];
4
+ export declare function geminiSettingsPath(): string;
5
+ export interface GeminiSetupOptions {
6
+ apiUrl?: string;
7
+ apiKey?: string;
8
+ map?: string;
9
+ cliPath?: string;
10
+ /** Override ~/.gemini/settings.json (tests). */
11
+ settingsPath?: string;
12
+ quiet?: boolean;
13
+ }
14
+ /** Install the nocville group into every Gemini event. Mutates `file`. */
15
+ export declare function installGeminiHooksInto(file: HooksFile, command: string, opts?: {
16
+ timeout?: number;
17
+ }): {
18
+ migrated: number;
19
+ installed: number;
20
+ };
21
+ export declare function buildGeminiHookCommand(_cliPath: string): string;
22
+ /** Merge into Gemini settings, preserving user hooks and a one-time backup. */
23
+ export declare function setupGeminiHooks(opts?: GeminiSetupOptions): boolean;
24
+ /** Remove all Nocville hooks from `~/.gemini/settings.json`. */
25
+ export declare function uninstallGeminiHooks(opts?: {
26
+ settingsPath?: string;
27
+ quiet?: boolean;
28
+ }): number;
29
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1,90 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { maskKey, persistCredentials } from '../../core/user-config.js';
4
+ import { buildShimHookCommand, stableNodePath, installHookGroups, readJsonFile, removeHookGroups, resolveCliPath, writeJsonFile, } from '../agent-common/hook-settings.js';
5
+ import { installHookShim } from '../agent-common/shim.js';
6
+ import { ensureStableCliPath } from '../agent-common/runtime.js';
7
+ import { GEMINI_DEFAULTS, geminiHome } from './defaults.js';
8
+ /** Telemetry hooks only; model output and prompt text never enter Nocville. */
9
+ export const GEMINI_HOOK_EVENTS = [
10
+ 'SessionStart',
11
+ 'SessionEnd',
12
+ 'BeforeAgent',
13
+ 'BeforeTool',
14
+ 'AfterTool',
15
+ 'Notification',
16
+ 'AfterAgent',
17
+ 'PreCompress',
18
+ ];
19
+ export function geminiSettingsPath() {
20
+ return join(geminiHome(), 'settings.json');
21
+ }
22
+ /** Install the nocville group into every Gemini event. Mutates `file`. */
23
+ export function installGeminiHooksInto(file, command, opts = {}) {
24
+ const result = installHookGroups(file, command, GEMINI_HOOK_EVENTS, {
25
+ perEvent: Object.fromEntries(GEMINI_HOOK_EVENTS.map((event) => [
26
+ event,
27
+ {
28
+ name: 'nocville',
29
+ timeout: opts.timeout ?? GEMINI_DEFAULTS.hookTimeoutMs,
30
+ },
31
+ ])),
32
+ });
33
+ // Omit the optional matcher so lifecycle sources/reasons are all observed too.
34
+ for (const event of GEMINI_HOOK_EVENTS) {
35
+ const group = file.hooks[event].at(-1);
36
+ delete group.matcher;
37
+ }
38
+ return result;
39
+ }
40
+ export function buildGeminiHookCommand(_cliPath) {
41
+ return buildShimHookCommand('gemini');
42
+ }
43
+ function out(opts, msg) {
44
+ if (!opts.quiet)
45
+ console.log(msg);
46
+ }
47
+ /** Merge into Gemini settings, preserving user hooks and a one-time backup. */
48
+ export function setupGeminiHooks(opts = {}) {
49
+ const settingsPath = opts.settingsPath ?? geminiSettingsPath();
50
+ const dir = join(settingsPath, '..');
51
+ if (!existsSync(dir)) {
52
+ console.error(`${dir} does not exist — is Gemini installed? Skipping Gemini setup.`);
53
+ return false;
54
+ }
55
+ const file = readJsonFile(settingsPath);
56
+ if (file === null) {
57
+ console.error(`Could not parse ${settingsPath}; fix it (or move it aside) and re-run setup.`);
58
+ return false;
59
+ }
60
+ const creds = persistCredentials(opts);
61
+ const resolved = resolveCliPath(opts.cliPath);
62
+ const cliPath = ensureStableCliPath(resolved);
63
+ installHookShim(stableNodePath(), cliPath);
64
+ const { installed } = installGeminiHooksInto(file, buildGeminiHookCommand(cliPath), {
65
+ timeout: GEMINI_DEFAULTS.hookTimeoutMs,
66
+ });
67
+ writeJsonFile(settingsPath, file);
68
+ out(opts, `✓ Gemini: installed Nocville hooks for ${installed} events in ${settingsPath}`);
69
+ out(opts, ` Hook runs: ${process.execPath} ${cliPath}`);
70
+ out(opts, ` API: ${creds.apiUrl ?? 'https://nocville.com'} key: ${maskKey(creds.apiKey)}`);
71
+ if (!creds.apiKey) {
72
+ out(opts, ' ⚠ No API key saved yet — run: nocville-npc init --api-key=<your team key>');
73
+ }
74
+ return true;
75
+ }
76
+ /** Remove all Nocville hooks from `~/.gemini/settings.json`. */
77
+ export function uninstallGeminiHooks(opts = {}) {
78
+ const settingsPath = opts.settingsPath ?? geminiSettingsPath();
79
+ const file = readJsonFile(settingsPath);
80
+ if (!file || !existsSync(settingsPath)) {
81
+ out(opts, 'Gemini: no settings.json — nothing to remove.');
82
+ return 0;
83
+ }
84
+ const removed = removeHookGroups(file);
85
+ if (removed > 0)
86
+ writeJsonFile(settingsPath, file);
87
+ out(opts, `✓ Gemini: removed ${removed} Nocville hook(s) from ${settingsPath}`);
88
+ return removed;
89
+ }
90
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1,8 @@
1
+ export declare const DEFAULTS: {
2
+ pollInterval: number;
3
+ fetchTimeoutMs: number;
4
+ perPage: number;
5
+ /** Polls to keep the 'new-commit' sub-status active after a fresh commit. */
6
+ newCommitPolls: number;
7
+ };
8
+ //# sourceMappingURL=defaults.d.ts.map
@@ -0,0 +1,8 @@
1
+ export const DEFAULTS = {
2
+ pollInterval: 60000, // 60s
3
+ fetchTimeoutMs: 5000,
4
+ perPage: 10,
5
+ /** Polls to keep the 'new-commit' sub-status active after a fresh commit. */
6
+ newCommitPolls: 2,
7
+ };
8
+ //# sourceMappingURL=defaults.js.map
@@ -0,0 +1,3 @@
1
+ /** Short relative-time string like "3m ago", "2h ago", "1d ago". */
2
+ export declare function relativeTime(iso: string, now?: number): string;
3
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1,18 @@
1
+ /** Short relative-time string like "3m ago", "2h ago", "1d ago". */
2
+ export function relativeTime(iso, now = Date.now()) {
3
+ const t = Date.parse(iso);
4
+ if (!Number.isFinite(t))
5
+ return 'unknown';
6
+ const secs = Math.max(0, Math.round((now - t) / 1000));
7
+ if (secs < 60)
8
+ return `${secs}s ago`;
9
+ const mins = Math.round(secs / 60);
10
+ if (mins < 60)
11
+ return `${mins}m ago`;
12
+ const hrs = Math.round(mins / 60);
13
+ if (hrs < 24)
14
+ return `${hrs}h ago`;
15
+ const days = Math.round(hrs / 24);
16
+ return `${days}d ago`;
17
+ }
18
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1,55 @@
1
+ export interface CommitInfo {
2
+ sha: string;
3
+ author: string;
4
+ message: string;
5
+ date: string;
6
+ }
7
+ export interface FetchCommitsResult {
8
+ /** 304 Not Modified — the ETag matched, nothing changed, no rate-limit cost. */
9
+ notModified: boolean;
10
+ commits: CommitInfo[];
11
+ etag: string | null;
12
+ rateRemaining: number | null;
13
+ status: number;
14
+ }
15
+ /** Minimal fetch signature so tests can inject a stub. */
16
+ export type FetchFn = (url: string, init?: {
17
+ headers?: Record<string, string>;
18
+ signal?: AbortSignal;
19
+ }) => Promise<{
20
+ ok: boolean;
21
+ status: number;
22
+ headers: {
23
+ get(name: string): string | null;
24
+ };
25
+ json(): Promise<unknown>;
26
+ text(): Promise<string>;
27
+ }>;
28
+ export interface FetchOptions {
29
+ token?: string;
30
+ etag?: string | null;
31
+ branch?: string;
32
+ timeoutMs?: number;
33
+ perPage?: number;
34
+ /** Injectable fetch (defaults to the global fetch). */
35
+ fetchFn?: FetchFn;
36
+ }
37
+ /** Look up a repo's default branch (one call at startup). Null on failure. */
38
+ export declare function fetchDefaultBranch(repo: string, opts?: FetchOptions): Promise<string | null>;
39
+ /**
40
+ * Fetch recent commits from the GitHub REST API using conditional requests.
41
+ * Passing the previous ETag yields a 304 (which does not count against the
42
+ * unauthenticated 60/hour limit) when nothing has changed. Never throws —
43
+ * network errors surface as `status: 0`.
44
+ */
45
+ export declare function fetchCommits(repo: string, opts?: FetchOptions): Promise<FetchCommitsResult>;
46
+ /** Count how many commits landed within the last `hours`. */
47
+ export declare function commitsWithin(commits: CommitInfo[], hours: number, now?: number): number;
48
+ /**
49
+ * Return the commits newer than `lastSeenSha`, newest-first. When the sha isn't
50
+ * present in the window (more than a page of new commits, or rewritten history)
51
+ * every fetched commit is treated as new. An undefined sha means "not seeded
52
+ * yet" — returns empty so the caller can seed without a spurious announcement.
53
+ */
54
+ export declare function newCommitsSince(commits: CommitInfo[], lastSeenSha: string | null): CommitInfo[];
55
+ //# sourceMappingURL=github-api.d.ts.map