busa-cli 0.97.0 → 0.98.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.
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as checkForUpdate, n as pkgVersion, r as runCli } from "./run-C7tO-SDN.js";
2
+ import { i as checkForUpdate, n as pkgVersion, r as runCli } from "./run-BVOkW2wU.js";
3
3
  //#region src/flush-stdio.ts
4
4
  /**
5
5
  * Wait for stdout/stderr to reach the other side before the process is torn down.
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { a as render, i as checkForUpdate, n as pkgVersion, r as runCli, t as HELP } from "./run-C7tO-SDN.js";
1
+ import { a as render, i as checkForUpdate, n as pkgVersion, r as runCli, t as HELP } from "./run-BVOkW2wU.js";
2
2
  import { DEFAULT_BASE_URL, createBusabaseClient, normalizeBaseUrl } from "busabase-sdk";
3
3
  export { DEFAULT_BASE_URL, HELP, checkForUpdate, createBusabaseClient, normalizeBaseUrl, pkgVersion, render, runCli };
@@ -18,7 +18,8 @@ import { checkAirApp } from "busabase-sdk/airapp-check";
18
18
  import { fileURLToPath } from "node:url";
19
19
  import { Uint8ArrayReader, ZipReader } from "@zip.js/zip.js";
20
20
  import QRCode from "qrcode";
21
- const LocaleSchema = z.enum({
21
+ //#region ../../packages/openlib/i18n/i18n.ts
22
+ const i18n = {
22
23
  defaultLocale: "en",
23
24
  locales: [
24
25
  "en",
@@ -46,7 +47,8 @@ const LocaleSchema = z.enum({
46
47
  "vi",
47
48
  "pt"
48
49
  ]
49
- }.locales);
50
+ };
51
+ const LocaleSchema = z.enum(i18n.locales);
50
52
  //#endregion
51
53
  //#region ../../packages/openlib/i18n/i-string.ts
52
54
  /**
@@ -1598,7 +1600,7 @@ const driveNodeType = makeFileTreeNodeType({
1598
1600
  routeBase: "drives",
1599
1601
  tag: "Drives",
1600
1602
  entryFile: "README.md",
1601
- publicAccess: "no"
1603
+ publicAccess: "detail"
1602
1604
  });
1603
1605
  //#endregion
1604
1606
  //#region ../../packages/busabase-contract/src/domains/file-node/definition.ts
@@ -1697,7 +1699,7 @@ const htmlNodeType = {
1697
1699
  capabilities: {
1698
1700
  hasDetail: true,
1699
1701
  creatable: true,
1700
- publicAccess: "no"
1702
+ publicAccess: "detail"
1701
1703
  },
1702
1704
  operations: [{
1703
1705
  kind: "html_document_update",
@@ -1715,7 +1717,7 @@ const skillNodeType = makeFileTreeNodeType({
1715
1717
  routeBase: "skills",
1716
1718
  tag: "Skills",
1717
1719
  entryFile: "SKILL.md",
1718
- publicAccess: "no"
1720
+ publicAccess: "detail"
1719
1721
  });
1720
1722
  //#endregion
1721
1723
  //#region ../../packages/busabase-contract/src/domains/whiteboard/definition.ts
@@ -2817,6 +2819,162 @@ const TASK_SUPERSEDED_MCP_TOOLS = [
2817
2819
  "bases_list_deleted_fields"
2818
2820
  ];
2819
2821
  //#endregion
2822
+ //#region ../../packages/busabase-contract/src/contract/agent-client.ts
2823
+ /**
2824
+ * Agent client attribution — "which agent program made this change".
2825
+ *
2826
+ * The API key says WHO is accountable (its name, its owner). On one machine,
2827
+ * several agents often share one key (`~/.busabase/.env`), so the key alone
2828
+ * cannot tell "Claude Code added this" from "Codex added this". A caller names
2829
+ * its agent client with ONE request header; busabase-cli detects it from the
2830
+ * environment and sends it on every request.
2831
+ *
2832
+ * It is SELF-REPORTED: a label for people reading the history, never identity,
2833
+ * authorization or anything security relevant. The server normalizes it with
2834
+ * `normalizeAgentClient` and stores only the normalized `{ id, name }`.
2835
+ */
2836
+ const BUSABASE_AGENT_HEADER = "x-busabase-agent";
2837
+ const AgentClientVOSchema = z.object({
2838
+ /** Normalized, lowercase `[a-z0-9-]`, at most 40 characters (e.g. `codex`). */
2839
+ id: z.string(),
2840
+ /** Display name: a known agent's product name, otherwise derived from the id. */
2841
+ name: z.string()
2842
+ });
2843
+ const MAX_AGENT_ID_CHARS = 40;
2844
+ /** Longer raw values are not an agent name; don't spend work on them. */
2845
+ const MAX_RAW_CHARS = 256;
2846
+ /**
2847
+ * Agents whose names we know, keyed by every id they are commonly reported
2848
+ * as. Only agents whose environment markers were verified (see busabase-cli
2849
+ * `agent-client.ts`) get a canonical entry; anything else still works through
2850
+ * the generic path below.
2851
+ */
2852
+ const KNOWN_AGENTS = {
2853
+ "claude-code": {
2854
+ id: "claude-code",
2855
+ name: "Claude Code"
2856
+ },
2857
+ claudecode: {
2858
+ id: "claude-code",
2859
+ name: "Claude Code"
2860
+ },
2861
+ codex: {
2862
+ id: "codex",
2863
+ name: "Codex"
2864
+ },
2865
+ "codex-cli": {
2866
+ id: "codex",
2867
+ name: "Codex"
2868
+ },
2869
+ "openai-codex": {
2870
+ id: "codex",
2871
+ name: "Codex"
2872
+ },
2873
+ opencode: {
2874
+ id: "opencode",
2875
+ name: "OpenCode"
2876
+ },
2877
+ "gemini-cli": {
2878
+ id: "gemini-cli",
2879
+ name: "Gemini CLI"
2880
+ },
2881
+ gemini: {
2882
+ id: "gemini-cli",
2883
+ name: "Gemini CLI"
2884
+ },
2885
+ pi: {
2886
+ id: "pi",
2887
+ name: "Pi"
2888
+ },
2889
+ "pi-coding-agent": {
2890
+ id: "pi",
2891
+ name: "Pi"
2892
+ }
2893
+ };
2894
+ /**
2895
+ * `AI_AGENT` is a cross-tool convention shaped `<tool>_<version>_agent`
2896
+ * (Claude Code: `claude-code_2-1-304_agent`); some tools set just `<tool>`
2897
+ * (OpenCode: `opencode`). Keep the tool part.
2898
+ */
2899
+ const stripAiAgentConvention = (value) => {
2900
+ return /^([^_]+)_[^_]*_agent$/.exec(value)?.[1] ?? value;
2901
+ };
2902
+ const toDisplayName = (id) => id.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
2903
+ /**
2904
+ * Normalize a self-reported agent client (the `x-busabase-agent` header, or a
2905
+ * raw `AI_AGENT` value) to `{ id, name }`, or null when there is nothing
2906
+ * usable. Known agents map to their product name; anything else is reduced to
2907
+ * a lowercase `[a-z0-9-]` id of at most 40 characters with a name derived from
2908
+ * it — so the output never carries markup, whitespace or control characters.
2909
+ */
2910
+ const normalizeAgentClient = (raw) => {
2911
+ const trimmed = raw?.trim();
2912
+ if (!trimmed || trimmed.length > MAX_RAW_CHARS) return null;
2913
+ const id = stripAiAgentConvention(trimmed.toLowerCase()).replace(/[^a-z0-9]+/g, "-").slice(0, MAX_AGENT_ID_CHARS).replace(/^-+|-+$/g, "");
2914
+ if (!id) return null;
2915
+ return KNOWN_AGENTS[id] ?? {
2916
+ id,
2917
+ name: toDisplayName(id)
2918
+ };
2919
+ };
2920
+ //#endregion
2921
+ //#region src/agent-client.ts
2922
+ const isSet = (value) => {
2923
+ const trimmed = value?.trim().toLowerCase();
2924
+ return Boolean(trimmed) && trimmed !== "0" && trimmed !== "false";
2925
+ };
2926
+ /**
2927
+ * Environment markers each agent puts on the commands it runs. Checked in
2928
+ * order; only markers backed by evidence are listed:
2929
+ * - Claude Code: `CLAUDECODE=1` (measured in a command Claude Code ran).
2930
+ * - Codex CLI: `CODEX_SESSION_ID` / `CODEX_THREAD_ID` (measured, Codex 0.163).
2931
+ * - OpenCode: `OPENCODE=1` (its shell tool sets it — packages/core/src/tool/plugin/shell.ts).
2932
+ * - Gemini CLI: `GEMINI_CLI=1` (ShellExecutionService, @google/gemini-cli-core 0.65).
2933
+ * - Pi: `PI_CODING_AGENT=true` (set in its own process, inherited by its bash
2934
+ * tool; @mariozechner/pi-coding-agent 0.73).
2935
+ * Anything else is left to the generic `AI_AGENT` or the `BUSABASE_AGENT` override.
2936
+ */
2937
+ const AGENT_MARKERS = [
2938
+ {
2939
+ id: "claude-code",
2940
+ vars: ["CLAUDECODE"]
2941
+ },
2942
+ {
2943
+ id: "codex",
2944
+ vars: ["CODEX_SESSION_ID", "CODEX_THREAD_ID"]
2945
+ },
2946
+ {
2947
+ id: "opencode",
2948
+ vars: ["OPENCODE"]
2949
+ },
2950
+ {
2951
+ id: "gemini-cli",
2952
+ vars: ["GEMINI_CLI"]
2953
+ },
2954
+ {
2955
+ id: "pi",
2956
+ vars: ["PI_CODING_AGENT"]
2957
+ }
2958
+ ];
2959
+ const normalizedId = (value) => normalizeAgentClient(value)?.id;
2960
+ /**
2961
+ * Which agent client is running this CLI, as the id sent in `x-busabase-agent`.
2962
+ * Precedence: an explicit `BUSABASE_AGENT` → a specific agent's marker → the
2963
+ * cross-tool `AI_AGENT` convention (`<tool>_<version>_agent`) → none.
2964
+ * Self-reported attribution only — it labels the change, it grants nothing.
2965
+ */
2966
+ const detectAgentClient = (env) => {
2967
+ const explicit = normalizedId(env.BUSABASE_AGENT);
2968
+ if (explicit) return explicit;
2969
+ for (const marker of AGENT_MARKERS) if (marker.vars.some((name) => isSet(env[name]))) return marker.id;
2970
+ return normalizedId(env.AI_AGENT);
2971
+ };
2972
+ /** The request header for the detected agent client, or undefined when there is none. */
2973
+ const agentClientHeaders = (env) => {
2974
+ const agent = detectAgentClient(env);
2975
+ return agent ? { [BUSABASE_AGENT_HEADER]: agent } : void 0;
2976
+ };
2977
+ //#endregion
2820
2978
  //#region src/config-file.ts
