aisnitch 0.2.2 → 0.2.4
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 +314 -666
- package/dist/cli/index.cjs +431 -63
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +431 -63
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +199 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -2
- package/dist/index.d.ts +14 -2
- package/dist/index.js +199 -74
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { Command, InvalidArgumentError } from "commander";
|
|
|
8
8
|
|
|
9
9
|
// src/package-info.ts
|
|
10
10
|
var AISNITCH_PACKAGE_NAME = "aisnitch";
|
|
11
|
-
var AISNITCH_VERSION = "0.2.
|
|
11
|
+
var AISNITCH_VERSION = "0.2.3";
|
|
12
12
|
var AISNITCH_DESCRIPTION = "Universal bridge for AI coding tool activity \u2014 capture, normalize, stream.";
|
|
13
13
|
|
|
14
14
|
// src/core/events/schema.ts
|
|
@@ -3220,8 +3220,16 @@ var EventBus = class {
|
|
|
3220
3220
|
},
|
|
3221
3221
|
"Published event"
|
|
3222
3222
|
);
|
|
3223
|
-
|
|
3224
|
-
|
|
3223
|
+
try {
|
|
3224
|
+
this.emitter.emit("event", parsedEvent.data);
|
|
3225
|
+
} catch (error) {
|
|
3226
|
+
logger.warn({ error, eventType: parsedEvent.data.type }, "\u{1F4D6} Error in EventBus global subscriber");
|
|
3227
|
+
}
|
|
3228
|
+
try {
|
|
3229
|
+
this.emitter.emit(`event:${parsedEvent.data.type}`, parsedEvent.data);
|
|
3230
|
+
} catch (error) {
|
|
3231
|
+
logger.warn({ error, eventType: parsedEvent.data.type }, "\u{1F4D6} Error in EventBus typed subscriber");
|
|
3232
|
+
}
|
|
3225
3233
|
return true;
|
|
3226
3234
|
}
|
|
3227
3235
|
/**
|
|
@@ -3487,6 +3495,7 @@ var WSServer = class {
|
|
|
3487
3495
|
});
|
|
3488
3496
|
socket.on("error", (error) => {
|
|
3489
3497
|
logger.warn({ error }, "WebSocket consumer error");
|
|
3498
|
+
this.consumers.delete(socket);
|
|
3490
3499
|
});
|
|
3491
3500
|
const welcomeMessage = {
|
|
3492
3501
|
type: "welcome",
|
|
@@ -3643,10 +3652,17 @@ var HTTPReceiver = class {
|
|
|
3643
3652
|
}
|
|
3644
3653
|
async handleRequest(request, response, options) {
|
|
3645
3654
|
this.requestCount += 1;
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3655
|
+
let requestUrl;
|
|
3656
|
+
try {
|
|
3657
|
+
requestUrl = new URL(
|
|
3658
|
+
request.url ?? "/",
|
|
3659
|
+
`http://${this.host}:${this.port ?? options.port}`
|
|
3660
|
+
);
|
|
3661
|
+
} catch {
|
|
3662
|
+
this.invalidRequestCount += 1;
|
|
3663
|
+
this.sendJson(response, 400, { error: "malformed request url" });
|
|
3664
|
+
return;
|
|
3665
|
+
}
|
|
3650
3666
|
if (request.method === "GET" && requestUrl.pathname === "/health") {
|
|
3651
3667
|
this.sendJson(response, 200, options.getHealthSnapshot());
|
|
3652
3668
|
return;
|
|
@@ -3987,15 +4003,24 @@ var BaseAdapter = class {
|
|
|
3987
4003
|
cwd: data.cwd ?? context.cwd
|
|
3988
4004
|
}
|
|
3989
4005
|
});
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
4006
|
+
let published;
|
|
4007
|
+
try {
|
|
4008
|
+
published = await this.publishEventImplementation(event, {
|
|
4009
|
+
cwd: context.cwd,
|
|
4010
|
+
env: context.env,
|
|
4011
|
+
hookPayload: context.hookPayload,
|
|
4012
|
+
pid: context.pid,
|
|
4013
|
+
sessionId,
|
|
4014
|
+
source: context.source,
|
|
4015
|
+
transcriptPath: context.transcriptPath
|
|
4016
|
+
});
|
|
4017
|
+
} catch (error) {
|
|
4018
|
+
logger.error(
|
|
4019
|
+
{ error, eventType: type, adapter: this.name, sessionId },
|
|
4020
|
+
"\u{1F4D6} Failed to publish event \u2014 swallowing to prevent daemon crash"
|
|
4021
|
+
);
|
|
4022
|
+
published = false;
|
|
4023
|
+
}
|
|
3999
4024
|
if (published) {
|
|
4000
4025
|
this.eventsEmitted += 1;
|
|
4001
4026
|
}
|
|
@@ -9123,22 +9148,40 @@ var AdapterRegistry = class {
|
|
|
9123
9148
|
}
|
|
9124
9149
|
/**
|
|
9125
9150
|
* Starts every adapter enabled in the current AISnitch config.
|
|
9151
|
+
* 📖 Each adapter is started independently — one failure does not prevent
|
|
9152
|
+
* the others from starting.
|
|
9126
9153
|
*/
|
|
9127
9154
|
async startAll(config) {
|
|
9128
9155
|
for (const adapter of this.list()) {
|
|
9129
9156
|
if (config.adapters[adapter.name]?.enabled !== true) {
|
|
9130
9157
|
continue;
|
|
9131
9158
|
}
|
|
9132
|
-
|
|
9159
|
+
try {
|
|
9160
|
+
await adapter.start();
|
|
9161
|
+
} catch (error) {
|
|
9162
|
+
logger.error(
|
|
9163
|
+
{ error, adapter: adapter.name },
|
|
9164
|
+
`\u{1F4D6} Failed to start adapter "${adapter.name}" \u2014 skipping`
|
|
9165
|
+
);
|
|
9166
|
+
}
|
|
9133
9167
|
}
|
|
9134
9168
|
}
|
|
9135
9169
|
/**
|
|
9136
9170
|
* Stops every adapter in reverse registration order.
|
|
9171
|
+
* 📖 Each adapter is stopped independently — one failure does not prevent
|
|
9172
|
+
* the others from being stopped.
|
|
9137
9173
|
*/
|
|
9138
9174
|
async stopAll() {
|
|
9139
9175
|
const adapters = this.list().reverse();
|
|
9140
9176
|
for (const adapter of adapters) {
|
|
9141
|
-
|
|
9177
|
+
try {
|
|
9178
|
+
await adapter.stop();
|
|
9179
|
+
} catch (error) {
|
|
9180
|
+
logger.warn(
|
|
9181
|
+
{ error, adapter: adapter.name },
|
|
9182
|
+
`\u{1F4D6} Error stopping adapter "${adapter.name}" \u2014 continuing`
|
|
9183
|
+
);
|
|
9184
|
+
}
|
|
9142
9185
|
}
|
|
9143
9186
|
}
|
|
9144
9187
|
};
|
|
@@ -9243,26 +9286,32 @@ var Pipeline = class {
|
|
|
9243
9286
|
await adapter.handleHook(payload);
|
|
9244
9287
|
});
|
|
9245
9288
|
}
|
|
9289
|
+
try {
|
|
9290
|
+
this.wsPort = await this.wsServer.start({
|
|
9291
|
+
port: resolvedWsPort,
|
|
9292
|
+
eventBus: this.eventBus,
|
|
9293
|
+
activeTools
|
|
9294
|
+
});
|
|
9295
|
+
this.httpPort = await this.httpReceiver.start({
|
|
9296
|
+
port: resolvedHttpPort,
|
|
9297
|
+
onHook: async (tool, payload) => {
|
|
9298
|
+
await this.handleHook(tool, payload);
|
|
9299
|
+
},
|
|
9300
|
+
getHealthSnapshot: () => this.getHealthSnapshot()
|
|
9301
|
+
});
|
|
9302
|
+
this.socketPath = await this.udsServer.start({
|
|
9303
|
+
socketPath,
|
|
9304
|
+
onEvent: async (event) => {
|
|
9305
|
+
await this.publishEvent(event);
|
|
9306
|
+
}
|
|
9307
|
+
});
|
|
9308
|
+
await this.adapterRegistry.startAll(config);
|
|
9309
|
+
} catch (error) {
|
|
9310
|
+
logger.error({ error }, "\u{1F4D6} Pipeline start failed \u2014 rolling back already-started components");
|
|
9311
|
+
await this.rollbackPartialStart();
|
|
9312
|
+
throw error;
|
|
9313
|
+
}
|
|
9246
9314
|
this.startedAt = Date.now();
|
|
9247
|
-
this.wsPort = await this.wsServer.start({
|
|
9248
|
-
port: resolvedWsPort,
|
|
9249
|
-
eventBus: this.eventBus,
|
|
9250
|
-
activeTools
|
|
9251
|
-
});
|
|
9252
|
-
this.httpPort = await this.httpReceiver.start({
|
|
9253
|
-
port: resolvedHttpPort,
|
|
9254
|
-
onHook: async (tool, payload) => {
|
|
9255
|
-
await this.handleHook(tool, payload);
|
|
9256
|
-
},
|
|
9257
|
-
getHealthSnapshot: () => this.getHealthSnapshot()
|
|
9258
|
-
});
|
|
9259
|
-
this.socketPath = await this.udsServer.start({
|
|
9260
|
-
socketPath,
|
|
9261
|
-
onEvent: async (event) => {
|
|
9262
|
-
await this.publishEvent(event);
|
|
9263
|
-
}
|
|
9264
|
-
});
|
|
9265
|
-
await this.adapterRegistry.startAll(config);
|
|
9266
9315
|
logger.info(this.getStatus(), "Core pipeline started");
|
|
9267
9316
|
return this.getStatus();
|
|
9268
9317
|
}
|
|
@@ -9270,10 +9319,25 @@ var Pipeline = class {
|
|
|
9270
9319
|
* Stops every pipeline component in reverse dependency order.
|
|
9271
9320
|
*/
|
|
9272
9321
|
async stop() {
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9322
|
+
const stopSafely = async (label, fn) => {
|
|
9323
|
+
try {
|
|
9324
|
+
await fn();
|
|
9325
|
+
} catch (error) {
|
|
9326
|
+
logger.warn({ error }, `\u{1F4D6} Error while stopping ${label} \u2014 continuing shutdown`);
|
|
9327
|
+
}
|
|
9328
|
+
};
|
|
9329
|
+
await stopSafely("adapter registry", async () => {
|
|
9330
|
+
await this.adapterRegistry?.stopAll();
|
|
9331
|
+
});
|
|
9332
|
+
await stopSafely("HTTP receiver", async () => {
|
|
9333
|
+
await this.httpReceiver.stop();
|
|
9334
|
+
});
|
|
9335
|
+
await stopSafely("UDS server", async () => {
|
|
9336
|
+
await this.udsServer.stop();
|
|
9337
|
+
});
|
|
9338
|
+
await stopSafely("WebSocket server", async () => {
|
|
9339
|
+
await this.wsServer.stop();
|
|
9340
|
+
});
|
|
9277
9341
|
this.eventBus.unsubscribeAll();
|
|
9278
9342
|
this.adapterRegistry = null;
|
|
9279
9343
|
this.enabledTools.clear();
|
|
@@ -9290,11 +9354,50 @@ var Pipeline = class {
|
|
|
9290
9354
|
registerHookHandler(tool, handler) {
|
|
9291
9355
|
this.hookHandlers.set(tool, handler);
|
|
9292
9356
|
}
|
|
9357
|
+
/**
|
|
9358
|
+
* 📖 Rolls back any components that were successfully started before a
|
|
9359
|
+
* failure occurred, preventing orphaned servers or leaking resources.
|
|
9360
|
+
*/
|
|
9361
|
+
async rollbackPartialStart() {
|
|
9362
|
+
const stopSafe = async (label, fn) => {
|
|
9363
|
+
try {
|
|
9364
|
+
await fn();
|
|
9365
|
+
} catch (error) {
|
|
9366
|
+
logger.warn({ error }, `\u{1F4D6} Error rolling back ${label}`);
|
|
9367
|
+
}
|
|
9368
|
+
};
|
|
9369
|
+
await stopSafe("adapter registry", async () => {
|
|
9370
|
+
await this.adapterRegistry?.stopAll();
|
|
9371
|
+
});
|
|
9372
|
+
await stopSafe("UDS server", async () => {
|
|
9373
|
+
await this.udsServer.stop();
|
|
9374
|
+
});
|
|
9375
|
+
await stopSafe("HTTP receiver", async () => {
|
|
9376
|
+
await this.httpReceiver.stop();
|
|
9377
|
+
});
|
|
9378
|
+
await stopSafe("WebSocket server", async () => {
|
|
9379
|
+
await this.wsServer.stop();
|
|
9380
|
+
});
|
|
9381
|
+
this.adapterRegistry = null;
|
|
9382
|
+
this.enabledTools.clear();
|
|
9383
|
+
this.hookHandlers.clear();
|
|
9384
|
+
}
|
|
9293
9385
|
/**
|
|
9294
9386
|
* Publishes an event after best-effort context enrichment.
|
|
9387
|
+
* 📖 If enrichment fails, the original event is published un-enriched
|
|
9388
|
+
* rather than being dropped entirely.
|
|
9295
9389
|
*/
|
|
9296
9390
|
async publishEvent(event, context = {}) {
|
|
9297
|
-
|
|
9391
|
+
let enrichedEvent;
|
|
9392
|
+
try {
|
|
9393
|
+
enrichedEvent = await this.contextDetector.enrich(event, context);
|
|
9394
|
+
} catch (error) {
|
|
9395
|
+
logger.warn(
|
|
9396
|
+
{ error, eventId: event.id },
|
|
9397
|
+
"\u{1F4D6} Context enrichment failed \u2014 publishing un-enriched event"
|
|
9398
|
+
);
|
|
9399
|
+
enrichedEvent = event;
|
|
9400
|
+
}
|
|
9298
9401
|
return this.eventBus.publish(enrichedEvent);
|
|
9299
9402
|
}
|
|
9300
9403
|
/**
|
|
@@ -9330,6 +9433,16 @@ var Pipeline = class {
|
|
|
9330
9433
|
};
|
|
9331
9434
|
}
|
|
9332
9435
|
async handleHook(tool, payload) {
|
|
9436
|
+
try {
|
|
9437
|
+
await this.handleHookInner(tool, payload);
|
|
9438
|
+
} catch (error) {
|
|
9439
|
+
logger.error(
|
|
9440
|
+
{ error, tool },
|
|
9441
|
+
"\u{1F4D6} Unhandled error in hook handler \u2014 swallowing to prevent daemon crash"
|
|
9442
|
+
);
|
|
9443
|
+
}
|
|
9444
|
+
}
|
|
9445
|
+
async handleHookInner(tool, payload) {
|
|
9333
9446
|
if (!this.enabledTools.has(tool)) {
|
|
9334
9447
|
logger.debug({ tool }, "Ignoring hook for disabled tool");
|
|
9335
9448
|
return;
|
|
@@ -10505,18 +10618,22 @@ function parseSocketPayload(data) {
|
|
|
10505
10618
|
return parsedEvent.success ? parsedEvent.data : null;
|
|
10506
10619
|
}
|
|
10507
10620
|
function parseUnknownPayload(data) {
|
|
10508
|
-
|
|
10509
|
-
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
|
|
10514
|
-
|
|
10515
|
-
|
|
10516
|
-
|
|
10517
|
-
|
|
10621
|
+
try {
|
|
10622
|
+
if (typeof data === "string") {
|
|
10623
|
+
return JSON.parse(data);
|
|
10624
|
+
}
|
|
10625
|
+
if (Array.isArray(data)) {
|
|
10626
|
+
return JSON.parse(Buffer.concat(data).toString("utf8"));
|
|
10627
|
+
}
|
|
10628
|
+
if (data instanceof ArrayBuffer) {
|
|
10629
|
+
return JSON.parse(
|
|
10630
|
+
Buffer.from(new Uint8Array(data)).toString("utf8")
|
|
10631
|
+
);
|
|
10632
|
+
}
|
|
10633
|
+
return JSON.parse(Buffer.from(data).toString("utf8"));
|
|
10634
|
+
} catch {
|
|
10635
|
+
return null;
|
|
10518
10636
|
}
|
|
10519
|
-
return JSON.parse(Buffer.from(data).toString("utf8"));
|
|
10520
10637
|
}
|
|
10521
10638
|
|
|
10522
10639
|
// src/tui/hooks/useKeyBinds.ts
|
|
@@ -11244,16 +11361,20 @@ function ManagedDaemonApp({
|
|
|
11244
11361
|
}
|
|
11245
11362
|
function parseSocketPayload2(data) {
|
|
11246
11363
|
let parsedPayload;
|
|
11247
|
-
|
|
11248
|
-
|
|
11249
|
-
|
|
11250
|
-
|
|
11251
|
-
|
|
11252
|
-
|
|
11253
|
-
|
|
11254
|
-
|
|
11255
|
-
|
|
11256
|
-
|
|
11364
|
+
try {
|
|
11365
|
+
if (typeof data === "string") {
|
|
11366
|
+
parsedPayload = JSON.parse(data);
|
|
11367
|
+
} else if (Array.isArray(data)) {
|
|
11368
|
+
parsedPayload = JSON.parse(Buffer.concat(data).toString("utf8"));
|
|
11369
|
+
} else if (data instanceof ArrayBuffer) {
|
|
11370
|
+
parsedPayload = JSON.parse(
|
|
11371
|
+
Buffer.from(new Uint8Array(data)).toString("utf8")
|
|
11372
|
+
);
|
|
11373
|
+
} else {
|
|
11374
|
+
parsedPayload = JSON.parse(Buffer.from(data).toString("utf8"));
|
|
11375
|
+
}
|
|
11376
|
+
} catch {
|
|
11377
|
+
return null;
|
|
11257
11378
|
}
|
|
11258
11379
|
if (typeof parsedPayload === "object" && parsedPayload !== null && "type" in parsedPayload && parsedPayload.type === "welcome") {
|
|
11259
11380
|
return null;
|
|
@@ -11748,6 +11869,183 @@ function toConfigArgv(options) {
|
|
|
11748
11869
|
return options.configPath ? ["--config", options.configPath] : [];
|
|
11749
11870
|
}
|
|
11750
11871
|
|
|
11872
|
+
// src/cli/live-logger.ts
|
|
11873
|
+
import { once as once5 } from "events";
|
|
11874
|
+
import WebSocket5 from "ws";
|
|
11875
|
+
function formatLoggerEventBlock(event) {
|
|
11876
|
+
const headerSegments = [
|
|
11877
|
+
colorize(`#${event["aisnitch.seqnum"]}`, EVENT_COLORS[event.type]),
|
|
11878
|
+
colorize(event["aisnitch.tool"], TOOL_COLORS[event["aisnitch.tool"]]),
|
|
11879
|
+
colorize(event.type, EVENT_COLORS[event.type]),
|
|
11880
|
+
colorize(formatSessionLabelFromEvent(event), TUI_THEME.warning),
|
|
11881
|
+
colorize(event.time, TUI_THEME.muted)
|
|
11882
|
+
];
|
|
11883
|
+
const flattenedLines = flattenEventRecord(event);
|
|
11884
|
+
return [
|
|
11885
|
+
`${colorize("\u256D\u2500", TUI_THEME.frame)} ${headerSegments.join(colorize(" ", TUI_THEME.muted))}`,
|
|
11886
|
+
...flattenedLines.map((line) => `${colorize("\u2502", TUI_THEME.frame)} ${line}`),
|
|
11887
|
+
colorize("\u2570\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\u2500\u2500\u2500\u2500\u2500\u2500", TUI_THEME.frame)
|
|
11888
|
+
].join("\n");
|
|
11889
|
+
}
|
|
11890
|
+
function formatLoggerWelcomeLine(message) {
|
|
11891
|
+
const tools = message.tools.length > 0 ? message.tools.join(", ") : "none configured";
|
|
11892
|
+
return `${colorize("AISnitch logger attached", TUI_THEME.success)} ${colorize(`v${message.version}`, TUI_THEME.warning)} ${colorize(`tools=${tools}`, TUI_THEME.muted)}`;
|
|
11893
|
+
}
|
|
11894
|
+
async function attachWebSocketLogger(url, output, filters = {}) {
|
|
11895
|
+
const socket = new WebSocket5(url);
|
|
11896
|
+
socket.on("message", (data) => {
|
|
11897
|
+
const parsedPayload = parseSocketMessage(data);
|
|
11898
|
+
if (isWelcomeMessage(parsedPayload)) {
|
|
11899
|
+
output.stdout(`${formatLoggerWelcomeLine(parsedPayload)}
|
|
11900
|
+
`);
|
|
11901
|
+
return;
|
|
11902
|
+
}
|
|
11903
|
+
const parsedEvent = AISnitchEventSchema.safeParse(parsedPayload);
|
|
11904
|
+
if (!parsedEvent.success) {
|
|
11905
|
+
output.stderr(
|
|
11906
|
+
`${colorize("logger:", TUI_THEME.danger)} received an unrecognized event payload.
|
|
11907
|
+
`
|
|
11908
|
+
);
|
|
11909
|
+
return;
|
|
11910
|
+
}
|
|
11911
|
+
if (!matchesFilters(parsedEvent.data, filters)) {
|
|
11912
|
+
return;
|
|
11913
|
+
}
|
|
11914
|
+
output.stdout(`${formatLoggerEventBlock(parsedEvent.data)}
|
|
11915
|
+
`);
|
|
11916
|
+
});
|
|
11917
|
+
socket.on("error", (error) => {
|
|
11918
|
+
output.stderr(
|
|
11919
|
+
`${colorize("logger:", TUI_THEME.danger)} ${error instanceof Error ? error.message : "unknown socket error"}
|
|
11920
|
+
`
|
|
11921
|
+
);
|
|
11922
|
+
});
|
|
11923
|
+
await once5(socket, "open");
|
|
11924
|
+
return async () => {
|
|
11925
|
+
if (socket.readyState === WebSocket5.CLOSING || socket.readyState === WebSocket5.CLOSED) {
|
|
11926
|
+
return;
|
|
11927
|
+
}
|
|
11928
|
+
socket.close();
|
|
11929
|
+
await once5(socket, "close");
|
|
11930
|
+
};
|
|
11931
|
+
}
|
|
11932
|
+
function matchesFilters(event, filters) {
|
|
11933
|
+
if (filters.tool && event["aisnitch.tool"] !== filters.tool) {
|
|
11934
|
+
return false;
|
|
11935
|
+
}
|
|
11936
|
+
if (filters.type && event.type !== filters.type) {
|
|
11937
|
+
return false;
|
|
11938
|
+
}
|
|
11939
|
+
return true;
|
|
11940
|
+
}
|
|
11941
|
+
function flattenEventRecord(event) {
|
|
11942
|
+
const lines = [];
|
|
11943
|
+
flattenValue(lines, "", event);
|
|
11944
|
+
return lines;
|
|
11945
|
+
}
|
|
11946
|
+
function flattenValue(lines, currentPath, value) {
|
|
11947
|
+
if (Array.isArray(value)) {
|
|
11948
|
+
if (value.length === 0) {
|
|
11949
|
+
lines.push(formatLoggerField(currentPath, "[]", "empty"));
|
|
11950
|
+
return;
|
|
11951
|
+
}
|
|
11952
|
+
value.forEach((entry, index) => {
|
|
11953
|
+
flattenValue(lines, `${currentPath}[${index}]`, entry);
|
|
11954
|
+
});
|
|
11955
|
+
return;
|
|
11956
|
+
}
|
|
11957
|
+
if (isRecord10(value)) {
|
|
11958
|
+
const entries = Object.entries(value).sort(
|
|
11959
|
+
([left], [right]) => left.localeCompare(right)
|
|
11960
|
+
);
|
|
11961
|
+
if (entries.length === 0) {
|
|
11962
|
+
lines.push(formatLoggerField(currentPath, "{}", "empty"));
|
|
11963
|
+
return;
|
|
11964
|
+
}
|
|
11965
|
+
for (const [key, entry] of entries) {
|
|
11966
|
+
const nextPath = currentPath.length === 0 ? key : `${currentPath}.${key}`;
|
|
11967
|
+
flattenValue(lines, nextPath, entry);
|
|
11968
|
+
}
|
|
11969
|
+
return;
|
|
11970
|
+
}
|
|
11971
|
+
lines.push(formatLoggerField(currentPath, value, inferValueKind(value)));
|
|
11972
|
+
}
|
|
11973
|
+
function formatLoggerField(path, value, kind) {
|
|
11974
|
+
const renderedValue = typeof value === "string" ? JSON.stringify(value) : value === null ? "null" : typeof value === "undefined" ? "undefined" : JSON.stringify(value);
|
|
11975
|
+
return `${colorize(path, TUI_THEME.warning)} ${colorize("=", TUI_THEME.muted)} ${colorize(
|
|
11976
|
+
renderedValue,
|
|
11977
|
+
getValueColor(kind)
|
|
11978
|
+
)}`;
|
|
11979
|
+
}
|
|
11980
|
+
function inferValueKind(value) {
|
|
11981
|
+
if (value === null) {
|
|
11982
|
+
return "null";
|
|
11983
|
+
}
|
|
11984
|
+
switch (typeof value) {
|
|
11985
|
+
case "string":
|
|
11986
|
+
return "string";
|
|
11987
|
+
case "number":
|
|
11988
|
+
return "number";
|
|
11989
|
+
case "boolean":
|
|
11990
|
+
return "boolean";
|
|
11991
|
+
default:
|
|
11992
|
+
return "other";
|
|
11993
|
+
}
|
|
11994
|
+
}
|
|
11995
|
+
function getValueColor(kind) {
|
|
11996
|
+
switch (kind) {
|
|
11997
|
+
case "string":
|
|
11998
|
+
return TUI_THEME.panelBody;
|
|
11999
|
+
case "number":
|
|
12000
|
+
return TUI_THEME.success;
|
|
12001
|
+
case "boolean":
|
|
12002
|
+
return TUI_THEME.warning;
|
|
12003
|
+
case "null":
|
|
12004
|
+
return TUI_THEME.danger;
|
|
12005
|
+
case "empty":
|
|
12006
|
+
return TUI_THEME.muted;
|
|
12007
|
+
default:
|
|
12008
|
+
return TUI_THEME.panelBody;
|
|
12009
|
+
}
|
|
12010
|
+
}
|
|
12011
|
+
function colorize(value, color) {
|
|
12012
|
+
const [red, green, blue] = hexToRgb(color);
|
|
12013
|
+
return `\x1B[38;2;${red};${green};${blue}m${value}\x1B[39m`;
|
|
12014
|
+
}
|
|
12015
|
+
function hexToRgb(hexColor) {
|
|
12016
|
+
const sanitized = hexColor.slice(1);
|
|
12017
|
+
return [
|
|
12018
|
+
Number.parseInt(sanitized.slice(0, 2), 16),
|
|
12019
|
+
Number.parseInt(sanitized.slice(2, 4), 16),
|
|
12020
|
+
Number.parseInt(sanitized.slice(4, 6), 16)
|
|
12021
|
+
];
|
|
12022
|
+
}
|
|
12023
|
+
function parseSocketMessage(data) {
|
|
12024
|
+
try {
|
|
12025
|
+
if (typeof data === "string") {
|
|
12026
|
+
return JSON.parse(data);
|
|
12027
|
+
}
|
|
12028
|
+
if (Array.isArray(data)) {
|
|
12029
|
+
return JSON.parse(Buffer.concat(data).toString("utf8"));
|
|
12030
|
+
}
|
|
12031
|
+
if (data instanceof ArrayBuffer) {
|
|
12032
|
+
return JSON.parse(Buffer.from(new Uint8Array(data)).toString("utf8"));
|
|
12033
|
+
}
|
|
12034
|
+
return JSON.parse(Buffer.from(data).toString("utf8"));
|
|
12035
|
+
} catch {
|
|
12036
|
+
return null;
|
|
12037
|
+
}
|
|
12038
|
+
}
|
|
12039
|
+
function isWelcomeMessage(payload) {
|
|
12040
|
+
if (!isRecord10(payload)) {
|
|
12041
|
+
return false;
|
|
12042
|
+
}
|
|
12043
|
+
return payload.type === "welcome" && typeof payload.version === "string" && Array.isArray(payload.tools);
|
|
12044
|
+
}
|
|
12045
|
+
function isRecord10(value) {
|
|
12046
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12047
|
+
}
|
|
12048
|
+
|
|
11751
12049
|
// src/cli/runtime.ts
|
|
11752
12050
|
var execFile11 = promisify11(execFileCallback11);
|
|
11753
12051
|
var DAEMON_READY_TIMEOUT_MS = 4e3;
|
|
@@ -12084,6 +12382,20 @@ function createCliRuntime(dependencies = {}) {
|
|
|
12084
12382
|
process.once("SIGINT", () => {
|
|
12085
12383
|
void shutdown("SIGINT");
|
|
12086
12384
|
});
|
|
12385
|
+
process.once("uncaughtException", (error) => {
|
|
12386
|
+
output.stderr(
|
|
12387
|
+
`AISnitch crashed: ${error instanceof Error ? error.message : "unknown exception"}
|
|
12388
|
+
`
|
|
12389
|
+
);
|
|
12390
|
+
void shutdown("uncaughtException", 1);
|
|
12391
|
+
});
|
|
12392
|
+
process.once("unhandledRejection", (reason) => {
|
|
12393
|
+
output.stderr(
|
|
12394
|
+
`AISnitch rejected a promise: ${reason instanceof Error ? reason.message : "unknown rejection"}
|
|
12395
|
+
`
|
|
12396
|
+
);
|
|
12397
|
+
void shutdown("unhandledRejection", 1);
|
|
12398
|
+
});
|
|
12087
12399
|
await renderForegroundTui({
|
|
12088
12400
|
configuredAdapters: getEnabledAdapters(config),
|
|
12089
12401
|
eventBus: pipeline.getEventBus(),
|
|
@@ -12225,6 +12537,43 @@ function createCliRuntime(dependencies = {}) {
|
|
|
12225
12537
|
}
|
|
12226
12538
|
});
|
|
12227
12539
|
}
|
|
12540
|
+
async function logger2(options) {
|
|
12541
|
+
const snapshot = await getStatusSnapshot(options);
|
|
12542
|
+
if (!snapshot.running) {
|
|
12543
|
+
throw new Error(
|
|
12544
|
+
"AISnitch logger requires a running daemon. Start one with `aisnitch start --daemon` or use `aisnitch start` first."
|
|
12545
|
+
);
|
|
12546
|
+
}
|
|
12547
|
+
const closeLogger = await attachWebSocketLogger(
|
|
12548
|
+
`ws://127.0.0.1:${snapshot.wsPort}`,
|
|
12549
|
+
output,
|
|
12550
|
+
{
|
|
12551
|
+
tool: options.tool,
|
|
12552
|
+
type: options.type
|
|
12553
|
+
}
|
|
12554
|
+
);
|
|
12555
|
+
await new Promise((resolve2) => {
|
|
12556
|
+
let closed = false;
|
|
12557
|
+
const shutdown = async () => {
|
|
12558
|
+
if (closed) {
|
|
12559
|
+
return;
|
|
12560
|
+
}
|
|
12561
|
+
closed = true;
|
|
12562
|
+
process.off("SIGINT", handleSigint);
|
|
12563
|
+
process.off("SIGTERM", handleSigterm);
|
|
12564
|
+
await Promise.resolve(closeLogger());
|
|
12565
|
+
resolve2();
|
|
12566
|
+
};
|
|
12567
|
+
const handleSigint = () => {
|
|
12568
|
+
void shutdown();
|
|
12569
|
+
};
|
|
12570
|
+
const handleSigterm = () => {
|
|
12571
|
+
void shutdown();
|
|
12572
|
+
};
|
|
12573
|
+
process.once("SIGINT", handleSigint);
|
|
12574
|
+
process.once("SIGTERM", handleSigterm);
|
|
12575
|
+
});
|
|
12576
|
+
}
|
|
12228
12577
|
async function mock(selection, options) {
|
|
12229
12578
|
const pathOptions = toPathOptions2(options);
|
|
12230
12579
|
const daemonState = await readDaemonState(pathOptions);
|
|
@@ -12480,6 +12829,7 @@ function createCliRuntime(dependencies = {}) {
|
|
|
12480
12829
|
aiderNotify,
|
|
12481
12830
|
attach,
|
|
12482
12831
|
install,
|
|
12832
|
+
logger: logger2,
|
|
12483
12833
|
mock,
|
|
12484
12834
|
runDaemonProcess,
|
|
12485
12835
|
selfUpdateRun,
|
|
@@ -12692,6 +13042,8 @@ Examples:
|
|
|
12692
13042
|
aisnitch status
|
|
12693
13043
|
aisnitch attach
|
|
12694
13044
|
aisnitch attach --view full-data
|
|
13045
|
+
aisnitch logger
|
|
13046
|
+
aisnitch logger --tool claude-code
|
|
12695
13047
|
aisnitch setup claude-code
|
|
12696
13048
|
aisnitch setup aider
|
|
12697
13049
|
aisnitch setup gemini-cli
|
|
@@ -12710,6 +13062,7 @@ Examples:
|
|
|
12710
13062
|
addAdaptersCommand(program, runtime);
|
|
12711
13063
|
addSetupCommand(program, runtime);
|
|
12712
13064
|
addAttachCommand(program, runtime);
|
|
13065
|
+
addLoggerCommand(program, runtime);
|
|
12713
13066
|
addMockCommand(program, runtime);
|
|
12714
13067
|
addWrapCommand(program, runtime);
|
|
12715
13068
|
addInstallCommand(program, runtime);
|
|
@@ -12850,6 +13203,21 @@ function addWrapCommand(program, runtime) {
|
|
|
12850
13203
|
}
|
|
12851
13204
|
);
|
|
12852
13205
|
}
|
|
13206
|
+
function addLoggerCommand(program, runtime) {
|
|
13207
|
+
addCommonOptions(
|
|
13208
|
+
program.command("logger").description("Stream exhaustive live event logs without the TUI").option(
|
|
13209
|
+
"--tool <tool>",
|
|
13210
|
+
"Filter the live logger by tool",
|
|
13211
|
+
wrapOptionParser(parseToolFilterOption)
|
|
13212
|
+
).option(
|
|
13213
|
+
"--type <type>",
|
|
13214
|
+
"Filter the live logger by event type",
|
|
13215
|
+
wrapOptionParser(parseEventTypeFilterOption)
|
|
13216
|
+
)
|
|
13217
|
+
).action(async (options) => {
|
|
13218
|
+
await runtime.logger(options);
|
|
13219
|
+
});
|
|
13220
|
+
}
|
|
12853
13221
|
function addInstallCommand(program, runtime) {
|
|
12854
13222
|
addCommonOptions(
|
|
12855
13223
|
program.command("install").description("Install a macOS LaunchAgent for AISnitch")
|