hyperstack-core 1.3.0 → 1.5.1

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/index.js CHANGED
@@ -1,18 +1,19 @@
1
- /**
2
- * hyperstack-core
3
- *
4
- * Typed graph memory for AI agents.
5
- *
6
- * Usage:
7
- * import { HyperStackClient } from "hyperstack-core";
8
- * const hs = new HyperStackClient({ apiKey: "hs_..." });
9
- * await hs.store({ slug: "use-clerk", title: "Use Clerk for auth", cardType: "decision" });
10
- * await hs.blockers("deploy-prod"); // → typed blockers
11
- *
12
- * OpenClaw:
13
- * import { createOpenClawAdapter } from "hyperstack-core/adapters/openclaw";
14
- * const adapter = createOpenClawAdapter({ agentId: "researcher" });
15
- */
16
-
17
- export { HyperStackClient } from "./src/client.js";
18
- export { createOpenClawAdapter } from "./adapters/openclaw.js";
1
+ /**
2
+ * hyperstack-core
3
+ *
4
+ * Typed graph memory for AI agents.
5
+ *
6
+ * Usage:
7
+ * import { HyperStackClient } from "hyperstack-core";
8
+ * const hs = new HyperStackClient({ apiKey: "hs_..." });
9
+ * await hs.store({ slug: "use-clerk", title: "Use Clerk for auth", cardType: "decision" });
10
+ * await hs.blockers("deploy-prod"); // → typed blockers
11
+ *
12
+ * OpenClaw:
13
+ * import { createOpenClawAdapter } from "hyperstack-core/adapters/openclaw";
14
+ * const adapter = createOpenClawAdapter({ agentId: "researcher" });
15
+ */
16
+
17
+ export { HyperStackClient } from "./src/client.js";
18
+ export { createOpenClawAdapter } from "./adapters/openclaw.js";
19
+ export { parse, parseMarkdown, parseConversation, parsePlainText, slugify } from "./src/parser.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperstack-core",
3
- "version": "1.3.0",
3
+ "version": "1.5.1",
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",
@@ -10,6 +10,7 @@
10
10
  "exports": {
11
11
  ".": "./index.js",
12
12
  "./client": "./src/client.js",
13
+ "./parser": "./src/parser.js",
13
14
  "./adapters/openclaw": "./adapters/openclaw.js"
14
15
  },
