@brainbase-labs/cli 0.16.0 → 0.16.2

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 (3) hide show
  1. package/README.md +6 -0
  2. package/dist/index.js +432 -166
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -22,6 +22,12 @@ brainbase template onboard <creator/slug> # install or refresh a template
22
22
 
23
23
  Run `brainbase help` to see every command.
24
24
 
25
+ ## Development
26
+
27
+ Use Bun 1.3.10 when building this repository. The generated
28
+ `dist/index.js` is committed and byte-checked in CI, and Bun patch releases
29
+ can produce different bundle output.
30
+
25
31
  ## Agent runtime configuration
26
32
 
27
33
  `brainbase.agent.yaml` can declare the provider and default model used by
package/dist/index.js CHANGED
@@ -16651,7 +16651,7 @@ Learn more about this warning here: https://reactjs.org/link/legacy-context`, so
16651
16651
 
16652
16652
  ` + ("" + errorBoundaryMessage);
16653
16653
  console["error"](combinedMessage);
16654
- }
16654
+ } else {}
16655
16655
  } catch (e2) {
16656
16656
  setTimeout(function() {
16657
16657
  throw e2;
@@ -36008,7 +36008,7 @@ function padStart(s, n) {
36008
36008
  // package.json
36009
36009
  var package_default = {
36010
36010
  name: "@brainbase-labs/cli",
36011
- version: "0.16.0",
36011
+ version: "0.16.2",
36012
36012
  description: "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
36013
36013
  type: "module",
36014
36014
  bin: {
@@ -36407,6 +36407,17 @@ function writeJson(p, data) {
36407
36407
  fs2.writeFileSync(p, JSON.stringify(data, null, 2) + `
