@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.
- package/README.md +42 -6
- package/dist/{chunk-NUJMJOEK.js → chunk-MDJG7IOU.js} +130 -121
- package/dist/chunk-VMN4KGAK.js +43 -0
- package/dist/config/index.d.ts +214 -0
- package/dist/config/index.js +241 -0
- package/dist/{generate-H4YM3WTV.js → generate-KSB3QH7S.js} +2 -1
- package/dist/hub-config-UATYEV6Q.js +10 -0
- package/dist/index.js +2432 -1310
- package/package.json +16 -3
|
@@ -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
|
|
5
|
-
import { join as
|
|
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
|
|
23
|
-
import { join
|
|
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 =
|
|
22
|
+
const filePath = join(hubDir, HUB_DIR, CONFIG_FILE);
|
|
29
23
|
if (!existsSync(filePath)) return {};
|
|
30
24
|
try {
|
|
31
|
-
const content = await
|
|
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 =
|
|
32
|
+
const dir = join(hubDir, HUB_DIR);
|
|
39
33
|
await mkdir(dir, { recursive: true });
|
|
40
|
-
await writeFile(
|
|
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 =
|
|
54
|
+
const fullPath = join(dir, entry.name);
|
|
61
55
|
if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
62
|
-
const content = await
|
|
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
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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(
|
|
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-
|
|
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
|
|
142
|
-
import { join as
|
|
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
|
|
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 =
|
|
371
|
+
const skillDir = join2(skillsDir, source.name);
|
|
376
372
|
await mkdir2(skillDir, { recursive: true });
|
|
377
373
|
const skillContent = buildSkillContent(fetched);
|
|
378
|
-
await writeFile2(
|
|
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(
|
|
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
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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 =
|
|
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
|
|
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 =
|
|
637
|
-
await mkdir3(
|
|
638
|
-
await mkdir3(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
708
|
+
const skillFile = join3(skillsDir, folder, "SKILL.md");
|
|
713
709
|
try {
|
|
714
|
-
await
|
|
715
|
-
const srcDir =
|
|
716
|
-
const targetDir =
|
|
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 =
|
|
723
|
+
const cursorSkillsDirForDocs = join3(cursorDir, "skills");
|
|
728
724
|
await mkdir3(cursorSkillsDirForDocs, { recursive: true });
|
|
729
725
|
await fetchHubDocsSkill(cursorSkillsDirForDocs);
|
|
730
|
-
await syncRemoteSources(config, hubDir,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
1202
|
-
await mkdir3(
|
|
1203
|
-
await mkdir3(
|
|
1204
|
-
await mkdir3(
|
|
1205
|
-
await mkdir3(
|
|
1206
|
-
await mkdir3(
|
|
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(
|
|
1218
|
+
await writeManagedFile(join3(hubDir, ".gitignore"), gitignoreLines);
|
|
1209
1219
|
console.log(chalk3.green(" Generated .gitignore"));
|
|
1210
1220
|
const orchestratorRule = buildOpenCodeOrchestratorRule(config);
|
|
1211
|
-
await writeFile3(
|
|
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
|
|
1228
|
+
const raw = await readFile3(join3(hubSteeringDirOC, file), "utf-8");
|
|
1219
1229
|
const content = stripFrontMatter(raw);
|
|
1220
|
-
await writeFile3(
|
|
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
|
-
|
|
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
|
|
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(
|
|
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 =
|
|
1279
|
+
const skillFile = join3(skillsDir, folder, "SKILL.md");
|
|
1270
1280
|
try {
|
|
1271
|
-
await
|
|
1272
|
-
await cp(
|
|
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(
|
|
1283
|
-
await syncRemoteSources(config, hubDir,
|
|
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(
|
|
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 =
|
|
1762
|
-
await mkdir3(
|
|
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(
|
|
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 =
|
|
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 =
|
|
1795
|
+
const skillFile = join3(skillsDir, folder, "SKILL.md");
|
|
1786
1796
|
try {
|
|
1787
|
-
await
|
|
1788
|
-
const srcDir =
|
|
1789
|
-
const targetDir =
|
|
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 =
|
|
1810
|
+
const claudeSkillsDirForDocs = join3(claudeDir, "skills");
|
|
1801
1811
|
await mkdir3(claudeSkillsDirForDocs, { recursive: true });
|
|
1802
1812
|
await fetchHubDocsSkill(claudeSkillsDirForDocs);
|
|
1803
|
-
await syncRemoteSources(config, hubDir,
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
1888
|
-
const steeringDir =
|
|
1889
|
-
const settingsDir =
|
|
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(
|
|
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(
|
|
1926
|
+
await writeFile3(join3(steeringDir, "orchestrator.md"), kiroOrchestrator, "utf-8");
|
|
1917
1927
|
console.log(chalk3.green(" Generated .kiro/steering/orchestrator.md"));
|
|
1918
|
-
await writeFile3(
|
|
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
|
|
1935
|
+
const raw = await readFile3(join3(hubSteeringDir, file), "utf-8");
|
|
1926
1936
|
const content = stripFrontMatter(raw);
|
|
1927
|
-
const destPath =
|
|
1937
|
+
const destPath = join3(steeringDir, file);
|
|
1928
1938
|
let inclusion = "always";
|
|
1929
1939
|
let meta;
|
|
1930
1940
|
if (existsSync2(destPath)) {
|
|
1931
|
-
const existingContent = await
|
|
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 =
|
|
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
|
|
1980
|
+
const agentContent = await readFile3(join3(agentsDir, file), "utf-8");
|
|
1971
1981
|
const kiroAgent = buildKiroAgentContent(agentContent);
|
|
1972
|
-
await writeFile3(
|
|
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 =
|
|
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 =
|
|
1995
|
+
const skillFile = join3(skillsDir, folder, "SKILL.md");
|
|
1986
1996
|
try {
|
|
1987
|
-
await
|
|
1988
|
-
const srcDir =
|
|
1989
|
-
const targetDir =
|
|
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 =
|
|
2010
|
+
const kiroSkillsDirForDocs = join3(kiroDir, "skills");
|
|
2001
2011
|
await mkdir3(kiroSkillsDirForDocs, { recursive: true });
|
|
2002
2012
|
await fetchHubDocsSkill(kiroSkillsDirForDocs);
|
|
2003
|
-
await syncRemoteSources(config, hubDir,
|
|
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 =
|
|
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 =
|
|
2056
|
+
const vscodeDir = join3(hubDir, ".vscode");
|
|
2047
2057
|
await mkdir3(vscodeDir, { recursive: true });
|
|
2048
|
-
const settingsPath =
|
|
2058
|
+
const settingsPath = join3(vscodeDir, "settings.json");
|
|
2049
2059
|
let existing = {};
|
|
2050
2060
|
if (existsSync2(settingsPath)) {
|
|
2051
2061
|
try {
|
|
2052
|
-
const raw = await
|
|
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 =
|
|
2077
|
+
const workspacePath = join3(hubDir, workspaceFile);
|
|
2068
2078
|
let existingWorkspace = {};
|
|
2069
2079
|
if (existsSync2(workspacePath)) {
|
|
2070
2080
|
try {
|
|
2071
|
-
const raw = await
|
|
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
|
|
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(
|
|
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
|