@arvoretech/hub 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +446 -70
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command18 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/init.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -747,6 +747,18 @@ ${prompt.sections.after_pipeline.trim()}`);
|
|
|
747
747
|
if (prompt?.sections?.after_delivery) {
|
|
748
748
|
sections.push(`
|
|
749
749
|
${prompt.sections.after_delivery.trim()}`);
|
|
750
|
+
}
|
|
751
|
+
if (config.memory) {
|
|
752
|
+
sections.push(`
|
|
753
|
+
## Team Memory
|
|
754
|
+
|
|
755
|
+
This workspace has a team memory knowledge base available via the \`team-memory\` MCP.
|
|
756
|
+
|
|
757
|
+
**Before starting any task**, use \`search_memories\` to find relevant context \u2014 past decisions, conventions, known issues, and domain knowledge. This avoids repeating mistakes and ensures consistency with previous choices.
|
|
758
|
+
|
|
759
|
+
**After completing a task**, if you discovered something valuable (a decision, a gotcha, a convention, domain insight), use \`add_memory\` to capture it for the team.
|
|
760
|
+
|
|
761
|
+
Available tools: \`search_memories\`, \`get_memory\`, \`add_memory\`, \`list_memories\`, \`archive_memory\`, \`remove_memory\`.`);
|
|
750
762
|
}
|
|
751
763
|
sections.push(`
|
|
752
764
|
## Troubleshooting and Debugging
|
|
@@ -803,6 +815,10 @@ Follow each step sequentially, applying the role-specific instructions from the
|
|
|
803
815
|
}
|
|
804
816
|
const stepTitle = step.step.charAt(0).toUpperCase() + step.step.slice(1);
|
|
805
817
|
parts.push(`### ${stepTitle}`);
|
|
818
|
+
if (step.mode === "plan") {
|
|
819
|
+
parts.push(`**This step is a planning phase.** Do NOT make any code changes. Focus on reading, analyzing, and collaborating with the user to define requirements before proceeding.`);
|
|
820
|
+
parts.push(``);
|
|
821
|
+
}
|
|
806
822
|
if (step.agent) {
|
|
807
823
|
parts.push(`Follow the instructions from the \`agent-${step.agent}.md\` steering file.${step.output ? ` Write output to \`${step.output}\`.` : ""}`);
|
|
808
824
|
if (step.step === "refinement") {
|
|
@@ -913,6 +929,18 @@ ${prompt.sections.after_pipeline.trim()}`);
|
|
|
913
929
|
if (prompt?.sections?.after_delivery) {
|
|
914
930
|
sections.push(`
|
|
915
931
|
${prompt.sections.after_delivery.trim()}`);
|
|
932
|
+
}
|
|
933
|
+
if (config.memory) {
|
|
934
|
+
sections.push(`
|
|
935
|
+
## Team Memory
|
|
936
|
+
|
|
937
|
+
This workspace has a team memory knowledge base available via the \`team-memory\` MCP.
|
|
938
|
+
|
|
939
|
+
**Before starting any task**, use \`search_memories\` to find relevant context \u2014 past decisions, conventions, known issues, and domain knowledge. This avoids repeating mistakes and ensures consistency with previous choices.
|
|
940
|
+
|
|
941
|
+
**After completing a task**, if you discovered something valuable (a decision, a gotcha, a convention, domain insight), use \`add_memory\` to capture it for the team.
|
|
942
|
+
|
|
943
|
+
Available tools: \`search_memories\`, \`get_memory\`, \`add_memory\`, \`list_memories\`, \`archive_memory\`, \`remove_memory\`.`);
|
|
916
944
|
}
|
|
917
945
|
sections.push(`
|
|
918
946
|
## Troubleshooting and Debugging
|
|
@@ -997,6 +1025,10 @@ function buildPipelineSection(steps) {
|
|
|
997
1025
|
}
|
|
998
1026
|
const stepTitle = step.step.charAt(0).toUpperCase() + step.step.slice(1);
|
|
999
1027
|
parts.push(`### ${stepTitle}`);
|
|
1028
|
+
if (step.mode === "plan") {
|
|
1029
|
+
parts.push(`**Before starting this step, switch to Plan Mode** by calling \`SwitchMode\` with \`target_mode_id: "plan"\`. This ensures collaborative planning with the user in a read-only context before any implementation begins.`);
|
|
1030
|
+
parts.push(``);
|
|
1031
|
+
}
|
|
1000
1032
|
if (step.agent) {
|
|
1001
1033
|
parts.push(`Call the \`${step.agent}\` agent.${step.output ? ` It writes to \`${step.output}\`.` : ""}`);
|
|
1002
1034
|
if (step.step === "refinement") {
|
|
@@ -1032,6 +1064,10 @@ If any coding agent has doubts, they will write questions in their document. App
|
|
|
1032
1064
|
If any validation agent leaves comments requiring fixes, call the relevant coding agents again to address them.`);
|
|
1033
1065
|
}
|
|
1034
1066
|
}
|
|
1067
|
+
if (step.mode === "plan") {
|
|
1068
|
+
parts.push(`
|
|
1069
|
+
**After this step is complete and approved**, switch back to Agent Mode to proceed with the next step.`);
|
|
1070
|
+
}
|
|
1035
1071
|
parts.push("");
|
|
1036
1072
|
}
|
|
1037
1073
|
return parts.join("\n");
|
|
@@ -1294,6 +1330,14 @@ function buildGitignoreLines(config) {
|
|
|
1294
1330
|
"# Task documents",
|
|
1295
1331
|
"tasks/"
|
|
1296
1332
|
);
|
|
1333
|
+
if (config.memory) {
|
|
1334
|
+
const memPath = config.memory.path || "memories";
|
|
1335
|
+
lines.push(
|
|
1336
|
+
"",
|
|
1337
|
+
"# Memory vector store (generated from markdown files)",
|
|
1338
|
+
`${memPath}/.lancedb/`
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1297
1341
|
return lines;
|
|
1298
1342
|
}
|
|
1299
1343
|
var generators = {
|
|
@@ -1304,6 +1348,25 @@ var generators = {
|
|
|
1304
1348
|
var generateCommand = new Command4("generate").description("Generate editor-specific configuration files from hub.yaml").option("-e, --editor <editor>", "Target editor (cursor, claude-code, kiro)", "cursor").action(async (opts) => {
|
|
1305
1349
|
const hubDir = process.cwd();
|
|
1306
1350
|
const config = await loadHubConfig(hubDir);
|
|
1351
|
+
if (config.memory) {
|
|
1352
|
+
const hasMemoryMcp = config.mcps?.some(
|
|
1353
|
+
(m) => m.name === "team-memory" || m.package === "@arvoretech/memory-mcp"
|
|
1354
|
+
);
|
|
1355
|
+
if (!hasMemoryMcp) {
|
|
1356
|
+
console.log(chalk4.red(`
|
|
1357
|
+
Error: 'memory' is configured but no memory MCP is declared in 'mcps'.
|
|
1358
|
+
`));
|
|
1359
|
+
console.log(chalk4.yellow(` Add this to your hub.yaml:
|
|
1360
|
+
`));
|
|
1361
|
+
console.log(chalk4.dim(` mcps:`));
|
|
1362
|
+
console.log(chalk4.dim(` - name: team-memory`));
|
|
1363
|
+
console.log(chalk4.dim(` package: "@arvoretech/memory-mcp"`));
|
|
1364
|
+
console.log(chalk4.dim(` env:`));
|
|
1365
|
+
console.log(chalk4.dim(` MEMORY_PATH: ${config.memory.path || "./memories"}
|
|
1366
|
+
`));
|
|
1367
|
+
process.exit(1);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1307
1370
|
const generator = generators[opts.editor];
|
|
1308
1371
|
if (!generator) {
|
|
1309
1372
|
console.log(
|
|
@@ -1892,17 +1955,32 @@ async function listLocalSkills(hubDir) {
|
|
|
1892
1955
|
}
|
|
1893
1956
|
return skills;
|
|
1894
1957
|
}
|
|
1895
|
-
async function
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1958
|
+
async function findSkillFolders(rootDir) {
|
|
1959
|
+
const skillsDir = join9(rootDir, "skills");
|
|
1960
|
+
if (existsSync6(skillsDir)) {
|
|
1961
|
+
const entries = await readdir2(skillsDir);
|
|
1962
|
+
const folders = entries.filter(
|
|
1963
|
+
(f) => existsSync6(join9(skillsDir, f, "SKILL.md"))
|
|
1964
|
+
);
|
|
1965
|
+
if (folders.length > 0) return { dir: skillsDir, folders };
|
|
1899
1966
|
}
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1967
|
+
if (existsSync6(rootDir)) {
|
|
1968
|
+
const entries = await readdir2(rootDir);
|
|
1969
|
+
const folders = entries.filter(
|
|
1970
|
+
(f) => !f.startsWith(".") && f !== "node_modules" && existsSync6(join9(rootDir, f, "SKILL.md"))
|
|
1971
|
+
);
|
|
1972
|
+
if (folders.length > 0) return { dir: rootDir, folders };
|
|
1973
|
+
}
|
|
1974
|
+
if (existsSync6(join9(rootDir, "SKILL.md"))) {
|
|
1975
|
+
return { dir: join9(rootDir, ".."), folders: [rootDir.split("/").pop()] };
|
|
1976
|
+
}
|
|
1977
|
+
return { dir: skillsDir, folders: [] };
|
|
1978
|
+
}
|
|
1979
|
+
async function installSkillsFromDir(sourceSkillsDir, hubDir, opts) {
|
|
1980
|
+
const rootDir = sourceSkillsDir.endsWith("/skills") ? sourceSkillsDir.replace(/\/skills$/, "") : sourceSkillsDir;
|
|
1981
|
+
const { dir, folders: skillFolders } = await findSkillFolders(rootDir);
|
|
1904
1982
|
if (skillFolders.length === 0) {
|
|
1905
|
-
console.log(chalk8.red(" No skills found (
|
|
1983
|
+
console.log(chalk8.red(" No skills found (looked in skills/, root dirs, and SKILL.md)"));
|
|
1906
1984
|
return;
|
|
1907
1985
|
}
|
|
1908
1986
|
const toInstall = opts.skill ? skillFolders.filter((s) => s === opts.skill) : skillFolders;
|
|
@@ -1913,7 +1991,7 @@ async function installSkillsFromDir(sourceSkillsDir, hubDir, opts) {
|
|
|
1913
1991
|
const targetBase = opts.global ? join9(process.env.HOME || "~", ".cursor", "skills") : join9(hubDir, "skills");
|
|
1914
1992
|
await mkdir4(targetBase, { recursive: true });
|
|
1915
1993
|
for (const skill of toInstall) {
|
|
1916
|
-
const src = join9(
|
|
1994
|
+
const src = join9(dir, skill);
|
|
1917
1995
|
const dest = join9(targetBase, skill);
|
|
1918
1996
|
await cp2(src, dest, { recursive: true });
|
|
1919
1997
|
console.log(chalk8.green(` Installed: ${skill}`));
|
|
@@ -1952,42 +2030,80 @@ async function addFromRegistry(skillName, hubDir, opts) {
|
|
|
1952
2030
|
}
|
|
1953
2031
|
async function addFromGitHubSkill(owner, repo, skillName, hubDir, opts) {
|
|
1954
2032
|
const fullRepo = `${owner}/${repo}`;
|
|
1955
|
-
const remotePath = `skills/${skillName}`;
|
|
1956
2033
|
const targetBase = opts.global ? join9(process.env.HOME || "~", ".cursor", "skills") : join9(hubDir, "skills");
|
|
1957
2034
|
const dest = join9(targetBase, skillName);
|
|
2035
|
+
const pathsToTry = [
|
|
2036
|
+
`skills/${skillName}`,
|
|
2037
|
+
skillName
|
|
2038
|
+
];
|
|
1958
2039
|
console.log(chalk8.cyan(` Downloading ${skillName} from ${fullRepo} via GitHub API...`));
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
2040
|
+
for (const remotePath of pathsToTry) {
|
|
2041
|
+
try {
|
|
2042
|
+
await downloadDirFromGitHub(fullRepo, remotePath, dest);
|
|
2043
|
+
if (existsSync6(join9(dest, "SKILL.md"))) {
|
|
2044
|
+
console.log(chalk8.green(` Installed: ${skillName} (from ${fullRepo})`));
|
|
2045
|
+
console.log(chalk8.green(`
|
|
2046
|
+
1 skill(s) installed to ${opts.global ? "global" : "project"}
|
|
2047
|
+
`));
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
await rm(dest, { recursive: true }).catch(() => {
|
|
2051
|
+
});
|
|
2052
|
+
} catch {
|
|
1962
2053
|
await rm(dest, { recursive: true }).catch(() => {
|
|
1963
2054
|
});
|
|
1964
|
-
console.log(chalk8.red(` Skill '${skillName}' not found in ${fullRepo}/skills/`));
|
|
1965
|
-
console.log(chalk8.dim(` Check available skills: hub skills add ${fullRepo} --list`));
|
|
1966
|
-
return;
|
|
1967
2055
|
}
|
|
1968
|
-
console.log(chalk8.green(` Installed: ${skillName} (from ${fullRepo})`));
|
|
1969
|
-
console.log(chalk8.green(`
|
|
1970
|
-
1 skill(s) installed to ${opts.global ? "global" : "project"}
|
|
1971
|
-
`));
|
|
1972
|
-
} catch (err) {
|
|
1973
|
-
console.log(chalk8.red(` Failed to download: ${err.message}`));
|
|
1974
2056
|
}
|
|
2057
|
+
console.log(chalk8.red(` Skill '${skillName}' not found in ${fullRepo}`));
|
|
2058
|
+
console.log(chalk8.dim(` Check available skills: hub skills add ${fullRepo} --list`));
|
|
1975
2059
|
}
|
|
1976
2060
|
async function listRemoteSkills(owner, repo) {
|
|
1977
2061
|
const fullRepo = `${owner}/${repo}`;
|
|
1978
2062
|
console.log(chalk8.cyan(` Fetching skills from ${fullRepo}...
|
|
1979
2063
|
`));
|
|
2064
|
+
const headers = { Accept: "application/vnd.github.v3+json" };
|
|
1980
2065
|
try {
|
|
1981
|
-
|
|
1982
|
-
const
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
2066
|
+
let dirs = [];
|
|
2067
|
+
const skillsRes = await fetch(
|
|
2068
|
+
`https://api.github.com/repos/${fullRepo}/contents/skills`,
|
|
2069
|
+
{ headers }
|
|
2070
|
+
);
|
|
2071
|
+
if (skillsRes.ok) {
|
|
2072
|
+
const items = await skillsRes.json();
|
|
2073
|
+
dirs = items.filter((i) => i.type === "dir");
|
|
2074
|
+
}
|
|
2075
|
+
if (dirs.length === 0) {
|
|
2076
|
+
const rootRes = await fetch(
|
|
2077
|
+
`https://api.github.com/repos/${fullRepo}/contents`,
|
|
2078
|
+
{ headers }
|
|
2079
|
+
);
|
|
2080
|
+
if (rootRes.ok) {
|
|
2081
|
+
const items = await rootRes.json();
|
|
2082
|
+
const candidates = items.filter(
|
|
2083
|
+
(i) => i.type === "dir" && !i.name.startsWith(".")
|
|
2084
|
+
);
|
|
2085
|
+
for (const c of candidates) {
|
|
2086
|
+
const checkRes = await fetch(
|
|
2087
|
+
`https://api.github.com/repos/${fullRepo}/contents/${c.name}/SKILL.md`,
|
|
2088
|
+
{ headers }
|
|
2089
|
+
);
|
|
2090
|
+
if (checkRes.ok) dirs.push({ name: c.name });
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
if (dirs.length === 0) {
|
|
2095
|
+
const rootSkill = await fetch(
|
|
2096
|
+
`https://api.github.com/repos/${fullRepo}/contents/SKILL.md`,
|
|
2097
|
+
{ headers }
|
|
2098
|
+
);
|
|
2099
|
+
if (rootSkill.ok) {
|
|
2100
|
+
console.log(chalk8.green(` This repo is a single skill.
|
|
2101
|
+
`));
|
|
2102
|
+
console.log(chalk8.dim(` Install with: hub skills add ${fullRepo}
|
|
2103
|
+
`));
|
|
2104
|
+
return;
|
|
2105
|
+
}
|
|
1988
2106
|
}
|
|
1989
|
-
const items = await res.json();
|
|
1990
|
-
const dirs = items.filter((i) => i.type === "dir");
|
|
1991
2107
|
if (dirs.length === 0) {
|
|
1992
2108
|
console.log(chalk8.dim(" No skills found."));
|
|
1993
2109
|
return;
|
|
@@ -2015,8 +2131,7 @@ async function addFromLocalPath(localPath, hubDir, opts) {
|
|
|
2015
2131
|
console.log(chalk8.red(` Path is not a directory: ${absPath}`));
|
|
2016
2132
|
return;
|
|
2017
2133
|
}
|
|
2018
|
-
|
|
2019
|
-
await installSkillsFromDir(sourceSkillsDir, hubDir, opts);
|
|
2134
|
+
await installSkillsFromDir(absPath, hubDir, opts);
|
|
2020
2135
|
}
|
|
2021
2136
|
async function addFromGitRepo(source, hubDir, opts) {
|
|
2022
2137
|
const tmp = tmpDir();
|
|
@@ -2031,8 +2146,7 @@ async function addFromGitRepo(source, hubDir, opts) {
|
|
|
2031
2146
|
console.log(chalk8.dim(" Make sure the URL is correct and you have access to the repository."));
|
|
2032
2147
|
return;
|
|
2033
2148
|
}
|
|
2034
|
-
|
|
2035
|
-
await installSkillsFromDir(sourceSkillsDir, hubDir, opts);
|
|
2149
|
+
await installSkillsFromDir(tmp, hubDir, opts);
|
|
2036
2150
|
} finally {
|
|
2037
2151
|
if (existsSync6(tmp)) {
|
|
2038
2152
|
await rm(tmp, { recursive: true });
|
|
@@ -2049,7 +2163,7 @@ function parseGitHubSource(source) {
|
|
|
2049
2163
|
return null;
|
|
2050
2164
|
}
|
|
2051
2165
|
var skillsCommand = new Command8("skills").description("Manage agent skills").addCommand(
|
|
2052
|
-
new Command8("add").description("Install skills from registry, GitHub
|
|
2166
|
+
new Command8("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) => {
|
|
2053
2167
|
const hubDir = process.cwd();
|
|
2054
2168
|
if (isLocalPath(source)) {
|
|
2055
2169
|
console.log(chalk8.blue(`
|
|
@@ -2094,9 +2208,10 @@ Installing skill ${source} from registry
|
|
|
2094
2208
|
await addFromRegistry(source, hubDir, opts);
|
|
2095
2209
|
})
|
|
2096
2210
|
).addCommand(
|
|
2097
|
-
new Command8("find").description("Browse
|
|
2098
|
-
const
|
|
2099
|
-
|
|
2211
|
+
new Command8("find").description("Browse curated skills in the Repo Hub directory").argument("[query]", "Search term").action(async (query) => {
|
|
2212
|
+
const base = "https://rhm-website.vercel.app/directory?type=skill";
|
|
2213
|
+
const url = query ? `${base}&q=${encodeURIComponent(query)}` : base;
|
|
2214
|
+
console.log(chalk8.blue("\n Browse curated skills at:\n"));
|
|
2100
2215
|
console.log(chalk8.cyan(` ${url}
|
|
2101
2216
|
`));
|
|
2102
2217
|
console.log(chalk8.dim(" Install with: hub skills add <owner>/<repo>/<skill-name>"));
|
|
@@ -2203,18 +2318,33 @@ async function addFromLocalPath2(localPath, hubDir, opts) {
|
|
|
2203
2318
|
console.log(chalk9.red(` Path not found: ${absPath}`));
|
|
2204
2319
|
return;
|
|
2205
2320
|
}
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
}
|
|
2209
|
-
async function installAgentsFromDir(sourceAgentsDir, hubDir, opts) {
|
|
2210
|
-
if (!existsSync7(sourceAgentsDir)) {
|
|
2211
|
-
console.log(chalk9.red(" No agents/ directory found in source"));
|
|
2321
|
+
if (!statSync2(absPath).isDirectory()) {
|
|
2322
|
+
console.log(chalk9.red(` Path is not a directory: ${absPath}`));
|
|
2212
2323
|
return;
|
|
2213
2324
|
}
|
|
2214
|
-
|
|
2215
|
-
|
|
2325
|
+
await installAgentsFromDir(absPath, hubDir, opts);
|
|
2326
|
+
}
|
|
2327
|
+
async function findAgentFiles(rootDir) {
|
|
2328
|
+
const agentsDir = join10(rootDir, "agents");
|
|
2329
|
+
if (existsSync7(agentsDir)) {
|
|
2330
|
+
const entries = await readdir3(agentsDir);
|
|
2331
|
+
const mdFiles = entries.filter((f) => f.endsWith(".md"));
|
|
2332
|
+
if (mdFiles.length > 0) return { dir: agentsDir, files: mdFiles };
|
|
2333
|
+
}
|
|
2334
|
+
if (existsSync7(rootDir)) {
|
|
2335
|
+
const entries = await readdir3(rootDir);
|
|
2336
|
+
const mdFiles = entries.filter(
|
|
2337
|
+
(f) => f.endsWith(".md") && !f.startsWith(".") && f !== "README.md" && f !== "CHANGELOG.md"
|
|
2338
|
+
);
|
|
2339
|
+
if (mdFiles.length > 0) return { dir: rootDir, files: mdFiles };
|
|
2340
|
+
}
|
|
2341
|
+
return { dir: agentsDir, files: [] };
|
|
2342
|
+
}
|
|
2343
|
+
async function installAgentsFromDir(sourceDir, hubDir, opts) {
|
|
2344
|
+
const rootDir = sourceDir.endsWith("/agents") ? sourceDir.replace(/\/agents$/, "") : sourceDir;
|
|
2345
|
+
const { dir, files: mdFiles } = await findAgentFiles(rootDir);
|
|
2216
2346
|
if (mdFiles.length === 0) {
|
|
2217
|
-
console.log(chalk9.red(" No agent files found (
|
|
2347
|
+
console.log(chalk9.red(" No agent files found (looked in agents/ and root .md files)"));
|
|
2218
2348
|
return;
|
|
2219
2349
|
}
|
|
2220
2350
|
const toInstall = opts.agent ? mdFiles.filter((f) => f === `${opts.agent}.md` || f === opts.agent) : mdFiles;
|
|
@@ -2226,7 +2356,7 @@ async function installAgentsFromDir(sourceAgentsDir, hubDir, opts) {
|
|
|
2226
2356
|
const targetBase = opts.global ? join10(process.env.HOME || "~", ".cursor", "agents") : join10(hubDir, "agents");
|
|
2227
2357
|
await mkdir5(targetBase, { recursive: true });
|
|
2228
2358
|
for (const file of toInstall) {
|
|
2229
|
-
await copyFile2(join10(
|
|
2359
|
+
await copyFile2(join10(dir, file), join10(targetBase, file));
|
|
2230
2360
|
console.log(chalk9.green(` Installed: ${file.replace(/\.md$/, "")}`));
|
|
2231
2361
|
}
|
|
2232
2362
|
console.log(
|
|
@@ -2249,8 +2379,7 @@ async function addFromGitRepo2(source, hubDir, opts) {
|
|
|
2249
2379
|
console.log(chalk9.red(` Repository not found or not accessible: ${source}`));
|
|
2250
2380
|
return;
|
|
2251
2381
|
}
|
|
2252
|
-
|
|
2253
|
-
await installAgentsFromDir(sourceAgentsDir, hubDir, opts);
|
|
2382
|
+
await installAgentsFromDir(tmp, hubDir, opts);
|
|
2254
2383
|
} finally {
|
|
2255
2384
|
if (existsSync7(tmp)) {
|
|
2256
2385
|
await rm2(tmp, { recursive: true });
|
|
@@ -2325,6 +2454,16 @@ Agent '${name}' not found in ${opts.global ? "global" : "project"}
|
|
|
2325
2454
|
Removed agent: ${name}
|
|
2326
2455
|
`));
|
|
2327
2456
|
})
|
|
2457
|
+
).addCommand(
|
|
2458
|
+
new Command9("find").description("Browse curated agents in the Repo Hub directory").argument("[query]", "Search term").action(async (query) => {
|
|
2459
|
+
const base = "https://rhm-website.vercel.app/directory?type=agent";
|
|
2460
|
+
const url = query ? `${base}&q=${encodeURIComponent(query)}` : base;
|
|
2461
|
+
console.log(chalk9.blue("\n Browse curated agents at:\n"));
|
|
2462
|
+
console.log(chalk9.cyan(` ${url}
|
|
2463
|
+
`));
|
|
2464
|
+
console.log(chalk9.dim(" Install with: hub agents add <owner>/<repo>"));
|
|
2465
|
+
console.log(chalk9.dim(" Example: hub agents add my-org/my-agents\n"));
|
|
2466
|
+
})
|
|
2328
2467
|
).addCommand(
|
|
2329
2468
|
new Command9("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) => {
|
|
2330
2469
|
const hubDir = process.cwd();
|
|
@@ -3329,17 +3468,253 @@ function extractVersion(s) {
|
|
|
3329
3468
|
return match?.[1] || s;
|
|
3330
3469
|
}
|
|
3331
3470
|
|
|
3332
|
-
// src/commands/
|
|
3471
|
+
// src/commands/memory.ts
|
|
3333
3472
|
import { Command as Command16 } from "commander";
|
|
3473
|
+
import { existsSync as existsSync14 } from "fs";
|
|
3474
|
+
import { mkdir as mkdir9, readdir as readdir6, readFile as readFile9, writeFile as writeFile11, rm as rm5, appendFile as appendFile2 } from "fs/promises";
|
|
3475
|
+
import { join as join17, resolve as resolve6, basename as basename2 } from "path";
|
|
3476
|
+
import chalk16 from "chalk";
|
|
3477
|
+
async function ensureLanceDbIgnored(memoriesDir, hubDir) {
|
|
3478
|
+
const relative = memoriesDir.replace(hubDir + "/", "");
|
|
3479
|
+
const pattern = `${relative}/.lancedb/`;
|
|
3480
|
+
const gitignorePath = join17(hubDir, ".gitignore");
|
|
3481
|
+
if (existsSync14(gitignorePath)) {
|
|
3482
|
+
const content = await readFile9(gitignorePath, "utf-8");
|
|
3483
|
+
if (content.includes(".lancedb")) return;
|
|
3484
|
+
await appendFile2(gitignorePath, `
|
|
3485
|
+
# Memory vector store (generated)
|
|
3486
|
+
${pattern}
|
|
3487
|
+
`);
|
|
3488
|
+
} else {
|
|
3489
|
+
await writeFile11(gitignorePath, `# Memory vector store (generated)
|
|
3490
|
+
${pattern}
|
|
3491
|
+
`, "utf-8");
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
var VALID_CATEGORIES = [
|
|
3495
|
+
"decisions",
|
|
3496
|
+
"conventions",
|
|
3497
|
+
"incidents",
|
|
3498
|
+
"domain",
|
|
3499
|
+
"gotchas"
|
|
3500
|
+
];
|
|
3501
|
+
function getMemoriesPath(hubDir, configPath) {
|
|
3502
|
+
return resolve6(hubDir, configPath || "memories");
|
|
3503
|
+
}
|
|
3504
|
+
function parseFrontmatter(raw) {
|
|
3505
|
+
const match = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
3506
|
+
if (!match) return { data: {}, content: raw };
|
|
3507
|
+
const data = {};
|
|
3508
|
+
for (const line of match[1].split("\n")) {
|
|
3509
|
+
const idx = line.indexOf(":");
|
|
3510
|
+
if (idx === -1) continue;
|
|
3511
|
+
const key = line.slice(0, idx).trim();
|
|
3512
|
+
let value = line.slice(idx + 1).trim();
|
|
3513
|
+
if (typeof value === "string" && value.startsWith("[") && value.endsWith("]")) {
|
|
3514
|
+
value = value.slice(1, -1).split(",").map((s) => s.trim());
|
|
3515
|
+
}
|
|
3516
|
+
data[key] = value;
|
|
3517
|
+
}
|
|
3518
|
+
return { data, content: match[2] };
|
|
3519
|
+
}
|
|
3520
|
+
function buildFrontmatter(data) {
|
|
3521
|
+
const lines = ["---"];
|
|
3522
|
+
for (const [key, value] of Object.entries(data)) {
|
|
3523
|
+
if (value === void 0) continue;
|
|
3524
|
+
if (Array.isArray(value)) {
|
|
3525
|
+
lines.push(`${key}: [${value.join(", ")}]`);
|
|
3526
|
+
} else {
|
|
3527
|
+
lines.push(`${key}: ${value}`);
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
lines.push("---");
|
|
3531
|
+
return lines.join("\n");
|
|
3532
|
+
}
|
|
3533
|
+
async function listMemories(memoriesDir, opts) {
|
|
3534
|
+
if (!existsSync14(memoriesDir)) {
|
|
3535
|
+
console.log(chalk16.dim(" No memories directory found."));
|
|
3536
|
+
return;
|
|
3537
|
+
}
|
|
3538
|
+
let total = 0;
|
|
3539
|
+
for (const cat of VALID_CATEGORIES) {
|
|
3540
|
+
if (opts.category && opts.category !== cat) continue;
|
|
3541
|
+
const catDir = join17(memoriesDir, cat);
|
|
3542
|
+
if (!existsSync14(catDir)) continue;
|
|
3543
|
+
const files = (await readdir6(catDir)).filter((f) => f.endsWith(".md"));
|
|
3544
|
+
if (files.length === 0) continue;
|
|
3545
|
+
const entries = [];
|
|
3546
|
+
for (const file of files) {
|
|
3547
|
+
const raw = await readFile9(join17(catDir, file), "utf-8");
|
|
3548
|
+
const { data } = parseFrontmatter(raw);
|
|
3549
|
+
const status = data.status || "active";
|
|
3550
|
+
if (opts.status && status !== opts.status) continue;
|
|
3551
|
+
entries.push({
|
|
3552
|
+
id: basename2(file, ".md"),
|
|
3553
|
+
title: data.title || basename2(file, ".md"),
|
|
3554
|
+
date: data.date || "unknown",
|
|
3555
|
+
status,
|
|
3556
|
+
tags: data.tags || []
|
|
3557
|
+
});
|
|
3558
|
+
}
|
|
3559
|
+
if (entries.length === 0) continue;
|
|
3560
|
+
console.log(chalk16.cyan(`
|
|
3561
|
+
${cat} (${entries.length})`));
|
|
3562
|
+
for (const e of entries) {
|
|
3563
|
+
const statusIcon = e.status === "active" ? chalk16.green("\u25CF") : chalk16.dim("\u25CB");
|
|
3564
|
+
const tags = e.tags.length > 0 ? chalk16.dim(` [${e.tags.join(", ")}]`) : "";
|
|
3565
|
+
console.log(` ${statusIcon} ${chalk16.yellow(e.id)} \u2014 ${e.title} ${chalk16.dim(`(${e.date})`)}${tags}`);
|
|
3566
|
+
}
|
|
3567
|
+
total += entries.length;
|
|
3568
|
+
}
|
|
3569
|
+
if (total === 0) {
|
|
3570
|
+
console.log(chalk16.dim(" No memories found."));
|
|
3571
|
+
} else {
|
|
3572
|
+
console.log(chalk16.green(`
|
|
3573
|
+
Total: ${total} memories
|
|
3574
|
+
`));
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
var memoryCommand = new Command16("memory").description("Manage team memories \u2014 persistent knowledge base for AI context").addCommand(
|
|
3578
|
+
new Command16("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(
|
|
3579
|
+
async (category, title, opts) => {
|
|
3580
|
+
if (!VALID_CATEGORIES.includes(category)) {
|
|
3581
|
+
console.log(
|
|
3582
|
+
chalk16.red(`
|
|
3583
|
+
Invalid category: ${category}. Valid: ${VALID_CATEGORIES.join(", ")}
|
|
3584
|
+
`)
|
|
3585
|
+
);
|
|
3586
|
+
return;
|
|
3587
|
+
}
|
|
3588
|
+
const hubDir = process.cwd();
|
|
3589
|
+
let memoriesDir;
|
|
3590
|
+
try {
|
|
3591
|
+
const config = await loadHubConfig(hubDir);
|
|
3592
|
+
memoriesDir = getMemoriesPath(hubDir, config.memory?.path);
|
|
3593
|
+
} catch {
|
|
3594
|
+
memoriesDir = getMemoriesPath(hubDir);
|
|
3595
|
+
}
|
|
3596
|
+
const catDir = join17(memoriesDir, category);
|
|
3597
|
+
await mkdir9(catDir, { recursive: true });
|
|
3598
|
+
await ensureLanceDbIgnored(memoriesDir, hubDir);
|
|
3599
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
3600
|
+
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
3601
|
+
const id = `${date}-${slug}`;
|
|
3602
|
+
const filePath = join17(catDir, `${id}.md`);
|
|
3603
|
+
const fm = {
|
|
3604
|
+
title,
|
|
3605
|
+
category,
|
|
3606
|
+
date,
|
|
3607
|
+
status: "active"
|
|
3608
|
+
};
|
|
3609
|
+
if (opts.author) fm.author = opts.author;
|
|
3610
|
+
if (opts.tags) fm.tags = opts.tags.split(",").map((t) => t.trim());
|
|
3611
|
+
const content = opts.content || `## Context
|
|
3612
|
+
|
|
3613
|
+
|
|
3614
|
+
|
|
3615
|
+
## Details
|
|
3616
|
+
|
|
3617
|
+
`;
|
|
3618
|
+
const fileContent = `${buildFrontmatter(fm)}
|
|
3619
|
+
|
|
3620
|
+
${content}
|
|
3621
|
+
`;
|
|
3622
|
+
await writeFile11(filePath, fileContent, "utf-8");
|
|
3623
|
+
console.log(chalk16.green(`
|
|
3624
|
+
Created: ${category}/${id}.md`));
|
|
3625
|
+
console.log(chalk16.dim(` Path: ${filePath}`));
|
|
3626
|
+
if (!opts.content) {
|
|
3627
|
+
console.log(chalk16.yellow(" Edit the file to add content.\n"));
|
|
3628
|
+
} else {
|
|
3629
|
+
console.log();
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
)
|
|
3633
|
+
).addCommand(
|
|
3634
|
+
new Command16("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) => {
|
|
3635
|
+
const hubDir = process.cwd();
|
|
3636
|
+
let memoriesDir;
|
|
3637
|
+
try {
|
|
3638
|
+
const config = await loadHubConfig(hubDir);
|
|
3639
|
+
memoriesDir = getMemoriesPath(hubDir, config.memory?.path);
|
|
3640
|
+
} catch {
|
|
3641
|
+
memoriesDir = getMemoriesPath(hubDir);
|
|
3642
|
+
}
|
|
3643
|
+
console.log(chalk16.blue("\nTeam Memories"));
|
|
3644
|
+
await listMemories(memoriesDir, opts);
|
|
3645
|
+
})
|
|
3646
|
+
).addCommand(
|
|
3647
|
+
new Command16("archive").description("Archive a memory (soft-delete)").argument("<id>", "Memory ID (filename without .md)").action(async (id) => {
|
|
3648
|
+
const hubDir = process.cwd();
|
|
3649
|
+
let memoriesDir;
|
|
3650
|
+
try {
|
|
3651
|
+
const config = await loadHubConfig(hubDir);
|
|
3652
|
+
memoriesDir = getMemoriesPath(hubDir, config.memory?.path);
|
|
3653
|
+
} catch {
|
|
3654
|
+
memoriesDir = getMemoriesPath(hubDir);
|
|
3655
|
+
}
|
|
3656
|
+
let found = false;
|
|
3657
|
+
for (const cat of VALID_CATEGORIES) {
|
|
3658
|
+
const filePath = join17(memoriesDir, cat, `${id}.md`);
|
|
3659
|
+
if (!existsSync14(filePath)) continue;
|
|
3660
|
+
const raw = await readFile9(filePath, "utf-8");
|
|
3661
|
+
const { data, content } = parseFrontmatter(raw);
|
|
3662
|
+
data.status = "archived";
|
|
3663
|
+
const updated = `${buildFrontmatter(data)}
|
|
3664
|
+
${content}`;
|
|
3665
|
+
await writeFile11(filePath, updated, "utf-8");
|
|
3666
|
+
console.log(chalk16.green(`
|
|
3667
|
+
Archived: ${cat}/${id}.md
|
|
3668
|
+
`));
|
|
3669
|
+
found = true;
|
|
3670
|
+
break;
|
|
3671
|
+
}
|
|
3672
|
+
if (!found) {
|
|
3673
|
+
console.log(chalk16.red(`
|
|
3674
|
+
Memory "${id}" not found.
|
|
3675
|
+
`));
|
|
3676
|
+
}
|
|
3677
|
+
})
|
|
3678
|
+
).addCommand(
|
|
3679
|
+
new Command16("remove").description("Permanently delete a memory").argument("<id>", "Memory ID (filename without .md)").action(async (id) => {
|
|
3680
|
+
const hubDir = process.cwd();
|
|
3681
|
+
let memoriesDir;
|
|
3682
|
+
try {
|
|
3683
|
+
const config = await loadHubConfig(hubDir);
|
|
3684
|
+
memoriesDir = getMemoriesPath(hubDir, config.memory?.path);
|
|
3685
|
+
} catch {
|
|
3686
|
+
memoriesDir = getMemoriesPath(hubDir);
|
|
3687
|
+
}
|
|
3688
|
+
let found = false;
|
|
3689
|
+
for (const cat of VALID_CATEGORIES) {
|
|
3690
|
+
const filePath = join17(memoriesDir, cat, `${id}.md`);
|
|
3691
|
+
if (!existsSync14(filePath)) continue;
|
|
3692
|
+
await rm5(filePath);
|
|
3693
|
+
console.log(chalk16.green(`
|
|
3694
|
+
Removed: ${cat}/${id}.md
|
|
3695
|
+
`));
|
|
3696
|
+
found = true;
|
|
3697
|
+
break;
|
|
3698
|
+
}
|
|
3699
|
+
if (!found) {
|
|
3700
|
+
console.log(chalk16.red(`
|
|
3701
|
+
Memory "${id}" not found.
|
|
3702
|
+
`));
|
|
3703
|
+
}
|
|
3704
|
+
})
|
|
3705
|
+
);
|
|
3706
|
+
|
|
3707
|
+
// src/commands/update.ts
|
|
3708
|
+
import { Command as Command17 } from "commander";
|
|
3334
3709
|
import { execSync as execSync12 } from "child_process";
|
|
3335
3710
|
import { readFileSync } from "fs";
|
|
3336
|
-
import { join as
|
|
3711
|
+
import { join as join18, dirname } from "path";
|
|
3337
3712
|
import { fileURLToPath } from "url";
|
|
3338
|
-
import
|
|
3713
|
+
import chalk17 from "chalk";
|
|
3339
3714
|
var PACKAGE_NAME = "@arvoretech/hub";
|
|
3340
3715
|
function getCurrentVersion() {
|
|
3341
3716
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
3342
|
-
const pkgPath =
|
|
3717
|
+
const pkgPath = join18(__dirname, "..", "package.json");
|
|
3343
3718
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
3344
3719
|
return pkg.version;
|
|
3345
3720
|
}
|
|
@@ -3362,54 +3737,54 @@ function detectPackageManager() {
|
|
|
3362
3737
|
}
|
|
3363
3738
|
}
|
|
3364
3739
|
}
|
|
3365
|
-
var updateCommand = new
|
|
3740
|
+
var updateCommand = new Command17("update").description("Update hub CLI to the latest version").option("--check", "Only check for updates without installing").action(async (opts) => {
|
|
3366
3741
|
const currentVersion = getCurrentVersion();
|
|
3367
|
-
console.log(
|
|
3742
|
+
console.log(chalk17.blue(`
|
|
3368
3743
|
Current version: ${currentVersion}`));
|
|
3369
3744
|
let latestVersion;
|
|
3370
3745
|
try {
|
|
3371
3746
|
latestVersion = await getLatestVersion();
|
|
3372
3747
|
} catch (err) {
|
|
3373
|
-
console.log(
|
|
3748
|
+
console.log(chalk17.red(` Failed to check for updates: ${err.message}
|
|
3374
3749
|
`));
|
|
3375
3750
|
return;
|
|
3376
3751
|
}
|
|
3377
|
-
console.log(
|
|
3752
|
+
console.log(chalk17.blue(` Latest version: ${latestVersion}`));
|
|
3378
3753
|
if (currentVersion === latestVersion) {
|
|
3379
|
-
console.log(
|
|
3754
|
+
console.log(chalk17.green("\n You're already on the latest version.\n"));
|
|
3380
3755
|
return;
|
|
3381
3756
|
}
|
|
3382
|
-
console.log(
|
|
3757
|
+
console.log(chalk17.yellow(`
|
|
3383
3758
|
Update available: ${currentVersion} \u2192 ${latestVersion}`));
|
|
3384
3759
|
if (opts.check) {
|
|
3385
3760
|
const pm2 = detectPackageManager();
|
|
3386
|
-
console.log(
|
|
3761
|
+
console.log(chalk17.dim(`
|
|
3387
3762
|
Run 'hub update' or '${pm2} install -g ${PACKAGE_NAME}@latest' to update.
|
|
3388
3763
|
`));
|
|
3389
3764
|
return;
|
|
3390
3765
|
}
|
|
3391
3766
|
const pm = detectPackageManager();
|
|
3392
3767
|
const installCmd = pm === "pnpm" ? `pnpm install -g ${PACKAGE_NAME}@latest` : pm === "yarn" ? `yarn global add ${PACKAGE_NAME}@latest` : `npm install -g ${PACKAGE_NAME}@latest`;
|
|
3393
|
-
console.log(
|
|
3768
|
+
console.log(chalk17.cyan(`
|
|
3394
3769
|
Updating with ${pm}...
|
|
3395
3770
|
`));
|
|
3396
|
-
console.log(
|
|
3771
|
+
console.log(chalk17.dim(` $ ${installCmd}
|
|
3397
3772
|
`));
|
|
3398
3773
|
try {
|
|
3399
3774
|
execSync12(installCmd, { stdio: "inherit" });
|
|
3400
|
-
console.log(
|
|
3775
|
+
console.log(chalk17.green(`
|
|
3401
3776
|
Updated to ${latestVersion} successfully.
|
|
3402
3777
|
`));
|
|
3403
3778
|
} catch {
|
|
3404
|
-
console.log(
|
|
3779
|
+
console.log(chalk17.red(`
|
|
3405
3780
|
Update failed. Try running manually:`));
|
|
3406
|
-
console.log(
|
|
3781
|
+
console.log(chalk17.dim(` $ ${installCmd}
|
|
3407
3782
|
`));
|
|
3408
3783
|
}
|
|
3409
3784
|
});
|
|
3410
3785
|
|
|
3411
3786
|
// src/index.ts
|
|
3412
|
-
var program = new
|
|
3787
|
+
var program = new Command18();
|
|
3413
3788
|
program.name("hub").description(
|
|
3414
3789
|
"Give your AI coding assistant the full picture. Multi-repo context, agent orchestration, and end-to-end workflows."
|
|
3415
3790
|
).version("0.2.0").enablePositionalOptions();
|
|
@@ -3430,5 +3805,6 @@ program.addCommand(execCommand);
|
|
|
3430
3805
|
program.addCommand(worktreeCommand);
|
|
3431
3806
|
program.addCommand(doctorCommand);
|
|
3432
3807
|
program.addCommand(toolsCommand);
|
|
3808
|
+
program.addCommand(memoryCommand);
|
|
3433
3809
|
program.addCommand(updateCommand);
|
|
3434
3810
|
program.parse();
|