@arvoretech/hub 0.6.0 → 0.6.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.
- package/dist/chunk-BLVKDXNY.js +1175 -0
- package/dist/generate-G3ZOX55F.js +8 -0
- package/dist/index.js +655 -1649
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
checkAndAutoRegenerate,
|
|
4
|
+
generateCommand,
|
|
5
|
+
loadHubConfig
|
|
6
|
+
} from "./chunk-BLVKDXNY.js";
|
|
2
7
|
|
|
3
8
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
9
|
+
import { Command as Command18 } from "commander";
|
|
5
10
|
|
|
6
11
|
// src/commands/init.ts
|
|
7
12
|
import { Command } from "commander";
|
|
@@ -164,20 +169,10 @@ Added ${repoName} to hub`));
|
|
|
164
169
|
import { Command as Command3 } from "commander";
|
|
165
170
|
import { existsSync as existsSync2 } from "fs";
|
|
166
171
|
import { writeFile as writeFile3 } from "fs/promises";
|
|
167
|
-
import { join as
|
|
172
|
+
import { join as join3 } from "path";
|
|
168
173
|
import { execSync } from "child_process";
|
|
169
174
|
import chalk3 from "chalk";
|
|
170
175
|
|
|
171
|
-
// src/core/hub-config.ts
|
|
172
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
173
|
-
import { join as join3 } from "path";
|
|
174
|
-
import { parse as parse2 } from "yaml";
|
|
175
|
-
async function loadHubConfig(dir) {
|
|
176
|
-
const configPath = join3(dir, "hub.yaml");
|
|
177
|
-
const content = await readFile2(configPath, "utf-8");
|
|
178
|
-
return parse2(content);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
176
|
// src/core/docker-compose.ts
|
|
182
177
|
import { stringify as stringify3 } from "yaml";
|
|
183
178
|
function generateDockerCompose(services) {
|
|
@@ -276,7 +271,7 @@ var setupCommand = new Command3("setup").description("Clone repos, start service
|
|
|
276
271
|
\u2501\u2501\u2501 Step ${step}/${totalSteps}: Cloning repositories \u2501\u2501\u2501
|
|
277
272
|
`));
|
|
278
273
|
for (const repo of config.repos) {
|
|
279
|
-
const fullPath =
|
|
274
|
+
const fullPath = join3(hubDir, repo.path);
|
|
280
275
|
console.log(chalk3.yellow(`\u25B8 ${repo.name}`));
|
|
281
276
|
if (existsSync2(fullPath)) {
|
|
282
277
|
console.log(chalk3.green(" Already exists, skipping"));
|
|
@@ -295,7 +290,7 @@ var setupCommand = new Command3("setup").description("Clone repos, start service
|
|
|
295
290
|
console.log(chalk3.blue(`
|
|
296
291
|
\u2501\u2501\u2501 Step ${step}/${totalSteps}: Starting services \u2501\u2501\u2501
|
|
297
292
|
`));
|
|
298
|
-
const composePath =
|
|
293
|
+
const composePath = join3(hubDir, "docker-compose.yml");
|
|
299
294
|
const composeContent = generateDockerCompose(config.services);
|
|
300
295
|
await writeFile3(composePath, composeContent, "utf-8");
|
|
301
296
|
console.log(chalk3.green(" Generated docker-compose.yml"));
|
|
@@ -323,7 +318,7 @@ var setupCommand = new Command3("setup").description("Clone repos, start service
|
|
|
323
318
|
} else {
|
|
324
319
|
if (config.tools && Object.keys(config.tools).length > 0) {
|
|
325
320
|
const content = generateMiseToml(config.tools, config.mise_settings);
|
|
326
|
-
await writeFile3(
|
|
321
|
+
await writeFile3(join3(hubDir, ".mise.toml"), content, "utf-8");
|
|
327
322
|
console.log(chalk3.green(` Generated .mise.toml (${Object.keys(config.tools).length} tools)`));
|
|
328
323
|
try {
|
|
329
324
|
run("mise trust && mise install", hubDir);
|
|
@@ -334,11 +329,11 @@ var setupCommand = new Command3("setup").description("Clone repos, start service
|
|
|
334
329
|
}
|
|
335
330
|
for (const repo of config.repos) {
|
|
336
331
|
if (!repo.tools || Object.keys(repo.tools).length === 0) continue;
|
|
337
|
-
const repoDir =
|
|
332
|
+
const repoDir = join3(hubDir, repo.path);
|
|
338
333
|
if (!existsSync2(repoDir)) continue;
|
|
339
334
|
const merged = { ...config.tools || {}, ...repo.tools };
|
|
340
335
|
const content = generateMiseToml(merged);
|
|
341
|
-
await writeFile3(
|
|
336
|
+
await writeFile3(join3(repoDir, ".mise.toml"), content, "utf-8");
|
|
342
337
|
console.log(chalk3.yellow(`\u25B8 ${repo.name}`));
|
|
343
338
|
try {
|
|
344
339
|
run("mise trust 2>/dev/null; mise install", repoDir);
|
|
@@ -355,10 +350,10 @@ var setupCommand = new Command3("setup").description("Clone repos, start service
|
|
|
355
350
|
`));
|
|
356
351
|
for (const repo of config.repos) {
|
|
357
352
|
if (!repo.env_file) continue;
|
|
358
|
-
const envPath =
|
|
353
|
+
const envPath = join3(hubDir, repo.path, repo.env_file);
|
|
359
354
|
const overrides = config.env?.overrides?.["local"]?.[repo.name];
|
|
360
355
|
if (overrides) {
|
|
361
|
-
const dir =
|
|
356
|
+
const dir = join3(hubDir, repo.path);
|
|
362
357
|
if (existsSync2(dir)) {
|
|
363
358
|
const lines = Object.entries(overrides).map(([k, v]) => `${k}=${v}`);
|
|
364
359
|
await writeFile3(envPath, lines.join("\n") + "\n", "utf-8");
|
|
@@ -374,7 +369,7 @@ var setupCommand = new Command3("setup").description("Clone repos, start service
|
|
|
374
369
|
\u2501\u2501\u2501 Step ${step}/${totalSteps}: Installing dependencies \u2501\u2501\u2501
|
|
375
370
|
`));
|
|
376
371
|
for (const repo of config.repos) {
|
|
377
|
-
const fullPath =
|
|
372
|
+
const fullPath = join3(hubDir, repo.path);
|
|
378
373
|
if (!existsSync2(fullPath)) continue;
|
|
379
374
|
const installCmd = repo.commands?.install;
|
|
380
375
|
if (!installCmd) {
|
|
@@ -395,1023 +390,20 @@ var setupCommand = new Command3("setup").description("Clone repos, start service
|
|
|
395
390
|
`));
|
|
396
391
|
}
|
|
397
392
|
console.log(chalk3.blue("\n\u2501\u2501\u2501 Setup complete \u2501\u2501\u2501\n"));
|
|
393
|
+
await checkAndAutoRegenerate(hubDir);
|
|
398
394
|
console.log("Next steps:");
|
|
399
|
-
console.log(` npx @arvoretech/hub generate
|
|
400
|
-
console.log(` Open the project in
|
|
395
|
+
console.log(` npx @arvoretech/hub generate`);
|
|
396
|
+
console.log(` Open the project in your editor and start building`);
|
|
401
397
|
console.log();
|
|
402
398
|
});
|
|
403
399
|
|
|
404
|
-
// src/commands/
|
|
400
|
+
// src/commands/env.ts
|
|
405
401
|
import { Command as Command4 } from "commander";
|
|
406
402
|
import { existsSync as existsSync3 } from "fs";
|
|
407
|
-
import {
|
|
408
|
-
import { join as
|
|
409
|
-
import chalk4 from "chalk";
|
|
410
|
-
var HUB_MARKER_START = "# >>> hub-managed (do not edit this section)";
|
|
411
|
-
var HUB_MARKER_END = "# <<< hub-managed";
|
|
412
|
-
var HOOK_EVENT_MAP = {
|
|
413
|
-
session_start: { cursor: "sessionStart", claude: "SessionStart", kiro: void 0 },
|
|
414
|
-
session_end: { cursor: "sessionEnd", claude: "SessionEnd", kiro: void 0 },
|
|
415
|
-
pre_tool_use: { cursor: "preToolUse", claude: "PreToolUse", kiro: "pre_tool_use" },
|
|
416
|
-
post_tool_use: { cursor: "postToolUse", claude: "PostToolUse", kiro: "post_tool_use" },
|
|
417
|
-
post_tool_use_failure: { cursor: void 0, claude: "PostToolUseFailure", kiro: void 0 },
|
|
418
|
-
stop: { cursor: "stop", claude: "Stop", kiro: "agent_stop" },
|
|
419
|
-
subagent_start: { cursor: "subagentStart", claude: "SubagentStart", kiro: void 0 },
|
|
420
|
-
subagent_stop: { cursor: "subagentStop", claude: "SubagentStop", kiro: void 0 },
|
|
421
|
-
pre_compact: { cursor: "preCompact", claude: "PreCompact", kiro: void 0 },
|
|
422
|
-
before_submit_prompt: { cursor: "beforeSubmitPrompt", claude: "UserPromptSubmit", kiro: "prompt_submit" },
|
|
423
|
-
before_shell_execution: { cursor: "beforeShellExecution", claude: void 0, kiro: void 0 },
|
|
424
|
-
after_shell_execution: { cursor: "afterShellExecution", claude: void 0, kiro: void 0 },
|
|
425
|
-
before_mcp_execution: { cursor: "beforeMCPExecution", claude: void 0, kiro: void 0 },
|
|
426
|
-
after_mcp_execution: { cursor: "afterMCPExecution", claude: void 0, kiro: void 0 },
|
|
427
|
-
after_file_edit: { cursor: "afterFileEdit", claude: void 0, kiro: "file_save" },
|
|
428
|
-
before_read_file: { cursor: "beforeReadFile", claude: void 0, kiro: void 0 },
|
|
429
|
-
before_tab_file_read: { cursor: "beforeTabFileRead", claude: void 0, kiro: void 0 },
|
|
430
|
-
after_tab_file_edit: { cursor: "afterTabFileEdit", claude: void 0, kiro: void 0 },
|
|
431
|
-
after_agent_response: { cursor: "afterAgentResponse", claude: void 0, kiro: void 0 },
|
|
432
|
-
after_agent_thought: { cursor: "afterAgentThought", claude: void 0, kiro: void 0 },
|
|
433
|
-
notification: { cursor: void 0, claude: "Notification", kiro: void 0 },
|
|
434
|
-
permission_request: { cursor: void 0, claude: "PermissionRequest", kiro: void 0 },
|
|
435
|
-
task_completed: { cursor: void 0, claude: "TaskCompleted", kiro: void 0 },
|
|
436
|
-
teammate_idle: { cursor: void 0, claude: "TeammateIdle", kiro: void 0 }
|
|
437
|
-
};
|
|
438
|
-
function buildCursorHooks(hooks) {
|
|
439
|
-
const cursorHooks = {};
|
|
440
|
-
for (const [event, entries] of Object.entries(hooks)) {
|
|
441
|
-
const mapped = HOOK_EVENT_MAP[event]?.cursor;
|
|
442
|
-
if (!mapped) continue;
|
|
443
|
-
const cursorEntries = entries.map((entry) => {
|
|
444
|
-
const obj = { type: entry.type };
|
|
445
|
-
if (entry.type === "command" && entry.command) obj.command = entry.command;
|
|
446
|
-
if (entry.type === "prompt" && entry.prompt) obj.prompt = entry.prompt;
|
|
447
|
-
if (entry.matcher) obj.matcher = entry.matcher;
|
|
448
|
-
if (entry.timeout_ms) obj.timeout = entry.timeout_ms;
|
|
449
|
-
return obj;
|
|
450
|
-
});
|
|
451
|
-
if (cursorEntries.length > 0) {
|
|
452
|
-
cursorHooks[mapped] = cursorEntries;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
if (Object.keys(cursorHooks).length === 0) return null;
|
|
456
|
-
return { version: 1, hooks: cursorHooks };
|
|
457
|
-
}
|
|
458
|
-
function buildClaudeHooks(hooks) {
|
|
459
|
-
const claudeHooks = {};
|
|
460
|
-
for (const [event, entries] of Object.entries(hooks)) {
|
|
461
|
-
const mapped = HOOK_EVENT_MAP[event]?.claude;
|
|
462
|
-
if (!mapped) continue;
|
|
463
|
-
const claudeEntries = entries.map((entry) => {
|
|
464
|
-
const obj = { type: entry.type };
|
|
465
|
-
if (entry.type === "command" && entry.command) obj.command = entry.command;
|
|
466
|
-
if (entry.type === "prompt" && entry.prompt) obj.prompt = entry.prompt;
|
|
467
|
-
if (entry.matcher) obj.matcher = entry.matcher;
|
|
468
|
-
if (entry.timeout_ms) obj.timeout = entry.timeout_ms;
|
|
469
|
-
return obj;
|
|
470
|
-
});
|
|
471
|
-
if (claudeEntries.length > 0) {
|
|
472
|
-
claudeHooks[mapped] = claudeEntries;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
if (Object.keys(claudeHooks).length === 0) return null;
|
|
476
|
-
return claudeHooks;
|
|
477
|
-
}
|
|
478
|
-
async function generateCursorCommands(config, hubDir, cursorDir) {
|
|
479
|
-
const commandsDir = join5(cursorDir, "commands");
|
|
480
|
-
let count = 0;
|
|
481
|
-
if (config.commands_dir) {
|
|
482
|
-
const srcDir = resolve(hubDir, config.commands_dir);
|
|
483
|
-
try {
|
|
484
|
-
const files = await readdir(srcDir);
|
|
485
|
-
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
486
|
-
if (mdFiles.length > 0) {
|
|
487
|
-
await mkdir2(commandsDir, { recursive: true });
|
|
488
|
-
for (const file of mdFiles) {
|
|
489
|
-
await copyFile(join5(srcDir, file), join5(commandsDir, file));
|
|
490
|
-
count++;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
} catch {
|
|
494
|
-
console.log(chalk4.yellow(` Commands directory ${config.commands_dir} not found, skipping`));
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
if (config.commands) {
|
|
498
|
-
await mkdir2(commandsDir, { recursive: true });
|
|
499
|
-
for (const [name, filePath] of Object.entries(config.commands)) {
|
|
500
|
-
const src = resolve(hubDir, filePath);
|
|
501
|
-
const dest = join5(commandsDir, name.endsWith(".md") ? name : `${name}.md`);
|
|
502
|
-
try {
|
|
503
|
-
await copyFile(src, dest);
|
|
504
|
-
count++;
|
|
505
|
-
} catch {
|
|
506
|
-
console.log(chalk4.yellow(` Command file ${filePath} not found, skipping`));
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
if (count > 0) {
|
|
511
|
-
console.log(chalk4.green(` Copied ${count} commands to .cursor/commands/`));
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
async function writeManagedFile(filePath, managedLines) {
|
|
515
|
-
const managedBlock = [HUB_MARKER_START, ...managedLines, HUB_MARKER_END].join("\n");
|
|
516
|
-
if (existsSync3(filePath)) {
|
|
517
|
-
const existing = await readFile3(filePath, "utf-8");
|
|
518
|
-
const startIdx = existing.indexOf(HUB_MARKER_START);
|
|
519
|
-
const endIdx = existing.indexOf(HUB_MARKER_END);
|
|
520
|
-
if (startIdx !== -1 && endIdx !== -1) {
|
|
521
|
-
const before = existing.substring(0, startIdx);
|
|
522
|
-
const after = existing.substring(endIdx + HUB_MARKER_END.length);
|
|
523
|
-
await writeFile4(filePath, before + managedBlock + after, "utf-8");
|
|
524
|
-
return;
|
|
525
|
-
}
|
|
526
|
-
await writeFile4(filePath, managedBlock + "\n\n" + existing, "utf-8");
|
|
527
|
-
return;
|
|
528
|
-
}
|
|
529
|
-
await writeFile4(filePath, managedBlock + "\n", "utf-8");
|
|
530
|
-
}
|
|
531
|
-
async function generateCursor(config, hubDir) {
|
|
532
|
-
const cursorDir = join5(hubDir, ".cursor");
|
|
533
|
-
await mkdir2(join5(cursorDir, "rules"), { recursive: true });
|
|
534
|
-
await mkdir2(join5(cursorDir, "agents"), { recursive: true });
|
|
535
|
-
const gitignoreLines = buildGitignoreLines(config);
|
|
536
|
-
await writeManagedFile(join5(hubDir, ".gitignore"), gitignoreLines);
|
|
537
|
-
console.log(chalk4.green(" Generated .gitignore"));
|
|
538
|
-
const cursorignoreLines = [
|
|
539
|
-
"# Re-include repositories for AI context"
|
|
540
|
-
];
|
|
541
|
-
for (const repo of config.repos) {
|
|
542
|
-
const repoDir = repo.path.replace("./", "");
|
|
543
|
-
cursorignoreLines.push(`!${repoDir}/`);
|
|
544
|
-
}
|
|
545
|
-
cursorignoreLines.push("", "# Re-include tasks for agent collaboration", "!tasks/");
|
|
546
|
-
await writeManagedFile(join5(hubDir, ".cursorignore"), cursorignoreLines);
|
|
547
|
-
console.log(chalk4.green(" Generated .cursorignore"));
|
|
548
|
-
if (config.mcps?.length) {
|
|
549
|
-
const mcpConfig = {};
|
|
550
|
-
for (const mcp of config.mcps) {
|
|
551
|
-
mcpConfig[mcp.name] = buildCursorMcpEntry(mcp);
|
|
552
|
-
}
|
|
553
|
-
await writeFile4(
|
|
554
|
-
join5(cursorDir, "mcp.json"),
|
|
555
|
-
JSON.stringify({ mcpServers: mcpConfig }, null, 2) + "\n",
|
|
556
|
-
"utf-8"
|
|
557
|
-
);
|
|
558
|
-
console.log(chalk4.green(" Generated .cursor/mcp.json"));
|
|
559
|
-
}
|
|
560
|
-
const orchestratorRule = buildOrchestratorRule(config);
|
|
561
|
-
await writeFile4(join5(cursorDir, "rules", "orchestrator.mdc"), orchestratorRule, "utf-8");
|
|
562
|
-
console.log(chalk4.green(" Generated .cursor/rules/orchestrator.mdc"));
|
|
563
|
-
const agentsDir = resolve(hubDir, "agents");
|
|
564
|
-
try {
|
|
565
|
-
const agentFiles = await readdir(agentsDir);
|
|
566
|
-
const mdFiles = agentFiles.filter((f) => f.endsWith(".md"));
|
|
567
|
-
for (const file of mdFiles) {
|
|
568
|
-
await copyFile(join5(agentsDir, file), join5(cursorDir, "agents", file));
|
|
569
|
-
}
|
|
570
|
-
console.log(chalk4.green(` Copied ${mdFiles.length} agent definitions`));
|
|
571
|
-
} catch {
|
|
572
|
-
console.log(chalk4.yellow(" No agents/ directory found, skipping agent copy"));
|
|
573
|
-
}
|
|
574
|
-
const skillsDir = resolve(hubDir, "skills");
|
|
575
|
-
try {
|
|
576
|
-
const skillFolders = await readdir(skillsDir);
|
|
577
|
-
const cursorSkillsDir = join5(cursorDir, "skills");
|
|
578
|
-
await mkdir2(cursorSkillsDir, { recursive: true });
|
|
579
|
-
let count = 0;
|
|
580
|
-
for (const folder of skillFolders) {
|
|
581
|
-
const skillFile = join5(skillsDir, folder, "SKILL.md");
|
|
582
|
-
try {
|
|
583
|
-
await readFile3(skillFile);
|
|
584
|
-
const srcDir = join5(skillsDir, folder);
|
|
585
|
-
const targetDir = join5(cursorSkillsDir, folder);
|
|
586
|
-
await cp(srcDir, targetDir, { recursive: true });
|
|
587
|
-
count++;
|
|
588
|
-
} catch {
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
if (count > 0) {
|
|
592
|
-
console.log(chalk4.green(` Copied ${count} skills`));
|
|
593
|
-
}
|
|
594
|
-
} catch {
|
|
595
|
-
}
|
|
596
|
-
if (config.hooks) {
|
|
597
|
-
const cursorHooks = buildCursorHooks(config.hooks);
|
|
598
|
-
if (cursorHooks) {
|
|
599
|
-
await writeFile4(
|
|
600
|
-
join5(cursorDir, "hooks.json"),
|
|
601
|
-
JSON.stringify(cursorHooks, null, 2) + "\n",
|
|
602
|
-
"utf-8"
|
|
603
|
-
);
|
|
604
|
-
console.log(chalk4.green(" Generated .cursor/hooks.json"));
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
await generateCursorCommands(config, hubDir, cursorDir);
|
|
608
|
-
await generateVSCodeSettings(config, hubDir);
|
|
609
|
-
}
|
|
610
|
-
function buildCursorMcpEntry(mcp) {
|
|
611
|
-
if (mcp.url) {
|
|
612
|
-
return { url: mcp.url, ...mcp.env && { env: mcp.env } };
|
|
613
|
-
}
|
|
614
|
-
if (mcp.image) {
|
|
615
|
-
const args = ["run", "-i", "--rm"];
|
|
616
|
-
if (mcp.env) {
|
|
617
|
-
for (const [key, value] of Object.entries(mcp.env)) {
|
|
618
|
-
args.push("-e", `${key}=${value}`);
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
args.push(mcp.image);
|
|
622
|
-
return { command: "docker", args };
|
|
623
|
-
}
|
|
624
|
-
return {
|
|
625
|
-
command: "npx",
|
|
626
|
-
args: ["-y", mcp.package],
|
|
627
|
-
...mcp.env && { env: mcp.env }
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
function buildClaudeCodeMcpEntry(mcp) {
|
|
631
|
-
if (mcp.url) {
|
|
632
|
-
return { type: "http", url: mcp.url };
|
|
633
|
-
}
|
|
634
|
-
if (mcp.image) {
|
|
635
|
-
const args = ["run", "-i", "--rm"];
|
|
636
|
-
if (mcp.env) {
|
|
637
|
-
for (const [key, value] of Object.entries(mcp.env)) {
|
|
638
|
-
args.push("-e", `${key}=${value}`);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
args.push(mcp.image);
|
|
642
|
-
return { command: "docker", args };
|
|
643
|
-
}
|
|
644
|
-
return {
|
|
645
|
-
command: "npx",
|
|
646
|
-
args: ["-y", mcp.package],
|
|
647
|
-
...mcp.env && { env: mcp.env }
|
|
648
|
-
};
|
|
649
|
-
}
|
|
650
|
-
function buildKiroMcpEntry(mcp) {
|
|
651
|
-
if (mcp.url) {
|
|
652
|
-
return { url: mcp.url, ...mcp.env && { env: mcp.env } };
|
|
653
|
-
}
|
|
654
|
-
if (mcp.image) {
|
|
655
|
-
const args = ["run", "-i", "--rm"];
|
|
656
|
-
if (mcp.env) {
|
|
657
|
-
for (const [key, value] of Object.entries(mcp.env)) {
|
|
658
|
-
args.push("-e", `${key}=${value}`);
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
args.push(mcp.image);
|
|
662
|
-
return { command: "docker", args };
|
|
663
|
-
}
|
|
664
|
-
return {
|
|
665
|
-
command: "npx",
|
|
666
|
-
args: ["-y", mcp.package],
|
|
667
|
-
...mcp.env && { env: mcp.env }
|
|
668
|
-
};
|
|
669
|
-
}
|
|
670
|
-
function buildKiroSteeringContent(content, inclusion = "always", meta) {
|
|
671
|
-
const frontMatter = ["---", `inclusion: ${inclusion}`];
|
|
672
|
-
if (meta?.name) frontMatter.push(`name: ${meta.name}`);
|
|
673
|
-
if (meta?.description) frontMatter.push(`description: ${meta.description}`);
|
|
674
|
-
frontMatter.push("---");
|
|
675
|
-
return `${frontMatter.join("\n")}
|
|
676
|
-
|
|
677
|
-
${content}`;
|
|
678
|
-
}
|
|
679
|
-
function buildKiroOrchestratorRule(config) {
|
|
680
|
-
const taskFolder = config.workflow?.task_folder || "./tasks/<TASK_ID>/";
|
|
681
|
-
const steps = config.workflow?.pipeline || [];
|
|
682
|
-
const prompt = config.workflow?.prompt;
|
|
683
|
-
const enforce = config.workflow?.enforce_workflow ?? false;
|
|
684
|
-
const sections = [];
|
|
685
|
-
sections.push(`# Orchestrator
|
|
686
|
-
|
|
687
|
-
## Your Main Responsibility
|
|
688
|
-
|
|
689
|
-
You are the development orchestrator. Your job is to ensure that any feature or task requested by the user is completed end-to-end by following a structured pipeline. You work as a single agent but follow specialized instructions from steering files for each phase of development.
|
|
690
|
-
|
|
691
|
-
> **Note:** This workspace uses steering files in \`.kiro/steering/\` to provide role-specific instructions for each pipeline step. When a step says "follow the instructions from steering file X", read that file and apply its guidelines to the current task.`);
|
|
692
|
-
if (enforce) {
|
|
693
|
-
sections.push(`
|
|
694
|
-
## STRICT WORKFLOW ENFORCEMENT
|
|
695
|
-
|
|
696
|
-
**YOU MUST FOLLOW THE PIPELINE DEFINED BELOW. NO EXCEPTIONS.**
|
|
697
|
-
|
|
698
|
-
- NEVER skip a pipeline step, even if the task seems simple or obvious.
|
|
699
|
-
- ALWAYS execute steps in the exact order defined. Do not reorder, merge, or parallelize steps unless the pipeline explicitly allows it.
|
|
700
|
-
- ALWAYS follow the designated steering file for each step. Do not improvise if a steering file is assigned.
|
|
701
|
-
- ALWAYS wait for a step to complete and validate its output before moving to the next step.
|
|
702
|
-
- If a step produces a document, READ the document and confirm it is complete before proceeding.
|
|
703
|
-
- If a step has unanswered questions or validation issues, RESOLVE them before advancing.
|
|
704
|
-
- NEVER jump directly to coding without completing refinement first.
|
|
705
|
-
- NEVER skip review or QA steps, even for small changes.
|
|
706
|
-
- If the user asks you to skip a step, explain why the pipeline exists and ask for explicit confirmation before proceeding.`);
|
|
707
|
-
}
|
|
708
|
-
if (prompt?.prepend) {
|
|
709
|
-
sections.push(`
|
|
710
|
-
${prompt.prepend.trim()}`);
|
|
711
|
-
}
|
|
712
|
-
if (config.integrations?.linear) {
|
|
713
|
-
const linear = config.integrations.linear;
|
|
714
|
-
sections.push(`
|
|
715
|
-
## Task Management
|
|
716
|
-
|
|
717
|
-
If the user doesn't have a task in their project management tool, create one using the Linear MCP.${linear.team ? ` Add it to the **${linear.team}** team.` : ""} Provide the link to the user so they can review and modify as needed.`);
|
|
718
|
-
}
|
|
719
|
-
sections.push(`
|
|
720
|
-
## Repositories
|
|
721
|
-
`);
|
|
722
|
-
for (const repo of config.repos) {
|
|
723
|
-
const parts = [`- **${repo.path}**`];
|
|
724
|
-
if (repo.description) parts.push(`\u2014 ${repo.description}`);
|
|
725
|
-
else if (repo.tech) parts.push(`\u2014 ${repo.tech}`);
|
|
726
|
-
if (repo.skills?.length) parts.push(`(skills: ${repo.skills.join(", ")})`);
|
|
727
|
-
sections.push(parts.join(" "));
|
|
728
|
-
if (repo.commands) {
|
|
729
|
-
const cmds = Object.entries(repo.commands).filter(([, v]) => v).map(([k, v]) => `\`${k}\`: \`${v}\``).join(", ");
|
|
730
|
-
if (cmds) sections.push(` Commands: ${cmds}`);
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
if (prompt?.sections?.after_repositories) {
|
|
734
|
-
sections.push(`
|
|
735
|
-
${prompt.sections.after_repositories.trim()}`);
|
|
736
|
-
}
|
|
737
|
-
const docStructure = buildDocumentStructure(steps, taskFolder);
|
|
738
|
-
sections.push(docStructure);
|
|
739
|
-
const pipelineSection = buildKiroPipelineSection(steps);
|
|
740
|
-
sections.push(pipelineSection);
|
|
741
|
-
if (prompt?.sections?.after_pipeline) {
|
|
742
|
-
sections.push(`
|
|
743
|
-
${prompt.sections.after_pipeline.trim()}`);
|
|
744
|
-
}
|
|
745
|
-
if (config.integrations?.slack || config.integrations?.github) {
|
|
746
|
-
sections.push(buildDeliverySection(config));
|
|
747
|
-
}
|
|
748
|
-
if (prompt?.sections?.after_delivery) {
|
|
749
|
-
sections.push(`
|
|
750
|
-
${prompt.sections.after_delivery.trim()}`);
|
|
751
|
-
}
|
|
752
|
-
if (config.memory) {
|
|
753
|
-
sections.push(`
|
|
754
|
-
## Team Memory
|
|
755
|
-
|
|
756
|
-
This workspace has a team memory knowledge base available via the \`team-memory\` MCP.
|
|
757
|
-
|
|
758
|
-
**Before starting any task**, use \`search_memories\` to find relevant context \u2014 past decisions, conventions, known issues, and domain knowledge. This avoids repeating mistakes and ensures consistency with previous choices.
|
|
759
|
-
|
|
760
|
-
**After completing a task**, if you discovered something valuable (a decision, a gotcha, a convention, domain insight), use \`add_memory\` to capture it for the team.
|
|
761
|
-
|
|
762
|
-
Available tools: \`search_memories\`, \`get_memory\`, \`add_memory\`, \`list_memories\`, \`archive_memory\`, \`remove_memory\`.`);
|
|
763
|
-
}
|
|
764
|
-
sections.push(`
|
|
765
|
-
## Troubleshooting and Debugging
|
|
766
|
-
|
|
767
|
-
For bug reports or unexpected behavior, follow the debugging process from the \`agent-debugger.md\` steering file (if available), or:
|
|
768
|
-
1. Collect context (symptoms, environment, timeline)
|
|
769
|
-
2. Analyze logs and stack traces
|
|
770
|
-
3. Form and test hypotheses systematically
|
|
771
|
-
4. Identify the root cause
|
|
772
|
-
5. Propose and implement the fix`);
|
|
773
|
-
if (prompt?.sections) {
|
|
774
|
-
const reservedKeys = /* @__PURE__ */ new Set(["after_repositories", "after_pipeline", "after_delivery"]);
|
|
775
|
-
for (const [name, content] of Object.entries(prompt.sections)) {
|
|
776
|
-
if (reservedKeys.has(name)) continue;
|
|
777
|
-
const title = name.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
778
|
-
sections.push(`
|
|
779
|
-
## ${title}
|
|
780
|
-
|
|
781
|
-
${content.trim()}`);
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
if (prompt?.append) {
|
|
785
|
-
sections.push(`
|
|
786
|
-
${prompt.append.trim()}`);
|
|
787
|
-
}
|
|
788
|
-
return sections.join("\n");
|
|
789
|
-
}
|
|
790
|
-
function buildKiroPipelineSection(steps) {
|
|
791
|
-
if (steps.length === 0) {
|
|
792
|
-
return `
|
|
793
|
-
## Development Pipeline
|
|
794
|
-
|
|
795
|
-
Since Kiro does not support sub-agents, follow each step sequentially, applying the guidelines from the corresponding steering file:
|
|
796
|
-
|
|
797
|
-
1. **Refinement** \u2014 Read and follow \`agent-refinement.md\` steering file to collect requirements. Write output to the task document.
|
|
798
|
-
2. **Coding** \u2014 Follow the coding steering files (\`agent-coding-backend.md\`, \`agent-coding-frontend.md\`) to implement the feature.
|
|
799
|
-
3. **Review** \u2014 Follow \`agent-code-reviewer.md\` to review the implementation.
|
|
800
|
-
4. **QA** \u2014 Follow \`agent-qa-backend.md\` and/or \`agent-qa-frontend.md\` to test.
|
|
801
|
-
5. **Delivery** \u2014 Create PRs and notify the team.`;
|
|
802
|
-
}
|
|
803
|
-
const parts = [`
|
|
804
|
-
## Development Pipeline
|
|
805
|
-
|
|
806
|
-
Follow each step sequentially, applying the role-specific instructions from the corresponding steering file at each phase.
|
|
807
|
-
`];
|
|
808
|
-
for (const step of steps) {
|
|
809
|
-
if (step.actions) {
|
|
810
|
-
parts.push(`### Delivery`);
|
|
811
|
-
parts.push(`After all validations pass, execute these actions:`);
|
|
812
|
-
for (const action of step.actions) {
|
|
813
|
-
parts.push(`- ${formatAction(action)}`);
|
|
814
|
-
}
|
|
815
|
-
continue;
|
|
816
|
-
}
|
|
817
|
-
const stepTitle = step.step.charAt(0).toUpperCase() + step.step.slice(1);
|
|
818
|
-
parts.push(`### ${stepTitle}`);
|
|
819
|
-
if (step.mode === "plan") {
|
|
820
|
-
parts.push(`**This step is a planning phase.** Do NOT make any code changes. Focus on reading, analyzing, and collaborating with the user to define requirements before proceeding.`);
|
|
821
|
-
parts.push(``);
|
|
822
|
-
}
|
|
823
|
-
if (step.agent) {
|
|
824
|
-
parts.push(`Follow the instructions from the \`agent-${step.agent}.md\` steering file.${step.output ? ` Write output to \`${step.output}\`.` : ""}`);
|
|
825
|
-
if (step.step === "refinement") {
|
|
826
|
-
parts.push(`
|
|
827
|
-
After completing the refinement, validate with the user:
|
|
828
|
-
- If there are unanswered questions, ask the user one at a time
|
|
829
|
-
- If the user requests adjustments, revisit the refinement
|
|
830
|
-
- Do not proceed until the document is complete and approved by the user`);
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
if (Array.isArray(step.agents)) {
|
|
834
|
-
const agentList = step.agents.map((a) => {
|
|
835
|
-
if (typeof a === "string") return { agent: a };
|
|
836
|
-
return a;
|
|
837
|
-
});
|
|
838
|
-
parts.push(`Follow the instructions from these steering files sequentially:`);
|
|
839
|
-
for (const a of agentList) {
|
|
840
|
-
let line = `- \`agent-${a.agent}.md\``;
|
|
841
|
-
if (a.output) line += ` \u2192 write to \`${a.output}\``;
|
|
842
|
-
if (a.when) line += ` (when: ${a.when})`;
|
|
843
|
-
parts.push(line);
|
|
844
|
-
}
|
|
845
|
-
if (step.step === "coding" || step.step === "code" || step.step === "implementation") {
|
|
846
|
-
parts.push(`
|
|
847
|
-
If you encounter doubts during coding, write questions in the task document and validate with the user before proceeding.`);
|
|
848
|
-
}
|
|
849
|
-
if (step.step === "validation" || step.step === "review" || step.step === "qa") {
|
|
850
|
-
parts.push(`
|
|
851
|
-
If any validation step reveals issues requiring fixes, go back to the relevant coding step to address them.`);
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
parts.push("");
|
|
855
|
-
}
|
|
856
|
-
return parts.join("\n");
|
|
857
|
-
}
|
|
858
|
-
function buildOrchestratorRule(config) {
|
|
859
|
-
const taskFolder = config.workflow?.task_folder || "./tasks/<TASK_ID>/";
|
|
860
|
-
const steps = config.workflow?.pipeline || [];
|
|
861
|
-
const prompt = config.workflow?.prompt;
|
|
862
|
-
const enforce = config.workflow?.enforce_workflow ?? false;
|
|
863
|
-
const sections = [];
|
|
864
|
-
sections.push(`---
|
|
865
|
-
description: "Orchestrator agent \u2014 coordinates sub-agents through the development pipeline"
|
|
866
|
-
alwaysApply: true
|
|
867
|
-
---
|
|
868
|
-
|
|
869
|
-
# Orchestrator
|
|
870
|
-
|
|
871
|
-
## Your Main Responsibility
|
|
872
|
-
|
|
873
|
-
You are an agent orchestrator. Your job is to ensure that any feature or task requested by the user is completed end-to-end using specialized sub-agents.`);
|
|
874
|
-
if (enforce) {
|
|
875
|
-
sections.push(`
|
|
876
|
-
## STRICT WORKFLOW ENFORCEMENT
|
|
877
|
-
|
|
878
|
-
**YOU MUST FOLLOW THE PIPELINE DEFINED BELOW. NO EXCEPTIONS.**
|
|
879
|
-
|
|
880
|
-
- NEVER skip a pipeline step, even if the task seems simple or obvious.
|
|
881
|
-
- ALWAYS execute steps in the exact order defined. Do not reorder, merge, or parallelize steps unless the pipeline explicitly allows it.
|
|
882
|
-
- ALWAYS call the designated sub-agent for each step. Do not attempt to perform a step yourself if an agent is assigned to it.
|
|
883
|
-
- ALWAYS wait for a step to complete and validate its output before moving to the next step.
|
|
884
|
-
- If a step produces a document, READ the document and confirm it is complete before proceeding.
|
|
885
|
-
- If a step has unanswered questions or validation issues, RESOLVE them before advancing.
|
|
886
|
-
- NEVER jump directly to coding without completing refinement first.
|
|
887
|
-
- NEVER skip review or QA steps, even for small changes.
|
|
888
|
-
- If the user asks you to skip a step, explain why the pipeline exists and ask for explicit confirmation before proceeding.`);
|
|
889
|
-
}
|
|
890
|
-
if (prompt?.prepend) {
|
|
891
|
-
sections.push(`
|
|
892
|
-
${prompt.prepend.trim()}`);
|
|
893
|
-
}
|
|
894
|
-
if (config.integrations?.linear) {
|
|
895
|
-
const linear = config.integrations.linear;
|
|
896
|
-
sections.push(`
|
|
897
|
-
## Task Management
|
|
898
|
-
|
|
899
|
-
If the user doesn't have a task in their project management tool, create one using the Linear MCP.${linear.team ? ` Add it to the **${linear.team}** team.` : ""} Provide the link to the user so they can review and modify as needed.`);
|
|
900
|
-
}
|
|
901
|
-
sections.push(`
|
|
902
|
-
## Repositories
|
|
903
|
-
`);
|
|
904
|
-
for (const repo of config.repos) {
|
|
905
|
-
const parts = [`- **${repo.path}**`];
|
|
906
|
-
if (repo.description) parts.push(`\u2014 ${repo.description}`);
|
|
907
|
-
else if (repo.tech) parts.push(`\u2014 ${repo.tech}`);
|
|
908
|
-
if (repo.skills?.length) parts.push(`(skills: ${repo.skills.join(", ")})`);
|
|
909
|
-
sections.push(parts.join(" "));
|
|
910
|
-
if (repo.commands) {
|
|
911
|
-
const cmds = Object.entries(repo.commands).filter(([, v]) => v).map(([k, v]) => `\`${k}\`: \`${v}\``).join(", ");
|
|
912
|
-
if (cmds) sections.push(` Commands: ${cmds}`);
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
if (prompt?.sections?.after_repositories) {
|
|
916
|
-
sections.push(`
|
|
917
|
-
${prompt.sections.after_repositories.trim()}`);
|
|
918
|
-
}
|
|
919
|
-
const docStructure = buildDocumentStructure(steps, taskFolder);
|
|
920
|
-
sections.push(docStructure);
|
|
921
|
-
const pipelineSection = buildPipelineSection(steps);
|
|
922
|
-
sections.push(pipelineSection);
|
|
923
|
-
if (prompt?.sections?.after_pipeline) {
|
|
924
|
-
sections.push(`
|
|
925
|
-
${prompt.sections.after_pipeline.trim()}`);
|
|
926
|
-
}
|
|
927
|
-
if (config.integrations?.slack || config.integrations?.github) {
|
|
928
|
-
sections.push(buildDeliverySection(config));
|
|
929
|
-
}
|
|
930
|
-
if (prompt?.sections?.after_delivery) {
|
|
931
|
-
sections.push(`
|
|
932
|
-
${prompt.sections.after_delivery.trim()}`);
|
|
933
|
-
}
|
|
934
|
-
if (config.memory) {
|
|
935
|
-
sections.push(`
|
|
936
|
-
## Team Memory
|
|
937
|
-
|
|
938
|
-
This workspace has a team memory knowledge base available via the \`team-memory\` MCP.
|
|
939
|
-
|
|
940
|
-
**Before starting any task**, use \`search_memories\` to find relevant context \u2014 past decisions, conventions, known issues, and domain knowledge. This avoids repeating mistakes and ensures consistency with previous choices.
|
|
941
|
-
|
|
942
|
-
**After completing a task**, if you discovered something valuable (a decision, a gotcha, a convention, domain insight), use \`add_memory\` to capture it for the team.
|
|
943
|
-
|
|
944
|
-
Available tools: \`search_memories\`, \`get_memory\`, \`add_memory\`, \`list_memories\`, \`archive_memory\`, \`remove_memory\`.`);
|
|
945
|
-
}
|
|
946
|
-
sections.push(`
|
|
947
|
-
## Troubleshooting and Debugging
|
|
948
|
-
|
|
949
|
-
For bug reports or unexpected behavior, use the \`debugger\` agent directly.
|
|
950
|
-
It will:
|
|
951
|
-
1. Collect context (symptoms, environment, timeline)
|
|
952
|
-
2. Analyze logs and stack traces
|
|
953
|
-
3. Form and test hypotheses systematically
|
|
954
|
-
4. Identify the root cause
|
|
955
|
-
5. Propose a solution or call coding agents to implement the fix`);
|
|
956
|
-
if (prompt?.sections) {
|
|
957
|
-
const reservedKeys = /* @__PURE__ */ new Set(["after_repositories", "after_pipeline", "after_delivery"]);
|
|
958
|
-
for (const [name, content] of Object.entries(prompt.sections)) {
|
|
959
|
-
if (reservedKeys.has(name)) continue;
|
|
960
|
-
const title = name.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
961
|
-
sections.push(`
|
|
962
|
-
## ${title}
|
|
963
|
-
|
|
964
|
-
${content.trim()}`);
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
if (prompt?.append) {
|
|
968
|
-
sections.push(`
|
|
969
|
-
${prompt.append.trim()}`);
|
|
970
|
-
}
|
|
971
|
-
return sections.join("\n");
|
|
972
|
-
}
|
|
973
|
-
function buildDocumentStructure(steps, taskFolder) {
|
|
974
|
-
const outputs = [];
|
|
975
|
-
for (const step of steps) {
|
|
976
|
-
if (step.output) {
|
|
977
|
-
outputs.push(step.output);
|
|
978
|
-
}
|
|
979
|
-
if (Array.isArray(step.agents)) {
|
|
980
|
-
for (const a of step.agents) {
|
|
981
|
-
if (typeof a === "object" && a.output) {
|
|
982
|
-
outputs.push(a.output);
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
if (outputs.length === 0) {
|
|
988
|
-
outputs.push("refinement.md", "code-backend.md", "code-frontend.md", "code-review.md", "qa-backend.md", "qa-frontend.md");
|
|
989
|
-
}
|
|
990
|
-
const tree = outputs.map((o) => `\u251C\u2500\u2500 ${o}`);
|
|
991
|
-
if (tree.length > 0) {
|
|
992
|
-
tree[tree.length - 1] = tree[tree.length - 1].replace("\u251C\u2500\u2500", "\u2514\u2500\u2500");
|
|
993
|
-
}
|
|
994
|
-
return `
|
|
995
|
-
## Document Structure
|
|
996
|
-
|
|
997
|
-
All task documents are stored in \`${taskFolder}\`:
|
|
998
|
-
|
|
999
|
-
\`\`\`
|
|
1000
|
-
${taskFolder}
|
|
1001
|
-
${tree.join("\n")}
|
|
1002
|
-
\`\`\``;
|
|
1003
|
-
}
|
|
1004
|
-
function buildPipelineSection(steps) {
|
|
1005
|
-
if (steps.length === 0) {
|
|
1006
|
-
return `
|
|
1007
|
-
## Development Pipeline
|
|
1008
|
-
|
|
1009
|
-
1. Use the \`refinement\` agent to collect requirements
|
|
1010
|
-
2. Use \`coding-backend\` and/or \`coding-frontend\` agents to implement
|
|
1011
|
-
3. Use \`code-reviewer\` to review the implementation
|
|
1012
|
-
4. Use \`qa-backend\` and/or \`qa-frontend\` to test
|
|
1013
|
-
5. Create PRs and notify the team`;
|
|
1014
|
-
}
|
|
1015
|
-
const parts = [`
|
|
1016
|
-
## Development Pipeline
|
|
1017
|
-
`];
|
|
1018
|
-
for (const step of steps) {
|
|
1019
|
-
if (step.actions) {
|
|
1020
|
-
parts.push(`### Delivery`);
|
|
1021
|
-
parts.push(`After all validations pass, execute these actions:`);
|
|
1022
|
-
for (const action of step.actions) {
|
|
1023
|
-
parts.push(`- ${formatAction(action)}`);
|
|
1024
|
-
}
|
|
1025
|
-
continue;
|
|
1026
|
-
}
|
|
1027
|
-
const stepTitle = step.step.charAt(0).toUpperCase() + step.step.slice(1);
|
|
1028
|
-
parts.push(`### ${stepTitle}`);
|
|
1029
|
-
if (step.mode === "plan") {
|
|
1030
|
-
parts.push(`**Before starting this step, switch to Plan Mode** by calling \`SwitchMode\` with \`target_mode_id: "plan"\`. This ensures collaborative planning with the user in a read-only context before any implementation begins.`);
|
|
1031
|
-
parts.push(``);
|
|
1032
|
-
}
|
|
1033
|
-
if (step.agent) {
|
|
1034
|
-
parts.push(`Call the \`${step.agent}\` agent.${step.output ? ` It writes to \`${step.output}\`.` : ""}`);
|
|
1035
|
-
if (step.step === "refinement") {
|
|
1036
|
-
parts.push(`
|
|
1037
|
-
After it runs, read the document and validate with the user:
|
|
1038
|
-
- If there are unanswered questions, ask the user one at a time
|
|
1039
|
-
- If the user requests adjustments, send back to the refinement agent
|
|
1040
|
-
- Do not proceed until the document is complete and approved by the user`);
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
if (Array.isArray(step.agents)) {
|
|
1044
|
-
const agentList = step.agents.map((a) => {
|
|
1045
|
-
if (typeof a === "string") return { agent: a };
|
|
1046
|
-
return a;
|
|
1047
|
-
});
|
|
1048
|
-
if (step.parallel) {
|
|
1049
|
-
parts.push(`Call these agents${step.parallel ? " in parallel" : ""}:`);
|
|
1050
|
-
} else {
|
|
1051
|
-
parts.push(`Call these agents in sequence:`);
|
|
1052
|
-
}
|
|
1053
|
-
for (const a of agentList) {
|
|
1054
|
-
let line = `- \`${a.agent}\``;
|
|
1055
|
-
if (a.output) line += ` \u2192 writes to \`${a.output}\``;
|
|
1056
|
-
if (a.when) line += ` (when: ${a.when})`;
|
|
1057
|
-
parts.push(line);
|
|
1058
|
-
}
|
|
1059
|
-
if (step.step === "coding" || step.step === "code" || step.step === "implementation") {
|
|
1060
|
-
parts.push(`
|
|
1061
|
-
If any coding agent has doubts, they will write questions in their document. Apply the same Q&A logic as refinement \u2014 validate with the user before proceeding.`);
|
|
1062
|
-
}
|
|
1063
|
-
if (step.step === "validation" || step.step === "review" || step.step === "qa") {
|
|
1064
|
-
parts.push(`
|
|
1065
|
-
If any validation agent leaves comments requiring fixes, call the relevant coding agents again to address them.`);
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
if (step.mode === "plan") {
|
|
1069
|
-
parts.push(`
|
|
1070
|
-
**After this step is complete and approved**, switch back to Agent Mode to proceed with the next step.`);
|
|
1071
|
-
}
|
|
1072
|
-
parts.push("");
|
|
1073
|
-
}
|
|
1074
|
-
return parts.join("\n");
|
|
1075
|
-
}
|
|
1076
|
-
function buildDeliverySection(config) {
|
|
1077
|
-
const parts = [`
|
|
1078
|
-
## Delivery Details
|
|
1079
|
-
`];
|
|
1080
|
-
if (config.integrations?.github) {
|
|
1081
|
-
const gh = config.integrations.github;
|
|
1082
|
-
parts.push(`### Pull Requests`);
|
|
1083
|
-
parts.push(`For each repository with changes, push the branch and create a PR using the GitHub MCP.`);
|
|
1084
|
-
if (gh.pr_branch_pattern) {
|
|
1085
|
-
parts.push(`Branch naming pattern: \`${gh.pr_branch_pattern}\``);
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
if (config.integrations?.slack) {
|
|
1089
|
-
const slack = config.integrations.slack;
|
|
1090
|
-
if (slack.channels) {
|
|
1091
|
-
parts.push(`
|
|
1092
|
-
### Slack Notifications`);
|
|
1093
|
-
for (const [purpose, channel] of Object.entries(slack.channels)) {
|
|
1094
|
-
parts.push(`- **${purpose}**: Post to \`${channel}\``);
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
if (slack.templates) {
|
|
1098
|
-
parts.push(`
|
|
1099
|
-
Message templates:`);
|
|
1100
|
-
for (const [name, template] of Object.entries(slack.templates)) {
|
|
1101
|
-
parts.push(`- **${name}**: \`${template}\``);
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
if (config.integrations?.linear) {
|
|
1106
|
-
parts.push(`
|
|
1107
|
-
### Task Management`);
|
|
1108
|
-
parts.push(`Update the Linear task status after PR creation.`);
|
|
1109
|
-
}
|
|
1110
|
-
return parts.join("\n");
|
|
1111
|
-
}
|
|
1112
|
-
function formatAction(action) {
|
|
1113
|
-
const map = {
|
|
1114
|
-
"create-pr": "Create pull requests for each repository with changes",
|
|
1115
|
-
"notify-slack": "Send notification to the configured Slack channel",
|
|
1116
|
-
"notify-slack-prs": "Send PR notification to the Slack PRs channel",
|
|
1117
|
-
"update-linear": "Update the Linear task status",
|
|
1118
|
-
"update-linear-status": "Update the Linear task status to Review",
|
|
1119
|
-
"update-jira": "Update the Jira task status"
|
|
1120
|
-
};
|
|
1121
|
-
return map[action] || action;
|
|
1122
|
-
}
|
|
1123
|
-
async function generateClaudeCode(config, hubDir) {
|
|
1124
|
-
const claudeDir = join5(hubDir, ".claude");
|
|
1125
|
-
await mkdir2(join5(claudeDir, "agents"), { recursive: true });
|
|
1126
|
-
const orchestratorRule = buildOrchestratorRule(config);
|
|
1127
|
-
const cleanedOrchestrator = orchestratorRule.replace(/^---[\s\S]*?---\n/m, "").trim();
|
|
1128
|
-
const claudeMdSections = [];
|
|
1129
|
-
claudeMdSections.push(cleanedOrchestrator);
|
|
1130
|
-
const agentsDir = resolve(hubDir, "agents");
|
|
1131
|
-
try {
|
|
1132
|
-
const agentFiles = await readdir(agentsDir);
|
|
1133
|
-
const mdFiles = agentFiles.filter((f) => f.endsWith(".md"));
|
|
1134
|
-
for (const file of mdFiles) {
|
|
1135
|
-
await copyFile(join5(agentsDir, file), join5(claudeDir, "agents", file));
|
|
1136
|
-
}
|
|
1137
|
-
console.log(chalk4.green(` Copied ${mdFiles.length} agents to .claude/agents/`));
|
|
1138
|
-
} catch {
|
|
1139
|
-
console.log(chalk4.yellow(" No agents/ directory found, skipping agent copy"));
|
|
1140
|
-
}
|
|
1141
|
-
const skillsDir = resolve(hubDir, "skills");
|
|
1142
|
-
try {
|
|
1143
|
-
const skillFolders = await readdir(skillsDir);
|
|
1144
|
-
const claudeSkillsDir = join5(claudeDir, "skills");
|
|
1145
|
-
await mkdir2(claudeSkillsDir, { recursive: true });
|
|
1146
|
-
let count = 0;
|
|
1147
|
-
for (const folder of skillFolders) {
|
|
1148
|
-
const skillFile = join5(skillsDir, folder, "SKILL.md");
|
|
1149
|
-
try {
|
|
1150
|
-
await readFile3(skillFile);
|
|
1151
|
-
const srcDir = join5(skillsDir, folder);
|
|
1152
|
-
const targetDir = join5(claudeSkillsDir, folder);
|
|
1153
|
-
await cp(srcDir, targetDir, { recursive: true });
|
|
1154
|
-
count++;
|
|
1155
|
-
} catch {
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
if (count > 0) {
|
|
1159
|
-
console.log(chalk4.green(` Copied ${count} skills to .claude/skills/`));
|
|
1160
|
-
}
|
|
1161
|
-
} catch {
|
|
1162
|
-
}
|
|
1163
|
-
await writeFile4(join5(hubDir, "CLAUDE.md"), claudeMdSections.join("\n"), "utf-8");
|
|
1164
|
-
console.log(chalk4.green(" Generated CLAUDE.md"));
|
|
1165
|
-
if (config.mcps?.length) {
|
|
1166
|
-
const mcpJson = {};
|
|
1167
|
-
for (const mcp of config.mcps) {
|
|
1168
|
-
mcpJson[mcp.name] = buildClaudeCodeMcpEntry(mcp);
|
|
1169
|
-
}
|
|
1170
|
-
await writeFile4(
|
|
1171
|
-
join5(hubDir, ".mcp.json"),
|
|
1172
|
-
JSON.stringify({ mcpServers: mcpJson }, null, 2) + "\n",
|
|
1173
|
-
"utf-8"
|
|
1174
|
-
);
|
|
1175
|
-
console.log(chalk4.green(" Generated .mcp.json"));
|
|
1176
|
-
}
|
|
1177
|
-
const mcpServerNames = config.mcps?.map((m) => m.name) || [];
|
|
1178
|
-
const claudeSettings = {
|
|
1179
|
-
$schema: "https://json.schemastore.org/claude-code-settings.json",
|
|
1180
|
-
permissions: {
|
|
1181
|
-
allow: [
|
|
1182
|
-
"Read(*)",
|
|
1183
|
-
"Edit(*)",
|
|
1184
|
-
"Write(*)",
|
|
1185
|
-
"Bash(git *)",
|
|
1186
|
-
"Bash(npm *)",
|
|
1187
|
-
"Bash(pnpm *)",
|
|
1188
|
-
"Bash(npx *)",
|
|
1189
|
-
"Bash(ls *)",
|
|
1190
|
-
"Bash(echo *)",
|
|
1191
|
-
"Bash(grep *)",
|
|
1192
|
-
...mcpServerNames.map((name) => `mcp__${name}__*`)
|
|
1193
|
-
],
|
|
1194
|
-
deny: [
|
|
1195
|
-
"Read(.env)",
|
|
1196
|
-
"Read(.env.*)",
|
|
1197
|
-
"Read(**/.env)",
|
|
1198
|
-
"Read(**/.env.*)",
|
|
1199
|
-
"Read(**/credentials*)",
|
|
1200
|
-
"Read(**/secrets*)",
|
|
1201
|
-
"Read(**/*.pem)",
|
|
1202
|
-
"Read(**/*.key)"
|
|
1203
|
-
]
|
|
1204
|
-
},
|
|
1205
|
-
enableAllProjectMcpServers: true
|
|
1206
|
-
};
|
|
1207
|
-
if (config.hooks) {
|
|
1208
|
-
const claudeHooks = buildClaudeHooks(config.hooks);
|
|
1209
|
-
if (claudeHooks) {
|
|
1210
|
-
claudeSettings.hooks = claudeHooks;
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
await writeFile4(
|
|
1214
|
-
join5(claudeDir, "settings.json"),
|
|
1215
|
-
JSON.stringify(claudeSettings, null, 2) + "\n",
|
|
1216
|
-
"utf-8"
|
|
1217
|
-
);
|
|
1218
|
-
console.log(chalk4.green(" Generated .claude/settings.json"));
|
|
1219
|
-
const gitignoreLines = buildGitignoreLines(config);
|
|
1220
|
-
await writeManagedFile(join5(hubDir, ".gitignore"), gitignoreLines);
|
|
1221
|
-
console.log(chalk4.green(" Generated .gitignore"));
|
|
1222
|
-
}
|
|
1223
|
-
async function generateKiro(config, hubDir) {
|
|
1224
|
-
const kiroDir = join5(hubDir, ".kiro");
|
|
1225
|
-
const steeringDir = join5(kiroDir, "steering");
|
|
1226
|
-
const settingsDir = join5(kiroDir, "settings");
|
|
1227
|
-
await mkdir2(steeringDir, { recursive: true });
|
|
1228
|
-
await mkdir2(settingsDir, { recursive: true });
|
|
1229
|
-
const gitignoreLines = buildGitignoreLines(config);
|
|
1230
|
-
await writeManagedFile(join5(hubDir, ".gitignore"), gitignoreLines);
|
|
1231
|
-
console.log(chalk4.green(" Generated .gitignore"));
|
|
1232
|
-
const kiroRule = buildKiroOrchestratorRule(config);
|
|
1233
|
-
const kiroOrchestrator = buildKiroSteeringContent(kiroRule);
|
|
1234
|
-
await writeFile4(join5(steeringDir, "orchestrator.md"), kiroOrchestrator, "utf-8");
|
|
1235
|
-
console.log(chalk4.green(" Generated .kiro/steering/orchestrator.md"));
|
|
1236
|
-
await writeFile4(join5(hubDir, "AGENTS.md"), kiroRule + "\n", "utf-8");
|
|
1237
|
-
console.log(chalk4.green(" Generated AGENTS.md"));
|
|
1238
|
-
const agentsDir = resolve(hubDir, "agents");
|
|
1239
|
-
try {
|
|
1240
|
-
const agentFiles = await readdir(agentsDir);
|
|
1241
|
-
const mdFiles = agentFiles.filter((f) => f.endsWith(".md"));
|
|
1242
|
-
for (const file of mdFiles) {
|
|
1243
|
-
const agentContent = await readFile3(join5(agentsDir, file), "utf-8");
|
|
1244
|
-
const agentName = file.replace(/\.md$/, "");
|
|
1245
|
-
const steeringContent = buildKiroSteeringContent(agentContent, "auto", {
|
|
1246
|
-
name: agentName,
|
|
1247
|
-
description: `Role-specific instructions for the ${agentName} phase. Include when working on ${agentName}-related tasks.`
|
|
1248
|
-
});
|
|
1249
|
-
const steeringName = `agent-${file}`;
|
|
1250
|
-
await writeFile4(join5(steeringDir, steeringName), steeringContent, "utf-8");
|
|
1251
|
-
}
|
|
1252
|
-
console.log(chalk4.green(` Copied ${mdFiles.length} agents as steering files`));
|
|
1253
|
-
} catch {
|
|
1254
|
-
console.log(chalk4.yellow(" No agents/ directory found, skipping agent copy"));
|
|
1255
|
-
}
|
|
1256
|
-
const skillsDir = resolve(hubDir, "skills");
|
|
1257
|
-
try {
|
|
1258
|
-
const skillFolders = await readdir(skillsDir);
|
|
1259
|
-
const kiroSkillsDir = join5(kiroDir, "skills");
|
|
1260
|
-
await mkdir2(kiroSkillsDir, { recursive: true });
|
|
1261
|
-
let count = 0;
|
|
1262
|
-
for (const folder of skillFolders) {
|
|
1263
|
-
const skillFile = join5(skillsDir, folder, "SKILL.md");
|
|
1264
|
-
try {
|
|
1265
|
-
await readFile3(skillFile);
|
|
1266
|
-
const srcDir = join5(skillsDir, folder);
|
|
1267
|
-
const targetDir = join5(kiroSkillsDir, folder);
|
|
1268
|
-
await cp(srcDir, targetDir, { recursive: true });
|
|
1269
|
-
count++;
|
|
1270
|
-
} catch {
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
1273
|
-
if (count > 0) {
|
|
1274
|
-
console.log(chalk4.green(` Copied ${count} skills to .kiro/skills/`));
|
|
1275
|
-
}
|
|
1276
|
-
} catch {
|
|
1277
|
-
}
|
|
1278
|
-
if (config.mcps?.length) {
|
|
1279
|
-
const mcpConfig = {};
|
|
1280
|
-
for (const mcp of config.mcps) {
|
|
1281
|
-
mcpConfig[mcp.name] = buildKiroMcpEntry(mcp);
|
|
1282
|
-
}
|
|
1283
|
-
await writeFile4(
|
|
1284
|
-
join5(settingsDir, "mcp.json"),
|
|
1285
|
-
JSON.stringify({ mcpServers: mcpConfig }, null, 2) + "\n",
|
|
1286
|
-
"utf-8"
|
|
1287
|
-
);
|
|
1288
|
-
console.log(chalk4.green(" Generated .kiro/settings/mcp.json"));
|
|
1289
|
-
}
|
|
1290
|
-
if (config.hooks) {
|
|
1291
|
-
const hookNotes = [];
|
|
1292
|
-
for (const [event, entries] of Object.entries(config.hooks)) {
|
|
1293
|
-
const mapped = HOOK_EVENT_MAP[event]?.kiro;
|
|
1294
|
-
if (!mapped) continue;
|
|
1295
|
-
for (const entry of entries) {
|
|
1296
|
-
hookNotes.push(`- **${mapped}**: ${entry.type === "command" ? entry.command : entry.prompt}`);
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
if (hookNotes.length > 0) {
|
|
1300
|
-
console.log(chalk4.yellow(` Note: Kiro hooks are managed via the Kiro panel UI.`));
|
|
1301
|
-
console.log(chalk4.yellow(` The following hooks should be configured manually:`));
|
|
1302
|
-
for (const note of hookNotes) {
|
|
1303
|
-
console.log(chalk4.yellow(` ${note}`));
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
await generateVSCodeSettings(config, hubDir);
|
|
1308
|
-
}
|
|
1309
|
-
async function generateVSCodeSettings(config, hubDir) {
|
|
1310
|
-
const vscodeDir = join5(hubDir, ".vscode");
|
|
1311
|
-
await mkdir2(vscodeDir, { recursive: true });
|
|
1312
|
-
const settingsPath = join5(vscodeDir, "settings.json");
|
|
1313
|
-
let existing = {};
|
|
1314
|
-
if (existsSync3(settingsPath)) {
|
|
1315
|
-
try {
|
|
1316
|
-
const raw = await readFile3(settingsPath, "utf-8");
|
|
1317
|
-
existing = JSON.parse(raw);
|
|
1318
|
-
} catch {
|
|
1319
|
-
existing = {};
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
const managed = {
|
|
1323
|
-
"git.autoRepositoryDetection": true,
|
|
1324
|
-
"git.detectSubmodules": true,
|
|
1325
|
-
"git.detectSubmodulesLimit": Math.max(config.repos.length * 2, 20)
|
|
1326
|
-
};
|
|
1327
|
-
const merged = { ...existing, ...managed };
|
|
1328
|
-
await writeFile4(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
1329
|
-
console.log(chalk4.green(" Generated .vscode/settings.json (git multi-repo detection)"));
|
|
1330
|
-
}
|
|
1331
|
-
function buildGitignoreLines(config) {
|
|
1332
|
-
const lines = [
|
|
1333
|
-
"node_modules/",
|
|
1334
|
-
".DS_Store",
|
|
1335
|
-
"",
|
|
1336
|
-
"# Repositories (managed by hub)"
|
|
1337
|
-
];
|
|
1338
|
-
for (const repo of config.repos) {
|
|
1339
|
-
lines.push(repo.path.replace("./", ""));
|
|
1340
|
-
}
|
|
1341
|
-
lines.push(
|
|
1342
|
-
"",
|
|
1343
|
-
"# Docker volumes",
|
|
1344
|
-
"*_data/",
|
|
1345
|
-
"",
|
|
1346
|
-
"# Environment files",
|
|
1347
|
-
"*.env",
|
|
1348
|
-
"*.env.local",
|
|
1349
|
-
"!.env.example",
|
|
1350
|
-
"",
|
|
1351
|
-
"# Generated files",
|
|
1352
|
-
"docker-compose.yml",
|
|
1353
|
-
"",
|
|
1354
|
-
"# Task documents",
|
|
1355
|
-
"tasks/"
|
|
1356
|
-
);
|
|
1357
|
-
if (config.memory) {
|
|
1358
|
-
const memPath = config.memory.path || "memories";
|
|
1359
|
-
lines.push(
|
|
1360
|
-
"",
|
|
1361
|
-
"# Memory vector store (generated from markdown files)",
|
|
1362
|
-
`${memPath}/.lancedb/`
|
|
1363
|
-
);
|
|
1364
|
-
}
|
|
1365
|
-
return lines;
|
|
1366
|
-
}
|
|
1367
|
-
var generators = {
|
|
1368
|
-
cursor: { name: "Cursor", generate: generateCursor },
|
|
1369
|
-
"claude-code": { name: "Claude Code", generate: generateClaudeCode },
|
|
1370
|
-
kiro: { name: "Kiro", generate: generateKiro }
|
|
1371
|
-
};
|
|
1372
|
-
var generateCommand = new Command4("generate").description("Generate editor-specific configuration files from hub.yaml").option("-e, --editor <editor>", "Target editor (cursor, claude-code, kiro)", "cursor").action(async (opts) => {
|
|
1373
|
-
const hubDir = process.cwd();
|
|
1374
|
-
const config = await loadHubConfig(hubDir);
|
|
1375
|
-
if (config.memory) {
|
|
1376
|
-
const hasMemoryMcp = config.mcps?.some(
|
|
1377
|
-
(m) => m.name === "team-memory" || m.package === "@arvoretech/memory-mcp"
|
|
1378
|
-
);
|
|
1379
|
-
if (!hasMemoryMcp) {
|
|
1380
|
-
console.log(chalk4.red(`
|
|
1381
|
-
Error: 'memory' is configured but no memory MCP is declared in 'mcps'.
|
|
1382
|
-
`));
|
|
1383
|
-
console.log(chalk4.yellow(` Add this to your hub.yaml:
|
|
1384
|
-
`));
|
|
1385
|
-
console.log(chalk4.dim(` mcps:`));
|
|
1386
|
-
console.log(chalk4.dim(` - name: team-memory`));
|
|
1387
|
-
console.log(chalk4.dim(` package: "@arvoretech/memory-mcp"`));
|
|
1388
|
-
console.log(chalk4.dim(` env:`));
|
|
1389
|
-
console.log(chalk4.dim(` MEMORY_PATH: ${config.memory.path || "./memories"}
|
|
1390
|
-
`));
|
|
1391
|
-
process.exit(1);
|
|
1392
|
-
}
|
|
1393
|
-
}
|
|
1394
|
-
const generator = generators[opts.editor];
|
|
1395
|
-
if (!generator) {
|
|
1396
|
-
console.log(
|
|
1397
|
-
chalk4.red(`Unknown editor: ${opts.editor}. Available: ${Object.keys(generators).join(", ")}`)
|
|
1398
|
-
);
|
|
1399
|
-
return;
|
|
1400
|
-
}
|
|
1401
|
-
console.log(chalk4.blue(`
|
|
1402
|
-
Generating ${generator.name} configuration
|
|
1403
|
-
`));
|
|
1404
|
-
await generator.generate(config, hubDir);
|
|
1405
|
-
console.log(chalk4.green("\nDone!\n"));
|
|
1406
|
-
});
|
|
1407
|
-
|
|
1408
|
-
// src/commands/env.ts
|
|
1409
|
-
import { Command as Command5 } from "commander";
|
|
1410
|
-
import { existsSync as existsSync4 } from "fs";
|
|
1411
|
-
import { writeFile as writeFile5, readFile as readFile4 } from "fs/promises";
|
|
1412
|
-
import { join as join6 } from "path";
|
|
403
|
+
import { writeFile as writeFile4, readFile as readFile2 } from "fs/promises";
|
|
404
|
+
import { join as join4 } from "path";
|
|
1413
405
|
import { execSync as execSync2 } from "child_process";
|
|
1414
|
-
import
|
|
406
|
+
import chalk4 from "chalk";
|
|
1415
407
|
var authenticatedProfiles = /* @__PURE__ */ new Set();
|
|
1416
408
|
function awsAuthenticated(profile) {
|
|
1417
409
|
if (authenticatedProfiles.has(profile)) return true;
|
|
@@ -1458,45 +450,45 @@ function buildDatabaseUrlFromSecret(secretData, buildConfig) {
|
|
|
1458
450
|
}
|
|
1459
451
|
return `mysql://${user}:${password || ""}@${host}:${port}/${database}`;
|
|
1460
452
|
}
|
|
1461
|
-
var envCommand = new
|
|
453
|
+
var envCommand = new Command4("env").description("Generate environment files from hub.yaml profiles").argument("[profile]", "Environment profile (local, staging, prod)", "local").action(async (profile) => {
|
|
1462
454
|
const hubDir = process.cwd();
|
|
1463
455
|
const config = await loadHubConfig(hubDir);
|
|
1464
456
|
const profiles = config.env?.profiles;
|
|
1465
457
|
if (!profiles) {
|
|
1466
|
-
console.log(
|
|
458
|
+
console.log(chalk4.yellow("\nNo env.profiles defined in hub.yaml\n"));
|
|
1467
459
|
return;
|
|
1468
460
|
}
|
|
1469
461
|
const profileConfig = profiles[profile];
|
|
1470
462
|
if (!profileConfig) {
|
|
1471
463
|
const available = Object.keys(profiles).join(", ");
|
|
1472
|
-
console.log(
|
|
464
|
+
console.log(chalk4.red(`
|
|
1473
465
|
Unknown profile: ${profile}. Available: ${available}
|
|
1474
466
|
`));
|
|
1475
467
|
return;
|
|
1476
468
|
}
|
|
1477
|
-
console.log(
|
|
469
|
+
console.log(chalk4.blue(`
|
|
1478
470
|
\u2501\u2501\u2501 Generating environment files (${profile}) \u2501\u2501\u2501
|
|
1479
471
|
`));
|
|
1480
472
|
const defaultAwsProfile = profileConfig.aws_profile || "";
|
|
1481
473
|
const secrets = profileConfig.secrets || {};
|
|
1482
474
|
const buildDbConfigs = profileConfig.build_database_url || {};
|
|
1483
475
|
if (profile !== "local" && defaultAwsProfile) {
|
|
1484
|
-
console.log(
|
|
476
|
+
console.log(chalk4.cyan(` Default AWS profile: ${defaultAwsProfile}`));
|
|
1485
477
|
if (!awsAuthenticated(defaultAwsProfile)) {
|
|
1486
|
-
console.log(
|
|
1487
|
-
console.log(
|
|
478
|
+
console.log(chalk4.red(` AWS profile ${defaultAwsProfile} not authenticated`));
|
|
479
|
+
console.log(chalk4.cyan(` Run: aws sso login --profile ${defaultAwsProfile}`));
|
|
1488
480
|
return;
|
|
1489
481
|
}
|
|
1490
|
-
console.log(
|
|
482
|
+
console.log(chalk4.green(` AWS authenticated`));
|
|
1491
483
|
}
|
|
1492
484
|
for (const repo of config.repos) {
|
|
1493
485
|
if (!repo.env_file) continue;
|
|
1494
|
-
const repoDir =
|
|
1495
|
-
if (!
|
|
1496
|
-
console.log(
|
|
486
|
+
const repoDir = join4(hubDir, repo.path);
|
|
487
|
+
if (!existsSync3(repoDir)) {
|
|
488
|
+
console.log(chalk4.dim(` ${repo.name}: repo not cloned, skipping`));
|
|
1497
489
|
continue;
|
|
1498
490
|
}
|
|
1499
|
-
console.log(
|
|
491
|
+
console.log(chalk4.yellow(`\u25B8 ${repo.name}`));
|
|
1500
492
|
const envVars = {};
|
|
1501
493
|
if (profile !== "local" && secrets[repo.name]) {
|
|
1502
494
|
const { secretName, profile: secretProfile } = resolveSecret(
|
|
@@ -1504,20 +496,20 @@ Unknown profile: ${profile}. Available: ${available}
|
|
|
1504
496
|
defaultAwsProfile
|
|
1505
497
|
);
|
|
1506
498
|
if (!secretProfile) {
|
|
1507
|
-
console.log(
|
|
499
|
+
console.log(chalk4.red(` No AWS profile available for secret: ${secretName}`));
|
|
1508
500
|
} else {
|
|
1509
501
|
if (!awsAuthenticated(secretProfile)) {
|
|
1510
|
-
console.log(
|
|
1511
|
-
console.log(
|
|
502
|
+
console.log(chalk4.red(` AWS profile ${secretProfile} not authenticated`));
|
|
503
|
+
console.log(chalk4.cyan(` Run: aws sso login --profile ${secretProfile}`));
|
|
1512
504
|
continue;
|
|
1513
505
|
}
|
|
1514
|
-
console.log(
|
|
506
|
+
console.log(chalk4.cyan(` Fetching: ${secretName} (profile: ${secretProfile})`));
|
|
1515
507
|
const secretData = fetchSecret(secretName, secretProfile);
|
|
1516
508
|
if (secretData) {
|
|
1517
509
|
Object.assign(envVars, secretData);
|
|
1518
|
-
console.log(
|
|
510
|
+
console.log(chalk4.green(` Loaded ${Object.keys(secretData).length} vars from AWS`));
|
|
1519
511
|
} else {
|
|
1520
|
-
console.log(
|
|
512
|
+
console.log(chalk4.red(` Failed to fetch secret: ${secretName}`));
|
|
1521
513
|
}
|
|
1522
514
|
}
|
|
1523
515
|
}
|
|
@@ -1525,25 +517,25 @@ Unknown profile: ${profile}. Available: ${available}
|
|
|
1525
517
|
const buildConfig = buildDbConfigs[repo.name];
|
|
1526
518
|
const dbProfile = buildConfig.profile || defaultAwsProfile;
|
|
1527
519
|
if (!dbProfile) {
|
|
1528
|
-
console.log(
|
|
520
|
+
console.log(chalk4.red(` No AWS profile available for build_database_url`));
|
|
1529
521
|
} else {
|
|
1530
522
|
if (!awsAuthenticated(dbProfile)) {
|
|
1531
|
-
console.log(
|
|
1532
|
-
console.log(
|
|
523
|
+
console.log(chalk4.red(` AWS profile ${dbProfile} not authenticated`));
|
|
524
|
+
console.log(chalk4.cyan(` Run: aws sso login --profile ${dbProfile}`));
|
|
1533
525
|
} else {
|
|
1534
|
-
console.log(
|
|
526
|
+
console.log(chalk4.cyan(` Building DATABASE_URL from ${buildConfig.from_secret} (profile: ${dbProfile})`));
|
|
1535
527
|
const dbSecretData = fetchSecret(buildConfig.from_secret, dbProfile);
|
|
1536
528
|
if (dbSecretData) {
|
|
1537
529
|
const dbUrl = buildDatabaseUrlFromSecret(dbSecretData, buildConfig);
|
|
1538
530
|
if (dbUrl) {
|
|
1539
531
|
envVars["DATABASE_URL"] = dbUrl;
|
|
1540
532
|
envVars["IDENTITY_DATABASE_URL"] = dbUrl;
|
|
1541
|
-
console.log(
|
|
533
|
+
console.log(chalk4.green(` Built DATABASE_URL from ${buildConfig.from_secret}`));
|
|
1542
534
|
} else {
|
|
1543
|
-
console.log(
|
|
535
|
+
console.log(chalk4.red(` Could not build DATABASE_URL - missing required fields`));
|
|
1544
536
|
}
|
|
1545
537
|
} else {
|
|
1546
|
-
console.log(
|
|
538
|
+
console.log(chalk4.red(` Failed to fetch secret: ${buildConfig.from_secret}`));
|
|
1547
539
|
}
|
|
1548
540
|
}
|
|
1549
541
|
}
|
|
@@ -1553,12 +545,12 @@ Unknown profile: ${profile}. Available: ${available}
|
|
|
1553
545
|
for (const [key, value] of Object.entries(overrides)) {
|
|
1554
546
|
envVars[key] = value;
|
|
1555
547
|
}
|
|
1556
|
-
console.log(
|
|
548
|
+
console.log(chalk4.cyan(` Applied ${Object.keys(overrides).length} overrides`));
|
|
1557
549
|
}
|
|
1558
|
-
const envPath =
|
|
550
|
+
const envPath = join4(repoDir, repo.env_file);
|
|
1559
551
|
const existingVars = {};
|
|
1560
552
|
try {
|
|
1561
|
-
const existing = await
|
|
553
|
+
const existing = await readFile2(envPath, "utf-8");
|
|
1562
554
|
for (const line of existing.split("\n")) {
|
|
1563
555
|
const trimmed = line.trim();
|
|
1564
556
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -1571,40 +563,40 @@ Unknown profile: ${profile}. Available: ${available}
|
|
|
1571
563
|
}
|
|
1572
564
|
const merged = { ...existingVars, ...envVars };
|
|
1573
565
|
const lines = Object.entries(merged).map(([k, v]) => `${k}=${v}`);
|
|
1574
|
-
await
|
|
1575
|
-
console.log(
|
|
566
|
+
await writeFile4(envPath, lines.join("\n") + "\n", "utf-8");
|
|
567
|
+
console.log(chalk4.green(` Created ${repo.env_file} (${lines.length} vars)`));
|
|
1576
568
|
}
|
|
1577
569
|
console.log();
|
|
1578
570
|
if (profile === "local") {
|
|
1579
|
-
console.log(
|
|
1580
|
-
console.log(
|
|
571
|
+
console.log(chalk4.cyan("Using local Docker services only"));
|
|
572
|
+
console.log(chalk4.cyan("Run: npx @arvoretech/hub services up"));
|
|
1581
573
|
} else if (profile === "prod") {
|
|
1582
|
-
console.log(
|
|
1583
|
-
console.log(
|
|
574
|
+
console.log(chalk4.red("WARNING: Using PRODUCTION database!"));
|
|
575
|
+
console.log(chalk4.red("Be careful with write operations!"));
|
|
1584
576
|
} else {
|
|
1585
|
-
console.log(
|
|
577
|
+
console.log(chalk4.cyan(`Using ${profile} environment`));
|
|
1586
578
|
}
|
|
1587
579
|
console.log();
|
|
1588
580
|
});
|
|
1589
581
|
|
|
1590
582
|
// src/commands/services.ts
|
|
1591
|
-
import { Command as
|
|
1592
|
-
import { existsSync as
|
|
1593
|
-
import { writeFile as
|
|
1594
|
-
import { join as
|
|
583
|
+
import { Command as Command5 } from "commander";
|
|
584
|
+
import { existsSync as existsSync4 } from "fs";
|
|
585
|
+
import { writeFile as writeFile5 } from "fs/promises";
|
|
586
|
+
import { join as join5 } from "path";
|
|
1595
587
|
import { execSync as execSync3 } from "child_process";
|
|
1596
|
-
import
|
|
588
|
+
import chalk5 from "chalk";
|
|
1597
589
|
async function ensureCompose(hubDir) {
|
|
1598
|
-
const composePath =
|
|
1599
|
-
if (!
|
|
590
|
+
const composePath = join5(hubDir, "docker-compose.yml");
|
|
591
|
+
if (!existsSync4(composePath)) {
|
|
1600
592
|
const config = await loadHubConfig(hubDir);
|
|
1601
593
|
if (!config.services?.length) {
|
|
1602
|
-
console.log(
|
|
594
|
+
console.log(chalk5.yellow("No services defined in hub.yaml"));
|
|
1603
595
|
process.exit(1);
|
|
1604
596
|
}
|
|
1605
597
|
const content = generateDockerCompose(config.services);
|
|
1606
|
-
await
|
|
1607
|
-
console.log(
|
|
598
|
+
await writeFile5(composePath, content, "utf-8");
|
|
599
|
+
console.log(chalk5.green("Generated docker-compose.yml"));
|
|
1608
600
|
}
|
|
1609
601
|
return composePath;
|
|
1610
602
|
}
|
|
@@ -1616,10 +608,10 @@ function isDockerRunning() {
|
|
|
1616
608
|
return false;
|
|
1617
609
|
}
|
|
1618
610
|
}
|
|
1619
|
-
var servicesCommand = new
|
|
611
|
+
var servicesCommand = new Command5("services").description("Manage Docker development services").argument("[action]", "up, down, restart, ps, logs, clean", "up").argument("[service...]", "Specific services (for logs)").action(async (action, serviceNames) => {
|
|
1620
612
|
if (!isDockerRunning()) {
|
|
1621
|
-
console.log(
|
|
1622
|
-
console.log(
|
|
613
|
+
console.log(chalk5.red("\nDocker daemon is not running."));
|
|
614
|
+
console.log(chalk5.dim("Start Docker Desktop or the Docker daemon and try again.\n"));
|
|
1623
615
|
return;
|
|
1624
616
|
}
|
|
1625
617
|
const hubDir = process.cwd();
|
|
@@ -1629,26 +621,26 @@ var servicesCommand = new Command6("services").description("Manage Docker develo
|
|
|
1629
621
|
switch (action) {
|
|
1630
622
|
case "up":
|
|
1631
623
|
case "start": {
|
|
1632
|
-
console.log(
|
|
624
|
+
console.log(chalk5.blue("\nStarting services\n"));
|
|
1633
625
|
execSync3(`${compose} up -d`, { stdio: "inherit", cwd: hubDir });
|
|
1634
626
|
const config = await loadHubConfig(hubDir);
|
|
1635
|
-
console.log(
|
|
627
|
+
console.log(chalk5.green("\nServices running:"));
|
|
1636
628
|
for (const svc of config.services || []) {
|
|
1637
629
|
const port = svc.port || svc.ports?.[0];
|
|
1638
|
-
if (port) console.log(
|
|
630
|
+
if (port) console.log(chalk5.cyan(` ${svc.name}: localhost:${port}`));
|
|
1639
631
|
}
|
|
1640
632
|
console.log();
|
|
1641
633
|
break;
|
|
1642
634
|
}
|
|
1643
635
|
case "down":
|
|
1644
636
|
case "stop":
|
|
1645
|
-
console.log(
|
|
637
|
+
console.log(chalk5.blue("\nStopping services\n"));
|
|
1646
638
|
execSync3(`${compose} down`, { stdio: "inherit", cwd: hubDir });
|
|
1647
|
-
console.log(
|
|
639
|
+
console.log(chalk5.green("\nServices stopped\n"));
|
|
1648
640
|
break;
|
|
1649
641
|
case "restart":
|
|
1650
642
|
execSync3(`${compose} restart`, { stdio: "inherit", cwd: hubDir });
|
|
1651
|
-
console.log(
|
|
643
|
+
console.log(chalk5.green("\nServices restarted\n"));
|
|
1652
644
|
break;
|
|
1653
645
|
case "ps":
|
|
1654
646
|
case "status":
|
|
@@ -1662,34 +654,34 @@ var servicesCommand = new Command6("services").description("Manage Docker develo
|
|
|
1662
654
|
}
|
|
1663
655
|
break;
|
|
1664
656
|
case "clean":
|
|
1665
|
-
console.log(
|
|
657
|
+
console.log(chalk5.blue("\nCleaning services (removing volumes)\n"));
|
|
1666
658
|
execSync3(`${compose} down -v`, { stdio: "inherit", cwd: hubDir });
|
|
1667
|
-
console.log(
|
|
659
|
+
console.log(chalk5.green("\nServices and volumes removed\n"));
|
|
1668
660
|
break;
|
|
1669
661
|
default:
|
|
1670
|
-
console.log(
|
|
662
|
+
console.log(chalk5.red(`Unknown action: ${action}`));
|
|
1671
663
|
console.log("Usage: hub services [up|down|restart|ps|logs|clean]");
|
|
1672
664
|
process.exit(1);
|
|
1673
665
|
}
|
|
1674
666
|
} catch {
|
|
1675
|
-
console.log(
|
|
1676
|
-
console.log(
|
|
667
|
+
console.log(chalk5.red("\nFailed to execute docker compose command."));
|
|
668
|
+
console.log(chalk5.dim("Check if Docker is running and the docker-compose.yml is valid.\n"));
|
|
1677
669
|
}
|
|
1678
670
|
});
|
|
1679
671
|
|
|
1680
672
|
// src/commands/skills.ts
|
|
1681
|
-
import { Command as
|
|
1682
|
-
import { existsSync as
|
|
1683
|
-
import { mkdir as
|
|
1684
|
-
import { join as
|
|
673
|
+
import { Command as Command7 } from "commander";
|
|
674
|
+
import { existsSync as existsSync5, statSync } from "fs";
|
|
675
|
+
import { mkdir as mkdir3, readdir, readFile as readFile3, rm, cp } from "fs/promises";
|
|
676
|
+
import { join as join7, resolve } from "path";
|
|
1685
677
|
import { execSync as execSync4 } from "child_process";
|
|
1686
|
-
import
|
|
678
|
+
import chalk7 from "chalk";
|
|
1687
679
|
|
|
1688
680
|
// src/commands/registry.ts
|
|
1689
|
-
import { Command as
|
|
1690
|
-
import { mkdir as
|
|
1691
|
-
import { join as
|
|
1692
|
-
import
|
|
681
|
+
import { Command as Command6 } from "commander";
|
|
682
|
+
import { mkdir as mkdir2, writeFile as writeFile6 } from "fs/promises";
|
|
683
|
+
import { join as join6 } from "path";
|
|
684
|
+
import chalk6 from "chalk";
|
|
1693
685
|
var DEFAULT_REGISTRY_REPO = process.env.HUB_REGISTRY || "arvoreeducacao/rhm";
|
|
1694
686
|
var DEFAULT_BRANCH = "main";
|
|
1695
687
|
async function downloadDirFromGitHub(repo, remotePath, destDir, branch = DEFAULT_BRANCH) {
|
|
@@ -1704,15 +696,15 @@ async function downloadDirFromGitHub(repo, remotePath, destDir, branch = DEFAULT
|
|
|
1704
696
|
throw new Error(`GitHub API error: ${res.status} ${res.statusText}`);
|
|
1705
697
|
}
|
|
1706
698
|
const items = await res.json();
|
|
1707
|
-
await
|
|
699
|
+
await mkdir2(destDir, { recursive: true });
|
|
1708
700
|
for (const item of items) {
|
|
1709
701
|
if (item.type === "file" && item.download_url) {
|
|
1710
702
|
const fileRes = await fetch(item.download_url);
|
|
1711
703
|
if (!fileRes.ok) continue;
|
|
1712
704
|
const content = await fileRes.text();
|
|
1713
|
-
await
|
|
705
|
+
await writeFile6(join6(destDir, item.name), content, "utf-8");
|
|
1714
706
|
} else if (item.type === "dir") {
|
|
1715
|
-
await downloadDirFromGitHub(repo, item.path,
|
|
707
|
+
await downloadDirFromGitHub(repo, item.path, join6(destDir, item.name), branch);
|
|
1716
708
|
}
|
|
1717
709
|
}
|
|
1718
710
|
}
|
|
@@ -1814,10 +806,10 @@ async function listRegistryCommands(repo) {
|
|
|
1814
806
|
return commands;
|
|
1815
807
|
}
|
|
1816
808
|
var TYPE_LABELS = {
|
|
1817
|
-
skill: (t) =>
|
|
1818
|
-
agent: (t) =>
|
|
1819
|
-
hook: (t) =>
|
|
1820
|
-
command: (t) =>
|
|
809
|
+
skill: (t) => chalk6.green(t),
|
|
810
|
+
agent: (t) => chalk6.blue(t),
|
|
811
|
+
hook: (t) => chalk6.magenta(t),
|
|
812
|
+
command: (t) => chalk6.cyan(t)
|
|
1821
813
|
};
|
|
1822
814
|
var INSTALL_HINTS = {
|
|
1823
815
|
skill: "hub skills add <name>",
|
|
@@ -1825,10 +817,10 @@ var INSTALL_HINTS = {
|
|
|
1825
817
|
hook: "hub hooks add <name>",
|
|
1826
818
|
command: "hub commands add <name>"
|
|
1827
819
|
};
|
|
1828
|
-
var registryCommand = new
|
|
1829
|
-
new
|
|
820
|
+
var registryCommand = new Command6("registry").description("Browse and install skills, agents, hooks, and commands from the registry").option("-r, --repo <repo>", "Registry repository (owner/repo)", DEFAULT_REGISTRY_REPO).addCommand(
|
|
821
|
+
new Command6("search").description("Search the registry").argument("[query]", "Search term").option("-t, --type <type>", "Filter by type (skill, agent, hook, command)").action(async (query, opts) => {
|
|
1830
822
|
const repo = registryCommand.opts().repo || DEFAULT_REGISTRY_REPO;
|
|
1831
|
-
console.log(
|
|
823
|
+
console.log(chalk6.blue(`
|
|
1832
824
|
\u2501\u2501\u2501 Hub Registry (${repo}) \u2501\u2501\u2501
|
|
1833
825
|
`));
|
|
1834
826
|
const results = [];
|
|
@@ -1838,7 +830,7 @@ var registryCommand = new Command7("registry").description("Browse and install s
|
|
|
1838
830
|
const skills = await listRegistrySkills(repo);
|
|
1839
831
|
for (const s of skills) results.push({ type: "skill", ...s });
|
|
1840
832
|
} catch {
|
|
1841
|
-
console.log(
|
|
833
|
+
console.log(chalk6.yellow(" Could not fetch skills from registry."));
|
|
1842
834
|
}
|
|
1843
835
|
}
|
|
1844
836
|
if (!typeFilter || typeFilter === "agent") {
|
|
@@ -1846,7 +838,7 @@ var registryCommand = new Command7("registry").description("Browse and install s
|
|
|
1846
838
|
const agents = await listRegistryAgents(repo);
|
|
1847
839
|
for (const a of agents) results.push({ type: "agent", ...a });
|
|
1848
840
|
} catch {
|
|
1849
|
-
console.log(
|
|
841
|
+
console.log(chalk6.yellow(" Could not fetch agents from registry."));
|
|
1850
842
|
}
|
|
1851
843
|
}
|
|
1852
844
|
if (!typeFilter || typeFilter === "hook") {
|
|
@@ -1854,7 +846,7 @@ var registryCommand = new Command7("registry").description("Browse and install s
|
|
|
1854
846
|
const hooks = await listRegistryHooks(repo);
|
|
1855
847
|
for (const h of hooks) results.push({ type: "hook", ...h });
|
|
1856
848
|
} catch {
|
|
1857
|
-
console.log(
|
|
849
|
+
console.log(chalk6.yellow(" Could not fetch hooks from registry."));
|
|
1858
850
|
}
|
|
1859
851
|
}
|
|
1860
852
|
if (!typeFilter || typeFilter === "command") {
|
|
@@ -1862,7 +854,7 @@ var registryCommand = new Command7("registry").description("Browse and install s
|
|
|
1862
854
|
const commands = await listRegistryCommands(repo);
|
|
1863
855
|
for (const c of commands) results.push({ type: "command", ...c });
|
|
1864
856
|
} catch {
|
|
1865
|
-
console.log(
|
|
857
|
+
console.log(chalk6.yellow(" Could not fetch commands from registry."));
|
|
1866
858
|
}
|
|
1867
859
|
}
|
|
1868
860
|
let filtered = results;
|
|
@@ -1873,28 +865,28 @@ var registryCommand = new Command7("registry").description("Browse and install s
|
|
|
1873
865
|
);
|
|
1874
866
|
}
|
|
1875
867
|
if (filtered.length === 0) {
|
|
1876
|
-
console.log(
|
|
868
|
+
console.log(chalk6.dim(" No results found.\n"));
|
|
1877
869
|
return;
|
|
1878
870
|
}
|
|
1879
871
|
for (const entry of filtered) {
|
|
1880
872
|
const labelFn = TYPE_LABELS[entry.type] || ((t) => t);
|
|
1881
|
-
console.log(` ${labelFn(`[${entry.type}]`)} ${
|
|
873
|
+
console.log(` ${labelFn(`[${entry.type}]`)} ${chalk6.yellow(entry.name)}`);
|
|
1882
874
|
if (entry.description) console.log(` ${entry.description}`);
|
|
1883
875
|
console.log();
|
|
1884
876
|
}
|
|
1885
|
-
console.log(
|
|
877
|
+
console.log(chalk6.cyan(` ${filtered.length} result(s)
|
|
1886
878
|
`));
|
|
1887
879
|
const types = [...new Set(filtered.map((e) => e.type))];
|
|
1888
880
|
for (const type of types) {
|
|
1889
881
|
const hint = INSTALL_HINTS[type];
|
|
1890
|
-
if (hint) console.log(
|
|
882
|
+
if (hint) console.log(chalk6.dim(` Install ${type}: ${hint}`));
|
|
1891
883
|
}
|
|
1892
884
|
console.log();
|
|
1893
885
|
})
|
|
1894
886
|
).addCommand(
|
|
1895
|
-
new
|
|
887
|
+
new Command6("list").description("List everything in the registry").option("-t, --type <type>", "Filter by type (skill, agent, hook, command)").action(async (opts) => {
|
|
1896
888
|
const repo = registryCommand.opts().repo || DEFAULT_REGISTRY_REPO;
|
|
1897
|
-
console.log(
|
|
889
|
+
console.log(chalk6.blue(`
|
|
1898
890
|
\u2501\u2501\u2501 Hub Registry (${repo}) \u2501\u2501\u2501
|
|
1899
891
|
`));
|
|
1900
892
|
const typeFilter = opts?.type;
|
|
@@ -1902,56 +894,56 @@ var registryCommand = new Command7("registry").description("Browse and install s
|
|
|
1902
894
|
try {
|
|
1903
895
|
const skills = await listRegistrySkills(repo);
|
|
1904
896
|
if (skills.length) {
|
|
1905
|
-
console.log(
|
|
897
|
+
console.log(chalk6.green(`Skills (${skills.length}):`));
|
|
1906
898
|
for (const s of skills) {
|
|
1907
|
-
console.log(` ${
|
|
899
|
+
console.log(` ${chalk6.yellow(s.name)}${s.description ? ` \u2014 ${s.description}` : ""}`);
|
|
1908
900
|
}
|
|
1909
901
|
console.log();
|
|
1910
902
|
}
|
|
1911
903
|
} catch {
|
|
1912
|
-
console.log(
|
|
904
|
+
console.log(chalk6.yellow(" Could not fetch skills.\n"));
|
|
1913
905
|
}
|
|
1914
906
|
}
|
|
1915
907
|
if (!typeFilter || typeFilter === "agent") {
|
|
1916
908
|
try {
|
|
1917
909
|
const agents = await listRegistryAgents(repo);
|
|
1918
910
|
if (agents.length) {
|
|
1919
|
-
console.log(
|
|
911
|
+
console.log(chalk6.blue(`Agents (${agents.length}):`));
|
|
1920
912
|
for (const a of agents) {
|
|
1921
|
-
console.log(` ${
|
|
913
|
+
console.log(` ${chalk6.yellow(a.name)}${a.description ? ` \u2014 ${a.description}` : ""}`);
|
|
1922
914
|
}
|
|
1923
915
|
console.log();
|
|
1924
916
|
}
|
|
1925
917
|
} catch {
|
|
1926
|
-
console.log(
|
|
918
|
+
console.log(chalk6.yellow(" Could not fetch agents.\n"));
|
|
1927
919
|
}
|
|
1928
920
|
}
|
|
1929
921
|
if (!typeFilter || typeFilter === "hook") {
|
|
1930
922
|
try {
|
|
1931
923
|
const hooks = await listRegistryHooks(repo);
|
|
1932
924
|
if (hooks.length) {
|
|
1933
|
-
console.log(
|
|
925
|
+
console.log(chalk6.magenta(`Hooks (${hooks.length}):`));
|
|
1934
926
|
for (const h of hooks) {
|
|
1935
|
-
console.log(` ${
|
|
927
|
+
console.log(` ${chalk6.yellow(h.name)}${h.description ? ` \u2014 ${h.description}` : ""}`);
|
|
1936
928
|
}
|
|
1937
929
|
console.log();
|
|
1938
930
|
}
|
|
1939
931
|
} catch {
|
|
1940
|
-
console.log(
|
|
932
|
+
console.log(chalk6.yellow(" Could not fetch hooks.\n"));
|
|
1941
933
|
}
|
|
1942
934
|
}
|
|
1943
935
|
if (!typeFilter || typeFilter === "command") {
|
|
1944
936
|
try {
|
|
1945
937
|
const commands = await listRegistryCommands(repo);
|
|
1946
938
|
if (commands.length) {
|
|
1947
|
-
console.log(
|
|
939
|
+
console.log(chalk6.cyan(`Commands (${commands.length}):`));
|
|
1948
940
|
for (const c of commands) {
|
|
1949
|
-
console.log(` ${
|
|
941
|
+
console.log(` ${chalk6.yellow(c.name)}${c.description ? ` \u2014 ${c.description}` : ""}`);
|
|
1950
942
|
}
|
|
1951
943
|
console.log();
|
|
1952
944
|
}
|
|
1953
945
|
} catch {
|
|
1954
|
-
console.log(
|
|
946
|
+
console.log(chalk6.yellow(" Could not fetch commands.\n"));
|
|
1955
947
|
}
|
|
1956
948
|
}
|
|
1957
949
|
})
|
|
@@ -1960,17 +952,17 @@ var registryCommand = new Command7("registry").description("Browse and install s
|
|
|
1960
952
|
// src/commands/skills.ts
|
|
1961
953
|
var DEFAULT_REGISTRY_REPO2 = process.env.HUB_REGISTRY || "arvoreeducacao/rhm";
|
|
1962
954
|
function tmpDir() {
|
|
1963
|
-
return
|
|
955
|
+
return join7(process.env.TMPDIR || "/tmp", `hub-skills-${Date.now()}`);
|
|
1964
956
|
}
|
|
1965
957
|
async function listLocalSkills(hubDir) {
|
|
1966
|
-
const skillsDir =
|
|
958
|
+
const skillsDir = join7(hubDir, "skills");
|
|
1967
959
|
const skills = [];
|
|
1968
|
-
if (!
|
|
1969
|
-
const folders = await
|
|
960
|
+
if (!existsSync5(skillsDir)) return skills;
|
|
961
|
+
const folders = await readdir(skillsDir);
|
|
1970
962
|
for (const folder of folders) {
|
|
1971
|
-
const skillFile =
|
|
1972
|
-
if (!
|
|
1973
|
-
const content = await
|
|
963
|
+
const skillFile = join7(skillsDir, folder, "SKILL.md");
|
|
964
|
+
if (!existsSync5(skillFile)) continue;
|
|
965
|
+
const content = await readFile3(skillFile, "utf-8");
|
|
1974
966
|
const descMatch = content.match(/^description:\s*(.+)$/m);
|
|
1975
967
|
skills.push({
|
|
1976
968
|
name: folder,
|
|
@@ -1980,23 +972,23 @@ async function listLocalSkills(hubDir) {
|
|
|
1980
972
|
return skills;
|
|
1981
973
|
}
|
|
1982
974
|
async function findSkillFolders(rootDir) {
|
|
1983
|
-
const skillsDir =
|
|
1984
|
-
if (
|
|
1985
|
-
const entries = await
|
|
975
|
+
const skillsDir = join7(rootDir, "skills");
|
|
976
|
+
if (existsSync5(skillsDir)) {
|
|
977
|
+
const entries = await readdir(skillsDir);
|
|
1986
978
|
const folders = entries.filter(
|
|
1987
|
-
(f) =>
|
|
979
|
+
(f) => existsSync5(join7(skillsDir, f, "SKILL.md"))
|
|
1988
980
|
);
|
|
1989
981
|
if (folders.length > 0) return { dir: skillsDir, folders };
|
|
1990
982
|
}
|
|
1991
|
-
if (
|
|
1992
|
-
const entries = await
|
|
983
|
+
if (existsSync5(rootDir)) {
|
|
984
|
+
const entries = await readdir(rootDir);
|
|
1993
985
|
const folders = entries.filter(
|
|
1994
|
-
(f) => !f.startsWith(".") && f !== "node_modules" &&
|
|
986
|
+
(f) => !f.startsWith(".") && f !== "node_modules" && existsSync5(join7(rootDir, f, "SKILL.md"))
|
|
1995
987
|
);
|
|
1996
988
|
if (folders.length > 0) return { dir: rootDir, folders };
|
|
1997
989
|
}
|
|
1998
|
-
if (
|
|
1999
|
-
return { dir:
|
|
990
|
+
if (existsSync5(join7(rootDir, "SKILL.md"))) {
|
|
991
|
+
return { dir: join7(rootDir, ".."), folders: [rootDir.split("/").pop()] };
|
|
2000
992
|
}
|
|
2001
993
|
return { dir: skillsDir, folders: [] };
|
|
2002
994
|
}
|
|
@@ -2004,24 +996,24 @@ async function installSkillsFromDir(sourceSkillsDir, hubDir, opts) {
|
|
|
2004
996
|
const rootDir = sourceSkillsDir.endsWith("/skills") ? sourceSkillsDir.replace(/\/skills$/, "") : sourceSkillsDir;
|
|
2005
997
|
const { dir, folders: skillFolders } = await findSkillFolders(rootDir);
|
|
2006
998
|
if (skillFolders.length === 0) {
|
|
2007
|
-
console.log(
|
|
999
|
+
console.log(chalk7.red(" No skills found (looked in skills/, root dirs, and SKILL.md)"));
|
|
2008
1000
|
return;
|
|
2009
1001
|
}
|
|
2010
1002
|
const toInstall = opts.skill ? skillFolders.filter((s) => s === opts.skill) : skillFolders;
|
|
2011
1003
|
if (opts.skill && toInstall.length === 0) {
|
|
2012
|
-
console.log(
|
|
1004
|
+
console.log(chalk7.red(` Skill '${opts.skill}' not found. Available: ${skillFolders.join(", ")}`));
|
|
2013
1005
|
return;
|
|
2014
1006
|
}
|
|
2015
|
-
const targetBase = opts.global ?
|
|
2016
|
-
await
|
|
1007
|
+
const targetBase = opts.global ? join7(process.env.HOME || "~", ".cursor", "skills") : join7(hubDir, "skills");
|
|
1008
|
+
await mkdir3(targetBase, { recursive: true });
|
|
2017
1009
|
for (const skill of toInstall) {
|
|
2018
|
-
const src =
|
|
2019
|
-
const dest =
|
|
2020
|
-
await
|
|
2021
|
-
console.log(
|
|
1010
|
+
const src = join7(dir, skill);
|
|
1011
|
+
const dest = join7(targetBase, skill);
|
|
1012
|
+
await cp(src, dest, { recursive: true });
|
|
1013
|
+
console.log(chalk7.green(` Installed: ${skill}`));
|
|
2022
1014
|
}
|
|
2023
1015
|
console.log(
|
|
2024
|
-
|
|
1016
|
+
chalk7.green(
|
|
2025
1017
|
`
|
|
2026
1018
|
${toInstall.length} skill(s) installed to ${opts.global ? "global" : "project"}
|
|
2027
1019
|
`
|
|
@@ -2031,42 +1023,42 @@ async function installSkillsFromDir(sourceSkillsDir, hubDir, opts) {
|
|
|
2031
1023
|
async function addFromRegistry(skillName, hubDir, opts) {
|
|
2032
1024
|
const repo = opts.repo || DEFAULT_REGISTRY_REPO2;
|
|
2033
1025
|
const remotePath = `skills/${skillName}`;
|
|
2034
|
-
const targetBase = opts.global ?
|
|
2035
|
-
const dest =
|
|
2036
|
-
console.log(
|
|
1026
|
+
const targetBase = opts.global ? join7(process.env.HOME || "~", ".cursor", "skills") : join7(hubDir, "skills");
|
|
1027
|
+
const dest = join7(targetBase, skillName);
|
|
1028
|
+
console.log(chalk7.cyan(` Downloading ${skillName} from ${repo}...`));
|
|
2037
1029
|
try {
|
|
2038
1030
|
await downloadDirFromGitHub(repo, remotePath, dest);
|
|
2039
|
-
if (!
|
|
1031
|
+
if (!existsSync5(join7(dest, "SKILL.md"))) {
|
|
2040
1032
|
await rm(dest, { recursive: true }).catch(() => {
|
|
2041
1033
|
});
|
|
2042
|
-
console.log(
|
|
2043
|
-
console.log(
|
|
1034
|
+
console.log(chalk7.red(` Skill '${skillName}' not found in registry (${repo})`));
|
|
1035
|
+
console.log(chalk7.dim(" Run 'hub registry list' to see available skills."));
|
|
2044
1036
|
return;
|
|
2045
1037
|
}
|
|
2046
|
-
console.log(
|
|
2047
|
-
console.log(
|
|
1038
|
+
console.log(chalk7.green(` Installed: ${skillName}`));
|
|
1039
|
+
console.log(chalk7.green(`
|
|
2048
1040
|
1 skill(s) installed to ${opts.global ? "global" : "project"}
|
|
2049
1041
|
`));
|
|
2050
1042
|
} catch (err) {
|
|
2051
|
-
console.log(
|
|
2052
|
-
console.log(
|
|
1043
|
+
console.log(chalk7.red(` Failed to download skill '${skillName}': ${err.message}`));
|
|
1044
|
+
console.log(chalk7.dim(" Run 'hub registry list' to see available skills."));
|
|
2053
1045
|
}
|
|
2054
1046
|
}
|
|
2055
1047
|
async function addFromGitHubSkill(owner, repo, skillName, hubDir, opts) {
|
|
2056
1048
|
const fullRepo = `${owner}/${repo}`;
|
|
2057
|
-
const targetBase = opts.global ?
|
|
2058
|
-
const dest =
|
|
1049
|
+
const targetBase = opts.global ? join7(process.env.HOME || "~", ".cursor", "skills") : join7(hubDir, "skills");
|
|
1050
|
+
const dest = join7(targetBase, skillName);
|
|
2059
1051
|
const pathsToTry = [
|
|
2060
1052
|
`skills/${skillName}`,
|
|
2061
1053
|
skillName
|
|
2062
1054
|
];
|
|
2063
|
-
console.log(
|
|
1055
|
+
console.log(chalk7.cyan(` Downloading ${skillName} from ${fullRepo} via GitHub API...`));
|
|
2064
1056
|
for (const remotePath of pathsToTry) {
|
|
2065
1057
|
try {
|
|
2066
1058
|
await downloadDirFromGitHub(fullRepo, remotePath, dest);
|
|
2067
|
-
if (
|
|
2068
|
-
console.log(
|
|
2069
|
-
console.log(
|
|
1059
|
+
if (existsSync5(join7(dest, "SKILL.md"))) {
|
|
1060
|
+
console.log(chalk7.green(` Installed: ${skillName} (from ${fullRepo})`));
|
|
1061
|
+
console.log(chalk7.green(`
|
|
2070
1062
|
1 skill(s) installed to ${opts.global ? "global" : "project"}
|
|
2071
1063
|
`));
|
|
2072
1064
|
return;
|
|
@@ -2078,12 +1070,12 @@ async function addFromGitHubSkill(owner, repo, skillName, hubDir, opts) {
|
|
|
2078
1070
|
});
|
|
2079
1071
|
}
|
|
2080
1072
|
}
|
|
2081
|
-
console.log(
|
|
2082
|
-
console.log(
|
|
1073
|
+
console.log(chalk7.red(` Skill '${skillName}' not found in ${fullRepo}`));
|
|
1074
|
+
console.log(chalk7.dim(` Check available skills: hub skills add ${fullRepo} --list`));
|
|
2083
1075
|
}
|
|
2084
1076
|
async function listRemoteSkills(owner, repo) {
|
|
2085
1077
|
const fullRepo = `${owner}/${repo}`;
|
|
2086
|
-
console.log(
|
|
1078
|
+
console.log(chalk7.cyan(` Fetching skills from ${fullRepo}...
|
|
2087
1079
|
`));
|
|
2088
1080
|
const headers = { Accept: "application/vnd.github.v3+json" };
|
|
2089
1081
|
try {
|
|
@@ -2121,38 +1113,38 @@ async function listRemoteSkills(owner, repo) {
|
|
|
2121
1113
|
{ headers }
|
|
2122
1114
|
);
|
|
2123
1115
|
if (rootSkill.ok) {
|
|
2124
|
-
console.log(
|
|
1116
|
+
console.log(chalk7.green(` This repo is a single skill.
|
|
2125
1117
|
`));
|
|
2126
|
-
console.log(
|
|
1118
|
+
console.log(chalk7.dim(` Install with: hub skills add ${fullRepo}
|
|
2127
1119
|
`));
|
|
2128
1120
|
return;
|
|
2129
1121
|
}
|
|
2130
1122
|
}
|
|
2131
1123
|
if (dirs.length === 0) {
|
|
2132
|
-
console.log(
|
|
1124
|
+
console.log(chalk7.dim(" No skills found."));
|
|
2133
1125
|
return;
|
|
2134
1126
|
}
|
|
2135
|
-
console.log(
|
|
1127
|
+
console.log(chalk7.green(` Available skills (${dirs.length}):
|
|
2136
1128
|
`));
|
|
2137
1129
|
for (const dir of dirs) {
|
|
2138
|
-
console.log(` ${
|
|
1130
|
+
console.log(` ${chalk7.yellow(dir.name)}`);
|
|
2139
1131
|
}
|
|
2140
|
-
console.log(
|
|
1132
|
+
console.log(chalk7.dim(`
|
|
2141
1133
|
Install with: hub skills add ${fullRepo}/<skill-name>`));
|
|
2142
|
-
console.log(
|
|
1134
|
+
console.log(chalk7.dim(` Install all: hub skills add ${fullRepo}
|
|
2143
1135
|
`));
|
|
2144
1136
|
} catch {
|
|
2145
|
-
console.log(
|
|
1137
|
+
console.log(chalk7.red(` Failed to fetch skill list from ${fullRepo}`));
|
|
2146
1138
|
}
|
|
2147
1139
|
}
|
|
2148
1140
|
async function addFromLocalPath(localPath, hubDir, opts) {
|
|
2149
|
-
const absPath =
|
|
2150
|
-
if (!
|
|
2151
|
-
console.log(
|
|
1141
|
+
const absPath = resolve(localPath);
|
|
1142
|
+
if (!existsSync5(absPath)) {
|
|
1143
|
+
console.log(chalk7.red(` Path not found: ${absPath}`));
|
|
2152
1144
|
return;
|
|
2153
1145
|
}
|
|
2154
1146
|
if (!statSync(absPath).isDirectory()) {
|
|
2155
|
-
console.log(
|
|
1147
|
+
console.log(chalk7.red(` Path is not a directory: ${absPath}`));
|
|
2156
1148
|
return;
|
|
2157
1149
|
}
|
|
2158
1150
|
await installSkillsFromDir(absPath, hubDir, opts);
|
|
@@ -2160,19 +1152,19 @@ async function addFromLocalPath(localPath, hubDir, opts) {
|
|
|
2160
1152
|
async function addFromGitRepo(source, hubDir, opts) {
|
|
2161
1153
|
const tmp = tmpDir();
|
|
2162
1154
|
try {
|
|
2163
|
-
console.log(
|
|
1155
|
+
console.log(chalk7.cyan(` Cloning ${source}...`));
|
|
2164
1156
|
try {
|
|
2165
1157
|
execSync4(`git clone --depth 1 ${source} ${tmp}`, {
|
|
2166
1158
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2167
1159
|
});
|
|
2168
1160
|
} catch {
|
|
2169
|
-
console.log(
|
|
2170
|
-
console.log(
|
|
1161
|
+
console.log(chalk7.red(` Repository not found or not accessible: ${source}`));
|
|
1162
|
+
console.log(chalk7.dim(" Make sure the URL is correct and you have access to the repository."));
|
|
2171
1163
|
return;
|
|
2172
1164
|
}
|
|
2173
1165
|
await installSkillsFromDir(tmp, hubDir, opts);
|
|
2174
1166
|
} finally {
|
|
2175
|
-
if (
|
|
1167
|
+
if (existsSync5(tmp)) {
|
|
2176
1168
|
await rm(tmp, { recursive: true });
|
|
2177
1169
|
}
|
|
2178
1170
|
}
|
|
@@ -2186,124 +1178,130 @@ function parseGitHubSource(source) {
|
|
|
2186
1178
|
if (parts.length === 3) return { owner: parts[0], repo: parts[1], skill: parts[2] };
|
|
2187
1179
|
return null;
|
|
2188
1180
|
}
|
|
2189
|
-
var skillsCommand = new
|
|
2190
|
-
new
|
|
1181
|
+
var skillsCommand = new Command7("skills").description("Manage agent skills").addCommand(
|
|
1182
|
+
new Command7("add").description("Install skills from registry, GitHub, git URL, or local path").argument("<source>", "Skill name, owner/repo, owner/repo/skill, git URL, or local path").option("-s, --skill <name>", "Install a specific skill only (for repo sources)").option("-g, --global", "Install to global ~/.cursor/skills/").option("-r, --repo <repo>", "Registry repository (owner/repo)").option("-l, --list", "List available skills without installing").action(async (source, opts) => {
|
|
2191
1183
|
const hubDir = process.cwd();
|
|
2192
1184
|
if (isLocalPath(source)) {
|
|
2193
|
-
console.log(
|
|
1185
|
+
console.log(chalk7.blue(`
|
|
2194
1186
|
Installing skills from ${source}
|
|
2195
1187
|
`));
|
|
2196
1188
|
await addFromLocalPath(source, hubDir, opts);
|
|
1189
|
+
if (!opts.global) await checkAndAutoRegenerate(hubDir);
|
|
2197
1190
|
return;
|
|
2198
1191
|
}
|
|
2199
1192
|
if (source.startsWith("git@") || source.startsWith("https://")) {
|
|
2200
|
-
console.log(
|
|
1193
|
+
console.log(chalk7.blue(`
|
|
2201
1194
|
Installing skills from ${source}
|
|
2202
1195
|
`));
|
|
2203
1196
|
await addFromGitRepo(source, hubDir, opts);
|
|
1197
|
+
if (!opts.global) await checkAndAutoRegenerate(hubDir);
|
|
2204
1198
|
return;
|
|
2205
1199
|
}
|
|
2206
1200
|
const parsed = parseGitHubSource(source);
|
|
2207
1201
|
if (parsed && opts.list) {
|
|
2208
|
-
console.log(
|
|
1202
|
+
console.log(chalk7.blue(`
|
|
2209
1203
|
Listing skills from ${parsed.owner}/${parsed.repo}
|
|
2210
1204
|
`));
|
|
2211
1205
|
await listRemoteSkills(parsed.owner, parsed.repo);
|
|
2212
1206
|
return;
|
|
2213
1207
|
}
|
|
2214
1208
|
if (parsed?.skill) {
|
|
2215
|
-
console.log(
|
|
1209
|
+
console.log(chalk7.blue(`
|
|
2216
1210
|
Installing skill ${parsed.skill} from ${parsed.owner}/${parsed.repo}
|
|
2217
1211
|
`));
|
|
2218
1212
|
await addFromGitHubSkill(parsed.owner, parsed.repo, parsed.skill, hubDir, opts);
|
|
1213
|
+
if (!opts.global) await checkAndAutoRegenerate(hubDir);
|
|
2219
1214
|
return;
|
|
2220
1215
|
}
|
|
2221
1216
|
if (parsed && !parsed.skill) {
|
|
2222
|
-
console.log(
|
|
1217
|
+
console.log(chalk7.blue(`
|
|
2223
1218
|
Installing skills from ${source}
|
|
2224
1219
|
`));
|
|
2225
1220
|
const url = `https://github.com/${source}.git`;
|
|
2226
1221
|
await addFromGitRepo(url, hubDir, opts);
|
|
1222
|
+
if (!opts.global) await checkAndAutoRegenerate(hubDir);
|
|
2227
1223
|
return;
|
|
2228
1224
|
}
|
|
2229
|
-
console.log(
|
|
1225
|
+
console.log(chalk7.blue(`
|
|
2230
1226
|
Installing skill ${source} from registry
|
|
2231
1227
|
`));
|
|
2232
1228
|
await addFromRegistry(source, hubDir, opts);
|
|
1229
|
+
if (!opts.global) await checkAndAutoRegenerate(hubDir);
|
|
2233
1230
|
})
|
|
2234
1231
|
).addCommand(
|
|
2235
|
-
new
|
|
1232
|
+
new Command7("find").description("Browse curated skills in the Repo Hub directory").argument("[query]", "Search term").action(async (query) => {
|
|
2236
1233
|
const base = "https://rhm-website.vercel.app/directory?type=skill";
|
|
2237
1234
|
const url = query ? `${base}&q=${encodeURIComponent(query)}` : base;
|
|
2238
|
-
console.log(
|
|
2239
|
-
console.log(
|
|
1235
|
+
console.log(chalk7.blue("\n Browse curated skills at:\n"));
|
|
1236
|
+
console.log(chalk7.cyan(` ${url}
|
|
2240
1237
|
`));
|
|
2241
|
-
console.log(
|
|
2242
|
-
console.log(
|
|
1238
|
+
console.log(chalk7.dim(" Install with: hub skills add <owner>/<repo>/<skill-name>"));
|
|
1239
|
+
console.log(chalk7.dim(" Example: hub skills add vercel-labs/agent-skills/react-best-practices\n"));
|
|
2243
1240
|
})
|
|
2244
1241
|
).addCommand(
|
|
2245
|
-
new
|
|
1242
|
+
new Command7("list").description("List installed skills").action(async () => {
|
|
2246
1243
|
const hubDir = process.cwd();
|
|
2247
|
-
console.log(
|
|
1244
|
+
console.log(chalk7.blue("\nInstalled skills\n"));
|
|
2248
1245
|
const projectSkills = await listLocalSkills(hubDir);
|
|
2249
1246
|
if (projectSkills.length > 0) {
|
|
2250
|
-
console.log(
|
|
1247
|
+
console.log(chalk7.cyan("Project:"));
|
|
2251
1248
|
for (const s of projectSkills) {
|
|
2252
|
-
console.log(` ${
|
|
1249
|
+
console.log(` ${chalk7.yellow(s.name)} \u2014 ${s.description}`);
|
|
2253
1250
|
}
|
|
2254
1251
|
} else {
|
|
2255
|
-
console.log(
|
|
1252
|
+
console.log(chalk7.dim(" No project skills (skills/)"));
|
|
2256
1253
|
}
|
|
2257
|
-
const globalDir =
|
|
2258
|
-
const globalSkills = await listLocalSkills(
|
|
1254
|
+
const globalDir = join7(process.env.HOME || "~", ".cursor", "skills");
|
|
1255
|
+
const globalSkills = await listLocalSkills(join7(globalDir, ".."));
|
|
2259
1256
|
console.log();
|
|
2260
1257
|
if (globalSkills.length > 0) {
|
|
2261
|
-
console.log(
|
|
1258
|
+
console.log(chalk7.cyan("Global:"));
|
|
2262
1259
|
for (const s of globalSkills) {
|
|
2263
|
-
console.log(` ${
|
|
1260
|
+
console.log(` ${chalk7.yellow(s.name)} \u2014 ${s.description}`);
|
|
2264
1261
|
}
|
|
2265
1262
|
} else {
|
|
2266
|
-
console.log(
|
|
1263
|
+
console.log(chalk7.dim(" No global skills (~/.cursor/skills/)"));
|
|
2267
1264
|
}
|
|
2268
1265
|
console.log();
|
|
2269
1266
|
})
|
|
2270
1267
|
).addCommand(
|
|
2271
|
-
new
|
|
1268
|
+
new Command7("remove").description("Remove a skill").argument("<name>", "Skill name to remove").option("-g, --global", "Remove from global skills").action(async (name, opts) => {
|
|
2272
1269
|
const hubDir = process.cwd();
|
|
2273
|
-
const base = opts.global ?
|
|
2274
|
-
const target =
|
|
2275
|
-
if (!
|
|
2276
|
-
console.log(
|
|
1270
|
+
const base = opts.global ? join7(process.env.HOME || "~", ".cursor", "skills") : join7(hubDir, "skills");
|
|
1271
|
+
const target = join7(base, name);
|
|
1272
|
+
if (!existsSync5(target)) {
|
|
1273
|
+
console.log(chalk7.red(`
|
|
2277
1274
|
Skill '${name}' not found in ${opts.global ? "global" : "project"}
|
|
2278
1275
|
`));
|
|
2279
1276
|
return;
|
|
2280
1277
|
}
|
|
2281
1278
|
await rm(target, { recursive: true });
|
|
2282
|
-
console.log(
|
|
1279
|
+
console.log(chalk7.green(`
|
|
2283
1280
|
Removed skill: ${name}
|
|
2284
1281
|
`));
|
|
1282
|
+
if (!opts.global) await checkAndAutoRegenerate(hubDir);
|
|
2285
1283
|
})
|
|
2286
1284
|
);
|
|
2287
1285
|
|
|
2288
1286
|
// src/commands/agents.ts
|
|
2289
|
-
import { Command as
|
|
2290
|
-
import { existsSync as
|
|
2291
|
-
import { mkdir as
|
|
2292
|
-
import { join as
|
|
1287
|
+
import { Command as Command8 } from "commander";
|
|
1288
|
+
import { existsSync as existsSync6, statSync as statSync2 } from "fs";
|
|
1289
|
+
import { mkdir as mkdir4, readdir as readdir2, readFile as readFile4, rm as rm2, copyFile, writeFile as writeFile7 } from "fs/promises";
|
|
1290
|
+
import { join as join8, resolve as resolve2 } from "path";
|
|
2293
1291
|
import { execSync as execSync5 } from "child_process";
|
|
2294
|
-
import
|
|
1292
|
+
import chalk8 from "chalk";
|
|
2295
1293
|
var DEFAULT_REGISTRY_REPO3 = process.env.HUB_REGISTRY || "arvoreeducacao/rhm";
|
|
2296
1294
|
var DEFAULT_BRANCH2 = "main";
|
|
2297
1295
|
function tmpDir2() {
|
|
2298
|
-
return
|
|
1296
|
+
return join8(process.env.TMPDIR || "/tmp", `hub-agents-${Date.now()}`);
|
|
2299
1297
|
}
|
|
2300
1298
|
async function listLocalAgents(agentsDir) {
|
|
2301
1299
|
const agents = [];
|
|
2302
|
-
if (!
|
|
2303
|
-
const files = await
|
|
1300
|
+
if (!existsSync6(agentsDir)) return agents;
|
|
1301
|
+
const files = await readdir2(agentsDir);
|
|
2304
1302
|
for (const file of files) {
|
|
2305
1303
|
if (!file.endsWith(".md")) continue;
|
|
2306
|
-
const content = await
|
|
1304
|
+
const content = await readFile4(join8(agentsDir, file), "utf-8");
|
|
2307
1305
|
const descMatch = content.match(/^description:\s*(.+)$/m);
|
|
2308
1306
|
agents.push({
|
|
2309
1307
|
name: file.replace(/\.md$/, ""),
|
|
@@ -2316,47 +1314,47 @@ async function addFromRegistry2(agentName, hubDir, opts) {
|
|
|
2316
1314
|
const repo = opts.repo || DEFAULT_REGISTRY_REPO3;
|
|
2317
1315
|
const fileName = agentName.endsWith(".md") ? agentName : `${agentName}.md`;
|
|
2318
1316
|
const rawUrl = `https://raw.githubusercontent.com/${repo}/${DEFAULT_BRANCH2}/agents/${fileName}`;
|
|
2319
|
-
console.log(
|
|
1317
|
+
console.log(chalk8.cyan(` Downloading ${agentName} from ${repo}...`));
|
|
2320
1318
|
try {
|
|
2321
1319
|
const res = await fetch(rawUrl);
|
|
2322
1320
|
if (!res.ok) {
|
|
2323
|
-
console.log(
|
|
2324
|
-
console.log(
|
|
1321
|
+
console.log(chalk8.red(` Agent '${agentName}' not found in registry (${repo})`));
|
|
1322
|
+
console.log(chalk8.dim(" Run 'hub registry list' to see available agents."));
|
|
2325
1323
|
return;
|
|
2326
1324
|
}
|
|
2327
1325
|
const content = await res.text();
|
|
2328
|
-
const targetBase = opts.global ?
|
|
2329
|
-
await
|
|
2330
|
-
await
|
|
2331
|
-
console.log(
|
|
2332
|
-
console.log(
|
|
1326
|
+
const targetBase = opts.global ? join8(process.env.HOME || "~", ".cursor", "agents") : join8(hubDir, "agents");
|
|
1327
|
+
await mkdir4(targetBase, { recursive: true });
|
|
1328
|
+
await writeFile7(join8(targetBase, fileName), content, "utf-8");
|
|
1329
|
+
console.log(chalk8.green(` Installed: ${agentName}`));
|
|
1330
|
+
console.log(chalk8.green(`
|
|
2333
1331
|
1 agent(s) installed to ${opts.global ? "global" : "project"}
|
|
2334
1332
|
`));
|
|
2335
1333
|
} catch (err) {
|
|
2336
|
-
console.log(
|
|
1334
|
+
console.log(chalk8.red(` Failed to download agent '${agentName}': ${err.message}`));
|
|
2337
1335
|
}
|
|
2338
1336
|
}
|
|
2339
1337
|
async function addFromLocalPath2(localPath, hubDir, opts) {
|
|
2340
|
-
const absPath =
|
|
2341
|
-
if (!
|
|
2342
|
-
console.log(
|
|
1338
|
+
const absPath = resolve2(localPath);
|
|
1339
|
+
if (!existsSync6(absPath)) {
|
|
1340
|
+
console.log(chalk8.red(` Path not found: ${absPath}`));
|
|
2343
1341
|
return;
|
|
2344
1342
|
}
|
|
2345
1343
|
if (!statSync2(absPath).isDirectory()) {
|
|
2346
|
-
console.log(
|
|
1344
|
+
console.log(chalk8.red(` Path is not a directory: ${absPath}`));
|
|
2347
1345
|
return;
|
|
2348
1346
|
}
|
|
2349
1347
|
await installAgentsFromDir(absPath, hubDir, opts);
|
|
2350
1348
|
}
|
|
2351
1349
|
async function findAgentFiles(rootDir) {
|
|
2352
|
-
const agentsDir =
|
|
2353
|
-
if (
|
|
2354
|
-
const entries = await
|
|
1350
|
+
const agentsDir = join8(rootDir, "agents");
|
|
1351
|
+
if (existsSync6(agentsDir)) {
|
|
1352
|
+
const entries = await readdir2(agentsDir);
|
|
2355
1353
|
const mdFiles = entries.filter((f) => f.endsWith(".md"));
|
|
2356
1354
|
if (mdFiles.length > 0) return { dir: agentsDir, files: mdFiles };
|
|
2357
1355
|
}
|
|
2358
|
-
if (
|
|
2359
|
-
const entries = await
|
|
1356
|
+
if (existsSync6(rootDir)) {
|
|
1357
|
+
const entries = await readdir2(rootDir);
|
|
2360
1358
|
const mdFiles = entries.filter(
|
|
2361
1359
|
(f) => f.endsWith(".md") && !f.startsWith(".") && f !== "README.md" && f !== "CHANGELOG.md"
|
|
2362
1360
|
);
|
|
@@ -2368,23 +1366,23 @@ async function installAgentsFromDir(sourceDir, hubDir, opts) {
|
|
|
2368
1366
|
const rootDir = sourceDir.endsWith("/agents") ? sourceDir.replace(/\/agents$/, "") : sourceDir;
|
|
2369
1367
|
const { dir, files: mdFiles } = await findAgentFiles(rootDir);
|
|
2370
1368
|
if (mdFiles.length === 0) {
|
|
2371
|
-
console.log(
|
|
1369
|
+
console.log(chalk8.red(" No agent files found (looked in agents/ and root .md files)"));
|
|
2372
1370
|
return;
|
|
2373
1371
|
}
|
|
2374
1372
|
const toInstall = opts.agent ? mdFiles.filter((f) => f === `${opts.agent}.md` || f === opts.agent) : mdFiles;
|
|
2375
1373
|
if (opts.agent && toInstall.length === 0) {
|
|
2376
1374
|
const available = mdFiles.map((f) => f.replace(/\.md$/, "")).join(", ");
|
|
2377
|
-
console.log(
|
|
1375
|
+
console.log(chalk8.red(` Agent '${opts.agent}' not found. Available: ${available}`));
|
|
2378
1376
|
return;
|
|
2379
1377
|
}
|
|
2380
|
-
const targetBase = opts.global ?
|
|
2381
|
-
await
|
|
1378
|
+
const targetBase = opts.global ? join8(process.env.HOME || "~", ".cursor", "agents") : join8(hubDir, "agents");
|
|
1379
|
+
await mkdir4(targetBase, { recursive: true });
|
|
2382
1380
|
for (const file of toInstall) {
|
|
2383
|
-
await
|
|
2384
|
-
console.log(
|
|
1381
|
+
await copyFile(join8(dir, file), join8(targetBase, file));
|
|
1382
|
+
console.log(chalk8.green(` Installed: ${file.replace(/\.md$/, "")}`));
|
|
2385
1383
|
}
|
|
2386
1384
|
console.log(
|
|
2387
|
-
|
|
1385
|
+
chalk8.green(
|
|
2388
1386
|
`
|
|
2389
1387
|
${toInstall.length} agent(s) installed to ${opts.global ? "global" : "project"}
|
|
2390
1388
|
`
|
|
@@ -2394,18 +1392,18 @@ async function installAgentsFromDir(sourceDir, hubDir, opts) {
|
|
|
2394
1392
|
async function addFromGitRepo2(source, hubDir, opts) {
|
|
2395
1393
|
const tmp = tmpDir2();
|
|
2396
1394
|
try {
|
|
2397
|
-
console.log(
|
|
1395
|
+
console.log(chalk8.cyan(` Cloning ${source}...`));
|
|
2398
1396
|
try {
|
|
2399
1397
|
execSync5(`git clone --depth 1 ${source} ${tmp}`, {
|
|
2400
1398
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2401
1399
|
});
|
|
2402
1400
|
} catch {
|
|
2403
|
-
console.log(
|
|
1401
|
+
console.log(chalk8.red(` Repository not found or not accessible: ${source}`));
|
|
2404
1402
|
return;
|
|
2405
1403
|
}
|
|
2406
1404
|
await installAgentsFromDir(tmp, hubDir, opts);
|
|
2407
1405
|
} finally {
|
|
2408
|
-
if (
|
|
1406
|
+
if (existsSync6(tmp)) {
|
|
2409
1407
|
await rm2(tmp, { recursive: true });
|
|
2410
1408
|
}
|
|
2411
1409
|
}
|
|
@@ -2416,10 +1414,10 @@ function isLocalPath2(source) {
|
|
|
2416
1414
|
function isRepoReference(source) {
|
|
2417
1415
|
return source.startsWith("git@") || source.startsWith("https://") || source.includes("/");
|
|
2418
1416
|
}
|
|
2419
|
-
var agentsCommand = new
|
|
2420
|
-
new
|
|
1417
|
+
var agentsCommand = new Command8("agents").description("Manage agent definitions").addCommand(
|
|
1418
|
+
new Command8("add").description("Install agents from the registry, a git repository, or local path").argument("<source>", "Agent name (from registry), GitHub shorthand (org/repo), git URL, or local path").option("-a, --agent <name>", "Install a specific agent only (for repo sources)").option("-g, --global", "Install to global ~/.cursor/agents/").option("-r, --repo <repo>", "Registry repository (owner/repo)").action(async (source, opts) => {
|
|
2421
1419
|
const hubDir = process.cwd();
|
|
2422
|
-
console.log(
|
|
1420
|
+
console.log(chalk8.blue(`
|
|
2423
1421
|
Installing agents from ${source}
|
|
2424
1422
|
`));
|
|
2425
1423
|
if (isLocalPath2(source)) {
|
|
@@ -2434,68 +1432,70 @@ Installing agents from ${source}
|
|
|
2434
1432
|
} else {
|
|
2435
1433
|
await addFromRegistry2(source, hubDir, opts);
|
|
2436
1434
|
}
|
|
1435
|
+
if (!opts.global) await checkAndAutoRegenerate(hubDir);
|
|
2437
1436
|
})
|
|
2438
1437
|
).addCommand(
|
|
2439
|
-
new
|
|
1438
|
+
new Command8("list").description("List installed agents").action(async () => {
|
|
2440
1439
|
const hubDir = process.cwd();
|
|
2441
|
-
console.log(
|
|
2442
|
-
const projectAgents = await listLocalAgents(
|
|
1440
|
+
console.log(chalk8.blue("\nInstalled agents\n"));
|
|
1441
|
+
const projectAgents = await listLocalAgents(join8(hubDir, "agents"));
|
|
2443
1442
|
if (projectAgents.length > 0) {
|
|
2444
|
-
console.log(
|
|
1443
|
+
console.log(chalk8.cyan("Project:"));
|
|
2445
1444
|
for (const a of projectAgents) {
|
|
2446
|
-
console.log(` ${
|
|
1445
|
+
console.log(` ${chalk8.yellow(a.name)}${a.description ? ` \u2014 ${a.description}` : ""}`);
|
|
2447
1446
|
}
|
|
2448
1447
|
} else {
|
|
2449
|
-
console.log(
|
|
1448
|
+
console.log(chalk8.dim(" No project agents (agents/)"));
|
|
2450
1449
|
}
|
|
2451
|
-
const globalDir =
|
|
1450
|
+
const globalDir = join8(process.env.HOME || "~", ".cursor", "agents");
|
|
2452
1451
|
const globalAgents = await listLocalAgents(globalDir);
|
|
2453
1452
|
console.log();
|
|
2454
1453
|
if (globalAgents.length > 0) {
|
|
2455
|
-
console.log(
|
|
1454
|
+
console.log(chalk8.cyan("Global:"));
|
|
2456
1455
|
for (const a of globalAgents) {
|
|
2457
|
-
console.log(` ${
|
|
1456
|
+
console.log(` ${chalk8.yellow(a.name)}${a.description ? ` \u2014 ${a.description}` : ""}`);
|
|
2458
1457
|
}
|
|
2459
1458
|
} else {
|
|
2460
|
-
console.log(
|
|
1459
|
+
console.log(chalk8.dim(" No global agents (~/.cursor/agents/)"));
|
|
2461
1460
|
}
|
|
2462
1461
|
console.log();
|
|
2463
1462
|
})
|
|
2464
1463
|
).addCommand(
|
|
2465
|
-
new
|
|
1464
|
+
new Command8("remove").description("Remove an agent").argument("<name>", "Agent name to remove").option("-g, --global", "Remove from global agents").action(async (name, opts) => {
|
|
2466
1465
|
const hubDir = process.cwd();
|
|
2467
|
-
const base = opts.global ?
|
|
1466
|
+
const base = opts.global ? join8(process.env.HOME || "~", ".cursor", "agents") : join8(hubDir, "agents");
|
|
2468
1467
|
const fileName = name.endsWith(".md") ? name : `${name}.md`;
|
|
2469
|
-
const target =
|
|
2470
|
-
if (!
|
|
2471
|
-
console.log(
|
|
1468
|
+
const target = join8(base, fileName);
|
|
1469
|
+
if (!existsSync6(target)) {
|
|
1470
|
+
console.log(chalk8.red(`
|
|
2472
1471
|
Agent '${name}' not found in ${opts.global ? "global" : "project"}
|
|
2473
1472
|
`));
|
|
2474
1473
|
return;
|
|
2475
1474
|
}
|
|
2476
1475
|
await rm2(target);
|
|
2477
|
-
console.log(
|
|
1476
|
+
console.log(chalk8.green(`
|
|
2478
1477
|
Removed agent: ${name}
|
|
2479
1478
|
`));
|
|
1479
|
+
if (!opts.global) await checkAndAutoRegenerate(hubDir);
|
|
2480
1480
|
})
|
|
2481
1481
|
).addCommand(
|
|
2482
|
-
new
|
|
1482
|
+
new Command8("find").description("Browse curated agents in the Repo Hub directory").argument("[query]", "Search term").action(async (query) => {
|
|
2483
1483
|
const base = "https://rhm-website.vercel.app/directory?type=agent";
|
|
2484
1484
|
const url = query ? `${base}&q=${encodeURIComponent(query)}` : base;
|
|
2485
|
-
console.log(
|
|
2486
|
-
console.log(
|
|
1485
|
+
console.log(chalk8.blue("\n Browse curated agents at:\n"));
|
|
1486
|
+
console.log(chalk8.cyan(` ${url}
|
|
2487
1487
|
`));
|
|
2488
|
-
console.log(
|
|
2489
|
-
console.log(
|
|
1488
|
+
console.log(chalk8.dim(" Install with: hub agents add <owner>/<repo>"));
|
|
1489
|
+
console.log(chalk8.dim(" Example: hub agents add my-org/my-agents\n"));
|
|
2490
1490
|
})
|
|
2491
1491
|
).addCommand(
|
|
2492
|
-
new
|
|
1492
|
+
new Command8("sync").description("Install all agents referenced in hub.yaml from the registry").option("-g, --global", "Install to global ~/.cursor/agents/").option("-r, --repo <repo>", "Registry repository (owner/repo)").option("-f, --force", "Re-install even if the agent already exists locally").action(async (opts) => {
|
|
2493
1493
|
const hubDir = process.cwd();
|
|
2494
1494
|
let config;
|
|
2495
1495
|
try {
|
|
2496
1496
|
config = await loadHubConfig(hubDir);
|
|
2497
1497
|
} catch {
|
|
2498
|
-
console.log(
|
|
1498
|
+
console.log(chalk8.red("\n Could not load hub.yaml in the current directory.\n"));
|
|
2499
1499
|
return;
|
|
2500
1500
|
}
|
|
2501
1501
|
const steps = config.workflow?.pipeline || [];
|
|
@@ -2509,20 +1509,20 @@ Removed agent: ${name}
|
|
|
2509
1509
|
}
|
|
2510
1510
|
}
|
|
2511
1511
|
if (agentNames.size === 0) {
|
|
2512
|
-
console.log(
|
|
1512
|
+
console.log(chalk8.yellow("\n No agents found in hub.yaml workflow pipeline.\n"));
|
|
2513
1513
|
return;
|
|
2514
1514
|
}
|
|
2515
|
-
console.log(
|
|
1515
|
+
console.log(chalk8.blue(`
|
|
2516
1516
|
\u2501\u2501\u2501 Syncing ${agentNames.size} agent(s) from hub.yaml \u2501\u2501\u2501
|
|
2517
1517
|
`));
|
|
2518
|
-
const targetBase = opts.global ?
|
|
1518
|
+
const targetBase = opts.global ? join8(process.env.HOME || "~", ".cursor", "agents") : join8(hubDir, "agents");
|
|
2519
1519
|
let installed = 0;
|
|
2520
1520
|
let skipped = 0;
|
|
2521
1521
|
for (const name of agentNames) {
|
|
2522
1522
|
const fileName = name.endsWith(".md") ? name : `${name}.md`;
|
|
2523
|
-
const targetPath =
|
|
2524
|
-
if (!opts.force &&
|
|
2525
|
-
console.log(
|
|
1523
|
+
const targetPath = join8(targetBase, fileName);
|
|
1524
|
+
if (!opts.force && existsSync6(targetPath)) {
|
|
1525
|
+
console.log(chalk8.dim(` \u2713 ${name} (already installed)`));
|
|
2526
1526
|
skipped++;
|
|
2527
1527
|
continue;
|
|
2528
1528
|
}
|
|
@@ -2530,40 +1530,41 @@ Removed agent: ${name}
|
|
|
2530
1530
|
installed++;
|
|
2531
1531
|
}
|
|
2532
1532
|
console.log();
|
|
2533
|
-
if (installed > 0) console.log(
|
|
2534
|
-
if (skipped > 0) console.log(
|
|
1533
|
+
if (installed > 0) console.log(chalk8.green(` ${installed} agent(s) installed`));
|
|
1534
|
+
if (skipped > 0) console.log(chalk8.dim(` ${skipped} agent(s) already up to date`));
|
|
2535
1535
|
if (installed === 0 && skipped > 0) {
|
|
2536
|
-
console.log(
|
|
1536
|
+
console.log(chalk8.green(" All agents are already installed. Use --force to re-install."));
|
|
2537
1537
|
}
|
|
2538
1538
|
console.log();
|
|
1539
|
+
if (!opts.global && installed > 0) await checkAndAutoRegenerate(hubDir);
|
|
2539
1540
|
})
|
|
2540
1541
|
);
|
|
2541
1542
|
|
|
2542
1543
|
// src/commands/hooks.ts
|
|
2543
|
-
import { Command as
|
|
2544
|
-
import { existsSync as
|
|
2545
|
-
import { mkdir as
|
|
2546
|
-
import { join as
|
|
1544
|
+
import { Command as Command9 } from "commander";
|
|
1545
|
+
import { existsSync as existsSync7, statSync as statSync3 } from "fs";
|
|
1546
|
+
import { mkdir as mkdir5, readdir as readdir3, readFile as readFile5, rm as rm3, copyFile as copyFile2, cp as cp2 } from "fs/promises";
|
|
1547
|
+
import { join as join9, resolve as resolve3 } from "path";
|
|
2547
1548
|
import { execSync as execSync6 } from "child_process";
|
|
2548
|
-
import
|
|
1549
|
+
import chalk9 from "chalk";
|
|
2549
1550
|
var DEFAULT_REGISTRY_REPO4 = process.env.HUB_REGISTRY || "arvoreeducacao/rhm";
|
|
2550
1551
|
function tmpDir3() {
|
|
2551
|
-
return
|
|
1552
|
+
return join9(process.env.TMPDIR || "/tmp", `hub-hooks-${Date.now()}`);
|
|
2552
1553
|
}
|
|
2553
1554
|
async function listLocalHooks(hooksDir) {
|
|
2554
1555
|
const hooks = [];
|
|
2555
|
-
if (!
|
|
2556
|
-
const entries = await
|
|
1556
|
+
if (!existsSync7(hooksDir)) return hooks;
|
|
1557
|
+
const entries = await readdir3(hooksDir);
|
|
2557
1558
|
for (const entry of entries) {
|
|
2558
|
-
const entryPath =
|
|
1559
|
+
const entryPath = join9(hooksDir, entry);
|
|
2559
1560
|
const stat = statSync3(entryPath);
|
|
2560
1561
|
if (stat.isFile() && entry.endsWith(".sh")) {
|
|
2561
1562
|
hooks.push({ name: entry.replace(/\.sh$/, ""), description: "" });
|
|
2562
1563
|
} else if (stat.isDirectory()) {
|
|
2563
|
-
const readmePath =
|
|
1564
|
+
const readmePath = join9(entryPath, "README.md");
|
|
2564
1565
|
let description = "";
|
|
2565
|
-
if (
|
|
2566
|
-
const content = await
|
|
1566
|
+
if (existsSync7(readmePath)) {
|
|
1567
|
+
const content = await readFile5(readmePath, "utf-8");
|
|
2567
1568
|
const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"));
|
|
2568
1569
|
description = firstLine?.trim() || "";
|
|
2569
1570
|
}
|
|
@@ -2574,84 +1575,84 @@ async function listLocalHooks(hooksDir) {
|
|
|
2574
1575
|
}
|
|
2575
1576
|
async function addFromRegistry3(hookName, hubDir, opts) {
|
|
2576
1577
|
const repo = opts.repo || DEFAULT_REGISTRY_REPO4;
|
|
2577
|
-
const targetDir =
|
|
2578
|
-
console.log(
|
|
1578
|
+
const targetDir = join9(hubDir, "hooks", hookName);
|
|
1579
|
+
console.log(chalk9.cyan(` Downloading hook '${hookName}' from ${repo}...`));
|
|
2579
1580
|
try {
|
|
2580
1581
|
await downloadDirFromGitHub(repo, `hooks/${hookName}`, targetDir);
|
|
2581
|
-
console.log(
|
|
2582
|
-
console.log(
|
|
2583
|
-
console.log(
|
|
2584
|
-
console.log(
|
|
2585
|
-
console.log(
|
|
2586
|
-
console.log(
|
|
1582
|
+
console.log(chalk9.green(` Installed: ${hookName}`));
|
|
1583
|
+
console.log(chalk9.dim("\n Add the hook to hub.yaml to bind it to an event. Example:"));
|
|
1584
|
+
console.log(chalk9.dim(` hooks:`));
|
|
1585
|
+
console.log(chalk9.dim(` after_file_edit:`));
|
|
1586
|
+
console.log(chalk9.dim(` - type: command`));
|
|
1587
|
+
console.log(chalk9.dim(` command: "./hooks/${hookName}/hook.sh"
|
|
2587
1588
|
`));
|
|
2588
1589
|
} catch {
|
|
2589
|
-
console.log(
|
|
2590
|
-
console.log(
|
|
1590
|
+
console.log(chalk9.red(` Hook '${hookName}' not found in registry (${repo})`));
|
|
1591
|
+
console.log(chalk9.dim(" Run 'hub registry list --type hook' to see available hooks."));
|
|
2591
1592
|
}
|
|
2592
1593
|
}
|
|
2593
1594
|
async function addFromLocalPath3(localPath, hubDir, opts) {
|
|
2594
|
-
const absPath =
|
|
2595
|
-
if (!
|
|
2596
|
-
console.log(
|
|
1595
|
+
const absPath = resolve3(localPath);
|
|
1596
|
+
if (!existsSync7(absPath)) {
|
|
1597
|
+
console.log(chalk9.red(` Path not found: ${absPath}`));
|
|
2597
1598
|
return;
|
|
2598
1599
|
}
|
|
2599
1600
|
const stat = statSync3(absPath);
|
|
2600
|
-
const sourceHooksDir = stat.isDirectory() ?
|
|
1601
|
+
const sourceHooksDir = stat.isDirectory() ? existsSync7(join9(absPath, "hooks")) ? join9(absPath, "hooks") : absPath : absPath;
|
|
2601
1602
|
await installHooksFromDir(sourceHooksDir, hubDir, opts);
|
|
2602
1603
|
}
|
|
2603
1604
|
async function installHooksFromDir(sourceDir, hubDir, opts) {
|
|
2604
|
-
if (!
|
|
2605
|
-
console.log(
|
|
1605
|
+
if (!existsSync7(sourceDir)) {
|
|
1606
|
+
console.log(chalk9.red(" No hooks directory found in source"));
|
|
2606
1607
|
return;
|
|
2607
1608
|
}
|
|
2608
|
-
const entries = await
|
|
1609
|
+
const entries = await readdir3(sourceDir);
|
|
2609
1610
|
const hookEntries = entries.filter((e) => {
|
|
2610
|
-
const p =
|
|
1611
|
+
const p = join9(sourceDir, e);
|
|
2611
1612
|
return e.endsWith(".sh") || statSync3(p).isDirectory();
|
|
2612
1613
|
});
|
|
2613
1614
|
if (hookEntries.length === 0) {
|
|
2614
|
-
console.log(
|
|
1615
|
+
console.log(chalk9.red(" No hook files found"));
|
|
2615
1616
|
return;
|
|
2616
1617
|
}
|
|
2617
1618
|
const toInstall = opts.hook ? hookEntries.filter((e) => e === opts.hook || e === `${opts.hook}.sh` || e.replace(/\.sh$/, "") === opts.hook) : hookEntries;
|
|
2618
1619
|
if (opts.hook && toInstall.length === 0) {
|
|
2619
1620
|
const available = hookEntries.map((e) => e.replace(/\.sh$/, "")).join(", ");
|
|
2620
|
-
console.log(
|
|
1621
|
+
console.log(chalk9.red(` Hook '${opts.hook}' not found. Available: ${available}`));
|
|
2621
1622
|
return;
|
|
2622
1623
|
}
|
|
2623
|
-
const targetBase =
|
|
2624
|
-
await
|
|
1624
|
+
const targetBase = join9(hubDir, "hooks");
|
|
1625
|
+
await mkdir5(targetBase, { recursive: true });
|
|
2625
1626
|
for (const entry of toInstall) {
|
|
2626
|
-
const src =
|
|
1627
|
+
const src = join9(sourceDir, entry);
|
|
2627
1628
|
const stat = statSync3(src);
|
|
2628
1629
|
if (stat.isDirectory()) {
|
|
2629
|
-
await
|
|
1630
|
+
await cp2(src, join9(targetBase, entry), { recursive: true });
|
|
2630
1631
|
} else {
|
|
2631
|
-
await
|
|
1632
|
+
await copyFile2(src, join9(targetBase, entry));
|
|
2632
1633
|
}
|
|
2633
|
-
console.log(
|
|
1634
|
+
console.log(chalk9.green(` Installed: ${entry.replace(/\.sh$/, "")}`));
|
|
2634
1635
|
}
|
|
2635
|
-
console.log(
|
|
1636
|
+
console.log(chalk9.green(`
|
|
2636
1637
|
${toInstall.length} hook(s) installed
|
|
2637
1638
|
`));
|
|
2638
1639
|
}
|
|
2639
1640
|
async function addFromGitRepo3(source, hubDir, opts) {
|
|
2640
1641
|
const tmp = tmpDir3();
|
|
2641
1642
|
try {
|
|
2642
|
-
console.log(
|
|
1643
|
+
console.log(chalk9.cyan(` Cloning ${source}...`));
|
|
2643
1644
|
try {
|
|
2644
1645
|
execSync6(`git clone --depth 1 ${source} ${tmp}`, {
|
|
2645
1646
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2646
1647
|
});
|
|
2647
1648
|
} catch {
|
|
2648
|
-
console.log(
|
|
1649
|
+
console.log(chalk9.red(` Repository not found or not accessible: ${source}`));
|
|
2649
1650
|
return;
|
|
2650
1651
|
}
|
|
2651
|
-
const sourceHooksDir =
|
|
1652
|
+
const sourceHooksDir = join9(tmp, "hooks");
|
|
2652
1653
|
await installHooksFromDir(sourceHooksDir, hubDir, opts);
|
|
2653
1654
|
} finally {
|
|
2654
|
-
if (
|
|
1655
|
+
if (existsSync7(tmp)) {
|
|
2655
1656
|
await rm3(tmp, { recursive: true });
|
|
2656
1657
|
}
|
|
2657
1658
|
}
|
|
@@ -2662,10 +1663,10 @@ function isLocalPath3(source) {
|
|
|
2662
1663
|
function isRepoReference2(source) {
|
|
2663
1664
|
return source.startsWith("git@") || source.startsWith("https://") || source.includes("/");
|
|
2664
1665
|
}
|
|
2665
|
-
var hooksCommand = new
|
|
2666
|
-
new
|
|
1666
|
+
var hooksCommand = new Command9("hooks").description("Manage editor hooks").addCommand(
|
|
1667
|
+
new Command9("add").description("Install hooks from the registry, a git repository, or local path").argument("<source>", "Hook name (from registry), GitHub shorthand (org/repo), git URL, or local path").option("--hook <name>", "Install a specific hook only (for repo sources)").option("-r, --repo <repo>", "Registry repository (owner/repo)").action(async (source, opts) => {
|
|
2667
1668
|
const hubDir = process.cwd();
|
|
2668
|
-
console.log(
|
|
1669
|
+
console.log(chalk9.blue(`
|
|
2669
1670
|
Installing hooks from ${source}
|
|
2670
1671
|
`));
|
|
2671
1672
|
if (isLocalPath3(source)) {
|
|
@@ -2680,75 +1681,78 @@ Installing hooks from ${source}
|
|
|
2680
1681
|
} else {
|
|
2681
1682
|
await addFromRegistry3(source, hubDir, opts);
|
|
2682
1683
|
}
|
|
1684
|
+
await checkAndAutoRegenerate(hubDir);
|
|
2683
1685
|
})
|
|
2684
1686
|
).addCommand(
|
|
2685
|
-
new
|
|
1687
|
+
new Command9("list").description("List installed hooks").action(async () => {
|
|
2686
1688
|
const hubDir = process.cwd();
|
|
2687
|
-
const hooksDir =
|
|
2688
|
-
console.log(
|
|
1689
|
+
const hooksDir = join9(hubDir, "hooks");
|
|
1690
|
+
console.log(chalk9.blue("\nInstalled hooks\n"));
|
|
2689
1691
|
const hooks = await listLocalHooks(hooksDir);
|
|
2690
1692
|
if (hooks.length > 0) {
|
|
2691
1693
|
for (const h of hooks) {
|
|
2692
|
-
console.log(` ${
|
|
1694
|
+
console.log(` ${chalk9.yellow(h.name)}${h.description ? ` \u2014 ${h.description}` : ""}`);
|
|
2693
1695
|
}
|
|
2694
1696
|
} else {
|
|
2695
|
-
console.log(
|
|
1697
|
+
console.log(chalk9.dim(" No hooks installed (hooks/)"));
|
|
2696
1698
|
}
|
|
2697
1699
|
console.log();
|
|
2698
1700
|
})
|
|
2699
1701
|
).addCommand(
|
|
2700
|
-
new
|
|
1702
|
+
new Command9("find").description("Browse curated hooks in the Repo Hub directory").argument("[query]", "Search term").action(async (query) => {
|
|
2701
1703
|
const base = "https://rhm-website.vercel.app/directory?type=hook";
|
|
2702
1704
|
const url = query ? `${base}&q=${encodeURIComponent(query)}` : base;
|
|
2703
|
-
console.log(
|
|
2704
|
-
console.log(
|
|
1705
|
+
console.log(chalk9.blue("\n Browse curated hooks at:\n"));
|
|
1706
|
+
console.log(chalk9.cyan(` ${url}
|
|
2705
1707
|
`));
|
|
2706
|
-
console.log(
|
|
2707
|
-
console.log(
|
|
1708
|
+
console.log(chalk9.dim(" Install with: hub hooks add <owner>/<repo>"));
|
|
1709
|
+
console.log(chalk9.dim(" Example: hub hooks add obra/superpowers\n"));
|
|
2708
1710
|
})
|
|
2709
1711
|
).addCommand(
|
|
2710
|
-
new
|
|
1712
|
+
new Command9("remove").description("Remove a hook").argument("<name>", "Hook name to remove").action(async (name) => {
|
|
2711
1713
|
const hubDir = process.cwd();
|
|
2712
|
-
const hooksDir =
|
|
2713
|
-
const shFile =
|
|
2714
|
-
const dirPath =
|
|
2715
|
-
if (
|
|
1714
|
+
const hooksDir = join9(hubDir, "hooks");
|
|
1715
|
+
const shFile = join9(hooksDir, `${name}.sh`);
|
|
1716
|
+
const dirPath = join9(hooksDir, name);
|
|
1717
|
+
if (existsSync7(dirPath) && statSync3(dirPath).isDirectory()) {
|
|
2716
1718
|
await rm3(dirPath, { recursive: true });
|
|
2717
|
-
console.log(
|
|
1719
|
+
console.log(chalk9.green(`
|
|
2718
1720
|
Removed hook: ${name}
|
|
2719
1721
|
`));
|
|
2720
|
-
} else if (
|
|
1722
|
+
} else if (existsSync7(shFile)) {
|
|
2721
1723
|
await rm3(shFile);
|
|
2722
|
-
console.log(
|
|
1724
|
+
console.log(chalk9.green(`
|
|
2723
1725
|
Removed hook: ${name}
|
|
2724
1726
|
`));
|
|
2725
1727
|
} else {
|
|
2726
|
-
console.log(
|
|
1728
|
+
console.log(chalk9.red(`
|
|
2727
1729
|
Hook '${name}' not found in hooks/
|
|
2728
1730
|
`));
|
|
1731
|
+
return;
|
|
2729
1732
|
}
|
|
1733
|
+
await checkAndAutoRegenerate(hubDir);
|
|
2730
1734
|
})
|
|
2731
1735
|
);
|
|
2732
1736
|
|
|
2733
1737
|
// src/commands/commands.ts
|
|
2734
|
-
import { Command as
|
|
2735
|
-
import { existsSync as
|
|
2736
|
-
import { mkdir as
|
|
2737
|
-
import { join as
|
|
1738
|
+
import { Command as Command10 } from "commander";
|
|
1739
|
+
import { existsSync as existsSync8, statSync as statSync4 } from "fs";
|
|
1740
|
+
import { mkdir as mkdir6, readdir as readdir4, readFile as readFile6, rm as rm4, copyFile as copyFile3, writeFile as writeFile8 } from "fs/promises";
|
|
1741
|
+
import { join as join10, resolve as resolve4 } from "path";
|
|
2738
1742
|
import { execSync as execSync7 } from "child_process";
|
|
2739
|
-
import
|
|
1743
|
+
import chalk10 from "chalk";
|
|
2740
1744
|
var DEFAULT_REGISTRY_REPO5 = process.env.HUB_REGISTRY || "arvoreeducacao/rhm";
|
|
2741
1745
|
var DEFAULT_BRANCH3 = "main";
|
|
2742
1746
|
function tmpDir4() {
|
|
2743
|
-
return
|
|
1747
|
+
return join10(process.env.TMPDIR || "/tmp", `hub-commands-${Date.now()}`);
|
|
2744
1748
|
}
|
|
2745
1749
|
async function listLocalCommands(commandsDir) {
|
|
2746
1750
|
const commands = [];
|
|
2747
|
-
if (!
|
|
2748
|
-
const files = await
|
|
1751
|
+
if (!existsSync8(commandsDir)) return commands;
|
|
1752
|
+
const files = await readdir4(commandsDir);
|
|
2749
1753
|
for (const file of files) {
|
|
2750
1754
|
if (!file.endsWith(".md")) continue;
|
|
2751
|
-
const content = await
|
|
1755
|
+
const content = await readFile6(join10(commandsDir, file), "utf-8");
|
|
2752
1756
|
const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"));
|
|
2753
1757
|
commands.push({
|
|
2754
1758
|
name: file.replace(/\.md$/, ""),
|
|
@@ -2761,91 +1765,91 @@ async function addFromRegistry4(commandName, hubDir, opts) {
|
|
|
2761
1765
|
const repo = opts.repo || DEFAULT_REGISTRY_REPO5;
|
|
2762
1766
|
const fileName = commandName.endsWith(".md") ? commandName : `${commandName}.md`;
|
|
2763
1767
|
const rawUrl = `https://raw.githubusercontent.com/${repo}/${DEFAULT_BRANCH3}/commands/${fileName}`;
|
|
2764
|
-
console.log(
|
|
1768
|
+
console.log(chalk10.cyan(` Downloading command '${commandName}' from ${repo}...`));
|
|
2765
1769
|
try {
|
|
2766
1770
|
const res = await fetch(rawUrl);
|
|
2767
1771
|
if (!res.ok) {
|
|
2768
|
-
console.log(
|
|
2769
|
-
console.log(
|
|
1772
|
+
console.log(chalk10.red(` Command '${commandName}' not found in registry (${repo})`));
|
|
1773
|
+
console.log(chalk10.dim(" Run 'hub registry list --type command' to see available commands."));
|
|
2770
1774
|
return;
|
|
2771
1775
|
}
|
|
2772
1776
|
const content = await res.text();
|
|
2773
|
-
const targetDir =
|
|
2774
|
-
await
|
|
2775
|
-
await
|
|
2776
|
-
console.log(
|
|
2777
|
-
console.log(
|
|
1777
|
+
const targetDir = join10(hubDir, "commands");
|
|
1778
|
+
await mkdir6(targetDir, { recursive: true });
|
|
1779
|
+
await writeFile8(join10(targetDir, fileName), content, "utf-8");
|
|
1780
|
+
console.log(chalk10.green(` Installed: ${commandName}`));
|
|
1781
|
+
console.log(chalk10.dim(`
|
|
2778
1782
|
Use it in Cursor with /${commandName}`));
|
|
2779
|
-
console.log(
|
|
1783
|
+
console.log(chalk10.dim(` Make sure hub.yaml has: commands_dir: ./commands
|
|
2780
1784
|
`));
|
|
2781
1785
|
} catch (err) {
|
|
2782
|
-
console.log(
|
|
1786
|
+
console.log(chalk10.red(` Failed to download command '${commandName}': ${err.message}`));
|
|
2783
1787
|
}
|
|
2784
1788
|
}
|
|
2785
1789
|
async function addFromLocalPath4(localPath, hubDir, opts) {
|
|
2786
|
-
const absPath =
|
|
2787
|
-
if (!
|
|
2788
|
-
console.log(
|
|
1790
|
+
const absPath = resolve4(localPath);
|
|
1791
|
+
if (!existsSync8(absPath)) {
|
|
1792
|
+
console.log(chalk10.red(` Path not found: ${absPath}`));
|
|
2789
1793
|
return;
|
|
2790
1794
|
}
|
|
2791
1795
|
const stat = statSync4(absPath);
|
|
2792
1796
|
if (stat.isFile() && absPath.endsWith(".md")) {
|
|
2793
|
-
const targetDir =
|
|
2794
|
-
await
|
|
1797
|
+
const targetDir = join10(hubDir, "commands");
|
|
1798
|
+
await mkdir6(targetDir, { recursive: true });
|
|
2795
1799
|
const fileName = absPath.split("/").pop();
|
|
2796
|
-
await
|
|
2797
|
-
console.log(
|
|
2798
|
-
console.log(
|
|
1800
|
+
await copyFile3(absPath, join10(targetDir, fileName));
|
|
1801
|
+
console.log(chalk10.green(` Installed: ${fileName.replace(/\.md$/, "")}`));
|
|
1802
|
+
console.log(chalk10.green(`
|
|
2799
1803
|
1 command(s) installed
|
|
2800
1804
|
`));
|
|
2801
1805
|
return;
|
|
2802
1806
|
}
|
|
2803
|
-
const sourceDir = stat.isDirectory() ?
|
|
1807
|
+
const sourceDir = stat.isDirectory() ? existsSync8(join10(absPath, "commands")) ? join10(absPath, "commands") : absPath : absPath;
|
|
2804
1808
|
await installCommandsFromDir(sourceDir, hubDir, opts);
|
|
2805
1809
|
}
|
|
2806
1810
|
async function installCommandsFromDir(sourceDir, hubDir, opts) {
|
|
2807
|
-
if (!
|
|
2808
|
-
console.log(
|
|
1811
|
+
if (!existsSync8(sourceDir)) {
|
|
1812
|
+
console.log(chalk10.red(" No commands directory found in source"));
|
|
2809
1813
|
return;
|
|
2810
1814
|
}
|
|
2811
|
-
const files = await
|
|
1815
|
+
const files = await readdir4(sourceDir);
|
|
2812
1816
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
2813
1817
|
if (mdFiles.length === 0) {
|
|
2814
|
-
console.log(
|
|
1818
|
+
console.log(chalk10.red(" No command files found (looking for *.md)"));
|
|
2815
1819
|
return;
|
|
2816
1820
|
}
|
|
2817
1821
|
const toInstall = opts.command ? mdFiles.filter((f) => f === `${opts.command}.md` || f === opts.command) : mdFiles;
|
|
2818
1822
|
if (opts.command && toInstall.length === 0) {
|
|
2819
1823
|
const available = mdFiles.map((f) => f.replace(/\.md$/, "")).join(", ");
|
|
2820
|
-
console.log(
|
|
1824
|
+
console.log(chalk10.red(` Command '${opts.command}' not found. Available: ${available}`));
|
|
2821
1825
|
return;
|
|
2822
1826
|
}
|
|
2823
|
-
const targetDir =
|
|
2824
|
-
await
|
|
1827
|
+
const targetDir = join10(hubDir, "commands");
|
|
1828
|
+
await mkdir6(targetDir, { recursive: true });
|
|
2825
1829
|
for (const file of toInstall) {
|
|
2826
|
-
await
|
|
2827
|
-
console.log(
|
|
1830
|
+
await copyFile3(join10(sourceDir, file), join10(targetDir, file));
|
|
1831
|
+
console.log(chalk10.green(` Installed: ${file.replace(/\.md$/, "")}`));
|
|
2828
1832
|
}
|
|
2829
|
-
console.log(
|
|
1833
|
+
console.log(chalk10.green(`
|
|
2830
1834
|
${toInstall.length} command(s) installed
|
|
2831
1835
|
`));
|
|
2832
1836
|
}
|
|
2833
1837
|
async function addFromGitRepo4(source, hubDir, opts) {
|
|
2834
1838
|
const tmp = tmpDir4();
|
|
2835
1839
|
try {
|
|
2836
|
-
console.log(
|
|
1840
|
+
console.log(chalk10.cyan(` Cloning ${source}...`));
|
|
2837
1841
|
try {
|
|
2838
1842
|
execSync7(`git clone --depth 1 ${source} ${tmp}`, {
|
|
2839
1843
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2840
1844
|
});
|
|
2841
1845
|
} catch {
|
|
2842
|
-
console.log(
|
|
1846
|
+
console.log(chalk10.red(` Repository not found or not accessible: ${source}`));
|
|
2843
1847
|
return;
|
|
2844
1848
|
}
|
|
2845
|
-
const sourceCommandsDir =
|
|
1849
|
+
const sourceCommandsDir = join10(tmp, "commands");
|
|
2846
1850
|
await installCommandsFromDir(sourceCommandsDir, hubDir, opts);
|
|
2847
1851
|
} finally {
|
|
2848
|
-
if (
|
|
1852
|
+
if (existsSync8(tmp)) {
|
|
2849
1853
|
await rm4(tmp, { recursive: true });
|
|
2850
1854
|
}
|
|
2851
1855
|
}
|
|
@@ -2856,10 +1860,10 @@ function isLocalPath4(source) {
|
|
|
2856
1860
|
function isRepoReference3(source) {
|
|
2857
1861
|
return source.startsWith("git@") || source.startsWith("https://") || source.includes("/");
|
|
2858
1862
|
}
|
|
2859
|
-
var commandsCommand = new
|
|
2860
|
-
new
|
|
1863
|
+
var commandsCommand = new Command10("commands").description("Manage slash commands (Cursor)").addCommand(
|
|
1864
|
+
new Command10("add").description("Install commands from the registry, a git repository, or local path").argument("<source>", "Command name (from registry), GitHub shorthand (org/repo), git URL, or local path").option("-c, --command <name>", "Install a specific command only (for repo sources)").option("-r, --repo <repo>", "Registry repository (owner/repo)").action(async (source, opts) => {
|
|
2861
1865
|
const hubDir = process.cwd();
|
|
2862
|
-
console.log(
|
|
1866
|
+
console.log(chalk10.blue(`
|
|
2863
1867
|
Installing commands from ${source}
|
|
2864
1868
|
`));
|
|
2865
1869
|
if (isLocalPath4(source)) {
|
|
@@ -2874,88 +1878,90 @@ Installing commands from ${source}
|
|
|
2874
1878
|
} else {
|
|
2875
1879
|
await addFromRegistry4(source, hubDir, opts);
|
|
2876
1880
|
}
|
|
1881
|
+
await checkAndAutoRegenerate(hubDir);
|
|
2877
1882
|
})
|
|
2878
1883
|
).addCommand(
|
|
2879
|
-
new
|
|
1884
|
+
new Command10("list").description("List installed commands").action(async () => {
|
|
2880
1885
|
const hubDir = process.cwd();
|
|
2881
|
-
const commandsDir =
|
|
2882
|
-
console.log(
|
|
1886
|
+
const commandsDir = join10(hubDir, "commands");
|
|
1887
|
+
console.log(chalk10.blue("\nInstalled commands\n"));
|
|
2883
1888
|
const commands = await listLocalCommands(commandsDir);
|
|
2884
1889
|
if (commands.length > 0) {
|
|
2885
1890
|
for (const c of commands) {
|
|
2886
|
-
console.log(` ${
|
|
1891
|
+
console.log(` ${chalk10.yellow(`/${c.name}`)}${c.description ? ` \u2014 ${c.description}` : ""}`);
|
|
2887
1892
|
}
|
|
2888
1893
|
} else {
|
|
2889
|
-
console.log(
|
|
1894
|
+
console.log(chalk10.dim(" No commands installed (commands/)"));
|
|
2890
1895
|
}
|
|
2891
1896
|
console.log();
|
|
2892
1897
|
})
|
|
2893
1898
|
).addCommand(
|
|
2894
|
-
new
|
|
1899
|
+
new Command10("find").description("Browse curated commands in the Repo Hub directory").argument("[query]", "Search term").action(async (query) => {
|
|
2895
1900
|
const base = "https://rhm-website.vercel.app/directory?type=command";
|
|
2896
1901
|
const url = query ? `${base}&q=${encodeURIComponent(query)}` : base;
|
|
2897
|
-
console.log(
|
|
2898
|
-
console.log(
|
|
1902
|
+
console.log(chalk10.blue("\n Browse curated commands at:\n"));
|
|
1903
|
+
console.log(chalk10.cyan(` ${url}
|
|
2899
1904
|
`));
|
|
2900
|
-
console.log(
|
|
2901
|
-
console.log(
|
|
1905
|
+
console.log(chalk10.dim(" Install with: hub commands add <owner>/<repo>"));
|
|
1906
|
+
console.log(chalk10.dim(" Example: hub commands add obra/superpowers\n"));
|
|
2902
1907
|
})
|
|
2903
1908
|
).addCommand(
|
|
2904
|
-
new
|
|
1909
|
+
new Command10("remove").description("Remove a command").argument("<name>", "Command name to remove").action(async (name) => {
|
|
2905
1910
|
const hubDir = process.cwd();
|
|
2906
|
-
const commandsDir =
|
|
1911
|
+
const commandsDir = join10(hubDir, "commands");
|
|
2907
1912
|
const fileName = name.endsWith(".md") ? name : `${name}.md`;
|
|
2908
|
-
const target =
|
|
2909
|
-
if (!
|
|
2910
|
-
console.log(
|
|
1913
|
+
const target = join10(commandsDir, fileName);
|
|
1914
|
+
if (!existsSync8(target)) {
|
|
1915
|
+
console.log(chalk10.red(`
|
|
2911
1916
|
Command '${name}' not found in commands/
|
|
2912
1917
|
`));
|
|
2913
1918
|
return;
|
|
2914
1919
|
}
|
|
2915
1920
|
await rm4(target);
|
|
2916
|
-
console.log(
|
|
1921
|
+
console.log(chalk10.green(`
|
|
2917
1922
|
Removed command: ${name}
|
|
2918
1923
|
`));
|
|
1924
|
+
await checkAndAutoRegenerate(hubDir);
|
|
2919
1925
|
})
|
|
2920
1926
|
);
|
|
2921
1927
|
|
|
2922
1928
|
// src/commands/repos.ts
|
|
2923
|
-
import { Command as
|
|
2924
|
-
import { existsSync as
|
|
2925
|
-
import { join as
|
|
1929
|
+
import { Command as Command11 } from "commander";
|
|
1930
|
+
import { existsSync as existsSync9 } from "fs";
|
|
1931
|
+
import { join as join11 } from "path";
|
|
2926
1932
|
import { execSync as execSync8 } from "child_process";
|
|
2927
|
-
import
|
|
2928
|
-
var pullCommand = new
|
|
1933
|
+
import chalk11 from "chalk";
|
|
1934
|
+
var pullCommand = new Command11("pull").description("Pull latest changes in all repositories").action(async () => {
|
|
2929
1935
|
const hubDir = process.cwd();
|
|
2930
1936
|
const config = await loadHubConfig(hubDir);
|
|
2931
|
-
console.log(
|
|
1937
|
+
console.log(chalk11.blue("\n\u2501\u2501\u2501 Pulling latest changes \u2501\u2501\u2501\n"));
|
|
2932
1938
|
for (const repo of config.repos) {
|
|
2933
|
-
const fullPath =
|
|
2934
|
-
if (!
|
|
2935
|
-
console.log(
|
|
1939
|
+
const fullPath = join11(hubDir, repo.path);
|
|
1940
|
+
if (!existsSync9(fullPath)) {
|
|
1941
|
+
console.log(chalk11.red(` ${repo.name}: not cloned`));
|
|
2936
1942
|
continue;
|
|
2937
1943
|
}
|
|
2938
|
-
console.log(
|
|
1944
|
+
console.log(chalk11.yellow(`\u25B8 ${repo.name}`));
|
|
2939
1945
|
try {
|
|
2940
1946
|
execSync8("git pull --rebase", { cwd: fullPath, stdio: "inherit" });
|
|
2941
|
-
console.log(
|
|
1947
|
+
console.log(chalk11.green(" Updated"));
|
|
2942
1948
|
} catch {
|
|
2943
|
-
console.log(
|
|
1949
|
+
console.log(chalk11.red(" Failed to pull"));
|
|
2944
1950
|
}
|
|
2945
1951
|
}
|
|
2946
1952
|
console.log();
|
|
2947
1953
|
});
|
|
2948
|
-
var statusCommand = new
|
|
1954
|
+
var statusCommand = new Command11("status").description("Show git status for all repositories").action(async () => {
|
|
2949
1955
|
const hubDir = process.cwd();
|
|
2950
1956
|
const config = await loadHubConfig(hubDir);
|
|
2951
|
-
console.log(
|
|
1957
|
+
console.log(chalk11.blue("\n\u2501\u2501\u2501 Git status \u2501\u2501\u2501\n"));
|
|
2952
1958
|
for (const repo of config.repos) {
|
|
2953
|
-
const fullPath =
|
|
2954
|
-
if (!
|
|
2955
|
-
console.log(
|
|
1959
|
+
const fullPath = join11(hubDir, repo.path);
|
|
1960
|
+
if (!existsSync9(fullPath)) {
|
|
1961
|
+
console.log(chalk11.red(` ${repo.name}: not cloned`));
|
|
2956
1962
|
continue;
|
|
2957
1963
|
}
|
|
2958
|
-
console.log(
|
|
1964
|
+
console.log(chalk11.yellow(`\u25B8 ${repo.name}`));
|
|
2959
1965
|
try {
|
|
2960
1966
|
const branch = execSync8("git branch --show-current", {
|
|
2961
1967
|
cwd: fullPath,
|
|
@@ -2986,43 +1992,43 @@ var statusCommand = new Command12("status").description("Show git status for all
|
|
|
2986
1992
|
console.log(` Changes: ${changes} file(s)`);
|
|
2987
1993
|
console.log(` Ahead: ${ahead} | Behind: ${behind}`);
|
|
2988
1994
|
} catch {
|
|
2989
|
-
console.log(
|
|
1995
|
+
console.log(chalk11.red(" Failed to get status"));
|
|
2990
1996
|
}
|
|
2991
1997
|
console.log();
|
|
2992
1998
|
}
|
|
2993
1999
|
});
|
|
2994
|
-
var execCommand = new
|
|
2000
|
+
var execCommand = new Command11("exec").description("Execute a command in all repositories").argument("<cmd...>", "Command to execute").passThroughOptions().allowUnknownOption().action(async (cmd) => {
|
|
2995
2001
|
const hubDir = process.cwd();
|
|
2996
2002
|
const config = await loadHubConfig(hubDir);
|
|
2997
2003
|
const command = cmd.join(" ");
|
|
2998
|
-
console.log(
|
|
2004
|
+
console.log(chalk11.blue(`
|
|
2999
2005
|
\u2501\u2501\u2501 Executing: ${command} \u2501\u2501\u2501
|
|
3000
2006
|
`));
|
|
3001
2007
|
for (const repo of config.repos) {
|
|
3002
|
-
const fullPath =
|
|
3003
|
-
if (!
|
|
3004
|
-
console.log(
|
|
2008
|
+
const fullPath = join11(hubDir, repo.path);
|
|
2009
|
+
if (!existsSync9(fullPath)) {
|
|
2010
|
+
console.log(chalk11.red(` ${repo.name}: not cloned`));
|
|
3005
2011
|
continue;
|
|
3006
2012
|
}
|
|
3007
|
-
console.log(
|
|
2013
|
+
console.log(chalk11.yellow(`\u25B8 ${repo.name}`));
|
|
3008
2014
|
try {
|
|
3009
2015
|
execSync8(command, { cwd: fullPath, stdio: "inherit" });
|
|
3010
2016
|
} catch {
|
|
3011
|
-
console.log(
|
|
2017
|
+
console.log(chalk11.red(` Command failed in ${repo.name}`));
|
|
3012
2018
|
}
|
|
3013
2019
|
}
|
|
3014
2020
|
console.log();
|
|
3015
2021
|
});
|
|
3016
2022
|
|
|
3017
2023
|
// src/commands/worktree.ts
|
|
3018
|
-
import { Command as
|
|
3019
|
-
import { existsSync as
|
|
3020
|
-
import { cp as
|
|
3021
|
-
import { join as
|
|
2024
|
+
import { Command as Command12 } from "commander";
|
|
2025
|
+
import { existsSync as existsSync10 } from "fs";
|
|
2026
|
+
import { cp as cp3, mkdir as mkdir7 } from "fs/promises";
|
|
2027
|
+
import { join as join12 } from "path";
|
|
3022
2028
|
import { execSync as execSync9 } from "child_process";
|
|
3023
|
-
import
|
|
2029
|
+
import chalk12 from "chalk";
|
|
3024
2030
|
function getWorktreeBase() {
|
|
3025
|
-
return
|
|
2031
|
+
return join12(process.env.HOME || "~", ".cursor", "worktrees", "repo-hub");
|
|
3026
2032
|
}
|
|
3027
2033
|
function isGitRepo(dir) {
|
|
3028
2034
|
try {
|
|
@@ -3035,74 +2041,74 @@ function isGitRepo(dir) {
|
|
|
3035
2041
|
return false;
|
|
3036
2042
|
}
|
|
3037
2043
|
}
|
|
3038
|
-
var worktreeCommand = new
|
|
3039
|
-
new
|
|
2044
|
+
var worktreeCommand = new Command12("worktree").description("Manage git worktrees for parallel work").addCommand(
|
|
2045
|
+
new Command12("add").description("Create a new worktree with environment files copied").argument("<name>", "Worktree name").action(async (name) => {
|
|
3040
2046
|
const hubDir = process.cwd();
|
|
3041
2047
|
if (!isGitRepo(hubDir)) {
|
|
3042
|
-
console.log(
|
|
3043
|
-
console.log(
|
|
2048
|
+
console.log(chalk12.red("\nThis directory is not a git repository."));
|
|
2049
|
+
console.log(chalk12.dim("Run 'git init' first or use worktrees from a repo directory.\n"));
|
|
3044
2050
|
return;
|
|
3045
2051
|
}
|
|
3046
2052
|
const config = await loadHubConfig(hubDir);
|
|
3047
2053
|
const worktreeBase = getWorktreeBase();
|
|
3048
|
-
const worktreePath =
|
|
3049
|
-
console.log(
|
|
2054
|
+
const worktreePath = join12(worktreeBase, name);
|
|
2055
|
+
console.log(chalk12.blue(`
|
|
3050
2056
|
\u2501\u2501\u2501 Creating worktree: ${name} \u2501\u2501\u2501
|
|
3051
2057
|
`));
|
|
3052
|
-
await
|
|
3053
|
-
console.log(
|
|
2058
|
+
await mkdir7(worktreeBase, { recursive: true });
|
|
2059
|
+
console.log(chalk12.cyan(" Creating git worktree..."));
|
|
3054
2060
|
try {
|
|
3055
2061
|
execSync9(`git worktree add "${worktreePath}" --detach`, {
|
|
3056
2062
|
cwd: hubDir,
|
|
3057
2063
|
stdio: "inherit"
|
|
3058
2064
|
});
|
|
3059
2065
|
} catch {
|
|
3060
|
-
console.log(
|
|
2066
|
+
console.log(chalk12.red(" Failed to create worktree. Make sure you have commits in this repo.\n"));
|
|
3061
2067
|
return;
|
|
3062
2068
|
}
|
|
3063
|
-
console.log(
|
|
2069
|
+
console.log(chalk12.cyan(" Copying environment files..."));
|
|
3064
2070
|
for (const repo of config.repos) {
|
|
3065
2071
|
if (!repo.env_file) continue;
|
|
3066
|
-
const srcEnv =
|
|
3067
|
-
if (!
|
|
3068
|
-
const destDir =
|
|
3069
|
-
if (
|
|
3070
|
-
const destEnv =
|
|
2072
|
+
const srcEnv = join12(hubDir, repo.path, repo.env_file);
|
|
2073
|
+
if (!existsSync10(srcEnv)) continue;
|
|
2074
|
+
const destDir = join12(worktreePath, repo.path);
|
|
2075
|
+
if (existsSync10(destDir)) {
|
|
2076
|
+
const destEnv = join12(destDir, repo.env_file);
|
|
3071
2077
|
try {
|
|
3072
|
-
await
|
|
3073
|
-
console.log(
|
|
2078
|
+
await cp3(srcEnv, destEnv);
|
|
2079
|
+
console.log(chalk12.green(` ${repo.name}: Copied ${repo.env_file}`));
|
|
3074
2080
|
} catch {
|
|
3075
|
-
console.log(
|
|
2081
|
+
console.log(chalk12.dim(` ${repo.name}: Could not copy env file`));
|
|
3076
2082
|
}
|
|
3077
2083
|
}
|
|
3078
2084
|
}
|
|
3079
|
-
console.log(
|
|
2085
|
+
console.log(chalk12.green(`
|
|
3080
2086
|
Worktree created at: ${worktreePath}`));
|
|
3081
|
-
console.log(
|
|
2087
|
+
console.log(chalk12.cyan(`Open in Cursor: cursor ${worktreePath}
|
|
3082
2088
|
`));
|
|
3083
2089
|
})
|
|
3084
2090
|
).addCommand(
|
|
3085
|
-
new
|
|
2091
|
+
new Command12("list").description("List all worktrees").action(async () => {
|
|
3086
2092
|
const hubDir = process.cwd();
|
|
3087
2093
|
if (!isGitRepo(hubDir)) {
|
|
3088
|
-
console.log(
|
|
3089
|
-
console.log(
|
|
2094
|
+
console.log(chalk12.red("\nThis directory is not a git repository."));
|
|
2095
|
+
console.log(chalk12.dim("Worktrees require a git repository.\n"));
|
|
3090
2096
|
return;
|
|
3091
2097
|
}
|
|
3092
|
-
console.log(
|
|
2098
|
+
console.log(chalk12.blue("\n\u2501\u2501\u2501 Git Worktrees \u2501\u2501\u2501\n"));
|
|
3093
2099
|
execSync9("git worktree list", { cwd: hubDir, stdio: "inherit" });
|
|
3094
2100
|
console.log();
|
|
3095
2101
|
})
|
|
3096
2102
|
).addCommand(
|
|
3097
|
-
new
|
|
2103
|
+
new Command12("remove").description("Remove a worktree").argument("<name>", "Worktree name").action(async (name) => {
|
|
3098
2104
|
const hubDir = process.cwd();
|
|
3099
2105
|
if (!isGitRepo(hubDir)) {
|
|
3100
|
-
console.log(
|
|
3101
|
-
console.log(
|
|
2106
|
+
console.log(chalk12.red("\nThis directory is not a git repository."));
|
|
2107
|
+
console.log(chalk12.dim("Worktrees require a git repository.\n"));
|
|
3102
2108
|
return;
|
|
3103
2109
|
}
|
|
3104
|
-
const worktreePath =
|
|
3105
|
-
console.log(
|
|
2110
|
+
const worktreePath = join12(getWorktreeBase(), name);
|
|
2111
|
+
console.log(chalk12.blue(`
|
|
3106
2112
|
\u2501\u2501\u2501 Removing worktree: ${name} \u2501\u2501\u2501
|
|
3107
2113
|
`));
|
|
3108
2114
|
try {
|
|
@@ -3110,39 +2116,39 @@ Worktree created at: ${worktreePath}`));
|
|
|
3110
2116
|
cwd: hubDir,
|
|
3111
2117
|
stdio: "inherit"
|
|
3112
2118
|
});
|
|
3113
|
-
console.log(
|
|
2119
|
+
console.log(chalk12.green(" Worktree removed\n"));
|
|
3114
2120
|
} catch {
|
|
3115
|
-
console.log(
|
|
2121
|
+
console.log(chalk12.red(` Failed to remove worktree '${name}'.
|
|
3116
2122
|
`));
|
|
3117
2123
|
}
|
|
3118
2124
|
})
|
|
3119
2125
|
).addCommand(
|
|
3120
|
-
new
|
|
2126
|
+
new Command12("copy-envs").description("Copy environment files to a worktree").argument("[name]", "Worktree name (copies to that worktree)").action(async (name) => {
|
|
3121
2127
|
const hubDir = process.cwd();
|
|
3122
2128
|
const config = await loadHubConfig(hubDir);
|
|
3123
|
-
const targetDir = name ?
|
|
3124
|
-
if (!
|
|
3125
|
-
console.log(
|
|
2129
|
+
const targetDir = name ? join12(getWorktreeBase(), name) : hubDir;
|
|
2130
|
+
if (!existsSync10(targetDir)) {
|
|
2131
|
+
console.log(chalk12.red(`
|
|
3126
2132
|
Worktree '${name}' not found at ${targetDir}
|
|
3127
2133
|
`));
|
|
3128
2134
|
return;
|
|
3129
2135
|
}
|
|
3130
|
-
console.log(
|
|
3131
|
-
console.log(
|
|
3132
|
-
console.log(
|
|
2136
|
+
console.log(chalk12.blue("\n\u2501\u2501\u2501 Copying environment files \u2501\u2501\u2501\n"));
|
|
2137
|
+
console.log(chalk12.cyan(` Source: ${hubDir}`));
|
|
2138
|
+
console.log(chalk12.cyan(` Target: ${targetDir}
|
|
3133
2139
|
`));
|
|
3134
2140
|
for (const repo of config.repos) {
|
|
3135
2141
|
if (!repo.env_file) continue;
|
|
3136
|
-
const srcEnv =
|
|
3137
|
-
if (!
|
|
3138
|
-
const destDir =
|
|
3139
|
-
if (!
|
|
3140
|
-
const destEnv =
|
|
2142
|
+
const srcEnv = join12(hubDir, repo.path, repo.env_file);
|
|
2143
|
+
if (!existsSync10(srcEnv)) continue;
|
|
2144
|
+
const destDir = join12(targetDir, repo.path);
|
|
2145
|
+
if (!existsSync10(destDir)) continue;
|
|
2146
|
+
const destEnv = join12(destDir, repo.env_file);
|
|
3141
2147
|
try {
|
|
3142
|
-
await
|
|
3143
|
-
console.log(
|
|
2148
|
+
await cp3(srcEnv, destEnv);
|
|
2149
|
+
console.log(chalk12.green(` ${repo.name}: Copied ${repo.env_file}`));
|
|
3144
2150
|
} catch {
|
|
3145
|
-
console.log(
|
|
2151
|
+
console.log(chalk12.red(` ${repo.name}: Failed to copy`));
|
|
3146
2152
|
}
|
|
3147
2153
|
}
|
|
3148
2154
|
console.log();
|
|
@@ -3150,11 +2156,11 @@ Worktree '${name}' not found at ${targetDir}
|
|
|
3150
2156
|
);
|
|
3151
2157
|
|
|
3152
2158
|
// src/commands/doctor.ts
|
|
3153
|
-
import { Command as
|
|
3154
|
-
import { existsSync as
|
|
3155
|
-
import { join as
|
|
2159
|
+
import { Command as Command13 } from "commander";
|
|
2160
|
+
import { existsSync as existsSync11 } from "fs";
|
|
2161
|
+
import { join as join13 } from "path";
|
|
3156
2162
|
import { execSync as execSync10 } from "child_process";
|
|
3157
|
-
import
|
|
2163
|
+
import chalk13 from "chalk";
|
|
3158
2164
|
var CHECKS = [
|
|
3159
2165
|
{ name: "git", command: "git", versionFlag: "--version", required: true },
|
|
3160
2166
|
{ name: "docker", command: "docker", versionFlag: "--version", required: true },
|
|
@@ -3209,7 +2215,7 @@ function versionMatches(actual, expected) {
|
|
|
3209
2215
|
const actualClean = extractVersion2(actual);
|
|
3210
2216
|
return actualClean.startsWith(expected) || expected.startsWith(actualClean);
|
|
3211
2217
|
}
|
|
3212
|
-
var doctorCommand = new
|
|
2218
|
+
var doctorCommand = new Command13("doctor").description("Check required dependencies and tool versions from hub.yaml").action(async () => {
|
|
3213
2219
|
const hubDir = process.cwd();
|
|
3214
2220
|
let config;
|
|
3215
2221
|
try {
|
|
@@ -3217,42 +2223,42 @@ var doctorCommand = new Command14("doctor").description("Check required dependen
|
|
|
3217
2223
|
} catch {
|
|
3218
2224
|
config = null;
|
|
3219
2225
|
}
|
|
3220
|
-
console.log(
|
|
2226
|
+
console.log(chalk13.blue("\nChecking dependencies\n"));
|
|
3221
2227
|
let allOk = true;
|
|
3222
2228
|
const required = CHECKS.filter((c) => c.required);
|
|
3223
2229
|
const recommended = CHECKS.filter((c) => !c.required);
|
|
3224
|
-
console.log(
|
|
2230
|
+
console.log(chalk13.cyan("Required:"));
|
|
3225
2231
|
for (const check of required) {
|
|
3226
2232
|
const result = checkCommand(check);
|
|
3227
2233
|
if (result.found) {
|
|
3228
|
-
console.log(
|
|
2234
|
+
console.log(chalk13.green(` \u2713 ${check.name}: ${result.version}`));
|
|
3229
2235
|
} else {
|
|
3230
|
-
console.log(
|
|
2236
|
+
console.log(chalk13.red(` \u2717 ${check.name}: not found`));
|
|
3231
2237
|
allOk = false;
|
|
3232
2238
|
}
|
|
3233
2239
|
}
|
|
3234
2240
|
console.log();
|
|
3235
|
-
console.log(
|
|
2241
|
+
console.log(chalk13.cyan("Recommended:"));
|
|
3236
2242
|
for (const check of recommended) {
|
|
3237
2243
|
const result = checkCommand(check);
|
|
3238
2244
|
if (result.found) {
|
|
3239
|
-
console.log(
|
|
2245
|
+
console.log(chalk13.green(` \u2713 ${check.name}: ${result.version}`));
|
|
3240
2246
|
} else {
|
|
3241
|
-
console.log(
|
|
2247
|
+
console.log(chalk13.dim(` - ${check.name}: not found`));
|
|
3242
2248
|
}
|
|
3243
2249
|
}
|
|
3244
2250
|
if (config?.tools && Object.keys(config.tools).length > 0) {
|
|
3245
2251
|
console.log();
|
|
3246
|
-
console.log(
|
|
2252
|
+
console.log(chalk13.cyan("Hub tools (from hub.yaml):"));
|
|
3247
2253
|
for (const [tool, expected] of Object.entries(config.tools)) {
|
|
3248
2254
|
const actual = getToolVersion(tool);
|
|
3249
2255
|
if (!actual) {
|
|
3250
|
-
console.log(
|
|
2256
|
+
console.log(chalk13.red(` \u2717 ${tool}: not found (expected ${expected})`));
|
|
3251
2257
|
allOk = false;
|
|
3252
2258
|
} else if (versionMatches(actual, expected)) {
|
|
3253
|
-
console.log(
|
|
2259
|
+
console.log(chalk13.green(` \u2713 ${tool}: ${actual} (expected ${expected})`));
|
|
3254
2260
|
} else {
|
|
3255
|
-
console.log(
|
|
2261
|
+
console.log(chalk13.yellow(` \u26A0 ${tool}: ${actual} (expected ${expected})`));
|
|
3256
2262
|
}
|
|
3257
2263
|
}
|
|
3258
2264
|
}
|
|
@@ -3262,22 +2268,22 @@ var doctorCommand = new Command14("doctor").description("Check required dependen
|
|
|
3262
2268
|
);
|
|
3263
2269
|
if (reposWithTools.length > 0) {
|
|
3264
2270
|
console.log();
|
|
3265
|
-
console.log(
|
|
2271
|
+
console.log(chalk13.cyan("Repo-specific tools:"));
|
|
3266
2272
|
for (const repo of reposWithTools) {
|
|
3267
|
-
const repoDir =
|
|
3268
|
-
if (!
|
|
3269
|
-
console.log(
|
|
2273
|
+
const repoDir = join13(hubDir, repo.path);
|
|
2274
|
+
if (!existsSync11(repoDir)) {
|
|
2275
|
+
console.log(chalk13.dim(` ${repo.name}: not cloned, skipping`));
|
|
3270
2276
|
continue;
|
|
3271
2277
|
}
|
|
3272
|
-
console.log(
|
|
2278
|
+
console.log(chalk13.yellow(` \u25B8 ${repo.name}`));
|
|
3273
2279
|
for (const [tool, expected] of Object.entries(repo.tools)) {
|
|
3274
2280
|
const actual = getToolVersion(tool);
|
|
3275
2281
|
if (!actual) {
|
|
3276
|
-
console.log(
|
|
2282
|
+
console.log(chalk13.red(` \u2717 ${tool}: not found (expected ${expected})`));
|
|
3277
2283
|
} else if (versionMatches(actual, expected)) {
|
|
3278
|
-
console.log(
|
|
2284
|
+
console.log(chalk13.green(` \u2713 ${tool}: ${actual} (expected ${expected})`));
|
|
3279
2285
|
} else {
|
|
3280
|
-
console.log(
|
|
2286
|
+
console.log(chalk13.yellow(` \u26A0 ${tool}: ${actual} (expected ${expected})`));
|
|
3281
2287
|
}
|
|
3282
2288
|
}
|
|
3283
2289
|
}
|
|
@@ -3285,23 +2291,23 @@ var doctorCommand = new Command14("doctor").description("Check required dependen
|
|
|
3285
2291
|
}
|
|
3286
2292
|
console.log();
|
|
3287
2293
|
if (allOk) {
|
|
3288
|
-
console.log(
|
|
2294
|
+
console.log(chalk13.green("All checks passed!\n"));
|
|
3289
2295
|
} else {
|
|
3290
|
-
console.log(
|
|
2296
|
+
console.log(chalk13.red("Some checks failed.\n"));
|
|
3291
2297
|
if (config?.tools) {
|
|
3292
|
-
console.log(
|
|
2298
|
+
console.log(chalk13.cyan("Fix with: hub tools install\n"));
|
|
3293
2299
|
}
|
|
3294
2300
|
process.exit(1);
|
|
3295
2301
|
}
|
|
3296
2302
|
});
|
|
3297
2303
|
|
|
3298
2304
|
// src/commands/tools.ts
|
|
3299
|
-
import { Command as
|
|
3300
|
-
import { existsSync as
|
|
3301
|
-
import { writeFile as
|
|
3302
|
-
import { join as
|
|
2305
|
+
import { Command as Command14 } from "commander";
|
|
2306
|
+
import { existsSync as existsSync12 } from "fs";
|
|
2307
|
+
import { writeFile as writeFile9 } from "fs/promises";
|
|
2308
|
+
import { join as join14 } from "path";
|
|
3303
2309
|
import { execSync as execSync11 } from "child_process";
|
|
3304
|
-
import
|
|
2310
|
+
import chalk14 from "chalk";
|
|
3305
2311
|
function hasMise2() {
|
|
3306
2312
|
try {
|
|
3307
2313
|
execSync11("mise --version", { stdio: ["pipe", "pipe", "pipe"] });
|
|
@@ -3338,148 +2344,148 @@ async function generateMiseFiles(config, hubDir) {
|
|
|
3338
2344
|
let count = 0;
|
|
3339
2345
|
if (config.tools && Object.keys(config.tools).length > 0) {
|
|
3340
2346
|
const content = generateMiseToml2(config.tools, config.mise_settings);
|
|
3341
|
-
await
|
|
3342
|
-
console.log(
|
|
2347
|
+
await writeFile9(join14(hubDir, ".mise.toml"), content, "utf-8");
|
|
2348
|
+
console.log(chalk14.green(` Generated .mise.toml (${Object.keys(config.tools).length} tools)`));
|
|
3343
2349
|
count++;
|
|
3344
2350
|
}
|
|
3345
2351
|
for (const repo of config.repos) {
|
|
3346
2352
|
if (!repo.tools || Object.keys(repo.tools).length === 0) continue;
|
|
3347
|
-
const repoDir =
|
|
3348
|
-
if (!
|
|
3349
|
-
console.log(
|
|
2353
|
+
const repoDir = join14(hubDir, repo.path);
|
|
2354
|
+
if (!existsSync12(repoDir)) {
|
|
2355
|
+
console.log(chalk14.dim(` ${repo.name}: not cloned, skipping`));
|
|
3350
2356
|
continue;
|
|
3351
2357
|
}
|
|
3352
2358
|
const merged = mergeTools(config.tools || {}, repo.tools);
|
|
3353
2359
|
const content = generateMiseToml2(merged);
|
|
3354
|
-
await
|
|
3355
|
-
console.log(
|
|
2360
|
+
await writeFile9(join14(repoDir, ".mise.toml"), content, "utf-8");
|
|
2361
|
+
console.log(chalk14.green(` ${repo.name}: Generated .mise.toml (${Object.keys(merged).length} tools)`));
|
|
3356
2362
|
count++;
|
|
3357
2363
|
}
|
|
3358
2364
|
return count;
|
|
3359
2365
|
}
|
|
3360
|
-
var toolsCommand = new
|
|
3361
|
-
new
|
|
2366
|
+
var toolsCommand = new Command14("tools").description("Manage development tool versions via mise").addCommand(
|
|
2367
|
+
new Command14("install").description("Install all tools defined in hub.yaml using mise").option("--generate", "Generate .mise.toml files before installing").action(async (opts) => {
|
|
3362
2368
|
const hubDir = process.cwd();
|
|
3363
2369
|
const config = await loadHubConfig(hubDir);
|
|
3364
2370
|
if (!hasMise2()) {
|
|
3365
|
-
console.log(
|
|
3366
|
-
console.log(
|
|
3367
|
-
console.log(
|
|
2371
|
+
console.log(chalk14.red("\nmise is not installed."));
|
|
2372
|
+
console.log(chalk14.cyan("Install with: curl https://mise.run | sh"));
|
|
2373
|
+
console.log(chalk14.cyan("Or: brew install mise\n"));
|
|
3368
2374
|
return;
|
|
3369
2375
|
}
|
|
3370
2376
|
if (!config.tools && !config.repos.some((r) => r.tools)) {
|
|
3371
|
-
console.log(
|
|
2377
|
+
console.log(chalk14.yellow("\nNo tools defined in hub.yaml\n"));
|
|
3372
2378
|
return;
|
|
3373
2379
|
}
|
|
3374
|
-
console.log(
|
|
2380
|
+
console.log(chalk14.blue("\n\u2501\u2501\u2501 Installing tools \u2501\u2501\u2501\n"));
|
|
3375
2381
|
if (opts.generate) {
|
|
3376
2382
|
await generateMiseFiles(config, hubDir);
|
|
3377
2383
|
console.log();
|
|
3378
2384
|
}
|
|
3379
2385
|
if (config.tools && Object.keys(config.tools).length > 0) {
|
|
3380
|
-
console.log(
|
|
2386
|
+
console.log(chalk14.cyan("Installing hub-level tools..."));
|
|
3381
2387
|
try {
|
|
3382
2388
|
execSync11("mise trust && mise install", {
|
|
3383
2389
|
cwd: hubDir,
|
|
3384
2390
|
stdio: "inherit"
|
|
3385
2391
|
});
|
|
3386
|
-
console.log(
|
|
2392
|
+
console.log(chalk14.green(" Hub tools installed\n"));
|
|
3387
2393
|
} catch {
|
|
3388
|
-
console.log(
|
|
2394
|
+
console.log(chalk14.red(" Failed to install hub tools\n"));
|
|
3389
2395
|
}
|
|
3390
2396
|
}
|
|
3391
2397
|
for (const repo of config.repos) {
|
|
3392
2398
|
if (!repo.tools || Object.keys(repo.tools).length === 0) continue;
|
|
3393
|
-
const repoDir =
|
|
3394
|
-
if (!
|
|
3395
|
-
console.log(
|
|
2399
|
+
const repoDir = join14(hubDir, repo.path);
|
|
2400
|
+
if (!existsSync12(repoDir)) {
|
|
2401
|
+
console.log(chalk14.dim(` ${repo.name}: not cloned, skipping`));
|
|
3396
2402
|
continue;
|
|
3397
2403
|
}
|
|
3398
|
-
console.log(
|
|
2404
|
+
console.log(chalk14.yellow(`\u25B8 ${repo.name}`));
|
|
3399
2405
|
try {
|
|
3400
2406
|
execSync11("mise trust 2>/dev/null; mise install", {
|
|
3401
2407
|
cwd: repoDir,
|
|
3402
2408
|
stdio: "inherit"
|
|
3403
2409
|
});
|
|
3404
|
-
console.log(
|
|
2410
|
+
console.log(chalk14.green(" Tools installed"));
|
|
3405
2411
|
} catch {
|
|
3406
|
-
console.log(
|
|
2412
|
+
console.log(chalk14.red(" Failed to install tools"));
|
|
3407
2413
|
}
|
|
3408
2414
|
}
|
|
3409
|
-
console.log(
|
|
3410
|
-
console.log(
|
|
2415
|
+
console.log(chalk14.green("\nAll tools installed!\n"));
|
|
2416
|
+
console.log(chalk14.cyan("Make sure mise is activated in your shell:"));
|
|
3411
2417
|
console.log(' eval "$(mise activate zsh)" # zsh');
|
|
3412
2418
|
console.log(' eval "$(mise activate bash)" # bash\n');
|
|
3413
2419
|
})
|
|
3414
2420
|
).addCommand(
|
|
3415
|
-
new
|
|
2421
|
+
new Command14("generate").description("Generate .mise.toml files from hub.yaml").action(async () => {
|
|
3416
2422
|
const hubDir = process.cwd();
|
|
3417
2423
|
const config = await loadHubConfig(hubDir);
|
|
3418
2424
|
if (!config.tools && !config.repos.some((r) => r.tools)) {
|
|
3419
|
-
console.log(
|
|
2425
|
+
console.log(chalk14.yellow("\nNo tools defined in hub.yaml\n"));
|
|
3420
2426
|
return;
|
|
3421
2427
|
}
|
|
3422
|
-
console.log(
|
|
2428
|
+
console.log(chalk14.blue("\n\u2501\u2501\u2501 Generating .mise.toml files \u2501\u2501\u2501\n"));
|
|
3423
2429
|
const count = await generateMiseFiles(config, hubDir);
|
|
3424
|
-
console.log(
|
|
2430
|
+
console.log(chalk14.green(`
|
|
3425
2431
|
Generated ${count} .mise.toml file(s)
|
|
3426
2432
|
`));
|
|
3427
|
-
console.log(
|
|
2433
|
+
console.log(chalk14.cyan("Install with: hub tools install\n"));
|
|
3428
2434
|
})
|
|
3429
2435
|
).addCommand(
|
|
3430
|
-
new
|
|
2436
|
+
new Command14("check").description("Verify installed tool versions match hub.yaml").action(async () => {
|
|
3431
2437
|
const hubDir = process.cwd();
|
|
3432
2438
|
const config = await loadHubConfig(hubDir);
|
|
3433
2439
|
if (!config.tools && !config.repos.some((r) => r.tools)) {
|
|
3434
|
-
console.log(
|
|
2440
|
+
console.log(chalk14.yellow("\nNo tools defined in hub.yaml\n"));
|
|
3435
2441
|
return;
|
|
3436
2442
|
}
|
|
3437
|
-
console.log(
|
|
2443
|
+
console.log(chalk14.blue("\n\u2501\u2501\u2501 Checking tool versions \u2501\u2501\u2501\n"));
|
|
3438
2444
|
let allOk = true;
|
|
3439
2445
|
if (config.tools) {
|
|
3440
|
-
console.log(
|
|
2446
|
+
console.log(chalk14.cyan("Hub tools:"));
|
|
3441
2447
|
for (const [tool, expected] of Object.entries(config.tools)) {
|
|
3442
2448
|
const actual = getInstalledVersion(tool);
|
|
3443
2449
|
if (!actual) {
|
|
3444
|
-
console.log(
|
|
2450
|
+
console.log(chalk14.red(` \u2717 ${tool}: not found (expected ${expected})`));
|
|
3445
2451
|
allOk = false;
|
|
3446
2452
|
} else if (actual.includes(expected) || expected.includes(extractVersion(actual))) {
|
|
3447
|
-
console.log(
|
|
2453
|
+
console.log(chalk14.green(` \u2713 ${tool} ${expected}`));
|
|
3448
2454
|
} else {
|
|
3449
|
-
console.log(
|
|
2455
|
+
console.log(chalk14.yellow(` \u26A0 ${tool}: ${extractVersion(actual)} (expected ${expected})`));
|
|
3450
2456
|
allOk = false;
|
|
3451
2457
|
}
|
|
3452
2458
|
}
|
|
3453
2459
|
}
|
|
3454
2460
|
for (const repo of config.repos) {
|
|
3455
2461
|
if (!repo.tools || Object.keys(repo.tools).length === 0) continue;
|
|
3456
|
-
const repoDir =
|
|
3457
|
-
if (!
|
|
3458
|
-
console.log(
|
|
2462
|
+
const repoDir = join14(hubDir, repo.path);
|
|
2463
|
+
if (!existsSync12(repoDir)) {
|
|
2464
|
+
console.log(chalk14.dim(`
|
|
3459
2465
|
${repo.name}: not cloned`));
|
|
3460
2466
|
continue;
|
|
3461
2467
|
}
|
|
3462
|
-
console.log(
|
|
2468
|
+
console.log(chalk14.yellow(`
|
|
3463
2469
|
\u25B8 ${repo.name}`));
|
|
3464
2470
|
for (const [tool, expected] of Object.entries(repo.tools)) {
|
|
3465
2471
|
const actual = getInstalledVersion(tool);
|
|
3466
2472
|
if (!actual) {
|
|
3467
|
-
console.log(
|
|
2473
|
+
console.log(chalk14.red(` \u2717 ${tool}: not found (expected ${expected})`));
|
|
3468
2474
|
allOk = false;
|
|
3469
2475
|
} else if (actual.includes(expected) || expected.includes(extractVersion(actual))) {
|
|
3470
|
-
console.log(
|
|
2476
|
+
console.log(chalk14.green(` \u2713 ${tool} ${expected}`));
|
|
3471
2477
|
} else {
|
|
3472
|
-
console.log(
|
|
2478
|
+
console.log(chalk14.yellow(` \u26A0 ${tool}: ${extractVersion(actual)} (expected ${expected})`));
|
|
3473
2479
|
allOk = false;
|
|
3474
2480
|
}
|
|
3475
2481
|
}
|
|
3476
2482
|
}
|
|
3477
2483
|
console.log();
|
|
3478
2484
|
if (allOk) {
|
|
3479
|
-
console.log(
|
|
2485
|
+
console.log(chalk14.green("All tool versions match!\n"));
|
|
3480
2486
|
} else {
|
|
3481
|
-
console.log(
|
|
3482
|
-
console.log(
|
|
2487
|
+
console.log(chalk14.red("Some tools are missing or have wrong versions.\n"));
|
|
2488
|
+
console.log(chalk14.cyan("Fix with: hub tools install --generate\n"));
|
|
3483
2489
|
}
|
|
3484
2490
|
})
|
|
3485
2491
|
);
|
|
@@ -3513,24 +2519,24 @@ function extractVersion(s) {
|
|
|
3513
2519
|
}
|
|
3514
2520
|
|
|
3515
2521
|
// src/commands/memory.ts
|
|
3516
|
-
import { Command as
|
|
3517
|
-
import { existsSync as
|
|
3518
|
-
import { mkdir as
|
|
3519
|
-
import { join as
|
|
3520
|
-
import
|
|
2522
|
+
import { Command as Command15 } from "commander";
|
|
2523
|
+
import { existsSync as existsSync13 } from "fs";
|
|
2524
|
+
import { mkdir as mkdir8, readdir as readdir5, readFile as readFile7, writeFile as writeFile10, rm as rm5, appendFile as appendFile2 } from "fs/promises";
|
|
2525
|
+
import { join as join15, resolve as resolve5, basename as basename2 } from "path";
|
|
2526
|
+
import chalk15 from "chalk";
|
|
3521
2527
|
async function ensureLanceDbIgnored(memoriesDir, hubDir) {
|
|
3522
2528
|
const relative = memoriesDir.replace(hubDir + "/", "");
|
|
3523
2529
|
const pattern = `${relative}/.lancedb/`;
|
|
3524
|
-
const gitignorePath =
|
|
3525
|
-
if (
|
|
3526
|
-
const content = await
|
|
2530
|
+
const gitignorePath = join15(hubDir, ".gitignore");
|
|
2531
|
+
if (existsSync13(gitignorePath)) {
|
|
2532
|
+
const content = await readFile7(gitignorePath, "utf-8");
|
|
3527
2533
|
if (content.includes(".lancedb")) return;
|
|
3528
2534
|
await appendFile2(gitignorePath, `
|
|
3529
2535
|
# Memory vector store (generated)
|
|
3530
2536
|
${pattern}
|
|
3531
2537
|
`);
|
|
3532
2538
|
} else {
|
|
3533
|
-
await
|
|
2539
|
+
await writeFile10(gitignorePath, `# Memory vector store (generated)
|
|
3534
2540
|
${pattern}
|
|
3535
2541
|
`, "utf-8");
|
|
3536
2542
|
}
|
|
@@ -3543,7 +2549,7 @@ var VALID_CATEGORIES = [
|
|
|
3543
2549
|
"gotchas"
|
|
3544
2550
|
];
|
|
3545
2551
|
function getMemoriesPath(hubDir, configPath) {
|
|
3546
|
-
return
|
|
2552
|
+
return resolve5(hubDir, configPath || "memories");
|
|
3547
2553
|
}
|
|
3548
2554
|
function parseFrontmatter(raw) {
|
|
3549
2555
|
const match = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
@@ -3575,20 +2581,20 @@ function buildFrontmatter(data) {
|
|
|
3575
2581
|
return lines.join("\n");
|
|
3576
2582
|
}
|
|
3577
2583
|
async function listMemories(memoriesDir, opts) {
|
|
3578
|
-
if (!
|
|
3579
|
-
console.log(
|
|
2584
|
+
if (!existsSync13(memoriesDir)) {
|
|
2585
|
+
console.log(chalk15.dim(" No memories directory found."));
|
|
3580
2586
|
return;
|
|
3581
2587
|
}
|
|
3582
2588
|
let total = 0;
|
|
3583
2589
|
for (const cat of VALID_CATEGORIES) {
|
|
3584
2590
|
if (opts.category && opts.category !== cat) continue;
|
|
3585
|
-
const catDir =
|
|
3586
|
-
if (!
|
|
3587
|
-
const files = (await
|
|
2591
|
+
const catDir = join15(memoriesDir, cat);
|
|
2592
|
+
if (!existsSync13(catDir)) continue;
|
|
2593
|
+
const files = (await readdir5(catDir)).filter((f) => f.endsWith(".md"));
|
|
3588
2594
|
if (files.length === 0) continue;
|
|
3589
2595
|
const entries = [];
|
|
3590
2596
|
for (const file of files) {
|
|
3591
|
-
const raw = await
|
|
2597
|
+
const raw = await readFile7(join15(catDir, file), "utf-8");
|
|
3592
2598
|
const { data } = parseFrontmatter(raw);
|
|
3593
2599
|
const status = data.status || "active";
|
|
3594
2600
|
if (opts.status && status !== opts.status) continue;
|
|
@@ -3601,29 +2607,29 @@ async function listMemories(memoriesDir, opts) {
|
|
|
3601
2607
|
});
|
|
3602
2608
|
}
|
|
3603
2609
|
if (entries.length === 0) continue;
|
|
3604
|
-
console.log(
|
|
2610
|
+
console.log(chalk15.cyan(`
|
|
3605
2611
|
${cat} (${entries.length})`));
|
|
3606
2612
|
for (const e of entries) {
|
|
3607
|
-
const statusIcon = e.status === "active" ?
|
|
3608
|
-
const tags = e.tags.length > 0 ?
|
|
3609
|
-
console.log(` ${statusIcon} ${
|
|
2613
|
+
const statusIcon = e.status === "active" ? chalk15.green("\u25CF") : chalk15.dim("\u25CB");
|
|
2614
|
+
const tags = e.tags.length > 0 ? chalk15.dim(` [${e.tags.join(", ")}]`) : "";
|
|
2615
|
+
console.log(` ${statusIcon} ${chalk15.yellow(e.id)} \u2014 ${e.title} ${chalk15.dim(`(${e.date})`)}${tags}`);
|
|
3610
2616
|
}
|
|
3611
2617
|
total += entries.length;
|
|
3612
2618
|
}
|
|
3613
2619
|
if (total === 0) {
|
|
3614
|
-
console.log(
|
|
2620
|
+
console.log(chalk15.dim(" No memories found."));
|
|
3615
2621
|
} else {
|
|
3616
|
-
console.log(
|
|
2622
|
+
console.log(chalk15.green(`
|
|
3617
2623
|
Total: ${total} memories
|
|
3618
2624
|
`));
|
|
3619
2625
|
}
|
|
3620
2626
|
}
|
|
3621
|
-
var memoryCommand = new
|
|
3622
|
-
new
|
|
2627
|
+
var memoryCommand = new Command15("memory").description("Manage team memories \u2014 persistent knowledge base for AI context").addCommand(
|
|
2628
|
+
new Command15("add").description("Create a new memory entry").argument("<category>", `Category: ${VALID_CATEGORIES.join(", ")}`).argument("<title>", "Memory title").option("-c, --content <content>", "Memory content (or provide via stdin)").option("-t, --tags <tags>", "Comma-separated tags").option("-a, --author <author>", "Author name").action(
|
|
3623
2629
|
async (category, title, opts) => {
|
|
3624
2630
|
if (!VALID_CATEGORIES.includes(category)) {
|
|
3625
2631
|
console.log(
|
|
3626
|
-
|
|
2632
|
+
chalk15.red(`
|
|
3627
2633
|
Invalid category: ${category}. Valid: ${VALID_CATEGORIES.join(", ")}
|
|
3628
2634
|
`)
|
|
3629
2635
|
);
|
|
@@ -3637,13 +2643,13 @@ var memoryCommand = new Command16("memory").description("Manage team memories \u
|
|
|
3637
2643
|
} catch {
|
|
3638
2644
|
memoriesDir = getMemoriesPath(hubDir);
|
|
3639
2645
|
}
|
|
3640
|
-
const catDir =
|
|
3641
|
-
await
|
|
2646
|
+
const catDir = join15(memoriesDir, category);
|
|
2647
|
+
await mkdir8(catDir, { recursive: true });
|
|
3642
2648
|
await ensureLanceDbIgnored(memoriesDir, hubDir);
|
|
3643
2649
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
3644
2650
|
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
3645
2651
|
const id = `${date}-${slug}`;
|
|
3646
|
-
const filePath =
|
|
2652
|
+
const filePath = join15(catDir, `${id}.md`);
|
|
3647
2653
|
const fm = {
|
|
3648
2654
|
title,
|
|
3649
2655
|
category,
|
|
@@ -3663,19 +2669,19 @@ var memoryCommand = new Command16("memory").description("Manage team memories \u
|
|
|
3663
2669
|
|
|
3664
2670
|
${content}
|
|
3665
2671
|
`;
|
|
3666
|
-
await
|
|
3667
|
-
console.log(
|
|
2672
|
+
await writeFile10(filePath, fileContent, "utf-8");
|
|
2673
|
+
console.log(chalk15.green(`
|
|
3668
2674
|
Created: ${category}/${id}.md`));
|
|
3669
|
-
console.log(
|
|
2675
|
+
console.log(chalk15.dim(` Path: ${filePath}`));
|
|
3670
2676
|
if (!opts.content) {
|
|
3671
|
-
console.log(
|
|
2677
|
+
console.log(chalk15.yellow(" Edit the file to add content.\n"));
|
|
3672
2678
|
} else {
|
|
3673
2679
|
console.log();
|
|
3674
2680
|
}
|
|
3675
2681
|
}
|
|
3676
2682
|
)
|
|
3677
2683
|
).addCommand(
|
|
3678
|
-
new
|
|
2684
|
+
new Command15("list").description("List all memories").option("-c, --category <category>", "Filter by category").option("-s, --status <status>", "Filter by status (active, archived, superseded)").action(async (opts) => {
|
|
3679
2685
|
const hubDir = process.cwd();
|
|
3680
2686
|
let memoriesDir;
|
|
3681
2687
|
try {
|
|
@@ -3684,11 +2690,11 @@ ${content}
|
|
|
3684
2690
|
} catch {
|
|
3685
2691
|
memoriesDir = getMemoriesPath(hubDir);
|
|
3686
2692
|
}
|
|
3687
|
-
console.log(
|
|
2693
|
+
console.log(chalk15.blue("\nTeam Memories"));
|
|
3688
2694
|
await listMemories(memoriesDir, opts);
|
|
3689
2695
|
})
|
|
3690
2696
|
).addCommand(
|
|
3691
|
-
new
|
|
2697
|
+
new Command15("archive").description("Archive a memory (soft-delete)").argument("<id>", "Memory ID (filename without .md)").action(async (id) => {
|
|
3692
2698
|
const hubDir = process.cwd();
|
|
3693
2699
|
let memoriesDir;
|
|
3694
2700
|
try {
|
|
@@ -3699,28 +2705,28 @@ ${content}
|
|
|
3699
2705
|
}
|
|
3700
2706
|
let found = false;
|
|
3701
2707
|
for (const cat of VALID_CATEGORIES) {
|
|
3702
|
-
const filePath =
|
|
3703
|
-
if (!
|
|
3704
|
-
const raw = await
|
|
2708
|
+
const filePath = join15(memoriesDir, cat, `${id}.md`);
|
|
2709
|
+
if (!existsSync13(filePath)) continue;
|
|
2710
|
+
const raw = await readFile7(filePath, "utf-8");
|
|
3705
2711
|
const { data, content } = parseFrontmatter(raw);
|
|
3706
2712
|
data.status = "archived";
|
|
3707
2713
|
const updated = `${buildFrontmatter(data)}
|
|
3708
2714
|
${content}`;
|
|
3709
|
-
await
|
|
3710
|
-
console.log(
|
|
2715
|
+
await writeFile10(filePath, updated, "utf-8");
|
|
2716
|
+
console.log(chalk15.green(`
|
|
3711
2717
|
Archived: ${cat}/${id}.md
|
|
3712
2718
|
`));
|
|
3713
2719
|
found = true;
|
|
3714
2720
|
break;
|
|
3715
2721
|
}
|
|
3716
2722
|
if (!found) {
|
|
3717
|
-
console.log(
|
|
2723
|
+
console.log(chalk15.red(`
|
|
3718
2724
|
Memory "${id}" not found.
|
|
3719
2725
|
`));
|
|
3720
2726
|
}
|
|
3721
2727
|
})
|
|
3722
2728
|
).addCommand(
|
|
3723
|
-
new
|
|
2729
|
+
new Command15("remove").description("Permanently delete a memory").argument("<id>", "Memory ID (filename without .md)").action(async (id) => {
|
|
3724
2730
|
const hubDir = process.cwd();
|
|
3725
2731
|
let memoriesDir;
|
|
3726
2732
|
try {
|
|
@@ -3731,17 +2737,17 @@ ${content}`;
|
|
|
3731
2737
|
}
|
|
3732
2738
|
let found = false;
|
|
3733
2739
|
for (const cat of VALID_CATEGORIES) {
|
|
3734
|
-
const filePath =
|
|
3735
|
-
if (!
|
|
2740
|
+
const filePath = join15(memoriesDir, cat, `${id}.md`);
|
|
2741
|
+
if (!existsSync13(filePath)) continue;
|
|
3736
2742
|
await rm5(filePath);
|
|
3737
|
-
console.log(
|
|
2743
|
+
console.log(chalk15.green(`
|
|
3738
2744
|
Removed: ${cat}/${id}.md
|
|
3739
2745
|
`));
|
|
3740
2746
|
found = true;
|
|
3741
2747
|
break;
|
|
3742
2748
|
}
|
|
3743
2749
|
if (!found) {
|
|
3744
|
-
console.log(
|
|
2750
|
+
console.log(chalk15.red(`
|
|
3745
2751
|
Memory "${id}" not found.
|
|
3746
2752
|
`));
|
|
3747
2753
|
}
|
|
@@ -3749,16 +2755,16 @@ ${content}`;
|
|
|
3749
2755
|
);
|
|
3750
2756
|
|
|
3751
2757
|
// src/commands/update.ts
|
|
3752
|
-
import { Command as
|
|
2758
|
+
import { Command as Command16 } from "commander";
|
|
3753
2759
|
import { execSync as execSync12 } from "child_process";
|
|
3754
2760
|
import { readFileSync } from "fs";
|
|
3755
|
-
import { join as
|
|
2761
|
+
import { join as join16, dirname } from "path";
|
|
3756
2762
|
import { fileURLToPath } from "url";
|
|
3757
|
-
import
|
|
2763
|
+
import chalk16 from "chalk";
|
|
3758
2764
|
var PACKAGE_NAME = "@arvoretech/hub";
|
|
3759
2765
|
function getCurrentVersion() {
|
|
3760
2766
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
3761
|
-
const pkgPath =
|
|
2767
|
+
const pkgPath = join16(__dirname, "..", "package.json");
|
|
3762
2768
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
3763
2769
|
return pkg.version;
|
|
3764
2770
|
}
|
|
@@ -3781,77 +2787,77 @@ function detectPackageManager() {
|
|
|
3781
2787
|
}
|
|
3782
2788
|
}
|
|
3783
2789
|
}
|
|
3784
|
-
var updateCommand = new
|
|
2790
|
+
var updateCommand = new Command16("update").description("Update hub CLI to the latest version").option("--check", "Only check for updates without installing").action(async (opts) => {
|
|
3785
2791
|
const currentVersion = getCurrentVersion();
|
|
3786
|
-
console.log(
|
|
2792
|
+
console.log(chalk16.blue(`
|
|
3787
2793
|
Current version: ${currentVersion}`));
|
|
3788
2794
|
let latestVersion;
|
|
3789
2795
|
try {
|
|
3790
2796
|
latestVersion = await getLatestVersion();
|
|
3791
2797
|
} catch (err) {
|
|
3792
|
-
console.log(
|
|
2798
|
+
console.log(chalk16.red(` Failed to check for updates: ${err.message}
|
|
3793
2799
|
`));
|
|
3794
2800
|
return;
|
|
3795
2801
|
}
|
|
3796
|
-
console.log(
|
|
2802
|
+
console.log(chalk16.blue(` Latest version: ${latestVersion}`));
|
|
3797
2803
|
if (currentVersion === latestVersion) {
|
|
3798
|
-
console.log(
|
|
2804
|
+
console.log(chalk16.green("\n You're already on the latest version.\n"));
|
|
3799
2805
|
return;
|
|
3800
2806
|
}
|
|
3801
|
-
console.log(
|
|
2807
|
+
console.log(chalk16.yellow(`
|
|
3802
2808
|
Update available: ${currentVersion} \u2192 ${latestVersion}`));
|
|
3803
2809
|
if (opts.check) {
|
|
3804
2810
|
const pm2 = detectPackageManager();
|
|
3805
|
-
console.log(
|
|
2811
|
+
console.log(chalk16.dim(`
|
|
3806
2812
|
Run 'hub update' or '${pm2} install -g ${PACKAGE_NAME}@latest' to update.
|
|
3807
2813
|
`));
|
|
3808
2814
|
return;
|
|
3809
2815
|
}
|
|
3810
2816
|
const pm = detectPackageManager();
|
|
3811
2817
|
const installCmd = pm === "pnpm" ? `pnpm install -g ${PACKAGE_NAME}@latest` : pm === "yarn" ? `yarn global add ${PACKAGE_NAME}@latest` : `npm install -g ${PACKAGE_NAME}@latest`;
|
|
3812
|
-
console.log(
|
|
2818
|
+
console.log(chalk16.cyan(`
|
|
3813
2819
|
Updating with ${pm}...
|
|
3814
2820
|
`));
|
|
3815
|
-
console.log(
|
|
2821
|
+
console.log(chalk16.dim(` $ ${installCmd}
|
|
3816
2822
|
`));
|
|
3817
2823
|
try {
|
|
3818
2824
|
execSync12(installCmd, { stdio: "inherit" });
|
|
3819
|
-
console.log(
|
|
2825
|
+
console.log(chalk16.green(`
|
|
3820
2826
|
Updated to ${latestVersion} successfully.
|
|
3821
2827
|
`));
|
|
3822
2828
|
} catch {
|
|
3823
|
-
console.log(
|
|
2829
|
+
console.log(chalk16.red(`
|
|
3824
2830
|
Update failed. Try running manually:`));
|
|
3825
|
-
console.log(
|
|
2831
|
+
console.log(chalk16.dim(` $ ${installCmd}
|
|
3826
2832
|
`));
|
|
3827
2833
|
}
|
|
3828
2834
|
});
|
|
3829
2835
|
|
|
3830
2836
|
// src/commands/directory.ts
|
|
3831
|
-
import { Command as
|
|
3832
|
-
import
|
|
2837
|
+
import { Command as Command17 } from "commander";
|
|
2838
|
+
import chalk17 from "chalk";
|
|
3833
2839
|
var BASE_URL = "https://rhm-website.vercel.app/directory";
|
|
3834
|
-
var directoryCommand = new
|
|
2840
|
+
var directoryCommand = new Command17("directory").alias("dir").description("Browse the Repo Hub directory of skills, agents, hooks, and commands").argument("[query]", "Search term").option("-t, --type <type>", "Filter by type (skill, agent, hook, command)").action(async (query, opts) => {
|
|
3835
2841
|
const params = new URLSearchParams();
|
|
3836
2842
|
if (opts?.type) params.set("type", opts.type);
|
|
3837
2843
|
if (query) params.set("q", query);
|
|
3838
2844
|
const qs = params.toString();
|
|
3839
2845
|
const url = qs ? `${BASE_URL}?${qs}` : BASE_URL;
|
|
3840
|
-
console.log(
|
|
3841
|
-
console.log(
|
|
3842
|
-
`));
|
|
3843
|
-
console.log(
|
|
3844
|
-
console.log(
|
|
3845
|
-
console.log(
|
|
3846
|
-
console.log(
|
|
3847
|
-
console.log(
|
|
2846
|
+
console.log(chalk17.blue("\n Repo Hub Directory\n"));
|
|
2847
|
+
console.log(chalk17.cyan(` ${url}
|
|
2848
|
+
`));
|
|
2849
|
+
console.log(chalk17.dim(" Install examples:"));
|
|
2850
|
+
console.log(chalk17.dim(" hub skills add <owner>/<repo>/<skill>"));
|
|
2851
|
+
console.log(chalk17.dim(" hub agents add <owner>/<repo>"));
|
|
2852
|
+
console.log(chalk17.dim(" hub hooks add <owner>/<repo>"));
|
|
2853
|
+
console.log(chalk17.dim(" hub commands add <owner>/<repo>\n"));
|
|
3848
2854
|
});
|
|
3849
2855
|
|
|
3850
2856
|
// src/index.ts
|
|
3851
|
-
var program = new
|
|
2857
|
+
var program = new Command18();
|
|
3852
2858
|
program.name("hub").description(
|
|
3853
2859
|
"Give your AI coding assistant the full picture. Multi-repo context, agent orchestration, and end-to-end workflows."
|
|
3854
|
-
).version("0.6.
|
|
2860
|
+
).version("0.6.1").enablePositionalOptions();
|
|
3855
2861
|
program.addCommand(initCommand);
|
|
3856
2862
|
program.addCommand(addRepoCommand);
|
|
3857
2863
|
program.addCommand(setupCommand);
|