vibegroup 0.1.0 → 0.1.1
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/.claude-plugin/marketplace.json +18 -0
- package/dist/cli.js +341 -124
- package/package.json +33 -8
- package/plugin/.claude-plugin/plugin.json +13 -0
- package/plugin/.mcp.json +8 -0
- package/plugin/commands/init.md +57 -0
- package/plugin/commands/vibegroup.md +24 -0
- package/plugin/dist/channel.js +16960 -0
- package/plugin/hooks/hooks.json +7 -0
- package/plugin/hooks/session-start.sh +5 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vibegroup",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "Terry Cruz Melo",
|
|
5
|
+
"email": "terry@cruz.pe"
|
|
6
|
+
},
|
|
7
|
+
"description": "vibegroup: let your Claude Code agents talk to each other across repos, machines, and networks.",
|
|
8
|
+
"plugins": [
|
|
9
|
+
{
|
|
10
|
+
"name": "vibegroup",
|
|
11
|
+
"source": "./plugin",
|
|
12
|
+
"description": "Ask peer Claude Code agents what they're working on, over an end-to-end-encrypted relay. Answers come from their live session via Claude Code Channels.",
|
|
13
|
+
"homepage": "https://vibegroup.sh",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"keywords": ["claude-code", "channel", "mcp", "agents", "collaboration", "relay"]
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -81,6 +81,12 @@ function mergeManagedSettings(existing) {
|
|
|
81
81
|
next.allowedChannelPlugins = has ? current : [...current, VIBEGROUP_ENTRY];
|
|
82
82
|
return next;
|
|
83
83
|
}
|
|
84
|
+
function isAllowlisted(existing) {
|
|
85
|
+
if (!existing || existing.channelsEnabled !== true)
|
|
86
|
+
return false;
|
|
87
|
+
const current = Array.isArray(existing.allowedChannelPlugins) ? existing.allowedChannelPlugins : [];
|
|
88
|
+
return current.some((e) => e?.marketplace === "vibegroup" && e?.plugin === "vibegroup");
|
|
89
|
+
}
|
|
84
90
|
var VIBEGROUP_ENTRY;
|
|
85
91
|
var init_allowlist = __esm(() => {
|
|
86
92
|
VIBEGROUP_ENTRY = { marketplace: "vibegroup", plugin: "vibegroup" };
|
|
@@ -111,6 +117,25 @@ var init_allowlist2 = __esm(() => {
|
|
|
111
117
|
init_allowlist();
|
|
112
118
|
});
|
|
113
119
|
|
|
120
|
+
// src/ui/runner.ts
|
|
121
|
+
import { render } from "ink";
|
|
122
|
+
function runInk(make) {
|
|
123
|
+
return new Promise((resolve) => {
|
|
124
|
+
let done = false;
|
|
125
|
+
let instance;
|
|
126
|
+
const finish = (code) => {
|
|
127
|
+
if (done)
|
|
128
|
+
return;
|
|
129
|
+
done = true;
|
|
130
|
+
instance?.unmount();
|
|
131
|
+
resolve(code);
|
|
132
|
+
};
|
|
133
|
+
instance = render(make(finish));
|
|
134
|
+
instance.waitUntilExit().then(() => finish(130));
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
var init_runner = () => {};
|
|
138
|
+
|
|
114
139
|
// src/lib/workosAuth.ts
|
|
115
140
|
async function json(res) {
|
|
116
141
|
const text = await res.text();
|
|
@@ -191,18 +216,6 @@ async function register(ep, body, f) {
|
|
|
191
216
|
claim: parseCeremony(data.claim)
|
|
192
217
|
};
|
|
193
218
|
}
|
|
194
|
-
async function startClaim(ep, claimToken, email, f) {
|
|
195
|
-
const res = await f(ep.claimEndpoint, {
|
|
196
|
-
method: "POST",
|
|
197
|
-
headers: { "content-type": "application/json" },
|
|
198
|
-
body: JSON.stringify({ claim_token: claimToken, email })
|
|
199
|
-
});
|
|
200
|
-
const data = await json(res);
|
|
201
|
-
const ceremony = parseCeremony(data.claim_attempt) ?? parseCeremony(data.claim);
|
|
202
|
-
if (!ceremony)
|
|
203
|
-
throw new Error(`claim start failed: ${data.error ?? `HTTP ${res.status}`}`);
|
|
204
|
-
return ceremony;
|
|
205
|
-
}
|
|
206
219
|
async function pollClaim(tokenEndpoint, claimToken, f, nowMs = Date.now()) {
|
|
207
220
|
const res = await f(tokenEndpoint, {
|
|
208
221
|
method: "POST",
|
|
@@ -223,24 +236,7 @@ async function pollClaim(tokenEndpoint, claimToken, f, nowMs = Date.now()) {
|
|
|
223
236
|
throw new Error(`claim poll failed: ${data.error ?? `HTTP ${res.status}`}`);
|
|
224
237
|
}
|
|
225
238
|
}
|
|
226
|
-
|
|
227
|
-
const params = new URLSearchParams({ grant_type: JWT_BEARER_GRANT, assertion });
|
|
228
|
-
if (resource)
|
|
229
|
-
params.set("resource", resource);
|
|
230
|
-
const res = await f(tokenEndpoint, {
|
|
231
|
-
method: "POST",
|
|
232
|
-
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
233
|
-
body: params.toString()
|
|
234
|
-
});
|
|
235
|
-
const data = await json(res);
|
|
236
|
-
if (!res.ok || !data.access_token)
|
|
237
|
-
throw new Error(`assertion exchange failed: ${data.error ?? `HTTP ${res.status}`}`);
|
|
238
|
-
const tokens = tokensFromResponse(data, nowMs);
|
|
239
|
-
if (!tokens.identityAssertion)
|
|
240
|
-
tokens.identityAssertion = assertion;
|
|
241
|
-
return tokens;
|
|
242
|
-
}
|
|
243
|
-
var CLAIM_GRANT = "urn:workos:agent-auth:grant-type:claim", JWT_BEARER_GRANT = "urn:ietf:params:oauth:grant-type:jwt-bearer";
|
|
239
|
+
var CLAIM_GRANT = "urn:workos:agent-auth:grant-type:claim";
|
|
244
240
|
|
|
245
241
|
// src/lib/loginFlow.ts
|
|
246
242
|
async function login(apiBase, f, hooks) {
|
|
@@ -250,116 +246,332 @@ async function login(apiBase, f, hooks) {
|
|
|
250
246
|
emit({ type: "discovering", apiBase });
|
|
251
247
|
const ep = await discover(apiBase, f);
|
|
252
248
|
emit({ type: "discovered", endpoints: ep });
|
|
253
|
-
|
|
249
|
+
const email = (await hooks.email())?.trim();
|
|
250
|
+
if (!email)
|
|
254
251
|
return null;
|
|
255
|
-
emit({ type: "registering" });
|
|
256
|
-
const reg = await register(ep, { type: "
|
|
257
|
-
if (!reg.
|
|
258
|
-
throw new Error("
|
|
259
|
-
|
|
260
|
-
emit({ type: "
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
if (
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
let interval = Math.max(1, ceremony.interval);
|
|
269
|
-
for (;; ) {
|
|
270
|
-
const r = await pollClaim(ep.tokenEndpoint, reg.claimToken, f, now());
|
|
271
|
-
if (r.status === "done") {
|
|
272
|
-
tokens = r.tokens;
|
|
273
|
-
emit({ type: "unlocked", scopes: reg.postClaimScopes });
|
|
274
|
-
return { tokens, claimed: true };
|
|
275
|
-
}
|
|
276
|
-
if (r.status === "expired")
|
|
277
|
-
break;
|
|
278
|
-
if (r.status === "slow_down")
|
|
279
|
-
interval += 5;
|
|
280
|
-
await sleep(interval * 1000);
|
|
252
|
+
emit({ type: "registering", email });
|
|
253
|
+
const reg = await register(ep, { type: "service_auth", login_hint: email }, f);
|
|
254
|
+
if (!reg.claim || !reg.claimToken)
|
|
255
|
+
throw new Error("service_auth did not return a claim ceremony");
|
|
256
|
+
emit({ type: "claim", ceremony: reg.claim });
|
|
257
|
+
emit({ type: "polling" });
|
|
258
|
+
let interval = Math.max(1, reg.claim.interval);
|
|
259
|
+
const deadline = now() + (hooks.timeoutMs ?? 10 * 60 * 1000);
|
|
260
|
+
for (;; ) {
|
|
261
|
+
const r = await pollClaim(ep.tokenEndpoint, reg.claimToken, f, now());
|
|
262
|
+
if (r.status === "done") {
|
|
263
|
+
emit({ type: "done" });
|
|
264
|
+
return { tokens: r.tokens, email };
|
|
281
265
|
}
|
|
266
|
+
if (r.status === "expired")
|
|
267
|
+
throw new Error("the code expired — run `vibegroup login` again");
|
|
268
|
+
if (r.status === "slow_down")
|
|
269
|
+
interval += 5;
|
|
270
|
+
if (now() > deadline)
|
|
271
|
+
throw new Error("login timed out");
|
|
272
|
+
await sleep(interval * 1000);
|
|
282
273
|
}
|
|
283
|
-
return { tokens, claimed: false };
|
|
284
274
|
}
|
|
285
275
|
var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
286
276
|
var init_loginFlow = () => {};
|
|
287
277
|
|
|
278
|
+
// src/ui/session.ts
|
|
279
|
+
function jwtClaim(jwt, key) {
|
|
280
|
+
try {
|
|
281
|
+
return JSON.parse(Buffer.from(jwt.split(".")[1], "base64url").toString("utf8"))[key];
|
|
282
|
+
} catch {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function sessionFromTokens(tokens, email) {
|
|
287
|
+
const sub = jwtClaim(tokens.accessToken, "sub");
|
|
288
|
+
const exp = jwtClaim(tokens.accessToken, "exp");
|
|
289
|
+
return {
|
|
290
|
+
...tokens,
|
|
291
|
+
user: { id: typeof sub === "string" ? sub : email, email },
|
|
292
|
+
accessTokenExpiresAt: typeof exp === "number" ? new Date(exp * 1000).toISOString() : tokens.accessTokenExpiresAt
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/lib/openBrowser.ts
|
|
297
|
+
import { spawn } from "node:child_process";
|
|
298
|
+
function openBrowser(url) {
|
|
299
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
300
|
+
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
301
|
+
try {
|
|
302
|
+
spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
|
|
303
|
+
} catch {}
|
|
304
|
+
}
|
|
305
|
+
var init_openBrowser = () => {};
|
|
306
|
+
|
|
307
|
+
// src/ui/theme.ts
|
|
308
|
+
var color;
|
|
309
|
+
var init_theme = __esm(() => {
|
|
310
|
+
color = {
|
|
311
|
+
brand: "magenta",
|
|
312
|
+
accent: "cyan",
|
|
313
|
+
ok: "green",
|
|
314
|
+
warn: "yellow",
|
|
315
|
+
err: "red",
|
|
316
|
+
dim: "gray"
|
|
317
|
+
};
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// src/ui/Login.tsx
|
|
321
|
+
import { useEffect, useRef, useState } from "react";
|
|
322
|
+
import { Box, Text } from "ink";
|
|
323
|
+
import { Spinner, StatusMessage, TextInput, Alert, Badge } from "@inkjs/ui";
|
|
324
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
325
|
+
function Login({
|
|
326
|
+
apiBase,
|
|
327
|
+
initialEmail,
|
|
328
|
+
onExit
|
|
329
|
+
}) {
|
|
330
|
+
const [phase, setPhase] = useState(initialEmail ? { t: "connecting" } : { t: "email" });
|
|
331
|
+
const opened = useRef(false);
|
|
332
|
+
const started = useRef(false);
|
|
333
|
+
const start = (email) => {
|
|
334
|
+
if (started.current)
|
|
335
|
+
return;
|
|
336
|
+
started.current = true;
|
|
337
|
+
setPhase({ t: "connecting" });
|
|
338
|
+
login(apiBase, fetch, {
|
|
339
|
+
email: async () => email,
|
|
340
|
+
onEvent: (e) => {
|
|
341
|
+
if (e.type === "registering")
|
|
342
|
+
setPhase({ t: "registering", email: e.email });
|
|
343
|
+
else if (e.type === "claim")
|
|
344
|
+
setPhase({ t: "waiting", code: e.ceremony.userCode, url: e.ceremony.verificationUri });
|
|
345
|
+
}
|
|
346
|
+
}).then((res) => {
|
|
347
|
+
if (!res) {
|
|
348
|
+
setPhase({ t: "error", message: "Login cancelled." });
|
|
349
|
+
setTimeout(() => onExit(1), 50);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
writeAuth(sessionFromTokens(res.tokens, res.email));
|
|
353
|
+
setPhase({ t: "done", email: res.email });
|
|
354
|
+
setTimeout(() => onExit(0), 500);
|
|
355
|
+
}).catch((err) => {
|
|
356
|
+
setPhase({ t: "error", message: err?.message ?? String(err) });
|
|
357
|
+
setTimeout(() => onExit(1), 800);
|
|
358
|
+
});
|
|
359
|
+
};
|
|
360
|
+
useEffect(() => {
|
|
361
|
+
if (initialEmail)
|
|
362
|
+
start(initialEmail);
|
|
363
|
+
}, []);
|
|
364
|
+
useEffect(() => {
|
|
365
|
+
if (phase.t === "waiting" && !opened.current) {
|
|
366
|
+
opened.current = true;
|
|
367
|
+
openBrowser(phase.url);
|
|
368
|
+
}
|
|
369
|
+
}, [phase]);
|
|
370
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
371
|
+
flexDirection: "column",
|
|
372
|
+
gap: 1,
|
|
373
|
+
paddingY: 1,
|
|
374
|
+
children: [
|
|
375
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
376
|
+
children: [
|
|
377
|
+
/* @__PURE__ */ jsx(Text, {
|
|
378
|
+
color: color.brand,
|
|
379
|
+
bold: true,
|
|
380
|
+
children: "vibegroup"
|
|
381
|
+
}),
|
|
382
|
+
/* @__PURE__ */ jsx(Text, {
|
|
383
|
+
dimColor: true,
|
|
384
|
+
children: " · sign in"
|
|
385
|
+
})
|
|
386
|
+
]
|
|
387
|
+
}),
|
|
388
|
+
phase.t === "email" && /* @__PURE__ */ jsxs(Box, {
|
|
389
|
+
flexDirection: "column",
|
|
390
|
+
children: [
|
|
391
|
+
/* @__PURE__ */ jsx(Text, {
|
|
392
|
+
children: "Email to sign in with:"
|
|
393
|
+
}),
|
|
394
|
+
/* @__PURE__ */ jsx(Box, {
|
|
395
|
+
marginTop: 1,
|
|
396
|
+
children: /* @__PURE__ */ jsx(TextInput, {
|
|
397
|
+
placeholder: "you@company.com",
|
|
398
|
+
onSubmit: (value) => {
|
|
399
|
+
const email = value.trim();
|
|
400
|
+
if (email)
|
|
401
|
+
start(email);
|
|
402
|
+
else
|
|
403
|
+
onExit(1);
|
|
404
|
+
}
|
|
405
|
+
})
|
|
406
|
+
})
|
|
407
|
+
]
|
|
408
|
+
}),
|
|
409
|
+
phase.t === "connecting" && /* @__PURE__ */ jsx(Spinner, {
|
|
410
|
+
label: "Connecting to vibegroup…"
|
|
411
|
+
}),
|
|
412
|
+
phase.t === "registering" && /* @__PURE__ */ jsx(Spinner, {
|
|
413
|
+
label: `Registering ${phase.email}…`
|
|
414
|
+
}),
|
|
415
|
+
phase.t === "waiting" && /* @__PURE__ */ jsxs(Box, {
|
|
416
|
+
flexDirection: "column",
|
|
417
|
+
gap: 1,
|
|
418
|
+
children: [
|
|
419
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
420
|
+
borderStyle: "round",
|
|
421
|
+
borderColor: color.accent,
|
|
422
|
+
paddingX: 1,
|
|
423
|
+
flexDirection: "column",
|
|
424
|
+
children: [
|
|
425
|
+
/* @__PURE__ */ jsx(Text, {
|
|
426
|
+
children: "Open this link, sign in, and enter the code:"
|
|
427
|
+
}),
|
|
428
|
+
/* @__PURE__ */ jsx(Box, {
|
|
429
|
+
marginY: 1,
|
|
430
|
+
children: /* @__PURE__ */ jsx(Badge, {
|
|
431
|
+
color: "cyan",
|
|
432
|
+
children: phase.code
|
|
433
|
+
})
|
|
434
|
+
}),
|
|
435
|
+
/* @__PURE__ */ jsx(Text, {
|
|
436
|
+
color: color.accent,
|
|
437
|
+
children: phase.url
|
|
438
|
+
}),
|
|
439
|
+
/* @__PURE__ */ jsx(Text, {
|
|
440
|
+
dimColor: true,
|
|
441
|
+
children: "(the code goes into that page — not back here)"
|
|
442
|
+
})
|
|
443
|
+
]
|
|
444
|
+
}),
|
|
445
|
+
/* @__PURE__ */ jsx(Spinner, {
|
|
446
|
+
label: "Waiting for you to confirm in the browser…"
|
|
447
|
+
})
|
|
448
|
+
]
|
|
449
|
+
}),
|
|
450
|
+
phase.t === "done" && /* @__PURE__ */ jsxs(StatusMessage, {
|
|
451
|
+
variant: "success",
|
|
452
|
+
children: [
|
|
453
|
+
"Signed in as ",
|
|
454
|
+
phase.email,
|
|
455
|
+
". You're good to go!"
|
|
456
|
+
]
|
|
457
|
+
}),
|
|
458
|
+
phase.t === "error" && /* @__PURE__ */ jsx(Alert, {
|
|
459
|
+
variant: "error",
|
|
460
|
+
children: phase.message
|
|
461
|
+
})
|
|
462
|
+
]
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
var init_Login = __esm(() => {
|
|
466
|
+
init_loginFlow();
|
|
467
|
+
init_auth();
|
|
468
|
+
init_openBrowser();
|
|
469
|
+
init_theme();
|
|
470
|
+
});
|
|
471
|
+
|
|
288
472
|
// src/commands/login.ts
|
|
289
473
|
var exports_login = {};
|
|
290
474
|
__export(exports_login, {
|
|
291
475
|
loginCommand: () => loginCommand
|
|
292
476
|
});
|
|
293
|
-
import {
|
|
294
|
-
async function
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
return (await rl.question(question)).trim();
|
|
298
|
-
} finally {
|
|
299
|
-
rl.close();
|
|
300
|
-
}
|
|
477
|
+
import { createElement } from "react";
|
|
478
|
+
async function loginCommand(rest, env = process.env) {
|
|
479
|
+
const base = apiBase(env);
|
|
480
|
+
return runInk((onExit) => createElement(Login, { apiBase: base, initialEmail: rest[0], onExit }));
|
|
301
481
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
break;
|
|
320
|
-
case "claim":
|
|
321
|
-
console.log(`
|
|
322
|
-
To unlock your account, sign in at vibegroup and enter this code: ${e.ceremony.userCode}`);
|
|
323
|
-
console.log(`${e.ceremony.verificationUri} — the code goes in there, not back to me.`);
|
|
324
|
-
break;
|
|
325
|
-
case "polling":
|
|
326
|
-
console.log(`
|
|
327
|
-
Waiting for you to confirm…`);
|
|
328
|
-
break;
|
|
329
|
-
case "unlocked":
|
|
330
|
-
console.log(" └ Unlocked · full access");
|
|
331
|
-
break;
|
|
332
|
-
}
|
|
482
|
+
var init_login = __esm(() => {
|
|
483
|
+
init_runner();
|
|
484
|
+
init_Login();
|
|
485
|
+
init_cli();
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// src/commands/init.ts
|
|
489
|
+
var exports_init = {};
|
|
490
|
+
__export(exports_init, {
|
|
491
|
+
initCommand: () => initCommand
|
|
492
|
+
});
|
|
493
|
+
import { spawnSync } from "node:child_process";
|
|
494
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
495
|
+
import { fileURLToPath } from "node:url";
|
|
496
|
+
import { dirname as dirname2, join as join2 } from "node:path";
|
|
497
|
+
function packageRoot() {
|
|
498
|
+
return join2(dirname2(fileURLToPath(import.meta.url)), "..");
|
|
333
499
|
}
|
|
334
|
-
|
|
335
|
-
|
|
500
|
+
function commandExists(cmd) {
|
|
501
|
+
return spawnSync(cmd, ["--version"], { stdio: "ignore" }).status === 0;
|
|
502
|
+
}
|
|
503
|
+
function pluginInstalled() {
|
|
504
|
+
const r = spawnSync("claude", ["plugin", "list"], { encoding: "utf8" });
|
|
505
|
+
return r.status === 0 && /vibegroup@vibegroup/.test(r.stdout ?? "");
|
|
506
|
+
}
|
|
507
|
+
function readManagedSettings() {
|
|
336
508
|
try {
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
509
|
+
const p = managedSettingsPath();
|
|
510
|
+
if (existsSync3(p))
|
|
511
|
+
return JSON.parse(readFileSync3(p, "utf8"));
|
|
512
|
+
} catch {}
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
function writeAllowlistWithSudo() {
|
|
516
|
+
const path = managedSettingsPath();
|
|
517
|
+
const json2 = JSON.stringify(mergeManagedSettings(readManagedSettings()), null, 2);
|
|
518
|
+
const script = `mkdir -p "${dirname2(path)}" && cat > "${path}"`;
|
|
519
|
+
const r = spawnSync("sudo", ["sh", "-c", script], { input: json2, stdio: ["pipe", "inherit", "inherit"] });
|
|
520
|
+
return r.status === 0;
|
|
521
|
+
}
|
|
522
|
+
async function initCommand(rest, flags = {}, env = process.env) {
|
|
523
|
+
const dev = flags.dev === true;
|
|
524
|
+
console.log(`vibegroup setup
|
|
525
|
+
`);
|
|
526
|
+
if (!commandExists("claude")) {
|
|
527
|
+
console.error("Claude Code (`claude`) is not on your PATH. Install it first: https://docs.claude.com/claude-code");
|
|
528
|
+
return 1;
|
|
529
|
+
}
|
|
530
|
+
if (pluginInstalled()) {
|
|
531
|
+
console.log("✓ Plugin already installed.");
|
|
532
|
+
} else {
|
|
533
|
+
console.log("• Installing the vibegroup plugin into Claude Code…");
|
|
534
|
+
spawnSync("claude", ["plugin", "marketplace", "add", packageRoot()], { stdio: "inherit" });
|
|
535
|
+
spawnSync("claude", ["plugin", "install", "vibegroup@vibegroup"], { stdio: "inherit" });
|
|
536
|
+
if (!pluginInstalled()) {
|
|
537
|
+
console.error("Plugin install did not complete. Try manually: claude plugin install vibegroup@vibegroup");
|
|
347
538
|
return 1;
|
|
348
539
|
}
|
|
349
|
-
|
|
350
|
-
console.log(result.claimed ? `
|
|
351
|
-
Logged in. You're good to go!` : "\nRegistered with limited access. Run `vibegroup login` again anytime to link your account and unlock full access.");
|
|
352
|
-
return 0;
|
|
353
|
-
} catch (err) {
|
|
354
|
-
console.error(`
|
|
355
|
-
login failed: ${err.message}`);
|
|
356
|
-
return 1;
|
|
540
|
+
console.log("✓ Plugin installed.");
|
|
357
541
|
}
|
|
542
|
+
if (dev) {
|
|
543
|
+
console.log("• Dev mode: skipping the channel allowlist (launch with `vibegroup claude --dev`).");
|
|
544
|
+
} else if (isAllowlisted(readManagedSettings())) {
|
|
545
|
+
console.log("✓ Channel already enabled.");
|
|
546
|
+
} else {
|
|
547
|
+
console.log(`
|
|
548
|
+
• Enabling the vibegroup channel needs admin once (Claude Code gates channel plugins).`);
|
|
549
|
+
console.log(` Writing ${managedSettingsPath()} — enter your password if prompted.`);
|
|
550
|
+
if (!writeAllowlistWithSudo()) {
|
|
551
|
+
console.error(" Could not write managed settings. Retry, or run `vibegroup init --dev` to skip it and use `vibegroup claude --dev`.");
|
|
552
|
+
return 1;
|
|
553
|
+
}
|
|
554
|
+
console.log("✓ Channel enabled.");
|
|
555
|
+
}
|
|
556
|
+
if (isLoggedIn(readAuth(env))) {
|
|
557
|
+
console.log("✓ Already signed in.");
|
|
558
|
+
} else {
|
|
559
|
+
console.log(`
|
|
560
|
+
• Sign in to vibegroup:`);
|
|
561
|
+
const code = await loginCommand(rest, env);
|
|
562
|
+
if (code !== 0)
|
|
563
|
+
return code;
|
|
564
|
+
}
|
|
565
|
+
console.log(`
|
|
566
|
+
✓ Setup complete. Next:`);
|
|
567
|
+
console.log(' vibegroup team create <slug> --name "<name>" # if you need a team');
|
|
568
|
+
console.log(" vibegroup claude --team <slug> # launch a channel session");
|
|
569
|
+
return 0;
|
|
358
570
|
}
|
|
359
|
-
var
|
|
360
|
-
init_loginFlow();
|
|
571
|
+
var init_init = __esm(() => {
|
|
361
572
|
init_auth();
|
|
362
|
-
|
|
573
|
+
init_allowlist();
|
|
574
|
+
init_login();
|
|
363
575
|
});
|
|
364
576
|
|
|
365
577
|
// src/lib/apiClient.ts
|
|
@@ -480,7 +692,7 @@ var init_team = __esm(() => {
|
|
|
480
692
|
});
|
|
481
693
|
|
|
482
694
|
// src/lib/claudeLaunch.ts
|
|
483
|
-
import { spawnSync } from "node:child_process";
|
|
695
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
484
696
|
function buildClaudeArgs(opts = {}) {
|
|
485
697
|
const extra = opts.extraArgs ?? [];
|
|
486
698
|
return opts.dangerously ? ["--dangerously-load-development-channels", CHANNEL_SPEC, ...extra] : ["--channels", CHANNEL_SPEC, ...extra];
|
|
@@ -489,7 +701,7 @@ function launchClaude(opts = {}, launcher = defaultLauncher) {
|
|
|
489
701
|
return launcher("claude", buildClaudeArgs(opts), opts.env);
|
|
490
702
|
}
|
|
491
703
|
var CHANNEL_SPEC = "plugin:vibegroup@vibegroup", defaultLauncher = (cmd, args, env) => {
|
|
492
|
-
const r =
|
|
704
|
+
const r = spawnSync2(cmd, args, { stdio: "inherit", env: env ? { ...process.env, ...env } : process.env });
|
|
493
705
|
if (r.error)
|
|
494
706
|
throw r.error;
|
|
495
707
|
return r.status ?? 0;
|
|
@@ -620,9 +832,13 @@ async function run(argv, env = process.env) {
|
|
|
620
832
|
const { allowlistPathCommand: allowlistPathCommand2 } = await Promise.resolve().then(() => (init_allowlist2(), exports_allowlist));
|
|
621
833
|
return allowlistPathCommand2();
|
|
622
834
|
}
|
|
835
|
+
case "init": {
|
|
836
|
+
const { initCommand: initCommand2 } = await Promise.resolve().then(() => (init_init(), exports_init));
|
|
837
|
+
return initCommand2(rest, flags, env);
|
|
838
|
+
}
|
|
623
839
|
case "login": {
|
|
624
840
|
const { loginCommand: loginCommand2 } = await Promise.resolve().then(() => (init_login(), exports_login));
|
|
625
|
-
return loginCommand2(env);
|
|
841
|
+
return loginCommand2(rest, env);
|
|
626
842
|
}
|
|
627
843
|
case "team": {
|
|
628
844
|
const { teamCommand: teamCommand2 } = await Promise.resolve().then(() => (init_team(), exports_team));
|
|
@@ -652,7 +868,8 @@ var VERSION = "0.1.0", DEFAULT_API_BASE2 = "https://api.vibegroup.sh", HELP = `v
|
|
|
652
868
|
Usage: vibegroup <command> [options]
|
|
653
869
|
|
|
654
870
|
Commands:
|
|
655
|
-
|
|
871
|
+
init [email] One-time setup: install the plugin, enable the channel, sign in
|
|
872
|
+
login [email] Sign in / sign up (opens your browser, verifies email)
|
|
656
873
|
logout Clear the cached session
|
|
657
874
|
status Show your auth + connection status
|
|
658
875
|
team create <slug> [--name] Create a team (a WorkOS org + a general room)
|
package/package.json
CHANGED
|
@@ -1,29 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibegroup",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Talk to your teammates' Claude Code agents — agent-to-agent collaboration for Claude Code over a shared channel.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
"bin": {
|
|
7
|
+
"vibegroup": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/cli.js",
|
|
11
|
+
"README.md",
|
|
12
|
+
".claude-plugin/marketplace.json",
|
|
13
|
+
"plugin/.claude-plugin",
|
|
14
|
+
"plugin/.mcp.json",
|
|
15
|
+
"plugin/dist",
|
|
16
|
+
"plugin/commands",
|
|
17
|
+
"plugin/hooks"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18"
|
|
21
|
+
},
|
|
9
22
|
"license": "UNLICENSED",
|
|
10
23
|
"homepage": "https://vibegroup.sh",
|
|
11
|
-
"bugs": {
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://vibegroup.sh"
|
|
26
|
+
},
|
|
12
27
|
"keywords": ["claude", "claude-code", "agents", "collaboration", "cli", "vibegroup"],
|
|
13
|
-
"publishConfig": {
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
14
31
|
"workspaces": ["packages/*", "plugin"],
|
|
15
32
|
"scripts": {
|
|
16
33
|
"build:relay": "cd packages/relay && bun run build",
|
|
17
34
|
"build:plugin": "cd plugin && bun run build",
|
|
18
|
-
"build:cli": "bun
|
|
35
|
+
"build:cli": "bun run scripts/build-cli.ts",
|
|
19
36
|
"build:server": "cd packages/server && bun run build",
|
|
20
37
|
"build": "bun run build:relay && bun run build:server && bun run build:plugin && bun run build:cli",
|
|
21
|
-
"prepublishOnly": "bun run build:cli",
|
|
38
|
+
"prepublishOnly": "bun run build:plugin && bun run build:cli",
|
|
22
39
|
"test:cli": "bun test ./test/",
|
|
23
40
|
"test:protocol": "cd packages/protocol && bun test",
|
|
24
41
|
"test:relay": "cd packages/relay && bun test",
|
|
25
42
|
"test:server": "cd packages/server && bun test",
|
|
26
43
|
"test:plugin": "cd plugin && bun test",
|
|
27
44
|
"test": "bun run test:cli && bun run test:protocol && bun run test:relay && bun run test:server && bun run test:plugin"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@inkjs/ui": "^2.0.0",
|
|
48
|
+
"ink": "^7.1.0",
|
|
49
|
+
"react": "^19.2.7"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/react": "^19.2.17"
|
|
28
53
|
}
|
|
29
54
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vibegroup",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Ask peer Claude Code agents in other repos and machines what they're working on, over an end-to-end-encrypted relay. Answers come from their live session via Claude Code Channels.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Terry Cruz Melo",
|
|
7
|
+
"email": "terry@cruz.pe"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://vibegroup.sh",
|
|
10
|
+
"repository": "https://github.com/TerryCM/vibegroup",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": ["claude-code", "channel", "mcp", "agents", "collaboration", "relay"]
|
|
13
|
+
}
|