@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.
- package/README.md +243 -2
- package/dist/cjs/cli.js +533 -0
- package/dist/cjs/core/api-key.js +69 -0
- package/dist/cjs/core/base-npc.js +160 -0
- package/dist/cjs/core/config.js +108 -0
- package/dist/cjs/core/event-npc.js +14 -0
- package/dist/cjs/core/logger.js +39 -0
- package/dist/cjs/core/polling-npc.js +65 -0
- package/dist/cjs/core/screen.js +28 -0
- package/dist/cjs/core/text.js +15 -0
- package/dist/cjs/core/thresholds.js +48 -0
- package/dist/cjs/core/types.js +10 -0
- package/dist/cjs/core/user-config.js +92 -0
- package/dist/cjs/doctor.js +130 -0
- package/dist/cjs/index.js +90 -0
- package/dist/cjs/npcs/agent-common/activity.js +291 -0
- package/dist/cjs/npcs/agent-common/git-stats.js +71 -0
- package/dist/cjs/npcs/agent-common/hook-runner.js +252 -0
- package/dist/cjs/npcs/agent-common/hook-settings.js +279 -0
- package/dist/cjs/npcs/agent-common/liveness.js +112 -0
- package/dist/cjs/npcs/agent-common/lock.js +99 -0
- package/dist/cjs/npcs/agent-common/runtime.js +154 -0
- package/dist/cjs/npcs/agent-common/session.js +390 -0
- package/dist/cjs/npcs/agent-common/shim.js +96 -0
- package/dist/cjs/npcs/agent-common/watchdog.js +149 -0
- package/dist/cjs/npcs/claude-code/defaults.js +20 -0
- package/dist/cjs/npcs/claude-code/hook-handler.js +139 -0
- package/dist/cjs/npcs/claude-code/index.js +104 -0
- package/dist/cjs/npcs/claude-code/setup.js +133 -0
- package/dist/cjs/npcs/claude-code/state-machine.js +130 -0
- package/dist/cjs/npcs/codex/defaults.js +32 -0
- package/dist/cjs/npcs/codex/hook-handler.js +116 -0
- package/dist/cjs/npcs/codex/index.js +92 -0
- package/dist/cjs/npcs/codex/rollout.js +246 -0
- package/dist/cjs/npcs/codex/setup.js +104 -0
- package/dist/cjs/npcs/codex/watcher.js +346 -0
- package/dist/cjs/npcs/cursor/defaults.js +15 -0
- package/dist/cjs/npcs/cursor/hook-handler.js +65 -0
- package/dist/cjs/npcs/cursor/index.js +72 -0
- package/dist/cjs/npcs/cursor/normalize.js +54 -0
- package/dist/cjs/npcs/cursor/setup.js +98 -0
- package/dist/cjs/npcs/gemini/defaults.js +15 -0
- package/dist/cjs/npcs/gemini/hook-handler.js +93 -0
- package/dist/cjs/npcs/gemini/index.js +69 -0
- package/dist/cjs/npcs/gemini/setup.js +98 -0
- package/dist/cjs/npcs/github-watcher/defaults.js +11 -0
- package/dist/cjs/npcs/github-watcher/format.js +21 -0
- package/dist/cjs/npcs/github-watcher/github-api.js +126 -0
- package/dist/cjs/npcs/github-watcher/index.js +148 -0
- package/dist/cjs/npcs/internet-connectivity/checks.js +92 -0
- package/dist/cjs/npcs/internet-connectivity/defaults.js +22 -0
- package/dist/cjs/npcs/internet-connectivity/index.js +251 -0
- package/dist/cjs/npcs/log-watcher/buffer.js +40 -0
- package/dist/cjs/npcs/log-watcher/defaults.js +12 -0
- package/dist/cjs/npcs/log-watcher/index.js +161 -0
- package/dist/cjs/npcs/log-watcher/tail.js +81 -0
- package/dist/cjs/npcs/process-watcher/defaults.js +9 -0
- package/dist/cjs/npcs/process-watcher/index.js +140 -0
- package/dist/cjs/npcs/process-watcher/process-scanner.js +114 -0
- package/dist/cjs/npcs/receptionist/defaults.js +9 -0
- package/dist/cjs/npcs/receptionist/index.js +120 -0
- package/dist/cjs/npcs/receptionist/weather.js +129 -0
- package/dist/cjs/npcs/system-monitor/collectors.js +68 -0
- package/dist/cjs/npcs/system-monitor/defaults.js +13 -0
- package/dist/cjs/npcs/system-monitor/index.js +105 -0
- package/dist/cjs/npcs/web-page-watcher/basic-mode.js +58 -0
- package/dist/cjs/npcs/web-page-watcher/capture.js +80 -0
- package/dist/cjs/npcs/web-page-watcher/defaults.js +31 -0
- package/dist/cjs/npcs/web-page-watcher/index.js +234 -0
- package/dist/cjs/npcs/web-page-watcher/pixelate.js +181 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +498 -0
- package/dist/core/api-key.d.ts +34 -0
- package/dist/core/api-key.js +64 -0
- package/dist/core/base-npc.d.ts +41 -0
- package/dist/core/base-npc.js +156 -0
- package/dist/core/config.d.ts +22 -0
- package/dist/core/config.js +101 -0
- package/dist/core/event-npc.d.ts +8 -0
- package/dist/core/event-npc.js +10 -0
- package/dist/core/logger.d.ts +11 -0
- package/dist/core/logger.js +35 -0
- package/dist/core/polling-npc.d.ts +18 -0
- package/dist/core/polling-npc.js +61 -0
- package/dist/core/screen.d.ts +11 -0
- package/dist/core/screen.js +24 -0
- package/dist/core/text.d.ts +6 -0
- package/dist/core/text.js +12 -0
- package/dist/core/thresholds.d.ts +10 -0
- package/dist/core/thresholds.js +44 -0
- package/dist/core/types.d.ts +60 -0
- package/dist/core/types.js +7 -0
- package/dist/core/user-config.d.ts +43 -0
- package/dist/core/user-config.js +85 -0
- package/dist/doctor.d.ts +8 -0
- package/dist/doctor.js +126 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +25 -0
- package/dist/npcs/agent-common/activity.d.ts +42 -0
- package/dist/npcs/agent-common/activity.js +280 -0
- package/dist/npcs/agent-common/git-stats.d.ts +11 -0
- package/dist/npcs/agent-common/git-stats.js +67 -0
- package/dist/npcs/agent-common/hook-runner.d.ts +62 -0
- package/dist/npcs/agent-common/hook-runner.js +245 -0
- package/dist/npcs/agent-common/hook-settings.d.ts +75 -0
- package/dist/npcs/agent-common/hook-settings.js +265 -0
- package/dist/npcs/agent-common/liveness.d.ts +12 -0
- package/dist/npcs/agent-common/liveness.js +105 -0
- package/dist/npcs/agent-common/lock.d.ts +6 -0
- package/dist/npcs/agent-common/lock.js +95 -0
- package/dist/npcs/agent-common/runtime.d.ts +16 -0
- package/dist/npcs/agent-common/runtime.js +149 -0
- package/dist/npcs/agent-common/session.d.ts +184 -0
- package/dist/npcs/agent-common/session.js +368 -0
- package/dist/npcs/agent-common/shim.d.ts +4 -0
- package/dist/npcs/agent-common/shim.js +90 -0
- package/dist/npcs/agent-common/watchdog.d.ts +16 -0
- package/dist/npcs/agent-common/watchdog.js +144 -0
- package/dist/npcs/claude-code/defaults.d.ts +15 -0
- package/dist/npcs/claude-code/defaults.js +17 -0
- package/dist/npcs/claude-code/hook-handler.d.ts +40 -0
- package/dist/npcs/claude-code/hook-handler.js +131 -0
- package/dist/npcs/claude-code/index.d.ts +36 -0
- package/dist/npcs/claude-code/index.js +92 -0
- package/dist/npcs/claude-code/setup.d.ts +58 -0
- package/dist/npcs/claude-code/setup.js +121 -0
- package/dist/npcs/claude-code/state-machine.d.ts +28 -0
- package/dist/npcs/claude-code/state-machine.js +125 -0
- package/dist/npcs/codex/defaults.d.ts +24 -0
- package/dist/npcs/codex/defaults.js +28 -0
- package/dist/npcs/codex/hook-handler.d.ts +24 -0
- package/dist/npcs/codex/hook-handler.js +110 -0
- package/dist/npcs/codex/index.d.ts +21 -0
- package/dist/npcs/codex/index.js +76 -0
- package/dist/npcs/codex/rollout.d.ts +39 -0
- package/dist/npcs/codex/rollout.js +242 -0
- package/dist/npcs/codex/setup.d.ts +37 -0
- package/dist/npcs/codex/setup.js +96 -0
- package/dist/npcs/codex/watcher.d.ts +62 -0
- package/dist/npcs/codex/watcher.js +341 -0
- package/dist/npcs/cursor/defaults.d.ts +9 -0
- package/dist/npcs/cursor/defaults.js +11 -0
- package/dist/npcs/cursor/hook-handler.d.ts +8 -0
- package/dist/npcs/cursor/hook-handler.js +60 -0
- package/dist/npcs/cursor/index.d.ts +18 -0
- package/dist/npcs/cursor/index.js +57 -0
- package/dist/npcs/cursor/normalize.d.ts +6 -0
- package/dist/npcs/cursor/normalize.js +49 -0
- package/dist/npcs/cursor/setup.d.ts +29 -0
- package/dist/npcs/cursor/setup.js +90 -0
- package/dist/npcs/gemini/defaults.d.ts +9 -0
- package/dist/npcs/gemini/defaults.js +11 -0
- package/dist/npcs/gemini/hook-handler.d.ts +10 -0
- package/dist/npcs/gemini/hook-handler.js +87 -0
- package/dist/npcs/gemini/index.d.ts +17 -0
- package/dist/npcs/gemini/index.js +56 -0
- package/dist/npcs/gemini/setup.d.ts +29 -0
- package/dist/npcs/gemini/setup.js +90 -0
- package/dist/npcs/github-watcher/defaults.d.ts +8 -0
- package/dist/npcs/github-watcher/defaults.js +8 -0
- package/dist/npcs/github-watcher/format.d.ts +3 -0
- package/dist/npcs/github-watcher/format.js +18 -0
- package/dist/npcs/github-watcher/github-api.d.ts +55 -0
- package/dist/npcs/github-watcher/github-api.js +120 -0
- package/dist/npcs/github-watcher/index.d.ts +35 -0
- package/dist/npcs/github-watcher/index.js +144 -0
- package/dist/npcs/internet-connectivity/checks.d.ts +24 -0
- package/dist/npcs/internet-connectivity/checks.js +84 -0
- package/dist/npcs/internet-connectivity/defaults.d.ts +20 -0
- package/dist/npcs/internet-connectivity/defaults.js +19 -0
- package/dist/npcs/internet-connectivity/index.d.ts +55 -0
- package/dist/npcs/internet-connectivity/index.js +246 -0
- package/dist/npcs/log-watcher/buffer.d.ts +18 -0
- package/dist/npcs/log-watcher/buffer.js +34 -0
- package/dist/npcs/log-watcher/defaults.d.ts +9 -0
- package/dist/npcs/log-watcher/defaults.js +9 -0
- package/dist/npcs/log-watcher/index.d.ts +45 -0
- package/dist/npcs/log-watcher/index.js +157 -0
- package/dist/npcs/log-watcher/tail.d.ts +9 -0
- package/dist/npcs/log-watcher/tail.js +78 -0
- package/dist/npcs/process-watcher/defaults.d.ts +6 -0
- package/dist/npcs/process-watcher/defaults.js +6 -0
- package/dist/npcs/process-watcher/index.d.ts +26 -0
- package/dist/npcs/process-watcher/index.js +136 -0
- package/dist/npcs/process-watcher/process-scanner.d.ts +19 -0
- package/dist/npcs/process-watcher/process-scanner.js +110 -0
- package/dist/npcs/receptionist/defaults.d.ts +6 -0
- package/dist/npcs/receptionist/defaults.js +6 -0
- package/dist/npcs/receptionist/index.d.ts +29 -0
- package/dist/npcs/receptionist/index.js +116 -0
- package/dist/npcs/receptionist/weather.d.ts +37 -0
- package/dist/npcs/receptionist/weather.js +121 -0
- package/dist/npcs/system-monitor/collectors.d.ts +45 -0
- package/dist/npcs/system-monitor/collectors.js +60 -0
- package/dist/npcs/system-monitor/defaults.d.ts +7 -0
- package/dist/npcs/system-monitor/defaults.js +10 -0
- package/dist/npcs/system-monitor/index.d.ts +27 -0
- package/dist/npcs/system-monitor/index.js +101 -0
- package/dist/npcs/web-page-watcher/basic-mode.d.ts +16 -0
- package/dist/npcs/web-page-watcher/basic-mode.js +52 -0
- package/dist/npcs/web-page-watcher/capture.d.ts +49 -0
- package/dist/npcs/web-page-watcher/capture.js +75 -0
- package/dist/npcs/web-page-watcher/defaults.d.ts +19 -0
- package/dist/npcs/web-page-watcher/defaults.js +27 -0
- package/dist/npcs/web-page-watcher/index.d.ts +42 -0
- package/dist/npcs/web-page-watcher/index.js +230 -0
- package/dist/npcs/web-page-watcher/pixelate.d.ts +36 -0
- package/dist/npcs/web-page-watcher/pixelate.js +172 -0
- package/package.json +73 -3
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BaseNPC = void 0;
|
|
4
|
+
const node_1 = require("@nocville/node");
|
|
5
|
+
const logger_js_1 = require("./logger.js");
|
|
6
|
+
const text_js_1 = require("./text.js");
|
|
7
|
+
class BaseNPC {
|
|
8
|
+
client;
|
|
9
|
+
logger;
|
|
10
|
+
config;
|
|
11
|
+
state = 'stopped';
|
|
12
|
+
shutdownHandlers = [];
|
|
13
|
+
authErrorLogged = false;
|
|
14
|
+
constructor(config) {
|
|
15
|
+
this.config = config;
|
|
16
|
+
// Logger prefix is set to a generic name; subclasses override via `name` getter
|
|
17
|
+
// We can't access `this.name` here since it's abstract, so use a placeholder
|
|
18
|
+
this.logger = new logger_js_1.Logger('npc', config.debug);
|
|
19
|
+
this.client = new node_1.NocvilleClient({
|
|
20
|
+
apiUrl: config.apiUrl,
|
|
21
|
+
apiKey: config.apiKey,
|
|
22
|
+
serviceId: config.serviceId,
|
|
23
|
+
debug: config.debug,
|
|
24
|
+
retries: 1,
|
|
25
|
+
timeout: 5000,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Friendly display name for the NPC banner, sent on every push. A `--name`
|
|
30
|
+
* flag / NOCVILLE_NAME env (via `config.name`) always wins; otherwise the
|
|
31
|
+
* per-NPC `defaultDisplayName`. null lets the server fall back to serviceId.
|
|
32
|
+
*/
|
|
33
|
+
get displayName() {
|
|
34
|
+
return this.config.name ?? this.defaultDisplayName;
|
|
35
|
+
}
|
|
36
|
+
/** Per-NPC default banner name. Overridden by each NPC; null means none. */
|
|
37
|
+
get defaultDisplayName() {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
/** The banner name to send, clamped to the API's 32-char limit, or undefined. */
|
|
41
|
+
pushName() {
|
|
42
|
+
const name = this.displayName;
|
|
43
|
+
return name ? (0, text_js_1.truncate)(name, 32) : undefined;
|
|
44
|
+
}
|
|
45
|
+
/** NPC archetype — steers movement speed and default behavior. Override per NPC. */
|
|
46
|
+
get archetype() {
|
|
47
|
+
return 'service';
|
|
48
|
+
}
|
|
49
|
+
/** Base behavior hints sent on every push (merged with per-cycle overrides). */
|
|
50
|
+
get behavior() {
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
/** Companion pixel-art object to spawn alongside this NPC, or null for none. */
|
|
54
|
+
get companion() {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
/** Heartbeat interval in seconds — the server marks the NPC unknown at 2x this. */
|
|
58
|
+
get heartbeatSeconds() {
|
|
59
|
+
return 120;
|
|
60
|
+
}
|
|
61
|
+
async start() {
|
|
62
|
+
if (this.state === 'running') {
|
|
63
|
+
this.logger.warn('NPC already running');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
this.state = 'running';
|
|
67
|
+
// Update logger prefix now that `name` is available
|
|
68
|
+
this.logger = new logger_js_1.Logger(this.name, this.config.debug);
|
|
69
|
+
this.logger.info(`Starting ${this.name}`);
|
|
70
|
+
this.attachSignalHandlers();
|
|
71
|
+
await this.onStart();
|
|
72
|
+
}
|
|
73
|
+
async stop() {
|
|
74
|
+
if (this.state !== 'running')
|
|
75
|
+
return;
|
|
76
|
+
this.state = 'stopping';
|
|
77
|
+
this.logger.info(`Stopping ${this.name}`);
|
|
78
|
+
try {
|
|
79
|
+
await this.onStop();
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
this.logger.error('Error during stop', {
|
|
83
|
+
error: err instanceof Error ? err.message : String(err),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// Announce clean shutdown so the server despawns the NPC promptly rather
|
|
87
|
+
// than waiting out the staleness window.
|
|
88
|
+
try {
|
|
89
|
+
await this.client.pushStatus({
|
|
90
|
+
status: 'unknown',
|
|
91
|
+
mood: 'idle',
|
|
92
|
+
name: this.pushName(),
|
|
93
|
+
message: 'Going offline',
|
|
94
|
+
subStatus: 'offline',
|
|
95
|
+
offline: true,
|
|
96
|
+
map: this.config.map,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Best effort
|
|
101
|
+
}
|
|
102
|
+
this.detachSignalHandlers();
|
|
103
|
+
this.state = 'stopped';
|
|
104
|
+
this.logger.info(`${this.name} stopped`);
|
|
105
|
+
}
|
|
106
|
+
async pushMetrics(metrics) {
|
|
107
|
+
this.logger.debug('Pushing metrics', {
|
|
108
|
+
status: metrics.status,
|
|
109
|
+
message: metrics.message,
|
|
110
|
+
});
|
|
111
|
+
const behavior = { ...this.behavior, ...metrics.behavior };
|
|
112
|
+
const result = await this.client.pushStatus({
|
|
113
|
+
status: metrics.status,
|
|
114
|
+
mood: metrics.mood,
|
|
115
|
+
name: this.pushName(),
|
|
116
|
+
message: metrics.message ? (0, text_js_1.truncate)(metrics.message, 500) : undefined,
|
|
117
|
+
metrics: metrics.metrics,
|
|
118
|
+
archetype: this.archetype,
|
|
119
|
+
subStatus: metrics.subStatus,
|
|
120
|
+
screenData: metrics.screenData,
|
|
121
|
+
behavior: Object.keys(behavior).length > 0 ? behavior : undefined,
|
|
122
|
+
companion: this.companion ?? undefined,
|
|
123
|
+
alertConfig: { heartbeatInterval: this.heartbeatSeconds },
|
|
124
|
+
map: this.config.map,
|
|
125
|
+
});
|
|
126
|
+
if (!result.success) {
|
|
127
|
+
this.reportPushFailure(result.error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** Log a push failure. A 401 gets a single actionable error; others warn each time. */
|
|
131
|
+
reportPushFailure(error) {
|
|
132
|
+
if (error && /\b401\b/.test(error)) {
|
|
133
|
+
if (!this.authErrorLogged) {
|
|
134
|
+
this.authErrorLogged = true;
|
|
135
|
+
this.logger.error('API key rejected (HTTP 401) — check NOCVILLE_API_KEY / team settings');
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
this.logger.warn('Failed to push metrics', { error });
|
|
140
|
+
}
|
|
141
|
+
attachSignalHandlers() {
|
|
142
|
+
const handler = () => {
|
|
143
|
+
void this.stop().then(() => process.exit(0));
|
|
144
|
+
};
|
|
145
|
+
process.on('SIGINT', handler);
|
|
146
|
+
process.on('SIGTERM', handler);
|
|
147
|
+
this.shutdownHandlers.push(() => {
|
|
148
|
+
process.removeListener('SIGINT', handler);
|
|
149
|
+
process.removeListener('SIGTERM', handler);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
detachSignalHandlers() {
|
|
153
|
+
for (const cleanup of this.shutdownHandlers) {
|
|
154
|
+
cleanup();
|
|
155
|
+
}
|
|
156
|
+
this.shutdownHandlers = [];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
exports.BaseNPC = BaseNPC;
|
|
160
|
+
//# sourceMappingURL=base-npc.js.map
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseArgs = parseArgs;
|
|
4
|
+
exports.resolveConfig = resolveConfig;
|
|
5
|
+
exports.stringFlag = stringFlag;
|
|
6
|
+
exports.boolFlag = boolFlag;
|
|
7
|
+
exports.numberFlag = numberFlag;
|
|
8
|
+
const user_config_js_1 = require("./user-config.js");
|
|
9
|
+
/**
|
|
10
|
+
* Simple hand-rolled CLI arg parser.
|
|
11
|
+
* Supports: --key=value, --key value, --flag (boolean)
|
|
12
|
+
*/
|
|
13
|
+
function parseArgs(argv) {
|
|
14
|
+
const args = argv.slice(2); // skip node and script path
|
|
15
|
+
const flags = {};
|
|
16
|
+
let command;
|
|
17
|
+
let subcommand;
|
|
18
|
+
const rest = [];
|
|
19
|
+
for (let i = 0; i < args.length; i++) {
|
|
20
|
+
const arg = args[i];
|
|
21
|
+
if (arg.startsWith('--')) {
|
|
22
|
+
const eqIndex = arg.indexOf('=');
|
|
23
|
+
if (eqIndex !== -1) {
|
|
24
|
+
const key = arg.slice(2, eqIndex);
|
|
25
|
+
const value = arg.slice(eqIndex + 1);
|
|
26
|
+
flags[key] = value;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
const key = arg.slice(2);
|
|
30
|
+
const next = args[i + 1];
|
|
31
|
+
if (next && !next.startsWith('--')) {
|
|
32
|
+
flags[key] = next;
|
|
33
|
+
i++;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
flags[key] = true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else if (!command) {
|
|
41
|
+
command = arg;
|
|
42
|
+
}
|
|
43
|
+
else if (!subcommand) {
|
|
44
|
+
subcommand = arg;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
rest.push(arg);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { command, subcommand, args: rest, flags };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Resolves NPC config from: constructor options > CLI args > env vars >
|
|
54
|
+
* the saved user config (`nocville-npc init`, ~/.config/nocville/config.json).
|
|
55
|
+
*/
|
|
56
|
+
function resolveConfig(flags, overrides) {
|
|
57
|
+
const saved = (0, user_config_js_1.loadUserConfig)();
|
|
58
|
+
const apiUrl = overrides?.apiUrl ||
|
|
59
|
+
stringFlag(flags, 'api-url') ||
|
|
60
|
+
process.env['NOCVILLE_API_URL'] ||
|
|
61
|
+
saved.apiUrl ||
|
|
62
|
+
'https://nocville.com';
|
|
63
|
+
const apiKey = overrides?.apiKey ||
|
|
64
|
+
stringFlag(flags, 'api-key') ||
|
|
65
|
+
process.env['NOCVILLE_API_KEY'] ||
|
|
66
|
+
saved.apiKey ||
|
|
67
|
+
'';
|
|
68
|
+
const serviceId = overrides?.serviceId ||
|
|
69
|
+
stringFlag(flags, 'service-id') ||
|
|
70
|
+
process.env['NOCVILLE_SERVICE_ID'] ||
|
|
71
|
+
'';
|
|
72
|
+
const debug = overrides?.debug ??
|
|
73
|
+
boolFlag(flags, 'debug') ??
|
|
74
|
+
(process.env['NOCVILLE_DEBUG'] === '1' || false);
|
|
75
|
+
const map = overrides?.map ||
|
|
76
|
+
stringFlag(flags, 'map') ||
|
|
77
|
+
process.env['NOCVILLE_MAP'] ||
|
|
78
|
+
saved.map ||
|
|
79
|
+
undefined;
|
|
80
|
+
const name = overrides?.name || stringFlag(flags, 'name') || process.env['NOCVILLE_NAME'] || undefined;
|
|
81
|
+
return { apiUrl, apiKey, serviceId, debug, map, name };
|
|
82
|
+
}
|
|
83
|
+
function stringFlag(flags, key) {
|
|
84
|
+
const val = flags[key];
|
|
85
|
+
if (typeof val === 'string')
|
|
86
|
+
return val;
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
function boolFlag(flags, key) {
|
|
90
|
+
const val = flags[key];
|
|
91
|
+
if (typeof val === 'boolean')
|
|
92
|
+
return val;
|
|
93
|
+
if (val === 'true')
|
|
94
|
+
return true;
|
|
95
|
+
if (val === 'false')
|
|
96
|
+
return false;
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
function numberFlag(flags, key) {
|
|
100
|
+
const val = flags[key];
|
|
101
|
+
if (typeof val === 'string') {
|
|
102
|
+
const n = Number(val);
|
|
103
|
+
if (!Number.isNaN(n))
|
|
104
|
+
return n;
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EventNPC = void 0;
|
|
4
|
+
const base_npc_js_1 = require("./base-npc.js");
|
|
5
|
+
class EventNPC extends base_npc_js_1.BaseNPC {
|
|
6
|
+
async onStart() {
|
|
7
|
+
await this.setupEventSource();
|
|
8
|
+
}
|
|
9
|
+
async onStop() {
|
|
10
|
+
await this.teardownEventSource();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.EventNPC = EventNPC;
|
|
14
|
+
//# sourceMappingURL=event-npc.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Logger = void 0;
|
|
4
|
+
const types_js_1 = require("./types.js");
|
|
5
|
+
class Logger {
|
|
6
|
+
level;
|
|
7
|
+
prefix;
|
|
8
|
+
constructor(prefix, debug = false) {
|
|
9
|
+
this.prefix = prefix;
|
|
10
|
+
this.level = debug ? types_js_1.LOG_LEVELS.debug.value : types_js_1.LOG_LEVELS.info.value;
|
|
11
|
+
}
|
|
12
|
+
debug(message, data) {
|
|
13
|
+
this.log('debug', message, data);
|
|
14
|
+
}
|
|
15
|
+
info(message, data) {
|
|
16
|
+
this.log('info', message, data);
|
|
17
|
+
}
|
|
18
|
+
warn(message, data) {
|
|
19
|
+
this.log('warn', message, data);
|
|
20
|
+
}
|
|
21
|
+
error(message, data) {
|
|
22
|
+
this.log('error', message, data);
|
|
23
|
+
}
|
|
24
|
+
log(level, message, data) {
|
|
25
|
+
const levelInfo = types_js_1.LOG_LEVELS[level];
|
|
26
|
+
if (levelInfo.value < this.level)
|
|
27
|
+
return;
|
|
28
|
+
const timestamp = new Date().toISOString();
|
|
29
|
+
const parts = [`[${timestamp}]`, `[${levelInfo.name}]`, `[${this.prefix}]`, message];
|
|
30
|
+
if (data && Object.keys(data).length > 0) {
|
|
31
|
+
parts.push(JSON.stringify(data));
|
|
32
|
+
}
|
|
33
|
+
const output = parts.join(' ');
|
|
34
|
+
// stdout belongs to hook protocols; diagnostics must never corrupt their JSON.
|
|
35
|
+
console.error(output);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.Logger = Logger;
|
|
39
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PollingNPC = void 0;
|
|
4
|
+
const base_npc_js_1 = require("./base-npc.js");
|
|
5
|
+
function clamp(n, min, max) {
|
|
6
|
+
return Math.min(max, Math.max(min, n));
|
|
7
|
+
}
|
|
8
|
+
class PollingNPC extends base_npc_js_1.BaseNPC {
|
|
9
|
+
timer = null;
|
|
10
|
+
collecting = false;
|
|
11
|
+
pollInterval;
|
|
12
|
+
constructor(config) {
|
|
13
|
+
super(config);
|
|
14
|
+
this.pollInterval = config.pollInterval ?? 15000;
|
|
15
|
+
}
|
|
16
|
+
/** Scale staleness to the poll cadence: the server marks unknown at ~2x the interval. */
|
|
17
|
+
get heartbeatSeconds() {
|
|
18
|
+
return clamp(Math.ceil((this.pollInterval * 2) / 1000), 30, 3600);
|
|
19
|
+
}
|
|
20
|
+
async onStart() {
|
|
21
|
+
// Collect immediately on start
|
|
22
|
+
await this.runCollection();
|
|
23
|
+
// Then on interval
|
|
24
|
+
this.timer = setInterval(() => {
|
|
25
|
+
void this.runCollection();
|
|
26
|
+
}, this.pollInterval);
|
|
27
|
+
// For slow pollers, keep the NPC alive with a lightweight heartbeat between
|
|
28
|
+
// collections so it never falsely goes stale mid-interval.
|
|
29
|
+
if (this.pollInterval > 60_000) {
|
|
30
|
+
this.client.startHeartbeat(30_000);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async onStop() {
|
|
34
|
+
if (this.timer) {
|
|
35
|
+
clearInterval(this.timer);
|
|
36
|
+
this.timer = null;
|
|
37
|
+
}
|
|
38
|
+
this.client.stopHeartbeat();
|
|
39
|
+
}
|
|
40
|
+
async runCollection() {
|
|
41
|
+
if (this.state !== 'running')
|
|
42
|
+
return;
|
|
43
|
+
// Overlap guard: if a slow collector is still running, skip this tick rather
|
|
44
|
+
// than stacking collections (load-bearing for screenshot/render collectors).
|
|
45
|
+
if (this.collecting) {
|
|
46
|
+
this.logger.debug('Previous collection still in flight, skipping this tick');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this.collecting = true;
|
|
50
|
+
try {
|
|
51
|
+
const metrics = await this.collect();
|
|
52
|
+
await this.pushMetrics(metrics);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
this.logger.error('Collection failed', {
|
|
56
|
+
error: err instanceof Error ? err.message : String(err),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
this.collecting = false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.PollingNPC = PollingNPC;
|
|
65
|
+
//# sourceMappingURL=polling-npc.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.statusColor = statusColor;
|
|
4
|
+
exports.pushHistory = pushHistory;
|
|
5
|
+
/** Map a status to the color the client renderer uses for it. */
|
|
6
|
+
function statusColor(status) {
|
|
7
|
+
switch (status) {
|
|
8
|
+
case 'healthy':
|
|
9
|
+
return 'green';
|
|
10
|
+
case 'warning':
|
|
11
|
+
return 'amber';
|
|
12
|
+
case 'critical':
|
|
13
|
+
return 'red';
|
|
14
|
+
default:
|
|
15
|
+
return 'gray';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Push a numeric sample into a bounded rolling history (oldest dropped past `max`).
|
|
20
|
+
* Mutates and returns the array. The API caps sparklines at 60 samples.
|
|
21
|
+
*/
|
|
22
|
+
function pushHistory(history, value, max = 60) {
|
|
23
|
+
history.push(value);
|
|
24
|
+
if (history.length > max)
|
|
25
|
+
history.splice(0, history.length - max);
|
|
26
|
+
return history;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=screen.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.truncate = truncate;
|
|
4
|
+
/**
|
|
5
|
+
* Truncate a string to at most `max` characters, appending an ellipsis when cut.
|
|
6
|
+
* Used to keep messages and screen lines within API limits.
|
|
7
|
+
*/
|
|
8
|
+
function truncate(s, max = 200) {
|
|
9
|
+
if (s.length <= max)
|
|
10
|
+
return s;
|
|
11
|
+
if (max <= 1)
|
|
12
|
+
return s.slice(0, Math.max(0, max));
|
|
13
|
+
return s.slice(0, max - 1) + '…';
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=text.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.evaluateThreshold = evaluateThreshold;
|
|
4
|
+
exports.worstStatus = worstStatus;
|
|
5
|
+
/**
|
|
6
|
+
* Evaluate a numeric value against thresholds to determine status.
|
|
7
|
+
*/
|
|
8
|
+
function evaluateThreshold(value, config) {
|
|
9
|
+
const direction = config.direction ?? 'above';
|
|
10
|
+
if (direction === 'above') {
|
|
11
|
+
if (value >= config.critical)
|
|
12
|
+
return 'critical';
|
|
13
|
+
if (value >= config.warning)
|
|
14
|
+
return 'warning';
|
|
15
|
+
return 'healthy';
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
if (value <= config.critical)
|
|
19
|
+
return 'critical';
|
|
20
|
+
if (value <= config.warning)
|
|
21
|
+
return 'warning';
|
|
22
|
+
return 'healthy';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const STATUS_SEVERITY = {
|
|
26
|
+
healthy: 0,
|
|
27
|
+
unknown: 1,
|
|
28
|
+
warning: 2,
|
|
29
|
+
critical: 3,
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Returns the worst (most severe) status from an array.
|
|
33
|
+
*/
|
|
34
|
+
function worstStatus(statuses) {
|
|
35
|
+
if (statuses.length === 0)
|
|
36
|
+
return 'unknown';
|
|
37
|
+
let worst = 'healthy';
|
|
38
|
+
let worstSeverity = 0;
|
|
39
|
+
for (const s of statuses) {
|
|
40
|
+
const severity = STATUS_SEVERITY[s];
|
|
41
|
+
if (severity > worstSeverity) {
|
|
42
|
+
worst = s;
|
|
43
|
+
worstSeverity = severity;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return worst;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=thresholds.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LOG_LEVELS = void 0;
|
|
4
|
+
exports.LOG_LEVELS = {
|
|
5
|
+
debug: { name: 'DEBUG', value: 0 },
|
|
6
|
+
info: { name: 'INFO', value: 1 },
|
|
7
|
+
warn: { name: 'WARN', value: 2 },
|
|
8
|
+
error: { name: 'ERROR', value: 3 },
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.userConfigPath = userConfigPath;
|
|
4
|
+
exports.loadUserConfig = loadUserConfig;
|
|
5
|
+
exports.saveUserConfig = saveUserConfig;
|
|
6
|
+
exports.maskKey = maskKey;
|
|
7
|
+
exports.persistCredentials = persistCredentials;
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
const node_os_1 = require("node:os");
|
|
10
|
+
const node_path_1 = require("node:path");
|
|
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
|
+
function userConfigPath() {
|
|
16
|
+
const override = process.env['NOCVILLE_CONFIG'];
|
|
17
|
+
if (override)
|
|
18
|
+
return override;
|
|
19
|
+
const xdg = process.env['XDG_CONFIG_HOME'];
|
|
20
|
+
const base = xdg && xdg.trim() ? xdg : (0, node_path_1.join)((0, node_os_1.homedir)(), '.config');
|
|
21
|
+
return (0, node_path_1.join)(base, 'nocville', 'config.json');
|
|
22
|
+
}
|
|
23
|
+
/** Read the user config; missing or unreadable files yield `{}` (never throws). */
|
|
24
|
+
function loadUserConfig(path = userConfigPath()) {
|
|
25
|
+
try {
|
|
26
|
+
const raw = JSON.parse((0, node_fs_1.readFileSync)(path, 'utf-8'));
|
|
27
|
+
const out = {};
|
|
28
|
+
if (typeof raw['apiUrl'] === 'string' && raw['apiUrl'])
|
|
29
|
+
out.apiUrl = raw['apiUrl'];
|
|
30
|
+
if (typeof raw['apiKey'] === 'string' && raw['apiKey'])
|
|
31
|
+
out.apiKey = raw['apiKey'];
|
|
32
|
+
if (typeof raw['map'] === 'string' && raw['map'])
|
|
33
|
+
out.map = raw['map'];
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Merge `patch` into the stored config and write it with owner-only perms
|
|
42
|
+
* (dir 0700, file 0600) via an atomic rename. Undefined fields are left as-is;
|
|
43
|
+
* an empty string clears a field. Returns the written path.
|
|
44
|
+
*/
|
|
45
|
+
function saveUserConfig(patch, path = userConfigPath()) {
|
|
46
|
+
const current = loadUserConfig(path);
|
|
47
|
+
const next = { ...current };
|
|
48
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
49
|
+
if (v === undefined)
|
|
50
|
+
continue;
|
|
51
|
+
if (v === '')
|
|
52
|
+
delete next[k];
|
|
53
|
+
else
|
|
54
|
+
next[k] = v;
|
|
55
|
+
}
|
|
56
|
+
const dir = (0, node_path_1.dirname)(path);
|
|
57
|
+
if (!(0, node_fs_1.existsSync)(dir))
|
|
58
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true, mode: 0o700 });
|
|
59
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
60
|
+
(0, node_fs_1.writeFileSync)(tmp, JSON.stringify(next, null, 2) + '\n', { encoding: 'utf-8', mode: 0o600 });
|
|
61
|
+
(0, node_fs_1.renameSync)(tmp, path);
|
|
62
|
+
try {
|
|
63
|
+
(0, node_fs_1.chmodSync)(path, 0o600);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// best effort (e.g. Windows)
|
|
67
|
+
}
|
|
68
|
+
return path;
|
|
69
|
+
}
|
|
70
|
+
/** Mask an API key for display: keep a short prefix/suffix only. */
|
|
71
|
+
function maskKey(key) {
|
|
72
|
+
if (!key)
|
|
73
|
+
return '(none)';
|
|
74
|
+
if (key.length <= 10)
|
|
75
|
+
return '****';
|
|
76
|
+
return `${key.slice(0, 4)}…${key.slice(-4)}`;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Save credentials (explicit flags win; else migrate any from a legacy hook
|
|
80
|
+
* command) and return the effective config. Shared by claude-code/codex setup.
|
|
81
|
+
*/
|
|
82
|
+
function persistCredentials(explicit, legacy = {}) {
|
|
83
|
+
const saved = loadUserConfig();
|
|
84
|
+
const patch = {
|
|
85
|
+
apiUrl: explicit.apiUrl ?? (saved.apiUrl ? undefined : legacy.apiUrl),
|
|
86
|
+
apiKey: explicit.apiKey ?? (saved.apiKey ? undefined : legacy.apiKey),
|
|
87
|
+
map: explicit.map,
|
|
88
|
+
};
|
|
89
|
+
const path = patch.apiUrl || patch.apiKey || patch.map ? saveUserConfig(patch) : userConfigPath();
|
|
90
|
+
return { ...loadUserConfig(), path };
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=user-config.js.map
|