@brainbase-labs/cli 0.52.0 → 0.53.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 (2) hide show
  1. package/dist/index.js +58 -17
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -36091,7 +36091,7 @@ function padStart(s, n) {
36091
36091
  // package.json
36092
36092
  var package_default = {
36093
36093
  name: "@brainbase-labs/cli",
36094
- version: "0.52.0",
36094
+ version: "0.53.0",
36095
36095
  description: "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
36096
36096
  type: "module",
36097
36097
  bin: {
@@ -67885,9 +67885,6 @@ function automationErrorEnvelope(command, error) {
67885
67885
  };
67886
67886
  }
67887
67887
 
67888
- // src/core/interfaces-api.ts
67889
- import { randomUUID as randomUUID2 } from "node:crypto";
67890
-
67891
67888
  // src/core/fastapi-detail.ts
67892
67889
  function record(value) {
67893
67890
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -67900,6 +67897,9 @@ function rejectsBodyField(body, field) {
67900
67897
  return fastApiDetails(body).some((entry) => entry.type === "extra_forbidden" && Array.isArray(entry.loc) && entry.loc[0] === "body" && entry.loc[1] === field);
67901
67898
  }
67902
67899
 
67900
+ // src/core/interfaces-api.ts
67901
+ import { randomUUID as randomUUID2 } from "node:crypto";
67902
+
67903
67903
  // src/ui/terminal-safe.ts
67904
67904
  function sanitizeLabel(s3) {
67905
67905
  return s3.replace(/\u001b\][\s\S]*?(?:\u0007|\u001b\\|$)/g, "").replace(/\u001b\[[\u0030-\u003f]*[\u0020-\u002f]*[\u0040-\u007e]?/g, "").replace(/\u001b[\s\S]?/g, "").replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g, "");
@@ -68450,6 +68450,7 @@ var REASONS = {
68450
68450
  "versioning is paused": "versioning_paused",
68451
68451
  "restore already in progress": "restore_in_progress"
68452
68452
  };
68453
+ var PIN_CONFLICTS = new Set(["pin_changed", "not_pinned"]);
68453
68454
  function sentence(value) {
68454
68455
  const trimmed = value.trim();
68455
68456
  const capital = trimmed.charAt(0).toUpperCase() + trimmed.slice(1);
@@ -68474,7 +68475,9 @@ function toVersionsError(error, agentId) {
68474
68475
  const detail = record2(body?.detail);
68475
68476
  const serverText = text2(body?.detail) ?? text2(detail?.message) ?? text2(body?.error);
68476
68477
  const said = clean(serverText ?? error.message);
68477
- const reason = serverText ? REASONS[serverText.toLowerCase()] ?? null : null;
68478
+ const detailCode = text2(detail?.code);
68479
+ const pinReason = status === 409 && detailCode && PIN_CONFLICTS.has(detailCode) ? detailCode : null;
68480
+ const reason = pinReason ?? (serverText ? REASONS[serverText.toLowerCase()] ?? null : null);
68478
68481
  const details = {
68479
68482
  status,
68480
68483
  retry_after: retrySeconds(error.retryAfter),
@@ -68510,7 +68513,7 @@ function toVersionsError(error, agentId) {
68510
68513
  break;
68511
68514
  case 409:
68512
68515
  code = "conflict";
68513
- message = reason === "versioning_paused" ? "Versioning is paused on Brainbase, so nothing was changed. Try again later." : reason === "restore_in_progress" ? "A restore of this agent is already running. Wait for it to finish, then check `brainbase agent versions list`." : sentence(said);
68516
+ message = reason === "versioning_paused" ? "Versioning is paused on Brainbase, so nothing was changed. Try again later." : reason === "restore_in_progress" ? "A restore of this agent is already running. Wait for it to finish, then check `brainbase agent versions list`." : reason === "pin_changed" ? `Someone changed this agent's pin after it was read, so nothing was changed. Run \`brainbase agent versions list --agent ${sanitizeLabel(agentId)}\` to see the pin now, then try again.` : reason === "not_pinned" ? "The agent is not pinned, so there was nothing to unpin." : sentence(said);
68514
68517
  break;
68515
68518
  case 502:
68516
68519
  if (details.sections?.some((section) => section.error !== null)) {
@@ -68638,11 +68641,21 @@ var agentVersionsApi = {
68638
68641
  throw new AgentVersionsError("No answer came back from the restore in time, so whether it happened is unknown. " + `Run \`brainbase agent versions list --agent ${sanitizeLabel(agentId)}\` and look for a new rollback version before trying again.`, "outcome_unknown", null, NO_DETAILS);
68639
68642
  }
68640
68643
  },
68641
- pin(agentId, versionId) {
68642
- return write2(agentId, pinPath(agentId), "PUT", { version_id: versionId });
68644
+ async pin(agentId, versionId, currentPinId) {
68645
+ try {
68646
+ return await write2(agentId, pinPath(agentId), "PUT", {
68647
+ version_id: versionId,
68648
+ expected_version_id: currentPinId
68649
+ });
68650
+ } catch (error) {
68651
+ if (!(error instanceof ApiError && error.status === 422 && rejectsBodyField(error.body, "expected_version_id"))) {
68652
+ throw error;
68653
+ }
68654
+ return write2(agentId, pinPath(agentId), "PUT", { version_id: versionId });
68655
+ }
68643
68656
  },
68644
- unpin(agentId) {
68645
- return write2(agentId, pinPath(agentId), "DELETE");
68657
+ unpin(agentId, pinnedVersionId) {
68658
+ return write2(agentId, `${pinPath(agentId)}${masListQuery({ expected_version_id: pinnedVersionId })}`, "DELETE");
68646
68659
  }
68647
68660
  };
68648
68661
 
@@ -81814,6 +81827,7 @@ function renderLines(lines, indent) {
81814
81827
  var import_picocolors46 = __toESM(require_picocolors(), 1);
81815
81828
  var PIN_NOTE = "New tasks will run this version's instructions, entrypoint, installed skills, tools, playbooks, harness and default model. " + "Secrets, credentials, machine settings, permission rules, connections and skill contents always stay current. " + "Tasks already running keep the version they started with.";
81816
81829
  var UNPIN_NOTE = "New tasks will run the agent's current configuration again. Tasks already running keep the version they started with.";
81830
+ var NOT_PINNED_NOTE = "This agent is not pinned, so nothing changed.";
81817
81831
  var PULL_NOTE = "This folder is now behind the cloud. Run `brainbase agent pull` to bring it up to date.";
81818
81832
  function pinnedNote(pin) {
81819
81833
  return `This agent is pinned to ${pinName(pin)}, so new tasks keep running that version, not the restored configuration, until you run \`brainbase agent versions unpin\`.`;
@@ -81995,18 +82009,18 @@ async function pin(ctx) {
81995
82009
  const agentId = resolveAgentId(ctx.cwd, parsed);
81996
82010
  const action = `Pinning agent ${sanitizeLabel(agentId)} to ${sanitizeLabel(ref)}`;
81997
82011
  requireConsentPossible(yes, json3, action, ctx.deps);
81998
- const { version: version2, capturePending } = await agentVersionsApi.resolve(agentId, ref);
82012
+ const { version: version2, pin: current, capturePending } = await agentVersionsApi.resolve(agentId, ref);
81999
82013
  const accepted = await confirmChange({
82000
82014
  yes,
82001
82015
  json: json3,
82002
82016
  action,
82003
- consequence: PIN_NOTE,
82017
+ consequence: current ? `${PIN_NOTE} It is pinned to ${pinName(current)} now.` : PIN_NOTE,
82004
82018
  question: `Pin the agent to ${versionName(version2)}?`,
82005
82019
  deps: ctx.deps
82006
82020
  });
82007
82021
  if (!accepted)
82008
82022
  return;
82009
- const result2 = await agentVersionsApi.pin(agentId, version2.id);
82023
+ const result2 = await agentVersionsApi.pin(agentId, version2.id, current?.version_id ?? null);
82010
82024
  const notes = [...pendingNotes(capturePending), PIN_NOTE];
82011
82025
  if (json3) {
82012
82026
  printJson({ ...result2, notes });
@@ -82018,6 +82032,24 @@ async function pin(ctx) {
82018
82032
  printNotes(notes);
82019
82033
  });
82020
82034
  }
82035
+ function printNotPinned(json3) {
82036
+ if (json3) {
82037
+ printJson({ pinned_version_id: null, pinned_at: null, pinned_by: null, changed: false, notes: [NOT_PINNED_NOTE] });
82038
+ return;
82039
+ }
82040
+ console.log(`
82041
+ ${NOT_PINNED_NOTE}
82042
+ `);
82043
+ }
82044
+ async function unpinIfStill(agentId, versionId) {
82045
+ try {
82046
+ return await agentVersionsApi.unpin(agentId, versionId);
82047
+ } catch (error2) {
82048
+ if (error2 instanceof AgentVersionsError && error2.reason === "not_pinned")
82049
+ return null;
82050
+ throw error2;
82051
+ }
82052
+ }
82021
82053
  async function unpin(ctx) {
82022
82054
  let json3 = wantsJson(ctx.args);
82023
82055
  await runReportingErrors(() => json3, async () => {
@@ -82028,24 +82060,33 @@ async function unpin(ctx) {
82028
82060
  const agentId = resolveAgentId(ctx.cwd, parsed);
82029
82061
  const action = `Unpinning agent ${sanitizeLabel(agentId)}`;
82030
82062
  requireConsentPossible(yes, json3, action, ctx.deps);
82063
+ const { pin: current } = await agentVersionsApi.latest(agentId);
82064
+ if (!current) {
82065
+ printNotPinned(json3);
82066
+ return;
82067
+ }
82031
82068
  const accepted = await confirmChange({
82032
82069
  yes,
82033
82070
  json: json3,
82034
82071
  action,
82035
82072
  consequence: UNPIN_NOTE,
82036
- question: "Unpin the agent?",
82073
+ question: `Unpin the agent from ${pinName(current)}?`,
82037
82074
  deps: ctx.deps
82038
82075
  });
82039
82076
  if (!accepted)
82040
82077
  return;
82041
- const result2 = await agentVersionsApi.unpin(agentId);
82078
+ const result2 = await unpinIfStill(agentId, current.version_id);
82079
+ if (!result2) {
82080
+ printNotPinned(json3);
82081
+ return;
82082
+ }
82042
82083
  const notes = [UNPIN_NOTE];
82043
82084
  if (json3) {
82044
- printJson({ ...result2, notes });
82085
+ printJson({ ...result2, changed: true, notes });
82045
82086
  return;
82046
82087
  }
82047
82088
  console.log(`
82048
- ${import_picocolors46.default.green("✓")} Unpinned
82089
+ ${import_picocolors46.default.green("✓")} Unpinned from ${pinName(current)}
82049
82090
  `);
82050
82091
  printNotes(notes);
82051
82092
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainbase-labs/cli",
3
- "version": "0.52.0",
3
+ "version": "0.53.0",
4
4
  "description": "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
5
5
  "type": "module",
6
6
  "bin": {