@wibeco/bridge 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,6 +30,9 @@ npx --yes @wibeco/bridge@latest doctor --repository <owner/repository>
30
30
  Use `--adapter claude-code` or `--adapter codex` for another supported agent.
31
31
  The setup command opens a one-time browser authorization and stores the
32
32
  project-scoped device credential in the operating-system keychain.
33
+ Matching reruns reuse that credential and retry verification without creating
34
+ another device. Use `setup --reauthorize` only when doctor reports that the
35
+ saved token was rejected or revoked.
33
36
 
34
37
  `setup` keeps reviewable templates under `.wibe/integrations/<adapter>` and
35
38
  installs native project configuration only when the destination file does not
@@ -12,7 +12,7 @@ import {
12
12
  normalizeGitHubRepository,
13
13
  pollDeviceToken,
14
14
  requestDeviceAuthorization
15
- } from "./chunk-BL74PDGC.js";
15
+ } from "./chunk-OTDZMJDK.js";
16
16
 
17
17
  // src/cli/commands.ts
18
18
  import { access, cp, mkdir, readFile, writeFile } from "fs/promises";
@@ -33,9 +33,7 @@ async function setupCommand(requestedAdapter, options = {}) {
33
33
  await assertExpectedRepository(cwd, expectedRepository);
34
34
  }
35
35
  const adapter = await detectAdapter(requestedAdapter, cwd);
36
- const packageRoot = resolve(fileURLToPath(new URL("../../../", import.meta.url)));
37
- const packagedTemplate = join(packageRoot, "templates", adapter);
38
- const source = await exists(packagedTemplate) ? packagedTemplate : resolve(packageRoot, "..", "..", "integrations", adapter);
36
+ const source = await resolveTemplateSource(adapter);
39
37
  const destination = join(cwd, ".wibe", "integrations", adapter);
40
38
  if (!await exists(destination)) {
41
39
  await mkdir(join(cwd, ".wibe", "integrations"), { recursive: true });
@@ -55,6 +53,24 @@ async function setupCommand(requestedAdapter, options = {}) {
55
53
  `Existing ${projectConfigPath} targets a different project, URL, adapter, or repository. It was not overwritten; remove it intentionally or rerun setup with matching options.`
56
54
  );
57
55
  }
