@wibeco/bridge 0.2.10 → 0.2.14
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.
|
@@ -19,15 +19,71 @@ import {
|
|
|
19
19
|
startPresenceSession,
|
|
20
20
|
stopPresenceSession,
|
|
21
21
|
updatePresenceSession
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-K7TK7FHF.js";
|
|
23
23
|
|
|
24
24
|
// src/cli/commands.ts
|
|
25
25
|
import { access, cp, mkdir, readFile, writeFile } from "fs/promises";
|
|
26
26
|
import { homedir, hostname } from "os";
|
|
27
|
-
import { join, resolve } from "path";
|
|
27
|
+
import { basename, join, resolve } from "path";
|
|
28
28
|
import { fileURLToPath } from "url";
|
|
29
29
|
import { execFile } from "child_process";
|
|
30
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
|
+
}
|
|
31
87
|
async function setupCommand(requestedAdapter, options = {}) {
|
|
32
88
|
const cwd = options.cwd ?? process.cwd();
|
|
33
89
|
const expectedRepository = options.expectedRepository ? normalizeGitHubRepository(options.expectedRepository) : void 0;
|
|
@@ -352,6 +408,10 @@ async function shareProgressCommand(options, cwd = process.cwd()) {
|
|
|
352
408
|
if (title && title.length > 120) {
|
|
353
409
|
throw new Error("Progress title must be 120 characters or fewer.");
|
|
354
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
|
+
}
|
|
355
415
|
const phases = [
|
|
356
416
|
"planning",
|
|
357
417
|
"implementing",
|
|
@@ -371,6 +431,12 @@ async function shareProgressCommand(options, cwd = process.cwd()) {
|
|
|
371
431
|
projectConfig.adapter,
|
|
372
432
|
cwd
|
|
373
433
|
);
|
|
434
|
+
const screenshot = options.screenshot?.trim() ? await uploadProgressScreenshot(
|
|
435
|
+
options.screenshot.trim(),
|
|
436
|
+
screenshotAlt,
|
|
437
|
+
credential,
|
|
438
|
+
cwd
|
|
439
|
+
) : {};
|
|
374
440
|
const event = createHookEvent({
|
|
375
441
|
source: projectConfig.adapter,
|
|
376
442
|
kind: "progress.shared",
|
|
@@ -380,6 +446,7 @@ async function shareProgressCommand(options, cwd = process.cwd()) {
|
|
|
380
446
|
...title ? { title } : {},
|
|
381
447
|
...options.phase ? { phase: options.phase } : {},
|
|
382
448
|
...options.confidence !== void 0 ? { confidence: options.confidence } : {},
|
|
449
|
+
...screenshot.artifactId ? { artifact_id: screenshot.artifactId } : {},
|
|
383
450
|
...workingTree ? {
|
|
384
451
|
paths: workingTree.paths,
|
|
385
452
|
lines_added: workingTree.linesAdded,
|
|
@@ -399,7 +466,7 @@ async function shareProgressCommand(options, cwd = process.cwd()) {
|
|
|
399
466
|
}).capture(event);
|
|
400
467
|
return {
|
|
401
468
|
exitCode: result.error ? 1 : 0,
|
|
402
|
-
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}` : ""}`
|
|
403
470
|
};
|
|
404
471
|
}
|
|
405
472
|
async function doctorCommand(cwd = process.cwd(), requestedRepository) {
|
|
@@ -502,7 +569,15 @@ async function installNativeConfigs(adapter, source, cwd, appUrl, projectId) {
|
|
|
502
569
|
const installed = [];
|
|
503
570
|
for (const [sourceName, destinationName] of files) {
|
|
504
571
|
const destinationPath = join(cwd, destinationName);
|
|
505
|
-
|
|
572
|
+
const destinationExists = await exists(destinationPath);
|
|
573
|
+
const isCursorActivityRule = adapter === "cursor" && destinationName === ".cursor/rules/wibe-activity.mdc";
|
|
574
|
+
if (destinationExists) {
|
|
575
|
+
if (!isCursorActivityRule) continue;
|
|
576
|
+
const existingRule = await readFile(destinationPath, "utf8");
|
|
577
|
+
if (!existingRule.includes("# Wibe activity") || !existingRule.includes("wibe_share_progress")) {
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
506
581
|
const template = await readFile(join(source, sourceName), "utf8");
|
|
507
582
|
await mkdir(resolve(destinationPath, ".."), { recursive: true });
|
|
508
583
|
await writeFile(
|
|
@@ -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" ? {
|
|
@@ -434,6 +435,7 @@ function toEnvelope(event, options) {
|
|
|
434
435
|
hook_kind: event.kind
|
|
435
436
|
} : event.kind.startsWith("git.") ? {
|
|
436
437
|
title: event.metadata.title,
|
|
438
|
+
commit_title: event.metadata.commit_title,
|
|
437
439
|
previous_branch: event.metadata.previous_branch,
|
|
438
440
|
branch: event.repo?.branch,
|
|
439
441
|
previous_commit: event.metadata.previous_commit,
|
|
@@ -1139,6 +1141,7 @@ async function observeRepositoryTransitions(source, sessionId, current) {
|
|
|
1139
1141
|
kind,
|
|
1140
1142
|
metadata: {
|
|
1141
1143
|
title: kind === "git.rebase_completed" ? "Rebased" : kind === "git.merge_completed" ? "Merged" : current.commitTitle || "Committed",
|
|
1144
|
+
...current.commitTitle ? { commit_title: current.commitTitle } : {},
|
|
1142
1145
|
previous_commit: previous.commit,
|
|
1143
1146
|
commit_sha: current.commit,
|
|
1144
1147
|
paths_known: change.pathsKnown,
|
|
@@ -1166,6 +1169,7 @@ async function observeRepositoryTransitions(source, sessionId, current) {
|
|
|
1166
1169
|
kind: "git.push_completed",
|
|
1167
1170
|
metadata: {
|
|
1168
1171
|
title: `Pushed to ${current.branch ?? "remote"}`,
|
|
1172
|
+
...current.commitTitle ? { commit_title: current.commitTitle } : {},
|
|
1169
1173
|
commit_sha: current.commit,
|
|
1170
1174
|
paths_known: change.pathsKnown,
|
|
1171
1175
|
paths: change.paths,
|
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-7LWUGOKM.js";
|
|
10
10
|
import {
|
|
11
11
|
runPresenceHeartbeat
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-K7TK7FHF.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.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wibeco/bridge",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.14",
|
|
4
4
|
"description": "Privacy-first live activity bridge for Cursor, Claude Code, and Codex.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
],
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "node scripts/copy-templates.mjs && tsup src/index.ts src/cli.ts src/codex-hook.ts --format esm --target node20 --dts --clean --tsconfig tsconfig.json",
|
|
31
|
-
"prepack": "npm run build",
|
|
31
|
+
"prepack": "npm run build 1>&2",
|
|
32
32
|
"test": "vitest run test --config vitest.config.ts"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
@@ -7,7 +7,11 @@ 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
|
+
- Frame screenshots around the feature, not the whole application. Use the browser snapshot to identify the smallest element that contains the changed component and the context needed to understand it, then call `browser_take_screenshot` with that element’s `ref` and a descriptive `element` name. Include the trigger with an open menu, popover, or dialog when practical. Use viewport or full-page screenshots only for page-wide work, and reject captures dominated by blank space.
|
|
15
|
+
- 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.
|
|
16
|
+
- 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
17
|
- If the Wibe MCP tool is unavailable, run `npx --yes --package @wibeco/bridge@latest wibe share-progress --summary "<summary>" --phase <shipped|blocked>`.
|