@brainbase-labs/cli 0.3.0 → 0.4.1

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 +129 -87
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -49586,6 +49586,77 @@ import path35 from "node:path";
49586
49586
  import os7 from "node:os";
49587
49587
  import fs32 from "node:fs";
49588
49588
  import { execFileSync } from "node:child_process";
49589
+ var SKIP_DIRS = new Set(["node_modules"]);
49590
+ function skillMdPath(dir) {
49591
+ try {
49592
+ const hit = fs32.readdirSync(dir, { withFileTypes: true }).find((e2) => e2.isFile() && e2.name.toLowerCase() === "skill.md");
49593
+ return hit ? path35.join(dir, hit.name) : null;
49594
+ } catch {
49595
+ return null;
49596
+ }
49597
+ }
49598
+ function hasSkillMd(dir) {
49599
+ return skillMdPath(dir) !== null;
49600
+ }
49601
+ function declaredSkillName(dir) {
49602
+ const md = skillMdPath(dir);
49603
+ if (!md)
49604
+ return null;
49605
+ let text;
49606
+ try {
49607
+ text = fs32.readFileSync(md, "utf8");
49608
+ } catch {
49609
+ return null;
49610
+ }
49611
+ const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
49612
+ if (!fm)
49613
+ return null;
49614
+ const m3 = /^name:[ \t]*(.+?)[ \t]*$/m.exec(fm[1]);
49615
+ if (!m3)
49616
+ return null;
49617
+ return m3[1].replace(/^['"]|['"]$/g, "").trim().toLowerCase() || null;
49618
+ }
49619
+ function findSkillDirs(root) {
49620
+ const found = [];
49621
+ const walk = (dir, depth) => {
49622
+ if (hasSkillMd(dir))
49623
+ found.push({ dir, depth });
49624
+ let entries;
49625
+ try {
49626
+ entries = fs32.readdirSync(dir, { withFileTypes: true });
49627
+ } catch {
49628
+ return;
49629
+ }
49630
+ for (const e2 of entries) {
49631
+ if (!e2.isDirectory())
49632
+ continue;
49633
+ if (e2.name.startsWith(".") || SKIP_DIRS.has(e2.name))
49634
+ continue;
49635
+ walk(path35.join(dir, e2.name), depth + 1);
49636
+ }
49637
+ };
49638
+ walk(root, 0);
49639
+ return found.sort((a3, b4) => a3.depth - b4.depth).map((x3) => x3.dir);
49640
+ }
49641
+ function resolveSkillRoot(tmp, subpath) {
49642
+ if (!subpath) {
49643
+ if (hasSkillMd(tmp))
49644
+ return tmp;
49645
+ const skillDirs = findSkillDirs(tmp);
49646
+ return skillDirs.length === 1 ? skillDirs[0] : tmp;
49647
+ }
49648
+ const literal = path35.join(tmp, subpath);
49649
+ const literalIsDir = fs32.existsSync(literal) && fs32.statSync(literal).isDirectory();
49650
+ if (literalIsDir && hasSkillMd(literal))
49651
+ return literal;
49652
+ const wanted = path35.basename(subpath).toLowerCase();
49653
+ const byName = findSkillDirs(tmp).find((d3) => path35.basename(d3).toLowerCase() === wanted || declaredSkillName(d3) === wanted);
49654
+ if (byName)
49655
+ return byName;
49656
+ if (literalIsDir)
49657
+ return literal;
49658
+ throw new Error(`Could not locate skill '${path35.basename(subpath)}' in repo: no ` + `'${subpath}' directory, and no skill (SKILL.md folder) whose name ` + `is '${path35.basename(subpath)}' was found anywhere in it.`);
49659
+ }
49589
49660
  function gitCloneSubpath(opts) {
49590
49661
  const tmp = fs32.mkdtempSync(path35.join(os7.tmpdir(), "bb-skill-"));
49591
49662
  try {
@@ -49600,14 +49671,7 @@ function gitCloneSubpath(opts) {
49600
49671
  const stderr = e2.stderr?.toString() ?? "";
49601
49672
  throw new Error(`git clone failed: ${stderr.trim() || e2.message}`.trim());
49602
49673
  }
49603
- const sourceRoot = opts.subpath ? path35.join(tmp, opts.subpath) : tmp;
49604
- if (!fs32.existsSync(sourceRoot)) {
49605
- throw new Error(`Path not found in repo: ${opts.subpath ?? "/"}`);
49606
- }
49607
- const stat = fs32.statSync(sourceRoot);
49608
- if (!stat.isDirectory()) {
49609
- throw new Error(`Source path is not a directory: ${opts.subpath}`);
49610
- }
49674
+ const sourceRoot = resolveSkillRoot(tmp, opts.subpath);
49611
49675
  ensureDir(opts.destDir);
49612
49676
  copyDir(sourceRoot, opts.destDir);
49613
49677
  } finally {
@@ -49874,59 +49938,52 @@ import path40 from "node:path";
49874
49938
  import os9 from "node:os";
49875
49939
  import fs37 from "node:fs";
49876
49940
  var import_picocolors16 = __toESM(require_picocolors(), 1);
49877
- function parseRef3(input) {
49878
- const at2 = input.indexOf("@");
49879
- if (at2 < 0)
49880
- return { name: input };
49881
- return { name: input.slice(0, at2), version: input.slice(at2 + 1) };
49941
+ function parseName(input) {
49942
+ if (!/^[a-z0-9_-]+\/[a-z0-9_-]+$/i.test(input))
49943
+ return null;
49944
+ const [creator, slug] = input.toLowerCase().split("/");
49945
+ return { creator, slug };
49882
49946
  }
49883
49947
  async function runSkillPublish(cwd2, args) {
49884
49948
  banner("skill publish — send a skill to the registry");
49885
- const candidates = collectSkillCandidates(cwd2);
49886
- if (candidates.length === 0) {
49887
- f2.error("No skills found. Add one with `brainbase skill add <source>` or write one locally first.");
49949
+ const skillDir = path40.resolve(cwd2, args.dir ?? ".");
49950
+ if (!exists(skillDir) || !fs37.statSync(skillDir).isDirectory()) {
49951
+ f2.error(`Not a directory: ${import_picocolors16.default.bold(skillDir)}`);
49888
49952
  return;
49889
49953
  }
49890
- let chosen;
49891
- let suggestedName;
49892
- let suggestedVersion;
49893
- if (args.ref) {
49894
- const { name: name2, version: version2 } = parseRef3(args.ref);
49895
- suggestedName = name2;
49896
- suggestedVersion = version2;
49897
- const slug = name2.split("/").pop();
49898
- const matches = candidates.filter((c2) => c2.slug === slug);
49899
- if (matches.length === 0) {
49900
- f2.error(`No local skill named ${import_picocolors16.default.bold(slug)} to publish.`);
49901
- return;
49902
- }
49903
- chosen = matches[0];
49904
- } else {
49905
- const pick = await ie({
49906
- message: "Which skill do you want to publish?",
49907
- options: candidates.map((c2, i) => ({
49908
- value: String(i),
49909
- label: `${import_picocolors16.default.bold(c2.slug.padEnd(24))} ${import_picocolors16.default.dim(c2.harness + "/" + c2.scope)}`
49910
- }))
49911
- });
49912
- chosen = candidates[Number(ensureNotCancelled(pick))];
49954
+ if (!exists(path40.join(skillDir, "SKILL.md"))) {
49955
+ 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.`);
49956
+ return;
49913
49957
  }
49914
- const existingMarker = readSkillMarker(chosen.dir);
49958
+ const folderSlug = path40.basename(skillDir).toLowerCase();
49959
+ const existingMarker = readSkillMarker(skillDir);
49960
+ let suggestedName;
49915
49961
  if (existingMarker && existingMarker.source.type === "brainbase") {
49916
- suggestedName ??= `${existingMarker.source.creator}/${existingMarker.source.slug}`;
49962
+ suggestedName = `${existingMarker.source.creator}/${existingMarker.source.slug}`;
49963
+ }
49964
+ if (existingMarker && (existingMarker.source.type === "github" || existingMarker.source.type === "git")) {
49965
+ f2.warn(`This skill was installed from ${existingMarker.source.type}. Publishing will fork it under your name.`);
49966
+ }
49967
+ let name = args.name ?? suggestedName;
49968
+ if (name && !parseName(name)) {
49969
+ f2.error(`Invalid --name "${name}". Use creator/slug.`);
49970
+ return;
49917
49971
  }
49918
- let name = suggestedName;
49919
49972
  if (!name) {
49920
49973
  const ans = await te({
49921
49974
  message: "Publish as (creator/slug)",
49922
- placeholder: `gokhan/${chosen.slug}`,
49923
- initialValue: `gokhan/${chosen.slug}`,
49924
- validate: (v3) => /^[a-z0-9_-]+\/[a-z0-9_-]+$/i.test(v3 ?? "") ? undefined : "Use creator/slug"
49975
+ placeholder: `gokhan/${folderSlug}`,
49976
+ initialValue: `gokhan/${folderSlug}`,
49977
+ validate: (v3) => parseName(v3 ?? "") ? undefined : "Use creator/slug"
49925
49978
  });
49926
49979
  name = ensureNotCancelled(ans).toLowerCase();
49927
49980
  }
49928
- const [creator, pkgSlug] = name.toLowerCase().split("/");
49929
- let version = suggestedVersion;
49981
+ const { creator, slug: pkgSlug } = parseName(name);
49982
+ let version = args.version;
49983
+ if (version && !/^\d+\.\d+\.\d+$/.test(version)) {
49984
+ f2.error(`Invalid --skill-version "${version}". Use MAJOR.MINOR.PATCH.`);
49985
+ return;
49986
+ }
49930
49987
  if (!version) {
49931
49988
  const ans = await te({
49932
49989
  message: "Version",
@@ -49979,7 +50036,7 @@ async function runSkillPublish(cwd2, args) {
49979
50036
  owner_user_id: owner.type === "user" ? owner.userId : undefined,
49980
50037
  owner_team_id: owner.type === "team" ? owner.teamId : undefined,
49981
50038
  visibility,
49982
- description: readSkillDescription(chosen.dir)
50039
+ description: readSkillDescription(skillDir)
49983
50040
  });
49984
50041
  } catch (err) {
49985
50042
  if (err instanceof ApiError && err.status === 409) {} else {
@@ -49991,20 +50048,20 @@ async function runSkillPublish(cwd2, args) {
49991
50048
  const tmp = fs37.mkdtempSync(path40.join(os9.tmpdir(), "bb-skill-pub-"));
49992
50049
  const tar = path40.join(tmp, "skill.tgz");
49993
50050
  try {
49994
- const files = collectSkillFiles(chosen.dir).filter((f4) => f4 !== SKILL_MARKER_FILE);
50051
+ const files = collectSkillFiles(skillDir).filter((f4) => f4 !== SKILL_MARKER_FILE);
49995
50052
  if (files.length === 0) {
49996
50053
  f2.error("Skill folder is empty.");
49997
50054
  return;
49998
50055
  }
49999
50056
  const buildSp = de();
50000
50057
  buildSp.start("Building skill bundle…");
50001
- await pack({ rootDir: chosen.dir, outFile: tar, files });
50058
+ await pack({ rootDir: skillDir, outFile: tar, files });
50002
50059
  const sha = await sha256OfFile(tar);
50003
50060
  const size = fs37.statSync(tar).size;
50004
50061
  buildSp.stop(`Bundle ready (${(size / 1024).toFixed(1)} KB).`);
50005
50062
  if (!args.yes) {
50006
50063
  const ok = await se({
50007
- message: `Publish ${import_picocolors16.default.bold(pkgSlug)}@${version} as ${import_picocolors16.default.bold(creator + "/" + pkgSlug)}?`,
50064
+ message: `Publish ${import_picocolors16.default.bold(skillDir)} as ${import_picocolors16.default.bold(creator + "/" + pkgSlug)}@${version}?`,
50008
50065
  initialValue: true
50009
50066
  });
50010
50067
  if (!ensureNotCancelled(ok)) {
@@ -50012,12 +50069,13 @@ async function runSkillPublish(cwd2, args) {
50012
50069
  return;
50013
50070
  }
50014
50071
  }
50015
- const manifest = buildSkillManifest(chosen.dir, {
50072
+ const sourceHarness = args.harness ?? "claude-code";
50073
+ const manifest = buildSkillManifest(skillDir, {
50016
50074
  name: `${creator}/${pkgSlug}`,
50017
50075
  version,
50018
- description: readSkillDescription(chosen.dir),
50019
- sourceHarness: chosen.harness,
50020
- agents: [chosen.harness]
50076
+ description: readSkillDescription(skillDir),
50077
+ sourceHarness,
50078
+ agents: args.harness ? [args.harness] : []
50021
50079
  });
50022
50080
  const upSp = de();
50023
50081
  upSp.start("Uploading…");
@@ -50028,7 +50086,7 @@ async function runSkillPublish(cwd2, args) {
50028
50086
  bundlePath: tar,
50029
50087
  bundleSha256: sha,
50030
50088
  version,
50031
- sourceHarness: chosen.harness
50089
+ sourceHarness
50032
50090
  });
50033
50091
  upSp.stop("Uploaded.");
50034
50092
  } catch (err) {
@@ -50041,7 +50099,7 @@ async function runSkillPublish(cwd2, args) {
50041
50099
  slug: pkgSlug,
50042
50100
  version
50043
50101
  };
50044
- writeSkillMarker(chosen.dir, newSource);
50102
+ writeSkillMarker(skillDir, newSource);
50045
50103
  $e(`${import_picocolors16.default.bold(creator + "/" + pkgSlug)}@${version} published.`);
50046
50104
  f2.info(import_picocolors16.default.dim(`id: ${result.id}`));
50047
50105
  } finally {
@@ -50050,30 +50108,6 @@ async function runSkillPublish(cwd2, args) {
50050
50108
  } catch {}
50051
50109
  }
50052
50110
  }
50053
- function collectSkillCandidates(cwd2) {
50054
- const out = [];
50055
- const sources = [
50056
- { harness: "claude-code", scope: "global", root: scopePaths(cwd2, "global").skills },
50057
- { harness: "claude-code", scope: "project", root: scopePaths(cwd2, "project").skills },
50058
- { harness: "codex", scope: "global", root: scopePaths2(cwd2, "global").skills },
50059
- { harness: "codex", scope: "project", root: scopePaths2(cwd2, "project").skills }
50060
- ];
50061
- for (const s3 of sources) {
50062
- if (!exists(s3.root))
50063
- continue;
50064
- for (const slug of fs37.readdirSync(s3.root)) {
50065
- const dir = path40.join(s3.root, slug);
50066
- if (!exists(path40.join(dir, "SKILL.md")))
50067
- continue;
50068
- const marker = readSkillMarker(dir);
50069
- if (marker && (marker.source.type === "github" || marker.source.type === "git")) {
50070
- continue;
50071
- }
50072
- out.push({ harness: s3.harness, scope: s3.scope, slug, dir });
50073
- }
50074
- }
50075
- return out;
50076
- }
50077
50111
  function collectSkillFiles(skillDir) {
50078
50112
  const out = [];
50079
50113
  function walk(dir, rel) {
@@ -50225,7 +50259,7 @@ async function runSkillSearch(args) {
50225
50259
 
50226
50260
  // src/cli/skill-info.ts
50227
50261
  var import_picocolors19 = __toESM(require_picocolors(), 1);
50228
- function parseRef4(input) {
50262
+ function parseRef3(input) {
50229
50263
  const at2 = input.indexOf("@");
50230
50264
  if (at2 < 0)
50231
50265
  return { name: input };
@@ -50233,7 +50267,7 @@ function parseRef4(input) {
50233
50267
  }
50234
50268
  async function runSkillInfo(args) {
50235
50269
  banner(`skill info — ${import_picocolors19.default.bold(args.ref)}`);
50236
- const { name } = parseRef4(args.ref);
50270
+ const { name } = parseRef3(args.ref);
50237
50271
  const [creator, slug] = name.split("/");
50238
50272
  if (!creator || !slug) {
50239
50273
  console.error("Invalid ref. Expected creator/slug.");
@@ -50331,8 +50365,11 @@ async function runSkill(cwd2, sub, rest, args) {
50331
50365
  }
50332
50366
  case "publish":
50333
50367
  await runSkillPublish(cwd2, {
50334
- ref: rest[0],
50368
+ dir: rest[0],
50369
+ name: args.name,
50370
+ version: args.skillVersion,
50335
50371
  visibility: args.visibility,
50372
+ harness: args.harness,
50336
50373
  yes: args.yes
50337
50374
  });
50338
50375
  return;
@@ -50360,7 +50397,7 @@ function printSkillHelp() {
50360
50397
  out.push(` ${import_picocolors20.default.cyan("remove")} ${import_picocolors20.default.dim("<slug>")} ${import_picocolors20.default.dim("uninstall a skill")}`);
50361
50398
  out.push(` ${import_picocolors20.default.cyan("search")} ${import_picocolors20.default.dim("[query]")} ${import_picocolors20.default.dim("search the brainbase skill registry")}`);
50362
50399
  out.push(` ${import_picocolors20.default.cyan("info")} ${import_picocolors20.default.dim("<creator/slug>")} ${import_picocolors20.default.dim("show registry details for a skill")}`);
50363
- 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")}`);
50400
+ out.push(` ${import_picocolors20.default.cyan("publish")} ${import_picocolors20.default.dim("[dir]")} ${import_picocolors20.default.dim("publish the SKILL.md folder (defaults to .)")}`);
50364
50401
  out.push("");
50365
50402
  out.push(` ${import_picocolors20.default.dim("Source forms accepted by `add`:")}`);
50366
50403
  out.push(` ${import_picocolors20.default.dim("owner/repo github short-form")}`);
@@ -50376,6 +50413,8 @@ function printSkillHelp() {
50376
50413
  out.push(` ${import_picocolors20.default.dim("--category <s>")} for search`);
50377
50414
  out.push(` ${import_picocolors20.default.dim("--agent <h>")} for search (filter by harness)`);
50378
50415
  out.push(` ${import_picocolors20.default.dim("--visibility <v>")} for publish: public | unlisted | private`);
50416
+ out.push(` ${import_picocolors20.default.dim("--name <c/s>")} for publish: creator/slug (skip prompt)`);
50417
+ out.push(` ${import_picocolors20.default.dim("--skill-version <v>")} for publish: MAJOR.MINOR.PATCH (skip prompt)`);
50379
50418
  out.push("");
50380
50419
  console.log(out.join(`
50381
50420
  `));
@@ -54969,7 +55008,7 @@ function help() {
54969
55008
  out.push(` ${import_picocolors38.default.cyan("skill remove")} ${import_picocolors38.default.dim("<slug>")} ${import_picocolors38.default.dim("uninstall a skill")}`);
54970
55009
  out.push(` ${import_picocolors38.default.cyan("skill search")} ${import_picocolors38.default.dim("[query]")} ${import_picocolors38.default.dim("search the brainbase skill registry")}`);
54971
55010
  out.push(` ${import_picocolors38.default.cyan("skill info")} ${import_picocolors38.default.dim("<creator/slug>")} ${import_picocolors38.default.dim("show registry details for a skill")}`);
54972
- 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")}`);
55011
+ out.push(` ${import_picocolors38.default.cyan("skill publish")} ${import_picocolors38.default.dim("[dir]")} ${import_picocolors38.default.dim("publish a SKILL.md folder (defaults to .)")}`);
54973
55012
  out.push("");
54974
55013
  out.push(divider("CLI TOKENS"));
54975
55014
  out.push("");
@@ -55090,6 +55129,7 @@ async function main() {
55090
55129
  const noTracking = hasFlag2(argv, "--no-tracking");
55091
55130
  const graphOnlyFlag = hasFlag2(argv, "--graph-only");
55092
55131
  const nameFlag = getFlag(argv, "--name");
55132
+ const skillVersionFlag = getFlag(argv, "--skill-version");
55093
55133
  const taglineFlag = getFlag(argv, "--tagline");
55094
55134
  const orgIdFlag = getFlag(argv, "--org");
55095
55135
  const teamIdFlag = getFlag(argv, "--team");
@@ -55133,7 +55173,9 @@ async function main() {
55133
55173
  as: asSlug,
55134
55174
  category,
55135
55175
  agent: agentFlag,
55136
- page
55176
+ page,
55177
+ name: nameFlag,
55178
+ skillVersion: skillVersionFlag
55137
55179
  });
55138
55180
  break;
55139
55181
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainbase-labs/cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
5
5
  "type": "module",
6
6
  "bin": {