36408
36408
  `);
36409
36409
  }
36410
+ function writeJsonAtomic(p, data) {
36411
+ ensureDir(path2.dirname(p));
36412
+ const temporaryPath = `${p}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`;
36413
+ try {
36414
+ fs2.writeFileSync(temporaryPath, JSON.stringify(data, null, 2) + `
36415
+ `, { flag: "wx", mode: 384 });
36416
+ fs2.renameSync(temporaryPath, p);
36417
+ } finally {
36418
+ fs2.rmSync(temporaryPath, { force: true });
36419
+ }
36420
+ }
36410
36421
  function listDirs(dir) {
36411
36422
  if (!exists(dir))
36412
36423
  return [];
@@ -40874,7 +40885,14 @@ import fs7 from "node:fs";
40874
40885
  // src/core/auth.ts
40875
40886
  import path8 from "node:path";
40876
40887
  import fs6 from "node:fs";
40888
+ import { randomUUID } from "node:crypto";
40877
40889
  var AUTH_FILE = path8.join(BRAINBASE_HOME, "auth.json");
40890
+ var AUTH_LOCK_FILE = `${AUTH_FILE}.lock`;
40891
+ var AUTH_LOCK_TIMEOUT_MS = 5000;
40892
+ var REFRESH_LOCK_FILE = `${AUTH_FILE}.refresh.lock`;
40893
+ var REFRESH_LOCK_TIMEOUT_MS = 1e4;
40894
+ var REFRESH_REQUEST_TIMEOUT_MS = 5000;
40895
+ var lockWaiter = new Int32Array(new SharedArrayBuffer(4));
40878
40896
  var AuthSessionSchema = exports_external.object({
40879
40897
  schemaVersion: exports_external.literal(1),
40880
40898
  access_token: exports_external.string(),
@@ -40883,6 +40901,7 @@ var AuthSessionSchema = exports_external.object({
40883
40901
  user_id: exports_external.string(),
40884
40902
  email: exports_external.string().optional(),
40885
40903
  server: exports_external.string().optional(),
40904
+ control_plane_url: exports_external.string().optional(),
40886
40905
  supabase_url: exports_external.string().optional(),
40887
40906
  supabase_anon_key: exports_external.string().optional(),
40888
40907
  authedAt: exports_external.string()
@@ -40896,22 +40915,99 @@ function readAuth() {
40896
40915
  return null;
40897
40916
  }
40898
40917
  }
40899
- function writeAuth(s) {
40918
+ function acquireLock(lockFile, timeoutMs) {
40900
40919
  ensureDir(BRAINBASE_HOME);
40901
- writeJson(AUTH_FILE, s);
40920
+ const deadline = Date.now() + timeoutMs;
40921
+ const lockId = randomUUID();
40922
+ const owner = `${process.pid}:${lockId}`;
40923
+ const candidate = `${lockFile}.${lockId}.tmp`;
40924
+ while (true) {
40925
+ try {
40926
+ fs6.writeFileSync(candidate, owner, {
40927
+ encoding: "utf8",
40928
+ mode: 384,
40929
+ flag: "wx"
40930
+ });
40931
+ fs6.linkSync(candidate, lockFile);
40932
+ fs6.rmSync(candidate, { force: true });
40933
+ return () => {
40934
+ try {
40935
+ if (fs6.readFileSync(lockFile, "utf8") === owner) {
40936
+ fs6.rmSync(lockFile, { force: true });
40937
+ }
40938
+ } catch {}
40939
+ };
40940
+ } catch (error) {
40941
+ fs6.rmSync(candidate, { force: true });
40942
+ if (error.code !== "EEXIST")
40943
+ throw error;
40944
+ if (Date.now() >= deadline)
40945
+ return null;
40946
+ Atomics.wait(lockWaiter, 0, 0, 10);
40947
+ }
40948
+ }
40949
+ }
40950
+ function acquireAuthLock() {
40951
+ const release = acquireLock(AUTH_LOCK_FILE, AUTH_LOCK_TIMEOUT_MS);
40952
+ if (!release) {
40953
+ throw new Error(`Timed out waiting to update CLI authentication; if no other brainbase process is running, remove ${AUTH_LOCK_FILE}`);
40954
+ }
40955
+ return release;
40956
+ }
40957
+ function withAuthLock(operation) {
40958
+ const release = acquireAuthLock();
40959
+ try {
40960
+ return operation();
40961
+ } finally {
40962
+ release();
40963
+ }
40964
+ }
40965
+ function writeAuthUnlocked(s) {
40966
+ writeJsonAtomic(AUTH_FILE, s);
40902
40967
  try {
40903
40968
  fs6.chmodSync(AUTH_FILE, 384);
40904
40969
  } catch {}
40905
40970
  }
40971
+ function writeAuth(s) {
40972
+ withAuthLock(() => writeAuthUnlocked(s));
40973
+ }
40906
40974
  function clearAuth() {
40907
- if (exists(AUTH_FILE))
40908
- fs6.rmSync(AUTH_FILE);
40975
+ withAuthLock(() => {
40976
+ if (exists(AUTH_FILE))
40977
+ fs6.rmSync(AUTH_FILE);
40978
+ });
40909
40979
  }
40910
40980
  function isExpired(session) {
40911
40981
  if (!session.expires_at)
40912
40982
  return false;
40913
40983
  const now = Math.floor(Date.now() / 1000);
40914
- return session.expires_at <= now - 5;
40984
+ return session.expires_at <= now + 5;
40985
+ }
40986
+ function isNearExpiry(session, marginSeconds = 60) {
40987
+ if (!session.expires_at)
40988
+ return false;
40989
+ const now = Math.floor(Date.now() / 1000);
40990
+ return session.expires_at <= now + marginSeconds;
40991
+ }
40992
+ function isAuthValid(session) {
40993
+ return !!session && !isExpired(session);
40994
+ }
40995
+ function isSameAuthIdentity(a3, b3) {
40996
+ return a3.user_id === b3.user_id && (a3.server ?? null) === (b3.server ?? null) && (a3.control_plane_url ?? null) === (b3.control_plane_url ?? null) && (a3.supabase_url ?? null) === (b3.supabase_url ?? null);
40997
+ }
40998
+
40999
+ class AuthSessionChangedError extends Error {
41000
+ constructor() {
41001
+ super("CLI authentication changed while this command was running; retry it");
41002
+ this.name = "AuthSessionChangedError";
41003
+ }
41004
+ }
41005
+
41006
+ class AuthRefreshLockTimeoutError extends Error {
41007
+ constructor() {
41008
+ super(`Timed out waiting to refresh CLI authentication; retry, or if no other brainbase process is running, remove ${REFRESH_LOCK_FILE}`);
41009
+ this.name = "AuthRefreshLockTimeoutError";
41010
+ }
40915
41011
  }
40916
41012
  function authStatus() {
40917
41013
  const s = readAuth();
@@ -40921,52 +41017,157 @@ function authStatus() {
40921
41017
  return { ok: false, session: s, reason: "session expired" };
40922
41018
  return { ok: true, session: s };
40923
41019
  }
40924
- async function tryRefreshSession() {
40925
- const session = readAuth();
40926
- if (!session)
40927
- return null;
41020
+ var refreshInFlight = null;
41021
+ function validatedRefreshResult(expectedSession, result) {
41022
+ const current = readAuth();
41023
+ if (!current || !isSameAuthIdentity(current, expectedSession) || result && !isSameAuthIdentity(result, expectedSession)) {
41024
+ throw new AuthSessionChangedError;
41025
+ }
41026
+ if (result)
41027
+ return result;
41028
+ return isAuthValid(current) ? current : null;
41029
+ }
41030
+ function adoptRefreshedLineage(session) {
41031
+ const current = readAuth();
41032
+ if (!current || !isSameAuthIdentity(current, session)) {
41033
+ throw new AuthSessionChangedError;
41034
+ }
41035
+ const sameRefreshLineage = current.access_token === session.access_token && current.refresh_token === session.refresh_token;
41036
+ if (sameRefreshLineage)
41037
+ return;
41038
+ return isAuthValid(current) ? current : null;
41039
+ }
41040
+ async function refreshSession(session) {
40928
41041
  if (!session.refresh_token)
40929
41042
  return null;
40930
41043
  if (!session.supabase_url || !session.supabase_anon_key)
40931
41044
  return null;
40932
- const url = `${session.supabase_url.replace(/\/+$/, "")}/auth/v1/token?grant_type=refresh_token`;
40933
- let res;
41045
+ const releaseRefreshLock = acquireLock(REFRESH_LOCK_FILE, REFRESH_LOCK_TIMEOUT_MS);
41046
+ if (!releaseRefreshLock) {
41047
+ const adopted = adoptRefreshedLineage(session);
41048
+ if (adopted !== undefined)
41049
+ return adopted;
41050
+ if (isAuthValid(session))
41051
+ return session;
41052
+ throw new AuthRefreshLockTimeoutError;
41053
+ }
40934
41054
  try {
40935
- res = await fetch(url, {
40936
- method: "POST",
40937
- headers: {
40938
- "Content-Type": "application/json",
40939
- apikey: session.supabase_anon_key,
40940
- Authorization: `Bearer ${session.supabase_anon_key}`
40941
- },
40942
- body: JSON.stringify({ refresh_token: session.refresh_token })
41055
+ const adopted = adoptRefreshedLineage(session);
41056
+ if (adopted !== undefined)
41057
+ return adopted;
41058
+ const url = `${session.supabase_url.replace(/\/+$/, "")}/auth/v1/token?grant_type=refresh_token`;
41059
+ let res;
41060
+ try {
41061
+ res = await fetch(url, {
41062
+ method: "POST",
41063
+ signal: AbortSignal.timeout(REFRESH_REQUEST_TIMEOUT_MS),
41064
+ headers: {
41065
+ "Content-Type": "application/json",
41066
+ apikey: session.supabase_anon_key,
41067
+ Authorization: `Bearer ${session.supabase_anon_key}`
41068
+ },
41069
+ body: JSON.stringify({ refresh_token: session.refresh_token })
41070
+ });
41071
+ } catch {
41072
+ return null;
41073
+ }
41074
+ if (!res.ok)
41075
+ return null;
41076
+ let data;
41077
+ try {
41078
+ data = await res.json();
41079
+ } catch {
41080
+ return null;
41081
+ }
41082
+ if (!data.access_token)
41083
+ return null;
41084
+ const now = Math.floor(Date.now() / 1000);
41085
+ const expires_at = data.expires_at ?? (data.expires_in ? now + data.expires_in : undefined);
41086
+ const updated = {
41087
+ ...session,
41088
+ access_token: data.access_token,
41089
+ refresh_token: data.refresh_token ?? session.refresh_token,
41090
+ expires_at,
41091
+ user_id: data.user?.id ?? session.user_id,
41092
+ email: data.user?.email ?? session.email,
41093
+ authedAt: new Date().toISOString()
41094
+ };
41095
+ if (!isSameAuthIdentity(updated, session)) {
41096
+ throw new AuthSessionChangedError;
41097
+ }
41098
+ return withAuthLock(() => {
41099
+ const adopted2 = adoptRefreshedLineage(session);
41100
+ if (adopted2 !== undefined)
41101
+ return adopted2;
41102
+ writeAuthUnlocked(updated);
41103
+ return updated;
40943
41104
  });
40944
- } catch {
40945
- return null;
41105
+ } finally {
41106
+ releaseRefreshLock();
40946
41107
  }
40947
- if (!res.ok)
41108
+ }
41109
+ async function tryRefreshSession(expectedSession = readAuth()) {
41110
+ if (!expectedSession)
40948
41111
  return null;
40949
- let data;
41112
+ if (refreshInFlight) {
41113
+ if (!isSameAuthIdentity(refreshInFlight.session, expectedSession)) {
41114
+ throw new AuthSessionChangedError;
41115
+ }
41116
+ const result = await refreshInFlight.promise;
41117
+ return validatedRefreshResult(expectedSession, result);
41118
+ }
41119
+ const current = readAuth();
41120
+ if (!current || !isSameAuthIdentity(current, expectedSession)) {
41121
+ throw new AuthSessionChangedError;
41122
+ }
41123
+ const refresh = refreshSession(current);
41124
+ refreshInFlight = { session: current, promise: refresh };
40950
41125
  try {
40951
- data = await res.json();
40952
- } catch {
40953
- return null;
41126
+ const result = await refresh;
41127
+ return validatedRefreshResult(expectedSession, result);
41128
+ } finally {
41129
+ if (refreshInFlight?.promise === refresh)
41130
+ refreshInFlight = null;
41131
+ }
41132
+ }
41133
+ async function refreshSessionAfterUnauthorized(rejectedSession) {
41134
+ const current = readAuth();
41135
+ if (!current || !isSameAuthIdentity(current, rejectedSession)) {
41136
+ throw new AuthSessionChangedError;
41137
+ }
41138
+ if (current.access_token !== rejectedSession.access_token && isAuthValid(current)) {
41139
+ return current;
40954
41140
  }
40955
- if (!data.access_token)
41141
+ const refreshed = await tryRefreshSession(rejectedSession);
41142
+ if (refreshed) {
41143
+ if (!isSameAuthIdentity(refreshed, rejectedSession)) {
41144
+ throw new AuthSessionChangedError;
41145
+ }
41146
+ return refreshed;
41147
+ }
41148
+ const latest = readAuth();
41149
+ if (!latest || !isSameAuthIdentity(latest, rejectedSession)) {
41150
+ throw new AuthSessionChangedError;
41151
+ }
41152
+ if (latest.access_token !== rejectedSession.access_token && isAuthValid(latest)) {
41153
+ return latest;
41154
+ }
41155
+ return null;
41156
+ }
41157
+ async function ensureFreshSession() {
41158
+ const s = readAuth();
41159
+ if (!s)
40956
41160
  return null;
40957
- const now = Math.floor(Date.now() / 1000);
40958
- const expires_at = data.expires_at ?? (data.expires_in ? now + data.expires_in : undefined);
40959
- const updated = {
40960
- ...session,
40961
- access_token: data.access_token,
40962
- refresh_token: data.refresh_token ?? session.refresh_token,
40963
- expires_at,
40964
- user_id: data.user?.id ?? session.user_id,
40965
- email: data.user?.email ?? session.email,
40966
- authedAt: new Date().toISOString()
40967
- };
40968
- writeAuth(updated);
40969
- return updated;
41161
+ if (!isExpired(s) && !isNearExpiry(s))
41162
+ return s;
41163
+ const refreshed = await tryRefreshSession(s);
41164
+ if (refreshed)
41165
+ return refreshed;
41166
+ const current = readAuth();
41167
+ if (current && isSameAuthIdentity(current, s) && isAuthValid(current)) {
41168
+ return current;
41169
+ }
41170
+ return null;
40970
41171
  }
40971
41172
 
40972
41173
  // src/core/skill-marker-write.ts
@@ -47245,6 +47446,7 @@ var HARNESS_ALIASES = {
47245
47446
  "claude-code-cloud": "claude-code",
47246
47447
  codex_cloud: "codex",
47247
47448
  "codex-cloud": "codex",
47449
+ qwen: "qwen-code",
47248
47450
  qwen_acp: "qwen-code",
47249
47451
  "qwen-acp": "qwen-code",
47250
47452
  qwen_cloud: "qwen-code",
@@ -53363,19 +53565,27 @@ class ApiError extends Error {
53363
53565
  this.name = "ApiError";
53364
53566
  }
53365
53567
  }
53366
- function apiBase(_session) {
53367
- const controlPlane = process.env.BRAINBASE_CONTROL_PLANE_URL;
53568
+ function normalizeControlPlaneUrl(url) {
53569
+ const normalized = url?.trim().replace(/\/+$/, "");
53570
+ return normalized || DEFAULT_CONTROL_PLANE_BASE;
53571
+ }
53572
+ function controlPlaneBaseUrl(session) {
53573
+ const controlPlane = process.env.BRAINBASE_CONTROL_PLANE_URL?.trim();
53368
53574
  if (controlPlane) {
53369
- return `${controlPlane.replace(/\/+$/, "")}/v2/cli`;
53575
+ return normalizeControlPlaneUrl(controlPlane);
53370
53576
  }
53371
- const legacyApi = process.env.BRAINBASE_API_URL;
53577
+ const legacyApi = process.env.BRAINBASE_API_URL?.trim();
53372
53578
  if (legacyApi) {
53373
- return `${legacyApi.replace(/\/+$/, "")}/api/cli`;
53579
+ return legacyApi.replace(/\/+$/, "");
53374
53580
  }
53375
- return `${DEFAULT_CONTROL_PLANE_BASE}/v2/cli`;
53581
+ return normalizeControlPlaneUrl(session?.control_plane_url);
53582
+ }
53583
+ function apiBase(session) {
53584
+ const suffix = usesLegacyControlPlane() ? "/api/cli" : "/v2/cli";
53585
+ return `${controlPlaneBaseUrl(session)}${suffix}`;
53376
53586
  }
53377
53587
  function usesLegacyControlPlane() {
53378
- return !process.env.BRAINBASE_CONTROL_PLANE_URL && !!process.env.BRAINBASE_API_URL;
53588
+ return !process.env.BRAINBASE_CONTROL_PLANE_URL?.trim() && !!process.env.BRAINBASE_API_URL?.trim();
53379
53589
  }
53380
53590
  function legacyScheduleError() {
53381
53591
  return new ApiError("Schedule-trigger writes require the MAS control plane. Set BRAINBASE_CONTROL_PLANE_URL or unset the legacy BRAINBASE_API_URL override.", 400);
@@ -53383,38 +53593,58 @@ function legacyScheduleError() {
53383
53593
  function legacyAgentConfigError() {
53384
53594
  return new ApiError("Declarative machine/model config requires the MAS control plane. Set BRAINBASE_CONTROL_PLANE_URL or unset the legacy BRAINBASE_API_URL override.", 400);
53385
53595
  }
53386
- function requireSession() {
53387
- const status = authStatus();
53388
- if (!status.ok || !status.session) {
53389
- throw new ApiError(status.reason ?? "not logged in", 401);
53390
- }
53391
- return status.session;
53392
- }
53393
- function resolveCredential() {
53596
+ async function resolveCredential() {
53394
53597
  const envToken = process.env.BRAINBASE_TOKEN;
53395
53598
  if (envToken && envToken.trim()) {
53396
- return { bearer: envToken.trim(), session: authStatus().session };
53599
+ return {
53600
+ bearer: envToken.trim(),
53601
+ session: null,
53602
+ source: "env_pat"
53603
+ };
53604
+ }
53605
+ const session = await ensureFreshSession();
53606
+ if (session) {
53607
+ return {
53608
+ bearer: session.access_token,
53609
+ session,
53610
+ source: "session"
53611
+ };
53397
53612
  }
53398
- const session = requireSession();
53399
- return { bearer: session.access_token, session };
53613
+ const status = authStatus();
53614
+ throw new ApiError(status.ok ? "CLI authentication changed while this command was starting; retry it" : status.reason ?? "not logged in", 401);
53400
53615
  }
53401
- async function request(pathname, init = {}) {
53402
- const { bearer, session } = resolveCredential();
53403
- const url = `${apiBase(session)}${pathname}`;
53404
- const headers = {
53405
- Authorization: `Bearer ${bearer}`,
53406
- Accept: "application/json",
53407
- ...init.headers
53408
- };
53409
- if (init.body && !headers["Content-Type"]) {
53410
- headers["Content-Type"] = "application/json";
53616
+ async function sendRequest(url, init, bearer) {
53617
+ const headers = new Headers(init.headers);
53618
+ if (!headers.has("Authorization")) {
53619
+ headers.set("Authorization", `Bearer ${bearer}`);
53620
+ }
53621
+ if (!headers.has("Accept"))
53622
+ headers.set("Accept", "application/json");
53623
+ if (init.body && !headers.has("Content-Type")) {
53624
+ headers.set("Content-Type", "application/json");
53411
53625
  }
53412
- let res;
53413
53626
  try {
53414
- res = await fetch(url, { ...init, headers });
53627
+ return await fetch(url, { ...init, headers });
53415
53628
  } catch (err) {
53416
53629
  throw new ApiError(`Network error: ${err.message}`);
53417
53630
  }
53631
+ }
53632
+ async function sendWithAuthRetry(session, send) {
53633
+ const res = await send();
53634
+ if (res.status !== 401 || !session)
53635
+ return res;
53636
+ const refreshed = await refreshSessionAfterUnauthorized(session);
53637
+ if (!refreshed || refreshed.access_token === session.access_token) {
53638
+ return res;
53639
+ }
53640
+ if (res.body)
53641
+ res.body.cancel().catch(() => {});
53642
+ return await send(refreshed);
53643
+ }
53644
+ async function request(pathname, init = {}) {
53645
+ const credential = await resolveCredential();
53646
+ const retrySession = credential.source === "session" && !new Headers(init.headers).has("Authorization") ? credential.session : null;
53647
+ const res = await sendWithAuthRetry(retrySession, (refreshed) => sendRequest(`${apiBase(refreshed ?? credential.session)}${pathname}`, init, refreshed?.access_token ?? credential.bearer));
53418
53648
  const text2 = await res.text();
53419
53649
  let body = text2;
53420
53650
  try {
@@ -53531,6 +53761,9 @@ function proxyBaseUrl(session) {
53531
53761
  const envOverride = process.env.BRAINBASE_PROXY_URL || process.env.BRAINBASE_API_URL;
53532
53762
  if (envOverride)
53533
53763
  return envOverride.replace(/\/+$/, "");
53764
+ if (process.env.BRAINBASE_TOKEN?.trim()) {
53765
+ return DEFAULT_PROXY_BASE.replace(/\/+$/, "");
53766
+ }
53534
53767
  if (session?.server)
53535
53768
  return session.server.replace(/\/+$/, "");
53536
53769
  return DEFAULT_PROXY_BASE.replace(/\/+$/, "");
@@ -53608,61 +53841,89 @@ function registryHost() {
53608
53841
  const env3 = process.env.BRAINBASE_REGISTRY_URL || process.env.BRAINBASE_API_URL;
53609
53842
  if (env3)
53610
53843
  return env3.replace(/\/+$/, "");
53844
+ if (process.env.BRAINBASE_TOKEN?.trim())
53845
+ return DEFAULT_BASE;
53611
53846
  const s = readAuth();
53612
53847
  if (s?.server)
53613
53848
  return s.server.replace(/\/+$/, "");
53614
53849
  return DEFAULT_BASE.replace(/\/+$/, "");
53615
53850
  }
53616
- function authHeaders() {
53617
- const envToken = process.env.BRAINBASE_TOKEN;
53618
- if (envToken) {
53619
- return {
53620
- headers: {
53621
- Authorization: `Bearer ${envToken}`,
53622
- "X-Auth-Source": "pat"
53623
- },
53624
- session: null
53625
- };
53851
+ function patAuth(token) {
53852
+ return {
53853
+ headers: {
53854
+ Authorization: `Bearer ${token}`,
53855
+ "X-Auth-Source": "pat"
53856
+ },
53857
+ session: null
53858
+ };
53859
+ }
53860
+ function jwtAuth(session) {
53861
+ return {
53862
+ headers: {
53863
+ Authorization: `Bearer ${session.access_token}`,
53864
+ "X-Auth-Source": "jwt"
53865
+ },
53866
+ session
53867
+ };
53868
+ }
53869
+ async function resolveAuth() {
53870
+ const envToken = process.env.BRAINBASE_TOKEN?.trim();
53871
+ if (envToken)
53872
+ return patAuth(envToken);
53873
+ let freshSession;
53874
+ try {
53875
+ freshSession = await ensureFreshSession();
53876
+ } catch (error) {
53877
+ if (!(error instanceof AuthRefreshLockTimeoutError))
53878
+ throw error;
53879
+ const pat2 = readToken();
53880
+ if (pat2)
53881
+ return patAuth(pat2.token);
53882
+ throw error;
53626
53883
  }
53884
+ if (freshSession)
53885
+ return jwtAuth(freshSession);
53627
53886
  const status = authStatus();
53628
- if (status.ok && status.session) {
53629
- return {
53630
- headers: {
53631
- Authorization: `Bearer ${status.session.access_token}`,
53632
- "X-Auth-Source": "jwt"
53633
- },
53634
- session: status.session
53635
- };
53636
- }
53637
53887
  const pat = readToken();
53638
- if (pat) {
53639
- return {
53640
- headers: {
53641
- Authorization: `Bearer ${pat.token}`,
53642
- "X-Auth-Source": "pat"
53643
- },
53644
- session: null
53645
- };
53646
- }
53647
- throw new ApiError(status.reason ?? "not logged in", 401);
53888
+ if (pat)
53889
+ return patAuth(pat.token);
53890
+ throw new ApiError(status.ok ? "CLI authentication changed while this command was starting; retry it" : status.reason ?? "not logged in", 401);
53648
53891
  }
53649
- async function jsonRequest(pathname, init = {}) {
53650
- const { headers, session } = authHeaders();
53892
+ function mergeHeaders(auth, overrides) {
53893
+ const headers = new Headers(auth);
53894
+ new Headers(overrides).forEach((value, key2) => headers.set(key2, value));
53895
+ return headers;
53896
+ }
53897
+ async function sendRequest2(pathname, init, session) {
53651
53898
  const url = `${baseUrl(session)}${pathname}`;
53652
- const merged = {
53653
- ...headers,
53654
- Accept: "application/json",
53655
- ...init.headers ?? {}
53656
- };
53657
- if (init.body && !merged["Content-Type"] && typeof init.body === "string") {
53658
- merged["Content-Type"] = "application/json";
53659
- }
53660
- let res;
53661
53899
  try {
53662
- res = await fetch(url, { ...init, headers: merged });
53900
+ return await fetch(url, init);
53663
53901
  } catch (err) {
53664
53902
  throw new ApiError(`Network error: ${err.message}`);
53665
53903
  }
53904
+ }
53905
+ async function sendAuthenticatedRequest(pathname, init, auth) {
53906
+ return await sendRequest2(pathname, {
53907
+ ...init,
53908
+ headers: mergeHeaders(auth.headers, init.headers)
53909
+ }, auth.session);
53910
+ }
53911
+ async function registryFetch(pathname, init = {}) {
53912
+ if (new Headers(init.headers).has("Authorization")) {
53913
+ return await sendRequest2(pathname, init, readAuth());
53914
+ }
53915
+ const auth = await resolveAuth();
53916
+ const retrySession = auth.headers["X-Auth-Source"] === "jwt" ? auth.session : null;
53917
+ return await sendWithAuthRetry(retrySession, (refreshed) => sendAuthenticatedRequest(pathname, init, refreshed ? jwtAuth(refreshed) : auth));
53918
+ }
53919
+ async function jsonRequest(pathname, init = {}) {
53920
+ const headers = new Headers(init.headers);
53921
+ if (!headers.has("Accept"))
53922
+ headers.set("Accept", "application/json");
53923
+ if (init.body && !headers.has("Content-Type") && typeof init.body === "string") {
53924
+ headers.set("Content-Type", "application/json");
53925
+ }
53926
+ const res = await registryFetch(pathname, { ...init, headers });
53666
53927
  const text2 = await res.text();
53667
53928
  let body = text2;
53668
53929
  try {
@@ -53720,9 +53981,8 @@ var registryApi = {
53720
53981
  return jsonRequest(`/v1/registry/templates/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}`);
53721
53982
  },
53722
53983
  async getFile(creator, slug, version, relPath) {
53723
- const { headers, session } = authHeaders();
53724
- const url = `${baseUrl(session)}/v1/registry/templates/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}/files/${relPath}`;
53725
- const res = await fetch(url, { headers });
53984
+ const pathname = `/v1/registry/templates/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}/files/${relPath}`;
53985
+ const res = await registryFetch(pathname);
53726
53986
  if (!res.ok) {
53727
53987
  throw new ApiError(`HTTP ${res.status} reading file ${relPath}`, res.status);
53728
53988
  }
@@ -53730,9 +53990,8 @@ var registryApi = {
53730
53990
  return Buffer.from(ab);
53731
53991
  },
53732
53992
  async downloadTarball(creator, slug, version, destPath) {
53733
- const { headers, session } = authHeaders();
53734
- const url = `${baseUrl(session)}/v1/registry/templates/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}/tarball`;
53735
- const res = await fetch(url, { headers });
53993
+ const pathname = `/v1/registry/templates/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}/tarball`;
53994
+ const res = await registryFetch(pathname);
53736
53995
  if (!res.ok || !res.body) {
53737
53996
  throw new ApiError(`HTTP ${res.status} downloading tarball`, res.status);
53738
53997
  }
@@ -53758,8 +54017,7 @@ var registryApi = {
53758
54017
  });
53759
54018
  },
53760
54019
  async publishVersion(creator, slug, input) {
53761
- const { headers, session } = authHeaders();
53762
- const url = `${baseUrl(session)}/v1/registry/templates/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}/versions`;
54020
+ const pathname = `/v1/registry/templates/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}/versions`;
53763
54021
  const form = new FormData;
53764
54022
  form.set("version", input.version);
53765
54023
  form.set("source_harness", input.sourceHarness);
@@ -53767,16 +54025,11 @@ var registryApi = {
53767
54025
  form.set("manifest", new Blob([input.manifestJson], { type: "application/json" }), "manifest.json");
53768
54026
  const bytes = fs45.readFileSync(input.bundlePath);
53769
54027
  form.set("bundle", new Blob([bytes], { type: "application/gzip" }), "bundle.tgz");
53770
- let res;
53771
- try {
53772
- res = await fetch(url, {
53773
- method: "POST",
53774
- headers: { ...headers, Accept: "application/json" },
53775
- body: form
53776
- });
53777
- } catch (err) {
53778
- throw new ApiError(`Network error: ${err.message}`);
53779
- }
54028
+ const res = await registryFetch(pathname, {
54029
+ method: "POST",
54030
+ headers: { Accept: "application/json" },
54031
+ body: form
54032
+ });
53780
54033
  const text2 = await res.text();
53781
54034
  let body = text2;
53782
54035
  try {
@@ -58375,9 +58628,8 @@ var skillsApi = {
58375
58628
  return jsonRequest(`/v1/skill-registry/skills/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}`);
58376
58629
  },
58377
58630
  async getFile(creator, slug, version, relPath) {
58378
- const { headers, session } = authHeaders();
58379
- const url = `${baseUrl(session)}/v1/skill-registry/skills/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}/files/${relPath}`;
58380
- const res = await fetch(url, { headers });
58631
+ const pathname = `/v1/skill-registry/skills/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}/files/${relPath}`;
58632
+ const res = await registryFetch(pathname);
58381
58633
  if (!res.ok) {
58382
58634
  throw new ApiError(`HTTP ${res.status} reading file ${relPath}`, res.status);
58383
58635
  }
@@ -58385,9 +58637,8 @@ var skillsApi = {
58385
58637
  return Buffer.from(ab);
58386
58638
  },
58387
58639
  async downloadTarball(creator, slug, version, destPath) {
58388
- const { headers, session } = authHeaders();
58389
- const url = `${baseUrl(session)}/v1/skill-registry/skills/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}/tarball`;
58390
- const res = await fetch(url, { headers });
58640
+ const pathname = `/v1/skill-registry/skills/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}/tarball`;
58641
+ const res = await registryFetch(pathname);
58391
58642
  if (!res.ok || !res.body) {
58392
58643
  throw new ApiError(`HTTP ${res.status} downloading tarball`, res.status);
58393
58644
  }
@@ -58413,8 +58664,7 @@ var skillsApi = {
58413
58664
  });
58414
58665
  },
58415
58666
  async publishVersion(creator, slug, input) {
58416
- const { headers, session } = authHeaders();
58417
- const url = `${baseUrl(session)}/v1/skill-registry/skills/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}/versions`;
58667
+ const pathname = `/v1/skill-registry/skills/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}/versions`;
58418
58668
  const form = new FormData;
58419
58669
  form.set("version", input.version);
58420
58670
  form.set("source_harness", input.sourceHarness);
@@ -58422,16 +58672,11 @@ var skillsApi = {
58422
58672
  form.set("manifest", new Blob([input.manifestJson], { type: "application/json" }), "manifest.json");
58423
58673
  const bytes = fs52.readFileSync(input.bundlePath);
58424
58674
  form.set("bundle", new Blob([bytes], { type: "application/gzip" }), "bundle.tgz");
58425
- let res;
58426
- try {
58427
- res = await fetch(url, {
58428
- method: "POST",
58429
- headers: { ...headers, Accept: "application/json" },
58430
- body: form
58431
- });
58432
- } catch (err) {
58433
- throw new ApiError(`Network error: ${err.message}`);
58434
- }
58675
+ const res = await registryFetch(pathname, {
58676
+ method: "POST",
58677
+ headers: { Accept: "application/json" },
58678
+ body: form
58679
+ });
58435
58680
  const text2 = await res.text();
58436
58681
  let body = text2;
58437
58682
  try {
@@ -60434,24 +60679,24 @@ function WelcomeCard(props) {
60434
60679
  /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
60435
60680
  flexDirection: "column",
60436
60681
  children: [
60437
- props.server && /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
60682
+ props.controlPlaneUrl && /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
60438
60683
  children: [
60439
60684
  /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
60440
- width: 10,
60685
+ width: 15,
60441
60686
  children: /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
60442
60687
  dimColor: true,
60443
- children: "server"
60688
+ children: "control plane"
60444
60689
  }, undefined, false, undefined, this)
60445
60690
  }, undefined, false, undefined, this),
60446
60691
  /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
60447
- children: props.server
60692
+ children: props.controlPlaneUrl
60448
60693
  }, undefined, false, undefined, this)
60449
60694
  ]
60450
60695
  }, undefined, true, undefined, this),
60451
60696
  expiresText && /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
60452
60697
  children: [
60453
60698
  /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
60454
- width: 10,
60699
+ width: 15,
60455
60700
  children: /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
60456
60701
  dimColor: true,
60457
60702
  children: "expires"
@@ -60484,8 +60729,12 @@ async function showWelcomeCard(props) {
60484
60729
  }
60485
60730
 
60486
60731
  // src/cli/login.ts
60487
- var DEFAULT_WEB_URL = "https://new.usekafka.com";
60732
+ var DEFAULT_WEB_URL = "https://app.brainbaselabs.com";
60488
60733
  var LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
60734
+ function resolveLoginWebBase(web, envWeb = process.env.BRAINBASE_WEB_URL) {
60735
+ const override = web?.trim() || envWeb?.trim() || DEFAULT_WEB_URL;
60736
+ return override.replace(/\/+$/, "");
60737
+ }
60489
60738
  function openInBrowser(url) {
60490
60739
  const platform2 = process.platform;
60491
60740
  const cmd = platform2 === "darwin" ? "open" : platform2 === "win32" ? "cmd" : "xdg-open";
@@ -60519,7 +60768,7 @@ async function readBody(req) {
60519
60768
  }
60520
60769
  async function runLogin(_cwd, args) {
60521
60770
  banner("login — connect this device to brainbase");
60522
- const webBase = (args.web ?? process.env.BRAINBASE_WEB_URL ?? DEFAULT_WEB_URL).replace(/\/+$/, "");
60771
+ const webBase = resolveLoginWebBase(args.web);
60523
60772
  const expectedState = randomBytes(16).toString("hex");
60524
60773
  const port = await getFreePort();
60525
60774
  const callback = `http://127.0.0.1:${port}/cb`;