56
+ const existingCredential = existingProjectConfig ? await loadCredential(cwd) : null;
57
+ if (!options.reauthorize && existingCredential && existingCredential.projectId === options.projectId && existingCredential.appUrl.replace(/\/$/, "") === appUrl) {
58
+ const installedNativeFiles2 = await installNativeConfigs(
59
+ adapter,
60
+ source,
61
+ cwd,
62
+ appUrl
63
+ );
64
+ const heartbeat2 = await sendVerificationHeartbeat(
65
+ existingCredential,
66
+ adapter,
67
+ "setup"
68
+ );
69
+ return {
70
+ exitCode: heartbeat2.error ? 1 : 0,
71
+ message: heartbeat2.error ? `Wibe is already authorized, but verification failed (${heartbeat2.error}). No new device was created. Retry with wibe doctor; if the token is rejected, rerun setup with --reauthorize.` : `Wibe was already authorized and the event connection was verified. Installed ${installedNativeFiles2.length} missing native config file(s); existing configs were left untouched.`
72
+ };
73
+ }
58
74
  const authorization = await requestDeviceAuthorization({
59
75
  appUrl,
60
76
  projectId: options.projectId,
@@ -213,16 +229,7 @@ async function doctorCommand(cwd = process.cwd(), requestedRepository) {
213
229
  adapterError = error instanceof Error ? error.message : String(error);
214
230
  }
215
231
  const heartbeat = credential && adapter && repositoryMatches ? await sendVerificationHeartbeat(credential, adapter, "doctor") : void 0;
216
- const nativeHooks = await Promise.all([
217
- exists(join(cwd, ".cursor", "hooks.json")),
218
- exists(join(cwd, ".claude", "settings.json")),
219
- exists(join(cwd, ".codex", "hooks.json"))
220
- ]);
221
- const nativeMcp = await Promise.all([
222
- exists(join(cwd, ".cursor", "mcp.json")),
223
- exists(join(cwd, ".mcp.json")),
224
- exists(join(cwd, ".codex", "config.toml"))
225
- ]);
232
+ const nativeConfig = adapter ? await validateNativeConfigs(adapter, cwd) : { hooks: false, mcp: false };
226
233
  const checks = [
227
234
  ["node", Number(process.versions.node.split(".")[0]) >= 20],
228
235
  ["git repository", Boolean(repository)],
@@ -239,9 +246,12 @@ async function doctorCommand(cwd = process.cwd(), requestedRepository) {
239
246
  adapterError ? `adapter detection (${adapterError})` : "adapter detection",
240
247
  Boolean(adapter)
241
248
  ],
242
- ["agent hooks", nativeHooks.some(Boolean)],
243
- ["MCP configuration", nativeMcp.some(Boolean)],
244
- ["verification heartbeat", Boolean(heartbeat && !heartbeat.error)]
249
+ ["agent hooks configuration", nativeConfig.hooks],
250
+ ["MCP endpoint configuration", nativeConfig.mcp],
251
+ [
252
+ heartbeat?.error ? `verification heartbeat (${heartbeat.error})` : "verification heartbeat",
253
+ Boolean(heartbeat && !heartbeat.error)
254
+ ]
245
255
  ];
246
256
  const failures = checks.filter(([, okay]) => !okay).length;
247
257
  return {
@@ -321,6 +331,73 @@ async function exists(path) {
321
331
  return false;
322
332
  }
323
333
  }
334
+ async function validateNativeConfigs(adapter, cwd) {
335
+ if (adapter === "codex") {
336
+ const hooks = await validJson(
337
+ join(cwd, ".codex", "hooks.json"),
338
+ (value) => isRecord(value.hooks)
339
+ );
340
+ let mcp = false;
341
+ try {
342
+ const config = await readFile(join(cwd, ".codex", "config.toml"), "utf8");
343
+ mcp = config.includes("[mcp_servers.wibe]") && config.includes("/api/mcp");
344
+ } catch {
345
+ mcp = false;
346
+ }
347
+ return { hooks, mcp };
348
+ }
349
+ const hookPath = adapter === "cursor" ? join(cwd, ".cursor", "hooks.json") : join(cwd, ".claude", "settings.json");
350
+ const mcpPath = adapter === "cursor" ? join(cwd, ".cursor", "mcp.json") : join(cwd, ".mcp.json");
351
+ return {
352
+ hooks: await validJson(hookPath, (value) => isRecord(value.hooks)),
353
+ mcp: await validJson(mcpPath, (value) => {
354
+ if (!isRecord(value.mcpServers)) return false;
355
+ const wibe = value.mcpServers.wibe;
356
+ return isRecord(wibe) && typeof wibe.url === "string" && wibe.url.replace(/\/$/, "").endsWith("/api/mcp");
357
+ })
358
+ };
359
+ }
360
+ async function validJson(path, predicate) {
361
+ try {
362
+ const value = JSON.parse(await readFile(path, "utf8"));
363
+ return isRecord(value) && predicate(value);
364
+ } catch {
365
+ return false;
366
+ }
367
+ }
368
+ function isRecord(value) {
369
+ return typeof value === "object" && value !== null && !Array.isArray(value);
370
+ }
371
+ async function resolveTemplateSource(adapter, moduleUrl = import.meta.url) {
372
+ const moduleDir = resolve(fileURLToPath(new URL(".", moduleUrl)));
373
+ const candidates = [
374
+ resolve(moduleDir, ".."),
375
+ resolve(moduleDir, "../..")
376
+ ];
377
+ for (const candidate of candidates) {
378
+ try {
379
+ const manifest = JSON.parse(
380
+ await readFile(join(candidate, "package.json"), "utf8")
381
+ );
382
+ if (manifest.name !== "@wibeco/bridge") continue;
383
+ const packagedTemplate = join(candidate, "templates", adapter);
384
+ if (await exists(packagedTemplate)) return packagedTemplate;
385
+ const developmentTemplate = resolve(
386
+ candidate,
387
+ "..",
388
+ "..",
389
+ "integrations",
390
+ adapter
391
+ );
392
+ if (await exists(developmentTemplate)) return developmentTemplate;
393
+ } catch {
394
+ continue;
395
+ }
396
+ }
397
+ throw new Error(
398
+ `Could not locate the ${adapter} integration templates relative to the Wibe bridge package.`
399
+ );
400
+ }
324
401
  function parseAdapter(value) {
325
402
  if (value && ADAPTERS.includes(value)) return value;
326
403
  throw new Error(`Adapter must be one of: ${ADAPTERS.join(", ")}`);
@@ -149,7 +149,13 @@ var events2 = {
149
149
  "tool-complete": "tool.completed",
150
150
  "command-start": "shell.started",
151
151
  "command-complete": "shell.completed",
152
- "file-change": "file.changed"
152
+ "file-change": "file.changed",
153
+ "user-prompt-submit": "lifecycle.before",
154
+ "pre-tool-use": "tool.started",
155
+ "post-tool-use": "tool.completed",
156
+ "subagent-start": "lifecycle.before",
157
+ "subagent-stop": "lifecycle.after",
158
+ stop: "session.ended"
153
159
  };
154
160
  var safeKeys2 = ["event", "tool_name", "command_type", "model", "reason", "turn_id"];
155
161
  function mapCodexHook(eventName, input) {
@@ -274,7 +280,22 @@ var SignedBatchClient = class {
274
280
  authorization: `Bearer ${this.options.accessToken}`
275
281
  }
276
282
  });
277
- if (!response.ok) throw new Error(`Collector returned HTTP ${response.status}`);
283
+ if (!response.ok) {
284
+ const requestId = response.headers.get("x-request-id");
285
+ let detail = "";
286
+ try {
287
+ const body2 = await response.json();
288
+ const code = typeof body2.code === "string" ? body2.code.slice(0, 80) : void 0;
289
+ const message = typeof body2.error === "string" ? body2.error.slice(0, 160) : void 0;
290
+ detail = [code, message].filter(Boolean).join(": ");
291
+ } catch {
292
+ }
293
+ const suffix = [
294
+ detail ? `: ${detail}` : "",
295
+ requestId ? ` (request ${requestId.slice(0, 80)})` : ""
296
+ ].join("");
297
+ throw new Error(`Collector returned HTTP ${response.status}${suffix}`);
298
+ }
278
299
  await this.options.queue.remove(events4.map((event) => event.id));
279
300
  return { sent: events4.length, remaining: await this.options.queue.size() };
280
301
  } catch (error) {
package/dist/cli.js CHANGED
@@ -5,16 +5,17 @@ import {
5
5
  parseAdapter,
6
6
  setupCommand,
7
7
  statusCommand
8
- } from "./chunk-5CBPZCBE.js";
9
- import "./chunk-BL74PDGC.js";
8
+ } from "./chunk-J2HWZMXA.js";
9
+ import "./chunk-OTDZMJDK.js";
10
10
 
11
11
  // src/cli.ts
12
12
  var HELP = `wibe-bridge <command>
13
13
 
14
14
  Commands:
15
- setup --project <uuid> [--adapter <cursor|claude-code|codex>] [--url <wibe-url>] [--repository <owner/repo>]
15
+ setup --project <uuid> [--adapter <cursor|claude-code|codex>] [--url <wibe-url>] [--repository <owner/repo>] [--reauthorize]
16
16
  Auto-detects one adapter from the environment or repository config.
17
17
  Use --adapter when multiple agent configs are present.
18
+ Use --reauthorize only to replace a rejected or revoked device token.
18
19
  status
19
20
  emit --adapter <name> --event <hook-name> (JSON payload on stdin)
20
21
  doctor [--repository <owner/repo>]`;
@@ -26,7 +27,8 @@ async function main() {
26
27
  result = await setupCommand(adapterOption ? parseAdapter(adapterOption) : void 0, {
27
28
  projectId: option(args, "--project"),
28
29
  appUrl: option(args, "--url"),
29
- expectedRepository: option(args, "--repository")
30
+ expectedRepository: option(args, "--repository"),
31
+ reauthorize: args.includes("--reauthorize")
30
32
  });
31
33
  } else if (command === "status") {
32
34
  result = await statusCommand();
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  emitCommand
4
- } from "./chunk-5CBPZCBE.js";
5
- import "./chunk-BL74PDGC.js";
4
+ } from "./chunk-J2HWZMXA.js";
5
+ import "./chunk-OTDZMJDK.js";
6
6
 