15
16
  "files": [
package/src/client.js CHANGED
@@ -1,267 +1,322 @@
1
- /**
2
- * hyperstack-core — HyperStack SDK for typed graph memory
3
- *
4
- * Lightweight client for the HyperStack API. Works in Node.js 18+.
5
- * No dependencies. Used by the OpenClaw adapter and CLI.
6
- */
7
-
8
- const DEFAULT_BASE = "https://hyperstack-cloud.vercel.app";
9
-
10
- // ESM-compatible sync file reading
11
- import { readFileSync, existsSync } from "fs";
12
- import { join } from "path";
13
- import { homedir } from "os";
14
-
15
- function loadCredApiKey() {
16
- try {
17
- const credFile = join(homedir(), ".hyperstack", "credentials.json");
18
- if (existsSync(credFile)) {
19
- const creds = JSON.parse(readFileSync(credFile, "utf-8"));
20
- if (creds.api_key) return creds.api_key;
21
- }
22
- } catch {}
23
- return "";
24
- }
25
-
26
- class HyperStackClient {
27
- /**
28
- * @param {object} opts
29
- * @param {string} opts.apiKey — HyperStack API key (hs_...)
30
- * @param {string} [opts.workspace="default"] — workspace slug
31
- * @param {string} [opts.baseUrl] — API base URL
32
- * @param {string} [opts.agentId] — agent identifier for multi-agent setups
33
- */
34
- constructor(opts = {}) {
35
- this.apiKey = opts.apiKey || process.env.HYPERSTACK_API_KEY || loadCredApiKey();
36
- this.workspace = opts.workspace || process.env.HYPERSTACK_WORKSPACE || "default";
37
- this.baseUrl = opts.baseUrl || process.env.HYPERSTACK_BASE_URL || DEFAULT_BASE;
38
- this.agentId = opts.agentId || null;
39
-
40
- if (!this.apiKey) {
41
- throw new Error(
42
- "HYPERSTACK_API_KEY required.\n" +
43
- "Run: npx hyperstack-core login\n" +
44
- "Or: export HYPERSTACK_API_KEY=hs_your_key\n" +
45
- "Get a free account: https://cascadeai.dev/hyperstack"
46
- );
47
- }
48
- }
49
-
50
- /** @private */
51
- async _request(method, path, body = null) {
52
- const url = `${this.baseUrl}${path}`;
53
- const opts = {
54
- method,
55
- headers: {
56
- "X-API-Key": this.apiKey,
57
- "Content-Type": "application/json",
58
- "User-Agent": "hyperstack-core/1.0.0",
59
- },
60
- };
61
- if (body) opts.body = JSON.stringify(body);
62
-
63
- const res = await fetch(url, opts);
64
- const data = await res.json();
65
- if (!res.ok) {
66
- const err = new Error(data.error || `HTTP ${res.status}`);
67
- err.status = res.status;
68
- err.body = data;
69
- throw err;
70
- }
71
- return data;
72
- }
73
-
74
- // ─── Cards ───────────────────────────────────────────
75
-
76
- /**
77
- * Create or update a card (upsert by slug).
78
- * @param {object} card
79
- * @param {string} card.slug — unique identifier
80
- * @param {string} card.title — short title
81
- * @param {string} [card.body] — description (2-5 sentences)
82
- * @param {string} [card.cardType] — person|project|decision|preference|workflow|event|general
83
- * @param {string} [card.stack] — projects|people|decisions|preferences|workflows|general
84
- * @param {string[]} [card.keywords] — search terms
85
- * @param {Array<{target: string, relation: string}>} [card.links] — typed relations
86
- * @param {object} [card.meta] — freeform metadata
87
- * @returns {Promise<{slug: string, updated: boolean}>}
88
- */
89
- async store(card) {
90
- if (!card.slug) throw new Error("card.slug required");
91
- if (!card.title) throw new Error("card.title required");
92
-
93
- // Auto-tag with agentId if set
94
- if (this.agentId) {
95
- card.meta = card.meta || {};
96
- card.meta.agentId = this.agentId;
97
- card.keywords = card.keywords || [];
98
- if (!card.keywords.includes(`agent:${this.agentId}`)) {
99
- card.keywords.push(`agent:${this.agentId}`);
100
- }
101
- }
102
-
103
- return this._request("POST", `/api/cards?workspace=${this.workspace}`, card);
104
- }
105
-
106
- /**
107
- * Search cards by query (hybrid semantic + keyword).
108
- * @param {string} query
109
- * @returns {Promise<{results: Array}>}
110
- */
111
- async search(query) {
112
- return this._request("GET", `/api/search?workspace=${this.workspace}&q=${encodeURIComponent(query)}`);
113
- }
114
-
115
- /**
116
- * List all cards in workspace.
117
- * @returns {Promise<{cards: Array, count: number, plan: string}>}
118
- */
119
- async list() {
120
- return this._request("GET", `/api/cards?workspace=${this.workspace}`);
121
- }
122
-
123
- /**
124
- * Delete a card by slug.
125
- * @param {string} slug
126
- * @returns {Promise<{deleted: boolean}>}
127
- */
128
- async delete(slug) {
129
- return this._request("DELETE", `/api/cards?workspace=${this.workspace}&id=${slug}`);
130
- }
131
-
132
- // ─── Graph ───────────────────────────────────────────
133
-
134
- /**
135
- * Traverse the knowledge graph from a starting card.
136
- * @param {string} fromstarting card slug
137
- * @param {object} [opts]
138
- * @param {number} [opts.depth=1] — hops to traverse (1-3)
139
- * @param {string} [opts.relation] filter by relation type
140
- * @param {string} [opts.type] — filter by card type
141
- * @param {string} [opts.at] — ISO timestamp for time-travel
142
- * @returns {Promise<{nodes: Array, edges: Array}>}
143
- */
144
- async graph(from, opts = {}) {
145
- let url = `/api/graph?workspace=${this.workspace}&from=${from}`;
146
- if (opts.depth) url += `&depth=${opts.depth}`;
147
- if (opts.relation) url += `&relation=${opts.relation}`;
148
- if (opts.type) url += `&type=${opts.type}`;
149
- if (opts.at) url += `&at=${encodeURIComponent(opts.at)}`;
150
- return this._request("GET", url);
151
- }
152
-
153
- // ─── Multi-Agent Helpers ──────────────────────────────
154
-
155
- /**
156
- * Query cards that block a specific card/task.
157
- * Shorthand for graph traversal with relation="blocks".
158
- * @param {string} slug — card being blocked
159
- * @returns {Promise<{blockers: Array}>}
160
- */
161
- async blockers(slug) {
162
- try {
163
- const result = await this.graph(slug, { depth: 2, relation: "blocks" });
164
- const blockers = (result.edges || [])
165
- .filter(e => e.relation === "blocks" && e.to === slug)
166
- .map(e => {
167
- const node = (result.nodes || []).find(n => n.slug === e.from);
168
- return node || { slug: e.from };
169
- });
170
- return { blockers, graph: result };
171
- } catch (err) {
172
- // If graph API not available (free tier), fallback to search
173
- if (err.status === 403) {
174
- const searchResult = await this.search(`blocks ${slug}`);
175
- return {
176
- blockers: (searchResult.results || []).filter(c =>
177
- c.links?.some(l => l.relation === "blocks" && l.target === slug)
178
- ),
179
- fallback: true,
180
- };
181
- }
182
- throw err;
183
- }
184
- }
185
-
186
- /**
187
- * Find cards owned by a specific agent.
188
- * @param {string} [agentId] — defaults to this.agentId
189
- * @returns {Promise<{cards: Array}>}
190
- */
191
- async agentCards(agentId) {
192
- const id = agentId || this.agentId;
193
- if (!id) throw new Error("agentId required");
194
- return this.search(`agent:${id}`);
195
- }
196
-
197
- /**
198
- * Record a decision with full provenance.
199
- * Creates a decision card + links to who decided and what it affects.
200
- * @param {object} decision
201
- * @param {string} decision.slug
202
- * @param {string} decision.title
203
- * @param {string} decision.body — rationale
204
- * @param {string} [decision.decidedBy] — agent/person slug
205
- * @param {string[]} [decision.affects] — slugs of affected cards
206
- * @param {string[]} [decision.blocks] — slugs of things this blocks
207
- * @param {object} [decision.meta]
208
- */
209
- async decide(decision) {
210
- const links = [];
211
- if (decision.decidedBy) {
212
- links.push({ target: decision.decidedBy, relation: "decided" });
213
- }
214
- if (decision.affects) {
215
- for (const a of decision.affects) {
216
- links.push({ target: a, relation: "triggers" });
217
- }
218
- }
219
- if (decision.blocks) {
220
- for (const b of decision.blocks) {
221
- links.push({ target: b, relation: "blocks" });
222
- }
223
- }
224
-
225
- return this.store({
226
- slug: decision.slug,
227
- title: decision.title,
228
- body: decision.body,
229
- cardType: "decision",
230
- stack: "decisions",
231
- links,
232
- meta: { ...decision.meta, decidedAt: new Date().toISOString() },
233
- keywords: decision.keywords || [],
234
- });
235
- }
236
-
237
- /**
238
- * Register an agent as a card in the graph.
239
- * @param {object} agent
240
- * @param {string} agent.id — unique agent ID
241
- * @param {string} agent.name — display name
242
- * @param {string} agent.role what this agent does
243
- * @param {string[]} [agent.owns] — slugs this agent owns
244
- */
245
- async registerAgent(agent) {
246
- const links = [];
247
- if (agent.owns) {
248
- for (const o of agent.owns) {
249
- links.push({ target: o, relation: "owns" });
250
- }
251
- }
252
-
253
- return this.store({
254
- slug: `agent-${agent.id}`,
255
- title: `Agent: ${agent.name}`,
256
- body: agent.role,
257
- cardType: "person",
258
- stack: "people",
259
- links,
260
- keywords: ["agent", agent.id, agent.name],
261
- meta: { agentId: agent.id, registeredAt: new Date().toISOString() },
262
- });
263
- }
264
- }
265
-
266
- export { HyperStackClient };
267
- export default HyperStackClient;
1
+ /**
2
+ * hyperstack-core — HyperStack SDK for typed graph memory
3
+ *
4
+ * Lightweight client for the HyperStack API. Works in Node.js 18+.
5
+ * No dependencies. Used by the OpenClaw adapter and CLI.
6
+ */
7
+
8
+ const DEFAULT_BASE = "https://hyperstack-cloud.vercel.app";
9
+
10
+ // ESM-compatible sync file reading
11
+ import { readFileSync, existsSync } from "fs";
12
+ import { join } from "path";
13
+ import { homedir } from "os";
14
+
15
+ function loadCredApiKey() {
16
+ try {
17
+ const credFile = join(homedir(), ".hyperstack", "credentials.json");
18
+ if (existsSync(credFile)) {
19
+ const creds = JSON.parse(readFileSync(credFile, "utf-8"));
20
+ if (creds.api_key) return creds.api_key;
21
+ }
22
+ } catch {}
23
+ return "";
24
+ }
25
+
26
+ class HyperStackClient {
27
+ /**
28
+ * @param {object} opts
29
+ * @param {string} opts.apiKey — HyperStack API key (hs_...)
30
+ * @param {string} [opts.workspace="default"] — workspace slug
31
+ * @param {string} [opts.baseUrl] — API base URL
32
+ * @param {string} [opts.agentId] — agent identifier for multi-agent setups
33
+ */
34
+ constructor(opts = {}) {
35
+ this.apiKey = opts.apiKey || process.env.HYPERSTACK_API_KEY || loadCredApiKey();
36
+ this.workspace = opts.workspace || process.env.HYPERSTACK_WORKSPACE || "default";
37
+ this.baseUrl = opts.baseUrl || process.env.HYPERSTACK_BASE_URL || DEFAULT_BASE;
38
+ this.agentId = opts.agentId || null;
39
+
40
+ if (!this.apiKey) {
41
+ throw new Error(
42
+ "HYPERSTACK_API_KEY required.\n" +
43
+ "Run: npx hyperstack-core login\n" +
44
+ "Or: export HYPERSTACK_API_KEY=hs_your_key\n" +
45
+ "Get a free account: https://cascadeai.dev/hyperstack"
46
+ );
47
+ }
48
+ }
49
+
50
+ /** @private */
51
+ async _request(method, path, body = null) {
52
+ const url = `${this.baseUrl}${path}`;
53
+ const opts = {
54
+ method,
55
+ headers: {
56
+ "X-API-Key": this.apiKey,
57
+ "Content-Type": "application/json",
58
+ "User-Agent": "hyperstack-core/1.0.0",
59
+ },
60
+ };
61
+ if (body) opts.body = JSON.stringify(body);
62
+
63
+ const res = await fetch(url, opts);
64
+ const data = await res.json();
65
+ if (!res.ok) {
66
+ const err = new Error(data.error || `HTTP ${res.status}`);
67
+ err.status = res.status;
68
+ err.body = data;
69
+ throw err;
70
+ }
71
+ return data;
72
+ }
73
+
74
+ // ─── Cards ───────────────────────────────────────────
75
+
76
+ /**
77
+ * Create or update a card (upsert by slug).
78
+ * @param {object} card
79
+ * @param {string} card.slug — unique identifier
80
+ * @param {string} card.title — short title
81
+ * @param {string} [card.body] — description (2-5 sentences)
82
+ * @param {string} [card.cardType] — person|project|decision|preference|workflow|event|general
83
+ * @param {string} [card.stack] — projects|people|decisions|preferences|workflows|general
84
+ * @param {string[]} [card.keywords] — search terms
85
+ * @param {Array<{target: string, relation: string}>} [card.links] — typed relations
86
+ * @param {object} [card.meta] — freeform metadata
87
+ * @returns {Promise<{slug: string, updated: boolean}>}
88
+ */
89
+ async store(card) {
90
+ if (!card.slug) throw new Error("card.slug required");
91
+ if (!card.title) throw new Error("card.title required");
92
+
93
+ // Auto-tag with agentId if set
94
+ if (this.agentId) {
95
+ card.meta = card.meta || {};
96
+ card.meta.agentId = this.agentId;
97
+ card.keywords = card.keywords || [];
98
+ if (!card.keywords.includes(`agent:${this.agentId}`)) {
99
+ card.keywords.push(`agent:${this.agentId}`);
100
+ }
101
+ }
102
+
103
+ return this._request("POST", `/api/cards?workspace=${this.workspace}`, card);
104
+ }
105
+
106
+ /**
107
+ * Search cards by query (hybrid semantic + keyword).
108
+ * @param {string} query
109
+ * @returns {Promise<{results: Array}>}
110
+ */
111
+ async search(query) {
112
+ return this._request("GET", `/api/search?workspace=${this.workspace}&q=${encodeURIComponent(query)}`);
113
+ }
114
+
115
+ /**
116
+ * List all cards in workspace.
117
+ * @returns {Promise<{cards: Array, count: number, plan: string}>}
118
+ */
119
+ async list() {
120
+ return this._request("GET", `/api/cards?workspace=${this.workspace}`);
121
+ }
122
+
123
+ /**
124
+ * Delete a card by slug.
125
+ * @param {string} slug
126
+ * @returns {Promise<{deleted: boolean}>}
127
+ */
128
+ async delete(slug) {
129
+ return this._request("DELETE", `/api/cards?workspace=${this.workspace}&id=${slug}`);
130
+ }
131
+
132
+ /**
133
+ * Batch-store an array of cards. Stores sequentially, collects errors.
134
+ * @param {Array<object>} cards — array of card objects (slug + title required)
135
+ * @param {object} [opts]
136
+ * @param {function} [opts.onProgress]called with (index, total, card, result|error)
137
+ * @returns {Promise<{stored: number, failed: number, errors: Array}>}
138
+ */
139
+ async ingest(cards, opts = {}) {
140
+ let stored = 0;
141
+ let failed = 0;
142
+ const errors = [];
143
+
144
+ for (let i = 0; i < cards.length; i++) {
145
+ try {
146
+ const result = await this.store(cards[i]);
147
+ stored++;
148
+ if (opts.onProgress) opts.onProgress(i, cards.length, cards[i], result);
149
+ } catch (err) {
150
+ failed++;
151
+ errors.push({ slug: cards[i].slug, error: err.message });
152
+ if (opts.onProgress) opts.onProgress(i, cards.length, cards[i], err);
153
+ }
154
+ }
155
+
156
+ return { stored, failed, errors };
157
+ }
158
+
159
+ // ─── Graph ───────────────────────────────────────────
160
+
161
+ /**
162
+ * Traverse the knowledge graph from a starting card.
163
+ * @param {string} from starting card slug
164
+ * @param {object} [opts]
165
+ * @param {number} [opts.depth=1] hops to traverse (1-3)
166
+ * @param {string} [opts.relation] filter by relation type
167
+ * @param {string} [opts.type] filter by card type
168
+ * @param {string} [opts.mode] — "impact" for reverse traversal, "replay" for decision replay
169
+ * @param {string} [opts.at] — ISO timestamp for time-travel
170
+ * @returns {Promise<{nodes: Array, edges: Array}>}
171
+ */
172
+ async graph(from, opts = {}) {
173
+ let url = `/api/graph?workspace=${this.workspace}&from=${from}`;
174
+ if (opts.depth) url += `&depth=${opts.depth}`;
175
+ if (opts.relation) url += `&relation=${opts.relation}`;
176
+ if (opts.type) url += `&type=${opts.type}`;
177
+ if (opts.mode) url += `&mode=${opts.mode}`;
178
+ if (opts.at) url += `&at=${encodeURIComponent(opts.at)}`;
179
+ return this._request("GET", url);
180
+ }
181
+
182
+ /**
183
+ * Deterministic impact analysis — reverse traversal.
184
+ * Answers: "what depends on X?" / "what would break if X changed?"
185
+ *
186
+ * @param {string} slug — the card to analyse
187
+ * @param {object} [opts]
188
+ * @param {number} [opts.depth=2] — hops to traverse upstream (1-3)
189
+ * @param {string} [opts.relation] — filter by relation type (e.g. "blocks", "depends_on")
190
+ * @returns {Promise<{root: string, mode: "impact", nodes: Array, edges: Array}>}
191
+ *
192
+ * @example
193
+ * // What depends on the billing API?
194
+ * const result = await hs.impact("billing-api", { depth: 2 });
195
+ * // Returns all cards that link TO billing-api, up to 2 hops upstream
196
+ *
197
+ * @example
198
+ * // What specifically blocks the deploy?
199
+ * const result = await hs.impact("prod-deploy", { relation: "blocks" });
200
+ */
201
+ async impact(slug, opts = {}) {
202
+ const { depth = 2, relation } = opts;
203
+ let url = `/api/graph?workspace=${this.workspace}&from=${slug}&mode=impact&depth=${depth}`;
204
+ if (relation) url += `&relation=${encodeURIComponent(relation)}`;
205
+ return this._request("GET", url);
206
+ }
207
+
208
+ // ─── Multi-Agent Helpers ──────────────────────────────
209
+
210
+ /**
211
+ * Query cards that block a specific card/task.
212
+ * Shorthand for graph traversal with relation="blocks".
213
+ * @param {string} slug — card being blocked
214
+ * @returns {Promise<{blockers: Array}>}
215
+ */
216
+ async blockers(slug) {
217
+ try {
218
+ const result = await this.graph(slug, { depth: 2, relation: "blocks" });
219
+ const blockers = (result.edges || [])
220
+ .filter(e => e.relation === "blocks" && e.to === slug)
221
+ .map(e => {
222
+ const node = (result.nodes || []).find(n => n.slug === e.from);
223
+ return node || { slug: e.from };
224
+ });
225
+ return { blockers, graph: result };
226
+ } catch (err) {
227
+ // If graph API not available (free tier), fallback to search
228
+ if (err.status === 403) {
229
+ const searchResult = await this.search(`blocks ${slug}`);
230
+ return {
231
+ blockers: (searchResult.results || []).filter(c =>
232
+ c.links?.some(l => l.relation === "blocks" && l.target === slug)
233
+ ),
234
+ fallback: true,
235
+ };
236
+ }
237
+ throw err;
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Find cards owned by a specific agent.
243
+ * @param {string} [agentId] — defaults to this.agentId
244
+ * @returns {Promise<{cards: Array}>}
245
+ */
246
+ async agentCards(agentId) {
247
+ const id = agentId || this.agentId;
248
+ if (!id) throw new Error("agentId required");
249
+ return this.search(`agent:${id}`);
250
+ }
251
+
252
+ /**
253
+ * Record a decision with full provenance.
254
+ * Creates a decision card + links to who decided and what it affects.
255
+ * @param {object} decision
256
+ * @param {string} decision.slug
257
+ * @param {string} decision.title
258
+ * @param {string} decision.body — rationale
259
+ * @param {string} [decision.decidedBy] — agent/person slug
260
+ * @param {string[]} [decision.affects] — slugs of affected cards
261
+ * @param {string[]} [decision.blocks] slugs of things this blocks
262
+ * @param {object} [decision.meta]
263
+ */
264
+ async decide(decision) {
265
+ const links = [];
266
+ if (decision.decidedBy) {
267
+ links.push({ target: decision.decidedBy, relation: "decided" });
268
+ }
269
+ if (decision.affects) {
270
+ for (const a of decision.affects) {
271
+ links.push({ target: a, relation: "triggers" });
272
+ }
273
+ }
274
+ if (decision.blocks) {
275
+ for (const b of decision.blocks) {
276
+ links.push({ target: b, relation: "blocks" });
277
+ }
278
+ }
279
+
280
+ return this.store({
281
+ slug: decision.slug,
282
+ title: decision.title,
283
+ body: decision.body,
284
+ cardType: "decision",
285
+ stack: "decisions",
286
+ links,
287
+ meta: { ...decision.meta, decidedAt: new Date().toISOString() },
288
+ keywords: decision.keywords || [],
289
+ });
290
+ }
291
+
292
+ /**
293
+ * Register an agent as a card in the graph.
294
+ * @param {object} agent
295
+ * @param {string} agent.id — unique agent ID
296
+ * @param {string} agent.name — display name
297
+ * @param {string} agent.role — what this agent does
298
+ * @param {string[]} [agent.owns] — slugs this agent owns
299
+ */
300
+ async registerAgent(agent) {
301
+ const links = [];
302
+ if (agent.owns) {
303
+ for (const o of agent.owns) {
304
+ links.push({ target: o, relation: "owns" });
305
+ }
306
+ }
307
+
308
+ return this.store({
309
+ slug: `agent-${agent.id}`,
310
+ title: `Agent: ${agent.name}`,
311
+ body: agent.role,
312
+ cardType: "person",
313
+ stack: "people",
314
+ links,
315
+ keywords: ["agent", agent.id, agent.name],
316
+ meta: { agentId: agent.id, registeredAt: new Date().toISOString() },
317
+ });
318
+ }
319
+ }
320
+
321
+ export { HyperStackClient };
322
+ export default HyperStackClient;