@wibeco/bridge 0.2.14 → 0.2.16
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 +5 -0
- package/dist/{chunk-7LWUGOKM.js → chunk-E2RWE6UW.js} +507 -8
- package/dist/cli.js +29 -2
- package/dist/codex-hook.js +1 -1
- package/package.json +1 -1
- package/templates/claude-code/README.md +5 -4
- package/templates/claude-code/wibe-activity.md.example +15 -0
- package/templates/codex/README.md +9 -10
- package/templates/codex/config.toml.example +0 -4
- package/templates/codex/wibe-activity.md.example +15 -0
- package/templates/cursor/wibe-activity.mdc.example +8 -2
package/README.md
CHANGED
|
@@ -18,6 +18,11 @@ Dependency-light TypeScript foundations for normalizing agent hooks and sending
|
|
|
18
18
|
Install and authorize Wibe from any GitHub repository:
|
|
19
19
|
|
|
20
20
|
```sh
|
|
21
|
+
npx --yes --package @wibeco/bridge@latest wibe onboard \
|
|
22
|
+
--adapter cursor \
|
|
23
|
+
--url https://trywibe.com \
|
|
24
|
+
--repository <owner/repository>
|
|
25
|
+
|
|
21
26
|
npx --yes --package @wibeco/bridge@latest wibe setup \
|
|
22
27
|
--adapter cursor \
|
|
23
28
|
--project <uuid> \
|
|
@@ -27,7 +27,189 @@ import { homedir, hostname } from "os";
|
|
|
27
27
|
import { basename, join, resolve } from "path";
|
|
28
28
|
import { fileURLToPath } from "url";
|
|
29
29
|
import { execFile } from "child_process";
|
|
30
|
+
|
|
31
|
+
// src/onboard.ts
|
|
32
|
+
import { z } from "zod";
|
|
33
|
+
var sessionCreatedSchema = z.object({
|
|
34
|
+
session_id: z.string().uuid(),
|
|
35
|
+
session_secret: z.string().min(1),
|
|
36
|
+
authorization_url: z.string().url(),
|
|
37
|
+
expires_at: z.string()
|
|
38
|
+
});
|
|
39
|
+
var snapshotSchema = z.object({
|
|
40
|
+
status: z.enum(["pending_auth", "authenticated", "completed", "expired"]),
|
|
41
|
+
next: z.enum([
|
|
42
|
+
"authenticate",
|
|
43
|
+
"create_organization",
|
|
44
|
+
"install_github",
|
|
45
|
+
"create_project",
|
|
46
|
+
"connect_device",
|
|
47
|
+
"activate",
|
|
48
|
+
"done"
|
|
49
|
+
]),
|
|
50
|
+
user_id: z.string().uuid().nullable(),
|
|
51
|
+
organization_id: z.string().uuid().nullable(),
|
|
52
|
+
project_id: z.string().uuid().nullable(),
|
|
53
|
+
github_connected: z.boolean(),
|
|
54
|
+
project_url: z.string().url().nullable(),
|
|
55
|
+
expires_at: z.string(),
|
|
56
|
+
access_token: z.string().min(1).optional()
|
|
57
|
+
});
|
|
58
|
+
var organizationSchema = z.object({
|
|
59
|
+
organization_id: z.string().uuid()
|
|
60
|
+
});
|
|
61
|
+
var githubInstallSchema = z.object({
|
|
62
|
+
github_connected: z.boolean(),
|
|
63
|
+
install_url: z.string().url().nullable()
|
|
64
|
+
});
|
|
65
|
+
var repositoriesSchema = z.object({
|
|
66
|
+
repositories: z.array(
|
|
67
|
+
z.object({
|
|
68
|
+
id: z.number().int(),
|
|
69
|
+
installationId: z.number().int(),
|
|
70
|
+
fullName: z.string(),
|
|
71
|
+
name: z.string()
|
|
72
|
+
})
|
|
73
|
+
)
|
|
74
|
+
});
|
|
75
|
+
var projectSchema = z.object({
|
|
76
|
+
project_id: z.string().uuid(),
|
|
77
|
+
reused: z.boolean().optional(),
|
|
78
|
+
setup_status: z.string().optional()
|
|
79
|
+
});
|
|
80
|
+
var invitesSchema = z.object({
|
|
81
|
+
invites: z.array(
|
|
82
|
+
z.object({
|
|
83
|
+
email: z.string().nullable().optional(),
|
|
84
|
+
url: z.string().url()
|
|
85
|
+
})
|
|
86
|
+
)
|
|
87
|
+
});
|
|
88
|
+
var deviceSchema = z.object({
|
|
89
|
+
accessToken: z.string().min(1),
|
|
90
|
+
projectId: z.string().uuid(),
|
|
91
|
+
organizationId: z.string().uuid(),
|
|
92
|
+
repositoryId: z.string().uuid().optional(),
|
|
93
|
+
deviceId: z.string().uuid()
|
|
94
|
+
});
|
|
95
|
+
var activateSchema = z.object({
|
|
96
|
+
project_id: z.string().uuid(),
|
|
97
|
+
project_url: z.string().url()
|
|
98
|
+
});
|
|
99
|
+
function origin(appUrl) {
|
|
100
|
+
return appUrl.replace(/\/$/, "");
|
|
101
|
+
}
|
|
102
|
+
async function readJson(response) {
|
|
103
|
+
return await response.json().catch(() => ({}));
|
|
104
|
+
}
|
|
105
|
+
async function requestJson(url, schema, init) {
|
|
106
|
+
const response = await fetch(url, init);
|
|
107
|
+
const body = await readJson(response);
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
typeof body.error === "string" ? body.error : `Onboarding request failed (${response.status}).`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return schema.parse(body);
|
|
114
|
+
}
|
|
115
|
+
function authorized(accessToken) {
|
|
116
|
+
return {
|
|
117
|
+
authorization: `Bearer ${accessToken}`,
|
|
118
|
+
"content-type": "application/json"
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
async function createOnboardingSession(appUrl) {
|
|
122
|
+
return requestJson(
|
|
123
|
+
`${origin(appUrl)}/api/onboarding/sessions`,
|
|
124
|
+
sessionCreatedSchema,
|
|
125
|
+
{ method: "POST" }
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
async function pollOnboardingSession(input) {
|
|
129
|
+
return requestJson(
|
|
130
|
+
`${origin(input.appUrl)}/api/onboarding/sessions/${input.sessionId}`,
|
|
131
|
+
snapshotSchema,
|
|
132
|
+
{ headers: { "x-wibe-onboarding-secret": input.sessionSecret } }
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
async function createOnboardingOrganization(input) {
|
|
136
|
+
return requestJson(
|
|
137
|
+
`${origin(input.appUrl)}/api/onboarding/organization`,
|
|
138
|
+
organizationSchema,
|
|
139
|
+
{
|
|
140
|
+
method: "POST",
|
|
141
|
+
headers: authorized(input.accessToken),
|
|
142
|
+
body: JSON.stringify({ name: input.name })
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
async function requestGitHubInstall(input) {
|
|
147
|
+
return requestJson(
|
|
148
|
+
`${origin(input.appUrl)}/api/onboarding/github/install`,
|
|
149
|
+
githubInstallSchema,
|
|
150
|
+
{ method: "POST", headers: authorized(input.accessToken) }
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
async function listOnboardingRepositories(input) {
|
|
154
|
+
return requestJson(
|
|
155
|
+
`${origin(input.appUrl)}/api/onboarding/github/repositories`,
|
|
156
|
+
repositoriesSchema,
|
|
157
|
+
{ headers: authorized(input.accessToken) }
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
async function createOnboardingProject(input) {
|
|
161
|
+
return requestJson(
|
|
162
|
+
`${origin(input.appUrl)}/api/onboarding/project`,
|
|
163
|
+
projectSchema,
|
|
164
|
+
{
|
|
165
|
+
method: "POST",
|
|
166
|
+
headers: authorized(input.accessToken),
|
|
167
|
+
body: JSON.stringify({
|
|
168
|
+
projectName: input.projectName,
|
|
169
|
+
githubInstallationId: input.githubInstallationId,
|
|
170
|
+
providerRepositoryId: input.providerRepositoryId,
|
|
171
|
+
repository: input.repository
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
async function createOnboardingInvites(input) {
|
|
177
|
+
return requestJson(
|
|
178
|
+
`${origin(input.appUrl)}/api/onboarding/invites`,
|
|
179
|
+
invitesSchema,
|
|
180
|
+
{
|
|
181
|
+
method: "POST",
|
|
182
|
+
headers: authorized(input.accessToken),
|
|
183
|
+
body: JSON.stringify({ emails: input.emails })
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
async function mintOnboardingDevice(input) {
|
|
188
|
+
return requestJson(
|
|
189
|
+
`${origin(input.appUrl)}/api/onboarding/device`,
|
|
190
|
+
deviceSchema,
|
|
191
|
+
{
|
|
192
|
+
method: "POST",
|
|
193
|
+
headers: authorized(input.accessToken),
|
|
194
|
+
body: JSON.stringify({
|
|
195
|
+
deviceName: input.deviceName,
|
|
196
|
+
agentName: input.agentName
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
async function activateOnboardingProject(input) {
|
|
202
|
+
return requestJson(
|
|
203
|
+
`${origin(input.appUrl)}/api/onboarding/activate`,
|
|
204
|
+
activateSchema,
|
|
205
|
+
{ method: "POST", headers: authorized(input.accessToken) }
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/cli/commands.ts
|
|
30
210
|
var ADAPTERS = ["cursor", "claude-code", "codex"];
|
|
211
|
+
var WIBE_ACTIVITY_BEGIN = "<!-- BEGIN:wibe-activity -->";
|
|
212
|
+
var WIBE_ACTIVITY_END = "<!-- END:wibe-activity -->";
|
|
31
213
|
var MAX_SCREENSHOT_BYTES = 5 * 1024 * 1024;
|
|
32
214
|
function screenshotMimeType(bytes) {
|
|
33
215
|
if ([137, 80, 78, 71, 13, 10, 26, 10].every(
|
|
@@ -132,7 +314,7 @@ async function setupCommand(requestedAdapter, options = {}) {
|
|
|
132
314
|
);
|
|
133
315
|
return {
|
|
134
316
|
exitCode: heartbeat2.error ? 1 : 0,
|
|
135
|
-
message: heartbeat2.error ? `Wibe is already authorized, but verification failed (${heartbeat2.error}). No new device was created. Retry with wibe doctor; if the token is rejected, rerun setup with --reauthorize.` : `Wibe was already authorized and the event connection was verified. Installed ${installedNativeFiles2.length}
|
|
317
|
+
message: `${heartbeat2.error ? `Wibe is already authorized, but verification failed (${heartbeat2.error}). No new device was created. Retry with wibe doctor; if the token is rejected, rerun setup with --reauthorize.` : `Wibe was already authorized and the event connection was verified. Installed or refreshed ${installedNativeFiles2.length} native config file(s); unmanaged configuration was left untouched.`}${adapterSetupNextSteps(adapter)}`
|
|
136
318
|
};
|
|
137
319
|
}
|
|
138
320
|
const authorization = await requestDeviceAuthorization({
|
|
@@ -218,9 +400,225 @@ Confirm code ${authorization.user_code}
|
|
|
218
400
|
const heartbeat = await sendVerificationHeartbeat(credential, adapter, "setup");
|
|
219
401
|
return {
|
|
220
402
|
exitCode: heartbeat.error ? 1 : 0,
|
|
221
|
-
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 ${installedNativeFiles.length} native config file(s);
|
|
403
|
+
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)}`
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
function adapterSetupNextSteps(adapter) {
|
|
407
|
+
return adapter === "codex" ? "\nCodex next steps: trust this project when prompted, review and trust the Wibe command hooks in /hooks, then restart Codex so the project MCP server and activity instructions load." : "";
|
|
408
|
+
}
|
|
409
|
+
async function onboardCommand(requestedAdapter, options = {}) {
|
|
410
|
+
const cwd = options.cwd ?? process.cwd();
|
|
411
|
+
const expectedRepository = options.expectedRepository ? normalizeGitHubRepository(options.expectedRepository) : void 0;
|
|
412
|
+
if (options.expectedRepository && !expectedRepository) {
|
|
413
|
+
throw new Error(
|
|
414
|
+
`Expected repository "${options.expectedRepository}" is not a valid GitHub owner/repository.`
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
const detectedRepository = await detectRepository(cwd);
|
|
418
|
+
const repository = expectedRepository ?? (detectedRepository?.remote ? normalizeGitHubRepository(detectedRepository.remote) : void 0);
|
|
419
|
+
if (!repository) {
|
|
420
|
+
throw new Error(
|
|
421
|
+
"Could not detect a GitHub repository remote. Run this from a repository root or pass --repository owner/repo."
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
if (expectedRepository) {
|
|
425
|
+
await assertExpectedRepository(cwd, expectedRepository);
|
|
426
|
+
}
|
|
427
|
+
const adapter = await detectAdapter(requestedAdapter, cwd);
|
|
428
|
+
const source = await resolveTemplateSource(adapter);
|
|
429
|
+
const destination = join(cwd, ".wibe", "integrations", adapter);
|
|
430
|
+
if (!await exists(destination)) {
|
|
431
|
+
await mkdir(join(cwd, ".wibe", "integrations"), { recursive: true });
|
|
432
|
+
await cp(source, destination, { recursive: true, errorOnExist: true, force: false });
|
|
433
|
+
}
|
|
434
|
+
const appUrl = (options.appUrl ?? process.env.WIBE_APP_URL ?? "http://localhost:3000").replace(/\/$/, "");
|
|
435
|
+
const created = options.sessionId && options.sessionSecret ? {
|
|
436
|
+
session_id: options.sessionId,
|
|
437
|
+
session_secret: options.sessionSecret,
|
|
438
|
+
authorization_url: `${appUrl}/onboard`,
|
|
439
|
+
expires_at: new Date(Date.now() + 30 * 60 * 1e3).toISOString()
|
|
440
|
+
} : await createOnboardingSession(appUrl);
|
|
441
|
+
let snapshot = await pollOnboardingSession({
|
|
442
|
+
appUrl,
|
|
443
|
+
sessionId: created.session_id,
|
|
444
|
+
sessionSecret: created.session_secret
|
|
445
|
+
});
|
|
446
|
+
if (snapshot.next === "authenticate") {
|
|
447
|
+
process.stdout.write(
|
|
448
|
+
`Open ${created.authorization_url}
|
|
449
|
+
Approve GitHub sign-in in your browser, then return here.
|
|
450
|
+
`
|
|
451
|
+
);
|
|
452
|
+
openBrowser(created.authorization_url);
|
|
453
|
+
snapshot = await waitForOnboarding(
|
|
454
|
+
appUrl,
|
|
455
|
+
created.session_id,
|
|
456
|
+
created.session_secret,
|
|
457
|
+
(current) => current.next !== "authenticate" && Boolean(current.access_token)
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
const accessToken = snapshot.access_token;
|
|
461
|
+
if (!accessToken) {
|
|
462
|
+
throw new Error("Wibe onboarding did not return an access token.");
|
|
463
|
+
}
|
|
464
|
+
if (snapshot.next === "create_organization" || !snapshot.organization_id) {
|
|
465
|
+
const orgName = options.orgName?.trim() || repository.split("/")[0] || "Organization";
|
|
466
|
+
await createOnboardingOrganization({
|
|
467
|
+
appUrl,
|
|
468
|
+
accessToken,
|
|
469
|
+
name: orgName
|
|
470
|
+
});
|
|
471
|
+
snapshot = await pollOnboardingSession({
|
|
472
|
+
appUrl,
|
|
473
|
+
sessionId: created.session_id,
|
|
474
|
+
sessionSecret: created.session_secret
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
if (snapshot.next === "install_github" || !snapshot.github_connected) {
|
|
478
|
+
const install = await requestGitHubInstall({ appUrl, accessToken });
|
|
479
|
+
if (!install.github_connected) {
|
|
480
|
+
if (!install.install_url) {
|
|
481
|
+
throw new Error("Wibe did not return a GitHub App install URL.");
|
|
482
|
+
}
|
|
483
|
+
process.stdout.write(
|
|
484
|
+
`Open ${install.install_url}
|
|
485
|
+
Install the Wibe GitHub App on ${repository}, then return here.
|
|
486
|
+
`
|
|
487
|
+
);
|
|
488
|
+
openBrowser(install.install_url);
|
|
489
|
+
snapshot = await waitForOnboarding(
|
|
490
|
+
appUrl,
|
|
491
|
+
created.session_id,
|
|
492
|
+
created.session_secret,
|
|
493
|
+
(current) => current.github_connected
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
const repositories = await listOnboardingRepositories({ appUrl, accessToken });
|
|
498
|
+
const matched = repositories.repositories.find(
|
|
499
|
+
(candidate) => normalizeGitHubRepository(candidate.fullName) === repository
|
|
500
|
+
);
|
|
501
|
+
if (!matched) {
|
|
502
|
+
throw new Error(
|
|
503
|
+
`GitHub App install does not include ${repository}. Install Wibe on that repository and retry.`
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
if (snapshot.next === "create_project" || !snapshot.project_id) {
|
|
507
|
+
await createOnboardingProject({
|
|
508
|
+
appUrl,
|
|
509
|
+
accessToken,
|
|
510
|
+
projectName: matched.name,
|
|
511
|
+
githubInstallationId: matched.installationId,
|
|
512
|
+
providerRepositoryId: matched.id,
|
|
513
|
+
repository: matched.fullName
|
|
514
|
+
});
|
|
515
|
+
snapshot = await pollOnboardingSession({
|
|
516
|
+
appUrl,
|
|
517
|
+
sessionId: created.session_id,
|
|
518
|
+
sessionSecret: created.session_secret
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
const inviteEmails = (options.invites ?? []).filter(Boolean);
|
|
522
|
+
const invites = await createOnboardingInvites({
|
|
523
|
+
appUrl,
|
|
524
|
+
accessToken,
|
|
525
|
+
emails: inviteEmails
|
|
526
|
+
});
|
|
527
|
+
if (!snapshot.project_id) {
|
|
528
|
+
throw new Error("Wibe onboarding did not create a project.");
|
|
529
|
+
}
|
|
530
|
+
const projectConfigPath = join(cwd, ".wibe", "project.json");
|
|
531
|
+
const existingProjectConfig = await readProjectConfig(projectConfigPath);
|
|
532
|
+
const existingCredential = existingProjectConfig ? await loadCredential(cwd) : null;
|
|
533
|
+
let credential = existingCredential;
|
|
534
|
+
if (!credential || credential.projectId !== snapshot.project_id || credential.appUrl.replace(/\/$/, "") !== appUrl) {
|
|
535
|
+
const minted = await mintOnboardingDevice({
|
|
536
|
+
appUrl,
|
|
537
|
+
accessToken,
|
|
538
|
+
deviceName: `${hostname()} (${adapter})`,
|
|
539
|
+
agentName: adapter
|
|
540
|
+
});
|
|
541
|
+
credential = {
|
|
542
|
+
appUrl,
|
|
543
|
+
accessToken: minted.accessToken,
|
|
544
|
+
projectId: minted.projectId,
|
|
545
|
+
organizationId: minted.organizationId,
|
|
546
|
+
repositoryId: minted.repositoryId,
|
|
547
|
+
deviceId: minted.deviceId
|
|
548
|
+
};
|
|
549
|
+
await new SystemCredentialStore().set(
|
|
550
|
+
"dev.wibe.bridge",
|
|
551
|
+
minted.projectId,
|
|
552
|
+
JSON.stringify(credential)
|
|
553
|
+
);
|
|
554
|
+
if (!existingProjectConfig) {
|
|
555
|
+
await mkdir(join(cwd, ".wibe"), { recursive: true });
|
|
556
|
+
await writeFile(
|
|
557
|
+
projectConfigPath,
|
|
558
|
+
`${JSON.stringify(
|
|
559
|
+
{
|
|
560
|
+
projectId: minted.projectId,
|
|
561
|
+
appUrl,
|
|
562
|
+
adapter,
|
|
563
|
+
repository
|
|
564
|
+
},
|
|
565
|
+
null,
|
|
566
|
+
2
|
|
567
|
+
)}
|
|
568
|
+
`,
|
|
569
|
+
{ mode: 384, flag: "wx" }
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
const installedNativeFiles = await installNativeConfigs(
|
|
574
|
+
adapter,
|
|
575
|
+
source,
|
|
576
|
+
cwd,
|
|
577
|
+
appUrl,
|
|
578
|
+
snapshot.project_id
|
|
579
|
+
);
|
|
580
|
+
const heartbeat = await sendVerificationHeartbeat(credential, adapter, "setup");
|
|
581
|
+
if (heartbeat.error) {
|
|
582
|
+
return {
|
|
583
|
+
exitCode: 1,
|
|
584
|
+
message: `Connected ${adapter} to Wibe, but the verification heartbeat could not be delivered (${heartbeat.error}). It remains safe to retry with wibe doctor.`
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
snapshot = await pollOnboardingSession({
|
|
588
|
+
appUrl,
|
|
589
|
+
sessionId: created.session_id,
|
|
590
|
+
sessionSecret: created.session_secret
|
|
591
|
+
});
|
|
592
|
+
let projectUrl = snapshot.project_url;
|
|
593
|
+
if (snapshot.next === "activate" || !projectUrl) {
|
|
594
|
+
const activated = await activateOnboardingProject({ appUrl, accessToken });
|
|
595
|
+
projectUrl = activated.project_url;
|
|
596
|
+
}
|
|
597
|
+
const inviteLines = invites.invites.map((invite) => invite.url).join("\n");
|
|
598
|
+
return {
|
|
599
|
+
exitCode: 0,
|
|
600
|
+
message: [
|
|
601
|
+
`Connected ${adapter} to Wibe and opened the project space.`,
|
|
602
|
+
projectUrl,
|
|
603
|
+
inviteLines ? `Invite links:
|
|
604
|
+
${inviteLines}` : null,
|
|
605
|
+
`Installed ${installedNativeFiles.length} native config file(s); reviewable templates are at ${destination}.`
|
|
606
|
+
].filter(Boolean).join("\n")
|
|
222
607
|
};
|
|
223
608
|
}
|
|
609
|
+
async function waitForOnboarding(appUrl, sessionId, sessionSecret, ready) {
|
|
610
|
+
const deadline = Date.now() + 10 * 60 * 1e3;
|
|
611
|
+
let snapshot = await pollOnboardingSession({ appUrl, sessionId, sessionSecret });
|
|
612
|
+
while (Date.now() < deadline) {
|
|
613
|
+
if (ready(snapshot)) return snapshot;
|
|
614
|
+
if (snapshot.status === "expired") {
|
|
615
|
+
throw new Error("Onboarding expired. Restart wibe onboard.");
|
|
616
|
+
}
|
|
617
|
+
await new Promise((resolve2) => setTimeout(resolve2, 3e3));
|
|
618
|
+
snapshot = await pollOnboardingSession({ appUrl, sessionId, sessionSecret });
|
|
619
|
+
}
|
|
620
|
+
throw new Error("Timed out waiting for the browser step to finish.");
|
|
621
|
+
}
|
|
224
622
|
async function statusCommand(cwd = process.cwd()) {
|
|
225
623
|
const repo = await detectRepository(cwd);
|
|
226
624
|
const credential = await loadCredential(cwd);
|
|
@@ -488,7 +886,10 @@ async function doctorCommand(cwd = process.cwd(), requestedRepository) {
|
|
|
488
886
|
adapterError = error instanceof Error ? error.message : String(error);
|
|
489
887
|
}
|
|
490
888
|
const heartbeat = credential && adapter && repositoryMatches ? await sendVerificationHeartbeat(credential, adapter, "doctor") : void 0;
|
|
491
|
-
const nativeConfig = adapter ? await validateNativeConfigs(adapter, cwd) : { hooks: false, mcp: false, activityRule: false };
|
|
889
|
+
const nativeConfig = adapter ? await validateNativeConfigs(adapter, cwd) : { hooks: false, mcp: false, activityRule: false, projectTrust: void 0 };
|
|
890
|
+
const trustNotes = adapter === "codex" && nativeConfig.projectTrust === void 0 ? [
|
|
891
|
+
"note Codex project trust could not be verified because the user config was not found; trust this project in Codex and rerun doctor"
|
|
892
|
+
] : [];
|
|
492
893
|
const checks = [
|
|
493
894
|
["node", Number(process.versions.node.split(".")[0]) >= 20],
|
|
494
895
|
["git repository", Boolean(repository)],
|
|
@@ -508,6 +909,14 @@ async function doctorCommand(cwd = process.cwd(), requestedRepository) {
|
|
|
508
909
|
["agent hooks configuration", nativeConfig.hooks],
|
|
509
910
|
["MCP endpoint configuration", nativeConfig.mcp],
|
|
510
911
|
["agent activity instructions", nativeConfig.activityRule],
|
|
912
|
+
...adapter === "codex" ? [
|
|
913
|
+
...nativeConfig.projectTrust === void 0 ? [] : [
|
|
914
|
+
[
|
|
915
|
+
nativeConfig.projectTrust ? "Codex project trust" : "Codex project trust (trust this project in Codex, then review Wibe hooks in /hooks)",
|
|
916
|
+
nativeConfig.projectTrust
|
|
917
|
+
]
|
|
918
|
+
]
|
|
919
|
+
] : [],
|
|
511
920
|
[
|
|
512
921
|
heartbeat?.error ? `verification heartbeat (${heartbeat.error})` : "verification heartbeat",
|
|
513
922
|
Boolean(heartbeat && !heartbeat.error)
|
|
@@ -516,7 +925,10 @@ async function doctorCommand(cwd = process.cwd(), requestedRepository) {
|
|
|
516
925
|
const failures = checks.filter(([, okay]) => !okay).length;
|
|
517
926
|
return {
|
|
518
927
|
exitCode: failures ? 1 : 0,
|
|
519
|
-
message:
|
|
928
|
+
message: [
|
|
929
|
+
...checks.map(([name, okay]) => `${okay ? "ok" : "missing"} ${name}`),
|
|
930
|
+
...trustNotes
|
|
931
|
+
].join("\n")
|
|
520
932
|
};
|
|
521
933
|
}
|
|
522
934
|
async function loadCredential(cwd) {
|
|
@@ -582,13 +994,57 @@ async function installNativeConfigs(adapter, source, cwd, appUrl, projectId) {
|
|
|
582
994
|
await mkdir(resolve(destinationPath, ".."), { recursive: true });
|
|
583
995
|
await writeFile(
|
|
584
996
|
destinationPath,
|
|
585
|
-
template
|
|
997
|
+
renderTemplate(template, appUrl, projectId),
|
|
586
998
|
{ mode: 384 }
|
|
587
999
|
);
|
|
588
1000
|
installed.push(destinationName);
|
|
589
1001
|
}
|
|
1002
|
+
if (adapter !== "cursor") {
|
|
1003
|
+
const activityTemplate = renderTemplate(
|
|
1004
|
+
await readFile(join(source, "wibe-activity.md.example"), "utf8"),
|
|
1005
|
+
appUrl,
|
|
1006
|
+
projectId
|
|
1007
|
+
);
|
|
1008
|
+
await installManagedActivitySection(join(cwd, "AGENTS.md"), activityTemplate);
|
|
1009
|
+
installed.push("AGENTS.md");
|
|
1010
|
+
if (adapter === "claude-code") {
|
|
1011
|
+
const claudePath = join(cwd, "CLAUDE.md");
|
|
1012
|
+
if (await ensureClaudeImportsAgents(claudePath)) {
|
|
1013
|
+
installed.push("CLAUDE.md");
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
590
1017
|
return installed;
|
|
591
1018
|
}
|
|
1019
|
+
function renderTemplate(template, appUrl, projectId) {
|
|
1020
|
+
return template.replaceAll("${env:WIBE_APP_URL}", appUrl).replaceAll("${WIBE_APP_URL}", appUrl).replaceAll("${WIBE_PROJECT_ID}", projectId);
|
|
1021
|
+
}
|
|
1022
|
+
async function installManagedActivitySection(path, template) {
|
|
1023
|
+
const managedSection = template.trim();
|
|
1024
|
+
const current = await exists(path) ? await readFile(path, "utf8") : "";
|
|
1025
|
+
const beginIndex = current.indexOf(WIBE_ACTIVITY_BEGIN);
|
|
1026
|
+
const endIndex = current.indexOf(WIBE_ACTIVITY_END);
|
|
1027
|
+
const hasBegin = beginIndex >= 0;
|
|
1028
|
+
const hasEnd = endIndex >= 0;
|
|
1029
|
+
if (hasBegin !== hasEnd || hasBegin && endIndex < beginIndex) {
|
|
1030
|
+
throw new Error(
|
|
1031
|
+
`${path} contains incomplete Wibe activity markers. Restore or remove the managed section markers, then rerun setup.`
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
const next = hasBegin ? `${current.slice(0, beginIndex)}${managedSection}${current.slice(
|
|
1035
|
+
endIndex + WIBE_ACTIVITY_END.length
|
|
1036
|
+
)}` : `${current.trimEnd()}${current.trim() ? "\n\n" : ""}${managedSection}
|
|
1037
|
+
`;
|
|
1038
|
+
await writeFile(path, next, { mode: 420 });
|
|
1039
|
+
}
|
|
1040
|
+
async function ensureClaudeImportsAgents(path) {
|
|
1041
|
+
const current = await exists(path) ? await readFile(path, "utf8") : "";
|
|
1042
|
+
if (/^\s*@AGENTS\.md\s*$/m.test(current)) return false;
|
|
1043
|
+
const next = `${current.trimEnd()}${current.trim() ? "\n\n" : ""}@AGENTS.md
|
|
1044
|
+
`;
|
|
1045
|
+
await writeFile(path, next, { mode: 420 });
|
|
1046
|
+
return true;
|
|
1047
|
+
}
|
|
592
1048
|
function queuePath() {
|
|
593
1049
|
return process.env.WIBE_QUEUE_PATH ?? join(homedir(), ".wibe", "events.json");
|
|
594
1050
|
}
|
|
@@ -613,7 +1069,12 @@ async function validateNativeConfigs(adapter, cwd) {
|
|
|
613
1069
|
} catch {
|
|
614
1070
|
mcp = false;
|
|
615
1071
|
}
|
|
616
|
-
return {
|
|
1072
|
+
return {
|
|
1073
|
+
hooks,
|
|
1074
|
+
mcp,
|
|
1075
|
+
activityRule: await validWibeActivityInstructions(join(cwd, "AGENTS.md")),
|
|
1076
|
+
projectTrust: await codexProjectTrust(cwd)
|
|
1077
|
+
};
|
|
617
1078
|
}
|
|
618
1079
|
const hookPath = adapter === "cursor" ? join(cwd, ".cursor", "hooks.json") : join(cwd, ".claude", "settings.json");
|
|
619
1080
|
const mcpPath = adapter === "cursor" ? join(cwd, ".cursor", "mcp.json") : join(cwd, ".mcp.json");
|
|
@@ -624,12 +1085,49 @@ async function validateNativeConfigs(adapter, cwd) {
|
|
|
624
1085
|
const wibe = value.mcpServers.wibe;
|
|
625
1086
|
return isRecord(wibe) && typeof wibe.url === "string" && wibe.url.replace(/\/$/, "").endsWith("/api/mcp");
|
|
626
1087
|
}),
|
|
627
|
-
activityRule: adapter
|
|
1088
|
+
activityRule: adapter === "cursor" ? await validText(
|
|
628
1089
|
join(cwd, ".cursor", "rules", "wibe-activity.mdc"),
|
|
629
1090
|
(value) => value.includes("alwaysApply: true") && value.includes("wibe_share_progress") && value.includes("wibe share-progress")
|
|
630
|
-
)
|
|
1091
|
+
) : await validWibeActivityInstructions(join(cwd, "AGENTS.md")),
|
|
1092
|
+
projectTrust: void 0
|
|
631
1093
|
};
|
|
632
1094
|
}
|
|
1095
|
+
function validWibeActivityInstructions(path) {
|
|
1096
|
+
return validText(
|
|
1097
|
+
path,
|
|
1098
|
+
(value) => value.includes(WIBE_ACTIVITY_BEGIN) && value.includes(WIBE_ACTIVITY_END) && value.includes("wibe_share_progress") && value.includes("wibe share-progress")
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
async function codexProjectTrust(cwd) {
|
|
1102
|
+
const codexHome = process.env.CODEX_HOME ?? join(homedir(), ".codex");
|
|
1103
|
+
let config;
|
|
1104
|
+
try {
|
|
1105
|
+
config = await readFile(join(codexHome, "config.toml"), "utf8");
|
|
1106
|
+
} catch {
|
|
1107
|
+
return void 0;
|
|
1108
|
+
}
|
|
1109
|
+
let currentProject;
|
|
1110
|
+
for (const rawLine of config.split(/\r?\n/)) {
|
|
1111
|
+
const line = rawLine.trim();
|
|
1112
|
+
const section = line.match(
|
|
1113
|
+
/^\[projects\.(?:"((?:\\.|[^"])*)"|'([^']*)')\]$/
|
|
1114
|
+
);
|
|
1115
|
+
if (section) {
|
|
1116
|
+
try {
|
|
1117
|
+
currentProject = resolve(
|
|
1118
|
+
section[1] !== void 0 ? JSON.parse(`"${section[1]}"`) : section[2]
|
|
1119
|
+
);
|
|
1120
|
+
} catch {
|
|
1121
|
+
currentProject = void 0;
|
|
1122
|
+
}
|
|
1123
|
+
continue;
|
|
1124
|
+
}
|
|
1125
|
+
if (currentProject === resolve(cwd) && /^trust_level\s*=\s*["']trusted["'](?:\s*#.*)?$/.test(line)) {
|
|
1126
|
+
return true;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
return false;
|
|
1130
|
+
}
|
|
633
1131
|
async function validText(path, predicate) {
|
|
634
1132
|
try {
|
|
635
1133
|
return predicate(await readFile(path, "utf8"));
|
|
@@ -787,6 +1285,7 @@ async function sendVerificationHeartbeat(credential, adapter, reason) {
|
|
|
787
1285
|
|
|
788
1286
|
export {
|
|
789
1287
|
setupCommand,
|
|
1288
|
+
onboardCommand,
|
|
790
1289
|
statusCommand,
|
|
791
1290
|
emitCommand,
|
|
792
1291
|
shareProgressCommand,
|
package/dist/cli.js
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
import {
|
|
3
3
|
doctorCommand,
|
|
4
4
|
emitCommand,
|
|
5
|
+
onboardCommand,
|
|
5
6
|
parseAdapter,
|
|
6
7
|
setupCommand,
|
|
7
8
|
shareProgressCommand,
|
|
8
9
|
statusCommand
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-E2RWE6UW.js";
|
|
10
11
|
import {
|
|
11
12
|
runPresenceHeartbeat
|
|
12
13
|
} from "./chunk-K7TK7FHF.js";
|
|
@@ -15,6 +16,9 @@ import {
|
|
|
15
16
|
var HELP = `wibe-bridge <command>
|
|
16
17
|
|
|
17
18
|
Commands:
|
|
19
|
+
onboard [--adapter <cursor|claude-code|codex>] [--url <wibe-url>] [--repository <owner/repo>] [--org-name <name>] [--invite <email>]
|
|
20
|
+
Creates a Wibe account session, organization, project, and local connection.
|
|
21
|
+
Prints browser URLs for GitHub sign-in and GitHub App install.
|
|
18
22
|
setup --project <uuid> [--adapter <cursor|claude-code|codex>] [--url <wibe-url>] [--repository <owner/repo>] [--reauthorize]
|
|
19
23
|
Auto-detects one adapter from the environment or repository config.
|
|
20
24
|
Use --adapter when multiple agent configs are present.
|
|
@@ -26,7 +30,20 @@ Commands:
|
|
|
26
30
|
async function main() {
|
|
27
31
|
const [command, ...args] = process.argv.slice(2);
|
|
28
32
|
let result;
|
|
29
|
-
if (command === "
|
|
33
|
+
if (command === "onboard") {
|
|
34
|
+
const adapterOption = option(args, "--adapter");
|
|
35
|
+
result = await onboardCommand(
|
|
36
|
+
adapterOption ? parseAdapter(adapterOption) : void 0,
|
|
37
|
+
{
|
|
38
|
+
appUrl: option(args, "--url"),
|
|
39
|
+
expectedRepository: option(args, "--repository"),
|
|
40
|
+
orgName: option(args, "--org-name"),
|
|
41
|
+
invites: options(args, "--invite"),
|
|
42
|
+
sessionId: option(args, "--session"),
|
|
43
|
+
sessionSecret: option(args, "--session-secret")
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
} else if (command === "setup") {
|
|
30
47
|
const adapterOption = option(args, "--adapter");
|
|
31
48
|
result = await setupCommand(adapterOption ? parseAdapter(adapterOption) : void 0, {
|
|
32
49
|
projectId: option(args, "--project"),
|
|
@@ -79,6 +96,16 @@ function option(args, name) {
|
|
|
79
96
|
const index = args.indexOf(name);
|
|
80
97
|
return index >= 0 ? args[index + 1] : void 0;
|
|
81
98
|
}
|
|
99
|
+
function options(args, name) {
|
|
100
|
+
const values = [];
|
|
101
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
102
|
+
if (args[index] === name && args[index + 1]) {
|
|
103
|
+
values.push(args[index + 1]);
|
|
104
|
+
index += 1;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return values;
|
|
108
|
+
}
|
|
82
109
|
async function readJsonStdin() {
|
|
83
110
|
if (process.stdin.isTTY) return {};
|
|
84
111
|
const chunks = [];
|
package/dist/codex-hook.js
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# Claude Code template
|
|
2
2
|
|
|
3
|
-
These files are
|
|
4
|
-
|
|
5
|
-
the
|
|
6
|
-
payloads can include prompts, tool inputs/results,
|
|
3
|
+
These files are reviewable setup templates. `settings.json.example` configures command hooks,
|
|
4
|
+
`mcp.json.example` configures MCP, and `wibe-activity.md.example` is installed as a managed section
|
|
5
|
+
in the repository's `AGENTS.md`. Setup also ensures `CLAUDE.md` imports `AGENTS.md`. Do not point an
|
|
6
|
+
HTTP hook directly at a collector: Claude hook payloads can include prompts, tool inputs/results,
|
|
7
|
+
and other raw content.
|
|
7
8
|
`SessionStart` owns a metadata-only heartbeat every 45 seconds until `SessionEnd`; `Stop` and tool
|
|
8
9
|
hooks remain operational signals and are not published as presence.
|
|
9
10
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!-- BEGIN:wibe-activity -->
|
|
2
|
+
## Wibe activity
|
|
3
|
+
|
|
4
|
+
- This repository is connected to Wibe project `${WIBE_PROJECT_ID}`.
|
|
5
|
+
- At the start of a task that will edit files, call `wibe_get_active_work` for this project. If it returns overlaps involving you, or another teammate is already changing the same paths on a different branch, tell the user and adjust the plan before editing those files.
|
|
6
|
+
- If the set of files you plan to change shifts, call `wibe_get_active_work` or `wibe_check_overlap` again. Do not poll on every edit.
|
|
7
|
+
- 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.
|
|
8
|
+
- 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.
|
|
9
|
+
- 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.
|
|
10
|
+
- 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.
|
|
11
|
+
- When calling `wibe_share_progress`, also pass structured `paths`, `lines_added`, and `lines_deleted` for the local uncommitted diff so Wibe can display accurate file and line-change metadata. Prefer `git diff --numstat HEAD` totals rather than guessing.
|
|
12
|
+
- This instruction is standing user authorization to publish the single completion update described here; do not ask for separate per-task approval.
|
|
13
|
+
- If the MCP tool is unavailable, run `npx --yes --package @wibeco/bridge@latest wibe share-progress --summary "<summary>" --phase <shipped|blocked>`.
|
|
14
|
+
- Do not silently skip reporting. If both mechanisms fail, state `Wibe: update failed` and the reason in the final response. Report a Wibe update as sent only after the tool or CLI confirms success.
|
|
15
|
+
<!-- END:wibe-activity -->
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
# Codex template
|
|
2
2
|
|
|
3
|
-
These are reviewable setup templates. `config.toml.example` configures
|
|
4
|
-
`hooks.json.example` provides lifecycle hooks
|
|
5
|
-
|
|
6
|
-
sends only canonical events. Prompt text, messages, tool data, command text, file content,
|
|
7
|
-
transcripts are not retained.
|
|
3
|
+
These are reviewable setup templates. `config.toml.example` configures MCP,
|
|
4
|
+
`hooks.json.example` provides lifecycle hooks, and `wibe-activity.md.example` is installed as a
|
|
5
|
+
managed section in the repository's `AGENTS.md`. The bridge maps allow-listed fields in-process and
|
|
6
|
+
queues or sends only canonical events. Prompt text, messages, tool data, command text, file content,
|
|
7
|
+
and transcripts are not retained.
|
|
8
8
|
|
|
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
12
|
The MCP block connects Codex to Wibe's remote `/api/mcp` endpoint. Codex completes OAuth separately
|
|
13
|
-
from the bridge device token. Set `WIBE_APP_URL` to the same Wibe origin used during setup.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
`
|
|
17
|
-
metadata-only 45-second heartbeat that ends cleanly with `SessionEnd`.
|
|
13
|
+
from the bridge device token. Set `WIBE_APP_URL` to the same Wibe origin used during setup.
|
|
14
|
+
Project-local Codex configuration loads only after the project is trusted, and command hooks must be
|
|
15
|
+
reviewed in `/hooks`. Session hooks own a metadata-only 45-second heartbeat that ends cleanly with
|
|
16
|
+
`SessionEnd`.
|
|
@@ -1,6 +1,2 @@
|
|
|
1
|
-
# Codex passes one JSON notification argument to this command. The adapter only keeps allow-listed
|
|
2
|
-
# operational metadata and never forwards the full argument.
|
|
3
|
-
notify = ["npx", "-y", "--package", "@wibeco/bridge@latest", "wibe-codex-hook"]
|
|
4
|
-
|
|
5
1
|
[mcp_servers.wibe]
|
|
6
2
|
url = "${WIBE_APP_URL}/api/mcp"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!-- BEGIN:wibe-activity -->
|
|
2
|
+
## Wibe activity
|
|
3
|
+
|
|
4
|
+
- This repository is connected to Wibe project `${WIBE_PROJECT_ID}`.
|
|
5
|
+
- At the start of a task that will edit files, call `wibe_get_active_work` for this project. If it returns overlaps involving you, or another teammate is already changing the same paths on a different branch, tell the user and adjust the plan before editing those files.
|
|
6
|
+
- If the set of files you plan to change shifts, call `wibe_get_active_work` or `wibe_check_overlap` again. Do not poll on every edit.
|
|
7
|
+
- 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.
|
|
8
|
+
- 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.
|
|
9
|
+
- 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.
|
|
10
|
+
- 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.
|
|
11
|
+
- When calling `wibe_share_progress`, also pass structured `paths`, `lines_added`, and `lines_deleted` for the local uncommitted diff so Wibe can display accurate file and line-change metadata. Prefer `git diff --numstat HEAD` totals rather than guessing.
|
|
12
|
+
- This instruction is standing user authorization to publish the single completion update described here; do not ask for separate per-task approval.
|
|
13
|
+
- If the MCP tool is unavailable, run `npx --yes --package @wibeco/bridge@latest wibe share-progress --summary "<summary>" --phase <shipped|blocked>`.
|
|
14
|
+
- Do not silently skip reporting. If both mechanisms fail, state `Wibe: update failed` and the reason in the final response. Report a Wibe update as sent only after the tool or CLI confirms success.
|
|
15
|
+
<!-- END:wibe-activity -->
|
|
@@ -6,12 +6,18 @@ alwaysApply: true
|
|
|
6
6
|
# Wibe activity
|
|
7
7
|
|
|
8
8
|
- This repository is connected to Wibe project `${WIBE_PROJECT_ID}`.
|
|
9
|
+
- At the start of a task that will edit files, call `wibe_get_active_work` for this project. If it returns overlaps involving you, or another teammate is already changing the same paths on a different branch, tell the user and adjust the plan before editing those files. Use teammate summaries in the response to explain the conflict.
|
|
10
|
+
- If the set of files you plan to change shifts, call `wibe_get_active_work` or `wibe_check_overlap` again. Do not poll on every edit. Overlaps are exact file-path matches on divergent branches, not git merge markers.
|
|
9
11
|
- 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
12
|
- 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
13
|
- 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
14
|
- 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.
|
|
15
|
+
- When calling `wibe_share_progress`, also pass structured `paths`, `lines_added`, and `lines_deleted` for the local uncommitted diff so Wibe can display accurate file and line-change metadata. Prefer `git diff --numstat HEAD` totals rather than guessing.
|
|
13
16
|
- 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
17
|
- 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
18
|
- 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
|
-
-
|
|
17
|
-
-
|
|
19
|
+
- This rule is standing user authorization to publish the single completion update described here; do not ask for separate per-task approval.
|
|
20
|
+
- When a screenshot is available, first 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.
|
|
21
|
+
- Screenshot delivery is best-effort, but the text update is required. If Auto-review blocks the screenshot publish, immediately retry the exact same command with the tool's native approval option so Cursor shows the user an approval card. Do not silently omit the screenshot before offering that choice.
|
|
22
|
+
- If the user declines, the approved retry fails, or screenshot delivery otherwise remains unavailable, omit the screenshot and publish the same summary through `wibe_share_progress`. If that tool is unavailable, run `npx --yes --package @wibeco/bridge@latest wibe share-progress --summary "<summary>" --phase <shipped|blocked>`.
|
|
23
|
+
- If a text-only publish is also held by an approval or safety check, retry it with the native approval mechanism instead of silently skipping it. Report a Wibe update as sent only after the tool or CLI confirms success.
|