busa-cli 0.9.11 → 0.9.13

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.
@@ -775,11 +775,10 @@ var BUSABASE_CLI_CLIENT_ID = "busabase-cli";
775
775
 
776
776
  // src/login.ts
777
777
  import { DEFAULT_BASE_URL, normalizeBaseUrl } from "busabase-sdk";
778
- var CLI_CLIENT_PLATFORM = "cli";
779
778
  var OAUTH_TIMEOUT_MS = 5 * 60 * 1e3;
780
779
  var EXPIRES_AT_KEY = "BUSABASE_TOKEN_EXPIRES_AT";
781
- var LEGACY_SESSION_TOKEN_PREFIX = "bss_";
782
- var AUTO_REFRESH_THRESHOLD_MS = 2 * 24 * 60 * 60 * 1e3;
780
+ var REFRESH_TOKEN_KEY = "BUSABASE_REFRESH_TOKEN";
781
+ var AUTO_REFRESH_THRESHOLD_MS = 5 * 60 * 1e3;
783
782
  var DEFAULT_LOCAL_URL = "http://localhost:15419";
784
783
  var base64url = (buffer) => buffer.toString("base64url");
785
784
  var isInteractive = () => Boolean(process.stdin.isTTY && process.stdout.isTTY);
@@ -828,7 +827,7 @@ async function verifyAuth(baseUrl, token) {
828
827
  });
829
828
  if (res.status === 401) {
830
829
  throw new Error(
831
- `The credential was rejected (401) by ${baseUrl}. If it's an API key, check it in Dashboard \u2192 Settings \u2192 API Keys; if it's a login session, run \`busabase-cli login\` again.`
830
+ `The credential was rejected (401) by ${baseUrl}. If it's an API key, check it in Dashboard \u2192 Settings \u2192 API Keys; if it's an OAuth login, run \`busabase-cli login\` again.`
832
831
  );
833
832
  }
