@arvoretech/hub 0.9.0 → 0.11.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.
@@ -1,43 +1,37 @@
1
+ import {
2
+ loadHubConfig
3
+ } from "./chunk-VMN4KGAK.js";
4
+
1
5
  // src/commands/generate.ts
2
6
  import { Command } from "commander";
3
7
  import { existsSync as existsSync2 } from "fs";
4
- import { mkdir as mkdir3, writeFile as writeFile3, readdir as readdir2, copyFile, readFile as readFile4, cp } from "fs/promises";
5
- import { join as join4, resolve as resolve2 } from "path";
8
+ import { mkdir as mkdir3, writeFile as writeFile3, readdir as readdir2, copyFile, readFile as readFile3, cp } from "fs/promises";
9
+ import { join as join3, resolve as resolve2 } from "path";
6
10
  import chalk3 from "chalk";
7
11
  import inquirer from "inquirer";
8
12
 
9
- // src/core/hub-config.ts
10
- import { readFile } from "fs/promises";
11
- import { join } from "path";
12
- import { parse } from "yaml";
13
- async function loadHubConfig(dir) {
14
- const configPath = join(dir, "hub.yaml");
15
- const content = await readFile(configPath, "utf-8");
16
- return parse(content);
17
- }
18
-
19
13
  // src/core/hub-cache.ts
20
14
  import { createHash } from "crypto";
21
15
  import { existsSync } from "fs";
22
- import { mkdir, readdir, readFile as readFile2, writeFile } from "fs/promises";
23
- import { join as join2 } from "path";
16
+ import { mkdir, readdir, readFile, writeFile } from "fs/promises";
17
+ import { join } from "path";
24
18
  import chalk from "chalk";
25
19
  var HUB_DIR = ".hub";
26
20
  var CONFIG_FILE = "config.json";
27
21
  async function readCache(hubDir) {
28
- const filePath = join2(hubDir, HUB_DIR, CONFIG_FILE);
22
+ const filePath = join(hubDir, HUB_DIR, CONFIG_FILE);
29
23
  if (!existsSync(filePath)) return {};
30
24
  try {
31
- const content = await readFile2(filePath, "utf-8");
25
+ const content = await readFile(filePath, "utf-8");
32
26
  return JSON.parse(content);
33
27
  } catch {
34
28
  return {};
35
29
  }
36
30
  }
37
31
  async function writeCache(hubDir, cache) {
38
- const dir = join2(hubDir, HUB_DIR);
32
+ const dir = join(hubDir, HUB_DIR);
39
33
  await mkdir(dir, { recursive: true });
40
- await writeFile(join2(dir, CONFIG_FILE), JSON.stringify(cache, null, 2) + "\n", "utf-8");
34
+ await writeFile(join(dir, CONFIG_FILE), JSON.stringify(cache, null, 2) + "\n", "utf-8");
41
35
  }