@@ -60568,6 +60817,7 @@ async function runLogin(_cwd, args) {
60568
60817
  user_id: payload.user_id,
60569
60818
  email: payload.email,
60570
60819
  server: payload.server,
60820
+ control_plane_url: normalizeControlPlaneUrl(payload.control_plane_url),
60571
60821
  supabase_url: payload.supabase_url,
60572
60822
  supabase_anon_key: payload.supabase_anon_key,
60573
60823
  authedAt: new Date().toISOString()
@@ -60609,7 +60859,7 @@ async function runLogin(_cwd, args) {
60609
60859
  if (session) {
60610
60860
  await showWelcomeCard({
60611
60861
  email: session.email ?? session.user_id,
60612
- server: session.server,
60862
+ controlPlaneUrl: controlPlaneBaseUrl(session),
60613
60863
  expiresAt: session.expires_at,
60614
60864
  hint: "brainbase template pack — bundle your first agent"
60615
60865
  });
@@ -60630,7 +60880,7 @@ function IdentityCard(props) {
60630
60880
  tone: props.tone ?? "info",
60631
60881
  children: [
60632
60882
  props.email && /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(Box_default, {
60633
- marginBottom: props.server || props.expiresAt ? 1 : 0,
60883
+ marginBottom: props.controlPlaneUrl || props.expiresAt ? 1 : 0,
60634
60884
  children: /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(Text, {
60635
60885
  bold: true,
60636
60886
  children: props.email
@@ -60639,24 +60889,24 @@ function IdentityCard(props) {
60639
60889
  /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(Box_default, {
60640
60890
  flexDirection: "column",
60641
60891
  children: [
60642
- props.server && /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(Box_default, {
60892
+ props.controlPlaneUrl && /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(Box_default, {
60643
60893
  children: [
60644
60894
  /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(Box_default, {
60645
- width: 10,
60895
+ width: 15,
60646
60896
  children: /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(Text, {
60647
60897
  dimColor: true,
60648
- children: "server"
60898
+ children: "control plane"
60649
60899
  }, undefined, false, undefined, this)
60650
60900
  }, undefined, false, undefined, this),
60651
60901
  /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(Text, {
60652
- children: props.server
60902
+ children: props.controlPlaneUrl
60653
60903
  }, undefined, false, undefined, this)
60654
60904
  ]
60655
60905
  }, undefined, true, undefined, this),
60656
60906
  props.expiresAt && /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(Box_default, {
60657
60907
  children: [
60658
60908
  /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(Box_default, {
60659
- width: 10,
60909
+ width: 15,
60660
60910
  children: /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(Text, {
60661
60911
  dimColor: true,
60662
60912
  children: "expires"
@@ -60720,7 +60970,7 @@ async function runWhoami() {
60720
60970
  title: "WHOAMI",
60721
60971
  tone: "ok",
60722
60972
  email: session.email ?? session.user_id,
60723
- server: session.server,
60973
+ controlPlaneUrl: controlPlaneBaseUrl(session),
60724
60974
  expiresAt: session.expires_at
60725
60975
  });
60726
60976
  }
@@ -75796,6 +76046,12 @@ var PROTECTED = new Set([
75796
76046
  "status",
75797
76047
  "token"
75798
76048
  ]);
76049
+ var STORED_PAT_COMMANDS = new Set([
76050
+ "template",
76051
+ "skill",
76052
+ "publish",
76053
+ "token"
76054
+ ]);
75799
76055
  function help() {
75800
76056
  const out = [];
75801
76057
  out.push("");
@@ -75873,7 +76129,7 @@ function help() {
75873
76129
  out.push(` ${import_picocolors43.default.dim("--track")} for agent create: enable tracking non-interactively (off without a TTY)`);
75874
76130
  out.push(` ${import_picocolors43.default.dim("--shell <sh|fish>")} for agent env: pick output format (auto-detected from $SHELL)`);
75875
76131
  out.push(` ${import_picocolors43.default.dim("--all")} for template list: include installs from other folders`);
75876
- out.push(` ${import_picocolors43.default.dim("--web <url>")} for login: web app URL (default https://new.usekafka.com)`);
76132
+ out.push(` ${import_picocolors43.default.dim("--web <url>")} for login: web app URL (default https://app.brainbaselabs.com)`);
75877
76133
  out.push("");
75878
76134
  out.push(divider("ENV"));
75879
76135
  out.push("");
@@ -75938,13 +76194,23 @@ async function requireAuth(cmd) {
75938
76194
  return;
75939
76195
  let status = authStatus();
75940
76196
  if (!status.ok && status.session?.refresh_token) {
75941
- const refreshed = await tryRefreshSession();
76197
+ let refreshed;
76198
+ try {
76199
+ refreshed = await tryRefreshSession(status.session);
76200
+ } catch (error2) {
76201
+ if (error2 instanceof AuthRefreshLockTimeoutError && STORED_PAT_COMMANDS.has(cmd) && readToken()) {
76202
+ return;
76203
+ }
76204
+ throw error2;
76205
+ }
75942
76206
  if (refreshed) {
75943
76207
  status = { ok: true, session: refreshed };
75944
76208
  }
75945
76209
  }
75946
76210
  if (status.ok)
75947
76211
  return;
76212
+ if (STORED_PAT_COMMANDS.has(cmd) && readToken())
76213
+ return;
75948
76214
  console.error("");
75949
76215
  console.error(` ${brandTint("◆")} ${import_picocolors43.default.bold("brainbase")}`);
75950
76216
  console.error("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainbase-labs/cli",
3
- "version": "0.16.0",
3
+ "version": "0.16.2",
4
4
  "description": "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
5
5
  "type": "module",
6
6
  "bin": {