2821
2979
  /**
2822
2980
  * Credential + settings storage for every Busabase client.
@@ -6223,6 +6381,27 @@ const TemplateSecretSchema = z.object({
6223
6381
  description: z.string().default(""),
6224
6382
  required: z.boolean().default(true)
6225
6383
  });
6384
+ /**
6385
+ * One locale's own gallery media — the value side of `template.locales`.
6386
+ *
6387
+ * Both fields optional and independent: a locale that only re-recorded the clip
6388
+ * declares just `video` and keeps the default screenshots. Paths are
6389
+ * package-relative in `busabase.json` and absolute URLs on the catalog card;
6390
+ * the shape is the same, which is why the card VO reuses this schema.
6391
+ */
6392
+ const TemplateLocaleMediaSchema = z.object({
6393
+ /** Replaces the default list WHOLE for this locale — never merged per index. */
6394
+ screenshots: z.array(z.string()).optional(),
6395
+ video: z.string().optional()
6396
+ });
6397
+ /**
6398
+ * Per-locale media keyed by locale (`"zh-CN"`, `"ja"`).
6399
+ *
6400
+ * Keys are plain strings on purpose, not the supported-locale enum: a template
6401
+ * written for a locale this server does not know yet must still install. The
6402
+ * audit warns about such keys; the schema never rejects them.
6403
+ */
6404
+ const TemplateLocalesSchema = z.record(z.string(), TemplateLocaleMediaSchema);
6226
6405
  const TemplateManifestSchema = z.object({
6227
6406
  /** Template Center category, e.g. `"crm"`, `"email"`, `"content"`. */
6228
6407
  category: z.string().min(1),
@@ -6243,6 +6422,21 @@ const TemplateManifestSchema = z.object({
6243
6422
  */
6244
6423
  video: z.string().optional(),
6245
6424
  /**
6425
+ * Localized gallery media, the App Store / Google Play rule: a viewer whose
6426
+ * locale has an entry sees that entry's `screenshots` / `video`; anything the
6427
+ * entry leaves out, and every locale without an entry, falls back to the two
6428
+ * fields above. Those stay the English/default set — they are what every
6429
+ * deployed server and CLI already reads, so their meaning cannot change.
6430
+ *
6431
+ * `{ "zh-CN": { "screenshots": ["assets/screenshots/cover-zh-CN.webp"],
6432
+ * "video": "assets/recordings/busa-rnd-zh-CN.mp4" } }`
6433
+ *
6434
+ * Additive by construction: an older parser strips the key (see the note on
6435
+ * `video`), so an older server simply shows the defaults. Selection lives in
6436
+ * one place — `pickTemplateMedia` in `domains/templates/media`.
6437
+ */
6438
+ locales: TemplateLocalesSchema.optional(),
6439
+ /**
6246
6440
  * Ready-made prompts shown after install ("Ask agent" prefills the first).
6247
6441
  *
6248
6442
  * They are the difference between a folder of tables and something a user can
@@ -6957,7 +7151,12 @@ const sourceAttributionSchema = z.object({
6957
7151
  ownerName: z.string().nullable(),
6958
7152
  channel: sourceChannelSchema.nullable(),
6959
7153
  /** Present only when the write declared a playbook the server could validate. */
6960
- playbook: PlaybookAttributionVOSchema.nullable().optional()
7154
+ playbook: PlaybookAttributionVOSchema.nullable().optional(),
7155
+ /**
7156
+ * Present only when the write named its agent client (`x-busabase-agent`;
7157
+ * busabase-cli sends it automatically). Self-reported — a label, not identity.
7158
+ */
7159
+ agent: AgentClientVOSchema.nullable().optional()
6961
7160
  });
6962
7161
  const commitSchema = z.object({
6963
7162
  id: z.string(),
@@ -8274,6 +8473,25 @@ const TEXT_EXTENSIONS = /\.(?:js|mjs|cjs|ts|mts|cts|json|ndjson|md|html|css|ya?m
8274
8473
  const IS_ASSERTION_CODE = /(?:^|\/)(?:scripts\/check\.mjs|tests?\/|[^/]*\.test\.[cm]?[jt]s)$/;
8275
8474
  /** Lockfiles carry integrity hashes that are not ours to interpret. */
8276
8475
  const NOT_SOURCE = /(?:^|\/)(?:package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb)$/;
8476
+ /**
8477
+ * The locales Busabase renders its interface in — the same list `iStringParse`'s
8478
+ * `LocaleSchema` is built from, never a second copy. A `template.locales` key
8479
+ * outside it is legal (the schema accepts any string so an install never fails
8480
+ * over one) but no viewer is ever served it.
8481
+ */
8482
+ const SUPPORTED_LOCALES = i18n.locales;
8483
+ /**
8484
+ * A file name that marks itself as one non-English locale's media —
8485
+ * `cover-zh-CN.webp`, `busa-rnd-ja.mp4` — following the `<name>-<locale>.<ext>`
8486
+ * convention. Case-insensitive because file systems and authors disagree on
8487
+ * `zh-cn` versus `zh-CN`; the separator may be `-`, `_` or `.`.
8488
+ */
8489
+ const LOCALIZED_FILE = new RegExp(`[-_.](${SUPPORTED_LOCALES.filter((locale) => locale !== i18n.defaultLocale).join("|")})\\.[a-z0-9]+$`, "i");
8490
+ const localeMarkedIn = (path) => {
8491
+ const match = LOCALIZED_FILE.exec(path);
8492
+ if (!match) return void 0;
8493
+ return SUPPORTED_LOCALES.find((locale) => locale.toLowerCase() === match[1].toLowerCase());
8494
+ };
8277
8495
  const relativePaths = (files, root = "") => {
8278
8496
  const prefix = root ? `${root.replace(/\/+$/, "")}/` : "";
8279
8497
  const out = [];
@@ -8311,6 +8529,24 @@ const auditPackage = (tree, files, options = {}) => {
8311
8529
  for (const screenshot of tree.manifest.template?.screenshots ?? []) if (!present.has(screenshot)) error("package/screenshot-missing", `busabase.json declares screenshot "${screenshot}", which is not in the package. The Template Center card would render a broken image.`);
8312
8530
  const video = tree.manifest.template?.video;
8313
8531
  if (video && !present.has(video)) error("package/video-missing", `busabase.json declares video "${video}", which is not in the package. The Template Center detail page would show a play button that leads nowhere.`);
8532
+ for (const [locale, media] of Object.entries(tree.manifest.template?.locales ?? {})) {
8533
+ if (!SUPPORTED_LOCALES.includes(locale)) warn("package/locale-unknown", `busabase.json declares media for locale "${locale}", which is not one Busabase shows its interface in (${SUPPORTED_LOCALES.join(", ")}). It still installs, but no viewer is ever served it — check the spelling (keys are exact, e.g. "zh-CN", not "zh").`);
8534
+ else if (locale === i18n.defaultLocale) warn("package/locale-default-language", `busabase.json declares \`template.locales.${locale}\`, but the default \`screenshots\` / \`video\` already are the English set — and they, not \`locales.${locale}\`, are what every language without its own entry falls back to. Put English media in the defaults and drop \`locales.${locale}\`, or the two drift apart.`);
8535
+ for (const screenshot of media.screenshots ?? []) if (!present.has(screenshot)) error("package/screenshot-missing", `busabase.json declares ${locale} screenshot "${screenshot}" (template.locales.${locale}.screenshots), which is not in the package. A ${locale} viewer's Template Center card would render a broken image.`);
8536
+ if (media.video && !present.has(media.video)) error("package/video-missing", `busabase.json declares ${locale} video "${media.video}" (template.locales.${locale}.video), which is not in the package. A ${locale} viewer's detail page would show a play button that leads nowhere.`);
8537
+ }
8538
+ const defaults = [...(tree.manifest.template?.screenshots ?? []).map((path) => ({
8539
+ path,
8540
+ field: "screenshots"
8541
+ })), ...video ? [{
8542
+ path: video,
8543
+ field: "video"
8544
+ }] : []];
8545
+ for (const { path, field } of defaults) {
8546
+ const locale = localeMarkedIn(path);
8547
+ if (!locale) continue;
8548
+ warn("package/localized-default-media", `"${path}" is in the default \`template.${field}\`, but its name marks it ${locale}. The defaults are the English set every language without its own entry falls back to; move it to \`template.locales.${locale}.${field}\`.`);
8549
+ }
8314
8550
  for (const relative of paths) {
8315
8551
  const base = relative.slice(relative.lastIndexOf("/") + 1);
8316
8552
  if (/^\.env(?!\.example$)(?:\.|$)/.test(base)) error("package/dotenv", `${relative} is part of the package. Credentials belong in Vault, never in something people install.`);
@@ -10623,6 +10859,41 @@ const assertSelfContained = (tree, options) => {
10623
10859
  }
10624
10860
  };
10625
10861
  //#endregion
10862
+ //#region ../../packages/busabase-contract/src/domains/templates/media.ts
10863
+ const nonEmptyStrings = (value) => {
10864
+ if (!Array.isArray(value)) return void 0;
10865
+ const list = value.filter((item) => typeof item === "string" && item !== "");
10866
+ return list.length > 0 ? list : void 0;
10867
+ };
10868
+ const nonEmptyString = (value) => typeof value === "string" && value !== "" ? value : void 0;
10869
+ const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
10870
+ /**
10871
+ * Normalize an untrusted `locales` value, or `undefined` when nothing usable is
10872
+ * left.
10873
+ *
10874
+ * Never throws — the catalog is fetched JSON from a repository the server does
10875
+ * not control, and one malformed entry must not blank the gallery for every
10876
+ * other card. A non-object `locales`, a non-object entry, a non-array
10877
+ * `screenshots` or a non-string `video` is ignored, and the viewer gets the
10878
+ * defaults. An empty `screenshots: []` or `video: ""` counts as "not declared":
10879
+ * a locale that ships no gallery should see the default one, not a placeholder.
10880
+ */
10881
+ const parseTemplateLocales = (raw) => {
10882
+ if (!isPlainObject(raw)) return void 0;
10883
+ const locales = {};
10884
+ for (const [locale, entry] of Object.entries(raw)) {
10885
+ if (!locale || !isPlainObject(entry)) continue;
10886
+ const screenshots = nonEmptyStrings(entry.screenshots);
10887
+ const video = nonEmptyString(entry.video);
10888
+ if (!screenshots && !video) continue;
10889
+ locales[locale] = {
10890
+ ...screenshots ? { screenshots } : {},
10891
+ ...video ? { video } : {}
10892
+ };
10893
+ }
10894
+ return Object.keys(locales).length > 0 ? locales : void 0;
10895
+ };
10896
+ //#endregion
10626
10897
  //#region ../../packages/busabase-package/src/plan.ts
10627
10898
  /**
10628
10899
  * Install planning: package tree + the target space's current state → a plan, a
@@ -11080,6 +11351,7 @@ const discoverPackages = (files) => {
11080
11351
  }
11081
11352
  const validation = validateTemplate(tree);
11082
11353
  const claimsTemplate = Boolean(tree.rootSkill) || Boolean(tree.manifest.template);
11354
+ const locales = parseTemplateLocales(tree.manifest.template?.locales);
11083
11355
  found.push({
11084
11356
  subdir,
11085
11357
  name: tree.manifest.name,
@@ -11093,6 +11365,7 @@ const discoverPackages = (files) => {
11093
11365
  ...validation.risk ? { risk: validation.risk } : {},
11094
11366
  screenshots: tree.manifest.template?.screenshots ?? [],
11095
11367
  ...tree.manifest.template?.video ? { video: tree.manifest.template.video } : {},
11368
+ ...locales ? { locales } : {},
11096
11369
  tags: [.../* @__PURE__ */ new Set([...tree.manifest.tags, ...tree.manifest.template?.tags ?? []])],
11097
11370
  agentPrompts: tree.manifest.template?.agentPrompts ?? [],
11098
11371
  ...tree.manifest.version ? { version: tree.manifest.version } : {},
@@ -11320,6 +11593,7 @@ const toEntry = (found, airapps) => ({
11320
11593
  tags: found.tags ?? [],
11321
11594
  screenshots: found.screenshots,
11322
11595
  ...found.video ? { video: found.video } : {},
11596
+ ...found.locales ? { locales: found.locales } : {},
11323
11597
  agentPrompts: found.agentPrompts ?? [],
11324
11598
  ...found.version ? { version: found.version } : {},
11325
11599
  ...found.author ? { author: found.author } : {},
@@ -14364,6 +14638,7 @@ function resolveConfig(opts, { strict = true } = {}) {
14364
14638
  spaceId: opts.spaceId ?? process.env.BUSABASE_SPACE_ID ?? file.BUSABASE_SPACE_ID,
14365
14639
  sourceChannel: "cli",
14366
14640
  playbook: resolvePlaybook(opts.playbook, strict),
14641
+ headers: agentClientHeaders(process.env),
14367
14642
  output: opts.output ?? configuredOutput() ?? "text",
14368
14643
  fetch: withRetry(fetch, { retries })
14369
14644
  };
@@ -14536,7 +14811,8 @@ async function rawFetch(config, method, path, body) {
14536
14811
  ...config.apiKey ? { authorization: `Bearer ${config.apiKey}` } : {},
14537
14812
  ...config.spaceId ? { "x-busabase-space": config.spaceId } : {},
14538
14813
  "x-busabase-channel": "cli",
14539
- ...config.playbook ? { [BUSABASE_PLAYBOOK_HEADER]: config.playbook } : {}
14814
+ ...config.playbook ? { [BUSABASE_PLAYBOOK_HEADER]: config.playbook } : {},
14815
+ ...typeof config.headers === "object" ? config.headers : {}
14540
14816
  },
14541
14817
  body: body !== void 0 ? JSON.stringify(body) : void 0
14542
14818
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "busa-cli",
3
- "version": "0.97.0",
3
+ "version": "0.98.0",
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.97.0",
36
+ "busabase-sdk": "0.98.0",
37
37
  "commander": "^14.0.0",
38
38
  "qrcode": "^1.5.4",
39
39
  "tar-stream": "^3.1.7",