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 CHANGED
@@ -82,10 +82,16 @@ brew install aisnitch
82
82
  ### 2. Run
83
83
 
84
84
  ```bash
85
- aisnitch start
85
+ aisnitch
86
86
  ```
87
87
 
88
- That's it! The dashboard opens with live events from all configured tools.
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 fs # Open dashboard (auto-starts daemon if needed)
226
- aisnitch fs --daemon # Start daemon + open dashboard
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 fs # Open web dashboard
343
- aisnitch fs --daemon # Start daemon + open
344
- aisnitch fs --dashboard-port 8080 # Custom port
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
 
@@ -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.25";
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.wsUrl}` : `Daemon not active \xB7 ${daemon.wsUrl}`;
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(daemonPathOptions);
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
- const snapshot = await getStatusSnapshot(options);
16496
- if (!snapshot.running && options.daemon) {
16655
+ let snapshot = await getStatusSnapshot(options);
16656
+ if (!snapshot.running) {
16497
16657
  output.stdout("Starting daemon...\n");
16498
- await startDetachedDaemon(options);
16499
- }
16500
- if (!snapshot.running && !options.daemon) {
16501
- throw new Error(
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 ?? 5174;
16663
+ const dashboardPort = options.dashboardPort ?? snapshot.dashboardPort;
16511
16664
  const dashboardUrl = `http://127.0.0.1:${dashboardPort}`;
16512
- const distPath = await resolveDashboardDistPath();
16513
- output.stdout(`Starting dashboard server on port ${dashboardPort}...
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
- const response = await fetchImplementation(dashboardUrl);
16608
- if (response.ok) {
16609
- serverReady = true;
16610
- break;
16611
- }
16612
- } catch {
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 (!viteProcess.pid) break;
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
- `Failed to start dashboard server process with ${nodeExecutable}: ${formatSpawnError(serverSpawnError)}`
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
- const openModule = await openPromise;
16634
- await openModule.default(dashboardUrl);
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 start
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", { isDefault: true }).description("Open the AISnitch dashboard and manage the daemon").option("--daemon", "Run AISnitch as a detached daemon").option(
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("Open the fullscreen web dashboard in browser (starts daemon if needed with --daemon)").alias("fullscreen").option(
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
- "Start the daemon automatically if not running"
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)",