@brainbase-labs/cli 0.2.3 → 0.2.4

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 (2) hide show
  1. package/dist/index.js +1055 -95
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  // src/index.ts
4
4
  import process2 from "node:process";
5
- import fs40 from "node:fs";
6
- import pc31 from "picocolors";
5
+ import fs44 from "node:fs";
6
+ import pc36 from "picocolors";
7
7
 
8
8
  // src/cli/template.ts
9
9
  import pc10 from "picocolors";
@@ -398,11 +398,13 @@ function globalPaths() {
398
398
  agents: path4.join(GLOBAL_ROOT, "agents"),
399
399
  settings: path4.join(GLOBAL_ROOT, "settings.json"),
400
400
  claudeMd: path4.join(GLOBAL_ROOT, "CLAUDE.md"),
401
- userClaudeJson: USER_CLAUDE_JSON
401
+ userClaudeJson: USER_CLAUDE_JSON,
402
+ mcpStore: USER_CLAUDE_JSON
402
403
  };
403
404
  }
404
405
  function projectPaths(cwd) {
405
406
  const root = projectRoot(cwd);
407
+ const mcpJson = projectMcpJson(cwd);
406
408
  return {
407
409
  root,
408
410
  skills: path4.join(root, "skills"),
@@ -410,8 +412,9 @@ function projectPaths(cwd) {
410
412
  agents: path4.join(root, "agents"),
411
413
  settings: path4.join(root, "settings.json"),
412
414
  claudeMd: path4.join(cwd, "CLAUDE.md"),
413
- mcpJson: projectMcpJson(cwd),
414
- userClaudeJson: USER_CLAUDE_JSON
415
+ mcpJson,
416
+ userClaudeJson: USER_CLAUDE_JSON,
417
+ mcpStore: mcpJson
415
418
  };
416
419
  }
417
420
  function scopePaths(cwd, scope) {
@@ -491,10 +494,6 @@ function readSettings(file) {
491
494
  function writeSettings(file, settings) {
492
495
  writeJson(file, settings);
493
496
  }
494
- function ensureSettings(file) {
495
- if (!exists(file))
496
- writeSettings(file, {});
497
- }
498
497
  function setMcpServer(file, name, entry) {
499
498
  const s = readSettings(file);
500
499
  if (!s.mcpServers)
@@ -518,6 +517,33 @@ function getMcpServer(file, name) {
518
517
  function listMcpServers(file) {
519
518
  return readSettings(file).mcpServers ?? {};
520
519
  }
520
+ function normalizeMcpPayload(payload) {
521
+ const out = { ...payload };
522
+ delete out.is_enabled;
523
+ const hasUrl = typeof out.url === "string" && out.url.length > 0;
524
+ const hasCommand = typeof out.command === "string" && out.command.length > 0;
525
+ if (!out.type) {
526
+ if (hasUrl)
527
+ out.type = "http";
528
+ else if (hasCommand)
529
+ out.type = "stdio";
530
+ }
531
+ if (out.type === "http" || out.type === "sse") {
532
+ if (Array.isArray(out.args) && out.args.length === 0)
533
+ delete out.args;
534
+ if (out.env && typeof out.env === "object" && Object.keys(out.env).length === 0) {
535
+ delete out.env;
536
+ }
537
+ if (out.headers && typeof out.headers === "object" && Object.keys(out.headers).length === 0) {
538
+ delete out.headers;
539
+ }
540
+ }
541
+ if (out.type === "stdio") {
542
+ delete out.url;
543
+ delete out.headers;
544
+ }
545
+ return out;
546
+ }
521
547
  function listMcpServersFromMcpJson(file) {
522
548
  return readJsonOr(file, {}).mcpServers ?? {};
523
549
  }
@@ -1028,14 +1054,13 @@ async function installAgent(comp, opts) {
1028
1054
  return installSingleFileComponent(comp, agents, `${comp.slug}.md`, opts);
1029
1055
  }
1030
1056
  async function installMcp(comp, opts, ctx) {
1031
- const { settings } = scopePaths(opts.cwd, opts.scope);
1032
- ensureSettings(settings);
1033
- const existing = getMcpServer(settings, comp.slug);
1057
+ const { mcpStore } = scopePaths(opts.cwd, opts.scope);
1058
+ const existing = getMcpServer(mcpStore, comp.slug);
1034
1059
  if (existing) {
1035
1060
  const choice = await opts.resolveConflict({
1036
1061
  type: "mcp",
1037
1062
  slug: comp.slug,
1038
- existingPath: settings
1063
+ existingPath: mcpStore
1039
1064
  });
1040
1065
  if (choice === "skip")
1041
1066
  return { skip: "user skipped existing mcp" };
@@ -1064,15 +1089,15 @@ async function installMcp(comp, opts, ctx) {
1064
1089
  }
1065
1090
  }
1066
1091
  }
1067
- setMcpServer(settings, comp.slug, payload);
1092
+ setMcpServer(mcpStore, comp.slug, normalizeMcpPayload(payload));
1068
1093
  return {
1069
1094
  type: "mcp",
1070
1095
  slug: comp.slug,
1071
- installedPaths: [settings],
1096
+ installedPaths: [mcpStore],
1072
1097
  sourceChecksum: comp.checksum,
1073
1098
  scope: opts.scope,
1074
1099
  embeddedKey: comp.slug,
1075
- settingsFile: settings
1100
+ settingsFile: mcpStore
1076
1101
  };
1077
1102
  }
1078
1103
  async function installInstruction(comp, opts, ctx) {
@@ -1296,8 +1321,8 @@ async function diffClaudeCode(components, opts) {
1296
1321
  break;
1297
1322
  }
1298
1323
  case "mcp": {
1299
- if (exists(sp.settings)) {
1300
- const servers = listMcpServers(sp.settings);
1324
+ if (exists(sp.mcpStore)) {
1325
+ const servers = listMcpServers(sp.mcpStore);
1301
1326
  const cur = servers[comp.slug];
1302
1327
  if (cur)
1303
1328
  installedChecksum = sha256(JSON.stringify(cur));
@@ -2880,6 +2905,21 @@ var api = {
2880
2905
  return request(`/api/cli/keys/${encodeURIComponent(keyId)}`, {
2881
2906
  method: "DELETE"
2882
2907
  });
2908
+ },
2909
+ listOrchestrations(orgId, teamId) {
2910
+ return request(`/api/cli/orgs/${encodeURIComponent(orgId)}/teams/${encodeURIComponent(teamId)}/orchestrations`);
2911
+ },
2912
+ getOrchestration(orchId) {
2913
+ return request(`/api/cli/orchestrations/${encodeURIComponent(orchId)}`);
2914
+ },
2915
+ getOrchestrationManifest(orchId) {
2916
+ return request(`/api/cli/orchestrations/${encodeURIComponent(orchId)}/manifest`);
2917
+ },
2918
+ updateOrchestration(orchId, input) {
2919
+ return request(`/api/cli/orchestrations/${encodeURIComponent(orchId)}`, {
2920
+ method: "PUT",
2921
+ body: JSON.stringify(input)
2922
+ });
2883
2923
  }
2884
2924
  };
2885
2925
  function proxyBaseUrl(session) {
@@ -9310,6 +9350,906 @@ function printHelp() {
9310
9350
  `));
9311
9351
  }
9312
9352
 
9353
+ // src/cli/orchestration.ts
9354
+ import pc33 from "picocolors";
9355
+
9356
+ // src/cli/orchestration-pull.ts
9357
+ import path46 from "node:path";
9358
+ import fs43 from "node:fs";
9359
+ import * as p21 from "@clack/prompts";
9360
+ import pc29 from "picocolors";
9361
+
9362
+ // src/core/orchestration-manifest.ts
9363
+ import path43 from "node:path";
9364
+ import fs40 from "node:fs";
9365
+ import { z as z8 } from "zod";
9366
+ import YAML2 from "yaml";
9367
+ var ORCH_MANIFEST_FILE = "brainbase-orchestration.yaml";
9368
+ var ORCH_MEMBERS_DIR = "agents";
9369
+ var OrchMetaSchema = z8.object({
9370
+ name: z8.string().min(1),
9371
+ description: z8.string().optional(),
9372
+ icon: z8.string().optional(),
9373
+ icon_color: z8.string().optional(),
9374
+ credit_limit: z8.number().optional()
9375
+ });
9376
+ var MemberSchema = z8.object({
9377
+ slug: z8.string().min(1),
9378
+ name: z8.string().optional()
9379
+ });
9380
+ var EdgeSchema = z8.object({
9381
+ from: z8.string().min(1),
9382
+ to: z8.string().min(1),
9383
+ description: z8.string().optional(),
9384
+ payload_schema: z8.record(z8.unknown()).optional()
9385
+ });
9386
+ var OrchestrationManifestSchema = z8.object({
9387
+ schema: z8.literal(1),
9388
+ orchestration: OrchMetaSchema,
9389
+ members: z8.array(MemberSchema).default([]),
9390
+ edges: z8.array(EdgeSchema).default([])
9391
+ });
9392
+ function orchManifestPath(cwd) {
9393
+ return path43.join(cwd, ORCH_MANIFEST_FILE);
9394
+ }
9395
+ function hasOrchManifest(cwd) {
9396
+ return fs40.existsSync(orchManifestPath(cwd));
9397
+ }
9398
+ function readOrchManifest(cwd) {
9399
+ const p20 = orchManifestPath(cwd);
9400
+ if (!fs40.existsSync(p20))
9401
+ return null;
9402
+ const raw = fs40.readFileSync(p20, "utf8");
9403
+ let parsed;
9404
+ try {
9405
+ parsed = YAML2.parse(raw);
9406
+ } catch (err) {
9407
+ throw new Error(`${ORCH_MANIFEST_FILE} is not valid YAML: ${err.message}`);
9408
+ }
9409
+ const result = OrchestrationManifestSchema.safeParse(parsed);
9410
+ if (!result.success) {
9411
+ throw new Error(`${ORCH_MANIFEST_FILE} is invalid: ${result.error.issues.map((i) => `${i.path.join(".") || "(root)"} — ${i.message}`).join("; ")}`);
9412
+ }
9413
+ return result.data;
9414
+ }
9415
+ function writeOrchManifest(cwd, manifest) {
9416
+ const doc = new YAML2.Document;
9417
+ doc.contents = manifest;
9418
+ doc.commentBefore = ` brainbase-orchestration.yaml — declarative orchestration manifest.
9419
+ ` + ` Committed to source control. Edit by hand, then
9420
+ ` + " `brainbase orchestration push`. Member agents live under ./agents/.";
9421
+ fs40.writeFileSync(orchManifestPath(cwd), String(doc), "utf8");
9422
+ }
9423
+ function memberDir(cwd, slug) {
9424
+ return path43.join(cwd, ORCH_MEMBERS_DIR, slug);
9425
+ }
9426
+
9427
+ // src/core/orchestration-link.ts
9428
+ import path44 from "node:path";
9429
+ import fs41 from "node:fs";
9430
+ import { z as z9 } from "zod";
9431
+ var ORCH_LINK_FILE = "orchestration-link.json";
9432
+ var ORCH_SYNC_STATE_FILE = "orchestration-sync-state.json";
9433
+ var OrchestrationLinkSchema = z9.object({
9434
+ schemaVersion: z9.literal(1),
9435
+ orchestration_id: z9.string(),
9436
+ group_id: z9.string(),
9437
+ org_id: z9.string(),
9438
+ team_id: z9.string(),
9439
+ name: z9.string(),
9440
+ description: z9.string().nullish().transform((v) => v ?? undefined),
9441
+ linked_at: z9.string(),
9442
+ linked_by: z9.string().nullish().transform((v) => v ?? undefined)
9443
+ });
9444
+ var SyncedMemberSchema = z9.object({
9445
+ agent_id: z9.string(),
9446
+ slug: z9.string(),
9447
+ revision: z9.number()
9448
+ });
9449
+ var SyncedEdgeSchema = z9.object({
9450
+ from_slug: z9.string(),
9451
+ to_slug: z9.string(),
9452
+ description: z9.string().default(""),
9453
+ payload_schema: z9.record(z9.unknown()).default({})
9454
+ });
9455
+ var OrchestrationSyncStateSchema = z9.object({
9456
+ schemaVersion: z9.literal(1),
9457
+ orchestration_id: z9.string(),
9458
+ revision: z9.number(),
9459
+ synced_at: z9.string(),
9460
+ members: z9.array(SyncedMemberSchema),
9461
+ edges: z9.array(SyncedEdgeSchema)
9462
+ });
9463
+ function orchLinkPath(cwd) {
9464
+ return path44.join(cwd, LINK_DIR, ORCH_LINK_FILE);
9465
+ }
9466
+ function orchSyncStatePath(cwd) {
9467
+ return path44.join(cwd, LINK_DIR, ORCH_SYNC_STATE_FILE);
9468
+ }
9469
+ function readOrchLink(cwd) {
9470
+ const p20 = orchLinkPath(cwd);
9471
+ if (!exists(p20))
9472
+ return null;
9473
+ try {
9474
+ return OrchestrationLinkSchema.parse(readJson(p20));
9475
+ } catch {
9476
+ return null;
9477
+ }
9478
+ }
9479
+ function writeOrchLink(cwd, link) {
9480
+ ensureDir(path44.join(cwd, LINK_DIR));
9481
+ const clean = {};
9482
+ for (const [k, v] of Object.entries(link)) {
9483
+ if (v !== null && v !== undefined)
9484
+ clean[k] = v;
9485
+ }
9486
+ writeJson(orchLinkPath(cwd), clean);
9487
+ ensureGitignore2(cwd);
9488
+ }
9489
+ function readOrchSyncState(cwd) {
9490
+ const p20 = orchSyncStatePath(cwd);
9491
+ if (!exists(p20))
9492
+ return null;
9493
+ try {
9494
+ return OrchestrationSyncStateSchema.parse(readJson(p20));
9495
+ } catch {
9496
+ return null;
9497
+ }
9498
+ }
9499
+ function writeOrchSyncState(cwd, state) {
9500
+ ensureDir(path44.join(cwd, LINK_DIR));
9501
+ writeJson(orchSyncStatePath(cwd), state);
9502
+ ensureGitignore2(cwd);
9503
+ }
9504
+ function ensureGitignore2(cwd) {
9505
+ const ignorePath = path44.join(cwd, LINK_DIR, ".gitignore");
9506
+ const desired = `${ORCH_SYNC_STATE_FILE}
9507
+ `;
9508
+ try {
9509
+ if (!exists(ignorePath)) {
9510
+ fs41.writeFileSync(ignorePath, desired);
9511
+ return;
9512
+ }
9513
+ const current = fs41.readFileSync(ignorePath, "utf8");
9514
+ if (!current.split(/\r?\n/).some((l) => l.trim() === ORCH_SYNC_STATE_FILE)) {
9515
+ fs41.writeFileSync(ignorePath, current.endsWith(`
9516
+ `) ? current + desired : current + `
9517
+ ` + desired);
9518
+ }
9519
+ } catch {}
9520
+ }
9521
+
9522
+ // src/core/agent-fresh-install.ts
9523
+ import path45 from "node:path";
9524
+ import fs42 from "node:fs";
9525
+ import os10 from "node:os";
9526
+ import * as p20 from "@clack/prompts";
9527
+ async function installAgentFresh(input) {
9528
+ const { cwd, agent, cloud, harness } = input;
9529
+ const scope = input.scope ?? "project";
9530
+ ensureDir(cwd);
9531
+ const stageRoot = stageManifestComponents2(cloud.components);
9532
+ const justInstalledPaths = new Map;
9533
+ try {
9534
+ if (cloud.components.length > 0) {
9535
+ const toInstall = cloud.components.map((c) => ({
9536
+ type: c.type,
9537
+ slug: c.slug,
9538
+ scope,
9539
+ rootDir: path45.join(stageRoot, c.type, c.slug),
9540
+ description: c.description,
9541
+ meta: c.meta,
9542
+ payload: c.meta?.mcp,
9543
+ checksum: c.hash
9544
+ }));
9545
+ const installOpts = {
9546
+ cwd,
9547
+ scope,
9548
+ resolveConflict: async (_c) => "overwrite",
9549
+ resolveSecret: async () => null
9550
+ };
9551
+ const result = await runHarnessInstall3(harness, toInstall, installOpts, agent.name);
9552
+ for (const o of result.installed) {
9553
+ justInstalledPaths.set(`${o.type}/${o.slug}`, o.installedPaths);
9554
+ }
9555
+ }
9556
+ materializeInstructions2(cwd, cloud);
9557
+ const manifest = buildManifestFromCloud(cloud, agent);
9558
+ writeManifest(cwd, manifest);
9559
+ writeLink(cwd, {
9560
+ schemaVersion: 1,
9561
+ agent_id: agent.id,
9562
+ org_id: agent.org_id,
9563
+ team_id: agent.team_id,
9564
+ slug: agent.slug,
9565
+ name: agent.name,
9566
+ tagline: agent.tagline,
9567
+ url: agent.url,
9568
+ linked_at: new Date().toISOString(),
9569
+ harness
9570
+ });
9571
+ const syncedComponents = cloud.components.map((c) => ({
9572
+ type: c.type,
9573
+ slug: c.slug,
9574
+ hash: c.hash,
9575
+ installedPaths: justInstalledPaths.get(`${c.type}/${c.slug}`) ?? []
9576
+ }));
9577
+ writeSyncState(cwd, {
9578
+ schemaVersion: 1,
9579
+ agent_id: agent.id,
9580
+ revision: cloud.revision,
9581
+ synced_at: new Date().toISOString(),
9582
+ components: syncedComponents,
9583
+ agentMeta: { name: agent.name, tagline: agent.tagline }
9584
+ });
9585
+ if (input.pullSecrets !== false) {
9586
+ await pullAgentSecrets(cwd, agent.id);
9587
+ }
9588
+ return {
9589
+ installedPaths: justInstalledPaths,
9590
+ manifest,
9591
+ syncedComponents
9592
+ };
9593
+ } finally {
9594
+ try {
9595
+ fs42.rmSync(stageRoot, { recursive: true, force: true });
9596
+ } catch {}
9597
+ }
9598
+ }
9599
+ function stageManifestComponents2(components) {
9600
+ const root = fs42.mkdtempSync(path45.join(os10.tmpdir(), "brainbase-orch-pull-"));
9601
+ for (const c of components) {
9602
+ const compDir = path45.join(root, c.type, c.slug);
9603
+ ensureDir(compDir);
9604
+ for (const f of c.files) {
9605
+ const target = path45.join(compDir, f.path);
9606
+ ensureDir(path45.dirname(target));
9607
+ fs42.writeFileSync(target, f.content);
9608
+ }
9609
+ }
9610
+ return root;
9611
+ }
9612
+ function runHarnessInstall3(harnessId, components, opts, agentName) {
9613
+ if (harnessId === "claude-code")
9614
+ return installClaudeCodeWithCtx(components, opts, agentName);
9615
+ if (harnessId === "codex")
9616
+ return installCodexWithCtx(components, opts, agentName);
9617
+ return getAdapter(harnessId).install(components, opts);
9618
+ }
9619
+ function materializeInstructions2(cwd, cloud) {
9620
+ for (const c of cloud.components) {
9621
+ if (c.type !== "instruction")
9622
+ continue;
9623
+ const body = c.files[0]?.content ?? "";
9624
+ if (!body.trim())
9625
+ continue;
9626
+ fs42.writeFileSync(path45.join(cwd, DEFAULT_INSTRUCTIONS_FILE), body, "utf8");
9627
+ return;
9628
+ }
9629
+ }
9630
+ function buildManifestFromCloud(cloud, agent) {
9631
+ const skills = cloud.components.filter((c) => c.type === "skill").map((c) => {
9632
+ const meta = c.meta ?? {};
9633
+ if (meta.name && meta.name.includes("/")) {
9634
+ return {
9635
+ source: meta.version ? `registry:${meta.name}@${meta.version}` : `registry:${meta.name}`
9636
+ };
9637
+ }
9638
+ return { source: `registry:${c.slug}` };
9639
+ });
9640
+ const hasInstructions = cloud.components.some((c) => c.type === "instruction" && c.files[0]?.content?.trim());
9641
+ const mcp = cloud.components.filter((c) => c.type === "mcp").map((c) => {
9642
+ const payload = (c.meta ?? {}).mcp ?? {};
9643
+ const entry = { name: c.slug };
9644
+ if (typeof payload.url === "string")
9645
+ entry.url = payload.url;
9646
+ if (typeof payload.command === "string")
9647
+ entry.command = payload.command;
9648
+ if (Array.isArray(payload.args))
9649
+ entry.args = payload.args.map(String);
9650
+ if (payload.env && typeof payload.env === "object")
9651
+ entry.env = payload.env;
9652
+ if (payload.headers && typeof payload.headers === "object")
9653
+ entry.headers = payload.headers;
9654
+ if (typeof payload.is_enabled === "boolean")
9655
+ entry.is_enabled = payload.is_enabled;
9656
+ return entry;
9657
+ });
9658
+ return {
9659
+ schema: 1,
9660
+ agent: {
9661
+ name: agent.name,
9662
+ ...agent.tagline ? { tagline: agent.tagline } : {}
9663
+ },
9664
+ ...hasInstructions ? { instructions: { file: DEFAULT_INSTRUCTIONS_FILE } } : {},
9665
+ skills,
9666
+ mcp
9667
+ };
9668
+ }
9669
+ async function pullAgentSecrets(cwd, agentId) {
9670
+ try {
9671
+ const res = await api.getAgentSecrets(agentId);
9672
+ const secrets = res.secrets ?? {};
9673
+ if (Object.keys(secrets).length > 0) {
9674
+ writeLocalSecrets(cwd, secrets);
9675
+ }
9676
+ } catch (err) {
9677
+ if (err instanceof ApiError && err.status !== 404) {
9678
+ p20.log.warn(`Skipped secrets for agent ${agentId}: ${err.message}`);
9679
+ }
9680
+ }
9681
+ }
9682
+
9683
+ // src/cli/orchestration-pull.ts
9684
+ async function runOrchestrationPull(cwd, args) {
9685
+ banner("orchestration pull — fetch orchestration + all member agents");
9686
+ let orchId = null;
9687
+ const existingLink = readOrchLink(cwd);
9688
+ if (existingLink) {
9689
+ orchId = existingLink.orchestration_id;
9690
+ } else if (args.orchestrationId) {
9691
+ orchId = args.orchestrationId;
9692
+ } else {
9693
+ p21.log.warn("This folder is not linked to any orchestration.");
9694
+ p21.log.info(`Run ${pc29.cyan("brainbase orchestration pull <id>")} with an orchestration id,
9695
+ or ${pc29.cyan("brainbase orchestration list")} to find one.`);
9696
+ return;
9697
+ }
9698
+ const sp = p21.spinner();
9699
+ sp.start(`Fetching orchestration ${orchId}…`);
9700
+ let cloud;
9701
+ try {
9702
+ cloud = await api.getOrchestrationManifest(orchId);
9703
+ sp.stop(`Cloud revision ${cloud.revision} — ${cloud.members.length} member${cloud.members.length === 1 ? "" : "s"}, ${cloud.edges.length} edge${cloud.edges.length === 1 ? "" : "s"}.`);
9704
+ } catch (err) {
9705
+ sp.stop("Failed.");
9706
+ handleApiError5(err);
9707
+ return;
9708
+ }
9709
+ const planLines = [];
9710
+ planLines.push("");
9711
+ planLines.push(` ${pc29.bold(cloud.name)} ${pc29.dim(`(${cloud.id})`)}`);
9712
+ if (cloud.description)
9713
+ planLines.push(` ${pc29.dim(cloud.description)}`);
9714
+ planLines.push("");
9715
+ planLines.push(` ${pc29.dim("members:")}`);
9716
+ for (const m of cloud.members) {
9717
+ const skipped = !m.manifest;
9718
+ const tail = skipped ? pc29.red(" (manifest unavailable — skipped)") : "";
9719
+ planLines.push(` ${pc29.cyan("•")} ${pc29.bold(m.slug)} ${pc29.dim(`(${m.name})`)}${tail}`);
9720
+ }
9721
+ if (cloud.edges.length) {
9722
+ planLines.push("");
9723
+ planLines.push(` ${pc29.dim("edges:")}`);
9724
+ for (const e of cloud.edges) {
9725
+ const from = e.from_slug ?? e.from_agent_id;
9726
+ const to = e.to_slug ?? e.to_agent_id;
9727
+ const desc = e.description ? ` ${pc29.dim("— " + e.description)}` : "";
9728
+ planLines.push(` ${pc29.cyan(from)} ${pc29.dim("→")} ${pc29.cyan(to)}${desc}`);
9729
+ }
9730
+ }
9731
+ planLines.push("");
9732
+ console.log(planLines.join(`
9733
+ `));
9734
+ const isRefresh = !!existingLink;
9735
+ if (!args.yes && !isRefresh) {
9736
+ const ok = await p21.confirm({
9737
+ message: `Pull into ${pc29.bold(cwd)}?`,
9738
+ initialValue: true
9739
+ });
9740
+ if (!ensureNotCancelled(ok)) {
9741
+ p21.outro("Aborted.");
9742
+ return;
9743
+ }
9744
+ }
9745
+ const fallbackHarness = args.harness ?? "claude-code";
9746
+ fs43.mkdirSync(cwd, { recursive: true });
9747
+ if (hasOrchManifest(cwd) && existingLink && existingLink.orchestration_id !== orchId) {
9748
+ p21.log.error(`This folder is linked to orchestration ${existingLink.orchestration_id}, not ${orchId}. Move to a fresh directory or unlink first.`);
9749
+ return;
9750
+ }
9751
+ const installedMembers = [];
9752
+ for (const m of cloud.members) {
9753
+ if (!m.manifest) {
9754
+ p21.log.warn(`Skipping member ${m.slug}: server did not return a manifest.`);
9755
+ continue;
9756
+ }
9757
+ const dest = memberDir(cwd, m.slug);
9758
+ const memberSp = p21.spinner();
9759
+ memberSp.start(`Installing ${m.slug}…`);
9760
+ try {
9761
+ await installAgentFresh({
9762
+ cwd: dest,
9763
+ agent: {
9764
+ id: m.agent_id,
9765
+ name: m.name || m.slug,
9766
+ slug: m.slug,
9767
+ tagline: undefined,
9768
+ org_id: cloud.group_id,
9769
+ team_id: cloud.group_id,
9770
+ url: undefined,
9771
+ harness: fallbackHarness
9772
+ },
9773
+ cloud: m.manifest,
9774
+ harness: fallbackHarness,
9775
+ scope: "project",
9776
+ pullSecrets: true
9777
+ });
9778
+ memberSp.stop(`Installed ${pc29.bold(m.slug)} ${pc29.dim(`(${m.manifest.components.length} components)`)}.`);
9779
+ installedMembers.push({
9780
+ agent_id: m.agent_id,
9781
+ slug: m.slug,
9782
+ revision: m.manifest.revision
9783
+ });
9784
+ } catch (err) {
9785
+ memberSp.stop(`Failed to install ${m.slug}.`);
9786
+ p21.log.error(err.message);
9787
+ }
9788
+ }
9789
+ const manifest = {
9790
+ schema: 1,
9791
+ orchestration: {
9792
+ name: cloud.name,
9793
+ ...cloud.description ? { description: cloud.description } : {},
9794
+ ...cloud.icon ? { icon: cloud.icon } : {},
9795
+ ...cloud.icon_color ? { icon_color: cloud.icon_color } : {},
9796
+ ...cloud.credit_limit != null ? { credit_limit: cloud.credit_limit } : {}
9797
+ },
9798
+ members: cloud.members.map((m) => ({
9799
+ slug: m.slug,
9800
+ ...m.name && m.name !== m.slug ? { name: m.name } : {}
9801
+ })),
9802
+ edges: cloud.edges.map((e) => ({
9803
+ from: e.from_slug ?? e.from_agent_id,
9804
+ to: e.to_slug ?? e.to_agent_id,
9805
+ ...e.description ? { description: e.description } : {},
9806
+ ...e.payload_schema && Object.keys(e.payload_schema).length ? { payload_schema: e.payload_schema } : {}
9807
+ }))
9808
+ };
9809
+ writeOrchManifest(cwd, manifest);
9810
+ writeOrchLink(cwd, {
9811
+ schemaVersion: 1,
9812
+ orchestration_id: cloud.id,
9813
+ group_id: cloud.group_id,
9814
+ org_id: cloud.group_id,
9815
+ team_id: cloud.group_id,
9816
+ name: cloud.name,
9817
+ description: cloud.description || undefined,
9818
+ linked_at: new Date().toISOString()
9819
+ });
9820
+ writeOrchSyncState(cwd, {
9821
+ schemaVersion: 1,
9822
+ orchestration_id: cloud.id,
9823
+ revision: cloud.revision,
9824
+ synced_at: new Date().toISOString(),
9825
+ members: installedMembers,
9826
+ edges: cloud.edges.map((e) => ({
9827
+ from_slug: e.from_slug ?? e.from_agent_id,
9828
+ to_slug: e.to_slug ?? e.to_agent_id,
9829
+ description: e.description ?? "",
9830
+ payload_schema: e.payload_schema ?? {}
9831
+ }))
9832
+ });
9833
+ p21.outro(`Pulled ${cloud.name} at revision ${cloud.revision} into ${path46.basename(cwd)}/ ${pc29.dim(`(${installedMembers.length}/${cloud.members.length} members)`)}.`);
9834
+ }
9835
+ function handleApiError5(err) {
9836
+ if (err instanceof ApiError) {
9837
+ if (err.status === 401) {
9838
+ p21.log.error("Your session is invalid. Run `brainbase login` and try again.");
9839
+ } else if (err.status === 403) {
9840
+ p21.log.error("You do not have access to this orchestration. Ask the team owner to grant you access via `group_memberships`.");
9841
+ } else {
9842
+ p21.log.error(err.message);
9843
+ }
9844
+ } else {
9845
+ p21.log.error(err.message);
9846
+ }
9847
+ }
9848
+
9849
+ // src/cli/orchestration-push.ts
9850
+ import * as p22 from "@clack/prompts";
9851
+ import pc30 from "picocolors";
9852
+ async function runOrchestrationPush(cwd, args) {
9853
+ banner("orchestration push — recursively push each member, then update the graph");
9854
+ const link = readOrchLink(cwd);
9855
+ if (!link) {
9856
+ p22.log.warn("This folder is not linked to any orchestration.");
9857
+ p22.log.info(`Run ${pc30.cyan("brainbase orchestration pull <id>")} first.`);
9858
+ return;
9859
+ }
9860
+ if (!hasOrchManifest(cwd)) {
9861
+ p22.log.warn(`No ${pc30.bold(ORCH_MANIFEST_FILE)} here.`);
9862
+ p22.log.info(`Run ${pc30.cyan("brainbase orchestration pull")} to materialise the manifest before pushing.`);
9863
+ return;
9864
+ }
9865
+ let manifest;
9866
+ try {
9867
+ manifest = readOrchManifest(cwd);
9868
+ } catch (err) {
9869
+ p22.log.error(err.message);
9870
+ return;
9871
+ }
9872
+ const memberSlugs = new Set(manifest.members.map((m) => m.slug));
9873
+ for (const e of manifest.edges) {
9874
+ if (!memberSlugs.has(e.from)) {
9875
+ p22.log.error(`Edge from "${pc30.bold(e.from)}" references a slug that isn't in members.`);
9876
+ return;
9877
+ }
9878
+ if (!memberSlugs.has(e.to)) {
9879
+ p22.log.error(`Edge to "${pc30.bold(e.to)}" references a slug that isn't in members.`);
9880
+ return;
9881
+ }
9882
+ if (e.from === e.to) {
9883
+ p22.log.error(`Edge ${pc30.bold(e.from)} → ${pc30.bold(e.to)}: self-loops are not allowed.`);
9884
+ return;
9885
+ }
9886
+ }
9887
+ const slugToAgentId = new Map;
9888
+ const missing = [];
9889
+ for (const m of manifest.members) {
9890
+ const dir = memberDir(cwd, m.slug);
9891
+ const memberLink = readLink(dir);
9892
+ if (!memberLink) {
9893
+ missing.push(m.slug);
9894
+ continue;
9895
+ }
9896
+ slugToAgentId.set(m.slug, memberLink.agent_id);
9897
+ }
9898
+ if (missing.length) {
9899
+ p22.log.error(`These members have no local checkout (expected at agents/<slug>/.brainbase/link.json): ${missing.join(", ")}.`);
9900
+ p22.log.info(`Run ${pc30.cyan("brainbase orchestration pull")} to materialise the missing folders.`);
9901
+ return;
9902
+ }
9903
+ const plan = [""];
9904
+ plan.push(` ${pc30.bold(link.name)} ${pc30.dim(`(${link.orchestration_id})`)}`);
9905
+ plan.push(` ${pc30.dim(`${manifest.members.length} member${manifest.members.length === 1 ? "" : "s"}, ${manifest.edges.length} edge${manifest.edges.length === 1 ? "" : "s"}`)}`);
9906
+ plan.push("");
9907
+ if (!args.graphOnly) {
9908
+ plan.push(` ${pc30.dim("per-member agent push:")}`);
9909
+ for (const m of manifest.members) {
9910
+ plan.push(` ${pc30.cyan("•")} ${pc30.bold(m.slug)}`);
9911
+ }
9912
+ plan.push("");
9913
+ }
9914
+ console.log(plan.join(`
9915
+ `));
9916
+ if (!args.yes) {
9917
+ const ok = await p22.confirm({
9918
+ message: args.graphOnly ? "Push graph (members + edges) only?" : "Push each member, then update the graph?",
9919
+ initialValue: true
9920
+ });
9921
+ if (!ensureNotCancelled(ok)) {
9922
+ p22.outro("Aborted.");
9923
+ return;
9924
+ }
9925
+ }
9926
+ if (!args.graphOnly) {
9927
+ for (const m of manifest.members) {
9928
+ const dir = memberDir(cwd, m.slug);
9929
+ console.log("");
9930
+ console.log(`${pc30.dim("───")} ${pc30.bold(m.slug)} ${pc30.dim("───")}`);
9931
+ try {
9932
+ await runAgentPush(dir, { yes: true });
9933
+ } catch (err) {
9934
+ p22.log.error(`Failed to push ${m.slug}: ${err.message}`);
9935
+ return;
9936
+ }
9937
+ }
9938
+ }
9939
+ const memberIds = manifest.members.map((m) => slugToAgentId.get(m.slug));
9940
+ const edges = manifest.edges.map((e) => ({
9941
+ from_agent_id: slugToAgentId.get(e.from),
9942
+ to_agent_id: slugToAgentId.get(e.to),
9943
+ description: e.description ?? "",
9944
+ payload_schema: e.payload_schema ?? {}
9945
+ }));
9946
+ const sp = p22.spinner();
9947
+ sp.start("Updating orchestration graph…");
9948
+ const lock = readOrchSyncState(cwd);
9949
+ try {
9950
+ const updated = await api.updateOrchestration(link.orchestration_id, {
9951
+ name: manifest.orchestration.name,
9952
+ description: manifest.orchestration.description ?? "",
9953
+ icon: manifest.orchestration.icon,
9954
+ icon_color: manifest.orchestration.icon_color,
9955
+ credit_limit: manifest.orchestration.credit_limit,
9956
+ members: memberIds,
9957
+ edges,
9958
+ ...lock?.revision != null ? { base_revision: lock.revision } : {}
9959
+ });
9960
+ sp.stop(`Graph updated. New cloud revision ${updated.revision}.`);
9961
+ writeOrchSyncState(cwd, {
9962
+ schemaVersion: 1,
9963
+ orchestration_id: updated.id,
9964
+ revision: updated.revision,
9965
+ synced_at: new Date().toISOString(),
9966
+ members: updated.members.map((m) => ({
9967
+ agent_id: m.agent_id,
9968
+ slug: m.slug,
9969
+ revision: m.revision ?? 0
9970
+ })),
9971
+ edges: updated.edges.map((e) => ({
9972
+ from_slug: e.from_slug ?? e.from_agent_id,
9973
+ to_slug: e.to_slug ?? e.to_agent_id,
9974
+ description: e.description ?? "",
9975
+ payload_schema: e.payload_schema ?? {}
9976
+ }))
9977
+ });
9978
+ p22.outro(`Pushed ${link.name} at revision ${updated.revision}.`);
9979
+ } catch (err) {
9980
+ sp.stop("Failed.");
9981
+ handleApiError6(err);
9982
+ }
9983
+ }
9984
+ function handleApiError6(err) {
9985
+ if (err instanceof ApiError) {
9986
+ if (err.status === 401) {
9987
+ p22.log.error("Your session is invalid. Run `brainbase login` and try again.");
9988
+ } else if (err.status === 403) {
9989
+ p22.log.error("You do not have access to this orchestration.");
9990
+ } else if (err.status === 409) {
9991
+ p22.log.error(err.message);
9992
+ p22.log.info(`Run ${pc30.cyan("brainbase orchestration pull")} to reconcile, then push again.`);
9993
+ } else {
9994
+ p22.log.error(err.message);
9995
+ }
9996
+ } else {
9997
+ p22.log.error(err.message);
9998
+ }
9999
+ }
10000
+
10001
+ // src/cli/orchestration-status.ts
10002
+ import * as p23 from "@clack/prompts";
10003
+ import pc31 from "picocolors";
10004
+ async function runOrchestrationStatus(cwd) {
10005
+ banner("orchestration status — what changed locally, remotely, both");
10006
+ const link = readOrchLink(cwd);
10007
+ if (!link) {
10008
+ p23.log.warn("This folder is not linked to any orchestration.");
10009
+ p23.log.info(`Run ${pc31.cyan("brainbase orchestration pull <id>")} first.`);
10010
+ return;
10011
+ }
10012
+ const localManifest = hasOrchManifest(cwd) ? readOrchManifest(cwd) : null;
10013
+ const lock = readOrchSyncState(cwd);
10014
+ let cloud;
10015
+ const sp = p23.spinner();
10016
+ sp.start(`Fetching ${link.name}…`);
10017
+ try {
10018
+ cloud = await api.getOrchestration(link.orchestration_id);
10019
+ sp.stop(`Cloud revision ${cloud.revision}.`);
10020
+ } catch (err) {
10021
+ sp.stop("Failed to reach brainbase.");
10022
+ if (err instanceof ApiError && err.status === 401) {
10023
+ p23.log.error("Your session is invalid. Run `brainbase login` and try again.");
10024
+ } else {
10025
+ p23.log.error(err.message);
10026
+ }
10027
+ return;
10028
+ }
10029
+ const lines = [];
10030
+ lines.push("");
10031
+ lines.push(` ${pc31.bold(link.name)} ${pc31.dim(`(${link.orchestration_id})`)}`);
10032
+ lines.push(` ${pc31.dim("revision")} cloud ${cloud.revision}${lock ? ` · lock ${lock.revision}` : " · never pulled"}`);
10033
+ lines.push("");
10034
+ const cloudMemberSet = new Set(cloud.members.map((m) => m.slug));
10035
+ const localMemberSet = new Set((localManifest?.members ?? []).map((m) => m.slug));
10036
+ const membersAdded = [...localMemberSet].filter((s) => !cloudMemberSet.has(s));
10037
+ const membersRemoved = [...cloudMemberSet].filter((s) => !localMemberSet.has(s));
10038
+ if (membersAdded.length || membersRemoved.length) {
10039
+ lines.push(` ${pc31.bold("members")}`);
10040
+ for (const slug of membersAdded) {
10041
+ lines.push(` ${pc31.yellow("→ push")} added in yaml: ${pc31.bold(slug)}`);
10042
+ }
10043
+ for (const slug of membersRemoved) {
10044
+ lines.push(` ${pc31.cyan("← pull")} added on cloud: ${pc31.bold(slug)}`);
10045
+ }
10046
+ lines.push("");
10047
+ }
10048
+ const cloudEdgeKey = (e) => `${e.from_slug ?? e.from_agent_id}->${e.to_slug ?? e.to_agent_id}|${e.description ?? ""}`;
10049
+ const localEdgeKey = (e) => `${e.from}->${e.to}|${e.description ?? ""}`;
10050
+ const cloudEdges = new Map;
10051
+ for (const e of cloud.edges)
10052
+ cloudEdges.set(cloudEdgeKey(e), true);
10053
+ const localEdges = new Map;
10054
+ for (const e of localManifest?.edges ?? [])
10055
+ localEdges.set(localEdgeKey(e), true);
10056
+ const edgesAdded = [...localEdges.keys()].filter((k) => !cloudEdges.has(k));
10057
+ const edgesRemoved = [...cloudEdges.keys()].filter((k) => !localEdges.has(k));
10058
+ if (edgesAdded.length || edgesRemoved.length) {
10059
+ lines.push(` ${pc31.bold("edges")}`);
10060
+ for (const k of edgesAdded)
10061
+ lines.push(` ${pc31.yellow("→ push")} added in yaml: ${k}`);
10062
+ for (const k of edgesRemoved)
10063
+ lines.push(` ${pc31.cyan("← pull")} added on cloud: ${k}`);
10064
+ lines.push("");
10065
+ }
10066
+ const lockByAgentId = new Map((lock?.members ?? []).map((m) => [m.agent_id, m]));
10067
+ const memberDrift = [];
10068
+ for (const m of cloud.members) {
10069
+ const dir = memberDir(cwd, m.slug);
10070
+ const memberSync = readSyncState(dir);
10071
+ if (!memberSync) {
10072
+ memberDrift.push({ slug: m.slug, reason: "no local checkout" });
10073
+ continue;
10074
+ }
10075
+ const lockEntry = lockByAgentId.get(m.agent_id);
10076
+ const lockedRev = lockEntry?.revision;
10077
+ if (m.revision != null && lockedRev != null && m.revision !== lockedRev) {
10078
+ memberDrift.push({
10079
+ slug: m.slug,
10080
+ reason: `cloud rev ${m.revision} ≠ lock ${lockedRev}`
10081
+ });
10082
+ }
10083
+ }
10084
+ if (memberDrift.length) {
10085
+ lines.push(` ${pc31.bold("member content drift")}`);
10086
+ for (const d of memberDrift) {
10087
+ lines.push(` ${pc31.cyan("?")} ${pc31.bold(d.slug)} ${pc31.dim("— " + d.reason)}`);
10088
+ }
10089
+ lines.push(` ${pc31.dim("cd into each member folder and run")} ${pc31.cyan("brainbase agent status")}`);
10090
+ lines.push("");
10091
+ }
10092
+ if (!membersAdded.length && !membersRemoved.length && !edgesAdded.length && !edgesRemoved.length && !memberDrift.length) {
10093
+ lines.push(` ${pc31.green("✓")} everything is in sync`);
10094
+ lines.push("");
10095
+ console.log(lines.join(`
10096
+ `));
10097
+ return;
10098
+ }
10099
+ lines.push(` ${pc31.dim("run")} ${pc31.cyan("brainbase orchestration pull")} ${pc31.dim("to apply cloud changes,")} ${pc31.cyan("brainbase orchestration push")} ${pc31.dim("to send yours")}`);
10100
+ lines.push("");
10101
+ console.log(lines.join(`
10102
+ `));
10103
+ }
10104
+
10105
+ // src/cli/orchestration-list.ts
10106
+ import * as p24 from "@clack/prompts";
10107
+ import pc32 from "picocolors";
10108
+ async function runOrchestrationList(args) {
10109
+ banner("orchestration list — orchestrations under a team");
10110
+ let orgId = args.orgId;
10111
+ let teamId = args.teamId;
10112
+ if (!orgId) {
10113
+ let orgs;
10114
+ try {
10115
+ orgs = await api.listOrgs();
10116
+ } catch (err) {
10117
+ handleApiError7(err);
10118
+ return;
10119
+ }
10120
+ if (orgs.length === 0) {
10121
+ p24.log.warn("You are not a member of any organization.");
10122
+ return;
10123
+ }
10124
+ if (orgs.length === 1) {
10125
+ orgId = orgs[0].id;
10126
+ } else {
10127
+ const choice = await p24.select({
10128
+ message: "Which organization?",
10129
+ options: orgs.map((o) => ({ value: o.id, label: o.name }))
10130
+ });
10131
+ orgId = ensureNotCancelled(choice);
10132
+ }
10133
+ }
10134
+ if (!teamId) {
10135
+ let teams;
10136
+ try {
10137
+ teams = await api.listTeams(orgId);
10138
+ } catch (err) {
10139
+ handleApiError7(err);
10140
+ return;
10141
+ }
10142
+ if (teams.length === 0) {
10143
+ p24.log.warn("No teams under this organization. Create one in the web app first.");
10144
+ return;
10145
+ }
10146
+ if (teams.length === 1) {
10147
+ teamId = teams[0].id;
10148
+ } else {
10149
+ const choice = await p24.select({
10150
+ message: "Which team?",
10151
+ options: teams.map((t) => ({ value: t.id, label: t.name }))
10152
+ });
10153
+ teamId = ensureNotCancelled(choice);
10154
+ }
10155
+ }
10156
+ let items;
10157
+ const sp = p24.spinner();
10158
+ sp.start("Fetching orchestrations…");
10159
+ try {
10160
+ items = await api.listOrchestrations(orgId, teamId);
10161
+ sp.stop(`${items.length} orchestration${items.length === 1 ? "" : "s"}.`);
10162
+ } catch (err) {
10163
+ sp.stop("Failed.");
10164
+ handleApiError7(err);
10165
+ return;
10166
+ }
10167
+ if (items.length === 0) {
10168
+ p24.log.info("This team has no orchestrations yet.");
10169
+ return;
10170
+ }
10171
+ const lines = [""];
10172
+ for (const o of items) {
10173
+ lines.push(` ${pc32.bold(o.name)} ${pc32.dim(o.id)}`);
10174
+ if (o.description)
10175
+ lines.push(` ${pc32.dim(o.description)}`);
10176
+ lines.push(` ${pc32.dim(`${o.member_count} member${o.member_count === 1 ? "" : "s"} · ${o.edge_count} edge${o.edge_count === 1 ? "" : "s"}`)}`);
10177
+ lines.push("");
10178
+ }
10179
+ lines.push(` ${pc32.dim("pull one with")} ${pc32.cyan("brainbase orchestration pull <id>")}`);
10180
+ lines.push("");
10181
+ console.log(lines.join(`
10182
+ `));
10183
+ }
10184
+ function handleApiError7(err) {
10185
+ if (err instanceof ApiError) {
10186
+ if (err.status === 401) {
10187
+ p24.log.error("Your session is invalid. Run `brainbase login` and try again.");
10188
+ } else {
10189
+ p24.log.error(err.message);
10190
+ }
10191
+ } else {
10192
+ p24.log.error(err.message);
10193
+ }
10194
+ }
10195
+
10196
+ // src/cli/orchestration.ts
10197
+ async function runOrchestration(cwd, sub, args, opts) {
10198
+ switch (sub) {
10199
+ case "pull":
10200
+ await runOrchestrationPull(cwd, {
10201
+ orchestrationId: args[0],
10202
+ yes: opts.yes,
10203
+ harness: opts.harness
10204
+ });
10205
+ return;
10206
+ case "push":
10207
+ await runOrchestrationPush(cwd, {
10208
+ yes: opts.yes,
10209
+ graphOnly: opts.graphOnly
10210
+ });
10211
+ return;
10212
+ case "status":
10213
+ await runOrchestrationStatus(cwd);
10214
+ return;
10215
+ case "list":
10216
+ case "ls":
10217
+ await runOrchestrationList({ orgId: opts.orgId, teamId: opts.teamId });
10218
+ return;
10219
+ case undefined:
10220
+ case "help":
10221
+ case "-h":
10222
+ case "--help":
10223
+ printHelp2();
10224
+ return;
10225
+ default:
10226
+ console.error(`Unknown orchestration subcommand: ${sub}
10227
+ `);
10228
+ printHelp2();
10229
+ process.exit(1);
10230
+ }
10231
+ }
10232
+ function printHelp2() {
10233
+ const out = [];
10234
+ out.push("");
10235
+ out.push(` ${pc33.bold("brainbase orchestration")} ${pc33.dim("<sub> [options]")}`);
10236
+ out.push("");
10237
+ out.push(` ${pc33.cyan("pull")} ${pc33.dim("<id>")} ${pc33.dim("fetch orchestration + every member agent into this folder")}`);
10238
+ out.push(` ${pc33.cyan("push")} ${pc33.dim("push each member, then update the orchestration graph")}`);
10239
+ out.push(` ${pc33.cyan("status")} ${pc33.dim("show what would push and what would pull")}`);
10240
+ out.push(` ${pc33.cyan("list")} ${pc33.dim("list orchestrations under a team")}`);
10241
+ out.push("");
10242
+ out.push(` ${pc33.bold("Flags")}`);
10243
+ out.push(` ${pc33.dim("--yes, -y")} skip confirmations`);
10244
+ out.push(` ${pc33.dim("--harness <id>")} harness for newly-created member folders (default claude-code)`);
10245
+ out.push(` ${pc33.dim("--graph-only")} for push: only update members + edges, skip per-member push`);
10246
+ out.push(` ${pc33.dim("--org <id>")} for list: org id (CLI vocab — DB teams.id)`);
10247
+ out.push(` ${pc33.dim("--team <id>")} for list: team id (CLI vocab — DB groups.id)`);
10248
+ out.push("");
10249
+ console.log(out.join(`
10250
+ `));
10251
+ }
10252
+
9313
10253
  // src/cli/run.ts
9314
10254
  import { spawn as spawn2 } from "node:child_process";
9315
10255
  async function runRun(cwd, args) {
@@ -9349,17 +10289,17 @@ async function runRun(cwd, args) {
9349
10289
  }
9350
10290
 
9351
10291
  // src/cli/publish.ts
9352
- import * as p20 from "@clack/prompts";
9353
- import pc29 from "picocolors";
10292
+ import * as p25 from "@clack/prompts";
10293
+ import pc34 from "picocolors";
9354
10294
  async function runPublish(cwd, _args) {
9355
10295
  banner("publish — send your changes to the team");
9356
10296
  const link = readLink(cwd);
9357
10297
  if (!link) {
9358
- p20.log.warn("This folder is not linked to any agent.");
9359
- p20.log.info(`Run ${pc29.cyan("brainbase link")} first.`);
10298
+ p25.log.warn("This folder is not linked to any agent.");
10299
+ p25.log.info(`Run ${pc34.cyan("brainbase link")} first.`);
9360
10300
  return;
9361
10301
  }
9362
- p20.log.info(`${pc29.bold("publish")} is coming soon — for now, edit the agent on the web app and run ${pc29.cyan("brainbase sync")} to bring changes here.`);
10302
+ p25.log.info(`${pc34.bold("publish")} is coming soon — for now, edit the agent on the web app and run ${pc34.cyan("brainbase sync")} to bring changes here.`);
9363
10303
  }
9364
10304
 
9365
10305
  // src/ui/ink/StatusCard.tsx
@@ -9658,8 +10598,8 @@ async function runStatus(cwd) {
9658
10598
  }
9659
10599
 
9660
10600
  // src/cli/token.ts
9661
- import * as p21 from "@clack/prompts";
9662
- import pc30 from "picocolors";
10601
+ import * as p26 from "@clack/prompts";
10602
+ import pc35 from "picocolors";
9663
10603
 
9664
10604
  // src/ui/ink/TokenCards.tsx
9665
10605
  import { Box as Box15, Text as Text16 } from "ink";
@@ -9905,7 +10845,7 @@ async function runTokenCreate(args) {
9905
10845
  banner("token create — make a long-lived CLI key");
9906
10846
  let name = args.name;
9907
10847
  if (!name) {
9908
- const ans = await p21.text({
10848
+ const ans = await p26.text({
9909
10849
  message: "Token label",
9910
10850
  placeholder: "my-laptop or ci-runner",
9911
10851
  validate: (v) => v.length === 0 ? "Required." : undefined
@@ -9913,13 +10853,13 @@ async function runTokenCreate(args) {
9913
10853
  name = ensureNotCancelled(ans);
9914
10854
  }
9915
10855
  const scopes = args.scopes && args.scopes.length > 0 ? args.scopes : DEFAULT_SCOPES;
9916
- const spinner17 = p21.spinner();
9917
- spinner17.start("Creating token…");
10856
+ const spinner21 = p26.spinner();
10857
+ spinner21.start("Creating token…");
9918
10858
  const created = await registryApi.createCliToken({
9919
10859
  name,
9920
10860
  scopes
9921
10861
  });
9922
- spinner17.stop("Token created.");
10862
+ spinner21.stop("Token created.");
9923
10863
  writeToken(created.token, name);
9924
10864
  await showTokenCreatedCard({
9925
10865
  token: created.token,
@@ -9953,8 +10893,8 @@ async function runTokenRevoke(args) {
9953
10893
  process.exit(1);
9954
10894
  }
9955
10895
  if (!args.yes) {
9956
- const ok = await p21.confirm({
9957
- message: `Revoke token ${pc30.bold(args.id)}? CIs and machines using it will stop working.`,
10896
+ const ok = await p26.confirm({
10897
+ message: `Revoke token ${pc35.bold(args.id)}? CIs and machines using it will stop working.`,
9958
10898
  initialValue: false
9959
10899
  });
9960
10900
  if (!ensureNotCancelled(ok))
@@ -9969,7 +10909,7 @@ async function runTokenRevoke(args) {
9969
10909
  }
9970
10910
  async function runTokenClear() {
9971
10911
  if (!readToken()) {
9972
- console.log(pc30.dim("No local token stored."));
10912
+ console.log(pc35.dim("No local token stored."));
9973
10913
  return;
9974
10914
  }
9975
10915
  clearToken();
@@ -10020,17 +10960,17 @@ async function runToken(sub, rest, args) {
10020
10960
  function printTokenHelp() {
10021
10961
  const out = [];
10022
10962
  out.push("");
10023
- out.push(` ${pc30.bold("brainbase token")} ${pc30.dim("<command>")}`);
10963
+ out.push(` ${pc35.bold("brainbase token")} ${pc35.dim("<command>")}`);
10024
10964
  out.push("");
10025
- out.push(` ${pc30.cyan("create")} ${pc30.dim("issue a new long-lived CLI key (PAT)")}`);
10026
- out.push(` ${pc30.cyan("list")} ${pc30.dim("show your active tokens")}`);
10027
- out.push(` ${pc30.cyan("revoke")} ${pc30.dim("<id>")} ${pc30.dim("revoke a token by id")}`);
10028
- out.push(` ${pc30.cyan("clear")} ${pc30.dim("forget the local token (does not revoke)")}`);
10965
+ out.push(` ${pc35.cyan("create")} ${pc35.dim("issue a new long-lived CLI key (PAT)")}`);
10966
+ out.push(` ${pc35.cyan("list")} ${pc35.dim("show your active tokens")}`);
10967
+ out.push(` ${pc35.cyan("revoke")} ${pc35.dim("<id>")} ${pc35.dim("revoke a token by id")}`);
10968
+ out.push(` ${pc35.cyan("clear")} ${pc35.dim("forget the local token (does not revoke)")}`);
10029
10969
  out.push("");
10030
- out.push(` ${pc30.bold("create flags")}`);
10031
- out.push(` ${pc30.cyan("--name, -n")} ${pc30.dim("<label>")} ${pc30.dim("token label (prompted if omitted)")}`);
10032
- out.push(` ${pc30.cyan("--scopes")} ${pc30.dim("<list>")} ${pc30.dim("comma-separated; allowed: read, publish, admin")}`);
10033
- out.push(` ${pc30.dim("default: read,publish")}`);
10970
+ out.push(` ${pc35.bold("create flags")}`);
10971
+ out.push(` ${pc35.cyan("--name, -n")} ${pc35.dim("<label>")} ${pc35.dim("token label (prompted if omitted)")}`);
10972
+ out.push(` ${pc35.cyan("--scopes")} ${pc35.dim("<list>")} ${pc35.dim("comma-separated; allowed: read, publish, admin")}`);
10973
+ out.push(` ${pc35.dim("default: read,publish")}`);
10034
10974
  out.push("");
10035
10975
  console.log(out.join(`
10036
10976
  `));
@@ -10050,81 +10990,88 @@ var PROTECTED = new Set([
10050
10990
  function help() {
10051
10991
  const out = [];
10052
10992
  out.push("");
10053
- out.push(` ${brandTint("◆")} ${pc31.bold("brainbase")} ${pc31.dim("v0.2.0")}`);
10054
- out.push(` ${pc31.dim("connect your local agent to the brainbase platform")}`);
10993
+ out.push(` ${brandTint("◆")} ${pc36.bold("brainbase")} ${pc36.dim("v0.2.0")}`);
10994
+ out.push(` ${pc36.dim("connect your local agent to the brainbase platform")}`);
10055
10995
  out.push("");
10056
10996
  out.push(divider("USAGE"));
10057
10997
  out.push("");
10058
- out.push(` ${pc31.bold("brainbase")} ${pc31.dim("<command> [options]")}`);
10998
+ out.push(` ${pc36.bold("brainbase")} ${pc36.dim("<command> [options]")}`);
10059
10999
  out.push("");
10060
11000
  out.push(divider("AUTH"));
10061
11001
  out.push("");
10062
- out.push(` ${pc31.cyan("login")} ${pc31.dim(" open the web app and connect this device")}`);
10063
- out.push(` ${pc31.cyan("logout")} ${pc31.dim(" clear the local session")}`);
10064
- out.push(` ${pc31.cyan("whoami")} ${pc31.dim(" show the current user")}`);
11002
+ out.push(` ${pc36.cyan("login")} ${pc36.dim(" open the web app and connect this device")}`);
11003
+ out.push(` ${pc36.cyan("logout")} ${pc36.dim(" clear the local session")}`);
11004
+ out.push(` ${pc36.cyan("whoami")} ${pc36.dim(" show the current user")}`);
10065
11005
  out.push("");
10066
11006
  out.push(divider("LINKED AGENT"));
10067
11007
  out.push("");
10068
- out.push(` ${pc31.cyan("agent create")} ${pc31.dim("make a new agent on the cloud and link this folder")}`);
10069
- out.push(` ${pc31.cyan("link")} ${pc31.dim("attach this folder to an existing agent")}`);
10070
- out.push(` ${pc31.cyan("agent pull")} ${pc31.dim("bring cloud changes into this folder")}`);
10071
- out.push(` ${pc31.cyan("agent push")} ${pc31.dim("send local changes to the cloud")}`);
10072
- out.push(` ${pc31.cyan("agent status")} ${pc31.dim("show what would pull and what would push")}`);
10073
- out.push(` ${pc31.cyan("agent env")} ${pc31.dim("print export lines for `eval $(brainbase agent env)`")}`);
10074
- out.push(` ${pc31.cyan("run")} ${pc31.dim("<cmd> [args...]")} ${pc31.dim("run <cmd> with secrets.env loaded into env")}`);
10075
- out.push(` ${pc31.cyan("status")} ${pc31.dim("show what this folder is linked to")}`);
10076
- out.push(` ${pc31.cyan("unlink")} ${pc31.dim("disconnect this folder")}`);
11008
+ out.push(` ${pc36.cyan("agent create")} ${pc36.dim("make a new agent on the cloud and link this folder")}`);
11009
+ out.push(` ${pc36.cyan("link")} ${pc36.dim("attach this folder to an existing agent")}`);
11010
+ out.push(` ${pc36.cyan("agent pull")} ${pc36.dim("bring cloud changes into this folder")}`);
11011
+ out.push(` ${pc36.cyan("agent push")} ${pc36.dim("send local changes to the cloud")}`);
11012
+ out.push(` ${pc36.cyan("agent status")} ${pc36.dim("show what would pull and what would push")}`);
11013
+ out.push(` ${pc36.cyan("agent env")} ${pc36.dim("print export lines for `eval $(brainbase agent env)`")}`);
11014
+ out.push(` ${pc36.cyan("run")} ${pc36.dim("<cmd> [args...]")} ${pc36.dim("run <cmd> with secrets.env loaded into env")}`);
11015
+ out.push(` ${pc36.cyan("status")} ${pc36.dim("show what this folder is linked to")}`);
11016
+ out.push(` ${pc36.cyan("unlink")} ${pc36.dim("disconnect this folder")}`);
11017
+ out.push("");
11018
+ out.push(divider("ORCHESTRATIONS"));
11019
+ out.push("");
11020
+ out.push(` ${pc36.cyan("orchestration list")} ${pc36.dim("list orchestrations under a team")}`);
11021
+ out.push(` ${pc36.cyan("orchestration pull")} ${pc36.dim("<id>")} ${pc36.dim("recursively fetch an orchestration + every member agent")}`);
11022
+ out.push(` ${pc36.cyan("orchestration push")} ${pc36.dim("recursively push each member, then update the graph")}`);
11023
+ out.push(` ${pc36.cyan("orchestration status")} ${pc36.dim("show what would push and what would pull")}`);
10077
11024
  out.push("");
10078
11025
  out.push(divider("TEMPLATES"));
10079
11026
  out.push("");
10080
- out.push(` ${pc31.cyan("template pack")} ${pc31.dim("bundle the current agent into a template")}`);
10081
- out.push(` ${pc31.cyan("template publish")} ${pc31.dim("upload a template to the registry")}`);
10082
- out.push(` ${pc31.cyan("template search")} ${pc31.dim("[query]")} ${pc31.dim("search the registry")}`);
10083
- out.push(` ${pc31.cyan("template info")} ${pc31.dim("<creator/slug>")} ${pc31.dim("show registry details for a template")}`);
10084
- out.push(` ${pc31.cyan("template onboard")} ${pc31.dim("<creator/slug>")} ${pc31.dim("install (or refresh) a template")}`);
10085
- out.push(` ${pc31.cyan("template list")} ${pc31.dim("show installed templates")}`);
10086
- out.push(` ${pc31.cyan("template remove")} ${pc31.dim("<creator/slug>")} ${pc31.dim("uninstall a template")}`);
11027
+ out.push(` ${pc36.cyan("template pack")} ${pc36.dim("bundle the current agent into a template")}`);
11028
+ out.push(` ${pc36.cyan("template publish")} ${pc36.dim("upload a template to the registry")}`);
11029
+ out.push(` ${pc36.cyan("template search")} ${pc36.dim("[query]")} ${pc36.dim("search the registry")}`);
11030
+ out.push(` ${pc36.cyan("template info")} ${pc36.dim("<creator/slug>")} ${pc36.dim("show registry details for a template")}`);
11031
+ out.push(` ${pc36.cyan("template onboard")} ${pc36.dim("<creator/slug>")} ${pc36.dim("install (or refresh) a template")}`);
11032
+ out.push(` ${pc36.cyan("template list")} ${pc36.dim("show installed templates")}`);
11033
+ out.push(` ${pc36.cyan("template remove")} ${pc36.dim("<creator/slug>")} ${pc36.dim("uninstall a template")}`);
10087
11034
  out.push("");
10088
11035
  out.push(divider("SKILLS"));
10089
11036
  out.push("");
10090
- out.push(` ${pc31.cyan("skill add")} ${pc31.dim("<source>")} ${pc31.dim("install a skill (github / git / brainbase)")}`);
10091
- out.push(` ${pc31.cyan("skill list")} ${pc31.dim("show locally installed skills + their source")}`);
10092
- out.push(` ${pc31.cyan("skill update")} ${pc31.dim("<slug>")} ${pc31.dim("re-fetch a skill from its recorded source")}`);
10093
- out.push(` ${pc31.cyan("skill remove")} ${pc31.dim("<slug>")} ${pc31.dim("uninstall a skill")}`);
10094
- out.push(` ${pc31.cyan("skill search")} ${pc31.dim("[query]")} ${pc31.dim("search the brainbase skill registry")}`);
10095
- out.push(` ${pc31.cyan("skill info")} ${pc31.dim("<creator/slug>")} ${pc31.dim("show registry details for a skill")}`);
10096
- out.push(` ${pc31.cyan("skill publish")} ${pc31.dim("[creator/slug][@v]")} ${pc31.dim("publish a local skill to the registry")}`);
11037
+ out.push(` ${pc36.cyan("skill add")} ${pc36.dim("<source>")} ${pc36.dim("install a skill (github / git / brainbase)")}`);
11038
+ out.push(` ${pc36.cyan("skill list")} ${pc36.dim("show locally installed skills + their source")}`);
11039
+ out.push(` ${pc36.cyan("skill update")} ${pc36.dim("<slug>")} ${pc36.dim("re-fetch a skill from its recorded source")}`);
11040
+ out.push(` ${pc36.cyan("skill remove")} ${pc36.dim("<slug>")} ${pc36.dim("uninstall a skill")}`);
11041
+ out.push(` ${pc36.cyan("skill search")} ${pc36.dim("[query]")} ${pc36.dim("search the brainbase skill registry")}`);
11042
+ out.push(` ${pc36.cyan("skill info")} ${pc36.dim("<creator/slug>")} ${pc36.dim("show registry details for a skill")}`);
11043
+ out.push(` ${pc36.cyan("skill publish")} ${pc36.dim("[creator/slug][@v]")} ${pc36.dim("publish a local skill to the registry")}`);
10097
11044
  out.push("");
10098
11045
  out.push(divider("CLI TOKENS"));
10099
11046
  out.push("");
10100
- out.push(` ${pc31.cyan("token create")} ${pc31.dim("issue a long-lived CLI key for CI / scripts")}`);
10101
- out.push(` ${pc31.cyan("token list")} ${pc31.dim("show your active tokens")}`);
10102
- out.push(` ${pc31.cyan("token revoke")} ${pc31.dim("<id>")} ${pc31.dim("revoke a token")}`);
11047
+ out.push(` ${pc36.cyan("token create")} ${pc36.dim("issue a long-lived CLI key for CI / scripts")}`);
11048
+ out.push(` ${pc36.cyan("token list")} ${pc36.dim("show your active tokens")}`);
11049
+ out.push(` ${pc36.cyan("token revoke")} ${pc36.dim("<id>")} ${pc36.dim("revoke a token")}`);
10103
11050
  out.push("");
10104
11051
  out.push(divider("FLAGS"));
10105
11052
  out.push("");
10106
- out.push(` ${pc31.dim("--harness <id>")} force harness for onboard / sync (e.g. claude-code)`);
10107
- out.push(` ${pc31.dim("--scope <s>")} force scope: global | project`);
10108
- out.push(` ${pc31.dim("--yes, -y")} skip confirmations / auto-overwrite`);
10109
- out.push(` ${pc31.dim("--agent <id>")} for link: attach this folder to an existing agent non-interactively`);
10110
- out.push(` ${pc31.dim("--no-tracking")} for link: skip routing LLM traffic through brainbase`);
10111
- out.push(` ${pc31.dim("--shell <sh|fish>")} for agent env: pick output format (auto-detected from $SHELL)`);
10112
- out.push(` ${pc31.dim("--all")} for template list: include installs from other folders`);
10113
- out.push(` ${pc31.dim("--web <url>")} for login: web app URL (default https://new.usekafka.com)`);
11053
+ out.push(` ${pc36.dim("--harness <id>")} force harness for onboard / sync (e.g. claude-code)`);
11054
+ out.push(` ${pc36.dim("--scope <s>")} force scope: global | project`);
11055
+ out.push(` ${pc36.dim("--yes, -y")} skip confirmations / auto-overwrite`);
11056
+ out.push(` ${pc36.dim("--agent <id>")} for link: attach this folder to an existing agent non-interactively`);
11057
+ out.push(` ${pc36.dim("--no-tracking")} for link: skip routing LLM traffic through brainbase`);
11058
+ out.push(` ${pc36.dim("--shell <sh|fish>")} for agent env: pick output format (auto-detected from $SHELL)`);
11059
+ out.push(` ${pc36.dim("--all")} for template list: include installs from other folders`);
11060
+ out.push(` ${pc36.dim("--web <url>")} for login: web app URL (default https://new.usekafka.com)`);
10114
11061
  out.push("");
10115
11062
  out.push(divider("ENV"));
10116
11063
  out.push("");
10117
- out.push(` ${pc31.dim("BRAINBASE_HOME")} override the local config dir (default ~/.brainbase)`);
10118
- out.push(` ${pc31.dim("BRAINBASE_WEB_URL")} override the web app URL used by login`);
10119
- out.push(` ${pc31.dim("BRAINBASE_API_URL")} override the API URL used by link / sync`);
10120
- out.push(` ${pc31.dim("BRAINBASE_REGISTRY_URL")} override the registry API URL`);
10121
- out.push(` ${pc31.dim("BRAINBASE_TOKEN")} long-lived CLI PAT (overrides token.json)`);
10122
- out.push(` ${pc31.dim("BRAINBASE_SKIP_AUTH")} bypass the auth gate for development`);
11064
+ out.push(` ${pc36.dim("BRAINBASE_HOME")} override the local config dir (default ~/.brainbase)`);
11065
+ out.push(` ${pc36.dim("BRAINBASE_WEB_URL")} override the web app URL used by login`);
11066
+ out.push(` ${pc36.dim("BRAINBASE_API_URL")} override the API URL used by link / sync`);
11067
+ out.push(` ${pc36.dim("BRAINBASE_REGISTRY_URL")} override the registry API URL`);
11068
+ out.push(` ${pc36.dim("BRAINBASE_TOKEN")} long-lived CLI PAT (overrides token.json)`);
11069
+ out.push(` ${pc36.dim("BRAINBASE_SKIP_AUTH")} bypass the auth gate for development`);
10123
11070
  out.push("");
10124
11071
  out.push(divider("HARNESSES"));
10125
11072
  out.push("");
10126
- out.push(` ${pc31.dim("•")} ${pc31.bold("claude-code")} ${pc31.dim("skills, mcps, agents, commands, instructions, files")}`);
10127
- out.push(` ${pc31.dim("•")} ${pc31.bold("codex")} ${pc31.dim("skills, mcps, commands, instructions, files")}`);
11073
+ out.push(` ${pc36.dim("•")} ${pc36.bold("claude-code")} ${pc36.dim("skills, mcps, agents, commands, instructions, files")}`);
11074
+ out.push(` ${pc36.dim("•")} ${pc36.bold("codex")} ${pc36.dim("skills, mcps, commands, instructions, files")}`);
10128
11075
  out.push("");
10129
11076
  console.log(out.join(`
10130
11077
  `));
@@ -10168,13 +11115,13 @@ async function requireAuth(cmd) {
10168
11115
  if (status.ok)
10169
11116
  return;
10170
11117
  console.error("");
10171
- console.error(` ${brandTint("◆")} ${pc31.bold("brainbase")}`);
11118
+ console.error(` ${brandTint("◆")} ${pc36.bold("brainbase")}`);
10172
11119
  console.error("");
10173
- console.error(` ${pc31.red("✗")} You need to sign in to use ${pc31.bold("brainbase " + cmd)}.`);
11120
+ console.error(` ${pc36.red("✗")} You need to sign in to use ${pc36.bold("brainbase " + cmd)}.`);
10174
11121
  if (status.reason)
10175
- console.error(` ${pc31.dim(status.reason)}`);
11122
+ console.error(` ${pc36.dim(status.reason)}`);
10176
11123
  console.error("");
10177
- console.error(` Run ${pc31.cyan("brainbase login")} to connect this device.`);
11124
+ console.error(` Run ${pc36.cyan("brainbase login")} to connect this device.`);
10178
11125
  console.error("");
10179
11126
  process2.exit(1);
10180
11127
  }
@@ -10184,7 +11131,7 @@ async function main() {
10184
11131
  const rawCwd = process2.cwd();
10185
11132
  const cwd = (() => {
10186
11133
  try {
10187
- return fs40.realpathSync(rawCwd);
11134
+ return fs44.realpathSync(rawCwd);
10188
11135
  } catch {
10189
11136
  return rawCwd;
10190
11137
  }
@@ -10211,6 +11158,7 @@ async function main() {
10211
11158
  const agentFlag = getFlag(argv, "--agent");
10212
11159
  const shellFlag = getFlag(argv, "--shell");
10213
11160
  const noTracking = hasFlag(argv, "--no-tracking");
11161
+ const graphOnlyFlag = hasFlag(argv, "--graph-only");
10214
11162
  const nameFlag = getFlag(argv, "--name");
10215
11163
  const taglineFlag = getFlag(argv, "--tagline");
10216
11164
  const orgIdFlag = getFlag(argv, "--org");
@@ -10295,6 +11243,18 @@ async function main() {
10295
11243
  });
10296
11244
  break;
10297
11245
  }
11246
+ case "orchestration":
11247
+ case "orch": {
11248
+ const sub = argv.shift();
11249
+ await runOrchestration(cwd, sub, argv, {
11250
+ yes,
11251
+ harness,
11252
+ orgId: orgIdFlag,
11253
+ teamId: teamIdFlag,
11254
+ graphOnly: graphOnlyFlag
11255
+ });
11256
+ break;
11257
+ }
10298
11258
  case "publish": {
10299
11259
  await runPublish(cwd, { yes });
10300
11260
  break;
@@ -10310,7 +11270,7 @@ async function main() {
10310
11270
  process2.exit(1);
10311
11271
  }
10312
11272
  } catch (err) {
10313
- console.error(pc31.red(`
11273
+ console.error(pc36.red(`
10314
11274
  ${err.message}`));
10315
11275
  if (process2.env.BRAINBASE_DEBUG)
10316
11276
  console.error(err.stack);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainbase-labs/cli",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
5
5
  "type": "module",
6
6
  "bin": {