vibegroup 0.1.0

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.
Files changed (4) hide show
  1. package/LICENSE +16 -0
  2. package/README.md +54 -0
  3. package/dist/cli.js +675 -0
  4. package/package.json +29 -0
package/LICENSE ADDED
@@ -0,0 +1,16 @@
1
+ vibegroup — Proprietary License
2
+
3
+ Copyright (c) 2026 vibegroup. All rights reserved.
4
+
5
+ This software and its source code are proprietary and confidential. The
6
+ `vibegroup` command-line client published to npm is distributed solely to let
7
+ end users access the vibegroup service; its use is governed by the vibegroup
8
+ Terms of Service.
9
+
10
+ No license is granted to copy, modify, distribute, sublicense, reverse
11
+ engineer, or create derivative works of this software, in whole or in part,
12
+ except as expressly permitted in writing by vibegroup.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
package/README.md ADDED
@@ -0,0 +1,54 @@
1
+ <h1 align="center">vibegroup ☎️</h1>
2
+
3
+ <p align="center">
4
+ <strong>Let your teammates' Claude Code agents talk to each other.</strong><br>
5
+ Agent-to-agent collaboration across repos, machines, and networks — end-to-end encrypted, scoped to your team.
6
+ </p>
7
+
8
+ ---
9
+
10
+ When you and your teammates are each heads-down in your own repo on your own machine, your agents are strangers. vibegroup ends that: join your team's room and your agents start talking — a teammate's agent can ask yours what you're working on, whether you pushed the migration, what the new API looks like, and **the question lands in your running session, which answers on its own.**
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm i -g vibegroup
16
+ ```
17
+
18
+ ## Quickstart
19
+
20
+ ```bash
21
+ vibegroup login # sign in (opens your browser)
22
+ vibegroup team create acme --name Acme # create a team (or get invited to one)
23
+ vibegroup claude --team acme # launch Claude Code wired to your team's room
24
+ ```
25
+
26
+ Inside the session, your agent can reach teammates' agents:
27
+
28
+ - `vibegroup_peers` — who's in the room and what they're working on
29
+ - `vibegroup_ask` — ask a peer's agent a question (it answers from its own running session)
30
+ - `vibegroup_reply` — answer a question that arrived from a peer
31
+
32
+ ## How it works
33
+
34
+ - **Teams & rooms.** A team is your group; every team has a `general` room. You join by invitation (`vibegroup invite <email> --team <slug>`).
35
+ - **Signed identity.** Every session authenticates with your account, so peers always know who they're talking to.
36
+ - **End-to-end encrypted.** Messages are sealed with your team's key before they leave your machine. The relay only ever routes ciphertext — it never sees your messages.
37
+
38
+ ## Commands
39
+
40
+ ```
41
+ vibegroup login Sign in / sign up
42
+ vibegroup logout Clear the cached session
43
+ vibegroup status Show your auth status
44
+ vibegroup team create <slug> [--name] Create a team (+ a general room)
45
+ vibegroup invite <email> --team <slug> Invite someone to a team
46
+ vibegroup rooms --team <slug> List a team's rooms
47
+ vibegroup claude --team <slug> [--room] Launch Claude Code on the channel
48
+ ```
49
+
50
+ ## Status
51
+
52
+ Alpha. Backend at `api.vibegroup.sh`, relay at `relay.vibegroup.sh`.
53
+
54
+ © vibegroup. All rights reserved. See [LICENSE](LICENSE).
package/dist/cli.js ADDED
@@ -0,0 +1,675 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true,
12
+ configurable: true,
13
+ set: __exportSetter.bind(all, name)
14
+ });
15
+ };
16
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
+
18
+ // src/lib/auth.ts
19
+ import { mkdirSync, readFileSync, writeFileSync, existsSync, rmSync, renameSync } from "node:fs";
20
+ import { join, dirname } from "node:path";
21
+ import { homedir } from "node:os";
22
+ function authDir(env = process.env, home = homedir()) {
23
+ const base = (env.CLAUDE_CONFIG_DIR ?? "").replace(/[\\/]+$/, "") || join(home, ".claude");
24
+ return join(base, "vibegroup");
25
+ }
26
+ function authPath(env = process.env, home = homedir()) {
27
+ return join(authDir(env, home), "auth.json");
28
+ }
29
+ function readAuthAt(path) {
30
+ try {
31
+ if (!existsSync(path))
32
+ return null;
33
+ const raw = JSON.parse(readFileSync(path, "utf8"));
34
+ if (!raw || typeof raw.accessToken !== "string" || raw.accessToken.length === 0)
35
+ return null;
36
+ return raw;
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+ function writeAuthAt(path, tokens) {
42
+ mkdirSync(dirname(path), { recursive: true });
43
+ const tmp = `${path}.tmp`;
44
+ writeFileSync(tmp, JSON.stringify(tokens, null, 2) + `
45
+ `, { mode: 384 });
46
+ renameSync(tmp, path);
47
+ }
48
+ function clearAuthAt(path) {
49
+ try {
50
+ if (existsSync(path))
51
+ rmSync(path);
52
+ } catch {}
53
+ }
54
+ function readAuth(env = process.env, home = homedir()) {
55
+ return readAuthAt(authPath(env, home));
56
+ }
57
+ function writeAuth(tokens, env = process.env, home = homedir()) {
58
+ writeAuthAt(authPath(env, home), tokens);
59
+ }
60
+ function clearAuth(env = process.env, home = homedir()) {
61
+ clearAuthAt(authPath(env, home));
62
+ }
63
+ function isLoggedIn(tokens) {
64
+ return !!tokens && typeof tokens.accessToken === "string" && tokens.accessToken.length > 0;
65
+ }
66
+ var init_auth = () => {};
67
+
68
+ // src/lib/allowlist.ts
69
+ function managedSettingsPath(plat = process.platform) {
70
+ if (plat === "darwin")
71
+ return "/Library/Application Support/ClaudeCode/managed-settings.json";
72
+ if (plat === "win32")
73
+ return "C:\\ProgramData\\ClaudeCode\\managed-settings.json";
74
+ return "/etc/claude-code/managed-settings.json";
75
+ }
76
+ function mergeManagedSettings(existing) {
77
+ const next = { ...existing ?? {} };
78
+ next.channelsEnabled = true;
79
+ const current = Array.isArray(next.allowedChannelPlugins) ? next.allowedChannelPlugins : [];
80
+ const has = current.some((e) => e?.marketplace === "vibegroup" && e?.plugin === "vibegroup");
81
+ next.allowedChannelPlugins = has ? current : [...current, VIBEGROUP_ENTRY];
82
+ return next;
83
+ }
84
+ var VIBEGROUP_ENTRY;
85
+ var init_allowlist = __esm(() => {
86
+ VIBEGROUP_ENTRY = { marketplace: "vibegroup", plugin: "vibegroup" };
87
+ });
88
+
89
+ // src/commands/allowlist.ts
90
+ var exports_allowlist = {};
91
+ __export(exports_allowlist, {
92
+ allowlistPathCommand: () => allowlistPathCommand,
93
+ allowlistJsonCommand: () => allowlistJsonCommand
94
+ });
95
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
96
+ function allowlistJsonCommand() {
97
+ const path = managedSettingsPath();
98
+ let existing = null;
99
+ try {
100
+ if (existsSync2(path))
101
+ existing = JSON.parse(readFileSync2(path, "utf8"));
102
+ } catch {}
103
+ console.log(JSON.stringify(mergeManagedSettings(existing), null, 2));
104
+ return 0;
105
+ }
106
+ function allowlistPathCommand() {
107
+ console.log(managedSettingsPath());
108
+ return 0;
109
+ }
110
+ var init_allowlist2 = __esm(() => {
111
+ init_allowlist();
112
+ });
113
+
114
+ // src/lib/workosAuth.ts
115
+ async function json(res) {
116
+ const text = await res.text();
117
+ try {
118
+ return text ? JSON.parse(text) : {};
119
+ } catch {
120
+ return {};
121
+ }
122
+ }
123
+ function parseCeremony(c) {
124
+ if (!c || !c.user_code || !c.verification_uri)
125
+ return;
126
+ return {
127
+ userCode: c.user_code,
128
+ verificationUri: c.verification_uri,
129
+ interval: typeof c.interval === "number" ? c.interval : 5,
130
+ expiresIn: typeof c.expires_in === "number" ? c.expires_in : 600
131
+ };
132
+ }
133
+ function tokensFromResponse(body, nowMs) {
134
+ const tokens = { accessToken: body.access_token };
135
+ if (typeof body.expires_in === "number") {
136
+ tokens.accessTokenExpiresAt = new Date(nowMs + body.expires_in * 1000).toISOString();
137
+ }
138
+ if (body.identity_assertion)
139
+ tokens.identityAssertion = body.identity_assertion;
140
+ if (body.assertion_expires)
141
+ tokens.assertionExpiresAt = body.assertion_expires;
142
+ if (body.scope)
143
+ tokens.scope = body.scope;
144
+ return tokens;
145
+ }
146
+ async function discover(apiBase, f) {
147
+ const base = apiBase.replace(/\/+$/, "");
148
+ const prmRes = await f(`${base}/.well-known/oauth-protected-resource`);
149
+ if (!prmRes.ok)
150
+ throw new Error(`discovery failed: protected-resource metadata HTTP ${prmRes.status}`);
151
+ const prm = await json(prmRes);
152
+ const as = (prm.authorization_servers ?? [])[0];
153
+ if (!as)
154
+ throw new Error("discovery failed: no authorization_servers in protected-resource metadata");
155
+ const asRes = await f(`${as.replace(/\/+$/, "")}/.well-known/oauth-authorization-server`);
156
+ if (!asRes.ok)
157
+ throw new Error(`discovery failed: authorization-server metadata HTTP ${asRes.status}`);
158
+ const meta = await json(asRes);
159
+ const agent = meta.agent_auth ?? {};
160
+ if (!meta.token_endpoint || !agent.identity_endpoint || !agent.claim_endpoint) {
161
+ throw new Error("discovery failed: authorization server is missing agent_auth endpoints");
162
+ }
163
+ return {
164
+ issuer: meta.issuer,
165
+ tokenEndpoint: meta.token_endpoint,
166
+ revocationEndpoint: meta.revocation_endpoint,
167
+ identityEndpoint: agent.identity_endpoint,
168
+ claimEndpoint: agent.claim_endpoint,
169
+ resource: prm.resource ?? meta.resource,
170
+ scopesSupported: prm.scopes_supported
171
+ };
172
+ }
173
+ async function register(ep, body, f) {
174
+ const res = await f(ep.identityEndpoint, {
175
+ method: "POST",
176
+ headers: { "content-type": "application/json" },
177
+ body: JSON.stringify(body)
178
+ });
179
+ const data = await json(res);
180
+ if (!res.ok && !data.registration_id) {
181
+ throw new Error(`register failed: ${data.error ?? `HTTP ${res.status}`}`);
182
+ }
183
+ return {
184
+ registrationId: data.registration_id,
185
+ registrationType: data.registration_type,
186
+ identityAssertion: data.identity_assertion,
187
+ assertionExpires: data.assertion_expires,
188
+ preClaimScopes: data.pre_claim_scopes,
189
+ postClaimScopes: data.post_claim_scopes,
190
+ claimToken: data.claim_token,
191
+ claim: parseCeremony(data.claim)
192
+ };
193
+ }
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
+ async function pollClaim(tokenEndpoint, claimToken, f, nowMs = Date.now()) {
207
+ const res = await f(tokenEndpoint, {
208
+ method: "POST",
209
+ headers: { "content-type": "application/x-www-form-urlencoded" },
210
+ body: new URLSearchParams({ grant_type: CLAIM_GRANT, claim_token: claimToken }).toString()
211
+ });
212
+ const data = await json(res);
213
+ if (res.ok && data.access_token)
214
+ return { status: "done", tokens: tokensFromResponse(data, nowMs) };
215
+ switch (data.error) {
216
+ case "authorization_pending":
217
+ return { status: "pending" };
218
+ case "slow_down":
219
+ return { status: "slow_down" };
220
+ case "expired_token":
221
+ return { status: "expired" };
222
+ default:
223
+ throw new Error(`claim poll failed: ${data.error ?? `HTTP ${res.status}`}`);
224
+ }
225
+ }
226
+ async function exchangeAssertion(tokenEndpoint, assertion, resource, f, nowMs = Date.now()) {
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";
244
+
245
+ // src/lib/loginFlow.ts
246
+ async function login(apiBase, f, hooks) {
247
+ const emit = (e) => hooks.onEvent?.(e);
248
+ const sleep = hooks.sleep ?? defaultSleep;
249
+ const now = hooks.now ?? Date.now;
250
+ emit({ type: "discovering", apiBase });
251
+ const ep = await discover(apiBase, f);
252
+ emit({ type: "discovered", endpoints: ep });
253
+ if (!await hooks.consent({ resource: ep.resource, scopes: ep.scopesSupported }))
254
+ return null;
255
+ emit({ type: "registering" });
256
+ const reg = await register(ep, { type: "anonymous" }, f);
257
+ if (!reg.identityAssertion)
258
+ throw new Error("register did not return a pre-claim assertion");
259
+ let tokens = await exchangeAssertion(ep.tokenEndpoint, reg.identityAssertion, ep.resource, f, now());
260
+ emit({ type: "registered", scopes: reg.preClaimScopes });
261
+ const email = hooks.emailFor ? await hooks.emailFor() : null;
262
+ if (email && reg.claimToken) {
263
+ const ceremony = await startClaim(ep, reg.claimToken, email, f);
264
+ emit({ type: "claim", ceremony });
265
+ if (hooks.waitForCode)
266
+ await hooks.waitForCode();
267
+ emit({ type: "polling" });
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);
281
+ }
282
+ }
283
+ return { tokens, claimed: false };
284
+ }
285
+ var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
286
+ var init_loginFlow = () => {};
287
+
288
+ // src/commands/login.ts
289
+ var exports_login = {};
290
+ __export(exports_login, {
291
+ loginCommand: () => loginCommand
292
+ });
293
+ import { createInterface } from "node:readline/promises";
294
+ async function ask(question) {
295
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
296
+ try {
297
+ return (await rl.question(question)).trim();
298
+ } finally {
299
+ rl.close();
300
+ }
301
+ }
302
+ async function confirm(question) {
303
+ return /^y(es)?$/i.test(await ask(`${question} [y/N] `));
304
+ }
305
+ function render(e) {
306
+ switch (e.type) {
307
+ case "discovering":
308
+ console.log(`
309
+ • Checking ${e.apiBase}/auth.md for instructions`);
310
+ break;
311
+ case "discovered":
312
+ console.log(" └ Found agent_auth · anonymous registration supported · claim to unlock full access");
313
+ break;
314
+ case "registering":
315
+ console.log("• Registering with vibegroup");
316
+ break;
317
+ case "registered":
318
+ console.log(" └ 200 OK · registered, acting with limited access");
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
+ }
333
+ }
334
+ async function loginCommand(env = process.env) {
335
+ const base = apiBase(env);
336
+ try {
337
+ const result = await login(base, fetch, {
338
+ consent: ({ resource }) => confirm(`Allow agent to register for vibegroup (${resource}) on your behalf?`),
339
+ emailFor: async () => {
340
+ const email = await ask("Email to link your account (leave blank to stay anonymous): ");
341
+ return email || null;
342
+ },
343
+ onEvent: render
344
+ });
345
+ if (!result) {
346
+ console.log("Cancelled — nothing was registered.");
347
+ return 1;
348
+ }
349
+ writeAuth(result.tokens, env);
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;
357
+ }
358
+ }
359
+ var init_login = __esm(() => {
360
+ init_loginFlow();
361
+ init_auth();
362
+ init_cli();
363
+ });
364
+
365
+ // src/lib/apiClient.ts
366
+ async function call(opts, method, path, body) {
367
+ const f = opts.fetchImpl ?? fetch;
368
+ const res = await f(`${opts.apiBase.replace(/\/+$/, "")}${path}`, {
369
+ method,
370
+ headers: {
371
+ authorization: `Bearer ${opts.token}`,
372
+ ...body !== undefined ? { "content-type": "application/json" } : {}
373
+ },
374
+ ...body !== undefined ? { body: JSON.stringify(body) } : {}
375
+ });
376
+ const text = await res.text();
377
+ const data = text ? JSON.parse(text) : {};
378
+ if (!res.ok)
379
+ throw new ApiError(res.status, data.error ?? "error", data.detail ?? data.error ?? `HTTP ${res.status}`);
380
+ return data;
381
+ }
382
+ function createTeam(opts, input) {
383
+ return call(opts, "POST", "/teams", { slug: input.slug, name: input.name });
384
+ }
385
+ function listRooms(opts, slug) {
386
+ return call(opts, "GET", `/teams/${encodeURIComponent(slug)}/rooms`);
387
+ }
388
+ function invite(opts, slug, email) {
389
+ return call(opts, "POST", `/teams/${encodeURIComponent(slug)}/invitations`, { email });
390
+ }
391
+ var ApiError;
392
+ var init_apiClient = __esm(() => {
393
+ ApiError = class ApiError extends Error {
394
+ status;
395
+ code;
396
+ constructor(status, code, message) {
397
+ super(message);
398
+ this.status = status;
399
+ this.code = code;
400
+ }
401
+ };
402
+ });
403
+
404
+ // src/commands/team.ts
405
+ var exports_team = {};
406
+ __export(exports_team, {
407
+ teamCommand: () => teamCommand,
408
+ roomsCommand: () => roomsCommand,
409
+ inviteCommand: () => inviteCommand
410
+ });
411
+ function client(env) {
412
+ const auth = readAuth(env);
413
+ if (!isLoggedIn(auth)) {
414
+ console.error("Not logged in — run `vibegroup login` first.");
415
+ return null;
416
+ }
417
+ return { apiBase: apiBase(env), token: auth.accessToken };
418
+ }
419
+ async function teamCommand(rest, flags, env) {
420
+ if (rest[0] !== "create" || !rest[1]) {
421
+ console.error("usage: vibegroup team create <slug> [--name <name>]");
422
+ return 1;
423
+ }
424
+ const opts = client(env);
425
+ if (!opts)
426
+ return 1;
427
+ try {
428
+ const { team, rooms } = await createTeam(opts, { slug: rest[1], name: str(flags.name) });
429
+ console.log(`Created team "${team.slug}" (${team.name}) — rooms: ${rooms.map((r) => r.name).join(", ")}`);
430
+ console.log(`Invite teammates: vibegroup invite <email> --team ${team.slug}`);
431
+ return 0;
432
+ } catch (e) {
433
+ console.error(`could not create team: ${e.message}`);
434
+ return 1;
435
+ }
436
+ }
437
+ async function roomsCommand(flags, env) {
438
+ const slug = str(flags.team);
439
+ if (!slug) {
440
+ console.error("usage: vibegroup rooms --team <slug>");
441
+ return 1;
442
+ }
443
+ const opts = client(env);
444
+ if (!opts)
445
+ return 1;
446
+ try {
447
+ const { rooms } = await listRooms(opts, slug);
448
+ console.log(rooms.length ? rooms.map((r) => `• ${r.name}`).join(`
449
+ `) : "(no rooms)");
450
+ return 0;
451
+ } catch (e) {
452
+ console.error(`could not list rooms: ${e.message}`);
453
+ return 1;
454
+ }
455
+ }
456
+ async function inviteCommand(rest, flags, env) {
457
+ const email = rest[0];
458
+ const slug = str(flags.team);
459
+ if (!email || !slug) {
460
+ console.error("usage: vibegroup invite <email> --team <slug>");
461
+ return 1;
462
+ }
463
+ const opts = client(env);
464
+ if (!opts)
465
+ return 1;
466
+ try {
467
+ await invite(opts, slug, email);
468
+ console.log(`Invitation sent to ${email} for team ${slug}.`);
469
+ return 0;
470
+ } catch (e) {
471
+ console.error(`could not invite: ${e.message}`);
472
+ return 1;
473
+ }
474
+ }
475
+ var str = (v) => typeof v === "string" ? v : undefined;
476
+ var init_team = __esm(() => {
477
+ init_auth();
478
+ init_apiClient();
479
+ init_cli();
480
+ });
481
+
482
+ // src/lib/claudeLaunch.ts
483
+ import { spawnSync } from "node:child_process";
484
+ function buildClaudeArgs(opts = {}) {
485
+ const extra = opts.extraArgs ?? [];
486
+ return opts.dangerously ? ["--dangerously-load-development-channels", CHANNEL_SPEC, ...extra] : ["--channels", CHANNEL_SPEC, ...extra];
487
+ }
488
+ function launchClaude(opts = {}, launcher = defaultLauncher) {
489
+ return launcher("claude", buildClaudeArgs(opts), opts.env);
490
+ }
491
+ var CHANNEL_SPEC = "plugin:vibegroup@vibegroup", defaultLauncher = (cmd, args, env) => {
492
+ const r = spawnSync(cmd, args, { stdio: "inherit", env: env ? { ...process.env, ...env } : process.env });
493
+ if (r.error)
494
+ throw r.error;
495
+ return r.status ?? 0;
496
+ };
497
+ var init_claudeLaunch = () => {};
498
+
499
+ // src/commands/claudeCmd.ts
500
+ var exports_claudeCmd = {};
501
+ __export(exports_claudeCmd, {
502
+ claudeCommand: () => claudeCommand
503
+ });
504
+ function extractFlag(args, name) {
505
+ const rest = [];
506
+ let value;
507
+ for (let i = 0;i < args.length; i++) {
508
+ const a = args[i];
509
+ if (a === `--${name}`) {
510
+ const next = args[i + 1];
511
+ if (next !== undefined && !next.startsWith("--")) {
512
+ value = next;
513
+ i++;
514
+ } else {
515
+ value = "";
516
+ }
517
+ } else if (a.startsWith(`--${name}=`)) {
518
+ value = a.slice(name.length + 3);
519
+ } else {
520
+ rest.push(a);
521
+ }
522
+ }
523
+ return { value, rest };
524
+ }
525
+ function claudeCommand(args, env = process.env, launcher) {
526
+ if (!isLoggedIn(readAuth(env))) {
527
+ console.error("Not logged in — run `vibegroup login` first.");
528
+ return 1;
529
+ }
530
+ const dangerously = args.includes("--dev");
531
+ const { value: team, rest: afterTeam } = extractFlag(args.filter((a) => a !== "--dev"), "team");
532
+ const { value: room, rest: extra } = extractFlag(afterTeam, "room");
533
+ if (!team) {
534
+ console.error("No team selected — pass `--team <slug>` (the team whose room you want to join).");
535
+ return 1;
536
+ }
537
+ const vg = {
538
+ VIBEGROUP_TEAM: team,
539
+ VIBEGROUP_ROOM: room && room.length > 0 ? room : "general",
540
+ VIBEGROUP_API: env.VIBEGROUP_API ?? DEFAULT_API_BASE
541
+ };
542
+ return launchClaude({ extraArgs: extra, dangerously, env: vg }, launcher);
543
+ }
544
+ var DEFAULT_API_BASE = "https://api.vibegroup.sh";
545
+ var init_claudeCmd = __esm(() => {
546
+ init_claudeLaunch();
547
+ init_auth();
548
+ });
549
+
550
+ // src/cli.ts
551
+ function parseArgs(argv) {
552
+ let command = "";
553
+ const rest = [];
554
+ const flags = {};
555
+ for (let i = 0;i < argv.length; i++) {
556
+ const a = argv[i];
557
+ if (a.startsWith("--")) {
558
+ const eq = a.indexOf("=");
559
+ if (eq >= 0) {
560
+ flags[a.slice(2, eq)] = a.slice(eq + 1);
561
+ } else {
562
+ const next = argv[i + 1];
563
+ if (next !== undefined && !next.startsWith("--")) {
564
+ flags[a.slice(2)] = next;
565
+ i++;
566
+ } else {
567
+ flags[a.slice(2)] = true;
568
+ }
569
+ }
570
+ } else if (!command) {
571
+ command = a;
572
+ } else {
573
+ rest.push(a);
574
+ }
575
+ }
576
+ return { command, rest, flags };
577
+ }
578
+ function formatStatus(tokens) {
579
+ if (!tokens)
580
+ return `Not logged in.
581
+ Run: vibegroup login`;
582
+ const who = tokens.user?.email ?? tokens.user?.name ?? "anonymous (limited access)";
583
+ const lines = [`Logged in as ${who}`];
584
+ if (tokens.scope)
585
+ lines.push(` scope: ${tokens.scope}`);
586
+ if (tokens.accessTokenExpiresAt)
587
+ lines.push(` token expires: ${tokens.accessTokenExpiresAt}`);
588
+ return lines.join(`
589
+ `);
590
+ }
591
+ function apiBase(env = process.env) {
592
+ return env.VIBEGROUP_API ?? DEFAULT_API_BASE2;
593
+ }
594
+ async function run(argv, env = process.env) {
595
+ const { command, rest, flags } = parseArgs(argv);
596
+ if (flags.version) {
597
+ console.log(VERSION);
598
+ return 0;
599
+ }
600
+ switch (command) {
601
+ case "":
602
+ case "help":
603
+ console.log(HELP);
604
+ return 0;
605
+ case "version":
606
+ console.log(VERSION);
607
+ return 0;
608
+ case "status":
609
+ console.log(formatStatus(readAuth(env)));
610
+ return 0;
611
+ case "logout":
612
+ clearAuth(env);
613
+ console.log("Logged out.");
614
+ return 0;
615
+ case "allowlist-json": {
616
+ const { allowlistJsonCommand: allowlistJsonCommand2 } = await Promise.resolve().then(() => (init_allowlist2(), exports_allowlist));
617
+ return allowlistJsonCommand2();
618
+ }
619
+ case "allowlist-path": {
620
+ const { allowlistPathCommand: allowlistPathCommand2 } = await Promise.resolve().then(() => (init_allowlist2(), exports_allowlist));
621
+ return allowlistPathCommand2();
622
+ }
623
+ case "login": {
624
+ const { loginCommand: loginCommand2 } = await Promise.resolve().then(() => (init_login(), exports_login));
625
+ return loginCommand2(env);
626
+ }
627
+ case "team": {
628
+ const { teamCommand: teamCommand2 } = await Promise.resolve().then(() => (init_team(), exports_team));
629
+ return teamCommand2(rest, flags, env);
630
+ }
631
+ case "rooms": {
632
+ const { roomsCommand: roomsCommand2 } = await Promise.resolve().then(() => (init_team(), exports_team));
633
+ return roomsCommand2(flags, env);
634
+ }
635
+ case "invite": {
636
+ const { inviteCommand: inviteCommand2 } = await Promise.resolve().then(() => (init_team(), exports_team));
637
+ return inviteCommand2(rest, flags, env);
638
+ }
639
+ case "claude": {
640
+ const { claudeCommand: claudeCommand2 } = await Promise.resolve().then(() => (init_claudeCmd(), exports_claudeCmd));
641
+ return claudeCommand2(argv.slice(argv.indexOf("claude") + 1));
642
+ }
643
+ default:
644
+ console.error(`unknown command: ${command}
645
+ `);
646
+ console.log(HELP);
647
+ return 1;
648
+ }
649
+ }
650
+ var VERSION = "0.1.0", DEFAULT_API_BASE2 = "https://api.vibegroup.sh", HELP = `vibegroup — talk to your teammates' Claude Code agents.
651
+
652
+ Usage: vibegroup <command> [options]
653
+
654
+ Commands:
655
+ login Sign in / sign up (opens your browser)
656
+ logout Clear the cached session
657
+ status Show your auth + connection status
658
+ team create <slug> [--name] Create a team (a WorkOS org + a general room)
659
+ invite <email> --team <s> Invite someone to a team
660
+ rooms --team <slug> List a team's rooms
661
+ claude --team <slug> [--room <name>] [...]
662
+ Launch Claude Code with the vibegroup channel
663
+ help Show this help
664
+
665
+ Run a command with --help for more.`;
666
+ var init_cli = __esm(() => {
667
+ init_auth();
668
+ });
669
+
670
+ // src/bin.ts
671
+ init_cli();
672
+ run(process.argv.slice(2)).then((code) => process.exit(code)).catch((err) => {
673
+ console.error(err?.message ?? err);
674
+ process.exit(1);
675
+ });
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "vibegroup",
3
+ "version": "0.1.0",
4
+ "description": "Talk to your teammates' Claude Code agents — agent-to-agent collaboration for Claude Code over a shared channel.",
5
+ "type": "module",
6
+ "bin": { "vibegroup": "dist/cli.js" },
7
+ "files": ["dist/cli.js", "README.md"],
8
+ "engines": { "node": ">=18" },
9
+ "license": "UNLICENSED",
10
+ "homepage": "https://vibegroup.sh",
11
+ "bugs": { "url": "https://vibegroup.sh" },
12
+ "keywords": ["claude", "claude-code", "agents", "collaboration", "cli", "vibegroup"],
13
+ "publishConfig": { "access": "public" },
14
+ "workspaces": ["packages/*", "plugin"],
15
+ "scripts": {
16
+ "build:relay": "cd packages/relay && bun run build",
17
+ "build:plugin": "cd plugin && bun run build",
18
+ "build:cli": "bun build src/bin.ts --target node --outfile dist/cli.js --banner \"#!/usr/bin/env node\"",
19
+ "build:server": "cd packages/server && bun run build",
20
+ "build": "bun run build:relay && bun run build:server && bun run build:plugin && bun run build:cli",
21
+ "prepublishOnly": "bun run build:cli",
22
+ "test:cli": "bun test ./test/",
23
+ "test:protocol": "cd packages/protocol && bun test",
24
+ "test:relay": "cd packages/relay && bun test",
25
+ "test:server": "cd packages/server && bun test",
26
+ "test:plugin": "cd plugin && bun test",
27
+ "test": "bun run test:cli && bun run test:protocol && bun run test:relay && bun run test:server && bun run test:plugin"
28
+ }
29
+ }