lensmcp 1.25.17 → 1.25.21

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 (61) hide show
  1. package/bin.js +1 -1
  2. package/bundled/bridge.js +38 -37
  3. package/bundled/capture-runner.js +6 -6
  4. package/bundled/dashboard-ui/app.css +1 -1
  5. package/bundled/dashboard-ui/app.js +92 -92
  6. package/bundled/dashboard-ui/chunks/ReplayView-vO4p7cGp.js +131 -0
  7. package/bundled/dashboard.js +288 -279
  8. package/bundled/main.js +228 -221
  9. package/bundled/shim.js +8 -8
  10. package/lib/agent-asked.js +4 -4
  11. package/lib/agent-plugins.js +2 -2
  12. package/lib/bridge-origins.js +1 -1
  13. package/lib/call-command.js +2 -2
  14. package/lib/cli-help.js +3 -3
  15. package/lib/cli.js +12 -12
  16. package/lib/dashboard-login.d.ts +9 -2
  17. package/lib/dashboard-login.js +3 -3
  18. package/lib/dismiss-command.js +2 -2
  19. package/lib/duplicate-registration.js +1 -1
  20. package/lib/forward-signals.js +1 -1
  21. package/lib/gateway-registration.js +1 -1
  22. package/lib/guest-graph.js +4 -4
  23. package/lib/heap-analyzer-main.js +1 -1
  24. package/lib/heap-analyzer.js +1 -1
  25. package/lib/hook-memory.js +1 -1
  26. package/lib/instance-command.js +9 -9
  27. package/lib/instance-registry.js +2 -2
  28. package/lib/instance-resources.js +5 -5
  29. package/lib/instance-worktree.js +6 -6
  30. package/lib/lens-ignore.js +2 -2
  31. package/lib/lint-args.js +1 -1
  32. package/lib/logs-follow.js +1 -1
  33. package/lib/mcp-mode.js +3 -3
  34. package/lib/mcp-restart.js +1 -1
  35. package/lib/mcp-sessions.js +2 -2
  36. package/lib/mcp-token.js +3 -3
  37. package/lib/mcp-wiring.js +2 -2
  38. package/lib/mem-command.js +3 -3
  39. package/lib/perf-rules-check.js +4 -4
  40. package/lib/plugin-version.js +3 -3
  41. package/lib/pod-source-maps.js +1 -1
  42. package/lib/process-identity.js +1 -1
  43. package/lib/process-ownership.js +2 -2
  44. package/lib/project-files.js +1 -1
  45. package/lib/recycle-policy.js +1 -1
  46. package/lib/resp-client.js +5 -5
  47. package/lib/restart-command.js +2 -2
  48. package/lib/server-bundle.js +2 -2
  49. package/lib/status-client.js +2 -2
  50. package/lib/status-policy.js +2 -2
  51. package/lib/sweep.js +1 -1
  52. package/lib/upgrade.js +2 -2
  53. package/lib/workspace-scope.js +2 -2
  54. package/package.json +4 -4
  55. package/plugin/.claude-plugin/plugin.json +1 -1
  56. package/plugin/skills/dashboard/SKILL.md +2 -1
  57. package/plugin/skills/using-lensmcp/SKILL.md +28 -1
  58. package/plugin/skills/worktrees/SKILL.md +2 -1
  59. package/plugins/lensmcp/.codex-plugin/plugin.json +1 -1
  60. package/plugins/lensmcp/skills/lensmcp-dashboard/SKILL.md +2 -1
  61. package/plugins/lensmcp/skills/using-lensmcp/SKILL.md +27 -1
