@vibecontrols/vibe-plugin-tunnel 2026.508.3 → 2026.509.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,21 @@
1
- import type { HostServices } from "./types.js";
2
- export declare function registerTunnelCommands(program: any, _hostServices: HostServices): void;
1
+ import type { HostServices } from "@vibecontrols/plugin-sdk/contract";
2
+ /**
3
+ * Minimal commander-shaped surface — keeps the plugin free of a hard
4
+ * dependency on the `commander` package while still type-checking the
5
+ * builder chain.
6
+ */
7
+ /**
8
+ * Minimal commander-shaped surface. We type `.action` as a generic so the
9
+ * concrete handler signature for each subcommand is preserved without
10
+ * needing `any` or eslint-disable comments. Mirrors commander.js's runtime
11
+ * contract where each positional `<arg>` becomes a leading parameter and
12
+ * trailing parameters carry the parsed flags.
13
+ */
14
+ interface CommanderLike {
15
+ command(name: string): CommanderLike;
16
+ description(text: string): CommanderLike;
17
+ option(flag: string, description?: string): CommanderLike;
18
+ action<H extends (...args: never[]) => unknown>(handler: H): CommanderLike;
19
+ }
20
+ export declare function registerTunnelCommands(program: CommanderLike, _hostServices: HostServices): void;
21
+ export {};
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { VibePluginFactory } from "./types.js";
1
+ import type { VibePluginFactory } from "@vibecontrols/plugin-sdk/contract";
2
2
  /**
3
3
  * Plugin Contract v2 factory. Per-profile state (the TunnelManager
4
4
  * instance) lives in this closure so concurrent profiles cannot share
package/dist/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  // @bun
2
- var __require=import.meta.require;import{existsSync,readFileSync}from"fs";import{join,resolve}from"path";function isInteractive(){return!!process.stdout.isTTY&&!!process.stdin.isTTY}function pickOutputMode(flags){if(flags.json)return"json";if(flags.plain)return"plain";if(flags.interactive)return"interactive";return"auto"}function isCi(){return!!process.env.CI||!!process.env.NO_COLOR||process.env.TERM==="dumb"}async function runMultimode(opts){let data=await opts.fetchData(),mode=opts.mode??"auto";if(mode==="json"){let shaped=opts.json?opts.json(data):data;process.stdout.write(`${JSON.stringify(shaped,null,2)}
2
+ var __require=import.meta.require;import{Elysia}from"elysia";function createLifecycleHooks(spec){let{name,onInit,onShutdown,telemetryEventName,skipPlatforms}=spec;return{onServerStart:async(_app,hostServices)=>{if(skipPlatforms&&skipPlatforms.includes(process.platform)){process.stderr.write(`[${name}] skipping init on unsupported platform '${process.platform}'
3
+ `);return}if(onInit)await onInit(hostServices);if(telemetryEventName)hostServices.telemetry?.emit(telemetryEventName,{plugin:name})},onServerStop:async(hostServices)=>{if(onShutdown)await onShutdown(hostServices)}}}var TelemetryEmitter=class{constructor(pluginName,pluginVersion,hostServices){this.pluginName=pluginName,this.pluginVersion=pluginVersion,this.hostServices=hostServices}pluginName;pluginVersion;hostServices;emit(eventName,payload){let target=this.hostServices?.telemetry;if(!target)return;target.emit(eventName,{plugin:this.pluginName,version:this.pluginVersion,timestamp:new Date().toISOString(),...payload??{}})}emitReady(context){this.emit(`${this.pluginName}.ready`,context)}emitError(error,context){this.emit(`${this.pluginName}.error`,{message:error.message,...context??{}})}emitEvent(type,payload){this.emit(type,payload)}},BoundLogger=class{constructor(logger,source){this.logger=logger,this.source=source}logger;source;info(message,meta){this.logger?.info?.(this.source,message,meta)}warn(message,meta){this.logger?.warn?.(this.source,message,meta)}error(message,meta){this.logger?.error?.(this.source,message,meta)}debug(message,meta){this.logger?.debug?.(this.source,message,meta)}};var ProviderRegistry=class{constructor(hostServices){this.hostServices=hostServices}hostServices;getServiceRegistry(){return this.hostServices?.serviceRegistry}registerProvider(type,name,provider){this.hostServices?.serviceRegistry?.registerService(type,name,provider)}getProvider(type,name){return this.hostServices?.serviceRegistry?.getService(type,name)}listProviders(type){return this.hostServices?.serviceRegistry?.listProvidersForType?.(type)??[]}withCliContribution(contribution){let contributors=this.hostServices?.cliContributors;if(!contributors)return;for(let section of contribution.statusSections??[])contributors.addStatusSection?.(section);for(let check of contribution.doctorChecks??[])contributors.addDoctorCheck?.(check)}};import{existsSync,readFileSync}from"fs";import{join,resolve}from"path";function isInteractive(){return!!process.stdout.isTTY&&!!process.stdin.isTTY}function pickOutputMode(flags){if(flags.json)return"json";if(flags.plain)return"plain";if(flags.interactive)return"interactive";return"auto"}function isCi(){return!!process.env.CI||!!process.env.NO_COLOR||process.env.TERM==="dumb"}async function runMultimode(opts){let data=await opts.fetchData(),mode=opts.mode??"auto";if(mode==="json"){let shaped=opts.json?opts.json(data):data;process.stdout.write(`${JSON.stringify(shaped,null,2)}
3
4
  `);return}if(mode==="plain"){await opts.plain(data);return}if((mode==="interactive"||isInteractive()&&!isCi())&&!!opts.interactive&&opts.interactive)try{await opts.interactive(data);return}catch{}await opts.plain(data)}function maybePrintJson(flags,data){if(!flags.json)return!1;return process.stdout.write(`${JSON.stringify(data,null,2)}
4
5
  `),!0}async function loadCore(){return await import("@opentui/core")}async function interactiveTable(opts){if(opts.rows.length===0)return null;let core=await loadCore(),{createCliRenderer,BoxRenderable,TextRenderable,SelectRenderable,SelectRenderableEvents}=core,renderer=await createCliRenderer({exitOnCtrlC:!0,targetFps:30}),ctx=renderer.root.ctx,root=new BoxRenderable(ctx,{width:"100%",height:"100%",flexDirection:"column",backgroundColor:"#0b0d12"}),title=new TextRenderable(ctx,{content:` ${opts.title}`,fg:"#8be9fd",height:1}),footer=new TextRenderable(ctx,{content:` ${opts.footer??"\u2191/\u2193 navigate \xB7 Enter select \xB7 q quit"}`,fg:"#6c7086",height:1}),body=new BoxRenderable(ctx,{width:"100%",flexGrow:1,flexDirection:"row"}),list=new SelectRenderable(ctx,{width:opts.listWidth??28,height:"100%",options:opts.rows.map((r)=>({name:r.label,description:r.hint??"",value:r.id})),selectedIndex:0,showDescription:!0,backgroundColor:"#0b0d12",textColor:"#cdd6f4",focusedBackgroundColor:"#0b0d12",focusedTextColor:"#cdd6f4",selectedBackgroundColor:"#1f2335",selectedTextColor:"#a6e3a1",descriptionColor:"#6c7086",selectedDescriptionColor:"#9399b2",showScrollIndicator:!0,wrapSelection:!0}),detailPane=new BoxRenderable(ctx,{flexGrow:1,height:"100%",flexDirection:"column",paddingLeft:2,paddingRight:2,backgroundColor:"#11141c"}),detailText=new TextRenderable(ctx,{content:"",fg:"#cdd6f4"});detailPane.add(detailText),body.add(list),body.add(detailPane),root.add(title),root.add(body),root.add(footer),renderer.root.add(root),list.focus();let renderDetail=(idx)=>{let row=opts.rows[idx];if(!row)return;detailText.content=`
5
6
  ${row.detail}
6
7
  `};return renderDetail(0),list.on(SelectRenderableEvents.SELECTION_CHANGED,(idx)=>{renderDetail(idx)}),await new Promise((resolve)=>{let cleanup=(chosen)=>{try{renderer.destroy()}catch{}resolve(chosen)};list.on(SelectRenderableEvents.ITEM_SELECTED,()=>{let row=opts.rows[list.getSelectedIndex()];cleanup(row??null)}),renderer.keyInput.on("keypress",(key)=>{if(key.name==="escape"||key.name==="q")cleanup(null)})}).then(async(chosen)=>{if(chosen&&opts.onSelect)await opts.onSelect(chosen);return chosen})}async function interactiveDetail(opts){let core=await loadCore(),{createCliRenderer,BoxRenderable,TextRenderable}=core,renderer=await createCliRenderer({exitOnCtrlC:!0,targetFps:30}),ctx=renderer.root.ctx,root=new BoxRenderable(ctx,{width:"100%",height:"100%",flexDirection:"column",backgroundColor:"#0b0d12"}),title=new TextRenderable(ctx,{content:` ${opts.title}`,fg:"#8be9fd",height:1}),bodyBox=new BoxRenderable(ctx,{width:"100%",flexGrow:1,paddingLeft:2,paddingRight:2,backgroundColor:"#11141c"}),bodyText=new TextRenderable(ctx,{content:`
7
8
  ${opts.body}
8
- `,fg:"#cdd6f4"}),footer=new TextRenderable(ctx,{content:` ${opts.footer??"q to quit"}`,fg:"#6c7086",height:1});bodyBox.add(bodyText),root.add(title),root.add(bodyBox),root.add(footer),renderer.root.add(root),await new Promise((resolve)=>{let cleanup=()=>{try{renderer.destroy()}catch{}resolve()};renderer.keyInput.on("keypress",(key)=>{if(key.name==="escape"||key.name==="q"||key.name==="return")cleanup()})})}var DEFAULT_AGENT_URL="http://localhost:3005";function agentBaseUrl(){return process.env.AGENT_BASE_URL??DEFAULT_AGENT_URL}function redactSecrets(value){if(Array.isArray(value))return value.map(redactSecrets);if(value&&typeof value==="object"){let out={};for(let[k,v]of Object.entries(value))if(/(token|secret|password|apikey|api_key)/i.test(k))out[k]="[REDACTED]";else out[k]=redactSecrets(v);return out}return value}function authHeaders(){let fromEnv=process.env.AGENT_API_KEY??process.env.X_AGENT_API_KEY;if(fromEnv)return{"x-agent-api-key":fromEnv};try{let dir=process.env.VIBECONTROLS_HOME??join(process.cwd(),".boff","vibecontrols"),configPath=join(resolve(dir),"agents",process.env.VIBECONTROLS_PROFILE??"default","config.json");if(existsSync(configPath)){let cfg=JSON.parse(readFileSync(configPath,"utf-8"));if(cfg["static-api-key"])return{"x-agent-api-key":cfg["static-api-key"]}}}catch{}return{}}async function apiGet(path){let res=await fetch(`${agentBaseUrl()}/api/tunnels${path}`,{headers:authHeaders()});if(!res.ok){let text=await res.text().catch(()=>"");throw Error(`GET ${path} failed (${res.status}): ${text}`)}return await res.json()}async function apiPost(path,body){let res=await fetch(`${agentBaseUrl()}/api/tunnels${path}`,{method:"POST",headers:{"Content-Type":"application/json",...authHeaders()},body:body?JSON.stringify(body):void 0});if(!res.ok){let text=await res.text().catch(()=>"");throw Error(`POST ${path} failed (${res.status}): ${text}`)}return await res.json()}async function apiDelete(path){let res=await fetch(`${agentBaseUrl()}/api/tunnels${path}`,{method:"DELETE",headers:authHeaders()});if(!res.ok){let text=await res.text().catch(()=>"");throw Error(`DELETE ${path} failed (${res.status}): ${text}`)}return await res.json()}function tunnelDetail(t){let lines=[`id: ${t.id}`,`provider: ${t.providerName??"-"}`,`status: ${t.status??"-"}`,`url: ${t.url??"-"}`,`protocol: ${t.protocol??"-"}`,`localPort: ${t.localPort??"-"}`];if(t.domains&&Array.isArray(t.domains)&&t.domains.length>0)lines.push(`domains: ${t.domains.join(", ")}`);return lines.join(`
9
- `)}function registerTunnelCommands(program,_hostServices){let cmd=program.command("tunnel").description("Manage tunnels across providers");cmd.command("list").description("List tunnels (optionally filtered by provider)").option("--provider <name>","Limit to a specific provider").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(opts)=>{let qs=opts.provider?`?provider=${encodeURIComponent(opts.provider)}`:"";await runMultimode({mode:pickOutputMode(opts),fetchData:()=>apiGet(`/${qs}`),plain:(result)=>{if(result.tunnels.length===0){console.log("No tunnels.");return}for(let t of result.tunnels)console.log(`${t.id} ${t.providerName} ${t.status} ${t.url} ${t.protocol}:${t.localPort}`)},interactive:async(result)=>{if(result.tunnels.length===0){console.log("No tunnels.");return}let rows=result.tunnels.map((t)=>({id:String(t.id),label:`${t.id} ${t.providerName??""}`.trim(),hint:t.status??"",detail:tunnelDetail(t)}));await interactiveTable({title:`tunnels \u2014 ${result.tunnels.length}`,rows})},json:(result)=>redactSecrets(result)})}),cmd.command("get <tunnelId>").description("Get tunnel status").option("--provider <name>").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(tunnelId,opts)=>{let qs=opts.provider?`?provider=${encodeURIComponent(opts.provider)}`:"";await runMultimode({mode:pickOutputMode(opts),fetchData:()=>apiGet(`/${tunnelId}${qs}`),plain:(result)=>{console.log(JSON.stringify(result,null,2))},interactive:async(result)=>{await interactiveDetail({title:`tunnel ${tunnelId}`,body:tunnelDetail(result)})},json:(result)=>redactSecrets(result)})}),cmd.command("start <tunnelId>").description("Start a prepared tunnel session").option("--provider <name>").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(tunnelId,opts)=>{let result=await apiPost(`/${tunnelId}/start`,{provider:opts.provider});if(maybePrintJson(opts,{ok:!0,action:"start",tunnelId,result:redactSecrets(result)}))return;console.log(JSON.stringify(result,null,2))}),cmd.command("stop <tunnelId>").description("Stop an active tunnel").option("--provider <name>").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(tunnelId,opts)=>{let result=await apiPost(`/${tunnelId}/stop`,{provider:opts.provider});if(maybePrintJson(opts,{ok:!0,action:"stop",tunnelId,result:redactSecrets(result)}))return;console.log(JSON.stringify(result,null,2))}),cmd.command("delete <tunnelId>").description("Delete a tunnel").option("--provider <name>").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(tunnelId,opts)=>{let qs=opts.provider?`?provider=${encodeURIComponent(opts.provider)}`:"",result=await apiDelete(`/${tunnelId}${qs}`);if(maybePrintJson(opts,{ok:!0,action:"delete",tunnelId,result:redactSecrets(result)}))return;console.log(JSON.stringify(result,null,2))}),cmd.command("sessions <tunnelId>").description("List sessions for a tunnel").option("--provider <name>").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(tunnelId,opts)=>{let qs=opts.provider?`?provider=${encodeURIComponent(opts.provider)}`:"";await runMultimode({mode:pickOutputMode(opts),fetchData:()=>apiGet(`/${tunnelId}/sessions${qs}`),plain:(result)=>{console.log(JSON.stringify(result,null,2))},interactive:async(result)=>{let sessions=Array.isArray(result?.sessions)?result.sessions:Array.isArray(result)?result:[];if(sessions.length===0){await interactiveDetail({title:`sessions \u2014 ${tunnelId}`,body:"No sessions."});return}let rows=sessions.map((s,i)=>({id:String(s.id??i),label:String(s.id??`session-${i}`),hint:s.status??"",detail:JSON.stringify(s,null,2)}));await interactiveTable({title:`sessions \u2014 ${tunnelId} (${sessions.length})`,rows})},json:(result)=>redactSecrets(result)})}),cmd.command("status").description("Manager + per-provider health summary").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(opts)=>{await runMultimode({mode:pickOutputMode(opts),fetchData:()=>apiGet("/health"),plain:(result)=>{console.log(JSON.stringify(result,null,2))},interactive:async(result)=>{await interactiveDetail({title:"tunnel status",body:JSON.stringify(result,null,2)})},json:(result)=>redactSecrets(result)})});let providersCmd=cmd.command("providers").description("Manage tunnel providers");providersCmd.command("list").description("List registered tunnel providers and capabilities").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(opts)=>{await runMultimode({mode:pickOutputMode(opts),fetchData:()=>apiGet("/providers"),plain:(result)=>{for(let p of result.providers){let star=p.isDefault?"* ":" ",ok=p.health.ok?"ok":`error (${p.health.message??""})`;console.log(`${star}${p.name} [${ok}]`)}},interactive:async(result)=>{if(result.providers.length===0){await interactiveDetail({title:"providers",body:"No providers."});return}let rows=result.providers.map((p)=>({id:String(p.name),label:`${p.isDefault?"* ":" "}${p.name}`,hint:p.health?.ok?"ok":"error",detail:JSON.stringify(p,null,2)}));await interactiveTable({title:`providers \u2014 ${result.providers.length}`,rows})},json:(result)=>redactSecrets(result)})}),providersCmd.command("set-default <name>").description("Set the default tunnel provider").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(name,opts)=>{let result=await apiPost("/default",{provider:name});if(maybePrintJson(opts,{ok:!0,action:"set-default",provider:name,result:redactSecrets(result)}))return;console.log(JSON.stringify(result,null,2))});let domainsCmd=cmd.command("domains").description("Manage custom domains on a tunnel");domainsCmd.command("add <tunnelId> <domain>").option("--provider <name>").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(tunnelId,domain,opts)=>{let result=await apiPost(`/${tunnelId}/domains`,{domain,provider:opts.provider});if(maybePrintJson(opts,{ok:!0,action:"domains-add",tunnelId,domain,result:redactSecrets(result)}))return;console.log(JSON.stringify(result,null,2))}),domainsCmd.command("rm <tunnelId> <domain>").option("--provider <name>").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(tunnelId,domain,opts)=>{let qs=opts.provider?`?provider=${encodeURIComponent(opts.provider)}`:"",result=await apiDelete(`/${tunnelId}/domains/${encodeURIComponent(domain)}${qs}`);if(maybePrintJson(opts,{ok:!0,action:"domains-rm",tunnelId,domain,result:redactSecrets(result)}))return;console.log(JSON.stringify(result,null,2))}),cmd.command("doctor").description("Diagnose tunnel providers").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(opts)=>{try{await runMultimode({mode:pickOutputMode(opts),fetchData:()=>apiGet("/health"),plain:(health)=>{console.log(`manager: ${health.manager}`);for(let p of health.providers)console.log(` ${p.name}: ${p.ok?"ok":`fail - ${p.message}`}`)},interactive:async(health)=>{let lines=[`manager: ${health.manager}`];for(let p of health.providers)lines.push(` ${p.name}: ${p.ok?"ok":`fail - ${p.message}`}`);await interactiveDetail({title:"tunnel doctor",body:lines.join(`
10
- `)})},json:(health)=>redactSecrets(health)})}catch(err){console.error(`Failed to reach agent: ${err}`),process.exitCode=1}})}var DEFAULT_PROVIDER_CONFIG_KEY="provider:default:tunnel";var LOG_SOURCE="tunnel-manager";class TunnelManager{registry;host;log={info:()=>{},warn:()=>{},error:()=>{},debug:()=>{}};init(host){if(this.host=host,this.registry=host.serviceRegistry,host.logger)this.log=host.logger;this.log.info(LOG_SOURCE,"Tunnel manager initialized")}async resolveProvider(name){if(!this.registry)throw Error("Service registry not available");let explicit=name?.trim();if(explicit){let provider2=this.registry.getProviderByName("tunnel",explicit);if(!provider2)throw Error(`Tunnel provider '${explicit}' is not registered`);return provider2}let fallback=await this.defaultProviderName();if(!fallback)throw Error("No default tunnel provider configured and none specified");let provider=this.registry.getProviderByName("tunnel",fallback);if(!provider)throw Error(`Default tunnel provider '${fallback}' is not registered`);return provider}async defaultProviderName(){let fromConfig=await this.host?.getConfig?.(DEFAULT_PROVIDER_CONFIG_KEY);if(fromConfig)return fromConfig;let entries=this.registry?.listProvidersForType("tunnel")??[],flagged=entries.find((e)=>e.isDefault);if(flagged)return flagged.pluginName;if(entries.length===1)return entries[0].pluginName;return}listProviderEntries(){return this.registry?.listProvidersForType("tunnel")??[]}async listAllTunnels(filterProvider){let entries=this.listProviderEntries(),targeted=filterProvider?entries.filter((e)=>e.pluginName===filterProvider):entries,results=[];for(let entry of targeted){let provider=this.registry?.getProviderByName("tunnel",entry.pluginName);if(!provider)continue;try{let tunnels=await provider.list();results.push({provider:entry.pluginName,tunnels})}catch(err){this.log.warn(LOG_SOURCE,`listAllTunnels: ${entry.pluginName} failed`,{error:String(err)})}}return results}async listProviders(){let entries=this.listProviderEntries(),snapshots=[];for(let entry of entries){let provider=this.registry?.getProviderByName("tunnel",entry.pluginName);if(!provider)continue;let health;try{health=await provider.healthCheck()}catch(err){health={ok:!1,message:String(err)}}snapshots.push({name:entry.pluginName,isDefault:entry.isDefault,capabilities:provider.getCapabilities(),health})}return snapshots}async getTunnel(id,opts={}){if(opts.provider){let provider=await this.resolveProvider(opts.provider),tunnel=await provider.getStatus(id);return tunnel?{provider:provider.name,tunnel}:null}for(let entry of this.listProviderEntries()){let provider=this.registry?.getProviderByName("tunnel",entry.pluginName);if(!provider)continue;try{let tunnel=await provider.getStatus(id);if(tunnel)return{provider:entry.pluginName,tunnel}}catch{}}return null}async issueSession(req){let{provider:providerName,...rest}=req,provider=await this.resolveProvider(providerName);return{...await provider.issueSession(rest),provider:provider.name}}async startTunnel(id,opts={}){return(await this.resolveProvider(opts.provider)).start(id)}async stopTunnel(id,opts={}){return(await this.resolveProvider(opts.provider)).stop(id)}async deleteTunnel(id,opts={}){return(await this.resolveProvider(opts.provider)).delete(id)}async rotate(id,opts={}){return(await this.resolveProvider(opts.provider)).rotate(id)}async attachDomain(id,domain,opts={}){return(await this.resolveProvider(opts.provider)).attachCustomDomain(id,domain)}async detachDomain(id,domain,opts={}){return(await this.resolveProvider(opts.provider)).detachCustomDomain(id,domain)}async listSessions(id,opts={}){return(await this.resolveProvider(opts.provider)).listSessions(id)}async getHealth(){let entries=this.listProviderEntries(),providers=[];for(let entry of entries){let provider=this.registry?.getProviderByName("tunnel",entry.pluginName);if(!provider){providers.push({name:entry.pluginName,ok:!1,message:"unavailable"});continue}try{let result=await provider.healthCheck();providers.push({name:entry.pluginName,ok:result.ok,message:result.message})}catch(err){providers.push({name:entry.pluginName,ok:!1,message:String(err)})}}return{manager:"ok",providers}}async setDefault(providerName){if(await this.resolveProvider(providerName),this.registry?.setProviderDefault)this.registry.setProviderDefault("tunnel",providerName);if(this.host?.setConfig)await this.host.setConfig(DEFAULT_PROVIDER_CONFIG_KEY,providerName);this.log.info(LOG_SOURCE,`Default tunnel provider set to ${providerName}`)}}import{Elysia,t}from"elysia";function readProvider(query){return query?.provider??void 0}function createTunnelManagerRoutes(manager){return new Elysia().get("/providers",async({set})=>{try{return{providers:await manager.listProviders()}}catch(err){return set.status=500,{error:"Failed to list providers",details:String(err)}}}).post("/default",async({body,set})=>{try{return await manager.setDefault(body.provider),{success:!0,provider:body.provider}}catch(err){return set.status=400,{error:String(err)}}},{body:t.Object({provider:t.String()})}).get("/health",async({set})=>{try{return await manager.getHealth()}catch(err){return set.status=500,{error:"Failed to get health",details:String(err)}}}).get("/",async({query,set})=>{try{let providerName=readProvider(query),results=await manager.listAllTunnels(providerName);return{tunnels:results.flatMap((r)=>r.tunnels.map((t2)=>({...t2,providerName:r.provider}))),byProvider:results}}catch(err){return set.status=500,{error:"Failed to list tunnels",details:String(err)}}}).post("/issue-session",async({body,set})=>{try{return await manager.issueSession({provider:body.provider,protocol:body.protocol,localPort:body.localPort,localHost:body.localHost,subdomain:body.subdomain,customDomain:body.customDomain,ttlSeconds:body.ttlSeconds,metadata:body.metadata,controlPlanePayload:body.controlPlanePayload})}catch(err){return set.status=500,{error:"Failed to issue session",details:String(err)}}},{body:t.Object({provider:t.Optional(t.String()),protocol:t.Union([t.Literal("http"),t.Literal("https"),t.Literal("tcp"),t.Literal("udp")]),localPort:t.Number(),localHost:t.Optional(t.String()),subdomain:t.Optional(t.String()),customDomain:t.Optional(t.String()),ttlSeconds:t.Optional(t.Number()),metadata:t.Optional(t.Any()),controlPlanePayload:t.Optional(t.Any())})}).get("/:id",async({params,query,set})=>{try{let result=await manager.getTunnel(params.id,{provider:readProvider(query)});if(!result)return set.status=404,{error:"Tunnel not found"};return result}catch(err){return set.status=500,{error:"Failed to get tunnel",details:String(err)}}}).get("/:id/sessions",async({params,query,set})=>{try{return{sessions:await manager.listSessions(params.id,{provider:readProvider(query)})}}catch(err){return set.status=500,{error:"Failed to list sessions",details:String(err)}}}).post("/:id/start",async({params,body,set})=>{try{return{success:!0,tunnel:await manager.startTunnel(params.id,{provider:body?.provider})}}catch(err){return set.status=500,{error:"Failed to start tunnel",details:String(err)}}},{body:t.Optional(t.Object({provider:t.Optional(t.String())}))}).post("/:id/stop",async({params,body,set})=>{try{return await manager.stopTunnel(params.id,{provider:body?.provider}),{success:!0}}catch(err){return set.status=500,{error:"Failed to stop tunnel",details:String(err)}}},{body:t.Optional(t.Object({provider:t.Optional(t.String())}))}).post("/:id/rotate",async({params,body,set})=>{try{return{success:!0,session:await manager.rotate(params.id,{provider:body?.provider})}}catch(err){return set.status=500,{error:"Failed to rotate tunnel",details:String(err)}}},{body:t.Optional(t.Object({provider:t.Optional(t.String())}))}).delete("/:id",async({params,query,set})=>{try{return await manager.deleteTunnel(params.id,{provider:readProvider(query)}),{success:!0}}catch(err){return set.status=500,{error:"Failed to delete tunnel",details:String(err)}}}).post("/:id/domains",async({params,body,set})=>{try{return await manager.attachDomain(params.id,body.domain,{provider:body.provider}),{success:!0}}catch(err){return set.status=500,{error:"Failed to attach domain",details:String(err)}}},{body:t.Object({domain:t.String(),provider:t.Optional(t.String())})}).delete("/:id/domains/:domain",async({params,query,set})=>{try{return await manager.detachDomain(params.id,params.domain,{provider:readProvider(query)}),{success:!0}}catch(err){return set.status=500,{error:"Failed to detach domain",details:String(err)}}})}var createPlugin=(_ctx)=>{let manager=new TunnelManager;return{capabilities:{storage:"rw",subprocess:!0,broadcast:!0,audit:!0,telemetry:!0},name:"tunnel",version:"0.1.0",description:"VibeTunnels manager \u2014 dispatches to registered tunnel providers",tags:["backend","cli","integration"],cliCommand:"tunnel",apiPrefix:"/api/tunnels",createRoutes:()=>createTunnelManagerRoutes(manager),onServerStart:(_app,hostServices)=>{hostServices?.telemetry?.emit("tunnel.meta.ready",{}),manager.init(hostServices)},onCliSetup:(program,hostServices)=>{registerTunnelCommands(program,hostServices),registerStatusContributors(hostServices)}}};function registerStatusContributors(hostServices){let reg=hostServices.cliContributors;if(!reg)return;reg.addStatusSection({source:"tunnel",title:"Tunnel",render:async({agentUrl})=>{try{let res=await fetch(`${agentUrl}/api/agent/tunnel`);if(!res.ok)return null;let t2=await res.json(),url=t2.tunnelUrl??t2.publicUrl??t2.url;if(url)return`\x1B[32m${url}\x1B[39m`;let status=t2.status??"unknown";if(status==="active"||status==="running")return"\x1B[33mstarting...\x1B[39m";return"\x1B[2m(not running)\x1B[22m"}catch{return null}},json:async({agentUrl})=>{try{let res=await fetch(`${agentUrl}/api/agent/tunnel`);if(!res.ok)return null;return await res.json()}catch{return null}},jsonKey:"tunnel"}),reg.addDoctorCheck({source:"tunnel",run:async()=>{try{let port=(process.env.AGENT_URL??"http://localhost:3005").replace(/\/+$/,""),res=await fetch(`${port}/api/agent/tunnel`);if(!res.ok)return[];let t2=await res.json();if(!t2.status||t2.status==="stopped")return[{name:"Tunnel state",ok:!0,message:"no tunnel currently running"}];if(t2.status==="active"||t2.status==="running")return[{name:"Tunnel state",ok:!0,message:`${t2.provider??"tunnel"} alive \u2192 ${t2.tunnelUrl??"(no url)"}`}];return[{name:"Tunnel state",ok:!1,grade:"warn",message:`tunnel reports status=${t2.status}`,hint:"Run `vibe tunnel kill --orphans` to clean up."}]}catch{return[]}}})}var src_default=createPlugin;export{src_default as default,createPlugin,TunnelManager};
9
+ `,fg:"#cdd6f4"}),footer=new TextRenderable(ctx,{content:` ${opts.footer??"q to quit"}`,fg:"#6c7086",height:1});bodyBox.add(bodyText),root.add(title),root.add(bodyBox),root.add(footer),renderer.root.add(root),await new Promise((resolve)=>{let cleanup=()=>{try{renderer.destroy()}catch{}resolve()};renderer.keyInput.on("keypress",(key)=>{if(key.name==="escape"||key.name==="q"||key.name==="return")cleanup()})})}var DEFAULT_AGENT_URL="http://localhost:3005";function agentBaseUrl(){return process.env.AGENT_BASE_URL??DEFAULT_AGENT_URL}function redactSecrets(value){if(Array.isArray(value))return value.map(redactSecrets);if(value&&typeof value==="object"){let out={};for(let[k,v]of Object.entries(value))if(/(token|secret|password|apikey|api_key)/i.test(k))out[k]="[REDACTED]";else out[k]=redactSecrets(v);return out}return value}function authHeaders(){let fromEnv=process.env.AGENT_API_KEY??process.env.X_AGENT_API_KEY;if(fromEnv)return{"x-agent-api-key":fromEnv};try{let dir=process.env.VIBECONTROLS_HOME??join(process.cwd(),".boff","vibecontrols"),configPath=join(resolve(dir),"agents",process.env.VIBECONTROLS_PROFILE??"default","config.json");if(existsSync(configPath)){let cfg=JSON.parse(readFileSync(configPath,"utf-8"));if(cfg["static-api-key"])return{"x-agent-api-key":cfg["static-api-key"]}}}catch{}return{}}async function apiGet(path){let res=await fetch(`${agentBaseUrl()}/api/tunnels${path}`,{headers:authHeaders()});if(!res.ok){let text=await res.text().catch(()=>"");throw Error(`GET ${path} failed (${res.status}): ${text}`)}return await res.json()}async function apiPost(path,body){let res=await fetch(`${agentBaseUrl()}/api/tunnels${path}`,{method:"POST",headers:{"Content-Type":"application/json",...authHeaders()},body:body?JSON.stringify(body):void 0});if(!res.ok){let text=await res.text().catch(()=>"");throw Error(`POST ${path} failed (${res.status}): ${text}`)}return await res.json()}async function apiDelete(path){let res=await fetch(`${agentBaseUrl()}/api/tunnels${path}`,{method:"DELETE",headers:authHeaders()});if(!res.ok){let text=await res.text().catch(()=>"");throw Error(`DELETE ${path} failed (${res.status}): ${text}`)}return await res.json()}function tunnelDetail(t){let lines=[`id: ${t.id??"-"}`,`provider: ${t.providerName??"-"}`,`status: ${t.status??"-"}`,`url: ${t.url??"-"}`,`protocol: ${t.protocol??"-"}`,`localPort: ${t.localPort??"-"}`];if(t.domains&&Array.isArray(t.domains)&&t.domains.length>0)lines.push(`domains: ${t.domains.join(", ")}`);return lines.join(`
10
+ `)}function registerTunnelCommands(program,_hostServices){let cmd=program.command("tunnel").description("Manage tunnels across providers");cmd.command("list").description("List tunnels (optionally filtered by provider)").option("--provider <name>","Limit to a specific provider").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(opts)=>{let qs=opts.provider?`?provider=${encodeURIComponent(opts.provider)}`:"";await runMultimode({mode:pickOutputMode(opts),fetchData:()=>apiGet(`/${qs}`),plain:(result)=>{if(result.tunnels.length===0){console.log("No tunnels.");return}for(let t of result.tunnels)console.log(`${t.id} ${t.providerName} ${t.status} ${t.url} ${t.protocol}:${t.localPort}`)},interactive:async(result)=>{if(result.tunnels.length===0){console.log("No tunnels.");return}let rows=result.tunnels.map((t)=>({id:String(t.id),label:`${t.id} ${t.providerName??""}`.trim(),hint:t.status??"",detail:tunnelDetail(t)}));await interactiveTable({title:`tunnels \u2014 ${result.tunnels.length}`,rows})},json:(result)=>redactSecrets(result)})}),cmd.command("get <tunnelId>").description("Get tunnel status").option("--provider <name>").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(tunnelId,opts)=>{let qs=opts.provider?`?provider=${encodeURIComponent(opts.provider)}`:"";await runMultimode({mode:pickOutputMode(opts),fetchData:()=>apiGet(`/${tunnelId}${qs}`),plain:(result)=>{console.log(JSON.stringify(result,null,2))},interactive:async(result)=>{await interactiveDetail({title:`tunnel ${tunnelId}`,body:tunnelDetail(result)})},json:(result)=>redactSecrets(result)})}),cmd.command("start <tunnelId>").description("Start a prepared tunnel session").option("--provider <name>").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(tunnelId,opts)=>{let result=await apiPost(`/${tunnelId}/start`,{provider:opts.provider});if(maybePrintJson(opts,{ok:!0,action:"start",tunnelId,result:redactSecrets(result)}))return;console.log(JSON.stringify(result,null,2))}),cmd.command("stop <tunnelId>").description("Stop an active tunnel").option("--provider <name>").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(tunnelId,opts)=>{let result=await apiPost(`/${tunnelId}/stop`,{provider:opts.provider});if(maybePrintJson(opts,{ok:!0,action:"stop",tunnelId,result:redactSecrets(result)}))return;console.log(JSON.stringify(result,null,2))}),cmd.command("delete <tunnelId>").description("Delete a tunnel").option("--provider <name>").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(tunnelId,opts)=>{let qs=opts.provider?`?provider=${encodeURIComponent(opts.provider)}`:"",result=await apiDelete(`/${tunnelId}${qs}`);if(maybePrintJson(opts,{ok:!0,action:"delete",tunnelId,result:redactSecrets(result)}))return;console.log(JSON.stringify(result,null,2))}),cmd.command("sessions <tunnelId>").description("List sessions for a tunnel").option("--provider <name>").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(tunnelId,opts)=>{let qs=opts.provider?`?provider=${encodeURIComponent(opts.provider)}`:"";await runMultimode({mode:pickOutputMode(opts),fetchData:()=>apiGet(`/${tunnelId}/sessions${qs}`),plain:(result)=>{console.log(JSON.stringify(result,null,2))},interactive:async(result)=>{let sessions=(()=>{if(result&&typeof result==="object"&&"sessions"in result&&Array.isArray(result.sessions))return result.sessions;return Array.isArray(result)?result:[]})();if(sessions.length===0){await interactiveDetail({title:`sessions \u2014 ${tunnelId}`,body:"No sessions."});return}let rows=sessions.map((s,i)=>({id:String(s.id??i),label:String(s.id??`session-${i}`),hint:s.status??"",detail:JSON.stringify(s,null,2)}));await interactiveTable({title:`sessions \u2014 ${tunnelId} (${sessions.length})`,rows})},json:(result)=>redactSecrets(result)})}),cmd.command("status").description("Manager + per-provider health summary").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(opts)=>{await runMultimode({mode:pickOutputMode(opts),fetchData:()=>apiGet("/health"),plain:(result)=>{console.log(JSON.stringify(result,null,2))},interactive:async(result)=>{await interactiveDetail({title:"tunnel status",body:JSON.stringify(result,null,2)})},json:(result)=>redactSecrets(result)})});let providersCmd=cmd.command("providers").description("Manage tunnel providers");providersCmd.command("list").description("List registered tunnel providers and capabilities").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(opts)=>{await runMultimode({mode:pickOutputMode(opts),fetchData:()=>apiGet("/providers"),plain:(result)=>{for(let p of result.providers){let star=p.isDefault?"* ":" ",ok=p.health.ok?"ok":`error (${p.health.message??""})`;console.log(`${star}${p.name} [${ok}]`)}},interactive:async(result)=>{if(result.providers.length===0){await interactiveDetail({title:"providers",body:"No providers."});return}let rows=result.providers.map((p)=>({id:String(p.name),label:`${p.isDefault?"* ":" "}${p.name}`,hint:p.health?.ok?"ok":"error",detail:JSON.stringify(p,null,2)}));await interactiveTable({title:`providers \u2014 ${result.providers.length}`,rows})},json:(result)=>redactSecrets(result)})}),providersCmd.command("set-default <name>").description("Set the default tunnel provider").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(name,opts)=>{let result=await apiPost("/default",{provider:name});if(maybePrintJson(opts,{ok:!0,action:"set-default",provider:name,result:redactSecrets(result)}))return;console.log(JSON.stringify(result,null,2))});let domainsCmd=cmd.command("domains").description("Manage custom domains on a tunnel");domainsCmd.command("add <tunnelId> <domain>").option("--provider <name>").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(tunnelId,domain,opts)=>{let result=await apiPost(`/${tunnelId}/domains`,{domain,provider:opts.provider});if(maybePrintJson(opts,{ok:!0,action:"domains-add",tunnelId,domain,result:redactSecrets(result)}))return;console.log(JSON.stringify(result,null,2))}),domainsCmd.command("rm <tunnelId> <domain>").option("--provider <name>").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(tunnelId,domain,opts)=>{let qs=opts.provider?`?provider=${encodeURIComponent(opts.provider)}`:"",result=await apiDelete(`/${tunnelId}/domains/${encodeURIComponent(domain)}${qs}`);if(maybePrintJson(opts,{ok:!0,action:"domains-rm",tunnelId,domain,result:redactSecrets(result)}))return;console.log(JSON.stringify(result,null,2))}),cmd.command("doctor").description("Diagnose tunnel providers").option("--json","Emit JSON").option("--plain","Force plain text output").action(async(opts)=>{try{await runMultimode({mode:pickOutputMode(opts),fetchData:()=>apiGet("/health"),plain:(health)=>{console.log(`manager: ${health.manager}`);for(let p of health.providers)console.log(` ${p.name}: ${p.ok?"ok":`fail - ${p.message}`}`)},interactive:async(health)=>{let lines=[`manager: ${health.manager}`];for(let p of health.providers)lines.push(` ${p.name}: ${p.ok?"ok":`fail - ${p.message}`}`);await interactiveDetail({title:"tunnel doctor",body:lines.join(`
11
+ `)})},json:(health)=>redactSecrets(health)})}catch(err){console.error(`Failed to reach agent: ${err}`),process.exitCode=1}})}var DEFAULT_PROVIDER_CONFIG_KEY="provider:default:tunnel";var LOG_SOURCE="tunnel-manager";class TunnelManager{registry;host;log=new BoundLogger(void 0,LOG_SOURCE);init(host){this.host=host,this.registry=host.serviceRegistry,this.log=new BoundLogger(host.logger,LOG_SOURCE),this.log.info("Tunnel manager initialized")}async resolveProvider(name){if(!this.registry)throw Error("Service registry not available");let explicit=name?.trim();if(explicit){let provider2=this.registry.getProviderByName("tunnel",explicit);if(!provider2)throw Error(`Tunnel provider '${explicit}' is not registered`);return provider2}let fallback=await this.defaultProviderName();if(!fallback)throw Error("No default tunnel provider configured and none specified");let provider=this.registry.getProviderByName("tunnel",fallback);if(!provider)throw Error(`Default tunnel provider '${fallback}' is not registered`);return provider}async defaultProviderName(){let fromConfig=await this.host?.getConfig?.(DEFAULT_PROVIDER_CONFIG_KEY);if(fromConfig)return fromConfig;let entries=this.registry?.listProvidersForType("tunnel")??[],flagged=entries.find((e)=>e.isDefault);if(flagged)return flagged.pluginName;if(entries.length===1)return entries[0].pluginName;return}listProviderEntries(){return this.registry?.listProvidersForType("tunnel")??[]}async listAllTunnels(filterProvider){let entries=this.listProviderEntries(),targeted=filterProvider?entries.filter((e)=>e.pluginName===filterProvider):entries,results=[];for(let entry of targeted){let provider=this.registry?.getProviderByName("tunnel",entry.pluginName);if(!provider)continue;try{let tunnels=await provider.list();results.push({provider:entry.pluginName,tunnels})}catch(err){this.log.warn(`listAllTunnels: ${entry.pluginName} failed`,{error:String(err)})}}return results}async listProviders(){let entries=this.listProviderEntries(),snapshots=[];for(let entry of entries){let provider=this.registry?.getProviderByName("tunnel",entry.pluginName);if(!provider)continue;let health;try{health=await provider.healthCheck()}catch(err){health={ok:!1,message:String(err)}}snapshots.push({name:entry.pluginName,isDefault:entry.isDefault,capabilities:provider.getCapabilities(),health})}return snapshots}async getTunnel(id,opts={}){if(opts.provider){let provider=await this.resolveProvider(opts.provider),tunnel=await provider.getStatus(id);return tunnel?{provider:provider.name,tunnel}:null}for(let entry of this.listProviderEntries()){let provider=this.registry?.getProviderByName("tunnel",entry.pluginName);if(!provider)continue;try{let tunnel=await provider.getStatus(id);if(tunnel)return{provider:entry.pluginName,tunnel}}catch{}}return null}async issueSession(req){let{provider:providerName,...rest}=req,provider=await this.resolveProvider(providerName);return{...await provider.issueSession(rest),provider:provider.name}}async startTunnel(id,opts={}){return(await this.resolveProvider(opts.provider)).start(id)}async stopTunnel(id,opts={}){return(await this.resolveProvider(opts.provider)).stop(id)}async deleteTunnel(id,opts={}){return(await this.resolveProvider(opts.provider)).delete(id)}async rotate(id,opts={}){return(await this.resolveProvider(opts.provider)).rotate(id)}async attachDomain(id,domain,opts={}){return(await this.resolveProvider(opts.provider)).attachCustomDomain(id,domain)}async detachDomain(id,domain,opts={}){return(await this.resolveProvider(opts.provider)).detachCustomDomain(id,domain)}async listSessions(id,opts={}){return(await this.resolveProvider(opts.provider)).listSessions(id)}async getHealth(){let entries=this.listProviderEntries(),providers=[];for(let entry of entries){let provider=this.registry?.getProviderByName("tunnel",entry.pluginName);if(!provider){providers.push({name:entry.pluginName,ok:!1,message:"unavailable"});continue}try{let result=await provider.healthCheck();providers.push({name:entry.pluginName,ok:result.ok,message:result.message})}catch(err){providers.push({name:entry.pluginName,ok:!1,message:String(err)})}}return{manager:"ok",providers}}async setDefault(providerName){if(await this.resolveProvider(providerName),this.registry?.setProviderDefault)this.registry.setProviderDefault("tunnel",providerName);let hostWithSetConfig=this.host;if(hostWithSetConfig?.setConfig)await hostWithSetConfig.setConfig(DEFAULT_PROVIDER_CONFIG_KEY,providerName);this.log.info(`Default tunnel provider set to ${providerName}`)}}import{Elysia as Elysia2,t}from"elysia";function readProvider(query){return query?.provider??void 0}function createTunnelManagerRoutes(manager){return new Elysia2().get("/providers",async({set})=>{try{return{providers:await manager.listProviders()}}catch(err){return set.status=500,{error:"Failed to list providers",details:String(err)}}}).post("/default",async({body,set})=>{try{return await manager.setDefault(body.provider),{success:!0,provider:body.provider}}catch(err){return set.status=400,{error:String(err)}}},{body:t.Object({provider:t.String()})}).get("/health",async({set})=>{try{return await manager.getHealth()}catch(err){return set.status=500,{error:"Failed to get health",details:String(err)}}}).get("/",async({query,set})=>{try{let providerName=readProvider(query),results=await manager.listAllTunnels(providerName);return{tunnels:results.flatMap((r)=>r.tunnels.map((t2)=>({...t2,providerName:r.provider}))),byProvider:results}}catch(err){return set.status=500,{error:"Failed to list tunnels",details:String(err)}}}).post("/issue-session",async({body,set})=>{try{return await manager.issueSession({provider:body.provider,protocol:body.protocol,localPort:body.localPort,localHost:body.localHost,subdomain:body.subdomain,customDomain:body.customDomain,ttlSeconds:body.ttlSeconds,metadata:body.metadata,controlPlanePayload:body.controlPlanePayload})}catch(err){return set.status=500,{error:"Failed to issue session",details:String(err)}}},{body:t.Object({provider:t.Optional(t.String()),protocol:t.Union([t.Literal("http"),t.Literal("https"),t.Literal("tcp"),t.Literal("udp")]),localPort:t.Number(),localHost:t.Optional(t.String()),subdomain:t.Optional(t.String()),customDomain:t.Optional(t.String()),ttlSeconds:t.Optional(t.Number()),metadata:t.Optional(t.Any()),controlPlanePayload:t.Optional(t.Any())})}).get("/:id",async({params,query,set})=>{try{let result=await manager.getTunnel(params.id,{provider:readProvider(query)});if(!result)return set.status=404,{error:"Tunnel not found"};return result}catch(err){return set.status=500,{error:"Failed to get tunnel",details:String(err)}}}).get("/:id/sessions",async({params,query,set})=>{try{return{sessions:await manager.listSessions(params.id,{provider:readProvider(query)})}}catch(err){return set.status=500,{error:"Failed to list sessions",details:String(err)}}}).post("/:id/start",async({params,body,set})=>{try{return{success:!0,tunnel:await manager.startTunnel(params.id,{provider:body?.provider})}}catch(err){return set.status=500,{error:"Failed to start tunnel",details:String(err)}}},{body:t.Optional(t.Object({provider:t.Optional(t.String())}))}).post("/:id/stop",async({params,body,set})=>{try{return await manager.stopTunnel(params.id,{provider:body?.provider}),{success:!0}}catch(err){return set.status=500,{error:"Failed to stop tunnel",details:String(err)}}},{body:t.Optional(t.Object({provider:t.Optional(t.String())}))}).post("/:id/rotate",async({params,body,set})=>{try{return{success:!0,session:await manager.rotate(params.id,{provider:body?.provider})}}catch(err){return set.status=500,{error:"Failed to rotate tunnel",details:String(err)}}},{body:t.Optional(t.Object({provider:t.Optional(t.String())}))}).delete("/:id",async({params,query,set})=>{try{return await manager.deleteTunnel(params.id,{provider:readProvider(query)}),{success:!0}}catch(err){return set.status=500,{error:"Failed to delete tunnel",details:String(err)}}}).post("/:id/domains",async({params,body,set})=>{try{return await manager.attachDomain(params.id,body.domain,{provider:body.provider}),{success:!0}}catch(err){return set.status=500,{error:"Failed to attach domain",details:String(err)}}},{body:t.Object({domain:t.String(),provider:t.Optional(t.String())})}).delete("/:id/domains/:domain",async({params,query,set})=>{try{return await manager.detachDomain(params.id,params.domain,{provider:readProvider(query)}),{success:!0}}catch(err){return set.status=500,{error:"Failed to detach domain",details:String(err)}}})}var PLUGIN_NAME="tunnel",PLUGIN_VERSION="0.1.0",createPlugin=(_ctx)=>{let manager=new TunnelManager,telemetry=new TelemetryEmitter(PLUGIN_NAME,PLUGIN_VERSION),lifecycle=createLifecycleHooks({name:PLUGIN_NAME,telemetryEventName:"tunnel.meta.ready",onInit:(hostServices)=>{manager.init(hostServices),telemetry.emit("tunnel.manager.ready")}});return{capabilities:{storage:"rw",subprocess:!0,broadcast:!0,audit:!0,telemetry:!0},name:PLUGIN_NAME,version:PLUGIN_VERSION,description:"VibeTunnels manager \u2014 dispatches to registered tunnel providers",tags:["backend","cli","integration"],cliCommand:"tunnel",apiPrefix:"/api/tunnels",createRoutes:()=>createTunnelManagerRoutes(manager),onServerStart:lifecycle.onServerStart,onServerStop:lifecycle.onServerStop,onCliSetup:(program,hostServices)=>{registerTunnelCommands(program,hostServices),registerCliContributors(hostServices)}}};function registerCliContributors(hostServices){let providers=new ProviderRegistry(hostServices),statusSection={source:"tunnel",title:"Tunnel",render:async({agentUrl})=>{try{let res=await fetch(`${agentUrl}/api/agent/tunnel`);if(!res.ok)return null;let t2=await res.json(),url=t2.tunnelUrl??t2.publicUrl??t2.url;if(url)return`\x1B[32m${url}\x1B[39m`;let status=t2.status??"unknown";if(status==="active"||status==="running")return"\x1B[33mstarting...\x1B[39m";return"\x1B[2m(not running)\x1B[22m"}catch{return null}},json:async({agentUrl})=>{try{let res=await fetch(`${agentUrl}/api/agent/tunnel`);if(!res.ok)return null;return await res.json()}catch{return null}},jsonKey:"tunnel"},doctorCheck={source:"tunnel",run:async()=>{try{let port=(process.env.AGENT_URL??"http://localhost:3005").replace(/\/+$/,""),res=await fetch(`${port}/api/agent/tunnel`);if(!res.ok)return[];let t2=await res.json();if(!t2.status||t2.status==="stopped")return[{name:"Tunnel state",ok:!0,message:"no tunnel currently running"}];if(t2.status==="active"||t2.status==="running")return[{name:"Tunnel state",ok:!0,message:`${t2.provider??"tunnel"} alive \u2192 ${t2.tunnelUrl??"(no url)"}`}];return[{name:"Tunnel state",ok:!1,grade:"warn",message:`tunnel reports status=${t2.status}`,hint:"Run `vibe tunnel kill --orphans` to clean up."}]}catch{return[]}}};providers.withCliContribution({statusSections:[statusSection],doctorChecks:[doctorCheck]})}var src_default=createPlugin;export{src_default as default,createPlugin,TunnelManager};
package/dist/manager.d.ts CHANGED
@@ -1,11 +1,5 @@
1
- /**
2
- * TunnelManager facade over registered tunnel providers.
3
- *
4
- * Looks up concrete providers in the agent's service registry by name and
5
- * dispatches each operation to the selected provider. Falls back to the
6
- * configured default provider when no explicit name is given.
7
- */
8
- import { type HostServices, type IssueSessionRequest, type TunnelInfo, type TunnelProviderCapabilities, type TunnelSessionInfo } from "./types.js";
1
+ import type { HostServices } from "@vibecontrols/plugin-sdk/contract";
2
+ import { type IssueSessionRequest, type TunnelInfo, type TunnelProviderCapabilities, type TunnelSessionInfo } from "./types.js";
9
3
  export interface ProviderSnapshot {
10
4
  name: string;
11
5
  isDefault: boolean;
package/dist/types.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
- * Locally-redeclared types so the plugin has no hard dependency on
3
- * @vibecontrols/agent. Keeps plugin bundles self-contained.
2
+ * Tunnel-domain types (provider contract, session/info shapes). Plugin
3
+ * lifecycle / host / profile / capability types now come from
4
+ * `@vibecontrols/plugin-sdk` and `@vibecontrols/plugin-sdk/contract`.
4
5
  */
5
6
  export type TunnelStatus = "starting" | "active" | "stopping" | "stopped" | "error";
6
7
  export type TunnelProtocol = "http" | "https" | "tcp" | "udp";
@@ -83,7 +84,14 @@ export interface TunnelProvider {
83
84
  streamLogs?(tunnelId: string): AsyncIterable<string>;
84
85
  getActiveTunnelUrl?(): Promise<string | null>;
85
86
  }
86
- export interface ServiceRegistryLike {
87
+ /**
88
+ * The tunnel manager dispatches via the agent's runtime registry which
89
+ * exposes a richer surface than the SDK's neutral `ServiceRegistry`
90
+ * (provider defaults, listing as `{pluginName, isDefault}` entries).
91
+ * We declare a structural extension so the manager can talk to it
92
+ * without depending on the agent package directly.
93
+ */
94
+ export interface TunnelServiceRegistry {
87
95
  registerService?(pluginName: string, serviceName: string, service: unknown): void;
88
96
  getProviderByName<T>(type: string, name: string): T | undefined;
89
97
  listProvidersForType(type: string): Array<{
@@ -92,95 +100,30 @@ export interface ServiceRegistryLike {
92
100
  }>;
93
101
  setProviderDefault?(type: string, name: string): void;
94
102
  }
95
- export interface HostLogger {
96
- info(source: string, msg: string, meta?: Record<string, unknown>): void;
97
- warn(source: string, msg: string, meta?: Record<string, unknown>): void;
98
- error(source: string, msg: string, meta?: Record<string, unknown>): void;
99
- debug(source: string, msg: string, meta?: Record<string, unknown>): void;
100
- }
101
103
  /**
102
- * Minimal contributor surface — duck-typed against
103
- * `vibecontrols-agent`'s `CliContributorRegistry`. Plugin uses these to
104
- * inject status/doctor sections without depending on agent types.
104
+ * Status / doctor contributor entry shapes — duck-typed against the
105
+ * agent's `CliContributorRegistry`. Used as the strongly-typed inputs to
106
+ * `ProviderRegistry.withCliContribution`.
105
107
  */
106
- export interface CliContributorRegistryLike {
107
- addStatusSection(section: {
108
- source: string;
109
- title: string;
110
- render: (ctx: {
111
- agentUrl: string;
112
- }) => Promise<string | null>;
113
- json?: (ctx: {
114
- agentUrl: string;
115
- }) => Promise<unknown>;
116
- jsonKey?: string;
117
- }): void;
118
- addDoctorCheck(check: {
119
- source: string;
120
- run: () => Promise<Array<{
121
- name: string;
122
- ok: boolean;
123
- grade?: "warn";
124
- message: string;
125
- hint?: string;
126
- }>>;
127
- }): void;
108
+ export interface TunnelStatusSection {
109
+ source: string;
110
+ title: string;
111
+ render: (ctx: {
112
+ agentUrl: string;
113
+ }) => Promise<string | null>;
114
+ json?: (ctx: {
115
+ agentUrl: string;
116
+ }) => Promise<unknown>;
117
+ jsonKey?: string;
128
118
  }
129
- export interface HostServices {
130
- telemetry?: {
131
- emit: (name: string, payload?: Record<string, unknown>) => void;
132
- };
133
- logger?: HostLogger;
134
- serviceRegistry?: ServiceRegistryLike;
135
- getConfig?(key: string): Promise<string | undefined>;
136
- setConfig?(key: string, value: string): Promise<void>;
137
- cliContributors?: CliContributorRegistryLike;
138
- }
139
- /**
140
- * Minimal facade of the agent's ProfileContext. Plugins have no hard
141
- * dependency on the agent package; they accept whichever shape the agent
142
- * passes that is structurally compatible with this interface.
143
- */
144
- export interface ProfileContext {
145
- name: string;
146
- dataDir: string;
147
- logger: {
148
- info: (...args: unknown[]) => void;
149
- warn: (...args: unknown[]) => void;
150
- error: (...args: unknown[]) => void;
151
- debug: (...args: unknown[]) => void;
152
- };
153
- audit?: {
154
- emit: (event: string, payload?: unknown) => void;
155
- };
156
- }
157
- export type VibePluginFactory = (ctx: ProfileContext) => VibePlugin;
158
- export type PluginTag = "backend" | "frontend" | "cli" | "provider" | "adapter" | "integration";
159
- export interface PluginCapabilities {
160
- storage?: "none" | "read" | "rw";
161
- secrets?: "none" | "read" | "rw";
162
- gateway?: boolean;
163
- broadcast?: boolean;
164
- subprocess?: boolean;
165
- audit?: boolean;
166
- telemetry?: boolean;
167
- }
168
- export interface VibePlugin {
169
- capabilities?: PluginCapabilities;
170
- name: string;
171
- version: string;
172
- description?: string;
173
- tags?: PluginTag[];
174
- cliCommand?: string;
175
- apiPrefix?: string;
176
- dependencies?: string[];
177
- createRoutes?: (deps: {
178
- serviceRegistry: ServiceRegistryLike;
179
- }) => any;
180
- onCliSetup?: (program: any, hostServices: HostServices) => void | Promise<void>;
181
- onServerStart?: (app: any, hostServices: HostServices) => void | Promise<void>;
182
- onServerStop?: (ctx?: {
183
- reason: "reload" | "shutdown";
184
- }) => void | Promise<void>;
119
+ export interface TunnelDoctorCheck {
120
+ source: string;
121
+ run: () => Promise<Array<{
122
+ name: string;
123
+ ok: boolean;
124
+ grade?: "warn";
125
+ message: string;
126
+ hint?: string;
127
+ }>>;
185
128
  }
186
129
  export declare const DEFAULT_PROVIDER_CONFIG_KEY = "provider:default:tunnel";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibecontrols/vibe-plugin-tunnel",
3
- "version": "2026.508.3",
3
+ "version": "2026.509.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -27,6 +27,7 @@
27
27
  "sanity": "bun run format:check && bun run lint && bun run type:check && bun run build"
28
28
  },
29
29
  "dependencies": {
30
+ "@vibecontrols/plugin-sdk": "2026.509.1",
30
31
  "elysia": "^1.4.2"
31
32
  },
32
33
  "devDependencies": {