@wibeco/bridge 0.2.1 → 0.2.2

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.
@@ -15,6 +15,7 @@ var hookEventKindSchema = z.enum([
15
15
  "shell.completed",
16
16
  "mcp.started",
17
17
  "mcp.completed",
18
+ "progress.shared",
18
19
  "unknown"
19
20
  ]);
20
21
  var safeScalarSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
@@ -328,6 +329,7 @@ function eventTypeForHook(event) {
328
329
  if (event.kind === "presence.heartbeat") return "presence.heartbeat";
329
330
  if (event.kind === "session.ended") return "presence.stopped";
330
331
  if (event.kind === "file.changed") return "workspace.files_changed";
332
+ if (event.kind === "progress.shared") return "agent.progress_shared";
331
333
  if (event.kind === "shell.completed" && event.outcome && isTestHookEvent(event)) {
332
334
  return "workspace.test_completed";
333
335
  }
@@ -352,7 +354,13 @@ function toEnvelope(event, options) {
352
354
  ...Array.isArray(event.metadata.paths) ? event.metadata.paths.filter((path) => typeof path === "string") : [],
353
355
  ...typeof event.metadata.path === "string" ? [event.metadata.path] : []
354
356
  ];
355
- const payload = event.kind === "file.changed" ? {
357
+ const payload = event.kind === "progress.shared" ? {
358
+ title: event.metadata.title,
359
+ summary: event.metadata.summary,
360
+ paths,
361
+ phase: event.metadata.phase,
362
+ confidence: event.metadata.confidence
363
+ } : event.kind === "file.changed" ? {
356
364
  paths,
357
365
  tool: event.source,
358
366
  agent_name: event.source,
@@ -556,6 +564,7 @@ import { homedir } from "os";
556
564
  import { join } from "path";
557
565
  var PRESENCE_HEARTBEAT_INTERVAL_MS = 45e3;
558
566
  var MAX_SESSION_DURATION_MS = 12 * 60 * 60 * 1e3;
567
+ var MAX_SESSION_LINE_COUNT = 1e7;
559
568
  async function startPresenceSession(source, sessionId, cwd = process.cwd()) {
560
569
  const statePath = presenceStatePath(source, sessionId, cwd);
561
570
  await rm(statePath, { force: true });
@@ -611,11 +620,14 @@ async function updatePresenceSession(source, sessionId, event, cwd = process.cwd
611
620
  const state = await readPresenceState(statePath);
612
621
  if (!state) return void 0;
613
622
  const measuredAt = (/* @__PURE__ */ new Date()).toISOString();
614
- accrueModelTime(state, measuredAt);
623
+ const isHeartbeat = event.kind === "presence.heartbeat";
624
+ if (!isHeartbeat) accrueModelTime(state, measuredAt);
615
625
  const model = typeof event.metadata.model === "string" ? event.metadata.model : void 0;
616
626
  if (model) state.model = model;
617
- state.linesAdded += safeCount(event.metadata.lines_added);
618
- state.linesDeleted += safeCount(event.metadata.lines_deleted);
627
+ if (!isHeartbeat) {
628
+ state.linesAdded += safeCount(event.metadata.lines_added);
629
+ state.linesDeleted += safeCount(event.metadata.lines_deleted);
630
+ }
619
631
  const workingLinesAdded = optionalCount(
620
632
  event.metadata.working_tree_lines_added
621
633
  );
@@ -624,17 +636,19 @@ async function updatePresenceSession(source, sessionId, event, cwd = process.cwd
624
636
  );
625
637
  if (workingLinesAdded !== void 0) {
626
638
  state.baselineLinesAdded ??= workingLinesAdded;
627
- state.linesAdded = Math.max(
628
- state.linesAdded,
639
+ const workingDelta = Math.max(
640
+ 0,
629
641
  workingLinesAdded - state.baselineLinesAdded
630
642
  );
643
+ state.linesAdded = isHeartbeat && state.linesAdded > MAX_SESSION_LINE_COUNT ? workingDelta : Math.max(state.linesAdded, workingDelta);
631
644
  }
632
645
  if (workingLinesDeleted !== void 0) {
633
646
  state.baselineLinesDeleted ??= workingLinesDeleted;
634
- state.linesDeleted = Math.max(
635
- state.linesDeleted,
647
+ const workingDelta = Math.max(
648
+ 0,
636
649
  workingLinesDeleted - state.baselineLinesDeleted
637
650
  );
651
+ state.linesDeleted = isHeartbeat && state.linesDeleted > MAX_SESSION_LINE_COUNT ? workingDelta : Math.max(state.linesDeleted, workingDelta);
638
652
  }
639
653
  const paths = [
640
654
  ...Array.isArray(event.metadata.paths) ? event.metadata.paths.filter(
@@ -17,7 +17,7 @@ import {
17
17
  startPresenceSession,
18
18
  stopPresenceSession,
19
19
  updatePresenceSession
20
- } from "./chunk-FLTLCZ6E.js";
20
+ } from "./chunk-A7KGZ7NX.js";
21
21
 
22
22
  // src/cli/commands.ts
23
23
  import { access, cp, mkdir, readFile, writeFile } from "fs/promises";
@@ -64,7 +64,8 @@ async function setupCommand(requestedAdapter, options = {}) {
64
64
  adapter,
65
65
  source,
66
66
  cwd,
67
- appUrl
67
+ appUrl,
68
+ options.projectId
68
69
  );
69
70
  const heartbeat2 = await sendVerificationHeartbeat(
70
71
  existingCredential,
@@ -153,7 +154,8 @@ Confirm code ${authorization.user_code}
153
154
  adapter,
154
155
  source,
155
156
  cwd,
156
- appUrl
157
+ appUrl,
158
+ token.projectId
157
159
  );
158
160
  const heartbeat = await sendVerificationHeartbeat(credential, adapter, "setup");
159
161
  return {
@@ -235,7 +237,16 @@ async function emitCommand(adapter, eventName, input) {
235
237
  mappedEvent.sessionId,
236
238
  mappedEvent
237
239
  );
238
- if (mappedEvent.kind === "presence.heartbeat" && metrics) {
240
+ if (mappedEvent.kind === "file.changed" && metrics) {
241
+ mappedEvent = {
242
+ ...mappedEvent,
243
+ metadata: {
244
+ ...mappedEvent.metadata,
245
+ lines_added: metrics.lines_added,
246
+ lines_deleted: metrics.lines_deleted
247
+ }
248
+ };
249
+ } else if (mappedEvent.kind === "presence.heartbeat" && metrics) {
239
250
  const publicMetadata = { ...mappedEvent.metadata };
240
251
  delete publicMetadata.working_tree_paths;
241
252
  delete publicMetadata.paths;
@@ -274,6 +285,62 @@ async function emitCommand(adapter, eventName, input) {
274
285
  message: result.error ? `Queued ${event.kind}; delivery failed: ${result.error}` : `Delivered ${result.sent} event(s); ${result.remaining} queued.`
275
286
  };
276
287
  }
288
+ async function shareProgressCommand(options, cwd = process.cwd()) {
289
+ const projectConfig = await readProjectConfig(
290
+ join(cwd, ".wibe", "project.json")
291
+ );
292
+ const credential = await loadCredential(cwd);
293
+ if (!projectConfig || !credential) {
294
+ throw new Error("Wibe is not authorized in this repository. Run wibe setup first.");
295
+ }
296
+ const summary = options.summary?.trim() ?? "";
297
+ const wordCount = summary.split(/\s+/).filter(Boolean).length;
298
+ if (wordCount < 8 || wordCount > 22 || summary.length > 500) {
299
+ throw new Error("Progress summary must contain 8\u201322 words.");
300
+ }
301
+ const title = options.title?.trim();
302
+ if (title && title.length > 120) {
303
+ throw new Error("Progress title must be 120 characters or fewer.");
304
+ }
305
+ const phases = [
306
+ "planning",
307
+ "implementing",
308
+ "validating",
309
+ "blocked",
310
+ "shipped"
311
+ ];
312
+ if (options.phase && !phases.includes(options.phase)) {
313
+ throw new Error(`Progress phase must be one of: ${phases.join(", ")}.`);
314
+ }
315
+ if (options.confidence !== void 0 && (!Number.isFinite(options.confidence) || options.confidence < 0 || options.confidence > 1)) {
316
+ throw new Error("Progress confidence must be between 0 and 1.");
317
+ }
318
+ const repo = await detectRepository(cwd);
319
+ const event = createHookEvent({
320
+ source: projectConfig.adapter,
321
+ kind: "progress.shared",
322
+ metadata: {
323
+ summary,
324
+ ...title ? { title } : {},
325
+ ...options.phase ? { phase: options.phase } : {},
326
+ ...options.confidence !== void 0 ? { confidence: options.confidence } : {}
327
+ },
328
+ ...repo ? { repo } : {}
329
+ });
330
+ const result = await new SignedBatchClient({
331
+ endpoint: `${credential.appUrl}/api/events/batch`,
332
+ accessToken: credential.accessToken,
333
+ organizationId: credential.organizationId,
334
+ projectId: credential.projectId,
335
+ repositoryId: credential.repositoryId,
336
+ deviceId: credential.deviceId,
337
+ queue: new JsonFileOfflineQueue(queuePath())
338
+ }).capture(event);
339
+ return {
340
+ exitCode: result.error ? 1 : 0,
341
+ message: result.error ? `Progress was queued; delivery failed: ${result.error}` : `Shared progress with Wibe; ${result.remaining} event(s) queued.`
342
+ };
343
+ }
277
344
  async function doctorCommand(cwd = process.cwd(), requestedRepository) {
278
345
  const credential = await loadCredential(cwd);
279
346
  const projectConfig = await readProjectConfig(join(cwd, ".wibe", "project.json"));
@@ -293,7 +360,7 @@ async function doctorCommand(cwd = process.cwd(), requestedRepository) {
293
360
  adapterError = error instanceof Error ? error.message : String(error);
294
361
  }
295
362
  const heartbeat = credential && adapter && repositoryMatches ? await sendVerificationHeartbeat(credential, adapter, "doctor") : void 0;
296
- const nativeConfig = adapter ? await validateNativeConfigs(adapter, cwd) : { hooks: false, mcp: false };
363
+ const nativeConfig = adapter ? await validateNativeConfigs(adapter, cwd) : { hooks: false, mcp: false, activityRule: false };
297
364
  const checks = [
298
365
  ["node", Number(process.versions.node.split(".")[0]) >= 20],
299
366
  ["git repository", Boolean(repository)],
@@ -312,6 +379,7 @@ async function doctorCommand(cwd = process.cwd(), requestedRepository) {
312
379
  ],
313
380
  ["agent hooks configuration", nativeConfig.hooks],
314
381
  ["MCP endpoint configuration", nativeConfig.mcp],
382
+ ["agent activity instructions", nativeConfig.activityRule],
315
383
  [
316
384
  heartbeat?.error ? `verification heartbeat (${heartbeat.error})` : "verification heartbeat",
317
385
  Boolean(heartbeat && !heartbeat.error)
@@ -358,10 +426,11 @@ function openBrowser(url) {
358
426
  }
359
427
  execFile(executable, args, () => void 0);
360
428
  }
361
- async function installNativeConfigs(adapter, source, cwd, appUrl) {
429
+ async function installNativeConfigs(adapter, source, cwd, appUrl, projectId) {
362
430
  const files = adapter === "cursor" ? [
363
431
  ["hooks.json.example", ".cursor/hooks.json"],
364
- ["mcp.json.example", ".cursor/mcp.json"]
432
+ ["mcp.json.example", ".cursor/mcp.json"],
433
+ ["wibe-activity.mdc.example", ".cursor/rules/wibe-activity.mdc"]
365
434
  ] : adapter === "claude-code" ? [
366
435
  ["settings.json.example", ".claude/settings.json"],
367
436
  ["mcp.json.example", ".mcp.json"]
@@ -377,7 +446,7 @@ async function installNativeConfigs(adapter, source, cwd, appUrl) {
377
446
  await mkdir(resolve(destinationPath, ".."), { recursive: true });
378
447
  await writeFile(
379
448
  destinationPath,
380
- template.replaceAll("${env:WIBE_APP_URL}", appUrl).replaceAll("${WIBE_APP_URL}", appUrl),
449
+ template.replaceAll("${env:WIBE_APP_URL}", appUrl).replaceAll("${WIBE_APP_URL}", appUrl).replaceAll("${WIBE_PROJECT_ID}", projectId),
381
450
  { mode: 384 }
382
451
  );
383
452
  installed.push(destinationName);
@@ -408,7 +477,7 @@ async function validateNativeConfigs(adapter, cwd) {
408
477
  } catch {
409
478
  mcp = false;
410
479
  }
411
- return { hooks, mcp };
480
+ return { hooks, mcp, activityRule: true };
412
481
  }
413
482
  const hookPath = adapter === "cursor" ? join(cwd, ".cursor", "hooks.json") : join(cwd, ".claude", "settings.json");
414
483
  const mcpPath = adapter === "cursor" ? join(cwd, ".cursor", "mcp.json") : join(cwd, ".mcp.json");
@@ -418,9 +487,20 @@ async function validateNativeConfigs(adapter, cwd) {
418
487
  if (!isRecord(value.mcpServers)) return false;
419
488
  const wibe = value.mcpServers.wibe;
420
489
  return isRecord(wibe) && typeof wibe.url === "string" && wibe.url.replace(/\/$/, "").endsWith("/api/mcp");
421
- })
490
+ }),
491
+ activityRule: adapter !== "cursor" || await validText(
492
+ join(cwd, ".cursor", "rules", "wibe-activity.mdc"),
493
+ (value) => value.includes("alwaysApply: true") && value.includes("wibe_share_progress") && value.includes("wibe share-progress")
494
+ )
422
495
  };
423
496
  }
497
+ async function validText(path, predicate) {
498
+ try {
499
+ return predicate(await readFile(path, "utf8"));
500
+ } catch {
501
+ return false;
502
+ }
503
+ }
424
504
  async function validJson(path, predicate) {
425
505
  try {
426
506
  const value = JSON.parse(await readFile(path, "utf8"));
@@ -573,6 +653,7 @@ export {
573
653
  setupCommand,
574
654
  statusCommand,
575
655
  emitCommand,
656
+ shareProgressCommand,
576
657
  doctorCommand,
577
658
  parseAdapter
578
659
  };
package/dist/cli.js CHANGED
@@ -4,11 +4,12 @@ import {
4
4
  emitCommand,
5
5
  parseAdapter,
6
6
  setupCommand,
7
+ shareProgressCommand,
7
8
  statusCommand
8
- } from "./chunk-YV3UV2XJ.js";
9
+ } from "./chunk-XMEA243L.js";
9
10
  import {
10
11
  runPresenceHeartbeat
11
- } from "./chunk-FLTLCZ6E.js";
12
+ } from "./chunk-A7KGZ7NX.js";
12
13
 
13
14
  // src/cli.ts
14
15
  var HELP = `wibe-bridge <command>
@@ -20,6 +21,7 @@ Commands:
20
21
  Use --reauthorize only to replace a rejected or revoked device token.
21
22
  status
22
23
  emit --adapter <name> --event <hook-name> (JSON payload on stdin)
24
+ share-progress --summary <8-22 words> [--title <title>] [--phase <phase>] [--confidence <0-1>]
23
25
  doctor [--repository <owner/repo>]`;
24
26
  async function main() {
25
27
  const [command, ...args] = process.argv.slice(2);
@@ -46,6 +48,14 @@ async function main() {
46
48
  return;
47
49
  } else if (command === "doctor") {
48
50
  result = await doctorCommand(process.cwd(), option(args, "--repository"));
51
+ } else if (command === "share-progress") {
52
+ const confidence = option(args, "--confidence");
53
+ result = await shareProgressCommand({
54
+ summary: option(args, "--summary"),
55
+ title: option(args, "--title"),
56
+ phase: option(args, "--phase"),
57
+ confidence: confidence === void 0 ? void 0 : Number(confidence)
58
+ });
49
59
  } else if (command === "_presence-heartbeat") {
50
60
  parseAdapter(option(args, "--adapter"));
51
61
  const statePath = option(args, "--state");
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  emitCommand
4
- } from "./chunk-YV3UV2XJ.js";
5
- import "./chunk-FLTLCZ6E.js";
4
+ } from "./chunk-XMEA243L.js";
5
+ import "./chunk-A7KGZ7NX.js";
6
6
 
7
7
  // src/codex-hook.ts
8
8
  async function main() {
package/dist/index.d.ts CHANGED
@@ -19,6 +19,7 @@ declare const hookEventKindSchema: z.ZodEnum<{
19
19
  "shell.completed": "shell.completed";
20
20
  "mcp.started": "mcp.started";
21
21
  "mcp.completed": "mcp.completed";
22
+ "progress.shared": "progress.shared";
22
23
  unknown: "unknown";
23
24
  }>;
24
25
  type HookEventKind = z.infer<typeof hookEventKindSchema>;
@@ -48,6 +49,7 @@ declare const canonicalHookEventSchema: z.ZodObject<{
48
49
  "shell.completed": "shell.completed";
49
50
  "mcp.started": "mcp.started";
50
51
  "mcp.completed": "mcp.completed";
52
+ "progress.shared": "progress.shared";
51
53
  unknown: "unknown";
52
54
  }>;
53
55
  occurredAt: z.ZodString;
@@ -74,7 +76,7 @@ declare function mapClaudeCodeHook(eventName: string, input: unknown): {
74
76
  id: string;
75
77
  version: 1;
76
78
  source: "cursor" | "claude-code" | "codex";
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";
79
+ 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" | "progress.shared" | "unknown";
78
80
  occurredAt: string;
79
81
  metadata: Record<string, SafeValue>;
80
82
  sessionId?: string | undefined;
@@ -92,7 +94,7 @@ declare function mapCodexHook(eventName: string, input: unknown): {
92
94
  id: string;
93
95
  version: 1;
94
96
  source: "cursor" | "claude-code" | "codex";
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";
97
+ 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" | "progress.shared" | "unknown";
96
98
  occurredAt: string;
97
99
  metadata: Record<string, SafeValue>;
98
100
  sessionId?: string | undefined;
@@ -110,7 +112,7 @@ declare function mapCursorHook(eventName: string, input: unknown): {
110
112
  id: string;
111
113
  version: 1;
112
114
  source: "cursor" | "claude-code" | "codex";
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";
115
+ 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" | "progress.shared" | "unknown";
114
116
  occurredAt: string;
115
117
  metadata: Record<string, SafeValue>;
116
118
  sessionId?: string | undefined;
@@ -237,7 +239,7 @@ interface SignedBatchClientOptions {
237
239
  batchSize?: number;
238
240
  timeoutMs?: number;
239
241
  }
240
- declare function eventTypeForHook(event: CanonicalHookEvent): "presence.heartbeat" | "presence.started" | "presence.stopped" | "workspace.files_changed" | "workspace.test_completed" | null;
242
+ declare function eventTypeForHook(event: CanonicalHookEvent): "presence.heartbeat" | "presence.started" | "presence.stopped" | "workspace.files_changed" | "agent.progress_shared" | "workspace.test_completed" | null;
241
243
  interface FlushResult {
242
244
  sent: number;
243
245
  remaining: number;
package/dist/index.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  startPresenceSession,
30
30
  stopPresenceSession,
31
31
  updatePresenceSession
32
- } from "./chunk-FLTLCZ6E.js";
32
+ } from "./chunk-A7KGZ7NX.js";
33
33
  export {
34
34
  JsonFileOfflineQueue,
35
35
  MemoryOfflineQueue,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wibeco/bridge",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Privacy-first live activity bridge for Cursor, Claude Code, and Codex.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,14 @@
1
+ ---
2
+ description: Share meaningful coding-agent milestones with Wibe
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # Wibe activity
7
+
8
+ - This repository is connected to Wibe project `${WIBE_PROJECT_ID}`.
9
+ - After a plan is settled, share it with `wibe_share_plan` when that tool is available.
10
+ - After a meaningful implementation, validation, blocker, or completion milestone, call `wibe_share_progress`.
11
+ - Write one concrete 8–22 word sentence. Use present progressive for active work and past tense for completed work.
12
+ - Mention validation or blockers when material. Never include prompts, source code, secrets, raw commands, praise, hype, or speculation.
13
+ - Do not publish routine tool calls or repeated updates. Wait at least 90 seconds between progress updates unless reporting a blocker or completion.
14
+ - If the Wibe MCP tools are unavailable, run `npx --yes --package @wibeco/bridge@latest wibe share-progress --summary "<summary>" --phase <planning|implementing|validating|blocked|shipped>`.