wikimemory 0.2.4 → 0.2.5

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.
@@ -5,6 +5,7 @@ import { WIKIMEMORY_VERSION } from "../src/version.js";
5
5
  import { deploymentArguments, installArguments } from "./cli-options.js";
6
6
  import { deploymentPaths } from "./deployment-record.js";
7
7
  import { packageRoot } from "./package-root.js";
8
+ import { conciseError } from "./subprocess.js";
8
9
  const [command, ...args] = process.argv.slice(2);
9
10
  function usage() {
10
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 --yes wikimemory install\n\nTry it locally without a Cloudflare deployment:\n npx --yes 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.`;
@@ -74,6 +75,6 @@ async function main() {
74
75
  throw new Error(`Unknown command: ${command}`);
75
76
  }
76
77
  main().catch((error) => {
77
- console.error(`Wikimemory failed: ${error instanceof Error ? error.message : "Unknown error"}`);
78
+ console.error(`Wikimemory failed: ${conciseError(error)}`);
78
79
  process.exitCode = 1;
79
80
  });
@@ -1,24 +1,19 @@
1
- import { spawn } from "node:child_process";
2
1
  import { cp, mkdir } from "node:fs/promises";
3
2
  import { homedir } from "node:os";
4
3
  import { join } from "node:path";
5
4
  import { deploymentRecordPath, readDeploymentRecord } from "./deployment-record.js";
5
+ import { commandFailureMessage, runCommand } from "./subprocess.js";
6
6
  function client(value) {
7
7
  if (value === "codex" || value === "claude")
8
8
  return value;
9
9
  throw new Error("Client must be codex or claude");
10
10
  }
11
- async function run(command, args) {
12
- await new Promise((resolve, reject) => {
13
- const child = spawn(command, args, { stdio: "inherit" });
14
- child.on("error", reject);
15
- child.on("close", (code) => {
16
- if (code === 0)
17
- resolve();
18
- else
19
- reject(new Error(`${command} exited with status ${code ?? "unknown"}`));
20
- });
11
+ async function run(command, args, interactive = false) {
12
+ const result = await runCommand(command, args, {
13
+ ...(interactive ? { forwardLimitBytes: 4000, inheritStdin: true } : {})
21
14
  });
15
+ if (result.exitCode !== 0)
16
+ throw new Error(commandFailureMessage(`${command} client configuration`, result));
22
17
  }
23
18
  export async function connectClient(deployment, selected) {
24
19
  const target = client(selected);
@@ -26,7 +21,8 @@ export async function connectClient(deployment, selected) {
26
21
  const endpoint = `${record.origin}/mcp`;
27
22
  if (target === "codex") {
28
23
  await run("codex", ["mcp", "add", deployment, "--url", endpoint]);
29
- await run("codex", ["mcp", "login", deployment, "--scopes", "memory:read,memory:write"]);
24
+ console.log("Complete authorization in your browser…");
25
+ await run("codex", ["mcp", "login", deployment, "--scopes", "memory:read,memory:write"], true);
30
26
  }
31
27
  else {
32
28
  await run("claude", [
@@ -39,8 +35,10 @@ export async function connectClient(deployment, selected) {
39
35
  deployment,
40
36
  endpoint
41
37
  ]);
42
- await run("claude", ["mcp", "login", deployment]);
38
+ console.log("Complete authorization in your browser…");
39
+ await run("claude", ["mcp", "login", deployment], true);
43
40
  }
41
+ console.log(`Connected ${target} to ${deployment}.`);
44
42
  }
45
43
  export async function installSkills(packageRoot, selected) {
46
44
  const target = client(selected);
@@ -1,6 +1,6 @@
1
- import { spawn } from "node:child_process";
2
1
  import { mkdir, writeFile } from "node:fs/promises";
3
2
  import { join, resolve } from "node:path";
3
+ import { commandFailureMessage, runCommand } from "./subprocess.js";
4
4
  export function localConfig(packageRoot) {
5
5
  return `${JSON.stringify({
6
6
  $schema: join(packageRoot, "node_modules", "wrangler", "config-schema.json"),
@@ -27,24 +27,19 @@ export function localConfig(packageRoot) {
27
27
  }, null, 2)}\n`;
28
28
  }
29
29
  async function runWrangler(args, inherited = false) {
30
- await new Promise((resolvePromise, reject) => {
31
- const child = spawn("npx", ["wrangler", ...args], {
32
- stdio: inherited ? "inherit" : ["ignore", "inherit", "inherit"]
33
- });
34
- child.on("error", reject);
35
- child.on("close", (code) => {
36
- if (code === 0)
37
- resolvePromise();
38
- else
39
- reject(new Error(`Wrangler exited with status ${code ?? "unknown"}`));
40
- });
30
+ const result = await runCommand("npx", ["wrangler", ...args], {
31
+ ...(inherited ? { forwardLimitBytes: 8000, inheritStdin: true } : {})
41
32
  });
33
+ if (result.exitCode !== 0)
34
+ throw new Error(commandFailureMessage("Wrangler", result));
42
35
  }
43
36
  export async function runDev(packageRoot, args) {
44
37
  const stateDirectory = resolve(".wikimemory", "dev");
45
38
  await mkdir(stateDirectory, { recursive: true });
46
39
  const configPath = join(stateDirectory, "wrangler.jsonc");
47
40
  await writeFile(configPath, localConfig(packageRoot), "utf8");
41
+ console.log("Preparing local database…");
48
42
  await runWrangler(["d1", "migrations", "apply", "wikimemory", "--local", "--config", configPath]);
43
+ console.log("Starting local Wikimemory…");
49
44
  await runWrangler(["dev", "--config", configPath, ...args], true);
50
45
  }
@@ -7,6 +7,7 @@ import { pathToFileURL } from "node:url";
7
7
  import { z } from "zod";
8
8
  import { passkeyRuntime } from "./lifecycle-runtime.js";
9
9
  import { configuredOrigin } from "./setup.js";
10
+ import { conciseError } from "./subprocess.js";
10
11
  const PRODUCTION_CONFIG = passkeyRuntime.config;
11
12
  const CLI_STATE = passkeyRuntime.client;
12
13
  const CALLBACK = "http://127.0.0.1:45831/callback";
@@ -191,7 +192,7 @@ export async function runPasskeys(args) {
191
192
  const entrypoint = process.argv[1];
192
193
  if (entrypoint !== undefined && import.meta.url === pathToFileURL(entrypoint).href) {
193
194
  runPasskeys(process.argv.slice(2)).catch((error) => {
194
- console.error(`Passkey command failed: ${error instanceof Error ? error.message : "Unknown error"}`);
195
+ console.error(`Passkey command failed: ${conciseError(error)}`);
195
196
  process.exitCode = 1;
196
197
  });
197
198
  }
@@ -1,4 +1,3 @@
1
- import { spawn } from "node:child_process";
2
1
  import { createHash, randomBytes } from "node:crypto";
3
2
  import { constants } from "node:fs";
4
3
  import { access, mkdtemp, readdir, readFile, rm, unlink, writeFile } from "node:fs/promises";
@@ -11,6 +10,8 @@ import { z } from "zod";
11
10
  import { WIKIMEMORY_VERSION } from "../src/version.js";
12
11
  import { deploymentRecordFromConfig, writeDeploymentRecord } from "./deployment-record.js";
13
12
  import { installedRecordPath, packageRoot, setupRuntime } from "./lifecycle-runtime.js";
13
+ import { commandFailureMessage, conciseError, runCommand } from "./subprocess.js";
14
+ export { commandFailureMessage } from "./subprocess.js";
14
15
  const PACKAGE_ROOT = packageRoot;
15
16
  const CONFIG_PATH = setupRuntime.config;
16
17
  const STATE_PATH = setupRuntime.progress;
@@ -55,16 +56,6 @@ function cloudflareOperation(args) {
55
56
  return "KV namespace creation";
56
57
  return `Cloudflare ${command}`;
57
58
  }
58
- function conciseDiagnostic(result) {
59
- const raw = result.stderr.trim() || result.stdout.trim();
60
- const plain = raw.replaceAll("\u001b", "").replaceAll(/\s+/gu, " ").trim();
61
- if (plain === "")
62
- return `process exited with status ${result.exitCode}`;
63
- return plain.length > 600 ? `${plain.slice(0, 597)}...` : plain;
64
- }
65
- export function commandFailureMessage(operation, result) {
66
- return `${operation} failed: ${conciseDiagnostic(result)}`;
67
- }
68
59
  async function sleep(milliseconds) {
69
60
  await new Promise((resolve) => {
70
61
  setTimeout(resolve, milliseconds);
@@ -146,44 +137,18 @@ async function exists(path) {
146
137
  }
147
138
  }
148
139
  async function run(command, args, input, allowFailure = false) {
149
- const result = await new Promise((resolve, reject) => {
150
- const child = spawn(command, args, { stdio: ["pipe", "pipe", "pipe"] });
151
- let stdout = "";
152
- let stderr = "";
153
- child.stdout.setEncoding("utf8");
154
- child.stderr.setEncoding("utf8");
155
- child.stdout.on("data", (chunk) => {
156
- stdout += chunk;
157
- });
158
- child.stderr.on("data", (chunk) => {
159
- stderr += chunk;
160
- });
161
- child.on("error", (error) => {
162
- reject(error);
163
- });
164
- child.on("close", (code) => {
165
- resolve({ stdout, stderr, exitCode: code ?? -1 });
166
- });
167
- child.stdin.end(input === undefined ? undefined : `${input}\n`);
168
- });
140
+ const result = await runCommand(command, args, { ...(input === undefined ? {} : { input }) });
169
141
  if (result.exitCode !== 0 && !allowFailure)
170
142
  throw new Error(commandFailureMessage(cloudflareOperation(args), result));
171
143
  return result;
172
144
  }
173
145
  async function runInteractive(command, args) {
174
- console.log(`\n> ${command} ${args.join(" ")}`);
175
- await new Promise((resolve, reject) => {
176
- const child = spawn(command, args, { stdio: "inherit" });
177
- child.on("error", (error) => {
178
- reject(error);
179
- });
180
- child.on("close", (code) => {
181
- if (code === 0)
182
- resolve();
183
- else
184
- reject(new Error(`${command} exited with status ${code ?? "unknown"}`));
185
- });
146
+ const result = await runCommand(command, args, {
147
+ forwardLimitBytes: 4000,
148
+ inheritStdin: true
186
149
  });
150
+ if (result.exitCode !== 0)
151
+ throw new Error(commandFailureMessage(cloudflareOperation(args), result));
187
152
  }
188
153
  export function initialConfig(options, account) {
189
154
  return `${JSON.stringify({
@@ -504,7 +469,6 @@ async function finalizeDeployment(options) {
504
469
  const deploymentRecord = deploymentRecordFromConfig(finalConfig, WIKIMEMORY_VERSION, options.kvName);
505
470
  const recordPath = installedRecordPath(options.workerName);
506
471
  await writeDeploymentRecord(deploymentRecord, recordPath);
507
- console.log(`\nSaved deployment record: ${recordPath}`);
508
472
  console.log(`\n${handoff(origin, secret.raw, options.workerName)}`);
509
473
  }
510
474
  async function freshDeployment(options) {
@@ -602,7 +566,7 @@ export async function runSetup(args) {
602
566
  const entrypoint = process.argv[1];
603
567
  if (entrypoint !== undefined && import.meta.url === pathToFileURL(entrypoint).href) {
604
568
  runSetup(process.argv.slice(2)).catch((error) => {
605
- console.error(`\nSetup failed: ${error instanceof Error ? error.message : "Unknown error"}`);
569
+ console.error(`\nSetup failed: ${conciseError(error)}`);
606
570
  process.exitCode = 1;
607
571
  });
608
572
  }
@@ -45,5 +45,11 @@ export async function deploymentStatus(deployment) {
45
45
  }
46
46
  export async function runStatus(deployment) {
47
47
  const status = await deploymentStatus(deployment);
48
- console.log(`Wikimemory deployment: ${status.deployment}\n Account: ${status.accountId}\n Worker: ${status.workerName}\n D1: ${status.database}\n KV: ${status.kvNamespace}\n Origin: ${status.origin}\n Recorded version: ${status.recordedVersion}\n Running version: ${status.runningVersion}\n Schema: ${status.schemaVersion}`);
48
+ console.log(statusSummary(status));
49
+ }
50
+ export function statusSummary(status) {
51
+ const mismatch = status.recordedVersion === status.runningVersion
52
+ ? ""
53
+ : `\nLocal record: ${status.recordedVersion} (run wikimemory upgrade to reconcile)`;
54
+ return `Wikimemory ${status.deployment}: ready\nURL: ${status.origin}\nVersion: ${status.runningVersion}\nDatabase: up to date.${mismatch}`;
49
55
  }
@@ -0,0 +1,74 @@
1
+ import { spawn } from "node:child_process";
2
+ import process from "node:process";
3
+ import { stripVTControlCharacters } from "node:util";
4
+ export function boundedOutputChunk(chunk, remaining) {
5
+ const visible = chunk.slice(0, remaining);
6
+ return {
7
+ visible,
8
+ remaining: Math.max(0, remaining - visible.length),
9
+ truncated: visible.length < chunk.length
10
+ };
11
+ }
12
+ function forwardBounded(destination, chunk, state) {
13
+ const limited = boundedOutputChunk(chunk, state.remaining);
14
+ if (limited.visible !== "")
15
+ destination.write(limited.visible);
16
+ state.remaining = limited.remaining;
17
+ if (limited.truncated && !state.suppressed) {
18
+ destination.write("\n… additional command output suppressed.\n");
19
+ state.suppressed = true;
20
+ }
21
+ }
22
+ export async function runCommand(command, args, options = {}) {
23
+ return await new Promise((resolve, reject) => {
24
+ const child = spawn(command, args, {
25
+ stdio: [options.inheritStdin === true ? "inherit" : "pipe", "pipe", "pipe"]
26
+ });
27
+ if (child.stdout === null || child.stderr === null) {
28
+ child.kill();
29
+ reject(new Error("Subprocess output pipes were not created"));
30
+ return;
31
+ }
32
+ let stdout = "";
33
+ let stderr = "";
34
+ const stdoutForward = { remaining: options.forwardLimitBytes ?? 0, suppressed: false };
35
+ const stderrForward = { remaining: options.forwardLimitBytes ?? 0, suppressed: false };
36
+ child.stdout.setEncoding("utf8");
37
+ child.stderr.setEncoding("utf8");
38
+ child.stdout.on("data", (chunk) => {
39
+ stdout += chunk;
40
+ if (options.forwardLimitBytes !== undefined)
41
+ forwardBounded(process.stdout, chunk, stdoutForward);
42
+ });
43
+ child.stderr.on("data", (chunk) => {
44
+ stderr += chunk;
45
+ if (options.forwardLimitBytes !== undefined)
46
+ forwardBounded(process.stderr, chunk, stderrForward);
47
+ });
48
+ child.on("error", reject);
49
+ child.on("close", (code) => {
50
+ resolve({ stdout, stderr, exitCode: code ?? -1 });
51
+ });
52
+ if (child.stdin !== null)
53
+ child.stdin.end(options.input === undefined ? undefined : `${options.input}\n`);
54
+ });
55
+ }
56
+ export function conciseDiagnostic(result, maximumLength = 600) {
57
+ const raw = result.stderr.trim() || result.stdout.trim();
58
+ const plain = stripVTControlCharacters(raw).replaceAll(/\s+/gu, " ").trim();
59
+ if (plain === "")
60
+ return `process exited with status ${result.exitCode}`;
61
+ if (plain.length <= maximumLength)
62
+ return plain;
63
+ return `${plain.slice(0, Math.max(0, maximumLength - 3))}...`;
64
+ }
65
+ export function commandFailureMessage(operation, result) {
66
+ return `${operation} failed: ${conciseDiagnostic(result)}`;
67
+ }
68
+ export function conciseError(error, maximumLength = 600) {
69
+ const raw = error instanceof Error ? error.message : "Unknown error";
70
+ const singleLine = stripVTControlCharacters(raw).replaceAll(/\s+/gu, " ").trim();
71
+ if (singleLine.length <= maximumLength)
72
+ return singleLine;
73
+ return `${singleLine.slice(0, Math.max(0, maximumLength - 3))}...`;
74
+ }
@@ -1,10 +1,10 @@
1
- import { spawn } from "node:child_process";
2
1
  import { readFile, unlink, writeFile } from "node:fs/promises";
3
2
  import process from "node:process";
4
3
  import { createInterface } from "node:readline/promises";
5
4
  import { pathToFileURL } from "node:url";
6
5
  import { removePackagedDeploymentRecord, uninstallRuntime } from "./lifecycle-runtime.js";
7
6
  import { bindingProperty, configValue, deploymentListIndicatesExisting } from "./setup.js";
7
+ import { commandFailureMessage, conciseError, runCommand as executeCommand } from "./subprocess.js";
8
8
  const CONFIG_PATH = uninstallRuntime.config;
9
9
  const STATE_PATH = uninstallRuntime.installProgress;
10
10
  const UNINSTALL_STATE_PATH = uninstallRuntime.uninstallProgress;
@@ -54,30 +54,9 @@ export function clientRemovalInstructions(connectorName = "wikimemory") {
54
54
  return `Client registrations are not removed automatically. Disconnect each configured client separately:\n\n codex mcp logout ${connectorName}\n codex mcp remove ${connectorName}\n\n claude mcp logout ${connectorName}\n claude mcp remove --scope user ${connectorName}\n\nFor Claude web or mobile, remove the custom connector from Settings > Connectors.`;
55
55
  }
56
56
  async function run(args, allowFailure = false) {
57
- console.log(`\n> npx ${args.join(" ")}`);
58
- const result = await new Promise((resolve, reject) => {
59
- const child = spawn("npx", args, { stdio: ["ignore", "pipe", "pipe"] });
60
- let stdout = "";
61
- let stderr = "";
62
- child.stdout.setEncoding("utf8");
63
- child.stderr.setEncoding("utf8");
64
- child.stdout.on("data", (chunk) => {
65
- stdout += chunk;
66
- process.stdout.write(chunk);
67
- });
68
- child.stderr.on("data", (chunk) => {
69
- stderr += chunk;
70
- process.stderr.write(chunk);
71
- });
72
- child.on("error", (error) => {
73
- reject(error);
74
- });
75
- child.on("close", (code) => {
76
- resolve({ stdout, stderr, exitCode: code ?? -1 });
77
- });
78
- });
57
+ const result = await executeCommand("npx", args);
79
58
  if (result.exitCode !== 0 && !allowFailure)
80
- throw new Error(`npx exited with status ${result.exitCode}`);
59
+ throw new Error(commandFailureMessage("Cloudflare resource deletion", result));
81
60
  return result;
82
61
  }
83
62
  async function requireExactName(workerName, supplied) {
@@ -145,6 +124,7 @@ export async function runUninstall(args, runCommand = run) {
145
124
  const progress = await loadProgress(targets);
146
125
  await saveProgress(progress);
147
126
  if (!progress.workerDeleted) {
127
+ console.log(" Removing Worker…");
148
128
  const probe = await runCommand([
149
129
  "wrangler",
150
130
  "deployments",
@@ -172,6 +152,7 @@ export async function runUninstall(args, runCommand = run) {
172
152
  await saveProgress(progress);
173
153
  }
174
154
  if (!progress.kvDeleted) {
155
+ console.log(" Removing OAuth state…");
175
156
  await runCommand([
176
157
  "wrangler",
177
158
  "kv",
@@ -187,6 +168,7 @@ export async function runUninstall(args, runCommand = run) {
187
168
  await saveProgress(progress);
188
169
  }
189
170
  if (!progress.databaseDeleted) {
171
+ console.log(" Removing database…");
190
172
  await runCommand([
191
173
  "wrangler",
192
174
  "d1",
@@ -214,7 +196,7 @@ export async function runUninstall(args, runCommand = run) {
214
196
  const entrypoint = process.argv[1];
215
197
  if (entrypoint !== undefined && import.meta.url === pathToFileURL(entrypoint).href) {
216
198
  runUninstall(process.argv.slice(2)).catch((error) => {
217
- console.error(`\nUninstall failed: ${error instanceof Error ? error.message : "Unknown error"}`);
199
+ console.error(`\nUninstall failed: ${conciseError(error)}`);
218
200
  process.exitCode = 1;
219
201
  });
220
202
  }
@@ -7,6 +7,7 @@ import { createInterface } from "node:readline/promises";
7
7
  import { z } from "zod";
8
8
  import { deploymentRecordPath, readDeploymentRecord, writeDeploymentRecord } from "./deployment-record.js";
9
9
  import { packageRoot } from "./package-root.js";
10
+ import { commandFailureMessage, runCommand } from "./subprocess.js";
10
11
  const PACKAGE_ROOT = packageRoot();
11
12
  const DEPLOYMENT_NAME = /^[a-z0-9][a-z0-9-]{0,62}$/u;
12
13
  const MIGRATION_NAME = /^\d{4}_[a-z0-9_]+\.sql$/u;
@@ -134,28 +135,14 @@ function migrationBundle(sql, migration) {
134
135
  return `${sql.trimEnd()}\n\nINSERT INTO d1_migrations(name) VALUES ('${name}');\n`;
135
136
  }
136
137
  async function command(commandName, args) {
137
- const { spawn } = await import("node:child_process");
138
- return await new Promise((resolve, reject) => {
139
- const child = spawn(commandName, args, { stdio: ["ignore", "pipe", "pipe"] });
140
- let stdout = "";
141
- let stderr = "";
142
- child.stdout.setEncoding("utf8");
143
- child.stderr.setEncoding("utf8");
144
- child.stdout.on("data", (chunk) => {
145
- stdout += chunk;
146
- process.stdout.write(chunk);
147
- });
148
- child.stderr.on("data", (chunk) => {
149
- stderr += chunk;
150
- process.stderr.write(chunk);
151
- });
152
- child.on("error", (error) => {
153
- reject(error);
154
- });
155
- child.on("close", (code) => {
156
- resolve({ stdout, stderr, exitCode: code ?? -1 });
157
- });
158
- });
138
+ return await runCommand(commandName, args);
139
+ }
140
+ export function upgradeSummary(record, currentVersion, targetVersion, databaseUpdates) {
141
+ const updates = databaseUpdates === 0 ? "none" : `${databaseUpdates} required`;
142
+ return `Upgrade Wikimemory “${record.workerName}” at ${record.origin}\n Version: ${currentVersion} -> ${targetVersion}\n Database updates: ${updates}`;
143
+ }
144
+ export function readySummary(version, alreadyCurrent = false) {
145
+ return `Wikimemory ${version} is ${alreadyCurrent ? "already " : ""}ready.\nDatabase: up to date.`;
159
146
  }
160
147
  function parsedJson(stdout) {
161
148
  return JSON.parse(stdout);
@@ -264,7 +251,7 @@ export async function runUpgrade(args) {
264
251
  command("npx", ["wrangler", "kv", "namespace", "list", ...common])
265
252
  ]);
266
253
  if ([whoami, deployments, d1, kv].some((result) => result.exitCode !== 0))
267
- throw new Error("Cloudflare target preflight failed");
254
+ throw new Error(commandFailureMessage("Cloudflare target preflight", [whoami, deployments, d1, kv].find((result) => result.exitCode !== 0) ?? whoami));
268
255
  const whoamiBody = z
269
256
  .object({ accounts: z.array(z.object({ id: z.string() })) })
270
257
  .parse(parsedJson(whoami.stdout));
@@ -292,7 +279,7 @@ export async function runUpgrade(args) {
292
279
  "SELECT name FROM d1_migrations ORDER BY id"
293
280
  ]);
294
281
  if (listed.exitCode !== 0)
295
- throw new Error("Could not read installed migrations");
282
+ throw new Error(commandFailureMessage("Database version check", listed));
296
283
  const query = z
297
284
  .array(z.object({ results: z.array(z.object({ name: z.string() })) }))
298
285
  .parse(parsedJson(listed.stdout));
@@ -308,10 +295,12 @@ export async function runUpgrade(args) {
308
295
  if (deploymentIsCurrent(current, manifest, pending)) {
309
296
  await verifyRelease(record.origin, manifest);
310
297
  await writeDeploymentRecord({ ...record, installedVersion: manifest.version }, recordPath);
311
- console.log(`\nWikimemory ${manifest.version} is already ready. Reconciled the local deployment record without redeploying.`);
298
+ console.log(`\n${readySummary(manifest.version, true)}`);
312
299
  return;
313
300
  }
314
- await confirmUpgrade(`\nWikimemory upgrade\n Account: ${record.accountId}\n Worker: ${record.workerName}\n D1: ${record.databaseName} (${record.databaseId})\n KV: ${record.kvName} (${record.kvId})\n Origin: ${record.origin}\n Version: ${current} -> ${manifest.version}\n Migrations: ${pending.map((item) => item.name).join(", ") || "none"}`, options.yes);
301
+ await confirmUpgrade(`\n${upgradeSummary(record, current, manifest.version, pending.length)}`, options.yes);
302
+ if (pending.length > 0)
303
+ console.log(" Updating database…");
315
304
  for (const migration of pending) {
316
305
  const bundlePath = join(temporary, migration.name);
317
306
  const sql = await readFile(join(PACKAGE_ROOT, "migrations", migration.name), "utf8");
@@ -327,14 +316,16 @@ export async function runUpgrade(args) {
327
316
  bundlePath
328
317
  ]);
329
318
  if (result.exitCode !== 0)
330
- throw new Error(`Migration failed: ${migration.name}`);
319
+ throw new Error(commandFailureMessage("Database update", result));
331
320
  }
321
+ console.log(" Deploying Wikimemory…");
332
322
  const deployed = await command("npx", ["wrangler", "deploy", "--strict", ...common]);
333
323
  if (deployed.exitCode !== 0)
334
- throw new Error("Worker deployment failed");
324
+ throw new Error(commandFailureMessage("Worker deployment", deployed));
325
+ console.log(" Verifying deployment…");
335
326
  await verifyRelease(record.origin, manifest);
336
327
  await writeDeploymentRecord({ ...record, installedVersion: manifest.version }, recordPath);
337
- console.log(`\nWikimemory ${manifest.version} is ready. Schema: ${manifest.schemaVersion}`);
328
+ console.log(`\n${readySummary(manifest.version)}`);
338
329
  }
339
330
  finally {
340
331
  await rm(temporary, { recursive: true, force: true });
@@ -1,2 +1,2 @@
1
- export const WIKIMEMORY_VERSION = "0.2.4";
1
+ export const WIKIMEMORY_VERSION = "0.2.5";
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.4",
3
+ "version": "0.2.5",
4
4
  "description": "Personal, passkey-protected memory for Claude, Codex, and other MCP clients",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.2.4",
2
+ "version": "0.2.5",
3
3
  "schemaVersion": "0004_credential_bound_registration_tokens.sql",
4
4
  "migrations": [
5
5
  {
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
- export const WIKIMEMORY_VERSION = "0.2.4";
1
+ export const WIKIMEMORY_VERSION = "0.2.5";
2
2
  export const LATEST_SCHEMA_VERSION = "0004_credential_bound_registration_tokens.sql";