@brainbase-labs/cli 0.22.0 → 0.23.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 (3) hide show
  1. package/README.md +23 -5
  2. package/dist/index.js +260 -49
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -144,7 +144,7 @@ Playbook files carry a small YAML frontmatter (`title`, `description`) — the C
144
144
  | `BRAINBASE_API_URL` | Legacy KLS host override. Control requests use `/api/cli`; it is also the fallback for model-proxy and registry traffic. |
145
145
  | `BRAINBASE_PROXY_URL` | Override the model-proxy host used when enabling harness tracking. |
146
146
  | `BRAINBASE_REGISTRY_URL` | Override the registry API host. |
147
- | `BRAINBASE_TOKEN` | Use a PAT instead of the stored login session |
147
+ | `BRAINBASE_TOKEN` | Use a PAT instead of the stored login session. The only way a PAT authenticates a control-plane command — see [Auth](#auth). |
148
148
  | `BRAINBASE_HOME` | Where local state lives (default: `~/.brainbase`) |
149
149
 
150
150
  Model-proxy and registry traffic fall back through `BRAINBASE_API_URL`,
@@ -152,11 +152,29 @@ the server captured at login, and finally `https://api.v1.brainbaselabs.com`.
152
152
 
153
153
  ## Auth
154
154
 
155
- Managed task creation uses this authentication precedence:
155
+ Three credentials, and which ones a command considers depends on the service
156
+ it talks to:
156
157
 
157
- 1. `BRAINBASE_TOKEN` an explicit PAT from the environment.
158
- 2. `brainbase login` — a Supabase session stored in `~/.brainbase/auth.json`.
159
- 3. `brainbase token create --scopes read,publish` a PAT stored in `~/.brainbase/token.json` when no login session is configured.
158
+ | Commands | Credentials tried, in order |
159
+ |-|-|
160
+ | `agent`, `orchestration`, `link`, `unlink`, `sync`, `status`, `team` | `BRAINBASE_TOKEN`, then the `auth.json` session |
161
+ | `template`, `skill`, `token` | `BRAINBASE_TOKEN`, then the session, then `token.json` |
162
+ | `task create` | `BRAINBASE_TOKEN`, then the session, then `token.json` — the last only when no session is configured |
163
+
164
+ `brainbase whoami` (or `--json`) reports which one is active.
165
+
166
+ **For CI:** `brainbase token create` saves the PAT to `~/.brainbase/token.json`,
167
+ and control-plane commands never read that file — `brainbase agent push` needs
168
+ the token in `BRAINBASE_TOKEN`:
169
+
170
+ ```bash
171
+ export BRAINBASE_TOKEN=bbpat_…
172
+ brainbase agent push
173
+ ```
174
+
175
+ Do not bake a `~/.brainbase` directory into a build image: a leftover
176
+ `auth.json` outranks the stored PAT, and every control-plane command starts
177
+ failing once its session expires.
160
178
 
161
179
  An explicit PAT does not inherit routing from a stored login. Set
162
180
  `BRAINBASE_CONTROL_PLANE_URL` when using a PAT against a non-default MAS host.
package/dist/index.js CHANGED
@@ -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.22.0",
36011
+ version: "0.23.0",
36012
36012
  description: "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
36013
36013
  type: "module",
36014
36014
  bin: {
@@ -53931,6 +53931,76 @@ function clearTokenIfMatches(prefix) {
53931
53931
  });
53932
53932
  }
53933
53933
 
