aisnitch 0.2.22 → 0.2.24

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.22";
746
+ var AISNITCH_VERSION = "0.2.24";
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
@@ -1927,6 +1927,9 @@ var OpenClawSetup = class {
1927
1927
  hookHandlerPath;
1928
1928
  hookUrl;
1929
1929
  openclawHomeDirectory;
1930
+ pluginDirectory;
1931
+ pluginFilePath;
1932
+ pluginDocumentPath;
1930
1933
  toolName = "openclaw";
1931
1934
  constructor(httpPort, dependencies = {}) {
1932
1935
  this.binaryExists = dependencies.binaryExists ?? isBinaryAvailable;
@@ -1940,6 +1943,13 @@ var OpenClawSetup = class {
1940
1943
  this.hookDocumentPath = join2(this.hookDirectory, "HOOK.md");
1941
1944
  this.hookHandlerPath = join2(this.hookDirectory, "handler.ts");
1942
1945
  this.hookUrl = `http://localhost:${httpPort}/hooks/openclaw`;
1946
+ this.pluginDirectory = join2(
1947
+ this.openclawHomeDirectory,
1948
+ "plugins",
1949
+ "aisnitch-monitor"
1950
+ );
1951
+ this.pluginFilePath = join2(this.pluginDirectory, "index.ts");
1952
+ this.pluginDocumentPath = join2(this.pluginDirectory, "README.md");
1943
1953
  }
1944
1954
  async detect() {
1945
1955
  return await this.binaryExists("openclaw") || await fileExists(this.openclawHomeDirectory) || await fileExists("/Applications/OpenClaw.app");
@@ -1951,9 +1961,13 @@ var OpenClawSetup = class {
1951
1961
  const currentConfigContent = await readOptionalFile(this.configPath);
1952
1962
  const currentHookDocument = await readOptionalFile(this.hookDocumentPath);
1953
1963
  const currentHookHandler = await readOptionalFile(this.hookHandlerPath);
1964
+ const currentPluginDocument = await readOptionalFile(this.pluginDocumentPath);
1965
+ const currentPluginFile = await readOptionalFile(this.pluginFilePath);
1954
1966
  const nextConfigContent = this.buildNextConfigContent(currentConfigContent);
1955
1967
  const nextHookDocument = buildOpenClawHookDocumentSource();
1956
1968
  const nextHookHandler = buildOpenClawHookHandlerSource(this.hookUrl);
1969
+ const nextPluginDocument = buildOpenClawPluginDocumentSource();
1970
+ const nextPluginFile = buildOpenClawPluginSource(this.hookUrl);
1957
1971
  return [
1958
1972
  renderColoredDiff(
1959
1973
  this.configPath,
@@ -1971,6 +1985,18 @@ var OpenClawSetup = class {
1971
1985
  this.hookHandlerPath,
1972
1986
  currentHookHandler,
1973
1987
  nextHookHandler
1988
+ ),
1989
+ "",
1990
+ renderColoredDiff(
1991
+ this.pluginDocumentPath,
1992
+ currentPluginDocument,
1993
+ nextPluginDocument
1994
+ ),
1995
+ "",
1996
+ renderColoredDiff(
1997
+ this.pluginFilePath,
1998
+ currentPluginFile,
1999
+ nextPluginFile
1974
2000
  )
1975
2001
  ].join("\n");
1976
2002
  }
@@ -1978,11 +2004,16 @@ var OpenClawSetup = class {
1978
2004
  const currentConfigContent = await readOptionalFile(this.configPath);
1979
2005
  const currentHookDocument = await readOptionalFile(this.hookDocumentPath);
1980
2006
  const currentHookHandler = await readOptionalFile(this.hookHandlerPath);
2007
+ const currentPluginDocument = await readOptionalFile(this.pluginDocumentPath);
2008
+ const currentPluginFile = await readOptionalFile(this.pluginFilePath);
1981
2009
  const nextConfigContent = this.buildNextConfigContent(currentConfigContent);
1982
2010
  const nextHookDocument = buildOpenClawHookDocumentSource();
1983
2011
  const nextHookHandler = buildOpenClawHookHandlerSource(this.hookUrl);
2012
+ const nextPluginDocument = buildOpenClawPluginDocumentSource();
2013
+ const nextPluginFile = buildOpenClawPluginSource(this.hookUrl);
1984
2014
  await mkdir2(dirname2(this.configPath), { recursive: true });
1985
2015
  await mkdir2(this.hookDirectory, { recursive: true });
2016
+ await mkdir2(this.pluginDirectory, { recursive: true });
1986
2017
  if (currentConfigContent !== null) {
1987
2018
  await copyFile(this.configPath, this.getBackupPath(this.configPath));
1988
2019
  }
@@ -1998,14 +2029,30 @@ var OpenClawSetup = class {
1998
2029
  this.getBackupPath(this.hookHandlerPath)
1999
2030
  );
2000
2031
  }
2032
+ if (currentPluginDocument !== null) {
2033
+ await copyFile(
2034
+ this.pluginDocumentPath,
2035
+ this.getBackupPath(this.pluginDocumentPath)
2036
+ );
2037
+ }
2038
+ if (currentPluginFile !== null) {
2039
+ await copyFile(
2040
+ this.pluginFilePath,
2041
+ this.getBackupPath(this.pluginFilePath)
2042
+ );
2043
+ }
2001
2044
  await writeFile2(this.configPath, nextConfigContent, "utf8");
2002
2045
  await writeFile2(this.hookDocumentPath, nextHookDocument, "utf8");
2003
2046
  await writeFile2(this.hookHandlerPath, nextHookHandler, "utf8");
2047
+ await writeFile2(this.pluginDocumentPath, nextPluginDocument, "utf8");
2048
+ await writeFile2(this.pluginFilePath, nextPluginFile, "utf8");
2004
2049
  }
2005
2050
  async revert() {
2006
2051
  await restoreBackupOrRemove(this.configPath);
2007
2052
  await restoreBackupOrRemove(this.hookDocumentPath);
2008
2053
  await restoreBackupOrRemove(this.hookHandlerPath);
2054
+ await restoreBackupOrRemove(this.pluginDocumentPath);
2055
+ await restoreBackupOrRemove(this.pluginFilePath);
2009
2056
  }
2010
2057
  buildNextConfigContent(currentContent) {
2011
2058
  const parsedConfig = parseOpenClawSettings(currentContent);
@@ -2027,6 +2074,15 @@ var OpenClawSetup = class {
2027
2074
  enabled: true
2028
2075
  }
2029
2076
  };
2077
+ const currentPlugins = parsedConfig.plugins ?? {};
2078
+ const currentPluginEntries = currentPlugins.entries ?? {};
2079
+ const nextPluginEntries = {
2080
+ ...currentPluginEntries,
2081
+ "aisnitch-monitor": {
2082
+ ...currentPluginEntries["aisnitch-monitor"] ?? {},
2083
+ enabled: true
2084
+ }
2085
+ };
2030
2086
  const nextConfig = {
2031
2087
  ...parsedConfig,
2032
2088
  hooks: {
@@ -2036,6 +2092,10 @@ var OpenClawSetup = class {
2036
2092
  enabled: true,
2037
2093
  entries: nextEntries
2038
2094
  }
2095
+ },
2096
+ plugins: {
2097
+ ...currentPlugins,
2098
+ entries: nextPluginEntries
2039
2099
  }
2040
2100
  };
2041
2101
  return `${JSON.stringify(nextConfig, null, 2)}
@@ -2870,6 +2930,231 @@ export default async function aisnitchForward(event) {
2870
2930
  }
2871
2931
  `;
2872
2932
  }
2933
+ function buildOpenClawPluginDocumentSource() {
2934
+ return `# AISnitch Monitor Plugin
2935
+
2936
+ \u{1F4D6} This managed plugin uses OpenClaw's Plugin SDK to forward rich real-time
2937
+ events to the local AISnitch HTTP receiver. Unlike the internal hook handler,
2938
+ the plugin has access to tool-level hooks (before_tool_call, after_tool_call),
2939
+ model-level hooks (model_call_started, model_call_ended), and agent turn hooks
2940
+ (agent_end, before_agent_run) \u2014 providing maximum observability fidelity.
2941
+
2942
+ ## Hooked Events
2943
+
2944
+ | Hook | AISnitch Event |
2945
+ |:---|:---|
2946
+ | gateway_start | session.start |
2947
+ | gateway_stop | session.end |
2948
+ | before_agent_run | task.start |
2949
+ | agent_end | task.complete |
2950
+ | before_tool_call | agent.coding / agent.tool_call |
2951
+ | after_tool_call | agent.coding / agent.tool_call + results |
2952
+ | model_call_started | agent.thinking |
2953
+ | model_call_ended | agent.streaming |
2954
+ | before_compaction | agent.compact |
2955
+ | after_compaction | agent.compact |
2956
+ | message_received | context info |
2957
+
2958
+ ## Managed by AISnitch
2959
+
2960
+ This file is managed by \`aisnitch setup openclaw\`. Re-running setup will
2961
+ overwrite it. Use \`aisnitch setup openclaw --revert\` to remove it.
2962
+ `;
2963
+ }
2964
+ function buildOpenClawPluginSource(hookUrl) {
2965
+ return `/**
2966
+ * AISnitch OpenClaw Plugin
2967
+ *
2968
+ * \u{1F4D6} Uses OpenClaw's Plugin SDK to forward rich real-time events to AISnitch.
2969
+ * Provides maximum visibility into tool calls, model usage, agent turns,
2970
+ * and lifecycle events \u2014 far beyond what passive file-watching can achieve.
2971
+ *
2972
+ * Managed by \`aisnitch setup openclaw\`. Re-running setup overwrites this file.
2973
+ */
2974
+
2975
+ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
2976
+
2977
+ const AISNITCH_ENDPOINT = ${JSON.stringify(hookUrl)};
2978
+
2979
+ async function postToAISnitch(payload: Record<string, unknown>): Promise<void> {
2980
+ try {
2981
+ await fetch(AISNITCH_ENDPOINT, {
2982
+ method: "POST",
2983
+ headers: { "content-type": "application/json" },
2984
+ body: JSON.stringify(payload),
2985
+ });
2986
+ } catch {
2987
+ // Silently ignore transport errors so OpenClaw keeps running.
2988
+ }
2989
+ }
2990
+
2991
+ function getSessionKey(ctx: Record<string, unknown> | undefined): string | undefined {
2992
+ if (!ctx) return undefined;
2993
+ return (
2994
+ (typeof ctx.sessionKey === "string" && ctx.sessionKey.length > 0 ? ctx.sessionKey : undefined) ??
2995
+ (typeof ctx.sessionId === "string" && ctx.sessionId.length > 0 ? ctx.sessionId : undefined)
2996
+ );
2997
+ }
2998
+
2999
+ function getWorkspaceDir(ctx: Record<string, unknown> | undefined): string | undefined {
3000
+ if (!ctx) return undefined;
3001
+ return (
3002
+ (typeof ctx.workspaceDir === "string" && ctx.workspaceDir.length > 0 ? ctx.workspaceDir : undefined) ??
3003
+ (typeof ctx.cwd === "string" && ctx.cwd.length > 0 ? ctx.cwd : undefined)
3004
+ );
3005
+ }
3006
+
3007
+ export default definePluginEntry({
3008
+ id: "aisnitch-monitor",
3009
+ name: "AISnitch Monitor",
3010
+ register(api) {
3011
+ // \u2500\u2500 Gateway lifecycle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3012
+
3013
+ api.on("gateway_start", (event) => {
3014
+ const ctx = event.ctx as Record<string, unknown> | undefined;
3015
+ void postToAISnitch({
3016
+ event: "gateway:startup",
3017
+ sessionKey: getSessionKey(ctx),
3018
+ context: ctx ?? {},
3019
+ timestamp: new Date().toISOString(),
3020
+ });
3021
+ });
3022
+
3023
+ api.on("gateway_stop", () => {
3024
+ void postToAISnitch({
3025
+ event: "gateway:shutdown",
3026
+ timestamp: new Date().toISOString(),
3027
+ });
3028
+ });
3029
+
3030
+ // \u2500\u2500 Agent turn lifecycle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3031
+
3032
+ api.on("before_agent_run", (event) => {
3033
+ const ctx = event.ctx as Record<string, unknown> | undefined;
3034
+ void postToAISnitch({
3035
+ event: "command:new",
3036
+ sessionKey: getSessionKey(ctx),
3037
+ context: {
3038
+ ...ctx,
3039
+ message: event.prompt ?? event.message ?? (ctx as any)?.bodyForAgent,
3040
+ },
3041
+ timestamp: new Date().toISOString(),
3042
+ });
3043
+ });
3044
+
3045
+ api.on("agent_end", (event) => {
3046
+ const ctx = event.ctx as Record<string, unknown> | undefined;
3047
+ void postToAISnitch({
3048
+ event: "command:stop",
3049
+ sessionKey: getSessionKey(ctx),
3050
+ context: ctx ?? {},
3051
+ success: event.success,
3052
+ durationMs: event.durationMs,
3053
+ timestamp: new Date().toISOString(),
3054
+ });
3055
+ });
3056
+
3057
+ // \u2500\u2500 Model calls \u2192 thinking / streaming \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3058
+
3059
+ api.on("model_call_started", (event) => {
3060
+ const ctx = event.ctx as Record<string, unknown> | undefined;
3061
+ void postToAISnitch({
3062
+ event: "model_call_started",
3063
+ sessionKey: getSessionKey(ctx),
3064
+ cwd: getWorkspaceDir(ctx),
3065
+ context: ctx ?? {},
3066
+ model: event.model,
3067
+ provider: event.provider,
3068
+ timestamp: new Date().toISOString(),
3069
+ });
3070
+ });
3071
+
3072
+ api.on("model_call_ended", (event) => {
3073
+ const ctx = event.ctx as Record<string, unknown> | undefined;
3074
+ void postToAISnitch({
3075
+ event: "model_call_ended",
3076
+ sessionKey: getSessionKey(ctx),
3077
+ cwd: getWorkspaceDir(ctx),
3078
+ context: ctx ?? {},
3079
+ model: event.model,
3080
+ durationMs: event.durationMs,
3081
+ outcome: event.outcome,
3082
+ timestamp: new Date().toISOString(),
3083
+ });
3084
+ });
3085
+
3086
+ // \u2500\u2500 Tool calls \u2014 richest signal for AISnitch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3087
+
3088
+ api.on("before_tool_call", (event) => {
3089
+ const ctx = event.ctx as Record<string, unknown> | undefined;
3090
+ void postToAISnitch({
3091
+ event: "before_tool_call",
3092
+ sessionKey: getSessionKey(ctx),
3093
+ cwd: getWorkspaceDir(ctx),
3094
+ context: ctx ?? {},
3095
+ toolName: event.toolName,
3096
+ toolInput: event.params,
3097
+ timestamp: new Date().toISOString(),
3098
+ });
3099
+ });
3100
+
3101
+ api.on("after_tool_call", (event) => {
3102
+ const ctx = event.ctx as Record<string, unknown> | undefined;
3103
+ void postToAISnitch({
3104
+ event: "tool_result_persist",
3105
+ sessionKey: getSessionKey(ctx),
3106
+ cwd: getWorkspaceDir(ctx),
3107
+ context: ctx ?? {},
3108
+ toolName: event.toolName,
3109
+ toolInput: event.params,
3110
+ error: event.error,
3111
+ duration: event.duration,
3112
+ result: event.result,
3113
+ timestamp: new Date().toISOString(),
3114
+ });
3115
+ });
3116
+
3117
+ // \u2500\u2500 Compaction \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3118
+
3119
+ api.on("before_compaction", (event) => {
3120
+ const ctx = event.ctx as Record<string, unknown> | undefined;
3121
+ void postToAISnitch({
3122
+ event: "before_compaction",
3123
+ sessionKey: getSessionKey(ctx),
3124
+ cwd: getWorkspaceDir(ctx),
3125
+ context: ctx ?? {},
3126
+ timestamp: new Date().toISOString(),
3127
+ });
3128
+ });
3129
+
3130
+ api.on("after_compaction", (event) => {
3131
+ const ctx = event.ctx as Record<string, unknown> | undefined;
3132
+ void postToAISnitch({
3133
+ event: "session:compact:after",
3134
+ sessionKey: getSessionKey(ctx),
3135
+ cwd: getWorkspaceDir(ctx),
3136
+ context: ctx ?? {},
3137
+ timestamp: new Date().toISOString(),
3138
+ });
3139
+ });
3140
+
3141
+ // \u2500\u2500 Messages \u2014 user input context \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3142
+
3143
+ api.on("message_received", (event) => {
3144
+ const ctx = event.ctx as Record<string, unknown> | undefined;
3145
+ void postToAISnitch({
3146
+ event: "message:received",
3147
+ sessionKey: getSessionKey(ctx),
3148
+ cwd: getWorkspaceDir(ctx),
3149
+ context: ctx ?? {},
3150
+ message: event.message,
3151
+ timestamp: new Date().toISOString(),
3152
+ });
3153
+ });
3154
+ },
3155
+ });
3156
+ `;
3157
+ }
2873
3158
  function parseOpenClawSettings(currentContent) {
2874
3159
  if (currentContent === null || currentContent.trim().length === 0) {
2875
3160
  return {};
@@ -3182,8 +3467,8 @@ function toConfigPathOptions(options) {
3182
3467
 
3183
3468
  // src/cli/runtime.ts
3184
3469
  import { execFile as execFileCallback14, spawn as spawnChildProcess2 } from "child_process";
3185
- import { closeSync, openSync } from "fs";
3186
- import { mkdtemp, readFile as readFile15, rename, rm as rm4, writeFile as writeFile5 } from "fs/promises";
3470
+ 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";
3187
3472
  import { createConnection as createConnection3 } from "net";
3188
3473
  import { tmpdir } from "os";
3189
3474
  import { basename as basename12, join as join18 } from "path";
@@ -10725,6 +11010,7 @@ var OpenClawAdapter = class extends BaseAdapter {
10725
11010
  displayName = "OpenClaw";
10726
11011
  name = "openclaw";
10727
11012
  strategies = [
11013
+ "plugin",
10728
11014
  "hooks",
10729
11015
  "log-watch",
10730
11016
  "jsonl-watch",
@@ -10944,11 +11230,32 @@ var OpenClawAdapter = class extends BaseAdapter {
10944
11230
  await this.emitOpenClawSessionEnd(sharedData, context);
10945
11231
  return;
10946
11232
  }
10947
- case "session:compact:before":
10948
- case "before_compaction": {
11233
+ case "model_call_started": {
10949
11234
  await this.ensureSessionStarted(sharedData, context);
10950
11235
  this.clearThinking(sessionId);
10951
- await this.emitStateChange("agent.compact", sharedData, context);
11236
+ await this.emitStateChange("agent.thinking", sharedData, context);
11237
+ return;
11238
+ }
11239
+ case "model_call_ended": {
11240
+ await this.ensureSessionStarted(sharedData, context);
11241
+ await this.emitStateChange("agent.streaming", {
11242
+ ...sharedData,
11243
+ raw: {
11244
+ ...sharedData.raw ?? {},
11245
+ durationMs: getNumber7(payload, "durationMs"),
11246
+ outcome: getString9(payload, "outcome"),
11247
+ source: "plugin"
11248
+ }
11249
+ }, context);
11250
+ return;
11251
+ }
11252
+ case "before_tool_call": {
11253
+ await this.ensureSessionStarted(sharedData, context);
11254
+ await this.emitStateChange(
11255
+ isOpenClawCodingTool(sharedData.toolName, sharedData.toolInput) ? "agent.coding" : "agent.tool_call",
11256
+ sharedData,
11257
+ context
11258
+ );
10952
11259
  return;
10953
11260
  }
10954
11261
  case "tool_result_persist": {
@@ -10961,6 +11268,13 @@ var OpenClawAdapter = class extends BaseAdapter {
10961
11268
  this.scheduleThinking(sessionId, sharedData, context, POST_TOOL_THINKING_DELAY_MS);
10962
11269
  return;
10963
11270
  }
11271
+ case "session:compact:before":
11272
+ case "before_compaction": {
11273
+ await this.ensureSessionStarted(sharedData, context);
11274
+ this.clearThinking(sessionId);
11275
+ await this.emitStateChange("agent.compact", sharedData, context);
11276
+ return;
11277
+ }
10964
11278
  case "message:received":
10965
11279
  case "message:preprocessed":
10966
11280
  case "session:compact:after":
@@ -11420,6 +11734,7 @@ function buildOpenClawEventData(payload) {
11420
11734
  return {
11421
11735
  activeFile: extractOpenClawActiveFile(payload) ?? toolInput?.filePath,
11422
11736
  cwd,
11737
+ duration: getNumber7(payload, "duration") ?? getNumber7(payload, "durationMs"),
11423
11738
  errorMessage: extractOpenClawErrorMessage(payload),
11424
11739
  errorType: inferOpenClawErrorType(payload),
11425
11740
  model: extractOpenClawModel(payload),
@@ -11484,7 +11799,7 @@ function extractOpenClawToolInput(payload) {
11484
11799
  };
11485
11800
  }
11486
11801
  function extractOpenClawErrorMessage(payload) {
11487
- return getString9(payload, "error") ?? getString9(payload, "message") ?? getString9(getRecord8(payload.error), "message") ?? getString9(getRecord8(payload.result), "error");
11802
+ return getString9(payload, "error") ?? getString9(payload, "errorMessage") ?? getString9(payload, "message") ?? getString9(getRecord8(payload.error), "message") ?? getString9(getRecord8(payload.result), "error") ?? getString9(getRecord8(payload.result), "message");
11488
11803
  }
11489
11804
  function inferOpenClawErrorType(payload) {
11490
11805
  const errorMessage = extractOpenClawErrorMessage(payload);
@@ -15605,6 +15920,20 @@ var DAEMON_READY_POLL_INTERVAL_MS = 100;
15605
15920
  var DAEMON_STOP_TIMEOUT_MS = 4e3;
15606
15921
  var DAEMON_LOG_MAX_BYTES = 5 * 1024 * 1024;
15607
15922
  var LAUNCH_AGENT_LABEL = "com.aisnitch.daemon";
15923
+ async function resolveNodeExecutable() {
15924
+ try {
15925
+ await access4(process.execPath, fsConstants4.X_OK);
15926
+ return process.execPath;
15927
+ } catch {
15928
+ return "node";
15929
+ }
15930
+ }
15931
+ function formatSpawnError(error) {
15932
+ if (error instanceof Error) {
15933
+ return error.message;
15934
+ }
15935
+ return String(error);
15936
+ }
15608
15937
  function createCliRuntime(dependencies = {}) {
15609
15938
  const output = dependencies.output ?? createProcessOutput();
15610
15939
  const fetchImplementation = dependencies.fetch ?? globalThis.fetch;
@@ -16104,11 +16433,11 @@ function createCliRuntime(dependencies = {}) {
16104
16433
  }
16105
16434
  async function fullscreen(options) {
16106
16435
  const snapshot = await getStatusSnapshot(options);
16107
- if (!snapshot.running && options.daemonMode) {
16436
+ if (!snapshot.running && options.daemon) {
16108
16437
  output.stdout("Starting daemon...\n");
16109
16438
  await startDetachedDaemon(options);
16110
16439
  }
16111
- if (!snapshot.running && !options.daemonMode) {
16440
+ if (!snapshot.running && !options.daemon) {
16112
16441
  throw new Error(
16113
16442
  "AISnitch daemon is not running. Start one with `aisnitch start --daemon` or use `aisnitch fs --daemon` to start and open the dashboard."
16114
16443
  );
@@ -16123,7 +16452,8 @@ function createCliRuntime(dependencies = {}) {
16123
16452
  const distPath = join18(process.cwd(), "examples", "fullscreen-dashboard", "dist");
16124
16453
  output.stdout(`Starting dashboard server on port ${dashboardPort}...
16125
16454
  `);
16126
- const viteProcess = spawnImplementation(process.execPath, [
16455
+ const nodeExecutable = await resolveNodeExecutable();
16456
+ const viteProcess = spawnImplementation(nodeExecutable, [
16127
16457
  "-e",
16128
16458
  `
16129
16459
  import { createServer } from 'vite';
@@ -16155,6 +16485,12 @@ process.stdin.resume();
16155
16485
  stdio: ["pipe", "pipe", "pipe"]
16156
16486
  });
16157
16487
  let serverOutput = "";
16488
+ let serverSpawnError;
16489
+ viteProcess.on("error", (error) => {
16490
+ serverSpawnError = error;
16491
+ serverOutput += `Dashboard server process failed: ${formatSpawnError(error)}
16492
+ `;
16493
+ });
16158
16494
  viteProcess.stdout?.on("data", (data) => {
16159
16495
  serverOutput += data.toString();
16160
16496
  });
@@ -16172,11 +16508,19 @@ process.stdin.resume();
16172
16508
  }
16173
16509
  } catch {
16174
16510
  }
16511
+ if (serverSpawnError !== void 0) {
16512
+ break;
16513
+ }
16175
16514
  if (!viteProcess.pid) break;
16176
16515
  }
16177
16516
  if (!serverReady) {
16178
16517
  output.stdout(`Server output: ${serverOutput}
16179
16518
  `);
16519
+ if (serverSpawnError !== void 0) {
16520
+ throw new Error(
16521
+ `Failed to start dashboard server process with ${nodeExecutable}: ${formatSpawnError(serverSpawnError)}`
16522
+ );
16523
+ }
16180
16524
  throw new Error("Failed to start dashboard server");
16181
16525
  }
16182
16526
  output.stdout(`Dashboard ready at ${dashboardUrl}