wikimemory 0.2.10 → 0.2.12
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 +18 -5
- package/dist/npm-cli/scripts/browse.js +27 -0
- package/dist/npm-cli/scripts/cli.js +12 -1
- package/dist/npm-cli/scripts/client-tools.js +72 -10
- package/dist/npm-cli/scripts/passkeys.js +1 -9
- package/dist/npm-cli/scripts/setup.js +1 -1
- package/dist/npm-cli/scripts/status.js +3 -1
- package/dist/npm-cli/src/version.js +1 -1
- package/package.json +2 -2
- package/release-manifest.json +1 -1
- package/src/mcp/server.ts +4 -1
- package/src/version.ts +1 -1
package/README.md
CHANGED
|
@@ -44,8 +44,9 @@ npx wikimemory install
|
|
|
44
44
|
|
|
45
45
|
The installer previews the exact Cloudflare account, Worker, D1 database
|
|
46
46
|
(Cloudflare's managed SQLite), and OAuth/session store before creating anything.
|
|
47
|
-
Open its one-time URL to register the owner passkey
|
|
48
|
-
|
|
47
|
+
Open its one-time URL to register the owner passkey. The installer also prints the
|
|
48
|
+
exact `/mcp` URL to paste into clients that require manual connector setup. Then
|
|
49
|
+
connect a command-line client and install its memory skills:
|
|
49
50
|
|
|
50
51
|
```sh
|
|
51
52
|
npx wikimemory connect codex
|
|
@@ -56,9 +57,20 @@ npx wikimemory connect claude
|
|
|
56
57
|
npx wikimemory skills install claude
|
|
57
58
|
```
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
To use Wikimemory on a phone, first add the HTTPS `/mcp` URL printed by the installer
|
|
61
|
+
or by `npx wikimemory status`:
|
|
62
|
+
|
|
63
|
+
Use the complete URL, including `/mcp`. The deployment root opens the Wikimemory web
|
|
64
|
+
application and is not an MCP endpoint.
|
|
65
|
+
|
|
66
|
+
- **Claude:** add it as a custom connector in Claude's web settings. It then appears
|
|
67
|
+
in Claude mobile.
|
|
68
|
+
- **ChatGPT:** enable developer mode on ChatGPT web, create an app using the `/mcp`
|
|
69
|
+
URL under **Settings → Plugins**, and finish passkey authorization. The app then
|
|
70
|
+
appears in ChatGPT mobile.
|
|
71
|
+
|
|
72
|
+
See the complete [installation guide](docs/installation.md) for detailed connection
|
|
73
|
+
steps, recovery, passkey management, upgrades, and safe uninstall.
|
|
62
74
|
|
|
63
75
|
## Run locally
|
|
64
76
|
|
|
@@ -75,6 +87,7 @@ and MCP transport remain real.
|
|
|
75
87
|
|
|
76
88
|
```sh
|
|
77
89
|
npx wikimemory status
|
|
90
|
+
npx wikimemory browse
|
|
78
91
|
npx wikimemory upgrade
|
|
79
92
|
npx wikimemory recover
|
|
80
93
|
npx wikimemory passkeys list
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { deploymentRecordPath, readDeploymentRecord } from "./deployment-record.js";
|
|
4
|
+
export function webAppUrl(origin) {
|
|
5
|
+
return new URL("/", origin).toString();
|
|
6
|
+
}
|
|
7
|
+
export function browserCommand(url, platform = process.platform) {
|
|
8
|
+
if (platform === "darwin")
|
|
9
|
+
return { executable: "open", args: [url] };
|
|
10
|
+
if (platform === "win32")
|
|
11
|
+
return { executable: "rundll32", args: ["url.dll,FileProtocolHandler", url] };
|
|
12
|
+
return { executable: "xdg-open", args: [url] };
|
|
13
|
+
}
|
|
14
|
+
export function openBrowser(url) {
|
|
15
|
+
const invocation = browserCommand(url);
|
|
16
|
+
const child = spawn(invocation.executable, invocation.args, {
|
|
17
|
+
detached: true,
|
|
18
|
+
stdio: "ignore"
|
|
19
|
+
});
|
|
20
|
+
child.unref();
|
|
21
|
+
}
|
|
22
|
+
export async function runBrowse(deployment) {
|
|
23
|
+
const record = await readDeploymentRecord(deploymentRecordPath(deployment));
|
|
24
|
+
const url = webAppUrl(record.origin);
|
|
25
|
+
openBrowser(url);
|
|
26
|
+
console.log(`Opened Wikimemory web app:\n${url}`);
|
|
27
|
+
}
|
|
@@ -8,7 +8,7 @@ import { packageRoot } from "./package-root.js";
|
|
|
8
8
|
import { conciseError } from "./subprocess.js";
|
|
9
9
|
const [command, ...args] = process.argv.slice(2);
|
|
10
10
|
function usage() {
|
|
11
|
-
return `Wikimemory ${WIKIMEMORY_VERSION}\nPersonal, passkey-protected memory for Claude, Codex, and other MCP clients.\n\nUsage: wikimemory COMMAND [OPTIONS]\n\nStart a personal Cloudflare-hosted instance:\n npx wikimemory install\n\nTry it locally without a Cloudflare deployment:\n npx wikimemory dev\n\nCommands:\n wikimemory install [--deployment NAME] [installer options]\n wikimemory recover [--deployment NAME]\n wikimemory dev [wrangler dev options]\n wikimemory status [--deployment NAME]\n wikimemory upgrade [--deployment NAME] [--yes]\n wikimemory passkeys [--deployment NAME] list|add|revoke\n wikimemory connect [--deployment NAME] codex|claude\n wikimemory skills install codex|claude\n wikimemory uninstall [--deployment NAME] [--apply]\n wikimemory --version\n\nUse \`wikimemory COMMAND --help\` for command-specific options.`;
|
|
11
|
+
return `Wikimemory ${WIKIMEMORY_VERSION}\nPersonal, passkey-protected memory for Claude, Codex, and other MCP clients.\n\nUsage: wikimemory COMMAND [OPTIONS]\n\nStart a personal Cloudflare-hosted instance:\n npx wikimemory install\n\nTry it locally without a Cloudflare deployment:\n npx wikimemory dev\n\nCommands:\n wikimemory install [--deployment NAME] [installer options]\n wikimemory recover [--deployment NAME]\n wikimemory dev [wrangler dev options]\n wikimemory status [--deployment NAME]\n wikimemory browse [--deployment NAME]\n wikimemory upgrade [--deployment NAME] [--yes]\n wikimemory passkeys [--deployment NAME] list|add|revoke\n wikimemory connect [--deployment NAME] codex|claude\n wikimemory skills install codex|claude\n wikimemory uninstall [--deployment NAME] [--apply]\n wikimemory --version\n\nUse \`wikimemory COMMAND --help\` for command-specific options.`;
|
|
12
12
|
}
|
|
13
13
|
async function main() {
|
|
14
14
|
if (command === undefined || command === "--help" || command === "help") {
|
|
@@ -37,6 +37,7 @@ async function main() {
|
|
|
37
37
|
const requiresInstalledDeployment = !parsed.remaining.includes("--help") &&
|
|
38
38
|
(command === "recover" ||
|
|
39
39
|
command === "status" ||
|
|
40
|
+
command === "browse" ||
|
|
40
41
|
command === "passkeys" ||
|
|
41
42
|
command === "connect" ||
|
|
42
43
|
command === "uninstall" ||
|
|
@@ -66,6 +67,16 @@ async function main() {
|
|
|
66
67
|
const { runStatus } = await import("./status.js");
|
|
67
68
|
await runStatus(parsed.deployment);
|
|
68
69
|
}
|
|
70
|
+
else if (command === "browse") {
|
|
71
|
+
if (parsed.remaining.length === 1 && parsed.remaining[0] === "--help") {
|
|
72
|
+
console.log("Usage: wikimemory browse [--deployment NAME]");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (parsed.remaining.length !== 0)
|
|
76
|
+
throw new Error("Usage: wikimemory browse [--deployment NAME]");
|
|
77
|
+
const { runBrowse } = await import("./browse.js");
|
|
78
|
+
await runBrowse(parsed.deployment);
|
|
79
|
+
}
|
|
69
80
|
else if (command === "passkeys") {
|
|
70
81
|
process.env["WIKIMEMORY_STATE_DIR"] = paths.directory;
|
|
71
82
|
process.env["WIKIMEMORY_PACKAGED"] = "1";
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import { cp, mkdir } from "node:fs/promises";
|
|
1
|
+
import { cp, mkdir, mkdtemp, rename, rm } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { deploymentRecordPath, readDeploymentRecord } from "./deployment-record.js";
|
|
5
5
|
import { commandFailureMessage, runCommand } from "./subprocess.js";
|
|
6
|
+
const SKILL_NAMES = [
|
|
7
|
+
"wikimemory-recall",
|
|
8
|
+
"wikimemory-ingest",
|
|
9
|
+
"wikimemory-lint",
|
|
10
|
+
"wikimemory-install"
|
|
11
|
+
];
|
|
6
12
|
function client(value) {
|
|
7
13
|
if (value === "codex" || value === "claude")
|
|
8
14
|
return value;
|
|
@@ -15,6 +21,69 @@ async function run(command, args, interactive = false) {
|
|
|
15
21
|
if (result.exitCode !== 0)
|
|
16
22
|
throw new Error(commandFailureMessage(`${command} client configuration`, result));
|
|
17
23
|
}
|
|
24
|
+
function errorCode(error) {
|
|
25
|
+
if (typeof error !== "object" || error === null)
|
|
26
|
+
return undefined;
|
|
27
|
+
return Reflect.get(error, "code");
|
|
28
|
+
}
|
|
29
|
+
async function moveIfPresent(source, destination) {
|
|
30
|
+
try {
|
|
31
|
+
await rename(source, destination);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (errorCode(error) === "ENOENT")
|
|
36
|
+
return false;
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export async function replaceSkillDirectories(packageRoot, destinationRoot, names) {
|
|
41
|
+
await mkdir(destinationRoot, { recursive: true, mode: 0o700 });
|
|
42
|
+
const stagingRoot = await mkdtemp(join(destinationRoot, ".wikimemory-install-"));
|
|
43
|
+
const incomingRoot = join(stagingRoot, "incoming");
|
|
44
|
+
const replacedRoot = join(stagingRoot, "replaced");
|
|
45
|
+
const installed = [];
|
|
46
|
+
try {
|
|
47
|
+
await mkdir(incomingRoot);
|
|
48
|
+
await mkdir(replacedRoot);
|
|
49
|
+
for (const name of names) {
|
|
50
|
+
if (!/^wikimemory-[a-z0-9-]+$/u.test(name))
|
|
51
|
+
throw new Error(`Invalid skill name: ${name}`);
|
|
52
|
+
await cp(join(packageRoot, "skills", name), join(incomingRoot, name), {
|
|
53
|
+
recursive: true,
|
|
54
|
+
force: false,
|
|
55
|
+
errorOnExist: true
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
for (const name of names) {
|
|
60
|
+
const destination = join(destinationRoot, name);
|
|
61
|
+
const backup = join(replacedRoot, name);
|
|
62
|
+
const hadExisting = await moveIfPresent(destination, backup);
|
|
63
|
+
try {
|
|
64
|
+
await rename(join(incomingRoot, name), destination);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (hadExisting)
|
|
68
|
+
await rename(backup, destination);
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
installed.push({ destination, backup, hadExisting });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
for (const item of installed.reverse()) {
|
|
76
|
+
await rm(item.destination, { recursive: true, force: true });
|
|
77
|
+
if (item.hadExisting)
|
|
78
|
+
await rename(item.backup, item.destination);
|
|
79
|
+
}
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
await rm(stagingRoot, { recursive: true, force: true });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
18
87
|
export async function connectClient(deployment, selected) {
|
|
19
88
|
const target = client(selected);
|
|
20
89
|
const record = await readDeploymentRecord(deploymentRecordPath(deployment));
|
|
@@ -43,13 +112,6 @@ export async function connectClient(deployment, selected) {
|
|
|
43
112
|
export async function installSkills(packageRoot, selected) {
|
|
44
113
|
const target = client(selected);
|
|
45
114
|
const destinationRoot = join(homedir(), target === "codex" ? ".codex" : ".claude", "skills");
|
|
46
|
-
await
|
|
47
|
-
|
|
48
|
-
for (const name of names) {
|
|
49
|
-
await cp(join(packageRoot, "skills", name), join(destinationRoot, name), {
|
|
50
|
-
recursive: true,
|
|
51
|
-
force: true
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
console.log(`Installed ${names.length} Wikimemory skills for ${target}. Restart the client.`);
|
|
115
|
+
await replaceSkillDirectories(packageRoot, destinationRoot, SKILL_NAMES);
|
|
116
|
+
console.log(`Installed ${SKILL_NAMES.length} Wikimemory skills for ${target}. Restart the client.`);
|
|
55
117
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
2
|
import { readFile, writeFile } from "node:fs/promises";
|
|
4
3
|
import { createServer } from "node:http";
|
|
5
|
-
import process from "node:process";
|
|
6
4
|
import { pathToFileURL } from "node:url";
|
|
7
5
|
import { z } from "zod";
|
|
6
|
+
import { openBrowser } from "./browse.js";
|
|
8
7
|
import { passkeyRuntime } from "./lifecycle-runtime.js";
|
|
9
8
|
import { configuredOrigin } from "./setup.js";
|
|
10
9
|
import { conciseError } from "./subprocess.js";
|
|
@@ -71,13 +70,6 @@ async function clientId(origin) {
|
|
|
71
70
|
});
|
|
72
71
|
return registered.client_id;
|
|
73
72
|
}
|
|
74
|
-
function openBrowser(url) {
|
|
75
|
-
const platform = process.platform;
|
|
76
|
-
const executable = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
|
|
77
|
-
const args = platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
78
|
-
const child = spawn(executable, args, { detached: true, stdio: "ignore" });
|
|
79
|
-
child.unref();
|
|
80
|
-
}
|
|
81
73
|
async function authorizationCode(authorizeUrl, expectedState) {
|
|
82
74
|
return await new Promise((resolve, reject) => {
|
|
83
75
|
const server = createServer((request, response) => {
|
|
@@ -348,7 +348,7 @@ export async function verifyEndpoint(origin, path, expectedStatus, fetcher = fet
|
|
|
348
348
|
}
|
|
349
349
|
export function handoff(origin, rawToken, deploymentName) {
|
|
350
350
|
const endpoint = `${origin}/mcp`;
|
|
351
|
-
return `Wikimemory is ready for owner setup.\n\nOpen this one-time URL on a device that can create a passkey:\n${origin}/setup#${encodeURIComponent(rawToken)}\n\nAfter setup, connect clients with:\n npx wikimemory connect --deployment ${deploymentName} codex\n npx wikimemory connect --deployment ${deploymentName} claude\n\
|
|
351
|
+
return `Wikimemory is ready for owner setup.\n\nOpen this one-time URL on a device that can create a passkey:\n${origin}/setup#${encodeURIComponent(rawToken)}\n\nAfter setup, connect clients with:\n npx wikimemory connect --deployment ${deploymentName} codex\n npx wikimemory connect --deployment ${deploymentName} claude\n\nManual connector: paste this exact URL into Claude, ChatGPT, or another MCP client:\n${endpoint}\nThe setup token was not written to disk. This is the only time it will be printed.`;
|
|
352
352
|
}
|
|
353
353
|
async function remoteWorkerExists(workerName) {
|
|
354
354
|
const result = await run("npx", ["wrangler", "deployments", "list", "--name", workerName, "--json", "--config", CONFIG_PATH], undefined, true);
|
|
@@ -49,8 +49,10 @@ export async function runStatus(deployment) {
|
|
|
49
49
|
console.log(statusSummary(status));
|
|
50
50
|
}
|
|
51
51
|
export function statusSummary(status) {
|
|
52
|
+
const webApp = new URL("/", status.origin).toString();
|
|
53
|
+
const mcpEndpoint = new URL("/mcp", status.origin).toString();
|
|
52
54
|
const mismatch = status.recordedVersion === status.runningVersion
|
|
53
55
|
? ""
|
|
54
56
|
: `\nLocal record: ${status.recordedVersion} (run wikimemory upgrade to reconcile)`;
|
|
55
|
-
return `Wikimemory ${status.deployment}: ready\
|
|
57
|
+
return `Wikimemory ${status.deployment}: ready\nWeb app: ${webApp}\nMCP endpoint: ${mcpEndpoint}\nVersion: ${status.runningVersion}\nDatabase: up to date.${mismatch}`;
|
|
56
58
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const WIKIMEMORY_VERSION = "0.2.
|
|
1
|
+
export const WIKIMEMORY_VERSION = "0.2.12";
|
|
2
2
|
export const LATEST_SCHEMA_VERSION = "0004_credential_bound_registration_tokens.sql";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wikimemory",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12",
|
|
4
4
|
"description": "Personal, passkey-protected memory for Claude, Codex, and other MCP clients",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"deploy": "wrangler deploy --config wrangler.production.jsonc",
|
|
44
44
|
"test": "vitest run && npm run test:installer",
|
|
45
45
|
"test:coverage": "vitest run --coverage.enabled",
|
|
46
|
-
"test:installer": "node --experimental-strip-types --experimental-test-coverage --test-coverage-include=scripts/setup.ts --test-coverage-include=scripts/uninstall.ts --test-coverage-lines=50 --test-coverage-branches=70 --test-coverage-functions=60 --test scripts/setup.node-test.ts scripts/upgrade.node-test.ts",
|
|
46
|
+
"test:installer": "node --experimental-strip-types --experimental-test-coverage --test-coverage-include=scripts/setup.ts --test-coverage-include=scripts/uninstall.ts --test-coverage-lines=50 --test-coverage-branches=70 --test-coverage-functions=60 --test scripts/browse.node-test.ts scripts/client-tools.node-test.ts scripts/setup.node-test.ts scripts/upgrade.node-test.ts",
|
|
47
47
|
"test:package": "node --experimental-strip-types scripts/package-smoke.ts",
|
|
48
48
|
"test:passkey": "node --experimental-strip-types scripts/passkey-e2e.ts",
|
|
49
49
|
"test:watch": "vitest",
|
package/release-manifest.json
CHANGED
package/src/mcp/server.ts
CHANGED
|
@@ -57,8 +57,11 @@ function textContent(text: string): { type: "text"; text: string } {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
function toolResult<T extends Record<string, unknown>>(value: T, message: string) {
|
|
60
|
+
// content[0] is the human-readable summary. content[1] is the MCP-recommended
|
|
61
|
+
// serialized fallback for clients that do not surface structuredContent.
|
|
62
|
+
// storedDataResult adds explicit untrusted-data framing to both representations.
|
|
60
63
|
return {
|
|
61
|
-
content: [textContent(message)],
|
|
64
|
+
content: [textContent(message), textContent(JSON.stringify(value))],
|
|
62
65
|
structuredContent: value
|
|
63
66
|
};
|
|
64
67
|
}
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const WIKIMEMORY_VERSION = "0.2.
|
|
1
|
+
export const WIKIMEMORY_VERSION = "0.2.12";
|
|
2
2
|
export const LATEST_SCHEMA_VERSION = "0004_credential_bound_registration_tokens.sql";
|