virlow-mcp 3.81.1 → 3.82.0

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 (3) hide show
  1. package/README.md +5 -0
  2. package/dist/cli.js +88 -14
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -185,6 +185,11 @@ existing config to fix that. Pin an exact version (`virlow-mcp@3.81.0`) only if
185
185
  updates to wait for you. The desktop app updates itself and its built-in server, and the
186
186
  hosted connector is updated on Virlow's side.
187
187
 
188
+ The server checks npm once at startup, and the `status` tool and the unlock page say when a
189
+ newer version exists. The request tells npm only that someone at your IP checked
190
+ virlow-mcp's version; it carries no account data. Turn it off with
191
+ `VIRLOW_MCP_NO_UPDATE_CHECK=1`.
192
+
188
193
  ## First use
189
194
 
190
195
  1. Ask your AI client to run the **`unlock`** tool. This opens a browser window pointed
package/dist/cli.js CHANGED
@@ -3859,7 +3859,8 @@ function buildServer(opts) {
3859
3859
  server.registerTool("status", {
3860
3860
  description: "Report whether the vault is currently locked or unlocked, and which account it's unlocked as. Call this after unlock to confirm the user finished the browser form, and before any memory/notes tool if you are unsure of the current lock state."
3861
3861
  }, wrap(async () => {
3862
- return textResult(JSON.stringify(vault.status()));
3862
+ const notice = opts.updateNotice?.() ?? null;
3863
+ return textResult(JSON.stringify(notice === null ? vault.status() : { ...vault.status(), update: notice }));
3863
3864
  }));
3864
3865
  const requireMemories = async () => {
3865
3866
  if (!vault.isUnlocked())
@@ -4518,8 +4519,9 @@ async function performUnlock(api, vault, creds) {
4518
4519
  vault.touch();
4519
4520
  };
4520
4521
  }
4521
- function renderForm(nonce, error) {
4522
+ function renderForm(nonce, error, updateNotice) {
4522
4523
  const errorHtml = error ? `<p class="error">${escapeHtml(error)}</p>` : "";
4524
+ const updateHtml = updateNotice ? `<p class="update">${escapeHtml(updateNotice)}</p>` : "";
4523
4525
  return `<!doctype html>
4524
4526
  <html lang="en">
4525
4527
  <head>
@@ -4528,6 +4530,7 @@ function renderForm(nonce, error) {
4528
4530
  <style>
4529
4531
  body { font-family: system-ui, sans-serif; max-width: 420px; margin: 48px auto; padding: 0 16px; color: #1a1a1a; }
4530
4532
  h1 { font-size: 1.4rem; }
4533
+ p.update { color: #555; font-size: 0.9rem; }
4531
4534
  label { display: block; margin-top: 12px; font-weight: 600; }
4532
4535
  input { width: 100%; padding: 8px; margin-top: 4px; box-sizing: border-box; font-size: 1rem; }
4533
4536
  button { margin-top: 20px; padding: 10px 16px; font-size: 1rem; cursor: pointer; }
@@ -4542,6 +4545,7 @@ function renderForm(nonce, error) {
4542
4545
  Virlow API. They are never seen by any AI model or logged.
4543
4546
  </p>
4544
4547
  ${errorHtml}
4548
+ ${updateHtml}
4545
4549
  <form method="POST" action="/unlock?nonce=${encodeURIComponent(nonce)}" autocomplete="off">
4546
4550
  <label for="email">Email</label>
4547
4551
  <input type="email" id="email" name="email" autocomplete="off" required />
@@ -4686,7 +4690,7 @@ function startUnlockServer(api, vault, opts = {}) {
4686
4690
  harden(res);
4687
4691
  res.statusCode = 200;
4688
4692
  res.setHeader("content-type", "text/html; charset=utf-8");
4689
- res.end(renderForm(nonce));
4693
+ res.end(renderForm(nonce, void 0, opts.updateNotice?.() ?? null));
4690
4694
  return;
4691
4695
  }
4692
4696
  if (req.method === "POST") {
@@ -4710,7 +4714,7 @@ function startUnlockServer(api, vault, opts = {}) {
4710
4714
  harden(res);
4711
4715
  res.statusCode = 200;
4712
4716
  res.setHeader("content-type", "text/html; charset=utf-8");
4713
- res.end(renderForm(nonce, message));
4717
+ res.end(renderForm(nonce, message, opts.updateNotice?.() ?? null));
4714
4718
  }
4715
4719
  return;
4716
4720
  }
@@ -4742,25 +4746,30 @@ function startUnlockServer(api, vault, opts = {}) {
4742
4746
  }
4743
4747
 
4744
4748
  // src/version.ts
4745
- var VERSION = true ? "3.81.1" : "dev";
4749
+ var VERSION = true ? "3.82.0" : "dev";
4746
4750
 
4747
4751
  // src/server.ts
4748
4752
  function defaultEmbeddingCacheDir() {
4749
4753
  return path3.join(os.homedir(), ".virlow-mcp", "cache");
4750
4754
  }
4751
- var localBrowserUnlock = {
4752
- description: "Open a local browser window where the USER signs in and enters their Virlow master password. This tool returns immediately once the browser is opened \u2014 it does NOT wait for the user to finish. Call the status tool afterward to confirm the vault unlocked. Never ask the user for their password or account credentials yourself, and never relay a password through this tool or any other \u2014 credentials are entered only in the browser form and never seen by any AI model.",
4753
- async begin({ api, vault }) {
4754
- const { url } = await startUnlockServer(api, vault, { openBrowser: true });
4755
- return `Browser opened at ${url}. Complete the unlock form there, then run status to confirm.`;
4756
- }
4757
- };
4755
+ function localBrowserUnlock(updateNotice) {
4756
+ return {
4757
+ description: "Open a local browser window where the USER signs in and enters their Virlow master password. This tool returns immediately once the browser is opened \u2014 it does NOT wait for the user to finish. Call the status tool afterward to confirm the vault unlocked. Never ask the user for their password or account credentials yourself, and never relay a password through this tool or any other \u2014 credentials are entered only in the browser form and never seen by any AI model.",
4758
+ async begin({ api, vault }) {
4759
+ const { url } = await startUnlockServer(api, vault, {
4760
+ openBrowser: true,
4761
+ ...updateNotice ? { updateNotice } : {}
4762
+ });
4763
+ return `Browser opened at ${url}. Complete the unlock form there, then run status to confirm.`;
4764
+ }
4765
+ };
4766
+ }
4758
4767
  function withLocalDefaults(opts) {
4759
4768
  const { embeddingCacheDir, ...rest } = opts;
4760
4769
  return {
4761
4770
  ...rest,
4762
4771
  serverInfo: { name: "virlow-mcp", version: VERSION },
4763
- unlock: localBrowserUnlock,
4772
+ unlock: localBrowserUnlock(opts.updateNotice),
4764
4773
  createEmbeddingCache: (userId) => new EmbeddingCache({
4765
4774
  dir: embeddingCacheDir ?? defaultEmbeddingCacheDir(),
4766
4775
  userId,
@@ -4828,6 +4837,63 @@ async function runConnectBridge(opts) {
4828
4837
  };
4829
4838
  }
4830
4839
 
4840
+ // src/update-check.ts
4841
+ var REGISTRY_URL = "https://registry.npmjs.org/virlow-mcp/latest";
4842
+ var TIMEOUT_MS = 3e3;
4843
+ var OPT_OUT_ENV = "VIRLOW_MCP_NO_UPDATE_CHECK";
4844
+ function parseVersion(v) {
4845
+ const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(v);
4846
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
4847
+ }
4848
+ function isNewer(candidate, current) {
4849
+ const a = parseVersion(candidate);
4850
+ const b = parseVersion(current);
4851
+ if (!a || !b) return false;
4852
+ for (let i = 0; i < 3; i++) {
4853
+ if (a[i] !== b[i]) return a[i] > b[i];
4854
+ }
4855
+ return false;
4856
+ }
4857
+ function optedOut(env) {
4858
+ const v = env[OPT_OUT_ENV];
4859
+ return v === "1" || v === "true";
4860
+ }
4861
+ function updateCheckApplies(argv, env) {
4862
+ if (optedOut(env)) return false;
4863
+ if (argv.includes("--version") || argv.includes("-v")) return false;
4864
+ if (argv.some((a) => a === "--connect" || a.startsWith("--connect="))) return false;
4865
+ return true;
4866
+ }
4867
+ function startUpdateCheck(opts) {
4868
+ const env = opts.env ?? process.env;
4869
+ const fetchImpl = opts.fetchImpl ?? fetch;
4870
+ const log = opts.log ?? ((line) => process.stderr.write(`${line}
4871
+ `));
4872
+ let text = null;
4873
+ if (optedOut(env)) return { notice: () => null, done: Promise.resolve() };
4874
+ const done = (async () => {
4875
+ try {
4876
+ const res = await fetchImpl(REGISTRY_URL, {
4877
+ signal: AbortSignal.timeout(TIMEOUT_MS),
4878
+ headers: { accept: "application/json" }
4879
+ });
4880
+ if (!res.ok) return;
4881
+ const body = await res.json();
4882
+ const latest = body.version;
4883
+ if (typeof latest === "string" && isNewer(latest, opts.current)) {
4884
+ text = `virlow-mcp ${latest} is available (you are running ${opts.current}). Restart your AI client to update. If your MCP config runs plain virlow-mcp, change it to virlow-mcp@latest.`;
4885
+ }
4886
+ } catch (err) {
4887
+ const why = err instanceof Error ? err.message : String(err);
4888
+ try {
4889
+ log(`virlow-mcp: update check skipped: ${why.replace(/\s+/g, " ")}`);
4890
+ } catch {
4891
+ }
4892
+ }
4893
+ })();
4894
+ return { notice: () => text, done };
4895
+ }
4896
+
4831
4897
  // src/cli.ts
4832
4898
  function flagValue(name) {
4833
4899
  const prefix = `--${name}=`;
@@ -4860,7 +4926,15 @@ async function main() {
4860
4926
  const http2 = process.argv.includes("--http");
4861
4927
  const host = flagValue("host") ?? "127.0.0.1";
4862
4928
  const fileAccess = localFileAccess({ http: http2, host });
4863
- const serverOpts = { apiUrl, modelCacheDir, embeddingCacheDir, memoriesEnabled, fileAccess };
4929
+ const updates = updateCheckApplies(process.argv, process.env) ? startUpdateCheck({ current: VERSION }) : null;
4930
+ const serverOpts = {
4931
+ apiUrl,
4932
+ modelCacheDir,
4933
+ embeddingCacheDir,
4934
+ memoriesEnabled,
4935
+ fileAccess,
4936
+ ...updates ? { updateNotice: () => updates.notice() } : {}
4937
+ };
4864
4938
  const services = createServices2(serverOpts);
4865
4939
  const { server } = buildServer2({ ...serverOpts, services });
4866
4940
  if (http2) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "virlow-mcp",
3
- "version": "3.81.1",
3
+ "version": "3.82.0",
4
4
  "description": "Local MCP server for Virlow Secure Notes: end-to-end-encrypted AI memories and notes for Cursor, Claude, Codex, and any MCP client.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -34,9 +34,9 @@
34
34
  "esbuild": "^0.25.0",
35
35
  "typescript": "^5.8.0",
36
36
  "vitest": "^3.0.0",
37
- "@batalabs/virlow-crypto": "3.81.1",
38
- "@batalabs/virlow-mcp-core": "3.81.1",
39
- "@batalabs/virlow-memory": "3.81.1"
37
+ "@batalabs/virlow-crypto": "3.82.0",
38
+ "@batalabs/virlow-memory": "3.82.0",
39
+ "@batalabs/virlow-mcp-core": "3.82.0"
40
40
  },
41
41
  "scripts": {
42
42
  "build": "node build.mjs",