@wastedcode/memex 0.1.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 (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +291 -0
  3. package/dist/cli/client.d.ts +35 -0
  4. package/dist/cli/client.js +183 -0
  5. package/dist/cli/client.js.map +1 -0
  6. package/dist/cli/commands/chown.d.ts +2 -0
  7. package/dist/cli/commands/chown.js +22 -0
  8. package/dist/cli/commands/chown.js.map +1 -0
  9. package/dist/cli/commands/config.d.ts +2 -0
  10. package/dist/cli/commands/config.js +132 -0
  11. package/dist/cli/commands/config.js.map +1 -0
  12. package/dist/cli/commands/create.d.ts +2 -0
  13. package/dist/cli/commands/create.js +21 -0
  14. package/dist/cli/commands/create.js.map +1 -0
  15. package/dist/cli/commands/destroy.d.ts +2 -0
  16. package/dist/cli/commands/destroy.js +34 -0
  17. package/dist/cli/commands/destroy.js.map +1 -0
  18. package/dist/cli/commands/ingest.d.ts +2 -0
  19. package/dist/cli/commands/ingest.js +74 -0
  20. package/dist/cli/commands/ingest.js.map +1 -0
  21. package/dist/cli/commands/lint.d.ts +2 -0
  22. package/dist/cli/commands/lint.js +46 -0
  23. package/dist/cli/commands/lint.js.map +1 -0
  24. package/dist/cli/commands/list.d.ts +2 -0
  25. package/dist/cli/commands/list.js +28 -0
  26. package/dist/cli/commands/list.js.map +1 -0
  27. package/dist/cli/commands/login.d.ts +2 -0
  28. package/dist/cli/commands/login.js +51 -0
  29. package/dist/cli/commands/login.js.map +1 -0
  30. package/dist/cli/commands/logs.d.ts +2 -0
  31. package/dist/cli/commands/logs.js +26 -0
  32. package/dist/cli/commands/logs.js.map +1 -0
  33. package/dist/cli/commands/query.d.ts +2 -0
  34. package/dist/cli/commands/query.js +48 -0
  35. package/dist/cli/commands/query.js.map +1 -0
  36. package/dist/cli/commands/serve.d.ts +2 -0
  37. package/dist/cli/commands/serve.js +14 -0
  38. package/dist/cli/commands/serve.js.map +1 -0
  39. package/dist/cli/commands/status.d.ts +2 -0
  40. package/dist/cli/commands/status.js +66 -0
  41. package/dist/cli/commands/status.js.map +1 -0
  42. package/dist/daemon/auth.d.ts +31 -0
  43. package/dist/daemon/auth.js +84 -0
  44. package/dist/daemon/auth.js.map +1 -0
  45. package/dist/daemon/db.d.ts +36 -0
  46. package/dist/daemon/db.js +181 -0
  47. package/dist/daemon/db.js.map +1 -0
  48. package/dist/daemon/namespace.d.ts +34 -0
  49. package/dist/daemon/namespace.js +74 -0
  50. package/dist/daemon/namespace.js.map +1 -0
  51. package/dist/daemon/peercred.d.ts +15 -0
  52. package/dist/daemon/peercred.js +19 -0
  53. package/dist/daemon/peercred.js.map +1 -0
  54. package/dist/daemon/queue.d.ts +26 -0
  55. package/dist/daemon/queue.js +126 -0
  56. package/dist/daemon/queue.js.map +1 -0
  57. package/dist/daemon/routes.d.ts +38 -0
  58. package/dist/daemon/routes.js +258 -0
  59. package/dist/daemon/routes.js.map +1 -0
  60. package/dist/daemon/runner.d.ts +25 -0
  61. package/dist/daemon/runner.js +195 -0
  62. package/dist/daemon/runner.js.map +1 -0
  63. package/dist/daemon/scaffold.d.ts +38 -0
  64. package/dist/daemon/scaffold.js +141 -0
  65. package/dist/daemon/scaffold.js.map +1 -0
  66. package/dist/daemon/server.d.ts +11 -0
  67. package/dist/daemon/server.js +145 -0
  68. package/dist/daemon/server.js.map +1 -0
  69. package/dist/daemon.d.ts +1 -0
  70. package/dist/daemon.js +55 -0
  71. package/dist/daemon.js.map +1 -0
  72. package/dist/index.d.ts +2 -0
  73. package/dist/index.js +36 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/lib/constants.d.ts +17 -0
  76. package/dist/lib/constants.js +30 -0
  77. package/dist/lib/constants.js.map +1 -0
  78. package/dist/lib/errors.d.ts +32 -0
  79. package/dist/lib/errors.js +64 -0
  80. package/dist/lib/errors.js.map +1 -0
  81. package/dist/lib/prompts/ingest.d.ts +9 -0
  82. package/dist/lib/prompts/ingest.js +48 -0
  83. package/dist/lib/prompts/ingest.js.map +1 -0
  84. package/dist/lib/prompts/lint.d.ts +8 -0
  85. package/dist/lib/prompts/lint.js +62 -0
  86. package/dist/lib/prompts/lint.js.map +1 -0
  87. package/dist/lib/prompts/query.d.ts +8 -0
  88. package/dist/lib/prompts/query.js +37 -0
  89. package/dist/lib/prompts/query.js.map +1 -0
  90. package/dist/lib/prompts/wiki.d.ts +11 -0
  91. package/dist/lib/prompts/wiki.js +112 -0
  92. package/dist/lib/prompts/wiki.js.map +1 -0
  93. package/dist/lib/types.d.ts +76 -0
  94. package/dist/lib/types.js +3 -0
  95. package/dist/lib/types.js.map +1 -0
  96. package/dist/standalone/memex.mjs +2313 -0
  97. package/dist/standalone/memex.mjs.map +7 -0
  98. package/package.json +54 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Inder Singh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,291 @@
1
+ # Memex
2
+
3
+ Isolated, queued `claude -p` runtime for persistent knowledge bases.
4
+
5
+ Each wiki gets its own filesystem namespace, job queue, and customizable Claude configuration. A privileged daemon handles namespace isolation and process spawning; unprivileged users interact through the `memex` CLI.
6
+
7
+ ## Why
8
+
9
+ `claude -p` gives you something the API can't: Claude Code's full file tool suite (Read, Write, Edit, Glob, Grep) running natively against a real filesystem. The wiki IS the filesystem, and Claude operates on it with the same tools a human developer would use.
10
+
11
+ The tradeoff is that `claude -p` is single-threaded and process-heavy. Memex compensates with per-wiki queuing and namespace isolation rather than fighting the CLI's concurrency model.
12
+
13
+ ## Quick start
14
+
15
+ ```bash
16
+ # Install
17
+ npm install -g memex
18
+
19
+ # Start the daemon (requires CAP_SYS_ADMIN for namespace isolation)
20
+ sudo memex serve
21
+
22
+ # Create a wiki
23
+ memex create my-wiki --name "My Knowledge Base"
24
+
25
+ # Authenticate Claude for this wiki
26
+ memex login my-wiki
27
+
28
+ # Ingest a document
29
+ memex ingest my-wiki notes.md report.pdf
30
+
31
+ # Ask a question
32
+ memex query my-wiki "What are the key themes across these documents?"
33
+
34
+ # Run a health check
35
+ memex lint my-wiki
36
+ ```
37
+
38
+ ## How it works
39
+
40
+ ```
41
+ ┌──────────────────────────────────────────────────────────────┐
42
+ │ CLI (unprivileged) │
43
+ │ │
44
+ │ memex ingest acme notes.md │
45
+ │ │ │
46
+ │ ▼ │
47
+ │ /run/memex/memex.sock ───────────────────────────────── │
48
+ └──────────────────────────┼───────────────────────────────────┘
49
+
50
+
51
+ ┌──────────────────────────────────────────────────────────────┐
52
+ │ DAEMON (memex serve) privileged │
53
+ │ │
54
+ │ Per-Wiki Job Queues Wiki Registry (SQLite) │
55
+ │ ┌─────────────────┐ ┌─────────────────────┐ │
56
+ │ │ acme: [▶ingest] │ │ wikis, jobs, audit │ │
57
+ │ │ beta: (idle) │ └─────────────────────┘ │
58
+ │ └────────┬────────┘ │
59
+ │ │ │
60
+ │ ▼ │
61
+ │ unshare -m -- mount --bind .../wikis/acme /workspace \ │
62
+ │ claude -p "..." --tools Read,Write,Edit,Glob,Grep │
63
+ │ │
64
+ │ ┌────────────────────────────────────────────────────────┐ │
65
+ │ │ MOUNT NAMESPACE — what Claude sees │ │
66
+ │ │ /workspace/ │ │
67
+ │ │ .claude.md wiki/_schema.md │ │
68
+ │ │ .claude/ wiki/_index.md │ │
69
+ │ │ .tools/ wiki/_log.md │ │
70
+ │ │ wiki/ wiki/raw/ │ │
71
+ │ │ │ │
72
+ │ │ No /home. No /etc. No other wikis. │ │
73
+ │ └────────────────────────────────────────────────────────┘ │
74
+ └──────────────────────────────────────────────────────────────┘
75
+ ```
76
+
77
+ **Serialize, don't parallelize.** Each wiki's queue processes jobs one at a time. This prevents concurrent writes to the same wiki files. Independent wikis run in parallel.
78
+
79
+ **Isolation through namespaces, not containers.** Mount namespaces give you filesystem isolation without Docker's overhead. Claude cannot see the host filesystem, other wikis, or anything outside `/workspace`.
80
+
81
+ ## Commands
82
+
83
+ | Command | Description |
84
+ |---------|-------------|
85
+ | `memex serve` | Start the daemon |
86
+ | `memex create <wiki> [--name "..."]` | Create a new wiki |
87
+ | `memex destroy <wiki> [--keep-data]` | Destroy a wiki |
88
+ | `memex config <wiki> --edit` | Open `.claude.md` in `$EDITOR` |
89
+ | `memex config <wiki> --set-key` | Set API key for this wiki |
90
+ | `memex config <wiki> --model opus` | Set default model |
91
+ | `memex login <wiki>` | Authenticate Claude (OAuth) |
92
+ | `memex ingest <wiki> <files...>` | Ingest source files into the wiki |
93
+ | `memex query <wiki> "question"` | Ask a question against the wiki |
94
+ | `memex lint <wiki>` | Run wiki health check |
95
+ | `memex logs <wiki> [--tail N]` | View audit log |
96
+ | `memex list` | List all wikis |
97
+ | `memex status <wiki> [jobId]` | Check job status |
98
+
99
+ Most commands block until the job completes. Pass `--async` to get a job ID and check status later.
100
+
101
+ ## Architecture
102
+
103
+ ### Per-wiki workspace
104
+
105
+ Each wiki gets an isolated directory:
106
+
107
+ ```
108
+ /var/lib/memex/wikis/{wikiId}/
109
+ .claude.md # Wiki-specific Claude conventions (user-editable)
110
+ .claude/ # Claude credentials (per-wiki, isolated)
111
+ .tools/
112
+ mcp.json # MCP server configuration (optional)
113
+ allowed-tools.txt # Extra tools beyond the base set (optional)
114
+ wiki/
115
+ _schema.md # Filing conventions — the LLM's institutional memory
116
+ _index.md # Table of contents — one-line summary of every page
117
+ _log.md # Chronological activity log
118
+ raw/ # Immutable source documents (never modified by Claude)
119
+ themes/ # (example — categories emerge from content)
120
+ customers/
121
+ ...
122
+ ```
123
+
124
+ ### Three layers
125
+
126
+ 1. **Raw sources** (`wiki/raw/`) — Immutable. Claude reads from them but never modifies them. Your source of truth.
127
+ 2. **The wiki** (`wiki/`) — LLM-generated markdown. Summaries, entity pages, concept pages, cross-references. Claude owns this layer entirely.
128
+ 3. **The schema** (`_schema.md`, `.claude.md`) — How the wiki is structured, what conventions to follow. Co-evolved by user and LLM.
129
+
130
+ ### Job types
131
+
132
+ | Type | What it does | Max turns | Timeout |
133
+ |------|-------------|-----------|---------|
134
+ | `ingest` | Process raw sources into wiki pages | 25 | 5 min |
135
+ | `query` | Search wiki and answer a question | 15 | 2 min |
136
+ | `lint` | Health check — find and fix issues | 30 | 10 min |
137
+
138
+ ### Auto-lint
139
+
140
+ After every 10 ingests (configurable), the daemon automatically queues a lint job. This keeps the wiki healthy as it grows — finding contradictions, orphan pages, missing cross-references, and index drift.
141
+
142
+ ## Security
143
+
144
+ **Namespace isolation** prevents Claude from accessing files outside the wiki's directory. This is the primary security boundary.
145
+
146
+ **Tool restriction** — Only `Read`, `Write`, `Edit`, `Glob`, `Grep` are available by default. No `Bash` (shell escape), no `WebSearch`/`WebFetch` (external calls), no `Agent` (sub-agents). Users can opt into higher-risk tools per-wiki via `.tools/allowed-tools.txt`.
147
+
148
+ **Per-wiki credentials** — Each wiki gets its own Claude authentication. Credentials are stored in the wiki's `.claude/` directory, isolated by the mount namespace.
149
+
150
+ **Audit log** — Every operation is recorded with the wiki, action, and detail.
151
+
152
+ ## Credential setup
153
+
154
+ Each wiki needs Claude credentials. Three options:
155
+
156
+ **Copy existing OAuth credentials (recommended):**
157
+ ```bash
158
+ # Uses ~/.claude/.credentials.json by default
159
+ memex login my-wiki
160
+
161
+ # Or specify a path
162
+ memex login my-wiki --credentials /path/to/.credentials.json
163
+ ```
164
+
165
+ If you've already run `claude auth login` on this machine, your credentials are at `~/.claude/.credentials.json`. This copies them into the wiki's isolated config directory.
166
+
167
+ **API key:**
168
+ ```bash
169
+ memex login my-wiki --api-key sk-ant-...
170
+ ```
171
+
172
+ **Global fallback:**
173
+ ```bash
174
+ # Set on the daemon's environment (e.g. in the systemd unit)
175
+ Environment=ANTHROPIC_API_KEY=sk-ant-...
176
+ ```
177
+
178
+ Credentials are checked in order: per-wiki API key > per-wiki OAuth > global `ANTHROPIC_API_KEY` env var on the daemon.
179
+
180
+ ## Customization
181
+
182
+ ### `.claude.md`
183
+
184
+ The most important customization point. This file is auto-discovered by Claude Code and extends the base system prompt. Use it to define:
185
+
186
+ - Domain vocabulary
187
+ - Filing conventions specific to your wiki
188
+ - Categories and page structures
189
+ - What to ignore or deprioritize
190
+ - How to handle specific types of content
191
+
192
+ ```bash
193
+ memex config my-wiki --edit
194
+ ```
195
+
196
+ ### `.tools/mcp.json`
197
+
198
+ Add external data sources via MCP servers:
199
+
200
+ ```json
201
+ {
202
+ "mcpServers": {
203
+ "notion": {
204
+ "command": "npx",
205
+ "args": ["-y", "@notionhq/mcp-server"],
206
+ "env": { "NOTION_API_KEY": "secret_..." }
207
+ }
208
+ }
209
+ }
210
+ ```
211
+
212
+ ### `.tools/allowed-tools.txt`
213
+
214
+ Extend the tool set beyond the safe defaults:
215
+
216
+ ```
217
+ Bash
218
+ WebSearch
219
+ ```
220
+
221
+ Use with caution — `Bash` gives Claude shell access inside the namespace.
222
+
223
+ ## Deployment
224
+
225
+ ### systemd (recommended)
226
+
227
+ ```bash
228
+ sudo cp systemd/memex.service /etc/systemd/system/
229
+ sudo systemctl enable --now memex
230
+ ```
231
+
232
+ The unit file grants `CAP_SYS_ADMIN` via `AmbientCapabilities` and creates the required directories automatically.
233
+
234
+ ```bash
235
+ # Lifecycle
236
+ systemctl start memex # Start the daemon
237
+ systemctl stop memex # Graceful shutdown (finishes current job)
238
+ systemctl restart memex # Stop + start
239
+ journalctl -u memex -f # Tail logs
240
+
241
+ # Status
242
+ systemctl status memex # Process state, recent log lines
243
+ ```
244
+
245
+ ### Why `memex serve` blocks
246
+
247
+ `memex serve` runs in the foreground deliberately. This is the standard pattern for systemd-managed daemons:
248
+
249
+ - **systemd owns the lifecycle** — backgrounding, restarts, boot ordering, and capability grants are its job, not the application's.
250
+ - **Logging is automatic** — stdout/stderr go straight to `journalctl`. No log file management needed.
251
+ - **Graceful shutdown works** — systemd sends SIGTERM, the daemon finishes the current job, closes the socket, and exits cleanly.
252
+ - **`Type=simple`** — systemd tracks the exact PID it spawned. Self-daemonizing (double-fork) breaks this and is a legacy SysV init pattern.
253
+
254
+ If you're running without systemd (development, containers), just background it yourself:
255
+
256
+ ```bash
257
+ sudo memex serve & # Background in shell
258
+ sudo memex serve &>/dev/null & # Silent background
259
+ ```
260
+
261
+ ### Manual
262
+
263
+ ```bash
264
+ sudo memex serve
265
+ ```
266
+
267
+ Requires root or `CAP_SYS_ADMIN` for mount namespace creation.
268
+
269
+ ## Environment variables
270
+
271
+ | Variable | Default | Description |
272
+ |----------|---------|-------------|
273
+ | `MEMEX_DATA_DIR` | `/var/lib/memex` | Data directory (wikis, database) |
274
+ | `MEMEX_RUN_DIR` | `/run/memex` | Runtime directory (socket, namespaces) |
275
+ | `MEMEX_SOCKET_PATH` | `/run/memex/memex.sock` | Unix socket path |
276
+ | `ANTHROPIC_API_KEY` | — | Global fallback API key |
277
+
278
+ ## Requirements
279
+
280
+ - Node.js >= 20
281
+ - Linux with mount namespace support (`unshare`, `nsenter` from util-linux)
282
+ - `CAP_SYS_ADMIN` capability (or root)
283
+ - `claude` CLI installed and in `$PATH`
284
+
285
+ ## The idea
286
+
287
+ Related in spirit to Vannevar Bush's Memex (1945) — a personal, curated knowledge store with associative trails between documents. The part Bush couldn't solve was who does the maintenance. The LLM handles that.
288
+
289
+ Most people's experience with LLMs and documents is RAG: retrieve chunks, generate answers, forget everything. Nothing compounds. Memex is different — the LLM incrementally builds and maintains a persistent wiki. The cross-references are already there. The contradictions have been flagged. The synthesis reflects everything ingested. Every source makes the whole richer.
290
+
291
+ You never write the wiki yourself. You source, explore, and ask questions. The LLM does the summarizing, cross-referencing, filing, and bookkeeping that makes a knowledge base actually useful over time.
@@ -0,0 +1,35 @@
1
+ import type { ApiResponse, Wiki, WikiConfig, QueueJob, JobType, AuditEntry } from '../lib/types.js';
2
+ export declare class MemexClient {
3
+ private socketPath;
4
+ constructor(socketPath?: string);
5
+ request<T = unknown>(method: string, path: string, body?: unknown): Promise<ApiResponse<T>>;
6
+ /**
7
+ * Make a request and stream the raw response body chunks.
8
+ * Used for login flow where we stream CLI output.
9
+ */
10
+ stream(method: string, path: string, body?: unknown): Promise<AsyncIterable<string>>;
11
+ createWiki(id: string, name?: string): Promise<ApiResponse<Wiki>>;
12
+ listWikis(): Promise<ApiResponse<Wiki[]>>;
13
+ getWiki(id: string): Promise<ApiResponse<Wiki & {
14
+ pending_jobs: number;
15
+ }>>;
16
+ destroyWiki(id: string, keepData?: boolean): Promise<ApiResponse<unknown>>;
17
+ updateConfig(wikiId: string, config: WikiConfig): Promise<ApiResponse<Wiki>>;
18
+ chownWiki(wikiId: string, uid: number): Promise<ApiResponse<Wiki>>;
19
+ setApiKey(wikiId: string, key: string): Promise<ApiResponse<unknown>>;
20
+ setCredentials(wikiId: string, credentials: string): Promise<ApiResponse<unknown>>;
21
+ submitJob(wikiId: string, type: JobType, payload: object, wait?: boolean): Promise<ApiResponse<QueueJob>>;
22
+ getJob(wikiId: string, jobId: number): Promise<ApiResponse<QueueJob>>;
23
+ listJobs(wikiId: string): Promise<ApiResponse<QueueJob[]>>;
24
+ getAuditLog(wikiId: string, limit?: number): Promise<ApiResponse<AuditEntry[]>>;
25
+ /**
26
+ * Upload a local file to the daemon for ingestion into a wiki's raw/ directory.
27
+ */
28
+ uploadFile(wikiId: string, localPath: string): Promise<ApiResponse<{
29
+ filename: string;
30
+ }>>;
31
+ /**
32
+ * Submit a job and poll until it completes.
33
+ */
34
+ waitForJob(wikiId: string, jobId: number, onPoll?: (job: QueueJob) => void): Promise<QueueJob>;
35
+ }
@@ -0,0 +1,183 @@
1
+ import { request as httpRequest } from 'node:http';
2
+ import { readFileSync } from 'node:fs';
3
+ import { basename } from 'node:path';
4
+ import { SOCKET_PATH, JOB_POLL_INTERVAL_MS } from '../lib/constants.js';
5
+ import { DaemonNotRunningError } from '../lib/errors.js';
6
+ export class MemexClient {
7
+ socketPath;
8
+ constructor(socketPath = SOCKET_PATH) {
9
+ this.socketPath = socketPath;
10
+ }
11
+ // ── Generic request ────────────────────────────────────────────────────
12
+ async request(method, path, body) {
13
+ return new Promise((resolve, reject) => {
14
+ const opts = {
15
+ socketPath: this.socketPath,
16
+ method,
17
+ path,
18
+ headers: { 'Content-Type': 'application/json' },
19
+ };
20
+ const req = httpRequest(opts, (res) => {
21
+ const chunks = [];
22
+ res.on('data', (chunk) => chunks.push(chunk));
23
+ res.on('end', () => {
24
+ const raw = Buffer.concat(chunks).toString('utf-8');
25
+ try {
26
+ resolve(JSON.parse(raw));
27
+ }
28
+ catch {
29
+ resolve({ ok: false, error: raw });
30
+ }
31
+ });
32
+ });
33
+ req.on('error', (err) => {
34
+ if (err.code === 'ECONNREFUSED' || err.code === 'ENOENT') {
35
+ reject(new DaemonNotRunningError());
36
+ }
37
+ else {
38
+ reject(err);
39
+ }
40
+ });
41
+ if (body !== undefined) {
42
+ req.write(JSON.stringify(body));
43
+ }
44
+ req.end();
45
+ });
46
+ }
47
+ /**
48
+ * Make a request and stream the raw response body chunks.
49
+ * Used for login flow where we stream CLI output.
50
+ */
51
+ async stream(method, path, body) {
52
+ return new Promise((resolve, reject) => {
53
+ const opts = {
54
+ socketPath: this.socketPath,
55
+ method,
56
+ path,
57
+ headers: { 'Content-Type': 'application/json' },
58
+ };
59
+ const req = httpRequest(opts, (res) => {
60
+ // Track response errors so the async iterator can propagate them
61
+ let responseError = null;
62
+ res.on('error', (err) => { responseError = err; });
63
+ const iterable = {
64
+ [Symbol.asyncIterator]() {
65
+ return {
66
+ next() {
67
+ return new Promise((resolveNext, rejectNext) => {
68
+ if (responseError) {
69
+ rejectNext(responseError);
70
+ return;
71
+ }
72
+ const onData = (chunk) => {
73
+ res.removeListener('end', onEnd);
74
+ res.removeListener('error', onError);
75
+ resolveNext({ value: chunk.toString('utf-8'), done: false });
76
+ };
77
+ const onEnd = () => {
78
+ res.removeListener('data', onData);
79
+ res.removeListener('error', onError);
80
+ resolveNext({ value: '', done: true });
81
+ };
82
+ const onError = (err) => {
83
+ res.removeListener('data', onData);
84
+ res.removeListener('end', onEnd);
85
+ rejectNext(err);
86
+ };
87
+ res.once('data', onData);
88
+ res.once('end', onEnd);
89
+ res.once('error', onError);
90
+ });
91
+ },
92
+ };
93
+ },
94
+ };
95
+ resolve(iterable);
96
+ });
97
+ req.on('error', (err) => {
98
+ if (err.code === 'ECONNREFUSED' || err.code === 'ENOENT') {
99
+ reject(new DaemonNotRunningError());
100
+ }
101
+ else {
102
+ reject(err);
103
+ }
104
+ });
105
+ if (body !== undefined) {
106
+ req.write(JSON.stringify(body));
107
+ }
108
+ req.end();
109
+ });
110
+ }
111
+ // ── Convenience methods ────────────────────────────────────────────────
112
+ createWiki(id, name) {
113
+ return this.request('POST', '/wikis', { id, name });
114
+ }
115
+ listWikis() {
116
+ return this.request('GET', '/wikis');
117
+ }
118
+ getWiki(id) {
119
+ return this.request('GET', `/wikis/${id}`);
120
+ }
121
+ destroyWiki(id, keepData = false) {
122
+ return this.request('DELETE', `/wikis/${id}`, { keepData });
123
+ }
124
+ updateConfig(wikiId, config) {
125
+ return this.request('PUT', `/wikis/${wikiId}/config`, config);
126
+ }
127
+ chownWiki(wikiId, uid) {
128
+ return this.request('POST', `/wikis/${wikiId}/chown`, { uid });
129
+ }
130
+ setApiKey(wikiId, key) {
131
+ return this.request('POST', `/wikis/${wikiId}/api-key`, { key });
132
+ }
133
+ setCredentials(wikiId, credentials) {
134
+ return this.request('POST', `/wikis/${wikiId}/credentials`, { credentials });
135
+ }
136
+ submitJob(wikiId, type, payload, wait = false) {
137
+ const path = `/wikis/${wikiId}/jobs` + (wait ? '?wait=true' : '');
138
+ return this.request('POST', path, { type, payload });
139
+ }
140
+ getJob(wikiId, jobId) {
141
+ return this.request('GET', `/wikis/${wikiId}/jobs/${jobId}`);
142
+ }
143
+ listJobs(wikiId) {
144
+ return this.request('GET', `/wikis/${wikiId}/jobs`);
145
+ }
146
+ getAuditLog(wikiId, limit) {
147
+ const path = `/wikis/${wikiId}/logs` + (limit ? `?limit=${limit}` : '');
148
+ return this.request('GET', path);
149
+ }
150
+ /**
151
+ * Upload a local file to the daemon for ingestion into a wiki's raw/ directory.
152
+ */
153
+ async uploadFile(wikiId, localPath) {
154
+ const content = readFileSync(localPath);
155
+ const filename = basename(localPath);
156
+ return this.request('POST', `/wikis/${wikiId}/ingest-file`, {
157
+ filename,
158
+ content: content.toString('base64'),
159
+ });
160
+ }
161
+ /**
162
+ * Submit a job and poll until it completes.
163
+ */
164
+ async waitForJob(wikiId, jobId, onPoll) {
165
+ while (true) {
166
+ const resp = await this.getJob(wikiId, jobId);
167
+ if (!resp.ok || !resp.data) {
168
+ throw new Error(resp.error ?? 'Failed to get job status');
169
+ }
170
+ const job = resp.data;
171
+ if (onPoll)
172
+ onPoll(job);
173
+ if (job.status === 'completed' || job.status === 'failed') {
174
+ return job;
175
+ }
176
+ await sleep(JOB_POLL_INTERVAL_MS);
177
+ }
178
+ }
179
+ }
180
+ function sleep(ms) {
181
+ return new Promise(resolve => setTimeout(resolve, ms));
182
+ }
183
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/cli/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const chownCommand: Command;
@@ -0,0 +1,22 @@
1
+ import { Command } from 'commander';
2
+ import { MemexClient } from '../client.js';
3
+ export const chownCommand = new Command('chown')
4
+ .description('Transfer wiki ownership to another user (by UID)')
5
+ .argument('<wikiId>', 'Wiki identifier')
6
+ .argument('<uid>', 'New owner UID (numeric)')
7
+ .action(async (wikiId, uidStr) => {
8
+ const uid = Number(uidStr);
9
+ if (!Number.isInteger(uid) || uid < 0) {
10
+ console.error(`Error: uid must be a non-negative integer, got '${uidStr}'`);
11
+ process.exit(1);
12
+ }
13
+ const client = new MemexClient();
14
+ const resp = await client.chownWiki(wikiId, uid);
15
+ if (!resp.ok) {
16
+ console.error(`Error: ${resp.error}`);
17
+ process.exit(1);
18
+ }
19
+ const wiki = resp.data;
20
+ console.log(`Transferred wiki '${wiki.id}' to uid ${wiki.owner_uid}`);
21
+ });
22
+ //# sourceMappingURL=chown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chown.js","sourceRoot":"","sources":["../../../src/cli/commands/chown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAG3C,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;KAC7C,WAAW,CAAC,kDAAkD,CAAC;KAC/D,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;KACvC,QAAQ,CAAC,OAAO,EAAE,yBAAyB,CAAC;KAC5C,MAAM,CAAC,KAAK,EAAE,MAAc,EAAE,MAAc,EAAE,EAAE;IAC/C,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAC3B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;QACtC,OAAO,CAAC,KAAK,CAAC,mDAAmD,MAAM,GAAG,CAAC,CAAC;QAC5E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;IACjC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAEjD,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,UAAU,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,IAAY,CAAC;IAC/B,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,CAAC,EAAE,YAAY,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;AACxE,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const configCommand: Command;