package/lib/upgrade.js CHANGED
@@ -1,3 +1,3 @@
1
- "use strict";var x=Object.defineProperty;var i=(e,t)=>x(e,"name",{value:t,configurable:!0});var $=Object.defineProperty,c=i((e,t)=>$(e,"name",{value:t,configurable:!0}),"i"),b=Object.defineProperty,p=c((e,t)=>b(e,"name",{value:t,configurable:!0}),"c");import{spawnSync as w}from"node:child_process";import{existsSync as y,readFileSync as f}from"node:fs";import{homedir as v}from"node:os";import{join as g}from"node:path";export const UPGRADE_SECTIONS=["dependencies","devDependencies","optionalDependencies","resolutions","overrides"];export function isLensmcpPackage(e){return e==="lensmcp"||e.startsWith("@lensmcp/")}i(isLensmcpPackage,"isLensmcpPackage"),c(isLensmcpPackage,"isLensmcpPackage"),p(isLensmcpPackage,"isLensmcpPackage");export function readConnectedWorkspaces(e={}){const t=e.home??process.env.LENSMCP_HOME??v(),n=e.exists??y,r=e.readFile??(u=>f(u,"utf8"));let s;try{s=JSON.parse(r(g(t,".lensmcp","workspaces.json")))}catch{return[]}if(!Array.isArray(s))return[];const a=new Set,d=[];for(const u of s){if(!u||typeof u!="object")continue;const{key:o,root:l}=u;typeof o!="string"||typeof l!="string"||l.length===0||a.has(l)||n(g(l,"package.json"))&&(a.add(l),d.push({key:o,root:l}))}return d}i(readConnectedWorkspaces,"readConnectedWorkspaces"),c(readConnectedWorkspaces,"readConnectedWorkspaces"),p(readConnectedWorkspaces,"readConnectedWorkspaces");export function upgradeTargets(e,t,n){const r=[],s=[],a=new Set;for(const d of[...e?[e]:[],...t])a.has(d.root)||(a.add(d.root),(n(d.root)?s:r).push(d));return{targets:r,instances:s}}i(upgradeTargets,"upgradeTargets"),c(upgradeTargets,"upgradeTargets"),p(upgradeTargets,"upgradeTargets");export function versionPrefix(e){const t=/^([~^]?)(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/.exec(e.trim());return t?t[1]:void 0}i(versionPrefix,"versionPrefix"),c(versionPrefix,"versionPrefix"),p(versionPrefix,"versionPrefix");function m(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}i(m,"m"),c(m,"h"),p(m,"escapeRegExp");export function rewriteLensmcpDeps(e,t){let n;try{n=JSON.parse(e)}catch{return{text:e,changes:[],skipped:[]}}const r=[],s=[];for(const d of UPGRADE_SECTIONS){const u=n[d];if(!(!u||typeof u!="object"))for(const[o,l]of Object.entries(u)){if(typeof l!="string"||!isLensmcpPackage(o))continue;const h=versionPrefix(l);if(h===void 0){s.push({name:o,section:d,spec:l,reason:"non-version-spec"});continue}const k=`${h}${t}`;if(k===l){s.push({name:o,section:d,spec:l,reason:"already-current"});continue}r.push({name:o,section:d,from:l,to:k})}}let a=e;for(const d of r){const u=new RegExp(`("${m(d.name)}"\\s*:\\s*)"${m(d.from)}"`,"g");a=a.replace(u,`$1"${d.to}"`)}return{text:a,changes:r,skipped:s}}i(rewriteLensmcpDeps,"rewriteLensmcpDeps"),c(rewriteLensmcpDeps,"rewriteLensmcpDeps"),p(rewriteLensmcpDeps,"rewriteLensmcpDeps");export function detectPackageManager(e,t=y,n=r=>f(r,"utf8")){let r;try{const a=JSON.parse(n(g(e,"package.json")));typeof a.packageManager=="string"&&(r=a.packageManager.split("@")[0])}catch{}const s=p(a=>({name:a,install:["install"]}),"manager");return r==="yarn"||r==="npm"||r==="pnpm"||r==="bun"?s(r):t(g(e,"yarn.lock"))?s("yarn"):t(g(e,"pnpm-lock.yaml"))?s("pnpm"):t(g(e,"bun.lockb"))?s("bun"):s("npm")}i(detectPackageManager,"detectPackageManager"),c(detectPackageManager,"detectPackageManager"),p(detectPackageManager,"detectPackageManager");export function detectGlobalInstall(e,t=n=>f(n,"utf8")){const n=e("npm",["root","-g"]);if(!n||n.code!==0||!n.stdout)return;const r=n.stdout.trim();if(r)try{const s=JSON.parse(t(g(r,"lensmcp","package.json")));return typeof s.version=="string"?{version:s.version,root:r}:void 0}catch{return}}i(detectGlobalInstall,"detectGlobalInstall"),c(detectGlobalInstall,"detectGlobalInstall"),p(detectGlobalInstall,"detectGlobalInstall");export function registryAnswer(e,t){if(!e)return"unknown";const n=(e.stdout??"").trim();return e.code===0?n.split(`
1
+ "use strict";var w=Object.defineProperty;var i=(e,t)=>w(e,"name",{value:t,configurable:!0});var x=Object.defineProperty,c=i((e,t)=>x(e,"name",{value:t,configurable:!0}),"c");import{spawnSync as k}from"node:child_process";import{existsSync as g,readFileSync as f}from"node:fs";import{homedir as $}from"node:os";import{join as l}from"node:path";export const UPGRADE_SECTIONS=["dependencies","devDependencies","optionalDependencies","resolutions","overrides"];export function isLensmcpPackage(e){return e==="lensmcp"||e.startsWith("@lensmcp/")}i(isLensmcpPackage,"isLensmcpPackage"),c(isLensmcpPackage,"isLensmcpPackage");export function readConnectedWorkspaces(e={}){const t=e.home??process.env.LENSMCP_HOME??$(),n=e.exists??g,r=e.readFile??(d=>f(d,"utf8"));let o;try{o=JSON.parse(r(l(t,".lensmcp","workspaces.json")))}catch{return[]}if(!Array.isArray(o))return[];const a=new Set,p=[];for(const d of o){if(!d||typeof d!="object")continue;const{key:s,root:u}=d;typeof s!="string"||typeof u!="string"||u.length===0||a.has(u)||n(l(u,"package.json"))&&(a.add(u),p.push({key:s,root:u}))}return p}i(readConnectedWorkspaces,"readConnectedWorkspaces"),c(readConnectedWorkspaces,"readConnectedWorkspaces");export function upgradeTargets(e,t,n){const r=[],o=[],a=new Set;for(const p of[...e?[e]:[],...t])a.has(p.root)||(a.add(p.root),(n(p.root)?o:r).push(p));return{targets:r,instances:o}}i(upgradeTargets,"upgradeTargets"),c(upgradeTargets,"upgradeTargets");export function versionPrefix(e){const t=/^([~^]?)(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/.exec(e.trim());return t?t[1]:void 0}i(versionPrefix,"versionPrefix"),c(versionPrefix,"versionPrefix");function m(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}i(m,"h"),c(m,"escapeRegExp");export function rewriteLensmcpDeps(e,t){let n;try{n=JSON.parse(e)}catch{return{text:e,changes:[],skipped:[]}}const r=[],o=[];for(const p of UPGRADE_SECTIONS){const d=n[p];if(!(!d||typeof d!="object"))for(const[s,u]of Object.entries(d)){if(typeof u!="string"||!isLensmcpPackage(s))continue;const y=versionPrefix(u);if(y===void 0){o.push({name:s,section:p,spec:u,reason:"non-version-spec"});continue}const h=`${y}${t}`;if(h===u){o.push({name:s,section:p,spec:u,reason:"already-current"});continue}r.push({name:s,section:p,from:u,to:h})}}let a=e;for(const p of r){const d=new RegExp(`("${m(p.name)}"\\s*:\\s*)"${m(p.from)}"`,"g");a=a.replace(d,`$1"${p.to}"`)}return{text:a,changes:r,skipped:o}}i(rewriteLensmcpDeps,"rewriteLensmcpDeps"),c(rewriteLensmcpDeps,"rewriteLensmcpDeps");export function detectPackageManager(e,t=g,n=r=>f(r,"utf8")){let r;try{const a=JSON.parse(n(l(e,"package.json")));typeof a.packageManager=="string"&&(r=a.packageManager.split("@")[0])}catch{}const o=c(a=>({name:a,install:["install"]}),"manager");return r==="yarn"||r==="npm"||r==="pnpm"||r==="bun"?o(r):t(l(e,"yarn.lock"))?o("yarn"):t(l(e,"pnpm-lock.yaml"))?o("pnpm"):t(l(e,"bun.lockb"))?o("bun"):o("npm")}i(detectPackageManager,"detectPackageManager"),c(detectPackageManager,"detectPackageManager");export function detectGlobalInstall(e,t=n=>f(n,"utf8")){const n=e("npm",["root","-g"]);if(!n||n.code!==0||!n.stdout)return;const r=n.stdout.trim();if(r)try{const o=JSON.parse(t(l(r,"lensmcp","package.json")));return typeof o.version=="string"?{version:o.version,root:r}:void 0}catch{return}}i(detectGlobalInstall,"detectGlobalInstall"),c(detectGlobalInstall,"detectGlobalInstall");export function registryAnswer(e,t){if(!e)return"unknown";const n=(e.stdout??"").trim();return e.code===0?n.split(`
2
2
  `).some(r=>r.trim()===t)?"published":n===""?"absent":"unknown":/\bE404\b|\bETARGET\b|No match found/i.test(`${e.stderr??""}
3
- ${n}`)?"absent":"unknown"}i(registryAnswer,"registryAnswer"),c(registryAnswer,"registryAnswer"),p(registryAnswer,"registryAnswer");export function lensPackagesIn(e){const t=new Set,n=/(?<![\w-])(@lensmcp\/[a-z0-9][a-z0-9-]*)(?=["'@:\s/])/g,r=/(?:(?<=node_modules\/)|(?<![\w@/.-]))(lensmcp)(?=["'@:\s])/g;for(const s of e){for(const a of s.matchAll(n))t.add(a[1]);for(const a of s.matchAll(r))t.add(a[1])}return[...t].sort()}i(lensPackagesIn,"lensPackagesIn"),c(lensPackagesIn,"lensPackagesIn"),p(lensPackagesIn,"lensPackagesIn");export function missingFromRegistry(e,t,n){return e.filter(r=>registryAnswer(n(r),t)==="absent")}i(missingFromRegistry,"missingFromRegistry"),c(missingFromRegistry,"missingFromRegistry"),p(missingFromRegistry,"missingFromRegistry");export function installedCliFor(e,t){const n=g(e,"node_modules","lensmcp"),r=g(n,"bin.js");try{return JSON.parse(f(g(n,"package.json"),"utf8")).version===t&&y(r)?r:void 0}catch{return}}i(installedCliFor,"installedCliFor"),c(installedCliFor,"installedCliFor"),p(installedCliFor,"installedCliFor");export function restartGatewayWith(e,t,n){return w(process.execPath,[e,"gateway","restart"],{cwd:t,env:n,stdio:"inherit"}).status??1}i(restartGatewayWith,"restartGatewayWith"),c(restartGatewayWith,"restartGatewayWith"),p(restartGatewayWith,"restartGatewayWith");export function installLanded(e,t){return e===t}i(installLanded,"installLanded"),c(installLanded,"installLanded"),p(installLanded,"installLanded");export function needsInstall(e){return e.rewritten||e.installedVersion!==e.targetVersion}i(needsInstall,"needsInstall"),c(needsInstall,"needsInstall"),p(needsInstall,"needsInstall");export function daemonVerdict(e){if(e.reported===e.target)return{ok:!0,line:` daemon \u2192 now running ${e.reported} \u2713`};const t=e.ownerInstalled!==void 0&&e.ownerInstalled!==e.target?`its workspace's node_modules holds ${e.ownerInstalled}, so the install did not land \u2014 run it${e.ownerRoot?` in ${e.ownerRoot}`:""}, then \`lensmcp upgrade ${e.target}\` again`:"it restarted onto files that are not the target \u2014 check `lensmcp gateway status`";return{ok:!1,line:` daemon \u2192 runs ${e.reported}, not ${e.target}: ${t}.`}}i(daemonVerdict,"daemonVerdict"),c(daemonVerdict,"daemonVerdict"),p(daemonVerdict,"daemonVerdict");export function setupReminder(e){if(!e.installedVersion||e.setupVersion===e.installedVersion)return;const t=e.setupVersion?`last set up for ${e.setupVersion}`:"not set up yet";return` setup \u2192 '${e.key}' is ${t}, and ${e.installedVersion} is installed: run \`lensmcp setup\` in ${e.root} \u2014 the lens tools are gated until it does.`}i(setupReminder,"setupReminder"),c(setupReminder,"setupReminder"),p(setupReminder,"setupReminder");export function planRestart(e){const t=new Map(e.known.map(o=>[o.key,o.root]));for(const o of e.upgraded)t.set(o.key,o.root);const n=new Set(e.upgraded.map(o=>o.key)),r=new Set(e.registeredKeys),s=e.daemonWsKey!==void 0&&e.daemonVersion!==void 0&&e.targetVersion!==void 0&&e.daemonVersion!==e.targetVersion;if(e.upgraded.length===0&&!s)return{steps:[],note:"nothing was upgraded"};if(!e.daemonWsKey)return{steps:[],note:"no gateway daemon is running \u2014 the next `lensmcp gateway start` picks up the new version"};const a=e.upgraded.find(o=>o.key===e.daemonWsKey),d=a?.root??t.get(e.daemonWsKey),u=[];if(a||s&&d){u.push({key:e.daemonWsKey,root:d,action:"restart",reason:a?"upgraded":"stale-daemon"});for(const o of e.registeredKeys){if(o===e.daemonWsKey)continue;const l=t.get(o);l&&u.push({key:o,root:l,action:"reregister",reason:n.has(o)?"upgraded":"rehost"})}return{steps:u}}if(s&&!d)return{steps:[],note:`the daemon ('${e.daemonWsKey}') runs ${e.daemonVersion} but its root is unknown \u2014 restart it with \`lensmcp gateway restart\` in its workspace`};for(const o of e.upgraded)r.has(o.key)&&u.push({key:o.key,root:o.root,action:"reregister",reason:"upgraded"});return u.length===0?{steps:[],note:`the running daemon ('${e.daemonWsKey}') hosts none of the upgraded workspaces`}:{steps:u}}i(planRestart,"planRestart"),c(planRestart,"planRestart"),p(planRestart,"planRestart");export function setupAfterUpgrade(e){const t=setupReminder(e);return t?e.skip||!e.bin||!installLanded(e.installedVersion,e.targetVersion)?{action:"remind",line:t}:{action:"run",bin:e.bin}:{action:"current"}}i(setupAfterUpgrade,"setupAfterUpgrade"),c(setupAfterUpgrade,"setupAfterUpgrade"),p(setupAfterUpgrade,"setupAfterUpgrade");export function setupWith(e,t,n){return w(process.execPath,[e,"setup","--no-recycle"],{cwd:t,env:n,stdio:"inherit"}).status??1}i(setupWith,"setupWith"),c(setupWith,"setupWith"),p(setupWith,"setupWith");
3
+ ${n}`)?"absent":"unknown"}i(registryAnswer,"registryAnswer"),c(registryAnswer,"registryAnswer");export function lensPackagesIn(e){const t=new Set,n=/(?<![\w-])(@lensmcp\/[a-z0-9][a-z0-9-]*)(?=["'@:\s/])/g,r=/(?:(?<=node_modules\/)|(?<![\w@/.-]))(lensmcp)(?=["'@:\s])/g;for(const o of e){for(const a of o.matchAll(n))t.add(a[1]);for(const a of o.matchAll(r))t.add(a[1])}return[...t].sort()}i(lensPackagesIn,"lensPackagesIn"),c(lensPackagesIn,"lensPackagesIn");export function missingFromRegistry(e,t,n){return e.filter(r=>registryAnswer(n(r),t)==="absent")}i(missingFromRegistry,"missingFromRegistry"),c(missingFromRegistry,"missingFromRegistry");export function installedCliFor(e,t){const n=l(e,"node_modules","lensmcp"),r=l(n,"bin.js");try{return JSON.parse(f(l(n,"package.json"),"utf8")).version===t&&g(r)?r:void 0}catch{return}}i(installedCliFor,"installedCliFor"),c(installedCliFor,"installedCliFor");export function restartGatewayWith(e,t,n){return k(process.execPath,[e,"gateway","restart"],{cwd:t,env:n,stdio:"inherit"}).status??1}i(restartGatewayWith,"restartGatewayWith"),c(restartGatewayWith,"restartGatewayWith");export function installLanded(e,t){return e===t}i(installLanded,"installLanded"),c(installLanded,"installLanded");export function needsInstall(e){return e.rewritten||e.installedVersion!==e.targetVersion}i(needsInstall,"needsInstall"),c(needsInstall,"needsInstall");export function daemonVerdict(e){if(e.reported===e.target)return{ok:!0,line:` daemon \u2192 now running ${e.reported} \u2713`};const t=e.ownerInstalled!==void 0&&e.ownerInstalled!==e.target?`its workspace's node_modules holds ${e.ownerInstalled}, so the install did not land \u2014 run it${e.ownerRoot?` in ${e.ownerRoot}`:""}, then \`lensmcp upgrade ${e.target}\` again`:"it restarted onto files that are not the target \u2014 check `lensmcp gateway status`";return{ok:!1,line:` daemon \u2192 runs ${e.reported}, not ${e.target}: ${t}.`}}i(daemonVerdict,"daemonVerdict"),c(daemonVerdict,"daemonVerdict");export function setupReminder(e){if(!e.installedVersion||e.setupVersion===e.installedVersion)return;const t=e.setupVersion?`last set up for ${e.setupVersion}`:"not set up yet";return` setup \u2192 '${e.key}' is ${t}, and ${e.installedVersion} is installed: run \`lensmcp setup\` in ${e.root} \u2014 the lens tools are gated until it does.`}i(setupReminder,"setupReminder"),c(setupReminder,"setupReminder");export function planRestart(e){const t=new Map(e.known.map(s=>[s.key,s.root]));for(const s of e.upgraded)t.set(s.key,s.root);const n=new Set(e.upgraded.map(s=>s.key)),r=new Set(e.registeredKeys),o=e.daemonWsKey!==void 0&&e.daemonVersion!==void 0&&e.targetVersion!==void 0&&e.daemonVersion!==e.targetVersion;if(e.upgraded.length===0&&!o)return{steps:[],note:"nothing was upgraded"};if(!e.daemonWsKey)return{steps:[],note:"no gateway daemon is running \u2014 the next `lensmcp gateway start` picks up the new version"};const a=e.upgraded.find(s=>s.key===e.daemonWsKey),p=a?.root??t.get(e.daemonWsKey),d=[];if(a||o&&p){d.push({key:e.daemonWsKey,root:p,action:"restart",reason:a?"upgraded":"stale-daemon"});for(const s of e.registeredKeys){if(s===e.daemonWsKey)continue;const u=t.get(s);u&&d.push({key:s,root:u,action:"reregister",reason:n.has(s)?"upgraded":"rehost"})}return{steps:d}}if(o&&!p)return{steps:[],note:`the daemon ('${e.daemonWsKey}') runs ${e.daemonVersion} but its root is unknown \u2014 restart it with \`lensmcp gateway restart\` in its workspace`};for(const s of e.upgraded)r.has(s.key)&&d.push({key:s.key,root:s.root,action:"reregister",reason:"upgraded"});return d.length===0?{steps:[],note:`the running daemon ('${e.daemonWsKey}') hosts none of the upgraded workspaces`}:{steps:d}}i(planRestart,"planRestart"),c(planRestart,"planRestart");export function setupAfterUpgrade(e){const t=setupReminder(e);return t?e.skip||!e.bin||!installLanded(e.installedVersion,e.targetVersion)?{action:"remind",line:t}:{action:"run",bin:e.bin}:{action:"current"}}i(setupAfterUpgrade,"setupAfterUpgrade"),c(setupAfterUpgrade,"setupAfterUpgrade");export function setupWith(e,t,n){return k(process.execPath,[e,"setup","--no-recycle"],{cwd:t,env:n,stdio:"inherit"}).status??1}i(setupWith,"setupWith"),c(setupWith,"setupWith");
@@ -1,2 +1,2 @@
1
- "use strict";var m=Object.defineProperty;var r=(e,n)=>m(e,"name",{value:n,configurable:!0});var l=Object.defineProperty,s=r((e,n)=>l(e,"name",{value:n,configurable:!0}),"r"),y=Object.defineProperty,o=s((e,n)=>y(e,"name",{value:n,configurable:!0}),"r");import{existsSync as d,mkdirSync as h,readFileSync as g,writeFileSync as k}from"node:fs";import{basename as b,join as c}from"node:path";export const INSTANCE_NAME_PATTERN=/^[a-z0-9](?:[a-z0-9-]{0,18}[a-z0-9])?$/;export function instanceSockNs(e){let n=2166136261;for(let t=0;t<e.length;t++)n^=e.charCodeAt(t),n=Math.imul(n,16777619);return`i-${(n>>>0&16777215).toString(16).padStart(6,"0")}`}r(instanceSockNs,"instanceSockNs"),s(instanceSockNs,"instanceSockNs"),o(instanceSockNs,"instanceSockNs");function p(e,n){let t=2166136261;for(let a=0;a<e.length;a++)t^=e.charCodeAt(a),t=Math.imul(t,16777619);return Math.abs(t|0)%n}r(p,"d"),s(p,"l"),o(p,"hashToRange");export function slugify(e){return e.toLowerCase().replace(/^@[^/]+\//,"").replace(/[^a-z0-9]+/g,"-").replace(/(^-|-$)/g,"")||"workspace"}r(slugify,"slugify"),s(slugify,"slugify"),o(slugify,"slugify");export function resolveWorkspaceKey(e){const n=i(c(e,"nx.json"));if(n?.lensmcp?.key)return slugify(n.lensmcp.key);const t=i(c(e,"package.json"));return t?.name?slugify(t.name):n?.name?slugify(n.name):slugify(b(e))}r(resolveWorkspaceKey,"resolveWorkspaceKey"),s(resolveWorkspaceKey,"resolveWorkspaceKey"),o(resolveWorkspaceKey,"resolveWorkspaceKey");const f={dashboard:4321,mcpHttp:4500,bridge:5747};export function derivePorts(e){const n=p(e,200);return{dashboard:f.dashboard+n,mcpHttp:f.mcpHttp+n,bridge:f.bridge+n}}r(derivePorts,"derivePorts"),s(derivePorts,"derivePorts"),o(derivePorts,"derivePorts");export function podNamespace(e){return typeof e.sockNs=="string"&&/^[a-z0-9][a-z0-9-]{0,31}$/.test(e.sockNs)?e.sockNs:isInstanceConfig(e)?instanceSockNs(e.key):e.key}r(podNamespace,"podNamespace"),s(podNamespace,"podNamespace"),o(podNamespace,"podNamespace");export function isInstanceConfig(e){const n=e.instance;return typeof n?.name=="string"&&INSTANCE_NAME_PATTERN.test(n.name)&&typeof n.baseKey=="string"&&n.baseKey.length>0&&e.key===`${n.baseKey}-${n.name}`}r(isInstanceConfig,"isInstanceConfig"),s(isInstanceConfig,"isInstanceConfig"),o(isInstanceConfig,"isInstanceConfig");export function deriveConfig(e){const n=resolveWorkspaceKey(e);return{schemaVersion:1,key:n,ports:derivePorts(n),dashboardBasePath:"/"+n}}r(deriveConfig,"deriveConfig"),s(deriveConfig,"deriveConfig"),o(deriveConfig,"deriveConfig");export function readLensConfig(e){const n=deriveConfig(e),t=i(c(e,".lensmcp","config.json"));if(!t)return n;const a=t.key?slugify(t.key):n.key;return{...t,schemaVersion:1,key:a,ports:{...n.ports,...t.ports??{}},dashboardBasePath:t.dashboardBasePath??"/"+a,...t.mcp?{mcp:{...t.mcp}}:{}}}r(readLensConfig,"readLensConfig"),s(readLensConfig,"readLensConfig"),o(readLensConfig,"readLensConfig");export function ensureLensConfig(e){const n=readLensConfig(e),t=c(e,".lensmcp");d(t)||h(t,{recursive:!0});const a=c(t,"config.json"),u=JSON.stringify(n,null,2)+`
2
- `;return(!d(a)||g(a,"utf8")!==u)&&k(a,u),n}r(ensureLensConfig,"ensureLensConfig"),s(ensureLensConfig,"ensureLensConfig"),o(ensureLensConfig,"ensureLensConfig");function i(e){try{return JSON.parse(g(e,"utf8"))}catch{return}}r(i,"c"),s(i,"c"),o(i,"readJson");
1
+ "use strict";var m=Object.defineProperty;var r=(e,n)=>m(e,"name",{value:n,configurable:!0});var g=Object.defineProperty,s=r((e,n)=>g(e,"name",{value:n,configurable:!0}),"r");import{existsSync as u,mkdirSync as l,readFileSync as f,writeFileSync as y}from"node:fs";import{basename as h,join as a}from"node:path";export const INSTANCE_NAME_PATTERN=/^[a-z0-9](?:[a-z0-9-]{0,18}[a-z0-9])?$/;export function instanceSockNs(e){let n=2166136261;for(let t=0;t<e.length;t++)n^=e.charCodeAt(t),n=Math.imul(n,16777619);return`i-${(n>>>0&16777215).toString(16).padStart(6,"0")}`}r(instanceSockNs,"instanceSockNs"),s(instanceSockNs,"instanceSockNs");function d(e,n){let t=2166136261;for(let o=0;o<e.length;o++)t^=e.charCodeAt(o),t=Math.imul(t,16777619);return Math.abs(t|0)%n}r(d,"l"),s(d,"hashToRange");export function slugify(e){return e.toLowerCase().replace(/^@[^/]+\//,"").replace(/[^a-z0-9]+/g,"-").replace(/(^-|-$)/g,"")||"workspace"}r(slugify,"slugify"),s(slugify,"slugify");export function resolveWorkspaceKey(e){const n=c(a(e,"nx.json"));if(n?.lensmcp?.key)return slugify(n.lensmcp.key);const t=c(a(e,"package.json"));return t?.name?slugify(t.name):n?.name?slugify(n.name):slugify(h(e))}r(resolveWorkspaceKey,"resolveWorkspaceKey"),s(resolveWorkspaceKey,"resolveWorkspaceKey");const i={dashboard:4321,mcpHttp:4500,bridge:5747};export function derivePorts(e){const n=d(e,200);return{dashboard:i.dashboard+n,mcpHttp:i.mcpHttp+n,bridge:i.bridge+n}}r(derivePorts,"derivePorts"),s(derivePorts,"derivePorts");export function podNamespace(e){return typeof e.sockNs=="string"&&/^[a-z0-9][a-z0-9-]{0,31}$/.test(e.sockNs)?e.sockNs:isInstanceConfig(e)?instanceSockNs(e.key):e.key}r(podNamespace,"podNamespace"),s(podNamespace,"podNamespace");export function isInstanceConfig(e){const n=e.instance;return typeof n?.name=="string"&&INSTANCE_NAME_PATTERN.test(n.name)&&typeof n.baseKey=="string"&&n.baseKey.length>0&&e.key===`${n.baseKey}-${n.name}`}r(isInstanceConfig,"isInstanceConfig"),s(isInstanceConfig,"isInstanceConfig");export function deriveConfig(e){const n=resolveWorkspaceKey(e);return{schemaVersion:1,key:n,ports:derivePorts(n),dashboardBasePath:"/"+n}}r(deriveConfig,"deriveConfig"),s(deriveConfig,"deriveConfig");export function readLensConfig(e){const n=deriveConfig(e),t=c(a(e,".lensmcp","config.json"));if(!t)return n;const o=t.key?slugify(t.key):n.key;return{...t,schemaVersion:1,key:o,ports:{...n.ports,...t.ports??{}},dashboardBasePath:t.dashboardBasePath??"/"+o,...t.mcp?{mcp:{...t.mcp}}:{}}}r(readLensConfig,"readLensConfig"),s(readLensConfig,"readLensConfig");export function ensureLensConfig(e){const n=readLensConfig(e),t=a(e,".lensmcp");u(t)||l(t,{recursive:!0});const o=a(t,"config.json"),p=JSON.stringify(n,null,2)+`
2
+ `;return(!u(o)||f(o,"utf8")!==p)&&y(o,p),n}r(ensureLensConfig,"ensureLensConfig"),s(ensureLensConfig,"ensureLensConfig");function c(e){try{return JSON.parse(f(e,"utf8"))}catch{return}}r(c,"c"),s(c,"readJson");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lensmcp",
3
- "version": "1.25.17",
3
+ "version": "1.25.21",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",
@@ -17,9 +17,9 @@
17
17
  }
18
18
  },
19
19
  "dependencies": {
20
- "@frontmcp/sdk": "^1.10.3",
21
- "@lensmcp/cluster": "1.25.17",
22
- "@lensmcp/nx-plugin": "1.25.17",
20
+ "@frontmcp/sdk": "^1.10.10",
21
+ "@lensmcp/cluster": "1.25.21",
22
+ "@lensmcp/nx-plugin": "1.25.21",
23
23
  "reflect-metadata": "^0.2.2",
24
24
  "tslib": "^2.3.0",
25
25
  "vectoriadb": "^2.2.0"
@@ -2,7 +2,7 @@
2
2
  "name": "lensmcp",
3
3
  "displayName": "LensMCP",
4
4
  "description": "The observability lens for coding agents. One command brings up the dev cluster gateway (every project.json `cluster` decl → its host on :443), the per-project lens dashboard at https://lensmcp.local/<project>/, and the MCP server your agent connects to — scoped automatically to whatever project you opened Claude Code in.",
5
- "version": "1.25.17",
5
+ "version": "1.25.21",
6
6
  "author": {
7
7
  "name": "David Antoon",
8
8
  "email": "davidmantoon@gmail.com"
@@ -13,7 +13,8 @@ Run the open script with the Bash tool:
13
13
  It reads the project's mount path from `.lensmcp/config.json` and opens
14
14
  `https://lensmcp.local/<key>/` — the human view of the lens (the same event stream the
15
15
  MCP server reads) — through the one-time sign-in link `lensmcp gateway status` mints, since
16
- the dashboard asks every caller for a credential. The browser then stays signed in. Five
16
+ the dashboard asks every caller for a credential. The browser then stays signed in — to every
17
+ workspace's and worktree's dashboard on that host. Five
17
18
  screens, each answering one question:
18
19
 
19
20
  - **Overview** — is anything wrong right now? (errors first, then health numbers, the
@@ -120,7 +120,7 @@ bootstrap stay available even while the gate is closed.
120
120
  | "the BullMQ queues look empty — is work waiting?" / "how big is the outbox backlog?" / "is the loop that drains it running?" | `observe_sources({ service?, kind? })` — what each service REPORTS (declared in `cluster.sources`): a `queue` waiting in its own tables (state, pending, the oldest wait, groups over `oldestWaitMin`, groups STALLED — nothing taken or finished for `stalledMin`) and the `loop` that drains it (last run, overdue, failing, taking nothing while its queue waits); one whole: `source_get({ service, name })` — its history, a loop's runs, what the app pushed with the `flowId` each ran in (`flow_get`). Only `current` was read — `unreachable` / `refused` / `malformed` / `not-running` / `stale` / `never-polled` mean UNKNOWN, never empty |
121
121
  | "is the partner rate-limiting us?" / "is the document store filling up?" | `observe_sources({ kind: 'traffic' })` — per channel what the app REPORTS: messages, failures, «too many requests», p95 (push-only sources counted from what the app pushes); `observe_sources({ kind: 'store' })` — objects and bytes against its quota, its errors, growth with nothing deleted; one whole: `source_get({ service, name })` with the flows of what it pushed |
122
122
  | "this request is huge — what happened in it?" | `flow_segments`, then `flow_get({ flowId, segmentId })`: a large unit comes back as an outline (one line a node); `expand: '<node id>'` opens a row |
123
- | "what happened when I clicked X?" | `graph_find_backend_work_caused_by_click`, `graph_trace_from_origin` |
123
+ | "what happened when I clicked X?" | `graph_find_backend_work_caused_by_click`, `graph_trace_from_origin` — a click on a router link is a `route-load` flow of its own (`route:<path>`) |
124
124
  | "why did this component re-render?" | `graph_explain_rerender` — with a view update's id, an event id from `flow_get` `expand`, or the component's id (either form) or bare name |
125
125
  | "what's slow?" / "is there a DB call in a loop?" | `graph_find_slow_path`, `graph_find_db_in_loop` |
126
126
  | "why is this page slow to load?" / "what does it wait for?" | `catalog_page({ path })` — ready p50/p95, each visit's requests (in series?) with the routes that answered them, SQL per visit; then `review_page({ page })` |
@@ -132,6 +132,9 @@ bootstrap stay available even while the gate is closed.
132
132
  | "did anything leak memory?" | `memory_explain_leak`, `memory_find_growth_by_flow` |
133
133
  | "why is the dev cluster so heavy / slow to build?" | `observe_current_status` → its `perf-rules` warnings, `perf://rules`, `memory_pods` |
134
134
  | "tell the story of this flow" | `story_compile`, `story_get` |
135
+ | "what did the user do, and what happened under it?" / "replay that bug" | `replay_story({ flowId \| requestId \| visitId \| loadId })` — from the session recording, as text: what was clicked (element, test id, file:line) and typed, the page's requests, and the backend under them with SQL values in place; `replay_list` / `replay://recent` — the recorded page loads |
136
+ | "what values did this query run with?" / "what did that INSERT write?" | `sql_get_values({ valuesId })` — every parameter whole and the rows returned; the `valuesId` is on a db-query step of `flow_get` and on a `replay_story` SQL item |
137
+ | "what did that cache command store or get back?" | `cache_get_value({ valuesId })` — every argument whole and the reply; the `valuesId` is on a redis-op step of `flow_get` and on a `replay_story` cache item |
135
138
  | visual / render checks | `visual_capture_frame`, `visual_inspect_node`, `visual_compare_frames` |
136
139
  | "do this in a worktree" / a risky change / a second task at once | `worktree_create` — then `/lensmcp:worktrees` for the workflow |
137
140
 
@@ -186,6 +189,30 @@ the comment when you change the statement.
186
189
  `review_sql { statement: "<name>", run: true }` — the new version is reviewed against what the old one was asked.
187
190
  - `review_remember { text, subject? }` — a fact about the project the reviewer cannot see, sent with later reviews.
188
191
 
192
+ ## Session replay — what the user did, and what ran under it
193
+
194
+ Every page of the app records itself while it is open through the gateway (Settings › Features › Session replay, on
195
+ by default; kept 14 days, 2 GB). You never get the recording itself — you get it as text:
196
+
197
+ - `replay_story` with ONE of `flowId`, `requestId`, `visitId` or `loadId` (+ `from`/`to`, epoch ms): one time-ordered
198
+ list, each item in a `lane` — `user` (clicks: the element, its test id, its file:line; typing, as a value only for a
199
+ field the recording names, never a password or credential field, secrets removed), `page` (requests with status and
200
+ time, visits, navigation, HMR, errors, lost chunks) and `system` (gateway, services, SQL with every value in place in
201
+ `sql.statement` and its first rows, cache, queue, calls out, AI calls). Follow up by the ids an item carries:
202
+ `flow_get { flowId }`, `sql_get_values { valuesId }`, `ai_get_call { callId: aiCallId }`.
203
+ - A story of one flow or request tells only its own requests; `otherFlows` names the rest, `notShown` counts what it
204
+ left out (info log lines too — `logs_read` reads them). A request is followed into the queue jobs it enqueued.
205
+ A busy request's SQL statements and cache calls are not all on the bus (a rate cap): a note and
206
+ `notShown.sqlNotRecorded` / `cacheNotRecorded` say how many a run made against how many are told ("212 cache calls,
207
+ 30 recorded") — count from those, never from the items.
208
+ Flows (`flow_get`, the story) also hold the files a request read or wrote (`fs-ops`) and the commands it ran (`exec`;
209
+ a non-zero exit is a warning, not a failure). NOT_FOUND = no recording holds it (another app, a page opened outside the gateway, swept); `replay_list`
210
+ shows what is recorded.
211
+ - `sql_get_values { valuesId }` — every parameter of one SQL run at full length and the rows it returned, where a flow
212
+ step keeps 10 values at 80 characters. Secret-bound values are `REDACTED`; `cut: true` past 64 KB.
213
+ - `cache_get_value { valuesId }` — the same for one cache (Redis) command: every argument and the reply, a JSON reply
214
+ parsed (`cav_…` ids on redis-op steps and story items). A key that keeps a credential (a session, a code) is never kept.
215
+
189
216
  ## Pages, routes and reviews — what the app costs, and what Claude found
190
217
 
191
218
  - `catalog://pages`, `catalog_page({ id | path })` — every frontend page by its router pattern (`<app> <pattern>`).
@@ -41,7 +41,8 @@ session's lens onto it, so every lens tool answers about the worktree's app.
41
41
  `root` and start every shell command with `cd <root> &&`.)
42
42
  - A command that needs the worktree's database, queues or hosts — tests against the DB, a script,
43
43
  a migration: `cd <root> && npx lensmcp instance exec -- <command>`.
44
- - Open the app at the `hosts` in the result. Services start on their first request.
44
+ - Open the app at the `hosts` in the result. Its always-warm services start as the worktree comes up, one at a
45
+ time while the machine has memory to spare; the rest (and any held back) start on their first request.
45
46
  5. **Verify with the lens**, exactly as in main: `observe_current_status`, `observe_errors`, flows —
46
47
  they describe the worktree's app now. `worktree_list` → `active` says which checkout the lens reads.
47
48
  6. **Commit** on the worktree's branch: `git -C <root> add … && git -C <root> commit …`. Tell the
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lensmcp",
3
- "version": "1.25.17",
3
+ "version": "1.25.21",
4
4
  "description": "Observe a running Nx workspace from Codex with runtime flows, build health, cluster failures, and a live dashboard.",
5
5
  "author": {
6
6
  "name": "David Antoon",
@@ -13,7 +13,8 @@ node node_modules/lensmcp/bin.js gateway status --cwd .
13
13
 
14
14
  The dashboard asks every caller for a credential, so the bare
15
15
  `https://lensmcp.local/<project-key>/` URL lands on its sign-in page. Open the printed
16
- `sign in →` link instead (one use, ten minutes; the browser then stays signed in) with the
16
+ `sign in →` link instead (one use, ten minutes; the browser then stays signed in — to every
17
+ workspace's and worktree's dashboard on that host, one sign-in for all) with the
17
18
  available browser tool, or give it to the user when browser opening is unavailable — the
18
19
  `dashboardLoginUrl` field of `gateway status --json` is the same link. If the gateway is
19
20
  not running, use the `lensmcp-start` workflow first.
@@ -34,7 +34,7 @@ Use these tools:
34
34
  | Is work waiting in an app's own tables (an outbox) while the BullMQ queues look empty — or is the loop that drains it stuck? | `observe_sources` (`service`, `kind`): queues (pending, oldest wait, stalled groups) and loops (last run, overdue, failing, idle while its queue waits); one source whole: `source_get` with `service` and `name` — its history, a loop's runs, the app's pushed events with their `flowId` (`flow_get`). REPORTED by the app; any state but `current` means unknown, not empty |
35
35
  | Is a partner rate-limiting the app, or is a storage backend filling up? | `observe_sources` with `kind: "traffic"` (per channel: messages, failures, «too many requests», p95) or `kind: "store"` (objects and bytes against its quota, errors, growth with nothing deleted); `source_get` for one whole — REPORTED by the app |
36
36
  | What happened inside one large request? | `flow_segments`, then `flow_get` with `segmentId` (an outline, one line a node; `expand` opens a row) |
37
- | What backend work followed a click? | `graph_find_backend_work_caused_by_click` or `graph_trace_from_origin` |
37
+ | What backend work followed a click? | `graph_find_backend_work_caused_by_click` or `graph_trace_from_origin` (a router link click is its own `route-load` flow, `route:<path>`) |
38
38
  | Why did a component re-render? | `graph_explain_rerender` (a view update's id, an id from `flow_get` `expand`, or the component's name) |
39
39
  | What is slow or running in a loop? | `graph_find_slow_path` or `graph_find_db_in_loop` |
40
40
  | Why is a page slow to load — what does it wait for? | `catalog_page` with `path` (ready p50/p95, each visit's requests and their routes, SQL per visit), then `review_page` |
@@ -46,6 +46,9 @@ Use these tools:
46
46
  | Did memory grow or leak? | `memory_explain_leak` or `memory_find_growth_by_flow` |
47
47
  | Why is the dev cluster heavy or slow to build? | `observe_current_status` (its `perf-rules` warnings), `perf://rules`, `memory_pods` |
48
48
  | Compile a readable flow narrative | `story_compile`, then `story_get` |
49
+ | What did the user do on the page, and what ran under it? | `replay_story` with ONE of `flowId`, `requestId`, `visitId`, `loadId` (+ `from`/`to`): clicks (element, test id, file:line) and typing, the page's requests, the backend with SQL values in place; `replay_list` / `replay://recent` for the recorded loads |
50
+ | What values did a query run with — what did an INSERT write? | `sql_get_values` with `valuesId` (on a db-query step of `flow_get`, or a `replay_story` SQL item): every parameter whole, the rows returned |
51
+ | What did a cache command store or get back? | `cache_get_value` with `valuesId` (on a redis-op step of `flow_get`, or a `replay_story` cache item): every argument whole, the reply |
49
52
  | Inspect visual/render output | `visual_capture_frame`, `visual_inspect_node`, or `visual_compare_frames` |
50
53
  | Work in an isolated worktree with its own app and data | `worktree_create`, then the `lensmcp-worktrees` workflow |
51
54
 
@@ -100,6 +103,29 @@ the comment when you change the statement.
100
103
  `review_sql { statement: "<name>", run: true }` — the new version is reviewed against what the old one was asked.
101
104
  - `review_remember { text, subject? }` — a fact about the project the reviewer cannot see, sent with later reviews.
102
105
 
106
+ ## Session replay — what the user did, and what ran under it
107
+
108
+ Every page of the app records itself while it is open through the gateway (Settings › Features › Session replay, on
109
+ by default; kept 14 days, 2 GB). Agents get it as text, never the recording:
110
+
111
+ - `replay_story` with one of `flowId`, `requestId`, `visitId` or `loadId` (+ `from`/`to`, epoch ms) — one time-ordered
112
+ list; each item's `lane` is `user` (clicks with element, test id and file:line; typing, as a value only for a field
113
+ the recording names, never a password or credential field, secrets removed), `page` (requests with status and time,
114
+ visits, navigation, HMR, errors, lost chunks) or `system` (gateway, services, SQL with every value in place in
115
+ `sql.statement` and its first rows, cache, queue, calls out, AI calls). Follow up by the ids on an item: `flow_get`,
116
+ `sql_get_values`, `ai_get_call` (`aiCallId`).
117
+ - A story of one flow or request tells only its own requests; `otherFlows` names the others, `notShown` counts what it
118
+ left out (info log lines too — `logs_read` reads them). A request is followed into the queue jobs it enqueued.
119
+ A busy request's SQL statements and cache calls are not all on the bus (a rate cap): a note and
120
+ `notShown.sqlNotRecorded` / `cacheNotRecorded` say how many a run made against how many are told ("212 cache calls,
121
+ 30 recorded") — count from those, never from the items.
122
+ Flows (`flow_get`, the story) also hold the files a request read or wrote (`fs-ops`) and the commands it ran (`exec`;
123
+ a non-zero exit is a warning, not a failure). NOT_FOUND means no recording holds it; `replay_list` shows what is recorded.
124
+ - `sql_get_values` with `valuesId` — every parameter of one SQL run at full length and its result rows (a flow step
125
+ keeps 10 values at 80 characters). Secret-bound values are `REDACTED`; `cut: true` past 64 KB.
126
+ - `cache_get_value` with `valuesId` — the same for one cache (Redis) command: every argument and the reply, a JSON
127
+ reply parsed (`cav_…` ids). A key that keeps a credential (a session, a one-time code) is never kept.
128
+
103
129
  ## Pages, routes and reviews
104
130
 
105
131
  - `catalog://pages`, `catalog_page { id | path }` — every frontend page by its router pattern (`<app> <pattern>`). The