@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,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,6 @@
1
+ /**
2
+ * Truncate a string to at most `max` characters, appending an ellipsis when cut.
3
+ * Used to keep messages and screen lines within API limits.
4
+ */
5
+ export declare function truncate(s: string, max?: number): string;
6
+ //# sourceMappingURL=text.d.ts.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Truncate a string to at most `max` characters, appending an ellipsis when cut.
3
+ * Used to keep messages and screen lines within API limits.
4
+ */
5
+ export function truncate(s, max = 200) {
6
+ if (s.length <= max)
7
+ return s;
8
+ if (max <= 1)
9
+ return s.slice(0, Math.max(0, max));
10
+ return s.slice(0, max - 1) + '…';
11
+ }
12
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1,10 @@
1
+ import type { Status, ThresholdConfig } from './types.js';
2
+ /**
3
+ * Evaluate a numeric value against thresholds to determine status.
4
+ */
5
+ export declare function evaluateThreshold(value: number, config: ThresholdConfig): Status;
6
+ /**
7
+ * Returns the worst (most severe) status from an array.
8
+ */
9
+ export declare function worstStatus(statuses: Status[]): Status;
10
+ //# sourceMappingURL=thresholds.d.ts.map
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Evaluate a numeric value against thresholds to determine status.
3
+ */
4
+ export function evaluateThreshold(value, config) {
5
+ const direction = config.direction ?? 'above';
6
+ if (direction === 'above') {
7
+ if (value >= config.critical)
8
+ return 'critical';
9
+ if (value >= config.warning)
10
+ return 'warning';
11
+ return 'healthy';
12
+ }
13
+ else {
14
+ if (value <= config.critical)
15
+ return 'critical';
16
+ if (value <= config.warning)
17
+ return 'warning';
18
+ return 'healthy';
19
+ }
20
+ }
21
+ const STATUS_SEVERITY = {
22
+ healthy: 0,
23
+ unknown: 1,
24
+ warning: 2,
25
+ critical: 3,
26
+ };
27
+ /**
28
+ * Returns the worst (most severe) status from an array.
29
+ */
30
+ export function worstStatus(statuses) {
31
+ if (statuses.length === 0)
32
+ return 'unknown';
33
+ let worst = 'healthy';
34
+ let worstSeverity = 0;
35
+ for (const s of statuses) {
36
+ const severity = STATUS_SEVERITY[s];
37
+ if (severity > worstSeverity) {
38
+ worst = s;
39
+ worstSeverity = severity;
40
+ }
41
+ }
42
+ return worst;
43
+ }
44
+ //# sourceMappingURL=thresholds.js.map
@@ -0,0 +1,60 @@
1
+ import type { Status, Mood, ScreenData, BehaviorHints, CompanionSpec } from '@nocville/node';
2
+ export type { Status, Mood } from '@nocville/node';
3
+ export type { ScreenData, ScreenValue, BehaviorHints, CompanionSpec } from '@nocville/node';
4
+ /** NPC archetype — drives server-side movement speed and default behavior. */
5
+ export type Archetype = 'service' | 'database' | 'trading' | 'ai-agent' | 'pipeline' | 'gateway' | 'custom';
6
+ /** A companion declaration is the SDK's CompanionSpec. */
7
+ export type CompanionDecl = CompanionSpec;
8
+ export interface NpcConfig {
9
+ apiUrl: string;
10
+ apiKey: string;
11
+ serviceId: string;
12
+ debug?: boolean;
13
+ /** Target map for status pushes — a map id or name. Omit for the team default. */
14
+ map?: string;
15
+ /** Friendly display name for the NPC banner. Overrides the per-NPC default. */
16
+ name?: string;
17
+ }
18
+ export interface CollectedMetrics {
19
+ status: Status;
20
+ mood?: Mood;
21
+ message: string;
22
+ metrics: Record<string, number | string>;
23
+ /** Granular sub-state (e.g. 'offline', 'new-commit', 'degraded'). */
24
+ subStatus?: string;
25
+ /** Companion screen payload for this cycle. */
26
+ screenData?: ScreenData;
27
+ /** Per-cycle behavior override, merged over the NPC's base `behavior`. */
28
+ behavior?: BehaviorHints;
29
+ }
30
+ export interface ThresholdConfig {
31
+ warning: number;
32
+ critical: number;
33
+ /** 'above' means value > threshold triggers; 'below' means value < threshold triggers */
34
+ direction?: 'above' | 'below';
35
+ }
36
+ export type NpcState = 'stopped' | 'running' | 'stopping';
37
+ export interface LogLevel {
38
+ name: string;
39
+ value: number;
40
+ }
41
+ export declare const LOG_LEVELS: {
42
+ readonly debug: {
43
+ readonly name: "DEBUG";
44
+ readonly value: 0;
45
+ };
46
+ readonly info: {
47
+ readonly name: "INFO";
48
+ readonly value: 1;
49
+ };
50
+ readonly warn: {
51
+ readonly name: "WARN";
52
+ readonly value: 2;
53
+ };
54
+ readonly error: {
55
+ readonly name: "ERROR";
56
+ readonly value: 3;
57
+ };
58
+ };
59
+ export type LogLevelName = keyof typeof LOG_LEVELS;
60
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,7 @@
1
+ export const LOG_LEVELS = {
2
+ debug: { name: 'DEBUG', value: 0 },
3
+ info: { name: 'INFO', value: 1 },
4
+ warn: { name: 'WARN', value: 2 },
5
+ error: { name: 'ERROR', value: 3 },
6
+ };
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Persisted per-user CLI config (`nocville-npc init`). Lets hook commands and
3
+ * long-running NPCs run without `--api-key` flags or env vars — the key never
4
+ * has to appear in ~/.claude/settings.json, ~/.codex/hooks.json or `ps` output.
5
+ */
6
+ export interface UserConfig {
7
+ apiUrl?: string;
8
+ apiKey?: string;
9
+ map?: string;
10
+ }
11
+ /**
12
+ * Location of the config file. `NOCVILLE_CONFIG` overrides (used by tests);
13
+ * otherwise `$XDG_CONFIG_HOME/nocville/config.json`, else `~/.config/nocville/config.json`.
14
+ */
15
+ export declare function userConfigPath(): string;
16
+ /** Read the user config; missing or unreadable files yield `{}` (never throws). */
17
+ export declare function loadUserConfig(path?: string): UserConfig;
18
+ /**
19
+ * Merge `patch` into the stored config and write it with owner-only perms
20
+ * (dir 0700, file 0600) via an atomic rename. Undefined fields are left as-is;
21
+ * an empty string clears a field. Returns the written path.
22
+ */
23
+ export declare function saveUserConfig(patch: UserConfig, path?: string): string;
24
+ /** Mask an API key for display: keep a short prefix/suffix only. */
25
+ export declare function maskKey(key: string | undefined): string;
26
+ /**
27
+ * Save credentials (explicit flags win; else migrate any from a legacy hook
28
+ * command) and return the effective config. Shared by claude-code/codex setup.
29
+ */
30
+ export declare function persistCredentials(explicit: {
31
+ apiUrl?: string;
32
+ apiKey?: string;
33
+ map?: string;
34
+ }, legacy?: {
35
+ apiUrl?: string;
36
+ apiKey?: string;
37
+ }): {
38
+ apiUrl?: string;
39
+ apiKey?: string;
40
+ map?: string;
41
+ path: string;
42
+ };
43
+ //# sourceMappingURL=user-config.d.ts.map