@opennous/cli 0.9.0 → 0.10.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/CHANGELOG.md +18 -0
- package/package.json +3 -2
- package/src/index.js +103 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# @opennous/cli changelog
|
|
2
|
+
|
|
3
|
+
## 0.10.0
|
|
4
|
+
|
|
5
|
+
### Added — `nous vault`: on-device retrieval over your linked local vault
|
|
6
|
+
|
|
7
|
+
Once a folder is paired with `nous link`, an agent running on your own machine
|
|
8
|
+
(Claude Code, Hermes, …) can now read and search your private vault **locally** —
|
|
9
|
+
with no server round-trip. It's fresher (no sync lag), fuller (whole files, not
|
|
10
|
+
snippets), faster (no network), and private (the reads never leave the device).
|
|
11
|
+
This is the native counterpart to the `search_my_vault` MCP tool, which stays the
|
|
12
|
+
fallback for when you're *not* on the machine.
|
|
13
|
+
|
|
14
|
+
- `nous vault search <query>` — local keyword search over your vault notes
|
|
15
|
+
(filename-weighted, ranked, with snippets). Options: `--folder`, `--limit`, `--json`.
|
|
16
|
+
- `nous vault read <path>` — print a note's full content (e.g. `decisions/pricing`).
|
|
17
|
+
- `nous vault list [folder]` — list vault files, optionally within one folder.
|
|
18
|
+
- `nous vault path` — print the linked folder, so you can point your own Grep/Read at it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opennous/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Nous CLI — set up Nous, connect it to your coding agent, and read the Context API from your terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"src",
|
|
11
|
-
"README.md"
|
|
11
|
+
"README.md",
|
|
12
|
+
"CHANGELOG.md"
|
|
12
13
|
],
|
|
13
14
|
"scripts": {
|
|
14
15
|
"dev": "node src/index.js",
|
package/src/index.js
CHANGED
|
@@ -1277,5 +1277,108 @@ program
|
|
|
1277
1277
|
console.log("✓ Unlinked. Your files are still in Nous; this folder just stopped syncing.");
|
|
1278
1278
|
});
|
|
1279
1279
|
|
|
1280
|
+
// ---------------------------------------------------------------------------
|
|
1281
|
+
// nous vault — read + search the LINKED local vault ON-DEVICE. When an agent runs
|
|
1282
|
+
// where the vault is linked (`nous link`), it should read these files directly
|
|
1283
|
+
// instead of calling the search_my_vault MCP tool: fresher (no sync lag), fuller
|
|
1284
|
+
// (whole files, not snippets), faster (no network), and private (the reads never
|
|
1285
|
+
// leave the machine). This is the native personal-vault retrieval path.
|
|
1286
|
+
// ---------------------------------------------------------------------------
|
|
1287
|
+
function linkedVaultRoot() {
|
|
1288
|
+
const dir = readConfig().link?.dir;
|
|
1289
|
+
if (!dir || !existsSync(dir)) return null;
|
|
1290
|
+
return dir;
|
|
1291
|
+
}
|
|
1292
|
+
function requireVaultRoot() {
|
|
1293
|
+
const root = linkedVaultRoot();
|
|
1294
|
+
if (!root) { console.error("No local vault linked. Run `nous link <dir>` first."); process.exit(1); }
|
|
1295
|
+
return root;
|
|
1296
|
+
}
|
|
1297
|
+
// Resolve a 'folder/[subfolder/]name' argument to an absolute .md path under the vault.
|
|
1298
|
+
function resolveVaultRel(root, p) {
|
|
1299
|
+
const rel = String(p).replace(/^\/+/, "").endsWith(".md") ? String(p).replace(/^\/+/, "") : `${String(p).replace(/^\/+/, "")}.md`;
|
|
1300
|
+
return { rel, abs: join(root, rel.split("/").join(sep)) };
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
const vaultCmd = program
|
|
1304
|
+
.command("vault")
|
|
1305
|
+
.description("Read and search your linked local vault — on-device, no server round-trip");
|
|
1306
|
+
|
|
1307
|
+
vaultCmd
|
|
1308
|
+
.command("path")
|
|
1309
|
+
.description("Print the linked vault folder (point your own Read/Grep at it)")
|
|
1310
|
+
.action(() => { console.log(requireVaultRoot()); });
|
|
1311
|
+
|
|
1312
|
+
vaultCmd
|
|
1313
|
+
.command("list [folder]")
|
|
1314
|
+
.description("List vault files, optionally within one folder")
|
|
1315
|
+
.option("--json", "Output JSON", false)
|
|
1316
|
+
.action((folder, { json }) => {
|
|
1317
|
+
const root = requireVaultRoot();
|
|
1318
|
+
let files = walkVaultMd(root).map((f) => f.rel);
|
|
1319
|
+
if (folder) files = files.filter((r) => r.split("/")[0] === folder);
|
|
1320
|
+
files.sort();
|
|
1321
|
+
if (json) { console.log(JSON.stringify(files)); return; }
|
|
1322
|
+
if (!files.length) { console.log(folder ? `No files in ${folder}.` : "Vault is empty."); return; }
|
|
1323
|
+
for (const r of files) console.log(` ${r}`);
|
|
1324
|
+
});
|
|
1325
|
+
|
|
1326
|
+
vaultCmd
|
|
1327
|
+
.command("read <path>")
|
|
1328
|
+
.description("Print a vault file's full content (e.g. decisions/pricing)")
|
|
1329
|
+
.action((p) => {
|
|
1330
|
+
const root = requireVaultRoot();
|
|
1331
|
+
const { rel, abs } = resolveVaultRel(root, p);
|
|
1332
|
+
const content = safeRead(abs);
|
|
1333
|
+
if (content == null) { console.error(`Not found: ${rel}`); process.exit(1); }
|
|
1334
|
+
process.stdout.write(content.endsWith("\n") ? content : content + "\n");
|
|
1335
|
+
});
|
|
1336
|
+
|
|
1337
|
+
vaultCmd
|
|
1338
|
+
.command("search <query...>")
|
|
1339
|
+
.description("Search the linked vault locally (on-device — never leaves your machine)")
|
|
1340
|
+
.option("--folder <folder>", "Restrict to one vault folder")
|
|
1341
|
+
.option("--limit <n>", "Max results", "6")
|
|
1342
|
+
.option("--json", "Output JSON", false)
|
|
1343
|
+
.action((queryParts, { folder, limit, json }) => {
|
|
1344
|
+
const root = requireVaultRoot();
|
|
1345
|
+
const query = queryParts.join(" ");
|
|
1346
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
1347
|
+
if (!terms.length) { console.error("Pass a query, e.g. `nous vault search pricing objections`."); process.exit(1); }
|
|
1348
|
+
|
|
1349
|
+
let files = walkVaultMd(root);
|
|
1350
|
+
if (folder) files = files.filter((f) => f.rel.split("/")[0] === folder);
|
|
1351
|
+
|
|
1352
|
+
const hits = [];
|
|
1353
|
+
for (const f of files) {
|
|
1354
|
+
const content = safeRead(f.abs) ?? "";
|
|
1355
|
+
const hay = content.toLowerCase();
|
|
1356
|
+
const name = f.rel.toLowerCase();
|
|
1357
|
+
let score = 0;
|
|
1358
|
+
for (const t of terms) {
|
|
1359
|
+
if (name.includes(t)) score += 5; // a filename hit is a strong signal
|
|
1360
|
+
score += Math.min(hay.split(t).length - 1, 5); // content occurrences, capped
|
|
1361
|
+
}
|
|
1362
|
+
if (score <= 0) continue;
|
|
1363
|
+
// Snippet around the first matching term.
|
|
1364
|
+
let snippet = "";
|
|
1365
|
+
for (const t of terms) {
|
|
1366
|
+
const i = hay.indexOf(t);
|
|
1367
|
+
if (i >= 0) { snippet = content.slice(Math.max(0, i - 60), i + 140).replace(/\s+/g, " ").trim(); break; }
|
|
1368
|
+
}
|
|
1369
|
+
hits.push({ path: f.rel, score, snippet });
|
|
1370
|
+
}
|
|
1371
|
+
hits.sort((a, b) => b.score - a.score);
|
|
1372
|
+
const top = hits.slice(0, Math.min(Math.max(1, Number(limit) || 6), 30));
|
|
1373
|
+
|
|
1374
|
+
if (json) { console.log(JSON.stringify(top)); return; }
|
|
1375
|
+
if (!top.length) { console.log(`No local vault notes matched "${query}".`); return; }
|
|
1376
|
+
console.log(`Local vault notes matching "${query}":\n`);
|
|
1377
|
+
for (const h of top) {
|
|
1378
|
+
console.log(` ${h.path}`);
|
|
1379
|
+
if (h.snippet) console.log(` ${h.snippet}`);
|
|
1380
|
+
}
|
|
1381
|
+
});
|
|
1382
|
+
|
|
1280
1383
|
// ---------------------------------------------------------------------------
|
|
1281
1384
|
program.name("nous").version("0.8.0").parse();
|