@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.
- package/README.md +99 -183
- package/dist/cli/client.d.ts +10 -1
- package/dist/cli/client.js +12 -2
- package/dist/cli/client.js.map +1 -1
- package/dist/cli/commands/config.js +10 -5
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/login.js +14 -6
- package/dist/cli/commands/login.js.map +1 -1
- package/dist/cli/commands/setup-token.d.ts +2 -0
- package/dist/cli/commands/setup-token.js +41 -0
- package/dist/cli/commands/setup-token.js.map +1 -0
- package/dist/daemon/auth.d.ts +16 -6
- package/dist/daemon/auth.js +50 -22
- package/dist/daemon/auth.js.map +1 -1
- package/dist/daemon/namespace.js +8 -2
- package/dist/daemon/namespace.js.map +1 -1
- package/dist/daemon/routes.d.ts +5 -1
- package/dist/daemon/routes.js +42 -9
- package/dist/daemon/routes.js.map +1 -1
- package/dist/daemon/runner.js +4 -0
- package/dist/daemon/runner.js.map +1 -1
- package/dist/daemon/scaffold.d.ts +14 -0
- package/dist/daemon/scaffold.js +49 -1
- package/dist/daemon/scaffold.js.map +1 -1
- package/dist/daemon/server.js +1 -0
- package/dist/daemon/server.js.map +1 -1
- package/dist/daemon.js +1 -1
- package/dist/daemon.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/constants.js +1 -1
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/errors.js +4 -2
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/prompts/ingest.js +8 -1
- package/dist/lib/prompts/ingest.js.map +1 -1
- package/dist/lib/prompts/wiki.js +13 -3
- package/dist/lib/prompts/wiki.js.map +1 -1
- package/dist/standalone/memex.mjs +241 -52
- package/dist/standalone/memex.mjs.map +4 -4
- 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
|
|
257
|
-
memex
|
|
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-
|
|
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
|
|
582
|
+
constructor(wikisDir = WIKIS_DIR) {
|
|
532
583
|
this.wikisDir = wikisDir;
|
|
533
|
-
this.
|
|
584
|
+
this.globalOAuthToken = this.loadGlobalToken();
|
|
534
585
|
}
|
|
535
586
|
wikisDir;
|
|
536
|
-
|
|
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
|
|
593
|
+
* 1. Per-wiki OAuth token file (.claude/oauth-token)
|
|
543
594
|
* 2. Per-wiki OAuth credentials (.claude/.credentials.json exists)
|
|
544
|
-
* 3. Global
|
|
595
|
+
* 3. Global OAuth token (from `memex setup-token`)
|
|
545
596
|
*/
|
|
546
597
|
resolveCredentials(wikiId) {
|
|
547
598
|
const claudeDir = this.configDir(wikiId);
|
|
548
|
-
const
|
|
549
|
-
if (existsSync3(
|
|
550
|
-
const
|
|
551
|
-
if (
|
|
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
|
-
|
|
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.
|
|
615
|
+
if (this.globalOAuthToken) {
|
|
565
616
|
return {
|
|
566
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
579
|
-
writeFileSync2(
|
|
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
|
|
668
|
+
const tokenPath = join3(claudeDir, "oauth-token");
|
|
602
669
|
const credsPath = join3(claudeDir, ".credentials.json");
|
|
603
|
-
return existsSync3(
|
|
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
|
|
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.
|
|
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: /^\/
|
|
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,
|
|
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
|
|
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
|
|
1297
|
-
if (!
|
|
1298
|
-
throw new ValidationError("
|
|
1406
|
+
const token = b.token;
|
|
1407
|
+
if (!token || typeof token !== "string") {
|
|
1408
|
+
throw new ValidationError("token (string) is required");
|
|
1299
1409
|
}
|
|
1300
|
-
|
|
1301
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
1766
|
-
return this.request("POST",
|
|
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-
|
|
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.
|
|
1892
|
-
const
|
|
1893
|
-
|
|
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(`
|
|
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("--
|
|
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.
|
|
1992
|
-
|
|
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(`
|
|
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
|
-
|
|
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
|
|
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);
|