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 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, then connect a client and install
48
- its memory skills:
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
- Use the same remote MCP from Claude web or mobile by adding the printed HTTPS `/mcp`
60
- URL as a custom connector. See the complete [installation guide](docs/installation.md)
61
- for recovery, passkey management, upgrades, and safe uninstall.
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 mkdir(destinationRoot, { recursive: true, mode: 0o700 });
47
- const names = ["wikimemory-recall", "wikimemory-ingest", "wikimemory-lint", "wikimemory-install"];
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\nMCP endpoint: ${endpoint}\nThe setup token was not written to disk. This is the only time it will be printed.`;
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\nURL: ${status.origin}\nVersion: ${status.runningVersion}\nDatabase: up to date.${mismatch}`;
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.10";
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.10",
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",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.2.10",
2
+ "version": "0.2.12",
3
3
  "schemaVersion": "0004_credential_bound_registration_tokens.sql",
4
4
  "migrations": [
5
5
  {
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.10";
1
+ export const WIKIMEMORY_VERSION = "0.2.12";
2
2
  export const LATEST_SCHEMA_VERSION = "0004_credential_bound_registration_tokens.sql";