834
833
  if (!res.ok) {
@@ -857,6 +856,7 @@ async function loopbackOauthLogin(baseUrl, useBrowser) {
857
856
  const error = url.searchParams.get("error");
858
857
  const returnedState = url.searchParams.get("state");
859
858
  const returnedCode = url.searchParams.get("code");
859
+ const returnedIssuer = url.searchParams.get("iss");
860
860
  if (error) {
861
861
  respond("Sign-in failed", "You can close this tab and return to the terminal.");
862
862
  cleanup();
@@ -869,6 +869,20 @@ async function loopbackOauthLogin(baseUrl, useBrowser) {
869
869
  reject(new Error("OAuth state mismatch \u2014 the callback did not match this request."));
870
870
  return;
871
871
  }
872
+ let issuerMatches = false;
873
+ try {
874
+ issuerMatches = Boolean(
875
+ returnedIssuer && new URL(returnedIssuer).origin === new URL(baseUrl).origin
876
+ );
877
+ } catch {
878
+ issuerMatches = false;
879
+ }
880
+ if (!issuerMatches) {
881
+ respond("Sign-in failed", "Authorization server mismatch. Close this tab and try again.");
882
+ cleanup();
883
+ reject(new Error("OAuth authorization server issuer mismatch."));
884
+ return;
885
+ }
872
886
  respond("Signed in \u2713", "You can close this tab and return to the terminal.");
873
887
  const address = server.address();
874
888
  cleanup();
@@ -892,7 +906,8 @@ async function loopbackOauthLogin(baseUrl, useBrowser) {
892
906
  const authorizeUrl = new URL(`${baseUrl}/api/oauth/authorize`);
893
907
  authorizeUrl.searchParams.set("response_type", "code");
894
908
  authorizeUrl.searchParams.set("client_id", BUSABASE_CLI_CLIENT_ID);
895
- authorizeUrl.searchParams.set("client_platform", CLI_CLIENT_PLATFORM);
909
+ authorizeUrl.searchParams.set("resource", `${baseUrl}/api/v1`);
910
+ authorizeUrl.searchParams.set("scope", "api");
896
911
  authorizeUrl.searchParams.set("code_challenge", codeChallenge);
897
912
  authorizeUrl.searchParams.set("code_challenge_method", "S256");
898
913
  authorizeUrl.searchParams.set("redirect_uri", redirect);
@@ -913,15 +928,14 @@ async function loopbackOauthLogin(baseUrl, useBrowser) {
913
928
  );
914
929
  const tokenRes = await fetch(`${baseUrl}/api/oauth/token`, {
915
930
  method: "POST",
916
- headers: { "content-type": "application/json" },
917
- body: JSON.stringify({
931
+ headers: { "content-type": "application/x-www-form-urlencoded" },
932
+ body: new URLSearchParams({
918
933
  grant_type: "authorization_code",
919
934
  client_id: BUSABASE_CLI_CLIENT_ID,
920
- client_platform: CLI_CLIENT_PLATFORM,
921
935
  code,
922
936
  code_verifier: codeVerifier,
923
937
  redirect_uri: redirectUri,
924
- state
938
+ resource: `${baseUrl}/api/v1`
925
939
  })
926
940
  });
927
941
  if (!tokenRes.ok) {
@@ -929,9 +943,13 @@ async function loopbackOauthLogin(baseUrl, useBrowser) {
929
943
  throw new Error(`Token exchange failed (HTTP ${tokenRes.status})${text ? `: ${text}` : ""}`);
930
944
  }
931
945
  const payload = await tokenRes.json();
932
- const token = payload.apiKey ?? payload.token ?? payload.accessToken;
946
+ const token = payload.access_token;
933
947
  if (!token) throw new Error("Token exchange returned no credential.");
934
- return payload.apiKey ? { token, apiKeyExpiresAt: payload.expiresAt ?? void 0 } : { token, expiresAt: payload.expiresAt ?? void 0 };
948
+ return {
949
+ token,
950
+ refreshToken: payload.refresh_token,
951
+ expiresAt: typeof payload.expires_in === "number" ? new Date(Date.now() + payload.expires_in * 1e3).toISOString() : void 0
952
+ };
935
953
  }
936
954
  var delay = (durationMs) => new Promise((resolve) => setTimeout(resolve, durationMs));
937
955
  async function readDeviceResponse(response) {
@@ -1036,15 +1054,26 @@ async function deviceLogin(baseUrl, useBrowser) {
1036
1054
  }
1037
1055
  throw new Error("The device code expired. Run `busabase-cli login` to request a new one.");
1038
1056
  }
1039
- async function refreshToken(baseUrl, token) {
1040
- const res = await fetch(`${baseUrl}/api/oauth/refresh`, {
1057
+ async function refreshToken(baseUrl, refreshToken2) {
1058
+ const res = await fetch(`${baseUrl}/api/oauth/token`, {
1041
1059
  method: "POST",
1042
- headers: { authorization: `Bearer ${token}` }
1060
+ headers: { "content-type": "application/x-www-form-urlencoded" },
1061
+ body: new URLSearchParams({
1062
+ grant_type: "refresh_token",
1063
+ refresh_token: refreshToken2,
1064
+ client_id: BUSABASE_CLI_CLIENT_ID,
1065
+ resource: `${baseUrl}/api/v1`
1066
+ })
1043
1067
  });
1044
- if (res.status === 401) throw new Error("SESSION_EXPIRED");
1068
+ if (res.status === 400 || res.status === 401) throw new Error("SESSION_EXPIRED");
1045
1069
  if (!res.ok) throw new Error(`Refresh failed (HTTP ${res.status}) from ${baseUrl}.`);
1046
1070
  const payload = await res.json();
1047
- return { token: payload.token ?? payload.accessToken ?? token, expiresAt: payload.expiresAt };
1071
+ if (!payload.access_token) throw new Error("Refresh response had no access token");
1072
+ return {
1073
+ token: payload.access_token,
1074
+ refreshToken: payload.refresh_token,
1075
+ expiresAt: typeof payload.expires_in === "number" ? new Date(Date.now() + payload.expires_in * 1e3).toISOString() : void 0
1076
+ };
1048
1077
  }
1049
1078
  async function pickSpaceId(verify, preselected) {
1050
1079
  if (preselected) return preselected;
@@ -1144,6 +1173,7 @@ async function runLogin(options) {
1144
1173
  let token;
1145
1174
  let expiresAt;
1146
1175
  let apiKeyExpiresAt;
1176
+ let refreshTokenValue;
1147
1177
  if (method === "device") {
1148
1178
  const result = await deviceLogin(baseUrl, options.browser);
1149
1179
  token = result.token;
@@ -1152,6 +1182,7 @@ async function runLogin(options) {
1152
1182
  const result = await loopbackOauthLogin(baseUrl, options.browser);
1153
1183
  token = result.token;
1154
1184
  expiresAt = result.expiresAt;
1185
+ refreshTokenValue = result.refreshToken;
1155
1186
  apiKeyExpiresAt = result.apiKeyExpiresAt;
1156
1187
  } else {
1157
1188
  if (!apiKey) {
@@ -1169,14 +1200,15 @@ async function runLogin(options) {
1169
1200
  BUSABASE_API_KEY: token,
1170
1201
  BUSABASE_SPACE_ID: spaceId ?? null,
1171
1202
  // Clear any stale expiry when switching to an API key.
1172
- [EXPIRES_AT_KEY]: expiresAt ?? null
1203
+ [EXPIRES_AT_KEY]: expiresAt ?? null,
1204
+ [REFRESH_TOKEN_KEY]: refreshTokenValue ?? null
1173
1205
  });
1174
1206
  say("");
1175
1207
  say(`\u2713 Signed in and saved to ${dotEnvPath()}`);
1176
1208
  return {
1177
1209
  status: "signed in",
1178
1210
  method: method === "loopback-oauth" ? "oauth" : method,
1179
- credentialType: method === "device" ? "api_key" : expiresAt ? "session" : "api_key",
1211
+ credentialType: method === "device" ? "api_key" : expiresAt ? "oauth" : "api_key",
1180
1212
  user: verify.user?.email ?? verify.user?.name ?? verify.user?.id ?? "(unknown)",
1181
1213
  space: spaceId ?? "(server default)",
1182
1214
  createdSpace: String(Boolean(verify.createdSpace)),
@@ -1191,29 +1223,34 @@ async function runRefresh(options) {
1191
1223
  const token = options.apiKey;
1192
1224
  if (!token) throw new Error("Not signed in \u2014 run `busabase-cli login` first.");
1193
1225
  const file = loadDotEnvFile();
1194
- const isSavedExpiringSession = file.BUSABASE_API_KEY === token && Boolean(file.BUSABASE_TOKEN_EXPIRES_AT);
1195
- if (!token.startsWith(LEGACY_SESSION_TOKEN_PREFIX) && !isSavedExpiringSession) {
1226
+ const isSavedExpiringOAuthToken = file.BUSABASE_API_KEY === token && Boolean(file.BUSABASE_TOKEN_EXPIRES_AT);
1227
+ if (!isSavedExpiringOAuthToken) {
1196
1228
  return {
1197
1229
  status: "nothing to refresh",
1198
- detail: "This credential is an API key and cannot be refreshed. Only OAuth sessions refresh."
1230
+ detail: "This credential is an API key and cannot be refreshed. Only OAuth token sets refresh."
1199
1231
  };
1200
1232
  }
1233
+ const savedRefreshToken = file[REFRESH_TOKEN_KEY];
1234
+ if (!savedRefreshToken) throw new Error("OAuth refresh token is missing \u2014 sign in again.");
1201
1235
  let refreshed;
1202
1236
  try {
1203
- refreshed = await refreshToken(baseUrl, token);
1237
+ refreshed = await refreshToken(baseUrl, savedRefreshToken);
1204
1238
  } catch (error) {
1205
1239
  if (error instanceof Error && error.message === "SESSION_EXPIRED") {
1206
1240
  throw new Error(
1207
- "Your login session has expired \u2014 run `busabase-cli login` to sign in again."
1241
+ "Your OAuth authorization has expired \u2014 run `busabase-cli login` to sign in again."
1208
1242
  );
1209
1243
  }
1210
1244
  throw error;
1211
1245
  }
1212
1246
  writeDotEnvFile({
1213
1247
  BUSABASE_API_KEY: refreshed.token,
1214
- [EXPIRES_AT_KEY]: refreshed.expiresAt ?? null
1248
+ [EXPIRES_AT_KEY]: refreshed.expiresAt ?? null,
1249
+ [REFRESH_TOKEN_KEY]: refreshed.refreshToken ?? null
1215
1250
  });
1216
- say(`\u2713 Session refreshed${refreshed.expiresAt ? ` \u2014 valid until ${refreshed.expiresAt}` : ""}`);
1251
+ say(
1252
+ `\u2713 OAuth token refreshed${refreshed.expiresAt ? ` \u2014 valid until ${refreshed.expiresAt}` : ""}`
1253
+ );
1217
1254
  return {
1218
1255
  status: "refreshed",
1219
1256
  expiresAt: refreshed.expiresAt ?? "(unknown)",
@@ -1221,22 +1258,26 @@ async function runRefresh(options) {
1221
1258
  };
1222
1259
  }
1223
1260
  async function maybeAutoRefresh(baseUrl, apiKey) {
1224
- if (!apiKey) return;
1261
+ if (!apiKey) return void 0;
1225
1262
  const file = loadDotEnvFile();
1226
- if (file.BUSABASE_API_KEY !== apiKey) return;
1263
+ if (file.BUSABASE_API_KEY !== apiKey) return void 0;
1227
1264
  const expiresRaw = file[EXPIRES_AT_KEY];
1228
- if (!expiresRaw) return;
1265
+ if (!expiresRaw) return void 0;
1229
1266
  const expiresAt = Date.parse(expiresRaw);
1230
- if (Number.isNaN(expiresAt)) return;
1231
- if (expiresAt <= Date.now()) return;
1232
- if (expiresAt - Date.now() > AUTO_REFRESH_THRESHOLD_MS) return;
1267
+ if (Number.isNaN(expiresAt)) return void 0;
1268
+ if (expiresAt - Date.now() > AUTO_REFRESH_THRESHOLD_MS) return void 0;
1233
1269
  try {
1234
- const refreshed = await refreshToken(normalizeBaseUrl(baseUrl), apiKey);
1270
+ const savedRefreshToken = file[REFRESH_TOKEN_KEY];
1271
+ if (!savedRefreshToken) return void 0;
1272
+ const refreshed = await refreshToken(normalizeBaseUrl(baseUrl), savedRefreshToken);
1235
1273
  writeDotEnvFile({
1236
1274
  BUSABASE_API_KEY: refreshed.token,
1237
- [EXPIRES_AT_KEY]: refreshed.expiresAt ?? null
1275
+ [EXPIRES_AT_KEY]: refreshed.expiresAt ?? null,
1276
+ [REFRESH_TOKEN_KEY]: refreshed.refreshToken ?? null
1238
1277
  });
1278
+ return refreshed.token;
1239
1279
  } catch {
1280
+ return void 0;
1240
1281
  }
1241
1282
  }
1242
1283
  function assertCredentialNotExpired(apiKey) {
@@ -1248,29 +1289,36 @@ function assertCredentialNotExpired(apiKey) {
1248
1289
  const expiresAt = Date.parse(expiresRaw);
1249
1290
  if (!Number.isNaN(expiresAt) && expiresAt <= Date.now()) {
1250
1291
  throw new Error(
1251
- "Your saved device login has expired. Run `busabase-cli login --device-code` to authorize this CLI again."
1292
+ "Your saved OAuth login has expired. Run `busabase-cli login` to sign in again."
1252
1293
  );
1253
1294
  }
1254
1295
  }
1255
1296
  async function runLogout(options) {
1256
1297
  const baseUrl = normalizeBaseUrl(options.baseUrl);
1257
1298
  const token = options.apiKey;
1258
- let revoked = "no session to revoke";
1299
+ let revoked = "no OAuth token to revoke";
1259
1300
  const file = loadDotEnvFile();
1260
- if (token && (token.startsWith(LEGACY_SESSION_TOKEN_PREFIX) || file.BUSABASE_API_KEY === token && file[EXPIRES_AT_KEY])) {
1301
+ if (token && file.BUSABASE_API_KEY === token && file[EXPIRES_AT_KEY]) {
1261
1302
  try {
1303
+ const tokenToRevoke = file[REFRESH_TOKEN_KEY] ?? token;
1262
1304
  const res = await fetch(`${baseUrl}/api/oauth/revoke`, {
1263
1305
  method: "POST",
1264
- headers: { authorization: `Bearer ${token}` }
1306
+ headers: { "content-type": "application/x-www-form-urlencoded" },
1307
+ body: new URLSearchParams({ token: tokenToRevoke })
1265
1308
  });
1266
- revoked = res.ok ? "session revoked" : `revoke returned HTTP ${res.status}`;
1309
+ revoked = res.ok ? "OAuth token family revoked" : `revoke returned HTTP ${res.status}`;
1267
1310
  } catch {
1268
1311
  revoked = "revoke request failed (cleared locally anyway)";
1269
1312
  }
1270
1313
  } else if (token) {
1271
1314
  revoked = "API key cleared locally (revoke it in the dashboard to disable it)";
1272
1315
  }
1273
- writeDotEnvFile({ BUSABASE_API_KEY: null, BUSABASE_SPACE_ID: null, [EXPIRES_AT_KEY]: null });
1316
+ writeDotEnvFile({
1317
+ BUSABASE_API_KEY: null,
1318
+ BUSABASE_SPACE_ID: null,
1319
+ [EXPIRES_AT_KEY]: null,
1320
+ [REFRESH_TOKEN_KEY]: null
1321
+ });
1274
1322
  say(`\u2713 Cleared the saved credential from ${dotEnvPath()}`);
1275
1323
  return { status: "signed out", detail: revoked, config: dotEnvPath() };
1276
1324
  }
@@ -1656,6 +1704,15 @@ var folderNodeType = {
1656
1704
  operations: []
1657
1705
  };
1658
1706
 
1707
+ // ../../packages/busabase-contract/src/domains/html/definition.ts
1708
+ var htmlNodeType = {
1709
+ type: "html",
1710
+ label: "HTML",
1711
+ icon: "code-xml",
1712
+ capabilities: { hasDetail: true, creatable: true },
1713
+ operations: []
1714
+ };
1715
+
1659
1716
  // ../../packages/busabase-contract/src/domains/skill/definition.ts
1660
1717
  var skillNodeType = makeFileTreeNodeType({
1661
1718
  type: "skill",
@@ -1666,6 +1723,24 @@ var skillNodeType = makeFileTreeNodeType({
1666
1723
  entryFile: "SKILL.md"
1667
1724
  });
1668
1725
 
1726
+ // ../../packages/busabase-contract/src/domains/whiteboard/definition.ts
1727
+ var whiteboardNodeType = {
1728
+ type: "whiteboard",
1729
+ label: "Whiteboard",
1730
+ icon: "pen-tool",
1731
+ capabilities: { hasDetail: true, creatable: true },
1732
+ operations: []
1733
+ };
1734
+
1735
+ // ../../packages/busabase-contract/src/domains/workflow/definition.ts
1736
+ var workflowNodeType = {
1737
+ type: "workflow",
1738
+ label: "Workflow",
1739
+ icon: "workflow",
1740
+ capabilities: { hasDetail: true, creatable: true },
1741
+ operations: []
1742
+ };
1743
+
1669
1744
  // ../../packages/busabase-contract/src/domains/registry.ts
1670
1745
  var GENERIC_NODE_OPERATIONS = [
1671
1746
  {
@@ -1693,7 +1768,10 @@ var BUILTIN_NODE_TYPES = [
1693
1768
  driveNodeType,
1694
1769
  airappNodeType,
1695
1770
  fileNodeType,
1696
- docNodeType
1771
+ docNodeType,
1772
+ whiteboardNodeType,
1773
+ workflowNodeType,
1774
+ htmlNodeType
1697
1775
  ];
1698
1776
  for (const definition of BUILTIN_NODE_TYPES) {
1699
1777
  registerNodeType(definition);
@@ -2154,6 +2232,10 @@ var authInfoSchema = z6.object({
2154
2232
  bootstrapRequired: z6.boolean().optional()
2155
2233
  });
2156
2234
 
2235
+ // ../../packages/busabase-contract/src/domains/base/types.ts
2236
+ var VIEW_FIELD_MIN_WIDTH = 92;
2237
+ var VIEW_FIELD_MAX_WIDTH = 640;
2238
+
2157
2239
  // ../../packages/busabase-contract/src/domains/base/contract/view-schemas.ts
2158
2240
  var viewFilterOperatorSchema = z7.enum([
2159
2241
  "contains",
@@ -2183,6 +2265,7 @@ var viewConfigSchema = z7.object({
2183
2265
  filters: z7.array(viewFilterSchema).default([]),
2184
2266
  sorts: z7.array(viewSortSchema).default([]),
2185
2267
  visibleFieldSlugs: z7.array(z7.string()).nullable().optional(),
2268
+ fieldWidths: z7.record(z7.string().min(1), z7.number().int().min(VIEW_FIELD_MIN_WIDTH).max(VIEW_FIELD_MAX_WIDTH)).optional(),
2186
2269
  // ── Gallery-only presentation config (ignored by table views) ──
2187
2270
  // Which attachment field supplies the cover image (null = no cover).
2188
2271
  coverFieldSlug: z7.string().nullable().optional(),
@@ -2360,7 +2443,8 @@ var PackageViewSortSchema = z8.object({
2360
2443
  var PackageViewConfigSchema = z8.object({
2361
2444
  filters: z8.array(PackageViewFilterSchema).default([]),
2362
2445
  sorts: z8.array(PackageViewSortSchema).default([]),
2363
- visibleFieldSlugs: z8.array(z8.string()).nullable().optional()
2446
+ visibleFieldSlugs: z8.array(z8.string()).nullable().optional(),
2447
+ fieldWidths: z8.record(z8.string().min(1), z8.number().int().min(VIEW_FIELD_MIN_WIDTH).max(VIEW_FIELD_MAX_WIDTH)).optional()
2364
2448
  });
2365
2449
  var PackageViewSchema = z8.object({
2366
2450
  slug: z8.string().min(1),
@@ -3108,7 +3192,8 @@ var toPackageView = (view) => ({
3108
3192
  direction: sort.direction,
3109
3193
  fieldSlug: sort.fieldSlug
3110
3194
  })),
3111
- visibleFieldSlugs: view.config.visibleFieldSlugs ?? void 0
3195
+ visibleFieldSlugs: view.config.visibleFieldSlugs ?? void 0,
3196
+ fieldWidths: view.config.fieldWidths
3112
3197
  }
3113
3198
  });
3114
3199
  var collectRecords = async (client, baseId, fields, options) => {
@@ -3698,7 +3783,8 @@ var serializeView = (view) => compact({
3698
3783
  direction: sort.direction,
3699
3784
  fieldSlug: sort.fieldSlug
3700
3785
  })),
3701
- visibleFieldSlugs: view.config.visibleFieldSlugs ?? void 0
3786
+ visibleFieldSlugs: view.config.visibleFieldSlugs ?? void 0,
3787
+ fieldWidths: view.config.fieldWidths
3702
3788
  })
3703
3789
  });
3704
3790
  var serializeBase = (base, position) => toJsonFile(
@@ -4469,9 +4555,10 @@ function runAction(state, handler) {
4469
4555
  return async (_opts, cmd) => {
4470
4556
  const opts = cmd.optsWithGlobals();
4471
4557
  const config = resolveConfig(opts);
4558
+ const refreshedToken = await maybeAutoRefresh(config.baseUrl, config.apiKey);
4559
+ if (refreshedToken) config.apiKey = refreshedToken;
4472
4560
  state.config = config;
4473
4561
  assertCredentialNotExpired(config.apiKey);
4474
- await maybeAutoRefresh(config.baseUrl, config.apiKey);
4475
4562
  const client = createBusabaseClient(config);
4476
4563
  const result = await handler(client, opts, config);
4477
4564
  console.log(render(result, config.output));
@@ -4481,9 +4568,10 @@ function runArgAction(state, handler) {
4481
4568
  return async (arg, _opts, cmd) => {
4482
4569
  const opts = cmd.optsWithGlobals();
4483
4570
  const config = resolveConfig(opts);
4571
+ const refreshedToken = await maybeAutoRefresh(config.baseUrl, config.apiKey);
4572
+ if (refreshedToken) config.apiKey = refreshedToken;
4484
4573
  state.config = config;
4485
4574
  assertCredentialNotExpired(config.apiKey);
4486
- await maybeAutoRefresh(config.baseUrl, config.apiKey);
4487
4575
  const client = createBusabaseClient(config);
4488
4576
  const result = await handler(arg, client, opts, config);
4489
4577
  console.log(render(result, config.output));
@@ -4556,7 +4644,7 @@ function registerGeneratedCommands(program, state) {
4556
4644
  const getGroup = (displayName, tag) => program.commands.find((c2) => c2.name() === displayName) ?? program.command(displayName).description(tag ?? `${displayName} endpoints`);
4557
4645
  const addLeaf = (parent, navPath, procKey, proc) => {
4558
4646
  const { route, inputSchema } = proc["~orpc"];
4559
- const name = kebab(procKey);
4647
+ const name = [...navPath.slice(1), procKey].map(kebab).join("-");
4560
4648
  if (parent.commands.some((c2) => c2.name() === name)) return;
4561
4649
  const fields = inputFields(inputSchema);
4562
4650
  const leaf = parent.command(name).description(route.summary ?? `${route.method} ${route.path}`);
@@ -4614,7 +4702,6 @@ OpenAPI: ${route.method} ${route.path}`);
4614
4702
  const id = [...navPath, key].join(".");
4615
4703
  if (isProcedure(child)) {
4616
4704
  if (GENERATED_SKIP.has(id)) continue;
4617
- if (navPath.length > 1) continue;
4618
4705
  const parent = navPath.length ? getGroup(kebab(navPath[0]), child["~orpc"].route.tags?.[0]) : program;
4619
4706
  addLeaf(parent, navPath, key, child);
4620
4707
  } else if (child && typeof child === "object") {
@@ -4669,7 +4756,7 @@ function buildProgram(state = {}) {
4669
4756
  runAction(state, (_client, _opts, config) => rawFetch(config, "GET", "/api/v1/openapi.json"))
4670
4757
  );
4671
4758
  addGlobalFlags(program.command("whoami")).description("Active space, user, and membership").action(runAction(state, (client) => client.auth.verify()));
4672
- addGlobalFlags(program.command("login")).description("Connect the CLI to Busabase \u2014 Personal/local, Cloud, or self-hosted").option("--device-code", "force device authorization and skip the method prompt").option("--oauth", "legacy same-machine OAuth callback flow").option("--no-browser", "print the sign-in URL instead of opening a browser").option("--refresh", "slide the saved OAuth session forward (no browser, no re-login)").addHelpText(
4759
+ addGlobalFlags(program.command("login")).description("Connect the CLI to Busabase \u2014 Personal/local, Cloud, or self-hosted").option("--device-code", "force device authorization and skip the method prompt").option("--oauth", "legacy same-machine OAuth callback flow").option("--no-browser", "print the sign-in URL instead of opening a browser").option("--refresh", "rotate the saved OAuth token set (no browser, no re-login)").addHelpText(
4673
4760
  "after",
4674
4761
  `
4675
4762
  Busabase is an approval-first database and knowledge base for AI agents: agents
@@ -4689,7 +4776,7 @@ The flags below skip the menu (handy for scripts / CI):
4689
4776
  busabase-cli login --oauth # legacy same-machine callback
4690
4777
  busabase-cli login --api-key sk_\u2026 # Cloud API key (headless/CI)
4691
4778
  busabase-cli login --base-url http://localhost:15419 # connect to a local server (no auth)
4692
- busabase-cli login --refresh # extend the current session (auto-runs too)`
4779
+ busabase-cli login --refresh # rotate the OAuth token set (auto-runs too)`
4693
4780
  ).action(async (_opts, cmd) => {
4694
4781
  const opts = cmd.optsWithGlobals();
4695
4782
  const config = resolveConfig(opts);
@@ -4704,7 +4791,7 @@ The flags below skip the menu (handy for scripts / CI):
4704
4791
  });
4705
4792
  console.log(render(summary, config.output));
4706
4793
  });
4707
- addGlobalFlags(program.command("logout")).description("Revoke the saved OAuth session (if any) and clear ~/.busabase/.env").action(async (_opts, cmd) => {
4794
+ addGlobalFlags(program.command("logout")).description("Revoke the saved OAuth token family (if any) and clear ~/.busabase/.env").action(async (_opts, cmd) => {
4708
4795
  const opts = cmd.optsWithGlobals();
4709
4796
  const config = resolveConfig(opts);
4710
4797
  state.config = config;
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "./chunk-IO4ARUEI.js";
4
+ } from "./chunk-LRUXSAKS.js";
5
5
 
6
6
  // src/cli.ts
7
7
  process.exit(await runCli(process.argv.slice(2)));
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  HELP,
3
3
  render,
4
4
  runCli
5
- } from "./chunk-IO4ARUEI.js";
5
+ } from "./chunk-LRUXSAKS.js";
6
6
 
7
7
  // src/index.ts
8
8
  import {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "busa-cli",
3
- "version": "0.9.11",
3
+ "version": "0.9.13",
4
4
  "description": "Command-line client for the Busabase OpenAPI REST API. Talks to a local or remote `busabase server`. Short-name alias for busabase-cli.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/busabase/busabase/tree/main/apps/busabase-cli",
@@ -33,7 +33,7 @@
33
33
  "dependencies": {
34
34
  "@zip.js/zip.js": "^2.8.20",
35
35
  "busabase-package": "workspace:*",
36
- "busabase-sdk": "0.9.11",
36
+ "busabase-sdk": "0.9.13",
37
37
  "commander": "^14.0.0",
38
38
  "tar-stream": "^3.1.7",
39
39
  "yaml": "^2.9.0",