@wibeco/bridge 0.1.2 → 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.
- package/dist/chunk-FLTLCZ6E.js +1074 -0
- package/dist/{chunk-J2HWZMXA.js → chunk-YV3UV2XJ.js} +81 -7
- package/dist/cli.js +11 -2
- package/dist/codex-hook.js +5 -2
- package/dist/index.d.ts +33 -4
- package/dist/index.js +19 -3
- package/package.json +3 -2
- package/templates/claude-code/README.md +2 -0
- package/templates/codex/README.md +3 -0
- package/templates/codex/config.toml.example +1 -1
- package/templates/codex/hooks.json.example +10 -0
- package/templates/cursor/README.md +2 -0
- package/dist/chunk-OTDZMJDK.js +0 -612
|
@@ -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
|
-
|
|
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
|
-
|
|
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: "
|
|
493
|
-
|
|
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-
|
|
9
|
-
import
|
|
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>
|
|
@@ -44,6 +46,13 @@ async function main() {
|
|
|
44
46
|
return;
|
|
45
47
|
} else if (command === "doctor") {
|
|
46
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;
|
|
47
56
|
} else {
|
|
48
57
|
process.stdout.write(`${HELP}
|
|
49
58
|
`);
|
package/dist/codex-hook.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
emitCommand
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
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
|
-
|
|
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.
|
|
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", "
|
|
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"
|
|
@@ -5,6 +5,8 @@ already exist; otherwise merge `hooks.json.example` into `.cursor/hooks.json` yo
|
|
|
5
5
|
run `npx -y @wibeco/bridge@latest emit`, which normalizes, redacts, queues, and delivers activity.
|
|
6
6
|
The emit command always returns a valid empty JSON object to Cursor and fails open, so telemetry
|
|
7
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.
|
|
8
10
|
|
|
9
11
|
Run `wibe setup` to authorize a revocable, project-scoped device token. Interactive credentials are
|
|
10
12
|
stored in the operating-system keychain; hooks do not require repository secrets. The mapper drops
|