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 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
- console.log(`Graph from [${from}]: ${result.nodes?.length || 0} nodes, ${result.edges?.length || 0} edges\n`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperstack-core",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "description": "Typed graph memory for AI agents. Replace GOALS.md with queryable cards + relations. Works with OpenClaw, Claude Desktop, Cursor.",
5
5
  "type": "module",
6
6
  "main": "index.js",
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
  }