53934
+ // src/core/credentials.ts
53935
+ var REGISTRY_COMMANDS = "template, skill, token";
53936
+ var CONTROL_PLANE_COMMANDS = "agent, orchestration, link, unlink, sync, status, team";
53937
+ var NOT_A_PAT = "(not a bbpat_… token)";
53938
+ function maskPat(token) {
53939
+ if (!isValidTokenFormat(token))
53940
+ return NOT_A_PAT;
53941
+ return token.slice(0, TOKEN_PREFIX_LEN);
53942
+ }
53943
+ function readEnvPat() {
53944
+ const value = process.env.BRAINBASE_TOKEN?.trim();
53945
+ if (!value)
53946
+ return null;
53947
+ return { prefix: maskPat(value), malformed: !isValidTokenFormat(value) };
53948
+ }
53949
+ function storedPatIgnoredHint(client) {
53950
+ const setEnv = `Set BRAINBASE_TOKEN to that token's value to authenticate with it here, ` + "or run `brainbase login` to refresh the session";
53951
+ switch (client) {
53952
+ case "control-plane":
53953
+ return `The PAT saved in ${TOKEN_FILE} is never used by control-plane ` + `commands (${CONTROL_PLANE_COMMANDS}). ${setEnv}.`;
53954
+ case "managed-task":
53955
+ return `The PAT saved in ${TOKEN_FILE} is not used while a login session is ` + `configured. ${setEnv} — or \`brainbase logout\`, which leaves the ` + "PAT as the only credential.";
53956
+ default: {
53957
+ const exhaustive = client;
53958
+ throw new Error(`unhandled credential client: ${String(exhaustive)}`);
53959
+ }
53960
+ }
53961
+ }
53962
+ function withStoredPatHint(reason, client) {
53963
+ if (!readToken())
53964
+ return reason;
53965
+ const trimmed = reason.trimEnd();
53966
+ const separator = /[.!?:]$/.test(trimmed) ? " " : ". ";
53967
+ return `${trimmed}${separator}${storedPatIgnoredHint(client)}`;
53968
+ }
53969
+ function withRejectedSessionHint(message, source, client) {
53970
+ if (source !== "session")
53971
+ return message;
53972
+ return withStoredPatHint(message, client);
53973
+ }
53974
+ function isRefreshable(session) {
53975
+ return Boolean(session.refresh_token && session.supabase_url && session.supabase_anon_key);
53976
+ }
53977
+ function describeCredentials() {
53978
+ const env3 = readEnvPat();
53979
+ const status = authStatus();
53980
+ const stored = readToken();
53981
+ const refreshable = Boolean(status.session && !status.ok && isRefreshable(status.session));
53982
+ const session = status.session ? {
53983
+ email: status.session.email ?? null,
53984
+ userId: status.session.user_id,
53985
+ expiresAt: status.session.expires_at ?? null,
53986
+ expired: !status.ok,
53987
+ refreshable
53988
+ } : null;
53989
+ let active = null;
53990
+ if (env3)
53991
+ active = "env_pat";
53992
+ else if (status.ok || refreshable)
53993
+ active = "session";
53994
+ else if (stored)
53995
+ active = "stored_pat";
53996
+ return {
53997
+ active,
53998
+ envPat: env3,
53999
+ session,
54000
+ storedPat: stored ? { name: stored.name ?? null, prefix: stored.prefix } : null
54001
+ };
54002
+ }
54003
+
53934
54004
  // src/core/api.ts
53935
54005
  var DEFAULT_CONTROL_PLANE_BASE = "https://api.brainbaselabs.com";
53936
54006
  var DEFAULT_PROXY_BASE = "https://api.v1.brainbaselabs.com";
@@ -54005,7 +54075,7 @@ function legacyScheduleError() {
54005
54075
  function legacyAgentConfigError() {
54006
54076
  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);
54007
54077
  }
