@brainbase-labs/cli 0.4.0 → 0.4.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 (2) hide show
  1. package/dist/index.js +73 -85
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -33782,7 +33782,7 @@ var BrainbaseSourceSchema = exports_external.object({
33782
33782
  type: exports_external.literal("brainbase"),
33783
33783
  creator: exports_external.string().regex(/^[a-z0-9_-]+$/i),
33784
33784
  slug: exports_external.string().regex(/^[a-z0-9_-]+$/i),
33785
- version: exports_external.string().regex(/^\d+\.\d+\.\d+$/)
33785
+ version: exports_external.string().regex(/^\d+\.\d+\.\d+$/).optional()
33786
33786
  });
33787
33787
  var InlineSourceSchema = exports_external.object({ type: exports_external.literal("inline") });
33788
33788
  var LocalSourceSchema = exports_external.object({ type: exports_external.literal("local") });
@@ -48541,7 +48541,7 @@ function parseSkillSource(input) {
48541
48541
  type: "brainbase",
48542
48542
  creator: bb[1],
48543
48543
  slug: bb[2],
48544
- version: bb[3] ?? "0.0.0"
48544
+ version: bb[3]
48545
48545
  };
48546
48546
  }
48547
48547
  if (s3.startsWith("file://") || s3.startsWith("./") || s3.startsWith("../") || s3.startsWith("/") || s3.startsWith("~")) {
@@ -48616,7 +48616,7 @@ function describeSource(source) {
48616
48616
  case "git":
48617
48617
  return `git:${source.url}${source.ref ? "@" + source.ref : ""}${source.subpath ? " (" + source.subpath + ")" : ""}`;
48618
48618
  case "brainbase":
48619
- return `brainbase://${source.creator}/${source.slug}@${source.version}`;
48619
+ return `brainbase://${source.creator}/${source.slug}${source.version ? "@" + source.version : " (latest)"}`;
48620
48620
  }
48621
48621
  }
48622
48622
 
