@wastedcode/memex 0.1.0 → 0.1.2

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 (41) hide show
  1. package/README.md +99 -183
  2. package/dist/cli/client.d.ts +10 -1
  3. package/dist/cli/client.js +12 -2
  4. package/dist/cli/client.js.map +1 -1
  5. package/dist/cli/commands/config.js +10 -5
  6. package/dist/cli/commands/config.js.map +1 -1
  7. package/dist/cli/commands/login.js +14 -6
  8. package/dist/cli/commands/login.js.map +1 -1
  9. package/dist/cli/commands/setup-token.d.ts +2 -0
  10. package/dist/cli/commands/setup-token.js +41 -0
  11. package/dist/cli/commands/setup-token.js.map +1 -0
  12. package/dist/daemon/auth.d.ts +16 -6
  13. package/dist/daemon/auth.js +50 -22
  14. package/dist/daemon/auth.js.map +1 -1
  15. package/dist/daemon/namespace.js +8 -2
  16. package/dist/daemon/namespace.js.map +1 -1
  17. package/dist/daemon/routes.d.ts +5 -1
  18. package/dist/daemon/routes.js +42 -9
  19. package/dist/daemon/routes.js.map +1 -1
  20. package/dist/daemon/runner.js +4 -0
  21. package/dist/daemon/runner.js.map +1 -1
  22. package/dist/daemon/scaffold.d.ts +14 -0
  23. package/dist/daemon/scaffold.js +49 -1
  24. package/dist/daemon/scaffold.js.map +1 -1
  25. package/dist/daemon/server.js +1 -0
  26. package/dist/daemon/server.js.map +1 -1
  27. package/dist/daemon.js +1 -1
  28. package/dist/daemon.js.map +1 -1
  29. package/dist/index.js +2 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/lib/constants.js +1 -1
  32. package/dist/lib/constants.js.map +1 -1
  33. package/dist/lib/errors.js +4 -2
  34. package/dist/lib/errors.js.map +1 -1
  35. package/dist/lib/prompts/ingest.js +8 -1
  36. package/dist/lib/prompts/ingest.js.map +1 -1
  37. package/dist/lib/prompts/wiki.js +13 -3
  38. package/dist/lib/prompts/wiki.js.map +1 -1
  39. package/dist/standalone/memex.mjs +241 -52
  40. package/dist/standalone/memex.mjs.map +4 -4
  41. package/package.json +8 -1
