@wibeco/bridge 0.2.15 → 0.2.17
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 +17 -7
- package/dist/{chunk-K7TK7FHF.js → chunk-4WIGVKDR.js} +178 -70
- package/dist/{chunk-7LWUGOKM.js → chunk-YLC4TF4F.js} +706 -33
- package/dist/cli.js +30 -3
- package/dist/codex-hook.js +2 -2
- package/dist/index.d.ts +15 -3
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/templates/claude-code/README.md +9 -6
- package/templates/claude-code/mcp.json.example +1 -1
- package/templates/claude-code/wibe-activity.md.example +15 -0
- package/templates/codex/README.md +12 -11
- package/templates/codex/config.toml.example +1 -5
- package/templates/codex/wibe-activity.md.example +15 -0
- package/templates/cursor/README.md +4 -2
- package/templates/cursor/mcp.json.example +1 -1
- package/templates/cursor/wibe-activity.mdc.example +3 -0
package/README.md
CHANGED
|
@@ -7,7 +7,8 @@ Dependency-light TypeScript foundations for normalizing agent hooks and sending
|
|
|
7
7
|
- Adapters copy only explicit allow-listed metadata. Prompt text, tool input/output, file content,
|
|
8
8
|
shell command text, messages, and transcripts are never captured by default.
|
|
9
9
|
- Redaction is defense in depth, not permission to send arbitrary payloads.
|
|
10
|
-
- Offline files are created with owner-only permissions
|
|
10
|
+
- Offline files are created with owner-only permissions and separated by Wibe project. Set
|
|
11
|
+
`WIBE_QUEUE_PATH` to relocate the queue directory.
|
|
11
12
|
- Device authorization opens Wibe in the browser and stores the resulting
|
|
12
13
|
revocable project-scoped bearer token in the operating-system keychain.
|
|
13
14
|
- Event batches are schema-validated, bounded, idempotent, and sent only to the
|
|
@@ -18,6 +19,11 @@ Dependency-light TypeScript foundations for normalizing agent hooks and sending
|
|
|
18
19
|
Install and authorize Wibe from any GitHub repository:
|
|
19
20
|
|
|
20
21
|
```sh
|
|
22
|
+
npx --yes --package @wibeco/bridge@latest wibe onboard \
|
|
23
|
+
--adapter cursor \
|
|
24
|
+
--url https://trywibe.com \
|
|
25
|
+
--repository <owner/repository>
|
|
26
|
+
|
|
21
27
|
npx --yes --package @wibeco/bridge@latest wibe setup \
|
|
22
28
|
--adapter cursor \
|
|
23
29
|
--project <uuid> \
|
|
@@ -36,14 +42,18 @@ saved token was rejected or revoked.
|
|
|
36
42
|
|
|
37
43
|
`setup` keeps reviewable templates under `.wibe/integrations/<adapter>` and
|
|
38
44
|
installs native project configuration only when the destination file does not
|
|
39
|
-
already exist. Existing
|
|
40
|
-
overwritten
|
|
45
|
+
already exist. Existing hooks and unrelated agent configuration are never
|
|
46
|
+
overwritten. Wibe-managed activity instructions and an existing Wibe MCP URL
|
|
47
|
+
are refreshed to the repository's current project.
|
|
41
48
|
|
|
42
49
|
`WIBE_APP_URL` overrides the default local Wibe URL during setup.
|
|
43
|
-
`WIBE_QUEUE_PATH` optionally relocates the owner-only
|
|
50
|
+
`WIBE_QUEUE_PATH` optionally relocates the owner-only per-project queue directory. If a legacy
|
|
51
|
+
file path is supplied, project queues are stored beside it under a `.d` suffix. CI and
|
|
44
52
|
headless environments can inject `WIBE_ACCESS_TOKEN`, `WIBE_PROJECT_ID`,
|
|
45
53
|
`WIBE_ORGANIZATION_ID`, `WIBE_REPOSITORY_ID`, and `WIBE_DEVICE_ID`; interactive
|
|
46
|
-
developer machines should use the keychain-backed browser flow.
|
|
54
|
+
developer machines should use the keychain-backed browser flow. Environment
|
|
55
|
+
credentials are accepted only when they match that repository's `.wibe/project.json`.
|
|
47
56
|
|
|
48
|
-
Without a device credential, `emit` keeps normalized events in
|
|
49
|
-
|
|
57
|
+
Without a device credential, `emit` keeps normalized events in a repository-scoped pending queue.
|
|
58
|
+
Setup adopts only that repository's pending events. Legacy identity-less queues are quarantined
|
|
59
|
+
and reported by `wibe doctor`; they are never delivered into an arbitrary project.
|
|
@@ -514,74 +514,107 @@ var SignedBatchClient = class {
|
|
|
514
514
|
return this.flush();
|
|
515
515
|
}
|
|
516
516
|
async flush() {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
try {
|
|
530
|
-
const response = await this.request(this.options.endpoint, {
|
|
531
|
-
method: "POST",
|
|
532
|
-
body,
|
|
533
|
-
signal: AbortSignal.timeout(this.timeoutMs),
|
|
534
|
-
headers: {
|
|
535
|
-
"content-type": "application/json",
|
|
536
|
-
"user-agent": "wibe-bridge/1",
|
|
537
|
-
authorization: `Bearer ${this.options.accessToken}`
|
|
538
|
-
}
|
|
517
|
+
return this.options.queue.withTransaction(async (queue) => {
|
|
518
|
+
const queuedEvents = await queue.peek(this.batchSize);
|
|
519
|
+
if (queuedEvents.length === 0) return { sent: 0, remaining: 0 };
|
|
520
|
+
const events4 = queuedEvents.filter((event) => eventTypeForHook(event));
|
|
521
|
+
const ignoredIds = queuedEvents.filter((event) => !eventTypeForHook(event)).map((event) => event.id);
|
|
522
|
+
if (ignoredIds.length > 0) await queue.remove(ignoredIds);
|
|
523
|
+
if (events4.length === 0) {
|
|
524
|
+
return { sent: 0, remaining: await queue.size() };
|
|
525
|
+
}
|
|
526
|
+
const body = JSON.stringify({
|
|
527
|
+
batch_id: randomUUID2(),
|
|
528
|
+
events: events4.map((event) => toEnvelope(event, this.options))
|
|
539
529
|
});
|
|
540
|
-
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
530
|
+
try {
|
|
531
|
+
const response = await this.request(this.options.endpoint, {
|
|
532
|
+
method: "POST",
|
|
533
|
+
body,
|
|
534
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
535
|
+
headers: {
|
|
536
|
+
"content-type": "application/json",
|
|
537
|
+
"user-agent": "wibe-bridge/1",
|
|
538
|
+
authorization: `Bearer ${this.options.accessToken}`
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
if (!response.ok) {
|
|
542
|
+
const requestId = response.headers.get("x-request-id");
|
|
543
|
+
let detail = "";
|
|
544
|
+
try {
|
|
545
|
+
const responseBody = await response.json();
|
|
546
|
+
const code = typeof responseBody.code === "string" ? responseBody.code.slice(0, 80) : void 0;
|
|
547
|
+
const message = typeof responseBody.error === "string" ? responseBody.error.slice(0, 160) : void 0;
|
|
548
|
+
detail = [code, message].filter(Boolean).join(": ");
|
|
549
|
+
} catch {
|
|
550
|
+
}
|
|
551
|
+
const suffix = [
|
|
552
|
+
detail ? `: ${detail}` : "",
|
|
553
|
+
requestId ? ` (request ${requestId.slice(0, 80)})` : ""
|
|
554
|
+
].join("");
|
|
555
|
+
throw new Error(`Collector returned HTTP ${response.status}${suffix}`);
|
|
549
556
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
557
|
+
await queue.remove(events4.map((event) => event.id));
|
|
558
|
+
return { sent: events4.length, remaining: await queue.size() };
|
|
559
|
+
} catch (error) {
|
|
560
|
+
return {
|
|
561
|
+
sent: 0,
|
|
562
|
+
remaining: await queue.size(),
|
|
563
|
+
error: error instanceof Error ? error.message : String(error)
|
|
564
|
+
};
|
|
555
565
|
}
|
|
556
|
-
|
|
557
|
-
return { sent: events4.length, remaining: await this.options.queue.size() };
|
|
558
|
-
} catch (error) {
|
|
559
|
-
return {
|
|
560
|
-
sent: 0,
|
|
561
|
-
remaining: await this.options.queue.size(),
|
|
562
|
-
error: error instanceof Error ? error.message : String(error)
|
|
563
|
-
};
|
|
564
|
-
}
|
|
566
|
+
});
|
|
565
567
|
}
|
|
566
568
|
};
|
|
567
569
|
|
|
568
570
|
// src/queue.ts
|
|
569
|
-
import {
|
|
571
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
572
|
+
import {
|
|
573
|
+
mkdir,
|
|
574
|
+
open,
|
|
575
|
+
readFile,
|
|
576
|
+
rename,
|
|
577
|
+
stat,
|
|
578
|
+
unlink,
|
|
579
|
+
writeFile
|
|
580
|
+
} from "fs/promises";
|
|
570
581
|
import { dirname } from "path";
|
|
571
582
|
var MemoryOfflineQueue = class {
|
|
572
583
|
events = [];
|
|
584
|
+
operation = Promise.resolve();
|
|
573
585
|
async enqueue(events4) {
|
|
574
|
-
this.
|
|
586
|
+
await this.withTransaction(async () => {
|
|
587
|
+
this.events.push(...events4);
|
|
588
|
+
});
|
|
575
589
|
}
|
|
576
590
|
async peek(limit) {
|
|
577
|
-
return this.
|
|
591
|
+
return this.withTransaction((queue) => queue.peek(limit));
|
|
578
592
|
}
|
|
579
593
|
async remove(ids) {
|
|
580
|
-
|
|
581
|
-
this.events = this.events.filter((event) => !removed.has(event.id));
|
|
594
|
+
await this.withTransaction((queue) => queue.remove(ids));
|
|
582
595
|
}
|
|
583
596
|
async size() {
|
|
584
|
-
return this.
|
|
597
|
+
return this.withTransaction((queue) => queue.size());
|
|
598
|
+
}
|
|
599
|
+
async withTransaction(operation) {
|
|
600
|
+
return this.serialize(
|
|
601
|
+
() => operation({
|
|
602
|
+
peek: async (limit) => this.events.slice(0, Math.max(0, limit)),
|
|
603
|
+
remove: async (ids) => {
|
|
604
|
+
const removed = new Set(ids);
|
|
605
|
+
this.events = this.events.filter((event) => !removed.has(event.id));
|
|
606
|
+
},
|
|
607
|
+
size: async () => this.events.length
|
|
608
|
+
})
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
async serialize(operation) {
|
|
612
|
+
const next = this.operation.then(operation);
|
|
613
|
+
this.operation = next.then(
|
|
614
|
+
() => void 0,
|
|
615
|
+
() => void 0
|
|
616
|
+
);
|
|
617
|
+
return next;
|
|
585
618
|
}
|
|
586
619
|
};
|
|
587
620
|
var JsonFileOfflineQueue = class {
|
|
@@ -591,21 +624,39 @@ var JsonFileOfflineQueue = class {
|
|
|
591
624
|
filePath;
|
|
592
625
|
operation = Promise.resolve();
|
|
593
626
|
async enqueue(events4) {
|
|
594
|
-
await this.
|
|
627
|
+
await this.withTransaction(async () => {
|
|
628
|
+
await this.updateUnlocked((current) => [...current, ...events4]);
|
|
629
|
+
});
|
|
595
630
|
}
|
|
596
631
|
async peek(limit) {
|
|
597
|
-
|
|
598
|
-
return (await this.read()).slice(0, Math.max(0, limit));
|
|
632
|
+
return this.withTransaction((queue) => queue.peek(limit));
|
|
599
633
|
}
|
|
600
634
|
async remove(ids) {
|
|
601
|
-
|
|
602
|
-
await this.update((current) => current.filter((event) => !removed.has(event.id)));
|
|
635
|
+
await this.withTransaction((queue) => queue.remove(ids));
|
|
603
636
|
}
|
|
604
637
|
async size() {
|
|
605
|
-
|
|
606
|
-
|
|
638
|
+
return this.withTransaction((queue) => queue.size());
|
|
639
|
+
}
|
|
640
|
+
async withTransaction(operation) {
|
|
641
|
+
return this.serialize(async () => {
|
|
642
|
+
const release = await this.acquireLock();
|
|
643
|
+
try {
|
|
644
|
+
return await operation({
|
|
645
|
+
peek: async (limit) => (await this.readUnlocked()).slice(0, Math.max(0, limit)),
|
|
646
|
+
remove: async (ids) => {
|
|
647
|
+
const removed = new Set(ids);
|
|
648
|
+
await this.updateUnlocked(
|
|
649
|
+
(current) => current.filter((event) => !removed.has(event.id))
|
|
650
|
+
);
|
|
651
|
+
},
|
|
652
|
+
size: async () => (await this.readUnlocked()).length
|
|
653
|
+
});
|
|
654
|
+
} finally {
|
|
655
|
+
await release();
|
|
656
|
+
}
|
|
657
|
+
});
|
|
607
658
|
}
|
|
608
|
-
async
|
|
659
|
+
async readUnlocked() {
|
|
609
660
|
try {
|
|
610
661
|
const data = JSON.parse(await readFile(this.filePath, "utf8"));
|
|
611
662
|
return canonicalHookEventSchema.array().parse(data);
|
|
@@ -614,21 +665,78 @@ var JsonFileOfflineQueue = class {
|
|
|
614
665
|
throw error;
|
|
615
666
|
}
|
|
616
667
|
}
|
|
617
|
-
async
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
668
|
+
async updateUnlocked(transform) {
|
|
669
|
+
const events4 = transform(await this.readUnlocked());
|
|
670
|
+
await mkdir(dirname(this.filePath), { recursive: true, mode: 448 });
|
|
671
|
+
const temporary = `${this.filePath}.${process.pid}.${randomUUID3()}.tmp`;
|
|
672
|
+
await writeFile(temporary, JSON.stringify(events4), {
|
|
673
|
+
encoding: "utf8",
|
|
674
|
+
mode: 384
|
|
624
675
|
});
|
|
625
|
-
|
|
626
|
-
|
|
676
|
+
await rename(temporary, this.filePath);
|
|
677
|
+
}
|
|
678
|
+
async serialize(operation) {
|
|
679
|
+
const next = this.operation.then(operation);
|
|
680
|
+
this.operation = next.then(
|
|
681
|
+
() => void 0,
|
|
682
|
+
() => void 0
|
|
683
|
+
);
|
|
684
|
+
return next;
|
|
685
|
+
}
|
|
686
|
+
async acquireLock() {
|
|
687
|
+
const lockPath = `${this.filePath}.lock`;
|
|
688
|
+
const token = randomUUID3();
|
|
689
|
+
const startedAt = Date.now();
|
|
690
|
+
await mkdir(dirname(this.filePath), { recursive: true, mode: 448 });
|
|
691
|
+
while (true) {
|
|
692
|
+
try {
|
|
693
|
+
const handle = await open(lockPath, "wx", 384);
|
|
694
|
+
try {
|
|
695
|
+
await handle.writeFile(
|
|
696
|
+
JSON.stringify({ pid: process.pid, token, createdAt: Date.now() }),
|
|
697
|
+
"utf8"
|
|
698
|
+
);
|
|
699
|
+
} catch (error) {
|
|
700
|
+
await handle.close().catch(() => void 0);
|
|
701
|
+
await unlink(lockPath).catch(() => void 0);
|
|
702
|
+
throw error;
|
|
703
|
+
}
|
|
704
|
+
await handle.close();
|
|
705
|
+
return async () => {
|
|
706
|
+
try {
|
|
707
|
+
const current = JSON.parse(
|
|
708
|
+
await readFile(lockPath, "utf8")
|
|
709
|
+
);
|
|
710
|
+
if (current.token === token) await unlink(lockPath);
|
|
711
|
+
} catch (error) {
|
|
712
|
+
if (error.code !== "ENOENT") throw error;
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
} catch (error) {
|
|
716
|
+
if (error.code !== "EEXIST") throw error;
|
|
717
|
+
}
|
|
718
|
+
try {
|
|
719
|
+
const details = await stat(lockPath);
|
|
720
|
+
if (Date.now() - details.mtimeMs > 3e4) {
|
|
721
|
+
const stalePath = `${lockPath}.${process.pid}.${token}.stale`;
|
|
722
|
+
await rename(lockPath, stalePath);
|
|
723
|
+
await unlink(stalePath).catch(() => void 0);
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
} catch (error) {
|
|
727
|
+
if (error.code === "ENOENT") continue;
|
|
728
|
+
throw error;
|
|
729
|
+
}
|
|
730
|
+
if (Date.now() - startedAt > 15e3) {
|
|
731
|
+
throw new Error(`Timed out waiting for offline queue lock: ${lockPath}`);
|
|
732
|
+
}
|
|
733
|
+
await new Promise((resolve2) => setTimeout(resolve2, 25));
|
|
734
|
+
}
|
|
627
735
|
}
|
|
628
736
|
};
|
|
629
737
|
|
|
630
738
|
// src/presence.ts
|
|
631
|
-
import { createHash, randomUUID as
|
|
739
|
+
import { createHash, randomUUID as randomUUID4 } from "crypto";
|
|
632
740
|
import { spawn } from "child_process";
|
|
633
741
|
import { mkdir as mkdir2, readFile as readFile2, readdir, rm, writeFile as writeFile2 } from "fs/promises";
|
|
634
742
|
import { homedir } from "os";
|
|
@@ -642,7 +750,7 @@ async function startPresenceSession(source, sessionId, cwd = process.cwd()) {
|
|
|
642
750
|
const statePath = presenceStatePath(source, sessionId, cwd);
|
|
643
751
|
await rm(statePath, { force: true });
|
|
644
752
|
await mkdir2(presenceDirectory(), { recursive: true, mode: 448 });
|
|
645
|
-
const instanceId =
|
|
753
|
+
const instanceId = randomUUID4();
|
|
646
754
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
647
755
|
const cliPath = process.argv[1];
|
|
648
756
|
const state = {
|