@wibeco/bridge 0.2.9 → 0.2.12
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-6P6IVLMW.js → chunk-7AU52DBJ.js} +76 -3
- package/dist/{chunk-DSMXRUNM.js → chunk-JXH47J5I.js} +32 -1
- package/dist/cli.js +6 -4
- package/dist/codex-hook.js +2 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -1
- package/package.json +1 -1
- package/templates/cursor/wibe-activity.mdc.example +4 -1
|
@@ -15,18 +15,75 @@ import {
|
|
|
15
15
|
observeRepositoryTransitions,
|
|
16
16
|
pollDeviceToken,
|
|
17
17
|
requestDeviceAuthorization,
|
|
18
|
+
resolveLatestPresenceSession,
|
|
18
19
|
startPresenceSession,
|
|
19
20
|
stopPresenceSession,
|
|
20
21
|
updatePresenceSession
|
|
21
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-JXH47J5I.js";
|
|
22
23
|
|
|
23
24
|
// src/cli/commands.ts
|
|
24
25
|
import { access, cp, mkdir, readFile, writeFile } from "fs/promises";
|
|
25
26
|
import { homedir, hostname } from "os";
|
|
26
|
-
import { join, resolve } from "path";
|
|
27
|
+
import { basename, join, resolve } from "path";
|
|
27
28
|
import { fileURLToPath } from "url";
|
|
28
29
|
import { execFile } from "child_process";
|
|
29
30
|
var ADAPTERS = ["cursor", "claude-code", "codex"];
|
|
31
|
+
var MAX_SCREENSHOT_BYTES = 5 * 1024 * 1024;
|
|
32
|
+
function screenshotMimeType(bytes) {
|
|
33
|
+
if ([137, 80, 78, 71, 13, 10, 26, 10].every(
|
|
34
|
+
(byte, index) => bytes[index] === byte
|
|
35
|
+
)) {
|
|
36
|
+
return "image/png";
|
|
37
|
+
}
|
|
38
|
+
if (bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) {
|
|
39
|
+
return "image/jpeg";
|
|
40
|
+
}
|
|
41
|
+
if (new TextDecoder().decode(bytes.slice(0, 4)) === "RIFF" && new TextDecoder().decode(bytes.slice(8, 12)) === "WEBP") {
|
|
42
|
+
return "image/webp";
|
|
43
|
+
}
|
|
44
|
+
return void 0;
|
|
45
|
+
}
|
|
46
|
+
async function uploadProgressScreenshot(screenshotPath, altText, credential, cwd) {
|
|
47
|
+
try {
|
|
48
|
+
const resolvedPath = resolve(cwd, screenshotPath);
|
|
49
|
+
const bytes = await readFile(resolvedPath);
|
|
50
|
+
if (!bytes.length || bytes.length > MAX_SCREENSHOT_BYTES) {
|
|
51
|
+
return {
|
|
52
|
+
warning: "Screenshot skipped: image must be between 1 byte and 5 MB."
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const mimeType = screenshotMimeType(bytes);
|
|
56
|
+
if (!mimeType) {
|
|
57
|
+
return {
|
|
58
|
+
warning: "Screenshot skipped: only PNG, JPEG, and WebP are supported."
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const form = new FormData();
|
|
62
|
+
form.set(
|
|
63
|
+
"screenshot",
|
|
64
|
+
new Blob([new Uint8Array(bytes)], { type: mimeType }),
|
|
65
|
+
basename(resolvedPath)
|
|
66
|
+
);
|
|
67
|
+
form.set("alt_text", altText);
|
|
68
|
+
const response = await fetch(`${credential.appUrl}/api/artifacts`, {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers: { authorization: `Bearer ${credential.accessToken}` },
|
|
71
|
+
body: form,
|
|
72
|
+
signal: AbortSignal.timeout(1e4)
|
|
73
|
+
});
|
|
74
|
+
const body = await response.json().catch(() => ({}));
|
|
75
|
+
if (!response.ok || typeof body.artifact_id !== "string") {
|
|
76
|
+
return {
|
|
77
|
+
warning: `Screenshot skipped: ${typeof body.error === "string" ? body.error : `upload failed with status ${response.status}`}.`
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return { artifactId: body.artifact_id };
|
|
81
|
+
} catch (error) {
|
|
82
|
+
return {
|
|
83
|
+
warning: `Screenshot skipped: ${error instanceof Error ? error.message : "upload failed"}.`
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
30
87
|
async function setupCommand(requestedAdapter, options = {}) {
|
|
31
88
|
const cwd = options.cwd ?? process.cwd();
|
|
32
89
|
const expectedRepository = options.expectedRepository ? normalizeGitHubRepository(options.expectedRepository) : void 0;
|
|
@@ -351,6 +408,10 @@ async function shareProgressCommand(options, cwd = process.cwd()) {
|
|
|
351
408
|
if (title && title.length > 120) {
|
|
352
409
|
throw new Error("Progress title must be 120 characters or fewer.");
|
|
353
410
|
}
|
|
411
|
+
const screenshotAlt = options.screenshotAlt?.trim() || (title ? `${title} screenshot` : "Agent update screenshot");
|
|
412
|
+
if (screenshotAlt.length > 240) {
|
|
413
|
+
throw new Error("Screenshot alt text must be 240 characters or fewer.");
|
|
414
|
+
}
|
|
354
415
|
const phases = [
|
|
355
416
|
"planning",
|
|
356
417
|
"implementing",
|
|
@@ -366,14 +427,26 @@ async function shareProgressCommand(options, cwd = process.cwd()) {
|
|
|
366
427
|
}
|
|
367
428
|
const repo = await detectRepository(cwd);
|
|
368
429
|
const workingTree = repo ? await detectWorkingTreeMetrics(repo.root) : void 0;
|
|
430
|
+
const sessionId = await resolveLatestPresenceSession(
|
|
431
|
+
projectConfig.adapter,
|
|
432
|
+
cwd
|
|
433
|
+
);
|
|
434
|
+
const screenshot = options.screenshot?.trim() ? await uploadProgressScreenshot(
|
|
435
|
+
options.screenshot.trim(),
|
|
436
|
+
screenshotAlt,
|
|
437
|
+
credential,
|
|
438
|
+
cwd
|
|
439
|
+
) : {};
|
|
369
440
|
const event = createHookEvent({
|
|
370
441
|
source: projectConfig.adapter,
|
|
371
442
|
kind: "progress.shared",
|
|
443
|
+
...sessionId ? { sessionId } : {},
|
|
372
444
|
metadata: {
|
|
373
445
|
summary,
|
|
374
446
|
...title ? { title } : {},
|
|
375
447
|
...options.phase ? { phase: options.phase } : {},
|
|
376
448
|
...options.confidence !== void 0 ? { confidence: options.confidence } : {},
|
|
449
|
+
...screenshot.artifactId ? { artifact_id: screenshot.artifactId } : {},
|
|
377
450
|
...workingTree ? {
|
|
378
451
|
paths: workingTree.paths,
|
|
379
452
|
lines_added: workingTree.linesAdded,
|
|
@@ -393,7 +466,7 @@ async function shareProgressCommand(options, cwd = process.cwd()) {
|
|
|
393
466
|
}).capture(event);
|
|
394
467
|
return {
|
|
395
468
|
exitCode: result.error ? 1 : 0,
|
|
396
|
-
message: result.error ? `Progress was queued; delivery failed: ${result.error}` : `Shared progress with Wibe; ${result.remaining} event(s) queued
|
|
469
|
+
message: result.error ? `Progress was queued; delivery failed: ${result.error}` : `Shared progress with Wibe; ${result.remaining} event(s) queued.${screenshot.warning ? ` ${screenshot.warning}` : ""}`
|
|
397
470
|
};
|
|
398
471
|
}
|
|
399
472
|
async function doctorCommand(cwd = process.cwd(), requestedRepository) {
|
|
@@ -389,6 +389,7 @@ function toEnvelope(event, options) {
|
|
|
389
389
|
paths,
|
|
390
390
|
phase: event.metadata.phase,
|
|
391
391
|
confidence: event.metadata.confidence,
|
|
392
|
+
artifact_id: typeof event.metadata.artifact_id === "string" ? event.metadata.artifact_id : void 0,
|
|
392
393
|
lines_added: typeof event.metadata.lines_added === "number" ? event.metadata.lines_added : void 0,
|
|
393
394
|
lines_deleted: typeof event.metadata.lines_deleted === "number" ? event.metadata.lines_deleted : void 0
|
|
394
395
|
} : event.kind === "file.changed" ? {
|
|
@@ -628,7 +629,7 @@ var JsonFileOfflineQueue = class {
|
|
|
628
629
|
// src/presence.ts
|
|
629
630
|
import { createHash, randomUUID as randomUUID3 } from "crypto";
|
|
630
631
|
import { spawn } from "child_process";
|
|
631
|
-
import { mkdir as mkdir2, readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
|
|
632
|
+
import { mkdir as mkdir2, readFile as readFile2, readdir, rm, writeFile as writeFile2 } from "fs/promises";
|
|
632
633
|
import { homedir } from "os";
|
|
633
634
|
import { join } from "path";
|
|
634
635
|
var PRESENCE_HEARTBEAT_INTERVAL_MS = 45e3;
|
|
@@ -815,6 +816,35 @@ function presenceStatePath(source, sessionId, cwd = process.cwd()) {
|
|
|
815
816
|
const key = createHash("sha256").update(`${source}\0${sessionId ?? ""}\0${cwd}`).digest("hex");
|
|
816
817
|
return join(presenceDirectory(), `${key}.json`);
|
|
817
818
|
}
|
|
819
|
+
async function resolveLatestPresenceSession(source, cwd = process.cwd(), maxAgeMs = 5 * 6e4) {
|
|
820
|
+
let names;
|
|
821
|
+
try {
|
|
822
|
+
names = await readdir(presenceDirectory());
|
|
823
|
+
} catch {
|
|
824
|
+
return void 0;
|
|
825
|
+
}
|
|
826
|
+
const now = Date.now();
|
|
827
|
+
let latest;
|
|
828
|
+
for (const name of names) {
|
|
829
|
+
if (!name.endsWith(".json")) continue;
|
|
830
|
+
const path = join(presenceDirectory(), name);
|
|
831
|
+
const state = await readPresenceState(path);
|
|
832
|
+
if (!state?.sessionId || state.source !== source || path !== presenceStatePath(source, state.sessionId, cwd)) {
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
const lastActivity = Date.parse(state.lastActivityAt);
|
|
836
|
+
if (!Number.isFinite(lastActivity) || lastActivity > now + 6e4 || now - lastActivity > maxAgeMs) {
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
if (!latest || state.lastActivityAt > latest.lastActivityAt) {
|
|
840
|
+
latest = {
|
|
841
|
+
sessionId: state.sessionId,
|
|
842
|
+
lastActivityAt: state.lastActivityAt
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return latest?.sessionId;
|
|
847
|
+
}
|
|
818
848
|
function presenceDirectory() {
|
|
819
849
|
return process.env.WIBE_PRESENCE_DIR ?? join(homedir(), ".wibe", "presence");
|
|
820
850
|
}
|
|
@@ -1417,6 +1447,7 @@ export {
|
|
|
1417
1447
|
stopPresenceSession,
|
|
1418
1448
|
runPresenceHeartbeat,
|
|
1419
1449
|
presenceStatePath,
|
|
1450
|
+
resolveLatestPresenceSession,
|
|
1420
1451
|
classifyHeadTransition,
|
|
1421
1452
|
isObservedPush,
|
|
1422
1453
|
detectRepository,
|
package/dist/cli.js
CHANGED
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
setupCommand,
|
|
7
7
|
shareProgressCommand,
|
|
8
8
|
statusCommand
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-7AU52DBJ.js";
|
|
10
10
|
import {
|
|
11
11
|
runPresenceHeartbeat
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-JXH47J5I.js";
|
|
13
13
|
|
|
14
14
|
// src/cli.ts
|
|
15
15
|
var HELP = `wibe-bridge <command>
|
|
@@ -21,7 +21,7 @@ Commands:
|
|
|
21
21
|
Use --reauthorize only to replace a rejected or revoked device token.
|
|
22
22
|
status
|
|
23
23
|
emit --adapter <name> --event <hook-name> (JSON payload on stdin)
|
|
24
|
-
share-progress --summary <20-55 words> [--title <title>] [--phase <phase>] [--confidence <0-1>]
|
|
24
|
+
share-progress --summary <20-55 words> [--title <title>] [--phase <phase>] [--confidence <0-1>] [--screenshot <path>] [--screenshot-alt <text>]
|
|
25
25
|
doctor [--repository <owner/repo>]`;
|
|
26
26
|
async function main() {
|
|
27
27
|
const [command, ...args] = process.argv.slice(2);
|
|
@@ -54,7 +54,9 @@ async function main() {
|
|
|
54
54
|
summary: option(args, "--summary"),
|
|
55
55
|
title: option(args, "--title"),
|
|
56
56
|
phase: option(args, "--phase"),
|
|
57
|
-
confidence: confidence === void 0 ? void 0 : Number(confidence)
|
|
57
|
+
confidence: confidence === void 0 ? void 0 : Number(confidence),
|
|
58
|
+
screenshot: option(args, "--screenshot"),
|
|
59
|
+
screenshotAlt: option(args, "--screenshot-alt")
|
|
58
60
|
});
|
|
59
61
|
} else if (command === "_presence-heartbeat") {
|
|
60
62
|
parseAdapter(option(args, "--adapter"));
|
package/dist/codex-hook.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -304,6 +304,7 @@ declare function updatePresenceSession(source: AgentSource, sessionId: string |
|
|
|
304
304
|
declare function stopPresenceSession(source: AgentSource, sessionId: string | undefined, cwd?: string): Promise<PresenceMetrics | undefined>;
|
|
305
305
|
declare function runPresenceHeartbeat(statePath: string, expectedInstanceId: string, emit: (source: AgentSource, eventName: string, payload: Record<string, unknown>) => Promise<unknown>, intervalMs?: number): Promise<void>;
|
|
306
306
|
declare function presenceStatePath(source: AgentSource, sessionId: string | undefined, cwd?: string): string;
|
|
307
|
+
declare function resolveLatestPresenceSession(source: AgentSource, cwd?: string, maxAgeMs?: number): Promise<string | undefined>;
|
|
307
308
|
|
|
308
309
|
interface RedactionOptions {
|
|
309
310
|
allowContent?: boolean;
|
|
@@ -345,4 +346,4 @@ declare function sanitizeRemote(remote: string): string;
|
|
|
345
346
|
declare function normalizeGitHubRepository(value: string): string | undefined;
|
|
346
347
|
declare function matchesGitHubRepository(remote: string | undefined, expected: string): boolean;
|
|
347
348
|
|
|
348
|
-
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 RepositoryTransition, type SafeValue, SignedBatchClient, type SignedBatchClientOptions, type StoredCredential, SystemCredentialStore, type WorkingTreeMetrics, agentSourceSchema, canonicalHookEventSchema, classifyHeadTransition, createHookEvent, detectRepository, detectWorkingTreeMetrics, deviceAuthorizationSchema, deviceTokenResponseSchema, eventTypeForHook, hookEventKindSchema, isObservedPush, mapClaudeCodeHook, mapCodexHook, mapCursorHook, matchesGitHubRepository, normalizeGitHubRepository, observeRepositoryTransitions, pollDeviceToken, presenceStatePath, redact, requestDeviceAuthorization, runPresenceHeartbeat, safeMetadata, safeValueSchema, sanitizeRemote, startPresenceSession, stopPresenceSession, updatePresenceSession };
|
|
349
|
+
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 RepositoryTransition, type SafeValue, SignedBatchClient, type SignedBatchClientOptions, type StoredCredential, SystemCredentialStore, type WorkingTreeMetrics, agentSourceSchema, canonicalHookEventSchema, classifyHeadTransition, createHookEvent, detectRepository, detectWorkingTreeMetrics, deviceAuthorizationSchema, deviceTokenResponseSchema, eventTypeForHook, hookEventKindSchema, isObservedPush, mapClaudeCodeHook, mapCodexHook, mapCursorHook, matchesGitHubRepository, normalizeGitHubRepository, observeRepositoryTransitions, pollDeviceToken, presenceStatePath, redact, requestDeviceAuthorization, resolveLatestPresenceSession, runPresenceHeartbeat, safeMetadata, safeValueSchema, sanitizeRemote, startPresenceSession, stopPresenceSession, updatePresenceSession };
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
presenceStatePath,
|
|
26
26
|
redact,
|
|
27
27
|
requestDeviceAuthorization,
|
|
28
|
+
resolveLatestPresenceSession,
|
|
28
29
|
runPresenceHeartbeat,
|
|
29
30
|
safeMetadata,
|
|
30
31
|
safeValueSchema,
|
|
@@ -32,7 +33,7 @@ import {
|
|
|
32
33
|
startPresenceSession,
|
|
33
34
|
stopPresenceSession,
|
|
34
35
|
updatePresenceSession
|
|
35
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-JXH47J5I.js";
|
|
36
37
|
export {
|
|
37
38
|
JsonFileOfflineQueue,
|
|
38
39
|
MemoryOfflineQueue,
|
|
@@ -60,6 +61,7 @@ export {
|
|
|
60
61
|
presenceStatePath,
|
|
61
62
|
redact,
|
|
62
63
|
requestDeviceAuthorization,
|
|
64
|
+
resolveLatestPresenceSession,
|
|
63
65
|
runPresenceHeartbeat,
|
|
64
66
|
safeMetadata,
|
|
65
67
|
safeValueSchema,
|
package/package.json
CHANGED
|
@@ -7,7 +7,10 @@ alwaysApply: true
|
|
|
7
7
|
|
|
8
8
|
- This repository is connected to Wibe project `${WIBE_PROJECT_ID}`.
|
|
9
9
|
- For most tasks, publish one update only after the requested outcome is complete. Do not publish plans, routine progress, or separate updates for editing, testing, committing, or pushing.
|
|
10
|
-
-
|
|
10
|
+
- For text-only outcomes, use `wibe_share_progress` with phase `shipped` at completion. Publish earlier only with phase `blocked` when a material blocker leaves the task unfinished.
|
|
11
11
|
- Write one self-contained paragraph of 20–55 words in past tense. Lead with what now works, then explain the user or team impact and meaningful validation. Mention important limitations when present.
|
|
12
12
|
- Describe the outcome, not the agent’s process. Never include first-person narration, prompts, source code, secrets, raw commands, routine tool calls, filenames, line counts, praise, hype, or speculation; Wibe displays code and file changes separately.
|
|
13
|
+
- Bias frontend outcomes toward visual evidence without asking for per-task approval. For UI components, pages, styling, responsive behavior, interactions, and visual fixes, attach one screenshot when the app or preview is already runnable. Prefer a screenshot already made during visual QA; otherwise capture the clearest final state.
|
|
14
|
+
- Skip screenshots for backend, infrastructure, documentation, refactors, and non-visual frontend logic. Never launch or repair an app solely for Wibe, and omit the image when navigation needs manual authentication, the state contains sensitive data, visual validation is blocked, or the project has visual updates disabled.
|
|
15
|
+
- When a screenshot is available, do not call `wibe_share_progress`. Instead run `npx --yes --package @wibeco/bridge@latest wibe share-progress --summary "<summary>" --phase <shipped|blocked> --screenshot "<path>"` so the local bridge uploads the image and publishes the outcome together.
|
|
13
16
|
- If the Wibe MCP tool is unavailable, run `npx --yes --package @wibeco/bridge@latest wibe share-progress --summary "<summary>" --phase <shipped|blocked>`.
|