@@ -253,8 +253,11 @@ var init_errors = __esm({
253
253
  super(
254
254
  `No credentials configured for wiki '${wikiId}'.
255
255
  Set credentials with:
256
- memex login ${wikiId} (OAuth)
257
- memex config ${wikiId} --set-key (API key)`,
256
+ memex setup-token <token> (global, all wikis)
257
+ memex login ${wikiId} --token <token> (per-wiki)
258
+ memex login ${wikiId} (copy ~/.claude credentials)
259
+
260
+ Generate a token by running: claude setup-token`,
258
261
  "NO_CREDENTIALS",
259
262
  400
260
263
  );
@@ -314,7 +317,7 @@ var init_constants = __esm({
314
317
  "WebFetch",
315
318
  "WebSearch"
316
319
  ]);
317
- WIKI_ID_PATTERN = /^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$/;
320
+ WIKI_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{1,62}[a-zA-Z0-9]$/;
318
321
  }
319
322
  });
320
323
 
@@ -370,9 +373,14 @@ var init_namespace = __esm({
370
373
  */
371
374
  wrapCommand(wikiId, innerCommand) {
372
375
  const wikiDir = join(this.wikisDir, wikiId);
376
+ const rawDir = join(wikiDir, "wiki", "raw");
377
+ const rawMount = `${WORKSPACE_MOUNT}/wiki/raw`;
373
378
  const script = [
374
379
  `mount --bind ${shellEscape(wikiDir)} ${shellEscape(WORKSPACE_MOUNT)}`,
375
380
  `mount -o remount,nosuid,nodev ${shellEscape(WORKSPACE_MOUNT)}`,
381
+ // raw/ is the immutable source archive — Claude can read but never write
382
+ `mount --bind ${shellEscape(rawDir)} ${shellEscape(rawMount)}`,
383
+ `mount -o remount,bind,ro ${shellEscape(rawMount)}`,
376
384
  `cd ${shellEscape(WORKSPACE_MOUNT)}`,
377
385
  `exec ${innerCommand.map(shellEscape).join(" ")}`
378
386
  ].join(" && ");
@@ -386,7 +394,7 @@ var init_namespace = __esm({
386
394
  });
387
395
 
388
396
  // src/daemon/scaffold.ts
389
- import { mkdirSync as mkdirSync2, writeFileSync, rmSync, readFileSync, existsSync as existsSync2 } from "node:fs";
397
+ import { mkdirSync as mkdirSync2, writeFileSync, rmSync, readFileSync, existsSync as existsSync2, readdirSync } from "node:fs";
390
398
  import { join as join2, basename } from "node:path";
391
399
  function sanitizeFilename(name) {
392
400
  return name.replace(/[^a-zA-Z0-9._-]/g, "_");
@@ -508,6 +516,48 @@ Chronological record of knowledge base activity.
508
516
  if (!existsSync2(p)) return [];
509
517
  return readFileSync(p, "utf-8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
510
518
  }
519
+ /**
520
+ * List files in a wiki's wiki/ directory.
521
+ * Returns an array of { path, type } entries relative to wiki/.
522
+ * If prefix is given, only lists entries under that subdirectory.
523
+ */
524
+ listWikiFiles(wikiId, prefix = "") {
525
+ const wikiContentDir = join2(this.wikiDir(wikiId), "wiki");
526
+ const target = prefix ? join2(wikiContentDir, prefix) : wikiContentDir;
527
+ if (!target.startsWith(wikiContentDir)) {
528
+ throw new Error("Invalid path");
529
+ }
530
+ if (!existsSync2(target)) return [];
531
+ const entries = readdirSync(target, { withFileTypes: true });
532
+ const results = [];
533
+ for (const entry of entries) {
534
+ if (entry.name.startsWith(".")) continue;
535
+ const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
536
+ results.push({
537
+ path: relPath,
538
+ type: entry.isDirectory() ? "directory" : "file"
539
+ });
540
+ }
541
+ return results.sort((a, b) => {
542
+ if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
543
+ return a.path.localeCompare(b.path);
544
+ });
545
+ }
546
+ /**
547
+ * Read a file from a wiki's wiki/ directory.
548
+ * Returns the file content as a UTF-8 string.
549
+ */
550
+ readWikiFile(wikiId, filePath) {
551
+ const wikiContentDir = join2(this.wikiDir(wikiId), "wiki");
552
+ const target = join2(wikiContentDir, filePath);
553
+ if (!target.startsWith(wikiContentDir)) {
554
+ throw new Error("Invalid path");
555
+ }
556
+ if (!existsSync2(target)) {
557
+ throw new Error(`File not found: ${filePath}`);
558
+ }
559
+ return readFileSync(target, "utf-8");
560
+ }
511
561
  /**
512
562
  * Check if a wiki directory exists on disk.
513
563
  */
@@ -521,36 +571,37 @@ Chronological record of knowledge base activity.
521
571
  // src/daemon/auth.ts
522
572
  import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "node:fs";
523
573
  import { join as join3 } from "node:path";
524
- var AuthManager;
574
+ var GLOBAL_TOKEN_PATH, AuthManager;
525
575
  var init_auth = __esm({
526
576
  "src/daemon/auth.ts"() {
527
577
  "use strict";
528
578
  init_constants();
529
579
  init_errors();
580
+ GLOBAL_TOKEN_PATH = join3(DATA_DIR, ".oauth-token");
530
581
  AuthManager = class {
531
- constructor(wikisDir = WIKIS_DIR, globalApiKey) {
582
+ constructor(wikisDir = WIKIS_DIR) {
532
583
  this.wikisDir = wikisDir;
533
- this.globalApiKey = globalApiKey;
584
+ this.globalOAuthToken = this.loadGlobalToken();
534
585
  }
535
586
  wikisDir;
536
- globalApiKey;
587
+ globalOAuthToken;
537
588
  /**
538
589
  * Resolve credentials for a wiki, returning environment variables
539
590
  * to set on the claude child process.
540
591
  *
541
592
  * Priority:
542
- * 1. Per-wiki API key file (.claude/api-key)
593
+ * 1. Per-wiki OAuth token file (.claude/oauth-token)
543
594
  * 2. Per-wiki OAuth credentials (.claude/.credentials.json exists)
544
- * 3. Global ANTHROPIC_API_KEY from daemon environment
595
+ * 3. Global OAuth token (from `memex setup-token`)
545
596
  */
546
597
  resolveCredentials(wikiId) {
547
598
  const claudeDir = this.configDir(wikiId);
548
- const apiKeyPath = join3(claudeDir, "api-key");
549
- if (existsSync3(apiKeyPath)) {
550
- const key = readFileSync2(apiKeyPath, "utf-8").trim();
551
- if (key) {
599
+ const tokenPath = join3(claudeDir, "oauth-token");
600
+ if (existsSync3(tokenPath)) {
601
+ const token = readFileSync2(tokenPath, "utf-8").trim();
602
+ if (token) {
552
603
  return {
553
- ANTHROPIC_API_KEY: key,
604
+ CLAUDE_CODE_OAUTH_TOKEN: token,
554
605
  CLAUDE_CONFIG_DIR: claudeDir
555
606
  };
556
607
  }
@@ -561,22 +612,38 @@ var init_auth = __esm({
561
612
  CLAUDE_CONFIG_DIR: claudeDir
562
613
  };
563
614
  }
564
- if (this.globalApiKey) {
615
+ if (this.globalOAuthToken) {
565
616
  return {
566
- ANTHROPIC_API_KEY: this.globalApiKey,
617
+ CLAUDE_CODE_OAUTH_TOKEN: this.globalOAuthToken,
567
618
  CLAUDE_CONFIG_DIR: claudeDir
568
619
  };
569
620
  }
570
621
  throw new NoCredentialsError(wikiId);
571
622
  }
572
623
  /**
573
- * Store an API key for a wiki.
624
+ * Store and activate a global OAuth token (from `claude setup-token`).
625
+ * This is the daemon-wide fallback used when a wiki has no per-wiki credentials.
626
+ */
627
+ setGlobalToken(token) {
628
+ const trimmed = token.trim();
629
+ mkdirSync3(DATA_DIR, { recursive: true });
630
+ writeFileSync2(GLOBAL_TOKEN_PATH, trimmed, { mode: 384 });
631
+ this.globalOAuthToken = trimmed;
632
+ }
633
+ /**
634
+ * Check if a global OAuth token is configured.
574
635
  */
575
- setApiKey(wikiId, key) {
636
+ hasGlobalToken() {
637
+ return !!this.globalOAuthToken;
638
+ }
639
+ /**
640
+ * Store an OAuth token for a specific wiki.
641
+ */
642
+ setWikiToken(wikiId, token) {
576
643
  const claudeDir = this.configDir(wikiId);
577
644
  mkdirSync3(claudeDir, { recursive: true, mode: 448 });
578
- const apiKeyPath = join3(claudeDir, "api-key");
579
- writeFileSync2(apiKeyPath, key.trim(), { mode: 384 });
645
+ const tokenPath = join3(claudeDir, "oauth-token");
646
+ writeFileSync2(tokenPath, token.trim(), { mode: 384 });
580
647
  }
581
648
  /**
582
649
  * Get the CLAUDE_CONFIG_DIR path for a wiki.
@@ -598,9 +665,17 @@ var init_auth = __esm({
598
665
  */
599
666
  hasCredentials(wikiId) {
600
667
  const claudeDir = this.configDir(wikiId);
601
- const apiKeyPath = join3(claudeDir, "api-key");
668
+ const tokenPath = join3(claudeDir, "oauth-token");
602
669
  const credsPath = join3(claudeDir, ".credentials.json");
603
- return existsSync3(apiKeyPath) || existsSync3(credsPath) || !!this.globalApiKey;
670
+ return existsSync3(tokenPath) || existsSync3(credsPath) || !!this.globalOAuthToken;
671
+ }
672
+ // ── Private ────────────────────────────────────────────────────────────
673
+ loadGlobalToken() {
674
+ if (existsSync3(GLOBAL_TOKEN_PATH)) {
675
+ const token = readFileSync2(GLOBAL_TOKEN_PATH, "utf-8").trim();
676
+ if (token) return token;
677
+ }
678
+ return void 0;
604
679
  }
605
680
  };
606
681
  }
@@ -627,9 +702,16 @@ Integrate these into the wiki:
627
702
  d. Maintain bidirectional links \u2014 if you link A\u2192B, update B\u2192A too.
628
703
  e. Update _index.md with current summaries for all affected pages.
629
704
  6. Append a dated ingest entry to _log.md summarizing what was ingested and what pages were affected.
705
+ 7. **Reflect on the schema.** Before finishing, re-read _schema.md and ask yourself:
706
+ - Have I established patterns that aren't documented? (e.g. bug lifecycle stages, severity conventions, required fields for a category)
707
+ - Are there recurring structures across pages in the same category? (e.g. all bugs have Status/Reporter/Impact \u2014 codify that)
708
+ - Has my filing behavior drifted from what the schema says? Update the schema to match reality.
709
+ - Are there cross-category patterns worth noting? (e.g. "bugs should always link to their owning product")
710
+ - Would a future version of me, seeing this schema for the first time, make the same decisions I just made?
711
+ The schema is your institutional memory. If you learned something from this ingest \u2014 a new convention, a refinement, a pattern \u2014 write it down. Vocabulary, heuristics, page templates, lifecycle stages, things to ignore. The schema should get richer with every ingest, not just when new categories appear.
630
712
 
631
713
  Rules:
632
- - NEVER modify files in raw/ \u2014 they are immutable sources
714
+ - NEVER modify files in raw/ \u2014 they are immutable sources (this is enforced by the filesystem)
633
715
  - Prefer updating existing pages over creating duplicates
634
716
  - If a source contradicts existing wiki content, UPDATE the existing page \u2014 resolve or flag the contradiction
635
717
  - Keep pages focused \u2014 one topic per page
@@ -771,13 +853,21 @@ IMPORTANT: You are operating in a sandboxed wiki directory. Only read and write
771
853
  - \`_schema.md\` \u2014 Filing conventions, categories, domain vocabulary, filing heuristics. YOUR institutional memory.
772
854
  - \`_index.md\` \u2014 One-line summary of every wiki page, organized by category. The table of contents.
773
855
  - \`_log.md\` \u2014 Chronological activity log. You maintain this.
774
- - \`raw/\` \u2014 Immutable source documents. NEVER modify or delete these files.
856
+ - \`raw/\` \u2014 Immutable source archive. Read-only (enforced by filesystem). Read from here, never write.
775
857
  - Everything else \u2014 Wiki pages organized by entity and topic.
776
858
 
777
859
  ## Your responsibilities
778
860
 
779
- ### 1. Schema (_schema.md)
780
- You own the schema. Update it when you establish or refine conventions \u2014 new categories, naming patterns, domain vocabulary, filing heuristics, things to ignore.
861
+ ### 1. Schema (_schema.md) \u2014 YOUR institutional memory
862
+ You own the schema. It is not a static config file \u2014 it is a living document that evolves with every ingest. The schema captures what you've learned about how THIS knowledge base works: what categories exist, how pages in each category are structured, what naming and filing patterns have emerged, what domain vocabulary matters.
863
+
864
+ Update the schema when you:
865
+ - Establish or refine category conventions (new categories, what belongs where)
866
+ - Notice structural patterns (e.g. "bugs have Status/Reporter/Impact fields", "people pages track contributions chronologically")
867
+ - Learn domain vocabulary or terminology
868
+ - Develop filing heuristics (e.g. "bug reports should always link to their owning product")
869
+ - Discover things to ignore or de-prioritize
870
+ - Notice lifecycle patterns (e.g. open \u2192 resolved, proposed \u2192 accepted)
781
871
 
782
872
  On the FIRST call for a new knowledge base (no _schema.md exists), CREATE it with the conventions you establish. Suggested starting categories (adapt to what fits):
783
873
  - customers/ \u2014 profiles and feedback per person or company
@@ -786,6 +876,8 @@ On the FIRST call for a new knowledge base (no _schema.md exists), CREATE it wit
786
876
  - research/ \u2014 deep dives and analyses
787
877
  - reference/ \u2014 factual reference material
788
878
 
879
+ The schema should get richer with experience. After 10 ingests it should contain conventions a generic prompt wouldn't know. A future session reading only the schema should understand how this knowledge base thinks.
880
+
789
881
  ### 2. Index (_index.md)
790
882
  ALWAYS keep _index.md current. Every wiki page gets a one-line summary: what it contains, how many connections, what matters most. Organize by category. A reader should understand the shape of the entire knowledge base by reading only the index.
791
883
 
@@ -904,6 +996,9 @@ var init_runner = __esm({
904
996
  if (credEnv["CLAUDE_CONFIG_DIR"]) {
905
997
  env["CLAUDE_CONFIG_DIR"] = `${WORKSPACE_MOUNT}/.claude`;
906
998
  }
999
+ if (credEnv["CLAUDE_CODE_OAUTH_TOKEN"]) {
1000
+ env["CLAUDE_CODE_OAUTH_TOKEN"] = credEnv["CLAUDE_CODE_OAUTH_TOKEN"];
1001
+ }
907
1002
  return new Promise((resolve) => {
908
1003
  const child = spawn(wrapped.command, wrapped.args, {
909
1004
  env,
@@ -1181,13 +1276,16 @@ var init_routes = __esm({
1181
1276
  { method: "DELETE", pattern: /^\/wikis\/(?<id>[^/]+)$/, handler: this.destroyWiki.bind(this) },
1182
1277
  { method: "PUT", pattern: /^\/wikis\/(?<id>[^/]+)\/config$/, handler: this.updateConfig.bind(this) },
1183
1278
  { method: "POST", pattern: /^\/wikis\/(?<id>[^/]+)\/chown$/, handler: this.chownWiki.bind(this) },
1184
- { method: "POST", pattern: /^\/wikis\/(?<id>[^/]+)\/api-key$/, handler: this.setApiKey.bind(this) },
1279
+ { method: "POST", pattern: /^\/auth\/token$/, handler: this.setGlobalToken.bind(this) },
1280
+ { method: "POST", pattern: /^\/wikis\/(?<id>[^/]+)\/token$/, handler: this.setWikiToken.bind(this) },
1185
1281
  { method: "POST", pattern: /^\/wikis\/(?<id>[^/]+)\/credentials$/, handler: this.setCredentials.bind(this) },
1186
1282
  { method: "POST", pattern: /^\/wikis\/(?<id>[^/]+)\/jobs$/, handler: this.submitJob.bind(this) },
1187
1283
  { method: "GET", pattern: /^\/wikis\/(?<id>[^/]+)\/jobs\/(?<jobId>\d+)$/, handler: this.getJob.bind(this) },
1188
1284
  { method: "GET", pattern: /^\/wikis\/(?<id>[^/]+)\/jobs$/, handler: this.listJobs.bind(this) },
1189
1285
  { method: "GET", pattern: /^\/wikis\/(?<id>[^/]+)\/logs$/, handler: this.getAuditLog.bind(this) },
1190
- { method: "POST", pattern: /^\/wikis\/(?<id>[^/]+)\/ingest-file$/, handler: this.receiveFile.bind(this) }
1286
+ { method: "POST", pattern: /^\/wikis\/(?<id>[^/]+)\/ingest-file$/, handler: this.receiveFile.bind(this) },
1287
+ { method: "GET", pattern: /^\/wikis\/(?<id>[^/]+)\/files$/, handler: this.listFiles.bind(this) },
1288
+ { method: "GET", pattern: /^\/wikis\/(?<id>[^/]+)\/files\/(?<path>.+)$/, handler: this.readFile.bind(this) }
1191
1289
  ];
1192
1290
  }
1193
1291
  db;
@@ -1213,7 +1311,7 @@ var init_routes = __esm({
1213
1311
  const name = b.name;
1214
1312
  if (!id || !WIKI_ID_PATTERN.test(id)) {
1215
1313
  throw new ValidationError(
1216
- `Invalid wiki ID '${id}'. Must be 3-64 chars, lowercase alphanumeric and hyphens, cannot start or end with a hyphen.`
1314
+ `Invalid wiki ID '${id}'. Must be 3-64 chars, alphanumeric with hyphens and underscores, cannot start or end with a hyphen or underscore.`
1217
1315
  );
1218
1316
  }
1219
1317
  if (this.db.getWiki(id)) {
@@ -1289,16 +1387,31 @@ var init_routes = __esm({
1289
1387
  return { status: 200, body: { ok: true, data: wiki } };
1290
1388
  }
1291
1389
  // ── Auth ─────────────────────────────────────────────────────────────────
1292
- async setApiKey(params, body, ctx) {
1390
+ async setGlobalToken(_params, body, _ctx) {
1391
+ const b = requireBody(body);
1392
+ const token = b.token;
1393
+ if (!token || typeof token !== "string") {
1394
+ throw new ValidationError("token (string) is required");
1395
+ }
1396
+ if (!token.startsWith("sk-ant-oat01-")) {
1397
+ throw new ValidationError("Token must be a Claude OAuth token (sk-ant-oat01-...)");
1398
+ }
1399
+ this.auth.setGlobalToken(token);
1400
+ return { status: 200, body: { ok: true } };
1401
+ }
1402
+ async setWikiToken(params, body, ctx) {
1293
1403
  const wikiId = params["id"];
1294
1404
  this.requireWiki(wikiId, ctx.callerUid);
1295
1405
  const b = requireBody(body);
1296
- const key = b.key;
1297
- if (!key || typeof key !== "string") {
1298
- throw new ValidationError("API key is required");
1406
+ const token = b.token;
1407
+ if (!token || typeof token !== "string") {
1408
+ throw new ValidationError("token (string) is required");
1299
1409
  }
1300
- this.auth.setApiKey(wikiId, key);
1301
- this.db.logAudit(wikiId, "wiki.api_key_set");
1410
+ if (!token.startsWith("sk-ant-oat01-")) {
1411
+ throw new ValidationError("Token must be a Claude OAuth token (sk-ant-oat01-...)");
1412
+ }
1413
+ this.auth.setWikiToken(wikiId, token);
1414
+ this.db.logAudit(wikiId, "wiki.oauth_token_set");
1302
1415
  return { status: 200, body: { ok: true } };
1303
1416
  }
1304
1417
  async setCredentials(params, body, ctx) {
@@ -1372,6 +1485,21 @@ var init_routes = __esm({
1372
1485
  const stored = this.scaffold.writeRawFile(wikiId, filename, buffer);
1373
1486
  return { status: 201, body: { ok: true, data: { filename: stored } } };
1374
1487
  }
1488
+ // ── File browsing ────────────────────────────────────────────────────
1489
+ async listFiles(params, body, ctx) {
1490
+ const wikiId = params["id"];
1491
+ this.requireWiki(wikiId, ctx.callerUid);
1492
+ const prefix = ctx.query?.get("prefix") ?? "";
1493
+ const files = this.scaffold.listWikiFiles(wikiId, prefix);
1494
+ return { status: 200, body: { ok: true, data: files } };
1495
+ }
1496
+ async readFile(params, body, ctx) {
1497
+ const wikiId = params["id"];
1498
+ this.requireWiki(wikiId, ctx.callerUid);
1499
+ const filePath = params["path"];
1500
+ const content = this.scaffold.readWikiFile(wikiId, filePath);
1501
+ return { status: 200, body: { ok: true, data: { path: filePath, content } } };
1502
+ }
1375
1503
  // ── Helpers ──────────────────────────────────────────────────────────────
1376
1504
  requireWiki(wikiId, callerUid) {
1377
1505
  const wiki = this.db.getWiki(wikiId);
@@ -1533,7 +1661,8 @@ var init_server = __esm({
1533
1661
  wait,
1534
1662
  res,
1535
1663
  // pass response for streaming login output
1536
- callerUid
1664
+ callerUid,
1665
+ query: urlObj.searchParams
1537
1666
  });
1538
1667
  if (res.writableEnded) return;
1539
1668
  sendJson(res, result.status, result.body);
@@ -1577,7 +1706,7 @@ async function startDaemon() {
1577
1706
  namespace.checkCapabilities();
1578
1707
  namespace.ensureDirectories();
1579
1708
  const scaffold = new WikiScaffold(WIKIS_DIR);
1580
- const auth = new AuthManager(WIKIS_DIR, process.env["ANTHROPIC_API_KEY"]);
1709
+ const auth = new AuthManager(WIKIS_DIR);
1581
1710
  const runner = new ClaudeRunner(namespace, auth, db, WIKIS_DIR);
1582
1711
  const queue = new QueueManager(db, runner, AUTO_LINT_INTERVAL);
1583
1712
  const routes = new RouteHandler(db, scaffold, namespace, queue, auth);
@@ -1617,7 +1746,7 @@ var init_daemon = __esm({
1617
1746
  });
1618
1747
 
1619
1748
  // src/index.ts
1620
- import { Command as Command13 } from "commander";
1749
+ import { Command as Command14 } from "commander";
1621
1750
 
1622
1751
  // src/cli/commands/serve.ts
1623
1752
  import { Command } from "commander";
@@ -1762,8 +1891,11 @@ var MemexClient = class {
1762
1891
  chownWiki(wikiId, uid) {
1763
1892
  return this.request("POST", `/wikis/${wikiId}/chown`, { uid });
1764
1893
  }
1765
- setApiKey(wikiId, key) {
1766
- return this.request("POST", `/wikis/${wikiId}/api-key`, { key });
1894
+ setGlobalToken(token) {
1895
+ return this.request("POST", "/auth/token", { token });
1896
+ }
1897
+ setWikiToken(wikiId, token) {
1898
+ return this.request("POST", `/wikis/${wikiId}/token`, { token });
1767
1899
  }
1768
1900
  setCredentials(wikiId, credentials) {
1769
1901
  return this.request("POST", `/wikis/${wikiId}/credentials`, { credentials });
@@ -1778,6 +1910,13 @@ var MemexClient = class {
1778
1910
  listJobs(wikiId) {
1779
1911
  return this.request("GET", `/wikis/${wikiId}/jobs`);
1780
1912
  }
1913
+ listFiles(wikiId, prefix) {
1914
+ const path = `/wikis/${wikiId}/files` + (prefix ? `?prefix=${encodeURIComponent(prefix)}` : "");
1915
+ return this.request("GET", path);
1916
+ }
1917
+ readFile(wikiId, filePath) {
1918
+ return this.request("GET", `/wikis/${wikiId}/files/${filePath}`);
1919
+ }
1781
1920
  getAuditLog(wikiId, limit) {
1782
1921
  const path = `/wikis/${wikiId}/logs` + (limit ? `?limit=${limit}` : "");
1783
1922
  return this.request("GET", path);
@@ -1869,7 +2008,7 @@ import { createInterface as createInterface2 } from "node:readline";
1869
2008
  import { execFileSync as execFileSync2 } from "node:child_process";
1870
2009
  init_constants();
1871
2010
  import { join as join5 } from "node:path";
1872
- var configCommand = new Command4("config").description("Configure a wiki").argument("<wikiId>", "Wiki to configure").option("--edit", "Open .claude.md in $EDITOR").option("--set-key", "Set the API key for this wiki").option("--model <model>", "Set the default model (e.g., sonnet, opus, haiku)").option("--allowed-tools <tools>", "Set allowed tools (comma-separated, e.g., WebSearch,WebFetch)").option("--list-tools", "Show available tools and current configuration").action(async (wikiId, opts) => {
2011
+ var configCommand = new Command4("config").description("Configure a wiki").argument("<wikiId>", "Wiki to configure").option("--edit", "Open .claude.md in $EDITOR").option("--set-token", "Set an OAuth token for this wiki (from `claude setup-token`)").option("--model <model>", "Set the default model (e.g., sonnet, opus, haiku)").option("--allowed-tools <tools>", "Set allowed tools (comma-separated, e.g., WebSearch,WebFetch)").option("--list-tools", "Show available tools and current configuration").action(async (wikiId, opts) => {
1873
2012
  const client = new MemexClient();
1874
2013
  const wikiResp = await client.getWiki(wikiId);
1875
2014
  if (!wikiResp.ok) {
@@ -1888,14 +2027,20 @@ var configCommand = new Command4("config").description("Configure a wiki").argum
1888
2027
  }
1889
2028
  return;
1890
2029
  }
1891
- if (opts.setKey) {
1892
- const key = await promptSecret("API key: ");
1893
- const resp = await client.setApiKey(wikiId, key);
2030
+ if (opts.setToken) {
2031
+ const token = await promptSecret("OAuth token (from `claude setup-token`): ");
2032
+ if (!token.startsWith("sk-ant-oat01-")) {
2033
+ console.error(`Error: Token must start with 'sk-ant-oat01-'.`);
2034
+ console.error(`
2035
+ Generate one by running: claude setup-token`);
2036
+ process.exit(1);
2037
+ }
2038
+ const resp = await client.setWikiToken(wikiId, token);
1894
2039
  if (!resp.ok) {
1895
2040
  console.error(`Error: ${resp.error}`);
1896
2041
  process.exit(1);
1897
2042
  }
1898
- console.log(`API key set for '${wikiId}'`);
2043
+ console.log(`OAuth token set for '${wikiId}'`);
1899
2044
  return;
1900
2045
  }
1901
2046
  if (opts.listTools) {
@@ -1981,27 +2126,36 @@ import { readFileSync as readFileSync5, existsSync as existsSync6 } from "node:f
1981
2126
  import { join as join6 } from "node:path";
1982
2127
  import { homedir } from "node:os";
1983
2128
  var DEFAULT_CREDS_PATH = join6(homedir(), ".claude", ".credentials.json");
1984
- var loginCommand = new Command5("login").description("Copy Claude credentials into a wiki").argument("<wikiId>", "Wiki to authenticate").option("--credentials <path>", "Path to .credentials.json", DEFAULT_CREDS_PATH).option("--api-key <key>", "Use an API key instead of OAuth credentials").action(async (wikiId, opts) => {
2129
+ var loginCommand = new Command5("login").description("Copy Claude credentials into a wiki").argument("<wikiId>", "Wiki to authenticate").option("--credentials <path>", "Path to .credentials.json", DEFAULT_CREDS_PATH).option("--token <token>", "Use an OAuth token from `claude setup-token`").action(async (wikiId, opts) => {
1985
2130
  const client = new MemexClient();
1986
2131
  const wikiResp = await client.getWiki(wikiId);
1987
2132
  if (!wikiResp.ok) {
1988
2133
  console.error(`Error: ${wikiResp.error}`);
1989
2134
  process.exit(1);
1990
2135
  }
1991
- if (opts.apiKey) {
1992
- const resp2 = await client.setApiKey(wikiId, opts.apiKey);
2136
+ if (opts.token) {
2137
+ if (!opts.token.startsWith("sk-ant-oat01-")) {
2138
+ console.error(`Error: Token must start with 'sk-ant-oat01-'.`);
2139
+ console.error(`
2140
+ Generate one by running: claude setup-token`);
2141
+ process.exit(1);
2142
+ }
2143
+ const resp2 = await client.setWikiToken(wikiId, opts.token);
1993
2144
  if (!resp2.ok) {
1994
2145
  console.error(`Error: ${resp2.error}`);
1995
2146
  process.exit(1);
1996
2147
  }
1997
- console.log(`API key stored for wiki '${wikiId}'.`);
2148
+ console.log(`OAuth token stored for wiki '${wikiId}'.`);
1998
2149
  return;
1999
2150
  }
2000
2151
  const credsPath = opts.credentials;
2001
2152
  if (!existsSync6(credsPath)) {
2002
2153
  console.error(`Error: Credentials file not found: ${credsPath}`);
2003
2154
  console.error(`
2004
- Run 'claude auth login' first, or pass --credentials <path>`);
2155
+ Options:`);
2156
+ console.error(` 1. Run 'claude auth login' first, then re-run this command`);
2157
+ console.error(` 2. Run 'claude setup-token' and use: memex login ${wikiId} --token <token>`);
2158
+ console.error(` 3. Set globally: memex setup-token <token>`);
2005
2159
  process.exit(1);
2006
2160
  }
2007
2161
  const credentials = readFileSync5(credsPath, "utf-8");
@@ -2291,8 +2445,42 @@ function padRow2(id, type, status, created) {
2291
2445
  return `${id.padEnd(8)} ${type.padEnd(10)} ${status.padEnd(12)} ${created}`;
2292
2446
  }
2293
2447
 
2448
+ // src/cli/commands/setup-token.ts
2449
+ import { Command as Command13 } from "commander";
2450
+ var TOKEN_PREFIX = "sk-ant-oat01-";
2451
+ var setupTokenCommand = new Command13("setup-token").description("Store a long-lived OAuth token from `claude setup-token` for all wikis").argument("<token>", "OAuth token (sk-ant-oat01-...)").option("--wiki <wikiId>", "Store for a specific wiki only (instead of globally)").action(async (token, opts) => {
2452
+ if (!token.startsWith(TOKEN_PREFIX)) {
2453
+ console.error(`Error: Token must start with '${TOKEN_PREFIX}'.`);
2454
+ console.error(`
2455
+ Generate one by running: claude setup-token`);
2456
+ process.exit(1);
2457
+ }
2458
+ const client = new MemexClient();
2459
+ if (opts.wiki) {
2460
+ const wikiResp = await client.getWiki(opts.wiki);
2461
+ if (!wikiResp.ok) {
2462
+ console.error(`Error: ${wikiResp.error}`);
2463
+ process.exit(1);
2464
+ }
2465
+ const resp = await client.setWikiToken(opts.wiki, token);
2466
+ if (!resp.ok) {
2467
+ console.error(`Error: ${resp.error}`);
2468
+ process.exit(1);
2469
+ }
2470
+ console.log(`OAuth token stored for wiki '${opts.wiki}'.`);
2471
+ } else {
2472
+ const resp = await client.setGlobalToken(token);
2473
+ if (!resp.ok) {
2474
+ console.error(`Error: ${resp.error}`);
2475
+ process.exit(1);
2476
+ }
2477
+ console.log("Global OAuth token stored.");
2478
+ console.log("All wikis without per-wiki credentials will use this token.");
2479
+ }
2480
+ });
2481
+
2294
2482
  // src/index.ts
2295
- var program = new Command13();
2483
+ var program = new Command14();
2296
2484
  program.name("memex").description("Isolated, queued claude -p runtime for persistent knowledge bases").version("0.1.0");
2297
2485
  program.addCommand(serveCommand);
2298
2486
  program.addCommand(createCommand);
@@ -2306,6 +2494,7 @@ program.addCommand(logsCommand);
2306
2494
  program.addCommand(listCommand);
2307
2495
  program.addCommand(chownCommand);
2308
2496
  program.addCommand(statusCommand);
2497
+ program.addCommand(setupTokenCommand);
2309
2498
  program.parseAsync().catch((err) => {
2310
2499
  console.error(err.message ?? err);
2311
2500
  process.exit(1);