@wibeco/bridge 0.2.16 → 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 +12 -7
- package/dist/{chunk-K7TK7FHF.js → chunk-4WIGVKDR.js} +178 -70
- package/dist/{chunk-E2RWE6UW.js → chunk-YLC4TF4F.js} +206 -32
- package/dist/cli.js +2 -2
- 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 +4 -2
- package/templates/claude-code/mcp.json.example +1 -1
- package/templates/codex/README.md +4 -2
- package/templates/codex/config.toml.example +1 -1
- package/templates/cursor/README.md +4 -2
- package/templates/cursor/mcp.json.example +1 -1
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
|
|
@@ -41,14 +42,18 @@ saved token was rejected or revoked.
|
|
|
41
42
|
|
|
42
43
|
`setup` keeps reviewable templates under `.wibe/integrations/<adapter>` and
|
|
43
44
|
installs native project configuration only when the destination file does not
|
|
44
|
-
already exist. Existing
|
|
45
|
-
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.
|
|
46
48
|
|
|
47
49
|
`WIBE_APP_URL` overrides the default local Wibe URL during setup.
|
|
48
|
-
`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
|
|
49
52
|
headless environments can inject `WIBE_ACCESS_TOKEN`, `WIBE_PROJECT_ID`,
|
|
50
53
|
`WIBE_ORGANIZATION_ID`, `WIBE_REPOSITORY_ID`, and `WIBE_DEVICE_ID`; interactive
|
|
51
|
-
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`.
|
|
52
56
|
|
|
53
|
-
Without a device credential, `emit` keeps normalized events in
|
|
54
|
-
|
|
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 = {
|
|
@@ -19,12 +19,21 @@ import {
|
|
|
19
19
|
startPresenceSession,
|
|
20
20
|
stopPresenceSession,
|
|
21
21
|
updatePresenceSession
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-4WIGVKDR.js";
|
|
23
23
|
|
|
24
24
|
// src/cli/commands.ts
|
|
25
|
-
import {
|
|
25
|
+
import { createHash } from "crypto";
|
|
26
|
+
import { realpathSync } from "fs";
|
|
27
|
+
import {
|
|
28
|
+
access,
|
|
29
|
+
cp,
|
|
30
|
+
mkdir,
|
|
31
|
+
readFile,
|
|
32
|
+
rename,
|
|
33
|
+
writeFile
|
|
34
|
+
} from "fs/promises";
|
|
26
35
|
import { homedir, hostname } from "os";
|
|
27
|
-
import { basename, join, resolve } from "path";
|
|
36
|
+
import { basename, extname, join, resolve } from "path";
|
|
28
37
|
import { fileURLToPath } from "url";
|
|
29
38
|
import { execFile } from "child_process";
|
|
30
39
|
|
|
@@ -307,10 +316,12 @@ async function setupCommand(requestedAdapter, options = {}) {
|
|
|
307
316
|
appUrl,
|
|
308
317
|
options.projectId
|
|
309
318
|
);
|
|
319
|
+
await adoptPendingQueue(cwd, options.projectId);
|
|
310
320
|
const heartbeat2 = await sendVerificationHeartbeat(
|
|
311
321
|
existingCredential,
|
|
312
322
|
adapter,
|
|
313
|
-
"setup"
|
|
323
|
+
"setup",
|
|
324
|
+
cwd
|
|
314
325
|
);
|
|
315
326
|
return {
|
|
316
327
|
exitCode: heartbeat2.error ? 1 : 0,
|
|
@@ -397,7 +408,13 @@ Confirm code ${authorization.user_code}
|
|
|
397
408
|
appUrl,
|
|
398
409
|
token.projectId
|
|
399
410
|
);
|
|
400
|
-
|
|
411
|
+
await adoptPendingQueue(cwd, token.projectId);
|
|
412
|
+
const heartbeat = await sendVerificationHeartbeat(
|
|
413
|
+
credential,
|
|
414
|
+
adapter,
|
|
415
|
+
"setup",
|
|
416
|
+
cwd
|
|
417
|
+
);
|
|
401
418
|
return {
|
|
402
419
|
exitCode: heartbeat.error ? 1 : 0,
|
|
403
420
|
message: `${heartbeat.error ? `Connected ${adapter} to Wibe, but the verification heartbeat could not be delivered (${heartbeat.error}). It remains safe to retry with wibe doctor. Installed or refreshed ${installedNativeFiles.length} native config file(s); unmanaged configuration was left untouched.` : `Connected ${adapter} to Wibe and verified the event connection. Installed or refreshed ${installedNativeFiles.length} native config file(s); unmanaged configuration was left untouched and reviewable templates are at ${destination}.`}${adapterSetupNextSteps(adapter)}`
|
|
@@ -577,7 +594,13 @@ Install the Wibe GitHub App on ${repository}, then return here.
|
|
|
577
594
|
appUrl,
|
|
578
595
|
snapshot.project_id
|
|
579
596
|
);
|
|
580
|
-
|
|
597
|
+
await adoptPendingQueue(cwd, snapshot.project_id);
|
|
598
|
+
const heartbeat = await sendVerificationHeartbeat(
|
|
599
|
+
credential,
|
|
600
|
+
adapter,
|
|
601
|
+
"setup",
|
|
602
|
+
cwd
|
|
603
|
+
);
|
|
581
604
|
if (heartbeat.error) {
|
|
582
605
|
return {
|
|
583
606
|
exitCode: 1,
|
|
@@ -620,9 +643,11 @@ async function waitForOnboarding(appUrl, sessionId, sessionSecret, ready) {
|
|
|
620
643
|
throw new Error("Timed out waiting for the browser step to finish.");
|
|
621
644
|
}
|
|
622
645
|
async function statusCommand(cwd = process.cwd()) {
|
|
646
|
+
const legacyQueue = await quarantineLegacyQueue();
|
|
623
647
|
const repo = await detectRepository(cwd);
|
|
624
648
|
const credential = await loadCredential(cwd);
|
|
625
|
-
const queue =
|
|
649
|
+
const queue = credential ? projectQueuePath(credential.projectId) : pendingQueuePath(repo?.root ?? cwd);
|
|
650
|
+
const queueBacklog = await new JsonFileOfflineQueue(queue).size();
|
|
626
651
|
const installed = (await Promise.all(
|
|
627
652
|
ADAPTERS.map(async (adapter) => ({
|
|
628
653
|
adapter,
|
|
@@ -638,7 +663,9 @@ async function statusCommand(cwd = process.cwd()) {
|
|
|
638
663
|
connected: Boolean(credential),
|
|
639
664
|
projectId: credential?.projectId ?? null,
|
|
640
665
|
eventEndpoint: credential ? `${credential.appUrl}/api/events/batch` : null,
|
|
641
|
-
offlineQueue: queue
|
|
666
|
+
offlineQueue: queue,
|
|
667
|
+
offlineQueueBacklog: queueBacklog,
|
|
668
|
+
legacyQueue: legacyQueue.pending ? legacyQueue.quarantined : null
|
|
642
669
|
},
|
|
643
670
|
null,
|
|
644
671
|
2
|
|
@@ -757,8 +784,11 @@ async function emitCommand(adapter, eventName, input) {
|
|
|
757
784
|
const publishableEvents = [event, ...repositoryEvents].filter(
|
|
758
785
|
(candidate) => eventTypeForHook(candidate)
|
|
759
786
|
);
|
|
760
|
-
const queue = new JsonFileOfflineQueue(queuePath());
|
|
761
787
|
const credential = await loadCredential(process.cwd());
|
|
788
|
+
await quarantineLegacyQueue();
|
|
789
|
+
const queue = new JsonFileOfflineQueue(
|
|
790
|
+
credential ? projectQueuePath(credential.projectId) : pendingQueuePath(repo?.root ?? process.cwd())
|
|
791
|
+
);
|
|
762
792
|
if (!credential) {
|
|
763
793
|
if (publishableEvents.length) await queue.enqueue(publishableEvents);
|
|
764
794
|
return {
|
|
@@ -797,6 +827,7 @@ async function shareProgressCommand(options, cwd = process.cwd()) {
|
|
|
797
827
|
if (!projectConfig || !credential) {
|
|
798
828
|
throw new Error("Wibe is not authorized in this repository. Run wibe setup first.");
|
|
799
829
|
}
|
|
830
|
+
await quarantineLegacyQueue();
|
|
800
831
|
const summary = options.summary?.trim() ?? "";
|
|
801
832
|
const wordCount = summary.split(/\s+/).filter(Boolean).length;
|
|
802
833
|
if (wordCount < 20 || wordCount > 55 || summary.length > 500) {
|
|
@@ -860,7 +891,7 @@ async function shareProgressCommand(options, cwd = process.cwd()) {
|
|
|
860
891
|
projectId: credential.projectId,
|
|
861
892
|
repositoryId: credential.repositoryId,
|
|
862
893
|
deviceId: credential.deviceId,
|
|
863
|
-
queue: new JsonFileOfflineQueue(
|
|
894
|
+
queue: new JsonFileOfflineQueue(projectQueuePath(credential.projectId))
|
|
864
895
|
}).capture(event);
|
|
865
896
|
return {
|
|
866
897
|
exitCode: result.error ? 1 : 0,
|
|
@@ -868,16 +899,29 @@ async function shareProgressCommand(options, cwd = process.cwd()) {
|
|
|
868
899
|
};
|
|
869
900
|
}
|
|
870
901
|
async function doctorCommand(cwd = process.cwd(), requestedRepository) {
|
|
902
|
+
const legacyQueue = await quarantineLegacyQueue();
|
|
871
903
|
const credential = await loadCredential(cwd);
|
|
872
904
|
const projectConfig = await readProjectConfig(join(cwd, ".wibe", "project.json"));
|
|
905
|
+
const repositoryForQueue = await detectRepository(cwd);
|
|
906
|
+
const queuePath = credential ? projectQueuePath(credential.projectId) : pendingQueuePath(repositoryForQueue?.root ?? cwd);
|
|
907
|
+
let queueBacklog;
|
|
908
|
+
let queueError;
|
|
909
|
+
try {
|
|
910
|
+
queueBacklog = await new JsonFileOfflineQueue(queuePath).size();
|
|
911
|
+
} catch (error) {
|
|
912
|
+
queueError = error instanceof Error ? error.message : String(error);
|
|
913
|
+
}
|
|
873
914
|
const expectedRepository = requestedRepository ? normalizeGitHubRepository(requestedRepository) : projectConfig?.repository;
|
|
874
915
|
if (requestedRepository && !expectedRepository) {
|
|
875
916
|
throw new Error(
|
|
876
917
|
`Expected repository "${requestedRepository}" is not a valid GitHub owner/repository.`
|
|
877
918
|
);
|
|
878
919
|
}
|
|
879
|
-
const repository =
|
|
920
|
+
const repository = repositoryForQueue;
|
|
880
921
|
const repositoryMatches = expectedRepository ? matchesGitHubRepository(repository?.remote, expectedRepository) : true;
|
|
922
|
+
const credentialMatchesProjectConfig = Boolean(
|
|
923
|
+
credential && projectConfig && credential.projectId === projectConfig.projectId && credential.appUrl.replace(/\/$/, "") === projectConfig.appUrl.replace(/\/$/, "")
|
|
924
|
+
);
|
|
881
925
|
let adapter;
|
|
882
926
|
let adapterError;
|
|
883
927
|
try {
|
|
@@ -885,11 +929,17 @@ async function doctorCommand(cwd = process.cwd(), requestedRepository) {
|
|
|
885
929
|
} catch (error) {
|
|
886
930
|
adapterError = error instanceof Error ? error.message : String(error);
|
|
887
931
|
}
|
|
888
|
-
const heartbeat = credential && adapter && repositoryMatches ? await sendVerificationHeartbeat(credential, adapter, "doctor") : void 0;
|
|
889
|
-
const nativeConfig = adapter ? await validateNativeConfigs(adapter, cwd) : { hooks: false, mcp: false, activityRule: false, projectTrust: void 0 };
|
|
932
|
+
const heartbeat = credential && adapter && repositoryMatches && credentialMatchesProjectConfig ? await sendVerificationHeartbeat(credential, adapter, "doctor", cwd) : void 0;
|
|
933
|
+
const nativeConfig = adapter ? await validateNativeConfigs(adapter, cwd, projectConfig?.projectId) : { hooks: false, mcp: false, activityRule: false, projectTrust: void 0 };
|
|
890
934
|
const trustNotes = adapter === "codex" && nativeConfig.projectTrust === void 0 ? [
|
|
891
935
|
"note Codex project trust could not be verified because the user config was not found; trust this project in Codex and rerun doctor"
|
|
892
936
|
] : [];
|
|
937
|
+
const queueNotes = [
|
|
938
|
+
`note project queue ${queuePath} contains ${queueBacklog ?? "unknown"} event(s)`,
|
|
939
|
+
...legacyQueue.pending ? [
|
|
940
|
+
`note legacy shared queue is quarantined at ${legacyQueue.quarantined} and will not be delivered automatically`
|
|
941
|
+
] : []
|
|
942
|
+
];
|
|
893
943
|
const checks = [
|
|
894
944
|
["node", Number(process.versions.node.split(".")[0]) >= 20],
|
|
895
945
|
["git repository", Boolean(repository)],
|
|
@@ -902,6 +952,14 @@ async function doctorCommand(cwd = process.cwd(), requestedRepository) {
|
|
|
902
952
|
["device authorization", Boolean(credential)],
|
|
903
953
|
["event endpoint", Boolean(credential?.appUrl)],
|
|
904
954
|
["project scope", Boolean(credential?.projectId)],
|
|
955
|
+
[
|
|
956
|
+
"project config matches device credential",
|
|
957
|
+
credentialMatchesProjectConfig
|
|
958
|
+
],
|
|
959
|
+
[
|
|
960
|
+
queueError ? `project queue (${queueError})` : "project queue",
|
|
961
|
+
queueError === void 0
|
|
962
|
+
],
|
|
905
963
|
[
|
|
906
964
|
adapterError ? `adapter detection (${adapterError})` : "adapter detection",
|
|
907
965
|
Boolean(adapter)
|
|
@@ -927,13 +985,14 @@ async function doctorCommand(cwd = process.cwd(), requestedRepository) {
|
|
|
927
985
|
exitCode: failures ? 1 : 0,
|
|
928
986
|
message: [
|
|
929
987
|
...checks.map(([name, okay]) => `${okay ? "ok" : "missing"} ${name}`),
|
|
930
|
-
...trustNotes
|
|
988
|
+
...trustNotes,
|
|
989
|
+
...queueNotes
|
|
931
990
|
].join("\n")
|
|
932
991
|
};
|
|
933
992
|
}
|
|
934
993
|
async function loadCredential(cwd) {
|
|
935
994
|
if (process.env.WIBE_ACCESS_TOKEN && process.env.WIBE_PROJECT_ID && process.env.WIBE_ORGANIZATION_ID && process.env.WIBE_DEVICE_ID) {
|
|
936
|
-
|
|
995
|
+
const credential = {
|
|
937
996
|
appUrl: process.env.WIBE_APP_URL ?? "http://localhost:3000",
|
|
938
997
|
accessToken: process.env.WIBE_ACCESS_TOKEN,
|
|
939
998
|
projectId: process.env.WIBE_PROJECT_ID,
|
|
@@ -941,17 +1000,22 @@ async function loadCredential(cwd) {
|
|
|
941
1000
|
repositoryId: process.env.WIBE_REPOSITORY_ID,
|
|
942
1001
|
deviceId: process.env.WIBE_DEVICE_ID
|
|
943
1002
|
};
|
|
1003
|
+
const project = await readProjectConfig(join(cwd, ".wibe", "project.json"));
|
|
1004
|
+
if (!project || project.projectId !== credential.projectId || project.appUrl.replace(/\/$/, "") !== credential.appUrl.replace(/\/$/, "")) {
|
|
1005
|
+
return null;
|
|
1006
|
+
}
|
|
1007
|
+
return credential;
|
|
944
1008
|
}
|
|
945
1009
|
try {
|
|
946
|
-
const project =
|
|
947
|
-
|
|
948
|
-
);
|
|
949
|
-
if (!project.projectId) return null;
|
|
1010
|
+
const project = await readProjectConfig(join(cwd, ".wibe", "project.json"));
|
|
1011
|
+
if (!project) return null;
|
|
950
1012
|
const stored = await new SystemCredentialStore().get(
|
|
951
1013
|
"dev.wibe.bridge",
|
|
952
1014
|
project.projectId
|
|
953
1015
|
);
|
|
954
|
-
|
|
1016
|
+
if (!stored) return null;
|
|
1017
|
+
const credential = JSON.parse(stored);
|
|
1018
|
+
return credential.projectId === project.projectId && credential.appUrl.replace(/\/$/, "") === project.appUrl.replace(/\/$/, "") ? credential : null;
|
|
955
1019
|
} catch {
|
|
956
1020
|
return null;
|
|
957
1021
|
}
|
|
@@ -982,8 +1046,19 @@ async function installNativeConfigs(adapter, source, cwd, appUrl, projectId) {
|
|
|
982
1046
|
for (const [sourceName, destinationName] of files) {
|
|
983
1047
|
const destinationPath = join(cwd, destinationName);
|
|
984
1048
|
const destinationExists = await exists(destinationPath);
|
|
1049
|
+
const isMcpConfig = destinationName === ".cursor/mcp.json" || destinationName === ".mcp.json" || destinationName === ".codex/config.toml";
|
|
985
1050
|
const isCursorActivityRule = adapter === "cursor" && destinationName === ".cursor/rules/wibe-activity.mdc";
|
|
986
1051
|
if (destinationExists) {
|
|
1052
|
+
if (isMcpConfig) {
|
|
1053
|
+
if (await refreshMcpConfig(
|
|
1054
|
+
adapter,
|
|
1055
|
+
destinationPath,
|
|
1056
|
+
`${appUrl}/api/mcp/projects/${projectId}`
|
|
1057
|
+
)) {
|
|
1058
|
+
installed.push(destinationName);
|
|
1059
|
+
}
|
|
1060
|
+
continue;
|
|
1061
|
+
}
|
|
987
1062
|
if (!isCursorActivityRule) continue;
|
|
988
1063
|
const existingRule = await readFile(destinationPath, "utf8");
|
|
989
1064
|
if (!existingRule.includes("# Wibe activity") || !existingRule.includes("wibe_share_progress")) {
|
|
@@ -1016,6 +1091,37 @@ async function installNativeConfigs(adapter, source, cwd, appUrl, projectId) {
|
|
|
1016
1091
|
}
|
|
1017
1092
|
return installed;
|
|
1018
1093
|
}
|
|
1094
|
+
async function refreshMcpConfig(adapter, path, resourceUrl) {
|
|
1095
|
+
const current = await readFile(path, "utf8");
|
|
1096
|
+
if (adapter === "codex") {
|
|
1097
|
+
const next = current.replace(
|
|
1098
|
+
/(\[mcp_servers\.wibe\][\s\S]*?^\s*url\s*=\s*)["'][^"']*\/api\/mcp(?:\/projects\/[0-9a-f-]+)?\/?["']/m,
|
|
1099
|
+
`$1${JSON.stringify(resourceUrl)}`
|
|
1100
|
+
);
|
|
1101
|
+
if (next === current) return false;
|
|
1102
|
+
await writeFile(path, next, { mode: 384 });
|
|
1103
|
+
return true;
|
|
1104
|
+
}
|
|
1105
|
+
let parsed;
|
|
1106
|
+
try {
|
|
1107
|
+
const value = JSON.parse(current);
|
|
1108
|
+
if (!isRecord(value)) return false;
|
|
1109
|
+
parsed = value;
|
|
1110
|
+
} catch {
|
|
1111
|
+
return false;
|
|
1112
|
+
}
|
|
1113
|
+
if (!isRecord(parsed.mcpServers) || !isRecord(parsed.mcpServers.wibe)) {
|
|
1114
|
+
return false;
|
|
1115
|
+
}
|
|
1116
|
+
const existingUrl = parsed.mcpServers.wibe.url;
|
|
1117
|
+
if (typeof existingUrl !== "string" || !existingUrl.includes("/api/mcp")) {
|
|
1118
|
+
return false;
|
|
1119
|
+
}
|
|
1120
|
+
parsed.mcpServers.wibe.url = resourceUrl;
|
|
1121
|
+
await writeFile(path, `${JSON.stringify(parsed, null, 2)}
|
|
1122
|
+
`, { mode: 384 });
|
|
1123
|
+
return true;
|
|
1124
|
+
}
|
|
1019
1125
|
function renderTemplate(template, appUrl, projectId) {
|
|
1020
1126
|
return template.replaceAll("${env:WIBE_APP_URL}", appUrl).replaceAll("${WIBE_APP_URL}", appUrl).replaceAll("${WIBE_PROJECT_ID}", projectId);
|
|
1021
1127
|
}
|
|
@@ -1045,8 +1151,69 @@ async function ensureClaudeImportsAgents(path) {
|
|
|
1045
1151
|
await writeFile(path, next, { mode: 420 });
|
|
1046
1152
|
return true;
|
|
1047
1153
|
}
|
|
1048
|
-
function
|
|
1049
|
-
|
|
1154
|
+
function queueRoot() {
|
|
1155
|
+
const override = process.env.WIBE_QUEUE_PATH?.trim();
|
|
1156
|
+
if (!override) return join(homedir(), ".wibe", "queue");
|
|
1157
|
+
return extname(override) ? `${override}.d` : override;
|
|
1158
|
+
}
|
|
1159
|
+
function projectQueuePath(projectId) {
|
|
1160
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(
|
|
1161
|
+
projectId
|
|
1162
|
+
)) {
|
|
1163
|
+
throw new Error("Wibe project ID must be a UUID.");
|
|
1164
|
+
}
|
|
1165
|
+
return join(queueRoot(), `${projectId}.json`);
|
|
1166
|
+
}
|
|
1167
|
+
function pendingQueuePath(cwd) {
|
|
1168
|
+
let canonicalRoot = resolve(cwd);
|
|
1169
|
+
try {
|
|
1170
|
+
canonicalRoot = realpathSync.native(canonicalRoot);
|
|
1171
|
+
} catch {
|
|
1172
|
+
}
|
|
1173
|
+
const repositoryHash = createHash("sha256").update(canonicalRoot).digest("hex").slice(0, 24);
|
|
1174
|
+
return join(queueRoot(), `pending-${repositoryHash}.json`);
|
|
1175
|
+
}
|
|
1176
|
+
function legacyQueuePaths() {
|
|
1177
|
+
const override = process.env.WIBE_QUEUE_PATH?.trim();
|
|
1178
|
+
if (override && extname(override)) {
|
|
1179
|
+
return {
|
|
1180
|
+
current: override,
|
|
1181
|
+
quarantined: `${override.slice(0, -extname(override).length)}.legacy${extname(override)}`
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
const directory = override || join(homedir(), ".wibe");
|
|
1185
|
+
return {
|
|
1186
|
+
current: join(directory, "events.json"),
|
|
1187
|
+
quarantined: join(directory, "events.legacy.json")
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
async function quarantineLegacyQueue() {
|
|
1191
|
+
const paths = legacyQueuePaths();
|
|
1192
|
+
if (await exists(paths.current)) {
|
|
1193
|
+
if (!await exists(paths.quarantined)) {
|
|
1194
|
+
await rename(paths.current, paths.quarantined).catch((error) => {
|
|
1195
|
+
if (error.code !== "ENOENT") throw error;
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
return {
|
|
1200
|
+
...paths,
|
|
1201
|
+
pending: await exists(paths.current) || await exists(paths.quarantined)
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
async function adoptPendingQueue(cwd, projectId) {
|
|
1205
|
+
await quarantineLegacyQueue();
|
|
1206
|
+
const repository = await detectRepository(cwd);
|
|
1207
|
+
const pending = new JsonFileOfflineQueue(
|
|
1208
|
+
pendingQueuePath(repository?.root ?? cwd)
|
|
1209
|
+
);
|
|
1210
|
+
const project = new JsonFileOfflineQueue(projectQueuePath(projectId));
|
|
1211
|
+
while (true) {
|
|
1212
|
+
const events = await pending.peek(100);
|
|
1213
|
+
if (!events.length) return;
|
|
1214
|
+
await project.enqueue(events);
|
|
1215
|
+
await pending.remove(events.map((event) => event.id));
|
|
1216
|
+
}
|
|
1050
1217
|
}
|
|
1051
1218
|
async function exists(path) {
|
|
1052
1219
|
try {
|
|
@@ -1056,7 +1223,7 @@ async function exists(path) {
|
|
|
1056
1223
|
return false;
|
|
1057
1224
|
}
|
|
1058
1225
|
}
|
|
1059
|
-
async function validateNativeConfigs(adapter, cwd) {
|
|
1226
|
+
async function validateNativeConfigs(adapter, cwd, projectId) {
|
|
1060
1227
|
if (adapter === "codex") {
|
|
1061
1228
|
const hooks = await validJson(
|
|
1062
1229
|
join(cwd, ".codex", "hooks.json"),
|
|
@@ -1065,14 +1232,19 @@ async function validateNativeConfigs(adapter, cwd) {
|
|
|
1065
1232
|
let mcp = false;
|
|
1066
1233
|
try {
|
|
1067
1234
|
const config = await readFile(join(cwd, ".codex", "config.toml"), "utf8");
|
|
1068
|
-
mcp = config.includes("[mcp_servers.wibe]") &&
|
|
1235
|
+
mcp = config.includes("[mcp_servers.wibe]") && Boolean(
|
|
1236
|
+
projectId && config.includes(`/api/mcp/projects/${projectId}`)
|
|
1237
|
+
);
|
|
1069
1238
|
} catch {
|
|
1070
1239
|
mcp = false;
|
|
1071
1240
|
}
|
|
1072
1241
|
return {
|
|
1073
1242
|
hooks,
|
|
1074
1243
|
mcp,
|
|
1075
|
-
activityRule: await validWibeActivityInstructions(
|
|
1244
|
+
activityRule: await validWibeActivityInstructions(
|
|
1245
|
+
join(cwd, "AGENTS.md"),
|
|
1246
|
+
projectId
|
|
1247
|
+
),
|
|
1076
1248
|
projectTrust: await codexProjectTrust(cwd)
|
|
1077
1249
|
};
|
|
1078
1250
|
}
|
|
@@ -1083,19 +1255,21 @@ async function validateNativeConfigs(adapter, cwd) {
|
|
|
1083
1255
|
mcp: await validJson(mcpPath, (value) => {
|
|
1084
1256
|
if (!isRecord(value.mcpServers)) return false;
|
|
1085
1257
|
const wibe = value.mcpServers.wibe;
|
|
1086
|
-
return isRecord(wibe) && typeof wibe.url === "string" &&
|
|
1258
|
+
return isRecord(wibe) && typeof wibe.url === "string" && Boolean(
|
|
1259
|
+
projectId && wibe.url.replace(/\/$/, "").endsWith(`/api/mcp/projects/${projectId}`)
|
|
1260
|
+
);
|
|
1087
1261
|
}),
|
|
1088
1262
|
activityRule: adapter === "cursor" ? await validText(
|
|
1089
1263
|
join(cwd, ".cursor", "rules", "wibe-activity.mdc"),
|
|
1090
|
-
(value) => value.includes("alwaysApply: true") && value.includes("wibe_share_progress") && value.includes("wibe share-progress")
|
|
1091
|
-
) : await validWibeActivityInstructions(join(cwd, "AGENTS.md")),
|
|
1264
|
+
(value) => value.includes("alwaysApply: true") && Boolean(projectId && value.includes(projectId)) && value.includes("wibe_share_progress") && value.includes("wibe share-progress")
|
|
1265
|
+
) : await validWibeActivityInstructions(join(cwd, "AGENTS.md"), projectId),
|
|
1092
1266
|
projectTrust: void 0
|
|
1093
1267
|
};
|
|
1094
1268
|
}
|
|
1095
|
-
function validWibeActivityInstructions(path) {
|
|
1269
|
+
function validWibeActivityInstructions(path, projectId) {
|
|
1096
1270
|
return validText(
|
|
1097
1271
|
path,
|
|
1098
|
-
(value) => value.includes(WIBE_ACTIVITY_BEGIN) && value.includes(WIBE_ACTIVITY_END) && value.includes("wibe_share_progress") && value.includes("wibe share-progress")
|
|
1272
|
+
(value) => value.includes(WIBE_ACTIVITY_BEGIN) && value.includes(WIBE_ACTIVITY_END) && Boolean(projectId && value.includes(projectId)) && value.includes("wibe_share_progress") && value.includes("wibe share-progress")
|
|
1099
1273
|
);
|
|
1100
1274
|
}
|
|
1101
1275
|
async function codexProjectTrust(cwd) {
|
|
@@ -1255,8 +1429,8 @@ async function assertExpectedRepository(cwd, expected) {
|
|
|
1255
1429
|
`Repository mismatch: expected GitHub repository "${expected}", but found ${actual}. Run this command from the expected repository root or correct the origin remote, then retry.`
|
|
1256
1430
|
);
|
|
1257
1431
|
}
|
|
1258
|
-
async function sendVerificationHeartbeat(credential, adapter, reason) {
|
|
1259
|
-
const repository = await detectRepository(
|
|
1432
|
+
async function sendVerificationHeartbeat(credential, adapter, reason, cwd) {
|
|
1433
|
+
const repository = await detectRepository(cwd);
|
|
1260
1434
|
const workingTree = repository ? await detectWorkingTreeMetrics(repository.root) : void 0;
|
|
1261
1435
|
return new SignedBatchClient({
|
|
1262
1436
|
endpoint: `${credential.appUrl}/api/events/batch`,
|
package/dist/cli.js
CHANGED
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
setupCommand,
|
|
8
8
|
shareProgressCommand,
|
|
9
9
|
statusCommand
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-YLC4TF4F.js";
|
|
11
11
|
import {
|
|
12
12
|
runPresenceHeartbeat
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-4WIGVKDR.js";
|
|
14
14
|
|
|
15
15
|
// src/cli.ts
|
|
16
16
|
var HELP = `wibe-bridge <command>
|
package/dist/codex-hook.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -228,18 +228,27 @@ declare function pollDeviceToken(input: {
|
|
|
228
228
|
}>;
|
|
229
229
|
type AgentName = "cursor" | "claude-code" | "codex";
|
|
230
230
|
|
|
231
|
+
interface OfflineQueueTransaction {
|
|
232
|
+
peek(limit: number): Promise<CanonicalHookEvent[]>;
|
|
233
|
+
remove(ids: readonly string[]): Promise<void>;
|
|
234
|
+
size(): Promise<number>;
|
|
235
|
+
}
|
|
231
236
|
interface OfflineQueue {
|
|
232
237
|
enqueue(events: readonly CanonicalHookEvent[]): Promise<void>;
|
|
233
238
|
peek(limit: number): Promise<CanonicalHookEvent[]>;
|
|
234
239
|
remove(ids: readonly string[]): Promise<void>;
|
|
235
240
|
size(): Promise<number>;
|
|
241
|
+
withTransaction<T>(operation: (queue: OfflineQueueTransaction) => Promise<T>): Promise<T>;
|
|
236
242
|
}
|
|
237
243
|
declare class MemoryOfflineQueue implements OfflineQueue {
|
|
238
244
|
private events;
|
|
245
|
+
private operation;
|
|
239
246
|
enqueue(events: readonly CanonicalHookEvent[]): Promise<void>;
|
|
240
247
|
peek(limit: number): Promise<CanonicalHookEvent[]>;
|
|
241
248
|
remove(ids: readonly string[]): Promise<void>;
|
|
242
249
|
size(): Promise<number>;
|
|
250
|
+
withTransaction<T>(operation: (queue: OfflineQueueTransaction) => Promise<T>): Promise<T>;
|
|
251
|
+
private serialize;
|
|
243
252
|
}
|
|
244
253
|
declare class JsonFileOfflineQueue implements OfflineQueue {
|
|
245
254
|
private readonly filePath;
|
|
@@ -249,8 +258,11 @@ declare class JsonFileOfflineQueue implements OfflineQueue {
|
|
|
249
258
|
peek(limit: number): Promise<CanonicalHookEvent[]>;
|
|
250
259
|
remove(ids: readonly string[]): Promise<void>;
|
|
251
260
|
size(): Promise<number>;
|
|
252
|
-
|
|
253
|
-
private
|
|
261
|
+
withTransaction<T>(operation: (queue: OfflineQueueTransaction) => Promise<T>): Promise<T>;
|
|
262
|
+
private readUnlocked;
|
|
263
|
+
private updateUnlocked;
|
|
264
|
+
private serialize;
|
|
265
|
+
private acquireLock;
|
|
254
266
|
}
|
|
255
267
|
|
|
256
268
|
interface SignedBatchClientOptions {
|
|
@@ -346,4 +358,4 @@ declare function sanitizeRemote(remote: string): string;
|
|
|
346
358
|
declare function normalizeGitHubRepository(value: string): string | undefined;
|
|
347
359
|
declare function matchesGitHubRepository(remote: string | undefined, expected: string): boolean;
|
|
348
360
|
|
|
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 };
|
|
361
|
+
export { type AgentSource, type CanonicalHookEvent, type CredentialStore, type DeviceAuthorization, type DeviceTokenResponse, type FlushResult, type HookEventKind, JsonFileOfflineQueue, MemoryOfflineQueue, type OfflineQueue, type OfflineQueueTransaction, 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
package/package.json
CHANGED
|
@@ -13,5 +13,7 @@ stored in the operating-system keychain; hooks do not require repository secrets
|
|
|
13
13
|
allow-list excludes prompt, content, messages, command text, tool arguments/results, and transcripts.
|
|
14
14
|
|
|
15
15
|
`mcp.json.example` connects Claude Code to Wibe's remote MCP endpoint at
|
|
16
|
-
`${env:WIBE_APP_URL}/api/mcp`. The
|
|
17
|
-
|
|
16
|
+
`${env:WIBE_APP_URL}/api/mcp/projects/${WIBE_PROJECT_ID}`. The project-specific resource keeps this
|
|
17
|
+
repository's OAuth grant isolated from other Wibe projects on the same machine. The MCP client
|
|
18
|
+
completes OAuth separately from the bridge device token. Set `WIBE_APP_URL` to the same Wibe origin
|
|
19
|
+
used during setup.
|
|
@@ -9,8 +9,10 @@ and transcripts are not retained.
|
|
|
9
9
|
Run `wibe setup` to authorize a revocable, project-scoped device token stored in the operating-system
|
|
10
10
|
keychain. Hooks do not require repository signing secrets.
|
|
11
11
|
|
|
12
|
-
The MCP block connects Codex to
|
|
13
|
-
|
|
12
|
+
The MCP block connects Codex to a project-specific remote
|
|
13
|
+
`/api/mcp/projects/${WIBE_PROJECT_ID}` endpoint, keeping this repository's OAuth grant isolated from
|
|
14
|
+
other Wibe projects on the same machine. Codex completes OAuth separately from the bridge device
|
|
15
|
+
token. Set `WIBE_APP_URL` to the same Wibe origin used during setup.
|
|
14
16
|
Project-local Codex configuration loads only after the project is trusted, and command hooks must be
|
|
15
17
|
reviewed in `/hooks`. Session hooks own a metadata-only 45-second heartbeat that ends cleanly with
|
|
16
18
|
`SessionEnd`.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
[mcp_servers.wibe]
|
|
2
|
-
url = "${WIBE_APP_URL}/api/mcp"
|
|
2
|
+
url = "${WIBE_APP_URL}/api/mcp/projects/${WIBE_PROJECT_ID}"
|
|
@@ -17,5 +17,7 @@ stored in the operating-system keychain; hooks do not require repository secrets
|
|
|
17
17
|
prompt, content, file body, command text, tool arguments/results, and transcripts.
|
|
18
18
|
|
|
19
19
|
`mcp.json.example` connects Cursor to Wibe's remote MCP endpoint at
|
|
20
|
-
`${env:WIBE_APP_URL}/api/mcp`.
|
|
21
|
-
|
|
20
|
+
`${env:WIBE_APP_URL}/api/mcp/projects/${WIBE_PROJECT_ID}`. The project-specific resource keeps this
|
|
21
|
+
repository's OAuth grant isolated from other Wibe projects on the same machine. Cursor completes
|
|
22
|
+
OAuth separately from the bridge device token. Set `WIBE_APP_URL` to the same Wibe origin used
|
|
23
|
+
during setup.
|