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