@brainbase-labs/cli 0.9.0 → 0.10.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 +406 -141
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -29449,7 +29449,7 @@ function padStart(s, n) {
29449
29449
  // package.json
29450
29450
  var package_default = {
29451
29451
  name: "@brainbase-labs/cli",
29452
- version: "0.9.0",
29452
+ version: "0.10.1",
29453
29453
  description: "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
29454
29454
  type: "module",
29455
29455
  bin: {
@@ -29921,6 +29921,73 @@ function scopePaths(cwd, scope) {
29921
29921
  return scope === "global" ? globalPaths() : projectPaths(cwd);
29922
29922
  }
29923
29923
 
29924
+ // src/harnesses/claude-code/settings.ts
29925
+ function readSettings(file) {
29926
+ return readJsonOr(file, {});
29927
+ }
29928
+ function writeSettings(file, settings) {
29929
+ writeJson(file, settings);
29930
+ }
29931
+ function setMcpServer(file, name, entry) {
29932
+ const s = readSettings(file);
29933
+ if (!s.mcpServers)
29934
+ s.mcpServers = {};
29935
+ s.mcpServers[name] = entry;
29936
+ writeSettings(file, s);
29937
+ }
29938
+ function removeMcpServer(file, name) {
29939
+ if (!exists(file))
29940
+ return;
29941
+ const s = readSettings(file);
29942
+ if (s.mcpServers && name in s.mcpServers) {
29943
+ delete s.mcpServers[name];
29944
+ writeSettings(file, s);
29945
+ }
29946
+ }
29947
+ function getMcpServer(file, name) {
29948
+ const s = readSettings(file);
29949
+ return s.mcpServers?.[name];
29950
+ }
29951
+ function listMcpServers(file) {
29952
+ return readSettings(file).mcpServers ?? {};
29953
+ }
29954
+ function normalizeMcpPayload(payload) {
29955
+ const out = { ...payload };
29956
+ delete out.is_enabled;
29957
+ const hasUrl = typeof out.url === "string" && out.url.length > 0;
29958
+ const hasCommand = typeof out.command === "string" && out.command.length > 0;
29959
+ if (!out.type) {
29960
+ if (hasUrl)
29961
+ out.type = "http";
29962
+ else if (hasCommand)
29963
+ out.type = "stdio";
29964
+ }
29965
+ if (out.type === "http" || out.type === "sse") {
29966
+ if (Array.isArray(out.args) && out.args.length === 0)
29967
+ delete out.args;
29968
+ if (out.env && typeof out.env === "object" && Object.keys(out.env).length === 0) {
29969
+ delete out.env;
29970
+ }
29971
+ if (out.headers && typeof out.headers === "object" && Object.keys(out.headers).length === 0) {
29972
+ delete out.headers;
29973
+ }
29974
+ }
29975
+ if (out.type === "stdio") {
29976
+ delete out.url;
29977
+ delete out.headers;
29978
+ }
29979
+ return out;
29980
+ }
29981
+ function listMcpServersFromMcpJson(file) {
29982
+ return readJsonOr(file, {}).mcpServers ?? {};
29983
+ }
29984
+ function listMcpServersFromClaudeJson(file, projectKey) {
29985
+ const data = readJsonOr(file, {});
29986
+ if (projectKey)
29987
+ return data.projects?.[projectKey]?.mcpServers ?? {};
29988
+ return data.mcpServers ?? {};
29989
+ }
29990
+
29924
29991
  // src/harnesses/claude-code/inventory.ts
29925
29992
  import fs4 from "node:fs";
29926
29993
  import path6 from "node:path";
@@ -33961,73 +34028,6 @@ function manifestSourceForPack(source) {
33961
34028
  return source.type === "local" ? { type: "inline" } : source;
33962
34029
  }
33963
34030
 
33964
- // src/harnesses/claude-code/settings.ts
33965
- function readSettings(file) {
33966
- return readJsonOr(file, {});
33967
- }
33968
- function writeSettings(file, settings) {
33969
- writeJson(file, settings);
33970
- }
33971
- function setMcpServer(file, name, entry) {
33972
- const s = readSettings(file);
33973
- if (!s.mcpServers)
33974
- s.mcpServers = {};
33975
- s.mcpServers[name] = entry;
33976
- writeSettings(file, s);
33977
- }
33978
- function removeMcpServer(file, name) {
33979
- if (!exists(file))
33980
- return;
33981
- const s = readSettings(file);
33982
- if (s.mcpServers && name in s.mcpServers) {
33983
- delete s.mcpServers[name];
33984
- writeSettings(file, s);
33985
- }
33986
- }
33987
- function getMcpServer(file, name) {
33988
- const s = readSettings(file);
33989
- return s.mcpServers?.[name];
33990
- }
33991
- function listMcpServers(file) {
33992
- return readSettings(file).mcpServers ?? {};
33993
- }
33994
- function normalizeMcpPayload(payload) {
33995
- const out = { ...payload };
33996
- delete out.is_enabled;
33997
- const hasUrl = typeof out.url === "string" && out.url.length > 0;
33998
- const hasCommand = typeof out.command === "string" && out.command.length > 0;
33999
- if (!out.type) {
34000
- if (hasUrl)
34001
- out.type = "http";
34002
- else if (hasCommand)
34003
- out.type = "stdio";
34004
- }
34005
- if (out.type === "http" || out.type === "sse") {
34006
- if (Array.isArray(out.args) && out.args.length === 0)
34007
- delete out.args;
34008
- if (out.env && typeof out.env === "object" && Object.keys(out.env).length === 0) {
34009
- delete out.env;
34010
- }
34011
- if (out.headers && typeof out.headers === "object" && Object.keys(out.headers).length === 0) {
34012
- delete out.headers;
34013
- }
34014
- }
34015
- if (out.type === "stdio") {
34016
- delete out.url;
34017
- delete out.headers;
34018
- }
34019
- return out;
34020
- }
34021
- function listMcpServersFromMcpJson(file) {
34022
- return readJsonOr(file, {}).mcpServers ?? {};
34023
- }
34024
- function listMcpServersFromClaudeJson(file, projectKey) {
34025
- const data = readJsonOr(file, {});
34026
- if (projectKey)
34027
- return data.projects?.[projectKey]?.mcpServers ?? {};
34028
- return data.mcpServers ?? {};
34029
- }
34030
-
34031
34031
  // src/harnesses/claude-code/inventory.ts
34032
34032
  function firstLine(file, max = 200) {
34033
34033
  try {
@@ -35070,6 +35070,11 @@ var claudeCode = {
35070
35070
  async function installClaudeCodeWithCtx(components, opts, templateName) {
35071
35071
  return installClaudeCode(components, opts, { templateName });
35072
35072
  }
35073
+ function removeClaudeCodeMcp(slugs, opts) {
35074
+ const { mcpStore } = scopePaths(opts.cwd, opts.scope);
35075
+ for (const slug of slugs)
35076
+ removeMcpServer(mcpStore, slug);
35077
+ }
35073
35078
 
35074
35079
  // src/harnesses/codex/paths.ts
35075
35080
  import path13 from "node:path";
@@ -35101,10 +35106,6 @@ function isBundledSkillDir(name) {
35101
35106
  return name === ".system" || name.startsWith(".");
35102
35107
  }
35103
35108
 
35104
- // src/harnesses/codex/inventory.ts
35105
- import fs12 from "node:fs";
35106
- import path15 from "node:path";
35107
-
35108
35109
  // src/harnesses/codex/config-toml.ts
35109
35110
  import fs11 from "node:fs";
35110
35111
 
@@ -36269,6 +36270,8 @@ function listMcpServers2(file) {
36269
36270
  }
36270
36271
 
36271
36272
  // src/harnesses/codex/inventory.ts
36273
+ import fs12 from "node:fs";
36274
+ import path15 from "node:path";
36272
36275
  function firstLine2(file, max = 200) {
36273
36276
  try {
36274
36277
  const head = fs12.readFileSync(file, "utf8").slice(0, 2000);
@@ -36882,6 +36885,11 @@ var codex = {
36882
36885
  async function installCodexWithCtx(components, opts, templateName) {
36883
36886
  return installCodex(components, opts, { templateName });
36884
36887
  }
36888
+ function removeCodexMcp(slugs, opts) {
36889
+ const { config } = scopePaths2(opts.cwd, opts.scope);
36890
+ for (const slug of slugs)
36891
+ removeMcpServer2(config, slug);
36892
+ }
36885
36893
 
36886
36894
  // src/harnesses/kafka/paths.ts
36887
36895
  import path19 from "node:path";
@@ -36919,10 +36927,6 @@ function scopePaths3(cwd, scope) {
36919
36927
  return scope === "global" ? globalPaths3() : projectPaths3(cwd);
36920
36928
  }
36921
36929
 
36922
- // src/harnesses/kafka/inventory.ts
36923
- import fs15 from "node:fs";
36924
- import path20 from "node:path";
36925
-
36926
36930
  // src/harnesses/kafka/settings.ts
36927
36931
  function readSettings2(file) {
36928
36932
  return readJsonOr(file, {});
@@ -36972,6 +36976,22 @@ function setMcpServer3(file, name, entry) {
36972
36976
  }
36973
36977
  writeSettings2(file, s);
36974
36978
  }
36979
+ function removeMcpServer3(file, name) {
36980
+ if (!exists(file))
36981
+ return;
36982
+ const s = readSettings2(file);
36983
+ const raw = s.mcp_servers ?? s.mcpServers;
36984
+ if (!raw)
36985
+ return;
36986
+ if (Array.isArray(raw)) {
36987
+ s.mcp_servers = raw.filter((e2) => e2.name !== name);
36988
+ } else {
36989
+ delete raw[name];
36990
+ s.mcp_servers = raw;
36991
+ }
36992
+ delete s.mcpServers;
36993
+ writeSettings2(file, s);
36994
+ }
36975
36995
  function normalizeMcpPayload2(payload) {
36976
36996
  const out = { ...payload };
36977
36997
  delete out.is_enabled;
@@ -37001,6 +37021,8 @@ function normalizeMcpPayload2(payload) {
37001
37021
  }
37002
37022
 
37003
37023
  // src/harnesses/kafka/inventory.ts
37024
+ import fs15 from "node:fs";
37025
+ import path20 from "node:path";
37004
37026
  function firstLine3(file, max = 200) {
37005
37027
  try {
37006
37028
  const head = fs15.readFileSync(file, "utf8").slice(0, 2000);
@@ -37650,6 +37672,11 @@ var kafka = {
37650
37672
  async function installKafkaWithCtx(components, opts, templateName) {
37651
37673
  return installKafka(components, opts, { templateName });
37652
37674
  }
37675
+ function removeKafkaMcp(slugs, opts) {
37676
+ const { mcpStore } = scopePaths3(opts.cwd, opts.scope);
37677
+ for (const slug of slugs)
37678
+ removeMcpServer3(mcpStore, slug);
37679
+ }
37653
37680
 
37654
37681
  // src/harnesses/index.ts
37655
37682
  var adapters = [claudeCode, codex, kafka];
@@ -37679,6 +37706,17 @@ async function detectHarnesses(cwd) {
37679
37706
  }
37680
37707
  return out;
37681
37708
  }
37709
+ function runHarnessRemoveMcp(harnessId, slugs, opts) {
37710
+ if (slugs.length === 0)
37711
+ return;
37712
+ const canonical = normalizeHarnessId(harnessId);
37713
+ if (canonical === "claude-code")
37714
+ return removeClaudeCodeMcp(slugs, opts);
37715
+ if (canonical === "codex")
37716
+ return removeCodexMcp(slugs, opts);
37717
+ if (canonical === "kafka")
37718
+ return removeKafkaMcp(slugs, opts);
37719
+ }
37682
37720
 
37683
37721
  // src/core/registry.ts
37684
37722
  import fs18 from "node:fs";
@@ -48632,6 +48670,8 @@ async function runRemove(cwd2, args) {
48632
48670
  if (c2.type === "mcp" && c2.settingsFile && c2.embeddedKey) {
48633
48671
  if (inst.harness === "codex")
48634
48672
  removeMcpServer2(c2.settingsFile, c2.embeddedKey);
48673
+ else if (inst.harness === "kafka")
48674
+ removeMcpServer3(c2.settingsFile, c2.embeddedKey);
48635
48675
  else
48636
48676
  removeMcpServer(c2.settingsFile, c2.embeddedKey);
48637
48677
  } else if (c2.type === "instruction") {
@@ -50373,6 +50413,7 @@ async function runSkillPublish(cwd2, args) {
50373
50413
  writeSkillMarker(skillDir, newSource);
50374
50414
  $e(`${import_picocolors16.default.bold(creator + "/" + pkgSlug)}@${version} published.`);
50375
50415
  f2.info(import_picocolors16.default.dim(`id: ${result2.id}`));
50416
+ f2.info(import_picocolors16.default.dim(`To use it on an agent: add ${import_picocolors16.default.cyan(`source: registry:${creator}/${pkgSlug}`)} under ${import_picocolors16.default.cyan("skills:")} in brainbase.agent.yaml, then ${import_picocolors16.default.cyan("brainbase agent push")}. Agents already linking it pick up ${version} on their next push.`));
50376
50417
  } finally {
50377
50418
  try {
50378
50419
  fs37.rmSync(tmp, { recursive: true, force: true });
@@ -50504,6 +50545,9 @@ async function runSkillUpdate(cwd2, args) {
50504
50545
  }
50505
50546
  writeSkillMarker(target.dir, marker.source);
50506
50547
  $e(`${import_picocolors17.default.bold(args.slug)} updated.`);
50548
+ if (marker.source.type === "brainbase") {
50549
+ f2.info(import_picocolors17.default.dim(`This refreshed your local copy only. Agents using this skill update on their next ${import_picocolors17.default.cyan("brainbase agent push")} (run it in the agent's folder).`));
50550
+ }
50507
50551
  }
50508
50552
 
50509
50553
  // src/cli/skill-search.ts
@@ -51098,6 +51142,11 @@ var McpEntrySchema = exports_external.object({
51098
51142
  headers: exports_external.record(exports_external.string()).optional(),
51099
51143
  is_enabled: exports_external.boolean().optional()
51100
51144
  });
51145
+ var CapabilitiesSchema = exports_external.object({
51146
+ memory: exports_external.boolean().optional(),
51147
+ browser: exports_external.boolean().optional(),
51148
+ slack: exports_external.boolean().optional()
51149
+ });
51101
51150
  var AgentManifestSchema = exports_external.object({
51102
51151
  schema: exports_external.literal(1),
51103
51152
  id: exports_external.string().min(1).optional(),
@@ -51108,6 +51157,7 @@ var AgentManifestSchema = exports_external.object({
51108
51157
  playbooks: exports_external.array(PlaybookSchema).default([]),
51109
51158
  skills: exports_external.array(SkillEntrySchema).default([]),
51110
51159
  mcp: exports_external.array(McpEntrySchema).default([]),
51160
+ capabilities: CapabilitiesSchema.optional(),
51111
51161
  commands: exports_external.array(exports_external.record(exports_external.unknown())).optional(),
51112
51162
  hooks: exports_external.array(exports_external.record(exports_external.unknown())).optional(),
51113
51163
  files: exports_external.array(exports_external.record(exports_external.unknown())).optional()
@@ -51894,6 +51944,39 @@ import os11 from "node:os";
51894
51944
  import crypto3 from "node:crypto";
51895
51945
  var import_picocolors24 = __toESM(require_picocolors(), 1);
51896
51946
 
51947
+ // src/core/memory-mcp.ts
51948
+ var DEFAULT_MEMORY_MCP_BASE = "https://brainbase-memory-mcp.onrender.com";
51949
+ var MEMORY_MCP_SLUG = "brainbase-memory";
51950
+ function memoryMcpBaseUrl() {
51951
+ const envOverride = process.env.BRAINBASE_MEMORY_MCP_URL;
51952
+ if (envOverride)
51953
+ return envOverride.replace(/\/+$/, "");
51954
+ return DEFAULT_MEMORY_MCP_BASE;
51955
+ }
51956
+ function memoryMcpUrl() {
51957
+ return `${memoryMcpBaseUrl()}/t/\${BRAINBASE_THREAD_ID}/mcp`;
51958
+ }
51959
+ function memoryMcpPayload() {
51960
+ return {
51961
+ url: memoryMcpUrl(),
51962
+ headers: {
51963
+ Authorization: "Bearer ${BRAINBASE_TOKEN}"
51964
+ }
51965
+ };
51966
+ }
51967
+ function buildMemoryMcpComponent(scope) {
51968
+ return {
51969
+ type: "mcp",
51970
+ slug: MEMORY_MCP_SLUG,
51971
+ scope,
51972
+ rootDir: "",
51973
+ description: "This agent's persistent SQL memory database (provision, query, " + "inspect schema). Scoped to this agent only.",
51974
+ meta: { mcp: memoryMcpPayload() },
51975
+ payload: memoryMcpPayload(),
51976
+ checksum: "builtin:brainbase-memory:v1"
51977
+ };
51978
+ }
51979
+
51897
51980
  // src/core/orchestration-mcp.ts
51898
51981
  var DEFAULT_ORCHESTRATION_MCP_BASE = "https://brainbase-orchestration-mcp.onrender.com";
51899
51982
  var ORCHESTRATION_MCP_SLUG = "brainbase-orchestration";
@@ -51927,39 +52010,109 @@ function buildOrchestrationMcpComponent(scope) {
51927
52010
  };
51928
52011
  }
51929
52012
 
51930
- // src/core/memory-mcp.ts
51931
- var DEFAULT_MEMORY_MCP_BASE = "https://brainbase-memory-mcp.onrender.com";
51932
- var MEMORY_MCP_SLUG = "brainbase-memory";
51933
- function memoryMcpBaseUrl() {
51934
- const envOverride = process.env.BRAINBASE_MEMORY_MCP_URL;
52013
+ // src/core/slack-mcp.ts
52014
+ var DEFAULT_SLACK_MCP_BASE = "https://brainbase-slack-mcp.onrender.com";
52015
+ var SLACK_MCP_SLUG = "brainbase-slack";
52016
+ function slackMcpBaseUrl() {
52017
+ const envOverride = process.env.BRAINBASE_SLACK_MCP_URL;
51935
52018
  if (envOverride)
51936
52019
  return envOverride.replace(/\/+$/, "");
51937
- return DEFAULT_MEMORY_MCP_BASE;
52020
+ return DEFAULT_SLACK_MCP_BASE;
51938
52021
  }
51939
- function memoryMcpUrl() {
51940
- return `${memoryMcpBaseUrl()}/t/\${BRAINBASE_THREAD_ID}/mcp`;
52022
+ function slackMcpUrl() {
52023
+ return `${slackMcpBaseUrl()}/t/\${BRAINBASE_THREAD_ID}/mcp`;
51941
52024
  }
51942
- function memoryMcpPayload() {
52025
+ function slackMcpPayload() {
51943
52026
  return {
51944
- url: memoryMcpUrl(),
52027
+ url: slackMcpUrl(),
51945
52028
  headers: {
51946
52029
  Authorization: "Bearer ${BRAINBASE_TOKEN}"
51947
52030
  }
51948
52031
  };
51949
52032
  }
51950
- function buildMemoryMcpComponent(scope) {
52033
+ function buildSlackMcpComponent(scope) {
51951
52034
  return {
51952
52035
  type: "mcp",
51953
- slug: MEMORY_MCP_SLUG,
52036
+ slug: SLACK_MCP_SLUG,
51954
52037
  scope,
51955
52038
  rootDir: "",
51956
- description: "This agent's persistent SQL memory database (provision, query, " + "inspect schema). Scoped to this agent only.",
51957
- meta: { mcp: memoryMcpPayload() },
51958
- payload: memoryMcpPayload(),
51959
- checksum: "builtin:brainbase-memory:v1"
52039
+ description: "This agent's Slack connector (read channels, send messages, manage " + "the workspace it is installed in). Scoped to this agent only.",
52040
+ meta: { mcp: slackMcpPayload() },
52041
+ payload: slackMcpPayload(),
52042
+ checksum: "builtin:brainbase-slack:v1"
51960
52043
  };
51961
52044
  }
51962
52045
 
52046
+ // src/core/browser-mcp.ts
52047
+ var DEFAULT_BROWSER_MCP_BASE = "https://brainbase-browser-mcp.onrender.com";
52048
+ var BROWSER_MCP_SLUG = "brainbase-browser";
52049
+ function browserMcpBaseUrl() {
52050
+ const envOverride = process.env.BRAINBASE_BROWSER_MCP_URL;
52051
+ if (envOverride)
52052
+ return envOverride.replace(/\/+$/, "");
52053
+ return DEFAULT_BROWSER_MCP_BASE;
52054
+ }
52055
+ function browserMcpUrl() {
52056
+ return `${browserMcpBaseUrl()}/t/\${BRAINBASE_THREAD_ID}/mcp`;
52057
+ }
52058
+ function browserMcpPayload() {
52059
+ return {
52060
+ url: browserMcpUrl(),
52061
+ headers: {
52062
+ Authorization: "Bearer ${BRAINBASE_TOKEN}"
52063
+ }
52064
+ };
52065
+ }
52066
+ function buildBrowserMcpComponent(scope) {
52067
+ return {
52068
+ type: "mcp",
52069
+ slug: BROWSER_MCP_SLUG,
52070
+ scope,
52071
+ rootDir: "",
52072
+ description: "This agent's headless browser (navigate, read pages, fill forms). " + "Scoped to this agent only.",
52073
+ meta: { mcp: browserMcpPayload() },
52074
+ payload: browserMcpPayload(),
52075
+ checksum: "builtin:brainbase-browser:v1"
52076
+ };
52077
+ }
52078
+
52079
+ // src/core/builtin-mcps.ts
52080
+ function capabilitiesFromAgent(agent) {
52081
+ return {
52082
+ memory: agent.memory_enabled !== false,
52083
+ browser: agent.browser_enabled !== false,
52084
+ slack: agent.slack_connected === true
52085
+ };
52086
+ }
52087
+ function capabilitiesFromManifest(manifest) {
52088
+ const c2 = manifest?.capabilities ?? {};
52089
+ return {
52090
+ memory: c2.memory !== false,
52091
+ browser: c2.browser !== false,
52092
+ slack: c2.slack === true
52093
+ };
52094
+ }
52095
+ function resolveBuiltinMcps(input) {
52096
+ const { caps, declaredSlugs, scope } = input;
52097
+ const gated = [
52098
+ { slug: ORCHESTRATION_MCP_SLUG, enabled: true, build: buildOrchestrationMcpComponent },
52099
+ { slug: MEMORY_MCP_SLUG, enabled: caps.memory, build: buildMemoryMcpComponent },
52100
+ { slug: BROWSER_MCP_SLUG, enabled: caps.browser, build: buildBrowserMcpComponent },
52101
+ { slug: SLACK_MCP_SLUG, enabled: caps.slack, build: buildSlackMcpComponent }
52102
+ ];
52103
+ const install = [];
52104
+ const removeSlugs = [];
52105
+ for (const g3 of gated) {
52106
+ if (declaredSlugs.has(g3.slug))
52107
+ continue;
52108
+ if (g3.enabled)
52109
+ install.push(g3.build(scope));
52110
+ else
52111
+ removeSlugs.push(g3.slug);
52112
+ }
52113
+ return { install, removeSlugs };
52114
+ }
52115
+
51963
52116
  // src/cli/sync.ts
51964
52117
  async function runSync(cwd2, args) {
51965
52118
  banner("sync — bring in the latest changes from your team");
@@ -51986,11 +52139,21 @@ async function runSync(cwd2, args) {
51986
52139
  }
51987
52140
  const prevState = readSyncState(cwd2);
51988
52141
  const diff2 = computeDiff(manifest.components, prevState);
51989
- const cloudHasOrchestrationMcp = manifest.components.some((c2) => c2.type === "mcp" && c2.slug === ORCHESTRATION_MCP_SLUG);
51990
- const needOrchestrationMcpInstall = !cloudHasOrchestrationMcp;
51991
- const cloudHasMemoryMcp = manifest.components.some((c2) => c2.type === "mcp" && c2.slug === MEMORY_MCP_SLUG);
51992
- const needMemoryMcpInstall = !cloudHasMemoryMcp;
51993
- if (diff2.added.length === 0 && diff2.upstreamUpdated.length === 0 && diff2.localModified.length === 0 && diff2.deletedUpstream.length === 0 && !needOrchestrationMcpInstall && !needMemoryMcpInstall) {
52142
+ const scope = args.scope ?? "project";
52143
+ let caps;
52144
+ try {
52145
+ caps = capabilitiesFromManifest(readManifest(cwd2));
52146
+ } catch (err) {
52147
+ f2.error(err.message);
52148
+ return;
52149
+ }
52150
+ const declaredMcpSlugs = new Set(manifest.components.filter((c2) => c2.type === "mcp").map((c2) => c2.slug));
52151
+ const { install: builtinInstall, removeSlugs: builtinRemoveSlugs } = resolveBuiltinMcps({
52152
+ caps,
52153
+ declaredSlugs: declaredMcpSlugs,
52154
+ scope
52155
+ });
52156
+ if (diff2.added.length === 0 && diff2.upstreamUpdated.length === 0 && diff2.localModified.length === 0 && diff2.deletedUpstream.length === 0 && builtinInstall.length === 0 && builtinRemoveSlugs.length === 0) {
51994
52157
  f2.info(`${sym.same} You're up to date.`);
51995
52158
  writeSyncState(cwd2, {
51996
52159
  schemaVersion: 1,
@@ -52054,7 +52217,6 @@ async function runSync(cwd2, args) {
52054
52217
  });
52055
52218
  }
52056
52219
  const adapter = getAdapter(adapterId);
52057
- const scope = args.scope ?? "project";
52058
52220
  const keepLocal = new Set;
52059
52221
  if (diff2.localModified.length > 0) {
52060
52222
  requireInteractive(`${diff2.localModified.length} component(s) changed both locally and in the cloud — run \`brainbase sync\` in an interactive terminal to resolve them (or revert your local changes).`);
@@ -52092,14 +52254,7 @@ async function runSync(cwd2, args) {
52092
52254
  payload: proxifyMcpPayload(c2.meta?.mcp),
52093
52255
  checksum: c2.hash
52094
52256
  }));
52095
- const hasUserOrchestrationMcp = toInstall.some((c2) => c2.type === "mcp" && c2.slug === ORCHESTRATION_MCP_SLUG);
52096
- if (!hasUserOrchestrationMcp) {
52097
- toInstall.push(buildOrchestrationMcpComponent(scope));
52098
- }
52099
- const hasUserMemoryMcp = toInstall.some((c2) => c2.type === "mcp" && c2.slug === MEMORY_MCP_SLUG);
52100
- if (!hasUserMemoryMcp) {
52101
- toInstall.push(buildMemoryMcpComponent(scope));
52102
- }
52257
+ toInstall.push(...builtinInstall);
52103
52258
  const justInstalledPaths = new Map;
52104
52259
  if (toInstall.length > 0) {
52105
52260
  const installSpinner = de();
@@ -52119,6 +52274,9 @@ async function runSync(cwd2, args) {
52119
52274
  f2.warn(`Skipped: ${result2.skipped.map((s3) => `${s3.type}/${s3.slug} (${s3.reason})`).join(", ")}`);
52120
52275
  }
52121
52276
  }
52277
+ if (builtinRemoveSlugs.length > 0) {
52278
+ runHarnessRemoveMcp(adapter.id, builtinRemoveSlugs, { cwd: cwd2, scope });
52279
+ }
52122
52280
  if (diff2.deletedUpstream.length > 0) {
52123
52281
  for (const removed of diff2.deletedUpstream) {
52124
52282
  let goAhead = autoProceed(args.yes);
@@ -52495,7 +52653,8 @@ function threeWayDiff(input) {
52495
52653
  } else if (present.local && !present.cloud && present.lock) {
52496
52654
  status = "removed-cloud";
52497
52655
  } else {
52498
- const localChanged = localHash !== null && lockHash !== undefined && localHash !== lockHash;
52656
+ const sourceChanged = !!local && local.hash === null && !!local.declaredSource && lock?.source !== undefined && lock.source !== local.declaredSource;
52657
+ const localChanged = sourceChanged || localHash !== null && lockHash !== undefined && localHash !== lockHash;
52499
52658
  const cloudChanged = cloudHash !== undefined && lockHash !== undefined && cloudHash !== lockHash;
52500
52659
  if (localChanged && cloudChanged)
52501
52660
  status = "modified-both";
@@ -52708,11 +52867,14 @@ async function runAgentPull(cwd2, args) {
52708
52867
  else
52709
52868
  toInstallKeys.add(r2.key);
52710
52869
  }
52711
- const cloudHasOrchestrationMcp = cloud.components.some((c2) => c2.type === "mcp" && c2.slug === ORCHESTRATION_MCP_SLUG);
52712
- const needOrchestrationMcpInstall = !cloudHasOrchestrationMcp;
52713
- const cloudHasMemoryMcp = cloud.components.some((c2) => c2.type === "mcp" && c2.slug === MEMORY_MCP_SLUG);
52714
- const needMemoryMcpInstall = !cloudHasMemoryMcp;
52715
- if (toInstallKeys.size === 0 && toRemoveKeys.size === 0 && !needOrchestrationMcpInstall && !needMemoryMcpInstall && !override) {
52870
+ const caps = capabilitiesFromAgent(cloudAgent);
52871
+ const declaredMcpSlugs = new Set(cloud.components.filter((c2) => c2.type === "mcp").map((c2) => c2.slug));
52872
+ const { install: builtinInstall, removeSlugs: builtinRemoveSlugs } = resolveBuiltinMcps({
52873
+ caps,
52874
+ declaredSlugs: declaredMcpSlugs,
52875
+ scope: args.scope ?? "project"
52876
+ });
52877
+ if (toInstallKeys.size === 0 && toRemoveKeys.size === 0 && builtinInstall.length === 0 && builtinRemoveSlugs.length === 0 && !override) {
52716
52878
  f2.info(`You're up to date.`);
52717
52879
  writeLink(cwd2, buildLinkFromAgent(cloudAgent, harness, readLink(cwd2)));
52718
52880
  writeSyncState(cwd2, buildLockFromCloud(agentId, cloud, lock, cloudAgent));
@@ -52766,7 +52928,7 @@ async function runAgentPull(cwd2, args) {
52766
52928
  const stageRoot = stageManifestComponents(installComponents);
52767
52929
  const justInstalledPaths = new Map;
52768
52930
  try {
52769
- if (installComponents.length > 0 || needOrchestrationMcpInstall || needMemoryMcpInstall) {
52931
+ if (installComponents.length > 0 || builtinInstall.length > 0) {
52770
52932
  const installSpinner = de();
52771
52933
  installSpinner.start("Applying updates…");
52772
52934
  const toInstall = installComponents.map((c2) => ({
@@ -52780,12 +52942,7 @@ async function runAgentPull(cwd2, args) {
52780
52942
  checksum: c2.hash,
52781
52943
  source: skillSourceFromMeta(c2)
52782
52944
  }));
52783
- if (needOrchestrationMcpInstall) {
52784
- toInstall.push(buildOrchestrationMcpComponent(scope));
52785
- }
52786
- if (needMemoryMcpInstall) {
52787
- toInstall.push(buildMemoryMcpComponent(scope));
52788
- }
52945
+ toInstall.push(...builtinInstall);
52789
52946
  const opts = {
52790
52947
  cwd: cwd2,
52791
52948
  scope,
@@ -52801,6 +52958,9 @@ async function runAgentPull(cwd2, args) {
52801
52958
  f2.warn(`Skipped: ${result2.skipped.map((s3) => `${s3.type}/${s3.slug} (${s3.reason})`).join(", ")}`);
52802
52959
  }
52803
52960
  }
52961
+ if (builtinRemoveSlugs.length > 0) {
52962
+ runHarnessRemoveMcp(harness, builtinRemoveSlugs, { cwd: cwd2, scope });
52963
+ }
52804
52964
  for (const r2 of removeRows) {
52805
52965
  const prior = lock?.components.find((c2) => c2.type === r2.type && c2.slug === r2.slug);
52806
52966
  if (!prior)
@@ -53053,6 +53213,7 @@ function mergeManifest(cwd2, prev, cloud, cloudAgent, harness) {
53053
53213
  entry.is_enabled = payload.is_enabled;
53054
53214
  return entry;
53055
53215
  });
53216
+ const caps = capabilitiesFromAgent(cloudAgent);
53056
53217
  return {
53057
53218
  schema: 1,
53058
53219
  id: cloudAgent.id,
@@ -53065,7 +53226,8 @@ function mergeManifest(cwd2, prev, cloud, cloudAgent, harness) {
53065
53226
  ...entrypoint ? { entrypoint } : {},
53066
53227
  playbooks,
53067
53228
  skills,
53068
- mcp
53229
+ mcp,
53230
+ capabilities: { memory: caps.memory, browser: caps.browser, slack: caps.slack }
53069
53231
  };
53070
53232
  }
53071
53233
  function materializeEntrypoint(cwd2, cloudEntrypoint, prev) {
@@ -53256,7 +53418,7 @@ var import_picocolors27 = __toESM(require_picocolors(), 1);
53256
53418
 
53257
53419
  // src/core/agent-outgoing.ts
53258
53420
  var import_picocolors26 = __toESM(require_picocolors(), 1);
53259
- async function buildOutgoingComponents(cwd2, manifest, cloud) {
53421
+ async function buildOutgoingComponents(cwd2, manifest, cloud, resolvedVersions) {
53260
53422
  const out = [];
53261
53423
  if (manifest.instructions) {
53262
53424
  if (manifest.instructions.text !== undefined && manifest.instructions.file !== undefined) {
@@ -53303,6 +53465,7 @@ async function buildOutgoingComponents(cwd2, manifest, cloud) {
53303
53465
  }
53304
53466
  const slug = registrySkillComponentSlug(parsed);
53305
53467
  const cloudMatch = cloud?.components.find((c2) => c2.type === "skill" && c2.slug === slug);
53468
+ const version = parsed.version ?? resolvedVersions?.get(slug);
53306
53469
  out.push({
53307
53470
  type: "skill",
53308
53471
  slug,
@@ -53310,7 +53473,7 @@ async function buildOutgoingComponents(cwd2, manifest, cloud) {
53310
53473
  files: [],
53311
53474
  meta: {
53312
53475
  name: parsed.creator ? `${parsed.creator}/${parsed.slug}` : parsed.slug,
53313
- ...parsed.version ? { version: parsed.version } : {}
53476
+ ...version ? { version } : {}
53314
53477
  }
53315
53478
  });
53316
53479
  }
@@ -53395,6 +53558,62 @@ function yamlScalar(s3) {
53395
53558
  return JSON.stringify(s3);
53396
53559
  }
53397
53560
 
53561
+ // src/core/registry-skill-updates.ts
53562
+ function parseSemver2(v3) {
53563
+ const m3 = /^(\d+)\.(\d+)\.(\d+)$/.exec(v3);
53564
+ if (!m3)
53565
+ return null;
53566
+ return [Number(m3[1]), Number(m3[2]), Number(m3[3])];
53567
+ }
53568
+ function compareDirection(current, latest) {
53569
+ if (!current)
53570
+ return "unknown";
53571
+ const a3 = parseSemver2(current);
53572
+ const b4 = parseSemver2(latest);
53573
+ if (!a3 || !b4)
53574
+ return "unknown";
53575
+ for (let i = 0;i < 3; i++) {
53576
+ if (b4[i] > a3[i])
53577
+ return "upgrade";
53578
+ if (b4[i] < a3[i])
53579
+ return "downgrade";
53580
+ }
53581
+ return "unknown";
53582
+ }
53583
+ function planRegistrySkillUpdates(skills, cloudComponents, latestByName) {
53584
+ const out = [];
53585
+ for (const entry of skills) {
53586
+ let parsed;
53587
+ try {
53588
+ parsed = parseSkillSource2(entry.source);
53589
+ } catch {
53590
+ continue;
53591
+ }
53592
+ if (parsed.kind !== "registry" || parsed.version || !parsed.creator) {
53593
+ continue;
53594
+ }
53595
+ const name = `${parsed.creator}/${parsed.slug}`;
53596
+ const latest = latestByName.get(name);
53597
+ if (!latest)
53598
+ continue;
53599
+ const componentSlug = registrySkillComponentSlug(parsed);
53600
+ const cloud = cloudComponents.find((c2) => c2.type === "skill" && c2.slug === componentSlug);
53601
+ if (!cloud)
53602
+ continue;
53603
+ const current = cloud.meta?.["version"];
53604
+ if (current === latest)
53605
+ continue;
53606
+ out.push({
53607
+ componentSlug,
53608
+ name,
53609
+ current,
53610
+ latest,
53611
+ direction: compareDirection(current, latest)
53612
+ });
53613
+ }
53614
+ return out;
53615
+ }
53616
+
53398
53617
  // src/cli/agent-push.ts
53399
53618
  async function runAgentPush(cwd2, args) {
53400
53619
  banner("agent push — send your local changes to the cloud");
@@ -53433,6 +53652,37 @@ async function runAgentPush(cwd2, args) {
53433
53652
  lock: lock?.components ?? [],
53434
53653
  cloud: cloud.components
53435
53654
  });
53655
+ const unpinned = new Map;
53656
+ for (const entry of manifest.skills) {
53657
+ try {
53658
+ const parsed = parseSkillSource2(entry.source);
53659
+ if (parsed.kind === "registry" && !parsed.version && parsed.creator) {
53660
+ unpinned.set(`${parsed.creator}/${parsed.slug}`, {
53661
+ creator: parsed.creator,
53662
+ slug: parsed.slug
53663
+ });
53664
+ }
53665
+ } catch {}
53666
+ }
53667
+ const latestByName = new Map;
53668
+ if (unpinned.size > 0) {
53669
+ await Promise.all([...unpinned.entries()].map(async ([name, ref]) => {
53670
+ try {
53671
+ const pkg = await skillsApi.getPackage(ref.creator, ref.slug);
53672
+ if (pkg.latest_version?.version) {
53673
+ latestByName.set(name, pkg.latest_version.version);
53674
+ }
53675
+ } catch {}
53676
+ }));
53677
+ }
53678
+ const skillUpdates = planRegistrySkillUpdates(manifest.skills, cloud.components, latestByName);
53679
+ const resolvedVersions = new Map(skillUpdates.map((u2) => [u2.componentSlug, u2.latest]));
53680
+ for (const u2 of skillUpdates) {
53681
+ const row = rows.find((r2) => r2.key === compKey("skill", u2.componentSlug));
53682
+ if (row && (row.status === "in-sync" || row.status === "modified-cloud" || row.status === "added-local")) {
53683
+ row.status = "modified-local";
53684
+ }
53685
+ }
53436
53686
  let cloudMeta = lock?.agentMeta;
53437
53687
  if (!cloudMeta) {
53438
53688
  try {
@@ -53516,6 +53766,16 @@ async function runAgentPush(cwd2, args) {
53516
53766
  }
53517
53767
  f2.info(`If you push now, your push targets revision ${cloud.revision} and may race. Consider \`brainbase agent pull\` first.`);
53518
53768
  }
53769
+ const sendKeys = new Set(toSend.map((r2) => r2.key));
53770
+ for (const u2 of skillUpdates) {
53771
+ if (!sendKeys.has(compKey("skill", u2.componentSlug)))
53772
+ continue;
53773
+ if (u2.direction === "downgrade") {
53774
+ f2.warn(`Skill ${import_picocolors27.default.bold(u2.name)}: registry latest is ${import_picocolors27.default.bold(u2.latest)}, below the agent's ${import_picocolors27.default.dim(u2.current)} — the agent's version was likely yanked. Converging on latest.`);
53775
+ } else {
53776
+ f2.info(`Skill ${import_picocolors27.default.bold(u2.name)} → ${import_picocolors27.default.bold(u2.latest)} (registry latest${u2.current ? `, agent has ${u2.current}` : ""}).`);
53777
+ }
53778
+ }
53519
53779
  const resultRows = [];
53520
53780
  if (meta.localChanged) {
53521
53781
  resultRows.push({
@@ -53570,7 +53830,7 @@ async function runAgentPush(cwd2, args) {
53570
53830
  return handleApiError3(err);
53571
53831
  }
53572
53832
  }
53573
- const outgoing = await buildOutgoingComponents(cwd2, manifest, cloud);
53833
+ const outgoing = await buildOutgoingComponents(cwd2, manifest, cloud, resolvedVersions);
53574
53834
  if (outgoing === null)
53575
53835
  return;
53576
53836
  const pushSpinner = de();
@@ -54366,12 +54626,14 @@ async function runAgentUnpack(cwd2, args) {
54366
54626
  checksum: ""
54367
54627
  });
54368
54628
  }
54369
- const haveOrchMcp = (manifest.mcp ?? []).some((m3) => m3.name === ORCHESTRATION_MCP_SLUG);
54370
- const haveMemoryMcp = (manifest.mcp ?? []).some((m3) => m3.name === MEMORY_MCP_SLUG);
54371
- if (!haveOrchMcp)
54372
- toInstall.push(buildOrchestrationMcpComponent(scope));
54373
- if (!haveMemoryMcp)
54374
- toInstall.push(buildMemoryMcpComponent(scope));
54629
+ const caps = capabilitiesFromManifest(manifest);
54630
+ const declaredMcpSlugs = new Set((manifest.mcp ?? []).map((m3) => m3.name));
54631
+ const { install: builtinInstall, removeSlugs: builtinRemoveSlugs } = resolveBuiltinMcps({
54632
+ caps,
54633
+ declaredSlugs: declaredMcpSlugs,
54634
+ scope
54635
+ });
54636
+ toInstall.push(...builtinInstall);
54375
54637
  const opts = {
54376
54638
  cwd: cwd2,
54377
54639
  scope,
@@ -54385,6 +54647,9 @@ async function runAgentUnpack(cwd2, args) {
54385
54647
  if (result2.skipped.length) {
54386
54648
  f2.warn(`Skipped: ${result2.skipped.map((s3) => `${s3.type}/${s3.slug} (${s3.reason})`).join(", ")}`);
54387
54649
  }
54650
+ if (builtinRemoveSlugs.length > 0) {
54651
+ runHarnessRemoveMcp(harness, builtinRemoveSlugs, { cwd: cwd2, scope });
54652
+ }
54388
54653
  } catch (err) {
54389
54654
  f2.error(`Install failed: ${err.message}`);
54390
54655
  return;
@@ -54813,12 +55078,15 @@ async function installAgentFresh(input) {
54813
55078
  ensureDir(cwd2);
54814
55079
  const stageRoot = stageManifestComponents2(cloud.components);
54815
55080
  const justInstalledPaths = new Map;
54816
- const cloudHasOrchestrationMcp = cloud.components.some((c2) => c2.type === "mcp" && c2.slug === ORCHESTRATION_MCP_SLUG);
54817
- const needOrchestrationMcpInstall = !cloudHasOrchestrationMcp;
54818
- const cloudHasMemoryMcp = cloud.components.some((c2) => c2.type === "mcp" && c2.slug === MEMORY_MCP_SLUG);
54819
- const needMemoryMcpInstall = !cloudHasMemoryMcp;
55081
+ const caps = capabilitiesFromAgent(agent);
55082
+ const declaredMcpSlugs = new Set(cloud.components.filter((c2) => c2.type === "mcp").map((c2) => c2.slug));
55083
+ const { install: builtinInstall } = resolveBuiltinMcps({
55084
+ caps,
55085
+ declaredSlugs: declaredMcpSlugs,
55086
+ scope
55087
+ });
54820
55088
  try {
54821
- if (cloud.components.length > 0 || needOrchestrationMcpInstall || needMemoryMcpInstall) {
55089
+ if (cloud.components.length > 0 || builtinInstall.length > 0) {
54822
55090
  const toInstall = cloud.components.map((c2) => ({
54823
55091
  type: c2.type,
54824
55092
  slug: c2.slug,
@@ -54829,12 +55097,7 @@ async function installAgentFresh(input) {
54829
55097
  payload: proxifyMcpPayload(c2.meta?.mcp),
54830
55098
  checksum: c2.hash
54831
55099
  }));
54832
- if (needOrchestrationMcpInstall) {
54833
- toInstall.push(buildOrchestrationMcpComponent(scope));
54834
- }
54835
- if (needMemoryMcpInstall) {
54836
- toInstall.push(buildMemoryMcpComponent(scope));
54837
- }
55100
+ toInstall.push(...builtinInstall);
54838
55101
  const installOpts = {
54839
55102
  cwd: cwd2,
54840
55103
  scope,
@@ -54952,6 +55215,7 @@ function buildManifestFromCloud(cloud, agent) {
54952
55215
  entry.is_enabled = payload.is_enabled;
54953
55216
  return entry;
54954
55217
  });
55218
+ const caps = capabilitiesFromAgent(agent);
54955
55219
  return {
54956
55220
  schema: 1,
54957
55221
  agent: {
@@ -54961,7 +55225,8 @@ function buildManifestFromCloud(cloud, agent) {
54961
55225
  ...hasInstructions ? { instructions: { file: DEFAULT_INSTRUCTIONS_FILE } } : {},
54962
55226
  playbooks: [],
54963
55227
  skills,
54964
- mcp
55228
+ mcp,
55229
+ capabilities: { memory: caps.memory, browser: caps.browser, slack: caps.slack }
54965
55230
  };
54966
55231
  }
54967
55232
  async function pullAgentSecrets(cwd2, agentId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainbase-labs/cli",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "description": "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
5
5
  "type": "module",
6
6
  "bin": {