@arvoretech/hub 0.16.0 → 0.17.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.
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
} from "./chunk-VMN4KGAK.js";
|
|
4
4
|
|
|
5
5
|
// src/commands/generate.ts
|
|
6
|
-
import { Command } from "commander";
|
|
7
|
-
import { existsSync as
|
|
8
|
-
import { mkdir as
|
|
9
|
-
import { join as
|
|
6
|
+
import { Command as Command2 } from "commander";
|
|
7
|
+
import { existsSync as existsSync3 } from "fs";
|
|
8
|
+
import { mkdir as mkdir4, writeFile as writeFile4, readdir as readdir2, copyFile, readFile as readFile4, cp, rm } from "fs/promises";
|
|
9
|
+
import { join as join4, resolve as resolve2 } from "path";
|
|
10
10
|
import chalk3 from "chalk";
|
|
11
11
|
import inquirer from "inquirer";
|
|
12
12
|
|
|
@@ -117,7 +117,7 @@ async function checkAndAutoRegenerate(hubDir) {
|
|
|
117
117
|
return;
|
|
118
118
|
}
|
|
119
119
|
console.log(chalk.yellow("\n Detected outdated configs, auto-regenerating..."));
|
|
120
|
-
const { generators: generators2 } = await import("./generate-
|
|
120
|
+
const { generators: generators2 } = await import("./generate-6CIWB5FN.js");
|
|
121
121
|
const generator = generators2[result.editor];
|
|
122
122
|
if (!generator) {
|
|
123
123
|
console.log(chalk.red(` Unknown editor '${result.editor}' in cache. Run 'hub generate' manually.`));
|
|
@@ -390,6 +390,337 @@ async function fetchRemoteSources(sources, hubDir, skillsDir, steeringDir) {
|
|
|
390
390
|
return { skills: skillCount, steering: steeringCount, errors };
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
+
// src/commands/persona.ts
|
|
394
|
+
import { Command } from "commander";
|
|
395
|
+
import { join as join3 } from "path";
|
|
396
|
+
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
397
|
+
import { existsSync as existsSync2 } from "fs";
|
|
398
|
+
import React from "react";
|
|
399
|
+
import { render } from "ink";
|
|
400
|
+
|
|
401
|
+
// src/tui/PersonaApp.tsx
|
|
402
|
+
import { useState, useCallback } from "react";
|
|
403
|
+
import { Box, Text, useInput, useStdout, useApp } from "ink";
|
|
404
|
+
import TextInput from "ink-text-input";
|
|
405
|
+
|
|
406
|
+
// src/tui/theme.ts
|
|
407
|
+
var colors = {
|
|
408
|
+
brand: "#22c55e",
|
|
409
|
+
brandDim: "#16a34a",
|
|
410
|
+
blue: "#3b82f6",
|
|
411
|
+
purple: "#a78bfa",
|
|
412
|
+
muted: "#6b7280",
|
|
413
|
+
dim: "#4b5563",
|
|
414
|
+
warning: "#eab308",
|
|
415
|
+
error: "#ef4444",
|
|
416
|
+
white: "#ffffff"
|
|
417
|
+
};
|
|
418
|
+
var symbols = {
|
|
419
|
+
check: "\u2713",
|
|
420
|
+
cross: "\u2717",
|
|
421
|
+
arrow: "\u276F",
|
|
422
|
+
dot: "\u25CF",
|
|
423
|
+
circle: "\u25CB",
|
|
424
|
+
tree: "\u{1F333}",
|
|
425
|
+
line: "\u2500",
|
|
426
|
+
corner: "\u256D",
|
|
427
|
+
cornerEnd: "\u2570",
|
|
428
|
+
vertical: "\u2502",
|
|
429
|
+
cornerRight: "\u256E",
|
|
430
|
+
cornerEndRight: "\u256F"
|
|
431
|
+
};
|
|
432
|
+
function horizontalLine(width) {
|
|
433
|
+
return symbols.line.repeat(width);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// src/tui/PersonaApp.tsx
|
|
437
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
438
|
+
var TECHNICAL_LEVELS = [
|
|
439
|
+
{ value: "non-technical", label: "Non-technical", description: "No coding experience \u2014 CEO, PM, designer, etc." },
|
|
440
|
+
{ value: "beginner", label: "Beginner", description: "Learning to code or just started working with devs" },
|
|
441
|
+
{ value: "intermediate", label: "Intermediate", description: "Comfortable with code, still learning some tools" },
|
|
442
|
+
{ value: "advanced", label: "Advanced", description: "Experienced developer, knows the stack well" }
|
|
443
|
+
];
|
|
444
|
+
var STEP_INFO = {
|
|
445
|
+
name: { title: "What's your name?", subtitle: "So the AI knows who it's talking to." },
|
|
446
|
+
role: { title: "What's your role?", subtitle: "e.g. CEO, Product Manager, Designer, Backend Dev, QA Engineer..." },
|
|
447
|
+
technical_level: { title: "How technical are you?", subtitle: "This changes how the AI explains things to you." },
|
|
448
|
+
context: { title: "Anything else the AI should know about you?", subtitle: `e.g. "I focus on business metrics", "I only work on the mobile app", "I review PRs but don't code"` },
|
|
449
|
+
language: { title: "What language should the AI use?", subtitle: "e.g. English, Portugu\xEAs, Espa\xF1ol..." }
|
|
450
|
+
};
|
|
451
|
+
function PersonaApp({ existing, onComplete }) {
|
|
452
|
+
const { stdout } = useStdout();
|
|
453
|
+
const { exit } = useApp();
|
|
454
|
+
const width = Math.min(stdout?.columns || 80, 80);
|
|
455
|
+
const CLEAR2 = "\x1B[2J\x1B[H";
|
|
456
|
+
const [step, setStep] = useState("name");
|
|
457
|
+
const [data, setData] = useState({
|
|
458
|
+
name: existing?.name || "",
|
|
459
|
+
role: existing?.role || "",
|
|
460
|
+
context: existing?.context || "",
|
|
461
|
+
technical_level: existing?.technical_level || "intermediate",
|
|
462
|
+
language: existing?.language || "English"
|
|
463
|
+
});
|
|
464
|
+
const [inputValue, setInputValue] = useState(existing?.name || "");
|
|
465
|
+
const [levelCursor, setLevelCursor] = useState(
|
|
466
|
+
TECHNICAL_LEVELS.findIndex((l) => l.value === (existing?.technical_level || "intermediate"))
|
|
467
|
+
);
|
|
468
|
+
const goTo = useCallback((next) => {
|
|
469
|
+
stdout?.write(CLEAR2);
|
|
470
|
+
setStep(next);
|
|
471
|
+
}, [stdout]);
|
|
472
|
+
const handleTextSubmit = useCallback((value, field, next) => {
|
|
473
|
+
if (!value.trim()) return;
|
|
474
|
+
setData((prev) => ({ ...prev, [field]: value.trim() }));
|
|
475
|
+
setInputValue("");
|
|
476
|
+
goTo(next);
|
|
477
|
+
}, [goTo]);
|
|
478
|
+
useInput((input, key) => {
|
|
479
|
+
if (step === "technical_level") {
|
|
480
|
+
if (key.upArrow) setLevelCursor((p) => p > 0 ? p - 1 : TECHNICAL_LEVELS.length - 1);
|
|
481
|
+
if (key.downArrow) setLevelCursor((p) => p < TECHNICAL_LEVELS.length - 1 ? p + 1 : 0);
|
|
482
|
+
if (key.return) {
|
|
483
|
+
setData((prev) => ({ ...prev, technical_level: TECHNICAL_LEVELS[levelCursor].value }));
|
|
484
|
+
setInputValue(data.context || "");
|
|
485
|
+
goTo("context");
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
if (step === "review") {
|
|
489
|
+
if (key.return || input === "y") {
|
|
490
|
+
onComplete(data);
|
|
491
|
+
goTo("done");
|
|
492
|
+
}
|
|
493
|
+
if (input === "b" || key.leftArrow) {
|
|
494
|
+
setInputValue(data.name);
|
|
495
|
+
goTo("name");
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (step === "done" && key.return) {
|
|
499
|
+
exit();
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
const info = STEP_INFO[step];
|
|
503
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width, children: [
|
|
504
|
+
/* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
505
|
+
/* @__PURE__ */ jsxs(Text, { color: colors.brand, bold: true, children: [
|
|
506
|
+
symbols.tree,
|
|
507
|
+
" Repo Hub"
|
|
508
|
+
] }),
|
|
509
|
+
/* @__PURE__ */ jsxs(Text, { color: colors.dim, children: [
|
|
510
|
+
" ",
|
|
511
|
+
symbols.line,
|
|
512
|
+
" Persona Setup"
|
|
513
|
+
] })
|
|
514
|
+
] }),
|
|
515
|
+
info && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, paddingLeft: 1, children: [
|
|
516
|
+
/* @__PURE__ */ jsx(Text, { color: colors.white, bold: true, children: info.title }),
|
|
517
|
+
/* @__PURE__ */ jsx(Text, { color: colors.dim, children: info.subtitle })
|
|
518
|
+
] }),
|
|
519
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [
|
|
520
|
+
step === "name" && /* @__PURE__ */ jsxs(Box, { children: [
|
|
521
|
+
/* @__PURE__ */ jsx(Text, { color: colors.brand, bold: true, children: "\u276F " }),
|
|
522
|
+
/* @__PURE__ */ jsx(
|
|
523
|
+
TextInput,
|
|
524
|
+
{
|
|
525
|
+
value: inputValue,
|
|
526
|
+
onChange: setInputValue,
|
|
527
|
+
onSubmit: (v) => {
|
|
528
|
+
handleTextSubmit(v, "name", "role");
|
|
529
|
+
setInputValue(data.role || "");
|
|
530
|
+
},
|
|
531
|
+
placeholder: "Jo\xE3o"
|
|
532
|
+
}
|
|
533
|
+
)
|
|
534
|
+
] }),
|
|
535
|
+
step === "role" && /* @__PURE__ */ jsxs(Box, { children: [
|
|
536
|
+
/* @__PURE__ */ jsx(Text, { color: colors.brand, bold: true, children: "\u276F " }),
|
|
537
|
+
/* @__PURE__ */ jsx(
|
|
538
|
+
TextInput,
|
|
539
|
+
{
|
|
540
|
+
value: inputValue,
|
|
541
|
+
onChange: setInputValue,
|
|
542
|
+
onSubmit: (v) => {
|
|
543
|
+
handleTextSubmit(v, "role", "technical_level");
|
|
544
|
+
},
|
|
545
|
+
placeholder: "CEO"
|
|
546
|
+
}
|
|
547
|
+
)
|
|
548
|
+
] }),
|
|
549
|
+
step === "technical_level" && /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: TECHNICAL_LEVELS.map((level, i) => {
|
|
550
|
+
const active = i === levelCursor;
|
|
551
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
552
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
553
|
+
/* @__PURE__ */ jsx(Text, { color: active ? colors.brand : colors.dim, children: active ? `${symbols.arrow} ` : " " }),
|
|
554
|
+
/* @__PURE__ */ jsx(Text, { color: active ? colors.white : colors.muted, bold: active, children: level.label })
|
|
555
|
+
] }),
|
|
556
|
+
/* @__PURE__ */ jsx(Box, { paddingLeft: 4, children: /* @__PURE__ */ jsx(Text, { color: colors.dim, children: level.description }) })
|
|
557
|
+
] }, level.value);
|
|
558
|
+
}) }),
|
|
559
|
+
step === "context" && /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsxs(Box, { children: [
|
|
560
|
+
/* @__PURE__ */ jsx(Text, { color: colors.brand, bold: true, children: "\u276F " }),
|
|
561
|
+
/* @__PURE__ */ jsx(
|
|
562
|
+
TextInput,
|
|
563
|
+
{
|
|
564
|
+
value: inputValue,
|
|
565
|
+
onChange: setInputValue,
|
|
566
|
+
onSubmit: (v) => {
|
|
567
|
+
setData((prev) => ({ ...prev, context: v.trim() }));
|
|
568
|
+
setInputValue(data.language || "English");
|
|
569
|
+
goTo("language");
|
|
570
|
+
},
|
|
571
|
+
placeholder: "(optional \u2014 press Enter to skip)"
|
|
572
|
+
}
|
|
573
|
+
)
|
|
574
|
+
] }) }),
|
|
575
|
+
step === "language" && /* @__PURE__ */ jsxs(Box, { children: [
|
|
576
|
+
/* @__PURE__ */ jsx(Text, { color: colors.brand, bold: true, children: "\u276F " }),
|
|
577
|
+
/* @__PURE__ */ jsx(
|
|
578
|
+
TextInput,
|
|
579
|
+
{
|
|
580
|
+
value: inputValue,
|
|
581
|
+
onChange: setInputValue,
|
|
582
|
+
onSubmit: (v) => {
|
|
583
|
+
handleTextSubmit(v || "English", "language", "review");
|
|
584
|
+
},
|
|
585
|
+
placeholder: "English"
|
|
586
|
+
}
|
|
587
|
+
)
|
|
588
|
+
] }),
|
|
589
|
+
step === "review" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
590
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingLeft: 1, borderStyle: "round", borderColor: colors.dim, paddingRight: 1, children: [
|
|
591
|
+
/* @__PURE__ */ jsx(ReviewRow, { label: "Name", value: data.name }),
|
|
592
|
+
/* @__PURE__ */ jsx(ReviewRow, { label: "Role", value: data.role }),
|
|
593
|
+
/* @__PURE__ */ jsx(ReviewRow, { label: "Level", value: TECHNICAL_LEVELS.find((l) => l.value === data.technical_level)?.label || data.technical_level }),
|
|
594
|
+
data.context && /* @__PURE__ */ jsx(ReviewRow, { label: "Context", value: data.context }),
|
|
595
|
+
/* @__PURE__ */ jsx(ReviewRow, { label: "Language", value: data.language })
|
|
596
|
+
] }),
|
|
597
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { color: colors.dim, children: [
|
|
598
|
+
/* @__PURE__ */ jsx(Text, { color: colors.brand, children: "enter" }),
|
|
599
|
+
" save ",
|
|
600
|
+
/* @__PURE__ */ jsx(Text, { color: colors.muted, children: "b" }),
|
|
601
|
+
" start over"
|
|
602
|
+
] }) })
|
|
603
|
+
] }),
|
|
604
|
+
step === "done" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", alignItems: "center", children: [
|
|
605
|
+
/* @__PURE__ */ jsxs(Text, { color: colors.brand, bold: true, children: [
|
|
606
|
+
symbols.check,
|
|
607
|
+
" Persona saved"
|
|
608
|
+
] }),
|
|
609
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { color: colors.dim, children: [
|
|
610
|
+
"Run ",
|
|
611
|
+
/* @__PURE__ */ jsx(Text, { color: colors.white, bold: true, children: "hub generate" }),
|
|
612
|
+
" to apply it to your AI agent."
|
|
613
|
+
] }) }),
|
|
614
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { color: colors.dim, children: [
|
|
615
|
+
"Press ",
|
|
616
|
+
/* @__PURE__ */ jsx(Text, { color: colors.brand, bold: true, children: "Enter" }),
|
|
617
|
+
" to exit"
|
|
618
|
+
] }) })
|
|
619
|
+
] })
|
|
620
|
+
] })
|
|
621
|
+
] });
|
|
622
|
+
}
|
|
623
|
+
function ReviewRow({ label, value }) {
|
|
624
|
+
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
625
|
+
/* @__PURE__ */ jsx(Box, { width: 10, children: /* @__PURE__ */ jsx(Text, { color: colors.muted, children: label }) }),
|
|
626
|
+
/* @__PURE__ */ jsx(Text, { color: colors.white, bold: true, children: value })
|
|
627
|
+
] });
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// src/commands/persona.ts
|
|
631
|
+
import { stringify } from "yaml";
|
|
632
|
+
var ENTER_ALT_SCREEN = "\x1B[?1049h";
|
|
633
|
+
var EXIT_ALT_SCREEN = "\x1B[?1049l";
|
|
634
|
+
var CLEAR = "\x1B[2J\x1B[H";
|
|
635
|
+
var HIDE_CURSOR = "\x1B[?25l";
|
|
636
|
+
var SHOW_CURSOR = "\x1B[?25h";
|
|
637
|
+
function getPersonaPath(hubDir) {
|
|
638
|
+
return join3(hubDir, ".hub", "persona.yaml");
|
|
639
|
+
}
|
|
640
|
+
async function loadPersona(hubDir) {
|
|
641
|
+
const personaPath = getPersonaPath(hubDir);
|
|
642
|
+
if (!existsSync2(personaPath)) return null;
|
|
643
|
+
try {
|
|
644
|
+
const { parse } = await import("yaml");
|
|
645
|
+
const content = await readFile3(personaPath, "utf-8");
|
|
646
|
+
return parse(content);
|
|
647
|
+
} catch {
|
|
648
|
+
return null;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
function buildPersonaSection(persona) {
|
|
652
|
+
const lines = [];
|
|
653
|
+
lines.push(`
|
|
654
|
+
## User Persona
|
|
655
|
+
`);
|
|
656
|
+
lines.push(`You are talking to **${persona.name}**, who is a **${persona.role}**.`);
|
|
657
|
+
if (persona.technical_level === "non-technical") {
|
|
658
|
+
lines.push(`
|
|
659
|
+
${persona.name} is not technical. Adapt your communication:
|
|
660
|
+
- Never use jargon, acronyms, or technical terms without explaining them in plain language first.
|
|
661
|
+
- Explain decisions in terms of business impact, user experience, and outcomes \u2014 not implementation details.
|
|
662
|
+
- When showing progress, focus on what changed for the user/product, not what code was modified.
|
|
663
|
+
- If you need to mention something technical, use analogies and simple language.
|
|
664
|
+
- Keep responses short and focused on what matters to them.
|
|
665
|
+
- When asking questions, frame them as business/product decisions, not technical choices.
|
|
666
|
+
- Never show code snippets, terminal output, or file paths unless explicitly asked.`);
|
|
667
|
+
} else if (persona.technical_level === "beginner") {
|
|
668
|
+
lines.push(`
|
|
669
|
+
${persona.name} is learning and not deeply technical yet. Adapt your communication:
|
|
670
|
+
- Explain technical concepts briefly when you first mention them.
|
|
671
|
+
- Avoid deep implementation details unless asked.
|
|
672
|
+
- Use simple language but don't shy away from introducing technical terms with context.
|
|
673
|
+
- When showing code or commands, briefly explain what they do.
|
|
674
|
+
- Be encouraging and patient \u2014 frame things as learning opportunities.`);
|
|
675
|
+
} else if (persona.technical_level === "intermediate") {
|
|
676
|
+
lines.push(`
|
|
677
|
+
${persona.name} is comfortable with code but may not know every tool or pattern. Adapt your communication:
|
|
678
|
+
- Use technical language normally but explain niche or advanced concepts when relevant.
|
|
679
|
+
- Show code and commands without excessive explanation, but add context for non-obvious decisions.
|
|
680
|
+
- Focus on the "why" behind architectural choices.`);
|
|
681
|
+
} else {
|
|
682
|
+
lines.push(`
|
|
683
|
+
${persona.name} is an experienced developer. Communicate directly:
|
|
684
|
+
- Be concise and technical. Skip basic explanations.
|
|
685
|
+
- Focus on trade-offs, edge cases, and non-obvious implications.
|
|
686
|
+
- Show code directly without hand-holding.`);
|
|
687
|
+
}
|
|
688
|
+
if (persona.context) {
|
|
689
|
+
lines.push(`
|
|
690
|
+
Additional context about ${persona.name}: ${persona.context}`);
|
|
691
|
+
}
|
|
692
|
+
if (persona.language && persona.language.toLowerCase() !== "english") {
|
|
693
|
+
lines.push(`
|
|
694
|
+
Always communicate with ${persona.name} in **${persona.language}**.`);
|
|
695
|
+
}
|
|
696
|
+
return lines.join("\n");
|
|
697
|
+
}
|
|
698
|
+
var personaCommand = new Command("persona").description("Set up your personal AI profile \u2014 adapts how the agent communicates with you").action(async () => {
|
|
699
|
+
const hubDir = process.cwd();
|
|
700
|
+
const hubPath = join3(hubDir, ".hub");
|
|
701
|
+
await mkdir3(hubPath, { recursive: true });
|
|
702
|
+
const existing = await loadPersona(hubDir);
|
|
703
|
+
process.stdout.write(ENTER_ALT_SCREEN + CLEAR + HIDE_CURSOR);
|
|
704
|
+
const cleanup = () => {
|
|
705
|
+
process.stdout.write(SHOW_CURSOR + EXIT_ALT_SCREEN);
|
|
706
|
+
};
|
|
707
|
+
process.on("SIGINT", () => {
|
|
708
|
+
cleanup();
|
|
709
|
+
process.exit(0);
|
|
710
|
+
});
|
|
711
|
+
const { waitUntilExit } = render(
|
|
712
|
+
React.createElement(PersonaApp, {
|
|
713
|
+
existing: existing ?? void 0,
|
|
714
|
+
onComplete: async (persona) => {
|
|
715
|
+
const personaPath = getPersonaPath(hubDir);
|
|
716
|
+
await writeFile3(personaPath, stringify(persona), "utf-8");
|
|
717
|
+
}
|
|
718
|
+
})
|
|
719
|
+
);
|
|
720
|
+
await waitUntilExit();
|
|
721
|
+
cleanup();
|
|
722
|
+
});
|
|
723
|
+
|
|
393
724
|
// src/commands/generate.ts
|
|
394
725
|
var HUB_DOCS_URL = "https://hub.arvore.com.br/llms-full.txt";
|
|
395
726
|
async function syncRemoteSources(config, hubDir, skillsDir, steeringDir) {
|
|
@@ -403,6 +734,13 @@ async function syncRemoteSources(config, hubDir, skillsDir, steeringDir) {
|
|
|
403
734
|
console.log(chalk3.yellow(` ${result.errors.length} remote source(s) failed`));
|
|
404
735
|
}
|
|
405
736
|
}
|
|
737
|
+
function getRemoteSkillNames(config) {
|
|
738
|
+
const names = /* @__PURE__ */ new Set();
|
|
739
|
+
for (const source of config.remote_sources ?? []) {
|
|
740
|
+
if (source.type === "skill") names.add(source.name);
|
|
741
|
+
}
|
|
742
|
+
return names;
|
|
743
|
+
}
|
|
406
744
|
function buildDesignSection(config) {
|
|
407
745
|
const design = config.design;
|
|
408
746
|
if (!design) return null;
|
|
@@ -464,9 +802,9 @@ function parseFrontMatter(content) {
|
|
|
464
802
|
}
|
|
465
803
|
async function readExistingMcpDisabledState(mcpJsonPath) {
|
|
466
804
|
const disabledState = {};
|
|
467
|
-
if (!
|
|
805
|
+
if (!existsSync3(mcpJsonPath)) return disabledState;
|
|
468
806
|
try {
|
|
469
|
-
const content = JSON.parse(await
|
|
807
|
+
const content = JSON.parse(await readFile4(mcpJsonPath, "utf-8"));
|
|
470
808
|
const servers = content.mcpServers || content.mcp || {};
|
|
471
809
|
for (const [name, config] of Object.entries(servers)) {
|
|
472
810
|
if (typeof config.disabled === "boolean") {
|
|
@@ -492,8 +830,8 @@ async function fetchHubDocsSkill(skillsDir) {
|
|
|
492
830
|
return;
|
|
493
831
|
}
|
|
494
832
|
const content = await res.text();
|
|
495
|
-
const hubSkillDir =
|
|
496
|
-
await
|
|
833
|
+
const hubSkillDir = join4(skillsDir, "hub-docs");
|
|
834
|
+
await mkdir4(hubSkillDir, { recursive: true });
|
|
497
835
|
const skillContent = `---
|
|
498
836
|
name: hub-docs
|
|
499
837
|
description: Repo Hub (rhm) documentation. Use when working with hub.yaml, hub CLI commands, agent orchestration, MCP configuration, skills, workflows, or multi-repo workspace setup.
|
|
@@ -501,7 +839,7 @@ triggers: [hub, rhm, hub.yaml, generate, scan, setup, orchestrator, multi-repo,
|
|
|
501
839
|
---
|
|
502
840
|
|
|
503
841
|
${content}`;
|
|
504
|
-
await
|
|
842
|
+
await writeFile4(join4(hubSkillDir, "SKILL.md"), skillContent, "utf-8");
|
|
505
843
|
console.log(chalk3.green(" Fetched hub-docs skill from hub.arvore.com.br"));
|
|
506
844
|
} catch {
|
|
507
845
|
console.log(chalk3.yellow(` Could not fetch hub docs, skipping hub-docs skill`));
|
|
@@ -576,7 +914,7 @@ function buildClaudeHooks(hooks) {
|
|
|
576
914
|
return claudeHooks;
|
|
577
915
|
}
|
|
578
916
|
async function generateEditorCommands(config, hubDir, targetDir, editorName) {
|
|
579
|
-
const commandsDir =
|
|
917
|
+
const commandsDir = join4(targetDir, "commands");
|
|
580
918
|
let count = 0;
|
|
581
919
|
if (config.commands_dir) {
|
|
582
920
|
const srcDir = resolve2(hubDir, config.commands_dir);
|
|
@@ -584,9 +922,9 @@ async function generateEditorCommands(config, hubDir, targetDir, editorName) {
|
|
|
584
922
|
const files = await readdir2(srcDir);
|
|
585
923
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
586
924
|
if (mdFiles.length > 0) {
|
|
587
|
-
await
|
|
925
|
+
await mkdir4(commandsDir, { recursive: true });
|
|
588
926
|
for (const file of mdFiles) {
|
|
589
|
-
await copyFile(
|
|
927
|
+
await copyFile(join4(srcDir, file), join4(commandsDir, file));
|
|
590
928
|
count++;
|
|
591
929
|
}
|
|
592
930
|
}
|
|
@@ -595,10 +933,10 @@ async function generateEditorCommands(config, hubDir, targetDir, editorName) {
|
|
|
595
933
|
}
|
|
596
934
|
}
|
|
597
935
|
if (config.commands) {
|
|
598
|
-
await
|
|
936
|
+
await mkdir4(commandsDir, { recursive: true });
|
|
599
937
|
for (const [name, filePath] of Object.entries(config.commands)) {
|
|
600
938
|
const src = resolve2(hubDir, filePath);
|
|
601
|
-
const dest =
|
|
939
|
+
const dest = join4(commandsDir, name.endsWith(".md") ? name : `${name}.md`);
|
|
602
940
|
try {
|
|
603
941
|
await copyFile(src, dest);
|
|
604
942
|
count++;
|
|
@@ -613,27 +951,27 @@ async function generateEditorCommands(config, hubDir, targetDir, editorName) {
|
|
|
613
951
|
}
|
|
614
952
|
async function writeManagedFile(filePath, managedLines) {
|
|
615
953
|
const managedBlock = [HUB_MARKER_START, ...managedLines, HUB_MARKER_END].join("\n");
|
|
616
|
-
if (
|
|
617
|
-
const existing = await
|
|
954
|
+
if (existsSync3(filePath)) {
|
|
955
|
+
const existing = await readFile4(filePath, "utf-8");
|
|
618
956
|
const startIdx = existing.indexOf(HUB_MARKER_START);
|
|
619
957
|
const endIdx = existing.indexOf(HUB_MARKER_END);
|
|
620
958
|
if (startIdx !== -1 && endIdx !== -1) {
|
|
621
959
|
const before = existing.substring(0, startIdx);
|
|
622
960
|
const after = existing.substring(endIdx + HUB_MARKER_END.length);
|
|
623
|
-
await
|
|
961
|
+
await writeFile4(filePath, before + managedBlock + after, "utf-8");
|
|
624
962
|
return;
|
|
625
963
|
}
|
|
626
|
-
await
|
|
964
|
+
await writeFile4(filePath, managedBlock + "\n\n" + existing, "utf-8");
|
|
627
965
|
return;
|
|
628
966
|
}
|
|
629
|
-
await
|
|
967
|
+
await writeFile4(filePath, managedBlock + "\n", "utf-8");
|
|
630
968
|
}
|
|
631
969
|
async function generateCursor(config, hubDir) {
|
|
632
|
-
const cursorDir =
|
|
633
|
-
await
|
|
634
|
-
await
|
|
970
|
+
const cursorDir = join4(hubDir, ".cursor");
|
|
971
|
+
await mkdir4(join4(cursorDir, "rules"), { recursive: true });
|
|
972
|
+
await mkdir4(join4(cursorDir, "agents"), { recursive: true });
|
|
635
973
|
const gitignoreLines = buildGitignoreLines(config);
|
|
636
|
-
await writeManagedFile(
|
|
974
|
+
await writeManagedFile(join4(hubDir, ".gitignore"), gitignoreLines);
|
|
637
975
|
console.log(chalk3.green(" Generated .gitignore"));
|
|
638
976
|
const cursorignoreLines = [
|
|
639
977
|
"# Re-include repositories for AI context"
|
|
@@ -643,7 +981,7 @@ async function generateCursor(config, hubDir) {
|
|
|
643
981
|
cursorignoreLines.push(`!${repoDir}/`);
|
|
644
982
|
}
|
|
645
983
|
cursorignoreLines.push("", "# Re-include tasks for agent collaboration", "!tasks/");
|
|
646
|
-
await writeManagedFile(
|
|
984
|
+
await writeManagedFile(join4(hubDir, ".cursorignore"), cursorignoreLines);
|
|
647
985
|
console.log(chalk3.green(" Generated .cursorignore"));
|
|
648
986
|
if (config.mcps?.length) {
|
|
649
987
|
const mcpConfig = {};
|
|
@@ -656,27 +994,36 @@ async function generateCursor(config, hubDir) {
|
|
|
656
994
|
mcpConfig[mcp.name] = buildCursorMcpEntry(mcp);
|
|
657
995
|
}
|
|
658
996
|
}
|
|
659
|
-
|
|
660
|
-
|
|
997
|
+
const sandbox = getSandboxService(config);
|
|
998
|
+
if (sandbox && !mcpConfig["sandbox"]) {
|
|
999
|
+
mcpConfig["sandbox"] = buildSandboxMcpEntry(sandbox.port);
|
|
1000
|
+
}
|
|
1001
|
+
await writeFile4(
|
|
1002
|
+
join4(cursorDir, "mcp.json"),
|
|
661
1003
|
JSON.stringify({ mcpServers: mcpConfig }, null, 2) + "\n",
|
|
662
1004
|
"utf-8"
|
|
663
1005
|
);
|
|
664
1006
|
console.log(chalk3.green(" Generated .cursor/mcp.json"));
|
|
665
1007
|
}
|
|
666
1008
|
const orchestratorRule = buildOrchestratorRule(config);
|
|
667
|
-
await
|
|
1009
|
+
await writeFile4(join4(cursorDir, "rules", "orchestrator.mdc"), orchestratorRule, "utf-8");
|
|
668
1010
|
console.log(chalk3.green(" Generated .cursor/rules/orchestrator.mdc"));
|
|
669
1011
|
const cleanedOrchestratorForAgents = orchestratorRule.replace(/^---[\s\S]*?---\n/m, "").trim();
|
|
670
1012
|
const skillsSectionCursor = await buildSkillsSection(hubDir, config);
|
|
671
|
-
const
|
|
672
|
-
|
|
1013
|
+
const personaCursor = await loadPersona(hubDir);
|
|
1014
|
+
const personaSectionCursor = personaCursor ? buildPersonaSection(personaCursor) : "";
|
|
1015
|
+
const agentsMdCursor = [cleanedOrchestratorForAgents, skillsSectionCursor, personaSectionCursor].filter(Boolean).join("\n");
|
|
1016
|
+
await writeFile4(join4(hubDir, "AGENTS.md"), agentsMdCursor + "\n", "utf-8");
|
|
673
1017
|
console.log(chalk3.green(" Generated AGENTS.md"));
|
|
1018
|
+
if (personaCursor) {
|
|
1019
|
+
console.log(chalk3.green(` Applied persona: ${personaCursor.name} (${personaCursor.role})`));
|
|
1020
|
+
}
|
|
674
1021
|
const hubSteeringDirCursor = resolve2(hubDir, "steering");
|
|
675
1022
|
try {
|
|
676
1023
|
const steeringFiles = await readdir2(hubSteeringDirCursor);
|
|
677
1024
|
const mdFiles = steeringFiles.filter((f) => f.endsWith(".md"));
|
|
678
1025
|
for (const file of mdFiles) {
|
|
679
|
-
const raw = await
|
|
1026
|
+
const raw = await readFile4(join4(hubSteeringDirCursor, file), "utf-8");
|
|
680
1027
|
const content = stripFrontMatter(raw);
|
|
681
1028
|
const mdcName = file.replace(/\.md$/, ".mdc");
|
|
682
1029
|
const mdcContent = `---
|
|
@@ -685,7 +1032,7 @@ alwaysApply: true
|
|
|
685
1032
|
---
|
|
686
1033
|
|
|
687
1034
|
${content}`;
|
|
688
|
-
await
|
|
1035
|
+
await writeFile4(join4(cursorDir, "rules", mdcName), mdcContent, "utf-8");
|
|
689
1036
|
}
|
|
690
1037
|
if (mdFiles.length > 0) {
|
|
691
1038
|
console.log(chalk3.green(` Copied ${mdFiles.length} steering files to .cursor/rules/`));
|
|
@@ -696,25 +1043,35 @@ ${content}`;
|
|
|
696
1043
|
try {
|
|
697
1044
|
const agentFiles = await readdir2(agentsDir);
|
|
698
1045
|
const mdFiles = agentFiles.filter((f) => f.endsWith(".md"));
|
|
1046
|
+
const sandboxSvc = getSandboxService(config);
|
|
699
1047
|
for (const file of mdFiles) {
|
|
700
|
-
|
|
1048
|
+
if (sandboxSvc) {
|
|
1049
|
+
const agentName = file.replace(/\.md$/, "");
|
|
1050
|
+
const agentContent = await readFile4(join4(agentsDir, file), "utf-8");
|
|
1051
|
+
const withSandbox = injectSandboxContext(agentName, agentContent, sandboxSvc.port);
|
|
1052
|
+
await writeFile4(join4(cursorDir, "agents", file), withSandbox, "utf-8");
|
|
1053
|
+
} else {
|
|
1054
|
+
await copyFile(join4(agentsDir, file), join4(cursorDir, "agents", file));
|
|
1055
|
+
}
|
|
701
1056
|
}
|
|
702
1057
|
console.log(chalk3.green(` Copied ${mdFiles.length} agent definitions`));
|
|
703
1058
|
} catch {
|
|
704
1059
|
console.log(chalk3.yellow(" No agents/ directory found, skipping agent copy"));
|
|
705
1060
|
}
|
|
706
1061
|
const skillsDir = resolve2(hubDir, "skills");
|
|
1062
|
+
const remoteSkillsCursor = getRemoteSkillNames(config);
|
|
707
1063
|
try {
|
|
708
1064
|
const skillFolders = await readdir2(skillsDir);
|
|
709
|
-
const cursorSkillsDir =
|
|
710
|
-
await
|
|
1065
|
+
const cursorSkillsDir = join4(cursorDir, "skills");
|
|
1066
|
+
await mkdir4(cursorSkillsDir, { recursive: true });
|
|
711
1067
|
let count = 0;
|
|
712
1068
|
for (const folder of skillFolders) {
|
|
713
|
-
|
|
1069
|
+
if (remoteSkillsCursor.has(folder)) continue;
|
|
1070
|
+
const skillFile = join4(skillsDir, folder, "SKILL.md");
|
|
714
1071
|
try {
|
|
715
|
-
await
|
|
716
|
-
const srcDir =
|
|
717
|
-
const targetDir =
|
|
1072
|
+
await readFile4(skillFile);
|
|
1073
|
+
const srcDir = join4(skillsDir, folder);
|
|
1074
|
+
const targetDir = join4(cursorSkillsDir, folder);
|
|
718
1075
|
await cp(srcDir, targetDir, { recursive: true });
|
|
719
1076
|
count++;
|
|
720
1077
|
} catch {
|
|
@@ -725,15 +1082,15 @@ ${content}`;
|
|
|
725
1082
|
}
|
|
726
1083
|
} catch {
|
|
727
1084
|
}
|
|
728
|
-
const cursorSkillsDirForDocs =
|
|
729
|
-
await
|
|
1085
|
+
const cursorSkillsDirForDocs = join4(cursorDir, "skills");
|
|
1086
|
+
await mkdir4(cursorSkillsDirForDocs, { recursive: true });
|
|
730
1087
|
await fetchHubDocsSkill(cursorSkillsDirForDocs);
|
|
731
|
-
await syncRemoteSources(config, hubDir,
|
|
1088
|
+
await syncRemoteSources(config, hubDir, join4(cursorDir, "skills"), join4(cursorDir, "rules"));
|
|
732
1089
|
if (config.hooks) {
|
|
733
1090
|
const cursorHooks = buildCursorHooks(config.hooks);
|
|
734
1091
|
if (cursorHooks) {
|
|
735
|
-
await
|
|
736
|
-
|
|
1092
|
+
await writeFile4(
|
|
1093
|
+
join4(cursorDir, "hooks.json"),
|
|
737
1094
|
JSON.stringify(cursorHooks, null, 2) + "\n",
|
|
738
1095
|
"utf-8"
|
|
739
1096
|
);
|
|
@@ -743,6 +1100,14 @@ ${content}`;
|
|
|
743
1100
|
await generateEditorCommands(config, hubDir, cursorDir, ".cursor/commands/");
|
|
744
1101
|
await generateVSCodeSettings(config, hubDir);
|
|
745
1102
|
}
|
|
1103
|
+
function buildSandboxMcpEntry(port) {
|
|
1104
|
+
return { url: `http://localhost:${port}/mcp` };
|
|
1105
|
+
}
|
|
1106
|
+
function getSandboxService(config) {
|
|
1107
|
+
const svc = config.services?.find((s) => s.type === "sandbox");
|
|
1108
|
+
if (!svc) return null;
|
|
1109
|
+
return { port: svc.port ?? 8080 };
|
|
1110
|
+
}
|
|
746
1111
|
function buildProxyUpstreams(proxyMcp, allMcps) {
|
|
747
1112
|
const upstreamNames = new Set(proxyMcp.upstreams || []);
|
|
748
1113
|
const upstreamEntries = [];
|
|
@@ -876,6 +1241,24 @@ function buildKiroMcpEntry(mcp, mode = "editor") {
|
|
|
876
1241
|
...autoApprove && { autoApprove }
|
|
877
1242
|
};
|
|
878
1243
|
}
|
|
1244
|
+
var SANDBOX_AGENT_TARGETS = /* @__PURE__ */ new Set(["qa-frontend", "qa-backend", "coding-frontend", "coding-backend"]);
|
|
1245
|
+
function injectSandboxContext(agentName, content, sandboxPort) {
|
|
1246
|
+
if (!SANDBOX_AGENT_TARGETS.has(agentName)) return content;
|
|
1247
|
+
const section = `
|
|
1248
|
+
## Sandbox Environment
|
|
1249
|
+
|
|
1250
|
+
A sandboxed execution environment is available via the \`sandbox\` MCP (http://localhost:${sandboxPort}/mcp).
|
|
1251
|
+
|
|
1252
|
+
Use it to:
|
|
1253
|
+
- Run shell commands: \`shell.exec\`
|
|
1254
|
+
- Read/write files: \`file.read\`, \`file.write\`
|
|
1255
|
+
- Control a real browser: \`browser.navigate\`, \`browser.screenshot\`, \`browser.click\`
|
|
1256
|
+
- Execute code: \`jupyter.execute\`
|
|
1257
|
+
|
|
1258
|
+
The sandbox workspace is mounted at \`/home/gem/workspace\`. Prefer running builds, tests, and browser interactions inside the sandbox rather than on the host machine.
|
|
1259
|
+
`;
|
|
1260
|
+
return content.trimEnd() + "\n" + section;
|
|
1261
|
+
}
|
|
879
1262
|
function buildKiroAgentContent(rawContent) {
|
|
880
1263
|
const fmMatch = rawContent.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
881
1264
|
if (!fmMatch) {
|
|
@@ -1179,9 +1562,9 @@ async function buildSkillsSection(hubDir, config) {
|
|
|
1179
1562
|
try {
|
|
1180
1563
|
const folders = await readdir2(skillsDir);
|
|
1181
1564
|
for (const folder of folders) {
|
|
1182
|
-
const skillPath =
|
|
1565
|
+
const skillPath = join4(skillsDir, folder, "SKILL.md");
|
|
1183
1566
|
try {
|
|
1184
|
-
const content = await
|
|
1567
|
+
const content = await readFile4(skillPath, "utf-8");
|
|
1185
1568
|
const fm = parseFrontMatter(content);
|
|
1186
1569
|
if (fm?.name) {
|
|
1187
1570
|
skillEntries.push({
|
|
@@ -1504,18 +1887,18 @@ If any validation agent leaves comments requiring fixes, call the relevant codin
|
|
|
1504
1887
|
return parts.join("\n");
|
|
1505
1888
|
}
|
|
1506
1889
|
async function generateOpenCode(config, hubDir) {
|
|
1507
|
-
const opencodeDir =
|
|
1508
|
-
await
|
|
1509
|
-
await
|
|
1510
|
-
await
|
|
1511
|
-
await
|
|
1512
|
-
await
|
|
1890
|
+
const opencodeDir = join4(hubDir, ".opencode");
|
|
1891
|
+
await mkdir4(join4(opencodeDir, "agents"), { recursive: true });
|
|
1892
|
+
await mkdir4(join4(opencodeDir, "rules"), { recursive: true });
|
|
1893
|
+
await mkdir4(join4(opencodeDir, "skills"), { recursive: true });
|
|
1894
|
+
await mkdir4(join4(opencodeDir, "commands"), { recursive: true });
|
|
1895
|
+
await mkdir4(join4(opencodeDir, "plugins"), { recursive: true });
|
|
1513
1896
|
const gitignoreLines = buildGitignoreLines(config);
|
|
1514
|
-
await writeManagedFile(
|
|
1897
|
+
await writeManagedFile(join4(hubDir, ".gitignore"), gitignoreLines);
|
|
1515
1898
|
console.log(chalk3.green(" Generated .gitignore"));
|
|
1516
1899
|
if (config.repos.length > 0) {
|
|
1517
1900
|
const ignoreContent = config.repos.map((r) => `!${r.name}`).join("\n") + "\n";
|
|
1518
|
-
await
|
|
1901
|
+
await writeFile4(join4(hubDir, ".ignore"), ignoreContent, "utf-8");
|
|
1519
1902
|
console.log(chalk3.green(" Generated .ignore"));
|
|
1520
1903
|
}
|
|
1521
1904
|
const orchestratorContent = buildOpenCodeOrchestratorRule(config);
|
|
@@ -1523,22 +1906,27 @@ async function generateOpenCode(config, hubDir) {
|
|
|
1523
1906
|
"Development orchestrator. Delegates specialized work to subagents following a structured pipeline: refinement, coding, review, QA, and delivery.",
|
|
1524
1907
|
orchestratorContent
|
|
1525
1908
|
);
|
|
1526
|
-
await
|
|
1909
|
+
await writeFile4(join4(opencodeDir, "agents", "orchestrator.md"), orchestratorAgent, "utf-8");
|
|
1527
1910
|
console.log(chalk3.green(" Generated .opencode/agents/orchestrator.md (primary agent)"));
|
|
1528
|
-
await rm(
|
|
1911
|
+
await rm(join4(opencodeDir, "rules", "orchestrator.md")).catch(() => {
|
|
1529
1912
|
});
|
|
1530
1913
|
const skillsSectionOC = await buildSkillsSection(hubDir, config);
|
|
1531
|
-
const
|
|
1532
|
-
|
|
1914
|
+
const personaOC = await loadPersona(hubDir);
|
|
1915
|
+
const personaSectionOC = personaOC ? buildPersonaSection(personaOC) : "";
|
|
1916
|
+
const agentsMdOC = [orchestratorContent, skillsSectionOC, personaSectionOC].filter(Boolean).join("\n");
|
|
1917
|
+
await writeFile4(join4(hubDir, "AGENTS.md"), agentsMdOC + "\n", "utf-8");
|
|
1533
1918
|
console.log(chalk3.green(" Generated AGENTS.md"));
|
|
1919
|
+
if (personaOC) {
|
|
1920
|
+
console.log(chalk3.green(` Applied persona: ${personaOC.name} (${personaOC.role})`));
|
|
1921
|
+
}
|
|
1534
1922
|
const hubSteeringDirOC = resolve2(hubDir, "steering");
|
|
1535
1923
|
try {
|
|
1536
1924
|
const steeringFiles = await readdir2(hubSteeringDirOC);
|
|
1537
1925
|
const mdFiles = steeringFiles.filter((f) => f.endsWith(".md"));
|
|
1538
1926
|
for (const file of mdFiles) {
|
|
1539
|
-
const raw = await
|
|
1927
|
+
const raw = await readFile4(join4(hubSteeringDirOC, file), "utf-8");
|
|
1540
1928
|
const content = stripFrontMatter(raw);
|
|
1541
|
-
await
|
|
1929
|
+
await writeFile4(join4(opencodeDir, "rules", file), content, "utf-8");
|
|
1542
1930
|
}
|
|
1543
1931
|
if (mdFiles.length > 0) {
|
|
1544
1932
|
console.log(chalk3.green(` Copied ${mdFiles.length} steering files to .opencode/rules/`));
|
|
@@ -1563,8 +1951,8 @@ async function generateOpenCode(config, hubDir) {
|
|
|
1563
1951
|
opencodeConfig.mcp = mcpConfig;
|
|
1564
1952
|
}
|
|
1565
1953
|
opencodeConfig.instructions = [".opencode/rules/*.md"];
|
|
1566
|
-
await
|
|
1567
|
-
|
|
1954
|
+
await writeFile4(
|
|
1955
|
+
join4(hubDir, "opencode.json"),
|
|
1568
1956
|
JSON.stringify(opencodeConfig, null, 2) + "\n",
|
|
1569
1957
|
"utf-8"
|
|
1570
1958
|
);
|
|
@@ -1575,24 +1963,26 @@ async function generateOpenCode(config, hubDir) {
|
|
|
1575
1963
|
const mdFiles = agentFiles.filter((f) => f.endsWith(".md"));
|
|
1576
1964
|
for (const file of mdFiles) {
|
|
1577
1965
|
if (file === "orchestrator.md") continue;
|
|
1578
|
-
const content = await
|
|
1966
|
+
const content = await readFile4(join4(agentsDir, file), "utf-8");
|
|
1579
1967
|
const agentName = file.replace(/\.md$/, "");
|
|
1580
1968
|
const converted = buildOpenCodeAgentMarkdown(agentName, content);
|
|
1581
|
-
await
|
|
1969
|
+
await writeFile4(join4(opencodeDir, "agents", file), converted, "utf-8");
|
|
1582
1970
|
}
|
|
1583
1971
|
console.log(chalk3.green(` Copied ${mdFiles.length} agents to .opencode/agents/`));
|
|
1584
1972
|
} catch {
|
|
1585
1973
|
console.log(chalk3.yellow(" No agents/ directory found, skipping agent copy"));
|
|
1586
1974
|
}
|
|
1587
1975
|
const skillsDir = resolve2(hubDir, "skills");
|
|
1976
|
+
const remoteSkillsOC = getRemoteSkillNames(config);
|
|
1588
1977
|
try {
|
|
1589
1978
|
const skillFolders = await readdir2(skillsDir);
|
|
1590
1979
|
let count = 0;
|
|
1591
1980
|
for (const folder of skillFolders) {
|
|
1592
|
-
|
|
1981
|
+
if (remoteSkillsOC.has(folder)) continue;
|
|
1982
|
+
const skillFile = join4(skillsDir, folder, "SKILL.md");
|
|
1593
1983
|
try {
|
|
1594
|
-
await
|
|
1595
|
-
await cp(
|
|
1984
|
+
await readFile4(skillFile);
|
|
1985
|
+
await cp(join4(skillsDir, folder), join4(opencodeDir, "skills", folder), { recursive: true });
|
|
1596
1986
|
count++;
|
|
1597
1987
|
} catch {
|
|
1598
1988
|
}
|
|
@@ -1602,13 +1992,13 @@ async function generateOpenCode(config, hubDir) {
|
|
|
1602
1992
|
}
|
|
1603
1993
|
} catch {
|
|
1604
1994
|
}
|
|
1605
|
-
await fetchHubDocsSkill(
|
|
1606
|
-
await syncRemoteSources(config, hubDir,
|
|
1995
|
+
await fetchHubDocsSkill(join4(opencodeDir, "skills"));
|
|
1996
|
+
await syncRemoteSources(config, hubDir, join4(opencodeDir, "skills"), join4(opencodeDir, "rules"));
|
|
1607
1997
|
await generateEditorCommands(config, hubDir, opencodeDir, ".opencode/commands/");
|
|
1608
1998
|
if (config.hooks) {
|
|
1609
1999
|
const plugin = buildOpenCodeHooksPlugin(config.hooks);
|
|
1610
2000
|
if (plugin) {
|
|
1611
|
-
await
|
|
2001
|
+
await writeFile4(join4(opencodeDir, "plugins", "hub-hooks.js"), plugin, "utf-8");
|
|
1612
2002
|
console.log(chalk3.green(" Generated .opencode/plugins/hub-hooks.js"));
|
|
1613
2003
|
}
|
|
1614
2004
|
}
|
|
@@ -2095,14 +2485,19 @@ function formatAction(action) {
|
|
|
2095
2485
|
return map[action] || action;
|
|
2096
2486
|
}
|
|
2097
2487
|
async function generateClaudeCode(config, hubDir) {
|
|
2098
|
-
const claudeDir =
|
|
2099
|
-
await
|
|
2488
|
+
const claudeDir = join4(hubDir, ".claude");
|
|
2489
|
+
await mkdir4(join4(claudeDir, "agents"), { recursive: true });
|
|
2100
2490
|
const orchestratorRule = buildOrchestratorRule(config);
|
|
2101
2491
|
const cleanedOrchestrator = orchestratorRule.replace(/^---[\s\S]*?---\n/m, "").trim();
|
|
2102
2492
|
const skillsSectionClaude = await buildSkillsSection(hubDir, config);
|
|
2103
|
-
const
|
|
2104
|
-
|
|
2493
|
+
const personaClaude = await loadPersona(hubDir);
|
|
2494
|
+
const personaSectionClaude = personaClaude ? buildPersonaSection(personaClaude) : "";
|
|
2495
|
+
const agentsMdClaude = [cleanedOrchestrator, skillsSectionClaude, personaSectionClaude].filter(Boolean).join("\n");
|
|
2496
|
+
await writeFile4(join4(hubDir, "AGENTS.md"), agentsMdClaude + "\n", "utf-8");
|
|
2105
2497
|
console.log(chalk3.green(" Generated AGENTS.md"));
|
|
2498
|
+
if (personaClaude) {
|
|
2499
|
+
console.log(chalk3.green(` Applied persona: ${personaClaude.name} (${personaClaude.role})`));
|
|
2500
|
+
}
|
|
2106
2501
|
const claudeMdSections = [];
|
|
2107
2502
|
claudeMdSections.push(cleanedOrchestrator);
|
|
2108
2503
|
const agentsDir = resolve2(hubDir, "agents");
|
|
@@ -2110,24 +2505,26 @@ async function generateClaudeCode(config, hubDir) {
|
|
|
2110
2505
|
const agentFiles = await readdir2(agentsDir);
|
|
2111
2506
|
const mdFiles = agentFiles.filter((f) => f.endsWith(".md"));
|
|
2112
2507
|
for (const file of mdFiles) {
|
|
2113
|
-
await copyFile(
|
|
2508
|
+
await copyFile(join4(agentsDir, file), join4(claudeDir, "agents", file));
|
|
2114
2509
|
}
|
|
2115
2510
|
console.log(chalk3.green(` Copied ${mdFiles.length} agents to .claude/agents/`));
|
|
2116
2511
|
} catch {
|
|
2117
2512
|
console.log(chalk3.yellow(" No agents/ directory found, skipping agent copy"));
|
|
2118
2513
|
}
|
|
2119
2514
|
const skillsDir = resolve2(hubDir, "skills");
|
|
2515
|
+
const remoteSkillsClaude = getRemoteSkillNames(config);
|
|
2120
2516
|
try {
|
|
2121
2517
|
const skillFolders = await readdir2(skillsDir);
|
|
2122
|
-
const claudeSkillsDir =
|
|
2123
|
-
await
|
|
2518
|
+
const claudeSkillsDir = join4(claudeDir, "skills");
|
|
2519
|
+
await mkdir4(claudeSkillsDir, { recursive: true });
|
|
2124
2520
|
let count = 0;
|
|
2125
2521
|
for (const folder of skillFolders) {
|
|
2126
|
-
|
|
2522
|
+
if (remoteSkillsClaude.has(folder)) continue;
|
|
2523
|
+
const skillFile = join4(skillsDir, folder, "SKILL.md");
|
|
2127
2524
|
try {
|
|
2128
|
-
await
|
|
2129
|
-
const srcDir =
|
|
2130
|
-
const targetDir =
|
|
2525
|
+
await readFile4(skillFile);
|
|
2526
|
+
const srcDir = join4(skillsDir, folder);
|
|
2527
|
+
const targetDir = join4(claudeSkillsDir, folder);
|
|
2131
2528
|
await cp(srcDir, targetDir, { recursive: true });
|
|
2132
2529
|
count++;
|
|
2133
2530
|
} catch {
|
|
@@ -2138,16 +2535,16 @@ async function generateClaudeCode(config, hubDir) {
|
|
|
2138
2535
|
}
|
|
2139
2536
|
} catch {
|
|
2140
2537
|
}
|
|
2141
|
-
const claudeSkillsDirForDocs =
|
|
2142
|
-
await
|
|
2538
|
+
const claudeSkillsDirForDocs = join4(claudeDir, "skills");
|
|
2539
|
+
await mkdir4(claudeSkillsDirForDocs, { recursive: true });
|
|
2143
2540
|
await fetchHubDocsSkill(claudeSkillsDirForDocs);
|
|
2144
|
-
await syncRemoteSources(config, hubDir,
|
|
2541
|
+
await syncRemoteSources(config, hubDir, join4(claudeDir, "skills"), join4(claudeDir, "steering"));
|
|
2145
2542
|
const hubSteeringDirClaude = resolve2(hubDir, "steering");
|
|
2146
2543
|
try {
|
|
2147
2544
|
const steeringFiles = await readdir2(hubSteeringDirClaude);
|
|
2148
2545
|
const mdFiles = steeringFiles.filter((f) => f.endsWith(".md"));
|
|
2149
2546
|
for (const file of mdFiles) {
|
|
2150
|
-
const raw = await
|
|
2547
|
+
const raw = await readFile4(join4(hubSteeringDirClaude, file), "utf-8");
|
|
2151
2548
|
const content = stripFrontMatter(raw).trim();
|
|
2152
2549
|
if (content) {
|
|
2153
2550
|
claudeMdSections.push(content);
|
|
@@ -2158,7 +2555,7 @@ async function generateClaudeCode(config, hubDir) {
|
|
|
2158
2555
|
}
|
|
2159
2556
|
} catch {
|
|
2160
2557
|
}
|
|
2161
|
-
await
|
|
2558
|
+
await writeFile4(join4(hubDir, "CLAUDE.md"), claudeMdSections.join("\n\n"), "utf-8");
|
|
2162
2559
|
console.log(chalk3.green(" Generated CLAUDE.md"));
|
|
2163
2560
|
if (config.mcps?.length) {
|
|
2164
2561
|
const mcpJson = {};
|
|
@@ -2171,8 +2568,8 @@ async function generateClaudeCode(config, hubDir) {
|
|
|
2171
2568
|
mcpJson[mcp.name] = buildClaudeCodeMcpEntry(mcp);
|
|
2172
2569
|
}
|
|
2173
2570
|
}
|
|
2174
|
-
await
|
|
2175
|
-
|
|
2571
|
+
await writeFile4(
|
|
2572
|
+
join4(hubDir, ".mcp.json"),
|
|
2176
2573
|
JSON.stringify({ mcpServers: mcpJson }, null, 2) + "\n",
|
|
2177
2574
|
"utf-8"
|
|
2178
2575
|
);
|
|
@@ -2214,22 +2611,22 @@ async function generateClaudeCode(config, hubDir) {
|
|
|
2214
2611
|
claudeSettings.hooks = claudeHooks;
|
|
2215
2612
|
}
|
|
2216
2613
|
}
|
|
2217
|
-
await
|
|
2218
|
-
|
|
2614
|
+
await writeFile4(
|
|
2615
|
+
join4(claudeDir, "settings.json"),
|
|
2219
2616
|
JSON.stringify(claudeSettings, null, 2) + "\n",
|
|
2220
2617
|
"utf-8"
|
|
2221
2618
|
);
|
|
2222
2619
|
console.log(chalk3.green(" Generated .claude/settings.json"));
|
|
2223
2620
|
const gitignoreLines = buildGitignoreLines(config);
|
|
2224
|
-
await writeManagedFile(
|
|
2621
|
+
await writeManagedFile(join4(hubDir, ".gitignore"), gitignoreLines);
|
|
2225
2622
|
console.log(chalk3.green(" Generated .gitignore"));
|
|
2226
2623
|
}
|
|
2227
2624
|
async function generateKiro(config, hubDir) {
|
|
2228
|
-
const kiroDir =
|
|
2229
|
-
const steeringDir =
|
|
2230
|
-
const settingsDir =
|
|
2231
|
-
await
|
|
2232
|
-
await
|
|
2625
|
+
const kiroDir = join4(hubDir, ".kiro");
|
|
2626
|
+
const steeringDir = join4(kiroDir, "steering");
|
|
2627
|
+
const settingsDir = join4(kiroDir, "settings");
|
|
2628
|
+
await mkdir4(steeringDir, { recursive: true });
|
|
2629
|
+
await mkdir4(settingsDir, { recursive: true });
|
|
2233
2630
|
let mode = await getKiroMode(hubDir);
|
|
2234
2631
|
if (!mode) {
|
|
2235
2632
|
const { kiroMode } = await inquirer.prompt([
|
|
@@ -2250,25 +2647,32 @@ async function generateKiro(config, hubDir) {
|
|
|
2250
2647
|
console.log(chalk3.dim(` Using saved Kiro mode: ${mode}`));
|
|
2251
2648
|
}
|
|
2252
2649
|
const gitignoreLines = buildGitignoreLines(config);
|
|
2253
|
-
await writeManagedFile(
|
|
2650
|
+
await writeManagedFile(join4(hubDir, ".gitignore"), gitignoreLines);
|
|
2254
2651
|
console.log(chalk3.green(" Generated .gitignore"));
|
|
2255
2652
|
const kiroRule = buildKiroOrchestratorRule(config);
|
|
2256
2653
|
const skillsSection = await buildSkillsSection(hubDir, config);
|
|
2257
|
-
const
|
|
2258
|
-
|
|
2654
|
+
const personaKiro = await loadPersona(hubDir);
|
|
2655
|
+
const personaSectionKiro = personaKiro ? buildPersonaSection(personaKiro) : "";
|
|
2656
|
+
const kiroRuleWithSkills = [kiroRule, skillsSection, personaSectionKiro].filter(Boolean).join("\n");
|
|
2657
|
+
await writeFile4(join4(hubDir, "AGENTS.md"), kiroRuleWithSkills + "\n", "utf-8");
|
|
2259
2658
|
console.log(chalk3.green(" Generated AGENTS.md"));
|
|
2659
|
+
if (personaKiro) {
|
|
2660
|
+
console.log(chalk3.green(` Applied persona: ${personaKiro.name} (${personaKiro.role})`));
|
|
2661
|
+
}
|
|
2662
|
+
await rm(join4(steeringDir, "orchestrator.md")).catch(() => {
|
|
2663
|
+
});
|
|
2260
2664
|
const hubSteeringDir = resolve2(hubDir, "steering");
|
|
2261
2665
|
try {
|
|
2262
2666
|
const steeringFiles = await readdir2(hubSteeringDir);
|
|
2263
2667
|
const mdFiles = steeringFiles.filter((f) => f.endsWith(".md"));
|
|
2264
2668
|
for (const file of mdFiles) {
|
|
2265
|
-
const raw = await
|
|
2669
|
+
const raw = await readFile4(join4(hubSteeringDir, file), "utf-8");
|
|
2266
2670
|
const content = stripFrontMatter(raw);
|
|
2267
|
-
const destPath =
|
|
2671
|
+
const destPath = join4(steeringDir, file);
|
|
2268
2672
|
let inclusion = "always";
|
|
2269
2673
|
let meta;
|
|
2270
|
-
if (
|
|
2271
|
-
const existingContent = await
|
|
2674
|
+
if (existsSync3(destPath)) {
|
|
2675
|
+
const existingContent = await readFile4(destPath, "utf-8");
|
|
2272
2676
|
const existingFm = parseFrontMatter(existingContent);
|
|
2273
2677
|
if (existingFm) {
|
|
2274
2678
|
if (existingFm.inclusion === "auto" || existingFm.inclusion === "manual" || existingFm.inclusion === "fileMatch") {
|
|
@@ -2293,7 +2697,7 @@ async function generateKiro(config, hubDir) {
|
|
|
2293
2697
|
}
|
|
2294
2698
|
}
|
|
2295
2699
|
const kiroSteering = buildKiroSteeringContent(content, inclusion, meta);
|
|
2296
|
-
await
|
|
2700
|
+
await writeFile4(destPath, kiroSteering, "utf-8");
|
|
2297
2701
|
}
|
|
2298
2702
|
if (mdFiles.length > 0) {
|
|
2299
2703
|
console.log(chalk3.green(` Copied ${mdFiles.length} steering files to .kiro/steering/`));
|
|
@@ -2302,31 +2706,36 @@ async function generateKiro(config, hubDir) {
|
|
|
2302
2706
|
}
|
|
2303
2707
|
const agentsDir = resolve2(hubDir, "agents");
|
|
2304
2708
|
try {
|
|
2305
|
-
const kiroAgentsDir =
|
|
2306
|
-
await
|
|
2709
|
+
const kiroAgentsDir = join4(kiroDir, "agents");
|
|
2710
|
+
await mkdir4(kiroAgentsDir, { recursive: true });
|
|
2307
2711
|
const agentFiles = await readdir2(agentsDir);
|
|
2308
2712
|
const mdFiles = agentFiles.filter((f) => f.endsWith(".md"));
|
|
2309
2713
|
for (const file of mdFiles) {
|
|
2310
|
-
const agentContent = await
|
|
2311
|
-
const
|
|
2312
|
-
|
|
2714
|
+
const agentContent = await readFile4(join4(agentsDir, file), "utf-8");
|
|
2715
|
+
const agentName = file.replace(/\.md$/, "");
|
|
2716
|
+
const sandboxSvc = getSandboxService(config);
|
|
2717
|
+
const withSandbox = sandboxSvc ? injectSandboxContext(agentName, agentContent, sandboxSvc.port) : agentContent;
|
|
2718
|
+
const kiroAgent = buildKiroAgentContent(withSandbox);
|
|
2719
|
+
await writeFile4(join4(kiroAgentsDir, file), kiroAgent, "utf-8");
|
|
2313
2720
|
}
|
|
2314
2721
|
console.log(chalk3.green(` Copied ${mdFiles.length} agents to .kiro/agents/`));
|
|
2315
2722
|
} catch {
|
|
2316
2723
|
console.log(chalk3.yellow(" No agents/ directory found, skipping agent copy"));
|
|
2317
2724
|
}
|
|
2318
2725
|
const skillsDir = resolve2(hubDir, "skills");
|
|
2726
|
+
const remoteSkillsKiro = getRemoteSkillNames(config);
|
|
2319
2727
|
try {
|
|
2320
2728
|
const skillFolders = await readdir2(skillsDir);
|
|
2321
|
-
const kiroSkillsDir =
|
|
2322
|
-
await
|
|
2729
|
+
const kiroSkillsDir = join4(kiroDir, "skills");
|
|
2730
|
+
await mkdir4(kiroSkillsDir, { recursive: true });
|
|
2323
2731
|
let count = 0;
|
|
2324
2732
|
for (const folder of skillFolders) {
|
|
2325
|
-
|
|
2733
|
+
if (remoteSkillsKiro.has(folder)) continue;
|
|
2734
|
+
const skillFile = join4(skillsDir, folder, "SKILL.md");
|
|
2326
2735
|
try {
|
|
2327
|
-
await
|
|
2328
|
-
const srcDir =
|
|
2329
|
-
const targetDir =
|
|
2736
|
+
await readFile4(skillFile);
|
|
2737
|
+
const srcDir = join4(skillsDir, folder);
|
|
2738
|
+
const targetDir = join4(kiroSkillsDir, folder);
|
|
2330
2739
|
await cp(srcDir, targetDir, { recursive: true });
|
|
2331
2740
|
count++;
|
|
2332
2741
|
} catch {
|
|
@@ -2337,10 +2746,10 @@ async function generateKiro(config, hubDir) {
|
|
|
2337
2746
|
}
|
|
2338
2747
|
} catch {
|
|
2339
2748
|
}
|
|
2340
|
-
const kiroSkillsDirForDocs =
|
|
2341
|
-
await
|
|
2749
|
+
const kiroSkillsDirForDocs = join4(kiroDir, "skills");
|
|
2750
|
+
await mkdir4(kiroSkillsDirForDocs, { recursive: true });
|
|
2342
2751
|
await fetchHubDocsSkill(kiroSkillsDirForDocs);
|
|
2343
|
-
await syncRemoteSources(config, hubDir,
|
|
2752
|
+
await syncRemoteSources(config, hubDir, join4(kiroDir, "skills"), steeringDir);
|
|
2344
2753
|
if (config.mcps?.length) {
|
|
2345
2754
|
const mcpConfig = {};
|
|
2346
2755
|
const upstreamSet = getUpstreamNames(config.mcps);
|
|
@@ -2353,10 +2762,14 @@ async function generateKiro(config, hubDir) {
|
|
|
2353
2762
|
mcpConfig[mcp.name] = buildKiroMcpEntry(mcp, mode);
|
|
2354
2763
|
}
|
|
2355
2764
|
}
|
|
2356
|
-
const
|
|
2765
|
+
const sandbox = getSandboxService(config);
|
|
2766
|
+
if (sandbox && !mcpConfig["sandbox"]) {
|
|
2767
|
+
mcpConfig["sandbox"] = buildSandboxMcpEntry(sandbox.port);
|
|
2768
|
+
}
|
|
2769
|
+
const mcpJsonPath = join4(settingsDir, "mcp.json");
|
|
2357
2770
|
const disabledState = await readExistingMcpDisabledState(mcpJsonPath);
|
|
2358
2771
|
applyDisabledState(mcpConfig, disabledState);
|
|
2359
|
-
await
|
|
2772
|
+
await writeFile4(
|
|
2360
2773
|
mcpJsonPath,
|
|
2361
2774
|
JSON.stringify({ mcpServers: mcpConfig }, null, 2) + "\n",
|
|
2362
2775
|
"utf-8"
|
|
@@ -2383,13 +2796,13 @@ async function generateKiro(config, hubDir) {
|
|
|
2383
2796
|
await generateVSCodeSettings(config, hubDir);
|
|
2384
2797
|
}
|
|
2385
2798
|
async function generateVSCodeSettings(config, hubDir) {
|
|
2386
|
-
const vscodeDir =
|
|
2387
|
-
await
|
|
2388
|
-
const settingsPath =
|
|
2799
|
+
const vscodeDir = join4(hubDir, ".vscode");
|
|
2800
|
+
await mkdir4(vscodeDir, { recursive: true });
|
|
2801
|
+
const settingsPath = join4(vscodeDir, "settings.json");
|
|
2389
2802
|
let existing = {};
|
|
2390
|
-
if (
|
|
2803
|
+
if (existsSync3(settingsPath)) {
|
|
2391
2804
|
try {
|
|
2392
|
-
const raw = await
|
|
2805
|
+
const raw = await readFile4(settingsPath, "utf-8");
|
|
2393
2806
|
existing = JSON.parse(raw);
|
|
2394
2807
|
} catch {
|
|
2395
2808
|
existing = {};
|
|
@@ -2401,14 +2814,14 @@ async function generateVSCodeSettings(config, hubDir) {
|
|
|
2401
2814
|
"git.detectSubmodulesLimit": Math.max(config.repos.length * 2, 20)
|
|
2402
2815
|
};
|
|
2403
2816
|
const merged = { ...existing, ...managed };
|
|
2404
|
-
await
|
|
2817
|
+
await writeFile4(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
2405
2818
|
console.log(chalk3.green(" Generated .vscode/settings.json (git multi-repo detection)"));
|
|
2406
2819
|
const workspaceFile = `${config.name}.code-workspace`;
|
|
2407
|
-
const workspacePath =
|
|
2820
|
+
const workspacePath = join4(hubDir, workspaceFile);
|
|
2408
2821
|
let existingWorkspace = {};
|
|
2409
|
-
if (
|
|
2822
|
+
if (existsSync3(workspacePath)) {
|
|
2410
2823
|
try {
|
|
2411
|
-
const raw = await
|
|
2824
|
+
const raw = await readFile4(workspacePath, "utf-8");
|
|
2412
2825
|
existingWorkspace = JSON.parse(raw);
|
|
2413
2826
|
} catch {
|
|
2414
2827
|
existingWorkspace = {};
|
|
@@ -2418,7 +2831,7 @@ async function generateVSCodeSettings(config, hubDir) {
|
|
|
2418
2831
|
const existing2 = files.find((f) => f.endsWith(".code-workspace"));
|
|
2419
2832
|
if (existing2) {
|
|
2420
2833
|
try {
|
|
2421
|
-
const raw = await
|
|
2834
|
+
const raw = await readFile4(join4(hubDir, existing2), "utf-8");
|
|
2422
2835
|
existingWorkspace = JSON.parse(raw);
|
|
2423
2836
|
} catch {
|
|
2424
2837
|
existingWorkspace = {};
|
|
@@ -2454,7 +2867,7 @@ async function generateVSCodeSettings(config, hubDir) {
|
|
|
2454
2867
|
folders,
|
|
2455
2868
|
settings: existingWorkspace.settings || {}
|
|
2456
2869
|
};
|
|
2457
|
-
await
|
|
2870
|
+
await writeFile4(workspacePath, JSON.stringify(workspace, null, " ") + "\n", "utf-8");
|
|
2458
2871
|
console.log(chalk3.green(` Generated ${workspaceFile}`));
|
|
2459
2872
|
}
|
|
2460
2873
|
function extractEnvVarsByMcp(mcps) {
|
|
@@ -2501,7 +2914,7 @@ async function generateEnvExample(config, hubDir) {
|
|
|
2501
2914
|
totalVars++;
|
|
2502
2915
|
}
|
|
2503
2916
|
if (totalVars === 0) return;
|
|
2504
|
-
await
|
|
2917
|
+
await writeFile4(join4(hubDir, ".env.example"), lines.join("\n") + "\n", "utf-8");
|
|
2505
2918
|
console.log(chalk3.green(` Generated .env.example (${totalVars} vars)`));
|
|
2506
2919
|
}
|
|
2507
2920
|
function buildGitignoreLines(config) {
|
|
@@ -2591,7 +3004,7 @@ async function resolveEditor(opts) {
|
|
|
2591
3004
|
]);
|
|
2592
3005
|
return editor;
|
|
2593
3006
|
}
|
|
2594
|
-
var generateCommand = new
|
|
3007
|
+
var generateCommand = new Command2("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) => {
|
|
2595
3008
|
const hubDir = process.cwd();
|
|
2596
3009
|
if (opts.check) {
|
|
2597
3010
|
const result = await checkOutdated(hubDir);
|
|
@@ -2656,6 +3069,10 @@ Generating ${generator.name} configuration
|
|
|
2656
3069
|
});
|
|
2657
3070
|
|
|
2658
3071
|
export {
|
|
3072
|
+
colors,
|
|
3073
|
+
symbols,
|
|
3074
|
+
horizontalLine,
|
|
3075
|
+
personaCommand,
|
|
2659
3076
|
generators,
|
|
2660
3077
|
generateCommand,
|
|
2661
3078
|
checkAndAutoRegenerate
|