54008
- async function resolveCredential() {
54078
+ async function resolveCredential(storedPatHint = "control-plane") {
54009
54079
  const envToken = process.env.BRAINBASE_TOKEN;
54010
54080
  if (envToken && envToken.trim()) {
54011
54081
  return {
@@ -54023,19 +54093,20 @@ async function resolveCredential() {
54023
54093
  };
54024
54094
  }
54025
54095
  const status = authStatus();
54026
- throw new ApiError(status.ok ? "CLI authentication changed while this command was starting; retry it" : status.reason ?? "not logged in", 401);
54096
+ const reason = status.reason ?? "not logged in";
54097
+ throw new ApiError(status.ok ? "CLI authentication changed while this command was starting; retry it" : storedPatHint ? withStoredPatHint(reason, storedPatHint) : reason, 401);
54027
54098
  }
54028
54099
  async function resolveMasCredential() {
54029
54100
  const configuredSession = readAuth();
54030
54101
  try {
54031
- return await resolveCredential();
54102
+ return await resolveCredential(null);
54032
54103
  } catch (error) {
54033
54104
  if (!(error instanceof ApiError && error.status === 401)) {
54034
54105
  throw error;
54035
54106
  }
54036
54107
  if (configuredSession || readAuth()) {
54037
54108
  if (readToken()) {
54038
- throw new ApiError(`${error.message}. A stored PAT is available but will not be used while a login session is configured. Run \`brainbase logout\` to use the stored PAT, or \`brainbase login\` to refresh the session.`, 401);
54109
+ throw new ApiError(`${error.message}. ${storedPatIgnoredHint("managed-task")}`, 401);
54039
54110
  }
54040
54111
  throw error;
54041
54112
  }
@@ -54134,7 +54205,8 @@ async function request(pathname, init = {}) {
54134
54205
  body = text2 ? JSON.parse(text2) : null;
54135
54206
  } catch {}
54136
54207
  if (!res.ok) {
54137
- throw new ApiError(apiErrorMessage(body, res.status), res.status, body);
54208
+ const message = apiErrorMessage(body, res.status);
54209
+ throw new ApiError(res.status === 401 ? withRejectedSessionHint(message, credential.source, "control-plane") : message, res.status, body);
54138
54210
  }
54139
54211
  return body;
54140
54212
  }
@@ -54277,7 +54349,7 @@ async function masRequest(pathname, init) {
54277
54349
  }
54278
54350
  if (!res.ok) {
54279
54351
  const message = masApiErrorMessage(body, res.status);
54280
- throw new ApiError(res.status >= 500 ? `${message} Task creation may still be processing; check your tasks before running this command again.` : message, res.status, body);
54352
+ throw new ApiError(res.status >= 500 ? `${message} Task creation may still be processing; check your tasks before running this command again.` : res.status === 401 ? withRejectedSessionHint(message, credential.source, "managed-task") : message, res.status, body);
54281
54353
  }
54282
54354
  return body;
54283
54355
  }
@@ -61658,12 +61730,13 @@ async function runLogin(_cwd, args) {
61658
61730
  // src/ui/ink/IdentityCard.tsx
61659
61731
  var jsx_dev_runtime15 = __toESM(require_jsx_dev_runtime(), 1);
61660
61732
  function IdentityCard(props) {
61733
+ const hasRows = Boolean(props.rows?.length || props.controlPlaneUrl || props.expiresAt);
61661
61734
  return /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(Card, {
61662
61735
  title: props.title,
61663
61736
  tone: props.tone ?? "info",
61664
61737
  children: [
61665
61738
  props.email && /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(Box_default, {
61666
- marginBottom: props.controlPlaneUrl || props.expiresAt ? 1 : 0,
61739
+ marginBottom: hasRows ? 1 : 0,
61667
61740
  children: /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(Text, {
61668
61741
  bold: true,
61669
61742
  children: props.email
@@ -61672,6 +61745,20 @@ function IdentityCard(props) {
61672
61745
  /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(Box_default, {
61673
61746
  flexDirection: "column",
61674
61747
  children: [
61748
+ props.rows?.map(([label, value]) => /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(Box_default, {
61749
+ children: [
61750
+ /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(Box_default, {
61751
+ width: 15,
61752
+ children: /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(Text, {
61753
+ dimColor: true,
61754
+ children: label
61755
+ }, undefined, false, undefined, this)
61756
+ }, undefined, false, undefined, this),
61757
+ /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(Text, {
61758
+ children: value
61759
+ }, undefined, false, undefined, this)
61760
+ ]
61761
+ }, label, true, undefined, this)),
61675
61762
  props.controlPlaneUrl && /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(Box_default, {
61676
61763
  children: [
61677
61764
  /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(Box_default, {
@@ -61739,22 +61826,117 @@ async function runLogout() {
61739
61826
  }
61740
61827
 
61741
61828
  // src/cli/whoami.ts
61742
- async function runWhoami() {
61743
- const { ok, session, reason } = authStatus();
61744
- if (!ok || !session) {
61745
- await showIdentityCard({
61746
- title: "NOT SIGNED IN",
61747
- tone: "warn",
61748
- message: `${reason ?? "not logged in"}. Run \`brainbase login\` to connect this device.`
61749
- });
61750
- process.exit(1);
61829
+ function usableForControlPlane(report) {
61830
+ switch (report.active) {
61831
+ case "env_pat":
61832
+ return !report.envPat?.malformed;
61833
+ case "session":
61834
+ return true;
61835
+ case "stored_pat":
61836
+ case null:
61837
+ return false;
61838
+ default: {
61839
+ const exhaustive = report.active;
61840
+ throw new Error(`unhandled credential: ${String(exhaustive)}`);
61841
+ }
61842
+ }
61843
+ }
61844
+ function exitCodeFor(report) {
61845
+ return usableForControlPlane(report) ? 0 : 1;
61846
+ }
61847
+ function reasonFor(report) {
61848
+ switch (report.active) {
61849
+ case "env_pat":
61850
+ return report.envPat?.malformed ? "BRAINBASE_TOKEN is set to something that is not a `bbpat_…` token, " + "and it outranks every other credential — so every request will fail " + "until it is corrected or unset. Mint one with `brainbase token create`." : null;
61851
+ case "session":
61852
+ return null;
61853
+ case "stored_pat":
61854
+ return `The stored PAT authenticates registry commands (${REGISTRY_COMMANDS}) only. ` + `Set BRAINBASE_TOKEN to its value for ${CONTROL_PLANE_COMMANDS}, ` + "or run `brainbase login`.";
61855
+ case null:
61856
+ return report.session?.expired ? "Session expired. Run `brainbase login` to connect this device." : "Not logged in. Run `brainbase login` to connect this device.";
61857
+ default: {
61858
+ const exhaustive = report.active;
61859
+ throw new Error(`unhandled credential: ${String(exhaustive)}`);
61860
+ }
61861
+ }
61862
+ }
61863
+ function toJson(report, reason) {
61864
+ const usingSession = report.active === "session";
61865
+ return {
61866
+ credential: report.active,
61867
+ authenticated: report.active !== null && !report.envPat?.malformed,
61868
+ control_plane: usableForControlPlane(report),
61869
+ email: usingSession ? report.session?.email ?? null : null,
61870
+ user_id: usingSession ? report.session?.userId ?? null : null,
61871
+ expires_at: usingSession ? report.session?.expiresAt ?? null : null,
61872
+ token_prefix: report.active === "env_pat" ? report.envPat?.prefix ?? null : report.active === "stored_pat" ? report.storedPat?.prefix ?? null : null,
61873
+ control_plane_url: controlPlaneBaseUrl(usingSession ? readAuth() : null),
61874
+ session: report.session ? {
61875
+ present: true,
61876
+ expired: report.session.expired,
61877
+ refreshable: report.session.refreshable
61878
+ } : { present: false, expired: false, refreshable: false },
61879
+ stored_pat: report.storedPat ? { present: true, name: report.storedPat.name } : { present: false, name: null },
61880
+ reason
61881
+ };
61882
+ }
61883
+ function displayPrefix(prefix) {
61884
+ return prefix === NOT_A_PAT ? prefix : `${prefix}…`;
61885
+ }
61886
+ function cardRows(report) {
61887
+ const rows = [];
61888
+ switch (report.active) {
61889
+ case "env_pat":
61890
+ rows.push(["credential", "BRAINBASE_TOKEN (env PAT)"]);
61891
+ if (report.envPat) {
61892
+ rows.push(["token", displayPrefix(report.envPat.prefix)]);
61893
+ }
61894
+ break;
61895
+ case "session":
61896
+ rows.push([
61897
+ "credential",
61898
+ report.session?.refreshable ? "login session (auth.json) — expired, renews on next use" : "login session (auth.json)"
61899
+ ]);
61900
+ break;
61901
+ case "stored_pat":
61902
+ rows.push(["credential", "stored PAT (token.json)"]);
61903
+ rows.push([
61904
+ "token",
61905
+ report.storedPat?.name ? `${displayPrefix(report.storedPat.prefix)} (${report.storedPat.name})` : displayPrefix(report.storedPat?.prefix ?? "?")
61906
+ ]);
61907
+ break;
61908
+ case null:
61909
+ break;
61910
+ default: {
61911
+ const exhaustive = report.active;
61912
+ throw new Error(`unhandled credential: ${String(exhaustive)}`);
61913
+ }
61914
+ }
61915
+ if (report.session && report.active !== "session") {
61916
+ rows.push([
61917
+ "session",
61918
+ `${report.session.email ?? report.session.userId} (${report.session.expired ? "expired" : "not used — env PAT wins"})`
61919
+ ]);
61920
+ }
61921
+ return rows;
61922
+ }
61923
+ async function runWhoami(args = {}) {
61924
+ const report = describeCredentials();
61925
+ const reason = reasonFor(report);
61926
+ process.exitCode = exitCodeFor(report);
61927
+ if (args.json) {
61928
+ console.log(JSON.stringify(toJson(report, reason), null, 2));
61929
+ return;
61751
61930
  }
61931
+ const signedIn = report.active !== null;
61752
61932
  await showIdentityCard({
61753
- title: "WHOAMI",
61754
- tone: "ok",
61755
- email: session.email ?? session.user_id,
61756
- controlPlaneUrl: controlPlaneBaseUrl(session),
61757
- expiresAt: session.expires_at
61933
+ title: signedIn ? "WHOAMI" : "NOT SIGNED IN",
61934
+ tone: process.exitCode === 0 ? "ok" : "warn",
61935
+ email: report.active === "session" ? report.session?.email ?? report.session?.userId ?? null : null,
61936
+ controlPlaneUrl: signedIn ? controlPlaneBaseUrl(report.active === "session" ? readAuth() : null) : null,
61937
+ expiresAt: report.active === "session" ? report.session?.expiresAt : null,
61938
+ rows: cardRows(report),
61939
+ message: reason ?? undefined
61758
61940
  });
61759
61941
  }
61760
61942
 
@@ -68922,14 +69104,6 @@ function TokenCreatedCard(props) {
68922
69104
  tone: "warn",
68923
69105
  subtitle: "shown ONCE — copy it now",
68924
69106
  children: [
68925
- /* @__PURE__ */ jsx_dev_runtime17.jsxDEV(Box_default, {
68926
- marginBottom: 1,
68927
- children: /* @__PURE__ */ jsx_dev_runtime17.jsxDEV(Text, {
68928
- bold: true,
68929
- color: "yellow",
68930
- children: props.token
68931
- }, undefined, false, undefined, this)
68932
- }, undefined, false, undefined, this),
68933
69107
  /* @__PURE__ */ jsx_dev_runtime17.jsxDEV(Box_default, {
68934
69108
  flexDirection: "column",
68935
69109
  marginBottom: 1,
@@ -68990,20 +69164,22 @@ function TokenCreatedCard(props) {
68990
69164
  /* @__PURE__ */ jsx_dev_runtime17.jsxDEV(Text, {
68991
69165
  dimColor: true,
68992
69166
  children: [
68993
- "saved locally to ",
69167
+ "saved to ",
68994
69168
  props.storedAt,
68995
- " (mode 0600)"
69169
+ " (mode 0600) — covers ",
69170
+ REGISTRY_COMMANDS
68996
69171
  ]
68997
69172
  }, undefined, true, undefined, this),
68998
69173
  /* @__PURE__ */ jsx_dev_runtime17.jsxDEV(Text, {
68999
69174
  dimColor: true,
69000
69175
  children: [
69001
- "for CI: set ",
69176
+ CONTROL_PLANE_COMMANDS,
69177
+ " do ",
69002
69178
  /* @__PURE__ */ jsx_dev_runtime17.jsxDEV(Text, {
69003
69179
  bold: true,
69004
- children: "BRAINBASE_TOKEN"
69180
+ children: "not"
69005
69181
  }, undefined, false, undefined, this),
69006
- " instead of relying on token.json"
69182
+ " read that file export it instead:"
69007
69183
  ]
69008
69184
  }, undefined, true, undefined, this)
69009
69185
  ]
@@ -69012,9 +69188,12 @@ function TokenCreatedCard(props) {
69012
69188
  }, undefined, true, undefined, this);
69013
69189
  }
69014
69190
  async function showTokenCreatedCard(props) {
69191
+ const { token, ...card } = props;
69015
69192
  await renderStatic(/* @__PURE__ */ jsx_dev_runtime17.jsxDEV(TokenCreatedCard, {
69016
- ...props
69193
+ ...card
69017
69194
  }, undefined, false, undefined, this));
69195
+ console.log(` export BRAINBASE_TOKEN=${token}`);
69196
+ console.log("");
69018
69197
  }
69019
69198
  function TokenListCard(props) {
69020
69199
  if (props.active.length === 0) {
@@ -69180,11 +69359,32 @@ function isAllowedScope(value) {
69180
69359
  function isExpired2(token) {
69181
69360
  return Boolean(token.expires_at && Date.parse(token.expires_at) <= Date.now());
69182
69361
  }
69362
+ function isJwtRequired(body) {
69363
+ if (!body || typeof body !== "object")
69364
+ return false;
69365
+ const detail = body.detail;
69366
+ if (!detail || typeof detail !== "object")
69367
+ return false;
69368
+ return detail.code === "jwt_required";
69369
+ }
69370
+ function sessionWouldBeUsed() {
69371
+ const { ok, session } = authStatus();
69372
+ if (ok)
69373
+ return true;
69374
+ return Boolean(session?.refresh_token && session.supabase_url && session.supabase_anon_key);
69375
+ }
69183
69376
  function withLoginHint(error) {
69184
- if (error instanceof ApiError && error.status === 403) {
69185
- return new Error("Managing tokens needs a logged-in session; a PAT (BRAINBASE_TOKEN) cannot. " + "Run `brainbase login` and try again.");
69186
- }
69187
- return error;
69377
+ if (!(error instanceof ApiError) || error.status !== 403)
69378
+ return error;
69379
+ if (!isJwtRequired(error.body))
69380
+ return error;
69381
+ if (!process.env.BRAINBASE_TOKEN?.trim()) {
69382
+ if (sessionWouldBeUsed() || !readToken())
69383
+ return error;
69384
+ return new Error("Managing tokens needs a logged-in session; the stored PAT cannot. " + "Run `brainbase login` and try again.");
69385
+ }
69386
+ const lead = "Managing tokens needs a logged-in session, and BRAINBASE_TOKEN is set — " + "that PAT is being used instead.";
69387
+ return new Error(sessionWouldBeUsed() ? `${lead} Run \`unset BRAINBASE_TOKEN\` and try again.` : `${lead} Run \`unset BRAINBASE_TOKEN\`, then \`brainbase login\`.`);
69188
69388
  }
69189
69389
  async function runTokenCreate(args) {
69190
69390
  banner("token create — make a long-lived CLI key");
@@ -69207,10 +69407,16 @@ async function runTokenCreate(args) {
69207
69407
  }
69208
69408
  const spinner = de();
69209
69409
  spinner.start("Creating token…");
69210
- const created = await registryApi.createCliToken({
69211
- name,
69212
- scopes
69213
- });
69410
+ let created;
69411
+ try {
69412
+ created = await registryApi.createCliToken({
69413
+ name,
69414
+ scopes
69415
+ });
69416
+ } catch (error) {
69417
+ spinner.stop("Could not create token.");
69418
+ throw withLoginHint(error);
69419
+ }
69214
69420
  spinner.stop("Token created.");
69215
69421
  try {
69216
69422
  writeToken(created.token, name);
@@ -69231,7 +69437,12 @@ async function runTokenCreate(args) {
69231
69437
  }
69232
69438
  async function runTokenList() {
69233
69439
  banner("tokens");
69234
- const tokens = await registryApi.listCliTokens();
69440
+ let tokens;
69441
+ try {
69442
+ tokens = await registryApi.listCliTokens();
69443
+ } catch (error) {
69444
+ throw withLoginHint(error);
69445
+ }
69235
69446
  const active = tokens.filter((t) => !t.revoked_at);
69236
69447
  const revoked = tokens.filter((t) => t.revoked_at);
69237
69448
  const local = readToken();
@@ -69354,7 +69565,7 @@ async function runTokenRevoke(args) {
69354
69565
  }
69355
69566
  throw new Error(`The server reported no active token with id ${args.id}, but it listed one a moment ago. ` + "The local token has been left alone, since that key may still work. " + "If this server predates `DELETE /v1/registry/cli-tokens/{id}`, revoke from the web app instead.");
69356
69567
  }
69357
- throw error;
69568
+ throw withLoginHint(error);
69358
69569
  }
69359
69570
  reconcileDeadToken(target, `Revoked ${import_picocolors48.default.bold(target.name)}.`);
69360
69571
  }
@@ -80201,7 +80412,7 @@ function help() {
80201
80412
  out.push("");
80202
80413
  out.push(` ${import_picocolors52.default.cyan("login")} ${import_picocolors52.default.dim(" open the web app and connect this device")}`);
80203
80414
  out.push(` ${import_picocolors52.default.cyan("logout")} ${import_picocolors52.default.dim(" clear the local session")}`);
80204
- out.push(` ${import_picocolors52.default.cyan("whoami")} ${import_picocolors52.default.dim(" show the current user")}`);
80415
+ out.push(` ${import_picocolors52.default.cyan("whoami")} ${import_picocolors52.default.dim("[--json]")} ${import_picocolors52.default.dim(" show which credential is in use and what it covers")}`);
80205
80416
  out.push("");
80206
80417
  out.push(divider("DISCOVERY"));
80207
80418
  out.push("");
@@ -80304,7 +80515,7 @@ function help() {
80304
80515
  out.push(` ${import_picocolors52.default.dim("BRAINBASE_API_URL")} legacy KLS host override (uses /api/cli; proxy/registry fallback)`);
80305
80516
  out.push(` ${import_picocolors52.default.dim("BRAINBASE_PROXY_URL")} override the model-proxy URL used by harness traffic (default https://api.v1.brainbaselabs.com)`);
80306
80517
  out.push(` ${import_picocolors52.default.dim("BRAINBASE_REGISTRY_URL")} override the registry API URL (default https://api.v1.brainbaselabs.com)`);
80307
- out.push(` ${import_picocolors52.default.dim("BRAINBASE_TOKEN")} long-lived CLI PAT (overrides token.json)`);
80518
+ out.push(` ${import_picocolors52.default.dim("BRAINBASE_TOKEN")} long-lived CLI PAT; the only PAT control-plane commands accept (token.json is not read there)`);
80308
80519
  out.push(` ${import_picocolors52.default.dim("BRAINBASE_SKIP_AUTH")} bypass the auth gate for development`);
80309
80520
  out.push(` ${import_picocolors52.default.dim("BRAINBASE_NON_INTERACTIVE")} force non-interactive mode — skip/auto-default prompts (CI & agents)`);
80310
80521
  out.push(` ${import_picocolors52.default.dim("BRAINBASE_RUN_ENTRYPOINT")} =1 → agent pull executes the agent entrypoint (sandbox boots; or pass --run-entrypoint)`);
@@ -80556,7 +80767,7 @@ async function main() {
80556
80767
  break;
80557
80768
  }
80558
80769
  case "whoami": {
80559
- await runWhoami();
80770
+ await runWhoami({ json: jsonFlag });
80560
80771
  break;
80561
80772
  }
80562
80773
  case "template": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainbase-labs/cli",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
5
5
  "type": "module",
6
6
  "bin": {