@arvoretech/hub 0.3.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 +434 -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
|
|
@@ -917,6 +929,18 @@ ${prompt.sections.after_pipeline.trim()}`);
|
|
|
917
929
|
if (prompt?.sections?.after_delivery) {
|
|
918
930
|
sections.push(`
|
|
919
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\`.`);
|
|
920
944
|
}
|
|
921
945
|
sections.push(`
|
|
922
946
|
## Troubleshooting and Debugging
|
|
@@ -1306,6 +1330,14 @@ function buildGitignoreLines(config) {
|
|
|
1306
1330
|
"# Task documents",
|
|
1307
1331
|
"tasks/"
|
|
1308
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
|
+
}
|
|
1309
1341
|
return lines;
|
|
1310
1342
|
}
|
|
1311
1343
|
var generators = {
|
|
@@ -1316,6 +1348,25 @@ var generators = {
|
|
|
1316
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) => {
|
|
1317
1349
|
const hubDir = process.cwd();
|
|
1318
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
|
+
}
|
|
1319
1370
|
const generator = generators[opts.editor];
|
|
1320
1371
|
if (!generator) {
|
|
1321
1372
|
console.log(
|
|
@@ -1904,17 +1955,32 @@ async function listLocalSkills(hubDir) {
|
|
|
1904
1955
|
}
|
|
1905
1956
|
return skills;
|
|
1906
1957
|
}
|
|
1907
|
-
async function
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
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 };
|
|
1911
1966
|
}
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
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);
|
|
1916
1982
|
if (skillFolders.length === 0) {
|
|
1917
|
-
console.log(chalk8.red(" No skills found (
|
|
1983
|
+
console.log(chalk8.red(" No skills found (looked in skills/, root dirs, and SKILL.md)"));
|
|
1918
1984
|
return;
|
|
1919
1985
|
}
|
|
1920
1986
|
const toInstall = opts.skill ? skillFolders.filter((s) => s === opts.skill) : skillFolders;
|
|
@@ -1925,7 +1991,7 @@ async function installSkillsFromDir(sourceSkillsDir, hubDir, opts) {
|
|
|
1925
1991
|
const targetBase = opts.global ? join9(process.env.HOME || "~", ".cursor", "skills") : join9(hubDir, "skills");
|
|
1926
1992
|
await mkdir4(targetBase, { recursive: true });
|
|
1927
1993
|
for (const skill of toInstall) {
|
|
1928
|
-
const src = join9(
|
|
1994
|
+
const src = join9(dir, skill);
|
|
1929
1995
|
const dest = join9(targetBase, skill);
|
|
1930
1996
|
await cp2(src, dest, { recursive: true });
|
|
1931
1997
|
console.log(chalk8.green(` Installed: ${skill}`));
|
|
@@ -1964,42 +2030,80 @@ async function addFromRegistry(skillName, hubDir, opts) {
|
|
|
1964
2030
|
}
|
|
1965
2031
|
async function addFromGitHubSkill(owner, repo, skillName, hubDir, opts) {
|
|
1966
2032
|
const fullRepo = `${owner}/${repo}`;
|
|
1967
|
-
const remotePath = `skills/${skillName}`;
|
|
1968
2033
|
const targetBase = opts.global ? join9(process.env.HOME || "~", ".cursor", "skills") : join9(hubDir, "skills");
|
|
1969
2034
|
const dest = join9(targetBase, skillName);
|
|
2035
|
+
const pathsToTry = [
|
|
2036
|
+
`skills/${skillName}`,
|
|
2037
|
+
skillName
|
|
2038
|
+
];
|
|
1970
2039
|
console.log(chalk8.cyan(` Downloading ${skillName} from ${fullRepo} via GitHub API...`));
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
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 {
|
|
1974
2053
|
await rm(dest, { recursive: true }).catch(() => {
|
|
1975
2054
|
});
|
|
1976
|
-
console.log(chalk8.red(` Skill '${skillName}' not found in ${fullRepo}/skills/`));
|
|
1977
|
-
console.log(chalk8.dim(` Check available skills: hub skills add ${fullRepo} --list`));
|
|
1978
|
-
return;
|
|
1979
2055
|
}
|
|
1980
|
-
console.log(chalk8.green(` Installed: ${skillName} (from ${fullRepo})`));
|
|
1981
|
-
console.log(chalk8.green(`
|
|
1982
|
-
1 skill(s) installed to ${opts.global ? "global" : "project"}
|
|
1983
|
-
`));
|
|
1984
|
-
} catch (err) {
|
|
1985
|
-
console.log(chalk8.red(` Failed to download: ${err.message}`));
|
|
1986
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`));
|
|
1987
2059
|
}
|
|
1988
2060
|
async function listRemoteSkills(owner, repo) {
|
|
1989
2061
|
const fullRepo = `${owner}/${repo}`;
|
|
1990
2062
|
console.log(chalk8.cyan(` Fetching skills from ${fullRepo}...
|
|
1991
2063
|
`));
|
|
2064
|
+
const headers = { Accept: "application/vnd.github.v3+json" };
|
|
1992
2065
|
try {
|
|
1993
|
-
|
|
1994
|
-
const
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
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
|
+
}
|
|
2000
2106
|
}
|
|
2001
|
-
const items = await res.json();
|
|
2002
|
-
const dirs = items.filter((i) => i.type === "dir");
|
|
2003
2107
|
if (dirs.length === 0) {
|
|
2004
2108
|
console.log(chalk8.dim(" No skills found."));
|
|
2005
2109
|
return;
|
|
@@ -2027,8 +2131,7 @@ async function addFromLocalPath(localPath, hubDir, opts) {
|
|
|
2027
2131
|
console.log(chalk8.red(` Path is not a directory: ${absPath}`));
|
|
2028
2132
|
return;
|
|
2029
2133
|
}
|
|
2030
|
-
|
|
2031
|
-
await installSkillsFromDir(sourceSkillsDir, hubDir, opts);
|
|
2134
|
+
await installSkillsFromDir(absPath, hubDir, opts);
|
|
2032
2135
|
}
|
|
2033
2136
|
async function addFromGitRepo(source, hubDir, opts) {
|
|
2034
2137
|
const tmp = tmpDir();
|
|
@@ -2043,8 +2146,7 @@ async function addFromGitRepo(source, hubDir, opts) {
|
|
|
2043
2146
|
console.log(chalk8.dim(" Make sure the URL is correct and you have access to the repository."));
|
|
2044
2147
|
return;
|
|
2045
2148
|
}
|
|
2046
|
-
|
|
2047
|
-
await installSkillsFromDir(sourceSkillsDir, hubDir, opts);
|
|
2149
|
+
await installSkillsFromDir(tmp, hubDir, opts);
|
|
2048
2150
|
} finally {
|
|
2049
2151
|
if (existsSync6(tmp)) {
|
|
2050
2152
|
await rm(tmp, { recursive: true });
|
|
@@ -2061,7 +2163,7 @@ function parseGitHubSource(source) {
|
|
|
2061
2163
|
return null;
|
|
2062
2164
|
}
|
|
2063
2165
|
var skillsCommand = new Command8("skills").description("Manage agent skills").addCommand(
|
|
2064
|
-
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) => {
|
|
2065
2167
|
const hubDir = process.cwd();
|
|
2066
2168
|
if (isLocalPath(source)) {
|
|
2067
2169
|
console.log(chalk8.blue(`
|
|
@@ -2106,9 +2208,10 @@ Installing skill ${source} from registry
|
|
|
2106
2208
|
await addFromRegistry(source, hubDir, opts);
|
|
2107
2209
|
})
|
|
2108
2210
|
).addCommand(
|
|
2109
|
-
new Command8("find").description("Browse
|
|
2110
|
-
const
|
|
2111
|
-
|
|
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"));
|
|
2112
2215
|
console.log(chalk8.cyan(` ${url}
|
|
2113
2216
|
`));
|
|
2114
2217
|
console.log(chalk8.dim(" Install with: hub skills add <owner>/<repo>/<skill-name>"));
|
|
@@ -2215,18 +2318,33 @@ async function addFromLocalPath2(localPath, hubDir, opts) {
|
|
|
2215
2318
|
console.log(chalk9.red(` Path not found: ${absPath}`));
|
|
2216
2319
|
return;
|
|
2217
2320
|
}
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
}
|
|
2221
|
-
async function installAgentsFromDir(sourceAgentsDir, hubDir, opts) {
|
|
2222
|
-
if (!existsSync7(sourceAgentsDir)) {
|
|
2223
|
-
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}`));
|
|
2224
2323
|
return;
|
|
2225
2324
|
}
|
|
2226
|
-
|
|
2227
|
-
|
|
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);
|
|
2228
2346
|
if (mdFiles.length === 0) {
|
|
2229
|
-
console.log(chalk9.red(" No agent files found (
|
|
2347
|
+
console.log(chalk9.red(" No agent files found (looked in agents/ and root .md files)"));
|
|
2230
2348
|
return;
|
|
2231
2349
|
}
|
|
2232
2350
|
const toInstall = opts.agent ? mdFiles.filter((f) => f === `${opts.agent}.md` || f === opts.agent) : mdFiles;
|
|
@@ -2238,7 +2356,7 @@ async function installAgentsFromDir(sourceAgentsDir, hubDir, opts) {
|
|
|
2238
2356
|
const targetBase = opts.global ? join10(process.env.HOME || "~", ".cursor", "agents") : join10(hubDir, "agents");
|
|
2239
2357
|
await mkdir5(targetBase, { recursive: true });
|
|
2240
2358
|
for (const file of toInstall) {
|
|
2241
|
-
await copyFile2(join10(
|
|
2359
|
+
await copyFile2(join10(dir, file), join10(targetBase, file));
|
|
2242
2360
|
console.log(chalk9.green(` Installed: ${file.replace(/\.md$/, "")}`));
|
|
2243
2361
|
}
|
|
2244
2362
|
console.log(
|
|
@@ -2261,8 +2379,7 @@ async function addFromGitRepo2(source, hubDir, opts) {
|
|
|
2261
2379
|
console.log(chalk9.red(` Repository not found or not accessible: ${source}`));
|
|
2262
2380
|
return;
|
|
2263
2381
|
}
|
|
2264
|
-
|
|
2265
|
-
await installAgentsFromDir(sourceAgentsDir, hubDir, opts);
|
|
2382
|
+
await installAgentsFromDir(tmp, hubDir, opts);
|
|
2266
2383
|
} finally {
|
|
2267
2384
|
if (existsSync7(tmp)) {
|
|
2268
2385
|
await rm2(tmp, { recursive: true });
|
|
@@ -2337,6 +2454,16 @@ Agent '${name}' not found in ${opts.global ? "global" : "project"}
|
|
|
2337
2454
|
Removed agent: ${name}
|
|
2338
2455
|
`));
|
|
2339
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
|
+
})
|
|
2340
2467
|
).addCommand(
|
|
2341
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) => {
|
|
2342
2469
|
const hubDir = process.cwd();
|
|
@@ -3341,17 +3468,253 @@ function extractVersion(s) {
|
|
|
3341
3468
|
return match?.[1] || s;
|
|
3342
3469
|
}
|
|
3343
3470
|
|
|
3344
|
-
// src/commands/
|
|
3471
|
+
// src/commands/memory.ts
|
|
3345
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";
|
|
3346
3709
|
import { execSync as execSync12 } from "child_process";
|
|
3347
3710
|
import { readFileSync } from "fs";
|
|
3348
|
-
import { join as
|
|
3711
|
+
import { join as join18, dirname } from "path";
|
|
3349
3712
|
import { fileURLToPath } from "url";
|
|
3350
|
-
import
|
|
3713
|
+
import chalk17 from "chalk";
|
|
3351
3714
|
var PACKAGE_NAME = "@arvoretech/hub";
|
|
3352
3715
|
function getCurrentVersion() {
|
|
3353
3716
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
3354
|
-
const pkgPath =
|
|
3717
|
+
const pkgPath = join18(__dirname, "..", "package.json");
|
|
3355
3718
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
3356
3719
|
return pkg.version;
|
|
3357
3720
|
}
|
|
@@ -3374,54 +3737,54 @@ function detectPackageManager() {
|
|
|
3374
3737
|
}
|
|
3375
3738
|
}
|
|
3376
3739
|
}
|
|
3377
|
-
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) => {
|
|
3378
3741
|
const currentVersion = getCurrentVersion();
|
|
3379
|
-
console.log(
|
|
3742
|
+
console.log(chalk17.blue(`
|
|
3380
3743
|
Current version: ${currentVersion}`));
|
|
3381
3744
|
let latestVersion;
|
|
3382
3745
|
try {
|
|
3383
3746
|
latestVersion = await getLatestVersion();
|
|
3384
3747
|
} catch (err) {
|
|
3385
|
-
console.log(
|
|
3748
|
+
console.log(chalk17.red(` Failed to check for updates: ${err.message}
|
|
3386
3749
|
`));
|
|
3387
3750
|
return;
|
|
3388
3751
|
}
|
|
3389
|
-
console.log(
|
|
3752
|
+
console.log(chalk17.blue(` Latest version: ${latestVersion}`));
|
|
3390
3753
|
if (currentVersion === latestVersion) {
|
|
3391
|
-
console.log(
|
|
3754
|
+
console.log(chalk17.green("\n You're already on the latest version.\n"));
|
|
3392
3755
|
return;
|
|
3393
3756
|
}
|
|
3394
|
-
console.log(
|
|
3757
|
+
console.log(chalk17.yellow(`
|
|
3395
3758
|
Update available: ${currentVersion} \u2192 ${latestVersion}`));
|
|
3396
3759
|
if (opts.check) {
|
|
3397
3760
|
const pm2 = detectPackageManager();
|
|
3398
|
-
console.log(
|
|
3761
|
+
console.log(chalk17.dim(`
|
|
3399
3762
|
Run 'hub update' or '${pm2} install -g ${PACKAGE_NAME}@latest' to update.
|
|
3400
3763
|
`));
|
|
3401
3764
|
return;
|
|
3402
3765
|
}
|
|
3403
3766
|
const pm = detectPackageManager();
|
|
3404
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`;
|
|
3405
|
-
console.log(
|
|
3768
|
+
console.log(chalk17.cyan(`
|
|
3406
3769
|
Updating with ${pm}...
|
|
3407
3770
|
`));
|
|
3408
|
-
console.log(
|
|
3771
|
+
console.log(chalk17.dim(` $ ${installCmd}
|
|
3409
3772
|
`));
|
|
3410
3773
|
try {
|
|
3411
3774
|
execSync12(installCmd, { stdio: "inherit" });
|
|
3412
|
-
console.log(
|
|
3775
|
+
console.log(chalk17.green(`
|
|
3413
3776
|
Updated to ${latestVersion} successfully.
|
|
3414
3777
|
`));
|
|
3415
3778
|
} catch {
|
|
3416
|
-
console.log(
|
|
3779
|
+
console.log(chalk17.red(`
|
|
3417
3780
|
Update failed. Try running manually:`));
|
|
3418
|
-
console.log(
|
|
3781
|
+
console.log(chalk17.dim(` $ ${installCmd}
|
|
3419
3782
|
`));
|
|
3420
3783
|
}
|
|
3421
3784
|
});
|
|
3422
3785
|
|
|
3423
3786
|
// src/index.ts
|
|
3424
|
-
var program = new
|
|
3787
|
+
var program = new Command18();
|
|
3425
3788
|
program.name("hub").description(
|
|
3426
3789
|
"Give your AI coding assistant the full picture. Multi-repo context, agent orchestration, and end-to-end workflows."
|
|
3427
3790
|
).version("0.2.0").enablePositionalOptions();
|
|
@@ -3442,5 +3805,6 @@ program.addCommand(execCommand);
|
|
|
3442
3805
|
program.addCommand(worktreeCommand);
|
|
3443
3806
|
program.addCommand(doctorCommand);
|
|
3444
3807
|
program.addCommand(toolsCommand);
|
|
3808
|
+
program.addCommand(memoryCommand);
|
|
3445
3809
|
program.addCommand(updateCommand);
|
|
3446
3810
|
program.parse();
|