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/src/client.js CHANGED
@@ -1,320 +1,320 @@
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
- * Auto-extract cards from raw text.
134
- * Finds preferences, people, decisions, tech stack mentions using regex.
135
- * Zero LLM cost. Great for onboarding.
136
- * @param {string} textraw conversation or project description
137
- * @returns {Promise<{cards: Array, count: number}>}
138
- */
139
- async ingest(text) {
140
- return this._request("POST", `/api/ingest?workspace=${this.workspace}`, { text });
141
- }
142
-
143
- // ─── Graph ───────────────────────────────────────────
144
-
145
- /**
146
- * Traverse the knowledge graph from a starting card.
147
- * @param {string} from — starting card slug
148
- * @param {object} [opts]
149
- * @param {number} [opts.depth=1] — hops to traverse (1-3)
150
- * @param {string} [opts.relation] — filter by relation type
151
- * @param {string} [opts.type] filter by card type
152
- * @param {string} [opts.at] ISO timestamp for time-travel
153
- * @returns {Promise<{nodes: Array, edges: Array}>}
154
- */
155
- async graph(from, opts = {}) {
156
- let url = `/api/graph?workspace=${this.workspace}&from=${from}`;
157
- if (opts.depth) url += `&depth=${opts.depth}`;
158
- if (opts.relation) url += `&relation=${opts.relation}`;
159
- if (opts.type) url += `&type=${opts.type}`;
160
- if (opts.at) url += `&at=${encodeURIComponent(opts.at)}`;
161
- return this._request("GET", url);
162
- }
163
-
164
- // ─── Multi-Agent Helpers ──────────────────────────────
165
-
166
- /**
167
- * Query cards that block a specific card/task.
168
- * Shorthand for graph traversal with relation="blocks".
169
- * @param {string} slug card being blocked
170
- * @returns {Promise<{blockers: Array}>}
171
- */
172
- async blockers(slug) {
173
- try {
174
- const result = await this.graph(slug, { depth: 2, relation: "blocks" });
175
- const blockers = (result.edges || [])
176
- .filter(e => e.relation === "blocks" && e.to === slug)
177
- .map(e => {
178
- const node = (result.nodes || []).find(n => n.slug === e.from);
179
- return node || { slug: e.from };
180
- });
181
- return { blockers, graph: result };
182
- } catch (err) {
183
- // If graph API not available (free tier), fallback to search
184
- if (err.status === 403) {
185
- const searchResult = await this.search(`blocks ${slug}`);
186
- return {
187
- blockers: (searchResult.results || []).filter(c =>
188
- c.links?.some(l => l.relation === "blocks" && l.target === slug)
189
- ),
190
- fallback: true,
191
- };
192
- }
193
- throw err;
194
- }
195
- }
196
-
197
- /**
198
- * Find cards owned by a specific agent.
199
- * @param {string} [agentId] — defaults to this.agentId
200
- * @returns {Promise<{cards: Array}>}
201
- */
202
- async agentCards(agentId) {
203
- const id = agentId || this.agentId;
204
- if (!id) throw new Error("agentId required");
205
- return this.search(`agent:${id}`);
206
- }
207
-
208
- /**
209
- * Record a decision with full provenance.
210
- * Creates a decision card + links to who decided and what it affects.
211
- * @param {object} decision
212
- * @param {string} decision.slug
213
- * @param {string} decision.title
214
- * @param {string} decision.body — rationale
215
- * @param {string} [decision.decidedBy] — agent/person slug
216
- * @param {string[]} [decision.affects] slugs of affected cards
217
- * @param {string[]} [decision.blocks] slugs of things this blocks
218
- * @param {object} [decision.meta]
219
- */
220
- async decide(decision) {
221
- const links = [];
222
- if (decision.decidedBy) {
223
- links.push({ target: decision.decidedBy, relation: "decided" });
224
- }
225
- if (decision.affects) {
226
- for (const a of decision.affects) {
227
- links.push({ target: a, relation: "triggers" });
228
- }
229
- }
230
- if (decision.blocks) {
231
- for (const b of decision.blocks) {
232
- links.push({ target: b, relation: "blocks" });
233
- }
234
- }
235
-
236
- return this.store({
237
- slug: decision.slug,
238
- title: decision.title,
239
- body: decision.body,
240
- cardType: "decision",
241
- stack: "decisions",
242
- links,
243
- meta: { ...decision.meta, decidedAt: new Date().toISOString() },
244
- keywords: decision.keywords || [],
245
- });
246
- }
247
-
248
- /**
249
- * Register an agent as a card in the graph.
250
- * @param {object} agent
251
- * @param {string} agent.id unique agent ID
252
- * @param {string} agent.name display name
253
- * @param {string} agent.role — what this agent does
254
- * @param {string[]} [agent.owns] — slugs this agent owns
255
- */
256
- /**
257
- * Check for cards directed at this agent by other agents.
258
- * Enables cross-agent coordination via shared memory.
259
- * @param {object} [opts]
260
- * @param {string} [opts.since] — ISO timestamp, only cards after this time
261
- * @returns {Promise<{cards: Array}>}
262
- */
263
- async inbox(opts = {}) {
264
- const agentId = this.agentId;
265
- if (!agentId) throw new Error("agentId required for inbox");
266
- let url = `/api/cards?workspace=${this.workspace}&targetAgent=${agentId}`;
267
- if (opts.since) url += `&since=${encodeURIComponent(opts.since)}`;
268
- return this._request("GET", url);
269
- }
270
-
271
- /**
272
- * Register a webhook to receive real-time notifications.
273
- * Agent gets notified when cards are directed at it. Requires Team plan.
274
- * @param {object} webhook
275
- * @param {string} webhook.url — URL to receive POST events
276
- * @param {string[]} [webhook.events] — events to listen for (default: all)
277
- * @param {string} [webhook.secret] — HMAC secret for signature verification
278
- * @returns {Promise<{id: string}>}
279
- */
280
- async registerWebhook(webhook) {
281
- return this._request("POST", `/api/agent-webhooks`, {
282
- agentId: this.agentId,
283
- url: webhook.url,
284
- events: webhook.events || ["*"],
285
- workspace: this.workspace,
286
- secret: webhook.secret || undefined,
287
- });
288
- }
289
-
290
- /**
291
- * List all registered webhooks for this account.
292
- * @returns {Promise<{webhooks: Array}>}
293
- */
294
- async listWebhooks() {
295
- return this._request("GET", `/api/agent-webhooks`);
296
- }
297
-
298
- async registerAgent(agent) {
299
- const links = [];
300
- if (agent.owns) {
301
- for (const o of agent.owns) {
302
- links.push({ target: o, relation: "owns" });
303
- }
304
- }
305
-
306
- return this.store({
307
- slug: `agent-${agent.id}`,
308
- title: `Agent: ${agent.name}`,
309
- body: agent.role,
310
- cardType: "person",
311
- stack: "people",
312
- links,
313
- keywords: ["agent", agent.id, agent.name],
314
- meta: { agentId: agent.id, registeredAt: new Date().toISOString() },
315
- });
316
- }
317
- }
318
-
319
- export { HyperStackClient };
320
- 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.at] ISO timestamp for time-travel
169
+ * @returns {Promise<{nodes: Array, edges: Array}>}
170
+ */
171
+ async graph(from, opts = {}) {
172
+ let url = `/api/graph?workspace=${this.workspace}&from=${from}`;
173
+ if (opts.depth) url += `&depth=${opts.depth}`;
174
+ if (opts.relation) url += `&relation=${opts.relation}`;
175
+ if (opts.type) url += `&type=${opts.type}`;
176
+ if (opts.at) url += `&at=${encodeURIComponent(opts.at)}`;
177
+ return this._request("GET", url);
178
+ }
179
+
180
+ /**
181
+ * Deterministic impact analysis reverse traversal.
182
+ * Answers: "what depends on X?" / "what would break if X changed?"
183
+ *
184
+ * @param {string} slug — the card to analyse
185
+ * @param {object} [opts]
186
+ * @param {number} [opts.depth=2] — hops to traverse upstream (1-3)
187
+ * @param {string} [opts.relation]filter by relation type (e.g. "blocks", "depends_on")
188
+ * @returns {Promise<{root: string, mode: "impact", nodes: Array, edges: Array}>}
189
+ *
190
+ * @example
191
+ * // What depends on the billing API?
192
+ * const result = await hs.impact("billing-api", { depth: 2 });
193
+ * // Returns all cards that link TO billing-api, up to 2 hops upstream
194
+ *
195
+ * @example
196
+ * // What specifically blocks the deploy?
197
+ * const result = await hs.impact("prod-deploy", { relation: "blocks" });
198
+ */
199
+ async impact(slug, opts = {}) {
200
+ const { depth = 2, relation } = opts;
201
+ let url = `/api/graph?workspace=${this.workspace}&from=${slug}&mode=impact&depth=${depth}`;
202
+ if (relation) url += `&relation=${encodeURIComponent(relation)}`;
203
+ return this._request("GET", url);
204
+ }
205
+
206
+ // ─── Multi-Agent Helpers ──────────────────────────────
207
+
208
+ /**
209
+ * Query cards that block a specific card/task.
210
+ * Shorthand for graph traversal with relation="blocks".
211
+ * @param {string} slug — card being blocked
212
+ * @returns {Promise<{blockers: Array}>}
213
+ */
214
+ async blockers(slug) {
215
+ try {
216
+ const result = await this.graph(slug, { depth: 2, relation: "blocks" });
217
+ const blockers = (result.edges || [])
218
+ .filter(e => e.relation === "blocks" && e.to === slug)
219
+ .map(e => {
220
+ const node = (result.nodes || []).find(n => n.slug === e.from);
221
+ return node || { slug: e.from };
222
+ });
223
+ return { blockers, graph: result };
224
+ } catch (err) {
225
+ // If graph API not available (free tier), fallback to search
226
+ if (err.status === 403) {
227
+ const searchResult = await this.search(`blocks ${slug}`);
228
+ return {
229
+ blockers: (searchResult.results || []).filter(c =>
230
+ c.links?.some(l => l.relation === "blocks" && l.target === slug)
231
+ ),
232
+ fallback: true,
233
+ };
234
+ }
235
+ throw err;
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Find cards owned by a specific agent.
241
+ * @param {string} [agentId] — defaults to this.agentId
242
+ * @returns {Promise<{cards: Array}>}
243
+ */
244
+ async agentCards(agentId) {
245
+ const id = agentId || this.agentId;
246
+ if (!id) throw new Error("agentId required");
247
+ return this.search(`agent:${id}`);
248
+ }
249
+
250
+ /**
251
+ * Record a decision with full provenance.
252
+ * Creates a decision card + links to who decided and what it affects.
253
+ * @param {object} decision
254
+ * @param {string} decision.slug
255
+ * @param {string} decision.title
256
+ * @param {string} decision.body — rationale
257
+ * @param {string} [decision.decidedBy] agent/person slug
258
+ * @param {string[]} [decision.affects] slugs of affected cards
259
+ * @param {string[]} [decision.blocks] — slugs of things this blocks
260
+ * @param {object} [decision.meta]
261
+ */
262
+ async decide(decision) {
263
+ const links = [];
264
+ if (decision.decidedBy) {
265
+ links.push({ target: decision.decidedBy, relation: "decided" });
266
+ }
267
+ if (decision.affects) {
268
+ for (const a of decision.affects) {
269
+ links.push({ target: a, relation: "triggers" });
270
+ }
271
+ }
272
+ if (decision.blocks) {
273
+ for (const b of decision.blocks) {
274
+ links.push({ target: b, relation: "blocks" });
275
+ }
276
+ }
277
+
278
+ return this.store({
279
+ slug: decision.slug,
280
+ title: decision.title,
281
+ body: decision.body,
282
+ cardType: "decision",
283
+ stack: "decisions",
284
+ links,
285
+ meta: { ...decision.meta, decidedAt: new Date().toISOString() },
286
+ keywords: decision.keywords || [],
287
+ });
288
+ }
289
+
290
+ /**
291
+ * Register an agent as a card in the graph.
292
+ * @param {object} agent
293
+ * @param {string} agent.id — unique agent ID
294
+ * @param {string} agent.name — display name
295
+ * @param {string} agent.role — what this agent does
296
+ * @param {string[]} [agent.owns] — slugs this agent owns
297
+ */
298
+ async registerAgent(agent) {
299
+ const links = [];
300
+ if (agent.owns) {
301
+ for (const o of agent.owns) {
302
+ links.push({ target: o, relation: "owns" });
303
+ }
304
+ }
305
+
306
+ return this.store({
307
+ slug: `agent-${agent.id}`,
308
+ title: `Agent: ${agent.name}`,
309
+ body: agent.role,
310
+ cardType: "person",
311
+ stack: "people",
312
+ links,
313
+ keywords: ["agent", agent.id, agent.name],
314
+ meta: { agentId: agent.id, registeredAt: new Date().toISOString() },
315
+ });
316
+ }
317
+ }
318
+
319
+ export { HyperStackClient };
320
+ export default HyperStackClient;