hyperstack-core 1.2.0 → 1.5.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/LICENSE +21 -21
- package/README.md +247 -67
- package/SKILL.md +679 -40
- package/adapters/openclaw.js +221 -288
- package/cli.js +631 -544
- package/examples/before-after.js +110 -110
- package/examples/openclaw-multiagent.js +214 -214
- package/index.js +19 -18
- package/package.json +51 -50
- package/src/client.js +320 -320
- package/src/parser.js +305 -0
- package/templates/openclaw-multiagent.json +98 -98
package/cli.js
CHANGED
|
@@ -1,544 +1,631 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* hyperstack-core CLI
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* npx hyperstack-core login ← NEW: OAuth device flow
|
|
8
|
-
* npx hyperstack-core init openclaw-multiagent
|
|
9
|
-
* npx hyperstack-core search "what blocks deploy"
|
|
10
|
-
* npx hyperstack-core store --slug task-1 --title "Deploy API" --type task
|
|
11
|
-
* npx hyperstack-core blockers task-1
|
|
12
|
-
* npx hyperstack-core graph task-1 --depth 2
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
16
|
-
import { resolve, dirname, join } from "path";
|
|
17
|
-
import { fileURLToPath } from "url";
|
|
18
|
-
import { homedir } from "os";
|
|
19
|
-
import { HyperStackClient } from "./src/client.js";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
--
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
console.log("
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
console.log("
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if (!
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
console.log(
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
if (
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* hyperstack-core CLI
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx hyperstack-core login ← NEW: OAuth device flow
|
|
8
|
+
* npx hyperstack-core init openclaw-multiagent
|
|
9
|
+
* npx hyperstack-core search "what blocks deploy"
|
|
10
|
+
* npx hyperstack-core store --slug task-1 --title "Deploy API" --type task
|
|
11
|
+
* npx hyperstack-core blockers task-1
|
|
12
|
+
* npx hyperstack-core graph task-1 --depth 2
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
16
|
+
import { resolve, dirname, join, basename } from "path";
|
|
17
|
+
import { fileURLToPath } from "url";
|
|
18
|
+
import { homedir } from "os";
|
|
19
|
+
import { HyperStackClient } from "./src/client.js";
|
|
20
|
+
import { parse, slugify } from "./src/parser.js";
|
|
21
|
+
|
|
22
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
|
|
24
|
+
const args = process.argv.slice(2);
|
|
25
|
+
const command = args[0];
|
|
26
|
+
|
|
27
|
+
function getFlag(name, fallback = "") {
|
|
28
|
+
const idx = args.indexOf(`--${name}`);
|
|
29
|
+
if (idx === -1 || idx + 1 >= args.length) return fallback;
|
|
30
|
+
return args[idx + 1];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function help() {
|
|
34
|
+
console.log(`
|
|
35
|
+
hyperstack-core — Typed graph memory for AI agents
|
|
36
|
+
|
|
37
|
+
Commands:
|
|
38
|
+
login Authenticate via browser (OAuth device flow)
|
|
39
|
+
logout Remove saved credentials
|
|
40
|
+
init <template> Initialize a project with a template
|
|
41
|
+
ingest <path> Auto-parse files into cards + edges (markdown/text/logs)
|
|
42
|
+
search <query> Search the knowledge graph
|
|
43
|
+
store Store a card (use --slug, --title, --body, --type, --links)
|
|
44
|
+
decide Record a decision (use --slug, --title, --rationale)
|
|
45
|
+
blockers <slug> Show what blocks a card
|
|
46
|
+
graph <slug> Traverse graph from a card
|
|
47
|
+
list List all cards
|
|
48
|
+
|
|
49
|
+
Templates:
|
|
50
|
+
openclaw-multiagent Multi-agent coordination for OpenClaw
|
|
51
|
+
|
|
52
|
+
Options:
|
|
53
|
+
--workspace <slug> Workspace (default: "default")
|
|
54
|
+
--agent <id> Agent ID for multi-agent setups
|
|
55
|
+
|
|
56
|
+
Ingest Options:
|
|
57
|
+
--source <prefix> Slug prefix for ingested cards (default: filename)
|
|
58
|
+
--type <cardType> Override default card type inference
|
|
59
|
+
--dry Preview cards without storing
|
|
60
|
+
|
|
61
|
+
Environment:
|
|
62
|
+
HYPERSTACK_API_KEY Your API key (or use 'login' command)
|
|
63
|
+
HYPERSTACK_WORKSPACE Default workspace
|
|
64
|
+
|
|
65
|
+
Examples:
|
|
66
|
+
npx hyperstack-core login
|
|
67
|
+
npx hyperstack-core ingest ./README.md
|
|
68
|
+
npx hyperstack-core ingest ./docs/
|
|
69
|
+
cat DECISIONS.md | npx hyperstack-core ingest --source decisions
|
|
70
|
+
npx hyperstack-core ingest ./README.md --dry
|
|
71
|
+
npx hyperstack-core store --slug "use-clerk" --title "Use Clerk for auth" --type decision
|
|
72
|
+
npx hyperstack-core blockers deploy-prod
|
|
73
|
+
npx hyperstack-core graph auth-api --depth 2
|
|
74
|
+
`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function init(template) {
|
|
78
|
+
const templatePath = resolve(__dirname, "templates", `${template}.json`);
|
|
79
|
+
if (!existsSync(templatePath)) {
|
|
80
|
+
console.error(`Template "${template}" not found.`);
|
|
81
|
+
console.error("Available: openclaw-multiagent");
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const tmpl = JSON.parse(readFileSync(templatePath, "utf-8"));
|
|
86
|
+
console.log(`\n🃏 HyperStack — ${tmpl.name}\n`);
|
|
87
|
+
console.log(` ${tmpl.description}\n`);
|
|
88
|
+
|
|
89
|
+
// Check for API key (env var or saved credentials)
|
|
90
|
+
const apiKey = getApiKey();
|
|
91
|
+
if (!apiKey) {
|
|
92
|
+
console.log("⚠️ Not authenticated.");
|
|
93
|
+
console.log(" Run: npx hyperstack-core login");
|
|
94
|
+
console.log(" Or: export HYPERSTACK_API_KEY=hs_your_key\n");
|
|
95
|
+
|
|
96
|
+
// Still create the config file
|
|
97
|
+
const configDir = ".hyperstack";
|
|
98
|
+
if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true });
|
|
99
|
+
writeFileSync(
|
|
100
|
+
resolve(configDir, "config.json"),
|
|
101
|
+
JSON.stringify({
|
|
102
|
+
workspace: "default",
|
|
103
|
+
template: template,
|
|
104
|
+
agents: tmpl.agentSetup?.agents || {},
|
|
105
|
+
}, null, 2)
|
|
106
|
+
);
|
|
107
|
+
console.log(`✅ Created .hyperstack/config.json`);
|
|
108
|
+
console.log(` Set HYPERSTACK_API_KEY and run again to seed starter cards.\n`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const client = new HyperStackClient({
|
|
113
|
+
apiKey: apiKey,
|
|
114
|
+
workspace: getFlag("workspace", "default"),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Create starter cards
|
|
118
|
+
console.log("Creating starter cards...\n");
|
|
119
|
+
for (const card of tmpl.starterCards || []) {
|
|
120
|
+
try {
|
|
121
|
+
const result = await client.store(card);
|
|
122
|
+
console.log(` ✅ [${card.slug}] ${card.title} — ${result.updated ? "updated" : "created"}`);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
console.log(` ❌ [${card.slug}] ${err.message}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Register agents if template has them
|
|
129
|
+
if (tmpl.agentSetup?.agents) {
|
|
130
|
+
console.log("\nRegistering agents...\n");
|
|
131
|
+
for (const [id, agent] of Object.entries(tmpl.agentSetup.agents)) {
|
|
132
|
+
try {
|
|
133
|
+
await client.registerAgent({
|
|
134
|
+
id,
|
|
135
|
+
name: id,
|
|
136
|
+
role: agent.role,
|
|
137
|
+
});
|
|
138
|
+
console.log(` ✅ Agent "${id}" registered (${agent.role.slice(0, 60)})`);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
console.log(` ❌ Agent "${id}": ${err.message}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Save config
|
|
146
|
+
const configDir = ".hyperstack";
|
|
147
|
+
if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true });
|
|
148
|
+
writeFileSync(
|
|
149
|
+
resolve(configDir, "config.json"),
|
|
150
|
+
JSON.stringify({
|
|
151
|
+
workspace: getFlag("workspace", "default"),
|
|
152
|
+
template: template,
|
|
153
|
+
agents: tmpl.agentSetup?.agents || {},
|
|
154
|
+
cardTypes: tmpl.cardTypes,
|
|
155
|
+
relationTypes: tmpl.relationTypes,
|
|
156
|
+
}, null, 2)
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
console.log(`\n✅ HyperStack initialized with "${template}" template`);
|
|
160
|
+
console.log(` Config: .hyperstack/config.json`);
|
|
161
|
+
console.log(` Cards: ${(tmpl.starterCards || []).length} starter cards created`);
|
|
162
|
+
console.log(` Agents: ${Object.keys(tmpl.agentSetup?.agents || {}).length} registered`);
|
|
163
|
+
|
|
164
|
+
// Show next steps
|
|
165
|
+
console.log(`
|
|
166
|
+
Next steps:
|
|
167
|
+
|
|
168
|
+
1. In your OpenClaw config, add HyperStack tools:
|
|
169
|
+
|
|
170
|
+
import { createOpenClawAdapter } from "hyperstack-core/adapters/openclaw";
|
|
171
|
+
const adapter = createOpenClawAdapter({ agentId: "researcher" });
|
|
172
|
+
|
|
173
|
+
2. Use typed graph instead of DECISIONS.md:
|
|
174
|
+
|
|
175
|
+
// Old: append to DECISIONS.md
|
|
176
|
+
// New:
|
|
177
|
+
await adapter.tools.hs_decide({
|
|
178
|
+
slug: "use-clerk",
|
|
179
|
+
title: "Use Clerk for auth",
|
|
180
|
+
rationale: "Better DX, lower cost, native Next.js support",
|
|
181
|
+
affects: "auth-api",
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
3. Query the graph:
|
|
185
|
+
|
|
186
|
+
await adapter.tools.hs_blockers({ slug: "deploy-prod" });
|
|
187
|
+
// → "2 blockers: [migration-23] needs approval, [auth-api] not deployed"
|
|
188
|
+
|
|
189
|
+
Docs: https://cascadeai.dev/hyperstack
|
|
190
|
+
Discord: Share your setup in #multi-agent
|
|
191
|
+
`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ─── Credentials file ─────────────────────────────────
|
|
195
|
+
|
|
196
|
+
const CRED_DIR = join(homedir(), ".hyperstack");
|
|
197
|
+
const CRED_FILE = join(CRED_DIR, "credentials.json");
|
|
198
|
+
const BASE_URL = process.env.HYPERSTACK_BASE_URL || "https://hyperstack-cloud.vercel.app";
|
|
199
|
+
|
|
200
|
+
function loadCredentials() {
|
|
201
|
+
try {
|
|
202
|
+
if (existsSync(CRED_FILE)) {
|
|
203
|
+
const creds = JSON.parse(readFileSync(CRED_FILE, "utf-8"));
|
|
204
|
+
return creds;
|
|
205
|
+
}
|
|
206
|
+
} catch {}
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function saveCredentials(creds) {
|
|
211
|
+
if (!existsSync(CRED_DIR)) mkdirSync(CRED_DIR, { recursive: true });
|
|
212
|
+
writeFileSync(CRED_FILE, JSON.stringify(creds, null, 2), { mode: 0o600 });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function deleteCredentials() {
|
|
216
|
+
try {
|
|
217
|
+
if (existsSync(CRED_FILE)) {
|
|
218
|
+
writeFileSync(CRED_FILE, "{}", { mode: 0o600 });
|
|
219
|
+
}
|
|
220
|
+
} catch {}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function getApiKey() {
|
|
224
|
+
// Priority: env var > credentials file
|
|
225
|
+
if (process.env.HYPERSTACK_API_KEY) return process.env.HYPERSTACK_API_KEY;
|
|
226
|
+
const creds = loadCredentials();
|
|
227
|
+
if (creds?.api_key) return creds.api_key;
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ─── Device flow login ────────────────────────────────
|
|
232
|
+
|
|
233
|
+
async function login() {
|
|
234
|
+
console.log("\n🃏 HyperStack Login\n");
|
|
235
|
+
|
|
236
|
+
// Check if already logged in
|
|
237
|
+
const existing = loadCredentials();
|
|
238
|
+
if (existing?.api_key) {
|
|
239
|
+
console.log(`Already logged in as ${existing.user?.email || "unknown"}`);
|
|
240
|
+
console.log(`API key: ${existing.api_key.slice(0, 8)}...`);
|
|
241
|
+
console.log(`Run 'hyperstack-core logout' to sign out.\n`);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Step 1: Request device code
|
|
246
|
+
console.log("Requesting device code...\n");
|
|
247
|
+
let deviceRes;
|
|
248
|
+
try {
|
|
249
|
+
const r = await fetch(BASE_URL + "/api/auth?action=device-code", { method: "POST" });
|
|
250
|
+
deviceRes = await r.json();
|
|
251
|
+
if (!r.ok) {
|
|
252
|
+
console.error("Error:", deviceRes.error || "Failed to get device code");
|
|
253
|
+
console.error("You can also set HYPERSTACK_API_KEY manually.");
|
|
254
|
+
console.error("Get a key at: https://cascadeai.dev/hyperstack\n");
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
} catch (err) {
|
|
258
|
+
console.error("Connection error:", err.message);
|
|
259
|
+
console.error("\nFallback: set HYPERSTACK_API_KEY manually.");
|
|
260
|
+
console.error("Get a key at: https://cascadeai.dev/hyperstack\n");
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Step 2: Show user the code and URL
|
|
265
|
+
console.log(" ┌─────────────────────────────────────┐");
|
|
266
|
+
console.log(" │ │");
|
|
267
|
+
console.log(` │ Code: ${deviceRes.user_code} │`);
|
|
268
|
+
console.log(" │ │");
|
|
269
|
+
console.log(" └─────────────────────────────────────┘\n");
|
|
270
|
+
console.log(" Open this URL in your browser:\n");
|
|
271
|
+
console.log(` ${deviceRes.verification_uri_complete}\n`);
|
|
272
|
+
console.log(" Waiting for approval...\n");
|
|
273
|
+
|
|
274
|
+
// Try to open browser automatically
|
|
275
|
+
try {
|
|
276
|
+
const { exec } = await import("child_process");
|
|
277
|
+
const url = deviceRes.verification_uri_complete;
|
|
278
|
+
const platform = process.platform;
|
|
279
|
+
if (platform === "darwin") exec(`open "${url}"`);
|
|
280
|
+
else if (platform === "linux") exec(`xdg-open "${url}" 2>/dev/null || echo ""`);
|
|
281
|
+
else if (platform === "win32") exec(`start "${url}"`);
|
|
282
|
+
} catch {}
|
|
283
|
+
|
|
284
|
+
// Step 3: Poll for approval
|
|
285
|
+
const pollInterval = (deviceRes.interval || 5) * 1000;
|
|
286
|
+
const maxAttempts = Math.ceil((deviceRes.expires_in || 600) / (deviceRes.interval || 5));
|
|
287
|
+
let attempt = 0;
|
|
288
|
+
|
|
289
|
+
while (attempt < maxAttempts) {
|
|
290
|
+
attempt++;
|
|
291
|
+
await new Promise(r => setTimeout(r, pollInterval));
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
const r = await fetch(BASE_URL + "/api/auth?action=device-token", {
|
|
295
|
+
method: "POST",
|
|
296
|
+
headers: { "Content-Type": "application/json" },
|
|
297
|
+
body: JSON.stringify({ device_code: deviceRes.device_code }),
|
|
298
|
+
});
|
|
299
|
+
const data = await r.json();
|
|
300
|
+
|
|
301
|
+
if (r.status === 428) {
|
|
302
|
+
// Still pending
|
|
303
|
+
process.stdout.write(".");
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (r.status === 403) {
|
|
308
|
+
console.log("\n\n❌ Device denied. Try again with 'hyperstack-core login'.\n");
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (r.status === 410) {
|
|
313
|
+
console.log("\n\n⏰ Code expired. Run 'hyperstack-core login' again.\n");
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (r.ok && data.api_key) {
|
|
318
|
+
// Success!
|
|
319
|
+
saveCredentials({
|
|
320
|
+
api_key: data.api_key,
|
|
321
|
+
user: data.user,
|
|
322
|
+
workspaces: data.workspaces,
|
|
323
|
+
authenticated_at: new Date().toISOString(),
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
console.log("\n");
|
|
327
|
+
console.log(" ✅ Logged in as " + data.user.email);
|
|
328
|
+
console.log(" Plan: " + data.user.plan);
|
|
329
|
+
console.log(" Workspaces: " + data.workspaces.map(w => w.slug).join(", "));
|
|
330
|
+
console.log(" Credentials saved to: ~/.hyperstack/credentials.json\n");
|
|
331
|
+
console.log(" You're ready! Try:");
|
|
332
|
+
console.log(" npx hyperstack-core init openclaw-multiagent");
|
|
333
|
+
console.log(" npx hyperstack-core list\n");
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Unknown error
|
|
338
|
+
console.error("\n\nUnexpected response:", data);
|
|
339
|
+
process.exit(1);
|
|
340
|
+
|
|
341
|
+
} catch (err) {
|
|
342
|
+
// Network error, keep trying
|
|
343
|
+
process.stdout.write("x");
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
console.log("\n\n⏰ Timed out. Run 'hyperstack-core login' again.\n");
|
|
348
|
+
process.exit(1);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async function logout() {
|
|
352
|
+
const creds = loadCredentials();
|
|
353
|
+
if (!creds) {
|
|
354
|
+
console.log("Not logged in.\n");
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
try {
|
|
358
|
+
writeFileSync(CRED_FILE, "{}", { mode: 0o600 });
|
|
359
|
+
} catch {}
|
|
360
|
+
console.log(`Logged out. Removed ~/.hyperstack/credentials.json\n`);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async function run() {
|
|
364
|
+
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
365
|
+
help();
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (command === "login") {
|
|
370
|
+
await login();
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (command === "logout") {
|
|
375
|
+
await logout();
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (command === "init") {
|
|
380
|
+
const template = args[1];
|
|
381
|
+
if (!template) {
|
|
382
|
+
console.error("Usage: npx hyperstack-core init <template>");
|
|
383
|
+
console.error("Available: openclaw-multiagent");
|
|
384
|
+
process.exit(1);
|
|
385
|
+
}
|
|
386
|
+
await init(template);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ─── Ingest command ─────────────────────────────────
|
|
391
|
+
|
|
392
|
+
if (command === "ingest") {
|
|
393
|
+
const target = args[1] && !args[1].startsWith("--") ? args[1] : null;
|
|
394
|
+
const sourceFlag = getFlag("source", "");
|
|
395
|
+
const typeFlag = getFlag("type", "");
|
|
396
|
+
const dryRun = args.includes("--dry");
|
|
397
|
+
|
|
398
|
+
let allCards = [];
|
|
399
|
+
let totalEdges = 0;
|
|
400
|
+
const fileSummaries = [];
|
|
401
|
+
|
|
402
|
+
if (target) {
|
|
403
|
+
const resolved = resolve(target);
|
|
404
|
+
if (!existsSync(resolved)) {
|
|
405
|
+
console.error(`Not found: ${target}`);
|
|
406
|
+
process.exit(1);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const stat = statSync(resolved);
|
|
410
|
+
if (stat.isDirectory()) {
|
|
411
|
+
const files = readdirSync(resolved)
|
|
412
|
+
.filter((f) => /\.(md|txt|log)$/i.test(f))
|
|
413
|
+
.sort();
|
|
414
|
+
if (!files.length) {
|
|
415
|
+
console.error(`No .md/.txt/.log files in ${target}`);
|
|
416
|
+
process.exit(1);
|
|
417
|
+
}
|
|
418
|
+
for (const f of files) {
|
|
419
|
+
const content = readFileSync(join(resolved, f), "utf-8");
|
|
420
|
+
const prefix = sourceFlag || slugify(f.replace(/\.[^.]+$/, ""));
|
|
421
|
+
const result = parse(content, { prefix, defaultType: typeFlag || undefined });
|
|
422
|
+
allCards.push(...result.cards);
|
|
423
|
+
totalEdges += result.edgeCount;
|
|
424
|
+
fileSummaries.push({ name: f, cards: result.cards.length, edges: result.edgeCount, format: result.format });
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
const content = readFileSync(resolved, "utf-8");
|
|
428
|
+
const name = basename(resolved).replace(/\.[^.]+$/, "");
|
|
429
|
+
const prefix = sourceFlag || slugify(name);
|
|
430
|
+
const result = parse(content, { prefix, defaultType: typeFlag || undefined });
|
|
431
|
+
allCards = result.cards;
|
|
432
|
+
totalEdges = result.edgeCount;
|
|
433
|
+
fileSummaries.push({ name: basename(resolved), cards: result.cards.length, edges: result.edgeCount, format: result.format });
|
|
434
|
+
}
|
|
435
|
+
} else if (!process.stdin.isTTY) {
|
|
436
|
+
const chunks = [];
|
|
437
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
438
|
+
const content = Buffer.concat(chunks).toString("utf-8");
|
|
439
|
+
const prefix = sourceFlag || "import";
|
|
440
|
+
const result = parse(content, { prefix, defaultType: typeFlag || undefined });
|
|
441
|
+
allCards = result.cards;
|
|
442
|
+
totalEdges = result.edgeCount;
|
|
443
|
+
fileSummaries.push({ name: "stdin", cards: result.cards.length, edges: result.edgeCount, format: result.format });
|
|
444
|
+
} else {
|
|
445
|
+
console.error("Usage: hyperstack-core ingest <file|directory>");
|
|
446
|
+
console.error(" cat file.md | hyperstack-core ingest --source my-doc");
|
|
447
|
+
process.exit(1);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (!allCards.length) {
|
|
451
|
+
console.log("No cards extracted from input.");
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Print parse summary
|
|
456
|
+
console.log(`\n🧠 Parsed: ${allCards.length} cards, ${totalEdges} edges\n`);
|
|
457
|
+
for (const s of fileSummaries) {
|
|
458
|
+
console.log(` 📄 ${s.name} → ${s.cards} cards, ${s.edges} edges (${s.format})`);
|
|
459
|
+
}
|
|
460
|
+
console.log();
|
|
461
|
+
|
|
462
|
+
// Print card preview
|
|
463
|
+
for (const card of allCards) {
|
|
464
|
+
const edgeInfo = card.links?.length
|
|
465
|
+
? ` → ${card.links.map((l) => `${l.relation}:${l.target}`).join(", ")}`
|
|
466
|
+
: "";
|
|
467
|
+
console.log(` [${card.slug}] ${card.title} (${card.cardType})${edgeInfo}`);
|
|
468
|
+
}
|
|
469
|
+
console.log();
|
|
470
|
+
|
|
471
|
+
if (dryRun) {
|
|
472
|
+
console.log("--dry: No cards stored. Remove --dry to store.\n");
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Store cards
|
|
477
|
+
const apiKey = getApiKey();
|
|
478
|
+
if (!apiKey) {
|
|
479
|
+
console.error("Not authenticated. Run: npx hyperstack-core login\n");
|
|
480
|
+
process.exit(1);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const client = new HyperStackClient({
|
|
484
|
+
apiKey,
|
|
485
|
+
workspace: getFlag("workspace", "default"),
|
|
486
|
+
agentId: getFlag("agent", undefined),
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
const result = await client.ingest(allCards, {
|
|
490
|
+
onProgress: (i, total, card, res) => {
|
|
491
|
+
const ok = !(res instanceof Error);
|
|
492
|
+
process.stdout.write(ok ? "." : "x");
|
|
493
|
+
},
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
console.log("\n");
|
|
497
|
+
console.log(`✅ ${result.stored} cards stored${result.failed ? `, ${result.failed} failed` : ""}`);
|
|
498
|
+
if (result.errors.length) {
|
|
499
|
+
for (const e of result.errors) console.log(` ❌ [${e.slug}] ${e.error}`);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Show next steps with first card slug
|
|
503
|
+
const firstSlug = allCards[0]?.slug || "";
|
|
504
|
+
console.log(`\nTry:`);
|
|
505
|
+
console.log(` npx hyperstack-core search "${allCards[0]?.title?.split(" ").slice(0, 3).join(" ") || "getting started"}"`);
|
|
506
|
+
if (firstSlug) console.log(` npx hyperstack-core graph ${firstSlug} --depth 2`);
|
|
507
|
+
console.log();
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// All other commands need API key (from env or credentials file)
|
|
512
|
+
const apiKey = getApiKey();
|
|
513
|
+
let client;
|
|
514
|
+
try {
|
|
515
|
+
client = new HyperStackClient({
|
|
516
|
+
apiKey: apiKey,
|
|
517
|
+
workspace: getFlag("workspace", "default"),
|
|
518
|
+
agentId: getFlag("agent", undefined),
|
|
519
|
+
});
|
|
520
|
+
} catch (err) {
|
|
521
|
+
console.error(err.message);
|
|
522
|
+
console.error("\nRun 'npx hyperstack-core login' to authenticate.\n");
|
|
523
|
+
process.exit(1);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (command === "search") {
|
|
527
|
+
const query = args.slice(1).filter(a => !a.startsWith("--")).join(" ");
|
|
528
|
+
if (!query) { console.error("Usage: hyperstack-core search <query>"); process.exit(1); }
|
|
529
|
+
const result = await client.search(query);
|
|
530
|
+
const cards = result.results || [];
|
|
531
|
+
if (!cards.length) { console.log("No results."); return; }
|
|
532
|
+
for (const c of cards.slice(0, 10)) {
|
|
533
|
+
console.log(`[${c.slug}] ${c.title} (${c.cardType || "general"})`);
|
|
534
|
+
if (c.body) console.log(` ${c.body.slice(0, 150)}`);
|
|
535
|
+
if (c.links?.length) console.log(` Links: ${c.links.map(l => `${l.relation}→${l.target}`).join(", ")}`);
|
|
536
|
+
console.log();
|
|
537
|
+
}
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (command === "store") {
|
|
542
|
+
const slug = getFlag("slug");
|
|
543
|
+
const title = getFlag("title");
|
|
544
|
+
if (!slug || !title) { console.error("Required: --slug and --title"); process.exit(1); }
|
|
545
|
+
const result = await client.store({
|
|
546
|
+
slug,
|
|
547
|
+
title,
|
|
548
|
+
body: getFlag("body"),
|
|
549
|
+
cardType: getFlag("type", "general"),
|
|
550
|
+
keywords: getFlag("keywords") ? getFlag("keywords").split(",").map(k => k.trim()) : [],
|
|
551
|
+
links: getFlag("links") ? getFlag("links").split(",").map(l => {
|
|
552
|
+
const [target, relation] = l.trim().split(":");
|
|
553
|
+
return { target, relation: relation || "related" };
|
|
554
|
+
}) : [],
|
|
555
|
+
});
|
|
556
|
+
console.log(`${result.updated ? "Updated" : "Created"} [${slug}]: ${title}`);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (command === "decide") {
|
|
561
|
+
const slug = getFlag("slug");
|
|
562
|
+
const title = getFlag("title");
|
|
563
|
+
if (!slug || !title) { console.error("Required: --slug and --title"); process.exit(1); }
|
|
564
|
+
await client.decide({
|
|
565
|
+
slug,
|
|
566
|
+
title,
|
|
567
|
+
body: getFlag("rationale", getFlag("body", "")),
|
|
568
|
+
affects: getFlag("affects") ? getFlag("affects").split(",").map(s => s.trim()) : [],
|
|
569
|
+
blocks: getFlag("blocks") ? getFlag("blocks").split(",").map(s => s.trim()) : [],
|
|
570
|
+
});
|
|
571
|
+
console.log(`Decision recorded: [${slug}] ${title}`);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (command === "blockers") {
|
|
576
|
+
const slug = args[1];
|
|
577
|
+
if (!slug) { console.error("Usage: hyperstack-core blockers <slug>"); process.exit(1); }
|
|
578
|
+
try {
|
|
579
|
+
const result = await client.blockers(slug);
|
|
580
|
+
const blockers = result.blockers || [];
|
|
581
|
+
if (!blockers.length) { console.log(`Nothing blocks [${slug}].`); return; }
|
|
582
|
+
console.log(`${blockers.length} blocker(s) for [${slug}]:`);
|
|
583
|
+
for (const b of blockers) {
|
|
584
|
+
console.log(` [${b.slug}] ${b.title || "?"}`);
|
|
585
|
+
}
|
|
586
|
+
} catch (err) {
|
|
587
|
+
console.error(`Error: ${err.message}`);
|
|
588
|
+
}
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (command === "graph") {
|
|
593
|
+
const from = args[1];
|
|
594
|
+
if (!from) { console.error("Usage: hyperstack-core graph <slug>"); process.exit(1); }
|
|
595
|
+
try {
|
|
596
|
+
const result = await client.graph(from, {
|
|
597
|
+
depth: parseInt(getFlag("depth", "2")),
|
|
598
|
+
relation: getFlag("relation") || undefined,
|
|
599
|
+
});
|
|
600
|
+
console.log(`Graph from [${from}]: ${result.nodes?.length || 0} nodes, ${result.edges?.length || 0} edges\n`);
|
|
601
|
+
for (const n of result.nodes || []) {
|
|
602
|
+
console.log(` [${n.slug}] ${n.title || "?"} (${n.cardType || "?"})`);
|
|
603
|
+
}
|
|
604
|
+
console.log();
|
|
605
|
+
for (const e of result.edges || []) {
|
|
606
|
+
console.log(` ${e.from} --${e.relation}--> ${e.to}`);
|
|
607
|
+
}
|
|
608
|
+
} catch (err) {
|
|
609
|
+
console.error(`Error: ${err.message}`);
|
|
610
|
+
}
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
if (command === "list") {
|
|
615
|
+
const result = await client.list();
|
|
616
|
+
console.log(`HyperStack: ${result.count ?? 0}/${result.limit ?? "?"} cards (plan: ${result.plan || "?"})\n`);
|
|
617
|
+
for (const c of result.cards || []) {
|
|
618
|
+
console.log(` [${c.slug}] ${c.title} (${c.cardType || "general"})`);
|
|
619
|
+
}
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
console.error(`Unknown command: ${command}`);
|
|
624
|
+
help();
|
|
625
|
+
process.exit(1);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
run().catch(err => {
|
|
629
|
+
console.error(`Error: ${err.message}`);
|
|
630
|
+
process.exit(1);
|
|
631
|
+
});
|