@interactive-inc/claude-funnel 0.31.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/bin.js +256 -256
- package/dist/gateway/daemon.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -5
- package/funnel.schema.json +4 -0
- package/package.json +1 -1
package/dist/gateway/daemon.js
CHANGED
|
@@ -532,7 +532,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
532
532
|
`)}}var xD1=RF($D1(),1);var JN0=($)=>{let x={};for(let[Y,X]of Object.entries($))x[Y]=X;return x},qN0=($)=>{if(!$||typeof $!=="object")return null;if(!("code"in $))return null;let x=$.code;if(typeof x!=="string"||!x.startsWith("slack_webapi_"))return null;if(!("data"in $))return null;let Y=$.data;if(!Y||typeof Y!=="object")return null;return Y};class Vk extends MY{client;constructor($){super();let x=V7({literal:$.config.botToken,envVar:$.config.botTokenEnv,env:$.env??process.env,label:`${$.config.name}.botToken`});this.client=$.client??new xD1.WebClient(x),Object.freeze(this)}async call($){let x=$.body!==null&&typeof $.body==="object"?JN0($.body):{};try{return await this.client.apiCall($.path,x)}catch(Y){let X=qN0(Y);if(X)return X;throw Y}}}var kB=RF(mk1(),1);var bd0=["type","subtype","user","bot_id","text","ts","thread_ts","channel","channel_type","files","attachments"],Rd0=["id","name","mimetype","filetype","size","url_private","permalink"],Ad0=["title","text","fallback"],AB=($)=>{return typeof $==="object"&&$!==null&&!Array.isArray($)},oE=($,x)=>{let Y={};for(let X of x)if($[X]!==void 0)Y[X]=$[X];return Y},kd0=($)=>{return Object.keys($).some((x)=>x.startsWith("thumb")||x.startsWith("preview"))},cd0=($)=>{if(!AB($))return $;let x=oE($,Rd0);if(kd0($))x._funnel_omitted=["thumb_*"];return x},rE=($)=>{if(!AB($))return"";let x=$.text;if(typeof x==="string")return x;let Y=$.elements;if(!Array.isArray(Y))return"";return Y.map(rE).join("")},Td0=($)=>{if(!Array.isArray($))return"";return $.map(rE).join("\t")},Sd0=($)=>{if(!AB($))return"";if($.type==="table"&&Array.isArray($.rows))return $.rows.map(Td0).join(`
|
|
533
533
|
`);return rE($)},Ed0=($)=>{return $.map(Sd0).filter((x)=>x.length>0).join(`
|
|
534
534
|
`)},ud0=($)=>{if(!AB($))return $;let x=oE($,Ad0),Y=$.blocks;if(Array.isArray(Y)){let X=Ed0(Y),Q=typeof x.text==="string"?x.text:"";x.text=Q?`${Q}
|
|
535
|
-
${X}`:X,x._funnel_omitted=["blocks"]}return x},hk1=($)=>{let x=oE($,bd0);if(Array.isArray(x.files))x.files=x.files.map(cd0);if(Array.isArray(x.attachments))x.attachments=x.attachments.map(ud0);return x};var Cd0=new Set(["message","app_mention"]),yd0=new Set([void 0,"thread_broadcast","bot_message","file_share"]),md0=1e4,zx=($,x)=>{let Y=$[x];return typeof Y==="string"?Y:void 0};class tE{ownBotUserId;ownBotId;minify;now;dedup=new Map;constructor($){this.ownBotUserId=$.ownBotUserId,this.ownBotId=$.ownBotId,this.minify=$.minify??!0,this.now=$.now??(()=>Date.now())}process($){let x=zx($,"type");if(!x||!Cd0.has(x))return{skip:!0,reason:"skip:type"};let Y=zx($,"subtype");if(!yd0.has(Y))return{skip:!0,reason:"skip:subtype"};let X=zx($,"channel")??"",Q=zx($,"event_ts")??zx($,"ts")??"",z=`${X}:${Q}`,Z=this.now();if(this.dedup.has(z))return{skip:!0,reason:"skip:dedup"};this.dedup.set(z,Z);for(let J of this.dedup.keys())if((this.dedup.get(J)??0)<Z-md0)this.dedup.delete(J);let K=zx($,"user"),V=zx($,"bot_id");if(K===this.ownBotUserId)return{skip:!0,reason:"skip:self-user"};if(V===this.ownBotId)return{skip:!0,reason:"skip:self-bot"};let w=(zx($,"text")??"").includes(`<@${this.ownBotUserId}>`),H=zx($,"thread_ts")??zx($,"ts")??"",G=this.minify?hk1($):$;return{skip:!1,content:JSON.stringify(G),meta:{event_type:"slack",channel_id:X,user_id:K??"",mentioned:String(w),thread_ts:H},shouldReact:w,channel:X,timestamp:zx($,"ts")??""}}}var hd0=S.object({event:S.record(S.string(),S.unknown()).optional()});class eE extends _7{config;channelId;env;logger;diagnosticLog;onAppCreated;preprocessEvent;app=null;constructor($){super();this.config=$.config,this.channelId=$.channelId??null,this.env=$.env??process.env,this.logger=$.logger,this.diagnosticLog=$.diagnosticLog,this.onAppCreated=$.onAppCreated??null,this.preprocessEvent=$.preprocessEvent??null}async start($){this.recordConnection("started","");let x=V7({literal:this.config.botToken,envVar:this.config.botTokenEnv,env:this.env,label:`${this.config.name}.botToken`}),Y=V7({literal:this.config.appToken,envVar:this.config.appTokenEnv,env:this.env,label:`${this.config.name}.appToken`}),X=new kB.App({token:x,appToken:Y,socketMode:!0,logLevel:kB.LogLevel.ERROR}),Q;try{Q=await X.client.auth.test({token:x})}catch(K){throw this.recordConnection("auth-failed",IW(K)),K}let z=new tE({ownBotUserId:Q.user_id??"",ownBotId:Q.bot_id??"",minify:this.config.minify}),Z=this.preprocessEvent;if(X.use(async(K)=>{let V=hd0.safeParse(K);if(!V.success||!V.data.event){await K.next();return}let W=V.data.event,w=crypto.randomUUID();this.recordRaw(w,W);let H=Z?Z(W):W;if(H===null){this.recordProcessed(w,W,"skip:preprocess","");return}let G=z.process(H);if(G.skip){this.recordProcessed(w,H,G.reason,"");return}try{await $(G.content,G.meta)}catch(J){throw this.recordProcessed(w,H,"emitted:delivery-failed",G.content),J}if(this.recordProcessed(w,H,"emitted",G.content),G.shouldReact)try{await X.client.reactions.add({token:x,channel:G.channel,timestamp:G.timestamp,name:"eyes"})}catch{}}),X.error(async(K)=>{let V=IW(K);this.recordConnection("error",V),this.logger?.error("Slack error",{error:V})}),this.onAppCreated)await this.onAppCreated(X);try{await X.start()}catch(K){throw this.recordConnection("error",IW(K)),K}this.app=X,this.recordConnection("connected","")}async stop(){if(!this.app)return;try{await this.app.stop(),this.recordConnection("disconnected","")}catch($){this.recordConnection("error",IW($)),this.logger?.error("Slack stop error",{error:IW($)})}finally{this.app=null,this.recordConnection("stopped","")}}isAlive(){return this.app!==null}recordRaw($,x){this.diagnosticLog?.recordRaw({eventId:$,type:"slack",connectorId:this.config.id,channelId:this.channelId,payload:JSON.stringify(x)})}recordProcessed($,x,Y,X){this.diagnosticLog?.recordProcessed({eventId:$,type:"slack",connectorId:this.config.id,channelId:this.channelId,outcome:Y,payload:X||JSON.stringify(x)})}recordConnection($,x){this.diagnosticLog?.recordConnection({type:"slack",connectorId:this.config.id,channelId:this.channelId,status:$,detail:x})}}var IW=($)=>{return $ instanceof Error?$.message:String($)};import{join as $u}from"path";var gd0=new j8,pd0=new N8;class xu{fs;process;logger;diagnosticLog;dir;slackListenerOptions;scheduleListenerOptions;constructor($={}){this.fs=$.fs??gd0,this.process=$.process??pd0,this.logger=$.logger,this.diagnosticLog=$.diagnosticLog,this.dir=$.dir??m9,this.slackListenerOptions=$.slackListenerOptions??{},this.scheduleListenerOptions=$.scheduleListenerOptions??{},Object.freeze(this)}createListener($,x){if(x.type==="slack")return new eE({config:x,channelId:$,logger:this.logger,diagnosticLog:this.diagnosticLog,onAppCreated:this.slackListenerOptions.onAppCreated,preprocessEvent:this.slackListenerOptions.preprocessEvent});if(x.type==="gh")return new AR({config:x,channelId:$,process:this.process,logger:this.logger,diagnosticLog:this.diagnosticLog});if(x.type==="discord")return new bR({config:x,channelId:$,logger:this.logger,diagnosticLog:this.diagnosticLog});let Y=new cR({path:$u(this.connectorDir($,x.id),"state.json"),fs:this.fs});return new kR({config:x,lastFiredStore:Y,channelId:$,logger:this.logger,diagnosticLog:this.diagnosticLog,onFired:this.scheduleListenerOptions.onFired})}createAdapter($){if($.type==="slack")return new Vk({config:$});if($.type==="gh")return new RR({process:this.process});if($.type==="discord")return new TI({config:$});return null}connectorDir($,x){return $u(this.dir,"channels",$,"connectors",x)}channelDir($){return $u(this.dir,"channels",$)}}function Yu($){switch($.type){case"slack":return[$.botToken,$.appToken].filter((x)=>x!==void 0);case"discord":return[$.botToken].filter((x)=>x!==void 0);case"gh":case"schedule":return[]}}function id0($,x){return $.type===x}function nQ($,x,Y){let X=$.connectors.find((Q)=>Q.name===x);if(!X)throw Error(`connector "${x}" not found in channel "${$.name}"`);if(!id0(X,Y))throw Error(`connector "${x}" is type "${X.type}", not "${Y}"`);return X}class MW{millis(){return this.now().getTime()}iso(){return this.now().toISOString()}}class dQ extends MW{now(){return new Date}}var Qu=($,x,Y,X)=>{let Q=Y[$];if(Q!==void 0)return{[$]:Q};let z=Y[x];if(z!==void 0)return{[x]:z};let Z={},K=X[$],V=X[x];if(typeof K==="string")Z[$]=K;if(typeof V==="string")Z[x]=V;return Z},ld0=new dQ,nd0=new Q7;class Xu{store;factory;profileChecker;clock;idGenerator;constructor($){this.store=$.store,this.factory=$.factory,this.profileChecker=$.profileChecker,this.clock=$.clock??ld0,this.idGenerator=$.idGenerator??nd0,Object.freeze(this)}list(){return this.store.read().channels}get($){return this.list().find((x)=>x.name===$)??null}getById($){return this.list().find((x)=>x.id===$)??null}add($){let x=this.store.read();if(x.channels.some((X)=>X.name===$.name))throw Error(`channel "${$.name}" already exists`);let Y={id:this.idGenerator.generate(),name:$.name,delivery:$.delivery??"fanout",connectors:[]};return x.channels.push(Y),this.store.write(x),Y}setDelivery($,x){let Y=this.store.read(),X=this.requireChannel(Y,$);X.delivery=x,this.store.write(Y)}remove($){let x=this.store.read(),Y=x.channels.findIndex((Q)=>Q.name===$);if(Y<0)throw Error(`channel "${$}" not found`);let X=x.channels[Y];if(X&&this.profileChecker.hasChannelRef(X.id))throw Error(`channel "${$}" is referenced by a profile`);x.channels.splice(Y,1),this.store.write(x)}rename($,x){let Y=this.store.read(),X=Y.channels.find((Q)=>Q.name===$);if(!X)throw Error(`channel "${$}" not found`);if(Y.channels.some((Q)=>Q.name===x))throw Error(`channel "${x}" already exists`);X.name=x,this.store.write(Y)}listConnectors($){return this.requireChannel(this.store.read(),$).connectors}getConnector($,x){let Y=this.get($);if(!Y)return null;return Y.connectors.find((X)=>X.name===x)??null}listAllConnectors(){let $=[];for(let x of this.list())for(let Y of x.connectors)$.push({...Y,channelId:x.id,channelName:x.name});return $}addConnector($,x){let Y=this.store.read(),X=this.requireChannel(Y,$);if(X.connectors.some((z)=>z.name===x.name))throw Error(`connector "${x.name}" already exists in channel "${$}"`);let Q=this.fromInput(x);return this.assertNoTokenCollision(Y,Q),X.connectors.push(Q),this.store.write(Y),Q}fromInput($){let x=this.idGenerator.generate(),Y=this.clock.iso(),X=Y,Q=Y;switch($.type){case"slack":return{id:x,type:"slack",name:$.name,...$.botToken!==void 0?{botToken:$.botToken}:{},...$.appToken!==void 0?{appToken:$.appToken}:{},...$.botTokenEnv!==void 0?{botTokenEnv:$.botTokenEnv}:{},...$.appTokenEnv!==void 0?{appTokenEnv:$.appTokenEnv}:{},minify:$.minify??!0,createdAt:X,updatedAt:Q};case"gh":return{id:x,type:"gh",name:$.name,...$.pollInterval!==void 0?{pollInterval:$.pollInterval}:{},createdAt:X,updatedAt:Q};case"discord":return{id:x,type:"discord",name:$.name,...$.botToken!==void 0?{botToken:$.botToken}:{},...$.botTokenEnv!==void 0?{botTokenEnv:$.botTokenEnv}:{},createdAt:X,updatedAt:Q};case"schedule":return{id:x,type:"schedule",name:$.name,entries:$.entries??[],createdAt:X,updatedAt:Q}}}removeConnector($,x){let Y=this.store.read(),X=this.requireChannel(Y,$),Q=X.connectors.findIndex((z)=>z.name===x);if(Q<0)throw Error(`connector "${x}" not found in channel "${$}"`);X.connectors.splice(Q,1),this.store.write(Y)}renameConnector($,x,Y){let X=this.store.read(),Q=this.requireChannel(X,$),z=Q.connectors.find((Z)=>Z.name===x);if(!z)throw Error(`connector "${x}" not found in channel "${$}"`);if(Q.connectors.some((Z)=>Z.name===Y))throw Error(`connector "${Y}" already exists in channel "${$}"`);z.name=Y,z.updatedAt=this.clock.iso(),this.store.write(X)}updateSlackConnector($,x,Y){let X=this.store.read(),Q=this.requireChannel(X,$),z=nQ(Q,x,"slack"),Z={id:z.id,name:z.name,type:"slack",minify:z.minify,createdAt:z.createdAt,updatedAt:this.clock.iso(),...Qu("botToken","botTokenEnv",Y,z),...Qu("appToken","appTokenEnv",Y,z)};this.assertNoTokenCollision(X,Z),this.replaceConnector(Q,z.name,Z),this.store.write(X)}updateGhConnector($,x,Y){let X=this.store.read(),Q=this.requireChannel(X,$),z=nQ(Q,x,"gh");if(Y.pollInterval!==void 0)z.pollInterval=Y.pollInterval;z.updatedAt=this.clock.iso(),this.store.write(X)}updateDiscordConnector($,x,Y){let X=this.store.read(),Q=this.requireChannel(X,$),z=nQ(Q,x,"discord"),Z={id:z.id,name:z.name,type:"discord",createdAt:z.createdAt,updatedAt:this.clock.iso(),...Qu("botToken","botTokenEnv",Y,z)};this.assertNoTokenCollision(X,Z),this.replaceConnector(Q,z.name,Z),this.store.write(X)}listScheduleEntries($,x){let Y=this.requireChannel(this.store.read(),$);return nQ(Y,x,"schedule").entries}addScheduleEntry($,x,Y){let X=this.store.read(),Q=this.requireChannel(X,$),z=nQ(Q,x,"schedule"),Z={id:Y.id??this.idGenerator.generate(),cron:Y.cron,prompt:Y.prompt,enabled:Y.enabled??!0,catchupPolicy:Y.catchupPolicy??"latest"};return z.entries.push(Z),z.updatedAt=this.clock.iso(),this.store.write(X),Z}removeScheduleEntry($,x,Y){let X=this.store.read(),Q=this.requireChannel(X,$),z=nQ(Q,x,"schedule"),Z=z.entries.findIndex((K)=>K.id===Y);if(Z<0)throw Error(`schedule entry "${Y}" not found`);z.entries.splice(Z,1),z.updatedAt=this.clock.iso(),this.store.write(X)}async call($,x,Y){let X=this.getConnector($,x);if(!X)throw Error(`connector "${x}" not found in channel "${$}"`);let Q=this.factory.createAdapter(X);if(!Q)throw Error(`connector type "${X.type}" does not support outbound calls`);return await Q.call(Y)}createListener($,x){let Y=this.get($);if(!Y)return null;let X=Y.connectors.find((Q)=>Q.name===x);if(!X)return null;return{config:X,channelId:Y.id,listener:this.factory.createListener(Y.id,X)}}createAllListeners(){let $=[];for(let x of this.list())for(let Y of x.connectors)$.push({config:Y,channelId:x.id,channelName:x.name,listener:this.factory.createListener(x.id,Y)});return $}requireChannel($,x){let Y=$.channels.find((X)=>X.name===x);if(!Y)throw Error(`channel "${x}" not found`);return Y}replaceConnector($,x,Y){let X=$.connectors.findIndex((Q)=>Q.name===x);if(X<0)throw Error(`connector "${x}" not found in channel "${$.name}"`);$.connectors[X]=Y}assertNoTokenCollision($,x){let Y=Yu(x);if(Y.length===0)return;for(let X of $.channels)for(let Q of X.connectors){if(Q.id===x.id)continue;for(let z of Yu(Q))if(Y.includes(z))throw Error(`token already in use by connector "${Q.name}" in channel "${X.name}"`)}}}import{homedir as dd0}from"os";import{join as cB}from"path";var ad0=new N8,sd0=new j8,od0=new Q7;class zu{channels;mcp;gateway;profiles;process;fs;idGenerator;logger;pidDir;constructor($){this.channels=$.channels,this.mcp=$.mcp,this.gateway=$.gateway,this.profiles=$.profiles,this.process=$.process??ad0,this.fs=$.fs??sd0,this.idGenerator=$.idGenerator??od0,this.logger=$.logger,this.pidDir=cB($.dir??m9,"claude"),Object.freeze(this)}async launch($){let x=this.channels.get($.channel)??this.channels.getById($.channel);if(!x)throw Error(`channel "${$.channel}" not found`);if($.profileId&&this.isRunning($.profileId))throw Error(`profile "${$.profileId}" is already running`);let Y=$.cwd??globalThis.process.cwd();if(($.installMcp??!0)&&!this.mcp.findInstalledName(Y))this.mcp.install(Y),this.logger?.info("added funnel MCP to .mcp.json",{cwd:Y});if(!this.gateway.isRunning())this.logger?.info("starting gateway automatically"),await this.gateway.start();if($.profileId)this.writePidFile($.profileId),this.installCleanup($.profileId);let z=($.resume??!1)&&$.profileId?this.resolveSession($.profileId,Y,$.userArgs??[],$.env??{}):null,Z=this.buildArgs($.options??[],$.userArgs??[],Y,z),K=this.buildEnv(x.id,$.env??{});this.logger?.info("claude launch",{channel:$.channel,channelId:x.id,cwd:Y});try{return await this.process.attach(["claude",...Z],{cwd:Y,env:K,onSpawned:$.onSpawned})}finally{if($.profileId)this.removePidFile($.profileId)}}isRunning($){let x=this.readPid($);if(!x)return!1;return this.isProcessAlive(x)}pidPath($){return cB(this.pidDir,`${$}.pid`)}readPid($){let x=this.pidPath($);if(!this.fs.existsSync(x))return null;try{let Y=this.fs.readFileSync(x).trim(),X=Number(Y);if(!X||X<=0)return null;return X}catch{return null}}writePidFile($){this.fs.mkdirSync(this.pidDir,{recursive:!0}),this.fs.writeFileSync(this.pidPath($),String(globalThis.process.pid))}removePidFile($){let x=this.pidPath($);if(this.fs.existsSync(x))this.fs.unlink(x)}installCleanup($){globalThis.process.once("exit",()=>this.removePidFile($))}isProcessAlive($){return this.process.isAlive($)}buildArgs($,x,Y,X){let Q=[...$,...x];if(X!==null)if(X.mode==="resume")Q.push("--resume",X.id);else Q.push("--session-id",X.id);let z=this.mcp.findInstalledName(Y);if(z&&!Q.includes("--dangerously-load-development-channels")&&!Q.includes("--channels"))Q.push("--dangerously-load-development-channels",`server:${z}`);return Q}resolveSession($,x,Y,X){for(let Z of Y){if(Z==="-c"||Z==="--continue")return null;if(Z==="--resume"||Z.startsWith("--resume="))return null;if(Z==="--session-id"||Z.startsWith("--session-id="))return null}let Q=this.profiles.getSessionId($);if(Q!==null&&this.sessionFileExists(x,Q,X))return{id:Q,mode:"resume"};let z=this.idGenerator.generate();return this.profiles.setSessionId($,z),{id:z,mode:"new"}}sessionFileExists($,x,Y){let X=Y.CLAUDE_CONFIG_DIR??globalThis.process.env.CLAUDE_CONFIG_DIR??cB(dd0(),".claude"),Q=$.replace(/\//g,"-");return this.fs.existsSync(cB(X,"projects",Q,`${x}.jsonl`))}buildEnv($,x){let Y={};for(let[X,Q]of Object.entries(x))Y[X]=Q;for(let[X,Q]of Object.entries(globalThis.process.env))if(typeof Q==="string")Y[X]=Q;return Y.FUNNEL_CHANNEL_ID=$,Y}}var rd0=384;class Zu extends YK{dirs;files;mtimes;modes;now;constructor($={}){super();this.dirs=new Set($.dirs??[]),this.files=new Map(Object.entries($.files??{})),this.mtimes=new Map(Object.entries($.mtimes??{})),this.modes=new Map(Object.entries($.modes??{})),this.now=$.now??(()=>Date.now())}existsSync($){return this.dirs.has($)||this.files.has($)}readFileSync($){return this.files.get($)??""}writeFileSync($,x){this.files.set($,x),this.touch($)}writeSecretFileSync($,x){this.files.set($,x),this.modes.set($,rd0),this.touch($)}appendFileSync($,x){let Y=this.files.get($)??"";this.files.set($,Y+x),this.touch($)}unlink($){this.files.delete($),this.mtimes.delete($),this.modes.delete($)}mkdirSync($,x){this.dirs.add($)}readdirSync($){let x=$.endsWith("/")?$:`${$}/`,Y=[];for(let X of this.files.keys()){if(!X.startsWith(x))continue;let Q=X.slice(x.length);if(!Q.includes("/"))Y.push(Q)}return Y}statSync($){let x=this.mtimes.get($);if(x===void 0)throw Error(`not found: ${$}`);return{mtimeMs:x,mode:this.modes.get($)??null}}setMtime($,x){this.mtimes.set($,x)}setMode($,x){this.modes.set($,x)}touch($){if(!this.mtimes.has($))this.mtimes.set($,this.now());else this.mtimes.set($,this.now())}}class Ku extends QK{counter=0;prefix;constructor($={}){super();this.prefix=$.prefix??"id"}generate(){return this.counter++,`${this.prefix}-${this.counter}`}}import{join as za0}from"path";var td0=S.object({type:S.literal("slack"),name:S.string(),minify:S.boolean().optional()}),ed0=S.object({type:S.literal("discord"),name:S.string()}),$a0=S.object({type:S.literal("gh"),name:S.string(),pollInterval:S.number().int().positive().optional()}),xa0=S.object({type:S.literal("schedule"),name:S.string()}),Ya0=S.discriminatedUnion("type",[td0,ed0,$a0,xa0]),Qa0=S.object({name:S.string(),connectors:S.array(Ya0).optional()}),Xa0=S.object({channel:S.string(),options:S.array(S.string()).optional(),env:S.record(S.string(),S.string()).optional(),resume:S.boolean().optional()}),gk1=S.object({$schema:S.string().optional(),id:S.string().optional(),channels:S.array(Qa0).min(1),profiles:S.array(Xa0).optional()}),VY="funnel.json";class Vu{fs;constructor($){this.fs=$.fs,Object.freeze(this)}read($){let x=za0($,VY);if(!this.fs.existsSync(x))return null;let Y=this.fs.readFileSync(x),X=(()=>{try{return JSON.parse(Y)}catch(z){let Z=z instanceof Error?z.message:String(z);throw Error(`${VY} is not valid JSON: ${Z}`)}})(),Q=gk1.safeParse(X);if(!Q.success)throw Error(`${VY} is invalid: ${Q.error.message}`);return this.assertProfilesValid(Q.data),Q.data}assertProfilesValid($){let x=$.profiles??[];if(x.length===0)return;let Y=new Set($.channels.map((Q)=>Q.name)),X=new Set;for(let Q of x){if(!Y.has(Q.channel))throw Error(`${VY} is invalid: a profile binds channel "${Q.channel}", which is not declared in channels[]`);if(X.has(Q.channel))throw Error(`${VY} is invalid: channel "${Q.channel}" has more than one profile; only the first is applied \u2014 remove the extras`);X.add(Q.channel)}}}class Wu{channels;prompter;constructor($){this.channels=$.channels,this.prompter=$.prompter,Object.freeze(this)}async ensure($){if(!this.channels.get($.name))this.channels.add({name:$.name});if($.connectors===void 0)return{touched:[],removed:[]};let Y=[],X=new Set;for(let z of $.connectors){let Z=await this.ensureConnector($.name,z);Y.push({name:Z.name,changed:Z.changed}),X.add(Z.id)}let Q=this.removeExtras($.name,X);return{touched:Y,removed:Q}}async ensureConnector($,x){if(x.type==="slack")return await this.ensureSlack($,x);if(x.type==="discord")return await this.ensureDiscord($,x);if(x.type==="gh")return this.ensureGh($,x);return this.ensureSchedule($,x)}async ensureSlack($,x){let Y=this.findExistingSlack($,x.name),X=await this.resolveSlot({label:`${x.name}.botToken`,existingLiteral:Y?.botToken,existingEnv:Y?.botTokenEnv}),Q=await this.resolveSlot({label:`${x.name}.appToken`,existingLiteral:Y?.appToken,existingEnv:Y?.appTokenEnv}),z={botToken:X.token,botTokenEnv:X.tokenEnv,appToken:Q.token,appTokenEnv:Q.tokenEnv};if(Y){if(!(Y.botToken===X.token&&Y.botTokenEnv===X.tokenEnv&&Y.appToken===Q.token&&Y.appTokenEnv===Q.tokenEnv))return this.channels.updateSlackConnector($,x.name,z),{id:Y.id,name:x.name,changed:!0};return{id:Y.id,name:x.name,changed:!1}}return{id:this.channels.addConnector($,{type:"slack",name:x.name,...z,...x.minify!==void 0?{minify:x.minify}:{}}).id,name:x.name,changed:!0}}async ensureDiscord($,x){let Y=this.findExistingDiscord($,x.name),X=await this.resolveSlot({label:`${x.name}.botToken`,existingLiteral:Y?.botToken,existingEnv:Y?.botTokenEnv}),Q={botToken:X.token,botTokenEnv:X.tokenEnv};if(Y){if(Y.botToken!==X.token||Y.botTokenEnv!==X.tokenEnv)return this.channels.updateDiscordConnector($,x.name,Q),{id:Y.id,name:x.name,changed:!0};return{id:Y.id,name:x.name,changed:!1}}return{id:this.channels.addConnector($,{type:"discord",name:x.name,...Q}).id,name:x.name,changed:!0}}ensureGh($,x){let Y=this.channels.getConnector($,x.name);if(Y&&Y.type!=="gh")throw Error(`connector "${x.name}" exists in channel "${$}" with type "${Y.type}", funnel.json declares "gh"`);if(Y&&Y.type==="gh"){if(x.pollInterval!==void 0&&Y.pollInterval!==x.pollInterval)return this.channels.updateGhConnector($,x.name,{pollInterval:x.pollInterval}),{id:Y.id,name:x.name,changed:!0};return{id:Y.id,name:x.name,changed:!1}}return{id:this.channels.addConnector($,{type:"gh",name:x.name,...x.pollInterval!==void 0?{pollInterval:x.pollInterval}:{}}).id,name:x.name,changed:!0}}ensureSchedule($,x){let Y=this.channels.getConnector($,x.name);if(Y&&Y.type!=="schedule")throw Error(`connector "${x.name}" exists in channel "${$}" with type "${Y.type}", funnel.json declares "schedule"`);if(Y&&Y.type==="schedule")return{id:Y.id,name:x.name,changed:!1};return{id:this.channels.addConnector($,{type:"schedule",name:x.name}).id,name:x.name,changed:!0}}findExistingSlack($,x){let Y=this.channels.getConnector($,x);if(!Y)return null;if(Y.type!=="slack")throw Error(`connector "${x}" exists in channel "${$}" with type "${Y.type}", funnel.json declares "slack"`);return Y}findExistingDiscord($,x){let Y=this.channels.getConnector($,x);if(!Y)return null;if(Y.type!=="discord")throw Error(`connector "${x}" exists in channel "${$}" with type "${Y.type}", funnel.json declares "discord"`);return Y}removeExtras($,x){let Y=this.channels.get($);if(!Y)return[];let X=Y.connectors.filter((Q)=>!x.has(Q.id));for(let Q of X)this.channels.removeConnector($,Q.name);return X.map((Q)=>Q.name)}async resolveSlot($){if($.existingEnv!==void 0)return{token:void 0,tokenEnv:$.existingEnv};if($.existingLiteral!==void 0)return{token:$.existingLiteral,tokenEnv:void 0};return{token:await this.prompter.promptSecret($.label),tokenEnv:void 0}}}import{join as Za0}from"path";var Ka0=($)=>{return typeof $==="object"&&$!==null&&!Array.isArray($)},Va0=($,x)=>{let Y={};if($.$schema!==void 0)Y.$schema=$.$schema;Y.id=x;for(let X of Object.keys($)){if(X==="$schema"||X==="id")continue;Y[X]=$[X]}return Y};class wu{fs;constructor($){this.fs=$.fs,Object.freeze(this)}ensureId($,x){let Y=Za0($,VY);if(!this.fs.existsSync(Y))return;let X=JSON.parse(this.fs.readFileSync(Y));if(!Ka0(X))return;if(typeof X.id==="string"&&X.id!=="")return;let Q=Va0(X,x);this.fs.writeFileSync(Y,`${JSON.stringify(Q,null,2)}
|
|
535
|
+
${X}`:X,x._funnel_omitted=["blocks"]}return x},hk1=($)=>{let x=oE($,bd0);if(Array.isArray(x.files))x.files=x.files.map(cd0);if(Array.isArray(x.attachments))x.attachments=x.attachments.map(ud0);return x};var Cd0=new Set(["message","app_mention"]),yd0=new Set([void 0,"thread_broadcast","bot_message","file_share"]),md0=1e4,zx=($,x)=>{let Y=$[x];return typeof Y==="string"?Y:void 0};class tE{ownBotUserId;ownBotId;minify;now;dedup=new Map;constructor($){this.ownBotUserId=$.ownBotUserId,this.ownBotId=$.ownBotId,this.minify=$.minify??!0,this.now=$.now??(()=>Date.now())}process($){let x=zx($,"type");if(!x||!Cd0.has(x))return{skip:!0,reason:"skip:type"};let Y=zx($,"subtype");if(!yd0.has(Y))return{skip:!0,reason:"skip:subtype"};let X=zx($,"channel")??"",Q=zx($,"event_ts")??zx($,"ts")??"",z=`${X}:${Q}`,Z=this.now();if(this.dedup.has(z))return{skip:!0,reason:"skip:dedup"};this.dedup.set(z,Z);for(let J of this.dedup.keys())if((this.dedup.get(J)??0)<Z-md0)this.dedup.delete(J);let K=zx($,"user"),V=zx($,"bot_id");if(K===this.ownBotUserId)return{skip:!0,reason:"skip:self-user"};if(V===this.ownBotId)return{skip:!0,reason:"skip:self-bot"};let w=(zx($,"text")??"").includes(`<@${this.ownBotUserId}>`),H=zx($,"thread_ts")??zx($,"ts")??"",G=this.minify?hk1($):$;return{skip:!1,content:JSON.stringify(G),meta:{event_type:"slack",channel_id:X,user_id:K??"",mentioned:String(w),thread_ts:H},shouldReact:w,channel:X,timestamp:zx($,"ts")??""}}}var hd0=S.object({event:S.record(S.string(),S.unknown()).optional()});class eE extends _7{config;channelId;env;logger;diagnosticLog;onAppCreated;preprocessEvent;app=null;constructor($){super();this.config=$.config,this.channelId=$.channelId??null,this.env=$.env??process.env,this.logger=$.logger,this.diagnosticLog=$.diagnosticLog,this.onAppCreated=$.onAppCreated??null,this.preprocessEvent=$.preprocessEvent??null}async start($){this.recordConnection("started","");let x=V7({literal:this.config.botToken,envVar:this.config.botTokenEnv,env:this.env,label:`${this.config.name}.botToken`}),Y=V7({literal:this.config.appToken,envVar:this.config.appTokenEnv,env:this.env,label:`${this.config.name}.appToken`}),X=new kB.App({token:x,appToken:Y,socketMode:!0,logLevel:kB.LogLevel.ERROR}),Q;try{Q=await X.client.auth.test({token:x})}catch(K){throw this.recordConnection("auth-failed",IW(K)),K}let z=new tE({ownBotUserId:Q.user_id??"",ownBotId:Q.bot_id??"",minify:this.config.minify}),Z=this.preprocessEvent;if(X.use(async(K)=>{let V=hd0.safeParse(K);if(!V.success||!V.data.event){await K.next();return}let W=V.data.event,w=crypto.randomUUID();this.recordRaw(w,W);let H=Z?Z(W):W;if(H===null){this.recordProcessed(w,W,"skip:preprocess","");return}let G=z.process(H);if(G.skip){this.recordProcessed(w,H,G.reason,"");return}try{await $(G.content,G.meta)}catch(J){throw this.recordProcessed(w,H,"emitted:delivery-failed",G.content),J}if(this.recordProcessed(w,H,"emitted",G.content),G.shouldReact)try{await X.client.reactions.add({token:x,channel:G.channel,timestamp:G.timestamp,name:"eyes"})}catch{}}),X.error(async(K)=>{let V=IW(K);this.recordConnection("error",V),this.logger?.error("Slack error",{error:V})}),this.onAppCreated)await this.onAppCreated(X);try{await X.start()}catch(K){throw this.recordConnection("error",IW(K)),K}this.app=X,this.recordConnection("connected","")}async stop(){if(!this.app)return;try{await this.app.stop(),this.recordConnection("disconnected","")}catch($){this.recordConnection("error",IW($)),this.logger?.error("Slack stop error",{error:IW($)})}finally{this.app=null,this.recordConnection("stopped","")}}isAlive(){return this.app!==null}recordRaw($,x){this.diagnosticLog?.recordRaw({eventId:$,type:"slack",connectorId:this.config.id,channelId:this.channelId,payload:JSON.stringify(x)})}recordProcessed($,x,Y,X){this.diagnosticLog?.recordProcessed({eventId:$,type:"slack",connectorId:this.config.id,channelId:this.channelId,outcome:Y,payload:X||JSON.stringify(x)})}recordConnection($,x){this.diagnosticLog?.recordConnection({type:"slack",connectorId:this.config.id,channelId:this.channelId,status:$,detail:x})}}var IW=($)=>{return $ instanceof Error?$.message:String($)};import{join as $u}from"path";var gd0=new j8,pd0=new N8;class xu{fs;process;logger;diagnosticLog;dir;slackListenerOptions;scheduleListenerOptions;constructor($={}){this.fs=$.fs??gd0,this.process=$.process??pd0,this.logger=$.logger,this.diagnosticLog=$.diagnosticLog,this.dir=$.dir??m9,this.slackListenerOptions=$.slackListenerOptions??{},this.scheduleListenerOptions=$.scheduleListenerOptions??{},Object.freeze(this)}createListener($,x){if(x.type==="slack")return new eE({config:x,channelId:$,logger:this.logger,diagnosticLog:this.diagnosticLog,onAppCreated:this.slackListenerOptions.onAppCreated,preprocessEvent:this.slackListenerOptions.preprocessEvent});if(x.type==="gh")return new AR({config:x,channelId:$,process:this.process,logger:this.logger,diagnosticLog:this.diagnosticLog});if(x.type==="discord")return new bR({config:x,channelId:$,logger:this.logger,diagnosticLog:this.diagnosticLog});let Y=new cR({path:$u(this.connectorDir($,x.id),"state.json"),fs:this.fs});return new kR({config:x,lastFiredStore:Y,channelId:$,logger:this.logger,diagnosticLog:this.diagnosticLog,onFired:this.scheduleListenerOptions.onFired})}createAdapter($){if($.type==="slack")return new Vk({config:$});if($.type==="gh")return new RR({process:this.process});if($.type==="discord")return new TI({config:$});return null}connectorDir($,x){return $u(this.dir,"channels",$,"connectors",x)}channelDir($){return $u(this.dir,"channels",$)}}function Yu($){switch($.type){case"slack":return[$.botToken,$.appToken].filter((x)=>x!==void 0);case"discord":return[$.botToken].filter((x)=>x!==void 0);case"gh":case"schedule":return[]}}function id0($,x){return $.type===x}function nQ($,x,Y){let X=$.connectors.find((Q)=>Q.name===x);if(!X)throw Error(`connector "${x}" not found in channel "${$.name}"`);if(!id0(X,Y))throw Error(`connector "${x}" is type "${X.type}", not "${Y}"`);return X}class MW{millis(){return this.now().getTime()}iso(){return this.now().toISOString()}}class dQ extends MW{now(){return new Date}}var Qu=($,x,Y,X)=>{let Q=Y[$];if(Q!==void 0)return{[$]:Q};let z=Y[x];if(z!==void 0)return{[x]:z};let Z={},K=X[$],V=X[x];if(typeof K==="string")Z[$]=K;if(typeof V==="string")Z[x]=V;return Z},ld0=new dQ,nd0=new Q7;class Xu{store;factory;profileChecker;clock;idGenerator;constructor($){this.store=$.store,this.factory=$.factory,this.profileChecker=$.profileChecker,this.clock=$.clock??ld0,this.idGenerator=$.idGenerator??nd0,Object.freeze(this)}list(){return this.store.read().channels}get($){return this.list().find((x)=>x.name===$)??null}getById($){return this.list().find((x)=>x.id===$)??null}add($){let x=this.store.read();if(x.channels.some((X)=>X.name===$.name))throw Error(`channel "${$.name}" already exists`);let Y={id:this.idGenerator.generate(),name:$.name,delivery:$.delivery??"fanout",connectors:[]};return x.channels.push(Y),this.store.write(x),Y}setDelivery($,x){let Y=this.store.read(),X=this.requireChannel(Y,$);X.delivery=x,this.store.write(Y)}remove($){let x=this.store.read(),Y=x.channels.findIndex((Q)=>Q.name===$);if(Y<0)throw Error(`channel "${$}" not found`);let X=x.channels[Y];if(X&&this.profileChecker.hasChannelRef(X.id))throw Error(`channel "${$}" is referenced by a profile`);x.channels.splice(Y,1),this.store.write(x)}rename($,x){let Y=this.store.read(),X=Y.channels.find((Q)=>Q.name===$);if(!X)throw Error(`channel "${$}" not found`);if(Y.channels.some((Q)=>Q.name===x))throw Error(`channel "${x}" already exists`);X.name=x,this.store.write(Y)}listConnectors($){return this.requireChannel(this.store.read(),$).connectors}getConnector($,x){let Y=this.get($);if(!Y)return null;return Y.connectors.find((X)=>X.name===x)??null}listAllConnectors(){let $=[];for(let x of this.list())for(let Y of x.connectors)$.push({...Y,channelId:x.id,channelName:x.name});return $}addConnector($,x){let Y=this.store.read(),X=this.requireChannel(Y,$);if(X.connectors.some((z)=>z.name===x.name))throw Error(`connector "${x.name}" already exists in channel "${$}"`);let Q=this.fromInput(x);return this.assertNoTokenCollision(Y,Q),X.connectors.push(Q),this.store.write(Y),Q}fromInput($){let x=this.idGenerator.generate(),Y=this.clock.iso(),X=Y,Q=Y;switch($.type){case"slack":return{id:x,type:"slack",name:$.name,...$.botToken!==void 0?{botToken:$.botToken}:{},...$.appToken!==void 0?{appToken:$.appToken}:{},...$.botTokenEnv!==void 0?{botTokenEnv:$.botTokenEnv}:{},...$.appTokenEnv!==void 0?{appTokenEnv:$.appTokenEnv}:{},minify:$.minify??!0,createdAt:X,updatedAt:Q};case"gh":return{id:x,type:"gh",name:$.name,...$.pollInterval!==void 0?{pollInterval:$.pollInterval}:{},createdAt:X,updatedAt:Q};case"discord":return{id:x,type:"discord",name:$.name,...$.botToken!==void 0?{botToken:$.botToken}:{},...$.botTokenEnv!==void 0?{botTokenEnv:$.botTokenEnv}:{},createdAt:X,updatedAt:Q};case"schedule":return{id:x,type:"schedule",name:$.name,entries:$.entries??[],createdAt:X,updatedAt:Q}}}removeConnector($,x){let Y=this.store.read(),X=this.requireChannel(Y,$),Q=X.connectors.findIndex((z)=>z.name===x);if(Q<0)throw Error(`connector "${x}" not found in channel "${$}"`);X.connectors.splice(Q,1),this.store.write(Y)}renameConnector($,x,Y){let X=this.store.read(),Q=this.requireChannel(X,$),z=Q.connectors.find((Z)=>Z.name===x);if(!z)throw Error(`connector "${x}" not found in channel "${$}"`);if(Q.connectors.some((Z)=>Z.name===Y))throw Error(`connector "${Y}" already exists in channel "${$}"`);z.name=Y,z.updatedAt=this.clock.iso(),this.store.write(X)}updateSlackConnector($,x,Y){let X=this.store.read(),Q=this.requireChannel(X,$),z=nQ(Q,x,"slack"),Z={id:z.id,name:z.name,type:"slack",minify:z.minify,createdAt:z.createdAt,updatedAt:this.clock.iso(),...Qu("botToken","botTokenEnv",Y,z),...Qu("appToken","appTokenEnv",Y,z)};this.assertNoTokenCollision(X,Z),this.replaceConnector(Q,z.name,Z),this.store.write(X)}updateGhConnector($,x,Y){let X=this.store.read(),Q=this.requireChannel(X,$),z=nQ(Q,x,"gh");if(Y.pollInterval!==void 0)z.pollInterval=Y.pollInterval;z.updatedAt=this.clock.iso(),this.store.write(X)}updateDiscordConnector($,x,Y){let X=this.store.read(),Q=this.requireChannel(X,$),z=nQ(Q,x,"discord"),Z={id:z.id,name:z.name,type:"discord",createdAt:z.createdAt,updatedAt:this.clock.iso(),...Qu("botToken","botTokenEnv",Y,z)};this.assertNoTokenCollision(X,Z),this.replaceConnector(Q,z.name,Z),this.store.write(X)}listScheduleEntries($,x){let Y=this.requireChannel(this.store.read(),$);return nQ(Y,x,"schedule").entries}addScheduleEntry($,x,Y){let X=this.store.read(),Q=this.requireChannel(X,$),z=nQ(Q,x,"schedule"),Z={id:Y.id??this.idGenerator.generate(),cron:Y.cron,prompt:Y.prompt,enabled:Y.enabled??!0,catchupPolicy:Y.catchupPolicy??"latest"};return z.entries.push(Z),z.updatedAt=this.clock.iso(),this.store.write(X),Z}removeScheduleEntry($,x,Y){let X=this.store.read(),Q=this.requireChannel(X,$),z=nQ(Q,x,"schedule"),Z=z.entries.findIndex((K)=>K.id===Y);if(Z<0)throw Error(`schedule entry "${Y}" not found`);z.entries.splice(Z,1),z.updatedAt=this.clock.iso(),this.store.write(X)}async call($,x,Y){let X=this.getConnector($,x);if(!X)throw Error(`connector "${x}" not found in channel "${$}"`);let Q=this.factory.createAdapter(X);if(!Q)throw Error(`connector type "${X.type}" does not support outbound calls`);return await Q.call(Y)}createListener($,x){let Y=this.get($);if(!Y)return null;let X=Y.connectors.find((Q)=>Q.name===x);if(!X)return null;return{config:X,channelId:Y.id,listener:this.factory.createListener(Y.id,X)}}createAllListeners(){let $=[];for(let x of this.list())for(let Y of x.connectors)$.push({config:Y,channelId:x.id,channelName:x.name,listener:this.factory.createListener(x.id,Y)});return $}requireChannel($,x){let Y=$.channels.find((X)=>X.name===x);if(!Y)throw Error(`channel "${x}" not found`);return Y}replaceConnector($,x,Y){let X=$.connectors.findIndex((Q)=>Q.name===x);if(X<0)throw Error(`connector "${x}" not found in channel "${$.name}"`);$.connectors[X]=Y}assertNoTokenCollision($,x){let Y=Yu(x);if(Y.length===0)return;for(let X of $.channels)for(let Q of X.connectors){if(Q.id===x.id)continue;for(let z of Yu(Q))if(Y.includes(z))throw Error(`token already in use by connector "${Q.name}" in channel "${X.name}"`)}}}import{homedir as dd0}from"os";import{join as cB}from"path";var ad0=new N8,sd0=new j8,od0=new Q7;class zu{channels;mcp;gateway;profiles;process;fs;idGenerator;logger;pidDir;constructor($){this.channels=$.channels,this.mcp=$.mcp,this.gateway=$.gateway,this.profiles=$.profiles,this.process=$.process??ad0,this.fs=$.fs??sd0,this.idGenerator=$.idGenerator??od0,this.logger=$.logger,this.pidDir=cB($.dir??m9,"claude"),Object.freeze(this)}async launch($){let x=this.channels.get($.channel)??this.channels.getById($.channel);if(!x)throw Error(`channel "${$.channel}" not found`);if($.profileId&&this.isRunning($.profileId))throw Error(`profile "${$.profileId}" is already running`);let Y=$.cwd??globalThis.process.cwd();if(($.installMcp??!0)&&!this.mcp.findInstalledName(Y))this.mcp.install(Y),this.logger?.info("added funnel MCP to .mcp.json",{cwd:Y});if(!this.gateway.isRunning())this.logger?.info("starting gateway automatically"),await this.gateway.start();if($.profileId)this.writePidFile($.profileId),this.installCleanup($.profileId);let z=($.resume??!1)&&$.profileId?this.resolveSession($.profileId,Y,$.userArgs??[],$.env??{}):null,Z=this.buildArgs($.options??[],$.userArgs??[],Y,z),K=this.buildEnv(x.id,$.env??{});this.logger?.info("claude launch",{channel:$.channel,channelId:x.id,cwd:Y});try{return await this.process.attach(["claude",...Z],{cwd:Y,env:K,onSpawned:$.onSpawned})}finally{if($.profileId)this.removePidFile($.profileId)}}isRunning($){let x=this.readPid($);if(!x)return!1;return this.isProcessAlive(x)}pidPath($){return cB(this.pidDir,`${$}.pid`)}readPid($){let x=this.pidPath($);if(!this.fs.existsSync(x))return null;try{let Y=this.fs.readFileSync(x).trim(),X=Number(Y);if(!X||X<=0)return null;return X}catch{return null}}writePidFile($){this.fs.mkdirSync(this.pidDir,{recursive:!0}),this.fs.writeFileSync(this.pidPath($),String(globalThis.process.pid))}removePidFile($){let x=this.pidPath($);if(this.fs.existsSync(x))this.fs.unlink(x)}installCleanup($){globalThis.process.once("exit",()=>this.removePidFile($))}isProcessAlive($){return this.process.isAlive($)}buildArgs($,x,Y,X){let Q=[...$,...x];if(X!==null)if(X.mode==="resume")Q.push("--resume",X.id);else Q.push("--session-id",X.id);let z=this.mcp.findInstalledName(Y);if(z&&!Q.includes("--dangerously-load-development-channels")&&!Q.includes("--channels"))Q.push("--dangerously-load-development-channels",`server:${z}`);return Q}resolveSession($,x,Y,X){for(let Z of Y){if(Z==="-c"||Z==="--continue")return null;if(Z==="--resume"||Z.startsWith("--resume="))return null;if(Z==="--session-id"||Z.startsWith("--session-id="))return null}let Q=this.profiles.getSessionId($);if(Q!==null&&this.sessionFileExists(x,Q,X))return{id:Q,mode:"resume"};let z=this.idGenerator.generate();return this.profiles.setSessionId($,z),{id:z,mode:"new"}}sessionFileExists($,x,Y){let X=Y.CLAUDE_CONFIG_DIR??globalThis.process.env.CLAUDE_CONFIG_DIR??cB(dd0(),".claude"),Q=$.replace(/\//g,"-");return this.fs.existsSync(cB(X,"projects",Q,`${x}.jsonl`))}buildEnv($,x){let Y={};for(let[X,Q]of Object.entries(x))Y[X]=Q;for(let[X,Q]of Object.entries(globalThis.process.env))if(typeof Q==="string")Y[X]=Q;return Y.FUNNEL_CHANNEL_ID=$,Y}}var rd0=384;class Zu extends YK{dirs;files;mtimes;modes;now;constructor($={}){super();this.dirs=new Set($.dirs??[]),this.files=new Map(Object.entries($.files??{})),this.mtimes=new Map(Object.entries($.mtimes??{})),this.modes=new Map(Object.entries($.modes??{})),this.now=$.now??(()=>Date.now())}existsSync($){return this.dirs.has($)||this.files.has($)}readFileSync($){return this.files.get($)??""}writeFileSync($,x){this.files.set($,x),this.touch($)}writeSecretFileSync($,x){this.files.set($,x),this.modes.set($,rd0),this.touch($)}appendFileSync($,x){let Y=this.files.get($)??"";this.files.set($,Y+x),this.touch($)}unlink($){this.files.delete($),this.mtimes.delete($),this.modes.delete($)}mkdirSync($,x){this.dirs.add($)}readdirSync($){let x=$.endsWith("/")?$:`${$}/`,Y=[];for(let X of this.files.keys()){if(!X.startsWith(x))continue;let Q=X.slice(x.length);if(!Q.includes("/"))Y.push(Q)}return Y}statSync($){let x=this.mtimes.get($);if(x===void 0)throw Error(`not found: ${$}`);return{mtimeMs:x,mode:this.modes.get($)??null}}setMtime($,x){this.mtimes.set($,x)}setMode($,x){this.modes.set($,x)}touch($){if(!this.mtimes.has($))this.mtimes.set($,this.now());else this.mtimes.set($,this.now())}}class Ku extends QK{counter=0;prefix;constructor($={}){super();this.prefix=$.prefix??"id"}generate(){return this.counter++,`${this.prefix}-${this.counter}`}}import{join as za0}from"path";var td0=S.object({type:S.literal("slack"),name:S.string(),minify:S.boolean().optional()}),ed0=S.object({type:S.literal("discord"),name:S.string()}),$a0=S.object({type:S.literal("gh"),name:S.string(),pollInterval:S.number().int().positive().optional()}),xa0=S.object({type:S.literal("schedule"),name:S.string()}),Ya0=S.discriminatedUnion("type",[td0,ed0,$a0,xa0]),Qa0=S.object({name:S.string(),connectors:S.array(Ya0).optional()}),Xa0=S.object({name:S.string(),channel:S.string(),options:S.array(S.string()).optional(),env:S.record(S.string(),S.string()).optional(),resume:S.boolean().optional()}),gk1=S.object({$schema:S.string().optional(),id:S.string().optional(),channels:S.array(Qa0).min(1),profiles:S.array(Xa0).optional()}),VY="funnel.json";class Vu{fs;constructor($){this.fs=$.fs,Object.freeze(this)}read($){let x=za0($,VY);if(!this.fs.existsSync(x))return null;let Y=this.fs.readFileSync(x),X=(()=>{try{return JSON.parse(Y)}catch(z){let Z=z instanceof Error?z.message:String(z);throw Error(`${VY} is not valid JSON: ${Z}`)}})(),Q=gk1.safeParse(X);if(!Q.success)throw Error(`${VY} is invalid: ${Q.error.message}`);return this.assertProfilesValid(Q.data),Q.data}assertProfilesValid($){let x=$.profiles??[];if(x.length===0)return;let Y=new Set($.channels.map((Q)=>Q.name)),X=new Set;for(let Q of x){if(!Y.has(Q.channel))throw Error(`${VY} is invalid: profile "${Q.name}" binds channel "${Q.channel}", which is not declared in channels[]`);if(X.has(Q.name))throw Error(`${VY} is invalid: more than one profile is named "${Q.name}" \u2014 names must be unique`);X.add(Q.name)}}}class Wu{channels;prompter;constructor($){this.channels=$.channels,this.prompter=$.prompter,Object.freeze(this)}async ensure($){if(!this.channels.get($.name))this.channels.add({name:$.name});if($.connectors===void 0)return{touched:[],removed:[]};let Y=[],X=new Set;for(let z of $.connectors){let Z=await this.ensureConnector($.name,z);Y.push({name:Z.name,changed:Z.changed}),X.add(Z.id)}let Q=this.removeExtras($.name,X);return{touched:Y,removed:Q}}async ensureConnector($,x){if(x.type==="slack")return await this.ensureSlack($,x);if(x.type==="discord")return await this.ensureDiscord($,x);if(x.type==="gh")return this.ensureGh($,x);return this.ensureSchedule($,x)}async ensureSlack($,x){let Y=this.findExistingSlack($,x.name),X=await this.resolveSlot({label:`${x.name}.botToken`,existingLiteral:Y?.botToken,existingEnv:Y?.botTokenEnv}),Q=await this.resolveSlot({label:`${x.name}.appToken`,existingLiteral:Y?.appToken,existingEnv:Y?.appTokenEnv}),z={botToken:X.token,botTokenEnv:X.tokenEnv,appToken:Q.token,appTokenEnv:Q.tokenEnv};if(Y){if(!(Y.botToken===X.token&&Y.botTokenEnv===X.tokenEnv&&Y.appToken===Q.token&&Y.appTokenEnv===Q.tokenEnv))return this.channels.updateSlackConnector($,x.name,z),{id:Y.id,name:x.name,changed:!0};return{id:Y.id,name:x.name,changed:!1}}return{id:this.channels.addConnector($,{type:"slack",name:x.name,...z,...x.minify!==void 0?{minify:x.minify}:{}}).id,name:x.name,changed:!0}}async ensureDiscord($,x){let Y=this.findExistingDiscord($,x.name),X=await this.resolveSlot({label:`${x.name}.botToken`,existingLiteral:Y?.botToken,existingEnv:Y?.botTokenEnv}),Q={botToken:X.token,botTokenEnv:X.tokenEnv};if(Y){if(Y.botToken!==X.token||Y.botTokenEnv!==X.tokenEnv)return this.channels.updateDiscordConnector($,x.name,Q),{id:Y.id,name:x.name,changed:!0};return{id:Y.id,name:x.name,changed:!1}}return{id:this.channels.addConnector($,{type:"discord",name:x.name,...Q}).id,name:x.name,changed:!0}}ensureGh($,x){let Y=this.channels.getConnector($,x.name);if(Y&&Y.type!=="gh")throw Error(`connector "${x.name}" exists in channel "${$}" with type "${Y.type}", funnel.json declares "gh"`);if(Y&&Y.type==="gh"){if(x.pollInterval!==void 0&&Y.pollInterval!==x.pollInterval)return this.channels.updateGhConnector($,x.name,{pollInterval:x.pollInterval}),{id:Y.id,name:x.name,changed:!0};return{id:Y.id,name:x.name,changed:!1}}return{id:this.channels.addConnector($,{type:"gh",name:x.name,...x.pollInterval!==void 0?{pollInterval:x.pollInterval}:{}}).id,name:x.name,changed:!0}}ensureSchedule($,x){let Y=this.channels.getConnector($,x.name);if(Y&&Y.type!=="schedule")throw Error(`connector "${x.name}" exists in channel "${$}" with type "${Y.type}", funnel.json declares "schedule"`);if(Y&&Y.type==="schedule")return{id:Y.id,name:x.name,changed:!1};return{id:this.channels.addConnector($,{type:"schedule",name:x.name}).id,name:x.name,changed:!0}}findExistingSlack($,x){let Y=this.channels.getConnector($,x);if(!Y)return null;if(Y.type!=="slack")throw Error(`connector "${x}" exists in channel "${$}" with type "${Y.type}", funnel.json declares "slack"`);return Y}findExistingDiscord($,x){let Y=this.channels.getConnector($,x);if(!Y)return null;if(Y.type!=="discord")throw Error(`connector "${x}" exists in channel "${$}" with type "${Y.type}", funnel.json declares "discord"`);return Y}removeExtras($,x){let Y=this.channels.get($);if(!Y)return[];let X=Y.connectors.filter((Q)=>!x.has(Q.id));for(let Q of X)this.channels.removeConnector($,Q.name);return X.map((Q)=>Q.name)}async resolveSlot($){if($.existingEnv!==void 0)return{token:void 0,tokenEnv:$.existingEnv};if($.existingLiteral!==void 0)return{token:$.existingLiteral,tokenEnv:void 0};return{token:await this.prompter.promptSecret($.label),tokenEnv:void 0}}}import{join as Za0}from"path";var Ka0=($)=>{return typeof $==="object"&&$!==null&&!Array.isArray($)},Va0=($,x)=>{let Y={};if($.$schema!==void 0)Y.$schema=$.$schema;Y.id=x;for(let X of Object.keys($)){if(X==="$schema"||X==="id")continue;Y[X]=$[X]}return Y};class wu{fs;constructor($){this.fs=$.fs,Object.freeze(this)}ensureId($,x){let Y=Za0($,VY);if(!this.fs.existsSync(Y))return;let X=JSON.parse(this.fs.readFileSync(Y));if(!Ka0(X))return;if(typeof X.id==="string"&&X.id!=="")return;let Q=Va0(X,x);this.fs.writeFileSync(Y,`${JSON.stringify(Q,null,2)}
|
|
536
536
|
`)}}class _W{}class Hu extends _W{file=null;entries=[];info($,x){this.entries.push({level:"info",message:$,meta:x})}warn($,x){this.entries.push({level:"warn",message:$,meta:x})}error($,x){this.entries.push({level:"error",message:$,meta:x})}clear(){this.entries.length=0}}import{join as pk1}from"path";var ik1="funnel",Wa0="funnel",wa0=S.object({command:S.string().optional(),args:S.array(S.string()).optional()}),Ha0=S.object({mcpServers:S.record(S.string(),wa0).optional()}),Ua0=new j8;class Uu{fs;constructor($={}){this.fs=$.fs??Ua0,Object.freeze(this)}install($){if(!this.fs.existsSync($))throw Error(`repository does not exist: ${$}`);let x=this.readConfig($),Y=x.mcpServers??{},Q=this.findServerName(Y)??Wa0;Y[Q]={command:ik1,args:["mcp"]},this.writeConfig($,{...x,mcpServers:Y})}uninstall($){if(!this.fs.existsSync($))return;let x=this.readConfig($),Y=x.mcpServers??{},X=this.findServerName(Y);if(!X)return;let Q={...Y};delete Q[X],this.writeConfig($,{...x,mcpServers:Q})}findInstalledName($){let x=this.readConfig($);return this.findServerName(x.mcpServers??{})}findServerName($){for(let x of Object.entries($)){let Y=x[0];if(x[1]?.command===ik1)return Y}return null}readConfig($){let x=pk1($,".mcp.json");if(!this.fs.existsSync(x))return{};let Y=this.fs.readFileSync(x).trim();if(!Y)return{};let X;try{X=JSON.parse(Y)}catch(z){throw Error(`invalid .mcp.json (${x}): ${z instanceof Error?z.message:String(z)}`)}let Q=Ha0.safeParse(X);if(!Q.success)throw Error(`invalid .mcp.json (${x}): ${Q.error.message}`);return Q.data}writeConfig($,x){let Y=pk1($,".mcp.json");this.fs.writeFileSync(Y,`${JSON.stringify(x,null,2)}
|
|
537
537
|
`)}}var lk1={exitCode:0,stdout:"",stderr:""};class Gu extends xK{calls=[];killed=[];handler=()=>lk1;syncHandler=()=>lk1;aliveStub=null;listStub=null;on($){return this.handler=$,this}onSync($){return this.syncHandler=$,this}onIsAlive($){return this.aliveStub=$,this}onListProcessesContaining($){return this.listStub=$,this}async run($,x={}){this.calls.push({kind:"run",command:$,options:x});let Y=await this.handler($);return{exitCode:Y.exitCode??0,stdout:Y.stdout??"",stderr:Y.stderr??""}}runSync($){this.calls.push({kind:"runSync",command:$});let x=this.syncHandler($);return{exitCode:x.exitCode??0,stdout:x.stdout??"",stderr:x.stderr??""}}async attach($,x={}){if(this.calls.push({kind:"attach",command:$,options:x}),x.onSpawned)x.onSpawned(1);return(await this.handler($)).exitCode??0}detach($,x={}){this.calls.push({kind:"detach",command:$,options:x})}kill($,x="SIGTERM"){this.calls.push({kind:"kill",command:[String($),x]}),this.killed.push({pid:$,signal:x})}isAlive($){if(this.aliveStub)return this.aliveStub($);let x=this.syncHandler(["ps","-p",String($),"-o","state="]);if((x.exitCode??0)!==0)return!1;let Y=(x.stdout??"").trim();if(!Y)return!1;return!Y.startsWith("Z")}listProcessesContaining($){if(this.listStub)return this.listStub($);return[]}}class Ju{store;idGenerator;constructor($){this.store=$.store,this.idGenerator=$.idGenerator,Object.freeze(this)}list(){return this.store.read().profiles}get($){return this.list().find((x)=>x.name===$)??null}getById($){return this.list().find((x)=>x.id===$)??null}getDefault(){return this.list()[0]??null}add($){let x=this.store.read();if(x.profiles.some((Y)=>Y.name===$.name))throw Error(`profile "${$.name}" already exists`);if(!x.channels.some((Y)=>Y.id===$.channelId))throw Error(`channel id "${$.channelId}" not found`);x.profiles.push({id:this.idGenerator.generate(),name:$.name,path:$.path,channelId:$.channelId,options:$.options??[],env:$.env??{},resume:$.resume??!0}),this.store.write(x)}remove($){let x=this.store.read(),Y=x.profiles.findIndex((X)=>X.name===$);if(Y<0)throw Error(`profile "${$}" not found`);x.profiles.splice(Y,1),this.store.write(x)}rename($,x){let Y=this.store.read(),X=Y.profiles.find((Q)=>Q.name===$);if(!X)throw Error(`profile "${$}" not found`);if(Y.profiles.some((Q)=>Q.name===x))throw Error(`profile "${x}" already exists`);X.name=x,this.store.write(Y)}asDefault($){let x=this.store.read(),Y=x.profiles.findIndex((Q)=>Q.name===$);if(Y<0)throw Error(`profile "${$}" not found`);if(Y===0)return;let[X]=x.profiles.splice(Y,1);if(!X)return;x.profiles.unshift(X),this.store.write(x)}hasChannelRef($){return this.store.read().profiles.some((x)=>x.channelId===$)}getSessionId($){return this.getById($)?.sessionId??null}setSessionId($,x){let Y=this.store.read(),X=Y.profiles.find((Q)=>Q.id===$);if(!X)throw Error(`profile id "${$}" not found`);X.sessionId=x,this.store.write(Y)}update($,x){let Y=this.store.read(),X=Y.profiles.find((Q)=>Q.name===$);if(!X)throw Error(`profile "${$}" not found`);if(x.channelId!==void 0){if(!Y.channels.some((Q)=>Q.id===x.channelId))throw Error(`channel id "${x.channelId}" not found`);X.channelId=x.channelId}if(x.path!==void 0)X.path=x.path;if(x.options!==void 0)X.options=x.options;if(x.env!==void 0)X.env=x.env;if(x.resume!==void 0)X.resume=x.resume;this.store.write(Y)}}import{stderr as TB,stdin as nx}from"process";class qu{}var Ga0="*",Ja0="\r",nk1=`
|
|
538
538
|
`,qa0=String.fromCharCode(8),Da0=String.fromCharCode(127),Ba0=String.fromCharCode(3),Fa0=String.fromCharCode(4);class Du extends qu{async promptSecret($){if(!nx.isTTY)throw Error(`cannot prompt for "${$}": stdin is not a TTY. Set the matching env var or run \`fnl channels <ch> connectors add ...\` first.`);TB.write(`${$}: `);let x=nx.isRaw;nx.setRawMode(!0),nx.resume();try{return await this.readSecret()}finally{nx.setRawMode(x),nx.pause(),TB.write(nk1)}}readSecret(){return new Promise(($,x)=>{let Y="",X=(Q)=>{for(let z of Q){let Z=String.fromCharCode(z);if(Z===nk1||Z===Ja0){nx.off("data",X),$(Y);return}if(Z===Ba0){nx.off("data",X),x(Error("prompt cancelled"));return}if(Z===Fa0){if(nx.off("data",X),Y.length===0)x(Error("prompt cancelled"));else $(Y);return}if(Z===qa0||Z===Da0){if(Y.length>0)Y=Y.slice(0,-1),TB.write("\b \b");continue}Y+=Z,TB.write(Ga0)}};nx.on("data",X)})}}var La0=($={})=>({version:K7,channels:[],profiles:[],...$});class Bu extends XK{state;constructor($){super();this.state=La0($)}read(){return this.state}write($){this.state=$}}class Fu extends MW{current;constructor($={}){super();this.current=$.start??new Date(0)}now(){return new Date(this.current.getTime())}set($){this.current=$}advance($){this.current=new Date(this.current.getTime()+$)}}var dk1=S.object({content:S.string().min(1),meta:S.record(S.string(),S.string()).optional(),connector:S.string().min(1).optional()}),ak1=S.object({ok:S.literal(!0),offset:S.number().int().nonnegative()});var Na0={state:"offline"};class Lu{port;isDaemonRunning;getToken;constructor($){this.port=$.port,this.isDaemonRunning=$.isDaemonRunning,this.getToken=$.getToken??(()=>null),Object.freeze(this)}async publish($,x){if(!this.isDaemonRunning())return Na0;try{let Y=`http://localhost:${this.port}/channels/${encodeURIComponent($)}/publish`,X=await fetch(Y,{method:"POST",headers:{...this.authHeaders(),"content-type":"application/json"},body:JSON.stringify(x)});if(!X.ok)return{state:"error",reason:await X.text()||`HTTP ${X.status}`};let Q=ak1.safeParse(await X.json());if(!Q.success)return{state:"error",reason:"malformed daemon response"};return{state:"ok",offset:Q.data.offset}}catch(Y){return{state:"error",reason:Y instanceof Error?Y.message:String(Y)}}}authHeaders(){let $=this.getToken();return $?{authorization:`Bearer ${$}`}:{}}}import{join as ok1}from"path";import{existsSync as ja0}from"fs";import{dirname as va0,resolve as Nu}from"path";import{fileURLToPath as Pa0}from"url";var sk1=()=>{let $=va0(Pa0(import.meta.url)),x=[Nu($,"./daemon.ts"),Nu($,"./daemon.js"),Nu($,"./gateway/daemon.js")];for(let Y of x)if(ja0(Y))return Y;throw Error(`daemon script not found (looked in ${x.join(", ")})`)};var Ia0=9742,Ma0=5000,_a0=2000,rk1=100,Oa0=200,fa0=new N8,ba0=new j8,Ra0=new dQ,Aa0=($)=>new Promise((x)=>{setTimeout(x,$)});class ju{process;fs;clock;dir;pidFile;gatewayLog;tmpDir;port;sleep;constructor($={}){this.process=$.process??fa0,this.fs=$.fs??ba0,this.clock=$.clock??Ra0,this.dir=$.dir??m9,this.tmpDir=$.tmpDir??qx(),this.pidFile=ok1(this.dir,"gateway.pid"),this.gatewayLog=ok1(this.tmpDir,"gateway.log"),this.port=$.port??Ia0,this.sleep=$.sleep??Aa0,Object.freeze(this)}isRunning(){let $=this.readPid();if(!$)return!1;return this.isProcessAlive($)}getStatus(){let $=this.readPid(),x=$!==null&&this.isProcessAlive($);return{running:x,pid:x?$:null,port:this.port}}async start($={}){if(this.isRunning())return!0;this.fs.mkdirSync(this.tmpDir,{recursive:!0});let x=sk1(),Y=this.buildStartCommand(x,$);this.process.detach(Y,{env:{FUNNEL_DIR:this.dir},stdoutFile:this.gatewayLog,stderrFile:this.gatewayLog});let X=this.clock.millis()+Ma0;while(this.clock.millis()<X){if(this.isRunning())return!0;await this.sleep(rk1)}return this.isRunning()}buildStartCommand($,x={}){let Y=`funnel-gateway[${this.dir}]`;if(x.caffeinate!==!1&&globalThis.process.platform==="darwin")return["caffeinate","-is","bun",$,Y];return["bun",$,Y]}async stop(){let $=this.readPid();if(!$)return!0;if(!this.isProcessAlive($))return this.removePid(),!0;try{this.process.kill($,"SIGTERM")}catch{return!1}let x=this.clock.millis()+_a0;while(this.clock.millis()<x){if(!this.isProcessAlive($))return this.removePid(),!0;await this.sleep(rk1)}try{this.process.kill($,"SIGKILL")}catch{}return await this.sleep(Oa0),this.removePid(),!this.isProcessAlive($)}async restart($={}){let x=this.isRunning();if($.onlyIfRunning&&!x)return{ok:!0,wasRunning:!1,stopped:!1,started:!1};let Y=x?await this.stop():!0;if(!Y)return{ok:!1,wasRunning:x,stopped:!1,started:!1};let X=await this.start({caffeinate:$.caffeinate});return{ok:X,wasRunning:x,stopped:Y,started:X}}getGatewayLog(){return this.gatewayLog}getPort(){return this.port}readPid(){if(!this.fs.existsSync(this.pidFile))return null;try{let $=this.fs.readFileSync(this.pidFile).trim(),x=Number($);if(!x||x<=0)return null;return x}catch{return null}}removePid(){this.fs.unlink(this.pidFile)}isProcessAlive($){return this.process.isAlive($)}}import{existsSync as vs0,mkdirSync as Ps0}from"fs";import{dirname as Is0,join as Ms0}from"path";import{timingSafeEqual as ka0}from"crypto";var SB=($)=>{return async(x,Y)=>{let z=(x.req.header("authorization")??"").match(/^Bearer\s+(.+)$/i)?.[1]??"";if(!EB(z,$.expected))return x.text("unauthorized",401);return await Y()}},EB=($,x)=>{let Y=Buffer.from($,"utf-8"),X=Buffer.from(x,"utf-8"),Q=Math.max(Y.length,X.length,1),z=Buffer.alloc(Q),Z=Buffer.alloc(Q);return Y.copy(z),X.copy(Z),ka0(z,Z)&&Y.length===X.length};var vu=($,x,Y)=>{return(X,Q)=>{let z=-1;return Z(0);async function Z(K){if(K<=z)throw Error("next() called multiple times");z=K;let V,W=!1,w;if($[K])w=$[K][0][0],X.req.routeIndex=K;else w=K===$.length&&Q||void 0;if(w)try{V=await w(X,()=>Z(K+1))}catch(H){if(H instanceof Error&&x)X.error=H,V=await x(H,X),W=!0;else throw H}else if(X.finalized===!1&&Y)V=await Y(X);if(V&&(X.finalized===!1||W))X.res=V;return X}}};var nZ=class extends Error{res;status;constructor($=500,x){super(x?.message,{cause:x?.cause});this.res=x?.res,this.status=$}getResponse(){if(this.res)return new Response(this.res.body,{status:this.status,headers:this.res.headers});return new Response(this.message,{status:this.status})}};var tk1=Symbol();var ek1=async($,x=Object.create(null))=>{let{all:Y=!1,dot:X=!1}=x,z=($ instanceof uB?$.raw.headers:$.headers).get("Content-Type");if(z?.startsWith("multipart/form-data")||z?.startsWith("application/x-www-form-urlencoded"))return ca0($,{all:Y,dot:X});return{}};async function ca0($,x){let Y=await $.formData();if(Y)return Ta0(Y,x);return{}}function Ta0($,x){let Y=Object.create(null);if($.forEach((X,Q)=>{if(!(x.all||Q.endsWith("[]")))Y[Q]=X;else Sa0(Y,Q,X)}),x.dot)Object.entries(Y).forEach(([X,Q])=>{if(X.includes("."))Ea0(Y,X,Q),delete Y[X]});return Y}var Sa0=($,x,Y)=>{if($[x]!==void 0)if(Array.isArray($[x]))$[x].push(Y);else $[x]=[$[x],Y];else if(!x.endsWith("[]"))$[x]=Y;else $[x]=[Y]},Ea0=($,x,Y)=>{if(/(?:^|\.)__proto__\./.test(x))return;let X=$,Q=x.split(".");Q.forEach((z,Z)=>{if(Z===Q.length-1)X[z]=Y;else{if(!X[z]||typeof X[z]!=="object"||Array.isArray(X[z])||X[z]instanceof File)X[z]=Object.create(null);X=X[z]}})};var Iu=($)=>{let x=$.split("/");if(x[0]==="")x.shift();return x},$c1=($)=>{let{groups:x,path:Y}=ua0($),X=Iu(Y);return Ca0(X,x)},ua0=($)=>{let x=[];return $=$.replace(/\{[^}]+\}/g,(Y,X)=>{let Q=`@${X}`;return x.push([Q,Y]),Q}),{groups:x,path:$}},Ca0=($,x)=>{for(let Y=x.length-1;Y>=0;Y--){let[X]=x[Y];for(let Q=$.length-1;Q>=0;Q--)if($[Q].includes(X)){$[Q]=$[Q].replace(X,x[Y][1]);break}}return $},CB={},xc1=($,x)=>{if($==="*")return"*";let Y=$.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);if(Y){let X=`${$}#${x}`;if(!CB[X])if(Y[2])CB[X]=x&&x[0]!==":"&&x[0]!=="*"?[X,Y[1],new RegExp(`^${Y[2]}(?=/${x})`)]:[$,Y[1],new RegExp(`^${Y[2]}$`)];else CB[X]=[$,Y[1],!0];return CB[X]}return null},dZ=($,x)=>{try{return x($)}catch{return $.replace(/(?:%[0-9A-Fa-f]{2})+/g,(Y)=>{try{return x(Y)}catch{return Y}})}},ya0=($)=>dZ($,decodeURI),Mu=($)=>{let x=$.url,Y=x.indexOf("/",x.indexOf(":")+4),X=Y;for(;X<x.length;X++){let Q=x.charCodeAt(X);if(Q===37){let z=x.indexOf("?",X),Z=x.indexOf("#",X),K=z===-1?Z===-1?void 0:Z:Z===-1?z:Math.min(z,Z),V=x.slice(Y,K);return ya0(V.includes("%25")?V.replace(/%25/g,"%2525"):V)}else if(Q===63||Q===35)break}return x.slice(Y,X)};var Yc1=($)=>{let x=Mu($);return x.length>1&&x.at(-1)==="/"?x.slice(0,-1):x},aQ=($,x,...Y)=>{if(Y.length)x=aQ(x,...Y);return`${$?.[0]==="/"?"":"/"}${$}${x==="/"?"":`${$?.at(-1)==="/"?"":"/"}${x?.[0]==="/"?x.slice(1):x}`}`},yB=($)=>{if($.charCodeAt($.length-1)!==63||!$.includes(":"))return null;let x=$.split("/"),Y=[],X="";return x.forEach((Q)=>{if(Q!==""&&!/\:/.test(Q))X+="/"+Q;else if(/\:/.test(Q))if(/\?/.test(Q)){if(Y.length===0&&X==="")Y.push("/");else Y.push(X);let z=Q.replace("?","");X+="/"+z,Y.push(X)}else X+="/"+Q}),Y.filter((Q,z,Z)=>Z.indexOf(Q)===z)},Pu=($)=>{if(!/[%+]/.test($))return $;if($.indexOf("+")!==-1)$=$.replace(/\+/g," ");return $.indexOf("%")!==-1?dZ($,OW):$},Qc1=($,x,Y)=>{let X;if(!Y&&x&&!/[%+]/.test(x)){let Z=$.indexOf("?",8);if(Z===-1)return;if(!$.startsWith(x,Z+1))Z=$.indexOf(`&${x}`,Z+1);while(Z!==-1){let K=$.charCodeAt(Z+x.length+1);if(K===61){let V=Z+x.length+2,W=$.indexOf("&",V);return Pu($.slice(V,W===-1?void 0:W))}else if(K==38||isNaN(K))return"";Z=$.indexOf(`&${x}`,Z+1)}if(X=/[%+]/.test($),!X)return}let Q={};X??=/[%+]/.test($);let z=$.indexOf("?",8);while(z!==-1){let Z=$.indexOf("&",z+1),K=$.indexOf("=",z);if(K>Z&&Z!==-1)K=-1;let V=$.slice(z+1,K===-1?Z===-1?void 0:Z:K);if(X)V=Pu(V);if(z=Z,V==="")continue;let W;if(K===-1)W="";else if(W=$.slice(K+1,Z===-1?void 0:Z),X)W=Pu(W);if(Y){if(!(Q[V]&&Array.isArray(Q[V])))Q[V]=[];Q[V].push(W)}else Q[V]??=W}return x?Q[x]:Q},Xc1=Qc1,zc1=($,x)=>{return Qc1($,x,!0)},OW=decodeURIComponent;var Zc1=($)=>dZ($,OW),uB=class{raw;#$;#x;routeIndex=0;path;bodyCache={};constructor($,x="/",Y=[[]]){this.raw=$,this.path=x,this.#x=Y,this.#$={}}param($){return $?this.#Y($):this.#z()}#Y($){let x=this.#x[0][this.routeIndex][1][$],Y=this.#X(x);return Y&&/\%/.test(Y)?Zc1(Y):Y}#z(){let $={},x=Object.keys(this.#x[0][this.routeIndex][1]);for(let Y of x){let X=this.#X(this.#x[0][this.routeIndex][1][Y]);if(X!==void 0)$[Y]=/\%/.test(X)?Zc1(X):X}return $}#X($){return this.#x[1]?this.#x[1][$]:$}query($){return Xc1(this.url,$)}queries($){return zc1(this.url,$)}header($){if($)return this.raw.headers.get($)??void 0;let x={};return this.raw.headers.forEach((Y,X)=>{x[X]=Y}),x}async parseBody($){return ek1(this,$)}#Q=($)=>{let{bodyCache:x,raw:Y}=this,X=x[$];if(X)return X;let Q=Object.keys(x)[0];if(Q)return x[Q].then((z)=>{if(Q==="json")z=JSON.stringify(z);return new Response(z)[$]()});return x[$]=Y[$]()};json(){return this.#Q("text").then(($)=>JSON.parse($))}text(){return this.#Q("text")}arrayBuffer(){return this.#Q("arrayBuffer")}bytes(){return this.#Q("arrayBuffer").then(($)=>new Uint8Array($))}blob(){return this.#Q("blob")}formData(){return this.#Q("formData")}addValidatedData($,x){this.#$[$]=x}valid($){return this.#$[$]}get url(){return this.raw.url}get method(){return this.raw.method}get[tk1](){return this.#x}get matchedRoutes(){return this.#x[0].map(([[,$]])=>$)}get routePath(){return this.#x[0].map(([[,$]])=>$)[this.routeIndex].path}};var Kc1={Stringify:1,BeforeStream:2,Stream:3},ma0=($,x)=>{let Y=new String($);return Y.isEscaped=!0,Y.callbacks=x,Y};var _u=async($,x,Y,X,Q)=>{if(typeof $==="object"&&!($ instanceof String)){if(!($ instanceof Promise))$=$.toString();if($ instanceof Promise)$=await $}let z=$.callbacks;if(!z?.length)return Promise.resolve($);if(Q)Q[0]+=$;else Q=[$];let Z=Promise.all(z.map((K)=>K({phase:x,buffer:Q,context:X}))).then((K)=>Promise.all(K.filter(Boolean).map((V)=>_u(V,x,!1,X,Q))).then(()=>Q[0]));if(Y)return ma0(await Z,z);else return Z};var ha0="text/plain; charset=UTF-8",Ou=($,x)=>{return{"Content-Type":$,...x}},fW=($,x)=>new Response($,x),Vc1=class{#$;#x;env={};#Y;finalized=!1;error;#z;#X;#Q;#w;#V;#W;#K;#H;#U;constructor($,x){if(this.#$=$,x)this.#X=x.executionCtx,this.env=x.env,this.#W=x.notFoundHandler,this.#U=x.path,this.#H=x.matchResult}get req(){return this.#x??=new uB(this.#$,this.#U,this.#H),this.#x}get event(){if(this.#X&&"respondWith"in this.#X)return this.#X;else throw Error("This context has no FetchEvent")}get executionCtx(){if(this.#X)return this.#X;else throw Error("This context has no ExecutionContext")}get res(){return this.#Q||=fW(null,{headers:this.#K??=new Headers})}set res($){if(this.#Q&&$){$=fW($.body,$);for(let[x,Y]of this.#Q.headers.entries()){if(x==="content-type")continue;if(x==="set-cookie"){let X=this.#Q.headers.getSetCookie();$.headers.delete("set-cookie");for(let Q of X)$.headers.append("set-cookie",Q)}else $.headers.set(x,Y)}}this.#Q=$,this.finalized=!0}render=(...$)=>{return this.#V??=(x)=>this.html(x),this.#V(...$)};setLayout=($)=>this.#w=$;getLayout=()=>this.#w;setRenderer=($)=>{this.#V=$};header=($,x,Y)=>{if(this.finalized)this.#Q=fW(this.#Q.body,this.#Q);let X=this.#Q?this.#Q.headers:this.#K??=new Headers;if(x===void 0)X.delete($);else if(Y?.append)X.append($,x);else X.set($,x)};status=($)=>{this.#z=$};set=($,x)=>{this.#Y??=new Map,this.#Y.set($,x)};get=($)=>{return this.#Y?this.#Y.get($):void 0};get var(){if(!this.#Y)return{};return Object.fromEntries(this.#Y)}#Z($,x,Y){let X=this.#Q?new Headers(this.#Q.headers):this.#K??new Headers;if(typeof x==="object"&&"headers"in x){let z=x.headers instanceof Headers?x.headers:new Headers(x.headers);for(let[Z,K]of z)if(Z.toLowerCase()==="set-cookie")X.append(Z,K);else X.set(Z,K)}if(Y)for(let[z,Z]of Object.entries(Y))if(typeof Z==="string")X.set(z,Z);else{X.delete(z);for(let K of Z)X.append(z,K)}let Q=typeof x==="number"?x:x?.status??this.#z;return fW($,{status:Q,headers:X})}newResponse=(...$)=>this.#Z(...$);body=($,x,Y)=>this.#Z($,x,Y);text=($,x,Y)=>{return!this.#K&&!this.#z&&!x&&!Y&&!this.finalized?new Response($):this.#Z($,x,Ou(ha0,Y))};json=($,x,Y)=>{return this.#Z(JSON.stringify($),x,Ou("application/json",Y))};html=($,x,Y)=>{let X=(Q)=>this.#Z(Q,x,Ou("text/html; charset=UTF-8",Y));return typeof $==="object"?_u($,Kc1.Stringify,!1,{}).then(X):X($)};redirect=($,x)=>{let Y=String($);return this.header("Location",!/[^\x00-\xFF]/.test(Y)?Y:encodeURI(Y)),this.newResponse(null,x??302)};notFound=()=>{return this.#W??=()=>fW(),this.#W(this)}};var a0="ALL",Wc1="all",wc1=["get","post","put","delete","options","patch"],mB="Can not add a route since the matcher is already built.",hB=class extends Error{};var Hc1="__COMPOSED_HANDLER";var ga0=($)=>{return $.text("404 Not Found",404)},Uc1=($,x)=>{if("getResponse"in $){let Y=$.getResponse();return x.newResponse(Y.body,Y)}return console.error($),x.text("Internal Server Error",500)},Gc1=class ${get;post;put;delete;options;patch;all;on;use;router;getPath;_basePath="/";#$="/";routes=[];constructor(x={}){[...wc1,Wc1].forEach((z)=>{this[z]=(Z,...K)=>{if(typeof Z==="string")this.#$=Z;else this.#z(z,this.#$,Z);return K.forEach((V)=>{this.#z(z,this.#$,V)}),this}}),this.on=(z,Z,...K)=>{for(let V of[Z].flat()){this.#$=V;for(let W of[z].flat())K.map((w)=>{this.#z(W.toUpperCase(),this.#$,w)})}return this},this.use=(z,...Z)=>{if(typeof z==="string")this.#$=z;else this.#$="*",Z.unshift(z);return Z.forEach((K)=>{this.#z(a0,this.#$,K)}),this};let{strict:X,...Q}=x;Object.assign(this,Q),this.getPath=X??!0?x.getPath??Mu:Yc1}#x(){let x=new $({router:this.router,getPath:this.getPath});return x.errorHandler=this.errorHandler,x.#Y=this.#Y,x.routes=this.routes,x}#Y=ga0;errorHandler=Uc1;route(x,Y){let X=this.basePath(x);return Y.routes.map((Q)=>{let z;if(Y.errorHandler===Uc1)z=Q.handler;else z=async(Z,K)=>(await vu([],Y.errorHandler)(Z,()=>Q.handler(Z,K))).res,z[Hc1]=Q.handler;X.#z(Q.method,Q.path,z)}),this}basePath(x){let Y=this.#x();return Y._basePath=aQ(this._basePath,x),Y}onError=(x)=>{return this.errorHandler=x,this};notFound=(x)=>{return this.#Y=x,this};mount(x,Y,X){let Q,z;if(X)if(typeof X==="function")z=X;else if(z=X.optionHandler,X.replaceRequest===!1)Q=(V)=>V;else Q=X.replaceRequest;let Z=z?(V)=>{let W=z(V);return Array.isArray(W)?W:[W]}:(V)=>{let W=void 0;try{W=V.executionCtx}catch{}return[V.env,W]};Q||=(()=>{let V=aQ(this._basePath,x),W=V==="/"?0:V.length;return(w)=>{let H=new URL(w.url);return H.pathname=H.pathname.slice(W)||"/",new Request(H,w)}})();let K=async(V,W)=>{let w=await Y(Q(V.req.raw),...Z(V));if(w)return w;await W()};return this.#z(a0,aQ(x,"*"),K),this}#z(x,Y,X){x=x.toUpperCase(),Y=aQ(this._basePath,Y);let Q={basePath:this._basePath,path:Y,method:x,handler:X};this.router.add(x,Y,[X,Q]),this.routes.push(Q)}#X(x,Y){if(x instanceof Error)return this.errorHandler(x,Y);throw x}#Q(x,Y,X,Q){if(Q==="HEAD")return(async()=>new Response(null,await this.#Q(x,Y,X,"GET")))();let z=this.getPath(x,{env:X}),Z=this.router.match(Q,z),K=new Vc1(x,{path:z,matchResult:Z,env:X,executionCtx:Y,notFoundHandler:this.#Y});if(Z[0].length===1){let W;try{W=Z[0][0][0][0](K,async()=>{K.res=await this.#Y(K)})}catch(w){return this.#X(w,K)}return W instanceof Promise?W.then((w)=>w||(K.finalized?K.res:this.#Y(K))).catch((w)=>this.#X(w,K)):W??this.#Y(K)}let V=vu(Z[0],this.errorHandler,this.#Y);return(async()=>{try{let W=await V(K);if(!W.finalized)throw Error("Context is not finalized. Did you forget to return a Response object or `await next()`?");return W.res}catch(W){return this.#X(W,K)}})()}fetch=(x,...Y)=>{return this.#Q(x,Y[1],Y[0],x.method)};request=(x,Y,X,Q)=>{if(x instanceof Request)return this.fetch(Y?new Request(x,Y):x,X,Q);return x=x.toString(),this.fetch(new Request(/^https?:\/\//.test(x)?x:`http://localhost${aQ("/",x)}`,Y),X,Q)};fire=()=>{addEventListener("fetch",(x)=>{x.respondWith(this.#Q(x.request,x,void 0,x.request.method))})}};var bW=[];function gB($,x){let Y=this.buildAllMatchers(),X=(Q,z)=>{let Z=Y[Q]||Y[a0],K=Z[2][z];if(K)return K;let V=z.match(Z[0]);if(!V)return[[],bW];let W=V.indexOf("",1);return[Z[1][W],V]};return this.match=X,X($,x)}var pB="[^/]+",RW=".*",AW="(?:|/.*)",sQ=Symbol(),pa0=new Set(".\\+*[^]$()");function ia0($,x){if($.length===1)return x.length===1?$<x?-1:1:-1;if(x.length===1)return 1;if($===RW||$===AW)return 1;else if(x===RW||x===AW)return-1;if($===pB)return 1;else if(x===pB)return-1;return $.length===x.length?$<x?-1:1:x.length-$.length}var Jc1=class ${#$;#x;#Y=Object.create(null);insert(x,Y,X,Q,z){if(x.length===0){if(this.#$!==void 0)throw sQ;if(z)return;this.#$=Y;return}let[Z,...K]=x,V=Z==="*"?K.length===0?["","",RW]:["","",pB]:Z==="/*"?["","",AW]:Z.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/),W;if(V){let w=V[1],H=V[2]||pB;if(w&&V[2]){if(H===".*")throw sQ;if(H=H.replace(/^\((?!\?:)(?=[^)]+\)$)/,"(?:"),/\((?!\?:)/.test(H))throw sQ}if(W=this.#Y[H],!W){if(Object.keys(this.#Y).some((G)=>G!==RW&&G!==AW))throw sQ;if(z)return;if(W=this.#Y[H]=new $,w!=="")W.#x=Q.varIndex++}if(!z&&w!=="")X.push([w,W.#x])}else if(W=this.#Y[Z],!W){if(Object.keys(this.#Y).some((w)=>w.length>1&&w!==RW&&w!==AW))throw sQ;if(z)return;W=this.#Y[Z]=new $}W.insert(K,Y,X,Q,z)}buildRegExpStr(){let Y=Object.keys(this.#Y).sort(ia0).map((X)=>{let Q=this.#Y[X];return(typeof Q.#x==="number"?`(${X})@${Q.#x}`:pa0.has(X)?`\\${X}`:X)+Q.buildRegExpStr()});if(typeof this.#$==="number")Y.unshift(`#${this.#$}`);if(Y.length===0)return"";if(Y.length===1)return Y[0];return"(?:"+Y.join("|")+")"}};var qc1=class{#$={varIndex:0};#x=new Jc1;insert($,x,Y){let X=[],Q=[];for(let Z=0;;){let K=!1;if($=$.replace(/\{[^}]+\}/g,(V)=>{let W=`@\\${Z}`;return Q[Z]=[W,V],Z++,K=!0,W}),!K)break}let z=$.match(/(?::[^\/]+)|(?:\/\*$)|./g)||[];for(let Z=Q.length-1;Z>=0;Z--){let[K]=Q[Z];for(let V=z.length-1;V>=0;V--)if(z[V].indexOf(K)!==-1){z[V]=z[V].replace(K,Q[Z][1]);break}}return this.#x.insert(z,x,X,this.#$,Y),X}buildRegExp(){let $=this.#x.buildRegExpStr();if($==="")return[/^$/,[],[]];let x=0,Y=[],X=[];return $=$.replace(/#(\d+)|@(\d+)|\.\*\$/g,(Q,z,Z)=>{if(z!==void 0)return Y[++x]=Number(z),"$()";if(Z!==void 0)return X[Number(Z)]=++x,"";return""}),[new RegExp(`^${$}`),Y,X]}};var la0=[/^$/,[],Object.create(null)],Dc1=Object.create(null);function Bc1($){return Dc1[$]??=new RegExp($==="*"?"":`^${$.replace(/\/\*$|([.\\+*[^\]$()])/g,(x,Y)=>Y?`\\${Y}`:"(?:|/.*)")}$`)}function na0(){Dc1=Object.create(null)}function da0($){let x=new qc1,Y=[];if($.length===0)return la0;let X=$.map((W)=>[!/\*|\/:/.test(W[0]),...W]).sort(([W,w],[H,G])=>W?1:H?-1:w.length-G.length),Q=Object.create(null);for(let W=0,w=-1,H=X.length;W<H;W++){let[G,J,q]=X[W];if(G)Q[J]=[q.map(([D])=>[D,Object.create(null)]),bW];else w++;let B;try{B=x.insert(J,w,G)}catch(D){throw D===sQ?new hB(J):D}if(G)continue;Y[w]=q.map(([D,F])=>{let v=Object.create(null);F-=1;for(;F>=0;F--){let[N,E]=B[F];v[N]=E}return[D,v]})}let[z,Z,K]=x.buildRegExp();for(let W=0,w=Y.length;W<w;W++)for(let H=0,G=Y[W].length;H<G;H++){let J=Y[W][H]?.[1];if(!J)continue;let q=Object.keys(J);for(let B=0,D=q.length;B<D;B++)J[q[B]]=K[J[q[B]]]}let V=[];for(let W in Z)V[W]=Y[Z[W]];return[z,V,Q]}function aZ($,x){if(!$)return;for(let Y of Object.keys($).sort((X,Q)=>Q.length-X.length))if(Bc1(Y).test(x))return[...$[Y]];return}var iB=class{name="RegExpRouter";#$;#x;constructor(){this.#$={[a0]:Object.create(null)},this.#x={[a0]:Object.create(null)}}add($,x,Y){let X=this.#$,Q=this.#x;if(!X||!Q)throw Error(mB);if(!X[$])[X,Q].forEach((K)=>{K[$]=Object.create(null),Object.keys(K[a0]).forEach((V)=>{K[$][V]=[...K[a0][V]]})});if(x==="/*")x="*";let z=(x.match(/\/:/g)||[]).length;if(/\*$/.test(x)){let K=Bc1(x);if($===a0)Object.keys(X).forEach((V)=>{X[V][x]||=aZ(X[V],x)||aZ(X[a0],x)||[]});else X[$][x]||=aZ(X[$],x)||aZ(X[a0],x)||[];Object.keys(X).forEach((V)=>{if($===a0||$===V)Object.keys(X[V]).forEach((W)=>{K.test(W)&&X[V][W].push([Y,z])})}),Object.keys(Q).forEach((V)=>{if($===a0||$===V)Object.keys(Q[V]).forEach((W)=>K.test(W)&&Q[V][W].push([Y,z]))});return}let Z=yB(x)||[x];for(let K=0,V=Z.length;K<V;K++){let W=Z[K];Object.keys(Q).forEach((w)=>{if($===a0||$===w)Q[w][W]||=[...aZ(X[w],W)||aZ(X[a0],W)||[]],Q[w][W].push([Y,z-V+K+1])})}}match=gB;buildAllMatchers(){let $=Object.create(null);return Object.keys(this.#x).concat(Object.keys(this.#$)).forEach((x)=>{$[x]||=this.#Y(x)}),this.#$=this.#x=void 0,na0(),$}#Y($){let x=[],Y=$===a0;if([this.#$,this.#x].forEach((X)=>{let Q=X[$]?Object.keys(X[$]).map((z)=>[z,X[$][z]]):[];if(Q.length!==0)Y||=!0,x.push(...Q);else if($!==a0)x.push(...Object.keys(X[a0]).map((z)=>[z,X[a0][z]]))}),!Y)return null;else return da0(x)}};var aa0=class{name="PreparedRegExpRouter";#$;#x;constructor($,x){this.#$=$,this.#x=x}#Y($,x){let Y=this.#$[$];Y[1].forEach((X)=>X&&X.push(x)),Object.values(Y[2]).forEach((X)=>X[0].push(x))}#z($,x,Y,X,Q){let z=this.#$[$];if(!Q)z[2][x][0].push([Y,{}]);else X.forEach((Z)=>{if(typeof Z==="number")z[1][Z].push([Y,Q]);else z[2][Z||x][0].push([Y,Q])})}add($,x,Y){if(!this.#$[$]){let Q=this.#$[a0],z={};for(let Z in Q[2])z[Z]=[Q[2][Z][0].slice(),bW];this.#$[$]=[Q[0],Q[1].map((Z)=>Array.isArray(Z)?Z.slice():0),z]}if(x==="/*"||x==="*"){let Q=[Y,{}];if($===a0)for(let z in this.#$)this.#Y(z,Q);else this.#Y($,Q);return}let X=this.#x[x];if(!X)throw Error(`Path ${x} is not registered`);for(let[Q,z]of X)if($===a0)for(let Z in this.#$)this.#z(Z,x,Y,Q,z);else this.#z($,x,Y,Q,z)}buildAllMatchers(){return this.#$}match=gB};var fu=class{name="SmartRouter";#$=[];#x=[];constructor($){this.#$=$.routers}add($,x,Y){if(!this.#x)throw Error(mB);this.#x.push([$,x,Y])}match($,x){if(!this.#x)throw Error("Fatal error");let Y=this.#$,X=this.#x,Q=Y.length,z=0,Z;for(;z<Q;z++){let K=Y[z];try{for(let V=0,W=X.length;V<W;V++)K.add(...X[V]);Z=K.match($,x)}catch(V){if(V instanceof hB)continue;throw V}this.match=K.match.bind(K),this.#$=[K],this.#x=void 0;break}if(z===Q)throw Error("Fatal error");return this.name=`SmartRouter + ${this.activeRouter.name}`,Z}get activeRouter(){if(this.#x||this.#$.length!==1)throw Error("No active router has been determined yet.");return this.#$[0]}};var kW=Object.create(null),sa0=($)=>{for(let x in $)return!0;return!1},Fc1=class ${#$;#x;#Y;#z=0;#X=kW;constructor(x,Y,X){if(this.#x=X||Object.create(null),this.#$=[],x&&Y){let Q=Object.create(null);Q[x]={handler:Y,possibleKeys:[],score:0},this.#$=[Q]}this.#Y=[]}insert(x,Y,X){this.#z=++this.#z;let Q=this,z=$c1(Y),Z=[];for(let K=0,V=z.length;K<V;K++){let W=z[K],w=z[K+1],H=xc1(W,w),G=Array.isArray(H)?H[0]:W;if(G in Q.#x){if(Q=Q.#x[G],H)Z.push(H[1]);continue}if(Q.#x[G]=new $,H)Q.#Y.push(H),Z.push(H[1]);Q=Q.#x[G]}return Q.#$.push({[x]:{handler:X,possibleKeys:Z.filter((K,V,W)=>W.indexOf(K)===V),score:this.#z}}),Q}#Q(x,Y,X,Q,z){for(let Z=0,K=Y.#$.length;Z<K;Z++){let V=Y.#$[Z],W=V[X]||V[a0],w={};if(W!==void 0){if(W.params=Object.create(null),x.push(W),Q!==kW||z&&z!==kW)for(let H=0,G=W.possibleKeys.length;H<G;H++){let J=W.possibleKeys[H],q=w[W.score];W.params[J]=z?.[J]&&!q?z[J]:Q[J]??z?.[J],w[W.score]=!0}}}}search(x,Y){let X=[];this.#X=kW;let z=[this],Z=Iu(Y),K=[],V=Z.length,W=null;for(let w=0;w<V;w++){let H=Z[w],G=w===V-1,J=[];for(let B=0,D=z.length;B<D;B++){let F=z[B],v=F.#x[H];if(v)if(v.#X=F.#X,G){if(v.#x["*"])this.#Q(X,v.#x["*"],x,F.#X);this.#Q(X,v,x,F.#X)}else J.push(v);for(let N=0,E=F.#Y.length;N<E;N++){let m=F.#Y[N],d=F.#X===kW?{}:{...F.#X};if(m==="*"){let W1=F.#x["*"];if(W1)this.#Q(X,W1,x,F.#X),W1.#X=d,J.push(W1);continue}let[U1,j1,g]=m;if(!H&&!(g instanceof RegExp))continue;let s=F.#x[U1];if(g instanceof RegExp){if(W===null){W=Array(V);let M1=Y[0]==="/"?1:0;for(let O1=0;O1<V;O1++)W[O1]=M1,M1+=Z[O1].length+1}let W1=Y.substring(W[w]),F1=g.exec(W1);if(F1){if(d[j1]=F1[0],this.#Q(X,s,x,F.#X,d),sa0(s.#x)){s.#X=d;let M1=F1[0].match(/\//)?.length??0;(K[M1]||=[]).push(s)}continue}}if(g===!0||g.test(H))if(d[j1]=H,G){if(this.#Q(X,s,x,d,F.#X),s.#x["*"])this.#Q(X,s.#x["*"],x,d,F.#X)}else s.#X=d,J.push(s)}}let q=K.shift();z=q?J.concat(q):J}if(X.length>1)X.sort((w,H)=>{return w.score-H.score});return[X.map(({handler:w,params:H})=>[w,H])]}};var bu=class{name="TrieRouter";#$;constructor(){this.#$=new Fc1}add($,x,Y){let X=yB(x);if(X){for(let Q=0,z=X.length;Q<z;Q++)this.#$.insert($,X[Q],Y);return}this.#$.insert($,x,Y)}match($,x){return this.#$.search($,x)}};var Lc1=class extends Gc1{constructor($={}){super($);this.router=$.router??new fu({routers:[new iB,new bu]})}};var oa0=class{initApp;#$;constructor($){this.initApp=$?.initApp,this.#$=$?.defaultAppOptions}createApp=($)=>{let x=new Lc1($&&this.#$?{...this.#$,...$}:$??this.#$);if(this.initApp)this.initApp(x);return x};createMiddleware=($)=>$;createHandlers=(...$)=>{return $.filter((x)=>x!==void 0)}},Nc1=($)=>new oa0($);var $8=Nc1();var jc1=($)=>{let x=Buffer.byteLength($.content,"utf-8");if($.meta)for(let[Y,X]of Object.entries($.meta))x+=Buffer.byteLength(Y,"utf-8")+Buffer.byteLength(X,"utf-8");return x},ra0=1048576,ta0=200,ea0=4194304,$s0=()=>{};class Ru{clients=new Map;subscribers=new Set;logger;onError;maxBufferedBytes;now;replayBufferSize;replayBufferMaxBytes;replayBuffer=[];persistentReplay;exclusiveCursor=new Map;replayBufferBytes=0;eventsBroadcast=0;droppedSlowClients=0;lastBroadcastAt=null;latestOffset=0;constructor($={}){this.logger=$.logger,this.onError=$.onError??$s0,this.maxBufferedBytes=$.maxBufferedBytes??ra0,this.now=$.now??(()=>Date.now()),this.replayBufferSize=Math.max(0,$.replayBufferSize??ta0),this.replayBufferMaxBytes=Math.max(0,$.replayBufferMaxBytes??ea0),this.persistentReplay=$.persistentReplay??null}getMetrics(){return{clients:this.clients.size,subscribers:this.subscribers.size,eventsBroadcast:this.eventsBroadcast,droppedSlowClients:this.droppedSlowClients,lastBroadcastAt:this.lastBroadcastAt?new Date(this.lastBroadcastAt).toISOString():null,latestOffset:this.latestOffset,oldestReplayableOffset:this.replayBuffer[0]?.offset??null}}replaySince($,x){let Y=this.replayBuffer[0]?.offset,X=this.persistentReplay&&(Y===void 0||$<Y-1),Q=this.replayBuffer.filter((V)=>V.offset>$&&this.matchesClient(V,x));if(!X)return Q;let z=this.persistentReplay?this.persistentReplay.loadSince($).filter((V)=>this.matchesClient(V,x)):[],Z=Y??Number.POSITIVE_INFINITY;return[...z.filter((V)=>V.offset<Z),...Q]}matchesClient($,x){if(x.tapAll)return!0;let Y=$.meta?.channelId;if(Y&&Y!==x.channel)return!1;let X=$.meta?.connector;if(!X)return!0;return x.connectors.includes(X)}pickRecipients($){let x=new Map,Y=[];for(let[X,Q]of this.clients){if(!this.matchesClient($,Q))continue;if(Q.tapAll){Y.push(X);continue}if(Q.delivery==="exclusive"){let z=x.get(Q.channel)??[];z.push(X),x.set(Q.channel,z);continue}Y.push(X)}for(let[X,Q]of x){if(Q.length===0)continue;let z=this.exclusiveCursor.get(X)??0,Z=Q[z%Q.length];if(Z)Y.push(Z);this.exclusiveCursor.set(X,z+1)}return Y}addClient($,x){this.clients.set($,x)}removeClient($){this.clients.delete($)}getClientCount(){return this.clients.size}listChannels(){return[...this.clients.values()].map(($)=>({...$}))}subscribe($){return this.subscribers.add($),()=>{this.subscribers.delete($)}}broadcast($,x){this.latestOffset+=1;let Y={content:$,meta:x,offset:this.latestOffset},X=JSON.stringify(Y),Q=x?.connector;if(this.eventsBroadcast+=1,this.lastBroadcastAt=this.now(),this.replayBufferSize>0){let Z=jc1(Y);this.replayBuffer.push(Y),this.replayBufferBytes+=Z;while((this.replayBuffer.length>this.replayBufferSize||this.replayBufferBytes>this.replayBufferMaxBytes)&&this.replayBuffer.length>0){let K=this.replayBuffer.shift();if(K)this.replayBufferBytes-=jc1(K)}}let z=this.pickRecipients(Y);for(let Z of z){let K=Z.getBufferedAmount();if(K>this.maxBufferedBytes){let V=this.clients.get(Z);this.logger?.warn("dropping slow WS client (backpressure)",{channel:V?.channel,buffered:K,max:this.maxBufferedBytes});try{Z.close(1009,"backpressure")}catch{}this.clients.delete(Z),this.droppedSlowClients+=1;continue}Z.send(X)}for(let Z of this.subscribers)try{Z(Y)}catch(K){let V=K instanceof Error?K:Error(String(K));this.logger?.error("broadcast subscriber threw",{error:V.message}),this.onError(V,{component:"broadcaster.subscriber",offset:Y.offset,connector:Y.meta?.connector??null,channel:Y.meta?.channel??null})}return Y}seedLatestOffset($){if($>this.latestOffset)this.latestOffset=$}}var AG6=S.object({type:S.string(),content:S.string(),channel_id:S.string().nullable(),connector_id:S.string().nullable(),meta:S.record(S.string(),S.string()).nullable()});class Au{}import{Database as xs0}from"bun:sqlite";var Ys0=/^[a-z_][a-z0-9_]*$/,Qs0=500,Xs0=new Set(["seq","ts","type","event"]),vc1=[["CREATE TABLE IF NOT EXISTS leuco_log (seq INTEGER PRIMARY KEY, ts INTEGER NOT NULL, type TEXT, event TEXT NOT NULL)","CREATE INDEX IF NOT EXISTS idx_leuco_log_ts ON leuco_log (ts)","CREATE INDEX IF NOT EXISTS idx_leuco_log_type ON leuco_log (type)"]];class oQ{db;maxRows;maxAgeMs;maxBytes;targetBytes;now;indexes;extractIndexes;insertStmt;insertWithSeqStmt;maxSeqStmt;countStmt;trimRowsStmt;trimAgeStmt;trimOldestStmt;insertsSinceByteCheck=0;constructor($){if(this.db=new xs0($.path),this.db.run("PRAGMA journal_mode = WAL"),this.migrate(),this.maxRows=$.maxRows??null,this.maxAgeMs=$.maxAgeMs??null,this.maxBytes=$.maxBytes??null,this.targetBytes=$.targetBytes??($.maxBytes!==void 0?Math.floor($.maxBytes/4):null),this.now=$.now??(()=>Date.now()),this.indexes=$.indexes??[],this.indexes.length>0)zs0(this.indexes),this.extractIndexes=$.extractIndexes??null,this.syncIndexColumns();else this.extractIndexes=null;let x=["ts","type","event",...this.indexes],Y=x.map(()=>"?").join(", ");this.insertStmt=this.db.prepare(`INSERT INTO leuco_log (${x.join(", ")}) VALUES (${Y})`);let X=["seq",...x],Q=X.map(()=>"?").join(", ");this.insertWithSeqStmt=this.db.prepare(`INSERT INTO leuco_log (${X.join(", ")}) VALUES (${Q})`),this.maxSeqStmt=this.db.prepare("SELECT COALESCE(MAX(seq), 0) AS max FROM leuco_log"),this.countStmt=this.db.prepare("SELECT COUNT(*) AS n FROM leuco_log"),this.trimRowsStmt=this.db.prepare("DELETE FROM leuco_log WHERE seq <= (SELECT seq FROM leuco_log ORDER BY seq DESC LIMIT 1 OFFSET ?)"),this.trimAgeStmt=this.db.prepare("DELETE FROM leuco_log WHERE ts < ?"),this.trimOldestStmt=this.db.prepare("DELETE FROM leuco_log WHERE seq IN (SELECT seq FROM leuco_log ORDER BY seq ASC LIMIT ?)")}insert($){try{let x=this.buildInsertParams($.ts,$.event),Y=this.insertStmt.run(...x),X=Number(Y.lastInsertRowid);return this.trim(),{seq:X,ts:$.ts,event:$.event}}catch(x){return x instanceof Error?x:Error(String(x))}}insertMany($){if($.length===0)return[];try{let x=[];return this.db.transaction((X)=>{for(let Q of X){let z=this.buildInsertParams(Q.ts,Q.event),Z=this.insertStmt.run(...z);x.push({seq:Number(Z.lastInsertRowid),ts:Q.ts,event:Q.event})}})($),this.trim(),x}catch(x){return x instanceof Error?x:Error(String(x))}}write($){try{let x=[$.seq,...this.buildInsertParams($.ts,$.event)];this.insertWithSeqStmt.run(...x),this.trim()}catch(x){return x instanceof Error?x:Error(String(x))}}getMaxSeq(){let $=this.maxSeqStmt.get();return $?$.max:0}getRecords($={}){let x=["seq > ?"],Y=[$.sinceSeq??0];if(typeof $.type==="string")x.push("type = ?"),Y.push($.type);if($.where)this.appendWhereConditions($.where,x,Y);let X=$.limit??1000;Y.push(X);let Q=$.order==="desc"?"DESC":"ASC",z=`SELECT seq, ts, type, event FROM leuco_log WHERE ${x.join(" AND ")} ORDER BY seq ${Q} LIMIT ?`,K=this.db.prepare(z).all(...Y);if(Q==="DESC")K.reverse();return K.map(Ks0)}getSchemaVersion(){return this.db.prepare("PRAGMA user_version").get()?.user_version??0}close(){this.db.close()}buildInsertParams($,x){let Y=Zs0(x),X=JSON.stringify(x);if(this.indexes.length===0)return[$,Y,X];let Q=this.extractIndexes?this.extractIndexes(x):null,z=this.indexes.map((Z)=>Q?.[Z]??null);return[$,Y,X,...z]}appendWhereConditions($,x,Y){let X=$;for(let Q of this.indexes){let z=X[Q];if(z===void 0)continue;if(z===null)x.push(`${Q} IS NULL`);else x.push(`${Q} = ?`),Y.push(z)}}trim(){if(this.maxRows!==null){let $=this.countStmt.get();if($&&$.n>this.maxRows)this.trimRowsStmt.run(this.maxRows)}if(this.maxAgeMs!==null)this.trimAgeStmt.run(this.now()-this.maxAgeMs);this.maybeTrimBytes()}maybeTrimBytes(){if(this.maxBytes===null||this.targetBytes===null)return;if(this.insertsSinceByteCheck+=1,this.insertsSinceByteCheck<Qs0)return;this.insertsSinceByteCheck=0;let $=this.byteSize();if($<=this.maxBytes)return;let x=this.countStmt.get()?.n??0;if(x===0)return;let Y=$-this.targetBytes,X=$/x,Q=Math.min(x,Math.ceil(Y/X));this.trimOldestStmt.run(Q),this.db.run("VACUUM")}byteSize(){let $=this.db.prepare("PRAGMA page_count").get()?.n??0,x=this.db.prepare("PRAGMA page_size").get()?.n??0;return $*x}clear(){this.db.run("DELETE FROM leuco_log"),this.db.run("VACUUM"),this.insertsSinceByteCheck=0}syncIndexColumns(){let $=new Set(this.db.prepare("PRAGMA table_info(leuco_log)").all().map((x)=>x.name));for(let x of this.indexes){if(!$.has(x))this.db.run(`ALTER TABLE leuco_log ADD COLUMN ${x} TEXT`);this.db.run(`CREATE INDEX IF NOT EXISTS idx_leuco_log_${x} ON leuco_log (${x})`)}}migrate(){let x=this.db.prepare("PRAGMA user_version").get()?.user_version??0;if(x>=vc1.length)return;let Y=vc1.slice(x),X=x;for(let Q of Y)X+=1,this.db.transaction(()=>{for(let Z of Q)this.db.run(Z);this.db.run(`PRAGMA user_version = ${X}`)})()}}function zs0($){for(let x of $){if(!Ys0.test(x))throw Error(`invalid index column name: ${x}`);if(Xs0.has(x))throw Error(`reserved index column name: ${x}`)}}function Zs0($){if(typeof $!=="object"||$===null)return null;if(!("type"in $))return null;let x=$.type;return typeof x==="string"?x:null}function Ks0($){return{seq:$.seq,ts:$.ts,event:JSON.parse($.event)}}var Pc1=2000;class ku extends Au{sink;now;constructor($){super();this.now=$.now??(()=>Date.now()),this.sink=new oQ({path:$.path,indexes:["channel_id","connector_id"],extractIndexes:(x)=>({channel_id:x.channel_id,connector_id:x.connector_id}),now:this.now,...$.maxRows!==void 0?{maxRows:$.maxRows}:{},...$.maxAgeMs!==void 0?{maxAgeMs:$.maxAgeMs}:{},...$.maxBytes!==void 0?{maxBytes:$.maxBytes}:{},...$.targetBytes!==void 0?{targetBytes:$.targetBytes}:{}})}record($){let x={type:$.meta?.event_type??"unknown",content:Vs0($.content),channel_id:$.channelId,connector_id:$.connectorId,meta:$.meta};this.sink.write({seq:$.offset,ts:this.now(),event:x})}loadSince($){let x=this.sink.getRecords({sinceSeq:$}),Y=[];for(let X of x)Y.push({content:X.event.content,meta:X.event.meta??void 0,offset:X.seq});return Y}loadForChannel($){let x={channel_id:$.channelId};if($.connectorId!==void 0)x.connector_id=$.connectorId;let Y=this.sink.getRecords({where:x,...$.sinceSeq!==void 0?{sinceSeq:$.sinceSeq}:{},...$.limit!==void 0?{limit:$.limit}:{}}),X=[];for(let Q of Y)X.push({content:Q.event.content,meta:Q.event.meta??void 0,offset:Q.seq});return X}findMaxOffset(){return this.sink.getMaxSeq()}clear(){this.sink.clear()}close(){this.sink.close()}}function Vs0($){if($.length<=Pc1)return $;return`${$.slice(0,Pc1)}...`}var Ws0=()=>{},ws0=30000,Hs0=60000,Us0=($)=>new Promise((x)=>{setTimeout(x,$)});class rQ{channels;notify;logger;onError;running=new Map;failureCounts=new Map;stats=new Map;healthCheckIntervalMs;maxBackoffMs;sleep;now;healthCheckTimer=null;healthCheckInFlight=!1;constructor($){this.channels=$.channels,this.notify=$.notify,this.logger=$.logger,this.onError=$.onError??Ws0,this.healthCheckIntervalMs=$.healthCheckIntervalMs??ws0,this.maxBackoffMs=$.maxBackoffMs??Hs0,this.sleep=$.sleep??Us0,this.now=$.now??(()=>Date.now())}static keyOf($,x){return`${$}/${x}`}isRunning($,x){return this.running.has(rQ.keyOf($,x))}list(){return[...this.running.entries()].map(([$,x])=>{let Y=this.stats.get($);return{channelName:x.channelName,channelId:x.channelId,name:x.config.name,type:x.config.type,alive:x.listener.isAlive(),events:Y?.events??0,errors:Y?.errors??0,failureCount:this.failureCounts.get($)??0,lastEventAt:Y?.lastEventAt??null}})}async start($,x){let Y=rQ.keyOf($,x);if(this.running.has(Y))return{ok:!0,reason:"already running"};let X=this.channels.createListener($,x);if(!X)return{ok:!1,reason:`connector "${x}" not found in channel "${$}"`};let Q=async(z,Z)=>{try{await this.notify($,x,z,Z),this.recordEvent(Y)}catch(K){throw this.recordError(Y),K}};try{return await X.listener.start(Q),this.running.set(Y,{config:X.config,channelName:$,channelId:X.channelId,listener:X.listener}),this.ensureStats(Y),this.logger?.info(`${X.config.type} listener started`,{channel:$,connector:x}),{ok:!0}}catch(z){let Z=z instanceof Error?z:Error(String(z));return this.logger?.error(`${X.config.type} listener failed to start`,{channel:$,connector:x,error:Z.message}),this.onError(Z,{component:"listener-supervisor.start",channel:$,connector:x,type:X.config.type}),{ok:!1,reason:Z.message}}}async stop($,x){let Y=rQ.keyOf($,x),X=this.running.get(Y);if(!X)return{ok:!0,reason:"not running"};try{return await X.listener.stop(),this.running.delete(Y),this.failureCounts.delete(Y),this.logger?.info(`${X.config.type} listener stopped`,{channel:$,connector:x}),{ok:!0}}catch(Q){let z=Q instanceof Error?Q:Error(String(Q));return this.logger?.error(`${X.config.type} listener failed to stop`,{channel:$,connector:x,error:z.message}),this.onError(z,{component:"listener-supervisor.stop",channel:$,connector:x,type:X.config.type}),{ok:!1,reason:z.message}}}async restart($,x){let Y=await this.stop($,x);if(!Y.ok)return Y;return await this.start($,x)}async startAll(){let $=this.channels.listAllConnectors();for(let x of $)await this.start(x.channelName,x.name);this.startHealthCheck()}async stopAll(){this.stopHealthCheck();for(let[,$]of[...this.running.entries()])await this.stop($.channelName,$.config.name)}ensureStats($){let x=this.stats.get($);if(x)return x;let Y={events:0,errors:0,failureCount:0,lastEventAt:null};return this.stats.set($,Y),Y}recordEvent($){let x=this.ensureStats($);x.events+=1,x.lastEventAt=new Date(this.now()).toISOString()}recordError($){this.ensureStats($).errors+=1}startHealthCheck(){if(this.healthCheckTimer)return;this.healthCheckTimer=setInterval(()=>{this.runHealthCheck()},this.healthCheckIntervalMs),this.healthCheckTimer.unref()}stopHealthCheck(){if(!this.healthCheckTimer)return;clearInterval(this.healthCheckTimer),this.healthCheckTimer=null}async runHealthCheck(){if(this.healthCheckInFlight)return;this.healthCheckInFlight=!0;try{for(let[$,x]of[...this.running.entries()]){if(x.listener.isAlive()){this.failureCounts.delete($);continue}await this.recoverDead(x.channelName,x.config.name,x.config.type)}}finally{this.healthCheckInFlight=!1}}async recoverDead($,x,Y){let X=rQ.keyOf($,x),Q=this.failureCounts.get(X)??0,z=Math.min(1000*2**Q,this.maxBackoffMs);if(this.logger?.warn(`${Y} listener unhealthy, restarting`,{channel:$,connector:x,attempt:Q+1,backoffMs:z}),await this.stop($,x),await this.sleep(z),(await this.start($,x)).ok)this.failureCounts.delete(X),this.logger?.info(`${Y} listener recovered`,{channel:$,connector:x});else this.failureCounts.set(X,Q+1)}}var Gs0=new N8,Js0=($)=>`funnel-gateway[${$}]`,Ic1=async($)=>{let x=$.process??Gs0,Y=$.logger,X=Js0($.dir),Q=x.listProcessesContaining(X),z=[];for(let Z of Q){if(Z.pid===$.selfPid)continue;x.kill(Z.pid,"SIGTERM"),z.push(Z.pid),Y?.info("killed competing Slack gateway process",{pid:Z.pid,args:Z.command.slice(0,160)})}return z};var qs0=/^[\w!#$%&'*.^`|~+-]+$/,Ds0=/^[ !#-:<-[\]-~]*$/,Mc1=($)=>{let x=0,Y=$.length;while(x<Y){let X=$.charCodeAt(x);if(X!==32&&X!==9)break;x++}while(Y>x){let X=$.charCodeAt(Y-1);if(X!==32&&X!==9)break;Y--}return x===0&&Y===$.length?$:$.slice(x,Y)},cu=($,x)=>{if(x&&$.indexOf(x)===-1)return{};let Y=$.split(";"),X=Object.create(null);for(let Q of Y){let z=Q.indexOf("=");if(z===-1)continue;let Z=Mc1(Q.substring(0,z));if(x&&x!==Z||!qs0.test(Z)||Z in X)continue;let K=Mc1(Q.substring(z+1));if(K.startsWith('"')&&K.endsWith('"'))K=K.slice(1,-1);if(Ds0.test(K)){if(X[Z]=K.indexOf("%")!==-1?dZ(K,OW):K,x)break}}return X};var _c1=($,x,Y)=>{let X=$.req.raw.headers.get("Cookie");if(typeof x==="string"){if(!X)return;let z=x;if(Y==="secure")z="__Secure-"+x;else if(Y==="host")z="__Host-"+x;return cu(X,z)[z]}if(!X)return{};return cu(X)};var Oc1=($,x)=>{return new Response($,{headers:{"Content-Type":x}}).formData()};var Bs0=/^application\/([a-z-\.]+\+)?json(;\s*[a-zA-Z0-9\-]+\=([^;]+))*$/,Fs0=/^multipart\/form-data(;\s?boundary=[a-zA-Z0-9'"()+_,\-./:=?]+)?$/,Ls0=/^application\/x-www-form-urlencoded(;\s*[a-zA-Z0-9\-]+\=([^;]+))*$/,Tu=($,x)=>{return async(Y,X)=>{let Q={},z=Y.req.header("Content-Type");switch($){case"json":if(!z||!Bs0.test(z))break;try{Q=await Y.req.json()}catch{throw new nZ(400,{message:"Malformed JSON in request body"})}break;case"form":{if(!z||!(Fs0.test(z)||Ls0.test(z)))break;let K;if(Y.req.bodyCache.formData)K=await Y.req.bodyCache.formData;else try{let W=await Y.req.arrayBuffer();K=await Oc1(W,z),Y.req.bodyCache.formData=K}catch(W){let w="Malformed FormData request.";throw w+=W instanceof Error?` ${W.message}`:` ${String(W)}`,new nZ(400,{message:w})}let V=Object.create(null);K.forEach((W,w)=>{if(w.endsWith("[]"))(V[w]??=[]).push(W);else if(Array.isArray(V[w]))V[w].push(W);else if(Object.hasOwn(V,w))V[w]=[V[w],W];else V[w]=W}),Q=V;break}case"query":Q=Object.fromEntries(Object.entries(Y.req.queries()).map(([K,V])=>{return V.length===1?[K,V[0]]:[K,V]}));break;case"param":Q=Y.req.param();break;case"header":Q=Y.req.header();break;case"cookie":Q=_c1(Y);break}let Z=await x(Q,Y);if(Z instanceof Response)return Z;return Y.req.addValidatedData($,Z),await X()}};function Ns0($,x,Y,X){return Tu($,async(Q,z)=>{let Z=Q;if($==="header"&&"_def"in x||$==="header"&&"_zod"in x){let V=Object.keys("in"in x?x.in.shape:x.shape),W=Object.fromEntries(V.map((w)=>[w.toLowerCase(),w]));Z=Object.fromEntries(Object.entries(Q).map(([w,H])=>[W[w]||w,H]))}let K=X&&X.validationFunction?await X.validationFunction(x,Z):await x.safeParseAsync(Z);if(Y){let V=await Y({data:Z,...K,target:$},z);if(V){if(V instanceof Response)return V;if("response"in V)return V.response}}if(!K.success)return z.json(K,400);return K.data})}var lB=Ns0;var dx=($)=>lB("param",$,(x,Y)=>{if(x.success)return;let X=x.error.issues[0],Q=X?`${X.path.join(".")}: ${X.message}`:"invalid request";return Y.json({ok:!1,reason:Q},400)});var js0=S.object({method:S.string().min(1),path:S.string().min(1),body:S.unknown().optional()}),fc1=$8.createHandlers(dx(S.object({channel:S.string().min(1),connector:S.string().min(1)})),async($)=>{let x=$.req.valid("param"),Y=await $.req.json().catch(()=>null),X=js0.safeParse(Y);if(!X.success)throw new nZ(400,{message:X.error.issues[0]?.message??"invalid body"});let Q=await $.var.deps.channels.call(x.channel,x.connector,{method:X.data.method,path:X.data.path,body:X.data.body??{}});return $.json({ok:!0,result:Q})});var bc1=$8.createHandlers(dx(S.object({channel:S.string().min(1)})),lB("json",dk1,($,x)=>{if($.success)return;let Y=$.error.issues[0],X=Y?`${Y.path.join(".")}: ${Y.message}`:"invalid body";return x.json({ok:!1,reason:X},400)}),($)=>{let x=$.req.valid("param"),Y=$.req.valid("json"),Q={ok:!0,offset:$.var.deps.emit({channel:x.channel,connector:Y.connector,content:Y.content,meta:Y.meta}).offset};return $.json(Q)});var Rc1=$8.createHandlers(($)=>{let x=$.var.deps;return $.json({ok:!0,pid:x.selfPid,clients:x.broadcaster.getClientCount(),listeners:x.supervisor.list()})});var Ac1=$8.createHandlers(($)=>{return $.json({listeners:$.var.deps.supervisor.list()})});var kc1=$8.createHandlers(dx(S.object({channel:S.string().min(1),connector:S.string().min(1)})),async($)=>{let x=$.req.valid("param"),Y=await $.var.deps.supervisor.restart(x.channel,x.connector);return $.json(Y,Y.ok?200:400)});var cc1=$8.createHandlers(dx(S.object({channel:S.string().min(1),connector:S.string().min(1)})),async($)=>{let x=$.req.valid("param"),Y=await $.var.deps.supervisor.start(x.channel,x.connector);return $.json(Y,Y.ok?200:400)});var Tc1=$8.createHandlers(dx(S.object({channel:S.string().min(1),connector:S.string().min(1)})),async($)=>{let x=$.req.valid("param"),Y=await $.var.deps.supervisor.stop(x.channel,x.connector);return $.json(Y,Y.ok?200:400)});var Sc1=$8.createHandlers(($)=>{let x=$.var.deps;return $.json({ok:!0,pid:x.selfPid,uptimeMs:x.uptimeMs(),clients:x.broadcaster.listChannels(),listeners:x.supervisor.list(),broadcaster:x.broadcaster.getMetrics()})});var Ec1=$8.createApp().get("/health",...Rc1).get("/status",...Sc1).get("/listeners",...Ac1).post("/listeners/:channel/:connector/start",...cc1).delete("/listeners/:channel/:connector",...Tc1).post("/listeners/:channel/:connector/restart",...kc1).post("/channels/:channel/connectors/:connector/call",...fc1).post("/channels/:channel/publish",...bc1);var _s0=9742,Os0="127.0.0.1",fs0=new Set(["127.0.0.1","localhost","::1","::ffff:127.0.0.1"]),bs0=()=>Ms0(qx(),"events.db"),Rs0=()=>{};class Su{channels;settings;port;hostname;dbPath;process;logger;onError;selfPid;dir;killCompetingSlack;token;broadcaster;eventLog;supervisor;nowMs;extraRoutes;startedAt=null;server=null;constructor($){this.channels=$.channels,this.settings=$.settings,this.port=$.port??_s0,this.hostname=$.hostname??Os0,this.dbPath=$.dbPath??bs0(),this.process=$.process,this.logger=$.logger,this.onError=$.onError??Rs0,this.selfPid=$.selfPid??globalThis.process.pid,this.dir=$.dir??m9,this.killCompetingSlack=$.killCompetingSlack??!0,this.token=$.token??"",this.extraRoutes=$.extraRoutes??null;let x=$.clock;if(this.nowMs=x?()=>x.millis():()=>Date.now(),$.eventLog)this.eventLog=$.eventLog;else{let Y=Is0(this.dbPath);if(!vs0(Y))Ps0(Y,{recursive:!0});this.eventLog=new ku({path:this.dbPath,now:this.nowMs})}this.broadcaster=new Ru({logger:this.logger,onError:this.onError,now:this.nowMs,persistentReplay:this.eventLog}),this.broadcaster.seedLatestOffset(this.eventLog.findMaxOffset()),this.supervisor=new rQ({channels:this.channels,logger:this.logger,onError:this.onError,notify:async(Y,X,Q,z)=>{this.emit({channel:Y,connector:X,content:Q,meta:z})},now:this.nowMs})}async start(){if(this.server)return this.server;if(!this.token&&!fs0.has(this.hostname))this.logger?.warn("gateway auth is disabled on a non-loopback bind \u2014 every endpoint is reachable without a token",{hostname:this.hostname});let $=this.buildApp();return this.startedAt=this.nowMs(),this.server=Bun.serve({port:this.port,hostname:this.hostname,development:!1,fetch:(x,Y)=>this.handleFetch(x,Y,$),websocket:{open:(x)=>this.handleWsOpen(x),close:(x)=>this.handleWsClose(x),message(){}}}),this.logServerStarted(),await this.bootListeners(),this.server}async stop(){if(await this.supervisor.stopAll(),this.server)this.server.stop(),this.server=null}getStatus(){return{clients:this.broadcaster.getClientCount(),channels:this.broadcaster.listChannels()}}getBroadcaster(){return this.broadcaster}getSupervisor(){return this.supervisor}getEventLog(){return this.eventLog}onEvent($){return this.broadcaster.subscribe($)}handleFetch($,x,Y){let X=new URL($.url);if(X.pathname==="/ws"&&$.headers.get("upgrade")==="websocket"){if(this.token&&!this.tokenMatchesUpgrade($))return new Response("unauthorized",{status:401});let Q=X.searchParams.get("tap")==="all",z=Q?"":X.searchParams.get("channel")??"",Z=!Q&&z?this.resolveChannel(z):null,K=Q?"":Z?.id??z,V=Q?null:Z?.name??null,W=Z?.connectors??[],w=Z?.delivery??"fanout",H=X.searchParams.get("since"),G=H===null?Number.NaN:Number.parseInt(H,10),J=Number.isFinite(G)&&G>=0?G:void 0;if(x.upgrade($,{data:{channel:K,channelName:V,connectors:W,tapAll:Q,delivery:w,since:J}}))return;return new Response("WebSocket upgrade failed",{status:400})}return Y.fetch($)}handleWsOpen($){if(typeof $.data.since==="number"){let x=this.broadcaster.replaySince($.data.since,$.data);for(let Y of x)$.send(JSON.stringify(Y))}if(this.broadcaster.addClient($,$.data),$.data.channelName){let x={event_type:"system",action:"channel_connect",channel:$.data.channelName,channelId:$.data.channel,connectors:$.data.connectors.join(","),total:String(this.broadcaster.getClientCount())};this.logger?.info("channel connected",x)}else this.logger?.info("tap-all client connected",{event_type:"system",action:"tap_connect",total:String(this.broadcaster.getClientCount())})}handleWsClose($){if(this.broadcaster.removeClient($),$.data.channelName)this.logger?.info("channel disconnected",{event_type:"system",action:"channel_disconnect",channel:$.data.channelName,channelId:$.data.channel,total:String(this.broadcaster.getClientCount())});else this.logger?.info("tap-all client disconnected",{event_type:"system",action:"tap_disconnect",total:String(this.broadcaster.getClientCount())})}logServerStarted(){this.logger?.info("gateway started",{event_type:"system",action:"gateway_start",port:String(this.port),pid:String(this.selfPid)}),this.logger?.info("funnel gateway listening",{url:`http://localhost:${this.port}`,websocket:`ws://localhost:${this.port}/ws`,health:`http://localhost:${this.port}/health`})}buildApp(){let $=$8.createApp();if($.use((Y,X)=>{return Y.set("deps",{selfPid:this.selfPid,broadcaster:this.broadcaster,supervisor:this.supervisor,channels:this.channels,uptimeMs:()=>this.startedAt?this.nowMs()-this.startedAt:0,emit:(Q)=>this.emit(Q)}),X()}),this.token)$.use("/listeners/*",SB({expected:this.token})),$.use("/status",SB({expected:this.token})),$.use("/channels/*",SB({expected:this.token}));return(this.extraRoutes?$.route("/",this.extraRoutes):$).route("/",Ec1)}tokenMatchesUpgrade($){let x=($.headers.get("sec-websocket-protocol")??"").split(",").map((Q)=>Q.trim()).filter((Q)=>Q.length>0);for(let Q of x)if(Q.startsWith("funnel.token.")&&EB(Q.slice(13),this.token))return!0;let X=($.headers.get("authorization")??"").match(/^Bearer\s+(.+)$/i);if(X&&EB(X[1]??"",this.token))return!0;return!1}resolveChannel($){let Y=this.settings.read()?.channels.find((X)=>X.id===$||X.name===$);if(!Y)return null;return{id:Y.id,name:Y.name,connectors:Y.connectors.map((X)=>X.name),delivery:Y.delivery}}async bootListeners(){let $=this.channels.listAllConnectors();if(this.killCompetingSlack&&$.some((x)=>x.type==="slack")){let x=await Ic1({selfPid:this.selfPid,dir:this.dir,process:this.process,logger:this.logger});if(x.length>0)this.logger?.info("killed competing Slack gateway processes",{event_type:"system",action:"kill_competing",pids:x.join(",")})}await this.supervisor.startAll();for(let x of this.supervisor.list())this.logger?.info(`${x.type} listener started: ${x.name}`,{event_type:"system",action:`${x.type}_connect`,channel:x.channelName,connector:x.name});this.logger?.info(`event store: ${this.dbPath}`),this.logger?.info("funnel gateway running")}emit($){let x=this.lookupChannelId($.channel),Y=x&&$.connector?this.lookupConnectorId(x,$.connector):null,X={...$.meta,channel:$.channel};if($.connector)X.connector=$.connector;if(x)X.channelId=x;if(Y)X.connectorId=Y;let Q=this.broadcaster.broadcast($.content,X);return this.eventLog.record({content:$.content,channelId:x??null,connectorId:Y??null,meta:X,offset:Q.offset}),{offset:Q.offset}}lookupChannelId($){return this.settings.read().channels.find((Y)=>Y.name===$)?.id??null}lookupConnectorId($,x){return this.settings.read().channels.find((Q)=>Q.id===$)?.connectors.find((Q)=>Q.name===x)?.id??null}}import{homedir as As0}from"os";import{dirname as ks0,join as uc1}from"path";var Cc1="gateway.token",cs0=32,Ts0=new j8,Ss0=()=>{let $=new Uint8Array(cs0);return crypto.getRandomValues($),[...$].map((x)=>x.toString(16).padStart(2,"0")).join("")};class Eu{fs;path;generate;constructor($={}){this.fs=$.fs??Ts0,this.path=uc1($.dir??m9,Cc1),this.generate=$.generate??Ss0,Object.freeze(this)}read(){if(!this.fs.existsSync(this.path))return null;let $=this.fs.readFileSync(this.path).trim();return $.length>0?$:null}ensure(){let $=this.read();if($)return $;let x=this.generate();return this.fs.mkdirSync(ks0(this.path),{recursive:!0}),this.fs.writeSecretFileSync(this.path,`${x}
|
package/dist/index.d.ts
CHANGED
|
@@ -607,6 +607,7 @@ declare const channelSpecSchema: z.ZodObject<{
|
|
|
607
607
|
}, z.core.$strip>;
|
|
608
608
|
type ChannelSpec = z.infer<typeof channelSpecSchema>;
|
|
609
609
|
declare const profileSpecSchema: z.ZodObject<{
|
|
610
|
+
name: z.ZodString;
|
|
610
611
|
channel: z.ZodString;
|
|
611
612
|
options: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
612
613
|
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
@@ -635,6 +636,7 @@ declare const localConfigSchema: z.ZodObject<{
|
|
|
635
636
|
}, z.core.$strip>], "type">>>;
|
|
636
637
|
}, z.core.$strip>>;
|
|
637
638
|
profiles: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
639
|
+
name: z.ZodString;
|
|
638
640
|
channel: z.ZodString;
|
|
639
641
|
options: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
640
642
|
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
package/dist/index.js
CHANGED
|
@@ -923,7 +923,9 @@ const channelSpecSchema = z.object({
|
|
|
923
923
|
connectors: z.array(connectorSpecSchema).optional()
|
|
924
924
|
});
|
|
925
925
|
const profileSpecSchema = z.object({
|
|
926
|
-
/**
|
|
926
|
+
/** Handle for `fnl claude --profile <name>`. A profile is only launchable by this name. */
|
|
927
|
+
name: z.string(),
|
|
928
|
+
/** Name of the channel (declared in `channels[]`) this profile binds. The profile depends on the channel, never the reverse. */
|
|
927
929
|
channel: z.string(),
|
|
928
930
|
/** Args prepended to the claude argv on every launch through this profile. */
|
|
929
931
|
options: z.array(z.string()).optional(),
|
|
@@ -986,11 +988,11 @@ var FunnelLocalConfig = class {
|
|
|
986
988
|
const profiles = config.profiles ?? [];
|
|
987
989
|
if (profiles.length === 0) return;
|
|
988
990
|
const channelNames = new Set(config.channels.map((channel) => channel.name));
|
|
989
|
-
const
|
|
991
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
990
992
|
for (const profile of profiles) {
|
|
991
|
-
if (!channelNames.has(profile.channel)) throw new Error(`${LOCAL_CONFIG_FILENAME} is invalid:
|
|
992
|
-
if (
|
|
993
|
-
|
|
993
|
+
if (!channelNames.has(profile.channel)) throw new Error(`${LOCAL_CONFIG_FILENAME} is invalid: profile "${profile.name}" binds channel "${profile.channel}", which is not declared in channels[]`);
|
|
994
|
+
if (seenNames.has(profile.name)) throw new Error(`${LOCAL_CONFIG_FILENAME} is invalid: more than one profile is named "${profile.name}" — names must be unique`);
|
|
995
|
+
seenNames.add(profile.name);
|
|
994
996
|
}
|
|
995
997
|
}
|
|
996
998
|
};
|
package/funnel.schema.json
CHANGED
|
@@ -112,6 +112,9 @@
|
|
|
112
112
|
"items": {
|
|
113
113
|
"type": "object",
|
|
114
114
|
"properties": {
|
|
115
|
+
"name": {
|
|
116
|
+
"type": "string"
|
|
117
|
+
},
|
|
115
118
|
"channel": {
|
|
116
119
|
"type": "string"
|
|
117
120
|
},
|
|
@@ -135,6 +138,7 @@
|
|
|
135
138
|
}
|
|
136
139
|
},
|
|
137
140
|
"required": [
|
|
141
|
+
"name",
|
|
138
142
|
"channel"
|
|
139
143
|
],
|
|
140
144
|
"additionalProperties": false
|
package/package.json
CHANGED