@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,41 @@
1
+ import { NocvilleClient } from '@nocville/node';
2
+ import type { NpcConfig, NpcState, CollectedMetrics, Archetype, BehaviorHints, CompanionDecl } from './types.js';
3
+ import { Logger } from './logger.js';
4
+ export declare abstract class BaseNPC {
5
+ protected client: NocvilleClient;
6
+ protected logger: Logger;
7
+ protected config: NpcConfig;
8
+ protected state: NpcState;
9
+ private shutdownHandlers;
10
+ private authErrorLogged;
11
+ constructor(config: NpcConfig);
12
+ abstract get name(): string;
13
+ /**
14
+ * Friendly display name for the NPC banner, sent on every push. A `--name`
15
+ * flag / NOCVILLE_NAME env (via `config.name`) always wins; otherwise the
16
+ * per-NPC `defaultDisplayName`. null lets the server fall back to serviceId.
17
+ */
18
+ get displayName(): string | null;
19
+ /** Per-NPC default banner name. Overridden by each NPC; null means none. */
20
+ protected get defaultDisplayName(): string | null;
21
+ /** The banner name to send, clamped to the API's 32-char limit, or undefined. */
22
+ private pushName;
23
+ /** NPC archetype — steers movement speed and default behavior. Override per NPC. */
24
+ get archetype(): Archetype;
25
+ /** Base behavior hints sent on every push (merged with per-cycle overrides). */
26
+ get behavior(): BehaviorHints;
27
+ /** Companion pixel-art object to spawn alongside this NPC, or null for none. */
28
+ get companion(): CompanionDecl | null;
29
+ /** Heartbeat interval in seconds — the server marks the NPC unknown at 2x this. */
30
+ get heartbeatSeconds(): number;
31
+ protected abstract onStart(): Promise<void>;
32
+ protected abstract onStop(): Promise<void>;
33
+ start(): Promise<void>;
34
+ stop(): Promise<void>;
35
+ protected pushMetrics(metrics: CollectedMetrics): Promise<void>;
36
+ /** Log a push failure. A 401 gets a single actionable error; others warn each time. */
37
+ protected reportPushFailure(error?: string): void;
38
+ private attachSignalHandlers;
39
+ private detachSignalHandlers;
40
+ }
41
+ //# sourceMappingURL=base-npc.d.ts.map
@@ -0,0 +1,156 @@
1
+ import { NocvilleClient } from '@nocville/node';
2
+ import { Logger } from './logger.js';
3
+ import { truncate } from './text.js';
4
+ export class BaseNPC {
5
+ client;
6
+ logger;
7
+ config;
8
+ state = 'stopped';
9
+ shutdownHandlers = [];
10
+ authErrorLogged = false;
11
+ constructor(config) {
12
+ this.config = config;
13
+ // Logger prefix is set to a generic name; subclasses override via `name` getter
14
+ // We can't access `this.name` here since it's abstract, so use a placeholder
15
+ this.logger = new Logger('npc', config.debug);
16
+ this.client = new NocvilleClient({
17
+ apiUrl: config.apiUrl,
18
+ apiKey: config.apiKey,
19
+ serviceId: config.serviceId,
20
+ debug: config.debug,
21
+ retries: 1,
22
+ timeout: 5000,
23
+ });
24
+ }
25
+ /**
26
+ * Friendly display name for the NPC banner, sent on every push. A `--name`
27
+ * flag / NOCVILLE_NAME env (via `config.name`) always wins; otherwise the
28
+ * per-NPC `defaultDisplayName`. null lets the server fall back to serviceId.
29
+ */
30
+ get displayName() {
31
+ return this.config.name ?? this.defaultDisplayName;
32
+ }
33
+ /** Per-NPC default banner name. Overridden by each NPC; null means none. */
34
+ get defaultDisplayName() {
35
+ return null;
36
+ }
37
+ /** The banner name to send, clamped to the API's 32-char limit, or undefined. */
38
+ pushName() {
39
+ const name = this.displayName;
40
+ return name ? truncate(name, 32) : undefined;
41
+ }
42
+ /** NPC archetype — steers movement speed and default behavior. Override per NPC. */
43
+ get archetype() {
44
+ return 'service';
45
+ }
46
+ /** Base behavior hints sent on every push (merged with per-cycle overrides). */
47
+ get behavior() {
48
+ return {};
49
+ }
50
+ /** Companion pixel-art object to spawn alongside this NPC, or null for none. */
51
+ get companion() {
52
+ return null;
53
+ }
54
+ /** Heartbeat interval in seconds — the server marks the NPC unknown at 2x this. */
55
+ get heartbeatSeconds() {
56
+ return 120;
57
+ }
58
+ async start() {
59
+ if (this.state === 'running') {
60
+ this.logger.warn('NPC already running');
61
+ return;
62
+ }
63
+ this.state = 'running';
64
+ // Update logger prefix now that `name` is available
65
+ this.logger = new Logger(this.name, this.config.debug);
66
+ this.logger.info(`Starting ${this.name}`);
67
+ this.attachSignalHandlers();
68
+ await this.onStart();
69
+ }
70
+ async stop() {
71
+ if (this.state !== 'running')
72
+ return;
73
+ this.state = 'stopping';
74
+ this.logger.info(`Stopping ${this.name}`);
75
+ try {
76
+ await this.onStop();
77
+ }
78
+ catch (err) {
79
+ this.logger.error('Error during stop', {
80
+ error: err instanceof Error ? err.message : String(err),
81
+ });
82
+ }
83
+ // Announce clean shutdown so the server despawns the NPC promptly rather
84
+ // than waiting out the staleness window.
85
+ try {
86
+ await this.client.pushStatus({
87
+ status: 'unknown',
88
+ mood: 'idle',
89
+ name: this.pushName(),
90
+ message: 'Going offline',
91
+ subStatus: 'offline',
92
+ offline: true,
93
+ map: this.config.map,
94
+ });
95
+ }
96
+ catch {
97
+ // Best effort
98
+ }
99
+ this.detachSignalHandlers();
100
+ this.state = 'stopped';
101
+ this.logger.info(`${this.name} stopped`);
102
+ }
103
+ async pushMetrics(metrics) {
104
+ this.logger.debug('Pushing metrics', {
105
+ status: metrics.status,
106
+ message: metrics.message,
107
+ });
108
+ const behavior = { ...this.behavior, ...metrics.behavior };
109
+ const result = await this.client.pushStatus({
110
+ status: metrics.status,
111
+ mood: metrics.mood,
112
+ name: this.pushName(),
113
+ message: metrics.message ? truncate(metrics.message, 500) : undefined,
114
+ metrics: metrics.metrics,
115
+ archetype: this.archetype,
116
+ subStatus: metrics.subStatus,
117
+ screenData: metrics.screenData,
118
+ behavior: Object.keys(behavior).length > 0 ? behavior : undefined,
119
+ companion: this.companion ?? undefined,
120
+ alertConfig: { heartbeatInterval: this.heartbeatSeconds },
121
+ map: this.config.map,
122
+ });
123
+ if (!result.success) {
124
+ this.reportPushFailure(result.error);
125
+ }
126
+ }
127
+ /** Log a push failure. A 401 gets a single actionable error; others warn each time. */
128
+ reportPushFailure(error) {
129
+ if (error && /\b401\b/.test(error)) {
130
+ if (!this.authErrorLogged) {
131
+ this.authErrorLogged = true;
132
+ this.logger.error('API key rejected (HTTP 401) — check NOCVILLE_API_KEY / team settings');
133
+ }
134
+ return;
135
+ }
136
+ this.logger.warn('Failed to push metrics', { error });
137
+ }
138
+ attachSignalHandlers() {
139
+ const handler = () => {
140
+ void this.stop().then(() => process.exit(0));
141
+ };
142
+ process.on('SIGINT', handler);
143
+ process.on('SIGTERM', handler);
144
+ this.shutdownHandlers.push(() => {
145
+ process.removeListener('SIGINT', handler);
146
+ process.removeListener('SIGTERM', handler);
147
+ });
148
+ }
149
+ detachSignalHandlers() {
150
+ for (const cleanup of this.shutdownHandlers) {
151
+ cleanup();
152
+ }
153
+ this.shutdownHandlers = [];
154
+ }
155
+ }
156
+ //# sourceMappingURL=base-npc.js.map
@@ -0,0 +1,22 @@
1
+ import type { NpcConfig } from './types.js';
2
+ export interface ParsedArgs {
3
+ command: string | undefined;
4
+ subcommand: string | undefined;
5
+ /** Positional args after the subcommand. */
6
+ args: string[];
7
+ flags: Record<string, string | boolean>;
8
+ }
9
+ /**
10
+ * Simple hand-rolled CLI arg parser.
11
+ * Supports: --key=value, --key value, --flag (boolean)
12
+ */
13
+ export declare function parseArgs(argv: string[]): ParsedArgs;
14
+ /**
15
+ * Resolves NPC config from: constructor options > CLI args > env vars >
16
+ * the saved user config (`nocville-npc init`, ~/.config/nocville/config.json).
17
+ */
18
+ export declare function resolveConfig(flags: Record<string, string | boolean>, overrides?: Partial<NpcConfig>): NpcConfig;
19
+ export declare function stringFlag(flags: Record<string, string | boolean>, key: string): string | undefined;
20
+ export declare function boolFlag(flags: Record<string, string | boolean>, key: string): boolean | undefined;
21
+ export declare function numberFlag(flags: Record<string, string | boolean>, key: string): number | undefined;
22
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1,101 @@
1
+ import { loadUserConfig } from './user-config.js';
2
+ /**
3
+ * Simple hand-rolled CLI arg parser.
4
+ * Supports: --key=value, --key value, --flag (boolean)
5
+ */
6
+ export function parseArgs(argv) {
7
+ const args = argv.slice(2); // skip node and script path
8
+ const flags = {};
9
+ let command;
10
+ let subcommand;
11
+ const rest = [];
12
+ for (let i = 0; i < args.length; i++) {
13
+ const arg = args[i];
14
+ if (arg.startsWith('--')) {
15
+ const eqIndex = arg.indexOf('=');
16
+ if (eqIndex !== -1) {
17
+ const key = arg.slice(2, eqIndex);
18
+ const value = arg.slice(eqIndex + 1);
19
+ flags[key] = value;
20
+ }
21
+ else {
22
+ const key = arg.slice(2);
23
+ const next = args[i + 1];
24
+ if (next && !next.startsWith('--')) {
25
+ flags[key] = next;
26
+ i++;
27
+ }
28
+ else {
29
+ flags[key] = true;
30
+ }
31
+ }
32
+ }
33
+ else if (!command) {
34
+ command = arg;
35
+ }
36
+ else if (!subcommand) {
37
+ subcommand = arg;
38
+ }
39
+ else {
40
+ rest.push(arg);
41
+ }
42
+ }
43
+ return { command, subcommand, args: rest, flags };
44
+ }
45
+ /**
46
+ * Resolves NPC config from: constructor options > CLI args > env vars >
47
+ * the saved user config (`nocville-npc init`, ~/.config/nocville/config.json).
48
+ */
49
+ export function resolveConfig(flags, overrides) {
50
+ const saved = loadUserConfig();
51
+ const apiUrl = overrides?.apiUrl ||
52
+ stringFlag(flags, 'api-url') ||
53
+ process.env['NOCVILLE_API_URL'] ||
54
+ saved.apiUrl ||
55
+ 'https://nocville.com';
56
+ const apiKey = overrides?.apiKey ||
57
+ stringFlag(flags, 'api-key') ||
58
+ process.env['NOCVILLE_API_KEY'] ||
59
+ saved.apiKey ||
60
+ '';
61
+ const serviceId = overrides?.serviceId ||
62
+ stringFlag(flags, 'service-id') ||
63
+ process.env['NOCVILLE_SERVICE_ID'] ||
64
+ '';
65
+ const debug = overrides?.debug ??
66
+ boolFlag(flags, 'debug') ??
67
+ (process.env['NOCVILLE_DEBUG'] === '1' || false);
68
+ const map = overrides?.map ||
69
+ stringFlag(flags, 'map') ||
70
+ process.env['NOCVILLE_MAP'] ||
71
+ saved.map ||
72
+ undefined;
73
+ const name = overrides?.name || stringFlag(flags, 'name') || process.env['NOCVILLE_NAME'] || undefined;
74
+ return { apiUrl, apiKey, serviceId, debug, map, name };
75
+ }
76
+ export function stringFlag(flags, key) {
77
+ const val = flags[key];
78
+ if (typeof val === 'string')
79
+ return val;
80
+ return undefined;
81
+ }
82
+ export function boolFlag(flags, key) {
83
+ const val = flags[key];
84
+ if (typeof val === 'boolean')
85
+ return val;
86
+ if (val === 'true')
87
+ return true;
88
+ if (val === 'false')
89
+ return false;
90
+ return undefined;
91
+ }
92
+ export function numberFlag(flags, key) {
93
+ const val = flags[key];
94
+ if (typeof val === 'string') {
95
+ const n = Number(val);
96
+ if (!Number.isNaN(n))
97
+ return n;
98
+ }
99
+ return undefined;
100
+ }
101
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,8 @@
1
+ import { BaseNPC } from './base-npc.js';
2
+ export declare abstract class EventNPC extends BaseNPC {
3
+ protected abstract setupEventSource(): Promise<void>;
4
+ protected abstract teardownEventSource(): Promise<void>;
5
+ protected onStart(): Promise<void>;
6
+ protected onStop(): Promise<void>;
7
+ }
8
+ //# sourceMappingURL=event-npc.d.ts.map
@@ -0,0 +1,10 @@
1
+ import { BaseNPC } from './base-npc.js';
2
+ export class EventNPC extends BaseNPC {
3
+ async onStart() {
4
+ await this.setupEventSource();
5
+ }
6
+ async onStop() {
7
+ await this.teardownEventSource();
8
+ }
9
+ }
10
+ //# sourceMappingURL=event-npc.js.map
@@ -0,0 +1,11 @@
1
+ export declare class Logger {
2
+ private level;
3
+ private prefix;
4
+ constructor(prefix: string, debug?: boolean);
5
+ debug(message: string, data?: Record<string, unknown>): void;
6
+ info(message: string, data?: Record<string, unknown>): void;
7
+ warn(message: string, data?: Record<string, unknown>): void;
8
+ error(message: string, data?: Record<string, unknown>): void;
9
+ private log;
10
+ }
11
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1,35 @@
1
+ import { LOG_LEVELS } from './types.js';
2
+ export class Logger {
3
+ level;
4
+ prefix;
5
+ constructor(prefix, debug = false) {
6
+ this.prefix = prefix;
7
+ this.level = debug ? LOG_LEVELS.debug.value : LOG_LEVELS.info.value;
8
+ }
9
+ debug(message, data) {
10
+ this.log('debug', message, data);
11
+ }
12
+ info(message, data) {
13
+ this.log('info', message, data);
14
+ }
15
+ warn(message, data) {
16
+ this.log('warn', message, data);
17
+ }
18
+ error(message, data) {
19
+ this.log('error', message, data);
20
+ }
21
+ log(level, message, data) {
22
+ const levelInfo = LOG_LEVELS[level];
23
+ if (levelInfo.value < this.level)
24
+ return;
25
+ const timestamp = new Date().toISOString();
26
+ const parts = [`[${timestamp}]`, `[${levelInfo.name}]`, `[${this.prefix}]`, message];
27
+ if (data && Object.keys(data).length > 0) {
28
+ parts.push(JSON.stringify(data));
29
+ }
30
+ const output = parts.join(' ');
31
+ // stdout belongs to hook protocols; diagnostics must never corrupt their JSON.
32
+ console.error(output);
33
+ }
34
+ }
35
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1,18 @@
1
+ import { BaseNPC } from './base-npc.js';
2
+ import type { NpcConfig, CollectedMetrics } from './types.js';
3
+ export interface PollingNpcConfig extends NpcConfig {
4
+ pollInterval?: number;
5
+ }
6
+ export declare abstract class PollingNPC extends BaseNPC {
7
+ private timer;
8
+ private collecting;
9
+ protected pollInterval: number;
10
+ constructor(config: PollingNpcConfig);
11
+ /** Scale staleness to the poll cadence: the server marks unknown at ~2x the interval. */
12
+ get heartbeatSeconds(): number;
13
+ protected abstract collect(): Promise<CollectedMetrics>;
14
+ protected onStart(): Promise<void>;
15
+ protected onStop(): Promise<void>;
16
+ private runCollection;
17
+ }
18
+ //# sourceMappingURL=polling-npc.d.ts.map
@@ -0,0 +1,61 @@
1
+ import { BaseNPC } from './base-npc.js';
2
+ function clamp(n, min, max) {
3
+ return Math.min(max, Math.max(min, n));
4
+ }
5
+ export class PollingNPC extends BaseNPC {
6
+ timer = null;
7
+ collecting = false;
8
+ pollInterval;
9
+ constructor(config) {
10
+ super(config);
11
+ this.pollInterval = config.pollInterval ?? 15000;
12
+ }
13
+ /** Scale staleness to the poll cadence: the server marks unknown at ~2x the interval. */
14
+ get heartbeatSeconds() {
15
+ return clamp(Math.ceil((this.pollInterval * 2) / 1000), 30, 3600);
16
+ }
17
+ async onStart() {
18
+ // Collect immediately on start
19
+ await this.runCollection();
20
+ // Then on interval
21
+ this.timer = setInterval(() => {
22
+ void this.runCollection();
23
+ }, this.pollInterval);
24
+ // For slow pollers, keep the NPC alive with a lightweight heartbeat between
25
+ // collections so it never falsely goes stale mid-interval.
26
+ if (this.pollInterval > 60_000) {
27
+ this.client.startHeartbeat(30_000);
28
+ }
29
+ }
30
+ async onStop() {
31
+ if (this.timer) {
32
+ clearInterval(this.timer);
33
+ this.timer = null;
34
+ }
35
+ this.client.stopHeartbeat();
36
+ }
37
+ async runCollection() {
38
+ if (this.state !== 'running')
39
+ return;
40
+ // Overlap guard: if a slow collector is still running, skip this tick rather
41
+ // than stacking collections (load-bearing for screenshot/render collectors).
42
+ if (this.collecting) {
43
+ this.logger.debug('Previous collection still in flight, skipping this tick');
44
+ return;
45
+ }
46
+ this.collecting = true;
47
+ try {
48
+ const metrics = await this.collect();
49
+ await this.pushMetrics(metrics);
50
+ }
51
+ catch (err) {
52
+ this.logger.error('Collection failed', {
53
+ error: err instanceof Error ? err.message : String(err),
54
+ });
55
+ }
56
+ finally {
57
+ this.collecting = false;
58
+ }
59
+ }
60
+ }
61
+ //# sourceMappingURL=polling-npc.js.map
@@ -0,0 +1,11 @@
1
+ import type { Status } from './types.js';
2
+ /** A ScreenValue color token understood by the client screen renderer. */
3
+ export type ScreenColor = 'green' | 'amber' | 'red' | 'gray';
4
+ /** Map a status to the color the client renderer uses for it. */
5
+ export declare function statusColor(status: Status): ScreenColor;
6
+ /**
7
+ * Push a numeric sample into a bounded rolling history (oldest dropped past `max`).
8
+ * Mutates and returns the array. The API caps sparklines at 60 samples.
9
+ */
10
+ export declare function pushHistory(history: number[], value: number, max?: number): number[];
11
+ //# sourceMappingURL=screen.d.ts.map
@@ -0,0 +1,24 @@
1
+ /** Map a status to the color the client renderer uses for it. */
2
+ export function statusColor(status) {
3
+ switch (status) {
4
+ case 'healthy':
5
+ return 'green';
6
+ case 'warning':
7
+ return 'amber';
8
+ case 'critical':
9
+ return 'red';
10
+ default:
11
+ return 'gray';
12
+ }
13
+ }
14
+ /**
15
+ * Push a numeric sample into a bounded rolling history (oldest dropped past `max`).
16
+ * Mutates and returns the array. The API caps sparklines at 60 samples.
17
+ */
18
+ export function pushHistory(history, value, max = 60) {
19
+ history.push(value);
20
+ if (history.length > max)
21
+ history.splice(0, history.length - max);
22
+ return history;
23
+ }
24
+ //# sourceMappingURL=screen.js.map
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Which AI-agent sessions publish to the office (`~/.config/nocville/sessions.json`).
3
+ *
4
+ * - Empty lists (or no file): every session publishes — the default.
5
+ * - `deny` always wins.
6
+ * - If `allow` has any entry, only sessions matching one of them publish.
7
+ *
8
+ * Patterns are case-insensitive globs (`*` any run of characters, `?` one character).
9
+ * A `projects` pattern without a slash matches any folder name on the session's working
10
+ * directory path (so `client-*` also covers `~/work/client-acme/api`); with a slash (or a
11
+ * leading `~`) it matches that directory or anything inside it (`~/work/secret`,
12
+ * `~/work/*`). A `sessions` pattern matches the agent's session name (Claude Code's
13
+ * session title); sessions without a name never match it.
14
+ *
15
+ * Read on every push, so edits apply to running sessions without reinstalling hooks.
16
+ */
17
+ export interface SessionRules {
18
+ projects: string[];
19
+ sessions: string[];
20
+ }
21
+ export interface SessionFilter {
22
+ allow: SessionRules;
23
+ deny: SessionRules;
24
+ /** Set when the file exists but can't be used; nothing publishes until it's fixed. */
25
+ error?: string;
26
+ }
27
+ export interface SessionIdentity {
28
+ /** The session's working directory. */
29
+ projectDir: string;
30
+ /** Session name/title, when the agent has one. */
31
+ name?: string;
32
+ }
33
+ export interface PublishDecision {
34
+ publish: boolean;
35
+ reason: string;
36
+ }
37
+ export declare const SESSION_FILTER_TEMPLATE: {
38
+ _help: string[];
39
+ allow: {
40
+ projects: never[];
41
+ sessions: never[];
42
+ };
43
+ deny: {
44
+ projects: never[];
45
+ sessions: never[];
46
+ };
47
+ };
48
+ /** `NOCVILLE_SESSIONS_CONFIG` overrides; otherwise `sessions.json` beside config.json. */
49
+ export declare function sessionFilterPath(): string;
50
+ /** Never throws. A missing file means "publish everything". */
51
+ export declare function loadSessionFilter(path?: string): SessionFilter;
52
+ /** Write the commented template if no file exists; returns the path. */
53
+ export declare function ensureSessionFilterTemplate(path?: string): {
54
+ path: string;
55
+ created: boolean;
56
+ };
57
+ /** Decide whether a session may publish. Pure apart from `homedir()` for `~` patterns. */
58
+ export declare function sessionPublishDecision(filter: SessionFilter, who: SessionIdentity): PublishDecision;
59
+ //# sourceMappingURL=session-filter.d.ts.map