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/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.24";
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.wsUrl}` : `Daemon not active \xB7 ${daemon.wsUrl}`;
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(daemonPathOptions);
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
- const snapshot = await getStatusSnapshot(options);
16436
- if (!snapshot.running && options.daemon) {
16629
+ let snapshot = await getStatusSnapshot(options);
16630
+ if (!snapshot.running) {
16437
16631
  output.stdout("Starting daemon...\n");
16438
- await startDetachedDaemon(options);
16439
- }
16440
- if (!snapshot.running && !options.daemon) {
16441
- throw new Error(
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 ?? 5174;
16637
+ const dashboardPort = options.dashboardPort ?? snapshot.dashboardPort;
16451
16638
  const dashboardUrl = `http://127.0.0.1:${dashboardPort}`;
16452
- const distPath = join18(process.cwd(), "examples", "fullscreen-dashboard", "dist");
16453
- output.stdout(`Starting dashboard server on port ${dashboardPort}...
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
- const response = await fetchImplementation(dashboardUrl);
16505
- if (response.ok) {
16506
- serverReady = true;
16507
- break;
16508
- }
16509
- } catch {
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 (!viteProcess.pid) break;
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
- `Failed to start dashboard server process with ${nodeExecutable}: ${formatSpawnError(serverSpawnError)}`
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
- const openModule = await openPromise;
16531
- await openModule.default(dashboardUrl);
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 start
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", { isDefault: true }).description("Open the AISnitch dashboard and manage the daemon").option("--daemon", "Run AISnitch as a detached daemon").option(
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("Open the fullscreen web dashboard in browser (starts daemon if needed with --daemon)").alias("fullscreen").option(
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
- "Start the daemon automatically if not running"
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)",