@com55/node-red-ai-access 0.2.0-beta.2 → 0.2.0-beta.3
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 +61 -1
- package/README.md +100 -75
- package/ai-access.html +1 -1
- package/dist/plugin.cjs +957 -650
- package/dist/plugin.d.cts +9 -2
- package/docs/README.md +2 -2
- package/docs/api/openapi.yaml +5 -5
- package/docs/errors.md +2 -2
- package/docs/mcp-clients.md +16 -15
- package/docs/mcp-tools.md +1 -1
- package/package.json +1 -1
package/dist/plugin.cjs
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var kd=Object.create;var dn=Object.defineProperty;var Ad=Object.getOwnPropertyDescriptor;var xd=Object.getOwnPropertyNames;var Sd=Object.getPrototypeOf,Td=Object.prototype.hasOwnProperty;var a=(e,t)=>dn(e,"name",{value:t,configurable:!0});var U=(e,t)=>()=>(e&&(t=e(e=0)),t);var ds=(e,t)=>{for(var n in t)dn(e,n,{get:t[n],enumerable:!0})},ls=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of xd(t))!Td.call(e,o)&&o!==n&&dn(e,o,{get:()=>t[o],enumerable:!(r=Ad(t,o))||r.enumerable});return e};var ln=(e,t,n)=>(n=e!=null?kd(Sd(e)):{},ls(t||!e||!e.__esModule?dn(n,"default",{value:e,enumerable:!0}):n,e)),Pd=e=>ls(dn({},"__esModule",{value:!0}),e);function Be(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 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
|
|
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 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="${
|
|
9
|
-
`}a(
|
|
3
|
+
`,o=Buffer.from(r,"utf8");if(o.byteLength>t)throw new X(n.kind,o.byteLength,t);return{bytes:o}}function us(e,t){let n=(0,cn.resolve)(t),r=(0,cn.resolve)(e);if(r!==n&&!r.startsWith(n+cn.sep))throw new Error("Path escapes .ai-access storage root");return r}function Rt(e,t,n){let r=us(t,e),o=`${r}.${process.pid}.tmp`;(0,Me.writeFileSync)(o,n.bytes,{mode:Hn}),(0,Me.renameSync)(o,r),(0,Me.chmodSync)(r,Hn)}function ps(e,t,n){let r=us(t,e);(0,Me.appendFileSync)(r,n.bytes,{mode:Hn});try{(0,Me.chmodSync)(r,Hn)}catch{}}var Me,cn,Yr,Hn,j,cs,X,G=U(()=>{"use strict";Me=require("fs"),cn=require("path"),Yr=448,Hn=384,j=15e5,cs=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(Be,"encodeTrustedJson");a(us,"assertUnderAiAccess");a(Rt,"writeTrustedAtomic");a(ps,"appendTrusted")});function pn(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 Ed(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 ms(e){return(t,n,r)=>{if(!Rd.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=pn(t.get("origin")),d=i===void 0?void 0:e().find(c=>c===i),l=d!==void 0;if(Ed(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",Cd),t.method==="OPTIONS"&&t.get("access-control-request-method")){n.setHeader("Access-Control-Allow-Methods",_d),n.setHeader("Access-Control-Allow-Headers",Ld),n.setHeader("Access-Control-Max-Age",String(600)),n.status(204).end();return}s=!1,r()}}function hs(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 Rd,_d,Ld,Cd,qn=U(()=>{"use strict";Rd=/^\/mcp(?:\/?$|\/(?:a(?:\/|$)|api(?:\/|$)|rpc\/?$))/,_d="GET, POST, DELETE, OPTIONS",Ld="Authorization, Content-Type, Accept, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID",Cd="Mcp-Session-Id, WWW-Authenticate, Retry-After, RateLimit, RateLimit-Policy, RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset";a(pn,"canonicalOrigin");a(Ed,"appendVary");a(ms,"aiCorsMiddleware");a(hs,"installAiCorsMiddleware")});function Nd(e){return e==="nodes"||e==="flows"||e==="full"}function Wn(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)),Nd(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=Ct(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=pn(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 Fd(e,t){if(!(0,mn.existsSync)(e))return{};try{let n=(0,mn.readFileSync)(e,"utf8");return Wn(JSON.parse(n),t)}catch(n){return t?.(`AI Access: config.json is corrupt or unreadable; defaults are used (${String(n)})`),{}}}function zn(e,t){let n={},r={...fn};for(let s of Object.keys(fn).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]??fn[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 eo(e,t){let n=Wn(e??{},t);for(let o of Object.keys(fn).filter(s=>s!=="oauth"))e&&Object.hasOwn(e,o)&&n[o]===void 0&&Object.assign(n,{[o]:fn[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 mn,Zr,Id,_t,fn,Lt,Gn=U(()=>{"use strict";mn=require("fs"),Zr=require("path");ie();qn();G();Id=16384,_t={enabled:!1,accessTokenTtlSec:3600,refreshIdleTtlSec:2592e3,refreshMaxLifetimeSec:7776e3,dcrMaxClients:5},fn={enabled:!0,allowWithoutAdminAuth:!1,defaultLinkTtlSeconds:14400,maxActiveLinks:10,defaultDeploymentType:"nodes",publicBaseUrl:"",corsAllowedOrigins:[],rateLimitWindowMs:6e4,rateLimitMaxPerIp:600,rateLimitMaxPerToken:300,oauth:{..._t}};a(Nd,"isDeploymentType");a(Wn,"sanitizePartial");a(Fd,"readDiskFile");a(zn,"mergeAiAccessSettings");a(eo,"settingsJsPartial");Lt=class{constructor(t,n,r){this.paths=t;this.filePath=(0,Zr.join)(t.root,"config.json"),this.settingsJs=eo(n,r),this.disk=Fd((0,mn.existsSync)(this.filePath)?this.filePath:(0,Zr.join)(t.root,"settings.json"),r),this.cached=zn(this.settingsJs,this.disk)}static{a(this,"SettingsStore")}filePath;disk;settingsJs;cached;getEffective(){return this.cached}getDisk(){return{...this.disk}}update(t){let n=Wn(t);for(let r of Object.keys(this.settingsJs))delete n[r];return this.persist(n),this.disk=n,this.cached=zn(this.settingsJs,this.disk),this.cached}patch(t){let n=Wn(t);return this.update({...this.disk,...n,...n.oauth?{oauth:{...this.disk.oauth,...n.oauth}}:{}})}persist(t){let n=Be(t,Id,{kind:"settings",pretty:!0});Rt(this.paths.root,this.filePath,n)}}});function Jn(e){to=e}function W(e){return e.adminAuth!=null&&e.adminAuth!==!1}function he(e){return to?to.getEffective():zn(eo(e.aiAccess),{})}function ge(e){let t=he(e);return t.enabled?W(e)?!0:t.allowWithoutAdminAuth:!1}var to,ye=U(()=>{"use strict";Gn();to=null;a(Jn,"bindSettingsStore");a(W,"isAdminAuthEnabled");a(he,"resolveAiAccessSettings");a(ge,"areAiFeaturesEnabled")});function ae(e){let t=`/${e??""}`.replace(/\/{2,}/g,"/");for(;t.length>1&&t.endsWith("/");)t=t.slice(0,-1);return t}function de(e){let t=ae(e);return t==="/"?"/mcp/api/v1":`${t}/mcp/api/v1`}function hn(e){let t=ae(e);return t==="/"?"/ai-access":`${t}/ai-access`}function qe(e){let t=ae(e);return t==="/"?"/mcp/rpc":`${t}/mcp/rpc`}function Od(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 We(e){if(e.auth)return e.auth.scopes.includes("propose")?"propose":e.auth.scopes.includes("read")?"read":null;let t=Od(e);return t.includes("*")?"*":t.includes("read")?"read":null}function $d(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 gs(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"?gs(o):null;e.auth.needsPermission(n)(r,o,d=>{if(i?.(),d){s(d);return}if(!r.user){if(t==="page"){o.redirect(ae(e.settings.httpAdminRoot));return}o.status(401).json({error:"Authentication required",code:"auth_required"});return}if(!We(r)){o.status(401).json({error:"Insufficient permissions",code:"insufficient_permissions"});return}s()})}}function Kn(e,t){let n=W(e.settings),r=he(e.settings),o=ge(e.settings),s=null;return n?s=We(t):o&&(s="*"),{adminAuthEnabled:n,aiFeaturesEnabled:o,allowWithoutAdminAuth:r.allowWithoutAdminAuth,permission:s,capabilities:$d(s,o)}}function Et(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 gn(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(We(t)!=="*"){n.status(403).json({error:"Full admin permission (*) is required for this action",code:"insufficient_permissions"});return}r()}}function Md(e){return e.startsWith("aia_")}function Dd(e){return Md(e)?!0:e.length<24}function jd(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 ys(e,t,n,r){if(!W(e.settings)){r();return}let o={error:le,code:Le},s=gs(n,o,Y);e.auth.needsPermission("read")(t,n,i=>{if(s(),i){r(i);return}if(!n.headersSent){if(!t.user||!We(t)){n.setHeader("WWW-Authenticate",Y),n.status(401).json(o);return}r()}})}function ws(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?(bn(t,r,l.link.kind),"wrong_kind"):(e.accessLink=l.link,"ok");if(l.status==="expired")return wn(e,t,o,l.expiresAt),"invalid";if(Dd(d)){t.setHeader("WWW-Authenticate",Y);let c=jd(d);return t.status(401).json({error:st,code:yn,...c?{hint:c}:{}}),"invalid"}return"continue"}function bs(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=ws(r,o,t,n,e.settings.httpAdminRoot);if(i==="ok"){s();return}i==="invalid"||i==="wrong_kind"||ys(e,r,o,s)}}function vs(e,t,n="rest"){return(r,o,s)=>{let i=ws(r,o,t,n,e.settings.httpAdminRoot);if(i==="ok"){s();return}i==="invalid"||i==="wrong_kind"||ys(e,r,o,s)}}var _e=U(()=>{"use strict";it();ye();a(ae,"normalizeHttpAdminRoot");a(de,"mcpApiBase");a(hn,"dashboardPath");a(qe,"mcpRpcPath");a(Od,"scopesFromRequest");a(We,"resolvePermission");a($d,"capabilitiesFor");a(gs,"interceptNeedsPermissionUnauthorized");a(we,"requireAdminSession");a(Kn,"describeAccess");a(Et,"requireAiFeatures");a(gn,"requireStarPermission");a(Md,"looksLikeAccessLinkToken");a(Dd,"looksLikeFailedAccessLinkAttempt");a(jd,"truncatedTokenHint");a(ys,"continueAsAdminSessionOrAccessLinkMissing");a(ws,"tryAccessLinkBearer");a(bs,"requireAdminReadOrAccessLink");a(vs,"requireAdminSessionOrAccessLink")});function Hd(e,t){let n=ae(e);return`${n==="/"?"/mcp/a":`${n}/mcp/a`}/${t}`}function Ud(e){return!!e.app.get("trust proxy")}function ks(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 Bd(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 at(e){try{let n=new URL(`http://${e.includes(":")&&!e.startsWith("[")?`[${e}]`:e}`).hostname.replace(/^\[|\]$/g,"");if(!(0,no.isIP)(n))return;if(n.startsWith("::ffff:")){let r=n.slice(7);if((0,no.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 Ct(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 Vn(e){ro=Bd(e)}function xs(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],!zd.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)||!Wd.test(t)&&!qd.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 Ts(e){let t=e?.trim().toLowerCase();return t==="http"||t==="https"?t:void 0}function Gd(e){let t=e.socket;return t?.encrypted?"https":t?"http":Ts(e.protocol)??"http"}function oo(e){if(ro)return ro;let t=Ud(e),n=t?ks(e,"x-forwarded-host"):void 0,r=e.get("host")?.trim(),o=xs(n)?n:xs(r)?r:void 0;if(!o)return;let s=(t?Ts(ks(e,"x-forwarded-proto")):void 0)??Gd(e);try{return new URL(`${s}://${o}`).origin}catch{return}}function vn(e,t){let n=oo(e);return n?`${n}${t}`:t}function Ps(e,t,n){return vn(e,Hd(t,n))}function so(e){return e.replace(/\/mcp\/a\/[^/?#]+/gi,"/mcp/a/<redacted>")}var no,It,Ss,ro,As,qd,Wd,zd,ie=U(()=>{"use strict";no=require("net");_e();It=8192,Ss=1800*1e3;a(Hd,"mcpAccessLinkPath");a(Ud,"isTrustProxyEnabled");a(ks,"firstForwardedValue");a(Bd,"normalizePublicBaseUrl");a(at,"canonicalizeIp");a(Ct,"validateSettingsPublicBaseUrl");a(Vn,"bindPublicBaseUrl");As="[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?",qd=new RegExp(`^${As}(?:\\.${As})*$`),Wd=/^(?:(?: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)$/,zd=/^\[[0-9A-Fa-f:.]+\]$/;a(xs,"isValidHostHeader");a(Ts,"validScheme");a(Gd,"connectionScheme");a(oo,"requestPublicOrigin");a(vn,"absolutizePath");a(Ps,"buildAccessLinkUrl");a(so,"redactAccessLinkTokenInPath")});function Qd(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",Y),e.status(401).json({error:t,code:n,...r?{hint:r}:{}})}function Zd(e){let t=e??"";for(;t.endsWith("/");)t=t.slice(0,-1);return`${t}/ai-access#links`}function el(e,t,n,r){let o=vn(e,Zd(t));return{error:Jd,code:Vd,...n?{expiredAt:n}:{},accessLinksUrl:o,hint:r??`Ask the user to create a new access link in Node-RED on the /ai-access dashboard \u2192 Access tokens (${o}).`}}function wn(e,t,n,r,o){t.setHeader("WWW-Authenticate",Y),t.status(401).json(el(e,n,r,o))}function tl(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 Xn(e){return e==="get_link"?"Please open Node-RED \u2192 /ai-access \u2192 Access tokens and create a get_link access URL, then send me that /mcp/a/<token> link.":`Please open Node-RED \u2192 /ai-access \u2192 Access tokens and create a kind=${e} token, then send me the Bearer token (not a get_link path URL).`}function rl(e,t){let n=nl;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: ${Xn("get_link")}`:e==="rest"?`This REST endpoint requires a rest token. This token is kind=${t} \u2014 ${n}. What to tell the user: ${Xn("rest")}`:`This MCP endpoint requires an mcp token. This token is kind=${t} \u2014 ${n}. What to tell the user: ${Xn("mcp")}`}function bn(e,t,n){e.setHeader("WWW-Authenticate",Y),e.status(403).json({error:`Access link kind mismatch: expected ${t}, got ${n}`,code:Yd,expectedKind:t,actualKind:n,hint:rl(t,n),tellUser:Xn(t)})}function Nt(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=Qd(r.headers.authorization);if(i.kind==="basic"){io(o,Kd,Xd);return}if(i.kind==="missing"||i.kind==="other"){io(o,le,Le);return}let d=t.validateResult(i.token);if(d.status==="expired"){wn(r,o,e.settings.httpAdminRoot,d.expiresAt);return}if(d.status!=="active"){io(o,st,yn,tl(i.token));return}if(d.link.kind!==n){bn(o,n,d.link.kind);return}r.accessLink=d.link,s()}}var st,Jd,le,Kd,Y,Le,yn,Vd,Xd,Yd,nl,it=U(()=>{"use strict";ie();ye();st="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.",Jd="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>.",Kd="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"',Le="access_link_missing",yn="access_link_invalid",Vd="token_expired",Xd="bearer_scheme_required",Yd="wrong_token_kind";a(Qd,"parseAuthorizationHeader");a(io,"sendUnauthorized");a(Zd,"accessLinksPagePath");a(el,"tokenExpiredBody");a(wn,"sendTokenExpired");a(tl,"truncatedBearerHint");nl="ask the owner: Node-RED /ai-access \u2192 Access tokens \u2192 create kind=rest/mcp (or get_link)";a(Xn,"whatToTellUserWrongTokenKind");a(rl,"wrongTokenKindHint");a(bn,"sendWrongTokenKind");a(Nt,"requireAccessLink")});var J,z,be,Ce,Qn,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}},z=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"}},Qn="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 Ee(e,t){if(!e||!ol.test(e)||e.includes(".."))throw new Error(`Invalid ${t}`);return e}function Zn(e){if(!e||!sl.test(e)||e.includes(".."))throw new Error("Invalid proposal id");return e}function ao(e,t){let n=(0,dt.basename)(t);if(!n||n!==t||n.includes(".."))throw new Error("Invalid file name");let r=(0,dt.resolve)(e),o=(0,dt.resolve)(r,n);if(o!==r&&!o.startsWith(r+dt.sep))throw new Error("Path escapes storage directory");return o}function Rs(e,t=1e5){if(typeof e=="string")return e.replace(/\0/g,"").slice(0,t)}function Ft(e){let t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},n={};for(let o of il){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=Rs(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 _s(e){let t={};for(let n of al){if(!(n in e))continue;let r=e[n];if(dl.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=Rs(r,2e3);o!==void 0&&(t[n]=o)}return t}var dt,ol,sl,il,al,dl,er=U(()=>{"use strict";dt=require("path");G();ol=/^[A-Za-z0-9_.-]+$/,sl=/^prop-[A-Za-z0-9_-]+$/,il=["id","type","label","disabled","info","env"],al=["proposalId","flowId","expectedRev","resultRev","reason","code","currentRev","title","displayName","flowLabel","linkId","label","expiresAt","createdAt","ttlSeconds","kind"],dl=new Set(["ttlSeconds"]);a(Ee,"assertSafeSegment");a(Zn,"assertSafeProposalId");a(ao,"resolveUnderDir");a(Rs,"sanitizeString");a(Ft,"sanitizeFlowTab");a(_s,"sanitizeAuditDetails")});function lo(e,t){return`oauth-${(0,nr.createHash)("sha256").update(`${e}\0${t}`).digest("hex")}`}function lt(e){return e.kind==="oauth"?lo(e.clientId,e.user):e.linkId}function ll(e){return e==="pending"||e==="approved"||e==="rejected"||e==="failed"||e==="withdrawn"||e==="expired"||e==="superseded"}function Ls(e){if(!Array.isArray(e)||e.length===0)return;let t=[];for(let n of e.slice(0,cl)){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 nr,ze,Cs,ne,te,Ot,kn,cl,tr,Ge=U(()=>{"use strict";nr=require("crypto"),ze=require("fs"),Cs=require("path");er();G();a(lo,"oauthOwnerId");a(lt,"proposalOwnerId");ne=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"}},Ot=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"}},kn=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(ll,"isProposalStatus");cl=50;a(Ls,"sanitizeProposalWarnings");tr=class{constructor(t,n={}){this.paths=t;this.now=n.now??(()=>Date.now()),this.randomId=n.randomId??(()=>(0,nr.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=Zn(t)}catch{throw new ne(t)}return ao(this.paths.proposals,`${n}.json`)}toDiskRecord(t){let n=Zn(t.id),r=Ee(String(t.expectedRev).slice(0,200),"expectedRev"),o=Ee(String(t.payload.flowId).slice(0,200),"flowId"),s=Ft(t.payload.flow),i=t.baseFlow!==void 0?Ft(t.baseFlow):void 0,d=t.createdBy.kind==="oauth"?{kind:"oauth",clientId:Ee(String(t.createdBy.clientId).slice(0,200),"clientId"),user:String(t.createdBy.user).replace(/\0/g,"").slice(0,200)}:{kind:"access-link",linkId:Ee(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:ll(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=Ls(t.warnings);if(c&&(l.warnings=c),t.decidedBy)if(t.decidedBy.kind==="access-link"){let u={kind:"access-link",linkId:Ee(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=Ee(t.resultRev.slice(0,200),"resultRev")),l}writeAtomic(t,n){let r=this.fileFor(t),o=this.toDiskRecord(n),s=Be(o,this.maxProposalBytes,{kind:"proposal",pretty:!0});Rt(this.paths.root,r,s)}create(t){let n=new Date(this.now()).toISOString(),r=Zn(`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=Ls(t.warnings);return s&&(o.warnings=s),this.writeAtomic(r,o),this.get(r)}get(t){let n=this.fileFor(t);if(!(0,ze.existsSync)(n))throw new ne(t);return JSON.parse((0,ze.readFileSync)(n,"utf8"))}list(t){if(!(0,ze.existsSync)(this.paths.proposals))return[];let n=(0,ze.readdirSync)(this.paths.proposals).filter(o=>o.endsWith(".json")),r=[];for(let o of n){let s=(0,Cs.basename)(o);if(!(s!==o||s.includes("..")))try{let i=JSON.parse((0,ze.readFileSync)(ao(this.paths.proposals,s),"utf8"));if(t?.linkId&<(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 kn(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 Es(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 rr(e,t,n){let r=e.find(d=>d.id===t);if(!r||r.type!=="tab"&&r.type!=="subflow")throw new z(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({...Es(l,`flowData.nodes[${d}]`),z:t});for(let[d,l]of(n.configs??[]).entries())s.push({...Es(l,`flowData.configs[${d}]`),z:t});return[...e.filter(d=>d.id!==t&&d.z!==t),o,...s]}function An(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 $t(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 z(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 z(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 or=U(()=>{"use strict";ve();a(Es,"asFlowNode");a(rr,"replaceFlowTabInFlatFlows");a(An,"assertFlowsSnapshot");a($t,"normalizeFlowTab")});function fo(e){return e?ul.has(e)?!0:e.startsWith("subflow:"):!1}function Je(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 wl(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 bl(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 kl(e){return Math.max(2,Math.floor(e.length/3))}function mo(e,t,n=5){if(typeof e!="string"||!e)return[];let r=kl(e);return t.filter(s=>typeof s=="string"&&s!==e&&!vl.has(s)&&!s.startsWith("_")&&!s.startsWith("subflow:")).map(s=>({c:s,dist:wl(e,s),prefix:bl(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 Al(e){return e.endsWith("/")?e.slice(0,-1):e}function Ds(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,Ms.createRequire)(e)}function xl(){try{let t=Ds()("@node-red/runtime/lib/settings");return typeof t.available=="function"&&t.available()===!0}catch{return!1}}function js(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=yl,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 Hs(e={}){if(xl())try{let n=Ds()("@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 Sl(e,t){let n=t.toLowerCase();return[e.type,e.module,e.category??"",e.description??""].join(`
|
|
4
|
+
`).toLowerCase().includes(n)}async function ir(e,t={}){let n=t.limit??pl,{byType:r}=await go(e),o=[...r.values()].sort((u,p)=>u.type.localeCompare(p.type)),s=t.q?.trim();s&&(o=o.filter(u=>Sl(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 Tl(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 Ns(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 Pl(e,t=2e3){let n=Ns(e,"script");return n=Ns(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 Rl(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:Fs(e,d,i)}return Fs(e,n.index,r)}catch{return null}}function Fs(e,t,n){let r=e.slice(t),o=r.indexOf("{");if(o<0)return{type:n};let s=xn(r.slice(o));if(!s)return{type:n};let i=Os(s,"category"),d=Os(s,"color"),l=$s(s,"inputs"),c=$s(s,"outputs"),u=Ll(s),p=[];if(u){for(let g of Object.values(u))if(g&&typeof g=="object"&&!Array.isArray(g)){let x=g.type;typeof x=="string"&&x.length>0&&p.push(x)}}let f,m=Tl(e,n);m&&(f=Pl(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 _l(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 xn(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 Os(e,t){let n=new RegExp(`\\b${t}\\s*:\\s*['"]([^'"]+)['"]`);return e.match(n)?.[1]}function $s(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 Ll(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=xn(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=xn(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=Il(m)}s[l]=u}return Object.keys(s).length>0?s:void 0}function Cl(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 El(e){return e==="_DEFAULT_"?"":e}function sr(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=Cl(t,o+1);s+=d.char,o=d.next;continue}if(i===r)break;s+=i,o+=1}return El(s)}if(t.startsWith("[")){let r=Us(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=sr(d);s.push(l);let c=co(d);i+=c>0?c:1}return s}if(t.startsWith("{")){let r=xn(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=sr(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]=sr(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=Us(t);return n+(o?.length??1)}if(t.startsWith("{")){let o=xn(t);return n+(o?.length??1)}let r=/^(?:-?\d+(?:\.\d+)?|true|false|null)/.exec(t);return r?n+r[0].length:n+1}function Us(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 Il(e){return sr(e)}function Nl(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 ct(e,t,n={}){if(!t||typeof t!="string")throw new y("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=Nl(o,t);if(!i){let l=yo(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: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=Rl(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=_l(c.help)))}}catch{}return d}async function Bs(e,t){let{allNames:n}=await go(e),r=new Set(n),o=new Set,s=a(l=>{let c=Je(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 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: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 qs(e,t){let n=Je(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 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:mo(n,r,5)}})}async function Fl(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 Ol(e,t){let n=Date.now();if(ke&&n-ke.fetchedAt<gl)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 ar(e,t,n={}){let r=t.trim();if(!r)throw new y("q query parameter is required","missing_query");let o=n.limit??fl,s=n.catalogueUrl??hl,i=n.catalogueFetcher??Fl,d=await Ol(i,s),l=Array.isArray(d.modules)?d.modules:[],c=await e.listNodeSets(),u=new Set(c.flatMap(g=>{let x=[g.module,g.id];return g.id.includes("/")&&x.push(g.id.split("/")[0]??g.module),x.map(S=>S.toLowerCase())})),p=r.toLowerCase(),f=[];for(let g of l){let x=g.id.toLowerCase(),S=(g.keywords??[]).map(ot=>String(ot).toLowerCase()),E=(g.description??"").toLowerCase(),I=$l(p,x,S,E);if(I<=0)continue;let N={module:g.id,installed:u.has(x)};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,x)=>x.score-g.score||(x.item.downloads??0)-(g.item.downloads??0)||g.item.module.localeCompare(x.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:ml}}function $l(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 Ms,ul,pl,fl,ml,hl,gl,yl,vl,uo,Is,po,ke,ut=U(()=>{"use strict";Ms=require("module");K();ul=new Set(["tab","subflow","group","junction"]),pl=100,fl=30,ml="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.",hl="https://catalogue.nodered.org/catalogue.json",gl=3600*1e3,yl=["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(Je,"requireNodeTypeName");a(wl,"editDistance");a(bl,"sharedPrefixLength");vl=new Set(["unknown","tab","subflow","group"]);a(kl,"nearestDistanceCutoff");a(mo,"nearestNodeTypes");a(Al,"trimTrailingSlash");a(Ds,"createPluginRequire");a(xl,"isRuntimeSettingsAvailable");a(js,"normalizeNodeSet");a(ho,"createStaticNodeTypesAdapter");uo=class{static{a(this,"HttpNodeTypesAdapter")}baseUrl;accessToken;fetchImpl;constructor(t){this.baseUrl=Al(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=js(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()}},Is={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(Is))}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=js(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",...Is}));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(Hs,"createNodeTypesAdapter");a(go,"flattenInstalledTypes");a(Sl,"matchesQuery");a(ir,"listNodeTypes");a(Tl,"extractHelpScriptBody");a(Ns,"removeHtmlBlocks");a(Pl,"stripHelpText");a(Rl,"parseRegisterTypeFromHtml");a(Fs,"parseRegisterTypeAt");a(_l,"splitHelpSections");a(xn,"extractBalancedObject");a(Os,"matchStringProp");a($s,"matchNumberProp");a(Ll,"parseDefaultsBlock");a(Cl,"unescapeJsStringEscape");a(El,"normalizeDefaultValue");a(sr,"parseJsLiteral");a(co,"measureJsLiteralLength");a(Us,"extractBalancedArray");a(Il,"parseLooseValue");a(Nl,"findSetForType");a(yo,"findCaseInsensitiveType");a(ct,"getNodeTypeDetail");a(Bs,"assertKnownNodeTypes");a(qs,"assertKnownNodeType");ke=null;a(Fl,"defaultCatalogueFetcher");a(Ol,"loadCatalogue");a(ar,"searchPalette");a($l,"paletteMatchScore")});function Ke(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 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 Ml(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 zs(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 ${Ml(c)}; node ids must be unique across all flows`,nodeId:l})}return o}function Ws(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 jl(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]);for(let r of n)if(!Dl.has(r)&&JSON.stringify(e[r])!==JSON.stringify(t[r]))return!0;return!1}function Hl(e,t){let n=Ws(t);if(!e)return new Set(n.keys());let r=Ws(e),o=new Set;for(let[s,i]of n){let d=r.get(s);(!d||jl(d,i))&&o.add(s)}return o}function Gs(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{Je(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&&Mt(c.wires,n,typeof c.id=="string"?c.id:void 0)}return n}function Mt(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 Ul(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 ct(t,n,{includeHelp:!1})).outputs}catch{return}}function Bl(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 ql(e){return Array.isArray(e)&&e.some(t=>typeof t=="string"&&t.length>0)}function Wl(e,t){return t<=0?!1:!("wires"in e)||e.wires===void 0?!0:Array.isArray(e.wires)?!e.wires.some(ql):!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 Ul(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(!Mt(d.wires,r,d.id))continue;let p=d.wires;if(l!==void 0){let f=Bl(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&&Wl(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 zl(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=Je(s.type,"node.type")}catch{continue}let d;try{d=await ct(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],x=zl(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:x});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:x})}}return r}async function Js(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?Hl(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 Dl,Ao=U(()=>{"use strict";K();ut();a(Ke,"throwValidationIssues");a(wo,"collectDuplicateNodeIdIssues");a(Ml,"describeLiveNode");a(zs,"collectCrossFlowDuplicateIssues");Dl=new Set(["x","y","w","h","g","id","z"]);a(Ws,"collectNodesMap");a(jl,"nodeContentChanged");a(Hl,"changedNodeIds");a(Gs,"collectFullFlowShapeIssues");a(Mt,"assertValidWiresShape");a(Ul,"resolveNodeOutputCount");a(Bl,"wireCountIssue");a(ql,"portHasTargets");a(Wl,"nodeIsUnwired");a(bo,"collectWireValidation");a(vo,"collectZMismatchIssues");a(zl,"configCandidates");a(ko,"collectConfigWarnings");a(Js,"validateFlowTab")});function Ve(e,t="node"){return typeof e=="string"&&e.trim()?e.trim():t}function dr(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 Vs(e,t){e[Ks]=t}function Kl(e){if(!e)return;let t=e[Ks];return t instanceof Map?t:void 0}function Vl(e){return typeof e!="string"||!e.trim()?new Set:new Set(e.split(",").map(t=>t.trim()).filter(Boolean))}function Xl(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 re(e){let t=Vl(e.include);return Xl(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=Kl(e);return r&&(n.liveFlowLabels=r),n}function Dt(e){return typeof e.name=="string"&&e.name.trim()?e.name.trim():typeof e.label=="string"&&e.label.trim()?e.label.trim():""}function Sn(e){return`"${e.replace(/"/g,"'")}"`}function jt(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 Yl(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 Ql(e,t){let n=Dt(e),r=Dt(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&&Yl(d,i)&&o.push({before:d,after:i})}return{added:n,removed:r,modified:o}}function lr(e){return e===1?"node":"nodes"}function Zl(e){return e.length===1&&e[0]?`adds 1 ${Ve(e[0].type)}`:`adds ${e.length} ${lr(e.length)}`}function ec(e){return`deletes ${e} ${lr(e)}`}function tc(e){return`edits ${e} ${lr(e)}`}function nc(e){let t=[];e.removed>0&&t.push(ec(e.removed));let n=e.added??[],r=e.addedCount??n.length;r>0&&t.push(n.length===r?Zl(n):`adds ${r} ${lr(r)}`),(e.modified??0)>0&&t.push(tc(e.modified??0));let o=t.length?t.join(", "):"replaces nodes",s=e.flowLabel?.trim();return s?`Replace ${s}: ${o}`:`Replace flow: ${o}`}function rc(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=Ve(u.type),f=Dt(u);return f?`Add ${p} node ${Sn(f)}`:`Add ${p} node`}if(n[0]){let u=n[0],p=Ve(u.type),f=Dt(u);return f?`Remove ${p} node ${Sn(f)}`:`Remove ${p} node`}let s=r[0];if(!s)return;let i=Ve(s.after.type,Ve(s.before.type)),d=Dt(s.before),l=Dt(s.after);if(Ql(s.before,s.after)&&d&&l)return`Rename ${i} node ${Sn(d)} \u2192 ${Sn(l)}`;let c=l||d;return c?`Edit ${i} ${Sn(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 cr(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 Xs(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(jt(e??{}),jt(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 nc({...o?{flowLabel:o}:{},removed:n.removed.length,added:n.added,modified:n.modified.length});if(r>Jl)return o?`${r} changes in ${o}`:`${r} changes`;let s=rc(n)??(o?`Update ${o}`:"Update flow");return Xs(s,o)}function oc(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 Po(e,t){return`${e} \xB7 ${ic(t)}`}function sc(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 ic(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=sc(-n.getTimezoneOffset());return`${r} ${s} ${i}:${d} ${l}`}function ur(e){if(typeof e!="string")return;let t=e.replace(/\0/g,"").replace(/[\u0001-\u001F\u007F]/g," ").replace(/\s+/g," ").trim().slice(0,Gl).trim();return t.length>0?t:void 0}function ac(e,t){let n=ae(t);return`${n==="/"?"/ai-access":`${n}/ai-access`}#proposal=${encodeURIComponent(e)}`}function Ro(e,t={}){let n=cr(e,t.liveFlowLabels),r=ur(e.title),o=To(e.baseFlow,n&&!So(e.payload.flow,e.baseFlow)?{...e.payload.flow,label:n}:e.payload.flow),s=xo(jt(e.baseFlow??{}),jt(e.payload.flow)),i=oc(r,s.removed.length)?o:r??o;return Xs(i,n)}function dc(e){return typeof e.storedTitle=="string"&&e.storedTitle?e.storedTitle:ur(e.title)}function oe(e,t={}){let n=Ro(e,t),r=cr(e,t.liveFlowLabels),o=dc(e)??n,s=ac(e.id,t.httpAdminRoot),i=t.req?vn(t.req,s):s;return{...e,title:n,storedTitle:o,flowLabel:r,displayName:Po(n,e.createdAt),reviewUrl:i,reviewPath:s}}function lc(e){let t=xo(jt(e.baseFlow??{}),jt(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 cc(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=oe(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:cc(n.createdBy),changeSummary:lc(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 Ys(e,t={}){return e.map(n=>ce(n,t))}var Gl,Jl,Ks,Ae=U(()=>{"use strict";ie();_e();Gl=80,Jl=1;a(Ve,"nodeTypeText");Ks=Symbol.for("ai-access.liveFlowLabels");a(dr,"liveFlowLabelsFromFlows");a(Vs,"setRequestFlowLabels");a(Kl,"getRequestFlowLabels");a(Vl,"parseIncludeQuery");a(Xl,"isTruthyFlag");a(re,"resolveIncludeFromQuery");a(q,"presentOptionsFor");a(Dt,"nodeLabel");a(Sn,"quoteName");a(jt,"collectNodes");a(Yl,"nodeContentChanged");a(Ql,"isRenameOnly");a(xo,"classifyFlowChanges");a(lr,"pluralNodes");a(Zl,"describeAddedClause");a(ec,"describeRemovedClause");a(tc,"describeModifiedClause");a(nc,"buildReplaceFlowTitle");a(rc,"describeOneChange");a(So,"flowLabelHint");a(cr,"resolveFlowLabel");a(Xs,"ensureTitleIncludesFlowName");a(To,"autoGenerateProposalTitle");a(oc,"shouldRefreshDisplayTitle");a(Po,"buildProposalDisplayName");a(sc,"formatUtcOffset");a(ic,"formatProposalSubmittedAt");a(ur,"sanitizeProposalTitle");a(ac,"buildProposalReviewPath");a(Ro,"resolveProposalTitle");a(dc,"storedTitleOf");a(oe,"presentProposal");a(lc,"buildChangeSummary");a(cc,"presentCreatedByForApi");a(ce,"presentProposalForApi");a(Ys,"presentProposalsForApi")});function Qs(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 pc(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]),r=[];for(let o of n)uc.has(o)||JSON.stringify(e[o])!==JSON.stringify(t[o])&&r.push(o);return r.sort()}function Zs(e){let t=Qs(e.baseFlow??{}),n=Qs(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=pc(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 fc(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",mc),r+=1}catch{}return r}function ei(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 Ie(e,t,n){let r=Zs(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=Ro(s),d=n?lt(s.createdBy)===n:lt(s.createdBy)===lt(t.createdBy),l=fc(r,Zs(s)),c=s.expectedRev===t.expectedRev,u=l.nodeIds.length>0;if(!c&&!u)continue;let p={id:s.id,title:i,displayName:Po(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 Ne(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 uc,mc,Xe=U(()=>{"use strict";Ge();Ae();uc=new Set(["x","y","w","h","g","id","z"]);a(Qs,"collectNodes");a(pc,"changedFieldKeys");a(Zs,"computeProposalTouches");a(fc,"overlapBetween");mc="Proposal expired: the live flow revision moved past expectedRev. Re-read the flow and resubmit.";a(V,"expireStalePendingProposals");a(ei,"supersedePendingOnFlow");a(Ie,"findConflictingProposals");a(Ne,"conflictWarningFields");a(Q,"autoClosedFields")});var di={};ds(di,{applyFlowNodeOps:()=>pt,applyReplaceTextToValue:()=>ai,buildPatchedFlowTab:()=>Tn,collectSetProps:()=>_o,decodeBase64UrlJson:()=>Pn,findTextOccurrences:()=>ii,interpretPatch:()=>Rn});function si(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function hc(e){if(typeof e!="object"||e===null||Array.isArray(e))throw new y(`add op needs node as an object (got ${si(e)})`,"invalid_node")}async function Tn(e,t,n){return pt(e,t,n.map(r=>({op:"set",nodeId:r.nodeId,set:r.set})))}function ii(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 ai(e,t,n,r,o){if(oi.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=ii(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 gc(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 ${si(r)})`});continue}let o=r;(!o.id||typeof o.id!="string")&&t.push({code:"missing_node_id",error:"add op needs node.id"});try{Je(o.type,"node.type")}catch(s){s instanceof y&&t.push({code:s.code,error:s.message,nodeId:o.id})}"wires"in o&&Mt(o.wires,t,typeof o.id=="string"?o.id:void 0)}else n.op==="set"&&typeof n.set=="object"&&"wires"in n.set&&Mt(n.set.wires,t,n.nodeId);return t}function yc(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 pt(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=gc(n);o.push(...wo([n.flatMap(h=>h.op==="add"?[h.node]:[])],"the batch (add ops)")),Ke(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 qs(r.nodeTypes,h.node.type),ni(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(...vo(c,t));let p=yc(n),f=await bo(c,r.nodeTypes,{softWarningNodeIds:p});u.push(...f.errors),Ke(u);for(let h of n)ni(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 ti(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 ni(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(Je(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=ti(e,n.nodeId,t);e.splice(i,1);return}let r=ti(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=ai(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(oi.has(i))throw new y(`Cannot change protected node property '${i}' via GET propose`,"protected_property");if(i==="wires"){let l=[];Mt(d,l,n.nodeId),Ke(l)}s[i]=d}e[r]=s}function ri(e){if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean"||typeof e=="bigint")return String(e)}function _o(e){let t={},n=e.set;if(n&&typeof n=="object"&&!Array.isArray(n))for(let[r,o]of Object.entries(n)){let s=ri(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=ri(o);i!==void 0&&(t[s]=i)}}return t}function Pn(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 wc(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 bc(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 vc(e){let t=e.node;if(t!==void 0)return hc(t),{op:"add",node:t};let n={...e};return delete n.op,{op:"add",node:n}}function kc(e){let t=Lo(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=bc(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 Ac(e){let t=Lo(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 xc(e){let t=typeof e.op=="string"?e.op.toLowerCase():"set";if(t==="add")return vc(e);if(t==="remove"){let n=Lo(e);if(!n)throw new y("remove op needs id","missing_node_id");return{op:"remove",nodeId:n}}if(t==="replace_text")return kc(e);if(t!=="set")throw new y("nodes[].op must be 'add', 'remove', 'set', or 'replace_text'","invalid_patch");return Ac(e)}function Rn(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=wc(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(xc(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 oi,pr=U(()=>{"use strict";K();Ao();ut();oi=new Set(["id","type","z"]);a(si,"describeNonObjectNode");a(hc,"assertAddNodeObject");a(Tn,"buildPatchedFlowTab");a(ii,"findTextOccurrences");a(ai,"applyReplaceTextToValue");a(gc,"collectOpShapeIssues");a(yc,"touchedNodeIdsFromOps");a(pt,"applyFlowNodeOps");a(ti,"requireNodeIndex");a(ni,"applyOneNodeOp");a(ri,"stringifyQueryValue");a(_o,"collectSetProps");a(Pn,"decodeBase64UrlJson");a(wc,"resolveReplaceFlowId");a(Lo,"resolveNodeId");a(bc,"parseOccurrence");a(vc,"parseAddOp");a(kc,"parseReplaceTextOp");a(Ac,"parseSetOp");a(xc,"parseNodeOp");a(Rn,"interpretPatch")});function Ht(e,t){return lt(e.createdBy)===t.id}function Co(e,t){return!t||t.length===0||t.includes(e)}function Sc(e){return typeof e=="string"&&ci.has(e)}function Bt(e,t){let n=Tc[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 fr(e,t){if(Array.isArray(t))throw Bt(e,t);return t}function Pc(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 Rc(e){if(!Eo(e)){if(e==="true"||e==="1"||e===!0)return!0;if(e==="false"||e==="0"||e===!1)return!1;throw Bt("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(!Sc(o))throw Bt("status",e);n.includes(o)||n.push(o)}}return n.length>0?n:void 0}function li(e,t,n,r){if(Eo(t))return;let o=Pc(t);if(o===null||o<n||o>r)throw Bt(e,t);return o}function mr(e){let t={},n=Rc(fr("mine",e.mine));n!==void 0&&(t.mine=n);let r=Io(e.status);r&&(t.status=r);let o=fr("flowId",e.flowId??e.flow_id??e.flow);typeof o=="string"&&o.length>0&&(t.flowId=o);let s=li("limit",fr("limit",e.limit),1,Ut);s!==void 0&&(t.limit=s);let i=li("offset",fr("offset",e.offset),0,Number.MAX_SAFE_INTEGER);return i!==void 0&&(t.offset=i),t}function ft(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 _c(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),Ut);return{items:e.slice(r,r+o),total:n,limit:o,offset:r}}function qt(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||!Ht(c,t)).filter(c=>Co(c.status,r.status)).map(c=>({...oe(c,n),mine:Ht(c,t)})),l=_c(d,r);return{proposals:l.items,total:l.total,limit:l.limit,offset:l.offset}}function De(e,t,n,r={}){if(!n)throw new y("Missing proposal id","missing_proposal_id");let o=e.get(n);return{...oe(o,r),mine:Ht(o,t)}}function Lc(e){let t={kind:"access-link",linkId:e.id};return typeof e.label=="string"&&e.label&&(t.label=e.label),t}function Wt(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 ne,f}if(!Ht(i,n))throw new Ot(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=Lc(n),c=e.markWithdrawn(r,l,d),u=oe(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 Cc(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"?{}:Ne(Ie(e,o,n))}function ue(e,t,n,r={},o={}){let s="mine"in n?n:{...oe(n,r),mine:Ht(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={...oe(u,r),mine:Ht(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,Cc(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:$t(t,n.flow)}}function Ec(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 ui(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 Ic(e,t,n,r,o){let{applyFlowNodeOps:s,interpretPatch:i}=await Promise.resolve().then(()=>(pr(),di));ui(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 Nc(e,t,n,r,o){let s=n.flowId,i=n.flow,d=!i||typeof i!="object"||Array.isArray(i);if(d&&ui(n,r),d&&(Array.isArray(n.nodes)||Array.isArray(r))){let l=await Ic(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 Fc(e,t){try{return{safeExpectedRev:Ee(e.slice(0,200),"expectedRev"),safeFlowId:Ee(t.slice(0,200),"flowId")}}catch{throw new y("expectedRev and payload.flowId must be safe path segments","invalid_path_segment")}}async function Ye(e,t,n,r,o,s={},i){let d=r.type,l=Ec(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 Nc(o,l,c,r.nodes,i),{safeExpectedRev:f,safeFlowId:m}=Fc(l,p.flowId),h=p.flow,g=Buffer.byteLength(JSON.stringify(c),"utf8");if(g>j)throw new X("proposal payload",g,j);Ke(Gs(h,m));let S=Ft(h),E=await o.getFlows();if(E.rev!==f)throw new J(f,E.rev,"submit_proposal");Ke(zs(S,m,E.flows));let I=await o.getFlow(m);if(I.rev!==f)throw new J(f,I.rev,"submit_proposal");let N=Ft(I.flow);i&&await Bs(i,S);let{errors:ot,warnings:me}=await Js(S,{flowId:m,baseFlow:N,...i?{nodeTypes:i}:{}});Ke(ot);let A={kind:"access-link",linkId:n.id};n.label!==void 0&&(A.label=n.label);let v=n.oauthClientId&&n.oauthUser?{kind:"oauth",clientId:n.oauthClientId,user:n.oauthUser}:A,w=ur(r.title)??To(N,S),b=e.create({type:"replace_flow",expectedRev:f,payload:{flowId:m,flow:S},baseFlow:N,title:w,createdBy:v,...me.length>0?{warnings:me}:{}}),k=oe(b,s),L=k.flowLabel||void 0;return t.append("proposal.created",A,{proposalId:b.id,flowId:m,expectedRev:f,title:k.title,displayName:k.displayName,...L?{flowLabel:L}:{},...me.length>0?{warningCount:me.length}:{}}),k}function O(e,t="rest"){return _n(e,t)}var Ut,ci,Tc,y,K=U(()=>{"use strict";ve();or();Ge();er();G();Fe();Ao();ut();Xe();Ae();a(Ht,"isMineProposal");Ut=200,ci=new Set(["pending","approved","rejected","failed","withdrawn","expired","superseded"]);a(Co,"matchesStatusFilter");a(Sc,"isProposalStatus");Tc={status:[...ci].join(", "),limit:`integer 1\u2013${Ut}`,offset:"integer \u2265 0",mine:"true, false, 1, 0",flowId:"one flow tab id"};a(Bt,"invalidFilterError");a(fr,"singleQueryValue");a(Pc,"parseIntegerFilter");a(Eo,"isUnset");a(Rc,"parseMineFilter");a(Io,"parseStatusFilter");a(li,"parseRangeFilter");a(mr,"parseListProposalsQuery");a(ft,"listPageFields");a(_c,"applyListPagination");a(qt,"listAccessibleProposals");a(De,"getAccessibleProposal");a(Lc,"accessLinkActor");a(Wt,"withdrawMyProposal");a(Cc,"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(Ec,"resolveExpectedRev");a(ui,"assertNodesArrayOrAbsent");a(Ic,"materializePayloadFromNodes");a(Nc,"resolveReplaceFlowPayload");a(Fc,"assertSafeRevAndFlowId");a(Ye,"submitReplaceFlowProposal");a(O,"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 mt,Ln=U(()=>{"use strict";a(No,"maxTtlSecondsForKind");a(Fo,"ttlCapErrorMessage");mt="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 P(e,t,n,r,o=Jt,s="error"){return{code:e,status:t,meaning:n,hint:r,channels:o,kind:s}}function Cn(e){return $c.get(e)?.hint}function pi(e){return e.replace(/\\/g,"\\\\").replace(/\|/g,"\\|")}function fi(){let e=a(s=>`| \`${s.code}\` | ${s.status} | ${pi(s.meaning)} | ${pi(s.hint)} | ${s.channels.join(", ")} |`,"row"),t=["| Code | HTTP | Meaning | Next step | Channels |","| --- | --- | --- | --- | --- |"],n=hr.filter(s=>s.kind==="error"),r=hr.filter(s=>s.kind==="warning"),o=hr.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 Jt,zt,Oo,Oc,Gt,hr,$c,gr=U(()=>{"use strict";Ln();Jt=["GET","REST","MCP"],zt=["GET"],Oo=["REST","MCP"],Oc=["GET","REST"],Gt=["admin"];a(P,"e");hr=[P("access_link_missing",401,"No Bearer / path token","Ask the user for a new access link of the right kind"),P("bearer_scheme_required",401,"Auth used Basic (or non-Bearer)","Send `Authorization: Bearer <token>`",Oo),P("token_expired",401,"Known token past `expiresAt` (body includes `expiredAt` and `accessLinksUrl`)","Ask the user to create a new link on the `/ai-access` dashboard \u2192 Access tokens (`accessLinksUrl`)"),P("access_link_invalid",401,"Unknown, revoked, or cleared after restart","Ask the user for a new access link"),P("wrong_token_kind",403,"Token kind does not match this surface","Mint the matching kind on `/ai-access` \u2192 Access tokens; show `tellUser` to the human"),P("ai_features_disabled",403,"Plugin AI features off","Owner enables AI Access / `adminAuth`"),P("auth_required",401,"Admin session required","Sign in to the Node-RED editor",Gt),P("insufficient_permissions",403,"Admin lacks required scope","Need `*` for link mint / approve",Gt),P("invalid_link_kind",400,"Unknown `kind` on create","Use `get_link`, `rest`, or `mcp`",Gt),P("invalid_ttl",400,"`ttlSeconds` is zero, negative, not a number, or over the cap for the kind",mt,Gt),P("access_link_limit",409,"Too many active links","Revoke an existing link first",Gt),P("access_link_not_found",404,"Unknown link id on revoke","List links and use an existing id",Gt),P("link_create_not_supported",403,"Attempted mint under a GET link","Admin mints via `POST /mcp/api/v1/links` only",zt),P("rate_limited",429,"Per-IP or per-token limit","Wait `retryAfter` / `Retry-After`"),P("payload_too_large",413,"Body / proposal over ~1.5MB","Shrink content"),P("invalid_json",400,"Malformed JSON body","Fix Content-Type / body",Oo),P("unsupported_media_type",415,"POST body is not `application/json`","Send `Content-Type: application/json`",Oo),P("url_too_long",414,"GET URL over soft cap (~8KB)","Use chunked drafts or REST/MCP",zt),P("not_found",404,"Unknown path or resource","Re-read docs / discovery"),P("missing_query",400,"Required search `q` missing","Pass `?q=`",Oc),P("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"),P("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"]),P("invalid_diff_format",400,"Unknown `format` on proposal diff","Use `format=summary` (default) or `format=full`"),P("confirm_required",400,"GET withdraw without `confirm=1`","Repeat the request with `confirm=1`",zt),P("missing_expected_rev",400,"Proposal submitted without `expectedRev` / `rev`","Read the flow and pass its `rev`"),P("version_mismatch",409,"Stale `rev` / `expectedRev`","Re-read flows and resubmit"),P("proposal_not_found",404,"Unknown proposal id","List proposals and use an existing id"),P("proposal_not_pending",409,"Action needs a pending proposal","Only pending can withdraw / decide"),P("proposal_not_owned",403,"Withdraw of another token\u2019s proposal","Only the creating token can withdraw"),P("validation_failed",400,"Several validation errors at once (message `N validation errors`)","Fix every entry in errors[] and resubmit."),P("duplicate_node_id",400,"The same node id appears more than once in a full tab or batch","Give every node a unique id"),P("missing_node_id",400,"Node / op without an id","Include a non-empty string id"),P("missing_node_type",400,"Node without a `type`","Set `type` to an installed palette type name"),P("invalid_node_type",400,"`type` is not a string","Use the palette type name exactly"),P("invalid_nodes",400,"`flow.nodes` / `flow.configs` not an array","Send arrays (use `[]` to clear)"),P("invalid_node",400,"Node entry is not an object","Send `{ id, type, \u2026 }` objects"),P("invalid_wires",400,"Bad wires shape, missing target, or more ports than outputs","Wire only to existing node ids / ports"),P("z_mismatch",400,"Node `z` differs from the flow id","Set `z` to the tab id or omit it"),P("node_already_exists",400,"`add` op for an id already in the flow","Use `set` or a new id"),P("protected_property",400,"Tried to change id / type / z","Omit id, type, and z from edits"),P("node_not_found",404,"Unknown node (single error only; several errors \u2192 `validation_failed`)","Re-read the flow"),P("field_not_found",404,"Unknown field on a node","Re-read the node"),P("text_not_found",400,"`replace_text` find had no match","Re-read field; find is exact"),P("ambiguous_match",409,"`replace_text` matched more than once","Pass `occurrence` (1-based) or unique find"),P("unknown_node_type",400,"Proposal uses a type that is not installed","List node-types / palette search; ask user to install"),P("node_type_not_found",404,"Unknown type on node-types detail","List types or search palette"),P("chunk_conflict",409,"Same draft seq re-sent with different data","Re-send identical data or start a new draft",zt),P("missing_chunks",400,"Draft submit with gaps in seq","Upload every seq from 0 through max",zt),P("draft_hash_mismatch",400,"Draft sha256 does not match assembled bytes","Send the hex sha256 of the assembled UTF-8",zt),P("missing_config_node",201,"Warning: node has no (or an unresolved) config node yet","Configure before deploy if needed; see `candidates`",Jt,"warning"),P("unwired_node",201,"Warning: node has outputs but no wires","Wire it, or ignore while scaffolding",Jt,"warning"),P("incomplete_wires",201,"Warning: fewer wire ports than outputs","Add ports if outputs should connect",Jt,"warning"),P("expired",200,"Status: pending proposal auto-closed because the live flow rev moved past `expectedRev`","Re-read the flow and submit a new proposal",Jt,"status"),P("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",Jt,"status"),P("upstream_error",502,"Node-RED runtime/flows error","Retry; check Node-RED logs"),P("internal_error",500,"Unexpected server error","Retry; check Node-RED logs")],$c=new Map(hr.map(e=>[e.code,e]));a(Cn,"registryHint");a(pi,"cell");a(fi,"renderGetLinkErrorTable")});function se(e,t,n={}){return $o(e,t,n)}function Mc(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 Dc(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 Qe(e,t,n,r){return e==="mcp"?t:e==="rest"?n:r}function $o(e,t,n={}){switch(e){case"version_mismatch":return Qe(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 Qe(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 Qe(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 Qe(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 Mc(t,n.cause);case"node_type_not_found":case"unknown_node_type":return Dc(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 Qe(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 Qe(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 Qe(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 Qe(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 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 z?o={statusCode:e.statusCode,body:{error:e.message,code:e.code,flowId:e.flowId}}:e instanceof ne||e instanceof Ot||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=Oe[s];i&&(o.body.error=i)}if(typeof o.body.hint!="string"||!o.body.hint){let i=$o(s,t,{cause:o.body.cause})??Cn(s);i&&(o.body.hint=i)}return o}function mi(e,t,n={}){let o={error:n.error??Oe[t]??"Request failed",code:t,...n.details??{}},s=$o(t,e,{cause:o.cause})??Cn(t);return s&&(o.hint=s),{statusCode:n.statusCode??400,body:o}}var Oe,Fe=U(()=>{"use strict";ve();Ge();G();K();gr();Oe={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(se,"channelErrorHint");a(Mc,"invalidWiresHint");a(Dc,"nodeTypeHint");a(Qe,"pickChannel");a($o,"hintFor");a(_n,"mapErrorForChannel");a(mi,"buildChannelError")});var vm={};ds(vm,{ACCESS_LINK_INVALID_MESSAGE:()=>st,ACCESS_LINK_MISSING_MESSAGE:()=>le,AccessLinkStore:()=>At,EDITOR_SIDEBAR_ASSET_VERSION:()=>Xt,MAX_PROPOSAL_JSON_BYTES:()=>j,SettingsStore:()=>Lt,acceptedMediaTypes:()=>wd,areAiFeaturesEnabled:()=>ge,bindSettingsStore:()=>Jn,createPluginServices:()=>Vr,default:()=>bm,getStorageDir:()=>Hr,isAdminAuthEnabled:()=>W,isMcpStreamRequest:()=>bd,packageVersion:()=>Vt,registerRoutes:()=>vd,renderEditorSidebarJs:()=>yr,renderEditorSidebarLoaderHtml:()=>wi,requireAccessLink:()=>Nt,resolveAiAccessSettings:()=>he});module.exports=Pd(vm);var yd=require("fs");var Un=require("fs"),un=require("path");G();function Bn(e){let t=(0,un.join)(e,".ai-access");return{root:t,proposals:(0,un.join)(t,"proposals"),logs:(0,un.join)(t,"logs"),settings:(0,un.join)(t,"config.json")}}a(Bn,"resolveAiAccessPaths");function Qr(e){(0,Un.mkdirSync)(e,{recursive:!0,mode:Yr});try{(0,Un.chmodSync)(e,Yr)}catch{}}a(Qr,"mkdirSecure");function fs(e){Qr(e.root),Qr(e.proposals),Qr(e.logs)}a(fs,"ensureAiAccessDirs");it();_e();function Yn(e){let t=ae(e);return`auth-tokens${(t==="/"?"":t).replace(/\//g,"-")}`}a(Yn,"editorAuthTokenKey");_e();Fe();function jc(e){if(!e)return!1;let t=e.split(";")[0]?.trim().toLowerCase()??"";return t==="application/json"||t.endsWith("+json")}a(jc,"isJsonContentType");function Hc(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(Hc,"hasRequestBody");function je(e="rest"){return(t,n,r)=>{let o=t.headers["content-type"];if(jc(o)){r();return}if(!o&&!Hc(t)){r();return}let s=mi(e,"unsupported_media_type",{statusCode:415});n.status(s.statusCode).json(s.body)}}a(je,"requireJsonContentType");var Uc="text/plain; charset=utf-8",Bc="text/html; charset=utf-8",qc="default-src 'none'; base-uri 'self'; form-action 'none'; frame-ancestors 'none'";function Wc(e){let t=e.query?.format;return typeof t=="string"?t.toLowerCase():void 0}a(Wc,"formatParam");function zc(e){let t=Wc(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(zc,"chooseGetLinkRepresentation");function hi(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}a(hi,"escapeHtml");var Gc=/(?<![A-Za-z0-9_.~\-/])\.\/[A-Za-z0-9._~\-/]+(?:\?[A-Za-z0-9._~\-=&%]+)?/g;function Jc(e){return e.replace(Gc,(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(Jc,"linkifyEscaped");function Kc(e,t){return`<!doctype html>
|
|
8
|
+
<html><head><meta charset="utf-8">${t?`<base href="${hi(t)}">`:""}<title>Node-RED AI Access</title></head><body><pre>${Jc(hi(e))}</pre></body></html>
|
|
9
|
+
`}a(Kc,"renderGetLinkHtml");function Vc(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(Vc,"getLinkBaseHref");function Xc(e){try{return JSON.stringify(JSON.parse(e),null,2)}catch{return e}}a(Xc,"prettyJson");function gi(){return(e,t,n)=>{let r=zc(e),o=Vc(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",Uc),s(u)):(t.setHeader("Content-Type",Bc),t.setHeader("Content-Security-Policy",qc),s(Kc(l?Xc(u):u,o)))}),n()}}a(gi,"getLinkContentNegotiation");qn();_e();function Yc(e){let t=de(e.settings.httpAdminRoot),n=qe(e.settings.httpAdminRoot),r="0.2.0-beta.3",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 /ai-access dashboard (Access tokens tab) (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(Yc,"buildDiscoveryIndex");function Mo(e){return(t,n)=>{n.status(200).json(Yc(e))}}a(Mo,"discoveryIndexHandler");function yi(){return(e,t)=>{t.status(404).json({error:"Not found",code:"not_found"})}}a(yi,"mcpApiNotFoundHandler");var Kt;function Vt(){return Kt||(Kt="0.2.0-beta.3",Kt)}a(Vt,"packageVersion");var Xt=Vt();function Qc(){return`#red-ui-sidebar-ai-access {
|
|
10
|
+
position: relative;
|
|
10
11
|
height: 100%;
|
|
11
12
|
display: flex;
|
|
12
13
|
flex-direction: column;
|
|
@@ -38,21 +39,13 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
38
39
|
margin-bottom: 10px;
|
|
39
40
|
line-height: 1.4;
|
|
40
41
|
}
|
|
41
|
-
#red-ui-sidebar-ai-access .ai-access-status-block {
|
|
42
|
-
margin: 0 0 12px;
|
|
43
|
-
padding: 8px 10px;
|
|
44
|
-
background: var(--red-ui-secondary-background, #f3f3f3);
|
|
45
|
-
border-radius: 4px;
|
|
46
|
-
}
|
|
47
|
-
#red-ui-sidebar-ai-access .ai-access-status-block p {
|
|
48
|
-
margin: 0 0 6px;
|
|
49
|
-
}
|
|
50
42
|
#red-ui-sidebar-ai-access .ai-access-oauth-warning {
|
|
51
43
|
color: #9b2c2c;
|
|
52
44
|
background: #fdf0f0;
|
|
53
45
|
border: 1px solid #f0c4c4;
|
|
54
46
|
border-radius: 4px;
|
|
55
47
|
padding: 8px 10px;
|
|
48
|
+
margin: 8px 0 4px;
|
|
56
49
|
line-height: 1.4;
|
|
57
50
|
}
|
|
58
51
|
#red-ui-sidebar-ai-access .ai-access-oauth-warning p {
|
|
@@ -78,94 +71,10 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
78
71
|
word-break: break-all;
|
|
79
72
|
white-space: normal;
|
|
80
73
|
}
|
|
81
|
-
#red-ui-sidebar-ai-access .ai-access-url-warning {
|
|
82
|
-
color: #8a5a00;
|
|
83
|
-
}
|
|
84
74
|
#red-ui-sidebar-ai-access .ai-access-oauth-warning a {
|
|
85
75
|
text-decoration: underline;
|
|
86
76
|
cursor: pointer;
|
|
87
77
|
}
|
|
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;
|
|
157
|
-
}
|
|
158
|
-
#red-ui-sidebar-ai-access .ai-access-oauth-client {
|
|
159
|
-
display: flex;
|
|
160
|
-
align-items: center;
|
|
161
|
-
justify-content: space-between;
|
|
162
|
-
gap: 8px;
|
|
163
|
-
padding: 5px 0;
|
|
164
|
-
border-bottom: 1px solid var(--red-ui-secondary-border-color, #ddd);
|
|
165
|
-
}
|
|
166
|
-
#red-ui-sidebar-ai-access .ai-access-oauth-client span {
|
|
167
|
-
overflow-wrap: anywhere;
|
|
168
|
-
}
|
|
169
78
|
#red-ui-sidebar-ai-access .ai-access-dialog {
|
|
170
79
|
position: absolute;
|
|
171
80
|
inset: 0;
|
|
@@ -178,68 +87,37 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
178
87
|
gap: 8px;
|
|
179
88
|
margin-top: 16px;
|
|
180
89
|
}
|
|
181
|
-
#red-ui-sidebar-ai-access .ai-access-ttl-presets {
|
|
182
|
-
display: flex;
|
|
183
|
-
flex-wrap: wrap;
|
|
184
|
-
gap: 6px;
|
|
185
|
-
margin-bottom: 4px;
|
|
186
|
-
}
|
|
187
|
-
#red-ui-sidebar-ai-access .ai-access-ttl-presets button[aria-pressed='true'] {
|
|
188
|
-
font-weight: 700;
|
|
189
|
-
border-color: var(--red-ui-form-input-focus-color, #999);
|
|
190
|
-
}
|
|
191
90
|
#red-ui-sidebar-ai-access .red-ui-settings-row {
|
|
192
91
|
display: flex;
|
|
193
92
|
gap: 10px;
|
|
194
93
|
align-items: flex-start;
|
|
195
94
|
padding: 5px 0 2px;
|
|
196
95
|
}
|
|
197
|
-
#red-ui-sidebar-ai-access .
|
|
198
|
-
display:
|
|
199
|
-
gap: 6px;
|
|
200
|
-
}
|
|
201
|
-
#red-ui-sidebar-ai-access .ai-access-public-url-controls input {
|
|
202
|
-
flex: 1;
|
|
96
|
+
#red-ui-sidebar-ai-access .red-ui-settings-row label {
|
|
97
|
+
display: inline-block;
|
|
203
98
|
min-width: 0;
|
|
99
|
+
flex: 1 1 auto;
|
|
204
100
|
}
|
|
205
|
-
#red-ui-sidebar-ai-access .
|
|
206
|
-
|
|
101
|
+
#red-ui-sidebar-ai-access .red-ui-settings-row label p {
|
|
102
|
+
margin: 0 0 2px;
|
|
103
|
+
font-weight: 600;
|
|
207
104
|
}
|
|
208
105
|
#red-ui-sidebar-ai-access .ai-access-actions {
|
|
209
|
-
margin-top:
|
|
106
|
+
margin-top: 14px;
|
|
210
107
|
display: flex;
|
|
211
|
-
flex-
|
|
212
|
-
align-items:
|
|
213
|
-
gap:
|
|
108
|
+
flex-direction: column;
|
|
109
|
+
align-items: stretch;
|
|
110
|
+
gap: 6px;
|
|
214
111
|
}
|
|
215
112
|
#red-ui-sidebar-ai-access .ai-access-actions .red-ui-button {
|
|
216
113
|
margin: 0;
|
|
217
|
-
flex: 1 1 auto;
|
|
218
114
|
white-space: nowrap;
|
|
219
115
|
}
|
|
220
|
-
#red-ui-sidebar-ai-access .
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
#red-ui-sidebar-ai-access .red-ui-settings-row label {
|
|
226
|
-
display: inline-block;
|
|
227
|
-
min-width: 0;
|
|
228
|
-
flex: 1 1 auto;
|
|
229
|
-
}
|
|
230
|
-
#red-ui-sidebar-ai-access .red-ui-settings-row.stacked label {
|
|
231
|
-
flex: none;
|
|
232
|
-
width: 100%;
|
|
233
|
-
}
|
|
234
|
-
#red-ui-sidebar-ai-access .red-ui-settings-row.stacked input,
|
|
235
|
-
#red-ui-sidebar-ai-access .red-ui-settings-row.stacked select {
|
|
236
|
-
width: 100%;
|
|
237
|
-
max-width: 100%;
|
|
238
|
-
box-sizing: border-box;
|
|
239
|
-
}
|
|
240
|
-
#red-ui-sidebar-ai-access .red-ui-settings-row label p {
|
|
241
|
-
margin: 0 0 2px;
|
|
242
|
-
font-weight: 600;
|
|
116
|
+
#red-ui-sidebar-ai-access .ai-access-muted {
|
|
117
|
+
color: var(--red-ui-secondary-text-color, #888);
|
|
118
|
+
font-size: 0.85em;
|
|
119
|
+
line-height: 1.35;
|
|
120
|
+
margin: 0;
|
|
243
121
|
}
|
|
244
122
|
/* Compact toast in the header \u2014 must not cover the intro description. */
|
|
245
123
|
#red-ui-sidebar-ai-access #ai-access-flash {
|
|
@@ -279,19 +157,20 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
279
157
|
color: #9b2c2c;
|
|
280
158
|
background: #fdecec;
|
|
281
159
|
}
|
|
282
|
-
/* After fade: strip chrome so no empty green/red outline remains. */
|
|
283
160
|
#red-ui-sidebar-ai-access #ai-access-flash:not(.is-visible) {
|
|
284
161
|
padding: 0;
|
|
285
162
|
border: 0;
|
|
286
163
|
background: transparent;
|
|
287
164
|
color: transparent;
|
|
288
|
-
}`}a(
|
|
165
|
+
}`}a(Qc,"editorSidebarStyles");function Zc(){return`
|
|
289
166
|
/**
|
|
290
167
|
* Editor right-hand sidebar tab: AI Access.
|
|
291
168
|
* Served from /mcp/editor/ai-access-sidebar.js (no-store + ?v= cache-bust).
|
|
292
169
|
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
170
|
+
* Since 0.2.0-beta.3 the sidebar holds only: the AI Access toggle, the MCP
|
|
171
|
+
* OAuth toggle, the OAuth route-conflict warning (with RED.view.reveal links),
|
|
172
|
+
* and a button opening the dashboard (<httpAdminRoot>/ai-access). Every other
|
|
173
|
+
* setting lives on the dashboard.
|
|
295
174
|
*/
|
|
296
175
|
(function () {
|
|
297
176
|
if (typeof RED === 'undefined' || !RED.sidebar || !RED.sidebar.addTab) {
|
|
@@ -312,13 +191,6 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
312
191
|
var ADMIN_AUTH = true;
|
|
313
192
|
var LAST = null;
|
|
314
193
|
var dialogOpen = false;
|
|
315
|
-
var FIELD_IDS = {
|
|
316
|
-
enabled: 'enabled', allowWithoutAdminAuth: 'allow-without',
|
|
317
|
-
defaultLinkTtlSeconds: 'ttl', defaultDeploymentType: 'deploy',
|
|
318
|
-
maxActiveLinks: 'max-links', publicBaseUrl: 'public-url',
|
|
319
|
-
corsAllowedOrigins: 'cors', rateLimitWindowMs: 'rate-window',
|
|
320
|
-
rateLimitMaxPerIp: 'rate-ip', rateLimitMaxPerToken: 'rate-token'
|
|
321
|
-
};
|
|
322
194
|
|
|
323
195
|
function authHeaders() {
|
|
324
196
|
var headers = { Accept: 'application/json', 'Content-Type': 'application/json' };
|
|
@@ -341,19 +213,18 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
341
213
|
return headers;
|
|
342
214
|
}
|
|
343
215
|
|
|
344
|
-
function
|
|
345
|
-
if (API_BASE) return API_BASE;
|
|
216
|
+
function adminRoot() {
|
|
346
217
|
var root = RED.settings.httpAdminRoot || '/';
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
218
|
+
return !root || root === '/' ? '' : root.replace(/\\/$/, '');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function resolveApiBase() {
|
|
222
|
+
if (!API_BASE) API_BASE = adminRoot() + '/mcp/api/v1';
|
|
352
223
|
return API_BASE;
|
|
353
224
|
}
|
|
354
225
|
|
|
355
|
-
function
|
|
356
|
-
var url = (LAST && LAST.mcpPageUrl) || '/
|
|
226
|
+
function dashboardUrl(hash) {
|
|
227
|
+
var url = (LAST && LAST.mcpPageUrl) || adminRoot() + '/ai-access';
|
|
357
228
|
if (hash) {
|
|
358
229
|
return url.split('#')[0] + '#' + hash.replace(/^#/, '');
|
|
359
230
|
}
|
|
@@ -414,6 +285,8 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
414
285
|
dialogOpen = true;
|
|
415
286
|
$('#ai-access-dialog-message').text(message);
|
|
416
287
|
$('#ai-access-dialog').show();
|
|
288
|
+
var allowBtn = document.getElementById('ai-access-dialog-allow');
|
|
289
|
+
if (allowBtn && allowBtn.focus) allowBtn.focus();
|
|
417
290
|
return new Promise(function (resolve) {
|
|
418
291
|
function finish(accepted) {
|
|
419
292
|
$('#ai-access-dialog').hide();
|
|
@@ -427,130 +300,35 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
427
300
|
});
|
|
428
301
|
}
|
|
429
302
|
|
|
430
|
-
function hoursFromSeconds(seconds) {
|
|
431
|
-
var s = Number(seconds);
|
|
432
|
-
if (!Number.isFinite(s) || s <= 0) return 4;
|
|
433
|
-
return Math.round(s / 3600);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
function selectedTtlHours() {
|
|
437
|
-
var pressed = $('#' + CONTENT_ID + ' .ai-access-ttl-presets button[aria-pressed="true"]');
|
|
438
|
-
if (pressed.length) {
|
|
439
|
-
return Number(pressed.attr('data-hours')) || 4;
|
|
440
|
-
}
|
|
441
|
-
return Number($('#ai-access-ttl').val()) || 4;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
function setTtlHours(hours) {
|
|
445
|
-
var h = Number(hours);
|
|
446
|
-
if (!Number.isFinite(h) || h <= 0) h = 4;
|
|
447
|
-
$('#ai-access-ttl').val(h);
|
|
448
|
-
$('#' + CONTENT_ID + ' .ai-access-ttl-presets button').each(function () {
|
|
449
|
-
var btn = $(this);
|
|
450
|
-
btn.attr('aria-pressed', Number(btn.attr('data-hours')) === h ? 'true' : 'false');
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
|
|
454
303
|
function applyForm(data) {
|
|
455
304
|
LAST = data;
|
|
456
305
|
CAN_EDIT = !!(data && data.canEdit);
|
|
457
306
|
ADMIN_AUTH = !!(data && data.adminAuthEnabled);
|
|
458
307
|
var s = (data && data.settings) || {};
|
|
308
|
+
var sources = (data && data.sources) || {};
|
|
309
|
+
var readonly = !CAN_EDIT;
|
|
459
310
|
|
|
460
|
-
$('#ai-access-enabled').prop('checked', s.enabled !== false)
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
$('#ai-access-full-warn').toggle($('#ai-access-deploy').val() === 'full');
|
|
465
|
-
|
|
311
|
+
$('#ai-access-enabled').prop('checked', s.enabled !== false)
|
|
312
|
+
.prop('disabled', readonly || sources.enabled === 'settings.js');
|
|
313
|
+
$('#ai-access-allow-without').prop('checked', !!s.allowWithoutAdminAuth)
|
|
314
|
+
.prop('disabled', readonly || sources.allowWithoutAdminAuth === 'settings.js');
|
|
466
315
|
$('#ai-access-adminauth-row').toggle(ADMIN_AUTH);
|
|
467
316
|
$('#ai-access-local-row').toggle(!ADMIN_AUTH);
|
|
468
|
-
$('#ai-access-local-warn').toggle(!ADMIN_AUTH);
|
|
469
317
|
|
|
470
|
-
var readonly = !CAN_EDIT;
|
|
471
|
-
$('#' + CONTENT_ID + ' input, #' + CONTENT_ID + ' select').prop('disabled', readonly);
|
|
472
|
-
$('#' + CONTENT_ID + ' .ai-access-ttl-presets button').prop('disabled', readonly);
|
|
473
|
-
$('#ai-access-readonly-note').toggle(readonly);
|
|
474
|
-
['maxActiveLinks', 'publicBaseUrl', 'rateLimitWindowMs', 'rateLimitMaxPerIp', 'rateLimitMaxPerToken'].forEach(function (key) {
|
|
475
|
-
$('#ai-access-' + FIELD_IDS[key]).val(s[key] == null ? '' : s[key]);
|
|
476
|
-
});
|
|
477
|
-
$('#ai-access-cors').val((s.corsAllowedOrigins || []).join(', '));
|
|
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
|
-
);
|
|
484
|
-
Object.keys(FIELD_IDS).forEach(function (key) {
|
|
485
|
-
var locked = data.sources && data.sources[key] === 'settings.js';
|
|
486
|
-
$('#ai-access-' + FIELD_IDS[key]).prop('disabled', readonly || locked);
|
|
487
|
-
$('#ai-access-lock-' + FIELD_IDS[key]).toggle(!!locked);
|
|
488
|
-
});
|
|
489
|
-
$('#' + CONTENT_ID + ' .ai-access-ttl-presets button').prop('disabled', readonly || (data.sources && data.sources.defaultLinkTtlSeconds === 'settings.js'));
|
|
490
|
-
$('#ai-access-check-public-url').prop('disabled', readonly);
|
|
491
318
|
var oauth = s.oauth || {};
|
|
492
|
-
$('#ai-access-oauth-enabled').prop('checked', !!oauth.enabled)
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
$('#ai-access-
|
|
496
|
-
$('#ai-access-
|
|
497
|
-
$('#ai-access-oauth-
|
|
498
|
-
var oauthLocked = readonly || (data.sources && data.sources.oauth === 'settings.js');
|
|
499
|
-
$('#ai-access-oauth-enabled, #ai-access-oauth-access-ttl, #ai-access-oauth-access-ttl-unit, #ai-access-oauth-refresh-ttl, #ai-access-oauth-refresh-ttl-unit, #ai-access-oauth-refresh-max, #ai-access-oauth-refresh-max-unit, #ai-access-oauth-dcr-cap').prop('disabled', oauthLocked);
|
|
500
|
-
$('#ai-access-oauth-lock').toggle(!!(data.sources && data.sources.oauth === 'settings.js'));
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
var OAUTH_ACCESS_UNITS = [['minutes', 60, 'Minutes'], ['hours', 3600, 'Hours']];
|
|
504
|
-
var OAUTH_REFRESH_UNITS = [['hours', 3600, 'Hours'], ['days', 86400, 'Days']];
|
|
505
|
-
var OAUTH_MAX_UNITS = [['days', 86400, 'Days']];
|
|
506
|
-
|
|
507
|
-
// Show seconds as the largest unit that divides them evenly (smallest unit otherwise).
|
|
508
|
-
function setDuration(field, seconds, units) {
|
|
509
|
-
var unit = units[0];
|
|
510
|
-
for (var i = units.length - 1; i >= 0; i--) {
|
|
511
|
-
if (seconds % units[i][1] === 0) { unit = units[i]; break; }
|
|
512
|
-
}
|
|
513
|
-
$('#ai-access-oauth-' + field).val(Math.max(1, Math.round(seconds / unit[1])));
|
|
514
|
-
$('#ai-access-oauth-' + field + '-unit').val(unit[0]);
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
function readDuration(field, units) {
|
|
518
|
-
var amount = Number($('#ai-access-oauth-' + field).val());
|
|
519
|
-
var name = $('#ai-access-oauth-' + field + '-unit').val();
|
|
520
|
-
var mult = units[0][1];
|
|
521
|
-
units.forEach(function (u) { if (u[0] === name) mult = u[1]; });
|
|
522
|
-
return Math.round(amount * mult);
|
|
319
|
+
$('#ai-access-oauth-enabled').prop('checked', !!oauth.enabled)
|
|
320
|
+
.prop('disabled', readonly || sources.oauth === 'settings.js');
|
|
321
|
+
$('#ai-access-readonly-note').toggle(readonly);
|
|
322
|
+
$('#ai-access-lock-enabled').toggle(sources.enabled === 'settings.js');
|
|
323
|
+
$('#ai-access-lock-allow-without').toggle(sources.allowWithoutAdminAuth === 'settings.js');
|
|
324
|
+
$('#ai-access-oauth-lock').toggle(sources.oauth === 'settings.js');
|
|
523
325
|
}
|
|
524
326
|
|
|
525
327
|
function refreshOAuth() {
|
|
526
328
|
api('/oauth/status').then(function (status) {
|
|
527
329
|
renderRouteWarning(status);
|
|
528
|
-
}).catch(function (
|
|
529
|
-
$('#ai-access-oauth-route-warning').empty().
|
|
530
|
-
});
|
|
531
|
-
api('/oauth/clients').then(function (data) {
|
|
532
|
-
var list = $('#ai-access-oauth-clients').empty();
|
|
533
|
-
var clients = (data && data.clients) || [];
|
|
534
|
-
if (!clients.length) {
|
|
535
|
-
$('<p></p>').text('No registered OAuth clients.').appendTo(list);
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
clients.forEach(function (client) {
|
|
539
|
-
var row = $('<div class="ai-access-oauth-client"></div>').appendTo(list);
|
|
540
|
-
$('<span></span>').text((client.client_name || 'Unnamed client') + ' (' + client.client_id + ')').appendTo(row);
|
|
541
|
-
$('<button type="button" class="red-ui-button"></button>').text('Revoke').prop('disabled', !CAN_EDIT)
|
|
542
|
-
.appendTo(row).on('click', function () {
|
|
543
|
-
openConfirmDialog('Revoke OAuth client "' + (client.client_name || client.client_id) + '" and its tokens?')
|
|
544
|
-
.then(function (accepted) {
|
|
545
|
-
if (!accepted) return;
|
|
546
|
-
api('/oauth/clients/' + encodeURIComponent(client.client_id), { method: 'DELETE' })
|
|
547
|
-
.then(function () { setFlash($('#ai-access-flash'), 'OAuth client revoked.', true); refreshOAuth(); })
|
|
548
|
-
.catch(function (err) { setFlash($('#ai-access-flash'), err.message || String(err), false); });
|
|
549
|
-
});
|
|
550
|
-
});
|
|
551
|
-
});
|
|
552
|
-
}).catch(function (err) {
|
|
553
|
-
$('#ai-access-oauth-clients').text('OAuth clients unavailable: ' + err.message);
|
|
330
|
+
}).catch(function () {
|
|
331
|
+
$('#ai-access-oauth-route-warning').empty().hide();
|
|
554
332
|
});
|
|
555
333
|
}
|
|
556
334
|
|
|
@@ -585,6 +363,7 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
585
363
|
return li;
|
|
586
364
|
}
|
|
587
365
|
|
|
366
|
+
// Only shown when real conflicts exist (spec: sidebar item 3).
|
|
588
367
|
function renderRouteWarning(status) {
|
|
589
368
|
var box = $('#ai-access-oauth-route-warning').empty();
|
|
590
369
|
var details = status.details;
|
|
@@ -593,9 +372,6 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
593
372
|
(status.authorization || []).forEach(function (t) { details.push({ kind: 'authorization', source: 'text', text: t }); });
|
|
594
373
|
(status.metadata || []).forEach(function (t) { details.push({ kind: 'metadata', source: 'text', text: t }); });
|
|
595
374
|
}
|
|
596
|
-
if (status.httpNodeRootFallback) {
|
|
597
|
-
$('<p></p>').text('httpNodeRoot is disabled: OAuth metadata is served under the admin /mcp path.').appendTo(box);
|
|
598
|
-
}
|
|
599
375
|
if (details.length) {
|
|
600
376
|
var intro = $('<p></p>').appendTo(box);
|
|
601
377
|
$('<strong></strong>').text('OAuth route conflict').appendTo(intro);
|
|
@@ -606,14 +382,7 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
606
382
|
else collisionItem(c).appendTo(ul);
|
|
607
383
|
});
|
|
608
384
|
}
|
|
609
|
-
box.toggle(!!
|
|
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 || '');
|
|
385
|
+
box.toggle(!!details.length);
|
|
617
386
|
}
|
|
618
387
|
|
|
619
388
|
function oauthPublicUrlWarning(value) {
|
|
@@ -629,58 +398,11 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
629
398
|
}
|
|
630
399
|
}
|
|
631
400
|
|
|
632
|
-
function formatPendingBadge(live, stale) {
|
|
633
|
-
var parts = [];
|
|
634
|
-
if (live > 0) parts.push(live + ' to review');
|
|
635
|
-
if (stale > 0) parts.push(stale + ' out of date');
|
|
636
|
-
return parts.length ? parts.join(' \xB7 ') : '0';
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
function pendingSidebarText(live, stale, total) {
|
|
640
|
-
if (total === 0) return '0 proposals waiting for review';
|
|
641
|
-
if (total === 1 && stale === 0) return '1 proposal to review';
|
|
642
|
-
return formatPendingBadge(live, stale);
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
function refreshPending() {
|
|
646
|
-
api('/proposals')
|
|
647
|
-
.then(function (data) {
|
|
648
|
-
var proposals = (data && data.proposals) || [];
|
|
649
|
-
var pending = proposals.filter(function (p) {
|
|
650
|
-
return p.status === 'pending';
|
|
651
|
-
});
|
|
652
|
-
var stale = pending.filter(function (p) {
|
|
653
|
-
return !!p.baseRevStale;
|
|
654
|
-
}).length;
|
|
655
|
-
var live = pending.length - stale;
|
|
656
|
-
$('#ai-access-pending-line').text(pendingSidebarText(live, stale, pending.length));
|
|
657
|
-
$('#ai-access-status-block').show();
|
|
658
|
-
})
|
|
659
|
-
.catch(function () {
|
|
660
|
-
$('#ai-access-pending-line').text('\u2014');
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
// /mcp page posts here after approve so the sidebar badge updates immediately.
|
|
665
|
-
try {
|
|
666
|
-
if (typeof BroadcastChannel !== 'undefined') {
|
|
667
|
-
var pendingChannel = new BroadcastChannel('ai-access-pending');
|
|
668
|
-
pendingChannel.onmessage = function (ev) {
|
|
669
|
-
if (ev && ev.data && ev.data.type === 'refresh-pending') {
|
|
670
|
-
refreshPending();
|
|
671
|
-
}
|
|
672
|
-
};
|
|
673
|
-
}
|
|
674
|
-
} catch (e) {
|
|
675
|
-
/* ignore */
|
|
676
|
-
}
|
|
677
|
-
|
|
678
401
|
function loadSettings() {
|
|
679
402
|
setFlash($('#ai-access-flash'), '', true);
|
|
680
403
|
api('/settings')
|
|
681
404
|
.then(function (data) {
|
|
682
405
|
applyForm(data);
|
|
683
|
-
refreshPending();
|
|
684
406
|
refreshOAuth();
|
|
685
407
|
})
|
|
686
408
|
.catch(function (err) {
|
|
@@ -688,9 +410,7 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
688
410
|
});
|
|
689
411
|
}
|
|
690
412
|
|
|
691
|
-
var saveTimer = null;
|
|
692
413
|
var saving = false;
|
|
693
|
-
|
|
694
414
|
var savedToastTimer = null;
|
|
695
415
|
|
|
696
416
|
function showSavedToast() {
|
|
@@ -703,79 +423,44 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
703
423
|
}, 2000);
|
|
704
424
|
}
|
|
705
425
|
|
|
706
|
-
|
|
707
|
-
|
|
426
|
+
// Partial PUT: only the toggles the sidebar owns (server merges the rest).
|
|
427
|
+
function saveSettings() {
|
|
708
428
|
if (!CAN_EDIT || saving || dialogOpen) return;
|
|
709
|
-
var
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
maxActiveLinks: Number($('#ai-access-max-links').val()),
|
|
715
|
-
publicBaseUrl: String($('#ai-access-public-url').val()).trim(),
|
|
716
|
-
corsAllowedOrigins: String($('#ai-access-cors').val()).split(',').map(function (s) { return s.trim(); }).filter(Boolean),
|
|
717
|
-
rateLimitWindowMs: Number($('#ai-access-rate-window').val()),
|
|
718
|
-
rateLimitMaxPerIp: Number($('#ai-access-rate-ip').val()),
|
|
719
|
-
rateLimitMaxPerToken: Number($('#ai-access-rate-token').val()),
|
|
720
|
-
oauth: {
|
|
721
|
-
enabled: $('#ai-access-oauth-enabled').is(':checked'),
|
|
722
|
-
accessTokenTtlSec: readDuration('access-ttl', OAUTH_ACCESS_UNITS),
|
|
723
|
-
refreshIdleTtlSec: readDuration('refresh-ttl', OAUTH_REFRESH_UNITS),
|
|
724
|
-
refreshMaxLifetimeSec: readDuration('refresh-max', OAUTH_MAX_UNITS),
|
|
725
|
-
dcrMaxClients: Number($('#ai-access-oauth-dcr-cap').val()),
|
|
726
|
-
},
|
|
727
|
-
};
|
|
728
|
-
Object.keys(payload).forEach(function (key) {
|
|
729
|
-
if (LAST && LAST.sources && LAST.sources[key] === 'settings.js') delete payload[key];
|
|
730
|
-
});
|
|
731
|
-
// Persist the max lifetime only when the user changed it, so the default can evolve.
|
|
732
|
-
var loadedOauth = (LAST && LAST.settings && LAST.settings.oauth) || {};
|
|
733
|
-
var loadedMax = typeof loadedOauth.refreshMaxLifetimeSec === 'number' ? loadedOauth.refreshMaxLifetimeSec : 7776000;
|
|
734
|
-
if (payload.oauth && Math.round(payload.oauth.refreshMaxLifetimeSec / 86400) === Math.round(loadedMax / 86400)) {
|
|
735
|
-
delete payload.oauth.refreshMaxLifetimeSec;
|
|
736
|
-
}
|
|
429
|
+
var sources = (LAST && LAST.sources) || {};
|
|
430
|
+
var payload = {};
|
|
431
|
+
if (sources.enabled !== 'settings.js') payload.enabled = $('#ai-access-enabled').is(':checked');
|
|
432
|
+
if (sources.allowWithoutAdminAuth !== 'settings.js') payload.allowWithoutAdminAuth = $('#ai-access-allow-without').is(':checked');
|
|
433
|
+
if (sources.oauth !== 'settings.js') payload.oauth = { enabled: $('#ai-access-oauth-enabled').is(':checked') };
|
|
737
434
|
if (LAST && LAST.settings && !(LAST.settings.oauth && LAST.settings.oauth.enabled) && payload.oauth && payload.oauth.enabled) {
|
|
738
|
-
var warning = oauthPublicUrlWarning(
|
|
435
|
+
var warning = oauthPublicUrlWarning(LAST.settings.publicBaseUrl);
|
|
739
436
|
if (warning) {
|
|
740
|
-
openConfirmDialog(warning + '\\n\\nEnable OAuth anyway?').then(function (accepted) {
|
|
741
|
-
if (accepted) persistSettings(payload
|
|
437
|
+
openConfirmDialog(warning + '\\n\\nEnable OAuth anyway? (Set the Public URL on the AI Access dashboard.)').then(function (accepted) {
|
|
438
|
+
if (accepted) persistSettings(payload);
|
|
742
439
|
else $('#ai-access-oauth-enabled').prop('checked', false);
|
|
743
440
|
});
|
|
744
441
|
return;
|
|
745
442
|
}
|
|
746
443
|
}
|
|
747
|
-
persistSettings(payload
|
|
444
|
+
persistSettings(payload);
|
|
748
445
|
}
|
|
749
446
|
|
|
750
|
-
function persistSettings(payload
|
|
447
|
+
function persistSettings(payload) {
|
|
751
448
|
saving = true;
|
|
752
449
|
api('/settings', { method: 'PUT', body: JSON.stringify(payload) })
|
|
753
450
|
.then(function (data) {
|
|
754
451
|
applyForm(data);
|
|
755
|
-
|
|
756
|
-
$('#ai-access-public-url-warning').text(urlWarnings).toggle(!!urlWarnings);
|
|
757
|
-
if (silent) showSavedToast();
|
|
758
|
-
else setFlash($('#ai-access-flash'), 'Settings saved.', true);
|
|
759
|
-
refreshPending();
|
|
452
|
+
showSavedToast();
|
|
760
453
|
refreshOAuth();
|
|
761
454
|
})
|
|
762
455
|
.catch(function (err) {
|
|
763
456
|
setFlash($('#ai-access-flash'), err.message || String(err), false);
|
|
457
|
+
loadSettings();
|
|
764
458
|
})
|
|
765
459
|
.then(function () {
|
|
766
460
|
saving = false;
|
|
767
461
|
});
|
|
768
462
|
}
|
|
769
|
-
|
|
770
|
-
function scheduleAutoSave() {
|
|
771
|
-
if (!CAN_EDIT) return;
|
|
772
|
-
if (saveTimer) clearTimeout(saveTimer);
|
|
773
|
-
saveTimer = setTimeout(function () {
|
|
774
|
-
saveTimer = null;
|
|
775
|
-
saveSettings({ silent: true });
|
|
776
|
-
}, 400);
|
|
777
|
-
}
|
|
778
|
-
`}a(Jc,"editorSidebarScriptPartA");function Kc(){return`
|
|
463
|
+
`}a(Zc,"editorSidebarScriptPartA");function eu(){return`
|
|
779
464
|
function buildContent() {
|
|
780
465
|
var content = $('<div></div>', { id: CONTENT_ID, class: 'red-ui-sidebar-ai-access' });
|
|
781
466
|
|
|
@@ -789,7 +474,7 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
789
474
|
.on('click', loadSettings);
|
|
790
475
|
|
|
791
476
|
var body = $('<div class="red-ui-sidebar-ai-access-body"></div>').appendTo(content);
|
|
792
|
-
var dialog = $('<div id="ai-access-dialog" class="ai-access-dialog" role="dialog" aria-modal="true" aria-labelledby="ai-access-dialog-title" style="display:none"></div>').appendTo(
|
|
477
|
+
var dialog = $('<div id="ai-access-dialog" class="ai-access-dialog" role="dialog" aria-modal="true" aria-labelledby="ai-access-dialog-title" style="display:none"></div>').appendTo(content);
|
|
793
478
|
$('<h3 id="ai-access-dialog-title"></h3>').text('Confirm OAuth action').appendTo(dialog);
|
|
794
479
|
$('<p id="ai-access-dialog-message"></p>').appendTo(dialog);
|
|
795
480
|
var dialogActions = $('<div class="ai-access-dialog-actions"></div>').appendTo(dialog);
|
|
@@ -797,35 +482,25 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
797
482
|
$('<button type="button" id="ai-access-dialog-allow" class="red-ui-button"></button>').text('Continue').appendTo(dialogActions);
|
|
798
483
|
|
|
799
484
|
$('<div class="red-ui-settings-section-description"></div>')
|
|
800
|
-
.text('
|
|
485
|
+
.text('Changes apply immediately. Tokens, proposals, and all other settings are on the AI Access dashboard.')
|
|
801
486
|
.appendTo(body);
|
|
802
487
|
|
|
803
488
|
$('<p id="ai-access-readonly-note" class="form-tips" style="display:none;"></p>')
|
|
804
489
|
.text('Read-only: full admin permission (*) is required to change these settings.')
|
|
805
490
|
.appendTo(body);
|
|
806
491
|
|
|
807
|
-
var status = $(
|
|
808
|
-
'<div id="ai-access-status-block" class="ai-access-status-block" style="display:none;"></div>'
|
|
809
|
-
).appendTo(body);
|
|
810
|
-
$('<p id="ai-access-pending-line"></p>')
|
|
811
|
-
.text('0 proposals waiting for review')
|
|
812
|
-
.appendTo(status);
|
|
813
|
-
$('<button type="button" id="ai-access-review" class="red-ui-button"></button>')
|
|
814
|
-
.text('Review')
|
|
815
|
-
.appendTo(status)
|
|
816
|
-
.on('click', function () {
|
|
817
|
-
window.open(mcpPageUrl('proposals'), '_blank');
|
|
818
|
-
});
|
|
819
|
-
|
|
820
492
|
var row;
|
|
821
493
|
|
|
494
|
+
// (1) Enable AI Access features (with adminAuth) / local-only opt-in (without).
|
|
822
495
|
row = $('<div class="red-ui-settings-row" id="ai-access-adminauth-row"></div>').appendTo(body);
|
|
823
496
|
$('<input id="ai-access-enabled" type="checkbox">').appendTo(row);
|
|
824
497
|
$('<label for="ai-access-enabled"></label>')
|
|
825
498
|
.html(
|
|
826
|
-
'<p>Enable AI Access features</p>When off, access
|
|
499
|
+
'<p>Enable AI Access features</p>When off, access tokens, proposals, and AI tools are disabled.'
|
|
827
500
|
)
|
|
828
501
|
.appendTo(row);
|
|
502
|
+
$('<span id="ai-access-lock-enabled" class="form-tips" style="display:none"></span>')
|
|
503
|
+
.text('\u{1F512} settings.js').appendTo(row);
|
|
829
504
|
|
|
830
505
|
row = $(
|
|
831
506
|
'<div class="red-ui-settings-row" id="ai-access-local-row" style="display:none;"></div>'
|
|
@@ -833,134 +508,36 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
833
508
|
$('<input id="ai-access-allow-without" type="checkbox">').appendTo(row);
|
|
834
509
|
$('<label for="ai-access-allow-without"></label>')
|
|
835
510
|
.html(
|
|
836
|
-
'<p>
|
|
511
|
+
'<p>Enable AI Access features without adminAuth (local-only)</p>Not for production. Anyone who can reach the editor admin port can use AI features.'
|
|
837
512
|
)
|
|
838
513
|
.appendTo(row);
|
|
514
|
+
$('<span id="ai-access-lock-allow-without" class="form-tips" style="display:none"></span>')
|
|
515
|
+
.text('\u{1F512} settings.js').appendTo(row);
|
|
839
516
|
|
|
840
|
-
|
|
841
|
-
.text(
|
|
842
|
-
'Warning: adminAuth is disabled. Enabling this opt-in exposes AI Access without login.'
|
|
843
|
-
)
|
|
844
|
-
.appendTo(body);
|
|
845
|
-
|
|
846
|
-
row = $('<div class="red-ui-settings-row stacked"></div>').appendTo(body);
|
|
847
|
-
$('<label for="ai-access-deploy"></label>').text('Default deploy type').appendTo(row);
|
|
848
|
-
var select = $('<select id="ai-access-deploy"></select>').appendTo(row);
|
|
849
|
-
$('<option value="nodes">Only changed nodes (recommended)</option>').appendTo(select);
|
|
850
|
-
$('<option value="flows">Changed flows</option>').appendTo(select);
|
|
851
|
-
$('<option value="full">Full redeploy</option>').appendTo(select);
|
|
852
|
-
$('<p id="ai-access-full-warn" class="form-tips" style="display:none;color:#b33;"></p>')
|
|
853
|
-
.text('Warning: full restarts the entire runtime.')
|
|
854
|
-
.appendTo(row);
|
|
855
|
-
select.on('change', function () {
|
|
856
|
-
$('#ai-access-full-warn').toggle(select.val() === 'full');
|
|
857
|
-
});
|
|
858
|
-
|
|
859
|
-
row = $('<div class="red-ui-settings-row stacked"></div>').appendTo(body);
|
|
860
|
-
$('<label for="ai-access-ttl"></label>').text('Default link expiry').appendTo(row);
|
|
861
|
-
var presets = $(
|
|
862
|
-
'<div class="ai-access-ttl-presets" role="group" aria-label="Link expiry"></div>'
|
|
863
|
-
).appendTo(row);
|
|
864
|
-
[1, 4, 24].forEach(function (h) {
|
|
865
|
-
$('<button type="button" class="red-ui-button"></button>')
|
|
866
|
-
.attr('data-hours', String(h))
|
|
867
|
-
.attr('aria-pressed', h === 4 ? 'true' : 'false')
|
|
868
|
-
.text(h === 1 ? '1 hour' : h + ' hours')
|
|
869
|
-
.appendTo(presets)
|
|
870
|
-
.on('click', function () {
|
|
871
|
-
setTtlHours(h);
|
|
872
|
-
scheduleAutoSave();
|
|
873
|
-
});
|
|
874
|
-
});
|
|
875
|
-
$('<input id="ai-access-ttl" type="hidden" value="4">').appendTo(row);
|
|
876
|
-
|
|
877
|
-
[
|
|
878
|
-
['max-links', 'Maximum active links', 'number'],
|
|
879
|
-
['public-url', 'Public URL (HTTPS origin)', 'text'],
|
|
880
|
-
['cors', 'CORS allowed origins (comma separated)', 'text'],
|
|
881
|
-
['rate-window', 'Rate limit window (ms)', 'number'],
|
|
882
|
-
['rate-ip', 'Requests per IP per window', 'number'],
|
|
883
|
-
['rate-token', 'Requests per token per window', 'number']
|
|
884
|
-
].forEach(function (field) {
|
|
885
|
-
var id = 'ai-access-' + field[0];
|
|
886
|
-
var fieldRow = $('<div class="red-ui-settings-row stacked"></div>').appendTo(body);
|
|
887
|
-
$('<label></label>').attr('for', id).text(field[1]).appendTo(fieldRow);
|
|
888
|
-
var controls = field[0] === 'public-url' ? $('<div class="ai-access-public-url-controls"></div>').appendTo(fieldRow) : fieldRow;
|
|
889
|
-
var input = $('<input>').attr('id', id).attr('type', field[2]).appendTo(controls);
|
|
890
|
-
if (field[2] === 'number') input.attr('min', '1').attr('step', 'any');
|
|
891
|
-
input.on('change', scheduleAutoSave);
|
|
892
|
-
if (field[0] === 'public-url') {
|
|
893
|
-
$('<button type="button" id="ai-access-check-public-url" class="red-ui-button"></button>')
|
|
894
|
-
.text('Check').appendTo(controls).on('click', function () {
|
|
895
|
-
setCheckResult('pending', 'Checking\u2026');
|
|
896
|
-
api('/settings/check-public-url', { method: 'POST', body: JSON.stringify({ publicBaseUrl: $('#ai-access-public-url').val() }) })
|
|
897
|
-
.then(function (result) {
|
|
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); });
|
|
900
|
-
});
|
|
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);
|
|
907
|
-
}
|
|
908
|
-
});
|
|
909
|
-
$('<h3></h3>').text('MCP OAuth').appendTo(body);
|
|
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);
|
|
517
|
+
// (2) Enable MCP OAuth.
|
|
922
518
|
row = $('<div class="red-ui-settings-row"></div>').appendTo(body);
|
|
923
519
|
$('<input id="ai-access-oauth-enabled" type="checkbox">').appendTo(row);
|
|
924
|
-
$('<label for="ai-access-oauth-enabled"></label>')
|
|
520
|
+
$('<label for="ai-access-oauth-enabled"></label>')
|
|
521
|
+
.html('<p>Enable MCP OAuth</p>MCP clients (ChatGPT, Claude, \u2026) sign in at <Public URL>/mcp.')
|
|
522
|
+
.appendTo(row);
|
|
925
523
|
$('<span id="ai-access-oauth-lock" class="form-tips" style="display:none"></span>')
|
|
926
|
-
.text('\u{1F512}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
['refresh-ttl', 'Refresh token idle lifetime', OAUTH_REFRESH_UNITS],
|
|
930
|
-
['refresh-max', 'Refresh token max lifetime (default 90 days, 0 = unlimited)', OAUTH_MAX_UNITS],
|
|
931
|
-
['dcr-cap', 'Maximum registered OAuth clients', null]
|
|
932
|
-
].forEach(function (field) {
|
|
933
|
-
var id = 'ai-access-oauth-' + field[0];
|
|
934
|
-
var oauthRow = $('<div class="red-ui-settings-row stacked"></div>').appendTo(body);
|
|
935
|
-
$('<label></label>').attr('for', id).text(field[1]).appendTo(oauthRow);
|
|
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);
|
|
938
|
-
if (!field[2]) return;
|
|
939
|
-
var select = $('<select></select>').attr('id', id + '-unit').attr('aria-label', field[1] + ' unit')
|
|
940
|
-
.appendTo(line).on('change', scheduleAutoSave);
|
|
941
|
-
field[2].forEach(function (u) { $('<option></option>').attr('value', u[0]).text(u[2]).appendTo(select); });
|
|
942
|
-
});
|
|
524
|
+
.text('\u{1F512} settings.js').appendTo(row);
|
|
525
|
+
|
|
526
|
+
// (3) OAuth route conflict warning \u2014 only rendered when conflicts exist.
|
|
943
527
|
$('<div id="ai-access-oauth-route-warning" class="form-tips ai-access-oauth-warning" role="status" style="display:none"></div>').appendTo(body);
|
|
944
|
-
$('<h4></h4>').text('Registered OAuth clients').appendTo(body);
|
|
945
|
-
$('<div id="ai-access-oauth-clients" aria-live="polite"></div>').appendTo(body);
|
|
946
|
-
Object.keys(FIELD_IDS).forEach(function (key) {
|
|
947
|
-
var id = FIELD_IDS[key];
|
|
948
|
-
$('<span class="form-tips" style="display:none"></span>')
|
|
949
|
-
.attr('id', 'ai-access-lock-' + id).text('\u{1F512} Locked by settings.js')
|
|
950
|
-
.appendTo(content.find('#ai-access-' + id).closest('.red-ui-settings-row'));
|
|
951
|
-
});
|
|
952
528
|
|
|
953
|
-
content.find('#ai-access-enabled, #ai-access-allow-without, #ai-access-
|
|
529
|
+
content.find('#ai-access-enabled, #ai-access-allow-without, #ai-access-oauth-enabled').on(
|
|
954
530
|
'change',
|
|
955
|
-
|
|
531
|
+
saveSettings
|
|
956
532
|
);
|
|
957
533
|
|
|
958
|
-
|
|
534
|
+
// (4) Open the dashboard.
|
|
535
|
+
row = $('<div class="ai-access-actions"></div>').appendTo(body);
|
|
959
536
|
$('<button type="button" id="ai-access-open-mcp" class="red-ui-button"></button>')
|
|
960
537
|
.text('Open AI Access dashboard')
|
|
961
538
|
.appendTo(row)
|
|
962
539
|
.on('click', function () {
|
|
963
|
-
window.open(
|
|
540
|
+
window.open(dashboardUrl(), '_blank');
|
|
964
541
|
});
|
|
965
542
|
|
|
966
543
|
return content;
|
|
@@ -985,8 +562,8 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
985
562
|
},
|
|
986
563
|
});
|
|
987
564
|
})();
|
|
988
|
-
`}a(
|
|
989
|
-
`)}a(
|
|
565
|
+
`}a(eu,"editorSidebarScriptPartB");function tu(){return Zc()+eu()}a(tu,"editorSidebarScriptBody");function yr(){let e=JSON.stringify(Qc());return[`/* @com55/node-red-ai-access editor sidebar v=${Xt} */`,"(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);"," }","})();",tu()].join(`
|
|
566
|
+
`)}a(yr,"renderEditorSidebarJs");function wi(e=Xt){return`<script>
|
|
990
567
|
/**
|
|
991
568
|
* Thin loader for the AI Access editor sidebar.
|
|
992
569
|
* Logic lives at GET /mcp/editor/ai-access-sidebar.js (Cache-Control: no-store)
|
|
@@ -1020,8 +597,8 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
1020
597
|
(document.head || document.documentElement).appendChild(s);
|
|
1021
598
|
})();
|
|
1022
599
|
</script>
|
|
1023
|
-
`}a(
|
|
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(
|
|
600
|
+
`}a(wi,"renderEditorSidebarLoaderHtml");K();Fe();function bi(e){return Oe[e]??"Request failed"}a(bi,"safeErrorText");function nu(e,t){let n=O(t,"rest");if(!n)return!1;let r=n.body.code,o={error:bi(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(nu,"sendMappedFlowError");function vi(e){return async(t,n)=>{try{n.json(await pe(e))}catch(r){if(nu(n,r))return;n.setHeader("X-Content-Type-Options","nosniff"),n.status(502).json({error:bi("upstream_error"),code:"upstream_error"})}}}a(vi,"listFlowsHandler");ie();K();_e();Fe();function Yt(e){return e==null||e===""?{ok:!0,format:"summary"}:e==="summary"||e==="full"?{ok:!0,format:e}:{ok:!1}}a(Yt,"resolveDiffFormat");function ru(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(ru,"compactLineDiff");function ou(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=ru(e.lines),d={key:e.key,kind:"text",beforeBytes:o,afterBytes:s,lines:i.lines};return i.truncated&&(d.truncated=!0),d}a(ou,"formatTextChange");function su(e,t){return e.map(n=>n.kind==="text"?ou(n,t):n)}a(su,"formatPropertyChanges");function ki(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)}
|
|
601
|
+
\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(ki,"formatPresenceText");function Ai(e,t){return{summary:e.summary,added:e.added.map(n=>ki(n,t)),removed:e.removed.map(n=>ki(n,t)),modified:e.modified.map(n=>({...n,changes:su(n.changes,t)}))}}a(Ai,"formatSections");function Qt(e,t="summary"){let n=Ai(e,t),r={...e,...n,format:t};return e.vsCurrent&&(r.vsCurrent=Ai(e.vsCurrent,t)),r}a(Qt,"applyDiffFormat");G();ie();gr();function Zt(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}a(Zt,"escapeForDocs");var xi="https://YOUR-NODE-RED/mcp/rpc";function iu(e,t,n,r=xi){return`## Also available (prefer when your client can send headers)
|
|
1025
602
|
|
|
1026
603
|
**Prefer MCP or REST** whenever the AI client can send custom headers and JSON
|
|
1027
604
|
bodies \u2014 no URL length limits and no chunked drafts. Use this **GET link
|
|
@@ -1033,7 +610,7 @@ from the Access links page (\`/mcp\` \u2192 Access links \u2192 create \`kind=re
|
|
|
1033
610
|
\`kind=mcp\`).
|
|
1034
611
|
|
|
1035
612
|
**What to tell the user** (copy/paste when you get \`wrong_token_kind\`):
|
|
1036
|
-
> Please open Node-RED \u2192 /
|
|
613
|
+
> Please open Node-RED \u2192 /ai-access \u2192 Access tokens and create a kind=rest or kind=mcp
|
|
1037
614
|
> token (or a get_link URL if that is what I asked for), then send it to me.
|
|
1038
615
|
|
|
1039
616
|
- REST: \`${e}\` with \`Authorization: Bearer <rest-token>\` (**rest** kind \u2014
|
|
@@ -1090,7 +667,7 @@ curl -sS -X POST "${r}" \\
|
|
|
1090
667
|
-H "Accept: application/json, text/event-stream" \\
|
|
1091
668
|
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
|
|
1092
669
|
\`\`\`
|
|
1093
|
-
`}a(
|
|
670
|
+
`}a(iu,"docsAlsoAvailable");function au(e,t){return`# Node-RED AI Access \u2014 self-contained GET link (get_link)${e}
|
|
1094
671
|
|
|
1095
672
|
**Git mental model:** a proposal is like a pull request. Human approve = merge;
|
|
1096
673
|
reject = close; withdraw = you close your own PR. If \`rev\`/\`expectedRev\` does
|
|
@@ -1122,7 +699,7 @@ same either way.
|
|
|
1122
699
|
- Link expires at: **${t}** (ISO-8601); \`GET ./status\` also reports
|
|
1123
700
|
\`caller.minutesRemaining\` (rounded up) and \`caller.secondsRemaining\`
|
|
1124
701
|
- After expiry, every path under this link returns \`401 token_expired\` with
|
|
1125
|
-
\`expiredAt\` and \`accessLinksUrl\` (the /
|
|
702
|
+
\`expiredAt\` and \`accessLinksUrl\` (the /ai-access dashboard \u2192 Access tokens tab)
|
|
1126
703
|
- Ask the user for a **new** link there if you see that error (or \`access_link_invalid\`)
|
|
1127
704
|
|
|
1128
705
|
## What you can do
|
|
@@ -1147,7 +724,7 @@ when it does not name the flow), \`storedTitle\` (the title as saved: your title
|
|
|
1147
724
|
\` in <flow>\` suffix, or the auto-generated title, which may already name the flow),
|
|
1148
725
|
\`flowLabel\`, \`displayName\` (\`title \xB7\` **server-local**
|
|
1149
726
|
datetime with offset, e.g. \`16 Oct 01:35 +00:00\`), \`reviewUrl\` (absolute when
|
|
1150
|
-
Host is known), and \`reviewPath\` (relative \`/
|
|
727
|
+
Host is known), and \`reviewPath\` (relative \`/ai-access#proposal=<id>\`).
|
|
1151
728
|
\`createdAt\` is ISO-8601. **Always tell the user about the proposal using
|
|
1152
729
|
\`displayName\` + \`reviewUrl\` \u2014 not the raw id.**
|
|
1153
730
|
|
|
@@ -1165,7 +742,7 @@ fields at the top level** (no \`ok\` / \`proposal\` wrapper). Read
|
|
|
1165
742
|
|
|
1166
743
|
Lean by default (no \`payload\` / \`baseFlow\`). Pass
|
|
1167
744
|
\`?include=payload,baseFlow\` or \`?include_payload=true\` to include them.
|
|
1168
|
-
`}a(
|
|
745
|
+
`}a(au,"docsIntro");function du(){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(du,"docsWorkflow");function lu(){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(lu,"docsUrlTable");function cu(){return`## Propose examples
|
|
1169
746
|
|
|
1170
747
|
**Recommended for small edits to long fields** \u2014 \`replace_text\` (find must be
|
|
1171
748
|
**unique** unless you pass \`occurrence\` as a 1-based index). Creates a normal
|
|
@@ -1243,9 +820,9 @@ GET ./propose?flow=tab1&rev=abc123&patch=<base64url>
|
|
|
1243
820
|
Node-level proposes create a normal \`replace_flow\` proposal; owners should
|
|
1244
821
|
approve with deployment type **\`nodes\`** (plugin default) so only changed
|
|
1245
822
|
nodes deploy.
|
|
1246
|
-
`}a(
|
|
823
|
+
`}a(cu,"docsProposeExamples");function uu(){return`## Chunked drafts (long HTML / large fields)
|
|
1247
824
|
|
|
1248
|
-
When the GET URL would exceed **${
|
|
825
|
+
When the GET URL would exceed **${It} bytes**, do not
|
|
1249
826
|
stuff everything into \`propose\`. Use drafts (expire after **30 minutes**,
|
|
1250
827
|
max **${j}** decoded bytes):
|
|
1251
828
|
|
|
@@ -1292,7 +869,7 @@ Or a full \`replace_flow\` document. JSON-mode drafts **may add nodes**, so
|
|
|
1292
869
|
\`draft/new\` does not require an existing \`node\`/\`field\`. Use JSON mode for
|
|
1293
870
|
structural changes; use field-mode only when you need to replace one whole
|
|
1294
871
|
string field that is too large for a single propose URL.
|
|
1295
|
-
`}a(
|
|
872
|
+
`}a(uu,"docsDrafts");function pu(){return`## Last resort: content larger than chunked drafts
|
|
1296
873
|
|
|
1297
874
|
If the field is still too large for chunked drafts (decoded size over the
|
|
1298
875
|
proposal cap), use this fallback only after drafts fail:
|
|
@@ -1306,7 +883,7 @@ proposal cap), use this fallback only after drafts fail:
|
|
|
1306
883
|
**Trade-off:** pasted content is **not** reviewed through the proposal diff.
|
|
1307
884
|
Prefer \`replace_text\` or chunked drafts whenever they fit so the human can
|
|
1308
885
|
still see a readable change on \`/mcp\`.
|
|
1309
|
-
`}a(
|
|
886
|
+
`}a(pu,"docsLastResortPaste");function fu(){return`## Size cap and REST/MCP nodes payload shapes
|
|
1310
887
|
|
|
1311
888
|
The proposal JSON cap is **${j} bytes**
|
|
1312
889
|
(\`MAX_PROPOSAL_JSON_BYTES\` / ~1.5MB) on **every** channel: GET propose/drafts,
|
|
@@ -1352,7 +929,7 @@ Example (canonical):
|
|
|
1352
929
|
**Alternative (also accepted):** top-level \`nodes\` array as an alias for
|
|
1353
930
|
\`payload.nodes\` (same op shapes). Full-tab replace remains supported via
|
|
1354
931
|
\`payload.flow\` (from \`GET ./flows/<id>\` / REST \`GET /flows/{id}\`).
|
|
1355
|
-
`}a(
|
|
932
|
+
`}a(fu,"docsRestNodesPayload");function mu(){return`## Known issues
|
|
1356
933
|
|
|
1357
934
|
- **Rate limits are per token (and per IP).** Each access-link token has its own
|
|
1358
935
|
token bucket. Several tokens can each use the full per-token limit at the same
|
|
@@ -1360,28 +937,64 @@ Example (canonical):
|
|
|
1360
937
|
- **\`RateLimit-*\` headers describe the per-token bucket** (default
|
|
1361
938
|
\`RateLimit-Policy: 300;w=60\`) on token requests; requests without a token
|
|
1362
939
|
report the per-IP bucket (\`600;w=60\`). Both are enforced.
|
|
1363
|
-
`}a(
|
|
940
|
+
`}a(mu,"docsKnownIssues");function hu(){return`## Error codes (JSON unless docs)
|
|
1364
941
|
|
|
1365
|
-
${
|
|
942
|
+
${fi()}
|
|
1366
943
|
|
|
1367
944
|
All JSON responses use \`Cache-Control: no-store\`. Never put secrets in query
|
|
1368
945
|
strings you will paste publicly beyond this temporary link.
|
|
1369
|
-
`}a(
|
|
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=
|
|
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(
|
|
946
|
+
`}a(hu,"docsErrorsAndAlso");function Si(e){let{link:t,openapiPath:n,restApiBase:r,mcpRpcPath:o,publicOrigin:s}=e,i=Zt(t.expiresAt),d=t.label?` (${Zt(t.label)})`:"",l=Zt(n),c=Zt(r),u=Zt(o);return[au(d,i),iu(c,l,u,s?Zt(`${s}${o}`):xi),du(),lu(),cu(),fu(),uu(),pu(),mu(),hu()].join(`
|
|
947
|
+
`)}a(Si,"renderGetLinkDocs");var Oi=require("crypto");G();K();Fe();Xe();Ae();K();function wr(e){return{error:e instanceof Error&&e.message.trim()?e.message:"Internal server error",code:"internal_error"}}a(wr,"internalErrorBody");function gu(e,t){e.headersSent||e.status(500).json(wr(t))}a(gu,"sendInternalError");function $(e,t){if(e.headersSent)return;let n=O(t);if(n){e.status(n.statusCode).json(n.body);return}gu(e,t)}a($,"sendCaughtRouteError");function R(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(R,"asyncRoute");var vr=require("crypto");G();ie();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}},ht=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"}},gt=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}},yu=/^[A-Za-z0-9_-]+={0,2}$/;function Ti(e){if(!yu.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(Ti,"assertValidBase64UrlChunk");var br=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,vr.randomBytes)(12).toString("hex")),this.maxBytes=t.maxBytes??j,this.ttlMs=t.ttlMs??Ss}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 gt(t,o.proposalId);let s=this.drafts.get(t);if(s?.linkId!==n)throw new ht(t);if(s.expiresAtMs<=r)throw new ht(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=Ti(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=>Ti(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,vr.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()}};pr();it();ie();var Ze=ln(require("express-rate-limit"),1);var jo=6e4,Pi=600,Ri=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 He(e){if(!e)return{windowMs:jo,maxPerIp:Pi,maxPerToken:Ri};let t=e;return{windowMs:Do(t.windowMs??t.rateLimitWindowMs,jo),maxPerIp:Do(t.maxPerIp??t.rateLimitMaxPerIp,Pi),maxPerToken:Do(t.maxPerToken??t.rateLimitMaxPerToken,Ri)}}a(He,"resolveRateLimitConfig");function wu(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(wu,"retryAfterSeconds");function _i(e,t,n=jo){let r=wu(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(_i,"sendRateLimited");function bu(e){return(t,n,r,o)=>{_i(t,n,e)}}a(bu,"rateLimitedHandler");function kr(e){return`ip:${(0,Ze.ipKeyGenerator)(e.ip??"unknown")}`}a(kr,"clientIpKey");function Ar(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(Ar,"accessTokenRateLimitKey");function Li(e,t,n,r){let o={windowMs:t,max:e,standardHeaders:!0,legacyHeaders:!1,keyGenerator:n,handler:bu(t)};return r&&(o.skip=r),(0,Ze.default)(o)}a(Li,"createLimiter");function Ho(e){let t=He(e);return Li(t.maxPerIp,t.windowMs,kr)}a(Ho,"createAiAccessRateLimiter");function vu(e){let t=He(e);return Li(t.maxPerToken,t.windowMs,n=>Ar(n)??kr(n),n=>Ar(n)==null)}a(vu,"createAiAccessTokenRateLimiter");function Ci(e){let t=He(e),n=Ho(t),r=vu(t);return{ip:n,token:r,stack:[n,r],config:t}}a(Ci,"createAiAccessDualRateLimiters");function Ei(e){let t=He(e()).windowMs,n=He(e()).maxPerIp,r=He(e()).maxPerToken,o=new Ze.MemoryStore,s=new Ze.MemoryStore,i=a(()=>{let h=He(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=((x,S)=>(x.toLowerCase()==="ratelimit-policy"&&typeof S=="string"&&(S=S.replace(/;w=\d+/,`;w=${Math.ceil(t/1e3)}`)),g(x,S)))},"useLiveWindowPolicy"),c=a((h,g)=>(0,Ze.default)({store:g,windowMs:t,limit:a(()=>{let x=He(e());return h?x.maxPerToken:x.maxPerIp},"limit"),standardHeaders:!0,legacyHeaders:!1,keyGenerator:h?x=>Ar(x)??kr(x):kr,skip:a(x=>h&&Ar(x)==null,"skip"),handler:a((x,S,E)=>{_i(x,S,He(e()).windowMs)},"handler")}),"make"),u=c(!1,o),p=c(!0,s);return{ip:a((h,g,x)=>(i(),l(g),u(h,g,x)),"ip"),token:a((h,g,x)=>(i(),l(g),p(h,g,x)),"token")}}a(Ei,"createLiveAiAccessDualRateLimiters");ye();function F(e){e.setHeader("Cache-Control","no-store"),e.setHeader("Pragma","no-cache")}a(F,"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 Ii(e,t){return(n,r,o)=>{F(r);let s=n.originalUrl||n.url||"";if(n.getLinkUrlBytes=Buffer.byteLength(s,"utf8"),Uo(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:Le,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"){wn(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:st,code:yn,hint:c});return}if(l.link.kind!=="get_link"){bn(r,"get_link",l.link.kind);return}n.accessLink=l.link,n.getLinkToken=d,o()}}a(Ii,"requireGetLinkToken");function en(e,t){let n=e.getLinkUrlBytes;return(typeof n=="number"?n:Buffer.byteLength(e.originalUrl||e.url||"","utf8"))>It?(F(t),t.status(414).json({error:`Request URL exceeds ${It} 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:It}),!0):!1}a(en,"rejectOversizedGetUrl");function C(e,t,n,r,o,s){F(e),e.status(t).json({error:n,code:r,...o?{hint:o}:{},...s})}a(C,"jsonError");function Ni(){return(e,t)=>{Uo(e),F(t),t.status(404).json({error:"Not found",code:"not_found"})}}a(Ni,"getLinkNotFoundHandler");var ku="link_create_not_supported";function Fi(){return(e,t)=>{Uo(e),F(t),t.status(403).json({error:"GET access links cannot create tokens",code:ku,hint:"Ask an admin with * permission to mint tokens via POST /mcp/api/v1/links on the /ai-access dashboard (Access tokens tab). This get_link URL cannot create, list, or revoke tokens."})}}a(Fi,"getLinkMintNotSupportedHandler");function _(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(_,"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 Au(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(Au,"validateFieldDraftTargets");function xu(e,t,n,r){if(r)try{let o=Pn(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(xu,"planDraftMode");async function Su(e,t,n,r){try{let o=await Au(t,n,r);if(o){let s=o.code==="node_not_found"||o.code==="field_not_found"?404:400;return C(e,s,o.message,o.code,Se(o.code)),!0}}catch(o){let s=O(o);return s?(C(e,s.statusCode,s.body.error,s.body.code,Se(s.body.code)),!0):($(e,o),!0)}return!1}a(Su,"rejectInvalidFieldTargets");function $i(e,t){return async(n,r)=>{if(en(n,r))return;F(r);let o=_(n.query,"flow"),s=_(n.query,"rev"),i=_(n.query,"node"),d=_(n.query,"field"),l=_(n.query,"summary"),c=_(n.query,"title")??l,u=_(n.query,"mode"),p=_(n.query,"targets");if(!o||!s){C(r,400,"flow and rev query parameters are required","missing_draft_params",Se("missing_draft_params"));return}if(!n.accessLink){C(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){C(r,g.statusCode,g.body.error,g.body.code,g.body.code==="flow_not_found"?se("flow_not_found","get")??"Use an id from GET ./flows":Se(g.body.code));return}$(r,h);return}let f=xu(i,d,u,p);if(!f.ok){C(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 Su(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($i,"getLinkDraftNewHandler");function Mi(e){return(t,n)=>{if(en(t,n))return;F(n);let r=t.params.draftId,o=_(t.query,"seq"),s=_(t.query,"data");if(typeof r!="string"||!r){C(n,400,"Missing draft id","missing_draft_id");return}if(!t.accessLink){C(n,401,"Access link required","access_link_missing");return}if(o===void 0||s===void 0){C(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 ht||d instanceof gt||d instanceof D){C(n,d.statusCode,d.message,d.code,Se(d.code),d instanceof gt?{proposalId:d.proposalId}:void 0);return}$(n,d);return}}}a(Mi,"getLinkDraftAppendHandler");function Tu(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(Tu,"editsFromFieldDraft");function Di(e,t,n,r,o,s){return async(i,d)=>{if(en(i,d))return;F(d);let l=i.params.draftId,c=_(i.query,"sha256"),u=_(i.query,"title");if(typeof l!="string"||!l){C(d,400,"Missing draft id","missing_draft_id");return}if(!i.accessLink){C(d,401,"Access link required","access_link_missing");return}if(!c){C(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 A=Tu(p,f),v=await Tn(r,p.flowId,A);if(v.expectedRev!==p.expectedRev){d.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:p.expectedRev,currentRev:v.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=v.flow,g=v.expectedRev}else{let A=Rn(JSON.parse(f),p.flowId);if(m=A.flowId,A.kind==="replace_flow")h={...A.flow,id:A.flow.id||m,type:A.flow.type??"tab",nodes:A.flow.nodes??[]};else{let v=await pt(r,m,A.ops,{expectedRev:p.expectedRev,...s?{nodeTypes:s}:{}});if(v.expectedRev!==p.expectedRev){d.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:p.expectedRev,currentRev:v.expectedRev,draftDeleted:!1,hint:"Draft was kept. Re-read the flow and start a new draft with the current rev."});return}h=v.flow,g=v.expectedRev}}let x=u??p.title??p.summary,S=q(i,o),E=re(i.query),I=await Ye(t,n,i.accessLink,{type:"replace_flow",expectedRev:g,payload:{flowId:m,flow:h},...x?{title:x}:{}},r,S,s);e.markSubmitted(l,i.accessLink.id,I.id);let N={...ce(I,{...S,include:E}),mine:!0},ot=V(t,I.expectedRev,{flowId:I.payload.flowId}),me=Ie(t,t.get(I.id),i.accessLink.id);d.status(201).json({ok:!0,proposal:N,sha256:(0,Oi.createHash)("sha256").update(Buffer.from(f,"utf8")).digest("hex"),...Ne(me),...Q(ot),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 gt){C(d,p.statusCode,p.message,p.code,Se(p.code),{proposalId:p.proposalId});return}if(p instanceof ht||p instanceof D){C(d,p.statusCode,p.message,p.code,Se(p.code));return}if(p instanceof SyntaxError){C(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(Di,"getLinkDraftSubmitHandler");K();Fe();gr();Xe();Ae();pr();var Pu={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 se(e,"get",{cause:t})??Pu[e]??Cn(e)}a(Bo,"proposeErrorHint");function Ru(e,t,n){e.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:t,currentRev:n,hint:Bo("version_mismatch")})}a(Ru,"sendVersionMismatch");async function _u(e,t,n,r,o){let s=Rn(Pn(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 pt(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(_u,"buildFromPatch");async function Lu(e,t,n,r,o){let s=await Tn(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(Lu,"buildFromSetProps");async function Cu(e,t,n,r,o,s,i,d){let l=await pt(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(Cu,"buildFromReplaceText");function Eu(e){let t=Object.prototype.hasOwnProperty.call(e.query,"replace");return{flowId:_(e.query,"flow"),rev:_(e.query,"rev"),nodeId:_(e.query,"node"),patchEncoded:_(e.query,"patch"),setProps:_o(e.query),findText:_(e.query,"find"),replaceText:t?_(e.query,"replace")??"":void 0,fieldName:_(e.query,"field"),occurrenceRaw:_(e.query,"occurrence")}}a(Eu,"readProposeQuery");function Te(e,t,n){C(e,400,n,t,Bo(t))}a(Te,"jsonProposeError");function Iu(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(Iu,"parseOccurrenceQuery");async function Nu(e,t,n,r,o){if(n.patchEncoded)return _u(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=Iu(e,n.occurrenceRaw);return i.ok?Cu(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):Lu(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(Nu,"buildProposeFromQuery");function ji(e,t,n,r,o){return async(s,i)=>{if(en(s,i))return;F(i);let d=Eu(s),l=_(s.query,"summary"),c=_(s.query,"title")??l;if(!d.rev){Te(i,"missing_expected_rev","rev query parameter is required");return}if(!s.accessLink){C(i,401,"Access link required","access_link_missing");return}try{let u=await Nu(i,n,d,d.rev,o);if(!u)return;if(!u.ok){Ru(i,u.expectedRev,u.currentRev);return}let p=q(s,r),f=re(s.query),m=await Ye(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}),x=Ie(e,e.get(m.id),s.accessLink.id),S=m.warnings;i.status(201).json({ok:!0,proposal:h,...l?{summary:l}:{},...Ne(x),...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=Bo(p.body.code,p.body.cause);i.status(p.statusCode).json({...p.body,...f?{hint:f}:{}});return}$(i,u)}}}a(ji,"getLinkProposeHandler");K();Fe();K();var Fu=50,Hi=40,Ou=20;function $u(e){return Buffer.byteLength(JSON.stringify(e),"utf8")}a($u,"nodeBytes");function Mu(e){let t={id:e.id,type:e.type,bytes:$u(e)};return typeof e.name=="string"&&(t.name=e.name),"wires"in e&&(t.wires=e.wires),t}a(Mu,"summarizeNode");async function xr(e,t){let{rev:n,flow:r}=await xe(e,t),o=r,s=Array.isArray(o.nodes)?o.nodes.map(Mu):[],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(xr,"getFlowSummary");function Ui(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(Ui,"collectTabs");async function Bi(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 Ui(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(Bi,"findNodeInFlows");async function Sr(e,t,n){let r=await Bi(e,t,n);return{rev:r.rev,flowId:r.flowId,node:r.node}}a(Sr,"getNodeById");function Du(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(Du,"parseLineRange");async function Tr(e,t,n,r={}){if(!n)throw new y("Missing field name","missing_field");let o=await Bi(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(`
|
|
948
|
+
`),d=i.length,l=Buffer.byteLength(s,"utf8"),c=Du(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(`
|
|
949
|
+
`);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(Tr,"getNodeField");function ju(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(ju,"stringFields");function Hu(e,t,n){let r=Math.max(0,t-Hi),o=Math.min(e.length,t+n+Hi),s=e.slice(r,o).replace(/\s+/g," ");return r>0&&(s=`\u2026${s}`),o<e.length&&(s=`${s}\u2026`),s}a(Hu,"snippetAround");function Uu(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(Uu,"lineNumberAtIndex");function qo(e,t,n,r,o){for(let{field:s,value:i}of ju(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(`
|
|
1373
950
|
`)){let f=i.split(`
|
|
1374
|
-
`);for(let m=0;m<f.length;m+=1){let g=(f[m]??"").indexOf(n);if(g>=0){let
|
|
1375
|
-
`)){let f=0;for(;f<=i.length;){let m=i.indexOf(n,f);if(m<0)break;u(
|
|
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 () {
|
|
951
|
+
`);for(let m=0;m<f.length;m+=1){let g=(f[m]??"").indexOf(n);if(g>=0){let x=0;for(let S=0;S<m;S+=1)x+=(f[S]?.length??0)+1;u(m+1,x+g)}}}if(n.includes(`
|
|
952
|
+
`)){let f=0;for(;f<=i.length;){let m=i.indexOf(n,f);if(m<0)break;u(Uu(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,Ou),lineCount:p,snippet:Hu(i,Math.max(0,c),n.length)})}return r.length>=o}a(qo,"searchInNode");function qi(e){return[...e.nodes??[],...e.configs??[]]}a(qi,"nodePool");async function Bu(e,t,n,r,o){let s=await xe(e,t);for(let i of qi(s.flow))if(qo(i,t,n,r,o))return{rev:s.rev,truncated:!0};return{rev:s.rev,truncated:!1}}a(Bu,"searchOneFlow");async function qu(e,t,n,r){let o=await pe(e),s=new Set;for(let i of Ui(o.flows))if(i.id)try{let d=await xe(e,i.id);for(let l of qi(d.flow))if(s.add(l.id),qo(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(qo(d,l,t,n,r))return{rev:o.rev,truncated:!0}}return{rev:o.rev,truncated:!1}}a(qu,"searchAllFlows");async function Pr(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):Fu,o=[],s=n.flowId?await Bu(e,n.flowId,t,o,r):await qu(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(Pr,"searchFlows");function Wi(e){if(e===void 0)return!1;let t=e.toLowerCase();return t==="1"||t==="true"||t==="yes"}a(Wi,"isTruthyQueryFlag");function Wu(e,t,n){switch(e){case"node_not_found":case"flow_not_found":case"field_not_found":return n??se(e,t);default:return se(e,t)}}a(Wu,"layeredReadErrorHint");function yt(e){return Oe[e]??"Request failed"}a(yt,"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 Rr(e,t,n,r){let o=O(t,n);if(!o)return!1;let s=o.body.code,i={error:yt(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=Wu(s,n,r);return d&&(i.hint=d),fe(e,o.statusCode,i),!0}a(Rr,"sendMappedError");function _r(e){fe(e,502,{error:yt("upstream_error"),code:"upstream_error"})}a(_r,"sendUpstreamError");function Lr(e,t="rest"){return async(n,r)=>{let o=n.params.id;if(typeof o!="string"||o.length===0){fe(r,400,{error:yt("missing_flow_id"),code:"missing_flow_id"});return}let s=Wi(_(n.query,"summary"));try{if(s){fe(r,200,await xr(e,o));return}fe(r,200,await xe(e,o))}catch(i){if(Rr(r,i,t))return;_r(r)}}}a(Lr,"getFlowOrSummaryHandler");function Cr(e,t="rest"){return async(n,r)=>{let o=n.params.id;if(typeof o!="string"||!o){fe(r,400,{error:yt("missing_node_id"),code:"missing_node_id"});return}let s=_(n.query,"flow");try{fe(r,200,await Sr(e,o,s))}catch(i){if(Rr(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;_r(r)}}}a(Cr,"getNodeHandler");function Er(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:yt("missing_node_id"),code:"missing_node_id"});return}if(typeof s!="string"||!s){fe(r,400,{error:yt("missing_field"),code:"missing_field"});return}let i=_(n.query,"flow"),d=_(n.query,"lines");try{fe(r,200,await Tr(e,o,s,{...i?{flowId:i}:{},...d?{lines:d}:{}}))}catch(l){if(Rr(r,l,t,"Use summary/search to discover node ids and fields."))return;_r(r)}}}a(Er,"getNodeFieldHandler");function Ir(e,t="rest"){return async(n,r)=>{let o=_(n.query,"q");if(!o){fe(r,400,{error:yt("missing_query"),code:"missing_query",hint:t==="get"?"Example: GET \u2026/search?q=TODO&flow=tab1":se("missing_query",t)});return}let s=_(n.query,"flow");try{fe(r,200,await Pr(e,o,s?{flowId:s}:{}))}catch(i){if(Rr(r,i,t))return;_r(r)}}}a(Ir,"searchFlowsHandler");K();ut();var zu={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 En(e){return zu[e]??"Request failed"}a(En,"safeErrorText");function et(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(et,"sendJson");function Wo(e,t){let n=O(t);if(!n)return!1;let r=n.body.code,o={error:En(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."),et(e,n.statusCode,o),!0}a(Wo,"sendMappedError");function Nr(e){return async(t,n)=>{try{let r=_(t.query,"q"),o=await ir(e.nodeTypes,r?{q:r}:{});et(n,200,o)}catch(r){if(Wo(n,r))return;et(n,502,{error:En("upstream_error"),code:"upstream_error"})}}}a(Nr,"listNodeTypesHandler");function Fr(e){return async(t,n)=>{try{let r=t.params.type,o=typeof r=="string"?decodeURIComponent(r):"",s=_(t.query,"help"),i=!(s==="0"||s==="false"||s==="no"||s==="omit"),d=await ct(e.nodeTypes,o,{includeHelp:i});et(n,200,d)}catch(r){if(Wo(n,r))return;et(n,502,{error:En("upstream_error"),code:"upstream_error"})}}}a(Fr,"getNodeTypeHandler");function Or(e){return async(t,n)=>{try{let r=_(t.query,"q");if(!r){et(n,400,{error:En("missing_query"),code:"missing_query",hint:"Pass ?q= to search the Node-RED catalogue (recommend only)."});return}let o=await ar(e.nodeTypes,r,{...e.catalogueFetcher?{catalogueFetcher:e.catalogueFetcher}:{},...e.catalogueUrl?{catalogueUrl:e.catalogueUrl}:{}});et(n,200,o)}catch(r){if(Wo(n,r))return;et(n,502,{error:En("upstream_error"),code:"upstream_error"})}}}a(Or,"searchPaletteHandler");Xe();ve();function tn(e,t){return e[t]??""}a(tn,"lineAt");function In(e,t,n){let r=e[t];return r?r[n]??0:0}a(In,"cell");function zi(e,t,n,r){let o=e[t];o&&(o[n]=r)}a(zi,"setCell");function Ji(e,t){let n=Gi(e),r=Gi(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)tn(n,u)===tn(r,p)?zi(i,u,p,In(i,u+1,p+1)+1):zi(i,u,p,Math.max(In(i,u+1,p),In(i,u,p+1)));let d=[],l=0,c=0;for(;l<o&&c<s;){let u=tn(n,l),p=tn(r,c);u===p?(d.push({op:"equal",text:u}),l+=1,c+=1):In(i,l+1,c)>=In(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:tn(n,l)}),l+=1;for(;c<s;)d.push({op:"add",text:tn(r,c)}),c+=1;return d}a(Ji,"diffLines");function Gi(e){return e.length===0?[]:e.split(`
|
|
953
|
+
`)}a(Gi,"splitLines");Ae();var Xi=new Set(["func","template","info","payload","code","text","format","syntax"]),Gu=new Set(["x","y","w","h","wires","g"]),Ju=new Set(["w","h","g"]);function wt(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(wt,"nodeName");function Ki(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(Ki,"collectNodes");function Dr(e){if(e===void 0)return"undefined";if(e===null||typeof e!="object")return JSON.stringify(e);if(Array.isArray(e))return`[${e.map(Dr).join(",")}]`;let t=e;return`{${Object.keys(t).sort().map(r=>`${JSON.stringify(r)}:${Dr(t[r])}`).join(",")}}`}a(Dr,"stableStringify");function Yi(e,t){return Dr(e)===Dr(t)}a(Yi,"valuesEqual");function jr(e){return typeof e=="string"?e:null}a(jr,"asText");function Ku(e){return/<\/?[a-z][\s\S]*>/i.test(e.slice(0,800))}a(Ku,"looksLikeHtml");function Vu(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:Ku(n)}a(Vu,"isHtmlTemplateField");function Qi(e,t,n){let r=jr(n);if(Xi.has(t)&&r!==null){let o={key:t,kind:"text",value:r};return Vu(e,t,r)&&(o.preview="html"),o}return{key:t,kind:"scalar",value:n===void 0?null:n}}a(Qi,"toPresenceProperty");function Xu(e,t){let n=[];for(let r of[...Object.keys(e)].sort())if(!(Ju.has(r)||r==="id"||r==="z")){if(r==="wires"){let o=Zi(e,t);n.push({key:"wires",kind:"scalar",value:o.length>0?o:e.wires??null});continue}n.push(Qi(e,r,e[r]))}return n}a(Xu,"nodeProperties");function Zi(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?wt(l)??l.type:d})}r.push({port:s,to:i})}),r}a(Zi,"describeWiresStructured");var Yu=new Set(["func","template","code","text"]);function Qu(e,t){let n=[],r=wt(e);r&&n.push({key:"name",kind:"scalar",value:r});let o=Zi(e,t);o.length>0&&n.push({key:"wires",kind:"scalar",value:o});for(let s of Yu){if(!(s in e))continue;let i=jr(e[s]);i===null||i.length===0||n.push(Qi(e,s,i))}return n}a(Qu,"buildKeyFields");function Zu(e,t,n){let r=Ve(e.type),o=wt(e),s=o?` "${o}"`:"",i=t?` in "${t}"`:"",d=n===1?"1 field":`${n} fields`;return`Changes ${r} node${s}${i} (${d})`}a(Zu,"buildModifiedSummary");function ep(e,t,n){let r=Ve(e.type),o=wt(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(ep,"buildPresenceSummary");function $r(e,t,n,r){let o={id:e.id,type:e.type,summary:ep(e,t,n),keyFields:Qu(e,r),properties:Xu(e,r)},s=wt(e);return s!==void 0&&(o.name=s),o}a($r,"toPresenceNode");function tp(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]),r=[];for(let o of[...n].sort()){if(Gu.has(o)||o==="id"||o==="z")continue;let s=e[o],i=t[o];if(Yi(s,i))continue;let d=jr(s),l=jr(i);if(Xi.has(o)&&(d!==null||l!==null)){let c=d??"",u=l??"";r.push({key:o,kind:"text",before:c,after:u,lines:Ji(c,u)});continue}r.push({key:o,kind:"scalar",before:s===void 0?null:s,after:i===void 0?null:i})}return r}a(tp,"compareNodes");function np(e,t){let n=["label","disabled","info","env"],r=[];for(let o of n)Yi(e[o],t[o])||r.push(o);return r}a(np,"compareTabMeta");function Mr(e){return typeof e.label=="string"&&e.label.trim()?e.label.trim():""}a(Mr,"flowLabelOf");function ea(e,t){let n=Ki(t),r=Ki(e),o=Mr(e)||Mr(t),s=Mr(t)||Mr(e),i=[],d=[],l=[];for(let c of e.nodes??[])n.has(c.id)||i.push($r(c,"added",o,r));for(let c of e.configs??[])n.has(c.id)||i.push($r(c,"added",o,r));for(let c of t.nodes??[])r.has(c.id)||d.push($r(c,"removed",s,n));for(let c of t.configs??[])r.has(c.id)||d.push($r(c,"removed",s,n));for(let[c,u]of r){let p=n.get(c);if(!p)continue;let f=tp(p,u);if(f.length>0){let m={id:c,type:u.type||p.type,summary:Zu(u,o||s,f.length),changes:f},h=wt(u)??wt(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:np(t,e),beforeNodeCount:n.size,afterNodeCount:r.size},added:i,removed:d,modified:l}}a(ea,"buildDiffSections");function Vi(e,t,n={}){let r=ea(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(Vi,"buildProposalDiff");async function nn(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 z))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?ea(e.payload.flow,s):void 0;return Vi(e,{rev:e.expectedRev,flow:l},{currentRev:o,compareAgainst:"base",baseRevStale:d,...c?{vsCurrent:c}:{}})}return Vi(e,{rev:o,flow:s},{compareAgainst:"current",baseSnapshotMissing:!0,baseRevStale:d})}a(nn,"computeProposalDiff");Ae();_e();ye();var ta=require("path");function Hr(e){return(0,ta.join)(e.userDir,".ai-access")}a(Hr,"getStorageDir");var rp=["status:read","flows:read","proposals:create","proposals:read-own"];function zo(){return Vt()}a(zo,"readPackageVersion");function op(e,t=Date.now()){let n=Date.parse(e);return Number.isFinite(n)?Math.max(0,Math.ceil((n-t)/1e3)):0}a(op,"secondsRemainingUntil");function sp(e,t=Date.now()){let n=Date.parse(e);return Number.isFinite(n)?Math.max(0,Math.ceil((n-t)/6e4)):0}a(sp,"minutesRemainingUntil");function ip(e,t){let n=de(t),r=qe(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(ip,"baseUrlsForTokenKind");function rn(e,t){let n=Kn(e,t),r=he(e.settings);if(t.auth){let o=t.auth.extra?.user;return{ok:!0,version:zo(),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:zo(),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:sp(t.accessLink.expiresAt,o),secondsRemaining:op(t.accessLink.expiresAt,o),permissions:[...rp],baseUrls:ip(t.accessLink.kind,e.settings.httpAdminRoot)}}}return{ok:!0,version:zo(),adminAuthEnabled:n.adminAuthEnabled,aiAccessEnabled:r.enabled,aiFeaturesEnabled:n.aiFeaturesEnabled,allowWithoutAdminAuth:n.allowWithoutAdminAuth,permission:n.permission,capabilities:n.capabilities,storageDir:Hr(e.settings),defaultLinkTtlSeconds:r.defaultLinkTtlSeconds,maxActiveLinks:r.maxActiveLinks,defaultDeploymentType:r.defaultDeploymentType,caller:{kind:"admin",...n.permission?{permission:n.permission}:{}}}}a(rn,"buildStatus");function na(e,t){let n=t.draftStore??new br,r=t.rateLimiters??Ci(t.rateLimit??e.settings.aiAccess),o=e.httpAdmin,s="/mcp/a/:token",i=[r.ip,Ii(e,t.linkStore),r.token];o.use("/mcp/a",gi()),o.get(s,...i,R((u,p)=>{F(p),p.setHeader("X-Content-Type-Options","nosniff");let f=de(e.settings.httpAdminRoot),m=u.accessLink;if(!m){C(p,401,"Access link required","access_link_missing");return}let h=Si({link:m,openapiPath:`${f}/openapi.yaml`,restApiBase:f,mcpRpcPath:qe(e.settings.httpAdminRoot),publicOrigin:oo(u)});p.status(200).type("text/markdown; charset=utf-8").send(h)})),o.get(`${s}/status`,...i,R((u,p)=>{F(p),p.json(rn(e,u))})),o.get(`${s}/flows`,...i,R(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}C(p,502,f instanceof Error?f.message:String(f),"upstream_error")}})),o.get(`${s}/flows/:id`,...i,R(async(u,p)=>{F(p),await Lr(t.flowAdapter,"get")(u,p)})),o.get(`${s}/nodes/:id`,...i,R(async(u,p)=>{F(p),await Cr(t.flowAdapter,"get")(u,p)})),o.get(`${s}/nodes/:id/field/:name`,...i,R(async(u,p)=>{F(p),await Er(t.flowAdapter,"get")(u,p)})),o.get(`${s}/search`,...i,R(async(u,p)=>{F(p),await Ir(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,R(async(u,p)=>{F(p),await Nr(d)(u,p)})),o.get(`${s}/node-types/:type`,...i,R(async(u,p)=>{F(p),await Fr(d)(u,p)})),o.get(`${s}/palette/search`,...i,R(async(u,p)=>{F(p),await Or(d)(u,p)}));let l=e.settings.httpAdminRoot;ap(o,s,i,t,l),o.get(`${s}/propose`,...i,R(ji(t.proposalStore,t.auditLog,t.flowAdapter,l,t.nodeTypesAdapter))),cp(o,s,i,n,t,l);let c=Fi();return o.all(`${s}/links`,c),o.all(`${s}/links/:id`,c),o.use("/mcp/a",Ni()),n}a(na,"registerGetLinkRoutes");function ap(e,t,n,r,o){e.get(`${t}/proposals`,...n,R(async(s,i)=>{F(i);let d=s.accessLink;if(!d){C(i,401,"Access link required","access_link_missing");return}let l=q(s,o),c=re(s.query),u=mr(s.query),p;try{p=(await pe(r.flowAdapter)).rev}catch{}let f=p?V(r.proposalStore,p):0,m=qt(r.proposalStore,d,l,u);i.json({proposals:m.proposals.map(h=>ue(r.proposalStore,d,h,{...l,include:c},{includeConflicts:!1})),...ft({...m,returned:m.proposals.length}),...Q(f)})})),e.get(`${t}/proposals/:id`,...n,R(async(s,i)=>{F(i);let d=s.params.id,l=s.accessLink;if(typeof d!="string"||!d){C(i,400,"Missing proposal id","missing_proposal_id");return}if(!l){C(i,401,"Access link required","access_link_missing");return}try{let c=q(s,o),u=re(s.query),p;try{p=(await pe(r.flowAdapter)).rev}catch{}let f=De(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}})),dp(e,t,n,r,o),lp(e,t,n,r,o)}a(ap,"registerGetLinkProposalRoutes");function dp(e,t,n,r,o){e.get(`${t}/proposals/:id/withdraw`,...n,R((s,i)=>{F(i);let d=s.params.id,l=s.accessLink;if(typeof d!="string"||!d){C(i,400,"Missing proposal id","missing_proposal_id");return}if(!l){C(i,401,"Access link required","access_link_missing");return}let c=s.query.confirm;if(c!=="1"&&c!=="true"){C(i,400,"Withdraw over GET requires confirm=1","confirm_required",se("confirm_required","get"));return}let u=typeof s.query.reason=="string"?s.query.reason:void 0,p=q(s,o),f=re(s.query);try{let m=Wt(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;C(i,h.statusCode,Oe[g]??"Request failed",g,se(g,"get"));return}C(i,500,"Internal server error","internal_error");return}}))}a(dp,"registerGetLinkWithdrawRoute");function lp(e,t,n,r,o){e.get(`${t}/proposals/:id/diff`,...n,R(async(s,i)=>{F(i);let d=s.params.id,l=s.accessLink;if(typeof d!="string"||!d){C(i,400,"Missing proposal id","missing_proposal_id");return}if(!l){C(i,401,"Access link required","access_link_missing");return}try{let c=De(r.proposalStore,l,d,q(s,o)),u=_(s.query,"format"),p=Yt(u==="json"||u==="md"?void 0:u);if(!p.ok){C(i,400,"Invalid diff format","invalid_diff_format","Valid formats: summary, full (default summary).");return}i.json(Qt(await nn(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(lp,"registerGetLinkDiffRoute");function cp(e,t,n,r,o,s){e.get(`${t}/draft/new`,...n,R($i(r,o.flowAdapter))),e.get(`${t}/draft/:draftId/append`,...n,R(Mi(r))),e.get(`${t}/draft/:draftId/submit`,...n,R(Di(r,o.proposalStore,o.auditLog,o.flowAdapter,s,o.nodeTypesAdapter)))}a(cp,"registerGetLinkDraftRoutes");G();var ra=require("module");function up(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,ra.createRequire)(e)}a(up,"createPluginRequire");function pp(e){try{let r=up()("@node-red/runtime")._?.adminApi?.httpAdmin;if(r&&typeof r.use=="function"&&r!==e.httpAdmin)return r}catch{}return null}a(pp,"resolveEditorHttpAdmin");function oa(e){let t=[e.httpAdmin],n=pp(e);return n&&t.push(n),t}a(oa,"httpAdminAppsForMcpErrors");var sa=Symbol.for("com55.ai-access.mcpJsonErrorHandler");function fp(e){let t=e??"/";for(;t.endsWith("/");)t=t.slice(0,-1);let n=new Set;for(let r of t?["",t]:[""])for(let o of["/mcp","/mcp/rpc"])n.add(r+o),n.add(`${r+o}/`);return r=>n.has(r)}a(fp,"mcpRpcMatcher");function Go(e){return e.includes("/mcp")}a(Go,"isMcpPath");function mp(e){return e.path||e.url||e.originalUrl||""}a(mp,"requestPath");function hp(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(hp,"isJsonParseError");function gp(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(gp,"isPayloadTooLarge");function yp(e){let t=fp(e);return(n,r,o,s)=>{if(o.headersSent){s(n);return}let i=mp(r);if(hp(n)&&Go(i)){if(t(i)){o.status(400).json({jsonrpc:"2.0",error:{code:-32700,message:"Parse error"},id:null});return}o.status(400).json({error:"Invalid JSON body",code:"invalid_json"});return}if(gp(n)&&Go(i)){o.status(413).json({error:`Request body exceeds limit of ${j} bytes`,code:"payload_too_large"});return}if(Go(i)){if(t(i)){o.status(500).json({jsonrpc:"2.0",error:{code:-32603,message:wr(n).error,data:{code:"internal_error"}},id:null});return}o.status(500).json(wr(n));return}s(n)}}a(yp,"mcpJsonErrorHandler");function wp(e,t){let n=e;n[sa]||(n[sa]=!0,e.use(yp(t)))}a(wp,"attachOnce");function ia(e){for(let t of oa(e))wp(t,e.settings.httpAdminRoot)}a(ia,"attachMcpJsonErrorHandlers");Ln();var bt=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"}},vt=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"}},Nn=class extends Error{static{a(this,"AccessLinkValidationError")}code="access_link_invalid";statusCode=400;constructor(t){super(t),this.name="AccessLinkValidationError"}},tt=class extends Error{static{a(this,"AccessLinkTtlError")}code="invalid_ttl";statusCode=400;hint=mt;constructor(t){super(t),this.name="AccessLinkTtlError"}};var kt=require("crypto");Ln();function Jo(e){return e==="get_link"||e==="rest"||e==="mcp"}a(Jo,"isAccessLinkKind");function Ko(e){return e??"get_link"}a(Ko,"resolveAccessLinkKind");function aa(e){return(0,kt.createHash)("sha256").update(e,"utf8").digest("hex")}a(aa,"hashToken");function bp(e,t){let n=Buffer.from(e,"utf8"),r=Buffer.from(t,"utf8");return n.length!==r.length?!1:(0,kt.timingSafeEqual)(n,r)}a(bp,"hashesEqual");function Fn(e){let t={id:e.id,kind:Ko(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(Fn,"toPublic");var At=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,kt.randomBytes)(12).toString("hex")),this.randomToken=t.randomToken??(()=>`aia_${(0,kt.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(Fn)}create(t={}){let n=this.now();if([...this.links.values()].filter(p=>this.isActive(p,n)).length>=this.maxActiveLinks)throw new bt(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 tt("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=Ko(t.kind),d=No(i);if(o>d)throw new tt(Fo(i,d));let l=this.randomId(),c=this.randomToken(),u={id:l,tokenHash:aa(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),{...Fn(u),token:c}}revoke(t){let n=this.links.get(t);if(!n)throw new vt(t);return n.revokedAtMs??=this.now(),Fn(n)}getById(t){if(typeof t!="string"||t.length===0)return null;let n=this.links.get(t);return n?Fn(n):null}validateResult(t){if(typeof t!="string"||t.length===0)return{status:"invalid"};let n=aa(t),r=this.now();for(let o of this.links.values())if(bp(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:Fn(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)}};Ln();ie();ye();function Vo(e,t){return t}a(Vo,"storeForRequest");function la(e){let t=typeof e.user?.username=="string"?e.user.username:void 0;return t?{kind:"admin",username:t}:{kind:"admin"}}a(la,"adminActor");function Ur(e){let t=he(e.settings);return new At({defaultTtlSeconds:t.defaultLinkTtlSeconds,maxActiveLinks:t.maxActiveLinks})}a(Ur,"createConfiguredLinkStore");function ca(e){e.setHeader("Cache-Control","no-store"),e.setHeader("Pragma","no-cache")}a(ca,"noStore");function ua(e,t){return(n,r)=>{let s=Vo(e,t).list();ca(r),r.json({links:s})}}a(ua,"listLinksHandler");function pa(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:mt});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=Vo(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",la(r),{linkId:d.id,label:d.label,expiresAt:d.expiresAt,createdAt:d.createdAt,kind:l,...p!==void 0?{ttlSeconds:p}:{}})}if(ca(o),l==="get_link"){let c=Ps(r,e.settings.httpAdminRoot,d.token);o.status(201).json({...d,url:c});return}o.status(201).json({...d})}catch(d){if(d instanceof bt){o.status(d.statusCode).json({error:d.message,code:d.code});return}if(d instanceof tt){o.status(d.statusCode).json({error:d.message,code:d.code,hint:d.hint});return}if(d instanceof Nn){o.status(d.statusCode).json({error:d.message,code:d.code});return}$(o,d);return}}}a(pa,"createLinkHandler");function fa(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=Vo(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",la(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 vt){o.status(i.statusCode).json({error:i.message,code:i.code});return}$(o,i);return}}}a(fa,"revokeLinkHandler");function vp(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(vp,"ttlSecondsFromLink");function kp(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=vp(l);c!==void 0&&(n.ttlSeconds=c)}return{...e,details:n}}a(kp,"enrichAuditLogEntry");function ma(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=>kp(d,t));r.json({logs:i})}}a(ma,"listLogsHandler");var Xo=require("@modelcontextprotocol/sdk/server/mcp.js"),ha=require("@modelcontextprotocol/sdk/server/streamableHttp.js"),T=require("zod");Ge();G();it();K();Fe();ut();Xe();Ae();function ga(){return"0.2.0-beta.3"}a(ga,"pluginVersion");async function ya(e){try{return(await pe(e)).rev}catch{return}}a(ya,"tryLiveRev");function Z(e){return{content:[{type:"text",text:JSON.stringify(e)}]}}a(Z,"jsonResult");function Yo(e){return e===!0?new Set(["payload","baseFlow"]):new Set}a(Yo,"includeSetFromPayloadFlag");function ee(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(ee,"jsonError");var Ap=new Set(["list_proposals","list_my_proposals"]),xp=new Set(["status","limit","offset","mine","flowId"]);function Sp(e){return!!e&&typeof e=="object"&&typeof e.safeParseAsync=="function"}a(Sp,"isZodLikeSchema");function Tp(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(Ap.has(e)&&xp.has(s))return Bt(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(Tp,"mcpArgumentError");function wa(e){let t=e,n=t.validateToolInput;typeof n=="function"&&(t.validateToolInput=async(r,o,s)=>{let i=r.inputSchema;if(!Sp(i))return n.call(e,r,o,s);let d=await i.safeParseAsync(o??{});if(d.success)return d.data;let l=_n(Tp(s,o,d.error.issues),"mcp"),c=l?{...l.body,statusCode:l.statusCode}:{};throw new Error(JSON.stringify(c))})}a(wa,"installMcpArgumentErrorMapping");function ba(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(rn(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 xr(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 Sr(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 Tr(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 Pr(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 ir(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 ct(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 ar(n.nodeTypesAdapter,o,{...n.catalogueFetcher?{catalogueFetcher:n.catalogueFetcher}:{},...n.catalogueUrl?{catalogueUrl:n.catalogueUrl}:{}}))}catch(s){return ee(s,502)}})}a(ba,"registerReadTools");function va(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 /ai-access dashboard (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 Ye(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=V(n.proposalStore,d.expectedRev,{flowId:d.payload.flowId}),u=Ne(Ie(n.proposalStore,n.proposalStore.get(d.id),o.id));return Z({...l,...u,...Q(c)})}catch(d){return ee(d)}}),Rp(e,n,o,s),_p(e,n,o,s)}a(va,"registerProposalTools");function Pp(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(Ut).optional().describe(`Page size (1\u2013${Ut}). 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=Yo(i.include_payload),l=await ya(t.flowAdapter),c=l?V(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=qt(t.proposalStore,n,r,u);return Z({proposals:f.proposals.map(m=>ue(t.proposalStore,n,m,{...r,include:d},{includeConflicts:!1})),...ft({...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(Pp,"registerListProposalsTools");function Rp(e,t,n,r){Pp(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 ya(t.flowAdapter),d=De(t.proposalStore,n,o,r);return Z(ue(t.proposalStore,n,d,{...r,include:Yo(s)},{...i?{liveRev:i}:{}}))}catch(i){return ee(i)}})}a(Rp,"registerProposalQueryTools");function _p(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=De(t.proposalStore,n,o),d=Yt(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=Qt(await nn(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=Wt(t.proposalStore,t.auditLog,n,o,s,r);return Z(ue(t.proposalStore,n,i,r))}catch(i){return ee(i)}})}a(_p,"registerProposalActionTools");function Lp(e,t,n){let r=new Xo.McpServer({name:"node-red-ai-access",version:ga()});wa(r);let o=n.accessLink;if(!o)throw new Error("Access link required");return ba(r,e,t,n),va(r,e,t,n,o),r}a(Lp,"createAiMcpServer");function Qo(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:Le});return}let s=n.accessLink?Lp(e,t,n):Cp(e,t,n),i=new ha.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(Qo,"mcpRpcPostHandler");function Cp(e,t,n){let r=new Xo.McpServer({name:"node-red-ai-access",version:ga()});if(wa(r),ba(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};va(r,e,t,n,l)}return r}a(Cp,"createOAuthMcpServer");function On(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(On,"mcpRpcMethodNotAllowed");var $n=require("crypto"),sn=require("fs"),ts=require("path"),B=require("@modelcontextprotocol/sdk/server/auth/errors.js"),Ta=require("@modelcontextprotocol/sdk/server/auth/handlers/authorize.js"),ns=require("@modelcontextprotocol/sdk/server/auth/handlers/metadata.js"),Pa=require("@modelcontextprotocol/sdk/server/auth/handlers/register.js"),Ra=require("@modelcontextprotocol/sdk/server/auth/handlers/revoke.js"),_a=require("@modelcontextprotocol/sdk/server/auth/handlers/token.js"),La=require("@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js"),Ca=require("@modelcontextprotocol/sdk/server/auth/router.js"),Ea=ln(require("express"),1);G();_e();function $e(e){return e.replace(/[&<>"']/g,t=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[t]??t)}a($e,"esc");var ka={read:'<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5c-5 0-9 4.5-10 7 1 2.5 5 7 10 7s9-4.5 10-7c-1-2.5-5-7-10-7Zm0 11.5a4.5 4.5 0 1 1 0-9 4.5 4.5 0 0 1 0 9Zm0-2.3a2.2 2.2 0 1 0 0-4.4 2.2 2.2 0 0 0 0 4.4Z"/></svg>',propose:'<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 17.2V20h2.8l8.3-8.3-2.8-2.8L4 17.2Zm13.7-7.6a1 1 0 0 0 0-1.4l-1.9-1.9a1 1 0 0 0-1.4 0l-1.4 1.4 2.8 2.8 1.9-1.9Z"/></svg>'},Ep={read:["Read your flows","View flows, nodes, node types and AI Access logs."],propose:["Propose flow changes","Submit change proposals. Nothing is deployed until you approve it in Node-RED."]},Ip='<svg class="logo" viewBox="0 0 48 48" aria-hidden="true"><rect width="48" height="48" rx="11" fill="#8f0000"/><path d="M11 15h9a4 4 0 0 1 4 4v10a4 4 0 0 0 4 4h9" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round"/><rect x="6" y="11" width="9" height="8" rx="2.5" fill="#fff"/><rect x="33" y="29" width="9" height="8" rx="2.5" fill="#fff"/><circle cx="37.5" cy="16" r="5" fill="none" stroke="#fff" stroke-width="2.4"/><path d="M37.5 13.5v5M35 16h5" stroke="#fff" stroke-width="2.2" stroke-linecap="round"/></svg>',Np=`
|
|
954
|
+
:root{color-scheme:light dark;--bg:#f3f4f6;--card:#fff;--text:#111827;--muted:#4b5563;--line:#e5e7eb;--soft:#f9fafb;--brand:#8f0000;--brand-2:#b91c1c;--ok:#0f766e;--warn-bg:#fff7ed;--warn:#9a3412;--focus:#2563eb}
|
|
955
|
+
@media (prefers-color-scheme:dark){:root{--bg:#0b0f14;--card:#151b23;--text:#e5e7eb;--muted:#9ca3af;--line:#273140;--soft:#1b2330;--brand:#c0262d;--brand-2:#ef4444;--warn-bg:#2a1a0e;--warn:#fdba74;--focus:#60a5fa}}
|
|
956
|
+
*{box-sizing:border-box}
|
|
957
|
+
body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px 16px;background:var(--bg);color:var(--text);font:15px/1.5 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
|
|
958
|
+
.card{width:100%;max-width:440px;background:var(--card);border:1px solid var(--line);border-radius:16px;box-shadow:0 10px 30px rgba(0,0,0,.08);overflow:hidden}
|
|
959
|
+
.head{display:flex;align-items:center;gap:12px;padding:18px 22px;border-bottom:1px solid var(--line)}
|
|
960
|
+
.logo{width:40px;height:40px;flex:none}
|
|
961
|
+
.brand b{display:block;font-size:15px}.brand span{font-size:13px;color:var(--muted)}
|
|
962
|
+
.body{padding:20px 22px 22px}
|
|
963
|
+
h1{font-size:19px;line-height:1.3;margin:0 0 6px}
|
|
964
|
+
.app{font-weight:700;overflow-wrap:anywhere}
|
|
965
|
+
.sub{margin:0 0 14px;color:var(--muted);font-size:14px}
|
|
966
|
+
.host{display:inline-block;font:600 13px ui-monospace,SFMono-Regular,Menlo,monospace;background:var(--soft);border:1px solid var(--line);border-radius:6px;padding:1px 6px;overflow-wrap:anywhere}
|
|
967
|
+
.warn{background:var(--warn-bg);color:var(--warn);border-radius:8px;padding:8px 10px;font-size:13px;margin:0 0 14px}
|
|
968
|
+
.perms{list-style:none;margin:0 0 14px;padding:0;border:1px solid var(--line);border-radius:12px}
|
|
969
|
+
.perms li{display:flex;gap:12px;padding:12px 14px}.perms li+li{border-top:1px solid var(--line)}
|
|
970
|
+
.perms svg{width:22px;height:22px;flex:none;fill:var(--brand-2);margin-top:2px}
|
|
971
|
+
.perms b{display:block;font-size:14px}.perms span{color:var(--muted);font-size:13px}
|
|
972
|
+
.perms li.dropped{opacity:.55}.perms li.dropped b::after{content:" \u2014 not granted";font-weight:400;color:var(--warn)}
|
|
973
|
+
.user{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--muted);margin:0 0 16px}
|
|
974
|
+
.avatar{width:26px;height:26px;border-radius:50%;background:var(--soft);border:1px solid var(--line);display:flex;align-items:center;justify-content:center;font-weight:700;color:var(--text);text-transform:uppercase}
|
|
975
|
+
.status{font-size:14px;color:var(--muted);margin:0 0 14px}.status.error{color:var(--brand-2)}
|
|
976
|
+
.actions{display:flex;gap:10px}
|
|
977
|
+
button{font:inherit;border-radius:10px;padding:10px 16px;cursor:pointer;border:1px solid var(--line);background:var(--card);color:var(--text)}
|
|
978
|
+
button:focus-visible,input:focus-visible,a:focus-visible{outline:3px solid var(--focus);outline-offset:2px}
|
|
979
|
+
button[disabled]{opacity:.6;cursor:default}
|
|
980
|
+
.primary{flex:1;background:var(--brand);border-color:var(--brand);color:#fff;font-weight:600}
|
|
981
|
+
.primary:hover:not([disabled]){background:var(--brand-2)}
|
|
982
|
+
.secondary{flex:none}
|
|
983
|
+
form.login{display:grid;gap:10px;margin:0 0 4px}
|
|
984
|
+
form.login label{display:grid;gap:4px;font-size:13px;color:var(--muted)}
|
|
985
|
+
form.login input{font:inherit;padding:9px 11px;border-radius:10px;border:1px solid var(--line);background:var(--soft);color:var(--text)}
|
|
986
|
+
.foot{padding:12px 22px;border-top:1px solid var(--line);font-size:12px;color:var(--muted);background:var(--soft)}
|
|
987
|
+
a{color:var(--focus)}
|
|
988
|
+
[hidden]{display:none!important}
|
|
989
|
+
@media (max-width:420px){.actions{flex-direction:column-reverse}.secondary{flex:1}}
|
|
990
|
+
`;function Aa(e){let t=e.scopes.map(n=>{let[r,o]=Ep[n]??[n,""];return`<li data-scope="${$e(n)}">${ka[n]??ka.read}<div><b>${$e(r)}</b><span>${$e(o)}</span></div></li>`}).join("");return`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="referrer" content="no-referrer"><title>Authorize MCP client \xB7 Node-RED AI Access</title><style>${Np}</style></head><body><main class="card" data-request="${$e(e.requestId)}" data-token-key="${$e(e.tokenKey)}" data-admin-root="${$e(e.adminRoot)}"><div class="head">${Ip}<div class="brand"><b>Node-RED AI Access</b><span>Authorization request</span></div></div><div class="body"><h1><span class="app">${$e(e.redirectHost)}</span> wants to access your Node-RED</h1><p class="sub">Access tokens will be sent to <span class="host">${$e(e.redirectHost)}</span>. App name (provided by the client, unverified): <b>${$e(e.clientName)}</b></p>${e.loopbackRedirect?'<p class="warn">Warning: this redirect address is reachable only on this machine.</p>':""}<ul class="perms" aria-label="Requested permissions">${t}</ul><p id="status" class="status" role="status" aria-live="polite">Checking your Node-RED session\u2026</p><form id="login" class="login" hidden autocomplete="on"><label>Username<input id="username" name="username" autocomplete="username" required></label><label>Password<input id="password" name="password" type="password" autocomplete="current-password" required></label><div class="actions"><button id="login-submit" class="primary" type="submit">Sign in</button></div></form><p id="grant" class="sub" hidden></p><p id="user" class="user" hidden><span class="avatar" id="avatar" aria-hidden="true"></span><span>Signed in as <b id="username-label"></b></span></p><div class="actions"><button id="deny" class="secondary" type="button" hidden>Deny</button><button id="allow" class="primary" type="button" hidden>Allow access</button><button id="retry" class="secondary" type="button" hidden>Retry</button></div></div><div class="foot">You can revoke this app at any time from the AI Access dashboard (OAuth tab) in Node-RED.</div></main><script nonce="${$e(e.nonce)}">${e.script}</script></body></html>`}a(Aa,"renderConsentPage");var xa=`(function () {
|
|
1377
991
|
var main = document.querySelector('main');
|
|
1378
992
|
var id = main.getAttribute('data-request');
|
|
1379
993
|
var key = main.getAttribute('data-token-key');
|
|
1380
|
-
var
|
|
1381
|
-
var grant =
|
|
1382
|
-
var
|
|
1383
|
-
var
|
|
1384
|
-
var retry = document.getElementById('retry');
|
|
994
|
+
var $ = function (x) { return document.getElementById(x); };
|
|
995
|
+
var status = $('status'), grant = $('grant'), allow = $('allow'), deny = $('deny'), retry = $('retry');
|
|
996
|
+
var login = $('login'), user = $('user');
|
|
997
|
+
var adminRoot = new URL(main.getAttribute('data-admin-root') || '/', window.location.href);
|
|
1385
998
|
var csrf = null;
|
|
1386
999
|
function headers() {
|
|
1387
1000
|
var h = { 'Content-Type': 'application/json', 'Node-RED-API-Version': 'v2' };
|
|
@@ -1392,31 +1005,87 @@ strings you will paste publicly beyond this temporary link.
|
|
|
1392
1005
|
return h;
|
|
1393
1006
|
}
|
|
1394
1007
|
function show(el, on) { el.hidden = !on; }
|
|
1008
|
+
function setStatus(text, isError) {
|
|
1009
|
+
status.textContent = text;
|
|
1010
|
+
status.className = 'status' + (isError ? ' error' : '');
|
|
1011
|
+
show(status, !!text);
|
|
1012
|
+
}
|
|
1013
|
+
function editorLink(prefix, suffix) {
|
|
1014
|
+
status.textContent = '';
|
|
1015
|
+
status.className = 'status';
|
|
1016
|
+
status.append(prefix);
|
|
1017
|
+
var a = document.createElement('a');
|
|
1018
|
+
a.href = adminRoot.href; a.target = '_blank'; a.rel = 'noopener'; a.textContent = 'Node-RED editor';
|
|
1019
|
+
status.append(a, suffix);
|
|
1020
|
+
show(status, true);
|
|
1021
|
+
}
|
|
1022
|
+
function showSignIn() {
|
|
1023
|
+
show(allow, false); show(deny, false); show(user, false); show(grant, false);
|
|
1024
|
+
fetch(new URL('auth/login', adminRoot).href, { headers: { 'Node-RED-API-Version': 'v2' }, credentials: 'same-origin' })
|
|
1025
|
+
.then(function (r) { return r.json(); })
|
|
1026
|
+
.then(function (cfg) {
|
|
1027
|
+
if (cfg && cfg.type === 'credentials') {
|
|
1028
|
+
setStatus('Sign in with your Node-RED account to continue.');
|
|
1029
|
+
show(login, true);
|
|
1030
|
+
$('username').focus();
|
|
1031
|
+
} else {
|
|
1032
|
+
editorLink('Sign in to the ', ' in this browser, then press Retry.');
|
|
1033
|
+
show(retry, true);
|
|
1034
|
+
}
|
|
1035
|
+
})
|
|
1036
|
+
.catch(function () {
|
|
1037
|
+
editorLink('Sign in to the ', ' in this browser, then press Retry.');
|
|
1038
|
+
show(retry, true);
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1395
1041
|
function load() {
|
|
1396
|
-
show(retry, false);
|
|
1397
|
-
|
|
1042
|
+
show(retry, false); show(login, false);
|
|
1043
|
+
setStatus('Checking your Node-RED session\u2026');
|
|
1398
1044
|
fetch('consent/' + encodeURIComponent(id), { headers: headers(), credentials: 'same-origin' })
|
|
1399
1045
|
.then(function (r) { return r.json().catch(function () { return {}; }).then(function (b) { return { r: r, b: b }; }); })
|
|
1400
1046
|
.then(function (x) {
|
|
1401
|
-
if (x.r.status === 401) {
|
|
1402
|
-
|
|
1403
|
-
status.append('Sign in to the ');
|
|
1404
|
-
var a = document.createElement('a');
|
|
1405
|
-
a.href = '../../'; a.target = '_blank'; a.rel = 'noopener'; a.textContent = 'Node-RED editor';
|
|
1406
|
-
status.append(a, ' in this browser, then press Retry.');
|
|
1407
|
-
show(retry, true);
|
|
1408
|
-
return;
|
|
1409
|
-
}
|
|
1410
|
-
if (!x.r.ok) { status.textContent = x.b.error || ('Error ' + x.r.status); return; }
|
|
1047
|
+
if (x.r.status === 401) { showSignIn(); return; }
|
|
1048
|
+
if (!x.r.ok) { setStatus(x.b.error || ('Error ' + x.r.status), true); return; }
|
|
1411
1049
|
csrf = x.b.csrf;
|
|
1412
|
-
|
|
1050
|
+
setStatus('');
|
|
1051
|
+
$('username-label').textContent = String(x.b.user);
|
|
1052
|
+
$('avatar').textContent = String(x.b.user || '?').charAt(0);
|
|
1053
|
+
show(user, true);
|
|
1413
1054
|
var dropped = x.b.requested.filter(function (s) { return x.b.grantable.indexOf(s) < 0; });
|
|
1055
|
+
document.querySelectorAll('.perms li').forEach(function (li) {
|
|
1056
|
+
li.classList.toggle('dropped', dropped.indexOf(li.getAttribute('data-scope')) >= 0);
|
|
1057
|
+
});
|
|
1414
1058
|
grant.textContent = 'Will grant: ' + x.b.grantable.join(', ') +
|
|
1415
|
-
(dropped.length ? ' (
|
|
1059
|
+
(dropped.length ? ' (your Node-RED permission does not allow: ' + dropped.join(', ') + ')' : '');
|
|
1416
1060
|
show(grant, true); show(allow, true); show(deny, true);
|
|
1061
|
+
allow.focus();
|
|
1417
1062
|
})
|
|
1418
|
-
.catch(function (e) {
|
|
1063
|
+
.catch(function (e) { setStatus('Error: ' + e.message, true); show(retry, true); });
|
|
1419
1064
|
}
|
|
1065
|
+
login.addEventListener('submit', function (ev) {
|
|
1066
|
+
ev.preventDefault();
|
|
1067
|
+
var btn = $('login-submit');
|
|
1068
|
+
btn.disabled = true;
|
|
1069
|
+
setStatus('Signing in\u2026');
|
|
1070
|
+
var body = new URLSearchParams({
|
|
1071
|
+
client_id: 'node-red-editor', grant_type: 'password', scope: '*',
|
|
1072
|
+
username: $('username').value, password: $('password').value
|
|
1073
|
+
});
|
|
1074
|
+
fetch(new URL('auth/token', adminRoot).href, {
|
|
1075
|
+
method: 'POST', credentials: 'same-origin',
|
|
1076
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Node-RED-API-Version': 'v2' },
|
|
1077
|
+
body: body.toString()
|
|
1078
|
+
})
|
|
1079
|
+
.then(function (r) { return r.json().catch(function () { return {}; }).then(function (b) { return { r: r, b: b }; }); })
|
|
1080
|
+
.then(function (x) {
|
|
1081
|
+
btn.disabled = false;
|
|
1082
|
+
$('password').value = '';
|
|
1083
|
+
if (!x.r.ok || !x.b.access_token) { setStatus('Sign in failed. Check your username and password.', true); return; }
|
|
1084
|
+
try { localStorage.setItem(key, JSON.stringify(x.b)); } catch (e) {}
|
|
1085
|
+
load();
|
|
1086
|
+
})
|
|
1087
|
+
.catch(function (e) { btn.disabled = false; setStatus('Error: ' + e.message, true); });
|
|
1088
|
+
});
|
|
1420
1089
|
function decide(decision) {
|
|
1421
1090
|
allow.disabled = deny.disabled = true;
|
|
1422
1091
|
fetch('consent/' + encodeURIComponent(id), {
|
|
@@ -1425,16 +1094,367 @@ strings you will paste publicly beyond this temporary link.
|
|
|
1425
1094
|
})
|
|
1426
1095
|
.then(function (r) { return r.json().then(function (b) { return { r: r, b: b }; }); })
|
|
1427
1096
|
.then(function (x) {
|
|
1428
|
-
if (x.r.ok && x.b.redirect) {
|
|
1429
|
-
else
|
|
1097
|
+
if (x.r.ok && x.b.redirect) { setStatus(decision === 'allow' ? 'Access allowed. Returning to the app\u2026' : 'Access denied. Returning to the app\u2026'); window.location.assign(x.b.redirect); }
|
|
1098
|
+
else { setStatus(x.b.error || ('Error ' + x.r.status), true); allow.disabled = deny.disabled = false; }
|
|
1430
1099
|
})
|
|
1431
|
-
.catch(function (e) {
|
|
1100
|
+
.catch(function (e) { setStatus('Error: ' + e.message, true); allow.disabled = deny.disabled = false; });
|
|
1432
1101
|
}
|
|
1433
1102
|
allow.addEventListener('click', function () { decide('allow'); });
|
|
1434
1103
|
deny.addEventListener('click', function () { decide('deny'); });
|
|
1435
1104
|
retry.addEventListener('click', load);
|
|
1436
1105
|
load();
|
|
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`
|
|
1106
|
+
})();`;ye();var nt=a(e=>(0,$n.createHash)("sha256").update(e).digest("hex"),"hash"),St=a(()=>(0,$n.randomBytes)(32).toString("base64url"),"secret"),Fp=36e5,Op=25,$p=5,Mp=500,Sa=a(()=>({clients:[],codes:[],tokens:[]}),"emptyDb"),Br=class{constructor(t,n){this.maxClients=n;this.path=(0,ts.join)(t.root,"oauth.json");try{let r=(0,sn.existsSync)(this.path)?JSON.parse((0,sn.readFileSync)(this.path,"utf8")):Sa();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=Sa()}(0,sn.mkdirSync)(t.root,{recursive:!0,mode:448}),this.prune()}static{a(this,"OAuthStore")}db;path;save(){Rt((0,ts.join)(this.path,".."),this.path,Be(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-Fp).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()>=Op)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:St(),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 os(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(os,"describeOAuthRouteCollision");var ss=a((e,t)=>e.toLowerCase().startsWith(t.toLowerCase()),"startsWithCi");function Zo(e){return e===!1?void 0:`/${typeof e=="string"?e:"/"}`.replace(/\/+/g,"/").replace(/\/$/,"")}a(Zo,"normalizeRoot");function es(e,t){if(e!==void 0){if(!e)return t;if(ss(t,`${e}/`))return t.slice(e.length)}}a(es,"relativePrefix");function Dp(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(Dp,"makeLiveCheck");function jp(e,t){if(!e||!t.length)return[];let n=[];try{e(s=>n.push(s))}catch{return[]}let r=Dp(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=>ss(i,d))||!r(s.z)||o.push({path:i,source:"http-in",nodeId:s.id,nodeName:s.name?s.name:void 0,flowId:s.z})}return o}a(jp,"findHttpInCollisions");var Hp=new Set(["bound dispatch","handle"]),Up=["authorize","token","register","revoke"],Bp=["authorization-server","protected-resource"];function qp(e){let t=e.length;for(;t>0&&e.charCodeAt(t-1)===47;)t--;return e.slice(0,t)}a(qp,"trimTrailingSlashes");function Wp(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(Wp,"dedupe");function Ia(e){let t=Zo(e.httpAdminRoot),n=e.httpNode?Zo(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=es(t,p),m=es(n,p),h=m?jp(e.eachNode,[m]):[],g=new Set(h.map(E=>E.path.toLowerCase())),x=m?rs(e.httpNode,[m]).filter(E=>!(E.source==="route"&&Hp.has(E.layer??"")&&g.has(E.path.toLowerCase()))):[],S=f?rs(e.httpAdmin,[f]):[];return Wp([...S,...x,...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=Zo(m.root),g=qp(m.path);for(let x of p){let S=es(h,x);!S||!l(`${g}${S}`)||f.push({path:x,source:"httpStatic",file:`${g}${S}`})}}},"checkStatic");return u(Up.map(p=>`${r}${p}`),i),u(Bp.map(p=>`${o}${p}`),d),{authorization:i,metadata:d}}a(Ia,"scanOAuthRouteCollisions");function zp(e){let t=a(n=>{try{return e?.[n]}catch{return}},"read");return t("_router")?.stack??t("router")?.stack??[]}a(zp,"routerStack");function Gp(e){let t=e.route?.path;return t?(Array.isArray(t)?t:[t]).map(String):[]}a(Gp,"layerPaths");function Jp(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(Jp,"middlewareHits");function rs(e,t=["/.well-known/oauth-","/mcp/oauth/"]){let n=[];for(let r of zp(e)){if(r.handle?.oauthOwned)continue;let o=Gp(r);for(let s of o)t.some(i=>ss(s,i))&&n.push({path:s,source:"route",layer:r.name??"route"});!o.length&&Jp(r,t)&&n.push({path:t.join(", "),source:"middleware",layer:r.name??"route"}),r.handle?.stack&&n.push(...rs({router:{stack:r.handle.stack}},t))}return n}a(rs,"detectOAuthRouteCollisionDetails");var qr=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(nt(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&&rt(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===nt(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&&rt(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===nt(t)&&o.type==="access"&&o.expires>Date.now());if(!n||n.resource!==rt(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===nt(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=St();return this.clientsStore.data.codes.push({hash:nt(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=rt(this.resource());if(t&&rt(t.href)!==n)throw new B.InvalidGrantError("Resource mismatch");return n}findCode(t,n){let r=this.clientsStore.data.codes.find(o=>o.hash===nt(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=St(),i=Date.now()){let d=St(),l=St(),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:nt(d),type:"access",clientId:t,family:s,scopes:n,resource:r,expires:c+this.accessTtl()*1e3,user:o,rotated:!1},{hash:nt(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 rt(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(rt,"canonicalResource");var on=["read","propose"];function Kp(e){let t=(e??"/").split("/").filter(Boolean).join("/");return t?`/${t}/`:"/"}a(Kp,"consentAdminRoot");var Vp=new Set(["localhost","127.0.0.1","[::1]"]);function xt(e){return typeof e=="string"?e:""}a(xt,"value");function Na(e,t,n,r){let o=a(A=>`${n().replace(/\/$/,"")}${A}`,"abs"),s=a((A,v,w,b)=>{let k=v.url;v.url="/",A(v,w,L=>{v.url=k,b(L)})},"atRoot"),i=new Map,d=a(()=>{let A=Date.now();for(let[v,w]of i)w.expires<=A&&i.delete(v)},"sweep"),l=a(A=>{d();let v=[...i].filter(([,w])=>w.clientId===A);for(;v.length>=$p;)i.delete(v.shift()[0]);return i.size<Mp},"reservePending"),c=a((A,v)=>{A.setHeader("Cache-Control","no-store"),A.setHeader("X-Frame-Options","DENY"),A.setHeader("Referrer-Policy","no-referrer"),A.setHeader("X-Content-Type-Options","nosniff"),A.setHeader("Content-Security-Policy",`default-src 'none'; ${v?`script-src 'nonce-${v}'; `:""}style-src 'unsafe-inline'; connect-src 'self'; form-action 'none'; base-uri 'none'; frame-ancestors 'none'`)},"securityHeaders"),u=Yn(e.settings.httpAdminRoot),p=a(A=>{if(!W(e.settings))return[...on];let v=We(A);return v==="*"?[...on]:v==="read"?["read"]:[]},"grantableFor"),f=a(A=>{let v=A.get("sec-fetch-site");if(v&&v!=="same-origin")return!1;let w=A.get("origin");if(!w)return!0;let b=new Set;try{b.add(new URL(n()).origin)}catch{}let k=A.get("host");return k&&(b.add(`http://${k}`),b.add(`https://${k}`)),b.has(w)},"sameOrigin"),m=a((A,v)=>{d();let w=A.query,b=t.clientsStore.getClient(xt(w.client_id));if(!b){v.status(400).send("Unknown OAuth client");return}let k=xt(w.redirect_uri)||(b.redirect_uris.length===1?b.redirect_uris[0]:"");if(!b.redirect_uris.some(Xr=>(0,Ta.redirectUriMatches)(k,Xr))){v.status(400).send("Unregistered redirect URI");return}if(w.response_type!=="code"||w.code_challenge_method!=="S256"||!/^[A-Za-z0-9_-]{43,128}$/.test(xt(w.code_challenge))){v.status(400).send("Authorization code with PKCE S256 is required");return}let L=[...new Set((xt(w.scope)||"read").split(" ").filter(Boolean))];if(L.some(Xr=>!on.includes(Xr))){v.status(400).send("Unsupported scope");return}let H=xt(w.resource)||r(),M;try{M=rt(H)}catch{v.status(400).send("Invalid resource");return}if(M!==rt(r())){v.status(400).send("Resource mismatch");return}if(!l(b.client_id)){v.status(429).send("Too many pending authorization requests; try again later");return}let Re=St();i.set(Re,{clientId:b.client_id,redirect:k,challenge:xt(w.code_challenge),scopes:L,resource:H,state:xt(w.state),csrf:St(),expires:Date.now()+10*6e4});let Ue=new URL(k).host,Pt=(0,$n.randomBytes)(16).toString("base64");c(v,Pt),v.status(200).type("html").send(Aa({requestId:Re,tokenKey:u,adminRoot:Kp(e.settings.httpAdminRoot),nonce:Pt,redirectHost:Ue,loopbackRedirect:Vp.has(new URL(k).hostname),clientName:b.client_name||"Unnamed client",scopes:L,script:xa}))},"authorizePage"),h=a((A,v,w)=>{c(v),Ea.default.json({limit:"4kb"})(A,v,()=>we(e,"api")(A,v,()=>{try{d();let b=/^\/mcp\/oauth\/consent\/([A-Za-z0-9_-]+)\/?$/.exec(A.path)?.[1]??"",k=i.get(b),L=k&&t.clientsStore.getClient(k.clientId);if(!k||!L){v.status(404).json({error:"Consent request expired or unknown"});return}let H=W(e.settings)&&A.user?.username||"owner",M=k.scopes.filter(Pt=>p(A).includes(Pt));if(!M.length){v.status(403).json({error:"Your Node-RED permissions do not allow granting the requested scopes",grantable:M});return}if(A.method==="GET"){k.approver=H,v.json({client_name:L.client_name??null,redirect_host:new URL(k.redirect).host,requested:k.scopes,grantable:M,user:H,csrf:k.csrf});return}if(A.method!=="POST"){v.status(405).end();return}let Re=A.body??{};if(!f(A)||Re.csrf!==k.csrf||k.approver!==H){v.status(403).json({error:"Consent request verification failed"});return}i.delete(b);let Ue=new URL(k.redirect);if(Re.decision!=="allow")Ue.searchParams.set("error","access_denied");else{try{t.clientsStore.approve(L.client_id)}catch{v.status(409).json({error:"Maximum active OAuth clients reached"});return}let Pt=t.createCode(L,{redirectUri:k.redirect,codeChallenge:k.challenge,scopes:M,resource:new URL(k.resource)},H);Ue.searchParams.set("code",Pt)}k.state&&Ue.searchParams.set("state",k.state),v.json({redirect:Ue.href})}catch(b){w(b)}}))},"consentApi"),g=(0,Pa.clientRegistrationHandler)({clientsStore:t.clientsStore,rateLimit:{windowMs:36e5,max:10,standardHeaders:!0,legacyHeaders:!1}}),x=(0,_a.tokenHandler)({provider:t}),S=(0,Ra.revocationHandler)({provider:t}),E,I="";return{server:a((A,v,w)=>{let b=A.path,k=b.replace(/\/+$/,"").toLowerCase();if(k==="/mcp/oauth/authorize"){A.method==="GET"?m(A,v,w):v.status(405).set("Allow","GET").end();return}if(k.startsWith("/mcp/oauth/consent/")){h(A,v,w);return}if(!["/mcp/oauth/token","/mcp/oauth/register","/mcp/oauth/revoke"].includes(k)){w();return}let L=n();if(L.startsWith("https://")||/^http:\/\/(localhost|127\.0\.0\.1)(:|\/|$)/.test(L)){if(I!==L)try{E=(0,Ca.mcpAuthRouter)({provider:t,issuerUrl:new URL(L),baseUrl:new URL(`${L.replace(/\/$/,"")}/mcp/oauth/`),resourceServerUrl:new URL(r()),scopesSupported:on,clientRegistrationOptions:{rateLimit:{windowMs:36e5,max:10}}}),I=L}catch{E=void 0}if(E){let H=A.url;A.url=A.url.slice(10)||"/",E(A,v,M=>{A.url=H,w(M)});return}}if(b==="/mcp/oauth/token"){s(x,A,v,w);return}if(b==="/mcp/oauth/register"){s(g,A,v,w);return}if(b==="/mcp/oauth/revoke"){s(S,A,v,w);return}w()},"server"),metadata:a((A,v,w)=>{let b=A.path;if(b==="/.well-known/oauth-authorization-server"){s((0,ns.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:on}),A,v,w);return}if(b==="/.well-known/oauth-protected-resource/mcp"||b==="/.well-known/oauth-protected-resource"){s((0,ns.metadataHandler)({resource:rt(r()),authorization_servers:[n()],scopes_supported:on}),A,v,w);return}w()},"metadata"),auth:a((A,v,w)=>{try{let b=v.set.bind(v);v.set=((k,L)=>(k==="WWW-Authenticate"&&typeof L=="string"&&(L=L.replace('scope="read"','scope="read propose"')),typeof k=="string"?b(k,L??""):b(k))),(0,La.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())})(A,v,k=>{v.set=b,w(k)})}catch(b){w(b)}},"auth")}}a(Na,"makeOAuthHandlers");var Mn=require("fs"),an=require("path"),Fa=require("url"),Oa=require("yaml"),Zp={};function Xp(){return typeof __dirname<"u"?__dirname:(0,an.dirname)((0,Fa.fileURLToPath)(Zp.url))}a(Xp,"moduleDir");function $a(){let e=Xp(),t=[(0,an.join)(e,"..","docs","api","openapi.yaml"),(0,an.join)(e,"..","..","docs","api","openapi.yaml"),(0,an.join)(process.cwd(),"docs","api","openapi.yaml")];for(let n of t)if((0,Mn.existsSync)(n))return n;return null}a($a,"resolveOpenApiYamlPath");function Yp(){let e=$a();if(!e)throw new Error("OpenAPI specification file was not found");return(0,Mn.readFileSync)(e,"utf8")}a(Yp,"loadOpenApiYaml");function Ma(){return(e,t)=>{try{let n=Yp();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(Ma,"openApiYamlHandler");var is;function Qp(){let e=$a();if(!e)throw new Error("OpenAPI specification file was not found");return is?.path!==e&&(is={path:e,json:JSON.stringify((0,Oa.parse)((0,Mn.readFileSync)(e,"utf8")))}),is.json}a(Qp,"loadOpenApiJson");function Da(){return(e,t)=>{try{t.status(200).type("application/json").send(Qp())}catch(n){let r=n instanceof Error?n.message:String(n);t.status(500).json({error:r})}}}a(Da,"openApiJsonHandler");function ef(){return`
|
|
1107
|
+
var settingsData = null;
|
|
1108
|
+
var settingsSaving = false;
|
|
1109
|
+
var settingsTimer = null;
|
|
1110
|
+
var SETTINGS_FIELDS = {
|
|
1111
|
+
enabled: 'enabled', allowWithoutAdminAuth: 'allow-without',
|
|
1112
|
+
defaultLinkTtlSeconds: 'ttl', defaultDeploymentType: 'deploy',
|
|
1113
|
+
maxActiveLinks: 'max-links', publicBaseUrl: 'public-url',
|
|
1114
|
+
corsAllowedOrigins: 'cors', rateLimitWindowMs: 'rate-window',
|
|
1115
|
+
rateLimitMaxPerIp: 'rate-ip', rateLimitMaxPerToken: 'rate-token'
|
|
1116
|
+
};
|
|
1117
|
+
var OAUTH_ACCESS_UNITS = [['minutes', 60], ['hours', 3600]];
|
|
1118
|
+
var OAUTH_REFRESH_UNITS = [['hours', 3600], ['days', 86400]];
|
|
1119
|
+
|
|
1120
|
+
function settingsCanEdit() { return !!(settingsData && settingsData.canEdit); }
|
|
1121
|
+
function fieldLocked(key) {
|
|
1122
|
+
return !!(settingsData && settingsData.sources && settingsData.sources[key] === 'settings.js');
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
function setDurationInputs(id, seconds, units) {
|
|
1126
|
+
var unit = units[0];
|
|
1127
|
+
for (var i = units.length - 1; i >= 0; i--) {
|
|
1128
|
+
if (seconds % units[i][1] === 0) { unit = units[i]; break; }
|
|
1129
|
+
}
|
|
1130
|
+
$(id).value = String(Math.max(1, Math.round(seconds / unit[1])));
|
|
1131
|
+
$(id + '-unit').value = unit[0];
|
|
1132
|
+
}
|
|
1133
|
+
function readDurationInputs(id, units) {
|
|
1134
|
+
var amount = Number($(id).value);
|
|
1135
|
+
var name = $(id + '-unit') ? $(id + '-unit').value : units[0][0];
|
|
1136
|
+
var mult = units[0][1];
|
|
1137
|
+
units.forEach(function (u) { if (u[0] === name) mult = u[1]; });
|
|
1138
|
+
return Math.round(amount * mult);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
function trustProxyText(tp) {
|
|
1142
|
+
return tp === false || tp === undefined || tp === null || tp === 0
|
|
1143
|
+
? 'Trust proxy: off (set httpServerOptions["trust proxy"] in settings.js if behind a reverse proxy). Rate limits key on the socket address.'
|
|
1144
|
+
: 'Trust proxy: ' + (tp === true ? 'on' : String(tp)) + '. Rate limits key on the forwarded client address.';
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
function mcpServerUrl() {
|
|
1148
|
+
var s = (settingsData && settingsData.settings) || {};
|
|
1149
|
+
var base = s.publicBaseUrl ? String(s.publicBaseUrl).replace(/\\/$/, '') : location.origin;
|
|
1150
|
+
return base + mcpRpc.replace(/\\/rpc$/, '');
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
function applySettings(data) {
|
|
1154
|
+
settingsData = data;
|
|
1155
|
+
var s = data.settings || {};
|
|
1156
|
+
var ro = !data.canEdit;
|
|
1157
|
+
var adminAuth = !!data.adminAuthEnabled;
|
|
1158
|
+
$('settings-readonly').hidden = !ro;
|
|
1159
|
+
$('row-enabled').hidden = !adminAuth;
|
|
1160
|
+
$('row-allow-without').hidden = adminAuth;
|
|
1161
|
+
$('set-enabled').checked = s.enabled !== false;
|
|
1162
|
+
$('set-allow-without').checked = !!s.allowWithoutAdminAuth;
|
|
1163
|
+
$('set-deploy').value = s.defaultDeploymentType || 'nodes';
|
|
1164
|
+
$('set-deploy-full-warn').hidden = $('set-deploy').value !== 'full';
|
|
1165
|
+
$('set-ttl').value = String(Math.max(1, Math.round((s.defaultLinkTtlSeconds || 14400) / 3600)));
|
|
1166
|
+
$('set-max-links').value = s.maxActiveLinks == null ? '' : String(s.maxActiveLinks);
|
|
1167
|
+
$('set-public-url').value = s.publicBaseUrl || '';
|
|
1168
|
+
$('set-cors').value = (s.corsAllowedOrigins || []).join(', ');
|
|
1169
|
+
$('set-rate-window').value = s.rateLimitWindowMs == null ? '' : String(s.rateLimitWindowMs);
|
|
1170
|
+
$('set-rate-ip').value = s.rateLimitMaxPerIp == null ? '' : String(s.rateLimitMaxPerIp);
|
|
1171
|
+
$('set-rate-token').value = s.rateLimitMaxPerToken == null ? '' : String(s.rateLimitMaxPerToken);
|
|
1172
|
+
var tpText = trustProxyText(data.trustProxy);
|
|
1173
|
+
$('set-trust-proxy').textContent = tpText;
|
|
1174
|
+
$('set-trust-proxy-inline').textContent = tpText;
|
|
1175
|
+
Object.keys(SETTINGS_FIELDS).forEach(function (key) {
|
|
1176
|
+
var id = SETTINGS_FIELDS[key];
|
|
1177
|
+
var locked = fieldLocked(key);
|
|
1178
|
+
var input = $('set-' + id);
|
|
1179
|
+
if (input) input.disabled = ro || locked;
|
|
1180
|
+
var badge = $('lock-' + id);
|
|
1181
|
+
if (badge) badge.hidden = !locked;
|
|
1182
|
+
});
|
|
1183
|
+
$('set-check-public-url').disabled = ro;
|
|
1184
|
+
|
|
1185
|
+
var o = s.oauth || {};
|
|
1186
|
+
var oauthLocked = fieldLocked('oauth');
|
|
1187
|
+
$('oauth-enabled').checked = !!o.enabled;
|
|
1188
|
+
setDurationInputs('oauth-access-ttl', o.accessTokenTtlSec || 3600, OAUTH_ACCESS_UNITS);
|
|
1189
|
+
setDurationInputs('oauth-refresh-ttl', o.refreshIdleTtlSec || 2592000, OAUTH_REFRESH_UNITS);
|
|
1190
|
+
$('oauth-refresh-max').value = String(Math.round((typeof o.refreshMaxLifetimeSec === 'number' ? o.refreshMaxLifetimeSec : 7776000) / 86400));
|
|
1191
|
+
$('oauth-dcr-cap').value = String(o.dcrMaxClients || 5);
|
|
1192
|
+
['oauth-enabled', 'oauth-access-ttl', 'oauth-access-ttl-unit', 'oauth-refresh-ttl', 'oauth-refresh-ttl-unit',
|
|
1193
|
+
'oauth-refresh-max', 'oauth-dcr-cap'].forEach(function (id) { $(id).disabled = ro || oauthLocked; });
|
|
1194
|
+
$('lock-oauth').hidden = !oauthLocked;
|
|
1195
|
+
$('oauth-server-url').textContent = mcpServerUrl();
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
function oauthEnableWarning(value) {
|
|
1199
|
+
if (!value) return 'No Public URL is configured. OAuth clients may not discover this server.';
|
|
1200
|
+
try {
|
|
1201
|
+
var url = new URL(value);
|
|
1202
|
+
var host = url.hostname.toLowerCase().replace(/\\.$/, '');
|
|
1203
|
+
if (url.protocol === 'https:' || host === 'localhost' || host === '127.0.0.1' || host === '[::1]') return '';
|
|
1204
|
+
return 'The Public URL does not use HTTPS. OAuth tokens and redirects may be exposed.';
|
|
1205
|
+
} catch (e) {
|
|
1206
|
+
return 'The Public URL is invalid. OAuth clients may not discover this server.';
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
function collectSettingsPayload() {
|
|
1211
|
+
var payload = {
|
|
1212
|
+
enabled: $('set-enabled').checked,
|
|
1213
|
+
allowWithoutAdminAuth: $('set-allow-without').checked,
|
|
1214
|
+
defaultLinkTtlSeconds: Math.max(1, Math.floor(Number($('set-ttl').value || 4) * 3600)),
|
|
1215
|
+
defaultDeploymentType: $('set-deploy').value,
|
|
1216
|
+
maxActiveLinks: Number($('set-max-links').value),
|
|
1217
|
+
publicBaseUrl: String($('set-public-url').value).trim(),
|
|
1218
|
+
corsAllowedOrigins: String($('set-cors').value).split(',').map(function (x) { return x.trim(); }).filter(Boolean),
|
|
1219
|
+
rateLimitWindowMs: Number($('set-rate-window').value),
|
|
1220
|
+
rateLimitMaxPerIp: Number($('set-rate-ip').value),
|
|
1221
|
+
rateLimitMaxPerToken: Number($('set-rate-token').value),
|
|
1222
|
+
oauth: {
|
|
1223
|
+
enabled: $('oauth-enabled').checked,
|
|
1224
|
+
accessTokenTtlSec: readDurationInputs('oauth-access-ttl', OAUTH_ACCESS_UNITS),
|
|
1225
|
+
refreshIdleTtlSec: readDurationInputs('oauth-refresh-ttl', OAUTH_REFRESH_UNITS),
|
|
1226
|
+
refreshMaxLifetimeSec: Math.round(Number($('oauth-refresh-max').value) * 86400),
|
|
1227
|
+
dcrMaxClients: Number($('oauth-dcr-cap').value)
|
|
1228
|
+
}
|
|
1229
|
+
};
|
|
1230
|
+
Object.keys(payload).forEach(function (key) { if (fieldLocked(key)) delete payload[key]; });
|
|
1231
|
+
// Persist the max lifetime only when changed, so the default can evolve.
|
|
1232
|
+
var loaded = (settingsData && settingsData.settings && settingsData.settings.oauth) || {};
|
|
1233
|
+
var loadedMax = typeof loaded.refreshMaxLifetimeSec === 'number' ? loaded.refreshMaxLifetimeSec : 7776000;
|
|
1234
|
+
if (payload.oauth && Math.round(payload.oauth.refreshMaxLifetimeSec / 86400) === Math.round(loadedMax / 86400)) {
|
|
1235
|
+
delete payload.oauth.refreshMaxLifetimeSec;
|
|
1236
|
+
}
|
|
1237
|
+
return payload;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
`}a(ef,"settingsPartForm");function tf(){return`
|
|
1241
|
+
function settingsFlash(msg, ok) {
|
|
1242
|
+
var tab = document.querySelector('nav.tabs button[aria-selected="true"]');
|
|
1243
|
+
var target = tab && tab.getAttribute('data-tab') === 'oauth' ? 'oauth-flash' : 'settings-flash';
|
|
1244
|
+
if (ok) showToast(msg, true);
|
|
1245
|
+
else showFlash($(target), msg, false);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
async function persistSettings(payload) {
|
|
1249
|
+
settingsSaving = true;
|
|
1250
|
+
try {
|
|
1251
|
+
var data = await api('/settings', { method: 'PUT', body: JSON.stringify(payload) });
|
|
1252
|
+
applySettings(data);
|
|
1253
|
+
var warn = data.warnings && data.warnings.length ? data.warnings.join(' ') : '';
|
|
1254
|
+
$('set-public-url-warning').textContent = warn;
|
|
1255
|
+
$('set-public-url-warning').hidden = !warn;
|
|
1256
|
+
showFlash($('settings-flash'), '', true);
|
|
1257
|
+
showFlash($('oauth-flash'), '', true);
|
|
1258
|
+
settingsFlash('Settings saved.', true);
|
|
1259
|
+
refreshStatusQuiet();
|
|
1260
|
+
loadOAuthStatus();
|
|
1261
|
+
} catch (e) {
|
|
1262
|
+
settingsFlash(e.message || String(e), false);
|
|
1263
|
+
loadSettings();
|
|
1264
|
+
} finally {
|
|
1265
|
+
settingsSaving = false;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
function saveSettingsNow() {
|
|
1270
|
+
if (!settingsCanEdit() || settingsSaving) return;
|
|
1271
|
+
var payload = collectSettingsPayload();
|
|
1272
|
+
var was = settingsData && settingsData.settings && settingsData.settings.oauth && settingsData.settings.oauth.enabled;
|
|
1273
|
+
if (!was && payload.oauth && payload.oauth.enabled) {
|
|
1274
|
+
var url = payload.publicBaseUrl === undefined ? settingsData.settings.publicBaseUrl : payload.publicBaseUrl;
|
|
1275
|
+
var warning = oauthEnableWarning(url);
|
|
1276
|
+
if (warning) {
|
|
1277
|
+
$('oauth-enabled').checked = false;
|
|
1278
|
+
openConfirmDialog({
|
|
1279
|
+
title: 'Enable MCP OAuth?',
|
|
1280
|
+
message: warning + ' Enable OAuth anyway?',
|
|
1281
|
+
confirmLabel: 'Enable anyway',
|
|
1282
|
+
onConfirm: function () { $('oauth-enabled').checked = true; persistSettings(payload); }
|
|
1283
|
+
});
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
persistSettings(payload);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
function scheduleSettingsSave() {
|
|
1291
|
+
if (!settingsCanEdit()) return;
|
|
1292
|
+
if (settingsTimer) clearTimeout(settingsTimer);
|
|
1293
|
+
settingsTimer = setTimeout(function () { settingsTimer = null; saveSettingsNow(); }, 400);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
async function loadSettings() {
|
|
1297
|
+
try {
|
|
1298
|
+
applySettings(await api('/settings'));
|
|
1299
|
+
} catch (e) {
|
|
1300
|
+
settingsFlash(e.message || String(e), false);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
async function refreshStatusQuiet() {
|
|
1305
|
+
try { applyStatus(await api('/status')); } catch (e) { /* ignore */ }
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
function setCheckResult(state, text) {
|
|
1309
|
+
var el = $('set-check-result');
|
|
1310
|
+
el.className = 'check-result' + (state ? ' is-' + state : '');
|
|
1311
|
+
el.textContent = text || '';
|
|
1312
|
+
el.hidden = !text;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
async function checkPublicUrl() {
|
|
1316
|
+
setCheckResult('pending', 'Checking\u2026');
|
|
1317
|
+
try {
|
|
1318
|
+
var r = await api('/settings/check-public-url', {
|
|
1319
|
+
method: 'POST', body: JSON.stringify({ publicBaseUrl: $('set-public-url').value })
|
|
1320
|
+
});
|
|
1321
|
+
setCheckResult(r.ok ? 'ok' : 'fail', (r.ok ? '\u2713 Reachable: ' : '\u2717 Failed: ') + (r.message || r.error || '') +
|
|
1322
|
+
(r.status ? ' (HTTP ' + r.status + ')' : ''));
|
|
1323
|
+
} catch (e) {
|
|
1324
|
+
setCheckResult('fail', '\u2717 ' + (e.message || String(e)));
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
`}a(tf,"settingsPartSave");function nf(){return`
|
|
1329
|
+
function editorNodeHref(c) {
|
|
1330
|
+
var base = editorRoot && editorRoot !== '/' ? editorRoot.replace(/\\/$/, '') + '/' : '/';
|
|
1331
|
+
if (c.nodeId) return base + '#node/' + encodeURIComponent(c.nodeId);
|
|
1332
|
+
if (c.flowId) return base + '#flow/' + encodeURIComponent(c.flowId);
|
|
1333
|
+
return '';
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
function collisionHtml(c) {
|
|
1337
|
+
var effect = c.kind === 'authorization' ? ' \u2014 OAuth login/token routes disabled' : ' \u2014 OAuth metadata disabled';
|
|
1338
|
+
var path = '<code>' + esc(c.path || '') + '</code>';
|
|
1339
|
+
var html;
|
|
1340
|
+
if (c.source === 'text') return esc(c.text || '');
|
|
1341
|
+
if (c.source === 'http-in') {
|
|
1342
|
+
var label = "'" + esc(c.nodeName || c.nodeId || 'unnamed') + "'";
|
|
1343
|
+
var href = editorNodeHref(c);
|
|
1344
|
+
html = 'Route ' + path + ' is already used by http-in node ' +
|
|
1345
|
+
(href ? '<a href="' + esc(href) + '" target="_blank" rel="noopener" title="Open node in the editor">' + label + '</a>' : label);
|
|
1346
|
+
} else if (c.source === 'httpStatic') {
|
|
1347
|
+
html = 'File ' + path + ' is served by httpStatic';
|
|
1348
|
+
} else if (c.source === 'middleware') {
|
|
1349
|
+
html = 'Path ' + path + ' is already handled by other middleware (' + esc(c.layer || 'unknown') + ')';
|
|
1350
|
+
} else {
|
|
1351
|
+
html = 'Route ' + path + ' is already registered by another node or plugin';
|
|
1352
|
+
}
|
|
1353
|
+
return html + esc(effect);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
function renderOAuthRouteWarning(status) {
|
|
1357
|
+
var box = $('oauth-route-warning');
|
|
1358
|
+
var details = status.details;
|
|
1359
|
+
if (!details) {
|
|
1360
|
+
details = [];
|
|
1361
|
+
(status.authorization || []).forEach(function (t) { details.push({ kind: 'authorization', source: 'text', text: t }); });
|
|
1362
|
+
(status.metadata || []).forEach(function (t) { details.push({ kind: 'metadata', source: 'text', text: t }); });
|
|
1363
|
+
}
|
|
1364
|
+
var html = '';
|
|
1365
|
+
if (status.httpNodeRootFallback) {
|
|
1366
|
+
html += '<p>httpNodeRoot is disabled: OAuth metadata is served under the admin /mcp path.</p>';
|
|
1367
|
+
}
|
|
1368
|
+
if (details.length) {
|
|
1369
|
+
html += '<h2>OAuth route conflict</h2><p>These paths are already in use, so AI Access will not serve them. ' +
|
|
1370
|
+
'Remove or change the conflicting routes, then deploy.</p><ul class="conflict-list">' +
|
|
1371
|
+
details.map(function (c) { return '<li>' + collisionHtml(c) + '</li>'; }).join('') + '</ul>';
|
|
1372
|
+
}
|
|
1373
|
+
box.innerHTML = html;
|
|
1374
|
+
box.hidden = !html;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
async function loadOAuthStatus() {
|
|
1378
|
+
try {
|
|
1379
|
+
renderOAuthRouteWarning(await api('/oauth/status'));
|
|
1380
|
+
} catch (e) {
|
|
1381
|
+
var box = $('oauth-route-warning');
|
|
1382
|
+
box.textContent = 'OAuth route status unavailable: ' + (e.message || String(e));
|
|
1383
|
+
box.hidden = false;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
async function loadOAuthClients() {
|
|
1388
|
+
var list = $('oauth-clients');
|
|
1389
|
+
try {
|
|
1390
|
+
var data = await api('/oauth/clients');
|
|
1391
|
+
var clients = (data && data.clients) || [];
|
|
1392
|
+
if (!clients.length) {
|
|
1393
|
+
list.innerHTML = '<p class="muted">No registered OAuth clients.</p>';
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
var canRevoke = settingsCanEdit();
|
|
1397
|
+
list.innerHTML = clients.map(function (c, i) {
|
|
1398
|
+
return '<div class="client-row"><div><strong>' + esc(c.client_name || 'Unnamed client') + '</strong>' +
|
|
1399
|
+
'<div class="client-meta"><code>' + esc(c.client_id) + '</code></div></div>' +
|
|
1400
|
+
'<button type="button" class="action danger" data-oauth-revoke="' + i + '"' + (canRevoke ? '' : ' disabled') +
|
|
1401
|
+
'>Revoke</button></div>';
|
|
1402
|
+
}).join('');
|
|
1403
|
+
list.querySelectorAll('[data-oauth-revoke]').forEach(function (btn) {
|
|
1404
|
+
btn.addEventListener('click', function () {
|
|
1405
|
+
var c = clients[Number(btn.getAttribute('data-oauth-revoke'))];
|
|
1406
|
+
if (!c) return;
|
|
1407
|
+
openConfirmDialog({
|
|
1408
|
+
title: 'Revoke OAuth client',
|
|
1409
|
+
message: 'Revoke OAuth client "' + (c.client_name || c.client_id) + '" and all of its tokens?',
|
|
1410
|
+
confirmLabel: 'Revoke',
|
|
1411
|
+
danger: true,
|
|
1412
|
+
onConfirm: async function () {
|
|
1413
|
+
try {
|
|
1414
|
+
await api('/oauth/clients/' + encodeURIComponent(c.client_id), { method: 'DELETE' });
|
|
1415
|
+
showToast('OAuth client revoked.', true);
|
|
1416
|
+
loadOAuthClients();
|
|
1417
|
+
} catch (e) {
|
|
1418
|
+
showFlash($('oauth-flash'), e.message || String(e), false);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
});
|
|
1422
|
+
});
|
|
1423
|
+
});
|
|
1424
|
+
} catch (e) {
|
|
1425
|
+
list.textContent = 'OAuth clients unavailable: ' + (e.message || String(e));
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
`}a(nf,"settingsPartOAuth");function rf(){return`
|
|
1430
|
+
function loadOAuthTab() {
|
|
1431
|
+
loadSettings();
|
|
1432
|
+
loadOAuthStatus();
|
|
1433
|
+
loadOAuthClients();
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
(function wireSettings() {
|
|
1437
|
+
var sec = $('section-settings');
|
|
1438
|
+
var oauthSec = $('section-oauth');
|
|
1439
|
+
[sec, oauthSec].forEach(function (host) {
|
|
1440
|
+
if (!host) return;
|
|
1441
|
+
host.querySelectorAll('input, select').forEach(function (el) {
|
|
1442
|
+
if (el.id === 'set-public-url') return;
|
|
1443
|
+
el.addEventListener('change', scheduleSettingsSave);
|
|
1444
|
+
});
|
|
1445
|
+
});
|
|
1446
|
+
$('set-public-url').addEventListener('change', scheduleSettingsSave);
|
|
1447
|
+
$('set-deploy').addEventListener('change', function () {
|
|
1448
|
+
$('set-deploy-full-warn').hidden = $('set-deploy').value !== 'full';
|
|
1449
|
+
});
|
|
1450
|
+
$('set-check-public-url').addEventListener('click', checkPublicUrl);
|
|
1451
|
+
$('oauth-refresh').addEventListener('click', function () { loadOAuthStatus(); loadOAuthClients(); });
|
|
1452
|
+
$('oauth-copy-url').addEventListener('click', function () {
|
|
1453
|
+
copyTextToClipboard($('oauth-server-url').textContent || '');
|
|
1454
|
+
showToast('Server URL copied.', true);
|
|
1455
|
+
});
|
|
1456
|
+
})();
|
|
1457
|
+
`}a(rf,"settingsPartWire");function ja(){return ef()+tf()+nf()+rf()}a(ja,"pageClientSettingsScript");function sf(){return`
|
|
1438
1458
|
(function () {
|
|
1439
1459
|
var root = document.getElementById('mcp-root');
|
|
1440
1460
|
if (!root) return;
|
|
@@ -1505,7 +1525,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
1505
1525
|
var gate = $('auth-gate');
|
|
1506
1526
|
var nav = $('main-nav');
|
|
1507
1527
|
if (nav) nav.hidden = true;
|
|
1508
|
-
['section-status', 'section-links', 'section-proposals', 'section-logs'].forEach(function (id) {
|
|
1528
|
+
['section-status', 'section-settings', 'section-links', 'section-oauth', 'section-proposals', 'section-logs'].forEach(function (id) {
|
|
1509
1529
|
var el = $(id);
|
|
1510
1530
|
if (el) el.hidden = true;
|
|
1511
1531
|
});
|
|
@@ -1515,7 +1535,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
1515
1535
|
if (ed) ed.setAttribute('href', editorLoginUrl());
|
|
1516
1536
|
var ret = $('auth-gate-return');
|
|
1517
1537
|
if (ret) {
|
|
1518
|
-
ret.setAttribute('href', (typeof location !== 'undefined' ? location.pathname : '/
|
|
1538
|
+
ret.setAttribute('href', (typeof location !== 'undefined' ? location.pathname : (cfg.dashboardPath || '/ai-access')) +
|
|
1519
1539
|
(typeof location !== 'undefined' ? location.search : '') +
|
|
1520
1540
|
(typeof location !== 'undefined' ? location.hash : ''));
|
|
1521
1541
|
}
|
|
@@ -1655,7 +1675,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
1655
1675
|
}
|
|
1656
1676
|
return body;
|
|
1657
1677
|
}
|
|
1658
|
-
`}a(
|
|
1678
|
+
`}a(sf,"pageClientScriptPartA");function af(){return`
|
|
1659
1679
|
// Time helpers \u2014 keep in sync with page-time.ts (always browser-local Intl).
|
|
1660
1680
|
function titleFromDisplayName(displayName) {
|
|
1661
1681
|
var s = String(displayName || '');
|
|
@@ -1762,7 +1782,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
1762
1782
|
btn.disabled = false;
|
|
1763
1783
|
}, 1600);
|
|
1764
1784
|
}
|
|
1765
|
-
`}a(
|
|
1785
|
+
`}a(af,"pageClientScriptPartATimeCopy");function df(){return`
|
|
1766
1786
|
function shortId(value, keep) {
|
|
1767
1787
|
var s = String(value == null ? '' : value);
|
|
1768
1788
|
var n = keep || 8;
|
|
@@ -1892,7 +1912,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
1892
1912
|
}
|
|
1893
1913
|
return '';
|
|
1894
1914
|
}
|
|
1895
|
-
`}a(
|
|
1915
|
+
`}a(df,"pageClientScriptPartAFormat");function lf(){return`
|
|
1896
1916
|
function lookupCachedProposal(id) {
|
|
1897
1917
|
if (!id) return null;
|
|
1898
1918
|
return cachedProposals.filter(function (x) { return x.id === id; })[0] || null;
|
|
@@ -2013,11 +2033,12 @@ strings you will paste publicly beyond this temporary link.
|
|
|
2013
2033
|
return k + '=' + String(v);
|
|
2014
2034
|
}).join(', '));
|
|
2015
2035
|
}
|
|
2016
|
-
`}a(
|
|
2036
|
+
`}a(lf,"pageClientScriptPartAAudit");function cf(){return`
|
|
2017
2037
|
// Hash helpers \u2014 keep in sync with page-hash.ts
|
|
2038
|
+
var TAB_IDS = ['status', 'settings', 'links', 'oauth', 'proposals'];
|
|
2018
2039
|
function hashForTab(tab) {
|
|
2019
2040
|
if (tab === 'logs') return 'audit';
|
|
2020
|
-
if (tab
|
|
2041
|
+
if (TAB_IDS.indexOf(tab) >= 0) return tab;
|
|
2021
2042
|
return 'status';
|
|
2022
2043
|
}
|
|
2023
2044
|
|
|
@@ -2025,7 +2046,9 @@ strings you will paste publicly beyond this temporary link.
|
|
|
2025
2046
|
var raw = String(hash || '').replace(/^#/, '');
|
|
2026
2047
|
if (!raw || raw.indexOf('proposal=') === 0) return '';
|
|
2027
2048
|
if (raw === 'audit' || raw === 'logs') return 'logs';
|
|
2028
|
-
if (raw === '
|
|
2049
|
+
if (raw === 'overview') return 'status';
|
|
2050
|
+
if (raw === 'tokens') return 'links';
|
|
2051
|
+
if (TAB_IDS.indexOf(raw) >= 0) return raw;
|
|
2029
2052
|
return '';
|
|
2030
2053
|
}
|
|
2031
2054
|
|
|
@@ -2085,6 +2108,8 @@ strings you will paste publicly beyond this temporary link.
|
|
|
2085
2108
|
}
|
|
2086
2109
|
|
|
2087
2110
|
function loadTabData(target) {
|
|
2111
|
+
if (target === 'settings') loadSettings();
|
|
2112
|
+
if (target === 'oauth') loadOAuthTab();
|
|
2088
2113
|
if (target === 'links') loadLinks();
|
|
2089
2114
|
if (target === 'proposals') loadProposals();
|
|
2090
2115
|
if (target === 'logs') {
|
|
@@ -2294,7 +2319,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
2294
2319
|
}
|
|
2295
2320
|
}
|
|
2296
2321
|
|
|
2297
|
-
`}a(
|
|
2322
|
+
`}a(cf,"pageClientScriptPartATabs");function uf(){return`
|
|
2298
2323
|
async function loadLinks() {
|
|
2299
2324
|
if (!aiOn || !canManage) {
|
|
2300
2325
|
var dashLinksOff = $('dash-links');
|
|
@@ -2363,7 +2388,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
2363
2388
|
showToast(e.message || String(e), false);
|
|
2364
2389
|
}
|
|
2365
2390
|
}
|
|
2366
|
-
`}a(
|
|
2391
|
+
`}a(uf,"pageClientScriptPartA2");function pf(){return`
|
|
2367
2392
|
|
|
2368
2393
|
function normalizeLinkKind(kind) {
|
|
2369
2394
|
if (kind === 'rest' || kind === 'mcp' || kind === 'get_link') return kind;
|
|
@@ -3053,7 +3078,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
3053
3078
|
decided: Math.max(decidedTotal, cachedProposals.filter(function (p) { return p.status !== 'pending'; }).length)
|
|
3054
3079
|
};
|
|
3055
3080
|
}
|
|
3056
|
-
`}a(
|
|
3081
|
+
`}a(pf,"pageClientScriptPartB");function ff(){return`
|
|
3057
3082
|
var DIFF_MODE_KEY = 'ai-access-diff-mode';
|
|
3058
3083
|
var DIFF_SPLIT_MIN_PX = ${900};
|
|
3059
3084
|
var PRESENCE_SHORT_TEXT_MAX = ${120};
|
|
@@ -3170,7 +3195,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
3170
3195
|
'</div>';
|
|
3171
3196
|
}
|
|
3172
3197
|
|
|
3173
|
-
`}a(
|
|
3198
|
+
`}a(ff,"pageClientScriptPartBDiff");function mf(){return`
|
|
3174
3199
|
function isCollapsiblePresenceText(value) {
|
|
3175
3200
|
var s = String(value == null ? '' : value);
|
|
3176
3201
|
if (s.indexOf('\\n') >= 0) return true;
|
|
@@ -3271,7 +3296,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
3271
3296
|
});
|
|
3272
3297
|
});
|
|
3273
3298
|
}
|
|
3274
|
-
`}a(
|
|
3299
|
+
`}a(mf,"pageClientScriptPartBDiffPresence");function hf(){return`
|
|
3275
3300
|
function textChangeLineCount(ch) {
|
|
3276
3301
|
return (ch.lines || []).length;
|
|
3277
3302
|
}
|
|
@@ -3330,7 +3355,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
3330
3355
|
'<code class="diff-before" title="before">' + esc(JSON.stringify(ch.before)) + '</code>' +
|
|
3331
3356
|
' \u2192 <code class="diff-after" title="after">' + esc(JSON.stringify(ch.after)) + '</code></div>';
|
|
3332
3357
|
}
|
|
3333
|
-
`}a(
|
|
3358
|
+
`}a(hf,"pageClientScriptPartBDiffView");function gf(){return`
|
|
3334
3359
|
function renderDiffSections(sections, mode, emptyMsg) {
|
|
3335
3360
|
var parts = '';
|
|
3336
3361
|
if (sections.added && sections.added.length) {
|
|
@@ -3418,7 +3443,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
3418
3443
|
// Full view kept for mode-toggle re-render inside .proposal-changes.
|
|
3419
3444
|
return renderHumanDiff(diff, mode);
|
|
3420
3445
|
}
|
|
3421
|
-
`}a(
|
|
3446
|
+
`}a(gf,"pageClientScriptPartBDiffView2");function yf(){return`
|
|
3422
3447
|
function updatePendingBadges() {
|
|
3423
3448
|
var counts = countPendingBuckets();
|
|
3424
3449
|
var fullText = formatPendingBadgeText(counts.live, counts.stale);
|
|
@@ -3534,7 +3559,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
3534
3559
|
});
|
|
3535
3560
|
});
|
|
3536
3561
|
}
|
|
3537
|
-
`}a(
|
|
3562
|
+
`}a(yf,"pageClientScriptPartB2");function wf(){return`
|
|
3538
3563
|
function renderDeleteBanner(diff) {
|
|
3539
3564
|
if (!isLargeDelete(diff)) return '';
|
|
3540
3565
|
var beforeN = diff.summary.beforeNodeCount || 0;
|
|
@@ -3616,7 +3641,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
3616
3641
|
loadProposals();
|
|
3617
3642
|
loadLogs();
|
|
3618
3643
|
}
|
|
3619
|
-
`}a(
|
|
3644
|
+
`}a(wf,"pageClientScriptPartB2Actions");function bf(){return`
|
|
3620
3645
|
function renderRequestedVia(p) {
|
|
3621
3646
|
if (!p.createdBy) return '';
|
|
3622
3647
|
if (p.createdBy.label) {
|
|
@@ -3704,7 +3729,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
3704
3729
|
actions +
|
|
3705
3730
|
'</article>';
|
|
3706
3731
|
}
|
|
3707
|
-
`}a(
|
|
3732
|
+
`}a(bf,"pageClientScriptPartB2Cards");function vf(){return`
|
|
3708
3733
|
function renderStaleItem(p) {
|
|
3709
3734
|
var cached = proposalDiffCache[p.id];
|
|
3710
3735
|
var title = titleForProposalCard(p);
|
|
@@ -3879,7 +3904,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
3879
3904
|
bindProposalActions(list);
|
|
3880
3905
|
focusProposalFromHash();
|
|
3881
3906
|
}
|
|
3882
|
-
`}a(
|
|
3907
|
+
`}a(vf,"pageClientScriptPartB2CardsList");function kf(){return`
|
|
3883
3908
|
async function refreshFlowLabels() {
|
|
3884
3909
|
try {
|
|
3885
3910
|
var data = await api('/flows');
|
|
@@ -4046,7 +4071,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
4046
4071
|
replaceLocationHash('#proposals');
|
|
4047
4072
|
return true;
|
|
4048
4073
|
}
|
|
4049
|
-
`}a(
|
|
4074
|
+
`}a(kf,"pageClientScriptPartB2Focus");function Af(){return`
|
|
4050
4075
|
function bindDiffModeToggle(host, diff) {
|
|
4051
4076
|
host.querySelectorAll('[data-diff-mode]').forEach(function (btn) {
|
|
4052
4077
|
btn.addEventListener('click', function () {
|
|
@@ -4158,7 +4183,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
4158
4183
|
esc(e.message || String(e)) + '</p>';
|
|
4159
4184
|
}
|
|
4160
4185
|
}
|
|
4161
|
-
}`}a(
|
|
4186
|
+
}`}a(Af,"pageClientScriptPartB2Apply");function xf(){return`
|
|
4162
4187
|
|
|
4163
4188
|
function dialogFocusables(dialog) {
|
|
4164
4189
|
if (!dialog) return [];
|
|
@@ -4472,7 +4497,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
4472
4497
|
throw e;
|
|
4473
4498
|
}
|
|
4474
4499
|
}
|
|
4475
|
-
`}a(
|
|
4500
|
+
`}a(xf,"pageClientScriptPartC");function Sf(){return`
|
|
4476
4501
|
var cachedLogs = [];
|
|
4477
4502
|
|
|
4478
4503
|
function renderLogsTable(logs) {
|
|
@@ -4549,7 +4574,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
4549
4574
|
showFlash(flash, e.message || String(e), false);
|
|
4550
4575
|
}
|
|
4551
4576
|
}
|
|
4552
|
-
`}a(
|
|
4577
|
+
`}a(Sf,"pageClientScriptPartCLogs");function Tf(){return`
|
|
4553
4578
|
function applyStatus(s) {
|
|
4554
4579
|
aiOn = !!s.aiFeaturesEnabled;
|
|
4555
4580
|
canManage = !!(s.capabilities && s.capabilities.manageLinks);
|
|
@@ -4589,7 +4614,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
4589
4614
|
var blurb = $('permissions-blurb');
|
|
4590
4615
|
if (blurb) {
|
|
4591
4616
|
if (!s.aiFeaturesEnabled) {
|
|
4592
|
-
blurb.textContent = 'AI Access is turned off. Enable it in the editor sidebar to create
|
|
4617
|
+
blurb.textContent = 'AI Access is turned off. Enable it on the Settings tab (or in the editor sidebar) to create access tokens and review proposals.';
|
|
4593
4618
|
} else if (canManage && canApprove) {
|
|
4594
4619
|
blurb.textContent = "You're an admin: you can create access links and approve or reject proposals.";
|
|
4595
4620
|
} else if (canManage) {
|
|
@@ -4710,7 +4735,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
4710
4735
|
});
|
|
4711
4736
|
}
|
|
4712
4737
|
}
|
|
4713
|
-
`}a(
|
|
4738
|
+
`}a(Tf,"pageClientScriptPartD");function Pf(){return`
|
|
4714
4739
|
async function boot() {
|
|
4715
4740
|
accessToken = readEditorToken();
|
|
4716
4741
|
if (!accessToken) {
|
|
@@ -4722,13 +4747,17 @@ strings you will paste publicly beyond this temporary link.
|
|
|
4722
4747
|
applyStatus(s);
|
|
4723
4748
|
$('main-nav').hidden = false;
|
|
4724
4749
|
$('section-status').hidden = false;
|
|
4750
|
+
$('section-settings').hidden = false;
|
|
4725
4751
|
$('section-links').hidden = false;
|
|
4752
|
+
$('section-oauth').hidden = false;
|
|
4726
4753
|
$('section-proposals').hidden = false;
|
|
4727
4754
|
$('section-logs').hidden = false;
|
|
4728
4755
|
setupStickyNav();
|
|
4729
4756
|
// Restore tab from hash (or default #status); keep #proposal=<id> until focus consumes it.
|
|
4730
4757
|
var initialTab = initialTabFromHash(location.hash || '');
|
|
4731
4758
|
selectTab(initialTab, { skipHash: !!readProposalHashId() });
|
|
4759
|
+
if (initialTab === 'settings') loadSettings();
|
|
4760
|
+
if (initialTab === 'oauth') loadOAuthTab();
|
|
4732
4761
|
if (aiOn) {
|
|
4733
4762
|
if (canManage) loadLinks();
|
|
4734
4763
|
loadProposals();
|
|
@@ -4812,7 +4841,7 @@ strings you will paste publicly beyond this temporary link.
|
|
|
4812
4841
|
|
|
4813
4842
|
boot();
|
|
4814
4843
|
})();
|
|
4815
|
-
`}a(
|
|
4844
|
+
`}a(Pf,"pageClientScriptPartDBoot");function Ha(){return(sf()+af()+df()+lf()+cf()+uf()+pf()+ff()+mf()+hf()+gf()+yf()+wf()+bf()+vf()+kf()+Af()+xf()+Sf()+Tf()+ja()+Pf()).trim()}a(Ha,"pageClientScript");function Rf(){return`
|
|
4816
4845
|
:root {
|
|
4817
4846
|
color-scheme: light;
|
|
4818
4847
|
--bg: #eef2f5;
|
|
@@ -4826,6 +4855,8 @@ strings you will paste publicly beyond this temporary link.
|
|
|
4826
4855
|
--card: #ffffff;
|
|
4827
4856
|
--line: #cfd8e3;
|
|
4828
4857
|
--ok: #1b6b4a;
|
|
4858
|
+
--soft: #f4f7fa;
|
|
4859
|
+
--input-bg: #ffffff;
|
|
4829
4860
|
}
|
|
4830
4861
|
* { box-sizing: border-box; }
|
|
4831
4862
|
/* Author display rules (e.g. .ttl-custom-row { display:flex }) must not defeat [hidden]. */
|
|
@@ -4880,7 +4911,7 @@ h1 {
|
|
|
4880
4911
|
text-decoration: none;
|
|
4881
4912
|
text-align: center;
|
|
4882
4913
|
}
|
|
4883
|
-
`}a(
|
|
4914
|
+
`}a(Rf,"pageStylesPartA");function _f(){return`
|
|
4884
4915
|
nav.tabs {
|
|
4885
4916
|
position: sticky;
|
|
4886
4917
|
top: 0;
|
|
@@ -4897,7 +4928,7 @@ nav.tabs {
|
|
|
4897
4928
|
nav.tabs.is-stuck {
|
|
4898
4929
|
box-shadow: 0 4px 12px rgba(26, 35, 48, 0.08);
|
|
4899
4930
|
border-bottom-color: var(--line);
|
|
4900
|
-
background:
|
|
4931
|
+
background: var(--soft);
|
|
4901
4932
|
}
|
|
4902
4933
|
nav.tabs button {
|
|
4903
4934
|
border: 1px solid var(--line);
|
|
@@ -4920,7 +4951,7 @@ nav.tabs button[aria-selected="true"] {
|
|
|
4920
4951
|
max-width: 14rem;
|
|
4921
4952
|
padding: 0.05rem 0.45rem;
|
|
4922
4953
|
border-radius: 999px;
|
|
4923
|
-
background:
|
|
4954
|
+
background: var(--card);
|
|
4924
4955
|
color: var(--accent);
|
|
4925
4956
|
font-size: 0.72rem;
|
|
4926
4957
|
font-weight: 700;
|
|
@@ -4974,7 +5005,7 @@ dd {
|
|
|
4974
5005
|
padding: 0.22rem 0.55rem;
|
|
4975
5006
|
border-radius: 6px;
|
|
4976
5007
|
cursor: help;
|
|
4977
|
-
background: var`}a(
|
|
5008
|
+
background: var`}a(_f,"pageStylesPartA2");function Lf(){return`(--accent-soft);
|
|
4978
5009
|
color: var(--accent);
|
|
4979
5010
|
font-size: 0.82rem;
|
|
4980
5011
|
font-weight: 650;
|
|
@@ -4985,7 +5016,7 @@ dd {
|
|
|
4985
5016
|
text-align: left;
|
|
4986
5017
|
border: 1px dashed var(--line);
|
|
4987
5018
|
border-radius: 8px;
|
|
4988
|
-
background:
|
|
5019
|
+
background: var(--soft);
|
|
4989
5020
|
}
|
|
4990
5021
|
.empty-state .empty-title {
|
|
4991
5022
|
margin: 0 0 0.25rem;
|
|
@@ -5014,7 +5045,7 @@ dd {
|
|
|
5014
5045
|
font-size: 0.78rem;
|
|
5015
5046
|
font-weight: 650;
|
|
5016
5047
|
white-space: nowrap;
|
|
5017
|
-
background:
|
|
5048
|
+
background: var(--soft);
|
|
5018
5049
|
color: #3a4a42;
|
|
5019
5050
|
}
|
|
5020
5051
|
.kind-badge.kind-get_link { background: #e8f1fb; color: #1a4a7a; }
|
|
@@ -5049,7 +5080,7 @@ input, select, textarea, button.action {
|
|
|
5049
5080
|
border: 1px solid var(--line);
|
|
5050
5081
|
border-radius: 7px;
|
|
5051
5082
|
padding: 0.4rem 0.55rem;
|
|
5052
|
-
background:
|
|
5083
|
+
background: var(--card);
|
|
5053
5084
|
color: var(--ink);
|
|
5054
5085
|
}
|
|
5055
5086
|
textarea { min-height: 4rem; width: min(100%, 28rem); }
|
|
@@ -5061,7 +5092,7 @@ button.action {
|
|
|
5061
5092
|
font-weight: 600;
|
|
5062
5093
|
}
|
|
5063
5094
|
button.action.secondary {
|
|
5064
|
-
background:
|
|
5095
|
+
background: var(--card);
|
|
5065
5096
|
color: var(--ink);
|
|
5066
5097
|
border-color: var(--line);
|
|
5067
5098
|
}
|
|
@@ -5086,7 +5117,7 @@ th, td {
|
|
|
5086
5117
|
}
|
|
5087
5118
|
th { color: var(--muted); font-weight: 600; }
|
|
5088
5119
|
.token-once {
|
|
5089
|
-
background:
|
|
5120
|
+
background: var(--soft);
|
|
5090
5121
|
border: 1px dashed var(--accent);
|
|
5091
5122
|
border-radius: 8px;
|
|
5092
5123
|
padding: 0.75rem 0.9rem;
|
|
@@ -5097,7 +5128,7 @@ th { color: var(--muted); font-weight: 600; }
|
|
|
5097
5128
|
.token-once .token-copy-block {
|
|
5098
5129
|
margin: 0.6rem 0;
|
|
5099
5130
|
padding: 0.55rem 0.65rem;
|
|
5100
|
-
background:
|
|
5131
|
+
background: var(--card);
|
|
5101
5132
|
border: 1px solid var(--line);
|
|
5102
5133
|
border-radius: 6px;
|
|
5103
5134
|
font-size: 0.8rem;
|
|
@@ -5107,7 +5138,7 @@ th { color: var(--muted); font-weight: 600; }
|
|
|
5107
5138
|
.token-once #token-copy-btn { margin-top: 0.35rem; }
|
|
5108
5139
|
.token-once #token-docs-hint { margin: 0.45rem 0 0; }
|
|
5109
5140
|
.token-usage {
|
|
5110
|
-
background: var(--panel-soft,
|
|
5141
|
+
background: var(--panel-soft, var(--soft));
|
|
5111
5142
|
border: 1px solid var(--line);
|
|
5112
5143
|
border-radius: 8px;
|
|
5113
5144
|
padding: 0.75rem 0.9rem;
|
|
@@ -5117,7 +5148,7 @@ th { color: var(--muted); font-weight: 600; }
|
|
|
5117
5148
|
.token-usage .token-copy-block {
|
|
5118
5149
|
margin: 0.6rem 0;
|
|
5119
5150
|
padding: 0.55rem 0.65rem;
|
|
5120
|
-
background:
|
|
5151
|
+
background: var(--card);
|
|
5121
5152
|
border: 1px solid var(--line);
|
|
5122
5153
|
border-radius: 6px;
|
|
5123
5154
|
font-size: 0.8rem;
|
|
@@ -5147,7 +5178,7 @@ dialog.token-modal { width: min(720px, 94vw); max-width: min(720px, 94vw); max-h
|
|
|
5147
5178
|
.proposal-detail {
|
|
5148
5179
|
margin-top: 0.75rem;
|
|
5149
5180
|
padding: 0.75rem;
|
|
5150
|
-
background:
|
|
5181
|
+
background: var(--soft);
|
|
5151
5182
|
border-radius: 8px;
|
|
5152
5183
|
border: 1px solid var(--line);
|
|
5153
5184
|
overflow-wrap: anywhere;
|
|
@@ -5165,7 +5196,7 @@ dialog.token-modal { width: min(720px, 94vw); max-width: min(720px, 94vw); max-h
|
|
|
5165
5196
|
color: var(--muted);
|
|
5166
5197
|
font-size: 0.92rem;
|
|
5167
5198
|
}
|
|
5168
|
-
`}a(
|
|
5199
|
+
`}a(Lf,"pageStylesPartB");function Cf(){return`
|
|
5169
5200
|
.logs-toolbar { margin-bottom: 0.65rem; }
|
|
5170
5201
|
table.audit-table {
|
|
5171
5202
|
table-layout: fixed;
|
|
@@ -5232,7 +5263,7 @@ table.audit-table {
|
|
|
5232
5263
|
max-height: 10rem;
|
|
5233
5264
|
overflow: auto;
|
|
5234
5265
|
font-size: 0.78rem;
|
|
5235
|
-
background:
|
|
5266
|
+
background: var(--soft);
|
|
5236
5267
|
padding: 0.4rem 0.5rem;
|
|
5237
5268
|
border-radius: 6px;
|
|
5238
5269
|
}
|
|
@@ -5241,11 +5272,11 @@ table.audit-table {
|
|
|
5241
5272
|
cursor: help;
|
|
5242
5273
|
border-bottom: 1px dotted var(--line);
|
|
5243
5274
|
}
|
|
5244
|
-
`}a(
|
|
5275
|
+
`}a(Cf,"pageStylesPartC");function Ef(){return`
|
|
5245
5276
|
.proposal-diff {
|
|
5246
5277
|
margin: 0.65rem 0;
|
|
5247
5278
|
padding: 0.55rem 0.65rem;
|
|
5248
|
-
background:
|
|
5279
|
+
background: var(--card);
|
|
5249
5280
|
border: 1px solid var(--line);
|
|
5250
5281
|
border-radius: 7px;
|
|
5251
5282
|
overflow-wrap: anywhere;
|
|
@@ -5263,7 +5294,7 @@ table.audit-table {
|
|
|
5263
5294
|
border: 1px solid var(--line);
|
|
5264
5295
|
border-radius: 7px;
|
|
5265
5296
|
overflow: hidden;
|
|
5266
|
-
background:
|
|
5297
|
+
background: var(--soft);
|
|
5267
5298
|
}
|
|
5268
5299
|
.diff-mode-toggle button {
|
|
5269
5300
|
border: 0;
|
|
@@ -5318,7 +5349,7 @@ table.audit-table {
|
|
|
5318
5349
|
color: var(--muted);
|
|
5319
5350
|
margin-bottom: 0.15rem;
|
|
5320
5351
|
}
|
|
5321
|
-
`}a(
|
|
5352
|
+
`}a(Ef,"pageStylesPartD");function If(){return`
|
|
5322
5353
|
.diff-block {
|
|
5323
5354
|
margin-top: 0.25rem;
|
|
5324
5355
|
font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
|
|
@@ -5329,7 +5360,7 @@ table.audit-table {
|
|
|
5329
5360
|
max-height: 18rem;
|
|
5330
5361
|
border: 1px solid var(--line);
|
|
5331
5362
|
border-radius: 5px;
|
|
5332
|
-
background:
|
|
5363
|
+
background: var(--soft);
|
|
5333
5364
|
}
|
|
5334
5365
|
.diff-line {
|
|
5335
5366
|
display: grid;
|
|
@@ -5380,7 +5411,7 @@ table.audit-table {
|
|
|
5380
5411
|
font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
|
|
5381
5412
|
font-size: 0.78rem;
|
|
5382
5413
|
line-height: 1.4;
|
|
5383
|
-
background:
|
|
5414
|
+
background: var(--soft);
|
|
5384
5415
|
}
|
|
5385
5416
|
.diff-split .diff-pane + .diff-pane {
|
|
5386
5417
|
border-left: 1px solid var(--line);
|
|
@@ -5400,7 +5431,7 @@ table.audit-table {
|
|
|
5400
5431
|
}
|
|
5401
5432
|
.diff-before { color: var(--danger); word-break: break-word; }
|
|
5402
5433
|
.diff-after { color: var(--ok); word-break: break-word; }
|
|
5403
|
-
`}a(
|
|
5434
|
+
`}a(If,"pageStylesPartE");function Nf(){return`
|
|
5404
5435
|
.proposal-detail {
|
|
5405
5436
|
scroll-margin-top: 4.5rem;
|
|
5406
5437
|
}
|
|
@@ -5423,7 +5454,7 @@ table.audit-table {
|
|
|
5423
5454
|
margin: 0.35rem 0 0.55rem;
|
|
5424
5455
|
border: 1px solid var(--line);
|
|
5425
5456
|
border-radius: 6px;
|
|
5426
|
-
background:
|
|
5457
|
+
background: var(--soft);
|
|
5427
5458
|
}
|
|
5428
5459
|
.node-presence > summary {
|
|
5429
5460
|
cursor: pointer;
|
|
@@ -5465,7 +5496,7 @@ table.audit-table {
|
|
|
5465
5496
|
padding: 0.15rem 0.55rem;
|
|
5466
5497
|
border: 1px solid var(--line);
|
|
5467
5498
|
border-radius: 4px;
|
|
5468
|
-
background:
|
|
5499
|
+
background: var(--card);
|
|
5469
5500
|
color: var(--ink);
|
|
5470
5501
|
cursor: pointer;
|
|
5471
5502
|
}
|
|
@@ -5477,7 +5508,7 @@ table.audit-table {
|
|
|
5477
5508
|
margin: 0.3rem 0;
|
|
5478
5509
|
border: 1px solid var(--line);
|
|
5479
5510
|
border-radius: 5px;
|
|
5480
|
-
background:
|
|
5511
|
+
background: var(--card);
|
|
5481
5512
|
overflow: hidden;
|
|
5482
5513
|
}
|
|
5483
5514
|
.presence-preview-frame {
|
|
@@ -5486,7 +5517,7 @@ table.audit-table {
|
|
|
5486
5517
|
min-height: 10rem;
|
|
5487
5518
|
max-height: 18rem;
|
|
5488
5519
|
border: 0;
|
|
5489
|
-
background:
|
|
5520
|
+
background: var(--card);
|
|
5490
5521
|
}
|
|
5491
5522
|
.presence-pre {
|
|
5492
5523
|
margin: 0.3rem 0 0;
|
|
@@ -5496,7 +5527,7 @@ table.audit-table {
|
|
|
5496
5527
|
font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
|
|
5497
5528
|
font-size: 0.78rem;
|
|
5498
5529
|
line-height: 1.4;
|
|
5499
|
-
background:
|
|
5530
|
+
background: var(--card);
|
|
5500
5531
|
border: 1px solid var(--line);
|
|
5501
5532
|
border-radius: 5px;
|
|
5502
5533
|
padding: 0.4rem 0.5rem;
|
|
@@ -5526,7 +5557,7 @@ table.audit-table {
|
|
|
5526
5557
|
.audit-table .col-action { width: 5.5rem; min-width: 5.5rem; }
|
|
5527
5558
|
.audit-table .col-actor { width: 7rem; min-width: 6rem; }
|
|
5528
5559
|
}
|
|
5529
|
-
`}a(
|
|
5560
|
+
`}a(Nf,"pageStylesPartF");function Ff(){return`
|
|
5530
5561
|
.dash-grid {
|
|
5531
5562
|
display: grid;
|
|
5532
5563
|
grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr));
|
|
@@ -5534,7 +5565,7 @@ table.audit-table {
|
|
|
5534
5565
|
margin: 0 0 1rem;
|
|
5535
5566
|
}
|
|
5536
5567
|
.dash-card {
|
|
5537
|
-
background:
|
|
5568
|
+
background: var(--soft);
|
|
5538
5569
|
border: 1px solid var(--line);
|
|
5539
5570
|
border-radius: 9px;
|
|
5540
5571
|
padding: 0.75rem 0.85rem;
|
|
@@ -5568,7 +5599,7 @@ table.audit-table {
|
|
|
5568
5599
|
padding: 0.28rem 0.65rem;
|
|
5569
5600
|
font: inherit;
|
|
5570
5601
|
font-size: 0.88rem;
|
|
5571
|
-
background:
|
|
5602
|
+
background: var(--card);
|
|
5572
5603
|
color: var(--ink);
|
|
5573
5604
|
}
|
|
5574
5605
|
.proposals-search:focus {
|
|
@@ -5598,7 +5629,7 @@ button.id-copy:focus {
|
|
|
5598
5629
|
}
|
|
5599
5630
|
.filter-chip {
|
|
5600
5631
|
border: 1px solid var(--line);
|
|
5601
|
-
background:
|
|
5632
|
+
background: var(--card);
|
|
5602
5633
|
color: var(--ink);
|
|
5603
5634
|
border-radius: 999px;
|
|
5604
5635
|
padding: 0.28rem 0.75rem;
|
|
@@ -5619,7 +5650,7 @@ button.id-copy:focus {
|
|
|
5619
5650
|
padding: 0.55rem 0.75rem;
|
|
5620
5651
|
border: 1px solid var(--line);
|
|
5621
5652
|
border-radius: 8px;
|
|
5622
|
-
background:
|
|
5653
|
+
background: var(--soft);
|
|
5623
5654
|
margin-top: 0.45rem;
|
|
5624
5655
|
cursor: pointer;
|
|
5625
5656
|
}
|
|
@@ -5721,7 +5752,7 @@ button.id-copy:focus {
|
|
|
5721
5752
|
.mcp-setup-tab {
|
|
5722
5753
|
appearance: none;
|
|
5723
5754
|
border: 1px solid var(--border, #ccd);
|
|
5724
|
-
background: var(--surface,
|
|
5755
|
+
background: var(--surface, var(--card));
|
|
5725
5756
|
color: inherit;
|
|
5726
5757
|
border-radius: 4px;
|
|
5727
5758
|
padding: 0.35rem 0.65rem;
|
|
@@ -5740,10 +5771,10 @@ button.id-copy:focus {
|
|
|
5740
5771
|
font-size: 0.85rem;
|
|
5741
5772
|
white-space: pre-wrap;
|
|
5742
5773
|
word-break: break-word;
|
|
5743
|
-
background: var(--code-bg,
|
|
5774
|
+
background: var(--code-bg, var(--soft));
|
|
5744
5775
|
border-radius: 4px;
|
|
5745
5776
|
}
|
|
5746
|
-
`}a(
|
|
5777
|
+
`}a(Ff,"pageStylesPartG");function Of(){return`
|
|
5747
5778
|
.stale-group {
|
|
5748
5779
|
margin: 1rem 0 0;
|
|
5749
5780
|
border: 1px solid var(--warn-border);
|
|
@@ -5762,7 +5793,7 @@ button.id-copy:focus {
|
|
|
5762
5793
|
.stale-item {
|
|
5763
5794
|
margin: 0.55rem 0 0;
|
|
5764
5795
|
padding: 0.55rem 0.65rem;
|
|
5765
|
-
background:
|
|
5796
|
+
background: var(--card);
|
|
5766
5797
|
border: 1px solid var(--line);
|
|
5767
5798
|
border-radius: 7px;
|
|
5768
5799
|
}
|
|
@@ -5907,7 +5938,93 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
5907
5938
|
margin-left: auto;
|
|
5908
5939
|
}
|
|
5909
5940
|
.debug-toggle input { width: auto; }
|
|
5910
|
-
`}a(
|
|
5941
|
+
`}a(Of,"pageStylesPartH");function $f(){return`
|
|
5942
|
+
.card {
|
|
5943
|
+
background: var(--card);
|
|
5944
|
+
border: 1px solid var(--line);
|
|
5945
|
+
border-radius: 10px;
|
|
5946
|
+
padding: 1rem 1.1rem;
|
|
5947
|
+
margin: 0 0 1rem;
|
|
5948
|
+
}
|
|
5949
|
+
.card h3 { margin: 0 0 0.75rem; font-size: 1.05rem; }
|
|
5950
|
+
.card-head { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; margin-bottom: 0.5rem; }
|
|
5951
|
+
.card-head h3 { margin: 0; }
|
|
5952
|
+
.settings-grid {
|
|
5953
|
+
display: grid;
|
|
5954
|
+
grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr));
|
|
5955
|
+
gap: 1rem;
|
|
5956
|
+
align-items: start;
|
|
5957
|
+
margin-bottom: 1rem;
|
|
5958
|
+
}
|
|
5959
|
+
.settings-grid .card { margin: 0; }
|
|
5960
|
+
.form-row { margin: 0 0 0.9rem; }
|
|
5961
|
+
.form-row:last-child { margin-bottom: 0; }
|
|
5962
|
+
.form-row > label, .form-row > .label { display: block; font-weight: 600; margin: 0 0 0.3rem; }
|
|
5963
|
+
.field-line { display: flex; flex-wrap: nowrap; align-items: center; gap: 0.5rem; min-width: 0; }
|
|
5964
|
+
.field-line .grow { flex: 1 1 auto; min-width: 0; }
|
|
5965
|
+
.field-line input.num { width: 7.5rem; flex: 0 0 auto; }
|
|
5966
|
+
.field-line select { flex: 0 1 auto; width: auto; max-width: 100%; min-width: 0; }
|
|
5967
|
+
.field-line .unit { color: var(--muted); white-space: nowrap; }
|
|
5968
|
+
.field-line code { overflow-wrap: anywhere; padding: 0.35rem 0.5rem; background: var(--soft); border-radius: 6px; }
|
|
5969
|
+
.hint { display: block; color: var(--muted); font-size: 0.88rem; font-weight: 400; margin: 0.25rem 0 0; line-height: 1.4; }
|
|
5970
|
+
.warn-text { color: #8a5a00; }
|
|
5971
|
+
.toggle-row { display: flex; align-items: flex-start; gap: 0.5rem; justify-content: space-between; }
|
|
5972
|
+
.switch-label { display: flex; gap: 0.6rem; align-items: flex-start; cursor: pointer; }
|
|
5973
|
+
.switch-label input { margin-top: 0.25rem; width: 1.05rem; height: 1.05rem; flex: 0 0 auto; }
|
|
5974
|
+
.lock-badge {
|
|
5975
|
+
font-size: 0.8rem; color: var(--muted); border: 1px solid var(--line);
|
|
5976
|
+
border-radius: 999px; padding: 0.1rem 0.5rem; white-space: nowrap;
|
|
5977
|
+
}
|
|
5978
|
+
.notice { padding: 0.6rem 0.8rem; border-radius: 8px; background: var(--soft); border: 1px solid var(--line); }
|
|
5979
|
+
.check-result { margin: 0.6rem 0 0; padding: 0.6rem 0.8rem; border-radius: 8px; font-weight: 600; overflow-wrap: anywhere; }
|
|
5980
|
+
.check-result.is-ok { color: var(--ok); background: #e8f6ef; border: 1px solid #a8d8b4; }
|
|
5981
|
+
.check-result.is-fail { color: var(--danger); background: #fdecec; border: 1px solid #f0c4c4; }
|
|
5982
|
+
.check-result.is-pending { color: var(--muted); font-weight: 400; background: var(--soft); border: 1px solid var(--line); }
|
|
5983
|
+
.steps { margin: 0.5rem 0 0.5rem 1.2rem; padding: 0; }
|
|
5984
|
+
.steps li { margin: 0.2rem 0; }
|
|
5985
|
+
.client-row, .conflict-list li { overflow-wrap: anywhere; }
|
|
5986
|
+
.client-row {
|
|
5987
|
+
display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
|
|
5988
|
+
padding: 0.55rem 0; border-top: 1px solid var(--line);
|
|
5989
|
+
}
|
|
5990
|
+
.client-row:first-child { border-top: 0; }
|
|
5991
|
+
.client-row .client-meta { color: var(--muted); font-size: 0.85rem; }
|
|
5992
|
+
.conflict-list { margin: 0.4rem 0 0 1.2rem; padding: 0; }
|
|
5993
|
+
.conflict-list li { margin: 0.25rem 0; }
|
|
5994
|
+
@media (max-width: 640px) {
|
|
5995
|
+
nav.tabs { overflow-x: auto; flex-wrap: nowrap; }
|
|
5996
|
+
nav.tabs button { flex: 0 0 auto; }
|
|
5997
|
+
.field-line { flex-wrap: wrap; }
|
|
5998
|
+
}
|
|
5999
|
+
@media (prefers-color-scheme: dark) {
|
|
6000
|
+
:root {
|
|
6001
|
+
color-scheme: dark;
|
|
6002
|
+
--bg: #12171d;
|
|
6003
|
+
--ink: #e3e9ef;
|
|
6004
|
+
--muted: #9aa8b6;
|
|
6005
|
+
--accent: #4cc59a;
|
|
6006
|
+
--accent-soft: #1d3a31;
|
|
6007
|
+
--warn-bg: #3a2e1a;
|
|
6008
|
+
--warn-border: #a2772f;
|
|
6009
|
+
--danger: #ff8a8a;
|
|
6010
|
+
--card: #1b222a;
|
|
6011
|
+
--line: #33404d;
|
|
6012
|
+
--ok: #6fd3a5;
|
|
6013
|
+
--soft: #222b35;
|
|
6014
|
+
--input-bg: #11161c;
|
|
6015
|
+
}
|
|
6016
|
+
body { background: var(--bg); }
|
|
6017
|
+
input, select, textarea { background: var(--input-bg); color: var(--ink); border-color: var(--line); }
|
|
6018
|
+
a { color: #7cc4ff; }
|
|
6019
|
+
.flash.error, .check-result.is-fail { background: #3a1f22; border-color: #6b3036; }
|
|
6020
|
+
.flash.ok, .check-result.is-ok { background: #173327; border-color: #2d6149; }
|
|
6021
|
+
.warn-text { color: #f0c36d; }
|
|
6022
|
+
.warning { background: var(--warn-bg); color: var(--ink); }
|
|
6023
|
+
button.action, button.action.danger, nav.tabs button[aria-selected="true"] { color: #0d1a14; }
|
|
6024
|
+
button.action.secondary { background: var(--soft); color: var(--ink); border-color: var(--line); }
|
|
6025
|
+
dialog { background: var(--card); color: var(--ink); }
|
|
6026
|
+
}
|
|
6027
|
+
`}a($f,"pageStylesDashboard");function Ua(){return(Rf()+_f()+Lf()+Cf()+Ef()+If()+Nf()+Ff()+Of()+$f()).trim()}a(Ua,"pageStyles");function Mf(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""")}a(Mf,"escapeHtml");function Df(){return`
|
|
5911
6028
|
<header class="page-header">
|
|
5912
6029
|
<h1>Node-RED AI Access</h1>
|
|
5913
6030
|
<p class="lede">
|
|
@@ -5925,20 +6042,22 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
5925
6042
|
<p>Open the editor, sign in, then return here to manage AI Access.</p>
|
|
5926
6043
|
<p class="row">
|
|
5927
6044
|
<a class="action" id="auth-gate-editor" href="/">Open Node-RED editor</a>
|
|
5928
|
-
<a class="action secondary" id="auth-gate-return" href="/
|
|
6045
|
+
<a class="action secondary" id="auth-gate-return" href="/ai-access">Return to AI Access</a>
|
|
5929
6046
|
</p>
|
|
5930
6047
|
</div>
|
|
5931
6048
|
<div id="toast-host" class="toast-host" aria-live="polite"></div>
|
|
5932
6049
|
<nav class="tabs" aria-label="Admin views" hidden id="main-nav">
|
|
5933
|
-
<button type="button" data-tab="status" aria-selected="true">
|
|
5934
|
-
<button type="button" data-tab="
|
|
6050
|
+
<button type="button" data-tab="status" aria-selected="true">Overview</button>
|
|
6051
|
+
<button type="button" data-tab="settings" aria-selected="false">Settings</button>
|
|
6052
|
+
<button type="button" data-tab="links" aria-selected="false">Access tokens</button>
|
|
6053
|
+
<button type="button" data-tab="oauth" aria-selected="false">OAuth</button>
|
|
5935
6054
|
<button type="button" data-tab="proposals" aria-selected="false">
|
|
5936
6055
|
Proposals <span class="tab-badge" id="proposals-pending-badge" hidden>0</span>
|
|
5937
6056
|
</button>
|
|
5938
6057
|
<button type="button" data-tab="logs" aria-selected="false">Audit log</button>
|
|
5939
|
-
</nav>`}a(
|
|
5940
|
-
<section class="panel active" id="section-status" aria-label="
|
|
5941
|
-
<h2>
|
|
6058
|
+
</nav>`}a(Df,"renderMcpPageHeader");function jf(){return`
|
|
6059
|
+
<section class="panel active" id="section-status" aria-label="Overview" hidden>
|
|
6060
|
+
<h2>Overview</h2>
|
|
5942
6061
|
<div id="status-flash" class="flash" hidden></div>
|
|
5943
6062
|
<aside id="status-warning" class="warning" role="status" hidden></aside>
|
|
5944
6063
|
<div class="dash-grid" id="status-dashboard">
|
|
@@ -5980,9 +6099,9 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
5980
6099
|
<div class="row" style="margin-top:1rem">
|
|
5981
6100
|
<button type="button" class="action secondary" id="refresh-status">Refresh</button>
|
|
5982
6101
|
</div>
|
|
5983
|
-
</section>`}a(
|
|
5984
|
-
<section class="panel" id="section-links" aria-label="Access
|
|
5985
|
-
<h2>Access
|
|
6102
|
+
</section>`}a(jf,"renderMcpPageStatusSection");function Hf(){return`
|
|
6103
|
+
<section class="panel" id="section-links" aria-label="Access tokens" hidden>
|
|
6104
|
+
<h2>Access tokens</h2>
|
|
5986
6105
|
<p class="muted" id="links-intro">
|
|
5987
6106
|
Create a token for the channel your AI uses: a GET link to paste in chat,
|
|
5988
6107
|
a REST Bearer, or an MCP token. It can read flows and submit proposals \u2014
|
|
@@ -5991,7 +6110,193 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
5991
6110
|
<div id="links-flash" class="flash" hidden></div>
|
|
5992
6111
|
<div id="links-manage"></div>
|
|
5993
6112
|
<div id="links-list"></div>
|
|
5994
|
-
</section>`}a(
|
|
6113
|
+
</section>`}a(Hf,"renderMcpPageLinksSection");function Pe(e){return`<span class="lock-badge" id="lock-${e}" hidden>Locked by settings.js</span>`}a(Pe,"lockBadge");function Uf(){return`
|
|
6114
|
+
<section class="panel" id="section-settings" aria-label="Settings" hidden>
|
|
6115
|
+
<h2>Settings</h2>
|
|
6116
|
+
<p class="muted">Changes save automatically and apply immediately (no restart).</p>
|
|
6117
|
+
<div id="settings-flash" class="flash" hidden></div>
|
|
6118
|
+
<p id="settings-readonly" class="notice" hidden>Read-only: full admin permission (*) is required to change these settings.</p>
|
|
6119
|
+
<div class="settings-grid">
|
|
6120
|
+
<div class="card settings-card">
|
|
6121
|
+
<h3>General</h3>
|
|
6122
|
+
<div class="form-row toggle-row" id="row-enabled">
|
|
6123
|
+
<label class="switch-label"><input type="checkbox" id="set-enabled" />
|
|
6124
|
+
<span><strong>Enable AI Access features</strong>
|
|
6125
|
+
<span class="hint">When off, access tokens, proposals, and AI tools are disabled.</span></span></label>
|
|
6126
|
+
${Pe("enabled")}
|
|
6127
|
+
</div>
|
|
6128
|
+
<div class="form-row toggle-row" id="row-allow-without" hidden>
|
|
6129
|
+
<label class="switch-label"><input type="checkbox" id="set-allow-without" />
|
|
6130
|
+
<span><strong>Allow AI Access without adminAuth (local-only)</strong>
|
|
6131
|
+
<span class="hint danger-text">Not for production. Anyone who can reach the editor admin port can use AI features.</span></span></label>
|
|
6132
|
+
${Pe("allow-without")}
|
|
6133
|
+
</div>
|
|
6134
|
+
<div class="form-row">
|
|
6135
|
+
<label for="set-deploy">Default deploy type</label>
|
|
6136
|
+
<div class="field-line">
|
|
6137
|
+
<select id="set-deploy">
|
|
6138
|
+
<option value="nodes">Only changed nodes (recommended)</option>
|
|
6139
|
+
<option value="flows">Changed flows</option>
|
|
6140
|
+
<option value="full">Full redeploy</option>
|
|
6141
|
+
</select>
|
|
6142
|
+
${Pe("deploy")}
|
|
6143
|
+
</div>
|
|
6144
|
+
<p class="hint danger-text" id="set-deploy-full-warn" hidden>Full redeploy restarts the entire runtime.</p>
|
|
6145
|
+
</div>
|
|
6146
|
+
<div class="form-row">
|
|
6147
|
+
<label for="set-ttl">Default access token expiry</label>
|
|
6148
|
+
<div class="field-line">
|
|
6149
|
+
<input type="number" id="set-ttl" min="1" step="1" class="num" />
|
|
6150
|
+
<span class="unit">hours</span>
|
|
6151
|
+
${Pe("ttl")}
|
|
6152
|
+
</div>
|
|
6153
|
+
</div>
|
|
6154
|
+
<div class="form-row">
|
|
6155
|
+
<label for="set-max-links">Maximum active tokens</label>
|
|
6156
|
+
<div class="field-line">
|
|
6157
|
+
<input type="number" id="set-max-links" min="1" step="1" class="num" />
|
|
6158
|
+
<span class="unit">tokens</span>
|
|
6159
|
+
${Pe("max-links")}
|
|
6160
|
+
</div>
|
|
6161
|
+
</div>
|
|
6162
|
+
</div>
|
|
6163
|
+
|
|
6164
|
+
<div class="card settings-card">
|
|
6165
|
+
<h3>Public URL</h3>
|
|
6166
|
+
<p class="hint">The HTTPS origin AI clients use to reach Node-RED (used in links and OAuth metadata).</p>
|
|
6167
|
+
<div class="form-row">
|
|
6168
|
+
<label for="set-public-url">Public URL</label>
|
|
6169
|
+
<div class="field-line">
|
|
6170
|
+
<input type="url" id="set-public-url" placeholder="https://nodered.example.com" class="grow" />
|
|
6171
|
+
<button type="button" class="action secondary" id="set-check-public-url">Check</button>
|
|
6172
|
+
${Pe("public-url")}
|
|
6173
|
+
</div>
|
|
6174
|
+
<div id="set-public-url-warning" class="hint warn-text" role="status" hidden></div>
|
|
6175
|
+
<div id="set-check-result" class="check-result" aria-live="polite" hidden></div>
|
|
6176
|
+
</div>
|
|
6177
|
+
<details class="tech-details">
|
|
6178
|
+
<summary>About the check</summary>
|
|
6179
|
+
<p class="hint">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.</p>
|
|
6180
|
+
<p class="hint" id="set-trust-proxy"></p>
|
|
6181
|
+
</details>
|
|
6182
|
+
</div>
|
|
6183
|
+
|
|
6184
|
+
<div class="card settings-card">
|
|
6185
|
+
<h3>CORS</h3>
|
|
6186
|
+
<div class="form-row">
|
|
6187
|
+
<label for="set-cors">Allowed origins <span class="hint">(comma separated)</span></label>
|
|
6188
|
+
<div class="field-line">
|
|
6189
|
+
<input type="text" id="set-cors" class="grow" placeholder="https://app.example.com" />
|
|
6190
|
+
${Pe("cors")}
|
|
6191
|
+
</div>
|
|
6192
|
+
</div>
|
|
6193
|
+
</div>
|
|
6194
|
+
|
|
6195
|
+
<div class="card settings-card">
|
|
6196
|
+
<h3>Rate limits</h3>
|
|
6197
|
+
<div class="form-row">
|
|
6198
|
+
<label for="set-rate-window">Window</label>
|
|
6199
|
+
<div class="field-line">
|
|
6200
|
+
<input type="number" id="set-rate-window" min="1" step="1" class="num" />
|
|
6201
|
+
<span class="unit">ms</span>
|
|
6202
|
+
${Pe("rate-window")}
|
|
6203
|
+
</div>
|
|
6204
|
+
</div>
|
|
6205
|
+
<div class="form-row">
|
|
6206
|
+
<label for="set-rate-ip">Per IP</label>
|
|
6207
|
+
<div class="field-line">
|
|
6208
|
+
<input type="number" id="set-rate-ip" min="1" step="1" class="num" />
|
|
6209
|
+
<span class="unit">requests / window</span>
|
|
6210
|
+
${Pe("rate-ip")}
|
|
6211
|
+
</div>
|
|
6212
|
+
</div>
|
|
6213
|
+
<div class="form-row">
|
|
6214
|
+
<label for="set-rate-token">Per token</label>
|
|
6215
|
+
<div class="field-line">
|
|
6216
|
+
<input type="number" id="set-rate-token" min="1" step="1" class="num" />
|
|
6217
|
+
<span class="unit">requests / window</span>
|
|
6218
|
+
${Pe("rate-token")}
|
|
6219
|
+
</div>
|
|
6220
|
+
</div>
|
|
6221
|
+
<p class="hint" id="set-trust-proxy-inline"></p>
|
|
6222
|
+
</div>
|
|
6223
|
+
</div>
|
|
6224
|
+
</section>`}a(Uf,"renderMcpPageSettingsSection");function Bf(){return`
|
|
6225
|
+
<section class="panel" id="section-oauth" aria-label="OAuth" hidden>
|
|
6226
|
+
<h2>MCP OAuth</h2>
|
|
6227
|
+
<p class="muted">MCP clients (ChatGPT, Claude, \u2026) can request read or propose access through OAuth.</p>
|
|
6228
|
+
<div id="oauth-flash" class="flash" hidden></div>
|
|
6229
|
+
<div id="oauth-route-warning" class="warning" role="status" hidden></div>
|
|
6230
|
+
<div class="settings-grid">
|
|
6231
|
+
<div class="card settings-card">
|
|
6232
|
+
<h3>Status</h3>
|
|
6233
|
+
<div class="form-row toggle-row">
|
|
6234
|
+
<label class="switch-label"><input type="checkbox" id="oauth-enabled" />
|
|
6235
|
+
<span><strong>Enable MCP OAuth</strong>
|
|
6236
|
+
<span class="hint">Also available in the editor sidebar (AI Access tab).</span></span></label>
|
|
6237
|
+
${Pe("oauth")}
|
|
6238
|
+
</div>
|
|
6239
|
+
<div class="form-row">
|
|
6240
|
+
<span class="label">Server URL for MCP clients</span>
|
|
6241
|
+
<div class="field-line"><code id="oauth-server-url" class="grow">\u2014</code>
|
|
6242
|
+
<button type="button" class="action secondary" id="oauth-copy-url">Copy</button></div>
|
|
6243
|
+
</div>
|
|
6244
|
+
<details class="tech-details" open>
|
|
6245
|
+
<summary>How to connect with OAuth</summary>
|
|
6246
|
+
<ol class="hint steps">
|
|
6247
|
+
<li>Set the Public URL on the Settings tab to the HTTPS address clients use, then click Check.</li>
|
|
6248
|
+
<li>Enable MCP OAuth (above, or in the editor sidebar AI Access tab).</li>
|
|
6249
|
+
<li>In your MCP client add the server URL <code><Public URL>/mcp</code> (shown above).</li>
|
|
6250
|
+
<li>The client registers itself automatically (Dynamic Client Registration).</li>
|
|
6251
|
+
<li>You are sent to the Node-RED login and consent page; approve read or propose access.</li>
|
|
6252
|
+
</ol>
|
|
6253
|
+
<p class="hint">Opening <code>/mcp</code> in a browser redirects to this dashboard; MCP clients use the same URL.</p>
|
|
6254
|
+
</details>
|
|
6255
|
+
</div>
|
|
6256
|
+
<div class="card settings-card">
|
|
6257
|
+
<h3>Token lifetimes</h3>
|
|
6258
|
+
<div class="form-row">
|
|
6259
|
+
<label for="oauth-access-ttl">Access token lifetime</label>
|
|
6260
|
+
<div class="field-line">
|
|
6261
|
+
<input type="number" id="oauth-access-ttl" min="1" step="1" class="num" />
|
|
6262
|
+
<select id="oauth-access-ttl-unit" aria-label="Access token lifetime unit">
|
|
6263
|
+
<option value="minutes">minutes</option><option value="hours">hours</option>
|
|
6264
|
+
</select>
|
|
6265
|
+
</div>
|
|
6266
|
+
</div>
|
|
6267
|
+
<div class="form-row">
|
|
6268
|
+
<label for="oauth-refresh-ttl">Refresh token idle lifetime</label>
|
|
6269
|
+
<div class="field-line">
|
|
6270
|
+
<input type="number" id="oauth-refresh-ttl" min="1" step="1" class="num" />
|
|
6271
|
+
<select id="oauth-refresh-ttl-unit" aria-label="Refresh token idle lifetime unit">
|
|
6272
|
+
<option value="hours">hours</option><option value="days">days</option>
|
|
6273
|
+
</select>
|
|
6274
|
+
</div>
|
|
6275
|
+
</div>
|
|
6276
|
+
<div class="form-row">
|
|
6277
|
+
<label for="oauth-refresh-max">Refresh token max lifetime</label>
|
|
6278
|
+
<div class="field-line">
|
|
6279
|
+
<input type="number" id="oauth-refresh-max" min="0" step="1" class="num" />
|
|
6280
|
+
<span class="unit">days</span>
|
|
6281
|
+
</div>
|
|
6282
|
+
<p class="hint">Default 90 days; 0 = unlimited.</p>
|
|
6283
|
+
</div>
|
|
6284
|
+
<div class="form-row">
|
|
6285
|
+
<label for="oauth-dcr-cap">Maximum registered clients</label>
|
|
6286
|
+
<div class="field-line">
|
|
6287
|
+
<input type="number" id="oauth-dcr-cap" min="1" step="1" class="num" />
|
|
6288
|
+
<span class="unit">clients</span>
|
|
6289
|
+
</div>
|
|
6290
|
+
<p class="hint">Applies to new registrations: existing clients keep working, but new apps are refused while the number of clients is at or above this limit.</p>
|
|
6291
|
+
</div>
|
|
6292
|
+
</div>
|
|
6293
|
+
</div>
|
|
6294
|
+
<div class="card">
|
|
6295
|
+
<div class="card-head"><h3>Registered OAuth clients</h3>
|
|
6296
|
+
<button type="button" class="action secondary" id="oauth-refresh">Refresh</button></div>
|
|
6297
|
+
<div id="oauth-clients" aria-live="polite"><p class="muted">Loading\u2026</p></div>
|
|
6298
|
+
</div>
|
|
6299
|
+
</section>`}a(Bf,"renderMcpPageOAuthSection");function qf(){return`
|
|
5995
6300
|
<section class="panel" id="section-proposals" aria-label="Proposals" hidden>
|
|
5996
6301
|
<h2>Proposals</h2>
|
|
5997
6302
|
<p id="proposals-hint" class="muted"></p>
|
|
@@ -6009,7 +6314,7 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
6009
6314
|
</div>
|
|
6010
6315
|
<div id="proposals-flash" class="flash" hidden></div>
|
|
6011
6316
|
<div id="proposals-list"><p class="muted">Loading\u2026</p></div>
|
|
6012
|
-
</section>`}a(
|
|
6317
|
+
</section>`}a(qf,"renderMcpPageProposalsSection");function Wf(){return`
|
|
6013
6318
|
<section class="panel" id="section-logs" aria-label="Audit log" hidden>
|
|
6014
6319
|
<h2>Audit log</h2>
|
|
6015
6320
|
<div id="logs-flash" class="flash" hidden></div>
|
|
@@ -6025,7 +6330,7 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
6025
6330
|
</label>
|
|
6026
6331
|
</div>
|
|
6027
6332
|
<div id="logs-list"><p class="muted">Loading\u2026</p></div>
|
|
6028
|
-
</section>`}a(
|
|
6333
|
+
</section>`}a(Wf,"renderMcpPageLogsSection");function zf(){return`
|
|
6029
6334
|
<dialog id="reject-dialog">
|
|
6030
6335
|
<form method="dialog" id="reject-form">
|
|
6031
6336
|
<h3>Reject proposal</h3>
|
|
@@ -6067,7 +6372,7 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
6067
6372
|
<button type="submit" value="confirm" class="action danger" id="confirm-action-btn">Confirm</button>
|
|
6068
6373
|
</div>
|
|
6069
6374
|
</form>
|
|
6070
|
-
</dialog>`}a(
|
|
6375
|
+
</dialog>`}a(zf,"renderMcpPageDialogs");function Ba(e){let t={apiBase:e.apiBase,mcpRpc:e.mcpRpc??"/mcp/rpc",dashboardPath:e.dashboardPath??"/ai-access",editorRoot:e.editorRoot,authTokenKey:e.authTokenKey};return`<!DOCTYPE html>
|
|
6071
6376
|
<html lang="en">
|
|
6072
6377
|
<head>
|
|
6073
6378
|
<meta charset="utf-8" />
|
|
@@ -6075,21 +6380,23 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
6075
6380
|
<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate" />
|
|
6076
6381
|
<meta http-equiv="Pragma" content="no-cache" />
|
|
6077
6382
|
<title>Node-RED AI Access</title>
|
|
6078
|
-
<style>${
|
|
6383
|
+
<style>${Ua()}</style>
|
|
6079
6384
|
</head>
|
|
6080
6385
|
<body>
|
|
6081
6386
|
<main
|
|
6082
6387
|
id="mcp-root"
|
|
6083
|
-
data-config="${
|
|
6084
|
-
>${
|
|
6085
|
-
${
|
|
6086
|
-
${
|
|
6087
|
-
${
|
|
6088
|
-
${
|
|
6089
|
-
${
|
|
6388
|
+
data-config="${Mf(JSON.stringify(t))}"
|
|
6389
|
+
>${Df()}
|
|
6390
|
+
${jf()}
|
|
6391
|
+
${Uf()}
|
|
6392
|
+
${Hf()}
|
|
6393
|
+
${Bf()}
|
|
6394
|
+
${qf()}
|
|
6395
|
+
${Wf()}
|
|
6396
|
+
${zf()}
|
|
6090
6397
|
</main>
|
|
6091
|
-
<script>${
|
|
6398
|
+
<script>${Ha()}</script>
|
|
6092
6399
|
</body>
|
|
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
|
|
6400
|
+
</html>`}a(Ba,"renderMcpPage");Ae();ve();Ge();G();it();K();Fe();Xe();Ae();function Tt(e,t,n,r={}){e.setHeader("X-Content-Type-Options","nosniff"),e.setHeader("Content-Type","application/json; charset=utf-8");let o={error:Oe[n]??"Request failed",code:n,...r},s=se(n,"rest");s&&typeof o.hint!="string"&&(o.hint=s),e.status(t).json(o)}a(Tt,"sendSafeJsonError");function qa(e){let t={kind:"admin"};return typeof e.user?.username=="string"&&(t.username=e.user.username),t}a(qa,"adminActor");function Wa(e,t,n,r,o){return async(s,i)=>{if(!s.accessLink){i.setHeader("WWW-Authenticate",Y),i.status(401).json({error:le,code:Le});return}try{let d=q(s,r),l=re(s.query),c=await Ye(e,t,s.accessLink,s.body??{},n,d,o),u=V(e,c.expectedRev,{flowId:c.payload.flowId}),p=Ie(e,e.get(c.id),s.accessLink.id),f=c.warnings;i.status(201).json({...ce(c,{...d,include:l}),mine:!0,...Ne(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(Wa,"createProposalHandler");function za(e,t,n){return async(r,o)=>{let s,i;try{let S=await t.getFlows();s=S.rev,i=dr(S.flows)}catch{}let d={...q(r,n),...i?{liveFlowLabels:i}:{}},l=re(r.query),c;try{c=mr(r.query)}catch(S){$(o,S);return}let u=s?V(e,s):0,p=r.accessLink;if(p){let S=qt(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,...ft({...S,returned:S.proposals.length}),...Q(u)});return}let f={};c.flowId&&(f.flowId=c.flowId);let m=Ys(e.list(f).filter(S=>Co(S.status,c.status)),{...d,include:l}),h=c.offset??0,x=(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:x,...ft({total:m.length,limit:c.limit??null,offset:h,returned:x.length}),...Q(u)})}}a(za,"listProposalsHandler");function Ga(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=re(r.query),l;if(n)try{l=(await n.getFlows()).rev}catch{}try{if(r.accessLink){let c=De(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 ne){Tt(o,404,c.code);return}let u=O(c,"rest");if(u){o.status(u.statusCode).json(u.body);return}$(o,c);return}}}a(Ga,"getProposalHandler");function Ja(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,qa(n),i);r.json(oe(d,q(n,t)))}catch(d){if(d instanceof ne||d instanceof te||d instanceof kn||d instanceof X){Tt(r,d.statusCode,d.code);return}$(r,d);return}}}a(Ja,"approveProposalHandler");function Ka(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=oe(e.reject(o,qa(n),i),q(n,t));r.json(d)}catch(d){if(d instanceof ne||d instanceof te||d instanceof X){Tt(r,d.statusCode,d.code);return}$(r,d);return}}}a(Ka,"rejectProposalHandler");function Va(e,t,n){return(r,o)=>{if(!r.accessLink){o.setHeader("WWW-Authenticate",Y),o.status(401).json({error:le,code:Le});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=re(r.query);try{let c=Wt(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){Tt(o,u.statusCode,u.body.code);return}Tt(o,500,"internal_error");return}}}a(Va,"withdrawProposalHandler");function Xa(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?De(e,n.accessLink,o):e.get(o),i=Yt(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=Qt(await nn(s,t),i.format);r.json(d)}catch(s){if(s instanceof ne){Tt(r,404,s.code);return}if(s instanceof z||s instanceof be||s instanceof Ce){Tt(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(Xa,"getProposalDiffHandler");ve();ve();or();function Gf(e){return e.endsWith("/")?e.slice(0,-1):e}a(Gf,"trimTrailingSlash");var Wr=class{static{a(this,"HttpAdapter")}kind="http";baseUrl;accessToken;fetchImpl;constructor(t){this.baseUrl=Gf(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 z(n):t.status===409?new J("unknown","unknown",`Admin API ${n}`):t.status===401||t.status===403?new Ce(Qn):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 An(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 z(t);let s=await this.readJson(r,"GET /flows"),i=await this.readJson(o,`GET /flow/${t}`);return{rev:An(s,"GET /flows").rev,flow:$t(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=rr(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();or();function Jf(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(Jf,"requireFlowsApi");function Kf(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(Kf,"resolveRev");var zr=class{static{a(this,"RedAdapter")}kind="red";flows;constructor(t){this.flows=Jf(t)}async getFlows(){let t=await this.flows.getFlows({});return An(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 Vf(o)?new z(t):o}return{rev:n.rev,flow:$t(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=rr(r.flows,t.flowId,t.flow),s=await this.flows.setFlows({flows:{flows:o,rev:t.expectedRev},deploymentType:n});return{flowId:t.flowId,rev:Kf(s)}}};function Ya(e){let t=e?.runtime?.flows;return!!(t?.getFlows&&t.getFlow&&t.setFlows)}a(Ya,"canUseRedRuntime");function Vf(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(Vf,"isRuntimeFlowNotFound");var Qa=require("module");function Za(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(Za,"isFlowsApi");function Xf(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,Qa.createRequire)(e)}a(Xf,"createPluginRequire");function Yf(e){try{let t=e("@node-red/runtime/lib/settings");return typeof t.available=="function"&&t.available()===!0}catch{return!1}}a(Yf,"isNodeRedRuntimeInitialized");function Qf(){try{let e=Xf();if(!Yf(e))return null;let t=e("@node-red/runtime");return Za(t.flows)?t.flows:null}catch{return null}}a(Qf,"tryRequireInitializedRuntimeFlows");function ed(e){if(e?.runtime&&Za(e.runtime.flows))return e;let t=Qf();return t?{runtime:{flows:t}}:null}a(ed,"resolveRuntimeHost");function td(e={}){let t=ed(e.RED??null);if(t&&Ya(t))return new zr(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 Wr(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(td,"createFlowAdapter");ve();var Jr=require("fs"),nd=require("path");er();G();var Zf=/^[a-z][a-z0-9._-]{0,63}$/;function em(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(em,"sanitizeActor");var Gr=class{constructor(t,n={}){this.paths=t;this.filePath=(0,nd.join)(t.logs,"audit.jsonl"),this.now=n.now??(()=>Date.now()),this.maxAppendBytes=n.maxAppendBytes??cs}static{a(this,"AuditLog")}filePath;now;maxAppendBytes;seq=0;append(t,n,r){let o=Zf.test(t)?t:"unknown";this.seq+=1;let s={id:`log-${this.now()}-${this.seq}`,at:new Date(this.now()).toISOString(),action:o,actor:em(n)};r!==void 0&&(s.details=_s(r));let i=Be(s,this.maxAppendBytes,{kind:"audit"});return ps(this.paths.root,this.filePath,i),s}list(t=100){if(!(0,Jr.existsSync)(this.filePath))return[];let n=(0,Jr.readFileSync)(this.filePath,"utf8").split(`
|
|
6401
|
+
`).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()}};Ge();Gn();ie();ut();ve();Ge();Xe();Ae();var tm="Flow revision conflict: the expectedRev is stale. Ask the AI to re-read flows and resubmit a new proposal.";function rd(e){let t=oe(e),n=cr(e)||void 0;return{proposalId:e.id,title:t.title,displayName:t.displayName,flowId:e.payload.flowId,...n?{flowLabel:n}:{}}}a(rd,"auditProposalMeta");var Kr=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=rd(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}),ei(this.proposals,o.payload.flowId,t,l.rev),c}catch(l){if(l instanceof J){let p=this.proposals.markRejected(t,n,tm);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=rd(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 nm(e){let t=a(()=>Promise.reject(new Ce(e)),"fail");return{kind:"http",getFlows:t,getFlow:t,updateFlow:t}}a(nm,"misconfiguredAdapter");function od(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(od,"localAdminBaseUrl");function Vr(e,t={}){let n=Bn(e.settings.userDir);fs(n);let r=t.settingsStore??new Lt(n,e.settings.aiAccess,u=>e.log?.warn?.(u));t.bindSettings!==!1&&Jn(r),Vn(r.getEffective().publicBaseUrl);let o=t.linkStore??Ur(e);o.configureDefaults({defaultTtlSeconds:r.getEffective().defaultLinkTtlSeconds,maxActiveLinks:r.getEffective().maxActiveLinks});let s=new tr(n),i=new Gr(n),d;if(t.flowAdapter)d=t.flowAdapter;else try{d=td({RED:e,http:{baseUrl:od(e)},adminAuthEnabled:W(e.settings)})}catch(u){let p=u instanceof be?u.message:Qn;e.log?.warn?.(`AI Access flow adapter unavailable: ${p}`),d=nm(p)}let l;if(t.nodeTypesAdapter)l=t.nodeTypesAdapter;else if(t.flowAdapter)l=ho();else try{l=Hs({baseUrl:od(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 Kr(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(Vr,"createPluginServices");G();ie();_e();qn();var sd=ln(require("dns"),1),id=ln(require("http"),1),ad=ln(require("https"),1),Dn=require("net");ie();function rm(e,t){return e===10||e===172&&t>=16&&t<=31||e===192&&t===168}a(rm,"isPrivateV4");function om(e,t){return e===169&&t===254}a(om,"isLinkLocalV4");function sm(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(sm,"isSpecialUseV4");function im(e){let[t=0,n=0,r=0]=e.split(".").map(Number);return t===127?"loopback":rm(t,n)||om(t,n)||sm(t,n,r)||e==="168.63.129.16"?"blocked":"public"}a(im,"classifyIpv4");function am(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(am,"expandIpv6");function dm(e){let t=am(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(dm,"classifyIpv6");function jn(e){let t=at(e);return t?(0,Dn.isIP)(t)===4?im(t):dm(t):"blocked"}a(jn,"classifyPublicUrlAddress");function lm(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(lm,"isMetadataHostname");async function dd(e,t,n){let r=Ct(e);if(!r)throw new Error("publicBaseUrl is required for Check");let o=new URL(r),s=o.hostname.replace(/^\[|\]$/g,"");if(lm(s))throw new Error("Public URL Check cannot target metadata hosts");let i=at(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&&jn(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 x=jn(g);if(x==="blocked"||x==="loopback"&&!c||c&&x!=="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,Dn.isIP)(i)};let f=await new Promise((g,x)=>{let S=a(()=>x(n.reason instanceof Error?n.reason:new Error(String(n.reason))),"abort");n.throwIfAborted(),n.addEventListener("abort",S,{once:!0}),sd.default.lookup(s,{all:!0},(E,I)=>{n.removeEventListener("abort",S),E?x(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 x=at(g.address);if(!x)throw new Error("Public URL Check returned an invalid address");m.some(S=>S.address===x)||m.push({address:x,family:(0,Dn.isIP)(x)})}c&&m.sort((g,x)=>g.family-x.family);let h=m[0];if(!h)throw new Error("Public URL Check hostname has no addresses");return{origin:r,...h,addresses:m}}a(dd,"preparePublicUrlCheck");function cm(e,t,n){let r=e.hostname.replace(/^\[|\]$/g,"");return new Promise((o,s)=>{let d=(e.protocol==="https:"?ad.default:id.default).request(e,{method:"GET",agent:!1,signal:n,headers:{Host:e.host},...(0,Dn.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(cm,"requestPinned");async function ld(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 cm(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(ld,"requestPublicUrlCheck");ye();function um(e){return hn(e.settings.httpAdminRoot)}a(um,"mcpPageUrl");function pm(e){return typeof e=="boolean"||typeof e=="number"||typeof e=="string"?e:Array.isArray(e)?e.join(", "):typeof e=="function"?"custom function":!1}a(pm,"normalizeTrustProxy");function pd(e){if(!e)return"Set a Public URL before enabling OAuth.";let t=new URL(e),n=at(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(pd,"oauthPublicUrlWarning");function fd(e,t,n){let{sources:r,...o}=t.getEffective(),{permission:s}=Kn(e,n);return{ok:!0,adminAuthEnabled:W(e.settings),canEdit:s==="*",permission:s,mcpPageUrl:um(e),apiBase:de(e.settings.httpAdminRoot),settings:o,oauthPublicUrlWarning:pd(o.publicBaseUrl),sources:r,disk:t.getDisk(),trustProxy:pm(n.app.get("trust proxy")),precedence:["settings.js","disk","default"]}}a(fd,"settingsResponse");function md(e,t){return(n,r)=>{r.json(fd(e,t,n))}}a(md,"getSettingsHandler");function cd(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(cd,"parseBooleanField");function ud(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(ud,"parsePositiveIntField");function fm(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(fm,"parseRateLimitField");function mm(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=>pn(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(mm,"parseCorsField");function hd(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}cd(i,"enabled",d),cd(i,"allowWithoutAdminAuth",d),ud(i,"defaultLinkTtlSeconds",d),ud(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=Ct(i.publicBaseUrl)),mm(i,d);for(let l of["rateLimitWindowMs","rateLimitMaxPerIp","rateLimitMaxPerToken"])fm(i,l,d)}catch(l){o.status(400).json({error:l.message,code:"invalid_body"});return}try{let l=t.patch(d);Vn(l.publicBaseUrl),n.configureDefaults({defaultTtlSeconds:l.defaultLinkTtlSeconds,maxActiveLinks:l.maxActiveLinks});let c=fd(e,t,r),u=l.publicBaseUrl?new URL(l.publicBaseUrl):void 0,p=u?at(u.hostname.replace(/^\[|\]$/g,"")):void 0,f=p?jn(p):void 0,m=[];f!==void 0&&f!=="public"&&m.push("Public URL uses a private or non-public IP address.");let h=pd(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(hd,"putSettingsHandler");function gd(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"||jn(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 dd(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 ld(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(gd,"checkPublicUrlHandler");ye();it();ye();K();Gn();var hm="com55-ai-access";function wd(e){let t=new Set;for(let n of String(e??"").split(",")){let[r,...o]=n.split(";").map(d=>d.trim().toLowerCase());if(!r)continue;let s=o.find(d=>d.startsWith("q=")),i=s?Number(s.slice(2)):1;Number.isFinite(i)&&i>0&&t.add(r)}return t}a(wd,"acceptedMediaTypes");function bd(e){let t=wd(e.headers.accept);return t.has("text/event-stream")&&!t.has("text/html")}a(bd,"isMcpStreamRequest");function gm(e,t,n){e.httpAdmin.get("/mcp/api/v1",t,n,Mo(e)),e.httpAdmin.get("/mcp/api/v1/",t,n,Mo(e)),e.httpAdmin.get("/mcp/api/v1/openapi.yaml",t,n,Ma()),e.httpAdmin.get("/mcp/api/v1/openapi.json",t,n,Da()),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",Xt),o.type("application/javascript").status(200).send(yr())})}a(gm,"registerMcpShellAndDiscovery");var ym=/^\/mcp\/(?:api\/v1\/proposals|rpc$|a\/[^/]+\/(?:proposals|propose|draft))/;function vd(e,t={}){let n=Vr(e,{...t,linkStore:t.linkStore??Ur(e)}),r=Ei(()=>n.settingsStore.getEffective()),{ip:o,token:s}=r,i=new Br(Bn(e.settings.userDir),()=>n.settingsStore.getEffective().oauth.dcrMaxClients),d=a(()=>n.settingsStore.getEffective().publicBaseUrl,"publicBase"),l=a(()=>`${d().replace(/\/$/,"")}/mcp`,"mcpUrl"),c=new qr(i,l,()=>n.settingsStore.getEffective().oauth.accessTokenTtlSec,()=>n.settingsStore.getEffective().oauth.refreshIdleTtlSec,()=>n.settingsStore.getEffective().oauth.refreshMaxLifetimeSec),u=Na(e,c,d,l);hs(e.httpAdmin,ms(()=>n.settingsStore.getEffective().corsAllowedOrigins));let p=a(w=>(Array.isArray(w)?w:w?[w]:[]).flatMap(b=>{if(typeof b=="string")return[b];let k=b;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=Ia({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:yd.existsSync}),b=w.authorization,k=w.metadata;f.authorization=b.map(os),f.metadata=k.map(os),f.details=[...b.map(M=>({...M,kind:"authorization"})),...k.map(M=>({...M,kind:"metadata"}))];let L=[...f.authorization.map(M=>`${M} \u2014 OAuth authorization routes disabled`),...f.metadata.map(M=>`${M} \u2014 OAuth metadata disabled`)],H=L.join(`
|
|
6402
|
+
`);if(H!==m)for(let M of L)e.log?.warn?.(`AI Access OAuth route collision: ${M}`);m=H},"refreshCollisions");h(),e.events?.on("flows:started",h);let g=Ho({windowMs:6e4,maxPerIp:30}),x=a((w,b,k)=>{if(!n.settingsStore.getEffective().oauth.enabled||f.authorization.length||!w.path.startsWith("/mcp/oauth/")){k();return}let L=w.path.replace(/\/+$/,"").toLowerCase(),H=a(()=>Et(e)(w,b,()=>u.server(w,b,k)),"run");L==="/mcp/oauth/authorize"?o(w,b,()=>g(w,b,H)):L.startsWith("/mcp/oauth/consent/")?o(w,b,H):H()},"oauthServerMiddleware");x.oauthOwned=!0,e.httpAdmin.use(x);let S=a((w,b,k)=>{if(!n.settingsStore.getEffective().oauth.enabled||f.metadata.length){k();return}u.metadata(w,b,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,b)=>b.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,b)=>{let k=!W(e.settings)||We(w)==="*";b.json({...f,details:k?f.details:f.details.map(({file:L,...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"),gn(e),(w,b)=>b.json({revoked:i.revokeClient(String(w.params.id))})),e.httpAdmin.use((w,b,k)=>{if(!ym.test(w.path)){k();return}n.flowAdapter.getFlows().then(L=>Vs(w,dr(L.flows))).catch(()=>{}).finally(()=>k())}),na(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}),e.httpAdmin.get("/ai-access",o,s,(w,b)=>{b.setHeader("Cache-Control","no-store, no-cache, must-revalidate, max-age=0"),b.setHeader("Pragma","no-cache"),b.setHeader("Expires","0"),b.type("html").status(200).send(Ba({apiBase:de(e.settings.httpAdminRoot),mcpRpc:qe(e.settings.httpAdminRoot),editorRoot:ae(e.settings.httpAdminRoot),authTokenKey:Yn(e.settings.httpAdminRoot),dashboardPath:hn(e.settings.httpAdminRoot)}))}),e.httpAdmin.get("/mcp",o,(w,b,k)=>{if(bd(w)){k();return}b.setHeader("Cache-Control","no-store"),b.redirect(302,hn(e.settings.httpAdminRoot))}),gm(e,o,s),e.httpAdmin.get("/mcp/api/v1/status",o,s,vs(e,w=>n.linkStore.validateResult(w)),R((w,b)=>{b.json(rn(e,w))})),e.httpAdmin.get("/mcp/api/v1/settings",o,s,we(e,"api"),R(md(e,n.settingsStore))),e.httpAdmin.put("/mcp/api/v1/settings",o,s,we(e,"api"),gn(e),je("rest"),R(hd(e,n.settingsStore,n.linkStore))),e.httpAdmin.post("/mcp/api/v1/settings/check-public-url",o,s,we(e,"api"),gn(e),je("rest"),R(gd(e)));let E=[o,s,we(e,"api"),Et(e),gn(e)];e.httpAdmin.get("/mcp/api/v1/links",...E,R(ua(e,n.linkStore))),e.httpAdmin.post("/mcp/api/v1/links",...E,R(pa(e,n.linkStore,n.auditLog))),e.httpAdmin.delete("/mcp/api/v1/links/:id",...E,R(fa(e,n.linkStore,n.auditLog)));let I=[o,s,Et(e),bs(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,R(vi(N))),e.httpAdmin.get("/mcp/api/v1/flows/:id",...I,R(Lr(N))),e.httpAdmin.get("/mcp/api/v1/nodes/:id",...I,R(Cr(N))),e.httpAdmin.get("/mcp/api/v1/nodes/:id/field/:name",...I,R(Er(N))),e.httpAdmin.get("/mcp/api/v1/search",...I,R(Ir(N))),e.httpAdmin.get("/mcp/api/v1/node-types",...I,R(Nr(me))),e.httpAdmin.get("/mcp/api/v1/node-types/:type",...I,R(Fr(me))),e.httpAdmin.get("/mcp/api/v1/palette/search",...I,R(Or(me)));let A=e.settings.httpAdminRoot;e.httpAdmin.get("/mcp/api/v1/proposals",...I,R(za(n.proposalStore,n.flowAdapter,A))),e.httpAdmin.get("/mcp/api/v1/proposals/:id",...I,R(Ga(n.proposalStore,A,n.flowAdapter))),e.httpAdmin.get("/mcp/api/v1/proposals/:id/diff",...I,R(Xa(n.proposalStore,n.flowAdapter))),e.httpAdmin.post("/mcp/api/v1/proposals",o,s,je("rest"),Nt(e,n.linkStore,"rest"),R(Wa(n.proposalStore,n.auditLog,n.flowAdapter,A,n.nodeTypesAdapter))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/approve",...E,je("rest"),R(Ja(n.proposalService,A))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/reject",...E,je("rest"),R(Ka(n.proposalService,A))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/withdraw",o,s,je("rest"),Nt(e,n.linkStore,"rest"),R(Va(n.proposalStore,n.auditLog,A)));let v=[o,s,(w,b,k)=>{let L=w.headers.authorization;if(!n.settingsStore.getEffective().oauth.enabled||L?.toLowerCase().startsWith("bearer aia_")){if(n.settingsStore.getEffective().oauth.enabled){let H=b.setHeader.bind(b);b.setHeader=((M,Re)=>{if(M.toLowerCase()==="www-authenticate"&&typeof Re=="string"&&!Re.includes("resource_metadata=")){let Ue=`${d().replace(/\/$/,"")}${f.httpNodeRootFallback?"/mcp":""}/.well-known/oauth-protected-resource/mcp`;Re+=`, resource_metadata="${Ue}", scope="read propose"`}return H(M,Re)})}Nt(e,n.linkStore,"mcp")(w,b,k);return}Et(e)(w,b,()=>u.auth(w,b,k))}];e.httpAdmin.post("/mcp/rpc",...v,je("mcp"),R(Qo(e,n))),e.httpAdmin.get("/mcp/rpc",...v,On),e.httpAdmin.delete("/mcp/rpc",...v,On),e.httpAdmin.post("/mcp",...v,je("mcp"),R(Qo(e,n))),e.httpAdmin.get("/mcp",...v,On),e.httpAdmin.delete("/mcp",...v,On),e.httpAdmin.get("/mcp/api/v1/logs",o,s,we(e,"api"),Et(e),R(ma(n.auditLog,n.linkStore))),e.httpAdmin.use("/mcp/api/v1",yi()),ia(e)}a(vd,"registerRoutes");function wm(e){e.plugins.registerPlugin(hm,{type:"com55-ai-access",onadd(){vd(e),e.log?.info?.("Node-RED AI Access plugin loaded (dashboard /ai-access, MCP /mcp)")}})}a(wm,"registerAiAccessPlugin");var bm=wm;0&&(module.exports={ACCESS_LINK_INVALID_MESSAGE,ACCESS_LINK_MISSING_MESSAGE,AccessLinkStore,EDITOR_SIDEBAR_ASSET_VERSION,MAX_PROPOSAL_JSON_BYTES,SettingsStore,acceptedMediaTypes,areAiFeaturesEnabled,bindSettingsStore,createPluginServices,getStorageDir,isAdminAuthEnabled,isMcpStreamRequest,packageVersion,registerRoutes,renderEditorSidebarJs,renderEditorSidebarLoaderHtml,requireAccessLink,resolveAiAccessSettings});
|