@arvoretech/hub 0.7.6 → 0.8.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.
@@ -121,7 +121,7 @@ async function checkAndAutoRegenerate(hubDir) {
121
121
  return;
122
122
  }
123
123
  console.log(chalk.yellow("\n Detected outdated configs, auto-regenerating..."));
124
- const { generators: generators2 } = await import("./generate-U6OR7AIX.js");
124
+ const { generators: generators2 } = await import("./generate-BYB47MCP.js");
125
125
  const generator = generators2[result.editor];
126
126
  if (!generator) {
127
127
  console.log(chalk.red(` Unknown editor '${result.editor}' in cache. Run 'hub generate' manually.`));
@@ -144,6 +144,41 @@ function stripFrontMatter(content) {
144
144
  if (match) return content.slice(match[0].length);
145
145
  return content;
146
146
  }
147
+ function parseFrontMatter(content) {
148
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
149
+ if (!match) return null;
150
+ const result = {};
151
+ for (const line of match[1].split("\n")) {
152
+ const colonIdx = line.indexOf(":");
153
+ if (colonIdx === -1) continue;
154
+ const key = line.slice(0, colonIdx).trim();
155
+ const value = line.slice(colonIdx + 1).trim();
156
+ if (key) result[key] = value;
157
+ }
158
+ return result;
159
+ }
160
+ async function readExistingMcpDisabledState(mcpJsonPath) {
161
+ const disabledState = {};
162
+ if (!existsSync2(mcpJsonPath)) return disabledState;
163
+ try {
164
+ const content = JSON.parse(await readFile3(mcpJsonPath, "utf-8"));
165
+ const servers = content.mcpServers || content.mcp || {};
166
+ for (const [name, config] of Object.entries(servers)) {
167
+ if (typeof config.disabled === "boolean") {
168
+ disabledState[name] = config.disabled;
169
+ }
170
+ }
171
+ } catch {
172
+ }
173
+ return disabledState;
174
+ }
175
+ function applyDisabledState(mcpConfig, disabledState) {
176
+ for (const [name, entry] of Object.entries(mcpConfig)) {
177
+ if (name in disabledState) {
178
+ entry.disabled = disabledState[name];
179
+ }
180
+ }
181
+ }
147
182
  async function fetchHubDocsSkill(skillsDir) {
148
183
  try {
149
184
  const res = await fetch(HUB_DOCS_URL);
@@ -1559,7 +1594,7 @@ async function generateKiro(config, hubDir) {
1559
1594
  await writeManagedFile(join3(hubDir, ".gitignore"), gitignoreLines);
1560
1595
  console.log(chalk2.green(" Generated .gitignore"));
1561
1596
  const kiroRule = buildKiroOrchestratorRule(config);
1562
- const kiroOrchestrator = buildKiroSteeringContent(kiroRule);
1597
+ const kiroOrchestrator = buildKiroSteeringContent(kiroRule, "always", { name: "orchestrator" });
1563
1598
  await writeFile2(join3(steeringDir, "orchestrator.md"), kiroOrchestrator, "utf-8");
1564
1599
  console.log(chalk2.green(" Generated .kiro/steering/orchestrator.md"));
1565
1600
  await writeFile2(join3(hubDir, "AGENTS.md"), kiroRule + "\n", "utf-8");
@@ -1571,8 +1606,36 @@ async function generateKiro(config, hubDir) {
1571
1606
  for (const file of mdFiles) {
1572
1607
  const raw = await readFile3(join3(hubSteeringDir, file), "utf-8");
1573
1608
  const content = stripFrontMatter(raw);
1574
- const kiroSteering = buildKiroSteeringContent(content);
1575
- await writeFile2(join3(steeringDir, file), kiroSteering, "utf-8");
1609
+ const destPath = join3(steeringDir, file);
1610
+ let inclusion = "always";
1611
+ let meta;
1612
+ if (existsSync2(destPath)) {
1613
+ const existingContent = await readFile3(destPath, "utf-8");
1614
+ const existingFm = parseFrontMatter(existingContent);
1615
+ if (existingFm) {
1616
+ if (existingFm.inclusion === "auto" || existingFm.inclusion === "manual" || existingFm.inclusion === "fileMatch") {
1617
+ inclusion = "auto";
1618
+ }
1619
+ if (existingFm.name || existingFm.description) {
1620
+ meta = {};
1621
+ if (existingFm.name) meta.name = existingFm.name;
1622
+ if (existingFm.description) meta.description = existingFm.description;
1623
+ }
1624
+ }
1625
+ }
1626
+ const sourceFm = parseFrontMatter(raw);
1627
+ if (sourceFm) {
1628
+ if (sourceFm.inclusion === "auto" || sourceFm.inclusion === "manual" || sourceFm.inclusion === "fileMatch") {
1629
+ inclusion = "auto";
1630
+ }
1631
+ if (sourceFm.name || sourceFm.description) {
1632
+ meta = meta || {};
1633
+ if (sourceFm.name) meta.name = sourceFm.name;
1634
+ if (sourceFm.description) meta.description = sourceFm.description;
1635
+ }
1636
+ }
1637
+ const kiroSteering = buildKiroSteeringContent(content, inclusion, meta);
1638
+ await writeFile2(destPath, kiroSteering, "utf-8");
1576
1639
  }
1577
1640
  if (mdFiles.length > 0) {
1578
1641
  console.log(chalk2.green(` Copied ${mdFiles.length} steering files to .kiro/steering/`));
@@ -1631,8 +1694,11 @@ async function generateKiro(config, hubDir) {
1631
1694
  mcpConfig[mcp.name] = buildKiroMcpEntry(mcp, mode);
1632
1695
  }
1633
1696
  }
1697
+ const mcpJsonPath = join3(settingsDir, "mcp.json");
1698
+ const disabledState = await readExistingMcpDisabledState(mcpJsonPath);
1699
+ applyDisabledState(mcpConfig, disabledState);
1634
1700
  await writeFile2(
1635
- join3(settingsDir, "mcp.json"),
1701
+ mcpJsonPath,
1636
1702
  JSON.stringify({ mcpServers: mcpConfig }, null, 2) + "\n",
1637
1703
  "utf-8"
1638
1704
  );
@@ -1762,7 +1828,7 @@ function buildGitignoreLines(config) {
1762
1828
  "tasks/"
1763
1829
  );
1764
1830
  if (config.memory) {
1765
- const memPath = config.memory.path || "memories";
1831
+ const memPath = (config.memory.path || "memories").replace(/^\.\//, "");
1766
1832
  lines.push(
1767
1833
  "",
1768
1834
  "# Memory vector store (generated from markdown files)",
@@ -1812,8 +1878,21 @@ async function resolveEditor(opts) {
1812
1878
  ]);
1813
1879
  return editor;
1814
1880
  }
1815
- var generateCommand = new Command("generate").description("Generate editor-specific configuration files from hub.yaml").option("-e, --editor <editor>", "Target editor (cursor, claude-code, kiro, opencode)").option("--reset-editor", "Reset saved editor preference and choose again").action(async (opts) => {
1881
+ var generateCommand = new Command("generate").description("Generate editor-specific configuration files from hub.yaml").option("-e, --editor <editor>", "Target editor (cursor, claude-code, kiro, opencode)").option("--reset-editor", "Reset saved editor preference and choose again").option("--check", "Check if generated configs are outdated (exit code 1 if outdated)").action(async (opts) => {
1816
1882
  const hubDir = process.cwd();
1883
+ if (opts.check) {
1884
+ const result = await checkOutdated(hubDir);
1885
+ if (result.reason === "no-previous-generate") {
1886
+ console.log(chalk2.yellow("No previous generate found. Run 'hub generate' first."));
1887
+ process.exit(1);
1888
+ }
1889
+ if (result.outdated) {
1890
+ console.log(chalk2.yellow("Generated configs are outdated. Run 'hub generate' to update."));
1891
+ process.exit(1);
1892
+ }
1893
+ console.log(chalk2.green("Generated configs are up to date."));
1894
+ return;
1895
+ }
1817
1896
  const config = await loadHubConfig(hubDir);
1818
1897
  if (config.memory) {
1819
1898
  const hasMemoryMcp = config.mcps?.some(
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  generateCommand,
3
3
  generators
4
- } from "./chunk-2PZA5OEV.js";
4
+ } from "./chunk-VMSQC56H.js";
5
5
  export {
6
6
  generateCommand,
7
7
  generators
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  checkAndAutoRegenerate,
4
4
  generateCommand,
5
5
  loadHubConfig
6
- } from "./chunk-2PZA5OEV.js";
6
+ } from "./chunk-VMSQC56H.js";
7
7
 
8
8
  // src/index.ts
9
9
  import { Command as Command19 } from "commander";
@@ -3021,6 +3021,34 @@ async function findUnsyncedAssets(hubDir) {
3021
3021
  } catch {
3022
3022
  }
3023
3023
  }
3024
+ const hubYamlPath = join17(hubDir, "hub.yaml");
3025
+ if (existsSync14(hubYamlPath)) {
3026
+ const hubContent = await readFile8(hubYamlPath, "utf-8");
3027
+ const hubConfig = parse2(hubContent);
3028
+ const hubMcpNames = new Set((hubConfig.mcps || []).map((m) => m.name));
3029
+ const mcpConfigPaths = [
3030
+ { path: join17(hubDir, ".cursor", "mcp.json"), source: ".cursor", key: "mcpServers" },
3031
+ { path: join17(hubDir, ".kiro", "settings", "mcp.json"), source: ".kiro", key: "mcpServers" },
3032
+ { path: join17(hubDir, ".mcp.json"), source: ".mcp.json", key: "mcpServers" },
3033
+ { path: join17(hubDir, "opencode.json"), source: "opencode.json", key: "mcp" }
3034
+ ];
3035
+ for (const { path: mcpPath, source, key } of mcpConfigPaths) {
3036
+ if (!existsSync14(mcpPath)) continue;
3037
+ try {
3038
+ const content = JSON.parse(await readFile8(mcpPath, "utf-8"));
3039
+ const servers = content[key];
3040
+ if (!servers) continue;
3041
+ for (const serverName of Object.keys(servers)) {
3042
+ if (hubMcpNames.has(serverName)) continue;
3043
+ const mcpKey = `mcp:${serverName}`;
3044
+ if (seen.has(mcpKey)) continue;
3045
+ seen.add(mcpKey);
3046
+ unsynced.push({ type: "mcp", name: serverName, source });
3047
+ }
3048
+ } catch {
3049
+ }
3050
+ }
3051
+ }
3024
3052
  return unsynced;
3025
3053
  }
3026
3054
  function stripFrontMatter(content) {
@@ -3030,6 +3058,10 @@ function stripFrontMatter(content) {
3030
3058
  }
3031
3059
  async function syncAssets(hubDir, assets) {
3032
3060
  for (const asset of assets) {
3061
+ if (asset.type === "mcp") {
3062
+ console.log(chalk18.yellow(` MCP '${asset.name}' found in ${asset.source} but not in hub.yaml \u2014 add it manually.`));
3063
+ continue;
3064
+ }
3033
3065
  if (asset.type === "skill") {
3034
3066
  const src = join17(hubDir, asset.source, "skills", asset.name);
3035
3067
  const dest = join17(hubDir, "skills", asset.name);
@@ -3126,7 +3158,7 @@ var scanCommand = new Command18("scan").description("Detect git repositories not
3126
3158
  hasChanges = true;
3127
3159
  }
3128
3160
  }
3129
- console.log(chalk18.blue("\nScanning for unsynced skills, agents, and steering...\n"));
3161
+ console.log(chalk18.blue("\nScanning for unsynced skills, agents, steering, and MCPs...\n"));
3130
3162
  const unsyncedAssets = await findUnsyncedAssets(hubDir);
3131
3163
  if (unsyncedAssets.length === 0) {
3132
3164
  console.log(chalk18.green("All assets are synced."));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arvoretech/hub",
3
- "version": "0.7.6",
3
+ "version": "0.8.0",
4
4
  "description": "CLI for managing AI-aware multi-repository workspaces",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",