aisnitch 0.2.24 → 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 +242 -120
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +244 -122
- 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/examples/fullscreen-dashboard/README.md +72 -0
- package/examples/fullscreen-dashboard/dist/assets/index-8iYVmJ7n.js +65 -0
- package/examples/fullscreen-dashboard/dist/index.html +16 -0
- package/package.json +3 -2
package/dist/cli/index.js
CHANGED
|
@@ -743,7 +743,7 @@ import { Command, InvalidArgumentError } from "commander";
|
|
|
743
743
|
|
|
744
744
|
// src/package-info.ts
|
|
745
745
|
var AISNITCH_PACKAGE_NAME = "aisnitch";
|
|
746
|
-
var AISNITCH_VERSION = "0.2.
|
|
746
|
+
var AISNITCH_VERSION = "0.2.26";
|
|
747
747
|
var AISNITCH_DESCRIPTION = "Universal bridge for AI coding tool activity \u2014 capture, normalize, stream.";
|
|
748
748
|
|
|
749
749
|
// src/core/events/schema.ts
|
|
@@ -1332,6 +1332,7 @@ var AutoUpdateConfigSchema = z2.strictObject({
|
|
|
1332
1332
|
var ConfigSchema = z2.strictObject({
|
|
1333
1333
|
wsPort: z2.number().int().min(1024).max(65535).default(4820),
|
|
1334
1334
|
httpPort: z2.number().int().min(1024).max(65535).default(4821),
|
|
1335
|
+
dashboardPort: z2.number().int().min(1024).max(65535).default(5174),
|
|
1335
1336
|
/**
|
|
1336
1337
|
* 📖 This is intentionally a partial record because most users will only
|
|
1337
1338
|
* override a couple of adapters instead of all supported tools at once.
|
|
@@ -1350,6 +1351,7 @@ var ConfigSchema = z2.strictObject({
|
|
|
1350
1351
|
var DEFAULT_CONFIG = {
|
|
1351
1352
|
wsPort: 4820,
|
|
1352
1353
|
httpPort: 4821,
|
|
1354
|
+
dashboardPort: 5174,
|
|
1353
1355
|
adapters: {},
|
|
1354
1356
|
autoUpdate: {
|
|
1355
1357
|
enabled: true,
|
|
@@ -3468,10 +3470,10 @@ function toConfigPathOptions(options) {
|
|
|
3468
3470
|
// src/cli/runtime.ts
|
|
3469
3471
|
import { execFile as execFileCallback14, spawn as spawnChildProcess2 } from "child_process";
|
|
3470
3472
|
import { closeSync, constants as fsConstants4, openSync } from "fs";
|
|
3471
|
-
import { access as access4, mkdtemp, readFile as readFile15, rename, rm as rm4, writeFile as writeFile5 } from "fs/promises";
|
|
3473
|
+
import { access as access4, mkdtemp, readFile as readFile15, realpath as realpath2, rename, rm as rm4, writeFile as writeFile5 } from "fs/promises";
|
|
3472
3474
|
import { createConnection as createConnection3 } from "net";
|
|
3473
3475
|
import { tmpdir } from "os";
|
|
3474
|
-
import { basename as basename12, join as join18 } from "path";
|
|
3476
|
+
import { basename as basename12, dirname as dirname8, join as join18 } from "path";
|
|
3475
3477
|
import { promisify as promisify21 } from "util";
|
|
3476
3478
|
|
|
3477
3479
|
// src/adapters/generic-pty.ts
|
|
@@ -14142,6 +14144,10 @@ function Header({
|
|
|
14142
14144
|
] }) }),
|
|
14143
14145
|
daemon ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
14144
14146
|
/* @__PURE__ */ jsx6(Text6, { color: connected ? TUI_THEME.success : TUI_THEME.warning, children: connected ? `\u25CF ${connectionLabel}` : `\u25CB ${connectionLabel}` }),
|
|
14147
|
+
/* @__PURE__ */ jsxs5(Text6, { color: TUI_THEME.success, children: [
|
|
14148
|
+
"Web ",
|
|
14149
|
+
daemon.dashboardUrl
|
|
14150
|
+
] }),
|
|
14145
14151
|
/* @__PURE__ */ jsxs5(Text6, { color: TUI_THEME.muted, children: [
|
|
14146
14152
|
"WS ",
|
|
14147
14153
|
daemon.wsUrl
|
|
@@ -14334,7 +14340,7 @@ function StatusBar({
|
|
|
14334
14340
|
}) {
|
|
14335
14341
|
const streamState = streamFrozen ? `Frozen +${pendingEventCount}` : latestEvent?.type ?? "Live";
|
|
14336
14342
|
const focusLabel = focusPanel === "events" ? "events" : viewMode === "full-data" ? "inspector" : "sessions";
|
|
14337
|
-
const daemonLabel = daemon === void 0 ? null : daemon.busyAction ? `Daemon ${daemon.busyAction}` : daemon.active ? `Daemon active \xB7 ${daemon.
|
|
14343
|
+
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}`;
|
|
14338
14344
|
return /* @__PURE__ */ jsxs9(
|
|
14339
14345
|
Box9,
|
|
14340
14346
|
{
|
|
@@ -15919,6 +15925,7 @@ var DAEMON_READY_TIMEOUT_MS = 4e3;
|
|
|
15919
15925
|
var DAEMON_READY_POLL_INTERVAL_MS = 100;
|
|
15920
15926
|
var DAEMON_STOP_TIMEOUT_MS = 4e3;
|
|
15921
15927
|
var DAEMON_LOG_MAX_BYTES = 5 * 1024 * 1024;
|
|
15928
|
+
var DEFAULT_DASHBOARD_PORT = 5174;
|
|
15922
15929
|
var LAUNCH_AGENT_LABEL = "com.aisnitch.daemon";
|
|
15923
15930
|
async function resolveNodeExecutable() {
|
|
15924
15931
|
try {
|
|
@@ -15934,6 +15941,40 @@ function formatSpawnError(error) {
|
|
|
15934
15941
|
}
|
|
15935
15942
|
return String(error);
|
|
15936
15943
|
}
|
|
15944
|
+
async function pathExists(path2) {
|
|
15945
|
+
try {
|
|
15946
|
+
await access4(path2, fsConstants4.R_OK);
|
|
15947
|
+
return true;
|
|
15948
|
+
} catch {
|
|
15949
|
+
return false;
|
|
15950
|
+
}
|
|
15951
|
+
}
|
|
15952
|
+
async function resolveDashboardDistPath() {
|
|
15953
|
+
if (process.env.AISNITCH_DASHBOARD_DIST) {
|
|
15954
|
+
const overridePath = process.env.AISNITCH_DASHBOARD_DIST;
|
|
15955
|
+
if (await pathExists(join18(overridePath, "index.html"))) {
|
|
15956
|
+
return overridePath;
|
|
15957
|
+
}
|
|
15958
|
+
throw new Error(
|
|
15959
|
+
`Fullscreen dashboard assets are missing at ${overridePath}. Reinstall AISnitch or run \`pnpm --filter aisnitch-fullscreen-dashboard build\` from the repository checkout.`
|
|
15960
|
+
);
|
|
15961
|
+
}
|
|
15962
|
+
const cliEntryPath = process.argv[1] ? await realpath2(process.argv[1]).catch(() => process.argv[1] ?? "") : "";
|
|
15963
|
+
const moduleDirectory = dirname8(cliEntryPath);
|
|
15964
|
+
const packageRoot = dirname8(dirname8(moduleDirectory));
|
|
15965
|
+
const candidates = [
|
|
15966
|
+
join18(packageRoot, "examples", "fullscreen-dashboard", "dist"),
|
|
15967
|
+
join18(process.cwd(), "examples", "fullscreen-dashboard", "dist")
|
|
15968
|
+
];
|
|
15969
|
+
for (const candidate of candidates) {
|
|
15970
|
+
if (await pathExists(join18(candidate, "index.html"))) {
|
|
15971
|
+
return candidate;
|
|
15972
|
+
}
|
|
15973
|
+
}
|
|
15974
|
+
throw new Error(
|
|
15975
|
+
"Fullscreen dashboard assets are missing. Reinstall AISnitch or run `pnpm --filter aisnitch-fullscreen-dashboard build` from the repository checkout."
|
|
15976
|
+
);
|
|
15977
|
+
}
|
|
15937
15978
|
function createCliRuntime(dependencies = {}) {
|
|
15938
15979
|
const output = dependencies.output ?? createProcessOutput();
|
|
15939
15980
|
const fetchImplementation = dependencies.fetch ?? globalThis.fetch;
|
|
@@ -15959,6 +16000,7 @@ function createCliRuntime(dependencies = {}) {
|
|
|
15959
16000
|
const config = ConfigSchema.parse({
|
|
15960
16001
|
...baseConfig,
|
|
15961
16002
|
...isStartCliOptions(options) ? {
|
|
16003
|
+
dashboardPort: options.dashboardPort ?? baseConfig.dashboardPort,
|
|
15962
16004
|
httpPort: options.httpPort ?? baseConfig.httpPort,
|
|
15963
16005
|
logLevel: options.logLevel ?? baseConfig.logLevel,
|
|
15964
16006
|
wsPort: options.wsPort ?? baseConfig.wsPort
|
|
@@ -15982,6 +16024,149 @@ function createCliRuntime(dependencies = {}) {
|
|
|
15982
16024
|
return null;
|
|
15983
16025
|
}
|
|
15984
16026
|
}
|
|
16027
|
+
async function fetchOk(url) {
|
|
16028
|
+
try {
|
|
16029
|
+
const response = await fetchImplementation(url);
|
|
16030
|
+
return response.ok;
|
|
16031
|
+
} catch {
|
|
16032
|
+
return false;
|
|
16033
|
+
}
|
|
16034
|
+
}
|
|
16035
|
+
async function waitForDashboardReady(port) {
|
|
16036
|
+
const dashboardUrl = `http://127.0.0.1:${port}`;
|
|
16037
|
+
for (let i = 0; i < 100; i++) {
|
|
16038
|
+
if (await fetchOk(dashboardUrl)) {
|
|
16039
|
+
return true;
|
|
16040
|
+
}
|
|
16041
|
+
await sleep(100);
|
|
16042
|
+
}
|
|
16043
|
+
return false;
|
|
16044
|
+
}
|
|
16045
|
+
async function startDashboardServerProcess(port) {
|
|
16046
|
+
const dashboardUrl = `http://127.0.0.1:${port}`;
|
|
16047
|
+
if (await fetchOk(dashboardUrl)) {
|
|
16048
|
+
return {
|
|
16049
|
+
kill: () => void 0,
|
|
16050
|
+
pid: void 0
|
|
16051
|
+
};
|
|
16052
|
+
}
|
|
16053
|
+
const distPath = await resolveDashboardDistPath();
|
|
16054
|
+
const nodeExecutable = await resolveNodeExecutable();
|
|
16055
|
+
const serverProcess = spawnImplementation(nodeExecutable, [
|
|
16056
|
+
"-e",
|
|
16057
|
+
buildDashboardServerScript(distPath, port)
|
|
16058
|
+
], {
|
|
16059
|
+
cwd: distPath,
|
|
16060
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
16061
|
+
});
|
|
16062
|
+
let serverOutput = "";
|
|
16063
|
+
let serverSpawnError;
|
|
16064
|
+
serverProcess.on("error", (error) => {
|
|
16065
|
+
serverSpawnError = error;
|
|
16066
|
+
serverOutput += `Dashboard server process failed: ${formatSpawnError(error)}
|
|
16067
|
+
`;
|
|
16068
|
+
});
|
|
16069
|
+
serverProcess.stdout?.on("data", (data) => {
|
|
16070
|
+
serverOutput += data.toString();
|
|
16071
|
+
});
|
|
16072
|
+
serverProcess.stderr?.on("data", (data) => {
|
|
16073
|
+
serverOutput += data.toString();
|
|
16074
|
+
});
|
|
16075
|
+
for (let i = 0; i < 100; i++) {
|
|
16076
|
+
if (await fetchOk(dashboardUrl)) {
|
|
16077
|
+
return {
|
|
16078
|
+
kill: () => {
|
|
16079
|
+
if (serverProcess.pid !== void 0) {
|
|
16080
|
+
try {
|
|
16081
|
+
process.kill(serverProcess.pid, "SIGTERM");
|
|
16082
|
+
} catch {
|
|
16083
|
+
}
|
|
16084
|
+
}
|
|
16085
|
+
},
|
|
16086
|
+
pid: serverProcess.pid
|
|
16087
|
+
};
|
|
16088
|
+
}
|
|
16089
|
+
if (serverSpawnError !== void 0) {
|
|
16090
|
+
throw new Error(
|
|
16091
|
+
`Failed to start dashboard server process with ${nodeExecutable}: ${formatSpawnError(serverSpawnError)}`
|
|
16092
|
+
);
|
|
16093
|
+
}
|
|
16094
|
+
await sleep(100);
|
|
16095
|
+
}
|
|
16096
|
+
throw new Error(
|
|
16097
|
+
`Failed to start dashboard server at ${dashboardUrl}. Server output: ${serverOutput}`
|
|
16098
|
+
);
|
|
16099
|
+
}
|
|
16100
|
+
function buildDashboardServerScript(distPath, port) {
|
|
16101
|
+
return `
|
|
16102
|
+
import { createReadStream } from 'node:fs';
|
|
16103
|
+
import { stat } from 'node:fs/promises';
|
|
16104
|
+
import { createServer } from 'node:http';
|
|
16105
|
+
import { extname, join, normalize, resolve } from 'node:path';
|
|
16106
|
+
|
|
16107
|
+
const distPath = ${JSON.stringify(distPath)};
|
|
16108
|
+
const port = ${port};
|
|
16109
|
+
const root = resolve(distPath);
|
|
16110
|
+
const contentTypes = new Map([
|
|
16111
|
+
['.css', 'text/css; charset=utf-8'],
|
|
16112
|
+
['.html', 'text/html; charset=utf-8'],
|
|
16113
|
+
['.js', 'text/javascript; charset=utf-8'],
|
|
16114
|
+
['.json', 'application/json; charset=utf-8'],
|
|
16115
|
+
['.map', 'application/json; charset=utf-8'],
|
|
16116
|
+
['.svg', 'image/svg+xml'],
|
|
16117
|
+
]);
|
|
16118
|
+
|
|
16119
|
+
function safePath(url) {
|
|
16120
|
+
const parsed = new URL(url ?? '/', 'http://127.0.0.1');
|
|
16121
|
+
const pathname = parsed.pathname === '/' ? '/index.html' : parsed.pathname;
|
|
16122
|
+
const decoded = decodeURIComponent(pathname);
|
|
16123
|
+
const normalized = normalize(decoded).replace(/^[/\\]+/, '');
|
|
16124
|
+
const absolute = resolve(join(root, normalized));
|
|
16125
|
+
|
|
16126
|
+
if (absolute !== root && !absolute.startsWith(root + '/')) {
|
|
16127
|
+
return null;
|
|
16128
|
+
}
|
|
16129
|
+
|
|
16130
|
+
return absolute;
|
|
16131
|
+
}
|
|
16132
|
+
|
|
16133
|
+
await stat(join(root, 'index.html'));
|
|
16134
|
+
|
|
16135
|
+
const server = createServer(async (request, response) => {
|
|
16136
|
+
const requestedPath = safePath(request.url);
|
|
16137
|
+
|
|
16138
|
+
if (requestedPath === null) {
|
|
16139
|
+
response.writeHead(403, { 'content-type': 'text/plain; charset=utf-8' });
|
|
16140
|
+
response.end('Forbidden');
|
|
16141
|
+
return;
|
|
16142
|
+
}
|
|
16143
|
+
|
|
16144
|
+
try {
|
|
16145
|
+
const fileStat = await stat(requestedPath);
|
|
16146
|
+
const filePath = fileStat.isFile() ? requestedPath : join(root, 'index.html');
|
|
16147
|
+
const contentType = contentTypes.get(extname(filePath)) ?? 'application/octet-stream';
|
|
16148
|
+
|
|
16149
|
+
response.writeHead(200, { 'content-type': contentType });
|
|
16150
|
+
createReadStream(filePath).pipe(response);
|
|
16151
|
+
} catch {
|
|
16152
|
+
response.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
|
|
16153
|
+
createReadStream(join(root, 'index.html')).pipe(response);
|
|
16154
|
+
}
|
|
16155
|
+
});
|
|
16156
|
+
|
|
16157
|
+
server.on('error', (error) => {
|
|
16158
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
16159
|
+
process.exitCode = 1;
|
|
16160
|
+
});
|
|
16161
|
+
|
|
16162
|
+
await new Promise((resolveListen, rejectListen) => {
|
|
16163
|
+
server.once('error', rejectListen);
|
|
16164
|
+
server.listen(port, '127.0.0.1', resolveListen);
|
|
16165
|
+
});
|
|
16166
|
+
|
|
16167
|
+
process.stdin.resume();
|
|
16168
|
+
`;
|
|
16169
|
+
}
|
|
15985
16170
|
async function ensureDaemonNotRunning(options) {
|
|
15986
16171
|
const pathOptions = toPathOptions2(options);
|
|
15987
16172
|
await cleanupStaleDaemonFiles(pathOptions);
|
|
@@ -16015,7 +16200,10 @@ function createCliRuntime(dependencies = {}) {
|
|
|
16015
16200
|
if (child.pid === void 0) {
|
|
16016
16201
|
throw new Error("Failed to obtain the AISnitch daemon PID.");
|
|
16017
16202
|
}
|
|
16018
|
-
return await waitForDaemonReady(
|
|
16203
|
+
return await waitForDaemonReady(
|
|
16204
|
+
daemonPathOptions,
|
|
16205
|
+
options.dashboardPort ?? DEFAULT_DASHBOARD_PORT
|
|
16206
|
+
);
|
|
16019
16207
|
}
|
|
16020
16208
|
async function stopDetachedDaemon(options) {
|
|
16021
16209
|
const pathOptions = toPathOptions2(options);
|
|
@@ -16046,6 +16234,7 @@ function createCliRuntime(dependencies = {}) {
|
|
|
16046
16234
|
consumerCount: snapshot.health?.consumers ?? 0,
|
|
16047
16235
|
daemon: {
|
|
16048
16236
|
active: snapshot.running,
|
|
16237
|
+
dashboardUrl: `http://127.0.0.1:${snapshot.dashboardPort}`,
|
|
16049
16238
|
httpUrl: `http://127.0.0.1:${snapshot.httpPort}/health`,
|
|
16050
16239
|
pid: snapshot.daemonPid,
|
|
16051
16240
|
socketPath: snapshot.socketPath,
|
|
@@ -16086,7 +16275,7 @@ function createCliRuntime(dependencies = {}) {
|
|
|
16086
16275
|
await rm4(backupPath, { force: true });
|
|
16087
16276
|
await rename(logFilePath, backupPath);
|
|
16088
16277
|
}
|
|
16089
|
-
async function waitForDaemonReady(pathOptions) {
|
|
16278
|
+
async function waitForDaemonReady(pathOptions, dashboardPort = DEFAULT_DASHBOARD_PORT) {
|
|
16090
16279
|
const deadline = Date.now() + DAEMON_READY_TIMEOUT_MS;
|
|
16091
16280
|
while (Date.now() < deadline) {
|
|
16092
16281
|
const daemonState = await readDaemonState(pathOptions);
|
|
@@ -16095,6 +16284,7 @@ function createCliRuntime(dependencies = {}) {
|
|
|
16095
16284
|
if (health !== null) {
|
|
16096
16285
|
return {
|
|
16097
16286
|
configuredAdapters: [],
|
|
16287
|
+
dashboardPort,
|
|
16098
16288
|
daemonPid: daemonState.pid,
|
|
16099
16289
|
health,
|
|
16100
16290
|
httpPort: daemonState.httpPort,
|
|
@@ -16195,6 +16385,7 @@ function createCliRuntime(dependencies = {}) {
|
|
|
16195
16385
|
...pathOptions
|
|
16196
16386
|
});
|
|
16197
16387
|
startMockEmitter(pipeline, options);
|
|
16388
|
+
const dashboardServer = daemonMode ? await startDashboardServerProcess(config.dashboardPort) : null;
|
|
16198
16389
|
if (daemonMode) {
|
|
16199
16390
|
const daemonPathOptions = toPathOptions2(options);
|
|
16200
16391
|
await writePid(process.pid, daemonPathOptions);
|
|
@@ -16231,6 +16422,7 @@ function createCliRuntime(dependencies = {}) {
|
|
|
16231
16422
|
wsServer: pipeline.getWsServer(),
|
|
16232
16423
|
eventBus: pipeline.getEventBus(),
|
|
16233
16424
|
cleanupFns: daemonMode ? [async () => {
|
|
16425
|
+
dashboardServer?.kill();
|
|
16234
16426
|
await Promise.all([
|
|
16235
16427
|
removePid(toPathOptions2(options)),
|
|
16236
16428
|
removeDaemonState(toPathOptions2(options))
|
|
@@ -16307,16 +16499,16 @@ function createCliRuntime(dependencies = {}) {
|
|
|
16307
16499
|
const snapshot = await startDetachedDaemon(options);
|
|
16308
16500
|
output.stdout(
|
|
16309
16501
|
`AISnitch daemon started (PID: ${snapshot.daemonPid ?? "unknown"}) on ws://${"127.0.0.1"}:${snapshot.wsPort}
|
|
16502
|
+
Dashboard: http://127.0.0.1:${snapshot.dashboardPort}
|
|
16310
16503
|
`
|
|
16311
16504
|
);
|
|
16312
16505
|
return;
|
|
16313
16506
|
}
|
|
16314
16507
|
let initialSnapshot = await getStatusSnapshot(options);
|
|
16508
|
+
if (!initialSnapshot.running) {
|
|
16509
|
+
initialSnapshot = await startDetachedDaemon(options);
|
|
16510
|
+
}
|
|
16315
16511
|
if (options.mock) {
|
|
16316
|
-
if (!initialSnapshot.running) {
|
|
16317
|
-
await startDetachedDaemon(options);
|
|
16318
|
-
initialSnapshot = await getStatusSnapshot(options);
|
|
16319
|
-
}
|
|
16320
16512
|
output.stdout(
|
|
16321
16513
|
`Starting mock scenario ${options.mock} (${options.mockSpeed ?? 1}x, ${options.mockDuration ?? 60}s${options.mockLoop ? ", loop" : ""}).
|
|
16322
16514
|
`
|
|
@@ -16376,6 +16568,8 @@ function createCliRuntime(dependencies = {}) {
|
|
|
16376
16568
|
output.stdout(`WebSocket port: ${snapshot.wsPort}
|
|
16377
16569
|
`);
|
|
16378
16570
|
output.stdout(`HTTP port: ${snapshot.httpPort}
|
|
16571
|
+
`);
|
|
16572
|
+
output.stdout(`Dashboard: http://127.0.0.1:${snapshot.dashboardPort}
|
|
16379
16573
|
`);
|
|
16380
16574
|
output.stdout(`Socket path: ${snapshot.socketPath ?? "none"}
|
|
16381
16575
|
`);
|
|
@@ -16432,126 +16626,49 @@ function createCliRuntime(dependencies = {}) {
|
|
|
16432
16626
|
});
|
|
16433
16627
|
}
|
|
16434
16628
|
async function fullscreen(options) {
|
|
16435
|
-
|
|
16436
|
-
if (!snapshot.running
|
|
16629
|
+
let snapshot = await getStatusSnapshot(options);
|
|
16630
|
+
if (!snapshot.running) {
|
|
16437
16631
|
output.stdout("Starting daemon...\n");
|
|
16438
|
-
await startDetachedDaemon(
|
|
16439
|
-
|
|
16440
|
-
|
|
16441
|
-
|
|
16442
|
-
"AISnitch daemon is not running. Start one with `aisnitch start --daemon` or use `aisnitch fs --daemon` to start and open the dashboard."
|
|
16443
|
-
);
|
|
16444
|
-
}
|
|
16445
|
-
for (let i = 0; i < 20; i++) {
|
|
16446
|
-
const health = await fetchHealth(snapshot.httpPort);
|
|
16447
|
-
if (health) break;
|
|
16448
|
-
await new Promise((resolve2) => setTimeout(resolve2, 200).unref());
|
|
16632
|
+
snapshot = await startDetachedDaemon({
|
|
16633
|
+
...options,
|
|
16634
|
+
dashboardPort: options.dashboardPort
|
|
16635
|
+
});
|
|
16449
16636
|
}
|
|
16450
|
-
const dashboardPort = options.dashboardPort ??
|
|
16637
|
+
const dashboardPort = options.dashboardPort ?? snapshot.dashboardPort;
|
|
16451
16638
|
const dashboardUrl = `http://127.0.0.1:${dashboardPort}`;
|
|
16452
|
-
|
|
16453
|
-
|
|
16454
|
-
`
|
|
16455
|
-
const nodeExecutable = await resolveNodeExecutable();
|
|
16456
|
-
const viteProcess = spawnImplementation(nodeExecutable, [
|
|
16457
|
-
"-e",
|
|
16458
|
-
`
|
|
16459
|
-
import { createServer } from 'vite';
|
|
16460
|
-
import path from 'path';
|
|
16461
|
-
|
|
16462
|
-
const distPath = '${distPath}';
|
|
16463
|
-
const port = ${dashboardPort};
|
|
16464
|
-
|
|
16465
|
-
const server = await createServer({
|
|
16466
|
-
root: distPath,
|
|
16467
|
-
server: {
|
|
16468
|
-
port,
|
|
16469
|
-
strictPort: true,
|
|
16470
|
-
allowedHosts: true,
|
|
16471
|
-
},
|
|
16472
|
-
preview: {
|
|
16473
|
-
port,
|
|
16474
|
-
strictPort: true,
|
|
16475
|
-
},
|
|
16476
|
-
});
|
|
16477
|
-
|
|
16478
|
-
await server.listen();
|
|
16479
|
-
console.log('READY');
|
|
16480
|
-
|
|
16481
|
-
process.stdin.resume();
|
|
16639
|
+
if (!await waitForDashboardReady(dashboardPort)) {
|
|
16640
|
+
output.stdout(
|
|
16641
|
+
`Dashboard not reachable at ${dashboardUrl}, starting a standalone server...
|
|
16482
16642
|
`
|
|
16483
|
-
|
|
16484
|
-
cwd: distPath,
|
|
16485
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
16486
|
-
});
|
|
16487
|
-
let serverOutput = "";
|
|
16488
|
-
let serverSpawnError;
|
|
16489
|
-
viteProcess.on("error", (error) => {
|
|
16490
|
-
serverSpawnError = error;
|
|
16491
|
-
serverOutput += `Dashboard server process failed: ${formatSpawnError(error)}
|
|
16492
|
-
`;
|
|
16493
|
-
});
|
|
16494
|
-
viteProcess.stdout?.on("data", (data) => {
|
|
16495
|
-
serverOutput += data.toString();
|
|
16496
|
-
});
|
|
16497
|
-
viteProcess.stderr?.on("data", (data) => {
|
|
16498
|
-
serverOutput += data.toString();
|
|
16499
|
-
});
|
|
16500
|
-
let serverReady = false;
|
|
16501
|
-
for (let i = 0; i < 100; i++) {
|
|
16502
|
-
await new Promise((resolve2) => setTimeout(resolve2, 100).unref());
|
|
16643
|
+
);
|
|
16503
16644
|
try {
|
|
16504
|
-
|
|
16505
|
-
|
|
16506
|
-
|
|
16507
|
-
|
|
16508
|
-
|
|
16509
|
-
|
|
16510
|
-
}
|
|
16511
|
-
if (serverSpawnError !== void 0) {
|
|
16512
|
-
break;
|
|
16645
|
+
await startDashboardServerProcess(dashboardPort);
|
|
16646
|
+
} catch (error) {
|
|
16647
|
+
throw new Error(
|
|
16648
|
+
`Dashboard did not become reachable at ${dashboardUrl}: ${error instanceof Error ? error.message : "unknown error"}`,
|
|
16649
|
+
{ cause: error }
|
|
16650
|
+
);
|
|
16513
16651
|
}
|
|
16514
|
-
if (!
|
|
16515
|
-
}
|
|
16516
|
-
if (!serverReady) {
|
|
16517
|
-
output.stdout(`Server output: ${serverOutput}
|
|
16518
|
-
`);
|
|
16519
|
-
if (serverSpawnError !== void 0) {
|
|
16652
|
+
if (!await waitForDashboardReady(dashboardPort)) {
|
|
16520
16653
|
throw new Error(
|
|
16521
|
-
`
|
|
16654
|
+
`Dashboard did not become reachable at ${dashboardUrl}.`
|
|
16522
16655
|
);
|
|
16523
16656
|
}
|
|
16524
|
-
throw new Error("Failed to start dashboard server");
|
|
16525
16657
|
}
|
|
16526
16658
|
output.stdout(`Dashboard ready at ${dashboardUrl}
|
|
16527
16659
|
`);
|
|
16528
16660
|
if (!options.noBrowser) {
|
|
16529
16661
|
output.stdout("Opening browser...\n");
|
|
16530
|
-
|
|
16531
|
-
|
|
16662
|
+
try {
|
|
16663
|
+
const openModule = await openPromise;
|
|
16664
|
+
await openModule.default(dashboardUrl);
|
|
16665
|
+
} catch (error) {
|
|
16666
|
+
output.stderr(
|
|
16667
|
+
`Failed to open browser automatically (${error instanceof Error ? error.message : "unknown error"}). Open ${dashboardUrl} manually.
|
|
16668
|
+
`
|
|
16669
|
+
);
|
|
16670
|
+
}
|
|
16532
16671
|
}
|
|
16533
|
-
output.stdout("Press Ctrl+C to stop\n");
|
|
16534
|
-
await new Promise((resolve2) => {
|
|
16535
|
-
const shutdown = () => {
|
|
16536
|
-
process.off("SIGINT", handleSigint);
|
|
16537
|
-
process.off("SIGTERM", handleSigterm);
|
|
16538
|
-
if (viteProcess.pid !== void 0) {
|
|
16539
|
-
try {
|
|
16540
|
-
process.kill(viteProcess.pid, "SIGTERM");
|
|
16541
|
-
} catch {
|
|
16542
|
-
}
|
|
16543
|
-
}
|
|
16544
|
-
resolve2();
|
|
16545
|
-
};
|
|
16546
|
-
const handleSigint = () => {
|
|
16547
|
-
shutdown();
|
|
16548
|
-
};
|
|
16549
|
-
const handleSigterm = () => {
|
|
16550
|
-
shutdown();
|
|
16551
|
-
};
|
|
16552
|
-
process.once("SIGINT", handleSigint);
|
|
16553
|
-
process.once("SIGTERM", handleSigterm);
|
|
16554
|
-
});
|
|
16555
16672
|
}
|
|
16556
16673
|
async function logger2(options) {
|
|
16557
16674
|
const snapshot = await getStatusSnapshot(options);
|
|
@@ -16818,6 +16935,7 @@ process.stdin.resume();
|
|
|
16818
16935
|
const health = running && daemonState !== null ? await fetchHealth(daemonState.httpPort) : null;
|
|
16819
16936
|
return {
|
|
16820
16937
|
configuredAdapters: getEnabledAdapters(config),
|
|
16938
|
+
dashboardPort: config.dashboardPort ?? DEFAULT_DASHBOARD_PORT,
|
|
16821
16939
|
daemonPid,
|
|
16822
16940
|
health,
|
|
16823
16941
|
httpPort: daemonState?.httpPort ?? config.httpPort,
|
|
@@ -17052,13 +17170,13 @@ function createProgram(dependencies = {}) {
|
|
|
17052
17170
|
"after",
|
|
17053
17171
|
`
|
|
17054
17172
|
Examples:
|
|
17055
|
-
aisnitch
|
|
17173
|
+
aisnitch (default: starts daemon + dashboard server, opens browser)
|
|
17174
|
+
aisnitch fs --dashboard-port 8080
|
|
17175
|
+
aisnitch fs --no-browser
|
|
17176
|
+
aisnitch start (TUI dashboard)
|
|
17056
17177
|
aisnitch start --daemon
|
|
17057
17178
|
aisnitch start --view full-data
|
|
17058
17179
|
aisnitch start --mock
|
|
17059
|
-
aisnitch fs
|
|
17060
|
-
aisnitch fs --daemon
|
|
17061
|
-
aisnitch fs --dashboard-port 8080
|
|
17062
17180
|
aisnitch status
|
|
17063
17181
|
aisnitch attach
|
|
17064
17182
|
aisnitch attach --view full-data
|
|
@@ -17101,7 +17219,7 @@ function addCommonOptions(command) {
|
|
|
17101
17219
|
}
|
|
17102
17220
|
function addStartCommand(program, runtime) {
|
|
17103
17221
|
addCommonOptions(
|
|
17104
|
-
program.command("start"
|
|
17222
|
+
program.command("start").description("Open the AISnitch TUI dashboard and manage the daemon").option("--daemon", "Run AISnitch as a detached daemon").option(
|
|
17105
17223
|
"--mock [tool]",
|
|
17106
17224
|
'Inject deterministic mock events (defaults to "all" when no tool is specified)',
|
|
17107
17225
|
wrapOptionParser(parseMockToolSelection)
|
|
@@ -17133,6 +17251,10 @@ function addStartCommand(program, runtime) {
|
|
|
17133
17251
|
"--http-port <port>",
|
|
17134
17252
|
"Override the HTTP hook port",
|
|
17135
17253
|
wrapOptionParser(parsePortOption)
|
|
17254
|
+
).option(
|
|
17255
|
+
"--dashboard-port <port>",
|
|
17256
|
+
"Override the web dashboard port",
|
|
17257
|
+
wrapOptionParser(parsePortOption)
|
|
17136
17258
|
).option(
|
|
17137
17259
|
"--log-level <level>",
|
|
17138
17260
|
"Override the runtime log level",
|
|
@@ -17217,9 +17339,9 @@ function addAttachCommand(program, runtime) {
|
|
|
17217
17339
|
}
|
|
17218
17340
|
function addFullscreenCommand(program, runtime) {
|
|
17219
17341
|
addCommonOptions(
|
|
17220
|
-
program.command("fs").description("
|
|
17342
|
+
program.command("fs", { isDefault: true }).description("Start the daemon, the dashboard server, and open the web dashboard in browser").alias("fullscreen").option(
|
|
17221
17343
|
"--daemon",
|
|
17222
|
-
"
|
|
17344
|
+
"Deprecated: daemon is now started automatically when needed"
|
|
17223
17345
|
).option(
|
|
17224
17346
|
"--dashboard-port <port>",
|
|
17225
17347
|
"Port for the dashboard server (default: 5174)",
|