plugmem 0.3.0 → 0.4.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.
Files changed (4) hide show
  1. package/README.md +87 -16
  2. package/index.d.ts +718 -0
  3. package/index.js +323 -0
  4. package/package.json +7 -7
package/README.md CHANGED
@@ -44,14 +44,16 @@ the CLI.**
44
44
  ## Usage (TypeScript)
45
45
 
46
46
  Every argument and result is typed — napi generates `index.d.ts`, so a TS host
47
- gets full autocomplete and checking:
47
+ gets full autocomplete and checking. A memory is opened with the static
48
+ `Plugmem.open`, not `new`: opening replays a journal and maps a snapshot, and a
49
+ JavaScript constructor has no way to hand that to a worker thread.
48
50
 
49
51
  ```typescript
50
52
  import { Plugmem } from "plugmem";
51
53
 
52
- const db = new Plugmem("agent.plugmem"); // or { readOnly: true }
54
+ const db = await Plugmem.open("agent.plugmem"); // or { readOnly: true }
53
55
 
54
- const out = db.remember({
56
+ const out = await db.remember({
55
57
  text: "prefers tokio",
56
58
  entity: "user",
57
59
  tags: ["pref"],
@@ -61,15 +63,15 @@ const out = db.remember({
61
63
  out.id; // number
62
64
  out.similar; // Similar[] — the engine surfaces conflicts, you decide
63
65
 
64
- const res = db.recall({ query: "runtime?", k: 5 });
66
+ const res = await db.recall({ query: "runtime?", k: 5 });
65
67
  res.rendered; // the prompt-ready block
66
68
  res.facts; // RecalledFact[] — { id, score, entity, recordedAt, … }
67
69
 
68
70
  const card = db.get(out.id);
69
71
  card.metadata; // Record<string,string> — keys sorted, {} when none
70
72
 
71
- db.revise(out.id, { text: "prefers async-std" });
72
- db.link({ src: "user", rel: "works_at", dst: "acme" });
73
+ await db.revise(out.id, { text: "prefers async-std" });
74
+ await db.link({ src: "user", rel: "works_at", dst: "acme" });
73
75
  db.unlink({ src: "user", rel: "works_at", dst: "acme" }); // closes the current edge
74
76
 
75
77
  await db.checkpoint(); // async (see below)
@@ -86,7 +88,7 @@ data directory. See the [full settings reference](https://github.com/m62624/plug
86
88
  for all fields and OS-specific paths.
87
89
 
88
90
  ```typescript
89
- const db = new Plugmem(undefined, { config: "./plugmem.toml" });
91
+ const db = await Plugmem.open(undefined, { config: "./plugmem.toml" });
90
92
  ```
91
93
 
92
94
  ```toml
@@ -108,7 +110,10 @@ provider's HTTP call runs outside the engine lock. Without one, there is no
108
110
  embedder and vector recall is skipped (lexical, tag, graph and time recall still
109
111
  answer). The optional `dim` open option sets the embedding size when there is no
110
112
  config; if the config configured an embedder, its dimension governs and `dim`
111
- must agree. A `{ readOnly: true }` handle never auto-embeds pass a vector.
113
+ must agree. A `{ readOnly: true }` handle cannot auto-embed inside the engine
114
+ embedding into a zero-copy mapping is what read-only exists to avoid — so this
115
+ binding embeds the query itself before the read, exactly as the CLI and the MCP
116
+ server do. A text `recall` therefore reaches the vector source in both modes.
112
117
 
113
118
  ## The verbs
114
119
 
@@ -122,6 +127,37 @@ engine logic is entirely the host's.
122
127
  `recall`, `get`, `tagsOf`, `stats`, `export`, `exportPage(cursor?)`, `verify`,
