@hexis-ai/engram-sdk 0.1.2 → 0.1.4
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/dist/client.d.ts +29 -1
- package/dist/client.js +49 -12
- package/dist/extract.d.ts +1 -1
- package/dist/extract.js +105 -0
- package/dist/id-token.d.ts +29 -0
- package/dist/id-token.js +62 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +2 -2
package/dist/client.d.ts
CHANGED
|
@@ -16,6 +16,25 @@ export interface EngramOptions {
|
|
|
16
16
|
batchSize?: number;
|
|
17
17
|
/** Hook invoked on transport errors. Default: console.error. */
|
|
18
18
|
onError?: (err: unknown) => void;
|
|
19
|
+
/**
|
|
20
|
+
* Header name to carry the api key. Defaults to "Authorization" with a
|
|
21
|
+
* `Bearer <key>` value for backward compatibility. Set to "x-api-key"
|
|
22
|
+
* when the host adds its own `Authorization` (e.g. a Cloud Run ID token
|
|
23
|
+
* for service-to-service IAM).
|
|
24
|
+
*/
|
|
25
|
+
apiKeyHeader?: "authorization" | "x-api-key";
|
|
26
|
+
/**
|
|
27
|
+
* Async hook resolved per-request that returns headers to attach. Use
|
|
28
|
+
* for short-lived credentials like Cloud Run identity tokens.
|
|
29
|
+
*/
|
|
30
|
+
authHeaders?: () => Promise<Record<string, string>> | Record<string, string>;
|
|
31
|
+
/**
|
|
32
|
+
* Max retry attempts for a failed event batch (transient transport errors).
|
|
33
|
+
* Default 4 (~30s total with exponential backoff). 0 disables retries.
|
|
34
|
+
*/
|
|
35
|
+
maxRetries?: number;
|
|
36
|
+
/** Base backoff in ms; doubled each attempt. Default 500. */
|
|
37
|
+
retryBackoffMs?: number;
|
|
19
38
|
}
|
|
20
39
|
export interface RecordStepInput {
|
|
21
40
|
tool: string;
|
|
@@ -54,6 +73,10 @@ export declare class Engram {
|
|
|
54
73
|
private readonly flushIntervalMs;
|
|
55
74
|
private readonly batchSize;
|
|
56
75
|
private readonly onError;
|
|
76
|
+
private readonly apiKeyHeader;
|
|
77
|
+
private readonly authHeaders?;
|
|
78
|
+
readonly maxRetries: number;
|
|
79
|
+
readonly retryBackoffMs: number;
|
|
57
80
|
constructor(opts: EngramOptions);
|
|
58
81
|
/** Begin a new session. Returns a handle for buffering events. */
|
|
59
82
|
startSession(init?: SessionInit): Promise<EngramSession>;
|
|
@@ -90,7 +113,12 @@ export declare class EngramSession {
|
|
|
90
113
|
setTitle(title: string): void;
|
|
91
114
|
/** Mark the session ended and flush buffered events. */
|
|
92
115
|
end(): Promise<void>;
|
|
93
|
-
/**
|
|
116
|
+
/**
|
|
117
|
+
* Force a flush regardless of batch size / timer. Transient failures
|
|
118
|
+
* are retried with exponential backoff up to `maxRetries`. If every
|
|
119
|
+
* attempt fails the events are returned to the head of the buffer so
|
|
120
|
+
* the next flush picks them up.
|
|
121
|
+
*/
|
|
94
122
|
flush(): Promise<void>;
|
|
95
123
|
private enqueue;
|
|
96
124
|
}
|
package/dist/client.js
CHANGED
|
@@ -7,6 +7,10 @@ export class Engram {
|
|
|
7
7
|
flushIntervalMs;
|
|
8
8
|
batchSize;
|
|
9
9
|
onError;
|
|
10
|
+
apiKeyHeader;
|
|
11
|
+
authHeaders;
|
|
12
|
+
maxRetries;
|
|
13
|
+
retryBackoffMs;
|
|
10
14
|
constructor(opts) {
|
|
11
15
|
if (!opts.apiKey)
|
|
12
16
|
throw new Error("Engram: apiKey is required");
|
|
@@ -18,6 +22,10 @@ export class Engram {
|
|
|
18
22
|
this.flushIntervalMs = opts.flushIntervalMs ?? 2000;
|
|
19
23
|
this.batchSize = opts.batchSize ?? 32;
|
|
20
24
|
this.onError = opts.onError ?? ((e) => console.error("[engram]", e));
|
|
25
|
+
this.apiKeyHeader = opts.apiKeyHeader ?? "authorization";
|
|
26
|
+
this.authHeaders = opts.authHeaders;
|
|
27
|
+
this.maxRetries = opts.maxRetries ?? 4;
|
|
28
|
+
this.retryBackoffMs = opts.retryBackoffMs ?? 500;
|
|
21
29
|
}
|
|
22
30
|
/** Begin a new session. Returns a handle for buffering events. */
|
|
23
31
|
async startSession(init = {}) {
|
|
@@ -53,12 +61,18 @@ export class Engram {
|
|
|
53
61
|
return { flushIntervalMs: this.flushIntervalMs, batchSize: this.batchSize, onError: this.onError };
|
|
54
62
|
}
|
|
55
63
|
async request(method, path, body) {
|
|
64
|
+
const headers = { "content-type": "application/json" };
|
|
65
|
+
if (this.apiKeyHeader === "authorization") {
|
|
66
|
+
headers["authorization"] = `Bearer ${this.apiKey}`;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
headers["x-api-key"] = this.apiKey;
|
|
70
|
+
}
|
|
71
|
+
if (this.authHeaders)
|
|
72
|
+
Object.assign(headers, await this.authHeaders());
|
|
56
73
|
const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
57
74
|
method,
|
|
58
|
-
headers
|
|
59
|
-
"content-type": "application/json",
|
|
60
|
-
authorization: `Bearer ${this.apiKey}`,
|
|
61
|
-
},
|
|
75
|
+
headers,
|
|
62
76
|
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
63
77
|
});
|
|
64
78
|
if (!res.ok) {
|
|
@@ -128,7 +142,12 @@ export class EngramSession {
|
|
|
128
142
|
});
|
|
129
143
|
await this.flush();
|
|
130
144
|
}
|
|
131
|
-
/**
|
|
145
|
+
/**
|
|
146
|
+
* Force a flush regardless of batch size / timer. Transient failures
|
|
147
|
+
* are retried with exponential backoff up to `maxRetries`. If every
|
|
148
|
+
* attempt fails the events are returned to the head of the buffer so
|
|
149
|
+
* the next flush picks them up.
|
|
150
|
+
*/
|
|
132
151
|
async flush() {
|
|
133
152
|
if (this.flushTimer) {
|
|
134
153
|
clearTimeout(this.flushTimer);
|
|
@@ -139,14 +158,32 @@ export class EngramSession {
|
|
|
139
158
|
if (this.buffer.length === 0)
|
|
140
159
|
return;
|
|
141
160
|
const events = this.buffer.splice(0);
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
.
|
|
145
|
-
this.engram.config.onError
|
|
161
|
+
const { maxRetries, retryBackoffMs, onError } = {
|
|
162
|
+
maxRetries: this.engram.maxRetries,
|
|
163
|
+
retryBackoffMs: this.engram.retryBackoffMs,
|
|
164
|
+
onError: this.engram.config.onError,
|
|
165
|
+
};
|
|
166
|
+
this.inFlight = (async () => {
|
|
167
|
+
let lastErr;
|
|
168
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
169
|
+
try {
|
|
170
|
+
await this.engram.sendBatch(this.id, { events });
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
lastErr = e;
|
|
175
|
+
onError(e);
|
|
176
|
+
if (attempt < maxRetries) {
|
|
177
|
+
const delay = retryBackoffMs * 2 ** attempt;
|
|
178
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// All retries exhausted — return events to the buffer head so a
|
|
183
|
+
// future flush (or session.end()) can take another swing.
|
|
146
184
|
this.buffer.unshift(...events);
|
|
147
|
-
throw
|
|
148
|
-
})
|
|
149
|
-
.finally(() => {
|
|
185
|
+
throw lastErr;
|
|
186
|
+
})().finally(() => {
|
|
150
187
|
this.inFlight = null;
|
|
151
188
|
});
|
|
152
189
|
await this.inFlight;
|
package/dist/extract.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Resource id format follows engram convention: `${service}:${id-or-url}`.
|
|
10
10
|
*/
|
|
11
|
-
export type ReferenceService = "linear" | "notion" | "web" | "github";
|
|
11
|
+
export type ReferenceService = "linear" | "notion" | "web" | "github" | "slack" | "gmail";
|
|
12
12
|
export type ReferenceAction = "read" | "write";
|
|
13
13
|
export interface RefCandidate {
|
|
14
14
|
service: ReferenceService;
|
package/dist/extract.js
CHANGED
|
@@ -10,13 +10,32 @@
|
|
|
10
10
|
*/
|
|
11
11
|
const isWebSearch = (n) => n === "WebSearch" || n === "web_search";
|
|
12
12
|
const isWebFetch = (n) => n === "WebFetch" || n === "web_fetch";
|
|
13
|
+
/**
|
|
14
|
+
* Tool names whose effect is a write (mutation). Defaults to read.
|
|
15
|
+
* Same map for all vendors — names are namespaced enough.
|
|
16
|
+
*/
|
|
13
17
|
const WRITE_TOOLS = {
|
|
18
|
+
// linear (native + mcp)
|
|
14
19
|
create_issue: "write",
|
|
15
20
|
update_issue: "write",
|
|
21
|
+
// notion mcp
|
|
16
22
|
"notion-create-pages": "write",
|
|
17
23
|
"notion-update-page": "write",
|
|
18
24
|
"notion-create-database": "write",
|
|
19
25
|
"notion-create-comment": "write",
|
|
26
|
+
// slack mcp
|
|
27
|
+
slack_post_message: "write",
|
|
28
|
+
slack_reply_to_thread: "write",
|
|
29
|
+
slack_add_reaction: "write",
|
|
30
|
+
// gmail mcp
|
|
31
|
+
gmail_send: "write",
|
|
32
|
+
gmail_send_message: "write",
|
|
33
|
+
gmail_draft_message: "write",
|
|
34
|
+
// github mcp
|
|
35
|
+
github_create_issue: "write",
|
|
36
|
+
github_create_pull_request: "write",
|
|
37
|
+
github_create_comment: "write",
|
|
38
|
+
github_add_pr_review_comment: "write",
|
|
20
39
|
};
|
|
21
40
|
function getAction(toolName) {
|
|
22
41
|
return WRITE_TOOLS[toolName] ?? "read";
|
|
@@ -51,6 +70,12 @@ export function extractReferences(vendor, toolName, input, result) {
|
|
|
51
70
|
return extractLinearRefs(toolName, input, result);
|
|
52
71
|
if (vendor === "notion")
|
|
53
72
|
return extractNotionRefs(toolName, input, result);
|
|
73
|
+
if (vendor === "slack")
|
|
74
|
+
return extractSlackRefs(toolName, input, result);
|
|
75
|
+
if (vendor === "gmail")
|
|
76
|
+
return extractGmailRefs(toolName, input, result);
|
|
77
|
+
if (vendor === "github")
|
|
78
|
+
return extractGithubRefs(toolName, input, result);
|
|
54
79
|
return [];
|
|
55
80
|
}
|
|
56
81
|
/** Convenience: encode RefCandidate to engram resource id (`service:resource_id`). */
|
|
@@ -135,3 +160,83 @@ function extractWebRefs(toolName, input) {
|
|
|
135
160
|
}
|
|
136
161
|
return [];
|
|
137
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Slack MCP: input names follow `channel_id` / `channel` / `user_id` / `thread_ts`.
|
|
165
|
+
* Resource id encodes "C123" for channels, "U123" for users, "C123:1234.567"
|
|
166
|
+
* for thread anchors. Read-style tools (list_*, get_history, ...) produce
|
|
167
|
+
* a channel resource when one is named; post / reply produce write resources.
|
|
168
|
+
*/
|
|
169
|
+
function extractSlackRefs(toolName, input, _result) {
|
|
170
|
+
const action = getAction(toolName);
|
|
171
|
+
const out = [];
|
|
172
|
+
const channel = (input.channel_id ?? input.channel);
|
|
173
|
+
const thread = (input.thread_ts ?? input.ts);
|
|
174
|
+
const user = (input.user_id ?? input.user);
|
|
175
|
+
if (channel && thread) {
|
|
176
|
+
out.push({ service: "slack", action, resource_id: `${channel}:${thread}` });
|
|
177
|
+
}
|
|
178
|
+
else if (channel) {
|
|
179
|
+
out.push({ service: "slack", action, resource_id: channel });
|
|
180
|
+
}
|
|
181
|
+
if (user) {
|
|
182
|
+
out.push({ service: "slack", action: "read", resource_id: user });
|
|
183
|
+
}
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Gmail MCP: `messageId`, `threadId`, `query` for search. Threads are the
|
|
188
|
+
* stable identity in Gmail; we prefer thread id when present, fall back to
|
|
189
|
+
* message id. Search produces `query:<q>` so two searches for the same
|
|
190
|
+
* topic dedupe.
|
|
191
|
+
*/
|
|
192
|
+
function extractGmailRefs(toolName, input, _result) {
|
|
193
|
+
const action = getAction(toolName);
|
|
194
|
+
const out = [];
|
|
195
|
+
const threadId = (input.threadId ?? input.thread_id);
|
|
196
|
+
const messageId = (input.messageId ?? input.message_id);
|
|
197
|
+
const query = input.query;
|
|
198
|
+
const to = input.to;
|
|
199
|
+
if (threadId) {
|
|
200
|
+
out.push({ service: "gmail", action, resource_id: `thread:${threadId}` });
|
|
201
|
+
}
|
|
202
|
+
else if (messageId) {
|
|
203
|
+
out.push({ service: "gmail", action, resource_id: `msg:${messageId}` });
|
|
204
|
+
}
|
|
205
|
+
if (action === "read" && query) {
|
|
206
|
+
out.push({ service: "gmail", action: "read", resource_id: `query:${query}` });
|
|
207
|
+
}
|
|
208
|
+
if (action === "write" && to) {
|
|
209
|
+
const recipients = Array.isArray(to) ? to : [to];
|
|
210
|
+
for (const r of recipients) {
|
|
211
|
+
out.push({ service: "gmail", action: "write", resource_id: `to:${r}` });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return out;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* GitHub MCP: `owner`, `repo`, `pull_number` / `issue_number` / `path`.
|
|
218
|
+
* PRs and issues are global identifiers within a repo so we encode as
|
|
219
|
+
* `<owner>/<repo>#<n>`. File paths become `<owner>/<repo>:<path>`.
|
|
220
|
+
*/
|
|
221
|
+
function extractGithubRefs(toolName, input, _result) {
|
|
222
|
+
const action = getAction(toolName);
|
|
223
|
+
const owner = input.owner;
|
|
224
|
+
const repo = input.repo;
|
|
225
|
+
if (!owner || !repo)
|
|
226
|
+
return [];
|
|
227
|
+
const base = `${owner}/${repo}`;
|
|
228
|
+
const pullNumber = input.pull_number ?? input.pullNumber;
|
|
229
|
+
if (pullNumber != null) {
|
|
230
|
+
return [{ service: "github", action, resource_id: `${base}#${pullNumber}` }];
|
|
231
|
+
}
|
|
232
|
+
const issueNumber = input.issue_number ?? input.issueNumber;
|
|
233
|
+
if (issueNumber != null) {
|
|
234
|
+
return [{ service: "github", action, resource_id: `${base}#${issueNumber}` }];
|
|
235
|
+
}
|
|
236
|
+
const path = input.path;
|
|
237
|
+
if (path) {
|
|
238
|
+
return [{ service: "github", action, resource_id: `${base}:${path}` }];
|
|
239
|
+
}
|
|
240
|
+
// Just naming the repo (list_prs etc.) — record the repo itself.
|
|
241
|
+
return [{ service: "github", action, resource_id: base }];
|
|
242
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity-token helper for Cloud Run service-to-service auth.
|
|
3
|
+
*
|
|
4
|
+
* On Cloud Run / Cloud Functions / GCE / GKE the metadata server hands out
|
|
5
|
+
* a Google-signed ID token scoped to a single audience (typically the URL
|
|
6
|
+
* of the target service). Callers can attach this token in `Authorization:
|
|
7
|
+
* Bearer <id-token>` so the target's IAM check (`roles/run.invoker`)
|
|
8
|
+
* passes for the calling service account.
|
|
9
|
+
*
|
|
10
|
+
* Tokens are cached per audience until ~30s before expiry. The metadata
|
|
11
|
+
* server itself is rate-limited, so this matters under load.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Fetch an identity token for `audience` from the GCE/Cloud Run metadata
|
|
15
|
+
* server. Throws if not on a GCP environment with a metadata endpoint.
|
|
16
|
+
* Result is cached until 30s before its embedded `exp` claim.
|
|
17
|
+
*/
|
|
18
|
+
export declare function fetchIdToken(audience: string): Promise<string>;
|
|
19
|
+
/**
|
|
20
|
+
* Convenience: build an `authHeaders` function suitable for `EngramOptions`.
|
|
21
|
+
*
|
|
22
|
+
* const engram = new Engram({
|
|
23
|
+
* apiKey: process.env.ENGRAM_API_KEY!,
|
|
24
|
+
* baseUrl: process.env.ENGRAM_BASE_URL!,
|
|
25
|
+
* apiKeyHeader: "x-api-key",
|
|
26
|
+
* authHeaders: cloudRunIdTokenAuth(process.env.ENGRAM_BASE_URL!),
|
|
27
|
+
* });
|
|
28
|
+
*/
|
|
29
|
+
export declare function cloudRunIdTokenAuth(audience: string): () => Promise<Record<string, string>>;
|
package/dist/id-token.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity-token helper for Cloud Run service-to-service auth.
|
|
3
|
+
*
|
|
4
|
+
* On Cloud Run / Cloud Functions / GCE / GKE the metadata server hands out
|
|
5
|
+
* a Google-signed ID token scoped to a single audience (typically the URL
|
|
6
|
+
* of the target service). Callers can attach this token in `Authorization:
|
|
7
|
+
* Bearer <id-token>` so the target's IAM check (`roles/run.invoker`)
|
|
8
|
+
* passes for the calling service account.
|
|
9
|
+
*
|
|
10
|
+
* Tokens are cached per audience until ~30s before expiry. The metadata
|
|
11
|
+
* server itself is rate-limited, so this matters under load.
|
|
12
|
+
*/
|
|
13
|
+
const METADATA_URL = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity";
|
|
14
|
+
const cache = new Map();
|
|
15
|
+
const SAFETY_WINDOW_MS = 30_000;
|
|
16
|
+
function decodeExp(token) {
|
|
17
|
+
const parts = token.split(".");
|
|
18
|
+
if (parts.length !== 3)
|
|
19
|
+
return null;
|
|
20
|
+
try {
|
|
21
|
+
const payload = JSON.parse(atob(parts[1]));
|
|
22
|
+
return typeof payload.exp === "number" ? payload.exp * 1000 : null;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Fetch an identity token for `audience` from the GCE/Cloud Run metadata
|
|
30
|
+
* server. Throws if not on a GCP environment with a metadata endpoint.
|
|
31
|
+
* Result is cached until 30s before its embedded `exp` claim.
|
|
32
|
+
*/
|
|
33
|
+
export async function fetchIdToken(audience) {
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
const cached = cache.get(audience);
|
|
36
|
+
if (cached && now < cached.expiresAt - SAFETY_WINDOW_MS)
|
|
37
|
+
return cached.token;
|
|
38
|
+
const url = `${METADATA_URL}?audience=${encodeURIComponent(audience)}`;
|
|
39
|
+
const res = await fetch(url, { headers: { "metadata-flavor": "Google" } });
|
|
40
|
+
if (!res.ok) {
|
|
41
|
+
throw new Error(`fetchIdToken ${res.status}: ${await res.text().catch(() => "")}`);
|
|
42
|
+
}
|
|
43
|
+
const token = (await res.text()).trim();
|
|
44
|
+
const exp = decodeExp(token);
|
|
45
|
+
cache.set(audience, { token, expiresAt: exp ?? now + 60 * 60 * 1000 });
|
|
46
|
+
return token;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Convenience: build an `authHeaders` function suitable for `EngramOptions`.
|
|
50
|
+
*
|
|
51
|
+
* const engram = new Engram({
|
|
52
|
+
* apiKey: process.env.ENGRAM_API_KEY!,
|
|
53
|
+
* baseUrl: process.env.ENGRAM_BASE_URL!,
|
|
54
|
+
* apiKeyHeader: "x-api-key",
|
|
55
|
+
* authHeaders: cloudRunIdTokenAuth(process.env.ENGRAM_BASE_URL!),
|
|
56
|
+
* });
|
|
57
|
+
*/
|
|
58
|
+
export function cloudRunIdTokenAuth(audience) {
|
|
59
|
+
return async () => ({
|
|
60
|
+
authorization: `Bearer ${await fetchIdToken(audience)}`,
|
|
61
|
+
});
|
|
62
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { Engram, EngramSession, type EngramOptions, type RecordStepInput, type SearchRequest, type SearchResponse, } from "./client";
|
|
2
2
|
export { extractReferences, encodeResourceId, type RefCandidate, type ReferenceService, type ReferenceAction, } from "./extract";
|
|
3
3
|
export { parseToolName, type ParsedToolName } from "./tool-name";
|
|
4
|
+
export { fetchIdToken, cloudRunIdTokenAuth } from "./id-token";
|
|
4
5
|
export type { SessionInit, SessionAck, SessionEvent, StepEvent, ParticipantEvent, TitleEvent, EndEvent, EventBatch, } from "./types";
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hexis-ai/engram-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Host SDK for engram. Records agent session steps and ships them to an engram server.",
|
|
5
5
|
"keywords": ["engram", "agents", "claude", "anthropic", "sdk", "observability"],
|
|
6
6
|
"homepage": "https://github.com/hexis-ltd/engram#readme",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"type-check": "tsc --noEmit"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@hexis-ai/engram-core": "^0.1.
|
|
43
|
+
"@hexis-ai/engram-core": "^0.1.4"
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|