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