@wibeco/bridge 0.1.1 → 0.2.0

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.
@@ -5,14 +5,19 @@ import {
5
5
  SystemCredentialStore,
6
6
  createHookEvent,
7
7
  detectRepository,
8
+ detectWorkingTreeMetrics,
9
+ eventTypeForHook,
8
10
  mapClaudeCodeHook,
9
11
  mapCodexHook,
10
12
  mapCursorHook,
11
13
  matchesGitHubRepository,
12
14
  normalizeGitHubRepository,
13
15
  pollDeviceToken,
14
- requestDeviceAuthorization
15
- } from "./chunk-OTDZMJDK.js";
16
+ requestDeviceAuthorization,
17
+ startPresenceSession,
18
+ stopPresenceSession,
19
+ updatePresenceSession
20
+ } from "./chunk-FLTLCZ6E.js";
16
21
 
17
22
  // src/cli/commands.ts
18
23
  import { access, cp, mkdir, readFile, writeFile } from "fs/promises";
@@ -184,16 +189,75 @@ async function statusCommand(cwd = process.cwd()) {
184
189
  }
185
190
  async function emitCommand(adapter, eventName, input) {
186
191
  const mapper = adapter === "cursor" ? mapCursorHook : adapter === "claude-code" ? mapClaudeCodeHook : mapCodexHook;
187
- const mappedEvent = mapper(eventName, input);
192
+ let mappedEvent = mapper(eventName, input);
188
193
  const repo = await detectRepository(process.cwd());
194
+ if (repo && (mappedEvent.kind === "session.started" || mappedEvent.kind === "session.ended" || mappedEvent.kind === "presence.heartbeat" || mappedEvent.kind === "file.changed")) {
195
+ const workingTree = await detectWorkingTreeMetrics(repo.root);
196
+ if (workingTree) {
197
+ mappedEvent = {
198
+ ...mappedEvent,
199
+ metadata: {
200
+ ...mappedEvent.metadata,
201
+ working_tree_lines_added: workingTree.linesAdded,
202
+ working_tree_lines_deleted: workingTree.linesDeleted,
203
+ ...mappedEvent.kind === "file.changed" ? { paths: workingTree.paths } : { working_tree_paths: workingTree.paths }
204
+ }
205
+ };
206
+ }
207
+ }
208
+ if (mappedEvent.kind === "session.started") {
209
+ await startPresenceSession(adapter, mappedEvent.sessionId);
210
+ const metrics = await updatePresenceSession(
211
+ adapter,
212
+ mappedEvent.sessionId,
213
+ mappedEvent
214
+ );
215
+ mappedEvent = {
216
+ ...mappedEvent,
217
+ metadata: { ...mappedEvent.metadata, ...metrics ?? {} }
218
+ };
219
+ } else if (mappedEvent.kind === "session.ended") {
220
+ const metrics = await stopPresenceSession(adapter, mappedEvent.sessionId);
221
+ if (metrics) {
222
+ mappedEvent = {
223
+ ...mappedEvent,
224
+ durationMs: mappedEvent.durationMs ?? metrics.session_elapsed_ms,
225
+ metadata: {
226
+ ...mappedEvent.metadata,
227
+ ...metrics,
228
+ session_ended_at: (/* @__PURE__ */ new Date()).toISOString()
229
+ }
230
+ };
231
+ }
232
+ } else {
233
+ const metrics = await updatePresenceSession(
234
+ adapter,
235
+ mappedEvent.sessionId,
236
+ mappedEvent
237
+ );
238
+ if (mappedEvent.kind === "presence.heartbeat" && metrics) {
239
+ const publicMetadata = { ...mappedEvent.metadata };
240
+ delete publicMetadata.working_tree_paths;
241
+ delete publicMetadata.paths;
242
+ mappedEvent = {
243
+ ...mappedEvent,
244
+ metadata: { ...publicMetadata, ...metrics }
245
+ };
246
+ }
247
+ }
248
+ if (mappedEvent.kind === "session.started" || mappedEvent.kind === "session.ended") {
249
+ const publicMetadata = { ...mappedEvent.metadata };
250
+ delete publicMetadata.working_tree_paths;
251
+ mappedEvent = { ...mappedEvent, metadata: publicMetadata };
252
+ }
189
253
  const event = repo ? { ...mappedEvent, repo } : mappedEvent;
190
254
  const queue = new JsonFileOfflineQueue(queuePath());
191
255
  const credential = await loadCredential(process.cwd());
192
256
  if (!credential) {
193
- await queue.enqueue([event]);
257
+ if (eventTypeForHook(event)) await queue.enqueue([event]);
194
258
  return {
195
259
  exitCode: 0,
196
- message: `Queued ${event.kind}; this repository is not connected to Wibe.`
260
+ message: eventTypeForHook(event) ? `Queued ${event.kind}; this repository is not connected to Wibe.` : `Ignored operational ${event.kind} hook.`
197
261
  };
198
262
  }
199
263
  const result = await new SignedBatchClient({
@@ -478,6 +542,8 @@ async function assertExpectedRepository(cwd, expected) {
478
542
  );
479
543
  }
480
544
  async function sendVerificationHeartbeat(credential, adapter, reason) {
545
+ const repository = await detectRepository(process.cwd());
546
+ const workingTree = repository ? await detectWorkingTreeMetrics(repository.root) : void 0;
481
547
  return new SignedBatchClient({
482
548
  endpoint: `${credential.appUrl}/api/events/batch`,
483
549
  accessToken: credential.accessToken,
@@ -489,8 +555,16 @@ async function sendVerificationHeartbeat(credential, adapter, reason) {
489
555
  }).capture(
490
556
  createHookEvent({
491
557
  source: adapter,
492
- kind: "unknown",
493
- metadata: { verification: reason }
558
+ kind: "presence.heartbeat",
559
+ ...repository ? { repo: repository } : {},
560
+ metadata: {
561
+ verification: reason,
562
+ ...workingTree ? {
563
+ changed_files: workingTree.paths.length,
564
+ lines_added: workingTree.linesAdded,
565
+ lines_deleted: workingTree.linesDeleted
566
+ } : {}
567
+ }
494
568
  })
495
569
  );
496
570
  }
package/dist/cli.js CHANGED
@@ -5,8 +5,10 @@ import {
5
5
  parseAdapter,
6
6
  setupCommand,
7
7
  statusCommand
8
- } from "./chunk-J2HWZMXA.js";
9
- import "./chunk-OTDZMJDK.js";
8
+ } from "./chunk-YV3UV2XJ.js";
9
+ import {
10
+ runPresenceHeartbeat
11
+ } from "./chunk-FLTLCZ6E.js";
10
12
 
11
13
  // src/cli.ts
12
14
  var HELP = `wibe-bridge <command>
@@ -37,8 +39,20 @@ async function main() {
37
39
  const eventName = option(args, "--event");
38
40
  if (!eventName) throw new Error("--event is required");
39
41
  result = await emitCommand(adapter, eventName, await readJsonStdin());
42
+ process.stderr.write(`${result.message}
43
+ `);
44
+ process.stdout.write("{}\n");
45
+ process.exitCode = 0;
46
+ return;
40
47
  } else if (command === "doctor") {
41
48
  result = await doctorCommand(process.cwd(), option(args, "--repository"));
49
+ } else if (command === "_presence-heartbeat") {
50
+ parseAdapter(option(args, "--adapter"));
51
+ const statePath = option(args, "--state");
52
+ const instanceId = option(args, "--instance");
53
+ if (!statePath || !instanceId) throw new Error("Presence state is incomplete");
54
+ await runPresenceHeartbeat(statePath, instanceId, emitCommand);
55
+ return;
42
56
  } else {
43
57
  process.stdout.write(`${HELP}
44
58
  `);
@@ -69,5 +83,10 @@ async function readJsonStdin() {
69
83
  main().catch((error) => {
70
84
  process.stderr.write(`wibe-bridge: ${error instanceof Error ? error.message : String(error)}
71
85
  `);
72
- process.exitCode = 1;
86
+ if (process.argv[2] === "emit") {
87
+ process.stdout.write("{}\n");
88
+ process.exitCode = 0;
89
+ } else {
90
+ process.exitCode = 1;
91
+ }
73
92
  });
@@ -1,12 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  emitCommand
4
- } from "./chunk-J2HWZMXA.js";
5
- import "./chunk-OTDZMJDK.js";
4
+ } from "./chunk-YV3UV2XJ.js";
5
+ import "./chunk-FLTLCZ6E.js";
6
6
 
7
7
  // src/codex-hook.ts
8
8
  async function main() {
9
9
  const text = process.argv[2];
10
+ if (text && Buffer.byteLength(text) > 1024 * 1024) {
11
+ throw new Error("notification payload exceeds 1 MiB limit");
12
+ }
10
13
  const payload = text ? JSON.parse(text) : {};
11
14
  const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
12
15
  const eventName = typeof record.type === "string" ? record.type : "turn-complete";
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ declare const agentSourceSchema: z.ZodEnum<{
7
7
  }>;
8
8
  type AgentSource = z.infer<typeof agentSourceSchema>;
9
9
  declare const hookEventKindSchema: z.ZodEnum<{
10
+ "presence.heartbeat": "presence.heartbeat";
10
11
  "session.started": "session.started";
11
12
  "session.ended": "session.ended";
12
13
  "lifecycle.before": "lifecycle.before";
@@ -35,6 +36,7 @@ declare const canonicalHookEventSchema: z.ZodObject<{
35
36
  codex: "codex";
36
37
  }>;
37
38
  kind: z.ZodEnum<{
39
+ "presence.heartbeat": "presence.heartbeat";
38
40
  "session.started": "session.started";
39
41
  "session.ended": "session.ended";
40
42
  "lifecycle.before": "lifecycle.before";
@@ -72,7 +74,7 @@ declare function mapClaudeCodeHook(eventName: string, input: unknown): {
72
74
  id: string;
73
75
  version: 1;
74
76
  source: "cursor" | "claude-code" | "codex";
75
- kind: "session.started" | "session.ended" | "lifecycle.before" | "lifecycle.after" | "tool.started" | "tool.completed" | "file.changed" | "shell.started" | "shell.completed" | "mcp.started" | "mcp.completed" | "unknown";
77
+ kind: "presence.heartbeat" | "session.started" | "session.ended" | "lifecycle.before" | "lifecycle.after" | "tool.started" | "tool.completed" | "file.changed" | "shell.started" | "shell.completed" | "mcp.started" | "mcp.completed" | "unknown";
76
78
  occurredAt: string;
77
79
  metadata: Record<string, SafeValue>;
78
80
  sessionId?: string | undefined;
@@ -90,7 +92,7 @@ declare function mapCodexHook(eventName: string, input: unknown): {
90
92
  id: string;
91
93
  version: 1;
92
94
  source: "cursor" | "claude-code" | "codex";
93
- kind: "session.started" | "session.ended" | "lifecycle.before" | "lifecycle.after" | "tool.started" | "tool.completed" | "file.changed" | "shell.started" | "shell.completed" | "mcp.started" | "mcp.completed" | "unknown";
95
+ kind: "presence.heartbeat" | "session.started" | "session.ended" | "lifecycle.before" | "lifecycle.after" | "tool.started" | "tool.completed" | "file.changed" | "shell.started" | "shell.completed" | "mcp.started" | "mcp.completed" | "unknown";
94
96
  occurredAt: string;
95
97
  metadata: Record<string, SafeValue>;
96
98
  sessionId?: string | undefined;
@@ -108,7 +110,7 @@ declare function mapCursorHook(eventName: string, input: unknown): {
108
110
  id: string;
109
111
  version: 1;
110
112
  source: "cursor" | "claude-code" | "codex";
111
- kind: "session.started" | "session.ended" | "lifecycle.before" | "lifecycle.after" | "tool.started" | "tool.completed" | "file.changed" | "shell.started" | "shell.completed" | "mcp.started" | "mcp.completed" | "unknown";
113
+ kind: "presence.heartbeat" | "session.started" | "session.ended" | "lifecycle.before" | "lifecycle.after" | "tool.started" | "tool.completed" | "file.changed" | "shell.started" | "shell.completed" | "mcp.started" | "mcp.completed" | "unknown";
112
114
  occurredAt: string;
113
115
  metadata: Record<string, SafeValue>;
114
116
  sessionId?: string | undefined;
@@ -235,6 +237,7 @@ interface SignedBatchClientOptions {
235
237
  batchSize?: number;
236
238
  timeoutMs?: number;
237
239
  }
240
+ declare function eventTypeForHook(event: CanonicalHookEvent): "presence.heartbeat" | "presence.started" | "presence.stopped" | "workspace.files_changed" | "workspace.test_completed" | null;
238
241
  interface FlushResult {
239
242
  sent: number;
240
243
  remaining: number;
@@ -250,6 +253,26 @@ declare class SignedBatchClient {
250
253
  flush(): Promise<FlushResult>;
251
254
  }
252
255
 
256
+ declare const PRESENCE_HEARTBEAT_INTERVAL_MS = 45000;
257
+ interface PresenceMetrics {
258
+ session_started_at: string;
259
+ session_elapsed_ms: number;
260
+ sequence: number;
261
+ active_seconds: number;
262
+ lines_added: number;
263
+ lines_deleted: number;
264
+ changed_files: number;
265
+ model_seconds: Record<string, number>;
266
+ tool_counts: Record<string, number>;
267
+ tests_passed: number;
268
+ tests_failed: number;
269
+ }
270
+ declare function startPresenceSession(source: AgentSource, sessionId: string | undefined, cwd?: string): Promise<PresenceMetrics>;
271
+ declare function updatePresenceSession(source: AgentSource, sessionId: string | undefined, event: CanonicalHookEvent, cwd?: string): Promise<PresenceMetrics | undefined>;
272
+ declare function stopPresenceSession(source: AgentSource, sessionId: string | undefined, cwd?: string): Promise<PresenceMetrics | undefined>;
273
+ declare function runPresenceHeartbeat(statePath: string, expectedInstanceId: string, emit: (source: AgentSource, eventName: string, payload: Record<string, unknown>) => Promise<unknown>, intervalMs?: number): Promise<void>;
274
+ declare function presenceStatePath(source: AgentSource, sessionId: string | undefined, cwd?: string): string;
275
+
253
276
  interface RedactionOptions {
254
277
  allowContent?: boolean;
255
278
  homeDirectory?: string;
@@ -264,9 +287,15 @@ interface RepositoryInfo {
264
287
  branch?: string;
265
288
  commit?: string;
266
289
  }
290
+ interface WorkingTreeMetrics {
291
+ linesAdded: number;
292
+ linesDeleted: number;
293
+ paths: string[];
294
+ }
267
295
  declare function detectRepository(cwd?: string): Promise<RepositoryInfo | undefined>;
296
+ declare function detectWorkingTreeMetrics(root: string): Promise<WorkingTreeMetrics | undefined>;
268
297
  declare function sanitizeRemote(remote: string): string;
269
298
  declare function normalizeGitHubRepository(value: string): string | undefined;
270
299
  declare function matchesGitHubRepository(remote: string | undefined, expected: string): boolean;
271
300
 
272
- export { type AgentSource, type CanonicalHookEvent, type CredentialStore, type DeviceAuthorization, type DeviceTokenResponse, type FlushResult, type HookEventKind, JsonFileOfflineQueue, MemoryOfflineQueue, type OfflineQueue, type RedactionOptions, type RepositoryInfo, type SafeValue, SignedBatchClient, type SignedBatchClientOptions, type StoredCredential, SystemCredentialStore, agentSourceSchema, canonicalHookEventSchema, createHookEvent, detectRepository, deviceAuthorizationSchema, deviceTokenResponseSchema, hookEventKindSchema, mapClaudeCodeHook, mapCodexHook, mapCursorHook, matchesGitHubRepository, normalizeGitHubRepository, pollDeviceToken, redact, requestDeviceAuthorization, safeMetadata, safeValueSchema, sanitizeRemote };
301
+ export { type AgentSource, type CanonicalHookEvent, type CredentialStore, type DeviceAuthorization, type DeviceTokenResponse, type FlushResult, type HookEventKind, JsonFileOfflineQueue, MemoryOfflineQueue, type OfflineQueue, PRESENCE_HEARTBEAT_INTERVAL_MS, type PresenceMetrics, type RedactionOptions, type RepositoryInfo, type SafeValue, SignedBatchClient, type SignedBatchClientOptions, type StoredCredential, SystemCredentialStore, type WorkingTreeMetrics, agentSourceSchema, canonicalHookEventSchema, createHookEvent, detectRepository, detectWorkingTreeMetrics, deviceAuthorizationSchema, deviceTokenResponseSchema, eventTypeForHook, hookEventKindSchema, mapClaudeCodeHook, mapCodexHook, mapCursorHook, matchesGitHubRepository, normalizeGitHubRepository, pollDeviceToken, presenceStatePath, redact, requestDeviceAuthorization, runPresenceHeartbeat, safeMetadata, safeValueSchema, sanitizeRemote, startPresenceSession, stopPresenceSession, updatePresenceSession };
package/dist/index.js CHANGED
@@ -1,14 +1,17 @@
1
1
  import {
2
2
  JsonFileOfflineQueue,
3
3
  MemoryOfflineQueue,
4
+ PRESENCE_HEARTBEAT_INTERVAL_MS,
4
5
  SignedBatchClient,
5
6
  SystemCredentialStore,
6
7
  agentSourceSchema,
7
8
  canonicalHookEventSchema,
8
9
  createHookEvent,
9
10
  detectRepository,
11
+ detectWorkingTreeMetrics,
10
12
  deviceAuthorizationSchema,
11
13
  deviceTokenResponseSchema,
14
+ eventTypeForHook,
12
15
  hookEventKindSchema,
13
16
  mapClaudeCodeHook,
14
17
  mapCodexHook,
@@ -16,23 +19,31 @@ import {
16
19
  matchesGitHubRepository,
17
20
  normalizeGitHubRepository,
18
21
  pollDeviceToken,
22
+ presenceStatePath,
19
23
  redact,
20
24
  requestDeviceAuthorization,
25
+ runPresenceHeartbeat,
21
26
  safeMetadata,
22
27
  safeValueSchema,
23
- sanitizeRemote
24
- } from "./chunk-OTDZMJDK.js";
28
+ sanitizeRemote,
29
+ startPresenceSession,
30
+ stopPresenceSession,
31
+ updatePresenceSession
32
+ } from "./chunk-FLTLCZ6E.js";
25
33
  export {
26
34
  JsonFileOfflineQueue,
27
35
  MemoryOfflineQueue,
36
+ PRESENCE_HEARTBEAT_INTERVAL_MS,
28
37
  SignedBatchClient,
29
38
  SystemCredentialStore,
30
39
  agentSourceSchema,
31
40
  canonicalHookEventSchema,
32
41
  createHookEvent,
33
42
  detectRepository,
43
+ detectWorkingTreeMetrics,
34
44
  deviceAuthorizationSchema,
35
45
  deviceTokenResponseSchema,
46
+ eventTypeForHook,
36
47
  hookEventKindSchema,
37
48
  mapClaudeCodeHook,
38
49
  mapCodexHook,
@@ -40,9 +51,14 @@ export {
40
51
  matchesGitHubRepository,
41
52
  normalizeGitHubRepository,
42
53
  pollDeviceToken,
54
+ presenceStatePath,
43
55
  redact,
44
56
  requestDeviceAuthorization,
57
+ runPresenceHeartbeat,
45
58
  safeMetadata,
46
59
  safeValueSchema,
47
- sanitizeRemote
60
+ sanitizeRemote,
61
+ startPresenceSession,
62
+ stopPresenceSession,
63
+ updatePresenceSession
48
64
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wibeco/bridge",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Privacy-first live activity bridge for Cursor, Claude Code, and Codex.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,7 +14,8 @@
14
14
  "type": "module",
15
15
  "bin": {
16
16
  "wibe": "dist/cli.js",
17
- "wibe-bridge": "dist/cli.js"
17
+ "wibe-bridge": "dist/cli.js",
18
+ "wibe-codex-hook": "dist/codex-hook.js"
18
19
  },
19
20
  "exports": {
20
21
  ".": "./dist/index.js"
@@ -4,6 +4,8 @@ These files are inert examples. Review and merge `settings.json.example` into th
4
4
  Claude Code settings file. It uses command hooks because the local bridge must normalize and redact
5
5
  the payload before network delivery. Do not point an HTTP hook directly at a collector: Claude hook
6
6
  payloads can include prompts, tool inputs/results, and other raw content.
7
+ `SessionStart` owns a metadata-only heartbeat every 45 seconds until `SessionEnd`; `Stop` and tool
8
+ hooks remain operational signals and are not published as presence.
7
9
 
8
10
  Run `wibe setup` to authorize a revocable, project-scoped device token. Interactive credentials are
9
11
  stored in the operating-system keychain; hooks do not require repository secrets. The bridge
@@ -12,3 +12,6 @@ keychain. Hooks do not require repository signing secrets.
12
12
  The MCP block connects Codex to Wibe's remote `/api/mcp` endpoint. Codex completes OAuth separately
13
13
  from the bridge device token. Set `WIBE_APP_URL` to the same Wibe origin used during setup. Codex
14
14
  notification coverage can be narrower than Cursor or Claude Code lifecycle hooks.
15
+ Codex appends one JSON notification argument to `wibe-codex-hook`; the bridge extracts only opaque
16
+ `thread-id`/`turn-id` and approved model metadata. Where session hooks are available, they own a
17
+ metadata-only 45-second heartbeat that ends cleanly with `SessionEnd`.
@@ -1,6 +1,6 @@
1
1
  # Codex passes one JSON notification argument to this command. The adapter only keeps allow-listed
2
2
  # operational metadata and never forwards the full argument.
3
- notify = ["npx", "-y", "@wibeco/bridge@latest", "emit", "--adapter", "codex", "--event", "notify"]
3
+ notify = ["npx", "-y", "--package", "@wibeco/bridge@latest", "wibe-codex-hook"]
4
4
 
5
5
  [mcp_servers.wibe]
6
6
  url = "${WIBE_APP_URL}/api/mcp"
@@ -10,6 +10,16 @@
10
10
  ]
11
11
  }
12
12
  ],
13
+ "SessionEnd": [
14
+ {
15
+ "hooks": [
16
+ {
17
+ "type": "command",
18
+ "command": "npx -y @wibeco/bridge@latest emit --adapter codex --event session-end"
19
+ }
20
+ ]
21
+ }
22
+ ],
13
23
  "UserPromptSubmit": [
14
24
  {
15
25
  "hooks": [
@@ -3,6 +3,10 @@
3
3
  These files are reviewable setup templates. Wibe installs them only when the destination does not
4
4
  already exist; otherwise merge `hooks.json.example` into `.cursor/hooks.json` yourself. Hook commands
5
5
  run `npx -y @wibeco/bridge@latest emit`, which normalizes, redacts, queues, and delivers activity.
6
+ The emit command always returns a valid empty JSON object to Cursor and fails open, so telemetry
7
+ delivery can never block an agent tool call.
8
+ Session hooks own a metadata-only heartbeat every 45 seconds and stop it at session end. Routine
9
+ prompt, tool, shell, and MCP lifecycle hooks are never reclassified as presence.
6
10
 
7
11
  Run `wibe setup` to authorize a revocable, project-scoped device token. Interactive credentials are
8
12
  stored in the operating-system keychain; hooks do not require repository secrets. The mapper drops
@@ -3,57 +3,68 @@
3
3
  "hooks": {
4
4
  "sessionStart": [
5
5
  {
6
- "command": "npx -y @wibeco/bridge@latest emit --adapter cursor --event session-start"
6
+ "command": "npx -y @wibeco/bridge@latest emit --adapter cursor --event session-start",
7
+ "failClosed": false
7
8
  }
8
9
  ],
9
10
  "sessionEnd": [
10
11
  {
11
- "command": "npx -y @wibeco/bridge@latest emit --adapter cursor --event session-end"
12
+ "command": "npx -y @wibeco/bridge@latest emit --adapter cursor --event session-end",
13
+ "failClosed": false
12
14
  }
13
15
  ],
14
16
  "beforeSubmitPrompt": [
15
17
  {
16
- "command": "npx -y @wibeco/bridge@latest emit --adapter cursor --event before-submit-prompt"
18
+ "command": "npx -y @wibeco/bridge@latest emit --adapter cursor --event before-submit-prompt",
19
+ "failClosed": false
17
20
  }
18
21
  ],
19
22
  "afterAgentResponse": [
20
23
  {
21
- "command": "npx -y @wibeco/bridge@latest emit --adapter cursor --event after-agent-response"
24
+ "command": "npx -y @wibeco/bridge@latest emit --adapter cursor --event after-agent-response",
25
+ "failClosed": false
22
26
  }
23
27
  ],
24
28
  "preToolUse": [
25
29
  {
26
- "command": "npx -y @wibeco/bridge@latest emit --adapter cursor --event before-tool"
30
+ "command": "npx -y @wibeco/bridge@latest emit --adapter cursor --event before-tool",
31
+ "failClosed": false
27
32
  }
28
33
  ],
29
34
  "postToolUse": [
30
35
  {
31
- "command": "npx -y @wibeco/bridge@latest emit --adapter cursor --event after-tool"
36
+ "command": "npx -y @wibeco/bridge@latest emit --adapter cursor --event after-tool",
37
+ "failClosed": false
32
38
  }
33
39
  ],
34
40
  "afterFileEdit": [
35
41
  {
36
- "command": "npx -y @wibeco/bridge@latest emit --adapter cursor --event after-file-edit"
42
+ "command": "npx -y @wibeco/bridge@latest emit --adapter cursor --event after-file-edit",
43
+ "failClosed": false
37
44
  }
38
45
  ],
39
46
  "beforeShellExecution": [
40
47
  {
41
- "command": "npx -y @wibeco/bridge@latest emit --adapter cursor --event before-shell-execution"
48
+ "command": "npx -y @wibeco/bridge@latest emit --adapter cursor --event before-shell-execution",
49
+ "failClosed": false
42
50
  }
43
51
  ],
44
52
  "afterShellExecution": [
45
53
  {
46
- "command": "npx -y @wibeco/bridge@latest emit --adapter cursor --event after-shell-execution"
54
+ "command": "npx -y @wibeco/bridge@latest emit --adapter cursor --event after-shell-execution",
55
+ "failClosed": false
47
56
  }
48
57
  ],
49
58
  "beforeMCPExecution": [
50
59
  {
51
- "command": "npx -y @wibeco/bridge@latest emit --adapter cursor --event before-mcp-execution"
60
+ "command": "npx -y @wibeco/bridge@latest emit --adapter cursor --event before-mcp-execution",
61
+ "failClosed": false
52
62
  }
53
63
  ],
54
64
  "afterMCPExecution": [
55
65
  {
56
- "command": "npx -y @wibeco/bridge@latest emit --adapter cursor --event after-mcp-execution"
66
+ "command": "npx -y @wibeco/bridge@latest emit --adapter cursor --event after-mcp-execution",
67
+ "failClosed": false
57
68
  }
58
69
  ]
59
70
  }