@com55/node-red-ai-access 0.2.0-beta.1 → 0.2.0-beta.2
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/CHANGELOG.md +46 -0
- package/README.md +3 -3
- package/ai-access.html +1 -1
- package/dist/plugin.cjs +443 -163
- package/dist/plugin.d.cts +15 -8
- package/package.json +1 -1
package/dist/plugin.cjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var hd=Object.create;var sn=Object.defineProperty;var gd=Object.getOwnPropertyDescriptor;var yd=Object.getOwnPropertyNames;var wd=Object.getPrototypeOf,bd=Object.prototype.hasOwnProperty;var a=(e,t)=>sn(e,"name",{value:t,configurable:!0});var U=(e,t)=>()=>(e&&(t=e(e=0)),t);var ss=(e,t)=>{for(var n in t)sn(e,n,{get:t[n],enumerable:!0})},is=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of yd(t))!bd.call(e,o)&&o!==n&&sn(e,o,{get:()=>t[o],enumerable:!(r=gd(t,o))||r.enumerable});return e};var an=(e,t,n)=>(n=e!=null?hd(wd(e)):{},is(t||!e||!e.__esModule?sn(n,"default",{value:e,enumerable:!0}):n,e)),vd=e=>is(sn({},"__esModule",{value:!0}),e);function je(e,t,n){let r=n.pretty?`${JSON.stringify(e,null,2)}
|
|
2
2
|
`:`${JSON.stringify(e)}
|
|
3
|
-
`,o=Buffer.from(r,"utf8");if(o.byteLength>t)throw new J(n.kind,o.byteLength,t);return{bytes:o}}function is(e,t){let n=(0,ln.resolve)(t),r=(0,ln.resolve)(e);if(r!==n&&!r.startsWith(n+ln.sep))throw new Error("Path escapes .ai-access storage root");return r}function xt(e,t,n){let r=is(t,e),o=`${r}.${process.pid}.tmp`;(0,Oe.writeFileSync)(o,n.bytes,{mode:Dn}),(0,Oe.renameSync)(o,r),(0,Oe.chmodSync)(r,Dn)}function as(e,t,n){let r=is(t,e);(0,Oe.appendFileSync)(r,n.bytes,{mode:Dn});try{(0,Oe.chmodSync)(r,Dn)}catch{}}var Oe,ln,Xr,Dn,$,ss,J,q=j(()=>{"use strict";Oe=require("fs"),ln=require("path"),Xr=448,Dn=384,$=15e5,ss=8192,J=class extends Error{constructor(n,r,o){super(`${n} size ${r} bytes exceeds limit of ${o} bytes`);this.kind=n;this.size=r;this.maxBytes=o;this.name="StoragePayloadTooLargeError"}static{a(this,"StoragePayloadTooLargeError")}code="payload_too_large";statusCode=413};a(De,"encodeTrustedJson");a(is,"assertUnderAiAccess");a(xt,"writeTrustedAtomic");a(as,"appendTrusted")});function un(e){if(typeof e!="string"||e.length===0||e.length>2048)return;let t;try{t=new URL(e)}catch{return}if(!(t.protocol!=="http:"&&t.protocol!=="https:")&&!(t.username||t.password||t.search||t.hash)&&!(t.pathname!=="/"||/^[a-z][a-z0-9+.-]*:\/\/[^/]*\//i.test(e)))return t.origin.toLowerCase()}function vd(e,t){let n=e.getHeader("Vary"),r=String(n??"").split(",").map(o=>o.trim()).filter(Boolean);r.some(o=>o.toLowerCase()===t.toLowerCase())||r.push(t),e.setHeader("Vary",r.join(", "))}function ls(e){return(t,n,r)=>{if(!gd.test(t.path)){r();return}for(let c of n.getHeaderNames())c.startsWith("access-control-")&&n.removeHeader(c);let o=n.setHeader.bind(n),s=!1;n.setHeader=((c,u)=>!s&&/^access-control-/i.test(c)?n:o(c,u));let i=un(t.get("origin")),d=i===void 0?void 0:e().find(c=>c===i),l=d!==void 0;if(vd(n,"Origin"),!l){if(t.method==="OPTIONS"&&t.get("access-control-request-method")){n.status(204).end();return}r();return}if(s=!0,n.setHeader("Access-Control-Allow-Origin",d),n.setHeader("Access-Control-Expose-Headers",bd),t.method==="OPTIONS"&&t.get("access-control-request-method")){n.setHeader("Access-Control-Allow-Methods",yd),n.setHeader("Access-Control-Allow-Headers",wd),n.setHeader("Access-Control-Max-Age",String(600)),n.status(204).end();return}s=!1,r()}}function cs(e,t){let n=e.parent,r=n&&typeof n.use=="function"?n:e;r.use(t);let o=r,i=(o._router??o.router)?.stack;if(!Array.isArray(i)||i.length<2)return;let d=i.pop(),l=0;for(;l<i.length;){let c=i[l].name;if(c!=="query"&&c!=="expressInit")break;l+=1}i.splice(l,0,d)}var gd,yd,wd,bd,Un=j(()=>{"use strict";gd=/^\/mcp\/(?:a(?:\/|$)|api(?:\/|$)|rpc\/?$)/,yd="GET, POST, DELETE, OPTIONS",wd="Authorization, Content-Type, Accept, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID",bd="Mcp-Session-Id, WWW-Authenticate, Retry-After, RateLimit, RateLimit-Policy, RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset";a(un,"canonicalOrigin");a(vd,"appendVary");a(ls,"aiCorsMiddleware");a(cs,"installAiCorsMiddleware")});function Ad(e){return e==="nodes"||e==="flows"||e==="full"}function Bn(e,t){if(!e||typeof e!="object"||Array.isArray(e))return{};let n=e,r={};if(typeof n.enabled=="boolean"&&(r.enabled=n.enabled),typeof n.allowWithoutAdminAuth=="boolean"&&(r.allowWithoutAdminAuth=n.allowWithoutAdminAuth),typeof n.defaultLinkTtlSeconds=="number"&&Number.isFinite(n.defaultLinkTtlSeconds)&&n.defaultLinkTtlSeconds>0&&(r.defaultLinkTtlSeconds=Math.floor(n.defaultLinkTtlSeconds)),Ad(n.defaultDeploymentType)&&(r.defaultDeploymentType=n.defaultDeploymentType),typeof n.maxActiveLinks=="number"&&Number.isFinite(n.maxActiveLinks)&&n.maxActiveLinks>0&&(r.maxActiveLinks=Math.floor(n.maxActiveLinks)),typeof n.publicBaseUrl=="string")try{r.publicBaseUrl=_t(n.publicBaseUrl)}catch{t?.(`AI Access: invalid publicBaseUrl value ${JSON.stringify(n.publicBaseUrl)} was dropped`)}if(Array.isArray(n.corsAllowedOrigins)){let o=new Set;for(let s of n.corsAllowedOrigins){let i=un(s);i?o.add(i):t?.(`AI Access: invalid CORS origin value ${JSON.stringify(s)} was dropped`)}r.corsAllowedOrigins=[...o]}for(let o of["rateLimitWindowMs","rateLimitMaxPerIp","rateLimitMaxPerToken"]){let s=n[o],i=o==="rateLimitWindowMs"?100:1,d=o==="rateLimitWindowMs"?36e5:1e6;typeof s=="number"&&Number.isInteger(s)&&s>=i&&s<=d&&(r[o]=s)}if(n.oauth&&typeof n.oauth=="object"&&!Array.isArray(n.oauth)){let o=n.oauth;r.oauth={},typeof o.enabled=="boolean"&&(r.oauth.enabled=o.enabled);for(let i of["accessTokenTtlSec","refreshIdleTtlSec","dcrMaxClients"]){let d=o[i];typeof d=="number"&&Number.isInteger(d)&&d>0&&d<=31536e3&&(r.oauth[i]=d)}let s=o.refreshMaxLifetimeSec;typeof s=="number"&&Number.isInteger(s)&&s>=0&&s<=31536e4&&(r.oauth.refreshMaxLifetimeSec=s)}return r}function xd(e,t){if(!(0,fn.existsSync)(e))return{};try{let n=(0,fn.readFileSync)(e,"utf8");return Bn(JSON.parse(n),t)}catch(n){return t?.(`AI Access: config.json is corrupt or unreadable; defaults are used (${String(n)})`),{}}}function qn(e,t){let n={},r={...pn};for(let s of Object.keys(pn).filter(i=>i!=="oauth"))n[s]=e[s]!==void 0?"settings.js":t[s]!==void 0?"disk":"default",Object.assign(r,{[s]:e[s]??t[s]??pn[s]});let o={...r,sources:n};return e.oauth!==void 0||t.oauth!==void 0?(o.oauth={...St,...t.oauth??{},...e.oauth??{}},n.oauth=e.oauth!==void 0?"settings.js":"disk"):(o.oauth={...St},n.oauth="default"),o}function Zr(e,t){let n=Bn(e??{},t);for(let o of Object.keys(pn).filter(s=>s!=="oauth"))e&&Object.hasOwn(e,o)&&n[o]===void 0&&Object.assign(n,{[o]:pn[o]});let r=e?.oauth;if(r&&typeof r=="object"&&!Array.isArray(r)){let o=n.oauth??={};for(let s of Object.keys(St))Object.hasOwn(r,s)&&o[s]===void 0&&(t?.(`AI Access: settings.js aiAccess.oauth.${s} is invalid; using the default (${String(St[s])})`),Object.assign(o,{[s]:St[s]}))}return n}var fn,Qr,kd,St,pn,Tt,Wn=j(()=>{"use strict";fn=require("fs"),Qr=require("path");ae();Un();q();kd=16384,St={enabled:!1,accessTokenTtlSec:3600,refreshIdleTtlSec:2592e3,refreshMaxLifetimeSec:7776e3,dcrMaxClients:20},pn={enabled:!0,allowWithoutAdminAuth:!1,defaultLinkTtlSeconds:14400,maxActiveLinks:10,defaultDeploymentType:"nodes",publicBaseUrl:"",corsAllowedOrigins:[],rateLimitWindowMs:6e4,rateLimitMaxPerIp:600,rateLimitMaxPerToken:300,oauth:{...St}};a(Ad,"isDeploymentType");a(Bn,"sanitizePartial");a(xd,"readDiskFile");a(qn,"mergeAiAccessSettings");a(Zr,"settingsJsPartial");Tt=class{constructor(t,n,r){this.paths=t;this.filePath=(0,Qr.join)(t.root,"config.json"),this.settingsJs=Zr(n,r),this.disk=xd((0,fn.existsSync)(this.filePath)?this.filePath:(0,Qr.join)(t.root,"settings.json"),r),this.cached=qn(this.settingsJs,this.disk)}static{a(this,"SettingsStore")}filePath;disk;settingsJs;cached;getEffective(){return this.cached}getDisk(){return{...this.disk}}update(t){let n=Bn(t);for(let r of Object.keys(this.settingsJs))delete n[r];return this.persist(n),this.disk=n,this.cached=qn(this.settingsJs,this.disk),this.cached}patch(t){let n=Bn(t);return this.update({...this.disk,...n,...n.oauth?{oauth:{...this.disk.oauth,...n.oauth}}:{}})}persist(t){let n=De(t,kd,{kind:"settings",pretty:!0});xt(this.paths.root,this.filePath,n)}}});function Gn(e){eo=e}function V(e){return e.adminAuth!=null&&e.adminAuth!==!1}function ge(e){return eo?eo.getEffective():qn(Zr(e.aiAccess),{})}function ye(e){let t=ge(e);return t.enabled?V(e)?!0:t.allowWithoutAdminAuth:!1}var eo,_e=j(()=>{"use strict";Wn();eo=null;a(Gn,"bindSettingsStore");a(V,"isAdminAuthEnabled");a(ge,"resolveAiAccessSettings");a(ye,"areAiFeaturesEnabled")});function ne(e){return!e||e==="/"?"/":e.endsWith("/")?e.slice(0,-1):e}function de(e){let t=ne(e);return t==="/"?"/mcp/api/v1":`${t}/mcp/api/v1`}function je(e){let t=ne(e);return t==="/"?"/mcp/rpc":`${t}/mcp/rpc`}function Sd(e){let t=e.authInfo?.scope;if(t)return Array.isArray(t)?t:[t];let n=e.user?.permissions;return n?Array.isArray(n)?n:[n]:[]}function Pt(e){if(e.auth)return e.auth.scopes.includes("propose")?"propose":e.auth.scopes.includes("read")?"read":null;let t=Sd(e);return t.includes("*")?"*":t.includes("read")?"read":null}function Td(e,t){return!t||!e?{view:!0,manageLinks:!1,approveProposals:!1}:e==="*"?{view:!0,manageLinks:!0,approveProposals:!0}:{view:!0,manageLinks:!1,approveProposals:!1}}function us(e,t={error:"Authentication required",code:"auth_required"},n){let r=e.end.bind(e),o=!0;return e.end=((...s)=>o&&e.statusCode===401&&!e.headersSent?(o=!1,e.removeHeader("Content-Length"),e.setHeader("Content-Type","application/json; charset=utf-8"),n&&e.setHeader("WWW-Authenticate",n),r(JSON.stringify(t))):r(...s)),()=>{o=!1,e.end=r}}function we(e,t){let n="read";return(r,o,s)=>{if(!V(e.settings)){s();return}let i=t==="api"?us(o):null;e.auth.needsPermission(n)(r,o,d=>{if(i?.(),d){s(d);return}if(!r.user){if(t==="page"){o.redirect(ne(e.settings.httpAdminRoot));return}o.status(401).json({error:"Authentication required",code:"auth_required"});return}if(!Pt(r)){o.status(401).json({error:"Insufficient permissions",code:"insufficient_permissions"});return}s()})}}function zn(e,t){let n=V(e.settings),r=ge(e.settings),o=ye(e.settings),s=null;return n?s=Pt(t):o&&(s="*"),{adminAuthEnabled:n,aiFeaturesEnabled:o,allowWithoutAdminAuth:r.allowWithoutAdminAuth,permission:s,capabilities:Td(s,o)}}function Rt(e){return(t,n,r)=>{if(!ye(e.settings)){n.status(403).json({error:"AI Access features are disabled. Enable adminAuth, or set aiAccess.allowWithoutAdminAuth to true for local-only use.",code:"ai_features_disabled"});return}r()}}function mn(e){return(t,n,r)=>{if(!V(e.settings)){if(ye(e.settings)){r();return}n.status(403).json({error:"AI Access features are disabled",code:"ai_features_disabled"});return}if(Pt(t)!=="*"){n.status(403).json({error:"Full admin permission (*) is required for this action",code:"insufficient_permissions"});return}r()}}function _d(e){return e.startsWith("aia_")}function Pd(e){return _d(e)?!0:e.length<24}function Rd(e){if(e.length<24||e.startsWith("aia_")&&e.length<40)return"The token may be truncated \u2014 ask the user for the full access-link token."}function ps(e,t,n,r){if(!V(e.settings)){r();return}let o={error:le,code:Re},s=us(n,o,K);e.auth.needsPermission("read")(t,n,i=>{if(s(),i){r(i);return}if(!n.headersSent){if(!t.user||!Pt(t)){n.setHeader("WWW-Authenticate",K),n.status(401).json(o);return}r()}})}function fs(e,t,n,r,o){let s=e.headers.authorization;if(typeof s!="string"||!/^Bearer\s+\S+/i.test(s.trim()))return"continue";let d=/^Bearer\s+(\S+)$/i.exec(s.trim())?.[1];if(!d)return"continue";let l=n(d);if(l.status==="active")return l.link.kind!==r?(yn(t,r,l.link.kind),"wrong_kind"):(e.accessLink=l.link,"ok");if(l.status==="expired")return gn(e,t,o,l.expiresAt),"invalid";if(Pd(d)){t.setHeader("WWW-Authenticate",K);let c=Rd(d);return t.status(401).json({error:et,code:hn,...c?{hint:c}:{}}),"invalid"}return"continue"}function ms(e,t,n="rest"){return(r,o,s)=>{if(!ye(e.settings)){o.status(403).json({error:"AI Access features are disabled. Enable adminAuth, or set aiAccess.allowWithoutAdminAuth to true for local-only use.",code:"ai_features_disabled"});return}let i=fs(r,o,t,n,e.settings.httpAdminRoot);if(i==="ok"){s();return}i==="invalid"||i==="wrong_kind"||ps(e,r,o,s)}}function hs(e,t,n="rest"){return(r,o,s)=>{let i=fs(r,o,t,n,e.settings.httpAdminRoot);if(i==="ok"){s();return}i==="invalid"||i==="wrong_kind"||ps(e,r,o,s)}}var Pe=j(()=>{"use strict";tt();_e();a(ne,"normalizeHttpAdminRoot");a(de,"mcpApiBase");a(je,"mcpRpcPath");a(Sd,"scopesFromRequest");a(Pt,"resolvePermission");a(Td,"capabilitiesFor");a(us,"interceptNeedsPermissionUnauthorized");a(we,"requireAdminSession");a(zn,"describeAccess");a(Rt,"requireAiFeatures");a(mn,"requireStarPermission");a(_d,"looksLikeAccessLinkToken");a(Pd,"looksLikeFailedAccessLinkAttempt");a(Rd,"truncatedTokenHint");a(ps,"continueAsAdminSessionOrAccessLinkMissing");a(fs,"tryAccessLinkBearer");a(ms,"requireAdminReadOrAccessLink");a(hs,"requireAdminSessionOrAccessLink")});function Ld(e,t){let n=ne(e);return`${n==="/"?"/mcp/a":`${n}/mcp/a`}/${t}`}function Cd(e){return!!e.app.get("trust proxy")}function gs(e,t){let n=e.get(t);if(typeof n!="string"||n.length===0)return;let r=n.split(",")[0]?.trim();return r&&r.length>0?r:void 0}function Ed(e){if(typeof e!="string"||e.trim().length===0)return;let t;try{t=new URL(e.trim())}catch{return}if(!(t.protocol!=="http:"&&t.protocol!=="https:"))return`${t.origin}${t.pathname.replace(/\/+$/,"")}`}function nt(e){try{let n=new URL(`http://${e.includes(":")&&!e.startsWith("[")?`[${e}]`:e}`).hostname.replace(/^\[|\]$/g,"");if(!(0,to.isIP)(n))return;if(n.startsWith("::ffff:")){let r=n.slice(7);if((0,to.isIP)(r)===4)return r;let o=r.split(":");if(o.length===2){let s=parseInt(o[0],16)*65536+parseInt(o[1],16);return[24,16,8,0].map(i=>s>>>i&255).join(".")}}return n}catch{return}}function _t(e){if(e==="")return"";let t="publicBaseUrl must be an HTTP(S) origin with no path, query, fragment, or credentials";if(typeof e!="string")throw new Error(t);let n;try{n=new URL(e.trim())}catch{throw new Error(t)}if(n.protocol!=="https:"&&n.protocol!=="http:"||!/^https?:\/\/[^/?#]+\/?$/i.test(e.trim())||n.pathname!=="/"||n.search||n.hash||n.username||n.password||e.includes("?")||e.includes("#"))throw new Error(t);return n.origin}function Jn(e){no=Ed(e)}function ws(e){if(typeof e!="string"||e.length===0||e.length>260)return!1;let t,n,r=/^(\[[^\]]*\])(?::(\d+))?$/.exec(e);if(r){if(t=r[1],n=r[2],!Fd.test(t))return!1;try{new URL(`http://${t}/`)}catch{return!1}}else{let o=/^([^:]+)(?::(\d+))?$/.exec(e);if(!o||(t=o[1],n=o[2],t.length>253)||!Nd.test(t)&&!Id.test(t))return!1}if(n!==void 0){if(!/^\d{1,5}$/.test(n))return!1;let o=Number(n);if(o<1||o>65535)return!1}return!0}function vs(e){let t=e?.trim().toLowerCase();return t==="http"||t==="https"?t:void 0}function Od(e){let t=e.socket;return t?.encrypted?"https":t?"http":vs(e.protocol)??"http"}function ro(e){if(no)return no;let t=Cd(e),n=t?gs(e,"x-forwarded-host"):void 0,r=e.get("host")?.trim(),o=ws(n)?n:ws(r)?r:void 0;if(!o)return;let s=(t?vs(gs(e,"x-forwarded-proto")):void 0)??Od(e);try{return new URL(`${s}://${o}`).origin}catch{return}}function wn(e,t){let n=ro(e);return n?`${n}${t}`:t}function oo(e,t,n){return wn(e,Ld(t,n))}function so(e){return e.replace(/\/mcp\/a\/[^/?#]+/gi,"/mcp/a/<redacted>")}var to,Lt,bs,no,ys,Id,Nd,Fd,ae=j(()=>{"use strict";to=require("net");Pe();Lt=8192,bs=1800*1e3;a(Ld,"mcpAccessLinkPath");a(Cd,"isTrustProxyEnabled");a(gs,"firstForwardedValue");a(Ed,"normalizePublicBaseUrl");a(nt,"canonicalizeIp");a(_t,"validateSettingsPublicBaseUrl");a(Jn,"bindPublicBaseUrl");ys="[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?",Id=new RegExp(`^${ys}(?:\\.${ys})*$`),Nd=/^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/,Fd=/^\[[0-9A-Fa-f:.]+\]$/;a(ws,"isValidHostHeader");a(vs,"validScheme");a(Od,"connectionScheme");a(ro,"requestPublicOrigin");a(wn,"absolutizePath");a(oo,"buildAccessLinkUrl");a(so,"redactAccessLinkTokenInPath")});function Ud(e){if(!e||e.trim().length===0)return{kind:"missing"};let t=e.trim();if(/^Basic\s+/i.test(t))return{kind:"basic"};let n=/^Bearer\s+(\S+)$/i.exec(t);return n?.[1]?{kind:"bearer",token:n[1]}:{kind:"other"}}function io(e,t,n,r){e.setHeader("WWW-Authenticate",K),e.status(401).json({error:t,code:n,...r?{hint:r}:{}})}function Bd(e){let t=e??"";for(;t.endsWith("/");)t=t.slice(0,-1);return`${t}/mcp#links`}function qd(e,t,n,r){let o=wn(e,Bd(t));return{error:Md,code:Dd,...n?{expiredAt:n}:{},accessLinksUrl:o,hint:r??`Ask the user to create a new access link in Node-RED on the /mcp page \u2192 Access links (${o}).`}}function gn(e,t,n,r,o){t.setHeader("WWW-Authenticate",K),t.status(401).json(qd(e,n,r,o))}function Wd(e){if(e.length<24||e.startsWith("aia_")&&e.length<40)return"The token may be truncated \u2014 ask the user for the full access-link token."}function Vn(e){return e==="get_link"?"Please open Node-RED \u2192 /mcp \u2192 Access links and create a get_link access URL, then send me that /mcp/a/<token> link.":`Please open Node-RED \u2192 /mcp \u2192 Access links and create a kind=${e} token, then send me the Bearer token (not a get_link path URL).`}function zd(e,t){let n=Gd;return e==="get_link"?`This URL requires a get_link token. This token is kind=${t} \u2014 ${n} for a get_link access URL (/mcp/a/<token>/\u2026). What to tell the user: ${Vn("get_link")}`:e==="rest"?`This REST endpoint requires a rest token. This token is kind=${t} \u2014 ${n}. What to tell the user: ${Vn("rest")}`:`This MCP endpoint requires an mcp token. This token is kind=${t} \u2014 ${n}. What to tell the user: ${Vn("mcp")}`}function yn(e,t,n){e.setHeader("WWW-Authenticate",K),e.status(403).json({error:`Access link kind mismatch: expected ${t}, got ${n}`,code:Hd,expectedKind:t,actualKind:n,hint:zd(t,n),tellUser:Vn(t)})}function Ct(e,t,n){return(r,o,s)=>{if(!ye(e.settings)){o.status(403).json({error:"AI Access features are disabled. Enable adminAuth, or set aiAccess.allowWithoutAdminAuth to true for local-only use.",code:"ai_features_disabled"});return}let i=Ud(r.headers.authorization);if(i.kind==="basic"){io(o,$d,jd);return}if(i.kind==="missing"||i.kind==="other"){io(o,le,Re);return}let d=t.validateResult(i.token);if(d.status==="expired"){gn(r,o,e.settings.httpAdminRoot,d.expiresAt);return}if(d.status!=="active"){io(o,et,hn,Wd(i.token));return}if(d.link.kind!==n){yn(o,n,d.link.kind);return}r.accessLink=d.link,s()}}var et,Md,le,$d,K,Re,hn,Dd,jd,Hd,Gd,tt=j(()=>{"use strict";ae();_e();et="Access link is invalid, revoked, or no longer available (it may have expired, or Node-RED may have restarted). Ask the user for a new access link.",Md="Access link token has expired. Ask the user for a new access link.",le="Access link Bearer token is missing. Ask the user for a new access link and send Authorization: Bearer <token>.",$d="Authorization must use the Bearer scheme (Authorization: Bearer <access-link-token>), not Basic. Ask the user for a new access link.",K='Bearer realm="node-red-ai-access", charset="UTF-8"',Re="access_link_missing",hn="access_link_invalid",Dd="token_expired",jd="bearer_scheme_required",Hd="wrong_token_kind";a(Ud,"parseAuthorizationHeader");a(io,"sendUnauthorized");a(Bd,"accessLinksPagePath");a(qd,"tokenExpiredBody");a(gn,"sendTokenExpired");a(Wd,"truncatedBearerHint");Gd="ask the owner: Node-RED /mcp \u2192 Access links \u2192 create kind=rest/mcp (or get_link)";a(Vn,"whatToTellUserWrongTokenKind");a(zd,"wrongTokenKindHint");a(yn,"sendWrongTokenKind");a(Ct,"requireAccessLink")});var W,B,be,Le,Xn,ve=j(()=>{"use strict";W=class extends Error{static{a(this,"FlowRevisionConflictError")}code="version_mismatch";statusCode=409;expectedRev;currentRev;context;constructor(t,n,r="updateFlow"){super(`Flow revision conflict in ${r}: expectedRev '${t}' does not match current rev '${n}'. The flows changed since you last read them \u2014 re-read flows, re-apply your change, and retry.`),this.name="FlowRevisionConflictError",this.expectedRev=t,this.currentRev=n,this.context=r}},B=class extends Error{static{a(this,"FlowNotFoundError")}code="flow_not_found";statusCode=404;flowId;constructor(t){super(`Flow '${t}' was not found`),this.name="FlowNotFoundError",this.flowId=t}},be=class extends Error{static{a(this,"AdapterUnavailableError")}code="adapter_unavailable";statusCode=502;constructor(t){super(t),this.name="AdapterUnavailableError"}},Le=class extends Error{static{a(this,"PluginMisconfiguredError")}code="plugin_misconfigured";statusCode=502;constructor(t){super(t),this.name="PluginMisconfiguredError"}},Xn="Node-RED AI Access could not use the in-process flow runtime and the HTTP Admin API fallback returned Unauthorized. The plugin is misconfigured \u2014 tell the admin to ensure the plugin loads inside Node-RED so RED.runtime.flows is available (do not rely on unauthenticated localhost HTTP when adminAuth is enabled)."});function Ce(e,t){if(!e||!Jd.test(e)||e.includes(".."))throw new Error(`Invalid ${t}`);return e}function Yn(e){if(!e||!Vd.test(e)||e.includes(".."))throw new Error("Invalid proposal id");return e}function ao(e,t){let n=(0,rt.basename)(t);if(!n||n!==t||n.includes(".."))throw new Error("Invalid file name");let r=(0,rt.resolve)(e),o=(0,rt.resolve)(r,n);if(o!==r&&!o.startsWith(r+rt.sep))throw new Error("Path escapes storage directory");return o}function ks(e,t=1e5){if(typeof e=="string")return e.replace(/\0/g,"").slice(0,t)}function Et(e){let t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},n={};for(let o of Kd){if(!(o in t))continue;let s=t[o];if(o==="disabled"&&typeof s=="boolean"){n[o]=s;continue}if(o==="env"&&Array.isArray(s)){let d=JSON.stringify(s);d.length<=5e5&&(n[o]=JSON.parse(d));continue}let i=ks(s,1e4);i!==void 0&&(n[o]=i)}for(let o of["nodes","configs"]){let s=t[o];if(!Array.isArray(s))continue;let i=JSON.stringify(s),d=Buffer.byteLength(i,"utf8");if(d>$)throw new J(`flow.${o}`,d,$);n[o]=JSON.parse(i)}let r=Buffer.byteLength(JSON.stringify(n),"utf8");if(r>$)throw new J("flow",r,$);return n}function As(e){let t={};for(let n of Xd){if(!(n in e))continue;let r=e[n];if(Yd.has(n)){if(typeof r=="number"&&Number.isFinite(r))t[n]=r;else if(typeof r=="string"&&r.trim()!==""){let s=Number(r);Number.isFinite(s)&&(t[n]=s)}continue}let o=ks(r,2e3);o!==void 0&&(t[n]=o)}return t}var rt,Jd,Vd,Kd,Xd,Yd,Qn=j(()=>{"use strict";rt=require("path");q();Jd=/^[A-Za-z0-9_.-]+$/,Vd=/^prop-[A-Za-z0-9_-]+$/,Kd=["id","type","label","disabled","info","env"],Xd=["proposalId","flowId","expectedRev","resultRev","reason","code","currentRev","title","displayName","flowLabel","linkId","label","expiresAt","createdAt","ttlSeconds","kind"],Yd=new Set(["ttlSeconds"]);a(Ce,"assertSafeSegment");a(Yn,"assertSafeProposalId");a(ao,"resolveUnderDir");a(ks,"sanitizeString");a(Et,"sanitizeFlowTab");a(As,"sanitizeAuditDetails")});function lo(e,t){return`oauth-${(0,er.createHash)("sha256").update(`${e}\0${t}`).digest("hex")}`}function ot(e){return e.kind==="oauth"?lo(e.clientId,e.user):e.linkId}function Qd(e){return e==="pending"||e==="approved"||e==="rejected"||e==="failed"||e==="withdrawn"||e==="expired"||e==="superseded"}function xs(e){if(!Array.isArray(e)||e.length===0)return;let t=[];for(let n of e.slice(0,Zd)){if(!n||typeof n!="object"||Array.isArray(n))continue;let r=n;if(typeof r.code!="string"||!r.code||typeof r.error!="string"||!r.error)continue;let o={code:r.code.replace(/\0/g,"").slice(0,80),error:r.error.replace(/\0/g,"").slice(0,500)};typeof r.nodeId=="string"&&r.nodeId&&(o.nodeId=r.nodeId.replace(/\0/g,"").slice(0,200)),typeof r.type=="string"&&r.type&&(o.type=r.type.replace(/\0/g,"").slice(0,200)),typeof r.property=="string"&&r.property&&(o.property=r.property.replace(/\0/g,"").slice(0,200)),typeof r.configType=="string"&&r.configType&&(o.configType=r.configType.replace(/\0/g,"").slice(0,200)),Array.isArray(r.candidates)&&(o.candidates=r.candidates.filter(s=>typeof s=="string"&&s.length>0).slice(0,20).map(s=>s.replace(/\0/g,"").slice(0,200))),t.push(o)}return t.length>0?t:void 0}var er,He,Ss,re,ee,It,bn,Zd,Zn,Ue=j(()=>{"use strict";er=require("crypto"),He=require("fs"),Ss=require("path");Qn();q();a(lo,"oauthOwnerId");a(ot,"proposalOwnerId");re=class extends Error{static{a(this,"ProposalNotFoundError")}code="proposal_not_found";statusCode=404;constructor(t){super(`Proposal '${t}' was not found`),this.name="ProposalNotFoundError"}},ee=class extends Error{static{a(this,"ProposalNotPendingError")}code="proposal_not_pending";statusCode=409;constructor(t,n){super(`Proposal '${t}' is '${n}', not pending`),this.name="ProposalNotPendingError"}},It=class extends Error{static{a(this,"ProposalNotOwnedError")}code="proposal_not_owned";statusCode=403;constructor(t){super(`Proposal '${t}' was not created by this access link`),this.name="ProposalNotOwnedError"}},bn=class extends Error{static{a(this,"ProposalConcurrentApproveError")}code="proposal_approve_in_progress";statusCode=409;constructor(t){super(`Proposal '${t}' is already being approved`),this.name="ProposalConcurrentApproveError"}};a(Qd,"isProposalStatus");Zd=50;a(xs,"sanitizeProposalWarnings");Zn=class{constructor(t,n={}){this.paths=t;this.now=n.now??(()=>Date.now()),this.randomId=n.randomId??(()=>(0,er.randomBytes)(12).toString("hex")),this.maxProposalBytes=n.maxProposalBytes??$}static{a(this,"ProposalStore")}inFlight=new Set;now;randomId;maxProposalBytes;fileFor(t){let n;try{n=Yn(t)}catch{throw new re(t)}return ao(this.paths.proposals,`${n}.json`)}toDiskRecord(t){let n=Yn(t.id),r=Ce(String(t.expectedRev).slice(0,200),"expectedRev"),o=Ce(String(t.payload.flowId).slice(0,200),"flowId"),s=Et(t.payload.flow),i=t.baseFlow!==void 0?Et(t.baseFlow):void 0,d=t.createdBy.kind==="oauth"?{kind:"oauth",clientId:Ce(String(t.createdBy.clientId).slice(0,200),"clientId"),user:String(t.createdBy.user).replace(/\0/g,"").slice(0,200)}:{kind:"access-link",linkId:Ce(String(t.createdBy.linkId).slice(0,200),"linkId"),...typeof t.createdBy.label=="string"?{label:t.createdBy.label.replace(/\0/g,"").slice(0,200)}:{}},l={id:n,type:"replace_flow",status:Qd(t.status)?t.status:"pending",expectedRev:r,payload:{flowId:o,flow:s},createdBy:d,createdAt:String(t.createdAt).slice(0,64),updatedAt:String(t.updatedAt).slice(0,64)};i!==void 0&&(l.baseFlow=i),typeof t.title=="string"&&t.title.length>0&&(l.title=t.title.replace(/\0/g,"").slice(0,80).trim(),l.title||delete l.title);let c=xs(t.warnings);if(c&&(l.warnings=c),t.decidedBy)if(t.decidedBy.kind==="access-link"){let u={kind:"access-link",linkId:Ce(String(t.decidedBy.linkId).slice(0,200),"linkId")};typeof t.decidedBy.label=="string"&&(u.label=t.decidedBy.label.replace(/\0/g,"").slice(0,200)),l.decidedBy=u}else{let u={kind:"admin"};typeof t.decidedBy.username=="string"&&(u.username=t.decidedBy.username.replace(/\0/g,"").slice(0,200)),l.decidedBy=u}return typeof t.reason=="string"&&(l.reason=t.reason.replace(/\0/g,"").slice(0,2e3)),typeof t.resultRev=="string"&&(l.resultRev=Ce(t.resultRev.slice(0,200),"resultRev")),l}writeAtomic(t,n){let r=this.fileFor(t),o=this.toDiskRecord(n),s=De(o,this.maxProposalBytes,{kind:"proposal",pretty:!0});xt(this.paths.root,r,s)}create(t){let n=new Date(this.now()).toISOString(),r=Yn(`prop-${this.randomId()}`),o={id:r,type:"replace_flow",status:"pending",expectedRev:t.expectedRev,payload:{flowId:t.payload.flowId,flow:t.payload.flow},baseFlow:t.baseFlow,title:t.title,createdBy:t.createdBy,createdAt:n,updatedAt:n},s=xs(t.warnings);return s&&(o.warnings=s),this.writeAtomic(r,o),this.get(r)}get(t){let n=this.fileFor(t);if(!(0,He.existsSync)(n))throw new re(t);return JSON.parse((0,He.readFileSync)(n,"utf8"))}list(t){if(!(0,He.existsSync)(this.paths.proposals))return[];let n=(0,He.readdirSync)(this.paths.proposals).filter(o=>o.endsWith(".json")),r=[];for(let o of n){let s=(0,Ss.basename)(o);if(!(s!==o||s.includes("..")))try{let i=JSON.parse((0,He.readFileSync)(ao(this.paths.proposals,s),"utf8"));if(t?.linkId&&ot(i.createdBy)!==t.linkId||t?.status&&i.status!==t.status||t?.flowId&&i.payload.flowId!==t.flowId)continue;r.push(i)}catch{}}return r.sort((o,s)=>o.createdAt<s.createdAt?1:-1)}beginApprove(t){if(this.inFlight.has(t))throw new bn(t);let n=this.get(t);if(n.status!=="pending")throw new ee(t,n.status);return this.inFlight.add(t),n}releaseLock(t){this.inFlight.delete(t)}markApproved(t,n,r){let o=this.get(t);return o.status="approved",o.decidedBy=n,o.resultRev=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markRejected(t,n,r){let o=this.get(t);return o.status="rejected",n&&(o.decidedBy=n),o.reason=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markFailed(t,n,r){let o=this.get(t);return o.status="failed",n&&(o.decidedBy=n),o.reason=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markWithdrawn(t,n,r){let o=this.get(t);if(o.status!=="pending")throw new ee(t,o.status);return o.status="withdrawn",o.decidedBy=n,o.reason=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markClosed(t,n,r,o){let s=this.get(t);if(s.status!=="pending")throw new ee(t,s.status);return s.status=n,o&&(s.decidedBy=o),s.reason=r,s.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,s),this.get(t)}}});function Ts(e,t){if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`${t} must be a node object`);let n=e;if(typeof n.id!="string"||n.id.length===0)throw new Error(`${t} needs a non-empty string id`);if(typeof n.type!="string"||n.type.length===0)throw new Error(`${t} '${n.id}' needs a non-empty string type`);return n}function tr(e,t,n){let r=e.find(d=>d.id===t);if(!r||r.type!=="tab"&&r.type!=="subflow")throw new B(t);let o={...r,id:t,type:r.type};delete o.nodes,delete o.configs,delete o.subflows,n.label!==void 0&&(o.label=n.label),n.disabled!==void 0&&(o.disabled=n.disabled),n.info!==void 0&&(o.info=n.info),n.env!==void 0&&(o.env=n.env);let s=[];for(let[d,l]of(n.nodes??[]).entries())s.push({...Ts(l,`flowData.nodes[${d}]`),z:t});for(let[d,l]of(n.configs??[]).entries())s.push({...Ts(l,`flowData.configs[${d}]`),z:t});return[...e.filter(d=>d.id!==t&&d.z!==t),o,...s]}function vn(e,t){if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`Expected ${t} to be an object with rev and flows`);let n=e;if(typeof n.rev!="string"||n.rev.length===0)throw new Error(`Expected ${t}.rev to be a non-empty string`);if(!Array.isArray(n.flows))throw new Error(`Expected ${t}.flows to be an array`);return{rev:n.rev,flows:n.flows}}function Nt(e,t){if(Array.isArray(t)){let d=t.find(u=>u&&typeof u=="object"&&u.id===e&&(u.type==="tab"||u.type==="subflow"));if(!d)throw new B(e);let l=t.filter(u=>u&&typeof u=="object"&&u.id!==e&&u.z===e),c={id:e,type:d.type,nodes:l};return typeof d.label=="string"&&(c.label=d.label),typeof d.disabled=="boolean"&&(c.disabled=d.disabled),typeof d.info=="string"&&(c.info=d.info),c}if(!t||typeof t!="object")throw new B(e);let n=t,r=typeof n.id=="string"&&n.id.length>0?n.id:e,o=typeof n.type=="string"&&n.type.length>0?n.type:"tab",s=Array.isArray(n.nodes)?n.nodes:[],i={...n,id:r,type:o,nodes:s};return Array.isArray(n.configs)&&(i.configs=n.configs),i}var nr=j(()=>{"use strict";ve();a(Ts,"asFlowNode");a(tr,"replaceFlowTabInFlatFlows");a(vn,"assertFlowsSnapshot");a(Nt,"normalizeFlowTab")});function fo(e){return e?el.has(e)?!0:e.startsWith("subflow:"):!1}function Be(e,t="node.type"){if(e==null||e==="")throw new b(`${t} is required`,"missing_node_type");if(typeof e!="string")throw new b(`${t} must be a non-empty string (got ${typeof e})`,"invalid_node_type",{details:{type:e,got:typeof e}});if(!e.trim())throw new b(`${t} is required`,"missing_node_type");return e}function al(e,t){if(typeof e!="string"||typeof t!="string")return Number.MAX_SAFE_INTEGER;let n=e.toLowerCase(),r=t.toLowerCase();if(n===r)return 0;let o=n.length,s=r.length;if(o===0)return s;if(s===0)return o;let i=new Array(s+1),d=new Array(s+1);for(let l=0;l<=s;l++)i[l]=l;for(let l=1;l<=o;l++){d[0]=l;let c=n.charAt(l-1);for(let u=1;u<=s;u++){let p=c===r.charAt(u-1)?0:1;d[u]=Math.min((d[u-1]??0)+1,(i[u]??0)+1,(i[u-1]??0)+p)}for(let u=0;u<=s;u++)i[u]=d[u]??0}return i[s]??s}function dl(e,t){let n=e.toLowerCase(),r=t.toLowerCase(),o=Math.min(n.length,r.length),s=0;for(;s<o&&n.charAt(s)===r.charAt(s);)s+=1;return s}function cl(e){return Math.max(2,Math.floor(e.length/3))}function mo(e,t,n=5){if(typeof e!="string"||!e)return[];let r=cl(e);return t.filter(s=>typeof s=="string"&&s!==e&&!ll.has(s)&&!s.startsWith("_")&&!s.startsWith("subflow:")).map(s=>({c:s,dist:al(e,s),prefix:dl(e,s)})).filter(s=>s.dist<=r).sort((s,i)=>s.dist-i.dist||i.prefix-s.prefix||s.c.localeCompare(i.c)).slice(0,n).map(s=>s.c)}function ul(e){return e.endsWith("/")?e.slice(0,-1):e}function Is(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,Es.createRequire)(e)}function pl(){try{let t=Is()("@node-red/runtime/lib/settings");return typeof t.available=="function"&&t.available()===!0}catch{return!1}}function Ns(e){let t=e.types,n=Array.isArray(t)?t.filter(i=>typeof i=="string"&&i.length>0):[],r="";if(typeof e.module=="string"&&e.module)r=e.module;else if(typeof e.name=="string"&&e.name)r=e.name;else if(typeof e.id=="string"){let i=String(e.id).split("/")[0];r=i&&i.length>0?i:String(e.id)}let o=typeof e.id=="string"&&e.id?e.id:n.length===1&&r?`${r}/${n[0]}`:r;if(!o||n.length===0)return null;let s={id:o,module:r.length>0?r:o,types:n};return typeof e.version=="string"&&(s.version=e.version),typeof e.enabled=="boolean"&&(s.enabled=e.enabled),typeof e.name=="string"&&(s.name=e.name),s}function ho(e=il,t={}){let n=t.module??"node-red",r=t.version??"4.0.0",o=[...e],s=t.configs??{};return{listNodeSets(){return Promise.resolve([{id:n,module:n,version:r,types:o,enabled:!0,name:n}])},getNodeConfigHtml(i){if(s[i])return Promise.resolve(s[i]??null);let d=i.includes("/")?i.slice(i.lastIndexOf("/")+1):i;return s[d]?Promise.resolve(s[d]??null):Promise.resolve(null)}}}function Fs(e={}){if(pl())try{let n=Is()("@node-red/runtime");if(n.nodes&&typeof n.nodes.getNodeList=="function")return new po(n.nodes)}catch{}if(e.baseUrl)return new uo({baseUrl:e.baseUrl,...e.accessToken?{accessToken:e.accessToken}:{},...e.fetch?{fetch:e.fetch}:{}});throw new Error("No usable NodeTypesAdapter: runtime.nodes unavailable and no HTTP baseUrl")}async function go(e){let t=await e.listNodeSets(),n=new Map;for(let r of t)for(let o of r.types){if(n.has(o))continue;let s={type:o,module:r.module,setId:r.id};typeof r.enabled=="boolean"&&(s.enabled=r.enabled),r.version&&(s.version=r.version),n.set(o,s)}return{byType:n,allNames:[...n.keys()]}}function fl(e,t){let n=t.toLowerCase();return[e.type,e.module,e.category??"",e.description??""].join(`
|
|
4
|
-
`).toLowerCase().includes(n)}async function
|
|
5
|
-
${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(let l of r){let c=l.trim();if(!c)continue;let u=/^(#{1,3})\s*(inputs?|outputs?)\b[\s:]*\n?([\s\S]*)$/i.exec(c);if(u){let p=(u[2]??"").toLowerCase(),f=(u[3]??"").trim();p.startsWith("input")?s=f||c:i=f||c}else o||(o=c.replace(/^#{1,3}\s*/,"").trim())}let d={};return o&&(d.summary=o),s&&(d.inputs=s),i&&(d.outputs=i),!d.summary&&!d.inputs&&!d.outputs&&(d.text=t),d}function
|
|
6
|
-
`;break;case"r":r="\r";break;case"t":r=" ";break;case"b":r="\b";break;case"f":r="\f";break;case"v":r="\v";break;case"0":r="\0";break;case"\\":r="\\";break;case"'":r="'";break;case'"':r='"';break;case"/":r="/";break;default:r=n;break}return{char:r,next:t+1}}function vl(e){return e==="_DEFAULT_"?"":e}function rr(e){let t=e.trimStart();if(t.startsWith("'")||t.startsWith('"')){let r=t[0],o=1,s="";for(;o<t.length;){let i=t[o];if(i==="\\"&&o+1<t.length){let d=bl(t,o+1);s+=d.char,o=d.next;continue}if(i===r)break;s+=i,o+=1}return vl(s)}if(t.startsWith("[")){let r=Os(t);if(!r)return;let o=r.slice(1,-1).trim();if(!o)return[];let s=[],i=0;for(;i<o.length;){for(;i<o.length&&/[\s,]/.test(o.charAt(i));)i+=1;if(i>=o.length)break;let d=o.slice(i),l=rr(d);s.push(l);let c=co(d);i+=c>0?c:1}return s}if(t.startsWith("{")){let r=kn(t);if(!r)return;let o=r.slice(1,-1);if(!o.trim())return{};let s={},i=0;for(;i<o.length;){for(;i<o.length&&/[\s,]/.test(o.charAt(i));)i+=1;if(i>=o.length)break;let d;if(o[i]==="'"||o[i]==='"'){let c=rr(o.slice(i));d=typeof c=="string"?c:"",i+=co(o.slice(i))}else{let c=/^[a-zA-Z_$][\w$]*/.exec(o.slice(i));if(!c)break;d=c[0],i+=d.length}for(;i<o.length&&/\s/.test(o.charAt(i));)i+=1;if(o[i]!==":")break;for(i+=1;i<o.length&&/\s/.test(o.charAt(i));)i+=1;let l=o.slice(i);s[d]=rr(l),i+=co(l)}return s}let n=/^-?\d+(?:\.\d+)?/.exec(t);if(n)return Number(n[0]);if(t.startsWith("true"))return!0;if(t.startsWith("false"))return!1;if(t.startsWith("null"))return null}function co(e){let t=e.trimStart(),n=e.length-t.length;if(t.startsWith("'")||t.startsWith('"')){let o=t[0],s=1;for(;s<t.length;){if(t[s]==="\\"&&s+1<t.length){s+=2;continue}if(t[s]===o)return n+s+1;s+=1}return n+t.length}if(t.startsWith("[")){let o=Os(t);return n+(o?.length??1)}if(t.startsWith("{")){let o=kn(t);return n+(o?.length??1)}let r=/^(?:-?\d+(?:\.\d+)?|true|false|null)/.exec(t);return r?n+r[0].length:n+1}function Os(e){if(!e.startsWith("["))return null;let t=0,n=null,r=!1;for(let o=0;o<e.length;o++){let s=e[o];if(n){if(r){r=!1;continue}if(s==="\\"){r=!0;continue}s===n&&(n=null);continue}if(s==="'"||s==='"'){n=s;continue}if(s==="[")t+=1;else if(s==="]"&&(t-=1,t===0))return e.slice(0,o+1)}return null}function kl(e){return rr(e)}function Al(e,t){for(let n of e)if(n.types.includes(t)){let r=n.id.includes("/")||n.types.length===1?n.id:`${n.module}/${t}`;return{set:n,setId:r}}return null}function yo(e,t){let n=e.toLowerCase();return t.includes(e)?e:t.find(r=>r.toLowerCase()===n)}async function st(e,t,n={}){if(!t||typeof t!="string")throw new b("Missing node type","missing_node_type");let r=n.includeHelp!==!1;if(fo(t)){let l={type:t,module:"node-red",category:"config",inputs:0,outputs:0,defaults:null};return r&&(l.help={summary:"Built-in structural Node-RED type (not a palette node)."}),l}let o=await e.listNodeSets(),s=o.flatMap(l=>l.types),i=Al(o,t);if(!i){let l=yo(t,s);throw l&&l!==t?new b(`Node type '${t}' was not found \u2014 did you mean '${l}'? Types are case-sensitive.`,"node_type_not_found",{statusCode:404,details:{type:t,didYouMean:l,caseSensitive:!0,nearest:[l]}}):new b(`Node type '${t}' was not found`,"node_type_not_found",{statusCode:404,details:{type:t,nearest:mo(t,s,5)}})}let d={type:t,module:i.set.module,setId:i.setId,inputs:1,outputs:1,defaults:null};if(i.set.version&&(d.version=i.set.version),typeof i.set.enabled=="boolean"&&(d.enabled=i.set.enabled),typeof e.getNodeConfigHtml=="function")try{let l=await e.getNodeConfigHtml(i.setId)??await e.getNodeConfigHtml(`${i.set.module}/${t}`)??await e.getNodeConfigHtml(t);if(l){let c=gl(l,t);c&&(c.category&&(d.category=c.category),c.color&&(d.color=c.color),d.inputs=c.inputs??d.inputs,d.outputs=c.outputs??d.outputs,c.defaults&&Object.keys(c.defaults).length>0?d.defaults=c.defaults:d.defaults=null,c.requiredConfigNodeTypes&&(d.requiredConfigNodeTypes=c.requiredConfigNodeTypes),r&&c.help&&(d.help=yl(c.help)))}}catch{}return d}async function Ms(e,t){let{allNames:n}=await go(e),r=new Set(n),o=new Set,s=a(l=>{let c=Be(l,"node.type");if(o.has(c)||fo(c)){o.add(c);return}if(o.add(c),r.has(c))return;let u=yo(c,n);throw u&&u!==c?new b(`Unknown node type '${c}' \u2014 did you mean '${u}'? Types are case-sensitive.`,"unknown_node_type",{details:{type:c,didYouMean:u,caseSensitive:!0,nearest:[u]}}):new b(`Unknown node type '${c}'. Call node-types / palette search; ask the user to install via Manage palette, then recheck.`,"unknown_node_type",{details:{type:c,nearest:mo(c,n,5)}})},"checkType"),i=a(l=>{if(!l||typeof l!="object"||Array.isArray(l))return;let c=l;typeof c.id=="string"&&c.id.length>0&&s(c.type)},"visitNodeOrConfig");a(l=>{if(!l||typeof l!="object"||Array.isArray(l))return;let c=l;if(c.type!==void 0&&s(c.type),Array.isArray(c.nodes))for(let u of c.nodes)i(u);if(Array.isArray(c.configs))for(let u of c.configs)i(u)},"visitTab")(t)}async function $s(e,t){let n=Be(t,"node.type");if(fo(n))return;let{allNames:r}=await go(e);if(r.includes(n))return;let o=yo(n,r);throw o&&o!==n?new b(`Unknown node type '${n}' \u2014 did you mean '${o}'? Types are case-sensitive.`,"unknown_node_type",{details:{type:n,didYouMean:o,caseSensitive:!0,nearest:[o]}}):new b(`Unknown node type '${n}'. Call node-types / palette search; ask the user to install via Manage palette, then recheck.`,"unknown_node_type",{details:{type:n,nearest:mo(n,r,5)}})}async function xl(e,t){let n=new Headers({Accept:"application/json"});t&&n.set("If-None-Match",t);let r=await fetch(e,{headers:n}),o=r.headers.get("etag")??void 0;if(r.status===304)return{status:304,...o?{etag:o}:{}};if(!r.ok)return{status:r.status,body:null,...o?{etag:o}:{}};let s=await r.json();return{status:r.status,body:s,...o?{etag:o}:{}}}async function Sl(e,t){let n=Date.now();if(ke&&n-ke.fetchedAt<sl)return ke.body;let r=ke?.etag,o=await e(t,r);if(o.status===304&&ke)return ke={...ke,fetchedAt:n,...o.etag?{etag:o.etag}:{}},ke.body;if(!o.body||!Array.isArray(o.body.modules)){if(ke)return ke.body;throw new b("Node-RED catalogue unavailable","catalogue_unavailable",{statusCode:502})}return ke={fetchedAt:n,body:o.body,...o.etag?{etag:o.etag}:{}},o.body}async function sr(e,t,n={}){let r=t.trim();if(!r)throw new b("q query parameter is required","missing_query");let o=n.limit??nl,s=n.catalogueUrl??ol,i=n.catalogueFetcher??xl,d=await Sl(i,s),l=Array.isArray(d.modules)?d.modules:[],c=await e.listNodeSets(),u=new Set(c.flatMap(g=>{let v=[g.module,g.id];return g.id.includes("/")&&v.push(g.id.split("/")[0]??g.module),v.map(A=>A.toLowerCase())})),p=r.toLowerCase(),f=[];for(let g of l){let v=g.id.toLowerCase(),A=(g.keywords??[]).map(te=>String(te).toLowerCase()),E=(g.description??"").toLowerCase(),D=Tl(p,v,A,E);if(D<=0)continue;let O={module:g.id,installed:u.has(v)};typeof g.description=="string"&&(O.description=g.description),typeof g.version=="string"&&(O.version=g.version),typeof g.updated_at=="string"&&(O.updated=g.updated_at),typeof g.downloads=="number"&&(O.downloads=g.downloads),typeof g.url=="string"&&(O.link=g.url),f.push({item:O,score:D})}f.sort((g,v)=>v.score-g.score||(v.item.downloads??0)-(g.item.downloads??0)||g.item.module.localeCompare(v.item.module));let m=f.length,h=m>o;return{q:r,modules:f.slice(0,o).map(g=>g.item),total:m,truncated:h,hint:rl}}function Tl(e,t,n,r){if(!e)return 0;if(t===e||t===`node-red-contrib-${e}`||t===`node-red-node-${e}`)return 1e4;let o=0;t.startsWith(`node-red-contrib-${e}`)||t.startsWith(`node-red-node-${e}`)?o+=500:t.startsWith(e)?o+=80:t.includes(e)&&(o+=60);for(let s of n)s===e?o+=50:s.startsWith(e)||e.startsWith(s)?o+=35:s.includes(e)&&(o+=25);return r.includes(e)&&(o+=10),o}var Es,el,tl,nl,rl,ol,sl,il,ll,uo,_s,po,ke,it=j(()=>{"use strict";Es=require("module");G();el=new Set(["tab","subflow","group","junction"]),tl=100,nl=30,rl="Recommend only \u2014 ask the user to install via Manage palette, then recheck node-types. This API never installs. Core Node-RED nodes (module node-red) are not listed in the public catalogue; use node-types for installed core types.",ol="https://catalogue.nodered.org/catalogue.json",sl=3600*1e3,il=["inject","debug","function","template","comment","change","switch","delay","trigger","link in","link out","link call","mqtt in","mqtt out","http in","http response","http request","websocket in","websocket out","tcp in","tcp out","udp in","udp out","catch","status","complete","join","split","sort","batch","csv","html","json","xml","yaml","file","file in","watch","exec","range","rbe"];a(fo,"isStructuralNodeType");a(Be,"requireNodeTypeName");a(al,"editDistance");a(dl,"sharedPrefixLength");ll=new Set(["unknown","tab","subflow","group"]);a(cl,"nearestDistanceCutoff");a(mo,"nearestNodeTypes");a(ul,"trimTrailingSlash");a(Is,"createPluginRequire");a(pl,"isRuntimeSettingsAvailable");a(Ns,"normalizeNodeSet");a(ho,"createStaticNodeTypesAdapter");uo=class{static{a(this,"HttpNodeTypesAdapter")}baseUrl;accessToken;fetchImpl;constructor(t){this.baseUrl=ul(t.baseUrl),this.accessToken=t.accessToken,this.fetchImpl=t.fetch??globalThis.fetch.bind(globalThis)}headers(t){let n=new Headers;return n.set("Accept",t),this.accessToken&&n.set("Authorization",`Bearer ${this.accessToken}`),n}async listNodeSets(){let t=await this.fetchImpl(`${this.baseUrl}/nodes`,{headers:this.headers("application/json")});if(!t.ok)throw new Error(`Admin API GET /nodes failed (${t.status})`);let n=await t.json();if(!Array.isArray(n))throw new Error("Admin API GET /nodes returned non-array JSON");let r=[];for(let o of n){if(!o||typeof o!="object"||Array.isArray(o))continue;let s=Ns(o);s&&r.push(s)}return r}async getNodeConfigHtml(t,n){let r=t.split("/").map(i=>encodeURIComponent(i)).join("/"),o=n?`?lang=${encodeURIComponent(n)}`:"",s=await this.fetchImpl(`${this.baseUrl}/nodes/${r}${o}`,{headers:this.headers("text/html")});return s.status===404||!s.ok?null:s.text()}},_s={user:null,req:null},po=class{constructor(t){this.nodes=t}static{a(this,"RedNodeTypesAdapter")}async listNodeSets(){if(typeof this.nodes.getNodeList!="function")return[];let t,n=this.nodes.getNodeList;try{t=await Promise.resolve(n(_s))}catch{try{t=await Promise.resolve(n())}catch{return[]}}if(!Array.isArray(t))return[];let r=[];for(let o of t){if(!o||typeof o!="object"||Array.isArray(o))continue;let s=Ns(o);s&&r.push(s)}return r}async getNodeConfigHtml(t,n){if(typeof this.nodes.getNodeConfig!="function")return null;try{let r=await Promise.resolve(this.nodes.getNodeConfig({id:t,lang:n??"en-US",..._s}));if(typeof r=="string"&&r.length>0)return r}catch{}try{let r=await Promise.resolve(this.nodes.getNodeConfig(t,n));return typeof r=="string"&&r.length>0?r:null}catch{return null}}};a(Fs,"createNodeTypesAdapter");a(go,"flattenInstalledTypes");a(fl,"matchesQuery");a(or,"listNodeTypes");a(ml,"extractHelpScriptBody");a(Ps,"removeHtmlBlocks");a(hl,"stripHelpText");a(gl,"parseRegisterTypeFromHtml");a(Rs,"parseRegisterTypeAt");a(yl,"splitHelpSections");a(kn,"extractBalancedObject");a(Ls,"matchStringProp");a(Cs,"matchNumberProp");a(wl,"parseDefaultsBlock");a(bl,"unescapeJsStringEscape");a(vl,"normalizeDefaultValue");a(rr,"parseJsLiteral");a(co,"measureJsLiteralLength");a(Os,"extractBalancedArray");a(kl,"parseLooseValue");a(Al,"findSetForType");a(yo,"findCaseInsensitiveType");a(st,"getNodeTypeDetail");a(Ms,"assertKnownNodeTypes");a($s,"assertKnownNodeType");ke=null;a(xl,"defaultCatalogueFetcher");a(Sl,"loadCatalogue");a(sr,"searchPalette");a(Tl,"paletteMatchScore")});function qe(e){if(e.length===0)return;let t=e[0];if(!t)return;let n=e.map(s=>{let i={...s};return delete i.statusCode,i});if(e.length>1)throw new b(`${e.length} validation errors`,"validation_failed",{statusCode:400,details:{errors:n,errorCount:e.length}});let r={errors:n};for(let[s,i]of Object.entries(t))s==="code"||s==="error"||s==="errors"||s==="statusCode"||i!==void 0&&(r[s]=i);let o=typeof t.statusCode=="number"?t.statusCode:400;throw new b(t.error,t.code,{statusCode:o,details:r})}function wo(e,t="payload.flow"){let n=new Map;for(let o of e)for(let s of o){if(!s||typeof s!="object"||Array.isArray(s))continue;let i=s.id;typeof i=="string"&&i.length>0&&n.set(i,(n.get(i)??0)+1)}let r=[];for(let[o,s]of n)s>1&&r.push({code:"duplicate_node_id",error:`Node id '${o}' appears ${s} times in ${t}; node ids must be unique`,nodeId:o,count:s});return r}function _l(e){let t=typeof e.label=="string"&&e.label?` '${e.label}'`:"";return e.type==="tab"?`tab${t||` '${e.id}'`}`:e.type==="subflow"?`subflow${t||` '${e.id}'`}`:typeof e.z=="string"&&e.z?`${e.type} node on '${e.z}'`:`global ${e.type} config node`}function js(e,t,n){let r=new Map;for(let i of n)!i||typeof i.id!="string"||i.id===t||i.z===t||r.set(i.id,i);let o=[],s=new Set;for(let i of[e.nodes,e.configs])if(Array.isArray(i))for(let d of i){let l=d&&typeof d=="object"?d.id:void 0;if(typeof l!="string"||!l||s.has(l))continue;if(s.add(l),l===t){o.push({code:"duplicate_node_id",error:`Node id '${l}' equals the tab id; node ids must be unique across all flows`,nodeId:l});continue}let c=r.get(l);c&&o.push({code:"duplicate_node_id",error:`Node id '${l}' is already used by ${_l(c)}; node ids must be unique across all flows`,nodeId:l})}return o}function Ds(e){let t=new Map;for(let n of[e.nodes,e.configs])if(Array.isArray(n))for(let r of n){if(!r||typeof r!="object"||Array.isArray(r))continue;let o=r;typeof o.id=="string"&&t.set(o.id,o)}return t}function Rl(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]);for(let r of n)if(!Pl.has(r)&&JSON.stringify(e[r])!==JSON.stringify(t[r]))return!0;return!1}function Ll(e,t){let n=Ds(t);if(!e)return new Set(n.keys());let r=Ds(e),o=new Set;for(let[s,i]of n){let d=r.get(s);(!d||Rl(d,i))&&o.add(s)}return o}function Hs(e,t){let n=[],r=e;typeof r.id!="string"||r.id.length===0?n.push({code:"missing_flow_id",error:"payload.flow.id is required and must match payload.flowId"}):r.id!==t&&n.push({code:"invalid_flow_id",error:`payload.flow.id ('${r.id}') must match payload.flowId ('${t}')`}),"nodes"in r?Array.isArray(r.nodes)||n.push({code:"invalid_nodes",error:"payload.flow.nodes must be an array"}):n.push({code:"invalid_nodes",error:"payload.flow.nodes must be an array (use [] to clear the tab intentionally)"}),"configs"in r&&r.configs!==void 0&&!Array.isArray(r.configs)&&n.push({code:"invalid_nodes",error:"payload.flow.configs must be an array"});let o=[];Array.isArray(r.nodes)&&o.push({key:"nodes",list:r.nodes}),Array.isArray(r.configs)&&o.push({key:"configs",list:r.configs}),n.push(...wo(o.map(s=>s.list)));for(let{key:s,list:i}of o)for(let d=0;d<i.length;d+=1){let l=i[d];if(!l||typeof l!="object"||Array.isArray(l)){n.push({code:"invalid_node",error:`payload.flow.${s}[${d}] must be an object`});continue}let c=l;(typeof c.id!="string"||c.id.length===0)&&n.push({code:"missing_node_id",error:`payload.flow.${s}[${d}] needs a non-empty string id`});try{Be(c.type,"node.type")}catch(u){u instanceof b&&n.push({code:u.code,error:u.message,...typeof c.id=="string"?{nodeId:c.id}:{}})}"wires"in c&&Ft(c.wires,n,typeof c.id=="string"?c.id:void 0)}return n}function Ft(e,t,n){if(!Array.isArray(e))return t.push({code:"invalid_wires",error:"wires must be an array of output ports (each a list of node ids)",...n?{nodeId:n}:{}}),!1;for(let r of e){if(!Array.isArray(r))return t.push({code:"invalid_wires",error:"wires must be an array of output ports (each a list of node ids)",...n?{nodeId:n}:{}}),!1;for(let o of r)if(typeof o!="string"||o.length===0)return t.push({code:"invalid_wires",error:"wires targets must be non-empty node id strings",...n?{nodeId:n}:{}}),!1}return!0}async function Cl(e,t){if(typeof e.outputs=="number"&&Number.isFinite(e.outputs)&&e.outputs>=0)return Math.trunc(e.outputs);let n=typeof e.type=="string"?e.type:"";if(!(!t||!n))try{return(await st(t,n,{includeHelp:!1})).outputs}catch{return}}function El(e,t,n){let r=typeof e.type=="string"?e.type:"node";if(!(n.length<=t))return t===0?{code:"invalid_wires",error:`${r} has 0 outputs; wires must be []`,nodeId:e.id,type:r}:{code:"invalid_wires",error:`${r} has ${t} outputs; wires has ${n.length} port arrays (max ${t})`,nodeId:e.id,type:r}}function Il(e){return Array.isArray(e)&&e.some(t=>typeof t=="string"&&t.length>0)}function Nl(e,t){return t<=0?!1:!("wires"in e)||e.wires===void 0?!0:Array.isArray(e.wires)?!e.wires.some(Il):!1}async function bo(e,t,n={}){let r=[],o=[],s=new Set(e.map(d=>d.id)),i=n.softWarningNodeIds;for(let d of e){let l=await Cl(d,t),c=typeof d.type=="string"?d.type:"node",u=!i||i.has(d.id);if("wires"in d&&d.wires!==void 0){if(!Ft(d.wires,r,d.id))continue;let p=d.wires;if(l!==void 0){let f=El(d,l,p);f?(f.cause="excess_ports",r.push(f)):u&&p.length<l&&o.push({code:"incomplete_wires",error:`${c} '${d.id}' declares ${l} outputs but wires has only ${p.length} port(s)`,nodeId:d.id,type:c})}for(let f of p)for(let m of f){let h=String(m);s.has(h)||r.push({code:"invalid_wires",error:`Wire target '${h}' on node '${d.id}' was not found in this flow (add the node in the same patch or use an existing id)`,nodeId:d.id,cause:"missing_target",targetId:h})}}u&&l!==void 0&&Nl(d,l)&&o.push({code:"unwired_node",error:`Node '${d.id}' (${c}) has ${l} output(s) but is not wired to anything yet`,nodeId:d.id,type:c})}return{errors:r,warnings:o}}function vo(e,t){let n=[];for(let r of e)r.z===void 0||r.z===""||typeof r.z=="string"&&r.z!==t&&n.push({code:"z_mismatch",error:`Node '${r.id}' has z='${r.z}' but flow id is '${t}'`,nodeId:r.id});return n}function Fl(e,t){return e.filter(n=>n.type===t).map(n=>n.id)}async function ko(e,t,n){let r=[],o=[...t];for(let s of e){let i;try{i=Be(s.type,"node.type")}catch{continue}let d;try{d=await st(n,i,{includeHelp:!1})}catch{continue}let l=d.defaults;if(!l||typeof l!="object")continue;let c=new Set(d.requiredConfigNodeTypes??[]);for(let[u,p]of Object.entries(l)){if(!p||typeof p!="object"||Array.isArray(p))continue;let f=p.type;if(typeof f!="string"||!f)continue;let h=p.required===!0||c.has(f),g=s[u],v=Fl(o,f);if(typeof g!="string"||g.length===0){h&&r.push({code:"missing_config_node",error:`Node '${s.id}' (${i}) has no ${f} on '${u}' yet \u2014 configure before deploy if needed`,nodeId:s.id,type:i,property:u,configType:f,candidates:v});continue}o.find(E=>E.id===g)?.type!==f&&r.push({code:"missing_config_node",error:`Node '${s.id}' property '${u}' does not resolve to a ${f} config yet`,nodeId:s.id,type:i,property:u,configType:f,candidates:v})}}return r}async function Us(e,t={}){let n=t.flowId??(typeof e.id=="string"?e.id:""),r=Array.isArray(e.nodes)?e.nodes:[],o=Array.isArray(e.configs)?e.configs:[],s=[],i=[];n&&s.push(...vo(r,n));let d=t.baseFlow?Ll(t.baseFlow,e):void 0,l=await bo(r,t.nodeTypes,{...d?{softWarningNodeIds:d}:{}});if(s.push(...l.errors),i.push(...l.warnings),t.nodeTypes){let c=await ko(r,o,t.nodeTypes);d?i.push(...c.filter(u=>!u.nodeId||d.has(u.nodeId))):i.push(...c)}return{errors:s,warnings:i}}var Pl,Ao=j(()=>{"use strict";G();it();a(qe,"throwValidationIssues");a(wo,"collectDuplicateNodeIdIssues");a(_l,"describeLiveNode");a(js,"collectCrossFlowDuplicateIssues");Pl=new Set(["x","y","w","h","g","id","z"]);a(Ds,"collectNodesMap");a(Rl,"nodeContentChanged");a(Ll,"changedNodeIds");a(Hs,"collectFullFlowShapeIssues");a(Ft,"assertValidWiresShape");a(Cl,"resolveNodeOutputCount");a(El,"wireCountIssue");a(Il,"portHasTargets");a(Nl,"nodeIsUnwired");a(bo,"collectWireValidation");a(vo,"collectZMismatchIssues");a(Fl,"configCandidates");a(ko,"collectConfigWarnings");a(Us,"validateFlowTab")});function We(e,t="node"){return typeof e=="string"&&e.trim()?e.trim():t}function ir(e){let t=new Map;for(let n of e)n.type==="tab"&&typeof n.id=="string"&&typeof n.label=="string"&&n.label.trim()&&t.set(n.id,n.label.trim());return t}function qs(e,t){e[Bs]=t}function $l(e){if(!e)return;let t=e[Bs];return t instanceof Map?t:void 0}function Dl(e){return typeof e!="string"||!e.trim()?new Set:new Set(e.split(",").map(t=>t.trim()).filter(Boolean))}function jl(e){if(e===!0||e===1)return!0;if(typeof e=="string"){let t=e.trim().toLowerCase();return t==="1"||t==="true"||t==="yes"}return!1}function oe(e){let t=Dl(e.include);return jl(e.include_payload)&&(t.add("payload"),t.add("baseFlow")),t}function U(e,t){let n={req:e};t!==void 0&&(n.httpAdminRoot=t);let r=$l(e);return r&&(n.liveFlowLabels=r),n}function Ot(e){return typeof e.name=="string"&&e.name.trim()?e.name.trim():typeof e.label=="string"&&e.label.trim()?e.label.trim():""}function An(e){return`"${e.replace(/"/g,"'")}"`}function Mt(e){let t=new Map;for(let n of e.nodes??[])t.set(n.id,n);for(let n of e.configs??[])t.set(n.id,n);return t}function Hl(e,t){let n=new Set(["x","y","w","h","g","id","z"]),r=new Set([...Object.keys(e),...Object.keys(t)]);for(let o of r)if(!n.has(o)&&JSON.stringify(e[o])!==JSON.stringify(t[o]))return!0;return!1}function Ul(e,t){let n=Ot(e),r=Ot(t);if(!n||!r||n===r)return!1;let o=new Set(["x","y","w","h","g","id","z","name","label"]),s=new Set([...Object.keys(e),...Object.keys(t)]);for(let i of s)if(!o.has(i)&&JSON.stringify(e[i])!==JSON.stringify(t[i]))return!1;return!0}function xo(e,t){let n=[],r=[],o=[];for(let[s,i]of t)e.has(s)||n.push(i);for(let[s,i]of e)t.has(s)||r.push(i);for(let[s,i]of t){let d=e.get(s);d&&Hl(d,i)&&o.push({before:d,after:i})}return{added:n,removed:r,modified:o}}function ar(e){return e===1?"node":"nodes"}function Bl(e){return e.length===1&&e[0]?`adds 1 ${We(e[0].type)}`:`adds ${e.length} ${ar(e.length)}`}function ql(e){return`deletes ${e} ${ar(e)}`}function Wl(e){return`edits ${e} ${ar(e)}`}function Gl(e){let t=[];e.removed>0&&t.push(ql(e.removed));let n=e.added??[],r=e.addedCount??n.length;r>0&&t.push(n.length===r?Bl(n):`adds ${r} ${ar(r)}`),(e.modified??0)>0&&t.push(Wl(e.modified??0));let o=t.length?t.join(", "):"replaces nodes",s=e.flowLabel?.trim();return s?`Replace ${s}: ${o}`:`Replace flow: ${o}`}function zl(e){let{added:t,removed:n,modified:r}=e;if(t.length+n.length+r.length!==1)return;if(t[0]){let u=t[0],p=We(u.type),f=Ot(u);return f?`Add ${p} node ${An(f)}`:`Add ${p} node`}if(n[0]){let u=n[0],p=We(u.type),f=Ot(u);return f?`Remove ${p} node ${An(f)}`:`Remove ${p} node`}let s=r[0];if(!s)return;let i=We(s.after.type,We(s.before.type)),d=Ot(s.before),l=Ot(s.after);if(Ul(s.before,s.after)&&d&&l)return`Rename ${i} node ${An(d)} \u2192 ${An(l)}`;let c=l||d;return c?`Edit ${i} ${An(c)}`:`Edit ${i}`}function So(e,t){return typeof e?.label=="string"&&e.label.trim()?e.label.trim():typeof t?.label=="string"&&t.label.trim()?t.label.trim():""}function dr(e,t){if(typeof e.flowLabel=="string"&&e.flowLabel.trim())return e.flowLabel.trim();let n=So(e.payload?.flow,e.baseFlow);if(n)return n;let r=e.payload?.flowId;return r&&t?.has(r)?t.get(r)??"":""}function Ws(e,t){let n=t.trim(),r=e.trim();if(!n||!r||r.includes(n))return r;let o=r.lastIndexOf(" in ");if(o>=0){let s=r.slice(o+4);if(s.length>0&&n.startsWith(s))return r}return`${r} in ${n}`}function To(e,t){let n=xo(Mt(e??{}),Mt(t)),r=n.added.length+n.removed.length+n.modified.length,o=So(t,e);if(r===0)return o?`Update ${o}`:"Update flow";if(n.removed.length>0)return Gl({...o?{flowLabel:o}:{},removed:n.removed.length,added:n.added,modified:n.modified.length});if(r>Ml)return o?`${r} changes in ${o}`:`${r} changes`;let s=zl(n)??(o?`Update ${o}`:"Update flow");return Ws(s,o)}function Jl(e,t){let n=(e??"").trim();return!!(!n||n.includes("[object Object]")||/^Update(\s|$)/i.test(n)||t>0&&!/^Replace\b/i.test(n))}function _o(e,t){return`${e} \xB7 ${Kl(t)}`}function Vl(e){let t=e>=0?"+":"-",n=Math.abs(e),r=String(Math.floor(n/60)).padStart(2,"0"),o=String(n%60).padStart(2,"0");return`${t}${r}:${o}`}function Kl(e,t=new Date(e)){let n=Number.isNaN(t.getTime())?new Date(e):t;if(Number.isNaN(n.getTime()))return e.slice(0,16);let r=n.getDate(),s=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][n.getMonth()]??"Jan",i=String(n.getHours()).padStart(2,"0"),d=String(n.getMinutes()).padStart(2,"0"),l=Vl(-n.getTimezoneOffset());return`${r} ${s} ${i}:${d} ${l}`}function lr(e){if(typeof e!="string")return;let t=e.replace(/\0/g,"").replace(/[\u0001-\u001F\u007F]/g," ").replace(/\s+/g," ").trim().slice(0,Ol).trim();return t.length>0?t:void 0}function Xl(e,t){let n=ne(t);return`${n==="/"?"/mcp":`${n}/mcp`}#proposal=${encodeURIComponent(e)}`}function Po(e,t={}){let n=dr(e,t.liveFlowLabels),r=lr(e.title),o=To(e.baseFlow,n&&!So(e.payload.flow,e.baseFlow)?{...e.payload.flow,label:n}:e.payload.flow),s=xo(Mt(e.baseFlow??{}),Mt(e.payload.flow)),i=Jl(r,s.removed.length)?o:r??o;return Ws(i,n)}function Yl(e){return typeof e.storedTitle=="string"&&e.storedTitle?e.storedTitle:lr(e.title)}function se(e,t={}){let n=Po(e,t),r=dr(e,t.liveFlowLabels),o=Yl(e)??n,s=Xl(e.id,t.httpAdminRoot),i=t.req?wn(t.req,s):s;return{...e,title:n,storedTitle:o,flowLabel:r,displayName:_o(n,e.createdAt),reviewUrl:i,reviewPath:s}}function Ql(e){let t=xo(Mt(e.baseFlow??{}),Mt(e.payload.flow)),n=[];return t.added.length>0&&n.push(t.added.length===1?"Adds 1 node":`Adds ${t.added.length} nodes`),t.removed.length>0&&n.push(t.removed.length===1?"Deletes 1 node":`Deletes ${t.removed.length} nodes`),t.modified.length>0&&n.push(t.modified.length===1?"Edits 1 node":`Edits ${t.modified.length} nodes`),n.length===0?"No node changes":n.map((r,o)=>o===0?r:r.charAt(0).toLowerCase()+r.slice(1)).join(", ")}function Zl(e){if(e.kind==="oauth")return{kind:"oauth",clientId:e.clientId,user:e.user};let t={kind:e.kind,linkId:e.linkId};return typeof e.label=="string"&&e.label.length>0&&(t.label=e.label),t}function ce(e,t={}){let n=se(e,t),r=t.include??new Set,o={id:n.id,status:n.status,type:n.type,title:n.title,storedTitle:n.storedTitle,displayName:n.displayName,flowLabel:n.flowLabel,reviewUrl:n.reviewUrl,reviewPath:n.reviewPath,createdAt:n.createdAt,updatedAt:n.updatedAt,expectedRev:n.expectedRev,createdBy:Zl(n.createdBy),changeSummary:Ql(e)};return r.has("payload")&&(o.payload=n.payload),r.has("baseFlow")&&n.baseFlow!==void 0&&(o.baseFlow=n.baseFlow),n.warnings&&n.warnings.length>0&&(o.warnings=n.warnings),n.baseRevStale!==void 0&&(o.baseRevStale=n.baseRevStale),n.decidedBy!==void 0&&(o.decidedBy=n.decidedBy),n.reason!==void 0&&(o.reason=n.reason),n.resultRev!==void 0&&(o.resultRev=n.resultRev),o}function Gs(e,t={}){return e.map(n=>ce(n,t))}var Ol,Ml,Bs,Ae=j(()=>{"use strict";ae();Pe();Ol=80,Ml=1;a(We,"nodeTypeText");Bs=Symbol.for("ai-access.liveFlowLabels");a(ir,"liveFlowLabelsFromFlows");a(qs,"setRequestFlowLabels");a($l,"getRequestFlowLabels");a(Dl,"parseIncludeQuery");a(jl,"isTruthyFlag");a(oe,"resolveIncludeFromQuery");a(U,"presentOptionsFor");a(Ot,"nodeLabel");a(An,"quoteName");a(Mt,"collectNodes");a(Hl,"nodeContentChanged");a(Ul,"isRenameOnly");a(xo,"classifyFlowChanges");a(ar,"pluralNodes");a(Bl,"describeAddedClause");a(ql,"describeRemovedClause");a(Wl,"describeModifiedClause");a(Gl,"buildReplaceFlowTitle");a(zl,"describeOneChange");a(So,"flowLabelHint");a(dr,"resolveFlowLabel");a(Ws,"ensureTitleIncludesFlowName");a(To,"autoGenerateProposalTitle");a(Jl,"shouldRefreshDisplayTitle");a(_o,"buildProposalDisplayName");a(Vl,"formatUtcOffset");a(Kl,"formatProposalSubmittedAt");a(lr,"sanitizeProposalTitle");a(Xl,"buildProposalReviewPath");a(Po,"resolveProposalTitle");a(Yl,"storedTitleOf");a(se,"presentProposal");a(Ql,"buildChangeSummary");a(Zl,"presentCreatedByForApi");a(ce,"presentProposalForApi");a(Gs,"presentProposalsForApi")});function zs(e){let t=new Map;for(let n of e.nodes??[])t.set(n.id,n);for(let n of e.configs??[])t.set(n.id,n);return t}function tc(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]),r=[];for(let o of n)ec.has(o)||JSON.stringify(e[o])!==JSON.stringify(t[o])&&r.push(o);return r.sort()}function Js(e){let t=zs(e.baseFlow??{}),n=zs(e.payload.flow),r=new Map;for(let[o,s]of n){let i=t.get(o);if(!i){r.set(o,new Set(["*"]));continue}let d=tc(i,s);d.length>0&&r.set(o,new Set(d))}for(let[o]of t)n.has(o)||r.set(o,new Set(["*"]));return r}function nc(e,t){let n=[],r=[];for(let[o,s]of e){let i=t.get(o);if(!i)continue;n.push(o);let d=[];for(let c of s){if(c==="*"){d.push(...[...i].filter(u=>u!=="*")),i.has("*")&&d.push("*");continue}(i.has(c)||i.has("*"))&&d.push(c)}let l=[...new Set(d)].sort();l.length>0&&r.push({nodeId:o,fields:l})}return n.sort(),{nodeIds:n,fields:r}}function z(e,t,n={}){let r=0;for(let o of e.list())if(o.status==="pending"&&!(n.flowId&&o.payload.flowId!==n.flowId)&&o.expectedRev!==t)try{e.markClosed(o.id,"expired",rc),r+=1}catch{}return r}function Vs(e,t,n,r){let o=`Superseded by approved proposal ${n} (resultRev ${r}). Re-read the flow and resubmit if still needed.`,s=0;for(let i of e.list())if(!(i.id===n||i.status!=="pending"||i.payload.flowId!==t))try{e.markClosed(i.id,"superseded",o),s+=1}catch{}return s}function Ee(e,t,n){let r=Js(t),o=[];for(let s of e.list()){if(s.id===t.id||s.status!=="pending"||s.payload.flowId!==t.payload.flowId)continue;let i=Po(s),d=n?ot(s.createdBy)===n:ot(s.createdBy)===ot(t.createdBy),l=nc(r,Js(s)),c=s.expectedRev===t.expectedRev,u=l.nodeIds.length>0;if(!c&&!u)continue;let p={id:s.id,title:i,displayName:_o(i,s.createdAt),mine:d};l.nodeIds.length>0&&(p.overlappingNodeIds=l.nodeIds),l.fields.length>0&&(p.overlappingFields=l.fields),o.push(p)}return{conflictingProposals:o}}function Ie(e){let t={};return e.conflictingProposals.length>0&&(t.conflictingProposals=e.conflictingProposals,t.warning="Other pending proposals touch the same flow \u2014 approve order may conflict (like a git merge)."),t}function X(e){return e>0?{autoClosedCount:e}:{}}var ec,rc,Ge=j(()=>{"use strict";Ue();Ae();ec=new Set(["x","y","w","h","g","id","z"]);a(zs,"collectNodes");a(tc,"changedFieldKeys");a(Js,"computeProposalTouches");a(nc,"overlapBetween");rc="Proposal expired: the live flow revision moved past expectedRev. Re-read the flow and resubmit.";a(z,"expireStalePendingProposals");a(Vs,"supersedePendingOnFlow");a(Ee,"findConflictingProposals");a(Ie,"conflictWarningFields");a(X,"autoClosedFields")});var ni={};rs(ni,{applyFlowNodeOps:()=>at,applyReplaceTextToValue:()=>ti,buildPatchedFlowTab:()=>xn,collectSetProps:()=>Ro,decodeBase64UrlJson:()=>Sn,findTextOccurrences:()=>ei,interpretPatch:()=>Tn});function Zs(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function oc(e){if(typeof e!="object"||e===null||Array.isArray(e))throw new b(`add op needs node as an object (got ${Zs(e)})`,"invalid_node")}async function xn(e,t,n){return at(e,t,n.map(r=>({op:"set",nodeId:r.nodeId,set:r.set})))}function ei(e,t){if(!t)return[];let n=[],r=0;for(;r<=e.length;){let o=e.indexOf(t,r);if(o<0)break;n.push(o),r=o+t.length}return n}function ti(e,t,n,r,o){if(Qs.has(t))throw new b(`Cannot change protected node property '${t}' via replace_text`,"protected_property");if(typeof n!="string"||n.length===0)throw new b("find must be a non-empty string","missing_find");if(typeof r!="string")throw new b("replace must be a string","invalid_replace");if(e===void 0)throw new b(`Field '${t}' was not found on the node`,"field_not_found");if(typeof e!="string")throw new b(`Field '${t}' is not a string (got ${e===null?"null":typeof e})`,"field_not_string");let s=ei(e,n);if(s.length===0)throw new b(`Text not found in field '${t}'`,"text_not_found",{details:{matchCount:0}});let i=o;if(i===void 0){if(s.length>1)throw new b(`Found ${s.length} matches in '${t}'; pass occurrence (1-based) to pick one`,"ambiguous_match",{statusCode:409,details:{matchCount:s.length}});i=1}if(!Number.isInteger(i)||i<1||i>s.length)throw new b(`occurrence must be an integer from 1 to ${s.length}`,"invalid_occurrence",{details:{matchCount:s.length}});let d=s[i-1];if(d===void 0)throw new b(`occurrence must be an integer from 1 to ${s.length}`,"invalid_occurrence",{details:{matchCount:s.length}});return e.slice(0,d)+r+e.slice(d+n.length)}function sc(e){let t=[];for(let n of e)if(n.op==="add"){let r=n.node;if(typeof r!="object"||r===null||Array.isArray(r)){t.push({code:"invalid_node",error:`add op needs node as an object (got ${Zs(r)})`});continue}let o=r;(!o.id||typeof o.id!="string")&&t.push({code:"missing_node_id",error:"add op needs node.id"});try{Be(o.type,"node.type")}catch(s){s instanceof b&&t.push({code:s.code,error:s.message,nodeId:o.id})}"wires"in o&&Ft(o.wires,t,typeof o.id=="string"?o.id:void 0)}else n.op==="set"&&typeof n.set=="object"&&"wires"in n.set&&Ft(n.set.wires,t,n.nodeId);return t}function ic(e){let t=new Set;for(let n of e)n.op==="add"&&typeof n.node.id=="string"?t.add(n.node.id):n.op!=="add"&&typeof n.nodeId=="string"&&t.add(n.nodeId);return t}async function at(e,t,n,r={}){if(!t)throw new b("flow id is required","missing_flow_id");if(!n.length)throw new b("At least one node edit is required","missing_edits");let o=sc(n);o.push(...wo([n.flatMap(h=>h.op==="add"?[h.node]:[])],"the batch (add ops)")),qe(o);let s=await e.getFlow(t);if(r.expectedRev!==void 0&&s.rev!==r.expectedRev){let h=Array.isArray(s.flow.nodes)?[...s.flow.nodes]:[];return{expectedRev:s.rev,flow:{...s.flow,id:s.flow.id,type:s.flow.type??"tab",nodes:h}}}let i=Array.isArray(s.flow.nodes)?[...s.flow.nodes]:[],d=Array.isArray(s.flow.configs)?[...s.flow.configs]:[],l={...s.flow,id:s.flow.id,type:s.flow.type??"tab",nodes:i,configs:d},c=i.map(h=>({...h})),u=[];for(let h of n)try{h.op==="add"&&r.nodeTypes&&await $s(r.nodeTypes,h.node.type),Xs(c,t,h)}catch(g){if(g instanceof b)u.push({code:g.code,error:g.message,...g.statusCode!==400?{statusCode:g.statusCode}:{},...g.details??{}});else throw g}u.push(...vo(c,t));let p=ic(n),f=await bo(c,r.nodeTypes,{softWarningNodeIds:p});u.push(...f.errors),qe(u);for(let h of n)Xs(i,t,h);let m=[...f.warnings];if(r.nodeTypes){let h=await ko(i,d,r.nodeTypes);m.push(...h.filter(g=>!g.nodeId||p.has(g.nodeId)))}return{expectedRev:s.rev,flow:l,...m.length>0?{warnings:m}:{}}}function Ks(e,t,n){if(!t||typeof t!="string")throw new b("Each edit needs a node id","missing_node_id");let r=e.findIndex(o=>o.id===t);if(r<0||!e[r])throw new b(`Node '${t}' was not found in flow '${n}'`,"node_not_found",{statusCode:404});return r}function Xs(e,t,n){if(n.op==="add"){let i=n.node;if(!i.id||typeof i.id!="string")throw new b("add op needs node.id","missing_node_id");if(Be(i.type,"node.type"),e.some(d=>d.id===i.id))throw new b(`Node '${i.id}' already exists in flow '${t}'`,"node_already_exists");e.push({...i,id:i.id,type:i.type,z:typeof i.z=="string"&&i.z?i.z:t});return}if(n.op==="remove"){let i=Ks(e,n.nodeId,t);e.splice(i,1);return}let r=Ks(e,n.nodeId,t),o=e[r];if(!o)throw new b(`Node '${n.nodeId}' was not found in flow '${t}'`,"node_not_found",{statusCode:404});if(n.op==="replace_text"){if(!(n.field in o))throw new b(`Field '${n.field}' was not found on the node`,"field_not_found");let i=ti(o[n.field],n.field,n.find,n.replace,n.occurrence);e[r]={...o,id:o.id,type:o.type,[n.field]:i};return}let s={...o,id:o.id,type:o.type};for(let[i,d]of Object.entries(n.set)){if(Qs.has(i))throw new b(`Cannot change protected node property '${i}' via GET propose`,"protected_property");if(i==="wires"){let l=[];Ft(d,l,n.nodeId),qe(l)}s[i]=d}e[r]=s}function Ys(e){if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean"||typeof e=="bigint")return String(e)}function Ro(e){let t={},n=e.set;if(n&&typeof n=="object"&&!Array.isArray(n))for(let[r,o]of Object.entries(n)){let s=Ys(o);s!==void 0&&(t[r]=s)}for(let[r,o]of Object.entries(e)){if(!r.startsWith("set.")||r.length<=4)continue;let s=r.slice(4);if(typeof o=="string")t[s]=o;else if(Array.isArray(o)&&typeof o[0]=="string")t[s]=o[0];else{let i=Ys(o);i!==void 0&&(t[s]=i)}}return t}function Sn(e){let t;try{t=Buffer.from(e,"base64url").toString("utf8")}catch{throw new b("patch must be base64url-encoded JSON","invalid_patch")}try{return JSON.parse(t)}catch{throw new b("patch JSON is malformed","invalid_patch_json")}}function ac(e,t){if(typeof e.flowId=="string"&&e.flowId)return e.flowId;let n=e.flow;if(n&&typeof n=="object"&&!Array.isArray(n)){let r=n.id;if(typeof r=="string"&&r)return r}return t}function Lo(e){return typeof e.id=="string"&&e.id?e.id:typeof e.nodeId=="string"&&e.nodeId?e.nodeId:typeof e.node=="string"&&e.node?e.node:""}function dc(e){if(e==null||e==="")return;let t;if(typeof e=="number")t=e;else if(typeof e=="string")t=Number(e);else if(typeof e=="boolean"||typeof e=="bigint")t=Number(e);else throw new b("occurrence must be a positive integer","invalid_occurrence");if(!Number.isFinite(t))throw new b("occurrence must be a positive integer","invalid_occurrence");return Math.trunc(t)}function lc(e){let t=e.node;if(t!==void 0)return oc(t),{op:"add",node:t};let n={...e};return delete n.op,{op:"add",node:n}}function cc(e){let t=Lo(e);if(!t)throw new b("replace_text op needs node id","missing_node_id");let n=typeof e.field=="string"?e.field:"";if(!n)throw new b("replace_text op needs field","missing_field");if(typeof e.find!="string")throw new b("replace_text op needs find string","missing_find");if(typeof e.replace!="string")throw new b("replace_text op needs replace string","invalid_replace");let r=dc(e.occurrence),o={op:"replace_text",nodeId:t,field:n,find:e.find,replace:e.replace};return r!==void 0&&(o.occurrence=r),o}function uc(e){let t=Lo(e);if(!t)throw new b("Each patch.nodes entry needs id","missing_node_id");let n=e.set,r={};if(n&&typeof n=="object"&&!Array.isArray(n))Object.assign(r,n);else for(let[o,s]of Object.entries(e))o==="id"||o==="nodeId"||o==="node"||o==="set"||o==="op"||(r[o]=s);return{op:"set",nodeId:t,set:r}}function pc(e){let t=typeof e.op=="string"?e.op.toLowerCase():"set";if(t==="add")return lc(e);if(t==="remove"){let n=Lo(e);if(!n)throw new b("remove op needs id","missing_node_id");return{op:"remove",nodeId:n}}if(t==="replace_text")return cc(e);if(t!=="set")throw new b("nodes[].op must be 'add', 'remove', 'set', or 'replace_text'","invalid_patch");return uc(e)}function Tn(e,t){if(!e||typeof e!="object"||Array.isArray(e))throw new b("patch must be a JSON object","invalid_patch");let n=e;if(n.type==="replace_flow"||n.flow&&typeof n.flow=="object"){let r=ac(n,t);if(!r)throw new b("patch.replace_flow needs flowId","missing_flow_id");return{kind:"replace_flow",flowId:r,flow:n.flow}}if(Array.isArray(n.nodes)){let r=typeof n.flowId=="string"?n.flowId:t;if(!r)throw new b("patch.nodes needs flow id","missing_flow_id");let o=[];for(let i of n.nodes){if(!i||typeof i!="object"||Array.isArray(i))throw new b("Each patch.nodes entry must be an object","invalid_patch");o.push(pc(i))}let s=o.filter(i=>i.op==="set").map(i=>({nodeId:i.nodeId,set:i.set}));return{kind:"nodes",flowId:r,ops:o,edits:s}}throw new b("patch must include nodes[] edits or type/flow for replace_flow","invalid_patch")}var Qs,cr=j(()=>{"use strict";G();Ao();it();Qs=new Set(["id","type","z"]);a(Zs,"describeNonObjectNode");a(oc,"assertAddNodeObject");a(xn,"buildPatchedFlowTab");a(ei,"findTextOccurrences");a(ti,"applyReplaceTextToValue");a(sc,"collectOpShapeIssues");a(ic,"touchedNodeIdsFromOps");a(at,"applyFlowNodeOps");a(Ks,"requireNodeIndex");a(Xs,"applyOneNodeOp");a(Ys,"stringifyQueryValue");a(Ro,"collectSetProps");a(Sn,"decodeBase64UrlJson");a(ac,"resolveReplaceFlowId");a(Lo,"resolveNodeId");a(dc,"parseOccurrence");a(lc,"parseAddOp");a(cc,"parseReplaceTextOp");a(uc,"parseSetOp");a(pc,"parseNodeOp");a(Tn,"interpretPatch")});function $t(e,t){return ot(e.createdBy)===t.id}function Co(e,t){return!t||t.length===0||t.includes(e)}function fc(e){return typeof e=="string"&&oi.has(e)}function jt(e,t){let n=mc[e],r=e!=="status"&&Array.isArray(t);return new b(r?`Invalid ${e} filter: pass it at most once (multiple values are not supported). Allowed: ${n}`:`Invalid ${e} filter. Allowed: ${n}`,"invalid_filter",{details:{parameter:e,allowed:n,hint:`Pass ${e} as ${n}, or omit it.`}})}function ur(e,t){if(Array.isArray(t))throw jt(e,t);return t}function hc(e){return typeof e=="number"?Number.isInteger(e)?e:null:typeof e=="string"&&/^-?\d+$/.test(e.trim())?Number(e.trim()):null}function Eo(e){return e==null||e===""}function gc(e){if(!Eo(e)){if(e==="true"||e==="1"||e===!0)return!0;if(e==="false"||e==="0"||e===!1)return!1;throw jt("mine",e)}}function Io(e){if(Eo(e))return;let t=(Array.isArray(e)?e:[e]).flatMap(r=>typeof r=="string"?r.split(","):[r]),n=[];for(let r of t){let o=typeof r=="string"?r.trim():r;if(o!==""){if(!fc(o))throw jt("status",e);n.includes(o)||n.push(o)}}return n.length>0?n:void 0}function ri(e,t,n,r){if(Eo(t))return;let o=hc(t);if(o===null||o<n||o>r)throw jt(e,t);return o}function pr(e){let t={},n=gc(ur("mine",e.mine));n!==void 0&&(t.mine=n);let r=Io(e.status);r&&(t.status=r);let o=ur("flowId",e.flowId??e.flow_id??e.flow);typeof o=="string"&&o.length>0&&(t.flowId=o);let s=ri("limit",ur("limit",e.limit),1,Dt);s!==void 0&&(t.limit=s);let i=ri("offset",ur("offset",e.offset),0,Number.MAX_SAFE_INTEGER);return i!==void 0&&(t.offset=i),t}function dt(e){let t=e.offset+e.returned,n=t<e.total;return{total:e.total,offset:e.offset,...e.limit!==null?{limit:e.limit}:{},hasMore:n,...n?{nextOffset:t}:{}}}function yc(e,t){let n=e.length,r=t.offset!==void 0&&t.offset>0?t.offset:0;if(t.limit===void 0)return{items:r>0?e.slice(r):e,total:n,limit:null,offset:r};let o=Math.min(Math.max(1,t.limit),Dt);return{items:e.slice(r,r+o),total:n,limit:o,offset:r}}function Ht(e,t,n={},r={}){let o=r.mine===!0?t.id:void 0,s={};o&&(s.linkId=o),r.flowId&&(s.flowId=r.flowId);let d=e.list(s).filter(c=>r.mine!==!1||!$t(c,t)).filter(c=>Co(c.status,r.status)).map(c=>({...se(c,n),mine:$t(c,t)})),l=yc(d,r);return{proposals:l.items,total:l.total,limit:l.limit,offset:l.offset}}function Me(e,t,n,r={}){if(!n)throw new b("Missing proposal id","missing_proposal_id");let o=e.get(n);return{...se(o,r),mine:$t(o,t)}}function wc(e){let t={kind:"access-link",linkId:e.id};return typeof e.label=="string"&&e.label&&(t.label=e.label),t}function Ut(e,t,n,r,o,s={}){if(!r)throw new b("Missing proposal id","missing_proposal_id");let i;try{i=e.get(r)}catch(f){throw f instanceof re,f}if(!$t(i,n))throw new It(r);if(i.status!=="pending")throw new ee(r,i.status);let d=typeof o=="string"&&o.trim()?o.replace(/\0/g,"").trim().slice(0,2e3):"Withdrawn by access link",l=wc(n),c=e.markWithdrawn(r,l,d),u=se(c,s),p=u.flowLabel||void 0;return t.append("proposal.withdrawn",l,{proposalId:c.id,flowId:c.payload.flowId,title:u.title,displayName:u.displayName,reason:d,...p?{flowLabel:p}:{}}),{...u,mine:!0}}function bc(e,t,n,r={}){let o;try{o=e.get(t.id)}catch{return{}}if(r.liveRev){z(e,r.liveRev,{flowId:o.payload.flowId});try{o=e.get(t.id)}catch{return{}}}return o.status!=="pending"?{}:Ie(Ee(e,o,n))}function ue(e,t,n,r={},o={}){let s="mine"in n?n:{...se(n,r),mine:$t(n,t)},i=0;if(o.liveRev)try{let c=e.get(s.id).payload.flowId;i=z(e,o.liveRev,{flowId:c});let u=e.get(s.id);s={...se(u,r),mine:$t(u,t)}}catch{}let d=ce(s,r),l={...d,mine:s.mine,...X(i)};return o.includeConflicts!==!1&&d.status==="pending"&&Object.assign(l,bc(e,d,t.id)),l}async function pe(e){let t=await e.getFlows();return{rev:t.rev,flows:t.flows}}async function xe(e,t){if(!t)throw new b("Missing flow id","missing_flow_id");let n=await e.getFlow(t);return{rev:n.rev,flow:Nt(t,n.flow)}}function vc(e){if(typeof e.expectedRev=="string"&&e.expectedRev.length>0)return e.expectedRev;if(typeof e.expected_rev=="string"&&e.expected_rev.length>0)return e.expected_rev}function si(e,t){if("nodes"in e&&e.nodes!==void 0&&!Array.isArray(e.nodes))throw new b("payload.nodes must be an array of node ops","invalid_nodes");if(t!==void 0&&!Array.isArray(t))throw new b("nodes must be an array of node ops","invalid_nodes")}async function kc(e,t,n,r,o){let{applyFlowNodeOps:s,interpretPatch:i}=await Promise.resolve().then(()=>(cr(),ni));si(n,r);let d=Array.isArray(n.nodes)?n.nodes:Array.isArray(r)?r:null;if(!d)throw new b("payload.flow is required","missing_flow");let l=n.flowId,c=typeof l=="string"?l:"";if(!c)throw new b("payload.flowId is required","missing_flow_id");let u=i({nodes:d,flowId:c},c);if(u.kind!=="nodes")throw new b("payload.nodes must be node ops","invalid_patch");let p=await s(e,u.flowId,u.ops,{expectedRev:t,...o?{nodeTypes:o}:{}});if(p.expectedRev!==t)throw new W(t,p.expectedRev,"submit_proposal");return{flowId:u.flowId,flow:p.flow}}async function Ac(e,t,n,r,o){let s=n.flowId,i=n.flow,d=!i||typeof i!="object"||Array.isArray(i);if(d&&si(n,r),d&&(Array.isArray(n.nodes)||Array.isArray(r))){let l=await kc(e,t,n,r,o);s=l.flowId,i=l.flow}if(typeof s!="string"||s.length===0)throw new b("payload.flowId is required","missing_flow_id");if(!i||typeof i!="object"||Array.isArray(i))throw new b("payload.flow is required","missing_flow");return{flowId:s,flow:i}}function xc(e,t){try{return{safeExpectedRev:Ce(e.slice(0,200),"expectedRev"),safeFlowId:Ce(t.slice(0,200),"flowId")}}catch{throw new b("expectedRev and payload.flowId must be safe path segments","invalid_path_segment")}}async function ze(e,t,n,r,o,s={},i){let d=r.type,l=vc(r),c=r.payload;if(d!=="replace_flow")throw new b("type must be 'replace_flow'","invalid_proposal_type");if(!l)throw new b("expectedRev is required (top-level field next to type and payload)","missing_expected_rev");if(!c||typeof c!="object"||Array.isArray(c))throw new b("payload must be an object","invalid_payload");let p=await Ac(o,l,c,r.nodes,i),{safeExpectedRev:f,safeFlowId:m}=xc(l,p.flowId),h=p.flow,g=Buffer.byteLength(JSON.stringify(c),"utf8");if(g>$)throw new J("proposal payload",g,$);qe(Hs(h,m));let A=Et(h),E=await o.getFlows();if(E.rev!==f)throw new W(f,E.rev,"submit_proposal");qe(js(A,m,E.flows));let D=await o.getFlow(m);if(D.rev!==f)throw new W(f,D.rev,"submit_proposal");let O=Et(D.flow);i&&await Ms(i,A);let{errors:te,warnings:me}=await Us(A,{flowId:m,baseFlow:O,...i?{nodeTypes:i}:{}});qe(te);let y={kind:"access-link",linkId:n.id};n.label!==void 0&&(y.label=n.label);let w=n.oauthClientId&&n.oauthUser?{kind:"oauth",clientId:n.oauthClientId,user:n.oauthUser}:y,k=lr(r.title)??To(O,A),T=e.create({type:"replace_flow",expectedRev:f,payload:{flowId:m,flow:A},baseFlow:O,title:k,createdBy:w,...me.length>0?{warnings:me}:{}}),_=se(T,s),C=_.flowLabel||void 0;return t.append("proposal.created",y,{proposalId:T.id,flowId:m,expectedRev:f,title:_.title,displayName:_.displayName,...C?{flowLabel:C}:{},...me.length>0?{warningCount:me.length}:{}}),_}function N(e,t="rest"){return _n(e,t)}var Dt,oi,mc,b,G=j(()=>{"use strict";ve();nr();Ue();Qn();q();Ne();Ao();it();Ge();Ae();a($t,"isMineProposal");Dt=200,oi=new Set(["pending","approved","rejected","failed","withdrawn","expired","superseded"]);a(Co,"matchesStatusFilter");a(fc,"isProposalStatus");mc={status:[...oi].join(", "),limit:`integer 1\u2013${Dt}`,offset:"integer \u2265 0",mine:"true, false, 1, 0",flowId:"one flow tab id"};a(jt,"invalidFilterError");a(ur,"singleQueryValue");a(hc,"parseIntegerFilter");a(Eo,"isUnset");a(gc,"parseMineFilter");a(Io,"parseStatusFilter");a(ri,"parseRangeFilter");a(pr,"parseListProposalsQuery");a(dt,"listPageFields");a(yc,"applyListPagination");a(Ht,"listAccessibleProposals");a(Me,"getAccessibleProposal");a(wc,"accessLinkActor");a(Ut,"withdrawMyProposal");a(bc,"conflictsForProposal");a(ue,"presentAccessibleProposalForApi");b=class extends Error{static{a(this,"AiOpsValidationError")}statusCode;code;details;constructor(t,n="validation_error",r={}){super(t),this.name="AiOpsValidationError",this.code=n,this.statusCode=r.statusCode??400,r.details&&(this.details=r.details)}};a(pe,"listFlows");a(xe,"getFlow");a(vc,"resolveExpectedRev");a(si,"assertNodesArrayOrAbsent");a(kc,"materializePayloadFromNodes");a(Ac,"resolveReplaceFlowPayload");a(xc,"assertSafeRevAndFlowId");a(ze,"submitReplaceFlowProposal");a(N,"aiOpsErrorToHttp")});function No(e){return e==="get_link"?86400:2592e3}function Fo(e,t){return e==="get_link"?`get_link ttlSeconds must be \u2264 ${t} (24 hours)`:`ttlSeconds must be \u2264 ${t} (30 days) for ${e} tokens`}var lt,Pn=j(()=>{"use strict";a(No,"maxTtlSecondsForKind");a(Fo,"ttlCapErrorMessage");lt="ttlSeconds must be a positive number of seconds within the cap for the token kind: get_link \u2264 86400 (24h); rest and mcp \u2264 2592000 (30 days)."});function S(e,t,n,r,o=Wt,s="error"){return{code:e,status:t,meaning:n,hint:r,channels:o,kind:s}}function Rn(e){return Tc.get(e)?.hint}function ii(e){return e.replace(/\\/g,"\\\\").replace(/\|/g,"\\|")}function ai(){let e=a(s=>`| \`${s.code}\` | ${s.status} | ${ii(s.meaning)} | ${ii(s.hint)} | ${s.channels.join(", ")} |`,"row"),t=["| Code | HTTP | Meaning | Next step | Channels |","| --- | --- | --- | --- | --- |"],n=fr.filter(s=>s.kind==="error"),r=fr.filter(s=>s.kind==="warning"),o=fr.filter(s=>s.kind==="status");return[...t,...n.map(e),"","**Warnings** (in `warnings[]` on a successful propose; never blocking):","",...t,...r.map(e),"","**Auto-closed proposal statuses.** Pending proposals are closed automatically: `expired` when the live flow rev moves past their `expectedRev`, `superseded` when another proposal on the same flow is approved. Submit, get, and list responses include `autoClosedCount` when that request auto-closed proposals (omitted when zero).","",...t,...o.map(e)].join(`
|
|
7
|
-
`)}var Wt,Bt,Oo,Sc,qt,fr,Tc,mr=j(()=>{"use strict";Pn();Wt=["GET","REST","MCP"],Bt=["GET"],Oo=["REST","MCP"],Sc=["GET","REST"],qt=["admin"];a(S,"e");fr=[S("access_link_missing",401,"No Bearer / path token","Ask the user for a new access link of the right kind"),S("bearer_scheme_required",401,"Auth used Basic (or non-Bearer)","Send `Authorization: Bearer <token>`",Oo),S("token_expired",401,"Known token past `expiresAt` (body includes `expiredAt` and `accessLinksUrl`)","Ask the user to create a new link on the `/mcp` page \u2192 Access links (`accessLinksUrl`)"),S("access_link_invalid",401,"Unknown, revoked, or cleared after restart","Ask the user for a new access link"),S("wrong_token_kind",403,"Token kind does not match this surface","Mint the matching kind on `/mcp` \u2192 Access links; show `tellUser` to the human"),S("ai_features_disabled",403,"Plugin AI features off","Owner enables AI Access / `adminAuth`"),S("auth_required",401,"Admin session required","Sign in to the Node-RED editor",qt),S("insufficient_permissions",403,"Admin lacks required scope","Need `*` for link mint / approve",qt),S("invalid_link_kind",400,"Unknown `kind` on create","Use `get_link`, `rest`, or `mcp`",qt),S("invalid_ttl",400,"`ttlSeconds` is zero, negative, not a number, or over the cap for the kind",lt,qt),S("access_link_limit",409,"Too many active links","Revoke an existing link first",qt),S("access_link_not_found",404,"Unknown link id on revoke","List links and use an existing id",qt),S("link_create_not_supported",403,"Attempted mint under a GET link","Admin mints via `POST /mcp/api/v1/links` only",Bt),S("rate_limited",429,"Per-IP or per-token limit","Wait `retryAfter` / `Retry-After`"),S("payload_too_large",413,"Body / proposal over ~1.5MB","Shrink content"),S("invalid_json",400,"Malformed JSON body","Fix Content-Type / body",Oo),S("unsupported_media_type",415,"POST body is not `application/json`","Send `Content-Type: application/json`",Oo),S("url_too_long",414,"GET URL over soft cap (~8KB)","Use chunked drafts or REST/MCP",Bt),S("not_found",404,"Unknown path or resource","Re-read docs / discovery"),S("missing_query",400,"Required search `q` missing","Pass `?q=`",Sc),S("invalid_filter",400,"Invalid list filter (`status`, `limit`, `offset`, `mine`); body names `parameter` and `allowed`","Use an allowed value: status one of the proposal statuses; limit integer 1\u2013200; offset integer \u2265 0; mine true|false"),S("invalid_arguments",400,"MCP tool arguments failed schema validation; body names `parameter`","Fix the named argument to match the tool input schema (see tools/list)",["MCP"]),S("invalid_diff_format",400,"Unknown `format` on proposal diff","Use `format=summary` (default) or `format=full`"),S("confirm_required",400,"GET withdraw without `confirm=1`","Repeat the request with `confirm=1`",Bt),S("missing_expected_rev",400,"Proposal submitted without `expectedRev` / `rev`","Read the flow and pass its `rev`"),S("version_mismatch",409,"Stale `rev` / `expectedRev`","Re-read flows and resubmit"),S("proposal_not_found",404,"Unknown proposal id","List proposals and use an existing id"),S("proposal_not_pending",409,"Action needs a pending proposal","Only pending can withdraw / decide"),S("proposal_not_owned",403,"Withdraw of another token\u2019s proposal","Only the creating token can withdraw"),S("validation_failed",400,"Several validation errors at once (message `N validation errors`)","Fix every entry in errors[] and resubmit."),S("duplicate_node_id",400,"The same node id appears more than once in a full tab or batch","Give every node a unique id"),S("missing_node_id",400,"Node / op without an id","Include a non-empty string id"),S("missing_node_type",400,"Node without a `type`","Set `type` to an installed palette type name"),S("invalid_node_type",400,"`type` is not a string","Use the palette type name exactly"),S("invalid_nodes",400,"`flow.nodes` / `flow.configs` not an array","Send arrays (use `[]` to clear)"),S("invalid_node",400,"Node entry is not an object","Send `{ id, type, \u2026 }` objects"),S("invalid_wires",400,"Bad wires shape, missing target, or more ports than outputs","Wire only to existing node ids / ports"),S("z_mismatch",400,"Node `z` differs from the flow id","Set `z` to the tab id or omit it"),S("node_already_exists",400,"`add` op for an id already in the flow","Use `set` or a new id"),S("protected_property",400,"Tried to change id / type / z","Omit id, type, and z from edits"),S("node_not_found",404,"Unknown node (single error only; several errors \u2192 `validation_failed`)","Re-read the flow"),S("field_not_found",404,"Unknown field on a node","Re-read the node"),S("text_not_found",400,"`replace_text` find had no match","Re-read field; find is exact"),S("ambiguous_match",409,"`replace_text` matched more than once","Pass `occurrence` (1-based) or unique find"),S("unknown_node_type",400,"Proposal uses a type that is not installed","List node-types / palette search; ask user to install"),S("node_type_not_found",404,"Unknown type on node-types detail","List types or search palette"),S("chunk_conflict",409,"Same draft seq re-sent with different data","Re-send identical data or start a new draft",Bt),S("missing_chunks",400,"Draft submit with gaps in seq","Upload every seq from 0 through max",Bt),S("draft_hash_mismatch",400,"Draft sha256 does not match assembled bytes","Send the hex sha256 of the assembled UTF-8",Bt),S("missing_config_node",201,"Warning: node has no (or an unresolved) config node yet","Configure before deploy if needed; see `candidates`",Wt,"warning"),S("unwired_node",201,"Warning: node has outputs but no wires","Wire it, or ignore while scaffolding",Wt,"warning"),S("incomplete_wires",201,"Warning: fewer wire ports than outputs","Add ports if outputs should connect",Wt,"warning"),S("expired",200,"Status: pending proposal auto-closed because the live flow rev moved past `expectedRev`","Re-read the flow and submit a new proposal",Wt,"status"),S("superseded",200,"Status: pending proposal auto-closed because another proposal on the same flow was approved","Re-read the flow and submit a new proposal if still needed",Wt,"status"),S("upstream_error",502,"Node-RED runtime/flows error","Retry; check Node-RED logs"),S("internal_error",500,"Unexpected server error","Retry; check Node-RED logs")],Tc=new Map(fr.map(e=>[e.code,e]));a(Rn,"registryHint");a(ii,"cell");a(ai,"renderGetLinkErrorTable")});function ie(e,t,n={}){return Mo(e,t,n)}function _c(e,t){return t==="excess_ports"?e==="mcp"?"This node has more wire ports than its type declares \u2014 remove extra ports or raise the node outputs, then retry.":e==="rest"?"This node has more wire ports than its type declares \u2014 trim wires[] or raise outputs, then retry.":"This node has more wire ports than its type declares \u2014 trim wires or raise outputs, then retry.":t==="missing_target"?e==="mcp"?"Wire targets must be node ids in this flow \u2014 add the node in the same patch or use get_flow ids.":e==="rest"?"Wire targets must be node ids in this flow \u2014 add the node in the same patch or use GET /flows/{id} ids.":"wires must list existing node ids (or add those nodes in the same patch).":e==="mcp"||e==="rest"?"Fix wires: targets must exist in this flow, and port count must not exceed the node outputs.":"wires must list existing node ids (or add those nodes in the same patch)."}function Pc(e){return e==="mcp"?"Call list_node_types or search_palette. Ask the user to install via Manage palette, then recheck. This API never installs.":e==="rest"?"Call GET /node-types?q= or GET /palette/search?q=. Ask the user to install via Manage palette, then recheck. This API never installs.":"Call GET \u2026/node-types?q= or \u2026/palette/search?q=. Ask the user to install via Manage palette, then recheck. This API never installs."}function Je(e,t,n,r){return e==="mcp"?t:e==="rest"?n:r}function Mo(e,t,n={}){switch(e){case"version_mismatch":return Je(t,"Re-read with list_flows/get_flow and retry with the current rev.","Re-read the flow and retry with the current rev.","Re-read the flow and retry with the current rev.");case"node_not_found":return Je(t,"Call get_flow or get_node and use a node id from that response.","Call GET /flows/{id} and use a node id from that response.","Call GET \u2026/flows/<id> and use a node id from that response.");case"flow_not_found":return Je(t,"Call list_flows to list valid ids.","Call GET /flows to list valid ids.","Use an id from GET ./flows.");case"proposal_not_found":return Je(t,"Call list_proposals and use an id from that response.","List proposals and use an id from that response.","List proposals and use an id from that response.");case"proposal_not_owned":return"Only the access link that created a pending proposal can withdraw it.";case"proposal_not_pending":return"Only pending proposals can be withdrawn or decided.";case"confirm_required":return"Add confirm=1 to the withdraw URL (required so link prefetchers cannot withdraw).";case"invalid_wires":return _c(t,n.cause);case"node_type_not_found":case"unknown_node_type":return Pc(t);case"missing_node_type":return'Each add op needs node.type as a non-empty string (e.g. "inject").';case"invalid_line_range":return Je(t,'Pass lines as "A" or "A-B" (1-based inclusive). Reversed ranges (end < start) are invalid.',"Use lines=A or lines=A-B (1-based inclusive). Reversed ranges (end < start) are invalid.","Use lines=A or lines=A-B (1-based inclusive). Reversed ranges (end < start) are invalid.");case"line_range_out_of_bounds":return"start is past the end of the field \u2014 check totalLines and request a lower lines range.";case"ambiguous_match":return"find matched more than once \u2014 pass occurrence=N (1-based) or a more unique find string.";case"missing_expected_rev":return Je(t,"Pass expectedRev from list_flows/get_flow (top-level, next to type and payload).","Pass expectedRev from GET /flows or GET /flows/{id} (top-level, next to type and payload).","Pass rev from GET \u2026/flows or \u2026/flows/<id>.");case"missing_query":return Je(t,"Pass a non-empty q string.","q query parameter is required.","q query parameter is required.");case"unsupported_media_type":return"Send Content-Type: application/json with a JSON body.";case"text_not_found":return"Re-read the node field; find must match exactly (case-sensitive).";case"field_not_found":return"Re-read the node; field must be an existing string property (template, func, \u2026).";case"field_not_string":return Je(t,"This tool only returns string fields (template, func, \u2026). Use get_node for other property types.","This endpoint only returns string fields (template, func, \u2026). Use GET /nodes/{id} for other property types.","This endpoint only returns string fields (template, func, \u2026). Use GET /nodes/{id} for other property types.");default:return}}function _n(e,t,n={}){let r=n.canonicalizeMessage!==!1,o=null;if(e instanceof b?o={statusCode:e.statusCode,body:{error:e.message,code:e.code,...e.details??{}}}:e instanceof W?o={statusCode:e.statusCode,body:{error:e.message,code:e.code,expectedRev:e.expectedRev,currentRev:e.currentRev}}:e instanceof B?o={statusCode:e.statusCode,body:{error:e.message,code:e.code,flowId:e.flowId}}:e instanceof re||e instanceof It||e instanceof ee||e instanceof J?o={statusCode:e.statusCode,body:{error:e.message,code:e.code}}:(e instanceof Le||e instanceof be)&&(o={statusCode:e.statusCode,body:{error:e.message,code:e.code}}),!o)return null;let s=o.body.code;if(r&&!Array.isArray(o.body.errors)){let i=Fe[s];i&&(o.body.error=i)}if(typeof o.body.hint!="string"||!o.body.hint){let i=Mo(s,t,{cause:o.body.cause})??Rn(s);i&&(o.body.hint=i)}return o}function di(e,t,n={}){let o={error:n.error??Fe[t]??"Request failed",code:t,...n.details??{}},s=Mo(t,e,{cause:o.cause})??Rn(t);return s&&(o.hint=s),{statusCode:n.statusCode??400,body:o}}var Fe,Ne=j(()=>{"use strict";ve();Ue();q();G();mr();Fe={node_not_found:"Node was not found",flow_not_found:"Flow was not found",field_not_found:"Field was not found",field_not_string:"Field is not a string",missing_query:"q query parameter is required",missing_flow_id:"Missing flow id",missing_node_id:"Missing node id",missing_field:"Missing field name",invalid_line_range:"lines must be A or A-B (1-based inclusive; end >= start)",line_range_out_of_bounds:"lines range is out of bounds",proposal_not_found:"Proposal was not found",proposal_not_owned:"Proposal was not created by this access link",proposal_not_pending:"Proposal is not pending",confirm_required:"Withdraw over GET requires confirm=1",unsupported_media_type:"Content-Type must be application/json",upstream_error:"Upstream request failed",internal_error:"Internal server error"};a(ie,"channelErrorHint");a(_c,"invalidWiresHint");a(Pc,"nodeTypeHint");a(Je,"pickChannel");a(Mo,"hintFor");a(_n,"mapErrorForChannel");a(di,"buildChannelError")});var Uf={};rs(Uf,{ACCESS_LINK_INVALID_MESSAGE:()=>et,ACCESS_LINK_MISSING_MESSAGE:()=>le,AccessLinkStore:()=>yt,EDITOR_SIDEBAR_ASSET_VERSION:()=>Jt,MAX_PROPOSAL_JSON_BYTES:()=>$,SettingsStore:()=>Tt,areAiFeaturesEnabled:()=>ye,bindSettingsStore:()=>Gn,createPluginServices:()=>Jr,default:()=>Hf,getStorageDir:()=>Dr,isAdminAuthEnabled:()=>V,packageVersion:()=>zt,registerRoutes:()=>ld,renderEditorSidebarJs:()=>hr,renderEditorSidebarLoaderHtml:()=>pi,requireAccessLink:()=>Ct,resolveAiAccessSettings:()=>ge});module.exports=hd(Uf);var dd=require("fs"),Vr=require("path");var jn=require("fs"),cn=require("path");q();function Hn(e){let t=(0,cn.join)(e,".ai-access");return{root:t,proposals:(0,cn.join)(t,"proposals"),logs:(0,cn.join)(t,"logs"),settings:(0,cn.join)(t,"config.json")}}a(Hn,"resolveAiAccessPaths");function Yr(e){(0,jn.mkdirSync)(e,{recursive:!0,mode:Xr});try{(0,jn.chmodSync)(e,Xr)}catch{}}a(Yr,"mkdirSecure");function ds(e){Yr(e.root),Yr(e.proposals),Yr(e.logs)}a(ds,"ensureAiAccessDirs");tt();Pe();function Kn(e){let t=ne(e);return`auth-tokens${(t==="/"?"":t).replace(/\//g,"-")}`}a(Kn,"editorAuthTokenKey");Pe();Ne();function Rc(e){if(!e)return!1;let t=e.split(";")[0]?.trim().toLowerCase()??"";return t==="application/json"||t.endsWith("+json")}a(Rc,"isJsonContentType");function Lc(e){let t=e.headers["content-length"];if(typeof t=="string"&&t.length>0){let n=Number(t);if(Number.isFinite(n)&&n>0)return!0;if(n===0)return!1}return e.headers["transfer-encoding"]!==void 0}a(Lc,"hasRequestBody");function Ve(e="rest"){return(t,n,r)=>{let o=t.headers["content-type"];if(Rc(o)){r();return}if(!o&&!Lc(t)){r();return}let s=di(e,"unsupported_media_type",{statusCode:415});n.status(s.statusCode).json(s.body)}}a(Ve,"requireJsonContentType");var Cc="text/plain; charset=utf-8",Ec="text/html; charset=utf-8",Ic="default-src 'none'; base-uri 'self'; form-action 'none'; frame-ancestors 'none'";function Nc(e){let t=e.query?.format;return typeof t=="string"?t.toLowerCase():void 0}a(Nc,"formatParam");function Fc(e){let t=Nc(e);if(t==="md"||t==="json")return"native";if(!e.get("accept"))return"html";let n=e.accepts(["text/html","application/json","text/markdown","text/plain"]);return n==="application/json"||n==="text/markdown"?"native":n==="text/plain"?"plain":"html"}a(Fc,"chooseGetLinkRepresentation");function li(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}a(li,"escapeHtml");var Oc=/(?<![A-Za-z0-9_.~\-/])\.\/[A-Za-z0-9._~\-/]+(?:\?[A-Za-z0-9._~\-=&%]+)?/g;function Mc(e){return e.replace(Oc,(t,n)=>{if(e.startsWith("<",n+t.length))return t;let r=t.replace(/[.]+$/,""),o=t.slice(r.length);return r.length<=2||r.includes("..")||r.includes("//")||r.includes("&")?t:`<a href="${r}">${r}</a>${o}`})}a(Mc,"linkifyEscaped");function $c(e,t){return`<!doctype html>
|
|
8
|
-
<html><head><meta charset="utf-8">${t?`<base href="${
|
|
9
|
-
`}a(
|
|
3
|
+
`,o=Buffer.from(r,"utf8");if(o.byteLength>t)throw new X(n.kind,o.byteLength,t);return{bytes:o}}function ds(e,t){let n=(0,dn.resolve)(t),r=(0,dn.resolve)(e);if(r!==n&&!r.startsWith(n+dn.sep))throw new Error("Path escapes .ai-access storage root");return r}function Tt(e,t,n){let r=ds(t,e),o=`${r}.${process.pid}.tmp`;(0,Oe.writeFileSync)(o,n.bytes,{mode:Mn}),(0,Oe.renameSync)(o,r),(0,Oe.chmodSync)(r,Mn)}function ls(e,t,n){let r=ds(t,e);(0,Oe.appendFileSync)(r,n.bytes,{mode:Mn});try{(0,Oe.chmodSync)(r,Mn)}catch{}}var Oe,dn,Kr,Mn,j,as,X,z=U(()=>{"use strict";Oe=require("fs"),dn=require("path"),Kr=448,Mn=384,j=15e5,as=8192,X=class extends Error{constructor(n,r,o){super(`${n} size ${r} bytes exceeds limit of ${o} bytes`);this.kind=n;this.size=r;this.maxBytes=o;this.name="StoragePayloadTooLargeError"}static{a(this,"StoragePayloadTooLargeError")}code="payload_too_large";statusCode=413};a(je,"encodeTrustedJson");a(ds,"assertUnderAiAccess");a(Tt,"writeTrustedAtomic");a(ls,"appendTrusted")});function cn(e){if(typeof e!="string"||e.length===0||e.length>2048)return;let t;try{t=new URL(e)}catch{return}if(!(t.protocol!=="http:"&&t.protocol!=="https:")&&!(t.username||t.password||t.search||t.hash)&&!(t.pathname!=="/"||/^[a-z][a-z0-9+.-]*:\/\/[^/]*\//i.test(e)))return t.origin.toLowerCase()}function Td(e,t){let n=e.getHeader("Vary"),r=String(n??"").split(",").map(o=>o.trim()).filter(Boolean);r.some(o=>o.toLowerCase()===t.toLowerCase())||r.push(t),e.setHeader("Vary",r.join(", "))}function us(e){return(t,n,r)=>{if(!kd.test(t.path)){r();return}for(let c of n.getHeaderNames())c.startsWith("access-control-")&&n.removeHeader(c);let o=n.setHeader.bind(n),s=!1;n.setHeader=((c,u)=>!s&&/^access-control-/i.test(c)?n:o(c,u));let i=cn(t.get("origin")),d=i===void 0?void 0:e().find(c=>c===i),l=d!==void 0;if(Td(n,"Origin"),!l){if(t.method==="OPTIONS"&&t.get("access-control-request-method")){n.status(204).end();return}r();return}if(s=!0,n.setHeader("Access-Control-Allow-Origin",d),n.setHeader("Access-Control-Expose-Headers",Sd),t.method==="OPTIONS"&&t.get("access-control-request-method")){n.setHeader("Access-Control-Allow-Methods",Ad),n.setHeader("Access-Control-Allow-Headers",xd),n.setHeader("Access-Control-Max-Age",String(600)),n.status(204).end();return}s=!1,r()}}function ps(e,t){let n=e.parent,r=n&&typeof n.use=="function"?n:e;r.use(t);let o=r,i=(o._router??o.router)?.stack;if(!Array.isArray(i)||i.length<2)return;let d=i.pop(),l=0;for(;l<i.length;){let c=i[l].name;if(c!=="query"&&c!=="expressInit")break;l+=1}i.splice(l,0,d)}var kd,Ad,xd,Sd,Hn=U(()=>{"use strict";kd=/^\/mcp\/(?:a(?:\/|$)|api(?:\/|$)|rpc\/?$)/,Ad="GET, POST, DELETE, OPTIONS",xd="Authorization, Content-Type, Accept, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID",Sd="Mcp-Session-Id, WWW-Authenticate, Retry-After, RateLimit, RateLimit-Policy, RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset";a(cn,"canonicalOrigin");a(Td,"appendVary");a(us,"aiCorsMiddleware");a(ps,"installAiCorsMiddleware")});function Pd(e){return e==="nodes"||e==="flows"||e==="full"}function Un(e,t){if(!e||typeof e!="object"||Array.isArray(e))return{};let n=e,r={};if(typeof n.enabled=="boolean"&&(r.enabled=n.enabled),typeof n.allowWithoutAdminAuth=="boolean"&&(r.allowWithoutAdminAuth=n.allowWithoutAdminAuth),typeof n.defaultLinkTtlSeconds=="number"&&Number.isFinite(n.defaultLinkTtlSeconds)&&n.defaultLinkTtlSeconds>0&&(r.defaultLinkTtlSeconds=Math.floor(n.defaultLinkTtlSeconds)),Pd(n.defaultDeploymentType)&&(r.defaultDeploymentType=n.defaultDeploymentType),typeof n.maxActiveLinks=="number"&&Number.isFinite(n.maxActiveLinks)&&n.maxActiveLinks>0&&(r.maxActiveLinks=Math.floor(n.maxActiveLinks)),typeof n.publicBaseUrl=="string")try{r.publicBaseUrl=Rt(n.publicBaseUrl)}catch{t?.(`AI Access: invalid publicBaseUrl value ${JSON.stringify(n.publicBaseUrl)} was dropped`)}if(Array.isArray(n.corsAllowedOrigins)){let o=new Set;for(let s of n.corsAllowedOrigins){let i=cn(s);i?o.add(i):t?.(`AI Access: invalid CORS origin value ${JSON.stringify(s)} was dropped`)}r.corsAllowedOrigins=[...o]}for(let o of["rateLimitWindowMs","rateLimitMaxPerIp","rateLimitMaxPerToken"]){let s=n[o],i=o==="rateLimitWindowMs"?100:1,d=o==="rateLimitWindowMs"?36e5:1e6;typeof s=="number"&&Number.isInteger(s)&&s>=i&&s<=d&&(r[o]=s)}if(n.oauth&&typeof n.oauth=="object"&&!Array.isArray(n.oauth)){let o=n.oauth;r.oauth={},typeof o.enabled=="boolean"&&(r.oauth.enabled=o.enabled);for(let i of["accessTokenTtlSec","refreshIdleTtlSec","dcrMaxClients"]){let d=o[i];typeof d=="number"&&Number.isInteger(d)&&d>0&&d<=31536e3&&(r.oauth[i]=d)}let s=o.refreshMaxLifetimeSec;typeof s=="number"&&Number.isInteger(s)&&s>=0&&s<=31536e4&&(r.oauth.refreshMaxLifetimeSec=s)}return r}function Rd(e,t){if(!(0,pn.existsSync)(e))return{};try{let n=(0,pn.readFileSync)(e,"utf8");return Un(JSON.parse(n),t)}catch(n){return t?.(`AI Access: config.json is corrupt or unreadable; defaults are used (${String(n)})`),{}}}function Bn(e,t){let n={},r={...un};for(let s of Object.keys(un).filter(i=>i!=="oauth"))n[s]=e[s]!==void 0?"settings.js":t[s]!==void 0?"disk":"default",Object.assign(r,{[s]:e[s]??t[s]??un[s]});let o={...r,sources:n};return e.oauth!==void 0||t.oauth!==void 0?(o.oauth={..._t,...t.oauth??{},...e.oauth??{}},n.oauth=e.oauth!==void 0?"settings.js":"disk"):(o.oauth={..._t},n.oauth="default"),o}function Yr(e,t){let n=Un(e??{},t);for(let o of Object.keys(un).filter(s=>s!=="oauth"))e&&Object.hasOwn(e,o)&&n[o]===void 0&&Object.assign(n,{[o]:un[o]});let r=e?.oauth;if(r&&typeof r=="object"&&!Array.isArray(r)){let o=n.oauth??={};for(let s of Object.keys(_t))Object.hasOwn(r,s)&&o[s]===void 0&&(t?.(`AI Access: settings.js aiAccess.oauth.${s} is invalid; using the default (${String(_t[s])})`),Object.assign(o,{[s]:_t[s]}))}return n}var pn,Xr,_d,_t,un,Pt,qn=U(()=>{"use strict";pn=require("fs"),Xr=require("path");ae();Hn();z();_d=16384,_t={enabled:!1,accessTokenTtlSec:3600,refreshIdleTtlSec:2592e3,refreshMaxLifetimeSec:7776e3,dcrMaxClients:20},un={enabled:!0,allowWithoutAdminAuth:!1,defaultLinkTtlSeconds:14400,maxActiveLinks:10,defaultDeploymentType:"nodes",publicBaseUrl:"",corsAllowedOrigins:[],rateLimitWindowMs:6e4,rateLimitMaxPerIp:600,rateLimitMaxPerToken:300,oauth:{..._t}};a(Pd,"isDeploymentType");a(Un,"sanitizePartial");a(Rd,"readDiskFile");a(Bn,"mergeAiAccessSettings");a(Yr,"settingsJsPartial");Pt=class{constructor(t,n,r){this.paths=t;this.filePath=(0,Xr.join)(t.root,"config.json"),this.settingsJs=Yr(n,r),this.disk=Rd((0,pn.existsSync)(this.filePath)?this.filePath:(0,Xr.join)(t.root,"settings.json"),r),this.cached=Bn(this.settingsJs,this.disk)}static{a(this,"SettingsStore")}filePath;disk;settingsJs;cached;getEffective(){return this.cached}getDisk(){return{...this.disk}}update(t){let n=Un(t);for(let r of Object.keys(this.settingsJs))delete n[r];return this.persist(n),this.disk=n,this.cached=Bn(this.settingsJs,this.disk),this.cached}patch(t){let n=Un(t);return this.update({...this.disk,...n,...n.oauth?{oauth:{...this.disk.oauth,...n.oauth}}:{}})}persist(t){let n=je(t,_d,{kind:"settings",pretty:!0});Tt(this.paths.root,this.filePath,n)}}});function Wn(e){Qr=e}function W(e){return e.adminAuth!=null&&e.adminAuth!==!1}function he(e){return Qr?Qr.getEffective():Bn(Yr(e.aiAccess),{})}function ge(e){let t=he(e);return t.enabled?W(e)?!0:t.allowWithoutAdminAuth:!1}var Qr,ye=U(()=>{"use strict";qn();Qr=null;a(Wn,"bindSettingsStore");a(W,"isAdminAuthEnabled");a(he,"resolveAiAccessSettings");a(ge,"areAiFeaturesEnabled")});function ne(e){return!e||e==="/"?"/":e.endsWith("/")?e.slice(0,-1):e}function de(e){let t=ne(e);return t==="/"?"/mcp/api/v1":`${t}/mcp/api/v1`}function He(e){let t=ne(e);return t==="/"?"/mcp/rpc":`${t}/mcp/rpc`}function Cd(e){let t=e.authInfo?.scope;if(t)return Array.isArray(t)?t:[t];let n=e.user?.permissions;return n?Array.isArray(n)?n:[n]:[]}function Ue(e){if(e.auth)return e.auth.scopes.includes("propose")?"propose":e.auth.scopes.includes("read")?"read":null;let t=Cd(e);return t.includes("*")?"*":t.includes("read")?"read":null}function Ld(e,t){return!t||!e?{view:!0,manageLinks:!1,approveProposals:!1}:e==="*"?{view:!0,manageLinks:!0,approveProposals:!0}:{view:!0,manageLinks:!1,approveProposals:!1}}function fs(e,t={error:"Authentication required",code:"auth_required"},n){let r=e.end.bind(e),o=!0;return e.end=((...s)=>o&&e.statusCode===401&&!e.headersSent?(o=!1,e.removeHeader("Content-Length"),e.setHeader("Content-Type","application/json; charset=utf-8"),n&&e.setHeader("WWW-Authenticate",n),r(JSON.stringify(t))):r(...s)),()=>{o=!1,e.end=r}}function we(e,t){let n="read";return(r,o,s)=>{if(!W(e.settings)){s();return}let i=t==="api"?fs(o):null;e.auth.needsPermission(n)(r,o,d=>{if(i?.(),d){s(d);return}if(!r.user){if(t==="page"){o.redirect(ne(e.settings.httpAdminRoot));return}o.status(401).json({error:"Authentication required",code:"auth_required"});return}if(!Ue(r)){o.status(401).json({error:"Insufficient permissions",code:"insufficient_permissions"});return}s()})}}function Gn(e,t){let n=W(e.settings),r=he(e.settings),o=ge(e.settings),s=null;return n?s=Ue(t):o&&(s="*"),{adminAuthEnabled:n,aiFeaturesEnabled:o,allowWithoutAdminAuth:r.allowWithoutAdminAuth,permission:s,capabilities:Ld(s,o)}}function Ct(e){return(t,n,r)=>{if(!ge(e.settings)){n.status(403).json({error:"AI Access features are disabled. Enable adminAuth, or set aiAccess.allowWithoutAdminAuth to true for local-only use.",code:"ai_features_disabled"});return}r()}}function fn(e){return(t,n,r)=>{if(!W(e.settings)){if(ge(e.settings)){r();return}n.status(403).json({error:"AI Access features are disabled",code:"ai_features_disabled"});return}if(Ue(t)!=="*"){n.status(403).json({error:"Full admin permission (*) is required for this action",code:"insufficient_permissions"});return}r()}}function Ed(e){return e.startsWith("aia_")}function Id(e){return Ed(e)?!0:e.length<24}function Nd(e){if(e.length<24||e.startsWith("aia_")&&e.length<40)return"The token may be truncated \u2014 ask the user for the full access-link token."}function ms(e,t,n,r){if(!W(e.settings)){r();return}let o={error:le,code:Re},s=fs(n,o,Y);e.auth.needsPermission("read")(t,n,i=>{if(s(),i){r(i);return}if(!n.headersSent){if(!t.user||!Ue(t)){n.setHeader("WWW-Authenticate",Y),n.status(401).json(o);return}r()}})}function hs(e,t,n,r,o){let s=e.headers.authorization;if(typeof s!="string"||!/^Bearer\s+\S+/i.test(s.trim()))return"continue";let d=/^Bearer\s+(\S+)$/i.exec(s.trim())?.[1];if(!d)return"continue";let l=n(d);if(l.status==="active")return l.link.kind!==r?(gn(t,r,l.link.kind),"wrong_kind"):(e.accessLink=l.link,"ok");if(l.status==="expired")return hn(e,t,o,l.expiresAt),"invalid";if(Id(d)){t.setHeader("WWW-Authenticate",Y);let c=Nd(d);return t.status(401).json({error:rt,code:mn,...c?{hint:c}:{}}),"invalid"}return"continue"}function gs(e,t,n="rest"){return(r,o,s)=>{if(!ge(e.settings)){o.status(403).json({error:"AI Access features are disabled. Enable adminAuth, or set aiAccess.allowWithoutAdminAuth to true for local-only use.",code:"ai_features_disabled"});return}let i=hs(r,o,t,n,e.settings.httpAdminRoot);if(i==="ok"){s();return}i==="invalid"||i==="wrong_kind"||ms(e,r,o,s)}}function ys(e,t,n="rest"){return(r,o,s)=>{let i=hs(r,o,t,n,e.settings.httpAdminRoot);if(i==="ok"){s();return}i==="invalid"||i==="wrong_kind"||ms(e,r,o,s)}}var Pe=U(()=>{"use strict";ot();ye();a(ne,"normalizeHttpAdminRoot");a(de,"mcpApiBase");a(He,"mcpRpcPath");a(Cd,"scopesFromRequest");a(Ue,"resolvePermission");a(Ld,"capabilitiesFor");a(fs,"interceptNeedsPermissionUnauthorized");a(we,"requireAdminSession");a(Gn,"describeAccess");a(Ct,"requireAiFeatures");a(fn,"requireStarPermission");a(Ed,"looksLikeAccessLinkToken");a(Id,"looksLikeFailedAccessLinkAttempt");a(Nd,"truncatedTokenHint");a(ms,"continueAsAdminSessionOrAccessLinkMissing");a(hs,"tryAccessLinkBearer");a(gs,"requireAdminReadOrAccessLink");a(ys,"requireAdminSessionOrAccessLink")});function Fd(e,t){let n=ne(e);return`${n==="/"?"/mcp/a":`${n}/mcp/a`}/${t}`}function Od(e){return!!e.app.get("trust proxy")}function ws(e,t){let n=e.get(t);if(typeof n!="string"||n.length===0)return;let r=n.split(",")[0]?.trim();return r&&r.length>0?r:void 0}function $d(e){if(typeof e!="string"||e.trim().length===0)return;let t;try{t=new URL(e.trim())}catch{return}if(!(t.protocol!=="http:"&&t.protocol!=="https:"))return`${t.origin}${t.pathname.replace(/\/+$/,"")}`}function st(e){try{let n=new URL(`http://${e.includes(":")&&!e.startsWith("[")?`[${e}]`:e}`).hostname.replace(/^\[|\]$/g,"");if(!(0,Zr.isIP)(n))return;if(n.startsWith("::ffff:")){let r=n.slice(7);if((0,Zr.isIP)(r)===4)return r;let o=r.split(":");if(o.length===2){let s=parseInt(o[0],16)*65536+parseInt(o[1],16);return[24,16,8,0].map(i=>s>>>i&255).join(".")}}return n}catch{return}}function Rt(e){if(e==="")return"";let t="publicBaseUrl must be an HTTP(S) origin with no path, query, fragment, or credentials";if(typeof e!="string")throw new Error(t);let n;try{n=new URL(e.trim())}catch{throw new Error(t)}if(n.protocol!=="https:"&&n.protocol!=="http:"||!/^https?:\/\/[^/?#]+\/?$/i.test(e.trim())||n.pathname!=="/"||n.search||n.hash||n.username||n.password||e.includes("?")||e.includes("#"))throw new Error(t);return n.origin}function zn(e){eo=$d(e)}function vs(e){if(typeof e!="string"||e.length===0||e.length>260)return!1;let t,n,r=/^(\[[^\]]*\])(?::(\d+))?$/.exec(e);if(r){if(t=r[1],n=r[2],!jd.test(t))return!1;try{new URL(`http://${t}/`)}catch{return!1}}else{let o=/^([^:]+)(?::(\d+))?$/.exec(e);if(!o||(t=o[1],n=o[2],t.length>253)||!Dd.test(t)&&!Md.test(t))return!1}if(n!==void 0){if(!/^\d{1,5}$/.test(n))return!1;let o=Number(n);if(o<1||o>65535)return!1}return!0}function As(e){let t=e?.trim().toLowerCase();return t==="http"||t==="https"?t:void 0}function Hd(e){let t=e.socket;return t?.encrypted?"https":t?"http":As(e.protocol)??"http"}function to(e){if(eo)return eo;let t=Od(e),n=t?ws(e,"x-forwarded-host"):void 0,r=e.get("host")?.trim(),o=vs(n)?n:vs(r)?r:void 0;if(!o)return;let s=(t?As(ws(e,"x-forwarded-proto")):void 0)??Hd(e);try{return new URL(`${s}://${o}`).origin}catch{return}}function yn(e,t){let n=to(e);return n?`${n}${t}`:t}function xs(e,t,n){return yn(e,Fd(t,n))}function no(e){return e.replace(/\/mcp\/a\/[^/?#]+/gi,"/mcp/a/<redacted>")}var Zr,Lt,ks,eo,bs,Md,Dd,jd,ae=U(()=>{"use strict";Zr=require("net");Pe();Lt=8192,ks=1800*1e3;a(Fd,"mcpAccessLinkPath");a(Od,"isTrustProxyEnabled");a(ws,"firstForwardedValue");a($d,"normalizePublicBaseUrl");a(st,"canonicalizeIp");a(Rt,"validateSettingsPublicBaseUrl");a(zn,"bindPublicBaseUrl");bs="[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?",Md=new RegExp(`^${bs}(?:\\.${bs})*$`),Dd=/^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/,jd=/^\[[0-9A-Fa-f:.]+\]$/;a(vs,"isValidHostHeader");a(As,"validScheme");a(Hd,"connectionScheme");a(to,"requestPublicOrigin");a(yn,"absolutizePath");a(xs,"buildAccessLinkUrl");a(no,"redactAccessLinkTokenInPath")});function zd(e){if(!e||e.trim().length===0)return{kind:"missing"};let t=e.trim();if(/^Basic\s+/i.test(t))return{kind:"basic"};let n=/^Bearer\s+(\S+)$/i.exec(t);return n?.[1]?{kind:"bearer",token:n[1]}:{kind:"other"}}function ro(e,t,n,r){e.setHeader("WWW-Authenticate",Y),e.status(401).json({error:t,code:n,...r?{hint:r}:{}})}function Jd(e){let t=e??"";for(;t.endsWith("/");)t=t.slice(0,-1);return`${t}/mcp#links`}function Kd(e,t,n,r){let o=yn(e,Jd(t));return{error:Ud,code:qd,...n?{expiredAt:n}:{},accessLinksUrl:o,hint:r??`Ask the user to create a new access link in Node-RED on the /mcp page \u2192 Access links (${o}).`}}function hn(e,t,n,r,o){t.setHeader("WWW-Authenticate",Y),t.status(401).json(Kd(e,n,r,o))}function Vd(e){if(e.length<24||e.startsWith("aia_")&&e.length<40)return"The token may be truncated \u2014 ask the user for the full access-link token."}function Jn(e){return e==="get_link"?"Please open Node-RED \u2192 /mcp \u2192 Access links and create a get_link access URL, then send me that /mcp/a/<token> link.":`Please open Node-RED \u2192 /mcp \u2192 Access links and create a kind=${e} token, then send me the Bearer token (not a get_link path URL).`}function Yd(e,t){let n=Xd;return e==="get_link"?`This URL requires a get_link token. This token is kind=${t} \u2014 ${n} for a get_link access URL (/mcp/a/<token>/\u2026). What to tell the user: ${Jn("get_link")}`:e==="rest"?`This REST endpoint requires a rest token. This token is kind=${t} \u2014 ${n}. What to tell the user: ${Jn("rest")}`:`This MCP endpoint requires an mcp token. This token is kind=${t} \u2014 ${n}. What to tell the user: ${Jn("mcp")}`}function gn(e,t,n){e.setHeader("WWW-Authenticate",Y),e.status(403).json({error:`Access link kind mismatch: expected ${t}, got ${n}`,code:Gd,expectedKind:t,actualKind:n,hint:Yd(t,n),tellUser:Jn(t)})}function Et(e,t,n){return(r,o,s)=>{if(!ge(e.settings)){o.status(403).json({error:"AI Access features are disabled. Enable adminAuth, or set aiAccess.allowWithoutAdminAuth to true for local-only use.",code:"ai_features_disabled"});return}let i=zd(r.headers.authorization);if(i.kind==="basic"){ro(o,Bd,Wd);return}if(i.kind==="missing"||i.kind==="other"){ro(o,le,Re);return}let d=t.validateResult(i.token);if(d.status==="expired"){hn(r,o,e.settings.httpAdminRoot,d.expiresAt);return}if(d.status!=="active"){ro(o,rt,mn,Vd(i.token));return}if(d.link.kind!==n){gn(o,n,d.link.kind);return}r.accessLink=d.link,s()}}var rt,Ud,le,Bd,Y,Re,mn,qd,Wd,Gd,Xd,ot=U(()=>{"use strict";ae();ye();rt="Access link is invalid, revoked, or no longer available (it may have expired, or Node-RED may have restarted). Ask the user for a new access link.",Ud="Access link token has expired. Ask the user for a new access link.",le="Access link Bearer token is missing. Ask the user for a new access link and send Authorization: Bearer <token>.",Bd="Authorization must use the Bearer scheme (Authorization: Bearer <access-link-token>), not Basic. Ask the user for a new access link.",Y='Bearer realm="node-red-ai-access", charset="UTF-8"',Re="access_link_missing",mn="access_link_invalid",qd="token_expired",Wd="bearer_scheme_required",Gd="wrong_token_kind";a(zd,"parseAuthorizationHeader");a(ro,"sendUnauthorized");a(Jd,"accessLinksPagePath");a(Kd,"tokenExpiredBody");a(hn,"sendTokenExpired");a(Vd,"truncatedBearerHint");Xd="ask the owner: Node-RED /mcp \u2192 Access links \u2192 create kind=rest/mcp (or get_link)";a(Jn,"whatToTellUserWrongTokenKind");a(Yd,"wrongTokenKindHint");a(gn,"sendWrongTokenKind");a(Et,"requireAccessLink")});var J,G,be,Ce,Vn,ve=U(()=>{"use strict";J=class extends Error{static{a(this,"FlowRevisionConflictError")}code="version_mismatch";statusCode=409;expectedRev;currentRev;context;constructor(t,n,r="updateFlow"){super(`Flow revision conflict in ${r}: expectedRev '${t}' does not match current rev '${n}'. The flows changed since you last read them \u2014 re-read flows, re-apply your change, and retry.`),this.name="FlowRevisionConflictError",this.expectedRev=t,this.currentRev=n,this.context=r}},G=class extends Error{static{a(this,"FlowNotFoundError")}code="flow_not_found";statusCode=404;flowId;constructor(t){super(`Flow '${t}' was not found`),this.name="FlowNotFoundError",this.flowId=t}},be=class extends Error{static{a(this,"AdapterUnavailableError")}code="adapter_unavailable";statusCode=502;constructor(t){super(t),this.name="AdapterUnavailableError"}},Ce=class extends Error{static{a(this,"PluginMisconfiguredError")}code="plugin_misconfigured";statusCode=502;constructor(t){super(t),this.name="PluginMisconfiguredError"}},Vn="Node-RED AI Access could not use the in-process flow runtime and the HTTP Admin API fallback returned Unauthorized. The plugin is misconfigured \u2014 tell the admin to ensure the plugin loads inside Node-RED so RED.runtime.flows is available (do not rely on unauthenticated localhost HTTP when adminAuth is enabled)."});function Le(e,t){if(!e||!Qd.test(e)||e.includes(".."))throw new Error(`Invalid ${t}`);return e}function Xn(e){if(!e||!Zd.test(e)||e.includes(".."))throw new Error("Invalid proposal id");return e}function oo(e,t){let n=(0,it.basename)(t);if(!n||n!==t||n.includes(".."))throw new Error("Invalid file name");let r=(0,it.resolve)(e),o=(0,it.resolve)(r,n);if(o!==r&&!o.startsWith(r+it.sep))throw new Error("Path escapes storage directory");return o}function Ss(e,t=1e5){if(typeof e=="string")return e.replace(/\0/g,"").slice(0,t)}function It(e){let t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},n={};for(let o of el){if(!(o in t))continue;let s=t[o];if(o==="disabled"&&typeof s=="boolean"){n[o]=s;continue}if(o==="env"&&Array.isArray(s)){let d=JSON.stringify(s);d.length<=5e5&&(n[o]=JSON.parse(d));continue}let i=Ss(s,1e4);i!==void 0&&(n[o]=i)}for(let o of["nodes","configs"]){let s=t[o];if(!Array.isArray(s))continue;let i=JSON.stringify(s),d=Buffer.byteLength(i,"utf8");if(d>j)throw new X(`flow.${o}`,d,j);n[o]=JSON.parse(i)}let r=Buffer.byteLength(JSON.stringify(n),"utf8");if(r>j)throw new X("flow",r,j);return n}function Ts(e){let t={};for(let n of tl){if(!(n in e))continue;let r=e[n];if(nl.has(n)){if(typeof r=="number"&&Number.isFinite(r))t[n]=r;else if(typeof r=="string"&&r.trim()!==""){let s=Number(r);Number.isFinite(s)&&(t[n]=s)}continue}let o=Ss(r,2e3);o!==void 0&&(t[n]=o)}return t}var it,Qd,Zd,el,tl,nl,Yn=U(()=>{"use strict";it=require("path");z();Qd=/^[A-Za-z0-9_.-]+$/,Zd=/^prop-[A-Za-z0-9_-]+$/,el=["id","type","label","disabled","info","env"],tl=["proposalId","flowId","expectedRev","resultRev","reason","code","currentRev","title","displayName","flowLabel","linkId","label","expiresAt","createdAt","ttlSeconds","kind"],nl=new Set(["ttlSeconds"]);a(Le,"assertSafeSegment");a(Xn,"assertSafeProposalId");a(oo,"resolveUnderDir");a(Ss,"sanitizeString");a(It,"sanitizeFlowTab");a(Ts,"sanitizeAuditDetails")});function so(e,t){return`oauth-${(0,Zn.createHash)("sha256").update(`${e}\0${t}`).digest("hex")}`}function at(e){return e.kind==="oauth"?so(e.clientId,e.user):e.linkId}function rl(e){return e==="pending"||e==="approved"||e==="rejected"||e==="failed"||e==="withdrawn"||e==="expired"||e==="superseded"}function _s(e){if(!Array.isArray(e)||e.length===0)return;let t=[];for(let n of e.slice(0,ol)){if(!n||typeof n!="object"||Array.isArray(n))continue;let r=n;if(typeof r.code!="string"||!r.code||typeof r.error!="string"||!r.error)continue;let o={code:r.code.replace(/\0/g,"").slice(0,80),error:r.error.replace(/\0/g,"").slice(0,500)};typeof r.nodeId=="string"&&r.nodeId&&(o.nodeId=r.nodeId.replace(/\0/g,"").slice(0,200)),typeof r.type=="string"&&r.type&&(o.type=r.type.replace(/\0/g,"").slice(0,200)),typeof r.property=="string"&&r.property&&(o.property=r.property.replace(/\0/g,"").slice(0,200)),typeof r.configType=="string"&&r.configType&&(o.configType=r.configType.replace(/\0/g,"").slice(0,200)),Array.isArray(r.candidates)&&(o.candidates=r.candidates.filter(s=>typeof s=="string"&&s.length>0).slice(0,20).map(s=>s.replace(/\0/g,"").slice(0,200))),t.push(o)}return t.length>0?t:void 0}var Zn,Be,Ps,re,te,Nt,wn,ol,Qn,qe=U(()=>{"use strict";Zn=require("crypto"),Be=require("fs"),Ps=require("path");Yn();z();a(so,"oauthOwnerId");a(at,"proposalOwnerId");re=class extends Error{static{a(this,"ProposalNotFoundError")}code="proposal_not_found";statusCode=404;constructor(t){super(`Proposal '${t}' was not found`),this.name="ProposalNotFoundError"}},te=class extends Error{static{a(this,"ProposalNotPendingError")}code="proposal_not_pending";statusCode=409;constructor(t,n){super(`Proposal '${t}' is '${n}', not pending`),this.name="ProposalNotPendingError"}},Nt=class extends Error{static{a(this,"ProposalNotOwnedError")}code="proposal_not_owned";statusCode=403;constructor(t){super(`Proposal '${t}' was not created by this access link`),this.name="ProposalNotOwnedError"}},wn=class extends Error{static{a(this,"ProposalConcurrentApproveError")}code="proposal_approve_in_progress";statusCode=409;constructor(t){super(`Proposal '${t}' is already being approved`),this.name="ProposalConcurrentApproveError"}};a(rl,"isProposalStatus");ol=50;a(_s,"sanitizeProposalWarnings");Qn=class{constructor(t,n={}){this.paths=t;this.now=n.now??(()=>Date.now()),this.randomId=n.randomId??(()=>(0,Zn.randomBytes)(12).toString("hex")),this.maxProposalBytes=n.maxProposalBytes??j}static{a(this,"ProposalStore")}inFlight=new Set;now;randomId;maxProposalBytes;fileFor(t){let n;try{n=Xn(t)}catch{throw new re(t)}return oo(this.paths.proposals,`${n}.json`)}toDiskRecord(t){let n=Xn(t.id),r=Le(String(t.expectedRev).slice(0,200),"expectedRev"),o=Le(String(t.payload.flowId).slice(0,200),"flowId"),s=It(t.payload.flow),i=t.baseFlow!==void 0?It(t.baseFlow):void 0,d=t.createdBy.kind==="oauth"?{kind:"oauth",clientId:Le(String(t.createdBy.clientId).slice(0,200),"clientId"),user:String(t.createdBy.user).replace(/\0/g,"").slice(0,200)}:{kind:"access-link",linkId:Le(String(t.createdBy.linkId).slice(0,200),"linkId"),...typeof t.createdBy.label=="string"?{label:t.createdBy.label.replace(/\0/g,"").slice(0,200)}:{}},l={id:n,type:"replace_flow",status:rl(t.status)?t.status:"pending",expectedRev:r,payload:{flowId:o,flow:s},createdBy:d,createdAt:String(t.createdAt).slice(0,64),updatedAt:String(t.updatedAt).slice(0,64)};i!==void 0&&(l.baseFlow=i),typeof t.title=="string"&&t.title.length>0&&(l.title=t.title.replace(/\0/g,"").slice(0,80).trim(),l.title||delete l.title);let c=_s(t.warnings);if(c&&(l.warnings=c),t.decidedBy)if(t.decidedBy.kind==="access-link"){let u={kind:"access-link",linkId:Le(String(t.decidedBy.linkId).slice(0,200),"linkId")};typeof t.decidedBy.label=="string"&&(u.label=t.decidedBy.label.replace(/\0/g,"").slice(0,200)),l.decidedBy=u}else{let u={kind:"admin"};typeof t.decidedBy.username=="string"&&(u.username=t.decidedBy.username.replace(/\0/g,"").slice(0,200)),l.decidedBy=u}return typeof t.reason=="string"&&(l.reason=t.reason.replace(/\0/g,"").slice(0,2e3)),typeof t.resultRev=="string"&&(l.resultRev=Le(t.resultRev.slice(0,200),"resultRev")),l}writeAtomic(t,n){let r=this.fileFor(t),o=this.toDiskRecord(n),s=je(o,this.maxProposalBytes,{kind:"proposal",pretty:!0});Tt(this.paths.root,r,s)}create(t){let n=new Date(this.now()).toISOString(),r=Xn(`prop-${this.randomId()}`),o={id:r,type:"replace_flow",status:"pending",expectedRev:t.expectedRev,payload:{flowId:t.payload.flowId,flow:t.payload.flow},baseFlow:t.baseFlow,title:t.title,createdBy:t.createdBy,createdAt:n,updatedAt:n},s=_s(t.warnings);return s&&(o.warnings=s),this.writeAtomic(r,o),this.get(r)}get(t){let n=this.fileFor(t);if(!(0,Be.existsSync)(n))throw new re(t);return JSON.parse((0,Be.readFileSync)(n,"utf8"))}list(t){if(!(0,Be.existsSync)(this.paths.proposals))return[];let n=(0,Be.readdirSync)(this.paths.proposals).filter(o=>o.endsWith(".json")),r=[];for(let o of n){let s=(0,Ps.basename)(o);if(!(s!==o||s.includes("..")))try{let i=JSON.parse((0,Be.readFileSync)(oo(this.paths.proposals,s),"utf8"));if(t?.linkId&&at(i.createdBy)!==t.linkId||t?.status&&i.status!==t.status||t?.flowId&&i.payload.flowId!==t.flowId)continue;r.push(i)}catch{}}return r.sort((o,s)=>o.createdAt<s.createdAt?1:-1)}beginApprove(t){if(this.inFlight.has(t))throw new wn(t);let n=this.get(t);if(n.status!=="pending")throw new te(t,n.status);return this.inFlight.add(t),n}releaseLock(t){this.inFlight.delete(t)}markApproved(t,n,r){let o=this.get(t);return o.status="approved",o.decidedBy=n,o.resultRev=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markRejected(t,n,r){let o=this.get(t);return o.status="rejected",n&&(o.decidedBy=n),o.reason=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markFailed(t,n,r){let o=this.get(t);return o.status="failed",n&&(o.decidedBy=n),o.reason=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markWithdrawn(t,n,r){let o=this.get(t);if(o.status!=="pending")throw new te(t,o.status);return o.status="withdrawn",o.decidedBy=n,o.reason=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markClosed(t,n,r,o){let s=this.get(t);if(s.status!=="pending")throw new te(t,s.status);return s.status=n,o&&(s.decidedBy=o),s.reason=r,s.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,s),this.get(t)}}});function Rs(e,t){if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`${t} must be a node object`);let n=e;if(typeof n.id!="string"||n.id.length===0)throw new Error(`${t} needs a non-empty string id`);if(typeof n.type!="string"||n.type.length===0)throw new Error(`${t} '${n.id}' needs a non-empty string type`);return n}function er(e,t,n){let r=e.find(d=>d.id===t);if(!r||r.type!=="tab"&&r.type!=="subflow")throw new G(t);let o={...r,id:t,type:r.type};delete o.nodes,delete o.configs,delete o.subflows,n.label!==void 0&&(o.label=n.label),n.disabled!==void 0&&(o.disabled=n.disabled),n.info!==void 0&&(o.info=n.info),n.env!==void 0&&(o.env=n.env);let s=[];for(let[d,l]of(n.nodes??[]).entries())s.push({...Rs(l,`flowData.nodes[${d}]`),z:t});for(let[d,l]of(n.configs??[]).entries())s.push({...Rs(l,`flowData.configs[${d}]`),z:t});return[...e.filter(d=>d.id!==t&&d.z!==t),o,...s]}function bn(e,t){if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`Expected ${t} to be an object with rev and flows`);let n=e;if(typeof n.rev!="string"||n.rev.length===0)throw new Error(`Expected ${t}.rev to be a non-empty string`);if(!Array.isArray(n.flows))throw new Error(`Expected ${t}.flows to be an array`);return{rev:n.rev,flows:n.flows}}function Ft(e,t){if(Array.isArray(t)){let d=t.find(u=>u&&typeof u=="object"&&u.id===e&&(u.type==="tab"||u.type==="subflow"));if(!d)throw new G(e);let l=t.filter(u=>u&&typeof u=="object"&&u.id!==e&&u.z===e),c={id:e,type:d.type,nodes:l};return typeof d.label=="string"&&(c.label=d.label),typeof d.disabled=="boolean"&&(c.disabled=d.disabled),typeof d.info=="string"&&(c.info=d.info),c}if(!t||typeof t!="object")throw new G(e);let n=t,r=typeof n.id=="string"&&n.id.length>0?n.id:e,o=typeof n.type=="string"&&n.type.length>0?n.type:"tab",s=Array.isArray(n.nodes)?n.nodes:[],i={...n,id:r,type:o,nodes:s};return Array.isArray(n.configs)&&(i.configs=n.configs),i}var tr=U(()=>{"use strict";ve();a(Rs,"asFlowNode");a(er,"replaceFlowTabInFlatFlows");a(bn,"assertFlowsSnapshot");a(Ft,"normalizeFlowTab")});function co(e){return e?sl.has(e)?!0:e.startsWith("subflow:"):!1}function We(e,t="node.type"){if(e==null||e==="")throw new y(`${t} is required`,"missing_node_type");if(typeof e!="string")throw new y(`${t} must be a non-empty string (got ${typeof e})`,"invalid_node_type",{details:{type:e,got:typeof e}});if(!e.trim())throw new y(`${t} is required`,"missing_node_type");return e}function pl(e,t){if(typeof e!="string"||typeof t!="string")return Number.MAX_SAFE_INTEGER;let n=e.toLowerCase(),r=t.toLowerCase();if(n===r)return 0;let o=n.length,s=r.length;if(o===0)return s;if(s===0)return o;let i=new Array(s+1),d=new Array(s+1);for(let l=0;l<=s;l++)i[l]=l;for(let l=1;l<=o;l++){d[0]=l;let c=n.charAt(l-1);for(let u=1;u<=s;u++){let p=c===r.charAt(u-1)?0:1;d[u]=Math.min((d[u-1]??0)+1,(i[u]??0)+1,(i[u-1]??0)+p)}for(let u=0;u<=s;u++)i[u]=d[u]??0}return i[s]??s}function fl(e,t){let n=e.toLowerCase(),r=t.toLowerCase(),o=Math.min(n.length,r.length),s=0;for(;s<o&&n.charAt(s)===r.charAt(s);)s+=1;return s}function hl(e){return Math.max(2,Math.floor(e.length/3))}function uo(e,t,n=5){if(typeof e!="string"||!e)return[];let r=hl(e);return t.filter(s=>typeof s=="string"&&s!==e&&!ml.has(s)&&!s.startsWith("_")&&!s.startsWith("subflow:")).map(s=>({c:s,dist:pl(e,s),prefix:fl(e,s)})).filter(s=>s.dist<=r).sort((s,i)=>s.dist-i.dist||i.prefix-s.prefix||s.c.localeCompare(i.c)).slice(0,n).map(s=>s.c)}function gl(e){return e.endsWith("/")?e.slice(0,-1):e}function Os(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,Fs.createRequire)(e)}function yl(){try{let t=Os()("@node-red/runtime/lib/settings");return typeof t.available=="function"&&t.available()===!0}catch{return!1}}function $s(e){let t=e.types,n=Array.isArray(t)?t.filter(i=>typeof i=="string"&&i.length>0):[],r="";if(typeof e.module=="string"&&e.module)r=e.module;else if(typeof e.name=="string"&&e.name)r=e.name;else if(typeof e.id=="string"){let i=String(e.id).split("/")[0];r=i&&i.length>0?i:String(e.id)}let o=typeof e.id=="string"&&e.id?e.id:n.length===1&&r?`${r}/${n[0]}`:r;if(!o||n.length===0)return null;let s={id:o,module:r.length>0?r:o,types:n};return typeof e.version=="string"&&(s.version=e.version),typeof e.enabled=="boolean"&&(s.enabled=e.enabled),typeof e.name=="string"&&(s.name=e.name),s}function po(e=ul,t={}){let n=t.module??"node-red",r=t.version??"4.0.0",o=[...e],s=t.configs??{};return{listNodeSets(){return Promise.resolve([{id:n,module:n,version:r,types:o,enabled:!0,name:n}])},getNodeConfigHtml(i){if(s[i])return Promise.resolve(s[i]??null);let d=i.includes("/")?i.slice(i.lastIndexOf("/")+1):i;return s[d]?Promise.resolve(s[d]??null):Promise.resolve(null)}}}function Ms(e={}){if(yl())try{let n=Os()("@node-red/runtime");if(n.nodes&&typeof n.nodes.getNodeList=="function")return new lo(n.nodes)}catch{}if(e.baseUrl)return new ao({baseUrl:e.baseUrl,...e.accessToken?{accessToken:e.accessToken}:{},...e.fetch?{fetch:e.fetch}:{}});throw new Error("No usable NodeTypesAdapter: runtime.nodes unavailable and no HTTP baseUrl")}async function fo(e){let t=await e.listNodeSets(),n=new Map;for(let r of t)for(let o of r.types){if(n.has(o))continue;let s={type:o,module:r.module,setId:r.id};typeof r.enabled=="boolean"&&(s.enabled=r.enabled),r.version&&(s.version=r.version),n.set(o,s)}return{byType:n,allNames:[...n.keys()]}}function wl(e,t){let n=t.toLowerCase();return[e.type,e.module,e.category??"",e.description??""].join(`
|
|
4
|
+
`).toLowerCase().includes(n)}async function rr(e,t={}){let n=t.limit??il,{byType:r}=await fo(e),o=[...r.values()].sort((u,p)=>u.type.localeCompare(p.type)),s=t.q?.trim();s&&(o=o.filter(u=>wl(u,s)));let i=o.length,d=i>n,c={types:o.slice(0,n),total:i,truncated:d};return s&&(c.q=s),c}function bl(e,t){let n=`data-help-name="${t}"`,r=`data-help-name='${t}'`,o=e.toLowerCase(),s=0;for(;s<e.length;){let i=e.indexOf(n,s),d=e.indexOf(r,s),l=-1;if(i>=0&&(d<0||i<d)?l=i:d>=0&&(l=d),l<0)return null;let c=o.lastIndexOf("<script",l);if(c<0||c<s){s=l+1;continue}let u=e.indexOf(">",l);if(u<0)return null;let p=o.indexOf("</script",u+1);if(p<0)return null;let f=p+8;for(;f<e.length&&/\s/.test(e.charAt(f));)f+=1;if(e.charAt(f)!==">"){s=p+1;continue}return e.slice(u+1,p)}return null}function Ls(e,t){let n=`<${t}`,r=`</${t}`,o="",s=0,i=e.toLowerCase();for(;s<e.length;){let d=i.indexOf(n,s);if(d<0){o+=e.slice(s);break}o+=e.slice(s,d);let l=e.indexOf(">",d);if(l<0)break;let c=i.indexOf(r,l+1);if(c<0)break;let u=c+r.length;for(;u<e.length&&/\s/.test(e.charAt(u));)u+=1;e.charAt(u)===">"?(s=u+1,o+=" "):s=c+r.length}return o}function vl(e,t=2e3){let n=Ls(e,"script");return n=Ls(n,"style"),n=n.replace(/<[^>]+>/g," ").replace(/ /gi," ").replace(/[#*_`]+/g," ").replace(/\s+/g," ").trim(),n.length>t&&(n=`${n.slice(0,t-1)}\u2026`),n}function kl(e,t){try{let n=/RED\.nodes\.registerType\s*\(\s*['"]([^'"]+)['"]\s*,\s*\{/.exec(e);if(!n?.[1])return null;let r=n[1];if(t&&r!==t){let o=/RED\.nodes\.registerType\s*\(\s*['"]([^'"]+)['"]\s*,\s*\{/g,s,i=null,d=-1;for(;(s=o.exec(e))!==null;)if(s[1]===t){i=s[1],d=s.index;break}return!i||d<0?null:Es(e,d,i)}return Es(e,n.index,r)}catch{return null}}function Es(e,t,n){let r=e.slice(t),o=r.indexOf("{");if(o<0)return{type:n};let s=vn(r.slice(o));if(!s)return{type:n};let i=Is(s,"category"),d=Is(s,"color"),l=Ns(s,"inputs"),c=Ns(s,"outputs"),u=xl(s),p=[];if(u){for(let g of Object.values(u))if(g&&typeof g=="object"&&!Array.isArray(g)){let A=g.type;typeof A=="string"&&A.length>0&&p.push(A)}}let f,m=bl(e,n);m&&(f=vl(m));let h={type:n};return i&&(h.category=i),d&&(h.color=d),l!==void 0&&(h.inputs=l),c!==void 0&&(h.outputs=c),u&&(h.defaults=u),p.length>0&&(h.requiredConfigNodeTypes=[...new Set(p)]),f&&(h.help=f),h}function Al(e){let t=e.trim();if(!t)return{};if(!/\n(?=#{1,3}\s*(inputs?|outputs?)\b)/i.test(`
|
|
5
|
+
${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(let l of r){let c=l.trim();if(!c)continue;let u=/^(#{1,3})\s*(inputs?|outputs?)\b[\s:]*\n?([\s\S]*)$/i.exec(c);if(u){let p=(u[2]??"").toLowerCase(),f=(u[3]??"").trim();p.startsWith("input")?s=f||c:i=f||c}else o||(o=c.replace(/^#{1,3}\s*/,"").trim())}let d={};return o&&(d.summary=o),s&&(d.inputs=s),i&&(d.outputs=i),!d.summary&&!d.inputs&&!d.outputs&&(d.text=t),d}function vn(e){if(!e.startsWith("{"))return null;let t=0,n=null,r=!1;for(let o=0;o<e.length;o++){let s=e[o];if(n){if(r){r=!1;continue}if(s==="\\"){r=!0;continue}s===n&&(n=null);continue}if(s==='"'||s==="'"||s==="`"){n=s;continue}if(s==="{")t+=1;else if(s==="}"&&(t-=1,t===0))return e.slice(0,o+1)}return null}function Is(e,t){let n=new RegExp(`\\b${t}\\s*:\\s*['"]([^'"]+)['"]`);return e.match(n)?.[1]}function Ns(e,t){let n=new RegExp(`\\b${t}\\s*:\\s*(-?\\d+(?:\\.\\d+)?)`),r=e.match(n);if(!r?.[1])return;let o=Number(r[1]);return Number.isFinite(o)?o:void 0}function xl(e){let t=e.search(/\bdefaults\s*:/);if(t<0)return;let n=e.slice(t),r=n.indexOf("{");if(r<0)return;let o=vn(n.slice(r));if(!o)return;let s={},i=/([a-zA-Z_$][\w$]*)\s*:\s*\{/g,d;for(;(d=i.exec(o))!==null;){let l=d[1];if(!l||l==="defaults")continue;let c=vn(o.slice(d.index+d[0].length-1));if(!c)continue;let u={},p=/\btype\s*:\s*['"]([^'"]+)['"]/.exec(c);p?.[1]&&(u.type=p[1]),/\brequired\s*:\s*true\b/.test(c)&&(u.required=!0);let f=/\bvalue\s*:\s*/.exec(c);if(f){let m=c.slice(f.index+f[0].length).trimStart();u.value=_l(m)}s[l]=u}return Object.keys(s).length>0?s:void 0}function Sl(e,t){let n=e.charAt(t);if(n==="u"&&/^[0-9a-fA-F]{4}/.test(e.slice(t+1,t+5)))return{char:String.fromCharCode(Number.parseInt(e.slice(t+1,t+5),16)),next:t+5};if(n==="x"&&/^[0-9a-fA-F]{2}/.test(e.slice(t+1,t+3)))return{char:String.fromCharCode(Number.parseInt(e.slice(t+1,t+3),16)),next:t+3};let r;switch(n){case"n":r=`
|
|
6
|
+
`;break;case"r":r="\r";break;case"t":r=" ";break;case"b":r="\b";break;case"f":r="\f";break;case"v":r="\v";break;case"0":r="\0";break;case"\\":r="\\";break;case"'":r="'";break;case'"':r='"';break;case"/":r="/";break;default:r=n;break}return{char:r,next:t+1}}function Tl(e){return e==="_DEFAULT_"?"":e}function nr(e){let t=e.trimStart();if(t.startsWith("'")||t.startsWith('"')){let r=t[0],o=1,s="";for(;o<t.length;){let i=t[o];if(i==="\\"&&o+1<t.length){let d=Sl(t,o+1);s+=d.char,o=d.next;continue}if(i===r)break;s+=i,o+=1}return Tl(s)}if(t.startsWith("[")){let r=Ds(t);if(!r)return;let o=r.slice(1,-1).trim();if(!o)return[];let s=[],i=0;for(;i<o.length;){for(;i<o.length&&/[\s,]/.test(o.charAt(i));)i+=1;if(i>=o.length)break;let d=o.slice(i),l=nr(d);s.push(l);let c=io(d);i+=c>0?c:1}return s}if(t.startsWith("{")){let r=vn(t);if(!r)return;let o=r.slice(1,-1);if(!o.trim())return{};let s={},i=0;for(;i<o.length;){for(;i<o.length&&/[\s,]/.test(o.charAt(i));)i+=1;if(i>=o.length)break;let d;if(o[i]==="'"||o[i]==='"'){let c=nr(o.slice(i));d=typeof c=="string"?c:"",i+=io(o.slice(i))}else{let c=/^[a-zA-Z_$][\w$]*/.exec(o.slice(i));if(!c)break;d=c[0],i+=d.length}for(;i<o.length&&/\s/.test(o.charAt(i));)i+=1;if(o[i]!==":")break;for(i+=1;i<o.length&&/\s/.test(o.charAt(i));)i+=1;let l=o.slice(i);s[d]=nr(l),i+=io(l)}return s}let n=/^-?\d+(?:\.\d+)?/.exec(t);if(n)return Number(n[0]);if(t.startsWith("true"))return!0;if(t.startsWith("false"))return!1;if(t.startsWith("null"))return null}function io(e){let t=e.trimStart(),n=e.length-t.length;if(t.startsWith("'")||t.startsWith('"')){let o=t[0],s=1;for(;s<t.length;){if(t[s]==="\\"&&s+1<t.length){s+=2;continue}if(t[s]===o)return n+s+1;s+=1}return n+t.length}if(t.startsWith("[")){let o=Ds(t);return n+(o?.length??1)}if(t.startsWith("{")){let o=vn(t);return n+(o?.length??1)}let r=/^(?:-?\d+(?:\.\d+)?|true|false|null)/.exec(t);return r?n+r[0].length:n+1}function Ds(e){if(!e.startsWith("["))return null;let t=0,n=null,r=!1;for(let o=0;o<e.length;o++){let s=e[o];if(n){if(r){r=!1;continue}if(s==="\\"){r=!0;continue}s===n&&(n=null);continue}if(s==="'"||s==='"'){n=s;continue}if(s==="[")t+=1;else if(s==="]"&&(t-=1,t===0))return e.slice(0,o+1)}return null}function _l(e){return nr(e)}function Pl(e,t){for(let n of e)if(n.types.includes(t)){let r=n.id.includes("/")||n.types.length===1?n.id:`${n.module}/${t}`;return{set:n,setId:r}}return null}function mo(e,t){let n=e.toLowerCase();return t.includes(e)?e:t.find(r=>r.toLowerCase()===n)}async function dt(e,t,n={}){if(!t||typeof t!="string")throw new y("Missing node type","missing_node_type");let r=n.includeHelp!==!1;if(co(t)){let l={type:t,module:"node-red",category:"config",inputs:0,outputs:0,defaults:null};return r&&(l.help={summary:"Built-in structural Node-RED type (not a palette node)."}),l}let o=await e.listNodeSets(),s=o.flatMap(l=>l.types),i=Pl(o,t);if(!i){let l=mo(t,s);throw l&&l!==t?new y(`Node type '${t}' was not found \u2014 did you mean '${l}'? Types are case-sensitive.`,"node_type_not_found",{statusCode:404,details:{type:t,didYouMean:l,caseSensitive:!0,nearest:[l]}}):new y(`Node type '${t}' was not found`,"node_type_not_found",{statusCode:404,details:{type:t,nearest:uo(t,s,5)}})}let d={type:t,module:i.set.module,setId:i.setId,inputs:1,outputs:1,defaults:null};if(i.set.version&&(d.version=i.set.version),typeof i.set.enabled=="boolean"&&(d.enabled=i.set.enabled),typeof e.getNodeConfigHtml=="function")try{let l=await e.getNodeConfigHtml(i.setId)??await e.getNodeConfigHtml(`${i.set.module}/${t}`)??await e.getNodeConfigHtml(t);if(l){let c=kl(l,t);c&&(c.category&&(d.category=c.category),c.color&&(d.color=c.color),d.inputs=c.inputs??d.inputs,d.outputs=c.outputs??d.outputs,c.defaults&&Object.keys(c.defaults).length>0?d.defaults=c.defaults:d.defaults=null,c.requiredConfigNodeTypes&&(d.requiredConfigNodeTypes=c.requiredConfigNodeTypes),r&&c.help&&(d.help=Al(c.help)))}}catch{}return d}async function js(e,t){let{allNames:n}=await fo(e),r=new Set(n),o=new Set,s=a(l=>{let c=We(l,"node.type");if(o.has(c)||co(c)){o.add(c);return}if(o.add(c),r.has(c))return;let u=mo(c,n);throw u&&u!==c?new y(`Unknown node type '${c}' \u2014 did you mean '${u}'? Types are case-sensitive.`,"unknown_node_type",{details:{type:c,didYouMean:u,caseSensitive:!0,nearest:[u]}}):new y(`Unknown node type '${c}'. Call node-types / palette search; ask the user to install via Manage palette, then recheck.`,"unknown_node_type",{details:{type:c,nearest:uo(c,n,5)}})},"checkType"),i=a(l=>{if(!l||typeof l!="object"||Array.isArray(l))return;let c=l;typeof c.id=="string"&&c.id.length>0&&s(c.type)},"visitNodeOrConfig");a(l=>{if(!l||typeof l!="object"||Array.isArray(l))return;let c=l;if(c.type!==void 0&&s(c.type),Array.isArray(c.nodes))for(let u of c.nodes)i(u);if(Array.isArray(c.configs))for(let u of c.configs)i(u)},"visitTab")(t)}async function Hs(e,t){let n=We(t,"node.type");if(co(n))return;let{allNames:r}=await fo(e);if(r.includes(n))return;let o=mo(n,r);throw o&&o!==n?new y(`Unknown node type '${n}' \u2014 did you mean '${o}'? Types are case-sensitive.`,"unknown_node_type",{details:{type:n,didYouMean:o,caseSensitive:!0,nearest:[o]}}):new y(`Unknown node type '${n}'. Call node-types / palette search; ask the user to install via Manage palette, then recheck.`,"unknown_node_type",{details:{type:n,nearest:uo(n,r,5)}})}async function Rl(e,t){let n=new Headers({Accept:"application/json"});t&&n.set("If-None-Match",t);let r=await fetch(e,{headers:n}),o=r.headers.get("etag")??void 0;if(r.status===304)return{status:304,...o?{etag:o}:{}};if(!r.ok)return{status:r.status,body:null,...o?{etag:o}:{}};let s=await r.json();return{status:r.status,body:s,...o?{etag:o}:{}}}async function Cl(e,t){let n=Date.now();if(ke&&n-ke.fetchedAt<cl)return ke.body;let r=ke?.etag,o=await e(t,r);if(o.status===304&&ke)return ke={...ke,fetchedAt:n,...o.etag?{etag:o.etag}:{}},ke.body;if(!o.body||!Array.isArray(o.body.modules)){if(ke)return ke.body;throw new y("Node-RED catalogue unavailable","catalogue_unavailable",{statusCode:502})}return ke={fetchedAt:n,body:o.body,...o.etag?{etag:o.etag}:{}},o.body}async function or(e,t,n={}){let r=t.trim();if(!r)throw new y("q query parameter is required","missing_query");let o=n.limit??al,s=n.catalogueUrl??ll,i=n.catalogueFetcher??Rl,d=await Cl(i,s),l=Array.isArray(d.modules)?d.modules:[],c=await e.listNodeSets(),u=new Set(c.flatMap(g=>{let A=[g.module,g.id];return g.id.includes("/")&&A.push(g.id.split("/")[0]??g.module),A.map(S=>S.toLowerCase())})),p=r.toLowerCase(),f=[];for(let g of l){let A=g.id.toLowerCase(),S=(g.keywords??[]).map(nt=>String(nt).toLowerCase()),E=(g.description??"").toLowerCase(),I=Ll(p,A,S,E);if(I<=0)continue;let N={module:g.id,installed:u.has(A)};typeof g.description=="string"&&(N.description=g.description),typeof g.version=="string"&&(N.version=g.version),typeof g.updated_at=="string"&&(N.updated=g.updated_at),typeof g.downloads=="number"&&(N.downloads=g.downloads),typeof g.url=="string"&&(N.link=g.url),f.push({item:N,score:I})}f.sort((g,A)=>A.score-g.score||(A.item.downloads??0)-(g.item.downloads??0)||g.item.module.localeCompare(A.item.module));let m=f.length,h=m>o;return{q:r,modules:f.slice(0,o).map(g=>g.item),total:m,truncated:h,hint:dl}}function Ll(e,t,n,r){if(!e)return 0;if(t===e||t===`node-red-contrib-${e}`||t===`node-red-node-${e}`)return 1e4;let o=0;t.startsWith(`node-red-contrib-${e}`)||t.startsWith(`node-red-node-${e}`)?o+=500:t.startsWith(e)?o+=80:t.includes(e)&&(o+=60);for(let s of n)s===e?o+=50:s.startsWith(e)||e.startsWith(s)?o+=35:s.includes(e)&&(o+=25);return r.includes(e)&&(o+=10),o}var Fs,sl,il,al,dl,ll,cl,ul,ml,ao,Cs,lo,ke,lt=U(()=>{"use strict";Fs=require("module");K();sl=new Set(["tab","subflow","group","junction"]),il=100,al=30,dl="Recommend only \u2014 ask the user to install via Manage palette, then recheck node-types. This API never installs. Core Node-RED nodes (module node-red) are not listed in the public catalogue; use node-types for installed core types.",ll="https://catalogue.nodered.org/catalogue.json",cl=3600*1e3,ul=["inject","debug","function","template","comment","change","switch","delay","trigger","link in","link out","link call","mqtt in","mqtt out","http in","http response","http request","websocket in","websocket out","tcp in","tcp out","udp in","udp out","catch","status","complete","join","split","sort","batch","csv","html","json","xml","yaml","file","file in","watch","exec","range","rbe"];a(co,"isStructuralNodeType");a(We,"requireNodeTypeName");a(pl,"editDistance");a(fl,"sharedPrefixLength");ml=new Set(["unknown","tab","subflow","group"]);a(hl,"nearestDistanceCutoff");a(uo,"nearestNodeTypes");a(gl,"trimTrailingSlash");a(Os,"createPluginRequire");a(yl,"isRuntimeSettingsAvailable");a($s,"normalizeNodeSet");a(po,"createStaticNodeTypesAdapter");ao=class{static{a(this,"HttpNodeTypesAdapter")}baseUrl;accessToken;fetchImpl;constructor(t){this.baseUrl=gl(t.baseUrl),this.accessToken=t.accessToken,this.fetchImpl=t.fetch??globalThis.fetch.bind(globalThis)}headers(t){let n=new Headers;return n.set("Accept",t),this.accessToken&&n.set("Authorization",`Bearer ${this.accessToken}`),n}async listNodeSets(){let t=await this.fetchImpl(`${this.baseUrl}/nodes`,{headers:this.headers("application/json")});if(!t.ok)throw new Error(`Admin API GET /nodes failed (${t.status})`);let n=await t.json();if(!Array.isArray(n))throw new Error("Admin API GET /nodes returned non-array JSON");let r=[];for(let o of n){if(!o||typeof o!="object"||Array.isArray(o))continue;let s=$s(o);s&&r.push(s)}return r}async getNodeConfigHtml(t,n){let r=t.split("/").map(i=>encodeURIComponent(i)).join("/"),o=n?`?lang=${encodeURIComponent(n)}`:"",s=await this.fetchImpl(`${this.baseUrl}/nodes/${r}${o}`,{headers:this.headers("text/html")});return s.status===404||!s.ok?null:s.text()}},Cs={user:null,req:null},lo=class{constructor(t){this.nodes=t}static{a(this,"RedNodeTypesAdapter")}async listNodeSets(){if(typeof this.nodes.getNodeList!="function")return[];let t,n=this.nodes.getNodeList;try{t=await Promise.resolve(n(Cs))}catch{try{t=await Promise.resolve(n())}catch{return[]}}if(!Array.isArray(t))return[];let r=[];for(let o of t){if(!o||typeof o!="object"||Array.isArray(o))continue;let s=$s(o);s&&r.push(s)}return r}async getNodeConfigHtml(t,n){if(typeof this.nodes.getNodeConfig!="function")return null;try{let r=await Promise.resolve(this.nodes.getNodeConfig({id:t,lang:n??"en-US",...Cs}));if(typeof r=="string"&&r.length>0)return r}catch{}try{let r=await Promise.resolve(this.nodes.getNodeConfig(t,n));return typeof r=="string"&&r.length>0?r:null}catch{return null}}};a(Ms,"createNodeTypesAdapter");a(fo,"flattenInstalledTypes");a(wl,"matchesQuery");a(rr,"listNodeTypes");a(bl,"extractHelpScriptBody");a(Ls,"removeHtmlBlocks");a(vl,"stripHelpText");a(kl,"parseRegisterTypeFromHtml");a(Es,"parseRegisterTypeAt");a(Al,"splitHelpSections");a(vn,"extractBalancedObject");a(Is,"matchStringProp");a(Ns,"matchNumberProp");a(xl,"parseDefaultsBlock");a(Sl,"unescapeJsStringEscape");a(Tl,"normalizeDefaultValue");a(nr,"parseJsLiteral");a(io,"measureJsLiteralLength");a(Ds,"extractBalancedArray");a(_l,"parseLooseValue");a(Pl,"findSetForType");a(mo,"findCaseInsensitiveType");a(dt,"getNodeTypeDetail");a(js,"assertKnownNodeTypes");a(Hs,"assertKnownNodeType");ke=null;a(Rl,"defaultCatalogueFetcher");a(Cl,"loadCatalogue");a(or,"searchPalette");a(Ll,"paletteMatchScore")});function Ge(e){if(e.length===0)return;let t=e[0];if(!t)return;let n=e.map(s=>{let i={...s};return delete i.statusCode,i});if(e.length>1)throw new y(`${e.length} validation errors`,"validation_failed",{statusCode:400,details:{errors:n,errorCount:e.length}});let r={errors:n};for(let[s,i]of Object.entries(t))s==="code"||s==="error"||s==="errors"||s==="statusCode"||i!==void 0&&(r[s]=i);let o=typeof t.statusCode=="number"?t.statusCode:400;throw new y(t.error,t.code,{statusCode:o,details:r})}function ho(e,t="payload.flow"){let n=new Map;for(let o of e)for(let s of o){if(!s||typeof s!="object"||Array.isArray(s))continue;let i=s.id;typeof i=="string"&&i.length>0&&n.set(i,(n.get(i)??0)+1)}let r=[];for(let[o,s]of n)s>1&&r.push({code:"duplicate_node_id",error:`Node id '${o}' appears ${s} times in ${t}; node ids must be unique`,nodeId:o,count:s});return r}function El(e){let t=typeof e.label=="string"&&e.label?` '${e.label}'`:"";return e.type==="tab"?`tab${t||` '${e.id}'`}`:e.type==="subflow"?`subflow${t||` '${e.id}'`}`:typeof e.z=="string"&&e.z?`${e.type} node on '${e.z}'`:`global ${e.type} config node`}function Bs(e,t,n){let r=new Map;for(let i of n)!i||typeof i.id!="string"||i.id===t||i.z===t||r.set(i.id,i);let o=[],s=new Set;for(let i of[e.nodes,e.configs])if(Array.isArray(i))for(let d of i){let l=d&&typeof d=="object"?d.id:void 0;if(typeof l!="string"||!l||s.has(l))continue;if(s.add(l),l===t){o.push({code:"duplicate_node_id",error:`Node id '${l}' equals the tab id; node ids must be unique across all flows`,nodeId:l});continue}let c=r.get(l);c&&o.push({code:"duplicate_node_id",error:`Node id '${l}' is already used by ${El(c)}; node ids must be unique across all flows`,nodeId:l})}return o}function Us(e){let t=new Map;for(let n of[e.nodes,e.configs])if(Array.isArray(n))for(let r of n){if(!r||typeof r!="object"||Array.isArray(r))continue;let o=r;typeof o.id=="string"&&t.set(o.id,o)}return t}function Nl(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]);for(let r of n)if(!Il.has(r)&&JSON.stringify(e[r])!==JSON.stringify(t[r]))return!0;return!1}function Fl(e,t){let n=Us(t);if(!e)return new Set(n.keys());let r=Us(e),o=new Set;for(let[s,i]of n){let d=r.get(s);(!d||Nl(d,i))&&o.add(s)}return o}function qs(e,t){let n=[],r=e;typeof r.id!="string"||r.id.length===0?n.push({code:"missing_flow_id",error:"payload.flow.id is required and must match payload.flowId"}):r.id!==t&&n.push({code:"invalid_flow_id",error:`payload.flow.id ('${r.id}') must match payload.flowId ('${t}')`}),"nodes"in r?Array.isArray(r.nodes)||n.push({code:"invalid_nodes",error:"payload.flow.nodes must be an array"}):n.push({code:"invalid_nodes",error:"payload.flow.nodes must be an array (use [] to clear the tab intentionally)"}),"configs"in r&&r.configs!==void 0&&!Array.isArray(r.configs)&&n.push({code:"invalid_nodes",error:"payload.flow.configs must be an array"});let o=[];Array.isArray(r.nodes)&&o.push({key:"nodes",list:r.nodes}),Array.isArray(r.configs)&&o.push({key:"configs",list:r.configs}),n.push(...ho(o.map(s=>s.list)));for(let{key:s,list:i}of o)for(let d=0;d<i.length;d+=1){let l=i[d];if(!l||typeof l!="object"||Array.isArray(l)){n.push({code:"invalid_node",error:`payload.flow.${s}[${d}] must be an object`});continue}let c=l;(typeof c.id!="string"||c.id.length===0)&&n.push({code:"missing_node_id",error:`payload.flow.${s}[${d}] needs a non-empty string id`});try{We(c.type,"node.type")}catch(u){u instanceof y&&n.push({code:u.code,error:u.message,...typeof c.id=="string"?{nodeId:c.id}:{}})}"wires"in c&&Ot(c.wires,n,typeof c.id=="string"?c.id:void 0)}return n}function Ot(e,t,n){if(!Array.isArray(e))return t.push({code:"invalid_wires",error:"wires must be an array of output ports (each a list of node ids)",...n?{nodeId:n}:{}}),!1;for(let r of e){if(!Array.isArray(r))return t.push({code:"invalid_wires",error:"wires must be an array of output ports (each a list of node ids)",...n?{nodeId:n}:{}}),!1;for(let o of r)if(typeof o!="string"||o.length===0)return t.push({code:"invalid_wires",error:"wires targets must be non-empty node id strings",...n?{nodeId:n}:{}}),!1}return!0}async function Ol(e,t){if(typeof e.outputs=="number"&&Number.isFinite(e.outputs)&&e.outputs>=0)return Math.trunc(e.outputs);let n=typeof e.type=="string"?e.type:"";if(!(!t||!n))try{return(await dt(t,n,{includeHelp:!1})).outputs}catch{return}}function $l(e,t,n){let r=typeof e.type=="string"?e.type:"node";if(!(n.length<=t))return t===0?{code:"invalid_wires",error:`${r} has 0 outputs; wires must be []`,nodeId:e.id,type:r}:{code:"invalid_wires",error:`${r} has ${t} outputs; wires has ${n.length} port arrays (max ${t})`,nodeId:e.id,type:r}}function Ml(e){return Array.isArray(e)&&e.some(t=>typeof t=="string"&&t.length>0)}function Dl(e,t){return t<=0?!1:!("wires"in e)||e.wires===void 0?!0:Array.isArray(e.wires)?!e.wires.some(Ml):!1}async function go(e,t,n={}){let r=[],o=[],s=new Set(e.map(d=>d.id)),i=n.softWarningNodeIds;for(let d of e){let l=await Ol(d,t),c=typeof d.type=="string"?d.type:"node",u=!i||i.has(d.id);if("wires"in d&&d.wires!==void 0){if(!Ot(d.wires,r,d.id))continue;let p=d.wires;if(l!==void 0){let f=$l(d,l,p);f?(f.cause="excess_ports",r.push(f)):u&&p.length<l&&o.push({code:"incomplete_wires",error:`${c} '${d.id}' declares ${l} outputs but wires has only ${p.length} port(s)`,nodeId:d.id,type:c})}for(let f of p)for(let m of f){let h=String(m);s.has(h)||r.push({code:"invalid_wires",error:`Wire target '${h}' on node '${d.id}' was not found in this flow (add the node in the same patch or use an existing id)`,nodeId:d.id,cause:"missing_target",targetId:h})}}u&&l!==void 0&&Dl(d,l)&&o.push({code:"unwired_node",error:`Node '${d.id}' (${c}) has ${l} output(s) but is not wired to anything yet`,nodeId:d.id,type:c})}return{errors:r,warnings:o}}function yo(e,t){let n=[];for(let r of e)r.z===void 0||r.z===""||typeof r.z=="string"&&r.z!==t&&n.push({code:"z_mismatch",error:`Node '${r.id}' has z='${r.z}' but flow id is '${t}'`,nodeId:r.id});return n}function jl(e,t){return e.filter(n=>n.type===t).map(n=>n.id)}async function wo(e,t,n){let r=[],o=[...t];for(let s of e){let i;try{i=We(s.type,"node.type")}catch{continue}let d;try{d=await dt(n,i,{includeHelp:!1})}catch{continue}let l=d.defaults;if(!l||typeof l!="object")continue;let c=new Set(d.requiredConfigNodeTypes??[]);for(let[u,p]of Object.entries(l)){if(!p||typeof p!="object"||Array.isArray(p))continue;let f=p.type;if(typeof f!="string"||!f)continue;let h=p.required===!0||c.has(f),g=s[u],A=jl(o,f);if(typeof g!="string"||g.length===0){h&&r.push({code:"missing_config_node",error:`Node '${s.id}' (${i}) has no ${f} on '${u}' yet \u2014 configure before deploy if needed`,nodeId:s.id,type:i,property:u,configType:f,candidates:A});continue}o.find(E=>E.id===g)?.type!==f&&r.push({code:"missing_config_node",error:`Node '${s.id}' property '${u}' does not resolve to a ${f} config yet`,nodeId:s.id,type:i,property:u,configType:f,candidates:A})}}return r}async function Ws(e,t={}){let n=t.flowId??(typeof e.id=="string"?e.id:""),r=Array.isArray(e.nodes)?e.nodes:[],o=Array.isArray(e.configs)?e.configs:[],s=[],i=[];n&&s.push(...yo(r,n));let d=t.baseFlow?Fl(t.baseFlow,e):void 0,l=await go(r,t.nodeTypes,{...d?{softWarningNodeIds:d}:{}});if(s.push(...l.errors),i.push(...l.warnings),t.nodeTypes){let c=await wo(r,o,t.nodeTypes);d?i.push(...c.filter(u=>!u.nodeId||d.has(u.nodeId))):i.push(...c)}return{errors:s,warnings:i}}var Il,bo=U(()=>{"use strict";K();lt();a(Ge,"throwValidationIssues");a(ho,"collectDuplicateNodeIdIssues");a(El,"describeLiveNode");a(Bs,"collectCrossFlowDuplicateIssues");Il=new Set(["x","y","w","h","g","id","z"]);a(Us,"collectNodesMap");a(Nl,"nodeContentChanged");a(Fl,"changedNodeIds");a(qs,"collectFullFlowShapeIssues");a(Ot,"assertValidWiresShape");a(Ol,"resolveNodeOutputCount");a($l,"wireCountIssue");a(Ml,"portHasTargets");a(Dl,"nodeIsUnwired");a(go,"collectWireValidation");a(yo,"collectZMismatchIssues");a(jl,"configCandidates");a(wo,"collectConfigWarnings");a(Ws,"validateFlowTab")});function ze(e,t="node"){return typeof e=="string"&&e.trim()?e.trim():t}function sr(e){let t=new Map;for(let n of e)n.type==="tab"&&typeof n.id=="string"&&typeof n.label=="string"&&n.label.trim()&&t.set(n.id,n.label.trim());return t}function zs(e,t){e[Gs]=t}function Bl(e){if(!e)return;let t=e[Gs];return t instanceof Map?t:void 0}function ql(e){return typeof e!="string"||!e.trim()?new Set:new Set(e.split(",").map(t=>t.trim()).filter(Boolean))}function Wl(e){if(e===!0||e===1)return!0;if(typeof e=="string"){let t=e.trim().toLowerCase();return t==="1"||t==="true"||t==="yes"}return!1}function oe(e){let t=ql(e.include);return Wl(e.include_payload)&&(t.add("payload"),t.add("baseFlow")),t}function q(e,t){let n={req:e};t!==void 0&&(n.httpAdminRoot=t);let r=Bl(e);return r&&(n.liveFlowLabels=r),n}function $t(e){return typeof e.name=="string"&&e.name.trim()?e.name.trim():typeof e.label=="string"&&e.label.trim()?e.label.trim():""}function kn(e){return`"${e.replace(/"/g,"'")}"`}function Mt(e){let t=new Map;for(let n of e.nodes??[])t.set(n.id,n);for(let n of e.configs??[])t.set(n.id,n);return t}function Gl(e,t){let n=new Set(["x","y","w","h","g","id","z"]),r=new Set([...Object.keys(e),...Object.keys(t)]);for(let o of r)if(!n.has(o)&&JSON.stringify(e[o])!==JSON.stringify(t[o]))return!0;return!1}function zl(e,t){let n=$t(e),r=$t(t);if(!n||!r||n===r)return!1;let o=new Set(["x","y","w","h","g","id","z","name","label"]),s=new Set([...Object.keys(e),...Object.keys(t)]);for(let i of s)if(!o.has(i)&&JSON.stringify(e[i])!==JSON.stringify(t[i]))return!1;return!0}function vo(e,t){let n=[],r=[],o=[];for(let[s,i]of t)e.has(s)||n.push(i);for(let[s,i]of e)t.has(s)||r.push(i);for(let[s,i]of t){let d=e.get(s);d&&Gl(d,i)&&o.push({before:d,after:i})}return{added:n,removed:r,modified:o}}function ir(e){return e===1?"node":"nodes"}function Jl(e){return e.length===1&&e[0]?`adds 1 ${ze(e[0].type)}`:`adds ${e.length} ${ir(e.length)}`}function Kl(e){return`deletes ${e} ${ir(e)}`}function Vl(e){return`edits ${e} ${ir(e)}`}function Xl(e){let t=[];e.removed>0&&t.push(Kl(e.removed));let n=e.added??[],r=e.addedCount??n.length;r>0&&t.push(n.length===r?Jl(n):`adds ${r} ${ir(r)}`),(e.modified??0)>0&&t.push(Vl(e.modified??0));let o=t.length?t.join(", "):"replaces nodes",s=e.flowLabel?.trim();return s?`Replace ${s}: ${o}`:`Replace flow: ${o}`}function Yl(e){let{added:t,removed:n,modified:r}=e;if(t.length+n.length+r.length!==1)return;if(t[0]){let u=t[0],p=ze(u.type),f=$t(u);return f?`Add ${p} node ${kn(f)}`:`Add ${p} node`}if(n[0]){let u=n[0],p=ze(u.type),f=$t(u);return f?`Remove ${p} node ${kn(f)}`:`Remove ${p} node`}let s=r[0];if(!s)return;let i=ze(s.after.type,ze(s.before.type)),d=$t(s.before),l=$t(s.after);if(zl(s.before,s.after)&&d&&l)return`Rename ${i} node ${kn(d)} \u2192 ${kn(l)}`;let c=l||d;return c?`Edit ${i} ${kn(c)}`:`Edit ${i}`}function ko(e,t){return typeof e?.label=="string"&&e.label.trim()?e.label.trim():typeof t?.label=="string"&&t.label.trim()?t.label.trim():""}function ar(e,t){if(typeof e.flowLabel=="string"&&e.flowLabel.trim())return e.flowLabel.trim();let n=ko(e.payload?.flow,e.baseFlow);if(n)return n;let r=e.payload?.flowId;return r&&t?.has(r)?t.get(r)??"":""}function Js(e,t){let n=t.trim(),r=e.trim();if(!n||!r||r.includes(n))return r;let o=r.lastIndexOf(" in ");if(o>=0){let s=r.slice(o+4);if(s.length>0&&n.startsWith(s))return r}return`${r} in ${n}`}function Ao(e,t){let n=vo(Mt(e??{}),Mt(t)),r=n.added.length+n.removed.length+n.modified.length,o=ko(t,e);if(r===0)return o?`Update ${o}`:"Update flow";if(n.removed.length>0)return Xl({...o?{flowLabel:o}:{},removed:n.removed.length,added:n.added,modified:n.modified.length});if(r>Ul)return o?`${r} changes in ${o}`:`${r} changes`;let s=Yl(n)??(o?`Update ${o}`:"Update flow");return Js(s,o)}function Ql(e,t){let n=(e??"").trim();return!!(!n||n.includes("[object Object]")||/^Update(\s|$)/i.test(n)||t>0&&!/^Replace\b/i.test(n))}function xo(e,t){return`${e} \xB7 ${ec(t)}`}function Zl(e){let t=e>=0?"+":"-",n=Math.abs(e),r=String(Math.floor(n/60)).padStart(2,"0"),o=String(n%60).padStart(2,"0");return`${t}${r}:${o}`}function ec(e,t=new Date(e)){let n=Number.isNaN(t.getTime())?new Date(e):t;if(Number.isNaN(n.getTime()))return e.slice(0,16);let r=n.getDate(),s=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][n.getMonth()]??"Jan",i=String(n.getHours()).padStart(2,"0"),d=String(n.getMinutes()).padStart(2,"0"),l=Zl(-n.getTimezoneOffset());return`${r} ${s} ${i}:${d} ${l}`}function dr(e){if(typeof e!="string")return;let t=e.replace(/\0/g,"").replace(/[\u0001-\u001F\u007F]/g," ").replace(/\s+/g," ").trim().slice(0,Hl).trim();return t.length>0?t:void 0}function tc(e,t){let n=ne(t);return`${n==="/"?"/mcp":`${n}/mcp`}#proposal=${encodeURIComponent(e)}`}function So(e,t={}){let n=ar(e,t.liveFlowLabels),r=dr(e.title),o=Ao(e.baseFlow,n&&!ko(e.payload.flow,e.baseFlow)?{...e.payload.flow,label:n}:e.payload.flow),s=vo(Mt(e.baseFlow??{}),Mt(e.payload.flow)),i=Ql(r,s.removed.length)?o:r??o;return Js(i,n)}function nc(e){return typeof e.storedTitle=="string"&&e.storedTitle?e.storedTitle:dr(e.title)}function se(e,t={}){let n=So(e,t),r=ar(e,t.liveFlowLabels),o=nc(e)??n,s=tc(e.id,t.httpAdminRoot),i=t.req?yn(t.req,s):s;return{...e,title:n,storedTitle:o,flowLabel:r,displayName:xo(n,e.createdAt),reviewUrl:i,reviewPath:s}}function rc(e){let t=vo(Mt(e.baseFlow??{}),Mt(e.payload.flow)),n=[];return t.added.length>0&&n.push(t.added.length===1?"Adds 1 node":`Adds ${t.added.length} nodes`),t.removed.length>0&&n.push(t.removed.length===1?"Deletes 1 node":`Deletes ${t.removed.length} nodes`),t.modified.length>0&&n.push(t.modified.length===1?"Edits 1 node":`Edits ${t.modified.length} nodes`),n.length===0?"No node changes":n.map((r,o)=>o===0?r:r.charAt(0).toLowerCase()+r.slice(1)).join(", ")}function oc(e){if(e.kind==="oauth")return{kind:"oauth",clientId:e.clientId,user:e.user};let t={kind:e.kind,linkId:e.linkId};return typeof e.label=="string"&&e.label.length>0&&(t.label=e.label),t}function ce(e,t={}){let n=se(e,t),r=t.include??new Set,o={id:n.id,status:n.status,type:n.type,title:n.title,storedTitle:n.storedTitle,displayName:n.displayName,flowLabel:n.flowLabel,reviewUrl:n.reviewUrl,reviewPath:n.reviewPath,createdAt:n.createdAt,updatedAt:n.updatedAt,expectedRev:n.expectedRev,createdBy:oc(n.createdBy),changeSummary:rc(e)};return r.has("payload")&&(o.payload=n.payload),r.has("baseFlow")&&n.baseFlow!==void 0&&(o.baseFlow=n.baseFlow),n.warnings&&n.warnings.length>0&&(o.warnings=n.warnings),n.baseRevStale!==void 0&&(o.baseRevStale=n.baseRevStale),n.decidedBy!==void 0&&(o.decidedBy=n.decidedBy),n.reason!==void 0&&(o.reason=n.reason),n.resultRev!==void 0&&(o.resultRev=n.resultRev),o}function Ks(e,t={}){return e.map(n=>ce(n,t))}var Hl,Ul,Gs,Ae=U(()=>{"use strict";ae();Pe();Hl=80,Ul=1;a(ze,"nodeTypeText");Gs=Symbol.for("ai-access.liveFlowLabels");a(sr,"liveFlowLabelsFromFlows");a(zs,"setRequestFlowLabels");a(Bl,"getRequestFlowLabels");a(ql,"parseIncludeQuery");a(Wl,"isTruthyFlag");a(oe,"resolveIncludeFromQuery");a(q,"presentOptionsFor");a($t,"nodeLabel");a(kn,"quoteName");a(Mt,"collectNodes");a(Gl,"nodeContentChanged");a(zl,"isRenameOnly");a(vo,"classifyFlowChanges");a(ir,"pluralNodes");a(Jl,"describeAddedClause");a(Kl,"describeRemovedClause");a(Vl,"describeModifiedClause");a(Xl,"buildReplaceFlowTitle");a(Yl,"describeOneChange");a(ko,"flowLabelHint");a(ar,"resolveFlowLabel");a(Js,"ensureTitleIncludesFlowName");a(Ao,"autoGenerateProposalTitle");a(Ql,"shouldRefreshDisplayTitle");a(xo,"buildProposalDisplayName");a(Zl,"formatUtcOffset");a(ec,"formatProposalSubmittedAt");a(dr,"sanitizeProposalTitle");a(tc,"buildProposalReviewPath");a(So,"resolveProposalTitle");a(nc,"storedTitleOf");a(se,"presentProposal");a(rc,"buildChangeSummary");a(oc,"presentCreatedByForApi");a(ce,"presentProposalForApi");a(Ks,"presentProposalsForApi")});function Vs(e){let t=new Map;for(let n of e.nodes??[])t.set(n.id,n);for(let n of e.configs??[])t.set(n.id,n);return t}function ic(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]),r=[];for(let o of n)sc.has(o)||JSON.stringify(e[o])!==JSON.stringify(t[o])&&r.push(o);return r.sort()}function Xs(e){let t=Vs(e.baseFlow??{}),n=Vs(e.payload.flow),r=new Map;for(let[o,s]of n){let i=t.get(o);if(!i){r.set(o,new Set(["*"]));continue}let d=ic(i,s);d.length>0&&r.set(o,new Set(d))}for(let[o]of t)n.has(o)||r.set(o,new Set(["*"]));return r}function ac(e,t){let n=[],r=[];for(let[o,s]of e){let i=t.get(o);if(!i)continue;n.push(o);let d=[];for(let c of s){if(c==="*"){d.push(...[...i].filter(u=>u!=="*")),i.has("*")&&d.push("*");continue}(i.has(c)||i.has("*"))&&d.push(c)}let l=[...new Set(d)].sort();l.length>0&&r.push({nodeId:o,fields:l})}return n.sort(),{nodeIds:n,fields:r}}function V(e,t,n={}){let r=0;for(let o of e.list())if(o.status==="pending"&&!(n.flowId&&o.payload.flowId!==n.flowId)&&o.expectedRev!==t)try{e.markClosed(o.id,"expired",dc),r+=1}catch{}return r}function Ys(e,t,n,r){let o=`Superseded by approved proposal ${n} (resultRev ${r}). Re-read the flow and resubmit if still needed.`,s=0;for(let i of e.list())if(!(i.id===n||i.status!=="pending"||i.payload.flowId!==t))try{e.markClosed(i.id,"superseded",o),s+=1}catch{}return s}function Ee(e,t,n){let r=Xs(t),o=[];for(let s of e.list()){if(s.id===t.id||s.status!=="pending"||s.payload.flowId!==t.payload.flowId)continue;let i=So(s),d=n?at(s.createdBy)===n:at(s.createdBy)===at(t.createdBy),l=ac(r,Xs(s)),c=s.expectedRev===t.expectedRev,u=l.nodeIds.length>0;if(!c&&!u)continue;let p={id:s.id,title:i,displayName:xo(i,s.createdAt),mine:d};l.nodeIds.length>0&&(p.overlappingNodeIds=l.nodeIds),l.fields.length>0&&(p.overlappingFields=l.fields),o.push(p)}return{conflictingProposals:o}}function Ie(e){let t={};return e.conflictingProposals.length>0&&(t.conflictingProposals=e.conflictingProposals,t.warning="Other pending proposals touch the same flow \u2014 approve order may conflict (like a git merge)."),t}function Q(e){return e>0?{autoClosedCount:e}:{}}var sc,dc,Je=U(()=>{"use strict";qe();Ae();sc=new Set(["x","y","w","h","g","id","z"]);a(Vs,"collectNodes");a(ic,"changedFieldKeys");a(Xs,"computeProposalTouches");a(ac,"overlapBetween");dc="Proposal expired: the live flow revision moved past expectedRev. Re-read the flow and resubmit.";a(V,"expireStalePendingProposals");a(Ys,"supersedePendingOnFlow");a(Ee,"findConflictingProposals");a(Ie,"conflictWarningFields");a(Q,"autoClosedFields")});var si={};ss(si,{applyFlowNodeOps:()=>ct,applyReplaceTextToValue:()=>oi,buildPatchedFlowTab:()=>An,collectSetProps:()=>To,decodeBase64UrlJson:()=>xn,findTextOccurrences:()=>ri,interpretPatch:()=>Sn});function ni(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function lc(e){if(typeof e!="object"||e===null||Array.isArray(e))throw new y(`add op needs node as an object (got ${ni(e)})`,"invalid_node")}async function An(e,t,n){return ct(e,t,n.map(r=>({op:"set",nodeId:r.nodeId,set:r.set})))}function ri(e,t){if(!t)return[];let n=[],r=0;for(;r<=e.length;){let o=e.indexOf(t,r);if(o<0)break;n.push(o),r=o+t.length}return n}function oi(e,t,n,r,o){if(ti.has(t))throw new y(`Cannot change protected node property '${t}' via replace_text`,"protected_property");if(typeof n!="string"||n.length===0)throw new y("find must be a non-empty string","missing_find");if(typeof r!="string")throw new y("replace must be a string","invalid_replace");if(e===void 0)throw new y(`Field '${t}' was not found on the node`,"field_not_found");if(typeof e!="string")throw new y(`Field '${t}' is not a string (got ${e===null?"null":typeof e})`,"field_not_string");let s=ri(e,n);if(s.length===0)throw new y(`Text not found in field '${t}'`,"text_not_found",{details:{matchCount:0}});let i=o;if(i===void 0){if(s.length>1)throw new y(`Found ${s.length} matches in '${t}'; pass occurrence (1-based) to pick one`,"ambiguous_match",{statusCode:409,details:{matchCount:s.length}});i=1}if(!Number.isInteger(i)||i<1||i>s.length)throw new y(`occurrence must be an integer from 1 to ${s.length}`,"invalid_occurrence",{details:{matchCount:s.length}});let d=s[i-1];if(d===void 0)throw new y(`occurrence must be an integer from 1 to ${s.length}`,"invalid_occurrence",{details:{matchCount:s.length}});return e.slice(0,d)+r+e.slice(d+n.length)}function cc(e){let t=[];for(let n of e)if(n.op==="add"){let r=n.node;if(typeof r!="object"||r===null||Array.isArray(r)){t.push({code:"invalid_node",error:`add op needs node as an object (got ${ni(r)})`});continue}let o=r;(!o.id||typeof o.id!="string")&&t.push({code:"missing_node_id",error:"add op needs node.id"});try{We(o.type,"node.type")}catch(s){s instanceof y&&t.push({code:s.code,error:s.message,nodeId:o.id})}"wires"in o&&Ot(o.wires,t,typeof o.id=="string"?o.id:void 0)}else n.op==="set"&&typeof n.set=="object"&&"wires"in n.set&&Ot(n.set.wires,t,n.nodeId);return t}function uc(e){let t=new Set;for(let n of e)n.op==="add"&&typeof n.node.id=="string"?t.add(n.node.id):n.op!=="add"&&typeof n.nodeId=="string"&&t.add(n.nodeId);return t}async function ct(e,t,n,r={}){if(!t)throw new y("flow id is required","missing_flow_id");if(!n.length)throw new y("At least one node edit is required","missing_edits");let o=cc(n);o.push(...ho([n.flatMap(h=>h.op==="add"?[h.node]:[])],"the batch (add ops)")),Ge(o);let s=await e.getFlow(t);if(r.expectedRev!==void 0&&s.rev!==r.expectedRev){let h=Array.isArray(s.flow.nodes)?[...s.flow.nodes]:[];return{expectedRev:s.rev,flow:{...s.flow,id:s.flow.id,type:s.flow.type??"tab",nodes:h}}}let i=Array.isArray(s.flow.nodes)?[...s.flow.nodes]:[],d=Array.isArray(s.flow.configs)?[...s.flow.configs]:[],l={...s.flow,id:s.flow.id,type:s.flow.type??"tab",nodes:i,configs:d},c=i.map(h=>({...h})),u=[];for(let h of n)try{h.op==="add"&&r.nodeTypes&&await Hs(r.nodeTypes,h.node.type),Zs(c,t,h)}catch(g){if(g instanceof y)u.push({code:g.code,error:g.message,...g.statusCode!==400?{statusCode:g.statusCode}:{},...g.details??{}});else throw g}u.push(...yo(c,t));let p=uc(n),f=await go(c,r.nodeTypes,{softWarningNodeIds:p});u.push(...f.errors),Ge(u);for(let h of n)Zs(i,t,h);let m=[...f.warnings];if(r.nodeTypes){let h=await wo(i,d,r.nodeTypes);m.push(...h.filter(g=>!g.nodeId||p.has(g.nodeId)))}return{expectedRev:s.rev,flow:l,...m.length>0?{warnings:m}:{}}}function Qs(e,t,n){if(!t||typeof t!="string")throw new y("Each edit needs a node id","missing_node_id");let r=e.findIndex(o=>o.id===t);if(r<0||!e[r])throw new y(`Node '${t}' was not found in flow '${n}'`,"node_not_found",{statusCode:404});return r}function Zs(e,t,n){if(n.op==="add"){let i=n.node;if(!i.id||typeof i.id!="string")throw new y("add op needs node.id","missing_node_id");if(We(i.type,"node.type"),e.some(d=>d.id===i.id))throw new y(`Node '${i.id}' already exists in flow '${t}'`,"node_already_exists");e.push({...i,id:i.id,type:i.type,z:typeof i.z=="string"&&i.z?i.z:t});return}if(n.op==="remove"){let i=Qs(e,n.nodeId,t);e.splice(i,1);return}let r=Qs(e,n.nodeId,t),o=e[r];if(!o)throw new y(`Node '${n.nodeId}' was not found in flow '${t}'`,"node_not_found",{statusCode:404});if(n.op==="replace_text"){if(!(n.field in o))throw new y(`Field '${n.field}' was not found on the node`,"field_not_found");let i=oi(o[n.field],n.field,n.find,n.replace,n.occurrence);e[r]={...o,id:o.id,type:o.type,[n.field]:i};return}let s={...o,id:o.id,type:o.type};for(let[i,d]of Object.entries(n.set)){if(ti.has(i))throw new y(`Cannot change protected node property '${i}' via GET propose`,"protected_property");if(i==="wires"){let l=[];Ot(d,l,n.nodeId),Ge(l)}s[i]=d}e[r]=s}function ei(e){if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean"||typeof e=="bigint")return String(e)}function To(e){let t={},n=e.set;if(n&&typeof n=="object"&&!Array.isArray(n))for(let[r,o]of Object.entries(n)){let s=ei(o);s!==void 0&&(t[r]=s)}for(let[r,o]of Object.entries(e)){if(!r.startsWith("set.")||r.length<=4)continue;let s=r.slice(4);if(typeof o=="string")t[s]=o;else if(Array.isArray(o)&&typeof o[0]=="string")t[s]=o[0];else{let i=ei(o);i!==void 0&&(t[s]=i)}}return t}function xn(e){let t;try{t=Buffer.from(e,"base64url").toString("utf8")}catch{throw new y("patch must be base64url-encoded JSON","invalid_patch")}try{return JSON.parse(t)}catch{throw new y("patch JSON is malformed","invalid_patch_json")}}function pc(e,t){if(typeof e.flowId=="string"&&e.flowId)return e.flowId;let n=e.flow;if(n&&typeof n=="object"&&!Array.isArray(n)){let r=n.id;if(typeof r=="string"&&r)return r}return t}function _o(e){return typeof e.id=="string"&&e.id?e.id:typeof e.nodeId=="string"&&e.nodeId?e.nodeId:typeof e.node=="string"&&e.node?e.node:""}function fc(e){if(e==null||e==="")return;let t;if(typeof e=="number")t=e;else if(typeof e=="string")t=Number(e);else if(typeof e=="boolean"||typeof e=="bigint")t=Number(e);else throw new y("occurrence must be a positive integer","invalid_occurrence");if(!Number.isFinite(t))throw new y("occurrence must be a positive integer","invalid_occurrence");return Math.trunc(t)}function mc(e){let t=e.node;if(t!==void 0)return lc(t),{op:"add",node:t};let n={...e};return delete n.op,{op:"add",node:n}}function hc(e){let t=_o(e);if(!t)throw new y("replace_text op needs node id","missing_node_id");let n=typeof e.field=="string"?e.field:"";if(!n)throw new y("replace_text op needs field","missing_field");if(typeof e.find!="string")throw new y("replace_text op needs find string","missing_find");if(typeof e.replace!="string")throw new y("replace_text op needs replace string","invalid_replace");let r=fc(e.occurrence),o={op:"replace_text",nodeId:t,field:n,find:e.find,replace:e.replace};return r!==void 0&&(o.occurrence=r),o}function gc(e){let t=_o(e);if(!t)throw new y("Each patch.nodes entry needs id","missing_node_id");let n=e.set,r={};if(n&&typeof n=="object"&&!Array.isArray(n))Object.assign(r,n);else for(let[o,s]of Object.entries(e))o==="id"||o==="nodeId"||o==="node"||o==="set"||o==="op"||(r[o]=s);return{op:"set",nodeId:t,set:r}}function yc(e){let t=typeof e.op=="string"?e.op.toLowerCase():"set";if(t==="add")return mc(e);if(t==="remove"){let n=_o(e);if(!n)throw new y("remove op needs id","missing_node_id");return{op:"remove",nodeId:n}}if(t==="replace_text")return hc(e);if(t!=="set")throw new y("nodes[].op must be 'add', 'remove', 'set', or 'replace_text'","invalid_patch");return gc(e)}function Sn(e,t){if(!e||typeof e!="object"||Array.isArray(e))throw new y("patch must be a JSON object","invalid_patch");let n=e;if(n.type==="replace_flow"||n.flow&&typeof n.flow=="object"){let r=pc(n,t);if(!r)throw new y("patch.replace_flow needs flowId","missing_flow_id");return{kind:"replace_flow",flowId:r,flow:n.flow}}if(Array.isArray(n.nodes)){let r=typeof n.flowId=="string"?n.flowId:t;if(!r)throw new y("patch.nodes needs flow id","missing_flow_id");let o=[];for(let i of n.nodes){if(!i||typeof i!="object"||Array.isArray(i))throw new y("Each patch.nodes entry must be an object","invalid_patch");o.push(yc(i))}let s=o.filter(i=>i.op==="set").map(i=>({nodeId:i.nodeId,set:i.set}));return{kind:"nodes",flowId:r,ops:o,edits:s}}throw new y("patch must include nodes[] edits or type/flow for replace_flow","invalid_patch")}var ti,lr=U(()=>{"use strict";K();bo();lt();ti=new Set(["id","type","z"]);a(ni,"describeNonObjectNode");a(lc,"assertAddNodeObject");a(An,"buildPatchedFlowTab");a(ri,"findTextOccurrences");a(oi,"applyReplaceTextToValue");a(cc,"collectOpShapeIssues");a(uc,"touchedNodeIdsFromOps");a(ct,"applyFlowNodeOps");a(Qs,"requireNodeIndex");a(Zs,"applyOneNodeOp");a(ei,"stringifyQueryValue");a(To,"collectSetProps");a(xn,"decodeBase64UrlJson");a(pc,"resolveReplaceFlowId");a(_o,"resolveNodeId");a(fc,"parseOccurrence");a(mc,"parseAddOp");a(hc,"parseReplaceTextOp");a(gc,"parseSetOp");a(yc,"parseNodeOp");a(Sn,"interpretPatch")});function Dt(e,t){return at(e.createdBy)===t.id}function Po(e,t){return!t||t.length===0||t.includes(e)}function wc(e){return typeof e=="string"&&ai.has(e)}function Ht(e,t){let n=bc[e],r=e!=="status"&&Array.isArray(t);return new y(r?`Invalid ${e} filter: pass it at most once (multiple values are not supported). Allowed: ${n}`:`Invalid ${e} filter. Allowed: ${n}`,"invalid_filter",{details:{parameter:e,allowed:n,hint:`Pass ${e} as ${n}, or omit it.`}})}function cr(e,t){if(Array.isArray(t))throw Ht(e,t);return t}function vc(e){return typeof e=="number"?Number.isInteger(e)?e:null:typeof e=="string"&&/^-?\d+$/.test(e.trim())?Number(e.trim()):null}function Ro(e){return e==null||e===""}function kc(e){if(!Ro(e)){if(e==="true"||e==="1"||e===!0)return!0;if(e==="false"||e==="0"||e===!1)return!1;throw Ht("mine",e)}}function Co(e){if(Ro(e))return;let t=(Array.isArray(e)?e:[e]).flatMap(r=>typeof r=="string"?r.split(","):[r]),n=[];for(let r of t){let o=typeof r=="string"?r.trim():r;if(o!==""){if(!wc(o))throw Ht("status",e);n.includes(o)||n.push(o)}}return n.length>0?n:void 0}function ii(e,t,n,r){if(Ro(t))return;let o=vc(t);if(o===null||o<n||o>r)throw Ht(e,t);return o}function ur(e){let t={},n=kc(cr("mine",e.mine));n!==void 0&&(t.mine=n);let r=Co(e.status);r&&(t.status=r);let o=cr("flowId",e.flowId??e.flow_id??e.flow);typeof o=="string"&&o.length>0&&(t.flowId=o);let s=ii("limit",cr("limit",e.limit),1,jt);s!==void 0&&(t.limit=s);let i=ii("offset",cr("offset",e.offset),0,Number.MAX_SAFE_INTEGER);return i!==void 0&&(t.offset=i),t}function ut(e){let t=e.offset+e.returned,n=t<e.total;return{total:e.total,offset:e.offset,...e.limit!==null?{limit:e.limit}:{},hasMore:n,...n?{nextOffset:t}:{}}}function Ac(e,t){let n=e.length,r=t.offset!==void 0&&t.offset>0?t.offset:0;if(t.limit===void 0)return{items:r>0?e.slice(r):e,total:n,limit:null,offset:r};let o=Math.min(Math.max(1,t.limit),jt);return{items:e.slice(r,r+o),total:n,limit:o,offset:r}}function Ut(e,t,n={},r={}){let o=r.mine===!0?t.id:void 0,s={};o&&(s.linkId=o),r.flowId&&(s.flowId=r.flowId);let d=e.list(s).filter(c=>r.mine!==!1||!Dt(c,t)).filter(c=>Po(c.status,r.status)).map(c=>({...se(c,n),mine:Dt(c,t)})),l=Ac(d,r);return{proposals:l.items,total:l.total,limit:l.limit,offset:l.offset}}function $e(e,t,n,r={}){if(!n)throw new y("Missing proposal id","missing_proposal_id");let o=e.get(n);return{...se(o,r),mine:Dt(o,t)}}function xc(e){let t={kind:"access-link",linkId:e.id};return typeof e.label=="string"&&e.label&&(t.label=e.label),t}function Bt(e,t,n,r,o,s={}){if(!r)throw new y("Missing proposal id","missing_proposal_id");let i;try{i=e.get(r)}catch(f){throw f instanceof re,f}if(!Dt(i,n))throw new Nt(r);if(i.status!=="pending")throw new te(r,i.status);let d=typeof o=="string"&&o.trim()?o.replace(/\0/g,"").trim().slice(0,2e3):"Withdrawn by access link",l=xc(n),c=e.markWithdrawn(r,l,d),u=se(c,s),p=u.flowLabel||void 0;return t.append("proposal.withdrawn",l,{proposalId:c.id,flowId:c.payload.flowId,title:u.title,displayName:u.displayName,reason:d,...p?{flowLabel:p}:{}}),{...u,mine:!0}}function Sc(e,t,n,r={}){let o;try{o=e.get(t.id)}catch{return{}}if(r.liveRev){V(e,r.liveRev,{flowId:o.payload.flowId});try{o=e.get(t.id)}catch{return{}}}return o.status!=="pending"?{}:Ie(Ee(e,o,n))}function ue(e,t,n,r={},o={}){let s="mine"in n?n:{...se(n,r),mine:Dt(n,t)},i=0;if(o.liveRev)try{let c=e.get(s.id).payload.flowId;i=V(e,o.liveRev,{flowId:c});let u=e.get(s.id);s={...se(u,r),mine:Dt(u,t)}}catch{}let d=ce(s,r),l={...d,mine:s.mine,...Q(i)};return o.includeConflicts!==!1&&d.status==="pending"&&Object.assign(l,Sc(e,d,t.id)),l}async function pe(e){let t=await e.getFlows();return{rev:t.rev,flows:t.flows}}async function xe(e,t){if(!t)throw new y("Missing flow id","missing_flow_id");let n=await e.getFlow(t);return{rev:n.rev,flow:Ft(t,n.flow)}}function Tc(e){if(typeof e.expectedRev=="string"&&e.expectedRev.length>0)return e.expectedRev;if(typeof e.expected_rev=="string"&&e.expected_rev.length>0)return e.expected_rev}function di(e,t){if("nodes"in e&&e.nodes!==void 0&&!Array.isArray(e.nodes))throw new y("payload.nodes must be an array of node ops","invalid_nodes");if(t!==void 0&&!Array.isArray(t))throw new y("nodes must be an array of node ops","invalid_nodes")}async function _c(e,t,n,r,o){let{applyFlowNodeOps:s,interpretPatch:i}=await Promise.resolve().then(()=>(lr(),si));di(n,r);let d=Array.isArray(n.nodes)?n.nodes:Array.isArray(r)?r:null;if(!d)throw new y("payload.flow is required","missing_flow");let l=n.flowId,c=typeof l=="string"?l:"";if(!c)throw new y("payload.flowId is required","missing_flow_id");let u=i({nodes:d,flowId:c},c);if(u.kind!=="nodes")throw new y("payload.nodes must be node ops","invalid_patch");let p=await s(e,u.flowId,u.ops,{expectedRev:t,...o?{nodeTypes:o}:{}});if(p.expectedRev!==t)throw new J(t,p.expectedRev,"submit_proposal");return{flowId:u.flowId,flow:p.flow}}async function Pc(e,t,n,r,o){let s=n.flowId,i=n.flow,d=!i||typeof i!="object"||Array.isArray(i);if(d&&di(n,r),d&&(Array.isArray(n.nodes)||Array.isArray(r))){let l=await _c(e,t,n,r,o);s=l.flowId,i=l.flow}if(typeof s!="string"||s.length===0)throw new y("payload.flowId is required","missing_flow_id");if(!i||typeof i!="object"||Array.isArray(i))throw new y("payload.flow is required","missing_flow");return{flowId:s,flow:i}}function Rc(e,t){try{return{safeExpectedRev:Le(e.slice(0,200),"expectedRev"),safeFlowId:Le(t.slice(0,200),"flowId")}}catch{throw new y("expectedRev and payload.flowId must be safe path segments","invalid_path_segment")}}async function Ke(e,t,n,r,o,s={},i){let d=r.type,l=Tc(r),c=r.payload;if(d!=="replace_flow")throw new y("type must be 'replace_flow'","invalid_proposal_type");if(!l)throw new y("expectedRev is required (top-level field next to type and payload)","missing_expected_rev");if(!c||typeof c!="object"||Array.isArray(c))throw new y("payload must be an object","invalid_payload");let p=await Pc(o,l,c,r.nodes,i),{safeExpectedRev:f,safeFlowId:m}=Rc(l,p.flowId),h=p.flow,g=Buffer.byteLength(JSON.stringify(c),"utf8");if(g>j)throw new X("proposal payload",g,j);Ge(qs(h,m));let S=It(h),E=await o.getFlows();if(E.rev!==f)throw new J(f,E.rev,"submit_proposal");Ge(Bs(S,m,E.flows));let I=await o.getFlow(m);if(I.rev!==f)throw new J(f,I.rev,"submit_proposal");let N=It(I.flow);i&&await js(i,S);let{errors:nt,warnings:me}=await Ws(S,{flowId:m,baseFlow:N,...i?{nodeTypes:i}:{}});Ge(nt);let v={kind:"access-link",linkId:n.id};n.label!==void 0&&(v.label=n.label);let b=n.oauthClientId&&n.oauthUser?{kind:"oauth",clientId:n.oauthClientId,user:n.oauthUser}:v,w=dr(r.title)??Ao(N,S),x=e.create({type:"replace_flow",expectedRev:f,payload:{flowId:m,flow:S},baseFlow:N,title:w,createdBy:b,...me.length>0?{warnings:me}:{}}),k=se(x,s),C=k.flowLabel||void 0;return t.append("proposal.created",v,{proposalId:x.id,flowId:m,expectedRev:f,title:k.title,displayName:k.displayName,...C?{flowLabel:C}:{},...me.length>0?{warningCount:me.length}:{}}),k}function O(e,t="rest"){return Tn(e,t)}var jt,ai,bc,y,K=U(()=>{"use strict";ve();tr();qe();Yn();z();Ne();bo();lt();Je();Ae();a(Dt,"isMineProposal");jt=200,ai=new Set(["pending","approved","rejected","failed","withdrawn","expired","superseded"]);a(Po,"matchesStatusFilter");a(wc,"isProposalStatus");bc={status:[...ai].join(", "),limit:`integer 1\u2013${jt}`,offset:"integer \u2265 0",mine:"true, false, 1, 0",flowId:"one flow tab id"};a(Ht,"invalidFilterError");a(cr,"singleQueryValue");a(vc,"parseIntegerFilter");a(Ro,"isUnset");a(kc,"parseMineFilter");a(Co,"parseStatusFilter");a(ii,"parseRangeFilter");a(ur,"parseListProposalsQuery");a(ut,"listPageFields");a(Ac,"applyListPagination");a(Ut,"listAccessibleProposals");a($e,"getAccessibleProposal");a(xc,"accessLinkActor");a(Bt,"withdrawMyProposal");a(Sc,"conflictsForProposal");a(ue,"presentAccessibleProposalForApi");y=class extends Error{static{a(this,"AiOpsValidationError")}statusCode;code;details;constructor(t,n="validation_error",r={}){super(t),this.name="AiOpsValidationError",this.code=n,this.statusCode=r.statusCode??400,r.details&&(this.details=r.details)}};a(pe,"listFlows");a(xe,"getFlow");a(Tc,"resolveExpectedRev");a(di,"assertNodesArrayOrAbsent");a(_c,"materializePayloadFromNodes");a(Pc,"resolveReplaceFlowPayload");a(Rc,"assertSafeRevAndFlowId");a(Ke,"submitReplaceFlowProposal");a(O,"aiOpsErrorToHttp")});function Lo(e){return e==="get_link"?86400:2592e3}function Eo(e,t){return e==="get_link"?`get_link ttlSeconds must be \u2264 ${t} (24 hours)`:`ttlSeconds must be \u2264 ${t} (30 days) for ${e} tokens`}var pt,_n=U(()=>{"use strict";a(Lo,"maxTtlSecondsForKind");a(Eo,"ttlCapErrorMessage");pt="ttlSeconds must be a positive number of seconds within the cap for the token kind: get_link \u2264 86400 (24h); rest and mcp \u2264 2592000 (30 days)."});function _(e,t,n,r,o=Gt,s="error"){return{code:e,status:t,meaning:n,hint:r,channels:o,kind:s}}function Pn(e){return Lc.get(e)?.hint}function li(e){return e.replace(/\\/g,"\\\\").replace(/\|/g,"\\|")}function ci(){let e=a(s=>`| \`${s.code}\` | ${s.status} | ${li(s.meaning)} | ${li(s.hint)} | ${s.channels.join(", ")} |`,"row"),t=["| Code | HTTP | Meaning | Next step | Channels |","| --- | --- | --- | --- | --- |"],n=pr.filter(s=>s.kind==="error"),r=pr.filter(s=>s.kind==="warning"),o=pr.filter(s=>s.kind==="status");return[...t,...n.map(e),"","**Warnings** (in `warnings[]` on a successful propose; never blocking):","",...t,...r.map(e),"","**Auto-closed proposal statuses.** Pending proposals are closed automatically: `expired` when the live flow rev moves past their `expectedRev`, `superseded` when another proposal on the same flow is approved. Submit, get, and list responses include `autoClosedCount` when that request auto-closed proposals (omitted when zero).","",...t,...o.map(e)].join(`
|
|
7
|
+
`)}var Gt,qt,Io,Cc,Wt,pr,Lc,fr=U(()=>{"use strict";_n();Gt=["GET","REST","MCP"],qt=["GET"],Io=["REST","MCP"],Cc=["GET","REST"],Wt=["admin"];a(_,"e");pr=[_("access_link_missing",401,"No Bearer / path token","Ask the user for a new access link of the right kind"),_("bearer_scheme_required",401,"Auth used Basic (or non-Bearer)","Send `Authorization: Bearer <token>`",Io),_("token_expired",401,"Known token past `expiresAt` (body includes `expiredAt` and `accessLinksUrl`)","Ask the user to create a new link on the `/mcp` page \u2192 Access links (`accessLinksUrl`)"),_("access_link_invalid",401,"Unknown, revoked, or cleared after restart","Ask the user for a new access link"),_("wrong_token_kind",403,"Token kind does not match this surface","Mint the matching kind on `/mcp` \u2192 Access links; show `tellUser` to the human"),_("ai_features_disabled",403,"Plugin AI features off","Owner enables AI Access / `adminAuth`"),_("auth_required",401,"Admin session required","Sign in to the Node-RED editor",Wt),_("insufficient_permissions",403,"Admin lacks required scope","Need `*` for link mint / approve",Wt),_("invalid_link_kind",400,"Unknown `kind` on create","Use `get_link`, `rest`, or `mcp`",Wt),_("invalid_ttl",400,"`ttlSeconds` is zero, negative, not a number, or over the cap for the kind",pt,Wt),_("access_link_limit",409,"Too many active links","Revoke an existing link first",Wt),_("access_link_not_found",404,"Unknown link id on revoke","List links and use an existing id",Wt),_("link_create_not_supported",403,"Attempted mint under a GET link","Admin mints via `POST /mcp/api/v1/links` only",qt),_("rate_limited",429,"Per-IP or per-token limit","Wait `retryAfter` / `Retry-After`"),_("payload_too_large",413,"Body / proposal over ~1.5MB","Shrink content"),_("invalid_json",400,"Malformed JSON body","Fix Content-Type / body",Io),_("unsupported_media_type",415,"POST body is not `application/json`","Send `Content-Type: application/json`",Io),_("url_too_long",414,"GET URL over soft cap (~8KB)","Use chunked drafts or REST/MCP",qt),_("not_found",404,"Unknown path or resource","Re-read docs / discovery"),_("missing_query",400,"Required search `q` missing","Pass `?q=`",Cc),_("invalid_filter",400,"Invalid list filter (`status`, `limit`, `offset`, `mine`); body names `parameter` and `allowed`","Use an allowed value: status one of the proposal statuses; limit integer 1\u2013200; offset integer \u2265 0; mine true|false"),_("invalid_arguments",400,"MCP tool arguments failed schema validation; body names `parameter`","Fix the named argument to match the tool input schema (see tools/list)",["MCP"]),_("invalid_diff_format",400,"Unknown `format` on proposal diff","Use `format=summary` (default) or `format=full`"),_("confirm_required",400,"GET withdraw without `confirm=1`","Repeat the request with `confirm=1`",qt),_("missing_expected_rev",400,"Proposal submitted without `expectedRev` / `rev`","Read the flow and pass its `rev`"),_("version_mismatch",409,"Stale `rev` / `expectedRev`","Re-read flows and resubmit"),_("proposal_not_found",404,"Unknown proposal id","List proposals and use an existing id"),_("proposal_not_pending",409,"Action needs a pending proposal","Only pending can withdraw / decide"),_("proposal_not_owned",403,"Withdraw of another token\u2019s proposal","Only the creating token can withdraw"),_("validation_failed",400,"Several validation errors at once (message `N validation errors`)","Fix every entry in errors[] and resubmit."),_("duplicate_node_id",400,"The same node id appears more than once in a full tab or batch","Give every node a unique id"),_("missing_node_id",400,"Node / op without an id","Include a non-empty string id"),_("missing_node_type",400,"Node without a `type`","Set `type` to an installed palette type name"),_("invalid_node_type",400,"`type` is not a string","Use the palette type name exactly"),_("invalid_nodes",400,"`flow.nodes` / `flow.configs` not an array","Send arrays (use `[]` to clear)"),_("invalid_node",400,"Node entry is not an object","Send `{ id, type, \u2026 }` objects"),_("invalid_wires",400,"Bad wires shape, missing target, or more ports than outputs","Wire only to existing node ids / ports"),_("z_mismatch",400,"Node `z` differs from the flow id","Set `z` to the tab id or omit it"),_("node_already_exists",400,"`add` op for an id already in the flow","Use `set` or a new id"),_("protected_property",400,"Tried to change id / type / z","Omit id, type, and z from edits"),_("node_not_found",404,"Unknown node (single error only; several errors \u2192 `validation_failed`)","Re-read the flow"),_("field_not_found",404,"Unknown field on a node","Re-read the node"),_("text_not_found",400,"`replace_text` find had no match","Re-read field; find is exact"),_("ambiguous_match",409,"`replace_text` matched more than once","Pass `occurrence` (1-based) or unique find"),_("unknown_node_type",400,"Proposal uses a type that is not installed","List node-types / palette search; ask user to install"),_("node_type_not_found",404,"Unknown type on node-types detail","List types or search palette"),_("chunk_conflict",409,"Same draft seq re-sent with different data","Re-send identical data or start a new draft",qt),_("missing_chunks",400,"Draft submit with gaps in seq","Upload every seq from 0 through max",qt),_("draft_hash_mismatch",400,"Draft sha256 does not match assembled bytes","Send the hex sha256 of the assembled UTF-8",qt),_("missing_config_node",201,"Warning: node has no (or an unresolved) config node yet","Configure before deploy if needed; see `candidates`",Gt,"warning"),_("unwired_node",201,"Warning: node has outputs but no wires","Wire it, or ignore while scaffolding",Gt,"warning"),_("incomplete_wires",201,"Warning: fewer wire ports than outputs","Add ports if outputs should connect",Gt,"warning"),_("expired",200,"Status: pending proposal auto-closed because the live flow rev moved past `expectedRev`","Re-read the flow and submit a new proposal",Gt,"status"),_("superseded",200,"Status: pending proposal auto-closed because another proposal on the same flow was approved","Re-read the flow and submit a new proposal if still needed",Gt,"status"),_("upstream_error",502,"Node-RED runtime/flows error","Retry; check Node-RED logs"),_("internal_error",500,"Unexpected server error","Retry; check Node-RED logs")],Lc=new Map(pr.map(e=>[e.code,e]));a(Pn,"registryHint");a(li,"cell");a(ci,"renderGetLinkErrorTable")});function ie(e,t,n={}){return No(e,t,n)}function Ec(e,t){return t==="excess_ports"?e==="mcp"?"This node has more wire ports than its type declares \u2014 remove extra ports or raise the node outputs, then retry.":e==="rest"?"This node has more wire ports than its type declares \u2014 trim wires[] or raise outputs, then retry.":"This node has more wire ports than its type declares \u2014 trim wires or raise outputs, then retry.":t==="missing_target"?e==="mcp"?"Wire targets must be node ids in this flow \u2014 add the node in the same patch or use get_flow ids.":e==="rest"?"Wire targets must be node ids in this flow \u2014 add the node in the same patch or use GET /flows/{id} ids.":"wires must list existing node ids (or add those nodes in the same patch).":e==="mcp"||e==="rest"?"Fix wires: targets must exist in this flow, and port count must not exceed the node outputs.":"wires must list existing node ids (or add those nodes in the same patch)."}function Ic(e){return e==="mcp"?"Call list_node_types or search_palette. Ask the user to install via Manage palette, then recheck. This API never installs.":e==="rest"?"Call GET /node-types?q= or GET /palette/search?q=. Ask the user to install via Manage palette, then recheck. This API never installs.":"Call GET \u2026/node-types?q= or \u2026/palette/search?q=. Ask the user to install via Manage palette, then recheck. This API never installs."}function Ve(e,t,n,r){return e==="mcp"?t:e==="rest"?n:r}function No(e,t,n={}){switch(e){case"version_mismatch":return Ve(t,"Re-read with list_flows/get_flow and retry with the current rev.","Re-read the flow and retry with the current rev.","Re-read the flow and retry with the current rev.");case"node_not_found":return Ve(t,"Call get_flow or get_node and use a node id from that response.","Call GET /flows/{id} and use a node id from that response.","Call GET \u2026/flows/<id> and use a node id from that response.");case"flow_not_found":return Ve(t,"Call list_flows to list valid ids.","Call GET /flows to list valid ids.","Use an id from GET ./flows.");case"proposal_not_found":return Ve(t,"Call list_proposals and use an id from that response.","List proposals and use an id from that response.","List proposals and use an id from that response.");case"proposal_not_owned":return"Only the access link that created a pending proposal can withdraw it.";case"proposal_not_pending":return"Only pending proposals can be withdrawn or decided.";case"confirm_required":return"Add confirm=1 to the withdraw URL (required so link prefetchers cannot withdraw).";case"invalid_wires":return Ec(t,n.cause);case"node_type_not_found":case"unknown_node_type":return Ic(t);case"missing_node_type":return'Each add op needs node.type as a non-empty string (e.g. "inject").';case"invalid_line_range":return Ve(t,'Pass lines as "A" or "A-B" (1-based inclusive). Reversed ranges (end < start) are invalid.',"Use lines=A or lines=A-B (1-based inclusive). Reversed ranges (end < start) are invalid.","Use lines=A or lines=A-B (1-based inclusive). Reversed ranges (end < start) are invalid.");case"line_range_out_of_bounds":return"start is past the end of the field \u2014 check totalLines and request a lower lines range.";case"ambiguous_match":return"find matched more than once \u2014 pass occurrence=N (1-based) or a more unique find string.";case"missing_expected_rev":return Ve(t,"Pass expectedRev from list_flows/get_flow (top-level, next to type and payload).","Pass expectedRev from GET /flows or GET /flows/{id} (top-level, next to type and payload).","Pass rev from GET \u2026/flows or \u2026/flows/<id>.");case"missing_query":return Ve(t,"Pass a non-empty q string.","q query parameter is required.","q query parameter is required.");case"unsupported_media_type":return"Send Content-Type: application/json with a JSON body.";case"text_not_found":return"Re-read the node field; find must match exactly (case-sensitive).";case"field_not_found":return"Re-read the node; field must be an existing string property (template, func, \u2026).";case"field_not_string":return Ve(t,"This tool only returns string fields (template, func, \u2026). Use get_node for other property types.","This endpoint only returns string fields (template, func, \u2026). Use GET /nodes/{id} for other property types.","This endpoint only returns string fields (template, func, \u2026). Use GET /nodes/{id} for other property types.");default:return}}function Tn(e,t,n={}){let r=n.canonicalizeMessage!==!1,o=null;if(e instanceof y?o={statusCode:e.statusCode,body:{error:e.message,code:e.code,...e.details??{}}}:e instanceof J?o={statusCode:e.statusCode,body:{error:e.message,code:e.code,expectedRev:e.expectedRev,currentRev:e.currentRev}}:e instanceof G?o={statusCode:e.statusCode,body:{error:e.message,code:e.code,flowId:e.flowId}}:e instanceof re||e instanceof Nt||e instanceof te||e instanceof X?o={statusCode:e.statusCode,body:{error:e.message,code:e.code}}:(e instanceof Ce||e instanceof be)&&(o={statusCode:e.statusCode,body:{error:e.message,code:e.code}}),!o)return null;let s=o.body.code;if(r&&!Array.isArray(o.body.errors)){let i=Fe[s];i&&(o.body.error=i)}if(typeof o.body.hint!="string"||!o.body.hint){let i=No(s,t,{cause:o.body.cause})??Pn(s);i&&(o.body.hint=i)}return o}function ui(e,t,n={}){let o={error:n.error??Fe[t]??"Request failed",code:t,...n.details??{}},s=No(t,e,{cause:o.cause})??Pn(t);return s&&(o.hint=s),{statusCode:n.statusCode??400,body:o}}var Fe,Ne=U(()=>{"use strict";ve();qe();z();K();fr();Fe={node_not_found:"Node was not found",flow_not_found:"Flow was not found",field_not_found:"Field was not found",field_not_string:"Field is not a string",missing_query:"q query parameter is required",missing_flow_id:"Missing flow id",missing_node_id:"Missing node id",missing_field:"Missing field name",invalid_line_range:"lines must be A or A-B (1-based inclusive; end >= start)",line_range_out_of_bounds:"lines range is out of bounds",proposal_not_found:"Proposal was not found",proposal_not_owned:"Proposal was not created by this access link",proposal_not_pending:"Proposal is not pending",confirm_required:"Withdraw over GET requires confirm=1",unsupported_media_type:"Content-Type must be application/json",upstream_error:"Upstream request failed",internal_error:"Internal server error"};a(ie,"channelErrorHint");a(Ec,"invalidWiresHint");a(Ic,"nodeTypeHint");a(Ve,"pickChannel");a(No,"hintFor");a(Tn,"mapErrorForChannel");a(ui,"buildChannelError")});var nm={};ss(nm,{ACCESS_LINK_INVALID_MESSAGE:()=>rt,ACCESS_LINK_MISSING_MESSAGE:()=>le,AccessLinkStore:()=>vt,EDITOR_SIDEBAR_ASSET_VERSION:()=>Kt,MAX_PROPOSAL_JSON_BYTES:()=>j,SettingsStore:()=>Pt,areAiFeaturesEnabled:()=>ge,bindSettingsStore:()=>Wn,createPluginServices:()=>zr,default:()=>tm,getStorageDir:()=>Mr,isAdminAuthEnabled:()=>W,packageVersion:()=>Jt,registerRoutes:()=>md,renderEditorSidebarJs:()=>mr,renderEditorSidebarLoaderHtml:()=>hi,requireAccessLink:()=>Et,resolveAiAccessSettings:()=>he});module.exports=vd(nm);var fd=require("fs");var Dn=require("fs"),ln=require("path");z();function jn(e){let t=(0,ln.join)(e,".ai-access");return{root:t,proposals:(0,ln.join)(t,"proposals"),logs:(0,ln.join)(t,"logs"),settings:(0,ln.join)(t,"config.json")}}a(jn,"resolveAiAccessPaths");function Vr(e){(0,Dn.mkdirSync)(e,{recursive:!0,mode:Kr});try{(0,Dn.chmodSync)(e,Kr)}catch{}}a(Vr,"mkdirSecure");function cs(e){Vr(e.root),Vr(e.proposals),Vr(e.logs)}a(cs,"ensureAiAccessDirs");ot();Pe();function Kn(e){let t=ne(e);return`auth-tokens${(t==="/"?"":t).replace(/\//g,"-")}`}a(Kn,"editorAuthTokenKey");Pe();Ne();function Nc(e){if(!e)return!1;let t=e.split(";")[0]?.trim().toLowerCase()??"";return t==="application/json"||t.endsWith("+json")}a(Nc,"isJsonContentType");function Fc(e){let t=e.headers["content-length"];if(typeof t=="string"&&t.length>0){let n=Number(t);if(Number.isFinite(n)&&n>0)return!0;if(n===0)return!1}return e.headers["transfer-encoding"]!==void 0}a(Fc,"hasRequestBody");function Xe(e="rest"){return(t,n,r)=>{let o=t.headers["content-type"];if(Nc(o)){r();return}if(!o&&!Fc(t)){r();return}let s=ui(e,"unsupported_media_type",{statusCode:415});n.status(s.statusCode).json(s.body)}}a(Xe,"requireJsonContentType");var Oc="text/plain; charset=utf-8",$c="text/html; charset=utf-8",Mc="default-src 'none'; base-uri 'self'; form-action 'none'; frame-ancestors 'none'";function Dc(e){let t=e.query?.format;return typeof t=="string"?t.toLowerCase():void 0}a(Dc,"formatParam");function jc(e){let t=Dc(e);if(t==="md"||t==="json")return"native";if(!e.get("accept"))return"html";let n=e.accepts(["text/html","application/json","text/markdown","text/plain"]);return n==="application/json"||n==="text/markdown"?"native":n==="text/plain"?"plain":"html"}a(jc,"chooseGetLinkRepresentation");function pi(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}a(pi,"escapeHtml");var Hc=/(?<![A-Za-z0-9_.~\-/])\.\/[A-Za-z0-9._~\-/]+(?:\?[A-Za-z0-9._~\-=&%]+)?/g;function Uc(e){return e.replace(Hc,(t,n)=>{if(e.startsWith("<",n+t.length))return t;let r=t.replace(/[.]+$/,""),o=t.slice(r.length);return r.length<=2||r.includes("..")||r.includes("//")||r.includes("&")?t:`<a href="${r}">${r}</a>${o}`})}a(Uc,"linkifyEscaped");function Bc(e,t){return`<!doctype html>
|
|
8
|
+
<html><head><meta charset="utf-8">${t?`<base href="${pi(t)}">`:""}<title>Node-RED AI Access</title></head><body><pre>${Uc(pi(e))}</pre></body></html>
|
|
9
|
+
`}a(Bc,"renderGetLinkHtml");function qc(e){let t=e.split("?")[0]??"",n=/^(.*?\/mcp\/a\/[A-Za-z0-9_-]+)(?:\/|$)/.exec(t);if(!(!n?.[1]||!/^[A-Za-z0-9._~\-/]*$/.test(n[1])))return`${n[1]}/`}a(qc,"getLinkBaseHref");function Wc(e){try{return JSON.stringify(JSON.parse(e),null,2)}catch{return e}}a(Wc,"prettyJson");function fi(){return(e,t,n)=>{let r=jc(e),o=qc(e.originalUrl);t.vary("Accept"),t.setHeader("Cache-Control","no-store"),t.setHeader("Referrer-Policy","no-referrer"),t.setHeader("X-Robots-Tag","noindex, nofollow"),t.setHeader("X-Content-Type-Options","nosniff");let s=t.send.bind(t);t.send=(i=>{let d=String(t.getHeader("Content-Type")??"").toLowerCase(),l=d.startsWith("application/json"),c=d.startsWith("text/markdown");if(r==="native"||!l&&!c||t.headersSent)return s(i);let u=typeof i=="string"?i:Buffer.isBuffer(i)?i.toString("utf8"):void 0;return u===void 0?s(i):r==="plain"?(t.setHeader("Content-Type",Oc),s(u)):(t.setHeader("Content-Type",$c),t.setHeader("Content-Security-Policy",Mc),s(Bc(l?Wc(u):u,o)))}),n()}}a(fi,"getLinkContentNegotiation");Hn();Pe();function Gc(e){let t=de(e.settings.httpAdminRoot),n=He(e.settings.httpAdminRoot),r="0.2.0-beta.2",o=[{method:"GET",path:"/",auth:"none",summary:"This discovery index"},{method:"GET",path:"/openapi.yaml",auth:"none",summary:"OpenAPI 3.1 specification (YAML)"},{method:"GET",path:"/openapi.json",auth:"none",summary:"OpenAPI 3.1 specification (JSON)"},{method:"GET",path:"/status",auth:"admin-or-access-link",summary:"Plugin status and caller info"},{method:"GET",path:"/flows",auth:"admin-or-access-link",summary:"List flows + rev"},{method:"GET",path:"/flows/{id}",auth:"admin-or-access-link",summary:"Get one flow tab + rev (?summary=1 for node list without field contents)"},{method:"GET",path:"/nodes/{id}",auth:"admin-or-access-link",summary:"Get one full node (optional ?flow=)"},{method:"GET",path:"/nodes/{id}/field/{name}",auth:"admin-or-access-link",summary:"Get one node field (?lines=A-B optional)"},{method:"GET",path:"/search",auth:"admin-or-access-link",summary:"Text search across string fields (?q=&flow=)"},{method:"GET",path:"/node-types",auth:"admin-or-access-link",summary:"List installed palette node types (?q= filter)"},{method:"GET",path:"/node-types/{type}",auth:"admin-or-access-link",summary:"Detail for one installed node type (defaults JSON, inputs/outputs, help; ?help=0 omits help)"},{method:"GET",path:"/palette/search",auth:"admin-or-access-link",summary:"Search Node-RED catalogue (recommend only; never installs) (?q=)"},{method:"POST",path:"/proposals",auth:"access-link",summary:"Create a replace_flow proposal"},{method:"GET",path:"/proposals",auth:"admin-or-access-link",summary:"List proposals (admin: all; link: own)"},{method:"GET",path:"/proposals/{id}",auth:"admin-or-access-link",summary:"Get one proposal"},{method:"GET",path:"/proposals/{id}/diff",auth:"admin-or-access-link",summary:"Diff proposal vs current flow"}];return{ok:!0,name:"node-red-ai-access",version:r,description:"Temporary, owner-approved Node-RED access for AI clients over plain HTTP (REST) or MCP.",auth:{scheme:"Bearer",header:"Authorization: Bearer <token>",description:"An admin with * mints tokens on the /mcp Access links page (POST /links only \u2014 never under GET /mcp/a/<token>). Each token has kind get_link | rest | mcp and only works on that surface. Send rest/mcp tokens as Authorization: Bearer <token>. Missing/invalid \u2192 401 access_link_missing / access_link_invalid; wrong kind \u2192 403 wrong_token_kind. GET /mcp/a links cannot create tokens.",tokenKinds:["get_link","rest","mcp"],mint:{path:"/links",method:"POST",auth:"admin",note:"Admin * only. Not available under /mcp/a/<token>."}},openapi:`${t}/openapi.yaml`,openapiJson:`${t}/openapi.json`,mcp:n,apiBase:t,getLink:{base:"/mcp/a/<token>",canMintTokens:!1,note:"Self-contained GET surface for get_link tokens only. No POST /links equivalent."},endpoints:o}}a(Gc,"buildDiscoveryIndex");function Fo(e){return(t,n)=>{n.status(200).json(Gc(e))}}a(Fo,"discoveryIndexHandler");function mi(){return(e,t)=>{t.status(404).json({error:"Not found",code:"not_found"})}}a(mi,"mcpApiNotFoundHandler");var zt;function Jt(){return zt||(zt="0.2.0-beta.2",zt)}a(Jt,"packageVersion");var Kt=Jt();function zc(){return`#red-ui-sidebar-ai-access {
|
|
10
10
|
height: 100%;
|
|
11
11
|
display: flex;
|
|
12
12
|
flex-direction: column;
|
|
@@ -49,7 +49,111 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
49
49
|
}
|
|
50
50
|
#red-ui-sidebar-ai-access .ai-access-oauth-warning {
|
|
51
51
|
color: #9b2c2c;
|
|
52
|
-
|
|
52
|
+
background: #fdf0f0;
|
|
53
|
+
border: 1px solid #f0c4c4;
|
|
54
|
+
border-radius: 4px;
|
|
55
|
+
padding: 8px 10px;
|
|
56
|
+
line-height: 1.4;
|
|
57
|
+
}
|
|
58
|
+
#red-ui-sidebar-ai-access .ai-access-oauth-warning p {
|
|
59
|
+
margin: 0 0 4px;
|
|
60
|
+
}
|
|
61
|
+
#red-ui-sidebar-ai-access .ai-access-oauth-warning ul {
|
|
62
|
+
margin: 0 0 6px 18px;
|
|
63
|
+
padding: 0;
|
|
64
|
+
}
|
|
65
|
+
#red-ui-sidebar-ai-access .ai-access-oauth-warning li {
|
|
66
|
+
margin: 2px 0;
|
|
67
|
+
overflow-wrap: anywhere;
|
|
68
|
+
}
|
|
69
|
+
#red-ui-sidebar-ai-access .ai-access-oauth-warning code {
|
|
70
|
+
font-family: var(--red-ui-monospace-font, monospace);
|
|
71
|
+
color: var(--red-ui-primary-text-color, #333);
|
|
72
|
+
background: rgba(0, 0, 0, 0.06);
|
|
73
|
+
border: 1px solid rgba(0, 0, 0, 0.08);
|
|
74
|
+
border-radius: 3px;
|
|
75
|
+
padding: 0 4px;
|
|
76
|
+
font-size: 0.95em;
|
|
77
|
+
overflow-wrap: anywhere;
|
|
78
|
+
word-break: break-all;
|
|
79
|
+
white-space: normal;
|
|
80
|
+
}
|
|
81
|
+
#red-ui-sidebar-ai-access .ai-access-url-warning {
|
|
82
|
+
color: #8a5a00;
|
|
83
|
+
}
|
|
84
|
+
#red-ui-sidebar-ai-access .ai-access-oauth-warning a {
|
|
85
|
+
text-decoration: underline;
|
|
86
|
+
cursor: pointer;
|
|
87
|
+
}
|
|
88
|
+
#red-ui-sidebar-ai-access .ai-access-duration {
|
|
89
|
+
display: flex;
|
|
90
|
+
flex-wrap: nowrap;
|
|
91
|
+
align-items: center;
|
|
92
|
+
gap: 6px;
|
|
93
|
+
}
|
|
94
|
+
#red-ui-sidebar-ai-access .red-ui-settings-row.stacked .ai-access-duration input {
|
|
95
|
+
width: 80px;
|
|
96
|
+
flex: 0 1 80px;
|
|
97
|
+
min-width: 50px;
|
|
98
|
+
margin: 0;
|
|
99
|
+
}
|
|
100
|
+
#red-ui-sidebar-ai-access .red-ui-settings-row.stacked .ai-access-duration select {
|
|
101
|
+
width: auto;
|
|
102
|
+
flex: 0 1 auto;
|
|
103
|
+
min-width: 70px;
|
|
104
|
+
margin: 0;
|
|
105
|
+
}
|
|
106
|
+
#red-ui-sidebar-ai-access .ai-access-check-result {
|
|
107
|
+
display: none;
|
|
108
|
+
margin: 6px 0 0;
|
|
109
|
+
padding: 6px 8px;
|
|
110
|
+
border-radius: 4px;
|
|
111
|
+
font-weight: 600;
|
|
112
|
+
overflow-wrap: anywhere;
|
|
113
|
+
}
|
|
114
|
+
#red-ui-sidebar-ai-access .ai-access-check-result.is-ok {
|
|
115
|
+
display: block;
|
|
116
|
+
color: #1e6b2e;
|
|
117
|
+
background: #e8f6ec;
|
|
118
|
+
border: 1px solid #a8d8b4;
|
|
119
|
+
}
|
|
120
|
+
#red-ui-sidebar-ai-access .ai-access-check-result.is-fail {
|
|
121
|
+
display: block;
|
|
122
|
+
color: #9b2c2c;
|
|
123
|
+
background: #fdf0f0;
|
|
124
|
+
border: 1px solid #f0c4c4;
|
|
125
|
+
}
|
|
126
|
+
#red-ui-sidebar-ai-access .ai-access-check-result.is-pending {
|
|
127
|
+
display: block;
|
|
128
|
+
font-weight: 400;
|
|
129
|
+
color: var(--red-ui-secondary-text-color, #666);
|
|
130
|
+
}
|
|
131
|
+
#red-ui-sidebar-ai-access .ai-access-muted {
|
|
132
|
+
color: var(--red-ui-secondary-text-color, #888);
|
|
133
|
+
font-size: 0.85em;
|
|
134
|
+
margin: 4px 0 0;
|
|
135
|
+
line-height: 1.35;
|
|
136
|
+
}
|
|
137
|
+
#red-ui-sidebar-ai-access details.ai-access-muted summary {
|
|
138
|
+
cursor: pointer;
|
|
139
|
+
}
|
|
140
|
+
#red-ui-sidebar-ai-access .ai-access-oauth-help {
|
|
141
|
+
margin: 4px 0 10px;
|
|
142
|
+
padding: 6px 10px;
|
|
143
|
+
background: var(--red-ui-secondary-background, #f3f3f3);
|
|
144
|
+
border-radius: 4px;
|
|
145
|
+
line-height: 1.4;
|
|
146
|
+
}
|
|
147
|
+
#red-ui-sidebar-ai-access .ai-access-oauth-help summary {
|
|
148
|
+
cursor: pointer;
|
|
149
|
+
font-weight: 600;
|
|
150
|
+
}
|
|
151
|
+
#red-ui-sidebar-ai-access .ai-access-oauth-help ol {
|
|
152
|
+
margin: 6px 0 4px 18px;
|
|
153
|
+
padding: 0;
|
|
154
|
+
}
|
|
155
|
+
#red-ui-sidebar-ai-access .ai-access-oauth-help li {
|
|
156
|
+
margin: 2px 0;
|
|
53
157
|
}
|
|
54
158
|
#red-ui-sidebar-ai-access .ai-access-oauth-client {
|
|
55
159
|
display: flex;
|
|
@@ -181,7 +285,7 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
181
285
|
border: 0;
|
|
182
286
|
background: transparent;
|
|
183
287
|
color: transparent;
|
|
184
|
-
}`}a(
|
|
288
|
+
}`}a(zc,"editorSidebarStyles");function Jc(){return`
|
|
185
289
|
/**
|
|
186
290
|
* Editor right-hand sidebar tab: AI Access.
|
|
187
291
|
* Served from /mcp/editor/ai-access-sidebar.js (no-store + ?v= cache-bust).
|
|
@@ -371,7 +475,12 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
371
475
|
$('#ai-access-' + FIELD_IDS[key]).val(s[key] == null ? '' : s[key]);
|
|
372
476
|
});
|
|
373
477
|
$('#ai-access-cors').val((s.corsAllowedOrigins || []).join(', '));
|
|
374
|
-
|
|
478
|
+
var tp = data.trustProxy;
|
|
479
|
+
$('#ai-access-trust-proxy').text(
|
|
480
|
+
tp === false || tp === undefined || tp === null || tp === 0
|
|
481
|
+
? 'Trust proxy: off (set httpServerOptions["trust proxy"] in settings.js if behind a reverse proxy)'
|
|
482
|
+
: 'Trust proxy: ' + (tp === true ? 'on' : String(tp))
|
|
483
|
+
);
|
|
375
484
|
Object.keys(FIELD_IDS).forEach(function (key) {
|
|
376
485
|
var locked = data.sources && data.sources[key] === 'settings.js';
|
|
377
486
|
$('#ai-access-' + FIELD_IDS[key]).prop('disabled', readonly || locked);
|
|
@@ -415,13 +524,9 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
415
524
|
|
|
416
525
|
function refreshOAuth() {
|
|
417
526
|
api('/oauth/status').then(function (status) {
|
|
418
|
-
|
|
419
|
-
if (status.httpNodeRootFallback) warnings.push('httpNodeRoot is disabled: OAuth metadata is served under the admin /mcp path.');
|
|
420
|
-
if (status.authorization && status.authorization.length) warnings.push('OAuth authorization routes collide with: ' + status.authorization.join(', '));
|
|
421
|
-
if (status.metadata && status.metadata.length) warnings.push('OAuth metadata routes collide with: ' + status.metadata.join(', '));
|
|
422
|
-
$('#ai-access-oauth-route-warning').text(warnings.join('\\n')).toggle(warnings.length > 0);
|
|
527
|
+
renderRouteWarning(status);
|
|
423
528
|
}).catch(function (err) {
|
|
424
|
-
$('#ai-access-oauth-route-warning').text('OAuth route status unavailable: ' + err.message).show();
|
|
529
|
+
$('#ai-access-oauth-route-warning').empty().text('OAuth route status unavailable: ' + err.message).show();
|
|
425
530
|
});
|
|
426
531
|
api('/oauth/clients').then(function (data) {
|
|
427
532
|
var list = $('#ai-access-oauth-clients').empty();
|
|
@@ -449,6 +554,68 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
449
554
|
});
|
|
450
555
|
}
|
|
451
556
|
|
|
557
|
+
function collisionItem(c) {
|
|
558
|
+
var li = $('<li></li>');
|
|
559
|
+
var effect = c.kind === 'authorization' ? ' \u2014 OAuth login/token routes disabled' : ' \u2014 OAuth metadata disabled';
|
|
560
|
+
if (c.source === 'http-in') {
|
|
561
|
+
$('<span></span>').text('Route ').appendTo(li);
|
|
562
|
+
$('<code></code>').text(c.path).appendTo(li);
|
|
563
|
+
$('<span></span>').text(' is already used by http-in node ').appendTo(li);
|
|
564
|
+
var label = "'" + (c.nodeName || c.nodeId || 'unnamed') + "'";
|
|
565
|
+
if (c.nodeId && RED.view && RED.view.reveal) {
|
|
566
|
+
$('<a href="#"></a>').text(label).attr('title', 'Show node in the editor').appendTo(li)
|
|
567
|
+
.on('click', function (e) { e.preventDefault(); RED.view.reveal(c.nodeId); });
|
|
568
|
+
} else {
|
|
569
|
+
$('<span></span>').text(label).appendTo(li);
|
|
570
|
+
}
|
|
571
|
+
} else if (c.source === 'httpStatic') {
|
|
572
|
+
$('<span></span>').text('File ').appendTo(li);
|
|
573
|
+
$('<code></code>').text(c.path).appendTo(li);
|
|
574
|
+
$('<span></span>').text(' is served by httpStatic').appendTo(li);
|
|
575
|
+
} else if (c.source === 'middleware') {
|
|
576
|
+
$('<span></span>').text('Path ').appendTo(li);
|
|
577
|
+
$('<code></code>').text(c.path).appendTo(li);
|
|
578
|
+
$('<span></span>').text(' is already handled by other middleware (' + (c.layer || 'unknown') + ')').appendTo(li);
|
|
579
|
+
} else {
|
|
580
|
+
$('<span></span>').text('Route ').appendTo(li);
|
|
581
|
+
$('<code></code>').text(c.path).appendTo(li);
|
|
582
|
+
$('<span></span>').text(' is already registered by another node or plugin').appendTo(li);
|
|
583
|
+
}
|
|
584
|
+
$('<span></span>').text(effect).appendTo(li);
|
|
585
|
+
return li;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function renderRouteWarning(status) {
|
|
589
|
+
var box = $('#ai-access-oauth-route-warning').empty();
|
|
590
|
+
var details = status.details;
|
|
591
|
+
if (!details) {
|
|
592
|
+
details = [];
|
|
593
|
+
(status.authorization || []).forEach(function (t) { details.push({ kind: 'authorization', source: 'text', text: t }); });
|
|
594
|
+
(status.metadata || []).forEach(function (t) { details.push({ kind: 'metadata', source: 'text', text: t }); });
|
|
595
|
+
}
|
|
596
|
+
if (status.httpNodeRootFallback) {
|
|
597
|
+
$('<p></p>').text('httpNodeRoot is disabled: OAuth metadata is served under the admin /mcp path.').appendTo(box);
|
|
598
|
+
}
|
|
599
|
+
if (details.length) {
|
|
600
|
+
var intro = $('<p></p>').appendTo(box);
|
|
601
|
+
$('<strong></strong>').text('OAuth route conflict').appendTo(intro);
|
|
602
|
+
$('<span></span>').text(': these paths are already in use, so AI Access will not serve them. Remove or change the conflicting routes, then deploy.').appendTo(intro);
|
|
603
|
+
var ul = $('<ul></ul>').appendTo(box);
|
|
604
|
+
details.forEach(function (c) {
|
|
605
|
+
if (c.source === 'text') $('<li></li>').text(c.text).appendTo(ul);
|
|
606
|
+
else collisionItem(c).appendTo(ul);
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
box.toggle(!!(details.length || status.httpNodeRootFallback));
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function setCheckResult(state, text) {
|
|
613
|
+
$('#ai-access-check-result')
|
|
614
|
+
.removeClass('is-ok is-fail is-pending')
|
|
615
|
+
.addClass(state ? 'is-' + state : '')
|
|
616
|
+
.text(text || '');
|
|
617
|
+
}
|
|
618
|
+
|
|
452
619
|
function oauthPublicUrlWarning(value) {
|
|
453
620
|
if (!value) return 'No Public URL is configured. OAuth clients may not discover this server.';
|
|
454
621
|
try {
|
|
@@ -585,9 +752,8 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
585
752
|
api('/settings', { method: 'PUT', body: JSON.stringify(payload) })
|
|
586
753
|
.then(function (data) {
|
|
587
754
|
applyForm(data);
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
}
|
|
755
|
+
var urlWarnings = (data.warnings && data.warnings.length) ? data.warnings.join(' ') : '';
|
|
756
|
+
$('#ai-access-public-url-warning').text(urlWarnings).toggle(!!urlWarnings);
|
|
591
757
|
if (silent) showSavedToast();
|
|
592
758
|
else setFlash($('#ai-access-flash'), 'Settings saved.', true);
|
|
593
759
|
refreshPending();
|
|
@@ -609,7 +775,7 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
609
775
|
saveSettings({ silent: true });
|
|
610
776
|
}, 400);
|
|
611
777
|
}
|
|
612
|
-
`}a(
|
|
778
|
+
`}a(Jc,"editorSidebarScriptPartA");function Kc(){return`
|
|
613
779
|
function buildContent() {
|
|
614
780
|
var content = $('<div></div>', { id: CONTENT_ID, class: 'red-ui-sidebar-ai-access' });
|
|
615
781
|
|
|
@@ -726,21 +892,33 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
726
892
|
if (field[0] === 'public-url') {
|
|
727
893
|
$('<button type="button" id="ai-access-check-public-url" class="red-ui-button"></button>')
|
|
728
894
|
.text('Check').appendTo(controls).on('click', function () {
|
|
729
|
-
|
|
895
|
+
setCheckResult('pending', 'Checking\u2026');
|
|
730
896
|
api('/settings/check-public-url', { method: 'POST', body: JSON.stringify({ publicBaseUrl: $('#ai-access-public-url').val() }) })
|
|
731
897
|
.then(function (result) {
|
|
732
|
-
|
|
733
|
-
}).catch(function (err) {
|
|
898
|
+
setCheckResult(result.ok ? 'ok' : 'fail', (result.ok ? '\u2713 OK: ' : '\u2717 Failed: ') + (result.message || result.error) + (result.status ? ' (HTTP ' + result.status + ')' : ''));
|
|
899
|
+
}).catch(function (err) { setCheckResult('fail', '\u2717 ' + err.message); });
|
|
734
900
|
});
|
|
735
|
-
$('<
|
|
736
|
-
$('<
|
|
901
|
+
$('<div id="ai-access-public-url-warning" class="ai-access-muted ai-access-url-warning" role="status" style="display:none"></div>').appendTo(fieldRow);
|
|
902
|
+
$('<div id="ai-access-check-result" class="ai-access-check-result" aria-live="polite"></div>').appendTo(fieldRow);
|
|
903
|
+
var notes = $('<details class="ai-access-muted"></details>').appendTo(fieldRow);
|
|
904
|
+
$('<summary></summary>').text('About the check').appendTo(notes);
|
|
905
|
+
$('<div></div>').text("Loopback checks use Node-RED's listening port; behind a reverse proxy, use the internal port. Unix-socket listeners are not supported. HTTPS checks fail with self-signed certificates.").appendTo(notes);
|
|
906
|
+
$('<div id="ai-access-trust-proxy"></div>').appendTo(notes);
|
|
737
907
|
}
|
|
738
908
|
});
|
|
739
|
-
var trustRow = $('<div class="red-ui-settings-row stacked"></div>').appendTo(body);
|
|
740
|
-
$('<label></label>').text('Express trust proxy (read-only)').appendTo(trustRow);
|
|
741
|
-
$('<span id="ai-access-trust-proxy"></span>').appendTo(trustRow);
|
|
742
909
|
$('<h3></h3>').text('MCP OAuth').appendTo(body);
|
|
743
910
|
$('<p class="form-tips"></p>').text('Clients can request read or propose access through OAuth. Changes apply immediately.').appendTo(body);
|
|
911
|
+
var help = $('<details class="ai-access-oauth-help"></details>').appendTo(body);
|
|
912
|
+
$('<summary></summary>').text('How to use MCP OAuth').appendTo(help);
|
|
913
|
+
var steps = $('<ol></ol>').appendTo(help);
|
|
914
|
+
[
|
|
915
|
+
'Set the Public URL above to the HTTPS address clients use to reach Node-RED, then click Check.',
|
|
916
|
+
'Enable MCP OAuth below.',
|
|
917
|
+
'In your MCP client (Claude, ChatGPT, etc.) add the server URL <Public URL>/mcp.',
|
|
918
|
+
'The client registers itself automatically (Dynamic Client Registration).',
|
|
919
|
+
'You are sent to the Node-RED login and consent page; approve read or propose access.'
|
|
920
|
+
].forEach(function (t) { $('<li></li>').text(t).appendTo(steps); });
|
|
921
|
+
$('<div></div>').text('If another node or plugin already uses an OAuth path, the affected OAuth routes are disabled and a warning is shown below.').appendTo(help);
|
|
744
922
|
row = $('<div class="red-ui-settings-row"></div>').appendTo(body);
|
|
745
923
|
$('<input id="ai-access-oauth-enabled" type="checkbox">').appendTo(row);
|
|
746
924
|
$('<label for="ai-access-oauth-enabled"></label>').text('Enable MCP OAuth').appendTo(row);
|
|
@@ -755,13 +933,14 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
755
933
|
var id = 'ai-access-oauth-' + field[0];
|
|
756
934
|
var oauthRow = $('<div class="red-ui-settings-row stacked"></div>').appendTo(body);
|
|
757
935
|
$('<label></label>').attr('for', id).text(field[1]).appendTo(oauthRow);
|
|
758
|
-
$('<
|
|
936
|
+
var line = $('<div class="ai-access-duration"></div>').appendTo(oauthRow);
|
|
937
|
+
$('<input type="number" step="1">').attr('id', id).attr('min', field[0] === 'refresh-max' ? 0 : 1).appendTo(line).on('change', scheduleAutoSave);
|
|
759
938
|
if (!field[2]) return;
|
|
760
939
|
var select = $('<select></select>').attr('id', id + '-unit').attr('aria-label', field[1] + ' unit')
|
|
761
|
-
.appendTo(
|
|
940
|
+
.appendTo(line).on('change', scheduleAutoSave);
|
|
762
941
|
field[2].forEach(function (u) { $('<option></option>').attr('value', u[0]).text(u[2]).appendTo(select); });
|
|
763
942
|
});
|
|
764
|
-
$('<
|
|
943
|
+
$('<div id="ai-access-oauth-route-warning" class="form-tips ai-access-oauth-warning" role="status" style="display:none"></div>').appendTo(body);
|
|
765
944
|
$('<h4></h4>').text('Registered OAuth clients').appendTo(body);
|
|
766
945
|
$('<div id="ai-access-oauth-clients" aria-live="polite"></div>').appendTo(body);
|
|
767
946
|
Object.keys(FIELD_IDS).forEach(function (key) {
|
|
@@ -806,8 +985,8 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
806
985
|
},
|
|
807
986
|
});
|
|
808
987
|
})();
|
|
809
|
-
`}a(
|
|
810
|
-
`)}a(
|
|
988
|
+
`}a(Kc,"editorSidebarScriptPartB");function Vc(){return Jc()+Kc()}a(Vc,"editorSidebarScriptBody");function mr(){let e=JSON.stringify(zc());return[`/* @com55/node-red-ai-access editor sidebar v=${Kt} */`,"(function(){"," if (typeof document === 'undefined') return;"," if (!document.getElementById('com55-ai-access-sidebar-css')) {"," var style = document.createElement('style');"," style.id = 'com55-ai-access-sidebar-css';",` style.textContent = ${e};`," document.head.appendChild(style);"," }","})();",Vc()].join(`
|
|
989
|
+
`)}a(mr,"renderEditorSidebarJs");function hi(e=Kt){return`<script>
|
|
811
990
|
/**
|
|
812
991
|
* Thin loader for the AI Access editor sidebar.
|
|
813
992
|
* Logic lives at GET /mcp/editor/ai-access-sidebar.js (Cache-Control: no-store)
|
|
@@ -841,8 +1020,8 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
841
1020
|
(document.head || document.documentElement).appendChild(s);
|
|
842
1021
|
})();
|
|
843
1022
|
</script>
|
|
844
|
-
`}a(
|
|
845
|
-
\u2026 (${o} bytes total; use ?format=full for complete text) \u2026`},"trimText");return{...e,keyFields:e.keyFields.map(r=>r.kind==="text"?{...r,value:n(r.value)}:r),properties:e.properties.map(r=>r.kind==="text"?{...r,value:n(r.value)}:r)}}a(
|
|
1023
|
+
`}a(hi,"renderEditorSidebarLoaderHtml");K();Ne();function gi(e){return Fe[e]??"Request failed"}a(gi,"safeErrorText");function Xc(e,t){let n=O(t,"rest");if(!n)return!1;let r=n.body.code,o={error:gi(r),code:r};return typeof n.body.hint=="string"&&(o.hint=n.body.hint),typeof n.body.expectedRev=="string"&&(o.expectedRev=n.body.expectedRev),typeof n.body.currentRev=="string"&&(o.currentRev=n.body.currentRev),e.setHeader("X-Content-Type-Options","nosniff"),e.status(n.statusCode).json(o),!0}a(Xc,"sendMappedFlowError");function yi(e){return async(t,n)=>{try{n.json(await pe(e))}catch(r){if(Xc(n,r))return;n.setHeader("X-Content-Type-Options","nosniff"),n.status(502).json({error:gi("upstream_error"),code:"upstream_error"})}}}a(yi,"listFlowsHandler");ae();K();Pe();Ne();function Vt(e){return e==null||e===""?{ok:!0,format:"summary"}:e==="summary"||e==="full"?{ok:!0,format:e}:{ok:!1}}a(Vt,"resolveDiffFormat");function Yc(e,t=2){if(e.length===0)return{lines:e,truncated:!1};let n=new Array(e.length).fill(!1);for(let i=0;i<e.length;i+=1){let d=e[i];if(!d||d.op==="equal")continue;let l=Math.max(0,i-t),c=Math.min(e.length-1,i+t);for(let u=l;u<=c;u+=1)n[u]=!0}if(!n.some(Boolean)){let i=Math.min(e.length,t*2+1);for(let d=0;d<i;d+=1)n[d]=!0}let r=[],o=!1,s=0;for(;s<e.length;){if(n[s]){let l=e[s];l&&r.push(l),s+=1;continue}let i=s;for(;i<e.length&&!n[i];)i+=1;let d=i-s;o=!0,r.push({op:"equal",text:`\u2026 (${d} unchanged line${d===1?"":"s"}) \u2026`}),s=i}return{lines:r,truncated:o}}a(Yc,"compactLineDiff");function Qc(e,t){let n=e.before??"",r=e.after??"",o=Buffer.byteLength(n,"utf8"),s=Buffer.byteLength(r,"utf8");if(t==="full")return{...e,before:n,after:r,beforeBytes:o,afterBytes:s};let i=Yc(e.lines),d={key:e.key,kind:"text",beforeBytes:o,afterBytes:s,lines:i.lines};return i.truncated&&(d.truncated=!0),d}a(Qc,"formatTextChange");function Zc(e,t){return e.map(n=>n.kind==="text"?Qc(n,t):n)}a(Zc,"formatPropertyChanges");function wi(e,t){if(t==="full")return e;let n=a(r=>{if(typeof r!="string")return r;let o=Buffer.byteLength(r,"utf8");return o<=4e3?r:`${r.slice(0,500)}
|
|
1024
|
+
\u2026 (${o} bytes total; use ?format=full for complete text) \u2026`},"trimText");return{...e,keyFields:e.keyFields.map(r=>r.kind==="text"?{...r,value:n(r.value)}:r),properties:e.properties.map(r=>r.kind==="text"?{...r,value:n(r.value)}:r)}}a(wi,"formatPresenceText");function bi(e,t){return{summary:e.summary,added:e.added.map(n=>wi(n,t)),removed:e.removed.map(n=>wi(n,t)),modified:e.modified.map(n=>({...n,changes:Zc(n.changes,t)}))}}a(bi,"formatSections");function Xt(e,t="summary"){let n=bi(e,t),r={...e,...n,format:t};return e.vsCurrent&&(r.vsCurrent=bi(e.vsCurrent,t)),r}a(Xt,"applyDiffFormat");z();ae();fr();function Yt(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}a(Yt,"escapeForDocs");var vi="https://YOUR-NODE-RED/mcp/rpc";function eu(e,t,n,r=vi){return`## Also available (prefer when your client can send headers)
|
|
846
1025
|
|
|
847
1026
|
**Prefer MCP or REST** whenever the AI client can send custom headers and JSON
|
|
848
1027
|
bodies \u2014 no URL length limits and no chunked drafts. Use this **GET link
|
|
@@ -911,7 +1090,7 @@ curl -sS -X POST "${r}" \\
|
|
|
911
1090
|
-H "Accept: application/json, text/event-stream" \\
|
|
912
1091
|
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
|
|
913
1092
|
\`\`\`
|
|
914
|
-
`}a(
|
|
1093
|
+
`}a(eu,"docsAlsoAvailable");function tu(e,t){return`# Node-RED AI Access \u2014 self-contained GET link (get_link)${e}
|
|
915
1094
|
|
|
916
1095
|
**Git mental model:** a proposal is like a pull request. Human approve = merge;
|
|
917
1096
|
reject = close; withdraw = you close your own PR. If \`rev\`/\`expectedRev\` does
|
|
@@ -986,7 +1165,7 @@ fields at the top level** (no \`ok\` / \`proposal\` wrapper). Read
|
|
|
986
1165
|
|
|
987
1166
|
Lean by default (no \`payload\` / \`baseFlow\`). Pass
|
|
988
1167
|
\`?include=payload,baseFlow\` or \`?include_payload=true\` to include them.
|
|
989
|
-
`}a(
|
|
1168
|
+
`}a(tu,"docsIntro");function nu(){return"## Step-by-step workflow\n\n1. `GET ./status` \u2014 confirm the link works; note `caller.expiresAt` /\n `caller.minutesRemaining`.\n2. `GET ./flows` \u2014 list tabs + current `rev`.\n3. **Layered read (prefer this before loading whole tabs):**\n - `GET ./flows/<id>?summary=1` \u2014 node list with `id`/`type`/`name`/`wires` +\n per-node `bytes` (no field contents).\n - `GET ./search?q=<text>&flow=<id>` \u2014 hits with node id, field, line numbers,\n short snippet (`truncated:true` when the result cap is hit).\n - `GET ./nodes/<id>` \u2014 one full node (`flowId` included when known).\n - `GET ./nodes/<id>/field/<name>?lines=A-B` \u2014 one field (optional 1-based\n line range) plus `totalLines` and `bytes`.\n4. Prefer a **targeted text replace** for small edits inside long string fields\n (`template`, `func`, \u2026) \u2014 unique `find` (or `occurrence`) keeps the diff short:\n\n```\nGET ./propose?flow=<flowId>&rev=<rev>&node=<nodeId>&field=<prop>&find=<text>&replace=<text>&occurrence=<n>&title=<text>\n```\n\n5. Or set whole properties (optional `title` \u226480 chars; `summary` is a title fallback):\n\n```\nGET ./propose?flow=<flowId>&rev=<rev>&node=<nodeId>&set.<prop>=<urlencoded>&title=<text>\n```\n\n6. Or multi-node / add / remove / `replace_text` / full tab via base64url JSON patch:\n\n```\nGET ./propose?flow=<flowId>&rev=<rev>&patch=<base64url-json>&title=<text>\n```\n\n7. Poll `GET ./proposals` / `./proposals/<id>` until approved, rejected, or\n withdrawn. Every token can list/read **all** proposals; each item has\n `mine:true|false`. Filter with `?mine=true` (own) or `?mine=false` (others'),\n `?status=pending,superseded` (comma-separated or repeated), `?flowId=`;\n paginate with `?limit=` (1\u2013200) / `?offset=` (response includes `total`,\n `offset`, `hasMore`, and `nextOffset` when more remain). Invalid filters\n return `400 invalid_filter` naming the parameter. Cite `displayName` +\n `reviewUrl` when updating the user.\n8. Optional: `GET ./proposals/<id>/diff` (default `format=summary` compact\n line hunks + byte sizes; `format=full` for complete text) against the\n **base snapshot** stored at submit. Added/removed nodes are presence blocks\n with a summary sentence; pending+stale may include `vsCurrent`.\n9. Withdraw your own pending proposal:\n `GET ./proposals/<id>/withdraw?confirm=1&reason=<text>` (status becomes `withdrawn`,\n not `rejected`). Only the link that created a proposal can withdraw it;\n others get `403 proposal_not_owned`.\n\n**Recommended edit loop:** summary \u2192 search \u2192 read a line range \u2192\n`replace_text` \u2192 propose.\n\nOn submit and on read of a pending proposal, the response may include\n`conflictingProposals` (same-rev or overlapping pending work; list omits this) with\n`overlappingNodeIds` / `overlappingFields` when detectable \u2014 treat like a\ngit conflict warning; prefer one pending change per flow when possible.\n\n## Node types and palette (read-only)\n\nBefore proposing unfamiliar nodes:\n\n1. `GET ./node-types?q=<text>` \u2014 list installed palette types (filter optional;\n results capped; may include `truncated` / `total`).\n2. `GET ./node-types/<type>` \u2014 detail (`defaults` as JSON, `inputs`/`outputs`,\n `requiredConfigNodeTypes`, help sections). Pass `?help=0` to omit help.\n Types are **case-sensitive** (`Debug` \u2192 did you mean `debug`?).\n3. `GET ./palette/search?q=<text>` \u2014 search the public Node-RED catalogue.\n **Recommend only** \u2014 ask the user to install via **Manage palette**, then\n recheck `./node-types`. This API **never installs** packages.\n Core `node-red` types are **not** in the catalogue \u2014 use `./node-types`.\n\nSubmit/propose with an unknown `node.type` returns `400 unknown_node_type`\nwith `nearest` suggestions \u2014 call node-types / palette search and ask the user\nto install the module, then retry.\n"}a(nu,"docsWorkflow");function ru(){return'## Exact URLs for this token (relative)\n\n| Purpose | Method + path |\n| --- | --- |\n| These docs | `GET ./` (this URL) |\n| Status | `GET ./status` |\n| List flows | `GET ./flows` |\n| One flow | `GET ./flows/<id>` |\n| Flow summary (no field bodies) | `GET ./flows/<id>?summary=1` |\n| One node | `GET ./nodes/<id>` |\n| One field (optional lines) | `GET ./nodes/<id>/field/<name>?lines=A-B` |\n| Text search | `GET ./search?q=&flow=` |\n| List node types | `GET ./node-types?q=` |\n| One node type | `GET ./node-types/<type>` (`?help=0` omits help) |\n| Palette search (recommend only) | `GET ./palette/search?q=` |\n| All proposals (`?mine=` / `?status=` / `?flowId=` / `?limit=` / `?offset=`) | `GET ./proposals` |\n| One proposal | `GET ./proposals/<id>` |\n| Proposal diff | `GET ./proposals/<id>/diff` |\n| Withdraw own pending | `GET ./proposals/<id>/withdraw?confirm=1&reason=` |\n| Propose (query) | `GET ./propose?\u2026` |\n| Draft start | `GET ./draft/new?flow=&rev=&node=&field=&title=` |\n| Draft chunk | `GET ./draft/<draftId>/append?seq=N&data=<base64url>` |\n| Draft submit | `GET ./draft/<draftId>/submit?sha256=<hex>&title=` |\n\nThere is **no** `POST ./links` (or any create-link) under this URL \u2014 GET links\n**cannot mint tokens**.\n\nUnknown paths under this link return JSON `{ "code": "not_found" }` (never HTML,\nand the token is never echoed). Attempted mint paths (`./links`) return\n`403 link_create_not_supported`.\n'}a(ru,"docsUrlTable");function ou(){return`## Propose examples
|
|
990
1169
|
|
|
991
1170
|
**Recommended for small edits to long fields** \u2014 \`replace_text\` (find must be
|
|
992
1171
|
**unique** unless you pass \`occurrence\` as a 1-based index). Creates a normal
|
|
@@ -1064,11 +1243,11 @@ GET ./propose?flow=tab1&rev=abc123&patch=<base64url>
|
|
|
1064
1243
|
Node-level proposes create a normal \`replace_flow\` proposal; owners should
|
|
1065
1244
|
approve with deployment type **\`nodes\`** (plugin default) so only changed
|
|
1066
1245
|
nodes deploy.
|
|
1067
|
-
`}a(
|
|
1246
|
+
`}a(ou,"docsProposeExamples");function su(){return`## Chunked drafts (long HTML / large fields)
|
|
1068
1247
|
|
|
1069
1248
|
When the GET URL would exceed **${Lt} bytes**, do not
|
|
1070
1249
|
stuff everything into \`propose\`. Use drafts (expire after **30 minutes**,
|
|
1071
|
-
max **${
|
|
1250
|
+
max **${j}** decoded bytes):
|
|
1072
1251
|
|
|
1073
1252
|
### Recommended chunk size
|
|
1074
1253
|
|
|
@@ -1113,7 +1292,7 @@ Or a full \`replace_flow\` document. JSON-mode drafts **may add nodes**, so
|
|
|
1113
1292
|
\`draft/new\` does not require an existing \`node\`/\`field\`. Use JSON mode for
|
|
1114
1293
|
structural changes; use field-mode only when you need to replace one whole
|
|
1115
1294
|
string field that is too large for a single propose URL.
|
|
1116
|
-
`}a(
|
|
1295
|
+
`}a(su,"docsDrafts");function iu(){return`## Last resort: content larger than chunked drafts
|
|
1117
1296
|
|
|
1118
1297
|
If the field is still too large for chunked drafts (decoded size over the
|
|
1119
1298
|
proposal cap), use this fallback only after drafts fail:
|
|
@@ -1127,9 +1306,9 @@ proposal cap), use this fallback only after drafts fail:
|
|
|
1127
1306
|
**Trade-off:** pasted content is **not** reviewed through the proposal diff.
|
|
1128
1307
|
Prefer \`replace_text\` or chunked drafts whenever they fit so the human can
|
|
1129
1308
|
still see a readable change on \`/mcp\`.
|
|
1130
|
-
`}a(
|
|
1309
|
+
`}a(iu,"docsLastResortPaste");function au(){return`## Size cap and REST/MCP nodes payload shapes
|
|
1131
1310
|
|
|
1132
|
-
The proposal JSON cap is **${
|
|
1311
|
+
The proposal JSON cap is **${j} bytes**
|
|
1133
1312
|
(\`MAX_PROPOSAL_JSON_BYTES\` / ~1.5MB) on **every** channel: GET propose/drafts,
|
|
1134
1313
|
REST \`POST /proposals\`, and MCP \`submit_proposal\`. Oversized bodies return
|
|
1135
1314
|
\`413 payload_too_large\` (never truncated).
|
|
@@ -1173,7 +1352,7 @@ Example (canonical):
|
|
|
1173
1352
|
**Alternative (also accepted):** top-level \`nodes\` array as an alias for
|
|
1174
1353
|
\`payload.nodes\` (same op shapes). Full-tab replace remains supported via
|
|
1175
1354
|
\`payload.flow\` (from \`GET ./flows/<id>\` / REST \`GET /flows/{id}\`).
|
|
1176
|
-
`}a(
|
|
1355
|
+
`}a(au,"docsRestNodesPayload");function du(){return`## Known issues
|
|
1177
1356
|
|
|
1178
1357
|
- **Rate limits are per token (and per IP).** Each access-link token has its own
|
|
1179
1358
|
token bucket. Several tokens can each use the full per-token limit at the same
|
|
@@ -1181,20 +1360,20 @@ Example (canonical):
|
|
|
1181
1360
|
- **\`RateLimit-*\` headers describe the per-token bucket** (default
|
|
1182
1361
|
\`RateLimit-Policy: 300;w=60\`) on token requests; requests without a token
|
|
1183
1362
|
report the per-IP bucket (\`600;w=60\`). Both are enforced.
|
|
1184
|
-
`}a(
|
|
1363
|
+
`}a(du,"docsKnownIssues");function lu(){return`## Error codes (JSON unless docs)
|
|
1185
1364
|
|
|
1186
|
-
${
|
|
1365
|
+
${ci()}
|
|
1187
1366
|
|
|
1188
1367
|
All JSON responses use \`Cache-Control: no-store\`. Never put secrets in query
|
|
1189
1368
|
strings you will paste publicly beyond this temporary link.
|
|
1190
|
-
`}a(
|
|
1191
|
-
`)}a(wi,"renderGetLinkDocs");var Li=require("crypto");q();G();Ne();Ge();Ae();G();function gr(e){return{error:e instanceof Error&&e.message.trim()?e.message:"Internal server error",code:"internal_error"}}a(gr,"internalErrorBody");function su(e,t){e.headersSent||e.status(500).json(gr(t))}a(su,"sendInternalError");function F(e,t){if(e.headersSent)return;let n=N(t);if(n){e.status(n.statusCode).json(n.body);return}su(e,t)}a(F,"sendCaughtRouteError");function P(e){return(t,n,r)=>{Promise.resolve().then(()=>e(t,n,r)).catch(o=>{if(n.headersSent){r(o instanceof Error?o:new Error(String(o)));return}F(n,o)})}}a(P,"asyncRoute");var wr=require("crypto");q();ae();var M=class extends Error{static{a(this,"DraftValidationError")}statusCode=400;code;constructor(t,n="draft_invalid"){super(t),this.name="DraftValidationError",this.code=n}},ct=class extends Error{static{a(this,"DraftNotFoundError")}statusCode=404;code="draft_not_found";constructor(t){super(`Draft '${t}' was not found or has expired`),this.name="DraftNotFoundError"}},ut=class extends Error{static{a(this,"DraftAlreadySubmittedError")}statusCode=409;code="draft_already_submitted";proposalId;constructor(t,n){super(`Draft '${t}' was already submitted as proposal '${n}'`),this.name="DraftAlreadySubmittedError",this.proposalId=n}},iu=/^[A-Za-z0-9_-]+={0,2}$/;function bi(e){if(!iu.test(e)||e.replace(/=+$/,"").length===0)throw new M("data must be non-empty base64url (A-Z, a-z, 0-9, -, _)","invalid_chunk_data");let t;try{t=Buffer.from(e,"base64url")}catch{throw new M("data must be non-empty base64url (A-Z, a-z, 0-9, -, _)","invalid_chunk_data")}if(t.byteLength===0&&e.length>0)throw new M("data must be non-empty base64url (A-Z, a-z, 0-9, -, _)","invalid_chunk_data");return t}a(bi,"assertValidBase64UrlChunk");var yr=class{static{a(this,"DraftStore")}drafts=new Map;submitted=new Map;now;randomId;maxBytes;ttlMs;constructor(t={}){this.now=t.now??(()=>Date.now()),this.randomId=t.randomId??(()=>(0,wr.randomBytes)(12).toString("hex")),this.maxBytes=t.maxBytes??$,this.ttlMs=t.ttlMs??bs}purgeExpired(t){for(let[n,r]of this.drafts)r.expiresAtMs<=t&&this.drafts.delete(n);for(let[n,r]of this.submitted)r.expiresAtMs<=t&&this.submitted.delete(n)}create(t){let n=this.now();this.purgeExpired(n);let r=this.randomId(),o={id:r,linkId:t.linkId,flowId:t.flowId,expectedRev:t.expectedRev,mode:t.mode,chunks:new Map,createdAtMs:n,expiresAtMs:n+this.ttlMs,totalBytes:0};return t.nodeId!==void 0&&(o.nodeId=t.nodeId),t.field!==void 0&&(o.field=t.field),t.targets!==void 0&&(o.targets=t.targets),t.summary!==void 0&&(o.summary=t.summary),t.title!==void 0&&(o.title=t.title),this.drafts.set(r,o),o}getForLink(t,n){let r=this.now();this.purgeExpired(r);let o=this.submitted.get(t);if(o?.linkId===n)throw new ut(t,o.proposalId);let s=this.drafts.get(t);if(s?.linkId!==n)throw new ct(t);if(s.expiresAtMs<=r)throw new ct(t);return s}markSubmitted(t,n,r){let o=this.now();this.drafts.delete(t),this.submitted.set(t,{linkId:n,proposalId:r,expiresAtMs:o+this.ttlMs})}append(t,n,r,o){if(!Number.isInteger(r)||r<0)throw new M("seq must be a non-negative integer","invalid_seq");if(typeof o!="string")throw new M("data must be a base64url string","invalid_chunk");let s=this.getForLink(t,n),i=s.chunks.get(r);if(i!==void 0){if(i!==o)throw new M(`Chunk seq ${r} already stored with different data`,"chunk_conflict")}else{let u=bi(o),p=s.totalBytes+u.byteLength;if(p>this.maxBytes)throw new M(`Draft exceeds limit of ${this.maxBytes} bytes (decoded)`,"payload_too_large");s.chunks.set(r,o),s.totalBytes=p}let d=[...s.chunks.keys()].sort((u,p)=>u-p),l=[],c=d.at(-1);if(c!==void 0)for(let u=0;u<=c;u+=1)s.chunks.has(u)||l.push(u);return{draft:s,receivedSeqs:d,missingSeqs:l,totalBytes:s.totalBytes,maxBytes:this.maxBytes}}getMaxBytes(){return this.maxBytes}assembleAndVerify(t,n,r){let o=this.getForLink(t,n);if(o.chunks.size===0)throw new M("Draft has no chunks","draft_empty");let i=[...o.chunks.keys()].sort((m,h)=>m-h).at(-1);if(i===void 0)throw new M("Draft has no chunks","draft_empty");let d=[];for(let m=0;m<=i;m+=1)o.chunks.has(m)||d.push(m);if(d.length>0)throw new M(`Missing chunk seqs: ${d.join(",")}`,"missing_chunks");let l=[];for(let m=0;m<=i;m+=1){let h=o.chunks.get(m);if(h===void 0)throw new M(`Missing chunk seqs: ${m}`,"missing_chunks");l.push(h)}let c=l.map(m=>bi(m)),u=Buffer.concat(c);if(u.byteLength>this.maxBytes)throw new M(`Assembled draft exceeds limit of ${this.maxBytes} bytes`,"payload_too_large");let p=r.trim().toLowerCase();if(!/^[0-9a-f]{64}$/.test(p))throw new M("invalid_sha256: expected 64 hex chars","invalid_sha256");if((0,wr.createHash)("sha256").update(u).digest("hex")!==p)throw new M("sha256 does not match assembled draft content","draft_hash_mismatch");return{draft:o,assembled:u.toString("utf8")}}size(){return this.purgeExpired(this.now()),this.drafts.size}clear(){this.drafts.clear(),this.submitted.clear()}};cr();tt();ae();var Ke=dn(require("express-rate-limit"),1);var jo=6e4,vi=600,ki=300;function Do(e,t){return typeof e=="number"&&Number.isFinite(e)&&e>0?Math.max(1,Math.floor(e)):t}a(Do,"positiveInt");function $e(e){if(!e)return{windowMs:jo,maxPerIp:vi,maxPerToken:ki};let t=e;return{windowMs:Do(t.windowMs??t.rateLimitWindowMs,jo),maxPerIp:Do(t.maxPerIp??t.rateLimitMaxPerIp,vi),maxPerToken:Do(t.maxPerToken??t.rateLimitMaxPerToken,ki)}}a($e,"resolveRateLimitConfig");function au(e,t){let r=e.rateLimit?.resetTime;return r instanceof Date&&!Number.isNaN(r.getTime())?Math.max(1,Math.ceil((r.getTime()-Date.now())/1e3)):Math.max(1,Math.ceil(t/1e3))}a(au,"retryAfterSeconds");function Ai(e,t,n=jo){let r=au(e,n);t.setHeader("Retry-After",String(r)),t.setHeader("Cache-Control","no-store"),t.status(429).json({error:"Too many requests. Slow down and retry.",code:"rate_limited",retryAfter:r,hint:"Wait retryAfter seconds, then retry. Prefer summary/search reads over full field downloads."})}a(Ai,"sendRateLimited");function du(e){return(t,n,r,o)=>{Ai(t,n,e)}}a(du,"rateLimitedHandler");function br(e){return`ip:${(0,Ke.ipKeyGenerator)(e.ip??"unknown")}`}a(br,"clientIpKey");function vr(e){let t=e.params.token;if(typeof t=="string"&&t.length>0)return`token:${t.slice(0,48)}`;let n=e.get("authorization")??e.headers.authorization;if(typeof n=="string"){let s=/^Bearer\s+(\S+)/i.exec(n.trim())?.[1];if(s&&(s.startsWith("aia_")||s.length>=24))return`token:${s.slice(0,48)}`}let r=e;return r.accessLink?.id?`token:link:${r.accessLink.id}`:null}a(vr,"accessTokenRateLimitKey");function xi(e,t,n,r){let o={windowMs:t,max:e,standardHeaders:!0,legacyHeaders:!1,keyGenerator:n,handler:du(t)};return r&&(o.skip=r),(0,Ke.default)(o)}a(xi,"createLimiter");function Ho(e){let t=$e(e);return xi(t.maxPerIp,t.windowMs,br)}a(Ho,"createAiAccessRateLimiter");function lu(e){let t=$e(e);return xi(t.maxPerToken,t.windowMs,n=>vr(n)??br(n),n=>vr(n)==null)}a(lu,"createAiAccessTokenRateLimiter");function Si(e){let t=$e(e),n=Ho(t),r=lu(t);return{ip:n,token:r,stack:[n,r],config:t}}a(Si,"createAiAccessDualRateLimiters");function Ti(e){let t=$e(e()).windowMs,n=$e(e()).maxPerIp,r=$e(e()).maxPerToken,o=new Ke.MemoryStore,s=new Ke.MemoryStore,i=a(()=>{let h=$e(e());if(h.windowMs!==t||h.maxPerIp!==n||h.maxPerToken!==r){let g=h.windowMs!==t;t=h.windowMs,n=h.maxPerIp,r=h.maxPerToken,o.resetAll(),s.resetAll(),g&&(o.init?.({windowMs:t}),s.init?.({windowMs:t}))}return h},"refreshWindow"),d=new WeakSet,l=a(h=>{if(d.has(h))return;d.add(h);let g=h.setHeader.bind(h);h.setHeader=((v,A)=>(v.toLowerCase()==="ratelimit-policy"&&typeof A=="string"&&(A=A.replace(/;w=\d+/,`;w=${Math.ceil(t/1e3)}`)),g(v,A)))},"useLiveWindowPolicy"),c=a((h,g)=>(0,Ke.default)({store:g,windowMs:t,limit:a(()=>{let v=$e(e());return h?v.maxPerToken:v.maxPerIp},"limit"),standardHeaders:!0,legacyHeaders:!1,keyGenerator:h?v=>vr(v)??br(v):br,skip:a(v=>h&&vr(v)==null,"skip"),handler:a((v,A,E)=>{Ai(v,A,$e(e()).windowMs)},"handler")}),"make"),u=c(!1,o),p=c(!0,s);return{ip:a((h,g,v)=>(i(),l(g),u(h,g,v)),"ip"),token:a((h,g,v)=>(i(),l(g),p(h,g,v)),"token")}}a(Ti,"createLiveAiAccessDualRateLimiters");_e();function I(e){e.setHeader("Cache-Control","no-store"),e.setHeader("Pragma","no-cache")}a(I,"setNoStore");function Uo(e){let t=e.originalUrl||e.url||"",n=so(t);n!==t&&(Object.defineProperty(e,"originalUrl",{value:n,writable:!0,configurable:!0}),typeof e.url=="string"&&Object.defineProperty(e,"url",{value:so(e.url),writable:!0,configurable:!0}))}a(Uo,"redactTokenFromRequestUrl");function _i(e,t){return(n,r,o)=>{I(r);let s=n.originalUrl||n.url||"";if(n.getLinkUrlBytes=Buffer.byteLength(s,"utf8"),Uo(n),!ye(e.settings)){r.status(403).json({error:"AI Access features are disabled. Enable adminAuth, or set aiAccess.allowWithoutAdminAuth to true for local-only use.",code:"ai_features_disabled",hint:"Ask the Node-RED owner to enable AI Access."});return}let i=n.params.token;if(typeof i!="string"||i.length===0){r.setHeader("WWW-Authenticate",K),r.status(401).json({error:le,code:Re,hint:"Ask the user for a new self-contained access link URL."});return}let d=i;try{d=decodeURIComponent(i)}catch{}let l=t.validateResult(d);if(l.status==="expired"){gn(n,r,e.settings.httpAdminRoot,l.expiresAt);return}if(l.status!=="active"){let c=d.length<24||d.startsWith("aia_")&&d.length<40?"The access-link URL may be truncated \u2014 ask the user for the full /mcp/a/<token> link.":"Ask the user for a new self-contained access link URL (it may have expired, or Node-RED may have restarted).";r.setHeader("WWW-Authenticate",K),r.status(401).json({error:et,code:hn,hint:c});return}if(l.link.kind!=="get_link"){yn(r,"get_link",l.link.kind);return}n.accessLink=l.link,n.getLinkToken=d,o()}}a(_i,"requireGetLinkToken");function Yt(e,t){let n=e.getLinkUrlBytes;return(typeof n=="number"?n:Buffer.byteLength(e.originalUrl||e.url||"","utf8"))>Lt?(I(t),t.status(414).json({error:`Request URL exceeds ${Lt} bytes`,code:"url_too_long",hint:"Use chunked drafts: GET \u2026/draft/new then \u2026/draft/<id>/append?seq=&data= then \u2026/draft/<id>/submit?sha256=, or use POST /mcp/api/v1/proposals with Authorization: Bearer <token>.",limitBytes:Lt}),!0):!1}a(Yt,"rejectOversizedGetUrl");function L(e,t,n,r,o,s){I(e),e.status(t).json({error:n,code:r,...o?{hint:o}:{},...s})}a(L,"jsonError");function Pi(){return(e,t)=>{Uo(e),I(t),t.status(404).json({error:"Not found",code:"not_found"})}}a(Pi,"getLinkNotFoundHandler");var cu="link_create_not_supported";function Ri(){return(e,t)=>{Uo(e),I(t),t.status(403).json({error:"GET access links cannot create tokens",code:cu,hint:"Ask an admin with * permission to mint tokens via POST /mcp/api/v1/links on the /mcp Access links page. This get_link URL cannot create, list, or revoke tokens."})}}a(Ri,"getLinkMintNotSupportedHandler");function R(e,t){let n=e[t];if(typeof n=="string"&&n.length>0)return n;if(Array.isArray(n)&&typeof n[0]=="string"&&n[0].length>0)return n[0]}a(R,"queryString");function Se(e){switch(e){case"draft_not_found":return"Start a new draft with GET \u2026/draft/new.";case"draft_already_submitted":return"This draft already created a proposal; poll that proposal or start a new draft.";case"chunk_conflict":return"Re-send the same data for that seq, or start a new draft.";case"payload_too_large":return"Shrink the content; totalBytes counts decoded bytes against maxBytes.";case"invalid_seq":return"seq must be a non-negative integer (0, 1, 2, \u2026).";case"invalid_chunk":case"invalid_chunk_data":return"data must be base64url of the raw chunk bytes (A-Z, a-z, 0-9, -, _).";case"missing_chunks":return"Upload every seq from 0 through max before submit.";case"invalid_sha256":return"sha256 must be exactly 64 hexadecimal characters.";case"draft_hash_mismatch":return"sha256 must be the hex digest of the assembled UTF-8 bytes (before base64url).";case"draft_empty":return"Append at least one chunk before submit.";case"node_not_found":return"Call GET \u2026/flows/<id> and use a node id from that response (or use mode=json with op:add).";case"field_not_found":return"Use a property that exists on the node, or mode=json to add/replace nodes.";case"missing_draft_params":return"Example: \u2026/draft/new?flow=tab1&rev=abc&node=n1&field=template";case"version_mismatch":return"Re-read the flow and start a new draft with the current rev.";case"protected_property":return"Omit id, type, and z from set props. wires are allowed when targets exist in the flow.";case"invalid_wires":return"wires must list existing node ids (or add those nodes in the same patch).";default:return}}a(Se,"draftErrorHint");async function uu(e,t,n){let r;try{r=await e.getFlow(t)}catch(s){let i=N(s);if(i)return{code:i.body.code,message:i.body.error};throw s}let o=[...Array.isArray(r.flow.nodes)?r.flow.nodes:[],...Array.isArray(r.flow.configs)?r.flow.configs:[]];for(let s of n){let i=o.find(d=>d.id===s.nodeId);if(!i)return{code:"node_not_found",message:`Node '${s.nodeId}' was not found in flow '${t}'`};if(!(s.field in i))return{code:"field_not_found",message:`Field '${s.field}' was not found on node '${s.nodeId}'`}}return null}a(uu,"validateFieldDraftTargets");function pu(e,t,n,r){if(r)try{let o=Sn(r);if(!Array.isArray(o)||o.length===0)throw new Error("targets must be a non-empty array");return{ok:!0,mode:"field",targets:o.map(i=>{if(!i||typeof i!="object")throw new Error("bad target");let d=i,l=typeof d.nodeId=="string"?d.nodeId:typeof d.node=="string"?d.node:"",c=typeof d.field=="string"?d.field:"";if(!l||!c)throw new Error("bad target");return{nodeId:l,field:c}})}}catch{return{ok:!1,status:400,error:"targets must be base64url JSON array of {nodeId,field}",code:"invalid_targets"}}if(n==="json"||!e&&!t)return{ok:!0,mode:"json"};if(!e||!t){let o=Se("missing_draft_params");return{ok:!1,status:400,error:"node and field are required for field drafts (or pass mode=json)",code:"missing_draft_params",...o?{hint:o}:{}}}return{ok:!0,mode:"field"}}a(pu,"planDraftMode");async function fu(e,t,n,r){try{let o=await uu(t,n,r);if(o){let s=o.code==="node_not_found"||o.code==="field_not_found"?404:400;return L(e,s,o.message,o.code,Se(o.code)),!0}}catch(o){let s=N(o);return s?(L(e,s.statusCode,s.body.error,s.body.code,Se(s.body.code)),!0):(F(e,o),!0)}return!1}a(fu,"rejectInvalidFieldTargets");function Ci(e,t){return async(n,r)=>{if(Yt(n,r))return;I(r);let o=R(n.query,"flow"),s=R(n.query,"rev"),i=R(n.query,"node"),d=R(n.query,"field"),l=R(n.query,"summary"),c=R(n.query,"title")??l,u=R(n.query,"mode"),p=R(n.query,"targets");if(!o||!s){L(r,400,"flow and rev query parameters are required","missing_draft_params",Se("missing_draft_params"));return}if(!n.accessLink){L(r,401,"Access link required","access_link_missing");return}try{let h=await t.getFlow(o);if(h.rev!==s){r.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:s,currentRev:h.rev,hint:Se("version_mismatch")});return}}catch(h){let g=N(h);if(g){L(r,g.statusCode,g.body.error,g.body.code,g.body.code==="flow_not_found"?ie("flow_not_found","get")??"Use an id from GET ./flows":Se(g.body.code));return}F(r,h);return}let f=pu(i,d,u,p);if(!f.ok){L(r,f.status,f.error,f.code,f.hint);return}if(f.mode==="field"){let h=f.targets&&f.targets.length>0?f.targets:i&&d?[{nodeId:i,field:d}]:[];if(h.length>0&&await fu(r,t,o,h))return}let m=e.create({linkId:n.accessLink.id,flowId:o,expectedRev:s,mode:f.mode,...i?{nodeId:i}:{},...d?{field:d}:{},...f.targets?{targets:f.targets}:{},...l?{summary:l}:{},...c?{title:c}:{}});r.status(201).json({ok:!0,draftId:m.id,mode:m.mode,expiresAt:new Date(m.expiresAtMs).toISOString(),maxBytes:$,hint:"Upload base64url chunks (~4\u20135 KB raw each; base64 adds ~33%; keep URL under ~8 KB) with GET \u2026/draft/<draftId>/append?seq=N&data=\u2026 then submit with sha256."})}}a(Ci,"getLinkDraftNewHandler");function Ei(e){return(t,n)=>{if(Yt(t,n))return;I(n);let r=t.params.draftId,o=R(t.query,"seq"),s=R(t.query,"data");if(typeof r!="string"||!r){L(n,400,"Missing draft id","missing_draft_id");return}if(!t.accessLink){L(n,401,"Access link required","access_link_missing");return}if(o===void 0||s===void 0){L(n,400,"seq and data query parameters are required","missing_chunk_params","data must be base64url of the raw chunk bytes.");return}let i=Number(o);try{let d=e.append(r,t.accessLink.id,i,s);n.json({ok:!0,draftId:r,receivedSeqs:d.receivedSeqs,missingSeqs:d.missingSeqs,totalBytes:d.totalBytes,maxBytes:d.maxBytes,hint:d.missingSeqs.length===0?"No gaps so far; call submit when all chunks are uploaded":`Upload missing seqs: ${d.missingSeqs.join(",")}`})}catch(d){if(d instanceof ct||d instanceof ut||d instanceof M){L(n,d.statusCode,d.message,d.code,Se(d.code),d instanceof ut?{proposalId:d.proposalId}:void 0);return}F(n,d);return}}}a(Ei,"getLinkDraftAppendHandler");function mu(e,t){let n=[];if(e.targets&&e.targets.length>0){let r;try{r=JSON.parse(t)}catch{let o=e.targets[0];if(e.targets.length===1&&o)return[{nodeId:o.nodeId,set:{[o.field]:t}}];throw new M("Multi-target draft body must be JSON","invalid_draft_body")}if(Array.isArray(r)){for(let o of r){let s=o;if(!s.nodeId||!s.field)throw new M("Invalid multi-target entry","invalid_draft_body");n.push({nodeId:s.nodeId,set:{[s.field]:s.value}})}return n}if(r&&typeof r=="object"){let o=r;for(let s of e.targets){let i=`${s.nodeId}.${s.field}`;if(!(i in o)&&!(s.field in o))throw new M(`Missing value for ${i} in draft JSON`,"invalid_draft_body");n.push({nodeId:s.nodeId,set:{[s.field]:i in o?o[i]:o[s.field]}})}return n}throw new M("Invalid multi-target draft JSON","invalid_draft_body")}if(e.nodeId&&e.field)return[{nodeId:e.nodeId,set:{[e.field]:t}}];throw new M("Field draft missing node/field","invalid_draft")}a(mu,"editsFromFieldDraft");function Ii(e,t,n,r,o,s){return async(i,d)=>{if(Yt(i,d))return;I(d);let l=i.params.draftId,c=R(i.query,"sha256"),u=R(i.query,"title");if(typeof l!="string"||!l){L(d,400,"Missing draft id","missing_draft_id");return}if(!i.accessLink){L(d,401,"Access link required","access_link_missing");return}if(!c){L(d,400,"sha256 query parameter is required","missing_sha256","Pass sha256 hex of the assembled UTF-8 bytes (before base64url).");return}try{let{draft:p,assembled:f}=e.assembleAndVerify(l,i.accessLink.id,c),m=p.flowId,h,g=p.expectedRev;if(p.mode==="field"){let y=mu(p,f),w=await xn(r,p.flowId,y);if(w.expectedRev!==p.expectedRev){d.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:p.expectedRev,currentRev:w.expectedRev,draftDeleted:!1,hint:"Draft was kept. Re-read the flow and start a new draft with the current rev (or retry if you refresh rev)."});return}h=w.flow,g=w.expectedRev}else{let y=Tn(JSON.parse(f),p.flowId);if(m=y.flowId,y.kind==="replace_flow")h={...y.flow,id:y.flow.id||m,type:y.flow.type??"tab",nodes:y.flow.nodes??[]};else{let w=await at(r,m,y.ops,{expectedRev:p.expectedRev,...s?{nodeTypes:s}:{}});if(w.expectedRev!==p.expectedRev){d.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:p.expectedRev,currentRev:w.expectedRev,draftDeleted:!1,hint:"Draft was kept. Re-read the flow and start a new draft with the current rev."});return}h=w.flow,g=w.expectedRev}}let v=u??p.title??p.summary,A=U(i,o),E=oe(i.query),D=await ze(t,n,i.accessLink,{type:"replace_flow",expectedRev:g,payload:{flowId:m,flow:h},...v?{title:v}:{}},r,A,s);e.markSubmitted(l,i.accessLink.id,D.id);let O={...ce(D,{...A,include:E}),mine:!0},te=z(t,D.expectedRev,{flowId:D.payload.flowId}),me=Ee(t,t.get(D.id),i.accessLink.id);d.status(201).json({ok:!0,proposal:O,sha256:(0,Li.createHash)("sha256").update(Buffer.from(f,"utf8")).digest("hex"),...Ie(me),...X(te),hint:`Waiting for human approval. Tell the user about "${O.displayName}" (${O.reviewUrl}). Poll GET \u2026/proposals/<id> for status.`})}catch(p){if(p instanceof ut){L(d,p.statusCode,p.message,p.code,Se(p.code),{proposalId:p.proposalId});return}if(p instanceof ct||p instanceof M){L(d,p.statusCode,p.message,p.code,Se(p.code));return}if(p instanceof SyntaxError){L(d,400,"Assembled draft is not valid JSON","invalid_draft_body");return}let f=N(p);if(f){d.status(f.statusCode).json({...f.body,...Se(f.body.code)?{hint:Se(f.body.code)}:{}});return}F(d,p);return}}}a(Ii,"getLinkDraftSubmitHandler");G();Ne();mr();Ge();Ae();cr();var hu={node_already_exists:"Choose a new node id, or use op:set / op:remove on the existing node.",protected_property:"Omit id, type, and z from set props. wires are allowed when targets exist in the flow.",invalid_wires:"wires must list existing node ids (or add those nodes in the same patch).",invalid_patch:"Pass patch as base64url JSON with nodes[{op,\u2026.}] or type/flow for replace_flow.",invalid_patch_json:"Pass patch as base64url JSON with nodes[{op,\u2026.}] or type/flow for replace_flow.",missing_edits:"Provide set.<prop>=value, find+replace+field, or patch nodes with at least one op.",missing_node_id:"Pass node=<id> with set.<prop>= or find/replace, or include id on each patch.nodes entry.",missing_flow_id:"Pass flow=<id> from GET \u2026/flows.",missing_field:"Pass field=<prop> (e.g. template, func) with find= and replace=.",missing_find:"Pass a non-empty find= string (URL-encoded).",invalid_occurrence:"occurrence must be a 1-based index within the match count.",invalid_replace:"Pass replace= as a string (URL-encoded; empty string is allowed to delete the find text).",conflicting_edits:"Use either find/replace/field or set.<prop>=, not both in one propose URL.",invalid_node_type:"node.type must be a string (not a number or object). Use the palette type name exactly.",invalid_node:"add op needs node as a JSON object: { id, type, \u2026 } \u2014 not a string or array.",z_mismatch:"Each node.z must match the flow id (tab id) you are editing."};function Bo(e,t){return ie(e,"get",{cause:t})??hu[e]??Rn(e)}a(Bo,"proposeErrorHint");function gu(e,t,n){e.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:t,currentRev:n,hint:Bo("version_mismatch")})}a(gu,"sendVersionMismatch");async function yu(e,t,n,r,o){let s=Tn(Sn(t),n),i=s.flowId;if(s.kind==="replace_flow")return{ok:!0,builtFlowId:i,builtFlow:{...s.flow,id:s.flow.id||i,type:s.flow.type??"tab",nodes:s.flow.nodes??[]},expectedRev:r};let d=await at(e,i,s.ops,{expectedRev:r,...o?{nodeTypes:o}:{}});return d.expectedRev!==r?{ok:!1,kind:"version_mismatch",expectedRev:r,currentRev:d.expectedRev}:{ok:!0,builtFlowId:i,builtFlow:d.flow,expectedRev:d.expectedRev}}a(yu,"buildFromPatch");async function wu(e,t,n,r,o){let s=await xn(e,t,[{nodeId:n,set:r}]);return s.expectedRev!==o?{ok:!1,kind:"version_mismatch",expectedRev:o,currentRev:s.expectedRev}:{ok:!0,builtFlowId:t,builtFlow:s.flow,expectedRev:s.expectedRev}}a(wu,"buildFromSetProps");async function bu(e,t,n,r,o,s,i,d){let l=await at(e,t,[{op:"replace_text",nodeId:n,field:r,find:o,replace:s,...i!==void 0?{occurrence:i}:{}}]);return l.expectedRev!==d?{ok:!1,kind:"version_mismatch",expectedRev:d,currentRev:l.expectedRev}:{ok:!0,builtFlowId:t,builtFlow:l.flow,expectedRev:l.expectedRev}}a(bu,"buildFromReplaceText");function vu(e){let t=Object.prototype.hasOwnProperty.call(e.query,"replace");return{flowId:R(e.query,"flow"),rev:R(e.query,"rev"),nodeId:R(e.query,"node"),patchEncoded:R(e.query,"patch"),setProps:Ro(e.query),findText:R(e.query,"find"),replaceText:t?R(e.query,"replace")??"":void 0,fieldName:R(e.query,"field"),occurrenceRaw:R(e.query,"occurrence")}}a(vu,"readProposeQuery");function Te(e,t,n){L(e,400,n,t,Bo(t))}a(Te,"jsonProposeError");function ku(e,t){if(t===void 0||t==="")return{ok:!0};let n=Number(t);return Number.isFinite(n)?{ok:!0,occurrence:Math.trunc(n)}:(Te(e,"invalid_occurrence","occurrence must be a positive integer"),{ok:!1})}a(ku,"parseOccurrenceQuery");async function Au(e,t,n,r,o){if(n.patchEncoded)return yu(t,n.patchEncoded,n.flowId,r,o);if(n.findText!==void 0||n.replaceText!==void 0||n.fieldName!==void 0){if(Object.keys(n.setProps).length>0)return Te(e,"conflicting_edits","Cannot combine replace_text (find/replace/field) with set.<prop> in one request"),null;if(!n.flowId)return Te(e,"missing_flow_id","flow query parameter is required"),null;if(!n.nodeId)return Te(e,"missing_node_id","node query parameter is required"),null;if(!n.fieldName)return Te(e,"missing_field","field query parameter is required for replace_text"),null;if(n.findText===void 0||n.findText.length===0)return Te(e,"missing_find","find query parameter must be a non-empty string"),null;if(n.replaceText===void 0)return Te(e,"invalid_replace","replace query parameter is required (may be empty)"),null;let i=ku(e,n.occurrenceRaw);return i.ok?bu(t,n.flowId,n.nodeId,n.fieldName,n.findText,n.replaceText,i.occurrence,r):null}return n.flowId?n.nodeId?Object.keys(n.setProps).length===0?(Te(e,"missing_edits","At least one set.<prop>=value is required"),null):wu(t,n.flowId,n.nodeId,n.setProps,r):(Te(e,"missing_node_id","node query parameter is required (or pass patch=)"),null):(Te(e,"missing_flow_id","flow query parameter is required"),null)}a(Au,"buildProposeFromQuery");function Ni(e,t,n,r,o){return async(s,i)=>{if(Yt(s,i))return;I(i);let d=vu(s),l=R(s.query,"summary"),c=R(s.query,"title")??l;if(!d.rev){Te(i,"missing_expected_rev","rev query parameter is required");return}if(!s.accessLink){L(i,401,"Access link required","access_link_missing");return}try{let u=await Au(i,n,d,d.rev,o);if(!u)return;if(!u.ok){gu(i,u.expectedRev,u.currentRev);return}let p=U(s,r),f=oe(s.query),m=await ze(e,t,s.accessLink,{type:"replace_flow",expectedRev:u.expectedRev,payload:{flowId:u.builtFlowId,flow:u.builtFlow},...c?{title:c}:{}},n,p,o),h={...ce(m,{...p,include:f}),mine:!0},g=z(e,m.expectedRev,{flowId:m.payload.flowId}),v=Ee(e,e.get(m.id),s.accessLink.id),A=m.warnings;i.status(201).json({ok:!0,proposal:h,...l?{summary:l}:{},...Ie(v),...X(g),...A&&A.length>0?{warnings:A}:{},hint:`Waiting for human approval. Tell the user about "${h.displayName}" (${h.reviewUrl}). Poll GET \u2026/proposals/<id> for status.${A&&A.length>0?` Soft warnings (${A.length}) are non-blocking \u2014 configure later if needed.`:""}`})}catch(u){let p=N(u,"get");if(p){let f=Bo(p.body.code,p.body.cause);i.status(p.statusCode).json({...p.body,...f?{hint:f}:{}});return}F(i,u)}}}a(Ni,"getLinkProposeHandler");G();Ne();G();var xu=50,Fi=40,Su=20;function Tu(e){return Buffer.byteLength(JSON.stringify(e),"utf8")}a(Tu,"nodeBytes");function _u(e){let t={id:e.id,type:e.type,bytes:Tu(e)};return typeof e.name=="string"&&(t.name=e.name),"wires"in e&&(t.wires=e.wires),t}a(_u,"summarizeNode");async function kr(e,t){let{rev:n,flow:r}=await xe(e,t),o=r,s=Array.isArray(o.nodes)?o.nodes.map(_u):[],i={rev:n,flowId:o.id||t,nodes:s,nodeCount:s.length};return typeof o.label=="string"&&(i.label=o.label),typeof o.type=="string"&&(i.type=o.type),i}a(kr,"getFlowSummary");function Oi(e){let t=[];for(let n of e){if(!n||typeof n!="object"||Array.isArray(n))continue;let r=n;(r.type==="tab"||r.type==="subflow"||Array.isArray(r.nodes))&&t.push(r)}return t}a(Oi,"collectTabs");async function Mi(e,t,n){if(!t)throw new b("Missing node id","missing_node_id");if(n){let{rev:d,flow:l}=await xe(e,n),c=l,u=Array.isArray(c.nodes)?c.nodes:[],p=Array.isArray(c.configs)?c.configs:[],f=[...u,...p].find(m=>m.id===t);if(!f)throw new b("Node was not found","node_not_found",{statusCode:404});return{rev:d,flowId:n,node:f}}let{rev:r,flows:o}=await pe(e),s,i=null;for(let d of o){if(!d||typeof d!="object"||Array.isArray(d))continue;let l=d;if(l.id===t&&typeof l.type=="string"&&l.type!=="tab"&&l.type!=="subflow"){s=l,i=typeof l.z=="string"&&l.z?l.z:null;break}}if(!s)for(let d of Oi(o)){let l=d.id;if(l)try{let u=(await xe(e,l)).flow,f=[...u.nodes??[],...u.configs??[]].find(m=>m.id===t);if(f){s=f,i=l;break}}catch{}}if(!s)throw new b("Node was not found","node_not_found",{statusCode:404});return{rev:r,flowId:i,node:s}}a(Mi,"findNodeInFlows");async function Ar(e,t,n){let r=await Mi(e,t,n);return{rev:r.rev,flowId:r.flowId,node:r.node}}a(Ar,"getNodeById");function Pu(e){if(e===void 0||e==="")return;let t=/^(\d+)(?:-(\d+))?$/.exec(e.trim());if(!t)throw new b("lines must be A or A-B (1-based inclusive; end >= start)","invalid_line_range");let n=Number(t[1]),r=t[2]!==void 0?Number(t[2]):n;if(!Number.isInteger(n)||!Number.isInteger(r)||n<1)throw new b("lines must be A or A-B (1-based inclusive; end >= start)","invalid_line_range");if(r<n)throw new b("lines must be A or A-B (1-based inclusive; end >= start)","invalid_line_range");return{start:n,end:r}}a(Pu,"parseLineRange");async function xr(e,t,n,r={}){if(!n)throw new b("Missing field name","missing_field");let o=await Mi(e,t,r.flowId);if(!(n in o.node))throw new b("Field was not found on the node","field_not_found");let s=o.node[n];if(typeof s!="string")throw new b("Field is not a string","field_not_string");let i=s.split(`
|
|
1192
|
-
`),d=i.length,l=Buffer.byteLength(s,"utf8"),c=
|
|
1193
|
-
`);return{rev:o.rev,flowId:o.flowId,nodeId:t,field:n,value:f,totalLines:d,bytes:l,lineRange:{start:c.start,end:Math.min(c.end,d)}}}a(
|
|
1369
|
+
`}a(lu,"docsErrorsAndAlso");function ki(e){let{link:t,openapiPath:n,restApiBase:r,mcpRpcPath:o,publicOrigin:s}=e,i=Yt(t.expiresAt),d=t.label?` (${Yt(t.label)})`:"",l=Yt(n),c=Yt(r),u=Yt(o);return[tu(d,i),eu(c,l,u,s?Yt(`${s}${o}`):vi),nu(),ru(),ou(),au(),su(),iu(),du(),lu()].join(`
|
|
1370
|
+
`)}a(ki,"renderGetLinkDocs");var Ii=require("crypto");z();K();Ne();Je();Ae();K();function hr(e){return{error:e instanceof Error&&e.message.trim()?e.message:"Internal server error",code:"internal_error"}}a(hr,"internalErrorBody");function cu(e,t){e.headersSent||e.status(500).json(hr(t))}a(cu,"sendInternalError");function $(e,t){if(e.headersSent)return;let n=O(t);if(n){e.status(n.statusCode).json(n.body);return}cu(e,t)}a($,"sendCaughtRouteError");function P(e){return(t,n,r)=>{Promise.resolve().then(()=>e(t,n,r)).catch(o=>{if(n.headersSent){r(o instanceof Error?o:new Error(String(o)));return}$(n,o)})}}a(P,"asyncRoute");var yr=require("crypto");z();ae();var D=class extends Error{static{a(this,"DraftValidationError")}statusCode=400;code;constructor(t,n="draft_invalid"){super(t),this.name="DraftValidationError",this.code=n}},ft=class extends Error{static{a(this,"DraftNotFoundError")}statusCode=404;code="draft_not_found";constructor(t){super(`Draft '${t}' was not found or has expired`),this.name="DraftNotFoundError"}},mt=class extends Error{static{a(this,"DraftAlreadySubmittedError")}statusCode=409;code="draft_already_submitted";proposalId;constructor(t,n){super(`Draft '${t}' was already submitted as proposal '${n}'`),this.name="DraftAlreadySubmittedError",this.proposalId=n}},uu=/^[A-Za-z0-9_-]+={0,2}$/;function Ai(e){if(!uu.test(e)||e.replace(/=+$/,"").length===0)throw new D("data must be non-empty base64url (A-Z, a-z, 0-9, -, _)","invalid_chunk_data");let t;try{t=Buffer.from(e,"base64url")}catch{throw new D("data must be non-empty base64url (A-Z, a-z, 0-9, -, _)","invalid_chunk_data")}if(t.byteLength===0&&e.length>0)throw new D("data must be non-empty base64url (A-Z, a-z, 0-9, -, _)","invalid_chunk_data");return t}a(Ai,"assertValidBase64UrlChunk");var gr=class{static{a(this,"DraftStore")}drafts=new Map;submitted=new Map;now;randomId;maxBytes;ttlMs;constructor(t={}){this.now=t.now??(()=>Date.now()),this.randomId=t.randomId??(()=>(0,yr.randomBytes)(12).toString("hex")),this.maxBytes=t.maxBytes??j,this.ttlMs=t.ttlMs??ks}purgeExpired(t){for(let[n,r]of this.drafts)r.expiresAtMs<=t&&this.drafts.delete(n);for(let[n,r]of this.submitted)r.expiresAtMs<=t&&this.submitted.delete(n)}create(t){let n=this.now();this.purgeExpired(n);let r=this.randomId(),o={id:r,linkId:t.linkId,flowId:t.flowId,expectedRev:t.expectedRev,mode:t.mode,chunks:new Map,createdAtMs:n,expiresAtMs:n+this.ttlMs,totalBytes:0};return t.nodeId!==void 0&&(o.nodeId=t.nodeId),t.field!==void 0&&(o.field=t.field),t.targets!==void 0&&(o.targets=t.targets),t.summary!==void 0&&(o.summary=t.summary),t.title!==void 0&&(o.title=t.title),this.drafts.set(r,o),o}getForLink(t,n){let r=this.now();this.purgeExpired(r);let o=this.submitted.get(t);if(o?.linkId===n)throw new mt(t,o.proposalId);let s=this.drafts.get(t);if(s?.linkId!==n)throw new ft(t);if(s.expiresAtMs<=r)throw new ft(t);return s}markSubmitted(t,n,r){let o=this.now();this.drafts.delete(t),this.submitted.set(t,{linkId:n,proposalId:r,expiresAtMs:o+this.ttlMs})}append(t,n,r,o){if(!Number.isInteger(r)||r<0)throw new D("seq must be a non-negative integer","invalid_seq");if(typeof o!="string")throw new D("data must be a base64url string","invalid_chunk");let s=this.getForLink(t,n),i=s.chunks.get(r);if(i!==void 0){if(i!==o)throw new D(`Chunk seq ${r} already stored with different data`,"chunk_conflict")}else{let u=Ai(o),p=s.totalBytes+u.byteLength;if(p>this.maxBytes)throw new D(`Draft exceeds limit of ${this.maxBytes} bytes (decoded)`,"payload_too_large");s.chunks.set(r,o),s.totalBytes=p}let d=[...s.chunks.keys()].sort((u,p)=>u-p),l=[],c=d.at(-1);if(c!==void 0)for(let u=0;u<=c;u+=1)s.chunks.has(u)||l.push(u);return{draft:s,receivedSeqs:d,missingSeqs:l,totalBytes:s.totalBytes,maxBytes:this.maxBytes}}getMaxBytes(){return this.maxBytes}assembleAndVerify(t,n,r){let o=this.getForLink(t,n);if(o.chunks.size===0)throw new D("Draft has no chunks","draft_empty");let i=[...o.chunks.keys()].sort((m,h)=>m-h).at(-1);if(i===void 0)throw new D("Draft has no chunks","draft_empty");let d=[];for(let m=0;m<=i;m+=1)o.chunks.has(m)||d.push(m);if(d.length>0)throw new D(`Missing chunk seqs: ${d.join(",")}`,"missing_chunks");let l=[];for(let m=0;m<=i;m+=1){let h=o.chunks.get(m);if(h===void 0)throw new D(`Missing chunk seqs: ${m}`,"missing_chunks");l.push(h)}let c=l.map(m=>Ai(m)),u=Buffer.concat(c);if(u.byteLength>this.maxBytes)throw new D(`Assembled draft exceeds limit of ${this.maxBytes} bytes`,"payload_too_large");let p=r.trim().toLowerCase();if(!/^[0-9a-f]{64}$/.test(p))throw new D("invalid_sha256: expected 64 hex chars","invalid_sha256");if((0,yr.createHash)("sha256").update(u).digest("hex")!==p)throw new D("sha256 does not match assembled draft content","draft_hash_mismatch");return{draft:o,assembled:u.toString("utf8")}}size(){return this.purgeExpired(this.now()),this.drafts.size}clear(){this.drafts.clear(),this.submitted.clear()}};lr();ot();ae();var Ye=an(require("express-rate-limit"),1);var $o=6e4,xi=600,Si=300;function Oo(e,t){return typeof e=="number"&&Number.isFinite(e)&&e>0?Math.max(1,Math.floor(e)):t}a(Oo,"positiveInt");function Me(e){if(!e)return{windowMs:$o,maxPerIp:xi,maxPerToken:Si};let t=e;return{windowMs:Oo(t.windowMs??t.rateLimitWindowMs,$o),maxPerIp:Oo(t.maxPerIp??t.rateLimitMaxPerIp,xi),maxPerToken:Oo(t.maxPerToken??t.rateLimitMaxPerToken,Si)}}a(Me,"resolveRateLimitConfig");function pu(e,t){let r=e.rateLimit?.resetTime;return r instanceof Date&&!Number.isNaN(r.getTime())?Math.max(1,Math.ceil((r.getTime()-Date.now())/1e3)):Math.max(1,Math.ceil(t/1e3))}a(pu,"retryAfterSeconds");function Ti(e,t,n=$o){let r=pu(e,n);t.setHeader("Retry-After",String(r)),t.setHeader("Cache-Control","no-store"),t.status(429).json({error:"Too many requests. Slow down and retry.",code:"rate_limited",retryAfter:r,hint:"Wait retryAfter seconds, then retry. Prefer summary/search reads over full field downloads."})}a(Ti,"sendRateLimited");function fu(e){return(t,n,r,o)=>{Ti(t,n,e)}}a(fu,"rateLimitedHandler");function wr(e){return`ip:${(0,Ye.ipKeyGenerator)(e.ip??"unknown")}`}a(wr,"clientIpKey");function br(e){let t=e.params.token;if(typeof t=="string"&&t.length>0)return`token:${t.slice(0,48)}`;let n=e.get("authorization")??e.headers.authorization;if(typeof n=="string"){let s=/^Bearer\s+(\S+)/i.exec(n.trim())?.[1];if(s&&(s.startsWith("aia_")||s.length>=24))return`token:${s.slice(0,48)}`}let r=e;return r.accessLink?.id?`token:link:${r.accessLink.id}`:null}a(br,"accessTokenRateLimitKey");function _i(e,t,n,r){let o={windowMs:t,max:e,standardHeaders:!0,legacyHeaders:!1,keyGenerator:n,handler:fu(t)};return r&&(o.skip=r),(0,Ye.default)(o)}a(_i,"createLimiter");function Mo(e){let t=Me(e);return _i(t.maxPerIp,t.windowMs,wr)}a(Mo,"createAiAccessRateLimiter");function mu(e){let t=Me(e);return _i(t.maxPerToken,t.windowMs,n=>br(n)??wr(n),n=>br(n)==null)}a(mu,"createAiAccessTokenRateLimiter");function Pi(e){let t=Me(e),n=Mo(t),r=mu(t);return{ip:n,token:r,stack:[n,r],config:t}}a(Pi,"createAiAccessDualRateLimiters");function Ri(e){let t=Me(e()).windowMs,n=Me(e()).maxPerIp,r=Me(e()).maxPerToken,o=new Ye.MemoryStore,s=new Ye.MemoryStore,i=a(()=>{let h=Me(e());if(h.windowMs!==t||h.maxPerIp!==n||h.maxPerToken!==r){let g=h.windowMs!==t;t=h.windowMs,n=h.maxPerIp,r=h.maxPerToken,o.resetAll(),s.resetAll(),g&&(o.init?.({windowMs:t}),s.init?.({windowMs:t}))}return h},"refreshWindow"),d=new WeakSet,l=a(h=>{if(d.has(h))return;d.add(h);let g=h.setHeader.bind(h);h.setHeader=((A,S)=>(A.toLowerCase()==="ratelimit-policy"&&typeof S=="string"&&(S=S.replace(/;w=\d+/,`;w=${Math.ceil(t/1e3)}`)),g(A,S)))},"useLiveWindowPolicy"),c=a((h,g)=>(0,Ye.default)({store:g,windowMs:t,limit:a(()=>{let A=Me(e());return h?A.maxPerToken:A.maxPerIp},"limit"),standardHeaders:!0,legacyHeaders:!1,keyGenerator:h?A=>br(A)??wr(A):wr,skip:a(A=>h&&br(A)==null,"skip"),handler:a((A,S,E)=>{Ti(A,S,Me(e()).windowMs)},"handler")}),"make"),u=c(!1,o),p=c(!0,s);return{ip:a((h,g,A)=>(i(),l(g),u(h,g,A)),"ip"),token:a((h,g,A)=>(i(),l(g),p(h,g,A)),"token")}}a(Ri,"createLiveAiAccessDualRateLimiters");ye();function F(e){e.setHeader("Cache-Control","no-store"),e.setHeader("Pragma","no-cache")}a(F,"setNoStore");function Do(e){let t=e.originalUrl||e.url||"",n=no(t);n!==t&&(Object.defineProperty(e,"originalUrl",{value:n,writable:!0,configurable:!0}),typeof e.url=="string"&&Object.defineProperty(e,"url",{value:no(e.url),writable:!0,configurable:!0}))}a(Do,"redactTokenFromRequestUrl");function Ci(e,t){return(n,r,o)=>{F(r);let s=n.originalUrl||n.url||"";if(n.getLinkUrlBytes=Buffer.byteLength(s,"utf8"),Do(n),!ge(e.settings)){r.status(403).json({error:"AI Access features are disabled. Enable adminAuth, or set aiAccess.allowWithoutAdminAuth to true for local-only use.",code:"ai_features_disabled",hint:"Ask the Node-RED owner to enable AI Access."});return}let i=n.params.token;if(typeof i!="string"||i.length===0){r.setHeader("WWW-Authenticate",Y),r.status(401).json({error:le,code:Re,hint:"Ask the user for a new self-contained access link URL."});return}let d=i;try{d=decodeURIComponent(i)}catch{}let l=t.validateResult(d);if(l.status==="expired"){hn(n,r,e.settings.httpAdminRoot,l.expiresAt);return}if(l.status!=="active"){let c=d.length<24||d.startsWith("aia_")&&d.length<40?"The access-link URL may be truncated \u2014 ask the user for the full /mcp/a/<token> link.":"Ask the user for a new self-contained access link URL (it may have expired, or Node-RED may have restarted).";r.setHeader("WWW-Authenticate",Y),r.status(401).json({error:rt,code:mn,hint:c});return}if(l.link.kind!=="get_link"){gn(r,"get_link",l.link.kind);return}n.accessLink=l.link,n.getLinkToken=d,o()}}a(Ci,"requireGetLinkToken");function Qt(e,t){let n=e.getLinkUrlBytes;return(typeof n=="number"?n:Buffer.byteLength(e.originalUrl||e.url||"","utf8"))>Lt?(F(t),t.status(414).json({error:`Request URL exceeds ${Lt} bytes`,code:"url_too_long",hint:"Use chunked drafts: GET \u2026/draft/new then \u2026/draft/<id>/append?seq=&data= then \u2026/draft/<id>/submit?sha256=, or use POST /mcp/api/v1/proposals with Authorization: Bearer <token>.",limitBytes:Lt}),!0):!1}a(Qt,"rejectOversizedGetUrl");function L(e,t,n,r,o,s){F(e),e.status(t).json({error:n,code:r,...o?{hint:o}:{},...s})}a(L,"jsonError");function Li(){return(e,t)=>{Do(e),F(t),t.status(404).json({error:"Not found",code:"not_found"})}}a(Li,"getLinkNotFoundHandler");var hu="link_create_not_supported";function Ei(){return(e,t)=>{Do(e),F(t),t.status(403).json({error:"GET access links cannot create tokens",code:hu,hint:"Ask an admin with * permission to mint tokens via POST /mcp/api/v1/links on the /mcp Access links page. This get_link URL cannot create, list, or revoke tokens."})}}a(Ei,"getLinkMintNotSupportedHandler");function R(e,t){let n=e[t];if(typeof n=="string"&&n.length>0)return n;if(Array.isArray(n)&&typeof n[0]=="string"&&n[0].length>0)return n[0]}a(R,"queryString");function Se(e){switch(e){case"draft_not_found":return"Start a new draft with GET \u2026/draft/new.";case"draft_already_submitted":return"This draft already created a proposal; poll that proposal or start a new draft.";case"chunk_conflict":return"Re-send the same data for that seq, or start a new draft.";case"payload_too_large":return"Shrink the content; totalBytes counts decoded bytes against maxBytes.";case"invalid_seq":return"seq must be a non-negative integer (0, 1, 2, \u2026).";case"invalid_chunk":case"invalid_chunk_data":return"data must be base64url of the raw chunk bytes (A-Z, a-z, 0-9, -, _).";case"missing_chunks":return"Upload every seq from 0 through max before submit.";case"invalid_sha256":return"sha256 must be exactly 64 hexadecimal characters.";case"draft_hash_mismatch":return"sha256 must be the hex digest of the assembled UTF-8 bytes (before base64url).";case"draft_empty":return"Append at least one chunk before submit.";case"node_not_found":return"Call GET \u2026/flows/<id> and use a node id from that response (or use mode=json with op:add).";case"field_not_found":return"Use a property that exists on the node, or mode=json to add/replace nodes.";case"missing_draft_params":return"Example: \u2026/draft/new?flow=tab1&rev=abc&node=n1&field=template";case"version_mismatch":return"Re-read the flow and start a new draft with the current rev.";case"protected_property":return"Omit id, type, and z from set props. wires are allowed when targets exist in the flow.";case"invalid_wires":return"wires must list existing node ids (or add those nodes in the same patch).";default:return}}a(Se,"draftErrorHint");async function gu(e,t,n){let r;try{r=await e.getFlow(t)}catch(s){let i=O(s);if(i)return{code:i.body.code,message:i.body.error};throw s}let o=[...Array.isArray(r.flow.nodes)?r.flow.nodes:[],...Array.isArray(r.flow.configs)?r.flow.configs:[]];for(let s of n){let i=o.find(d=>d.id===s.nodeId);if(!i)return{code:"node_not_found",message:`Node '${s.nodeId}' was not found in flow '${t}'`};if(!(s.field in i))return{code:"field_not_found",message:`Field '${s.field}' was not found on node '${s.nodeId}'`}}return null}a(gu,"validateFieldDraftTargets");function yu(e,t,n,r){if(r)try{let o=xn(r);if(!Array.isArray(o)||o.length===0)throw new Error("targets must be a non-empty array");return{ok:!0,mode:"field",targets:o.map(i=>{if(!i||typeof i!="object")throw new Error("bad target");let d=i,l=typeof d.nodeId=="string"?d.nodeId:typeof d.node=="string"?d.node:"",c=typeof d.field=="string"?d.field:"";if(!l||!c)throw new Error("bad target");return{nodeId:l,field:c}})}}catch{return{ok:!1,status:400,error:"targets must be base64url JSON array of {nodeId,field}",code:"invalid_targets"}}if(n==="json"||!e&&!t)return{ok:!0,mode:"json"};if(!e||!t){let o=Se("missing_draft_params");return{ok:!1,status:400,error:"node and field are required for field drafts (or pass mode=json)",code:"missing_draft_params",...o?{hint:o}:{}}}return{ok:!0,mode:"field"}}a(yu,"planDraftMode");async function wu(e,t,n,r){try{let o=await gu(t,n,r);if(o){let s=o.code==="node_not_found"||o.code==="field_not_found"?404:400;return L(e,s,o.message,o.code,Se(o.code)),!0}}catch(o){let s=O(o);return s?(L(e,s.statusCode,s.body.error,s.body.code,Se(s.body.code)),!0):($(e,o),!0)}return!1}a(wu,"rejectInvalidFieldTargets");function Ni(e,t){return async(n,r)=>{if(Qt(n,r))return;F(r);let o=R(n.query,"flow"),s=R(n.query,"rev"),i=R(n.query,"node"),d=R(n.query,"field"),l=R(n.query,"summary"),c=R(n.query,"title")??l,u=R(n.query,"mode"),p=R(n.query,"targets");if(!o||!s){L(r,400,"flow and rev query parameters are required","missing_draft_params",Se("missing_draft_params"));return}if(!n.accessLink){L(r,401,"Access link required","access_link_missing");return}try{let h=await t.getFlow(o);if(h.rev!==s){r.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:s,currentRev:h.rev,hint:Se("version_mismatch")});return}}catch(h){let g=O(h);if(g){L(r,g.statusCode,g.body.error,g.body.code,g.body.code==="flow_not_found"?ie("flow_not_found","get")??"Use an id from GET ./flows":Se(g.body.code));return}$(r,h);return}let f=yu(i,d,u,p);if(!f.ok){L(r,f.status,f.error,f.code,f.hint);return}if(f.mode==="field"){let h=f.targets&&f.targets.length>0?f.targets:i&&d?[{nodeId:i,field:d}]:[];if(h.length>0&&await wu(r,t,o,h))return}let m=e.create({linkId:n.accessLink.id,flowId:o,expectedRev:s,mode:f.mode,...i?{nodeId:i}:{},...d?{field:d}:{},...f.targets?{targets:f.targets}:{},...l?{summary:l}:{},...c?{title:c}:{}});r.status(201).json({ok:!0,draftId:m.id,mode:m.mode,expiresAt:new Date(m.expiresAtMs).toISOString(),maxBytes:j,hint:"Upload base64url chunks (~4\u20135 KB raw each; base64 adds ~33%; keep URL under ~8 KB) with GET \u2026/draft/<draftId>/append?seq=N&data=\u2026 then submit with sha256."})}}a(Ni,"getLinkDraftNewHandler");function Fi(e){return(t,n)=>{if(Qt(t,n))return;F(n);let r=t.params.draftId,o=R(t.query,"seq"),s=R(t.query,"data");if(typeof r!="string"||!r){L(n,400,"Missing draft id","missing_draft_id");return}if(!t.accessLink){L(n,401,"Access link required","access_link_missing");return}if(o===void 0||s===void 0){L(n,400,"seq and data query parameters are required","missing_chunk_params","data must be base64url of the raw chunk bytes.");return}let i=Number(o);try{let d=e.append(r,t.accessLink.id,i,s);n.json({ok:!0,draftId:r,receivedSeqs:d.receivedSeqs,missingSeqs:d.missingSeqs,totalBytes:d.totalBytes,maxBytes:d.maxBytes,hint:d.missingSeqs.length===0?"No gaps so far; call submit when all chunks are uploaded":`Upload missing seqs: ${d.missingSeqs.join(",")}`})}catch(d){if(d instanceof ft||d instanceof mt||d instanceof D){L(n,d.statusCode,d.message,d.code,Se(d.code),d instanceof mt?{proposalId:d.proposalId}:void 0);return}$(n,d);return}}}a(Fi,"getLinkDraftAppendHandler");function bu(e,t){let n=[];if(e.targets&&e.targets.length>0){let r;try{r=JSON.parse(t)}catch{let o=e.targets[0];if(e.targets.length===1&&o)return[{nodeId:o.nodeId,set:{[o.field]:t}}];throw new D("Multi-target draft body must be JSON","invalid_draft_body")}if(Array.isArray(r)){for(let o of r){let s=o;if(!s.nodeId||!s.field)throw new D("Invalid multi-target entry","invalid_draft_body");n.push({nodeId:s.nodeId,set:{[s.field]:s.value}})}return n}if(r&&typeof r=="object"){let o=r;for(let s of e.targets){let i=`${s.nodeId}.${s.field}`;if(!(i in o)&&!(s.field in o))throw new D(`Missing value for ${i} in draft JSON`,"invalid_draft_body");n.push({nodeId:s.nodeId,set:{[s.field]:i in o?o[i]:o[s.field]}})}return n}throw new D("Invalid multi-target draft JSON","invalid_draft_body")}if(e.nodeId&&e.field)return[{nodeId:e.nodeId,set:{[e.field]:t}}];throw new D("Field draft missing node/field","invalid_draft")}a(bu,"editsFromFieldDraft");function Oi(e,t,n,r,o,s){return async(i,d)=>{if(Qt(i,d))return;F(d);let l=i.params.draftId,c=R(i.query,"sha256"),u=R(i.query,"title");if(typeof l!="string"||!l){L(d,400,"Missing draft id","missing_draft_id");return}if(!i.accessLink){L(d,401,"Access link required","access_link_missing");return}if(!c){L(d,400,"sha256 query parameter is required","missing_sha256","Pass sha256 hex of the assembled UTF-8 bytes (before base64url).");return}try{let{draft:p,assembled:f}=e.assembleAndVerify(l,i.accessLink.id,c),m=p.flowId,h,g=p.expectedRev;if(p.mode==="field"){let v=bu(p,f),b=await An(r,p.flowId,v);if(b.expectedRev!==p.expectedRev){d.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:p.expectedRev,currentRev:b.expectedRev,draftDeleted:!1,hint:"Draft was kept. Re-read the flow and start a new draft with the current rev (or retry if you refresh rev)."});return}h=b.flow,g=b.expectedRev}else{let v=Sn(JSON.parse(f),p.flowId);if(m=v.flowId,v.kind==="replace_flow")h={...v.flow,id:v.flow.id||m,type:v.flow.type??"tab",nodes:v.flow.nodes??[]};else{let b=await ct(r,m,v.ops,{expectedRev:p.expectedRev,...s?{nodeTypes:s}:{}});if(b.expectedRev!==p.expectedRev){d.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:p.expectedRev,currentRev:b.expectedRev,draftDeleted:!1,hint:"Draft was kept. Re-read the flow and start a new draft with the current rev."});return}h=b.flow,g=b.expectedRev}}let A=u??p.title??p.summary,S=q(i,o),E=oe(i.query),I=await Ke(t,n,i.accessLink,{type:"replace_flow",expectedRev:g,payload:{flowId:m,flow:h},...A?{title:A}:{}},r,S,s);e.markSubmitted(l,i.accessLink.id,I.id);let N={...ce(I,{...S,include:E}),mine:!0},nt=V(t,I.expectedRev,{flowId:I.payload.flowId}),me=Ee(t,t.get(I.id),i.accessLink.id);d.status(201).json({ok:!0,proposal:N,sha256:(0,Ii.createHash)("sha256").update(Buffer.from(f,"utf8")).digest("hex"),...Ie(me),...Q(nt),hint:`Waiting for human approval. Tell the user about "${N.displayName}" (${N.reviewUrl}). Poll GET \u2026/proposals/<id> for status.`})}catch(p){if(p instanceof mt){L(d,p.statusCode,p.message,p.code,Se(p.code),{proposalId:p.proposalId});return}if(p instanceof ft||p instanceof D){L(d,p.statusCode,p.message,p.code,Se(p.code));return}if(p instanceof SyntaxError){L(d,400,"Assembled draft is not valid JSON","invalid_draft_body");return}let f=O(p);if(f){d.status(f.statusCode).json({...f.body,...Se(f.body.code)?{hint:Se(f.body.code)}:{}});return}$(d,p);return}}}a(Oi,"getLinkDraftSubmitHandler");K();Ne();fr();Je();Ae();lr();var vu={node_already_exists:"Choose a new node id, or use op:set / op:remove on the existing node.",protected_property:"Omit id, type, and z from set props. wires are allowed when targets exist in the flow.",invalid_wires:"wires must list existing node ids (or add those nodes in the same patch).",invalid_patch:"Pass patch as base64url JSON with nodes[{op,\u2026.}] or type/flow for replace_flow.",invalid_patch_json:"Pass patch as base64url JSON with nodes[{op,\u2026.}] or type/flow for replace_flow.",missing_edits:"Provide set.<prop>=value, find+replace+field, or patch nodes with at least one op.",missing_node_id:"Pass node=<id> with set.<prop>= or find/replace, or include id on each patch.nodes entry.",missing_flow_id:"Pass flow=<id> from GET \u2026/flows.",missing_field:"Pass field=<prop> (e.g. template, func) with find= and replace=.",missing_find:"Pass a non-empty find= string (URL-encoded).",invalid_occurrence:"occurrence must be a 1-based index within the match count.",invalid_replace:"Pass replace= as a string (URL-encoded; empty string is allowed to delete the find text).",conflicting_edits:"Use either find/replace/field or set.<prop>=, not both in one propose URL.",invalid_node_type:"node.type must be a string (not a number or object). Use the palette type name exactly.",invalid_node:"add op needs node as a JSON object: { id, type, \u2026 } \u2014 not a string or array.",z_mismatch:"Each node.z must match the flow id (tab id) you are editing."};function jo(e,t){return ie(e,"get",{cause:t})??vu[e]??Pn(e)}a(jo,"proposeErrorHint");function ku(e,t,n){e.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:t,currentRev:n,hint:jo("version_mismatch")})}a(ku,"sendVersionMismatch");async function Au(e,t,n,r,o){let s=Sn(xn(t),n),i=s.flowId;if(s.kind==="replace_flow")return{ok:!0,builtFlowId:i,builtFlow:{...s.flow,id:s.flow.id||i,type:s.flow.type??"tab",nodes:s.flow.nodes??[]},expectedRev:r};let d=await ct(e,i,s.ops,{expectedRev:r,...o?{nodeTypes:o}:{}});return d.expectedRev!==r?{ok:!1,kind:"version_mismatch",expectedRev:r,currentRev:d.expectedRev}:{ok:!0,builtFlowId:i,builtFlow:d.flow,expectedRev:d.expectedRev}}a(Au,"buildFromPatch");async function xu(e,t,n,r,o){let s=await An(e,t,[{nodeId:n,set:r}]);return s.expectedRev!==o?{ok:!1,kind:"version_mismatch",expectedRev:o,currentRev:s.expectedRev}:{ok:!0,builtFlowId:t,builtFlow:s.flow,expectedRev:s.expectedRev}}a(xu,"buildFromSetProps");async function Su(e,t,n,r,o,s,i,d){let l=await ct(e,t,[{op:"replace_text",nodeId:n,field:r,find:o,replace:s,...i!==void 0?{occurrence:i}:{}}]);return l.expectedRev!==d?{ok:!1,kind:"version_mismatch",expectedRev:d,currentRev:l.expectedRev}:{ok:!0,builtFlowId:t,builtFlow:l.flow,expectedRev:l.expectedRev}}a(Su,"buildFromReplaceText");function Tu(e){let t=Object.prototype.hasOwnProperty.call(e.query,"replace");return{flowId:R(e.query,"flow"),rev:R(e.query,"rev"),nodeId:R(e.query,"node"),patchEncoded:R(e.query,"patch"),setProps:To(e.query),findText:R(e.query,"find"),replaceText:t?R(e.query,"replace")??"":void 0,fieldName:R(e.query,"field"),occurrenceRaw:R(e.query,"occurrence")}}a(Tu,"readProposeQuery");function Te(e,t,n){L(e,400,n,t,jo(t))}a(Te,"jsonProposeError");function _u(e,t){if(t===void 0||t==="")return{ok:!0};let n=Number(t);return Number.isFinite(n)?{ok:!0,occurrence:Math.trunc(n)}:(Te(e,"invalid_occurrence","occurrence must be a positive integer"),{ok:!1})}a(_u,"parseOccurrenceQuery");async function Pu(e,t,n,r,o){if(n.patchEncoded)return Au(t,n.patchEncoded,n.flowId,r,o);if(n.findText!==void 0||n.replaceText!==void 0||n.fieldName!==void 0){if(Object.keys(n.setProps).length>0)return Te(e,"conflicting_edits","Cannot combine replace_text (find/replace/field) with set.<prop> in one request"),null;if(!n.flowId)return Te(e,"missing_flow_id","flow query parameter is required"),null;if(!n.nodeId)return Te(e,"missing_node_id","node query parameter is required"),null;if(!n.fieldName)return Te(e,"missing_field","field query parameter is required for replace_text"),null;if(n.findText===void 0||n.findText.length===0)return Te(e,"missing_find","find query parameter must be a non-empty string"),null;if(n.replaceText===void 0)return Te(e,"invalid_replace","replace query parameter is required (may be empty)"),null;let i=_u(e,n.occurrenceRaw);return i.ok?Su(t,n.flowId,n.nodeId,n.fieldName,n.findText,n.replaceText,i.occurrence,r):null}return n.flowId?n.nodeId?Object.keys(n.setProps).length===0?(Te(e,"missing_edits","At least one set.<prop>=value is required"),null):xu(t,n.flowId,n.nodeId,n.setProps,r):(Te(e,"missing_node_id","node query parameter is required (or pass patch=)"),null):(Te(e,"missing_flow_id","flow query parameter is required"),null)}a(Pu,"buildProposeFromQuery");function $i(e,t,n,r,o){return async(s,i)=>{if(Qt(s,i))return;F(i);let d=Tu(s),l=R(s.query,"summary"),c=R(s.query,"title")??l;if(!d.rev){Te(i,"missing_expected_rev","rev query parameter is required");return}if(!s.accessLink){L(i,401,"Access link required","access_link_missing");return}try{let u=await Pu(i,n,d,d.rev,o);if(!u)return;if(!u.ok){ku(i,u.expectedRev,u.currentRev);return}let p=q(s,r),f=oe(s.query),m=await Ke(e,t,s.accessLink,{type:"replace_flow",expectedRev:u.expectedRev,payload:{flowId:u.builtFlowId,flow:u.builtFlow},...c?{title:c}:{}},n,p,o),h={...ce(m,{...p,include:f}),mine:!0},g=V(e,m.expectedRev,{flowId:m.payload.flowId}),A=Ee(e,e.get(m.id),s.accessLink.id),S=m.warnings;i.status(201).json({ok:!0,proposal:h,...l?{summary:l}:{},...Ie(A),...Q(g),...S&&S.length>0?{warnings:S}:{},hint:`Waiting for human approval. Tell the user about "${h.displayName}" (${h.reviewUrl}). Poll GET \u2026/proposals/<id> for status.${S&&S.length>0?` Soft warnings (${S.length}) are non-blocking \u2014 configure later if needed.`:""}`})}catch(u){let p=O(u,"get");if(p){let f=jo(p.body.code,p.body.cause);i.status(p.statusCode).json({...p.body,...f?{hint:f}:{}});return}$(i,u)}}}a($i,"getLinkProposeHandler");K();Ne();K();var Ru=50,Mi=40,Cu=20;function Lu(e){return Buffer.byteLength(JSON.stringify(e),"utf8")}a(Lu,"nodeBytes");function Eu(e){let t={id:e.id,type:e.type,bytes:Lu(e)};return typeof e.name=="string"&&(t.name=e.name),"wires"in e&&(t.wires=e.wires),t}a(Eu,"summarizeNode");async function vr(e,t){let{rev:n,flow:r}=await xe(e,t),o=r,s=Array.isArray(o.nodes)?o.nodes.map(Eu):[],i={rev:n,flowId:o.id||t,nodes:s,nodeCount:s.length};return typeof o.label=="string"&&(i.label=o.label),typeof o.type=="string"&&(i.type=o.type),i}a(vr,"getFlowSummary");function Di(e){let t=[];for(let n of e){if(!n||typeof n!="object"||Array.isArray(n))continue;let r=n;(r.type==="tab"||r.type==="subflow"||Array.isArray(r.nodes))&&t.push(r)}return t}a(Di,"collectTabs");async function ji(e,t,n){if(!t)throw new y("Missing node id","missing_node_id");if(n){let{rev:d,flow:l}=await xe(e,n),c=l,u=Array.isArray(c.nodes)?c.nodes:[],p=Array.isArray(c.configs)?c.configs:[],f=[...u,...p].find(m=>m.id===t);if(!f)throw new y("Node was not found","node_not_found",{statusCode:404});return{rev:d,flowId:n,node:f}}let{rev:r,flows:o}=await pe(e),s,i=null;for(let d of o){if(!d||typeof d!="object"||Array.isArray(d))continue;let l=d;if(l.id===t&&typeof l.type=="string"&&l.type!=="tab"&&l.type!=="subflow"){s=l,i=typeof l.z=="string"&&l.z?l.z:null;break}}if(!s)for(let d of Di(o)){let l=d.id;if(l)try{let u=(await xe(e,l)).flow,f=[...u.nodes??[],...u.configs??[]].find(m=>m.id===t);if(f){s=f,i=l;break}}catch{}}if(!s)throw new y("Node was not found","node_not_found",{statusCode:404});return{rev:r,flowId:i,node:s}}a(ji,"findNodeInFlows");async function kr(e,t,n){let r=await ji(e,t,n);return{rev:r.rev,flowId:r.flowId,node:r.node}}a(kr,"getNodeById");function Iu(e){if(e===void 0||e==="")return;let t=/^(\d+)(?:-(\d+))?$/.exec(e.trim());if(!t)throw new y("lines must be A or A-B (1-based inclusive; end >= start)","invalid_line_range");let n=Number(t[1]),r=t[2]!==void 0?Number(t[2]):n;if(!Number.isInteger(n)||!Number.isInteger(r)||n<1)throw new y("lines must be A or A-B (1-based inclusive; end >= start)","invalid_line_range");if(r<n)throw new y("lines must be A or A-B (1-based inclusive; end >= start)","invalid_line_range");return{start:n,end:r}}a(Iu,"parseLineRange");async function Ar(e,t,n,r={}){if(!n)throw new y("Missing field name","missing_field");let o=await ji(e,t,r.flowId);if(!(n in o.node))throw new y("Field was not found on the node","field_not_found");let s=o.node[n];if(typeof s!="string")throw new y("Field is not a string","field_not_string");let i=s.split(`
|
|
1371
|
+
`),d=i.length,l=Buffer.byteLength(s,"utf8"),c=Iu(r.lines);if(!c)return{rev:o.rev,flowId:o.flowId,nodeId:t,field:n,value:s,totalLines:d,bytes:l};if(c.start>d)throw new y(`lines range is out of bounds (start ${c.start} > ${d} total lines)`,"line_range_out_of_bounds",{details:{totalLines:d}});let u=c.start-1,p=Math.min(c.end,i.length),f=i.slice(u,p).join(`
|
|
1372
|
+
`);return{rev:o.rev,flowId:o.flowId,nodeId:t,field:n,value:f,totalLines:d,bytes:l,lineRange:{start:c.start,end:Math.min(c.end,d)}}}a(Ar,"getNodeField");function Nu(e){let t=[];for(let[n,r]of Object.entries(e))n==="id"||n==="type"||n==="z"||n==="wires"||typeof r=="string"&&r.length>0&&t.push({field:n,value:r});return t}a(Nu,"stringFields");function Fu(e,t,n){let r=Math.max(0,t-Mi),o=Math.min(e.length,t+n+Mi),s=e.slice(r,o).replace(/\s+/g," ");return r>0&&(s=`\u2026${s}`),o<e.length&&(s=`${s}\u2026`),s}a(Fu,"snippetAround");function Ou(e,t){let n=1;for(let r=0;r<t&&r<e.length;r+=1)e.charCodeAt(r)===10&&(n+=1);return n}a(Ou,"lineNumberAtIndex");function Ho(e,t,n,r,o){for(let{field:s,value:i}of Nu(e)){if(r.length>=o)return!0;let d=[],l=new Set,c=-1,u=a((f,m)=>{l.has(f)||(l.add(f),d.push(f)),c<0&&(c=m)},"pushLine");if(!n.includes(`
|
|
1194
1373
|
`)){let f=i.split(`
|
|
1195
|
-
`);for(let m=0;m<f.length;m+=1){let g=(f[m]??"").indexOf(n);if(g>=0){let
|
|
1196
|
-
`)){let f=0;for(;f<=i.length;){let m=i.indexOf(n,f);if(m<0)break;u(
|
|
1197
|
-
`)}a(Hi,"splitLines");Ae();var Wi=new Set(["func","template","info","payload","code","text","format","syntax"]),Ou=new Set(["x","y","w","h","wires","g"]),Mu=new Set(["w","h","g"]);function ft(e){if(typeof e.name=="string"&&e.name.length>0)return e.name;if(typeof e.label=="string"&&e.label.length>0)return e.label}a(ft,"nodeName");function Bi(e){let t=new Map;for(let n of e.nodes??[])t.set(n.id,n);for(let n of e.configs??[])t.set(n.id,n);return t}a(Bi,"collectNodes");function Mr(e){if(e===void 0)return"undefined";if(e===null||typeof e!="object")return JSON.stringify(e);if(Array.isArray(e))return`[${e.map(Mr).join(",")}]`;let t=e;return`{${Object.keys(t).sort().map(r=>`${JSON.stringify(r)}:${Mr(t[r])}`).join(",")}}`}a(Mr,"stableStringify");function Gi(e,t){return Mr(e)===Mr(t)}a(Gi,"valuesEqual");function $r(e){return typeof e=="string"?e:null}a($r,"asText");function $u(e){return/<\/?[a-z][\s\S]*>/i.test(e.slice(0,800))}a($u,"looksLikeHtml");function Du(e,t,n){if(t!=="template")return!1;let r=typeof e.format=="string"?e.format.toLowerCase():"";return r==="html"||r==="handlebars"||r==="mustache"?!0:$u(n)}a(Du,"isHtmlTemplateField");function zi(e,t,n){let r=$r(n);if(Wi.has(t)&&r!==null){let o={key:t,kind:"text",value:r};return Du(e,t,r)&&(o.preview="html"),o}return{key:t,kind:"scalar",value:n===void 0?null:n}}a(zi,"toPresenceProperty");function ju(e,t){let n=[];for(let r of[...Object.keys(e)].sort())if(!(Mu.has(r)||r==="id"||r==="z")){if(r==="wires"){let o=Ji(e,t);n.push({key:"wires",kind:"scalar",value:o.length>0?o:e.wires??null});continue}n.push(zi(e,r,e[r]))}return n}a(ju,"nodeProperties");function Ji(e,t){let n=e.wires;if(!Array.isArray(n)||n.length===0)return[];let r=[];return n.forEach((o,s)=>{if(!Array.isArray(o))return;let i=[];for(let d of o){if(typeof d!="string"||!d)continue;let l=t.get(d);i.push({id:d,name:l?ft(l)??l.type:d})}r.push({port:s,to:i})}),r}a(Ji,"describeWiresStructured");var Hu=new Set(["func","template","code","text"]);function Uu(e,t){let n=[],r=ft(e);r&&n.push({key:"name",kind:"scalar",value:r});let o=Ji(e,t);o.length>0&&n.push({key:"wires",kind:"scalar",value:o});for(let s of Hu){if(!(s in e))continue;let i=$r(e[s]);i===null||i.length===0||n.push(zi(e,s,i))}return n}a(Uu,"buildKeyFields");function Bu(e,t,n){let r=We(e.type),o=ft(e),s=o?` "${o}"`:"",i=t?` in "${t}"`:"",d=n===1?"1 field":`${n} fields`;return`Changes ${r} node${s}${i} (${d})`}a(Bu,"buildModifiedSummary");function qu(e,t,n){let r=We(e.type),o=ft(e),s=t==="added"?"Adds":"Removes",i=t==="added"?"to":"from",d=o?` ${JSON.stringify(o)}`:"",l=n?` ${i} flow ${JSON.stringify(n)}`:"",c=`${s} ${r} node${d}${l}`;if(t==="added"&&r==="template"){let u=typeof e.field=="string"&&e.field?e.field:"";u&&(c+=`, output to msg.${u}`)}return c}a(qu,"buildPresenceSummary");function Fr(e,t,n,r){let o={id:e.id,type:e.type,summary:qu(e,t,n),keyFields:Uu(e,r),properties:ju(e,r)},s=ft(e);return s!==void 0&&(o.name=s),o}a(Fr,"toPresenceNode");function Wu(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]),r=[];for(let o of[...n].sort()){if(Ou.has(o)||o==="id"||o==="z")continue;let s=e[o],i=t[o];if(Gi(s,i))continue;let d=$r(s),l=$r(i);if(Wi.has(o)&&(d!==null||l!==null)){let c=d??"",u=l??"";r.push({key:o,kind:"text",before:c,after:u,lines:Ui(c,u)});continue}r.push({key:o,kind:"scalar",before:s===void 0?null:s,after:i===void 0?null:i})}return r}a(Wu,"compareNodes");function Gu(e,t){let n=["label","disabled","info","env"],r=[];for(let o of n)Gi(e[o],t[o])||r.push(o);return r}a(Gu,"compareTabMeta");function Or(e){return typeof e.label=="string"&&e.label.trim()?e.label.trim():""}a(Or,"flowLabelOf");function Vi(e,t){let n=Bi(t),r=Bi(e),o=Or(e)||Or(t),s=Or(t)||Or(e),i=[],d=[],l=[];for(let c of e.nodes??[])n.has(c.id)||i.push(Fr(c,"added",o,r));for(let c of e.configs??[])n.has(c.id)||i.push(Fr(c,"added",o,r));for(let c of t.nodes??[])r.has(c.id)||d.push(Fr(c,"removed",s,n));for(let c of t.configs??[])r.has(c.id)||d.push(Fr(c,"removed",s,n));for(let[c,u]of r){let p=n.get(c);if(!p)continue;let f=Wu(p,u);if(f.length>0){let m={id:c,type:u.type||p.type,summary:Bu(u,o||s,f.length),changes:f},h=ft(u)??ft(p);h!==void 0&&(m.name=h),l.push(m)}}return l.sort((c,u)=>c.id.localeCompare(u.id)),{summary:{added:i.length,removed:d.length,modified:l.length,tabChanges:Gu(t,e),beforeNodeCount:n.size,afterNodeCount:r.size},added:i,removed:d,modified:l}}a(Vi,"buildDiffSections");function qi(e,t,n={}){let r=Vi(e.payload.flow,t.flow),o=n.currentRev??t.rev,s=e.expectedRev,i=n.baseRevStale??(e.status==="pending"&&s!==o),d={proposalId:e.id,flowId:e.payload.flowId,expectedRev:s,currentRev:o,baseRevStale:i,compareAgainst:n.compareAgainst??"current",...r};return e.warnings&&e.warnings.length>0&&(d.warnings=e.warnings.map(l=>({...l}))),n.baseSnapshotMissing&&(d.baseSnapshotMissing=!0),n.vsCurrent&&(d.vsCurrent=n.vsCurrent),d}a(qi,"buildProposalDiff");async function Zt(e,t){let n=e.payload.flowId,r={id:n,type:"tab",nodes:[],configs:[]},o=e.expectedRev,s=r;try{let c=await t.getFlow(n);o=c.rev,s=c.flow}catch(c){if(!(c instanceof B))throw c;try{o=(await t.getFlows()).rev}catch{}}let d=e.status==="pending"&&e.expectedRev!==o,l=e.baseFlow;if(l!==void 0){let c=d?Vi(e.payload.flow,s):void 0;return qi(e,{rev:e.expectedRev,flow:l},{currentRev:o,compareAgainst:"base",baseRevStale:d,...c?{vsCurrent:c}:{}})}return qi(e,{rev:o,flow:s},{compareAgainst:"current",baseSnapshotMissing:!0,baseRevStale:d})}a(Zt,"computeProposalDiff");Ae();Pe();_e();var Ki=require("path");function Dr(e){return(0,Ki.join)(e.userDir,".ai-access")}a(Dr,"getStorageDir");var zu=["status:read","flows:read","proposals:create","proposals:read-own"];function Go(){return zt()}a(Go,"readPackageVersion");function Ju(e,t=Date.now()){let n=Date.parse(e);return Number.isFinite(n)?Math.max(0,Math.ceil((n-t)/1e3)):0}a(Ju,"secondsRemainingUntil");function Vu(e,t=Date.now()){let n=Date.parse(e);return Number.isFinite(n)?Math.max(0,Math.ceil((n-t)/6e4)):0}a(Vu,"minutesRemainingUntil");function Ku(e,t){let n=de(t),r=je(t),o=`${n}/openapi.yaml`,s=`${n}/openapi.json`;return e==="rest"?{api:n,openapi:o,openapiJson:s,discovery:n}:e==="mcp"?{rpc:r,discovery:n}:{discovery:n}}a(Ku,"baseUrlsForTokenKind");function en(e,t){let n=zn(e,t),r=ge(e.settings);if(t.auth){let o=t.auth.extra?.user;return{ok:!0,version:Go(),adminAuthEnabled:n.adminAuthEnabled,aiAccessEnabled:r.enabled,aiFeaturesEnabled:n.aiFeaturesEnabled,allowWithoutAdminAuth:n.allowWithoutAdminAuth,permission:t.auth.scopes.includes("propose")?"propose":"read",capabilities:{view:!0,manageLinks:!1,approveProposals:!1},defaultLinkTtlSeconds:r.defaultLinkTtlSeconds,maxActiveLinks:r.maxActiveLinks,defaultDeploymentType:r.defaultDeploymentType,caller:{kind:"oauth",clientId:t.auth.clientId,user:typeof o=="string"?o:"owner",scopes:t.auth.scopes}}}if(t.accessLink){let o=Date.now();return{ok:!0,version:Go(),adminAuthEnabled:n.adminAuthEnabled,aiAccessEnabled:r.enabled,aiFeaturesEnabled:n.aiFeaturesEnabled,allowWithoutAdminAuth:n.allowWithoutAdminAuth,permission:null,capabilities:{view:!0,manageLinks:!1,approveProposals:!1},defaultLinkTtlSeconds:r.defaultLinkTtlSeconds,maxActiveLinks:r.maxActiveLinks,defaultDeploymentType:r.defaultDeploymentType,caller:{kind:"access-link",linkId:t.accessLink.id,tokenKind:t.accessLink.kind,...t.accessLink.label!==void 0?{label:t.accessLink.label}:{},expiresAt:t.accessLink.expiresAt,minutesRemaining:Vu(t.accessLink.expiresAt,o),secondsRemaining:Ju(t.accessLink.expiresAt,o),permissions:[...zu],baseUrls:Ku(t.accessLink.kind,e.settings.httpAdminRoot)}}}return{ok:!0,version:Go(),adminAuthEnabled:n.adminAuthEnabled,aiAccessEnabled:r.enabled,aiFeaturesEnabled:n.aiFeaturesEnabled,allowWithoutAdminAuth:n.allowWithoutAdminAuth,permission:n.permission,capabilities:n.capabilities,storageDir:Dr(e.settings),defaultLinkTtlSeconds:r.defaultLinkTtlSeconds,maxActiveLinks:r.maxActiveLinks,defaultDeploymentType:r.defaultDeploymentType,caller:{kind:"admin",...n.permission?{permission:n.permission}:{}}}}a(en,"buildStatus");function Xi(e,t){let n=t.draftStore??new yr,r=t.rateLimiters??Si(t.rateLimit??e.settings.aiAccess),o=e.httpAdmin,s="/mcp/a/:token",i=[r.ip,_i(e,t.linkStore),r.token];o.use("/mcp/a",ci()),o.get(s,...i,P((u,p)=>{I(p),p.setHeader("X-Content-Type-Options","nosniff");let f=de(e.settings.httpAdminRoot),m=u.accessLink;if(!m){L(p,401,"Access link required","access_link_missing");return}let h=wi({link:m,openapiPath:`${f}/openapi.yaml`,restApiBase:f,mcpRpcPath:je(e.settings.httpAdminRoot),publicOrigin:ro(u)});p.status(200).type("text/markdown; charset=utf-8").send(h)})),o.get(`${s}/status`,...i,P((u,p)=>{I(p),p.json(en(e,u))})),o.get(`${s}/flows`,...i,P(async(u,p)=>{I(p);try{p.json(await pe(t.flowAdapter))}catch(f){let m=N(f);if(m){p.status(m.statusCode).json({...m.body,hint:"Retry after the owner fixes the plugin runtime."});return}L(p,502,f instanceof Error?f.message:String(f),"upstream_error")}})),o.get(`${s}/flows/:id`,...i,P(async(u,p)=>{I(p),await Pr(t.flowAdapter,"get")(u,p)})),o.get(`${s}/nodes/:id`,...i,P(async(u,p)=>{I(p),await Rr(t.flowAdapter,"get")(u,p)})),o.get(`${s}/nodes/:id/field/:name`,...i,P(async(u,p)=>{I(p),await Lr(t.flowAdapter,"get")(u,p)})),o.get(`${s}/search`,...i,P(async(u,p)=>{I(p),await Cr(t.flowAdapter,"get")(u,p)}));let d={nodeTypes:t.nodeTypesAdapter,...t.catalogueFetcher?{catalogueFetcher:t.catalogueFetcher}:{},...t.catalogueUrl?{catalogueUrl:t.catalogueUrl}:{}};o.get(`${s}/node-types`,...i,P(async(u,p)=>{I(p),await Er(d)(u,p)})),o.get(`${s}/node-types/:type`,...i,P(async(u,p)=>{I(p),await Ir(d)(u,p)})),o.get(`${s}/palette/search`,...i,P(async(u,p)=>{I(p),await Nr(d)(u,p)}));let l=e.settings.httpAdminRoot;Xu(o,s,i,t,l),o.get(`${s}/propose`,...i,P(Ni(t.proposalStore,t.auditLog,t.flowAdapter,l,t.nodeTypesAdapter))),Zu(o,s,i,n,t,l);let c=Ri();return o.all(`${s}/links`,c),o.all(`${s}/links/:id`,c),o.use("/mcp/a",Pi()),n}a(Xi,"registerGetLinkRoutes");function Xu(e,t,n,r,o){e.get(`${t}/proposals`,...n,P(async(s,i)=>{I(i);let d=s.accessLink;if(!d){L(i,401,"Access link required","access_link_missing");return}let l=U(s,o),c=oe(s.query),u=pr(s.query),p;try{p=(await pe(r.flowAdapter)).rev}catch{}let f=p?z(r.proposalStore,p):0,m=Ht(r.proposalStore,d,l,u);i.json({proposals:m.proposals.map(h=>ue(r.proposalStore,d,h,{...l,include:c},{includeConflicts:!1})),...dt({...m,returned:m.proposals.length}),...X(f)})})),e.get(`${t}/proposals/:id`,...n,P(async(s,i)=>{I(i);let d=s.params.id,l=s.accessLink;if(typeof d!="string"||!d){L(i,400,"Missing proposal id","missing_proposal_id");return}if(!l){L(i,401,"Access link required","access_link_missing");return}try{let c=U(s,o),u=oe(s.query),p;try{p=(await pe(r.flowAdapter)).rev}catch{}let f=Me(r.proposalStore,l,d,c);i.json(ue(r.proposalStore,l,f,{...c,include:u},{...p?{liveRev:p}:{}}))}catch(c){let u=N(c,"get");if(u){i.status(u.statusCode).json({...u.body,hint:u.body.hint??"List proposals with GET \u2026/proposals."});return}F(i,c);return}})),Yu(e,t,n,r,o),Qu(e,t,n,r,o)}a(Xu,"registerGetLinkProposalRoutes");function Yu(e,t,n,r,o){e.get(`${t}/proposals/:id/withdraw`,...n,P((s,i)=>{I(i);let d=s.params.id,l=s.accessLink;if(typeof d!="string"||!d){L(i,400,"Missing proposal id","missing_proposal_id");return}if(!l){L(i,401,"Access link required","access_link_missing");return}let c=s.query.confirm;if(c!=="1"&&c!=="true"){L(i,400,"Withdraw over GET requires confirm=1","confirm_required",ie("confirm_required","get"));return}let u=typeof s.query.reason=="string"?s.query.reason:void 0,p=U(s,o),f=oe(s.query);try{let m=Ut(r.proposalStore,r.auditLog,l,d,u,p);i.json(ue(r.proposalStore,l,m,{...p,include:f}))}catch(m){let h=N(m,"get");if(h){let g=h.body.code;L(i,h.statusCode,Fe[g]??"Request failed",g,ie(g,"get"));return}L(i,500,"Internal server error","internal_error");return}}))}a(Yu,"registerGetLinkWithdrawRoute");function Qu(e,t,n,r,o){e.get(`${t}/proposals/:id/diff`,...n,P(async(s,i)=>{I(i);let d=s.params.id,l=s.accessLink;if(typeof d!="string"||!d){L(i,400,"Missing proposal id","missing_proposal_id");return}if(!l){L(i,401,"Access link required","access_link_missing");return}try{let c=Me(r.proposalStore,l,d,U(s,o)),u=R(s.query,"format"),p=Vt(u==="json"||u==="md"?void 0:u);if(!p.ok){L(i,400,"Invalid diff format","invalid_diff_format","Valid formats: summary, full (default summary).");return}i.json(Kt(await Zt(c,r.flowAdapter),p.format))}catch(c){let u=N(c);if(u){i.status(u.statusCode).json(u.body);return}F(i,c);return}}))}a(Qu,"registerGetLinkDiffRoute");function Zu(e,t,n,r,o,s){e.get(`${t}/draft/new`,...n,P(Ci(r,o.flowAdapter))),e.get(`${t}/draft/:draftId/append`,...n,P(Ei(r))),e.get(`${t}/draft/:draftId/submit`,...n,P(Ii(r,o.proposalStore,o.auditLog,o.flowAdapter,s,o.nodeTypesAdapter)))}a(Zu,"registerGetLinkDraftRoutes");q();var Yi=require("module");function ep(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,Yi.createRequire)(e)}a(ep,"createPluginRequire");function tp(e){try{let r=ep()("@node-red/runtime")._?.adminApi?.httpAdmin;if(r&&typeof r.use=="function"&&r!==e.httpAdmin)return r}catch{}return null}a(tp,"resolveEditorHttpAdmin");function Qi(e){let t=[e.httpAdmin],n=tp(e);return n&&t.push(n),t}a(Qi,"httpAdminAppsForMcpErrors");var Zi=Symbol.for("com55.ai-access.mcpJsonErrorHandler");function ea(e){return/\/mcp\/rpc\/?$/.test(e)||e.includes("/mcp/rpc")}a(ea,"isMcpRpcPath");function zo(e){return e.includes("/mcp")}a(zo,"isMcpPath");function np(e){return e.path||e.url||e.originalUrl||""}a(np,"requestPath");function rp(e){if(!e||typeof e!="object")return!1;let t=e;if(t.type==="entity.parse.failed"||e instanceof SyntaxError)return!0;let n=typeof e.message=="string"?e.message:"";return/JSON/i.test(n)&&(t.status===400||t.statusCode===400)}a(rp,"isJsonParseError");function op(e){if(!e||typeof e!="object")return!1;let t=e;return t.type==="entity.too.large"||t.status===413||t.statusCode===413||t.message==="request entity too large"}a(op,"isPayloadTooLarge");function sp(){return(e,t,n,r)=>{if(n.headersSent){r(e);return}let o=np(t);if(rp(e)&&zo(o)){if(ea(o)){n.status(400).json({jsonrpc:"2.0",error:{code:-32700,message:"Parse error"},id:null});return}n.status(400).json({error:"Invalid JSON body",code:"invalid_json"});return}if(op(e)&&zo(o)){n.status(413).json({error:`Request body exceeds limit of ${$} bytes`,code:"payload_too_large"});return}if(zo(o)){if(ea(o)){n.status(500).json({jsonrpc:"2.0",error:{code:-32603,message:gr(e).error,data:{code:"internal_error"}},id:null});return}n.status(500).json(gr(e));return}r(e)}}a(sp,"mcpJsonErrorHandler");function ip(e){let t=e;t[Zi]||(t[Zi]=!0,e.use(sp()))}a(ip,"attachOnce");function ta(e){for(let t of Qi(e))ip(t)}a(ta,"attachMcpJsonErrorHandlers");Pn();var mt=class extends Error{static{a(this,"AccessLinkLimitError")}code="access_link_limit";statusCode=409;constructor(t){super(`Cannot create access link: at most ${t} active links are allowed. Revoke an existing link first.`),this.name="AccessLinkLimitError"}},ht=class extends Error{static{a(this,"AccessLinkNotFoundError")}code="access_link_not_found";statusCode=404;constructor(t){super(`Access link '${t}' was not found`),this.name="AccessLinkNotFoundError"}},En=class extends Error{static{a(this,"AccessLinkValidationError")}code="access_link_invalid";statusCode=400;constructor(t){super(t),this.name="AccessLinkValidationError"}},Ye=class extends Error{static{a(this,"AccessLinkTtlError")}code="invalid_ttl";statusCode=400;hint=lt;constructor(t){super(t),this.name="AccessLinkTtlError"}};var gt=require("crypto");Pn();function Jo(e){return e==="get_link"||e==="rest"||e==="mcp"}a(Jo,"isAccessLinkKind");function Vo(e){return e??"get_link"}a(Vo,"resolveAccessLinkKind");function na(e){return(0,gt.createHash)("sha256").update(e,"utf8").digest("hex")}a(na,"hashToken");function ap(e,t){let n=Buffer.from(e,"utf8"),r=Buffer.from(t,"utf8");return n.length!==r.length?!1:(0,gt.timingSafeEqual)(n,r)}a(ap,"hashesEqual");function In(e){let t={id:e.id,kind:Vo(e.kind),createdAt:new Date(e.createdAtMs).toISOString(),expiresAt:new Date(e.expiresAtMs).toISOString()};return e.label!==void 0&&(t.label=e.label),e.lastUsedAtMs!==void 0&&(t.lastUsedAt=new Date(e.lastUsedAtMs).toISOString()),e.createdBy!==void 0&&(t.createdBy=e.createdBy),t}a(In,"toPublic");var yt=class{static{a(this,"AccessLinkStore")}links=new Map;defaultTtlSeconds;maxActiveLinks;now;randomId;randomToken;constructor(t={}){this.defaultTtlSeconds=t.defaultTtlSeconds??14400,this.maxActiveLinks=t.maxActiveLinks??10,this.now=t.now??(()=>Date.now()),this.randomId=t.randomId??(()=>(0,gt.randomBytes)(12).toString("hex")),this.randomToken=t.randomToken??(()=>`aia_${(0,gt.randomBytes)(32).toString("base64url")}`)}configureDefaults(t){typeof t.defaultTtlSeconds=="number"&&Number.isFinite(t.defaultTtlSeconds)&&t.defaultTtlSeconds>0&&(this.defaultTtlSeconds=Math.floor(t.defaultTtlSeconds)),typeof t.maxActiveLinks=="number"&&Number.isFinite(t.maxActiveLinks)&&t.maxActiveLinks>0&&(this.maxActiveLinks=Math.floor(t.maxActiveLinks))}isActive(t,n){return t.revokedAtMs!==void 0?!1:t.expiresAtMs>n}list(){let t=this.now();return[...this.links.values()].filter(n=>this.isActive(n,t)).sort((n,r)=>r.createdAtMs-n.createdAtMs).map(In)}create(t={}){let n=this.now();if([...this.links.values()].filter(p=>this.isActive(p,n)).length>=this.maxActiveLinks)throw new mt(this.maxActiveLinks);let o=this.defaultTtlSeconds;if(t.ttlSeconds!==void 0){if(typeof t.ttlSeconds!="number"||!Number.isFinite(t.ttlSeconds)||t.ttlSeconds<=0)throw new Ye("ttlSeconds must be a positive number");o=Math.floor(t.ttlSeconds)}let s=typeof t.label=="string"&&t.label.trim().length>0?t.label.trim():void 0,i=Vo(t.kind),d=No(i);if(o>d)throw new Ye(Fo(i,d));let l=this.randomId(),c=this.randomToken(),u={id:l,tokenHash:na(c),token:c,kind:i,createdAtMs:n,expiresAtMs:n+o*1e3};return s!==void 0&&(u.label=s),t.createdBy!==void 0&&(u.createdBy=t.createdBy),this.links.set(l,u),{...In(u),token:c}}peekToken(t){let n=this.links.get(t);return!n||!this.isActive(n,this.now())?null:n.token}revoke(t){let n=this.links.get(t);if(!n)throw new ht(t);return n.revokedAtMs??=this.now(),In(n)}getById(t){if(typeof t!="string"||t.length===0)return null;let n=this.links.get(t);return n?In(n):null}validateResult(t){if(typeof t!="string"||t.length===0)return{status:"invalid"};let n=na(t),r=this.now();for(let o of this.links.values())if(ap(o.tokenHash,n))return o.revokedAtMs!==void 0?{status:"invalid"}:o.expiresAtMs<=r?{status:"expired",expiresAt:new Date(o.expiresAtMs).toISOString()}:(o.lastUsedAtMs=r,{status:"active",link:In(o)});return{status:"invalid"}}validate(t){let n=this.validateResult(t);return n.status==="active"?n.link:null}size(){return this.links.size}clear(){this.links.clear()}upsertRecordForTest(t){this.links.set(t.id,t)}};Pn();ae();_e();function Ko(e,t){return t}a(Ko,"storeForRequest");function oa(e){let t=typeof e.user?.username=="string"?e.user.username:void 0;return t?{kind:"admin",username:t}:{kind:"admin"}}a(oa,"adminActor");function jr(e){let t=ge(e.settings);return new yt({defaultTtlSeconds:t.defaultLinkTtlSeconds,maxActiveLinks:t.maxActiveLinks})}a(jr,"createConfiguredLinkStore");function sa(e,t){return(n,r)=>{let o=Ko(e,t),s=o.list().map(i=>{let d=o.peekToken(i.id);return d?i.kind==="get_link"?{...i,url:oo(n,e.settings.httpAdminRoot,d)}:{...i,token:d}:i});r.json({links:s})}}a(sa,"listLinksHandler");function ia(e,t,n){return(r,o)=>{let s=r.body??{},i={};if(s.ttlSeconds!==void 0){if(typeof s.ttlSeconds!="number"){o.status(400).json({error:"ttlSeconds must be a positive number",code:"invalid_ttl",hint:lt});return}i.ttlSeconds=s.ttlSeconds}if(typeof s.label=="string"&&(i.label=s.label),s.kind!==void 0){if(!Jo(s.kind)){o.status(400).json({error:"kind must be one of: 'get_link', 'rest', 'mcp'",code:"invalid_link_kind"});return}i.kind=s.kind}typeof r.user?.username=="string"&&(i.createdBy=r.user.username);try{let d=Ko(e,t).create(i),l=d.kind;if(n){let c=Date.parse(d.createdAt),u=Date.parse(d.expiresAt),p=Number.isFinite(c)&&Number.isFinite(u)&&u>c?Math.round((u-c)/1e3):void 0;n.append("link.created",oa(r),{linkId:d.id,label:d.label,expiresAt:d.expiresAt,createdAt:d.createdAt,kind:l,...p!==void 0?{ttlSeconds:p}:{}})}if(l==="get_link"){let c=oo(r,e.settings.httpAdminRoot,d.token);o.status(201).json({...d,url:c});return}o.status(201).json({...d})}catch(d){if(d instanceof mt){o.status(d.statusCode).json({error:d.message,code:d.code});return}if(d instanceof Ye){o.status(d.statusCode).json({error:d.message,code:d.code,hint:d.hint});return}if(d instanceof En){o.status(d.statusCode).json({error:d.message,code:d.code});return}F(o,d);return}}}a(ia,"createLinkHandler");function aa(e,t,n){return(r,o)=>{let s=r.params.id;if(typeof s!="string"||s.length===0){o.status(400).json({error:"Missing link id"});return}try{let i=Ko(e,t).revoke(s);if(n){let d=Date.parse(i.createdAt),l=Date.parse(i.expiresAt),c=Number.isFinite(d)&&Number.isFinite(l)&&l>d?Math.round((l-d)/1e3):void 0;n.append("link.revoked",oa(r),{linkId:i.id,label:i.label,expiresAt:i.expiresAt,createdAt:i.createdAt,kind:i.kind,...c!==void 0?{ttlSeconds:c}:{}})}o.json({revoked:!0,link:i})}catch(i){if(i instanceof ht){o.status(i.statusCode).json({error:i.message,code:i.code});return}F(o,i);return}}}a(aa,"revokeLinkHandler");function dp(e){let t=Date.parse(e.createdAt),n=Date.parse(e.expiresAt);if(Number.isFinite(t)&&Number.isFinite(n)&&n>t)return Math.round((n-t)/1e3)}a(dp,"ttlSecondsFromLink");function lp(e,t){if(!t||e.action!=="link.created"&&e.action!=="link.revoked")return e;let n={...e.details??{}},r=typeof n.linkId=="string"?n.linkId:"";if(!r)return e;let o=n.label==null||n.label==="",s=n.expiresAt==null||n.expiresAt==="",i=n.createdAt==null||n.createdAt==="",d=n.ttlSeconds==null;if(!o&&!s&&!i&&!d)return e;let l=t.getById(r);if(!l)return e;if(o&&l.label&&(n.label=l.label),s&&(n.expiresAt=l.expiresAt),i&&(n.createdAt=l.createdAt),d){let c=dp(l);c!==void 0&&(n.ttlSeconds=c)}return{...e,details:n}}a(lp,"enrichAuditLogEntry");function da(e,t){return(n,r)=>{let o=n.query.limit,s=100;if(typeof o=="string"&&o.length>0){let d=Number(o);Number.isFinite(d)&&d>0&&(s=Math.min(Math.floor(d),500))}let i=e.list(s).map(d=>lp(d,t));r.json({logs:i})}}a(da,"listLogsHandler");var Xo=require("@modelcontextprotocol/sdk/server/mcp.js"),la=require("@modelcontextprotocol/sdk/server/streamableHttp.js"),x=require("zod");Ue();q();tt();G();Ne();it();Ge();Ae();function ca(){return"0.2.0-beta.1"}a(ca,"pluginVersion");async function ua(e){try{return(await pe(e)).rev}catch{return}}a(ua,"tryLiveRev");function Y(e){return{content:[{type:"text",text:JSON.stringify(e)}]}}a(Y,"jsonResult");function Yo(e){return e===!0?new Set(["payload","baseFlow"]):new Set}a(Yo,"includeSetFromPayloadFlag");function Q(e,t=500){let n=_n(e,"mcp");if(n)return{isError:!0,content:[{type:"text",text:JSON.stringify({...n.body,statusCode:n.statusCode})}]};let r=e instanceof Error?e.message:String(e);return{isError:!0,content:[{type:"text",text:JSON.stringify({error:r,code:"internal_error",statusCode:t})}]}}a(Q,"jsonError");var cp=new Set(["list_proposals","list_my_proposals"]),up=new Set(["status","limit","offset","mine","flowId"]);function pp(e){return!!e&&typeof e=="object"&&typeof e.safeParseAsync=="function"}a(pp,"isZodLikeSchema");function fp(e,t,n){let r=t&&typeof t=="object"?t:{},o=n[0],s=o?.path&&o.path.length>0?String(o.path[0]):"(arguments)";if(cp.has(e)&&up.has(s))return jt(s,r[s]);let i=n.map(l=>({parameter:l.path&&l.path.length>0?l.path.map(String).join("."):"(arguments)",message:l.message??"Invalid value"})),d=i.length===1?`Invalid argument '${i[0]?.parameter}' for tool ${e}: ${i[0]?.message}`:`${i.length} invalid arguments for tool ${e}`;return new b(d,"invalid_arguments",{details:{parameter:s,errors:i,hint:`Fix '${s}' to match the ${e} input schema (see tools/list).`}})}a(fp,"mcpArgumentError");function pa(e){let t=e,n=t.validateToolInput;typeof n=="function"&&(t.validateToolInput=async(r,o,s)=>{let i=r.inputSchema;if(!pp(i))return n.call(e,r,o,s);let d=await i.safeParseAsync(o??{});if(d.success)return d.data;let l=_n(fp(s,o,d.error.issues),"mcp"),c=l?{...l.body,statusCode:l.statusCode}:{};throw new Error(JSON.stringify(c))})}a(pa,"installMcpArgumentErrorMapping");function fa(e,t,n,r){e.registerTool("get_status",{description:'Return plugin status (version, AI feature flags, storage path, deploy defaults). For access-link callers, permission is null and caller.kind is "access-link" \u2014 that is expected, not an error. Use before other tools to confirm the plugin is enabled.'},()=>{try{return Y(en(t,r))}catch(o){return Q(o)}}),e.registerTool("list_flows",{description:"List all flows as a flat Admin API v2 array plus the current rev string. Returns { rev, flows }. Call this (or get_flow) before submit_proposal so expectedRev matches."},async()=>{try{return Y(await pe(n.flowAdapter))}catch(o){return Q(o,502)}}),e.registerTool("get_flow",{description:"Get one tab/subflow by id with nested nodes/configs and the current rev. Pass summary=true for a lightweight node list (id/type/name/wires/bytes, no field contents). Prefer summary \u2192 search_flows \u2192 get_node_field(lines) \u2192 replace_text \u2192 submit_proposal. Returns { rev, flow } or summary shape. Use rev as expectedRev when submitting.",inputSchema:{id:x.z.string().min(1).describe("Flow / tab id"),summary:x.z.boolean().optional().describe("When true, return node list with sizes only (no field contents)")}},async({id:o,summary:s})=>{try{return Y(s?await kr(n.flowAdapter,o):await xe(n.flowAdapter,o))}catch(i){return Q(i,502)}}),e.registerTool("get_node",{description:"Get one full node by id (includes owning flowId when known). Optional flow scopes the lookup.",inputSchema:{id:x.z.string().min(1).describe("Node id"),flow:x.z.string().min(1).optional().describe("Optional tab id to scope the lookup")}},async({id:o,flow:s})=>{try{return Y(await Ar(n.flowAdapter,o,s))}catch(i){return Q(i,502)}}),e.registerTool("get_node_field",{description:'Get one string field from a node. Optional lines="A-B" (1-based inclusive) returns a slice; response always includes totalLines and bytes.',inputSchema:{id:x.z.string().min(1).describe("Node id"),field:x.z.string().min(1).describe("Field name (e.g. template, func)"),lines:x.z.string().optional().describe("Optional 1-based line range A or A-B"),flow:x.z.string().min(1).optional().describe("Optional tab id to scope the lookup")}},async({id:o,field:s,lines:i,flow:d})=>{try{return Y(await xr(n.flowAdapter,o,s,{...d?{flowId:d}:{},...i?{lines:i}:{}}))}catch(l){return Q(l,502)}}),e.registerTool("search_flows",{description:"Text search across string fields. Returns hits with node id, field, line numbers, and a short snippet. Results are capped; truncated=true when the cap is hit. Optional flow scopes to one tab.",inputSchema:{q:x.z.string().describe("Substring to find (case-sensitive; non-empty required)"),flow:x.z.string().min(1).optional().describe("Optional tab id")}},async({q:o,flow:s})=>{try{return Y(await Sr(n.flowAdapter,o,s?{flowId:s}:{}))}catch(i){return Q(i,502)}}),e.registerTool("list_node_types",{description:"List installed Node-RED palette node types (type, module, category, description, \u2026). Optional q filters type/module/category/description (case-insensitive). Results are capped. Call this before proposing nodes with unfamiliar types.",inputSchema:{q:x.z.string().optional().describe("Optional case-insensitive filter")}},async({q:o})=>{try{return Y(await or(n.nodeTypesAdapter,o?{q:o}:{}))}catch(s){return Q(s,502)}}),e.registerTool("get_node_type",{description:"Get detail for one installed node type: defaults (JSON), inputs/outputs (always set), requiredConfigNodeTypes, help sections (summary/inputs/outputs), category/module/color. Types are case-sensitive. Pass includeHelp=false to omit help. 404 node_type_not_found when unknown (didYouMean on case mismatch; else nearest names).",inputSchema:{type:x.z.string().min(1).describe("Node type id (e.g. inject, template) \u2014 case-sensitive"),includeHelp:x.z.boolean().optional().describe("When false, omit help sections (default true)")}},async({type:o,includeHelp:s})=>{try{return Y(await st(n.nodeTypesAdapter,o,{includeHelp:s!==!1}))}catch(i){return Q(i,502)}}),e.registerTool("search_palette",{description:"Search the public Node-RED catalogue for modules to recommend. Returns module id, description, version, updated, link, and installed boolean (cross-checked against installed modules). Core node-red types are not in the catalogue \u2014 use list_node_types. RECOMMEND ONLY \u2014 never installs.",inputSchema:{q:x.z.string().describe("Search string (id / description / keywords; non-empty required)")}},async({q:o})=>{try{return Y(await sr(n.nodeTypesAdapter,o,{...n.catalogueFetcher?{catalogueFetcher:n.catalogueFetcher}:{},...n.catalogueUrl?{catalogueUrl:n.catalogueUrl}:{}}))}catch(s){return Q(s,502)}})}a(fa,"registerReadTools");function ma(e,t,n,r,o){let s=U(r,t.settings.httpAdminRoot);e.registerTool("submit_proposal",{description:`Submit a replace_flow proposal for owner approval (does not deploy). Preferred for small string edits: payload: { flowId, nodes: [{ op:"replace_text", node, field, find, replace, occurrence? }] } \u2014 find must be unique unless occurrence (1-based) is set; multiple replace_text ops apply in order; yields a short readable diff. Or send a full tab: payload.flow from get_flow (id, type, label?, nodes[], configs?). Inputs (all top-level, next to each other): type="replace_flow", expectedRev (required string from list_flows/get_flow rev; expected_rev alias accepted), optional title (\u226480 chars), and payload: { flowId, flow? } and/or payload.nodes / top-level nodes. Do not nest expectedRev inside payload. Payload size limit is ${$} bytes (413 if exceeded). expectedRev is checked against the live rev at submit time (409 if stale). replace_text errors: 400 text_not_found; 409 ambiguous_match with matchCount. Unknown palette types return 400 unknown_node_type with nearest matches \u2014 call list_node_types / search_palette and ask the user to install via Manage palette (never auto-install). Returns a lean proposal (id, status, title, storedTitle, flowLabel, displayName, reviewUrl, changeSummary, \u2026) by default; title may gain " in <flow>"; storedTitle is the title as saved (your title without that suffix, or the auto-generated title, which may already name the flow). Pass include_payload=true to include payload and baseFlow. When telling the user about the proposal, cite displayName and reviewUrl \u2014 not the raw id. Humans approve on the /mcp page (reviewUrl opens/scrolls to it).`,inputSchema:{type:x.z.literal("replace_flow").describe("Must be 'replace_flow'"),expectedRev:x.z.string().min(1).optional().describe("Top-level current flow revision from get_flow/list_flows (required at submit; sit next to type and payload, not inside payload)"),expected_rev:x.z.string().min(1).optional().describe("Alias for expectedRev (snake_case); prefer expectedRev"),title:x.z.string().max(80).optional().describe("Optional short human title (\u226480 chars). Auto-generated from changes when omitted."),include_payload:x.z.boolean().optional().describe("When true, include payload and baseFlow in the response (default lean)"),nodes:x.z.array(x.z.record(x.z.string(),x.z.unknown())).optional().describe("Optional node ops (set/add/remove/replace_text). Same as payload.nodes when flow is omitted."),payload:x.z.object({flowId:x.z.string().min(1).describe("Tab id to replace"),flow:x.z.object({id:x.z.string().optional(),type:x.z.string().optional().describe("Usually 'tab' (included by get_flow)"),label:x.z.string().optional(),nodes:x.z.array(x.z.record(x.z.string(),x.z.unknown())).optional().describe("Child nodes for the tab (from get_flow.nodes)"),configs:x.z.array(x.z.record(x.z.string(),x.z.unknown())).optional()}).passthrough().optional().describe("Full tab from get_flow; omit when using nodes ops"),nodes:x.z.array(x.z.record(x.z.string(),x.z.unknown())).optional().describe("Node ops applied to the live tab (prefer replace_text for small string edits)")}).describe("Replacement flow payload and/or node ops")}},async i=>{try{let d=await ze(n.proposalStore,n.auditLog,o,{...i,expectedRev:typeof i.expectedRev=="string"&&i.expectedRev.length>0?i.expectedRev:i.expected_rev},n.flowAdapter,s,n.nodeTypesAdapter),l={...ce(d,{...s,include:Yo(i.include_payload)}),mine:!0},c=z(n.proposalStore,d.expectedRev,{flowId:d.payload.flowId}),u=Ie(Ee(n.proposalStore,n.proposalStore.get(d.id),o.id));return Y({...l,...u,...X(c)})}catch(d){return Q(d)}}),hp(e,n,o,s),gp(e,n,o,s)}a(ma,"registerProposalTools");function mp(e,t,n,r){let o={mine:x.z.boolean().optional().describe("true: only proposals created by this access link; false: only other links\u2019 proposals"),status:x.z.union([x.z.string(),x.z.array(x.z.string())]).optional().describe("Filter by proposal status: one value, an array, or a comma-separated string (pending, approved, rejected, failed, withdrawn, expired, superseded). An empty array or empty string means no status filter."),flowId:x.z.string().optional().describe("Filter by flow tab id (payload.flowId)"),limit:x.z.number().int().min(1).max(Dt).optional().describe(`Page size (1\u2013${Dt}). Omit to return all matches.`),offset:x.z.number().int().min(0).optional().describe("Skip this many filtered results (default 0)"),include_payload:x.z.boolean().optional().describe("When true, include payload and baseFlow on each proposal")},s=a(async i=>{try{let d=Yo(i.include_payload),l=await ua(t.flowAdapter),c=l?z(t.proposalStore,l):0,u={};typeof i.mine=="boolean"&&(u.mine=i.mine);let p=Io(i.status);p&&(u.status=p),typeof i.flowId=="string"&&i.flowId.length>0&&(u.flowId=i.flowId),typeof i.limit=="number"&&(u.limit=i.limit),typeof i.offset=="number"&&(u.offset=i.offset);let f=Ht(t.proposalStore,n,r,u);return Y({proposals:f.proposals.map(m=>ue(t.proposalStore,n,m,{...r,include:d},{includeConflicts:!1})),...dt({...f,returned:f.proposals.length}),...X(c)})}catch(d){return Q(d)}},"listProposalsHandler");e.registerTool("list_proposals",{description:"List proposals visible to this access link (all proposals by default). Each item includes mine:true|false for the caller. Filter with mine, status, and/or flowId; paginate with limit/offset (response includes total, offset, hasMore, nextOffset). Invalid filters return invalid_filter. Returns lean { proposals, total, \u2026 } with title, displayName, reviewUrl, changeSummary (no payload/baseFlow unless include_payload=true). Prefer displayName + reviewUrl when referring to a proposal with the user (not the raw id).",inputSchema:o},s),e.registerTool("list_my_proposals",{description:"Alias of list_proposals. Prefer list_proposals. Lists proposals visible to this access link with optional mine/status/flowId filters and limit/offset pagination.",inputSchema:o},s)}a(mp,"registerListProposalsTools");function hp(e,t,n,r){mp(e,t,n,r),e.registerTool("get_proposal",{description:"Get any proposal by id (all tokens can read). Response includes mine:true|false and, for pending proposals, conflictingProposals when other pending work touches the same flow. Lean by default (no payload/baseFlow); pass include_payload=true for full bodies. Cite displayName + reviewUrl when talking to the user.",inputSchema:{id:x.z.string().min(1).describe("Proposal id (prop-\u2026)"),include_payload:x.z.boolean().optional().describe("When true, include payload and baseFlow")}},async({id:o,include_payload:s})=>{try{let i=await ua(t.flowAdapter),d=Me(t.proposalStore,n,o,r);return Y(ue(t.proposalStore,n,d,{...r,include:Yo(s)},{...i?{liveRev:i}:{}}))}catch(i){return Q(i)}})}a(hp,"registerProposalQueryTools");function gp(e,t,n,r){e.registerTool("get_proposal_diff",{description:"Diff any proposal against its stored baseFlow snapshot (what changed). Optional format=summary|full (default summary). Pending + stale also includes vsCurrent.",inputSchema:{id:x.z.string().min(1).describe("Proposal id (prop-\u2026)"),format:x.z.enum(["summary","full"]).optional().describe("Diff payload format (default summary)")}},async({id:o,format:s})=>{try{let i=Me(t.proposalStore,n,o),d=Vt(s??"summary");if(!d.ok)return Q(new b("Invalid diff format","invalid_diff_format",{details:{hint:"Valid formats: summary, full (default summary)."}}));let l=Kt(await Zt(i,t.flowAdapter),d.format);return Y(l)}catch(i){return Q(i)}}),e.registerTool("withdraw_proposal",{description:"Withdraw one of YOUR pending proposals (status becomes withdrawn, not rejected). Optional reason. Only the creating access link can withdraw; other tokens get 403. Cite displayName + reviewUrl when telling the user.",inputSchema:{id:x.z.string().min(1).describe("Proposal id (prop-\u2026) owned by this access link"),reason:x.z.string().optional().describe("Optional withdraw reason")}},({id:o,reason:s})=>{try{let i=Ut(t.proposalStore,t.auditLog,n,o,s,r);return Y(ue(t.proposalStore,n,i,r))}catch(i){return Q(i)}})}a(gp,"registerProposalActionTools");function yp(e,t,n){let r=new Xo.McpServer({name:"node-red-ai-access",version:ca()});pa(r);let o=n.accessLink;if(!o)throw new Error("Access link required");return fa(r,e,t,n),ma(r,e,t,n,o),r}a(yp,"createAiMcpServer");function ha(e,t){return async(n,r,o)=>{try{if(!n.accessLink&&!n.auth){r.setHeader("WWW-Authenticate",K),r.status(401).json({error:le,code:Re});return}let s=n.accessLink?yp(e,t,n):wp(e,t,n),i=new la.StreamableHTTPServerTransport({enableJsonResponse:!0});await s.connect(i),await i.handleRequest(n,r,n.body),r.on("close",()=>{i.close(),s.close()})}catch(s){if(!r.headersSent){F(r,s);return}o(s instanceof Error?s:new Error(String(s)))}}}a(ha,"mcpRpcPostHandler");function wp(e,t,n){let r=new Xo.McpServer({name:"node-red-ai-access",version:ca()});if(pa(r),fa(r,e,t,n),(n.auth?.scopes??[]).includes("propose")){let s=typeof n.auth?.extra?.user=="string"?n.auth.extra.user:"owner",i=n.auth?.clientId??"",l={id:lo(i,s),kind:"mcp",createdAt:new Date().toISOString(),expiresAt:new Date((n.auth?.expiresAt??0)*1e3).toISOString(),createdBy:s,oauthClientId:i,oauthUser:s};ma(r,e,t,n,l)}return r}a(wp,"createOAuthMcpServer");function Qo(e,t){t.status(405).set("Allow","POST").json({jsonrpc:"2.0",error:{code:-32e3,message:"Method not allowed. Use POST for Streamable HTTP."},id:null})}a(Qo,"mcpRpcMethodNotAllowed");var Fn=require("crypto"),nn=require("fs"),Zo=require("path"),H=require("@modelcontextprotocol/sdk/server/auth/errors.js"),wa=require("@modelcontextprotocol/sdk/server/auth/handlers/authorize.js"),es=require("@modelcontextprotocol/sdk/server/auth/handlers/metadata.js"),ba=require("@modelcontextprotocol/sdk/server/auth/handlers/register.js"),va=require("@modelcontextprotocol/sdk/server/auth/handlers/revoke.js"),ka=require("@modelcontextprotocol/sdk/server/auth/handlers/token.js"),Aa=require("@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js"),xa=require("@modelcontextprotocol/sdk/server/auth/router.js"),Sa=dn(require("express"),1);q();Pe();var ga=`(function () {
|
|
1374
|
+
`);for(let m=0;m<f.length;m+=1){let g=(f[m]??"").indexOf(n);if(g>=0){let A=0;for(let S=0;S<m;S+=1)A+=(f[S]?.length??0)+1;u(m+1,A+g)}}}if(n.includes(`
|
|
1375
|
+
`)){let f=0;for(;f<=i.length;){let m=i.indexOf(n,f);if(m<0)break;u(Ou(i,m),m),f=m+Math.max(1,n.length)}}if(d.length===0)continue;let p=d.length;r.push({flowId:t,nodeId:e.id,field:s,lines:d.slice(0,Cu),lineCount:p,snippet:Fu(i,Math.max(0,c),n.length)})}return r.length>=o}a(Ho,"searchInNode");function Hi(e){return[...e.nodes??[],...e.configs??[]]}a(Hi,"nodePool");async function $u(e,t,n,r,o){let s=await xe(e,t);for(let i of Hi(s.flow))if(Ho(i,t,n,r,o))return{rev:s.rev,truncated:!0};return{rev:s.rev,truncated:!1}}a($u,"searchOneFlow");async function Mu(e,t,n,r){let o=await pe(e),s=new Set;for(let i of Di(o.flows))if(i.id)try{let d=await xe(e,i.id);for(let l of Hi(d.flow))if(s.add(l.id),Ho(l,i.id,t,n,r))return{rev:o.rev,truncated:!0}}catch{}for(let i of o.flows){if(!i||typeof i!="object"||Array.isArray(i))continue;let d=i;if(!d.id||s.has(d.id)||d.type==="tab"||d.type==="subflow")continue;let l=typeof d.z=="string"?d.z:null;if(Ho(d,l,t,n,r))return{rev:o.rev,truncated:!0}}return{rev:o.rev,truncated:!1}}a(Mu,"searchAllFlows");async function xr(e,t,n={}){if(typeof t!="string"||!t.trim())throw new y("q query parameter is required","missing_query");let r=typeof n.cap=="number"&&n.cap>0?Math.floor(n.cap):Ru,o=[],s=n.flowId?await $u(e,n.flowId,t,o,r):await Mu(e,t,o,r),i={rev:s.rev,q:t,hits:o,hitCount:o.length,truncated:s.truncated,cap:r};return n.flowId&&(i.flowId=n.flowId),i}a(xr,"searchFlows");function Ui(e){if(e===void 0)return!1;let t=e.toLowerCase();return t==="1"||t==="true"||t==="yes"}a(Ui,"isTruthyQueryFlag");function Du(e,t,n){switch(e){case"node_not_found":case"flow_not_found":case"field_not_found":return n??ie(e,t);default:return ie(e,t)}}a(Du,"layeredReadErrorHint");function ht(e){return Fe[e]??"Request failed"}a(ht,"safeErrorText");function fe(e,t,n){e.setHeader("X-Content-Type-Options","nosniff"),e.setHeader("Content-Type","application/json; charset=utf-8"),e.status(t).json(n)}a(fe,"sendJson");function Sr(e,t,n,r){let o=O(t,n);if(!o)return!1;let s=o.body.code,i={error:ht(s),code:s};typeof o.body.matchCount=="number"&&(i.matchCount=o.body.matchCount),typeof o.body.totalLines=="number"&&(i.totalLines=o.body.totalLines),typeof o.body.expectedRev=="string"&&(i.expectedRev=o.body.expectedRev),typeof o.body.currentRev=="string"&&(i.currentRev=o.body.currentRev);let d=Du(s,n,r);return d&&(i.hint=d),fe(e,o.statusCode,i),!0}a(Sr,"sendMappedError");function Tr(e){fe(e,502,{error:ht("upstream_error"),code:"upstream_error"})}a(Tr,"sendUpstreamError");function _r(e,t="rest"){return async(n,r)=>{let o=n.params.id;if(typeof o!="string"||o.length===0){fe(r,400,{error:ht("missing_flow_id"),code:"missing_flow_id"});return}let s=Ui(R(n.query,"summary"));try{if(s){fe(r,200,await vr(e,o));return}fe(r,200,await xe(e,o))}catch(i){if(Sr(r,i,t))return;Tr(r)}}}a(_r,"getFlowOrSummaryHandler");function Pr(e,t="rest"){return async(n,r)=>{let o=n.params.id;if(typeof o!="string"||!o){fe(r,400,{error:ht("missing_node_id"),code:"missing_node_id"});return}let s=R(n.query,"flow");try{fe(r,200,await kr(e,o,s))}catch(i){if(Sr(r,i,t,t==="get"?"Call GET \u2026/flows/<id>?summary=1 or \u2026/search?q=\u2026":t==="mcp"?void 0:"Call GET \u2026/flows/<id>?summary=1 or \u2026/search?q=\u2026"))return;Tr(r)}}}a(Pr,"getNodeHandler");function Rr(e,t="rest"){return async(n,r)=>{let o=n.params.id,s=n.params.name;if(typeof o!="string"||!o){fe(r,400,{error:ht("missing_node_id"),code:"missing_node_id"});return}if(typeof s!="string"||!s){fe(r,400,{error:ht("missing_field"),code:"missing_field"});return}let i=R(n.query,"flow"),d=R(n.query,"lines");try{fe(r,200,await Ar(e,o,s,{...i?{flowId:i}:{},...d?{lines:d}:{}}))}catch(l){if(Sr(r,l,t,"Use summary/search to discover node ids and fields."))return;Tr(r)}}}a(Rr,"getNodeFieldHandler");function Cr(e,t="rest"){return async(n,r)=>{let o=R(n.query,"q");if(!o){fe(r,400,{error:ht("missing_query"),code:"missing_query",hint:t==="get"?"Example: GET \u2026/search?q=TODO&flow=tab1":ie("missing_query",t)});return}let s=R(n.query,"flow");try{fe(r,200,await xr(e,o,s?{flowId:s}:{}))}catch(i){if(Sr(r,i,t))return;Tr(r)}}}a(Cr,"searchFlowsHandler");K();lt();var ju={missing_node_type:"Missing node type",missing_query:"q query parameter is required",node_type_not_found:"Node type was not found",catalogue_unavailable:"Node-RED catalogue unavailable",unknown_node_type:"Unknown node type",upstream_error:"Upstream error"};function Rn(e){return ju[e]??"Request failed"}a(Rn,"safeErrorText");function Qe(e,t,n){e.setHeader("X-Content-Type-Options","nosniff"),e.setHeader("Content-Type","application/json; charset=utf-8"),e.setHeader("Cache-Control","no-store"),e.status(t).json(n)}a(Qe,"sendJson");function Uo(e,t){let n=O(t);if(!n)return!1;let r=n.body.code,o={error:Rn(r),code:r};return typeof n.body.type=="string"&&(o.type=n.body.type),Array.isArray(n.body.nearest)&&(o.nearest=n.body.nearest),typeof n.body.didYouMean=="string"?(o.didYouMean=n.body.didYouMean,o.caseSensitive=!0,o.hint=`Types are case-sensitive \u2014 use '${n.body.didYouMean}' exactly.`):(r==="node_type_not_found"||r==="unknown_node_type")&&(o.hint="Call GET \u2026/node-types?q= or \u2026/palette/search?q=. Ask the user to install via Manage palette, then recheck node-types. This API never installs."),r==="catalogue_unavailable"&&(o.hint="Retry later; palette search is recommend-only and never installs packages."),Qe(e,n.statusCode,o),!0}a(Uo,"sendMappedError");function Lr(e){return async(t,n)=>{try{let r=R(t.query,"q"),o=await rr(e.nodeTypes,r?{q:r}:{});Qe(n,200,o)}catch(r){if(Uo(n,r))return;Qe(n,502,{error:Rn("upstream_error"),code:"upstream_error"})}}}a(Lr,"listNodeTypesHandler");function Er(e){return async(t,n)=>{try{let r=t.params.type,o=typeof r=="string"?decodeURIComponent(r):"",s=R(t.query,"help"),i=!(s==="0"||s==="false"||s==="no"||s==="omit"),d=await dt(e.nodeTypes,o,{includeHelp:i});Qe(n,200,d)}catch(r){if(Uo(n,r))return;Qe(n,502,{error:Rn("upstream_error"),code:"upstream_error"})}}}a(Er,"getNodeTypeHandler");function Ir(e){return async(t,n)=>{try{let r=R(t.query,"q");if(!r){Qe(n,400,{error:Rn("missing_query"),code:"missing_query",hint:"Pass ?q= to search the Node-RED catalogue (recommend only)."});return}let o=await or(e.nodeTypes,r,{...e.catalogueFetcher?{catalogueFetcher:e.catalogueFetcher}:{},...e.catalogueUrl?{catalogueUrl:e.catalogueUrl}:{}});Qe(n,200,o)}catch(r){if(Uo(n,r))return;Qe(n,502,{error:Rn("upstream_error"),code:"upstream_error"})}}}a(Ir,"searchPaletteHandler");Je();ve();function Zt(e,t){return e[t]??""}a(Zt,"lineAt");function Cn(e,t,n){let r=e[t];return r?r[n]??0:0}a(Cn,"cell");function Bi(e,t,n,r){let o=e[t];o&&(o[n]=r)}a(Bi,"setCell");function Wi(e,t){let n=qi(e),r=qi(t),o=n.length,s=r.length,i=[];for(let u=0;u<=o;u+=1){let p=[];for(let f=0;f<=s;f+=1)p.push(0);i.push(p)}for(let u=o-1;u>=0;u-=1)for(let p=s-1;p>=0;p-=1)Zt(n,u)===Zt(r,p)?Bi(i,u,p,Cn(i,u+1,p+1)+1):Bi(i,u,p,Math.max(Cn(i,u+1,p),Cn(i,u,p+1)));let d=[],l=0,c=0;for(;l<o&&c<s;){let u=Zt(n,l),p=Zt(r,c);u===p?(d.push({op:"equal",text:u}),l+=1,c+=1):Cn(i,l+1,c)>=Cn(i,l,c+1)?(d.push({op:"remove",text:u}),l+=1):(d.push({op:"add",text:p}),c+=1)}for(;l<o;)d.push({op:"remove",text:Zt(n,l)}),l+=1;for(;c<s;)d.push({op:"add",text:Zt(r,c)}),c+=1;return d}a(Wi,"diffLines");function qi(e){return e.length===0?[]:e.split(`
|
|
1376
|
+
`)}a(qi,"splitLines");Ae();var Ji=new Set(["func","template","info","payload","code","text","format","syntax"]),Hu=new Set(["x","y","w","h","wires","g"]),Uu=new Set(["w","h","g"]);function gt(e){if(typeof e.name=="string"&&e.name.length>0)return e.name;if(typeof e.label=="string"&&e.label.length>0)return e.label}a(gt,"nodeName");function Gi(e){let t=new Map;for(let n of e.nodes??[])t.set(n.id,n);for(let n of e.configs??[])t.set(n.id,n);return t}a(Gi,"collectNodes");function Or(e){if(e===void 0)return"undefined";if(e===null||typeof e!="object")return JSON.stringify(e);if(Array.isArray(e))return`[${e.map(Or).join(",")}]`;let t=e;return`{${Object.keys(t).sort().map(r=>`${JSON.stringify(r)}:${Or(t[r])}`).join(",")}}`}a(Or,"stableStringify");function Ki(e,t){return Or(e)===Or(t)}a(Ki,"valuesEqual");function $r(e){return typeof e=="string"?e:null}a($r,"asText");function Bu(e){return/<\/?[a-z][\s\S]*>/i.test(e.slice(0,800))}a(Bu,"looksLikeHtml");function qu(e,t,n){if(t!=="template")return!1;let r=typeof e.format=="string"?e.format.toLowerCase():"";return r==="html"||r==="handlebars"||r==="mustache"?!0:Bu(n)}a(qu,"isHtmlTemplateField");function Vi(e,t,n){let r=$r(n);if(Ji.has(t)&&r!==null){let o={key:t,kind:"text",value:r};return qu(e,t,r)&&(o.preview="html"),o}return{key:t,kind:"scalar",value:n===void 0?null:n}}a(Vi,"toPresenceProperty");function Wu(e,t){let n=[];for(let r of[...Object.keys(e)].sort())if(!(Uu.has(r)||r==="id"||r==="z")){if(r==="wires"){let o=Xi(e,t);n.push({key:"wires",kind:"scalar",value:o.length>0?o:e.wires??null});continue}n.push(Vi(e,r,e[r]))}return n}a(Wu,"nodeProperties");function Xi(e,t){let n=e.wires;if(!Array.isArray(n)||n.length===0)return[];let r=[];return n.forEach((o,s)=>{if(!Array.isArray(o))return;let i=[];for(let d of o){if(typeof d!="string"||!d)continue;let l=t.get(d);i.push({id:d,name:l?gt(l)??l.type:d})}r.push({port:s,to:i})}),r}a(Xi,"describeWiresStructured");var Gu=new Set(["func","template","code","text"]);function zu(e,t){let n=[],r=gt(e);r&&n.push({key:"name",kind:"scalar",value:r});let o=Xi(e,t);o.length>0&&n.push({key:"wires",kind:"scalar",value:o});for(let s of Gu){if(!(s in e))continue;let i=$r(e[s]);i===null||i.length===0||n.push(Vi(e,s,i))}return n}a(zu,"buildKeyFields");function Ju(e,t,n){let r=ze(e.type),o=gt(e),s=o?` "${o}"`:"",i=t?` in "${t}"`:"",d=n===1?"1 field":`${n} fields`;return`Changes ${r} node${s}${i} (${d})`}a(Ju,"buildModifiedSummary");function Ku(e,t,n){let r=ze(e.type),o=gt(e),s=t==="added"?"Adds":"Removes",i=t==="added"?"to":"from",d=o?` ${JSON.stringify(o)}`:"",l=n?` ${i} flow ${JSON.stringify(n)}`:"",c=`${s} ${r} node${d}${l}`;if(t==="added"&&r==="template"){let u=typeof e.field=="string"&&e.field?e.field:"";u&&(c+=`, output to msg.${u}`)}return c}a(Ku,"buildPresenceSummary");function Nr(e,t,n,r){let o={id:e.id,type:e.type,summary:Ku(e,t,n),keyFields:zu(e,r),properties:Wu(e,r)},s=gt(e);return s!==void 0&&(o.name=s),o}a(Nr,"toPresenceNode");function Vu(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]),r=[];for(let o of[...n].sort()){if(Hu.has(o)||o==="id"||o==="z")continue;let s=e[o],i=t[o];if(Ki(s,i))continue;let d=$r(s),l=$r(i);if(Ji.has(o)&&(d!==null||l!==null)){let c=d??"",u=l??"";r.push({key:o,kind:"text",before:c,after:u,lines:Wi(c,u)});continue}r.push({key:o,kind:"scalar",before:s===void 0?null:s,after:i===void 0?null:i})}return r}a(Vu,"compareNodes");function Xu(e,t){let n=["label","disabled","info","env"],r=[];for(let o of n)Ki(e[o],t[o])||r.push(o);return r}a(Xu,"compareTabMeta");function Fr(e){return typeof e.label=="string"&&e.label.trim()?e.label.trim():""}a(Fr,"flowLabelOf");function Yi(e,t){let n=Gi(t),r=Gi(e),o=Fr(e)||Fr(t),s=Fr(t)||Fr(e),i=[],d=[],l=[];for(let c of e.nodes??[])n.has(c.id)||i.push(Nr(c,"added",o,r));for(let c of e.configs??[])n.has(c.id)||i.push(Nr(c,"added",o,r));for(let c of t.nodes??[])r.has(c.id)||d.push(Nr(c,"removed",s,n));for(let c of t.configs??[])r.has(c.id)||d.push(Nr(c,"removed",s,n));for(let[c,u]of r){let p=n.get(c);if(!p)continue;let f=Vu(p,u);if(f.length>0){let m={id:c,type:u.type||p.type,summary:Ju(u,o||s,f.length),changes:f},h=gt(u)??gt(p);h!==void 0&&(m.name=h),l.push(m)}}return l.sort((c,u)=>c.id.localeCompare(u.id)),{summary:{added:i.length,removed:d.length,modified:l.length,tabChanges:Xu(t,e),beforeNodeCount:n.size,afterNodeCount:r.size},added:i,removed:d,modified:l}}a(Yi,"buildDiffSections");function zi(e,t,n={}){let r=Yi(e.payload.flow,t.flow),o=n.currentRev??t.rev,s=e.expectedRev,i=n.baseRevStale??(e.status==="pending"&&s!==o),d={proposalId:e.id,flowId:e.payload.flowId,expectedRev:s,currentRev:o,baseRevStale:i,compareAgainst:n.compareAgainst??"current",...r};return e.warnings&&e.warnings.length>0&&(d.warnings=e.warnings.map(l=>({...l}))),n.baseSnapshotMissing&&(d.baseSnapshotMissing=!0),n.vsCurrent&&(d.vsCurrent=n.vsCurrent),d}a(zi,"buildProposalDiff");async function en(e,t){let n=e.payload.flowId,r={id:n,type:"tab",nodes:[],configs:[]},o=e.expectedRev,s=r;try{let c=await t.getFlow(n);o=c.rev,s=c.flow}catch(c){if(!(c instanceof G))throw c;try{o=(await t.getFlows()).rev}catch{}}let d=e.status==="pending"&&e.expectedRev!==o,l=e.baseFlow;if(l!==void 0){let c=d?Yi(e.payload.flow,s):void 0;return zi(e,{rev:e.expectedRev,flow:l},{currentRev:o,compareAgainst:"base",baseRevStale:d,...c?{vsCurrent:c}:{}})}return zi(e,{rev:o,flow:s},{compareAgainst:"current",baseSnapshotMissing:!0,baseRevStale:d})}a(en,"computeProposalDiff");Ae();Pe();ye();var Qi=require("path");function Mr(e){return(0,Qi.join)(e.userDir,".ai-access")}a(Mr,"getStorageDir");var Yu=["status:read","flows:read","proposals:create","proposals:read-own"];function Bo(){return Jt()}a(Bo,"readPackageVersion");function Qu(e,t=Date.now()){let n=Date.parse(e);return Number.isFinite(n)?Math.max(0,Math.ceil((n-t)/1e3)):0}a(Qu,"secondsRemainingUntil");function Zu(e,t=Date.now()){let n=Date.parse(e);return Number.isFinite(n)?Math.max(0,Math.ceil((n-t)/6e4)):0}a(Zu,"minutesRemainingUntil");function ep(e,t){let n=de(t),r=He(t),o=`${n}/openapi.yaml`,s=`${n}/openapi.json`;return e==="rest"?{api:n,openapi:o,openapiJson:s,discovery:n}:e==="mcp"?{rpc:r,discovery:n}:{discovery:n}}a(ep,"baseUrlsForTokenKind");function tn(e,t){let n=Gn(e,t),r=he(e.settings);if(t.auth){let o=t.auth.extra?.user;return{ok:!0,version:Bo(),adminAuthEnabled:n.adminAuthEnabled,aiAccessEnabled:r.enabled,aiFeaturesEnabled:n.aiFeaturesEnabled,allowWithoutAdminAuth:n.allowWithoutAdminAuth,permission:t.auth.scopes.includes("propose")?"propose":"read",capabilities:{view:!0,manageLinks:!1,approveProposals:!1},defaultLinkTtlSeconds:r.defaultLinkTtlSeconds,maxActiveLinks:r.maxActiveLinks,defaultDeploymentType:r.defaultDeploymentType,caller:{kind:"oauth",clientId:t.auth.clientId,user:typeof o=="string"?o:"owner",scopes:t.auth.scopes}}}if(t.accessLink){let o=Date.now();return{ok:!0,version:Bo(),adminAuthEnabled:n.adminAuthEnabled,aiAccessEnabled:r.enabled,aiFeaturesEnabled:n.aiFeaturesEnabled,allowWithoutAdminAuth:n.allowWithoutAdminAuth,permission:null,capabilities:{view:!0,manageLinks:!1,approveProposals:!1},defaultLinkTtlSeconds:r.defaultLinkTtlSeconds,maxActiveLinks:r.maxActiveLinks,defaultDeploymentType:r.defaultDeploymentType,caller:{kind:"access-link",linkId:t.accessLink.id,tokenKind:t.accessLink.kind,...t.accessLink.label!==void 0?{label:t.accessLink.label}:{},expiresAt:t.accessLink.expiresAt,minutesRemaining:Zu(t.accessLink.expiresAt,o),secondsRemaining:Qu(t.accessLink.expiresAt,o),permissions:[...Yu],baseUrls:ep(t.accessLink.kind,e.settings.httpAdminRoot)}}}return{ok:!0,version:Bo(),adminAuthEnabled:n.adminAuthEnabled,aiAccessEnabled:r.enabled,aiFeaturesEnabled:n.aiFeaturesEnabled,allowWithoutAdminAuth:n.allowWithoutAdminAuth,permission:n.permission,capabilities:n.capabilities,storageDir:Mr(e.settings),defaultLinkTtlSeconds:r.defaultLinkTtlSeconds,maxActiveLinks:r.maxActiveLinks,defaultDeploymentType:r.defaultDeploymentType,caller:{kind:"admin",...n.permission?{permission:n.permission}:{}}}}a(tn,"buildStatus");function Zi(e,t){let n=t.draftStore??new gr,r=t.rateLimiters??Pi(t.rateLimit??e.settings.aiAccess),o=e.httpAdmin,s="/mcp/a/:token",i=[r.ip,Ci(e,t.linkStore),r.token];o.use("/mcp/a",fi()),o.get(s,...i,P((u,p)=>{F(p),p.setHeader("X-Content-Type-Options","nosniff");let f=de(e.settings.httpAdminRoot),m=u.accessLink;if(!m){L(p,401,"Access link required","access_link_missing");return}let h=ki({link:m,openapiPath:`${f}/openapi.yaml`,restApiBase:f,mcpRpcPath:He(e.settings.httpAdminRoot),publicOrigin:to(u)});p.status(200).type("text/markdown; charset=utf-8").send(h)})),o.get(`${s}/status`,...i,P((u,p)=>{F(p),p.json(tn(e,u))})),o.get(`${s}/flows`,...i,P(async(u,p)=>{F(p);try{p.json(await pe(t.flowAdapter))}catch(f){let m=O(f);if(m){p.status(m.statusCode).json({...m.body,hint:"Retry after the owner fixes the plugin runtime."});return}L(p,502,f instanceof Error?f.message:String(f),"upstream_error")}})),o.get(`${s}/flows/:id`,...i,P(async(u,p)=>{F(p),await _r(t.flowAdapter,"get")(u,p)})),o.get(`${s}/nodes/:id`,...i,P(async(u,p)=>{F(p),await Pr(t.flowAdapter,"get")(u,p)})),o.get(`${s}/nodes/:id/field/:name`,...i,P(async(u,p)=>{F(p),await Rr(t.flowAdapter,"get")(u,p)})),o.get(`${s}/search`,...i,P(async(u,p)=>{F(p),await Cr(t.flowAdapter,"get")(u,p)}));let d={nodeTypes:t.nodeTypesAdapter,...t.catalogueFetcher?{catalogueFetcher:t.catalogueFetcher}:{},...t.catalogueUrl?{catalogueUrl:t.catalogueUrl}:{}};o.get(`${s}/node-types`,...i,P(async(u,p)=>{F(p),await Lr(d)(u,p)})),o.get(`${s}/node-types/:type`,...i,P(async(u,p)=>{F(p),await Er(d)(u,p)})),o.get(`${s}/palette/search`,...i,P(async(u,p)=>{F(p),await Ir(d)(u,p)}));let l=e.settings.httpAdminRoot;tp(o,s,i,t,l),o.get(`${s}/propose`,...i,P($i(t.proposalStore,t.auditLog,t.flowAdapter,l,t.nodeTypesAdapter))),op(o,s,i,n,t,l);let c=Ei();return o.all(`${s}/links`,c),o.all(`${s}/links/:id`,c),o.use("/mcp/a",Li()),n}a(Zi,"registerGetLinkRoutes");function tp(e,t,n,r,o){e.get(`${t}/proposals`,...n,P(async(s,i)=>{F(i);let d=s.accessLink;if(!d){L(i,401,"Access link required","access_link_missing");return}let l=q(s,o),c=oe(s.query),u=ur(s.query),p;try{p=(await pe(r.flowAdapter)).rev}catch{}let f=p?V(r.proposalStore,p):0,m=Ut(r.proposalStore,d,l,u);i.json({proposals:m.proposals.map(h=>ue(r.proposalStore,d,h,{...l,include:c},{includeConflicts:!1})),...ut({...m,returned:m.proposals.length}),...Q(f)})})),e.get(`${t}/proposals/:id`,...n,P(async(s,i)=>{F(i);let d=s.params.id,l=s.accessLink;if(typeof d!="string"||!d){L(i,400,"Missing proposal id","missing_proposal_id");return}if(!l){L(i,401,"Access link required","access_link_missing");return}try{let c=q(s,o),u=oe(s.query),p;try{p=(await pe(r.flowAdapter)).rev}catch{}let f=$e(r.proposalStore,l,d,c);i.json(ue(r.proposalStore,l,f,{...c,include:u},{...p?{liveRev:p}:{}}))}catch(c){let u=O(c,"get");if(u){i.status(u.statusCode).json({...u.body,hint:u.body.hint??"List proposals with GET \u2026/proposals."});return}$(i,c);return}})),np(e,t,n,r,o),rp(e,t,n,r,o)}a(tp,"registerGetLinkProposalRoutes");function np(e,t,n,r,o){e.get(`${t}/proposals/:id/withdraw`,...n,P((s,i)=>{F(i);let d=s.params.id,l=s.accessLink;if(typeof d!="string"||!d){L(i,400,"Missing proposal id","missing_proposal_id");return}if(!l){L(i,401,"Access link required","access_link_missing");return}let c=s.query.confirm;if(c!=="1"&&c!=="true"){L(i,400,"Withdraw over GET requires confirm=1","confirm_required",ie("confirm_required","get"));return}let u=typeof s.query.reason=="string"?s.query.reason:void 0,p=q(s,o),f=oe(s.query);try{let m=Bt(r.proposalStore,r.auditLog,l,d,u,p);i.json(ue(r.proposalStore,l,m,{...p,include:f}))}catch(m){let h=O(m,"get");if(h){let g=h.body.code;L(i,h.statusCode,Fe[g]??"Request failed",g,ie(g,"get"));return}L(i,500,"Internal server error","internal_error");return}}))}a(np,"registerGetLinkWithdrawRoute");function rp(e,t,n,r,o){e.get(`${t}/proposals/:id/diff`,...n,P(async(s,i)=>{F(i);let d=s.params.id,l=s.accessLink;if(typeof d!="string"||!d){L(i,400,"Missing proposal id","missing_proposal_id");return}if(!l){L(i,401,"Access link required","access_link_missing");return}try{let c=$e(r.proposalStore,l,d,q(s,o)),u=R(s.query,"format"),p=Vt(u==="json"||u==="md"?void 0:u);if(!p.ok){L(i,400,"Invalid diff format","invalid_diff_format","Valid formats: summary, full (default summary).");return}i.json(Xt(await en(c,r.flowAdapter),p.format))}catch(c){let u=O(c);if(u){i.status(u.statusCode).json(u.body);return}$(i,c);return}}))}a(rp,"registerGetLinkDiffRoute");function op(e,t,n,r,o,s){e.get(`${t}/draft/new`,...n,P(Ni(r,o.flowAdapter))),e.get(`${t}/draft/:draftId/append`,...n,P(Fi(r))),e.get(`${t}/draft/:draftId/submit`,...n,P(Oi(r,o.proposalStore,o.auditLog,o.flowAdapter,s,o.nodeTypesAdapter)))}a(op,"registerGetLinkDraftRoutes");z();var ea=require("module");function sp(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,ea.createRequire)(e)}a(sp,"createPluginRequire");function ip(e){try{let r=sp()("@node-red/runtime")._?.adminApi?.httpAdmin;if(r&&typeof r.use=="function"&&r!==e.httpAdmin)return r}catch{}return null}a(ip,"resolveEditorHttpAdmin");function ta(e){let t=[e.httpAdmin],n=ip(e);return n&&t.push(n),t}a(ta,"httpAdminAppsForMcpErrors");var na=Symbol.for("com55.ai-access.mcpJsonErrorHandler");function ra(e){return/\/mcp\/rpc\/?$/.test(e)||e.includes("/mcp/rpc")}a(ra,"isMcpRpcPath");function qo(e){return e.includes("/mcp")}a(qo,"isMcpPath");function ap(e){return e.path||e.url||e.originalUrl||""}a(ap,"requestPath");function dp(e){if(!e||typeof e!="object")return!1;let t=e;if(t.type==="entity.parse.failed"||e instanceof SyntaxError)return!0;let n=typeof e.message=="string"?e.message:"";return/JSON/i.test(n)&&(t.status===400||t.statusCode===400)}a(dp,"isJsonParseError");function lp(e){if(!e||typeof e!="object")return!1;let t=e;return t.type==="entity.too.large"||t.status===413||t.statusCode===413||t.message==="request entity too large"}a(lp,"isPayloadTooLarge");function cp(){return(e,t,n,r)=>{if(n.headersSent){r(e);return}let o=ap(t);if(dp(e)&&qo(o)){if(ra(o)){n.status(400).json({jsonrpc:"2.0",error:{code:-32700,message:"Parse error"},id:null});return}n.status(400).json({error:"Invalid JSON body",code:"invalid_json"});return}if(lp(e)&&qo(o)){n.status(413).json({error:`Request body exceeds limit of ${j} bytes`,code:"payload_too_large"});return}if(qo(o)){if(ra(o)){n.status(500).json({jsonrpc:"2.0",error:{code:-32603,message:hr(e).error,data:{code:"internal_error"}},id:null});return}n.status(500).json(hr(e));return}r(e)}}a(cp,"mcpJsonErrorHandler");function up(e){let t=e;t[na]||(t[na]=!0,e.use(cp()))}a(up,"attachOnce");function oa(e){for(let t of ta(e))up(t)}a(oa,"attachMcpJsonErrorHandlers");_n();var yt=class extends Error{static{a(this,"AccessLinkLimitError")}code="access_link_limit";statusCode=409;constructor(t){super(`Cannot create access link: at most ${t} active links are allowed. Revoke an existing link first.`),this.name="AccessLinkLimitError"}},wt=class extends Error{static{a(this,"AccessLinkNotFoundError")}code="access_link_not_found";statusCode=404;constructor(t){super(`Access link '${t}' was not found`),this.name="AccessLinkNotFoundError"}},Ln=class extends Error{static{a(this,"AccessLinkValidationError")}code="access_link_invalid";statusCode=400;constructor(t){super(t),this.name="AccessLinkValidationError"}},Ze=class extends Error{static{a(this,"AccessLinkTtlError")}code="invalid_ttl";statusCode=400;hint=pt;constructor(t){super(t),this.name="AccessLinkTtlError"}};var bt=require("crypto");_n();function Wo(e){return e==="get_link"||e==="rest"||e==="mcp"}a(Wo,"isAccessLinkKind");function Go(e){return e??"get_link"}a(Go,"resolveAccessLinkKind");function sa(e){return(0,bt.createHash)("sha256").update(e,"utf8").digest("hex")}a(sa,"hashToken");function pp(e,t){let n=Buffer.from(e,"utf8"),r=Buffer.from(t,"utf8");return n.length!==r.length?!1:(0,bt.timingSafeEqual)(n,r)}a(pp,"hashesEqual");function En(e){let t={id:e.id,kind:Go(e.kind),createdAt:new Date(e.createdAtMs).toISOString(),expiresAt:new Date(e.expiresAtMs).toISOString()};return e.label!==void 0&&(t.label=e.label),e.lastUsedAtMs!==void 0&&(t.lastUsedAt=new Date(e.lastUsedAtMs).toISOString()),e.createdBy!==void 0&&(t.createdBy=e.createdBy),t}a(En,"toPublic");var vt=class{static{a(this,"AccessLinkStore")}links=new Map;defaultTtlSeconds;maxActiveLinks;now;randomId;randomToken;constructor(t={}){this.defaultTtlSeconds=t.defaultTtlSeconds??14400,this.maxActiveLinks=t.maxActiveLinks??10,this.now=t.now??(()=>Date.now()),this.randomId=t.randomId??(()=>(0,bt.randomBytes)(12).toString("hex")),this.randomToken=t.randomToken??(()=>`aia_${(0,bt.randomBytes)(32).toString("base64url")}`)}configureDefaults(t){typeof t.defaultTtlSeconds=="number"&&Number.isFinite(t.defaultTtlSeconds)&&t.defaultTtlSeconds>0&&(this.defaultTtlSeconds=Math.floor(t.defaultTtlSeconds)),typeof t.maxActiveLinks=="number"&&Number.isFinite(t.maxActiveLinks)&&t.maxActiveLinks>0&&(this.maxActiveLinks=Math.floor(t.maxActiveLinks))}isActive(t,n){return t.revokedAtMs!==void 0?!1:t.expiresAtMs>n}list(){let t=this.now();return[...this.links.values()].filter(n=>this.isActive(n,t)).sort((n,r)=>r.createdAtMs-n.createdAtMs).map(En)}create(t={}){let n=this.now();if([...this.links.values()].filter(p=>this.isActive(p,n)).length>=this.maxActiveLinks)throw new yt(this.maxActiveLinks);let o=this.defaultTtlSeconds;if(t.ttlSeconds!==void 0){if(typeof t.ttlSeconds!="number"||!Number.isFinite(t.ttlSeconds)||t.ttlSeconds<=0)throw new Ze("ttlSeconds must be a positive number");o=Math.floor(t.ttlSeconds)}let s=typeof t.label=="string"&&t.label.trim().length>0?t.label.trim():void 0,i=Go(t.kind),d=Lo(i);if(o>d)throw new Ze(Eo(i,d));let l=this.randomId(),c=this.randomToken(),u={id:l,tokenHash:sa(c),kind:i,createdAtMs:n,expiresAtMs:n+o*1e3};return s!==void 0&&(u.label=s),t.createdBy!==void 0&&(u.createdBy=t.createdBy),this.links.set(l,u),{...En(u),token:c}}revoke(t){let n=this.links.get(t);if(!n)throw new wt(t);return n.revokedAtMs??=this.now(),En(n)}getById(t){if(typeof t!="string"||t.length===0)return null;let n=this.links.get(t);return n?En(n):null}validateResult(t){if(typeof t!="string"||t.length===0)return{status:"invalid"};let n=sa(t),r=this.now();for(let o of this.links.values())if(pp(o.tokenHash,n))return o.revokedAtMs!==void 0?{status:"invalid"}:o.expiresAtMs<=r?{status:"expired",expiresAt:new Date(o.expiresAtMs).toISOString()}:(o.lastUsedAtMs=r,{status:"active",link:En(o)});return{status:"invalid"}}validate(t){let n=this.validateResult(t);return n.status==="active"?n.link:null}size(){return this.links.size}clear(){this.links.clear()}upsertRecordForTest(t){let n={...t};delete n.token,this.links.set(t.id,n)}};_n();ae();ye();function zo(e,t){return t}a(zo,"storeForRequest");function aa(e){let t=typeof e.user?.username=="string"?e.user.username:void 0;return t?{kind:"admin",username:t}:{kind:"admin"}}a(aa,"adminActor");function Dr(e){let t=he(e.settings);return new vt({defaultTtlSeconds:t.defaultLinkTtlSeconds,maxActiveLinks:t.maxActiveLinks})}a(Dr,"createConfiguredLinkStore");function da(e){e.setHeader("Cache-Control","no-store"),e.setHeader("Pragma","no-cache")}a(da,"noStore");function la(e,t){return(n,r)=>{let s=zo(e,t).list();da(r),r.json({links:s})}}a(la,"listLinksHandler");function ca(e,t,n){return(r,o)=>{let s=r.body??{},i={};if(s.ttlSeconds!==void 0){if(typeof s.ttlSeconds!="number"){o.status(400).json({error:"ttlSeconds must be a positive number",code:"invalid_ttl",hint:pt});return}i.ttlSeconds=s.ttlSeconds}if(typeof s.label=="string"&&(i.label=s.label),s.kind!==void 0){if(!Wo(s.kind)){o.status(400).json({error:"kind must be one of: 'get_link', 'rest', 'mcp'",code:"invalid_link_kind"});return}i.kind=s.kind}typeof r.user?.username=="string"&&(i.createdBy=r.user.username);try{let d=zo(e,t).create(i),l=d.kind;if(n){let c=Date.parse(d.createdAt),u=Date.parse(d.expiresAt),p=Number.isFinite(c)&&Number.isFinite(u)&&u>c?Math.round((u-c)/1e3):void 0;n.append("link.created",aa(r),{linkId:d.id,label:d.label,expiresAt:d.expiresAt,createdAt:d.createdAt,kind:l,...p!==void 0?{ttlSeconds:p}:{}})}if(da(o),l==="get_link"){let c=xs(r,e.settings.httpAdminRoot,d.token);o.status(201).json({...d,url:c});return}o.status(201).json({...d})}catch(d){if(d instanceof yt){o.status(d.statusCode).json({error:d.message,code:d.code});return}if(d instanceof Ze){o.status(d.statusCode).json({error:d.message,code:d.code,hint:d.hint});return}if(d instanceof Ln){o.status(d.statusCode).json({error:d.message,code:d.code});return}$(o,d);return}}}a(ca,"createLinkHandler");function ua(e,t,n){return(r,o)=>{let s=r.params.id;if(typeof s!="string"||s.length===0){o.status(400).json({error:"Missing link id"});return}try{let i=zo(e,t).revoke(s);if(n){let d=Date.parse(i.createdAt),l=Date.parse(i.expiresAt),c=Number.isFinite(d)&&Number.isFinite(l)&&l>d?Math.round((l-d)/1e3):void 0;n.append("link.revoked",aa(r),{linkId:i.id,label:i.label,expiresAt:i.expiresAt,createdAt:i.createdAt,kind:i.kind,...c!==void 0?{ttlSeconds:c}:{}})}o.json({revoked:!0,link:i})}catch(i){if(i instanceof wt){o.status(i.statusCode).json({error:i.message,code:i.code});return}$(o,i);return}}}a(ua,"revokeLinkHandler");function fp(e){let t=Date.parse(e.createdAt),n=Date.parse(e.expiresAt);if(Number.isFinite(t)&&Number.isFinite(n)&&n>t)return Math.round((n-t)/1e3)}a(fp,"ttlSecondsFromLink");function mp(e,t){if(!t||e.action!=="link.created"&&e.action!=="link.revoked")return e;let n={...e.details??{}},r=typeof n.linkId=="string"?n.linkId:"";if(!r)return e;let o=n.label==null||n.label==="",s=n.expiresAt==null||n.expiresAt==="",i=n.createdAt==null||n.createdAt==="",d=n.ttlSeconds==null;if(!o&&!s&&!i&&!d)return e;let l=t.getById(r);if(!l)return e;if(o&&l.label&&(n.label=l.label),s&&(n.expiresAt=l.expiresAt),i&&(n.createdAt=l.createdAt),d){let c=fp(l);c!==void 0&&(n.ttlSeconds=c)}return{...e,details:n}}a(mp,"enrichAuditLogEntry");function pa(e,t){return(n,r)=>{let o=n.query.limit,s=100;if(typeof o=="string"&&o.length>0){let d=Number(o);Number.isFinite(d)&&d>0&&(s=Math.min(Math.floor(d),500))}let i=e.list(s).map(d=>mp(d,t));r.json({logs:i})}}a(pa,"listLogsHandler");var Jo=require("@modelcontextprotocol/sdk/server/mcp.js"),fa=require("@modelcontextprotocol/sdk/server/streamableHttp.js"),T=require("zod");qe();z();ot();K();Ne();lt();Je();Ae();function ma(){return"0.2.0-beta.2"}a(ma,"pluginVersion");async function ha(e){try{return(await pe(e)).rev}catch{return}}a(ha,"tryLiveRev");function Z(e){return{content:[{type:"text",text:JSON.stringify(e)}]}}a(Z,"jsonResult");function Ko(e){return e===!0?new Set(["payload","baseFlow"]):new Set}a(Ko,"includeSetFromPayloadFlag");function ee(e,t=500){let n=Tn(e,"mcp");if(n)return{isError:!0,content:[{type:"text",text:JSON.stringify({...n.body,statusCode:n.statusCode})}]};let r=e instanceof Error?e.message:String(e);return{isError:!0,content:[{type:"text",text:JSON.stringify({error:r,code:"internal_error",statusCode:t})}]}}a(ee,"jsonError");var hp=new Set(["list_proposals","list_my_proposals"]),gp=new Set(["status","limit","offset","mine","flowId"]);function yp(e){return!!e&&typeof e=="object"&&typeof e.safeParseAsync=="function"}a(yp,"isZodLikeSchema");function wp(e,t,n){let r=t&&typeof t=="object"?t:{},o=n[0],s=o?.path&&o.path.length>0?String(o.path[0]):"(arguments)";if(hp.has(e)&&gp.has(s))return Ht(s,r[s]);let i=n.map(l=>({parameter:l.path&&l.path.length>0?l.path.map(String).join("."):"(arguments)",message:l.message??"Invalid value"})),d=i.length===1?`Invalid argument '${i[0]?.parameter}' for tool ${e}: ${i[0]?.message}`:`${i.length} invalid arguments for tool ${e}`;return new y(d,"invalid_arguments",{details:{parameter:s,errors:i,hint:`Fix '${s}' to match the ${e} input schema (see tools/list).`}})}a(wp,"mcpArgumentError");function ga(e){let t=e,n=t.validateToolInput;typeof n=="function"&&(t.validateToolInput=async(r,o,s)=>{let i=r.inputSchema;if(!yp(i))return n.call(e,r,o,s);let d=await i.safeParseAsync(o??{});if(d.success)return d.data;let l=Tn(wp(s,o,d.error.issues),"mcp"),c=l?{...l.body,statusCode:l.statusCode}:{};throw new Error(JSON.stringify(c))})}a(ga,"installMcpArgumentErrorMapping");function ya(e,t,n,r){e.registerTool("get_status",{description:'Return plugin status (version, AI feature flags, storage path, deploy defaults). For access-link callers, permission is null and caller.kind is "access-link" \u2014 that is expected, not an error. Use before other tools to confirm the plugin is enabled.'},()=>{try{return Z(tn(t,r))}catch(o){return ee(o)}}),e.registerTool("list_flows",{description:"List all flows as a flat Admin API v2 array plus the current rev string. Returns { rev, flows }. Call this (or get_flow) before submit_proposal so expectedRev matches."},async()=>{try{return Z(await pe(n.flowAdapter))}catch(o){return ee(o,502)}}),e.registerTool("get_flow",{description:"Get one tab/subflow by id with nested nodes/configs and the current rev. Pass summary=true for a lightweight node list (id/type/name/wires/bytes, no field contents). Prefer summary \u2192 search_flows \u2192 get_node_field(lines) \u2192 replace_text \u2192 submit_proposal. Returns { rev, flow } or summary shape. Use rev as expectedRev when submitting.",inputSchema:{id:T.z.string().min(1).describe("Flow / tab id"),summary:T.z.boolean().optional().describe("When true, return node list with sizes only (no field contents)")}},async({id:o,summary:s})=>{try{return Z(s?await vr(n.flowAdapter,o):await xe(n.flowAdapter,o))}catch(i){return ee(i,502)}}),e.registerTool("get_node",{description:"Get one full node by id (includes owning flowId when known). Optional flow scopes the lookup.",inputSchema:{id:T.z.string().min(1).describe("Node id"),flow:T.z.string().min(1).optional().describe("Optional tab id to scope the lookup")}},async({id:o,flow:s})=>{try{return Z(await kr(n.flowAdapter,o,s))}catch(i){return ee(i,502)}}),e.registerTool("get_node_field",{description:'Get one string field from a node. Optional lines="A-B" (1-based inclusive) returns a slice; response always includes totalLines and bytes.',inputSchema:{id:T.z.string().min(1).describe("Node id"),field:T.z.string().min(1).describe("Field name (e.g. template, func)"),lines:T.z.string().optional().describe("Optional 1-based line range A or A-B"),flow:T.z.string().min(1).optional().describe("Optional tab id to scope the lookup")}},async({id:o,field:s,lines:i,flow:d})=>{try{return Z(await Ar(n.flowAdapter,o,s,{...d?{flowId:d}:{},...i?{lines:i}:{}}))}catch(l){return ee(l,502)}}),e.registerTool("search_flows",{description:"Text search across string fields. Returns hits with node id, field, line numbers, and a short snippet. Results are capped; truncated=true when the cap is hit. Optional flow scopes to one tab.",inputSchema:{q:T.z.string().describe("Substring to find (case-sensitive; non-empty required)"),flow:T.z.string().min(1).optional().describe("Optional tab id")}},async({q:o,flow:s})=>{try{return Z(await xr(n.flowAdapter,o,s?{flowId:s}:{}))}catch(i){return ee(i,502)}}),e.registerTool("list_node_types",{description:"List installed Node-RED palette node types (type, module, category, description, \u2026). Optional q filters type/module/category/description (case-insensitive). Results are capped. Call this before proposing nodes with unfamiliar types.",inputSchema:{q:T.z.string().optional().describe("Optional case-insensitive filter")}},async({q:o})=>{try{return Z(await rr(n.nodeTypesAdapter,o?{q:o}:{}))}catch(s){return ee(s,502)}}),e.registerTool("get_node_type",{description:"Get detail for one installed node type: defaults (JSON), inputs/outputs (always set), requiredConfigNodeTypes, help sections (summary/inputs/outputs), category/module/color. Types are case-sensitive. Pass includeHelp=false to omit help. 404 node_type_not_found when unknown (didYouMean on case mismatch; else nearest names).",inputSchema:{type:T.z.string().min(1).describe("Node type id (e.g. inject, template) \u2014 case-sensitive"),includeHelp:T.z.boolean().optional().describe("When false, omit help sections (default true)")}},async({type:o,includeHelp:s})=>{try{return Z(await dt(n.nodeTypesAdapter,o,{includeHelp:s!==!1}))}catch(i){return ee(i,502)}}),e.registerTool("search_palette",{description:"Search the public Node-RED catalogue for modules to recommend. Returns module id, description, version, updated, link, and installed boolean (cross-checked against installed modules). Core node-red types are not in the catalogue \u2014 use list_node_types. RECOMMEND ONLY \u2014 never installs.",inputSchema:{q:T.z.string().describe("Search string (id / description / keywords; non-empty required)")}},async({q:o})=>{try{return Z(await or(n.nodeTypesAdapter,o,{...n.catalogueFetcher?{catalogueFetcher:n.catalogueFetcher}:{},...n.catalogueUrl?{catalogueUrl:n.catalogueUrl}:{}}))}catch(s){return ee(s,502)}})}a(ya,"registerReadTools");function wa(e,t,n,r,o){let s=q(r,t.settings.httpAdminRoot);e.registerTool("submit_proposal",{description:`Submit a replace_flow proposal for owner approval (does not deploy). Preferred for small string edits: payload: { flowId, nodes: [{ op:"replace_text", node, field, find, replace, occurrence? }] } \u2014 find must be unique unless occurrence (1-based) is set; multiple replace_text ops apply in order; yields a short readable diff. Or send a full tab: payload.flow from get_flow (id, type, label?, nodes[], configs?). Inputs (all top-level, next to each other): type="replace_flow", expectedRev (required string from list_flows/get_flow rev; expected_rev alias accepted), optional title (\u226480 chars), and payload: { flowId, flow? } and/or payload.nodes / top-level nodes. Do not nest expectedRev inside payload. Payload size limit is ${j} bytes (413 if exceeded). expectedRev is checked against the live rev at submit time (409 if stale). replace_text errors: 400 text_not_found; 409 ambiguous_match with matchCount. Unknown palette types return 400 unknown_node_type with nearest matches \u2014 call list_node_types / search_palette and ask the user to install via Manage palette (never auto-install). Returns a lean proposal (id, status, title, storedTitle, flowLabel, displayName, reviewUrl, changeSummary, \u2026) by default; title may gain " in <flow>"; storedTitle is the title as saved (your title without that suffix, or the auto-generated title, which may already name the flow). Pass include_payload=true to include payload and baseFlow. When telling the user about the proposal, cite displayName and reviewUrl \u2014 not the raw id. Humans approve on the /mcp page (reviewUrl opens/scrolls to it).`,inputSchema:{type:T.z.literal("replace_flow").describe("Must be 'replace_flow'"),expectedRev:T.z.string().min(1).optional().describe("Top-level current flow revision from get_flow/list_flows (required at submit; sit next to type and payload, not inside payload)"),expected_rev:T.z.string().min(1).optional().describe("Alias for expectedRev (snake_case); prefer expectedRev"),title:T.z.string().max(80).optional().describe("Optional short human title (\u226480 chars). Auto-generated from changes when omitted."),include_payload:T.z.boolean().optional().describe("When true, include payload and baseFlow in the response (default lean)"),nodes:T.z.array(T.z.record(T.z.string(),T.z.unknown())).optional().describe("Optional node ops (set/add/remove/replace_text). Same as payload.nodes when flow is omitted."),payload:T.z.object({flowId:T.z.string().min(1).describe("Tab id to replace"),flow:T.z.object({id:T.z.string().optional(),type:T.z.string().optional().describe("Usually 'tab' (included by get_flow)"),label:T.z.string().optional(),nodes:T.z.array(T.z.record(T.z.string(),T.z.unknown())).optional().describe("Child nodes for the tab (from get_flow.nodes)"),configs:T.z.array(T.z.record(T.z.string(),T.z.unknown())).optional()}).passthrough().optional().describe("Full tab from get_flow; omit when using nodes ops"),nodes:T.z.array(T.z.record(T.z.string(),T.z.unknown())).optional().describe("Node ops applied to the live tab (prefer replace_text for small string edits)")}).describe("Replacement flow payload and/or node ops")}},async i=>{try{let d=await Ke(n.proposalStore,n.auditLog,o,{...i,expectedRev:typeof i.expectedRev=="string"&&i.expectedRev.length>0?i.expectedRev:i.expected_rev},n.flowAdapter,s,n.nodeTypesAdapter),l={...ce(d,{...s,include:Ko(i.include_payload)}),mine:!0},c=V(n.proposalStore,d.expectedRev,{flowId:d.payload.flowId}),u=Ie(Ee(n.proposalStore,n.proposalStore.get(d.id),o.id));return Z({...l,...u,...Q(c)})}catch(d){return ee(d)}}),vp(e,n,o,s),kp(e,n,o,s)}a(wa,"registerProposalTools");function bp(e,t,n,r){let o={mine:T.z.boolean().optional().describe("true: only proposals created by this access link; false: only other links\u2019 proposals"),status:T.z.union([T.z.string(),T.z.array(T.z.string())]).optional().describe("Filter by proposal status: one value, an array, or a comma-separated string (pending, approved, rejected, failed, withdrawn, expired, superseded). An empty array or empty string means no status filter."),flowId:T.z.string().optional().describe("Filter by flow tab id (payload.flowId)"),limit:T.z.number().int().min(1).max(jt).optional().describe(`Page size (1\u2013${jt}). Omit to return all matches.`),offset:T.z.number().int().min(0).optional().describe("Skip this many filtered results (default 0)"),include_payload:T.z.boolean().optional().describe("When true, include payload and baseFlow on each proposal")},s=a(async i=>{try{let d=Ko(i.include_payload),l=await ha(t.flowAdapter),c=l?V(t.proposalStore,l):0,u={};typeof i.mine=="boolean"&&(u.mine=i.mine);let p=Co(i.status);p&&(u.status=p),typeof i.flowId=="string"&&i.flowId.length>0&&(u.flowId=i.flowId),typeof i.limit=="number"&&(u.limit=i.limit),typeof i.offset=="number"&&(u.offset=i.offset);let f=Ut(t.proposalStore,n,r,u);return Z({proposals:f.proposals.map(m=>ue(t.proposalStore,n,m,{...r,include:d},{includeConflicts:!1})),...ut({...f,returned:f.proposals.length}),...Q(c)})}catch(d){return ee(d)}},"listProposalsHandler");e.registerTool("list_proposals",{description:"List proposals visible to this access link (all proposals by default). Each item includes mine:true|false for the caller. Filter with mine, status, and/or flowId; paginate with limit/offset (response includes total, offset, hasMore, nextOffset). Invalid filters return invalid_filter. Returns lean { proposals, total, \u2026 } with title, displayName, reviewUrl, changeSummary (no payload/baseFlow unless include_payload=true). Prefer displayName + reviewUrl when referring to a proposal with the user (not the raw id).",inputSchema:o},s),e.registerTool("list_my_proposals",{description:"Alias of list_proposals. Prefer list_proposals. Lists proposals visible to this access link with optional mine/status/flowId filters and limit/offset pagination.",inputSchema:o},s)}a(bp,"registerListProposalsTools");function vp(e,t,n,r){bp(e,t,n,r),e.registerTool("get_proposal",{description:"Get any proposal by id (all tokens can read). Response includes mine:true|false and, for pending proposals, conflictingProposals when other pending work touches the same flow. Lean by default (no payload/baseFlow); pass include_payload=true for full bodies. Cite displayName + reviewUrl when talking to the user.",inputSchema:{id:T.z.string().min(1).describe("Proposal id (prop-\u2026)"),include_payload:T.z.boolean().optional().describe("When true, include payload and baseFlow")}},async({id:o,include_payload:s})=>{try{let i=await ha(t.flowAdapter),d=$e(t.proposalStore,n,o,r);return Z(ue(t.proposalStore,n,d,{...r,include:Ko(s)},{...i?{liveRev:i}:{}}))}catch(i){return ee(i)}})}a(vp,"registerProposalQueryTools");function kp(e,t,n,r){e.registerTool("get_proposal_diff",{description:"Diff any proposal against its stored baseFlow snapshot (what changed). Optional format=summary|full (default summary). Pending + stale also includes vsCurrent.",inputSchema:{id:T.z.string().min(1).describe("Proposal id (prop-\u2026)"),format:T.z.enum(["summary","full"]).optional().describe("Diff payload format (default summary)")}},async({id:o,format:s})=>{try{let i=$e(t.proposalStore,n,o),d=Vt(s??"summary");if(!d.ok)return ee(new y("Invalid diff format","invalid_diff_format",{details:{hint:"Valid formats: summary, full (default summary)."}}));let l=Xt(await en(i,t.flowAdapter),d.format);return Z(l)}catch(i){return ee(i)}}),e.registerTool("withdraw_proposal",{description:"Withdraw one of YOUR pending proposals (status becomes withdrawn, not rejected). Optional reason. Only the creating access link can withdraw; other tokens get 403. Cite displayName + reviewUrl when telling the user.",inputSchema:{id:T.z.string().min(1).describe("Proposal id (prop-\u2026) owned by this access link"),reason:T.z.string().optional().describe("Optional withdraw reason")}},({id:o,reason:s})=>{try{let i=Bt(t.proposalStore,t.auditLog,n,o,s,r);return Z(ue(t.proposalStore,n,i,r))}catch(i){return ee(i)}})}a(kp,"registerProposalActionTools");function Ap(e,t,n){let r=new Jo.McpServer({name:"node-red-ai-access",version:ma()});ga(r);let o=n.accessLink;if(!o)throw new Error("Access link required");return ya(r,e,t,n),wa(r,e,t,n,o),r}a(Ap,"createAiMcpServer");function ba(e,t){return async(n,r,o)=>{try{if(!n.accessLink&&!n.auth){r.setHeader("WWW-Authenticate",Y),r.status(401).json({error:le,code:Re});return}let s=n.accessLink?Ap(e,t,n):xp(e,t,n),i=new fa.StreamableHTTPServerTransport({enableJsonResponse:!0});await s.connect(i),await i.handleRequest(n,r,n.body),r.on("close",()=>{i.close(),s.close()})}catch(s){if(!r.headersSent){$(r,s);return}o(s instanceof Error?s:new Error(String(s)))}}}a(ba,"mcpRpcPostHandler");function xp(e,t,n){let r=new Jo.McpServer({name:"node-red-ai-access",version:ma()});if(ga(r),ya(r,e,t,n),(n.auth?.scopes??[]).includes("propose")){let s=typeof n.auth?.extra?.user=="string"?n.auth.extra.user:"owner",i=n.auth?.clientId??"",l={id:so(i,s),kind:"mcp",createdAt:new Date().toISOString(),expiresAt:new Date((n.auth?.expiresAt??0)*1e3).toISOString(),createdBy:s,oauthClientId:i,oauthUser:s};wa(r,e,t,n,l)}return r}a(xp,"createOAuthMcpServer");function Vo(e,t){t.status(405).set("Allow","POST").json({jsonrpc:"2.0",error:{code:-32e3,message:"Method not allowed. Use POST for Streamable HTTP."},id:null})}a(Vo,"mcpRpcMethodNotAllowed");var Nn=require("crypto"),rn=require("fs"),Qo=require("path"),B=require("@modelcontextprotocol/sdk/server/auth/errors.js"),Aa=require("@modelcontextprotocol/sdk/server/auth/handlers/authorize.js"),Zo=require("@modelcontextprotocol/sdk/server/auth/handlers/metadata.js"),xa=require("@modelcontextprotocol/sdk/server/auth/handlers/register.js"),Sa=require("@modelcontextprotocol/sdk/server/auth/handlers/revoke.js"),Ta=require("@modelcontextprotocol/sdk/server/auth/handlers/token.js"),_a=require("@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js"),Pa=require("@modelcontextprotocol/sdk/server/auth/router.js"),Ra=an(require("express"),1);z();Pe();var va=`(function () {
|
|
1198
1377
|
var main = document.querySelector('main');
|
|
1199
1378
|
var id = main.getAttribute('data-request');
|
|
1200
1379
|
var key = main.getAttribute('data-token-key');
|
|
@@ -1255,7 +1434,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
1255
1434
|
deny.addEventListener('click', function () { decide('deny'); });
|
|
1256
1435
|
retry.addEventListener('click', load);
|
|
1257
1436
|
load();
|
|
1258
|
-
})();`;_e();var Qe=a(e=>(0,Fn.createHash)("sha256").update(e).digest("hex"),"hash"),bt=a(()=>(0,Fn.randomBytes)(32).toString("base64url"),"secret"),bp=36e5,vp=100,kp=5,Ap=500,ya=a(()=>({clients:[],codes:[],tokens:[]}),"emptyDb"),Hr=class{constructor(t,n){this.maxClients=n;this.path=(0,Zo.join)(t.root,"oauth.json");try{let r=(0,nn.existsSync)(this.path)?JSON.parse((0,nn.readFileSync)(this.path,"utf8")):ya();if(!r||typeof r!="object"||!("clients"in r)||!("codes"in r)||!("tokens"in r)||!Array.isArray(r.clients)||!Array.isArray(r.codes)||!Array.isArray(r.tokens))throw new Error("Invalid OAuth store");this.db=r}catch{this.db=ya()}(0,nn.mkdirSync)(t.root,{recursive:!0,mode:448}),this.prune()}static{a(this,"OAuthStore")}db;path;save(){xt((0,Zo.join)(this.path,".."),this.path,De(this.db,8e6,{kind:"oauth",pretty:!0}))}prune(t=Date.now()){let n=this.db.clients.length+this.db.codes.length+this.db.tokens.length,r=new Set(this.db.clients.filter(o=>o.approved?(o.lastUsedAt??(o.client_id_issued_at??0)*1e3)>t-30*864e5:(o.client_id_issued_at??0)*1e3>t-bp).map(o=>o.client_id));this.db.clients=this.db.clients.filter(o=>r.has(o.client_id)),this.db.codes=this.db.codes.filter(o=>o.expires>t&&r.has(o.clientId)),this.db.tokens=this.db.tokens.filter(o=>r.has(o.clientId)&&o.expires>t),this.db.clients.length+this.db.codes.length+this.db.tokens.length!==n&&this.save()}getClient(t){return this.prune(),this.db.clients.find(n=>n.client_id===t)}registerClient(t){if(this.prune(),this.approvedCount()>=this.maxClients())throw new H.InvalidClientMetadataError("Maximum active OAuth clients reached");if(this.db.clients.length-this.approvedCount()>=vp)throw new H.InvalidClientMetadataError("Too many OAuth clients awaiting consent");if(!Array.isArray(t.redirect_uris)||!t.redirect_uris.length||t.redirect_uris.some(r=>{try{let o=new URL(r);return!!o.username||!!o.password||!!o.hash||o.protocol!=="https:"&&!(o.protocol==="http:"&&["localhost","127.0.0.1","[::1]"].includes(o.hostname))}catch{return!0}}))throw new H.InvalidClientMetadataError("Redirect URIs must use HTTPS or loopback HTTP");if(t.token_endpoint_auth_method&&t.token_endpoint_auth_method!=="none")throw new H.InvalidClientMetadataError("Only public clients are supported");let n={...t,client_secret:void 0,client_secret_expires_at:void 0,client_id:bt(),client_id_issued_at:Math.floor(Date.now()/1e3),token_endpoint_auth_method:"none",lastUsedAt:Date.now()};return this.db.clients.push(n),this.save(),n}approvedCount(){return this.db.clients.filter(t=>t.approved).length}approve(t){let n=this.db.clients.find(r=>r.client_id===t);if(!(!n||n.approved)){if(this.approvedCount()>=this.maxClients())throw new H.InvalidClientMetadataError("Maximum active OAuth clients reached");n.approved=!0,this.save()}}get data(){return this.db}persist(){this.touchTimer&&(clearTimeout(this.touchTimer),this.touchTimer=void 0),this.save()}touchTimer;touch(){this.touchTimer||(this.touchTimer=setTimeout(()=>{this.touchTimer=void 0,this.save()},5e3),this.touchTimer.unref?.())}revokeClient(t){let n=this.db.clients.length;return this.db.clients=this.db.clients.filter(r=>r.client_id!==t),this.db.tokens=this.db.tokens.filter(r=>r.clientId!==t),this.db.codes=this.db.codes.filter(r=>r.clientId!==t),this.save(),this.db.clients.length!==n}};function rn(e,t=["/.well-known/oauth-","/mcp/oauth/"]){let n=[],r=a(s=>{try{return e?.[s]}catch{return}},"read"),o=r("_router")?.stack??r("router")?.stack??[];for(let s of o){if(s.handle?.oauthOwned)continue;let i=s.route?.path?Array.isArray(s.route.path)?s.route.path:[s.route.path]:[];for(let l of i)t.some(c=>String(l).startsWith(c))&&n.push(`${String(l)} (${s.name??"route"})`);let d=s.matchers??(s.regexp?[l=>s.regexp?.test(l)]:void 0);if(!i.length&&d?.length&&s.name!=="router"){let l=a(u=>d.some(p=>!!p(u)),"matches");!l("/unrelated-ai-access-collision-probe")&&t.some(u=>l(`${u}token`)||l(`${u}mcp`))&&n.push(`mounted middleware (${s.name??"route"})`)}s.handle?.stack&&n.push(...rn({router:{stack:s.handle.stack}},t))}return n}a(rn,"detectOAuthRouteCollisions");var Ur=class{constructor(t,n,r,o,s=()=>0){this.clientsStore=t;this.resource=n;this.accessTtl=r;this.refreshTtl=o;this.refreshMax=s}static{a(this,"LocalOAuthProvider")}skipLocalPkceValidation=!1;inflight=new Map;async authorize(t,n,r){throw new H.InvalidGrantError("Admin consent is required")}async challengeForAuthorizationCode(t,n){let r=this.findCode(t,n);return this.clientsStore.data.codes=this.clientsStore.data.codes.filter(o=>o!==r),this.clientsStore.persist(),this.inflight.set(r.hash,r),setTimeout(()=>this.inflight.delete(r.hash),3e4).unref?.(),r.challenge}async exchangeAuthorizationCode(t,n,r,o,s){let i=this.inflight.get(Qe(n))??this.findCode(t,n);if(this.inflight.delete(i.hash),this.clientsStore.data.codes=this.clientsStore.data.codes.filter(d=>d!==i),this.clientsStore.persist(),i.clientId!==t.client_id||i.expires<=Date.now()||o!==i.redirectUri||s&&Ze(s.href)!==i.resource)throw new H.InvalidGrantError("Invalid authorization code");return this.issue(t.client_id,i.scopes,i.resource,i.user)}async exchangeRefreshToken(t,n,r,o){let s=this.clientsStore.data.tokens.find(l=>l.hash===Qe(n)&&l.type==="refresh"&&l.clientId===t.client_id);if(!s||s.expires<=Date.now())throw new H.InvalidGrantError("Invalid refresh token");if(s.rotated)throw this.clientsStore.data.tokens=this.clientsStore.data.tokens.filter(l=>l.family!==s.family),this.clientsStore.persist(),new H.InvalidGrantError("Refresh token reuse detected");let i=this.refreshMax()*1e3,d=s.familyStart??Date.now();if(i>0&&d+i<=Date.now())throw new H.InvalidGrantError("Refresh token family reached its maximum lifetime");if(o&&Ze(o.href)!==s.resource)throw new H.InvalidGrantError("Resource mismatch");if(r?.some(l=>!s.scopes.includes(l)))throw new H.InvalidGrantError("Invalid scope");return s.rotated=!0,this.issue(t.client_id,r??s.scopes,s.resource,s.user,s.family,d)}async verifyAccessToken(t){let n=this.clientsStore.data.tokens.find(o=>o.hash===Qe(t)&&o.type==="access"&&o.expires>Date.now());if(!n||n.resource!==Ze(this.resource()))throw new H.InvalidTokenError("Invalid access token");let r=this.clientsStore.data.clients.find(o=>o.client_id===n.clientId);if(!r)throw new H.InvalidTokenError("Invalid client");return r.lastUsedAt=Date.now(),this.clientsStore.touch(),{token:t,clientId:n.clientId,scopes:n.scopes,expiresAt:Math.floor(n.expires/1e3),resource:new URL(n.resource),extra:{user:n.user}}}async revokeToken(t,n){let r=this.clientsStore.data.tokens.find(o=>o.hash===Qe(n.token)&&o.clientId===t.client_id);r&&(this.clientsStore.data.tokens=this.clientsStore.data.tokens.filter(o=>o.family!==r.family)),this.clientsStore.persist()}createCode(t,n,r){let o=bt();return this.clientsStore.data.codes.push({hash:Qe(o),clientId:t.client_id,challenge:n.codeChallenge,redirectUri:n.redirectUri,scopes:n.scopes??["read"],resource:this.validateResource(n.resource),expires:Date.now()+6e4,user:r}),this.clientsStore.persist(),o}validateResource(t){let n=Ze(this.resource());if(t&&Ze(t.href)!==n)throw new H.InvalidGrantError("Resource mismatch");return n}findCode(t,n){let r=this.clientsStore.data.codes.find(o=>o.hash===Qe(n)&&o.clientId===t.client_id);if(!r||r.expires<=Date.now())throw new H.InvalidGrantError("Invalid or expired authorization code");return r}issue(t,n,r,o,s=bt(),i=Date.now()){let d=bt(),l=bt(),c=Date.now(),u=this.refreshMax()*1e3,p=Math.min(c+this.refreshTtl()*1e3,u>0?i+u:1/0),f=this.clientsStore.data,m=f.clients.find(h=>h.client_id===t);return m&&(m.lastUsedAt=c),f.tokens.push({hash:Qe(d),type:"access",clientId:t,family:s,scopes:n,resource:r,expires:c+this.accessTtl()*1e3,user:o,rotated:!1},{hash:Qe(l),type:"refresh",clientId:t,family:s,scopes:n,resource:r,expires:p,user:o,rotated:!1,familyStart:i}),this.clientsStore.persist(),{access_token:d,token_type:"Bearer",expires_in:this.accessTtl(),refresh_token:l,scope:n.join(" ")}}};function Ze(e){let t=new URL(e);if(!["http:","https:"].includes(t.protocol)||t.username||t.password||t.search||t.hash)throw new Error("Invalid resource URL");return`${t.protocol}//${t.host}${t.pathname.replace(/\/+$/,"")}`}a(Ze,"canonicalResource");var tn=["read","propose"],xp=new Set(["localhost","127.0.0.1","[::1]"]);function Nn(e){return e.replace(/[&<>"']/g,t=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[t])}a(Nn,"escapeHtml");function wt(e){return typeof e=="string"?e:""}a(wt,"value");function Ta(e,t,n,r){let o=a(y=>`${n().replace(/\/$/,"")}${y}`,"abs"),s=a((y,w,k,T)=>{let _=w.url;w.url="/",y(w,k,C=>{w.url=_,T(C)})},"atRoot"),i=new Map,d=a(()=>{let y=Date.now();for(let[w,k]of i)k.expires<=y&&i.delete(w)},"sweep"),l=a(y=>{d();let w=[...i].filter(([,k])=>k.clientId===y);for(;w.length>=kp;)i.delete(w.shift()[0]);return i.size<Ap},"reservePending"),c=a((y,w)=>{y.setHeader("Cache-Control","no-store"),y.setHeader("X-Frame-Options","DENY"),y.setHeader("Referrer-Policy","no-referrer"),y.setHeader("X-Content-Type-Options","nosniff"),y.setHeader("Content-Security-Policy",`default-src 'none'; ${w?`script-src 'nonce-${w}'; `:""}style-src 'unsafe-inline'; connect-src 'self'; form-action 'none'; base-uri 'none'; frame-ancestors 'none'`)},"securityHeaders"),u=Kn(e.settings.httpAdminRoot),p=a(y=>{if(!V(e.settings))return[...tn];let w=Pt(y);return w==="*"?[...tn]:w==="read"?["read"]:[]},"grantableFor"),f=a(y=>{let w=y.get("sec-fetch-site");if(w&&w!=="same-origin")return!1;let k=y.get("origin");if(!k)return!0;let T=new Set;try{T.add(new URL(n()).origin)}catch{}let _=y.get("host");return _&&(T.add(`http://${_}`),T.add(`https://${_}`)),T.has(k)},"sameOrigin"),m=a((y,w)=>{d();let k=y.query,T=t.clientsStore.getClient(wt(k.client_id));if(!T){w.status(400).send("Unknown OAuth client");return}let _=wt(k.redirect_uri)||(T.redirect_uris.length===1?T.redirect_uris[0]:"");if(!T.redirect_uris.some(Kr=>(0,wa.redirectUriMatches)(_,Kr))){w.status(400).send("Unregistered redirect URI");return}if(k.response_type!=="code"||k.code_challenge_method!=="S256"||!/^[A-Za-z0-9_-]{43,128}$/.test(wt(k.code_challenge))){w.status(400).send("Authorization code with PKCE S256 is required");return}let C=[...new Set((wt(k.scope)||"read").split(" ").filter(Boolean))];if(C.some(Kr=>!tn.includes(Kr))){w.status(400).send("Unsupported scope");return}let Z=wt(k.resource)||r(),he;try{he=Ze(Z)}catch{w.status(400).send("Invalid resource");return}if(he!==Ze(r())){w.status(400).send("Resource mismatch");return}if(!l(T.client_id)){w.status(429).send("Too many pending authorization requests; try again later");return}let sn=bt();i.set(sn,{clientId:T.client_id,redirect:_,challenge:wt(k.code_challenge),scopes:C,resource:Z,state:wt(k.state),csrf:bt(),expires:Date.now()+10*6e4});let kt=new URL(_).host,At=(0,Fn.randomBytes)(16).toString("base64");c(w,At),w.status(200).type("html").send(`<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Authorize MCP client</title><style>body{font-family:system-ui,sans-serif;max-width:560px;margin:40px auto;padding:0 16px}h1{font-size:1.4em;word-break:break-all}.warn{color:#9b2c2c}.muted{color:#555}button{margin-right:8px;padding:6px 14px}</style><main data-request="${Nn(sn)}" data-token-key="${Nn(u)}"><p class="muted">An MCP client wants to access Node-RED and will receive tokens at:</p><h1>${Nn(kt)}</h1>${xp.has(new URL(_).hostname)?'<p class="warn">Warning: this redirect URI is reachable only on this machine.</p>':""}<p class="muted">Client-provided name (unverified): ${Nn(T.client_name||"Unnamed client")}</p><p>Requested permissions: ${Nn(C.join(", "))}</p><p id="status">Checking your Node-RED editor session\u2026</p><p id="grant" hidden></p><p><button id="allow" type="button" hidden>Allow</button><button id="deny" type="button" hidden>Deny</button><button id="retry" type="button" hidden>Retry</button></p></main><script nonce="${At}">${ga}</script></html>`)},"authorizePage"),h=a((y,w,k)=>{c(w),Sa.default.json({limit:"4kb"})(y,w,()=>we(e,"api")(y,w,()=>{try{d();let T=/^\/mcp\/oauth\/consent\/([A-Za-z0-9_-]+)\/?$/.exec(y.path)?.[1]??"",_=i.get(T),C=_&&t.clientsStore.getClient(_.clientId);if(!_||!C){w.status(404).json({error:"Consent request expired or unknown"});return}let Z=V(e.settings)&&y.user?.username||"owner",he=_.scopes.filter(At=>p(y).includes(At));if(!he.length){w.status(403).json({error:"Your Node-RED permissions do not allow granting the requested scopes",grantable:he});return}if(y.method==="GET"){_.approver=Z,w.json({client_name:C.client_name??null,redirect_host:new URL(_.redirect).host,requested:_.scopes,grantable:he,user:Z,csrf:_.csrf});return}if(y.method!=="POST"){w.status(405).end();return}let sn=y.body??{};if(!f(y)||sn.csrf!==_.csrf||_.approver!==Z){w.status(403).json({error:"Consent request verification failed"});return}i.delete(T);let kt=new URL(_.redirect);if(sn.decision!=="allow")kt.searchParams.set("error","access_denied");else{try{t.clientsStore.approve(C.client_id)}catch{w.status(409).json({error:"Maximum active OAuth clients reached"});return}let At=t.createCode(C,{redirectUri:_.redirect,codeChallenge:_.challenge,scopes:he,resource:new URL(_.resource)},Z);kt.searchParams.set("code",At)}_.state&&kt.searchParams.set("state",_.state),w.json({redirect:kt.href})}catch(T){k(T)}}))},"consentApi"),g=(0,ba.clientRegistrationHandler)({clientsStore:t.clientsStore,rateLimit:{windowMs:36e5,max:10,standardHeaders:!0,legacyHeaders:!1}}),v=(0,ka.tokenHandler)({provider:t}),A=(0,va.revocationHandler)({provider:t}),E,D="";return{server:a((y,w,k)=>{let T=y.path,_=T.replace(/\/+$/,"").toLowerCase();if(_==="/mcp/oauth/authorize"){y.method==="GET"?m(y,w,k):w.status(405).set("Allow","GET").end();return}if(_.startsWith("/mcp/oauth/consent/")){h(y,w,k);return}if(!["/mcp/oauth/token","/mcp/oauth/register","/mcp/oauth/revoke"].includes(_)){k();return}let C=n();if(C.startsWith("https://")||/^http:\/\/(localhost|127\.0\.0\.1)(:|\/|$)/.test(C)){if(D!==C)try{E=(0,xa.mcpAuthRouter)({provider:t,issuerUrl:new URL(C),baseUrl:new URL(`${C.replace(/\/$/,"")}/mcp/oauth/`),resourceServerUrl:new URL(r()),scopesSupported:tn,clientRegistrationOptions:{rateLimit:{windowMs:36e5,max:10}}}),D=C}catch{E=void 0}if(E){let Z=y.url;y.url=y.url.slice(10)||"/",E(y,w,he=>{y.url=Z,k(he)});return}}if(T==="/mcp/oauth/token"){s(v,y,w,k);return}if(T==="/mcp/oauth/register"){s(g,y,w,k);return}if(T==="/mcp/oauth/revoke"){s(A,y,w,k);return}k()},"server"),metadata:a((y,w,k)=>{let T=y.path;if(T==="/.well-known/oauth-authorization-server"){s((0,es.metadataHandler)({issuer:n(),authorization_endpoint:o("/mcp/oauth/authorize"),token_endpoint:o("/mcp/oauth/token"),registration_endpoint:o("/mcp/oauth/register"),revocation_endpoint:o("/mcp/oauth/revoke"),response_types_supported:["code"],code_challenge_methods_supported:["S256"],grant_types_supported:["authorization_code","refresh_token"],token_endpoint_auth_methods_supported:["none"],scopes_supported:tn}),y,w,k);return}if(T==="/.well-known/oauth-protected-resource/mcp"||T==="/.well-known/oauth-protected-resource"){s((0,es.metadataHandler)({resource:Ze(r()),authorization_servers:[n()],scopes_supported:tn}),y,w,k);return}k()},"metadata"),auth:a((y,w,k)=>{try{let T=w.set.bind(w);w.set=((_,C)=>(_==="WWW-Authenticate"&&typeof C=="string"&&(C=C.replace('scope="read"','scope="read propose"')),typeof _=="string"?T(_,C??""):T(_))),(0,Aa.requireBearerAuth)({verifier:t,requiredScopes:["read"],resourceMetadataUrl:o(e.settings.httpNodeRoot===!1||!e.httpNode?"/mcp/.well-known/oauth-protected-resource/mcp":"/.well-known/oauth-protected-resource/mcp"),expectedResource:new URL(r())})(y,w,_=>{w.set=T,k(_)})}catch(T){k(T)}},"auth")}}a(Ta,"makeOAuthHandlers");var On=require("fs"),on=require("path"),_a=require("url"),Pa=require("yaml"),Pp={};function Sp(){return typeof __dirname<"u"?__dirname:(0,on.dirname)((0,_a.fileURLToPath)(Pp.url))}a(Sp,"moduleDir");function Ra(){let e=Sp(),t=[(0,on.join)(e,"..","docs","api","openapi.yaml"),(0,on.join)(e,"..","..","docs","api","openapi.yaml"),(0,on.join)(process.cwd(),"docs","api","openapi.yaml")];for(let n of t)if((0,On.existsSync)(n))return n;return null}a(Ra,"resolveOpenApiYamlPath");function Tp(){let e=Ra();if(!e)throw new Error("OpenAPI specification file was not found");return(0,On.readFileSync)(e,"utf8")}a(Tp,"loadOpenApiYaml");function La(){return(e,t)=>{try{let n=Tp();t.status(200).type("application/yaml").send(n)}catch(n){let r=n instanceof Error?n.message:String(n);t.status(500).json({error:r})}}}a(La,"openApiYamlHandler");var ts;function _p(){let e=Ra();if(!e)throw new Error("OpenAPI specification file was not found");return ts?.path!==e&&(ts={path:e,json:JSON.stringify((0,Pa.parse)((0,On.readFileSync)(e,"utf8")))}),ts.json}a(_p,"loadOpenApiJson");function Ca(){return(e,t)=>{try{t.status(200).type("application/json").send(_p())}catch(n){let r=n instanceof Error?n.message:String(n);t.status(500).json({error:r})}}}a(Ca,"openApiJsonHandler");function Lp(){return`
|
|
1437
|
+
})();`;ye();var et=a(e=>(0,Nn.createHash)("sha256").update(e).digest("hex"),"hash"),At=a(()=>(0,Nn.randomBytes)(32).toString("base64url"),"secret"),Sp=36e5,Tp=100,_p=5,Pp=500,ka=a(()=>({clients:[],codes:[],tokens:[]}),"emptyDb"),jr=class{constructor(t,n){this.maxClients=n;this.path=(0,Qo.join)(t.root,"oauth.json");try{let r=(0,rn.existsSync)(this.path)?JSON.parse((0,rn.readFileSync)(this.path,"utf8")):ka();if(!r||typeof r!="object"||!("clients"in r)||!("codes"in r)||!("tokens"in r)||!Array.isArray(r.clients)||!Array.isArray(r.codes)||!Array.isArray(r.tokens))throw new Error("Invalid OAuth store");this.db=r}catch{this.db=ka()}(0,rn.mkdirSync)(t.root,{recursive:!0,mode:448}),this.prune()}static{a(this,"OAuthStore")}db;path;save(){Tt((0,Qo.join)(this.path,".."),this.path,je(this.db,8e6,{kind:"oauth",pretty:!0}))}prune(t=Date.now()){let n=this.db.clients.length+this.db.codes.length+this.db.tokens.length,r=new Set(this.db.clients.filter(o=>o.approved?(o.lastUsedAt??(o.client_id_issued_at??0)*1e3)>t-30*864e5:(o.client_id_issued_at??0)*1e3>t-Sp).map(o=>o.client_id));this.db.clients=this.db.clients.filter(o=>r.has(o.client_id)),this.db.codes=this.db.codes.filter(o=>o.expires>t&&r.has(o.clientId)),this.db.tokens=this.db.tokens.filter(o=>r.has(o.clientId)&&o.expires>t),this.db.clients.length+this.db.codes.length+this.db.tokens.length!==n&&this.save()}getClient(t){return this.prune(),this.db.clients.find(n=>n.client_id===t)}registerClient(t){if(this.prune(),this.approvedCount()>=this.maxClients())throw new B.InvalidClientMetadataError("Maximum active OAuth clients reached");if(this.db.clients.length-this.approvedCount()>=Tp)throw new B.InvalidClientMetadataError("Too many OAuth clients awaiting consent");if(!Array.isArray(t.redirect_uris)||!t.redirect_uris.length||t.redirect_uris.some(r=>{try{let o=new URL(r);return!!o.username||!!o.password||!!o.hash||o.protocol!=="https:"&&!(o.protocol==="http:"&&["localhost","127.0.0.1","[::1]"].includes(o.hostname))}catch{return!0}}))throw new B.InvalidClientMetadataError("Redirect URIs must use HTTPS or loopback HTTP");if(t.token_endpoint_auth_method&&t.token_endpoint_auth_method!=="none")throw new B.InvalidClientMetadataError("Only public clients are supported");let n={...t,client_secret:void 0,client_secret_expires_at:void 0,client_id:At(),client_id_issued_at:Math.floor(Date.now()/1e3),token_endpoint_auth_method:"none",lastUsedAt:Date.now()};return this.db.clients.push(n),this.save(),n}approvedCount(){return this.db.clients.filter(t=>t.approved).length}approve(t){let n=this.db.clients.find(r=>r.client_id===t);if(!(!n||n.approved)){if(this.approvedCount()>=this.maxClients())throw new B.InvalidClientMetadataError("Maximum active OAuth clients reached");n.approved=!0,this.save()}}get data(){return this.db}persist(){this.touchTimer&&(clearTimeout(this.touchTimer),this.touchTimer=void 0),this.save()}touchTimer;touch(){this.touchTimer||(this.touchTimer=setTimeout(()=>{this.touchTimer=void 0,this.save()},5e3),this.touchTimer.unref?.())}revokeClient(t){let n=this.db.clients.length;return this.db.clients=this.db.clients.filter(r=>r.client_id!==t),this.db.tokens=this.db.tokens.filter(r=>r.clientId!==t),this.db.codes=this.db.codes.filter(r=>r.clientId!==t),this.save(),this.db.clients.length!==n}};function ts(e){switch(e.source){case"http-in":return`Route ${e.path} is already used by http-in node '${e.nodeName??e.nodeId??"unnamed"}'`;case"httpStatic":return`File ${e.path} is served by httpStatic`;case"middleware":return`Path prefix is already handled by mounted middleware (${e.layer??"unknown"})`;default:return`Route ${e.path} is already registered by another handler (${e.layer??"route"})`}}a(ts,"describeOAuthRouteCollision");var ns=a((e,t)=>e.toLowerCase().startsWith(t.toLowerCase()),"startsWithCi");function Xo(e){return e===!1?void 0:`/${typeof e=="string"?e:"/"}`.replace(/\/+/g,"/").replace(/\/$/,"")}a(Xo,"normalizeRoot");function Yo(e,t){if(e!==void 0){if(!e)return t;if(ns(t,`${e}/`))return t.slice(e.length)}}a(Yo,"relativePrefix");function Rp(e){let t=new Map;for(let r of e)r.id&&t.set(r.id,r);let n=a((r,o=new Set)=>{if(!r)return!0;let s=t.get(r);return s?s.type==="tab"?!s.disabled&&!s.d:s.type!=="subflow"||o.has(r)?!1:(o.add(r),e.some(i=>i.type===`subflow:${r}`&&!i.d&&n(i.z,o))):!0},"live");return n}a(Rp,"makeLiveCheck");function Cp(e,t){if(!e||!t.length)return[];let n=[];try{e(s=>n.push(s))}catch{return[]}let r=Rp(n),o=[];for(let s of n){if(s.type!=="http in"||typeof s.url!="string"||s.d)continue;let i=s.url.startsWith("/")?s.url:`/${s.url}`;!t.some(d=>ns(i,d))||!r(s.z)||o.push({path:i,source:"http-in",nodeId:s.id,nodeName:s.name?s.name:void 0})}return o}a(Cp,"findHttpInCollisions");var Lp=new Set(["bound dispatch","handle"]),Ep=["authorize","token","register","revoke"],Ip=["authorization-server","protected-resource"];function Np(e){let t=e.length;for(;t>0&&e.charCodeAt(t-1)===47;)t--;return e.slice(0,t)}a(Np,"trimTrailingSlashes");function Fp(e){let t=new Set;return e.filter(n=>{let r=`${n.source}\0${n.path.toLowerCase()}`;return t.has(r)?!1:(t.add(r),!0)})}a(Fp,"dedupe");function Ca(e){let t=Xo(e.httpAdminRoot),n=e.httpNode?Xo(e.httpNodeRoot):void 0,r=`${t??""}/mcp/oauth/`,o=e.metadataFallback?`${t??""}/mcp/.well-known/oauth-`:`${n??""}/.well-known/oauth-`,s=a(p=>{let f=Yo(t,p),m=Yo(n,p),h=m?Cp(e.eachNode,[m]):[],g=new Set(h.map(E=>E.path.toLowerCase())),A=m?es(e.httpNode,[m]).filter(E=>!(E.source==="route"&&Lp.has(E.layer??"")&&g.has(E.path.toLowerCase()))):[],S=f?es(e.httpAdmin,[f]):[];return Fp([...S,...A,...h])},"scan"),i=s(r),d=s(o),l=e.fileExists??(()=>!1),c=(e.staticDirs??[]).map(p=>typeof p=="string"?{path:p,root:e.httpStaticRoot}:p),u=a((p,f)=>{for(let m of c){let h=Xo(m.root),g=Np(m.path);for(let A of p){let S=Yo(h,A);!S||!l(`${g}${S}`)||f.push({path:A,source:"httpStatic",file:`${g}${S}`})}}},"checkStatic");return u(Ep.map(p=>`${r}${p}`),i),u(Ip.map(p=>`${o}${p}`),d),{authorization:i,metadata:d}}a(Ca,"scanOAuthRouteCollisions");function Op(e){let t=a(n=>{try{return e?.[n]}catch{return}},"read");return t("_router")?.stack??t("router")?.stack??[]}a(Op,"routerStack");function $p(e){let t=e.route?.path;return t?(Array.isArray(t)?t:[t]).map(String):[]}a($p,"layerPaths");function Mp(e,t){if(e.name==="router")return!1;let n=e.regexp,r=e.matchers??(n?[s=>n.test(s)]:[]);if(!r.length)return!1;let o=a(s=>r.some(i=>!!i(s)),"matches");return o("/unrelated-ai-access-collision-probe")?!1:t.some(s=>o(`${s}token`)||o(`${s}mcp`))}a(Mp,"middlewareHits");function es(e,t=["/.well-known/oauth-","/mcp/oauth/"]){let n=[];for(let r of Op(e)){if(r.handle?.oauthOwned)continue;let o=$p(r);for(let s of o)t.some(i=>ns(s,i))&&n.push({path:s,source:"route",layer:r.name??"route"});!o.length&&Mp(r,t)&&n.push({path:t.join(", "),source:"middleware",layer:r.name??"route"}),r.handle?.stack&&n.push(...es({router:{stack:r.handle.stack}},t))}return n}a(es,"detectOAuthRouteCollisionDetails");var Hr=class{constructor(t,n,r,o,s=()=>0){this.clientsStore=t;this.resource=n;this.accessTtl=r;this.refreshTtl=o;this.refreshMax=s}static{a(this,"LocalOAuthProvider")}skipLocalPkceValidation=!1;inflight=new Map;async authorize(t,n,r){throw new B.InvalidGrantError("Admin consent is required")}async challengeForAuthorizationCode(t,n){let r=this.findCode(t,n);return this.clientsStore.data.codes=this.clientsStore.data.codes.filter(o=>o!==r),this.clientsStore.persist(),this.inflight.set(r.hash,r),setTimeout(()=>this.inflight.delete(r.hash),3e4).unref?.(),r.challenge}async exchangeAuthorizationCode(t,n,r,o,s){let i=this.inflight.get(et(n))??this.findCode(t,n);if(this.inflight.delete(i.hash),this.clientsStore.data.codes=this.clientsStore.data.codes.filter(d=>d!==i),this.clientsStore.persist(),i.clientId!==t.client_id||i.expires<=Date.now()||o!==i.redirectUri||s&&tt(s.href)!==i.resource)throw new B.InvalidGrantError("Invalid authorization code");return this.issue(t.client_id,i.scopes,i.resource,i.user)}async exchangeRefreshToken(t,n,r,o){let s=this.clientsStore.data.tokens.find(l=>l.hash===et(n)&&l.type==="refresh"&&l.clientId===t.client_id);if(!s||s.expires<=Date.now())throw new B.InvalidGrantError("Invalid refresh token");if(s.rotated)throw this.clientsStore.data.tokens=this.clientsStore.data.tokens.filter(l=>l.family!==s.family),this.clientsStore.persist(),new B.InvalidGrantError("Refresh token reuse detected");let i=this.refreshMax()*1e3,d=s.familyStart??Date.now();if(i>0&&d+i<=Date.now())throw new B.InvalidGrantError("Refresh token family reached its maximum lifetime");if(o&&tt(o.href)!==s.resource)throw new B.InvalidGrantError("Resource mismatch");if(r?.some(l=>!s.scopes.includes(l)))throw new B.InvalidGrantError("Invalid scope");return s.rotated=!0,this.issue(t.client_id,r??s.scopes,s.resource,s.user,s.family,d)}async verifyAccessToken(t){let n=this.clientsStore.data.tokens.find(o=>o.hash===et(t)&&o.type==="access"&&o.expires>Date.now());if(!n||n.resource!==tt(this.resource()))throw new B.InvalidTokenError("Invalid access token");let r=this.clientsStore.data.clients.find(o=>o.client_id===n.clientId);if(!r)throw new B.InvalidTokenError("Invalid client");return r.lastUsedAt=Date.now(),this.clientsStore.touch(),{token:t,clientId:n.clientId,scopes:n.scopes,expiresAt:Math.floor(n.expires/1e3),resource:new URL(n.resource),extra:{user:n.user}}}async revokeToken(t,n){let r=this.clientsStore.data.tokens.find(o=>o.hash===et(n.token)&&o.clientId===t.client_id);r&&(this.clientsStore.data.tokens=this.clientsStore.data.tokens.filter(o=>o.family!==r.family)),this.clientsStore.persist()}createCode(t,n,r){let o=At();return this.clientsStore.data.codes.push({hash:et(o),clientId:t.client_id,challenge:n.codeChallenge,redirectUri:n.redirectUri,scopes:n.scopes??["read"],resource:this.validateResource(n.resource),expires:Date.now()+6e4,user:r}),this.clientsStore.persist(),o}validateResource(t){let n=tt(this.resource());if(t&&tt(t.href)!==n)throw new B.InvalidGrantError("Resource mismatch");return n}findCode(t,n){let r=this.clientsStore.data.codes.find(o=>o.hash===et(n)&&o.clientId===t.client_id);if(!r||r.expires<=Date.now())throw new B.InvalidGrantError("Invalid or expired authorization code");return r}issue(t,n,r,o,s=At(),i=Date.now()){let d=At(),l=At(),c=Date.now(),u=this.refreshMax()*1e3,p=Math.min(c+this.refreshTtl()*1e3,u>0?i+u:1/0),f=this.clientsStore.data,m=f.clients.find(h=>h.client_id===t);return m&&(m.lastUsedAt=c),f.tokens.push({hash:et(d),type:"access",clientId:t,family:s,scopes:n,resource:r,expires:c+this.accessTtl()*1e3,user:o,rotated:!1},{hash:et(l),type:"refresh",clientId:t,family:s,scopes:n,resource:r,expires:p,user:o,rotated:!1,familyStart:i}),this.clientsStore.persist(),{access_token:d,token_type:"Bearer",expires_in:this.accessTtl(),refresh_token:l,scope:n.join(" ")}}};function tt(e){let t=new URL(e);if(!["http:","https:"].includes(t.protocol)||t.username||t.password||t.search||t.hash)throw new Error("Invalid resource URL");return`${t.protocol}//${t.host}${t.pathname.replace(/\/+$/,"")}`}a(tt,"canonicalResource");var nn=["read","propose"],Dp=new Set(["localhost","127.0.0.1","[::1]"]);function In(e){return e.replace(/[&<>"']/g,t=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[t])}a(In,"escapeHtml");function kt(e){return typeof e=="string"?e:""}a(kt,"value");function La(e,t,n,r){let o=a(v=>`${n().replace(/\/$/,"")}${v}`,"abs"),s=a((v,b,w,x)=>{let k=b.url;b.url="/",v(b,w,C=>{b.url=k,x(C)})},"atRoot"),i=new Map,d=a(()=>{let v=Date.now();for(let[b,w]of i)w.expires<=v&&i.delete(b)},"sweep"),l=a(v=>{d();let b=[...i].filter(([,w])=>w.clientId===v);for(;b.length>=_p;)i.delete(b.shift()[0]);return i.size<Pp},"reservePending"),c=a((v,b)=>{v.setHeader("Cache-Control","no-store"),v.setHeader("X-Frame-Options","DENY"),v.setHeader("Referrer-Policy","no-referrer"),v.setHeader("X-Content-Type-Options","nosniff"),v.setHeader("Content-Security-Policy",`default-src 'none'; ${b?`script-src 'nonce-${b}'; `:""}style-src 'unsafe-inline'; connect-src 'self'; form-action 'none'; base-uri 'none'; frame-ancestors 'none'`)},"securityHeaders"),u=Kn(e.settings.httpAdminRoot),p=a(v=>{if(!W(e.settings))return[...nn];let b=Ue(v);return b==="*"?[...nn]:b==="read"?["read"]:[]},"grantableFor"),f=a(v=>{let b=v.get("sec-fetch-site");if(b&&b!=="same-origin")return!1;let w=v.get("origin");if(!w)return!0;let x=new Set;try{x.add(new URL(n()).origin)}catch{}let k=v.get("host");return k&&(x.add(`http://${k}`),x.add(`https://${k}`)),x.has(w)},"sameOrigin"),m=a((v,b)=>{d();let w=v.query,x=t.clientsStore.getClient(kt(w.client_id));if(!x){b.status(400).send("Unknown OAuth client");return}let k=kt(w.redirect_uri)||(x.redirect_uris.length===1?x.redirect_uris[0]:"");if(!x.redirect_uris.some(Jr=>(0,Aa.redirectUriMatches)(k,Jr))){b.status(400).send("Unregistered redirect URI");return}if(w.response_type!=="code"||w.code_challenge_method!=="S256"||!/^[A-Za-z0-9_-]{43,128}$/.test(kt(w.code_challenge))){b.status(400).send("Authorization code with PKCE S256 is required");return}let C=[...new Set((kt(w.scope)||"read").split(" ").filter(Boolean))];if(C.some(Jr=>!nn.includes(Jr))){b.status(400).send("Unsupported scope");return}let H=kt(w.resource)||r(),M;try{M=tt(H)}catch{b.status(400).send("Invalid resource");return}if(M!==tt(r())){b.status(400).send("Resource mismatch");return}if(!l(x.client_id)){b.status(429).send("Too many pending authorization requests; try again later");return}let _e=At();i.set(_e,{clientId:x.client_id,redirect:k,challenge:kt(w.code_challenge),scopes:C,resource:H,state:kt(w.state),csrf:At(),expires:Date.now()+10*6e4});let De=new URL(k).host,St=(0,Nn.randomBytes)(16).toString("base64");c(b,St),b.status(200).type("html").send(`<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Authorize MCP client</title><style>body{font-family:system-ui,sans-serif;max-width:560px;margin:40px auto;padding:0 16px}h1{font-size:1.4em;word-break:break-all}.warn{color:#9b2c2c}.muted{color:#555}button{margin-right:8px;padding:6px 14px}</style><main data-request="${In(_e)}" data-token-key="${In(u)}"><p class="muted">An MCP client wants to access Node-RED and will receive tokens at:</p><h1>${In(De)}</h1>${Dp.has(new URL(k).hostname)?'<p class="warn">Warning: this redirect URI is reachable only on this machine.</p>':""}<p class="muted">Client-provided name (unverified): ${In(x.client_name||"Unnamed client")}</p><p>Requested permissions: ${In(C.join(", "))}</p><p id="status">Checking your Node-RED editor session\u2026</p><p id="grant" hidden></p><p><button id="allow" type="button" hidden>Allow</button><button id="deny" type="button" hidden>Deny</button><button id="retry" type="button" hidden>Retry</button></p></main><script nonce="${St}">${va}</script></html>`)},"authorizePage"),h=a((v,b,w)=>{c(b),Ra.default.json({limit:"4kb"})(v,b,()=>we(e,"api")(v,b,()=>{try{d();let x=/^\/mcp\/oauth\/consent\/([A-Za-z0-9_-]+)\/?$/.exec(v.path)?.[1]??"",k=i.get(x),C=k&&t.clientsStore.getClient(k.clientId);if(!k||!C){b.status(404).json({error:"Consent request expired or unknown"});return}let H=W(e.settings)&&v.user?.username||"owner",M=k.scopes.filter(St=>p(v).includes(St));if(!M.length){b.status(403).json({error:"Your Node-RED permissions do not allow granting the requested scopes",grantable:M});return}if(v.method==="GET"){k.approver=H,b.json({client_name:C.client_name??null,redirect_host:new URL(k.redirect).host,requested:k.scopes,grantable:M,user:H,csrf:k.csrf});return}if(v.method!=="POST"){b.status(405).end();return}let _e=v.body??{};if(!f(v)||_e.csrf!==k.csrf||k.approver!==H){b.status(403).json({error:"Consent request verification failed"});return}i.delete(x);let De=new URL(k.redirect);if(_e.decision!=="allow")De.searchParams.set("error","access_denied");else{try{t.clientsStore.approve(C.client_id)}catch{b.status(409).json({error:"Maximum active OAuth clients reached"});return}let St=t.createCode(C,{redirectUri:k.redirect,codeChallenge:k.challenge,scopes:M,resource:new URL(k.resource)},H);De.searchParams.set("code",St)}k.state&&De.searchParams.set("state",k.state),b.json({redirect:De.href})}catch(x){w(x)}}))},"consentApi"),g=(0,xa.clientRegistrationHandler)({clientsStore:t.clientsStore,rateLimit:{windowMs:36e5,max:10,standardHeaders:!0,legacyHeaders:!1}}),A=(0,Ta.tokenHandler)({provider:t}),S=(0,Sa.revocationHandler)({provider:t}),E,I="";return{server:a((v,b,w)=>{let x=v.path,k=x.replace(/\/+$/,"").toLowerCase();if(k==="/mcp/oauth/authorize"){v.method==="GET"?m(v,b,w):b.status(405).set("Allow","GET").end();return}if(k.startsWith("/mcp/oauth/consent/")){h(v,b,w);return}if(!["/mcp/oauth/token","/mcp/oauth/register","/mcp/oauth/revoke"].includes(k)){w();return}let C=n();if(C.startsWith("https://")||/^http:\/\/(localhost|127\.0\.0\.1)(:|\/|$)/.test(C)){if(I!==C)try{E=(0,Pa.mcpAuthRouter)({provider:t,issuerUrl:new URL(C),baseUrl:new URL(`${C.replace(/\/$/,"")}/mcp/oauth/`),resourceServerUrl:new URL(r()),scopesSupported:nn,clientRegistrationOptions:{rateLimit:{windowMs:36e5,max:10}}}),I=C}catch{E=void 0}if(E){let H=v.url;v.url=v.url.slice(10)||"/",E(v,b,M=>{v.url=H,w(M)});return}}if(x==="/mcp/oauth/token"){s(A,v,b,w);return}if(x==="/mcp/oauth/register"){s(g,v,b,w);return}if(x==="/mcp/oauth/revoke"){s(S,v,b,w);return}w()},"server"),metadata:a((v,b,w)=>{let x=v.path;if(x==="/.well-known/oauth-authorization-server"){s((0,Zo.metadataHandler)({issuer:n(),authorization_endpoint:o("/mcp/oauth/authorize"),token_endpoint:o("/mcp/oauth/token"),registration_endpoint:o("/mcp/oauth/register"),revocation_endpoint:o("/mcp/oauth/revoke"),response_types_supported:["code"],code_challenge_methods_supported:["S256"],grant_types_supported:["authorization_code","refresh_token"],token_endpoint_auth_methods_supported:["none"],scopes_supported:nn}),v,b,w);return}if(x==="/.well-known/oauth-protected-resource/mcp"||x==="/.well-known/oauth-protected-resource"){s((0,Zo.metadataHandler)({resource:tt(r()),authorization_servers:[n()],scopes_supported:nn}),v,b,w);return}w()},"metadata"),auth:a((v,b,w)=>{try{let x=b.set.bind(b);b.set=((k,C)=>(k==="WWW-Authenticate"&&typeof C=="string"&&(C=C.replace('scope="read"','scope="read propose"')),typeof k=="string"?x(k,C??""):x(k))),(0,_a.requireBearerAuth)({verifier:t,requiredScopes:["read"],resourceMetadataUrl:o(e.settings.httpNodeRoot===!1||!e.httpNode?"/mcp/.well-known/oauth-protected-resource/mcp":"/.well-known/oauth-protected-resource/mcp"),expectedResource:new URL(r())})(v,b,k=>{b.set=x,w(k)})}catch(x){w(x)}},"auth")}}a(La,"makeOAuthHandlers");var Fn=require("fs"),on=require("path"),Ea=require("url"),Ia=require("yaml"),Bp={};function jp(){return typeof __dirname<"u"?__dirname:(0,on.dirname)((0,Ea.fileURLToPath)(Bp.url))}a(jp,"moduleDir");function Na(){let e=jp(),t=[(0,on.join)(e,"..","docs","api","openapi.yaml"),(0,on.join)(e,"..","..","docs","api","openapi.yaml"),(0,on.join)(process.cwd(),"docs","api","openapi.yaml")];for(let n of t)if((0,Fn.existsSync)(n))return n;return null}a(Na,"resolveOpenApiYamlPath");function Hp(){let e=Na();if(!e)throw new Error("OpenAPI specification file was not found");return(0,Fn.readFileSync)(e,"utf8")}a(Hp,"loadOpenApiYaml");function Fa(){return(e,t)=>{try{let n=Hp();t.status(200).type("application/yaml").send(n)}catch(n){let r=n instanceof Error?n.message:String(n);t.status(500).json({error:r})}}}a(Fa,"openApiYamlHandler");var rs;function Up(){let e=Na();if(!e)throw new Error("OpenAPI specification file was not found");return rs?.path!==e&&(rs={path:e,json:JSON.stringify((0,Ia.parse)((0,Fn.readFileSync)(e,"utf8")))}),rs.json}a(Up,"loadOpenApiJson");function Oa(){return(e,t)=>{try{t.status(200).type("application/json").send(Up())}catch(n){let r=n instanceof Error?n.message:String(n);t.status(500).json({error:r})}}}a(Oa,"openApiJsonHandler");function Wp(){return`
|
|
1259
1438
|
(function () {
|
|
1260
1439
|
var root = document.getElementById('mcp-root');
|
|
1261
1440
|
if (!root) return;
|
|
@@ -1476,7 +1655,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
1476
1655
|
}
|
|
1477
1656
|
return body;
|
|
1478
1657
|
}
|
|
1479
|
-
`}a(
|
|
1658
|
+
`}a(Wp,"pageClientScriptPartA");function Gp(){return`
|
|
1480
1659
|
// Time helpers \u2014 keep in sync with page-time.ts (always browser-local Intl).
|
|
1481
1660
|
function titleFromDisplayName(displayName) {
|
|
1482
1661
|
var s = String(displayName || '');
|
|
@@ -1583,7 +1762,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
1583
1762
|
btn.disabled = false;
|
|
1584
1763
|
}, 1600);
|
|
1585
1764
|
}
|
|
1586
|
-
`}a(
|
|
1765
|
+
`}a(Gp,"pageClientScriptPartATimeCopy");function zp(){return`
|
|
1587
1766
|
function shortId(value, keep) {
|
|
1588
1767
|
var s = String(value == null ? '' : value);
|
|
1589
1768
|
var n = keep || 8;
|
|
@@ -1713,7 +1892,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
1713
1892
|
}
|
|
1714
1893
|
return '';
|
|
1715
1894
|
}
|
|
1716
|
-
`}a(
|
|
1895
|
+
`}a(zp,"pageClientScriptPartAFormat");function Jp(){return`
|
|
1717
1896
|
function lookupCachedProposal(id) {
|
|
1718
1897
|
if (!id) return null;
|
|
1719
1898
|
return cachedProposals.filter(function (x) { return x.id === id; })[0] || null;
|
|
@@ -1834,7 +2013,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
1834
2013
|
return k + '=' + String(v);
|
|
1835
2014
|
}).join(', '));
|
|
1836
2015
|
}
|
|
1837
|
-
`}a(
|
|
2016
|
+
`}a(Jp,"pageClientScriptPartAAudit");function Kp(){return`
|
|
1838
2017
|
// Hash helpers \u2014 keep in sync with page-hash.ts
|
|
1839
2018
|
function hashForTab(tab) {
|
|
1840
2019
|
if (tab === 'logs') return 'audit';
|
|
@@ -2094,29 +2273,28 @@ strings you will paste publicly beyond this temporary link.
|
|
|
2094
2273
|
'<p id="link-ttl-warn" class="ttl-long-warn" hidden role="alert">' +
|
|
2095
2274
|
'Long-lived tokens stay usable longer if they leak. Prefer the shortest expiry that works.' +
|
|
2096
2275
|
'</p></form>' +
|
|
2097
|
-
'<div id="token-
|
|
2098
|
-
'<p id="token-
|
|
2099
|
-
'<p id="token-
|
|
2100
|
-
'<
|
|
2101
|
-
'<div
|
|
2102
|
-
'<
|
|
2103
|
-
'<div class="mcp-setup-tabs" role="tablist" aria-label="MCP client setup" id="mcp-setup-tabs"></div>' +
|
|
2104
|
-
'<div id="mcp-setup-body" class="mcp-setup-body"></div>' +
|
|
2276
|
+
'<div id="token-usage" class="token-usage" aria-live="polite">' +
|
|
2277
|
+
'<p id="token-usage-title" class="token-kind-label"></p>' +
|
|
2278
|
+
'<p id="token-usage-hint" class="muted"></p>' +
|
|
2279
|
+
'<div id="token-usage-mcp" class="mcp-setup-panel" hidden>' +
|
|
2280
|
+
'<div class="mcp-setup-tabs" role="tablist" aria-label="MCP client setup (example)" id="token-usage-tabs"></div>' +
|
|
2281
|
+
'<div id="token-usage-body" class="mcp-setup-body"></div>' +
|
|
2105
2282
|
'</div>' +
|
|
2106
|
-
'<
|
|
2107
|
-
'<
|
|
2108
|
-
'
|
|
2109
|
-
'
|
|
2110
|
-
'<pre id="token-copy-block" class="token-copy-block"></pre>' +
|
|
2111
|
-
'</details></div>';
|
|
2283
|
+
'<pre id="token-usage-block" class="token-copy-block"></pre>' +
|
|
2284
|
+
'<p class="muted token-usage-note">Replace <YOUR_TOKEN> with the token shown once after you create it. ' +
|
|
2285
|
+
'Tokens cannot be viewed again later \u2014 revoke and create a new one instead.</p>' +
|
|
2286
|
+
'</div>';
|
|
2112
2287
|
wireTtlControls();
|
|
2288
|
+
var kindSel = $('link-kind');
|
|
2289
|
+
if (kindSel) kindSel.addEventListener('change', renderTokenUsage);
|
|
2290
|
+
renderTokenUsage();
|
|
2113
2291
|
var createForm = $('create-link-form');
|
|
2114
2292
|
if (createForm) {
|
|
2115
2293
|
createForm.addEventListener('submit', onCreateLink);
|
|
2116
2294
|
}
|
|
2117
2295
|
}
|
|
2118
2296
|
|
|
2119
|
-
`}a(
|
|
2297
|
+
`}a(Kp,"pageClientScriptPartATabs");function Vp(){return`
|
|
2120
2298
|
async function loadLinks() {
|
|
2121
2299
|
if (!aiOn || !canManage) {
|
|
2122
2300
|
var dashLinksOff = $('dash-links');
|
|
@@ -2144,12 +2322,6 @@ strings you will paste publicly beyond this temporary link.
|
|
|
2144
2322
|
'</tr></thead><tbody>' +
|
|
2145
2323
|
links.map(function (l) {
|
|
2146
2324
|
var kind = normalizeLinkKind(l.kind);
|
|
2147
|
-
var material = linkCopyMaterial(kind, l.token || '', l.url || '');
|
|
2148
|
-
var hasCopy = !!(material && material.primary);
|
|
2149
|
-
var copyDisabled = hasCopy ? '' : ' disabled title="Copy unavailable \u2014 create a new token"';
|
|
2150
|
-
var copyLabel = kind === 'get_link'
|
|
2151
|
-
? 'Copy GET link'
|
|
2152
|
-
: (kind === 'rest' ? 'Copy REST prompt' : 'Copy MCP Bearer');
|
|
2153
2325
|
return '<tr data-id="' + esc(l.id) + '" data-kind="' + esc(kind) + '">' +
|
|
2154
2326
|
'<td>' + timeCell(l.createdAt) + '</td>' +
|
|
2155
2327
|
'<td>' + kindBadgeHtml(kind) + '</td>' +
|
|
@@ -2157,37 +2329,9 @@ strings you will paste publicly beyond this temporary link.
|
|
|
2157
2329
|
'<td>' + esc(l.label || '\u2014') + '</td>' +
|
|
2158
2330
|
'<td>' + timeCell(l.expiresAt) + '</td>' +
|
|
2159
2331
|
'<td class="row-actions">' +
|
|
2160
|
-
'<button type="button" class="action secondary copy-link" data-id="' + esc(l.id) + '"' +
|
|
2161
|
-
' data-kind="' + esc(kind) + '"' +
|
|
2162
|
-
' data-token="' + esc(l.token || '') + '"' +
|
|
2163
|
-
' data-url="' + esc(l.url || '') + '"' +
|
|
2164
|
-
' data-label="' + esc(copyLabel) + '"' +
|
|
2165
|
-
copyDisabled + '>' + esc(copyLabel) + '</button> ' +
|
|
2166
2332
|
'<button type="button" class="action danger revoke-link" data-id="' + esc(l.id) + '">Revoke</button>' +
|
|
2167
2333
|
'</td></tr>';
|
|
2168
2334
|
}).join('') + '</tbody></table>';
|
|
2169
|
-
list.querySelectorAll('.copy-link').forEach(function (btn) {
|
|
2170
|
-
btn.addEventListener('click', function () {
|
|
2171
|
-
var kind = normalizeLinkKind(btn.getAttribute('data-kind'));
|
|
2172
|
-
var material = linkCopyMaterial(
|
|
2173
|
-
kind,
|
|
2174
|
-
btn.getAttribute('data-token') || '',
|
|
2175
|
-
btn.getAttribute('data-url') || ''
|
|
2176
|
-
);
|
|
2177
|
-
var text = material.primary || '';
|
|
2178
|
-
if (!text) {
|
|
2179
|
-
showFlash(flash, 'Clipboard unavailable \u2014 create a new token to copy.', false);
|
|
2180
|
-
return;
|
|
2181
|
-
}
|
|
2182
|
-
var okMsg = material.copiedOk || 'Copied.';
|
|
2183
|
-
copyTextToClipboard(text).then(function () {
|
|
2184
|
-
markCopied(btn, flash, okMsg);
|
|
2185
|
-
showToast(okMsg, true);
|
|
2186
|
-
}).catch(function () {
|
|
2187
|
-
showFlash(flash, 'Clipboard unavailable \u2014 create a new token and copy from the flash block.', false);
|
|
2188
|
-
});
|
|
2189
|
-
});
|
|
2190
|
-
});
|
|
2191
2335
|
list.querySelectorAll('.revoke-link').forEach(function (btn) {
|
|
2192
2336
|
btn.addEventListener('click', function () { revokeLink(btn.getAttribute('data-id')); });
|
|
2193
2337
|
});
|
|
@@ -2212,8 +2356,6 @@ strings you will paste publicly beyond this temporary link.
|
|
|
2212
2356
|
await api('/links/' + encodeURIComponent(id), { method: 'DELETE' });
|
|
2213
2357
|
showFlash(flash, 'Link revoked.', true);
|
|
2214
2358
|
showToast('Link revoked.', true);
|
|
2215
|
-
var once = $('token-once');
|
|
2216
|
-
if (once) once.hidden = true;
|
|
2217
2359
|
loadLinks();
|
|
2218
2360
|
loadLogs();
|
|
2219
2361
|
} catch (e) {
|
|
@@ -2221,7 +2363,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
2221
2363
|
showToast(e.message || String(e), false);
|
|
2222
2364
|
}
|
|
2223
2365
|
}
|
|
2224
|
-
`}a(
|
|
2366
|
+
`}a(Vp,"pageClientScriptPartA2");function Xp(){return`
|
|
2225
2367
|
|
|
2226
2368
|
function normalizeLinkKind(kind) {
|
|
2227
2369
|
if (kind === 'rest' || kind === 'mcp' || kind === 'get_link') return kind;
|
|
@@ -2363,7 +2505,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
2363
2505
|
label: 'GET link (chat paste)',
|
|
2364
2506
|
buttonLabel: 'Copy GET link for AI chat',
|
|
2365
2507
|
primary: getLink,
|
|
2366
|
-
hint: 'Paste this GET link in your AI chat. It is get_link only \u2014 REST and MCP need separate tokens from this page.
|
|
2508
|
+
hint: 'Paste this GET link in your AI chat. It is get_link only \u2014 REST and MCP need separate tokens from this page. It is shown only once.',
|
|
2367
2509
|
flashOk: 'GET link created.',
|
|
2368
2510
|
copiedOk: 'Copied GET link for AI chat.',
|
|
2369
2511
|
block: [
|
|
@@ -2410,8 +2552,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
2410
2552
|
var created = await api('/links', { method: 'POST', body: JSON.stringify(payload) });
|
|
2411
2553
|
var createdKind = normalizeLinkKind(created.kind || kind);
|
|
2412
2554
|
var material = linkCopyMaterial(createdKind, created.token || '', created.url || '');
|
|
2413
|
-
var
|
|
2414
|
-
box.hidden = false;
|
|
2555
|
+
var modal = openTokenModal(material.label);
|
|
2415
2556
|
// Each new token starts with "For developers" collapsed.
|
|
2416
2557
|
var devDetails = $('token-dev-details');
|
|
2417
2558
|
if (devDetails) devDetails.open = false;
|
|
@@ -2433,11 +2574,11 @@ strings you will paste publicly beyond this temporary link.
|
|
|
2433
2574
|
var devBlurb = $('token-dev-blurb');
|
|
2434
2575
|
if (devBlurb) {
|
|
2435
2576
|
if (createdKind === 'rest') {
|
|
2436
|
-
devBlurb.textContent = 'REST prompt + Bearer
|
|
2577
|
+
devBlurb.textContent = 'REST prompt + Bearer token.';
|
|
2437
2578
|
} else if (createdKind === 'mcp') {
|
|
2438
|
-
devBlurb.textContent = 'MCP Bearer for /mcp/rpc
|
|
2579
|
+
devBlurb.textContent = 'MCP Bearer for /mcp/rpc.';
|
|
2439
2580
|
} else {
|
|
2440
|
-
devBlurb.textContent = 'GET link details
|
|
2581
|
+
devBlurb.textContent = 'GET link details.';
|
|
2441
2582
|
}
|
|
2442
2583
|
}
|
|
2443
2584
|
var copyBtn = $('token-copy-btn');
|
|
@@ -2477,6 +2618,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
2477
2618
|
}
|
|
2478
2619
|
}
|
|
2479
2620
|
if (labelInput) labelInput.value = '';
|
|
2621
|
+
showInPageDialog(modal);
|
|
2480
2622
|
showFlash(flash, material.flashOk, true);
|
|
2481
2623
|
showToast(material.flashOk, true);
|
|
2482
2624
|
loadLinks();
|
|
@@ -2487,9 +2629,116 @@ strings you will paste publicly beyond this temporary link.
|
|
|
2487
2629
|
}
|
|
2488
2630
|
}
|
|
2489
2631
|
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2632
|
+
var TOKEN_PLACEHOLDER = '<YOUR_TOKEN>';
|
|
2633
|
+
|
|
2634
|
+
/** Generic (placeholder-token) usage for the channel currently selected in the form. */
|
|
2635
|
+
function renderTokenUsage() {
|
|
2636
|
+
var kindSelect = $('link-kind');
|
|
2637
|
+
var kind = normalizeLinkKind(kindSelect && kindSelect.value);
|
|
2638
|
+
var material = linkCopyMaterial(kind, TOKEN_PLACEHOLDER, '');
|
|
2639
|
+
var titles = {
|
|
2640
|
+
get_link: 'How to use a GET link (chat paste)',
|
|
2641
|
+
rest: 'How to use a REST Bearer token',
|
|
2642
|
+
mcp: 'How to use an MCP token (/mcp/rpc)'
|
|
2643
|
+
};
|
|
2644
|
+
var hints = {
|
|
2645
|
+
get_link: 'Paste the GET link into your AI chat. It works only as a GET link \u2014 REST and MCP need their own tokens.',
|
|
2646
|
+
rest: 'Give your AI this prompt with the token filled in. The token works only for the REST API \u2014 not as a GET link or for /mcp/rpc.',
|
|
2647
|
+
mcp: 'Add Node-RED to your MCP client with one of these configs. The token works only on POST /mcp/rpc. If your AI app cannot add MCP servers, use a REST or GET link instead.'
|
|
2648
|
+
};
|
|
2649
|
+
var title = $('token-usage-title');
|
|
2650
|
+
if (title) title.textContent = titles[kind];
|
|
2651
|
+
var hint = $('token-usage-hint');
|
|
2652
|
+
if (hint) hint.textContent = hints[kind];
|
|
2653
|
+
var block = $('token-usage-block');
|
|
2654
|
+
var mcpPanel = $('token-usage-mcp');
|
|
2655
|
+
if (kind === 'mcp') {
|
|
2656
|
+
if (block) { block.textContent = ''; block.hidden = true; }
|
|
2657
|
+
if (mcpPanel) mcpPanel.hidden = false;
|
|
2658
|
+
renderMcpSetupPanel(material.mcpSnippets || {}, $('links-flash'), 'token-usage');
|
|
2659
|
+
} else {
|
|
2660
|
+
if (mcpPanel) mcpPanel.hidden = true;
|
|
2661
|
+
var tabs = $('token-usage-tabs');
|
|
2662
|
+
var tbody = $('token-usage-body');
|
|
2663
|
+
if (tabs) tabs.innerHTML = '';
|
|
2664
|
+
if (tbody) tbody.innerHTML = '';
|
|
2665
|
+
if (block) { block.hidden = false; block.textContent = kind === 'rest' ? material.primary : material.block; }
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
/**
|
|
2670
|
+
* One-time modal holding the real token. Closing it (button, Done, or Esc)
|
|
2671
|
+
* empties the dialog so the token no longer exists in the DOM.
|
|
2672
|
+
*/
|
|
2673
|
+
function openTokenModal(label) {
|
|
2674
|
+
var old = $('token-modal');
|
|
2675
|
+
if (old && old.parentNode) old.parentNode.removeChild(old);
|
|
2676
|
+
var dialog = document.createElement('dialog');
|
|
2677
|
+
dialog.id = 'token-modal';
|
|
2678
|
+
dialog.className = 'token-modal';
|
|
2679
|
+
dialog.setAttribute('aria-labelledby', 'token-modal-title');
|
|
2680
|
+
dialog.setAttribute('aria-describedby', 'token-modal-warning');
|
|
2681
|
+
dialog.innerHTML =
|
|
2682
|
+
'<form method="dialog" class="token-modal-form">' +
|
|
2683
|
+
'<div class="token-modal-head">' +
|
|
2684
|
+
'<h3 id="token-modal-title">' + esc(label || 'Access token created') + '</h3>' +
|
|
2685
|
+
'<button type="submit" value="close" class="action secondary token-modal-close" aria-label="Close">\xD7</button>' +
|
|
2686
|
+
'</div>' +
|
|
2687
|
+
'<p id="token-modal-warning" class="token-modal-warning" role="alert"><strong>This is the only time the token is shown.</strong> ' +
|
|
2688
|
+
'Copy it now \u2014 it cannot be viewed again. If you lose it, revoke it and create a new one.</p>' +
|
|
2689
|
+
'<div id="token-once" class="token-once">' +
|
|
2690
|
+
'<p id="token-kind-label" class="token-kind-label" hidden></p>' +
|
|
2691
|
+
'<p id="token-docs-hint" class="muted"></p>' +
|
|
2692
|
+
'<button type="button" class="action copy-ai-primary" id="token-copy-btn">Copy GET link for AI chat</button>' +
|
|
2693
|
+
'<div id="mcp-setup-panel" class="mcp-setup-panel" hidden>' +
|
|
2694
|
+
'<p class="muted mcp-setup-fallback">If your AI app cannot add MCP servers, create a REST or GET link instead.</p>' +
|
|
2695
|
+
'<div class="mcp-setup-tabs" role="tablist" aria-label="MCP client setup" id="mcp-setup-tabs"></div>' +
|
|
2696
|
+
'<div id="mcp-setup-body" class="mcp-setup-body"></div>' +
|
|
2697
|
+
'</div>' +
|
|
2698
|
+
'<details class="advanced-block" id="token-dev-details">' +
|
|
2699
|
+
'<summary>For developers</summary>' +
|
|
2700
|
+
'<p id="token-dev-blurb" class="muted"></p>' +
|
|
2701
|
+
'<code id="token-value"></code>' +
|
|
2702
|
+
'<pre id="token-copy-block" class="token-copy-block"></pre>' +
|
|
2703
|
+
'</details></div>' +
|
|
2704
|
+
'<div class="row dialog-actions"><button type="submit" value="done" class="action">Done</button></div>' +
|
|
2705
|
+
'</form>';
|
|
2706
|
+
var opener = document.activeElement;
|
|
2707
|
+
var cleaned = false;
|
|
2708
|
+
function cleanup() {
|
|
2709
|
+
if (cleaned) return;
|
|
2710
|
+
cleaned = true;
|
|
2711
|
+
dialog.innerHTML = '';
|
|
2712
|
+
document.removeEventListener('keydown', onKey, true);
|
|
2713
|
+
if (dialog.parentNode) dialog.parentNode.removeChild(dialog);
|
|
2714
|
+
if (opener && typeof opener.focus === 'function') opener.focus();
|
|
2715
|
+
}
|
|
2716
|
+
function closeTokenModal() {
|
|
2717
|
+
// Native <dialog>: close() fires 'close' -> cleanup. Fallback (open attribute): clean up directly.
|
|
2718
|
+
if (typeof dialog.close === 'function' && dialog.open) dialog.close();
|
|
2719
|
+
cleanup();
|
|
2720
|
+
}
|
|
2721
|
+
function onKey(e) {
|
|
2722
|
+
if (e.key === 'Escape' || e.key === 'Esc') {
|
|
2723
|
+
e.preventDefault();
|
|
2724
|
+
closeTokenModal();
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
dialog.addEventListener('close', cleanup);
|
|
2728
|
+
dialog.addEventListener('cancel', function (e) { e.preventDefault(); closeTokenModal(); });
|
|
2729
|
+
document.addEventListener('keydown', onKey, true);
|
|
2730
|
+
var form = dialog.querySelector('form');
|
|
2731
|
+
if (form) {
|
|
2732
|
+
form.addEventListener('submit', function (e) { e.preventDefault(); closeTokenModal(); });
|
|
2733
|
+
}
|
|
2734
|
+
document.body.appendChild(dialog);
|
|
2735
|
+
return dialog;
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
function renderMcpSetupPanel(snippets, flash, prefix) {
|
|
2739
|
+
var idp = prefix || 'mcp-setup';
|
|
2740
|
+
var tabsHost = $(idp + '-tabs');
|
|
2741
|
+
var body = $(idp + '-body');
|
|
2493
2742
|
if (!tabsHost || !body) return;
|
|
2494
2743
|
var keys = Object.keys(snippets);
|
|
2495
2744
|
if (!keys.length) {
|
|
@@ -2508,15 +2757,15 @@ strings you will paste publicly beyond this temporary link.
|
|
|
2508
2757
|
}).join('');
|
|
2509
2758
|
var sn = snippets[active];
|
|
2510
2759
|
body.innerHTML =
|
|
2511
|
-
'<pre class="mcp-setup-snippet" id="
|
|
2512
|
-
'<button type="button" class="action" id="
|
|
2760
|
+
'<pre class="mcp-setup-snippet" id="' + idp + '-snippet">' + esc(sn.text) + '</pre>' +
|
|
2761
|
+
'<button type="button" class="action" id="' + idp + '-copy">' + esc(sn.label) + '</button>';
|
|
2513
2762
|
tabsHost.querySelectorAll('[data-mcp-tab]').forEach(function (btn) {
|
|
2514
2763
|
btn.addEventListener('click', function () {
|
|
2515
2764
|
active = btn.getAttribute('data-mcp-tab') || keys[0];
|
|
2516
2765
|
paint();
|
|
2517
2766
|
});
|
|
2518
2767
|
});
|
|
2519
|
-
var copyBtn = $('
|
|
2768
|
+
var copyBtn = $(idp + '-copy');
|
|
2520
2769
|
if (copyBtn) {
|
|
2521
2770
|
copyBtn.onclick = function () {
|
|
2522
2771
|
copyTextToClipboard(sn.text).then(function () {
|
|
@@ -2804,7 +3053,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
2804
3053
|
decided: Math.max(decidedTotal, cachedProposals.filter(function (p) { return p.status !== 'pending'; }).length)
|
|
2805
3054
|
};
|
|
2806
3055
|
}
|
|
2807
|
-
`}a(
|
|
3056
|
+
`}a(Xp,"pageClientScriptPartB");function Yp(){return`
|
|
2808
3057
|
var DIFF_MODE_KEY = 'ai-access-diff-mode';
|
|
2809
3058
|
var DIFF_SPLIT_MIN_PX = ${900};
|
|
2810
3059
|
var PRESENCE_SHORT_TEXT_MAX = ${120};
|
|
@@ -2921,7 +3170,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
2921
3170
|
'</div>';
|
|
2922
3171
|
}
|
|
2923
3172
|
|
|
2924
|
-
`}a(
|
|
3173
|
+
`}a(Yp,"pageClientScriptPartBDiff");function Qp(){return`
|
|
2925
3174
|
function isCollapsiblePresenceText(value) {
|
|
2926
3175
|
var s = String(value == null ? '' : value);
|
|
2927
3176
|
if (s.indexOf('\\n') >= 0) return true;
|
|
@@ -3022,7 +3271,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
3022
3271
|
});
|
|
3023
3272
|
});
|
|
3024
3273
|
}
|
|
3025
|
-
`}a(
|
|
3274
|
+
`}a(Qp,"pageClientScriptPartBDiffPresence");function Zp(){return`
|
|
3026
3275
|
function textChangeLineCount(ch) {
|
|
3027
3276
|
return (ch.lines || []).length;
|
|
3028
3277
|
}
|
|
@@ -3081,7 +3330,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
3081
3330
|
'<code class="diff-before" title="before">' + esc(JSON.stringify(ch.before)) + '</code>' +
|
|
3082
3331
|
' \u2192 <code class="diff-after" title="after">' + esc(JSON.stringify(ch.after)) + '</code></div>';
|
|
3083
3332
|
}
|
|
3084
|
-
`}a(
|
|
3333
|
+
`}a(Zp,"pageClientScriptPartBDiffView");function ef(){return`
|
|
3085
3334
|
function renderDiffSections(sections, mode, emptyMsg) {
|
|
3086
3335
|
var parts = '';
|
|
3087
3336
|
if (sections.added && sections.added.length) {
|
|
@@ -3169,7 +3418,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
3169
3418
|
// Full view kept for mode-toggle re-render inside .proposal-changes.
|
|
3170
3419
|
return renderHumanDiff(diff, mode);
|
|
3171
3420
|
}
|
|
3172
|
-
`}a(
|
|
3421
|
+
`}a(ef,"pageClientScriptPartBDiffView2");function tf(){return`
|
|
3173
3422
|
function updatePendingBadges() {
|
|
3174
3423
|
var counts = countPendingBuckets();
|
|
3175
3424
|
var fullText = formatPendingBadgeText(counts.live, counts.stale);
|
|
@@ -3285,7 +3534,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
3285
3534
|
});
|
|
3286
3535
|
});
|
|
3287
3536
|
}
|
|
3288
|
-
`}a(
|
|
3537
|
+
`}a(tf,"pageClientScriptPartB2");function nf(){return`
|
|
3289
3538
|
function renderDeleteBanner(diff) {
|
|
3290
3539
|
if (!isLargeDelete(diff)) return '';
|
|
3291
3540
|
var beforeN = diff.summary.beforeNodeCount || 0;
|
|
@@ -3367,7 +3616,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
3367
3616
|
loadProposals();
|
|
3368
3617
|
loadLogs();
|
|
3369
3618
|
}
|
|
3370
|
-
`}a(
|
|
3619
|
+
`}a(nf,"pageClientScriptPartB2Actions");function rf(){return`
|
|
3371
3620
|
function renderRequestedVia(p) {
|
|
3372
3621
|
if (!p.createdBy) return '';
|
|
3373
3622
|
if (p.createdBy.label) {
|
|
@@ -3455,7 +3704,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
3455
3704
|
actions +
|
|
3456
3705
|
'</article>';
|
|
3457
3706
|
}
|
|
3458
|
-
`}a(
|
|
3707
|
+
`}a(rf,"pageClientScriptPartB2Cards");function of(){return`
|
|
3459
3708
|
function renderStaleItem(p) {
|
|
3460
3709
|
var cached = proposalDiffCache[p.id];
|
|
3461
3710
|
var title = titleForProposalCard(p);
|
|
@@ -3630,7 +3879,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
3630
3879
|
bindProposalActions(list);
|
|
3631
3880
|
focusProposalFromHash();
|
|
3632
3881
|
}
|
|
3633
|
-
`}a(
|
|
3882
|
+
`}a(of,"pageClientScriptPartB2CardsList");function sf(){return`
|
|
3634
3883
|
async function refreshFlowLabels() {
|
|
3635
3884
|
try {
|
|
3636
3885
|
var data = await api('/flows');
|
|
@@ -3797,7 +4046,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
3797
4046
|
replaceLocationHash('#proposals');
|
|
3798
4047
|
return true;
|
|
3799
4048
|
}
|
|
3800
|
-
`}a(
|
|
4049
|
+
`}a(sf,"pageClientScriptPartB2Focus");function af(){return`
|
|
3801
4050
|
function bindDiffModeToggle(host, diff) {
|
|
3802
4051
|
host.querySelectorAll('[data-diff-mode]').forEach(function (btn) {
|
|
3803
4052
|
btn.addEventListener('click', function () {
|
|
@@ -3909,7 +4158,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
3909
4158
|
esc(e.message || String(e)) + '</p>';
|
|
3910
4159
|
}
|
|
3911
4160
|
}
|
|
3912
|
-
}`}a(
|
|
4161
|
+
}`}a(af,"pageClientScriptPartB2Apply");function df(){return`
|
|
3913
4162
|
|
|
3914
4163
|
function dialogFocusables(dialog) {
|
|
3915
4164
|
if (!dialog) return [];
|
|
@@ -4223,7 +4472,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
4223
4472
|
throw e;
|
|
4224
4473
|
}
|
|
4225
4474
|
}
|
|
4226
|
-
`}a(
|
|
4475
|
+
`}a(df,"pageClientScriptPartC");function lf(){return`
|
|
4227
4476
|
var cachedLogs = [];
|
|
4228
4477
|
|
|
4229
4478
|
function renderLogsTable(logs) {
|
|
@@ -4300,7 +4549,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
4300
4549
|
showFlash(flash, e.message || String(e), false);
|
|
4301
4550
|
}
|
|
4302
4551
|
}
|
|
4303
|
-
`}a(
|
|
4552
|
+
`}a(lf,"pageClientScriptPartCLogs");function cf(){return`
|
|
4304
4553
|
function applyStatus(s) {
|
|
4305
4554
|
aiOn = !!s.aiFeaturesEnabled;
|
|
4306
4555
|
canManage = !!(s.capabilities && s.capabilities.manageLinks);
|
|
@@ -4461,7 +4710,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
4461
4710
|
});
|
|
4462
4711
|
}
|
|
4463
4712
|
}
|
|
4464
|
-
`}a(
|
|
4713
|
+
`}a(cf,"pageClientScriptPartD");function uf(){return`
|
|
4465
4714
|
async function boot() {
|
|
4466
4715
|
accessToken = readEditorToken();
|
|
4467
4716
|
if (!accessToken) {
|
|
@@ -4563,7 +4812,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
4563
4812
|
|
|
4564
4813
|
boot();
|
|
4565
4814
|
})();
|
|
4566
|
-
`}a(
|
|
4815
|
+
`}a(uf,"pageClientScriptPartDBoot");function $a(){return(Wp()+Gp()+zp()+Jp()+Kp()+Vp()+Xp()+Yp()+Qp()+Zp()+ef()+tf()+nf()+rf()+of()+sf()+af()+df()+lf()+cf()+uf()).trim()}a($a,"pageClientScript");function pf(){return`
|
|
4567
4816
|
:root {
|
|
4568
4817
|
color-scheme: light;
|
|
4569
4818
|
--bg: #eef2f5;
|
|
@@ -4631,7 +4880,7 @@ h1 {
|
|
|
4631
4880
|
text-decoration: none;
|
|
4632
4881
|
text-align: center;
|
|
4633
4882
|
}
|
|
4634
|
-
`}a(
|
|
4883
|
+
`}a(pf,"pageStylesPartA");function ff(){return`
|
|
4635
4884
|
nav.tabs {
|
|
4636
4885
|
position: sticky;
|
|
4637
4886
|
top: 0;
|
|
@@ -4725,7 +4974,7 @@ dd {
|
|
|
4725
4974
|
padding: 0.22rem 0.55rem;
|
|
4726
4975
|
border-radius: 6px;
|
|
4727
4976
|
cursor: help;
|
|
4728
|
-
background: var`}a(
|
|
4977
|
+
background: var`}a(ff,"pageStylesPartA2");function mf(){return`(--accent-soft);
|
|
4729
4978
|
color: var(--accent);
|
|
4730
4979
|
font-size: 0.82rem;
|
|
4731
4980
|
font-weight: 650;
|
|
@@ -4857,6 +5106,36 @@ th { color: var(--muted); font-weight: 600; }
|
|
|
4857
5106
|
}
|
|
4858
5107
|
.token-once #token-copy-btn { margin-top: 0.35rem; }
|
|
4859
5108
|
.token-once #token-docs-hint { margin: 0.45rem 0 0; }
|
|
5109
|
+
.token-usage {
|
|
5110
|
+
background: var(--panel-soft, #f7f8fa);
|
|
5111
|
+
border: 1px solid var(--line);
|
|
5112
|
+
border-radius: 8px;
|
|
5113
|
+
padding: 0.75rem 0.9rem;
|
|
5114
|
+
margin: 0.75rem 0;
|
|
5115
|
+
}
|
|
5116
|
+
.token-usage .token-kind-label { font-weight: 600; margin: 0 0 0.25rem; }
|
|
5117
|
+
.token-usage .token-copy-block {
|
|
5118
|
+
margin: 0.6rem 0;
|
|
5119
|
+
padding: 0.55rem 0.65rem;
|
|
5120
|
+
background: #fff;
|
|
5121
|
+
border: 1px solid var(--line);
|
|
5122
|
+
border-radius: 6px;
|
|
5123
|
+
font-size: 0.8rem;
|
|
5124
|
+
white-space: pre-wrap;
|
|
5125
|
+
word-break: break-word;
|
|
5126
|
+
}
|
|
5127
|
+
.token-usage .token-usage-note { font-size: 0.85rem; margin: 0.4rem 0 0; }
|
|
5128
|
+
dialog.token-modal { width: min(720px, 94vw); max-width: min(720px, 94vw); max-height: 90vh; overflow: auto; }
|
|
5129
|
+
.token-modal-head { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; }
|
|
5130
|
+
.token-modal-close { min-width: 2.2rem; font-size: 1.1rem; line-height: 1; }
|
|
5131
|
+
.token-modal-warning {
|
|
5132
|
+
margin: 0.4rem 0 0.6rem;
|
|
5133
|
+
padding: 0.5rem 0.65rem;
|
|
5134
|
+
border: 1px solid #f0c36d;
|
|
5135
|
+
background: #fff8e5;
|
|
5136
|
+
border-radius: 6px;
|
|
5137
|
+
color: #6b4a00;
|
|
5138
|
+
}
|
|
4860
5139
|
.flash {
|
|
4861
5140
|
margin: 0.5rem 0 0.85rem;
|
|
4862
5141
|
padding: 0.55rem 0.7rem;
|
|
@@ -4886,7 +5165,7 @@ th { color: var(--muted); font-weight: 600; }
|
|
|
4886
5165
|
color: var(--muted);
|
|
4887
5166
|
font-size: 0.92rem;
|
|
4888
5167
|
}
|
|
4889
|
-
`}a(
|
|
5168
|
+
`}a(mf,"pageStylesPartB");function hf(){return`
|
|
4890
5169
|
.logs-toolbar { margin-bottom: 0.65rem; }
|
|
4891
5170
|
table.audit-table {
|
|
4892
5171
|
table-layout: fixed;
|
|
@@ -4962,7 +5241,7 @@ table.audit-table {
|
|
|
4962
5241
|
cursor: help;
|
|
4963
5242
|
border-bottom: 1px dotted var(--line);
|
|
4964
5243
|
}
|
|
4965
|
-
`}a(
|
|
5244
|
+
`}a(hf,"pageStylesPartC");function gf(){return`
|
|
4966
5245
|
.proposal-diff {
|
|
4967
5246
|
margin: 0.65rem 0;
|
|
4968
5247
|
padding: 0.55rem 0.65rem;
|
|
@@ -5039,7 +5318,7 @@ table.audit-table {
|
|
|
5039
5318
|
color: var(--muted);
|
|
5040
5319
|
margin-bottom: 0.15rem;
|
|
5041
5320
|
}
|
|
5042
|
-
`}a(
|
|
5321
|
+
`}a(gf,"pageStylesPartD");function yf(){return`
|
|
5043
5322
|
.diff-block {
|
|
5044
5323
|
margin-top: 0.25rem;
|
|
5045
5324
|
font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
|
|
@@ -5121,7 +5400,7 @@ table.audit-table {
|
|
|
5121
5400
|
}
|
|
5122
5401
|
.diff-before { color: var(--danger); word-break: break-word; }
|
|
5123
5402
|
.diff-after { color: var(--ok); word-break: break-word; }
|
|
5124
|
-
`}a(
|
|
5403
|
+
`}a(yf,"pageStylesPartE");function wf(){return`
|
|
5125
5404
|
.proposal-detail {
|
|
5126
5405
|
scroll-margin-top: 4.5rem;
|
|
5127
5406
|
}
|
|
@@ -5247,7 +5526,7 @@ table.audit-table {
|
|
|
5247
5526
|
.audit-table .col-action { width: 5.5rem; min-width: 5.5rem; }
|
|
5248
5527
|
.audit-table .col-actor { width: 7rem; min-width: 6rem; }
|
|
5249
5528
|
}
|
|
5250
|
-
`}a(
|
|
5529
|
+
`}a(wf,"pageStylesPartF");function bf(){return`
|
|
5251
5530
|
.dash-grid {
|
|
5252
5531
|
display: grid;
|
|
5253
5532
|
grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr));
|
|
@@ -5464,7 +5743,7 @@ button.id-copy:focus {
|
|
|
5464
5743
|
background: var(--code-bg, #f6f8fa);
|
|
5465
5744
|
border-radius: 4px;
|
|
5466
5745
|
}
|
|
5467
|
-
`}a(
|
|
5746
|
+
`}a(bf,"pageStylesPartG");function vf(){return`
|
|
5468
5747
|
.stale-group {
|
|
5469
5748
|
margin: 1rem 0 0;
|
|
5470
5749
|
border: 1px solid var(--warn-border);
|
|
@@ -5628,7 +5907,7 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
5628
5907
|
margin-left: auto;
|
|
5629
5908
|
}
|
|
5630
5909
|
.debug-toggle input { width: auto; }
|
|
5631
|
-
`}a(
|
|
5910
|
+
`}a(vf,"pageStylesPartH");function Ma(){return(pf()+ff()+mf()+hf()+gf()+yf()+wf()+bf()+vf()).trim()}a(Ma,"pageStyles");function kf(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""")}a(kf,"escapeHtml");function Af(){return`
|
|
5632
5911
|
<header class="page-header">
|
|
5633
5912
|
<h1>Node-RED AI Access</h1>
|
|
5634
5913
|
<p class="lede">
|
|
@@ -5657,7 +5936,7 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
5657
5936
|
Proposals <span class="tab-badge" id="proposals-pending-badge" hidden>0</span>
|
|
5658
5937
|
</button>
|
|
5659
5938
|
<button type="button" data-tab="logs" aria-selected="false">Audit log</button>
|
|
5660
|
-
</nav>`}a(
|
|
5939
|
+
</nav>`}a(Af,"renderMcpPageHeader");function xf(){return`
|
|
5661
5940
|
<section class="panel active" id="section-status" aria-label="Status" hidden>
|
|
5662
5941
|
<h2>Status</h2>
|
|
5663
5942
|
<div id="status-flash" class="flash" hidden></div>
|
|
@@ -5701,7 +5980,7 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
5701
5980
|
<div class="row" style="margin-top:1rem">
|
|
5702
5981
|
<button type="button" class="action secondary" id="refresh-status">Refresh</button>
|
|
5703
5982
|
</div>
|
|
5704
|
-
</section>`}a(
|
|
5983
|
+
</section>`}a(xf,"renderMcpPageStatusSection");function Sf(){return`
|
|
5705
5984
|
<section class="panel" id="section-links" aria-label="Access links" hidden>
|
|
5706
5985
|
<h2>Access links</h2>
|
|
5707
5986
|
<p class="muted" id="links-intro">
|
|
@@ -5712,7 +5991,7 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
5712
5991
|
<div id="links-flash" class="flash" hidden></div>
|
|
5713
5992
|
<div id="links-manage"></div>
|
|
5714
5993
|
<div id="links-list"></div>
|
|
5715
|
-
</section>`}a(
|
|
5994
|
+
</section>`}a(Sf,"renderMcpPageLinksSection");function Tf(){return`
|
|
5716
5995
|
<section class="panel" id="section-proposals" aria-label="Proposals" hidden>
|
|
5717
5996
|
<h2>Proposals</h2>
|
|
5718
5997
|
<p id="proposals-hint" class="muted"></p>
|
|
@@ -5730,7 +6009,7 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
5730
6009
|
</div>
|
|
5731
6010
|
<div id="proposals-flash" class="flash" hidden></div>
|
|
5732
6011
|
<div id="proposals-list"><p class="muted">Loading\u2026</p></div>
|
|
5733
|
-
</section>`}a(
|
|
6012
|
+
</section>`}a(Tf,"renderMcpPageProposalsSection");function _f(){return`
|
|
5734
6013
|
<section class="panel" id="section-logs" aria-label="Audit log" hidden>
|
|
5735
6014
|
<h2>Audit log</h2>
|
|
5736
6015
|
<div id="logs-flash" class="flash" hidden></div>
|
|
@@ -5746,7 +6025,7 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
5746
6025
|
</label>
|
|
5747
6026
|
</div>
|
|
5748
6027
|
<div id="logs-list"><p class="muted">Loading\u2026</p></div>
|
|
5749
|
-
</section>`}a(
|
|
6028
|
+
</section>`}a(_f,"renderMcpPageLogsSection");function Pf(){return`
|
|
5750
6029
|
<dialog id="reject-dialog">
|
|
5751
6030
|
<form method="dialog" id="reject-form">
|
|
5752
6031
|
<h3>Reject proposal</h3>
|
|
@@ -5788,7 +6067,7 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
5788
6067
|
<button type="submit" value="confirm" class="action danger" id="confirm-action-btn">Confirm</button>
|
|
5789
6068
|
</div>
|
|
5790
6069
|
</form>
|
|
5791
|
-
</dialog>`}a(
|
|
6070
|
+
</dialog>`}a(Pf,"renderMcpPageDialogs");function Da(e){let t={apiBase:e.apiBase,mcpRpc:e.mcpRpc??"/mcp/rpc",editorRoot:e.editorRoot,authTokenKey:e.authTokenKey};return`<!DOCTYPE html>
|
|
5792
6071
|
<html lang="en">
|
|
5793
6072
|
<head>
|
|
5794
6073
|
<meta charset="utf-8" />
|
|
@@ -5796,20 +6075,21 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
5796
6075
|
<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate" />
|
|
5797
6076
|
<meta http-equiv="Pragma" content="no-cache" />
|
|
5798
6077
|
<title>Node-RED AI Access</title>
|
|
5799
|
-
<style>${
|
|
6078
|
+
<style>${Ma()}</style>
|
|
5800
6079
|
</head>
|
|
5801
6080
|
<body>
|
|
5802
6081
|
<main
|
|
5803
6082
|
id="mcp-root"
|
|
5804
|
-
data-config="${
|
|
5805
|
-
>${
|
|
5806
|
-
${
|
|
5807
|
-
${
|
|
5808
|
-
${
|
|
5809
|
-
${
|
|
5810
|
-
${
|
|
6083
|
+
data-config="${kf(JSON.stringify(t))}"
|
|
6084
|
+
>${Af()}
|
|
6085
|
+
${xf()}
|
|
6086
|
+
${Sf()}
|
|
6087
|
+
${Tf()}
|
|
6088
|
+
${_f()}
|
|
6089
|
+
${Pf()}
|
|
5811
6090
|
</main>
|
|
5812
|
-
<script>${
|
|
6091
|
+
<script>${$a()}</script>
|
|
5813
6092
|
</body>
|
|
5814
|
-
</html>`}a(Na,"renderMcpPage");Ae();ve();Ue();q();tt();G();Ne();Ge();Ae();function vt(e,t,n,r={}){e.setHeader("X-Content-Type-Options","nosniff"),e.setHeader("Content-Type","application/json; charset=utf-8");let o={error:Fe[n]??"Request failed",code:n,...r},s=ie(n,"rest");s&&typeof o.hint!="string"&&(o.hint=s),e.status(t).json(o)}a(vt,"sendSafeJsonError");function Fa(e){let t={kind:"admin"};return typeof e.user?.username=="string"&&(t.username=e.user.username),t}a(Fa,"adminActor");function Oa(e,t,n,r,o){return async(s,i)=>{if(!s.accessLink){i.setHeader("WWW-Authenticate",K),i.status(401).json({error:le,code:Re});return}try{let d=U(s,r),l=oe(s.query),c=await ze(e,t,s.accessLink,s.body??{},n,d,o),u=z(e,c.expectedRev,{flowId:c.payload.flowId}),p=Ee(e,e.get(c.id),s.accessLink.id),f=c.warnings;i.status(201).json({...ce(c,{...d,include:l}),mine:!0,...Ie(p),...X(u),...f&&f.length>0?{warnings:f}:{}})}catch(d){let l=N(d,"rest");if(l){i.status(l.statusCode).json(l.body);return}F(i,d);return}}}a(Oa,"createProposalHandler");function Ma(e,t,n){return async(r,o)=>{let s,i;try{let A=await t.getFlows();s=A.rev,i=ir(A.flows)}catch{}let d={...U(r,n),...i?{liveFlowLabels:i}:{}},l=oe(r.query),c;try{c=pr(r.query)}catch(A){F(o,A);return}let u=s?z(e,s):0,p=r.accessLink;if(p){let A=Ht(e,p,d,c),E=A.proposals.map(D=>{let O=ue(e,p,D,{...d,include:l},{includeConflicts:!1});return O.status!=="pending"||s===void 0?O:{...O,baseRevStale:O.expectedRev!==s}});o.json({proposals:E,...dt({...A,returned:A.proposals.length}),...X(u)});return}let f={};c.flowId&&(f.flowId=c.flowId);let m=Gs(e.list(f).filter(A=>Co(A.status,c.status)),{...d,include:l}),h=c.offset??0,v=(c.limit!==void 0?m.slice(h,h+c.limit):h>0?m.slice(h):m).map(A=>A.status!=="pending"||s===void 0?A:{...A,baseRevStale:A.expectedRev!==s});o.json({proposals:v,...dt({total:m.length,limit:c.limit??null,offset:h,returned:v.length}),...X(u)})}}a(Ma,"listProposalsHandler");function $a(e,t,n){return async(r,o)=>{let s=r.params.id;if(typeof s!="string"||s.length===0){o.status(400).json({error:"Missing proposal id"});return}let i=U(r,t),d=oe(r.query),l;if(n)try{l=(await n.getFlows()).rev}catch{}try{if(r.accessLink){let c=Me(e,r.accessLink,s,i);o.json(ue(e,r.accessLink,c,{...i,include:d},{...l?{liveRev:l}:{}}));return}l&&z(e,l),o.json(ce(e.get(s),{...i,include:d}))}catch(c){if(c instanceof re){vt(o,404,c.code);return}let u=N(c,"rest");if(u){o.status(u.statusCode).json(u.body);return}F(o,c);return}}}a($a,"getProposalHandler");function Da(e,t){return async(n,r)=>{let o=n.params.id;if(typeof o!="string"||o.length===0){r.status(400).json({error:"Missing proposal id"});return}let s=n.body??{},i={};if(s.deploymentType!==void 0){if(s.deploymentType!=="nodes"&&s.deploymentType!=="flows"&&s.deploymentType!=="full"){r.status(400).json({error:"deploymentType must be 'nodes', 'flows', or 'full'"});return}i.deploymentType=s.deploymentType}try{let d=await e.approve(o,Fa(n),i);r.json(se(d,U(n,t)))}catch(d){if(d instanceof re||d instanceof ee||d instanceof bn||d instanceof J){vt(r,d.statusCode,d.code);return}F(r,d);return}}}a(Da,"approveProposalHandler");function ja(e,t){return(n,r)=>{let o=n.params.id;if(typeof o!="string"||o.length===0){r.status(400).json({error:"Missing proposal id"});return}let s=n.body??{},i=typeof s.reason=="string"?s.reason:"Rejected by admin";try{let d=se(e.reject(o,Fa(n),i),U(n,t));r.json(d)}catch(d){if(d instanceof re||d instanceof ee||d instanceof J){vt(r,d.statusCode,d.code);return}F(r,d);return}}}a(ja,"rejectProposalHandler");function Ha(e,t,n){return(r,o)=>{if(!r.accessLink){o.setHeader("WWW-Authenticate",K),o.status(401).json({error:le,code:Re});return}let s=r.params.id;if(typeof s!="string"||s.length===0){o.status(400).json({error:"Missing proposal id",code:"missing_proposal_id"});return}let i=r.body??{},d=U(r,n),l=oe(r.query);try{let c=Ut(e,t,r.accessLink,s,i.reason,d);o.json(ue(e,r.accessLink,c,{...d,include:l}))}catch(c){let u=N(c,"rest");if(u){vt(o,u.statusCode,u.body.code);return}vt(o,500,"internal_error");return}}}a(Ha,"withdrawProposalHandler");function Ua(e,t){return async(n,r)=>{let o=n.params.id;if(typeof o!="string"||o.length===0){r.status(400).json({error:"Missing proposal id"});return}try{let s=n.accessLink?Me(e,n.accessLink,o):e.get(o),i=Vt(n.query.format);if(!i.ok){r.status(400).json({error:"Invalid diff format",code:"invalid_diff_format",hint:"Valid formats: summary, full (default summary)."});return}let d=Kt(await Zt(s,t),i.format);r.json(d)}catch(s){if(s instanceof re){vt(r,404,s.code);return}if(s instanceof B||s instanceof be||s instanceof Le){vt(r,s.statusCode,s.code);return}let i=N(s,"rest");if(i){r.status(i.statusCode).json(i.body);return}F(r,s);return}}}a(Ua,"getProposalDiffHandler");ve();ve();nr();function ff(e){return e.endsWith("/")?e.slice(0,-1):e}a(ff,"trimTrailingSlash");var Br=class{static{a(this,"HttpAdapter")}kind="http";baseUrl;accessToken;fetchImpl;constructor(t){this.baseUrl=ff(t.baseUrl),this.accessToken=t.accessToken,this.fetchImpl=t.fetch??globalThis.fetch.bind(globalThis)}headers(t){let n=new Headers(t);return n.set("Accept","application/json"),n.set("Node-RED-API-Version","v2"),this.accessToken&&n.set("Authorization",`Bearer ${this.accessToken}`),n}async request(t,n={}){return await this.fetchImpl(`${this.baseUrl}${t}`,{...n,headers:this.headers(n.headers)})}async readJson(t,n){let r=await t.text();if(!t.ok){let o=r;try{let s=JSON.parse(r);o=s.message??s.code??r}catch{}throw t.status===404?new B(n):t.status===409?new W("unknown","unknown",`Admin API ${n}`):t.status===401||t.status===403?new Le(Xn):new Error(`Admin API ${n} failed (${t.status}): ${o}`)}if(!r)return null;try{return JSON.parse(r)}catch{throw new Error(`Admin API ${n} returned non-JSON`)}}async getFlows(){let t=await this.request("/flows"),n=await this.readJson(t,"GET /flows");return vn(n,"GET /flows")}async getFlow(t){let n=encodeURIComponent(t),[r,o]=await Promise.all([this.request("/flows"),this.request(`/flow/${n}`)]);if(o.status===404)throw new B(t);let s=await this.readJson(r,"GET /flows"),i=await this.readJson(o,`GET /flow/${t}`);return{rev:vn(s,"GET /flows").rev,flow:Nt(t,i)}}async updateFlow(t){let n=t.deploymentType??"nodes",r=await this.getFlows();if(r.rev!==t.expectedRev)throw new W(t.expectedRev,r.rev,"updateFlow");let o=tr(r.flows,t.flowId,t.flow),s=await this.request("/flows",{method:"POST",headers:{"Content-Type":"application/json","Node-RED-Deployment-Type":n},body:JSON.stringify({flows:o,rev:t.expectedRev})});if(s.status===409){let d=r.rev;try{let l=await s.clone().json();typeof l.current_rev=="string"&&(d=l.current_rev)}catch{}throw new W(t.expectedRev,d,"updateFlow")}let i=await this.readJson(s,"POST /flows");if(!i||typeof i!="object"||typeof i.rev!="string")throw new Error("Admin API POST /flows did not return a rev");return{flowId:t.flowId,rev:i.rev}}};ve();nr();function mf(e){let t=e.runtime?.flows;if(!t?.getFlows||!t.getFlow||!t.setFlows)throw new Error("RED.runtime.flows is missing getFlows, getFlow, or setFlows");return t}a(mf,"requireFlowsApi");function hf(e){if(typeof e=="string")return e;if(typeof e.rev=="string"&&e.rev.length>0)return e.rev;throw new Error("RED.runtime.flows.setFlows did not return a rev")}a(hf,"resolveRev");var qr=class{static{a(this,"RedAdapter")}kind="red";flows;constructor(t){this.flows=mf(t)}async getFlows(){let t=await this.flows.getFlows({});return vn(t,"RED.runtime.flows.getFlows()")}async getFlow(t){let n=await this.getFlows(),r;try{r=await this.flows.getFlow({id:t})}catch(o){throw gf(o)?new B(t):o}return{rev:n.rev,flow:Nt(t,r)}}async updateFlow(t){let n=t.deploymentType??"nodes",r=await this.getFlows();if(r.rev!==t.expectedRev)throw new W(t.expectedRev,r.rev,"updateFlow");let o=tr(r.flows,t.flowId,t.flow),s=await this.flows.setFlows({flows:{flows:o,rev:t.expectedRev},deploymentType:n});return{flowId:t.flowId,rev:hf(s)}}};function Ba(e){let t=e?.runtime?.flows;return!!(t?.getFlows&&t.getFlow&&t.setFlows)}a(Ba,"canUseRedRuntime");function gf(e){if(!e||typeof e!="object")return!1;let t=e;if((typeof t.status=="number"?t.status:t.statusCode)===404||t.code==="not_found"||t.code==="flow_not_found")return!0;let r=typeof t.message=="string"?t.message.toLowerCase():"";return r.includes("not found")||r.includes("unknown flow")}a(gf,"isRuntimeFlowNotFound");var qa=require("module");function Wa(e){if(!e||typeof e!="object")return!1;let t=e;return typeof t.getFlows=="function"&&typeof t.getFlow=="function"&&typeof t.setFlows=="function"}a(Wa,"isFlowsApi");function yf(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,qa.createRequire)(e)}a(yf,"createPluginRequire");function wf(e){try{let t=e("@node-red/runtime/lib/settings");return typeof t.available=="function"&&t.available()===!0}catch{return!1}}a(wf,"isNodeRedRuntimeInitialized");function bf(){try{let e=yf();if(!wf(e))return null;let t=e("@node-red/runtime");return Wa(t.flows)?t.flows:null}catch{return null}}a(bf,"tryRequireInitializedRuntimeFlows");function Ga(e){if(e?.runtime&&Wa(e.runtime.flows))return e;let t=bf();return t?{runtime:{flows:t}}:null}a(Ga,"resolveRuntimeHost");function za(e={}){let t=Ga(e.RED??null);if(t&&Ba(t))return new qr(t);let n=e.adminAuthEnabled===!0;if(e.http?.baseUrl){if(n&&!e.http.accessToken)throw new be("In-process Node-RED flow runtime is unavailable and HTTP Admin API fallback has no credentials while adminAuth is enabled. Ensure the plugin runs inside Node-RED so @node-red/runtime.flows can be used.");return new Br(e.http)}throw new be("No usable Node-RED flow adapter: in-process flows API is unavailable and no HTTP Admin API baseUrl was provided")}a(za,"createFlowAdapter");ve();var Gr=require("fs"),Ja=require("path");Qn();q();var vf=/^[a-z][a-z0-9._-]{0,63}$/;function kf(e){if(e.kind==="system")return{kind:"system"};if(e.kind==="access-link"){let n={kind:"access-link",linkId:String(e.linkId).replace(/\0/g,"").slice(0,200)};return typeof e.label=="string"&&(n.label=e.label.replace(/\0/g,"").slice(0,200)),n}let t={kind:"admin"};return typeof e.username=="string"&&(t.username=e.username.replace(/\0/g,"").slice(0,200)),t}a(kf,"sanitizeActor");var Wr=class{constructor(t,n={}){this.paths=t;this.filePath=(0,Ja.join)(t.logs,"audit.jsonl"),this.now=n.now??(()=>Date.now()),this.maxAppendBytes=n.maxAppendBytes??ss}static{a(this,"AuditLog")}filePath;now;maxAppendBytes;seq=0;append(t,n,r){let o=vf.test(t)?t:"unknown";this.seq+=1;let s={id:`log-${this.now()}-${this.seq}`,at:new Date(this.now()).toISOString(),action:o,actor:kf(n)};r!==void 0&&(s.details=As(r));let i=De(s,this.maxAppendBytes,{kind:"audit"});return as(this.paths.root,this.filePath,i),s}list(t=100){if(!(0,Gr.existsSync)(this.filePath))return[];let n=(0,Gr.readFileSync)(this.filePath,"utf8").split(`
|
|
5815
|
-
`).map(o=>o.trim()).filter(Boolean),r=[];for(let o of n)try{r.push(JSON.parse(o))}catch{}return r.slice(-Math.max(1,t)).reverse()}};Ue();Wn();ae();it();ve();Ue();Ge();Ae();var Af="Flow revision conflict: the expectedRev is stale. Ask the AI to re-read flows and resubmit a new proposal.";function Va(e){let t=se(e),n=dr(e)||void 0;return{proposalId:e.id,title:t.title,displayName:t.displayName,flowId:e.payload.flowId,...n?{flowLabel:n}:{}}}a(Va,"auditProposalMeta");var zr=class{constructor(t,n,r,o){this.proposals=t;this.audit=n;this.adapter=r;this.getDeploymentType=o}static{a(this,"ProposalService")}async approve(t,n,r={}){let o=this.proposals.beginApprove(t),s=Va(o),i=r.deploymentType??this.getDeploymentType();try{let d=o.type;if(d!=="replace_flow"){let l=this.proposals.markFailed(t,n,`Unsupported proposal type '${d}'`);return this.audit.append("proposal.failed",n,{...s,reason:l.reason}),l}try{let l=await this.adapter.updateFlow({flowId:o.payload.flowId,flow:o.payload.flow,expectedRev:o.expectedRev,deploymentType:i}),c=this.proposals.markApproved(t,n,l.rev);return this.audit.append("proposal.approved",n,{...s,resultRev:l.rev,flowId:o.payload.flowId}),Vs(this.proposals,o.payload.flowId,t,l.rev),c}catch(l){if(l instanceof W){let p=this.proposals.markRejected(t,n,Af);return this.audit.append("proposal.rejected",n,{...s,reason:p.reason,code:l.code,expectedRev:l.expectedRev,currentRev:l.currentRev}),p}let c=l instanceof Error?l.message:String(l),u=this.proposals.markFailed(t,n,c);return this.audit.append("proposal.failed",n,{...s,reason:c}),u}}finally{this.proposals.releaseLock(t)}}reject(t,n,r){let o=this.proposals.get(t);if(o.status!=="pending")throw new ee(t,o.status);let s=Va(o),i=this.proposals.markRejected(t,n,r.trim()||"Rejected by admin");return this.audit.append("proposal.rejected",n,{...s,reason:i.reason}),i}};_e();function xf(e){let t=a(()=>Promise.reject(new Le(e)),"fail");return{kind:"http",getFlows:t,getFlow:t,updateFlow:t}}a(xf,"misconfiguredAdapter");function Ka(e){let t=e.settings,n=typeof t.uiPort=="number"?t.uiPort:1880,r=t.httpAdminRoot??"/",o=!r||r==="/"?"":r.replace(/\/$/,"");return`http://127.0.0.1:${n}${o}`}a(Ka,"localAdminBaseUrl");function Jr(e,t={}){let n=Hn(e.settings.userDir);ds(n);let r=t.settingsStore??new Tt(n,e.settings.aiAccess,u=>e.log?.warn?.(u));t.bindSettings!==!1&&Gn(r),Jn(r.getEffective().publicBaseUrl);let o=t.linkStore??jr(e);o.configureDefaults({defaultTtlSeconds:r.getEffective().defaultLinkTtlSeconds,maxActiveLinks:r.getEffective().maxActiveLinks});let s=new Zn(n),i=new Wr(n),d;if(t.flowAdapter)d=t.flowAdapter;else try{d=za({RED:e,http:{baseUrl:Ka(e)},adminAuthEnabled:V(e.settings)})}catch(u){let p=u instanceof be?u.message:Xn;e.log?.warn?.(`AI Access flow adapter unavailable: ${p}`),d=xf(p)}let l;if(t.nodeTypesAdapter)l=t.nodeTypesAdapter;else if(t.flowAdapter)l=ho();else try{l=Fs({baseUrl:Ka(e)})}catch(u){let p=u instanceof Error?u.message:String(u);e.log?.warn?.(`AI Access node-types adapter unavailable: ${p}`),l=ho([])}let c=new zr(s,i,d,()=>ge(e.settings).defaultDeploymentType);return{linkStore:o,proposalStore:s,auditLog:i,flowAdapter:d,nodeTypesAdapter:l,proposalService:c,settingsStore:r,...t.catalogueFetcher?{catalogueFetcher:t.catalogueFetcher}:{},...t.catalogueUrl?{catalogueUrl:t.catalogueUrl}:{}}}a(Jr,"createPluginServices");q();ae();Pe();Un();var Xa=dn(require("dns"),1),Ya=dn(require("http"),1),Qa=dn(require("https"),1),Mn=require("net");ae();function Sf(e,t){return e===10||e===172&&t>=16&&t<=31||e===192&&t===168}a(Sf,"isPrivateV4");function Tf(e,t){return e===169&&t===254}a(Tf,"isLinkLocalV4");function _f(e,t,n){return e===0||e===100&&t>=64&&t<=127||e>=224||e===192&&t===0&&(n===0||n===2)||e===198&&(t===18||t===19||t===51&&n===100)||e===203&&t===0&&n===113}a(_f,"isSpecialUseV4");function Pf(e){let[t=0,n=0,r=0]=e.split(".").map(Number);return t===127?"loopback":Sf(t,n)||Tf(t,n)||_f(t,n,r)||e==="168.63.129.16"?"blocked":"public"}a(Pf,"classifyIpv4");function Rf(e){let t=e.split(":"),n=t.indexOf(""),r=[];for(let o of t){if(!o)continue;if(o.includes(".")){let i=o.split(".").map(Number);if(i.length!==4||i.some(d=>!Number.isInteger(d)||d<0||d>255))return;r.push((i[0]??0)<<8|(i[1]??0)),r.push((i[2]??0)<<8|(i[3]??0));continue}let s=Number.parseInt(o,16);if(!/^[\da-f]{1,4}$/i.test(o)||!Number.isInteger(s))return;r.push(s)}if(n>=0){let o=8-r.length;if(o<1)return;let s=t.slice(0,n).filter(Boolean).length;r.splice(s,0,...Array(o).fill(0))}else if(r.length!==8)return;return r.length===8?r:void 0}a(Rf,"expandIpv6");function Lf(e){let t=Rf(e);if(!t)return"blocked";if(t.slice(0,7).every(s=>s===0)&&t[7]===1)return"loopback";let n=t[0]??0,r=t[1]??0;return n===0||n<8192||n>16383||n===8193&&r<=511||n===8193&&r===3512||n===16383&&r<=4095||n===8194?"blocked":"public"}a(Lf,"classifyIpv6");function $n(e){let t=nt(e);return t?(0,Mn.isIP)(t)===4?Pf(t):Lf(t):"blocked"}a($n,"classifyPublicUrlAddress");function Cf(e){let t=e.toLowerCase().replace(/\.$/,"");return["metadata","metadata.google.internal","metadata.google.com","metadata.goog","instance-data","instance-data.ec2.internal","metadata.azure.internal","metadata.aws.internal"].some(n=>t===n||t.endsWith(`.${n}`))}a(Cf,"isMetadataHostname");async function Za(e,t,n){let r=_t(e);if(!r)throw new Error("publicBaseUrl is required for Check");let o=new URL(r),s=o.hostname.replace(/^\[|\]$/g,"");if(Cf(s))throw new Error("Public URL Check cannot target metadata hosts");let i=nt(s),l=(typeof e=="string"?/^https?:\/\/(\[[^\]]+\]|[^/?#:]+)/i.exec(e.trim())?.[1]:void 0)?.replace(/^\[|\]$/g,"");if(i&&l&&l.toLowerCase()!==i.toLowerCase())throw new Error("Public URL Check requires canonical IP address notation");let c=s.toLowerCase().replace(/\.$/,"")==="localhost"||i!==void 0&&$n(i)==="loopback",u=Number(o.port||(o.protocol==="https:"?443:80));if(c&&u!==t)throw new Error("Public URL Check may only target this Node-RED instance's port for loopback URLs");let p=a(g=>{let v=$n(g);if(v==="blocked"||v==="loopback"&&!c||c&&v!=="loopback")throw new Error("Public URL Check cannot target private or non-public addresses")},"validateAddress");if(i)return p(i),{origin:r,address:i,family:(0,Mn.isIP)(i)};let f=await new Promise((g,v)=>{let A=a(()=>v(n.reason instanceof Error?n.reason:new Error(String(n.reason))),"abort");n.throwIfAborted(),n.addEventListener("abort",A,{once:!0}),Xa.default.lookup(s,{all:!0},(E,D)=>{n.removeEventListener("abort",A),E?v(E):g(D)})});if(n.throwIfAborted(),!f.length)throw new Error("Public URL Check hostname has no addresses");for(let g of f)p(g.address);let m=[];for(let g of f){let v=nt(g.address);if(!v)throw new Error("Public URL Check returned an invalid address");m.some(A=>A.address===v)||m.push({address:v,family:(0,Mn.isIP)(v)})}c&&m.sort((g,v)=>g.family-v.family);let h=m[0];if(!h)throw new Error("Public URL Check hostname has no addresses");return{origin:r,...h,addresses:m}}a(Za,"preparePublicUrlCheck");function Ef(e,t,n){let r=e.hostname.replace(/^\[|\]$/g,"");return new Promise((o,s)=>{let d=(e.protocol==="https:"?Qa.default:Ya.default).request(e,{method:"GET",agent:!1,signal:n,headers:{Host:e.host},...(0,Mn.isIP)(r)?{}:{servername:r},lookup:a((l,c,u)=>{c.all?u(null,[{address:t.address,family:t.family}]):u(null,t.address,t.family)},"lookup")},l=>{o(l.statusCode??0),l.destroy()});d.on("error",s),d.end()})}a(Ef,"requestPinned");async function ed(e,t,n){let r=new URL(e.origin+t),o=e.addresses?.length?e.addresses:[{address:e.address,family:e.family}],s;for(let i of o){n.throwIfAborted();try{return await Ef(r,i,n)}catch(d){if(n.aborted)throw d;s=d}}throw s instanceof Error?s:new Error("Public URL Check request failed")}a(ed,"requestPublicUrlCheck");_e();function If(e){let t=ne(e.settings.httpAdminRoot);return t==="/"?"/mcp":`${t}/mcp`}a(If,"mcpPageUrl");function Nf(e){return typeof e=="boolean"||typeof e=="number"||typeof e=="string"?e:Array.isArray(e)?e.join(", "):typeof e=="function"?"custom function":!1}a(Nf,"normalizeTrustProxy");function rd(e){if(!e)return"Set a Public URL before enabling OAuth.";let t=new URL(e),n=nt(t.hostname),r=t.hostname.toLowerCase().replace(/\.$/,"")==="localhost"||n==="::1"||n?.startsWith("127.");return t.protocol==="http:"&&!r?"OAuth Public URL uses HTTP; HTTPS is recommended.":null}a(rd,"oauthPublicUrlWarning");function od(e,t,n){let{sources:r,...o}=t.getEffective(),{permission:s}=zn(e,n);return{ok:!0,adminAuthEnabled:V(e.settings),canEdit:s==="*",permission:s,mcpPageUrl:If(e),apiBase:de(e.settings.httpAdminRoot),settings:o,oauthPublicUrlWarning:rd(o.publicBaseUrl),sources:r,disk:t.getDisk(),trustProxy:Nf(n.app.get("trust proxy")),precedence:["settings.js","disk","default"]}}a(od,"settingsResponse");function sd(e,t){return(n,r)=>{r.json(od(e,t,n))}}a(sd,"getSettingsHandler");function td(e,t,n){let r=e[t];if(r!==void 0){if(typeof r!="boolean")throw new Error(`${t} must be a boolean`);n[t]=r}}a(td,"parseBooleanField");function nd(e,t,n){let r=e[t];if(r!==void 0){if(typeof r!="number"||!Number.isFinite(r)||r<=0)throw new Error(`${t} must be a positive number`);n[t]=Math.floor(r)}}a(nd,"parsePositiveIntField");function Ff(e,t,n){let r=e[t];if(r===void 0)return;let o=t==="rateLimitWindowMs"?100:1,s=t==="rateLimitWindowMs"?36e5:1e6;if(typeof r!="number"||!Number.isInteger(r)||r<o||r>s)throw new Error(`${t} must be an integer between ${o} and ${s}`);n[t]=r}a(Ff,"parseRateLimitField");function Of(e,t){if(e.corsAllowedOrigins===void 0)return;if(!Array.isArray(e.corsAllowedOrigins))throw new Error("corsAllowedOrigins must be a list of HTTP(S) origins");let n=e.corsAllowedOrigins.map(r=>un(r));if(n.some(r=>r===void 0))throw new Error("corsAllowedOrigins must contain only HTTP(S) origins without paths, credentials, queries, or fragments");t.corsAllowedOrigins=[...new Set(n)]}a(Of,"parseCorsField");function id(e,t,n){return(r,o)=>{let s=r.body;if(!s||typeof s!="object"||Array.isArray(s)){o.status(400).json({error:"Request body must be a settings object",code:"invalid_body"});return}let i={...s};for(let[l,c]of Object.entries(t.getEffective().sources))c==="settings.js"&&delete i[l];t.getEffective().sources.oauth==="settings.js"&&delete i.oauth;let d={};try{if(i.oauth!==void 0){if(!i.oauth||typeof i.oauth!="object"||Array.isArray(i.oauth))throw new Error("oauth must be an object");let l=i.oauth,c={};if(l.enabled!==void 0){if(typeof l.enabled!="boolean")throw new Error("oauth.enabled must be a boolean");c.enabled=l.enabled}for(let p of["accessTokenTtlSec","refreshIdleTtlSec","dcrMaxClients"])if(l[p]!==void 0){if(typeof l[p]!="number"||!Number.isInteger(l[p])||l[p]<1||l[p]>31536e3)throw new Error(`oauth.${p} must be a positive integer`);c[p]=l[p]}let u=l.refreshMaxLifetimeSec;if(u!==void 0){if(typeof u!="number"||!Number.isInteger(u)||u<0||u>31536e4)throw new Error("oauth.refreshMaxLifetimeSec must be a non-negative integer");c.refreshMaxLifetimeSec=u}d.oauth=c}td(i,"enabled",d),td(i,"allowWithoutAdminAuth",d),nd(i,"defaultLinkTtlSeconds",d),nd(i,"maxActiveLinks",d)}catch(l){o.status(400).json({error:l.message,code:"invalid_body"});return}if(i.defaultDeploymentType!==void 0){let l=i.defaultDeploymentType;if(l!=="nodes"&&l!=="flows"&&l!=="full"){o.status(400).json({error:"defaultDeploymentType must be 'nodes', 'flows', or 'full'",code:"invalid_body"});return}d.defaultDeploymentType=l}try{i.publicBaseUrl!==void 0&&(d.publicBaseUrl=_t(i.publicBaseUrl)),Of(i,d);for(let l of["rateLimitWindowMs","rateLimitMaxPerIp","rateLimitMaxPerToken"])Ff(i,l,d)}catch(l){o.status(400).json({error:l.message,code:"invalid_body"});return}try{let l=t.patch(d);Jn(l.publicBaseUrl),n.configureDefaults({defaultTtlSeconds:l.defaultLinkTtlSeconds,maxActiveLinks:l.maxActiveLinks});let c=od(e,t,r),u=l.publicBaseUrl?new URL(l.publicBaseUrl):void 0,p=u?nt(u.hostname.replace(/^\[|\]$/g,"")):void 0,f=p?$n(p):void 0,m=[];f!==void 0&&f!=="public"&&m.push("Public URL uses a private or non-public IP address.");let h=rd(l.publicBaseUrl);h&&l.publicBaseUrl&&m.push(h),o.json({...c,...m.length?{warnings:m}:{}})}catch(l){if(l instanceof J){o.status(l.statusCode).json({error:l.message,code:l.code});return}F(o,l);return}}}a(id,"putSettingsHandler");function ad(e){return async(t,n)=>{let r=new AbortController,o=setTimeout(()=>r.abort(),5e3),s;try{let i;try{let c=e.settings,p=e.server?.address(),f=t.body?.publicBaseUrl,m=typeof f=="string"?new URL(f.trim()).hostname:"";if((m.toLowerCase().replace(/^\[|\]$/g,"").replace(/\.$/,"")==="localhost"||$n(m.replace(/^\[|\]$/g,""))==="loopback")&&typeof p=="string")throw new Error("Public URL Check does not support loopback URLs when Node-RED listens on a Unix socket");let g=p&&typeof p=="object"?p.port:c.uiPort??1880;i=await Za(t.body?.publicBaseUrl,g,r.signal)}catch(c){if(r.signal.aborted)throw c;n.status(400).json({error:c.message,code:"invalid_body"});return}s=i.origin+de(e.settings.httpAdminRoot);let d=await ed(i,de(e.settings.httpAdminRoot),r.signal),l={status:d,ok:d>=200&&d<300};n.json({ok:l.ok,status:l.status,url:s,message:l.ok?"Discovery endpoint is reachable":`Discovery endpoint returned HTTP ${l.status}`})}catch{n.json({ok:!1,status:null,url:s,error:r.signal.aborted?"Public URL check timed out after 5 seconds":"Could not reach the discovery endpoint"})}finally{clearTimeout(o)}}}a(ad,"checkPublicUrlHandler");tt();_e();G();Wn();var Mf="com55-ai-access";function $f(e,t,n){e.httpAdmin.get("/mcp",t,(r,o)=>{o.setHeader("Cache-Control","no-store, no-cache, must-revalidate, max-age=0"),o.setHeader("Pragma","no-cache"),o.setHeader("Expires","0"),o.type("html").status(200).send(Na({apiBase:de(e.settings.httpAdminRoot),mcpRpc:je(e.settings.httpAdminRoot),editorRoot:ne(e.settings.httpAdminRoot),authTokenKey:Kn(e.settings.httpAdminRoot)}))}),e.httpAdmin.get("/mcp/api/v1",t,n,$o(e)),e.httpAdmin.get("/mcp/api/v1/",t,n,$o(e)),e.httpAdmin.get("/mcp/api/v1/openapi.yaml",t,n,La()),e.httpAdmin.get("/mcp/api/v1/openapi.json",t,n,Ca()),e.httpAdmin.get("/mcp/editor/ai-access-sidebar.js",t,(r,o)=>{o.setHeader("Cache-Control","no-store, no-cache, must-revalidate, max-age=0"),o.setHeader("Pragma","no-cache"),o.setHeader("Expires","0"),o.setHeader("X-AI-Access-Sidebar-Version",Jt),o.type("application/javascript").status(200).send(hr())})}a($f,"registerMcpShellAndDiscovery");var Df=/^\/mcp\/(?:api\/v1\/proposals|rpc$|a\/[^/]+\/(?:proposals|propose|draft))/;function ld(e,t={}){let n=Jr(e,{...t,linkStore:t.linkStore??jr(e)}),r=Ti(()=>n.settingsStore.getEffective()),{ip:o,token:s}=r,i=new Hr(Hn(e.settings.userDir),()=>n.settingsStore.getEffective().oauth.dcrMaxClients),d=a(()=>n.settingsStore.getEffective().publicBaseUrl,"publicBase"),l=a(()=>`${d().replace(/\/$/,"")}/mcp`,"mcpUrl"),c=new Ur(i,l,()=>n.settingsStore.getEffective().oauth.accessTokenTtlSec,()=>n.settingsStore.getEffective().oauth.refreshIdleTtlSec,()=>n.settingsStore.getEffective().oauth.refreshMaxLifetimeSec),u=Ta(e,c,d,l);cs(e.httpAdmin,ls(()=>n.settingsStore.getEffective().corsAllowedOrigins));let p={authorization:[],metadata:[],httpNodeRootFallback:e.settings.httpNodeRoot===!1||!e.httpNode},f=a(()=>{p.authorization=[...rn(e.httpAdmin,["/mcp/oauth/"]),...rn(e.httpNode,["/mcp/oauth/"])];let y=p.httpNodeRootFallback?["/mcp/.well-known/oauth-"]:["/.well-known/oauth-"];p.metadata=[...rn(e.httpAdmin,y),...rn(e.httpNode,y)];try{e.nodes?.eachNode(k=>{if(k.type!=="http in"||typeof k.url!="string")return;let T=k.url.startsWith("/")?k.url:`/${k.url}`;T.startsWith("/mcp/oauth/")&&p.authorization.push(`http-in node ${T}`),T.startsWith("/.well-known/oauth-")&&p.metadata.push(`http-in node ${T}`)})}catch{}let w=e.settings.httpStatic?[e.settings.httpStatic].flat():[];for(let k of w)for(let T of[".well-known/oauth-authorization-server",".well-known/oauth-protected-resource","mcp/oauth/authorize","mcp/oauth/token","mcp/oauth/register","mcp/oauth/revoke"])(0,dd.existsSync)((0,Vr.join)(k,T))&&(T.startsWith("mcp/")?p.authorization.push(`httpStatic ${(0,Vr.join)(k,T)}`):p.metadata.push(`httpStatic ${(0,Vr.join)(k,T)}`));for(let k of[...p.authorization,...p.metadata])e.log?.warn?.(`AI Access OAuth route collision: ${k}`)},"refreshCollisions");f(),e.events?.on("flows:started",f);let m=Ho({windowMs:6e4,maxPerIp:30}),h=a((y,w,k)=>{if(!n.settingsStore.getEffective().oauth.enabled||p.authorization.length||!y.path.startsWith("/mcp/oauth/")){k();return}let T=y.path.replace(/\/+$/,"").toLowerCase(),_=a(()=>Rt(e)(y,w,()=>u.server(y,w,k)),"run");T==="/mcp/oauth/authorize"?o(y,w,()=>m(y,w,_)):T.startsWith("/mcp/oauth/consent/")?o(y,w,_):_()},"oauthServerMiddleware");h.oauthOwned=!0,e.httpAdmin.use(h);let g=a((y,w,k)=>{if(!n.settingsStore.getEffective().oauth.enabled||p.metadata.length){k();return}u.metadata(y,w,k)},"metadataHandler");g.oauthOwned=!0,p.httpNodeRootFallback?e.httpAdmin.use("/mcp",g):e.httpNode?.use(g),e.httpAdmin.get("/mcp/api/v1/oauth/clients",o,s,we(e,"api"),(y,w)=>w.json({clients:i.data.clients.map(k=>({client_id:k.client_id,client_name:k.client_name,redirect_uris:k.redirect_uris,lastUsedAt:k.lastUsedAt}))})),e.httpAdmin.get("/mcp/api/v1/oauth/status",o,s,we(e,"api"),(y,w)=>w.json({...p,collisions:[...p.authorization,...p.metadata],oauthEnabled:n.settingsStore.getEffective().oauth.enabled})),e.httpAdmin.delete("/mcp/api/v1/oauth/clients/:id",o,s,we(e,"api"),mn(e),(y,w)=>w.json({revoked:i.revokeClient(String(y.params.id))})),e.httpAdmin.use((y,w,k)=>{if(!Df.test(y.path)){k();return}n.flowAdapter.getFlows().then(T=>qs(y,ir(T.flows))).catch(()=>{}).finally(()=>k())}),Xi(e,{linkStore:n.linkStore,proposalStore:n.proposalStore,auditLog:n.auditLog,flowAdapter:n.flowAdapter,nodeTypesAdapter:n.nodeTypesAdapter,...n.catalogueFetcher?{catalogueFetcher:n.catalogueFetcher}:{},...n.catalogueUrl?{catalogueUrl:n.catalogueUrl}:{},rateLimiters:r}),$f(e,o,s),e.httpAdmin.get("/mcp/api/v1/status",o,s,hs(e,y=>n.linkStore.validateResult(y)),P((y,w)=>{w.json(en(e,y))})),e.httpAdmin.get("/mcp/api/v1/settings",o,s,we(e,"api"),P(sd(e,n.settingsStore))),e.httpAdmin.put("/mcp/api/v1/settings",o,s,we(e,"api"),mn(e),Ve("rest"),P(id(e,n.settingsStore,n.linkStore))),e.httpAdmin.post("/mcp/api/v1/settings/check-public-url",o,s,we(e,"api"),mn(e),Ve("rest"),P(ad(e)));let v=[o,s,we(e,"api"),Rt(e),mn(e)];e.httpAdmin.get("/mcp/api/v1/links",...v,P(sa(e,n.linkStore))),e.httpAdmin.post("/mcp/api/v1/links",...v,P(ia(e,n.linkStore,n.auditLog))),e.httpAdmin.delete("/mcp/api/v1/links/:id",...v,P(aa(e,n.linkStore,n.auditLog)));let A=[o,s,Rt(e),ms(e,y=>n.linkStore.validateResult(y))],E=n.flowAdapter,O={nodeTypes:n.nodeTypesAdapter,...n.catalogueFetcher?{catalogueFetcher:n.catalogueFetcher}:{},...n.catalogueUrl?{catalogueUrl:n.catalogueUrl}:{}};e.httpAdmin.get("/mcp/api/v1/flows",...A,P(mi(E))),e.httpAdmin.get("/mcp/api/v1/flows/:id",...A,P(Pr(E))),e.httpAdmin.get("/mcp/api/v1/nodes/:id",...A,P(Rr(E))),e.httpAdmin.get("/mcp/api/v1/nodes/:id/field/:name",...A,P(Lr(E))),e.httpAdmin.get("/mcp/api/v1/search",...A,P(Cr(E))),e.httpAdmin.get("/mcp/api/v1/node-types",...A,P(Er(O))),e.httpAdmin.get("/mcp/api/v1/node-types/:type",...A,P(Ir(O))),e.httpAdmin.get("/mcp/api/v1/palette/search",...A,P(Nr(O)));let te=e.settings.httpAdminRoot;e.httpAdmin.get("/mcp/api/v1/proposals",...A,P(Ma(n.proposalStore,n.flowAdapter,te))),e.httpAdmin.get("/mcp/api/v1/proposals/:id",...A,P($a(n.proposalStore,te,n.flowAdapter))),e.httpAdmin.get("/mcp/api/v1/proposals/:id/diff",...A,P(Ua(n.proposalStore,n.flowAdapter))),e.httpAdmin.post("/mcp/api/v1/proposals",o,s,Ve("rest"),Ct(e,n.linkStore,"rest"),P(Oa(n.proposalStore,n.auditLog,n.flowAdapter,te,n.nodeTypesAdapter))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/approve",...v,Ve("rest"),P(Da(n.proposalService,te))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/reject",...v,Ve("rest"),P(ja(n.proposalService,te))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/withdraw",o,s,Ve("rest"),Ct(e,n.linkStore,"rest"),P(Ha(n.proposalStore,n.auditLog,te)));let me=[o,s,(y,w,k)=>{let T=y.headers.authorization;if(!n.settingsStore.getEffective().oauth.enabled||T?.toLowerCase().startsWith("bearer aia_")){if(n.settingsStore.getEffective().oauth.enabled){let _=w.setHeader.bind(w);w.setHeader=((C,Z)=>{if(C.toLowerCase()==="www-authenticate"&&typeof Z=="string"&&!Z.includes("resource_metadata=")){let he=`${d().replace(/\/$/,"")}${p.httpNodeRootFallback?"/mcp":""}/.well-known/oauth-protected-resource/mcp`;Z+=`, resource_metadata="${he}", scope="read propose"`}return _(C,Z)})}Ct(e,n.linkStore,"mcp")(y,w,k);return}Rt(e)(y,w,()=>u.auth(y,w,k))}];e.httpAdmin.post("/mcp/rpc",...me,Ve("mcp"),P(ha(e,n))),e.httpAdmin.get("/mcp/rpc",...me,Qo),e.httpAdmin.delete("/mcp/rpc",...me,Qo),e.httpAdmin.get("/mcp/api/v1/logs",o,s,we(e,"api"),Rt(e),P(da(n.auditLog,n.linkStore))),e.httpAdmin.use("/mcp/api/v1",ui()),ta(e)}a(ld,"registerRoutes");function jf(e){e.plugins.registerPlugin(Mf,{type:"com55-ai-access",onadd(){ld(e),e.log?.info?.("Node-RED AI Access plugin loaded (/mcp)")}})}a(jf,"registerAiAccessPlugin");var Hf=jf;0&&(module.exports={ACCESS_LINK_INVALID_MESSAGE,ACCESS_LINK_MISSING_MESSAGE,AccessLinkStore,EDITOR_SIDEBAR_ASSET_VERSION,MAX_PROPOSAL_JSON_BYTES,SettingsStore,areAiFeaturesEnabled,bindSettingsStore,createPluginServices,getStorageDir,isAdminAuthEnabled,packageVersion,registerRoutes,renderEditorSidebarJs,renderEditorSidebarLoaderHtml,requireAccessLink,resolveAiAccessSettings});
|
|
6093
|
+
</html>`}a(Da,"renderMcpPage");Ae();ve();qe();z();ot();K();Ne();Je();Ae();function xt(e,t,n,r={}){e.setHeader("X-Content-Type-Options","nosniff"),e.setHeader("Content-Type","application/json; charset=utf-8");let o={error:Fe[n]??"Request failed",code:n,...r},s=ie(n,"rest");s&&typeof o.hint!="string"&&(o.hint=s),e.status(t).json(o)}a(xt,"sendSafeJsonError");function ja(e){let t={kind:"admin"};return typeof e.user?.username=="string"&&(t.username=e.user.username),t}a(ja,"adminActor");function Ha(e,t,n,r,o){return async(s,i)=>{if(!s.accessLink){i.setHeader("WWW-Authenticate",Y),i.status(401).json({error:le,code:Re});return}try{let d=q(s,r),l=oe(s.query),c=await Ke(e,t,s.accessLink,s.body??{},n,d,o),u=V(e,c.expectedRev,{flowId:c.payload.flowId}),p=Ee(e,e.get(c.id),s.accessLink.id),f=c.warnings;i.status(201).json({...ce(c,{...d,include:l}),mine:!0,...Ie(p),...Q(u),...f&&f.length>0?{warnings:f}:{}})}catch(d){let l=O(d,"rest");if(l){i.status(l.statusCode).json(l.body);return}$(i,d);return}}}a(Ha,"createProposalHandler");function Ua(e,t,n){return async(r,o)=>{let s,i;try{let S=await t.getFlows();s=S.rev,i=sr(S.flows)}catch{}let d={...q(r,n),...i?{liveFlowLabels:i}:{}},l=oe(r.query),c;try{c=ur(r.query)}catch(S){$(o,S);return}let u=s?V(e,s):0,p=r.accessLink;if(p){let S=Ut(e,p,d,c),E=S.proposals.map(I=>{let N=ue(e,p,I,{...d,include:l},{includeConflicts:!1});return N.status!=="pending"||s===void 0?N:{...N,baseRevStale:N.expectedRev!==s}});o.json({proposals:E,...ut({...S,returned:S.proposals.length}),...Q(u)});return}let f={};c.flowId&&(f.flowId=c.flowId);let m=Ks(e.list(f).filter(S=>Po(S.status,c.status)),{...d,include:l}),h=c.offset??0,A=(c.limit!==void 0?m.slice(h,h+c.limit):h>0?m.slice(h):m).map(S=>S.status!=="pending"||s===void 0?S:{...S,baseRevStale:S.expectedRev!==s});o.json({proposals:A,...ut({total:m.length,limit:c.limit??null,offset:h,returned:A.length}),...Q(u)})}}a(Ua,"listProposalsHandler");function Ba(e,t,n){return async(r,o)=>{let s=r.params.id;if(typeof s!="string"||s.length===0){o.status(400).json({error:"Missing proposal id"});return}let i=q(r,t),d=oe(r.query),l;if(n)try{l=(await n.getFlows()).rev}catch{}try{if(r.accessLink){let c=$e(e,r.accessLink,s,i);o.json(ue(e,r.accessLink,c,{...i,include:d},{...l?{liveRev:l}:{}}));return}l&&V(e,l),o.json(ce(e.get(s),{...i,include:d}))}catch(c){if(c instanceof re){xt(o,404,c.code);return}let u=O(c,"rest");if(u){o.status(u.statusCode).json(u.body);return}$(o,c);return}}}a(Ba,"getProposalHandler");function qa(e,t){return async(n,r)=>{let o=n.params.id;if(typeof o!="string"||o.length===0){r.status(400).json({error:"Missing proposal id"});return}let s=n.body??{},i={};if(s.deploymentType!==void 0){if(s.deploymentType!=="nodes"&&s.deploymentType!=="flows"&&s.deploymentType!=="full"){r.status(400).json({error:"deploymentType must be 'nodes', 'flows', or 'full'"});return}i.deploymentType=s.deploymentType}try{let d=await e.approve(o,ja(n),i);r.json(se(d,q(n,t)))}catch(d){if(d instanceof re||d instanceof te||d instanceof wn||d instanceof X){xt(r,d.statusCode,d.code);return}$(r,d);return}}}a(qa,"approveProposalHandler");function Wa(e,t){return(n,r)=>{let o=n.params.id;if(typeof o!="string"||o.length===0){r.status(400).json({error:"Missing proposal id"});return}let s=n.body??{},i=typeof s.reason=="string"?s.reason:"Rejected by admin";try{let d=se(e.reject(o,ja(n),i),q(n,t));r.json(d)}catch(d){if(d instanceof re||d instanceof te||d instanceof X){xt(r,d.statusCode,d.code);return}$(r,d);return}}}a(Wa,"rejectProposalHandler");function Ga(e,t,n){return(r,o)=>{if(!r.accessLink){o.setHeader("WWW-Authenticate",Y),o.status(401).json({error:le,code:Re});return}let s=r.params.id;if(typeof s!="string"||s.length===0){o.status(400).json({error:"Missing proposal id",code:"missing_proposal_id"});return}let i=r.body??{},d=q(r,n),l=oe(r.query);try{let c=Bt(e,t,r.accessLink,s,i.reason,d);o.json(ue(e,r.accessLink,c,{...d,include:l}))}catch(c){let u=O(c,"rest");if(u){xt(o,u.statusCode,u.body.code);return}xt(o,500,"internal_error");return}}}a(Ga,"withdrawProposalHandler");function za(e,t){return async(n,r)=>{let o=n.params.id;if(typeof o!="string"||o.length===0){r.status(400).json({error:"Missing proposal id"});return}try{let s=n.accessLink?$e(e,n.accessLink,o):e.get(o),i=Vt(n.query.format);if(!i.ok){r.status(400).json({error:"Invalid diff format",code:"invalid_diff_format",hint:"Valid formats: summary, full (default summary)."});return}let d=Xt(await en(s,t),i.format);r.json(d)}catch(s){if(s instanceof re){xt(r,404,s.code);return}if(s instanceof G||s instanceof be||s instanceof Ce){xt(r,s.statusCode,s.code);return}let i=O(s,"rest");if(i){r.status(i.statusCode).json(i.body);return}$(r,s);return}}}a(za,"getProposalDiffHandler");ve();ve();tr();function Rf(e){return e.endsWith("/")?e.slice(0,-1):e}a(Rf,"trimTrailingSlash");var Ur=class{static{a(this,"HttpAdapter")}kind="http";baseUrl;accessToken;fetchImpl;constructor(t){this.baseUrl=Rf(t.baseUrl),this.accessToken=t.accessToken,this.fetchImpl=t.fetch??globalThis.fetch.bind(globalThis)}headers(t){let n=new Headers(t);return n.set("Accept","application/json"),n.set("Node-RED-API-Version","v2"),this.accessToken&&n.set("Authorization",`Bearer ${this.accessToken}`),n}async request(t,n={}){return await this.fetchImpl(`${this.baseUrl}${t}`,{...n,headers:this.headers(n.headers)})}async readJson(t,n){let r=await t.text();if(!t.ok){let o=r;try{let s=JSON.parse(r);o=s.message??s.code??r}catch{}throw t.status===404?new G(n):t.status===409?new J("unknown","unknown",`Admin API ${n}`):t.status===401||t.status===403?new Ce(Vn):new Error(`Admin API ${n} failed (${t.status}): ${o}`)}if(!r)return null;try{return JSON.parse(r)}catch{throw new Error(`Admin API ${n} returned non-JSON`)}}async getFlows(){let t=await this.request("/flows"),n=await this.readJson(t,"GET /flows");return bn(n,"GET /flows")}async getFlow(t){let n=encodeURIComponent(t),[r,o]=await Promise.all([this.request("/flows"),this.request(`/flow/${n}`)]);if(o.status===404)throw new G(t);let s=await this.readJson(r,"GET /flows"),i=await this.readJson(o,`GET /flow/${t}`);return{rev:bn(s,"GET /flows").rev,flow:Ft(t,i)}}async updateFlow(t){let n=t.deploymentType??"nodes",r=await this.getFlows();if(r.rev!==t.expectedRev)throw new J(t.expectedRev,r.rev,"updateFlow");let o=er(r.flows,t.flowId,t.flow),s=await this.request("/flows",{method:"POST",headers:{"Content-Type":"application/json","Node-RED-Deployment-Type":n},body:JSON.stringify({flows:o,rev:t.expectedRev})});if(s.status===409){let d=r.rev;try{let l=await s.clone().json();typeof l.current_rev=="string"&&(d=l.current_rev)}catch{}throw new J(t.expectedRev,d,"updateFlow")}let i=await this.readJson(s,"POST /flows");if(!i||typeof i!="object"||typeof i.rev!="string")throw new Error("Admin API POST /flows did not return a rev");return{flowId:t.flowId,rev:i.rev}}};ve();tr();function Cf(e){let t=e.runtime?.flows;if(!t?.getFlows||!t.getFlow||!t.setFlows)throw new Error("RED.runtime.flows is missing getFlows, getFlow, or setFlows");return t}a(Cf,"requireFlowsApi");function Lf(e){if(typeof e=="string")return e;if(typeof e.rev=="string"&&e.rev.length>0)return e.rev;throw new Error("RED.runtime.flows.setFlows did not return a rev")}a(Lf,"resolveRev");var Br=class{static{a(this,"RedAdapter")}kind="red";flows;constructor(t){this.flows=Cf(t)}async getFlows(){let t=await this.flows.getFlows({});return bn(t,"RED.runtime.flows.getFlows()")}async getFlow(t){let n=await this.getFlows(),r;try{r=await this.flows.getFlow({id:t})}catch(o){throw Ef(o)?new G(t):o}return{rev:n.rev,flow:Ft(t,r)}}async updateFlow(t){let n=t.deploymentType??"nodes",r=await this.getFlows();if(r.rev!==t.expectedRev)throw new J(t.expectedRev,r.rev,"updateFlow");let o=er(r.flows,t.flowId,t.flow),s=await this.flows.setFlows({flows:{flows:o,rev:t.expectedRev},deploymentType:n});return{flowId:t.flowId,rev:Lf(s)}}};function Ja(e){let t=e?.runtime?.flows;return!!(t?.getFlows&&t.getFlow&&t.setFlows)}a(Ja,"canUseRedRuntime");function Ef(e){if(!e||typeof e!="object")return!1;let t=e;if((typeof t.status=="number"?t.status:t.statusCode)===404||t.code==="not_found"||t.code==="flow_not_found")return!0;let r=typeof t.message=="string"?t.message.toLowerCase():"";return r.includes("not found")||r.includes("unknown flow")}a(Ef,"isRuntimeFlowNotFound");var Ka=require("module");function Va(e){if(!e||typeof e!="object")return!1;let t=e;return typeof t.getFlows=="function"&&typeof t.getFlow=="function"&&typeof t.setFlows=="function"}a(Va,"isFlowsApi");function If(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,Ka.createRequire)(e)}a(If,"createPluginRequire");function Nf(e){try{let t=e("@node-red/runtime/lib/settings");return typeof t.available=="function"&&t.available()===!0}catch{return!1}}a(Nf,"isNodeRedRuntimeInitialized");function Ff(){try{let e=If();if(!Nf(e))return null;let t=e("@node-red/runtime");return Va(t.flows)?t.flows:null}catch{return null}}a(Ff,"tryRequireInitializedRuntimeFlows");function Xa(e){if(e?.runtime&&Va(e.runtime.flows))return e;let t=Ff();return t?{runtime:{flows:t}}:null}a(Xa,"resolveRuntimeHost");function Ya(e={}){let t=Xa(e.RED??null);if(t&&Ja(t))return new Br(t);let n=e.adminAuthEnabled===!0;if(e.http?.baseUrl){if(n&&!e.http.accessToken)throw new be("In-process Node-RED flow runtime is unavailable and HTTP Admin API fallback has no credentials while adminAuth is enabled. Ensure the plugin runs inside Node-RED so @node-red/runtime.flows can be used.");return new Ur(e.http)}throw new be("No usable Node-RED flow adapter: in-process flows API is unavailable and no HTTP Admin API baseUrl was provided")}a(Ya,"createFlowAdapter");ve();var Wr=require("fs"),Qa=require("path");Yn();z();var Of=/^[a-z][a-z0-9._-]{0,63}$/;function $f(e){if(e.kind==="system")return{kind:"system"};if(e.kind==="access-link"){let n={kind:"access-link",linkId:String(e.linkId).replace(/\0/g,"").slice(0,200)};return typeof e.label=="string"&&(n.label=e.label.replace(/\0/g,"").slice(0,200)),n}let t={kind:"admin"};return typeof e.username=="string"&&(t.username=e.username.replace(/\0/g,"").slice(0,200)),t}a($f,"sanitizeActor");var qr=class{constructor(t,n={}){this.paths=t;this.filePath=(0,Qa.join)(t.logs,"audit.jsonl"),this.now=n.now??(()=>Date.now()),this.maxAppendBytes=n.maxAppendBytes??as}static{a(this,"AuditLog")}filePath;now;maxAppendBytes;seq=0;append(t,n,r){let o=Of.test(t)?t:"unknown";this.seq+=1;let s={id:`log-${this.now()}-${this.seq}`,at:new Date(this.now()).toISOString(),action:o,actor:$f(n)};r!==void 0&&(s.details=Ts(r));let i=je(s,this.maxAppendBytes,{kind:"audit"});return ls(this.paths.root,this.filePath,i),s}list(t=100){if(!(0,Wr.existsSync)(this.filePath))return[];let n=(0,Wr.readFileSync)(this.filePath,"utf8").split(`
|
|
6094
|
+
`).map(o=>o.trim()).filter(Boolean),r=[];for(let o of n)try{r.push(JSON.parse(o))}catch{}return r.slice(-Math.max(1,t)).reverse()}};qe();qn();ae();lt();ve();qe();Je();Ae();var Mf="Flow revision conflict: the expectedRev is stale. Ask the AI to re-read flows and resubmit a new proposal.";function Za(e){let t=se(e),n=ar(e)||void 0;return{proposalId:e.id,title:t.title,displayName:t.displayName,flowId:e.payload.flowId,...n?{flowLabel:n}:{}}}a(Za,"auditProposalMeta");var Gr=class{constructor(t,n,r,o){this.proposals=t;this.audit=n;this.adapter=r;this.getDeploymentType=o}static{a(this,"ProposalService")}async approve(t,n,r={}){let o=this.proposals.beginApprove(t),s=Za(o),i=r.deploymentType??this.getDeploymentType();try{let d=o.type;if(d!=="replace_flow"){let l=this.proposals.markFailed(t,n,`Unsupported proposal type '${d}'`);return this.audit.append("proposal.failed",n,{...s,reason:l.reason}),l}try{let l=await this.adapter.updateFlow({flowId:o.payload.flowId,flow:o.payload.flow,expectedRev:o.expectedRev,deploymentType:i}),c=this.proposals.markApproved(t,n,l.rev);return this.audit.append("proposal.approved",n,{...s,resultRev:l.rev,flowId:o.payload.flowId}),Ys(this.proposals,o.payload.flowId,t,l.rev),c}catch(l){if(l instanceof J){let p=this.proposals.markRejected(t,n,Mf);return this.audit.append("proposal.rejected",n,{...s,reason:p.reason,code:l.code,expectedRev:l.expectedRev,currentRev:l.currentRev}),p}let c=l instanceof Error?l.message:String(l),u=this.proposals.markFailed(t,n,c);return this.audit.append("proposal.failed",n,{...s,reason:c}),u}}finally{this.proposals.releaseLock(t)}}reject(t,n,r){let o=this.proposals.get(t);if(o.status!=="pending")throw new te(t,o.status);let s=Za(o),i=this.proposals.markRejected(t,n,r.trim()||"Rejected by admin");return this.audit.append("proposal.rejected",n,{...s,reason:i.reason}),i}};ye();function Df(e){let t=a(()=>Promise.reject(new Ce(e)),"fail");return{kind:"http",getFlows:t,getFlow:t,updateFlow:t}}a(Df,"misconfiguredAdapter");function ed(e){let t=e.settings,n=typeof t.uiPort=="number"?t.uiPort:1880,r=t.httpAdminRoot??"/",o=!r||r==="/"?"":r.replace(/\/$/,"");return`http://127.0.0.1:${n}${o}`}a(ed,"localAdminBaseUrl");function zr(e,t={}){let n=jn(e.settings.userDir);cs(n);let r=t.settingsStore??new Pt(n,e.settings.aiAccess,u=>e.log?.warn?.(u));t.bindSettings!==!1&&Wn(r),zn(r.getEffective().publicBaseUrl);let o=t.linkStore??Dr(e);o.configureDefaults({defaultTtlSeconds:r.getEffective().defaultLinkTtlSeconds,maxActiveLinks:r.getEffective().maxActiveLinks});let s=new Qn(n),i=new qr(n),d;if(t.flowAdapter)d=t.flowAdapter;else try{d=Ya({RED:e,http:{baseUrl:ed(e)},adminAuthEnabled:W(e.settings)})}catch(u){let p=u instanceof be?u.message:Vn;e.log?.warn?.(`AI Access flow adapter unavailable: ${p}`),d=Df(p)}let l;if(t.nodeTypesAdapter)l=t.nodeTypesAdapter;else if(t.flowAdapter)l=po();else try{l=Ms({baseUrl:ed(e)})}catch(u){let p=u instanceof Error?u.message:String(u);e.log?.warn?.(`AI Access node-types adapter unavailable: ${p}`),l=po([])}let c=new Gr(s,i,d,()=>he(e.settings).defaultDeploymentType);return{linkStore:o,proposalStore:s,auditLog:i,flowAdapter:d,nodeTypesAdapter:l,proposalService:c,settingsStore:r,...t.catalogueFetcher?{catalogueFetcher:t.catalogueFetcher}:{},...t.catalogueUrl?{catalogueUrl:t.catalogueUrl}:{}}}a(zr,"createPluginServices");z();ae();Pe();Hn();var td=an(require("dns"),1),nd=an(require("http"),1),rd=an(require("https"),1),On=require("net");ae();function jf(e,t){return e===10||e===172&&t>=16&&t<=31||e===192&&t===168}a(jf,"isPrivateV4");function Hf(e,t){return e===169&&t===254}a(Hf,"isLinkLocalV4");function Uf(e,t,n){return e===0||e===100&&t>=64&&t<=127||e>=224||e===192&&t===0&&(n===0||n===2)||e===198&&(t===18||t===19||t===51&&n===100)||e===203&&t===0&&n===113}a(Uf,"isSpecialUseV4");function Bf(e){let[t=0,n=0,r=0]=e.split(".").map(Number);return t===127?"loopback":jf(t,n)||Hf(t,n)||Uf(t,n,r)||e==="168.63.129.16"?"blocked":"public"}a(Bf,"classifyIpv4");function qf(e){let t=e.split(":"),n=t.indexOf(""),r=[];for(let o of t){if(!o)continue;if(o.includes(".")){let i=o.split(".").map(Number);if(i.length!==4||i.some(d=>!Number.isInteger(d)||d<0||d>255))return;r.push((i[0]??0)<<8|(i[1]??0)),r.push((i[2]??0)<<8|(i[3]??0));continue}let s=Number.parseInt(o,16);if(!/^[\da-f]{1,4}$/i.test(o)||!Number.isInteger(s))return;r.push(s)}if(n>=0){let o=8-r.length;if(o<1)return;let s=t.slice(0,n).filter(Boolean).length;r.splice(s,0,...Array(o).fill(0))}else if(r.length!==8)return;return r.length===8?r:void 0}a(qf,"expandIpv6");function Wf(e){let t=qf(e);if(!t)return"blocked";if(t.slice(0,7).every(s=>s===0)&&t[7]===1)return"loopback";let n=t[0]??0,r=t[1]??0;return n===0||n<8192||n>16383||n===8193&&r<=511||n===8193&&r===3512||n===16383&&r<=4095||n===8194?"blocked":"public"}a(Wf,"classifyIpv6");function $n(e){let t=st(e);return t?(0,On.isIP)(t)===4?Bf(t):Wf(t):"blocked"}a($n,"classifyPublicUrlAddress");function Gf(e){let t=e.toLowerCase().replace(/\.$/,"");return["metadata","metadata.google.internal","metadata.google.com","metadata.goog","instance-data","instance-data.ec2.internal","metadata.azure.internal","metadata.aws.internal"].some(n=>t===n||t.endsWith(`.${n}`))}a(Gf,"isMetadataHostname");async function od(e,t,n){let r=Rt(e);if(!r)throw new Error("publicBaseUrl is required for Check");let o=new URL(r),s=o.hostname.replace(/^\[|\]$/g,"");if(Gf(s))throw new Error("Public URL Check cannot target metadata hosts");let i=st(s),l=(typeof e=="string"?/^https?:\/\/(\[[^\]]+\]|[^/?#:]+)/i.exec(e.trim())?.[1]:void 0)?.replace(/^\[|\]$/g,"");if(i&&l&&l.toLowerCase()!==i.toLowerCase())throw new Error("Public URL Check requires canonical IP address notation");let c=s.toLowerCase().replace(/\.$/,"")==="localhost"||i!==void 0&&$n(i)==="loopback",u=Number(o.port||(o.protocol==="https:"?443:80));if(c&&u!==t)throw new Error("Public URL Check may only target this Node-RED instance's port for loopback URLs");let p=a(g=>{let A=$n(g);if(A==="blocked"||A==="loopback"&&!c||c&&A!=="loopback")throw new Error("Public URL Check cannot target private or non-public addresses")},"validateAddress");if(i)return p(i),{origin:r,address:i,family:(0,On.isIP)(i)};let f=await new Promise((g,A)=>{let S=a(()=>A(n.reason instanceof Error?n.reason:new Error(String(n.reason))),"abort");n.throwIfAborted(),n.addEventListener("abort",S,{once:!0}),td.default.lookup(s,{all:!0},(E,I)=>{n.removeEventListener("abort",S),E?A(E):g(I)})});if(n.throwIfAborted(),!f.length)throw new Error("Public URL Check hostname has no addresses");for(let g of f)p(g.address);let m=[];for(let g of f){let A=st(g.address);if(!A)throw new Error("Public URL Check returned an invalid address");m.some(S=>S.address===A)||m.push({address:A,family:(0,On.isIP)(A)})}c&&m.sort((g,A)=>g.family-A.family);let h=m[0];if(!h)throw new Error("Public URL Check hostname has no addresses");return{origin:r,...h,addresses:m}}a(od,"preparePublicUrlCheck");function zf(e,t,n){let r=e.hostname.replace(/^\[|\]$/g,"");return new Promise((o,s)=>{let d=(e.protocol==="https:"?rd.default:nd.default).request(e,{method:"GET",agent:!1,signal:n,headers:{Host:e.host},...(0,On.isIP)(r)?{}:{servername:r},lookup:a((l,c,u)=>{c.all?u(null,[{address:t.address,family:t.family}]):u(null,t.address,t.family)},"lookup")},l=>{o(l.statusCode??0),l.destroy()});d.on("error",s),d.end()})}a(zf,"requestPinned");async function sd(e,t,n){let r=new URL(e.origin+t),o=e.addresses?.length?e.addresses:[{address:e.address,family:e.family}],s;for(let i of o){n.throwIfAborted();try{return await zf(r,i,n)}catch(d){if(n.aborted)throw d;s=d}}throw s instanceof Error?s:new Error("Public URL Check request failed")}a(sd,"requestPublicUrlCheck");ye();function Jf(e){let t=ne(e.settings.httpAdminRoot);return t==="/"?"/mcp":`${t}/mcp`}a(Jf,"mcpPageUrl");function Kf(e){return typeof e=="boolean"||typeof e=="number"||typeof e=="string"?e:Array.isArray(e)?e.join(", "):typeof e=="function"?"custom function":!1}a(Kf,"normalizeTrustProxy");function dd(e){if(!e)return"Set a Public URL before enabling OAuth.";let t=new URL(e),n=st(t.hostname),r=t.hostname.toLowerCase().replace(/\.$/,"")==="localhost"||n==="::1"||n?.startsWith("127.");return t.protocol==="http:"&&!r?"OAuth Public URL uses HTTP; HTTPS is recommended.":null}a(dd,"oauthPublicUrlWarning");function ld(e,t,n){let{sources:r,...o}=t.getEffective(),{permission:s}=Gn(e,n);return{ok:!0,adminAuthEnabled:W(e.settings),canEdit:s==="*",permission:s,mcpPageUrl:Jf(e),apiBase:de(e.settings.httpAdminRoot),settings:o,oauthPublicUrlWarning:dd(o.publicBaseUrl),sources:r,disk:t.getDisk(),trustProxy:Kf(n.app.get("trust proxy")),precedence:["settings.js","disk","default"]}}a(ld,"settingsResponse");function cd(e,t){return(n,r)=>{r.json(ld(e,t,n))}}a(cd,"getSettingsHandler");function id(e,t,n){let r=e[t];if(r!==void 0){if(typeof r!="boolean")throw new Error(`${t} must be a boolean`);n[t]=r}}a(id,"parseBooleanField");function ad(e,t,n){let r=e[t];if(r!==void 0){if(typeof r!="number"||!Number.isFinite(r)||r<=0)throw new Error(`${t} must be a positive number`);n[t]=Math.floor(r)}}a(ad,"parsePositiveIntField");function Vf(e,t,n){let r=e[t];if(r===void 0)return;let o=t==="rateLimitWindowMs"?100:1,s=t==="rateLimitWindowMs"?36e5:1e6;if(typeof r!="number"||!Number.isInteger(r)||r<o||r>s)throw new Error(`${t} must be an integer between ${o} and ${s}`);n[t]=r}a(Vf,"parseRateLimitField");function Xf(e,t){if(e.corsAllowedOrigins===void 0)return;if(!Array.isArray(e.corsAllowedOrigins))throw new Error("corsAllowedOrigins must be a list of HTTP(S) origins");let n=e.corsAllowedOrigins.map(r=>cn(r));if(n.some(r=>r===void 0))throw new Error("corsAllowedOrigins must contain only HTTP(S) origins without paths, credentials, queries, or fragments");t.corsAllowedOrigins=[...new Set(n)]}a(Xf,"parseCorsField");function ud(e,t,n){return(r,o)=>{let s=r.body;if(!s||typeof s!="object"||Array.isArray(s)){o.status(400).json({error:"Request body must be a settings object",code:"invalid_body"});return}let i={...s};for(let[l,c]of Object.entries(t.getEffective().sources))c==="settings.js"&&delete i[l];t.getEffective().sources.oauth==="settings.js"&&delete i.oauth;let d={};try{if(i.oauth!==void 0){if(!i.oauth||typeof i.oauth!="object"||Array.isArray(i.oauth))throw new Error("oauth must be an object");let l=i.oauth,c={};if(l.enabled!==void 0){if(typeof l.enabled!="boolean")throw new Error("oauth.enabled must be a boolean");c.enabled=l.enabled}for(let p of["accessTokenTtlSec","refreshIdleTtlSec","dcrMaxClients"])if(l[p]!==void 0){if(typeof l[p]!="number"||!Number.isInteger(l[p])||l[p]<1||l[p]>31536e3)throw new Error(`oauth.${p} must be a positive integer`);c[p]=l[p]}let u=l.refreshMaxLifetimeSec;if(u!==void 0){if(typeof u!="number"||!Number.isInteger(u)||u<0||u>31536e4)throw new Error("oauth.refreshMaxLifetimeSec must be a non-negative integer");c.refreshMaxLifetimeSec=u}d.oauth=c}id(i,"enabled",d),id(i,"allowWithoutAdminAuth",d),ad(i,"defaultLinkTtlSeconds",d),ad(i,"maxActiveLinks",d)}catch(l){o.status(400).json({error:l.message,code:"invalid_body"});return}if(i.defaultDeploymentType!==void 0){let l=i.defaultDeploymentType;if(l!=="nodes"&&l!=="flows"&&l!=="full"){o.status(400).json({error:"defaultDeploymentType must be 'nodes', 'flows', or 'full'",code:"invalid_body"});return}d.defaultDeploymentType=l}try{i.publicBaseUrl!==void 0&&(d.publicBaseUrl=Rt(i.publicBaseUrl)),Xf(i,d);for(let l of["rateLimitWindowMs","rateLimitMaxPerIp","rateLimitMaxPerToken"])Vf(i,l,d)}catch(l){o.status(400).json({error:l.message,code:"invalid_body"});return}try{let l=t.patch(d);zn(l.publicBaseUrl),n.configureDefaults({defaultTtlSeconds:l.defaultLinkTtlSeconds,maxActiveLinks:l.maxActiveLinks});let c=ld(e,t,r),u=l.publicBaseUrl?new URL(l.publicBaseUrl):void 0,p=u?st(u.hostname.replace(/^\[|\]$/g,"")):void 0,f=p?$n(p):void 0,m=[];f!==void 0&&f!=="public"&&m.push("Public URL uses a private or non-public IP address.");let h=dd(l.publicBaseUrl);h&&l.publicBaseUrl&&m.push(h),o.json({...c,...m.length?{warnings:m}:{}})}catch(l){if(l instanceof X){o.status(l.statusCode).json({error:l.message,code:l.code});return}$(o,l);return}}}a(ud,"putSettingsHandler");function pd(e){return async(t,n)=>{let r=new AbortController,o=setTimeout(()=>r.abort(),5e3),s;try{let i;try{let c=e.settings,p=e.server?.address(),f=t.body?.publicBaseUrl,m=typeof f=="string"?new URL(f.trim()).hostname:"";if((m.toLowerCase().replace(/^\[|\]$/g,"").replace(/\.$/,"")==="localhost"||$n(m.replace(/^\[|\]$/g,""))==="loopback")&&typeof p=="string")throw new Error("Public URL Check does not support loopback URLs when Node-RED listens on a Unix socket");let g=p&&typeof p=="object"?p.port:c.uiPort??1880;i=await od(t.body?.publicBaseUrl,g,r.signal)}catch(c){if(r.signal.aborted)throw c;n.status(400).json({error:c.message,code:"invalid_body"});return}s=i.origin+de(e.settings.httpAdminRoot);let d=await sd(i,de(e.settings.httpAdminRoot),r.signal),l={status:d,ok:d>=200&&d<300};n.json({ok:l.ok,status:l.status,url:s,message:l.ok?"Discovery endpoint is reachable":`Discovery endpoint returned HTTP ${l.status}`})}catch{n.json({ok:!1,status:null,url:s,error:r.signal.aborted?"Public URL check timed out after 5 seconds":"Could not reach the discovery endpoint"})}finally{clearTimeout(o)}}}a(pd,"checkPublicUrlHandler");ye();ot();ye();K();qn();var Yf="com55-ai-access";function Qf(e,t,n){e.httpAdmin.get("/mcp",t,(r,o)=>{o.setHeader("Cache-Control","no-store, no-cache, must-revalidate, max-age=0"),o.setHeader("Pragma","no-cache"),o.setHeader("Expires","0"),o.type("html").status(200).send(Da({apiBase:de(e.settings.httpAdminRoot),mcpRpc:He(e.settings.httpAdminRoot),editorRoot:ne(e.settings.httpAdminRoot),authTokenKey:Kn(e.settings.httpAdminRoot)}))}),e.httpAdmin.get("/mcp/api/v1",t,n,Fo(e)),e.httpAdmin.get("/mcp/api/v1/",t,n,Fo(e)),e.httpAdmin.get("/mcp/api/v1/openapi.yaml",t,n,Fa()),e.httpAdmin.get("/mcp/api/v1/openapi.json",t,n,Oa()),e.httpAdmin.get("/mcp/editor/ai-access-sidebar.js",t,(r,o)=>{o.setHeader("Cache-Control","no-store, no-cache, must-revalidate, max-age=0"),o.setHeader("Pragma","no-cache"),o.setHeader("Expires","0"),o.setHeader("X-AI-Access-Sidebar-Version",Kt),o.type("application/javascript").status(200).send(mr())})}a(Qf,"registerMcpShellAndDiscovery");var Zf=/^\/mcp\/(?:api\/v1\/proposals|rpc$|a\/[^/]+\/(?:proposals|propose|draft))/;function md(e,t={}){let n=zr(e,{...t,linkStore:t.linkStore??Dr(e)}),r=Ri(()=>n.settingsStore.getEffective()),{ip:o,token:s}=r,i=new jr(jn(e.settings.userDir),()=>n.settingsStore.getEffective().oauth.dcrMaxClients),d=a(()=>n.settingsStore.getEffective().publicBaseUrl,"publicBase"),l=a(()=>`${d().replace(/\/$/,"")}/mcp`,"mcpUrl"),c=new Hr(i,l,()=>n.settingsStore.getEffective().oauth.accessTokenTtlSec,()=>n.settingsStore.getEffective().oauth.refreshIdleTtlSec,()=>n.settingsStore.getEffective().oauth.refreshMaxLifetimeSec),u=La(e,c,d,l);ps(e.httpAdmin,us(()=>n.settingsStore.getEffective().corsAllowedOrigins));let p=a(w=>(Array.isArray(w)?w:w?[w]:[]).flatMap(x=>{if(typeof x=="string")return[x];let k=x;return k&&typeof k.path=="string"?[{path:k.path,root:typeof k.root=="string"?k.root:void 0}]:[]}),"staticMounts"),f={authorization:[],metadata:[],details:[],httpNodeRootFallback:e.settings.httpNodeRoot===!1||!e.httpNode},m="",h=a(()=>{let w=Ca({httpAdmin:e.httpAdmin,httpNode:e.httpNode,httpAdminRoot:e.settings.httpAdminRoot,httpNodeRoot:e.settings.httpNodeRoot,metadataFallback:f.httpNodeRootFallback,eachNode:e.nodes?M=>e.nodes?.eachNode(M):void 0,staticDirs:p(e.settings.httpStatic),httpStaticRoot:e.settings.httpStaticRoot,fileExists:fd.existsSync}),x=w.authorization,k=w.metadata;f.authorization=x.map(ts),f.metadata=k.map(ts),f.details=[...x.map(M=>({...M,kind:"authorization"})),...k.map(M=>({...M,kind:"metadata"}))];let C=[...f.authorization.map(M=>`${M} \u2014 OAuth authorization routes disabled`),...f.metadata.map(M=>`${M} \u2014 OAuth metadata disabled`)],H=C.join(`
|
|
6095
|
+
`);if(H!==m)for(let M of C)e.log?.warn?.(`AI Access OAuth route collision: ${M}`);m=H},"refreshCollisions");h(),e.events?.on("flows:started",h);let g=Mo({windowMs:6e4,maxPerIp:30}),A=a((w,x,k)=>{if(!n.settingsStore.getEffective().oauth.enabled||f.authorization.length||!w.path.startsWith("/mcp/oauth/")){k();return}let C=w.path.replace(/\/+$/,"").toLowerCase(),H=a(()=>Ct(e)(w,x,()=>u.server(w,x,k)),"run");C==="/mcp/oauth/authorize"?o(w,x,()=>g(w,x,H)):C.startsWith("/mcp/oauth/consent/")?o(w,x,H):H()},"oauthServerMiddleware");A.oauthOwned=!0,e.httpAdmin.use(A);let S=a((w,x,k)=>{if(!n.settingsStore.getEffective().oauth.enabled||f.metadata.length){k();return}u.metadata(w,x,k)},"metadataHandler");S.oauthOwned=!0,f.httpNodeRootFallback?e.httpAdmin.use("/mcp",S):e.httpNode?.use(S),e.httpAdmin.get("/mcp/api/v1/oauth/clients",o,s,we(e,"api"),(w,x)=>x.json({clients:i.data.clients.map(k=>({client_id:k.client_id,client_name:k.client_name,redirect_uris:k.redirect_uris,lastUsedAt:k.lastUsedAt}))})),e.httpAdmin.get("/mcp/api/v1/oauth/status",o,s,we(e,"api"),(w,x)=>{let k=!W(e.settings)||Ue(w)==="*";x.json({...f,details:k?f.details:f.details.map(({file:C,...H})=>H),collisions:[...f.authorization,...f.metadata],oauthEnabled:n.settingsStore.getEffective().oauth.enabled})}),e.httpAdmin.delete("/mcp/api/v1/oauth/clients/:id",o,s,we(e,"api"),fn(e),(w,x)=>x.json({revoked:i.revokeClient(String(w.params.id))})),e.httpAdmin.use((w,x,k)=>{if(!Zf.test(w.path)){k();return}n.flowAdapter.getFlows().then(C=>zs(w,sr(C.flows))).catch(()=>{}).finally(()=>k())}),Zi(e,{linkStore:n.linkStore,proposalStore:n.proposalStore,auditLog:n.auditLog,flowAdapter:n.flowAdapter,nodeTypesAdapter:n.nodeTypesAdapter,...n.catalogueFetcher?{catalogueFetcher:n.catalogueFetcher}:{},...n.catalogueUrl?{catalogueUrl:n.catalogueUrl}:{},rateLimiters:r}),Qf(e,o,s),e.httpAdmin.get("/mcp/api/v1/status",o,s,ys(e,w=>n.linkStore.validateResult(w)),P((w,x)=>{x.json(tn(e,w))})),e.httpAdmin.get("/mcp/api/v1/settings",o,s,we(e,"api"),P(cd(e,n.settingsStore))),e.httpAdmin.put("/mcp/api/v1/settings",o,s,we(e,"api"),fn(e),Xe("rest"),P(ud(e,n.settingsStore,n.linkStore))),e.httpAdmin.post("/mcp/api/v1/settings/check-public-url",o,s,we(e,"api"),fn(e),Xe("rest"),P(pd(e)));let E=[o,s,we(e,"api"),Ct(e),fn(e)];e.httpAdmin.get("/mcp/api/v1/links",...E,P(la(e,n.linkStore))),e.httpAdmin.post("/mcp/api/v1/links",...E,P(ca(e,n.linkStore,n.auditLog))),e.httpAdmin.delete("/mcp/api/v1/links/:id",...E,P(ua(e,n.linkStore,n.auditLog)));let I=[o,s,Ct(e),gs(e,w=>n.linkStore.validateResult(w))],N=n.flowAdapter,me={nodeTypes:n.nodeTypesAdapter,...n.catalogueFetcher?{catalogueFetcher:n.catalogueFetcher}:{},...n.catalogueUrl?{catalogueUrl:n.catalogueUrl}:{}};e.httpAdmin.get("/mcp/api/v1/flows",...I,P(yi(N))),e.httpAdmin.get("/mcp/api/v1/flows/:id",...I,P(_r(N))),e.httpAdmin.get("/mcp/api/v1/nodes/:id",...I,P(Pr(N))),e.httpAdmin.get("/mcp/api/v1/nodes/:id/field/:name",...I,P(Rr(N))),e.httpAdmin.get("/mcp/api/v1/search",...I,P(Cr(N))),e.httpAdmin.get("/mcp/api/v1/node-types",...I,P(Lr(me))),e.httpAdmin.get("/mcp/api/v1/node-types/:type",...I,P(Er(me))),e.httpAdmin.get("/mcp/api/v1/palette/search",...I,P(Ir(me)));let v=e.settings.httpAdminRoot;e.httpAdmin.get("/mcp/api/v1/proposals",...I,P(Ua(n.proposalStore,n.flowAdapter,v))),e.httpAdmin.get("/mcp/api/v1/proposals/:id",...I,P(Ba(n.proposalStore,v,n.flowAdapter))),e.httpAdmin.get("/mcp/api/v1/proposals/:id/diff",...I,P(za(n.proposalStore,n.flowAdapter))),e.httpAdmin.post("/mcp/api/v1/proposals",o,s,Xe("rest"),Et(e,n.linkStore,"rest"),P(Ha(n.proposalStore,n.auditLog,n.flowAdapter,v,n.nodeTypesAdapter))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/approve",...E,Xe("rest"),P(qa(n.proposalService,v))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/reject",...E,Xe("rest"),P(Wa(n.proposalService,v))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/withdraw",o,s,Xe("rest"),Et(e,n.linkStore,"rest"),P(Ga(n.proposalStore,n.auditLog,v)));let b=[o,s,(w,x,k)=>{let C=w.headers.authorization;if(!n.settingsStore.getEffective().oauth.enabled||C?.toLowerCase().startsWith("bearer aia_")){if(n.settingsStore.getEffective().oauth.enabled){let H=x.setHeader.bind(x);x.setHeader=((M,_e)=>{if(M.toLowerCase()==="www-authenticate"&&typeof _e=="string"&&!_e.includes("resource_metadata=")){let De=`${d().replace(/\/$/,"")}${f.httpNodeRootFallback?"/mcp":""}/.well-known/oauth-protected-resource/mcp`;_e+=`, resource_metadata="${De}", scope="read propose"`}return H(M,_e)})}Et(e,n.linkStore,"mcp")(w,x,k);return}Ct(e)(w,x,()=>u.auth(w,x,k))}];e.httpAdmin.post("/mcp/rpc",...b,Xe("mcp"),P(ba(e,n))),e.httpAdmin.get("/mcp/rpc",...b,Vo),e.httpAdmin.delete("/mcp/rpc",...b,Vo),e.httpAdmin.get("/mcp/api/v1/logs",o,s,we(e,"api"),Ct(e),P(pa(n.auditLog,n.linkStore))),e.httpAdmin.use("/mcp/api/v1",mi()),oa(e)}a(md,"registerRoutes");function em(e){e.plugins.registerPlugin(Yf,{type:"com55-ai-access",onadd(){md(e),e.log?.info?.("Node-RED AI Access plugin loaded (/mcp)")}})}a(em,"registerAiAccessPlugin");var tm=em;0&&(module.exports={ACCESS_LINK_INVALID_MESSAGE,ACCESS_LINK_MISSING_MESSAGE,AccessLinkStore,EDITOR_SIDEBAR_ASSET_VERSION,MAX_PROPOSAL_JSON_BYTES,SettingsStore,areAiFeaturesEnabled,bindSettingsStore,createPluginServices,getStorageDir,isAdminAuthEnabled,packageVersion,registerRoutes,renderEditorSidebarJs,renderEditorSidebarLoaderHtml,requireAccessLink,resolveAiAccessSettings});
|