123
128
  plus `generation()` (the pinned snapshot generation) and `refresh()` (advance
124
129
  to the writer's latest checkpoint); the write verbs throw.
130
+ **Both**: `path()` — the file the handle resolved to, which is the only way to
131
+ learn it when the constructor was given no path.
132
+
133
+ ### Errors
134
+
135
+ Every failure plugmem itself decides carries a stable `code`, so a program
136
+ branches on it instead of on wording:
137
+
138
+ ```js
139
+ try {
140
+ db = await Plugmem.open("agent.plugmem");
141
+ } catch (err) {
142
+ if (err.code === "PLUGMEM_LOCKED") retryLater();
143
+ else throw err;
144
+ }
145
+ ```
146
+
147
+ `PLUGMEM_LOCKED`, `PLUGMEM_NEEDS_CHECKPOINT`, `PLUGMEM_CONFIG` and
148
+ `PLUGMEM_OPEN` come from opening; `PLUGMEM_INVALID_ARG` and
149
+ `PLUGMEM_INVALID_NAME` from an argument that was refused; `PLUGMEM_CLOSED`,
150
+ `PLUGMEM_READ_ONLY`, `PLUGMEM_WRITER_ONLY` and `PLUGMEM_BUSY` from calling a
151
+ verb the handle cannot serve; `PLUGMEM_ENGINE` from the engine itself, carrying
152
+ the host's own message.
153
+
154
+ The code is there whether the verb threw or the promise rejected — the two are
155
+ the same contract, so nothing has to be handled twice.
156
+
157
+ An argument that shapes an answer is refused rather than dropped: `range` must
158
+ be exactly `[from, to]`, and `range`, `asOf` and `validFrom` must each be a
159
+ finite, non-negative instant. Silently ignoring one produced an answer computed
160
+ without it — indistinguishable from a correct one.
125
161
 
126
162
  ### What the host has and this does not
127
163
 
@@ -141,7 +177,7 @@ when it already owns the input records.
141
177
 
142
178
  ## Many memories in one directory (optional)
143
179
 
144
- **Default: one memory, one file.** `new Plugmem(path)` and nothing here applies.
180
+ **Default: one memory, one file.** `Plugmem.open(path)` and nothing here applies.
145
181
 
146
182
  A process that serves many independent memories — one per conversation, per
147
183
  tenant, per project — can point at a directory and address them by name:
@@ -153,12 +189,12 @@ const ws = new Workspace("/srv/memories", { maxOpen: 16, idleTimeoutMs: 60_000 }
153
189
 
154
190
  // `open` hands back the same `Plugmem` class, so a named memory has exactly the
155
191
  // verbs a path-opened one has. A first write to an unused name creates it.
156
- ws.open("chat-42").remember({ text: "prefers tokio" });
192
+ await (await ws.open("chat-42")).remember({ text: "prefers tokio" });
157
193
 
158
194
  // Do not know the name? Ask what each memory is for. Owners are searchable too,
159
195
  // even though an owner is a graph edge rather than text.
160
- ws.describe("chat-42", { description: "release planning", owner: "ann" });
161
- const hits: DbEntry[] = ws.find("release planning"); // → [{ db: "chat-42", … }]
196
+ await ws.describe("chat-42", { description: "release planning", owner: "ann" });
197
+ const hits: DbEntry[] = await ws.find("release planning"); // → [{ db: "chat-42", … }]
162
198
  ```
163
199
 
164
200
  A name is `[a-z0-9][a-z0-9_-]*` and **cannot represent a path**, so it resolves
@@ -178,6 +214,11 @@ wrong one is unreachable from the other rather than merely misplaced. And **who
178
214
  may reach which memory is not this package's responsibility** — the name comes
179
215
  from your code, so put the policy there.
180
216
 
217
+ Every `Workspace` verb that touches a database returns a promise — `open`,
218
+ `list`, `entries`, `find`, `describe`, `archive`, `reindex`, `verify` — because
219
+ the registry is itself a plugmem memory and a named memory is a real file being
220
+ opened. `closeIdle()` and `openCount()` stay synchronous.
221
+
181
222
  `reindex()` and `verify()` return promises: they open and read every memory in
182
223
  the directory, which is not work for the main thread.
183
224
 
@@ -216,10 +257,40 @@ a no-op report when there is nothing to purge, reindex or optimize.
216
257
  version; the heavier ones buy bytes and index freshness. `"full"` is the only
217
258
  one that repacks the edge arenas, which a relink-heavy workload fragments.
218
259
 
219
- Small single-record and read verbs stay synchronous for a direct API. A single
220
- write can still wait for the configured durability policy, and a configured
221
- embedder can perform a remote request; use `rememberMany` when that work should
222
- run away from the event loop.
260
+ ### What is async, and why
261
+
262
+ `remember`, `revise`, `recall`, `rememberMany`, `exportPage`, `maintain` and
263
+ `checkpoint` return promises. Everything else — `get`, `stats`, `tagsOf`,
264
+ `forget`, `link`, `unlink`, `verify`, `export`, `path`, `generation`,
265
+ `refresh` — is synchronous.
266
+
267
+ The line is drawn at blocking work. Node runs all JavaScript on **one** thread,
268
+ so a native call that waits on an embedder's HTTP round trip or on an fsync
269
+ freezes every timer, socket and callback in the process for as long as it takes.
270
+ The verbs above can do exactly that — with an `[embedder]` configured,
271
+ `remember` and a text `recall` each cost a request to the provider — so they run
272
+ on a libuv worker and hand JavaScript a promise. The rest touch only mapped
273
+ memory and return in microseconds, where a promise would be pure ceremony.
274
+
275
+ Arguments are still checked on your thread: a refused one **throws** at the call
276
+ site rather than rejecting later, so a mistake in your code and a failure in the
277
+ engine never arrive the same way.
278
+
279
+ ### Bringing your own embedding
280
+
281
+ `remember`, `revise`, `rememberMany` and `recall` all take an optional
282
+ `vector` — a precomputed embedding whose length must equal the configured `dim`:
283
+
284
+ ```typescript
285
+ const own = await myEmbedder(text); // your model, your pipeline
286
+ await db.remember({ text, vector: own }); // nothing is sent to `[embedder]`
287
+ const res = await db.recall({ query: text, vector: own });
288
+ ```
289
+
290
+ Given one, it **replaces** the embedder for that call — the engine embeds only
291
+ when the field is absent. Use it for vectors you already have, for a model that
292
+ is not an OpenAI-shaped HTTP endpoint, or for a deterministic test with no
293
+ network. The CLI (`--vector`) and the MCP tools (`vector`) take the same thing.
223
294
 
224
295
  `close()` releases the file and its lock; every verb afterwards throws, and it is
225
296
  idempotent (the handle is also released on garbage collection, but `close()`
package/index.d.ts ADDED
@@ -0,0 +1,718 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+
4
+ /* auto-generated by NAPI-RS */
5
+
6
+ /** Options for [`Plugmem::new`]. */
7
+ export interface OpenOptions {
8
+ /**
9
+ * Embedding dimension (`Config::dim`). Omit or 0 to keep vectors off. When
10
+ * the config file configures an `[embedder]`, that embedder's dimension is
11
+ * authoritative and this — if given — must agree with it.
12
+ */
13
+ dim?: number
14
+ /**
15
+ * Open read-only over another process's writer (requires a checkpointed
16
+ * database). The write verbs then throw; `generation`/`refresh` appear.
17
+ *
18
+ * A text `recall` still reaches the vector source: the engine cannot embed
19
+ * on a read-only handle (replaying into it would defeat the zero-copy
20
+ * open), so this binding embeds the query itself before asking — the same
21
+ * thing the CLI and the MCP server do for their read-only paths.
22
+ */
23
+ readOnly?: boolean
24
+ /**
25
+ * Path to a `config.toml` (`[database]` / `[engine]` / `[embedder]` /
26
+ * `[maintenance]`). When the constructor path is omitted, `[database].path`
27
+ * participates in database-path resolution.
28
+ * Omitted, the standard discovery applies — `$PLUGMEM_CONFIG`, then
29
+ * `$XDG_CONFIG_HOME/plugmem/config.toml` — exactly as the CLI and MCP
30
+ * server resolve it. The `[embedder]` section is what makes a text-only
31
+ * `remember`/`recall` auto-embed; with no config there is no embedder
32
+ * (lexical, tag, graph and time recall still answer).
33
+ */
34
+ config?: string
35
+ }
36
+ /** One typed edge on a remembered fact: `entity` gains relation `rel`. */
37
+ export interface LinkRef {
38
+ /** The relation name. */
39
+ rel: string
40
+ /** The target entity name. */
41
+ entity: string
42
+ }
43
+ /** Arguments for [`Plugmem::remember`] / [`Plugmem::revise`]. */
44
+ export interface RememberArgs {
45
+ /** The fact text (required). */
46
+ text: string
47
+ /** Subject entity name. */
48
+ entity?: string
49
+ /** Tag strings. */
50
+ tags?: Array<string>
51
+ /** Typed edges to attach. */
52
+ links?: Array<LinkRef>
53
+ /**
54
+ * Opaque metadata as a key→value map (a URI to the real payload, a mime
55
+ * type, an external key). The engine never interprets it.
56
+ */
57
+ metadata?: Record<string, string>
58
+ /** Validity start, unix milliseconds (default: the fact's record time). */
59
+ validFrom?: number
60
+ /**
61
+ * A precomputed embedding. Its length must equal the configured `dim`.
62
+ *
63
+ * Given, it **replaces** the embedder: nothing is sent to the provider.
64
+ * That is the host's own precedence — it embeds only when this is absent —
65
+ * and it is the route for a vector you already have, or for a model that
66
+ * is not an OpenAI-shaped HTTP endpoint.
67
+ */
68
+ vector?: Array<number>
69
+ }
70
+ /**
71
+ * Arguments for [`Plugmem::recall`] — every field optional; lexical/tag/graph/
72
+ * time still answer with no query vector.
73
+ */
74
+ export interface RecallArgs {
75
+ /** Free-text query (embedded by the engine when an embedder is configured). */
76
+ query?: string
77
+ /** Restrict to facts carrying all these tags. */
78
+ tags?: Array<string>
79
+ /** Anchor entities for the graph source. */
80
+ entities?: Array<string>
81
+ /** "What was true at" this instant, unix milliseconds (bitemporal as-of). */
82
+ asOf?: number
83
+ /** Time window `[from, to)` over `recorded_at`, unix milliseconds. */
84
+ range?: Array<number>
85
+ /** Max facts to return (0 = engine default). */
86
+ k?: number
87
+ /** Include closed revisions (default false). */
88
+ closed?: boolean
89
+ /**
90
+ * A precomputed embedding. Its length must equal the configured `dim`.
91
+ *
92
+ * Given, it **replaces** the embedder: nothing is sent to the provider.
93
+ * That is the host's own precedence — it embeds only when this is absent —
94
+ * and it is the route for a vector you already have, or for a model that
95
+ * is not an OpenAI-shaped HTTP endpoint.
96
+ */
97
+ vector?: Array<number>
98
+ }
99
+ /** Arguments for [`Plugmem::link`]. */
100
+ export interface LinkArgs {
101
+ /** Source entity name. */
102
+ src: string
103
+ /** Relation name. */
104
+ rel: string
105
+ /** Destination entity name. */
106
+ dst: string
107
+ }
108
+ /** One similar / potentially-conflicting live fact surfaced by `remember`. */
109
+ export interface Similar {
110
+ /** The existing fact's id. */
111
+ id: number
112
+ /** Match strength (higher = closer). */
113
+ score: number
114
+ /** What triggered the hint: `"LexicalOverlap"` or `"VectorCosine"`. */
115
+ reason: string
116
+ }
117
+ /** The result of `remember` / `revise`. */
118
+ export interface RememberOutcome {
119
+ /** The new fact's id. */
120
+ id: number
121
+ /** The subject entity id, if one was named. */
122
+ entity?: number
123
+ /**
124
+ * Similar / potentially-conflicting live facts (best first; the engine
125
+ * never merges on its own — the caller decides).
126
+ */
127
+ similar: Array<Similar>
128
+ }
129
+ /** One recalled fact. */
130
+ export interface RecalledFact {
131
+ /** The fact id. */
132
+ id: number
133
+ /** Fused score (reciprocal-rank fusion + recency). */
134
+ score: number
135
+ /** Bit set of the sources that surfaced it. */
136
+ sources: number
137
+ /** Subject entity id (a sentinel when none). */
138
+ entity: number
139
+ /** Knowledge axis: when the memory learned it (unix ms). */
140
+ recordedAt: number
141
+ /** Truth axis start (unix ms). */
142
+ validFrom: number
143
+ /** Truth axis end (unix ms), or the open sentinel — see the module note. */
144
+ validTo: number
145
+ }
146
+ /** One edge walked by the graph source. */
147
+ export interface RecalledEdge {
148
+ /** Source entity id. */
149
+ src: number
150
+ /** Relation term id. */
151
+ rel: number
152
+ /** Destination entity id. */
153
+ dst: number
154
+ /** Provenance fact id (a sentinel when none). */
155
+ provenance: number
156
+ }
157
+ /** A recall response: the structured hits plus the prompt-ready block. */
158
+ export interface RecallResult {
159
+ /** Selected facts, descending fused score. */
160
+ facts: Array<RecalledFact>
161
+ /** Edges the graph source walked (deduplicated). */
162
+ edges: Array<RecalledEdge>
163
+ /** The compact prompt block (empty when nothing was found). */
164
+ rendered: string
165
+ /** `true` when selection stopped at `k`/the token budget with more left. */
166
+ truncated: boolean
167
+ }
168
+ /** Engine size counters. */
169
+ export interface Stats {
170
+ /** Fact records stored (live, closed and tombstoned-awaiting-maintain). */
171
+ facts: number
172
+ /** Entities. */
173
+ entities: number
174
+ /** Interned terms (tokens, tags, relations, names). */
175
+ terms: number
176
+ /** Directed edges. */
177
+ edges: number
178
+ /** Historical edge versions, including closed versions. */
179
+ edgeVersions: number
180
+ /** Quantized vector slots. */
181
+ vectors: number
182
+ /** Tombstoned fact records awaiting physical purge. */
183
+ tombstones: number
184
+ /** Vector slots covered by HNSW. */
185
+ hnswIndexed: number
186
+ /** The next fact id to be assigned. */
187
+ nextFact: number
188
+ /** The next entity id to be assigned. */
189
+ nextEntity: number
190
+ /** The next edge-version id to be assigned. */
191
+ nextEdge: number
192
+ /** Total bytes held by the engine's pools. */
193
+ poolBytes: number
194
+ }
195
+ /**
196
+ * The raw record behind a [`FactSnapshot`] — temporality and flags. (Internal
197
+ * pointers — the blob/vector slots and the reserved `kind` — are omitted.)
198
+ */
199
+ export interface FactRecord {
200
+ /** The fact id. */
201
+ id: number
202
+ /** Subject entity id (a sentinel when none). */
203
+ entity: number
204
+ /** Bit set of fact flags (tombstone / closed / has-vector). */
205
+ flags: number
206
+ /** Predecessor in the revision chain (a sentinel when none). */
207
+ revises: number
208
+ /** Knowledge axis: when the memory learned it (unix ms). */
209
+ recordedAt: number
210
+ /** Truth axis start (unix ms). */
211
+ validFrom: number
212
+ /** Truth axis end (unix ms), or the open sentinel — see the module note. */
213
+ validTo: number
214
+ }
215
+ /** One fact's full card (from `get`). */
216
+ export interface FactSnapshot {
217
+ /** The raw record (temporality, flags, references). */
218
+ record: FactRecord
219
+ /** The fact text. */
220
+ text: string
221
+ /**
222
+ * The fact's metadata as a key→value map (empty when it has none). Opaque
223
+ * to the engine — a URI to the real payload, a mime type, an external key.
224
+ */
225
+ metadata: Record<string, string>
226
+ }
227
+ /** One exported fact — the id-free, import-ready shape. */
228
+ export interface ExportedFact {
229
+ /** The fact text. */
230
+ text: string
231
+ /** Subject entity name, if any. */
232
+ entity?: string
233
+ /** Tag strings. */
234
+ tags: Array<string>
235
+ /** Metadata as a key→value map (empty when none); preserved on import. */
236
+ metadata: Record<string, string>
237
+ /** When the memory learned it (unix ms; informational). */
238
+ recordedAt: number
239
+ /** Validity start (unix ms; preserved on import). */
240
+ validFrom: number
241
+ }
242
+ /** One bounded page returned by `exportPage`. */
243
+ export interface ExportPage {
244
+ /** Open facts in fact-id order; never longer than the native page bound. */
245
+ facts: Array<ExportedFact>
246
+ /** Pass this opaque cursor to the next call; absent when the scan is done. */
247
+ nextCursor?: number
248
+ }
249
+ /** The report of a `maintain` pass. */
250
+ export interface MaintainReport {
251
+ /** Tombstoned facts physically removed by this pass. */
252
+ purged: number
253
+ /** On-disk image bytes before the pass. */
254
+ bytesBefore: number
255
+ /** On-disk image bytes after the pass. */
256
+ bytesAfter: number
257
+ /** No storage/index rewrite was needed. */
258
+ noOp: boolean
259
+ /** Tombstones present before the pass. */
260
+ tombstonesBefore: number
261
+ /** Fact records before the pass. */
262
+ factsBefore: number
263
+ /** Fact records after the pass. */
264
+ factsAfter: number
265
+ /** Vector slots before the pass. */
266
+ vectorsBefore: number
267
+ /** Vector slots after the pass. */
268
+ vectorsAfter: number
269
+ /** HNSW coverage before the pass. */
270
+ hnswIndexedBefore: number
271
+ /** HNSW coverage after the pass. */
272
+ hnswIndexedAfter: number
273
+ /** Physical compaction ran. */
274
+ structuralCompacted: boolean
275
+ /** BM25 was compacted from existing postings. */
276
+ bm25Compacted: boolean
277
+ /** BM25 was rebuilt from text. */
278
+ bm25Reindexed: boolean
279
+ /** HNSW was rebuilt from empty. */
280
+ hnswRebuilt: boolean
281
+ /** HNSW was carried/remapped. */
282
+ hnswRemapped: boolean
283
+ /** Vector slots inserted into HNSW. */
284
+ hnswInserted: number
285
+ /**
286
+ * The edge arenas were rewritten page-dense (`full` only). No edge
287
+ * version is ever dropped, so the version count is unchanged — only the
288
+ * bytes shrink.
289
+ */
290
+ edgesCompacted: boolean
291
+ /** Current edges before the pass. */
292
+ edgesBefore: number
293
+ /** Historical edge versions before the pass. */
294
+ edgeVersionsBefore: number
295
+ }
296
+ /** How much work a `maintain` pass should do. */
297
+ export const enum MaintainMode {
298
+ /**
299
+ * Only pending work: purge tombstones, refresh a stale text index, and
300
+ * advance the vector graph within a bounded budget. Cheap to run often.
301
+ */
302
+ Auto = 'auto',
303
+ /** Physically purge tombstoned facts and compact storage and indexes. */
304
+ Compact = 'compact',
305
+ /** Rebuild the text index by re-reading and re-tokenizing every fact. */
306
+ ReindexText = 'reindex-text',
307
+ /** Build or advance the vector graph without compacting anything else. */
308
+ OptimizeVectors = 'optimize-vectors',
309
+ /**
310
+ * Rebuild every rebuildable structure, fully optimize vectors and repack
311
+ * the edge arenas. O(database) work; no history is ever dropped.
312
+ */
313
+ Full = 'full'
314
+ }
315
+ /**
316
+ * One memory as the workspace registry knows it.
317
+ *
318
+ * Hand-mapped rather than serde-round-tripped like the rest of this file:
319
+ * `DbEntry` carries a `DbName`, which is a validated newtype with no serde
320
+ * derive, and giving one to a public host type only so a wrapper could
321
+ * round-trip it would be the wrong direction of dependency.
322
+ */
323
+ export interface DbEntry {
324
+ /** The memory's name — its identity, and what `Workspace.open` takes. */
325
+ db: string
326
+ /** What it is for. */
327
+ description: string
328
+ /** Its tags. */
329
+ tags: Array<string>
330
+ /** Its owner, if recorded. */
331
+ owner?: string
332
+ /** Whether it is labelled archived. */
333
+ archived: boolean
334
+ }
335
+ /** What a `Workspace.reindex()` pass did. */
336
+ export interface ReindexReport {
337
+ /** Memories whose own description was copied into the registry. */
338
+ indexed: Array<string>
339
+ /**
340
+ * Memories nobody has described. Not a fault — a memory works without a
341
+ * description; it just cannot be found by one.
342
+ */
343
+ undescribed: Array<string>
344
+ /**
345
+ * Memories another process holds open, so this pass could not read them.
346
+ * Named rather than skipped silently: the registry is knowingly incomplete.
347
+ */
348
+ busy: Array<string>
349
+ }
350
+ /** Something `Workspace.verify()` found. Reported, never repaired. */
351
+ export interface WorkspaceProblem {
352
+ /** The memory it concerns (empty for a kind this binding does not know). */
353
+ db: string
354
+ /**
355
+ * What kind: `"missing"`, `"undescribed"`, `"stale"`, `"unreadable"`,
356
+ * `"ambiguous-self"`. The same vocabulary the CLI prints in `--json`.
357
+ */
358
+ issue: string
359
+ /** More detail, where the kind carries any. */
360
+ detail?: string
361
+ }
362
+ /** Options for [`Workspace::new`]. */
363
+ export interface WorkspaceOptions {
364
+ /**
365
+ * Embedding dimension, as in [`crate::db::OpenOptions`]. Applies to every
366
+ * memory in the workspace and to the registry.
367
+ */
368
+ dim?: number
369
+ /**
370
+ * Path to a `config.toml`. Resolution is the standard one when omitted.
371
+ * Its `[workspace]` section supplies the pool defaults below.
372
+ */
373
+ config?: string
374
+ /**
375
+ * Memories kept open at once; the least recently used is closed to make
376
+ * room. Defaults to `[workspace].max_open`, else 16.
377
+ */
378
+ maxOpen?: number
379
+ /**
380
+ * Milliseconds a memory may sit unused before `closeIdle()` closes it.
381
+ * `0` disables the sweep. Defaults to `[workspace].idle_timeout_ms`, else
382
+ * 60000.
383
+ *
384
+ * This is a liveness setting, not a memory one: an open memory holds the
385
+ * file's exclusive lock, so a long-running process that never let go would
386
+ * make its memories unreachable from anything else on the machine.
387
+ */
388
+ idleTimeoutMs?: number
389
+ }
390
+ /** What to record about a memory — the argument of [`Workspace::describe`]. */
391
+ export interface DescribeArgs {
392
+ /**
393
+ * What this memory is for, in the words someone would search with. This is
394
+ * the text `find` matches.
395
+ */
396
+ description: string
397
+ /** Tags to filter by. */
398
+ tags?: Array<string>
399
+ /**
400
+ * Who it belongs to. Recorded as a graph edge, so `find("ann")` returns
401
+ * what Ann owns even though no description mentions her.
402
+ */
403
+ owner?: string
404
+ }
405
+ /**
406
+ * The engine/package version (the workspace version; the npm package tracks
407
+ * it release-for-release).
408
+ */
409
+ export declare function version(): string
410
+ /** A short, version-free description pointing the caller at the skill. */
411
+ export declare function about(): string
412
+ /** One config.toml setting returned by [`settings_help`]. */
413
+ export interface SettingHelpItem {
414
+ /** TOML section name. */
415
+ section: string
416
+ /** TOML key name. */
417
+ key: string
418
+ /** Human-readable value type. */
419
+ valueType: string
420
+ /** Displayed default value. */
421
+ defaultValue: string
422
+ /** Setting behavior. */
423
+ description: string
424
+ /** Owning surface: shared, CLI or MCP. */
425
+ scope: string
426
+ }
427
+ /** Complete config.toml help returned by [`settings_help`]. */
428
+ export interface SettingsHelpResult {
429
+ /** Config discovery order from highest to lowest precedence. */
430
+ configPathPrecedence: Array<string>
431
+ /** Resolved platform default config path, if the OS exposes a user home. */
432
+ defaultConfigPath?: string
433
+ /** Every supported config.toml setting. */
434
+ settings: Array<SettingHelpItem>
435
+ }
436
+ /** Return the complete settings catalogue without opening a database. */
437
+ export declare function settingsHelp(): SettingsHelpResult
438
+ /**
439
+ * The companion skill for napi consumers: the canonical `SKILL.md` with the
440
+ * CLI/MCP "Run it" appendix removed (a napi host has one transport and always
441
+ * ships skill and engine from the same release, so that ceremony never applies).
442
+ */
443
+ export declare function skill(): string
444
+ /** The canonical, unstripped `SKILL.md` (what CLI/MCP consumers read). */
445
+ export declare function skillFull(): string
446
+ /**
447
+ * The `<!-- skill-version: X.Y.Z -->` marker value from the canonical skill
448
+ * (read from the raw text, so the marker living inside the stripped block is
449
+ * still visible here).
450
+ */
451
+ export declare function skillVersion(): string
452
+ /**
453
+ * A memory over one plugmem file — the napi mirror of [`plugmem_host::Database`]
454
+ * (writer) or [`plugmem_host::ReadOnlyDatabase`] (with `{ readOnly: true }`).
455
+ * Construct it, call the verbs, and `close()` it to release the file when done.
456
+ */
457
+ export declare class Plugmem {
458
+ /**
459
+ * Opens (or creates) the memory at `path` and resolves with the handle. If
460
+ * `path` is omitted, resolution is `PLUGMEM_DB` > `[database].path` > the
461
+ * platform data path — and [`path()`](Plugmem::path) reports what that was.
462
+ *
463
+ * **A static method, not a constructor, and that is the point.** Opening
464
+ * takes the file's exclusive lock, replays the journal and maps the
465
+ * snapshot — work proportional to what is on disk. A JavaScript
466
+ * constructor must evaluate to its object immediately, so `new` has no way
467
+ * to hand that to a worker: it would run on the one thread that executes
468
+ * JavaScript and freeze the process for the length of the replay. A static
469
+ * method can return a `Promise`, so it does.
470
+ *
471
+ * @throws synchronously on a config error (the file is read before any
472
+ * work is scheduled); rejects if another writer holds the lock
473
+ * (`PLUGMEM_LOCKED`), if `readOnly` is set on a database with no published
474
+ * snapshot (`PLUGMEM_NEEDS_CHECKPOINT`), or on an IO error.
475
+ */
476
+ static open(path?: string | undefined | null, options?: OpenOptions | undefined | null): Promise<Plugmem>
477
+ /**
478
+ * The file this memory is open on.
479
+ *
480
+ * Worth having because the constructor may resolve the path rather than be
481
+ * given one — `PLUGMEM_DB`, then `[database].path`, then the platform data
482
+ * path — and `new Plugmem()` with no argument otherwise leaves the caller
483
+ * unable to say which file it just wrote to.
484
+ */
485
+ path(): string
486
+ /**
487
+ * Stores a fact and resolves with its id plus similar/conflicting live
488
+ * facts.
489
+ *
490
+ * **Async** (returns a `Promise`): with an `[embedder]` configured this
491
+ * makes an HTTP call to the provider, and a write waits for the journal's
492
+ * durability policy. Both are blocking work, and Node has exactly one
493
+ * thread that runs JavaScript — doing them on it would freeze every timer,
494
+ * socket and callback in the process for the whole round trip. It runs on
495
+ * a libuv worker instead. Arguments are still checked synchronously, so a
496
+ * refused one throws here rather than rejecting later.
497
+ * @throws synchronously in read-only mode.
498
+ */
499
+ remember(args: RememberArgs): Promise<RememberOutcome>
500
+ /**
501
+ * Stores a batch of facts and resolves with one outcome per input.
502
+ *
503
+ * A batch may call a remote embedder and always performs one journal sync,
504
+ * so it runs on napi-rs' libuv worker pool.
505
+ */
506
+ rememberMany(args: Array<RememberArgs>): Promise<RememberOutcome[]>
507
+ /**
508
+ * Closes fact `id`, records `args` as its successor, and resolves with the
509
+ * outcome. **Async** for the same reasons as
510
+ * [`remember`](Plugmem::remember).
511
+ * @throws synchronously in read-only mode.
512
+ */
513
+ revise(id: number, args: RememberArgs): Promise<RememberOutcome>
514
+ /**
515
+ * Ranked, fused recall. Resolves with the structured result (its `rendered`
516
+ * field is the prompt-ready block; `facts`/`edges` are the structured hits).
517
+ *
518
+ * **Async** (returns a `Promise`): a text query with an `[embedder]`
519
+ * configured costs an HTTP round trip, and blocking the one thread that
520
+ * runs JavaScript for it would stall the whole process. The query shape is
521
+ * still validated synchronously.
522
+ */
523
+ recall(args?: RecallArgs | undefined | null): Promise<RecallResult>
524
+ /**
525
+ * Tombstones fact `id` (physically purged at the next `maintain`) and
526
+ * resolves with whether it was a live fact.
527
+ *
528
+ * **Async**: every write syncs the journal, and the host's post-write
529
+ * policy can fire a whole maintenance pass or a reshard from here — work
530
+ * proportional to the database, which the JS thread must not be holding.
531
+ * @throws synchronously in read-only mode.
532
+ */
533
+ forget(id: number): Promise<boolean>
534
+ /**
535
+ * Upserts a typed edge `src -rel-> dst`. **Async** for the same reason as
536
+ * [`forget`](Plugmem::forget). @throws synchronously in read-only mode.
537
+ */
538
+ link(args: LinkArgs): Promise<void>
539
+ /**
540
+ * Closes the current typed edge `src -rel-> dst`, resolving with whether
541
+ * one was open. **Async** for the same reason as
542
+ * [`forget`](Plugmem::forget). @throws synchronously in read-only mode.
543
+ */
544
+ unlink(args: LinkArgs): Promise<boolean>
545
+ /** One fact's full card by `id`, or `null` if unknown/tombstoned. */
546
+ get(id: number): FactSnapshot | null
547
+ /** Engine size counters. */
548
+ stats(): Stats
549
+ /**
550
+ * Every currently-open fact, as one array (id-free, import-ready).
551
+ *
552
+ * **Async, but still unbounded**: the scan is off the JS thread, yet the
553
+ * whole memory is materialized into a single array before it resolves, so
554
+ * the peak memory is the whole export. `exportPage` is the same data in
555
+ * bounded pages — prefer it for anything but a small memory or a script.
556
+ */
557
+ export(): Promise<ExportedFact[]>
558
+ /**
559
+ * Returns at most 128 inspected fact ids' open facts on a libuv worker
560
+ * thread. A sparse page can be empty and still carry `nextCursor`.
561
+ *
562
+ * Pass `nextCursor` back as `cursor` until it is absent. Each Promise owns
563
+ * exactly one bounded page and resolves only after its native scan has
564
+ * completed; there is no callback queue and no database lock held while JS
565
+ * processes the result. A writer may change between page calls, so do not
566
+ * mutate it during a snapshot-style backup; a read-only handle is stable.
567
+ */
568
+ exportPage(cursor?: number | undefined | null): Promise<ExportPage>
569
+ /** One fact's tags, or an empty array for an unknown or tombstoned id. */
570
+ tagsOf(id: number): Array<string>
571
+ /**
572
+ * Content-integrity check; rejects on the first inconsistency found.
573
+ *
574
+ * **Async**: this is a full sweep — every fact's text and metadata, the
575
+ * vector mapping, and both directions of every edge. On a large memory
576
+ * that is seconds of work, and it belongs on a worker.
577
+ */
578
+ verify(): Promise<void>
579
+ /**
580
+ * Runs policy-driven maintenance; resolves with the before/after report.
581
+ * **Async** (returns a `Promise`): the pass may do disk I/O (compaction,
582
+ * HNSW work), so it runs on a libuv worker thread and never blocks the
583
+ * event loop. @throws synchronously in read-only mode.
584
+ *
585
+ * `mode` defaults to `auto`, which does only what is pending. `full`
586
+ * rebuilds everything and repacks the edge arenas — O(database) work, and
587
+ * the only mode that reclaims edge-history page slack.
588
+ */
589
+ maintain(mode?: 'auto' | 'compact' | 'reindex-text' | 'optimize-vectors' | 'full'): Promise<MaintainReport>
590
+ /**
591
+ * Flushes the journal into a fresh snapshot. **Async** (returns a `Promise`):
592
+ * it writes and fsyncs a snapshot file, so it runs on a libuv worker thread.
593
+ * @throws synchronously in read-only mode.
594
+ */
595
+ checkpoint(): Promise<void>
596
+ /**
597
+ * The pinned snapshot generation (read-only mode only).
598
+ * @throws on a writer.
599
+ */
600
+ generation(): number
601
+ /**
602
+ * Advance to the writer's latest published checkpoint (read-only mode only);
603
+ * returns whether a newer generation was adopted. @throws on a writer.
604
+ */
605
+ refresh(): boolean
606
+ /**
607
+ * Releases the file and its lock. Every verb afterwards throws; calling it
608
+ * again is a no-op. (The handle is also released when the object is GC'd,
609
+ * but `close()` makes the moment explicit — e.g. before a read-only reopen.)
610
+ */
611
+ close(): void
612
+ }
613
+ /**
614
+ * A directory of named memories — the napi mirror of
615
+ * [`plugmem_host::Workspace`].
616
+ */
617
+ export declare class Workspace {
618
+ /**
619
+ * Opens the workspace rooted at `root`. Creates nothing: the directories
620
+ * appear when a memory is first written.
621
+ *
622
+ * @throws on a config error.
623
+ */
624
+ constructor(root: string, options?: WorkspaceOptions | undefined | null)
625
+ /**
626
+ * Opens the memory named `db` and returns it as a [`Plugmem`] — the same
627
+ * class, and the same verbs, as a memory opened by path.
628
+ *
629
+ * `create` defaults to `true`: a first use of an unused name brings that
630
+ * memory into being, which is what makes a new conversation need no
631
+ * registration step. Pass `false` to require that it already exists, which
632
+ * is what a read should do so a misspelled name is diagnosed rather than
633
+ * answered with nothing.
634
+ *
635
+ * @throws if the name is not a usable memory name, if it does not exist and
636
+ * `create` is false, or if another process holds it.
637
+ * **Async**: a first open replays the memory's journal and maps its
638
+ * snapshot, and making room in the pool closes another memory — file work
639
+ * that the one thread running JavaScript must not be holding.
640
+ */
641
+ open(db: string, create?: boolean | undefined | null): Promise<Plugmem>
642
+ /**
643
+ * Every memory in the directory, sorted by name.
644
+ *
645
+ * Reads the filesystem, not the registry: a memory that exists but was
646
+ * never described still appears.
647
+ * **Async**: it reads the directory.
648
+ */
649
+ list(): Promise<string[]>
650
+ /**
651
+ * Every described memory, sorted by name.
652
+ *
653
+ * **Async**: the registry is itself a memory, so this opens and reads a
654
+ * database.
655
+ */
656
+ entries(): Promise<DbEntry[]>
657
+ /**
658
+ * The memories whose descriptions best match `query`, best first.
659
+ *
660
+ * This is the answer to "I do not know the name": ask in words, get names
661
+ * back, then open by name. A person's name works too — an owner is a graph
662
+ * edge, and the graph source reaches it.
663
+ * **Async**: this is a full recall against the registry memory — the same
664
+ * hybrid retrieval any other recall runs.
665
+ */
666
+ find(query: string, k?: number | undefined | null): Promise<DbEntry[]>
667
+ /**
668
+ * Records what a memory is for — in the memory itself and in the registry,
669
+ * so the registry can always be rebuilt from the memories. Creates the
670
+ * memory if it does not exist.
671
+ *
672
+ * Called again for the same memory this revises rather than duplicating,
673
+ * so the history of what it used to be for is kept.
674
+ * **Async**: it writes twice — into the memory itself and into the
675
+ * registry — and each write syncs a journal.
676
+ */
677
+ describe(db: string, args: DescribeArgs): Promise<void>
678
+ /**
679
+ * Labels a memory archived, keeping its description. Returns whether
680
+ * anything changed. Archiving does not close, move or delete anything.
681
+ *
682
+ * @throws if the memory has no registry record to archive.
683
+ * **Async**: a registry write.
684
+ */
685
+ archive(db: string): Promise<boolean>
686
+ /**
687
+ * Rebuilds the registry from the memories' own descriptions.
688
+ *
689
+ * Runs on a libuv thread: it opens and reads every memory in the
690
+ * directory, which is not work for the main thread. A memory another
691
+ * process holds open cannot be read and is named in the report rather than
692
+ * skipped silently.
693
+ */
694
+ reindex(): Promise<ReindexReport>
695
+ /**
696
+ * Checks the registry against the directory. Reports every disagreement
697
+ * and repairs nothing — a workspace is a directory a person can edit, and
698
+ * guessing at their intent is how a consistency check loses data.
699
+ */
700
+ verify(): Promise<WorkspaceProblem[]>
701
+ /**
702
+ * Closes every memory unused for longer than the idle timeout, returning
703
+ * how many were closed. Call it on a timer: nothing else releases the file
704
+ * lock on a memory nobody is asking about.
705
+ */
706
+ closeIdle(): number
707
+ /** How many memories are open right now. */
708
+ openCount(): number
709
+ /**
710
+ * Closes every pooled memory and the registry, releasing their file locks,
711
+ * and closes the workspace. Every method then throws.
712
+ *
713
+ * A [`Plugmem`] handed out by `open()` is **not** closed by this: it is its
714
+ * own handle and holds its own lock until it is closed or garbage
715
+ * collected.
716
+ */
717
+ close(): void
718
+ }
package/index.js ADDED
@@ -0,0 +1,323 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ /* prettier-ignore */
4
+
5
+ /* auto-generated by NAPI-RS */
6
+
7
+ const { existsSync, readFileSync } = require('fs')
8
+ const { join } = require('path')
9
+
10
+ const { platform, arch } = process
11
+
12
+ let nativeBinding = null
13
+ let localFileExisted = false
14
+ let loadError = null
15
+
16
+ function isMusl() {
17
+ // For Node 10
18
+ if (!process.report || typeof process.report.getReport !== 'function') {
19
+ try {
20
+ const lddPath = require('child_process').execSync('which ldd').toString().trim()
21
+ return readFileSync(lddPath, 'utf8').includes('musl')
22
+ } catch (e) {
23
+ return true
24
+ }
25
+ } else {
26
+ const { glibcVersionRuntime } = process.report.getReport().header
27
+ return !glibcVersionRuntime
28
+ }
29
+ }
30
+
31
+ switch (platform) {
32
+ case 'android':
33
+ switch (arch) {
34
+ case 'arm64':
35
+ localFileExisted = existsSync(join(__dirname, 'plugmem.android-arm64.node'))
36
+ try {
37
+ if (localFileExisted) {
38
+ nativeBinding = require('./plugmem.android-arm64.node')
39
+ } else {
40
+ nativeBinding = require('plugmem-android-arm64')
41
+ }
42
+ } catch (e) {
43
+ loadError = e
44
+ }
45
+ break
46
+ case 'arm':
47
+ localFileExisted = existsSync(join(__dirname, 'plugmem.android-arm-eabi.node'))
48
+ try {
49
+ if (localFileExisted) {
50
+ nativeBinding = require('./plugmem.android-arm-eabi.node')
51
+ } else {
52
+ nativeBinding = require('plugmem-android-arm-eabi')
53
+ }
54
+ } catch (e) {
55
+ loadError = e
56
+ }
57
+ break
58
+ default:
59
+ throw new Error(`Unsupported architecture on Android ${arch}`)
60
+ }
61
+ break
62
+ case 'win32':
63
+ switch (arch) {
64
+ case 'x64':
65
+ localFileExisted = existsSync(
66
+ join(__dirname, 'plugmem.win32-x64-msvc.node')
67
+ )
68
+ try {
69
+ if (localFileExisted) {
70
+ nativeBinding = require('./plugmem.win32-x64-msvc.node')
71
+ } else {
72
+ nativeBinding = require('plugmem-win32-x64-msvc')
73
+ }
74
+ } catch (e) {
75
+ loadError = e
76
+ }
77
+ break
78
+ case 'ia32':
79
+ localFileExisted = existsSync(
80
+ join(__dirname, 'plugmem.win32-ia32-msvc.node')
81
+ )
82
+ try {
83
+ if (localFileExisted) {
84
+ nativeBinding = require('./plugmem.win32-ia32-msvc.node')
85
+ } else {
86
+ nativeBinding = require('plugmem-win32-ia32-msvc')
87
+ }
88
+ } catch (e) {
89
+ loadError = e
90
+ }
91
+ break
92
+ case 'arm64':
93
+ localFileExisted = existsSync(
94
+ join(__dirname, 'plugmem.win32-arm64-msvc.node')
95
+ )
96
+ try {
97
+ if (localFileExisted) {
98
+ nativeBinding = require('./plugmem.win32-arm64-msvc.node')
99
+ } else {
100
+ nativeBinding = require('plugmem-win32-arm64-msvc')
101
+ }
102
+ } catch (e) {
103
+ loadError = e
104
+ }
105
+ break
106
+ default:
107
+ throw new Error(`Unsupported architecture on Windows: ${arch}`)
108
+ }
109
+ break
110
+ case 'darwin':
111
+ localFileExisted = existsSync(join(__dirname, 'plugmem.darwin-universal.node'))
112
+ try {
113
+ if (localFileExisted) {
114
+ nativeBinding = require('./plugmem.darwin-universal.node')
115
+ } else {
116
+ nativeBinding = require('plugmem-darwin-universal')
117
+ }
118
+ break
119
+ } catch {}
120
+ switch (arch) {
121
+ case 'x64':
122
+ localFileExisted = existsSync(join(__dirname, 'plugmem.darwin-x64.node'))
123
+ try {
124
+ if (localFileExisted) {
125
+ nativeBinding = require('./plugmem.darwin-x64.node')
126
+ } else {
127
+ nativeBinding = require('plugmem-darwin-x64')
128
+ }
129
+ } catch (e) {
130
+ loadError = e
131
+ }
132
+ break
133
+ case 'arm64':
134
+ localFileExisted = existsSync(
135
+ join(__dirname, 'plugmem.darwin-arm64.node')
136
+ )
137
+ try {
138
+ if (localFileExisted) {
139
+ nativeBinding = require('./plugmem.darwin-arm64.node')
140
+ } else {
141
+ nativeBinding = require('plugmem-darwin-arm64')
142
+ }
143
+ } catch (e) {
144
+ loadError = e
145
+ }
146
+ break
147
+ default:
148
+ throw new Error(`Unsupported architecture on macOS: ${arch}`)
149
+ }
150
+ break
151
+ case 'freebsd':
152
+ if (arch !== 'x64') {
153
+ throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
154
+ }
155
+ localFileExisted = existsSync(join(__dirname, 'plugmem.freebsd-x64.node'))
156
+ try {
157
+ if (localFileExisted) {
158
+ nativeBinding = require('./plugmem.freebsd-x64.node')
159
+ } else {
160
+ nativeBinding = require('plugmem-freebsd-x64')
161
+ }
162
+ } catch (e) {
163
+ loadError = e
164
+ }
165
+ break
166
+ case 'linux':
167
+ switch (arch) {
168
+ case 'x64':
169
+ if (isMusl()) {
170
+ localFileExisted = existsSync(
171
+ join(__dirname, 'plugmem.linux-x64-musl.node')
172
+ )
173
+ try {
174
+ if (localFileExisted) {
175
+ nativeBinding = require('./plugmem.linux-x64-musl.node')
176
+ } else {
177
+ nativeBinding = require('plugmem-linux-x64-musl')
178
+ }
179
+ } catch (e) {
180
+ loadError = e
181
+ }
182
+ } else {
183
+ localFileExisted = existsSync(
184
+ join(__dirname, 'plugmem.linux-x64-gnu.node')
185
+ )
186
+ try {
187
+ if (localFileExisted) {
188
+ nativeBinding = require('./plugmem.linux-x64-gnu.node')
189
+ } else {
190
+ nativeBinding = require('plugmem-linux-x64-gnu')
191
+ }
192
+ } catch (e) {
193
+ loadError = e
194
+ }
195
+ }
196
+ break
197
+ case 'arm64':
198
+ if (isMusl()) {
199
+ localFileExisted = existsSync(
200
+ join(__dirname, 'plugmem.linux-arm64-musl.node')
201
+ )
202
+ try {
203
+ if (localFileExisted) {
204
+ nativeBinding = require('./plugmem.linux-arm64-musl.node')
205
+ } else {
206
+ nativeBinding = require('plugmem-linux-arm64-musl')
207
+ }
208
+ } catch (e) {
209
+ loadError = e
210
+ }
211
+ } else {
212
+ localFileExisted = existsSync(
213
+ join(__dirname, 'plugmem.linux-arm64-gnu.node')
214
+ )
215
+ try {
216
+ if (localFileExisted) {
217
+ nativeBinding = require('./plugmem.linux-arm64-gnu.node')
218
+ } else {
219
+ nativeBinding = require('plugmem-linux-arm64-gnu')
220
+ }
221
+ } catch (e) {
222
+ loadError = e
223
+ }
224
+ }
225
+ break
226
+ case 'arm':
227
+ if (isMusl()) {
228
+ localFileExisted = existsSync(
229
+ join(__dirname, 'plugmem.linux-arm-musleabihf.node')
230
+ )
231
+ try {
232
+ if (localFileExisted) {
233
+ nativeBinding = require('./plugmem.linux-arm-musleabihf.node')
234
+ } else {
235
+ nativeBinding = require('plugmem-linux-arm-musleabihf')
236
+ }
237
+ } catch (e) {
238
+ loadError = e
239
+ }
240
+ } else {
241
+ localFileExisted = existsSync(
242
+ join(__dirname, 'plugmem.linux-arm-gnueabihf.node')
243
+ )
244
+ try {
245
+ if (localFileExisted) {
246
+ nativeBinding = require('./plugmem.linux-arm-gnueabihf.node')
247
+ } else {
248
+ nativeBinding = require('plugmem-linux-arm-gnueabihf')
249
+ }
250
+ } catch (e) {
251
+ loadError = e
252
+ }
253
+ }
254
+ break
255
+ case 'riscv64':
256
+ if (isMusl()) {
257
+ localFileExisted = existsSync(
258
+ join(__dirname, 'plugmem.linux-riscv64-musl.node')
259
+ )
260
+ try {
261
+ if (localFileExisted) {
262
+ nativeBinding = require('./plugmem.linux-riscv64-musl.node')
263
+ } else {
264
+ nativeBinding = require('plugmem-linux-riscv64-musl')
265
+ }
266
+ } catch (e) {
267
+ loadError = e
268
+ }
269
+ } else {
270
+ localFileExisted = existsSync(
271
+ join(__dirname, 'plugmem.linux-riscv64-gnu.node')
272
+ )
273
+ try {
274
+ if (localFileExisted) {
275
+ nativeBinding = require('./plugmem.linux-riscv64-gnu.node')
276
+ } else {
277
+ nativeBinding = require('plugmem-linux-riscv64-gnu')
278
+ }
279
+ } catch (e) {
280
+ loadError = e
281
+ }
282
+ }
283
+ break
284
+ case 's390x':
285
+ localFileExisted = existsSync(
286
+ join(__dirname, 'plugmem.linux-s390x-gnu.node')
287
+ )
288
+ try {
289
+ if (localFileExisted) {
290
+ nativeBinding = require('./plugmem.linux-s390x-gnu.node')
291
+ } else {
292
+ nativeBinding = require('plugmem-linux-s390x-gnu')
293
+ }
294
+ } catch (e) {
295
+ loadError = e
296
+ }
297
+ break
298
+ default:
299
+ throw new Error(`Unsupported architecture on Linux: ${arch}`)
300
+ }
301
+ break
302
+ default:
303
+ throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
304
+ }
305
+
306
+ if (!nativeBinding) {
307
+ if (loadError) {
308
+ throw loadError
309
+ }
310
+ throw new Error(`Failed to load native binding`)
311
+ }
312
+
313
+ const { Plugmem, MaintainMode, Workspace, version, about, settingsHelp, skill, skillFull, skillVersion } = nativeBinding
314
+
315
+ module.exports.Plugmem = Plugmem
316
+ module.exports.MaintainMode = MaintainMode
317
+ module.exports.Workspace = Workspace
318
+ module.exports.version = version
319
+ module.exports.about = about
320
+ module.exports.settingsHelp = settingsHelp
321
+ module.exports.skill = skill
322
+ module.exports.skillFull = skillFull
323
+ module.exports.skillVersion = skillVersion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plugmem",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Native Node.js addon for plugmem: an embedded long-term memory engine for LLM agents (remember / recall / revise / forget over one local file).",
5
5
  "repository": {
6
6
  "type": "git",
@@ -41,11 +41,11 @@
41
41
  "typecheck": "tsc -p tsconfig.json"
42
42
  },
43
43
  "optionalDependencies": {
44
- "plugmem-linux-x64-gnu": "0.3.0",
45
- "plugmem-linux-arm64-gnu": "0.3.0",
46
- "plugmem-darwin-x64": "0.3.0",
47
- "plugmem-darwin-arm64": "0.3.0",
48
- "plugmem-win32-x64-msvc": "0.3.0",
49
- "plugmem-win32-arm64-msvc": "0.3.0"
44
+ "plugmem-linux-x64-gnu": "0.4.0",
45
+ "plugmem-linux-arm64-gnu": "0.4.0",
46
+ "plugmem-darwin-x64": "0.4.0",
47
+ "plugmem-darwin-arm64": "0.4.0",
48
+ "plugmem-win32-x64-msvc": "0.4.0",
49
+ "plugmem-win32-arm64-msvc": "0.4.0"
50
50
  }
51
51
  }