hyperstack-core 1.5.0 → 1.5.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/README.md +10 -1
- package/cli.js +116 -2
- package/package.json +1 -1
- package/src/client.js +2 -0
package/README.md
CHANGED
|
@@ -223,6 +223,12 @@ await hs.store({
|
|
|
223
223
|
## CLI
|
|
224
224
|
|
|
225
225
|
```bash
|
|
226
|
+
# Ingest a README into cards + edges (no manual card creation)
|
|
227
|
+
npx hyperstack-core ingest ./README.md
|
|
228
|
+
npx hyperstack-core ingest ./docs/ # whole directory
|
|
229
|
+
cat DECISIONS.md | npx hyperstack-core ingest --source decisions # stdin pipe
|
|
230
|
+
npx hyperstack-core ingest ./README.md --dry # preview without storing
|
|
231
|
+
|
|
226
232
|
# Store a card
|
|
227
233
|
npx hyperstack-core store --slug "use-clerk" --title "Use Clerk" --type decision
|
|
228
234
|
|
|
@@ -232,9 +238,12 @@ npx hyperstack-core decide --slug "use-clerk" --title "Use Clerk" --rationale "B
|
|
|
232
238
|
# Check blockers
|
|
233
239
|
npx hyperstack-core blockers deploy-prod
|
|
234
240
|
|
|
235
|
-
# Traverse graph
|
|
241
|
+
# Traverse graph — forward (what this card points to)
|
|
236
242
|
npx hyperstack-core graph auth-api --depth 2
|
|
237
243
|
|
|
244
|
+
# Traverse graph — reverse (what points at this card)
|
|
245
|
+
npx hyperstack-core graph auth-api --depth 2 --reverse
|
|
246
|
+
|
|
238
247
|
# Search
|
|
239
248
|
npx hyperstack-core search "authentication setup"
|
|
240
249
|
|
package/cli.js
CHANGED
|
@@ -37,13 +37,14 @@ hyperstack-core — Typed graph memory for AI agents
|
|
|
37
37
|
Commands:
|
|
38
38
|
login Authenticate via browser (OAuth device flow)
|
|
39
39
|
logout Remove saved credentials
|
|
40
|
+
quickstart Scan current dir, ingest docs, show graph — 90 seconds to value
|
|
40
41
|
init <template> Initialize a project with a template
|
|
41
42
|
ingest <path> Auto-parse files into cards + edges (markdown/text/logs)
|
|
42
43
|
search <query> Search the knowledge graph
|
|
43
44
|
store Store a card (use --slug, --title, --body, --type, --links)
|
|
44
45
|
decide Record a decision (use --slug, --title, --rationale)
|
|
45
46
|
blockers <slug> Show what blocks a card
|
|
46
|
-
graph <slug> Traverse graph from a card
|
|
47
|
+
graph <slug> Traverse graph from a card (--reverse for inbound edges)
|
|
47
48
|
list List all cards
|
|
48
49
|
|
|
49
50
|
Templates:
|
|
@@ -387,6 +388,116 @@ async function run() {
|
|
|
387
388
|
return;
|
|
388
389
|
}
|
|
389
390
|
|
|
391
|
+
// ─── Quickstart command ─────────────────────────────
|
|
392
|
+
|
|
393
|
+
if (command === "quickstart") {
|
|
394
|
+
console.log("\n🧠 HyperStack Quickstart\n");
|
|
395
|
+
|
|
396
|
+
// 1. Auth check
|
|
397
|
+
const qsApiKey = getApiKey();
|
|
398
|
+
if (!qsApiKey) {
|
|
399
|
+
console.log(" Not authenticated.\n");
|
|
400
|
+
console.log(" Option A: npx hyperstack-core login");
|
|
401
|
+
console.log(" Option B: export HYPERSTACK_API_KEY=hs_your_key");
|
|
402
|
+
console.log(" Get a free key: https://cascadeai.dev/hyperstack\n");
|
|
403
|
+
process.exit(1);
|
|
404
|
+
}
|
|
405
|
+
console.log(" ✅ Authenticated\n");
|
|
406
|
+
|
|
407
|
+
// 2. Scan current directory for ingestable files
|
|
408
|
+
const cwd = process.cwd();
|
|
409
|
+
const scanDirs = [cwd];
|
|
410
|
+
const docsDir = join(cwd, "docs");
|
|
411
|
+
if (existsSync(docsDir) && statSync(docsDir).isDirectory()) scanDirs.push(docsDir);
|
|
412
|
+
|
|
413
|
+
const found = [];
|
|
414
|
+
for (const dir of scanDirs) {
|
|
415
|
+
const entries = readdirSync(dir).filter((f) => /\.(md|txt)$/i.test(f));
|
|
416
|
+
for (const f of entries) {
|
|
417
|
+
const full = join(dir, f);
|
|
418
|
+
const stat = statSync(full);
|
|
419
|
+
const rel = dir === cwd ? f : `docs/${f}`;
|
|
420
|
+
found.push({ path: full, name: rel, size: stat.size });
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (!found.length) {
|
|
425
|
+
console.log(" No .md or .txt files found in current directory.\n");
|
|
426
|
+
console.log(" Try: npx hyperstack-core ingest <file>");
|
|
427
|
+
process.exit(0);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
console.log(" Found files:\n");
|
|
431
|
+
for (const f of found) {
|
|
432
|
+
const kb = (f.size / 1024).toFixed(1);
|
|
433
|
+
console.log(` 📄 ${f.name} (${kb} KB)`);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const workspace = getFlag("workspace", "default");
|
|
437
|
+
console.log(`\n Workspace: ${workspace}`);
|
|
438
|
+
console.log(` Files: ${found.length}\n`);
|
|
439
|
+
|
|
440
|
+
// 3. Confirm (skip with --yes)
|
|
441
|
+
if (!args.includes("--yes") && !args.includes("-y")) {
|
|
442
|
+
const { createInterface } = await import("readline");
|
|
443
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
444
|
+
const answer = await new Promise((res) => {
|
|
445
|
+
rl.question(" Ingest these files? [Y/n] ", (a) => { rl.close(); res(a.trim().toLowerCase()); });
|
|
446
|
+
});
|
|
447
|
+
if (answer === "n" || answer === "no") {
|
|
448
|
+
console.log("\n Cancelled.\n");
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// 4. Parse all files
|
|
454
|
+
console.log("\n Parsing...\n");
|
|
455
|
+
let allCards = [];
|
|
456
|
+
let totalEdges = 0;
|
|
457
|
+
|
|
458
|
+
for (const f of found) {
|
|
459
|
+
const content = readFileSync(f.path, "utf-8");
|
|
460
|
+
const prefix = slugify(f.name.replace(/\.[^.]+$/, ""));
|
|
461
|
+
const result = parse(content, { prefix });
|
|
462
|
+
allCards.push(...result.cards);
|
|
463
|
+
totalEdges += result.edgeCount;
|
|
464
|
+
console.log(` 📄 ${f.name} → ${result.cards.length} cards, ${result.edgeCount} edges (${result.format})`);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (!allCards.length) {
|
|
468
|
+
console.log("\n No cards extracted. Files may be empty.\n");
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// 5. Store
|
|
473
|
+
console.log(`\n Storing ${allCards.length} cards...`);
|
|
474
|
+
const qsClient = new HyperStackClient({ apiKey: qsApiKey, workspace });
|
|
475
|
+
|
|
476
|
+
const result = await qsClient.ingest(allCards, {
|
|
477
|
+
onProgress: () => process.stdout.write("."),
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
console.log("\n");
|
|
481
|
+
if (result.failed) {
|
|
482
|
+
console.log(` ✅ ${result.stored} cards stored, ${result.failed} failed`);
|
|
483
|
+
for (const e of result.errors) console.log(` ❌ [${e.slug}] ${e.error}`);
|
|
484
|
+
} else {
|
|
485
|
+
console.log(` ✅ ${result.stored} cards stored, ${totalEdges} edges from ${found.length} file(s)`);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// 6. Next steps
|
|
489
|
+
const firstSlug = allCards[0]?.slug || "";
|
|
490
|
+
const searchTerm = allCards[0]?.title?.split(" ").slice(0, 3).join(" ") || "getting started";
|
|
491
|
+
console.log(`
|
|
492
|
+
Next steps:
|
|
493
|
+
|
|
494
|
+
npx hyperstack-core search "${searchTerm}"
|
|
495
|
+
npx hyperstack-core graph ${firstSlug} --depth 2 --reverse
|
|
496
|
+
npx hyperstack-core list
|
|
497
|
+
`);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
|
|
390
501
|
// ─── Ingest command ─────────────────────────────────
|
|
391
502
|
|
|
392
503
|
if (command === "ingest") {
|
|
@@ -592,12 +703,15 @@ async function run() {
|
|
|
592
703
|
if (command === "graph") {
|
|
593
704
|
const from = args[1];
|
|
594
705
|
if (!from) { console.error("Usage: hyperstack-core graph <slug>"); process.exit(1); }
|
|
706
|
+
const reverse = args.includes("--reverse");
|
|
595
707
|
try {
|
|
596
708
|
const result = await client.graph(from, {
|
|
597
709
|
depth: parseInt(getFlag("depth", "2")),
|
|
598
710
|
relation: getFlag("relation") || undefined,
|
|
711
|
+
mode: reverse ? "impact" : undefined,
|
|
599
712
|
});
|
|
600
|
-
|
|
713
|
+
const direction = reverse ? "pointing at" : "from";
|
|
714
|
+
console.log(`Graph ${direction} [${from}]: ${result.nodes?.length || 0} nodes, ${result.edges?.length || 0} edges\n`);
|
|
601
715
|
for (const n of result.nodes || []) {
|
|
602
716
|
console.log(` [${n.slug}] ${n.title || "?"} (${n.cardType || "?"})`);
|
|
603
717
|
}
|
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -165,6 +165,7 @@ class HyperStackClient {
|
|
|
165
165
|
* @param {number} [opts.depth=1] — hops to traverse (1-3)
|
|
166
166
|
* @param {string} [opts.relation] — filter by relation type
|
|
167
167
|
* @param {string} [opts.type] — filter by card type
|
|
168
|
+
* @param {string} [opts.mode] — "impact" for reverse traversal, "replay" for decision replay
|
|
168
169
|
* @param {string} [opts.at] — ISO timestamp for time-travel
|
|
169
170
|
* @returns {Promise<{nodes: Array, edges: Array}>}
|
|
170
171
|
*/
|
|
@@ -173,6 +174,7 @@ class HyperStackClient {
|
|
|
173
174
|
if (opts.depth) url += `&depth=${opts.depth}`;
|
|
174
175
|
if (opts.relation) url += `&relation=${opts.relation}`;
|
|
175
176
|
if (opts.type) url += `&type=${opts.type}`;
|
|
177
|
+
if (opts.mode) url += `&mode=${opts.mode}`;
|
|
176
178
|
if (opts.at) url += `&at=${encodeURIComponent(opts.at)}`;
|
|
177
179
|
return this._request("GET", url);
|
|
178
180
|
}
|