7
7
  // src/codex-hook.ts
8
8
  async function main() {
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  safeMetadata,
22
22
  safeValueSchema,
23
23
  sanitizeRemote
24
- } from "./chunk-BL74PDGC.js";
24
+ } from "./chunk-OTDZMJDK.js";
25
25
  export {
26
26
  JsonFileOfflineQueue,
27
27
  MemoryOfflineQueue,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wibeco/bridge",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Privacy-first live activity bridge for Cursor, Claude Code, and Codex.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -5,10 +5,10 @@ Claude Code settings file. It uses command hooks because the local bridge must n
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
7
 
8
- Configure `WIBE_COLLECTOR_URL`, `WIBE_KEY_ID`, and `WIBE_SIGNING_SECRET` in the hook environment.
9
- Store the secret in an OS keychain/secret manager. The bridge allow-list excludes prompt, content,
10
- messages, command text, tool arguments/results, and transcripts.
8
+ Run `wibe setup` to authorize a revocable, project-scoped device token. Interactive credentials are
9
+ stored in the operating-system keychain; hooks do not require repository secrets. The bridge
10
+ allow-list excludes prompt, content, messages, command text, tool arguments/results, and transcripts.
11
11
 
12
- `mcp.json.example` is only a config shape. Replace `WIBE_MCP_COMMAND` with an installed MCP server
13
- executable and inject `WIBE_MCP_TOKEN_FROM_KEYCHAIN` at launch. The bridge does not yet implement an
14
- MCP server.
12
+ `mcp.json.example` connects Claude Code to Wibe's remote MCP endpoint at
13
+ `${env:WIBE_APP_URL}/api/mcp`. The MCP client completes OAuth separately from the bridge device token.
14
+ Set `WIBE_APP_URL` to the same Wibe origin used during setup.
@@ -1,13 +1,14 @@
1
1
  # Codex template
2
2
 
3
- This is an inert config fragment. Review and merge `config.toml.example` into Codex's configuration.
4
- The `notify` command receives Codex's JSON notification argument, maps allow-listed fields in-process,
5
- and queues or sends only the canonical event. Prompt text, messages, tool data, command text, file
6
- content, and transcripts are not retained.
3
+ These are reviewable setup templates. `config.toml.example` configures Codex notifications and MCP;
4
+ `hooks.json.example` provides lifecycle hooks where supported. Wibe installs native files only when
5
+ the destination does not already exist. The bridge maps allow-listed fields in-process and queues or
6
+ sends only canonical events. Prompt text, messages, tool data, command text, file content, and
7
+ transcripts are not retained.
7
8
 
8
- Configure `WIBE_COLLECTOR_URL`, `WIBE_KEY_ID`, and `WIBE_SIGNING_SECRET` in the Codex environment,
9
- with the signing secret supplied by an OS keychain/secret manager.
9
+ Run `wibe setup` to authorize a revocable, project-scoped device token stored in the operating-system
10
+ keychain. Hooks do not require repository signing secrets.
10
11
 
11
- The MCP block is only a config shape. Replace `WIBE_MCP_COMMAND` with an installed MCP server
12
- executable and inject `WIBE_MCP_TOKEN_FROM_KEYCHAIN` at launch. The bridge does not yet implement an
13
- MCP server. Codex notification coverage is narrower than Cursor or Claude Code lifecycle hooks.
12
+ The MCP block connects Codex to Wibe's remote `/api/mcp` endpoint. Codex completes OAuth separately
13
+ from the bridge device token. Set `WIBE_APP_URL` to the same Wibe origin used during setup. Codex
14
+ notification coverage can be narrower than Cursor or Claude Code lifecycle hooks.
@@ -1,13 +1,13 @@
1
1
  # Cursor template
2
2
 
3
- These files are inert examples. Review Cursor's hook schema for your installed release, then merge
4
- `hooks.json.example` into `.cursor/hooks.json`; hook names can change between releases. Commands use
5
- `npx --no-install`, so they never download packages during a hook.
3
+ These files are reviewable setup templates. Wibe installs them only when the destination does not
4
+ already exist; otherwise merge `hooks.json.example` into `.cursor/hooks.json` yourself. Hook commands
5
+ run `npx -y @wibeco/bridge@latest emit`, which normalizes, redacts, queues, and delivers activity.
6
6
 
7
- Configure `WIBE_COLLECTOR_URL`, `WIBE_KEY_ID`, and `WIBE_SIGNING_SECRET` in the process environment.
8
- Prefer an OS keychain/secret manager for the signing secret. The mapper drops prompt, content, file
9
- body, command text, tool arguments/results, and transcripts.
7
+ Run `wibe setup` to authorize a revocable, project-scoped device token. Interactive credentials are
8
+ stored in the operating-system keychain; hooks do not require repository secrets. The mapper drops
9
+ prompt, content, file body, command text, tool arguments/results, and transcripts.
10
10
 
11
- `mcp.json.example` is only a config shape. Replace `WIBE_MCP_COMMAND` with an installed MCP server
12
- executable and inject `WIBE_MCP_TOKEN_FROM_KEYCHAIN` at launch. The bridge does not yet implement an
13
- MCP server and this template must not be enabled unchanged.
11
+ `mcp.json.example` connects Cursor to Wibe's remote MCP endpoint at
12
+ `${env:WIBE_APP_URL}/api/mcp`. Cursor completes the endpoint's OAuth flow separately from the bridge
13
+ device token. Set `WIBE_APP_URL` to the same Wibe origin used during setup.