@brainbase-labs/cli 0.10.1 → 0.11.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.
Files changed (2) hide show
  1. package/dist/index.js +103 -73
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -29449,7 +29449,7 @@ function padStart(s, n) {
29449
29449
  // package.json
29450
29450
  var package_default = {
29451
29451
  name: "@brainbase-labs/cli",
29452
- version: "0.10.1",
29452
+ version: "0.11.1",
29453
29453
  description: "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
29454
29454
  type: "module",
29455
29455
  bin: {
@@ -43741,6 +43741,33 @@ function removeInstalledRecord(name, scope, cwd2) {
43741
43741
  // src/core/registry-remote.ts
43742
43742
  import fs22 from "node:fs";
43743
43743
 
43744
+ // src/core/api-error-message.ts
43745
+ function apiErrorMessage(body, status) {
43746
+ if (body && typeof body === "object") {
43747
+ const obj = body;
43748
+ for (const key2 of ["error", "message", "detail"]) {
43749
+ const v2 = obj[key2];
43750
+ if (typeof v2 === "string" && v2.trim())
43751
+ return v2.trim();
43752
+ if (Array.isArray(v2)) {
43753
+ const msgs = v2.map((item) => {
43754
+ if (typeof item === "string" && item.trim())
43755
+ return item.trim();
43756
+ if (item && typeof item === "object" && typeof item.msg === "string") {
43757
+ return item.msg;
43758
+ }
43759
+ return null;
43760
+ }).filter((m2) => !!m2);
43761
+ if (msgs.length)
43762
+ return msgs.join("; ");
43763
+ }
43764
+ }
43765
+ } else if (typeof body === "string" && body.trim()) {
43766
+ return body.trim();
43767
+ }
43768
+ return `HTTP ${status}`;
43769
+ }
43770
+
43744
43771
  // src/core/api.ts
43745
43772
  var DEFAULT_API_BASE = "https://kafka-llm-service.onrender.com";
43746
43773
 
@@ -43800,8 +43827,7 @@ async function request(pathname, init = {}) {
43800
43827
  body = text2 ? JSON.parse(text2) : null;
43801
43828
  } catch {}
43802
43829
  if (!res.ok) {
43803
- const msg = (body && typeof body === "object" && "error" in body ? String(body.error) : null) ?? (body && typeof body === "object" && "message" in body ? String(body.message) : null) ?? `HTTP ${res.status}`;
43804
- throw new ApiError(msg, res.status, body);
43830
+ throw new ApiError(apiErrorMessage(body, res.status), res.status, body);
43805
43831
  }
43806
43832
  return body;
43807
43833
  }
@@ -52792,6 +52818,14 @@ function diffSecrets(local, cloud) {
52792
52818
  };
52793
52819
  }
52794
52820
 
52821
+ // src/core/entrypoint-gate.ts
52822
+ function entrypointExecutionAllowed(opts) {
52823
+ if (opts.flag)
52824
+ return true;
52825
+ const v3 = (opts.env.BRAINBASE_RUN_ENTRYPOINT ?? "").trim().toLowerCase();
52826
+ return v3 === "1" || v3 === "true";
52827
+ }
52828
+
52795
52829
  // src/cli/agent-pull.ts
52796
52830
  async function runAgentPull(cwd2, args) {
52797
52831
  banner("agent pull — bring cloud changes into this folder");
@@ -53007,7 +53041,7 @@ async function runAgentPull(cwd2, args) {
53007
53041
  };
53008
53042
  writeSyncState(cwd2, newState);
53009
53043
  await pullSecrets(cwd2, agentId);
53010
- await runEntrypointIfPresent(cwd2, yaml);
53044
+ await runEntrypointIfPresent(cwd2, yaml, entrypointExecutionAllowed({ flag: args.runEntrypoint, env: process.env }));
53011
53045
  $e(`Pulled ${cloudAgent.name} at revision ${cloud.revision}.`);
53012
53046
  } finally {
53013
53047
  try {
@@ -53326,7 +53360,7 @@ function buildLinkFromAgent(agent, harness, prev) {
53326
53360
  tracking: prev?.tracking
53327
53361
  };
53328
53362
  }
53329
- async function runEntrypointIfPresent(cwd2, manifest) {
53363
+ async function runEntrypointIfPresent(cwd2, manifest, execute) {
53330
53364
  if (!manifest.entrypoint)
53331
53365
  return;
53332
53366
  const body = resolveEntrypoint(cwd2, manifest);
@@ -53340,6 +53374,10 @@ async function runEntrypointIfPresent(cwd2, manifest) {
53340
53374
  try {
53341
53375
  fs45.chmodSync(scriptPath, 493);
53342
53376
  } catch {}
53377
+ if (!execute) {
53378
+ f2.info(`Agent has an entrypoint — written to ${import_picocolors25.default.dim(path48.relative(cwd2, scriptPath))}, not executed. ` + `Run it with ${import_picocolors25.default.cyan("brainbase run bash .brainbase/entrypoint.sh")} or re-pull with ${import_picocolors25.default.cyan("--run-entrypoint")}. Sandboxes run it automatically.`);
53379
+ return;
53380
+ }
53343
53381
  f2.info(`Running entrypoint ${import_picocolors25.default.dim(`(${path48.relative(cwd2, scriptPath)})`)}`);
53344
53382
  const secrets = readLocalSecrets(cwd2);
53345
53383
  const env3 = { ...process.env, ...secrets };
@@ -53477,42 +53515,6 @@ async function buildOutgoingComponents(cwd2, manifest, cloud, resolvedVersions)
53477
53515
  }
53478
53516
  });
53479
53517
  }
53480
- const seenPlaybookSlugs = new Set;
53481
- for (const entry of manifest.playbooks ?? []) {
53482
- if (entry.content.text !== undefined && entry.content.file !== undefined) {
53483
- f2.error(`Playbook ${import_picocolors26.default.bold(entry.title)} sets both ${import_picocolors26.default.cyan("text")} and ${import_picocolors26.default.cyan("file")} — pick one.`);
53484
- return null;
53485
- }
53486
- const body = resolvePlaybookContent(cwd2, entry);
53487
- if (body === null) {
53488
- if (entry.content.file) {
53489
- f2.error(`Playbook ${import_picocolors26.default.bold(entry.title)} content file ${import_picocolors26.default.bold(entry.content.file)} not found.`);
53490
- } else {
53491
- f2.error(`Playbook ${import_picocolors26.default.bold(entry.title)} content is empty.`);
53492
- }
53493
- return null;
53494
- }
53495
- const slug = slugifyPlaybookTitle(entry.title);
53496
- if (seenPlaybookSlugs.has(slug)) {
53497
- f2.error(`Two playbooks resolve to the same slug ${import_picocolors26.default.bold(slug)} (from title ${import_picocolors26.default.bold(entry.title)}). Pick distinct titles.`);
53498
- return null;
53499
- }
53500
- seenPlaybookSlugs.add(slug);
53501
- const wireBody = assemblePlaybookBody(entry, body);
53502
- const fileName = `${slug}.md`;
53503
- const fileHash2 = hashString(wireBody);
53504
- out.push({
53505
- type: "playbook",
53506
- slug,
53507
- description: entry.description,
53508
- hash: componentHashFromFileHashes([fileHash2]),
53509
- files: [{ path: fileName, content: wireBody, hash: fileHash2 }],
53510
- meta: {
53511
- title: entry.title,
53512
- ...entry.description ? { description: entry.description } : {}
53513
- }
53514
- });
53515
- }
53516
53518
  for (const entry of manifest.mcp ?? []) {
53517
53519
  if (!entry.url && !entry.command) {
53518
53520
  f2.error(`MCP ${import_picocolors26.default.bold(entry.name)} needs either ${import_picocolors26.default.cyan("url")} or ${import_picocolors26.default.cyan("command")}.`);
@@ -53540,23 +53542,6 @@ async function buildOutgoingComponents(cwd2, manifest, cloud, resolvedVersions)
53540
53542
  }
53541
53543
  return out;
53542
53544
  }
53543
- function assemblePlaybookBody(entry, body) {
53544
- if (/^---\s*\n/.test(body))
53545
- return body;
53546
- const lines = ["---", `title: ${yamlScalar(entry.title)}`];
53547
- if (entry.description) {
53548
- lines.push(`description: ${yamlScalar(entry.description)}`);
53549
- }
53550
- lines.push("---", "");
53551
- return lines.join(`
53552
- `) + body.replace(/^\n+/, "");
53553
- }
53554
- function yamlScalar(s3) {
53555
- if (!/[\n":#&*!|>'%@`{}[\]]/.test(s3) && s3.trim() === s3 && s3 !== "") {
53556
- return s3;
53557
- }
53558
- return JSON.stringify(s3);
53559
- }
53560
53545
 
53561
53546
  // src/core/registry-skill-updates.ts
53562
53547
  function parseSemver2(v3) {
@@ -53652,29 +53637,56 @@ async function runAgentPush(cwd2, args) {
53652
53637
  lock: lock?.components ?? [],
53653
53638
  cloud: cloud.components
53654
53639
  });
53655
- const unpinned = new Map;
53640
+ const registryRefs = new Map;
53656
53641
  for (const entry of manifest.skills) {
53657
53642
  try {
53658
53643
  const parsed = parseSkillSource2(entry.source);
53659
- if (parsed.kind === "registry" && !parsed.version && parsed.creator) {
53660
- unpinned.set(`${parsed.creator}/${parsed.slug}`, {
53644
+ if (parsed.kind === "registry" && parsed.creator) {
53645
+ registryRefs.set(`${parsed.creator}/${parsed.slug}`, {
53661
53646
  creator: parsed.creator,
53662
- slug: parsed.slug
53647
+ slug: parsed.slug,
53648
+ version: parsed.version
53663
53649
  });
53664
53650
  }
53665
53651
  } catch {}
53666
53652
  }
53667
53653
  const latestByName = new Map;
53668
- if (unpinned.size > 0) {
53669
- await Promise.all([...unpinned.entries()].map(async ([name, ref]) => {
53654
+ const notInRegistry = new Set;
53655
+ if (registryRefs.size > 0) {
53656
+ await Promise.all([...registryRefs.entries()].map(async ([name, ref]) => {
53670
53657
  try {
53671
53658
  const pkg = await skillsApi.getPackage(ref.creator, ref.slug);
53672
53659
  if (pkg.latest_version?.version) {
53673
53660
  latestByName.set(name, pkg.latest_version.version);
53674
53661
  }
53675
- } catch {}
53662
+ } catch (err) {
53663
+ if (err instanceof ApiError && err.status === 404) {
53664
+ notInRegistry.add(name);
53665
+ }
53666
+ }
53676
53667
  }));
53677
53668
  }
53669
+ const unresolvable = [...notInRegistry].filter((name) => {
53670
+ const ref = registryRefs.get(name);
53671
+ const componentSlug = registrySkillComponentSlug({
53672
+ kind: "registry",
53673
+ creator: ref.creator,
53674
+ slug: ref.slug
53675
+ });
53676
+ return !cloud.components.some((c2) => c2.type === "skill" && c2.slug === componentSlug);
53677
+ });
53678
+ if (unresolvable.length > 0) {
53679
+ for (const name of unresolvable) {
53680
+ f2.error(`Skill ${import_picocolors27.default.bold(name)} isn't in the registry (not published, or private to another owner).`);
53681
+ }
53682
+ const noun = unresolvable.length === 1 ? "it" : "each one";
53683
+ f2.info(`Publish ${noun} first, then push again:`);
53684
+ for (const name of unresolvable) {
53685
+ const version = registryRefs.get(name)?.version ?? "0.1.0";
53686
+ f2.info(` ${import_picocolors27.default.cyan(`brainbase skill publish ./path/to/skill --name ${name} --skill-version ${version} --yes`)}`);
53687
+ }
53688
+ return;
53689
+ }
53678
53690
  const skillUpdates = planRegistrySkillUpdates(manifest.skills, cloud.components, latestByName);
53679
53691
  const resolvedVersions = new Map(skillUpdates.map((u2) => [u2.componentSlug, u2.latest]));
53680
53692
  for (const u2 of skillUpdates) {
@@ -53711,7 +53723,7 @@ async function runAgentPush(cwd2, args) {
53711
53723
  const entrypointChanged = resolvedEntrypoint !== undefined && (resolvedEntrypoint ?? "").trim() !== (lock?.agentMeta?.entrypoint ?? "").trim();
53712
53724
  for (const r2 of rows) {
53713
53725
  if (r2.type !== "instruction" && r2.type !== "skill" && r2.type !== "mcp" && r2.type !== "playbook") {
53714
- f2.error(`Component ${fmtType(r2.type)} ${import_picocolors27.default.bold(r2.slug)} can't be pushed yet — the server accepts instructions, skills, mcps, and playbooks in this version.`);
53726
+ f2.error(`Component ${fmtType(r2.type)} ${import_picocolors27.default.bold(r2.slug)} can't be pushed yet — the server accepts instructions, skills, and mcps in this version.`);
53715
53727
  return;
53716
53728
  }
53717
53729
  }
@@ -53730,7 +53742,13 @@ async function runAgentPush(cwd2, args) {
53730
53742
  const toSend = [];
53731
53743
  const conflicts = [];
53732
53744
  const upstreamOnly = [];
53745
+ const unpushablePlaybooks = [];
53733
53746
  for (const r2 of rows) {
53747
+ const localish = r2.status === "modified-local" || r2.status === "added-only-local" || r2.status === "removed-local" || r2.status === "modified-both";
53748
+ if (r2.type === "playbook" && localish) {
53749
+ unpushablePlaybooks.push(r2);
53750
+ continue;
53751
+ }
53734
53752
  switch (r2.status) {
53735
53753
  case "modified-local":
53736
53754
  case "added-only-local":
@@ -53747,6 +53765,9 @@ async function runAgentPush(cwd2, args) {
53747
53765
  break;
53748
53766
  }
53749
53767
  }
53768
+ if (unpushablePlaybooks.length > 0) {
53769
+ f2.warn(`Local playbook change${unpushablePlaybooks.length === 1 ? "" : "s"} can't be pushed yet (the server doesn't accept playbooks) — edit playbooks in the UI instead: ${unpushablePlaybooks.map((r2) => import_picocolors27.default.bold(r2.slug)).join(", ")}`);
53770
+ }
53750
53771
  if (!meta.localChanged && !entrypointChanged && toSend.length === 0 && conflicts.length === 0) {
53751
53772
  f2.info("Nothing to push — local is in sync with the cloud.");
53752
53773
  return;
@@ -53861,6 +53882,8 @@ async function runAgentPush(cwd2, args) {
53861
53882
  }
53862
53883
  const localHashByKey = new Map;
53863
53884
  for (const lc of localComponents) {
53885
+ if (lc.type === "playbook")
53886
+ continue;
53864
53887
  if (lc.hash)
53865
53888
  localHashByKey.set(`${lc.type}/${lc.slug}`, lc.hash);
53866
53889
  }
@@ -54386,6 +54409,9 @@ async function runAgentCreate(cwd2, args) {
54386
54409
  writeLink(cwd2, link2);
54387
54410
  manifest = readManifest(cwd2);
54388
54411
  let updatedCloud = null;
54412
+ if ((manifest.playbooks ?? []).length > 0) {
54413
+ f2.info(`Playbooks aren't pushable yet — skipping ${manifest.playbooks.length}. Create playbooks in the UI instead.`);
54414
+ }
54389
54415
  const hasContent = !!manifest.instructions || manifest.skills.length > 0 || (manifest.mcp ?? []).length > 0;
54390
54416
  if (hasContent) {
54391
54417
  const outgoing = await buildOutgoingComponents(cwd2, manifest, null);
@@ -54756,14 +54782,14 @@ function copyDirRecursive(src, dest) {
54756
54782
  }
54757
54783
  }
54758
54784
  function assembleFrontmatter(title, description) {
54759
- const lines = ["---", `title: ${yamlScalar2(title)}`];
54785
+ const lines = ["---", `title: ${yamlScalar(title)}`];
54760
54786
  if (description)
54761
- lines.push(`description: ${yamlScalar2(description)}`);
54787
+ lines.push(`description: ${yamlScalar(description)}`);
54762
54788
  lines.push("---", "");
54763
54789
  return lines.join(`
54764
54790
  `);
54765
54791
  }
54766
- function yamlScalar2(s3) {
54792
+ function yamlScalar(s3) {
54767
54793
  if (!/[\n":#&*!|>'%@`{}[\]]/.test(s3) && s3.trim() === s3 && s3 !== "")
54768
54794
  return s3;
54769
54795
  return JSON.stringify(s3);
@@ -54811,7 +54837,8 @@ async function runAgent(cwd2, sub, args, opts) {
54811
54837
  yes: opts.yes,
54812
54838
  scope: opts.scope,
54813
54839
  agentIdArg: args[0],
54814
- force: opts.force
54840
+ force: opts.force,
54841
+ runEntrypoint: opts.runEntrypoint
54815
54842
  });
54816
54843
  return;
54817
54844
  case "push":
@@ -54849,7 +54876,7 @@ function printHelp() {
54849
54876
  out.push(` ${import_picocolors32.default.bold("brainbase agent")} ${import_picocolors32.default.dim("<sub> [options]")}`);
54850
54877
  out.push("");
54851
54878
  out.push(` ${import_picocolors32.default.cyan("create")} ${import_picocolors32.default.dim("claim an unclaimed brainbase.agent.yaml and create the cloud agent")}`);
54852
- out.push(` ${import_picocolors32.default.cyan("pull")} ${import_picocolors32.default.dim("[<id>]")} ${import_picocolors32.default.dim("apply cloud changes into this folder — pass <id> to switch (--force to override)")}`);
54879
+ out.push(` ${import_picocolors32.default.cyan("pull")} ${import_picocolors32.default.dim("[<id>]")} ${import_picocolors32.default.dim("apply cloud changes into this folder — pass <id> to switch (--force to override); --run-entrypoint to also execute the agent entrypoint")}`);
54853
54880
  out.push(` ${import_picocolors32.default.cyan("push")} ${import_picocolors32.default.dim("send local changes to the cloud")}`);
54854
54881
  out.push(` ${import_picocolors32.default.cyan("unpack")} ${import_picocolors32.default.dim("install the claimed agent into a harness layout (--harness to override)")}`);
54855
54882
  out.push(` ${import_picocolors32.default.cyan("status")} ${import_picocolors32.default.dim("show what would push and what would pull")}`);
@@ -56782,7 +56809,7 @@ function help() {
56782
56809
  out.push(divider("LINKED AGENT"));
56783
56810
  out.push("");
56784
56811
  out.push(` ${import_picocolors41.default.cyan("agent create")} ${import_picocolors41.default.dim("claim an unclaimed brainbase.agent.yaml and create the cloud agent")}`);
56785
- out.push(` ${import_picocolors41.default.cyan("agent pull")} ${import_picocolors41.default.dim("[<id>]")} ${import_picocolors41.default.dim("bring cloud changes into this folder (--force to override)")}`);
56812
+ out.push(` ${import_picocolors41.default.cyan("agent pull")} ${import_picocolors41.default.dim("[<id>]")} ${import_picocolors41.default.dim("bring cloud changes into this folder (--force to override; --run-entrypoint to also execute the agent entrypoint)")}`);
56786
56813
  out.push(` ${import_picocolors41.default.cyan("agent push")} ${import_picocolors41.default.dim("send local changes to the cloud")}`);
56787
56814
  out.push(` ${import_picocolors41.default.cyan("agent unpack")} ${import_picocolors41.default.dim("install the claimed agent into a harness layout")}`);
56788
56815
  out.push(` ${import_picocolors41.default.cyan("link")} ${import_picocolors41.default.dim("attach this folder to an existing agent")}`);
@@ -56846,6 +56873,7 @@ function help() {
56846
56873
  out.push(` ${import_picocolors41.default.dim("BRAINBASE_TOKEN")} long-lived CLI PAT (overrides token.json)`);
56847
56874
  out.push(` ${import_picocolors41.default.dim("BRAINBASE_SKIP_AUTH")} bypass the auth gate for development`);
56848
56875
  out.push(` ${import_picocolors41.default.dim("BRAINBASE_NON_INTERACTIVE")} force non-interactive mode — skip/auto-default prompts (CI & agents)`);
56876
+ out.push(` ${import_picocolors41.default.dim("BRAINBASE_RUN_ENTRYPOINT")} =1 → agent pull executes the agent entrypoint (sandbox boots; or pass --run-entrypoint)`);
56849
56877
  out.push("");
56850
56878
  out.push(divider("HARNESSES"));
56851
56879
  out.push("");
@@ -56950,6 +56978,7 @@ async function main() {
56950
56978
  const noTracking = hasFlag2(argv, "--no-tracking");
56951
56979
  const track = hasFlag2(argv, "--track");
56952
56980
  const forceFlag = hasFlag2(argv, "--force");
56981
+ const runEntrypointFlag = hasFlag2(argv, "--run-entrypoint");
56953
56982
  const graphOnlyFlag = hasFlag2(argv, "--graph-only");
56954
56983
  const nameFlag = getFlag(argv, "--name");
56955
56984
  const skillVersionFlag = getFlag(argv, "--skill-version");
@@ -57041,7 +57070,8 @@ async function main() {
57041
57070
  teamId: teamIdFlag,
57042
57071
  noTracking,
57043
57072
  track,
57044
- force: forceFlag
57073
+ force: forceFlag,
57074
+ runEntrypoint: runEntrypointFlag
57045
57075
  });
57046
57076
  break;
57047
57077
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainbase-labs/cli",
3
- "version": "0.10.1",
3
+ "version": "0.11.1",
4
4
  "description": "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
5
5
  "type": "module",
6
6
  "bin": {