@@ -49722,14 +49722,24 @@ var brainbaseResolver = {
49722
49722
  if (source.type !== "brainbase") {
49723
49723
  throw new Error("expected brainbase source");
49724
49724
  }
49725
- const remoteVersion = await skillsApi.getVersion(source.creator, source.slug, source.version);
49725
+ let version = source.version;
49726
+ if (!version) {
49727
+ const pkg = await skillsApi.getPackage(source.creator, source.slug);
49728
+ const latest = pkg.latest_version;
49729
+ if (!latest) {
49730
+ throw new Error(`${source.creator}/${source.slug} has no published versions`);
49731
+ }
49732
+ version = latest.version;
49733
+ source.version = version;
49734
+ }
49735
+ const remoteVersion = await skillsApi.getVersion(source.creator, source.slug, version);
49726
49736
  if (remoteVersion.yanked) {
49727
- throw new Error(`${source.creator}/${source.slug}@${source.version} has been yanked${remoteVersion.yanked_reason ? ": " + remoteVersion.yanked_reason : ""}`);
49737
+ throw new Error(`${source.creator}/${source.slug}@${version} has been yanked${remoteVersion.yanked_reason ? ": " + remoteVersion.yanked_reason : ""}`);
49728
49738
  }
49729
49739
  const tmp = fs33.mkdtempSync(path36.join(os8.tmpdir(), "bb-skill-"));
49730
49740
  const tarPath = path36.join(tmp, "skill.tgz");
49731
49741
  try {
49732
- await skillsApi.downloadTarball(source.creator, source.slug, source.version, tarPath);
49742
+ await skillsApi.downloadTarball(source.creator, source.slug, version, tarPath);
49733
49743
  await verifyTarball(tarPath, remoteVersion.bundle_sha256);
49734
49744
  ensureDir(destDir);
49735
49745
  await extract({ tarFile: tarPath, outDir: destDir });
@@ -49938,59 +49948,52 @@ import path40 from "node:path";
49938
49948
  import os9 from "node:os";
49939
49949
  import fs37 from "node:fs";
49940
49950
  var import_picocolors16 = __toESM(require_picocolors(), 1);
49941
- function parseRef3(input) {
49942
- const at2 = input.indexOf("@");
49943
- if (at2 < 0)
49944
- return { name: input };
49945
- return { name: input.slice(0, at2), version: input.slice(at2 + 1) };
49951
+ function parseName(input) {
49952
+ if (!/^[a-z0-9_-]+\/[a-z0-9_-]+$/i.test(input))
49953
+ return null;
49954
+ const [creator, slug] = input.toLowerCase().split("/");
49955
+ return { creator, slug };
49946
49956
  }
49947
49957
  async function runSkillPublish(cwd2, args) {
49948
49958
  banner("skill publish — send a skill to the registry");
49949
- const candidates = collectSkillCandidates(cwd2);
49950
- if (candidates.length === 0) {
49951
- f2.error("No skills found. Add one with `brainbase skill add <source>` or write one locally first.");
49959
+ const skillDir = path40.resolve(cwd2, args.dir ?? ".");
49960
+ if (!exists(skillDir) || !fs37.statSync(skillDir).isDirectory()) {
49961
+ f2.error(`Not a directory: ${import_picocolors16.default.bold(skillDir)}`);
49952
49962
  return;
49953
49963
  }
49954
- let chosen;
49955
- let suggestedName;
49956
- let suggestedVersion;
49957
- if (args.ref) {
49958
- const { name: name2, version: version2 } = parseRef3(args.ref);
49959
- suggestedName = name2;
49960
- suggestedVersion = version2;
49961
- const slug = name2.split("/").pop();
49962
- const matches = candidates.filter((c2) => c2.slug === slug);
49963
- if (matches.length === 0) {
49964
- f2.error(`No local skill named ${import_picocolors16.default.bold(slug)} to publish.`);
49965
- return;
49966
- }
49967
- chosen = matches[0];
49968
- } else {
49969
- const pick = await ie({
49970
- message: "Which skill do you want to publish?",
49971
- options: candidates.map((c2, i) => ({
49972
- value: String(i),
49973
- label: `${import_picocolors16.default.bold(c2.slug.padEnd(24))} ${import_picocolors16.default.dim(c2.harness + "/" + c2.scope)}`
49974
- }))
49975
- });
49976
- chosen = candidates[Number(ensureNotCancelled(pick))];
49964
+ if (!exists(path40.join(skillDir, "SKILL.md"))) {
49965
+ f2.error(`No ${import_picocolors16.default.bold("SKILL.md")} in ${import_picocolors16.default.bold(skillDir)}. Point at a folder that contains one, or create the file first.`);
49966
+ return;
49977
49967
  }
49978
- const existingMarker = readSkillMarker(chosen.dir);
49968
+ const folderSlug = path40.basename(skillDir).toLowerCase();
49969
+ const existingMarker = readSkillMarker(skillDir);
49970
+ let suggestedName;
49979
49971
  if (existingMarker && existingMarker.source.type === "brainbase") {
49980
- suggestedName ??= `${existingMarker.source.creator}/${existingMarker.source.slug}`;
49972
+ suggestedName = `${existingMarker.source.creator}/${existingMarker.source.slug}`;
49973
+ }
49974
+ if (existingMarker && (existingMarker.source.type === "github" || existingMarker.source.type === "git")) {
49975
+ f2.warn(`This skill was installed from ${existingMarker.source.type}. Publishing will fork it under your name.`);
49976
+ }
49977
+ let name = args.name ?? suggestedName;
49978
+ if (name && !parseName(name)) {
49979
+ f2.error(`Invalid --name "${name}". Use creator/slug.`);
49980
+ return;
49981
49981
  }
49982
- let name = suggestedName;
49983
49982
  if (!name) {
49984
49983
  const ans = await te({
49985
49984
  message: "Publish as (creator/slug)",
49986
- placeholder: `gokhan/${chosen.slug}`,
49987
- initialValue: `gokhan/${chosen.slug}`,
49988
- validate: (v3) => /^[a-z0-9_-]+\/[a-z0-9_-]+$/i.test(v3 ?? "") ? undefined : "Use creator/slug"
49985
+ placeholder: `gokhan/${folderSlug}`,
49986
+ initialValue: `gokhan/${folderSlug}`,
49987
+ validate: (v3) => parseName(v3 ?? "") ? undefined : "Use creator/slug"
49989
49988
  });
49990
49989
  name = ensureNotCancelled(ans).toLowerCase();
49991
49990
  }
49992
- const [creator, pkgSlug] = name.toLowerCase().split("/");
49993
- let version = suggestedVersion;
49991
+ const { creator, slug: pkgSlug } = parseName(name);
49992
+ let version = args.version;
49993
+ if (version && !/^\d+\.\d+\.\d+$/.test(version)) {
49994
+ f2.error(`Invalid --skill-version "${version}". Use MAJOR.MINOR.PATCH.`);
49995
+ return;
49996
+ }
49994
49997
  if (!version) {
49995
49998
  const ans = await te({
49996
49999
  message: "Version",
@@ -50043,7 +50046,7 @@ async function runSkillPublish(cwd2, args) {
50043
50046
  owner_user_id: owner.type === "user" ? owner.userId : undefined,
50044
50047
  owner_team_id: owner.type === "team" ? owner.teamId : undefined,
50045
50048
  visibility,
50046
- description: readSkillDescription(chosen.dir)
50049
+ description: readSkillDescription(skillDir)
50047
50050
  });
50048
50051
  } catch (err) {
50049
50052
  if (err instanceof ApiError && err.status === 409) {} else {
@@ -50055,20 +50058,20 @@ async function runSkillPublish(cwd2, args) {
50055
50058
  const tmp = fs37.mkdtempSync(path40.join(os9.tmpdir(), "bb-skill-pub-"));
50056
50059
  const tar = path40.join(tmp, "skill.tgz");
50057
50060
  try {
50058
- const files = collectSkillFiles(chosen.dir).filter((f4) => f4 !== SKILL_MARKER_FILE);
50061
+ const files = collectSkillFiles(skillDir).filter((f4) => f4 !== SKILL_MARKER_FILE);
50059
50062
  if (files.length === 0) {
50060
50063
  f2.error("Skill folder is empty.");
50061
50064
  return;
50062
50065
  }
50063
50066
  const buildSp = de();
50064
50067
  buildSp.start("Building skill bundle…");
50065
- await pack({ rootDir: chosen.dir, outFile: tar, files });
50068
+ await pack({ rootDir: skillDir, outFile: tar, files });
50066
50069
  const sha = await sha256OfFile(tar);
50067
50070
  const size = fs37.statSync(tar).size;
50068
50071
  buildSp.stop(`Bundle ready (${(size / 1024).toFixed(1)} KB).`);
50069
50072
  if (!args.yes) {
50070
50073
  const ok = await se({
50071
- message: `Publish ${import_picocolors16.default.bold(pkgSlug)}@${version} as ${import_picocolors16.default.bold(creator + "/" + pkgSlug)}?`,
50074
+ message: `Publish ${import_picocolors16.default.bold(skillDir)} as ${import_picocolors16.default.bold(creator + "/" + pkgSlug)}@${version}?`,
50072
50075
  initialValue: true
50073
50076
  });
50074
50077
  if (!ensureNotCancelled(ok)) {
@@ -50076,12 +50079,13 @@ async function runSkillPublish(cwd2, args) {
50076
50079
  return;
50077
50080
  }
50078
50081
  }
50079
- const manifest = buildSkillManifest(chosen.dir, {
50082
+ const sourceHarness = args.harness ?? "claude-code";
50083
+ const manifest = buildSkillManifest(skillDir, {
50080
50084
  name: `${creator}/${pkgSlug}`,
50081
50085
  version,
50082
- description: readSkillDescription(chosen.dir),
50083
- sourceHarness: chosen.harness,
50084
- agents: [chosen.harness]
50086
+ description: readSkillDescription(skillDir),
50087
+ sourceHarness,
50088
+ agents: args.harness ? [args.harness] : []
50085
50089
  });
50086
50090
  const upSp = de();
50087
50091
  upSp.start("Uploading…");
@@ -50092,7 +50096,7 @@ async function runSkillPublish(cwd2, args) {
50092
50096
  bundlePath: tar,
50093
50097
  bundleSha256: sha,
50094
50098
  version,
50095
- sourceHarness: chosen.harness
50099
+ sourceHarness
50096
50100
  });
50097
50101
  upSp.stop("Uploaded.");
50098
50102
  } catch (err) {
@@ -50105,7 +50109,7 @@ async function runSkillPublish(cwd2, args) {
50105
50109
  slug: pkgSlug,
50106
50110
  version
50107
50111
  };
50108
- writeSkillMarker(chosen.dir, newSource);
50112
+ writeSkillMarker(skillDir, newSource);
50109
50113
  $e(`${import_picocolors16.default.bold(creator + "/" + pkgSlug)}@${version} published.`);
50110
50114
  f2.info(import_picocolors16.default.dim(`id: ${result.id}`));
50111
50115
  } finally {
@@ -50114,30 +50118,6 @@ async function runSkillPublish(cwd2, args) {
50114
50118
  } catch {}
50115
50119
  }
50116
50120
  }
50117
- function collectSkillCandidates(cwd2) {
50118
- const out = [];
50119
- const sources = [
50120
- { harness: "claude-code", scope: "global", root: scopePaths(cwd2, "global").skills },
50121
- { harness: "claude-code", scope: "project", root: scopePaths(cwd2, "project").skills },
50122
- { harness: "codex", scope: "global", root: scopePaths2(cwd2, "global").skills },
50123
- { harness: "codex", scope: "project", root: scopePaths2(cwd2, "project").skills }
50124
- ];
50125
- for (const s3 of sources) {
50126
- if (!exists(s3.root))
50127
- continue;
50128
- for (const slug of fs37.readdirSync(s3.root)) {
50129
- const dir = path40.join(s3.root, slug);
50130
- if (!exists(path40.join(dir, "SKILL.md")))
50131
- continue;
50132
- const marker = readSkillMarker(dir);
50133
- if (marker && (marker.source.type === "github" || marker.source.type === "git")) {
50134
- continue;
50135
- }
50136
- out.push({ harness: s3.harness, scope: s3.scope, slug, dir });
50137
- }
50138
- }
50139
- return out;
50140
- }
50141
50121
  function collectSkillFiles(skillDir) {
50142
50122
  const out = [];
50143
50123
  function walk(dir, rel) {
@@ -50289,7 +50269,7 @@ async function runSkillSearch(args) {
50289
50269
 
50290
50270
  // src/cli/skill-info.ts
50291
50271
  var import_picocolors19 = __toESM(require_picocolors(), 1);
50292
- function parseRef4(input) {
50272
+ function parseRef3(input) {
50293
50273
  const at2 = input.indexOf("@");
50294
50274
  if (at2 < 0)
50295
50275
  return { name: input };
@@ -50297,7 +50277,7 @@ function parseRef4(input) {
50297
50277
  }
50298
50278
  async function runSkillInfo(args) {
50299
50279
  banner(`skill info — ${import_picocolors19.default.bold(args.ref)}`);
50300
- const { name } = parseRef4(args.ref);
50280
+ const { name } = parseRef3(args.ref);
50301
50281
  const [creator, slug] = name.split("/");
50302
50282
  if (!creator || !slug) {
50303
50283
  console.error("Invalid ref. Expected creator/slug.");
@@ -50395,8 +50375,11 @@ async function runSkill(cwd2, sub, rest, args) {
50395
50375
  }
50396
50376
  case "publish":
50397
50377
  await runSkillPublish(cwd2, {
50398
- ref: rest[0],
50378
+ dir: rest[0],
50379
+ name: args.name,
50380
+ version: args.skillVersion,
50399
50381
  visibility: args.visibility,
50382
+ harness: args.harness,
50400
50383
  yes: args.yes
50401
50384
  });
50402
50385
  return;
@@ -50424,7 +50407,7 @@ function printSkillHelp() {
50424
50407
  out.push(` ${import_picocolors20.default.cyan("remove")} ${import_picocolors20.default.dim("<slug>")} ${import_picocolors20.default.dim("uninstall a skill")}`);
50425
50408
  out.push(` ${import_picocolors20.default.cyan("search")} ${import_picocolors20.default.dim("[query]")} ${import_picocolors20.default.dim("search the brainbase skill registry")}`);
50426
50409
  out.push(` ${import_picocolors20.default.cyan("info")} ${import_picocolors20.default.dim("<creator/slug>")} ${import_picocolors20.default.dim("show registry details for a skill")}`);
50427
- out.push(` ${import_picocolors20.default.cyan("publish")} ${import_picocolors20.default.dim("[creator/slug][@v]")} ${import_picocolors20.default.dim("publish a local skill to the registry")}`);
50410
+ out.push(` ${import_picocolors20.default.cyan("publish")} ${import_picocolors20.default.dim("[dir]")} ${import_picocolors20.default.dim("publish the SKILL.md folder (defaults to .)")}`);
50428
50411
  out.push("");
50429
50412
  out.push(` ${import_picocolors20.default.dim("Source forms accepted by `add`:")}`);
50430
50413
  out.push(` ${import_picocolors20.default.dim("owner/repo github short-form")}`);
@@ -50440,6 +50423,8 @@ function printSkillHelp() {
50440
50423
  out.push(` ${import_picocolors20.default.dim("--category <s>")} for search`);
50441
50424
  out.push(` ${import_picocolors20.default.dim("--agent <h>")} for search (filter by harness)`);
50442
50425
  out.push(` ${import_picocolors20.default.dim("--visibility <v>")} for publish: public | unlisted | private`);
50426
+ out.push(` ${import_picocolors20.default.dim("--name <c/s>")} for publish: creator/slug (skip prompt)`);
50427
+ out.push(` ${import_picocolors20.default.dim("--skill-version <v>")} for publish: MAJOR.MINOR.PATCH (skip prompt)`);
50443
50428
  out.push("");
50444
50429
  console.log(out.join(`
50445
50430
  `));
@@ -55033,7 +55018,7 @@ function help() {
55033
55018
  out.push(` ${import_picocolors38.default.cyan("skill remove")} ${import_picocolors38.default.dim("<slug>")} ${import_picocolors38.default.dim("uninstall a skill")}`);
55034
55019
  out.push(` ${import_picocolors38.default.cyan("skill search")} ${import_picocolors38.default.dim("[query]")} ${import_picocolors38.default.dim("search the brainbase skill registry")}`);
55035
55020
  out.push(` ${import_picocolors38.default.cyan("skill info")} ${import_picocolors38.default.dim("<creator/slug>")} ${import_picocolors38.default.dim("show registry details for a skill")}`);
55036
- out.push(` ${import_picocolors38.default.cyan("skill publish")} ${import_picocolors38.default.dim("[creator/slug][@v]")} ${import_picocolors38.default.dim("publish a local skill to the registry")}`);
55021
+ out.push(` ${import_picocolors38.default.cyan("skill publish")} ${import_picocolors38.default.dim("[dir]")} ${import_picocolors38.default.dim("publish a SKILL.md folder (defaults to .)")}`);
55037
55022
  out.push("");
55038
55023
  out.push(divider("CLI TOKENS"));
55039
55024
  out.push("");
@@ -55154,6 +55139,7 @@ async function main() {
55154
55139
  const noTracking = hasFlag2(argv, "--no-tracking");
55155
55140
  const graphOnlyFlag = hasFlag2(argv, "--graph-only");
55156
55141
  const nameFlag = getFlag(argv, "--name");
55142
+ const skillVersionFlag = getFlag(argv, "--skill-version");
55157
55143
  const taglineFlag = getFlag(argv, "--tagline");
55158
55144
  const orgIdFlag = getFlag(argv, "--org");
55159
55145
  const teamIdFlag = getFlag(argv, "--team");
@@ -55197,7 +55183,9 @@ async function main() {
55197
55183
  as: asSlug,
55198
55184
  category,
55199
55185
  agent: agentFlag,
55200
- page
55186
+ page,
55187
+ name: nameFlag,
55188
+ skillVersion: skillVersionFlag
55201
55189
  });
55202
55190
  break;
55203
55191
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainbase-labs/cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
5
5
  "type": "module",
6
6
  "bin": {