@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,75 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
const MAC_PATHS = [
|
|
4
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
5
|
+
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
|
|
6
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
7
|
+
'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
|
8
|
+
'/Applications/Brave Browser.app/Contents/MacOS/Brave Browser',
|
|
9
|
+
];
|
|
10
|
+
const LINUX_BINARIES = [
|
|
11
|
+
'google-chrome',
|
|
12
|
+
'google-chrome-stable',
|
|
13
|
+
'chromium',
|
|
14
|
+
'chromium-browser',
|
|
15
|
+
'brave-browser',
|
|
16
|
+
];
|
|
17
|
+
function which(bin) {
|
|
18
|
+
try {
|
|
19
|
+
const out = execFileSync('which', [bin], { encoding: 'utf-8' }).trim();
|
|
20
|
+
return out || null;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Find a usable Chrome/Chromium executable: explicit path → CHROME_PATH env →
|
|
28
|
+
* platform-specific known locations (mac app bundles; `which` on linux).
|
|
29
|
+
*/
|
|
30
|
+
export function findChrome(explicit) {
|
|
31
|
+
const envPath = process.env['CHROME_PATH'];
|
|
32
|
+
if (explicit && existsSync(explicit))
|
|
33
|
+
return explicit;
|
|
34
|
+
if (envPath && existsSync(envPath))
|
|
35
|
+
return envPath;
|
|
36
|
+
if (process.platform === 'darwin') {
|
|
37
|
+
for (const p of MAC_PATHS)
|
|
38
|
+
if (existsSync(p))
|
|
39
|
+
return p;
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (process.platform === 'linux') {
|
|
43
|
+
for (const bin of LINUX_BINARIES) {
|
|
44
|
+
const resolved = which(bin);
|
|
45
|
+
if (resolved && existsSync(resolved))
|
|
46
|
+
return resolved;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Load the optional `puppeteer-core` peer dependency without a static import,
|
|
54
|
+
* so the package builds and runs when it isn't installed. Returns null if the
|
|
55
|
+
* module is absent.
|
|
56
|
+
*/
|
|
57
|
+
export async function loadPuppeteer() {
|
|
58
|
+
try {
|
|
59
|
+
const dynImport = new Function('m', 'return import(m)');
|
|
60
|
+
const mod = await dynImport('puppeteer-core');
|
|
61
|
+
return (mod.default ?? mod);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** True if the browser reports a live connection. */
|
|
68
|
+
export function browserConnected(browser) {
|
|
69
|
+
if (typeof browser.connected === 'boolean')
|
|
70
|
+
return browser.connected;
|
|
71
|
+
if (typeof browser.isConnected === 'function')
|
|
72
|
+
return browser.isConnected();
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=capture.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const DEFAULTS: {
|
|
2
|
+
pollInterval: number;
|
|
3
|
+
minPollInterval: number;
|
|
4
|
+
navTimeoutMs: number;
|
|
5
|
+
maxColors: number;
|
|
6
|
+
/** Polls to keep the 'changed' sub-status active after a page change. */
|
|
7
|
+
changedPolls: number;
|
|
8
|
+
screenW: number;
|
|
9
|
+
screenH: number;
|
|
10
|
+
scaleDivisor: number;
|
|
11
|
+
waitUntil: string;
|
|
12
|
+
};
|
|
13
|
+
export type WatchMode = 'auto' | 'screenshot' | 'basic';
|
|
14
|
+
/** Parse a "WxH" string into a {width, height} pair, or null if malformed. */
|
|
15
|
+
export declare function parseDimensions(s: string): {
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
} | null;
|
|
19
|
+
//# sourceMappingURL=defaults.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const DEFAULTS = {
|
|
2
|
+
pollInterval: 60000, // 60s
|
|
3
|
+
minPollInterval: 15000, // floor
|
|
4
|
+
navTimeoutMs: 15000,
|
|
5
|
+
maxColors: 32,
|
|
6
|
+
/** Polls to keep the 'changed' sub-status active after a page change. */
|
|
7
|
+
changedPolls: 2,
|
|
8
|
+
// Default TV resolution (4:3, matches the 4×3 web-view companion). The client
|
|
9
|
+
// reads WxH from the pixel-image `fmt` string, so any size is self-consistent.
|
|
10
|
+
screenW: 160,
|
|
11
|
+
screenH: 120,
|
|
12
|
+
// deviceScaleFactor is 1/8, so the viewport defaults to screen × 8 → Chrome
|
|
13
|
+
// rasterizes the screen size natively (overridable via --viewport).
|
|
14
|
+
scaleDivisor: 8,
|
|
15
|
+
waitUntil: 'networkidle2',
|
|
16
|
+
};
|
|
17
|
+
/** Parse a "WxH" string into a {width, height} pair, or null if malformed. */
|
|
18
|
+
export function parseDimensions(s) {
|
|
19
|
+
const m = s
|
|
20
|
+
.trim()
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.match(/^(\d+)x(\d+)$/);
|
|
23
|
+
if (!m)
|
|
24
|
+
return null;
|
|
25
|
+
return { width: parseInt(m[1], 10), height: parseInt(m[2], 10) };
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=defaults.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { PollingNPC } from '../../core/polling-npc.js';
|
|
2
|
+
import type { PollingNpcConfig } from '../../core/polling-npc.js';
|
|
3
|
+
import type { CollectedMetrics, Archetype, CompanionDecl } from '../../core/types.js';
|
|
4
|
+
import type { WatchMode } from './defaults.js';
|
|
5
|
+
export interface WebPageWatcherConfig extends PollingNpcConfig {
|
|
6
|
+
url: string;
|
|
7
|
+
mode?: WatchMode;
|
|
8
|
+
chromePath?: string;
|
|
9
|
+
viewportWidth?: number;
|
|
10
|
+
viewportHeight?: number;
|
|
11
|
+
screenWidth?: number;
|
|
12
|
+
screenHeight?: number;
|
|
13
|
+
waitUntil?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare class WebPageWatcherNPC extends PollingNPC {
|
|
16
|
+
private url;
|
|
17
|
+
private mode;
|
|
18
|
+
private chromePath?;
|
|
19
|
+
private viewportW;
|
|
20
|
+
private viewportH;
|
|
21
|
+
private screenW;
|
|
22
|
+
private screenH;
|
|
23
|
+
private waitUntil;
|
|
24
|
+
private puppeteer;
|
|
25
|
+
private browser;
|
|
26
|
+
private browserUnavailable;
|
|
27
|
+
private chromeHintShown;
|
|
28
|
+
private lastHash;
|
|
29
|
+
private changedPollsLeft;
|
|
30
|
+
constructor(config: WebPageWatcherConfig);
|
|
31
|
+
get name(): string;
|
|
32
|
+
protected get defaultDisplayName(): string;
|
|
33
|
+
get archetype(): Archetype;
|
|
34
|
+
get companion(): CompanionDecl;
|
|
35
|
+
protected collect(): Promise<CollectedMetrics>;
|
|
36
|
+
protected onStop(): Promise<void>;
|
|
37
|
+
private collectScreenshot;
|
|
38
|
+
private ensureBrowser;
|
|
39
|
+
private maybeShowChromeHint;
|
|
40
|
+
private collectBasic;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { PollingNPC } from '../../core/polling-npc.js';
|
|
2
|
+
import { DEFAULTS } from './defaults.js';
|
|
3
|
+
import { pngToPixelImage } from './pixelate.js';
|
|
4
|
+
import { findChrome, loadPuppeteer, browserConnected } from './capture.js';
|
|
5
|
+
import { basicCheck, statusFromHttp, hostnameOf } from './basic-mode.js';
|
|
6
|
+
export class WebPageWatcherNPC extends PollingNPC {
|
|
7
|
+
url;
|
|
8
|
+
mode;
|
|
9
|
+
chromePath;
|
|
10
|
+
viewportW;
|
|
11
|
+
viewportH;
|
|
12
|
+
screenW;
|
|
13
|
+
screenH;
|
|
14
|
+
waitUntil;
|
|
15
|
+
puppeteer = null;
|
|
16
|
+
browser = null;
|
|
17
|
+
browserUnavailable = false;
|
|
18
|
+
chromeHintShown = false;
|
|
19
|
+
lastHash = 0;
|
|
20
|
+
changedPollsLeft = 0;
|
|
21
|
+
constructor(config) {
|
|
22
|
+
super({
|
|
23
|
+
...config,
|
|
24
|
+
// Floor the poll interval — screenshots are expensive.
|
|
25
|
+
pollInterval: Math.max(config.pollInterval ?? DEFAULTS.pollInterval, DEFAULTS.minPollInterval),
|
|
26
|
+
});
|
|
27
|
+
this.url = config.url;
|
|
28
|
+
this.mode = config.mode ?? 'auto';
|
|
29
|
+
this.chromePath = config.chromePath;
|
|
30
|
+
this.screenW = config.screenWidth ?? DEFAULTS.screenW;
|
|
31
|
+
this.screenH = config.screenHeight ?? DEFAULTS.screenH;
|
|
32
|
+
// Derive the viewport from the screen size for native downscaling unless the
|
|
33
|
+
// user overrides it (in which case pngToPixelImage box-filters to screen).
|
|
34
|
+
this.viewportW = config.viewportWidth ?? this.screenW * DEFAULTS.scaleDivisor;
|
|
35
|
+
this.viewportH = config.viewportHeight ?? this.screenH * DEFAULTS.scaleDivisor;
|
|
36
|
+
this.waitUntil = config.waitUntil ?? DEFAULTS.waitUntil;
|
|
37
|
+
}
|
|
38
|
+
get name() {
|
|
39
|
+
return 'web-page-watcher';
|
|
40
|
+
}
|
|
41
|
+
get defaultDisplayName() {
|
|
42
|
+
return hostnameOf(this.url);
|
|
43
|
+
}
|
|
44
|
+
get archetype() {
|
|
45
|
+
return 'custom';
|
|
46
|
+
}
|
|
47
|
+
get companion() {
|
|
48
|
+
return {
|
|
49
|
+
screenType: 'web-view',
|
|
50
|
+
size: { w: 4, h: 3 },
|
|
51
|
+
placement: 'adjacent-wall',
|
|
52
|
+
label: hostnameOf(this.url),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
async collect() {
|
|
56
|
+
if (this.mode === 'basic')
|
|
57
|
+
return this.collectBasic();
|
|
58
|
+
const browser = await this.ensureBrowser();
|
|
59
|
+
if (!browser) {
|
|
60
|
+
// 'auto' silently degrades; explicit 'screenshot' warns once.
|
|
61
|
+
this.maybeShowChromeHint();
|
|
62
|
+
return this.collectBasic();
|
|
63
|
+
}
|
|
64
|
+
return this.collectScreenshot(browser);
|
|
65
|
+
}
|
|
66
|
+
async onStop() {
|
|
67
|
+
await super.onStop();
|
|
68
|
+
if (this.browser) {
|
|
69
|
+
try {
|
|
70
|
+
await this.browser.close();
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// best effort
|
|
74
|
+
}
|
|
75
|
+
this.browser = null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// --- Screenshot mode ---
|
|
79
|
+
async collectScreenshot(browser) {
|
|
80
|
+
const page = await browser.newPage();
|
|
81
|
+
try {
|
|
82
|
+
// deviceScaleFactor 0.125 makes Chrome rasterize the viewport down to the
|
|
83
|
+
// screen size natively (e.g. 1280×800 → 160×100); pngToPixelImage
|
|
84
|
+
// box-filters as a fallback if it doesn't.
|
|
85
|
+
await page.setViewport({
|
|
86
|
+
width: this.viewportW,
|
|
87
|
+
height: this.viewportH,
|
|
88
|
+
deviceScaleFactor: 0.125,
|
|
89
|
+
});
|
|
90
|
+
let httpStatus = 0;
|
|
91
|
+
let timedOut = false;
|
|
92
|
+
const loadStart = Date.now();
|
|
93
|
+
try {
|
|
94
|
+
const resp = await page.goto(this.url, {
|
|
95
|
+
waitUntil: this.waitUntil,
|
|
96
|
+
timeout: DEFAULTS.navTimeoutMs,
|
|
97
|
+
});
|
|
98
|
+
httpStatus = resp ? resp.status() : 0;
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
timedOut = err instanceof Error && /timeout/i.test(err.message);
|
|
102
|
+
}
|
|
103
|
+
const loadMs = Date.now() - loadStart;
|
|
104
|
+
const captureStart = Date.now();
|
|
105
|
+
const shotRaw = await page.screenshot({ type: 'png' });
|
|
106
|
+
const shot = Buffer.isBuffer(shotRaw) ? shotRaw : Buffer.from(shotRaw);
|
|
107
|
+
const { screenData, byteSize, hash } = pngToPixelImage(shot, DEFAULTS.maxColors, this.screenW, this.screenH);
|
|
108
|
+
const captureMs = Date.now() - captureStart;
|
|
109
|
+
const changed = this.lastHash !== 0 && hash !== this.lastHash;
|
|
110
|
+
this.lastHash = hash;
|
|
111
|
+
if (changed)
|
|
112
|
+
this.changedPollsLeft = DEFAULTS.changedPolls;
|
|
113
|
+
else if (this.changedPollsLeft > 0)
|
|
114
|
+
this.changedPollsLeft--;
|
|
115
|
+
const status = statusFromHttp(httpStatus, timedOut);
|
|
116
|
+
const host = hostnameOf(this.url);
|
|
117
|
+
const message = changed
|
|
118
|
+
? 'Page updated!'
|
|
119
|
+
: httpStatus
|
|
120
|
+
? `${host} ${httpStatus < 400 ? 'OK' : 'ERR'} (${httpStatus}, ${loadMs}ms)`
|
|
121
|
+
: `${host} unreachable`;
|
|
122
|
+
return {
|
|
123
|
+
status,
|
|
124
|
+
mood: moodFor(status),
|
|
125
|
+
message,
|
|
126
|
+
subStatus: this.changedPollsLeft > 0 ? 'changed' : undefined,
|
|
127
|
+
screenData,
|
|
128
|
+
metrics: {
|
|
129
|
+
status_code: httpStatus,
|
|
130
|
+
load_ms: loadMs,
|
|
131
|
+
capture_ms: captureMs,
|
|
132
|
+
frame_bytes: byteSize,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
await page.close().catch(() => undefined);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async ensureBrowser() {
|
|
141
|
+
if (this.browserUnavailable)
|
|
142
|
+
return null;
|
|
143
|
+
if (this.browser && browserConnected(this.browser))
|
|
144
|
+
return this.browser;
|
|
145
|
+
if (this.browser)
|
|
146
|
+
this.browser = null; // disconnected — relaunch
|
|
147
|
+
if (!this.puppeteer) {
|
|
148
|
+
this.puppeteer = await loadPuppeteer();
|
|
149
|
+
}
|
|
150
|
+
const chromePath = findChrome(this.chromePath);
|
|
151
|
+
if (!this.puppeteer || !chromePath) {
|
|
152
|
+
this.browserUnavailable = true;
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
this.browser = await this.puppeteer.launch({
|
|
157
|
+
executablePath: chromePath,
|
|
158
|
+
headless: true,
|
|
159
|
+
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu', '--hide-scrollbars'],
|
|
160
|
+
});
|
|
161
|
+
this.browser.on('disconnected', () => {
|
|
162
|
+
this.browser = null;
|
|
163
|
+
});
|
|
164
|
+
return this.browser;
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
this.logger.warn('Failed to launch Chrome', {
|
|
168
|
+
error: err instanceof Error ? err.message : String(err),
|
|
169
|
+
});
|
|
170
|
+
this.browserUnavailable = true;
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
maybeShowChromeHint() {
|
|
175
|
+
if (this.chromeHintShown)
|
|
176
|
+
return;
|
|
177
|
+
this.chromeHintShown = true;
|
|
178
|
+
this.logger.warn('Chrome/puppeteer-core unavailable — falling back to basic mode. ' +
|
|
179
|
+
'Install `puppeteer-core` and Google Chrome (or set --chrome-path/CHROME_PATH) for the pixel-art TV.');
|
|
180
|
+
}
|
|
181
|
+
// --- Basic mode ---
|
|
182
|
+
async collectBasic() {
|
|
183
|
+
const loadStart = Date.now();
|
|
184
|
+
const check = await basicCheck(this.url);
|
|
185
|
+
const loadMs = Date.now() - loadStart;
|
|
186
|
+
// Basic mode can't render the pixel TV, so it never reports fully healthy —
|
|
187
|
+
// a healthy HTTP result is downgraded to a warning to nudge Chrome setup.
|
|
188
|
+
let status = statusFromHttp(check.status, check.timedOut);
|
|
189
|
+
if (status === 'healthy')
|
|
190
|
+
status = 'warning';
|
|
191
|
+
const host = hostnameOf(this.url);
|
|
192
|
+
const message = check.title ? `${host}: ${check.title}` : `${host} (${check.status || 'ERR'})`;
|
|
193
|
+
return {
|
|
194
|
+
status,
|
|
195
|
+
mood: moodFor(status),
|
|
196
|
+
message,
|
|
197
|
+
screenData: {
|
|
198
|
+
layout: 'ticker',
|
|
199
|
+
values: [
|
|
200
|
+
{ label: 'URL', value: host },
|
|
201
|
+
{
|
|
202
|
+
label: 'HTTP',
|
|
203
|
+
value: check.status ? String(check.status) : 'ERR',
|
|
204
|
+
color: check.status >= 200 && check.status < 400
|
|
205
|
+
? 'green'
|
|
206
|
+
: check.status >= 500 || check.status === 0
|
|
207
|
+
? 'red'
|
|
208
|
+
: 'amber',
|
|
209
|
+
},
|
|
210
|
+
{ label: 'Title', value: check.title || '(no title)' },
|
|
211
|
+
{ label: 'Mode', value: 'basic' },
|
|
212
|
+
],
|
|
213
|
+
},
|
|
214
|
+
metrics: { status_code: check.status, load_ms: loadMs, capture_ms: 0 },
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function moodFor(status) {
|
|
219
|
+
switch (status) {
|
|
220
|
+
case 'healthy':
|
|
221
|
+
return 'happy';
|
|
222
|
+
case 'warning':
|
|
223
|
+
return 'concerned';
|
|
224
|
+
case 'critical':
|
|
225
|
+
return 'stressed';
|
|
226
|
+
default:
|
|
227
|
+
return 'idle';
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ScreenData } from '../../core/types.js';
|
|
2
|
+
/** Default target resolution for the web-view TV (4:3 to match a 4×3 companion). */
|
|
3
|
+
export declare const TARGET_W = 160;
|
|
4
|
+
export declare const TARGET_H = 120;
|
|
5
|
+
/** Decode a PNG buffer to RGBA bytes + dimensions. */
|
|
6
|
+
export declare function decodePng(buf: Buffer): {
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
data: Buffer;
|
|
10
|
+
};
|
|
11
|
+
/** Area-average (box filter) downscale of RGBA `src` to `tw`×`th`. */
|
|
12
|
+
export declare function boxDownscale(src: Buffer, sw: number, sh: number, tw: number, th: number): Buffer;
|
|
13
|
+
/**
|
|
14
|
+
* Median-cut quantization of RGBA pixels into at most `maxColors` colors.
|
|
15
|
+
* Returns a flat RGB palette and a per-pixel palette-index array. Each pixel's
|
|
16
|
+
* index is the box it lands in, so no separate nearest-color pass is needed.
|
|
17
|
+
*/
|
|
18
|
+
export declare function quantize(rgba: Buffer, pixelCount: number, maxColors: number): {
|
|
19
|
+
palette: Uint8Array;
|
|
20
|
+
indices: Uint8Array;
|
|
21
|
+
};
|
|
22
|
+
/** Build a pixel-image screenData payload from a palette + index array. */
|
|
23
|
+
export declare function buildPixelImageScreen(width: number, height: number, palette: Uint8Array, indices: Uint8Array): ScreenData;
|
|
24
|
+
/** FNV-1a hash over bytes — cheap change detection for the rendered frame. */
|
|
25
|
+
export declare function fnv1a(bytes: Uint8Array): number;
|
|
26
|
+
/**
|
|
27
|
+
* Decode a PNG screenshot into a pixel-image screenData payload, downscaling to
|
|
28
|
+
* the target resolution if the source isn't already there (box-filter fallback
|
|
29
|
+
* for when Chrome's fractional deviceScaleFactor doesn't rasterize natively).
|
|
30
|
+
*/
|
|
31
|
+
export declare function pngToPixelImage(pngBuffer: Buffer, maxColors?: number, targetW?: number, targetH?: number): {
|
|
32
|
+
screenData: ScreenData;
|
|
33
|
+
byteSize: number;
|
|
34
|
+
hash: number;
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=pixelate.d.ts.map
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { PNG } from 'pngjs';
|
|
2
|
+
/** Default target resolution for the web-view TV (4:3 to match a 4×3 companion). */
|
|
3
|
+
export const TARGET_W = 160;
|
|
4
|
+
export const TARGET_H = 120;
|
|
5
|
+
/** Decode a PNG buffer to RGBA bytes + dimensions. */
|
|
6
|
+
export function decodePng(buf) {
|
|
7
|
+
const png = PNG.sync.read(buf);
|
|
8
|
+
return { width: png.width, height: png.height, data: png.data };
|
|
9
|
+
}
|
|
10
|
+
/** Area-average (box filter) downscale of RGBA `src` to `tw`×`th`. */
|
|
11
|
+
export function boxDownscale(src, sw, sh, tw, th) {
|
|
12
|
+
const out = Buffer.alloc(tw * th * 4);
|
|
13
|
+
for (let ty = 0; ty < th; ty++) {
|
|
14
|
+
const sy0 = Math.floor((ty * sh) / th);
|
|
15
|
+
const sy1 = Math.max(sy0 + 1, Math.floor(((ty + 1) * sh) / th));
|
|
16
|
+
for (let tx = 0; tx < tw; tx++) {
|
|
17
|
+
const sx0 = Math.floor((tx * sw) / tw);
|
|
18
|
+
const sx1 = Math.max(sx0 + 1, Math.floor(((tx + 1) * sw) / tw));
|
|
19
|
+
let r = 0;
|
|
20
|
+
let g = 0;
|
|
21
|
+
let b = 0;
|
|
22
|
+
let n = 0;
|
|
23
|
+
for (let sy = sy0; sy < sy1; sy++) {
|
|
24
|
+
for (let sx = sx0; sx < sx1; sx++) {
|
|
25
|
+
const i = (sy * sw + sx) * 4;
|
|
26
|
+
r += src[i];
|
|
27
|
+
g += src[i + 1];
|
|
28
|
+
b += src[i + 2];
|
|
29
|
+
n++;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const o = (ty * tw + tx) * 4;
|
|
33
|
+
out[o] = Math.round(r / n);
|
|
34
|
+
out[o + 1] = Math.round(g / n);
|
|
35
|
+
out[o + 2] = Math.round(b / n);
|
|
36
|
+
out[o + 3] = 255;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Median-cut quantization of RGBA pixels into at most `maxColors` colors.
|
|
43
|
+
* Returns a flat RGB palette and a per-pixel palette-index array. Each pixel's
|
|
44
|
+
* index is the box it lands in, so no separate nearest-color pass is needed.
|
|
45
|
+
*/
|
|
46
|
+
export function quantize(rgba, pixelCount, maxColors) {
|
|
47
|
+
const pixels = new Array(pixelCount);
|
|
48
|
+
for (let i = 0; i < pixelCount; i++) {
|
|
49
|
+
const o = i * 4;
|
|
50
|
+
pixels[i] = { r: rgba[o], g: rgba[o + 1], b: rgba[o + 2] };
|
|
51
|
+
}
|
|
52
|
+
const refs = pixels.map((_, i) => i);
|
|
53
|
+
const boxes = [{ start: 0, end: refs.length }];
|
|
54
|
+
const widestChannel = (box) => {
|
|
55
|
+
let rmin = 255;
|
|
56
|
+
let rmax = 0;
|
|
57
|
+
let gmin = 255;
|
|
58
|
+
let gmax = 0;
|
|
59
|
+
let bmin = 255;
|
|
60
|
+
let bmax = 0;
|
|
61
|
+
for (let i = box.start; i < box.end; i++) {
|
|
62
|
+
const p = pixels[refs[i]];
|
|
63
|
+
if (p.r < rmin)
|
|
64
|
+
rmin = p.r;
|
|
65
|
+
if (p.r > rmax)
|
|
66
|
+
rmax = p.r;
|
|
67
|
+
if (p.g < gmin)
|
|
68
|
+
gmin = p.g;
|
|
69
|
+
if (p.g > gmax)
|
|
70
|
+
gmax = p.g;
|
|
71
|
+
if (p.b < bmin)
|
|
72
|
+
bmin = p.b;
|
|
73
|
+
if (p.b > bmax)
|
|
74
|
+
bmax = p.b;
|
|
75
|
+
}
|
|
76
|
+
const rr = rmax - rmin;
|
|
77
|
+
const gr = gmax - gmin;
|
|
78
|
+
const br = bmax - bmin;
|
|
79
|
+
if (rr >= gr && rr >= br)
|
|
80
|
+
return { channel: 'r', range: rr };
|
|
81
|
+
if (gr >= br)
|
|
82
|
+
return { channel: 'g', range: gr };
|
|
83
|
+
return { channel: 'b', range: br };
|
|
84
|
+
};
|
|
85
|
+
while (boxes.length < maxColors) {
|
|
86
|
+
let target = -1;
|
|
87
|
+
let best = 0;
|
|
88
|
+
for (let i = 0; i < boxes.length; i++) {
|
|
89
|
+
const box = boxes[i];
|
|
90
|
+
if (box.end - box.start < 2)
|
|
91
|
+
continue;
|
|
92
|
+
const { range } = widestChannel(box);
|
|
93
|
+
if (range > best) {
|
|
94
|
+
best = range;
|
|
95
|
+
target = i;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (target < 0 || best <= 0)
|
|
99
|
+
break;
|
|
100
|
+
const box = boxes[target];
|
|
101
|
+
const { channel } = widestChannel(box);
|
|
102
|
+
const slice = refs
|
|
103
|
+
.slice(box.start, box.end)
|
|
104
|
+
.sort((a, b) => pixels[a][channel] - pixels[b][channel]);
|
|
105
|
+
for (let i = 0; i < slice.length; i++)
|
|
106
|
+
refs[box.start + i] = slice[i];
|
|
107
|
+
const mid = box.start + ((box.end - box.start) >> 1);
|
|
108
|
+
boxes.splice(target, 1, { start: box.start, end: mid }, { start: mid, end: box.end });
|
|
109
|
+
}
|
|
110
|
+
const palette = new Uint8Array(boxes.length * 3);
|
|
111
|
+
const indices = new Uint8Array(pixelCount);
|
|
112
|
+
boxes.forEach((box, bi) => {
|
|
113
|
+
let r = 0;
|
|
114
|
+
let g = 0;
|
|
115
|
+
let b = 0;
|
|
116
|
+
const n = box.end - box.start;
|
|
117
|
+
for (let i = box.start; i < box.end; i++) {
|
|
118
|
+
const ref = refs[i];
|
|
119
|
+
const p = pixels[ref];
|
|
120
|
+
r += p.r;
|
|
121
|
+
g += p.g;
|
|
122
|
+
b += p.b;
|
|
123
|
+
indices[ref] = bi;
|
|
124
|
+
}
|
|
125
|
+
palette[bi * 3] = Math.round(r / Math.max(1, n));
|
|
126
|
+
palette[bi * 3 + 1] = Math.round(g / Math.max(1, n));
|
|
127
|
+
palette[bi * 3 + 2] = Math.round(b / Math.max(1, n));
|
|
128
|
+
});
|
|
129
|
+
return { palette, indices };
|
|
130
|
+
}
|
|
131
|
+
/** Build a pixel-image screenData payload from a palette + index array. */
|
|
132
|
+
export function buildPixelImageScreen(width, height, palette, indices) {
|
|
133
|
+
const paletteCount = palette.length / 3;
|
|
134
|
+
return {
|
|
135
|
+
layout: 'pixel-image',
|
|
136
|
+
values: [
|
|
137
|
+
{ label: 'fmt', value: `idx8;${width}x${height};p${paletteCount}` },
|
|
138
|
+
{ label: 'palette', value: Buffer.from(palette).toString('base64') },
|
|
139
|
+
{ label: 'pixels', value: Buffer.from(indices).toString('base64') },
|
|
140
|
+
],
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/** FNV-1a hash over bytes — cheap change detection for the rendered frame. */
|
|
144
|
+
export function fnv1a(bytes) {
|
|
145
|
+
let h = 0x811c9dc5;
|
|
146
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
147
|
+
h ^= bytes[i];
|
|
148
|
+
h = Math.imul(h, 0x01000193);
|
|
149
|
+
}
|
|
150
|
+
return h >>> 0;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Decode a PNG screenshot into a pixel-image screenData payload, downscaling to
|
|
154
|
+
* the target resolution if the source isn't already there (box-filter fallback
|
|
155
|
+
* for when Chrome's fractional deviceScaleFactor doesn't rasterize natively).
|
|
156
|
+
*/
|
|
157
|
+
export function pngToPixelImage(pngBuffer, maxColors = 32, targetW = TARGET_W, targetH = TARGET_H) {
|
|
158
|
+
const { width, height, data } = decodePng(pngBuffer);
|
|
159
|
+
let rgba = data;
|
|
160
|
+
let w = width;
|
|
161
|
+
let h = height;
|
|
162
|
+
if (width !== targetW || height !== targetH) {
|
|
163
|
+
rgba = boxDownscale(data, width, height, targetW, targetH);
|
|
164
|
+
w = targetW;
|
|
165
|
+
h = targetH;
|
|
166
|
+
}
|
|
167
|
+
const { palette, indices } = quantize(rgba, w * h, maxColors);
|
|
168
|
+
const screenData = buildPixelImageScreen(w, h, palette, indices);
|
|
169
|
+
const byteSize = screenData.values.reduce((sum, v) => sum + v.value.length, 0);
|
|
170
|
+
return { screenData, byteSize, hash: fnv1a(indices) };
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=pixelate.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,76 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocville/npc",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Put your Claude Code and Codex sessions (and your servers) in the Nocville office as live NPCs",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"nocville-npc": "dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"require": "./dist/cjs/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"!dist/**/*.map",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"pngjs": "^7.0.0",
|
|
27
|
+
"@nocville/node": "^0.2.0"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"puppeteer-core": "*"
|
|
31
|
+
},
|
|
32
|
+
"peerDependenciesMeta": {
|
|
33
|
+
"puppeteer-core": {
|
|
34
|
+
"optional": true
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^20.10.0",
|
|
39
|
+
"@types/pngjs": "^6.0.5",
|
|
40
|
+
"typescript": "^5.3.2",
|
|
41
|
+
"vitest": "^3.2.6",
|
|
42
|
+
"puppeteer-core": "^24.0.0"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=20.12.0"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"keywords": [
|
|
51
|
+
"nocville",
|
|
52
|
+
"claude-code",
|
|
53
|
+
"codex",
|
|
54
|
+
"ai-agents",
|
|
55
|
+
"monitoring",
|
|
56
|
+
"npc",
|
|
57
|
+
"status",
|
|
58
|
+
"cli"
|
|
59
|
+
],
|
|
60
|
+
"license": "MIT",
|
|
61
|
+
"homepage": "https://nocville.com",
|
|
62
|
+
"repository": {
|
|
63
|
+
"type": "git",
|
|
64
|
+
"url": "git+https://github.com/marshmansf/nocville.git",
|
|
65
|
+
"directory": "libs/nocville-npc-library"
|
|
66
|
+
},
|
|
67
|
+
"scripts": {
|
|
68
|
+
"build": "tsc && tsc -p tsconfig.cjs.json && node -e \"require('fs').writeFileSync('dist/cjs/package.json', JSON.stringify({ type: 'commonjs' }))\" && node -e \"require('fs').chmodSync('dist/cli.js', 0o755)\"",
|
|
69
|
+
"clean": "rm -rf dist",
|
|
70
|
+
"typecheck": "tsc --noEmit",
|
|
71
|
+
"test": "vitest run",
|
|
72
|
+
"test:watch": "vitest",
|
|
73
|
+
"smoke": "node scripts/smoke.mjs",
|
|
74
|
+
"simulate": "node scripts/simulate.mjs"
|
|
75
|
+
}
|
|
6
76
|
}
|