42
36
  async function getSavedEditor(hubDir) {
43
37
  const cache = await readCache(hubDir);
@@ -57,9 +51,9 @@ async function collectFileHashes(dir, extensions) {
57
51
  const entries = await readdir(dir, { withFileTypes: true });
58
52
  const hashes = [];
59
53
  for (const entry of entries) {
60
- const fullPath = join2(dir, entry.name);
54
+ const fullPath = join(dir, entry.name);
61
55
  if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
62
- const content = await readFile2(fullPath, "utf-8");
56
+ const content = await readFile(fullPath, "utf-8");
63
57
  hashes.push(`${entry.name}:${createHash("sha256").update(content).digest("hex")}`);
64
58
  } else if (entry.isDirectory()) {
65
59
  const subHashes = await collectFileHashes(fullPath, extensions);
@@ -70,14 +64,16 @@ async function collectFileHashes(dir, extensions) {
70
64
  }
71
65
  async function computeInputsHash(hubDir) {
72
66
  const parts = [];
73
- const hubYamlPath = join2(hubDir, "hub.yaml");
74
- if (existsSync(hubYamlPath)) {
75
- const content = await readFile2(hubYamlPath, "utf-8");
76
- parts.push(`hub.yaml:${createHash("sha256").update(content).digest("hex")}`);
67
+ const { resolveConfigPath } = await import("./hub-config-UATYEV6Q.js");
68
+ const { path: activeConfigPath, format } = resolveConfigPath(hubDir);
69
+ if (existsSync(activeConfigPath)) {
70
+ const configFile = format === "typescript" ? "hub.config.ts" : "hub.yaml";
71
+ const content = await readFile(activeConfigPath, "utf-8");
72
+ parts.push(`${configFile}:${createHash("sha256").update(content).digest("hex")}`);
77
73
  }
78
74
  const dirs = ["agents", "skills", "hooks", "commands"];
79
75
  for (const dir of dirs) {
80
- const dirHashes = await collectFileHashes(join2(hubDir, dir), [".md", ".sh"]);
76
+ const dirHashes = await collectFileHashes(join(hubDir, dir), [".md", ".sh"]);
81
77
  parts.push(...dirHashes.map((h) => `${dir}/${h}`));
82
78
  }
83
79
  return createHash("sha256").update(parts.join("\n")).digest("hex");
@@ -121,7 +117,7 @@ async function checkAndAutoRegenerate(hubDir) {
121
117
  return;
122
118
  }
123
119
  console.log(chalk.yellow("\n Detected outdated configs, auto-regenerating..."));
124
- const { generators: generators2 } = await import("./generate-H4YM3WTV.js");
120
+ const { generators: generators2 } = await import("./generate-KSB3QH7S.js");
125
121
  const generator = generators2[result.editor];
126
122
  if (!generator) {
127
123
  console.log(chalk.red(` Unknown editor '${result.editor}' in cache. Run 'hub generate' manually.`));
@@ -138,8 +134,8 @@ async function checkAndAutoRegenerate(hubDir) {
138
134
  }
139
135
 
140
136
  // src/core/design-sources.ts
141
- import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
142
- import { join as join3, relative, resolve } from "path";
137
+ import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
138
+ import { join as join2, relative, resolve } from "path";
143
139
  import chalk2 from "chalk";
144
140
 
145
141
  // src/core/notion.ts
@@ -325,7 +321,7 @@ async function fetchFromPath(source, hubDir) {
325
321
  if (rel.startsWith("..") || resolve(fullPath) === fullPath && !fullPath.startsWith(hubDir)) {
326
322
  throw new Error(`Path "${source.path}" escapes the workspace for source: ${source.name}`);
327
323
  }
328
- return readFile3(fullPath, "utf-8");
324
+ return readFile2(fullPath, "utf-8");
329
325
  }
330
326
  async function fetchSourceContent(source, hubDir) {
331
327
  if (source.notion_page) return fetchFromNotion(source);
@@ -372,16 +368,16 @@ async function fetchRemoteSources(sources, hubDir, skillsDir, steeringDir) {
372
368
  instructions: source.instructions
373
369
  };
374
370
  if (source.type === "skill") {
375
- const skillDir = join3(skillsDir, source.name);
371
+ const skillDir = join2(skillsDir, source.name);
376
372
  await mkdir2(skillDir, { recursive: true });
377
373
  const skillContent = buildSkillContent(fetched);
378
- await writeFile2(join3(skillDir, "SKILL.md"), skillContent, "utf-8");
374
+ await writeFile2(join2(skillDir, "SKILL.md"), skillContent, "utf-8");
379
375
  skillCount++;
380
376
  console.log(chalk2.green(` \u2713 ${source.name} (skill)`));
381
377
  } else {
382
378
  await mkdir2(steeringDir, { recursive: true });
383
379
  const steeringContent = buildSteeringContent(fetched);
384
- await writeFile2(join3(steeringDir, `${source.name}.md`), steeringContent, "utf-8");
380
+ await writeFile2(join2(steeringDir, `${source.name}.md`), steeringContent, "utf-8");
385
381
  steeringCount++;
386
382
  console.log(chalk2.green(` \u2713 ${source.name} (steering)`));
387
383
  }
@@ -470,7 +466,7 @@ async function readExistingMcpDisabledState(mcpJsonPath) {
470
466
  const disabledState = {};
471
467
  if (!existsSync2(mcpJsonPath)) return disabledState;
472
468
  try {
473
- const content = JSON.parse(await readFile4(mcpJsonPath, "utf-8"));
469
+ const content = JSON.parse(await readFile3(mcpJsonPath, "utf-8"));
474
470
  const servers = content.mcpServers || content.mcp || {};
475
471
  for (const [name, config] of Object.entries(servers)) {
476
472
  if (typeof config.disabled === "boolean") {
@@ -496,7 +492,7 @@ async function fetchHubDocsSkill(skillsDir) {
496
492
  return;
497
493
  }
498
494
  const content = await res.text();
499
- const hubSkillDir = join4(skillsDir, "hub-docs");
495
+ const hubSkillDir = join3(skillsDir, "hub-docs");
500
496
  await mkdir3(hubSkillDir, { recursive: true });
501
497
  const skillContent = `---
502
498
  name: hub-docs
@@ -505,7 +501,7 @@ triggers: [hub, rhm, hub.yaml, generate, scan, setup, orchestrator, multi-repo,
505
501
  ---
506
502
 
507
503
  ${content}`;
508
- await writeFile3(join4(hubSkillDir, "SKILL.md"), skillContent, "utf-8");
504
+ await writeFile3(join3(hubSkillDir, "SKILL.md"), skillContent, "utf-8");
509
505
  console.log(chalk3.green(" Fetched hub-docs skill from hub.arvore.com.br"));
510
506
  } catch {
511
507
  console.log(chalk3.yellow(` Could not fetch hub docs, skipping hub-docs skill`));
@@ -580,7 +576,7 @@ function buildClaudeHooks(hooks) {
580
576
  return claudeHooks;
581
577
  }
582
578
  async function generateEditorCommands(config, hubDir, targetDir, editorName) {
583
- const commandsDir = join4(targetDir, "commands");
579
+ const commandsDir = join3(targetDir, "commands");
584
580
  let count = 0;
585
581
  if (config.commands_dir) {
586
582
  const srcDir = resolve2(hubDir, config.commands_dir);
@@ -590,7 +586,7 @@ async function generateEditorCommands(config, hubDir, targetDir, editorName) {
590
586
  if (mdFiles.length > 0) {
591
587
  await mkdir3(commandsDir, { recursive: true });
592
588
  for (const file of mdFiles) {
593
- await copyFile(join4(srcDir, file), join4(commandsDir, file));
589
+ await copyFile(join3(srcDir, file), join3(commandsDir, file));
594
590
  count++;
595
591
  }
596
592
  }
@@ -602,7 +598,7 @@ async function generateEditorCommands(config, hubDir, targetDir, editorName) {
602
598
  await mkdir3(commandsDir, { recursive: true });
603
599
  for (const [name, filePath] of Object.entries(config.commands)) {
604
600
  const src = resolve2(hubDir, filePath);
605
- const dest = join4(commandsDir, name.endsWith(".md") ? name : `${name}.md`);
601
+ const dest = join3(commandsDir, name.endsWith(".md") ? name : `${name}.md`);
606
602
  try {
607
603
  await copyFile(src, dest);
608
604
  count++;
@@ -618,7 +614,7 @@ async function generateEditorCommands(config, hubDir, targetDir, editorName) {
618
614
  async function writeManagedFile(filePath, managedLines) {
619
615
  const managedBlock = [HUB_MARKER_START, ...managedLines, HUB_MARKER_END].join("\n");
620
616
  if (existsSync2(filePath)) {
621
- const existing = await readFile4(filePath, "utf-8");
617
+ const existing = await readFile3(filePath, "utf-8");
622
618
  const startIdx = existing.indexOf(HUB_MARKER_START);
623
619
  const endIdx = existing.indexOf(HUB_MARKER_END);
624
620
  if (startIdx !== -1 && endIdx !== -1) {
@@ -633,11 +629,11 @@ async function writeManagedFile(filePath, managedLines) {
633
629
  await writeFile3(filePath, managedBlock + "\n", "utf-8");
634
630
  }
635
631
  async function generateCursor(config, hubDir) {
636
- const cursorDir = join4(hubDir, ".cursor");
637
- await mkdir3(join4(cursorDir, "rules"), { recursive: true });
638
- await mkdir3(join4(cursorDir, "agents"), { recursive: true });
632
+ const cursorDir = join3(hubDir, ".cursor");
633
+ await mkdir3(join3(cursorDir, "rules"), { recursive: true });
634
+ await mkdir3(join3(cursorDir, "agents"), { recursive: true });
639
635
  const gitignoreLines = buildGitignoreLines(config);
640
- await writeManagedFile(join4(hubDir, ".gitignore"), gitignoreLines);
636
+ await writeManagedFile(join3(hubDir, ".gitignore"), gitignoreLines);
641
637
  console.log(chalk3.green(" Generated .gitignore"));
642
638
  const cursorignoreLines = [
643
639
  "# Re-include repositories for AI context"
@@ -647,7 +643,7 @@ async function generateCursor(config, hubDir) {
647
643
  cursorignoreLines.push(`!${repoDir}/`);
648
644
  }
649
645
  cursorignoreLines.push("", "# Re-include tasks for agent collaboration", "!tasks/");
650
- await writeManagedFile(join4(hubDir, ".cursorignore"), cursorignoreLines);
646
+ await writeManagedFile(join3(hubDir, ".cursorignore"), cursorignoreLines);
651
647
  console.log(chalk3.green(" Generated .cursorignore"));
652
648
  if (config.mcps?.length) {
653
649
  const mcpConfig = {};
@@ -661,21 +657,21 @@ async function generateCursor(config, hubDir) {
661
657
  }
662
658
  }
663
659
  await writeFile3(
664
- join4(cursorDir, "mcp.json"),
660
+ join3(cursorDir, "mcp.json"),
665
661
  JSON.stringify({ mcpServers: mcpConfig }, null, 2) + "\n",
666
662
  "utf-8"
667
663
  );
668
664
  console.log(chalk3.green(" Generated .cursor/mcp.json"));
669
665
  }
670
666
  const orchestratorRule = buildOrchestratorRule(config);
671
- await writeFile3(join4(cursorDir, "rules", "orchestrator.mdc"), orchestratorRule, "utf-8");
667
+ await writeFile3(join3(cursorDir, "rules", "orchestrator.mdc"), orchestratorRule, "utf-8");
672
668
  console.log(chalk3.green(" Generated .cursor/rules/orchestrator.mdc"));
673
669
  const hubSteeringDirCursor = resolve2(hubDir, "steering");
674
670
  try {
675
671
  const steeringFiles = await readdir2(hubSteeringDirCursor);
676
672
  const mdFiles = steeringFiles.filter((f) => f.endsWith(".md"));
677
673
  for (const file of mdFiles) {
678
- const raw = await readFile4(join4(hubSteeringDirCursor, file), "utf-8");
674
+ const raw = await readFile3(join3(hubSteeringDirCursor, file), "utf-8");
679
675
  const content = stripFrontMatter(raw);
680
676
  const mdcName = file.replace(/\.md$/, ".mdc");
681
677
  const mdcContent = `---
@@ -684,7 +680,7 @@ alwaysApply: true
684
680
  ---
685
681
 
686
682
  ${content}`;
687
- await writeFile3(join4(cursorDir, "rules", mdcName), mdcContent, "utf-8");
683
+ await writeFile3(join3(cursorDir, "rules", mdcName), mdcContent, "utf-8");
688
684
  }
689
685
  if (mdFiles.length > 0) {
690
686
  console.log(chalk3.green(` Copied ${mdFiles.length} steering files to .cursor/rules/`));
@@ -696,7 +692,7 @@ ${content}`;
696
692
  const agentFiles = await readdir2(agentsDir);
697
693
  const mdFiles = agentFiles.filter((f) => f.endsWith(".md"));
698
694
  for (const file of mdFiles) {
699
- await copyFile(join4(agentsDir, file), join4(cursorDir, "agents", file));
695
+ await copyFile(join3(agentsDir, file), join3(cursorDir, "agents", file));
700
696
  }
701
697
  console.log(chalk3.green(` Copied ${mdFiles.length} agent definitions`));
702
698
  } catch {
@@ -705,15 +701,15 @@ ${content}`;
705
701
  const skillsDir = resolve2(hubDir, "skills");
706
702
  try {
707
703
  const skillFolders = await readdir2(skillsDir);
708
- const cursorSkillsDir = join4(cursorDir, "skills");
704
+ const cursorSkillsDir = join3(cursorDir, "skills");
709
705
  await mkdir3(cursorSkillsDir, { recursive: true });
710
706
  let count = 0;
711
707
  for (const folder of skillFolders) {
712
- const skillFile = join4(skillsDir, folder, "SKILL.md");
708
+ const skillFile = join3(skillsDir, folder, "SKILL.md");
713
709
  try {
714
- await readFile4(skillFile);
715
- const srcDir = join4(skillsDir, folder);
716
- const targetDir = join4(cursorSkillsDir, folder);
710
+ await readFile3(skillFile);
711
+ const srcDir = join3(skillsDir, folder);
712
+ const targetDir = join3(cursorSkillsDir, folder);
717
713
  await cp(srcDir, targetDir, { recursive: true });
718
714
  count++;
719
715
  } catch {
@@ -724,15 +720,15 @@ ${content}`;
724
720
  }
725
721
  } catch {
726
722
  }
727
- const cursorSkillsDirForDocs = join4(cursorDir, "skills");
723
+ const cursorSkillsDirForDocs = join3(cursorDir, "skills");
728
724
  await mkdir3(cursorSkillsDirForDocs, { recursive: true });
729
725
  await fetchHubDocsSkill(cursorSkillsDirForDocs);
730
- await syncRemoteSources(config, hubDir, join4(cursorDir, "skills"), join4(cursorDir, "rules"));
726
+ await syncRemoteSources(config, hubDir, join3(cursorDir, "skills"), join3(cursorDir, "rules"));
731
727
  if (config.hooks) {
732
728
  const cursorHooks = buildCursorHooks(config.hooks);
733
729
  if (cursorHooks) {
734
730
  await writeFile3(
735
- join4(cursorDir, "hooks.json"),
731
+ join3(cursorDir, "hooks.json"),
736
732
  JSON.stringify(cursorHooks, null, 2) + "\n",
737
733
  "utf-8"
738
734
  );
@@ -977,6 +973,7 @@ function buildMcpToolsSection(mcps) {
977
973
  const proxyMcp = mcps.find((m) => m.upstreams && m.upstreams.length > 0);
978
974
  const upstreamNames = getUpstreamNames(mcps);
979
975
  const directMcps = mcps.filter((m) => !m.upstreams && !upstreamNames.has(m.name));
976
+ const mcpByName = new Map(mcps.map((m) => [m.name, m]));
980
977
  if (!proxyMcp && directMcps.length === 0) return "";
981
978
  const lines = [];
982
979
  lines.push(`
@@ -993,20 +990,33 @@ Some MCPs are aggregated behind a proxy (\`${proxyMcp.name}\`). Their tools are
993
990
 
994
991
  **MCPs available via proxy:**`);
995
992
  for (const name of proxyMcp.upstreams) {
996
- lines.push(`- \`${name}\``);
993
+ const mcp = mcpByName.get(name);
994
+ const desc = mcp?.description ? ` \u2014 ${mcp.description}` : "";
995
+ lines.push(`- \`${name}\`${desc}`);
997
996
  }
998
997
  }
999
998
  if (directMcps.length > 0) {
1000
999
  lines.push(`
1001
1000
  **MCPs available directly:**`);
1002
1001
  for (const mcp of directMcps) {
1003
- lines.push(`- \`${mcp.name}\``);
1002
+ const desc = mcp.description ? ` \u2014 ${mcp.description}` : "";
1003
+ lines.push(`- \`${mcp.name}\`${desc}`);
1004
1004
  }
1005
1005
  }
1006
1006
  if (proxyMcp) {
1007
1007
  lines.push(`
1008
1008
  > When you need a capability and are unsure which tool to use, always try \`mcp_search\` first with relevant keywords. The proxy aggregates tools from all upstream MCPs.`);
1009
1009
  }
1010
+ const mcpsWithInstructions = mcps.filter((m) => m.instructions && !m.upstreams);
1011
+ if (mcpsWithInstructions.length > 0) {
1012
+ lines.push(`
1013
+ ### MCP Instructions`);
1014
+ for (const mcp of mcpsWithInstructions) {
1015
+ lines.push(`
1016
+ #### ${mcp.name}
1017
+ ${mcp.instructions.trim()}`);
1018
+ }
1019
+ }
1010
1020
  return lines.join("\n");
1011
1021
  }
1012
1022
  function buildOpenCodeOrchestratorRule(config) {
@@ -1198,26 +1208,26 @@ If any validation agent leaves comments requiring fixes, call the relevant codin
1198
1208
  return parts.join("\n");
1199
1209
  }
1200
1210
  async function generateOpenCode(config, hubDir) {
1201
- const opencodeDir = join4(hubDir, ".opencode");
1202
- await mkdir3(join4(opencodeDir, "agents"), { recursive: true });
1203
- await mkdir3(join4(opencodeDir, "rules"), { recursive: true });
1204
- await mkdir3(join4(opencodeDir, "skills"), { recursive: true });
1205
- await mkdir3(join4(opencodeDir, "commands"), { recursive: true });
1206
- await mkdir3(join4(opencodeDir, "plugins"), { recursive: true });
1211
+ const opencodeDir = join3(hubDir, ".opencode");
1212
+ await mkdir3(join3(opencodeDir, "agents"), { recursive: true });
1213
+ await mkdir3(join3(opencodeDir, "rules"), { recursive: true });
1214
+ await mkdir3(join3(opencodeDir, "skills"), { recursive: true });
1215
+ await mkdir3(join3(opencodeDir, "commands"), { recursive: true });
1216
+ await mkdir3(join3(opencodeDir, "plugins"), { recursive: true });
1207
1217
  const gitignoreLines = buildGitignoreLines(config);
1208
- await writeManagedFile(join4(hubDir, ".gitignore"), gitignoreLines);
1218
+ await writeManagedFile(join3(hubDir, ".gitignore"), gitignoreLines);
1209
1219
  console.log(chalk3.green(" Generated .gitignore"));
1210
1220
  const orchestratorRule = buildOpenCodeOrchestratorRule(config);
1211
- await writeFile3(join4(opencodeDir, "rules", "orchestrator.md"), orchestratorRule + "\n", "utf-8");
1221
+ await writeFile3(join3(opencodeDir, "rules", "orchestrator.md"), orchestratorRule + "\n", "utf-8");
1212
1222
  console.log(chalk3.green(" Generated .opencode/rules/orchestrator.md"));
1213
1223
  const hubSteeringDirOC = resolve2(hubDir, "steering");
1214
1224
  try {
1215
1225
  const steeringFiles = await readdir2(hubSteeringDirOC);
1216
1226
  const mdFiles = steeringFiles.filter((f) => f.endsWith(".md"));
1217
1227
  for (const file of mdFiles) {
1218
- const raw = await readFile4(join4(hubSteeringDirOC, file), "utf-8");
1228
+ const raw = await readFile3(join3(hubSteeringDirOC, file), "utf-8");
1219
1229
  const content = stripFrontMatter(raw);
1220
- await writeFile3(join4(opencodeDir, "rules", file), content, "utf-8");
1230
+ await writeFile3(join3(opencodeDir, "rules", file), content, "utf-8");
1221
1231
  }
1222
1232
  if (mdFiles.length > 0) {
1223
1233
  console.log(chalk3.green(` Copied ${mdFiles.length} steering files to .opencode/rules/`));
@@ -1242,7 +1252,7 @@ async function generateOpenCode(config, hubDir) {
1242
1252
  }
1243
1253
  opencodeConfig.instructions = [".opencode/rules/*.md"];
1244
1254
  await writeFile3(
1245
- join4(hubDir, "opencode.json"),
1255
+ join3(hubDir, "opencode.json"),
1246
1256
  JSON.stringify(opencodeConfig, null, 2) + "\n",
1247
1257
  "utf-8"
1248
1258
  );
@@ -1252,10 +1262,10 @@ async function generateOpenCode(config, hubDir) {
1252
1262
  const agentFiles = await readdir2(agentsDir);
1253
1263
  const mdFiles = agentFiles.filter((f) => f.endsWith(".md"));
1254
1264
  for (const file of mdFiles) {
1255
- const content = await readFile4(join4(agentsDir, file), "utf-8");
1265
+ const content = await readFile3(join3(agentsDir, file), "utf-8");
1256
1266
  const agentName = file.replace(/\.md$/, "");
1257
1267
  const converted = buildOpenCodeAgentMarkdown(agentName, content);
1258
- await writeFile3(join4(opencodeDir, "agents", file), converted, "utf-8");
1268
+ await writeFile3(join3(opencodeDir, "agents", file), converted, "utf-8");
1259
1269
  }
1260
1270
  console.log(chalk3.green(` Copied ${mdFiles.length} agents to .opencode/agents/`));
1261
1271
  } catch {
@@ -1266,10 +1276,10 @@ async function generateOpenCode(config, hubDir) {
1266
1276
  const skillFolders = await readdir2(skillsDir);
1267
1277
  let count = 0;
1268
1278
  for (const folder of skillFolders) {
1269
- const skillFile = join4(skillsDir, folder, "SKILL.md");
1279
+ const skillFile = join3(skillsDir, folder, "SKILL.md");
1270
1280
  try {
1271
- await readFile4(skillFile);
1272
- await cp(join4(skillsDir, folder), join4(opencodeDir, "skills", folder), { recursive: true });
1281
+ await readFile3(skillFile);
1282
+ await cp(join3(skillsDir, folder), join3(opencodeDir, "skills", folder), { recursive: true });
1273
1283
  count++;
1274
1284
  } catch {
1275
1285
  }
@@ -1279,13 +1289,13 @@ async function generateOpenCode(config, hubDir) {
1279
1289
  }
1280
1290
  } catch {
1281
1291
  }
1282
- await fetchHubDocsSkill(join4(opencodeDir, "skills"));
1283
- await syncRemoteSources(config, hubDir, join4(opencodeDir, "skills"), join4(opencodeDir, "rules"));
1292
+ await fetchHubDocsSkill(join3(opencodeDir, "skills"));
1293
+ await syncRemoteSources(config, hubDir, join3(opencodeDir, "skills"), join3(opencodeDir, "rules"));
1284
1294
  await generateEditorCommands(config, hubDir, opencodeDir, ".opencode/commands/");
1285
1295
  if (config.hooks) {
1286
1296
  const plugin = buildOpenCodeHooksPlugin(config.hooks);
1287
1297
  if (plugin) {
1288
- await writeFile3(join4(opencodeDir, "plugins", "hub-hooks.js"), plugin, "utf-8");
1298
+ await writeFile3(join3(opencodeDir, "plugins", "hub-hooks.js"), plugin, "utf-8");
1289
1299
  console.log(chalk3.green(" Generated .opencode/plugins/hub-hooks.js"));
1290
1300
  }
1291
1301
  }
@@ -1758,8 +1768,8 @@ function formatAction(action) {
1758
1768
  return map[action] || action;
1759
1769
  }
1760
1770
  async function generateClaudeCode(config, hubDir) {
1761
- const claudeDir = join4(hubDir, ".claude");
1762
- await mkdir3(join4(claudeDir, "agents"), { recursive: true });
1771
+ const claudeDir = join3(hubDir, ".claude");
1772
+ await mkdir3(join3(claudeDir, "agents"), { recursive: true });
1763
1773
  const orchestratorRule = buildOrchestratorRule(config);
1764
1774
  const cleanedOrchestrator = orchestratorRule.replace(/^---[\s\S]*?---\n/m, "").trim();
1765
1775
  const claudeMdSections = [];
@@ -1769,7 +1779,7 @@ async function generateClaudeCode(config, hubDir) {
1769
1779
  const agentFiles = await readdir2(agentsDir);
1770
1780
  const mdFiles = agentFiles.filter((f) => f.endsWith(".md"));
1771
1781
  for (const file of mdFiles) {
1772
- await copyFile(join4(agentsDir, file), join4(claudeDir, "agents", file));
1782
+ await copyFile(join3(agentsDir, file), join3(claudeDir, "agents", file));
1773
1783
  }
1774
1784
  console.log(chalk3.green(` Copied ${mdFiles.length} agents to .claude/agents/`));
1775
1785
  } catch {
@@ -1778,15 +1788,15 @@ async function generateClaudeCode(config, hubDir) {
1778
1788
  const skillsDir = resolve2(hubDir, "skills");
1779
1789
  try {
1780
1790
  const skillFolders = await readdir2(skillsDir);
1781
- const claudeSkillsDir = join4(claudeDir, "skills");
1791
+ const claudeSkillsDir = join3(claudeDir, "skills");
1782
1792
  await mkdir3(claudeSkillsDir, { recursive: true });
1783
1793
  let count = 0;
1784
1794
  for (const folder of skillFolders) {
1785
- const skillFile = join4(skillsDir, folder, "SKILL.md");
1795
+ const skillFile = join3(skillsDir, folder, "SKILL.md");
1786
1796
  try {
1787
- await readFile4(skillFile);
1788
- const srcDir = join4(skillsDir, folder);
1789
- const targetDir = join4(claudeSkillsDir, folder);
1797
+ await readFile3(skillFile);
1798
+ const srcDir = join3(skillsDir, folder);
1799
+ const targetDir = join3(claudeSkillsDir, folder);
1790
1800
  await cp(srcDir, targetDir, { recursive: true });
1791
1801
  count++;
1792
1802
  } catch {
@@ -1797,16 +1807,16 @@ async function generateClaudeCode(config, hubDir) {
1797
1807
  }
1798
1808
  } catch {
1799
1809
  }
1800
- const claudeSkillsDirForDocs = join4(claudeDir, "skills");
1810
+ const claudeSkillsDirForDocs = join3(claudeDir, "skills");
1801
1811
  await mkdir3(claudeSkillsDirForDocs, { recursive: true });
1802
1812
  await fetchHubDocsSkill(claudeSkillsDirForDocs);
1803
- await syncRemoteSources(config, hubDir, join4(claudeDir, "skills"), join4(claudeDir, "steering"));
1813
+ await syncRemoteSources(config, hubDir, join3(claudeDir, "skills"), join3(claudeDir, "steering"));
1804
1814
  const hubSteeringDirClaude = resolve2(hubDir, "steering");
1805
1815
  try {
1806
1816
  const steeringFiles = await readdir2(hubSteeringDirClaude);
1807
1817
  const mdFiles = steeringFiles.filter((f) => f.endsWith(".md"));
1808
1818
  for (const file of mdFiles) {
1809
- const raw = await readFile4(join4(hubSteeringDirClaude, file), "utf-8");
1819
+ const raw = await readFile3(join3(hubSteeringDirClaude, file), "utf-8");
1810
1820
  const content = stripFrontMatter(raw).trim();
1811
1821
  if (content) {
1812
1822
  claudeMdSections.push(content);
@@ -1817,7 +1827,7 @@ async function generateClaudeCode(config, hubDir) {
1817
1827
  }
1818
1828
  } catch {
1819
1829
  }
1820
- await writeFile3(join4(hubDir, "CLAUDE.md"), claudeMdSections.join("\n\n"), "utf-8");
1830
+ await writeFile3(join3(hubDir, "CLAUDE.md"), claudeMdSections.join("\n\n"), "utf-8");
1821
1831
  console.log(chalk3.green(" Generated CLAUDE.md"));
1822
1832
  if (config.mcps?.length) {
1823
1833
  const mcpJson = {};
@@ -1831,7 +1841,7 @@ async function generateClaudeCode(config, hubDir) {
1831
1841
  }
1832
1842
  }
1833
1843
  await writeFile3(
1834
- join4(hubDir, ".mcp.json"),
1844
+ join3(hubDir, ".mcp.json"),
1835
1845
  JSON.stringify({ mcpServers: mcpJson }, null, 2) + "\n",
1836
1846
  "utf-8"
1837
1847
  );
@@ -1874,19 +1884,19 @@ async function generateClaudeCode(config, hubDir) {
1874
1884
  }
1875
1885
  }
1876
1886
  await writeFile3(
1877
- join4(claudeDir, "settings.json"),
1887
+ join3(claudeDir, "settings.json"),
1878
1888
  JSON.stringify(claudeSettings, null, 2) + "\n",
1879
1889
  "utf-8"
1880
1890
  );
1881
1891
  console.log(chalk3.green(" Generated .claude/settings.json"));
1882
1892
  const gitignoreLines = buildGitignoreLines(config);
1883
- await writeManagedFile(join4(hubDir, ".gitignore"), gitignoreLines);
1893
+ await writeManagedFile(join3(hubDir, ".gitignore"), gitignoreLines);
1884
1894
  console.log(chalk3.green(" Generated .gitignore"));
1885
1895
  }
1886
1896
  async function generateKiro(config, hubDir) {
1887
- const kiroDir = join4(hubDir, ".kiro");
1888
- const steeringDir = join4(kiroDir, "steering");
1889
- const settingsDir = join4(kiroDir, "settings");
1897
+ const kiroDir = join3(hubDir, ".kiro");
1898
+ const steeringDir = join3(kiroDir, "steering");
1899
+ const settingsDir = join3(kiroDir, "settings");
1890
1900
  await mkdir3(steeringDir, { recursive: true });
1891
1901
  await mkdir3(settingsDir, { recursive: true });
1892
1902
  let mode = await getKiroMode(hubDir);
@@ -1909,26 +1919,26 @@ async function generateKiro(config, hubDir) {
1909
1919
  console.log(chalk3.dim(` Using saved Kiro mode: ${mode}`));
1910
1920
  }
1911
1921
  const gitignoreLines = buildGitignoreLines(config);
1912
- await writeManagedFile(join4(hubDir, ".gitignore"), gitignoreLines);
1922
+ await writeManagedFile(join3(hubDir, ".gitignore"), gitignoreLines);
1913
1923
  console.log(chalk3.green(" Generated .gitignore"));
1914
1924
  const kiroRule = buildKiroOrchestratorRule(config);
1915
1925
  const kiroOrchestrator = buildKiroSteeringContent(kiroRule, "always", { name: "orchestrator" });
1916
- await writeFile3(join4(steeringDir, "orchestrator.md"), kiroOrchestrator, "utf-8");
1926
+ await writeFile3(join3(steeringDir, "orchestrator.md"), kiroOrchestrator, "utf-8");
1917
1927
  console.log(chalk3.green(" Generated .kiro/steering/orchestrator.md"));
1918
- await writeFile3(join4(hubDir, "AGENTS.md"), kiroRule + "\n", "utf-8");
1928
+ await writeFile3(join3(hubDir, "AGENTS.md"), kiroRule + "\n", "utf-8");
1919
1929
  console.log(chalk3.green(" Generated AGENTS.md"));
1920
1930
  const hubSteeringDir = resolve2(hubDir, "steering");
1921
1931
  try {
1922
1932
  const steeringFiles = await readdir2(hubSteeringDir);
1923
1933
  const mdFiles = steeringFiles.filter((f) => f.endsWith(".md"));
1924
1934
  for (const file of mdFiles) {
1925
- const raw = await readFile4(join4(hubSteeringDir, file), "utf-8");
1935
+ const raw = await readFile3(join3(hubSteeringDir, file), "utf-8");
1926
1936
  const content = stripFrontMatter(raw);
1927
- const destPath = join4(steeringDir, file);
1937
+ const destPath = join3(steeringDir, file);
1928
1938
  let inclusion = "always";
1929
1939
  let meta;
1930
1940
  if (existsSync2(destPath)) {
1931
- const existingContent = await readFile4(destPath, "utf-8");
1941
+ const existingContent = await readFile3(destPath, "utf-8");
1932
1942
  const existingFm = parseFrontMatter(existingContent);
1933
1943
  if (existingFm) {
1934
1944
  if (existingFm.inclusion === "auto" || existingFm.inclusion === "manual" || existingFm.inclusion === "fileMatch") {
@@ -1962,14 +1972,14 @@ async function generateKiro(config, hubDir) {
1962
1972
  }
1963
1973
  const agentsDir = resolve2(hubDir, "agents");
1964
1974
  try {
1965
- const kiroAgentsDir = join4(kiroDir, "agents");
1975
+ const kiroAgentsDir = join3(kiroDir, "agents");
1966
1976
  await mkdir3(kiroAgentsDir, { recursive: true });
1967
1977
  const agentFiles = await readdir2(agentsDir);
1968
1978
  const mdFiles = agentFiles.filter((f) => f.endsWith(".md"));
1969
1979
  for (const file of mdFiles) {
1970
- const agentContent = await readFile4(join4(agentsDir, file), "utf-8");
1980
+ const agentContent = await readFile3(join3(agentsDir, file), "utf-8");
1971
1981
  const kiroAgent = buildKiroAgentContent(agentContent);
1972
- await writeFile3(join4(kiroAgentsDir, file), kiroAgent, "utf-8");
1982
+ await writeFile3(join3(kiroAgentsDir, file), kiroAgent, "utf-8");
1973
1983
  }
1974
1984
  console.log(chalk3.green(` Copied ${mdFiles.length} agents to .kiro/agents/`));
1975
1985
  } catch {
@@ -1978,15 +1988,15 @@ async function generateKiro(config, hubDir) {
1978
1988
  const skillsDir = resolve2(hubDir, "skills");
1979
1989
  try {
1980
1990
  const skillFolders = await readdir2(skillsDir);
1981
- const kiroSkillsDir = join4(kiroDir, "skills");
1991
+ const kiroSkillsDir = join3(kiroDir, "skills");
1982
1992
  await mkdir3(kiroSkillsDir, { recursive: true });
1983
1993
  let count = 0;
1984
1994
  for (const folder of skillFolders) {
1985
- const skillFile = join4(skillsDir, folder, "SKILL.md");
1995
+ const skillFile = join3(skillsDir, folder, "SKILL.md");
1986
1996
  try {
1987
- await readFile4(skillFile);
1988
- const srcDir = join4(skillsDir, folder);
1989
- const targetDir = join4(kiroSkillsDir, folder);
1997
+ await readFile3(skillFile);
1998
+ const srcDir = join3(skillsDir, folder);
1999
+ const targetDir = join3(kiroSkillsDir, folder);
1990
2000
  await cp(srcDir, targetDir, { recursive: true });
1991
2001
  count++;
1992
2002
  } catch {
@@ -1997,10 +2007,10 @@ async function generateKiro(config, hubDir) {
1997
2007
  }
1998
2008
  } catch {
1999
2009
  }
2000
- const kiroSkillsDirForDocs = join4(kiroDir, "skills");
2010
+ const kiroSkillsDirForDocs = join3(kiroDir, "skills");
2001
2011
  await mkdir3(kiroSkillsDirForDocs, { recursive: true });
2002
2012
  await fetchHubDocsSkill(kiroSkillsDirForDocs);
2003
- await syncRemoteSources(config, hubDir, join4(kiroDir, "skills"), steeringDir);
2013
+ await syncRemoteSources(config, hubDir, join3(kiroDir, "skills"), steeringDir);
2004
2014
  if (config.mcps?.length) {
2005
2015
  const mcpConfig = {};
2006
2016
  const upstreamSet = getUpstreamNames(config.mcps);
@@ -2013,7 +2023,7 @@ async function generateKiro(config, hubDir) {
2013
2023
  mcpConfig[mcp.name] = buildKiroMcpEntry(mcp, mode);
2014
2024
  }
2015
2025
  }
2016
- const mcpJsonPath = join4(settingsDir, "mcp.json");
2026
+ const mcpJsonPath = join3(settingsDir, "mcp.json");
2017
2027
  const disabledState = await readExistingMcpDisabledState(mcpJsonPath);
2018
2028
  applyDisabledState(mcpConfig, disabledState);
2019
2029
  await writeFile3(
@@ -2043,13 +2053,13 @@ async function generateKiro(config, hubDir) {
2043
2053
  await generateVSCodeSettings(config, hubDir);
2044
2054
  }
2045
2055
  async function generateVSCodeSettings(config, hubDir) {
2046
- const vscodeDir = join4(hubDir, ".vscode");
2056
+ const vscodeDir = join3(hubDir, ".vscode");
2047
2057
  await mkdir3(vscodeDir, { recursive: true });
2048
- const settingsPath = join4(vscodeDir, "settings.json");
2058
+ const settingsPath = join3(vscodeDir, "settings.json");
2049
2059
  let existing = {};
2050
2060
  if (existsSync2(settingsPath)) {
2051
2061
  try {
2052
- const raw = await readFile4(settingsPath, "utf-8");
2062
+ const raw = await readFile3(settingsPath, "utf-8");
2053
2063
  existing = JSON.parse(raw);
2054
2064
  } catch {
2055
2065
  existing = {};
@@ -2064,11 +2074,11 @@ async function generateVSCodeSettings(config, hubDir) {
2064
2074
  await writeFile3(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
2065
2075
  console.log(chalk3.green(" Generated .vscode/settings.json (git multi-repo detection)"));
2066
2076
  const workspaceFile = `${config.name}.code-workspace`;
2067
- const workspacePath = join4(hubDir, workspaceFile);
2077
+ const workspacePath = join3(hubDir, workspaceFile);
2068
2078
  let existingWorkspace = {};
2069
2079
  if (existsSync2(workspacePath)) {
2070
2080
  try {
2071
- const raw = await readFile4(workspacePath, "utf-8");
2081
+ const raw = await readFile3(workspacePath, "utf-8");
2072
2082
  existingWorkspace = JSON.parse(raw);
2073
2083
  } catch {
2074
2084
  existingWorkspace = {};
@@ -2078,7 +2088,7 @@ async function generateVSCodeSettings(config, hubDir) {
2078
2088
  const existing2 = files.find((f) => f.endsWith(".code-workspace"));
2079
2089
  if (existing2) {
2080
2090
  try {
2081
- const raw = await readFile4(join4(hubDir, existing2), "utf-8");
2091
+ const raw = await readFile3(join3(hubDir, existing2), "utf-8");
2082
2092
  existingWorkspace = JSON.parse(raw);
2083
2093
  } catch {
2084
2094
  existingWorkspace = {};
@@ -2161,7 +2171,7 @@ async function generateEnvExample(config, hubDir) {
2161
2171
  totalVars++;
2162
2172
  }
2163
2173
  if (totalVars === 0) return;
2164
- await writeFile3(join4(hubDir, ".env.example"), lines.join("\n") + "\n", "utf-8");
2174
+ await writeFile3(join3(hubDir, ".env.example"), lines.join("\n") + "\n", "utf-8");
2165
2175
  console.log(chalk3.green(` Generated .env.example (${totalVars} vars)`));
2166
2176
  }
2167
2177
  function buildGitignoreLines(config) {
@@ -2309,7 +2319,6 @@ Generating ${generator.name} configuration
2309
2319
  });
2310
2320
 
2311
2321
  export {
2312
- loadHubConfig,
2313
2322
  generators,
2314
2323
  generateCommand,
2315
2324
  checkAndAutoRegenerate