aisnitch 0.2.25 → 0.2.26
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 +14 -8
- package/dist/cli/index.cjs +208 -163
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +208 -163
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +8 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -82,10 +82,16 @@ brew install aisnitch
|
|
|
82
82
|
### 2. Run
|
|
83
83
|
|
|
84
84
|
```bash
|
|
85
|
-
aisnitch
|
|
85
|
+
aisnitch
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
That's it!
|
|
88
|
+
That's it! `aisnitch` (no argument) starts the daemon, launches the dashboard server in the background, and opens the web dashboard in your browser.
|
|
89
|
+
|
|
90
|
+
Want the in-terminal TUI instead?
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
aisnitch start
|
|
94
|
+
```
|
|
89
95
|
|
|
90
96
|
**No AI tools yet?** Try the demo mode:
|
|
91
97
|
|
|
@@ -222,8 +228,8 @@ brew upgrade aisnitch
|
|
|
222
228
|
Open a beautiful real-time dashboard in your browser:
|
|
223
229
|
|
|
224
230
|
```bash
|
|
225
|
-
aisnitch
|
|
226
|
-
aisnitch fs
|
|
231
|
+
aisnitch # Default: start daemon + dashboard + open browser
|
|
232
|
+
aisnitch fs # Same as `aisnitch`
|
|
227
233
|
aisnitch fs --dashboard-port 8080 # Custom port
|
|
228
234
|
aisnitch fs --no-browser # Just start the server
|
|
229
235
|
```
|
|
@@ -336,12 +342,12 @@ aisnitch start --type agent.coding # Filter by event type
|
|
|
336
342
|
aisnitch start --view full-data # Show full JSON
|
|
337
343
|
```
|
|
338
344
|
|
|
339
|
-
### Web Dashboard
|
|
345
|
+
### Web Dashboard (Default)
|
|
340
346
|
|
|
341
347
|
```bash
|
|
342
|
-
aisnitch
|
|
343
|
-
aisnitch fs
|
|
344
|
-
aisnitch fs --dashboard-port 8080
|
|
348
|
+
aisnitch # Default: daemon + dashboard + browser
|
|
349
|
+
aisnitch fs # Explicit form (same effect)
|
|
350
|
+
aisnitch fs --dashboard-port 8080 # Custom port
|
|
345
351
|
aisnitch fs --no-browser # Server only
|
|
346
352
|
```
|
|
347
353
|
|
package/dist/cli/index.cjs
CHANGED
|
@@ -771,7 +771,7 @@ var import_commander = require("commander");
|
|
|
771
771
|
|
|
772
772
|
// src/package-info.ts
|
|
773
773
|
var AISNITCH_PACKAGE_NAME = "aisnitch";
|
|
774
|
-
var AISNITCH_VERSION = "0.2.
|
|
774
|
+
var AISNITCH_VERSION = "0.2.26";
|
|
775
775
|
var AISNITCH_DESCRIPTION = "Universal bridge for AI coding tool activity \u2014 capture, normalize, stream.";
|
|
776
776
|
|
|
777
777
|
// src/core/events/schema.ts
|
|
@@ -1360,6 +1360,7 @@ var AutoUpdateConfigSchema = import_zod2.z.strictObject({
|
|
|
1360
1360
|
var ConfigSchema = import_zod2.z.strictObject({
|
|
1361
1361
|
wsPort: import_zod2.z.number().int().min(1024).max(65535).default(4820),
|
|
1362
1362
|
httpPort: import_zod2.z.number().int().min(1024).max(65535).default(4821),
|
|
1363
|
+
dashboardPort: import_zod2.z.number().int().min(1024).max(65535).default(5174),
|
|
1363
1364
|
/**
|
|
1364
1365
|
* 📖 This is intentionally a partial record because most users will only
|
|
1365
1366
|
* override a couple of adapters instead of all supported tools at once.
|
|
@@ -1378,6 +1379,7 @@ var ConfigSchema = import_zod2.z.strictObject({
|
|
|
1378
1379
|
var DEFAULT_CONFIG = {
|
|
1379
1380
|
wsPort: 4820,
|
|
1380
1381
|
httpPort: 4821,
|
|
1382
|
+
dashboardPort: 5174,
|
|
1381
1383
|
adapters: {},
|
|
1382
1384
|
autoUpdate: {
|
|
1383
1385
|
enabled: true,
|
|
@@ -14168,6 +14170,10 @@ function Header({
|
|
|
14168
14170
|
] }) }),
|
|
14169
14171
|
daemon ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
14170
14172
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Text, { color: connected ? TUI_THEME.success : TUI_THEME.warning, children: connected ? `\u25CF ${connectionLabel}` : `\u25CB ${connectionLabel}` }),
|
|
14173
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Text, { color: TUI_THEME.success, children: [
|
|
14174
|
+
"Web ",
|
|
14175
|
+
daemon.dashboardUrl
|
|
14176
|
+
] }),
|
|
14171
14177
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Text, { color: TUI_THEME.muted, children: [
|
|
14172
14178
|
"WS ",
|
|
14173
14179
|
daemon.wsUrl
|
|
@@ -14360,7 +14366,7 @@ function StatusBar({
|
|
|
14360
14366
|
}) {
|
|
14361
14367
|
const streamState = streamFrozen ? `Frozen +${pendingEventCount}` : latestEvent?.type ?? "Live";
|
|
14362
14368
|
const focusLabel = focusPanel === "events" ? "events" : viewMode === "full-data" ? "inspector" : "sessions";
|
|
14363
|
-
const daemonLabel = daemon === void 0 ? null : daemon.busyAction ? `Daemon ${daemon.busyAction}` : daemon.active ? `Daemon active \xB7 ${daemon.
|
|
14369
|
+
const daemonLabel = daemon === void 0 ? null : daemon.busyAction ? `Daemon ${daemon.busyAction}` : daemon.active ? `Daemon active \xB7 Web ${daemon.dashboardUrl}` : `Daemon not active \xB7 Web ${daemon.dashboardUrl}`;
|
|
14364
14370
|
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
14365
14371
|
import_ink10.Box,
|
|
14366
14372
|
{
|
|
@@ -15945,6 +15951,7 @@ var DAEMON_READY_TIMEOUT_MS = 4e3;
|
|
|
15945
15951
|
var DAEMON_READY_POLL_INTERVAL_MS = 100;
|
|
15946
15952
|
var DAEMON_STOP_TIMEOUT_MS = 4e3;
|
|
15947
15953
|
var DAEMON_LOG_MAX_BYTES = 5 * 1024 * 1024;
|
|
15954
|
+
var DEFAULT_DASHBOARD_PORT = 5174;
|
|
15948
15955
|
var LAUNCH_AGENT_LABEL = "com.aisnitch.daemon";
|
|
15949
15956
|
async function resolveNodeExecutable() {
|
|
15950
15957
|
try {
|
|
@@ -16019,6 +16026,7 @@ function createCliRuntime(dependencies = {}) {
|
|
|
16019
16026
|
const config = ConfigSchema.parse({
|
|
16020
16027
|
...baseConfig,
|
|
16021
16028
|
...isStartCliOptions(options) ? {
|
|
16029
|
+
dashboardPort: options.dashboardPort ?? baseConfig.dashboardPort,
|
|
16022
16030
|
httpPort: options.httpPort ?? baseConfig.httpPort,
|
|
16023
16031
|
logLevel: options.logLevel ?? baseConfig.logLevel,
|
|
16024
16032
|
wsPort: options.wsPort ?? baseConfig.wsPort
|
|
@@ -16042,6 +16050,149 @@ function createCliRuntime(dependencies = {}) {
|
|
|
16042
16050
|
return null;
|
|
16043
16051
|
}
|
|
16044
16052
|
}
|
|
16053
|
+
async function fetchOk(url) {
|
|
16054
|
+
try {
|
|
16055
|
+
const response = await fetchImplementation(url);
|
|
16056
|
+
return response.ok;
|
|
16057
|
+
} catch {
|
|
16058
|
+
return false;
|
|
16059
|
+
}
|
|
16060
|
+
}
|
|
16061
|
+
async function waitForDashboardReady(port) {
|
|
16062
|
+
const dashboardUrl = `http://127.0.0.1:${port}`;
|
|
16063
|
+
for (let i = 0; i < 100; i++) {
|
|
16064
|
+
if (await fetchOk(dashboardUrl)) {
|
|
16065
|
+
return true;
|
|
16066
|
+
}
|
|
16067
|
+
await sleep(100);
|
|
16068
|
+
}
|
|
16069
|
+
return false;
|
|
16070
|
+
}
|
|
16071
|
+
async function startDashboardServerProcess(port) {
|
|
16072
|
+
const dashboardUrl = `http://127.0.0.1:${port}`;
|
|
16073
|
+
if (await fetchOk(dashboardUrl)) {
|
|
16074
|
+
return {
|
|
16075
|
+
kill: () => void 0,
|
|
16076
|
+
pid: void 0
|
|
16077
|
+
};
|
|
16078
|
+
}
|
|
16079
|
+
const distPath = await resolveDashboardDistPath();
|
|
16080
|
+
const nodeExecutable = await resolveNodeExecutable();
|
|
16081
|
+
const serverProcess = spawnImplementation(nodeExecutable, [
|
|
16082
|
+
"-e",
|
|
16083
|
+
buildDashboardServerScript(distPath, port)
|
|
16084
|
+
], {
|
|
16085
|
+
cwd: distPath,
|
|
16086
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
16087
|
+
});
|
|
16088
|
+
let serverOutput = "";
|
|
16089
|
+
let serverSpawnError;
|
|
16090
|
+
serverProcess.on("error", (error) => {
|
|
16091
|
+
serverSpawnError = error;
|
|
16092
|
+
serverOutput += `Dashboard server process failed: ${formatSpawnError(error)}
|
|
16093
|
+
`;
|
|
16094
|
+
});
|
|
16095
|
+
serverProcess.stdout?.on("data", (data) => {
|
|
16096
|
+
serverOutput += data.toString();
|
|
16097
|
+
});
|
|
16098
|
+
serverProcess.stderr?.on("data", (data) => {
|
|
16099
|
+
serverOutput += data.toString();
|
|
16100
|
+
});
|
|
16101
|
+
for (let i = 0; i < 100; i++) {
|
|
16102
|
+
if (await fetchOk(dashboardUrl)) {
|
|
16103
|
+
return {
|
|
16104
|
+
kill: () => {
|
|
16105
|
+
if (serverProcess.pid !== void 0) {
|
|
16106
|
+
try {
|
|
16107
|
+
process.kill(serverProcess.pid, "SIGTERM");
|
|
16108
|
+
} catch {
|
|
16109
|
+
}
|
|
16110
|
+
}
|
|
16111
|
+
},
|
|
16112
|
+
pid: serverProcess.pid
|
|
16113
|
+
};
|
|
16114
|
+
}
|
|
16115
|
+
if (serverSpawnError !== void 0) {
|
|
16116
|
+
throw new Error(
|
|
16117
|
+
`Failed to start dashboard server process with ${nodeExecutable}: ${formatSpawnError(serverSpawnError)}`
|
|
16118
|
+
);
|
|
16119
|
+
}
|
|
16120
|
+
await sleep(100);
|
|
16121
|
+
}
|
|
16122
|
+
throw new Error(
|
|
16123
|
+
`Failed to start dashboard server at ${dashboardUrl}. Server output: ${serverOutput}`
|
|
16124
|
+
);
|
|
16125
|
+
}
|
|
16126
|
+
function buildDashboardServerScript(distPath, port) {
|
|
16127
|
+
return `
|
|
16128
|
+
import { createReadStream } from 'node:fs';
|
|
16129
|
+
import { stat } from 'node:fs/promises';
|
|
16130
|
+
import { createServer } from 'node:http';
|
|
16131
|
+
import { extname, join, normalize, resolve } from 'node:path';
|
|
16132
|
+
|
|
16133
|
+
const distPath = ${JSON.stringify(distPath)};
|
|
16134
|
+
const port = ${port};
|
|
16135
|
+
const root = resolve(distPath);
|
|
16136
|
+
const contentTypes = new Map([
|
|
16137
|
+
['.css', 'text/css; charset=utf-8'],
|
|
16138
|
+
['.html', 'text/html; charset=utf-8'],
|
|
16139
|
+
['.js', 'text/javascript; charset=utf-8'],
|
|
16140
|
+
['.json', 'application/json; charset=utf-8'],
|
|
16141
|
+
['.map', 'application/json; charset=utf-8'],
|
|
16142
|
+
['.svg', 'image/svg+xml'],
|
|
16143
|
+
]);
|
|
16144
|
+
|
|
16145
|
+
function safePath(url) {
|
|
16146
|
+
const parsed = new URL(url ?? '/', 'http://127.0.0.1');
|
|
16147
|
+
const pathname = parsed.pathname === '/' ? '/index.html' : parsed.pathname;
|
|
16148
|
+
const decoded = decodeURIComponent(pathname);
|
|
16149
|
+
const normalized = normalize(decoded).replace(/^[/\\]+/, '');
|
|
16150
|
+
const absolute = resolve(join(root, normalized));
|
|
16151
|
+
|
|
16152
|
+
if (absolute !== root && !absolute.startsWith(root + '/')) {
|
|
16153
|
+
return null;
|
|
16154
|
+
}
|
|
16155
|
+
|
|
16156
|
+
return absolute;
|
|
16157
|
+
}
|
|
16158
|
+
|
|
16159
|
+
await stat(join(root, 'index.html'));
|
|
16160
|
+
|
|
16161
|
+
const server = createServer(async (request, response) => {
|
|
16162
|
+
const requestedPath = safePath(request.url);
|
|
16163
|
+
|
|
16164
|
+
if (requestedPath === null) {
|
|
16165
|
+
response.writeHead(403, { 'content-type': 'text/plain; charset=utf-8' });
|
|
16166
|
+
response.end('Forbidden');
|
|
16167
|
+
return;
|
|
16168
|
+
}
|
|
16169
|
+
|
|
16170
|
+
try {
|
|
16171
|
+
const fileStat = await stat(requestedPath);
|
|
16172
|
+
const filePath = fileStat.isFile() ? requestedPath : join(root, 'index.html');
|
|
16173
|
+
const contentType = contentTypes.get(extname(filePath)) ?? 'application/octet-stream';
|
|
16174
|
+
|
|
16175
|
+
response.writeHead(200, { 'content-type': contentType });
|
|
16176
|
+
createReadStream(filePath).pipe(response);
|
|
16177
|
+
} catch {
|
|
16178
|
+
response.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
|
|
16179
|
+
createReadStream(join(root, 'index.html')).pipe(response);
|
|
16180
|
+
}
|
|
16181
|
+
});
|
|
16182
|
+
|
|
16183
|
+
server.on('error', (error) => {
|
|
16184
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
16185
|
+
process.exitCode = 1;
|
|
16186
|
+
});
|
|
16187
|
+
|
|
16188
|
+
await new Promise((resolveListen, rejectListen) => {
|
|
16189
|
+
server.once('error', rejectListen);
|
|
16190
|
+
server.listen(port, '127.0.0.1', resolveListen);
|
|
16191
|
+
});
|
|
16192
|
+
|
|
16193
|
+
process.stdin.resume();
|
|
16194
|
+
`;
|
|
16195
|
+
}
|
|
16045
16196
|
async function ensureDaemonNotRunning(options) {
|
|
16046
16197
|
const pathOptions = toPathOptions2(options);
|
|
16047
16198
|
await cleanupStaleDaemonFiles(pathOptions);
|
|
@@ -16075,7 +16226,10 @@ function createCliRuntime(dependencies = {}) {
|
|
|
16075
16226
|
if (child.pid === void 0) {
|
|
16076
16227
|
throw new Error("Failed to obtain the AISnitch daemon PID.");
|
|
16077
16228
|
}
|
|
16078
|
-
return await waitForDaemonReady(
|
|
16229
|
+
return await waitForDaemonReady(
|
|
16230
|
+
daemonPathOptions,
|
|
16231
|
+
options.dashboardPort ?? DEFAULT_DASHBOARD_PORT
|
|
16232
|
+
);
|
|
16079
16233
|
}
|
|
16080
16234
|
async function stopDetachedDaemon(options) {
|
|
16081
16235
|
const pathOptions = toPathOptions2(options);
|
|
@@ -16106,6 +16260,7 @@ function createCliRuntime(dependencies = {}) {
|
|
|
16106
16260
|
consumerCount: snapshot.health?.consumers ?? 0,
|
|
16107
16261
|
daemon: {
|
|
16108
16262
|
active: snapshot.running,
|
|
16263
|
+
dashboardUrl: `http://127.0.0.1:${snapshot.dashboardPort}`,
|
|
16109
16264
|
httpUrl: `http://127.0.0.1:${snapshot.httpPort}/health`,
|
|
16110
16265
|
pid: snapshot.daemonPid,
|
|
16111
16266
|
socketPath: snapshot.socketPath,
|
|
@@ -16146,7 +16301,7 @@ function createCliRuntime(dependencies = {}) {
|
|
|
16146
16301
|
await (0, import_promises22.rm)(backupPath, { force: true });
|
|
16147
16302
|
await (0, import_promises22.rename)(logFilePath, backupPath);
|
|
16148
16303
|
}
|
|
16149
|
-
async function waitForDaemonReady(pathOptions) {
|
|
16304
|
+
async function waitForDaemonReady(pathOptions, dashboardPort = DEFAULT_DASHBOARD_PORT) {
|
|
16150
16305
|
const deadline = Date.now() + DAEMON_READY_TIMEOUT_MS;
|
|
16151
16306
|
while (Date.now() < deadline) {
|
|
16152
16307
|
const daemonState = await readDaemonState(pathOptions);
|
|
@@ -16155,6 +16310,7 @@ function createCliRuntime(dependencies = {}) {
|
|
|
16155
16310
|
if (health !== null) {
|
|
16156
16311
|
return {
|
|
16157
16312
|
configuredAdapters: [],
|
|
16313
|
+
dashboardPort,
|
|
16158
16314
|
daemonPid: daemonState.pid,
|
|
16159
16315
|
health,
|
|
16160
16316
|
httpPort: daemonState.httpPort,
|
|
@@ -16255,6 +16411,7 @@ function createCliRuntime(dependencies = {}) {
|
|
|
16255
16411
|
...pathOptions
|
|
16256
16412
|
});
|
|
16257
16413
|
startMockEmitter(pipeline, options);
|
|
16414
|
+
const dashboardServer = daemonMode ? await startDashboardServerProcess(config.dashboardPort) : null;
|
|
16258
16415
|
if (daemonMode) {
|
|
16259
16416
|
const daemonPathOptions = toPathOptions2(options);
|
|
16260
16417
|
await writePid(process.pid, daemonPathOptions);
|
|
@@ -16291,6 +16448,7 @@ function createCliRuntime(dependencies = {}) {
|
|
|
16291
16448
|
wsServer: pipeline.getWsServer(),
|
|
16292
16449
|
eventBus: pipeline.getEventBus(),
|
|
16293
16450
|
cleanupFns: daemonMode ? [async () => {
|
|
16451
|
+
dashboardServer?.kill();
|
|
16294
16452
|
await Promise.all([
|
|
16295
16453
|
removePid(toPathOptions2(options)),
|
|
16296
16454
|
removeDaemonState(toPathOptions2(options))
|
|
@@ -16367,16 +16525,16 @@ function createCliRuntime(dependencies = {}) {
|
|
|
16367
16525
|
const snapshot = await startDetachedDaemon(options);
|
|
16368
16526
|
output.stdout(
|
|
16369
16527
|
`AISnitch daemon started (PID: ${snapshot.daemonPid ?? "unknown"}) on ws://${"127.0.0.1"}:${snapshot.wsPort}
|
|
16528
|
+
Dashboard: http://127.0.0.1:${snapshot.dashboardPort}
|
|
16370
16529
|
`
|
|
16371
16530
|
);
|
|
16372
16531
|
return;
|
|
16373
16532
|
}
|
|
16374
16533
|
let initialSnapshot = await getStatusSnapshot(options);
|
|
16534
|
+
if (!initialSnapshot.running) {
|
|
16535
|
+
initialSnapshot = await startDetachedDaemon(options);
|
|
16536
|
+
}
|
|
16375
16537
|
if (options.mock) {
|
|
16376
|
-
if (!initialSnapshot.running) {
|
|
16377
|
-
await startDetachedDaemon(options);
|
|
16378
|
-
initialSnapshot = await getStatusSnapshot(options);
|
|
16379
|
-
}
|
|
16380
16538
|
output.stdout(
|
|
16381
16539
|
`Starting mock scenario ${options.mock} (${options.mockSpeed ?? 1}x, ${options.mockDuration ?? 60}s${options.mockLoop ? ", loop" : ""}).
|
|
16382
16540
|
`
|
|
@@ -16436,6 +16594,8 @@ function createCliRuntime(dependencies = {}) {
|
|
|
16436
16594
|
output.stdout(`WebSocket port: ${snapshot.wsPort}
|
|
16437
16595
|
`);
|
|
16438
16596
|
output.stdout(`HTTP port: ${snapshot.httpPort}
|
|
16597
|
+
`);
|
|
16598
|
+
output.stdout(`Dashboard: http://127.0.0.1:${snapshot.dashboardPort}
|
|
16439
16599
|
`);
|
|
16440
16600
|
output.stdout(`Socket path: ${snapshot.socketPath ?? "none"}
|
|
16441
16601
|
`);
|
|
@@ -16492,169 +16652,49 @@ function createCliRuntime(dependencies = {}) {
|
|
|
16492
16652
|
});
|
|
16493
16653
|
}
|
|
16494
16654
|
async function fullscreen(options) {
|
|
16495
|
-
|
|
16496
|
-
if (!snapshot.running
|
|
16655
|
+
let snapshot = await getStatusSnapshot(options);
|
|
16656
|
+
if (!snapshot.running) {
|
|
16497
16657
|
output.stdout("Starting daemon...\n");
|
|
16498
|
-
await startDetachedDaemon(
|
|
16499
|
-
|
|
16500
|
-
|
|
16501
|
-
|
|
16502
|
-
"AISnitch daemon is not running. Start one with `aisnitch start --daemon` or use `aisnitch fs --daemon` to start and open the dashboard."
|
|
16503
|
-
);
|
|
16504
|
-
}
|
|
16505
|
-
for (let i = 0; i < 20; i++) {
|
|
16506
|
-
const health = await fetchHealth(snapshot.httpPort);
|
|
16507
|
-
if (health) break;
|
|
16508
|
-
await new Promise((resolve2) => setTimeout(resolve2, 200).unref());
|
|
16658
|
+
snapshot = await startDetachedDaemon({
|
|
16659
|
+
...options,
|
|
16660
|
+
dashboardPort: options.dashboardPort
|
|
16661
|
+
});
|
|
16509
16662
|
}
|
|
16510
|
-
const dashboardPort = options.dashboardPort ??
|
|
16663
|
+
const dashboardPort = options.dashboardPort ?? snapshot.dashboardPort;
|
|
16511
16664
|
const dashboardUrl = `http://127.0.0.1:${dashboardPort}`;
|
|
16512
|
-
|
|
16513
|
-
|
|
16514
|
-
`
|
|
16515
|
-
const nodeExecutable = await resolveNodeExecutable();
|
|
16516
|
-
const viteProcess = spawnImplementation(nodeExecutable, [
|
|
16517
|
-
"-e",
|
|
16518
|
-
`
|
|
16519
|
-
import { createReadStream } from 'node:fs';
|
|
16520
|
-
import { stat } from 'node:fs/promises';
|
|
16521
|
-
import { createServer } from 'node:http';
|
|
16522
|
-
import { extname, join, normalize, resolve } from 'node:path';
|
|
16523
|
-
|
|
16524
|
-
const distPath = ${JSON.stringify(distPath)};
|
|
16525
|
-
const port = ${dashboardPort};
|
|
16526
|
-
const root = resolve(distPath);
|
|
16527
|
-
const contentTypes = new Map([
|
|
16528
|
-
['.css', 'text/css; charset=utf-8'],
|
|
16529
|
-
['.html', 'text/html; charset=utf-8'],
|
|
16530
|
-
['.js', 'text/javascript; charset=utf-8'],
|
|
16531
|
-
['.json', 'application/json; charset=utf-8'],
|
|
16532
|
-
['.map', 'application/json; charset=utf-8'],
|
|
16533
|
-
['.svg', 'image/svg+xml'],
|
|
16534
|
-
]);
|
|
16535
|
-
|
|
16536
|
-
function safePath(url) {
|
|
16537
|
-
const parsed = new URL(url ?? '/', 'http://127.0.0.1');
|
|
16538
|
-
const pathname = parsed.pathname === '/' ? '/index.html' : parsed.pathname;
|
|
16539
|
-
const decoded = decodeURIComponent(pathname);
|
|
16540
|
-
const normalized = normalize(decoded).replace(/^[/\\]+/, '');
|
|
16541
|
-
const absolute = resolve(join(root, normalized));
|
|
16542
|
-
|
|
16543
|
-
if (absolute !== root && !absolute.startsWith(root + '/')) {
|
|
16544
|
-
return null;
|
|
16545
|
-
}
|
|
16546
|
-
|
|
16547
|
-
return absolute;
|
|
16548
|
-
}
|
|
16549
|
-
|
|
16550
|
-
await stat(join(root, 'index.html'));
|
|
16551
|
-
|
|
16552
|
-
const server = createServer(async (request, response) => {
|
|
16553
|
-
const requestedPath = safePath(request.url);
|
|
16554
|
-
|
|
16555
|
-
if (requestedPath === null) {
|
|
16556
|
-
response.writeHead(403, { 'content-type': 'text/plain; charset=utf-8' });
|
|
16557
|
-
response.end('Forbidden');
|
|
16558
|
-
return;
|
|
16559
|
-
}
|
|
16560
|
-
|
|
16561
|
-
try {
|
|
16562
|
-
const fileStat = await stat(requestedPath);
|
|
16563
|
-
const filePath = fileStat.isFile() ? requestedPath : join(root, 'index.html');
|
|
16564
|
-
const contentType = contentTypes.get(extname(filePath)) ?? 'application/octet-stream';
|
|
16565
|
-
|
|
16566
|
-
response.writeHead(200, { 'content-type': contentType });
|
|
16567
|
-
createReadStream(filePath).pipe(response);
|
|
16568
|
-
} catch {
|
|
16569
|
-
response.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
|
|
16570
|
-
createReadStream(join(root, 'index.html')).pipe(response);
|
|
16571
|
-
}
|
|
16572
|
-
});
|
|
16573
|
-
|
|
16574
|
-
server.on('error', (error) => {
|
|
16575
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
16576
|
-
process.exitCode = 1;
|
|
16577
|
-
});
|
|
16578
|
-
|
|
16579
|
-
await new Promise((resolveListen, rejectListen) => {
|
|
16580
|
-
server.once('error', rejectListen);
|
|
16581
|
-
server.listen(port, '127.0.0.1', resolveListen);
|
|
16582
|
-
});
|
|
16583
|
-
|
|
16584
|
-
process.stdin.resume();
|
|
16665
|
+
if (!await waitForDashboardReady(dashboardPort)) {
|
|
16666
|
+
output.stdout(
|
|
16667
|
+
`Dashboard not reachable at ${dashboardUrl}, starting a standalone server...
|
|
16585
16668
|
`
|
|
16586
|
-
|
|
16587
|
-
cwd: distPath,
|
|
16588
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
16589
|
-
});
|
|
16590
|
-
let serverOutput = "";
|
|
16591
|
-
let serverSpawnError;
|
|
16592
|
-
viteProcess.on("error", (error) => {
|
|
16593
|
-
serverSpawnError = error;
|
|
16594
|
-
serverOutput += `Dashboard server process failed: ${formatSpawnError(error)}
|
|
16595
|
-
`;
|
|
16596
|
-
});
|
|
16597
|
-
viteProcess.stdout?.on("data", (data) => {
|
|
16598
|
-
serverOutput += data.toString();
|
|
16599
|
-
});
|
|
16600
|
-
viteProcess.stderr?.on("data", (data) => {
|
|
16601
|
-
serverOutput += data.toString();
|
|
16602
|
-
});
|
|
16603
|
-
let serverReady = false;
|
|
16604
|
-
for (let i = 0; i < 100; i++) {
|
|
16605
|
-
await new Promise((resolve2) => setTimeout(resolve2, 100).unref());
|
|
16669
|
+
);
|
|
16606
16670
|
try {
|
|
16607
|
-
|
|
16608
|
-
|
|
16609
|
-
|
|
16610
|
-
|
|
16611
|
-
|
|
16612
|
-
|
|
16613
|
-
}
|
|
16614
|
-
if (serverSpawnError !== void 0) {
|
|
16615
|
-
break;
|
|
16671
|
+
await startDashboardServerProcess(dashboardPort);
|
|
16672
|
+
} catch (error) {
|
|
16673
|
+
throw new Error(
|
|
16674
|
+
`Dashboard did not become reachable at ${dashboardUrl}: ${error instanceof Error ? error.message : "unknown error"}`,
|
|
16675
|
+
{ cause: error }
|
|
16676
|
+
);
|
|
16616
16677
|
}
|
|
16617
|
-
if (!
|
|
16618
|
-
}
|
|
16619
|
-
if (!serverReady) {
|
|
16620
|
-
output.stdout(`Server output: ${serverOutput}
|
|
16621
|
-
`);
|
|
16622
|
-
if (serverSpawnError !== void 0) {
|
|
16678
|
+
if (!await waitForDashboardReady(dashboardPort)) {
|
|
16623
16679
|
throw new Error(
|
|
16624
|
-
`
|
|
16680
|
+
`Dashboard did not become reachable at ${dashboardUrl}.`
|
|
16625
16681
|
);
|
|
16626
16682
|
}
|
|
16627
|
-
throw new Error("Failed to start dashboard server");
|
|
16628
16683
|
}
|
|
16629
16684
|
output.stdout(`Dashboard ready at ${dashboardUrl}
|
|
16630
16685
|
`);
|
|
16631
16686
|
if (!options.noBrowser) {
|
|
16632
16687
|
output.stdout("Opening browser...\n");
|
|
16633
|
-
|
|
16634
|
-
|
|
16688
|
+
try {
|
|
16689
|
+
const openModule = await openPromise;
|
|
16690
|
+
await openModule.default(dashboardUrl);
|
|
16691
|
+
} catch (error) {
|
|
16692
|
+
output.stderr(
|
|
16693
|
+
`Failed to open browser automatically (${error instanceof Error ? error.message : "unknown error"}). Open ${dashboardUrl} manually.
|
|
16694
|
+
`
|
|
16695
|
+
);
|
|
16696
|
+
}
|
|
16635
16697
|
}
|
|
16636
|
-
output.stdout("Press Ctrl+C to stop\n");
|
|
16637
|
-
await new Promise((resolve2) => {
|
|
16638
|
-
const shutdown = () => {
|
|
16639
|
-
process.off("SIGINT", handleSigint);
|
|
16640
|
-
process.off("SIGTERM", handleSigterm);
|
|
16641
|
-
if (viteProcess.pid !== void 0) {
|
|
16642
|
-
try {
|
|
16643
|
-
process.kill(viteProcess.pid, "SIGTERM");
|
|
16644
|
-
} catch {
|
|
16645
|
-
}
|
|
16646
|
-
}
|
|
16647
|
-
resolve2();
|
|
16648
|
-
};
|
|
16649
|
-
const handleSigint = () => {
|
|
16650
|
-
shutdown();
|
|
16651
|
-
};
|
|
16652
|
-
const handleSigterm = () => {
|
|
16653
|
-
shutdown();
|
|
16654
|
-
};
|
|
16655
|
-
process.once("SIGINT", handleSigint);
|
|
16656
|
-
process.once("SIGTERM", handleSigterm);
|
|
16657
|
-
});
|
|
16658
16698
|
}
|
|
16659
16699
|
async function logger2(options) {
|
|
16660
16700
|
const snapshot = await getStatusSnapshot(options);
|
|
@@ -16921,6 +16961,7 @@ process.stdin.resume();
|
|
|
16921
16961
|
const health = running && daemonState !== null ? await fetchHealth(daemonState.httpPort) : null;
|
|
16922
16962
|
return {
|
|
16923
16963
|
configuredAdapters: getEnabledAdapters(config),
|
|
16964
|
+
dashboardPort: config.dashboardPort ?? DEFAULT_DASHBOARD_PORT,
|
|
16924
16965
|
daemonPid,
|
|
16925
16966
|
health,
|
|
16926
16967
|
httpPort: daemonState?.httpPort ?? config.httpPort,
|
|
@@ -17155,13 +17196,13 @@ function createProgram(dependencies = {}) {
|
|
|
17155
17196
|
"after",
|
|
17156
17197
|
`
|
|
17157
17198
|
Examples:
|
|
17158
|
-
aisnitch
|
|
17199
|
+
aisnitch (default: starts daemon + dashboard server, opens browser)
|
|
17200
|
+
aisnitch fs --dashboard-port 8080
|
|
17201
|
+
aisnitch fs --no-browser
|
|
17202
|
+
aisnitch start (TUI dashboard)
|
|
17159
17203
|
aisnitch start --daemon
|
|
17160
17204
|
aisnitch start --view full-data
|
|
17161
17205
|
aisnitch start --mock
|
|
17162
|
-
aisnitch fs
|
|
17163
|
-
aisnitch fs --daemon
|
|
17164
|
-
aisnitch fs --dashboard-port 8080
|
|
17165
17206
|
aisnitch status
|
|
17166
17207
|
aisnitch attach
|
|
17167
17208
|
aisnitch attach --view full-data
|
|
@@ -17204,7 +17245,7 @@ function addCommonOptions(command) {
|
|
|
17204
17245
|
}
|
|
17205
17246
|
function addStartCommand(program, runtime) {
|
|
17206
17247
|
addCommonOptions(
|
|
17207
|
-
program.command("start"
|
|
17248
|
+
program.command("start").description("Open the AISnitch TUI dashboard and manage the daemon").option("--daemon", "Run AISnitch as a detached daemon").option(
|
|
17208
17249
|
"--mock [tool]",
|
|
17209
17250
|
'Inject deterministic mock events (defaults to "all" when no tool is specified)',
|
|
17210
17251
|
wrapOptionParser(parseMockToolSelection)
|
|
@@ -17236,6 +17277,10 @@ function addStartCommand(program, runtime) {
|
|
|
17236
17277
|
"--http-port <port>",
|
|
17237
17278
|
"Override the HTTP hook port",
|
|
17238
17279
|
wrapOptionParser(parsePortOption)
|
|
17280
|
+
).option(
|
|
17281
|
+
"--dashboard-port <port>",
|
|
17282
|
+
"Override the web dashboard port",
|
|
17283
|
+
wrapOptionParser(parsePortOption)
|
|
17239
17284
|
).option(
|
|
17240
17285
|
"--log-level <level>",
|
|
17241
17286
|
"Override the runtime log level",
|
|
@@ -17320,9 +17365,9 @@ function addAttachCommand(program, runtime) {
|
|
|
17320
17365
|
}
|
|
17321
17366
|
function addFullscreenCommand(program, runtime) {
|
|
17322
17367
|
addCommonOptions(
|
|
17323
|
-
program.command("fs").description("
|
|
17368
|
+
program.command("fs", { isDefault: true }).description("Start the daemon, the dashboard server, and open the web dashboard in browser").alias("fullscreen").option(
|
|
17324
17369
|
"--daemon",
|
|
17325
|
-
"
|
|
17370
|
+
"Deprecated: daemon is now started automatically when needed"
|
|
17326
17371
|
).option(
|
|
17327
17372
|
"--dashboard-port <port>",
|
|
17328
17373
|
"Port for the dashboard server (default: 5174)",
|