@amr-m-abdelgawad/devctl 0.2.5 → 0.3.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.
Files changed (2) hide show
  1. package/dist/devctl.js +1630 -87
  2. package/package.json +1 -1
package/dist/devctl.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
- var EC=Object.defineProperty;var TC=(e)=>e;function AC(e,t){this[e]=TC.bind(null,t)}var LC=(e,t)=>{for(var r in t)EC(e,r,{get:t[r],enumerable:!0,configurable:!0,set:AC.bind(t,r)})};var D=(e,t)=>()=>(e&&(t=e(e=0)),t);var DC=import.meta.require;function um(e,t=""){if(t==="MetadataLookupWarning")return!0;return e.includes("MetadataLookupWarning")||e.startsWith("received unexpected error =")}function Gs(){if(dm)return;dm=!0;let e=process.emitWarning.bind(process);process.emitWarning=(t,...r)=>{let n=OC(r[0]),o=MC(t);if(um(o,n))return;e(t,...r)}}function pm(){let e=process.stderr.write.bind(process.stderr);return process.stderr.write=(t,r,n)=>{let o=IC(t);if(um(o)){let i=typeof r==="function"?r:typeof n==="function"?n:void 0;if(typeof i==="function")i();return!0}if(typeof r==="function")return e(t,r);return e(t,r,n)},()=>{process.stderr.write=e}}function MC(e){if(typeof e==="string")return e;if(e instanceof Error)return e.message;return String(e)}function OC(e){if(typeof e==="string")return e;if(e&&typeof e==="object"&&"type"in e&&typeof e.type==="string")return e.type;return""}function IC(e){if(typeof e==="string")return e;if(e instanceof Uint8Array)return new TextDecoder().decode(e);return""}var dm=!1;import{createHash as $C}from"crypto";import{resolve as FC}from"path";function ar(e){let t=FC(e),r=process.platform==="win32"?t.toLowerCase():t;return $C("sha256").update(r).digest("hex").slice(0,NC)}var NC=16;var Us=()=>{};function Gl(e){let t=e.indexOf("Z-"),r=t>=0?e.slice(0,t+1):e,n=r.indexOf("T");if(n<0||!r.endsWith("Z"))return;let o=r.slice(0,n),i=r.slice(n+1,-1).replace(/-/g,":"),s=new Date(`${o}T${i}Z`);return Number.isNaN(s.getTime())?void 0:s}function ed(e=process.platform){return e==="darwin"}function rR(e=process.platform){return ed(e)?"cmd":"ctrl"}function _o(e,t=process.platform){return`${rR(t)}+${e}`}function nR(e=process.platform){return ed(e)?"command":"ctrl"}function Mt(e,t=process.platform){return`${nR(t)}+${e}`}function Ci(e){return e.replace(/\bcmd\b/gi,"command")}function Or(e,t=process.platform){let r=ed(t),n=Boolean(e.meta||e.super),o=Boolean(e.ctrl);return r?n&&!o:o&&!n}function Xr(e=process.platform){return _o("c",e)}function oR(e=process.platform){return{leader:_o("x",e),command_list:_o("p",e),command:"/",help:"?",search:"f",quit:"q",services:"s",logs:"l",auth:"a",proxy:"p",doctor:"d",config:"c",setup:"u",refresh:"r",restart:"R",find:"f",fullscreen:"z",confirm:"enter",cancel:"esc",select:"space",interrupt:"escape",copy:Xr(e)}}function bm(){return{theme:"devctl",leader_timeout:2000,font_size:14,keybinds:{...iR},scroll_speed:3,scroll_acceleration:{enabled:!1},diff_style:"auto",cursor:{style:"block",blinking:!0},mouse:!0,attention:{enabled:!1,notifications:!0,sound:!0,volume:0.4,sound_pack:"default",sounds:{}},log_timestamps:!0,log_metadata:!0,mcp_enabled:!1}}function sR(e){if(e===""||e==="none")return[];return e.split(",").flatMap((t)=>{let r=t.trim();if(r===""||r==="none"||/\s/.test(r))return[];let n=r.toLowerCase().split("+"),o=n[n.length-1]??"";return[{name:o==="return"?"return":o,ctrl:n.includes("ctrl")||n.includes("control"),shift:n.includes("shift"),meta:n.includes("meta")||n.includes("cmd")||n.includes("super"),alt:n.includes("alt")||n.includes("option")}]})}function pn(e,t){return sR(t).some((n)=>(e.name??"").toLowerCase()===n.name&&Boolean(e.ctrl)===n.ctrl&&Boolean(e.shift)===n.shift&&Boolean(e.meta||e.super)===n.meta&&Boolean(e.option??e.alt)===n.alt)}var Zl=2000,Eo=14,xm="DEVCTL_TUI_CONFIG",iR;var Js=D(()=>{iR=oR()});function Se(e,t,r){return{type:e,timestamp:new Date().toISOString(),service:t,payload:r}}class od{subs=new Map;nextID=0;buffer=[];maxBuf;constructor(e){this.maxBuf=e>0?e:1024}subscribe(e,t=[]){this.nextID+=1;let r=this.nextID;return this.subs.set(r,{types:new Set(t),handler:e}),()=>{this.subs.delete(r)}}publish(e){let t=e.timestamp===""?{...e,timestamp:new Date().toISOString()}:e;if(this.buffer.length>=this.maxBuf)this.buffer=this.buffer.slice(1);this.buffer.push(t);for(let r of this.subs.values())if(r.types.size===0||r.types.has(t.type))r.handler(t)}recent(){return[...this.buffer]}}var km="ServiceStarted",ta="ServiceStopped",ra="ServiceFailed",Cm="ServiceStateChanged",Rm="ServiceHealthChanged",Ao="LogReceived",Lo="TokenRefreshed",jn="TokenRefreshFailed",rd="AuthenticationChanged",Pm="ProxyStarted",_m="ProxyStopped",nd="ProxyRequest",na="ConfigurationChanged",Ri="ConfigurationReloadFailed",Em="SessionRecovered";var Tm=5000,Am=500,id=16384,Lm=65536,oa="TRACE",ia="DEBUG",sa="INFO",Pi="WARN",Gn="ERROR",Do="FATAL",aa="UNKNOWN",sd;var _i=D(()=>{sd={["TRACE"]:0,["DEBUG"]:1,["INFO"]:2,["WARN"]:3,["ERROR"]:4,["FATAL"]:5,["UNKNOWN"]:2}});function Dm(){return{name:"default",parse:(e)=>{let t=dd(e);if(t)return t;return{level:ud(e),request_id:pd(e)||void 0}}}}function Ei(e,t){for(let r of t){let n=e[r];if(typeof n==="string"&&n.trim()!=="")return n}return}function Om(e){for(let t of mR){let r=e[t];if(typeof r==="number"){let n=kR[r];if(n)return n;continue}if(typeof r==="string"&&r.trim()!=="")return r.toUpperCase()}return}function Ti(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function Zt(e){if(typeof e==="string"&&e.trim()!=="")return e;if(typeof e==="number"&&Number.isFinite(e))return String(e);return}function Mo(e){if(typeof e==="number"&&Number.isFinite(e))return e;if(typeof e==="string"&&e.trim()!==""){let t=Number(e);if(Number.isFinite(t))return t}return}function Ir(e,t){for(let r of t)if(e[r]!==void 0&&e[r]!==null)return e[r];return}function cd(e){return e.filter((t)=>t!==void 0&&t!=="").join(" ")}function CR(e){if(e>=hR)return Gn;if(e>=gR)return Pi;return}function RR(e){if(Ti(e))return e;return{}}function Im(e){let t=Zt(Ir(e,["http.method","method","request_method"])),r=Zt(Ir(e,["http.route","url.path","path","route","request_uri"])),n=Zt(Ir(e,["http.status_code","status","status_code"])),o=cd([t,r,n]);return o===""?void 0:o}function $m(e){if(!Ti(e))return Zt(e);if(typeof e.stringValue==="string")return e.stringValue.trim()===""?void 0:e.stringValue;return Zt(e.intValue??e.doubleValue??e.boolValue)}function PR(e){if(typeof e==="string"&&e.trim()!=="")return e;return $m(e)}function Fm(e){if(!Array.isArray(e)||e.length===0)return!1;return e.every((t)=>Ti(t)&&typeof t.key==="string"&&t.value!==void 0)}function _R(e){let t={};if(!Fm(e))return t;for(let r of e){let n=r.key;if(typeof n==="string"&&n!==""){let o=$m(r.value);if(o!==void 0)t[n]=o}}return t}function ER(e){let t=Om(e);if(t)return t;let r=Mo(e.severityNumber);if(r===void 0)return;if(r>=SR)return Do;if(r>=wR)return Gn;if(r>=bR)return Pi;if(r>=xR)return sa;if(r>=vR)return ia;if(r>=yR)return oa;return}function TR(e){if(e.timeUnixNano!==void 0||e.observedTimeUnixNano!==void 0)return!0;if(Ti(e.body)&&(e.body.stringValue!==void 0||e.body.intValue!==void 0||e.body.doubleValue!==void 0))return!0;return Fm(e.attributes)}function AR(e,t){if(!TR(e))return;let r=_R(e.attributes),n=PR(e.body)??Im(r)??"log",o=Ei(e,ad)??Zt(r.trace_id??r.traceId);return{message:n,level:ER(e),request_id:o,raw:t}}function LR(e,t){let r=Zt(e.metric_name);if(r===void 0)return;let n=Zt(e.metric_type),o=Mo(e.value);if(n===void 0&&o===void 0)return;let i=Zt(e.unit),s=o===void 0?void 0:i===void 0?String(o):`${o} ${i}`;return{message:cd([r,n,s,Im(RR(e.attributes))]),raw:t}}function DR(e){let t=e.trim().split(/\s+/);return{method:t[0],uri:t[1]}}function MR(e,t){if(Ei(e,Mm)!==void 0)return;let r=Zt(e.request),n=r===void 0?{}:DR(r),o=Zt(Ir(e,["request_method","method","http_method","verb"]))??n.method,i=Zt(Ir(e,["request_uri","uri","url","path","request_path"]))??n.uri,s=Mo(Ir(e,["status","status_code","statusCode","http_status"])),a=Zt(Ir(e,["remote_ip","remote_addr","client_ip"]));if(o===void 0||i===void 0)return;if(s===void 0&&a===void 0&&r===void 0)return;let c=Mo(Ir(e,["body_bytes_sent","bytes_sent","bytes"])),l=Mo(Ir(e,["request_time","duration"])),d=Mo(Ir(e,["request_time_ms","latency_ms","duration_ms"])),u=c===void 0?void 0:`${c}B`,f=d!==void 0?`${d}ms`:l===void 0?void 0:`${l}s`;return{message:cd([a,o,i,s===void 0?void 0:String(s),u,f]),level:s===void 0?void 0:CR(s),request_id:Ei(e,ad),raw:t}}function OR(e,t){let r=Ei(e,Mm),n=Om(e),o=Ei(e,ad);if(r===void 0&&n===void 0&&o===void 0)return;return{message:r??t,level:n,request_id:o,raw:t}}function ld(e){if(e.length<=id)return e;return e.slice(0,id)}function dd(e){let t=e.trim();if(!t.startsWith("{")||!t.endsWith("}"))return;if(t.length>Lm)return;let r;try{r=JSON.parse(t)}catch{return}if(!Ti(r))return;return AR(r,t)??LR(r,t)??MR(r,t)??OR(r,t)}function ud(e){return $R.find((r)=>r.re.test(e))?.level??aa}function pd(e){return IR.exec(e)?.[1]??""}var Mm,mR,ad,gR=400,hR=500,yR=1,vR=5,xR=9,bR=13,wR=17,SR=21,kR,IR,$R;var Nm=D(()=>{_i();Mm=["message","msg","text","log","event"],mR=["level","severity","severityText","levelname","loglevel","lvl"],ad=["request_id","requestId","trace_id","traceId","correlation_id","correlationId"],kR={10:oa,20:ia,30:sa,40:Pi,50:Gn,60:Do};IR=/(?:x-devctl-request-id|request[_-]?id)[=: ]+([A-Za-z0-9-]+)/i,$R=[{re:/\b(fatal|critical)\b/i,level:Do},{re:/\b(error|err)\b/i,level:Gn},{re:/\b(warn|warning)\b/i,level:Pi},{re:/\b(debug|dbg)\b/i,level:ia},{re:/\b(trace)\b/i,level:oa},{re:/\b(info|information)\b/i,level:sa}]});function Ai(e){let t=FR(e);if(t===void 0)return;return new RegExp(t)}function FR(e){if(e.length===0||e.length>200)return;let t={raw:e,i:0,out:[],quantifiers:0,depth:0,lastAtom:"none",groupQuant:[]};if(!Hm(t)||t.i!==e.length||t.depth!==0)return;return t.out.join("")}function Hm(e){while(e.i<e.raw.length){if(e.raw[e.i]===")")return!0;if(!NR(e))return!1}return!0}function NR(e){let t=e.raw[e.i]??"";if(t==="|")return ca(e,"|","none");if(t==="^")return ca(e,"^","none");if(t==="$")return ca(e,"$","none");if(t===".")return ca(e,".","atom");if(t==="(")return HR(e);if(t==="[")return WR(e);if(t==="*"||t==="+"||t==="?"||t==="{")return BR(e);if(t==="\\")return Bm(e);return e.out.push(RegExp.escape(t)),e.i+=1,e.lastAtom="atom",!0}function ca(e,t,r){return e.out.push(t),e.i+=1,e.lastAtom=r,!0}function HR(e){if(e.i+=1,e.depth+=1,e.depth>2)return!1;if(e.groupQuant.push(!1),e.out.push("(?:"),!Hm(e)||e.raw[e.i]!==")")return!1;e.out.push(")"),e.i+=1,e.depth-=1;let t=e.groupQuant.pop()===!0;return e.lastAtom=t?"quantified-group":"atom",!0}function BR(e){let t=e.raw[e.i]??"";if(t==="{"||e.lastAtom==="none"||e.lastAtom==="quant"||e.lastAtom==="quantified-group")return!1;if(e.quantifiers>=8)return!1;if(t==="*")e.out.push("*");else if(t==="+")e.out.push("+");else if(t==="?")e.out.push("?");else return!1;e.i+=1,e.quantifiers+=1;let r=e.groupQuant.length-1;if(r>=0)e.groupQuant[r]=!0;if(e.raw[e.i]==="?")e.out.push("?"),e.i+=1;return e.lastAtom="quant",!0}function WR(e){if(e.i+=1,e.out.push("["),e.raw[e.i]==="^")e.out.push("^"),e.i+=1;if(e.raw[e.i]==="]")e.out.push(RegExp.escape("]")),e.i+=1;while(e.i<e.raw.length&&e.raw[e.i]!=="]"){let t=e.raw[e.i]??"";if(t==="\\"){if(!Bm(e))return!1}else if(t==="-")e.out.push("-"),e.i+=1;else e.out.push(RegExp.escape(t)),e.i+=1}if(e.raw[e.i]!=="]")return!1;return e.out.push("]"),e.i+=1,e.lastAtom="atom",!0}function Bm(e){let t=e.raw[e.i+1];if(t===void 0)return!1;if(t==="d")e.out.push("\\d");else if(t==="D")e.out.push("\\D");else if(t==="w")e.out.push("\\w");else if(t==="W")e.out.push("\\W");else if(t==="s")e.out.push("\\s");else if(t==="S")e.out.push("\\S");else if(t==="n")e.out.push("\\n");else if(t==="t")e.out.push("\\t");else if(t==="r")e.out.push("\\r");else if(t==="b")e.out.push("\\b");else if(t==="B")e.out.push("\\B");else e.out.push(RegExp.escape(t));return e.i+=2,e.lastAtom="atom",!0}function Oo(e,t){if(e.services&&e.services.length>0&&!e.services.includes(t.service))return!1;if(e.level&&(sd[t.level]??2)<(sd[e.level]??2))return!1;if(e.source&&t.source!==e.source)return!1;if(e.since&&t.timestamp<e.since)return!1;if(e.until&&t.timestamp>e.until)return!1;return!0}function fd(e){if(!e.search)return()=>!0;if(e.regex){let r=Ai(e.search);if(r)return(n)=>r.test(n.message)||n.raw!==void 0&&r.test(n.raw)}let t=e.search.toLowerCase();return(r)=>r.message.toLowerCase().includes(t)||r.raw!==void 0&&r.raw.toLowerCase().includes(t)}function md(e){let t=fd(e);return(r)=>Oo(e,r)&&t(r)}var Wm=D(()=>{_i()});function jm(e){if(!Number.isInteger(e)||(e??0)<=0)return Am;return Math.min(e,Tm)}var Gm=D(()=>{_i()});var la=D(()=>{_i();Nm();Wm();Gm()});function M(e,t){return new Qe(e,t)}function Re(e,t,r){let n=r instanceof Error?r:Error(String(r));return new Qe(e,t,{cause:n})}function Ne(e,t,r,n=""){return new Qe(e,t,{hint:r,service:n})}function ua(e,t){return e instanceof Qe&&e.kind===t}function eg(e){if(e instanceof Qe)return e.exitCode();return 1}function tg(e){if(e instanceof Qe)return{error:A(e),kind:e.kind,hint:e.hint,service:e.service};return{error:A(e)}}function rg(e){if(typeof e==="string")return M("general",e);if(e.kind)return new Qe(e.kind,e.error.replace(`${e.kind}: `,""),{hint:e.hint,service:e.service});return M("general",e.error)}function A(e){if(e instanceof Qe){if(e.hint!=="")return`${Xm(e)} \u2014 ${e.hint}`;return Xm(e)}if(e instanceof Error)return e.message;return String(e)}function Xm(e){let t=`${e.kind}: `;if(e.message.startsWith(t)){let r=e.message.slice(t.length),n=e.causeError?`: ${e.causeError.message}`:"";if(n!==""&&r.endsWith(n))return r.slice(0,r.length-n.length);return r}return e.message}var Qm=1,ee="configuration",Kn="configuration_missing",Io="service_not_found",Zm="dependency",He="process_start",$o="authentication",qn="authorization",Qr="token",da="impersonation",Sd="iap",fn="proxy",kd="health_check",ct="general",Qe;var te=D(()=>{Qe=class Qe extends Error{kind;hint;service;causeError;constructor(e,t,r){super(r?.cause?`${e}: ${t}: ${r.cause.message}`:`${e}: ${t}`);this.name="DevctlError",this.kind=e,this.hint=r?.hint??"",this.service=r?.service??"",this.causeError=r?.cause}exitCode(){switch(this.kind){case"configuration":case"configuration_missing":case"service_not_found":case"dependency":return 2;case"authentication":case"token":case"iap":return 3;case"authorization":case"impersonation":return 4;case"process_start":return 5;case"health_check":return 6;case"proxy":return 7;default:return 1}}}});function ya(e){return e>0?e:300}function Pd(){return{enabled:!1,paths:[],debounce_ms:300,ignore:[...Di]}}function No(){return{args:[],shell:!1}}function _d(){return{vars:{},required:[],defaults:{}}}function Ed(){return{type:"",url:"",address:"",command:No(),interval_seconds:0,timeout_seconds:0,start_period_seconds:0,unhealthy_threshold:3,healthy_reset_threshold:10}}function Jt(e){return typeof e==="string"?e:e.service}function mn(e){return typeof e==="string"?"service_started":e.condition||"service_started"}function xa(e){return mn(e)==="service_healthy"?`${Jt(e)} (healthy)`:Jt(e)}function Td(){return{type:"",mode:"",service_account:"",config:{}}}function gn(){return{extends:"",description:"",command:No(),shell:!1,working_dir:"",dependencies:[],ports:[],environment:_d(),health:Ed(),identity:Td(),logs:{stdout:!1,stderr:!1},restart:{policy:"",max_retries:0,backoff_seconds:0},startup:{wait_for_healthy:!1,timeout_seconds:0},capabilities:[],proxy:[],container:void 0,watch:Pd(),hooks:{pre_start:No(),post_start:No()}}}function Jn(){return{version:1,project:{name:""},google:{project_id:"",region:""},profiles:{},templates:{},services:{},tasks:{},proxy:{enabled:!1,listen:{host:va,port:0},token_endpoint:{enabled:!1,host:"",port:0},routes:[]},logs:{max_memory_events:pP,persistence:{enabled:!0,directory:"~/.devctl/logs",retention_days:fP,max_session_logs:0}},auth:{refresh_threshold_seconds:sg},shutdown:{grace_seconds:ig},ui:{theme:"system",keymap:{}},secrets:{extra_markers:[],extra_patterns:[]},doctor:{tools:[]},plugins:[],environment:{sources:[],secrets:{}},provenance:{},repoRoot:"",configPath:""}}function hn(e){return e.args.length===0||e.args.length===1&&(e.args[0]??"").trim()===""}function Oi(e){if(e.type!=="")return e.type;return e.mode}function Zr(e){let t=Oi(e).toLowerCase();return t==="service"||t==="service_account"}function ag(e){let t=Oi(e).toLowerCase();return t==="user"||t===""}function ba(e){if(e.policy!==""){if(e.policy==="always"||e.policy==="on_failure"||e.policy==="never")return e.policy;return e.policy}if(e.enabled===!0)return"on_failure";return"never"}function yn(e){let t=e.host===""?va:e.host;if(e.port===0)return t;return`${t}:${e.port}`}function cg(e){if(e.refresh_threshold_seconds<=0)return sg;return e.refresh_threshold_seconds}function wa(e){if(e.stop_services_on_exit===void 0)return!0;return e.stop_services_on_exit}function vn(e){if(e.grace_seconds<=0)return ig;return e.grace_seconds}function lg(e){if(!e.logs.stdout&&!e.logs.stderr)return!0;return e.logs.stdout}function dg(e){if(!e.logs.stdout&&!e.logs.stderr)return!0;return e.logs.stderr}function Ad(e,t){return e.find((r)=>r.name===t)}function ug(e){return e[0]}var Ho=1,ma="never",ga="on_failure",ha="always",Di,Mi=8080,ig=10,sg=300,pP=50000,fP=14,va="127.0.0.1";var mt=D(()=>{Di=["**/node_modules/**","**/.git/**"]});var Nt=".devctl",lr="config.yaml";import{basename as mP}from"path";function xn(e,t=""){return{name:mP(e),project:t,profile:"",sa:"",audience:"",proxyPort:Mi}}function Sa(e,t=Mi){let r=Number(e);return Number.isFinite(r)&&r>0?r:t}function pg(e){let t=e;return`# yaml-language-server: $schema=https://raw.githubusercontent.com/amr-m-abdelgawad/devctl/main/schema/devctl.config.schema.json
3
+ var VC=Object.defineProperty;var zC=(e)=>e;function YC(e,t){this[e]=zC.bind(null,t)}var JC=(e,t)=>{for(var r in t)VC(e,r,{get:t[r],enumerable:!0,configurable:!0,set:YC.bind(t,r)})};var D=(e,t)=>()=>(e&&(t=e(e=0)),t);var XC=import.meta.require;function xm(e,t=""){if(t==="MetadataLookupWarning")return!0;return e.includes("MetadataLookupWarning")||e.startsWith("received unexpected error =")}function zs(){if(ym)return;ym=!0;let e=process.emitWarning.bind(process);process.emitWarning=(t,...r)=>{let n=ZC(r[0]),o=QC(t);if(xm(o,n))return;e(t,...r)}}function bm(){let e=process.stderr.write.bind(process.stderr);return process.stderr.write=(t,r,n)=>{let o=eP(t);if(xm(o)){let i=typeof r==="function"?r:typeof n==="function"?n:void 0;if(typeof i==="function")i();return!0}if(typeof r==="function")return e(t,r);return e(t,r,n)},()=>{process.stderr.write=e}}function QC(e){if(typeof e==="string")return e;if(e instanceof Error)return e.message;return String(e)}function ZC(e){if(typeof e==="string")return e;if(e&&typeof e==="object"&&"type"in e&&typeof e.type==="string")return e.type;return""}function eP(e){if(typeof e==="string")return e;if(e instanceof Uint8Array)return new TextDecoder().decode(e);return""}var ym=!1;import{createHash as tP}from"crypto";import{resolve as rP}from"path";function ar(e){let t=rP(e),r=process.platform==="win32"?t.toLowerCase():t;return tP("sha256").update(r).digest("hex").slice(0,nP)}var nP=16;var Ys=()=>{};function Yl(e){let t=e.indexOf("Z-"),r=t>=0?e.slice(0,t+1):e,n=r.indexOf("T");if(n<0||!r.endsWith("Z"))return;let o=r.slice(0,n),i=r.slice(n+1,-1).replace(/-/g,":"),s=new Date(`${o}T${i}Z`);return Number.isNaN(s.getTime())?void 0:s}function sd(e=process.platform){return e==="darwin"}function wP(e=process.platform){return sd(e)?"cmd":"ctrl"}function Lo(e,t=process.platform){return`${wP(t)}+${e}`}function SP(e=process.platform){return sd(e)?"command":"ctrl"}function It(e,t=process.platform){return`${SP(t)}+${e}`}function Ri(e){return e.replace(/\bcmd\b/gi,"command")}function Or(e,t=process.platform){let r=sd(t),n=Boolean(e.meta||e.super),o=Boolean(e.ctrl);return r?n&&!o:o&&!n}function Zr(e=process.platform){return Lo("c",e)}function kP(e=process.platform){return{leader:Lo("x",e),command_list:Lo("p",e),command:"/",help:"?",search:"f",quit:"q",services:"s",logs:"l",auth:"a",proxy:"p",doctor:"d",config:"c",setup:"u",refresh:"r",restart:"R",find:"f",fullscreen:"z",confirm:"enter",cancel:"esc",select:"space",interrupt:"escape",copy:Zr(e)}}function Em(){return{theme:"devctl",leader_timeout:2000,font_size:14,keybinds:{...CP},scroll_speed:3,scroll_acceleration:{enabled:!1},diff_style:"auto",cursor:{style:"block",blinking:!0},mouse:!0,attention:{enabled:!1,notifications:!0,sound:!0,volume:0.4,sound_pack:"default",sounds:{}},log_timestamps:!0,log_metadata:!0,mcp_enabled:!1}}function PP(e){if(e===""||e==="none")return[];return e.split(",").flatMap((t)=>{let r=t.trim();if(r===""||r==="none"||/\s/.test(r))return[];let n=r.toLowerCase().split("+"),o=n[n.length-1]??"";return[{name:o==="return"?"return":o,ctrl:n.includes("ctrl")||n.includes("control"),shift:n.includes("shift"),meta:n.includes("meta")||n.includes("cmd")||n.includes("super"),alt:n.includes("alt")||n.includes("option")}]})}function mn(e,t){return PP(t).some((n)=>(e.name??"").toLowerCase()===n.name&&Boolean(e.ctrl)===n.ctrl&&Boolean(e.shift)===n.shift&&Boolean(e.meta||e.super)===n.meta&&Boolean(e.option??e.alt)===n.alt)}var id=2000,Do=14,_m="DEVCTL_TUI_CONFIG",CP;var Zs=D(()=>{CP=kP()});function Se(e,t,r){return{type:e,timestamp:new Date().toISOString(),service:t,payload:r}}class dd{subs=new Map;nextID=0;buffer=[];maxBuf;constructor(e){this.maxBuf=e>0?e:1024}subscribe(e,t=[]){this.nextID+=1;let r=this.nextID;return this.subs.set(r,{types:new Set(t),handler:e}),()=>{this.subs.delete(r)}}publish(e){let t=e.timestamp===""?{...e,timestamp:new Date().toISOString()}:e;if(this.buffer.length>=this.maxBuf)this.buffer=this.buffer.slice(1);this.buffer.push(t);for(let r of this.subs.values())if(r.types.size===0||r.types.has(t.type))r.handler(t)}recent(){return[...this.buffer]}}var Lm="ServiceStarted",sa="ServiceStopped",aa="ServiceFailed",Dm="ServiceStateChanged",Im="ServiceHealthChanged",Mo="LogReceived",Oo="TokenRefreshed",Gn="TokenRefreshFailed",cd="AuthenticationChanged",Mm="ProxyStarted",Om="ProxyStopped",ld="ProxyRequest",ca="ConfigurationChanged",_i="ConfigurationReloadFailed",$m="SessionRecovered";var Fm=5000,Nm=500,ud=16384,Hm=65536,la="TRACE",da="DEBUG",ua="INFO",Ei="WARN",Kn="ERROR",$o="FATAL",pa="UNKNOWN",pd;var Ti=D(()=>{pd={["TRACE"]:0,["DEBUG"]:1,["INFO"]:2,["WARN"]:3,["ERROR"]:4,["FATAL"]:5,["UNKNOWN"]:2}});function jm(){return{name:"default",parse:(e)=>{let t=gd(e);if(t)return t;return{level:vd(e),request_id:yd(e)||void 0}}}}function Ai(e,t){for(let r of t){let n=e[r];if(typeof n==="string"&&n.trim()!=="")return n}return}function Um(e){for(let t of IP){let r=e[t];if(typeof r==="number"){let n=UP[r];if(n)return n;continue}if(typeof r==="string"&&r.trim()!=="")return r.toUpperCase()}return}function Li(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function Zt(e){if(typeof e==="string"&&e.trim()!=="")return e;if(typeof e==="number"&&Number.isFinite(e))return String(e);return}function Fo(e){if(typeof e==="number"&&Number.isFinite(e))return e;if(typeof e==="string"&&e.trim()!==""){let t=Number(e);if(Number.isFinite(t))return t}return}function $r(e,t){for(let r of t)if(e[r]!==void 0&&e[r]!==null)return e[r];return}function md(e){return e.filter((t)=>t!==void 0&&t!=="").join(" ")}function WP(e){if(e>=OP)return Kn;if(e>=MP)return Ei;return}function GP(e){if(Li(e))return e;return{}}function Wm(e){let t=Zt($r(e,["http.method","method","request_method"])),r=Zt($r(e,["http.route","url.path","path","route","request_uri"])),n=Zt($r(e,["http.status_code","status","status_code"])),o=md([t,r,n]);return o===""?void 0:o}function Gm(e){if(!Li(e))return Zt(e);if(typeof e.stringValue==="string")return e.stringValue.trim()===""?void 0:e.stringValue;return Zt(e.intValue??e.doubleValue??e.boolValue)}function KP(e){if(typeof e==="string"&&e.trim()!=="")return e;return Gm(e)}function Km(e){if(!Array.isArray(e)||e.length===0)return!1;return e.every((t)=>Li(t)&&typeof t.key==="string"&&t.value!==void 0)}function qP(e){let t={};if(!Km(e))return t;for(let r of e){let n=r.key;if(typeof n==="string"&&n!==""){let o=Gm(r.value);if(o!==void 0)t[n]=o}}return t}function VP(e){let t=Um(e);if(t)return t;let r=Fo(e.severityNumber);if(r===void 0)return;if(r>=BP)return $o;if(r>=jP)return Kn;if(r>=HP)return Ei;if(r>=NP)return ua;if(r>=FP)return da;if(r>=$P)return la;return}function zP(e){if(e.timeUnixNano!==void 0||e.observedTimeUnixNano!==void 0)return!0;if(Li(e.body)&&(e.body.stringValue!==void 0||e.body.intValue!==void 0||e.body.doubleValue!==void 0))return!0;return Km(e.attributes)}function YP(e,t){if(!zP(e))return;let r=qP(e.attributes),n=KP(e.body)??Wm(r)??"log",o=Ai(e,fd)??Zt(r.trace_id??r.traceId);return{message:n,level:VP(e),request_id:o,raw:t}}function JP(e,t){let r=Zt(e.metric_name);if(r===void 0)return;let n=Zt(e.metric_type),o=Fo(e.value);if(n===void 0&&o===void 0)return;let i=Zt(e.unit),s=o===void 0?void 0:i===void 0?String(o):`${o} ${i}`;return{message:md([r,n,s,Wm(GP(e.attributes))]),raw:t}}function XP(e){let t=e.trim().split(/\s+/);return{method:t[0],uri:t[1]}}function QP(e,t){if(Ai(e,Bm)!==void 0)return;let r=Zt(e.request),n=r===void 0?{}:XP(r),o=Zt($r(e,["request_method","method","http_method","verb"]))??n.method,i=Zt($r(e,["request_uri","uri","url","path","request_path"]))??n.uri,s=Fo($r(e,["status","status_code","statusCode","http_status"])),a=Zt($r(e,["remote_ip","remote_addr","client_ip"]));if(o===void 0||i===void 0)return;if(s===void 0&&a===void 0&&r===void 0)return;let c=Fo($r(e,["body_bytes_sent","bytes_sent","bytes"])),l=Fo($r(e,["request_time","duration"])),d=Fo($r(e,["request_time_ms","latency_ms","duration_ms"])),u=c===void 0?void 0:`${c}B`,f=d!==void 0?`${d}ms`:l===void 0?void 0:`${l}s`;return{message:md([a,o,i,s===void 0?void 0:String(s),u,f]),level:s===void 0?void 0:WP(s),request_id:Ai(e,fd),raw:t}}function ZP(e,t){let r=Ai(e,Bm),n=Um(e),o=Ai(e,fd);if(r===void 0&&n===void 0&&o===void 0)return;return{message:r??t,level:n,request_id:o,raw:t}}function hd(e){if(e.length<=ud)return e;return e.slice(0,ud)}function gd(e){let t=e.trim();if(!t.startsWith("{")||!t.endsWith("}"))return;if(t.length>Hm)return;let r;try{r=JSON.parse(t)}catch{return}if(!Li(r))return;return YP(r,t)??JP(r,t)??QP(r,t)??ZP(r,t)}function vd(e){return tR.find((r)=>r.re.test(e))?.level??pa}function yd(e){return eR.exec(e)?.[1]??""}var Bm,IP,fd,MP=400,OP=500,$P=1,FP=5,NP=9,HP=13,jP=17,BP=21,UP,eR,tR;var qm=D(()=>{Ti();Bm=["message","msg","text","log","event"],IP=["level","severity","severityText","levelname","loglevel","lvl"],fd=["request_id","requestId","trace_id","traceId","correlation_id","correlationId"],UP={10:la,20:da,30:ua,40:Ei,50:Kn,60:$o};eR=/(?:x-devctl-request-id|request[_-]?id)[=: ]+([A-Za-z0-9-]+)/i,tR=[{re:/\b(fatal|critical)\b/i,level:$o},{re:/\b(error|err)\b/i,level:Kn},{re:/\b(warn|warning)\b/i,level:Ei},{re:/\b(debug|dbg)\b/i,level:da},{re:/\b(trace)\b/i,level:la},{re:/\b(info|information)\b/i,level:ua}]});function Di(e){let t=rR(e);if(t===void 0)return;return new RegExp(t)}function rR(e){if(e.length===0||e.length>200)return;let t={raw:e,i:0,out:[],quantifiers:0,depth:0,lastAtom:"none",groupQuant:[]};if(!Vm(t)||t.i!==e.length||t.depth!==0)return;return t.out.join("")}function Vm(e){while(e.i<e.raw.length){if(e.raw[e.i]===")")return!0;if(!nR(e))return!1}return!0}function nR(e){let t=e.raw[e.i]??"";if(t==="|")return fa(e,"|","none");if(t==="^")return fa(e,"^","none");if(t==="$")return fa(e,"$","none");if(t===".")return fa(e,".","atom");if(t==="(")return oR(e);if(t==="[")return sR(e);if(t==="*"||t==="+"||t==="?"||t==="{")return iR(e);if(t==="\\")return zm(e);return e.out.push(RegExp.escape(t)),e.i+=1,e.lastAtom="atom",!0}function fa(e,t,r){return e.out.push(t),e.i+=1,e.lastAtom=r,!0}function oR(e){if(e.i+=1,e.depth+=1,e.depth>2)return!1;if(e.groupQuant.push(!1),e.out.push("(?:"),!Vm(e)||e.raw[e.i]!==")")return!1;e.out.push(")"),e.i+=1,e.depth-=1;let t=e.groupQuant.pop()===!0;return e.lastAtom=t?"quantified-group":"atom",!0}function iR(e){let t=e.raw[e.i]??"";if(t==="{"||e.lastAtom==="none"||e.lastAtom==="quant"||e.lastAtom==="quantified-group")return!1;if(e.quantifiers>=8)return!1;if(t==="*")e.out.push("*");else if(t==="+")e.out.push("+");else if(t==="?")e.out.push("?");else return!1;e.i+=1,e.quantifiers+=1;let r=e.groupQuant.length-1;if(r>=0)e.groupQuant[r]=!0;if(e.raw[e.i]==="?")e.out.push("?"),e.i+=1;return e.lastAtom="quant",!0}function sR(e){if(e.i+=1,e.out.push("["),e.raw[e.i]==="^")e.out.push("^"),e.i+=1;if(e.raw[e.i]==="]")e.out.push(RegExp.escape("]")),e.i+=1;while(e.i<e.raw.length&&e.raw[e.i]!=="]"){let t=e.raw[e.i]??"";if(t==="\\"){if(!zm(e))return!1}else if(t==="-")e.out.push("-"),e.i+=1;else e.out.push(RegExp.escape(t)),e.i+=1}if(e.raw[e.i]!=="]")return!1;return e.out.push("]"),e.i+=1,e.lastAtom="atom",!0}function zm(e){let t=e.raw[e.i+1];if(t===void 0)return!1;if(t==="d")e.out.push("\\d");else if(t==="D")e.out.push("\\D");else if(t==="w")e.out.push("\\w");else if(t==="W")e.out.push("\\W");else if(t==="s")e.out.push("\\s");else if(t==="S")e.out.push("\\S");else if(t==="n")e.out.push("\\n");else if(t==="t")e.out.push("\\t");else if(t==="r")e.out.push("\\r");else if(t==="b")e.out.push("\\b");else if(t==="B")e.out.push("\\B");else e.out.push(RegExp.escape(t));return e.i+=2,e.lastAtom="atom",!0}function No(e,t){if(e.services&&e.services.length>0&&!e.services.includes(t.service))return!1;if(e.level&&(pd[t.level]??2)<(pd[e.level]??2))return!1;if(e.source&&t.source!==e.source)return!1;if(e.since&&t.timestamp<e.since)return!1;if(e.until&&t.timestamp>e.until)return!1;return!0}function xd(e){if(!e.search)return()=>!0;if(e.regex){let r=Di(e.search);if(r)return(n)=>r.test(n.message)||n.raw!==void 0&&r.test(n.raw)}let t=e.search.toLowerCase();return(r)=>r.message.toLowerCase().includes(t)||r.raw!==void 0&&r.raw.toLowerCase().includes(t)}function bd(e){let t=xd(e);return(r)=>No(e,r)&&t(r)}var Ym=D(()=>{Ti()});function Jm(e){if(!Number.isInteger(e)||(e??0)<=0)return Nm;return Math.min(e,Fm)}var Xm=D(()=>{Ti()});var ma=D(()=>{Ti();qm();Ym();Xm()});function I(e,t){return new Ve(e,t)}function Pe(e,t,r){let n=r instanceof Error?r:Error(String(r));return new Ve(e,t,{cause:n})}function Te(e,t,r,n=""){return new Ve(e,t,{hint:r,service:n})}function va(e,t){return e instanceof Ve&&e.kind===t}function ch(e){if(e instanceof Ve)return e.exitCode();return 1}function lh(e){if(e instanceof Ve)return{error:A(e),kind:e.kind,hint:e.hint,service:e.service};return{error:A(e)}}function dh(e){if(typeof e==="string")return I("general",e);if(e.kind)return new Ve(e.kind,e.error.replace(`${e.kind}: `,""),{hint:e.hint,service:e.service});return I("general",e.error)}function A(e){if(e instanceof Ve){if(e.hint!=="")return`${ih(e)} \u2014 ${e.hint}`;return ih(e)}if(e instanceof Error)return e.message;return String(e)}function ih(e){let t=`${e.kind}: `;if(e.message.startsWith(t)){let r=e.message.slice(t.length),n=e.causeError?`: ${e.causeError.message}`:"";if(n!==""&&r.endsWith(n))return r.slice(0,r.length-n.length);return r}return e.message}var sh=1,Q="configuration",Vn="configuration_missing",Ho="service_not_found",ah="dependency",He="process_start",hn="authentication",zn="authorization",Fr="token",ha="impersonation",ga="iap",Nr="proxy",Ed="health_check",lt="general",Ve;var te=D(()=>{Ve=class Ve extends Error{kind;hint;service;causeError;constructor(e,t,r){super(r?.cause?`${e}: ${t}: ${r.cause.message}`:`${e}: ${t}`);this.name="DevctlError",this.kind=e,this.hint=r?.hint??"",this.service=r?.service??"",this.causeError=r?.cause}exitCode(){switch(this.kind){case"configuration":case"configuration_missing":case"service_not_found":case"dependency":return 2;case"authentication":case"token":case"iap":return 3;case"authorization":case"impersonation":return 4;case"process_start":return 5;case"health_check":return 6;case"proxy":return 7;default:return 1}}}});function ka(e){return e>0?e:300}function Ld(){return{enabled:!1,paths:[],debounce_ms:300,ignore:[...Mi]}}function Dd(){return{enabled:void 0,host:"",port:""}}function Oi(){return{type:"",identity:{type:"",service_account:""},audience:"",service_account:"",client_id:"",client_secret:""}}function Bo(){return{args:[],shell:!1}}function Id(){return{vars:{},required:[],defaults:{}}}function Md(){return{type:"",url:"",address:"",command:Bo(),interval_seconds:0,timeout_seconds:0,start_period_seconds:0,unhealthy_threshold:3,healthy_reset_threshold:10}}function zt(e){return typeof e==="string"?e:e.service}function gn(e){return typeof e==="string"?"service_started":e.condition||"service_started"}function Pa(e){return gn(e)==="service_healthy"?`${zt(e)} (healthy)`:zt(e)}function Od(){return{type:"",mode:"",service_account:"",config:{}}}function vn(){return{extends:"",description:"",command:Bo(),shell:!1,working_dir:"",dependencies:[],ports:[],environment:Id(),health:Md(),identity:Od(),logs:{stdout:!1,stderr:!1},restart:{policy:"",max_retries:0,backoff_seconds:0},startup:{wait_for_healthy:!1,timeout_seconds:0},capabilities:[],proxy:[],expose:Dd(),container:void 0,watch:Ld(),hooks:{pre_start:Bo(),post_start:Bo()}}}function Jn(){return{version:1,project:{name:""},google:{project_id:"",region:""},profiles:{},templates:{},services:{},tasks:{},proxy:{enabled:!1,gateway:!1,listen:{host:Ca,port:0},token_endpoint:{enabled:!1,host:"",port:0},routes:[]},logs:{max_memory_events:LR,persistence:{enabled:!0,directory:"~/.devctl/logs",retention_days:DR,max_session_logs:0}},auth:{refresh_threshold_seconds:mh},shutdown:{grace_seconds:fh},ui:{theme:"system",keymap:{}},secrets:{extra_markers:[],extra_patterns:[]},doctor:{tools:[]},plugins:[],environment:{sources:[],secrets:{}},provenance:{},repoRoot:"",configPath:""}}function yn(e){return e.args.length===0||e.args.length===1&&(e.args[0]??"").trim()===""}function Fi(e){if(e.type!=="")return e.type;return e.mode}function en(e){let t=Fi(e).toLowerCase();return t==="service"||t==="service_account"}function hh(e){let t=Fi(e).toLowerCase();return t==="user"||t===""}function Ra(e){if(e.policy!==""){if(e.policy==="always"||e.policy==="on_failure"||e.policy==="never")return e.policy;return e.policy}if(e.enabled===!0)return"on_failure";return"never"}function xn(e){let t=e.host===""?Ca:e.host;if(e.port===0)return t;return`${t}:${e.port}`}function gh(e){if(e.refresh_threshold_seconds<=0)return mh;return e.refresh_threshold_seconds}function _a(e){if(e.stop_services_on_exit===void 0)return!0;return e.stop_services_on_exit}function bn(e){if(e.grace_seconds<=0)return fh;return e.grace_seconds}function vh(e){if(!e.logs.stdout&&!e.logs.stderr)return!0;return e.logs.stdout}function yh(e){if(!e.logs.stdout&&!e.logs.stderr)return!0;return e.logs.stderr}function Xn(e,t){return e.find((r)=>r.name===t)}function xh(e){return e[0]}var Uo=1,ba="never",wa="on_failure",Sa="always",Mi,$i=8080,fh=10,mh=300,LR=50000,DR=14,Ca="127.0.0.1";var ht=D(()=>{Mi=["**/node_modules/**","**/.git/**"]});var Nt=".devctl",lr="config.yaml";import{basename as IR}from"path";function wn(e,t=""){return{name:IR(e),project:t,profile:"",sa:"",audience:"",proxyPort:$i}}function Ea(e,t=$i){let r=Number(e);return Number.isFinite(r)&&r>0?r:t}function bh(e){let t=e;return`# yaml-language-server: $schema=https://raw.githubusercontent.com/amr-m-abdelgawad/devctl/main/schema/devctl.config.schema.json
4
4
  version: 1
5
5
 
6
6
  project:
@@ -47,8 +47,8 @@ auth:
47
47
  shutdown:
48
48
  stop_services_on_exit: true
49
49
  grace_seconds: 10
50
- `}var wt;var ka=D(()=>{mt();wt=[{id:"repo",title:"Repository",prompt:"Repository root"},{id:"name",title:"Environment",prompt:"Environment / project name"},{id:"project",title:"Google project",prompt:"Google Cloud project"},{id:"auth",title:"Authentication",prompt:"Run gcloud ADC login now? (y/N)"},{id:"sa",title:"Service account",prompt:"Service account email to record (optional)"},{id:"audience",title:"IAP",prompt:"IAP audience to record on a sample route (optional)"},{id:"port",title:"Ports",prompt:"Proxy listen port"},{id:"profile",title:"Profiles",prompt:"Default profile name (optional)"},{id:"write",title:"Validation",prompt:"Write starter configuration"}]});import{existsSync as Md,mkdirSync as Ld,statSync as gP,writeFileSync as hP}from"fs";import{createInterface as yP}from"readline/promises";import{stdin as vP,stdout as xP}from"process";import{basename as Ca,dirname as Dd,join as en,resolve as bP}from"path";function Od(e,t){let r=e===""?process.cwd():e;if(t==="")return{repo:r,cfgPath:en(r,Nt,lr)};let n=bP(t);if(!(Md(n)?gP(n).isDirectory():!/\.ya?ml$/i.test(n))){let i=Dd(n);return{repo:Ca(i)===Nt?Dd(i):i,cfgPath:n}}if(Ca(n)===Nt)return{repo:Dd(n),cfgPath:en(n,lr)};return{repo:n,cfgPath:en(n,Nt,lr)}}function fg(e,t=Ca(e),r="",n="",o=!1){let i=en(e,Nt,lr);if(!o&&Md(i))throw M(ee,`configuration already exists at ${i}; not overwriting`);return Ra(e,{...xn(e,r),name:t,profile:n}),i}async function mg(e,t,r="",n=!1){let{repo:o,cfgPath:i}=Od(t,r);if(!n&&Md(i)){Ht(`Found existing configuration at ${i}; nothing written.`),Ht("Pass --force to overwrite it, or edit it directly.");return}let s=yP({input:vP,output:xP}),a=async(g,y)=>{let x=y!==""?` [${y}]`:"",h=(await s.question(`${g}${x}: `)).trim();return h===""?y:h},c=0,l=()=>(c+=1,c);if(Ht(`devctl setup \u2014 ${wt.length} steps`),Ht(""),r==="")await a(`${l()}. Repository root`,o);let d=xn(o,""),u=await a(`${l()}. ${wt[1]?.prompt}`,Ca(o));d.name=u;let f=await e.detectGoogle(""),p=await a(`${l()}. ${wt[2]?.prompt}`,f.projectID);if(d.project=p,Ht(`${l()}. ${wt[3]?.title}`),!f.adcAvailable){if(Ht(" ADC is not available. Run: gcloud auth application-default login"),(await a(" Run login now? (y/N)","n")).toLowerCase()==="y")try{await e.loginGoogle()}catch(y){Ht(` login failed: ${y instanceof Error?y.message:String(y)}`)}}else if(Ht(" \u2713 Application Default Credentials available"),f.userEmail!=="")Ht(` user: ${f.userEmail}`);d.sa=await a(`${l()}. ${wt[4]?.prompt}`,""),d.audience=await a(`${l()}. ${wt[5]?.prompt}`,"");let m=await a(`${l()}. ${wt[6]?.prompt}`,String(Mi));d.proxyPort=Sa(m),d.profile=await a(`${l()}. ${wt[7]?.prompt}`,""),s.close(),Ra(o,d),Ht(""),Ht(`Wrote starter configuration to ${i}`),Ht(`${l()}. Validation`);try{let g=e.load(o,""),y=await e.runDoctor.execute(g);Ht(e.formatDoctor(y))}catch(g){Ht(` configuration is not valid yet: ${g instanceof Error?g.message:String(g)}`),Ht(" edit .devctl/config.yaml and run `devctl config validate`")}}function Ra(e,t){let r=en(e,".devctl");Ld(en(r,"services"),{recursive:!0}),Ld(en(r,"profiles"),{recursive:!0}),Ld(en(r,"proxy"),{recursive:!0}),hP(en(r,"config.yaml"),pg(t))}function Ht(e){process.stdout.write(`${e}
51
- `)}var Pa=D(()=>{mt();ka();te()});function Cn(e){return Object.entries(e.provenance).sort(([t],[r])=>t.localeCompare(r)).flatMap(([t,r])=>{let n=r[r.length-1];if(!n)return[];return[{path:t,value:T_(e,t),...n,shadowed:r.slice(0,-1)}]})}function T_(e,t){let r=e;for(let n of t.split(".")){if(typeof r!=="object"||r===null)return;r=r[n]}return r}function Fa(e){if(Zr(e))return dr({kind:eo,serviceAccount:e.service_account});if(ag(e)&&(e.type!==""||e.mode!==""))return dr({kind:$a});let t=(e.type||e.mode).toLowerCase();if(t!=="")return dr({kind:t,providerConfig:e.config??{}});return dr({kind:Wo})}function to(e){if(e.type.toLowerCase()==="none")return dr({kind:Wo});let t=e.identity.type.toLowerCase(),r=e.identity.service_account||e.service_account;if(t==="service"||t==="service_account"||e.type==="service_account")return dr({kind:eo,serviceAccount:r});if(e.type.toLowerCase()==="iap"&&t==="")return dr({kind:Wo});if(e.type===""&&t==="")return dr({kind:Wo});return dr({kind:$a})}function mh(e){return e.kind===$a||e.kind===eo}function jo(e){if(e.tokenKey!=="")return e.tokenKey;if(e.kind===eo)return`sa:${e.serviceAccount}`;if(e.email!=="")return`user:${e.email}`;return"user"}function dr(e={}){return{kind:Wo,email:"",serviceAccount:"",project:"",projectSource:"",adcAvailable:!1,providerConfig:{},tokenKey:"",...e}}function Yd(e){let t=new Map,r=(n,o)=>{if(n!=="")t.set(n,(t.get(n)??!1)||o)};for(let n of Object.values(e.services))if(Zr(n.identity)&&n.identity.service_account!=="")r(n.identity.service_account,!0);for(let n of e.proxy.routes){let o=n.auth.type.toLowerCase(),i=n.auth.identity.type.toLowerCase(),s=n.auth.identity.service_account||n.auth.service_account;if(i==="service"||i==="service_account"||o==="service_account")r(s,o!=="none")}return[...t.entries()].map(([n,o])=>({email:n,active:o})).sort((n,o)=>n.email.localeCompare(o.email))}function $r(e){return Yd(e).filter((t)=>t.active).map((t)=>t.email)}function gh(e,t,r){let n=[];for(let o of t){let i=e.services[o];if(!i)continue;let s=Fa(i.identity);if(Zr(i.identity)&&i.identity.service_account===""){n.push({name:o,message:"service account identity is not configured"});continue}if((mh(s)||i.capabilities.some((c)=>c==="google_api"||c==="iap"||c==="service_identity"))&&!r)n.push({name:o,message:"ADC unavailable"})}return n}function Hi(e){if($r(e).length>0)return!0;if(e.proxy.routes.some((t)=>t.auth.type.toLowerCase()==="iap"))return!0;return Object.values(e.services).some((t)=>{if(mh(Fa(t.identity)))return!0;return t.capabilities.some((r)=>r==="google_api"||r==="iap"||r==="service_identity")})}function Xd(){return{name:"user",accepts:(e)=>!Zr(e),resolve:async(e,t)=>{if(e.type===""&&e.mode==="")return dr({kind:Wo});return{...await t(),kind:$a}}}}function Qd(){return{name:"service_account",accepts:(e)=>Zr(e),resolve:async(e,t)=>{if(e.service_account==="")throw M(da,"service account identity is not configured");return{...await t(),kind:eo,serviceAccount:e.service_account}}}}async function hh(e,t,r){if(r)for(let n=r.length-1;n>=0;n-=1){let o=r[n];if(o?.accepts(e))return o.resolve(e,t)}if(Zr(e))return Qd().resolve(e,t);return Xd().resolve(e,t)}var $a="user",eo="service_account",Wo="none";var Fr=D(()=>{mt();te()});function er(){return"devctl 0.2.5"}var Bt="0.2.5",ji=1;function Oh(e){return e}function ur(e){return e}function Jo(e){if(e.state===Be&&e.health===Wt)return pr;if(e.state===Be&&e.health===Me)return Nr;return e.state}function Ki(e){return{name:e,state:_t,health:Et,pid:0,ports:{},restarts:0,last_error:"",identity:"",startTime:void 0,cpuPercent:void 0,memoryKB:void 0,profile:"",env_source:"daemon",orphaned:!1}}function Fh(e,t){let r={},n={};for(let a of Object.keys(t))r[a]=0;for(let a of Object.keys(t)){let c=e.services[a]?.dependencies??[];for(let l of c){let d=Jt(l);if(!t[d])continue;n[d]=[...n[d]??[],a],r[a]=(r[a]??0)+1}}let o=[],i=Object.keys(t).length,s={};while(i>0){let a=Object.entries(r).filter(([c,l])=>l===0&&!s[c]).map(([c])=>c).sort();if(a.length===0)throw M(Zm,"dependency cycle detected while planning startup");o.push(a);for(let c of a){s[c]=!0,i-=1;for(let l of n[c]??[])r[l]=(r[l]??0)-1}}return o}function uu(e,t){if(!e.services[t])throw M(Io,`unknown service "${t}"`)}function za(e,t,r){let n={},o=(a)=>{if(n[a])return;uu(e,a),n[a]=!0;for(let c of e.services[a]?.dependencies??[])o(Jt(c))};for(let a of t)o(a);let i=Fh(e,n),s=[];return i.forEach((a,c)=>{for(let l of a)s.push({name:l,wave:c+1,dependencies:[...e.services[l]?.dependencies??[]]})}),{profile:r,steps:s,waves:i,blockers:[]}}function io(e,t){let r={};for(let[i,s]of Object.entries(e.services))for(let a of s.dependencies){let c=Jt(a);r[c]=[...r[c]??[],i]}let n={},o=(i)=>{if(n[i])return;uu(e,i),n[i]=!0;for(let s of r[i]??[])o(s)};for(let i of t)o(i);return Object.keys(n)}function Nh(e,t){let r=[...Fh(e,t)].reverse().map((o)=>[...o].sort()),n=[];return r.forEach((o,i)=>{for(let s of o)n.push({name:s,wave:i+1,dependencies:[...e.services[s]?.dependencies??[]]})}),{steps:n,waves:r,profile:"",blockers:[]}}function Ya(e,t){let r=Object.fromEntries(io(e,t).map((n)=>[n,!0]));return Nh(e,r)}function Xa(e,t){let r={};for(let n of t)uu(e,n),r[n]=!0;return Nh(e,r)}function Hh(e){let t="";if(e.profile!=="")t+=`Profile: ${e.profile}
50
+ `}var wt;var Ta=D(()=>{ht();wt=[{id:"repo",title:"Repository",prompt:"Repository root"},{id:"name",title:"Environment",prompt:"Environment / project name"},{id:"project",title:"Google project",prompt:"Google Cloud project"},{id:"auth",title:"Authentication",prompt:"Run gcloud ADC login now? (y/N)"},{id:"sa",title:"Service account",prompt:"Service account email to record (optional)"},{id:"audience",title:"IAP",prompt:"IAP audience to record on a sample route (optional)"},{id:"port",title:"Ports",prompt:"Proxy listen port"},{id:"profile",title:"Profiles",prompt:"Default profile name (optional)"},{id:"write",title:"Validation",prompt:"Write starter configuration"}]});import{existsSync as Nd,mkdirSync as $d,statSync as MR,writeFileSync as OR}from"fs";import{createInterface as $R}from"readline/promises";import{stdin as FR,stdout as NR}from"process";import{basename as Aa,dirname as Fd,join as tn,resolve as HR}from"path";function Hd(e,t){let r=e===""?process.cwd():e;if(t==="")return{repo:r,cfgPath:tn(r,Nt,lr)};let n=HR(t);if(!(Nd(n)?MR(n).isDirectory():!/\.ya?ml$/i.test(n))){let i=Fd(n);return{repo:Aa(i)===Nt?Fd(i):i,cfgPath:n}}if(Aa(n)===Nt)return{repo:Fd(n),cfgPath:tn(n,lr)};return{repo:n,cfgPath:tn(n,Nt,lr)}}function wh(e,t=Aa(e),r="",n="",o=!1){let i=tn(e,Nt,lr);if(!o&&Nd(i))throw I(Q,`configuration already exists at ${i}; not overwriting`);return La(e,{...wn(e,r),name:t,profile:n}),i}async function Sh(e,t,r="",n=!1){let{repo:o,cfgPath:i}=Hd(t,r);if(!n&&Nd(i)){Ht(`Found existing configuration at ${i}; nothing written.`),Ht("Pass --force to overwrite it, or edit it directly.");return}let s=$R({input:FR,output:NR}),a=async(h,v)=>{let x=v!==""?` [${v}]`:"",g=(await s.question(`${h}${x}: `)).trim();return g===""?v:g},c=0,l=()=>(c+=1,c);if(Ht(`devctl setup \u2014 ${wt.length} steps`),Ht(""),r==="")await a(`${l()}. Repository root`,o);let d=wn(o,""),u=await a(`${l()}. ${wt[1]?.prompt}`,Aa(o));d.name=u;let f=await e.detectGoogle(""),p=await a(`${l()}. ${wt[2]?.prompt}`,f.projectID);if(d.project=p,Ht(`${l()}. ${wt[3]?.title}`),!f.adcAvailable){if(Ht(" ADC is not available. Run: gcloud auth application-default login"),(await a(" Run login now? (y/N)","n")).toLowerCase()==="y")try{await e.loginGoogle()}catch(v){Ht(` login failed: ${v instanceof Error?v.message:String(v)}`)}}else if(Ht(" \u2713 Application Default Credentials available"),f.userEmail!=="")Ht(` user: ${f.userEmail}`);d.sa=await a(`${l()}. ${wt[4]?.prompt}`,""),d.audience=await a(`${l()}. ${wt[5]?.prompt}`,"");let m=await a(`${l()}. ${wt[6]?.prompt}`,String($i));d.proxyPort=Ea(m),d.profile=await a(`${l()}. ${wt[7]?.prompt}`,""),s.close(),La(o,d),Ht(""),Ht(`Wrote starter configuration to ${i}`),Ht(`${l()}. Validation`);try{let h=e.load(o,""),v=await e.runDoctor.execute(h);Ht(e.formatDoctor(v))}catch(h){Ht(` configuration is not valid yet: ${h instanceof Error?h.message:String(h)}`),Ht(" edit .devctl/config.yaml and run `devctl config validate`")}}function La(e,t){let r=tn(e,".devctl");$d(tn(r,"services"),{recursive:!0}),$d(tn(r,"profiles"),{recursive:!0}),$d(tn(r,"proxy"),{recursive:!0}),OR(tn(r,"config.yaml"),bh(t))}function Ht(e){process.stdout.write(`${e}
51
+ `)}var Da=D(()=>{ht();Ta();te()});function gg(){return/\$\{(?:env\.)?([A-Za-z_][A-Za-z0-9_]*)\}/g}function Zd(e){return[...e.matchAll(gg())].map((t)=>t[1]??"")}function vg(e,t){let r=[];return{value:e.replace(gg(),(o,i)=>{let s=t[i];if(s===void 0||s==="")return r.push(i),"";return s}),missing:r}}function Ha(e){let t=e.trim();if(t===""||Zd(t).length===0)return;return t}function yg(e){return/^\$\{(?:env\.)?[A-Za-z_][A-Za-z0-9_]*\}$/.test(e.trim())}function Pn(e){return Object.entries(e.provenance).sort(([t],[r])=>t.localeCompare(r)).flatMap(([t,r])=>{let n=r[r.length-1];if(!n)return[];return[{path:t,value:Q_(e,t),...n,shadowed:r.slice(0,-1)}]})}function Q_(e,t){let r=e;for(let n of t.split(".")){if(typeof r!=="object"||r===null)return;r=r[n]}return r}function Ga(e){if(en(e))return dr({kind:no,serviceAccount:e.service_account});if(hh(e)&&(e.type!==""||e.mode!==""))return dr({kind:Wa});let t=(e.type||e.mode).toLowerCase();if(t!=="")return dr({kind:t,providerConfig:e.config??{}});return dr({kind:Go})}function oo(e){if(e.type.toLowerCase()==="none")return dr({kind:Go});let t=e.identity.type.toLowerCase(),r=e.identity.service_account||e.service_account;if(t==="service"||t==="service_account"||e.type==="service_account")return dr({kind:no,serviceAccount:r});if(e.type.toLowerCase()==="iap"&&t==="")return dr({kind:Go});if(e.type===""&&t==="")return dr({kind:Go});return dr({kind:Wa})}function _g(e){return e.kind===Wa||e.kind===no}function Ko(e){if(e.tokenKey!=="")return e.tokenKey;if(e.kind===no)return`sa:${e.serviceAccount}`;if(e.email!=="")return`user:${e.email}`;return"user"}function dr(e={}){return{kind:Go,email:"",serviceAccount:"",project:"",projectSource:"",adcAvailable:!1,providerConfig:{},tokenKey:"",...e}}function nu(e){let t=new Map,r=(n,o)=>{if(n!=="")t.set(n,(t.get(n)??!1)||o)};for(let n of Object.values(e.services))if(en(n.identity)&&n.identity.service_account!=="")r(n.identity.service_account,!0);for(let n of e.proxy.routes){let o=n.auth.type.toLowerCase(),i=n.auth.identity.type.toLowerCase(),s=n.auth.identity.service_account||n.auth.service_account;if(i==="service"||i==="service_account"||o==="service_account")r(s,o!=="none")}return[...t.entries()].map(([n,o])=>({email:n,active:o})).sort((n,o)=>n.email.localeCompare(o.email))}function Hr(e){return nu(e).filter((t)=>t.active).map((t)=>t.email)}function Eg(e,t,r){let n=[];for(let o of t){let i=e.services[o];if(!i)continue;let s=Ga(i.identity);if(en(i.identity)&&i.identity.service_account===""){n.push({name:o,message:"service account identity is not configured"});continue}if((_g(s)||i.capabilities.some((c)=>c==="google_api"||c==="iap"||c==="service_identity"))&&!r)n.push({name:o,message:"ADC unavailable"})}return n}function Ui(e){if(Hr(e).length>0)return!0;if(e.proxy.routes.some((t)=>t.auth.type.toLowerCase()==="iap"))return!0;return Object.values(e.services).some((t)=>{if(_g(Ga(t.identity)))return!0;return t.capabilities.some((r)=>r==="google_api"||r==="iap"||r==="service_identity")})}function ou(){return{name:"user",accepts:(e)=>!en(e),resolve:async(e,t)=>{if(e.type===""&&e.mode==="")return dr({kind:Go});return{...await t(),kind:Wa}}}}function iu(){return{name:"service_account",accepts:(e)=>en(e),resolve:async(e,t)=>{if(e.service_account==="")throw I(ha,"service account identity is not configured");return{...await t(),kind:no,serviceAccount:e.service_account}}}}async function Tg(e,t,r){if(r)for(let n=r.length-1;n>=0;n-=1){let o=r[n];if(o?.accepts(e))return o.resolve(e,t)}if(en(e))return iu().resolve(e,t);return ou().resolve(e,t)}var Wa="user",no="service_account",Go="none";var jr=D(()=>{ht();te()});function er(){return"devctl 0.3.0"}var jt="0.3.0",Vi=1;function zg(e){return e}function ur(e){return e}function Jo(e){if(e.state===je&&e.health===Bt)return pr;if(e.state===je&&e.health===Oe)return Br;return e.state}function Ji(e){return{name:e,state:_t,health:Et,pid:0,ports:{},restarts:0,last_error:"",identity:"",startTime:void 0,cpuPercent:void 0,memoryKB:void 0,profile:"",env_source:"daemon",orphaned:!1}}function Xg(e,t){let r={},n={};for(let a of Object.keys(t))r[a]=0;for(let a of Object.keys(t)){let c=e.services[a]?.dependencies??[];for(let l of c){let d=zt(l);if(!t[d])continue;n[d]=[...n[d]??[],a],r[a]=(r[a]??0)+1}}let o=[],i=Object.keys(t).length,s={};while(i>0){let a=Object.entries(r).filter(([c,l])=>l===0&&!s[c]).map(([c])=>c).sort();if(a.length===0)throw I(ah,"dependency cycle detected while planning startup");o.push(a);for(let c of a){s[c]=!0,i-=1;for(let l of n[c]??[])r[l]=(r[l]??0)-1}}return o}function yu(e,t){if(!e.services[t])throw I(Ho,`unknown service "${t}"`)}function tc(e,t,r){let n={},o=(a)=>{if(n[a])return;yu(e,a),n[a]=!0;for(let c of e.services[a]?.dependencies??[])o(zt(c))};for(let a of t)o(a);let i=Xg(e,n),s=[];return i.forEach((a,c)=>{for(let l of a)s.push({name:l,wave:c+1,dependencies:[...e.services[l]?.dependencies??[]]})}),{profile:r,steps:s,waves:i,blockers:[]}}function lo(e,t){let r={};for(let[i,s]of Object.entries(e.services))for(let a of s.dependencies){let c=zt(a);r[c]=[...r[c]??[],i]}let n={},o=(i)=>{if(n[i])return;yu(e,i),n[i]=!0;for(let s of r[i]??[])o(s)};for(let i of t)o(i);return Object.keys(n)}function Qg(e,t){let r=[...Xg(e,t)].reverse().map((o)=>[...o].sort()),n=[];return r.forEach((o,i)=>{for(let s of o)n.push({name:s,wave:i+1,dependencies:[...e.services[s]?.dependencies??[]]})}),{steps:n,waves:r,profile:"",blockers:[]}}function rc(e,t){let r=Object.fromEntries(lo(e,t).map((n)=>[n,!0]));return Qg(e,r)}function nc(e,t){let r={};for(let n of t)yu(e,n),r[n]=!0;return Qg(e,r)}function Zg(e){let t="";if(e.profile!=="")t+=`Profile: ${e.profile}
52
52
 
53
53
  `;t+=`Plan:
54
54
 
@@ -56,8 +56,8 @@ shutdown:
56
56
  `,r+=1;if((e.blockers??[]).length>0){t+=`
57
57
  Blocked:
58
58
  `;for(let n of e.blockers??[])t+=`- ${n.name}: ${n.message}
59
- `}return t}function Qa(e){return`${e.join(", ")} changed and only take effect after a full \`devctl down && devctl start\`, not a reload`}function PE(e,t,r){let n={};if(t!==""){let o=e.profiles[t];if(!o)throw M("configuration",`unknown profile "${t}"`);Object.assign(n,o.environment)}if(r.length>0)return{services:Ih(e,r),env:n};if(t==="")return{services:Object.keys(e.services),env:n};return{services:Ih(e,e.profiles[t]?.services??[]),env:n}}function _E(e){let t=Object.keys(e.profiles).sort();return ur(t[0]??"")}function so(e,t){if(t==="")return{};return{...e.profiles[ur(t)]?.environment??{}}}function Za(e,t){let r=t.services??[],n=t.profile??ur("");if(r.length===0&&n===""){if(n=t.activeProfile||_E(e),n==="")throw M(ee,"no profile or services given; pass service names or add a profile")}let o=PE(e,n,r);return{services:o.services,env:o.env,profile:n}}function Ih(e,t){let r={},n=[];for(let o of t){if(r[o])continue;if(!e.services[o])throw M("service_not_found",`unknown service "${o}"`);r[o]=!0,n.push(o)}return n}var $h="UNKNOWN",rn="STARTING",Be="RUNNING",pr="HEALTHY",Nr="UNHEALTHY",Rr="STOPPING",_t="STOPPED",lt="FAILED",fr="RESTARTING",Et="UNKNOWN",Wt="HEALTHY",Me="UNHEALTHY";var Tt=D(()=>{mt();te()});function py(e){if(e.newer)return`update available ${e.current} \u2192 ${e.latest} ${e.hint}`;if(e.latest!=="")return`${e.current} up to date`;return`${e.current} (latest unavailable)`}function fy(e){let t=dy(e.scriptPath),r=dy(e.execPath);if(!e.standalone&&t.includes("/@amr-m-abdelgawad/devctl/")){if(t.includes("/_npx/"))return{kind:"npx",hint:"npx @amr-m-abdelgawad/devctl@latest"};let n=r0();return{kind:"npm",command:n,hint:n.join(" ")}}if(e.standalone&&r.toLowerCase().includes("/cellar/devctl/"))return{kind:"homebrew",command:["brew","reinstall","--formula","https://raw.githubusercontent.com/amr-m-abdelgawad/devctl/main/homebrew/devctl.rb"],hint:"brew reinstall --formula https://raw.githubusercontent.com/amr-m-abdelgawad/devctl/main/homebrew/devctl.rb"};if(e.standalone)return{kind:"release",hint:"re-download the matching GitHub Release binary \u2014 see docs/installation.md"};if(t.endsWith("/src/bin.ts"))return{kind:"source",hint:"git pull && cd app && bun install"};return{kind:"unknown",hint:`npm install --global ${"@amr-m-abdelgawad/devctl"}@latest # alternatives: Homebrew or the unsigned GitHub Release binaries \u2014 see docs/installation.md`}}function my(e,t){let r=uy(e),n=uy(t);for(let o=0;o<3;o+=1){let i=(r[o]??0)-(n[o]??0);if(i!==0)return i>0?1:-1}return 0}function r0(){return[process.platform==="win32"?"npm.cmd":"npm","install","--global","@amr-m-abdelgawad/devctl@latest"]}function dy(e){return e.replaceAll("\\","/")}function uy(e){return e.replace(/^v/,"").split(".").slice(0,3).map((t)=>{let r=Number.parseInt(t.replace(/[^0-9].*$/,""),10);return Number.isFinite(r)?r:0})}var sc="restart the daemon with `devctl down` then start again";function Pn(e){if(!Number.isInteger(e))return Gu;return Math.min(WT,Math.max(jT,e))}function uv(e,t){if(t.length!==1||t<"0"||t>"9")return e;if(e.length>=GT)return t;return`${e}${t}`}function pv(e){return e.slice(0,-1)}function fv(e,t){if(e==="")return Pn(t);let r=Number(e);if(!Number.isInteger(r))return Pn(t);return Pn(r)}function lo(e){let t=Number.parseInt(ar(e).slice(0,BT),16),r=Number.isFinite(t)?t%dv:0;return Gu+r}function mv(e,t){return t===lo(e)}var Gu=18700,dv=600,ZH,BT=8,WT=65535,jT=1024,GT=5;var Uu=D(()=>{Us();ZH=Gu+dv-1});class mr{nameMarkers;patterns;constructor(e,t){this.nameMarkers=[...Sv,...e],this.patterns=t.flatMap((r)=>{try{return[new RegExp(r)]}catch{return[]}})}update(e,t){this.nameMarkers=[...Sv,...e],this.patterns=t.flatMap((r)=>{try{return[new RegExp(r)]}catch{return[]}})}isSecretName(e){let t=e.toUpperCase();return this.nameMarkers.some((r)=>t.includes(r.toUpperCase()))}redactValue(e,t){if(t==="")return t;if(this.isSecretName(e))return"********";if(this.patterns.some((r)=>r.test(t)))return"********";return t}redactMap(e){let t={};for(let[r,n]of Object.entries(e))t[r]=this.redactValue(r,n);return t}redactText(e){if(e==="")return e;let t=e;for(let r of this.patterns)t=t.replace(r,"********");return eA(t)}}function eA(e){let t=e.toLowerCase(),r="authorization: bearer ",n=t.indexOf("authorization: bearer ");if(n>=0)return kv(e,n+22);let o=t.indexOf("bearer ");if(o<0)return e;return kv(e,o+7)}function kv(e,t){let r=t;while(r<e.length&&!tA(e[r]??""))r+=1;return e.slice(0,t)+"********"+e.slice(r)}function tA(e){return e===" "||e==="\t"||e===`
60
- `||e==="\r"}var Sv;var ti=D(()=>{Sv=["PASSWORD","SECRET","TOKEN","PRIVATE_KEY","CLIENT_SECRET","API_KEY","CREDENTIAL","ACCESS_KEY","AUTH_KEY"]});var yc;var Cv=D(()=>{yc={procedure:`---
59
+ `}return t}function oc(e){return`${e.join(", ")} changed and only take effect after a full \`devctl down && devctl start\`, not a reload`}function XE(e,t,r){let n={};if(t!==""){let o=e.profiles[t];if(!o)throw I("configuration",`unknown profile "${t}"`);Object.assign(n,o.environment)}if(r.length>0)return{services:Yg(e,r),env:n};if(t==="")return{services:Object.keys(e.services),env:n};return{services:Yg(e,e.profiles[t]?.services??[]),env:n}}function QE(e){let t=Object.keys(e.profiles).sort();return ur(t[0]??"")}function uo(e,t){if(t==="")return{};return{...e.profiles[ur(t)]?.environment??{}}}function ic(e,t){let r=t.services??[],n=t.profile??ur("");if(r.length===0&&n===""){if(n=t.activeProfile||QE(e),n==="")throw I(Q,"no profile or services given; pass service names or add a profile")}let o=XE(e,n,r);return{services:o.services,env:o.env,profile:n}}function Yg(e,t){let r={},n=[];for(let o of t){if(r[o])continue;if(!e.services[o])throw I("service_not_found",`unknown service "${o}"`);r[o]=!0,n.push(o)}return n}var Jg="UNKNOWN",on="STARTING",je="RUNNING",pr="HEALTHY",Br="UNHEALTHY",Rr="STOPPING",_t="STOPPED",dt="FAILED",fr="RESTARTING",Et="UNKNOWN",Bt="HEALTHY",Oe="UNHEALTHY";var Tt=D(()=>{ht();te()});function Rv(e){if(e.newer)return`update available ${e.current} \u2192 ${e.latest} ${e.hint}`;if(e.latest!=="")return`${e.current} up to date`;return`${e.current} (latest unavailable)`}function _v(e){let t=Cv(e.scriptPath),r=Cv(e.execPath);if(!e.standalone&&t.includes("/@amr-m-abdelgawad/devctl/")){if(t.includes("/_npx/"))return{kind:"npx",hint:"npx @amr-m-abdelgawad/devctl@latest"};let n=_T();return{kind:"npm",command:n,hint:n.join(" ")}}if(e.standalone&&r.toLowerCase().includes("/cellar/devctl/"))return{kind:"homebrew",command:["brew","reinstall","--formula","https://raw.githubusercontent.com/amr-m-abdelgawad/devctl/main/homebrew/devctl.rb"],hint:"brew reinstall --formula https://raw.githubusercontent.com/amr-m-abdelgawad/devctl/main/homebrew/devctl.rb"};if(e.standalone)return{kind:"release",hint:"re-download the matching GitHub Release binary \u2014 see docs/installation.md"};if(t.endsWith("/src/bin.ts"))return{kind:"source",hint:"git pull && cd app && bun install"};return{kind:"unknown",hint:`npm install --global ${"@amr-m-abdelgawad/devctl"}@latest # alternatives: Homebrew or the unsigned GitHub Release binaries \u2014 see docs/installation.md`}}function Ev(e,t){let r=Pv(e),n=Pv(t);for(let o=0;o<3;o+=1){let i=(r[o]??0)-(n[o]??0);if(i!==0)return i>0?1:-1}return 0}function _T(){return[process.platform==="win32"?"npm.cmd":"npm","install","--global","@amr-m-abdelgawad/devctl@latest"]}function Cv(e){return e.replaceAll("\\","/")}function Pv(e){return e.replace(/^v/,"").split(".").slice(0,3).map((t)=>{let r=Number.parseInt(t.replace(/[^0-9].*$/,""),10);return Number.isFinite(r)?r:0})}var pc="restart the daemon with `devctl down` then start again";function _n(e){if(!Number.isInteger(e))return Ju;return Math.min(pA,Math.max(fA,e))}function Py(e,t){if(t.length!==1||t<"0"||t>"9")return e;if(e.length>=mA)return t;return`${e}${t}`}function Ry(e){return e.slice(0,-1)}function _y(e,t){if(e==="")return _n(t);let r=Number(e);if(!Number.isInteger(r))return _n(t);return _n(r)}function mo(e){let t=Number.parseInt(ar(e).slice(0,uA),16),r=Number.isFinite(t)?t%Cy:0;return Ju+r}function Ey(e,t){return t===mo(e)}var Ju=18700,Cy=600,Uj,uA=8,pA=65535,fA=1024,mA=5;var Xu=D(()=>{Ys();Uj=Ju+Cy-1});class mr{nameMarkers;patterns;constructor(e,t){this.nameMarkers=[...$y,...e],this.patterns=t.flatMap((r)=>{try{return[new RegExp(r)]}catch{return[]}})}update(e,t){this.nameMarkers=[...$y,...e],this.patterns=t.flatMap((r)=>{try{return[new RegExp(r)]}catch{return[]}})}isSecretName(e){let t=e.toUpperCase();return this.nameMarkers.some((r)=>t.includes(r.toUpperCase()))}redactValue(e,t){if(t==="")return t;if(this.isSecretName(e))return"********";if(this.patterns.some((r)=>r.test(t)))return"********";return t}redactMap(e){let t={};for(let[r,n]of Object.entries(e))t[r]=this.redactValue(r,n);return t}redactText(e){if(e==="")return e;let t=e;for(let r of this.patterns)t=t.replace(r,"********");return PA(t)}}function PA(e){let t=e.toLowerCase(),r="authorization: bearer ",n=t.indexOf("authorization: bearer ");if(n>=0)return Fy(e,n+22);let o=t.indexOf("bearer ");if(o<0)return e;return Fy(e,o+7)}function Fy(e,t){let r=t;while(r<e.length&&!RA(e[r]??""))r+=1;return e.slice(0,t)+"********"+e.slice(r)}function RA(e){return e===" "||e==="\t"||e===`
60
+ `||e==="\r"}var $y;var ni=D(()=>{$y=["PASSWORD","SECRET","TOKEN","PRIVATE_KEY","CLIENT_SECRET","API_KEY","CREDENTIAL","ACCESS_KEY","AUTH_KEY"]});var Tn;var op=D(()=>{Tn={procedure:`---
61
61
  name: devctl-onboard
62
62
  description: Survey a repository \u2014 its runnable services, docker-compose, Terraform, Kubernetes manifests, .env files, task runners \u2014 and author a validated .devctl configuration for it. Use when asked to onboard a repo to devctl, add or fix .devctl/config.yaml, generate devctl services/profiles/proxy routes, or diagnose why a devctl config fails to load.
63
63
  ---
@@ -129,7 +129,7 @@ nothing at all. What Terraform legitimately contributes:
129
129
  | Terraform | Becomes |
130
130
  |---|---|
131
131
  | \`google_service_account\` | \`identity.service_account\`, or a route's \`auth.identity.service_account\` |
132
- | IAP brand / OAuth client / \`iap_web_*\` | route \`auth.type: iap\` + \`audience\` |
132
+ | IAP brand / OAuth client / \`iap_web_*\` | route \`auth.type: iap\` + \`audience\`; optional \`client_id\` + \`client_secret\` (\`\${NAME}\` or a literal) to mint with a specific user OAuth client |
133
133
  | \`google_secret_manager_secret\` | a name under \`environment.secrets\` \u2014 never the value |
134
134
  | \`project\`, \`region\` in provider/vars | \`google.project_id\`, \`google.region\` |
135
135
  | Cloud Run / GKE endpoint you do **not** run locally | proxy route \`upstream.url\` |
@@ -353,8 +353,10 @@ Finish with:
353
353
 
354
354
  Both are also served by the MCP server itself, as
355
355
  \`get_setup_guide { "section": "discovery" | "authoring" }\` \u2014 the same text,
356
- compiled into the devctl binary. If you reached this procedure through MCP,
357
- there is nothing to install: fetch the sections you need from there.
356
+ compiled into the devctl binary. \`search_docs\` searches the rest of the
357
+ product wiki (IAP, proxy, configuration, and similar). If you reached this
358
+ procedure through MCP, there is nothing to install: fetch the sections you
359
+ need from there.
358
360
 
359
361
  ### Starting from nothing
360
362
 
@@ -365,7 +367,7 @@ the agent to set it up" a complete path. In that state \`get_status\` reports
365
367
  arguments reports the missing configuration rather than a parse error. Draft,
366
368
  validate the candidate text, write the files, then call \`reload_config\`: setup
367
369
  mode clears on the reload that finds a valid configuration.
368
- `,authoring:"# Authoring rules the loader enforces\n\nThe JSON Schema (`schema/devctl.config.schema.json`) describes the shape.\nThis file describes what the **loader and validator actually reject** \u2014 the\nrules that turn a schema-plausible config into one that fails at\n`devctl config validate`. Check anything you are unsure of here before writing.\n\n---\n\n## Unknown fields are rejected everywhere\n\nNot warned about \u2014 rejected, with the offending path named. These are the\ncomplete allowlists.\n\n**Top level:** `version` `project` `google` `profiles` `templates` `services`\n`tasks` `proxy` `logs` `auth` `shutdown` `ui` `secrets` `doctor` `plugins`\n`environment`\n\n**Service** (and `templates.<name>`, same shape): `extends` `description`\n`command` `shell` `working_dir` `dependencies` `ports` `environment` `health`\n`identity` `logs` `restart` `startup` `capabilities` `proxy` `container` `watch` `hooks`\n\n| Section | Allowed keys |\n|---|---|\n| `project` | `name` |\n| `google` | `project_id` `region` |\n| `profiles.<name>` | `services` `environment` |\n| `service.health` | `type` `url` `address` `command` `interval_seconds` `timeout_seconds` `start_period_seconds` `unhealthy_threshold` `healthy_reset_threshold` |\n| `service.hooks` | `pre_start` `post_start` |\n| `service.container` | `image` `runtime` `ports` `env` `volumes` |\n| `service.watch` | `enabled` `paths` `debounce_ms` `ignore` |\n| `tasks.<name>` | `command` `shell` `working_dir` `dependencies` `environment` |\n| `service.identity` | `type` `mode` `service_account` |\n| `service.restart` | `enabled` `policy` `max_retries` `backoff_seconds` |\n| `service.startup` | `wait_for_healthy` `timeout_seconds` |\n| `service.logs` | `stdout` `stderr` |\n| `service.environment` | `required` `defaults` + arbitrary `KEY: value` pairs |\n| `proxy` | `enabled` `listen` `token_endpoint` `routes` |\n| `proxy.listen` | `host` `port` |\n| `proxy.token_endpoint` | `enabled` `host` `port` |\n| `route` | `name` `match` `upstream` `auth` |\n| `route.match` | `host` `path` |\n| `route.upstream` | `url` |\n| `route.auth` | `type` `identity` `audience` `service_account` |\n| `logs` | `max_memory_events` `persistence` |\n| `logs.persistence` | `enabled` `directory` `retention_days` `max_session_logs` |\n| `auth` | `refresh_threshold_seconds` |\n| `shutdown` | `stop_services_on_exit` `grace_seconds` |\n| `ui` | `theme` `keymap` |\n| `secrets` | `extra_markers` `extra_patterns` |\n| `doctor` | `tools` (each `{ name, command }`) |\n| `plugins[]` | `path` |\n| `environment` | `sources` `secrets` |\n\nThere is no `depends_on`, `build`, `replicas`, or `env_file`. Container fields\nbelong under `service.container`, not directly on the service.\n\n---\n\n## Required, at minimum\n\n- `version: 1`. Anything else: *unsupported config version N; no migration is\n available.*\n- **At least one service.** An empty or missing `services` map fails with *at\n least one service must be defined* \u2014 so a proxy-only or profiles-only config\n is not valid.\n- Every host service needs a non-empty `command`; a container service may rely on its image command.\n\n---\n\n## Ports\n\n- **Globally unique across all services.** Two services on `8000` gives\n *duplicate port 8000 used by api and worker*. This is the single most common\n failure when onboarding a repo whose services each assumed they owned the\n default port.\n- Range 1\u201365535.\n- Forms accepted: a named map (`ports: { http: 8000 }`, preferred), a list, a\n bare number, or `auto`. `auto` is exempt from the uniqueness check \u2014\n devctl allocates at start.\n- Name the port `http` when it serves HTTP.\n- Prefer the explicit `${services.X.ports.http}` reference form. The short\n `${services.X.port}` form is only unambiguous for a single-port service: once\n the service is running it prefers the assigned port named `http`, but when it\n resolves against the configuration it takes the **first port declared**,\n whatever its name. Two forms of the same reference disagreeing on a\n multi-port service is a genuinely confusing bug to chase.\n\n## References\n\n`${services.<name>.ports.<portname>}` and `${services.<name>.port}` are the\nsupported forms. Anything else \u2014 `${env.FOO}`, `${project.name}` \u2014 throws.\n\n- The referenced service must exist and the named port must be defined, or\n validation fails with *unresolvable reference*.\n- References resolve inside service `environment` values, `defaults`, profile\n environments and dotenv values, before the process starts.\n- Use them for every cross-service URL. Hard-coded ports silently break when a\n port changes or is switched to `auto`.\n\n## Dependencies\n\n- Must name a service that exists.\n- A service may not depend on itself.\n- Cycles are rejected: *dependency cycle: a \u2192 b \u2192 a*.\n- Direction: `dependencies` means \"start these first\". `start x` walks **up**\n and starts x's dependencies; `stop x` walks **down** and stops x's\n *dependents*, never its dependencies.\n- A string dependency uses `service_started`. Use `{ service: db, condition:\n service_healthy }` to wait for the dependency's configured health check.\n\n## Commands and `shell`\n\nA command whose argv contains any of `|` `||` `&&` `;` `>` `>>` `<` `&` is\nrejected unless `shell: true`:\n\n```yaml\n# rejected\ncommand: \"npm run build && npm start\"\n\n# correct\ncommand: \"npm run build && npm start\"\nshell: true\n\n# better \u2014 no shell needed\ncommand: [npm, start]\n```\n\nPrefer argv lists. A string command is split on whitespace, which mangles\narguments containing spaces.\n\n## `working_dir`\n\nRelative to the **repository root** \u2014 the directory containing `.devctl` \u2014 not\nthe process cwd, and not the `.devctl` directory itself.\n\n---\n\n## Health\n\n| `type` | Also requires |\n|---|---|\n| `http` | `url` \u2014 omit it and you get *health.url is required for http health checks* |\n| `tcp` | `address`, **or** at least one port defined on the service |\n| `command` | `health.command` (non-empty) |\n| `process` or omitted | nothing; only checks the pid is alive |\n\nAny other `type` is rejected \u2014 *health.type must be http, tcp, process, or\ncommand* \u2014 **unless** `plugins` is non-empty. That exemption is not approval:\nplugins load after validation, so the supervisor re-checks the type at boot and\nat reload. A custom type no plugin provides fails as a *reload rejection*, not\na config error, which is much harder to diagnose. Only use a custom type when\nyou can point at the plugin that registers it.\n\nDefaults when omitted: interval 2s, timeout 2s, start period 0s, unhealthy\nthreshold 3, healthy reset threshold 10. Failures during the start period do\nnot mark the service unhealthy or consume restart budget.\n\n## Identity\n\n- `type: user` (or omitted) needs nothing else.\n- `type: service_account` (or `service`) **requires** `service_account`, and it\n must contain `@` \u2014 *service_account must be an email*.\n- `mode` is an accepted alias of `type`.\n- Any other type behaves like a custom health type: allowed only with\n `plugins` set, re-checked at boot.\n\nNever fabricate an SA email. If Terraform interpolates it beyond what you can\nresolve, leave a clearly-marked placeholder and tell the user.\n\n## Restart\n\n`policy` must be `never`, `on_failure`, or `always`. `enabled: true` with no\npolicy behaves as `on_failure`.\n\n`max_retries` is a real budget: it resets on a manual stop/start/restart and\nafter a sustained healthy run, but an automatic health-triggered restart\npreserves it across its own cycle.\n\n## Capabilities\n\nDocumentation for doctor, not behaviour. Only these are accepted:\n`google` `google_api` `iap` `network` `service_identity` `local_http`.\nAnything else is rejected.\n\n---\n\n## Proxy\n\n- `proxy.listen.port` is **required** when `proxy.enabled: true`.\n- `proxy.listen.host` must be loopback (`127.0.0.1`, `localhost`, `::1`, or `127.0.0.0/8`). `0.0.0.0` and `::` are rejected.\n- `proxy.token_endpoint.host` must be loopback; `0.0.0.0` and `::` are rejected.\n- Every route needs a `name` and an `upstream.url`.\n- **Route names must be unique** \u2014 including names generated from per-service\n `proxy` fragments.\n- `auth.type: iap` requires **both** `audience` and `auth.identity.type`. A\n missing identity type on an IAP route is a configuration error, not a\n default.\n- An identity of `service` / `service_account` requires an SA email, from\n either `auth.identity.service_account` or the route's `auth.service_account`.\n- `auth.type: none` means no auth at all \u2014 any identity left on such a route is\n ignored entirely, and will not be probed at start or by doctor.\n\n### Per-service route fragments\n\nA service's `proxy` key is one route or a list. At load they append into the\n**same** global `proxy.routes` list, named `<service>` or `<service>-<n>`:\n\n```yaml\nservices:\n api:\n command: [python3, main.py]\n proxy:\n - match: { path: /api }\n upstream: { url: \"http://127.0.0.1:8000\" }\n```\n\nThat generates a route named `api`. So a service named `api` **and** a global\nroute named `api` collide \u2014 *duplicate route name api*. Watch for this when\nmixing both styles.\n\nMatching is host + optional path prefix. There is no path rewriting or\nstripping: the matched path is forwarded to the upstream as-is.\n\n---\n\n## Modular layout\n\nWhen the main file is `.devctl/config.yaml`, these merge in automatically:\n\n```\n.devctl/services/<name>.yaml \u2192 services.<name> (FILENAME is the key)\n.devctl/profiles/<name>.yaml \u2192 profiles.<name>\n.devctl/proxy/routes.yaml \u2192 proxy.routes (see the wrapper below)\n```\n\nA service or profile file contains that object's body **only** \u2014 no `services:`\nwrapper, no `version:`. Getting this wrong reads oddly: a `services:` wrapper\ninside `.devctl/services/api.yaml` reports\n`unknown fields: services.api.services`.\n\n`proxy/routes.yaml` is the exception: it accepts either a full `proxy:` wrapper\nor a bare top-level `routes:` list. Both append to the same global route list.\n\n```yaml\n# .devctl/proxy/routes.yaml \u2014 wrapper form, also lets you set proxy.listen here\nproxy:\n routes:\n - name: invoices-api\n match: { host: invoices-api.local }\n upstream: { url: \"http://127.0.0.1:18000\" }\n```\n\n```yaml\n# .devctl/proxy/routes.yaml \u2014 bare form, equivalent for routes alone\nroutes:\n - name: invoices-api\n match: { host: invoices-api.local }\n upstream: { url: \"http://127.0.0.1:18000\" }\n```\n\nPick one. Both keys in the same file append **both** lists, which trips the\nduplicate-route-name check.\n\nOverlays merge after the repo config, later wins:\nbuilt-in defaults \u2192 repo `.devctl` \u2192 `~/.devctl/config.local.yaml` \u2192\n`.devctl/config.local.yaml` \u2014 the repo-local overlay wins over the home one.\nOverlays are presence-aware:\n`false`, `0` and\nempty collections in an overlay do override, so `proxy.enabled: false` in a\nlocal overlay genuinely turns the proxy off.\n\n---\n\n## Environment\n\nMerge order \u2014 later sources win:\n\n```\nprocess \u2192 profile \u2192 dotenv \u2192 generated \u2192 keychain \u2192 secret_manager \u2192 defaults \u2192 vars \u2192 runtime\n```\n\n`process`, `defaults`, `vars` and `runtime` always run. Listing\n`environment.sources` **adds** optional sources (`profile`, `dotenv`,\n`generated`, `keychain`, `secret_manager`) to that always-on set \u2014 it does not\nreplace it, and it does not reorder anything.\n\n- `dotenv` reads repo root then `working_dir`: `.env`, `.env.development`,\n `.env.local`, `.env.<profile>`.\n- `keychain` and `secret_manager` throw when listed and the fetch fails, so\n only list them when the repo genuinely uses them.\n- `environment.required` on a service fails the start if those keys are still\n empty after the whole merge \u2014 the right place to encode \"this cannot run\n without X\".\n\nRuntime values devctl injects: `SERVICE_PORT`, `SERVICE_HOST`,\n`DEVCTL_PROXY_URL`, `DEVCTL_SERVICE_NAME`, `DEVCTL_ENVIRONMENT`,\n`DEVCTL_TOKEN_URL`, `DEVCTL_INTERNAL_TOKEN`. Do not define these yourself.\n\n---\n\n## Templates\n\n```yaml\ntemplates:\n python-http:\n health: { type: http, interval_seconds: 2, timeout_seconds: 1 }\n logs: { stdout: true, stderr: true }\n restart: { policy: on_failure, max_retries: 2, backoff_seconds: 1 }\n\nservices:\n api:\n extends: python-http\n command: [python3, main.py]\n health: { url: \"http://127.0.0.1:8000/health\" } # merged field-by-field\n```\n\n- `extends` must name a template that exists.\n- Nested sections merge **field by field**, so a service can override one\n health field without restating the rest.\n- A template is not a service and is never started.\n\n---\n\n## Reading validation output\n\nEvery message names its path. Fix the path it names.\n\n| Message | Meaning |\n|---|---|\n| `at least one service must be defined` | empty/missing `services` |\n| `duplicate port N used by X and Y` | two services pinned the same port |\n| `services.X.command is required` | missing or empty command |\n| `services.X.command contains shell metacharacters` | add `shell: true` or use argv |\n| `services.X.dependencies: unknown service \"Y\"` | typo, or Y lives in a file you did not create |\n| `dependency cycle: a \u2192 b \u2192 a` | remove an edge |\n| `services.X.health.url is required for http health checks` | add `url`, or change the type |\n| `services.X.identity.service_account must be an email` | placeholder left unresolved |\n| `services.X.environment.K: unresolvable reference ${\u2026}` | referenced service or port name does not exist |\n| `services.X.capabilities: unknown capability \"c\"` | only the six listed above are accepted |\n| `profiles.P references unknown service \"S\"` | profile lists a service that is not defined |\n| `proxy.routes[i]: duplicate route name N` | often a per-service fragment colliding with a global route |\n| `proxy.routes[i].auth.audience is required when auth.type is iap` | IAP needs both audience and identity.type |\n| `proxy.listen.port is required when proxy.enabled is true` | pin a port |\n| `unsupported config version N (expected 1)` | `version:` must be `1` |\n| `unknown fields: services.a.depends_on` | not in the allowlists at the top of this file \u2014 usually a compose or k8s spelling |\n\nEvery message above is the validator's exact wording. `devctl config validate`\nprints one per line and exits 2; a clean run prints `configuration is valid`\nand exits 0.\n",discovery:`# Discovery: repository signal \u2192 devctl service
370
+ `,authoring:"# Authoring rules the loader enforces\n\nThe JSON Schema (`schema/devctl.config.schema.json`) describes the shape.\nThis file describes what the **loader and validator actually reject** \u2014 the\nrules that turn a schema-plausible config into one that fails at\n`devctl config validate`. Check anything you are unsure of here before writing.\n\n---\n\n## Unknown fields are rejected everywhere\n\nNot warned about \u2014 rejected, with the offending path named. These are the\ncomplete allowlists.\n\n**Top level:** `version` `project` `google` `profiles` `templates` `services`\n`tasks` `proxy` `logs` `auth` `shutdown` `ui` `secrets` `doctor` `plugins`\n`environment`\n\n**Service** (and `templates.<name>`, same shape): `extends` `description`\n`command` `shell` `working_dir` `dependencies` `ports` `environment` `health`\n`identity` `logs` `restart` `startup` `capabilities` `proxy` `expose` `container` `watch` `hooks`\n\n| Section | Allowed keys |\n|---|---|\n| `project` | `name` |\n| `google` | `project_id` `region` |\n| `profiles.<name>` | `services` `environment` |\n| `service.health` | `type` `url` `address` `command` `interval_seconds` `timeout_seconds` `start_period_seconds` `unhealthy_threshold` `healthy_reset_threshold` |\n| `service.hooks` | `pre_start` `post_start` |\n| `service.container` | `image` `runtime` `ports` `env` `volumes` |\n| `service.watch` | `enabled` `paths` `debounce_ms` `ignore` |\n| `tasks.<name>` | `command` `shell` `working_dir` `dependencies` `environment` |\n| `service.identity` | `type` `mode` `service_account` |\n| `service.restart` | `enabled` `policy` `max_retries` `backoff_seconds` |\n| `service.startup` | `wait_for_healthy` `timeout_seconds` |\n| `service.logs` | `stdout` `stderr` |\n| `service.environment` | `required` `defaults` + arbitrary `KEY: value` pairs |\n| `service.expose` | `enabled` `host` `port` (or the `true` shorthand) |\n| `proxy` | `enabled` `gateway` `listen` `token_endpoint` `routes` |\n| `proxy.listen` | `host` `port` |\n| `proxy.token_endpoint` | `enabled` `host` `port` |\n| `route` | `name` `match` `upstream` `auth` |\n| `route.match` | `host` `path` |\n| `route.upstream` | `url` `service` `port` |\n| `route.auth` | `type` `identity` `audience` `service_account` `client_id` `client_secret` |\n| `logs` | `max_memory_events` `persistence` |\n| `logs.persistence` | `enabled` `directory` `retention_days` `max_session_logs` |\n| `auth` | `refresh_threshold_seconds` |\n| `shutdown` | `stop_services_on_exit` `grace_seconds` |\n| `ui` | `theme` `keymap` |\n| `secrets` | `extra_markers` `extra_patterns` |\n| `doctor` | `tools` (each `{ name, command }`) |\n| `plugins[]` | `path` |\n| `environment` | `sources` `secrets` |\n\nThere is no `depends_on`, `build`, `replicas`, or `env_file`. Container fields\nbelong under `service.container`, not directly on the service.\n\n---\n\n## Required, at minimum\n\n- `version: 1`. Anything else: *unsupported config version N; no migration is\n available.*\n- **At least one service.** An empty or missing `services` map fails with *at\n least one service must be defined* \u2014 so a proxy-only or profiles-only config\n is not valid.\n- Every host service needs a non-empty `command`; a container service may rely on its image command.\n\n---\n\n## Ports\n\n- **Globally unique across all services.** Two services on `8000` gives\n *duplicate port 8000 used by api and worker*. This is the single most common\n failure when onboarding a repo whose services each assumed they owned the\n default port.\n- Range 1\u201365535.\n- Forms accepted: a named map (`ports: { http: 8000 }`, preferred), a list, a\n bare number, or `auto`. `auto` is exempt from the uniqueness check \u2014\n devctl allocates at start.\n- Name the port `http` when it serves HTTP.\n- Prefer the explicit `${services.X.ports.http}` reference form. The short\n `${services.X.port}` form is only unambiguous for a single-port service: once\n the service is running it prefers the assigned port named `http`, but when it\n resolves against the configuration it takes the **first port declared**,\n whatever its name. Two forms of the same reference disagreeing on a\n multi-port service is a genuinely confusing bug to chase.\n\n## References\n\n`${services.<name>.ports.<portname>}` and `${services.<name>.port}` are the\nsupported forms. Anything else \u2014 `${env.FOO}`, `${project.name}` \u2014 throws.\n\n- The referenced service must exist and the named port must be defined, or\n validation fails with *unresolvable reference*.\n- References resolve inside service `environment` values, `defaults`, profile\n environments and dotenv values, before the process starts.\n- Use them for every cross-service URL. Hard-coded ports silently break when a\n port changes or is switched to `auto`.\n\n## Dependencies\n\n- Must name a service that exists.\n- A service may not depend on itself.\n- Cycles are rejected: *dependency cycle: a \u2192 b \u2192 a*.\n- Direction: `dependencies` means \"start these first\". `start x` walks **up**\n and starts x's dependencies; `stop x` walks **down** and stops x's\n *dependents*, never its dependencies.\n- A string dependency uses `service_started`. Use `{ service: db, condition:\n service_healthy }` to wait for the dependency's configured health check.\n\n## Commands and `shell`\n\nA command whose argv contains any of `|` `||` `&&` `;` `>` `>>` `<` `&` is\nrejected unless `shell: true`:\n\n```yaml\n# rejected\ncommand: \"npm run build && npm start\"\n\n# correct\ncommand: \"npm run build && npm start\"\nshell: true\n\n# better \u2014 no shell needed\ncommand: [npm, start]\n```\n\nPrefer argv lists. A string command is split on whitespace, which mangles\narguments containing spaces.\n\n## `working_dir`\n\nRelative to the **repository root** \u2014 the directory containing `.devctl` \u2014 not\nthe process cwd, and not the `.devctl` directory itself.\n\n---\n\n## Health\n\n| `type` | Also requires |\n|---|---|\n| `http` | `url` \u2014 omit it and you get *health.url is required for http health checks* |\n| `tcp` | `address`, **or** at least one port defined on the service |\n| `command` | `health.command` (non-empty) |\n| `process` or omitted | nothing; only checks the pid is alive |\n\nAny other `type` is rejected \u2014 *health.type must be http, tcp, process, or\ncommand* \u2014 **unless** `plugins` is non-empty. That exemption is not approval:\nplugins load after validation, so the supervisor re-checks the type at boot and\nat reload. A custom type no plugin provides fails as a *reload rejection*, not\na config error, which is much harder to diagnose. Only use a custom type when\nyou can point at the plugin that registers it.\n\nDefaults when omitted: interval 2s, timeout 2s, start period 0s, unhealthy\nthreshold 3, healthy reset threshold 10. Failures during the start period do\nnot mark the service unhealthy or consume restart budget.\n\n## Identity\n\n- `type: user` (or omitted) needs nothing else.\n- `type: service_account` (or `service`) **requires** `service_account`, and it\n must contain `@` \u2014 *service_account must be an email*.\n- `mode` is an accepted alias of `type`.\n- Any other type behaves like a custom health type: allowed only with\n `plugins` set, re-checked at boot.\n\nNever fabricate an SA email. If Terraform interpolates it beyond what you can\nresolve, leave a clearly-marked placeholder and tell the user.\n\n## Restart\n\n`policy` must be `never`, `on_failure`, or `always`. `enabled: true` with no\npolicy behaves as `on_failure`.\n\n`max_retries` is a real budget: it resets on a manual stop/start/restart and\nafter a sustained healthy run, but an automatic health-triggered restart\npreserves it across its own cycle.\n\n## Capabilities\n\nDocumentation for doctor, not behaviour. Only these are accepted:\n`google` `google_api` `iap` `network` `service_identity` `local_http`.\nAnything else is rejected.\n\n---\n\n## Proxy\n\n- `proxy.listen.port` is **required** when `proxy.enabled: true`.\n- `proxy.listen.host` must be loopback (`127.0.0.1`, `localhost`, `::1`, or `127.0.0.0/8`). `0.0.0.0` and `::` are rejected.\n- `proxy.token_endpoint.host` must be loopback; `0.0.0.0` and `::` are rejected.\n- Every route needs a `name` and either `upstream.url` or `upstream.service`.\n A service reference must name a real service and an existing port (default\n port name is `http`). `service.expose` and `proxy.gateway` synthesize\n service-reference routes; they do nothing unless `proxy.enabled` is true.\n A hand-written route of the same name wins.\n- **Route names must be unique** \u2014 including names generated from per-service\n `proxy` fragments.\n- `auth.type: iap` requires **both** `audience` and `auth.identity.type`. A\n missing identity type on an IAP route is a configuration error, not a\n default.\n- Optional `client_id` plus `client_secret` mints the user IAP ID token with\n that OAuth client instead of ADC's default client. Put `${NAME}` or\n `${env.NAME}` in `client_secret` so the value is read from the environment\n at mint time (this is not service-env interpolation). These fields are\n invalid on non-IAP routes and on IAP routes whose identity is a service\n account.\n- An identity of `service` / `service_account` requires an SA email, from\n either `auth.identity.service_account` or the route's `auth.service_account`.\n- `auth.type: none` means no auth at all \u2014 any identity left on such a route is\n ignored entirely, and will not be probed at start or by doctor.\n\n### Per-service route fragments\n\nA service's `proxy` key is one route or a list. At load they append into the\n**same** global `proxy.routes` list, named `<service>` or `<service>-<n>`:\n\n```yaml\nservices:\n api:\n command: [python3, main.py]\n proxy:\n - match: { path: /api }\n upstream: { url: \"http://127.0.0.1:8000\" }\n```\n\nThat generates a route named `api`. So a service named `api` **and** a global\nroute named `api` collide \u2014 *duplicate route name api*. Watch for this when\nmixing both styles.\n\nMatching is host + optional path prefix. There is no path rewriting or\nstripping: the matched path is forwarded to the upstream as-is.\n\n---\n\n## Modular layout\n\nWhen the main file is `.devctl/config.yaml`, these merge in automatically:\n\n```\n.devctl/services/<name>.yaml \u2192 services.<name> (FILENAME is the key)\n.devctl/profiles/<name>.yaml \u2192 profiles.<name>\n.devctl/proxy/routes.yaml \u2192 proxy.routes (see the wrapper below)\n```\n\nA service or profile file contains that object's body **only** \u2014 no `services:`\nwrapper, no `version:`. Getting this wrong reads oddly: a `services:` wrapper\ninside `.devctl/services/api.yaml` reports\n`unknown fields: services.api.services`.\n\n`proxy/routes.yaml` is the exception: it accepts either a full `proxy:` wrapper\nor a bare top-level `routes:` list. Both append to the same global route list.\n\n```yaml\n# .devctl/proxy/routes.yaml \u2014 wrapper form, also lets you set proxy.listen here\nproxy:\n routes:\n - name: invoices-api\n match: { host: invoices-api.local }\n upstream: { url: \"http://127.0.0.1:18000\" }\n```\n\n```yaml\n# .devctl/proxy/routes.yaml \u2014 bare form, equivalent for routes alone\nroutes:\n - name: invoices-api\n match: { host: invoices-api.local }\n upstream: { url: \"http://127.0.0.1:18000\" }\n```\n\nPick one. Both keys in the same file append **both** lists, which trips the\nduplicate-route-name check.\n\nOverlays merge after the repo config, later wins:\nbuilt-in defaults \u2192 repo `.devctl` \u2192 `~/.devctl/config.local.yaml` \u2192\n`.devctl/config.local.yaml` \u2014 the repo-local overlay wins over the home one.\nOverlays are presence-aware:\n`false`, `0` and\nempty collections in an overlay do override, so `proxy.enabled: false` in a\nlocal overlay genuinely turns the proxy off.\n\n---\n\n## Environment\n\nMerge order \u2014 later sources win:\n\n```\nprocess \u2192 profile \u2192 dotenv \u2192 generated \u2192 keychain \u2192 secret_manager \u2192 defaults \u2192 vars \u2192 runtime\n```\n\n`process`, `defaults`, `vars` and `runtime` always run. Listing\n`environment.sources` **adds** optional sources (`profile`, `dotenv`,\n`generated`, `keychain`, `secret_manager`) to that always-on set \u2014 it does not\nreplace it, and it does not reorder anything.\n\n- `dotenv` reads repo root then `working_dir`: `.env`, `.env.development`,\n `.env.local`, `.env.<profile>`.\n- `keychain` and `secret_manager` throw when listed and the fetch fails, so\n only list them when the repo genuinely uses them.\n- `environment.required` on a service fails the start if those keys are still\n empty after the whole merge \u2014 the right place to encode \"this cannot run\n without X\".\n\nRuntime values devctl injects: `SERVICE_PORT`, `SERVICE_HOST`,\n`DEVCTL_PROXY_URL`, `DEVCTL_SERVICE_NAME`, `DEVCTL_ENVIRONMENT`,\n`DEVCTL_TOKEN_URL`, `DEVCTL_INTERNAL_TOKEN`. Do not define these yourself.\n\n---\n\n## Templates\n\n```yaml\ntemplates:\n python-http:\n health: { type: http, interval_seconds: 2, timeout_seconds: 1 }\n logs: { stdout: true, stderr: true }\n restart: { policy: on_failure, max_retries: 2, backoff_seconds: 1 }\n\nservices:\n api:\n extends: python-http\n command: [python3, main.py]\n health: { url: \"http://127.0.0.1:8000/health\" } # merged field-by-field\n```\n\n- `extends` must name a template that exists.\n- Nested sections merge **field by field**, so a service can override one\n health field without restating the rest.\n- A template is not a service and is never started.\n\n---\n\n## Reading validation output\n\nEvery message names its path. Fix the path it names.\n\n| Message | Meaning |\n|---|---|\n| `at least one service must be defined` | empty/missing `services` |\n| `duplicate port N used by X and Y` | two services pinned the same port |\n| `services.X.command is required` | missing or empty command |\n| `services.X.command contains shell metacharacters` | add `shell: true` or use argv |\n| `services.X.dependencies: unknown service \"Y\"` | typo, or Y lives in a file you did not create |\n| `dependency cycle: a \u2192 b \u2192 a` | remove an edge |\n| `services.X.health.url is required for http health checks` | add `url`, or change the type |\n| `services.X.identity.service_account must be an email` | placeholder left unresolved |\n| `services.X.environment.K: unresolvable reference ${\u2026}` | referenced service or port name does not exist |\n| `services.X.capabilities: unknown capability \"c\"` | only the six listed above are accepted |\n| `profiles.P references unknown service \"S\"` | profile lists a service that is not defined |\n| `proxy.routes[i]: duplicate route name N` | often a per-service fragment colliding with a global route |\n| `proxy.routes[i].auth.audience is required when auth.type is iap` | IAP needs both audience and identity.type |\n| `proxy.routes[i].auth.client_id is only valid when auth.type is iap` | `client_id` / secret only apply to IAP user routes |\n| `proxy.routes[i].auth.client_id is required when client_secret is set` | secret without client_id |\n| `proxy.routes[i].auth.client_secret is required when client_id is set` | client_id needs a secret |\n| `proxy.routes[i].auth.client_id is only valid with identity.type user` | SA IAP uses generateIdToken, not a user OAuth client |\n| `proxy.listen.port is required when proxy.enabled is true` | pin a port |\n| `unsupported config version N (expected 1)` | `version:` must be `1` |\n| `unknown fields: services.a.depends_on` | not in the allowlists at the top of this file \u2014 usually a compose or k8s spelling |\n\nEvery message above is the validator's exact wording. `devctl config validate`\nprints one per line and exits 2; a clean run prints `configuration is valid`\nand exits 0.\n",discovery:`# Discovery: repository signal \u2192 devctl service
369
371
 
370
372
  How to turn what a repository contains into the service list you draft from.
371
373
  Read the signals in roughly this order \u2014 the earlier ones describe *how the
@@ -592,77 +594,1618 @@ Rules for turning that into YAML:
592
594
  a full one. Remember that a bare \`devctl start\` falls back to the first
593
595
  configured profile, and the TUI's default is the first profile
594
596
  alphabetically \u2014 so name them with that in mind.
595
- `}});import{existsSync as rA,readFileSync as nA}from"fs";function En(e,t){return!(t??[]).includes(e)}function Rv(e){return xc.filter((t)=>En(t.name,e))}function bc(e){return xc.some((t)=>t.name===e)}function is(e){return new mr(e.secrets.extra_markers,e.secrets.extra_patterns)}function Pv(e){return Object.values(e.services).map((t)=>({name:t.name,state:t.state,health:t.health,ports:t.ports,pid:t.pid,last_error:t.last_error}))}function iA(e,t){let r=e.config(),n=r.services[t];if(!n)throw Error(`unknown service ${t}`);let i=e.status().services[t],s=is(r);return{name:t,state:i?.state,health:i?.health,pid:i?.pid,last_error:i?.last_error,command:n.command.args,cwd:n.working_dir,ports:i?.ports??Object.fromEntries(n.ports.map((a)=>[a.name,a.value])),environment:s.redactMap({...n.environment.defaults,...n.environment.vars}),container:n.container?{...n.container,env:s.redactMap(n.container.env)}:void 0}}function _v(e){return{session_id:e.session_id,repo_root:e.repo_root,profile:e.profile,setup_mode:e.setup_mode===!0?!0:void 0,identity:{user:e.identity.user,project:e.identity.project,adc:e.identity.adc,iap:e.identity.iap,service_accounts:e.identity.service_accounts,service_account_status:e.identity.service_account_status},proxy:{running:e.proxy.running,address:e.proxy.address,routes:e.proxy.routes},logs:e.logs,mcp:e.mcp?{running:e.mcp.running,address:e.mcp.address,port:e.mcp.port}:{running:!1}}}async function Ev(e,t){let r=typeof t.service==="string"?t.service:"",n=typeof t.since==="string"?t.since:"",o=typeof t.cursor==="string"?t.cursor:void 0,i=await e.logsPage({services:r===""?[]:[r],level:typeof t.level==="string"?t.level:"",search:typeof t.search==="string"?t.search:"",source:typeof t.source==="string"?t.source:"",since:n,until:typeof t.until==="string"?t.until:"",cursor:o,direction:o?"forward":void 0,limit:oA}),s=is(e.config());return{events:i.events.map((a)=>({timestamp:a.timestamp,service:a.service,source:a.source,level:a.level,message:s.redactText(a.message),pid:a.pid})),truncated:i.hasPrev,has_more:i.hasNext,next_since:i.events[i.events.length-1]?.timestamp??n,next_cursor:i.nextCursor,session_changed:i.sessionChanged}}function sA(e){return Object.entries(e.profiles).sort(([t],[r])=>t.localeCompare(r)).map(([t,r])=>({name:t,services:r.services}))}function Tv(e){let t=Object.entries(e.services).map(([r,n])=>({name:r,description:n.description,command:n.command.args,cwd:n.working_dir,ports:n.ports.map((o)=>({name:o.name,value:o.auto?0:o.value,auto:o.auto})),dependencies:n.dependencies,container:n.container?{image:n.container.image,runtime:n.container.runtime||"docker",ports:n.container.ports,volumes:n.container.volumes}:void 0}));return{project:e.project.name,config_path:e.configPath,repo_root:e.repoRoot,services:t,proxy:{enabled:e.proxy.enabled,listen:{host:e.proxy.listen.host,port:e.proxy.listen.port},routes:e.proxy.routes.map((r)=>({name:r.name,match:r.match,upstream:r.upstream.url,auth:r.auth.type}))}}}function aA(e){let t=is(e);return{entries:Cn(e).map((r)=>{let n=typeof r.value==="string"?r.value:JSON.stringify(r.value);return{...r,value:t.redactMap({[r.path]:n})[r.path]}})}}function vc(e){if(!Array.isArray(e))return[];return e.filter((t)=>typeof t==="string")}async function Av(e,t,r){switch(t){case"list_services":return Pv(e.status());case"get_service":if(typeof r.name!=="string"||r.name==="")throw Error("name is required");return iA(e,r.name);case"get_status":return _v(e.status());case"get_logs":return Ev(e,r);case"list_profiles":return sA(e.config());case"get_config":return Tv(e.config());case"get_config_sources":return aA(e.config());case"run_doctor":return e.doctor();case"start_services":return e.start({services:vc(r.services),profile:typeof r.profile==="string"&&r.profile!==""?r.profile:void 0});case"stop_services":return await e.stop(vc(r.services)),{ok:!0};case"restart_services":return await e.restart(vc(r.services),r.cascade===!0),{ok:!0};case"reload_config":return e.reload();case"run_task":{if(typeof r.name!=="string"||r.name==="")throw Error("name is required");let n=await e.runTask(r.name),o=is(e.config());return{...n,stdout:o.redactText(n.stdout),stderr:o.redactText(n.stderr)}}case"start_proxy":return await e.startProxy(),{ok:!0};case"stop_proxy":return await e.stopProxy(),{ok:!0};case"exec_service":{if(typeof r.service!=="string"||r.service==="")throw Error("service is required");if(!e.exec)throw Error("exec is unavailable");let n=await e.exec(r.service,vc(r.command),r.print_env===!0),o=is(e.config());return{...n,stdout:o.redactText(n.stdout),stderr:o.redactText(n.stderr),environment:n.environment?o.redactMap(n.environment):void 0}}case"get_setup_guide":return cA(r);case"validate_config":return lA(e,r);default:throw Error(`unknown tool ${t}`)}}function cA(e){let t=typeof e.section==="string"?e.section:"procedure",r=t in yc?t:"procedure";return{section:r,text:yc[r],sections:Object.keys(yc)}}function lA(e,t){let r=e.config();if(typeof t.text==="string"){let o=e.validateConfigText(t.text);return{valid:o.length===0,issues:o,source:"candidate",config_path:r.configPath}}if(!rA(r.configPath))return{valid:!1,issues:[`no configuration at ${r.configPath}`],source:"disk",config_path:r.configPath,setup_mode:!0};let n=e.validateConfigText(nA(r.configPath,"utf8"));return{valid:n.length===0,issues:n,source:"disk",config_path:r.configPath}}function Lv(e){return Xu.includes(e)}async function Dv(e,t){switch(t){case"devctl://status":return _v(e.status());case"devctl://services":return Pv(e.status());case"devctl://logs":return Ev(e,{});case"devctl://config":return Tv(e.config());case"devctl://doctor":return e.doctor();default:throw Error(`unknown resource ${t}`)}}var oA=200,Xu,Qu,xc;var ss=D(()=>{ti();Cv();Xu=["devctl://status","devctl://services","devctl://logs","devctl://config","devctl://doctor"],Qu=["inspect","logs","diagnostics","control","setup"],xc=[{name:"list_services",label:"List services",summary:"Name, state, health, ports, pid",category:"inspect",description:"List services with state, health, ports, pid, and last error",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"get_service",label:"Service detail",summary:"One service with command, cwd, ports",category:"inspect",description:"One service plus command, cwd, and ports. Environment values are redacted.",inputSchema:{type:"object",properties:{name:{type:"string"}},required:["name"],additionalProperties:!1}},{name:"get_status",label:"Status",summary:"Profile, identity, proxy, log counts",category:"inspect",description:"Profile, session, identity flags (no tokens), proxy, and log counts",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"get_logs",label:"Read logs",summary:"Filtered log pages, secrets redacted",category:"logs",description:"Recent log lines, optionally filtered. Capped at 200 events per page. Secrets are redacted. Pass cursor=next_cursor to page forward with no duplicate or same-millisecond-lost events; since/until are plain timestamp filters for a fresh query, not a follow cursor.",inputSchema:{type:"object",properties:{service:{type:"string"},level:{type:"string"},search:{type:"string"},source:{type:"string"},since:{type:"string",description:"Only events at or after this timestamp"},until:{type:"string",description:"Only events at or before this timestamp"},cursor:{type:"string",description:"Opaque cursor from a previous response's next_cursor; continues forward from exactly there"}},additionalProperties:!1}},{name:"list_profiles",label:"List profiles",summary:"Configured profiles and their members",category:"inspect",description:"Configured profiles and their member services",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"get_config",label:"Read config",summary:"Merged project summary, no secret values",category:"inspect",description:"Merged project summary: services, routes, and proxy listen paths. No secret env values.",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"get_config_sources",label:"Config sources",summary:"Winning and shadowed config sources",category:"inspect",description:"List effective configuration values with their winning source/layer and any shadowed sources. Secret-like values are redacted.",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"run_doctor",label:"Run doctor",summary:"Environment diagnostics",category:"diagnostics",description:"Run environment diagnostics",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"start_services",label:"Start services",summary:"Start named services or a profile",category:"control",mutates:!0,description:"Start named services, or a profile when names are omitted. Empty start uses profile, then the active session profile, then the first configured profile \u2014 never every service.",inputSchema:{type:"object",properties:{services:{type:"array",items:{type:"string"}},profile:{type:"string",description:"Profile to start when services is omitted"}},additionalProperties:!1}},{name:"stop_services",label:"Stop services",summary:"Stop services and their dependents",category:"control",mutates:!0,description:"Stop named services, or all started services when names are omitted. Also stops every service that transitively depends on a named one (never a named service's own dependencies, which other running services may still need).",inputSchema:{type:"object",properties:{services:{type:"array",items:{type:"string"}}},additionalProperties:!1}},{name:"restart_services",label:"Restart services",summary:"Restart services, optionally cascading",category:"control",mutates:!0,description:"Restart named services. By default this touches only the named services, not anything that depends on them. Pass cascade=true to also restart their transitive dependents (the same set stop_services would affect).",inputSchema:{type:"object",properties:{services:{type:"array",items:{type:"string"}},cascade:{type:"boolean",description:"Also restart transitive dependents; default false restarts only the named services"}},additionalProperties:!1}},{name:"reload_config",label:"Reload config",summary:"Re-read .devctl configuration",category:"control",mutates:!0,description:"Reload .devctl configuration",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"run_task",label:"Run task",summary:"Run a named one-off task",category:"control",mutates:!0,description:"Run a named task from configuration. Output is also written to the log ring as task:<name>.",inputSchema:{type:"object",properties:{name:{type:"string"}},required:["name"],additionalProperties:!1}},{name:"start_proxy",label:"Start proxy",summary:"Start the local reverse proxy",category:"control",mutates:!0,description:"Start the local reverse proxy. Same as CLI `devctl proxy start` and TUI start on the proxy screen.",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"stop_proxy",label:"Stop proxy",summary:"Stop the local reverse proxy",category:"control",mutates:!0,description:"Stop the local reverse proxy and leave it suppressed until start_proxy (or CLI/TUI start) runs again.",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"exec_service",label:"Execute in service",summary:"Run a command in a resolved service context",category:"control",mutates:!0,description:"Run an arbitrary command with a service's fully resolved environment and working directory, whether or not it is running. Output and print_env values are redacted.",inputSchema:{type:"object",properties:{service:{type:"string"},command:{type:"array",items:{type:"string"}},print_env:{type:"boolean",description:"Return the resolved environment without executing a command"}},required:["service"],additionalProperties:!1}},{name:"get_setup_guide",label:"Setup guide",summary:"How to author a .devctl for this repo",category:"setup",description:"The devctl onboarding guide: how to survey a repository and author a .devctl configuration for it. Read section=procedure first; read section=authoring BEFORE writing any YAML (it carries the rules the loader rejects on, which the JSON Schema does not state); read section=discovery for mapping compose/package.json/pyproject/Terraform/k8s/.env to services. Write the files with your own editing tools \u2014 this server does not write them \u2014 then check your work with validate_config.",inputSchema:{type:"object",properties:{section:{type:"string",enum:["procedure","authoring","discovery"],description:"Which part of the guide to return; defaults to procedure"}},additionalProperties:!1}},{name:"validate_config",label:"Validate config",summary:"Check configuration or a draft for errors",category:"setup",description:"Validate devctl configuration and return the exact issues the loader would report. With no arguments, validates what is on disk. Pass text to validate a candidate config.yaml before writing it \u2014 the candidate is run through the real load pipeline (modular services/profiles, overlays, templates), so it works even when no configuration exists yet. This is the only way to validate over MCP; there is no CLI round-trip needed.",inputSchema:{type:"object",properties:{text:{type:"string",description:"Candidate config.yaml contents to validate instead of the file on disk"}},additionalProperties:!1}}]});function Hv(e){return{...e,runDoctor:(t,r,n)=>e.runDoctor.execute(t,r,n),startupPlan:(t,r,n)=>e.getStartupPlan.execute(t,r,n),shutdownPlan:(t,r)=>e.getShutdownPlan.execute(t,r),shutdownPlanExact:(t,r)=>Xa(t,r),resolveStartRequest:(t,r)=>e.resolveStart.execute(t,{...r,profile:r.profile?ur(r.profile):void 0,activeProfile:r.activeProfile===void 0?void 0:ur(r.activeProfile)})}}var Bv=D(()=>{Tt()});function Sc(e){let t=e.toLowerCase().trim(),r=SA[t];if(r)return r;return St.includes(t)?t:"devctl"}function kA(){if(process.env.DEVCTL_THEME_LIGHT==="1")return!0;if(process.env.DEVCTL_THEME_LIGHT==="0")return!1;if(process.platform==="darwin")try{let t=Bun.spawnSync(["defaults","read","-g","AppleInterfaceStyle"],{stdout:"pipe",stderr:"pipe"}),r=t.stdout.toString();if(t.exitCode===0&&r.toLowerCase().includes("dark"))return!1;if(t.exitCode!==0)return!0}catch{}let e=process.env.COLORFGBG??"";if(e.includes(";15")||e.endsWith(";7"))return!0;if(e.includes(";0")||e.endsWith(";8"))return!1;return!1}function Wv(e){let t=Sc(e);if(t==="system"){if(kA())return{...ep["solarized-light"],name:"system"};return ep.system}return ep[t]}function gr(e){switch(e.toUpperCase()){case"HEALTHY":case"OK":return"\u2713";case"UNHEALTHY":case"WARNING":case"WARN":return"!";case"FAILED":case"ERROR":return"\u2717";case"STOPPED":case"UNKNOWN":return"\u25CB";case"RUNNING":case"STARTING":case"RESTARTING":return"\u25CF";default:return"\u25CB"}}function uo(e,t){return us(e)>=ds?kc:t}function us(e){let t=e.startsWith("#")?e.slice(1):e,r=Number.parseInt(t,DA);if(Number.isNaN(r))return 0;let n=r>>MA&tp,o=r>>OA&tp,i=r&tp;return(n*EA+o*TA+i*AA)/LA}function IA(e){let t=PA;for(let r=0;r<e.length;r+=1)t^=e.charCodeAt(r),t=Math.imul(t,_A);return t>>>0}function Cc(e){return us(e.background)>=ds}function np(e){if(!e||!/^#[0-9a-f]{6}$/i.test(e))return!1;return us(e)<ds}function Rc(e,t){let r=$A[e];return Cc(t)?r.light:r.dark}function jv(e,t){return us(Rc(e,t))>=ds?FA:NA}function Ze(e,t){if(e===""||e==="all")return t.muted;let r=us(t.background)>=ds?RA:CA;return r[IA(e)%r.length]??t.info}function HA(e,t){switch(t.toUpperCase()){case"ERROR":case"FATAL":return e.error;case"WARN":case"WARNING":return e.warning;case"INFO":return e.text;case"DEBUG":case"TRACE":return e.muted;default:return e.text}}function Gv(e,t,r){if(r==="search")return e.search;if(r==="string")return e.accent;if(r==="number")return e.info;if(r==="keyword")return e.error;return HA(e,t)}function et(e,t){switch(t.toUpperCase()){case"HEALTHY":case"OK":return e.success;case"UNHEALTHY":case"WARNING":case"WARN":return e.warning;case"FAILED":case"ERROR":return e.error;case"RUNNING":case"STARTING":case"RESTARTING":return e.primary;case"INFO":return e.info;case"FATAL":return e.error;case"DEBUG":case"TRACE":case"UNKNOWN":return e.muted;default:return e.muted}}var St,rp,ep,SA,CA,RA,PA=2166136261,_A=16777619,EA=299,TA=587,AA=114,LA=1000,ds=160,DA=16,tp=255,MA=16,OA=8,kc="#1A1A1A",$A,FA="#1A1A1A",NA="#F5F5F5";var tt=D(()=>{St=["devctl","ember","tokyonight","catppuccin","catppuccin-latte","nord","gruvbox","kanagawa","dracula","onedark","monokai","solarized-dark","solarized-light","rose-pine","everforest","github-dark","iceberg","ayu-dark","oxocarbon","night-owl","terminal","voltage","voltage-light","ironclad","ironclad-light","signal","signal-light","system"],rp={devctl:"amber + cyan, product theme",ember:"warm orange",tokyonight:"Tokyo Night storm blues",catppuccin:"Catppuccin Mocha","catppuccin-latte":"Catppuccin Latte (light)",nord:"Arctic Nord frost",gruvbox:"Gruvbox dark hard",kanagawa:"Kanagawa wave / gold",dracula:"classic Dracula purple",onedark:"Atom / VS Code One Dark",monokai:"Monokai Sublime","solarized-dark":"Solarized dark","solarized-light":"Solarized light","rose-pine":"Ros\xE9 Pine moon",everforest:"Everforest dark medium","github-dark":"GitHub dimmed canvas",iceberg:"Iceberg navy","ayu-dark":"Ayu Mirage-adjacent dark",oxocarbon:"IBM Oxocarbon","night-owl":"Sarah Drasner Night Owl",terminal:"black + VGA ANSI chrome",voltage:"max contrast, electric neon (dark)","voltage-light":"max contrast, electric neon (light)",ironclad:"max contrast, steel & amber (dark)","ironclad-light":"max contrast, steel & amber (light)",signal:"max contrast, traffic-light semantics (dark)","signal-light":"max contrast, traffic-light semantics (light)",system:"neutral dark (system default)"},ep={tokyonight:{name:"tokyonight",primary:"#82AAFF",accent:"#C792EA",error:"#FF5370",warning:"#FFCB6B",search:"#FFCB6B",success:"#C3E88D",info:"#89DDFF",text:"#F4F7FF",muted:"#A8B2CC",background:"#0F111A",panel:"#1A1B2E",element:"#24283B",highlight:"#2E3350",inverse:"#0F111A",border:"#3D4466",borderActive:"#82AAFF"},catppuccin:{name:"catppuccin",primary:"#89B4FA",accent:"#F5C2E7",error:"#F38BA8",warning:"#F9E2AF",search:"#F9E2AF",success:"#A6E3A1",info:"#89DCEB",text:"#E4E8F7",muted:"#A6A9BE",background:"#11111B",panel:"#1E1E2E",element:"#313244",highlight:"#45475A",inverse:"#11111B",border:"#585B70",borderActive:"#89B4FA"},"catppuccin-latte":{name:"catppuccin-latte",primary:"#1E66F5",accent:"#EA76CB",error:"#D20F39",warning:"#DF8E1D",search:"#DF8E1D",success:"#40A02B",info:"#179299",text:"#4C4F69",muted:"#6C6F85",background:"#EFF1F5",panel:"#E6E9EF",element:"#DCE0E8",highlight:"#CCD0DA",inverse:"#EFF1F5",border:"#9CA0B0",borderActive:"#1E66F5"},nord:{name:"nord",primary:"#88C0D0",accent:"#B48EAD",error:"#BF616A",warning:"#EBCB8B",search:"#EBCB8B",success:"#A3BE8C",info:"#8FBCBB",text:"#F2F5FA",muted:"#A4AEBF",background:"#2E3440",panel:"#3B4252",element:"#434C5E",highlight:"#4C566A",inverse:"#2E3440",border:"#5E6A7D",borderActive:"#88C0D0"},gruvbox:{name:"gruvbox",primary:"#FE8019",accent:"#FABD2F",error:"#FB4934",warning:"#FE8019",search:"#FE8019",success:"#B8BB26",info:"#8EC07C",text:"#F2E5BC",muted:"#C4B394",background:"#1D2021",panel:"#282828",element:"#3C3836",highlight:"#504945",inverse:"#1D2021",border:"#665C54",borderActive:"#FE8019"},kanagawa:{name:"kanagawa",primary:"#7FB4CA",accent:"#E6C384",error:"#E82424",warning:"#FFA066",search:"#FFA066",success:"#98BB6C",info:"#7FB4CA",text:"#E8E4C9",muted:"#B4C0D6",background:"#16161D",panel:"#1F1F28",element:"#2A2A37",highlight:"#363646",inverse:"#16161D",border:"#54546D",borderActive:"#E6C384"},dracula:{name:"dracula",primary:"#BD93F9",accent:"#FF79C6",error:"#FF5555",warning:"#F1FA8C",search:"#F1FA8C",success:"#50FA7B",info:"#8BE9FD",text:"#F8F8F2",muted:"#8B9DC9",background:"#21222C",panel:"#282A36",element:"#343746",highlight:"#44475A",inverse:"#21222C",border:"#6272A4",borderActive:"#BD93F9"},onedark:{name:"onedark",primary:"#61AFEF",accent:"#C678DD",error:"#E06C75",warning:"#E5C07B",search:"#E5C07B",success:"#98C379",info:"#56B6C2",text:"#ABB2BF",muted:"#9DA5B4",background:"#21252B",panel:"#282C34",element:"#2C323C",highlight:"#3E4451",inverse:"#21252B",border:"#4B5263",borderActive:"#61AFEF"},monokai:{name:"monokai",primary:"#66D9EF",accent:"#AE81FF",error:"#F92672",warning:"#E6DB74",search:"#E6DB74",success:"#A6E22E",info:"#66D9EF",text:"#F8F8F2",muted:"#A39E80",background:"#1D1E19",panel:"#272822",element:"#3E3D32",highlight:"#49483E",inverse:"#1D1E19",border:"#75715E",borderActive:"#F92672"},"solarized-dark":{name:"solarized-dark",primary:"#268BD2",accent:"#6C71C4",error:"#DC322F",warning:"#B58900",search:"#B58900",success:"#859900",info:"#2AA198",text:"#93A1A1",muted:"#586E75",background:"#002B36",panel:"#073642",element:"#0A4A58",highlight:"#1A5A68",inverse:"#002B36",border:"#586E75",borderActive:"#268BD2"},"solarized-light":{name:"solarized-light",primary:"#268BD2",accent:"#D33682",error:"#DC322F",warning:"#CB4B16",search:"#CB4B16",success:"#859900",info:"#2AA198",text:"#586E75",muted:"#839496",background:"#FDF6E3",panel:"#EEE8D5",element:"#E4DCC8",highlight:"#D5CDB6",inverse:"#FDF6E3",border:"#93A1A1",borderActive:"#268BD2"},"rose-pine":{name:"rose-pine",primary:"#C4A7E7",accent:"#EBBCBA",error:"#EB6F92",warning:"#F6C177",search:"#F6C177",success:"#31748F",info:"#9CCFD8",text:"#E0DEF4",muted:"#908CAA",background:"#191724",panel:"#1F1D2E",element:"#26233A",highlight:"#403D52",inverse:"#191724",border:"#6E6A86",borderActive:"#C4A7E7"},everforest:{name:"everforest",primary:"#7FBBB3",accent:"#D699B6",error:"#E67E80",warning:"#DBBC7F",search:"#DBBC7F",success:"#A7C080",info:"#83C092",text:"#D3C6AA",muted:"#9DA9A0",background:"#232A2E",panel:"#2D353B",element:"#343F44",highlight:"#3D484D",inverse:"#232A2E",border:"#4F585E",borderActive:"#A7C080"},"github-dark":{name:"github-dark",primary:"#58A6FF",accent:"#A371F7",error:"#F85149",warning:"#D29922",search:"#D29922",success:"#3FB950",info:"#79C0FF",text:"#E6EDF3",muted:"#8B949E",background:"#0D1117",panel:"#161B22",element:"#21262D",highlight:"#30363D",inverse:"#0D1117",border:"#30363D",borderActive:"#58A6FF"},iceberg:{name:"iceberg",primary:"#84A0C6",accent:"#A093C7",error:"#E27878",warning:"#E2A478",search:"#E2A478",success:"#B4BE82",info:"#89B8C2",text:"#C6C8D1",muted:"#9AA0B8",background:"#161821",panel:"#1E2132",element:"#2A3150",highlight:"#3D425B",inverse:"#161821",border:"#444B71",borderActive:"#84A0C6"},"ayu-dark":{name:"ayu-dark",primary:"#59C2FF",accent:"#FFB454",error:"#D95757",warning:"#FF8F40",search:"#FF8F40",success:"#C2D94C",info:"#95E6CB",text:"#B3B1AD",muted:"#8B939C",background:"#0A0E14",panel:"#0D1017",element:"#131721",highlight:"#1B212C",inverse:"#0A0E14",border:"#2D3640",borderActive:"#FFB454"},oxocarbon:{name:"oxocarbon",primary:"#78A9FF",accent:"#EE5396",error:"#FA4D56",warning:"#F1C21B",search:"#F1C21B",success:"#42BE65",info:"#3DDBD9",text:"#F2F4F8",muted:"#A8A8A8",background:"#161616",panel:"#262626",element:"#393939",highlight:"#525252",inverse:"#161616",border:"#6F6F6F",borderActive:"#78A9FF"},"night-owl":{name:"night-owl",primary:"#82AAFF",accent:"#C792EA",error:"#EF5350",warning:"#ECC48D",search:"#ECC48D",success:"#ADDB67",info:"#7FDBCA",text:"#D6DEEB",muted:"#8FA4A4",background:"#011627",panel:"#0B2942",element:"#1D3B53",highlight:"#1E4B6B",inverse:"#011627",border:"#5F7E97",borderActive:"#82AAFF"},terminal:{name:"terminal",primary:"#55FFFF",accent:"#FF55FF",error:"#FF5555",warning:"#FFFF55",search:"#FFFF55",success:"#55FF55",info:"#5555FF",text:"#C0C0C0",muted:"#808080",background:"#000000",panel:"#000000",element:"#000000",highlight:"#404040",inverse:"#000000",border:"#555555",borderActive:"#55FFFF"},voltage:{name:"voltage",primary:"#00E5FF",accent:"#FF3EA5",error:"#FF1744",warning:"#FFEA00",search:"#FFEA00",success:"#00E676",info:"#40C4FF",text:"#FFFFFF",muted:"#C0C0C0",background:"#000000",panel:"#000000",element:"#0D0D0D",highlight:"#262626",inverse:"#000000",border:"#808080",borderActive:"#FFFFFF"},"voltage-light":{name:"voltage-light",primary:"#0057B7",accent:"#C2185B",error:"#D50000",warning:"#FF6D00",search:"#FF6D00",success:"#2E7D32",info:"#00838F",text:"#000000",muted:"#424242",background:"#FFFFFF",panel:"#FFFFFF",element:"#EDEDED",highlight:"#D6D6D6",inverse:"#FFFFFF",border:"#616161",borderActive:"#000000"},ironclad:{name:"ironclad",primary:"#4FC3F7",accent:"#FFD54F",error:"#FF5252",warning:"#FFB300",search:"#FFB300",success:"#69F0AE",info:"#64B5F6",text:"#FFFFFF",muted:"#BDBDBD",background:"#000000",panel:"#000000",element:"#101010",highlight:"#242424",inverse:"#000000",border:"#757575",borderActive:"#4FC3F7"},"ironclad-light":{name:"ironclad-light",primary:"#01579B",accent:"#E65100",error:"#B71C1C",warning:"#EF6C00",search:"#EF6C00",success:"#1B5E20",info:"#0277BD",text:"#000000",muted:"#4A4A4A",background:"#FFFFFF",panel:"#FFFFFF",element:"#EFEFEF",highlight:"#DADADA",inverse:"#FFFFFF",border:"#6E6E6E",borderActive:"#01579B"},signal:{name:"signal",primary:"#29B6F6",accent:"#AB47BC",error:"#FF0000",warning:"#FFC400",search:"#FFC400",success:"#00E400",info:"#00B8D4",text:"#FFFFFF",muted:"#B3B3B3",background:"#000000",panel:"#000000",element:"#0A0A0A",highlight:"#202020",inverse:"#000000",border:"#8A8A8A",borderActive:"#29B6F6"},"signal-light":{name:"signal-light",primary:"#01579B",accent:"#6A1B9A",error:"#C62828",warning:"#F9A825",search:"#F9A825",success:"#2E7D32",info:"#00796B",text:"#000000",muted:"#3D3D3D",background:"#FFFFFF",panel:"#FFFFFF",element:"#ECECEC",highlight:"#D2D2D2",inverse:"#FFFFFF",border:"#5C5C5C",borderActive:"#01579B"},system:{name:"system",primary:"#60A5FA",accent:"#C084FC",error:"#F87171",warning:"#FBBF24",search:"#FBBF24",success:"#34D399",info:"#22D3EE",text:"#F9FAFB",muted:"#C4CAD3",background:"#030712",panel:"#111827",element:"#1F2937",highlight:"#374151",inverse:"#030712",border:"#4B5563",borderActive:"#60A5FA"},devctl:{name:"devctl",primary:"#FFB020",accent:"#3DE0FF",error:"#FF5C7A",warning:"#FFD166",search:"#FFD166",success:"#3DFF9A",info:"#3DE0FF",text:"#F7FAFD",muted:"#B7C3D0",background:"#070B10",panel:"#121A22",element:"#1C2833",highlight:"#243444",inverse:"#0A1016",border:"#334556",borderActive:"#FFB020"},ember:{name:"ember",primary:"#FF9F43",accent:"#2EE6FF",error:"#FF4D6D",warning:"#FFE566",search:"#FFE566",success:"#3DFF9A",info:"#2EE6FF",text:"#F7F9FF",muted:"#B0B8CC",background:"#07090F",panel:"#12161F",element:"#1A2130",highlight:"#243044",inverse:"#0B0D12",border:"#334155",borderActive:"#FF9F43"}},SA={opencode:"ember",light:"system",mocha:"catppuccin",latte:"catppuccin-latte","one-dark":"onedark","one dark":"onedark",solarized:"solarized-dark",rosepine:"rose-pine","rose pine":"rose-pine",github:"github-dark",ayu:"ayu-dark","night owl":"night-owl",ansi:"terminal",xterm:"terminal",console:"terminal",classic:"terminal",vga:"terminal",contrast:"voltage","contrast-light":"voltage-light",hc:"voltage","hc-light":"voltage-light",a11y:"voltage","a11y-light":"voltage-light"};CA=["#7AA2F7","#9ECE6A","#E0AF68","#BB9AF7","#2AC3DE","#FF9E64","#F7768E","#73DACA","#C0CAF5","#FF79C6","#7DCFFF","#C3E88D"],RA=["#2E5AAC","#3D7A1F","#9A6B12","#6B3FA0","#0E7A86","#B85C1A","#B4234A","#1A7A6D","#4A5080","#9A3480","#1565C0","#4A7C19"];$A={claude:{dark:"#E08A6A",light:"#B54328"},cursor:{dark:"#B8D4FF",light:"#1E4EBF"},kilo:{dark:"#F0C94A",light:"#8A6400"},codex:{dark:"#4ADE80",light:"#0B7A4B"}}});var Pr=D(()=>{Js()});function qv(e){return e!==void 0}function Uv(e){let t=e/1000;if(Number.isInteger(t))return`${t}s`;return`${e}ms`}function sp(e,t,r){let n=e.indexOf(t),i=((n<0?0:n)+r+e.length)%e.length;return e[i]??e[0]}function Vv(e,t){let r=[...St],n=r.includes(e)?e:ip;return sp(r,n,t)}function Jv(e,t){let r=[...BA],n=r.reduce((o,i)=>Math.abs(i-e)<Math.abs(o-e)?i:o,r[0]);return sp(r,n,t)}function ps(e){let t=[...op];return t.reduce((r,n)=>Math.abs(n-e)<Math.abs(r-e)?n:r,t[0])}function fs(e,t){return sp([...op],ps(e),t)}function ms(e){let t=ps(e),r=Kv[t]??Kv[Eo];return{px:t,...r}}function po(e){return e.label==="compact"}function gs(e){return e.steps<=2}function zv(e){return op.map((r,n)=>n<e?"\u2588":"\u2591").join(" ")}function Pc(e){return ms(e).label}function Yv(e){let t=e.locked?"Applies this session only. DEVCTL_TUI_CONFIG overrides the user file.":`Saved to ${e.configPath}.`;return[{id:"theme",group:"Appearance",kind:"cycle",name:"Theme",value:e.themeName,hint:"\u2190 \u2192 save enter picker",detail:`Arrows write the theme to the user file. Enter opens the picker. ${t}`},{id:"font",group:"Appearance",kind:"cycle",name:"Display size",value:Pc(e.fontSize),hint:"\u2190 \u2192 save",detail:`Scales content padding and list-row height. Default keeps a thin rule between chrome and panes. Compact sits toolbars flush against borders. Header, nav, and status stay one line. Does not change the terminal font. ${t}`},{id:"mouse",group:"Input",kind:"toggle",name:"Mouse",value:e.mouse?"on":"off",hint:"space or enter toggle",detail:`Clicks on nav and lists. Restart the TUI after a change. ${t}`},{id:"leader",group:"Input",kind:"cycle",name:"Leader",value:Uv(e.leaderMs),hint:"\u2190 \u2192 save",detail:`How long ${Mt("x")} waits for the next key. ${t}`},{id:"mcp",group:"MCP",kind:"page",name:"Settings page",value:e.mcpRunning?"running \u2192 /mcp":"off \u2192 /mcp",hint:"enter open the MCP page",detail:"Opens a dedicated MCP page (also /mcp or /agent). Start or stop the localhost server, change the port, and copy Claude, Cursor, Codex, and Kilo Code config."},{id:"file",group:"About",kind:"info",name:"File",value:e.configPath,hint:"read-only",detail:e.locked?`Using ${e.configPath} from DEVCTL_TUI_CONFIG.`:`User preferences live in ${e.configPath}.`},{id:"version",group:"About",kind:"info",name:"Version",value:Bt,hint:"read-only",detail:er()},{id:"reset",group:"About",kind:"action",name:"Reset",value:"defaults",hint:"enter confirm reset",detail:`Restore theme ${ip}, display ${Pc(Eo)}, mouse on, leader ${Uv(Zl)}. Enter asks first. ${t}`}]}function _c(){return{theme:ip,mouse:!0,leader_timeout:Zl,font_size:Eo}}function Xv(e){return["Appearance","Input","MCP","About"].flatMap((r)=>{let n=e.filter((o)=>o.group===r);if(n.length===0)return[];return[{group:r,items:n}]})}function ap(e,t){if(e.length===0)return 0;return Math.min(Math.max(t,0),e.length-1)}function Tn(e,t){return e[ap(e,t)]}function cp(e,t){return e??t}var BA,op,ip="devctl",Kv;var _r=D(()=>{tt();Pr();BA=[1000,2000,3000],op=[12,14,16,18,20,22];Kv={12:{pad:0,rowH:1,gap:0,steps:1,label:"compact"},14:{pad:0,rowH:1,gap:0,steps:2,label:"default"},16:{pad:1,rowH:1,gap:1,steps:3,label:"comfortable"},18:{pad:1,rowH:2,gap:1,steps:4,label:"large"},20:{pad:2,rowH:2,gap:1,steps:5,label:"xl"},22:{pad:2,rowH:2,gap:2,steps:6,label:"xxl"}}});import{createContext as WA,useContext as jA}from"react";function ve(){return jA(lp)}var lp;var Ot=D(()=>{_r();Pr();lp=WA(ms(Eo))});function _e(e,t){if(t<=0)return"";if(e.length<=t)return e;if(t===1)return"\u2026";return`${e.slice(0,t-1)}\u2026`}function oe(e,t){if(t<=0)return"";if(e.length>=t)return _e(e,t);return e.padEnd(t)}function ni(e,t=20){let r=Number.isFinite(e)?Math.min(1,Math.max(0,e)):0,n=Math.round(r*t);return"\u2588".repeat(n)+"\u2591".repeat(Math.max(0,t-n))}function hs(e){if(!Number.isFinite(e)||e<dp)return"< 1s";let t=Math.floor(e/Qv),r=Math.floor(e%Qv/pp),n=Math.floor(e%pp/up),o=Math.floor(e%up/dp);if(t>0)return`${t}d ${r}h`;if(r>0)return`${r}h ${n}m`;if(n>0)return`${n}m ${String(o).padStart(2,"0")}s`;return`${o}s`}function ys(e){if(!Number.isFinite(e)||e<0)return"\u2014";let t=e/GA;if(t>=Zv)return`${(t/Zv).toFixed(1)}G`;if(t>=1)return`${Math.round(t)}M`;return`${Math.round(e)}K`}function ex(e){if(!Number.isFinite(e)||e<0)return"\u2014";return`${e.toFixed(1)}%`}var dp=1000,up,pp,Qv,GA=1024,Zv=1024;var It=D(()=>{up=60*dp,pp=60*up,Qv=24*pp});function mp(e){return e>=qA}function gp(e){return e>=VA}function hp(e){return e>=JA}function Lc(e,t=0){return Math.max(1,e-rx-t*2)}function nx(e){let t=tx+Er+$t;if(mp(e))t+=Hr;if(gp(e))t+=Tc;if(hp(e))t+=nn;return Math.max(Ac,e-t)}function Dc(e,t,r){if(r)return e;let n=t.reduce((a,c)=>Math.max(a,c.length),0),o=Math.min(fp,Math.max(Ac,n+UA)),i=tx+Er+$t+o+rx+KA,s=Math.max(Ec,Math.floor(e*zA));return Math.min(s,Math.max(Ec,i))}function ox(e){if(!e)return"";return Object.keys(e.profiles).sort()[0]??""}function Mc(e){if(!e)return!0;let t=Object.values(e.services);if(t.length===0)return!0;return!t.some((r)=>mo(r))}function yp(e,t){if(e.length>0)return e;return[...t]}function Oc(e,t){if(e.length>0)return[...e];return t===""?[]:[t]}function Ic(e){return e.command.args.join(" ")||"\u2014"}function ws(e,t){let r=fo(t);if(r!=="")return r;if(e.ports.length===0)return"\u2014";return e.ports.map((n)=>`${n.name}:${n.auto?"auto":n.value}`).join(" ")}function $c(e){let t=e.health.type||"none",r=e.health.url||e.health.address;return r===""?t:`${t} ${r}`}function Fc(e,t){if(t?.identity)return t.identity;let r=e.identity.type||"none",n=e.identity.service_account;return n===""?r:`${r} ${n}`}function Nc(e){let t=e.restart.policy||"none";if(e.restart.max_retries>0)return`${t} \xD7${e.restart.max_retries}`;return t}function ix(e,t,r,n,o){let i={...e.environment.defaults,...e.environment.vars},a=YA(o??i,t,r,n);return[...new Set([...Object.keys(a),...e.environment.required])].map((l)=>({key:l,value:a[l]??"",required:e.environment.required.includes(l),fromConfig:Object.prototype.hasOwnProperty.call(i,l)})).sort((l,d)=>l.fromConfig===d.fromConfig?l.key.localeCompare(d.key):l.fromConfig?-1:1)}function sx(e,t){if(!e||e.processes.length===0)return;if(t!==void 0&&t!==""&&e.session_id===t)return;return e}function fo(e){if(!e)return"";if(e.ports.http!==void 0)return String(e.ports.http);let t=Object.values(e.ports)[0];return t!==void 0?String(t):""}function tr(e){if(!e)return"STOPPED";return Jo(e)}function YA(e,t,r,n){if(t)return e;return new mr(r,n).redactMap(e)}function Ss(e){return XA.has(e)}function mo(e){if(!e)return!1;return Ss(e.state)}var tx=8,Er=12,Hr=10,Tc=8,nn=8,vs=9,xs=7,bs=8,Ac=12,fp=40,$t=2,UA=2,rx=2,KA=2,Ec=34,qA=48,VA=60,JA=72,zA=0.48,XA;var At=D(()=>{Tt();ti();XA=new Set(["RUNNING","STARTING","RESTARTING","HEALTHY","UNHEALTHY"])});function jc(e){return e===aa?"\u2014":e}function vp(e,t,r){let n=r?cx:QA+cx+Math.max(0,t)*2;return Math.max(1,e-n)}function rL(e){if(e.startsWith('"')||e.startsWith("'"))return"string";if(/^\d+$/.test(e))return"number";return"keyword"}function Gc(e){return e.replace(nL,"")}function px(e){try{return JSON.stringify(JSON.parse(e),null,2)}catch{return e}}function Uc(e,t){let r=Math.max(1,t),n=Gc(e).replace(/\r\n/g,`
597
+ `}});var Ny;var Hy=D(()=>{Ny=[{path:"docs/README.md",title:"devctl wiki",body:`# devctl wiki
598
+
599
+ \`devctl\` is a configuration-driven local development orchestrator. It starts and stops services, resolves environment, optional Google identity, an auth-aware proxy, health checks, and centralized logs \u2014 from a keyboard-first TUI, a CLI, or an optional MCP server for coding agents.
600
+
601
+ The running product is TypeScript on [Bun](https://bun.sh) with an [OpenTUI](https://opentui.com/docs/) interface. There is no Go binary.
602
+
603
+ ## Start here
604
+
605
+ | Page | What you get |
606
+ |------|----------------|
607
+ | [How it fits together](overview.md) | Supervisor, TUI, CLI, MCP, and what lives on disk |
608
+ | [Installation](installation.md) | npm, release binaries, source install, optional \`gcloud\` |
609
+ | [Quick start](quickstart.md) | First session: setup \u2192 doctor \u2192 TUI |
610
+ | [Developer setup](developer-setup.md) | Day-to-day loop without admin privileges |
611
+ | [Demo platform](../examples/demo-platform/README.md) | Local invoicing example (no Google Cloud; opt-in Docker \`data\` profile) |
612
+ | [Agent skills](../skills/README.md) | Onboard a repo: survey what it runs and author \`.devctl/\` |
613
+
614
+ ## Using it
615
+
616
+ | Page | Side |
617
+ |------|------|
618
+ | [TUI](tui.md) | Screens, keys, slash commands, themes, settings |
619
+ | [CLI](cli.md) | Commands, flags, exit codes, attach vs start |
620
+ | [MCP](mcp.md) | Localhost Streamable HTTP for Claude, Cursor, Codex, Kilo |
621
+ | [Logs](logs.md) | Buffer, filters, export, history |
622
+ | [Doctor](doctor.md) | Environment and Google diagnostics |
623
+ | [Troubleshooting](troubleshooting.md) | Symptom \u2192 fix |
624
+
625
+ ## Configuration
626
+
627
+ | Page | Side |
628
+ |------|------|
629
+ | [Configuration](configuration.md) | Discovery, merge, validation, JSON Schema, reload |
630
+ | [Services](services.md) | Commands, ports, health, restart, dependencies |
631
+ | [Profiles](profiles.md) | Named sets, session recovery |
632
+ | [Environment](environment.md) | Source order, \`\${\u2026}\` refs, secrets |
633
+ | [Plugins](plugins.md) | SDK contract, extension points, generic OIDC provider |
634
+
635
+ ## Identity and proxy
636
+
637
+ | Page | Side |
638
+ |------|------|
639
+ | [Authentication](authentication.md) | ADC, project source, \`devctl auth\` |
640
+ | [Impersonation](impersonation.md) | Service-account tokens without keys |
641
+ | [IAP](iap.md) | Audience, user vs SA identity tokens |
642
+ | [Proxy](proxy.md) | Loopback routes, token endpoint |
643
+ | [Admin setup](admin-setup.md) | IAM and APIs administrators own |
644
+ | [Security](security.md) | Redaction, bind rules, credential files |
645
+
646
+ ## Reference
647
+
648
+ | Page | Side |
649
+ |------|------|
650
+ | [Building from source](typescript.md) | \`app/\` layout, tests, TUI config file |
651
+ | [Architecture](architecture.md) | Layers, composition roots, import rules |
652
+ | [Platform bets](platform-bets.md) | Remote/multi-repo/k8s/OIDC/signing \u2014 design separately |
653
+ | [Contributing](../CONTRIBUTING.md) | Source loop, tests, and where to edit docs |
654
+ | [npm publishing](npm-publishing.md) | Maintainer bootstrap, trusted publishing, and release trust |
655
+ | [Architecture spec](devctl-architecture.md) | Original implementation specification (historical). Do not treat the \`cmd/\` tree there as the current repo. |
656
+ | [License](../LICENSE) | MIT |
657
+ | [Security policy](../SECURITY.md) | How to report a vulnerability |
658
+
659
+ Configuration never hard-codes service names, ports, or service accounts. Those belong in \`.devctl/\`.
660
+ `},{path:"docs/admin-setup.md",title:"Google Cloud administrator setup",body:'# Google Cloud administrator setup\n\nAdministrators own cloud access. Developers never need project-wide admin.\n\n1. Create development service accounts (for example `worker-dev@PROJECT.iam.gserviceaccount.com`).\n2. Enable APIs as needed: IAM Credentials, Cloud Resource Manager, Identity-Aware Proxy. Do not expect `devctl` to enable APIs \u2014 Doctor reports, never auto-enables.\n3. Configure IAP on protected backends and record the audience for each route.\n4. Create a Google Group such as `dev-developers@company.com`.\n5. Bind `roles/iam.serviceAccountTokenCreator` **on each development service account** to that group (not project-wide unless policy requires it).\n6. Confirm organization policies allow impersonation and ADC.\n7. Validate with a test developer: `devctl doctor` should report impersonation success.\n\n```mermaid\nflowchart TB\n group["dev-developers@company.com"] --> role["roles/iam.serviceAccountTokenCreator"]\n role --> sa["worker-dev@company-dev.iam.gserviceaccount.com"]\n```\n\n`devctl` source code must not contain those emails; they belong in repository configuration.\n\nOrganization policies that disable service-account impersonation or constrain ADC will make Doctor report UNAVAILABLE even when IAM bindings look correct. Confirm those policies before changing developer machines.\n\nSee [examples/admin-iam.yaml](../examples/admin-iam.yaml) for a permission-distribution sketch (documentation only; not loaded at runtime).\n\n## Related\n\n- [Developer setup](developer-setup.md)\n- [Impersonation](impersonation.md)\n- [IAP](iap.md)\n- [Doctor](doctor.md)\n'},{path:"docs/architecture.md",title:"Architecture",body:`# Architecture
661
+
662
+ \`devctl\` is a modular monolith with ports-and-adapters layering. The process model is unchanged: TUI, CLI, and MCP talk to a long-lived supervisor over a local socket. This page is the living layer map. The older Manager-centric sketch in [devctl-architecture.md](devctl-architecture.md) \xA73\u20134 is historical.
663
+
664
+ ## Layers
665
+
666
+ \`\`\`text
667
+ app/src/
668
+ presentation/ cli, tui, mcp
669
+ application/ commands, queries, orchestrator
670
+ domain/ service, identity, health, config types
671
+ ports/ ProcessRuntime, Clock, FileSystem, HealthChecker, \u2026
672
+ adapters/ daemon, rpc, doctor, environment, plugins, net, secrets, system,
673
+ process, google, config, health, proxy, storage, containers
674
+ shared/ events, errors, retry, warnings
675
+ bootstrap/ one composition root per process
676
+ \`\`\`
677
+
678
+ Dependency direction points inward:
679
+
680
+ \`\`\`text
681
+ presentation \u2192 application, ports, shared, domain types
682
+ application \u2192 domain, ports, shared
683
+ domain \u2192 shared (and other domain)
684
+ ports \u2192 domain, shared
685
+ adapters \u2192 ports, domain, shared
686
+ bootstrap \u2192 everything
687
+ test \u2192 any layer
688
+ \`\`\`
689
+
690
+ Forbidden: domain \u2192 adapters/application/presentation; application \u2192 adapters/presentation; adapters \u2192 presentation/application.
691
+
692
+ ## Composition roots
693
+
694
+ There is exactly one composition root **per process**:
695
+
696
+ - \`bootstrap/daemon.ts\` \u2014 supervisor, orchestrator, adapters, MCP, proxy
697
+ - \`bootstrap/client.ts\` \u2014 CLI/TUI, Controller, offline commands
698
+
699
+ No DI container. Constructor injection only. Bootstrap is allowed to be ugly.
700
+
701
+ ## Commands vs events
702
+
703
+ Commands are explicit calls (\`StartService.execute\`). Events on \`Bus\` are facts that already happened (\`ServiceStarted\`). Do not drive orchestration through the event bus.
704
+
705
+ ## Enforcement
706
+
707
+ \`\`\`bash
708
+ cd app && bun run check:architecture
709
+ \`\`\`
710
+
711
+ CI runs the same script. Domain, application, and ports must not import \`google-auth-library\` or \`@opentui/*\`.
712
+ Presentation may import presentation, application, ports, shared, and domain modules.
713
+ Adapters cannot import presentation, application, or leftover root modules.
714
+ \`*.test.ts\` files are a \`test\` layer and may compose any layer, including
715
+ bootstrap fixtures. The production allowlist is empty: a new forbidden pair
716
+ fails, and a stale exception for a removed import also fails.
717
+
718
+ The checker parses TypeScript syntax, including type imports, re-exports, literal
719
+ dynamic imports, and \`require()\` calls. Comments and example strings do not count
720
+ as dependencies.
721
+
722
+ ## Related
723
+
724
+ - [How it fits together](overview.md)
725
+ - [Building from source](typescript.md)
726
+ - Agent rules: \`.cursor/rules/architecture.mdc\`
727
+
728
+
729
+ ## Migration progress
730
+
731
+ The service launch and health extraction is complete. \`application/orchestrator.ts\`
732
+ owns launch sequencing, hooks, health waits, and stop/restart plans.
733
+ \`application/health-monitor.ts\` owns health probes, lifecycle generations, restart
734
+ timers, and the shared crash/unhealthy retry budget. Process/container launch and
735
+ transient commands use \`ProcessRuntime\`; health probes use \`HealthCheckerFactory\`.
736
+ Supervisor coordinates service lifecycles. RPC accept, line framing, and dispatch
737
+ routing live in \`adapters/rpc/server.ts\` (paired with the existing controller
738
+ client). Identity cache, credential entries, and service-account probes live in
739
+ \`adapters/daemon/identity-coordinator.ts\`. Client/profile environment resolution
740
+ lives in \`adapters/daemon/environment-bridge.ts\`. Proxy and token-endpoint bind
741
+ live in \`adapters/daemon/proxy-coordinator.ts\`. MCP listen and the tool deny-list
742
+ live in \`adapters/daemon/mcp-coordinator.ts\`. Host CPU/memory sampling lives in
743
+ \`adapters/daemon/resource-sampler.ts\`. Supervisor still owns persistence,
744
+ adoption, config watch, and the host facades that bind those slices.
745
+ Its public start/stop/restart methods continue to delegate to the application.
746
+
747
+ The legacy daemon, controller, doctor, environment, plugin registry, network-port,
748
+ secret-detector, and host-stat modules and their tests now live under \`adapters/\`.
749
+ The setup command and its tests live under \`presentation/cli/\`. \`plugin-sdk.ts\`
750
+ and \`bin.ts\` retain their public paths. Daemon command and MCP composition live
751
+ in \`bootstrap/daemon.ts\` and \`bootstrap/test-supervisor.ts\`, so adapters do not
752
+ import application or presentation. Integration tests compose through those
753
+ fixtures rather than architecture exceptions.
754
+
755
+ The stricter layer rules and doctor boundary are in place. \`RunDoctor\` depends on
756
+ \`ports/doctor-runner.ts\`; the adapter implements it with the existing diagnostics.
757
+ Doctor reports, progress, runtime context, and port-holder data live in \`domain/\`,
758
+ so application code and doctor screens do not import adapter types for these values.
759
+ Production CLI, TUI, and MCP modules now have no adapter or bootstrap import
760
+ exceptions. \`bin.ts\` supplies the client runtime and daemon launcher to the CLI;
761
+ the TUI workspace receives that same runtime. The application owns the
762
+ \`ClientRuntime\` and \`Controller\` contracts. Config, Google status, logs, session,
763
+ and preference view types live in domain modules. Preference persistence stays
764
+ in the config adapter, and MCP validation is supplied by its host. Integration
765
+ tests compose through bootstrap fixtures instead of layer exceptions.
766
+
767
+ The TUI decomposition is complete. \`App.tsx\` coordinates screen rendering and
768
+ state wiring. Hooks under \`presentation/tui/hooks/\` own log filtering/windowing
769
+ and paged queries, daemon event subscriptions, diagnostics, lifecycle commands,
770
+ environment inspection, config editing, MCP controls, preferences, command
771
+ dispatch, and keyboard/overlay dispatch (\`use-app-keyboard.ts\`). They receive
772
+ the client workspace or controller explicitly. Screen helpers live in focused
773
+ modules under \`presentation/tui/helpers/\`; production consumers import those
774
+ modules directly. The old \`helpers.ts\` is a compatibility barrel, also
775
+ exercised by the existing helper tests.
776
+
777
+ Hook regression tests cover stale diagnostic and environment results, pinned log
778
+ windows, failed lifecycle commands, config validation before writes, preference
779
+ preview/override behavior, and rendering App in setup mode.
780
+
781
+ The final dependency/ID phase is complete. Supervisor requires its token and
782
+ process runtimes, clock, filesystem, event bus, Google detector, health-checker
783
+ factory, orchestrator, log store, secret detector, session id, process
784
+ inspect/alive helpers, lock/socket functions, and an MCP listener factory.
785
+ \`createDaemon\` selects production defaults, shares the injected clock and bus,
786
+ builds \`commandsForHost\`, and attaches commands after construct. The explicit
787
+ test fixture mirrors that wiring. Supervisor does not import application or
788
+ presentation modules; it talks to \`DaemonCommands\`, \`LifecycleSession\`,
789
+ \`McpHost\`, and \`McpListener\` ports.
790
+
791
+ \`StartProfile\`, \`ResolveStart\`, and domain profile resolution use \`ProfileId\`.
792
+ Transport and UI entry points convert strings before calling those boundaries;
793
+ RPC/JSON fields stay strings. \`ProcessManager implements ProcessRuntime\` and
794
+ health-checker injection remain in place. Snapshot and RPC payload types live
795
+ in \`domain/status.ts\`; \`types.ts\` is the shared \`Envelope\` only. All six
796
+ phases in the remaining-work plan are complete. The follow-up leftover work
797
+ emptied the architecture allowlist: tests are a first-class layer, daemon
798
+ composition lives in bootstrap, and there are no production import exceptions.
799
+
800
+ Keep RPC names, JSON fields, \`plugin-sdk.ts\`, and \`bin.ts\` stable. Validate each
801
+ phase with \`bun test\`, \`./node_modules/.bin/tsc --noEmit\`,
802
+ \`bun run check:architecture\`, \`bun run check:coverage\`, \`bun run check:dead\`, and \`bun run check:dup\`
803
+ from \`app/\`.
804
+ `},{path:"docs/authentication.md",title:"Authentication",body:`# Authentication
805
+
806
+ Google identity is optional. Local-only services start without \`gcloud\`.
807
+
808
+ \`\`\`bash
809
+ devctl auth status
810
+ devctl auth login
811
+ devctl auth refresh
812
+ devctl auth logout
813
+ \`\`\`
814
+
815
+ \`login\` runs \`gcloud auth application-default login\`. \`devctl\` prefers Application Default Credentials and does not invent a proprietary login protocol.
816
+
817
+ \`--json\` works on \`status\` and \`refresh\`. Refresh output includes identity and \`expires_at\`, never the token.
818
+
819
+ ## Project resolution
820
+
821
+ The TUI Identity screen and \`devctl auth status\` always show the source:
822
+
823
+ \`\`\`mermaid
824
+ flowchart TB
825
+ cfg{"google.project_id in config?"}
826
+ cfg -->|yes| useCfg["Use configuration"]
827
+ cfg -->|no| env{"GOOGLE_CLOUD_PROJECT<br/>or GCLOUD_PROJECT?"}
828
+ env -->|yes| useEnv["Use environment"]
829
+ env -->|no| gcloud{"gcloud config set?"}
830
+ gcloud -->|yes| useGcloud["Use gcloud config"]
831
+ gcloud -->|no| adc["ADC project"]
832
+ \`\`\`
833
+
834
+ ## User vs service identity
835
+
836
+ User identity and service identity are separate. A service or proxy route must declare which one to use. A user ADC token is never substituted for a service-account route.
837
+
838
+ \`devctl auth refresh\` uses \`auth.refresh_threshold_seconds\` (default 300). Tokens live in the OS keychain when available, otherwise \`~/.devctl/credentials\` with mode \`0600\`. Metadata files never include the raw access token.
839
+
840
+ The TUI **identity** tab (\`a\`) shows user, project, source, ADC, gcloud, configured SAs, impersonation availability, and whether IAP routes exist. The **credentials** tab lists store backend and entry names only.
841
+
842
+ IAP routes with a service-account identity impersonate that account and then mint an IAP ID token. See [Impersonation](impersonation.md) and [IAP](iap.md).
843
+
844
+ ## Related
845
+
846
+ - [Impersonation](impersonation.md)
847
+ - [IAP](iap.md)
848
+ - [Doctor](doctor.md)
849
+ - [Developer setup](developer-setup.md)
850
+ `},{path:"docs/cli.md",title:"CLI",body:`# CLI
851
+
852
+ The CLI and the TUI share one supervisor. Global flag: \`--config <path>\` (file or \`.devctl\` directory).
853
+
854
+ \`\`\`text
855
+ devctl # TUI (attaches to a daemon, spawning one if none is running)
856
+ devctl version
857
+ devctl start [svc\u2026] [--profile] [--detach] [--json]
858
+ devctl stop [svc\u2026] [--json]
859
+ devctl restart [svc\u2026] [--cascade] [--json]
860
+ devctl run <task> [--json]
861
+ devctl exec <service> -- <command\u2026>
862
+ devctl exec <service> --print-env [--reveal] [--json]
863
+ devctl down [--repo <path>] [--keep-services]
864
+ devctl status [--repo <path>] [--json] [--watch]
865
+ devctl config import compose <file> [--write]
866
+ devctl logs [svc\u2026] [--level] [--search] [--regex] [--source] [--since] [--until] [--output] [--json] [-f|--follow] [--all]
867
+ devctl logs export --output FILE
868
+ devctl daemon logs [-f|--follow]
869
+ devctl reload
870
+ devctl doctor [--json]
871
+ devctl setup [--force]
872
+ devctl auth status|login|logout|refresh [--json]
873
+ devctl proxy status|start|stop
874
+ devctl mcp [--on|--off] [--port N] [--json]
875
+ devctl config validate|show|diff [--json]
876
+ devctl attach
877
+ devctl completion zsh|bash|fish
878
+ devctl update [--json] [--check]
879
+ \`\`\`
880
+
881
+ \`_supervisor\` is an internal command. Do not invoke it by hand.
882
+
883
+ ## Start, stop, status
884
+
885
+ - \`start\` with \`--profile\` starts that profile\u2019s members (plus dependencies).
886
+ - \`start\` with **no** profile and **no** names uses the active session profile, then the first configured profile (alphabetically). With no profiles it errors instead of starting every service.
887
+ - \`start\` always ensures a daemon and leaves it (and its services) running after the command exits \u2014 that is not conditional on any flag.
888
+ - \`--detach\` is **deprecated**: it predates that always-on daemon and no longer changes behavior. Passing it prints a warning on stderr; it does nothing else.
889
+ - \`start\` exits **5** when a requested service fails to spawn, **6** when it starts but never becomes healthy.
890
+ - \`stop\` with no names stops every started service; naming one or more services also stops everything that (transitively) depends on them \u2014 never their dependencies, which other running services may still need. This is a **breaking change**: stop used to mirror start and pull in dependencies instead.
891
+ - \`restart\` touches only the named services, not their dependents; \`--cascade\` also restarts those dependents (the same set \`stop\` would affect). Either way, start still expands the named services' own dependencies if they aren't already running.
892
+ - \`run\` starts the task's declared service dependencies, executes the transient command once with the resolved project/task environment, and returns its exit status. \`--json\` includes captured stdout and stderr. The TUI equivalent is \`/run <task>\` (output lands in Logs under \`task:<name>\`).
893
+ - \`exec\` runs once in a service's resolved environment and working directory, even when that service is stopped. \`--print-env\` prints the exact environment without running a command; secret-like values are redacted unless \`--reveal\` is explicitly supplied. The TUI equivalents are \`/exec <service> -- <command\u2026>\` and \`/exec <service> --print-env\` (the env inspector shows the same resolved map, not config-only \`vars\`/\`defaults\`).
894
+ - \`down\` stops the daemon's services and the daemon itself; \`--keep-services\` stops only the daemon, leaving services running to be adopted later. \`--repo\` targets a repository directly, without needing a loadable configuration there; the global \`--config\` also resolves it (by file location, not by parsing) when \`--repo\` is not given.
895
+ - \`status\` and \`down\` resolve their target the same way: \`--repo\` wins outright, else the global \`--config\` (or plain discovery from the working directory) locates it by file, else a state-directory scan finds a still-live daemon whose original config is now gone.
896
+ - \`status\` with no socket prints persisted per-repo state (or \u201Cstopped\u201D) and exits **0**.
897
+ - \`status\` also prints proxy and MCP listen lines when a supervisor is up.
898
+ - \`status --watch\` reprints the same status every 2 seconds, each under its own timestamp header, until interrupted (\`ctrl+c\`).
899
+ - \`logs -f\` (and the TUI's own live view) keeps printing new matching events until interrupted instead of exiting after the current page; see [Logs](logs.md) for pagination and filtering details.
900
+ - \`devctl daemon logs [-f]\` prints the detached supervisor's own bootstrap stderr (its log location, before it has a config to start services from) \u2014 useful when \`start\`/\`attach\` reports "supervisor failed to start" and points at a path. Prints "no daemon bootstrap log yet" if the daemon has never been spawned for this repository. \`-f\` follows it live the same way \`logs -f\` does. The TUI equivalent is \`/daemon\`.
901
+
902
+ \`devctl attach\` dials an existing supervisor only. It does not start one. If nothing is listening, it errors with a hint to run \`devctl start\` first.
903
+
904
+ \`devctl reload\` re-reads \`.devctl/\`. Changing the \`plugins\` path list hot-applies token providers and proxy middleware; editing an already-imported plugin file still requires \`devctl down && devctl start\`. Opt-in \`service.watch\` (see [Services](services.md)) restarts that one service when listed paths change.
905
+
906
+ ## Setup
907
+
908
+ \`devctl setup\` honors \`--config\` (the repo/path to set up, not just the one to read). It never overwrites a configuration that already exists there \u2014 it prints the existing path and writes nothing. Pass \`--force\` to overwrite it instead; without a config on disk yet, setup runs its interactive wizard as usual.
909
+
910
+ RPC errors include \`{ error, kind, hint }\` so the CLI maps kinds to the table below.
911
+
912
+ ## Completions
913
+
914
+ \`devctl completion zsh\` (or \`bash\` / \`fish\`) prints a script that calls \`devctl __complete\` for live values (profiles, service names, \`start\` / \`stop\` / \`logs\`).
915
+
916
+ \`\`\`bash
917
+ # zsh \u2014 add to ~/.zshrc
918
+ source <(devctl completion zsh)
919
+
920
+ # bash
921
+ source <(devctl completion bash)
922
+
923
+ # fish
924
+ devctl completion fish > ~/.config/fish/completions/devctl.fish
925
+ \`\`\`
926
+
927
+ \`__complete\` is an internal helper. It loads configuration the same way the CLI does.
928
+
929
+ ## Configuration provenance
930
+
931
+ \`devctl config diff\` lists every explicitly configured effective value, the file and merge layer that won, and any earlier sources it shadowed. \`--json\` returns the same information as \`{ "entries": [...] }\` for automation. The TUI equivalent is \`/diff\` (secret-like values stay redacted unless \`/reveal\` is on).
932
+
933
+ ## Update
934
+
935
+ \`devctl update\` checks the latest GitHub Release, reports which install channel this process is (npm, npx, Homebrew, GitHub Release binary, or source), and **installs** when that channel has a known package-manager command (npm global or Homebrew). \`--json\` and \`--check\` only report. After a successful install, restart a running daemon with \`devctl down\` then start again. \`/update\` in the TUI does the same install; \`/version\` still only checks.
936
+
937
+ ## Exit codes
938
+
939
+ | Code | Meaning |
940
+ |------|---------|
941
+ | 0 | success |
942
+ | 1 | general error |
943
+ | 2 | configuration (also \`devctl doctor\` when any check is not ok) |
944
+ | 3 | authentication |
945
+ | 4 | authorization |
946
+ | 5 | service startup |
947
+ | 6 | health check |
948
+ | 7 | proxy (\`proxy start\` with no listen port, or bind failed) |
949
+
950
+ ## Related
951
+
952
+ - [TUI](tui.md)
953
+ - [MCP](mcp.md)
954
+ - [Logs](logs.md)
955
+ - [How it fits together](overview.md)
956
+ `},{path:"docs/configuration.md",title:"Configuration",body:`# Configuration
957
+
958
+ \`devctl\` is driven by YAML. Unknown fields are rejected. \`version: 1\` is required.
959
+
960
+ Editors and agents: point at the JSON Schema so field names complete.
961
+
962
+ \`\`\`yaml
963
+ # yaml-language-server: $schema=https://raw.githubusercontent.com/amr-m-abdelgawad/devctl/main/schema/devctl.config.schema.json
964
+ version: 1
965
+ \`\`\`
966
+
967
+ The schema file lives at [\`schema/devctl.config.schema.json\`](../schema/devctl.config.schema.json). The demo config uses a relative path so it works offline.
968
+
969
+ ## Discovery
970
+
971
+ Walks from the current directory toward the filesystem root:
972
+
973
+ \`\`\`mermaid
974
+ flowchart TB
975
+ cwd["Current directory"] --> nested{"Has .devctl/config.yaml?"}
976
+ nested -->|yes| root["Repo root = that directory"]
977
+ nested -->|no| single{"Has devctl.yaml?"}
978
+ single -->|yes| root
979
+ single -->|no| top{"Filesystem root?"}
980
+ top -->|no| parent["Walk to parent"]
981
+ parent --> nested
982
+ top -->|yes| miss["No configuration found"]
983
+ \`\`\`
984
+
985
+ \`--config\` points at a file or a \`.devctl\` directory. Repository root is the directory that contains \`.devctl\` (or the parent of \`devctl.yaml\`).
986
+
987
+ When the main file lives in \`.devctl/\`, modular files merge in:
988
+
989
+ \`\`\`mermaid
990
+ flowchart TB
991
+ main[".devctl/config.yaml"] --> services[".devctl/services/*.yaml"]
992
+ main --> profiles[".devctl/profiles/*.yaml"]
993
+ main --> routes[".devctl/proxy/routes.yaml"]
994
+ \`\`\`
995
+
996
+ Service and profile filenames become keys (\`identity.yaml\` \u2192 service \`identity\`).
997
+ Files within each modular directory are loaded in sorted filename order, making
998
+ overrides deterministic even when both \`.yaml\` and \`.yml\` fragments resolve to
999
+ the same key.
1000
+
1001
+ ## Overlays and precedence
1002
+
1003
+ Local overlays merge after the repo config. Each stage overrides the one before
1004
+ it, so the rightmost source wins:
1005
+
1006
+ \`\`\`mermaid
1007
+ flowchart LR
1008
+ defaults["Built-in defaults"] --> repo["Repository .devctl"]
1009
+ repo --> homeLocal["~/.devctl/config.local.yaml"]
1010
+ homeLocal --> repoLocal[".devctl/config.local.yaml"]
1011
+ repoLocal --> env["DEVCTL_* / ENV_SOURCE_ORDER"]
1012
+ env --> flags["CLI --config"]
1013
+ \`\`\`
1014
+
1015
+ The repository's own \`config.local.yaml\` overrides the one in your home
1016
+ directory, not the other way round: overlays are applied home-first so the
1017
+ repo-specific file gets the last word.
1018
+
1019
+ TUI appearance is **not** this file. Theme, keys, mouse, and MCP listen live in \`tui.json\` \u2014 see [Building from source](typescript.md) and [TUI](tui.md).
1020
+
1021
+ ## Top-level keys
1022
+
1023
+ | Key | Role |
1024
+ |-----|------|
1025
+ | \`version\` | Must be \`1\` |
1026
+ | \`project.name\` | Shown in the TUI header |
1027
+ | \`google.project_id\` / \`region\` | Cloud project (optional) |
1028
+ | \`templates\` | Named service bases (\`extends\`) |
1029
+ | \`services\` | Process definitions |
1030
+ | \`tasks\` | Named transient commands run with \`devctl run\` |
1031
+ | \`profiles\` | Named service sets + extra env |
1032
+ | \`proxy\` | Listen address, token endpoint, routes |
1033
+ | \`logs\` | In-memory cap and persistence |
1034
+ | \`auth.refresh_threshold_seconds\` | Token refresh window (default 300) |
1035
+ | \`shutdown\` | \`stop_services_on_exit\`, \`grace_seconds\` |
1036
+ | \`ui\` | Optional theme / keymap hints in YAML (TUI prefs still win from \`tui.json\`) |
1037
+ | \`secrets\` | Extra redaction markers and regexes |
1038
+ | \`doctor.tools\` | Extra CLI binaries to probe |
1039
+ | \`plugins\` | \`{ path }\` modules loaded when the supervisor starts |
1040
+ | \`environment.sources\` / \`secrets\` | Env source order and named secrets |
1041
+
1042
+ ## Templates
1043
+
1044
+ \`\`\`yaml
1045
+ templates:
1046
+ python-http:
1047
+ health: { type: http, interval_seconds: 2, timeout_seconds: 1 }
1048
+ logs: { stdout: true, stderr: true }
1049
+ restart: { policy: on_failure, max_retries: 2, backoff_seconds: 1 }
1050
+
1051
+ services:
1052
+ api:
1053
+ extends: python-http
1054
+ command: [python3, main.py]
1055
+ \`\`\`
1056
+
1057
+ ## Tasks
1058
+
1059
+ Tasks accept \`command\`, \`shell\`, \`working_dir\`, \`dependencies\`, and \`environment\`. Dependencies name services and are made ready before the one-off command runs. Tasks have no ports, health checks, restart policy, or status entry; see [Services](services.md#hooks-and-one-off-tasks) for an example.
1060
+
1061
+ ## Validation and reload
1062
+
1063
+ \`\`\`bash
1064
+ devctl config validate
1065
+ devctl config validate --json
1066
+ devctl config show
1067
+ devctl config diff
1068
+ devctl reload
1069
+ \`\`\`
1070
+
1071
+ \`config diff\` explains the resolved result instead of merely printing it. Each
1072
+ entry includes the winning source file and layer (\`main\`, \`modular_service\`,
1073
+ \`modular_profile\`, \`modular_proxy\`, \`home_local\`, \`repo_local\`, or
1074
+ \`synthesized\`) and the ordered sources it shadowed. Use \`--json\` for structured
1075
+ output.
1076
+
1077
+ Checks: YAML syntax, required fields, unknown fields, service references, dependency conditions and cycles, health thresholds, duplicate ports, identities, proxy routes (including per-service \`proxy\` fragments merged at load), \`proxy.listen.port\` when \`proxy.enabled\` is true, environment references, profile references, and optional \`plugins[].path\`.
1078
+
1079
+ The TUI Config screen \`v\` / \`/buffer\` overlay validates this text before writing. Invalid YAML is not saved. \`e\` still opens \`$EDITOR\`.
1080
+
1081
+ The supervisor watches \`.devctl/\` (\`fs.watch\`, ~200ms debounce) and runs the same path as \`/reload\`. \`devctl reload\` and TUI \`/reload\` re-read configuration, publish \`ConfigurationChanged\`, and list services that must restart because command, environment, ports, identity, or \`watch\` changed.
1082
+
1083
+ Changing the \`plugins\` **path list** hot-applies token providers, log parsers, and proxy middleware. Editing an already-imported plugin file (same path, newer mtime) still requires \`devctl down && devctl start\` \u2014 Bun\u2019s module cache cannot unload it. A running service\u2019s environment is unchanged until that service restarts.
1084
+
1085
+ ## Related
1086
+
1087
+ - [Services](services.md)
1088
+ - [Profiles](profiles.md)
1089
+ - [Environment](environment.md)
1090
+ - [Plugins](plugins.md)
1091
+ - [How it fits together](overview.md)
1092
+ `},{path:"docs/developer-setup.md",title:"Developer setup",body:`# Developer setup
1093
+
1094
+ You do not need project-wide Google admin. Administrators own IAM and APIs \u2014 see [Admin setup](admin-setup.md).
1095
+
1096
+ \`\`\`bash
1097
+ # 1. Install Bun and (if this repo needs Google) gcloud
1098
+ # 2. Clone the service repository
1099
+ cd your-repo
1100
+
1101
+ # 3. Authenticate only when identity, impersonation, or IAP is required
1102
+ gcloud auth application-default login
1103
+
1104
+ # 4. Onboard
1105
+ devctl setup
1106
+ devctl doctor
1107
+
1108
+ # 5. Work
1109
+ devctl
1110
+ \`\`\`
1111
+
1112
+ Typical TUI flow:
1113
+
1114
+ \`\`\`mermaid
1115
+ flowchart LR
1116
+ profile["o \u2014 pick profile"] --> start["enter / n \u2014 start"]
1117
+ start --> logs["l \u2014 logs"]
1118
+ start --> ident["a \u2014 identity"]
1119
+ start --> creds["credentials"]
1120
+ start --> proxy["p \u2014 proxy"]
1121
+ \`\`\`
1122
+
1123
+ \`devctl start\` leaves the supervisor running; \`devctl\` or \`devctl attach\` reconnects. \`devctl down\` stops it again. (\`--detach\` on \`start\` is deprecated \u2014 the daemon already outlives the command without it.)
1124
+
1125
+ ## Quit behavior
1126
+
1127
+ \`shutdown.stop_services_on_exit\` in config:
1128
+
1129
+ | Value | What happens on \`q\` |
1130
+ |-------|---------------------|
1131
+ | \`true\` | Stop managed services and exit |
1132
+ | \`false\` | Detach; services keep running |
1133
+ | unset | Confirm: \`enter\` stops, \`d\` detaches, \`esc\` stays |
1134
+
1135
+ The starter config writes \`stop_services_on_exit: true\`. \`esc\` twice uses the same rule.
1136
+
1137
+ ## Related
1138
+
1139
+ - [Quick start](quickstart.md)
1140
+ - [TUI](tui.md)
1141
+ - [Authentication](authentication.md)
1142
+ - [Troubleshooting](troubleshooting.md)
1143
+ `},{path:"docs/doctor.md",title:"Doctor",body:`# Doctor
1144
+
1145
+ When one or more services declare \`container\`, doctor checks that each selected
1146
+ Docker or Podman executable is installed and that its daemon is reachable. A
1147
+ present CLI with a stopped or inaccessible daemon is reported separately from
1148
+ a missing runtime.
1149
+
1150
+ \`\`\`bash
1151
+ devctl doctor
1152
+ devctl doctor --json
1153
+ \`\`\`
1154
+
1155
+ Exit code **2** when any check is not ok (same code as configuration errors).
1156
+
1157
+ The TUI **doctor** tab re-runs on every visit (\`r\` also refreshes). \`j\`/\`k\` move. \`enter\` on a busy port asks to SIGTERM that process (then SIGKILL if it stays up). Ports owned by a running container service are treated as healthy; \`enter\` never offers to kill the Docker or Podman daemon.
1158
+
1159
+ ## What it checks
1160
+
1161
+ - Google CLI installed
1162
+ - Application Default Credentials
1163
+ - Project (with source)
1164
+ - Live IAM Credentials / Resource Manager / IAP API reachability via Service Usage (reported, **never** auto-enabled)
1165
+ - Impersonation for each configured service account
1166
+ - IAP audiences (including SA impersonation)
1167
+ - Configured \`doctor.tools\` binaries (demo: \`python3\`, \`bun\`)
1168
+ - Docker or Podman CLI installed, and that daemon reachable, when any service declares \`container\` (every such service in config, not only the active profile \u2014 the demo probes Docker because \`postgres\` is always declared)
1169
+ - Ports declared in config
1170
+ - Repository configuration validity
1171
+
1172
+ Doctor probes IAP / service-account identity when any route or service declares them, even if the rest of the repo looks local-only.
1173
+
1174
+ Failures include an actionable hint. Error classes: authentication, authorization, missing API, missing IAM role, wrong project, wrong service account, IAP, expired credential, network.
1175
+
1176
+ Ports held by **your own** running services \u2014 host processes or published container ports \u2014 show as \u201Cin use\u201D. That is expected after \`start\`. Use the free-port action only for leftovers that are not this supervisor\u2019s children.
1177
+
1178
+ Typical loop:
1179
+
1180
+ \`\`\`mermaid
1181
+ flowchart LR
1182
+ doctor["devctl doctor"] --> hint["Fix the named hint"]
1183
+ hint --> auth["devctl auth status"]
1184
+ auth --> start["Start services"]
1185
+ start --> logs["Logs if health still fails"]
1186
+ \`\`\`
1187
+
1188
+ ## Related
1189
+
1190
+ - [Authentication](authentication.md)
1191
+ - [Troubleshooting](troubleshooting.md)
1192
+ - [Services](services.md)
1193
+ - [Admin setup](admin-setup.md)
1194
+ - [TUI](tui.md)
1195
+ `},{path:"docs/environment.md",title:"Environment",body:"# Environment\n\nEach service process gets a merged environment. Later sources override earlier ones.\n\nDefault source order (`ENV_SOURCE_ORDER` / `environment.sources`):\n\n```mermaid\nflowchart LR\n process --> profile --> dotenv --> generated --> keychain --> secret_manager --> defaults --> vars --> runtime\n```\n\n`process`, `defaults`, `vars`, and `runtime` always run for host services. Container services deliberately omit `process` so the caller's whole shell is not stored in inspectable container metadata. If you set `environment.sources`, the listed optional sources (`profile`, `dotenv`, `generated`, `keychain`, `secret_manager`) are added to the always-on set.\n\n| Source | What it loads |\n|--------|----------------|\n| `process` | The env of whichever CLI/TUI client most recently started or restarted this service (forwarded over the RPC as `client_env`), falling back to the supervisor's own environment if no client has done so yet \u2014 see below |\n| `profile` | `profiles.<name>.environment` |\n| `dotenv` | Repo-root then service working-dir: `.env`, `.env.development`, `.env.local`, `.env.<profile>` |\n| `generated` | Built-in hook that always returns `{}`. A plugin may register `environmentSources` if you need generated values |\n| `keychain` | Named secrets from `environment.secrets` / the credential store |\n| `secret_manager` | Values that look like `projects/*/secrets/*` via the Google REST API |\n| `defaults` | `services.<name>.environment.defaults` |\n| `vars` | Explicit `services.<name>.environment` keys |\n| `runtime` | Values `devctl` injects at start |\n\n`keychain` and `secret_manager` throw only when that source is listed and fetch fails.\n\n### `process` and the daemon-replacement limitation\n\nThe daemon remembers each service's `client_env` only in memory, per service, never on disk. A crash/health-triggered auto-restart or an MCP-initiated `start`/`restart` reuses the last one a real client supplied; a service that has never been started/restarted by a real client this daemon's lifetime \u2014 e.g. one adopted from a prior session by `recoverSession()` \u2014 has none, and falls back to the daemon's own (possibly stale) environment.\n\nThis memory does not survive the daemon process itself being replaced (upgrade, crash, `devctl down` then a fresh start): a new daemon starts with no client history at all, so anything it restarts before a client issues a fresh `start`/`restart` runs on whatever environment that new daemon process itself inherited at spawn. If a service depends on env that changed since the daemon last started, restart it explicitly (`devctl restart <service>` or the TUI) rather than relying on an automatic restart to pick it up.\n\n## Runtime-generated variables\n\nInjected when applicable:\n\n- `SERVICE_PORT`, `SERVICE_HOST`\n- `DEVCTL_PROXY_URL`\n- `DEVCTL_SERVICE_NAME`\n- `DEVCTL_ENVIRONMENT`\n- `DEVCTL_TOKEN_URL` and `DEVCTL_INTERNAL_TOKEN` for host services (never a raw access token); containers omit both because container loopback cannot reach the host loopback endpoint\n\nReferences such as `${services.identity.ports.http}` resolve before process start, including inside profile and dotenv values. `${env.NAME}` is rejected there. IAP route `auth.client_secret` is the exception: `${NAME}` and `${env.NAME}` are expanded from the process environment when the token is minted, not at config load.\n\n`environment.required` on a service fails start if those keys are still empty after the merge.\n\n## TUI / CLI flag precedence\n\n```mermaid\nflowchart LR\n cli[\"CLI flags\"] --> env[\"DEVCTL_* env\"]\n env --> tuiEnv[\"DEVCTL_TUI_CONFIG\"]\n tuiEnv --> tuiJson[\"repo / user tui.json\"]\n tuiJson --> yaml[\"repo .devctl\"]\n yaml --> defaults[\"defaults\"]\n```\n\n## File plugins\n\n`plugins[].path` can register additional named environment sources. Unknown configured source names fail after plugins load instead of being silently skipped. See [Plugins](plugins.md) for the SDK contract, failure behavior, and examples. The built-in `generated` source stays empty unless a plugin registers an environment source with that name.\n\n## Related\n\n- [Services](services.md)\n- [Configuration](configuration.md)\n- [Security](security.md)\n"},{path:"docs/iap.md",title:"Identity-Aware Proxy",body:`# Identity-Aware Proxy
1196
+
1197
+ IAP routes must set an audience (IAP OAuth client ID or IAP resource name) **and** an identity. A missing \`identity.type\` is a configuration error, not a silent default to the current user.
1198
+
1199
+ \`\`\`yaml
1200
+ auth:
1201
+ type: iap
1202
+ audience: "/projects/PROJECT_NUMBER/iap_web/..."
1203
+ identity:
1204
+ type: user
1205
+ \`\`\`
1206
+
1207
+ or
1208
+
1209
+ \`\`\`yaml
1210
+ auth:
1211
+ type: iap
1212
+ audience: "..."
1213
+ identity:
1214
+ type: service_account
1215
+ service_account: backend-dev@company-dev.iam.gserviceaccount.com
1216
+ \`\`\`
1217
+
1218
+ User identity mints an ID token with ADC's default OAuth client. To mint with a **specific** OAuth client instead (a Desktop client, or the IAP client's own ID), set \`client_id\` and \`client_secret\`. Put \`\${NAME}\` or \`\${env.NAME}\` in \`client_secret\` so the value is read from the environment at mint time \u2014 that is not the same as service-env \`\${services\u2026}\` interpolation, which does not run on route auth:
1219
+
1220
+ \`\`\`yaml
1221
+ auth:
1222
+ type: iap
1223
+ audience: "IAP_CLIENT_ID.apps.googleusercontent.com"
1224
+ identity:
1225
+ type: user
1226
+ client_id: "DESKTOP_CLIENT_ID.apps.googleusercontent.com"
1227
+ client_secret: "\${IAP_OAUTH_CLIENT_SECRET}"
1228
+ \`\`\`
1229
+
1230
+ Omit \`client_id\` to keep the default ADC client. \`client_id\` is only valid on \`auth.type: iap\` with \`identity.type: user\`. Service-account IAP still uses IAM \`generateIdToken\` against \`audience\`.
1231
+
1232
+ The ADC refresh token must have been issued to that OAuth client. \`gcloud auth application-default login\` uses the Cloud SDK client by default; a mismatch fails as \`unauthorized_client\`. Login with a client secret file that matches \`client_id\`, or omit \`client_id\`.
1233
+
1234
+ \`\`\`mermaid
1235
+ flowchart LR
1236
+ client["Local client"] --> proxy["devctl proxy"]
1237
+ proxy --> ident{"Route identity"}
1238
+ ident -->|user, default client| adc["ADC user ID token"]
1239
+ ident -->|user, client_id| oauth["UserRefreshClient ID token"]
1240
+ ident -->|service_account| iam["IAM generateIdToken"]
1241
+ adc --> iap["Google IAP"]
1242
+ oauth --> iap
1243
+ iam --> iap
1244
+ iap --> up["Upstream"]
1245
+ \`\`\`
1246
+
1247
+ The local proxy mints the token and injects \`Authorization: Bearer \u2026\`. Services do not implement IAP themselves.
1248
+
1249
+ Tokens refresh when \`expires_at - now < auth.refresh_threshold_seconds\` (default 300). Concurrent refreshes for the same identity + audience + scope + OAuth client share one in-flight request.
1250
+
1251
+ Doctor probes IAP audiences (including SA impersonation and a configured OAuth client) even if the rest of the repo looks local-only.
1252
+
1253
+ Local demos can use \`auth.type: none\` so routes still appear in the proxy screen without calling real IAP.
1254
+
1255
+ ## Related
1256
+
1257
+ - [Proxy](proxy.md)
1258
+ - [Impersonation](impersonation.md)
1259
+ - [Authentication](authentication.md)
1260
+ - [Admin setup](admin-setup.md)
1261
+ `},{path:"docs/impersonation.md",title:"Service account impersonation",body:'# Service account impersonation\n\nPreferred flow:\n\n```mermaid\nflowchart LR\n user["Developer Google identity"] --> iam["IAM Credentials API<br/>generateAccessToken / generateIdToken"]\n iam --> sat["Short-lived service account token"]\n```\n\nDevelopers need `roles/iam.serviceAccountTokenCreator` on each target service account (preferably bound via a Google Group). `devctl` never downloads or stores service-account private keys.\n\n```yaml\nidentity:\n type: service_account\n service_account: worker-dev@company-dev.iam.gserviceaccount.com\n```\n\n`type: service` is accepted as an alias for `service_account`.\n\nIf impersonation is unavailable, **that** service fails to start. Unrelated local services still run.\n\nWhen a proxy route uses `auth.type: iap` and a service-account identity, `devctl` calls IAM Credentials `generateIdToken` for that account (never a user ID token). Access tokens without an audience still use `generateAccessToken`.\n\nProbing a configured SA is lazy and cached, never automatic: it happens the first time a service actually starts under that identity, on an explicit `auth_refresh`, or on a doctor inspection \u2014 never on the daemon\'s own boot or after a reload, which only ever refresh ADC/user/project. Status is one of `unknown` (not probed yet), `available`, or `unavailable`; doctor and the TUI\'s Auth screen render "not probed yet" distinctly from a confirmed failure rather than treating it as unavailable. Organization policies that disable impersonation or constrain ADC look like IAM failures \u2014 confirm those before changing developer machines. See [Admin setup](admin-setup.md).\n\n## Related\n\n- [Authentication](authentication.md)\n- [IAP](iap.md)\n- [Proxy](proxy.md)\n- [Security](security.md)\n'},{path:"docs/index.md",title:"index",body:`---
1262
+ layout: home
1263
+ ---
1264
+
1265
+ <script setup>
1266
+ import { ref } from 'vue'
1267
+ import { withBase } from 'vitepress'
1268
+ import TerminalHero from './.vitepress/theme/TerminalHero.vue'
1269
+ const copyLabel = ref('Copy command')
1270
+ const activeProfile = ref('backend')
1271
+ const profileExamples = {
1272
+ backend: { label: 'Build an API', description: 'Focus on the backend. Bring up identity, the API, and its worker as a single group.', services: ['identity', 'invoices-api', 'invoices-worker'] },
1273
+ full: { label: 'Work end to end', description: 'Bring the console into the picture when your work crosses the frontend and backend.', services: ['identity', 'invoices-api', 'invoices-worker', 'billing-console'] },
1274
+ data: { label: 'Start with data', description: 'Run the demo\u2019s optional PostgreSQL container when you need a local database.', services: ['postgres'] }
1275
+ }
1276
+ async function copyInstall() {
1277
+ try {
1278
+ await navigator.clipboard.writeText('npx @amr-m-abdelgawad/devctl@latest')
1279
+ copyLabel.value = 'Copied!'
1280
+ } catch {
1281
+ copyLabel.value = 'Select the command to copy'
1282
+ }
1283
+ }
1284
+ </script>
1285
+
1286
+ <div class="landing vp-raw">
1287
+ <section class="landing-hero" aria-labelledby="hero-title">
1288
+ <div class="hero-copy">
1289
+ <p class="eyebrow"><span class="status-dot"></span> YOUR LOCAL DEVELOPMENT, IN SYNC</p>
1290
+ <h1 id="hero-title">More building.<br>Less <span>tab juggling.</span></h1>
1291
+ <p class="hero-description">Your services, logs, and local stack. Together in one terminal. Keep everything in view with devctl \u2014 from the first process to the last request.</p>
1292
+ <div class="hero-actions">
1293
+ <a class="primary-link" :href="withBase('/quickstart')">Get started <span aria-hidden="true">\u2197</span></a>
1294
+ <a class="text-link" href="https://github.com/amr-m-abdelgawad/devctl">Explore on GitHub <span aria-hidden="true">\u2197</span></a>
1295
+ </div>
1296
+ <p class="hero-footnote">Open source. Local first. Your workflow.</p>
1297
+ </div>
1298
+ <div class="hero-preview">
1299
+ <div class="preview-caption"><span>ONE TERMINAL. THE WHOLE PICTURE.</span><span aria-hidden="true">\u2199</span></div>
1300
+ <TerminalHero />
1301
+ <div class="preview-footer"><span class="status-dot"></span> An illustrative devctl session <span class="preview-shortcut">\u2318 / ctrl + p <span>command palette</span></span></div>
1302
+ </div>
1303
+ </section>
1304
+
1305
+ <div class="landing-signal"><span>Less switching. <b>More context.</b></span><span>TUI <i>/</i> CLI <i>/</i> MCP</span><span>One shared session <span aria-hidden="true">\u2197</span></span></div>
1306
+
1307
+ <section class="landing-section" aria-labelledby="surfaces-title">
1308
+ <div class="section-heading"><div><p class="eyebrow">01 / WORK YOUR WAY</p><h2 id="surfaces-title">One stack. Your kind of control.</h2></div><p>Stay hands-on, script the routine,<br>or let your agent take the next step.</p></div>
1309
+ <div class="surface-grid">
1310
+ <a class="surface" :href="withBase('/tui')"><span class="surface-symbol" aria-hidden="true">\u25A4</span><span class="surface-label">FOR YOUR FLOW</span><h3>A home for your stack.</h3><p>Start services, follow logs, and check health in a keyboard-first terminal interface.</p><span class="surface-link">Explore the TUI <span aria-hidden="true">\u2197</span></span></a>
1311
+ <a class="surface" :href="withBase('/cli')"><span class="surface-symbol" aria-hidden="true">&gt;_</span><span class="surface-label">FOR THE REPEATABLE</span><h3>Make it a command.</h3><p>Bring the same controls to scripts and CI. Run tasks, inspect config, and keep moving.</p><span class="surface-link">Meet the CLI <span aria-hidden="true">\u2197</span></span></a>
1312
+ <a class="surface" :href="withBase('/mcp')"><span class="surface-symbol" aria-hidden="true">\u2733</span><span class="surface-label">FOR YOUR AGENT</span><h3>Give AI the context.</h3><p>Connect your agent over MCP to inspect and operate the same local session. Enabled when you choose.</p><span class="surface-link">Connect with MCP <span aria-hidden="true">\u2197</span></span></a>
1313
+ </div>
1314
+ </section>
1315
+
1316
+ <section class="workflow-section landing-section" aria-labelledby="workflow-title">
1317
+ <div class="section-heading"><div><p class="eyebrow">02 / FIND YOUR FOCUS</p><h2 id="workflow-title">The right services.<br>For the task at hand.</h2></div><p>Group services into named profiles.<br>Start the part of your stack you need.</p></div>
1318
+ <div class="workflow-panel">
1319
+ <div class="workflow-options" role="group" aria-label="Explore example profiles">
1320
+ <button v-for="(profile, name) in profileExamples" :key="name" type="button" :aria-pressed="activeProfile === name" @click="activeProfile = name"><span>{{ profile.label }}</span><code>{{ name }}</code><span aria-hidden="true">\u2197</span></button>
1321
+ <a class="text-link" :href="withBase('/profiles')">Explore profiles <span aria-hidden="true">\u2197</span></a>
1322
+ </div>
1323
+ <div class="workflow-example" aria-live="polite" aria-atomic="true">
1324
+ <p class="eyebrow">DEMO PLATFORM / EXAMPLE PROFILE</p>
1325
+ <p class="workflow-command"><span aria-hidden="true">$ </span><code>devctl start --profile {{ activeProfile }}</code></p>
1326
+ <p class="workflow-description">{{ profileExamples[activeProfile].description }}</p>
1327
+ <div class="workflow-services"><span v-for="service in profileExamples[activeProfile].services" :key="service"><i aria-hidden="true"></i>{{ service }}</span></div>
1328
+ <p class="workflow-note">Your repo, your names. Profiles are defined in your config.</p>
1329
+ </div>
1330
+ </div>
1331
+ </section>
1332
+
1333
+ <section class="setup-section landing-section" aria-labelledby="setup-title">
1334
+ <div class="setup-copy"><p class="eyebrow">03 / FROM REPO TO RUNNING</p><h2 id="setup-title">Small setup.<br>Clear head.</h2><p>Start with Node.js. The npm package includes its own Bun runtime. Google Cloud is optional.</p><a class="text-link" :href="withBase('/installation')">Installation guide <span aria-hidden="true">\u2197</span></a></div>
1335
+ <div class="setup-panel">
1336
+ <div class="install-heading"><span>TRY IT IN YOUR TERMINAL</span><button type="button" @click="copyInstall" aria-live="polite">{{ copyLabel }}</button></div>
1337
+ <div class="install-command"><span aria-hidden="true">$</span><code>npx @amr-m-abdelgawad/devctl@latest</code></div>
1338
+ <ol class="setup-steps"><li><span>01</span><div><code>devctl setup</code><p>Describe your services in a single config.</p></div></li><li><span>02</span><div><code>devctl doctor</code><p>See what\u2019s missing before you start.</p></div></li><li><span>03</span><div><code>devctl</code><p>Open your dashboard. Press enter to start a profile.</p></div></li></ol>
1339
+ </div>
1340
+ </section>
1341
+
1342
+ <section class="details-section landing-section" aria-labelledby="details-title"><div><p class="eyebrow">04 / THOUGHTFUL BY DEFAULT</p><h2 id="details-title">Your machine.<br>Your ground rules.</h2><p>One supervisor keeps processes, containers, the proxy, and logs in sync across every control surface.</p><a class="text-link" :href="withBase('/overview')">See how it fits together <span aria-hidden="true">\u2197</span></a></div><div class="detail-list"><a :href="withBase('/configuration')"><span>01</span><div><h3>Configuration, not custom code.</h3><p>Define services, profiles, health gates, and hooks in YAML.</p></div><span aria-hidden="true">\u2197</span></a><a :href="withBase('/proxy')"><span>02</span><div><h3>Authentication, handled locally.</h3><p>An auth-aware proxy injects Google / IAP tokens. Tokens stay out of logs.</p></div><span aria-hidden="true">\u2197</span></a><a :href="withBase('/doctor')"><span>03</span><div><h3>Diagnostics without surprises.</h3><p>Doctor reports missing tools and setup issues. It never auto-enables anything.</p></div><span aria-hidden="true">\u2197</span></a></div></section>
1343
+
1344
+ <section class="demo-section landing-section" aria-labelledby="demo-title">
1345
+ <div class="demo-band"><div><p class="eyebrow">05 / TAKE A LOOK AROUND</p><h2 id="demo-title">Meet your practice stack.</h2><p>A console, an API, identity, and a worker. Explore the included demo platform before configuring your own repo.</p></div><a class="primary-link" href="https://github.com/amr-m-abdelgawad/devctl/tree/main/examples/demo-platform">Explore the demo <span aria-hidden="true">\u2197</span></a></div>
1346
+ </section>
1347
+
1348
+ <section class="faq-section landing-section" aria-labelledby="faq-title">
1349
+ <div><p class="eyebrow">06 / A FEW GOOD QUESTIONS</p><h2 id="faq-title">Before you<br>press enter.</h2><p>A little context for your first session.</p></div>
1350
+ <div class="faq-list">
1351
+ <details><summary>Do I need Docker or Google Cloud?</summary><p>Neither is required for local host processes. Use Docker or Podman when your configuration includes containers. Google Cloud tools are optional and only needed for the Google authentication features you choose to use. <a :href="withBase('/installation')">See installation requirements \u2197</a></p></details>
1352
+ <details><summary>Do I have to change my application code?</summary><p>Describe how your services run in <code>.devctl/config.yaml</code>: their commands, working directories, environment, and health checks. devctl works with that configuration. <a :href="withBase('/configuration')">Explore configuration \u2197</a></p></details>
1353
+ <details><summary>Can I use the CLI and TUI together?</summary><p>Yes. The TUI, CLI, and MCP connect to the same per-repo supervisor. Start a profile from the CLI and attach to its session with <code>devctl attach</code>. <a :href="withBase('/overview')">See how sessions work \u2197</a></p></details>
1354
+ <details><summary>Does my agent get access automatically?</summary><p>No. MCP is off by default. When enabled, it listens on the local loopback interface. You choose when to connect your agent and can disable individual tools. <a :href="withBase('/mcp')">Read the MCP guide \u2197</a></p></details>
1355
+ <details><summary>Can I try it without a global install?</summary><p>Yes. Run <code>npx @amr-m-abdelgawad/devctl@latest</code> from your repo. Node.js is required; the npm package includes its own Bun runtime. <a :href="withBase('/quickstart')">Follow the quick start \u2197</a></p></details>
1356
+ </div>
1357
+ </section>
1358
+
1359
+ <section class="closing"><p class="eyebrow">LESS FRICTION. MORE FORWARD.</p><h2>Get your stack together.</h2><a class="primary-link" :href="withBase('/quickstart')">Start your first session <span aria-hidden="true">\u2197</span></a><p>Free and open source \xB7 MIT licensed</p></section>
1360
+ </div>
1361
+ `},{path:"docs/installation.md",title:"Installation",body:`# Installation
1362
+
1363
+ The recommended installation is the public npm package. It includes an official Bun runtime, so Node.js 18 or later is the only prerequisite.
1364
+
1365
+ ## From npm (recommended)
1366
+
1367
+ Install the command for regular use:
1368
+
1369
+ \`\`\`bash
1370
+ npm install --global @amr-m-abdelgawad/devctl
1371
+ devctl version
1372
+ \`\`\`
1373
+
1374
+ Or try the latest version without a global installation:
1375
+
1376
+ \`\`\`bash
1377
+ npx @amr-m-abdelgawad/devctl@latest
1378
+ \`\`\`
1379
+
1380
+ The npm package contains bundled JavaScript, not a compiled devctl executable. Its small Node launcher invokes the package-local official Bun runtime and forwards the terminal, arguments, current directory, environment, signals, and exit status. npm provenance links public releases to this repository's release workflow.
1381
+
1382
+ Do not install with \`--ignore-scripts\`: Bun's npm package uses its installation script to select the runtime for the current operating system and CPU.
1383
+
1384
+ ## From a GitHub Release (unsigned alternative)
1385
+
1386
+ After a \`v*\` tag, the [Release](https://github.com/amr-m-abdelgawad/devctl/actions/workflows/release.yml) workflow publishes these standalone artifacts:
1387
+
1388
+ | Asset | Platform |
1389
+ |-------|----------|
1390
+ | \`devctl-darwin-arm64\` | macOS Apple silicon |
1391
+ | \`devctl-darwin-x64\` | macOS Intel |
1392
+ | \`devctl-linux-x64\` | Linux x64 |
1393
+ | \`devctl-linux-arm64\` | Linux arm64 |
1394
+ | \`devctl-windows-x64.exe\` | Windows x64 |
1395
+
1396
+ \`\`\`bash
1397
+ # Example only: download the asset and SHA256SUMS from the same release,
1398
+ # verify its hash, then install it.
1399
+ grep ' devctl-darwin-arm64$' SHA256SUMS | shasum -a 256 -c -
1400
+ chmod +x devctl-darwin-arm64
1401
+ sudo mv devctl-darwin-arm64 /usr/local/bin/devctl
1402
+ devctl version
1403
+ \`\`\`
1404
+
1405
+ These binaries are not signed with Apple Developer ID or Windows Authenticode. Checksums and GitHub build attestations prove integrity and build origin, but they do not make the operating system display devctl as a verified publisher.
1406
+
1407
+ A Homebrew formula lives in this repository rather than Homebrew-core:
1408
+
1409
+ \`\`\`bash
1410
+ brew install --formula https://raw.githubusercontent.com/amr-m-abdelgawad/devctl/main/homebrew/devctl.rb
1411
+ \`\`\`
1412
+
1413
+ \`devctl update\` reports whether a newer GitHub Release exists and which install channel this process is. For an npm global or Homebrew install it runs that upgrade; otherwise it prints the matching command. \`--check\` reports without installing.
1414
+
1415
+ ## From source
1416
+
1417
+ \`\`\`bash
1418
+ # Source development requires Bun 1.4.0 or later.
1419
+ export PATH="$HOME/.bun/bin:$PATH"
1420
+
1421
+ git clone https://github.com/amr-m-abdelgawad/devctl.git
1422
+ cd devctl/app
1423
+ bun install
1424
+ \`\`\`
1425
+
1426
+ Run without installing a global command:
1427
+
1428
+ \`\`\`bash
1429
+ bun run src/bin.ts --help
1430
+ # from the repo root:
1431
+ bun run app/src/bin.ts --help
1432
+ \`\`\`
1433
+
1434
+ Link the \`devctl\` command onto your PATH:
1435
+
1436
+ \`\`\`bash
1437
+ cd app && bun link
1438
+ devctl version
1439
+ \`\`\`
1440
+
1441
+ \`bun link\` registers the \`bin\` entry in \`app/package.json\` (\`devctl\` \u2192 \`./src/bin.ts\`).
1442
+
1443
+ ## Shell completions
1444
+
1445
+ \`\`\`bash
1446
+ source <(devctl completion zsh) # or bash
1447
+ devctl completion fish > ~/.config/fish/completions/devctl.fish
1448
+ \`\`\`
1449
+
1450
+ See [CLI](cli.md).
1451
+
1452
+ ## Google Cloud CLI (optional)
1453
+
1454
+ Install \`gcloud\` only if you use user identity, impersonation, or IAP. Local-only services do not need it. See [Authentication](authentication.md).
1455
+
1456
+ ## Cross-platform
1457
+
1458
+ The npm package supports macOS arm64/x64, Linux arm64/x64 (glibc or musl), and Windows x64. Process-group handling is OS-specific (\`app/src/adapters/process/\`). Attach and CLI-over-session use \`devctl.sock\` on Unix and a named pipe (\`\\\\.\\pipe\\devctl-<repoID>\`) on Windows.
1459
+
1460
+ ## Related
1461
+
1462
+ - [Quick start](quickstart.md)
1463
+ - [Building from source](typescript.md)
1464
+ - [Contributing](../CONTRIBUTING.md)
1465
+ - [npm publishing (maintainers)](npm-publishing.md)
1466
+ - [How it fits together](overview.md)
1467
+ - [Changelog](../CHANGELOG.md)
1468
+ `},{path:"docs/logs.md",title:"Logs",body:"# Logs\n\nAll service stdout/stderr, proxy events, health checks, authentication events, and `devctl` internal lines go through one log manager on the supervisor.\n\nSources you will see: `stdout`, `stderr`, `health`, `auth`, `devctl`, plus proxy lines.\n\n## Buffer and persistence\n\n- In-memory circular buffer: `logs.max_memory_events` (default 50,000). Retention stays O(1) per line even after the buffer fills.\n- The live ring lives in a Bun Worker behind `LogStore` when running from source or npm, so parse and search do not stall the supervisor event loop. The main thread only receives page/facet/export results (and a cached snapshot for `status`). Compiled standalone binaries (`bun build --compile`) keep the ring in-process \u2014 Bun 1.4.0 cannot resolve the worker script inside a single-file executable. If the worker fails to start, the daemon falls back to the in-process store rather than hanging.\n- Ingest truncates lines longer than 16 KiB and skips `JSON.parse` on payloads larger than 64 KiB. Regex search is already capped (pattern length, nested quantifiers).\n- Optional persistence under `~/.devctl/logs/` (`persistence.enabled`, `directory`, `retention_days`, `max_session_logs`).\n- Ingest is a bounded channel; UI updates batch (~30ms) so a noisy service cannot freeze the TUI.\n- The detached supervisor's own bootstrap stderr (before it has a config, so before any of the above even starts) is a separate file with its own rotation \u2014 the last 5 boot attempts are kept, each overwrite-proof against the next. See `devctl daemon logs` in the [CLI reference](cli.md).\n\n## Pagination and facets\n\nQueries (CLI, TUI, MCP) return a bounded, cursor-paged slice instead of the whole matching history: a page defaults to the latest 500 matching events, capped at 5,000. The cursor is opaque (carries the daemon session and an internal per-event sequence number) and pages both backward (older) and forward (newer) without duplicating or dropping events that share the same millisecond \u2014 a plain timestamp boundary can't make that guarantee once two events land in the same millisecond and a page cuts between them. `since`/`until` keep working as ordinary timestamp filters alongside the cursor. Exporting (`/export`, `devctl logs export`) still reads the entire matching history \u2014 page size never truncates an export.\n\nFacets \u2014 the total matching count, plus per-service/level/source counts (each computed under every *other* active filter, not its own) \u2014 come from a separate, lightweight stats query with no event payload. The TUI refreshes them every two seconds while its logs screen is open, and immediately on a filter change, a clear, or reconnecting, so the filter chips' counts and the log pane title stay accurate even though the TUI itself only ever holds a bounded page rather than the full history.\n\n## TUI (Logs tab)\n\n- `f` focuses search. `/` stays the command line. Matches are highlighted in the log line (plain or `/regex`). Dashboard tail uses the same search filter.\n- `e` / `/filter` \u2014 ERROR and above.\n- `p` / `/pause` \u2014 freeze the live stream.\n- `z` / `/fullscreen` \u2014 hide header and nav so the stream fills a small editor terminal. `z` or `esc` exits.\n- `t` / `m` \u2014 timestamp and metadata columns (persist in `tui.json`).\n- `w` / `/wrap` \u2014 clip \u2192 unwrap the selected row \u2192 wrap every long line.\n- `g` \u2014 jump to latest. Leaving the tail pins the view (`pinned \xB7 +N new`).\n- `\u2190`/`\u2192` or click a chip \u2014 cycle service filters. Digits `1`\u2013`4` jump nav tabs, not log sources.\n- `\\\\` / `/split` \u2014 second pane on the same live stream, with its own service filter. Shared search. `|` focuses the other pane.\n- `/trace <id>` \u2014 set search to that request/trace id. Enter in the details overlay on a row that has an id does the same.\n- `enter` \u2014 details overlay (full message, pid, stream, request_id, identity).\n- `command+c` (macOS) or `ctrl+c` (Linux/Windows) \u2014 copy the highlighted selection. Remap with `keybinds.copy`.\n- `/export [path]` \u2014 write the **current** filters. Default file: `~/.devctl/exports/devctl-logs-<timestamp>.log`.\n- `/exports` or the **open folder** chip \u2014 reveal that directory.\n- `/history [id]` \u2014 load a persisted session (`LogManager.listSessions`).\n- `/regex`, `/since`, `/until` \u2014 search and time range (`until` is exclusive of later lines).\n\nLong lines fold with a `\u25B8N` marker. `j`/`k` moves the highlight and unwraps that row.\n\n## CLI\n\n```bash\ndevctl logs [svc\u2026] [--level] [--search] [--regex] [--source] [--since] [--until] [--json]\ndevctl logs # latest page (same as MCP get_logs); pass --all for the full match set\ndevctl logs -f # keep printing new matching events until interrupted\ndevctl logs --output FILE # same filters, write a file (full history, not just one page)\ndevctl logs export --output FILE # explicit export subcommand\ndevctl daemon logs [-f] # the supervisor's own bootstrap stderr, not service logs\n```\n\n## Related\n\n- [TUI](tui.md)\n- [CLI](cli.md)\n- [Security](security.md)\n"},{path:"docs/mcp.md",title:"MCP server for coding agents",body:"# MCP server for coding agents\n\n`devctl` can expose a **localhost Streamable HTTP** MCP server so Claude, Cursor, Codex, and Kilo Code can read status, logs, and config, and start or stop services.\n\nThe listener lives on the **supervisor** (the same process that owns services and the proxy). The TUI only toggles it and copies config. Agents need a URL; a stdio child of the TUI would die when the TUI exits.\n\n## Setting up a repository that has no `.devctl` yet\n\n`devctl mcp --on` works in a repository with no configuration at all. The\ndaemon boots in **setup mode**: nothing is validated and no service can start,\nbut the MCP server is up, so you can point an agent at it and ask it to set\ndevctl up for the repository.\n\n```bash\ndevctl mcp --on\n```\n\nThe agent calls `get_setup_guide` and `search_docs`, surveys the repo, drafts a\nconfig, checks it with `validate_config` (which accepts candidate text, so it\ncan check a draft before writing it), writes the files, and calls\n`reload_config`. Setup mode clears on the reload that finds a valid\nconfiguration, and `.devctl/` starts being watched for changes from then on.\n\n`get_status` reports `setup_mode: true` while this state is active, so an agent\ncan tell an empty service list apart from a daemon that failed to start\nanything.\n\nDefault is **off** (`mcp_enabled` in `~/.devctl/tui.json`). Once on, the supervisor applies that preference itself at startup \u2014 whether it was spawned by the TUI or by a plain CLI command \u2014 so the listener comes back on the next `devctl start` too, not only while the TUI is attached.\n\n## Enable it\n\n```mermaid\nflowchart LR\n open[\"/mcp or Settings \u2192 MCP page\"] --> listen[\"Listen ON\"]\n listen --> copy[\"Copy JSON / TOML\"]\n copy --> agent[\"Claude / Cursor / Codex / Kilo\"]\n agent --> http[\"127.0.0.1:port/mcp\"]\n http --> sup[\"Supervisor\"]\n```\n\nMCP is **not** a nav tab. Flip **Listen** (`space` / `enter`). The header shows an **MCP** chip when it is running.\n\nThe default port is derived from the repo so checkouts do not collide:\n\n`18700 + (parseInt(repoID.slice(0, 8), 16) % 600)` \u2192 **18700\u201319299**.\n\nChange it with `\u2190` / `\u2192` or `devctl mcp --port`. An override is persisted as `mcp_port` only when it is not the derived default. If the preferred port is busy, the supervisor walks upward until it finds a free one.\n\nThe server binds **`127.0.0.1` only**. Mutating tools require `Authorization: Bearer` with a short session token. Copied snippets include that header. Tool output never includes tokens or raw secret env values. `get_status` reports MCP running/address/port, not the bearer token.\n\n## CLI\n\n```text\ndevctl mcp # URL + four snippets\ndevctl mcp --on [--port N]\ndevctl mcp --off\ndevctl mcp --json\n```\n\n`--on` starts a supervisor if needed. `--off` stops the listener only.\n\n## Tools and resources\n\n| Tool | Group | What it does |\n|------|-------|----------------|\n| `list_services` | inspect | Name, state, health, ports, pid, last error |\n| `get_service` | inspect | One service plus command/cwd/ports (env redacted or left as `${\u2026}` refs) |\n| `get_status` | inspect | Profile, session, identity flags, proxy, log counts, MCP listen |\n| `get_logs` | logs | Filtered logs, capped at 200 events per page, secrets redacted. Pass `cursor` from the previous `next_cursor` to page forward with no duplicate or same-millisecond-lost lines; `since`/`until` are plain timestamp filters for a fresh query |\n| `list_profiles` | inspect | Config profiles and members |\n| `get_config` | inspect | Merged summary: project, services, routes, proxy paths |\n| `get_config_sources` | inspect | Effective values with winning and shadowed configuration sources; secret-like values are redacted |\n| `run_doctor` | diagnostics | Doctor report |\n| `start_services` | control | Named list, or a `profile`. Omitted names use `profile`, then the active session profile, then the first configured profile \u2014 never every service. No profile and no names fails closed |\n| `stop_services` | control | Named list, or all started services when omitted. Also stops every transitive dependent of a named service \u2014 never its dependencies |\n| `restart_services` | control | Named list; touches only those services, not dependents, unless `cascade: true`. Start still expands dependencies |\n| `reload_config` | control | Reload `.devctl` |\n| `run_task` | control | Run a named task from configuration; output is also in the log ring as `task:<name>` |\n| `start_proxy` / `stop_proxy` | control | Start or stop the local reverse proxy |\n| `exec_service` | control | Run an arbitrary command in a service's resolved environment/cwd, or inspect its redacted environment with `print_env` |\n| `get_setup_guide` | setup | The onboarding guide for authoring a `.devctl`. `section`: `procedure` (default), `authoring`, `discovery`. Same text as [`skills/devctl-onboard`](../skills/devctl-onboard/SKILL.md), compiled into the binary so no skill install is needed |\n| `search_docs` | setup | Keyword search over the compiled-in product docs (`docs/*.md`) and the onboarding skill. Pass `query`; optional `limit` (default 5, max 10). Returns ranked pages with snippets |\n| `validate_config` | setup | Validate configuration and return the loader's exact issues. No arguments validates what is on disk; `text` validates a candidate `config.yaml` through the real load pipeline before it is written |\n\nNo tool writes files. An agent authors `.devctl` with its own editing tools and uses `validate_config` to check the result.\n\nInteractive `gcloud` login stays CLI/TUI-only (`devctl auth login` / `/auth login`). MCP `run_doctor` already probes service accounts; run `devctl auth login` when ADC is missing.\n\n`get_logs` is paged (cap 200). To follow, poll with `cursor=next_cursor`. There is no blocking `follow` tool.\n\n## Enabling and disabling tools\n\nEvery tool is on by default. The TUI's **MCP** page lists them grouped by the\n`Group` column above, each marked `read` or `write`, and `space` toggles the\nhighlighted one. The common case is turning off the whole `control` group \u2014\n`start_services`, `stop_services`, `restart_services`, `reload_config`, `run_task`, `start_proxy`, `stop_proxy`, `exec_service` \u2014 so an\nagent can read status and logs but not start or stop anything.\n\nA disabled tool is left out of `tools/list` **and** refused if called anyway,\nsince an agent may still hold a tool list from before it was turned off. The\nrefusal names the tool and says it is disabled, rather than reporting it as\nunknown.\n\nThe setting is a deny-list stored as `mcp_disabled_tools` in `tui.json`, so a\ntool added by a later devctl version is available without editing anything.\nThe daemon applies it at boot the same way it applies `mcp_enabled`, and a TUI\ntoggle takes effect immediately without restarting the listener.\n\nAn agent cannot change this: `mcp_set_tools` is a local RPC and is deliberately\nabsent from the MCP host surface, so a connected client cannot re-enable a tool\nits operator turned off.\n\nResources (always-fresh reads): `devctl://status`, `devctl://services`, `devctl://logs`, `devctl://config`, `devctl://doctor`.\n\nDoctor may report ports \u201Cin use\u201D while your own services hold them \u2014 that is expected after a successful start.\n\n## Related\n\n- [TUI](tui.md)\n- [CLI](cli.md)\n- [How it fits together](overview.md)\n- [Agent skills](../skills/README.md)\n- [Security](security.md)\n"},{path:"docs/npm-publishing.md",title:"npm publishing",body:`# npm publishing
1469
+
1470
+ This project publishes \`@amr-m-abdelgawad/devctl\` as a free public npm package through npm Trusted Publishing. The release workflow uses GitHub's short-lived OpenID Connect identity; the repository must not contain an npm access token.
1471
+
1472
+ ## Bootstrap status
1473
+
1474
+ The one-time bootstrap is complete: \`0.1.2-bootstrap.0\` was published under the \`bootstrap\` tag and the GitHub Actions Trusted Publisher was configured. Do not publish that version again. These are the historical commands that established the package name:
1475
+
1476
+ \`\`\`bash
1477
+ cd app
1478
+ bun install --frozen-lockfile
1479
+ bun run build:npm 0.1.2-bootstrap.0
1480
+ cd ../dist/npm
1481
+ npm login
1482
+ npm publish --access public --tag bootstrap
1483
+ \`\`\`
1484
+
1485
+ The prerelease established the npm package so Trusted Publishing could be configured. Normal releases now use GitHub OIDC and do not need an npm token.
1486
+
1487
+ The configured Trusted Publisher uses these exact values:
1488
+
1489
+ - Provider: GitHub Actions
1490
+ - Organization or user: \`amr-m-abdelgawad\`
1491
+ - Repository: \`devctl\`
1492
+ - Workflow filename: \`release.yml\`
1493
+ - Environment: \`npm\`
1494
+
1495
+ The repository also has a GitHub Actions environment named \`npm\`. No npm secret is needed. The workflow uses Node 24 and npm 11.5.1, satisfying npm's Node 22.14+/npm 11.5.1+ requirement for trusted publication.
1496
+
1497
+ In the repository's Actions settings, enable the option that allows GitHub Actions to create pull requests. This is needed for the automated Homebrew checksum update; npm and GitHub Release publication do not depend on that optional PR succeeding.
1498
+
1499
+ ## Normal release
1500
+
1501
+ Before tagging, update both version sources and confirm all CI checks pass:
1502
+
1503
+ \`\`\`bash
1504
+ # app/package.json and app/src/version.ts must contain the same version
1505
+ git tag vX.Y.Z
1506
+ git push origin vX.Y.Z
1507
+ \`\`\`
1508
+
1509
+ The release workflow then:
1510
+
1511
+ 1. Builds the npm tarball and native binaries once.
1512
+ 2. Tests that exact tarball on supported macOS, Linux/glibc, Linux/musl, and Windows runners.
1513
+ 3. Generates \`SHA256SUMS\` and GitHub build-provenance attestations.
1514
+ 4. Publishes the npm version with provenance if it is not already present.
1515
+ 5. Finalizes the GitHub Release and opens a Homebrew checksum update pull request.
1516
+
1517
+ Publication is idempotent: rerunning a completed or partially completed workflow skips an npm version that is already on the registry and continues release finalization.
1518
+
1519
+ ## Trust model
1520
+
1521
+ npm provenance shows that the JavaScript package was published by this repository's workflow. GitHub attestations and \`SHA256SUMS\` establish the origin and integrity of standalone release files. They are free, but they do not replace Apple Developer ID notarization or Windows Authenticode signing; standalone binaries remain explicitly unsigned.
1522
+ `},{path:"docs/overview.md",title:"How it fits together",body:`# How it fits together
1523
+
1524
+ \`devctl\` is one product with four faces on the same supervisor.
1525
+
1526
+ \`\`\`mermaid
1527
+ flowchart TB
1528
+ tui["TUI \u2014 OpenTUI screens and keys"]
1529
+ cli["CLI \u2014 start / stop / logs / auth"]
1530
+ mcp["MCP \u2014 http://127.0.0.1:port/mcp"]
1531
+ sup["Supervisor"]
1532
+ disk["~/.devctl/state/repoID/"]
1533
+
1534
+ tui --> sup
1535
+ cli --> sup
1536
+ mcp --> sup
1537
+ sup --> runtime["Host processes + containers"]
1538
+ sup --> proxy["Proxy + token endpoint"]
1539
+ sup --> logs["Log buffer"]
1540
+ sup --> disk
1541
+ \`\`\`
1542
+
1543
+ Nothing in the application knows your services by name. The supervisor reads \`.devctl/\`, starts argv (or explicit shell) processes and optional Docker/Podman containers, injects resolved env, and reports health.
1544
+
1545
+ ## Supervisor
1546
+
1547
+ The supervisor is the long-lived process. It:
1548
+
1549
+ - Starts, stops, and restarts host processes and optional Docker/Podman containers in dependency waves
1550
+ - Optionally starts the proxy and the MCP listener
1551
+ - Ingests stdout/stderr, health, auth, and proxy events into one log buffer
1552
+ - Persists session state under \`~/.devctl/state/<repoID>/\` (\`state.json\`, \`devctl.lock\`, and on Unix \`devctl.sock\`)
1553
+
1554
+ \`repoID\` is the first 16 hex characters of \`sha256(absolute repo root)\`. Two checkouts get two state directories. A leftover \`~/.devctl/sessions/<id>/\` is migrated once.
1555
+
1556
+ \`devctl start\` always ensures a daemon and leaves it running after the command exits (\`--detach\` is deprecated and no longer changes that). \`devctl\` (no args) and \`devctl attach\` dial the session socket: \`devctl.sock\` on macOS/Linux, \`\\\\.\\pipe\\devctl-<repoID>\` on Windows. Attach never starts a supervisor; the default TUI may. \`devctl down\` stops the daemon (and, unless \`--keep-services\`, its services).
1557
+
1558
+ Override the home directory with \`DEVCTL_HOME\` (default \`~/.devctl\`).
1559
+
1560
+ ## TUI
1561
+
1562
+ The TUI is an OpenTUI React app. It attaches to a supervisor and paints status, logs, identity, doctor, config, and settings. It does not own child processes. Closing the TUI can stop services or detach, depending on \`shutdown.stop_services_on_exit\` \u2014 see [TUI](tui.md).
1563
+
1564
+ Startup locates and attaches to an already-running daemon first, independent of whether the on-disk config still parses \u2014 an attached daemon's \`config_snapshot\` (its own last-known-good in-memory config) is always the effective config, never a local reparse. Local parsing only comes into play when no daemon is reachable: a valid config spawns a fresh one, a missing config opens **setup**, and anything else (invalid YAML, a schema violation) is a real boot error with nothing started. Once attached, a \`/reload\` (or an external edit picked up by the config-file watcher) refetches the snapshot on success; a failed reload leaves a banner up under the nav bar until the next one succeeds.
1565
+
1566
+ ## CLI
1567
+
1568
+ The CLI is the same controller over the same socket. Use it for scripts, CI, and one-shot status. See [CLI](cli.md).
1569
+
1570
+ ## MCP
1571
+
1572
+ Coding agents cannot keep a TUI child alive, so MCP is a **localhost Streamable HTTP** server on the supervisor. Default off. See [MCP](mcp.md).
1573
+
1574
+ ## Configuration vs preferences
1575
+
1576
+ | What | Where |
1577
+ |------|--------|
1578
+ | Services, profiles, proxy, Google project | \`.devctl/config.yaml\` and modular YAML |
1579
+ | Machine overlay (gitignored) | \`.devctl/config.local.yaml\` and \`~/.devctl/config.local.yaml\` |
1580
+ | TUI theme, keys, MCP listen flag | \`~/.devctl/tui.json\` (or \`DEVCTL_TUI_CONFIG\`) |
1581
+ | Session / lock / socket | \`~/.devctl/state/<repoID>/\` |
1582
+ | Persisted logs | \`~/.devctl/logs/\` |
1583
+ | Log exports | \`~/.devctl/exports/\` |
1584
+ | Credential files (if no keychain) | \`~/.devctl/credentials/\` (mode \`0600\`) |
1585
+
1586
+ ## Typical loop
1587
+
1588
+ \`\`\`mermaid
1589
+ flowchart LR
1590
+ setup[".devctl/config.yaml<br/>or devctl setup"] --> validate["devctl config validate"]
1591
+ validate --> doctor["devctl doctor"]
1592
+ doctor --> run["devctl<br/>or devctl start"]
1593
+ run --> logs["Logs"]
1594
+ logs -.-> auth["Auth only if Google is required"]
1595
+ \`\`\`
1596
+
1597
+ Local-only services (the [demo platform](../examples/demo-platform/README.md)) run without \`gcloud\`.
1598
+
1599
+ ## Related
1600
+
1601
+ - [Installation](installation.md)
1602
+ - [Configuration](configuration.md)
1603
+ - [TUI](tui.md)
1604
+ - [CLI](cli.md)
1605
+ - [MCP](mcp.md)
1606
+ `},{path:"docs/platform-bets.md",title:"Platform bets (Phase 5)",body:`# Platform bets (Phase 5)
1607
+
1608
+ These are separate products. Do not start them until Phases 1\u20133 of the product-gaps roadmap are in daily use. Each item needs its own scoped design.
1609
+
1610
+ | Item | Why it is late | Honest constraint |
1611
+ |------|----------------|-------------------|
1612
+ | Worker inside \`bun build --compile\` | Binaries already degrade to in-process | Needs a Bun embedding incantation that actually resolves at runtime; fallback stays |
1613
+ | Structured-clone benchmark / coalesce stats echo | Measure, not defect | Source/npm only |
1614
+ | Remote / SSH / Dev Container supervisor | Breaks \u201Cloopback + one socket per checkout\u201D | New process model, auth on RPC |
1615
+ | Multi-repo TUI | Two checkouts are two daemons by design | Picker that attaches to another \`repoID\` |
1616
+ | K8s / Skaffold import | Cluster objects have no local equivalent | Keep discovery hints only unless we invent a tiny subset |
1617
+ | Container \`build\`, networks, limits, \`--workdir\` | Compose parity | Schema + \`containers.ts\` only; no k8s |
1618
+ | OIDC browser / device / refresh persistence | Plugin is client-credentials by design | New plugin, not core Google |
1619
+ | OIDC as **route** auth | Proxy auth is \`none\` / \`iap\` / \`service_account\` | New proxy adapter path |
1620
+ | Non-Google SSO in core | Violates \u201CGoogle is an adapter\u201D if it lands in domain | Plugin only |
1621
+ | Windows named-pipe DACL | Bun does not expose DACL | Document until Bun can; optional RPC token is a bigger threat-model change (Unix also trusts anyone who can open the socket) |
1622
+ | Apple notarization / Authenticode | Release/legal, not app code | Signing pipeline in \`.github\` |
1623
+ | Custom TUI layouts, crash bell, mouse drag-select | Chrome, not orchestration | After parity |
1624
+ | Workflow DAG beyond tasks + start waves | Tasks already exist | Only if \`/run\` pickers prove insufficient |
1625
+
1626
+ Interactive \`gcloud\` login stays a local TTY flow. MCP never owns a browser or a login TTY.
1627
+
1628
+ ## Related
1629
+
1630
+ - [Architecture](architecture.md)
1631
+ - [Plugins](plugins.md)
1632
+ - [MCP](mcp.md)
1633
+ `},{path:"docs/plugins.md",title:"Plugins",body:`# Plugins
1634
+
1635
+ Plugins extend devctl without changing the supervisor. A plugin is a JavaScript or TypeScript module loaded from \`plugins[].path\` when the supervisor starts. Relative paths are resolved from the repository root.
1636
+
1637
+ \`\`\`yaml
1638
+ version: 1
1639
+ plugins:
1640
+ - path: ./plugins/team/index.ts
1641
+ \`\`\`
1642
+
1643
+ Every plugin must export the current SDK version as a named export. The current version is \`1\`.
1644
+
1645
+ \`\`\`ts
1646
+ import { PLUGIN_SDK_VERSION } from "../../app/src/plugin-sdk.ts";
1647
+
1648
+ export const sdkVersion = PLUGIN_SDK_VERSION;
1649
+ \`\`\`
1650
+
1651
+ An incompatible, malformed, or throwing plugin is skipped and reported in the devctl log instead of crashing the daemon. Configuration that depends on an extension from the skipped plugin still fails with a focused \u201Cunknown \u2026\u201D error; devctl never silently ignores an unknown health check, identity type, or environment source.
1652
+
1653
+ ## Extension points
1654
+
1655
+ A module may export any combination of these named arrays:
1656
+
1657
+ | Export | Required shape | Purpose |
1658
+ |--------|----------------|---------|
1659
+ | \`environmentSources\` | \`{ name, load(ctx) }\` | Add values to a service environment |
1660
+ | \`healthChecks\` | \`{ name, check(config, ctx) }\` | Implement a custom health-check type |
1661
+ | \`identityProviders\` | \`{ name, accepts(config), resolve(config, detect) }\` | Resolve custom service identities |
1662
+ | \`tokenProviders\` | \`{ name, accepts(identity), fetch(identity, audience, scopes, oauth?) }\` | Mint and refresh access tokens |
1663
+ | \`logParsers\` | \`{ name, parse(line) }\` | Parse service log lines |
1664
+ | \`proxyMiddleware\` | \`{ name, apply(ctx) }\` | Participate in proxy request handling |
1665
+
1666
+ The TypeScript contracts and SDK constant are exported by [\`app/src/plugin-sdk.ts\`](../app/src/plugin-sdk.ts). A plugin must export arrays, each entry must have a non-empty \`name\`, and the methods shown above must be functions. Keep plugin startup code small: top-level exceptions cause the whole module to be skipped.
1667
+
1668
+ ## Custom identity configuration
1669
+
1670
+ \`services.<name>.identity.config\` is an opaque object passed unchanged to the selected identity provider. devctl owns \`type\`; the plugin owns the keys under \`config\`.
1671
+
1672
+ \`\`\`yaml
1673
+ services:
1674
+ api:
1675
+ command: [bun, run, src/api.ts]
1676
+ identity:
1677
+ type: my_provider
1678
+ config:
1679
+ tenant: development
1680
+ \`\`\`
1681
+
1682
+ An identity provider can return a stable \`tokenKey\`. Token providers use that key to recognize the identity, cache tokens, and refresh them safely.
1683
+
1684
+ ## Generic OIDC reference plugin
1685
+
1686
+ The repository includes [\`plugins/oidc/index.ts\`](../plugins/oidc/index.ts), a generic OAuth 2.0 client-credentials provider with OpenID Connect discovery.
1687
+
1688
+ \`\`\`yaml
1689
+ version: 1
1690
+ plugins:
1691
+ - path: ./plugins/oidc/index.ts
1692
+
1693
+ services:
1694
+ api:
1695
+ command: [bun, run, src/api.ts]
1696
+ identity:
1697
+ type: oidc
1698
+ config:
1699
+ issuer: https://identity.example.com
1700
+ client_id: local-api
1701
+ client_secret_env: DEVCTL_OIDC_CLIENT_SECRET
1702
+ scopes: [api.read]
1703
+ audience: https://api.example.com
1704
+ \`\`\`
1705
+
1706
+ The plugin discovers \`token_endpoint\` from \`<issuer>/.well-known/openid-configuration\`. Set \`token_endpoint\` explicitly to bypass discovery. \`scopes\` may be an array or a space-separated string, and a caller-provided audience or scope list takes precedence over the configured defaults.
1707
+
1708
+ Prefer \`client_secret_env\` and supply the secret through your shell, keychain, or secret manager. Inline \`client_secret\` is supported for local experiments but places a credential in the repository configuration. The reference plugin keeps resolved configuration only in memory and never writes tokens or secrets to disk.
1709
+
1710
+ The OIDC plugin is intentionally a reference implementation: it supports client credentials, not browser login, authorization code, device code, refresh-token persistence, or provider-specific claims.
1711
+
1712
+ ## Operational notes
1713
+
1714
+ - Plugin code runs inside the supervisor process and receives the same permissions. Only load code you trust.
1715
+ - Adding or removing \`plugins[].path\` entries is hot-applied on reload (token providers and proxy middleware swap immediately). Editing an already-loaded file still requires restarting the supervisor \u2014 Bun\u2019s module cache does not reload the same path. A running service keeps its old environment until that service restarts.
1716
+ - A configured plugin path must exist. Invalid or missing paths are rejected during configuration validation.
1717
+ - Unknown custom environment sources, health types, and identity types are errors after plugins load.
1718
+
1719
+ ## Related
1720
+
1721
+ - [Configuration](configuration.md)
1722
+ - [Environment](environment.md)
1723
+ - [Authentication](authentication.md)
1724
+ - [Security](security.md)
1725
+ `},{path:"docs/profiles.md",title:"Profiles",body:"# Profiles\n\nA profile is a named list of services plus optional extra environment.\n\n```yaml\nprofiles:\n data:\n services: [postgres]\n minimal:\n services: [identity, invoices-api]\n backend:\n services: [identity, invoices-api, invoices-worker]\n environment:\n LOG_LEVEL: DEBUG\n full:\n services: [identity, invoices-api, invoices-worker, billing-console]\n```\n\n```bash\ndevctl start --profile backend\n```\n\nThe TUI **profiles** screen (`o` or `/profiles`) lists configured profiles. `enter` selects one and offers start. None are hard-coded.\n\nEmpty-dashboard `enter` uses the first profile name **alphabetically** when no session profile is set \u2014 YAML key order does not matter. In the [demo platform](../examples/demo-platform/README.md) that is `backend`, not `data`, even though `data` is listed first in the file.\n\n`devctl start` / MCP `start_services` with **no** profile and **no** names starts the active session profile, or the first configured profile (alphabetically). With no profiles it fails closed. Pass `--profile` or explicit names to stay on a subset. It never expands to every service just because the list was empty.\n\nThe demo also defines `data` (opt-in Docker/PostgreSQL). It is not a default profile; start it with `--profile data`.\n\n## Sessions\n\nPer-repo state lives under `~/.devctl/state/<repoID>/`:\n\n| File | Role |\n|------|------|\n| `state.json` | session id, profile, pid / command / cwd / startTime / ports |\n| `devctl.lock` | supervisor lock (stale locks from dead PIDs are replaced) |\n| `devctl.sock` | JSON-RPC socket for TUI, CLI, and attach (Unix) |\n| `\\\\.\\pipe\\devctl-<repoID>` | Named pipe used instead of the socket on Windows |\n\nA leftover `~/.devctl/sessions/<repoID>/` is migrated once.\n\nA new supervisor **adopts** leftover processes only when pid + command + cwd + startTime still match. An empty cwd on either side is ignored. It never signals an unrelated PID. `SessionRecovered` is published when anything is adopted. Adopted processes keep health polling; stdout/stderr from before adopt are not captured. A port-only leftover is logged and shown in Doctor; it is not attached.\n\nOverride the home directory with `DEVCTL_HOME`.\n\n## Related\n\n- [Services](services.md)\n- [How it fits together](overview.md)\n- [CLI](cli.md)\n"},{path:"docs/proxy.md",title:"Proxy",body:"# Proxy\n\nThe local proxy injects authentication so services do not each implement Google/IAP logic. It runs **inside the supervisor**.\n\nThere is no implicit listen port. `proxy.listen.port` is **required** when `proxy.enabled` is true \u2014 `devctl config validate` and any load fail with `proxy.listen.port is required when proxy.enabled is true` (exit **2**). `devctl setup` writes `127.0.0.1:8080` as a starter; the demo uses `127.0.0.1:18080`. Binding to `0.0.0.0` or `::` is rejected.\n\n`devctl proxy start`, TUI `n` on the proxy screen, and MCP `start_proxy` still refuse a `0` port even when the proxy is not enabled: same wording, CLI exit **7**.\n\n```bash\ndevctl proxy start\ndevctl proxy status\ndevctl proxy stop\n```\n\nThe TUI **proxy** tab (`p`) shows status, routes, and a live log of recent requests. `n` starts, `x` stops. SA email is shown when a route uses one.\n\nEach request gets `X-Devctl-Request-ID` \u2014 propagated from the caller if it sent one, generated otherwise \u2014 and it's echoed back on the response so a caller can find its own request in the log below. Proxy logs never include `Authorization` headers. Bodies are streamed.\n\nWebSocket upgrades use the same route matching, identity injection, middleware, request logging, and statistics as ordinary HTTP traffic (HMR and other upgraded connections behind a route). Active upgraded sockets are closed during proxy shutdown so `devctl down` cannot hang.\n\nIf `proxy.enabled` is true, `devctl start` also starts the proxy.\n\n## Routes\n\n```yaml\nproxy:\n enabled: true\n listen:\n host: 127.0.0.1\n port: 8080\n routes:\n - name: invoices-api\n match:\n host: invoices-api.local\n path: \"\"\n upstream:\n url: http://127.0.0.1:18000\n auth:\n type: none # none | iap | service_account\n identity: user # or { type: service_account, service_account: email }\n # IAP only: audience is required. Optional client_id + client_secret\n # mint the ID token with that OAuth client instead of ADC's default.\n # client_secret may be a literal or ${NAME} / ${env.NAME}.\n```\n\nMatch is host + optional path prefix.\n\n### Per-service routes\n\nOptional `proxy` on a service is one route fragment or a list. At load they append to the **same** global `proxy.routes` list with stable names (`<service>` or `<service>-<n>`). Duplicate names fail validation. Runtime stays one listener.\n\n```yaml\nservices:\n api:\n command: python main.py\n proxy:\n - match:\n path: /api\n upstream:\n url: http://127.0.0.1:8000\n```\n\n## Expose \u2014 the proxy as a stable entry point\n\nInstead of hand-writing a route, a service can be **exposed** through the proxy at a stable, logical address. The synthesized route addresses its target by service name, so the proxy resolves the service's **current** port at request time \u2014 a service that restarts on a different auto-assigned port is followed with no proxy reload and no consumer change.\n\n```yaml\nproxy:\n enabled: true # expose requires an enabled proxy\n listen: { host: 127.0.0.1, port: 8080 }\nservices:\n invoices-api:\n command: python main.py\n ports: { http: 18000 }\n expose: true # \u2192 route \"invoices-api\", match host invoices-api.local\n```\n\n`expose: true` matches host `<service>.local` and forwards to the service's `http` port. Exposure is host-based: the request path is forwarded verbatim, so a path prefix belongs on a hand-written route, not here. Use the object form to override the host or port:\n\n```yaml\n expose:\n host: api.internal # default: <service>.local\n port: grpc # named port to forward to (default: http)\n```\n\nSet `proxy.gateway: true` to expose **every** HTTP-capable service (one with a port named `http`) at once \u2014 sugar over per-service `expose`. For selective exposure, leave `gateway` off and mark services individually.\n\nA hand-written route or `proxy:` fragment of the same name always wins over a synthesized one, so you can override any auto route (for example to attach auth).\n\n**Auth is always `none` on synthesized routes.** An internal service-to-service hop never silently acquires a service's identity token \u2014 injecting credentials stays an explicit choice you make with a hand-written route.\n\n### Referencing an exposed service \u2014 `${services.<name>.url}`\n\n`${services.<name>.url}` and `${services.<name>.host}` give a service a stable logical address in another service's environment:\n\n```yaml\nservices:\n billing-console:\n environment:\n API_URL: ${services.invoices-api.url}\n```\n\n- **Direct** (target not exposed): resolves to `http://127.0.0.1:<port>` \u2014 a startup snapshot, like `${services.<name>.port}`.\n- **Hub** (target exposed and proxy enabled): resolves to the proxy entry address, e.g. `http://invoices-api.local:8080`. This is stable \u2014 the consumer keeps working even when the target moves to a new port.\n\nHost-based addressing is for **host** clients: `<service>.local` must resolve to `127.0.0.1` on the machine that makes the request \u2014 add it to `/etc/hosts` or your resolver. A container's loopback is isolated from the host proxy, and the container schema has no host-network or extra-hosts mode, so do not point a container at `<service>.local`.\n\n## Token endpoint\n\nOptional `GET /token` (`proxy.token_endpoint`) binds to loopback (never `0.0.0.0` or `::`), requires `X-Devctl-Internal-Token`, and only accepts loopback peers.\n\n```json\n{\n \"access_token\": \"\u2026\",\n \"token_type\": \"Bearer\",\n \"expires_at\": \"2026-08-30T00:05:00.000Z\",\n \"identity\": \"user\"\n}\n```\n\nManaged processes receive `DEVCTL_TOKEN_URL` (rewritten to the bound port after listen) and `DEVCTL_INTERNAL_TOKEN`, not raw tokens in the environment.\n\n## Live request log\n\nThe proxy keeps the last 100 requests in memory \u2014 method, path, matched route (blank for a request that matched no route, still logged as a 404), identity key used, status, duration, and request id \u2014 and reports a running total/error count alongside them. This is part of the regular status snapshot, so it updates the same way everything else in the TUI does: the moment a request refreshes a token or hits a route, the **proxy** tab reflects it without pressing `r` or restarting anything.\n\nPaths are redacted the same way response header values already are, since a query string can carry secrets. Nothing here is persisted \u2014 it's an in-memory ring buffer, reset on daemon restart.\n\n## Request flow\n\n```mermaid\nflowchart TB\n client[\"Local client\"] --> listen[\"127.0.0.1:proxy\"]\n listen --> match[\"Match route: host + path prefix\"]\n match --> ident{\"identity.type\"}\n ident -->|user| adc[\"Application Default Credentials\"]\n ident -->|service_account| sa[\"Impersonated SA token\"]\n adc --> auth{\"auth.type\"}\n sa --> auth\n auth -->|iap| iap[\"IAP ID token for audience\"]\n auth -->|none| inject\n auth -->|service_account| satoken[\"OAuth access token for the SA\"]\n satoken --> inject\n iap --> inject[\"Inject Authorization + X-Devctl-Request-ID\"]\n inject --> up[\"Stream to upstream.url\"]\n```\n\nA missing `identity.type` on an IAP route is a configuration error.\n\n## Related\n\n- [IAP](iap.md)\n- [Impersonation](impersonation.md)\n- [Security](security.md)\n- [TUI](tui.md)\n"},{path:"docs/quickstart.md",title:"Quick start",body:`# Quick start
1726
+
1727
+ From a repository that should be orchestrated:
1728
+
1729
+ \`\`\`bash
1730
+ cd your-repo
1731
+ devctl setup
1732
+ gcloud auth application-default login # only if you need Google Cloud
1733
+ devctl doctor
1734
+ devctl
1735
+ \`\`\`
1736
+
1737
+ \`devctl setup\` is a 9-step CLI wizard (repo root, project name, Google project, optional ADC login, optional SA email, optional IAP audience, proxy port, default profile, then doctor). If \`.devctl/config.yaml\` is missing it writes a starter file.
1738
+
1739
+ With no config, the TUI opens **setup** instead of exiting: Enter writes a starter config, Esc leaves.
1740
+
1741
+ ## In the TUI
1742
+
1743
+ 1. Press \`o\` to pick a profile (if you defined any).
1744
+ 2. On an empty dashboard, \`enter\` starts the default profile (first profile name alphabetically) after a plan overlay.
1745
+ 3. \`n\` starts the highlighted row or the space-selected set; \`x\` stops; \`R\` restarts.
1746
+ 4. \`l\` opens centralized logs. Identity is \`a\`. Credentials (never raw tokens) are the **credentials** tab.
1747
+
1748
+ See [TUI](tui.md) for every screen and key.
1749
+
1750
+ ## CLI equivalent
1751
+
1752
+ \`\`\`bash
1753
+ devctl start --profile backend
1754
+ devctl status
1755
+ devctl logs invoices-api
1756
+ devctl attach
1757
+ devctl down
1758
+ \`\`\`
1759
+
1760
+ \`devctl start\` with no profile and no service names starts the active session profile, or the first configured profile. Pass \`--profile\` or explicit names to stay narrower. With no profiles, start fails instead of launching every service. It always leaves the daemon running after it exits \u2014 \`--detach\` is deprecated and no longer needed for that.
1761
+
1762
+ \`devctl attach\` only dials an existing supervisor. If nothing is listening, start with \`devctl start\` first. \`devctl down\` stops the daemon when you are done (add \`--keep-services\` to leave services running).
1763
+
1764
+ ## Try the demo
1765
+
1766
+ Node.js 18 or later. From a clone:
1767
+
1768
+ \`\`\`bash
1769
+ cd examples/demo-platform
1770
+ npx @amr-m-abdelgawad/devctl@latest
1771
+ \`\`\`
1772
+
1773
+ From a source checkout (\`bun\` on PATH):
1774
+
1775
+ \`\`\`bash
1776
+ cd examples/demo-platform
1777
+ bun run ../../app/src/bin.ts
1778
+ \`\`\`
1779
+
1780
+ No Google Cloud for the host services. Profiles: \`minimal\`, \`backend\`, \`full\`, and opt-in \`data\` (Docker/PostgreSQL). Empty start is \`backend\` (first name alphabetically). Details: [demo platform](../examples/demo-platform/README.md).
1781
+
1782
+ ## Related
1783
+
1784
+ - [Installation](installation.md)
1785
+ - [Configuration](configuration.md)
1786
+ - [Developer setup](developer-setup.md)
1787
+ - [Agent skills](../skills/README.md)
1788
+ - [Doctor](doctor.md)
1789
+ `},{path:"docs/security.md",title:"Security",body:`<div align="center">
1790
+
1791
+ # Security
1792
+
1793
+ **Loopback, redaction, no private keys.**
1794
+
1795
+ Tokens never sit in the TUI, logs, or MCP output. Listeners bind \`127.0.0.1\`. Service-account keys are never created.
1796
+
1797
+ <p>
1798
+ <a href="#what-we-guarantee"><strong>Guarantees</strong></a>
1799
+ \xB7
1800
+ <a href="#bind-rules">Bind rules</a>
1801
+ \xB7
1802
+ <a href="#secrets-and-reveal">Secrets</a>
1803
+ \xB7
1804
+ <a href="#identity">Identity</a>
1805
+ \xB7
1806
+ <a href="#on-disk">On disk</a>
1807
+ </p>
1808
+
1809
+ </div>
1810
+
1811
+ ---
1812
+
1813
+ ## What we guarantee
1814
+
1815
+ | Rule | What you see |
1816
+ |------|----------------|
1817
+ | **No tokens on screen** | TUI, \`devctl status\`, and MCP tool results never print access tokens |
1818
+ | **Redacted env** | Names matching PASSWORD, SECRET, TOKEN, PRIVATE_KEY, CLIENT_SECRET, API_KEY, CREDENTIAL, ACCESS_KEY, AUTH_KEY \u2192 \`********\` |
1819
+ | **Loopback only** | Proxy, token endpoint, and MCP refuse \`0.0.0.0\`, \`::\`, and other non-loopback binds |
1820
+ | **Argv by default** | Shell metacharacters fail validation unless \`shell: true\` |
1821
+ | **No SA keys** | Impersonation uses IAM Credentials APIs, never a downloaded JSON key |
1822
+ | **Config is not a secret store** | Working dirs join the repo root. Put secrets in overlays, keychain, or Secret Manager |
1823
+
1824
+ Extra redaction: \`secrets.extra_markers\` and \`secrets.extra_patterns\` in \`.devctl\`.
1825
+
1826
+ ---
1827
+
1828
+ ## Bind rules
1829
+
1830
+ \`\`\`mermaid
1831
+ flowchart TB
1832
+ ask["Listen request"] --> host{"Host"}
1833
+ host -->|127.0.0.1 / ::1| ok["Bind"]
1834
+ host -->|0.0.0.0 / :: / other| refuse["Refused"]
1835
+ ok --> proxy["Proxy"]
1836
+ ok --> token["GET /token"]
1837
+ ok --> mcp["MCP /mcp"]
1838
+ \`\`\`
1839
+
1840
+ Three listeners, same rule:
1841
+
1842
+ | Listener | Auth at the door |
1843
+ |----------|------------------|
1844
+ | **Proxy** | Route identity (user ADC or impersonated SA). Logs never include \`Authorization\` |
1845
+ | **Token endpoint** | \`X-Devctl-Internal-Token\` + loopback peer only. Returns \`access_token\` to that caller |
1846
+ | **MCP** | Off by default. Mutating tools need \`Authorization: Bearer\` (session token). Copied snippets include it; \`get_status\` does not |
1847
+
1848
+ Host child processes always get \`DEVCTL_INTERNAL_TOKEN\`. They only get \`DEVCTL_TOKEN_URL\` when \`proxy.token_endpoint.enabled\` is turned on (off by default) \u2014 never a raw Google token in the environment. Containers get neither value: the loopback token endpoint is not reachable as container loopback, and embedding the internal token in inspectable container metadata would add exposure without providing access. With the token endpoint off, a service that needs its own Google credential (rather than relying on the proxy to inject one on inbound requests) must get it another way, e.g. its own ADC discovery.
1849
+
1850
+ ---
1851
+
1852
+ ## Secrets and \`/reveal\`
1853
+
1854
+ \`\`\`mermaid
1855
+ flowchart LR
1856
+ env["Process env"] --> detect["Name / pattern detector"]
1857
+ detect --> hide["******** in TUI"]
1858
+ hide -->|/reveal this session| show["Values shown \xB7 header: secrets shown"]
1859
+ \`\`\`
1860
+
1861
+ \`/reveal\` lasts for this TUI session only. The header says **secrets shown** so it cannot stay silent.
1862
+
1863
+ Proxy log lines include method, path, route, identity, status, duration \u2014 never the bearer header.
1864
+
1865
+ ---
1866
+
1867
+ ## Identity
1868
+
1869
+ User identity and service identity are never swapped. A route or service must declare which one to use. A user ADC token is not substituted for \`service_account\`.
1870
+
1871
+ \`\`\`mermaid
1872
+ flowchart LR
1873
+ user["Developer ADC"] --> iam["IAM Credentials"]
1874
+ iam --> sat["Short-lived SA token"]
1875
+ \`\`\`
1876
+
1877
+ Developers need \`roles/iam.serviceAccountTokenCreator\` on each target SA (group binding preferred). Doctor reports AVAILABLE / UNAVAILABLE. See [Impersonation](impersonation.md) and [Admin setup](admin-setup.md).
1878
+
1879
+ ---
1880
+
1881
+ ## Commands
1882
+
1883
+ String commands that contain \`|\`, \`||\`, \`&&\`, \`;\`, \`>\`, \`>>\`, \`<\`, or \`&\` fail \`config validate\` unless the service sets \`shell: true\`. Prefer argv lists:
1884
+
1885
+ \`\`\`yaml
1886
+ command: [python3, main.py]
1887
+ shell: false
1888
+ \`\`\`
1889
+
1890
+ ---
1891
+
1892
+ ## On disk
1893
+
1894
+ Two checkouts do not share a lock. \`repoID\` is \`sha256(canonical repo root)\` (16 hex chars).
1895
+
1896
+ | Path | Mode / note |
1897
+ |------|-------------|
1898
+ | \`~/.devctl/state/<repoID>/\` | \`state.json\`, \`devctl.lock\`, and on Unix \`devctl.sock\`. Windows attach uses \`\\\\.\\pipe\\devctl-<repoID>\` |
1899
+ | leftover \`~/.devctl/sessions/\` | Migrated once |
1900
+ | Stale lock from a dead PID | Replaced |
1901
+ | \`~/.devctl/credentials/\` | Directory \`0700\`, files \`0600\` (Unix mode bits; Windows uses ACLs). OS keychain holds tokens; the file fallback stores metadata only (no access token). Cache keys are sanitized so they are valid filenames on Windows. Restart remints via ADC |
1902
+ | \`.devctl/config.local.yaml\` | Gitignore-friendly overlay \u2014 still do not commit secrets |
1903
+
1904
+ On Unix, the owner-only state directory restricts access to the supervisor RPC socket. Bun's networking API does not currently expose named-pipe DACL configuration on Windows, so devctl cannot promise equivalent current-user-only access control for \`\\\\.\\pipe\\devctl-<repoID>\`; this is a documented platform limitation rather than enforced parity.
1905
+
1906
+ Override the home directory with \`DEVCTL_HOME\`.
1907
+
1908
+ ---
1909
+
1910
+ ## Threat model (local)
1911
+
1912
+ \`devctl\` is a **localhost** orchestrator. It is not a multi-tenant server.
1913
+
1914
+ - Anyone who can reach your user account can reach \`127.0.0.1\` listeners.
1915
+ - MCP is off until you flip it. Treat the copied bearer token like a session secret.
1916
+ - \`/reveal\` and log export write what you can already see on that machine.
1917
+ - Doctor never enables Google APIs or grants IAM.
1918
+
1919
+ ---
1920
+
1921
+ ## Related
1922
+
1923
+ | Page | Why |
1924
+ |------|-----|
1925
+ | [Authentication](authentication.md) | ADC, project source, \`devctl auth\` |
1926
+ | [Impersonation](impersonation.md) | SA tokens without keys |
1927
+ | [IAP](iap.md) | Audience + identity on each route |
1928
+ | [Proxy](proxy.md) | Request flow and token endpoint |
1929
+ | [MCP](mcp.md) | Loopback Streamable HTTP |
1930
+ | [Environment](environment.md) | Source order, keychain, Secret Manager |
1931
+ | [Security policy](../SECURITY.md) | How to report a vulnerability |
1932
+ `},{path:"docs/services.md",title:"Services",body:`# Services
1933
+
1934
+ Services are configuration objects. \`devctl\` never hard-codes names, ports, or commands.
1935
+
1936
+ \`\`\`yaml
1937
+ services:
1938
+ api:
1939
+ description: Invoices API
1940
+ command: [python3, main.py] # argv preferred
1941
+ shell: false # set true only when a shell is required
1942
+ working_dir: invoices-api # relative to repository root
1943
+ dependencies: [identity]
1944
+ ports:
1945
+ http: 8000 # or auto
1946
+ environment:
1947
+ AUTH_URL: http://127.0.0.1:\${services.identity.ports.http}
1948
+ required: [AUTH_URL]
1949
+ defaults:
1950
+ LOG_LEVEL: INFO
1951
+ health:
1952
+ type: http # http | tcp | process | command
1953
+ url: http://127.0.0.1:8000/health
1954
+ interval_seconds: 2
1955
+ timeout_seconds: 1
1956
+ start_period_seconds: 10 # early failures remain STARTING
1957
+ unhealthy_threshold: 3 # consecutive failures before restart
1958
+ healthy_reset_threshold: 10 # healthy checks before retry budget resets
1959
+ identity:
1960
+ type: user # or service_account
1961
+ proxy: # optional; merged into the global proxy at load
1962
+ match: { path: /api }
1963
+ upstream: { url: http://127.0.0.1:8000 }
1964
+ expose: true # optional; reach it through the proxy at api.local
1965
+ restart:
1966
+ policy: on_failure # never | on_failure | always
1967
+ max_retries: 3
1968
+ backoff_seconds: 2
1969
+ startup:
1970
+ wait_for_healthy: true
1971
+ timeout_seconds: 20
1972
+ capabilities: [local_http]
1973
+ logs:
1974
+ stdout: true
1975
+ stderr: true
1976
+ hooks:
1977
+ pre_start: [python3, migrate.py]
1978
+ post_start: [python3, warm_cache.py]
1979
+ \`\`\`
1980
+
1981
+ Working directories resolve from the repository root (the directory that contains \`.devctl\`), not the process cwd.
1982
+
1983
+ \`\${services.<name>.ports.<port>}\` interpolates another service's port; \`\${services.<name>.url}\` and \`\${services.<name>.host}\` give a stable base address that routes through the proxy when the target is exposed (see [Proxy \u2192 Expose](proxy.md)). \`expose: true\` publishes the service through the proxy at \`<service>.local\` when \`proxy.enabled\` is true; \`proxy.gateway: true\` does the same for every HTTP service at once. Neither flag creates a route if the proxy is off.
1984
+
1985
+ String commands that contain \`|\`, \`||\`, \`&&\`, \`;\`, \`>\`, \`>>\`, \`<\`, or \`&\` fail validation unless \`shell: true\`.
1986
+
1987
+ ## Hooks and one-off tasks
1988
+
1989
+ \`pre_start\` runs to completion before an explicitly started service is spawned. A non-zero exit fails the service and prevents launch. \`post_start\` runs after a successful spawn; failure stops and marks the service failed. Hooks use the service's resolved environment, working directory, and \`shell\` setting. They do not run during automatic crash or health restarts.
1990
+
1991
+ Top-level tasks are transient commands: they are not retained as services and do not restart. Declared service dependencies are started first, and the task uses the same layered environment resolution as services.
1992
+
1993
+ \`\`\`yaml
1994
+ tasks:
1995
+ migrate:
1996
+ command: [python3, migrate.py]
1997
+ working_dir: invoices-api
1998
+ dependencies: [postgres]
1999
+ environment:
2000
+ MODE: development
2001
+ \`\`\`
2002
+
2003
+ Run one with \`devctl run migrate\`. Its exit code determines command success, and its stdout/stderr are also captured in supervisor logs under \`task:migrate\`.
2004
+
2005
+ Use \`devctl exec api -- python3 check.py\` to run an ad-hoc command in a service's same resolved context without starting it. \`devctl exec api --print-env\` inspects that context with secrets redacted by default.
2006
+
2007
+ ## Container services
2008
+
2009
+ Set \`container.image\` to let devctl own a Docker or Podman container with the
2010
+ same dependency, health, logging, restart, and shutdown lifecycle as a host
2011
+ service. \`runtime\` defaults to Docker. A service \`command\`, when present,
2012
+ overrides the image command.
2013
+
2014
+ \`\`\`yaml
2015
+ services:
2016
+ postgres:
2017
+ ports:
2018
+ db: 15432
2019
+ container:
2020
+ image: postgres:16
2021
+ runtime: docker
2022
+ ports:
2023
+ db: 5432 # service port name \u2192 port inside the container
2024
+ env:
2025
+ POSTGRES_PASSWORD: local
2026
+ volumes:
2027
+ - pgdata:/var/lib/postgresql/data
2028
+ health:
2029
+ type: tcp
2030
+ address: 127.0.0.1:15432
2031
+ \`\`\`
2032
+
2033
+ Container names are deterministic and scoped to the repository, allowing a
2034
+ new devctl daemon to adopt containers left running by its predecessor. Secret
2035
+ environment values are supplied through the runtime process environment and
2036
+ are not placed in command-line arguments. Published ports bind to
2037
+ \`127.0.0.1\` by default rather than every network interface. Containers do not
2038
+ inherit the caller's entire shell environment; profile, dotenv, keychain,
2039
+ secret-manager, defaults, explicit service/container variables, plugin sources,
2040
+ and non-secret runtime metadata still apply. \`devctl down\` stops and removes
2041
+ managed containers; container exit codes feed the normal restart policy.
2042
+
2043
+ ## Lifecycle
2044
+
2045
+ \`\`\`mermaid
2046
+ stateDiagram-v2
2047
+ [*] --> STOPPED
2048
+ STOPPED --> STARTING
2049
+ STARTING --> RUNNING
2050
+ STARTING --> FAILED
2051
+ RUNNING --> HEALTHY: probe ok
2052
+ RUNNING --> UNHEALTHY: probe fail
2053
+ HEALTHY --> UNHEALTHY: probe fail
2054
+ UNHEALTHY --> HEALTHY: probe ok
2055
+ HEALTHY --> STOPPING
2056
+ UNHEALTHY --> STOPPING
2057
+ RUNNING --> STOPPING
2058
+ STOPPING --> STOPPED
2059
+ FAILED --> RESTARTING: on_failure / always
2060
+ STOPPED --> RESTARTING: always
2061
+ RESTARTING --> STARTING
2062
+ \`\`\`
2063
+
2064
+ The TUI and CLI display \`HEALTHY\` / \`UNHEALTHY\` when the process is running and the health probe has an answer.
2065
+
2066
+ Independent services in the same wave start and stop in parallel. Dependents wait. Cycles are configuration errors.
2067
+
2068
+ Dependencies accept either the original string form or a condition:
2069
+
2070
+ \`\`\`yaml
2071
+ dependencies:
2072
+ - identity
2073
+ - service: postgres
2074
+ condition: service_healthy
2075
+ \`\`\`
2076
+
2077
+ \`service_started\` is the default and allows the dependent to launch once its dependency process has spawned. \`service_healthy\` waits for the dependency's health check. A dependency using \`service_healthy\` must define a health check; startup fails on its normal startup timeout if it never becomes healthy.
2078
+
2079
+ ## Start, stop, restart
2080
+
2081
+ Named start expands **up** the dependency graph (\`startupPlan\`): starting \`invoices-worker\` also starts \`identity\` and \`invoices-api\`, in that order.
2082
+
2083
+ > **Breaking change:** stop no longer mirrors start. \`devctl stop x\` stops \`x\` and everything that (transitively) **depends on** \`x\` \u2014 never \`x\`'s own dependencies, which other running services may still need (\`shutdownPlan\`). Previously, stopping a leaf also stopped the dependencies it had pulled in; that direction was backwards and is not preserved.
2084
+
2085
+ \`\`\`mermaid
2086
+ flowchart LR
2087
+ identity --> api["invoices-api"] --> worker["invoices-worker"]
2088
+ \`\`\`
2089
+
2090
+ Stopping \`identity\` also stops \`invoices-api\` and \`invoices-worker\` (both depend on it, transitively). Stopping \`invoices-api\` also stops \`invoices-worker\`, but leaves \`identity\` running \u2014 it's \`invoices-api\`'s dependency, not its dependent. Empty \`stop\` stops every started service but leaves the daemon itself running.
2091
+
2092
+ \`devctl restart x\` restarts only \`x\` \u2014 never its dependents. Pass \`--cascade\` to also restart everything that depends on \`x\` (the same set a \`stop x\` would affect). Either way, a restarted service's own dependencies are started first if they aren't already running, exactly like a plain \`start\` would. Waves run left to right on start; stop and cascading restart run them right to left, restricted to whichever services are actually in scope.
2093
+
2094
+ A service's restart count (against \`max_retries\`) resets to zero on a manual \`stop\` or \`start\` \u2014 including the stop/start half of a \`restart\` a client asks for \u2014 and also forgives itself once the service has run healthily for long enough. Only an automatic, health-triggered restart preserves the count across its own stop/start cycle; that's what makes \`max_retries\` actually a limit instead of resetting itself every cycle.
2095
+
2096
+ \`devctl start\` with **no** profile and **no** service names starts the active session profile, or the first configured profile \u2014 the same contract as MCP \`start_services\`. Use \`--profile\` or explicit names. With no profiles, start fails closed.
2097
+
2098
+ Default TUI profile (empty dashboard \`enter\`) is the first profile name **alphabetically**.
2099
+
2100
+ ## Health
2101
+
2102
+ | \`type\` | Probe |
2103
+ |--------|--------|
2104
+ | \`http\` | GET \`url\`; 2xx is healthy (default interval 2s, timeout 2s) |
2105
+ | \`tcp\` | Connect to \`address\` or a named port |
2106
+ | \`command\` | Run \`health.command\`; exit 0 is healthy |
2107
+ | \`process\` or empty | PID still alive |
2108
+
2109
+ During \`health.start_period_seconds\`, failing probes leave the service in its startup state and do not contribute to restart streaks. Afterward, \`health.unhealthy_threshold\` consecutive failures trigger the configured restart policy (default 3). \`health.healthy_reset_threshold\` consecutive successes forgive prior restart attempts (default 10).
2110
+
2111
+ \`devctl\` watches \`.devctl/\` and offers reload. Source-file restart is **opt-in** per service \u2014 off by default so a noisy tree cannot bounce the fleet:
2112
+
2113
+ \`\`\`yaml
2114
+ services:
2115
+ api:
2116
+ watch:
2117
+ enabled: true
2118
+ paths: ["invoices-api"] # relative to the repo root; never the whole checkout
2119
+ debounce_ms: 300
2120
+ ignore: ["**/node_modules/**", "**/.git/**"]
2121
+ \`\`\`
2122
+
2123
+ A matching change restarts only that service. Leave \`watch\` off and put \`air\`, \`bun --watch\`, or your language\u2019s reloader in \`command\` when you want in-process reload instead.
2124
+
2125
+ A service a reload adds shows up immediately, stopped \u2014 not just once it's first started. One a reload removes is forgotten immediately if it was already stopped; if it's still running, it becomes **orphaned**: still visible and stoppable by name (\`devctl stop <name>\`), but no longer restartable or reachable by a cascade, since there's no configuration left to describe how. Stopping it drops it from status entirely. A reload that references a health or identity type nothing provides \u2014 a plugin type from a service just added or edited \u2014 is rejected outright, the same as an unparseable config file, and the daemon keeps running on its last-known-good configuration.
2126
+
2127
+ Plugins can register extra health types. \`capabilities\` document intent (\`local_http\`, \`google\`, \`iap\`, \u2026) for doctor; they do not start processes.
2128
+
2129
+ ## Related
2130
+
2131
+ - [Profiles](profiles.md)
2132
+ - [Environment](environment.md)
2133
+ - [Configuration](configuration.md)
2134
+ - [Logs](logs.md)
2135
+ `},{path:"docs/troubleshooting.md",title:"Troubleshooting",body:"# Troubleshooting\n\n| Symptom | What to do |\n|---------|------------|\n| `gcloud` not installed | Install the Cloud SDK only if you need Google identity. Local-only services still run |\n| ADC unavailable | `/auth login` or `gcloud auth application-default login`, then `devctl auth status` / `/auth refresh` |\n| Wrong project | Set `google.project_id` or check `gcloud config get-value core/project`. Identity shows the source |\n| Permission denied | Ask an admin for the missing IAM role; `devctl doctor` names the resource |\n| Cannot impersonate SA | Need `roles/iam.serviceAccountTokenCreator` on that service account (group binding preferred) |\n| IAP authentication failure | Confirm audience, IAP client, and that the identity matches the route. If `client_id` is set, the ADC refresh token must belong to that OAuth client |\n| Port already in use | Doctor lists the holder. Stop a leftover, or change config. Running your own services will also show as \u201Cin use\u201D |\n| Service crashes | Open Logs, filter `ERROR` (`e`), restart with `R` |\n| Health check failure | Confirm the health URL/port; `process` checks only PID liveness |\n| Proxy unavailable / missing listen port | Pin `proxy.listen.port` (required when `proxy.enabled` is true \u2014 validate exits **2**). Then `devctl proxy start` or TUI `n`. Starting with port `0` exits **7**. Bind is loopback only |\n| Token expired | Automatic refresh uses `auth.refresh_threshold_seconds`; run `devctl auth refresh`. Open Doctor if ADC itself expired |\n| Token audience incorrect | Set `auth.audience` on the IAP route; Doctor flags missing audiences |\n| IAP used a user token for an SA route | Confirm the route identity is `service_account`; Doctor probes impersonated IAP separately |\n| Leftover process after crash | Reopen `devctl` \u2014 adopt only when pid + command + cwd + startTime match `~/.devctl/state/<hash>/state.json`. A port-only leftover is never attached |\n| `devctl attach` fails | No supervisor. Use `devctl start` first; attach never starts one |\n| `devctl status` looks empty | If the socket is down, status prints persisted state and exits 0 when nothing is running. The TUI still starts a supervisor; leftover PIDs from the previous session appear on the idle dashboard |\n| Start exits 5 or 6 | 5 = spawn failed; 6 = health never passed. Doctor then Logs |\n| `start` brought up extra services | Empty start uses the active or first profile **alphabetically**, plus dependencies \u2014 YAML key order does not matter (demo: `backend`, not `data`). Pass `--profile` or explicit names to stay narrower |\n| Docker / Podman missing or daemon down | A service in config declares `container` (the demo's `postgres` always does, even if you never start `data`). Install and start that runtime, or drop the service. Default profiles do not start postgres |\n| Doctor offers to kill a busy port that is Docker | It should not: ports owned by a running container service are healthy, and Doctor never offers to terminate the Docker or Podman daemon. Re-run Doctor after the container is up |\n| MCP tools work but nothing starts | `get_status` reports `setup_mode: true` \u2014 there is no `.devctl` yet. Have the agent call `get_setup_guide`, `search_docs`, and `validate_config`, write the files, then `reload_config` |\n| `devctl exec --print-env` hides values | Secret-like names are redacted unless you also pass `--reveal` (TUI: `/exec <svc> --print-env --reveal` or `/reveal`). MCP `exec_service` never reveals them |\n| TUI env pane looks incomplete | It now loads the same resolved map as `devctl exec --print-env`. If the chip says `config fallback`, the daemon `exec` call failed \u2014 check `/daemon` and that the supervisor is up |\n| Supervisor will not start | `devctl daemon logs` or TUI `/daemon` is the bootstrap stderr, not the service log bus |\n| TUI stale / not updating | TUI follows the event bus (20\u201350ms batch). Quit and let a new supervisor start if an old one is still listening |\n| Reload needs a restart | `devctl reload` and `/reload` list services whose command, env, ports, or identity changed |\n| Configuration invalid | `devctl config validate` \u2014 unknown fields, cycles, and missing refs fail closed. TUI `v` / `/buffer` validates before write |\n| Config on disk is broken but the TUI still opens fine | Expected: it attached to an already-running daemon and is showing its `config_snapshot` (last-known-good), not a fresh reparse of the broken file. Fix the file and `/reload` |\n| `devctl update` says unavailable | GitHub Releases API could not be reached. For npm: `npm install --global @amr-m-abdelgawad/devctl@latest`. Homebrew and GitHub binaries: see [installation](installation.md) |\n| MCP agent cannot connect | Listener is off by default. `/mcp` or `devctl mcp --on`. URL is loopback only; snippets include the bearer token |\n| `devctl: command not found` | Run `npm install --global @amr-m-abdelgawad/devctl`, then ensure npm's global binary directory is on `PATH`. See [Installation](installation.md) |\n| Bundled Bun runtime was not installed | Reinstall the npm package without `--ignore-scripts`; Bun uses its install script to select the correct platform runtime |\n\nInternal `devctl` logs appear in the Logs screen with source `devctl`.\n\n## Related\n\n- [Doctor](doctor.md)\n- [Logs](logs.md)\n- [CLI](cli.md)\n- [How it fits together](overview.md)\n"},{path:"docs/tui.md",title:"TUI",body:"# TUI\n\nThe TUI is [OpenTUI](https://opentui.com/docs/) (`@opentui/core` + `@opentui/react`). It attaches to the supervisor and never owns child processes.\n\n```bash\ncd examples/demo-platform\nnpx @amr-m-abdelgawad/devctl@latest\n```\n\nFrom a source checkout (`bun` on PATH):\n\n```bash\ncd examples/demo-platform\nbun run ../../app/src/bin.ts\n```\n\nIf a supervisor session already exists, the TUI attaches to it. Preferences: `tui.json` / `DEVCTL_TUI_CONFIG` \u2014 see [Building from source](typescript.md).\n\n## First run\n\nWith no `.devctl` configuration the TUI opens **setup**: \u201CNo configuration found. Would you like to run setup? **[Enter] Setup [Esc] Exit**\u201D. Enter starts the same 9-step wizard as `devctl setup` (OpenTUI fields, then write and attach the daemon \u2014 no process restart). Invalid existing YAML still refuses overwrite.\n\nIf a `.devctl/config.yaml` exists but fails to parse or validate, the TUI shows **Configuration error** with the actual error instead \u2014 pressing Enter here does not run setup, since that would silently overwrite the file the error is about. Fix the file and restart devctl, or run `devctl config validate` for the same error from the CLI.\n\nWhen services exist but none are running, the dashboard empty state:\n\n- `enter` starts the default profile (first profile name alphabetically) after a plan overlay\n- `n` / `x` start or stop the highlighted row (or the space-selected set)\n- a **lifecycle panel** shows start and stop waves; later start waves wait for health and do not run if a wave fails\n- the panel stays open until `esc` so you can read the result\n- `o` picks a profile, then confirms start\n\n## Quit\n\n`q` / `/exit` / `esc` twice:\n\n| `shutdown.stop_services_on_exit` | Behavior |\n|----------------------------------|----------|\n| `true` | Stop managed services and leave |\n| `false` | Detach immediately |\n| unset | Confirm: `enter` stops services, `d` detaches (daemon stays), `k` stops the daemon and leaves services (`devctl down --keep-services`), `esc` stays |\n\n**Detach** (`d`) leaves the supervisor running. **`/down --keep-services`** (or quit `k`) stops the supervisor and persists PIDs so a later start can adopt them. **`/down`** stops services and the supervisor. `/stop` only stops selected services.\n\n## Interaction model\n\nKeyboard-first. Chords use **command** on macOS and **ctrl** on Linux and Windows. Help, the status bar, and empty-state hints label the modifier for the OS you are on.\n\n| Input | What it does |\n|-------|----------------|\n| `/` | Command overlay \u2014 ranked as you type (name, alias, fuzzy, then description). `/start api` still lists `start`. `\u2191`/`\u2193` to move, `enter` to run |\n| `command+p` / `ctrl+p` | Same command overlay as `/` |\n| `command+x` / `ctrl+x` | Leader key (2s), then a shortcut \u2014 keymap overlay |\n| `?` | Grouped help \u2014 `j`/`k` scroll when the list is taller than the terminal |\n| `tab` / `shift+tab` / `1`\u2013`4` | Cycle or jump the **four nav tabs**. Other screens are `/auth`, `/credentials`, `/doctor`, `/config`, `/profiles`, `/setup`, `/stats`, `/settings`, `/mcp`. On a secondary screen, `tab` returns to the dashboard. When the strip is wider than the terminal it slides (`\u2039` `\u203A`). |\n| `s` `l` `a` `p` `d` `c` `u` | Direct letter nav when no overlay owns keys (services, logs, identity, proxy, doctor, config, setup) |\n| `r` | Refresh snapshot (doctor `r` re-runs checks) |\n| `R` | Restart selected services |\n| `j` `k` / arrows | Move selection |\n| `enter` | Start (empty dashboard) or open service detail |\n| `space` | Multi-select a service |\n| `esc` | Back / close overlay. Twice (when nothing else is open) asks to quit |\n| `f` | Focus log search (`g` jumps to latest). Remap with `keybinds.search` |\n| `z` | Expand logs to fill the terminal. `z` or `esc` exits |\n| `w` | Cycle log wrap: clip, unwrap the selected row, or wrap every long line |\n| `command+c` / `ctrl+c` | Copy the highlighted selection (drag with the mouse). Remap with `keybinds.copy` |\n| `command+=` / `ctrl+=` (and `-` / `0`) | Display size (padding/row height, not the terminal font) |\n| `esc` `esc` | Twice to quit when no overlay or back target is open. The OS copy chord does not quit |\n| Mouse | Click nav, click a service, scroll logs (toggle in Settings) |\n\nThe status bar only lists keys that work **on the current screen**. There is no idle command row \u2014 `/` and the OS palette chord open the command overlay.\n\n## Nav tabs (4)\n\n1. dashboard \xB7 2. services \xB7 3. logs \xB7 4. proxy\n\nEverything else is a slash command (or a letter jump): `/auth`, `/credentials`, `/doctor`, `/config`, `/profiles`, `/setup`, `/stats`, `/settings`. **MCP** is `/mcp`, `/agent`, or Settings \u2192 **MCP \u2192 Settings page**.\n\n## Screens\n\n- **Dashboard** \u2014 services, proxy, live log tail. Identity lives on `/auth`; ADC status is in the header. When nothing is running, a **last session** panel shows leftover PIDs from the previous supervisor (same data `devctl status` prints when the socket is down)\n- **Services** \u2014 list plus a live inspector: status chips, two-column facts, then a scrollable **resolved** env pane (dotenv, profile, secrets, plugins, runtime ports). Narrow terminals stack the panes. `enter` opens the full detail screen\n- **Service detail** \u2014 same inspector; env pane is focused so `j`/`k` scroll. `/reveal` shows secrets. `n`/`x`/`R`/`l`\n- **Logs** \u2014 ANSI color codes are stripped so wrap uses visible width; `w` cycles clip / wrap selected / wrap all. `\\\\` / `/split` opens a second pane on the same live stream (independent service filter, shared search). `/trace <id>` or Enter on a log details request id jumps search to that id. See [Logs](logs.md)\n- **Identity** \u2014 user, project, source, ADC, gcloud, configured SAs, impersonation AVAILABLE/UNAVAILABLE, IAP (no tokens). `/auth login` suspends the TUI, runs `gcloud auth application-default login` on the real terminal, then restores the TUI. `/auth logout` revokes ADC without leaving the screen\n- **Credentials** \u2014 store backend and entry names only. Tokens stay in the OS keychain or `~/.devctl/credentials`\n- **Proxy** \u2014 status + routes (match and upstream wrap instead of clipping); click a route for full details. `n` start / `x` stop\n- **Doctor** \u2014 re-runs on every visit; \u2713 / ! / \u2717 with hints. `enter` on a busy host port asks to stop that process; it never offers to kill the Docker or Podman daemon. `r` reruns\n- **Config** \u2014 merged view including **tasks**. `v` / `/buffer` opens a validate/save overlay on `cfg.configPath` (invalid YAML is not written; `esc` discards). `e` / `/edit` still opens `$EDITOR` / `DEVCTL_EDITOR`. `/diff` shows provenance (`devctl config diff`). `/reload` re-reads after an external edit\n- **Profiles** \u2014 members; `enter` selects and offers start\n- **Setup** \u2014 onboarding checklist. First-run with no config still opens here\n- **Settings** \u2014 grouped prefs: theme, display size, mouse, leader timeout, **MCP settings page**, about, reset. `\u2190`/`\u2192` writes the highlighted cycle or toggles mouse. Reset asks before restoring defaults. Saves to `~/.devctl/tui.json` unless `DEVCTL_TUI_CONFIG` is set\n- **MCP** \u2014 Listen `[ ON ]` / `[ OFF ]`, port stepper `\u2039 N \u203A`, per-agent **Copy JSON** / **Copy TOML**, and a **Tools** list grouped by purpose (inspect, logs, diagnostics, control, setup) with each tool marked `read` or `write`; `space` enables or disables the highlighted one, all on by default. Off by default. See [MCP](mcp.md)\n\n`/reveal` toggles secret env values for this session only. The header shows `secrets shown`.\n\n## Slash commands\n\n```text\n/start [service\u2026] start selection, args, or the current profile\n/stop [service\u2026]\n/down [--keep-services] stop the supervisor (and services unless --keep-services)\n/restart [--cascade|-c] named services only; cascade also restarts dependents\n R with dependents: Enter = named, c = cascade\n/run [task] one-off task; empty /run opens a picker. Output is in Logs under task:<name>\n/exec [service] -- <command\u2026>\n empty /exec opens a service picker, then type the command\n/exec <service> --print-env [--reveal]\n resolved env (dotenv, profile, secrets, plugins, ports), not config-only vars\n/logs /services /auth /credentials /proxy /mcp /doctor /config /profiles /setup\n/stats system and service statistics (sparklines when the supervisor has samples)\n/split second log pane (`\\\\`); `|` focuses the other pane\n/trace <id> set log search to a request_id / trace_id\n/dashboard return home\n/themes [name] picker with live preview; enter saves to ~/.devctl/tui.json\n/settings theme, mouse, display size, MCP page\n/filter toggle ERROR+\n/pause freeze the live log stream\n/reveal show or hide secret env values\n/wrap cycle log wrap (selected / all / clip)\n/copy copy visible logs to the clipboard\n/export [path] write filtered logs to ~/.devctl/exports (or the given path)\n/exports open the export folder\n/regex /since /until /history /edit /buffer\n/clear\n/refresh\n/reload reload .devctl\n/diff winning config sources and what they shadowed (`devctl config diff`)\n/import compose [path] [--write]\n/daemon supervisor bootstrap stderr (`devctl daemon logs`)\n/auth login|logout|refresh\n/update install a newer GitHub Release when the method is known (npm/Homebrew)\n/version current version, then the same update check (does not install)\n/exit /quit /q\n```\n\nAliases include `/up`, `/identity`, `/creds`, `/agent`, `/init`, `/home`, `/prefs`, `/task`, `/provenance`, `/bootstrap`. `/down` is no longer an alias of `/stop`.\n\n## Leader key\n\nDefault leader is `command+x` on macOS and `ctrl+x` elsewhere (2 second timeout). Then:\n\n```text\nn start x stop R restart (c cascade if dependents) s services l logs\na auth p proxy d doctor c config o profiles\nt themes e export r refresh i setup h dashboard\nq quit z fullscreen\n```\n\nOverride in `tui.json` (`keybinds`) or `DEVCTL_TUI_CONFIG`.\n\n## Layout\n\n- **Header** \u2014 product + version as text, then project and profile; chips only for running count, live proxy, MCP when on, ADC, and secrets-shown\n- **Nav** \u2014 the four primary tabs; the active tab is highlighted, not filled\n- **Body** \u2014 dashboard or a focused screen\n- **Command overlay** \u2014 `/` and `command+p` / `ctrl+p` open the same grouped list with a real OpenTUI input\n- **Status bar** \u2014 live/paused, last human result, contextual keys\n\nStatus is never color-only: `\u2713` healthy, `\u25CF` running, `!` warning, `\u2717` failed, `\u25CB` stopped.\n\n## Themes\n\n`/themes` opens a picker with live preview. Built-ins:\n\n- Product: `devctl` (default), `ember`\n- Common dark: `tokyonight`, `catppuccin`, `nord`, `gruvbox`, `kanagawa`, `dracula`, `onedark`, `monokai`, `rose-pine`, `everforest`, `github-dark`, `iceberg`, `ayu-dark`, `oxocarbon`, `night-owl`\n- Light: `catppuccin-latte`, `solarized-light`\n- Other: `solarized-dark`, `terminal` (black + VGA ANSI chrome; aliases `ansi`, `xterm`, `console`), `system` (follows macOS `AppleInterfaceStyle` / `COLORFGBG`; light uses Solarized Light)\n\nAliases: `mocha` \u2192 Catppuccin Mocha, `latte`, `one-dark`, `solarized` (dark), `rosepine`, `github`, `ayu`, `night owl`.\n\nMCP agent chips use brand colors (Claude terracotta, Cursor blue, Kilo gold, Codex green) with light/dark variants.\n\n## Related\n\n- [Logs](logs.md)\n- [MCP](mcp.md)\n- [Building from source](typescript.md)\n- [CLI](cli.md)\n"},{path:"docs/typescript.md",title:"Building from source",body:'# Building from source\n\nThe application lives in `app/`. Runtime is [Bun](https://bun.sh). The TUI is [OpenTUI](https://opentui.com/docs/).\n\n```bash\nexport PATH="$HOME/.bun/bin:$PATH"\ncd app\nbun install\nbun run src/bin.ts --help\nbun test\nbun run check:coverage\nbunx tsc --noEmit\nbun run check:architecture\nbun run check:dead\nbun run check:dup\n```\n\nFrom the repository root (after `bun install` in `app/`):\n\n```bash\nbun run app/src/bin.ts\ncd app && bun link # optional: `devctl` on PATH\n```\n\n## Layout\n\n| Path | Role |\n|------|------|\n| `app/src/bin.ts` | Entry |\n| `app/src/bootstrap/` | Composition roots (daemon + client) |\n| `app/src/application/client-runtime.ts` | Injected CLI/TUI operations and controller contract |\n| `app/src/presentation/cli/` | Commander CLI |\n| `app/src/presentation/tui/` | OpenTUI screens and overlays |\n| `app/src/presentation/tui/hooks/` | Client queries, command execution, and TUI state |\n| `app/src/presentation/tui/helpers/` | Screen-specific formatting, navigation, logs, and plan helpers |\n| `app/src/presentation/mcp/` | Streamable HTTP MCP server |\n| `app/src/domain/` | Service, identity, config, log, session, and preference types and policies |\n| `app/src/adapters/config/` | Discover, decode, merge, validate |\n| `app/src/adapters/process/` | Host process runtime |\n| `app/src/adapters/google/` | Google / IAP / tokens |\n| `app/src/adapters/daemon/supervisor.ts` | Daemon host (socket, recovery, watch, listeners) |\n| `app/src/adapters/rpc/controller.ts` | Local supervisor or socket / named-pipe client |\n| `app/tui.json` | Starter TUI preferences |\n\nLayer rules: [architecture.md](architecture.md). Check with `bun run check:architecture`. Coverage: `bun run check:coverage` (aggregate funcs/lines; OpenTUI screens and hooks are ignored). Unused files and dependencies: `bun run check:dead` ([Knip](https://knip.dev)). Copy-paste clones: `bun run check:dup` ([jscpd](https://jscpd.dev)).\n\nThere is no separate Go tree.\n\n## TUI preferences\n\nConfiguration is **`tui.json` or `tui.jsonc`**: `theme`, `keybinds`, `leader_timeout`, `font_size`, `mouse`, `scroll_speed`, `log_timestamps`, `log_metadata`, `mcp_enabled`, `mcp_port`, `mcp_disabled_tools`.\n\n`mcp_disabled_tools` is a deny-list of MCP tool names (empty means every tool is available). See [MCP](mcp.md).\n\nSearch order:\n\n```mermaid\nflowchart TB\n env{"DEVCTL_TUI_CONFIG<br/>or OPENCODE_TUI_CONFIG exists?"}\n env -->|yes| only["Use that file only"]\n env -->|no| first["First existing of<br/>./tui.jsonc \xB7 .devctl/tui.jsonc \xB7 ~/.devctl/tui.jsonc"]\n first --> merge{"~/.devctl/tui.json is a different path?"}\n merge -->|yes| overlay["Merge user file on top"]\n merge -->|no| done["Use that file"]\n```\n\nSettings writes go to `~/.devctl/tui.json` unless the env override is set (then changes apply for this session only).\n\n`keybinds` merge with the built-in defaults, so you only override what you change. Defaults use `cmd` on macOS and `ctrl` on Linux/Windows (`command+c` / `ctrl+c` in the TUI).\n\n```json\n{\n "theme": "devctl",\n "leader_timeout": 2000,\n "keybinds": {\n "leader": "ctrl+x",\n "command_list": "ctrl+p"\n },\n "mouse": true,\n "mcp_enabled": false,\n "mcp_disabled_tools": []\n}\n```\n\n## Tests\n\n```bash\ncd app && bun test\n```\n\nIntegration tests that need Google stay skipped unless credentials are present.\n\n## Related\n\n- [Installation](installation.md)\n- [TUI](tui.md)\n- [How it fits together](overview.md)\n- [Contributing](../CONTRIBUTING.md)\n'}]});function OA(){return[{path:"skills/devctl-onboard/SKILL.md",title:"Onboard procedure",body:Tn.procedure},{path:"skills/devctl-onboard/references/authoring.md",title:"Onboard authoring",body:Tn.authoring},{path:"skills/devctl-onboard/references/discovery.md",title:"Onboard discovery",body:Tn.discovery}]}function $A(e){return e.toLowerCase().split(/[^a-z0-9_]+/).filter((t)=>t.length>=IA)}function FA(e,t){let r=e.title.toLowerCase(),n=e.path.toLowerCase(),o=e.body.toLowerCase(),i=0;for(let a of t){if(r.includes(a))i+=TA;if(n.includes(a))i+=AA;let c=o.split(a).length-1;i+=Math.min(c,LA)}let s=t.join(" ");if(s.length>=MA&&o.includes(s))i+=DA;return i}function NA(e,t){let r=e.split(`
2136
+ `),n=0,o=-1;for(let s=0;s<r.length;s++){let a=r.slice(s,s+By).join(`
2137
+ `).toLowerCase(),c=0;for(let l of t)if(a.includes(l))c+=1;if(c>o)o=c,n=s}let i=r.slice(n,n+By).join(`
2138
+ `).trim();if(i.length<=jy)return i;return`${i.slice(0,jy).trimEnd()}\u2026`}function HA(e){if(e===void 0||Number.isNaN(e))return _A;return Math.min(EA,Math.max(1,Math.floor(e)))}function Uy(e,t){let r=e.trim(),n=$A(r);if(n.length===0)throw Error("query is required");let o=HA(t),i=[...Ny,...OA()].map((s)=>({path:s.path,title:s.title,score:FA(s,n),snippet:NA(s.body,n)})).filter((s)=>s.score>0).sort((s,a)=>a.score-s.score||s.path.localeCompare(a.path)).slice(0,o);return{query:r,hits:i}}var _A=5,EA=10,TA=12,AA=8,LA=8,DA=15,jy=400,By=5,IA=2,MA=4;var Wy=D(()=>{op();Hy()});import{existsSync as jA,readFileSync as BA}from"fs";function An(e,t){return!(t??[]).includes(e)}function Gy(e){return Pc.filter((t)=>An(t.name,e))}function Rc(e){return Pc.some((t)=>t.name===e)}function ds(e){return new mr(e.secrets.extra_markers,e.secrets.extra_patterns)}function Ky(e){return Object.values(e.services).map((t)=>({name:t.name,state:t.state,health:t.health,ports:t.ports,pid:t.pid,last_error:t.last_error}))}function WA(e,t){let r=e.config(),n=r.services[t];if(!n)throw Error(`unknown service ${t}`);let i=e.status().services[t],s=ds(r);return{name:t,state:i?.state,health:i?.health,pid:i?.pid,last_error:i?.last_error,command:n.command.args,cwd:n.working_dir,ports:i?.ports??Object.fromEntries(n.ports.map((a)=>[a.name,a.value])),environment:s.redactMap({...n.environment.defaults,...n.environment.vars}),container:n.container?{...n.container,env:s.redactMap(n.container.env)}:void 0}}function qy(e){return{session_id:e.session_id,repo_root:e.repo_root,profile:e.profile,setup_mode:e.setup_mode===!0?!0:void 0,identity:{user:e.identity.user,project:e.identity.project,adc:e.identity.adc,iap:e.identity.iap,service_accounts:e.identity.service_accounts,service_account_status:e.identity.service_account_status},proxy:{running:e.proxy.running,address:e.proxy.address,routes:e.proxy.routes},logs:e.logs,mcp:e.mcp?{running:e.mcp.running,address:e.mcp.address,port:e.mcp.port}:{running:!1}}}async function Vy(e,t){let r=typeof t.service==="string"?t.service:"",n=typeof t.since==="string"?t.since:"",o=typeof t.cursor==="string"?t.cursor:void 0,i=await e.logsPage({services:r===""?[]:[r],level:typeof t.level==="string"?t.level:"",search:typeof t.search==="string"?t.search:"",source:typeof t.source==="string"?t.source:"",since:n,until:typeof t.until==="string"?t.until:"",cursor:o,direction:o?"forward":void 0,limit:UA}),s=ds(e.config());return{events:i.events.map((a)=>({timestamp:a.timestamp,service:a.service,source:a.source,level:a.level,message:s.redactText(a.message),pid:a.pid})),truncated:i.hasPrev,has_more:i.hasNext,next_since:i.events[i.events.length-1]?.timestamp??n,next_cursor:i.nextCursor,session_changed:i.sessionChanged}}function GA(e){return Object.entries(e.profiles).sort(([t],[r])=>t.localeCompare(r)).map(([t,r])=>({name:t,services:r.services}))}function kc(e){return e.trim()===""?void 0:e}function KA(e){let t={name:e.name,match:e.match,upstream:e.upstream.url,auth:e.auth.type},r=kc(e.auth.identity.type),n=kc(e.auth.audience),o=kc(e.auth.identity.service_account||e.auth.service_account),i=kc(e.auth.client_id),s=Ha(e.auth.client_secret);if(r)t.identity=r;if(n)t.audience=n;if(o)t.service_account=o;if(i)t.client_id=i;if(s)t.client_secret=s;return t}function zy(e){let t=Object.entries(e.services).map(([r,n])=>({name:r,description:n.description,command:n.command.args,cwd:n.working_dir,ports:n.ports.map((o)=>({name:o.name,value:o.auto?0:o.value,auto:o.auto})),dependencies:n.dependencies,container:n.container?{image:n.container.image,runtime:n.container.runtime||"docker",ports:n.container.ports,volumes:n.container.volumes}:void 0}));return{project:e.project.name,config_path:e.configPath,repo_root:e.repoRoot,services:t,proxy:{enabled:e.proxy.enabled,listen:{host:e.proxy.listen.host,port:e.proxy.listen.port},routes:e.proxy.routes.map((r)=>KA(r))}}}function qA(e){let t=ds(e);return{entries:Pn(e).map((r)=>{let n=typeof r.value==="string"?r.value:JSON.stringify(r.value);return{...r,value:t.redactMap({[r.path]:n})[r.path]}})}}function Cc(e){if(!Array.isArray(e))return[];return e.filter((t)=>typeof t==="string")}async function Yy(e,t,r){switch(t){case"list_services":return Ky(e.status());case"get_service":if(typeof r.name!=="string"||r.name==="")throw Error("name is required");return WA(e,r.name);case"get_status":return qy(e.status());case"get_logs":return Vy(e,r);case"list_profiles":return GA(e.config());case"get_config":return zy(e.config());case"get_config_sources":return qA(e.config());case"run_doctor":return e.doctor();case"start_services":return e.start({services:Cc(r.services),profile:typeof r.profile==="string"&&r.profile!==""?r.profile:void 0});case"stop_services":return await e.stop(Cc(r.services)),{ok:!0};case"restart_services":return await e.restart(Cc(r.services),r.cascade===!0),{ok:!0};case"reload_config":return e.reload();case"run_task":{if(typeof r.name!=="string"||r.name==="")throw Error("name is required");let n=await e.runTask(r.name),o=ds(e.config());return{...n,stdout:o.redactText(n.stdout),stderr:o.redactText(n.stderr)}}case"start_proxy":return await e.startProxy(),{ok:!0};case"stop_proxy":return await e.stopProxy(),{ok:!0};case"exec_service":{if(typeof r.service!=="string"||r.service==="")throw Error("service is required");if(!e.exec)throw Error("exec is unavailable");let n=await e.exec(r.service,Cc(r.command),r.print_env===!0),o=ds(e.config());return{...n,stdout:o.redactText(n.stdout),stderr:o.redactText(n.stderr),environment:n.environment?o.redactMap(n.environment):void 0}}case"get_setup_guide":return VA(r);case"search_docs":return Uy(typeof r.query==="string"?r.query:"",typeof r.limit==="number"?r.limit:void 0);case"validate_config":return zA(e,r);default:throw Error(`unknown tool ${t}`)}}function VA(e){let t=typeof e.section==="string"?e.section:"procedure",r=t in Tn?t:"procedure";return{section:r,text:Tn[r],sections:Object.keys(Tn)}}function zA(e,t){let r=e.config();if(typeof t.text==="string"){let o=e.validateConfigText(t.text);return{valid:o.length===0,issues:o,source:"candidate",config_path:r.configPath}}if(!jA(r.configPath))return{valid:!1,issues:[`no configuration at ${r.configPath}`],source:"disk",config_path:r.configPath,setup_mode:!0};let n=e.validateConfigText(BA(r.configPath,"utf8"));return{valid:n.length===0,issues:n,source:"disk",config_path:r.configPath}}function Jy(e){return ip.includes(e)}async function Xy(e,t){switch(t){case"devctl://status":return qy(e.status());case"devctl://services":return Ky(e.status());case"devctl://logs":return Vy(e,{});case"devctl://config":return zy(e.config());case"devctl://doctor":return e.doctor();default:throw Error(`unknown resource ${t}`)}}var UA=200,ip,sp,Pc;var us=D(()=>{ni();Wy();op();ip=["devctl://status","devctl://services","devctl://logs","devctl://config","devctl://doctor"],sp=["inspect","logs","diagnostics","control","setup"],Pc=[{name:"list_services",label:"List services",summary:"Name, state, health, ports, pid",category:"inspect",description:"List services with state, health, ports, pid, and last error",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"get_service",label:"Service detail",summary:"One service with command, cwd, ports",category:"inspect",description:"One service plus command, cwd, and ports. Environment values are redacted.",inputSchema:{type:"object",properties:{name:{type:"string"}},required:["name"],additionalProperties:!1}},{name:"get_status",label:"Status",summary:"Profile, identity, proxy, log counts",category:"inspect",description:"Profile, session, identity flags (no tokens), proxy, and log counts",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"get_logs",label:"Read logs",summary:"Filtered log pages, secrets redacted",category:"logs",description:"Recent log lines, optionally filtered. Capped at 200 events per page. Secrets are redacted. Pass cursor=next_cursor to page forward with no duplicate or same-millisecond-lost events; since/until are plain timestamp filters for a fresh query, not a follow cursor.",inputSchema:{type:"object",properties:{service:{type:"string"},level:{type:"string"},search:{type:"string"},source:{type:"string"},since:{type:"string",description:"Only events at or after this timestamp"},until:{type:"string",description:"Only events at or before this timestamp"},cursor:{type:"string",description:"Opaque cursor from a previous response's next_cursor; continues forward from exactly there"}},additionalProperties:!1}},{name:"list_profiles",label:"List profiles",summary:"Configured profiles and their members",category:"inspect",description:"Configured profiles and their member services",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"get_config",label:"Read config",summary:"Merged project summary, no secret values",category:"inspect",description:"Merged project summary: services, routes, and proxy listen paths. Route auth includes type, identity, audience, and optional IAP client_id. client_secret is returned only as a ${ENV} template, never a resolved secret.",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"get_config_sources",label:"Config sources",summary:"Winning and shadowed config sources",category:"inspect",description:"List effective configuration values with their winning source/layer and any shadowed sources. Secret-like values are redacted.",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"run_doctor",label:"Run doctor",summary:"Environment diagnostics",category:"diagnostics",description:"Run environment diagnostics",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"start_services",label:"Start services",summary:"Start named services or a profile",category:"control",mutates:!0,description:"Start named services, or a profile when names are omitted. Empty start uses profile, then the active session profile, then the first configured profile \u2014 never every service.",inputSchema:{type:"object",properties:{services:{type:"array",items:{type:"string"}},profile:{type:"string",description:"Profile to start when services is omitted"}},additionalProperties:!1}},{name:"stop_services",label:"Stop services",summary:"Stop services and their dependents",category:"control",mutates:!0,description:"Stop named services, or all started services when names are omitted. Also stops every service that transitively depends on a named one (never a named service's own dependencies, which other running services may still need).",inputSchema:{type:"object",properties:{services:{type:"array",items:{type:"string"}}},additionalProperties:!1}},{name:"restart_services",label:"Restart services",summary:"Restart services, optionally cascading",category:"control",mutates:!0,description:"Restart named services. By default this touches only the named services, not anything that depends on them. Pass cascade=true to also restart their transitive dependents (the same set stop_services would affect).",inputSchema:{type:"object",properties:{services:{type:"array",items:{type:"string"}},cascade:{type:"boolean",description:"Also restart transitive dependents; default false restarts only the named services"}},additionalProperties:!1}},{name:"reload_config",label:"Reload config",summary:"Re-read .devctl configuration",category:"control",mutates:!0,description:"Reload .devctl configuration",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"run_task",label:"Run task",summary:"Run a named one-off task",category:"control",mutates:!0,description:"Run a named task from configuration. Output is also written to the log ring as task:<name>.",inputSchema:{type:"object",properties:{name:{type:"string"}},required:["name"],additionalProperties:!1}},{name:"start_proxy",label:"Start proxy",summary:"Start the local reverse proxy",category:"control",mutates:!0,description:"Start the local reverse proxy. Same as CLI `devctl proxy start` and TUI start on the proxy screen.",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"stop_proxy",label:"Stop proxy",summary:"Stop the local reverse proxy",category:"control",mutates:!0,description:"Stop the local reverse proxy and leave it suppressed until start_proxy (or CLI/TUI start) runs again.",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"exec_service",label:"Execute in service",summary:"Run a command in a resolved service context",category:"control",mutates:!0,description:"Run an arbitrary command with a service's fully resolved environment and working directory, whether or not it is running. Output and print_env values are redacted.",inputSchema:{type:"object",properties:{service:{type:"string"},command:{type:"array",items:{type:"string"}},print_env:{type:"boolean",description:"Return the resolved environment without executing a command"}},required:["service"],additionalProperties:!1}},{name:"get_setup_guide",label:"Setup guide",summary:"How to author a .devctl for this repo",category:"setup",description:"The devctl onboarding guide: how to survey a repository and author a .devctl configuration for it. Read section=procedure first; read section=authoring BEFORE writing any YAML (it carries the rules the loader rejects on, which the JSON Schema does not state); read section=discovery for mapping compose/package.json/pyproject/Terraform/k8s/.env to services. Write the files with your own editing tools \u2014 this server does not write them \u2014 then check your work with validate_config.",inputSchema:{type:"object",properties:{section:{type:"string",enum:["procedure","authoring","discovery"],description:"Which part of the guide to return; defaults to procedure"}},additionalProperties:!1}},{name:"search_docs",label:"Search docs",summary:"Search the embedded product documentation",category:"setup",description:"Search the compiled-in product documentation (docs/*.md plus the onboarding skill). Returns ranked pages with short snippets. Use this for IAP, proxy, MCP, configuration, and similar topics; use get_setup_guide for the full onboarding procedure.",inputSchema:{type:"object",properties:{query:{type:"string",description:"Keywords or a short phrase to search for"},limit:{type:"integer",description:"Maximum hits to return (default 5, max 10)"}},required:["query"],additionalProperties:!1}},{name:"validate_config",label:"Validate config",summary:"Check configuration or a draft for errors",category:"setup",description:"Validate devctl configuration and return the exact issues the loader would report. With no arguments, validates what is on disk. Pass text to validate a candidate config.yaml before writing it \u2014 the candidate is run through the real load pipeline (modular services/profiles, overlays, templates), so it works even when no configuration exists yet. This is the only way to validate over MCP; there is no CLI round-trip needed.",inputSchema:{type:"object",properties:{text:{type:"string",description:"Candidate config.yaml contents to validate instead of the file on disk"}},additionalProperties:!1}}]});function ox(e){return{...e,runDoctor:(t,r,n)=>e.runDoctor.execute(t,r,n),startupPlan:(t,r,n)=>e.getStartupPlan.execute(t,r,n),shutdownPlan:(t,r)=>e.getShutdownPlan.execute(t,r),shutdownPlanExact:(t,r)=>nc(t,r),resolveStartRequest:(t,r)=>e.resolveStart.execute(t,{...r,profile:r.profile?ur(r.profile):void 0,activeProfile:r.activeProfile===void 0?void 0:ur(r.activeProfile)})}}var ix=D(()=>{Tt()});function Ec(e){let t=e.toLowerCase().trim(),r=aL[t];if(r)return r;return St.includes(t)?t:"devctl"}function cL(){if(process.env.DEVCTL_THEME_LIGHT==="1")return!0;if(process.env.DEVCTL_THEME_LIGHT==="0")return!1;if(process.platform==="darwin")try{let t=Bun.spawnSync(["defaults","read","-g","AppleInterfaceStyle"],{stdout:"pipe",stderr:"pipe"}),r=t.stdout.toString();if(t.exitCode===0&&r.toLowerCase().includes("dark"))return!1;if(t.exitCode!==0)return!0}catch{}let e=process.env.COLORFGBG??"";if(e.includes(";15")||e.endsWith(";7"))return!0;if(e.includes(";0")||e.endsWith(";8"))return!1;return!1}function sx(e){let t=Ec(e);if(t==="system"){if(cL())return{...cp["solarized-light"],name:"system"};return cp.system}return cp[t]}function hr(e){switch(e.toUpperCase()){case"HEALTHY":case"OK":return"\u2713";case"UNHEALTHY":case"WARNING":case"WARN":return"!";case"FAILED":case"ERROR":return"\u2717";case"STOPPED":case"UNKNOWN":return"\u25CB";case"RUNNING":case"STARTING":case"RESTARTING":return"\u25CF";default:return"\u25CB"}}function ho(e,t){return gs(e)>=hs?Tc:t}function gs(e){let t=e.startsWith("#")?e.slice(1):e,r=Number.parseInt(t,vL);if(Number.isNaN(r))return 0;let n=r>>yL&lp,o=r>>xL&lp,i=r&lp;return(n*fL+o*mL+i*hL)/gL}function bL(e){let t=uL;for(let r=0;r<e.length;r+=1)t^=e.charCodeAt(r),t=Math.imul(t,pL);return t>>>0}function Ac(e){return gs(e.background)>=hs}function up(e){if(!e||!/^#[0-9a-f]{6}$/i.test(e))return!1;return gs(e)<hs}function Lc(e,t){let r=wL[e];return Ac(t)?r.light:r.dark}function ax(e,t){return gs(Lc(e,t))>=hs?SL:kL}function Ze(e,t){if(e===""||e==="all")return t.muted;let r=gs(t.background)>=hs?dL:lL;return r[bL(e)%r.length]??t.info}function CL(e,t){switch(t.toUpperCase()){case"ERROR":case"FATAL":return e.error;case"WARN":case"WARNING":return e.warning;case"INFO":return e.text;case"DEBUG":case"TRACE":return e.muted;default:return e.text}}function cx(e,t,r){if(r==="search")return e.search;if(r==="string")return e.accent;if(r==="number")return e.info;if(r==="keyword")return e.error;return CL(e,t)}function et(e,t){switch(t.toUpperCase()){case"HEALTHY":case"OK":return e.success;case"UNHEALTHY":case"WARNING":case"WARN":return e.warning;case"FAILED":case"ERROR":return e.error;case"RUNNING":case"STARTING":case"RESTARTING":return e.primary;case"INFO":return e.info;case"FATAL":return e.error;case"DEBUG":case"TRACE":case"UNKNOWN":return e.muted;default:return e.muted}}var St,dp,cp,aL,lL,dL,uL=2166136261,pL=16777619,fL=299,mL=587,hL=114,gL=1000,hs=160,vL=16,lp=255,yL=16,xL=8,Tc="#1A1A1A",wL,SL="#1A1A1A",kL="#F5F5F5";var tt=D(()=>{St=["devctl","ember","tokyonight","catppuccin","catppuccin-latte","nord","gruvbox","kanagawa","dracula","onedark","monokai","solarized-dark","solarized-light","rose-pine","everforest","github-dark","iceberg","ayu-dark","oxocarbon","night-owl","terminal","voltage","voltage-light","ironclad","ironclad-light","signal","signal-light","system"],dp={devctl:"forest + mint, website palette",ember:"warm orange",tokyonight:"Tokyo Night storm blues",catppuccin:"Catppuccin Mocha","catppuccin-latte":"Catppuccin Latte (light)",nord:"Arctic Nord frost",gruvbox:"Gruvbox dark hard",kanagawa:"Kanagawa wave / gold",dracula:"classic Dracula purple",onedark:"Atom / VS Code One Dark",monokai:"Monokai Sublime","solarized-dark":"Solarized dark","solarized-light":"Solarized light","rose-pine":"Ros\xE9 Pine moon",everforest:"Everforest dark medium","github-dark":"GitHub dimmed canvas",iceberg:"Iceberg navy","ayu-dark":"Ayu Mirage-adjacent dark",oxocarbon:"IBM Oxocarbon","night-owl":"Sarah Drasner Night Owl",terminal:"black + VGA ANSI chrome",voltage:"max contrast, electric neon (dark)","voltage-light":"max contrast, electric neon (light)",ironclad:"max contrast, steel & amber (dark)","ironclad-light":"max contrast, steel & amber (light)",signal:"max contrast, traffic-light semantics (dark)","signal-light":"max contrast, traffic-light semantics (light)",system:"neutral dark (system default)"},cp={tokyonight:{name:"tokyonight",primary:"#82AAFF",accent:"#C792EA",error:"#FF5370",warning:"#FFCB6B",search:"#FFCB6B",success:"#C3E88D",info:"#89DDFF",text:"#F4F7FF",muted:"#A8B2CC",background:"#0F111A",panel:"#1A1B2E",element:"#24283B",highlight:"#2E3350",inverse:"#0F111A",border:"#3D4466",borderActive:"#82AAFF"},catppuccin:{name:"catppuccin",primary:"#89B4FA",accent:"#F5C2E7",error:"#F38BA8",warning:"#F9E2AF",search:"#F9E2AF",success:"#A6E3A1",info:"#89DCEB",text:"#E4E8F7",muted:"#A6A9BE",background:"#11111B",panel:"#1E1E2E",element:"#313244",highlight:"#45475A",inverse:"#11111B",border:"#585B70",borderActive:"#89B4FA"},"catppuccin-latte":{name:"catppuccin-latte",primary:"#1E66F5",accent:"#EA76CB",error:"#D20F39",warning:"#DF8E1D",search:"#DF8E1D",success:"#40A02B",info:"#179299",text:"#4C4F69",muted:"#6C6F85",background:"#EFF1F5",panel:"#E6E9EF",element:"#DCE0E8",highlight:"#CCD0DA",inverse:"#EFF1F5",border:"#9CA0B0",borderActive:"#1E66F5"},nord:{name:"nord",primary:"#88C0D0",accent:"#B48EAD",error:"#BF616A",warning:"#EBCB8B",search:"#EBCB8B",success:"#A3BE8C",info:"#8FBCBB",text:"#F2F5FA",muted:"#A4AEBF",background:"#2E3440",panel:"#3B4252",element:"#434C5E",highlight:"#4C566A",inverse:"#2E3440",border:"#5E6A7D",borderActive:"#88C0D0"},gruvbox:{name:"gruvbox",primary:"#FE8019",accent:"#FABD2F",error:"#FB4934",warning:"#FE8019",search:"#FE8019",success:"#B8BB26",info:"#8EC07C",text:"#F2E5BC",muted:"#C4B394",background:"#1D2021",panel:"#282828",element:"#3C3836",highlight:"#504945",inverse:"#1D2021",border:"#665C54",borderActive:"#FE8019"},kanagawa:{name:"kanagawa",primary:"#7FB4CA",accent:"#E6C384",error:"#E82424",warning:"#FFA066",search:"#FFA066",success:"#98BB6C",info:"#7FB4CA",text:"#E8E4C9",muted:"#B4C0D6",background:"#16161D",panel:"#1F1F28",element:"#2A2A37",highlight:"#363646",inverse:"#16161D",border:"#54546D",borderActive:"#E6C384"},dracula:{name:"dracula",primary:"#BD93F9",accent:"#FF79C6",error:"#FF5555",warning:"#F1FA8C",search:"#F1FA8C",success:"#50FA7B",info:"#8BE9FD",text:"#F8F8F2",muted:"#8B9DC9",background:"#21222C",panel:"#282A36",element:"#343746",highlight:"#44475A",inverse:"#21222C",border:"#6272A4",borderActive:"#BD93F9"},onedark:{name:"onedark",primary:"#61AFEF",accent:"#C678DD",error:"#E06C75",warning:"#E5C07B",search:"#E5C07B",success:"#98C379",info:"#56B6C2",text:"#ABB2BF",muted:"#9DA5B4",background:"#21252B",panel:"#282C34",element:"#2C323C",highlight:"#3E4451",inverse:"#21252B",border:"#4B5263",borderActive:"#61AFEF"},monokai:{name:"monokai",primary:"#66D9EF",accent:"#AE81FF",error:"#F92672",warning:"#E6DB74",search:"#E6DB74",success:"#A6E22E",info:"#66D9EF",text:"#F8F8F2",muted:"#A39E80",background:"#1D1E19",panel:"#272822",element:"#3E3D32",highlight:"#49483E",inverse:"#1D1E19",border:"#75715E",borderActive:"#F92672"},"solarized-dark":{name:"solarized-dark",primary:"#268BD2",accent:"#6C71C4",error:"#DC322F",warning:"#B58900",search:"#B58900",success:"#859900",info:"#2AA198",text:"#93A1A1",muted:"#586E75",background:"#002B36",panel:"#073642",element:"#0A4A58",highlight:"#1A5A68",inverse:"#002B36",border:"#586E75",borderActive:"#268BD2"},"solarized-light":{name:"solarized-light",primary:"#268BD2",accent:"#D33682",error:"#DC322F",warning:"#CB4B16",search:"#CB4B16",success:"#859900",info:"#2AA198",text:"#586E75",muted:"#839496",background:"#FDF6E3",panel:"#EEE8D5",element:"#E4DCC8",highlight:"#D5CDB6",inverse:"#FDF6E3",border:"#93A1A1",borderActive:"#268BD2"},"rose-pine":{name:"rose-pine",primary:"#C4A7E7",accent:"#EBBCBA",error:"#EB6F92",warning:"#F6C177",search:"#F6C177",success:"#31748F",info:"#9CCFD8",text:"#E0DEF4",muted:"#908CAA",background:"#191724",panel:"#1F1D2E",element:"#26233A",highlight:"#403D52",inverse:"#191724",border:"#6E6A86",borderActive:"#C4A7E7"},everforest:{name:"everforest",primary:"#7FBBB3",accent:"#D699B6",error:"#E67E80",warning:"#DBBC7F",search:"#DBBC7F",success:"#A7C080",info:"#83C092",text:"#D3C6AA",muted:"#9DA9A0",background:"#232A2E",panel:"#2D353B",element:"#343F44",highlight:"#3D484D",inverse:"#232A2E",border:"#4F585E",borderActive:"#A7C080"},"github-dark":{name:"github-dark",primary:"#58A6FF",accent:"#A371F7",error:"#F85149",warning:"#D29922",search:"#D29922",success:"#3FB950",info:"#79C0FF",text:"#E6EDF3",muted:"#8B949E",background:"#0D1117",panel:"#161B22",element:"#21262D",highlight:"#30363D",inverse:"#0D1117",border:"#30363D",borderActive:"#58A6FF"},iceberg:{name:"iceberg",primary:"#84A0C6",accent:"#A093C7",error:"#E27878",warning:"#E2A478",search:"#E2A478",success:"#B4BE82",info:"#89B8C2",text:"#C6C8D1",muted:"#9AA0B8",background:"#161821",panel:"#1E2132",element:"#2A3150",highlight:"#3D425B",inverse:"#161821",border:"#444B71",borderActive:"#84A0C6"},"ayu-dark":{name:"ayu-dark",primary:"#59C2FF",accent:"#FFB454",error:"#D95757",warning:"#FF8F40",search:"#FF8F40",success:"#C2D94C",info:"#95E6CB",text:"#B3B1AD",muted:"#8B939C",background:"#0A0E14",panel:"#0D1017",element:"#131721",highlight:"#1B212C",inverse:"#0A0E14",border:"#2D3640",borderActive:"#FFB454"},oxocarbon:{name:"oxocarbon",primary:"#78A9FF",accent:"#EE5396",error:"#FA4D56",warning:"#F1C21B",search:"#F1C21B",success:"#42BE65",info:"#3DDBD9",text:"#F2F4F8",muted:"#A8A8A8",background:"#161616",panel:"#262626",element:"#393939",highlight:"#525252",inverse:"#161616",border:"#6F6F6F",borderActive:"#78A9FF"},"night-owl":{name:"night-owl",primary:"#82AAFF",accent:"#C792EA",error:"#EF5350",warning:"#ECC48D",search:"#ECC48D",success:"#ADDB67",info:"#7FDBCA",text:"#D6DEEB",muted:"#8FA4A4",background:"#011627",panel:"#0B2942",element:"#1D3B53",highlight:"#1E4B6B",inverse:"#011627",border:"#5F7E97",borderActive:"#82AAFF"},terminal:{name:"terminal",primary:"#55FFFF",accent:"#FF55FF",error:"#FF5555",warning:"#FFFF55",search:"#FFFF55",success:"#55FF55",info:"#5555FF",text:"#C0C0C0",muted:"#808080",background:"#000000",panel:"#000000",element:"#000000",highlight:"#404040",inverse:"#000000",border:"#555555",borderActive:"#55FFFF"},voltage:{name:"voltage",primary:"#00E5FF",accent:"#FF3EA5",error:"#FF1744",warning:"#FFEA00",search:"#FFEA00",success:"#00E676",info:"#40C4FF",text:"#FFFFFF",muted:"#C0C0C0",background:"#000000",panel:"#000000",element:"#0D0D0D",highlight:"#262626",inverse:"#000000",border:"#808080",borderActive:"#FFFFFF"},"voltage-light":{name:"voltage-light",primary:"#0057B7",accent:"#C2185B",error:"#D50000",warning:"#FF6D00",search:"#FF6D00",success:"#2E7D32",info:"#00838F",text:"#000000",muted:"#424242",background:"#FFFFFF",panel:"#FFFFFF",element:"#EDEDED",highlight:"#D6D6D6",inverse:"#FFFFFF",border:"#616161",borderActive:"#000000"},ironclad:{name:"ironclad",primary:"#4FC3F7",accent:"#FFD54F",error:"#FF5252",warning:"#FFB300",search:"#FFB300",success:"#69F0AE",info:"#64B5F6",text:"#FFFFFF",muted:"#BDBDBD",background:"#000000",panel:"#000000",element:"#101010",highlight:"#242424",inverse:"#000000",border:"#757575",borderActive:"#4FC3F7"},"ironclad-light":{name:"ironclad-light",primary:"#01579B",accent:"#E65100",error:"#B71C1C",warning:"#EF6C00",search:"#EF6C00",success:"#1B5E20",info:"#0277BD",text:"#000000",muted:"#4A4A4A",background:"#FFFFFF",panel:"#FFFFFF",element:"#EFEFEF",highlight:"#DADADA",inverse:"#FFFFFF",border:"#6E6E6E",borderActive:"#01579B"},signal:{name:"signal",primary:"#29B6F6",accent:"#AB47BC",error:"#FF0000",warning:"#FFC400",search:"#FFC400",success:"#00E400",info:"#00B8D4",text:"#FFFFFF",muted:"#B3B3B3",background:"#000000",panel:"#000000",element:"#0A0A0A",highlight:"#202020",inverse:"#000000",border:"#8A8A8A",borderActive:"#29B6F6"},"signal-light":{name:"signal-light",primary:"#01579B",accent:"#6A1B9A",error:"#C62828",warning:"#F9A825",search:"#F9A825",success:"#2E7D32",info:"#00796B",text:"#000000",muted:"#3D3D3D",background:"#FFFFFF",panel:"#FFFFFF",element:"#ECECEC",highlight:"#D2D2D2",inverse:"#FFFFFF",border:"#5C5C5C",borderActive:"#01579B"},system:{name:"system",primary:"#60A5FA",accent:"#C084FC",error:"#F87171",warning:"#FBBF24",search:"#FBBF24",success:"#34D399",info:"#22D3EE",text:"#F9FAFB",muted:"#C4CAD3",background:"#030712",panel:"#111827",element:"#1F2937",highlight:"#374151",inverse:"#030712",border:"#4B5563",borderActive:"#60A5FA"},devctl:{name:"devctl",primary:"#77DDBA",accent:"#9EE9CF",error:"#F28B91",warning:"#E8C586",search:"#E8C586",success:"#91D6A0",info:"#8EC8D8",text:"#DEEEE5",muted:"#A4B9AE",background:"#15201D",panel:"#202923",element:"#293B32",highlight:"#365448",inverse:"#15201D",border:"#526D60",borderActive:"#77DDBA"},ember:{name:"ember",primary:"#FF9F43",accent:"#2EE6FF",error:"#FF4D6D",warning:"#FFE566",search:"#FFE566",success:"#3DFF9A",info:"#2EE6FF",text:"#F7F9FF",muted:"#B0B8CC",background:"#07090F",panel:"#12161F",element:"#1A2130",highlight:"#243044",inverse:"#0B0D12",border:"#334155",borderActive:"#FF9F43"}},aL={opencode:"ember",light:"system",mocha:"catppuccin",latte:"catppuccin-latte","one-dark":"onedark","one dark":"onedark",solarized:"solarized-dark",rosepine:"rose-pine","rose pine":"rose-pine",github:"github-dark",ayu:"ayu-dark","night owl":"night-owl",ansi:"terminal",xterm:"terminal",console:"terminal",classic:"terminal",vga:"terminal",contrast:"voltage","contrast-light":"voltage-light",hc:"voltage","hc-light":"voltage-light",a11y:"voltage","a11y-light":"voltage-light"};lL=["#7AA2F7","#9ECE6A","#E0AF68","#BB9AF7","#2AC3DE","#FF9E64","#F7768E","#73DACA","#C0CAF5","#FF79C6","#7DCFFF","#C3E88D"],dL=["#2E5AAC","#3D7A1F","#9A6B12","#6B3FA0","#0E7A86","#B85C1A","#B4234A","#1A7A6D","#4A5080","#9A3480","#1565C0","#4A7C19"];wL={claude:{dark:"#E08A6A",light:"#B54328"},cursor:{dark:"#B8D4FF",light:"#1E4EBF"},kilo:{dark:"#F0C94A",light:"#8A6400"},codex:{dark:"#4ADE80",light:"#0B7A4B"}}});var _r=D(()=>{Zs()});function ux(e){return e!==void 0}function lx(e){let t=e/1000;if(Number.isInteger(t))return`${t}s`;return`${e}ms`}function mp(e,t,r){let n=e.indexOf(t),i=((n<0?0:n)+r+e.length)%e.length;return e[i]??e[0]}function px(e,t){let r=[...St],n=r.includes(e)?e:fp;return mp(r,n,t)}function fx(e,t){let r=[...PL],n=r.reduce((o,i)=>Math.abs(i-e)<Math.abs(o-e)?i:o,r[0]);return mp(r,n,t)}function vs(e){let t=[...pp];return t.reduce((r,n)=>Math.abs(n-e)<Math.abs(r-e)?n:r,t[0])}function ys(e,t){return mp([...pp],vs(e),t)}function xs(e){let t=vs(e),r=dx[t]??dx[Do];return{px:t,...r}}function go(e){return e.label==="compact"}function bs(e){return e.steps<=2}function mx(e){return pp.map((r,n)=>n<e?"\u2588":"\u2591").join(" ")}function Dc(e){return xs(e).label}function hx(e){let t=e.locked?"Applies this session only. DEVCTL_TUI_CONFIG overrides the user file.":`Saved to ${e.configPath}.`;return[{id:"theme",group:"Appearance",kind:"cycle",name:"Theme",value:e.themeName,hint:"\u2190 \u2192 save enter picker",detail:`Arrows write the theme to the user file. Enter opens the picker. ${t}`},{id:"font",group:"Appearance",kind:"cycle",name:"Display size",value:Dc(e.fontSize),hint:"\u2190 \u2192 save",detail:`Scales content padding and list-row height. Default keeps a thin rule between chrome and panes. Compact sits toolbars flush against borders. Header, nav, and status stay one line. Does not change the terminal font. ${t}`},{id:"mouse",group:"Input",kind:"toggle",name:"Mouse",value:e.mouse?"on":"off",hint:"space or enter toggle",detail:`Clicks on nav and lists. Restart the TUI after a change. ${t}`},{id:"leader",group:"Input",kind:"cycle",name:"Leader",value:lx(e.leaderMs),hint:"\u2190 \u2192 save",detail:`How long ${It("x")} waits for the next key. ${t}`},{id:"mcp",group:"MCP",kind:"page",name:"Settings page",value:e.mcpRunning?"running \u2192 /mcp":"off \u2192 /mcp",hint:"enter open the MCP page",detail:"Opens a dedicated MCP page (also /mcp or /agent). Start or stop the localhost server, change the port, and copy Claude, Cursor, Codex, and Kilo Code config."},{id:"file",group:"About",kind:"info",name:"File",value:e.configPath,hint:"read-only",detail:e.locked?`Using ${e.configPath} from DEVCTL_TUI_CONFIG.`:`User preferences live in ${e.configPath}.`},{id:"version",group:"About",kind:"info",name:"Version",value:jt,hint:"read-only",detail:er()},{id:"reset",group:"About",kind:"action",name:"Reset",value:"defaults",hint:"enter confirm reset",detail:`Restore theme ${fp}, display ${Dc(Do)}, mouse on, leader ${lx(id)}. Enter asks first. ${t}`}]}function Ic(){return{theme:fp,mouse:!0,leader_timeout:id,font_size:Do}}function gx(e){return["Appearance","Input","MCP","About"].flatMap((r)=>{let n=e.filter((o)=>o.group===r);if(n.length===0)return[];return[{group:r,items:n}]})}function hp(e,t){if(e.length===0)return 0;return Math.min(Math.max(t,0),e.length-1)}function Ln(e,t){return e[hp(e,t)]}function gp(e,t){return e??t}var PL,pp,fp="devctl",dx;var Er=D(()=>{tt();_r();PL=[1000,2000,3000],pp=[12,14,16,18,20,22];dx={12:{pad:0,rowH:1,gap:0,steps:1,label:"compact"},14:{pad:0,rowH:1,gap:0,steps:2,label:"default"},16:{pad:1,rowH:1,gap:1,steps:3,label:"comfortable"},18:{pad:1,rowH:2,gap:1,steps:4,label:"large"},20:{pad:2,rowH:2,gap:1,steps:5,label:"xl"},22:{pad:2,rowH:2,gap:2,steps:6,label:"xxl"}}});import{createContext as RL,useContext as _L}from"react";function ye(){return _L(vp)}var vp;var Mt=D(()=>{Er();_r();vp=RL(xs(Do))});function _e(e,t){if(t<=0)return"";if(e.length<=t)return e;if(t===1)return"\u2026";return`${e.slice(0,t-1)}\u2026`}function oe(e,t){if(t<=0)return"";if(e.length>=t)return _e(e,t);return e.padEnd(t)}function ii(e,t=20){let r=Number.isFinite(e)?Math.min(1,Math.max(0,e)):0,n=Math.round(r*t);return"\u2588".repeat(n)+"\u2591".repeat(Math.max(0,t-n))}function ws(e){if(!Number.isFinite(e)||e<yp)return"< 1s";let t=Math.floor(e/vx),r=Math.floor(e%vx/bp),n=Math.floor(e%bp/xp),o=Math.floor(e%xp/yp);if(t>0)return`${t}d ${r}h`;if(r>0)return`${r}h ${n}m`;if(n>0)return`${n}m ${String(o).padStart(2,"0")}s`;return`${o}s`}function Ss(e){if(!Number.isFinite(e)||e<0)return"\u2014";let t=e/EL;if(t>=yx)return`${(t/yx).toFixed(1)}G`;if(t>=1)return`${Math.round(t)}M`;return`${Math.round(e)}K`}function xx(e){if(!Number.isFinite(e)||e<0)return"\u2014";return`${e.toFixed(1)}%`}var yp=1000,xp,bp,vx,EL=1024,yx=1024;var Ot=D(()=>{xp=60*yp,bp=60*xp,vx=24*bp});function Sp(e){return e>=LL}function kp(e){return e>=DL}function Cp(e){return e>=IL}function Fc(e,t=0){return Math.max(1,e-wx-t*2)}function Sx(e){let t=bx+Tr+$t;if(Sp(e))t+=Ur;if(kp(e))t+=Oc;if(Cp(e))t+=sn;return Math.max($c,e-t)}function Nc(e,t,r){if(r)return e;let n=t.reduce((a,c)=>Math.max(a,c.length),0),o=Math.min(wp,Math.max($c,n+TL)),i=bx+Tr+$t+o+wx+AL,s=Math.max(Mc,Math.floor(e*ML));return Math.min(s,Math.max(Mc,i))}function kx(e){if(!e)return"";return Object.keys(e.profiles).sort()[0]??""}function Hc(e){if(!e)return!0;let t=Object.values(e.services);if(t.length===0)return!0;return!t.some((r)=>yo(r))}function Pp(e,t){if(e.length>0)return e;return[...t]}function jc(e,t){if(e.length>0)return[...e];return t===""?[]:[t]}function Bc(e){return e.command.args.join(" ")||"\u2014"}function Rs(e,t){let r=vo(t);if(r!=="")return r;if(e.ports.length===0)return"\u2014";return e.ports.map((n)=>`${n.name}:${n.auto?"auto":n.value}`).join(" ")}function Uc(e){let t=e.health.type||"none",r=e.health.url||e.health.address;return r===""?t:`${t} ${r}`}function Wc(e,t){if(t?.identity)return t.identity;let r=e.identity.type||"none",n=e.identity.service_account;return n===""?r:`${r} ${n}`}function Gc(e){let t=e.restart.policy||"none";if(e.restart.max_retries>0)return`${t} \xD7${e.restart.max_retries}`;return t}function Cx(e,t,r,n,o){let i={...e.environment.defaults,...e.environment.vars},a=OL(o??i,t,r,n);return[...new Set([...Object.keys(a),...e.environment.required])].map((l)=>({key:l,value:a[l]??"",required:e.environment.required.includes(l),fromConfig:Object.prototype.hasOwnProperty.call(i,l)})).sort((l,d)=>l.fromConfig===d.fromConfig?l.key.localeCompare(d.key):l.fromConfig?-1:1)}function Px(e,t){if(!e||e.processes.length===0)return;if(t!==void 0&&t!==""&&e.session_id===t)return;return e}function vo(e){if(!e)return"";if(e.ports.http!==void 0)return String(e.ports.http);let t=Object.values(e.ports)[0];return t!==void 0?String(t):""}function tr(e){if(!e)return"STOPPED";return Jo(e)}function OL(e,t,r,n){if(t)return e;return new mr(r,n).redactMap(e)}function _s(e){return $L.has(e)}function yo(e){if(!e)return!1;return _s(e.state)}var bx=8,Tr=12,Ur=10,Oc=8,sn=8,ks=9,Cs=7,Ps=8,$c=12,wp=40,$t=2,TL=2,wx=2,AL=2,Mc=34,LL=48,DL=60,IL=72,ML=0.48,$L;var At=D(()=>{Tt();ni();$L=new Set(["RUNNING","STARTING","RESTARTING","HEALTHY","UNHEALTHY"])});function zc(e){return e===pa?"\u2014":e}function Rp(e,t,r){let n=r?_x:FL+_x+Math.max(0,t)*2;return Math.max(1,e-n)}function BL(e){if(e.startsWith('"')||e.startsWith("'"))return"string";if(/^\d+$/.test(e))return"number";return"keyword"}function Yc(e){return e.replace(UL,"")}function Lx(e){try{return JSON.stringify(JSON.parse(e),null,2)}catch{return e}}function Jc(e,t){let r=Math.max(1,t),n=Yc(e).replace(/\r\n/g,`
596
2139
  `).replace(/\r/g,`
597
2140
  `).split(`
598
- `),o=[];for(let i of n)if(i.length<=r)o.push(i);else o.push(...oL(i,r));return o.length>0?o:[""]}function fx(e,t,r){let n=e.length>0?[...e]:[""];if(r||n.length<=1)return{visible:n,mark:"",folded:!1,hidden:0};let o=n.length-1,i=` ${ZA}${o}`,s=Math.max(1,t-i.length);return{visible:[_e(n[0]??"",s)],mark:i,folded:!0,hidden:o}}function mx(e,t){return e==="all"||e==="focus"&&t}function Kc(e){if(e==="clip")return"focus";if(e==="focus")return"all";return"clip"}function xp(e,t,r,n=Tr){if(e.length===0)return{start:0,items:[],newer:0};if(!t){let a=Math.max(0,e.length-n);return{start:a,items:e.slice(a),newer:0}}let o=e.length-1,i=Math.max(0,Math.min(r,o)),s=e.slice(i,i+n);return{start:i,items:s,newer:Math.max(0,e.length-i-s.length)}}function qc(e,t=Tr){return Math.max(0,e-t)}function bp(e,t,r,n){let o=Math.max(t-1,0);if(e<0&&r>0)return{selected:0,startDelta:Math.max(-r,e)};if(e>o&&n>0)return{selected:o,startDelta:Math.min(n,e-o)};return{selected:Math.min(o,Math.max(0,e)),startDelta:0}}function Vc(e){if(e==="all")return"wrap all";if(e==="focus")return"wrap selected";return"clip"}function oL(e,t){let r=[],n=e;while(n.length>t){let o=n.slice(0,t),i=iL(o);if(i>0)r.push(n.slice(0,i).trimEnd()),n=n.slice(i).trimStart();else r.push(n.slice(0,t)),n=n.slice(t)}if(n.length>0)r.push(n);return r}function iL(e){let t=e.lastIndexOf(" "),r=e.lastIndexOf("\t"),n=e.lastIndexOf(","),o=Math.max(t,r,n);if(o<Math.floor(e.length*eL))return-1;return e[o]===","?o+1:o}function sL(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function aL(e,t=!1){let r=e.trim();if(r==="")return;if(t){let n=Ai(r);if(n)return new RegExp(n.source,n.flags.includes("g")?n.flags:`${n.flags}g`)}return new RegExp(sL(r),"gi")}function cL(e,t){let r=e.map((a)=>a.text).join(""),n=[];t.lastIndex=0;let o=t.exec(r);while(o){let a=o[0]??"";if(a.length>0)n.push({start:o.index,end:o.index+a.length});if(a.length===0)t.lastIndex+=1;o=t.exec(r)}if(n.length===0)return e;let i=[],s=0;for(let a of e){let c=s,l=s+a.text.length,d=0;for(let u of n){let f=Math.max(u.start,c),p=Math.min(u.end,l);if(p<=f)continue;let m=f-c,g=p-c;if(m>d)i.push({text:a.text.slice(d,m),kind:a.kind});i.push({text:a.text.slice(m,g),kind:"search"}),d=g}if(d<a.text.length)i.push({text:a.text.slice(d),kind:a.kind});s=l}return i.filter((a)=>a.text!=="")}function gx(e,t="",r=!1){let n=Gc(e),o=[],i=new RegExp(tL.source,"gi"),s=0,a=i.exec(n);while(a){let d=a[0]??"";if(a.index>s)o.push({text:n.slice(s,a.index),kind:"text"});o.push({text:d,kind:rL(d)}),s=a.index+d.length,a=i.exec(n)}if(s<n.length)o.push({text:n.slice(s),kind:"text"});let c=o.length===0?[{text:n,kind:"text"}]:o,l=aL(t,r);return l?cL(c,l):c}function hx(e){let t=e.showTimestamps?Bc:0,r=e.showMeta?Hc:0;return Math.max(lx,e.width-t-e.serviceWidth-Wc-r-dx-ks*ux)}function yx(e,t){let r=t.reduce((s,a)=>Math.max(s,a.length),0),n=Bc+Wc+dx+lx+ks*ux,o=Math.max(ax,e-n),i=Math.max(ax,r);return Math.min(fp,o,i)}function oi(e){return lL.has(e)}function xx(e,t,r,n){let o=r===""?t:t.filter((a)=>a.timestamp>=r);if(o.length===0)return e;let i=Math.max(1,n);if(o.length>=i)return o.slice(-i);let s=Math.max(0,e.length+o.length-i);return e.slice(s).concat(o)}function bx(e,t){let r=t.length>0?t[t.length-1].seq:-1,n=e.filter((o)=>o.seq>r);return[...t,...n]}function wx(e,t){if(t.length===0)return e;let r=new Set(e.map((o)=>o.seq)),n=t.filter((o)=>!r.has(o.seq));return n.length===0?e:[...n,...e]}function wp(e,t,r){return e&&t<=0&&r}function Sx(e){return e.filter((t)=>t.level==="ERROR"||t.level==="FATAL").length}function go(e,t){let r=t.services?.filter((l)=>l!=="")??[],n=t.service??"",o=(t.search??"").trim(),i=t.source??"",s=t.since??"",a=t.until??"",c;if(o!=="")if(t.regex===!0){let l=Ai(o);if(l)c=(d)=>l.test(d);else c=(d)=>d.toLowerCase().includes(o.toLowerCase())}else{let l=o.toLowerCase();c=(d)=>d.toLowerCase().includes(l)}return e.filter((l)=>{if(r.length>0&&!r.includes(l.service))return!1;if(n!==""&&l.service!==n)return!1;if(i!==""&&l.source!==i)return!1;if(t.systemLogs===!1&&oi(l.source))return!1;if(s!==""&&l.timestamp<s)return!1;if(a!==""&&l.timestamp>a)return!1;if(t.errorOnly===!0&&l.level!=="ERROR"&&l.level!=="FATAL")return!1;if(!c)return!0;return c(l.message)||c(l.service)})}function dL(e,t){let r={};for(let i of e)r[i.service]=(r[i.service]??0)+1;let n=t.map((i)=>({name:i,count:r[i]??0})),o=Object.keys(r).filter((i)=>!t.includes(i)).sort().map((i)=>({name:i,count:r[i]??0}));return[...n,...o]}function kx(e,t,r=[]){let n=Jc(e,t,r);return[{name:"",count:t.length},...dL(t,n)]}function uL(e,t){let r=e.map((o)=>({name:o,count:t[o]??0})),n=Object.keys(t).filter((o)=>!e.includes(o)).sort().map((o)=>({name:o,count:t[o]??0}));return[...r,...n]}function Cx(e,t,r=[]){let n=Object.keys(t.byService).map((i)=>({service:i})),o=Jc(e,n,r);return[{name:"",count:t.total},...uL(o,t.byService)]}function Rx(e,t){if(e<=0)return"none started";return`${e}/${t} running`}function Jc(e,t,r=[]){let n=[...e],o=(i)=>{if(i!==""&&!n.includes(i))n.push(i)};for(let i of r)o(i);for(let i of t)o(i.service);return n}function Sp(e,t,r){let n=["",...e],o=n.indexOf(t),s=((o<0?0:o)+r+n.length)%n.length;return n[s]??""}var Hc=8,Bc=9,Wc=7,ax=10,lx=16,dx=2,ks=1,Tr=200,QA=2,cx=1,ZA="\u25B8",ux=2,eL=0.4,tL,nL,lL,vx;var hr=D(()=>{la();It();At();tL=/("[^"]*"|'[^']*'|\b(?:ERROR|FATAL|WARN(?:ING)?|FAIL(?:ED)?)\b|\b\d{3,5}\b)/gi;nL=/\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-Z\\-_]/g;lL=new Set(["auth","devctl","proxy"]),vx=["devctl","mcp","auth"]});function ii(e){return e.length+fL}function Px(e,t){if(t<gL)return _e(e,3);if(t<mL)return _e(e,4);return e}function _x(e,t,r){let n=e.length;if(n===0||r<=0)return{start:0,end:-1};let o=t>=0&&t<n?t:0,i=kp(e,o,r);if(i.start===0&&i.end===n-1)return i;let s=o>0?zc:0,a=o<n-1?zc:0,c=s+a,l=kp(e,o,Math.max(e[o]??0,r-c)),d=(s>0&&l.start===0?zc:0)+(a>0&&l.end===n-1?zc:0);if(d===0)return l;return kp(e,o,Math.max(e[o]??0,r-c+d))}function kp(e,t,r){let n=e.length,o=t,i=t,s=e[t]??0;if(s>=r)return{start:t,end:t};while(o>0||i<n-1){let a=o>0?e[o-1]??0:Number.POSITIVE_INFINITY,c=i<n-1?e[i+1]??0:Number.POSITIVE_INFINITY,l=o>0&&s+a<=r,d=i<n-1&&s+c<=r;if(!l&&!d)break;let u=t-(o-1),f=i+1-t;if(l&&(!d||u<=f))o-=1,s+=a;else if(d)i+=1,s+=c;else break}return{start:o,end:i}}function Ex(e){if(e==="detail")return ho.findIndex((t)=>t.id==="services");return ho.findIndex((t)=>t.id===e)}function Tx(e,t){if(e==="doctor")return t.doctor;if(e==="settings")return t.settings;if(e==="profiles")return t.profiles;if(e==="dashboard"||e==="services")return t.services;if(e==="logs")return t.logs??0;if(e==="mcp")return t.mcp??0;if(e==="setup")return pL;if(e==="config")return t.config??0;return 0}function Ax(e){if(e.length===1&&e>="1"&&e<="9")return ho[Number(e)-1]?.id;return}var pL=9,fL=2,zc=2,mL=80,gL=56,ho,An;var Ln=D(()=>{It();ho=[{id:"dashboard",label:"dashboard"},{id:"services",label:"services"},{id:"logs",label:"logs"},{id:"proxy",label:"proxy"}];An=ho.map((e)=>e.id)});function Mx(e,t=!0){let r=e<Yc?Lx+1:Lx,n=t?wL:0;return r+n+xL+n+bL+n}function ai(e){return Math.max(yL,e-vL*2)}function Ox(e){let t=e.width<hL,r=e.proxyOn?`\u25CF ${_e(e.proxyAddress,t?10:18)}`:"";return[{label:t?`${e.running}/${e.total}`:Rx(e.running,e.total),tone:e.running>0?"success":"idle"},{label:r,tone:"info",hide:!e.proxyOn},{label:"MCP",tone:"info",hide:!e.mcpOn},{label:e.adc?t?"ADC":"ADC ok":t?"!ADC":"ADC missing",tone:e.adc?"success":"error"},{label:t?"sec":"secrets shown",tone:"warning",hide:!e.reveal}]}function Dx(e){return e.key==="/"&&e.label==="command"}function SL(e){return e.key.length+e.label.length+3}function Ix(e,t){let r=e.filter(Dx),n=e.filter((s)=>!Dx(s)),o=[],i=0;for(let s of[...r,...n]){let a=SL(s);if(i+a>t)break;o.push(s),i+=a}return o}function Xc(e,t,r,n,o="center",i=!0){let s=Math.min(r,Math.max(1,e-4)),a=Math.min(Mx(e,i),Math.max(0,t-1)),c=Math.min(n,Math.max(1,t-a)),l=Math.max(0,Math.min(Math.floor((e-s)/2),Math.max(0,e-s))),d=o==="bottom"?t-2-c:Math.floor((t-c)/2),u=Math.max(0,Math.min(d,t-c));return{left:l,top:u,width:s,height:c}}function $x(e){if(e==="")return"idle";if(CL.test(e))return"error";if(e.includes("session only"))return"warning";if(kL.test(e))return"success";return"info"}function Fx(e,t,r){if(e==="restart-cascade")return{title:"Restart dependents?",body:`${r?.services?.join(", ")||"selected services"} has dependents. Enter restarts only the named services. Press c to cascade (same as /restart --cascade).`};if(e==="quit")return{title:"Quit",body:"Stop managed services and leave the TUI? Press d to detach (daemon stays). Press k to stop the daemon and leave services running (devctl down --keep-services)."};if(e==="reload")return{title:"Reload applied",body:t===""?"Configuration changed. Restart marked services?":`Restart required: ${t}`};if(e==="reset-prefs")return{title:"Reset preferences",body:"Restore theme, display size, mouse, and leader timeout to defaults? This overwrites your saved tui.json values."};if(e==="free-port"){let n=r?.port??0,o=r?.process||"process",i=r?.pid??0;return{title:`Free port ${n}`,body:`Stop ${o} (pid ${i}) so port ${n} can be used? This sends SIGTERM, then SIGKILL if it stays up.`}}return{title:"Start profile",body:t===""?"Start the configured services?":`Start profile ${t}?`}}function Nx(e){if(e==="quit")return[{key:"enter",label:"stop services"},{key:"d",label:"detach"},{key:"k",label:"down, keep services"},{key:"esc",label:"stay"}];if(e==="restart-cascade")return[{key:"enter",label:"named only"},{key:"c",label:"cascade"},{key:"esc",label:"stay"}];return[{key:"enter",label:"confirm"},{key:"esc",label:"stay"}]}function Hx(e){let t=e.payload?.error;return typeof t==="string"&&t!==""?t:"configuration reload failed"}var si=100,Yc=90,hL=60,yL=8,vL=6,Lx=1,xL=1,bL=1,wL=1,rj,kL,CL;var yr=D(()=>{It();hr();Ln();rj=Mx(Yc-1);kL=/^(started|stopped|restarted|refreshed|cleared|copied|exported|jumped|proxy started|proxy stopped|secrets|re-running|theme |mouse |leader |display |restored)/i,CL=/\b(fail|error|unknown|not found|no configuration|blocked|already in use|is using port)\b/i});function Qc(){return[{name:"start",aliases:["up"],desc:"Start selected services or the current profile",leader:"n",group:"services",usage:"<service>"},{name:"stop",aliases:[],desc:"Stop selected services",leader:"x",group:"services",usage:"<service>"},{name:"restart",aliases:[],desc:"Restart selected services",leader:"R",group:"services",usage:"<service>",suggest:[{token:"--cascade",desc:"Restart selected services and their dependents"}]},{name:"run",aliases:["task"],desc:"Run a one-off task; empty /run opens a picker",leader:"",group:"services",usage:"<task>"},{name:"exec",aliases:[],desc:"Run a command in a service context; empty /exec opens a picker",leader:"",group:"services",usage:"<service>"},{name:"services",aliases:["s"],desc:"Open the services screen",leader:"s",group:"nav"},{name:"logs",aliases:["l"],desc:"Open the log viewer",leader:"l",group:"nav"},{name:"auth",aliases:["identity","a"],desc:"Open identity",leader:"a",group:"nav",suggest:[{token:"login",desc:"Run gcloud ADC login"},{token:"logout",desc:"Revoke application-default credentials"},{token:"refresh",desc:"Probe identities"}]},{name:"credentials",aliases:["creds"],desc:"Open credential store status",leader:"",group:"nav"},{name:"reload",aliases:[],desc:"Reload configuration",leader:"",group:"ui"},{name:"proxy",aliases:["p"],desc:"Open the proxy screen",leader:"p",group:"nav"},{name:"mcp",aliases:["agent"],desc:"Open the MCP server screen for coding agents",leader:"",group:"nav"},{name:"doctor",aliases:["d"],desc:"Run environment diagnostics",leader:"d",group:"nav"},{name:"stats",aliases:["metrics"],desc:"View system and service statistics",leader:"m",group:"nav"},{name:"config",aliases:["c"],desc:"View merged configuration",leader:"c",group:"nav"},{name:"import",aliases:[],desc:"Preview or write a Compose mapping",leader:"",group:"ui",suggest:[{token:"compose",desc:"Preview a Compose mapping; add --write to save"}]},{name:"diff",aliases:["provenance"],desc:"Show winning config sources and what they shadowed",leader:"",group:"ui"},{name:"daemon",aliases:["bootstrap"],desc:"Show supervisor bootstrap logs (same file as devctl daemon logs)",leader:"",group:"app"},{name:"update",aliases:[],desc:"Install a newer GitHub Release when the install method is known",leader:"",group:"app"},{name:"profiles",aliases:["o"],desc:"Select a development profile",leader:"o",group:"nav"},{name:"setup",aliases:["init"],desc:"Open setup guidance",leader:"i",group:"nav"},{name:"dashboard",aliases:["home"],desc:"Return to the dashboard",leader:"h",group:"nav"},{name:"themes",aliases:["theme"],desc:"List available themes",leader:"t",group:"ui"},{name:"settings",aliases:["prefs","preferences"],desc:"Open TUI settings (theme, mouse, MCP page)",leader:"",group:"ui"},{name:"help",aliases:["?"],desc:"Show the help dialog",leader:"",group:"ui"},{name:"refresh",aliases:[],desc:"Refresh status and logs",leader:"r",group:"ui"},{name:"regex",aliases:[],desc:"Toggle regex log search",leader:"",group:"logs"},{name:"since",aliases:[],desc:"Filter logs after an ISO timestamp",leader:"",group:"logs",usage:"<timestamp>"},{name:"until",aliases:[],desc:"Filter logs before an ISO timestamp",leader:"",group:"logs",usage:"<timestamp>"},{name:"history",aliases:[],desc:"Load a persisted log session",leader:"",group:"logs",usage:"<session>"},{name:"edit",aliases:[],desc:"Open configuration in $EDITOR",leader:"",group:"ui"},{name:"buffer",aliases:[],desc:"Edit configuration in a validate/save buffer",leader:"",group:"ui"},{name:"pause",aliases:[],desc:"Pause or resume live logs",leader:"",group:"logs"},{name:"fullscreen",aliases:["zen","expand"],desc:"Expand logs to fill the terminal",leader:"z",group:"logs"},{name:"split",aliases:[],desc:"Split the logs screen into two service panes",leader:"",group:"logs"},{name:"trace",aliases:[],desc:"Search logs for a request or trace id",leader:"",group:"logs",usage:"<id>"},{name:"filter",aliases:[],desc:"Toggle ERROR+ log filter",leader:"",group:"logs"},{name:"system",aliases:["internal"],desc:"Show or hide internal auth/mcp/devctl/proxy logs",leader:"",group:"logs"},{name:"reveal",aliases:[],desc:"Reveal or hide secret environment values",leader:"",group:"ui"},{name:"wrap",aliases:[],desc:"Cycle log wrap: selected, all lines, or clip",leader:"",group:"logs"},{name:"copy",aliases:[],desc:"Copy the highlighted selection to the clipboard",leader:"",group:"ui"},{name:"export",aliases:[],desc:"Write filtered logs to ~/.devctl/exports",leader:"e",group:"logs"},{name:"exports",aliases:["open-exports"],desc:"Open the log export folder",leader:"",group:"logs"},{name:"clear",aliases:["new"],desc:"Clear the on-screen log buffer",leader:"",group:"logs"},{name:"version",aliases:["v"],desc:"Show the current devctl version",leader:"",group:"app"},{name:"down",aliases:[],desc:"Stop the supervisor",leader:"",group:"app",suggest:[{token:"--keep-services",desc:"Stop the supervisor and leave processes running"}]},{name:"exit",aliases:["quit","q"],desc:"Exit (detach or stop services)",leader:"q",group:"app"}]}function yo(e){let t=e.trim().toLowerCase().replace(/^\//,""),r=t.indexOf(" ");return r===-1?t:t.slice(0,r)}function Ux(e){let t=yo(e);if(t==="")return Qc();return Qc().map((r)=>({command:r,score:HL(r,t)})).filter((r)=>r.score>0).sort((r,n)=>n.score-r.score||r.command.name.localeCompare(n.command.name)).map((r)=>r.command)}function HL(e,t){if(t==="")return 1;let r=e.name.toLowerCase(),n=e.aliases.map((o)=>o.toLowerCase());return WL(r,n,t)||jL(r,n,t)||GL(r,n,t)||UL(r,n,t)||BL(e.suggest,t)||KL(e.desc.toLowerCase(),t)}function BL(e,t){if(!e||e.length===0)return 0;let r=e.map((n)=>n.token.toLowerCase());if(r.some((n)=>n===t||n.replace(/^-+/,"")===t))return Wx;if(t.length<Gx)return 0;return r.some((n)=>n.includes(t)||n.replace(/^-+/,"").startsWith(t))?jx:0}function WL(e,t,r){if(e===r)return RL;return t.includes(r)?PL:0}function jL(e,t,r){if(e.startsWith(r))return _L;if(t.some((i)=>i.startsWith(r)))return EL;let n=e.split("-");return n.length>1&&n.some((i)=>i.startsWith(r))?TL:0}function GL(e,t,r){if(r.length<Gx)return 0;if(e.includes(r))return Wx;return t.some((n)=>n.includes(r))?jx:0}function UL(e,t,r){if(r.length<OL)return 0;let n=Bx(e,r);if(n>0)return AL+Math.min(n,NL);return t.some((o)=>Bx(o,r)>0)?LL:0}function Bx(e,t){let r=0,n=0,o=0;for(let i=0;i<t.length;i+=1){let s=t[i];if(s===void 0)return 0;let a=e.indexOf(s,r);if(a<0)return 0;if(r>0&&a===r)n+=1,o+=2+n;else n=0,o+=a===0?4:1;r=a+1}return o}function KL(e,t){if(t.length<IL)return 0;if(e.split(/[^a-z0-9+]+/).filter((n)=>n.length>=$L).some((n)=>n.startsWith(t)))return DL;return t.length>=FL&&e.includes(t)?ML:0}function Br(e){let t=e.trim().toLowerCase().replace(/^\//,"").split(" ")[0]??"";return Qc().find((r)=>r.name===t||r.aliases.includes(t))}function Cp(e){return e.trim().replace(/^\//,"").split(/\s+/).slice(1).filter((r)=>r!=="")}function Kx(e){return Qc().find((r)=>r.leader===e)?.name??""}function qx(e){let t=e.includes("--cascade")||e.includes("-c");return{services:e.filter((n)=>n!=="--cascade"&&n!=="-c"),cascade:t}}function Vx(e){let t=e.includes("--print-env"),r=e.includes("--reveal"),n=e.filter((o)=>o!=="--print-env"&&o!=="--reveal"&&o!=="--");return{service:n[0]??"",printEnv:t,reveal:r,command:n.slice(1)}}var RL=100,PL=90,_L=80,EL=70,TL=60,Wx=50,jx=45,AL=30,LL=28,DL=20,ML=10,Gx=2,OL=3,IL=4,$L=4,FL=4,NL=9;function Xx(e){return e==="detail"?"services":e}function Qx(e){let t=An.indexOf(Xx(e));if(t<0)return"dashboard";return An[(t+1)%An.length]??"dashboard"}function Zx(e){let t=An.indexOf(Xx(e));if(t<0)return"dashboard";return An[(t-1+An.length)%An.length]??"dashboard"}function Pp(e){return["services","nav","logs","ui","app","tasks"].flatMap((r)=>{let n=e.filter((o)=>o.group===r);if(n.length===0)return[];return[{group:r,items:n}]})}function Zc(e){return e.hint?`${e.name} ${e.hint}`:e.name}function _p(e){return e.name}function eb(e){if(e.hint)return`${e.hint} \xB7 ${e.desc}`;let t=[e.usage,...(e.suggest??[]).map((r)=>r.token)].filter((r)=>Boolean(r));if(t.length===0)return e.desc;return`${e.desc} \xB7 ${t.join(" \xB7 ")}`}function tb(e){let t=e.reduce((n,o)=>Math.max(n,_p(o).length),0),r=Math.min(Rp,Math.max(t,1));return qL+r+VL}function rb(e){return e.hint?`${e.name} ${e.hint} `:`${e.name} `}function nb(e,t){if(e.hint)return e.hint.split(/\s+/);return Br(t)?.name===e.name?Cp(t):[]}function Jx(e,t){if(t==="")return!0;let r=t.toLowerCase(),n=e.toLowerCase();return n.startsWith(r)||n.includes(r)}function JL(e,t){if(e.name==="start"||e.name==="stop"||e.name==="restart")return(t.services??[]).map((r)=>({token:r,desc:`${e.name} ${r}`}));if(e.name==="run")return(t.tasks??[]).map((r)=>({token:r,desc:`run ${r}`}));if(e.name==="exec")return(t.services??[]).map((r)=>({token:r,desc:`exec in ${r}`}));return[]}function zx(e,t){return{...e,hint:t.token,desc:t.desc,usage:void 0,suggest:void 0}}function zL(e,t,r={}){let n=Cp(t).join(" "),o=t.trim().includes(" ")||t.endsWith(" "),i=yo(t),s=[];for(let a of e){if(s.push({...a,hint:void 0}),i==="")continue;let c=a.name===i||a.aliases.includes(i),l=c?n:i;for(let d of a.suggest??[])if(Jx(d.token,l))s.push(zx(a,d));if(!c||!o)continue;for(let d of JL(a,r))if(Jx(d.token,n))s.push(zx(a,d))}return s}function ob(e,t={}){let r=Ux(e),n=yo(e)===""?Pp(r).flatMap((o)=>o.items):r;return zL(n,e,t)}function Ep(e,t,r,n){let o=yo(t),i=[...e].sort().map((s)=>({name:s,aliases:[],desc:n,leader:"",group:r}));if(o==="")return i;return i.filter((s)=>s.name.toLowerCase().includes(o))}function ib(e,t,r=Xr()){if(t==="slash")return[{key:"\u2191\u2193",label:"suggest"},{key:"tab",label:"complete"},{key:"enter",label:"run"},{key:"esc",label:"cancel"}];if(t==="themes")return[Wr,{key:"\u2191\u2193",label:"move"},{key:"enter",label:"save"},{key:"esc",label:"revert"}];if(t==="help")return[Wr,{key:"j/k",label:"scroll"},{key:"esc",label:"close"}];if(t==="log-details"||t==="scroll-text")return[Wr,{key:"j/k",label:"scroll"},{key:Ci(r),label:"copy"},{key:"esc",label:"close"}];if(t==="confirm")return[{key:"enter",label:"confirm"},{key:"esc",label:"stay"}];if(t==="plan")return[Wr,{key:"esc",label:"back to dashboard"},{key:"enter",label:"done"}];if(t==="leader")return[Wr,...Tp()];if(t==="config-edit")return[{key:Mt("s"),label:"save"},{key:"esc",label:"discard"}];return YL(e,r)}function Tp(){return[{key:"n",label:"start"},{key:"x",label:"stop"},{key:"R",label:"restart"},{key:"s",label:"services"},{key:"l",label:"logs"},{key:"t",label:"themes"},{key:"q",label:"quit"}]}function YL(e,t){let r=[{key:Ci(t),label:"copy"},Wr,{key:"?",label:"help"}];switch(e){case"dashboard":return[{key:"space",label:"select"},{key:"*",label:"all"},{key:"-",label:"none"},{key:"enter",label:"start or open"},{key:"n",label:"start"},{key:"x",label:"stop"},{key:"r",label:"refresh"},{key:"R",label:"restart"},{key:"\u2190\u2192",label:"log filter"},{key:"g",label:"latest"},{key:"z",label:"full logs"},{key:"i",label:"internal logs"},{key:Mt("l"),label:"clear logs"},{key:"j/k",label:"move"},...r];case"services":return[{key:"enter",label:"detail"},{key:"space",label:"select"},{key:"*",label:"all"},{key:"-",label:"none"},{key:"n",label:"start"},{key:"x",label:"stop"},{key:"r",label:"refresh"},{key:"R",label:"restart"},...r];case"detail":return[{key:"j/k",label:"scroll env"},{key:"n",label:"start"},{key:"x",label:"stop"},{key:"o",label:"config"},{key:"l",label:"logs"},{key:"esc",label:"back"},...r];case"logs":return[{key:"\u2190\u2192",label:"filter"},{key:"e",label:"errors"},{key:"i",label:"internal logs"},{key:Mt("l"),label:"clear logs"},{key:"g",label:"latest"},{key:"f",label:"search"},{key:"t",label:"time"},{key:"m",label:"meta"},{key:"w",label:"wrap"},{key:"j/k",label:"move"},{key:"p",label:"pause"},{key:"z",label:"full screen"},{key:"\\",label:"split"},{key:"/exports",label:"open folder"},...r];case"profiles":return[{key:"space",label:"set current"},{key:"enter",label:"set and start"},{key:"j/k",label:"move"},...r];case"proxy":return[{key:"n",label:"start proxy"},{key:"x",label:"stop proxy"},...r];case"mcp":return[{key:"j/k",label:"move"},{key:"space",label:"start or copy"},{key:"\u2190\u2192",label:"change port"},{key:"enter",label:"start or copy"},...r];case"config":return[{key:"enter",label:"run task"},{key:"v",label:"buffer"},{key:"e",label:"editor"},{key:"/diff",label:"sources"},{key:"/reload",label:"reload"},{key:"j/k",label:"scroll or select task"},...r];case"setup":return[{key:"j/k",label:"steps"},{key:"enter",label:"continue"},{key:"esc",label:"back"},{key:"esc\xD72",label:"quit"},...r];case"doctor":return[{key:"r",label:"run doctor again"},{key:"j/k",label:"move"},{key:"enter",label:"fix port"},...r];case"auth":return[{key:"r",label:"probe identities"},{key:"/auth login",label:"ADC login"},{key:"/auth logout",label:"revoke ADC"},{key:"/auth refresh",label:"probe"},...r];case"settings":return[{key:"j/k",label:"move"},{key:"\u2190\u2192",label:"save"},{key:"enter",label:"apply or open page"},{key:"space",label:"toggle"},...r];default:return[{key:"tab",label:"screens"},...r]}}function sb(e,t,r){if(r<=t||e<t)return 0;return Math.min(e-t+1,r-t)}function Yx(e,t,r){let n=e.slice(t,r+1),o=new Set(n.map((i)=>i.group));return n.length+o.size}function ab(e,t,r){if(e.length===0)return[];let n=Math.min(Math.max(t,0),e.length-1),o=n,i=n;for(;;){let s=i+1<e.length&&Yx(e,o,i+1)<=r,a=o>0&&Yx(e,o-1,i)<=r;if(s)i+=1;else if(a)o-=1;else break}return e.slice(o,i+1)}function ci(e,t){if(e.length===0)return;let r=Math.min(Math.max(t,0),e.length-1);return e[r]}var Wr,qL=3,VL=2,Rp=22;var vo=D(()=>{Pr();Ln();Wr={key:"/",label:"command"}});function tl(e,t){let r=t??Object.keys(e?.services??{});return{running:r.filter((o)=>mo(e?.services[o])).length,total:r.length}}function cb(e,t){let r=0,n=0,o=0,i=0,s=0,a=0;for(let c of e){let l=t?.services[c];if(l?.health==="HEALTHY")o+=1;let d=l?.state??"STOPPED";if(d==="FAILED")i+=1;else if(d==="STOPPING")s+=1;else if(d==="STARTING"||d==="RESTARTING")n+=1;else if(Ss(d))r+=1;else a+=1}return{total:e.length,live:r+n,running:r,starting:n,healthy:o,failed:i,stopping:s,stopped:a}}function lb(e,t){if(!Number.isFinite(e)||!Number.isFinite(t)||t<=0)return 0;return e/t}function rD(e,t,r){if(!Number.isFinite(e))return"success";if(e>r)return"error";if(e>t)return"warning";return"success"}function nD(e,t){return rD(lb(e,t),XL,QL)}function Mp(e,t=24){if(e.length===0||t<=0)return"";let r=e.length>t?e.slice(e.length-t):e,n=Math.max(...r,0);if(n<=0)return Cs[0]?.repeat(r.length)??"";return r.map((o)=>{let i=Number.isFinite(o)?Math.min(1,Math.max(0,o/n)):0,s=Math.min(Cs.length-1,Math.round(i*(Cs.length-1)));return Cs[s]??Cs[0]}).join("")}function oD(e,t){let r=t>0?e/t:1;if(r<eD)return"error";if(r<ZL)return"warning";return"success"}function Ap(e){if(!Number.isFinite(e)||e<0)return"\u2014";return e.toFixed(2)}function iD(e){if(!Number.isFinite(e)||e<0)return"\u2014";return`${Math.round(e*100)}%`}function db(e,t=Date.now()){if(!e?.startTime)return"\u2014";let r=new Date(e.startTime).getTime();if(!Number.isFinite(r))return"\u2014";return hs(t-r)}function ub(e,t=tD){return Object.entries(e).sort((r,n)=>n[1]-r[1]||r[0].localeCompare(n[0])).slice(0,t)}function pb(e,t=1){return Math.max(24,e-pD-t*2-fD-mD-gD)}function fb(e){let t=e>=sD,r=e>=aD,n=e>=cD,o=e>=lD,i=e>=dD,s=e>=uD,a=Er+$t;if(t)a+=Hr+$t;if(r)a+=xs+$t;if(n)a+=bs+$t;if(o)a+=vs+$t;if(i)a+=rl+$t;if(s)a+=nn;return{name:Math.max(Ac,e-a),health:t,cpu:r,mem:n,up:o,rst:i,pid:s}}function mb(e){let t=Number.isFinite(e)&&e>0?e:0;return{ratio:Math.min(1,t),label:iD(t)}}function gb(e,t=vD){return`[${ni(e.ratio,t)}] ${e.label.padStart(4)}`}function Op(e,t){let r=e.reduce((l,d)=>Math.max(l,d.what.length),4),n=e.reduce((l,d)=>Math.max(l,d.reading.length),7),o=e.some((l)=>l.meter)?xD:0,i=li*(o>0?3:2),s=Math.min(hD,Math.max(Lp,r)),a=Math.min(yD,Math.max(Dp,n)),c=t-s-a-o-i;if(c<el&&a>Dp)a=Math.max(Dp,a-(el-c)),c=t-s-a-o-i;if(c<el&&s>Lp)s=Math.max(Lp,s-(el-c)),c=t-s-a-o-i;return{what:s,reading:a,meaning:Math.max(0,c),meter:o}}function hb(e){return bD.has((e?.health.type??"").toLowerCase())}function yb(e){if(e==="darwin")return"macOS";if(e==="win32")return"Windows";if(e==="linux")return"Linux";return e||"this computer"}function vb(e,t){let r=Math.max(1,t),n=nD(e,r),o=n==="error"?"overloaded":n==="warning"?"busy":"not busy",i=n==="error"?`${r} cores are queued up \u2014 last minute ${Ap(e)}`:n==="warning"?`${r} cores nearly full \u2014 last minute ${Ap(e)}`:`${r} cores. Last minute ${Ap(e)} (under ${r} is fine)`;return{what:"CPU work",reading:o,meaning:i,tone:n,meter:mb(lb(e,r))}}function xb(e,t){let r=oD(e,t),n=`${ys(e)} of ${ys(t)}`,o=r==="error"?"Almost no RAM left for new work":r==="warning"?"RAM is getting tight":"RAM the computer can still give out",i=t>0?Math.max(0,(t-e)/t):0;return{what:"RAM leftover",reading:n,meaning:o,tone:r,meter:mb(i)}}function bb(e){let t=e?.state??"STOPPED";if(t==="FAILED")return"crashed";if(t==="STOPPING")return"stopping";if(t==="STARTING"||t==="RESTARTING")return"starting";if(Ss(t))return"up";return"off"}function wb(e){if(!e||!Ss(e.state))return"\u2014";if(e.health==="HEALTHY")return"ready";if(e.health==="UNHEALTHY")return"failing";if(e.state==="STARTING"||e.state==="RESTARTING")return"waiting";return"checking"}function Sb(e){if(e==="keychain")return"password store";if(e==="file")return"local file";return e||"unknown store"}function kb(e,t){let r=[{what:"Started",reading:String(e.live),meaning:e.live===0?"nothing is running yet":"process is up",tone:e.live>0?"success":"muted"}];if(t)r.push({what:"Ready",reading:String(e.healthy),meaning:e.live===0?"start services first":"passed their health check",tone:e.live>0&&e.healthy<e.live?"warning":e.healthy>0?"success":"muted"});if(e.starting>0)r.push({what:"Still starting",reading:String(e.starting),meaning:"not ready for traffic yet",tone:"warning"});if(e.stopping>0)r.push({what:"Stopping",reading:String(e.stopping),meaning:"shutting down",tone:"warning"});return r.push({what:"Crashed",reading:String(e.failed),meaning:e.failed>0?"open the service for the error":"none",tone:e.failed>0?"error":"muted"}),r.push({what:"Not started",reading:String(e.stopped),meaning:e.stopped>0?"off until you start them":"all are up",tone:"muted"}),r}var XL=0.85,QL=1.5,ZL=0.2,eD=0.1,tD=5,Cs="\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588",sD=52,aD=64,cD=72,lD=82,dD=90,uD=100,pD=2,fD=2,mD=2,gD=1,rl=8,Lp=8,hD=22,Dp=6,yD=28,el=12,li=2,vD=10,xD=18,bD;var nl=D(()=>{It();At();bD=new Set(["http","tcp","command"])});import{useEffect as wD,useRef as SD}from"react";import{jsxDEV as me,Fragment as CD}from"@opentui/react/jsx-dev-runtime";function kD(e,t){switch(t){case"primary":return{bg:e.primary,fg:uo(e.primary,e.inverse)};case"accent":return{bg:e.accent,fg:Cc(e)?kc:uo(e.accent,e.inverse)};case"success":return{bg:e.success,fg:uo(e.success,e.inverse)};case"warning":return{bg:e.warning,fg:Cc(e)?kc:uo(e.warning,e.inverse)};case"error":return{bg:e.error,fg:uo(e.error,e.inverse)};case"info":return{bg:e.info,fg:uo(e.info,e.inverse)};case"muted":return{bg:e.element,fg:e.text};case"ghost":return{bg:e.element,fg:e.muted};default:return{bg:e.element,fg:e.text}}}function Cb(e){let{palette:t,dir:r}=e;return me("box",{width:2,height:1,flexShrink:0,overflow:"hidden",children:me("text",{fg:t.muted,children:r==="left"?"\u2039":"\u203A"},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function jt(e){let{palette:t,children:r,backgroundColor:n,edge:o="bottom"}=e,i=ve(),s=e.ruled??!po(i),a=n??t.element,c=me("box",{flexShrink:0,flexDirection:"column",backgroundColor:a,overflow:"hidden",children:r},void 0,!1,void 0,this);if(!s)return c;return me("box",{flexShrink:0,flexDirection:"column",overflow:"hidden",border:[...o==="both"?["top","bottom"]:[o]],borderStyle:"single",borderColor:t.border,children:c},void 0,!1,void 0,this)}function ol(e){let{palette:t,items:r,active:n,width:o,onPick:i,emphasis:s="ghost"}=e,a=r.map((u)=>ii(u.label)),c=_x(a,n,Math.max(1,o)),l=c.start>0,d=c.end>=0&&c.end<r.length-1;return me("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:t.element,children:[l?me(Cb,{palette:t,dir:"left"},void 0,!1,void 0,this):null,r.map((u,f)=>{if(f<c.start||f>c.end)return null;let p=f===n,m=s==="fill"&&p;return me(We,{palette:t,label:u.label,tone:m?"primary":"ghost",bg:m?void 0:p?t.highlight:t.element,fg:m?void 0:p?t.primary:u.color??t.muted,onMouseDown:()=>i(f)},u.id,!1,void 0,this)}),d?me(Cb,{palette:t,dir:"right"},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}function We(e){let{palette:t,label:r,tone:n="idle",fg:o,bg:i,onMouseDown:s}=e,a=kD(t,n);return me("box",{height:1,paddingLeft:1,paddingRight:1,backgroundColor:i??a.bg,overflow:"hidden",flexShrink:0,onMouseDown:s,children:me("text",{fg:o??a.fg,children:r},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function ot(e){let{palette:t,hints:r}=e;return me("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:r.map((n)=>me("box",{height:1,paddingLeft:1,paddingRight:1,flexShrink:0,overflow:"hidden",children:me("text",{wrapMode:"none",children:[me("span",{fg:t.primary,children:n.key},void 0,!1,void 0,this),me("span",{fg:t.muted,children:` ${n.label}`},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},`${n.key}-${n.label}`,!1,void 0,this))},void 0,!1,void 0,this)}function dt(e){let{palette:t,items:r,hints:n,ruled:o=!0}=e,i=me("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:t.element,children:[r.map((s,a)=>me(We,{palette:t,label:s.text,tone:s.tone,onMouseDown:s.onMouseDown},`${s.text}-${a}`,!1,void 0,this)),n&&n.length>0?me(CD,{children:[me("box",{flexGrow:1,backgroundColor:t.element},void 0,!1,void 0,this),me(ot,{palette:t,hints:n},void 0,!1,void 0,this)]},void 0,!0,void 0,this):null]},void 0,!0,void 0,this);if(!o)return i;return me(jt,{palette:t,backgroundColor:t.element,children:i},void 0,!1,void 0,this)}function ut(e){let{palette:t,title:r,termW:n,termH:o,preferW:i,preferH:s,anchor:a="center",borderColor:c,bottomTitle:l,gap:d,children:u}=e,f=po(ve()),p=Xc(n,o,i,s,a,!f);return me("box",{position:"absolute",left:p.left,top:p.top,width:p.width,height:p.height,border:!0,borderStyle:Ip,borderColor:c??t.borderActive,backgroundColor:t.panel,padding:f?0:1,title:r,titleColor:t.primary,bottomTitle:l,gap:f?0:d,flexDirection:"column",overflow:"hidden",children:u},void 0,!1,void 0,this)}function je(e){return{rootOptions:{flexGrow:1,height:"100%",overflow:"hidden",backgroundColor:e.panel},viewportOptions:{backgroundColor:e.panel},contentOptions:{backgroundColor:e.panel},scrollbarOptions:{trackOptions:{foregroundColor:e.primary,backgroundColor:e.element}}}}function rt(e,t){if(!e)return;e.scrollBy({x:0,y:t})}function di(e,t){let r=SD(null);return wD(()=>{let n=r.current;if(!n||e<0)return;let o=requestAnimationFrame(()=>{n.scrollChildIntoView(`${t}-${e}`)});return()=>cancelAnimationFrame(o)},[t,e]),r}function kt(e){let t=ve(),r=gs(t),n=e.scroll?me("scrollbox",{focused:!1,stickyScroll:!1,scrollX:!1,style:je(e.palette),children:me("box",{flexDirection:"column",overflow:"hidden",children:e.children},void 0,!1,void 0,this)},void 0,!1,void 0,this):e.children;return me("box",{flexGrow:1,border:!0,borderStyle:Ip,borderColor:e.borderColor??e.palette.border,title:e.title,titleColor:e.palette.primary,paddingLeft:t.pad,paddingRight:t.pad,paddingTop:r?0:t.pad,paddingBottom:r?0:t.pad,backgroundColor:e.palette.panel,flexDirection:"column",overflow:"hidden",children:n},void 0,!1,void 0,this)}function xo(e){let{palette:t,label:r,value:n,tone:o="text"}=e,i=ve(),s=o==="success"?t.success:o==="warning"?t.warning:o==="error"?t.error:o==="muted"?t.muted:t.text;return me("box",{height:i.rowH,flexDirection:"row",overflow:"hidden",children:[me("box",{width:14,flexShrink:0,overflow:"hidden",children:me("text",{fg:t.muted,children:r},void 0,!1,void 0,this)},void 0,!1,void 0,this),me("box",{flexGrow:1,overflow:"hidden",children:me("text",{fg:s,wrapMode:"none",children:n},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function il(e){let{palette:t,title:r,body:n,hint:o}=e;return me("box",{paddingLeft:1,paddingRight:1,flexDirection:"column",overflow:"hidden",border:!0,borderStyle:Ip,borderColor:t.borderActive,titleColor:t.primary,children:[me("text",{fg:t.primary,children:r},void 0,!1,void 0,this),me("text",{fg:t.text,wrapMode:"word",children:n},void 0,!1,void 0,this),o?me("text",{fg:t.muted,wrapMode:"word",children:o},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}var Ip="rounded";var ke=D(()=>{Ot();yr();Ln();_r();tt()});import{jsxDEV as Oe,Fragment as PD}from"@opentui/react/jsx-dev-runtime";function Pb(e){let{palette:t,cfg:r,snap:n,google:o,profile:i,reveal:s,width:a}=e,c=tl(n,r?Object.keys(r.services):void 0),l=n?.proxy.running===!0,d=o?.adcAvailable===!0,u=a<Yc,f=_e(r?.project.name||"local",u?18:22),p=_e(i||n?.profile||"no profile",16),m=Ox({width:a,running:c.running,total:c.total,proxyOn:l,proxyAddress:n?.proxy.address??"",mcpOn:n?.mcp?.running===!0,adc:d,reveal:s}).filter((x)=>!x.hide&&x.label!=="").map((x)=>Oe(We,{palette:t,label:x.label,tone:x.tone},x.label,!1,void 0,this)),g=gs(ve()),y=Oe("box",{height:1,flexGrow:1,overflow:"hidden",backgroundColor:t.panel,paddingLeft:1,children:Oe("text",{wrapMode:"none",children:[Oe("span",{fg:t.primary,children:er()},void 0,!1,void 0,this),Oe("span",{fg:t.muted,children:` ${f}`},void 0,!1,void 0,this),Oe("span",{fg:t.accent,children:` ${p}`},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this);return Oe(jt,{palette:t,backgroundColor:t.panel,ruled:!g,children:Oe("box",{height:u?2:1,flexDirection:"column",backgroundColor:t.panel,overflow:"hidden",children:[Oe("box",{height:1,flexDirection:"row",overflow:"hidden",children:[y,u?null:m]},void 0,!0,void 0,this),u?Oe("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:t.panel,children:m},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}function _b(e){let{palette:t,screen:r,width:n,onSelect:o}=e,i=n>=Rb+RD,s=i?Math.max(1,n-Rb):n,a=ho.map((l)=>({id:l.id,label:Px(l.label,s)})),c=Ex(r);return Oe(jt,{palette:t,backgroundColor:t.element,children:Oe("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:t.element,children:[Oe(ol,{palette:t,items:a,active:c,width:s,onPick:(l)=>{let d=ho[l];if(d)o(d.id)}},void 0,!1,void 0,this),i?Oe(PD,{children:[Oe("box",{flexGrow:1,backgroundColor:t.element},void 0,!1,void 0,this),Oe(ot,{palette:t,hints:[Wr]},void 0,!1,void 0,this)]},void 0,!0,void 0,this):null]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}function Eb(e){let{palette:t,screen:r,overlay:n,status:o,paused:i,errorOnly:s,width:a,copyKey:c}=e,l=gs(ve()),d=Math.max(18,Math.floor(a*0.42)),u=Ix(ib(r,n,c),d),f=$x(o);return Oe(jt,{palette:t,backgroundColor:t.panel,edge:"top",ruled:!l,children:Oe("box",{height:1,backgroundColor:t.panel,overflow:"hidden",children:Oe(dt,{ruled:!1,palette:t,items:[{text:r,tone:"ghost"},{text:i?"PAUSED":"LIVE",tone:i?"warning":"success"},...s?[{text:"ERROR+",tone:"error"}]:[],...o===""?[]:[{text:_e(o,Math.max(16,a-d-28)),tone:f}]],hints:u},void 0,!1,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function Ve(e){return Oe("box",{flexGrow:1,padding:1,overflow:"hidden",children:Oe(il,{palette:e.palette,title:e.title,body:e.body,hint:e.hint},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function sl(e){return Oe("box",{flexGrow:1,padding:1,overflow:"hidden",children:Oe("text",{fg:e.palette.text,children:e.label},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function Tb(e){return Oe("box",{flexGrow:1,padding:1,flexDirection:"column",overflow:"hidden",children:[Oe("text",{fg:e.palette.error,children:e.title},void 0,!1,void 0,this),Oe("text",{fg:e.palette.text,wrapMode:"word",children:e.body},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var Rb,RD=8;var zt=D(()=>{Ot();yr();vo();It();Ln();nl();ke();_r();Rb=Wr.key.length+Wr.label.length+3});function _D(){if(process.platform==="darwin")return[["pbcopy"]];if(process.platform==="win32")return[["clip"]];return[["wl-copy"],["xclip","-selection","clipboard"],["xsel","--clipboard","--input"]]}function ED(){return _D().filter((e)=>{let t=e[0];return typeof t==="string"&&Bun.which(t)!==null})}function TD(e){return`\x1B]52;c;${Buffer.from(e,"utf8").toString("base64")}\x07`}function Ab(){if(process.platform==="linux")return"install wl-clipboard (Wayland) or xclip (X11)";return"no clipboard helper found"}async function al(e){let t=ED(),r;for(let n of t)try{await LD(n,e);return}catch(o){r=o instanceof Error?o:Error(String(o))}if(process.platform!=="win32")try{await AD(e);return}catch(n){r=n instanceof Error?n:Error(String(n))}throw Ne(ct,"clipboard unavailable",r?`${Ab()} (${r.message})`:Ab())}async function AD(e){let t=TD(e);try{await Bun.write("/dev/tty",t)}catch{if(!process.stderr.write(t))throw Error("terminal clipboard write failed")}}async function LD(e,t){let r=Bun.spawn(e,{stdin:"pipe",stdout:"ignore",stderr:"ignore"});r.stdin.write(t),await r.stdin.end();let n=await r.exited;if(n!==0)throw Error(`${e[0]} exited ${n}`)}var $p=D(()=>{te()});function Fp(e){return e==="slash"||e==="config-edit"||e==="setup-wizard"}function Lb(e,t){return pn(e,t.keybinds.leader??_o("x"))}function Db(e,t){return pn(e,t.keybinds.command_list??_o("p"))}function on(e,t){return pn(e,t.keybinds.command??"/")||e.name==="/"}function Mb(e,t){return pn(e,t.keybinds.help??"?")||e.sequence==="?"}function Ob(e,t){return pn(e,t.keybinds.search??"f")}function Ib(e){return(e.name??"").toLowerCase()==="q"&&!e.ctrl&&!e.meta&&!e.super&&!e.alt&&!e.option}function $b(e){let t=e.name??"";return t==="R"||e.shift===!0&&t.toLowerCase()==="r"}function Fb(e){return Or(e)&&(e.name??"").toLowerCase()==="l"}function Nb(e,t){return pn(e,t.keybinds.copy??Xr())}function Hb(e,t,r=DD){return t>0&&e-t<=r}function Dn(e,t,r,n){return pn(e,t.keybinds[r]??n)}function Rs(e){let t=(e.name??"").toLowerCase();return t==="pagedown"||Or(e)&&t==="d"}function Ps(e){let t=(e.name??"").toLowerCase();return t==="pageup"||Or(e)&&t==="u"}var DD=2000;var cl=D(()=>{Pr()});function ui(e){return`http://127.0.0.1:${e}/mcp`}function Np(e){if(e==="")return{};return{Authorization:`Bearer ${e}`}}function Hp(e,t){return JSON.stringify({mcpServers:{devctl:{type:"http",url:e,headers:Np(t)}}},null,2)}function Bp(e,t){return JSON.stringify({mcpServers:{devctl:{url:e,headers:Np(t)}}},null,2)}function Wp(e,t){return JSON.stringify({mcp:{devctl:{type:"remote",url:e,headers:Np(t),enabled:!0}}},null,2)}function jp(e,t){let r=t===""?"":`
2141
+ `),o=[];for(let i of n)if(i.length<=r)o.push(i);else o.push(...WL(i,r));return o.length>0?o:[""]}function Dx(e,t,r){let n=e.length>0?[...e]:[""];if(r||n.length<=1)return{visible:n,mark:"",folded:!1,hidden:0};let o=n.length-1,i=` ${NL}${o}`,s=Math.max(1,t-i.length);return{visible:[_e(n[0]??"",s)],mark:i,folded:!0,hidden:o}}function Ix(e,t){return e==="all"||e==="focus"&&t}function Xc(e){if(e==="clip")return"focus";if(e==="focus")return"all";return"clip"}function _p(e,t,r,n=Ar){if(e.length===0)return{start:0,items:[],newer:0};if(!t){let a=Math.max(0,e.length-n);return{start:a,items:e.slice(a),newer:0}}let o=e.length-1,i=Math.max(0,Math.min(r,o)),s=e.slice(i,i+n);return{start:i,items:s,newer:Math.max(0,e.length-i-s.length)}}function Qc(e,t=Ar){return Math.max(0,e-t)}function Ep(e,t,r,n){let o=Math.max(t-1,0);if(e<0&&r>0)return{selected:0,startDelta:Math.max(-r,e)};if(e>o&&n>0)return{selected:o,startDelta:Math.min(n,e-o)};return{selected:Math.min(o,Math.max(0,e)),startDelta:0}}function Zc(e){if(e==="all")return"wrap all";if(e==="focus")return"wrap selected";return"clip"}function WL(e,t){let r=[],n=e;while(n.length>t){let o=n.slice(0,t),i=GL(o);if(i>0)r.push(n.slice(0,i).trimEnd()),n=n.slice(i).trimStart();else r.push(n.slice(0,t)),n=n.slice(t)}if(n.length>0)r.push(n);return r}function GL(e){let t=e.lastIndexOf(" "),r=e.lastIndexOf("\t"),n=e.lastIndexOf(","),o=Math.max(t,r,n);if(o<Math.floor(e.length*HL))return-1;return e[o]===","?o+1:o}function KL(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function qL(e,t=!1){let r=e.trim();if(r==="")return;if(t){let n=Di(r);if(n)return new RegExp(n.source,n.flags.includes("g")?n.flags:`${n.flags}g`)}return new RegExp(KL(r),"gi")}function VL(e,t){let r=e.map((a)=>a.text).join(""),n=[];t.lastIndex=0;let o=t.exec(r);while(o){let a=o[0]??"";if(a.length>0)n.push({start:o.index,end:o.index+a.length});if(a.length===0)t.lastIndex+=1;o=t.exec(r)}if(n.length===0)return e;let i=[],s=0;for(let a of e){let c=s,l=s+a.text.length,d=0;for(let u of n){let f=Math.max(u.start,c),p=Math.min(u.end,l);if(p<=f)continue;let m=f-c,h=p-c;if(m>d)i.push({text:a.text.slice(d,m),kind:a.kind});i.push({text:a.text.slice(m,h),kind:"search"}),d=h}if(d<a.text.length)i.push({text:a.text.slice(d),kind:a.kind});s=l}return i.filter((a)=>a.text!=="")}function Mx(e,t="",r=!1){let n=Yc(e),o=[],i=new RegExp(jL.source,"gi"),s=0,a=i.exec(n);while(a){let d=a[0]??"";if(a.index>s)o.push({text:n.slice(s,a.index),kind:"text"});o.push({text:d,kind:BL(d)}),s=a.index+d.length,a=i.exec(n)}if(s<n.length)o.push({text:n.slice(s),kind:"text"});let c=o.length===0?[{text:n,kind:"text"}]:o,l=qL(t,r);return l?VL(c,l):c}function Ox(e){let t=e.showTimestamps?qc:0,r=e.showMeta?Kc:0;return Math.max(Ex,e.width-t-e.serviceWidth-Vc-r-Tx-Es*Ax)}function $x(e,t){let r=t.reduce((s,a)=>Math.max(s,a.length),0),n=qc+Vc+Tx+Ex+Es*Ax,o=Math.max(Rx,e-n),i=Math.max(Rx,r);return Math.min(wp,o,i)}function si(e){return zL.has(e)}function Nx(e,t,r,n){let o=r===""?t:t.filter((a)=>a.timestamp>=r);if(o.length===0)return e;let i=Math.max(1,n);if(o.length>=i)return o.slice(-i);let s=Math.max(0,e.length+o.length-i);return e.slice(s).concat(o)}function Hx(e,t){let r=t.length>0?t[t.length-1].seq:-1,n=e.filter((o)=>o.seq>r);return[...t,...n]}function jx(e,t){if(t.length===0)return e;let r=new Set(e.map((o)=>o.seq)),n=t.filter((o)=>!r.has(o.seq));return n.length===0?e:[...n,...e]}function Tp(e,t,r){return e&&t<=0&&r}function Bx(e){return e.filter((t)=>t.level==="ERROR"||t.level==="FATAL").length}function xo(e,t){let r=t.services?.filter((l)=>l!=="")??[],n=t.service??"",o=(t.search??"").trim(),i=t.source??"",s=t.since??"",a=t.until??"",c;if(o!=="")if(t.regex===!0){let l=Di(o);if(l)c=(d)=>l.test(d);else c=(d)=>d.toLowerCase().includes(o.toLowerCase())}else{let l=o.toLowerCase();c=(d)=>d.toLowerCase().includes(l)}return e.filter((l)=>{if(r.length>0&&!r.includes(l.service))return!1;if(n!==""&&l.service!==n)return!1;if(i!==""&&l.source!==i)return!1;if(t.systemLogs===!1&&si(l.source))return!1;if(s!==""&&l.timestamp<s)return!1;if(a!==""&&l.timestamp>a)return!1;if(t.errorOnly===!0&&l.level!=="ERROR"&&l.level!=="FATAL")return!1;if(!c)return!0;return c(l.message)||c(l.service)})}function YL(e,t){let r={};for(let i of e)r[i.service]=(r[i.service]??0)+1;let n=t.map((i)=>({name:i,count:r[i]??0})),o=Object.keys(r).filter((i)=>!t.includes(i)).sort().map((i)=>({name:i,count:r[i]??0}));return[...n,...o]}function Ux(e,t,r=[]){let n=el(e,t,r);return[{name:"",count:t.length},...YL(t,n)]}function JL(e,t){let r=e.map((o)=>({name:o,count:t[o]??0})),n=Object.keys(t).filter((o)=>!e.includes(o)).sort().map((o)=>({name:o,count:t[o]??0}));return[...r,...n]}function Wx(e,t,r=[]){let n=Object.keys(t.byService).map((i)=>({service:i})),o=el(e,n,r);return[{name:"",count:t.total},...JL(o,t.byService)]}function Gx(e,t){if(e<=0)return"none started";return`${e}/${t} running`}function el(e,t,r=[]){let n=[...e],o=(i)=>{if(i!==""&&!n.includes(i))n.push(i)};for(let i of r)o(i);for(let i of t)o(i.service);return n}function Ap(e,t,r){let n=["",...e],o=n.indexOf(t),s=((o<0?0:o)+r+n.length)%n.length;return n[s]??""}var Kc=8,qc=9,Vc=7,Rx=10,Ex=16,Tx=2,Es=1,Ar=200,FL=2,_x=1,NL="\u25B8",Ax=2,HL=0.4,jL,UL,zL,Fx;var gr=D(()=>{ma();Ot();At();jL=/("[^"]*"|'[^']*'|\b(?:ERROR|FATAL|WARN(?:ING)?|FAIL(?:ED)?)\b|\b\d{3,5}\b)/gi;UL=/\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-Z\\-_]/g;zL=new Set(["auth","devctl","proxy"]),Fx=["devctl","mcp","auth"]});function ai(e){return e.length+QL}function Kx(e,t){if(t<eD)return _e(e,3);if(t<ZL)return _e(e,4);return e}function qx(e,t,r){let n=e.length;if(n===0||r<=0)return{start:0,end:-1};let o=t>=0&&t<n?t:0,i=Lp(e,o,r);if(i.start===0&&i.end===n-1)return i;let s=o>0?tl:0,a=o<n-1?tl:0,c=s+a,l=Lp(e,o,Math.max(e[o]??0,r-c)),d=(s>0&&l.start===0?tl:0)+(a>0&&l.end===n-1?tl:0);if(d===0)return l;return Lp(e,o,Math.max(e[o]??0,r-c+d))}function Lp(e,t,r){let n=e.length,o=t,i=t,s=e[t]??0;if(s>=r)return{start:t,end:t};while(o>0||i<n-1){let a=o>0?e[o-1]??0:Number.POSITIVE_INFINITY,c=i<n-1?e[i+1]??0:Number.POSITIVE_INFINITY,l=o>0&&s+a<=r,d=i<n-1&&s+c<=r;if(!l&&!d)break;let u=t-(o-1),f=i+1-t;if(l&&(!d||u<=f))o-=1,s+=a;else if(d)i+=1,s+=c;else break}return{start:o,end:i}}function Vx(e){if(e==="detail")return bo.findIndex((t)=>t.id==="services");return bo.findIndex((t)=>t.id===e)}function zx(e,t){if(e==="doctor")return t.doctor;if(e==="settings")return t.settings;if(e==="profiles")return t.profiles;if(e==="dashboard"||e==="services")return t.services;if(e==="logs")return t.logs??0;if(e==="mcp")return t.mcp??0;if(e==="setup")return XL;if(e==="config")return t.config??0;return 0}function Yx(e){if(e.length===1&&e>="1"&&e<="9")return bo[Number(e)-1]?.id;return}var XL=9,QL=2,tl=2,ZL=80,eD=56,bo,Dn;var In=D(()=>{Ot();bo=[{id:"dashboard",label:"dashboard"},{id:"services",label:"services"},{id:"logs",label:"logs"},{id:"proxy",label:"proxy"}];Dn=bo.map((e)=>e.id)});function Qx(e,t=!0){let r=e<rl?Jx+1:Jx,n=t?sD:0;return r+n+oD+n+iD+n}function li(e){return Math.max(rD,e-nD*2)}function Zx(e){let t=e.width<tD,r=e.proxyOn?`\u25CF ${_e(e.proxyAddress,t?10:18)}`:"";return[{label:t?`${e.running}/${e.total}`:Gx(e.running,e.total),tone:e.running>0?"success":"idle"},{label:r,tone:"info",hide:!e.proxyOn},{label:"MCP",tone:"info",hide:!e.mcpOn},{label:e.adc?t?"ADC":"ADC ok":t?"!ADC":"ADC missing",tone:e.adc?"success":"error"},{label:t?"sec":"secrets shown",tone:"warning",hide:!e.reveal}]}function Xx(e){return e.key==="/"&&e.label==="command"}function aD(e){return e.key.length+e.label.length+3}function eb(e,t){let r=e.filter(Xx),n=e.filter((s)=>!Xx(s)),o=[],i=0;for(let s of[...r,...n]){let a=aD(s);if(i+a>t)break;o.push(s),i+=a}return o}function nl(e,t,r,n,o="center",i=!0){let s=Math.min(r,Math.max(1,e-4)),a=Math.min(Qx(e,i),Math.max(0,t-1)),c=Math.min(n,Math.max(1,t-a)),l=Math.max(0,Math.min(Math.floor((e-s)/2),Math.max(0,e-s))),d=o==="bottom"?t-2-c:Math.floor((t-c)/2),u=Math.max(0,Math.min(d,t-c));return{left:l,top:u,width:s,height:c}}function tb(e){if(e==="")return"idle";if(lD.test(e))return"error";if(e.includes("session only"))return"warning";if(cD.test(e))return"success";return"info"}function rb(e,t,r){if(e==="restart-cascade")return{title:"Restart dependents?",body:`${r?.services?.join(", ")||"selected services"} has dependents. Enter restarts only the named services. Press c to cascade (same as /restart --cascade).`};if(e==="quit")return{title:"Quit",body:"Stop managed services and leave the TUI? Press d to detach (daemon stays). Press k to stop the daemon and leave services running (devctl down --keep-services)."};if(e==="reload")return{title:"Reload applied",body:t===""?"Configuration changed. Restart marked services?":`Restart required: ${t}`};if(e==="reset-prefs")return{title:"Reset preferences",body:"Restore theme, display size, mouse, and leader timeout to defaults? This overwrites your saved tui.json values."};if(e==="free-port"){let n=r?.port??0,o=r?.process||"process",i=r?.pid??0;return{title:`Free port ${n}`,body:`Stop ${o} (pid ${i}) so port ${n} can be used? This sends SIGTERM, then SIGKILL if it stays up.`}}return{title:"Start profile",body:t===""?"Start the configured services?":`Start profile ${t}?`}}function nb(e){if(e==="quit")return[{key:"enter",label:"stop services"},{key:"d",label:"detach"},{key:"k",label:"down, keep services"},{key:"esc",label:"stay"}];if(e==="restart-cascade")return[{key:"enter",label:"named only"},{key:"c",label:"cascade"},{key:"esc",label:"stay"}];return[{key:"enter",label:"confirm"},{key:"esc",label:"stay"}]}function ob(e){let t=e.payload?.error;return typeof t==="string"&&t!==""?t:"configuration reload failed"}var ci=100,rl=90,tD=60,rD=8,nD=6,Jx=1,oD=1,iD=1,sD=1,XU,cD,lD;var vr=D(()=>{Ot();gr();In();XU=Qx(rl-1);cD=/^(started|stopped|restarted|refreshed|cleared|copied|exported|jumped|proxy started|proxy stopped|secrets|re-running|theme |mouse |leader |display |restored)/i,lD=/\b(fail|error|unknown|not found|no configuration|blocked|already in use|is using port)\b/i});function ol(){return[{name:"start",aliases:["up"],desc:"Start selected services or the current profile",leader:"n",group:"services",usage:"<service>"},{name:"stop",aliases:[],desc:"Stop selected services",leader:"x",group:"services",usage:"<service>"},{name:"restart",aliases:[],desc:"Restart selected services",leader:"R",group:"services",usage:"<service>",suggest:[{token:"--cascade",desc:"Restart selected services and their dependents"}]},{name:"run",aliases:["task"],desc:"Run a one-off task; empty /run opens a picker",leader:"",group:"services",usage:"<task>"},{name:"exec",aliases:[],desc:"Run a command in a service context; empty /exec opens a picker",leader:"",group:"services",usage:"<service>"},{name:"services",aliases:["s"],desc:"Open the services screen",leader:"s",group:"nav"},{name:"logs",aliases:["l"],desc:"Open the log viewer",leader:"l",group:"nav"},{name:"auth",aliases:["identity","a"],desc:"Open identity",leader:"a",group:"nav",suggest:[{token:"login",desc:"Run gcloud ADC login"},{token:"logout",desc:"Revoke application-default credentials"},{token:"refresh",desc:"Probe identities"}]},{name:"credentials",aliases:["creds"],desc:"Open credential store status",leader:"",group:"nav"},{name:"reload",aliases:[],desc:"Reload configuration",leader:"",group:"ui"},{name:"proxy",aliases:["p"],desc:"Open the proxy screen",leader:"p",group:"nav"},{name:"mcp",aliases:["agent"],desc:"Open the MCP server screen for coding agents",leader:"",group:"nav"},{name:"doctor",aliases:["d"],desc:"Run environment diagnostics",leader:"d",group:"nav"},{name:"stats",aliases:["metrics"],desc:"View system and service statistics",leader:"m",group:"nav"},{name:"config",aliases:["c"],desc:"View merged configuration",leader:"c",group:"nav"},{name:"import",aliases:[],desc:"Preview or write a Compose mapping",leader:"",group:"ui",suggest:[{token:"compose",desc:"Preview a Compose mapping; add --write to save"}]},{name:"diff",aliases:["provenance"],desc:"Show winning config sources and what they shadowed",leader:"",group:"ui"},{name:"daemon",aliases:["bootstrap"],desc:"Show supervisor bootstrap logs (same file as devctl daemon logs)",leader:"",group:"app"},{name:"update",aliases:[],desc:"Install a newer GitHub Release when the install method is known",leader:"",group:"app"},{name:"profiles",aliases:["o"],desc:"Select a development profile",leader:"o",group:"nav"},{name:"setup",aliases:["init"],desc:"Open setup guidance",leader:"i",group:"nav"},{name:"dashboard",aliases:["home"],desc:"Return to the dashboard",leader:"h",group:"nav"},{name:"themes",aliases:["theme"],desc:"List available themes",leader:"t",group:"ui"},{name:"settings",aliases:["prefs","preferences"],desc:"Open TUI settings (theme, mouse, MCP page)",leader:"",group:"ui"},{name:"help",aliases:["?"],desc:"Show the help dialog",leader:"",group:"ui"},{name:"refresh",aliases:[],desc:"Refresh status and logs",leader:"r",group:"ui"},{name:"regex",aliases:[],desc:"Toggle regex log search",leader:"",group:"logs"},{name:"since",aliases:[],desc:"Filter logs after an ISO timestamp",leader:"",group:"logs",usage:"<timestamp>"},{name:"until",aliases:[],desc:"Filter logs before an ISO timestamp",leader:"",group:"logs",usage:"<timestamp>"},{name:"history",aliases:[],desc:"Load a persisted log session",leader:"",group:"logs",usage:"<session>"},{name:"edit",aliases:[],desc:"Open configuration in $EDITOR",leader:"",group:"ui"},{name:"buffer",aliases:[],desc:"Edit configuration in a validate/save buffer",leader:"",group:"ui"},{name:"pause",aliases:[],desc:"Pause or resume live logs",leader:"",group:"logs"},{name:"fullscreen",aliases:["zen","expand"],desc:"Expand logs to fill the terminal",leader:"z",group:"logs"},{name:"split",aliases:[],desc:"Split the logs screen into two service panes",leader:"",group:"logs"},{name:"trace",aliases:[],desc:"Search logs for a request or trace id",leader:"",group:"logs",usage:"<id>"},{name:"filter",aliases:[],desc:"Toggle ERROR+ log filter",leader:"",group:"logs"},{name:"system",aliases:["internal"],desc:"Show or hide internal auth/mcp/devctl/proxy logs",leader:"",group:"logs"},{name:"reveal",aliases:[],desc:"Reveal or hide secret environment values",leader:"",group:"ui"},{name:"wrap",aliases:[],desc:"Cycle log wrap: selected, all lines, or clip",leader:"",group:"logs"},{name:"copy",aliases:[],desc:"Copy the highlighted selection to the clipboard",leader:"",group:"ui"},{name:"export",aliases:[],desc:"Write filtered logs to ~/.devctl/exports",leader:"e",group:"logs"},{name:"exports",aliases:["open-exports"],desc:"Open the log export folder",leader:"",group:"logs"},{name:"clear",aliases:["new"],desc:"Clear the on-screen log buffer",leader:"",group:"logs"},{name:"version",aliases:["v"],desc:"Show the current devctl version",leader:"",group:"app"},{name:"down",aliases:[],desc:"Stop the supervisor",leader:"",group:"app",suggest:[{token:"--keep-services",desc:"Stop the supervisor and leave processes running"}]},{name:"exit",aliases:["quit","q"],desc:"Exit (detach or stop services)",leader:"q",group:"app"}]}function wo(e){let t=e.trim().toLowerCase().replace(/^\//,""),r=t.indexOf(" ");return r===-1?t:t.slice(0,r)}function lb(e){let t=wo(e);if(t==="")return ol();return ol().map((r)=>({command:r,score:CD(r,t)})).filter((r)=>r.score>0).sort((r,n)=>n.score-r.score||r.command.name.localeCompare(n.command.name)).map((r)=>r.command)}function CD(e,t){if(t==="")return 1;let r=e.name.toLowerCase(),n=e.aliases.map((o)=>o.toLowerCase());return RD(r,n,t)||_D(r,n,t)||ED(r,n,t)||TD(r,n,t)||PD(e.suggest,t)||AD(e.desc.toLowerCase(),t)}function PD(e,t){if(!e||e.length===0)return 0;let r=e.map((n)=>n.token.toLowerCase());if(r.some((n)=>n===t||n.replace(/^-+/,"")===t))return sb;if(t.length<cb)return 0;return r.some((n)=>n.includes(t)||n.replace(/^-+/,"").startsWith(t))?ab:0}function RD(e,t,r){if(e===r)return dD;return t.includes(r)?uD:0}function _D(e,t,r){if(e.startsWith(r))return pD;if(t.some((i)=>i.startsWith(r)))return fD;let n=e.split("-");return n.length>1&&n.some((i)=>i.startsWith(r))?mD:0}function ED(e,t,r){if(r.length<cb)return 0;if(e.includes(r))return sb;return t.some((n)=>n.includes(r))?ab:0}function TD(e,t,r){if(r.length<xD)return 0;let n=ib(e,r);if(n>0)return hD+Math.min(n,kD);return t.some((o)=>ib(o,r)>0)?gD:0}function ib(e,t){let r=0,n=0,o=0;for(let i=0;i<t.length;i+=1){let s=t[i];if(s===void 0)return 0;let a=e.indexOf(s,r);if(a<0)return 0;if(r>0&&a===r)n+=1,o+=2+n;else n=0,o+=a===0?4:1;r=a+1}return o}function AD(e,t){if(t.length<bD)return 0;if(e.split(/[^a-z0-9+]+/).filter((n)=>n.length>=wD).some((n)=>n.startsWith(t)))return vD;return t.length>=SD&&e.includes(t)?yD:0}function Wr(e){let t=e.trim().toLowerCase().replace(/^\//,"").split(" ")[0]??"";return ol().find((r)=>r.name===t||r.aliases.includes(t))}function Dp(e){return e.trim().replace(/^\//,"").split(/\s+/).slice(1).filter((r)=>r!=="")}function db(e){return ol().find((r)=>r.leader===e)?.name??""}function ub(e){let t=e.includes("--cascade")||e.includes("-c");return{services:e.filter((n)=>n!=="--cascade"&&n!=="-c"),cascade:t}}function pb(e){let t=e.includes("--print-env"),r=e.includes("--reveal"),n=e.filter((o)=>o!=="--print-env"&&o!=="--reveal"&&o!=="--");return{service:n[0]??"",printEnv:t,reveal:r,command:n.slice(1)}}var dD=100,uD=90,pD=80,fD=70,mD=60,sb=50,ab=45,hD=30,gD=28,vD=20,yD=10,cb=2,xD=3,bD=4,wD=4,SD=4,kD=9;function gb(e){return e==="detail"?"services":e}function vb(e){let t=Dn.indexOf(gb(e));if(t<0)return"dashboard";return Dn[(t+1)%Dn.length]??"dashboard"}function yb(e){let t=Dn.indexOf(gb(e));if(t<0)return"dashboard";return Dn[(t-1+Dn.length)%Dn.length]??"dashboard"}function Mp(e){return["services","nav","logs","ui","app","tasks"].flatMap((r)=>{let n=e.filter((o)=>o.group===r);if(n.length===0)return[];return[{group:r,items:n}]})}function il(e){return e.hint?`${e.name} ${e.hint}`:e.name}function Op(e){return e.name}function xb(e){if(e.hint)return`${e.hint} \xB7 ${e.desc}`;let t=[e.usage,...(e.suggest??[]).map((r)=>r.token)].filter((r)=>Boolean(r));if(t.length===0)return e.desc;return`${e.desc} \xB7 ${t.join(" \xB7 ")}`}function bb(e){let t=e.reduce((n,o)=>Math.max(n,Op(o).length),0),r=Math.min(Ip,Math.max(t,1));return LD+r+DD}function wb(e){return e.hint?`${e.name} ${e.hint} `:`${e.name} `}function Sb(e,t){if(e.hint)return e.hint.split(/\s+/);return Wr(t)?.name===e.name?Dp(t):[]}function fb(e,t){if(t==="")return!0;let r=t.toLowerCase(),n=e.toLowerCase();return n.startsWith(r)||n.includes(r)}function ID(e,t){if(e.name==="start"||e.name==="stop"||e.name==="restart")return(t.services??[]).map((r)=>({token:r,desc:`${e.name} ${r}`}));if(e.name==="run")return(t.tasks??[]).map((r)=>({token:r,desc:`run ${r}`}));if(e.name==="exec")return(t.services??[]).map((r)=>({token:r,desc:`exec in ${r}`}));return[]}function mb(e,t){return{...e,hint:t.token,desc:t.desc,usage:void 0,suggest:void 0}}function MD(e,t,r={}){let n=Dp(t).join(" "),o=t.trim().includes(" ")||t.endsWith(" "),i=wo(t),s=[];for(let a of e){if(s.push({...a,hint:void 0}),i==="")continue;let c=a.name===i||a.aliases.includes(i),l=c?n:i;for(let d of a.suggest??[])if(fb(d.token,l))s.push(mb(a,d));if(!c||!o)continue;for(let d of ID(a,r))if(fb(d.token,n))s.push(mb(a,d))}return s}function kb(e,t={}){let r=lb(e),n=wo(e)===""?Mp(r).flatMap((o)=>o.items):r;return MD(n,e,t)}function $p(e,t,r,n){let o=wo(t),i=[...e].sort().map((s)=>({name:s,aliases:[],desc:n,leader:"",group:r}));if(o==="")return i;return i.filter((s)=>s.name.toLowerCase().includes(o))}function Cb(e,t,r=Zr()){if(t==="slash")return[{key:"\u2191\u2193",label:"suggest"},{key:"tab",label:"complete"},{key:"enter",label:"run"},{key:"esc",label:"cancel"}];if(t==="themes")return[Gr,{key:"\u2191\u2193",label:"move"},{key:"enter",label:"save"},{key:"esc",label:"revert"}];if(t==="help")return[Gr,{key:"j/k",label:"scroll"},{key:"esc",label:"close"}];if(t==="log-details"||t==="scroll-text")return[Gr,{key:"j/k",label:"scroll"},{key:Ri(r),label:"copy"},{key:"esc",label:"close"}];if(t==="confirm")return[{key:"enter",label:"confirm"},{key:"esc",label:"stay"}];if(t==="plan")return[Gr,{key:"esc",label:"back to dashboard"},{key:"enter",label:"done"}];if(t==="leader")return[Gr,...Fp()];if(t==="config-edit")return[{key:It("s"),label:"save"},{key:"esc",label:"discard"}];return OD(e,r)}function Fp(){return[{key:"n",label:"start"},{key:"x",label:"stop"},{key:"R",label:"restart"},{key:"s",label:"services"},{key:"l",label:"logs"},{key:"t",label:"themes"},{key:"q",label:"quit"}]}function OD(e,t){let r=[{key:Ri(t),label:"copy"},Gr,{key:"?",label:"help"}];switch(e){case"dashboard":return[{key:"space",label:"select"},{key:"*",label:"all"},{key:"-",label:"none"},{key:"enter",label:"start or open"},{key:"n",label:"start"},{key:"x",label:"stop"},{key:"r",label:"refresh"},{key:"R",label:"restart"},{key:"\u2190\u2192",label:"log filter"},{key:"g",label:"latest"},{key:"z",label:"full logs"},{key:"i",label:"internal logs"},{key:It("l"),label:"clear logs"},{key:"j/k",label:"move"},...r];case"services":return[{key:"enter",label:"detail"},{key:"space",label:"select"},{key:"*",label:"all"},{key:"-",label:"none"},{key:"n",label:"start"},{key:"x",label:"stop"},{key:"r",label:"refresh"},{key:"R",label:"restart"},...r];case"detail":return[{key:"j/k",label:"scroll env"},{key:"n",label:"start"},{key:"x",label:"stop"},{key:"o",label:"config"},{key:"l",label:"logs"},{key:"esc",label:"back"},...r];case"logs":return[{key:"\u2190\u2192",label:"filter"},{key:"e",label:"errors"},{key:"i",label:"internal logs"},{key:It("l"),label:"clear logs"},{key:"g",label:"latest"},{key:"f",label:"search"},{key:"t",label:"time"},{key:"m",label:"meta"},{key:"w",label:"wrap"},{key:"j/k",label:"move"},{key:"p",label:"pause"},{key:"z",label:"full screen"},{key:"\\",label:"split"},{key:"/exports",label:"open folder"},...r];case"profiles":return[{key:"space",label:"set current"},{key:"enter",label:"set and start"},{key:"j/k",label:"move"},...r];case"proxy":return[{key:"n",label:"start proxy"},{key:"x",label:"stop proxy"},...r];case"mcp":return[{key:"j/k",label:"move"},{key:"space",label:"start or copy"},{key:"\u2190\u2192",label:"change port"},{key:"enter",label:"start or copy"},...r];case"config":return[{key:"enter",label:"run task"},{key:"v",label:"buffer"},{key:"e",label:"editor"},{key:"/diff",label:"sources"},{key:"/reload",label:"reload"},{key:"j/k",label:"scroll or select task"},...r];case"setup":return[{key:"j/k",label:"steps"},{key:"enter",label:"continue"},{key:"esc",label:"back"},{key:"esc\xD72",label:"quit"},...r];case"doctor":return[{key:"r",label:"run doctor again"},{key:"j/k",label:"move"},{key:"enter",label:"fix port"},...r];case"auth":return[{key:"r",label:"probe identities"},{key:"/auth login",label:"ADC login"},{key:"/auth logout",label:"revoke ADC"},{key:"/auth refresh",label:"probe"},...r];case"settings":return[{key:"j/k",label:"move"},{key:"\u2190\u2192",label:"save"},{key:"enter",label:"apply or open page"},{key:"space",label:"toggle"},...r];default:return[{key:"tab",label:"screens"},...r]}}function Pb(e,t,r){if(r<=t||e<t)return 0;return Math.min(e-t+1,r-t)}function hb(e,t,r){let n=e.slice(t,r+1),o=new Set(n.map((i)=>i.group));return n.length+o.size}function Rb(e,t,r){if(e.length===0)return[];let n=Math.min(Math.max(t,0),e.length-1),o=n,i=n;for(;;){let s=i+1<e.length&&hb(e,o,i+1)<=r,a=o>0&&hb(e,o-1,i)<=r;if(s)i+=1;else if(a)o-=1;else break}return e.slice(o,i+1)}function di(e,t){if(e.length===0)return;let r=Math.min(Math.max(t,0),e.length-1);return e[r]}var Gr,LD=3,DD=2,Ip=22;var So=D(()=>{_r();In();Gr={key:"/",label:"command"}});function al(e,t){let r=t??Object.keys(e?.services??{});return{running:r.filter((o)=>yo(e?.services[o])).length,total:r.length}}function _b(e,t){let r=0,n=0,o=0,i=0,s=0,a=0;for(let c of e){let l=t?.services[c];if(l?.health==="HEALTHY")o+=1;let d=l?.state??"STOPPED";if(d==="FAILED")i+=1;else if(d==="STOPPING")s+=1;else if(d==="STARTING"||d==="RESTARTING")n+=1;else if(_s(d))r+=1;else a+=1}return{total:e.length,live:r+n,running:r,starting:n,healthy:o,failed:i,stopping:s,stopped:a}}function Eb(e,t){if(!Number.isFinite(e)||!Number.isFinite(t)||t<=0)return 0;return e/t}function BD(e,t,r){if(!Number.isFinite(e))return"success";if(e>r)return"error";if(e>t)return"warning";return"success"}function UD(e,t){return BD(Eb(e,t),$D,FD)}function Bp(e,t=24){if(e.length===0||t<=0)return"";let r=e.length>t?e.slice(e.length-t):e,n=Math.max(...r,0);if(n<=0)return Ts[0]?.repeat(r.length)??"";return r.map((o)=>{let i=Number.isFinite(o)?Math.min(1,Math.max(0,o/n)):0,s=Math.min(Ts.length-1,Math.round(i*(Ts.length-1)));return Ts[s]??Ts[0]}).join("")}function WD(e,t){let r=t>0?e/t:1;if(r<HD)return"error";if(r<ND)return"warning";return"success"}function Np(e){if(!Number.isFinite(e)||e<0)return"\u2014";return e.toFixed(2)}function GD(e){if(!Number.isFinite(e)||e<0)return"\u2014";return`${Math.round(e*100)}%`}function Tb(e,t=Date.now()){if(!e?.startTime)return"\u2014";let r=new Date(e.startTime).getTime();if(!Number.isFinite(r))return"\u2014";return ws(t-r)}function Ab(e,t=jD){return Object.entries(e).sort((r,n)=>n[1]-r[1]||r[0].localeCompare(n[0])).slice(0,t)}function Lb(e,t=1){return Math.max(24,e-XD-t*2-QD-ZD-eI)}function Db(e){let t=e>=KD,r=e>=qD,n=e>=VD,o=e>=zD,i=e>=YD,s=e>=JD,a=Tr+$t;if(t)a+=Ur+$t;if(r)a+=Cs+$t;if(n)a+=Ps+$t;if(o)a+=ks+$t;if(i)a+=cl+$t;if(s)a+=sn;return{name:Math.max($c,e-a),health:t,cpu:r,mem:n,up:o,rst:i,pid:s}}function Ib(e){let t=Number.isFinite(e)&&e>0?e:0;return{ratio:Math.min(1,t),label:GD(t)}}function Mb(e,t=nI){return`[${ii(e.ratio,t)}] ${e.label.padStart(4)}`}function Up(e,t){let r=e.reduce((l,d)=>Math.max(l,d.what.length),4),n=e.reduce((l,d)=>Math.max(l,d.reading.length),7),o=e.some((l)=>l.meter)?oI:0,i=ui*(o>0?3:2),s=Math.min(tI,Math.max(Hp,r)),a=Math.min(rI,Math.max(jp,n)),c=t-s-a-o-i;if(c<sl&&a>jp)a=Math.max(jp,a-(sl-c)),c=t-s-a-o-i;if(c<sl&&s>Hp)s=Math.max(Hp,s-(sl-c)),c=t-s-a-o-i;return{what:s,reading:a,meaning:Math.max(0,c),meter:o}}function Ob(e){return iI.has((e?.health.type??"").toLowerCase())}function $b(e){if(e==="darwin")return"macOS";if(e==="win32")return"Windows";if(e==="linux")return"Linux";return e||"this computer"}function Fb(e,t){let r=Math.max(1,t),n=UD(e,r),o=n==="error"?"overloaded":n==="warning"?"busy":"not busy",i=n==="error"?`${r} cores are queued up \u2014 last minute ${Np(e)}`:n==="warning"?`${r} cores nearly full \u2014 last minute ${Np(e)}`:`${r} cores. Last minute ${Np(e)} (under ${r} is fine)`;return{what:"CPU work",reading:o,meaning:i,tone:n,meter:Ib(Eb(e,r))}}function Nb(e,t){let r=WD(e,t),n=`${Ss(e)} of ${Ss(t)}`,o=r==="error"?"Almost no RAM left for new work":r==="warning"?"RAM is getting tight":"RAM the computer can still give out",i=t>0?Math.max(0,(t-e)/t):0;return{what:"RAM leftover",reading:n,meaning:o,tone:r,meter:Ib(i)}}function Hb(e){let t=e?.state??"STOPPED";if(t==="FAILED")return"crashed";if(t==="STOPPING")return"stopping";if(t==="STARTING"||t==="RESTARTING")return"starting";if(_s(t))return"up";return"off"}function jb(e){if(!e||!_s(e.state))return"\u2014";if(e.health==="HEALTHY")return"ready";if(e.health==="UNHEALTHY")return"failing";if(e.state==="STARTING"||e.state==="RESTARTING")return"waiting";return"checking"}function Bb(e){if(e==="keychain")return"password store";if(e==="file")return"local file";return e||"unknown store"}function Ub(e,t){let r=[{what:"Started",reading:String(e.live),meaning:e.live===0?"nothing is running yet":"process is up",tone:e.live>0?"success":"muted"}];if(t)r.push({what:"Ready",reading:String(e.healthy),meaning:e.live===0?"start services first":"passed their health check",tone:e.live>0&&e.healthy<e.live?"warning":e.healthy>0?"success":"muted"});if(e.starting>0)r.push({what:"Still starting",reading:String(e.starting),meaning:"not ready for traffic yet",tone:"warning"});if(e.stopping>0)r.push({what:"Stopping",reading:String(e.stopping),meaning:"shutting down",tone:"warning"});return r.push({what:"Crashed",reading:String(e.failed),meaning:e.failed>0?"open the service for the error":"none",tone:e.failed>0?"error":"muted"}),r.push({what:"Not started",reading:String(e.stopped),meaning:e.stopped>0?"off until you start them":"all are up",tone:"muted"}),r}var $D=0.85,FD=1.5,ND=0.2,HD=0.1,jD=5,Ts="\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588",KD=52,qD=64,VD=72,zD=82,YD=90,JD=100,XD=2,QD=2,ZD=2,eI=1,cl=8,Hp=8,tI=22,jp=6,rI=28,sl=12,ui=2,nI=10,oI=18,iI;var ll=D(()=>{Ot();At();iI=new Set(["http","tcp","command"])});import{useEffect as sI,useRef as aI}from"react";import{jsxDEV as me,Fragment as lI}from"@opentui/react/jsx-dev-runtime";function cI(e,t){switch(t){case"primary":return{bg:e.primary,fg:ho(e.primary,e.inverse)};case"accent":return{bg:e.accent,fg:Ac(e)?Tc:ho(e.accent,e.inverse)};case"success":return{bg:e.success,fg:ho(e.success,e.inverse)};case"warning":return{bg:e.warning,fg:Ac(e)?Tc:ho(e.warning,e.inverse)};case"error":return{bg:e.error,fg:ho(e.error,e.inverse)};case"info":return{bg:e.info,fg:ho(e.info,e.inverse)};case"muted":return{bg:e.element,fg:e.text};case"ghost":return{bg:e.element,fg:e.muted};default:return{bg:e.element,fg:e.text}}}function Wb(e){let{palette:t,dir:r}=e;return me("box",{width:2,height:1,flexShrink:0,overflow:"hidden",children:me("text",{fg:t.muted,children:r==="left"?"\u2039":"\u203A"},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function Ut(e){let{palette:t,children:r,backgroundColor:n,edge:o="bottom"}=e,i=ye(),s=e.ruled??!go(i),a=n??t.element,c=me("box",{flexShrink:0,flexDirection:"column",backgroundColor:a,overflow:"hidden",children:r},void 0,!1,void 0,this);if(!s)return c;return me("box",{flexShrink:0,flexDirection:"column",overflow:"hidden",border:[...o==="both"?["top","bottom"]:[o]],borderStyle:"single",borderColor:t.border,children:c},void 0,!1,void 0,this)}function dl(e){let{palette:t,items:r,active:n,width:o,onPick:i,emphasis:s="ghost"}=e,a=r.map((u)=>ai(u.label)),c=qx(a,n,Math.max(1,o)),l=c.start>0,d=c.end>=0&&c.end<r.length-1;return me("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:t.element,children:[l?me(Wb,{palette:t,dir:"left"},void 0,!1,void 0,this):null,r.map((u,f)=>{if(f<c.start||f>c.end)return null;let p=f===n,m=s==="fill"&&p;return me(Be,{palette:t,label:u.label,tone:m?"primary":"ghost",bg:m?void 0:p?t.highlight:t.element,fg:m?void 0:p?t.primary:u.color??t.muted,onMouseDown:()=>i(f)},u.id,!1,void 0,this)}),d?me(Wb,{palette:t,dir:"right"},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}function Be(e){let{palette:t,label:r,tone:n="idle",fg:o,bg:i,onMouseDown:s}=e,a=cI(t,n);return me("box",{height:1,paddingLeft:1,paddingRight:1,backgroundColor:i??a.bg,overflow:"hidden",flexShrink:0,onMouseDown:s,children:me("text",{fg:o??a.fg,children:r},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function it(e){let{palette:t,hints:r}=e;return me("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:r.map((n)=>me("box",{height:1,paddingLeft:1,paddingRight:1,flexShrink:0,overflow:"hidden",children:me("text",{wrapMode:"none",children:[me("span",{fg:t.primary,children:n.key},void 0,!1,void 0,this),me("span",{fg:t.muted,children:` ${n.label}`},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},`${n.key}-${n.label}`,!1,void 0,this))},void 0,!1,void 0,this)}function ut(e){let{palette:t,items:r,hints:n,ruled:o=!0}=e,i=me("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:t.element,children:[r.map((s,a)=>me(Be,{palette:t,label:s.text,tone:s.tone,onMouseDown:s.onMouseDown},`${s.text}-${a}`,!1,void 0,this)),n&&n.length>0?me(lI,{children:[me("box",{flexGrow:1,backgroundColor:t.element},void 0,!1,void 0,this),me(it,{palette:t,hints:n},void 0,!1,void 0,this)]},void 0,!0,void 0,this):null]},void 0,!0,void 0,this);if(!o)return i;return me(Ut,{palette:t,backgroundColor:t.element,children:i},void 0,!1,void 0,this)}function pt(e){let{palette:t,title:r,termW:n,termH:o,preferW:i,preferH:s,anchor:a="center",borderColor:c,bottomTitle:l,gap:d,children:u}=e,f=go(ye()),p=nl(n,o,i,s,a,!f);return me("box",{position:"absolute",left:p.left,top:p.top,width:p.width,height:p.height,border:!0,borderStyle:Wp,borderColor:c??t.borderActive,backgroundColor:t.panel,padding:f?0:1,title:r,titleColor:t.primary,bottomTitle:l,gap:f?0:d,flexDirection:"column",overflow:"hidden",children:u},void 0,!1,void 0,this)}function Ue(e){return{rootOptions:{flexGrow:1,height:"100%",overflow:"hidden",backgroundColor:e.panel},viewportOptions:{backgroundColor:e.panel},contentOptions:{backgroundColor:e.panel},scrollbarOptions:{trackOptions:{foregroundColor:e.primary,backgroundColor:e.element}}}}function rt(e,t){if(!e)return;e.scrollBy({x:0,y:t})}function pi(e,t){let r=aI(null);return sI(()=>{let n=r.current;if(!n||e<0)return;let o=requestAnimationFrame(()=>{n.scrollChildIntoView(`${t}-${e}`)});return()=>cancelAnimationFrame(o)},[t,e]),r}function kt(e){let t=ye(),r=bs(t),n=e.scroll?me("scrollbox",{focused:!1,stickyScroll:!1,scrollX:!1,style:Ue(e.palette),children:me("box",{flexDirection:"column",overflow:"hidden",children:e.children},void 0,!1,void 0,this)},void 0,!1,void 0,this):e.children;return me("box",{flexGrow:1,border:!0,borderStyle:Wp,borderColor:e.borderColor??e.palette.border,title:e.title,titleColor:e.palette.primary,paddingLeft:t.pad,paddingRight:t.pad,paddingTop:r?0:t.pad,paddingBottom:r?0:t.pad,backgroundColor:e.palette.panel,flexDirection:"column",overflow:"hidden",children:n},void 0,!1,void 0,this)}function ko(e){let{palette:t,label:r,value:n,tone:o="text"}=e,i=ye(),s=o==="success"?t.success:o==="warning"?t.warning:o==="error"?t.error:o==="muted"?t.muted:t.text;return me("box",{height:i.rowH,flexDirection:"row",overflow:"hidden",children:[me("box",{width:14,flexShrink:0,overflow:"hidden",children:me("text",{fg:t.muted,children:r},void 0,!1,void 0,this)},void 0,!1,void 0,this),me("box",{flexGrow:1,overflow:"hidden",children:me("text",{fg:s,wrapMode:"none",children:n},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function ul(e){let{palette:t,title:r,body:n,hint:o}=e;return me("box",{paddingLeft:1,paddingRight:1,flexDirection:"column",overflow:"hidden",border:!0,borderStyle:Wp,borderColor:t.borderActive,titleColor:t.primary,children:[me("text",{fg:t.primary,children:r},void 0,!1,void 0,this),me("text",{fg:t.text,wrapMode:"word",children:n},void 0,!1,void 0,this),o?me("text",{fg:t.muted,wrapMode:"word",children:o},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}var Wp="rounded";var ke=D(()=>{Mt();vr();In();Er();tt()});import{jsxDEV as $e,Fragment as uI}from"@opentui/react/jsx-dev-runtime";function Kb(e){let{palette:t,cfg:r,snap:n,google:o,profile:i,reveal:s,width:a}=e,c=al(n,r?Object.keys(r.services):void 0),l=n?.proxy.running===!0,d=o?.adcAvailable===!0,u=a<rl,f=_e(r?.project.name||"local",u?18:22),p=_e(i||n?.profile||"no profile",16),m=Zx({width:a,running:c.running,total:c.total,proxyOn:l,proxyAddress:n?.proxy.address??"",mcpOn:n?.mcp?.running===!0,adc:d,reveal:s}).filter((x)=>!x.hide&&x.label!=="").map((x)=>$e(Be,{palette:t,label:x.label,tone:x.tone},x.label,!1,void 0,this)),h=bs(ye()),v=$e("box",{height:1,flexGrow:1,overflow:"hidden",backgroundColor:t.panel,paddingLeft:1,children:$e("text",{wrapMode:"none",children:[$e("span",{fg:t.primary,children:er()},void 0,!1,void 0,this),$e("span",{fg:t.muted,children:` ${f}`},void 0,!1,void 0,this),$e("span",{fg:t.accent,children:` ${p}`},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this);return $e(Ut,{palette:t,backgroundColor:t.panel,ruled:!h,children:$e("box",{height:u?2:1,flexDirection:"column",backgroundColor:t.panel,overflow:"hidden",children:[$e("box",{height:1,flexDirection:"row",overflow:"hidden",children:[v,u?null:m]},void 0,!0,void 0,this),u?$e("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:t.panel,children:m},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}function qb(e){let{palette:t,screen:r,width:n,onSelect:o}=e,i=n>=Gb+dI,s=i?Math.max(1,n-Gb):n,a=bo.map((l)=>({id:l.id,label:Kx(l.label,s)})),c=Vx(r);return $e(Ut,{palette:t,backgroundColor:t.element,children:$e("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:t.element,children:[$e(dl,{palette:t,items:a,active:c,width:s,onPick:(l)=>{let d=bo[l];if(d)o(d.id)}},void 0,!1,void 0,this),i?$e(uI,{children:[$e("box",{flexGrow:1,backgroundColor:t.element},void 0,!1,void 0,this),$e(it,{palette:t,hints:[Gr]},void 0,!1,void 0,this)]},void 0,!0,void 0,this):null]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}function Vb(e){let{palette:t,screen:r,overlay:n,status:o,paused:i,errorOnly:s,width:a,copyKey:c}=e,l=bs(ye()),d=Math.max(18,Math.floor(a*0.42)),u=eb(Cb(r,n,c),d),f=tb(o);return $e(Ut,{palette:t,backgroundColor:t.panel,edge:"top",ruled:!l,children:$e("box",{height:1,backgroundColor:t.panel,overflow:"hidden",children:$e(ut,{ruled:!1,palette:t,items:[{text:r,tone:"ghost"},{text:i?"PAUSED":"LIVE",tone:i?"warning":"success"},...s?[{text:"ERROR+",tone:"error"}]:[],...o===""?[]:[{text:_e(o,Math.max(16,a-d-28)),tone:f}]],hints:u},void 0,!1,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function ze(e){return $e("box",{flexGrow:1,padding:1,overflow:"hidden",children:$e(ul,{palette:e.palette,title:e.title,body:e.body,hint:e.hint},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function pl(e){return $e("box",{flexGrow:1,padding:1,overflow:"hidden",children:$e("text",{fg:e.palette.text,children:e.label},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function zb(e){return $e("box",{flexGrow:1,padding:1,flexDirection:"column",overflow:"hidden",children:[$e("text",{fg:e.palette.error,children:e.title},void 0,!1,void 0,this),$e("text",{fg:e.palette.text,wrapMode:"word",children:e.body},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var Gb,dI=8;var Yt=D(()=>{Mt();vr();So();Ot();In();ll();ke();Er();Gb=Gr.key.length+Gr.label.length+3});function pI(){if(process.platform==="darwin")return[["pbcopy"]];if(process.platform==="win32")return[["clip"]];return[["wl-copy"],["xclip","-selection","clipboard"],["xsel","--clipboard","--input"]]}function fI(){return pI().filter((e)=>{let t=e[0];return typeof t==="string"&&Bun.which(t)!==null})}function mI(e){return`\x1B]52;c;${Buffer.from(e,"utf8").toString("base64")}\x07`}function Yb(){if(process.platform==="linux")return"install wl-clipboard (Wayland) or xclip (X11)";return"no clipboard helper found"}async function fl(e){let t=fI(),r;for(let n of t)try{await gI(n,e);return}catch(o){r=o instanceof Error?o:Error(String(o))}if(process.platform!=="win32")try{await hI(e);return}catch(n){r=n instanceof Error?n:Error(String(n))}throw Te(lt,"clipboard unavailable",r?`${Yb()} (${r.message})`:Yb())}async function hI(e){let t=mI(e);try{await Bun.write("/dev/tty",t)}catch{if(!process.stderr.write(t))throw Error("terminal clipboard write failed")}}async function gI(e,t){let r=Bun.spawn(e,{stdin:"pipe",stdout:"ignore",stderr:"ignore"});r.stdin.write(t),await r.stdin.end();let n=await r.exited;if(n!==0)throw Error(`${e[0]} exited ${n}`)}var Gp=D(()=>{te()});function Kp(e){return e==="slash"||e==="config-edit"||e==="setup-wizard"}function Jb(e,t){return mn(e,t.keybinds.leader??Lo("x"))}function Xb(e,t){return mn(e,t.keybinds.command_list??Lo("p"))}function an(e,t){return mn(e,t.keybinds.command??"/")||e.name==="/"}function Qb(e,t){return mn(e,t.keybinds.help??"?")||e.sequence==="?"}function Zb(e,t){return mn(e,t.keybinds.search??"f")}function ew(e){return(e.name??"").toLowerCase()==="q"&&!e.ctrl&&!e.meta&&!e.super&&!e.alt&&!e.option}function tw(e){let t=e.name??"";return t==="R"||e.shift===!0&&t.toLowerCase()==="r"}function rw(e){return Or(e)&&(e.name??"").toLowerCase()==="l"}function nw(e,t){return mn(e,t.keybinds.copy??Zr())}function ow(e,t,r=vI){return t>0&&e-t<=r}function Mn(e,t,r,n){return mn(e,t.keybinds[r]??n)}function As(e){let t=(e.name??"").toLowerCase();return t==="pagedown"||Or(e)&&t==="d"}function Ls(e){let t=(e.name??"").toLowerCase();return t==="pageup"||Or(e)&&t==="u"}var vI=2000;var ml=D(()=>{_r()});function fi(e){return`http://127.0.0.1:${e}/mcp`}function qp(e){if(e==="")return{};return{Authorization:`Bearer ${e}`}}function Vp(e,t){return JSON.stringify({mcpServers:{devctl:{type:"http",url:e,headers:qp(t)}}},null,2)}function zp(e,t){return JSON.stringify({mcpServers:{devctl:{url:e,headers:qp(t)}}},null,2)}function Yp(e,t){return JSON.stringify({mcp:{devctl:{type:"remote",url:e,headers:qp(t),enabled:!0}}},null,2)}function Jp(e,t){let r=t===""?"":`
599
2142
  http_headers = { Authorization = "Bearer ${t}" }`;return`[mcp_servers.devctl]
600
2143
  url = "${e}"${r}
601
- `}function MD(e){return`codex mcp add --url ${e}`}function _s(e,t){return[{kind:"claude",title:"Claude",path:".mcp.json",language:"json",text:Hp(e,t),hint:"claude mcp add-json"},{kind:"cursor",title:"Cursor",path:".cursor/mcp.json",language:"json",text:Bp(e,t)},{kind:"kilo",title:"Kilo Code",path:"kilo.jsonc",language:"json",text:Wp(e,t)},{kind:"codex",title:"Codex",path:"~/.codex/config.toml",language:"toml",text:jp(e,t),hint:MD(e)}]}function Bb(e,t){let r=[`URL ${e}`,""];for(let n of _s(e,t))r.push(`# ${n.title} ${n.path}${n.hint?` (${n.hint})`:""}`),r.push(n.text.trimEnd()),r.push("");return r.join(`
602
- `)}import{jsxDEV as K}from"@opentui/react/jsx-dev-runtime";function qp(){return Qu.flatMap((e)=>xc.filter((t)=>t.category===e))}function dl(){return ll+qp().length}function Gb(){return dl()+jb}function Es(e){if(e<ll||e>=dl())return;return qp()[e-ll]}function Vp(e){let t=e-dl();return t>=0&&t<jb?t:void 0}function Ub(e){let{palette:t,snap:r,port:n,portDraft:o="",selected:i,onPick:s,onToggle:a,onCopy:c,onToggleTool:l}=e,d=ve(),u=r?.mcp?.running===!0,f=r?.mcp?.port??n,p=r?.mcp?.address??ui(f),m=r?.mcp?.token??"",g=_s(p,m),y=r?.mcp?.disabled_tools??[],x=qp(),h=x.filter((w)=>!En(w.name,y)).length,P=g[Vp(i)??-1],_=P??g[0];return K(kt,{palette:t,title:"mcp settings",children:[K(dt,{palette:t,items:[{text:u?"RUNNING":"STOPPED",tone:u?"success":"idle"},{text:`127.0.0.1:${f}`,tone:"info"}]},void 0,!1,void 0,this),K("box",{flexGrow:1,overflow:"hidden",children:K("scrollbox",{focused:!1,stickyScroll:!1,scrollX:!1,style:je(t),children:K("box",{flexDirection:"column",overflow:"hidden",children:[K(Gp,{palette:t,text:"Server"},void 0,!1,void 0,this),K(Wb,{palette:t,active:i===Up,rowH:d.rowH,label:"Listen",value:u?"[ ON ]":"[ OFF ]",valueTone:u?"success":"muted",hint:u?"space or enter stop":"space or enter start",onPick:()=>s(Up),onActivate:a},void 0,!1,void 0,this),K(Wb,{palette:t,active:i===Kp,rowH:d.rowH,label:"Port",value:`\u2039 ${o===""?n:o} \u203A`,valueTone:"info",hint:o===""?"\u2190 \u2192 change type digits enter apply":"typing\u2026 enter apply \u232B erase",onPick:()=>s(Kp)},void 0,!1,void 0,this),K("box",{height:1,overflow:"hidden",children:K("text",{fg:t.muted,wrapMode:"none",children:u?"Listening on loopback. Agents use the URL and token in the JSON below.":"Server is stopped. Highlight Listen and press space to start it."},void 0,!1,void 0,this)},void 0,!1,void 0,this),K(Gp,{palette:t,text:"Tools",extra:h===0?"all enabled":`${h} disabled`},void 0,!1,void 0,this),K("box",{height:1,overflow:"hidden",children:K("text",{fg:t.muted,wrapMode:"none",children:"Disabled tools are hidden from agents and refused if called anyway."},void 0,!1,void 0,this)},void 0,!1,void 0,this),Qu.map((w)=>{let v=x.filter((b)=>b.category===w);if(v.length===0)return null;return K("box",{flexDirection:"column",overflow:"hidden",children:[K("box",{height:1,marginTop:1,overflow:"hidden",children:K("text",{fg:t.muted,wrapMode:"none",children:OD[w]??w},void 0,!1,void 0,this)},void 0,!1,void 0,this),v.map((b)=>{let S=ll+x.indexOf(b);return K(ND,{palette:t,tool:b,enabled:En(b.name,y),active:i===S,rowH:d.rowH,onPick:()=>s(S),onToggle:()=>l(b)},b.name,!1,void 0,this)})]},w,!0,void 0,this)}),K(Gp,{palette:t,text:"Copy agent config",extra:"highlight a row, then enter or click Copy"},void 0,!1,void 0,this),g.map((w,v)=>{let b=dl()+v;return K(FD,{palette:t,snippet:w,active:i===b,rowH:d.rowH,onPick:()=>s(b),onCopy:()=>c(w)},w.kind,!1,void 0,this)}),K(HD,{palette:t,snippet:_,selected:Boolean(P),brand:_?Rc(_.kind,t):t.muted},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this),K(jt,{palette:t,backgroundColor:t.element,edge:"top",children:K(ot,{palette:t,hints:$D(i,u)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function $D(e,t){if(e===Up)return[{key:"space",label:t?"stop server":"start server"},{key:"j/k",label:"move"}];if(e===Kp)return[{key:"\u2190\u2192",label:"change port"},{key:"0-9",label:"type port"},{key:"enter",label:"apply port"},{key:"j/k",label:"move"}];if(Es(e))return[{key:"space",label:"enable / disable"},{key:"enter",label:"enable / disable"},{key:"j/k",label:"move"}];return[{key:"enter",label:"copy JSON"},{key:"space",label:"copy JSON"},{key:"j/k",label:"move"}]}function Gp(e){let t=e.extra?` \xB7 ${e.extra}`:"";return K("box",{height:1,marginTop:1,overflow:"hidden",children:K("text",{wrapMode:"none",children:[K("span",{fg:e.palette.primary,children:e.text},void 0,!1,void 0,this),K("span",{fg:e.palette.muted,children:t},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}function Wb(e){let{palette:t,active:r,rowH:n,label:o,value:i,valueTone:s,hint:a,onPick:c,onActivate:l}=e,d=s==="success"?t.success:s==="info"?t.info:t.muted;return K("box",{height:n,flexDirection:"row",overflow:"hidden",backgroundColor:r?t.highlight:t.panel,onMouseDown:()=>{if(r&&l){l();return}c()},children:[K("box",{width:2,flexShrink:0,overflow:"hidden",children:K("text",{fg:t.primary,children:r?"\u203A":" "},void 0,!1,void 0,this)},void 0,!1,void 0,this),K("box",{width:Jp,flexShrink:0,overflow:"hidden",children:K("text",{fg:r?t.primary:t.text,wrapMode:"none",children:o},void 0,!1,void 0,this)},void 0,!1,void 0,this),K("box",{width:ID,flexShrink:0,overflow:"hidden",children:K("text",{fg:d,wrapMode:"none",children:i},void 0,!1,void 0,this)},void 0,!1,void 0,this),K("box",{flexGrow:1,overflow:"hidden",children:K("text",{fg:r?t.text:t.muted,wrapMode:"none",children:a},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function FD(e){let{palette:t,snippet:r,active:n,rowH:o,onPick:i,onCopy:s}=e,a=Rc(r.kind,t),c=jv(r.kind,t),l=r.language==="toml"?"Copy TOML":"Copy JSON";return K("box",{height:o,flexDirection:"row",overflow:"hidden",backgroundColor:n?t.highlight:t.panel,onMouseDown:i,children:[K("box",{width:2,flexShrink:0,overflow:"hidden",children:K("text",{fg:a,children:n?"\u203A":" "},void 0,!1,void 0,this)},void 0,!1,void 0,this),K("box",{width:Jp,flexShrink:0,overflow:"hidden",children:K("text",{fg:a,wrapMode:"none",children:r.title},void 0,!1,void 0,this)},void 0,!1,void 0,this),K("box",{flexGrow:1,overflow:"hidden",children:K("text",{fg:t.muted,wrapMode:"none",children:r.path},void 0,!1,void 0,this)},void 0,!1,void 0,this),K(We,{palette:t,label:l,bg:a,fg:c,onMouseDown:()=>{i(),s()}},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function ND(e){let{palette:t,tool:r,enabled:n,active:o,rowH:i,onPick:s,onToggle:a}=e;return K("box",{height:i,flexDirection:"row",overflow:"hidden",backgroundColor:o?t.highlight:t.panel,onMouseDown:()=>{if(o){a();return}s()},children:[K("box",{width:2,flexShrink:0,overflow:"hidden",children:K("text",{fg:t.primary,children:o?"\u203A":" "},void 0,!1,void 0,this)},void 0,!1,void 0,this),K("box",{width:5,flexShrink:0,overflow:"hidden",children:K("text",{fg:n?t.success:t.muted,wrapMode:"none",children:n?" ON ":" OFF "},void 0,!1,void 0,this)},void 0,!1,void 0,this),K("box",{width:Jp+4,flexShrink:0,overflow:"hidden",children:K("text",{fg:o?t.primary:n?t.text:t.muted,wrapMode:"none",children:r.label},void 0,!1,void 0,this)},void 0,!1,void 0,this),K("box",{width:7,flexShrink:0,overflow:"hidden",children:K("text",{fg:r.mutates?t.warning:t.muted,wrapMode:"none",children:r.mutates?"write":"read"},void 0,!1,void 0,this)},void 0,!1,void 0,this),K("box",{flexGrow:1,overflow:"hidden",children:K("text",{fg:o?t.text:t.muted,wrapMode:"none",children:o?`space or enter ${n?"disable":"enable"} ${r.name}`:r.summary},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function HD(e){let{palette:t,snippet:r,selected:n,brand:o}=e;if(!r)return null;let i=r.hint?` \xB7 ${r.hint}`:"";return K("box",{marginTop:1,flexDirection:"column",overflow:"hidden",children:[K("text",{fg:o,wrapMode:"none",children:n?`${r.title} \xB7 paste into ${r.path}${i}`:`Preview ${r.title} \xB7 j/k to a copy row, then enter`},void 0,!1,void 0,this),K("text",{fg:t.text,children:r.text},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var Up=0,Kp=1,ll=2,jb=4,OD,Jp=14,ID=14;var Ts=D(()=>{Ot();ke();tt();ss();OD={inspect:"Inspect",logs:"Logs",diagnostics:"Diagnostics",control:"Control",setup:"Setup"}});import{jsxDEV as Ie,Fragment as qb}from"@opentui/react/jsx-dev-runtime";function VD(e){return[{key:"\u2190\u2192",label:"filter services"},{key:"e",label:"ERROR+ only"},{key:"g",label:"jump to latest"},{key:"p",label:"pause stream"},{key:"z",label:"full-screen logs"},{key:"\\",label:"split panes"},{key:"|",label:"focus other pane"},{key:"/trace",label:"jump to request id"},{key:"f",label:"search"},{key:"t",label:"timestamps"},{key:"m",label:"metadata"},{key:"w",label:"clip / wrap selected / wrap all"},{key:"j/k",label:"move and unfold"},{key:Ci(e),label:"copy selection"},{key:"/export",label:"write ~/.devctl/exports"},{key:"/exports",label:"open export folder"}]}function On(e){return e+jD}function Vb(e){let t=On(Yp.length),r=On(Xp.length),n=On(e.logCount),o=On(Qp.length),i=On(Zp.length),s=ef+e.pad*2;if(e.stacked)return s+t+r+n+o+i+zp+(KD-1)*e.gap;let a=Math.max(t,r),c=Math.max(o,i);return s+a+n+c+zp+(qD-1)*e.gap}function JD(e){return Vb(e)-ef-e.pad*2}function zD(e){return e-ef*2<WD}function Jb(e){let{palette:t,termW:r,termH:n,copyKey:o=Xr(),scrollRef:i}=e,s=po(ve()),a=zD(r),c=s?0:1,l=s?0:1,d=VD(o),u={stacked:a,logCount:d.length,pad:c,gap:l},f=Vb(u),p=JD(u);return Ie(ut,{palette:t,title:"help",bottomTitle:`${er()} \xB7 j/k scroll \xB7 esc close`,termW:r,termH:n,preferW:BD,preferH:f,gap:0,children:Ie("scrollbox",{ref:i,focused:!1,stickyScroll:!1,scrollX:!1,style:je(t),children:Ie("box",{height:p,flexDirection:"column",flexShrink:0,overflow:"hidden",gap:l,children:[a?Ie(qb,{children:[Ie(Mn,{palette:t,title:"navigation",bindings:Yp},void 0,!1,void 0,this),Ie(Mn,{palette:t,title:"services",bindings:Xp},void 0,!1,void 0,this),Ie(Mn,{palette:t,title:"logs",bindings:d},void 0,!1,void 0,this),Ie(Mn,{palette:t,title:"commands",bindings:Qp},void 0,!1,void 0,this),Ie(Mn,{palette:t,title:"display",bindings:Zp},void 0,!1,void 0,this)]},void 0,!0,void 0,this):Ie(qb,{children:[Ie(Kb,{palette:t,left:{title:"navigation",bindings:Yp},right:{title:"services",bindings:Xp}},void 0,!1,void 0,this),Ie(Mn,{palette:t,title:"logs",bindings:d},void 0,!1,void 0,this),Ie(Kb,{palette:t,left:{title:"commands",bindings:Qp},right:{title:"display",bindings:Zp}},void 0,!1,void 0,this)]},void 0,!0,void 0,this),Ie("box",{height:zp,flexShrink:0,overflow:"hidden",paddingLeft:1,children:Ie("text",{fg:t.muted,wrapMode:"none",children:"~/.devctl/tui.json \xB7 DEVCTL_TUI_CONFIG"},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function Kb(e){let{palette:t,left:r,right:n}=e,o=Math.max(On(r.bindings.length),On(n.bindings.length));return Ie("box",{height:o,flexDirection:"row",flexShrink:0,overflow:"hidden",gap:UD,children:[Ie(Mn,{palette:t,title:r.title,bindings:r.bindings,height:o,stretch:!0},void 0,!1,void 0,this),Ie(Mn,{palette:t,title:n.title,bindings:n.bindings,height:o,stretch:!0},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function Mn(e){let{palette:t,title:r,bindings:n,stretch:o=!1}=e,i=e.height??On(n.length),s=YD(n);return Ie("box",{height:i,flexGrow:o?1:0,flexBasis:o?0:void 0,flexShrink:0,flexDirection:"column",overflow:"hidden",border:!0,borderStyle:"rounded",borderColor:t.border,title:r,titleColor:t.accent,children:n.map((a)=>Ie("box",{height:1,flexShrink:0,overflow:"hidden",paddingLeft:1,paddingRight:1,children:Ie(XD,{palette:t,binding:a,keyWidth:s},void 0,!1,void 0,this)},`${a.key}-${a.label}`,!1,void 0,this))},void 0,!1,void 0,this)}function YD(e){return e.reduce((r,n)=>Math.max(r,n.key.length),0)+GD}function XD(e){let{palette:t,binding:r,keyWidth:n}=e;return Ie("box",{height:1,flexDirection:"row",overflow:"hidden",children:[Ie("box",{width:n,flexShrink:0,overflow:"hidden",children:Ie("text",{fg:t.primary,wrapMode:"none",children:r.key},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ie("box",{flexGrow:1,overflow:"hidden",children:Ie("text",{fg:t.text,wrapMode:"none",children:r.label},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var BD=78,WD=56,jD=2,ef=2,zp=1,tf=8,GD=2,UD=2,KD=6,qD=4,Yp,Xp,Qp,Zp;var rf=D(()=>{Ot();ke();_r();Pr();Yp=[{key:"tab",label:"next tab"},{key:"1-4",label:"dashboard services logs proxy"},{key:"s/l/a/p/d/c/u",label:"letter jump"},{key:"/",label:"command overlay \xB7 other screens"},{key:"j/k",label:"move selection"},{key:"esc",label:"back / close overlay"},{key:"esc \xD72",label:"quit"},{key:"q \xD72",label:"quit"}],Xp=[{key:"enter",label:"start profile or open"},{key:"space",label:"multi-select"},{key:"n",label:"start"},{key:"x",label:"stop"},{key:"r",label:"refresh"},{key:"R",label:"restart (c cascade if dependents)"}];Qp=[{key:Mt("c"),label:"copy highlighted selection"},{key:"/",label:"command overlay"},{key:Mt("p"),label:"same overlay"},{key:Mt("x"),label:"leader chord"},{key:"/settings",label:"preferences \xB7 MCP page"},{key:"/themes",label:"preview themes"},{key:"/diff",label:"config sources"},{key:"/mcp",label:"agent MCP server"}],Zp=[{key:Mt("="),label:"larger"},{key:Mt("-"),label:"smaller"},{key:"?",label:"this overlay"}]});function pi(e){e.setQuery(""),e.setSlashIndex(0),e.setSlashPicker("commands"),e.setOverlay("slash")}function zb(e,t){let{overlay:r,tui:n,height:o,closeOverlay:i,confirmKind:s,onQuit:a,confirmAction:c,planBusy:l,logDetailsScrollRef:d,scrollTextScrollRef:u,routeDetailsScrollRef:f,planScrollRef:p,helpScrollRef:m,revertThemePreview:g,setPaletteIndex:y,setThemeName:x,paletteIndex:h,applyTheme:P,leaderTimer:_,setOverlay:w,runCommand:v,setConfigEditError:b,saveConfigBuffer:S,setSlashIndex:k,filtered:L,setQuery:I,slashIndex:F,submitSlash:Q,advanceWizard:G}=e,C=(t.name??"").toLowerCase();if(r==="none")return!1;if(r==="setup-wizard"){if(C==="escape")return i(),!0;if(C==="return")return G?.(),!0;return Fp(r)}if(r==="confirm"){if(C==="escape")return i(),!0;if(s==="quit"&&C==="d")return a(!0),!0;if(s==="quit"&&C==="k")return e.onDown(!0),!0;if(s==="restart-cascade"&&C==="c")return c("cascade"),!0;if(C==="return")c();return!0}if(r==="log-details"||r==="scroll-text"){if(on(t,n))return pi(e),!0;if(C==="escape")return i(),!0;if(r==="log-details"&&C==="return"){let W=e.logDetail?.request_id?.trim()??"";if(W!=="")e.setLogSearch(W),e.setScreen("logs"),e.closeOverlay(),e.setStatus(`tracing ${W}`);return!0}let B=r==="log-details"?d.current:u.current;if(C==="down"||C==="j")return rt(B,n.scroll_speed),!0;if(C==="up"||C==="k")return rt(B,-n.scroll_speed),!0;if(Rs(t))return rt(B,ai(o)),!0;if(Ps(t))return rt(B,-ai(o)),!0;return!0}if(r==="route-details"){if(on(t,n))return pi(e),!0;if(C==="escape"||C==="return")return i(),!0;if(C==="down"||C==="j")return rt(f.current,n.scroll_speed),!0;if(C==="up"||C==="k")return rt(f.current,-n.scroll_speed),!0;return!0}if(r==="plan"){if(on(t,n))return pi(e),!0;if(C==="escape"||C==="return"&&!l)return i(),!0;if(C==="down"||C==="j")return rt(p.current,n.scroll_speed),!0;if(C==="up"||C==="k")return rt(p.current,-n.scroll_speed),!0;return!0}if(r==="help"){if(on(t,n))return pi(e),!0;if(C==="escape")return i(),!0;if(C==="down"||C==="j")return rt(m.current,1),!0;if(C==="up"||C==="k")return rt(m.current,-1),!0;if(C==="pagedown"||Rs(t))return rt(m.current,tf),!0;if(C==="pageup"||Ps(t))return rt(m.current,-tf),!0;return!0}if(r==="themes"){if(on(t,n))return g(),pi(e),!0;if(C==="escape")return g(),i(),!0;if(C==="down"||C==="j")return y((B)=>{let W=Math.min(B+1,St.length-1),V=St[W];if(V)x(V);return W}),!0;if(C==="up"||C==="k")return y((B)=>{let W=Math.max(0,B-1),V=St[W];if(V)x(V);return W}),!0;if(C==="return"){let B=St[h%St.length];if(B)P(B)}return!0}if(r==="leader"){if(on(t,n)){if(_.current)clearTimeout(_.current);return pi(e),!0}if(_.current)clearTimeout(_.current);w("none");let B=Kx(C),W=B?Br(B):void 0;if(W)v(W,[]);return!0}if(r==="config-edit"){if(C==="escape")return b(""),i(),!0;if(Or(t)&&C==="s")return S(),!0;return!0}if(Fp(r)){if(C==="escape")return i(),!0;if(r==="slash"&&C==="down")return k((B)=>Math.min(Math.max(L.length-1,0),B+1)),!0;if(r==="slash"&&C==="up")return k((B)=>Math.max(0,B-1)),!0;if(r==="slash"&&C==="tab"){if(t.shift)return k((W)=>Math.max(0,W-1)),!0;let B=ci(L,F);if(B)I(rb(B));return!0}if(r==="slash"&&C==="return")return Q(),!0;return!0}return!0}var Yb=D(()=>{yr();vo();cl();Pr();ke();rf();tt()});var ul=D(()=>{Uu()});function Xb(e,t){if(t.length===0)return[];let r=new Set(t);return io(e,t).filter((n)=>!r.has(n))}function Qb(e,t,r){let n=r!==""&&e.profiles[r]?r:"";if(t.length>0)return{services:t,profile:n};if(n!=="")return{services:[...e.profiles[r]?.services??[]],profile:n};return{services:Object.keys(e.services),profile:""}}function pl(e){return e.waves.flat().join(" \u2192 ")}function Zb(e){let t=pl(e);if(t==="")return"Started selected services";return`Started ${t}`}function ew(e){let t=pl(e);if(t==="")return"Stopped running services";return`Stopped ${t}`}function tw(e,t,r,n="start"){let o=r?.services[e],i=tr(o);if(o?.last_error&&n!=="stop")return o.last_error;if(n==="stop"){if(i==="STOPPED")return"stopped";if(i==="STOPPING")return"stopping now";if(i==="FAILED")return"already failed";return"queued"}if(i==="HEALTHY")return"ready";if(i==="STARTING")return"starting now";if(i==="RUNNING")return"up, waiting for health";if(i==="UNHEALTHY")return"unhealthy \u2014 retrying health check";if(i==="STOPPING")return"stopping first";if(i==="FAILED")return"failed \u2014 later waves will not start";let a=(t.steps.find((c)=>c.name===e)?.dependencies??[]).filter((c)=>{let l=tr(r?.services[Jt(c)]);return mn(c)==="service_healthy"?l!=="HEALTHY":l==="STOPPED"||l==="FAILED"}).map(Jt);if(a.length>0)return`waits for ${a.join(", ")}`;return"queued"}function As(e,t,r="start"){if(e.length===0)return"completed";let n=e.map((i)=>t?.services[i]);if(n.some((i)=>i?.state==="FAILED"))return"failed";if(r==="stop"){if(n.every((i)=>!i||i.state==="STOPPED"||i.state==="UNKNOWN"))return"completed";if(n.some((i)=>i?.state==="STOPPING"))return"active";return"queued"}let o=(i)=>Boolean(i&&(i.state==="HEALTHY"||i.health==="HEALTHY"||i.state==="RUNNING"&&i.health!=="UNHEALTHY"));if(n.every(o))return"completed";if(n.some((i)=>i?.state==="UNHEALTHY"||i?.health==="UNHEALTHY"))return"unhealthy";if(n.some((i)=>i?.state==="STARTING"||i?.state==="RUNNING"||i?.state==="RESTARTING"))return"active";return"queued"}function rw(e,t,r="start"){let o=e.waves.flat().length,i=0,s=0,a=0,c=0;for(let m=0;m<e.waves.length;m++){let g=e.waves[m]??[],y=As(g,t,r);if(y==="active"||y==="unhealthy"||y==="failed")c=m;else if(y==="completed"&&c===m&&m<e.waves.length-1)c=m+1;for(let x of g){let h=t?.services[x];if(r==="stop"?!h||h.state==="STOPPED"||h.state==="UNKNOWN":Boolean(h&&(h.state==="HEALTHY"||h.health==="HEALTHY"||h.state==="RUNNING"&&h.health!=="UNHEALTHY")))i++;else if(h?.state==="FAILED")s++;else if(h?.state==="STARTING"||h?.state==="RUNNING"||h?.state==="UNHEALTHY"||h?.health==="UNHEALTHY"||h?.state==="STOPPING"||h?.state==="RESTARTING")a++}}let l=o>0?Math.round(i/o*100):100,d=16,u=Math.round(l/100*d),f="\u2588".repeat(u)+"\u2591".repeat(Math.max(0,d-u)),p=i===o&&o>0;return{total:o,ready:i,active:a,failed:s,percent:l,progressBar:f,currentWaveIndex:c,totalWaves:e.waves.length,isComplete:p}}function nw(e,t,r){let n=r==="completed"?"\u2713 Completed":r==="failed"?"\u2717 Failed":r==="unhealthy"?"\u26A0 Unhealthy":r==="active"?"\u23F3 In Progress":"\u25CB Queued";return`${e==="stop"?t===0?"Wave 1 (Stop First)":`Wave ${t+1}`:t===0?"Wave 1 (Start First)":`Wave ${t+1}`} \xB7 ${n}`}function ow(e,t,r,n){let o=n?` \xB7 Profile ${n}`:"";if(r)return`${e==="stop"?"Shutdown":"Startup"} Failed (${r})`;if(t)return`${e==="stop"?"Stopping Services":e==="restart"?"Restarting Pipeline":"Starting Pipeline"}${o}`;return`${e==="stop"?"Shutdown Complete":"Startup Complete"}${o}`}function iw(e,t){if(e)return{primary:"Working\u2026 esc hide this panel (keeps running in background)",secondary:"Services are transitioning. You can dismiss anytime without stopping them."};if(t)return{primary:"enter or esc back to dashboard",secondary:"Execution stopped. Check logs or open Doctor to resolve errors."};return{primary:"enter or esc back to dashboard",secondary:"All waves finished. Click here or press enter to continue."}}var fl=D(()=>{mt();Tt();At()});function sw(e,t){let r=(t.name??"").toLowerCase();if(e.screen==="mcp"&&e.listCursor===1&&r.length===1&&r>="0"&&r<="9")return e.setMcpPortDraft((n)=>uv(n,r)),!0;return!1}function aw(e,t){let r=(t.name??"").toLowerCase(),{screen:n,overlay:o,tui:i,cfg:s,controller:a,profile:c,listCursor:l,names:d,listCount:u,height:f,checked:p,detailName:m,bootErrorMissing:g,bootError:y,logSearchFocused:x,logSources:h,logService:P,logShowTimestamps:_,logShowMeta:w,logWrap:v,showSystemLogs:b,logPinned:S,dashboardLogCursor:k,applyLogCursor:L,applyDashboardLogCursor:I,jumpToLatestLogs:F,toggleSystemLogs:Q,clearLogs:G,setLogService:C,setLogShowTimestamps:B,setLogShowMeta:W,setPaused:V,setErrorOnly:de,setLogWrap:Ee,beginStart:$e,beginStop:ne,beginRestart:H,applyMcpPortDraft:ae,toggleMcp:X,toggleMcpTool:nt,copyFocusedMcpSnippet:pt,persistMcpPort:ie,restartMcpOnPort:Ae,setMcpPortDraft:De,settingRows:Ke,persistPrefs:ht,cycleSetting:Ye,toggleMouse:Ct,setDoctorTick:he,refreshAuth:yt,configScrollRef:st,detailScrollRef:vt,handleEnter:rr,setScreen:Xe,setChecked:Xt,setStatus:qe,setSelected:ft,setProfile:vr,refresh:Kt,toggleChecked:xr,createStarterConfig:nr,startWizard:qt,setConfirmKind:or,setConfirmDetail:ir,setOverlay:sr,openConfigBuffer:Vt,runCommand:ce}=e,U=(n==="logs"||n==="dashboard")&&!x;if(U&&(r==="left"||r==="["||n==="logs"&&r==="h")){C(Sp(h,P,-1));return}if(U&&(r==="right"||r==="]"||n==="logs"&&r==="l")){C(Sp(h,P,1));return}if((n==="dashboard"||n==="setup")&&r==="o"){Xe("profiles");return}if((n==="dashboard"||n==="services")&&(r==="*"||t.shift&&r==="8")){Xt([...d]),qe(`Selected ${d.length} services`);return}if((n==="dashboard"||n==="services")&&(r==="-"||r==="_"||r==="minus")){Xt([]),qe("Selection cleared");return}if((n==="dashboard"||n==="services")&&r==="n"){$e(Oc(p,d[l]??""),c);return}if((n==="dashboard"||n==="services")&&r==="x"){ne(Oc(p,d[l]??""));return}if((n==="dashboard"||n==="services"||n==="detail")&&$b(t)){let T=Oc(p,d[l]??m);if(s&&Xb(s,T).length>0){or("restart-cascade"),ir({services:T}),sr("confirm");return}H(T,c);return}if((n==="dashboard"||n==="services")&&r==="r"){Kt(),qe("Refreshed");return}if(n==="auth"&&r==="r"){yt();return}if(n==="logs"&&r==="t"){let T=!_;B(T),ht({log_timestamps:T},T?"timestamps on":"timestamps off");return}if(n==="logs"&&r==="m"){let T=!w;W(T),ht({log_metadata:T},T?"metadata on":"metadata off");return}if(r==="down"||r==="j"){if(n==="mcp"&&l===1)ae();if(n==="config"){if(u>0)ft((T)=>Math.min(Math.max(u-1,0),T+1));rt(st.current,i.scroll_speed);return}if(n==="detail"){rt(vt.current,i.scroll_speed);return}if(n==="logs"){L(l+1);return}if(n==="dashboard"&&k>=0){I(k+1);return}ft((T)=>Math.min(Math.max(u-1,0),T+1));return}if(r==="up"||r==="k"){if(n==="mcp"&&l===1)ae();if(n==="config"){if(u>0)ft((T)=>Math.max(0,Math.min(T,Math.max(u-1,0))-1));rt(st.current,-i.scroll_speed);return}if(n==="detail"){rt(vt.current,-i.scroll_speed);return}if(n==="logs"){L(l-1);return}if(n==="dashboard"&&k>=0){I(k-1);return}ft((T)=>Math.max(0,Math.min(T,Math.max(u-1,0))-1));return}if(Rs(t)){let T=ai(f);if(n==="config"){rt(st.current,T);return}if(n==="detail"){rt(vt.current,T);return}if(n==="logs"){L(l+T);return}if(n==="dashboard"&&(S||k>=0)){I(Math.max(0,k)+T);return}ft((ye)=>Math.min(Math.max(u-1,0),ye+T));return}if(Ps(t)){let T=ai(f);if(n==="config"){rt(st.current,-T);return}if(n==="detail"){rt(vt.current,-T);return}if(n==="logs"){L(l-T);return}if(n==="dashboard"&&(S||k>=0)){I(Math.max(0,k)-T);return}ft((ye)=>Math.max(0,ye-T));return}if(n==="proxy"&&r==="n"){a?.proxyStart().then(async()=>{qe("Proxy started"),await Kt()});return}if(n==="proxy"&&r==="x"){a?.proxyStop().then(async()=>{qe("Proxy stopped"),await Kt()});return}if(n==="detail"&&m!==""){if(r==="n"){$e([m],c);return}if(r==="x"){ne([m]);return}if(r==="o"){Xe("config");return}if(r==="l"){C(m),Xe("logs");return}}if(n==="mcp"&&l===1&&(r==="left"||r==="h")){let T=Pn(ae()-1);ie(T),Ae(T);return}if(n==="mcp"&&l===1&&(r==="right"||r==="l")){let T=Pn(ae()+1);ie(T),Ae(T);return}if(n==="mcp"&&l===1&&(r==="backspace"||r==="delete")){De((T)=>pv(T));return}if(n==="settings"&&(r==="left"||r==="h")){Ye(-1,l);return}if(n==="settings"&&(r==="right"||r==="l")){Ye(1,l);return}if(r==="space"){if(n==="profiles"){let z=Object.keys(s?.profiles??{}).sort()[l];if(z)vr(z),qe(`Profile ${z}`);return}if(n==="mcp"){if(l===0){X();return}let ye=Es(l);if(ye){nt(ye);return}pt(l);return}if(n==="logs")return;if(n==="settings"){if(Tn(Ke,l)?.id==="mouse")Ct();return}let T=d[l];if(T)xr(T);return}if(r==="return"){if(n==="setup"&&!a){if(!g){qe(y||"Existing configuration is invalid \u2014 fix it and restart devctl.");return}qt();return}if(n==="setup"&&a){$e([],c);return}rr();return}if(Dn(t,i,"services","s")&&o==="none"&&n!=="logs"){Xe("services");return}if(Dn(t,i,"logs","l")&&o==="none"&&n!=="detail"&&n!=="logs"&&n!=="settings"){Xe("logs");return}if(Dn(t,i,"auth","a")&&o==="none"){Xe("auth");return}if(Dn(t,i,"proxy","p")&&o==="none"&&n!=="logs"){Xe("proxy");return}if(Dn(t,i,"doctor","d")&&o==="none"&&n!=="doctor"){Xe("doctor");return}if(Dn(t,i,"config","c")&&o==="none"){Xe("config");return}if(Dn(t,i,"setup","u")&&o==="none"){Xe("setup");return}if(n==="config"&&r==="e"){ce(Br("/edit")??{name:"edit",aliases:[],desc:"",leader:"",group:"ui"},[]);return}if(n==="config"&&r==="v"){Vt();return}if(n==="doctor"&&r==="r"){he((T)=>T+1),qe("Re-running doctor");return}if(n==="logs"&&r==="p"){V((T)=>!T);return}if((n==="logs"||n==="dashboard")&&r==="g"){F();return}if((n==="logs"||n==="dashboard")&&r==="w"){let T=Kc(v);Ee(T),qe(`Log ${Vc(T)}`);return}if((n==="logs"||n==="dashboard")&&r==="e"){de((T)=>!T);return}if((n==="logs"||n==="dashboard")&&r==="i"){Q(),qe(b?"Hiding internal auth/mcp/devctl/proxy logs":"Showing internal logs");return}if((n==="logs"||n==="dashboard")&&Fb(t))G()}var cw=D(()=>{ul();yr();hr();fl();At();cl();ke();Ts();_r()});import{useKeyboard as QD}from"@opentui/react";import{useCallback as nf}from"react";function lw({tui:e,controller:t,cfg:r,snap:n,ui:o,logView:i,lifecycleActions:s,mcp:a,preferences:c,diagnostics:l,refs:d}){let{screen:u,onQuit:f,closeOverlay:p,confirmKind:m,confirmDetail:g,portTarget:y,profile:x,listCursor:h,names:P,runCommand:_,copySelection:w,setOverlay:v,setConfirmKind:b,setConfirmDetail:S,setPortTarget:k,setLogDetail:L,setProfile:I,setStatus:F,setSlashIndex:Q,setQuery:G,setSlashPicker:C,setScreen:B,freePort:W,openDetail:V}=o,{logSlice:de,logSliceB:Ee,logSearchFocused:$e,logsFullscreen:ne,setLogSearchFocused:H,setLogsFullscreen:ae,splitLogs:X,splitFocus:nt,toggleSplitLogs:pt,cycleSplitFocus:ie,setActiveLogService:Ae,logService:De,logServiceB:Ke}=i,{beginStart:ht,beginRestart:Ye,planBusy:Ct}=s,{applyMcpPortDraft:he,toggleMcp:yt,toggleMcpTool:st,copyFocusedMcpSnippet:vt}=a,{settingRows:rr,activateSetting:Xe,applyFont:Xt,applyReset:qe,fontSize:ft,revertThemePreview:vr,setThemeName:Kt,leaderMs:xr}=c,{doctor:nr}=l,{interruptArmedAt:qt,leaderTimer:or}=d,ir=nf(()=>{if(r?.shutdown.stop_services_on_exit===void 0){b("quit"),v("confirm");return}f(r.shutdown.stop_services_on_exit===!1)},[r,f,b,v]),sr=nf((ce)=>{if(m==="quit"){f(!1);return}if(m==="reload"){p();let U=n?.restart_required??[];if(U.length>0)Ye(U,x);return}if(m==="restart-cascade"){let U=g?.services??[];p(),Ye(U,x,ce==="cascade");return}if(m==="reset-prefs"){p(),qe();return}if(m==="free-port"){let U=y;if(p(),!U)return;W(U).then(()=>{F(`Stopped ${U.command} (pid ${U.pid}) on port ${U.port}`),l.setDoctorTick((T)=>T+1)}).catch((T)=>{F(A(T))});return}p(),ht([],x)},[qe,Ye,ht,p,g,m,l,W,f,y,x,F,n]),Vt=nf(()=>{if(u==="logs"){let U=(X&&nt===1?Ee:de)[h];if(U)L(U),v("log-details");return}if(u==="profiles"){let T=Object.keys(r?.profiles??{}).sort()[h];if(T)I(T),b("start-profile"),v("confirm");return}if(u==="doctor"){let U=nr?.checks[h];if(U?.action?.kind==="free-port"){let T=U.action.holder;k(T),S({port:T.port,pid:T.pid,process:T.command}),b("free-port"),v("confirm")}return}if(u==="mcp"){if(h===0){yt();return}if(h===1){he();return}let U=Es(h);if(U){st(U);return}vt(h);return}if(u==="settings"){let U=Tn(rr,h);if(U)Xe(U);return}if(u==="config"){let U=Object.keys(r?.tasks??{}).sort()[h],T=Br("run");if(U&&T)_(T,[U]);return}if(u==="dashboard"&&Mc(n)){ht([],x);return}let ce=P[h];if(ce&&(u==="dashboard"||u==="services"))V(ce)},[Xe,he,ht,r,vt,nr,h,de,Ee,P,V,x,_,u,S,b,L,v,k,I,rr,n,nt,X,yt,st]);QD((ce)=>{let U=(ce.name??"").toLowerCase();if(Nb(ce,e)){w();return}if(Or(ce)&&(U==="="||U==="+"||U==="plus")){Xt(fs(ft,1));return}if(Or(ce)&&(U==="-"||U==="_"||U==="minus")){Xt(fs(ft,-1));return}if(Or(ce)&&U==="0"){Xt(_c().font_size);return}if(u==="logs"&&!$e&&(ce.sequence==="\\"||U==="\\")){pt(),F(X?"Single log pane":"Split log panes");return}if(u==="logs"&&!$e&&(ce.sequence==="|"||U==="|")){ie();return}if(zb({...o,tui:e,confirmAction:sr,planBusy:Ct,leaderTimer:or,logDetailsScrollRef:d.logDetailsScrollRef,scrollTextScrollRef:d.scrollTextScrollRef,routeDetailsScrollRef:d.routeDetailsScrollRef,planScrollRef:d.planScrollRef,helpScrollRef:d.helpScrollRef,revertThemePreview:vr,setThemeName:Kt},ce))return;if($e){if(U==="escape")H(!1);return}if(Lb(ce,e)){v("leader"),or.current=setTimeout(()=>v("none"),xr);return}if(Db(ce,e)||on(ce,e)){G(""),Q(0),C("commands"),v("slash");return}if(Mb(ce,e)){v("help");return}if(Ob(ce,e)||u==="logs"&&U==="f"&&!ce.ctrl&&!ce.meta){B("logs"),H(!0);return}if(U==="escape"||Ib(ce)){if(U==="escape"){if(ne){ae(!1);return}if(u==="detail"){B("services");return}if(u==="setup"&&t){B("dashboard");return}}let z=Date.now();if(Hb(z,qt.current)){qt.current=0,ir();return}qt.current=z,F(U==="q"?"q again to quit":"Esc again to quit");return}if(U==="z"&&(u==="logs"||u==="dashboard")&&!ce.ctrl&&!ce.meta){B("logs"),ae((z)=>u==="logs"?!z:!0);return}if(U==="tab"){B(ce.shift?Zx(u):Qx(u));return}let T={...o,tui:e,controller:t,cfg:r,snap:n,...i,...s,...a,...c,logService:X&&nt===1?Ke:De,setLogService:Ae,setDoctorTick:l.setDoctorTick,refreshAuth:l.refreshAuth,configScrollRef:d.configScrollRef,detailScrollRef:d.detailScrollRef,handleEnter:Vt};if(sw(T,ce))return;let ye=Ax(U);if(ye){B(ye);return}aw(T,ce)})}var dw=D(()=>{te();vo();Ln();At();cl();Ts();_r();Pr();Yb();cw()});import{parse as ZD,stringify as eM}from"yaml";function ml(e,t="imported"){let r=ZD(e);if(!r||typeof r!=="object"||Array.isArray(r))throw Error("compose file is not a mapping");let n=r,o=fi(n.services)?n.services:{},i=[];if(n.networks!==void 0)i.push({service:"(root)",field:"networks",reason:uw.networks??"not modeled"});let s={},a=[];for(let[l,d]of Object.entries(o)){if(!fi(d))continue;let u=tM(l,d,i);if(s[l]=u.body,!u.container)a.push(l)}return{yaml:eM({version:1,project:{name:t},services:s,profiles:{default:{services:a}}}),dropped:i,services:Object.keys(s),profileServices:a}}function gl(e){let t=[e.yaml.trimEnd(),"","Dropped fields:"];if(e.dropped.length===0)t.push("(none)");else{t.push("service\tfield\treason");for(let r of e.dropped)t.push(`${r.service} ${r.field} ${r.reason}`)}if(e.profileServices.length<e.services.length)t.push(""),t.push("Container services were omitted from profile default so first start does not require Docker.");return`${t.join(`
2144
+ `}function yI(e){return`codex mcp add --url ${e}`}function Ds(e,t){return[{kind:"claude",title:"Claude",path:".mcp.json",language:"json",text:Vp(e,t),hint:"claude mcp add-json"},{kind:"cursor",title:"Cursor",path:".cursor/mcp.json",language:"json",text:zp(e,t)},{kind:"kilo",title:"Kilo Code",path:"kilo.jsonc",language:"json",text:Yp(e,t)},{kind:"codex",title:"Codex",path:"~/.codex/config.toml",language:"toml",text:Jp(e,t),hint:yI(e)}]}function iw(e,t){let r=[`URL ${e}`,""];for(let n of Ds(e,t))r.push(`# ${n.title} ${n.path}${n.hint?` (${n.hint})`:""}`),r.push(n.text.trimEnd()),r.push("");return r.join(`
2145
+ `)}import{jsxDEV as q}from"@opentui/react/jsx-dev-runtime";function ef(){return sp.flatMap((e)=>Pc.filter((t)=>t.category===e))}function gl(){return hl+ef().length}function cw(){return gl()+aw}function Is(e){if(e<hl||e>=gl())return;return ef()[e-hl]}function tf(e){let t=e-gl();return t>=0&&t<aw?t:void 0}function lw(e){let{palette:t,snap:r,port:n,portDraft:o="",selected:i,onPick:s,onToggle:a,onCopy:c,onToggleTool:l}=e,d=ye(),u=r?.mcp?.running===!0,f=r?.mcp?.port??n,p=r?.mcp?.address??fi(f),m=r?.mcp?.token??"",h=Ds(p,m),v=r?.mcp?.disabled_tools??[],x=ef(),g=x.filter((S)=>!An(S.name,v)).length,R=h[tf(i)??-1],_=R??h[0];return q(kt,{palette:t,title:"mcp settings",children:[q(ut,{palette:t,items:[{text:u?"RUNNING":"STOPPED",tone:u?"success":"idle"},{text:`127.0.0.1:${f}`,tone:"info"}]},void 0,!1,void 0,this),q("box",{flexGrow:1,overflow:"hidden",children:q("scrollbox",{focused:!1,stickyScroll:!1,scrollX:!1,style:Ue(t),children:q("box",{flexDirection:"column",overflow:"hidden",children:[q(Xp,{palette:t,text:"Server"},void 0,!1,void 0,this),q(sw,{palette:t,active:i===Qp,rowH:d.rowH,label:"Listen",value:u?"[ ON ]":"[ OFF ]",valueTone:u?"success":"muted",hint:u?"space or enter stop":"space or enter start",onPick:()=>s(Qp),onActivate:a},void 0,!1,void 0,this),q(sw,{palette:t,active:i===Zp,rowH:d.rowH,label:"Port",value:`\u2039 ${o===""?n:o} \u203A`,valueTone:"info",hint:o===""?"\u2190 \u2192 change type digits enter apply":"typing\u2026 enter apply \u232B erase",onPick:()=>s(Zp)},void 0,!1,void 0,this),q("box",{height:1,overflow:"hidden",children:q("text",{fg:t.muted,wrapMode:"none",children:u?"Listening on loopback. Agents use the URL and token in the JSON below.":"Server is stopped. Highlight Listen and press space to start it."},void 0,!1,void 0,this)},void 0,!1,void 0,this),q(Xp,{palette:t,text:"Tools",extra:g===0?"all enabled":`${g} disabled`},void 0,!1,void 0,this),q("box",{height:1,overflow:"hidden",children:q("text",{fg:t.muted,wrapMode:"none",children:"Disabled tools are hidden from agents and refused if called anyway."},void 0,!1,void 0,this)},void 0,!1,void 0,this),sp.map((S)=>{let y=x.filter((b)=>b.category===S);if(y.length===0)return null;return q("box",{flexDirection:"column",overflow:"hidden",children:[q("box",{height:1,marginTop:1,overflow:"hidden",children:q("text",{fg:t.muted,wrapMode:"none",children:xI[S]??S},void 0,!1,void 0,this)},void 0,!1,void 0,this),y.map((b)=>{let w=hl+x.indexOf(b);return q(kI,{palette:t,tool:b,enabled:An(b.name,v),active:i===w,rowH:d.rowH,onPick:()=>s(w),onToggle:()=>l(b)},b.name,!1,void 0,this)})]},S,!0,void 0,this)}),q(Xp,{palette:t,text:"Copy agent config",extra:"highlight a row, then enter or click Copy"},void 0,!1,void 0,this),h.map((S,y)=>{let b=gl()+y;return q(SI,{palette:t,snippet:S,active:i===b,rowH:d.rowH,onPick:()=>s(b),onCopy:()=>c(S)},S.kind,!1,void 0,this)}),q(CI,{palette:t,snippet:_,selected:Boolean(R),brand:_?Lc(_.kind,t):t.muted},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this),q(Ut,{palette:t,backgroundColor:t.element,edge:"top",children:q(it,{palette:t,hints:wI(i,u)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function wI(e,t){if(e===Qp)return[{key:"space",label:t?"stop server":"start server"},{key:"j/k",label:"move"}];if(e===Zp)return[{key:"\u2190\u2192",label:"change port"},{key:"0-9",label:"type port"},{key:"enter",label:"apply port"},{key:"j/k",label:"move"}];if(Is(e))return[{key:"space",label:"enable / disable"},{key:"enter",label:"enable / disable"},{key:"j/k",label:"move"}];return[{key:"enter",label:"copy JSON"},{key:"space",label:"copy JSON"},{key:"j/k",label:"move"}]}function Xp(e){let t=e.extra?` \xB7 ${e.extra}`:"";return q("box",{height:1,marginTop:1,overflow:"hidden",children:q("text",{wrapMode:"none",children:[q("span",{fg:e.palette.primary,children:e.text},void 0,!1,void 0,this),q("span",{fg:e.palette.muted,children:t},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}function sw(e){let{palette:t,active:r,rowH:n,label:o,value:i,valueTone:s,hint:a,onPick:c,onActivate:l}=e,d=s==="success"?t.success:s==="info"?t.info:t.muted;return q("box",{height:n,flexDirection:"row",overflow:"hidden",backgroundColor:r?t.highlight:t.panel,onMouseDown:()=>{if(r&&l){l();return}c()},children:[q("box",{width:2,flexShrink:0,overflow:"hidden",children:q("text",{fg:t.primary,children:r?"\u203A":" "},void 0,!1,void 0,this)},void 0,!1,void 0,this),q("box",{width:rf,flexShrink:0,overflow:"hidden",children:q("text",{fg:r?t.primary:t.text,wrapMode:"none",children:o},void 0,!1,void 0,this)},void 0,!1,void 0,this),q("box",{width:bI,flexShrink:0,overflow:"hidden",children:q("text",{fg:d,wrapMode:"none",children:i},void 0,!1,void 0,this)},void 0,!1,void 0,this),q("box",{flexGrow:1,overflow:"hidden",children:q("text",{fg:r?t.text:t.muted,wrapMode:"none",children:a},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function SI(e){let{palette:t,snippet:r,active:n,rowH:o,onPick:i,onCopy:s}=e,a=Lc(r.kind,t),c=ax(r.kind,t),l=r.language==="toml"?"Copy TOML":"Copy JSON";return q("box",{height:o,flexDirection:"row",overflow:"hidden",backgroundColor:n?t.highlight:t.panel,onMouseDown:i,children:[q("box",{width:2,flexShrink:0,overflow:"hidden",children:q("text",{fg:a,children:n?"\u203A":" "},void 0,!1,void 0,this)},void 0,!1,void 0,this),q("box",{width:rf,flexShrink:0,overflow:"hidden",children:q("text",{fg:a,wrapMode:"none",children:r.title},void 0,!1,void 0,this)},void 0,!1,void 0,this),q("box",{flexGrow:1,overflow:"hidden",children:q("text",{fg:t.muted,wrapMode:"none",children:r.path},void 0,!1,void 0,this)},void 0,!1,void 0,this),q(Be,{palette:t,label:l,bg:a,fg:c,onMouseDown:()=>{i(),s()}},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function kI(e){let{palette:t,tool:r,enabled:n,active:o,rowH:i,onPick:s,onToggle:a}=e;return q("box",{height:i,flexDirection:"row",overflow:"hidden",backgroundColor:o?t.highlight:t.panel,onMouseDown:()=>{if(o){a();return}s()},children:[q("box",{width:2,flexShrink:0,overflow:"hidden",children:q("text",{fg:t.primary,children:o?"\u203A":" "},void 0,!1,void 0,this)},void 0,!1,void 0,this),q("box",{width:5,flexShrink:0,overflow:"hidden",children:q("text",{fg:n?t.success:t.muted,wrapMode:"none",children:n?" ON ":" OFF "},void 0,!1,void 0,this)},void 0,!1,void 0,this),q("box",{width:rf+4,flexShrink:0,overflow:"hidden",children:q("text",{fg:o?t.primary:n?t.text:t.muted,wrapMode:"none",children:r.label},void 0,!1,void 0,this)},void 0,!1,void 0,this),q("box",{width:7,flexShrink:0,overflow:"hidden",children:q("text",{fg:r.mutates?t.warning:t.muted,wrapMode:"none",children:r.mutates?"write":"read"},void 0,!1,void 0,this)},void 0,!1,void 0,this),q("box",{flexGrow:1,overflow:"hidden",children:q("text",{fg:o?t.text:t.muted,wrapMode:"none",children:o?`space or enter ${n?"disable":"enable"} ${r.name}`:r.summary},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function CI(e){let{palette:t,snippet:r,selected:n,brand:o}=e;if(!r)return null;let i=r.hint?` \xB7 ${r.hint}`:"";return q("box",{marginTop:1,flexDirection:"column",overflow:"hidden",children:[q("text",{fg:o,wrapMode:"none",children:n?`${r.title} \xB7 paste into ${r.path}${i}`:`Preview ${r.title} \xB7 j/k to a copy row, then enter`},void 0,!1,void 0,this),q("text",{fg:t.text,children:r.text},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var Qp=0,Zp=1,hl=2,aw=4,xI,rf=14,bI=14;var Ms=D(()=>{Mt();ke();tt();us();xI={inspect:"Inspect",logs:"Logs",diagnostics:"Diagnostics",control:"Control",setup:"Setup"}});import{jsxDEV as Fe,Fragment as uw}from"@opentui/react/jsx-dev-runtime";function DI(e){return[{key:"\u2190\u2192",label:"filter services"},{key:"e",label:"ERROR+ only"},{key:"g",label:"jump to latest"},{key:"p",label:"pause stream"},{key:"z",label:"full-screen logs"},{key:"\\",label:"split panes"},{key:"|",label:"focus other pane"},{key:"/trace",label:"jump to request id"},{key:"f",label:"search"},{key:"t",label:"timestamps"},{key:"m",label:"metadata"},{key:"w",label:"clip / wrap selected / wrap all"},{key:"j/k",label:"move and unfold"},{key:Ri(e),label:"copy selection"},{key:"/export",label:"write ~/.devctl/exports"},{key:"/exports",label:"open export folder"}]}function $n(e){return e+_I}function pw(e){let t=$n(of.length),r=$n(sf.length),n=$n(e.logCount),o=$n(af.length),i=$n(cf.length),s=lf+e.pad*2;if(e.stacked)return s+t+r+n+o+i+nf+(AI-1)*e.gap;let a=Math.max(t,r),c=Math.max(o,i);return s+a+n+c+nf+(LI-1)*e.gap}function II(e){return pw(e)-lf-e.pad*2}function MI(e){return e-lf*2<RI}function fw(e){let{palette:t,termW:r,termH:n,copyKey:o=Zr(),scrollRef:i}=e,s=go(ye()),a=MI(r),c=s?0:1,l=s?0:1,d=DI(o),u={stacked:a,logCount:d.length,pad:c,gap:l},f=pw(u),p=II(u);return Fe(pt,{palette:t,title:"help",bottomTitle:`${er()} \xB7 j/k scroll \xB7 esc close`,termW:r,termH:n,preferW:PI,preferH:f,gap:0,children:Fe("scrollbox",{ref:i,focused:!1,stickyScroll:!1,scrollX:!1,style:Ue(t),children:Fe("box",{height:p,flexDirection:"column",flexShrink:0,overflow:"hidden",gap:l,children:[a?Fe(uw,{children:[Fe(On,{palette:t,title:"navigation",bindings:of},void 0,!1,void 0,this),Fe(On,{palette:t,title:"services",bindings:sf},void 0,!1,void 0,this),Fe(On,{palette:t,title:"logs",bindings:d},void 0,!1,void 0,this),Fe(On,{palette:t,title:"commands",bindings:af},void 0,!1,void 0,this),Fe(On,{palette:t,title:"display",bindings:cf},void 0,!1,void 0,this)]},void 0,!0,void 0,this):Fe(uw,{children:[Fe(dw,{palette:t,left:{title:"navigation",bindings:of},right:{title:"services",bindings:sf}},void 0,!1,void 0,this),Fe(On,{palette:t,title:"logs",bindings:d},void 0,!1,void 0,this),Fe(dw,{palette:t,left:{title:"commands",bindings:af},right:{title:"display",bindings:cf}},void 0,!1,void 0,this)]},void 0,!0,void 0,this),Fe("box",{height:nf,flexShrink:0,overflow:"hidden",paddingLeft:1,children:Fe("text",{fg:t.muted,wrapMode:"none",children:"~/.devctl/tui.json \xB7 DEVCTL_TUI_CONFIG"},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function dw(e){let{palette:t,left:r,right:n}=e,o=Math.max($n(r.bindings.length),$n(n.bindings.length));return Fe("box",{height:o,flexDirection:"row",flexShrink:0,overflow:"hidden",gap:TI,children:[Fe(On,{palette:t,title:r.title,bindings:r.bindings,height:o,stretch:!0},void 0,!1,void 0,this),Fe(On,{palette:t,title:n.title,bindings:n.bindings,height:o,stretch:!0},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function On(e){let{palette:t,title:r,bindings:n,stretch:o=!1}=e,i=e.height??$n(n.length),s=OI(n);return Fe("box",{height:i,flexGrow:o?1:0,flexBasis:o?0:void 0,flexShrink:0,flexDirection:"column",overflow:"hidden",border:!0,borderStyle:"rounded",borderColor:t.border,title:r,titleColor:t.accent,children:n.map((a)=>Fe("box",{height:1,flexShrink:0,overflow:"hidden",paddingLeft:1,paddingRight:1,children:Fe($I,{palette:t,binding:a,keyWidth:s},void 0,!1,void 0,this)},`${a.key}-${a.label}`,!1,void 0,this))},void 0,!1,void 0,this)}function OI(e){return e.reduce((r,n)=>Math.max(r,n.key.length),0)+EI}function $I(e){let{palette:t,binding:r,keyWidth:n}=e;return Fe("box",{height:1,flexDirection:"row",overflow:"hidden",children:[Fe("box",{width:n,flexShrink:0,overflow:"hidden",children:Fe("text",{fg:t.primary,wrapMode:"none",children:r.key},void 0,!1,void 0,this)},void 0,!1,void 0,this),Fe("box",{flexGrow:1,overflow:"hidden",children:Fe("text",{fg:t.text,wrapMode:"none",children:r.label},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var PI=78,RI=56,_I=2,lf=2,nf=1,df=8,EI=2,TI=2,AI=6,LI=4,of,sf,af,cf;var uf=D(()=>{Mt();ke();Er();_r();of=[{key:"tab",label:"next tab"},{key:"1-4",label:"dashboard services logs proxy"},{key:"s/l/a/p/d/c/u",label:"letter jump"},{key:"/",label:"command overlay \xB7 other screens"},{key:"j/k",label:"move selection"},{key:"esc",label:"back / close overlay"},{key:"esc \xD72",label:"quit"},{key:"q \xD72",label:"quit"}],sf=[{key:"enter",label:"start profile or open"},{key:"space",label:"multi-select"},{key:"n",label:"start"},{key:"x",label:"stop"},{key:"r",label:"refresh"},{key:"R",label:"restart (c cascade if dependents)"}];af=[{key:It("c"),label:"copy highlighted selection"},{key:"/",label:"command overlay"},{key:It("p"),label:"same overlay"},{key:It("x"),label:"leader chord"},{key:"/settings",label:"preferences \xB7 MCP page"},{key:"/themes",label:"preview themes"},{key:"/diff",label:"config sources"},{key:"/mcp",label:"agent MCP server"}],cf=[{key:It("="),label:"larger"},{key:It("-"),label:"smaller"},{key:"?",label:"this overlay"}]});function mi(e){e.setQuery(""),e.setSlashIndex(0),e.setSlashPicker("commands"),e.setOverlay("slash")}function mw(e,t){let{overlay:r,tui:n,height:o,closeOverlay:i,confirmKind:s,onQuit:a,confirmAction:c,planBusy:l,logDetailsScrollRef:d,scrollTextScrollRef:u,routeDetailsScrollRef:f,planScrollRef:p,helpScrollRef:m,revertThemePreview:h,setPaletteIndex:v,setThemeName:x,paletteIndex:g,applyTheme:R,leaderTimer:_,setOverlay:S,runCommand:y,setConfigEditError:b,saveConfigBuffer:w,setSlashIndex:k,filtered:L,setQuery:O,slashIndex:F,submitSlash:Z,advanceWizard:W}=e,C=(t.name??"").toLowerCase();if(r==="none")return!1;if(r==="setup-wizard"){if(C==="escape")return i(),!0;if(C==="return")return W?.(),!0;return Kp(r)}if(r==="confirm"){if(C==="escape")return i(),!0;if(s==="quit"&&C==="d")return a(!0),!0;if(s==="quit"&&C==="k")return e.onDown(!0),!0;if(s==="restart-cascade"&&C==="c")return c("cascade"),!0;if(C==="return")c();return!0}if(r==="log-details"||r==="scroll-text"){if(an(t,n))return mi(e),!0;if(C==="escape")return i(),!0;if(r==="log-details"&&C==="return"){let B=e.logDetail?.request_id?.trim()??"";if(B!=="")e.setLogSearch(B),e.setScreen("logs"),e.closeOverlay(),e.setStatus(`tracing ${B}`);return!0}let j=r==="log-details"?d.current:u.current;if(C==="down"||C==="j")return rt(j,n.scroll_speed),!0;if(C==="up"||C==="k")return rt(j,-n.scroll_speed),!0;if(As(t))return rt(j,li(o)),!0;if(Ls(t))return rt(j,-li(o)),!0;return!0}if(r==="route-details"){if(an(t,n))return mi(e),!0;if(C==="escape"||C==="return")return i(),!0;if(C==="down"||C==="j")return rt(f.current,n.scroll_speed),!0;if(C==="up"||C==="k")return rt(f.current,-n.scroll_speed),!0;return!0}if(r==="plan"){if(an(t,n))return mi(e),!0;if(C==="escape"||C==="return"&&!l)return i(),!0;if(C==="down"||C==="j")return rt(p.current,n.scroll_speed),!0;if(C==="up"||C==="k")return rt(p.current,-n.scroll_speed),!0;return!0}if(r==="help"){if(an(t,n))return mi(e),!0;if(C==="escape")return i(),!0;if(C==="down"||C==="j")return rt(m.current,1),!0;if(C==="up"||C==="k")return rt(m.current,-1),!0;if(C==="pagedown"||As(t))return rt(m.current,df),!0;if(C==="pageup"||Ls(t))return rt(m.current,-df),!0;return!0}if(r==="themes"){if(an(t,n))return h(),mi(e),!0;if(C==="escape")return h(),i(),!0;if(C==="down"||C==="j")return v((j)=>{let B=Math.min(j+1,St.length-1),z=St[B];if(z)x(z);return B}),!0;if(C==="up"||C==="k")return v((j)=>{let B=Math.max(0,j-1),z=St[B];if(z)x(z);return B}),!0;if(C==="return"){let j=St[g%St.length];if(j)R(j)}return!0}if(r==="leader"){if(an(t,n)){if(_.current)clearTimeout(_.current);return mi(e),!0}if(_.current)clearTimeout(_.current);S("none");let j=db(C),B=j?Wr(j):void 0;if(B)y(B,[]);return!0}if(r==="config-edit"){if(C==="escape")return b(""),i(),!0;if(Or(t)&&C==="s")return w(),!0;return!0}if(Kp(r)){if(C==="escape")return i(),!0;if(r==="slash"&&C==="down")return k((j)=>Math.min(Math.max(L.length-1,0),j+1)),!0;if(r==="slash"&&C==="up")return k((j)=>Math.max(0,j-1)),!0;if(r==="slash"&&C==="tab"){if(t.shift)return k((B)=>Math.max(0,B-1)),!0;let j=di(L,F);if(j)O(wb(j));return!0}if(r==="slash"&&C==="return")return Z(),!0;return!0}return!0}var hw=D(()=>{vr();So();ml();_r();ke();uf();tt()});var vl=D(()=>{Xu()});function gw(e,t){if(t.length===0)return[];let r=new Set(t);return lo(e,t).filter((n)=>!r.has(n))}function vw(e,t,r){let n=r!==""&&e.profiles[r]?r:"";if(t.length>0)return{services:t,profile:n};if(n!=="")return{services:[...e.profiles[r]?.services??[]],profile:n};return{services:Object.keys(e.services),profile:""}}function yl(e){return e.waves.flat().join(" \u2192 ")}function yw(e){let t=yl(e);if(t==="")return"Started selected services";return`Started ${t}`}function xw(e){let t=yl(e);if(t==="")return"Stopped running services";return`Stopped ${t}`}function bw(e,t,r,n="start"){let o=r?.services[e],i=tr(o);if(o?.last_error&&n!=="stop")return o.last_error;if(n==="stop"){if(i==="STOPPED")return"stopped";if(i==="STOPPING")return"stopping now";if(i==="FAILED")return"already failed";return"queued"}if(i==="HEALTHY")return"ready";if(i==="STARTING")return"starting now";if(i==="RUNNING")return"up, waiting for health";if(i==="UNHEALTHY")return"unhealthy \u2014 retrying health check";if(i==="STOPPING")return"stopping first";if(i==="FAILED")return"failed \u2014 later waves will not start";let a=(t.steps.find((c)=>c.name===e)?.dependencies??[]).filter((c)=>{let l=tr(r?.services[zt(c)]);return gn(c)==="service_healthy"?l!=="HEALTHY":l==="STOPPED"||l==="FAILED"}).map(zt);if(a.length>0)return`waits for ${a.join(", ")}`;return"queued"}function Os(e,t,r="start"){if(e.length===0)return"completed";let n=e.map((i)=>t?.services[i]);if(n.some((i)=>i?.state==="FAILED"))return"failed";if(r==="stop"){if(n.every((i)=>!i||i.state==="STOPPED"||i.state==="UNKNOWN"))return"completed";if(n.some((i)=>i?.state==="STOPPING"))return"active";return"queued"}let o=(i)=>Boolean(i&&(i.state==="HEALTHY"||i.health==="HEALTHY"||i.state==="RUNNING"&&i.health!=="UNHEALTHY"));if(n.every(o))return"completed";if(n.some((i)=>i?.state==="UNHEALTHY"||i?.health==="UNHEALTHY"))return"unhealthy";if(n.some((i)=>i?.state==="STARTING"||i?.state==="RUNNING"||i?.state==="RESTARTING"))return"active";return"queued"}function ww(e,t,r="start"){let o=e.waves.flat().length,i=0,s=0,a=0,c=0;for(let m=0;m<e.waves.length;m++){let h=e.waves[m]??[],v=Os(h,t,r);if(v==="active"||v==="unhealthy"||v==="failed")c=m;else if(v==="completed"&&c===m&&m<e.waves.length-1)c=m+1;for(let x of h){let g=t?.services[x];if(r==="stop"?!g||g.state==="STOPPED"||g.state==="UNKNOWN":Boolean(g&&(g.state==="HEALTHY"||g.health==="HEALTHY"||g.state==="RUNNING"&&g.health!=="UNHEALTHY")))i++;else if(g?.state==="FAILED")s++;else if(g?.state==="STARTING"||g?.state==="RUNNING"||g?.state==="UNHEALTHY"||g?.health==="UNHEALTHY"||g?.state==="STOPPING"||g?.state==="RESTARTING")a++}}let l=o>0?Math.round(i/o*100):100,d=16,u=Math.round(l/100*d),f="\u2588".repeat(u)+"\u2591".repeat(Math.max(0,d-u)),p=i===o&&o>0;return{total:o,ready:i,active:a,failed:s,percent:l,progressBar:f,currentWaveIndex:c,totalWaves:e.waves.length,isComplete:p}}function Sw(e,t,r){let n=r==="completed"?"\u2713 Completed":r==="failed"?"\u2717 Failed":r==="unhealthy"?"\u26A0 Unhealthy":r==="active"?"\u23F3 In Progress":"\u25CB Queued";return`${e==="stop"?t===0?"Wave 1 (Stop First)":`Wave ${t+1}`:t===0?"Wave 1 (Start First)":`Wave ${t+1}`} \xB7 ${n}`}function kw(e,t,r,n){let o=n?` \xB7 Profile ${n}`:"";if(r)return`${e==="stop"?"Shutdown":"Startup"} Failed (${r})`;if(t)return`${e==="stop"?"Stopping Services":e==="restart"?"Restarting Pipeline":"Starting Pipeline"}${o}`;return`${e==="stop"?"Shutdown Complete":"Startup Complete"}${o}`}function Cw(e,t){if(e)return{primary:"Working\u2026 esc hide this panel (keeps running in background)",secondary:"Services are transitioning. You can dismiss anytime without stopping them."};if(t)return{primary:"enter or esc back to dashboard",secondary:"Execution stopped. Check logs or open Doctor to resolve errors."};return{primary:"enter or esc back to dashboard",secondary:"All waves finished. Click here or press enter to continue."}}var xl=D(()=>{ht();Tt();At()});function Pw(e,t){let r=(t.name??"").toLowerCase();if(e.screen==="mcp"&&e.listCursor===1&&r.length===1&&r>="0"&&r<="9")return e.setMcpPortDraft((n)=>Py(n,r)),!0;return!1}function Rw(e,t){let r=(t.name??"").toLowerCase(),{screen:n,overlay:o,tui:i,cfg:s,controller:a,profile:c,listCursor:l,names:d,listCount:u,height:f,checked:p,detailName:m,bootErrorMissing:h,bootError:v,logSearchFocused:x,logSources:g,logService:R,logShowTimestamps:_,logShowMeta:S,logWrap:y,showSystemLogs:b,logPinned:w,dashboardLogCursor:k,applyLogCursor:L,applyDashboardLogCursor:O,jumpToLatestLogs:F,toggleSystemLogs:Z,clearLogs:W,setLogService:C,setLogShowTimestamps:j,setLogShowMeta:B,setPaused:z,setErrorOnly:de,setLogWrap:Ee,beginStart:Ne,beginStop:ne,beginRestart:H,applyMcpPortDraft:ae,toggleMcp:X,toggleMcpTool:nt,copyFocusedMcpSnippet:ft,persistMcpPort:ie,restartMcpOnPort:Le,setMcpPortDraft:Me,settingRows:Ke,persistPrefs:gt,cycleSetting:Xe,toggleMouse:Ct,setDoctorTick:ge,refreshAuth:vt,configScrollRef:at,detailScrollRef:yt,handleEnter:rr,setScreen:Qe,setChecked:Xt,setStatus:qe,setSelected:mt,setProfile:xr,refresh:Kt,toggleChecked:br,createStarterConfig:nr,startWizard:qt,setConfirmKind:or,setConfirmDetail:ir,setOverlay:sr,openConfigBuffer:Vt,runCommand:ce}=e,K=(n==="logs"||n==="dashboard")&&!x;if(K&&(r==="left"||r==="["||n==="logs"&&r==="h")){C(Ap(g,R,-1));return}if(K&&(r==="right"||r==="]"||n==="logs"&&r==="l")){C(Ap(g,R,1));return}if((n==="dashboard"||n==="setup")&&r==="o"){Qe("profiles");return}if((n==="dashboard"||n==="services")&&(r==="*"||t.shift&&r==="8")){Xt([...d]),qe(`Selected ${d.length} services`);return}if((n==="dashboard"||n==="services")&&(r==="-"||r==="_"||r==="minus")){Xt([]),qe("Selection cleared");return}if((n==="dashboard"||n==="services")&&r==="n"){Ne(jc(p,d[l]??""),c);return}if((n==="dashboard"||n==="services")&&r==="x"){ne(jc(p,d[l]??""));return}if((n==="dashboard"||n==="services"||n==="detail")&&tw(t)){let T=jc(p,d[l]??m);if(s&&gw(s,T).length>0){or("restart-cascade"),ir({services:T}),sr("confirm");return}H(T,c);return}if((n==="dashboard"||n==="services")&&r==="r"){Kt(),qe("Refreshed");return}if(n==="auth"&&r==="r"){vt();return}if(n==="logs"&&r==="t"){let T=!_;j(T),gt({log_timestamps:T},T?"timestamps on":"timestamps off");return}if(n==="logs"&&r==="m"){let T=!S;B(T),gt({log_metadata:T},T?"metadata on":"metadata off");return}if(r==="down"||r==="j"){if(n==="mcp"&&l===1)ae();if(n==="config"){if(u>0)mt((T)=>Math.min(Math.max(u-1,0),T+1));rt(at.current,i.scroll_speed);return}if(n==="detail"){rt(yt.current,i.scroll_speed);return}if(n==="logs"){L(l+1);return}if(n==="dashboard"&&k>=0){O(k+1);return}mt((T)=>Math.min(Math.max(u-1,0),T+1));return}if(r==="up"||r==="k"){if(n==="mcp"&&l===1)ae();if(n==="config"){if(u>0)mt((T)=>Math.max(0,Math.min(T,Math.max(u-1,0))-1));rt(at.current,-i.scroll_speed);return}if(n==="detail"){rt(yt.current,-i.scroll_speed);return}if(n==="logs"){L(l-1);return}if(n==="dashboard"&&k>=0){O(k-1);return}mt((T)=>Math.max(0,Math.min(T,Math.max(u-1,0))-1));return}if(As(t)){let T=li(f);if(n==="config"){rt(at.current,T);return}if(n==="detail"){rt(yt.current,T);return}if(n==="logs"){L(l+T);return}if(n==="dashboard"&&(w||k>=0)){O(Math.max(0,k)+T);return}mt((ve)=>Math.min(Math.max(u-1,0),ve+T));return}if(Ls(t)){let T=li(f);if(n==="config"){rt(at.current,-T);return}if(n==="detail"){rt(yt.current,-T);return}if(n==="logs"){L(l-T);return}if(n==="dashboard"&&(w||k>=0)){O(Math.max(0,k)-T);return}mt((ve)=>Math.max(0,ve-T));return}if(n==="proxy"&&r==="n"){a?.proxyStart().then(async()=>{qe("Proxy started"),await Kt()});return}if(n==="proxy"&&r==="x"){a?.proxyStop().then(async()=>{qe("Proxy stopped"),await Kt()});return}if(n==="detail"&&m!==""){if(r==="n"){Ne([m],c);return}if(r==="x"){ne([m]);return}if(r==="o"){Qe("config");return}if(r==="l"){C(m),Qe("logs");return}}if(n==="mcp"&&l===1&&(r==="left"||r==="h")){let T=_n(ae()-1);ie(T),Le(T);return}if(n==="mcp"&&l===1&&(r==="right"||r==="l")){let T=_n(ae()+1);ie(T),Le(T);return}if(n==="mcp"&&l===1&&(r==="backspace"||r==="delete")){Me((T)=>Ry(T));return}if(n==="settings"&&(r==="left"||r==="h")){Xe(-1,l);return}if(n==="settings"&&(r==="right"||r==="l")){Xe(1,l);return}if(r==="space"){if(n==="profiles"){let Y=Object.keys(s?.profiles??{}).sort()[l];if(Y)xr(Y),qe(`Profile ${Y}`);return}if(n==="mcp"){if(l===0){X();return}let ve=Is(l);if(ve){nt(ve);return}ft(l);return}if(n==="logs")return;if(n==="settings"){if(Ln(Ke,l)?.id==="mouse")Ct();return}let T=d[l];if(T)br(T);return}if(r==="return"){if(n==="setup"&&!a){if(!h){qe(v||"Existing configuration is invalid \u2014 fix it and restart devctl.");return}qt();return}if(n==="setup"&&a){Ne([],c);return}rr();return}if(Mn(t,i,"services","s")&&o==="none"&&n!=="logs"){Qe("services");return}if(Mn(t,i,"logs","l")&&o==="none"&&n!=="detail"&&n!=="logs"&&n!=="settings"){Qe("logs");return}if(Mn(t,i,"auth","a")&&o==="none"){Qe("auth");return}if(Mn(t,i,"proxy","p")&&o==="none"&&n!=="logs"){Qe("proxy");return}if(Mn(t,i,"doctor","d")&&o==="none"&&n!=="doctor"){Qe("doctor");return}if(Mn(t,i,"config","c")&&o==="none"){Qe("config");return}if(Mn(t,i,"setup","u")&&o==="none"){Qe("setup");return}if(n==="config"&&r==="e"){ce(Wr("/edit")??{name:"edit",aliases:[],desc:"",leader:"",group:"ui"},[]);return}if(n==="config"&&r==="v"){Vt();return}if(n==="doctor"&&r==="r"){ge((T)=>T+1),qe("Re-running doctor");return}if(n==="logs"&&r==="p"){z((T)=>!T);return}if((n==="logs"||n==="dashboard")&&r==="g"){F();return}if((n==="logs"||n==="dashboard")&&r==="w"){let T=Xc(y);Ee(T),qe(`Log ${Zc(T)}`);return}if((n==="logs"||n==="dashboard")&&r==="e"){de((T)=>!T);return}if((n==="logs"||n==="dashboard")&&r==="i"){Z(),qe(b?"Hiding internal auth/mcp/devctl/proxy logs":"Showing internal logs");return}if((n==="logs"||n==="dashboard")&&rw(t))W()}var _w=D(()=>{vl();vr();gr();xl();At();ml();ke();Ms();Er()});import{useKeyboard as FI}from"@opentui/react";import{useCallback as pf}from"react";function Ew({tui:e,controller:t,cfg:r,snap:n,ui:o,logView:i,lifecycleActions:s,mcp:a,preferences:c,diagnostics:l,refs:d}){let{screen:u,onQuit:f,closeOverlay:p,confirmKind:m,confirmDetail:h,portTarget:v,profile:x,listCursor:g,names:R,runCommand:_,copySelection:S,setOverlay:y,setConfirmKind:b,setConfirmDetail:w,setPortTarget:k,setLogDetail:L,setProfile:O,setStatus:F,setSlashIndex:Z,setQuery:W,setSlashPicker:C,setScreen:j,freePort:B,openDetail:z}=o,{logSlice:de,logSliceB:Ee,logSearchFocused:Ne,logsFullscreen:ne,setLogSearchFocused:H,setLogsFullscreen:ae,splitLogs:X,splitFocus:nt,toggleSplitLogs:ft,cycleSplitFocus:ie,setActiveLogService:Le,logService:Me,logServiceB:Ke}=i,{beginStart:gt,beginRestart:Xe,planBusy:Ct}=s,{applyMcpPortDraft:ge,toggleMcp:vt,toggleMcpTool:at,copyFocusedMcpSnippet:yt}=a,{settingRows:rr,activateSetting:Qe,applyFont:Xt,applyReset:qe,fontSize:mt,revertThemePreview:xr,setThemeName:Kt,leaderMs:br}=c,{doctor:nr}=l,{interruptArmedAt:qt,leaderTimer:or}=d,ir=pf(()=>{if(r?.shutdown.stop_services_on_exit===void 0){b("quit"),y("confirm");return}f(r.shutdown.stop_services_on_exit===!1)},[r,f,b,y]),sr=pf((ce)=>{if(m==="quit"){f(!1);return}if(m==="reload"){p();let K=n?.restart_required??[];if(K.length>0)Xe(K,x);return}if(m==="restart-cascade"){let K=h?.services??[];p(),Xe(K,x,ce==="cascade");return}if(m==="reset-prefs"){p(),qe();return}if(m==="free-port"){let K=v;if(p(),!K)return;B(K).then(()=>{F(`Stopped ${K.command} (pid ${K.pid}) on port ${K.port}`),l.setDoctorTick((T)=>T+1)}).catch((T)=>{F(A(T))});return}p(),gt([],x)},[qe,Xe,gt,p,h,m,l,B,f,v,x,F,n]),Vt=pf(()=>{if(u==="logs"){let K=(X&&nt===1?Ee:de)[g];if(K)L(K),y("log-details");return}if(u==="profiles"){let T=Object.keys(r?.profiles??{}).sort()[g];if(T)O(T),b("start-profile"),y("confirm");return}if(u==="doctor"){let K=nr?.checks[g];if(K?.action?.kind==="free-port"){let T=K.action.holder;k(T),w({port:T.port,pid:T.pid,process:T.command}),b("free-port"),y("confirm")}return}if(u==="mcp"){if(g===0){vt();return}if(g===1){ge();return}let K=Is(g);if(K){at(K);return}yt(g);return}if(u==="settings"){let K=Ln(rr,g);if(K)Qe(K);return}if(u==="config"){let K=Object.keys(r?.tasks??{}).sort()[g],T=Wr("run");if(K&&T)_(T,[K]);return}if(u==="dashboard"&&Hc(n)){gt([],x);return}let ce=R[g];if(ce&&(u==="dashboard"||u==="services"))z(ce)},[Qe,ge,gt,r,yt,nr,g,de,Ee,R,z,x,_,u,w,b,L,y,k,O,rr,n,nt,X,vt,at]);FI((ce)=>{let K=(ce.name??"").toLowerCase();if(nw(ce,e)){S();return}if(Or(ce)&&(K==="="||K==="+"||K==="plus")){Xt(ys(mt,1));return}if(Or(ce)&&(K==="-"||K==="_"||K==="minus")){Xt(ys(mt,-1));return}if(Or(ce)&&K==="0"){Xt(Ic().font_size);return}if(u==="logs"&&!Ne&&(ce.sequence==="\\"||K==="\\")){ft(),F(X?"Single log pane":"Split log panes");return}if(u==="logs"&&!Ne&&(ce.sequence==="|"||K==="|")){ie();return}if(mw({...o,tui:e,confirmAction:sr,planBusy:Ct,leaderTimer:or,logDetailsScrollRef:d.logDetailsScrollRef,scrollTextScrollRef:d.scrollTextScrollRef,routeDetailsScrollRef:d.routeDetailsScrollRef,planScrollRef:d.planScrollRef,helpScrollRef:d.helpScrollRef,revertThemePreview:xr,setThemeName:Kt},ce))return;if(Ne){if(K==="escape")H(!1);return}if(Jb(ce,e)){y("leader"),or.current=setTimeout(()=>y("none"),br);return}if(Xb(ce,e)||an(ce,e)){W(""),Z(0),C("commands"),y("slash");return}if(Qb(ce,e)){y("help");return}if(Zb(ce,e)||u==="logs"&&K==="f"&&!ce.ctrl&&!ce.meta){j("logs"),H(!0);return}if(K==="escape"||ew(ce)){if(K==="escape"){if(ne){ae(!1);return}if(u==="detail"){j("services");return}if(u==="setup"&&t){j("dashboard");return}}let Y=Date.now();if(ow(Y,qt.current)){qt.current=0,ir();return}qt.current=Y,F(K==="q"?"q again to quit":"Esc again to quit");return}if(K==="z"&&(u==="logs"||u==="dashboard")&&!ce.ctrl&&!ce.meta){j("logs"),ae((Y)=>u==="logs"?!Y:!0);return}if(K==="tab"){j(ce.shift?yb(u):vb(u));return}let T={...o,tui:e,controller:t,cfg:r,snap:n,...i,...s,...a,...c,logService:X&&nt===1?Ke:Me,setLogService:Le,setDoctorTick:l.setDoctorTick,refreshAuth:l.refreshAuth,configScrollRef:d.configScrollRef,detailScrollRef:d.detailScrollRef,handleEnter:Vt};if(Pw(T,ce))return;let ve=Yx(K);if(ve){j(ve);return}Rw(T,ce)})}var Tw=D(()=>{te();So();In();At();ml();Ms();Er();_r();hw();_w()});import{parse as NI,stringify as HI}from"yaml";function bl(e,t="imported"){let r=NI(e);if(!r||typeof r!=="object"||Array.isArray(r))throw Error("compose file is not a mapping");let n=r,o=hi(n.services)?n.services:{},i=[];if(n.networks!==void 0)i.push({service:"(root)",field:"networks",reason:Aw.networks??"not modeled"});let s={},a=[];for(let[l,d]of Object.entries(o)){if(!hi(d))continue;let u=jI(l,d,i);if(s[l]=u.body,!u.container)a.push(l)}return{yaml:HI({version:1,project:{name:t},services:s,profiles:{default:{services:a}}}),dropped:i,services:Object.keys(s),profileServices:a}}function wl(e){let t=[e.yaml.trimEnd(),"","Dropped fields:"];if(e.dropped.length===0)t.push("(none)");else{t.push("service\tfield\treason");for(let r of e.dropped)t.push(`${r.service} ${r.field} ${r.reason}`)}if(e.profileServices.length<e.services.length)t.push(""),t.push("Container services were omitted from profile default so first start does not require Docker.");return`${t.join(`
603
2146
  `)}
604
- `}function tM(e,t,r){for(let f of Object.keys(t)){let p=uw[f];if(p)r.push({service:e,field:f,reason:p})}let n={},o=pw(t.command);if(o)n.command=o;let i=typeof t.working_dir==="string"?t.working_dir:typeof t.workingDir==="string"?t.workingDir:"";if(i!=="")n.working_dir=i;let s=rM(t.depends_on);if(s.length>0)n.dependencies=s;let a=nM(t.environment);if(Object.keys(a).length>0)n.environment={defaults:a};let c=oM(t.ports);if(c.host.length>0)n.ports=c.host;let l=sM(t.healthcheck);if(l)n.health=l;let d=typeof t.image==="string"?t.image:"",u=d!=="";if(u)n.container={image:d,...c.container&&Object.keys(c.container).length>0?{ports:c.container}:{},...Object.keys(a).length>0?{env:a}:{}};return{body:n,container:u}}function pw(e){if(typeof e==="string"&&e.trim()!=="")return["sh","-c",e];if(Array.isArray(e)&&e.every((t)=>typeof t==="string"))return e;return}function rM(e){if(Array.isArray(e))return e.filter((t)=>typeof t==="string");if(fi(e))return Object.keys(e);return[]}function nM(e){let t={};if(Array.isArray(e)){for(let r of e){if(typeof r!=="string")continue;let n=r.indexOf("=");if(n===-1)t[r]="";else t[r.slice(0,n)]=r.slice(n+1)}return t}if(fi(e))for(let[r,n]of Object.entries(e))t[r]=n===void 0||n===null?"":String(n);return t}function oM(e){let t=[],r={};if(!Array.isArray(e))return{host:t,container:r};let n=0;for(let o of e){let i=iM(o);if(!i)continue;n+=1;let s=n===1?"http":`port${n}`;t.push({name:s,value:i.host}),r[s]=i.container}return{host:t,container:r}}function iM(e){if(typeof e==="number"&&Number.isFinite(e))return{host:e,container:e};if(typeof e==="string"){let r=e.replace(/\/(tcp|udp)$/i,"").split(":"),n=Number(r[r.length-1]),o=r.length>1?Number(r[r.length-2]):n;if(!Number.isFinite(n)||!Number.isFinite(o))return;return{host:o,container:n}}if(fi(e)){let t=Number(e.published??e.target),r=Number(e.target??e.published);if(!Number.isFinite(t)||!Number.isFinite(r))return;return{host:t,container:r}}return}function sM(e){if(!fi(e)||e.disable===!0)return;let t=pw(e.test);if(!t||t[0]==="CMD"&&t.length===1||t[0]==="NONE")return;let r=t[0]==="CMD"||t[0]==="CMD-SHELL"?t.slice(1):t;if(r.length===0)return;return{type:"command",command:r}}function fi(e){return Boolean(e)&&typeof e==="object"&&!Array.isArray(e)}var uw;var of=D(()=>{uw={build:"not modeled; use a local command or pre-built image",networks:"not modeled",deploy:"not modeled",replicas:"not modeled",env_file:"per-service env_file is dropped; set environment.sources at the project level",volumes:"not modeled on import",labels:"not modeled",restart:"use service.restart in authoring",expose:"use ports",privileged:"not modeled",user:"not modeled",entrypoint:"not modeled; command is mapped when present",hostname:"not modeled",extra_hosts:"not modeled",cap_add:"not modeled",cap_drop:"not modeled",mem_limit:"not modeled",cpus:"not modeled",scale:"not modeled"}});function fw(e){let t=Object.keys(e.services).length,r=Object.keys(e.profiles).length,n=e.proxy.routes.length,o=Object.keys(e.tasks).length,i=[{text:e.project.name||"unnamed",tone:"primary"},{text:`schema ${e.version}`,tone:"muted"},{text:`${t} service${t===1?"":"s"}`,tone:t>0?"info":"idle"},{text:`${r} profile${r===1?"":"s"}`,tone:r>0?"info":"idle"},{text:e.proxy.enabled?"proxy on":"proxy off",tone:e.proxy.enabled?"success":"idle"}];if(n>0)i.push({text:`${n} route${n===1?"":"s"}`,tone:"info"});if(o>0)i.push({text:`${o} task${o===1?"":"s"}`,tone:"info"});return i}function mw(e,t){return[{label:"path",value:e.configPath||"\u2014"},{label:"repo",value:e.repoRoot||"\u2014"},{label:"devctl",value:t},{label:"schema",value:String(e.version)}]}function gw(e){return[{label:"project",value:e.google.project_id||"(unset)",tone:e.google.project_id?"text":"muted"},{label:"region",value:e.google.region||"(unset)",tone:e.google.region?"text":"muted"}]}function hw(e){let t=aM(e);return[{label:"exit",value:t,tone:t==="ask on quit"?"warning":"text"},{label:"grace",value:`${e.shutdown.grace_seconds}s`},{label:"refresh",value:`${cg(e.auth)}s`},{label:"env",value:e.environment.sources.join(", ")||"\u2014",tone:e.environment.sources.length>0?"text":"muted"},{label:"theme",value:e.ui.theme||"system"}]}function yw(e){let t=e.logs.persistence;return[{label:"memory",value:`${e.logs.max_memory_events} events`},{label:"persist",value:t.enabled?t.directory:"off",tone:t.enabled?"text":"muted"},{label:"keep",value:t.enabled?`${t.retention_days}d \xB7 ${t.max_session_logs||"\u221E"} sessions`:"\u2014"}]}function vw(e){return[{label:"listen",value:yn(e.proxy.listen),tone:e.proxy.enabled?"text":"muted"},{label:"token",value:dM(e),tone:e.proxy.token_endpoint.enabled?"text":"muted"}]}function xw(e){return Object.keys(e.services).sort().map((t)=>{let r=e.services[t]??gn();return{name:t,extends:r.extends||"\u2014",command:Ic(r),ports:ws(r),identity:Fc(r),health:$c(r),depends:r.dependencies.map(xa).join(", ")||"\u2014",restart:Nc(r)}})}function bw(e){return e.proxy.routes.map((t)=>({name:t.name||t.match.host||"(unnamed)",match:uM(t),upstream:t.upstream.url||"\u2014",auth:t.auth.type||"none",identity:pM(t)}))}function ww(e){return Object.keys(e.profiles).sort().map((t)=>{let r=e.profiles[t],n=Object.keys(r?.environment??{}).sort();return{name:t,services:r?.services.join(", ")||"\u2014",env:n.length>0?n.join(", "):"\u2014"}})}function Sw(e){return Object.keys(e.templates).sort().map((t)=>({name:t,summary:fM(e.templates[t])}))}function kw(e){return Object.keys(e.tasks).sort().map((t)=>{let r=e.tasks[t],n=r?.command.args.join(" ")||"\u2014",o=r?.dependencies.join(", ");return{name:t,summary:o?`${n} depends ${o}`:n}})}function Cw(e,t){let r=Cn(e);if(r.length===0)return"No provenance recorded for this configuration.";let n=new mr(e.secrets.extra_markers,e.secrets.extra_patterns);return r.map((o)=>{let i=typeof o.value==="string"?o.value:JSON.stringify(o.value),s=t?i:n.redactMap({[o.path]:i})[o.path],a=o.shadowed.length===0?"":`
2147
+ `}function jI(e,t,r){for(let f of Object.keys(t)){let p=Aw[f];if(p)r.push({service:e,field:f,reason:p})}let n={},o=Lw(t.command);if(o)n.command=o;let i=typeof t.working_dir==="string"?t.working_dir:typeof t.workingDir==="string"?t.workingDir:"";if(i!=="")n.working_dir=i;let s=BI(t.depends_on);if(s.length>0)n.dependencies=s;let a=UI(t.environment);if(Object.keys(a).length>0)n.environment={defaults:a};let c=WI(t.ports);if(c.host.length>0)n.ports=c.host;let l=KI(t.healthcheck);if(l)n.health=l;let d=typeof t.image==="string"?t.image:"",u=d!=="";if(u)n.container={image:d,...c.container&&Object.keys(c.container).length>0?{ports:c.container}:{},...Object.keys(a).length>0?{env:a}:{}};return{body:n,container:u}}function Lw(e){if(typeof e==="string"&&e.trim()!=="")return["sh","-c",e];if(Array.isArray(e)&&e.every((t)=>typeof t==="string"))return e;return}function BI(e){if(Array.isArray(e))return e.filter((t)=>typeof t==="string");if(hi(e))return Object.keys(e);return[]}function UI(e){let t={};if(Array.isArray(e)){for(let r of e){if(typeof r!=="string")continue;let n=r.indexOf("=");if(n===-1)t[r]="";else t[r.slice(0,n)]=r.slice(n+1)}return t}if(hi(e))for(let[r,n]of Object.entries(e))t[r]=n===void 0||n===null?"":String(n);return t}function WI(e){let t=[],r={};if(!Array.isArray(e))return{host:t,container:r};let n=0;for(let o of e){let i=GI(o);if(!i)continue;n+=1;let s=n===1?"http":`port${n}`;t.push({name:s,value:i.host}),r[s]=i.container}return{host:t,container:r}}function GI(e){if(typeof e==="number"&&Number.isFinite(e))return{host:e,container:e};if(typeof e==="string"){let r=e.replace(/\/(tcp|udp)$/i,"").split(":"),n=Number(r[r.length-1]),o=r.length>1?Number(r[r.length-2]):n;if(!Number.isFinite(n)||!Number.isFinite(o))return;return{host:o,container:n}}if(hi(e)){let t=Number(e.published??e.target),r=Number(e.target??e.published);if(!Number.isFinite(t)||!Number.isFinite(r))return;return{host:t,container:r}}return}function KI(e){if(!hi(e)||e.disable===!0)return;let t=Lw(e.test);if(!t||t[0]==="CMD"&&t.length===1||t[0]==="NONE")return;let r=t[0]==="CMD"||t[0]==="CMD-SHELL"?t.slice(1):t;if(r.length===0)return;return{type:"command",command:r}}function hi(e){return Boolean(e)&&typeof e==="object"&&!Array.isArray(e)}var Aw;var ff=D(()=>{Aw={build:"not modeled; use a local command or pre-built image",networks:"not modeled",deploy:"not modeled",replicas:"not modeled",env_file:"per-service env_file is dropped; set environment.sources at the project level",volumes:"not modeled on import",labels:"not modeled",restart:"use service.restart in authoring",expose:"use ports",privileged:"not modeled",user:"not modeled",entrypoint:"not modeled; command is mapped when present",hostname:"not modeled",extra_hosts:"not modeled",cap_add:"not modeled",cap_drop:"not modeled",mem_limit:"not modeled",cpus:"not modeled",scale:"not modeled"}});function Dw(e){let t=Object.keys(e.services).length,r=Object.keys(e.profiles).length,n=e.proxy.routes.length,o=Object.keys(e.tasks).length,i=[{text:e.project.name||"unnamed",tone:"primary"},{text:`schema ${e.version}`,tone:"muted"},{text:`${t} service${t===1?"":"s"}`,tone:t>0?"info":"idle"},{text:`${r} profile${r===1?"":"s"}`,tone:r>0?"info":"idle"},{text:e.proxy.enabled?"proxy on":"proxy off",tone:e.proxy.enabled?"success":"idle"}];if(n>0)i.push({text:`${n} route${n===1?"":"s"}`,tone:"info"});if(o>0)i.push({text:`${o} task${o===1?"":"s"}`,tone:"info"});return i}function Iw(e,t){return[{label:"path",value:e.configPath||"\u2014"},{label:"repo",value:e.repoRoot||"\u2014"},{label:"devctl",value:t},{label:"schema",value:String(e.version)}]}function Mw(e){return[{label:"project",value:e.google.project_id||"(unset)",tone:e.google.project_id?"text":"muted"},{label:"region",value:e.google.region||"(unset)",tone:e.google.region?"text":"muted"}]}function Ow(e){let t=qI(e);return[{label:"exit",value:t,tone:t==="ask on quit"?"warning":"text"},{label:"grace",value:`${e.shutdown.grace_seconds}s`},{label:"refresh",value:`${gh(e.auth)}s`},{label:"env",value:e.environment.sources.join(", ")||"\u2014",tone:e.environment.sources.length>0?"text":"muted"},{label:"theme",value:e.ui.theme||"system"}]}function $w(e){let t=e.logs.persistence;return[{label:"memory",value:`${e.logs.max_memory_events} events`},{label:"persist",value:t.enabled?t.directory:"off",tone:t.enabled?"text":"muted"},{label:"keep",value:t.enabled?`${t.retention_days}d \xB7 ${t.max_session_logs||"\u221E"} sessions`:"\u2014"}]}function Fw(e){return[{label:"listen",value:xn(e.proxy.listen),tone:e.proxy.enabled?"text":"muted"},{label:"token",value:YI(e),tone:e.proxy.token_endpoint.enabled?"text":"muted"}]}function Nw(e){return Object.keys(e.services).sort().map((t)=>{let r=e.services[t]??vn();return{name:t,extends:r.extends||"\u2014",command:Bc(r),ports:Rs(r),identity:Wc(r),health:Uc(r),depends:r.dependencies.map(Pa).join(", ")||"\u2014",restart:Gc(r)}})}function Hw(e){return e.proxy.routes.map((t)=>({name:t.name||t.match.host||"(unnamed)",match:JI(t),upstream:t.upstream.url||"\u2014",auth:t.auth.type||"none",identity:XI(t)}))}function jw(e){return Object.keys(e.profiles).sort().map((t)=>{let r=e.profiles[t],n=Object.keys(r?.environment??{}).sort();return{name:t,services:r?.services.join(", ")||"\u2014",env:n.length>0?n.join(", "):"\u2014"}})}function Bw(e){return Object.keys(e.templates).sort().map((t)=>({name:t,summary:QI(e.templates[t])}))}function Uw(e){return Object.keys(e.tasks).sort().map((t)=>{let r=e.tasks[t],n=r?.command.args.join(" ")||"\u2014",o=r?.dependencies.join(", ");return{name:t,summary:o?`${n} depends ${o}`:n}})}function Ww(e,t){let r=Pn(e);if(r.length===0)return"No provenance recorded for this configuration.";let n=new mr(e.secrets.extra_markers,e.secrets.extra_patterns);return r.map((o)=>{let i=typeof o.value==="string"?o.value:JSON.stringify(o.value),s=t?i:n.redactMap({[o.path]:i})[o.path],a=o.shadowed.length===0?"":`
605
2148
  shadowed ${o.shadowed.map((c)=>`${c.layer} \xB7 ${c.source}`).join("; ")}`;return`${o.path}
606
2149
  ${o.layer} \xB7 ${o.source}
607
2150
  ${s??""}${a}`}).join(`
608
2151
 
609
- `)}function Rw(e){let t=e.doctor.tools.map((i)=>i.name||i.command).filter((i)=>i!==""),r=e.plugins.map((i)=>i.path).filter((i)=>i!==""),n=Object.keys(e.ui.keymap),o=[...e.secrets.extra_markers,...e.secrets.extra_patterns];return[{label:"doctor",value:t.join(", ")||"\u2014",tone:t.length>0?"text":"muted"},{label:"plugins",value:r.join(", ")||"\u2014",tone:r.length>0?"text":"muted"},{label:"keymap",value:n.length>0?`${n.length} override${n.length===1?"":"s"}`:"defaults",tone:n.length>0?"text":"muted"},{label:"secrets",value:o.length>0?o.join(", "):"defaults",tone:o.length>0?"text":"muted"}]}function aM(e){if(e.shutdown.stop_services_on_exit===void 0)return"ask on quit";return e.shutdown.stop_services_on_exit?"stop services":"detach"}function Pw(e){let t=e.reduce((r,n)=>Math.max(r,n.name.length),0);return Math.min(lM,Math.max(cM,t))}function dM(e){let t=e.proxy.token_endpoint;if(!t.enabled)return"off";let r=t.host===""?"127.0.0.1":t.host;if(t.port===0)return r;return`${r}:${t.port}`}function uM(e){let t=e.match.host||"*",r=e.match.path;if(r==="")return t;return`${t}${r.startsWith("/")?r:`/${r}`}`}function pM(e){if(e.auth.identity.service_account!=="")return e.auth.identity.service_account;if(e.auth.service_account!=="")return e.auth.service_account;return e.auth.identity.type||"user"}function fM(e){if(!e)return"empty";let t=[];if(e.health.type!=="")t.push(`health ${e.health.type}`);if(e.restart.policy!=="")t.push(`restart ${e.restart.policy}`);if(e.logs.stdout||e.logs.stderr)t.push("logs");return t.join(" \xB7 ")||"empty"}var cM=8,lM=20,_w=80,mi=10;var sf=D(()=>{mt();ti();At()});async function hl(e,t){e.suspend();try{return await t()}finally{e.resume(),e.intermediateRender()}}import{useCallback as mM,useRef as gM}from"react";function Ew({setOverlay:e,setQuery:t,setSlashPicker:r,checked:n,setConfirmKind:o,setStatus:i,persistTheme:s,setPaletteIndex:a,themeName:c,setScreen:l,setSelected:d,screen:u,renderer:f,cfg:p,setScrollText:m,reveal:g,controller:y,refresh:x,profile:h,setReveal:P,resolveEnvironment:_,openDetail:w,copySelection:v,lastExportPath:b,openConfigBuffer:S,onDown:k,workspace:L,logView:I,diagnostics:F,lifecycleActions:Q}){let{loginGoogle:G,detectGoogle:C,logoutGoogle:B,bootstrapLogPath:W,fileExists:V,readTextFile:de,writeTextFile:Ee,validateConfigText:$e,resolveExportPath:ne,writeLogExport:H,exportsDir:ae,openInFileManager:X,listSessions:nt,loadSessionEvents:pt,checkUpdate:ie,applyUpdate:Ae,formatUpdateStatus:De}=L,{setLogService:Ke,setLogsFullscreen:ht,setPaused:Ye,setErrorOnly:Ct,toggleSystemLogs:he,clearLogs:yt,logWrap:st,setLogWrap:vt,logServices:rr,errorOnly:Xe,logLevel:Xt,logSearch:qe,logRegex:ft,logSource:vr,filteredLogs:Kt,setLogRegex:xr,setLogSince:nr,setLogUntil:qt,setLogs:or,setLogSearch:ir,toggleSplitLogs:sr}=I,{refreshAuth:Vt,setGoogle:ce}=F,{beginStart:U,beginStop:T,beginRestart:ye}=Q,z=gM(!1);return{runCommand:mM(async(at,Ce)=>{if(z.current)return;if(z.current=!0,at.name!=="start"&&at.name!=="stop"&&at.name!=="restart")e("none");t("");let So=yp(Ce,n);try{switch(at.name){case"exit":o("quit"),e("confirm");return;case"down":{let R=Ce.includes("--keep-services")||Ce.includes("-k");k(R);return}case"help":e("help");return;case"version":i(er()),ie().then((R)=>i(De(R))).catch((R)=>i(A(R)));return;case"update":i("checking for update\u2026"),ie().then(async(R)=>{if(!R.newer||!R.command){i(De(R));return}i(`installing ${R.current} \u2192 ${R.latest} via ${R.kind}\u2026`);let ue=await Ae(R.command);if(ue.code!==0){i(`update failed (${ue.code}): ${(ue.stderr||ue.stdout).trim()||R.hint}`);return}i(`updated to ${R.latest}; ${sc}`)}).catch((R)=>i(A(R)));return;case"themes":if(Ce[0]){s(Ce[0]);return}a(Math.max(0,St.indexOf(c))),t(""),e("themes");return;case"settings":l("settings"),d(0);return;case"dashboard":case"services":l(at.name);return;case"logs":if(Ce[0])Ke(Ce[0]);l("logs");return;case"fullscreen":l("logs"),ht((R)=>u==="logs"?!R:!0);return;case"split":l("logs"),sr(),i("Split log panes");return;case"trace":{let R=Ce.join(" ").trim();if(R===""){i("usage: /trace <request-or-trace-id>");return}ir(R),l("logs"),i(`tracing ${R}`);return}case"auth":{let R=(Ce[0]??"").toLowerCase();if(R==="refresh"){l("auth"),await Vt();return}if(R==="login"){l("auth"),i("starting gcloud ADC login\u2026");try{await hl(f,()=>G()),ce(await C(p?.google.project_id??"")),await Vt(),i("ADC login complete")}catch(ue){i(A(ue))}return}if(R==="logout"){l("auth"),i("revoking ADC\u2026");try{await B(),ce(await C(p?.google.project_id??"")),await Vt(),i("ADC revoked")}catch(ue){i(A(ue))}return}l("auth");return}case"credentials":case"proxy":case"mcp":case"config":case"profiles":case"setup":l(at.name);return;case"import":{let R=Ce[0]??"",ue=Ce.includes("--write"),Vr=Ce.find((se)=>se!=="compose"&&se!=="--write")??"compose.yaml";if(R!=="compose"){i("usage: /import compose [path] [--write]");return}if(!V(Vr)){i(`compose file not found: ${Vr}`);return}let Jr=p?.repoRoot||process.cwd(),br=p?.configPath||`${Jr}/.devctl/config.yaml`;try{let se=ml(de(Vr),Jr.split("/").pop()??"imported"),Hn=$e(Jr,br,se.yaml),Bn=`${gl(se)}${Hn.length>0?`
2152
+ `)}function Gw(e){let t=e.doctor.tools.map((i)=>i.name||i.command).filter((i)=>i!==""),r=e.plugins.map((i)=>i.path).filter((i)=>i!==""),n=Object.keys(e.ui.keymap),o=[...e.secrets.extra_markers,...e.secrets.extra_patterns];return[{label:"doctor",value:t.join(", ")||"\u2014",tone:t.length>0?"text":"muted"},{label:"plugins",value:r.join(", ")||"\u2014",tone:r.length>0?"text":"muted"},{label:"keymap",value:n.length>0?`${n.length} override${n.length===1?"":"s"}`:"defaults",tone:n.length>0?"text":"muted"},{label:"secrets",value:o.length>0?o.join(", "):"defaults",tone:o.length>0?"text":"muted"}]}function qI(e){if(e.shutdown.stop_services_on_exit===void 0)return"ask on quit";return e.shutdown.stop_services_on_exit?"stop services":"detach"}function Kw(e){let t=e.reduce((r,n)=>Math.max(r,n.name.length),0);return Math.min(zI,Math.max(VI,t))}function YI(e){let t=e.proxy.token_endpoint;if(!t.enabled)return"off";let r=t.host===""?"127.0.0.1":t.host;if(t.port===0)return r;return`${r}:${t.port}`}function JI(e){let t=e.match.host||"*",r=e.match.path;if(r==="")return t;return`${t}${r.startsWith("/")?r:`/${r}`}`}function XI(e){if(e.auth.identity.service_account!=="")return e.auth.identity.service_account;if(e.auth.service_account!=="")return e.auth.service_account;return e.auth.identity.type||"user"}function QI(e){if(!e)return"empty";let t=[];if(e.health.type!=="")t.push(`health ${e.health.type}`);if(e.restart.policy!=="")t.push(`restart ${e.restart.policy}`);if(e.logs.stdout||e.logs.stderr)t.push("logs");return t.join(" \xB7 ")||"empty"}var VI=8,zI=20,qw=80,gi=10;var mf=D(()=>{ht();ni();At()});async function Sl(e,t){e.suspend();try{return await t()}finally{e.resume(),e.intermediateRender()}}import{useCallback as ZI,useRef as eM}from"react";function Vw({setOverlay:e,setQuery:t,setSlashPicker:r,checked:n,setConfirmKind:o,setStatus:i,persistTheme:s,setPaletteIndex:a,themeName:c,setScreen:l,setSelected:d,screen:u,renderer:f,cfg:p,setScrollText:m,reveal:h,controller:v,refresh:x,profile:g,setReveal:R,resolveEnvironment:_,openDetail:S,copySelection:y,lastExportPath:b,openConfigBuffer:w,onDown:k,workspace:L,logView:O,diagnostics:F,lifecycleActions:Z}){let{loginGoogle:W,detectGoogle:C,logoutGoogle:j,bootstrapLogPath:B,fileExists:z,readTextFile:de,writeTextFile:Ee,validateConfigText:Ne,resolveExportPath:ne,writeLogExport:H,exportsDir:ae,openInFileManager:X,listSessions:nt,loadSessionEvents:ft,checkUpdate:ie,applyUpdate:Le,formatUpdateStatus:Me}=L,{setLogService:Ke,setLogsFullscreen:gt,setPaused:Xe,setErrorOnly:Ct,toggleSystemLogs:ge,clearLogs:vt,logWrap:at,setLogWrap:yt,logServices:rr,errorOnly:Qe,logLevel:Xt,logSearch:qe,logRegex:mt,logSource:xr,filteredLogs:Kt,setLogRegex:br,setLogSince:nr,setLogUntil:qt,setLogs:or,setLogSearch:ir,toggleSplitLogs:sr}=O,{refreshAuth:Vt,setGoogle:ce}=F,{beginStart:K,beginStop:T,beginRestart:ve}=Z,Y=eM(!1);return{runCommand:ZI(async(ct,Ce)=>{if(Y.current)return;if(Y.current=!0,ct.name!=="start"&&ct.name!=="stop"&&ct.name!=="restart")e("none");t("");let Ro=Pp(Ce,n);try{switch(ct.name){case"exit":o("quit"),e("confirm");return;case"down":{let P=Ce.includes("--keep-services")||Ce.includes("-k");k(P);return}case"help":e("help");return;case"version":i(er()),ie().then((P)=>i(Me(P))).catch((P)=>i(A(P)));return;case"update":i("checking for update\u2026"),ie().then(async(P)=>{if(!P.newer||!P.command){i(Me(P));return}i(`installing ${P.current} \u2192 ${P.latest} via ${P.kind}\u2026`);let ue=await Le(P.command);if(ue.code!==0){i(`update failed (${ue.code}): ${(ue.stderr||ue.stdout).trim()||P.hint}`);return}i(`updated to ${P.latest}; ${pc}`)}).catch((P)=>i(A(P)));return;case"themes":if(Ce[0]){s(Ce[0]);return}a(Math.max(0,St.indexOf(c))),t(""),e("themes");return;case"settings":l("settings"),d(0);return;case"dashboard":case"services":l(ct.name);return;case"logs":if(Ce[0])Ke(Ce[0]);l("logs");return;case"fullscreen":l("logs"),gt((P)=>u==="logs"?!P:!0);return;case"split":l("logs"),sr(),i("Split log panes");return;case"trace":{let P=Ce.join(" ").trim();if(P===""){i("usage: /trace <request-or-trace-id>");return}ir(P),l("logs"),i(`tracing ${P}`);return}case"auth":{let P=(Ce[0]??"").toLowerCase();if(P==="refresh"){l("auth"),await Vt();return}if(P==="login"){l("auth"),i("starting gcloud ADC login\u2026");try{await Sl(f,()=>W()),ce(await C(p?.google.project_id??"")),await Vt(),i("ADC login complete")}catch(ue){i(A(ue))}return}if(P==="logout"){l("auth"),i("revoking ADC\u2026");try{await j(),ce(await C(p?.google.project_id??"")),await Vt(),i("ADC revoked")}catch(ue){i(A(ue))}return}l("auth");return}case"credentials":case"proxy":case"mcp":case"config":case"profiles":case"setup":l(ct.name);return;case"import":{let P=Ce[0]??"",ue=Ce.includes("--write"),Yr=Ce.find((se)=>se!=="compose"&&se!=="--write")??"compose.yaml";if(P!=="compose"){i("usage: /import compose [path] [--write]");return}if(!z(Yr)){i(`compose file not found: ${Yr}`);return}let Jr=p?.repoRoot||process.cwd(),wr=p?.configPath||`${Jr}/.devctl/config.yaml`;try{let se=bl(de(Yr),Jr.split("/").pop()??"imported"),Bn=Ne(Jr,wr,se.yaml),Un=`${wl(se)}${Bn.length>0?`
610
2153
  validation:
611
- ${Hn.map((Lr)=>` ${Lr}`).join(`
2154
+ ${Bn.map((Dr)=>` ${Dr}`).join(`
612
2155
  `)}
613
- `:""}`;if(ue){if(Hn.length>0){m({title:"compose import",body:Bn}),e("scroll-text"),i("import is invalid; not written");return}if(V(br)){i(`configuration already exists at ${br}; not overwriting`);return}Ee(br,se.yaml),i(`wrote ${br}; /reload to apply`);return}m({title:"compose import (dry-run)",body:`${Bn}
614
- Enter is preview only. /import compose ${Vr} --write to save.`}),e("scroll-text")}catch(se){i(A(se))}return}case"diff":if(!p){i("no configuration loaded");return}m({title:"config sources",body:Cw(p,g)}),e("scroll-text");return;case"daemon":{let R=p?.repoRoot;if(!R){i("no repository");return}let ue=W(R);if(!V(ue)){i("no daemon bootstrap log yet for this repository");return}m({title:"daemon bootstrap",body:de(ue)}),e("scroll-text");return}case"reload":if(!y)return;y.reload().then((R)=>{if(i(R.restart_required.length===0?"Configuration reloaded":`Reload requires restart: ${R.restart_required.join(", ")}`),R.restart_required.length>0)o("reload"),e("confirm");x()});return;case"doctor":l("doctor");return;case"stats":l("stats");return;case"start":await U(So,h);return;case"stop":await T(So);return;case"restart":{let R=qx(Ce),ue=yp(R.services,n);await ye(ue,h,R.cascade);return}case"run":{let R=Ce[0]??"";if(!R){r("tasks"),t(""),e("slash"),i(Object.keys(p?.tasks??{}).length===0?"no tasks configured":"pick a task");return}if(!y){i("no daemon attached");return}try{let ue=await y.runTask(R);Ke(`task:${R}`),l("logs"),i(`task ${R} exited ${ue.code}`)}catch(ue){throw Ke(`task:${R}`),l("logs"),ue}return}case"exec":{let R=Vx(Ce);if(!R.service){r("services"),t(""),e("slash"),i(Object.keys(p?.services??{}).length===0?"no services configured":"pick a service");return}if(!y){i("no daemon attached");return}if(R.printEnv){if(!p?.services[R.service]){i(`unknown service ${R.service}`);return}if(R.reveal)P(!0);i(`Resolving environment for ${R.service}\u2026`);try{await _(R.service),w(R.service),i(`Resolved environment for ${R.service}`)}catch(ue){w(R.service),i(A(ue))}return}if(R.command.length===0){i("usage: /exec <service> -- <command\u2026>");return}try{let ue=await y.execService(R.service,R.command);Ke(`${R.service}:exec`),l("logs"),i(`${R.service}:exec exited ${ue.code}`)}catch(ue){throw Ke(`${R.service}:exec`),l("logs"),ue}return}case"refresh":if(u==="auth"){await Vt();return}await x(),i("Refreshed status and logs");return;case"pause":Ye((R)=>!R),l("logs");return;case"filter":Ct((R)=>!R),l("logs");return;case"system":he(),l("logs");return;case"clear":yt();return;case"reveal":{let R=!g;P(R),i(R?"Secrets visible this session":"Secrets hidden");return}case"wrap":{let R=Kc(st);vt(R),i(`Log ${Vc(R)}`);return}case"copy":await v();return;case"export":{let R=ne(Ce[0]);if(y)await y.logs({export:R,services:rr,level:Xe?"ERROR":Xt,search:qe,regex:ft,source:vr});else H(R,Kt);b.current=R,i(`Exported ${R}`);return}case"exports":{let R=b.current||ae();X(R),i(`Opened ${ae()}`);return}case"regex":xr((R)=>!R),i(ft?"Substring search":"Regex search");return;case"since":nr(Ce[0]||new Date(Date.now()-3600000).toISOString()),i(`Logs since ${Ce[0]||"1h"}`);return;case"until":qt(Ce[0]||new Date().toISOString()),i(`Logs until ${Ce[0]||"now"}`);return;case"history":{let R=nt(),ue=Ce[0]||R[0];if(!ue){i("No persisted log sessions");return}or(pt(ue)),i(`Loaded session ${ue}`);return}case"buffer":S();return;case"edit":{if(!p)return;let R=process.env.DEVCTL_EDITOR||process.env.EDITOR,ue=R?[R,p.configPath]:process.platform==="darwin"?["open",p.configPath]:["xdg-open",p.configPath];Bun.spawn({cmd:ue,stdout:"ignore",stderr:"ignore",stdin:"ignore"}),i(`Opened ${p.configPath} in ${ue[0]}. Run /reload after saving.`);return}default:i(`/${at.name}`)}}catch(R){i(A(R))}finally{setTimeout(()=>{z.current=!1},hM)}},[ye,U,T,n,p,yt,y,v,Xe,Kt,Xt,ft,qe,rr,vr,st,k,S,w,s,h,x,Vt,f,g,u,ir,r,c,sr,he])}}var hM=50;var Tw=D(()=>{te();of();sf();hr();At();tt()});import{useCallback as Aw,useRef as yM,useState as Lw}from"react";function Dw({cfg:e,readTextFile:t,setOverlay:r,setStatus:n,validateConfigText:o,writeTextFile:i,controller:s,setConfirmKind:a,refresh:c}){let l=yM(null),[d,u]=Lw(""),[f,p]=Lw(""),m=Aw(()=>{if(!e)return;try{u(t(e.configPath)),p(""),r("config-edit")}catch(y){n(A(y))}},[e]),g=Aw(()=>{if(!e)return;let y=l.current?.plainText??d,x=o(e.repoRoot,e.configPath,y);if(x.length>0){p(x.join(`
615
- `)),n("Config buffer not saved");return}if(i(e.configPath,y),p(""),r("none"),!s){n(`Wrote ${e.configPath}`);return}s.reload().then((h)=>{if(n(h.restart_required.length===0?"Configuration saved and reloaded":`Saved; restart required: ${h.restart_required.join(", ")}`),h.restart_required.length>0)a("reload"),r("confirm");c()})},[e,d,s,c]);return{configEditRef:l,configEditText:d,configEditError:f,setConfigEditError:p,openConfigBuffer:m,saveConfigBuffer:g}}var Mw=D(()=>{te()});import{useEffect as vM}from"react";function Ow({controller:e,cfg:t,paused:r,logSince:n,refresh:o,setLogs:i,setCfg:s,setConfigReloadError:a,setStatus:c}){return vM(()=>{if(!e)return;let l,d=!1,u=[],f=t&&t.logs.max_memory_events>0?t.logs.max_memory_events:50000,p=()=>{if(l=void 0,u.length>0){let g=u.splice(0,u.length);i((y)=>xx(y,g,n,f))}if(d)d=!1,o()},m=e.onEvent((g)=>{if(g.type===Ao&&g.payload&&typeof g.payload==="object"&&"event"in g.payload){let y=g.payload.event;if(!r)u.push(y);if(!l)l=setTimeout(p,30);return}if(g.type===Ri){a(Hx(g));return}if(g.type===na)a(void 0),e.configSnapshot().then(s).catch((y)=>c(A(y)));if(d=!0,!l)l=setTimeout(p,30)});return()=>{if(m(),l)clearTimeout(l)}},[e,n,r,o]),{}}var Iw=D(()=>{te();yr();hr()});import{useCallback as xM,useEffect as $w,useRef as Fw,useState as gi}from"react";function Nw({controller:e,workspace:t,cfg:r,snap:n,screen:o,configReloadError:i,setSnap:s,setStatus:a}){let{detectGoogle:c,runDoctor:l,loadPath:d}=t,[u,f]=gi(),[p,m]=gi(),[g,y]=gi(!1),[x,h]=gi(""),[P,_]=gi(0),[w,v]=gi({active:"Preparing diagnostics",checks:[]}),b=Fw(void 0),S=Fw(0),k=xM(async()=>{if(!e){a("Supervisor is not running");return}a("Probing configured identities\u2026");try{let L=await e.refreshAuth();s((G)=>G?{...G,identity:L}:G);let I=Object.values(L.service_account_status),F=I.filter((G)=>G==="available").length,Q=I.filter((G)=>G==="unavailable").length;a(I.length===0?"Identity refreshed \u2014 no service accounts configured":`Identity refreshed \u2014 ${F} available${Q>0?`, ${Q} unavailable`:""}`)}catch(L){a(A(L))}},[e]);return $w(()=>{c(r?.google.project_id??"").then(f).catch((L)=>{a(A(L))})},[r?.google.project_id,n?.identity.project]),$w(()=>{if(o!=="doctor"||!r)return;let L=b.current,I=n?.session_id??"";if(L?.cfg===r&&L.tick===P&&L.sessionID===I&&L.reloadError===i)return;b.current={cfg:r,tick:P,sessionID:I,reloadError:i};let F=++S.current;y(!0),h(""),v({active:"Preparing diagnostics",checks:[]});let Q;if(r.configPath!==""){Q="";try{d(r.repoRoot,r.configPath)}catch(G){Q=A(G)}}l(r,(G)=>{if(F===S.current)v(G)},{services:n?.services,proxyRunning:n?.proxy.running,repositoryConfigError:Q}).then((G)=>{if(F!==S.current)return;m(G)}).catch((G)=>{if(F!==S.current)return;h(A(G))}).finally(()=>{if(F===S.current)y(!1)})},[o,r,P,n?.session_id,i]),{google:u,setGoogle:f,doctor:p,doctorLoading:g,doctorError:x,doctorProgress:w,setDoctorTick:_,refreshAuth:k}}var Hw=D(()=>{te()});import{useCallback as af,useState as yl}from"react";function Bw({controller:e,workspace:t,cfg:r,snap:n,refresh:o,setStatus:i,setOverlay:s}){let{resolveStartRequest:a,startupPlan:c,shutdownPlan:l,shutdownPlanExact:d}=t,[u,f]=yl(),[p,m]=yl([]),[g,y]=yl(!1),[x,h]=yl("start"),P=af(async(v,b)=>{if(!e||!r){i("no configuration loaded");return}try{let S=v.length>0,k=a(r,{services:v,profile:S?void 0:b}),L=c(r,k.services,k.profile);h("start"),m(L.waves.flat().filter((Q)=>mo(n?.services[Q]))),f(L),s("plan"),y(!0);let I=k.services.filter((Q)=>!mo(n?.services[Q])),F=await e.start({services:I.length>0?I:k.services,profile:S?void 0:k.profile});await o(),y(!1),i(Zb(F))}catch(S){y(!1),await o(),i(A(S))}},[r,e,o,n]),_=af(async(v)=>{if(!e||!r){i("no configuration loaded");return}let b=v.length>0?v:Object.entries(n?.services??{}).filter(([,S])=>S.state!=="STOPPED"&&S.state!=="UNKNOWN").map(([S])=>S);if(b.length===0){i("nothing to stop");return}try{let S=l(r,b);h("stop"),m([]),f(S),s("plan"),y(!0),await e.stop(v),await o(),y(!1),i(ew(S))}catch(S){y(!1),await o(),i(A(S))}},[r,e,o,n]),w=af(async(v,b,S=!1)=>{if(!e||!r){i("no configuration loaded");return}try{let k=Qb(r,v,b),L=S?l(r,k.services):d(r,k.services);h("restart"),m([]),f(L),s("plan"),y(!0),await e.restart(k.services,S),await o(),y(!1);let I=pl(L);i(I===""?"Restarted selected services":`Restarted ${I}`)}catch(k){y(!1),await o(),i(A(k))}},[r,e,o]);return{plan:u,planInitiallyRunning:p,planBusy:g,lifecycle:x,beginStart:P,beginStop:_,beginRestart:w}}var Ww=D(()=>{te();fl();At()});import{useCallback as Ar,useEffect as jr,useMemo as In,useRef as bM,useState as xe}from"react";function Gw(e,t,r,n,o){let i=bp(e,t.sliceLen,t.windowStart,t.windowNewer);if(i.startDelta!==0){r(Math.max(0,t.windowStart+i.startDelta)),n(!0),o(i.selected);return}let s=Math.max(t.sliceLen-1,0),a=t.sliceLen>0&&(i.selected<s||t.windowNewer>0);if(a&&!t.pinned)r(qc(t.filteredLen));n(a),o(i.selected)}function Uw({controller:e,tui:t,names:r,screen:n,refresh:o,setStatus:i}){let[s,a]=xe([]),[c,l]=xe(!1),[d,u]=xe(!1),[f,p]=xe(!0),[m,g]=xe(""),[y,x]=xe(!1),[h,P]=xe(0),[_,w]=xe(""),[v,b]=xe(""),[S,k]=xe(""),[L,I]=xe(""),[F,Q]=xe(!1),[G,C]=xe(0),[B,W]=xe(Tr-1),[V,de]=xe(!1),[Ee,$e]=xe(0),ne=jw,[H,ae]=xe(t.log_timestamps!==!1),[X,nt]=xe(t.log_metadata!==!1),[pt,ie]=xe(()=>[...vx]),[Ae]=xe(""),[De]=xe(""),[Ke,ht]=xe(!1),[Ye,Ct]=xe("focus"),[he,yt]=xe(!1),[st,vt]=xe(Tr-1),[rr,Xe]=xe(!1),[Xt,qe]=xe(-1),[ft,vr]=xe(),[Kt,xr]=xe(""),[nr,qt]=xe(!1),[or,ir]=xe(!1),sr=bM([]),[Vt,ce]=xe(0),U=In(()=>Jc(r,s,pt),[r,s,pt]);jr(()=>{ie((le)=>{let xt=new Set(le),Rt=!1;for(let zr of s)if(zr.service!==""&&!r.includes(zr.service)&&!xt.has(zr.service))xt.add(zr.service),Rt=!0;return Rt?[...xt].sort():le})},[s,r]);let T=In(()=>go(s,{service:S,services:ne,errorOnly:d,search:m,regex:Ke,source:De,since:_,until:v,systemLogs:f}),[d,Ke,m,S,ne,_,De,v,s,f]),ye=In(()=>xp(T,he,Vt),[T,he,Vt]),z=ye.items,Lt=In(()=>go(s,{service:L,services:ne,errorOnly:d,search:m,regex:Ke,source:De,since:_,until:v,systemLogs:f}),[d,Ke,m,L,ne,_,De,v,s,f]),at=In(()=>xp(Lt,V,Ee),[Lt,V,Ee]),Ce=at.items;jr(()=>{qe(-1),yt(!1),vt(Math.max(0,Math.min(Tr,T.length)-1))},[d,m,S,f]),jr(()=>{de(!1),W(Math.max(0,Math.min(Tr,Lt.length)-1))},[d,m,L,f]),jr(()=>{if(n!=="logs")Xe(!1)},[n]),jr(()=>{if(n!=="logs"||he)return;vt(Math.max(0,Math.min(Tr,T.length)-1))},[T.length,he,n]),jr(()=>{if(n!=="logs"||V)return;W(Math.max(0,Math.min(Tr,Lt.length)-1))},[Lt.length,V,n]);let So=Ar(()=>{if(!he)ce(qc(T.length)),yt(!0)},[T.length,he]),R=Ar(()=>{if(!V)$e(qc(Lt.length)),de(!0)},[Lt.length,V]),ue=In(()=>({sliceLen:z.length,windowStart:ye.start,windowNewer:ye.newer,filteredLen:T.length,pinned:he}),[T.length,he,z.length,ye.newer,ye.start]),Vr=In(()=>({sliceLen:Ce.length,windowStart:at.start,windowNewer:at.newer,filteredLen:Lt.length,pinned:V}),[Lt.length,V,Ce.length,at.newer,at.start]),Jr=Ar((le)=>Gw(le,ue,ce,yt,vt),[ue]),br=Ar((le)=>Gw(le,Vr,$e,de,W),[Vr]),se=Ar((le)=>{if(F&&G===1){br(le);return}Jr(le)},[Jr,br,G,F]),Hn=Ar((le)=>{let xt=z.length,Rt=bp(le,xt,ye.start,ye.newer);if(Rt.startDelta!==0)ce(Math.max(0,ye.start+Rt.startDelta)),yt(!0);qe(Rt.selected)},[z.length,ye.newer,ye.start]),Bn=Ar(()=>{if(yt(!1),de(!1),qe(-1),n==="logs")vt(Math.max(0,Math.min(Tr,T.length)-1)),W(Math.max(0,Math.min(Tr,Lt.length)-1));P((xt)=>xt+1);let le=Math.max(ye.newer,at.newer);i(le>0?`Jumped to latest (+${le} new)`:"Jumped to latest logs")},[T.length,Lt.length,ye.newer,at.newer,n]),Lr=In(()=>({services:F?jw:S!==""?[S]:ne,level:d?"ERROR":Ae,search:m,regex:Ke,source:De,since:_,until:v}),[d,Ae,Ke,m,S,ne,_,De,v,F]),cn=Ar(async()=>{if(!e)return;try{vr(await e.logsStats(Lr))}catch(le){i(A(le))}},[e,Lr]),$s=Ar(async()=>{if(!e||c)return;try{let le=await e.logsPage(Lr);a((xt)=>bx(xt,le.events)),xr(le.prevCursor),qt(le.hasPrev),await cn()}catch(le){i(A(le))}},[e,Lr,c,cn]),wi=Ar(()=>{a([]),w(new Date().toISOString()),yt(!1),de(!1),i("Cleared on-screen log buffer")},[]),Si=Ar(()=>{p((le)=>!le)},[]);return jr(()=>{o().then(()=>$s())},[o,$s]),jr(()=>{sr.current=s},[s]),jr(()=>{let le=wp(he,ye.start,nr),xt=F&&wp(V,at.start,nr);if(!e||or||!le&&!xt)return;ir(!0),e.logsPage({...Lr,cursor:Kt,direction:"backward"}).then((Rt)=>{if(Rt.sessionChanged){i("Daemon session changed \u2014 older log history is no longer available"),qt(!1);return}let zr=sr.current,Fs=wx(zr,Rt.events);a(Fs);let ko=Fs.length-zr.length;if(ko>0){if(he)ce((wr)=>wr+ko);if(V)$e((wr)=>wr+ko)}xr(Rt.prevCursor),qt(Rt.hasPrev)}).catch((Rt)=>i(A(Rt))).finally(()=>ir(!1))},[e,Lr,or,nr,he,V,Kt,ye.start,at.start,F]),jr(()=>{if(!e||c||n!=="logs")return;let le=setInterval(()=>{cn()},wM);return()=>clearInterval(le)},[e,c,cn,n]),{logs:s,setLogs:a,paused:c,setPaused:l,errorOnly:d,setErrorOnly:u,showSystemLogs:f,logSearch:m,setLogSearch:g,logSearchFocused:y,setLogSearchFocused:x,logFollow:h,logSince:_,setLogSince:w,setLogUntil:b,logService:S,setLogService:k,logServiceB:L,setLogServiceB:I,splitLogs:F,splitFocus:G,setSplitFocus:C,toggleSplitLogs:()=>{if(F){C(0),Q(!1);return}if(L===""){let le=r.find((xt)=>xt!==S);if(le!==void 0)I(le)}Q(!0)},cycleSplitFocus:()=>C((le)=>le===0?1:0),setActiveLogService:(le)=>{let xt=(Rt)=>typeof le==="function"?le(Rt):le;if(F&&G===1){I(xt);return}k(xt)},logSliceB:Ce,logWindowB:at,logSelectedB:B,logPinnedB:V,logServices:ne,logShowTimestamps:H,setLogShowTimestamps:ae,logShowMeta:X,setLogShowMeta:nt,logLevel:Ae,logSource:De,logRegex:Ke,setLogRegex:ht,logWrap:Ye,setLogWrap:Ct,logPinned:he,logSelected:st,logsFullscreen:rr,setLogsFullscreen:Xe,dashboardLogCursor:Xt,setDashboardLogCursor:qe,logFacets:ft,logSources:U,filteredLogs:T,filteredLogsB:Lt,logWindow:ye,logSlice:z,pinLogView:So,pinLogViewB:R,applyLogCursor:se,applyLogCursorA:Jr,applyLogCursorB:br,applyDashboardLogCursor:Hn,jumpToLatestLogs:Bn,clearLogs:wi,toggleSystemLogs:Si}}var jw,wM=2000;var Kw=D(()=>{te();hr();jw=[]});import{useCallback as bo,useState as qw}from"react";function Vw({tui:e,controller:t,cfg:r,persistPrefs:n,snap:o,refresh:i,setStatus:s}){let[a,c]=qw(""),[l,d]=qw(()=>e.mcp_port??lo(t?.cfg.repoRoot??process.cwd())),u=bo((h)=>{let P=Pn(h);d(P);let _=r?.repoRoot??process.cwd();if(mv(_,P)){n({mcp_port:null},`MCP port ${P} (default)`);return}n({mcp_port:P},`MCP port ${P}`)},[r,n]),f=bo(()=>{if(a==="")return l;let h=fv(a,l);return c(""),u(h),h},[l,a,u]),p=bo(async(h)=>{if(!t||o?.mcp?.running!==!0)return;await t.mcpStop(),await t.mcpStart({port:h}),await i()},[t,i,o?.mcp?.running]),m=bo(async(h)=>{if(!t){s("Supervisor is not running");return}let P=o?.mcp?.disabled_tools??[],_=En(h.name,P),w=_?[...P,h.name]:P.filter((v)=>v!==h.name);try{let v=await t.mcpSetTools(w);n({mcp_disabled_tools:v},`${h.label} ${_?"disabled":"enabled"}`),await i()}catch(v){s(A(v))}},[t,n,i,o]),g=bo(async()=>{if(!t){s("Supervisor is not running");return}try{if(o?.mcp?.running)await t.mcpStop(),n({mcp_enabled:!1},"MCP off"),s("MCP stopped");else await t.mcpStart({port:l}),n({mcp_enabled:!0},"MCP on"),s("MCP started");await i()}catch(h){s(A(h))}},[t,l,n,i,o?.mcp?.running]),y=bo((h)=>{al(h.text).then(()=>{s(`Copied ${h.title} snippet`)}).catch((P)=>{s(A(P))})},[]),x=bo((h)=>{let P=Vp(h);if(P===void 0)return!1;let _=o?.mcp?.address??ui(o?.mcp?.port??l),w=_s(_,o?.mcp?.token??"")[P];if(!w)return!1;return y(w),!0},[y,l,o?.mcp?.address,o?.mcp?.port,o?.mcp?.token]);return{mcpPortDraft:a,setMcpPortDraft:c,mcpPort:l,persistMcpPort:u,applyMcpPortDraft:f,restartMcpOnPort:p,toggleMcpTool:m,toggleMcp:g,copyMcpSnippet:y,copyFocusedMcpSnippet:x}}var Jw=D(()=>{te();ul();ss();$p();Ts()});import{RGBA as SM}from"@opentui/core";import{useCallback as sn,useEffect as kM,useMemo as zw,useRef as vl,useState as xl}from"react";function Yw({tui:e,controller:t,resolveTuiOverridePath:r,terminalBackground:n,userTuiConfigPath:o,snap:i,setStatus:s,saveTuiPreferences:a,setPaletteIndex:c,setOverlay:l,setConfirmKind:d,setScreen:u,setSelected:f,screen:p}){let[m,g]=xl(e.theme||t?.cfg.ui.theme||"devctl"),y=vl(m),[x,h]=xl(e.mouse),P=vl(e.mouse),[_,w]=xl(e.leader_timeout),v=vl(e.leader_timeout),[b,S]=xl(()=>ps(e.font_size)),k=vl(b),L=qv(r()),I=zw(()=>{let H=Wv(m);if(Sc(m)!=="terminal"||!np(n))return H;let ae=n??H.background;return{...H,background:ae,panel:ae,element:ae,inverse:ae}},[n,m]),F=Sc(m)==="terminal"&&np(n)?SM.defaultBackground(n??void 0):I.background,Q=zw(()=>Yv({themeName:m,fontSize:b,mouse:x,leaderMs:_,locked:L,configPath:L?e.path||cp(r(),o()):cp(r(),o()),mcpRunning:i?.mcp?.running===!0}),[b,_,x,L,i?.mcp?.running,m,e.path]),G=sn((H,ae)=>{if(L){s(`${ae} session only`);return}let X=a(H);s(`${ae} saved ${X}`)},[L]),C=sn((H)=>{g(H),y.current=H,G({theme:H},`theme ${H}`)},[G]),B=sn(()=>{let H=!x;h(H),P.current=H,G({mouse:H},`mouse ${H?"on":"off"} restart TUI to apply clicks`)},[x,G]),W=sn((H)=>{w(H),v.current=H,G({leader_timeout:H},`leader ${H}ms`)},[G]),V=sn((H)=>{let ae=ps(H);S(ae),k.current=ae,G({font_size:ae},`display ${Pc(ae)}`)},[G]),de=sn(()=>{let H=_c();g(H.theme),y.current=H.theme,h(H.mouse),P.current=H.mouse,w(H.leader_timeout),v.current=H.leader_timeout,S(H.font_size),k.current=H.font_size,G(H,"restored default preferences")},[G]),Ee=sn((H)=>{if(H.id==="theme"){c(Math.max(0,St.indexOf(m))),l("themes");return}if(H.id==="mouse"){B();return}if(H.id==="leader"){W(_);return}if(H.id==="font"){V(b);return}if(H.id==="reset"){d("reset-prefs"),l("confirm");return}if(H.id==="mcp"){u("mcp"),f(0);return}s(H.detail)},[V,W,b,_,m,B]),$e=sn((H,ae)=>{let X=Tn(Q,ae);if(!X)return;if(X.id==="theme"){C(Vv(m,H));return}if(X.id==="leader"){W(Jv(_,H));return}if(X.id==="font"){V(fs(b,H));return}if(X.id==="mouse")B()},[V,W,b,_,C,Q,m,B]),ne=sn(()=>{g(y.current)},[]);return kM(()=>{if(p==="settings")return;g(y.current),S(k.current),w(v.current),h(P.current)},[p]),{themeName:m,setThemeName:g,leaderMs:_,fontSize:b,prefsLocked:L,palette:I,rootBackground:F,settingRows:Q,persistPrefs:G,persistTheme:C,toggleMouse:B,applyFont:V,applyReset:de,activateSetting:Ee,cycleSetting:$e,revertThemePreview:ne}}var Xw=D(()=>{_r();tt()});import{jsxDEV as Ls}from"@opentui/react/jsx-dev-runtime";function Qw(e){let{palette:t,termW:r,termH:n,step:o,answers:i,repo:s,draft:a,authStatus:c,onDraft:l,onSubmit:d}=e,u=wt[o]??wt[0],f=wt.length,p=CM(u.id,i,s);return Ls(ut,{palette:t,title:`setup ${o+1}/${f} ${u.title}`,bottomTitle:"enter next \xB7 esc cancel",termW:r,termH:n,preferW:64,preferH:12,borderColor:t.primary,gap:1,children:[Ls("text",{fg:t.muted,wrapMode:"word",children:p},void 0,!1,void 0,this),u.id==="auth"||u.id==="write"||u.id==="repo"?Ls("text",{fg:t.text,wrapMode:"word",children:u.id==="auth"?c:u.id==="repo"?s:"Write .devctl/config.yaml and attach the daemon."},void 0,!1,void 0,this):Ls("input",{focused:!0,value:a,placeholder:u.prompt,onInput:l,onSubmit:d,backgroundColor:t.element,focusedBackgroundColor:t.element,textColor:t.text,cursorColor:t.primary},void 0,!1,void 0,this),Ls(ot,{palette:t,hints:[{key:"enter",label:u.id==="write"?"write and attach":"next"},{key:"esc",label:"cancel"}]},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function cf(e,t,r){if(e==="name")return t.name;if(e==="project")return t.project;if(e==="sa")return t.sa;if(e==="audience")return t.audience;if(e==="port")return String(t.proxyPort);if(e==="profile")return t.profile;if(e==="repo")return r;return""}function Zw(e,t,r,n){let o={...t};if(e==="name")o.name=r.trim()||xn(n).name;else if(e==="project")o.project=r.trim();else if(e==="sa")o.sa=r.trim();else if(e==="audience")o.audience=r.trim();else if(e==="port")o.proxyPort=Sa(r,t.proxyPort);else if(e==="profile")o.profile=r.trim();return o}function CM(e,t,r){if(e==="repo")return`Using ${r}. The TUI always writes under this checkout.`;if(e==="auth")return"ADC login needs a real TTY. Enter continues; if ADC is missing, login runs gcloud on the terminal.";if(e==="write")return`Project ${t.name||"(unnamed)"}. Enter writes the starter file and attaches the daemon.`;return wt.find((n)=>n.id===e)?.prompt??""}var lf=D(()=>{ka();ke()});import{useCallback as df,useState as bl}from"react";function eS({workspace:e,renderer:t,setOverlay:r,setStatus:n,setScreen:o,onAttached:i,setController:s,setCfg:a,setBootError:c}){let l=process.cwd(),[d,u]=bl(0),[f,p]=bl(()=>xn(l)),[m,g]=bl(""),[y,x]=bl("Checking Application Default Credentials\u2026"),h=df(()=>{let w=xn(l);u(0),p(w),g(cf("repo",w,l)),r("setup-wizard"),e.detectGoogle("").then((v)=>{p((b)=>({...b,project:v.projectID||b.project})),x(v.adcAvailable?`ADC available${v.userEmail?` (${v.userEmail})`:""}. Enter continues.`:"ADC is not available. Enter runs gcloud ADC login on this TTY, then continues.")})},[l,r,e]),P=df(async(w)=>{try{Ra(l,w);let v=await e.openTui(l,"");i?.(v),s(v),a(v.cfg),c(void 0),r("none"),o("dashboard"),n("Wrote .devctl/config.yaml and attached the daemon")}catch(v){n(A(v))}},[i,l,c,a,s,r,o,n,e]),_=df(async()=>{let w=wt[d];if(!w)return;let v=Zw(w.id,f,m,l);if(w.id==="auth"){if(!(await e.detectGoogle(v.project)).adcAvailable)try{await hl(t,()=>e.loginGoogle())}catch(L){n(A(L))}}if(p(v),w.id==="write"){await P(v);return}let b=Math.min(d+1,wt.length-1),S=wt[b];u(b),g(S?cf(S.id,v,l):"")},[f,m,P,t,l,d,e,n]);return{step:d,answers:f,draft:m,authStatus:y,repo:l,setDraft:g,startWizard:h,advanceWizard:_}}var tS=D(()=>{ka();Pa();te();lf()});import{useCallback as RM,useEffect as rS,useState as uf}from"react";function nS({controller:e,cfg:t,envService:r}){let[n,o]=uf(),[i,s]=uf(!1),[a,c]=uf(""),l=n?.name===r,d=l?n?.env:void 0,u=r===""?"config":i&&!l?"loading":l?"resolved":a!==""?"error":"config",f=l||r===""?"":a;rS(()=>{o(void 0),c("")},[t]),rS(()=>{if(!e||r===""||!t?.services[r])return;if(n?.name===r)return;let m=!1;return s(!0),c(""),e.execService(r,[],!0).then((g)=>{if(m)return;o({name:r,env:g.environment??{}}),c("")}).catch((g)=>{if(m)return;c(A(g))}).finally(()=>{s(!1)}),()=>{m=!0}},[t,e,r,n?.name]);let p=RM(async(m)=>{if(!e)return;try{let g=await e.execService(m,[],!0);o({name:m,env:g.environment??{}}),c("")}catch(g){throw c(A(g)),g}},[e]);return{inspectorEnv:d,inspectorEnvStatus:u,inspectorEnvError:f,resolveEnvironment:p}}var oS=D(()=>{te()});import{jsxDEV as $n}from"@opentui/react/jsx-dev-runtime";function iS(e){let{palette:t,path:r,initialValue:n,error:o,termW:i,termH:s,textareaRef:a}=e;return $n(ut,{palette:t,title:"config buffer",bottomTitle:`${Mt("s")} save \xB7 esc discard \xB7 e still opens $EDITOR`,termW:i,termH:s,preferW:88,preferH:28,gap:1,children:[$n("box",{height:1,overflow:"hidden",children:$n("text",{fg:t.muted,wrapMode:"none",children:r},void 0,!1,void 0,this)},void 0,!1,void 0,this),$n("textarea",{ref:a,focused:!0,initialValue:n,backgroundColor:t.panel,focusedBackgroundColor:t.panel,textColor:t.text,focusedTextColor:t.text,cursorColor:t.primary},void 0,!1,void 0,this),o!==""?$n("box",{height:2,overflow:"hidden",children:$n("text",{fg:t.error,wrapMode:"word",children:o},void 0,!1,void 0,this)},void 0,!1,void 0,this):$n("box",{height:1,overflow:"hidden",children:$n("text",{fg:t.muted,children:"validate, then write. invalid YAML is not saved."},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var sS=D(()=>{ke();Pr()});import{jsxDEV as pf}from"@opentui/react/jsx-dev-runtime";function aS(e){let{palette:t,title:r,body:n,kind:o,termW:i,termH:s}=e,a=Nx(o),c=a.map((l)=>`${l.key} ${l.label}`).join(" \xB7 ");return pf(ut,{palette:t,title:r,bottomTitle:c,termW:i,termH:s,preferW:52,preferH:8,borderColor:t.warning,gap:1,children:[pf("text",{fg:t.text,wrapMode:"word",children:n},void 0,!1,void 0,this),pf(ot,{palette:t,hints:a},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var cS=D(()=>{yr();ke()});import{jsxDEV as lS}from"@opentui/react/jsx-dev-runtime";function dS(e){let{palette:t,termW:r,termH:n}=e,o=Tp();return lS(ut,{palette:t,title:"leader",termW:r,termH:n,preferW:68,preferH:5,anchor:"bottom",children:lS(ot,{palette:t,hints:o},void 0,!1,void 0,this)},void 0,!1,void 0,this)}var uS=D(()=>{vo();ke()});import{jsxDEV as Yt,Fragment as PM}from"@opentui/react/jsx-dev-runtime";function pS(e){let{palette:t,event:r,termW:n,termH:o,scrollRef:i}=e;if(!r)return null;let s=r.raw?px(r.raw):void 0,a=r.message.length>120||r.message.includes(`
616
- `)||s!==void 0;return Yt(ut,{palette:t,title:"log details",bottomTitle:"j/k scroll \xB7 esc close",termW:n,termH:o,preferW:a?84:72,preferH:a?22:14,gap:1,children:Yt("scrollbox",{ref:i,focused:!1,stickyScroll:!1,scrollX:!1,style:je(t),children:Yt("box",{flexDirection:"column",overflow:"hidden",children:[Yt("text",{fg:t.text,wrapMode:"word",children:Gc(r.message)},void 0,!1,void 0,this),Yt("text",{fg:t.muted,children:`time ${r.timestamp}`},void 0,!1,void 0,this),Yt("text",{fg:Ze(r.service,t),children:`service ${r.service}`},void 0,!1,void 0,this),Yt("text",{fg:t.muted,children:`source ${r.source}${r.stream?` / ${r.stream}`:""}`},void 0,!1,void 0,this),Yt("text",{fg:t.muted,children:`level ${jc(r.level)}`},void 0,!1,void 0,this),Yt("text",{fg:t.muted,children:`pid ${r.pid||"\u2014"}`},void 0,!1,void 0,this),Yt("text",{fg:t.muted,children:`request ${r.request_id||"\u2014"}`},void 0,!1,void 0,this),Yt("text",{fg:t.muted,children:`identity ${r.identity||"\u2014"}`},void 0,!1,void 0,this),s===void 0?null:Yt(PM,{children:[Yt("text",{fg:t.muted,children:"raw json"},void 0,!1,void 0,this),Yt("text",{fg:t.text,wrapMode:"word",children:s},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this)}var fS=D(()=>{hr();ke();tt()});import{jsxDEV as j}from"@opentui/react/jsx-dev-runtime";function ff(e,t){switch(e){case"completed":return{icon:"\u2713",label:"Completed",tone:"success",color:t.success};case"failed":return{icon:"\u2717",label:"Failed",tone:"error",color:t.error};case"unhealthy":return{icon:"\u26A0",label:"Unhealthy",tone:"warning",color:t.warning};case"active":return{icon:"\u25CF",label:"In progress",tone:"primary",color:t.primary};default:return{icon:"\u25CB",label:"Queued",tone:"muted",color:t.muted}}}function _M(e,t,r,n){if(t!=="")return{icon:"\u2717",label:"Failed",tone:"error",color:n.error};if(!e)return{icon:"\u2713",label:"Finished",tone:"success",color:n.success};if(r==="unhealthy")return{icon:"\u26A0",label:"Waiting on health",tone:"warning",color:n.warning};return{icon:"\u25CF",label:"Running",tone:"primary",color:n.primary}}function mS(e){return j("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:j("text",{wrapMode:"none",children:[j("span",{fg:e.palette.primary,children:e.label.toUpperCase()},void 0,!1,void 0,this),e.detail?j("span",{fg:e.palette.muted,children:` ${e.detail}`},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}function gS(e){let{palette:t,plan:r,snap:n,busy:o,failed:i,kind:s,termW:a=80,termH:c=24,scrollRef:l,initiallyRunning:d=[],onDismiss:u}=e,f=new Set(d),p=r.waves.map((C,B)=>({wave:C.filter((W)=>!f.has(W)),waveIdx:B})).filter(({wave:C})=>C.length>0),m=rw(r,n,s),g=r.waves[m.currentWaveIndex]??[],y=As(g,n,s),x=ff(y,t),h=_M(o,i,y,t),P=ow(s,o,i,r.profile),_=iw(o,i),w=i?t.inverse:t.text,v=i?t.error:t.element,b=Math.min(92,Math.max(40,a-8)),S=b>=82?22:b>=66?17:13,k=12,L=9,I=b>=56,F=b>=72,Q=b<72||r.waves.length>3,G=r.profile?`PROFILE ${r.profile}`:s.toUpperCase();return j(ut,{palette:t,title:P,bottomTitle:"enter close \xB7 esc hide (operation continues)",termW:a,termH:c,preferW:98,preferH:28,borderColor:h.color,gap:0,children:[j("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:t.element,children:[j(We,{palette:t,label:`${h.icon} ${h.label.toUpperCase()}`,tone:h.tone},void 0,!1,void 0,this),j("box",{paddingLeft:1,flexGrow:1,overflow:"hidden",children:j("text",{fg:t.text,wrapMode:"none",children:G},void 0,!1,void 0,this)},void 0,!1,void 0,this),j(We,{palette:t,label:`${m.ready}/${m.total} READY`,tone:m.isComplete?"success":"info"},void 0,!1,void 0,this),j(We,{palette:t,label:`WAVE ${Math.min(m.currentWaveIndex+1,Math.max(1,m.totalWaves))}/${Math.max(1,m.totalWaves)}`,tone:x.tone},void 0,!1,void 0,this)]},void 0,!0,void 0,this),j("box",{height:2,flexDirection:"column",overflow:"hidden",flexShrink:0,paddingLeft:1,paddingRight:1,children:[j("box",{height:1,flexDirection:"row",overflow:"hidden",children:[j("text",{wrapMode:"none",children:[j("span",{fg:t.muted,children:"PROGRESS "},void 0,!1,void 0,this),j("span",{fg:h.color,children:`[${m.progressBar}]`},void 0,!1,void 0,this),j("span",{fg:t.text,children:` ${m.percent}%`},void 0,!1,void 0,this)]},void 0,!0,void 0,this),j("box",{flexGrow:1},void 0,!1,void 0,this),j("text",{wrapMode:"none",children:j("span",{fg:x.color,children:`${x.icon} ${x.label}`},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this),j("box",{height:1,overflow:"hidden",children:j("text",{fg:t.muted,wrapMode:"none",children:o?s==="stop"?"Dependents stop first; each wave waits for the previous wave.":"Each wave waits until its services are healthy before the next wave starts.":_.primary},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this),j(mS,{palette:t,label:"Pipeline",detail:`${r.waves.length} execution wave${r.waves.length===1?"":"s"}`},void 0,!1,void 0,this),j("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:t.element,children:r.waves.map((C,B)=>{let W=As(C,n,s),V=ff(W,t),de=Q?`W${B+1} ${V.icon} ${C.length}`:`W${B+1} ${V.icon} ${V.label} \xB7 ${C.length}`;return j("box",{flexDirection:"row",overflow:"hidden",flexShrink:0,children:[j(We,{palette:t,label:de,tone:V.tone},void 0,!1,void 0,this),B<r.waves.length-1?j("box",{width:3,flexShrink:0,overflow:"hidden",children:j("text",{fg:t.muted,children:" \u2500\u25BA"},void 0,!1,void 0,this)},void 0,!1,void 0,this):null]},`step-${B}`,!0,void 0,this)})},void 0,!1,void 0,this),(r.blockers??[]).length>0?j("box",{minHeight:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:t.element,paddingLeft:1,children:j("text",{fg:t.error,wrapMode:"word",children:`BLOCKED ${r.blockers?.map((C)=>`${C.name}: ${C.message}`).join(" \xB7 ")}`},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,d.length>0?j("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,paddingLeft:1,children:j("text",{wrapMode:"none",children:[j("span",{fg:t.success,children:"\u2713 ALREADY READY"},void 0,!1,void 0,this),j("span",{fg:t.muted,children:` ${d.join(", ")}`},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this):null,j(mS,{palette:t,label:"Wave details",detail:"live process and health state"},void 0,!1,void 0,this),j("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:t.element,paddingLeft:1,children:[j("box",{width:2,flexShrink:0},void 0,!1,void 0,this),j("box",{width:S,flexShrink:0,overflow:"hidden",children:j("text",{fg:t.muted,children:oe("SERVICE",S)},void 0,!1,void 0,this)},void 0,!1,void 0,this),j("box",{width:12,flexShrink:0,overflow:"hidden",children:j("text",{fg:t.muted,children:oe("STATE",12)},void 0,!1,void 0,this)},void 0,!1,void 0,this),I?j("box",{width:9,flexShrink:0,overflow:"hidden",children:j("text",{fg:t.muted,children:oe("PORT",9)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,F?j("box",{flexGrow:1,overflow:"hidden",children:j("text",{fg:t.muted,children:"DETAIL"},void 0,!1,void 0,this)},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),j("scrollbox",{ref:l,focused:!1,stickyScroll:!1,scrollX:!1,style:je(t),children:j("box",{flexDirection:"column",overflow:"hidden",gap:1,children:p.map(({wave:C,waveIdx:B})=>{let W=As(C,n,s),V=ff(W,t),de=`${nw(s,B,W)} \xB7 ${C.length} service${C.length===1?"":"s"}`;return j("box",{border:!0,borderStyle:"rounded",borderColor:V.color,title:de,titleColor:V.color,flexDirection:"column",paddingLeft:1,paddingRight:1,flexShrink:0,overflow:"hidden",children:C.map((Ee)=>{let $e=n?.services[Ee],ne=tr($e),H=tw(Ee,r,n,s),ae=Ee===i||ne==="FAILED",X=ne==="UNHEALTHY",nt=ne==="HEALTHY"||s==="stop"&&ne==="STOPPED",pt=fo($e),ie=ae?"\u2717":nt?"\u2713":X?"\u26A0":ne==="STARTING"||ne==="RUNNING"||ne==="STOPPING"||ne==="RESTARTING"?"\u25CF":"\u25CB",Ae=ae?t.error:et(t,ne),De=ae?t.error:X?t.warning:nt?t.success:t.muted;return j("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:[j("box",{width:2,flexShrink:0,overflow:"hidden",children:j("text",{fg:Ae,children:ie},void 0,!1,void 0,this)},void 0,!1,void 0,this),j("box",{width:S,flexShrink:0,overflow:"hidden",children:j("text",{fg:Ze(Ee,t),wrapMode:"none",children:oe(Ee,S)},void 0,!1,void 0,this)},void 0,!1,void 0,this),j("box",{width:12,flexShrink:0,overflow:"hidden",children:j("text",{fg:et(t,ne),wrapMode:"none",children:oe(ne,12)},void 0,!1,void 0,this)},void 0,!1,void 0,this),I?j("box",{width:9,flexShrink:0,overflow:"hidden",children:j("text",{fg:pt?t.info:t.muted,wrapMode:"none",children:oe(pt?`:${pt}`:"\u2014",9)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,F?j("box",{flexGrow:1,overflow:"hidden",children:j("text",{fg:De,wrapMode:"none",children:H},void 0,!1,void 0,this)},void 0,!1,void 0,this):null]},Ee,!0,void 0,this)})},`wave-card-${B}`,!1,void 0,this)})},void 0,!1,void 0,this)},void 0,!1,void 0,this),j("box",{height:3,flexShrink:0,flexDirection:"column",backgroundColor:v,border:["top"],borderStyle:"single",borderColor:h.color,paddingLeft:1,paddingRight:1,overflow:"hidden",onMouseDown:u,children:[j("text",{fg:w,wrapMode:"none",children:_.primary},void 0,!1,void 0,this),j("text",{fg:i?t.inverse:t.muted,wrapMode:"none",children:_.secondary},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}var hS=D(()=>{It();fl();At();ke();tt()});import{jsxDEV as Gr}from"@opentui/react/jsx-dev-runtime";function yS(e){let{palette:t,route:r,termW:n,termH:o,scrollRef:i}=e;if(!r)return null;return Gr(ut,{palette:t,title:r.name,bottomTitle:"j/k scroll \xB7 esc close",termW:n,termH:o,preferW:78,preferH:16,gap:1,children:Gr("scrollbox",{ref:i,focused:!1,stickyScroll:!1,scrollX:!1,style:je(t),children:Gr("box",{flexDirection:"column",overflow:"hidden",children:[Gr("text",{fg:t.muted,children:`auth ${r.authType||"none"}`},void 0,!1,void 0,this),Gr("text",{fg:t.muted,children:`identity ${r.identityType||"\u2014"}`},void 0,!1,void 0,this),Gr("text",{fg:t.text,wrapMode:"word",children:`account ${r.serviceAccount||"\u2014"}`},void 0,!1,void 0,this),Gr("text",{fg:t.text,wrapMode:"word",children:`audience ${r.audience||"\u2014"}`},void 0,!1,void 0,this),Gr("text",{fg:t.muted,children:`match host ${r.matchHost||"*"}`},void 0,!1,void 0,this),Gr("text",{fg:t.muted,children:`match path ${r.matchPath||"*"}`},void 0,!1,void 0,this),Gr("text",{fg:t.text,wrapMode:"word",children:`upstream ${r.upstream}`},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this)}var vS=D(()=>{ke()});import{jsxDEV as wl}from"@opentui/react/jsx-dev-runtime";function xS(e){let{palette:t,title:r,body:n,termW:o,termH:i,scrollRef:s}=e,a=n.length>400||n.split(`
617
- `).length>16;return wl(ut,{palette:t,title:r,bottomTitle:"j/k scroll \xB7 esc close",termW:o,termH:i,preferW:a?88:76,preferH:a?24:16,gap:1,children:wl("scrollbox",{ref:s,focused:!1,stickyScroll:!1,scrollX:!1,style:je(t),children:wl("box",{flexDirection:"column",overflow:"hidden",children:wl("text",{fg:n===""?t.muted:t.text,wrapMode:"word",children:n===""?"(empty)":n},void 0,!1,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this)}var bS=D(()=>{ke()});import{jsxDEV as Gt}from"@opentui/react/jsx-dev-runtime";function wS(e){let{palette:t,items:r,query:n,selected:o,onQuery:i,onSubmit:s,title:a="commands"}=e,c=yo(n)!=="",l=sb(o,mf,r.length),d=c?r.slice(l,l+mf):ab(r,o,mf),u=c?[{group:"",items:d}]:Pp(d),f=Math.max(d.length+(c?0:u.length),1),p=ci(r,o),m=tb(r);return Gt("box",{height:f+EM,flexShrink:0,border:!0,borderStyle:"rounded",borderColor:t.borderActive,titleColor:t.primary,backgroundColor:t.panel,title:a,flexDirection:"column",overflow:"hidden",children:[d.length===0?Gt("box",{height:1,paddingLeft:1,overflow:"hidden",children:Gt("text",{fg:t.muted,children:"no matching command"},void 0,!1,void 0,this)},void 0,!1,void 0,this):u.flatMap((g)=>[...g.group===""?[]:[Gt("box",{height:1,paddingLeft:1,overflow:"hidden",backgroundColor:t.panel,children:Gt("text",{fg:t.muted,wrapMode:"none",children:g.group},void 0,!1,void 0,this)},`g-${g.group}`,!1,void 0,this)],...g.items.map((x)=>{let h=Zc(x)===(p?Zc(p):"");return Gt("box",{height:1,flexDirection:"row",overflow:"hidden",paddingLeft:1,paddingRight:1,backgroundColor:h?t.highlight:t.panel,children:[Gt("box",{width:m,flexShrink:0,overflow:"hidden",children:Gt("text",{fg:h?t.primary:t.text,wrapMode:"none",children:`${h?"\u203A":" "} /${_p(x).slice(0,Rp)}`},void 0,!1,void 0,this)},void 0,!1,void 0,this),Gt("box",{flexGrow:1,overflow:"hidden",children:Gt("text",{fg:h?t.accent:t.muted,wrapMode:"none",children:eb(x)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},Zc(x),!0,void 0,this)})]),Gt("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:t.highlight,paddingLeft:1,children:[Gt("box",{width:2,flexShrink:0,children:Gt("text",{fg:t.primary,children:"/"},void 0,!1,void 0,this)},void 0,!1,void 0,this),Gt("box",{flexGrow:1,overflow:"hidden",children:Gt("input",{focused:!0,value:n,placeholder:"start logs settings",onInput:i,onSubmit:()=>s(),backgroundColor:t.highlight,focusedBackgroundColor:t.highlight,textColor:t.text,cursorColor:t.primary},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}var mf=10,EM=3;var SS=D(()=>{vo()});import{jsxDEV as RS}from"@opentui/react/jsx-dev-runtime";function PS(e){let{palette:t,themeName:r,selected:n,termW:o,termH:i,onIndex:s,onPreview:a,onPick:c}=e,l=St.map((u)=>({name:u===r?`${u} (active)`:u,description:u===r?`${rp[u]} \xB7 current`:rp[u],value:u})),d=Xc(o,i,kS,CS,"center",!po(ve()));return RS(ut,{palette:t,title:"/themes",bottomTitle:"enter save \xB7 esc revert",termW:o,termH:i,preferW:kS,preferH:CS,children:RS("select",{height:Math.max(4,d.height-3),options:l,selectedIndex:n,showDescription:!0,showSelectionIndicator:!0,wrapSelection:!0,backgroundColor:t.panel,focusedBackgroundColor:t.panel,textColor:t.text,focusedTextColor:t.text,selectedBackgroundColor:t.highlight,selectedTextColor:t.primary,descriptionColor:t.muted,selectedDescriptionColor:t.accent,onChange:(u)=>{s(u);let f=St[u];if(f)a(f)},onSelect:(u,f)=>{let p=f?.value;if(typeof p==="string"&&p!=="")c(p)}},void 0,!1,void 0,this)},void 0,!1,void 0,this)}var kS=52,CS=22;var _S=D(()=>{Ot();yr();ke();_r();tt()});function Sl(e,t,r){let n=e?.google?.project_id??"";if(n!=="")return{project:n,source:"configuration"};let o=t?.project||r?.projectID||"",i=t?.project_source||r?.projectSource||"";return{project:o,source:i}}import{jsxDEV as it}from"@opentui/react/jsx-dev-runtime";function kl(e){let{palette:t,title:r,tone:n="muted",children:o}=e,i=n==="success"?t.success:n==="warning"?t.warning:n==="error"?t.error:t.border;return it("box",{border:!0,borderStyle:"rounded",borderColor:i,title:r,titleColor:i,flexDirection:"column",flexShrink:0,paddingLeft:1,paddingRight:1,marginBottom:1,overflow:"hidden",children:o},void 0,!1,void 0,this)}function ES(e){let{palette:t,cfg:r,google:n,identity:o}=e;if(!n&&!o)return it(sl,{palette:t,label:"Checking Application Default Credentials\u2026"},void 0,!1,void 0,this);let i=TM(r,o),s=o?.user||n?.userEmail||"",{project:a,source:c}=Sl(r,o,n),l=o?.adc??n?.adcAvailable===!0,d=o?.iap??(r?.proxy.routes??[]).some((g)=>g.auth.type.toLowerCase()==="iap"),u=i.filter((g)=>g.inactive!==!0),f=u.some((g)=>g.ok===!0),p=u.some((g)=>g.ok===!1),m=f?"AVAILABLE":p?"UNAVAILABLE":u.length>0?"NOT PROBED":i.length>0?"not required":"not configured";return it(kt,{palette:t,title:"identity",scroll:!0,children:[it(kl,{palette:t,title:"Google identity",tone:l?"success":"warning",children:[it(xo,{palette:t,label:"user",value:s||"(unknown)"},void 0,!1,void 0,this),it(xo,{palette:t,label:"project",value:a||"(unset)"},void 0,!1,void 0,this),it(xo,{palette:t,label:"source",value:c||"\u2014"},void 0,!1,void 0,this),it(xo,{palette:t,label:"ADC",value:l?"\u2713 available":"\u2717 missing",tone:l?"success":"error"},void 0,!1,void 0,this),it(xo,{palette:t,label:"gcloud",value:n?.gcloudInstalled?"\u2713 installed":"\u2717 not installed",tone:n?.gcloudInstalled?"success":"error"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),it(kl,{palette:t,title:`Service accounts (${i.length})`,tone:u.length===0?"muted":f?"success":"warning",children:[i.length===0?it("text",{fg:t.muted,wrapMode:"word",children:"none configured \u2014 identities come from config, never hard-coded"},void 0,!1,void 0,this):i.map((g)=>it("text",{fg:g.inactive?t.muted:g.ok===!0?t.success:g.ok===!1?t.error:t.text,wrapMode:"none",children:`${g.inactive?"\u2013":g.ok===!0?"\u2713":g.ok===!1?"\u2717":"\u25CB"} ${g.email}${g.inactive?" \u2014 inactive (route auth is none)":""}`},g.email,!1,void 0,this)),it("box",{height:1,flexShrink:0},void 0,!1,void 0,this),it(xo,{palette:t,label:"impersonation",value:m,tone:f?"success":u.length>0?"warning":"muted"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),it(kl,{palette:t,title:"IAP",tone:d?"success":"muted",children:[it("box",{flexDirection:"row",overflow:"hidden",flexShrink:0,children:it(We,{palette:t,label:d?"routes present":"no IAP routes",tone:d?"success":"idle"},void 0,!1,void 0,this)},void 0,!1,void 0,this),d?it("text",{fg:t.muted,wrapMode:"word",children:"each route needs its own audience"},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),!l?it(kl,{palette:t,title:"ADC unavailable",tone:"warning",children:[it("text",{fg:t.text,wrapMode:"word",children:"Local services still start. Cloud identity, impersonation, and IAP need ADC."},void 0,!1,void 0,this),it("text",{fg:t.muted,wrapMode:"word",children:"/auth login suspends this screen and runs gcloud ADC login, then restores the TUI"},void 0,!1,void 0,this)]},void 0,!0,void 0,this):null]},void 0,!0,void 0,this)}function TM(e,t){let r=new Map((e?Yd(e):[]).map((i)=>[i.email,i.active])),n=t?.service_account_status??{};return[...new Set([...r.keys(),...Object.keys(n)])].sort((i,s)=>i.localeCompare(s)).map((i)=>{let s=n[i]??"unknown",a=r.get(i)===!1;return{email:i,inactive:a,ok:a||s==="unknown"?void 0:s==="available"}})}var TS=D(()=>{Fr();zt();ke()});import{jsxDEV as O}from"@opentui/react/jsx-dev-runtime";function MS(e){let{palette:t,cfg:r,width:n,selectedTask:o=0}=e,i=ve();if(!r)return O(Ve,{palette:t,title:"No configuration",body:"Create .devctl/config.yaml or run setup."},void 0,!1,void 0,this);let s=Math.max(mi+8,n-6-i.pad*2),a=s>=_w,c=a?Math.floor((s-LS)/2):s,l=xw(r),d=bw(r),u=ww(r),f=Sw(r),p=kw(r);return O(kt,{palette:t,title:`config ${r.project.name||"unnamed"}`,children:[O(dt,{palette:t,items:fw(r)},void 0,!1,void 0,this),O("box",{flexGrow:1,overflow:"hidden",children:O("scrollbox",{ref:e.scrollRef,focused:!1,stickyScroll:!1,scrollX:!1,style:je(t),children:O("box",{flexDirection:"column",overflow:"hidden",gap:DS,children:[O(gf,{wide:a,left:O(Ur,{palette:t,title:"project",children:O(hi,{palette:t,facts:mw(r,Bt),width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this),right:O(Ur,{palette:t,title:"google",children:O(hi,{palette:t,facts:gw(r),width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this),O(gf,{wide:a,left:O(Ur,{palette:t,title:"runtime",children:O(hi,{palette:t,facts:hw(r),width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this),right:O(Ur,{palette:t,title:"logs",children:O(hi,{palette:t,facts:yw(r),width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this),O(Ur,{palette:t,title:`proxy ${r.proxy.enabled?"enabled":"disabled"}`,children:[O(hi,{palette:t,facts:vw(r),width:s},void 0,!1,void 0,this),O(LM,{palette:t,routes:d,width:s},void 0,!1,void 0,this)]},void 0,!0,void 0,this),O(Ur,{palette:t,title:`services ${l.length}`,children:O(AM,{palette:t,rows:l,width:s},void 0,!1,void 0,this)},void 0,!1,void 0,this),O(Ur,{palette:t,title:`tasks ${p.length}`,children:O(AS,{palette:t,rows:p,empty:"no tasks",width:s,selected:o},void 0,!1,void 0,this)},void 0,!1,void 0,this),O(gf,{wide:a,left:O(Ur,{palette:t,title:`profiles ${u.length}`,children:O(DM,{palette:t,rows:u,width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this),right:O(Ur,{palette:t,title:`templates ${f.length}`,children:O(AS,{palette:t,rows:f,empty:"no templates",width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this),O(Ur,{palette:t,title:"extras",children:O(hi,{palette:t,facts:Rw(r),width:s},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this),O(jt,{palette:t,backgroundColor:t.element,edge:"top",children:O(ot,{palette:t,hints:[{key:"v",label:"validate/save buffer"},{key:"e",label:"open in $EDITOR"},{key:"/diff",label:"config sources"},{key:"/reload",label:"re-read .devctl after editing"},{key:"enter",label:"run task"},{key:"j/k",label:"scroll"}]},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function gf(e){let{wide:t,left:r,right:n}=e;if(!t)return O("box",{flexDirection:"column",overflow:"hidden",gap:DS,flexShrink:0,children:[r,n]},void 0,!0,void 0,this);return O("box",{flexDirection:"row",overflow:"hidden",gap:LS,flexShrink:0,children:[O("box",{flexGrow:1,flexBasis:0,overflow:"hidden",children:r},void 0,!1,void 0,this),O("box",{flexGrow:1,flexBasis:0,overflow:"hidden",children:n},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function Ur(e){let{palette:t,title:r,children:n}=e;return O("box",{border:!0,borderStyle:"rounded",borderColor:t.border,title:r,titleColor:t.accent,paddingLeft:1,paddingRight:1,flexDirection:"column",overflow:"hidden",flexShrink:0,children:n},void 0,!1,void 0,this)}function hi(e){let{palette:t,facts:r,width:n}=e,o=Math.max(4,n-mi-4);return O("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:r.map((i)=>O("box",{height:1,flexDirection:"row",overflow:"hidden",children:[O("box",{width:mi,flexShrink:0,overflow:"hidden",children:O("text",{fg:t.muted,wrapMode:"none",children:oe(i.label,mi)},void 0,!1,void 0,this)},void 0,!1,void 0,this),O("box",{width:o,flexShrink:0,overflow:"hidden",children:O("text",{fg:IM(t,i.tone),wrapMode:"none",children:oe(i.value,o)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},i.label,!0,void 0,this))},void 0,!1,void 0,this)}function AM(e){let{palette:t,rows:r,width:n}=e;if(r.length===0)return O(Cl,{palette:t,text:"no services"},void 0,!1,void 0,this);let o=Pw(r),i=Math.max(8,n-o-6);return O("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:r.map((s)=>O("box",{height:1,flexDirection:"row",overflow:"hidden",children:[O("box",{width:o,flexShrink:0,overflow:"hidden",children:O("text",{fg:Ze(s.name,t),wrapMode:"none",children:oe(s.name,o)},void 0,!1,void 0,this)},void 0,!1,void 0,this),O("box",{width:i,flexShrink:0,overflow:"hidden",children:O("text",{fg:t.text,wrapMode:"none",children:_e(MM(s),i)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},s.name,!0,void 0,this))},void 0,!1,void 0,this)}function LM(e){let{palette:t,routes:r,width:n}=e;if(r.length===0)return O(Cl,{palette:t,text:"no routes"},void 0,!1,void 0,this);let o=Math.min(mi+4,Math.max(8,...r.map((s)=>s.name.length))),i=Math.max(8,n-o-6);return O("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:r.map((s)=>O("box",{height:1,flexDirection:"row",overflow:"hidden",children:[O("box",{width:o,flexShrink:0,overflow:"hidden",children:O("text",{fg:t.primary,wrapMode:"none",children:oe(s.name,o)},void 0,!1,void 0,this)},void 0,!1,void 0,this),O("box",{width:i,flexShrink:0,overflow:"hidden",children:O("text",{fg:t.text,wrapMode:"none",children:_e(OM(s),i)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},`${s.name}-${s.match}`,!0,void 0,this))},void 0,!1,void 0,this)}function DM(e){let{palette:t,rows:r,width:n}=e;if(r.length===0)return O(Cl,{palette:t,text:"no profiles"},void 0,!1,void 0,this);let o=Math.min(12,Math.max(8,...r.map((s)=>s.name.length))),i=Math.max(8,n-o-6);return O("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:r.map((s)=>O("box",{height:1,flexDirection:"row",overflow:"hidden",children:[O("box",{width:o,flexShrink:0,overflow:"hidden",children:O("text",{fg:t.primary,wrapMode:"none",children:oe(s.name,o)},void 0,!1,void 0,this)},void 0,!1,void 0,this),O("box",{width:i,flexShrink:0,overflow:"hidden",children:O("text",{fg:t.text,wrapMode:"none",children:_e(s.env==="\u2014"?s.services:`${s.services} env ${s.env}`,i)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},s.name,!0,void 0,this))},void 0,!1,void 0,this)}function AS(e){let{palette:t,rows:r,empty:n,width:o,selected:i}=e;if(r.length===0)return O(Cl,{palette:t,text:n},void 0,!1,void 0,this);let s=Math.min(14,Math.max(8,...r.map((c)=>c.name.length))),a=Math.max(8,o-s-6);return O("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:r.map((c,l)=>{let d=i===l;return O("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:d?t.highlight:void 0,children:[O("box",{width:s,flexShrink:0,overflow:"hidden",children:O("text",{fg:d?t.primary:t.text,wrapMode:"none",children:oe(c.name,s)},void 0,!1,void 0,this)},void 0,!1,void 0,this),O("box",{width:a,flexShrink:0,overflow:"hidden",children:O("text",{fg:d?t.accent:t.muted,wrapMode:"none",children:_e(c.summary,a)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},c.name,!0,void 0,this)})},void 0,!1,void 0,this)}function Cl(e){return O("box",{height:1,overflow:"hidden",children:O("text",{fg:e.palette.muted,wrapMode:"none",children:e.text},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function MM(e){let t=e.extends==="\u2014"?"":` ${e.extends}`;return` ${e.ports} ${e.identity} ${e.health} ${e.restart} \u2190 ${e.depends}${t}`}function OM(e){return` ${e.auth} ${e.identity} ${e.match} \u2192 ${e.upstream}`}function IM(e,t){if(t==="success")return e.success;if(t==="warning")return e.warning;if(t==="error")return e.error;if(t==="muted")return e.muted;return e.text}var LS=1,DS=1;var OS=D(()=>{zt();sf();Ot();It();ke();tt()});import{jsxDEV as Ft}from"@opentui/react/jsx-dev-runtime";function IS(e){let{palette:t,credentials:r}=e;if(!r)return Ft(sl,{palette:t,label:"Reading credential store\u2026"},void 0,!1,void 0,this);let n=r.entries.filter((i)=>i.valid).length,o=r.entries.length-n;return Ft(kt,{palette:t,title:"credentials",scroll:!0,children:[Ft("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:[Ft(We,{palette:t,label:`backend: ${r.backend}`,tone:"info"},void 0,!1,void 0,this),r.entries.length>0?Ft(We,{palette:t,label:`${n} valid`,tone:"success"},void 0,!1,void 0,this):null,o>0?Ft(We,{palette:t,label:`${o} expired`,tone:"warning"},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),Ft("text",{fg:t.muted,wrapMode:"word",children:"Tokens stay in the OS keychain or ~/.devctl/credentials (0600). They are never shown here."},void 0,!1,void 0,this),Ft("box",{height:1,flexShrink:0},void 0,!1,void 0,this),r.entries.length===0?Ft("text",{fg:t.muted,children:"no cached credentials"},void 0,!1,void 0,this):Ft("box",{flexDirection:"column",overflow:"hidden",gap:1,children:r.entries.map((i)=>Ft("box",{border:!0,borderStyle:"rounded",borderColor:i.valid?t.success:t.warning,title:i.identity,titleColor:i.valid?t.success:t.warning,flexDirection:"column",flexShrink:0,paddingLeft:1,paddingRight:1,overflow:"hidden",children:[Ft("text",{wrapMode:"none",children:[Ft("span",{fg:i.valid?t.success:t.warning,children:i.valid?"\u2713 valid":"\u2717 expired"},void 0,!1,void 0,this),Ft("span",{fg:t.muted,children:` \xB7 expires ${i.expires_at}`},void 0,!1,void 0,this)]},void 0,!0,void 0,this),Ft("text",{fg:t.muted,wrapMode:"none",children:`audience: ${i.audience||"(none)"}`},void 0,!1,void 0,this)]},`${i.identity}|${i.audience}`,!0,void 0,this))},void 0,!1,void 0,this),Ft(il,{palette:t,title:"Actions",body:"Use /refresh to mint again or auth refresh on the CLI. Invalidate clears the store without printing tokens.",hint:"/refresh /auth"},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var $S=D(()=>{zt();ke()});import{jsxDEV as Rl}from"@opentui/react/jsx-dev-runtime";function yi(e){let t=e.newer>0?`g jump to latest \xB7 ${e.newer} new`:"g jump to latest",r=Math.min(e.width,t.length+4);return Rl("box",{position:"absolute",left:Math.max(0,Math.floor((e.width-r)/2)),bottom:e.bottom??2,width:r,height:3,border:!0,borderStyle:"rounded",borderColor:e.palette.warning,backgroundColor:e.palette.panel,alignItems:"center",justifyContent:"center",onMouseDown:e.onJump,children:Rl("text",{wrapMode:"none",children:[Rl("span",{fg:e.palette.primary,children:"g"},void 0,!1,void 0,this),Rl("span",{fg:e.palette.text,children:t.slice(1)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}var FS=()=>{};import{jsxDEV as Ds}from"@opentui/react/jsx-dev-runtime";function vi(e){let{palette:t,logs:r,names:n,service:o,errorOnly:i,width:s,onService:a,onToggleErrors:c,facets:l}=e,d=l?Cx(n,l):kx(n,r),u=s<80,f=d.map((y)=>{let x=y.name===""?"all":y.name;return{id:x,label:u?x:`${x} \xB7 ${y.count}`,color:Ze(y.name,t)}}),p=Math.max(0,d.findIndex((y)=>y.name===o)),m=i?"ERROR+":u?"lvls":"all levels",g=Math.max(ii(f[p]?.label??"all"),s-ii(m));return Ds(jt,{palette:t,backgroundColor:t.element,children:Ds("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:t.element,children:[Ds("box",{flexGrow:1,overflow:"hidden",children:Ds(ol,{palette:t,items:f,active:p,width:g,emphasis:"fill",onPick:(y)=>{let x=d[y];if(x)a(x.name)}},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ds(We,{palette:t,label:m,tone:i?"error":"muted",onMouseDown:c},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}var NS=D(()=>{hr();Ln();ke();tt()});import{jsxDEV as $M}from"@opentui/react/jsx-dev-runtime";function Ms(e){let t=Math.min(e.total,e.start+e.count),r=Math.max(0,e.start),n=Math.max(0,e.total-t);return $M(dt,{palette:e.palette,items:[{text:`view ${e.start+1}\u2013${t} of ${e.total}`,tone:"primary"},{text:r>0?`\u2191 ${r} older`:"start of history",tone:r>0?"info":"idle"},{text:n>0?`\u2193 ${n} newer`:"at latest",tone:n>0?"warning":"success"}],hints:[{key:"pgup/pgdn",label:"move history window"},{key:"g",label:"latest"}]},void 0,!1,void 0,this)}var HS=D(()=>{ke()});import{useEffect as hf,useRef as BS}from"react";import{jsxDEV as Je}from"@opentui/react/jsx-dev-runtime";function xi(e){let{palette:t,logs:r,limit:n,width:o=80,followTick:i=0,focused:s=!1,wrapMode:a="clip",selected:c=-1,follow:l=!0,showTimestamps:d=!0,showMeta:u=!0,onPick:f,onLeaveLatest:p,viewStart:m=0,search:g="",regex:y=!1}=e,x=BS(null),h=BS(""),P=n===void 0?r:r.slice(-n),_=[...new Set(r.map((k)=>k.service))],w=yx(o,_),v=hx({width:o,serviceWidth:w,showTimestamps:d,showMeta:u}),b=P[P.length-1],S=b===void 0?"empty":String(b.seq);return hf(()=>{let k=x.current;if(!k)return;if(k.stickyScroll=l,l)k.stickyStart="bottom",k.scrollTo({x:k.scrollLeft,y:Math.max(0,k.scrollHeight)})},[l,i,S]),hf(()=>{let k=`${m}:${c}:${a}`;if(h.current===k)return;if(h.current=k,l&&(c<0||c>=P.length-1))return;let L=x.current;if(!L||c<0)return;let I=`log-row-${c}`,F=requestAnimationFrame(()=>{L.stickyScroll=!1,L.scrollChildIntoView(I)});return()=>cancelAnimationFrame(F)},[l,c,P.length,m,a]),hf(()=>{if(!l||!p)return;let k=!1,L=0,I=setInterval(()=>{let F=x.current;if(!F)return;let Q=F.viewport.height;if(Q<=0)return;let G=F.scrollHeight;if(F.scrollTop+Q>=G-NM){k=!0,L=G;return}if(G>L){F.stickyScroll=!0,F.stickyStart="bottom",F.scrollTo({x:F.scrollLeft,y:Math.max(0,G)}),L=G;return}if(k)p()},FM);return()=>{clearInterval(I)}},[l,i,p]),Je("box",{flexGrow:1,height:"100%",overflow:"hidden",children:Je("scrollbox",{ref:x,focused:s,stickyScroll:l,stickyStart:"bottom",scrollX:!1,style:{rootOptions:{flexGrow:1,height:"100%",overflow:"hidden",backgroundColor:t.panel},viewportOptions:{backgroundColor:t.panel},contentOptions:{backgroundColor:t.panel},scrollbarOptions:{trackOptions:{foregroundColor:t.primary,backgroundColor:t.element}}},children:Je("box",{flexDirection:"column",overflow:"hidden",children:P.map((k,L)=>Je(HM,{id:`log-row-${L}`,palette:t,event:k,serviceWidth:w,msgWidth:v,expanded:mx(a,L===c),active:L===c,showTimestamps:d,showMeta:u,onPick:f?()=>f(L):void 0,search:g,regex:y},`${k.timestamp}-${k.service}-${L}`,!1,void 0,this))},void 0,!1,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function HM(e){let{id:t,palette:r,event:n,serviceWidth:o,msgWidth:i,expanded:s,active:a,showTimestamps:c=!0,showMeta:l=!0,onPick:d,search:u="",regex:f=!1}=e,p=fx(Uc(n.message,i),i,s);return Je("box",{id:t,flexShrink:0,flexDirection:"column",overflow:"hidden",backgroundColor:a?r.highlight:void 0,onMouseDown:d,children:p.visible.map((m,g)=>Je("box",{height:1,flexDirection:"row",overflow:"hidden",children:[c?Je("box",{width:Bc,flexShrink:0,overflow:"hidden",children:Je("text",{fg:r.muted,children:g===0?n.timestamp.slice(11,19):""},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,Je("box",{width:o,flexShrink:0,overflow:"hidden",children:Je("text",{fg:g===0?Ze(n.service,r):r.muted,wrapMode:"none",children:g===0?oe(n.service,o):""},void 0,!1,void 0,this)},void 0,!1,void 0,this),Je("box",{width:ks,flexShrink:0,overflow:"hidden",children:Je("text",{children:" "},void 0,!1,void 0,this)},void 0,!1,void 0,this),Je("box",{width:Wc,flexShrink:0,overflow:"hidden",children:Je("text",{fg:g===0?et(r,n.level):r.muted,children:g===0?jc(String(n.level)):""},void 0,!1,void 0,this)},void 0,!1,void 0,this),l?Je("box",{width:Hc,flexShrink:0,overflow:"hidden",children:Je("text",{fg:r.muted,children:g===0&&n.source?oe(n.source,Hc):""},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,Je("box",{width:ks,flexShrink:0,overflow:"hidden",children:Je("text",{fg:r.muted,children:g===0?" ":"\u2502"},void 0,!1,void 0,this)},void 0,!1,void 0,this),Je("box",{flexGrow:1,overflow:"hidden",children:Je(BM,{palette:r,level:n.level,text:m,mark:g===0?p.mark:"",search:u,regex:f},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},`${n.timestamp}-${g}`,!0,void 0,this))},void 0,!1,void 0,this)}function BM(e){let{palette:t,level:r,text:n,mark:o,search:i="",regex:s=!1}=e;return Je("text",{wrapMode:"none",children:[gx(n,i,s).map((a,c)=>Je("span",{fg:Gv(t,r,a.kind),children:a.text},`${a.kind}-${c}`,!1,void 0,this)),o===""?null:Je("span",{fg:t.muted,children:o},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var FM=200,NM=2;var WS=D(()=>{It();hr();tt()});var yf=D(()=>{FS();NS();HS();WS()});import{useRef as WM}from"react";import{jsxDEV as Le}from"@opentui/react/jsx-dev-runtime";function Pl(e){let{palette:t,names:r,snap:n,selected:o,checked:i,width:s,onOpen:a,onSelectIndex:c,onToggle:l}=e,d=ve(),u=WM(!1),f=new Set(i),p=hp(s),m=gp(s),g=mp(s),y=nx(s);return Le("box",{flexDirection:"column",flexGrow:1,overflow:"hidden",children:[Le("box",{height:1,flexDirection:"row",overflow:"hidden",children:[Le(Ut,{width:2,fg:t.muted,text:""},void 0,!1,void 0,this),Le(Ut,{width:4,fg:t.accent,text:"sel"},void 0,!1,void 0,this),Le(Ut,{width:2,fg:t.muted,text:""},void 0,!1,void 0,this),Le(Ut,{width:y,fg:t.muted,text:"name"},void 0,!1,void 0,this),Le(Ut,{width:$t,fg:t.muted,text:""},void 0,!1,void 0,this),Le(Ut,{width:Er,fg:t.muted,text:"state"},void 0,!1,void 0,this),g?Le(Ut,{width:Hr,fg:t.muted,text:"health"},void 0,!1,void 0,this):null,m?Le(Ut,{width:Tc,fg:t.muted,text:"port"},void 0,!1,void 0,this):null,p?Le(Ut,{width:nn,fg:t.muted,text:"pid"},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),r.map((x,h)=>{let P=n?.services[x],_=tr(P),w=P?.health??"UNKNOWN",v=f.has(x),b=h===o,S=v?"[x]":"[ ]",k=b?"\u203A":" ";return Le("box",{height:d.rowH,flexDirection:"row",overflow:"hidden",backgroundColor:b?t.highlight:v?t.element:void 0,onMouseDown:()=>{if(u.current)return;if(b){a(x);return}c(h)},children:[Le("box",{width:2,flexShrink:0,children:Le("text",{fg:t.primary,children:k},void 0,!1,void 0,this)},void 0,!1,void 0,this),Le("box",{width:4,flexShrink:0,onMouseDown:()=>{u.current=!0,l?.(x),setTimeout(()=>{u.current=!1},0)},children:Le("text",{fg:jM(t,v,b),children:S},void 0,!1,void 0,this)},void 0,!1,void 0,this),Le("box",{width:2,flexShrink:0,children:Le("text",{fg:et(t,_),children:gr(_)},void 0,!1,void 0,this)},void 0,!1,void 0,this),Le(Ut,{width:y,fg:b?t.primary:Ze(x,t),text:oe(x,y)},void 0,!1,void 0,this),Le(Ut,{width:$t,fg:t.muted,text:""},void 0,!1,void 0,this),Le(Ut,{width:Er,fg:et(t,_),text:_},void 0,!1,void 0,this),g?Le(Ut,{width:Hr,fg:et(t,w),text:w},void 0,!1,void 0,this):null,m?Le(Ut,{width:Tc,fg:t.text,text:fo(P)},void 0,!1,void 0,this):null,p?Le(Ut,{width:nn,fg:t.text,text:P?.pid?String(P.pid):""},void 0,!1,void 0,this):null]},x,!0,void 0,this)})]},void 0,!0,void 0,this)}function _l(e){let{palette:t,checked:r}=e,n=r.length;if(n>0)return Le(dt,{palette:t,items:[{text:`${n} selected`,tone:"primary"}],hints:[{key:"space",label:"unmark"},{key:"n",label:"start these"},{key:"x",label:"stop these"}]},void 0,!1,void 0,this);return null}function jM(e,t,r){if(t)return e.success;if(r)return e.primary;return e.muted}function Ut(e){return Le("box",{width:e.width,flexShrink:0,overflow:"hidden",children:Le("text",{fg:e.fg,wrapMode:"none",children:e.text},void 0,!1,void 0,this)},void 0,!1,void 0,this)}var vf=D(()=>{Ot();It();At();ke();tt()});import{jsxDEV as fe}from"@opentui/react/jsx-dev-runtime";function GS(e){let{palette:t,cfg:r,snap:n,logs:o,names:i,selected:s,selectedLog:a=-1,checked:c,width:l,followTick:d,logService:u,logSources:f,errorOnly:p,showSystemLogs:m,onOpen:g,onSelectIndex:y,onToggle:x,onFilterService:h,onToggleErrors:P,onShowErrors:_,wrapMode:w="clip",view:v,follow:b=!0,onLeaveLatest:S,onPickLog:k,viewStart:L=0,viewTotal:I,newer:F=0,onJumpLatest:Q,facets:G,leftover:C,search:B="",regex:W=!1}=e,V=ve();if(!r||i.length===0)return fe(Ve,{palette:t,title:"No services configured",body:"Add services to .devctl/config.yaml or finish setup.",hint:"open the setup screen with /setup"},void 0,!1,void 0,this);let de=l<si,Ee=Mc(n),$e=tl(n,i),ne=i.filter((he)=>n?.services[he]?.state==="FAILED").length,H=Sx(o),ae=Ee?sx(C,n?.session_id):void 0,X=Dc(l,i,de),nt=Math.max(24,de?l-4:l-X-4),pt=m?o:o.filter((he)=>!oi(he.source)),ie=go(o,{service:u,errorOnly:p,systemLogs:m,search:B,regex:W}),Ae=v??ie,De=I??ie.length,Ke=Math.min(De,L+Ae.length),ht=De===0?"empty":`${L+1}\u2013${Ke} of ${De}`,Ye=u===""?"all":u,Ct=[...$e.running>0?[{text:`${$e.running}/${i.length}`,tone:"success"}]:[],...ne>0?[{text:`${ne} failed`,tone:"error"}]:[],...c.length>0?[{text:`${c.length} selected`,tone:"primary"}]:[],...H>0?[{text:`${H} errors`,tone:"error",onMouseDown:_}]:[]];return fe("box",{flexGrow:1,flexDirection:de?"column":"row",overflow:"hidden",children:[fe("box",{flexGrow:de?1:0,flexShrink:0,minWidth:de?void 0:X,width:de?void 0:X,border:!0,borderStyle:"rounded",borderColor:Ee?t.border:t.borderActive,backgroundColor:t.panel,title:"services",titleColor:t.primary,flexDirection:"column",overflow:"hidden",children:[Ct.length>0?fe(dt,{palette:t,items:Ct},void 0,!1,void 0,this):null,fe(_l,{palette:t,checked:c},void 0,!1,void 0,this),fe("box",{flexGrow:1,paddingLeft:V.pad,paddingRight:V.pad,overflow:"hidden",children:fe(Pl,{palette:t,names:i,snap:n,selected:s,checked:c,width:Lc(X,V.pad),onOpen:g,onSelectIndex:y,onToggle:x},void 0,!1,void 0,this)},void 0,!1,void 0,this),fe(qM,{palette:t,names:i,snap:n,width:X,onOpen:g},void 0,!1,void 0,this),ae?fe(JM,{palette:t,leftover:ae,width:X},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),fe("box",{position:"relative",flexGrow:2,minWidth:de?void 0:32,minHeight:de?8:void 0,border:!0,borderStyle:"rounded",borderColor:t.border,backgroundColor:t.panel,title:`logs \xB7 ${Ye} \xB7 ${ht}`,titleColor:t.primary,overflow:"hidden",flexDirection:"column",children:[fe(vi,{palette:t,logs:pt,names:f??i,service:u,errorOnly:p,width:nt,onService:h,onToggleErrors:P,facets:G},void 0,!1,void 0,this),De>Ae.length?fe(Ms,{palette:t,start:L,count:Ae.length,total:De},void 0,!1,void 0,this):null,ie.length===0?fe(Ve,{palette:t,title:Ee?"Waiting for logs":o.length===0?"No events yet":"No events in this filter",body:Ee?"Start services to stream output here. Later starts keep earlier logs.":o.length===0?"New lines appear as services write output.":"Click All or another service chip, or turn off ERROR+.",hint:"\u2190 \u2192 cycle service e errors g latest"},void 0,!1,void 0,this):fe(xi,{palette:t,logs:Ae,width:nt,followTick:d,focused:!1,wrapMode:w,selected:a,follow:b,onLeaveLatest:S,onPick:k,viewStart:L,search:B,regex:W},d,!1,void 0,this),!b?fe(yi,{palette:t,width:nt,newer:F,bottom:0,onJump:Q},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}function UM(e){if(!e)return!1;return e.state===lt||e.health===Me||e.state===fr||e.restarts>0||e.last_error!==""}function jS(e){if(e.state===lt)return 0;if(e.health===Me)return 1;if(e.state===fr)return 2;return 3}function KM(e){if(e.last_error!=="")return e.last_error;if(e.restarts>0)return`restarted ${e.restarts}x, no error recorded`;return e.health===Me?"failing health check":e.state.toLowerCase()}function qM(e){let{palette:t,names:r,snap:n,width:o,onOpen:i}=e,s=r.map((d)=>({name:d,rt:n?.services[d]})).filter((d)=>UM(d.rt)).sort((d,u)=>jS(d.rt)-jS(u.rt));if(s.length===0)return null;let a=s.slice(0,GM),c=s.length-a.length,l=Math.max(8,o-xf-4);return fe("box",{flexDirection:"column",flexShrink:0,paddingLeft:1,paddingRight:1,overflow:"hidden",children:[fe("box",{height:1,overflow:"hidden",children:fe("text",{fg:t.error,wrapMode:"none",children:`issues (${s.length})`},void 0,!1,void 0,this)},void 0,!1,void 0,this),a.map(({name:d,rt:u})=>{let f=tr(u);return fe("box",{height:1,flexDirection:"row",overflow:"hidden",onMouseDown:()=>i(d),children:[fe("box",{width:2,flexShrink:0,children:fe("text",{fg:et(t,f),children:gr(f)},void 0,!1,void 0,this)},void 0,!1,void 0,this),fe("box",{width:xf,flexShrink:0,overflow:"hidden",children:fe("text",{fg:Ze(d,t),wrapMode:"none",children:oe(d,xf)},void 0,!1,void 0,this)},void 0,!1,void 0,this),fe("box",{width:l,flexShrink:0,overflow:"hidden",children:fe("text",{fg:t.muted,wrapMode:"none",children:_e(KM(u),l)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},d,!0,void 0,this)}),c>0?fe("text",{fg:t.muted,wrapMode:"none",children:`+${c} more \u2014 open a service to see details`},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}function JM(e){let{palette:t,leftover:r,width:n}=e,o=r.processes.slice(0,VM),i=r.processes.length-o.length,s=Math.max(8,n-bf-4);return fe("box",{flexDirection:"column",flexShrink:0,paddingLeft:1,paddingRight:1,overflow:"hidden",children:[fe("box",{height:1,overflow:"hidden",children:fe("text",{fg:t.warning,wrapMode:"none",children:"last session"},void 0,!1,void 0,this)},void 0,!1,void 0,this),fe("text",{fg:t.muted,wrapMode:"none",children:_e(`${r.session_id} ${r.profile||"(none)"}`,Math.max(8,n-4))},void 0,!1,void 0,this),o.map((a)=>fe("box",{height:1,flexDirection:"row",overflow:"hidden",children:[fe("box",{width:bf,flexShrink:0,overflow:"hidden",children:fe("text",{fg:t.text,wrapMode:"none",children:oe(a.name,bf)},void 0,!1,void 0,this)},void 0,!1,void 0,this),fe("box",{width:s,flexShrink:0,overflow:"hidden",children:fe("text",{fg:t.muted,wrapMode:"none",children:_e(`pid ${a.pid} leftover`,s)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},`${a.name}-${a.pid}`,!0,void 0,this)),i>0?fe("text",{fg:t.muted,wrapMode:"none",children:`+${i} more leftover pids`},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}var GM=4,xf=14,VM=4,bf=14;var US=D(()=>{Tt();zt();Ot();yr();It();hr();At();nl();ke();tt();yf();vf()});import zM from"cli-spinners";import{useEffect as YM,useState as XM}from"react";import{jsxDEV as re}from"@opentui/react/jsx-dev-runtime";function KS(e){let{palette:t,report:r,loading:n,progress:o,error:i,selected:s,onPick:a,onReload:c}=e,l=di(s,Sf);if(n)return re(QM,{palette:t,progress:o},void 0,!1,void 0,this);if(i)return re(Tb,{palette:t,title:"Doctor failed",body:i},void 0,!1,void 0,this);if(!r)return re(Ve,{palette:t,title:"Doctor",body:"Diagnostics have not run yet.",hint:"opening this screen runs doctor"},void 0,!1,void 0,this);let d=r.checks.filter((y)=>y.severity==="ok").length,u=r.checks.filter((y)=>y.severity==="warn").length,f=r.checks.filter((y)=>y.severity==="error").length,p=r.checks.length,m=ni(p>0?d/p:0),g=f>0?t.error:u>0?t.warning:t.success;return re(kt,{palette:t,title:`doctor ${r.issues} issue(s)`,children:[re("box",{height:4,flexDirection:"column",flexShrink:0,alignItems:"center",justifyContent:"center",overflow:"hidden",border:!0,borderStyle:"rounded",borderColor:t.primary,backgroundColor:t.element,onMouseDown:c,children:[re("text",{wrapMode:"none",children:[re("span",{fg:t.inverse,bg:t.primary,children:" r "},void 0,!1,void 0,this),re("span",{fg:t.primary,children:" RUN DOCTOR AGAIN"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),re("text",{fg:t.muted,wrapMode:"none",children:"results stay cached until you refresh"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),re("box",{height:1,flexShrink:0},void 0,!1,void 0,this),re("box",{height:1,flexShrink:0,overflow:"hidden",children:re("text",{wrapMode:"none",children:[re("span",{fg:g,children:`[${m}]`},void 0,!1,void 0,this),re("span",{fg:t.success,children:` ${d} ok`},void 0,!1,void 0,this),u>0?re("span",{fg:t.warning,children:` \xB7 ${u} warn`},void 0,!1,void 0,this):null,f>0?re("span",{fg:t.error,children:` \xB7 ${f} error`},void 0,!1,void 0,this):null,u===0&&f===0?re("span",{fg:t.muted,children:" \xB7 all clear"},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)},void 0,!1,void 0,this),re("box",{height:1,flexShrink:0},void 0,!1,void 0,this),re("scrollbox",{ref:l,focused:!1,stickyScroll:!1,scrollX:!1,style:je(t),children:re("box",{flexDirection:"column",overflow:"hidden",gap:1,children:r.checks.map((y,x)=>re(ZM,{palette:t,check:y,index:x,active:x===s,onPick:a},`${y.name}-${x}`,!1,void 0,this))},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function QM({palette:e,progress:t}){let[r,n]=XM(0);return YM(()=>{let o=setInterval(()=>n((i)=>(i+1)%wf.frames.length),wf.interval);return()=>clearInterval(o)},[]),re("box",{flexGrow:1,alignItems:"center",justifyContent:"center",overflow:"hidden",children:re("box",{width:"80%",maxWidth:52,height:10,flexShrink:1,border:!0,borderStyle:"rounded",borderColor:e.borderActive,backgroundColor:e.panel,flexDirection:"column",alignItems:"center",justifyContent:"center",overflow:"hidden",children:[re("text",{wrapMode:"none",children:[re("span",{fg:e.primary,children:`${wf.frames[r]} `},void 0,!1,void 0,this),re("span",{fg:e.text,children:"devctl doctor"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),re("box",{height:1,flexShrink:0},void 0,!1,void 0,this),re("text",{fg:e.text,wrapMode:"none",children:`Checking ${t.active}`},void 0,!1,void 0,this),re("box",{height:1,flexShrink:0},void 0,!1,void 0,this),t.checks.slice(-4).map((o,i)=>re("text",{wrapMode:"none",children:[re("span",{fg:et(e,o.severity==="error"?"ERROR":o.severity==="warn"?"WARN":"OK"),children:`${gr(o.severity==="error"?"ERROR":o.severity==="warn"?"WARN":"OK")} `},void 0,!1,void 0,this),re("span",{fg:e.muted,children:o.name},void 0,!1,void 0,this)]},`${o.name}-${i}`,!0,void 0,this)),t.checks.length===0?re("text",{fg:e.muted,children:"Waiting for the first result\u2026"},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}function ZM(e){let{palette:t,check:r,index:n,active:o,onPick:i}=e,s=r.severity==="error"?"ERROR":r.severity==="warn"?"WARN":"OK";if(r.severity==="ok")return re("box",{id:`${Sf}-${n}`,flexDirection:"row",flexShrink:0,paddingLeft:1,overflow:"hidden",backgroundColor:o?t.highlight:void 0,onMouseDown:()=>i(n),children:re("text",{wrapMode:"none",children:[re("span",{fg:t.primary,children:o?"\u203A ":" "},void 0,!1,void 0,this),re("span",{fg:et(t,s),children:gr(s)},void 0,!1,void 0,this),re("span",{fg:o?t.primary:t.text,children:` ${r.name}`},void 0,!1,void 0,this),re("span",{fg:t.muted,children:r.message?` \xB7 ${r.message}`:""},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this);let c=r.severity==="error"?t.error:t.warning;return re("box",{id:`${Sf}-${n}`,border:!0,borderStyle:"rounded",borderColor:o?t.borderActive:c,title:r.name,titleColor:c,flexDirection:"column",flexShrink:0,paddingLeft:1,paddingRight:1,overflow:"hidden",backgroundColor:o?t.highlight:void 0,onMouseDown:()=>i(n),children:[re("text",{wrapMode:"word",children:[re("span",{fg:et(t,s),children:`${gr(s)} `},void 0,!1,void 0,this),re("span",{fg:t.text,children:r.message},void 0,!1,void 0,this)]},void 0,!0,void 0,this),r.hint?re("text",{fg:t.info,wrapMode:"word",children:`\u2192 ${r.hint}`},void 0,!1,void 0,this):null,o&&r.action?.kind==="free-port"?re("text",{fg:t.primary,wrapMode:"none",children:`enter stop ${r.action.holder.command} (pid ${r.action.holder.pid})`},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}var Sf="doctor-row",wf;var qS=D(()=>{zt();It();ke();tt();wf=zM.bouncingBall});import{jsxDEV as Ge,Fragment as eO}from"@opentui/react/jsx-dev-runtime";function JS(e){let{palette:t,logs:r,names:n,logSources:o,service:i,errorOnly:s,showSystemLogs:a,search:c,searchFocused:l,followTick:d,width:u,onSearch:f,onService:p,onToggleErrors:m,onSelect:g,wrapMode:y="focus",selected:x=-1,follow:h=!0,newer:P=0,viewStart:_=0,viewTotal:w,onLeaveLatest:v,onJumpLatest:b,view:S,fullscreen:k=!1,facets:L,split:I=!1,splitFocus:F=0,serviceB:Q="",viewB:G,selectedB:C=-1,followB:B=!0,newerB:W=0,viewStartB:V=0,viewTotalB:de,onServiceB:Ee,onSelectB:$e,onLeaveLatestB:ne,onFocusPane:H}=e,ae=ve(),X=vp(u,ae.pad,k),nt=a?r:r.filter((he)=>!oi(he.source)),pt=go(r,{service:i,services:e.services,errorOnly:s,search:c,regex:e.regex,source:e.source,systemLogs:a}),ie=S??pt,Ae=w??pt.length,De=Math.min(Ae,_+ie.length),Ke=Ae===0?"empty":`${_+1}\u2013${De} of ${Ae}`,ht=e.services??[],Ye=ht.length>0?ht.join(","):i===""?"all services":i,Ct=[...h?[]:[{text:P>0?`${I?"left ":""}pinned \xB7 +${P} new`:`${I?"left ":""}pinned`,tone:"warning"}],...I&&!B?[{text:W>0?`right pinned \xB7 +${W} new`:"right pinned",tone:"warning"}]:[],...e.source?[{text:`src ${e.source}`,tone:"info"}]:[],...e.regex?[{text:"regex",tone:"accent"}]:[],...c===""?[]:[{text:`search ${c}`,tone:"accent"}],...I?[{text:F===0?"left pane":"right pane",tone:"accent"}]:[]];if(I){let he=Math.max(20,Math.floor(u/2)),yt=Math.max(20,u-he),st=G??[],vt=de??st.length;return Ge("box",{flexGrow:1,flexDirection:"column",overflow:"hidden",children:[l?Ge("box",{height:1,paddingLeft:1,backgroundColor:t.highlight,overflow:"hidden",children:Ge("input",{focused:!0,value:c,placeholder:"search messages or service names",onInput:f,backgroundColor:t.highlight,focusedBackgroundColor:t.highlight,textColor:t.text,cursorColor:t.primary},void 0,!1,void 0,this)},void 0,!1,void 0,this):Ge(dt,{palette:t,items:Ct},void 0,!1,void 0,this),Ge("box",{flexGrow:1,flexDirection:"row",overflow:"hidden",children:[Ge(VS,{palette:t,names:o??n,logs:r,service:i,errorOnly:s,showSystemLogs:a,search:c,regex:e.regex,source:e.source,width:he,shown:ie,shownTotal:Ae,selected:x,follow:h,newer:P,viewStart:_,wrapMode:y,showTimestamps:e.showTimestamps!==!1,showMeta:e.showMeta!==!1,followTick:d,facets:L,focused:F===0,onService:p,onToggleErrors:m,onSelect:g,onLeaveLatest:v,onFocus:()=>H?.(0)},void 0,!1,void 0,this),Ge(VS,{palette:t,names:o??n,logs:r,service:Q,errorOnly:s,showSystemLogs:a,search:c,regex:e.regex,source:e.source,width:yt,shown:st,shownTotal:vt,selected:C,follow:B,newer:W,viewStart:V,wrapMode:y,showTimestamps:e.showTimestamps!==!1,showMeta:e.showMeta!==!1,followTick:d,facets:L,focused:F===1,onService:Ee??p,onToggleErrors:m,onSelect:$e,onLeaveLatest:ne,onFocus:()=>H?.(1)},void 0,!1,void 0,this)]},void 0,!0,void 0,this),!h||!B?Ge(yi,{palette:t,width:u,newer:Math.max(P,W),onJump:b},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}return Ge("box",{flexGrow:1,flexDirection:"column",overflow:"hidden",children:[k?null:Ge(eO,{children:[Ct.length>0?Ge(dt,{palette:t,items:Ct},void 0,!1,void 0,this):null,Ge(vi,{palette:t,logs:nt,names:o??n,service:i,errorOnly:s,width:u,onService:p,onToggleErrors:m,facets:L},void 0,!1,void 0,this)]},void 0,!0,void 0,this),l?Ge("box",{height:1,paddingLeft:1,backgroundColor:t.highlight,overflow:"hidden",children:Ge("input",{focused:!0,value:c,placeholder:"search messages or service names",onInput:f,backgroundColor:t.highlight,focusedBackgroundColor:t.highlight,textColor:t.text,cursorColor:t.primary},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,S&&Ae>ie.length?Ge(Ms,{palette:t,start:_,count:ie.length,total:Ae},void 0,!1,void 0,this):null,Ge("box",{flexGrow:1,border:!k,borderStyle:"rounded",borderColor:t.border,title:k?void 0:`logs ${Ye} \xB7 ${Ke}`,titleColor:t.primary,padding:k?0:ae.pad,overflow:"hidden",children:ie.length===0?Ge(Ve,{palette:t,title:r.length===0?"No log events":"No events in this filter",body:r.length===0?"Start services to stream logs.":"Pick All, another service, or clear search / ERROR+.",hint:`\u2190 \u2192 cycle filters e errors i internal ${Mt("l")} clear`},void 0,!1,void 0,this):Ge(xi,{palette:t,logs:ie,width:X,followTick:d,focused:!1,wrapMode:y,selected:x,follow:h,showTimestamps:e.showTimestamps!==!1,showMeta:e.showMeta!==!1,onPick:g,onLeaveLatest:v,viewStart:_,search:c,regex:e.regex===!0},d,!1,void 0,this)},void 0,!1,void 0,this),!h?Ge(yi,{palette:t,width:u,newer:P,onJump:b},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}function VS(e){let{palette:t,width:r,service:n,shown:o,shownTotal:i,viewStart:s,focused:a}=e,c=e.showSystemLogs?e.logs:e.logs.filter((u)=>!oi(u.source)),l=n===""?"all services":n,d=i===0?"empty":`${s+1}\u2013${Math.min(i,s+o.length)} of ${i}`;return Ge("box",{width:r,flexGrow:0,flexShrink:0,flexDirection:"column",overflow:"hidden",onMouseDown:e.onFocus,children:[Ge(vi,{palette:t,logs:c,names:e.names,service:n,errorOnly:e.errorOnly,width:r,onService:(u)=>{e.onFocus(),e.onService(u)},onToggleErrors:()=>{e.onFocus(),e.onToggleErrors()},facets:e.facets},void 0,!1,void 0,this),Ge("box",{flexGrow:1,border:!0,borderStyle:"rounded",borderColor:a?t.borderActive:t.border,title:`logs ${l} \xB7 ${d}`,titleColor:a?t.primary:t.muted,overflow:"hidden",children:o.length===0?Ge(Ve,{palette:t,title:"No events in this pane",body:"Click the pane, then \u2190 \u2192 to pick a service.",hint:"\\\\ split | focus"},void 0,!1,void 0,this):Ge(xi,{palette:t,logs:o,width:vp(r,0,!1),followTick:e.followTick,focused:!1,wrapMode:e.wrapMode,selected:e.selected,follow:e.follow,showTimestamps:e.showTimestamps,showMeta:e.showMeta,onPick:(u)=>{e.onFocus(),e.onSelect?.(u)},onLeaveLatest:e.onLeaveLatest,viewStart:s,search:e.search,regex:e.regex===!0},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var zS=D(()=>{yf();zt();Ot();hr();ke();Pr()});import{jsxDEV as Ue}from"@opentui/react/jsx-dev-runtime";function XS(e){let{palette:t,cfg:r,snap:n,profile:o,selected:i,onPick:s}=e,a=ve(),c=di(i,YS),l=Object.keys(r?.profiles??{}).sort();if(l.length===0)return Ue(Ve,{palette:t,title:"No profiles",body:"Add profiles.<name>.services in config.yaml."},void 0,!1,void 0,this);return Ue(kt,{palette:t,title:"profiles",children:[Ue("box",{height:1,flexShrink:0,overflow:"hidden",children:Ue("text",{fg:t.muted,wrapMode:"none",children:[`${l.length} profile${l.length===1?"":"s"} \xB7 current: `,Ue("span",{fg:t.success,children:o||"(none)"},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this),Ue("box",{height:1,flexShrink:0},void 0,!1,void 0,this),Ue("scrollbox",{ref:c,focused:!1,stickyScroll:!1,scrollX:!1,style:je(t),children:Ue("box",{flexDirection:"column",overflow:"hidden",gap:a.gap||1,children:l.map((d,u)=>{let f=d===o,p=u===i,m=r?.profiles[d]?.services??[],g=Object.keys(r?.profiles[d]?.environment??{}).length,y=p?t.borderActive:f?t.success:t.border,x=p?t.primary:f?t.success:t.muted;return Ue("box",{id:`${YS}-${u}`,border:!0,borderStyle:"rounded",borderColor:y,title:d,titleColor:x,flexDirection:"column",flexShrink:0,paddingLeft:1,paddingRight:1,overflow:"hidden",backgroundColor:p?t.highlight:void 0,onMouseDown:()=>s(u),children:[Ue("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:Ue("text",{wrapMode:"none",children:[f?Ue("span",{fg:t.success,children:"\u25CF current profile"},void 0,!1,void 0,this):Ue("span",{fg:t.muted,children:"\u25CB not active"},void 0,!1,void 0,this),Ue("span",{fg:t.muted,children:` \xB7 ${m.length} service${m.length===1?"":"s"}`},void 0,!1,void 0,this),g>0?Ue("span",{fg:t.muted,children:` \xB7 ${g} env var${g===1?"":"s"}`},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)},void 0,!1,void 0,this),Ue("box",{flexDirection:"row",overflow:"hidden",flexShrink:0,children:m.length===0?Ue("text",{fg:t.muted,wrapMode:"none",children:"(no services)"},void 0,!1,void 0,this):Ue("text",{wrapMode:"word",children:m.map((h,P)=>{let _=tr(n?.services[h]);return Ue("span",{children:[Ue("span",{fg:et(t,_),children:gr(_)},void 0,!1,void 0,this),Ue("span",{fg:Ze(h,t),children:` ${h}`},void 0,!1,void 0,this),P<m.length-1?Ue("span",{fg:t.muted,children:" "},void 0,!1,void 0,this):null]},h,!0,void 0,this)})},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},d,!0,void 0,this)})},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ue(jt,{palette:t,backgroundColor:t.element,edge:"top",children:Ue(ot,{palette:t,hints:[{key:"space",label:"set current"},{key:"enter",label:"set and start"}]},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var YS="profile-row";var QS=D(()=>{zt();Ot();At();ke();tt()});import{jsxDEV as q,Fragment as rk}from"@opentui/react/jsx-dev-runtime";function tO(e){let t=e.toLowerCase();if(t==="iap")return"info";if(t==="service"||t==="service_account")return"primary";return"idle"}function rO(e,t,r){if(r||t>=500)return e.error;if(t===0||t>=400)return e.warning;return e.success}function nO(e,t){if(t.startsWith("sa:"))return{label:"SA",color:e.primary};if(t!=="")return{label:"USR",color:e.info};return{label:"\u2014",color:e.muted}}function aO(e){let{palette:t,req:r,cols:n}=e,o=rO(t,r.status,r.error),i=r.status>0?String(r.status):"ERR",s=nO(t,r.identity),a=r.route||nk,c=r.error?`${r.path} \u2014 ${_e(r.error,sO)}`:r.path;return q("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:[q("box",{width:kf,flexShrink:0,overflow:"hidden",children:q("text",{fg:t.muted,children:r.timestamp.slice(11,19)},void 0,!1,void 0,this)},void 0,!1,void 0,this),n.showMethod?q("box",{width:El,flexShrink:0,overflow:"hidden",children:q("text",{fg:t.text,wrapMode:"none",children:oe(r.method,El)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,q("box",{width:Tl,flexShrink:0,overflow:"hidden",children:q("text",{fg:o,wrapMode:"none",children:oe(i,Tl)},void 0,!1,void 0,this)},void 0,!1,void 0,this),n.showDur?q("box",{width:Al,flexShrink:0,overflow:"hidden",children:q("text",{fg:t.muted,wrapMode:"none",children:oe(`${r.durationMs}ms`,Al)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,q("box",{width:Ll,flexShrink:0,overflow:"hidden",children:q("text",{fg:s.color,wrapMode:"none",children:oe(s.label,Ll)},void 0,!1,void 0,this)},void 0,!1,void 0,this),q("box",{width:n.routeCol,flexShrink:0,overflow:"hidden",children:q("text",{fg:r.route?t.info:t.muted,wrapMode:"none",children:oe(a,n.routeCol)},void 0,!1,void 0,this)},void 0,!1,void 0,this),q("box",{flexGrow:1,overflow:"hidden",children:q("text",{fg:r.error?t.error:t.text,wrapMode:"none",children:c},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function cO(e){let{palette:t,cols:r}=e;return q("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:t.element,children:[q("box",{width:kf,flexShrink:0,overflow:"hidden",children:q("text",{fg:t.muted,children:oe("TIME",kf)},void 0,!1,void 0,this)},void 0,!1,void 0,this),r.showMethod?q("box",{width:El,flexShrink:0,overflow:"hidden",children:q("text",{fg:t.muted,children:oe("METHOD",El)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,q("box",{width:Tl,flexShrink:0,overflow:"hidden",children:q("text",{fg:t.muted,children:oe("ST",Tl)},void 0,!1,void 0,this)},void 0,!1,void 0,this),r.showDur?q("box",{width:Al,flexShrink:0,overflow:"hidden",children:q("text",{fg:t.muted,children:oe("DUR",Al)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,q("box",{width:Ll,flexShrink:0,overflow:"hidden",children:q("text",{fg:t.muted,children:oe("WHO",Ll)},void 0,!1,void 0,this)},void 0,!1,void 0,this),q("box",{width:r.routeCol,flexShrink:0,overflow:"hidden",children:q("text",{fg:t.muted,children:oe("ROUTE",r.routeCol)},void 0,!1,void 0,this)},void 0,!1,void 0,this),q("box",{flexGrow:1,overflow:"hidden",children:q("text",{fg:t.muted,children:"PATH"},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function lO(e){let{palette:t,name:r,auth:n,identity:o,upstream:i,match:s,onMouseDown:a}=e;return q("box",{flexDirection:"column",flexShrink:0,overflow:"hidden",onMouseDown:a,children:[q("text",{fg:t.text,wrapMode:"none",children:r},void 0,!1,void 0,this),q("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:[q(We,{palette:t,label:n||"no auth",tone:tO(n)},void 0,!1,void 0,this),o?q(We,{palette:t,label:o,tone:"idle"},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),s?q("text",{fg:t.muted,wrapMode:"word",children:`match: ${s}`},void 0,!1,void 0,this):null,q("text",{fg:t.muted,wrapMode:"word",children:`\u2192 ${i}`},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function ok(e){let{palette:t,cfg:r,snap:n,width:o,onSelectRoute:i}=e,s=n?.proxy.routes??[],a=new Map((r?.proxy.routes??[]).map((w)=>[w.name,w])),c=new Map((r?.proxy.routes??[]).map((w)=>[w.name,w.match])),l=n?.proxy.recentRequests??[],d=n?.proxy.requestTotal??0,u=n?.proxy.requestErrors??0,f=o<si,p=Math.max(ZS,Math.min(dO,Math.floor(o*0.36))),m=f?o-ek:o-p-ek,g=Math.max(20,m-2),y=g>=tk,x=g>=uO,h=g>=tk?pO:fO,P=l.reduce((w,v)=>Math.min(h,Math.max(w,(v.route||nk).length+iO)),oO),_={showMethod:y,showDur:x,routeCol:P};return q(kt,{palette:t,title:"proxy",children:[q(dt,{palette:t,items:[{text:n?.proxy.running?"RUNNING":"STOPPED",tone:n?.proxy.running?"success":"idle"},...n?.proxy.address?[{text:n.proxy.address,tone:"info"}]:[],{text:`${s.length} routes`,tone:s.length>0?"primary":"idle"},...d>0?[{text:`${d} requests`,tone:"idle"},...u>0?[{text:`${u} errors`,tone:"warning"}]:[]]:[]]},void 0,!1,void 0,this),q("box",{flexGrow:1,flexDirection:f?"column":"row",overflow:"hidden",children:[q("box",{flexGrow:f?1:0,flexBasis:f?0:void 0,flexShrink:0,minWidth:f?void 0:ZS,width:f?void 0:p,minHeight:f?8:void 0,border:!0,borderStyle:"rounded",borderColor:t.border,title:"routes",titleColor:t.primary,paddingLeft:1,paddingRight:1,flexDirection:"column",overflow:"hidden",children:s.length===0?q(Ve,{palette:t,title:"No proxy routes",body:"Add routes under proxy.routes or .devctl/proxy/routes.yaml."},void 0,!1,void 0,this):q(rk,{children:[i?q("text",{fg:t.muted,wrapMode:"word",children:"click a route for full details"},void 0,!1,void 0,this):null,q("scrollbox",{focused:!1,stickyScroll:!1,scrollX:!1,style:je(t),children:q("box",{flexDirection:"column",overflow:"hidden",gap:1,children:s.map((w)=>{let v=c.get(w.name),b=v&&(v.host||v.path)?`${v.host||"*"}${v.path?v.path:""}`:void 0,S=a.get(w.name);return q(lO,{palette:t,name:w.name,auth:w.auth,identity:w.identity,upstream:w.upstream,match:b,onMouseDown:i?()=>i({name:w.name,authType:S?.auth.type||w.auth,identityType:S?.auth.identity.type??"",serviceAccount:S?.auth.identity.service_account||S?.auth.service_account||"",audience:S?.auth.audience??"",matchHost:S?.match.host??"",matchPath:S?.match.path??"",upstream:w.upstream}):void 0},w.name,!1,void 0,this)})},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this),q("box",{flexGrow:1,flexBasis:0,minWidth:f?void 0:30,minHeight:f?10:void 0,border:!0,borderStyle:"rounded",borderColor:t.borderActive,title:"requests",titleColor:t.primary,flexDirection:"column",overflow:"hidden",children:l.length===0?q("box",{paddingLeft:1,paddingRight:1,children:q("text",{fg:t.muted,wrapMode:"word",children:n?.proxy.running?"No requests seen yet. Send one through the proxy to see it show up here \u2014 no need to restart or press r.":"Start the proxy, then send it a request to see live traffic here."},void 0,!1,void 0,this)},void 0,!1,void 0,this):q(rk,{children:[q(cO,{palette:t,cols:_},void 0,!1,void 0,this),q("scrollbox",{focused:!1,stickyScroll:!1,scrollX:!1,style:je(t),children:q("box",{flexDirection:"column",overflow:"hidden",children:l.map((w)=>q(aO,{palette:t,req:w,cols:_},w.requestId,!1,void 0,this))},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this),q(jt,{palette:t,backgroundColor:t.element,edge:"top",children:q(ot,{palette:t,hints:[{key:"n",label:"start proxy"},{key:"x",label:"stop proxy"}]},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var kf=9,El=8,Tl=4,Al=8,Ll=4,oO=8,iO=2,sO=64,nk="(none)",ZS=26,dO=56,ek=2,tk=46,uO=54,pO=20,fO=12;var ik=D(()=>{zt();yr();It();ke()});import{jsxDEV as pe}from"@opentui/react/jsx-dev-runtime";function ck(e){let{palette:t,name:r}=e;return pe("box",{flexGrow:1,border:!0,borderStyle:"rounded",borderColor:t.borderActive,backgroundColor:t.panel,title:r,titleColor:Ze(r,t),flexDirection:"column",overflow:"hidden",children:pe(Cf,{...e,compact:!1},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function Cf(e){let{palette:t,cfg:r,snap:n,name:o,reveal:i,width:s,compact:a=!0,envScrollRef:c}=e,l=ve(),d=r?.services[o];if(!d)return pe(Ve,{palette:t,title:"Unknown service",body:`No service named ${o}.`,hint:"j/k move"},void 0,!1,void 0,this);let u=n?.services[o],f=tr(u),p=u?.health??"UNKNOWN",m=fo(u)||ws(d,u),g=ix(d,i,r?.secrets.extra_markers??[],r?.secrets.extra_patterns??[],e.resolvedEnv),y=e.envStatus==="error"?"error":e.envStatus==="resolved"?"success":e.envStatus==="loading"?"info":"muted",x=e.envStatus==="resolved"?"resolved":e.envStatus==="loading"?"resolving":e.envStatus==="error"?"config fallback":"config",h=s>=gO,P=[{label:"health",value:$c(d)},{label:"workdir",value:d.working_dir||"."},{label:"restart",value:Nc(d)},{label:"caps",value:d.capabilities.join(", ")||"\u2014"},{label:"runtime",value:d.container?`${d.container.runtime||"docker"} \xB7 ${d.container.image}`:"host"},...d.watch.enabled?[{label:"watch",value:d.watch.paths.join(" ")||"on"}]:[]],_=[{label:"identity",value:Fc(d,u)},{label:"depends",value:d.dependencies.map(xa).join(", ")||"\u2014"},{label:"ports",value:ws(d,u)},{label:"tries",value:String(u?.restarts??0),tone:u?.restarts?"warning":"muted"}],w=Math.max(wo+8,s-2),v=h?Math.floor(w/2):w;return pe("box",{padding:l.pad,flexGrow:1,flexDirection:"column",overflow:"hidden",children:[pe(dt,{palette:t,items:[{text:f,tone:ak(f)},{text:p,tone:ak(p)},{text:u?.pid?`pid ${u.pid}`:"pid \u2014",tone:u?.pid?"info":"idle"},{text:m==="\u2014"?"port \u2014":`port ${m}`,tone:m==="\u2014"?"idle":"info"}]},void 0,!1,void 0,this),d.description?pe("box",{height:1,overflow:"hidden",children:pe("text",{fg:t.muted,wrapMode:"none",children:_e(d.description,Math.max(8,s-4))},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,u?.last_error?pe("box",{height:1,overflow:"hidden",children:pe("text",{fg:t.error,wrapMode:"none",children:_e(u.last_error,mO)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,pe(Rf,{palette:t,item:{label:"command",value:Ic(d)},valueWidth:Math.max(8,w-wo)},void 0,!1,void 0,this),pe(hO,{palette:t,left:P,right:_,wide:h,colWidth:v},void 0,!1,void 0,this),pe(yO,{palette:t,entries:g,reveal:i,width:s,focused:!a,scrollRef:c,source:x,sourceTone:y,error:e.envError,onSelect:e.onSelectEnv},void 0,!1,void 0,this),a?null:pe(ot,{palette:t,hints:[{key:"n",label:"start"},{key:"x",label:"stop"},{key:"R",label:"restart"},{key:"o",label:"config"},{key:"l",label:"logs"},{key:"/reveal",label:"secrets"},{key:"esc",label:"back"}]},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function hO(e){let{palette:t,left:r,right:n,wide:o,colWidth:i}=e,s=Math.max(4,i-wo);if(!o)return pe("box",{width:i,flexShrink:0,flexDirection:"column",overflow:"hidden",children:[...r,...n].map((a)=>pe(Rf,{palette:t,item:a,valueWidth:s},a.label,!1,void 0,this))},void 0,!1,void 0,this);return pe("box",{height:r.length,flexShrink:0,flexDirection:"row",overflow:"hidden",children:[pe(sk,{palette:t,items:r,width:i},void 0,!1,void 0,this),pe(sk,{palette:t,items:n,width:i},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function sk(e){let t=Math.max(4,e.width-wo);return pe("box",{width:e.width,flexShrink:0,flexDirection:"column",overflow:"hidden",children:e.items.map((r)=>pe(Rf,{palette:e.palette,item:r,valueWidth:t},r.label,!1,void 0,this))},void 0,!1,void 0,this)}function Rf(e){let{palette:t,item:r,valueWidth:n}=e,o=r.tone==="warning"?t.warning:r.tone==="muted"?t.muted:t.text;return pe("box",{height:1,width:wo+n,flexDirection:"row",overflow:"hidden",children:[pe("box",{width:wo,flexShrink:0,overflow:"hidden",children:pe("text",{fg:t.muted,wrapMode:"none",children:oe(r.label,wo)},void 0,!1,void 0,this)},void 0,!1,void 0,this),pe("box",{width:n,flexShrink:0,overflow:"hidden",children:pe("text",{fg:o,wrapMode:"none",children:oe(r.value,n)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function yO(e){let{palette:t,entries:r,reveal:n,width:o,focused:i,scrollRef:s,source:a,sourceTone:c,error:l,onSelect:d}=e;return pe("box",{flexGrow:1,flexDirection:"column",overflow:"hidden",children:[pe(dt,{palette:t,items:[{text:`env ${r.length}`,tone:r.length>0?"info":"idle"},{text:a,tone:c},{text:n?"secrets shown":"redacted",tone:n?"warning":"muted"}],hints:i?[{key:"j/k",label:"scroll"}]:[{key:"/reveal",label:"secrets"}]},void 0,!1,void 0,this),l?pe("box",{height:1,overflow:"hidden",children:pe("text",{fg:t.warning,wrapMode:"none",children:_e(l,Math.max(8,o-4))},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,r.length===0?pe("text",{fg:t.muted,children:"no service env overrides"},void 0,!1,void 0,this):pe("box",{flexGrow:1,height:"100%",overflow:"hidden",children:[d?pe("text",{fg:t.muted,wrapMode:"word",children:"click a variable for its full value"},void 0,!1,void 0,this):null,pe("scrollbox",{ref:s,focused:i,stickyScroll:!1,scrollX:!1,style:{rootOptions:{flexGrow:1,height:"100%",overflow:"hidden",backgroundColor:t.panel},viewportOptions:{backgroundColor:t.panel},contentOptions:{backgroundColor:t.panel},scrollbarOptions:{trackOptions:{foregroundColor:t.primary,backgroundColor:t.element}}},children:pe("box",{flexDirection:"column",overflow:"hidden",children:r.map((u)=>{let f=u.required?t.warning:u.fromConfig?t.accent:t.muted,p=u.value===""&&u.required?"(required)":u.value;return pe("box",{flexDirection:"row",overflow:"hidden",onMouseDown:d?()=>d(u):void 0,children:pe("text",{wrapMode:"word",children:[pe("span",{fg:f,children:u.required?`${u.key}*`:u.key},void 0,!1,void 0,this),pe("span",{fg:u.value===""?t.muted:t.text,children:` ${p}`},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},u.key,!1,void 0,this)})},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}function ak(e){switch(e.toUpperCase()){case"HEALTHY":case"RUNNING":return"success";case"UNHEALTHY":case"WARNING":case"WARN":case"STARTING":case"RESTARTING":return"warning";case"FAILED":case"ERROR":return"error";default:return"muted"}}var mO=72,gO=56,wo=9;var Pf=D(()=>{mt();zt();Ot();It();At();ke();tt()});import{jsxDEV as Fn}from"@opentui/react/jsx-dev-runtime";function lk(e){let{palette:t,cfg:r,names:n,snap:o,selected:i,checked:s,width:a,reveal:c,onOpen:l,onSelectIndex:d,onToggle:u,resolvedEnv:f,envStatus:p,envError:m,onSelectEnv:g}=e;if(n.length===0)return Fn(Ve,{palette:t,title:"No services",body:"This configuration does not define any services."},void 0,!1,void 0,this);let y=a<si,x=n[i]??"",h=Dc(a,n,y),P=4,_=y?Math.max(_f,a-P):Math.max(_f,a-h-P);return Fn("box",{flexGrow:1,flexDirection:y?"column":"row",overflow:"hidden",children:[Fn("box",{flexGrow:y?1:0,flexShrink:0,minWidth:y?void 0:Ec,width:y?void 0:h,border:!0,borderStyle:"rounded",borderColor:t.borderActive,backgroundColor:t.panel,title:"services",titleColor:t.primary,flexDirection:"column",overflow:"hidden",children:[Fn(_l,{palette:t,checked:s},void 0,!1,void 0,this),Fn("box",{flexGrow:1,overflow:"hidden",children:Fn(Pl,{palette:t,names:n,snap:o,selected:i,checked:s,width:Lc(h),onOpen:l,onSelectIndex:d,onToggle:u},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this),Fn("box",{flexGrow:2,minWidth:y?void 0:_f,minHeight:y?vO:void 0,border:!0,borderStyle:"rounded",borderColor:t.border,backgroundColor:t.panel,title:x||"detail",titleColor:x?Ze(x,t):t.primary,overflow:"hidden",flexDirection:"column",children:Fn(Cf,{palette:t,cfg:r,snap:o,name:x,reveal:c,width:_,resolvedEnv:f,envStatus:p,envError:m,onSelectEnv:g},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var _f=36,vO=12;var dk=D(()=>{zt();yr();At();tt();Pf();vf()});import{jsxDEV as we}from"@opentui/react/jsx-dev-runtime";function bO(e){switch(e){case"cycle":return"\u2194";case"toggle":return"\u23FB";case"page":return"\u25B8";case"action":return"!";default:return"\xB7"}}function mk(e){let{palette:t,items:r,selected:n,locked:o,width:i,onPick:s,onActivate:a}=e,c=ve(),l=ap(r,n),d=Tn(r,l);return we("box",{flexGrow:1,border:!0,borderStyle:"rounded",borderColor:t.borderActive,backgroundColor:t.panel,title:"settings",titleColor:t.primary,flexDirection:"column",overflow:"hidden",children:[we("box",{height:2,paddingLeft:c.pad,flexShrink:0,flexDirection:"column",overflow:"hidden",children:[we("text",{fg:t.primary,wrapMode:"none",children:"Preferences"},void 0,!1,void 0,this),we("text",{fg:t.muted,wrapMode:"none",children:o?"DEVCTL_TUI_CONFIG is set \u2014 changes stay in this session.":"Changes save to your user tui.json unless noted."},void 0,!1,void 0,this)]},void 0,!0,void 0,this),we("box",{flexGrow:1,paddingLeft:c.pad,paddingRight:c.pad,overflow:"hidden",flexShrink:1,children:we(wO,{palette:t,items:r,active:l,scale:c,width:i,onPick:s,onActivate:a},void 0,!1,void 0,this)},void 0,!1,void 0,this),we(kO,{palette:t,item:d,scale:c},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function wO(e){let{palette:t,items:r,active:n,scale:o,width:i,onPick:s,onActivate:a}=e,c=di(n,fk),l=Math.max(8,i-uk-pk-10),d=0,u=Xv(r);return we("scrollbox",{ref:c,focused:!1,stickyScroll:!1,scrollX:!1,style:je(t),children:we("box",{flexDirection:"column",overflow:"hidden",children:u.map((f,p)=>{let m=d;d+=f.items.length;let g=n>=m&&n<m+f.items.length;return we("box",{flexDirection:"column",flexShrink:0,marginBottom:p<u.length-1?Math.max(1,o.gap):0,overflow:"hidden",children:[we("box",{height:1,overflow:"hidden",children:we("text",{fg:g?t.primary:t.muted,wrapMode:"none",children:f.group},void 0,!1,void 0,this)},void 0,!1,void 0,this),f.group==="MCP"?we("box",{height:1,overflow:"hidden",children:we("text",{fg:t.muted,wrapMode:"none",children:"dedicated page \u2014 also /mcp or /agent"},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,f.items.map((y,x)=>{let h=m+x;return we(SO,{index:h,palette:t,item:y,active:h===n,rowH:o.rowH,valueWidth:l,onPick:()=>s(h),onActivate:()=>a(y)},y.id,!1,void 0,this)})]},f.group,!0,void 0,this)})},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function SO(e){let{palette:t,index:r,item:n,active:o,rowH:i,valueWidth:s,onPick:a,onActivate:c}=e;return we("box",{id:`${fk}-${r}`,height:i,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:o?t.highlight:void 0,onMouseDown:()=>{if(o){c();return}a()},children:[we("box",{width:2,flexShrink:0,overflow:"hidden",children:we("text",{fg:t.primary,children:o?"\u203A":" "},void 0,!1,void 0,this)},void 0,!1,void 0,this),we("box",{width:pk,flexShrink:0,overflow:"hidden",children:we("text",{fg:o?t.primary:t.muted,children:bO(n.kind)},void 0,!1,void 0,this)},void 0,!1,void 0,this),we("box",{width:uk,flexShrink:0,overflow:"hidden",children:we("text",{fg:o?t.primary:t.text,wrapMode:"none",children:n.name},void 0,!1,void 0,this)},void 0,!1,void 0,this),we("box",{flexGrow:1,overflow:"hidden",children:we("text",{fg:CO(t,n,o),wrapMode:"none",children:_e(n.value,s)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function kO(e){let{palette:t,item:r,scale:n}=e,o=r?.id==="font",i=r?.kind==="page"?`${r.name} \u2192 dedicated page`:r?.name??"";return we("box",{height:xO,flexShrink:0,border:!0,borderStyle:"rounded",borderColor:t.border,title:"selected",titleColor:t.muted,backgroundColor:t.element,paddingLeft:n.pad,paddingRight:n.pad,flexDirection:"column",overflow:"hidden",children:[we("box",{height:1,overflow:"hidden",children:we("text",{fg:t.primary,wrapMode:"none",children:o?`${zv(n.steps)} ${n.label}`:i},void 0,!1,void 0,this)},void 0,!1,void 0,this),we("box",{height:1,overflow:"hidden",children:we("text",{fg:t.text,wrapMode:"word",children:r?.detail??""},void 0,!1,void 0,this)},void 0,!1,void 0,this),we("box",{height:1,overflow:"hidden",children:we("text",{fg:t.muted,wrapMode:"none",children:r?r.kind==="info"?"read-only":r.hint:""},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function CO(e,t,r){if(t.id==="mouse")return t.value==="on"?e.success:e.text;if(t.kind==="page")return e.info;if(t.kind==="info")return e.text;return r?e.primary:e.text}var uk=14,pk=2,xO=5,fk="settings-row";var gk=D(()=>{Ot();It();ke();_r()});import{jsxDEV as Te}from"@opentui/react/jsx-dev-runtime";function hk(e){let{palette:t,cfg:r,google:n,bootError:o,bootErrorMissing:i=!1,step:s=0}=e,a=ve();if(o&&!r)return i?Te(Ve,{palette:t,title:"No configuration found",body:"Would you like to run setup?",hint:"[Enter] 9-step setup [Esc] Exit"},void 0,!1,void 0,this):Te(Ve,{palette:t,title:"Configuration error",body:o,hint:"Fix .devctl/config.yaml, then restart devctl [Esc] Exit"},void 0,!1,void 0,this);let c=e.issues,l=RO(r,n,c),d=l.filter((h)=>h.ok).length,u=l.length,f=u>0?Math.round(d/u*100):0,p=20,m=Math.round(f/100*p),g="\u2588".repeat(m)+"\u2591".repeat(Math.max(0,p-m)),y=f===100?t.success:f===0?t.muted:t.primary,x=l[s];return Te(kt,{palette:t,title:"setup",scroll:!0,children:[Te("box",{flexDirection:"row",overflow:"hidden",flexShrink:0,children:Te("text",{fg:t.primary,children:"Developer onboarding"},void 0,!1,void 0,this)},void 0,!1,void 0,this),Te("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:Te("text",{wrapMode:"none",children:[Te("span",{fg:y,children:`[${g}]`},void 0,!1,void 0,this),Te("span",{fg:t.text,children:` ${d}/${u} ready`},void 0,!1,void 0,this),Te("span",{fg:t.muted,children:` (${f}%)`},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this),Te("box",{height:1,flexShrink:0},void 0,!1,void 0,this),l.map((h,P)=>{let _=P===s;return Te("box",{height:a.rowH,flexDirection:"row",overflow:"hidden",backgroundColor:_?t.highlight:void 0,paddingLeft:1,children:[Te("box",{width:2,flexShrink:0,children:Te("text",{fg:et(t,h.ok?"OK":"WARN"),children:gr(h.ok?"OK":"WARN")},void 0,!1,void 0,this)},void 0,!1,void 0,this),Te("box",{width:3,flexShrink:0,children:Te("text",{fg:_?t.primary:t.muted,children:String(P+1).padStart(2," ")},void 0,!1,void 0,this)},void 0,!1,void 0,this),Te("box",{width:20,flexShrink:0,overflow:"hidden",children:Te("text",{fg:_?t.primary:t.text,children:h.name},void 0,!1,void 0,this)},void 0,!1,void 0,this),Te("box",{flexGrow:1,overflow:"hidden",children:Te("text",{fg:_?t.text:t.muted,wrapMode:"none",children:h.detail},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},h.name,!0,void 0,this)}),Te("box",{height:1,flexShrink:0},void 0,!1,void 0,this),x?Te("box",{border:!0,borderStyle:"rounded",borderColor:x.ok?t.success:t.warning,title:x.name,titleColor:x.ok?t.success:t.warning,flexDirection:"column",paddingLeft:1,paddingRight:1,flexShrink:0,overflow:"hidden",children:Te("text",{fg:x.ok?t.text:t.warning,wrapMode:"word",children:x.detail},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,Te("box",{height:1,flexShrink:0},void 0,!1,void 0,this),Te("box",{flexDirection:"row",overflow:"hidden",flexShrink:0,children:Te(We,{palette:t,label:"never hard-coded",tone:"idle"},void 0,!1,void 0,this)},void 0,!1,void 0,this),Te("text",{fg:t.muted,wrapMode:"word",children:"Service accounts and IAP audiences stay in configuration. Run `devctl setup` for the interactive CLI wizard."},void 0,!1,void 0,this),Te(ot,{palette:t,hints:[{key:"j/k",label:"steps"},{key:"enter",label:r?"start":"begin setup"},{key:"esc",label:"exit"},{key:"o",label:"profiles"}]},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function RO(e,t,r=[]){let n=Object.values(e?.services??{}).flatMap((s)=>s.ports.filter((a)=>!a.auto)),o=(e?.proxy.routes??[]).some((s)=>s.auth.type.toLowerCase()==="iap"),i=Sl(e,void 0,t).project;return[{name:an[0],ok:Boolean(e?.repoRoot),detail:e?.repoRoot||"unknown"},{name:an[1],ok:Boolean(e?.project.name),detail:e?.project.name||"set project.name"},{name:an[2],ok:Boolean(i),detail:i||"optional for local-only"},{name:an[3],ok:t?.adcAvailable===!0,detail:t?.adcAvailable?"ADC available":"needed only for cloud identity / IAP"},{name:an[4],ok:!0,detail:"read from configuration; never hard-coded"},{name:an[5],ok:!o||(e?.proxy.routes??[]).every((s)=>s.auth.type.toLowerCase()!=="iap"||s.auth.audience!==""),detail:o?"IAP routes present":"no IAP routes"},{name:an[6],ok:n.length===0||Boolean(e),detail:`${n.length} fixed ports from configuration`},{name:an[7],ok:Object.keys(e?.profiles??{}).length>0,detail:`${Object.keys(e?.profiles??{}).length} profile(s)`},{name:an[8],ok:r.length===0,detail:r.length===0?"configuration is valid":r[0]??"invalid"}]}var an;var yk=D(()=>{zt();Ot();ke();tt();an=["Repository","Environment","Google project","Authentication","Service accounts","IAP","Ports","Profiles","Validation"]});import{useEffect as PO,useState as _O}from"react";import{jsxDEV as Z}from"@opentui/react/jsx-dev-runtime";function bi(e){let{palette:t,title:r,tone:n="muted",children:o}=e,i=n==="success"?t.success:n==="warning"?t.warning:n==="error"?t.error:t.border;return Z("box",{border:!0,borderStyle:"rounded",borderColor:i,title:r,titleColor:i,flexDirection:"column",flexShrink:0,paddingLeft:1,paddingRight:1,marginBottom:1,overflow:"hidden",children:o},void 0,!1,void 0,this)}function Ef(e,t){if(t.color)return t.color;switch(t.tone){case"success":return e.success;case"warning":return e.warning;case"error":return e.error;case"primary":return e.primary;case"muted":return e.muted;default:return e.text}}function Is(e){let{palette:t,cells:r,header:n,gap:o=0,stripe:i}=e;return Z("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:i?t.element:void 0,children:r.map((s,a)=>Z("box",{flexDirection:"row",flexShrink:0,overflow:"hidden",children:[a>0&&o>0?Z("box",{width:o,flexShrink:0},void 0,!1,void 0,this):null,Z("box",{width:s.width,flexShrink:0,overflow:"hidden",children:Z("text",{fg:n?t.primary:Ef(t,s),wrapMode:"none",children:oe(s.text,s.width)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},`${s.text}-${a}`,!0,void 0,this))},void 0,!1,void 0,this)}function vk(e){let{palette:t,width:r}=e;return Z("box",{height:1,flexShrink:0,overflow:"hidden",children:Z("text",{fg:t.border,wrapMode:"none",children:oe("\u2500".repeat(Math.max(1,r)),r)},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function Os(e){let{palette:t,facts:r,width:n}=e,o=Op(r,n),i=[{text:"what",width:o.what},{text:"reading",width:o.reading}];if(o.meter>0)i.push({text:"used",width:o.meter});return i.push({text:"meaning",width:o.meaning}),Z("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:[Z(Is,{palette:t,header:!0,gap:li,cells:i},void 0,!1,void 0,this),Z(vk,{palette:t,width:n},void 0,!1,void 0,this),r.map((s,a)=>{let c=s.meaning===""?[""]:Uc(s.meaning,Math.max(1,o.meaning)),l=s.meter?gb(s.meter):o.meter>0?"":void 0,d=[{text:s.what,width:o.what,tone:"text"},{text:s.reading,width:o.reading,tone:s.tone??"text"}];if(o.meter>0)d.push({text:l??"",width:o.meter,tone:s.meter?s.tone??"text":"muted"});return d.push({text:c[0]??"",width:o.meaning,tone:"muted"}),Z("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:[Z(Is,{palette:t,gap:li,stripe:a%2===1,cells:d},void 0,!1,void 0,this),c.slice(1).map((u,f)=>{let p=[{text:"",width:o.what},{text:"",width:o.reading}];if(o.meter>0)p.push({text:"",width:o.meter});return p.push({text:u,width:o.meaning,tone:"muted"}),Z(Is,{palette:t,gap:li,stripe:a%2===1,cells:p},`${s.what}-more-${f}`,!1,void 0,this)})]},`${s.what}-${s.reading}-${a}`,!0,void 0,this)})]},void 0,!0,void 0,this)}function xk(e){let{palette:t,cfg:r,snap:n,width:o,onRefresh:i}=e,s=ve(),[,a]=_O(0);if(PO(()=>{let W=setInterval(()=>{a((V)=>{let de=V+1;if(i&&de%TO===0)i();return de})},EO);return()=>clearInterval(W)},[i]),!r)return Z(Ve,{palette:t,title:"No configuration loaded",body:"Add services to .devctl/config.yaml or finish setup."},void 0,!1,void 0,this);let c=Object.keys(r.services).sort(),l=c.some((W)=>hb(r.services[W])),d=cb(c,n),u=kb(d,l),f=l&&d.live>0?d.healthy/d.live:d.total>0?d.live/d.total:0,p=ni(f,AO),m=d.failed>0?"error":l&&d.live>0&&d.healthy<d.live?"warning":"success",g=pb(o,s.pad),y=n?.session_id?Gl(n.session_id):void 0,x=y?hs(Date.now()-y.getTime()):"just started",h=n?.logs.total??0,P=n?.logs.errors??0,_=ub(n?.logs.counts??{}),w=n?.proxy.routes??[],v=n?.credentials,b=(v?.entries??[]).filter((W)=>W.valid).length,S=n?.system,k=S?S.memAvailableKB??S.memFreeKB:0,L=S?vb(S.loadAvg1,S.cpuCount):void 0,I=S?xb(k,S.memTotalKB):void 0,F=[];if(S&&L&&I){F.push(L,I,{what:"Computer",reading:yb(S.platform),meaning:`on for ${hs(S.hostUptimeSec*LO)}`,tone:"text"});let W=n?.stats_series;if(W&&W.cpu.length>1)F.push({what:"Load trend",reading:Mp(W.cpu),meaning:`${Math.round(W.interval_ms/1000)}s samples`,tone:"text"});if(W&&W.mem.length>1)F.push({what:"RAM trend",reading:Mp(W.mem),meaning:"used memory over the same window",tone:"text"})}let Q=d.failed>0||L?.tone==="error"||I?.tone==="error"?"error":m==="warning"||L?.tone==="warning"||I?.tone==="warning"?"warning":"success",G=[{what:"This session",reading:x,meaning:"how long this run has been going",tone:"text"},{what:"Profile",reading:n?.profile||"none",meaning:"which group of services is selected",tone:n?.profile?"text":"muted"},{what:"This window",reading:n?.detached?"closed":"watching",meaning:n?.detached?"services keep running without this screen":"you are watching the services from here",tone:n?.detached?"warning":"text"},{what:"Project folder",reading:"",meaning:r.repoRoot,tone:"muted"}],C=[{what:"Lines kept",reading:String(h),meaning:"log lines still in memory",tone:"text"},{what:"Error lines",reading:String(P),meaning:P>0?"open Logs and press e to see only errors":"no ERROR or FATAL lines",tone:P>0?"error":"muted"},..._.map(([W,V])=>({what:W,reading:String(V),meaning:"lines from this service",tone:"text"}))],B=[{what:"Web proxy",reading:n?.proxy.running?"on":"off",meaning:n?.proxy.running?`${w.length} apps${n.proxy.address?` at ${n.proxy.address}`:""}`:"not forwarding local URLs",tone:n?.proxy.running?"success":"muted"},{what:"Agent tools",reading:n?.mcp?.running?"on":"off",meaning:n?.mcp?.address?`Cursor talks to devctl at ${n.mcp.address}`:"turn on from Settings \u2192 MCP",tone:n?.mcp?.running?"success":"muted"},{what:"Google account",reading:n?.identity.user?"signed in":"not signed in",meaning:[n?.identity.user,n?.identity.project].filter(Boolean).join(" \xB7 ")||"no Google user for this session",tone:n?.identity.user?"success":"muted"},{what:"Saved tokens",reading:v?`${b} of ${v.entries.length} good`:"none",meaning:v?Sb(v.backend):"nothing stored yet",tone:v&&b<v.entries.length?"warning":v?"success":"muted"}];return Z(kt,{palette:t,title:"stats",scroll:!0,children:[Z(bi,{palette:t,title:`Your services ${d.live} of ${d.total} started`,tone:Q,children:[Z(Os,{palette:t,facts:u,width:g},void 0,!1,void 0,this),Z("box",{height:1,flexShrink:0},void 0,!1,void 0,this),Z("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:Z("text",{wrapMode:"none",children:[Z("span",{fg:t.muted,children:oe(l?"Ready":"Started",Op(u,g).what)},void 0,!1,void 0,this),Z("span",{children:" ".repeat(li)},void 0,!1,void 0,this),Z("span",{fg:Ef(t,{text:"",width:1,tone:m}),children:`[${p}]`},void 0,!1,void 0,this),Z("span",{fg:Ef(t,{text:"",width:1,tone:m}),children:l?` ${d.healthy} of ${d.live||d.total} passing checks`:` ${d.live} of ${d.total} up`},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this),S?Z(bi,{palette:t,title:"This computer",tone:L?.tone==="error"||I?.tone==="error"?"error":L?.tone==="warning"||I?.tone==="warning"?"warning":"success",children:Z(Os,{palette:t,facts:F,width:g},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,Z(bi,{palette:t,title:"This session",children:Z(Os,{palette:t,facts:G,width:g},void 0,!1,void 0,this)},void 0,!1,void 0,this),Z(bi,{palette:t,title:"Each service",tone:d.failed>0?"error":l&&d.live>0&&d.healthy<d.live?"warning":"success",children:d.total===0?Z("text",{fg:t.muted,children:"No services in this project yet."},void 0,!1,void 0,this):Z("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:[Z(DO,{palette:t,names:c,snap:n,width:g},void 0,!1,void 0,this),Z("box",{height:1,flexShrink:0},void 0,!1,void 0,this),Z("text",{fg:t.muted,wrapMode:"none",children:oe("CPU is % of one core. RAM is what that process is holding now.",g)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this),Z(bi,{palette:t,title:"Log lines",tone:P>0?"warning":"muted",children:Z(Os,{palette:t,facts:C,width:g},void 0,!1,void 0,this)},void 0,!1,void 0,this),Z(bi,{palette:t,title:"Other parts",children:Z(Os,{palette:t,facts:B,width:g},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function DO(e){let{palette:t,names:r,snap:n,width:o}=e,i=fb(o),s=[{text:"service",width:i.name},{text:"status",width:Er}];if(i.health)s.push({text:"check",width:Hr});if(i.cpu)s.push({text:"cpu",width:xs});if(i.mem)s.push({text:"ram",width:bs});if(i.up)s.push({text:"up",width:vs});if(i.rst)s.push({text:"restarts",width:rl});if(i.pid)s.push({text:"pid",width:nn});return Z("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:[Z(Is,{palette:t,header:!0,gap:$t,cells:s},void 0,!1,void 0,this),Z(vk,{palette:t,width:o},void 0,!1,void 0,this),r.map((a,c)=>Z($O,{palette:t,name:a,rt:n?.services[a],cols:i,stripe:c%2===1},a,!1,void 0,this))]},void 0,!0,void 0,this)}function IO(e){if(e===void 0)return"muted";if(e>OO)return"error";if(e>MO)return"warning";return"text"}function $O(e){let{palette:t,name:r,rt:n,cols:o,stripe:i}=e,s=bb(n),a=wb(n),c=n?.restarts??0,l=[{text:r,width:o.name,color:Ze(r,t)},{text:s,width:Er,color:et(t,n?.state??"STOPPED")}];if(o.health)l.push({text:a,width:Hr,color:et(t,n?.health??"UNKNOWN")});if(o.cpu)l.push({text:n?.cpuPercent!==void 0?ex(n.cpuPercent):"\u2014",width:xs,tone:IO(n?.cpuPercent)});if(o.mem)l.push({text:n?.memoryKB!==void 0?ys(n.memoryKB):"\u2014",width:bs,tone:"text"});if(o.up)l.push({text:db(n),width:vs,tone:"text"});if(o.rst)l.push({text:String(c),width:rl,tone:c>0?"warning":"muted"});if(o.pid)l.push({text:n?.pid?String(n.pid):"\u2014",width:nn,tone:"muted"});return Z("box",{flexDirection:"column",flexShrink:0,overflow:"hidden",children:[Z(Is,{palette:t,gap:$t,stripe:i,cells:l},void 0,!1,void 0,this),n?.last_error?Z("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:Z("text",{fg:t.error,wrapMode:"none",children:oe(` ${n.last_error}`,Math.max(24,o.name+$t+Er+Hr))},void 0,!1,void 0,this)},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}var EO=1000,TO=5,AO=16,LO=1000,MO=80,OO=100;var bk=D(()=>{zt();Ot();It();hr();At();nl();ke();tt()});import{useRenderer as FO,useTerminalDimensions as NO}from"@opentui/react";import{useCallback as Nn,useEffect as HO,useMemo as wk,useRef as Kr,useState as ze}from"react";import{jsxDEV as ge,Fragment as BO}from"@opentui/react/jsx-dev-runtime";function Sk({controller:e,tui:t,onQuit:r,onDown:n,onAttached:o,bootError:i,bootErrorMissing:s=!1,terminalBackground:a,workspace:c}){let{saveTuiPreferences:l,resolveTuiOverridePath:d,userTuiConfigPath:u,validateConfigText:f,freePort:p,readTextFile:m,writeTextFile:g,createStarterConfig:y,validate:x}=c,[h,P]=ze(e),[_,w]=ze(i),v=FO(),{width:b,height:S}=NO(),[k,L]=ze(h?"dashboard":"setup"),[I,F]=ze("none"),[Q,G]=ze(""),[C,B]=ze("commands"),[W,V]=ze(0),[de,Ee]=ze(0),[$e,ne]=ze(0),[H,ae]=ze([]),[X,nt]=ze(),[pt,ie]=ze(_??""),[Ae,De]=ze({}),[Ke,ht]=ze(),[Ye,Ct]=ze(ox(h?.cfg)),[he,yt]=ze(""),[st,vt]=ze(!1),[rr,Xe]=ze(),[Xt,qe]=ze(),[ft,vr]=ze(),Kt=Kr(null),xr=Kr(null),nr=Kr(null),qt=Kr(null),or=Kr(null),ir=Kr(null),sr=Kr(null),Vt=Kr(""),[ce,U]=ze("quit"),T=Kr(void 0),ye=Kr(0),[z,Lt]=ze(h?.cfg),at=h?.previousPersisted,[Ce,So]=ze(void 0),R=Yw({tui:t,controller:h,resolveTuiOverridePath:d,terminalBackground:a,userTuiConfigPath:u,snap:X,setStatus:ie,saveTuiPreferences:l,setPaletteIndex:V,setOverlay:F,setConfirmKind:U,setScreen:L,setSelected:ne,screen:k}),{themeName:ue,setThemeName:Vr,fontSize:Jr,prefsLocked:br,palette:se,rootBackground:Hn,settingRows:Bn,persistPrefs:Lr,persistTheme:cn,activateSetting:$s}=R,wi=Nw({controller:h,workspace:c,cfg:z,snap:X,screen:k,configReloadError:Ce,setSnap:nt,setStatus:ie}),{google:Si,doctor:le,doctorLoading:xt,doctorError:Rt,doctorProgress:zr,setDoctorTick:Fs}=wi,ko=t.keybinds.copy??Xr(),wr=wk(()=>Object.keys(z?.services??{}).sort(),[z]),ln=Nn(async()=>{if(!h)return;try{let Y=await h.status();if(nt(Y),Y.profile&&Ye==="")Ct(Y.profile);return Y}catch(Y){ie(A(Y));return}},[h,Ye]),Dl=Uw({controller:h,tui:t,names:wr,screen:k,refresh:ln,setStatus:ie}),{logs:Of,setLogs:Vk,paused:If,errorOnly:Ml,setErrorOnly:Ol,showSystemLogs:$f,logSearch:Ff,setLogSearch:Nf,logSearchFocused:Jk,setLogSearchFocused:zk,logFollow:Hf,logSince:Yk,logService:Bf,setLogService:Il,logServiceB:Xk,setLogServiceB:Qk,splitLogs:$l,splitFocus:Fl,setSplitFocus:Zk,logSliceB:Wf,logWindowB:jf,logSelectedB:Gf,logPinnedB:eC,pinLogViewB:tC,logServices:rC,logShowTimestamps:nC,logShowMeta:oC,logSource:iC,logRegex:Uf,logWrap:Kf,logPinned:qf,logSelected:Vf,logsFullscreen:Nl,dashboardLogCursor:sC,setDashboardLogCursor:aC,logFacets:Jf,logSources:zf,filteredLogs:Yf,filteredLogsB:cC,logWindow:Ns,logSlice:Hs,pinLogView:Hl,applyLogCursorA:lC,applyLogCursorB:dC,jumpToLatestLogs:Xf}=Dl,Co=wk(()=>{if(C==="tasks")return Ep(Object.keys(z?.tasks??{}),Q,"tasks","run this task");if(C==="services")return Ep(Object.keys(z?.services??{}),Q,"services","exec in this service");return ob(Q,{services:Object.keys(z?.services??{}),tasks:Object.keys(z?.tasks??{})})},[z,Q,C]);HO(()=>{Ee(0)},[Q]);let Bl=Tx(k,{doctor:le?.checks.length??0,settings:Bn.length,profiles:Object.keys(z?.profiles??{}).length,services:wr.length,logs:$l&&Fl===1?Wf.length:Hs.length,mcp:Gb(),config:Object.keys(z?.tasks??{}).length}),Qf=k==="logs"?$l&&Fl===1?Gf:Vf:$e,Dr=Bl<=0?Math.max(0,Qf):Math.max(0,Math.min(Qf,Bl-1)),uC=k==="detail"?he:k==="services"?wr[Dr]??"":"",{inspectorEnv:Zf,inspectorEnvStatus:em,inspectorEnvError:tm,resolveEnvironment:pC}=nS({controller:h,cfg:z,envService:uC}),Ro=Nn(()=>{if(F("none"),G(""),B("commands"),zk(!1),T.current)clearTimeout(T.current)},[]),Wl=Nn((Y)=>{ae((bt)=>bt.includes(Y)?bt.filter((_C)=>_C!==Y):[...bt,Y])},[]),rm=Vw({tui:t,controller:h,cfg:z,persistPrefs:Lr,snap:X,refresh:ln,setStatus:ie}),{mcpPortDraft:fC,mcpPort:mC,toggleMcpTool:gC,toggleMcp:hC,copyMcpSnippet:yC}=rm;Ow({controller:h,cfg:z,paused:If,logSince:Yk,refresh:ln,setLogs:Vk,setCfg:Lt,setConfigReloadError:So,setStatus:ie});let Bs=Nn((Y)=>{yt(Y),L("detail")},[]),nm=Nn((Y)=>{let bt=Y.value!==""?Y.value:Y.required?"(required, not set)":"(empty)";vr({title:Y.key,body:bt}),F("scroll-text")},[]),jl=Bw({controller:h,workspace:c,cfg:z,snap:X,refresh:ln,setStatus:ie,setOverlay:F}),{plan:Ws,planInitiallyRunning:vC,planBusy:xC,lifecycle:om}=jl,Yr=eS({workspace:c,renderer:v,setOverlay:F,setStatus:ie,setScreen:L,onAttached:o,setController:P,setCfg:Lt,setBootError:w}),{configEditRef:bC,configEditText:wC,configEditError:SC,setConfigEditError:kC,openConfigBuffer:im,saveConfigBuffer:CC}=Dw({cfg:z,readTextFile:m,setOverlay:F,setStatus:ie,validateConfigText:f,writeTextFile:g,controller:h,setConfirmKind:U,refresh:ln}),sm=Nn(async()=>{let Y=v.getSelection()?.getSelectedText()??"";if(Y.trim()===""){ie("Nothing selected \u2014 drag to highlight text");return}try{await al(Y);let bt=Y.split(`
618
- `).length;ie(bt===1?"Copied selection":`Copied ${bt} selected lines`)}catch(bt){ie(A(bt))}},[v]),{runCommand:js}=Ew({setOverlay:F,setQuery:G,setSlashPicker:B,checked:H,setConfirmKind:U,setStatus:ie,persistTheme:cn,setPaletteIndex:V,themeName:ue,setScreen:L,setSelected:ne,screen:k,renderer:v,cfg:z,setScrollText:vr,reveal:st,controller:h,refresh:ln,profile:Ye,setReveal:vt,resolveEnvironment:pC,openDetail:Bs,copySelection:sm,lastExportPath:Vt,openConfigBuffer:im,onDown:n??((Y)=>r(Y)),workspace:c,logView:Dl,diagnostics:wi,lifecycleActions:jl}),am=Nn(()=>{let Y=ci(Co,de);if(C==="tasks"){if(!Y){ie(Co.length===0?"no tasks configured":"pick a task");return}B("commands");let bt=Br("run");if(bt)js(bt,[Y.name]);return}if(C==="services"){if(!Y){ie(Co.length===0?"no services configured":"pick a service");return}B("commands"),G(`exec ${Y.name} -- `);return}if(!Y){ie(Q.trim()===""?"pick a command from the list":`unknown command /${Q}`),Ro();return}js(Y,nb(Y,Q))},[Ro,Co,Q,js,de,C]),cm=Nn((Y)=>{cn(Y),Ro()},[Ro,cn]);lw({tui:t,controller:h,cfg:z,snap:X,ui:{screen:k,overlay:I,onQuit:r,onDown:n??((Y)=>r(Y)),closeOverlay:Ro,confirmKind:ce,confirmDetail:Ae,portTarget:Ke,profile:Ye,listCursor:Dr,names:wr,listCount:Bl,openDetail:Bs,copySelection:sm,height:S,paletteIndex:W,applyTheme:cm,runCommand:js,saveConfigBuffer:CC,filtered:Co,slashIndex:de,submitSlash:am,advanceWizard:()=>void Yr.advanceWizard(),bootErrorMissing:s,bootError:_,createStarterConfig:y,startWizard:Yr.startWizard,toggleChecked:Wl,checked:H,detailName:he,refresh:ln,openConfigBuffer:im,setOverlay:F,setConfirmKind:U,setConfirmDetail:De,setPortTarget:ht,setLogDetail:Xe,logDetail:rr,setLogSearch:Nf,setProfile:Ct,setStatus:ie,setPaletteIndex:V,setSlashIndex:Ee,setQuery:G,setSlashPicker:B,setScreen:L,setSelected:ne,setChecked:ae,setConfigEditError:kC,freePort:p},logView:Dl,lifecycleActions:jl,mcp:rm,preferences:R,diagnostics:wi,refs:{interruptArmedAt:ye,leaderTimer:T,logDetailsScrollRef:qt,scrollTextScrollRef:ir,routeDetailsScrollRef:or,planScrollRef:sr,helpScrollRef:xr,configScrollRef:Kt,detailScrollRef:nr}});let lm=Fx(ce,Ye,Ae),RC=new Set(Ws?.waves.flat()??[]),PC=Ws===void 0||om==="stop"?"":Object.entries(X?.services??{}).find(([Y,bt])=>RC.has(Y)&&bt.state==="FAILED")?.[0]??"";return ge(lp.Provider,{value:ms(Jr),children:ge("box",{flexDirection:"column",width:b,height:S,backgroundColor:Hn,overflow:"hidden",children:[Nl?null:ge(BO,{children:[ge(Pb,{palette:se,cfg:z,snap:X,google:Si,profile:Ye,reveal:st,width:b},void 0,!1,void 0,this),ge(_b,{palette:se,screen:k,width:b,onSelect:L},void 0,!1,void 0,this),Ce?ge("box",{height:1,paddingLeft:1,backgroundColor:se.panel,overflow:"hidden",children:ge("text",{fg:se.error,wrapMode:"none",children:`\u26A0 configuration reload failed: ${Ce}`},void 0,!1,void 0,this)},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),ge("box",{flexGrow:1,overflow:"hidden",children:[k==="dashboard"?ge(GS,{palette:se,cfg:z,snap:X,logs:Of,names:wr,selected:Dr,checked:H,width:b,followTick:Hf,logService:Bf,errorOnly:Ml,showSystemLogs:$f,onOpen:Bs,onSelectIndex:ne,selectedLog:sC,onToggle:Wl,logSources:zf,onFilterService:Il,onToggleErrors:()=>Ol((Y)=>!Y),onShowErrors:()=>{Il(""),Ol(!0)},wrapMode:Kf,view:Hs,follow:!qf,onLeaveLatest:Hl,onPickLog:(Y)=>{let bt=Hs[Y];if(!bt)return;aC(Y),Xe(bt),Hl()},viewStart:Ns.start,viewTotal:Yf.length,newer:Ns.newer,onJumpLatest:Xf,facets:Jf,leftover:at,search:Ff,regex:Uf},void 0,!1,void 0,this):null,k==="services"?ge(lk,{palette:se,cfg:z,names:wr,snap:X,selected:Dr,checked:H,width:b,reveal:st,onOpen:Bs,onSelectIndex:ne,onToggle:Wl,resolvedEnv:Zf,envStatus:em,envError:tm,onSelectEnv:nm},void 0,!1,void 0,this):null,k==="detail"?ge(ck,{palette:se,cfg:z,snap:X,name:he,reveal:st,width:b,envScrollRef:nr,resolvedEnv:Zf,envStatus:em,envError:tm,onSelectEnv:nm},void 0,!1,void 0,this):null,k==="logs"?ge(JS,{palette:se,logs:Of,names:wr,logSources:zf,service:Bf,errorOnly:Ml,showSystemLogs:$f,search:Ff,searchFocused:Jk,followTick:Hf,width:b,onSearch:Nf,onService:Il,onToggleErrors:()=>Ol((Y)=>!Y),source:iC,regex:Uf,services:rC,showTimestamps:nC,showMeta:oC,view:Hs,wrapMode:Kf,selected:Vf,follow:!qf,newer:Ns.newer,viewStart:Ns.start,viewTotal:Yf.length,onSelect:lC,fullscreen:Nl,onLeaveLatest:Hl,onJumpLatest:Xf,facets:Jf,split:$l,splitFocus:Fl,serviceB:Xk,viewB:Wf,selectedB:Gf,followB:!eC,newerB:jf.newer,viewStartB:jf.start,viewTotalB:cC.length,onServiceB:Qk,onSelectB:dC,onLeaveLatestB:tC,onFocusPane:Zk},void 0,!1,void 0,this):null,k==="auth"?ge(ES,{palette:se,cfg:z,google:Si,identity:X?.identity},void 0,!1,void 0,this):null,k==="credentials"?ge(IS,{palette:se,credentials:X?.credentials},void 0,!1,void 0,this):null,k==="proxy"?ge(ok,{palette:se,cfg:z,snap:X,width:b,onSelectRoute:(Y)=>{qe(Y),F("route-details")}},void 0,!1,void 0,this):null,k==="mcp"?ge(Ub,{palette:se,snap:X,port:mC,portDraft:fC,selected:Dr,onPick:ne,onToggle:()=>{hC()},onCopy:yC,onToggleTool:(Y)=>{gC(Y)}},void 0,!1,void 0,this):null,k==="doctor"?ge(KS,{palette:se,report:le,loading:xt,progress:zr,error:Rt,selected:Dr,onPick:ne,onReload:()=>{Fs((Y)=>Y+1),ie("Re-running doctor")}},void 0,!1,void 0,this):null,k==="stats"?ge(xk,{palette:se,cfg:z,snap:X,width:b,onRefresh:ln},void 0,!1,void 0,this):null,k==="config"?ge(MS,{palette:se,cfg:z,width:b,selectedTask:Dr,scrollRef:Kt},void 0,!1,void 0,this):null,k==="profiles"?ge(XS,{palette:se,cfg:z,snap:X,profile:Ye,selected:Dr,onPick:ne},void 0,!1,void 0,this):null,k==="setup"?ge(hk,{issues:z?x(z):["configuration not loaded"],palette:se,cfg:z,google:Si,bootError:_,bootErrorMissing:s,step:Dr},void 0,!1,void 0,this):null,k==="settings"?ge(mk,{palette:se,items:Bn,selected:Dr,locked:br,width:b,onPick:ne,onActivate:$s},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),I==="slash"?ge(wS,{palette:se,items:Co,query:Q,selected:de,title:C==="tasks"?"tasks":C==="services"?"services":"commands",onQuery:G,onSubmit:am},void 0,!1,void 0,this):null,I==="themes"?ge(PS,{palette:se,themeName:ue,selected:W%St.length,termW:b,termH:S,onIndex:V,onPreview:Vr,onPick:cm},void 0,!1,void 0,this):null,I==="help"?ge(Jb,{palette:se,termW:b,termH:S,copyKey:ko,scrollRef:xr},void 0,!1,void 0,this):null,I==="leader"?ge(dS,{palette:se,termW:b,termH:S},void 0,!1,void 0,this):null,I==="confirm"?ge(aS,{palette:se,title:lm.title,body:lm.body,kind:ce,termW:b,termH:S},void 0,!1,void 0,this):null,I==="setup-wizard"?ge(Qw,{palette:se,termW:b,termH:S,step:Yr.step,answers:Yr.answers,repo:Yr.repo,draft:Yr.draft,authStatus:Yr.authStatus,onDraft:Yr.setDraft,onSubmit:()=>void Yr.advanceWizard()},void 0,!1,void 0,this):null,I==="log-details"?ge(pS,{palette:se,event:rr,termW:b,termH:S,scrollRef:qt},void 0,!1,void 0,this):null,I==="route-details"?ge(yS,{palette:se,route:Xt,termW:b,termH:S,scrollRef:or},void 0,!1,void 0,this):null,I==="scroll-text"&&ft?ge(xS,{palette:se,title:ft.title,body:ft.body,termW:b,termH:S,scrollRef:ir},void 0,!1,void 0,this):null,I==="config-edit"&&z?ge(iS,{palette:se,path:z.configPath,initialValue:wC,error:SC,termW:b,termH:S,textareaRef:bC},void 0,!1,void 0,this):null,I==="plan"&&Ws?ge(gS,{palette:se,plan:Ws,snap:X,busy:xC,failed:PC,kind:om,termW:b,termH:S,scrollRef:sr,initiallyRunning:vC,onDismiss:Ro},void 0,!1,void 0,this):null,Nl?null:ge(Eb,{palette:se,screen:k,overlay:I,status:pt,paused:If,errorOnly:Ml,width:b,copyKey:ko},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}var kk=D(()=>{te();zt();$p();Ot();yr();vo();Ln();At();dw();Tw();Mw();Iw();Hw();Ww();Kw();Jw();Xw();tS();oS();sS();cS();rf();uS();fS();hS();vS();bS();lf();SS();_S();TS();OS();$S();US();qS();zS();Ts();QS();ik();Pf();dk();gk();yk();bk();_r();tt();Pr()});var Ck={};LC(Ck,{renderApp:()=>Af,runTui:()=>Tf,runTuiWithController:()=>GO});import{createCliRenderer as WO}from"@opentui/core";import{createRoot as jO}from"@opentui/react";import{jsxDEV as UO}from"@opentui/react/jsx-dev-runtime";async function GO(e,t){let r=e.loadTuiConfig(t.cfg.repoRoot,t.cfg.ui.keymap);await Af(e,t,r)}async function Tf(e,t){let r,n,o=!1;try{r=await e.openTui("",t)}catch(s){n=A(s),o=ua(s,Kn)}let i=e.loadTuiConfig(r?.cfg.repoRoot??process.cwd(),r?.cfg.ui.keymap);await Af(e,r,i,n,o)}async function Af(e,t,r=e.loadTuiConfig(t?.cfg.repoRoot??process.cwd(),t?.cfg.ui.keymap),n,o=!1){Gs();let i=pm(),s=await WO({exitOnCtrlC:!1,useMouse:r.mouse,enableMouseMovement:r.mouse,useKittyKeyboard:{}}),a=jO(s),c=null;try{c=(await s.getPalette({timeout:120,size:16})).defaultBackground}catch{}await new Promise((l)=>{let d=(f)=>{let p=()=>{a.unmount(),i(),s.destroy(),l(),process.exit(0)};if(!t){p();return}t.close({detach:f,shutdownSupervisor:!0}).finally(p)},u=(f)=>{let p=()=>{a.unmount(),i(),s.destroy(),l(),process.exit(0)};if(!t){p();return}t.shutdown({stopServices:!f}).then(()=>t?.close({detach:!0})).finally(p)};a.render(UO(Sk,{workspace:Hv(e),controller:t,tui:r,onQuit:d,onDown:u,onAttached:(f)=>{t=f},bootError:n,bootErrorMissing:o,terminalBackground:c},void 0,!1,void 0,this))})}var Lf=D(()=>{Bv();te();kk()});if(process.env.METADATA_SERVER_DETECTION===void 0)process.env.METADATA_SERVER_DETECTION="bios-only";if(process.env.GCE_METADATA_TIMEOUT===void 0)process.env.GCE_METADATA_TIMEOUT="0";import{existsSync as Qs,readFileSync as zs}from"fs";import{dirname as aR,isAbsolute as wm,join as Xs,resolve as cR}from"path";Us();Us();import{spawnSync as HC}from"child_process";import{randomBytes as ql}from"crypto";import{copyFileSync as BC,existsSync as dn,mkdirSync as WC,readdirSync as jC,readFileSync as Vl,renameSync as Jl,statSync as GC,unlinkSync as Ul,writeFileSync as UC}from"fs";import{homedir as KC}from"os";import{basename as qC,dirname as Kl,join as Dt}from"path";var VC=448,JC=384;function cr(){let e=process.env.DEVCTL_HOME;if(e&&e!=="")return e;return Dt(KC(),".devctl")}function Mr(e){WC(e,{recursive:!0,mode:VC})}function Po(e){let t=Dt(cr(),"state",ar(e)),r=Dt(cr(),"sessions",ar(e));if(!dn(t)&&dn(r)){Mr(Kl(t));try{Jl(r,t)}catch{Mr(t);for(let n of["state.json","devctl.lock","devctl.sock"]){let o=Dt(r,n);if(dn(o))BC(o,Dt(t,n))}}}return Mr(t),t}function Ks(){let e=Dt(cr(),"logs");return Mr(e),e}function qs(){let e=Dt(cr(),"exports");return Mr(e),e}function Sr(){let e=Dt(cr(),"credentials");return Mr(e),e}function Vs(e,t=process.platform){if(t==="win32")return`\\\\.\\pipe\\devctl-${ar(e)}`;return Dt(Po(e),"devctl.sock")}function zC(e){return Dt(Po(e),"devctl.lock")}function fm(e){return Dt(Po(e),"state.json")}function ki(e){return Dt(Po(e),"bootstrap.log")}var YC=5,mm="bootstrap-";function gm(e){let t=ki(e);if(!dn(t))return;let r=Po(e),n=Dt(r,`${mm}${zl()}.log`);try{Jl(t,n)}catch{return}XC(r)}function XC(e){let t;try{t=jC(e).filter((n)=>n.startsWith(mm)&&n.endsWith(".log"))}catch{return}let r=t.map((n)=>{let o=Dt(e,n);try{return{path:o,mtime:GC(o).mtimeMs}}catch{return}}).filter((n)=>n!==void 0).sort((n,o)=>o.mtime-n.mtime);for(let n=YC;n<r.length;n++)try{Ul(r[n].path)}catch{}}function kr(e,t){Mr(Kl(e));let r=Dt(Kl(e),`.${qC(e)}.tmp-${process.pid}-${ql(4).toString("hex")}`);UC(r,t,{mode:JC}),Jl(r,e)}function zl(e=new Date){return`${e.toISOString().slice(0,19).replace(/:/g,"-")+"Z"}-${ql(3).toString("hex")}`}function un(e){return Yl(fm(e))}function Yl(e){if(!dn(e))return;try{let t=JSON.parse(Vl(e,"utf8"));if(Array.isArray(t.processes)&&typeof t.repo_root==="string")return t;return}catch{return}}function hm(e,t){kr(fm(e),`${JSON.stringify(t,null,2)}
619
- `)}function ym(e,t){let r=zC(e);if(dn(r)){let n=QC(r);if(n&&Qt(n.pid))throw Error(`supervisor already running (pid ${n.pid})`);try{Ul(r)}catch{}}return kr(r,JSON.stringify({pid:process.pid,socket:t})),{release:()=>{try{Ul(r)}catch{}}}}function QC(e){if(!dn(e))return;try{let t=JSON.parse(Vl(e,"utf8"));if(typeof t.pid==="number"&&typeof t.socket==="string")return{pid:t.pid,socket:t.socket}}catch{return}return}function Qt(e){if(e<=0)return!1;if(process.platform==="win32")return ZC(e);try{return process.kill(e,0),!0}catch{return!1}}function Xl(e){try{let t=HC("cmd.exe",["/d","/c",`tasklist /FO CSV /NH /FI "PID eq ${e}"`],{encoding:"buffer",windowsHide:!0,timeout:5000});return eR(t.stdout)}catch{return""}}function ZC(e){try{return process.kill(e,0),!0}catch{}let t=Xl(e).toLowerCase();if(t===""||t.includes("no tasks")||t.includes("no matching"))return!1;return t.includes(String(e))}function eR(e){if(!e)return"";if(typeof e==="string")return e;if(e.length>=2&&e[0]===255&&e[1]===254)return e.toString("utf16le");if(e.length>=4&&e[1]===0&&e[3]===0)return e.toString("utf16le");return e.toString("utf8")}function Ql(){return ql(24).toString("hex")}function tR(e){return Dt(Po(e),"mcp-token")}function vm(e){let t=tR(e);if(dn(t)){let n=Vl(t,"utf8").trim();if(n!=="")return n}let r=Ql();return kr(t,r),r}Js();Js();var lR="OPENCODE_TUI_CONFIG";function Zs(){return Xs(cr(),"tui.json")}function ea(e=""){for(let t of[xm,lR]){let r=process.env[t];if(!r||r==="")continue;let n=wm(r)?r:cR(e||process.cwd(),r);if(Qs(n))return n}return}function dR(e,t){if(!t||Object.keys(t).length===0)return e;return{...e,keybinds:{...e.keybinds,...t}}}function To(e,t){let r=dR(bm(),t),n=ea(e);if(n)return td(r,Ys(zs(n,"utf8")),n);let o=uR(e),i=o?td(r,Ys(zs(o,"utf8")),o):r,s=Zs();if(!Qs(s)||o===s)return i;return td(i,Ys(zs(s,"utf8")),s)}function Sm(e){let t=Zs(),r={};if(Qs(t)){let o=Ys(zs(t,"utf8"));if(Wn(o))r=o}let n={...r};if(e.theme!==void 0)n.theme=e.theme;if(e.mouse!==void 0)n.mouse=e.mouse;if(e.leader_timeout!==void 0)n.leader_timeout=e.leader_timeout;if(e.font_size!==void 0)n.font_size=e.font_size;if(e.log_timestamps!==void 0)n.log_timestamps=e.log_timestamps;if(e.log_metadata!==void 0)n.log_metadata=e.log_metadata;if(e.mcp_enabled!==void 0)n.mcp_enabled=e.mcp_enabled;if(e.mcp_port===null)delete n.mcp_port;else if(e.mcp_port!==void 0)n.mcp_port=e.mcp_port;if(e.mcp_disabled_tools!==void 0)n.mcp_disabled_tools=e.mcp_disabled_tools;return kr(t,`${JSON.stringify(n,null,2)}
620
- `),t}function uR(e){let t=ea(e);if(t)return t;let r=[e||process.cwd(),Xs(e||process.cwd(),".devctl"),cr()],n=["tui.json","tui.jsonc"];for(let o of r)for(let i of n){let s=Xs(o,i);if(Qs(s))return s}return}function td(e,t,r){let n=Wn(t)?t:{},o={...e.keybinds};if(Wn(n.keybinds)){for(let[c,l]of Object.entries(n.keybinds))if(typeof l==="string")o[c]=l}let i={...e.cursor};if(Wn(n.cursor)){if(fR(n.cursor.style))i.style=n.cursor.style;if(typeof n.cursor.blinking==="boolean")i.blinking=n.cursor.blinking}let s={...e.scroll_acceleration};if(Wn(n.scroll_acceleration)&&typeof n.scroll_acceleration.enabled==="boolean")s.enabled=n.scroll_acceleration.enabled;let a={...e.attention,sounds:{...e.attention.sounds}};if(Wn(n.attention)){if(typeof n.attention.enabled==="boolean")a.enabled=n.attention.enabled;if(typeof n.attention.notifications==="boolean")a.notifications=n.attention.notifications;if(typeof n.attention.sound==="boolean")a.sound=n.attention.sound;if(typeof n.attention.volume==="number")a.volume=n.attention.volume;if(typeof n.attention.sound_pack==="string")a.sound_pack=n.attention.sound_pack;if(Wn(n.attention.sounds)){for(let[c,l]of Object.entries(n.attention.sounds))if(typeof l==="string")a.sounds[c]=pR(l,r)}}return{theme:typeof n.theme==="string"?n.theme:e.theme,leader_timeout:typeof n.leader_timeout==="number"?n.leader_timeout:e.leader_timeout,font_size:typeof n.font_size==="number"&&Number.isFinite(n.font_size)?Math.round(n.font_size):e.font_size,keybinds:o,scroll_speed:typeof n.scroll_speed==="number"?n.scroll_speed:e.scroll_speed,scroll_acceleration:s,diff_style:n.diff_style==="stacked"?"stacked":"auto",cursor:i,mouse:typeof n.mouse==="boolean"?n.mouse:e.mouse,attention:a,log_timestamps:typeof n.log_timestamps==="boolean"?n.log_timestamps:e.log_timestamps,log_metadata:typeof n.log_metadata==="boolean"?n.log_metadata:e.log_metadata,mcp_enabled:typeof n.mcp_enabled==="boolean"?n.mcp_enabled:e.mcp_enabled,mcp_port:typeof n.mcp_port==="number"&&Number.isInteger(n.mcp_port)?n.mcp_port:e.mcp_port,mcp_disabled_tools:Array.isArray(n.mcp_disabled_tools)?n.mcp_disabled_tools.filter((c)=>typeof c==="string"):e.mcp_disabled_tools,path:r}}function pR(e,t){if(e.startsWith("file://")||wm(e))return e;return Xs(aR(t),e)}function fR(e){return e==="block"||e==="underline"||e==="line"||e==="default"}function Wn(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function Ys(e){let t=e.replace(/\/\*[\s\S]*?\*\//g,"").replace(/^\s*\/\/.*$/gm,"");return JSON.parse(t)}import{existsSync as c0,readFileSync as l0,writeFileSync as d0}from"fs";import{spawn as gd}from"child_process";import{createWriteStream as jR,existsSync as Li,mkdirSync as GR,readdirSync as vd,readFileSync as UR,rmSync as KR,statSync as yd,writeFileSync as qR}from"fs";import{homedir as VR}from"os";import{dirname as Km,isAbsolute as JR,join as Un,resolve as zR}from"path";la();la();var YR=50000,xd="session-";function Um(e){return Buffer.from(JSON.stringify(e),"utf8").toString("base64url")}function XR(e){try{let t=JSON.parse(Buffer.from(e,"base64url").toString("utf8"));if(typeof t==="object"&&t!==null&&typeof t.session==="string"&&typeof t.seq==="number")return t}catch{}return}function hd(e,t){let r={...e};return r[t]=void 0,r}class bd{events=[];eventStart=0;nextSeq=1;max;bus;detector;persistDir;persist;sessionID;streams=new Map;lastWrite=new Map;parsers=[];constructor(e,t,r,n,o,i,s=0,a=0){this.max=e>0?e:YR,this.bus=t,this.detector=r,this.sessionID=i;let c=o===""||o.startsWith("~/")?Ks():o;if(this.persist=n&&i!=="",this.persistDir=this.persist?Un(c,`${xd}${i}`):"",this.persist)GR(this.persistDir,{recursive:!0,mode:448}),eP(c,s,a)}sessionDir(){return this.persistDir}setParsers(e){this.parsers=e}append(e){let t=ld(e.message),r=this.parseLine(t),n=(c)=>this.detector?this.detector.redactText(c):c,o=r.raw!==void 0,i=n(t),s=r.message!==void 0?n(ld(r.message)):void 0,a={...e,timestamp:e.timestamp||new Date().toISOString(),level:e.level||r.level||ud(t),request_id:e.request_id||r.request_id||pd(t),message:o&&s!==void 0?s:i,raw:o?i:void 0,seq:this.nextSeq++};if(this.events.length<this.max)this.events.push(a);else this.events[this.eventStart]=a,this.eventStart=(this.eventStart+1)%this.max;if(this.bus?.publish(Se(Ao,a.service,{event:a,level:a.level})),this.persist){let c=`${a.timestamp} ${a.service} ${a.level} ${a.raw??a.message}
621
- `,l=ZR(a.service),d=this.streamFor(l);this.lastWrite.set(l,new Promise((u)=>{d.write(c,()=>u())}))}return a}async flush(){await Promise.all([...this.lastWrite.values()])}parseLine(e){let t={};for(let r of this.parsers)try{let n=r.parse(e);if(n)t={...t,...n}}catch{}return t}streamFor(e){let t=this.streams.get(e);if(t===void 0)t=jR(Un(this.persistDir,`${e}.log`),{flags:"a",mode:384}),t.on("error",()=>{}),this.streams.set(e,t);return t}async close(){await this.flush(),await Promise.all([...this.streams.values()].map((e)=>new Promise((t)=>{e.end(()=>t())}))),this.streams.clear(),this.lastWrite.clear()}query(e){let t=md(e),r=[];return this.forEachEvent((n)=>{if(t(n))r.push(n)}),r}queryPage(e,t={}){let r=jm(t.limit),n=t.cursor?XR(t.cursor):void 0,o=n!==void 0&&n.session!==this.sessionID,i=o?void 0:n,s=i?t.direction??"backward":"backward",a=md(e),c=[];this.forEachEvent((m)=>{if(a(m))c.push(m)});let l;if(!i)l=c.slice(Math.max(0,c.length-r));else if(s==="forward")l=c.filter((m)=>m.seq>i.seq).slice(0,r);else{let m=c.filter((g)=>g.seq<i.seq);l=m.slice(Math.max(0,m.length-r))}let d=l[0]?.seq,u=l[l.length-1]?.seq,f=d!==void 0&&c.some((m)=>m.seq<d),p=u!==void 0&&c.some((m)=>m.seq>u);return{events:l,prevCursor:Um({session:this.sessionID,seq:d??i?.seq??0}),nextCursor:Um({session:this.sessionID,seq:u??i?.seq??this.nextSeq-1}),hasNext:p,hasPrev:f,sessionChanged:o}}queryFacets(e){let t=hd(e,"services"),r=hd(e,"level"),n=hd(e,"source"),o=fd(e),i=0,s={},a={},c={};return this.forEachEvent((l)=>{if(!o(l))return;if(Oo(e,l))i+=1;if(Oo(t,l))s[l.service]=(s[l.service]??0)+1;if(Oo(r,l))a[l.level]=(a[l.level]??0)+1;if(Oo(n,l))c[l.source]=(c[l.source]??0)+1}),{total:i,byService:s,byLevel:a,bySource:c}}snapshot(){let e={},t=0;return this.forEachEvent((r)=>{if(e[r.service]=(e[r.service]??0)+1,r.level===Gn||r.level===Do)t+=1}),{total:this.events.length,errors:t,counts:e}}forEachEvent(e){let t=this.events.length;for(let r=0;r<t;r+=1){let n=this.events[(this.eventStart+r)%t];if(n)e(n)}}exportTo(e,t){wd(e,this.query(t))}}function qm(e){return{append:(t)=>{e.append(t)},query:async(t)=>e.query(t),queryPage:async(t,r)=>e.queryPage(t,r),queryFacets:async(t)=>e.queryFacets(t),snapshot:()=>e.snapshot(),exportTo:async(t,r)=>{e.exportTo(t,r)},setParsers:(t)=>{e.setParsers(t)},setSecrets:(t,r)=>{},close:()=>e.close()}}function QR(e=new Date){let t=e.toISOString().replace(/[:.]/g,"-");return Un(qs(),`devctl-logs-${t}.log`)}function Vm(e=""){if(e==="")return QR();let t=e.startsWith("~/")?Un(VR(),e.slice(2)):e;return JR(t)?t:zR(process.cwd(),t)}function wd(e,t){Mr(Km(e));let r=t.map((n)=>`${n.timestamp} ${n.service} ${n.level} ${n.raw??n.message}`);qR(e,`${r.join(`
2156
+ `:""}`;if(ue){if(Bn.length>0){m({title:"compose import",body:Un}),e("scroll-text"),i("import is invalid; not written");return}if(z(wr)){i(`configuration already exists at ${wr}; not overwriting`);return}Ee(wr,se.yaml),i(`wrote ${wr}; /reload to apply`);return}m({title:"compose import (dry-run)",body:`${Un}
2157
+ Enter is preview only. /import compose ${Yr} --write to save.`}),e("scroll-text")}catch(se){i(A(se))}return}case"diff":if(!p){i("no configuration loaded");return}m({title:"config sources",body:Ww(p,h)}),e("scroll-text");return;case"daemon":{let P=p?.repoRoot;if(!P){i("no repository");return}let ue=B(P);if(!z(ue)){i("no daemon bootstrap log yet for this repository");return}m({title:"daemon bootstrap",body:de(ue)}),e("scroll-text");return}case"reload":if(!v)return;v.reload().then((P)=>{if(i(P.restart_required.length===0?"Configuration reloaded":`Reload requires restart: ${P.restart_required.join(", ")}`),P.restart_required.length>0)o("reload"),e("confirm");x()});return;case"doctor":l("doctor");return;case"stats":l("stats");return;case"start":await K(Ro,g);return;case"stop":await T(Ro);return;case"restart":{let P=ub(Ce),ue=Pp(P.services,n);await ve(ue,g,P.cascade);return}case"run":{let P=Ce[0]??"";if(!P){r("tasks"),t(""),e("slash"),i(Object.keys(p?.tasks??{}).length===0?"no tasks configured":"pick a task");return}if(!v){i("no daemon attached");return}try{let ue=await v.runTask(P);Ke(`task:${P}`),l("logs"),i(`task ${P} exited ${ue.code}`)}catch(ue){throw Ke(`task:${P}`),l("logs"),ue}return}case"exec":{let P=pb(Ce);if(!P.service){r("services"),t(""),e("slash"),i(Object.keys(p?.services??{}).length===0?"no services configured":"pick a service");return}if(!v){i("no daemon attached");return}if(P.printEnv){if(!p?.services[P.service]){i(`unknown service ${P.service}`);return}if(P.reveal)R(!0);i(`Resolving environment for ${P.service}\u2026`);try{await _(P.service),S(P.service),i(`Resolved environment for ${P.service}`)}catch(ue){S(P.service),i(A(ue))}return}if(P.command.length===0){i("usage: /exec <service> -- <command\u2026>");return}try{let ue=await v.execService(P.service,P.command);Ke(`${P.service}:exec`),l("logs"),i(`${P.service}:exec exited ${ue.code}`)}catch(ue){throw Ke(`${P.service}:exec`),l("logs"),ue}return}case"refresh":if(u==="auth"){await Vt();return}await x(),i("Refreshed status and logs");return;case"pause":Xe((P)=>!P),l("logs");return;case"filter":Ct((P)=>!P),l("logs");return;case"system":ge(),l("logs");return;case"clear":vt();return;case"reveal":{let P=!h;R(P),i(P?"Secrets visible this session":"Secrets hidden");return}case"wrap":{let P=Xc(at);yt(P),i(`Log ${Zc(P)}`);return}case"copy":await y();return;case"export":{let P=ne(Ce[0]);if(v)await v.logs({export:P,services:rr,level:Qe?"ERROR":Xt,search:qe,regex:mt,source:xr});else H(P,Kt);b.current=P,i(`Exported ${P}`);return}case"exports":{let P=b.current||ae();X(P),i(`Opened ${ae()}`);return}case"regex":br((P)=>!P),i(mt?"Substring search":"Regex search");return;case"since":nr(Ce[0]||new Date(Date.now()-3600000).toISOString()),i(`Logs since ${Ce[0]||"1h"}`);return;case"until":qt(Ce[0]||new Date().toISOString()),i(`Logs until ${Ce[0]||"now"}`);return;case"history":{let P=nt(),ue=Ce[0]||P[0];if(!ue){i("No persisted log sessions");return}or(ft(ue)),i(`Loaded session ${ue}`);return}case"buffer":w();return;case"edit":{if(!p)return;let P=process.env.DEVCTL_EDITOR||process.env.EDITOR,ue=P?[P,p.configPath]:process.platform==="darwin"?["open",p.configPath]:["xdg-open",p.configPath];Bun.spawn({cmd:ue,stdout:"ignore",stderr:"ignore",stdin:"ignore"}),i(`Opened ${p.configPath} in ${ue[0]}. Run /reload after saving.`);return}default:i(`/${ct.name}`)}}catch(P){i(A(P))}finally{setTimeout(()=>{Y.current=!1},tM)}},[ve,K,T,n,p,vt,v,y,Qe,Kt,Xt,mt,qe,rr,xr,at,k,w,S,s,g,x,Vt,f,h,u,ir,r,c,sr,ge])}}var tM=50;var zw=D(()=>{te();ff();mf();gr();At();tt()});import{useCallback as Yw,useRef as rM,useState as Jw}from"react";function Xw({cfg:e,readTextFile:t,setOverlay:r,setStatus:n,validateConfigText:o,writeTextFile:i,controller:s,setConfirmKind:a,refresh:c}){let l=rM(null),[d,u]=Jw(""),[f,p]=Jw(""),m=Yw(()=>{if(!e)return;try{u(t(e.configPath)),p(""),r("config-edit")}catch(v){n(A(v))}},[e]),h=Yw(()=>{if(!e)return;let v=l.current?.plainText??d,x=o(e.repoRoot,e.configPath,v);if(x.length>0){p(x.join(`
2158
+ `)),n("Config buffer not saved");return}if(i(e.configPath,v),p(""),r("none"),!s){n(`Wrote ${e.configPath}`);return}s.reload().then((g)=>{if(n(g.restart_required.length===0?"Configuration saved and reloaded":`Saved; restart required: ${g.restart_required.join(", ")}`),g.restart_required.length>0)a("reload"),r("confirm");c()})},[e,d,s,c]);return{configEditRef:l,configEditText:d,configEditError:f,setConfigEditError:p,openConfigBuffer:m,saveConfigBuffer:h}}var Qw=D(()=>{te()});import{useEffect as nM}from"react";function Zw({controller:e,cfg:t,paused:r,logSince:n,refresh:o,setLogs:i,setCfg:s,setConfigReloadError:a,setStatus:c}){return nM(()=>{if(!e)return;let l,d=!1,u=[],f=t&&t.logs.max_memory_events>0?t.logs.max_memory_events:50000,p=()=>{if(l=void 0,u.length>0){let h=u.splice(0,u.length);i((v)=>Nx(v,h,n,f))}if(d)d=!1,o()},m=e.onEvent((h)=>{if(h.type===Mo&&h.payload&&typeof h.payload==="object"&&"event"in h.payload){let v=h.payload.event;if(!r)u.push(v);if(!l)l=setTimeout(p,30);return}if(h.type===_i){a(ob(h));return}if(h.type===ca)a(void 0),e.configSnapshot().then(s).catch((v)=>c(A(v)));if(d=!0,!l)l=setTimeout(p,30)});return()=>{if(m(),l)clearTimeout(l)}},[e,n,r,o]),{}}var eS=D(()=>{te();vr();gr()});import{useCallback as oM,useEffect as tS,useRef as rS,useState as vi}from"react";function nS({controller:e,workspace:t,cfg:r,snap:n,screen:o,configReloadError:i,setSnap:s,setStatus:a}){let{detectGoogle:c,runDoctor:l,loadPath:d}=t,[u,f]=vi(),[p,m]=vi(),[h,v]=vi(!1),[x,g]=vi(""),[R,_]=vi(0),[S,y]=vi({active:"Preparing diagnostics",checks:[]}),b=rS(void 0),w=rS(0),k=oM(async()=>{if(!e){a("Supervisor is not running");return}a("Probing configured identities\u2026");try{let L=await e.refreshAuth();s((W)=>W?{...W,identity:L}:W);let O=Object.values(L.service_account_status),F=O.filter((W)=>W==="available").length,Z=O.filter((W)=>W==="unavailable").length;a(O.length===0?"Identity refreshed \u2014 no service accounts configured":`Identity refreshed \u2014 ${F} available${Z>0?`, ${Z} unavailable`:""}`)}catch(L){a(A(L))}},[e]);return tS(()=>{c(r?.google.project_id??"").then(f).catch((L)=>{a(A(L))})},[r?.google.project_id,n?.identity.project]),tS(()=>{if(o!=="doctor"||!r)return;let L=b.current,O=n?.session_id??"";if(L?.cfg===r&&L.tick===R&&L.sessionID===O&&L.reloadError===i)return;b.current={cfg:r,tick:R,sessionID:O,reloadError:i};let F=++w.current;v(!0),g(""),y({active:"Preparing diagnostics",checks:[]});let Z;if(r.configPath!==""){Z="";try{d(r.repoRoot,r.configPath)}catch(W){Z=A(W)}}l(r,(W)=>{if(F===w.current)y(W)},{services:n?.services,proxyRunning:n?.proxy.running,repositoryConfigError:Z}).then((W)=>{if(F!==w.current)return;m(W)}).catch((W)=>{if(F!==w.current)return;g(A(W))}).finally(()=>{if(F===w.current)v(!1)})},[o,r,R,n?.session_id,i]),{google:u,setGoogle:f,doctor:p,doctorLoading:h,doctorError:x,doctorProgress:S,setDoctorTick:_,refreshAuth:k}}var oS=D(()=>{te()});import{useCallback as hf,useState as kl}from"react";function iS({controller:e,workspace:t,cfg:r,snap:n,refresh:o,setStatus:i,setOverlay:s}){let{resolveStartRequest:a,startupPlan:c,shutdownPlan:l,shutdownPlanExact:d}=t,[u,f]=kl(),[p,m]=kl([]),[h,v]=kl(!1),[x,g]=kl("start"),R=hf(async(y,b)=>{if(!e||!r){i("no configuration loaded");return}try{let w=y.length>0,k=a(r,{services:y,profile:w?void 0:b}),L=c(r,k.services,k.profile);g("start"),m(L.waves.flat().filter((Z)=>yo(n?.services[Z]))),f(L),s("plan"),v(!0);let O=k.services.filter((Z)=>!yo(n?.services[Z])),F=await e.start({services:O.length>0?O:k.services,profile:w?void 0:k.profile});await o(),v(!1),i(yw(F))}catch(w){v(!1),await o(),i(A(w))}},[r,e,o,n]),_=hf(async(y)=>{if(!e||!r){i("no configuration loaded");return}let b=y.length>0?y:Object.entries(n?.services??{}).filter(([,w])=>w.state!=="STOPPED"&&w.state!=="UNKNOWN").map(([w])=>w);if(b.length===0){i("nothing to stop");return}try{let w=l(r,b);g("stop"),m([]),f(w),s("plan"),v(!0),await e.stop(y),await o(),v(!1),i(xw(w))}catch(w){v(!1),await o(),i(A(w))}},[r,e,o,n]),S=hf(async(y,b,w=!1)=>{if(!e||!r){i("no configuration loaded");return}try{let k=vw(r,y,b),L=w?l(r,k.services):d(r,k.services);g("restart"),m([]),f(L),s("plan"),v(!0),await e.restart(k.services,w),await o(),v(!1);let O=yl(L);i(O===""?"Restarted selected services":`Restarted ${O}`)}catch(k){v(!1),await o(),i(A(k))}},[r,e,o]);return{plan:u,planInitiallyRunning:p,planBusy:h,lifecycle:x,beginStart:R,beginStop:_,beginRestart:S}}var sS=D(()=>{te();xl();At()});import{useCallback as Lr,useEffect as Kr,useMemo as Fn,useRef as iM,useState as xe}from"react";function cS(e,t,r,n,o){let i=Ep(e,t.sliceLen,t.windowStart,t.windowNewer);if(i.startDelta!==0){r(Math.max(0,t.windowStart+i.startDelta)),n(!0),o(i.selected);return}let s=Math.max(t.sliceLen-1,0),a=t.sliceLen>0&&(i.selected<s||t.windowNewer>0);if(a&&!t.pinned)r(Qc(t.filteredLen));n(a),o(i.selected)}function lS({controller:e,tui:t,names:r,screen:n,refresh:o,setStatus:i}){let[s,a]=xe([]),[c,l]=xe(!1),[d,u]=xe(!1),[f,p]=xe(!0),[m,h]=xe(""),[v,x]=xe(!1),[g,R]=xe(0),[_,S]=xe(""),[y,b]=xe(""),[w,k]=xe(""),[L,O]=xe(""),[F,Z]=xe(!1),[W,C]=xe(0),[j,B]=xe(Ar-1),[z,de]=xe(!1),[Ee,Ne]=xe(0),ne=aS,[H,ae]=xe(t.log_timestamps!==!1),[X,nt]=xe(t.log_metadata!==!1),[ft,ie]=xe(()=>[...Fx]),[Le]=xe(""),[Me]=xe(""),[Ke,gt]=xe(!1),[Xe,Ct]=xe("focus"),[ge,vt]=xe(!1),[at,yt]=xe(Ar-1),[rr,Qe]=xe(!1),[Xt,qe]=xe(-1),[mt,xr]=xe(),[Kt,br]=xe(""),[nr,qt]=xe(!1),[or,ir]=xe(!1),sr=iM([]),[Vt,ce]=xe(0),K=Fn(()=>el(r,s,ft),[r,s,ft]);Kr(()=>{ie((le)=>{let xt=new Set(le),Pt=!1;for(let Xr of s)if(Xr.service!==""&&!r.includes(Xr.service)&&!xt.has(Xr.service))xt.add(Xr.service),Pt=!0;return Pt?[...xt].sort():le})},[s,r]);let T=Fn(()=>xo(s,{service:w,services:ne,errorOnly:d,search:m,regex:Ke,source:Me,since:_,until:y,systemLogs:f}),[d,Ke,m,w,ne,_,Me,y,s,f]),ve=Fn(()=>_p(T,ge,Vt),[T,ge,Vt]),Y=ve.items,Lt=Fn(()=>xo(s,{service:L,services:ne,errorOnly:d,search:m,regex:Ke,source:Me,since:_,until:y,systemLogs:f}),[d,Ke,m,L,ne,_,Me,y,s,f]),ct=Fn(()=>_p(Lt,z,Ee),[Lt,z,Ee]),Ce=ct.items;Kr(()=>{qe(-1),vt(!1),yt(Math.max(0,Math.min(Ar,T.length)-1))},[d,m,w,f]),Kr(()=>{de(!1),B(Math.max(0,Math.min(Ar,Lt.length)-1))},[d,m,L,f]),Kr(()=>{if(n!=="logs")Qe(!1)},[n]),Kr(()=>{if(n!=="logs"||ge)return;yt(Math.max(0,Math.min(Ar,T.length)-1))},[T.length,ge,n]),Kr(()=>{if(n!=="logs"||z)return;B(Math.max(0,Math.min(Ar,Lt.length)-1))},[Lt.length,z,n]);let Ro=Lr(()=>{if(!ge)ce(Qc(T.length)),vt(!0)},[T.length,ge]),P=Lr(()=>{if(!z)Ne(Qc(Lt.length)),de(!0)},[Lt.length,z]),ue=Fn(()=>({sliceLen:Y.length,windowStart:ve.start,windowNewer:ve.newer,filteredLen:T.length,pinned:ge}),[T.length,ge,Y.length,ve.newer,ve.start]),Yr=Fn(()=>({sliceLen:Ce.length,windowStart:ct.start,windowNewer:ct.newer,filteredLen:Lt.length,pinned:z}),[Lt.length,z,Ce.length,ct.newer,ct.start]),Jr=Lr((le)=>cS(le,ue,ce,vt,yt),[ue]),wr=Lr((le)=>cS(le,Yr,Ne,de,B),[Yr]),se=Lr((le)=>{if(F&&W===1){wr(le);return}Jr(le)},[Jr,wr,W,F]),Bn=Lr((le)=>{let xt=Y.length,Pt=Ep(le,xt,ve.start,ve.newer);if(Pt.startDelta!==0)ce(Math.max(0,ve.start+Pt.startDelta)),vt(!0);qe(Pt.selected)},[Y.length,ve.newer,ve.start]),Un=Lr(()=>{if(vt(!1),de(!1),qe(-1),n==="logs")yt(Math.max(0,Math.min(Ar,T.length)-1)),B(Math.max(0,Math.min(Ar,Lt.length)-1));R((xt)=>xt+1);let le=Math.max(ve.newer,ct.newer);i(le>0?`Jumped to latest (+${le} new)`:"Jumped to latest logs")},[T.length,Lt.length,ve.newer,ct.newer,n]),Dr=Fn(()=>({services:F?aS:w!==""?[w]:ne,level:d?"ERROR":Le,search:m,regex:Ke,source:Me,since:_,until:y}),[d,Le,Ke,m,w,ne,_,Me,y,F]),dn=Lr(async()=>{if(!e)return;try{xr(await e.logsStats(Dr))}catch(le){i(A(le))}},[e,Dr]),Bs=Lr(async()=>{if(!e||c)return;try{let le=await e.logsPage(Dr);a((xt)=>Hx(xt,le.events)),br(le.prevCursor),qt(le.hasPrev),await dn()}catch(le){i(A(le))}},[e,Dr,c,dn]),ki=Lr(()=>{a([]),S(new Date().toISOString()),vt(!1),de(!1),i("Cleared on-screen log buffer")},[]),Ci=Lr(()=>{p((le)=>!le)},[]);return Kr(()=>{o().then(()=>Bs())},[o,Bs]),Kr(()=>{sr.current=s},[s]),Kr(()=>{let le=Tp(ge,ve.start,nr),xt=F&&Tp(z,ct.start,nr);if(!e||or||!le&&!xt)return;ir(!0),e.logsPage({...Dr,cursor:Kt,direction:"backward"}).then((Pt)=>{if(Pt.sessionChanged){i("Daemon session changed \u2014 older log history is no longer available"),qt(!1);return}let Xr=sr.current,Us=jx(Xr,Pt.events);a(Us);let _o=Us.length-Xr.length;if(_o>0){if(ge)ce((Sr)=>Sr+_o);if(z)Ne((Sr)=>Sr+_o)}br(Pt.prevCursor),qt(Pt.hasPrev)}).catch((Pt)=>i(A(Pt))).finally(()=>ir(!1))},[e,Dr,or,nr,ge,z,Kt,ve.start,ct.start,F]),Kr(()=>{if(!e||c||n!=="logs")return;let le=setInterval(()=>{dn()},sM);return()=>clearInterval(le)},[e,c,dn,n]),{logs:s,setLogs:a,paused:c,setPaused:l,errorOnly:d,setErrorOnly:u,showSystemLogs:f,logSearch:m,setLogSearch:h,logSearchFocused:v,setLogSearchFocused:x,logFollow:g,logSince:_,setLogSince:S,setLogUntil:b,logService:w,setLogService:k,logServiceB:L,setLogServiceB:O,splitLogs:F,splitFocus:W,setSplitFocus:C,toggleSplitLogs:()=>{if(F){C(0),Z(!1);return}if(L===""){let le=r.find((xt)=>xt!==w);if(le!==void 0)O(le)}Z(!0)},cycleSplitFocus:()=>C((le)=>le===0?1:0),setActiveLogService:(le)=>{let xt=(Pt)=>typeof le==="function"?le(Pt):le;if(F&&W===1){O(xt);return}k(xt)},logSliceB:Ce,logWindowB:ct,logSelectedB:j,logPinnedB:z,logServices:ne,logShowTimestamps:H,setLogShowTimestamps:ae,logShowMeta:X,setLogShowMeta:nt,logLevel:Le,logSource:Me,logRegex:Ke,setLogRegex:gt,logWrap:Xe,setLogWrap:Ct,logPinned:ge,logSelected:at,logsFullscreen:rr,setLogsFullscreen:Qe,dashboardLogCursor:Xt,setDashboardLogCursor:qe,logFacets:mt,logSources:K,filteredLogs:T,filteredLogsB:Lt,logWindow:ve,logSlice:Y,pinLogView:Ro,pinLogViewB:P,applyLogCursor:se,applyLogCursorA:Jr,applyLogCursorB:wr,applyDashboardLogCursor:Bn,jumpToLatestLogs:Un,clearLogs:ki,toggleSystemLogs:Ci}}var aS,sM=2000;var dS=D(()=>{te();gr();aS=[]});import{useCallback as Co,useState as uS}from"react";function pS({tui:e,controller:t,cfg:r,persistPrefs:n,snap:o,refresh:i,setStatus:s}){let[a,c]=uS(""),[l,d]=uS(()=>e.mcp_port??mo(t?.cfg.repoRoot??process.cwd())),u=Co((g)=>{let R=_n(g);d(R);let _=r?.repoRoot??process.cwd();if(Ey(_,R)){n({mcp_port:null},`MCP port ${R} (default)`);return}n({mcp_port:R},`MCP port ${R}`)},[r,n]),f=Co(()=>{if(a==="")return l;let g=_y(a,l);return c(""),u(g),g},[l,a,u]),p=Co(async(g)=>{if(!t||o?.mcp?.running!==!0)return;await t.mcpStop(),await t.mcpStart({port:g}),await i()},[t,i,o?.mcp?.running]),m=Co(async(g)=>{if(!t){s("Supervisor is not running");return}let R=o?.mcp?.disabled_tools??[],_=An(g.name,R),S=_?[...R,g.name]:R.filter((y)=>y!==g.name);try{let y=await t.mcpSetTools(S);n({mcp_disabled_tools:y},`${g.label} ${_?"disabled":"enabled"}`),await i()}catch(y){s(A(y))}},[t,n,i,o]),h=Co(async()=>{if(!t){s("Supervisor is not running");return}try{if(o?.mcp?.running)await t.mcpStop(),n({mcp_enabled:!1},"MCP off"),s("MCP stopped");else await t.mcpStart({port:l}),n({mcp_enabled:!0},"MCP on"),s("MCP started");await i()}catch(g){s(A(g))}},[t,l,n,i,o?.mcp?.running]),v=Co((g)=>{fl(g.text).then(()=>{s(`Copied ${g.title} snippet`)}).catch((R)=>{s(A(R))})},[]),x=Co((g)=>{let R=tf(g);if(R===void 0)return!1;let _=o?.mcp?.address??fi(o?.mcp?.port??l),S=Ds(_,o?.mcp?.token??"")[R];if(!S)return!1;return v(S),!0},[v,l,o?.mcp?.address,o?.mcp?.port,o?.mcp?.token]);return{mcpPortDraft:a,setMcpPortDraft:c,mcpPort:l,persistMcpPort:u,applyMcpPortDraft:f,restartMcpOnPort:p,toggleMcpTool:m,toggleMcp:h,copyMcpSnippet:v,copyFocusedMcpSnippet:x}}var fS=D(()=>{te();vl();us();Gp();Ms()});import{RGBA as aM}from"@opentui/core";import{useCallback as cn,useEffect as cM,useMemo as mS,useRef as Cl,useState as Pl}from"react";function hS({tui:e,controller:t,resolveTuiOverridePath:r,terminalBackground:n,userTuiConfigPath:o,snap:i,setStatus:s,saveTuiPreferences:a,setPaletteIndex:c,setOverlay:l,setConfirmKind:d,setScreen:u,setSelected:f,screen:p}){let[m,h]=Pl(e.theme||t?.cfg.ui.theme||"devctl"),v=Cl(m),[x,g]=Pl(e.mouse),R=Cl(e.mouse),[_,S]=Pl(e.leader_timeout),y=Cl(e.leader_timeout),[b,w]=Pl(()=>vs(e.font_size)),k=Cl(b),L=ux(r()),O=mS(()=>{let H=sx(m);if(Ec(m)!=="terminal"||!up(n))return H;let ae=n??H.background;return{...H,background:ae,panel:ae,element:ae,inverse:ae}},[n,m]),F=Ec(m)==="terminal"&&up(n)?aM.defaultBackground(n??void 0):O.background,Z=mS(()=>hx({themeName:m,fontSize:b,mouse:x,leaderMs:_,locked:L,configPath:L?e.path||gp(r(),o()):gp(r(),o()),mcpRunning:i?.mcp?.running===!0}),[b,_,x,L,i?.mcp?.running,m,e.path]),W=cn((H,ae)=>{if(L){s(`${ae} session only`);return}let X=a(H);s(`${ae} saved ${X}`)},[L]),C=cn((H)=>{h(H),v.current=H,W({theme:H},`theme ${H}`)},[W]),j=cn(()=>{let H=!x;g(H),R.current=H,W({mouse:H},`mouse ${H?"on":"off"} restart TUI to apply clicks`)},[x,W]),B=cn((H)=>{S(H),y.current=H,W({leader_timeout:H},`leader ${H}ms`)},[W]),z=cn((H)=>{let ae=vs(H);w(ae),k.current=ae,W({font_size:ae},`display ${Dc(ae)}`)},[W]),de=cn(()=>{let H=Ic();h(H.theme),v.current=H.theme,g(H.mouse),R.current=H.mouse,S(H.leader_timeout),y.current=H.leader_timeout,w(H.font_size),k.current=H.font_size,W(H,"restored default preferences")},[W]),Ee=cn((H)=>{if(H.id==="theme"){c(Math.max(0,St.indexOf(m))),l("themes");return}if(H.id==="mouse"){j();return}if(H.id==="leader"){B(_);return}if(H.id==="font"){z(b);return}if(H.id==="reset"){d("reset-prefs"),l("confirm");return}if(H.id==="mcp"){u("mcp"),f(0);return}s(H.detail)},[z,B,b,_,m,j]),Ne=cn((H,ae)=>{let X=Ln(Z,ae);if(!X)return;if(X.id==="theme"){C(px(m,H));return}if(X.id==="leader"){B(fx(_,H));return}if(X.id==="font"){z(ys(b,H));return}if(X.id==="mouse")j()},[z,B,b,_,C,Z,m,j]),ne=cn(()=>{h(v.current)},[]);return cM(()=>{if(p==="settings")return;h(v.current),w(k.current),S(y.current),g(R.current)},[p]),{themeName:m,setThemeName:h,leaderMs:_,fontSize:b,prefsLocked:L,palette:O,rootBackground:F,settingRows:Z,persistPrefs:W,persistTheme:C,toggleMouse:j,applyFont:z,applyReset:de,activateSetting:Ee,cycleSetting:Ne,revertThemePreview:ne}}var gS=D(()=>{Er();tt()});import{jsxDEV as $s}from"@opentui/react/jsx-dev-runtime";function vS(e){let{palette:t,termW:r,termH:n,step:o,answers:i,repo:s,draft:a,authStatus:c,onDraft:l,onSubmit:d}=e,u=wt[o]??wt[0],f=wt.length,p=lM(u.id,i,s);return $s(pt,{palette:t,title:`setup ${o+1}/${f} ${u.title}`,bottomTitle:"enter next \xB7 esc cancel",termW:r,termH:n,preferW:64,preferH:12,borderColor:t.primary,gap:1,children:[$s("text",{fg:t.muted,wrapMode:"word",children:p},void 0,!1,void 0,this),u.id==="auth"||u.id==="write"||u.id==="repo"?$s("text",{fg:t.text,wrapMode:"word",children:u.id==="auth"?c:u.id==="repo"?s:"Write .devctl/config.yaml and attach the daemon."},void 0,!1,void 0,this):$s("input",{focused:!0,value:a,placeholder:u.prompt,onInput:l,onSubmit:d,backgroundColor:t.element,focusedBackgroundColor:t.element,textColor:t.text,cursorColor:t.primary},void 0,!1,void 0,this),$s(it,{palette:t,hints:[{key:"enter",label:u.id==="write"?"write and attach":"next"},{key:"esc",label:"cancel"}]},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function gf(e,t,r){if(e==="name")return t.name;if(e==="project")return t.project;if(e==="sa")return t.sa;if(e==="audience")return t.audience;if(e==="port")return String(t.proxyPort);if(e==="profile")return t.profile;if(e==="repo")return r;return""}function yS(e,t,r,n){let o={...t};if(e==="name")o.name=r.trim()||wn(n).name;else if(e==="project")o.project=r.trim();else if(e==="sa")o.sa=r.trim();else if(e==="audience")o.audience=r.trim();else if(e==="port")o.proxyPort=Ea(r,t.proxyPort);else if(e==="profile")o.profile=r.trim();return o}function lM(e,t,r){if(e==="repo")return`Using ${r}. The TUI always writes under this checkout.`;if(e==="auth")return"ADC login needs a real TTY. Enter continues; if ADC is missing, login runs gcloud on the terminal.";if(e==="write")return`Project ${t.name||"(unnamed)"}. Enter writes the starter file and attaches the daemon.`;return wt.find((n)=>n.id===e)?.prompt??""}var vf=D(()=>{Ta();ke()});import{useCallback as yf,useState as Rl}from"react";function xS({workspace:e,renderer:t,setOverlay:r,setStatus:n,setScreen:o,onAttached:i,setController:s,setCfg:a,setBootError:c}){let l=process.cwd(),[d,u]=Rl(0),[f,p]=Rl(()=>wn(l)),[m,h]=Rl(""),[v,x]=Rl("Checking Application Default Credentials\u2026"),g=yf(()=>{let S=wn(l);u(0),p(S),h(gf("repo",S,l)),r("setup-wizard"),e.detectGoogle("").then((y)=>{p((b)=>({...b,project:y.projectID||b.project})),x(y.adcAvailable?`ADC available${y.userEmail?` (${y.userEmail})`:""}. Enter continues.`:"ADC is not available. Enter runs gcloud ADC login on this TTY, then continues.")})},[l,r,e]),R=yf(async(S)=>{try{La(l,S);let y=await e.openTui(l,"");i?.(y),s(y),a(y.cfg),c(void 0),r("none"),o("dashboard"),n("Wrote .devctl/config.yaml and attached the daemon")}catch(y){n(A(y))}},[i,l,c,a,s,r,o,n,e]),_=yf(async()=>{let S=wt[d];if(!S)return;let y=yS(S.id,f,m,l);if(S.id==="auth"){if(!(await e.detectGoogle(y.project)).adcAvailable)try{await Sl(t,()=>e.loginGoogle())}catch(L){n(A(L))}}if(p(y),S.id==="write"){await R(y);return}let b=Math.min(d+1,wt.length-1),w=wt[b];u(b),h(w?gf(w.id,y,l):"")},[f,m,R,t,l,d,e,n]);return{step:d,answers:f,draft:m,authStatus:v,repo:l,setDraft:h,startWizard:g,advanceWizard:_}}var bS=D(()=>{Ta();Da();te();vf()});import{useCallback as dM,useEffect as wS,useState as xf}from"react";function SS({controller:e,cfg:t,envService:r}){let[n,o]=xf(),[i,s]=xf(!1),[a,c]=xf(""),l=n?.name===r,d=l?n?.env:void 0,u=r===""?"config":i&&!l?"loading":l?"resolved":a!==""?"error":"config",f=l||r===""?"":a;wS(()=>{o(void 0),c("")},[t]),wS(()=>{if(!e||r===""||!t?.services[r])return;if(n?.name===r)return;let m=!1;return s(!0),c(""),e.execService(r,[],!0).then((h)=>{if(m)return;o({name:r,env:h.environment??{}}),c("")}).catch((h)=>{if(m)return;c(A(h))}).finally(()=>{s(!1)}),()=>{m=!0}},[t,e,r,n?.name]);let p=dM(async(m)=>{if(!e)return;try{let h=await e.execService(m,[],!0);o({name:m,env:h.environment??{}}),c("")}catch(h){throw c(A(h)),h}},[e]);return{inspectorEnv:d,inspectorEnvStatus:u,inspectorEnvError:f,resolveEnvironment:p}}var kS=D(()=>{te()});import{jsxDEV as Nn}from"@opentui/react/jsx-dev-runtime";function CS(e){let{palette:t,path:r,initialValue:n,error:o,termW:i,termH:s,textareaRef:a}=e;return Nn(pt,{palette:t,title:"config buffer",bottomTitle:`${It("s")} save \xB7 esc discard \xB7 e still opens $EDITOR`,termW:i,termH:s,preferW:88,preferH:28,gap:1,children:[Nn("box",{height:1,overflow:"hidden",children:Nn("text",{fg:t.muted,wrapMode:"none",children:r},void 0,!1,void 0,this)},void 0,!1,void 0,this),Nn("textarea",{ref:a,focused:!0,initialValue:n,backgroundColor:t.panel,focusedBackgroundColor:t.panel,textColor:t.text,focusedTextColor:t.text,cursorColor:t.primary},void 0,!1,void 0,this),o!==""?Nn("box",{height:2,overflow:"hidden",children:Nn("text",{fg:t.error,wrapMode:"word",children:o},void 0,!1,void 0,this)},void 0,!1,void 0,this):Nn("box",{height:1,overflow:"hidden",children:Nn("text",{fg:t.muted,children:"validate, then write. invalid YAML is not saved."},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var PS=D(()=>{ke();_r()});import{jsxDEV as bf}from"@opentui/react/jsx-dev-runtime";function RS(e){let{palette:t,title:r,body:n,kind:o,termW:i,termH:s}=e,a=nb(o),c=a.map((l)=>`${l.key} ${l.label}`).join(" \xB7 ");return bf(pt,{palette:t,title:r,bottomTitle:c,termW:i,termH:s,preferW:52,preferH:8,borderColor:t.warning,gap:1,children:[bf("text",{fg:t.text,wrapMode:"word",children:n},void 0,!1,void 0,this),bf(it,{palette:t,hints:a},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var _S=D(()=>{vr();ke()});import{jsxDEV as ES}from"@opentui/react/jsx-dev-runtime";function TS(e){let{palette:t,termW:r,termH:n}=e,o=Fp();return ES(pt,{palette:t,title:"leader",termW:r,termH:n,preferW:68,preferH:5,anchor:"bottom",children:ES(it,{palette:t,hints:o},void 0,!1,void 0,this)},void 0,!1,void 0,this)}var AS=D(()=>{So();ke()});import{jsxDEV as Jt,Fragment as uM}from"@opentui/react/jsx-dev-runtime";function LS(e){let{palette:t,event:r,termW:n,termH:o,scrollRef:i}=e;if(!r)return null;let s=r.raw?Lx(r.raw):void 0,a=r.message.length>120||r.message.includes(`
2159
+ `)||s!==void 0;return Jt(pt,{palette:t,title:"log details",bottomTitle:"j/k scroll \xB7 esc close",termW:n,termH:o,preferW:a?84:72,preferH:a?22:14,gap:1,children:Jt("scrollbox",{ref:i,focused:!1,stickyScroll:!1,scrollX:!1,style:Ue(t),children:Jt("box",{flexDirection:"column",overflow:"hidden",children:[Jt("text",{fg:t.text,wrapMode:"word",children:Yc(r.message)},void 0,!1,void 0,this),Jt("text",{fg:t.muted,children:`time ${r.timestamp}`},void 0,!1,void 0,this),Jt("text",{fg:Ze(r.service,t),children:`service ${r.service}`},void 0,!1,void 0,this),Jt("text",{fg:t.muted,children:`source ${r.source}${r.stream?` / ${r.stream}`:""}`},void 0,!1,void 0,this),Jt("text",{fg:t.muted,children:`level ${zc(r.level)}`},void 0,!1,void 0,this),Jt("text",{fg:t.muted,children:`pid ${r.pid||"\u2014"}`},void 0,!1,void 0,this),Jt("text",{fg:t.muted,children:`request ${r.request_id||"\u2014"}`},void 0,!1,void 0,this),Jt("text",{fg:t.muted,children:`identity ${r.identity||"\u2014"}`},void 0,!1,void 0,this),s===void 0?null:Jt(uM,{children:[Jt("text",{fg:t.muted,children:"raw json"},void 0,!1,void 0,this),Jt("text",{fg:t.text,wrapMode:"word",children:s},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this)}var DS=D(()=>{gr();ke();tt()});import{jsxDEV as U}from"@opentui/react/jsx-dev-runtime";function wf(e,t){switch(e){case"completed":return{icon:"\u2713",label:"Completed",tone:"success",color:t.success};case"failed":return{icon:"\u2717",label:"Failed",tone:"error",color:t.error};case"unhealthy":return{icon:"\u26A0",label:"Unhealthy",tone:"warning",color:t.warning};case"active":return{icon:"\u25CF",label:"In progress",tone:"primary",color:t.primary};default:return{icon:"\u25CB",label:"Queued",tone:"muted",color:t.muted}}}function pM(e,t,r,n){if(t!=="")return{icon:"\u2717",label:"Failed",tone:"error",color:n.error};if(!e)return{icon:"\u2713",label:"Finished",tone:"success",color:n.success};if(r==="unhealthy")return{icon:"\u26A0",label:"Waiting on health",tone:"warning",color:n.warning};return{icon:"\u25CF",label:"Running",tone:"primary",color:n.primary}}function IS(e){return U("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:U("text",{wrapMode:"none",children:[U("span",{fg:e.palette.primary,children:e.label.toUpperCase()},void 0,!1,void 0,this),e.detail?U("span",{fg:e.palette.muted,children:` ${e.detail}`},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}function MS(e){let{palette:t,plan:r,snap:n,busy:o,failed:i,kind:s,termW:a=80,termH:c=24,scrollRef:l,initiallyRunning:d=[],onDismiss:u}=e,f=new Set(d),p=r.waves.map((C,j)=>({wave:C.filter((B)=>!f.has(B)),waveIdx:j})).filter(({wave:C})=>C.length>0),m=ww(r,n,s),h=r.waves[m.currentWaveIndex]??[],v=Os(h,n,s),x=wf(v,t),g=pM(o,i,v,t),R=kw(s,o,i,r.profile),_=Cw(o,i),S=i?t.inverse:t.text,y=i?t.error:t.element,b=Math.min(92,Math.max(40,a-8)),w=b>=82?22:b>=66?17:13,k=12,L=9,O=b>=56,F=b>=72,Z=b<72||r.waves.length>3,W=r.profile?`PROFILE ${r.profile}`:s.toUpperCase();return U(pt,{palette:t,title:R,bottomTitle:"enter close \xB7 esc hide (operation continues)",termW:a,termH:c,preferW:98,preferH:28,borderColor:g.color,gap:0,children:[U("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:t.element,children:[U(Be,{palette:t,label:`${g.icon} ${g.label.toUpperCase()}`,tone:g.tone},void 0,!1,void 0,this),U("box",{paddingLeft:1,flexGrow:1,overflow:"hidden",children:U("text",{fg:t.text,wrapMode:"none",children:W},void 0,!1,void 0,this)},void 0,!1,void 0,this),U(Be,{palette:t,label:`${m.ready}/${m.total} READY`,tone:m.isComplete?"success":"info"},void 0,!1,void 0,this),U(Be,{palette:t,label:`WAVE ${Math.min(m.currentWaveIndex+1,Math.max(1,m.totalWaves))}/${Math.max(1,m.totalWaves)}`,tone:x.tone},void 0,!1,void 0,this)]},void 0,!0,void 0,this),U("box",{height:2,flexDirection:"column",overflow:"hidden",flexShrink:0,paddingLeft:1,paddingRight:1,children:[U("box",{height:1,flexDirection:"row",overflow:"hidden",children:[U("text",{wrapMode:"none",children:[U("span",{fg:t.muted,children:"PROGRESS "},void 0,!1,void 0,this),U("span",{fg:g.color,children:`[${m.progressBar}]`},void 0,!1,void 0,this),U("span",{fg:t.text,children:` ${m.percent}%`},void 0,!1,void 0,this)]},void 0,!0,void 0,this),U("box",{flexGrow:1},void 0,!1,void 0,this),U("text",{wrapMode:"none",children:U("span",{fg:x.color,children:`${x.icon} ${x.label}`},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this),U("box",{height:1,overflow:"hidden",children:U("text",{fg:t.muted,wrapMode:"none",children:o?s==="stop"?"Dependents stop first; each wave waits for the previous wave.":"Each wave waits until its services are healthy before the next wave starts.":_.primary},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this),U(IS,{palette:t,label:"Pipeline",detail:`${r.waves.length} execution wave${r.waves.length===1?"":"s"}`},void 0,!1,void 0,this),U("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:t.element,children:r.waves.map((C,j)=>{let B=Os(C,n,s),z=wf(B,t),de=Z?`W${j+1} ${z.icon} ${C.length}`:`W${j+1} ${z.icon} ${z.label} \xB7 ${C.length}`;return U("box",{flexDirection:"row",overflow:"hidden",flexShrink:0,children:[U(Be,{palette:t,label:de,tone:z.tone},void 0,!1,void 0,this),j<r.waves.length-1?U("box",{width:3,flexShrink:0,overflow:"hidden",children:U("text",{fg:t.muted,children:" \u2500\u25BA"},void 0,!1,void 0,this)},void 0,!1,void 0,this):null]},`step-${j}`,!0,void 0,this)})},void 0,!1,void 0,this),(r.blockers??[]).length>0?U("box",{minHeight:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:t.element,paddingLeft:1,children:U("text",{fg:t.error,wrapMode:"word",children:`BLOCKED ${r.blockers?.map((C)=>`${C.name}: ${C.message}`).join(" \xB7 ")}`},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,d.length>0?U("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,paddingLeft:1,children:U("text",{wrapMode:"none",children:[U("span",{fg:t.success,children:"\u2713 ALREADY READY"},void 0,!1,void 0,this),U("span",{fg:t.muted,children:` ${d.join(", ")}`},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this):null,U(IS,{palette:t,label:"Wave details",detail:"live process and health state"},void 0,!1,void 0,this),U("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:t.element,paddingLeft:1,children:[U("box",{width:2,flexShrink:0},void 0,!1,void 0,this),U("box",{width:w,flexShrink:0,overflow:"hidden",children:U("text",{fg:t.muted,children:oe("SERVICE",w)},void 0,!1,void 0,this)},void 0,!1,void 0,this),U("box",{width:12,flexShrink:0,overflow:"hidden",children:U("text",{fg:t.muted,children:oe("STATE",12)},void 0,!1,void 0,this)},void 0,!1,void 0,this),O?U("box",{width:9,flexShrink:0,overflow:"hidden",children:U("text",{fg:t.muted,children:oe("PORT",9)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,F?U("box",{flexGrow:1,overflow:"hidden",children:U("text",{fg:t.muted,children:"DETAIL"},void 0,!1,void 0,this)},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),U("scrollbox",{ref:l,focused:!1,stickyScroll:!1,scrollX:!1,style:Ue(t),children:U("box",{flexDirection:"column",overflow:"hidden",gap:1,children:p.map(({wave:C,waveIdx:j})=>{let B=Os(C,n,s),z=wf(B,t),de=`${Sw(s,j,B)} \xB7 ${C.length} service${C.length===1?"":"s"}`;return U("box",{border:!0,borderStyle:"rounded",borderColor:z.color,title:de,titleColor:z.color,flexDirection:"column",paddingLeft:1,paddingRight:1,flexShrink:0,overflow:"hidden",children:C.map((Ee)=>{let Ne=n?.services[Ee],ne=tr(Ne),H=bw(Ee,r,n,s),ae=Ee===i||ne==="FAILED",X=ne==="UNHEALTHY",nt=ne==="HEALTHY"||s==="stop"&&ne==="STOPPED",ft=vo(Ne),ie=ae?"\u2717":nt?"\u2713":X?"\u26A0":ne==="STARTING"||ne==="RUNNING"||ne==="STOPPING"||ne==="RESTARTING"?"\u25CF":"\u25CB",Le=ae?t.error:et(t,ne),Me=ae?t.error:X?t.warning:nt?t.success:t.muted;return U("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:[U("box",{width:2,flexShrink:0,overflow:"hidden",children:U("text",{fg:Le,children:ie},void 0,!1,void 0,this)},void 0,!1,void 0,this),U("box",{width:w,flexShrink:0,overflow:"hidden",children:U("text",{fg:Ze(Ee,t),wrapMode:"none",children:oe(Ee,w)},void 0,!1,void 0,this)},void 0,!1,void 0,this),U("box",{width:12,flexShrink:0,overflow:"hidden",children:U("text",{fg:et(t,ne),wrapMode:"none",children:oe(ne,12)},void 0,!1,void 0,this)},void 0,!1,void 0,this),O?U("box",{width:9,flexShrink:0,overflow:"hidden",children:U("text",{fg:ft?t.info:t.muted,wrapMode:"none",children:oe(ft?`:${ft}`:"\u2014",9)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,F?U("box",{flexGrow:1,overflow:"hidden",children:U("text",{fg:Me,wrapMode:"none",children:H},void 0,!1,void 0,this)},void 0,!1,void 0,this):null]},Ee,!0,void 0,this)})},`wave-card-${j}`,!1,void 0,this)})},void 0,!1,void 0,this)},void 0,!1,void 0,this),U("box",{height:3,flexShrink:0,flexDirection:"column",backgroundColor:y,border:["top"],borderStyle:"single",borderColor:g.color,paddingLeft:1,paddingRight:1,overflow:"hidden",onMouseDown:u,children:[U("text",{fg:S,wrapMode:"none",children:_.primary},void 0,!1,void 0,this),U("text",{fg:i?t.inverse:t.muted,wrapMode:"none",children:_.secondary},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}var OS=D(()=>{Ot();xl();At();ke();tt()});import{jsxDEV as yr}from"@opentui/react/jsx-dev-runtime";function $S(e){let{palette:t,route:r,termW:n,termH:o,scrollRef:i}=e;if(!r)return null;return yr(pt,{palette:t,title:r.name,bottomTitle:"j/k scroll \xB7 esc close",termW:n,termH:o,preferW:78,preferH:18,gap:1,children:yr("scrollbox",{ref:i,focused:!1,stickyScroll:!1,scrollX:!1,style:Ue(t),children:yr("box",{flexDirection:"column",overflow:"hidden",children:[yr("text",{fg:t.muted,children:`auth ${r.authType||"none"}`},void 0,!1,void 0,this),yr("text",{fg:t.muted,children:`identity ${r.identityType||"\u2014"}`},void 0,!1,void 0,this),yr("text",{fg:t.text,wrapMode:"word",children:`account ${r.serviceAccount||"\u2014"}`},void 0,!1,void 0,this),yr("text",{fg:t.text,wrapMode:"word",children:`audience ${r.audience||"\u2014"}`},void 0,!1,void 0,this),yr("text",{fg:t.muted,wrapMode:"word",children:`client id ${r.clientId||"ADC default"}`},void 0,!1,void 0,this),yr("text",{fg:t.muted,wrapMode:"word",children:`client secret ${r.clientSecret||"\u2014"}`},void 0,!1,void 0,this),yr("text",{fg:t.muted,children:`match host ${r.matchHost||"*"}`},void 0,!1,void 0,this),yr("text",{fg:t.muted,children:`match path ${r.matchPath||"*"}`},void 0,!1,void 0,this),yr("text",{fg:t.text,wrapMode:"word",children:`upstream ${r.upstream}`},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this)}var FS=D(()=>{ke()});import{jsxDEV as _l}from"@opentui/react/jsx-dev-runtime";function NS(e){let{palette:t,title:r,body:n,termW:o,termH:i,scrollRef:s}=e,a=n.length>400||n.split(`
2160
+ `).length>16;return _l(pt,{palette:t,title:r,bottomTitle:"j/k scroll \xB7 esc close",termW:o,termH:i,preferW:a?88:76,preferH:a?24:16,gap:1,children:_l("scrollbox",{ref:s,focused:!1,stickyScroll:!1,scrollX:!1,style:Ue(t),children:_l("box",{flexDirection:"column",overflow:"hidden",children:_l("text",{fg:n===""?t.muted:t.text,wrapMode:"word",children:n===""?"(empty)":n},void 0,!1,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this)}var HS=D(()=>{ke()});import{jsxDEV as Wt}from"@opentui/react/jsx-dev-runtime";function jS(e){let{palette:t,items:r,query:n,selected:o,onQuery:i,onSubmit:s,title:a="commands"}=e,c=wo(n)!=="",l=Pb(o,Sf,r.length),d=c?r.slice(l,l+Sf):Rb(r,o,Sf),u=c?[{group:"",items:d}]:Mp(d),f=Math.max(d.length+(c?0:u.length),1),p=di(r,o),m=bb(r);return Wt("box",{height:f+fM,flexShrink:0,border:!0,borderStyle:"rounded",borderColor:t.borderActive,titleColor:t.primary,backgroundColor:t.panel,title:a,flexDirection:"column",overflow:"hidden",children:[d.length===0?Wt("box",{height:1,paddingLeft:1,overflow:"hidden",children:Wt("text",{fg:t.muted,children:"no matching command"},void 0,!1,void 0,this)},void 0,!1,void 0,this):u.flatMap((h)=>[...h.group===""?[]:[Wt("box",{height:1,paddingLeft:1,overflow:"hidden",backgroundColor:t.panel,children:Wt("text",{fg:t.muted,wrapMode:"none",children:h.group},void 0,!1,void 0,this)},`g-${h.group}`,!1,void 0,this)],...h.items.map((x)=>{let g=il(x)===(p?il(p):"");return Wt("box",{height:1,flexDirection:"row",overflow:"hidden",paddingLeft:1,paddingRight:1,backgroundColor:g?t.highlight:t.panel,children:[Wt("box",{width:m,flexShrink:0,overflow:"hidden",children:Wt("text",{fg:g?t.primary:t.text,wrapMode:"none",children:`${g?"\u203A":" "} /${Op(x).slice(0,Ip)}`},void 0,!1,void 0,this)},void 0,!1,void 0,this),Wt("box",{flexGrow:1,overflow:"hidden",children:Wt("text",{fg:g?t.accent:t.muted,wrapMode:"none",children:xb(x)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},il(x),!0,void 0,this)})]),Wt("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:t.highlight,paddingLeft:1,children:[Wt("box",{width:2,flexShrink:0,children:Wt("text",{fg:t.primary,children:"/"},void 0,!1,void 0,this)},void 0,!1,void 0,this),Wt("box",{flexGrow:1,overflow:"hidden",children:Wt("input",{focused:!0,value:n,placeholder:"start logs settings",onInput:i,onSubmit:()=>s(),backgroundColor:t.highlight,focusedBackgroundColor:t.highlight,textColor:t.text,cursorColor:t.primary},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}var Sf=10,fM=3;var BS=D(()=>{So()});import{jsxDEV as GS}from"@opentui/react/jsx-dev-runtime";function KS(e){let{palette:t,themeName:r,selected:n,termW:o,termH:i,onIndex:s,onPreview:a,onPick:c}=e,l=St.map((u)=>({name:u===r?`${u} (active)`:u,description:u===r?`${dp[u]} \xB7 current`:dp[u],value:u})),d=nl(o,i,US,WS,"center",!go(ye()));return GS(pt,{palette:t,title:"/themes",bottomTitle:"enter save \xB7 esc revert",termW:o,termH:i,preferW:US,preferH:WS,children:GS("select",{height:Math.max(4,d.height-3),options:l,selectedIndex:n,showDescription:!0,showSelectionIndicator:!0,wrapSelection:!0,backgroundColor:t.panel,focusedBackgroundColor:t.panel,textColor:t.text,focusedTextColor:t.text,selectedBackgroundColor:t.highlight,selectedTextColor:t.primary,descriptionColor:t.muted,selectedDescriptionColor:t.accent,onChange:(u)=>{s(u);let f=St[u];if(f)a(f)},onSelect:(u,f)=>{let p=f?.value;if(typeof p==="string"&&p!=="")c(p)}},void 0,!1,void 0,this)},void 0,!1,void 0,this)}var US=52,WS=22;var qS=D(()=>{Mt();vr();ke();Er();tt()});function El(e,t,r){let n=e?.google?.project_id??"";if(n!=="")return{project:n,source:"configuration"};let o=t?.project||r?.projectID||"",i=t?.project_source||r?.projectSource||"";return{project:o,source:i}}import{jsxDEV as st}from"@opentui/react/jsx-dev-runtime";function Tl(e){let{palette:t,title:r,tone:n="muted",children:o}=e,i=n==="success"?t.success:n==="warning"?t.warning:n==="error"?t.error:t.border;return st("box",{border:!0,borderStyle:"rounded",borderColor:i,title:r,titleColor:i,flexDirection:"column",flexShrink:0,paddingLeft:1,paddingRight:1,marginBottom:1,overflow:"hidden",children:o},void 0,!1,void 0,this)}function VS(e){let{palette:t,cfg:r,google:n,identity:o}=e;if(!n&&!o)return st(pl,{palette:t,label:"Checking Application Default Credentials\u2026"},void 0,!1,void 0,this);let i=mM(r,o),s=o?.user||n?.userEmail||"",{project:a,source:c}=El(r,o,n),l=o?.adc??n?.adcAvailable===!0,d=o?.iap??(r?.proxy.routes??[]).some((h)=>h.auth.type.toLowerCase()==="iap"),u=i.filter((h)=>h.inactive!==!0),f=u.some((h)=>h.ok===!0),p=u.some((h)=>h.ok===!1),m=f?"AVAILABLE":p?"UNAVAILABLE":u.length>0?"NOT PROBED":i.length>0?"not required":"not configured";return st(kt,{palette:t,title:"identity",scroll:!0,children:[st(Tl,{palette:t,title:"Google identity",tone:l?"success":"warning",children:[st(ko,{palette:t,label:"user",value:s||"(unknown)"},void 0,!1,void 0,this),st(ko,{palette:t,label:"project",value:a||"(unset)"},void 0,!1,void 0,this),st(ko,{palette:t,label:"source",value:c||"\u2014"},void 0,!1,void 0,this),st(ko,{palette:t,label:"ADC",value:l?"\u2713 available":"\u2717 missing",tone:l?"success":"error"},void 0,!1,void 0,this),st(ko,{palette:t,label:"gcloud",value:n?.gcloudInstalled?"\u2713 installed":"\u2717 not installed",tone:n?.gcloudInstalled?"success":"error"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),st(Tl,{palette:t,title:`Service accounts (${i.length})`,tone:u.length===0?"muted":f?"success":"warning",children:[i.length===0?st("text",{fg:t.muted,wrapMode:"word",children:"none configured \u2014 identities come from config, never hard-coded"},void 0,!1,void 0,this):i.map((h)=>st("text",{fg:h.inactive?t.muted:h.ok===!0?t.success:h.ok===!1?t.error:t.text,wrapMode:"none",children:`${h.inactive?"\u2013":h.ok===!0?"\u2713":h.ok===!1?"\u2717":"\u25CB"} ${h.email}${h.inactive?" \u2014 inactive (route auth is none)":""}`},h.email,!1,void 0,this)),st("box",{height:1,flexShrink:0},void 0,!1,void 0,this),st(ko,{palette:t,label:"impersonation",value:m,tone:f?"success":u.length>0?"warning":"muted"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),st(Tl,{palette:t,title:"IAP",tone:d?"success":"muted",children:[st("box",{flexDirection:"row",overflow:"hidden",flexShrink:0,children:st(Be,{palette:t,label:d?"routes present":"no IAP routes",tone:d?"success":"idle"},void 0,!1,void 0,this)},void 0,!1,void 0,this),d?st("text",{fg:t.muted,wrapMode:"word",children:"each route needs its own audience"},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),!l?st(Tl,{palette:t,title:"ADC unavailable",tone:"warning",children:[st("text",{fg:t.text,wrapMode:"word",children:"Local services still start. Cloud identity, impersonation, and IAP need ADC."},void 0,!1,void 0,this),st("text",{fg:t.muted,wrapMode:"word",children:"/auth login suspends this screen and runs gcloud ADC login, then restores the TUI"},void 0,!1,void 0,this)]},void 0,!0,void 0,this):null]},void 0,!0,void 0,this)}function mM(e,t){let r=new Map((e?nu(e):[]).map((i)=>[i.email,i.active])),n=t?.service_account_status??{};return[...new Set([...r.keys(),...Object.keys(n)])].sort((i,s)=>i.localeCompare(s)).map((i)=>{let s=n[i]??"unknown",a=r.get(i)===!1;return{email:i,inactive:a,ok:a||s==="unknown"?void 0:s==="available"}})}var zS=D(()=>{jr();Yt();ke()});import{jsxDEV as M}from"@opentui/react/jsx-dev-runtime";function QS(e){let{palette:t,cfg:r,width:n,selectedTask:o=0}=e,i=ye();if(!r)return M(ze,{palette:t,title:"No configuration",body:"Create .devctl/config.yaml or run setup."},void 0,!1,void 0,this);let s=Math.max(gi+8,n-6-i.pad*2),a=s>=qw,c=a?Math.floor((s-JS)/2):s,l=Nw(r),d=Hw(r),u=jw(r),f=Bw(r),p=Uw(r);return M(kt,{palette:t,title:`config ${r.project.name||"unnamed"}`,children:[M(ut,{palette:t,items:Dw(r)},void 0,!1,void 0,this),M("box",{flexGrow:1,overflow:"hidden",children:M("scrollbox",{ref:e.scrollRef,focused:!1,stickyScroll:!1,scrollX:!1,style:Ue(t),children:M("box",{flexDirection:"column",overflow:"hidden",gap:XS,children:[M(kf,{wide:a,left:M(qr,{palette:t,title:"project",children:M(yi,{palette:t,facts:Iw(r,jt),width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this),right:M(qr,{palette:t,title:"google",children:M(yi,{palette:t,facts:Mw(r),width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this),M(kf,{wide:a,left:M(qr,{palette:t,title:"runtime",children:M(yi,{palette:t,facts:Ow(r),width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this),right:M(qr,{palette:t,title:"logs",children:M(yi,{palette:t,facts:$w(r),width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this),M(qr,{palette:t,title:`proxy ${r.proxy.enabled?"enabled":"disabled"}`,children:[M(yi,{palette:t,facts:Fw(r),width:s},void 0,!1,void 0,this),M(gM,{palette:t,routes:d,width:s},void 0,!1,void 0,this)]},void 0,!0,void 0,this),M(qr,{palette:t,title:`services ${l.length}`,children:M(hM,{palette:t,rows:l,width:s},void 0,!1,void 0,this)},void 0,!1,void 0,this),M(qr,{palette:t,title:`tasks ${p.length}`,children:M(YS,{palette:t,rows:p,empty:"no tasks",width:s,selected:o},void 0,!1,void 0,this)},void 0,!1,void 0,this),M(kf,{wide:a,left:M(qr,{palette:t,title:`profiles ${u.length}`,children:M(vM,{palette:t,rows:u,width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this),right:M(qr,{palette:t,title:`templates ${f.length}`,children:M(YS,{palette:t,rows:f,empty:"no templates",width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this),M(qr,{palette:t,title:"extras",children:M(yi,{palette:t,facts:Gw(r),width:s},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this),M(Ut,{palette:t,backgroundColor:t.element,edge:"top",children:M(it,{palette:t,hints:[{key:"v",label:"validate/save buffer"},{key:"e",label:"open in $EDITOR"},{key:"/diff",label:"config sources"},{key:"/reload",label:"re-read .devctl after editing"},{key:"enter",label:"run task"},{key:"j/k",label:"scroll"}]},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function kf(e){let{wide:t,left:r,right:n}=e;if(!t)return M("box",{flexDirection:"column",overflow:"hidden",gap:XS,flexShrink:0,children:[r,n]},void 0,!0,void 0,this);return M("box",{flexDirection:"row",overflow:"hidden",gap:JS,flexShrink:0,children:[M("box",{flexGrow:1,flexBasis:0,overflow:"hidden",children:r},void 0,!1,void 0,this),M("box",{flexGrow:1,flexBasis:0,overflow:"hidden",children:n},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function qr(e){let{palette:t,title:r,children:n}=e;return M("box",{border:!0,borderStyle:"rounded",borderColor:t.border,title:r,titleColor:t.accent,paddingLeft:1,paddingRight:1,flexDirection:"column",overflow:"hidden",flexShrink:0,children:n},void 0,!1,void 0,this)}function yi(e){let{palette:t,facts:r,width:n}=e,o=Math.max(4,n-gi-4);return M("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:r.map((i)=>M("box",{height:1,flexDirection:"row",overflow:"hidden",children:[M("box",{width:gi,flexShrink:0,overflow:"hidden",children:M("text",{fg:t.muted,wrapMode:"none",children:oe(i.label,gi)},void 0,!1,void 0,this)},void 0,!1,void 0,this),M("box",{width:o,flexShrink:0,overflow:"hidden",children:M("text",{fg:bM(t,i.tone),wrapMode:"none",children:oe(i.value,o)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},i.label,!0,void 0,this))},void 0,!1,void 0,this)}function hM(e){let{palette:t,rows:r,width:n}=e;if(r.length===0)return M(Al,{palette:t,text:"no services"},void 0,!1,void 0,this);let o=Kw(r),i=Math.max(8,n-o-6);return M("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:r.map((s)=>M("box",{height:1,flexDirection:"row",overflow:"hidden",children:[M("box",{width:o,flexShrink:0,overflow:"hidden",children:M("text",{fg:Ze(s.name,t),wrapMode:"none",children:oe(s.name,o)},void 0,!1,void 0,this)},void 0,!1,void 0,this),M("box",{width:i,flexShrink:0,overflow:"hidden",children:M("text",{fg:t.text,wrapMode:"none",children:_e(yM(s),i)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},s.name,!0,void 0,this))},void 0,!1,void 0,this)}function gM(e){let{palette:t,routes:r,width:n}=e;if(r.length===0)return M(Al,{palette:t,text:"no routes"},void 0,!1,void 0,this);let o=Math.min(gi+4,Math.max(8,...r.map((s)=>s.name.length))),i=Math.max(8,n-o-6);return M("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:r.map((s)=>M("box",{height:1,flexDirection:"row",overflow:"hidden",children:[M("box",{width:o,flexShrink:0,overflow:"hidden",children:M("text",{fg:t.primary,wrapMode:"none",children:oe(s.name,o)},void 0,!1,void 0,this)},void 0,!1,void 0,this),M("box",{width:i,flexShrink:0,overflow:"hidden",children:M("text",{fg:t.text,wrapMode:"none",children:_e(xM(s),i)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},`${s.name}-${s.match}`,!0,void 0,this))},void 0,!1,void 0,this)}function vM(e){let{palette:t,rows:r,width:n}=e;if(r.length===0)return M(Al,{palette:t,text:"no profiles"},void 0,!1,void 0,this);let o=Math.min(12,Math.max(8,...r.map((s)=>s.name.length))),i=Math.max(8,n-o-6);return M("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:r.map((s)=>M("box",{height:1,flexDirection:"row",overflow:"hidden",children:[M("box",{width:o,flexShrink:0,overflow:"hidden",children:M("text",{fg:t.primary,wrapMode:"none",children:oe(s.name,o)},void 0,!1,void 0,this)},void 0,!1,void 0,this),M("box",{width:i,flexShrink:0,overflow:"hidden",children:M("text",{fg:t.text,wrapMode:"none",children:_e(s.env==="\u2014"?s.services:`${s.services} env ${s.env}`,i)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},s.name,!0,void 0,this))},void 0,!1,void 0,this)}function YS(e){let{palette:t,rows:r,empty:n,width:o,selected:i}=e;if(r.length===0)return M(Al,{palette:t,text:n},void 0,!1,void 0,this);let s=Math.min(14,Math.max(8,...r.map((c)=>c.name.length))),a=Math.max(8,o-s-6);return M("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:r.map((c,l)=>{let d=i===l;return M("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:d?t.highlight:void 0,children:[M("box",{width:s,flexShrink:0,overflow:"hidden",children:M("text",{fg:d?t.primary:t.text,wrapMode:"none",children:oe(c.name,s)},void 0,!1,void 0,this)},void 0,!1,void 0,this),M("box",{width:a,flexShrink:0,overflow:"hidden",children:M("text",{fg:d?t.accent:t.muted,wrapMode:"none",children:_e(c.summary,a)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},c.name,!0,void 0,this)})},void 0,!1,void 0,this)}function Al(e){return M("box",{height:1,overflow:"hidden",children:M("text",{fg:e.palette.muted,wrapMode:"none",children:e.text},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function yM(e){let t=e.extends==="\u2014"?"":` ${e.extends}`;return` ${e.ports} ${e.identity} ${e.health} ${e.restart} \u2190 ${e.depends}${t}`}function xM(e){return` ${e.auth} ${e.identity} ${e.match} \u2192 ${e.upstream}`}function bM(e,t){if(t==="success")return e.success;if(t==="warning")return e.warning;if(t==="error")return e.error;if(t==="muted")return e.muted;return e.text}var JS=1,XS=1;var ZS=D(()=>{Yt();mf();Mt();Ot();ke();tt()});import{jsxDEV as Ft}from"@opentui/react/jsx-dev-runtime";function ek(e){let{palette:t,credentials:r}=e;if(!r)return Ft(pl,{palette:t,label:"Reading credential store\u2026"},void 0,!1,void 0,this);let n=r.entries.filter((i)=>i.valid).length,o=r.entries.length-n;return Ft(kt,{palette:t,title:"credentials",scroll:!0,children:[Ft("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:[Ft(Be,{palette:t,label:`backend: ${r.backend}`,tone:"info"},void 0,!1,void 0,this),r.entries.length>0?Ft(Be,{palette:t,label:`${n} valid`,tone:"success"},void 0,!1,void 0,this):null,o>0?Ft(Be,{palette:t,label:`${o} expired`,tone:"warning"},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),Ft("text",{fg:t.muted,wrapMode:"word",children:"Tokens stay in the OS keychain or ~/.devctl/credentials (0600). They are never shown here."},void 0,!1,void 0,this),Ft("box",{height:1,flexShrink:0},void 0,!1,void 0,this),r.entries.length===0?Ft("text",{fg:t.muted,children:"no cached credentials"},void 0,!1,void 0,this):Ft("box",{flexDirection:"column",overflow:"hidden",gap:1,children:r.entries.map((i)=>Ft("box",{border:!0,borderStyle:"rounded",borderColor:i.valid?t.success:t.warning,title:i.identity,titleColor:i.valid?t.success:t.warning,flexDirection:"column",flexShrink:0,paddingLeft:1,paddingRight:1,overflow:"hidden",children:[Ft("text",{wrapMode:"none",children:[Ft("span",{fg:i.valid?t.success:t.warning,children:i.valid?"\u2713 valid":"\u2717 expired"},void 0,!1,void 0,this),Ft("span",{fg:t.muted,children:` \xB7 expires ${i.expires_at}`},void 0,!1,void 0,this)]},void 0,!0,void 0,this),Ft("text",{fg:t.muted,wrapMode:"none",children:`audience: ${i.audience||"(none)"}`},void 0,!1,void 0,this)]},`${i.identity}|${i.audience}`,!0,void 0,this))},void 0,!1,void 0,this),Ft(ul,{palette:t,title:"Actions",body:"Use /refresh to mint again or auth refresh on the CLI. Invalidate clears the store without printing tokens.",hint:"/refresh /auth"},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var tk=D(()=>{Yt();ke()});import{jsxDEV as Ll}from"@opentui/react/jsx-dev-runtime";function xi(e){let t=e.newer>0?`g jump to latest \xB7 ${e.newer} new`:"g jump to latest",r=Math.min(e.width,t.length+4);return Ll("box",{position:"absolute",left:Math.max(0,Math.floor((e.width-r)/2)),bottom:e.bottom??2,width:r,height:3,border:!0,borderStyle:"rounded",borderColor:e.palette.warning,backgroundColor:e.palette.panel,alignItems:"center",justifyContent:"center",onMouseDown:e.onJump,children:Ll("text",{wrapMode:"none",children:[Ll("span",{fg:e.palette.primary,children:"g"},void 0,!1,void 0,this),Ll("span",{fg:e.palette.text,children:t.slice(1)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}var rk=()=>{};import{jsxDEV as Fs}from"@opentui/react/jsx-dev-runtime";function bi(e){let{palette:t,logs:r,names:n,service:o,errorOnly:i,width:s,onService:a,onToggleErrors:c,facets:l}=e,d=l?Wx(n,l):Ux(n,r),u=s<80,f=d.map((v)=>{let x=v.name===""?"all":v.name;return{id:x,label:u?x:`${x} \xB7 ${v.count}`,color:Ze(v.name,t)}}),p=Math.max(0,d.findIndex((v)=>v.name===o)),m=i?"ERROR+":u?"lvls":"all levels",h=Math.max(ai(f[p]?.label??"all"),s-ai(m));return Fs(Ut,{palette:t,backgroundColor:t.element,children:Fs("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:t.element,children:[Fs("box",{flexGrow:1,overflow:"hidden",children:Fs(dl,{palette:t,items:f,active:p,width:h,emphasis:"fill",onPick:(v)=>{let x=d[v];if(x)a(x.name)}},void 0,!1,void 0,this)},void 0,!1,void 0,this),Fs(Be,{palette:t,label:m,tone:i?"error":"muted",onMouseDown:c},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}var nk=D(()=>{gr();In();ke();tt()});import{jsxDEV as wM}from"@opentui/react/jsx-dev-runtime";function Ns(e){let t=Math.min(e.total,e.start+e.count),r=Math.max(0,e.start),n=Math.max(0,e.total-t);return wM(ut,{palette:e.palette,items:[{text:`view ${e.start+1}\u2013${t} of ${e.total}`,tone:"primary"},{text:r>0?`\u2191 ${r} older`:"start of history",tone:r>0?"info":"idle"},{text:n>0?`\u2193 ${n} newer`:"at latest",tone:n>0?"warning":"success"}],hints:[{key:"pgup/pgdn",label:"move history window"},{key:"g",label:"latest"}]},void 0,!1,void 0,this)}var ok=D(()=>{ke()});import{useEffect as Cf,useRef as ik}from"react";import{jsxDEV as Ye}from"@opentui/react/jsx-dev-runtime";function wi(e){let{palette:t,logs:r,limit:n,width:o=80,followTick:i=0,focused:s=!1,wrapMode:a="clip",selected:c=-1,follow:l=!0,showTimestamps:d=!0,showMeta:u=!0,onPick:f,onLeaveLatest:p,viewStart:m=0,search:h="",regex:v=!1}=e,x=ik(null),g=ik(""),R=n===void 0?r:r.slice(-n),_=[...new Set(r.map((k)=>k.service))],S=$x(o,_),y=Ox({width:o,serviceWidth:S,showTimestamps:d,showMeta:u}),b=R[R.length-1],w=b===void 0?"empty":String(b.seq);return Cf(()=>{let k=x.current;if(!k)return;if(k.stickyScroll=l,l)k.stickyStart="bottom",k.scrollTo({x:k.scrollLeft,y:Math.max(0,k.scrollHeight)})},[l,i,w]),Cf(()=>{let k=`${m}:${c}:${a}`;if(g.current===k)return;if(g.current=k,l&&(c<0||c>=R.length-1))return;let L=x.current;if(!L||c<0)return;let O=`log-row-${c}`,F=requestAnimationFrame(()=>{L.stickyScroll=!1,L.scrollChildIntoView(O)});return()=>cancelAnimationFrame(F)},[l,c,R.length,m,a]),Cf(()=>{if(!l||!p)return;let k=!1,L=0,O=setInterval(()=>{let F=x.current;if(!F)return;let Z=F.viewport.height;if(Z<=0)return;let W=F.scrollHeight;if(F.scrollTop+Z>=W-kM){k=!0,L=W;return}if(W>L){F.stickyScroll=!0,F.stickyStart="bottom",F.scrollTo({x:F.scrollLeft,y:Math.max(0,W)}),L=W;return}if(k)p()},SM);return()=>{clearInterval(O)}},[l,i,p]),Ye("box",{flexGrow:1,height:"100%",overflow:"hidden",children:Ye("scrollbox",{ref:x,focused:s,stickyScroll:l,stickyStart:"bottom",scrollX:!1,style:{rootOptions:{flexGrow:1,height:"100%",overflow:"hidden",backgroundColor:t.panel},viewportOptions:{backgroundColor:t.panel},contentOptions:{backgroundColor:t.panel},scrollbarOptions:{trackOptions:{foregroundColor:t.primary,backgroundColor:t.element}}},children:Ye("box",{flexDirection:"column",overflow:"hidden",children:R.map((k,L)=>Ye(CM,{id:`log-row-${L}`,palette:t,event:k,serviceWidth:S,msgWidth:y,expanded:Ix(a,L===c),active:L===c,showTimestamps:d,showMeta:u,onPick:f?()=>f(L):void 0,search:h,regex:v},`${k.timestamp}-${k.service}-${L}`,!1,void 0,this))},void 0,!1,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function CM(e){let{id:t,palette:r,event:n,serviceWidth:o,msgWidth:i,expanded:s,active:a,showTimestamps:c=!0,showMeta:l=!0,onPick:d,search:u="",regex:f=!1}=e,p=Dx(Jc(n.message,i),i,s);return Ye("box",{id:t,flexShrink:0,flexDirection:"column",overflow:"hidden",backgroundColor:a?r.highlight:void 0,onMouseDown:d,children:p.visible.map((m,h)=>Ye("box",{height:1,flexDirection:"row",overflow:"hidden",children:[c?Ye("box",{width:qc,flexShrink:0,overflow:"hidden",children:Ye("text",{fg:r.muted,children:h===0?n.timestamp.slice(11,19):""},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,Ye("box",{width:o,flexShrink:0,overflow:"hidden",children:Ye("text",{fg:h===0?Ze(n.service,r):r.muted,wrapMode:"none",children:h===0?oe(n.service,o):""},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ye("box",{width:Es,flexShrink:0,overflow:"hidden",children:Ye("text",{children:" "},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ye("box",{width:Vc,flexShrink:0,overflow:"hidden",children:Ye("text",{fg:h===0?et(r,n.level):r.muted,children:h===0?zc(String(n.level)):""},void 0,!1,void 0,this)},void 0,!1,void 0,this),l?Ye("box",{width:Kc,flexShrink:0,overflow:"hidden",children:Ye("text",{fg:r.muted,children:h===0&&n.source?oe(n.source,Kc):""},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,Ye("box",{width:Es,flexShrink:0,overflow:"hidden",children:Ye("text",{fg:r.muted,children:h===0?" ":"\u2502"},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ye("box",{flexGrow:1,overflow:"hidden",children:Ye(PM,{palette:r,level:n.level,text:m,mark:h===0?p.mark:"",search:u,regex:f},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},`${n.timestamp}-${h}`,!0,void 0,this))},void 0,!1,void 0,this)}function PM(e){let{palette:t,level:r,text:n,mark:o,search:i="",regex:s=!1}=e;return Ye("text",{wrapMode:"none",children:[Mx(n,i,s).map((a,c)=>Ye("span",{fg:cx(t,r,a.kind),children:a.text},`${a.kind}-${c}`,!1,void 0,this)),o===""?null:Ye("span",{fg:t.muted,children:o},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var SM=200,kM=2;var sk=D(()=>{Ot();gr();tt()});var Pf=D(()=>{rk();nk();ok();sk()});import{useRef as RM}from"react";import{jsxDEV as Ie}from"@opentui/react/jsx-dev-runtime";function Dl(e){let{palette:t,names:r,snap:n,selected:o,checked:i,width:s,onOpen:a,onSelectIndex:c,onToggle:l}=e,d=ye(),u=RM(!1),f=new Set(i),p=Cp(s),m=kp(s),h=Sp(s),v=Sx(s);return Ie("box",{flexDirection:"column",flexGrow:1,overflow:"hidden",children:[Ie("box",{height:1,flexDirection:"row",overflow:"hidden",children:[Ie(Gt,{width:2,fg:t.muted,text:""},void 0,!1,void 0,this),Ie(Gt,{width:4,fg:t.accent,text:"sel"},void 0,!1,void 0,this),Ie(Gt,{width:2,fg:t.muted,text:""},void 0,!1,void 0,this),Ie(Gt,{width:v,fg:t.muted,text:"name"},void 0,!1,void 0,this),Ie(Gt,{width:$t,fg:t.muted,text:""},void 0,!1,void 0,this),Ie(Gt,{width:Tr,fg:t.muted,text:"state"},void 0,!1,void 0,this),h?Ie(Gt,{width:Ur,fg:t.muted,text:"health"},void 0,!1,void 0,this):null,m?Ie(Gt,{width:Oc,fg:t.muted,text:"port"},void 0,!1,void 0,this):null,p?Ie(Gt,{width:sn,fg:t.muted,text:"pid"},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),r.map((x,g)=>{let R=n?.services[x],_=tr(R),S=R?.health??"UNKNOWN",y=f.has(x),b=g===o,w=y?"[x]":"[ ]",k=b?"\u203A":" ";return Ie("box",{height:d.rowH,flexDirection:"row",overflow:"hidden",backgroundColor:b?t.highlight:y?t.element:void 0,onMouseDown:()=>{if(u.current)return;if(b){a(x);return}c(g)},children:[Ie("box",{width:2,flexShrink:0,children:Ie("text",{fg:t.primary,children:k},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ie("box",{width:4,flexShrink:0,onMouseDown:()=>{u.current=!0,l?.(x),setTimeout(()=>{u.current=!1},0)},children:Ie("text",{fg:_M(t,y,b),children:w},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ie("box",{width:2,flexShrink:0,children:Ie("text",{fg:et(t,_),children:hr(_)},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ie(Gt,{width:v,fg:b?t.primary:Ze(x,t),text:oe(x,v)},void 0,!1,void 0,this),Ie(Gt,{width:$t,fg:t.muted,text:""},void 0,!1,void 0,this),Ie(Gt,{width:Tr,fg:et(t,_),text:_},void 0,!1,void 0,this),h?Ie(Gt,{width:Ur,fg:et(t,S),text:S},void 0,!1,void 0,this):null,m?Ie(Gt,{width:Oc,fg:t.text,text:vo(R)},void 0,!1,void 0,this):null,p?Ie(Gt,{width:sn,fg:t.text,text:R?.pid?String(R.pid):""},void 0,!1,void 0,this):null]},x,!0,void 0,this)})]},void 0,!0,void 0,this)}function Il(e){let{palette:t,checked:r}=e,n=r.length;if(n>0)return Ie(ut,{palette:t,items:[{text:`${n} selected`,tone:"primary"}],hints:[{key:"space",label:"unmark"},{key:"n",label:"start these"},{key:"x",label:"stop these"}]},void 0,!1,void 0,this);return null}function _M(e,t,r){if(t)return e.success;if(r)return e.primary;return e.muted}function Gt(e){return Ie("box",{width:e.width,flexShrink:0,overflow:"hidden",children:Ie("text",{fg:e.fg,wrapMode:"none",children:e.text},void 0,!1,void 0,this)},void 0,!1,void 0,this)}var Rf=D(()=>{Mt();Ot();At();ke();tt()});import{jsxDEV as fe}from"@opentui/react/jsx-dev-runtime";function ck(e){let{palette:t,cfg:r,snap:n,logs:o,names:i,selected:s,selectedLog:a=-1,checked:c,width:l,followTick:d,logService:u,logSources:f,errorOnly:p,showSystemLogs:m,onOpen:h,onSelectIndex:v,onToggle:x,onFilterService:g,onToggleErrors:R,onShowErrors:_,wrapMode:S="clip",view:y,follow:b=!0,onLeaveLatest:w,onPickLog:k,viewStart:L=0,viewTotal:O,newer:F=0,onJumpLatest:Z,facets:W,leftover:C,search:j="",regex:B=!1}=e,z=ye();if(!r||i.length===0)return fe(ze,{palette:t,title:"No services configured",body:"Add services to .devctl/config.yaml or finish setup.",hint:"open the setup screen with /setup"},void 0,!1,void 0,this);let de=l<ci,Ee=Hc(n),Ne=al(n,i),ne=i.filter((ge)=>n?.services[ge]?.state==="FAILED").length,H=Bx(o),ae=Ee?Px(C,n?.session_id):void 0,X=Nc(l,i,de),nt=Math.max(24,de?l-4:l-X-4),ft=m?o:o.filter((ge)=>!si(ge.source)),ie=xo(o,{service:u,errorOnly:p,systemLogs:m,search:j,regex:B}),Le=y??ie,Me=O??ie.length,Ke=Math.min(Me,L+Le.length),gt=Me===0?"empty":`${L+1}\u2013${Ke} of ${Me}`,Xe=u===""?"all":u,Ct=[...Ne.running>0?[{text:`${Ne.running}/${i.length}`,tone:"success"}]:[],...ne>0?[{text:`${ne} failed`,tone:"error"}]:[],...c.length>0?[{text:`${c.length} selected`,tone:"primary"}]:[],...H>0?[{text:`${H} errors`,tone:"error",onMouseDown:_}]:[]];return fe("box",{flexGrow:1,flexDirection:de?"column":"row",overflow:"hidden",children:[fe("box",{flexGrow:de?1:0,flexShrink:0,minWidth:de?void 0:X,width:de?void 0:X,border:!0,borderStyle:"rounded",borderColor:Ee?t.border:t.borderActive,backgroundColor:t.panel,title:"services",titleColor:t.primary,flexDirection:"column",overflow:"hidden",children:[Ct.length>0?fe(ut,{palette:t,items:Ct},void 0,!1,void 0,this):null,fe(Il,{palette:t,checked:c},void 0,!1,void 0,this),fe("box",{flexGrow:1,paddingLeft:z.pad,paddingRight:z.pad,overflow:"hidden",children:fe(Dl,{palette:t,names:i,snap:n,selected:s,checked:c,width:Fc(X,z.pad),onOpen:h,onSelectIndex:v,onToggle:x},void 0,!1,void 0,this)},void 0,!1,void 0,this),fe(LM,{palette:t,names:i,snap:n,width:X,onOpen:h},void 0,!1,void 0,this),ae?fe(IM,{palette:t,leftover:ae,width:X},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),fe("box",{position:"relative",flexGrow:2,minWidth:de?void 0:32,minHeight:de?8:void 0,border:!0,borderStyle:"rounded",borderColor:t.border,backgroundColor:t.panel,title:`logs \xB7 ${Xe} \xB7 ${gt}`,titleColor:t.primary,overflow:"hidden",flexDirection:"column",children:[fe(bi,{palette:t,logs:ft,names:f??i,service:u,errorOnly:p,width:nt,onService:g,onToggleErrors:R,facets:W},void 0,!1,void 0,this),Me>Le.length?fe(Ns,{palette:t,start:L,count:Le.length,total:Me},void 0,!1,void 0,this):null,ie.length===0?fe(ze,{palette:t,title:Ee?"Waiting for logs":o.length===0?"No events yet":"No events in this filter",body:Ee?"Start services to stream output here. Later starts keep earlier logs.":o.length===0?"New lines appear as services write output.":"Click All or another service chip, or turn off ERROR+.",hint:"\u2190 \u2192 cycle service e errors g latest"},void 0,!1,void 0,this):fe(wi,{palette:t,logs:Le,width:nt,followTick:d,focused:!1,wrapMode:S,selected:a,follow:b,onLeaveLatest:w,onPick:k,viewStart:L,search:j,regex:B},d,!1,void 0,this),!b?fe(xi,{palette:t,width:nt,newer:F,bottom:0,onJump:Z},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}function TM(e){if(!e)return!1;return e.state===dt||e.health===Oe||e.state===fr||e.restarts>0||e.last_error!==""}function ak(e){if(e.state===dt)return 0;if(e.health===Oe)return 1;if(e.state===fr)return 2;return 3}function AM(e){if(e.last_error!=="")return e.last_error;if(e.restarts>0)return`restarted ${e.restarts}x, no error recorded`;return e.health===Oe?"failing health check":e.state.toLowerCase()}function LM(e){let{palette:t,names:r,snap:n,width:o,onOpen:i}=e,s=r.map((d)=>({name:d,rt:n?.services[d]})).filter((d)=>TM(d.rt)).sort((d,u)=>ak(d.rt)-ak(u.rt));if(s.length===0)return null;let a=s.slice(0,EM),c=s.length-a.length,l=Math.max(8,o-_f-4);return fe("box",{flexDirection:"column",flexShrink:0,paddingLeft:1,paddingRight:1,overflow:"hidden",children:[fe("box",{height:1,overflow:"hidden",children:fe("text",{fg:t.error,wrapMode:"none",children:`issues (${s.length})`},void 0,!1,void 0,this)},void 0,!1,void 0,this),a.map(({name:d,rt:u})=>{let f=tr(u);return fe("box",{height:1,flexDirection:"row",overflow:"hidden",onMouseDown:()=>i(d),children:[fe("box",{width:2,flexShrink:0,children:fe("text",{fg:et(t,f),children:hr(f)},void 0,!1,void 0,this)},void 0,!1,void 0,this),fe("box",{width:_f,flexShrink:0,overflow:"hidden",children:fe("text",{fg:Ze(d,t),wrapMode:"none",children:oe(d,_f)},void 0,!1,void 0,this)},void 0,!1,void 0,this),fe("box",{width:l,flexShrink:0,overflow:"hidden",children:fe("text",{fg:t.muted,wrapMode:"none",children:_e(AM(u),l)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},d,!0,void 0,this)}),c>0?fe("text",{fg:t.muted,wrapMode:"none",children:`+${c} more \u2014 open a service to see details`},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}function IM(e){let{palette:t,leftover:r,width:n}=e,o=r.processes.slice(0,DM),i=r.processes.length-o.length,s=Math.max(8,n-Ef-4);return fe("box",{flexDirection:"column",flexShrink:0,paddingLeft:1,paddingRight:1,overflow:"hidden",children:[fe("box",{height:1,overflow:"hidden",children:fe("text",{fg:t.warning,wrapMode:"none",children:"last session"},void 0,!1,void 0,this)},void 0,!1,void 0,this),fe("text",{fg:t.muted,wrapMode:"none",children:_e(`${r.session_id} ${r.profile||"(none)"}`,Math.max(8,n-4))},void 0,!1,void 0,this),o.map((a)=>fe("box",{height:1,flexDirection:"row",overflow:"hidden",children:[fe("box",{width:Ef,flexShrink:0,overflow:"hidden",children:fe("text",{fg:t.text,wrapMode:"none",children:oe(a.name,Ef)},void 0,!1,void 0,this)},void 0,!1,void 0,this),fe("box",{width:s,flexShrink:0,overflow:"hidden",children:fe("text",{fg:t.muted,wrapMode:"none",children:_e(`pid ${a.pid} leftover`,s)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},`${a.name}-${a.pid}`,!0,void 0,this)),i>0?fe("text",{fg:t.muted,wrapMode:"none",children:`+${i} more leftover pids`},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}var EM=4,_f=14,DM=4,Ef=14;var lk=D(()=>{Tt();Yt();Mt();vr();Ot();gr();At();ll();ke();tt();Pf();Rf()});import MM from"cli-spinners";import{useEffect as OM,useState as $M}from"react";import{jsxDEV as re}from"@opentui/react/jsx-dev-runtime";function dk(e){let{palette:t,report:r,loading:n,progress:o,error:i,selected:s,onPick:a,onReload:c}=e,l=pi(s,Af);if(n)return re(FM,{palette:t,progress:o},void 0,!1,void 0,this);if(i)return re(zb,{palette:t,title:"Doctor failed",body:i},void 0,!1,void 0,this);if(!r)return re(ze,{palette:t,title:"Doctor",body:"Diagnostics have not run yet.",hint:"opening this screen runs doctor"},void 0,!1,void 0,this);let d=r.checks.filter((v)=>v.severity==="ok").length,u=r.checks.filter((v)=>v.severity==="warn").length,f=r.checks.filter((v)=>v.severity==="error").length,p=r.checks.length,m=ii(p>0?d/p:0),h=f>0?t.error:u>0?t.warning:t.success;return re(kt,{palette:t,title:`doctor ${r.issues} issue(s)`,children:[re("box",{height:4,flexDirection:"column",flexShrink:0,alignItems:"center",justifyContent:"center",overflow:"hidden",border:!0,borderStyle:"rounded",borderColor:t.primary,backgroundColor:t.element,onMouseDown:c,children:[re("text",{wrapMode:"none",children:[re("span",{fg:t.inverse,bg:t.primary,children:" r "},void 0,!1,void 0,this),re("span",{fg:t.primary,children:" RUN DOCTOR AGAIN"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),re("text",{fg:t.muted,wrapMode:"none",children:"results stay cached until you refresh"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),re("box",{height:1,flexShrink:0},void 0,!1,void 0,this),re("box",{height:1,flexShrink:0,overflow:"hidden",children:re("text",{wrapMode:"none",children:[re("span",{fg:h,children:`[${m}]`},void 0,!1,void 0,this),re("span",{fg:t.success,children:` ${d} ok`},void 0,!1,void 0,this),u>0?re("span",{fg:t.warning,children:` \xB7 ${u} warn`},void 0,!1,void 0,this):null,f>0?re("span",{fg:t.error,children:` \xB7 ${f} error`},void 0,!1,void 0,this):null,u===0&&f===0?re("span",{fg:t.muted,children:" \xB7 all clear"},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)},void 0,!1,void 0,this),re("box",{height:1,flexShrink:0},void 0,!1,void 0,this),re("scrollbox",{ref:l,focused:!1,stickyScroll:!1,scrollX:!1,style:Ue(t),children:re("box",{flexDirection:"column",overflow:"hidden",gap:1,children:r.checks.map((v,x)=>re(NM,{palette:t,check:v,index:x,active:x===s,onPick:a},`${v.name}-${x}`,!1,void 0,this))},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function FM({palette:e,progress:t}){let[r,n]=$M(0);return OM(()=>{let o=setInterval(()=>n((i)=>(i+1)%Tf.frames.length),Tf.interval);return()=>clearInterval(o)},[]),re("box",{flexGrow:1,alignItems:"center",justifyContent:"center",overflow:"hidden",children:re("box",{width:"80%",maxWidth:52,height:10,flexShrink:1,border:!0,borderStyle:"rounded",borderColor:e.borderActive,backgroundColor:e.panel,flexDirection:"column",alignItems:"center",justifyContent:"center",overflow:"hidden",children:[re("text",{wrapMode:"none",children:[re("span",{fg:e.primary,children:`${Tf.frames[r]} `},void 0,!1,void 0,this),re("span",{fg:e.text,children:"devctl doctor"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),re("box",{height:1,flexShrink:0},void 0,!1,void 0,this),re("text",{fg:e.text,wrapMode:"none",children:`Checking ${t.active}`},void 0,!1,void 0,this),re("box",{height:1,flexShrink:0},void 0,!1,void 0,this),t.checks.slice(-4).map((o,i)=>re("text",{wrapMode:"none",children:[re("span",{fg:et(e,o.severity==="error"?"ERROR":o.severity==="warn"?"WARN":"OK"),children:`${hr(o.severity==="error"?"ERROR":o.severity==="warn"?"WARN":"OK")} `},void 0,!1,void 0,this),re("span",{fg:e.muted,children:o.name},void 0,!1,void 0,this)]},`${o.name}-${i}`,!0,void 0,this)),t.checks.length===0?re("text",{fg:e.muted,children:"Waiting for the first result\u2026"},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}function NM(e){let{palette:t,check:r,index:n,active:o,onPick:i}=e,s=r.severity==="error"?"ERROR":r.severity==="warn"?"WARN":"OK";if(r.severity==="ok")return re("box",{id:`${Af}-${n}`,flexDirection:"row",flexShrink:0,paddingLeft:1,overflow:"hidden",backgroundColor:o?t.highlight:void 0,onMouseDown:()=>i(n),children:re("text",{wrapMode:"none",children:[re("span",{fg:t.primary,children:o?"\u203A ":" "},void 0,!1,void 0,this),re("span",{fg:et(t,s),children:hr(s)},void 0,!1,void 0,this),re("span",{fg:o?t.primary:t.text,children:` ${r.name}`},void 0,!1,void 0,this),re("span",{fg:t.muted,children:r.message?` \xB7 ${r.message}`:""},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this);let c=r.severity==="error"?t.error:t.warning;return re("box",{id:`${Af}-${n}`,border:!0,borderStyle:"rounded",borderColor:o?t.borderActive:c,title:r.name,titleColor:c,flexDirection:"column",flexShrink:0,paddingLeft:1,paddingRight:1,overflow:"hidden",backgroundColor:o?t.highlight:void 0,onMouseDown:()=>i(n),children:[re("text",{wrapMode:"word",children:[re("span",{fg:et(t,s),children:`${hr(s)} `},void 0,!1,void 0,this),re("span",{fg:t.text,children:r.message},void 0,!1,void 0,this)]},void 0,!0,void 0,this),r.hint?re("text",{fg:t.info,wrapMode:"word",children:`\u2192 ${r.hint}`},void 0,!1,void 0,this):null,o&&r.action?.kind==="free-port"?re("text",{fg:t.primary,wrapMode:"none",children:`enter stop ${r.action.holder.command} (pid ${r.action.holder.pid})`},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}var Af="doctor-row",Tf;var uk=D(()=>{Yt();Ot();ke();tt();Tf=MM.bouncingBall});import{jsxDEV as We,Fragment as HM}from"@opentui/react/jsx-dev-runtime";function fk(e){let{palette:t,logs:r,names:n,logSources:o,service:i,errorOnly:s,showSystemLogs:a,search:c,searchFocused:l,followTick:d,width:u,onSearch:f,onService:p,onToggleErrors:m,onSelect:h,wrapMode:v="focus",selected:x=-1,follow:g=!0,newer:R=0,viewStart:_=0,viewTotal:S,onLeaveLatest:y,onJumpLatest:b,view:w,fullscreen:k=!1,facets:L,split:O=!1,splitFocus:F=0,serviceB:Z="",viewB:W,selectedB:C=-1,followB:j=!0,newerB:B=0,viewStartB:z=0,viewTotalB:de,onServiceB:Ee,onSelectB:Ne,onLeaveLatestB:ne,onFocusPane:H}=e,ae=ye(),X=Rp(u,ae.pad,k),nt=a?r:r.filter((ge)=>!si(ge.source)),ft=xo(r,{service:i,services:e.services,errorOnly:s,search:c,regex:e.regex,source:e.source,systemLogs:a}),ie=w??ft,Le=S??ft.length,Me=Math.min(Le,_+ie.length),Ke=Le===0?"empty":`${_+1}\u2013${Me} of ${Le}`,gt=e.services??[],Xe=gt.length>0?gt.join(","):i===""?"all services":i,Ct=[...g?[]:[{text:R>0?`${O?"left ":""}pinned \xB7 +${R} new`:`${O?"left ":""}pinned`,tone:"warning"}],...O&&!j?[{text:B>0?`right pinned \xB7 +${B} new`:"right pinned",tone:"warning"}]:[],...e.source?[{text:`src ${e.source}`,tone:"info"}]:[],...e.regex?[{text:"regex",tone:"accent"}]:[],...c===""?[]:[{text:`search ${c}`,tone:"accent"}],...O?[{text:F===0?"left pane":"right pane",tone:"accent"}]:[]];if(O){let ge=Math.max(20,Math.floor(u/2)),vt=Math.max(20,u-ge),at=W??[],yt=de??at.length;return We("box",{flexGrow:1,flexDirection:"column",overflow:"hidden",children:[l?We("box",{height:1,paddingLeft:1,backgroundColor:t.highlight,overflow:"hidden",children:We("input",{focused:!0,value:c,placeholder:"search messages or service names",onInput:f,backgroundColor:t.highlight,focusedBackgroundColor:t.highlight,textColor:t.text,cursorColor:t.primary},void 0,!1,void 0,this)},void 0,!1,void 0,this):We(ut,{palette:t,items:Ct},void 0,!1,void 0,this),We("box",{flexGrow:1,flexDirection:"row",overflow:"hidden",children:[We(pk,{palette:t,names:o??n,logs:r,service:i,errorOnly:s,showSystemLogs:a,search:c,regex:e.regex,source:e.source,width:ge,shown:ie,shownTotal:Le,selected:x,follow:g,newer:R,viewStart:_,wrapMode:v,showTimestamps:e.showTimestamps!==!1,showMeta:e.showMeta!==!1,followTick:d,facets:L,focused:F===0,onService:p,onToggleErrors:m,onSelect:h,onLeaveLatest:y,onFocus:()=>H?.(0)},void 0,!1,void 0,this),We(pk,{palette:t,names:o??n,logs:r,service:Z,errorOnly:s,showSystemLogs:a,search:c,regex:e.regex,source:e.source,width:vt,shown:at,shownTotal:yt,selected:C,follow:j,newer:B,viewStart:z,wrapMode:v,showTimestamps:e.showTimestamps!==!1,showMeta:e.showMeta!==!1,followTick:d,facets:L,focused:F===1,onService:Ee??p,onToggleErrors:m,onSelect:Ne,onLeaveLatest:ne,onFocus:()=>H?.(1)},void 0,!1,void 0,this)]},void 0,!0,void 0,this),!g||!j?We(xi,{palette:t,width:u,newer:Math.max(R,B),onJump:b},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}return We("box",{flexGrow:1,flexDirection:"column",overflow:"hidden",children:[k?null:We(HM,{children:[Ct.length>0?We(ut,{palette:t,items:Ct},void 0,!1,void 0,this):null,We(bi,{palette:t,logs:nt,names:o??n,service:i,errorOnly:s,width:u,onService:p,onToggleErrors:m,facets:L},void 0,!1,void 0,this)]},void 0,!0,void 0,this),l?We("box",{height:1,paddingLeft:1,backgroundColor:t.highlight,overflow:"hidden",children:We("input",{focused:!0,value:c,placeholder:"search messages or service names",onInput:f,backgroundColor:t.highlight,focusedBackgroundColor:t.highlight,textColor:t.text,cursorColor:t.primary},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,w&&Le>ie.length?We(Ns,{palette:t,start:_,count:ie.length,total:Le},void 0,!1,void 0,this):null,We("box",{flexGrow:1,border:!k,borderStyle:"rounded",borderColor:t.border,title:k?void 0:`logs ${Xe} \xB7 ${Ke}`,titleColor:t.primary,padding:k?0:ae.pad,overflow:"hidden",children:ie.length===0?We(ze,{palette:t,title:r.length===0?"No log events":"No events in this filter",body:r.length===0?"Start services to stream logs.":"Pick All, another service, or clear search / ERROR+.",hint:`\u2190 \u2192 cycle filters e errors i internal ${It("l")} clear`},void 0,!1,void 0,this):We(wi,{palette:t,logs:ie,width:X,followTick:d,focused:!1,wrapMode:v,selected:x,follow:g,showTimestamps:e.showTimestamps!==!1,showMeta:e.showMeta!==!1,onPick:h,onLeaveLatest:y,viewStart:_,search:c,regex:e.regex===!0},d,!1,void 0,this)},void 0,!1,void 0,this),!g?We(xi,{palette:t,width:u,newer:R,onJump:b},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}function pk(e){let{palette:t,width:r,service:n,shown:o,shownTotal:i,viewStart:s,focused:a}=e,c=e.showSystemLogs?e.logs:e.logs.filter((u)=>!si(u.source)),l=n===""?"all services":n,d=i===0?"empty":`${s+1}\u2013${Math.min(i,s+o.length)} of ${i}`;return We("box",{width:r,flexGrow:0,flexShrink:0,flexDirection:"column",overflow:"hidden",onMouseDown:e.onFocus,children:[We(bi,{palette:t,logs:c,names:e.names,service:n,errorOnly:e.errorOnly,width:r,onService:(u)=>{e.onFocus(),e.onService(u)},onToggleErrors:()=>{e.onFocus(),e.onToggleErrors()},facets:e.facets},void 0,!1,void 0,this),We("box",{flexGrow:1,border:!0,borderStyle:"rounded",borderColor:a?t.borderActive:t.border,title:`logs ${l} \xB7 ${d}`,titleColor:a?t.primary:t.muted,overflow:"hidden",children:o.length===0?We(ze,{palette:t,title:"No events in this pane",body:"Click the pane, then \u2190 \u2192 to pick a service.",hint:"\\\\ split | focus"},void 0,!1,void 0,this):We(wi,{palette:t,logs:o,width:Rp(r,0,!1),followTick:e.followTick,focused:!1,wrapMode:e.wrapMode,selected:e.selected,follow:e.follow,showTimestamps:e.showTimestamps,showMeta:e.showMeta,onPick:(u)=>{e.onFocus(),e.onSelect?.(u)},onLeaveLatest:e.onLeaveLatest,viewStart:s,search:e.search,regex:e.regex===!0},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var mk=D(()=>{Pf();Yt();Mt();gr();ke();_r()});import{jsxDEV as Ge}from"@opentui/react/jsx-dev-runtime";function gk(e){let{palette:t,cfg:r,snap:n,profile:o,selected:i,onPick:s}=e,a=ye(),c=pi(i,hk),l=Object.keys(r?.profiles??{}).sort();if(l.length===0)return Ge(ze,{palette:t,title:"No profiles",body:"Add profiles.<name>.services in config.yaml."},void 0,!1,void 0,this);return Ge(kt,{palette:t,title:"profiles",children:[Ge("box",{height:1,flexShrink:0,overflow:"hidden",children:Ge("text",{fg:t.muted,wrapMode:"none",children:[`${l.length} profile${l.length===1?"":"s"} \xB7 current: `,Ge("span",{fg:t.success,children:o||"(none)"},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this),Ge("box",{height:1,flexShrink:0},void 0,!1,void 0,this),Ge("scrollbox",{ref:c,focused:!1,stickyScroll:!1,scrollX:!1,style:Ue(t),children:Ge("box",{flexDirection:"column",overflow:"hidden",gap:a.gap||1,children:l.map((d,u)=>{let f=d===o,p=u===i,m=r?.profiles[d]?.services??[],h=Object.keys(r?.profiles[d]?.environment??{}).length,v=p?t.borderActive:f?t.success:t.border,x=p?t.primary:f?t.success:t.muted;return Ge("box",{id:`${hk}-${u}`,border:!0,borderStyle:"rounded",borderColor:v,title:d,titleColor:x,flexDirection:"column",flexShrink:0,paddingLeft:1,paddingRight:1,overflow:"hidden",backgroundColor:p?t.highlight:void 0,onMouseDown:()=>s(u),children:[Ge("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:Ge("text",{wrapMode:"none",children:[f?Ge("span",{fg:t.success,children:"\u25CF current profile"},void 0,!1,void 0,this):Ge("span",{fg:t.muted,children:"\u25CB not active"},void 0,!1,void 0,this),Ge("span",{fg:t.muted,children:` \xB7 ${m.length} service${m.length===1?"":"s"}`},void 0,!1,void 0,this),h>0?Ge("span",{fg:t.muted,children:` \xB7 ${h} env var${h===1?"":"s"}`},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)},void 0,!1,void 0,this),Ge("box",{flexDirection:"row",overflow:"hidden",flexShrink:0,children:m.length===0?Ge("text",{fg:t.muted,wrapMode:"none",children:"(no services)"},void 0,!1,void 0,this):Ge("text",{wrapMode:"word",children:m.map((g,R)=>{let _=tr(n?.services[g]);return Ge("span",{children:[Ge("span",{fg:et(t,_),children:hr(_)},void 0,!1,void 0,this),Ge("span",{fg:Ze(g,t),children:` ${g}`},void 0,!1,void 0,this),R<m.length-1?Ge("span",{fg:t.muted,children:" "},void 0,!1,void 0,this):null]},g,!0,void 0,this)})},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},d,!0,void 0,this)})},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ge(Ut,{palette:t,backgroundColor:t.element,edge:"top",children:Ge(it,{palette:t,hints:[{key:"space",label:"set current"},{key:"enter",label:"set and start"}]},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var hk="profile-row";var vk=D(()=>{Yt();Mt();At();ke();tt()});import{jsxDEV as V,Fragment as wk}from"@opentui/react/jsx-dev-runtime";function jM(e){let t=e.toLowerCase();if(t==="iap")return"info";if(t==="service"||t==="service_account")return"primary";return"idle"}function BM(e,t,r){if(r||t>=500)return e.error;if(t===0||t>=400)return e.warning;return e.success}function UM(e,t){if(t.startsWith("sa:"))return{label:"SA",color:e.primary};if(t!=="")return{label:"USR",color:e.info};return{label:"\u2014",color:e.muted}}function qM(e){let{palette:t,req:r,cols:n}=e,o=BM(t,r.status,r.error),i=r.status>0?String(r.status):"ERR",s=UM(t,r.identity),a=r.route||Sk,c=r.error?`${r.path} \u2014 ${_e(r.error,KM)}`:r.path;return V("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:[V("box",{width:Lf,flexShrink:0,overflow:"hidden",children:V("text",{fg:t.muted,children:r.timestamp.slice(11,19)},void 0,!1,void 0,this)},void 0,!1,void 0,this),n.showMethod?V("box",{width:Ml,flexShrink:0,overflow:"hidden",children:V("text",{fg:t.text,wrapMode:"none",children:oe(r.method,Ml)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,V("box",{width:Ol,flexShrink:0,overflow:"hidden",children:V("text",{fg:o,wrapMode:"none",children:oe(i,Ol)},void 0,!1,void 0,this)},void 0,!1,void 0,this),n.showDur?V("box",{width:$l,flexShrink:0,overflow:"hidden",children:V("text",{fg:t.muted,wrapMode:"none",children:oe(`${r.durationMs}ms`,$l)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,V("box",{width:Fl,flexShrink:0,overflow:"hidden",children:V("text",{fg:s.color,wrapMode:"none",children:oe(s.label,Fl)},void 0,!1,void 0,this)},void 0,!1,void 0,this),V("box",{width:n.routeCol,flexShrink:0,overflow:"hidden",children:V("text",{fg:r.route?t.info:t.muted,wrapMode:"none",children:oe(a,n.routeCol)},void 0,!1,void 0,this)},void 0,!1,void 0,this),V("box",{flexGrow:1,overflow:"hidden",children:V("text",{fg:r.error?t.error:t.text,wrapMode:"none",children:c},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function VM(e){let{palette:t,cols:r}=e;return V("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:t.element,children:[V("box",{width:Lf,flexShrink:0,overflow:"hidden",children:V("text",{fg:t.muted,children:oe("TIME",Lf)},void 0,!1,void 0,this)},void 0,!1,void 0,this),r.showMethod?V("box",{width:Ml,flexShrink:0,overflow:"hidden",children:V("text",{fg:t.muted,children:oe("METHOD",Ml)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,V("box",{width:Ol,flexShrink:0,overflow:"hidden",children:V("text",{fg:t.muted,children:oe("ST",Ol)},void 0,!1,void 0,this)},void 0,!1,void 0,this),r.showDur?V("box",{width:$l,flexShrink:0,overflow:"hidden",children:V("text",{fg:t.muted,children:oe("DUR",$l)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,V("box",{width:Fl,flexShrink:0,overflow:"hidden",children:V("text",{fg:t.muted,children:oe("WHO",Fl)},void 0,!1,void 0,this)},void 0,!1,void 0,this),V("box",{width:r.routeCol,flexShrink:0,overflow:"hidden",children:V("text",{fg:t.muted,children:oe("ROUTE",r.routeCol)},void 0,!1,void 0,this)},void 0,!1,void 0,this),V("box",{flexGrow:1,overflow:"hidden",children:V("text",{fg:t.muted,children:"PATH"},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function zM(e){let{palette:t,name:r,auth:n,identity:o,upstream:i,match:s,onMouseDown:a}=e;return V("box",{flexDirection:"column",flexShrink:0,overflow:"hidden",onMouseDown:a,children:[V("text",{fg:t.text,wrapMode:"none",children:r},void 0,!1,void 0,this),V("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:[V(Be,{palette:t,label:n||"no auth",tone:jM(n)},void 0,!1,void 0,this),o?V(Be,{palette:t,label:o,tone:"idle"},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),s?V("text",{fg:t.muted,wrapMode:"word",children:`match: ${s}`},void 0,!1,void 0,this):null,V("text",{fg:t.muted,wrapMode:"word",children:`\u2192 ${i}`},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function kk(e){let{palette:t,cfg:r,snap:n,width:o,onSelectRoute:i}=e,s=n?.proxy.routes??[],a=new Map((r?.proxy.routes??[]).map((S)=>[S.name,S])),c=new Map((r?.proxy.routes??[]).map((S)=>[S.name,S.match])),l=n?.proxy.recentRequests??[],d=n?.proxy.requestTotal??0,u=n?.proxy.requestErrors??0,f=o<ci,p=Math.max(yk,Math.min(YM,Math.floor(o*0.36))),m=f?o-xk:o-p-xk,h=Math.max(20,m-2),v=h>=bk,x=h>=JM,g=h>=bk?XM:QM,R=l.reduce((S,y)=>Math.min(g,Math.max(S,(y.route||Sk).length+GM)),WM),_={showMethod:v,showDur:x,routeCol:R};return V(kt,{palette:t,title:"proxy",children:[V(ut,{palette:t,items:[{text:n?.proxy.running?"RUNNING":"STOPPED",tone:n?.proxy.running?"success":"idle"},...n?.proxy.address?[{text:n.proxy.address,tone:"info"}]:[],{text:`${s.length} routes`,tone:s.length>0?"primary":"idle"},...d>0?[{text:`${d} requests`,tone:"idle"},...u>0?[{text:`${u} errors`,tone:"warning"}]:[]]:[]]},void 0,!1,void 0,this),V("box",{flexGrow:1,flexDirection:f?"column":"row",overflow:"hidden",children:[V("box",{flexGrow:f?1:0,flexBasis:f?0:void 0,flexShrink:0,minWidth:f?void 0:yk,width:f?void 0:p,minHeight:f?8:void 0,border:!0,borderStyle:"rounded",borderColor:t.border,title:"routes",titleColor:t.primary,paddingLeft:1,paddingRight:1,flexDirection:"column",overflow:"hidden",children:s.length===0?V(ze,{palette:t,title:"No proxy routes",body:"Add routes under proxy.routes or .devctl/proxy/routes.yaml."},void 0,!1,void 0,this):V(wk,{children:[i?V("text",{fg:t.muted,wrapMode:"word",children:"click a route for full details"},void 0,!1,void 0,this):null,V("scrollbox",{focused:!1,stickyScroll:!1,scrollX:!1,style:Ue(t),children:V("box",{flexDirection:"column",overflow:"hidden",gap:1,children:s.map((S)=>{let y=c.get(S.name),b=y&&(y.host||y.path)?`${y.host||"*"}${y.path?y.path:""}`:void 0,w=a.get(S.name);return V(zM,{palette:t,name:S.name,auth:S.auth,identity:S.identity,upstream:S.upstream,match:b,onMouseDown:i?()=>i({name:S.name,authType:w?.auth.type||S.auth,identityType:w?.auth.identity.type??"",serviceAccount:w?.auth.identity.service_account||w?.auth.service_account||"",audience:w?.auth.audience??"",clientId:w?.auth.client_id??"",clientSecret:Ha(w?.auth.client_secret??"")??((w?.auth.client_secret??"").trim()?"inline":""),matchHost:w?.match.host??"",matchPath:w?.match.path??"",upstream:S.upstream}):void 0},S.name,!1,void 0,this)})},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this),V("box",{flexGrow:1,flexBasis:0,minWidth:f?void 0:30,minHeight:f?10:void 0,border:!0,borderStyle:"rounded",borderColor:t.borderActive,title:"requests",titleColor:t.primary,flexDirection:"column",overflow:"hidden",children:l.length===0?V("box",{paddingLeft:1,paddingRight:1,children:V("text",{fg:t.muted,wrapMode:"word",children:n?.proxy.running?"No requests seen yet. Send one through the proxy to see it show up here \u2014 no need to restart or press r.":"Start the proxy, then send it a request to see live traffic here."},void 0,!1,void 0,this)},void 0,!1,void 0,this):V(wk,{children:[V(VM,{palette:t,cols:_},void 0,!1,void 0,this),V("scrollbox",{focused:!1,stickyScroll:!1,scrollX:!1,style:Ue(t),children:V("box",{flexDirection:"column",overflow:"hidden",children:l.map((S)=>V(qM,{palette:t,req:S,cols:_},S.requestId,!1,void 0,this))},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this),V(Ut,{palette:t,backgroundColor:t.element,edge:"top",children:V(it,{palette:t,hints:[{key:"n",label:"start proxy"},{key:"x",label:"stop proxy"}]},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var Lf=9,Ml=8,Ol=4,$l=8,Fl=4,WM=8,GM=2,KM=64,Sk="(none)",yk=26,YM=56,xk=2,bk=46,JM=54,XM=20,QM=12;var Ck=D(()=>{Yt();vr();Ot();ke()});import{jsxDEV as pe}from"@opentui/react/jsx-dev-runtime";function _k(e){let{palette:t,name:r}=e;return pe("box",{flexGrow:1,border:!0,borderStyle:"rounded",borderColor:t.borderActive,backgroundColor:t.panel,title:r,titleColor:Ze(r,t),flexDirection:"column",overflow:"hidden",children:pe(Df,{...e,compact:!1},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function Df(e){let{palette:t,cfg:r,snap:n,name:o,reveal:i,width:s,compact:a=!0,envScrollRef:c}=e,l=ye(),d=r?.services[o];if(!d)return pe(ze,{palette:t,title:"Unknown service",body:`No service named ${o}.`,hint:"j/k move"},void 0,!1,void 0,this);let u=n?.services[o],f=tr(u),p=u?.health??"UNKNOWN",m=vo(u)||Rs(d,u),h=Cx(d,i,r?.secrets.extra_markers??[],r?.secrets.extra_patterns??[],e.resolvedEnv),v=e.envStatus==="error"?"error":e.envStatus==="resolved"?"success":e.envStatus==="loading"?"info":"muted",x=e.envStatus==="resolved"?"resolved":e.envStatus==="loading"?"resolving":e.envStatus==="error"?"config fallback":"config",g=s>=eO,R=[{label:"health",value:Uc(d)},{label:"workdir",value:d.working_dir||"."},{label:"restart",value:Gc(d)},{label:"caps",value:d.capabilities.join(", ")||"\u2014"},{label:"runtime",value:d.container?`${d.container.runtime||"docker"} \xB7 ${d.container.image}`:"host"},...d.watch.enabled?[{label:"watch",value:d.watch.paths.join(" ")||"on"}]:[]],_=[{label:"identity",value:Wc(d,u)},{label:"depends",value:d.dependencies.map(Pa).join(", ")||"\u2014"},{label:"ports",value:Rs(d,u)},{label:"tries",value:String(u?.restarts??0),tone:u?.restarts?"warning":"muted"}],S=Math.max(Po+8,s-2),y=g?Math.floor(S/2):S;return pe("box",{padding:l.pad,flexGrow:1,flexDirection:"column",overflow:"hidden",children:[pe(ut,{palette:t,items:[{text:f,tone:Rk(f)},{text:p,tone:Rk(p)},{text:u?.pid?`pid ${u.pid}`:"pid \u2014",tone:u?.pid?"info":"idle"},{text:m==="\u2014"?"port \u2014":`port ${m}`,tone:m==="\u2014"?"idle":"info"}]},void 0,!1,void 0,this),d.description?pe("box",{height:1,overflow:"hidden",children:pe("text",{fg:t.muted,wrapMode:"none",children:_e(d.description,Math.max(8,s-4))},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,u?.last_error?pe("box",{height:1,overflow:"hidden",children:pe("text",{fg:t.error,wrapMode:"none",children:_e(u.last_error,ZM)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,pe(If,{palette:t,item:{label:"command",value:Bc(d)},valueWidth:Math.max(8,S-Po)},void 0,!1,void 0,this),pe(tO,{palette:t,left:R,right:_,wide:g,colWidth:y},void 0,!1,void 0,this),pe(rO,{palette:t,entries:h,reveal:i,width:s,focused:!a,scrollRef:c,source:x,sourceTone:v,error:e.envError,onSelect:e.onSelectEnv},void 0,!1,void 0,this),a?null:pe(it,{palette:t,hints:[{key:"n",label:"start"},{key:"x",label:"stop"},{key:"R",label:"restart"},{key:"o",label:"config"},{key:"l",label:"logs"},{key:"/reveal",label:"secrets"},{key:"esc",label:"back"}]},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function tO(e){let{palette:t,left:r,right:n,wide:o,colWidth:i}=e,s=Math.max(4,i-Po);if(!o)return pe("box",{width:i,flexShrink:0,flexDirection:"column",overflow:"hidden",children:[...r,...n].map((a)=>pe(If,{palette:t,item:a,valueWidth:s},a.label,!1,void 0,this))},void 0,!1,void 0,this);return pe("box",{height:r.length,flexShrink:0,flexDirection:"row",overflow:"hidden",children:[pe(Pk,{palette:t,items:r,width:i},void 0,!1,void 0,this),pe(Pk,{palette:t,items:n,width:i},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function Pk(e){let t=Math.max(4,e.width-Po);return pe("box",{width:e.width,flexShrink:0,flexDirection:"column",overflow:"hidden",children:e.items.map((r)=>pe(If,{palette:e.palette,item:r,valueWidth:t},r.label,!1,void 0,this))},void 0,!1,void 0,this)}function If(e){let{palette:t,item:r,valueWidth:n}=e,o=r.tone==="warning"?t.warning:r.tone==="muted"?t.muted:t.text;return pe("box",{height:1,width:Po+n,flexDirection:"row",overflow:"hidden",children:[pe("box",{width:Po,flexShrink:0,overflow:"hidden",children:pe("text",{fg:t.muted,wrapMode:"none",children:oe(r.label,Po)},void 0,!1,void 0,this)},void 0,!1,void 0,this),pe("box",{width:n,flexShrink:0,overflow:"hidden",children:pe("text",{fg:o,wrapMode:"none",children:oe(r.value,n)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function rO(e){let{palette:t,entries:r,reveal:n,width:o,focused:i,scrollRef:s,source:a,sourceTone:c,error:l,onSelect:d}=e;return pe("box",{flexGrow:1,flexDirection:"column",overflow:"hidden",children:[pe(ut,{palette:t,items:[{text:`env ${r.length}`,tone:r.length>0?"info":"idle"},{text:a,tone:c},{text:n?"secrets shown":"redacted",tone:n?"warning":"muted"}],hints:i?[{key:"j/k",label:"scroll"}]:[{key:"/reveal",label:"secrets"}]},void 0,!1,void 0,this),l?pe("box",{height:1,overflow:"hidden",children:pe("text",{fg:t.warning,wrapMode:"none",children:_e(l,Math.max(8,o-4))},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,r.length===0?pe("text",{fg:t.muted,children:"no service env overrides"},void 0,!1,void 0,this):pe("box",{flexGrow:1,height:"100%",overflow:"hidden",children:[d?pe("text",{fg:t.muted,wrapMode:"word",children:"click a variable for its full value"},void 0,!1,void 0,this):null,pe("scrollbox",{ref:s,focused:i,stickyScroll:!1,scrollX:!1,style:{rootOptions:{flexGrow:1,height:"100%",overflow:"hidden",backgroundColor:t.panel},viewportOptions:{backgroundColor:t.panel},contentOptions:{backgroundColor:t.panel},scrollbarOptions:{trackOptions:{foregroundColor:t.primary,backgroundColor:t.element}}},children:pe("box",{flexDirection:"column",overflow:"hidden",children:r.map((u)=>{let f=u.required?t.warning:u.fromConfig?t.accent:t.muted,p=u.value===""&&u.required?"(required)":u.value;return pe("box",{flexDirection:"row",overflow:"hidden",onMouseDown:d?()=>d(u):void 0,children:pe("text",{wrapMode:"word",children:[pe("span",{fg:f,children:u.required?`${u.key}*`:u.key},void 0,!1,void 0,this),pe("span",{fg:u.value===""?t.muted:t.text,children:` ${p}`},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},u.key,!1,void 0,this)})},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}function Rk(e){switch(e.toUpperCase()){case"HEALTHY":case"RUNNING":return"success";case"UNHEALTHY":case"WARNING":case"WARN":case"STARTING":case"RESTARTING":return"warning";case"FAILED":case"ERROR":return"error";default:return"muted"}}var ZM=72,eO=56,Po=9;var Mf=D(()=>{ht();Yt();Mt();Ot();At();ke();tt()});import{jsxDEV as Hn}from"@opentui/react/jsx-dev-runtime";function Ek(e){let{palette:t,cfg:r,names:n,snap:o,selected:i,checked:s,width:a,reveal:c,onOpen:l,onSelectIndex:d,onToggle:u,resolvedEnv:f,envStatus:p,envError:m,onSelectEnv:h}=e;if(n.length===0)return Hn(ze,{palette:t,title:"No services",body:"This configuration does not define any services."},void 0,!1,void 0,this);let v=a<ci,x=n[i]??"",g=Nc(a,n,v),R=4,_=v?Math.max(Of,a-R):Math.max(Of,a-g-R);return Hn("box",{flexGrow:1,flexDirection:v?"column":"row",overflow:"hidden",children:[Hn("box",{flexGrow:v?1:0,flexShrink:0,minWidth:v?void 0:Mc,width:v?void 0:g,border:!0,borderStyle:"rounded",borderColor:t.borderActive,backgroundColor:t.panel,title:"services",titleColor:t.primary,flexDirection:"column",overflow:"hidden",children:[Hn(Il,{palette:t,checked:s},void 0,!1,void 0,this),Hn("box",{flexGrow:1,overflow:"hidden",children:Hn(Dl,{palette:t,names:n,snap:o,selected:i,checked:s,width:Fc(g),onOpen:l,onSelectIndex:d,onToggle:u},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this),Hn("box",{flexGrow:2,minWidth:v?void 0:Of,minHeight:v?nO:void 0,border:!0,borderStyle:"rounded",borderColor:t.border,backgroundColor:t.panel,title:x||"detail",titleColor:x?Ze(x,t):t.primary,overflow:"hidden",flexDirection:"column",children:Hn(Df,{palette:t,cfg:r,snap:o,name:x,reveal:c,width:_,resolvedEnv:f,envStatus:p,envError:m,onSelectEnv:h},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var Of=36,nO=12;var Tk=D(()=>{Yt();vr();At();tt();Mf();Rf()});import{jsxDEV as we}from"@opentui/react/jsx-dev-runtime";function iO(e){switch(e){case"cycle":return"\u2194";case"toggle":return"\u23FB";case"page":return"\u25B8";case"action":return"!";default:return"\xB7"}}function Ik(e){let{palette:t,items:r,selected:n,locked:o,width:i,onPick:s,onActivate:a}=e,c=ye(),l=hp(r,n),d=Ln(r,l);return we("box",{flexGrow:1,border:!0,borderStyle:"rounded",borderColor:t.borderActive,backgroundColor:t.panel,title:"settings",titleColor:t.primary,flexDirection:"column",overflow:"hidden",children:[we("box",{height:2,paddingLeft:c.pad,flexShrink:0,flexDirection:"column",overflow:"hidden",children:[we("text",{fg:t.primary,wrapMode:"none",children:"Preferences"},void 0,!1,void 0,this),we("text",{fg:t.muted,wrapMode:"none",children:o?"DEVCTL_TUI_CONFIG is set \u2014 changes stay in this session.":"Changes save to your user tui.json unless noted."},void 0,!1,void 0,this)]},void 0,!0,void 0,this),we("box",{flexGrow:1,paddingLeft:c.pad,paddingRight:c.pad,overflow:"hidden",flexShrink:1,children:we(sO,{palette:t,items:r,active:l,scale:c,width:i,onPick:s,onActivate:a},void 0,!1,void 0,this)},void 0,!1,void 0,this),we(cO,{palette:t,item:d,scale:c},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function sO(e){let{palette:t,items:r,active:n,scale:o,width:i,onPick:s,onActivate:a}=e,c=pi(n,Dk),l=Math.max(8,i-Ak-Lk-10),d=0,u=gx(r);return we("scrollbox",{ref:c,focused:!1,stickyScroll:!1,scrollX:!1,style:Ue(t),children:we("box",{flexDirection:"column",overflow:"hidden",children:u.map((f,p)=>{let m=d;d+=f.items.length;let h=n>=m&&n<m+f.items.length;return we("box",{flexDirection:"column",flexShrink:0,marginBottom:p<u.length-1?Math.max(1,o.gap):0,overflow:"hidden",children:[we("box",{height:1,overflow:"hidden",children:we("text",{fg:h?t.primary:t.muted,wrapMode:"none",children:f.group},void 0,!1,void 0,this)},void 0,!1,void 0,this),f.group==="MCP"?we("box",{height:1,overflow:"hidden",children:we("text",{fg:t.muted,wrapMode:"none",children:"dedicated page \u2014 also /mcp or /agent"},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,f.items.map((v,x)=>{let g=m+x;return we(aO,{index:g,palette:t,item:v,active:g===n,rowH:o.rowH,valueWidth:l,onPick:()=>s(g),onActivate:()=>a(v)},v.id,!1,void 0,this)})]},f.group,!0,void 0,this)})},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function aO(e){let{palette:t,index:r,item:n,active:o,rowH:i,valueWidth:s,onPick:a,onActivate:c}=e;return we("box",{id:`${Dk}-${r}`,height:i,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:o?t.highlight:void 0,onMouseDown:()=>{if(o){c();return}a()},children:[we("box",{width:2,flexShrink:0,overflow:"hidden",children:we("text",{fg:t.primary,children:o?"\u203A":" "},void 0,!1,void 0,this)},void 0,!1,void 0,this),we("box",{width:Lk,flexShrink:0,overflow:"hidden",children:we("text",{fg:o?t.primary:t.muted,children:iO(n.kind)},void 0,!1,void 0,this)},void 0,!1,void 0,this),we("box",{width:Ak,flexShrink:0,overflow:"hidden",children:we("text",{fg:o?t.primary:t.text,wrapMode:"none",children:n.name},void 0,!1,void 0,this)},void 0,!1,void 0,this),we("box",{flexGrow:1,overflow:"hidden",children:we("text",{fg:lO(t,n,o),wrapMode:"none",children:_e(n.value,s)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function cO(e){let{palette:t,item:r,scale:n}=e,o=r?.id==="font",i=r?.kind==="page"?`${r.name} \u2192 dedicated page`:r?.name??"";return we("box",{height:oO,flexShrink:0,border:!0,borderStyle:"rounded",borderColor:t.border,title:"selected",titleColor:t.muted,backgroundColor:t.element,paddingLeft:n.pad,paddingRight:n.pad,flexDirection:"column",overflow:"hidden",children:[we("box",{height:1,overflow:"hidden",children:we("text",{fg:t.primary,wrapMode:"none",children:o?`${mx(n.steps)} ${n.label}`:i},void 0,!1,void 0,this)},void 0,!1,void 0,this),we("box",{height:1,overflow:"hidden",children:we("text",{fg:t.text,wrapMode:"word",children:r?.detail??""},void 0,!1,void 0,this)},void 0,!1,void 0,this),we("box",{height:1,overflow:"hidden",children:we("text",{fg:t.muted,wrapMode:"none",children:r?r.kind==="info"?"read-only":r.hint:""},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function lO(e,t,r){if(t.id==="mouse")return t.value==="on"?e.success:e.text;if(t.kind==="page")return e.info;if(t.kind==="info")return e.text;return r?e.primary:e.text}var Ak=14,Lk=2,oO=5,Dk="settings-row";var Mk=D(()=>{Mt();Ot();ke();Er()});import{jsxDEV as Ae}from"@opentui/react/jsx-dev-runtime";function Ok(e){let{palette:t,cfg:r,google:n,bootError:o,bootErrorMissing:i=!1,step:s=0}=e,a=ye();if(o&&!r)return i?Ae(ze,{palette:t,title:"No configuration found",body:"Would you like to run setup?",hint:"[Enter] 9-step setup [Esc] Exit"},void 0,!1,void 0,this):Ae(ze,{palette:t,title:"Configuration error",body:o,hint:"Fix .devctl/config.yaml, then restart devctl [Esc] Exit"},void 0,!1,void 0,this);let c=e.issues,l=dO(r,n,c),d=l.filter((g)=>g.ok).length,u=l.length,f=u>0?Math.round(d/u*100):0,p=20,m=Math.round(f/100*p),h="\u2588".repeat(m)+"\u2591".repeat(Math.max(0,p-m)),v=f===100?t.success:f===0?t.muted:t.primary,x=l[s];return Ae(kt,{palette:t,title:"setup",scroll:!0,children:[Ae("box",{flexDirection:"row",overflow:"hidden",flexShrink:0,children:Ae("text",{fg:t.primary,children:"Developer onboarding"},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ae("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:Ae("text",{wrapMode:"none",children:[Ae("span",{fg:v,children:`[${h}]`},void 0,!1,void 0,this),Ae("span",{fg:t.text,children:` ${d}/${u} ready`},void 0,!1,void 0,this),Ae("span",{fg:t.muted,children:` (${f}%)`},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this),Ae("box",{height:1,flexShrink:0},void 0,!1,void 0,this),l.map((g,R)=>{let _=R===s;return Ae("box",{height:a.rowH,flexDirection:"row",overflow:"hidden",backgroundColor:_?t.highlight:void 0,paddingLeft:1,children:[Ae("box",{width:2,flexShrink:0,children:Ae("text",{fg:et(t,g.ok?"OK":"WARN"),children:hr(g.ok?"OK":"WARN")},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ae("box",{width:3,flexShrink:0,children:Ae("text",{fg:_?t.primary:t.muted,children:String(R+1).padStart(2," ")},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ae("box",{width:20,flexShrink:0,overflow:"hidden",children:Ae("text",{fg:_?t.primary:t.text,children:g.name},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ae("box",{flexGrow:1,overflow:"hidden",children:Ae("text",{fg:_?t.text:t.muted,wrapMode:"none",children:g.detail},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},g.name,!0,void 0,this)}),Ae("box",{height:1,flexShrink:0},void 0,!1,void 0,this),x?Ae("box",{border:!0,borderStyle:"rounded",borderColor:x.ok?t.success:t.warning,title:x.name,titleColor:x.ok?t.success:t.warning,flexDirection:"column",paddingLeft:1,paddingRight:1,flexShrink:0,overflow:"hidden",children:Ae("text",{fg:x.ok?t.text:t.warning,wrapMode:"word",children:x.detail},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,Ae("box",{height:1,flexShrink:0},void 0,!1,void 0,this),Ae("box",{flexDirection:"row",overflow:"hidden",flexShrink:0,children:Ae(Be,{palette:t,label:"never hard-coded",tone:"idle"},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ae("text",{fg:t.muted,wrapMode:"word",children:"Service accounts and IAP audiences stay in configuration. Run `devctl setup` for the interactive CLI wizard."},void 0,!1,void 0,this),Ae(it,{palette:t,hints:[{key:"j/k",label:"steps"},{key:"enter",label:r?"start":"begin setup"},{key:"esc",label:"exit"},{key:"o",label:"profiles"}]},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function dO(e,t,r=[]){let n=Object.values(e?.services??{}).flatMap((s)=>s.ports.filter((a)=>!a.auto)),o=(e?.proxy.routes??[]).some((s)=>s.auth.type.toLowerCase()==="iap"),i=El(e,void 0,t).project;return[{name:ln[0],ok:Boolean(e?.repoRoot),detail:e?.repoRoot||"unknown"},{name:ln[1],ok:Boolean(e?.project.name),detail:e?.project.name||"set project.name"},{name:ln[2],ok:Boolean(i),detail:i||"optional for local-only"},{name:ln[3],ok:t?.adcAvailable===!0,detail:t?.adcAvailable?"ADC available":"needed only for cloud identity / IAP"},{name:ln[4],ok:!0,detail:"read from configuration; never hard-coded"},{name:ln[5],ok:!o||(e?.proxy.routes??[]).every((s)=>s.auth.type.toLowerCase()!=="iap"||s.auth.audience!==""),detail:o?"IAP routes present":"no IAP routes"},{name:ln[6],ok:n.length===0||Boolean(e),detail:`${n.length} fixed ports from configuration`},{name:ln[7],ok:Object.keys(e?.profiles??{}).length>0,detail:`${Object.keys(e?.profiles??{}).length} profile(s)`},{name:ln[8],ok:r.length===0,detail:r.length===0?"configuration is valid":r[0]??"invalid"}]}var ln;var $k=D(()=>{Yt();Mt();ke();tt();ln=["Repository","Environment","Google project","Authentication","Service accounts","IAP","Ports","Profiles","Validation"]});import{useEffect as uO,useState as pO}from"react";import{jsxDEV as ee}from"@opentui/react/jsx-dev-runtime";function Si(e){let{palette:t,title:r,tone:n="muted",children:o}=e,i=n==="success"?t.success:n==="warning"?t.warning:n==="error"?t.error:t.border;return ee("box",{border:!0,borderStyle:"rounded",borderColor:i,title:r,titleColor:i,flexDirection:"column",flexShrink:0,paddingLeft:1,paddingRight:1,marginBottom:1,overflow:"hidden",children:o},void 0,!1,void 0,this)}function $f(e,t){if(t.color)return t.color;switch(t.tone){case"success":return e.success;case"warning":return e.warning;case"error":return e.error;case"primary":return e.primary;case"muted":return e.muted;default:return e.text}}function js(e){let{palette:t,cells:r,header:n,gap:o=0,stripe:i}=e;return ee("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:i?t.element:void 0,children:r.map((s,a)=>ee("box",{flexDirection:"row",flexShrink:0,overflow:"hidden",children:[a>0&&o>0?ee("box",{width:o,flexShrink:0},void 0,!1,void 0,this):null,ee("box",{width:s.width,flexShrink:0,overflow:"hidden",children:ee("text",{fg:n?t.primary:$f(t,s),wrapMode:"none",children:oe(s.text,s.width)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},`${s.text}-${a}`,!0,void 0,this))},void 0,!1,void 0,this)}function Fk(e){let{palette:t,width:r}=e;return ee("box",{height:1,flexShrink:0,overflow:"hidden",children:ee("text",{fg:t.border,wrapMode:"none",children:oe("\u2500".repeat(Math.max(1,r)),r)},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function Hs(e){let{palette:t,facts:r,width:n}=e,o=Up(r,n),i=[{text:"what",width:o.what},{text:"reading",width:o.reading}];if(o.meter>0)i.push({text:"used",width:o.meter});return i.push({text:"meaning",width:o.meaning}),ee("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:[ee(js,{palette:t,header:!0,gap:ui,cells:i},void 0,!1,void 0,this),ee(Fk,{palette:t,width:n},void 0,!1,void 0,this),r.map((s,a)=>{let c=s.meaning===""?[""]:Jc(s.meaning,Math.max(1,o.meaning)),l=s.meter?Mb(s.meter):o.meter>0?"":void 0,d=[{text:s.what,width:o.what,tone:"text"},{text:s.reading,width:o.reading,tone:s.tone??"text"}];if(o.meter>0)d.push({text:l??"",width:o.meter,tone:s.meter?s.tone??"text":"muted"});return d.push({text:c[0]??"",width:o.meaning,tone:"muted"}),ee("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:[ee(js,{palette:t,gap:ui,stripe:a%2===1,cells:d},void 0,!1,void 0,this),c.slice(1).map((u,f)=>{let p=[{text:"",width:o.what},{text:"",width:o.reading}];if(o.meter>0)p.push({text:"",width:o.meter});return p.push({text:u,width:o.meaning,tone:"muted"}),ee(js,{palette:t,gap:ui,stripe:a%2===1,cells:p},`${s.what}-more-${f}`,!1,void 0,this)})]},`${s.what}-${s.reading}-${a}`,!0,void 0,this)})]},void 0,!0,void 0,this)}function Nk(e){let{palette:t,cfg:r,snap:n,width:o,onRefresh:i}=e,s=ye(),[,a]=pO(0);if(uO(()=>{let B=setInterval(()=>{a((z)=>{let de=z+1;if(i&&de%mO===0)i();return de})},fO);return()=>clearInterval(B)},[i]),!r)return ee(ze,{palette:t,title:"No configuration loaded",body:"Add services to .devctl/config.yaml or finish setup."},void 0,!1,void 0,this);let c=Object.keys(r.services).sort(),l=c.some((B)=>Ob(r.services[B])),d=_b(c,n),u=Ub(d,l),f=l&&d.live>0?d.healthy/d.live:d.total>0?d.live/d.total:0,p=ii(f,hO),m=d.failed>0?"error":l&&d.live>0&&d.healthy<d.live?"warning":"success",h=Lb(o,s.pad),v=n?.session_id?Yl(n.session_id):void 0,x=v?ws(Date.now()-v.getTime()):"just started",g=n?.logs.total??0,R=n?.logs.errors??0,_=Ab(n?.logs.counts??{}),S=n?.proxy.routes??[],y=n?.credentials,b=(y?.entries??[]).filter((B)=>B.valid).length,w=n?.system,k=w?w.memAvailableKB??w.memFreeKB:0,L=w?Fb(w.loadAvg1,w.cpuCount):void 0,O=w?Nb(k,w.memTotalKB):void 0,F=[];if(w&&L&&O){F.push(L,O,{what:"Computer",reading:$b(w.platform),meaning:`on for ${ws(w.hostUptimeSec*gO)}`,tone:"text"});let B=n?.stats_series;if(B&&B.cpu.length>1)F.push({what:"Load trend",reading:Bp(B.cpu),meaning:`${Math.round(B.interval_ms/1000)}s samples`,tone:"text"});if(B&&B.mem.length>1)F.push({what:"RAM trend",reading:Bp(B.mem),meaning:"used memory over the same window",tone:"text"})}let Z=d.failed>0||L?.tone==="error"||O?.tone==="error"?"error":m==="warning"||L?.tone==="warning"||O?.tone==="warning"?"warning":"success",W=[{what:"This session",reading:x,meaning:"how long this run has been going",tone:"text"},{what:"Profile",reading:n?.profile||"none",meaning:"which group of services is selected",tone:n?.profile?"text":"muted"},{what:"This window",reading:n?.detached?"closed":"watching",meaning:n?.detached?"services keep running without this screen":"you are watching the services from here",tone:n?.detached?"warning":"text"},{what:"Project folder",reading:"",meaning:r.repoRoot,tone:"muted"}],C=[{what:"Lines kept",reading:String(g),meaning:"log lines still in memory",tone:"text"},{what:"Error lines",reading:String(R),meaning:R>0?"open Logs and press e to see only errors":"no ERROR or FATAL lines",tone:R>0?"error":"muted"},..._.map(([B,z])=>({what:B,reading:String(z),meaning:"lines from this service",tone:"text"}))],j=[{what:"Web proxy",reading:n?.proxy.running?"on":"off",meaning:n?.proxy.running?`${S.length} apps${n.proxy.address?` at ${n.proxy.address}`:""}`:"not forwarding local URLs",tone:n?.proxy.running?"success":"muted"},{what:"Agent tools",reading:n?.mcp?.running?"on":"off",meaning:n?.mcp?.address?`Cursor talks to devctl at ${n.mcp.address}`:"turn on from Settings \u2192 MCP",tone:n?.mcp?.running?"success":"muted"},{what:"Google account",reading:n?.identity.user?"signed in":"not signed in",meaning:[n?.identity.user,n?.identity.project].filter(Boolean).join(" \xB7 ")||"no Google user for this session",tone:n?.identity.user?"success":"muted"},{what:"Saved tokens",reading:y?`${b} of ${y.entries.length} good`:"none",meaning:y?Bb(y.backend):"nothing stored yet",tone:y&&b<y.entries.length?"warning":y?"success":"muted"}];return ee(kt,{palette:t,title:"stats",scroll:!0,children:[ee(Si,{palette:t,title:`Your services ${d.live} of ${d.total} started`,tone:Z,children:[ee(Hs,{palette:t,facts:u,width:h},void 0,!1,void 0,this),ee("box",{height:1,flexShrink:0},void 0,!1,void 0,this),ee("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:ee("text",{wrapMode:"none",children:[ee("span",{fg:t.muted,children:oe(l?"Ready":"Started",Up(u,h).what)},void 0,!1,void 0,this),ee("span",{children:" ".repeat(ui)},void 0,!1,void 0,this),ee("span",{fg:$f(t,{text:"",width:1,tone:m}),children:`[${p}]`},void 0,!1,void 0,this),ee("span",{fg:$f(t,{text:"",width:1,tone:m}),children:l?` ${d.healthy} of ${d.live||d.total} passing checks`:` ${d.live} of ${d.total} up`},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this),w?ee(Si,{palette:t,title:"This computer",tone:L?.tone==="error"||O?.tone==="error"?"error":L?.tone==="warning"||O?.tone==="warning"?"warning":"success",children:ee(Hs,{palette:t,facts:F,width:h},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,ee(Si,{palette:t,title:"This session",children:ee(Hs,{palette:t,facts:W,width:h},void 0,!1,void 0,this)},void 0,!1,void 0,this),ee(Si,{palette:t,title:"Each service",tone:d.failed>0?"error":l&&d.live>0&&d.healthy<d.live?"warning":"success",children:d.total===0?ee("text",{fg:t.muted,children:"No services in this project yet."},void 0,!1,void 0,this):ee("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:[ee(vO,{palette:t,names:c,snap:n,width:h},void 0,!1,void 0,this),ee("box",{height:1,flexShrink:0},void 0,!1,void 0,this),ee("text",{fg:t.muted,wrapMode:"none",children:oe("CPU is % of one core. RAM is what that process is holding now.",h)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this),ee(Si,{palette:t,title:"Log lines",tone:R>0?"warning":"muted",children:ee(Hs,{palette:t,facts:C,width:h},void 0,!1,void 0,this)},void 0,!1,void 0,this),ee(Si,{palette:t,title:"Other parts",children:ee(Hs,{palette:t,facts:j,width:h},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function vO(e){let{palette:t,names:r,snap:n,width:o}=e,i=Db(o),s=[{text:"service",width:i.name},{text:"status",width:Tr}];if(i.health)s.push({text:"check",width:Ur});if(i.cpu)s.push({text:"cpu",width:Cs});if(i.mem)s.push({text:"ram",width:Ps});if(i.up)s.push({text:"up",width:ks});if(i.rst)s.push({text:"restarts",width:cl});if(i.pid)s.push({text:"pid",width:sn});return ee("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:[ee(js,{palette:t,header:!0,gap:$t,cells:s},void 0,!1,void 0,this),ee(Fk,{palette:t,width:o},void 0,!1,void 0,this),r.map((a,c)=>ee(wO,{palette:t,name:a,rt:n?.services[a],cols:i,stripe:c%2===1},a,!1,void 0,this))]},void 0,!0,void 0,this)}function bO(e){if(e===void 0)return"muted";if(e>xO)return"error";if(e>yO)return"warning";return"text"}function wO(e){let{palette:t,name:r,rt:n,cols:o,stripe:i}=e,s=Hb(n),a=jb(n),c=n?.restarts??0,l=[{text:r,width:o.name,color:Ze(r,t)},{text:s,width:Tr,color:et(t,n?.state??"STOPPED")}];if(o.health)l.push({text:a,width:Ur,color:et(t,n?.health??"UNKNOWN")});if(o.cpu)l.push({text:n?.cpuPercent!==void 0?xx(n.cpuPercent):"\u2014",width:Cs,tone:bO(n?.cpuPercent)});if(o.mem)l.push({text:n?.memoryKB!==void 0?Ss(n.memoryKB):"\u2014",width:Ps,tone:"text"});if(o.up)l.push({text:Tb(n),width:ks,tone:"text"});if(o.rst)l.push({text:String(c),width:cl,tone:c>0?"warning":"muted"});if(o.pid)l.push({text:n?.pid?String(n.pid):"\u2014",width:sn,tone:"muted"});return ee("box",{flexDirection:"column",flexShrink:0,overflow:"hidden",children:[ee(js,{palette:t,gap:$t,stripe:i,cells:l},void 0,!1,void 0,this),n?.last_error?ee("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:ee("text",{fg:t.error,wrapMode:"none",children:oe(` ${n.last_error}`,Math.max(24,o.name+$t+Tr+Ur))},void 0,!1,void 0,this)},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}var fO=1000,mO=5,hO=16,gO=1000,yO=80,xO=100;var Hk=D(()=>{Yt();Mt();Ot();gr();At();ll();ke();tt()});import{useRenderer as SO,useTerminalDimensions as kO}from"@opentui/react";import{useCallback as jn,useEffect as CO,useMemo as jk,useRef as Vr,useState as Je}from"react";import{jsxDEV as he,Fragment as PO}from"@opentui/react/jsx-dev-runtime";function Bk({controller:e,tui:t,onQuit:r,onDown:n,onAttached:o,bootError:i,bootErrorMissing:s=!1,terminalBackground:a,workspace:c}){let{saveTuiPreferences:l,resolveTuiOverridePath:d,userTuiConfigPath:u,validateConfigText:f,freePort:p,readTextFile:m,writeTextFile:h,createStarterConfig:v,validate:x}=c,[g,R]=Je(e),[_,S]=Je(i),y=SO(),{width:b,height:w}=kO(),[k,L]=Je(g?"dashboard":"setup"),[O,F]=Je("none"),[Z,W]=Je(""),[C,j]=Je("commands"),[B,z]=Je(0),[de,Ee]=Je(0),[Ne,ne]=Je(0),[H,ae]=Je([]),[X,nt]=Je(),[ft,ie]=Je(_??""),[Le,Me]=Je({}),[Ke,gt]=Je(),[Xe,Ct]=Je(kx(g?.cfg)),[ge,vt]=Je(""),[at,yt]=Je(!1),[rr,Qe]=Je(),[Xt,qe]=Je(),[mt,xr]=Je(),Kt=Vr(null),br=Vr(null),nr=Vr(null),qt=Vr(null),or=Vr(null),ir=Vr(null),sr=Vr(null),Vt=Vr(""),[ce,K]=Je("quit"),T=Vr(void 0),ve=Vr(0),[Y,Lt]=Je(g?.cfg),ct=g?.previousPersisted,[Ce,Ro]=Je(void 0),P=hS({tui:t,controller:g,resolveTuiOverridePath:d,terminalBackground:a,userTuiConfigPath:u,snap:X,setStatus:ie,saveTuiPreferences:l,setPaletteIndex:z,setOverlay:F,setConfirmKind:K,setScreen:L,setSelected:ne,screen:k}),{themeName:ue,setThemeName:Yr,fontSize:Jr,prefsLocked:wr,palette:se,rootBackground:Bn,settingRows:Un,persistPrefs:Dr,persistTheme:dn,activateSetting:Bs}=P,ki=nS({controller:g,workspace:c,cfg:Y,snap:X,screen:k,configReloadError:Ce,setSnap:nt,setStatus:ie}),{google:Ci,doctor:le,doctorLoading:xt,doctorError:Pt,doctorProgress:Xr,setDoctorTick:Us}=ki,_o=t.keybinds.copy??Zr(),Sr=jk(()=>Object.keys(Y?.services??{}).sort(),[Y]),un=jn(async()=>{if(!g)return;try{let J=await g.status();if(nt(J),J.profile&&Xe==="")Ct(J.profile);return J}catch(J){ie(A(J));return}},[g,Xe]),Nl=lS({controller:g,tui:t,names:Sr,screen:k,refresh:un,setStatus:ie}),{logs:Uf,setLogs:pC,paused:Wf,errorOnly:Hl,setErrorOnly:jl,showSystemLogs:Gf,logSearch:Kf,setLogSearch:qf,logSearchFocused:fC,setLogSearchFocused:mC,logFollow:Vf,logSince:hC,logService:zf,setLogService:Bl,logServiceB:gC,setLogServiceB:vC,splitLogs:Ul,splitFocus:Wl,setSplitFocus:yC,logSliceB:Yf,logWindowB:Jf,logSelectedB:Xf,logPinnedB:xC,pinLogViewB:bC,logServices:wC,logShowTimestamps:SC,logShowMeta:kC,logSource:CC,logRegex:Qf,logWrap:Zf,logPinned:em,logSelected:tm,logsFullscreen:Gl,dashboardLogCursor:PC,setDashboardLogCursor:RC,logFacets:rm,logSources:nm,filteredLogs:om,filteredLogsB:_C,logWindow:Ws,logSlice:Gs,pinLogView:Kl,applyLogCursorA:EC,applyLogCursorB:TC,jumpToLatestLogs:im}=Nl,Eo=jk(()=>{if(C==="tasks")return $p(Object.keys(Y?.tasks??{}),Z,"tasks","run this task");if(C==="services")return $p(Object.keys(Y?.services??{}),Z,"services","exec in this service");return kb(Z,{services:Object.keys(Y?.services??{}),tasks:Object.keys(Y?.tasks??{})})},[Y,Z,C]);CO(()=>{Ee(0)},[Z]);let ql=zx(k,{doctor:le?.checks.length??0,settings:Un.length,profiles:Object.keys(Y?.profiles??{}).length,services:Sr.length,logs:Ul&&Wl===1?Yf.length:Gs.length,mcp:cw(),config:Object.keys(Y?.tasks??{}).length}),sm=k==="logs"?Ul&&Wl===1?Xf:tm:Ne,Ir=ql<=0?Math.max(0,sm):Math.max(0,Math.min(sm,ql-1)),AC=k==="detail"?ge:k==="services"?Sr[Ir]??"":"",{inspectorEnv:am,inspectorEnvStatus:cm,inspectorEnvError:lm,resolveEnvironment:LC}=SS({controller:g,cfg:Y,envService:AC}),To=jn(()=>{if(F("none"),W(""),j("commands"),mC(!1),T.current)clearTimeout(T.current)},[]),Vl=jn((J)=>{ae((bt)=>bt.includes(J)?bt.filter((qC)=>qC!==J):[...bt,J])},[]),dm=pS({tui:t,controller:g,cfg:Y,persistPrefs:Dr,snap:X,refresh:un,setStatus:ie}),{mcpPortDraft:DC,mcpPort:IC,toggleMcpTool:MC,toggleMcp:OC,copyMcpSnippet:$C}=dm;Zw({controller:g,cfg:Y,paused:Wf,logSince:hC,refresh:un,setLogs:pC,setCfg:Lt,setConfigReloadError:Ro,setStatus:ie});let Ks=jn((J)=>{vt(J),L("detail")},[]),um=jn((J)=>{let bt=J.value!==""?J.value:J.required?"(required, not set)":"(empty)";xr({title:J.key,body:bt}),F("scroll-text")},[]),zl=iS({controller:g,workspace:c,cfg:Y,snap:X,refresh:un,setStatus:ie,setOverlay:F}),{plan:qs,planInitiallyRunning:FC,planBusy:NC,lifecycle:pm}=zl,Qr=xS({workspace:c,renderer:y,setOverlay:F,setStatus:ie,setScreen:L,onAttached:o,setController:R,setCfg:Lt,setBootError:S}),{configEditRef:HC,configEditText:jC,configEditError:BC,setConfigEditError:UC,openConfigBuffer:fm,saveConfigBuffer:WC}=Xw({cfg:Y,readTextFile:m,setOverlay:F,setStatus:ie,validateConfigText:f,writeTextFile:h,controller:g,setConfirmKind:K,refresh:un}),mm=jn(async()=>{let J=y.getSelection()?.getSelectedText()??"";if(J.trim()===""){ie("Nothing selected \u2014 drag to highlight text");return}try{await fl(J);let bt=J.split(`
2161
+ `).length;ie(bt===1?"Copied selection":`Copied ${bt} selected lines`)}catch(bt){ie(A(bt))}},[y]),{runCommand:Vs}=Vw({setOverlay:F,setQuery:W,setSlashPicker:j,checked:H,setConfirmKind:K,setStatus:ie,persistTheme:dn,setPaletteIndex:z,themeName:ue,setScreen:L,setSelected:ne,screen:k,renderer:y,cfg:Y,setScrollText:xr,reveal:at,controller:g,refresh:un,profile:Xe,setReveal:yt,resolveEnvironment:LC,openDetail:Ks,copySelection:mm,lastExportPath:Vt,openConfigBuffer:fm,onDown:n??((J)=>r(J)),workspace:c,logView:Nl,diagnostics:ki,lifecycleActions:zl}),hm=jn(()=>{let J=di(Eo,de);if(C==="tasks"){if(!J){ie(Eo.length===0?"no tasks configured":"pick a task");return}j("commands");let bt=Wr("run");if(bt)Vs(bt,[J.name]);return}if(C==="services"){if(!J){ie(Eo.length===0?"no services configured":"pick a service");return}j("commands"),W(`exec ${J.name} -- `);return}if(!J){ie(Z.trim()===""?"pick a command from the list":`unknown command /${Z}`),To();return}Vs(J,Sb(J,Z))},[To,Eo,Z,Vs,de,C]),gm=jn((J)=>{dn(J),To()},[To,dn]);Ew({tui:t,controller:g,cfg:Y,snap:X,ui:{screen:k,overlay:O,onQuit:r,onDown:n??((J)=>r(J)),closeOverlay:To,confirmKind:ce,confirmDetail:Le,portTarget:Ke,profile:Xe,listCursor:Ir,names:Sr,listCount:ql,openDetail:Ks,copySelection:mm,height:w,paletteIndex:B,applyTheme:gm,runCommand:Vs,saveConfigBuffer:WC,filtered:Eo,slashIndex:de,submitSlash:hm,advanceWizard:()=>void Qr.advanceWizard(),bootErrorMissing:s,bootError:_,createStarterConfig:v,startWizard:Qr.startWizard,toggleChecked:Vl,checked:H,detailName:ge,refresh:un,openConfigBuffer:fm,setOverlay:F,setConfirmKind:K,setConfirmDetail:Me,setPortTarget:gt,setLogDetail:Qe,logDetail:rr,setLogSearch:qf,setProfile:Ct,setStatus:ie,setPaletteIndex:z,setSlashIndex:Ee,setQuery:W,setSlashPicker:j,setScreen:L,setSelected:ne,setChecked:ae,setConfigEditError:UC,freePort:p},logView:Nl,lifecycleActions:zl,mcp:dm,preferences:P,diagnostics:ki,refs:{interruptArmedAt:ve,leaderTimer:T,logDetailsScrollRef:qt,scrollTextScrollRef:ir,routeDetailsScrollRef:or,planScrollRef:sr,helpScrollRef:br,configScrollRef:Kt,detailScrollRef:nr}});let vm=rb(ce,Xe,Le),GC=new Set(qs?.waves.flat()??[]),KC=qs===void 0||pm==="stop"?"":Object.entries(X?.services??{}).find(([J,bt])=>GC.has(J)&&bt.state==="FAILED")?.[0]??"";return he(vp.Provider,{value:xs(Jr),children:he("box",{flexDirection:"column",width:b,height:w,backgroundColor:Bn,overflow:"hidden",children:[Gl?null:he(PO,{children:[he(Kb,{palette:se,cfg:Y,snap:X,google:Ci,profile:Xe,reveal:at,width:b},void 0,!1,void 0,this),he(qb,{palette:se,screen:k,width:b,onSelect:L},void 0,!1,void 0,this),Ce?he("box",{height:1,paddingLeft:1,backgroundColor:se.panel,overflow:"hidden",children:he("text",{fg:se.error,wrapMode:"none",children:`\u26A0 configuration reload failed: ${Ce}`},void 0,!1,void 0,this)},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),he("box",{flexGrow:1,overflow:"hidden",children:[k==="dashboard"?he(ck,{palette:se,cfg:Y,snap:X,logs:Uf,names:Sr,selected:Ir,checked:H,width:b,followTick:Vf,logService:zf,errorOnly:Hl,showSystemLogs:Gf,onOpen:Ks,onSelectIndex:ne,selectedLog:PC,onToggle:Vl,logSources:nm,onFilterService:Bl,onToggleErrors:()=>jl((J)=>!J),onShowErrors:()=>{Bl(""),jl(!0)},wrapMode:Zf,view:Gs,follow:!em,onLeaveLatest:Kl,onPickLog:(J)=>{let bt=Gs[J];if(!bt)return;RC(J),Qe(bt),Kl()},viewStart:Ws.start,viewTotal:om.length,newer:Ws.newer,onJumpLatest:im,facets:rm,leftover:ct,search:Kf,regex:Qf},void 0,!1,void 0,this):null,k==="services"?he(Ek,{palette:se,cfg:Y,names:Sr,snap:X,selected:Ir,checked:H,width:b,reveal:at,onOpen:Ks,onSelectIndex:ne,onToggle:Vl,resolvedEnv:am,envStatus:cm,envError:lm,onSelectEnv:um},void 0,!1,void 0,this):null,k==="detail"?he(_k,{palette:se,cfg:Y,snap:X,name:ge,reveal:at,width:b,envScrollRef:nr,resolvedEnv:am,envStatus:cm,envError:lm,onSelectEnv:um},void 0,!1,void 0,this):null,k==="logs"?he(fk,{palette:se,logs:Uf,names:Sr,logSources:nm,service:zf,errorOnly:Hl,showSystemLogs:Gf,search:Kf,searchFocused:fC,followTick:Vf,width:b,onSearch:qf,onService:Bl,onToggleErrors:()=>jl((J)=>!J),source:CC,regex:Qf,services:wC,showTimestamps:SC,showMeta:kC,view:Gs,wrapMode:Zf,selected:tm,follow:!em,newer:Ws.newer,viewStart:Ws.start,viewTotal:om.length,onSelect:EC,fullscreen:Gl,onLeaveLatest:Kl,onJumpLatest:im,facets:rm,split:Ul,splitFocus:Wl,serviceB:gC,viewB:Yf,selectedB:Xf,followB:!xC,newerB:Jf.newer,viewStartB:Jf.start,viewTotalB:_C.length,onServiceB:vC,onSelectB:TC,onLeaveLatestB:bC,onFocusPane:yC},void 0,!1,void 0,this):null,k==="auth"?he(VS,{palette:se,cfg:Y,google:Ci,identity:X?.identity},void 0,!1,void 0,this):null,k==="credentials"?he(ek,{palette:se,credentials:X?.credentials},void 0,!1,void 0,this):null,k==="proxy"?he(kk,{palette:se,cfg:Y,snap:X,width:b,onSelectRoute:(J)=>{qe(J),F("route-details")}},void 0,!1,void 0,this):null,k==="mcp"?he(lw,{palette:se,snap:X,port:IC,portDraft:DC,selected:Ir,onPick:ne,onToggle:()=>{OC()},onCopy:$C,onToggleTool:(J)=>{MC(J)}},void 0,!1,void 0,this):null,k==="doctor"?he(dk,{palette:se,report:le,loading:xt,progress:Xr,error:Pt,selected:Ir,onPick:ne,onReload:()=>{Us((J)=>J+1),ie("Re-running doctor")}},void 0,!1,void 0,this):null,k==="stats"?he(Nk,{palette:se,cfg:Y,snap:X,width:b,onRefresh:un},void 0,!1,void 0,this):null,k==="config"?he(QS,{palette:se,cfg:Y,width:b,selectedTask:Ir,scrollRef:Kt},void 0,!1,void 0,this):null,k==="profiles"?he(gk,{palette:se,cfg:Y,snap:X,profile:Xe,selected:Ir,onPick:ne},void 0,!1,void 0,this):null,k==="setup"?he(Ok,{issues:Y?x(Y):["configuration not loaded"],palette:se,cfg:Y,google:Ci,bootError:_,bootErrorMissing:s,step:Ir},void 0,!1,void 0,this):null,k==="settings"?he(Ik,{palette:se,items:Un,selected:Ir,locked:wr,width:b,onPick:ne,onActivate:Bs},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),O==="slash"?he(jS,{palette:se,items:Eo,query:Z,selected:de,title:C==="tasks"?"tasks":C==="services"?"services":"commands",onQuery:W,onSubmit:hm},void 0,!1,void 0,this):null,O==="themes"?he(KS,{palette:se,themeName:ue,selected:B%St.length,termW:b,termH:w,onIndex:z,onPreview:Yr,onPick:gm},void 0,!1,void 0,this):null,O==="help"?he(fw,{palette:se,termW:b,termH:w,copyKey:_o,scrollRef:br},void 0,!1,void 0,this):null,O==="leader"?he(TS,{palette:se,termW:b,termH:w},void 0,!1,void 0,this):null,O==="confirm"?he(RS,{palette:se,title:vm.title,body:vm.body,kind:ce,termW:b,termH:w},void 0,!1,void 0,this):null,O==="setup-wizard"?he(vS,{palette:se,termW:b,termH:w,step:Qr.step,answers:Qr.answers,repo:Qr.repo,draft:Qr.draft,authStatus:Qr.authStatus,onDraft:Qr.setDraft,onSubmit:()=>void Qr.advanceWizard()},void 0,!1,void 0,this):null,O==="log-details"?he(LS,{palette:se,event:rr,termW:b,termH:w,scrollRef:qt},void 0,!1,void 0,this):null,O==="route-details"?he($S,{palette:se,route:Xt,termW:b,termH:w,scrollRef:or},void 0,!1,void 0,this):null,O==="scroll-text"&&mt?he(NS,{palette:se,title:mt.title,body:mt.body,termW:b,termH:w,scrollRef:ir},void 0,!1,void 0,this):null,O==="config-edit"&&Y?he(CS,{palette:se,path:Y.configPath,initialValue:jC,error:BC,termW:b,termH:w,textareaRef:HC},void 0,!1,void 0,this):null,O==="plan"&&qs?he(MS,{palette:se,plan:qs,snap:X,busy:NC,failed:KC,kind:pm,termW:b,termH:w,scrollRef:sr,initiallyRunning:FC,onDismiss:To},void 0,!1,void 0,this):null,Gl?null:he(Vb,{palette:se,screen:k,overlay:O,status:ft,paused:Wf,errorOnly:Hl,width:b,copyKey:_o},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}var Uk=D(()=>{te();Yt();Gp();Mt();vr();So();In();At();Tw();zw();Qw();eS();oS();sS();dS();fS();gS();bS();kS();PS();_S();uf();AS();DS();OS();FS();HS();vf();BS();qS();zS();ZS();tk();lk();uk();mk();Ms();vk();Ck();Mf();Tk();Mk();$k();Hk();Er();tt();_r()});var Wk={};JC(Wk,{renderApp:()=>Nf,runTui:()=>Ff,runTuiWithController:()=>EO});import{createCliRenderer as RO}from"@opentui/core";import{createRoot as _O}from"@opentui/react";import{jsxDEV as TO}from"@opentui/react/jsx-dev-runtime";async function EO(e,t){let r=e.loadTuiConfig(t.cfg.repoRoot,t.cfg.ui.keymap);await Nf(e,t,r)}async function Ff(e,t){let r,n,o=!1;try{r=await e.openTui("",t)}catch(s){n=A(s),o=va(s,Vn)}let i=e.loadTuiConfig(r?.cfg.repoRoot??process.cwd(),r?.cfg.ui.keymap);await Nf(e,r,i,n,o)}async function Nf(e,t,r=e.loadTuiConfig(t?.cfg.repoRoot??process.cwd(),t?.cfg.ui.keymap),n,o=!1){zs();let i=bm(),s=await RO({exitOnCtrlC:!1,useMouse:r.mouse,enableMouseMovement:r.mouse,useKittyKeyboard:{}}),a=_O(s),c=null;try{c=(await s.getPalette({timeout:120,size:16})).defaultBackground}catch{}await new Promise((l)=>{let d=(f)=>{let p=()=>{a.unmount(),i(),s.destroy(),l(),process.exit(0)};if(!t){p();return}t.close({detach:f,shutdownSupervisor:!0}).finally(p)},u=(f)=>{let p=()=>{a.unmount(),i(),s.destroy(),l(),process.exit(0)};if(!t){p();return}t.shutdown({stopServices:!f}).then(()=>t?.close({detach:!0})).finally(p)};a.render(TO(Bk,{workspace:ox(e),controller:t,tui:r,onQuit:d,onDown:u,onAttached:(f)=>{t=f},bootError:n,bootErrorMissing:o,terminalBackground:c},void 0,!1,void 0,this))})}var Hf=D(()=>{ix();te();Uk()});if(process.env.METADATA_SERVER_DETECTION===void 0)process.env.METADATA_SERVER_DETECTION="bios-only";if(process.env.GCE_METADATA_TIMEOUT===void 0)process.env.GCE_METADATA_TIMEOUT="0";import{existsSync as na,readFileSync as ea}from"fs";import{dirname as RP,isAbsolute as Tm,join as ra,resolve as _P}from"path";Ys();Ys();import{spawnSync as oP}from"child_process";import{randomBytes as Ql}from"crypto";import{copyFileSync as iP,existsSync as pn,mkdirSync as sP,readdirSync as aP,readFileSync as Zl,renameSync as ed,statSync as cP,unlinkSync as Jl,writeFileSync as lP}from"fs";import{homedir as dP}from"os";import{basename as uP,dirname as Xl,join as Dt}from"path";var pP=448,fP=384;function cr(){let e=process.env.DEVCTL_HOME;if(e&&e!=="")return e;return Dt(dP(),".devctl")}function Mr(e){sP(e,{recursive:!0,mode:pP})}function Ao(e){let t=Dt(cr(),"state",ar(e)),r=Dt(cr(),"sessions",ar(e));if(!pn(t)&&pn(r)){Mr(Xl(t));try{ed(r,t)}catch{Mr(t);for(let n of["state.json","devctl.lock","devctl.sock"]){let o=Dt(r,n);if(pn(o))iP(o,Dt(t,n))}}}return Mr(t),t}function Js(){let e=Dt(cr(),"logs");return Mr(e),e}function Xs(){let e=Dt(cr(),"exports");return Mr(e),e}function kr(){let e=Dt(cr(),"credentials");return Mr(e),e}function Qs(e,t=process.platform){if(t==="win32")return`\\\\.\\pipe\\devctl-${ar(e)}`;return Dt(Ao(e),"devctl.sock")}function mP(e){return Dt(Ao(e),"devctl.lock")}function wm(e){return Dt(Ao(e),"state.json")}function Pi(e){return Dt(Ao(e),"bootstrap.log")}var hP=5,Sm="bootstrap-";function km(e){let t=Pi(e);if(!pn(t))return;let r=Ao(e),n=Dt(r,`${Sm}${td()}.log`);try{ed(t,n)}catch{return}gP(r)}function gP(e){let t;try{t=aP(e).filter((n)=>n.startsWith(Sm)&&n.endsWith(".log"))}catch{return}let r=t.map((n)=>{let o=Dt(e,n);try{return{path:o,mtime:cP(o).mtimeMs}}catch{return}}).filter((n)=>n!==void 0).sort((n,o)=>o.mtime-n.mtime);for(let n=hP;n<r.length;n++)try{Jl(r[n].path)}catch{}}function Cr(e,t){Mr(Xl(e));let r=Dt(Xl(e),`.${uP(e)}.tmp-${process.pid}-${Ql(4).toString("hex")}`);lP(r,t,{mode:fP}),ed(r,e)}function td(e=new Date){return`${e.toISOString().slice(0,19).replace(/:/g,"-")+"Z"}-${Ql(3).toString("hex")}`}function fn(e){return rd(wm(e))}function rd(e){if(!pn(e))return;try{let t=JSON.parse(Zl(e,"utf8"));if(Array.isArray(t.processes)&&typeof t.repo_root==="string")return t;return}catch{return}}function Cm(e,t){Cr(wm(e),`${JSON.stringify(t,null,2)}
2162
+ `)}function Pm(e,t){let r=mP(e);if(pn(r)){let n=vP(r);if(n&&Qt(n.pid))throw Error(`supervisor already running (pid ${n.pid})`);try{Jl(r)}catch{}}return Cr(r,JSON.stringify({pid:process.pid,socket:t})),{release:()=>{try{Jl(r)}catch{}}}}function vP(e){if(!pn(e))return;try{let t=JSON.parse(Zl(e,"utf8"));if(typeof t.pid==="number"&&typeof t.socket==="string")return{pid:t.pid,socket:t.socket}}catch{return}return}function Qt(e){if(e<=0)return!1;if(process.platform==="win32")return yP(e);try{return process.kill(e,0),!0}catch{return!1}}function nd(e){try{let t=oP("cmd.exe",["/d","/c",`tasklist /FO CSV /NH /FI "PID eq ${e}"`],{encoding:"buffer",windowsHide:!0,timeout:5000});return xP(t.stdout)}catch{return""}}function yP(e){try{return process.kill(e,0),!0}catch{}let t=nd(e).toLowerCase();if(t===""||t.includes("no tasks")||t.includes("no matching"))return!1;return t.includes(String(e))}function xP(e){if(!e)return"";if(typeof e==="string")return e;if(e.length>=2&&e[0]===255&&e[1]===254)return e.toString("utf16le");if(e.length>=4&&e[1]===0&&e[3]===0)return e.toString("utf16le");return e.toString("utf8")}function od(){return Ql(24).toString("hex")}function bP(e){return Dt(Ao(e),"mcp-token")}function Rm(e){let t=bP(e);if(pn(t)){let n=Zl(t,"utf8").trim();if(n!=="")return n}let r=od();return Cr(t,r),r}Zs();Zs();var EP="OPENCODE_TUI_CONFIG";function oa(){return ra(cr(),"tui.json")}function ia(e=""){for(let t of[_m,EP]){let r=process.env[t];if(!r||r==="")continue;let n=Tm(r)?r:_P(e||process.cwd(),r);if(na(n))return n}return}function TP(e,t){if(!t||Object.keys(t).length===0)return e;return{...e,keybinds:{...e.keybinds,...t}}}function Io(e,t){let r=TP(Em(),t),n=ia(e);if(n)return ad(r,ta(ea(n,"utf8")),n);let o=AP(e),i=o?ad(r,ta(ea(o,"utf8")),o):r,s=oa();if(!na(s)||o===s)return i;return ad(i,ta(ea(s,"utf8")),s)}function Am(e){let t=oa(),r={};if(na(t)){let o=ta(ea(t,"utf8"));if(Wn(o))r=o}let n={...r};if(e.theme!==void 0)n.theme=e.theme;if(e.mouse!==void 0)n.mouse=e.mouse;if(e.leader_timeout!==void 0)n.leader_timeout=e.leader_timeout;if(e.font_size!==void 0)n.font_size=e.font_size;if(e.log_timestamps!==void 0)n.log_timestamps=e.log_timestamps;if(e.log_metadata!==void 0)n.log_metadata=e.log_metadata;if(e.mcp_enabled!==void 0)n.mcp_enabled=e.mcp_enabled;if(e.mcp_port===null)delete n.mcp_port;else if(e.mcp_port!==void 0)n.mcp_port=e.mcp_port;if(e.mcp_disabled_tools!==void 0)n.mcp_disabled_tools=e.mcp_disabled_tools;return Cr(t,`${JSON.stringify(n,null,2)}
2163
+ `),t}function AP(e){let t=ia(e);if(t)return t;let r=[e||process.cwd(),ra(e||process.cwd(),".devctl"),cr()],n=["tui.json","tui.jsonc"];for(let o of r)for(let i of n){let s=ra(o,i);if(na(s))return s}return}function ad(e,t,r){let n=Wn(t)?t:{},o={...e.keybinds};if(Wn(n.keybinds)){for(let[c,l]of Object.entries(n.keybinds))if(typeof l==="string")o[c]=l}let i={...e.cursor};if(Wn(n.cursor)){if(DP(n.cursor.style))i.style=n.cursor.style;if(typeof n.cursor.blinking==="boolean")i.blinking=n.cursor.blinking}let s={...e.scroll_acceleration};if(Wn(n.scroll_acceleration)&&typeof n.scroll_acceleration.enabled==="boolean")s.enabled=n.scroll_acceleration.enabled;let a={...e.attention,sounds:{...e.attention.sounds}};if(Wn(n.attention)){if(typeof n.attention.enabled==="boolean")a.enabled=n.attention.enabled;if(typeof n.attention.notifications==="boolean")a.notifications=n.attention.notifications;if(typeof n.attention.sound==="boolean")a.sound=n.attention.sound;if(typeof n.attention.volume==="number")a.volume=n.attention.volume;if(typeof n.attention.sound_pack==="string")a.sound_pack=n.attention.sound_pack;if(Wn(n.attention.sounds)){for(let[c,l]of Object.entries(n.attention.sounds))if(typeof l==="string")a.sounds[c]=LP(l,r)}}return{theme:typeof n.theme==="string"?n.theme:e.theme,leader_timeout:typeof n.leader_timeout==="number"?n.leader_timeout:e.leader_timeout,font_size:typeof n.font_size==="number"&&Number.isFinite(n.font_size)?Math.round(n.font_size):e.font_size,keybinds:o,scroll_speed:typeof n.scroll_speed==="number"?n.scroll_speed:e.scroll_speed,scroll_acceleration:s,diff_style:n.diff_style==="stacked"?"stacked":"auto",cursor:i,mouse:typeof n.mouse==="boolean"?n.mouse:e.mouse,attention:a,log_timestamps:typeof n.log_timestamps==="boolean"?n.log_timestamps:e.log_timestamps,log_metadata:typeof n.log_metadata==="boolean"?n.log_metadata:e.log_metadata,mcp_enabled:typeof n.mcp_enabled==="boolean"?n.mcp_enabled:e.mcp_enabled,mcp_port:typeof n.mcp_port==="number"&&Number.isInteger(n.mcp_port)?n.mcp_port:e.mcp_port,mcp_disabled_tools:Array.isArray(n.mcp_disabled_tools)?n.mcp_disabled_tools.filter((c)=>typeof c==="string"):e.mcp_disabled_tools,path:r}}function LP(e,t){if(e.startsWith("file://")||Tm(e))return e;return ra(RP(t),e)}function DP(e){return e==="block"||e==="underline"||e==="line"||e==="default"}function Wn(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function ta(e){let t=e.replace(/\/\*[\s\S]*?\*\//g,"").replace(/^\s*\/\/.*$/gm,"");return JSON.parse(t)}import{existsSync as IT,readFileSync as MT,writeFileSync as OT}from"fs";import{spawn as wd}from"child_process";import{createWriteStream as aR,existsSync as Ii,mkdirSync as cR,readdirSync as Cd,readFileSync as lR,rmSync as dR,statSync as kd,writeFileSync as uR}from"fs";import{homedir as pR}from"os";import{dirname as Zm,isAbsolute as fR,join as qn,resolve as mR}from"path";ma();ma();var hR=50000,Pd="session-";function Qm(e){return Buffer.from(JSON.stringify(e),"utf8").toString("base64url")}function gR(e){try{let t=JSON.parse(Buffer.from(e,"base64url").toString("utf8"));if(typeof t==="object"&&t!==null&&typeof t.session==="string"&&typeof t.seq==="number")return t}catch{}return}function Sd(e,t){let r={...e};return r[t]=void 0,r}class Rd{events=[];eventStart=0;nextSeq=1;max;bus;detector;persistDir;persist;sessionID;streams=new Map;lastWrite=new Map;parsers=[];constructor(e,t,r,n,o,i,s=0,a=0){this.max=e>0?e:hR,this.bus=t,this.detector=r,this.sessionID=i;let c=o===""||o.startsWith("~/")?Js():o;if(this.persist=n&&i!=="",this.persistDir=this.persist?qn(c,`${Pd}${i}`):"",this.persist)cR(this.persistDir,{recursive:!0,mode:448}),xR(c,s,a)}sessionDir(){return this.persistDir}setParsers(e){this.parsers=e}append(e){let t=hd(e.message),r=this.parseLine(t),n=(c)=>this.detector?this.detector.redactText(c):c,o=r.raw!==void 0,i=n(t),s=r.message!==void 0?n(hd(r.message)):void 0,a={...e,timestamp:e.timestamp||new Date().toISOString(),level:e.level||r.level||vd(t),request_id:e.request_id||r.request_id||yd(t),message:o&&s!==void 0?s:i,raw:o?i:void 0,seq:this.nextSeq++};if(this.events.length<this.max)this.events.push(a);else this.events[this.eventStart]=a,this.eventStart=(this.eventStart+1)%this.max;if(this.bus?.publish(Se(Mo,a.service,{event:a,level:a.level})),this.persist){let c=`${a.timestamp} ${a.service} ${a.level} ${a.raw??a.message}
2164
+ `,l=yR(a.service),d=this.streamFor(l);this.lastWrite.set(l,new Promise((u)=>{d.write(c,()=>u())}))}return a}async flush(){await Promise.all([...this.lastWrite.values()])}parseLine(e){let t={};for(let r of this.parsers)try{let n=r.parse(e);if(n)t={...t,...n}}catch{}return t}streamFor(e){let t=this.streams.get(e);if(t===void 0)t=aR(qn(this.persistDir,`${e}.log`),{flags:"a",mode:384}),t.on("error",()=>{}),this.streams.set(e,t);return t}async close(){await this.flush(),await Promise.all([...this.streams.values()].map((e)=>new Promise((t)=>{e.end(()=>t())}))),this.streams.clear(),this.lastWrite.clear()}query(e){let t=bd(e),r=[];return this.forEachEvent((n)=>{if(t(n))r.push(n)}),r}queryPage(e,t={}){let r=Jm(t.limit),n=t.cursor?gR(t.cursor):void 0,o=n!==void 0&&n.session!==this.sessionID,i=o?void 0:n,s=i?t.direction??"backward":"backward",a=bd(e),c=[];this.forEachEvent((m)=>{if(a(m))c.push(m)});let l;if(!i)l=c.slice(Math.max(0,c.length-r));else if(s==="forward")l=c.filter((m)=>m.seq>i.seq).slice(0,r);else{let m=c.filter((h)=>h.seq<i.seq);l=m.slice(Math.max(0,m.length-r))}let d=l[0]?.seq,u=l[l.length-1]?.seq,f=d!==void 0&&c.some((m)=>m.seq<d),p=u!==void 0&&c.some((m)=>m.seq>u);return{events:l,prevCursor:Qm({session:this.sessionID,seq:d??i?.seq??0}),nextCursor:Qm({session:this.sessionID,seq:u??i?.seq??this.nextSeq-1}),hasNext:p,hasPrev:f,sessionChanged:o}}queryFacets(e){let t=Sd(e,"services"),r=Sd(e,"level"),n=Sd(e,"source"),o=xd(e),i=0,s={},a={},c={};return this.forEachEvent((l)=>{if(!o(l))return;if(No(e,l))i+=1;if(No(t,l))s[l.service]=(s[l.service]??0)+1;if(No(r,l))a[l.level]=(a[l.level]??0)+1;if(No(n,l))c[l.source]=(c[l.source]??0)+1}),{total:i,byService:s,byLevel:a,bySource:c}}snapshot(){let e={},t=0;return this.forEachEvent((r)=>{if(e[r.service]=(e[r.service]??0)+1,r.level===Kn||r.level===$o)t+=1}),{total:this.events.length,errors:t,counts:e}}forEachEvent(e){let t=this.events.length;for(let r=0;r<t;r+=1){let n=this.events[(this.eventStart+r)%t];if(n)e(n)}}exportTo(e,t){_d(e,this.query(t))}}function eh(e){return{append:(t)=>{e.append(t)},query:async(t)=>e.query(t),queryPage:async(t,r)=>e.queryPage(t,r),queryFacets:async(t)=>e.queryFacets(t),snapshot:()=>e.snapshot(),exportTo:async(t,r)=>{e.exportTo(t,r)},setParsers:(t)=>{e.setParsers(t)},setSecrets:(t,r)=>{},close:()=>e.close()}}function vR(e=new Date){let t=e.toISOString().replace(/[:.]/g,"-");return qn(Xs(),`devctl-logs-${t}.log`)}function th(e=""){if(e==="")return vR();let t=e.startsWith("~/")?qn(pR(),e.slice(2)):e;return fR(t)?t:mR(process.cwd(),t)}function _d(e,t){Mr(Zm(e));let r=t.map((n)=>`${n.timestamp} ${n.service} ${n.level} ${n.raw??n.message}`);uR(e,`${r.join(`
622
2165
  `)}
623
- `,{mode:384})}function Jm(e){let t=Li(e)&&yd(e).isDirectory()?e:Km(e);if(Mr(t),process.platform==="darwin"){let r=Li(e)&&yd(e).isFile()?["-R",e]:[t];gd("open",r,{detached:!0,stdio:"ignore"}).unref();return}if(process.platform==="win32"){gd("explorer",[t],{detached:!0,stdio:"ignore"}).unref();return}gd("xdg-open",[t],{detached:!0,stdio:"ignore"}).unref()}function zm(e=Ks()){if(!Li(e))return[];return vd(e).filter((t)=>t.startsWith(xd)).sort().reverse()}function Ym(e,t=Ks()){let r=Un(t,e);if(!Li(r))return[];let n=[];for(let i of vd(r)){if(!i.endsWith(".log"))continue;let s=UR(Un(r,i),"utf8");for(let a of s.split(`
624
- `)){if(a.trim()==="")continue;let c=a.split(" "),l=c.slice(3).join(" "),d=dd(l);n.push({timestamp:c[0]??"",service:c[1]??i.replace(/\.log$/,""),source:"history",level:c[2]??"INFO",message:d?.message??l,raw:d?l:void 0,pid:0,seq:0})}}let o=n.sort((i,s)=>i.timestamp.localeCompare(s.timestamp));return o.forEach((i,s)=>{i.seq=s+1}),o}function ZR(e){let t=e.replace(/[^A-Za-z0-9._-]+/g,"_");return t===""?"service":t}function eP(e,t,r){if(!Li(e))return;let n=vd(e).filter((i)=>i.startsWith(xd)).map((i)=>{let s=Un(e,i),a=yd(s);return{path:s,mtime:a.mtimeMs}}).sort((i,s)=>s.mtime-i.mtime),o=t>0?Date.now()-t*86400000:0;n.forEach((i,s)=>{let a=o>0&&i.mtime<o,c=r>0&&s>=r;if(a||c)KR(i.path,{recursive:!0,force:!0})})}te();var{spawn:Rd}=globalThis.Bun;import{createServer as ng}from"net";var tP=65535,Cd=1;async function og(e,t,r={}){let n={};for(let[i,s]of Object.entries(r))for(let a of Object.values(s))n[a]=i;let o={};for(let[i,s]of Object.entries(e.services)){if(t.length>0&&!t.includes(i))continue;let a={};for(let c of s.ports){let l=r[i]?.[c.name],d=l&&l>=Cd?l:c.value;if(l===void 0&&c.auto)d=await rP();if(d<Cd||d>tP)throw new Qe(ee,`invalid port ${d} on service ${i}`,{service:i});if(n[d]&&n[d]!==i)throw new Qe(ee,`duplicate port ${d} used by ${n[d]} and ${i}`,{service:i});if(l!==d&&!c.auto&&!await Fo(d))throw await nP(i,c.name,d);n[d]=i,a[c.name]=d}o[i]=a}return o}function rP(){return new Promise((e,t)=>{let r=ng();r.listen(0,"127.0.0.1",()=>{let n=r.address();r.close(()=>{if(n&&typeof n==="object")e(n.port);else t(Re(ee,"unable to allocate dynamic port",Error("no address")))})}),r.on("error",(n)=>t(Re(ee,"unable to allocate dynamic port",n)))})}async function pa(e){let t=e.ports.filter((n)=>!n.auto&&n.value>=Cd);if(t.length===0)return;let r={};for(let n of t){if(await Fo(n.value))return;r[n.name]=n.value}return r}function Fo(e){return new Promise((t)=>{let r=ng();r.listen(e,"127.0.0.1",()=>{r.close(()=>t(!0))}),r.on("error",()=>t(!1))})}async function nP(e,t,r){let n=await Vn(r);return oP(e,t,r,n)}function oP(e,t,r,n){let o=t===""?`${e} port`:`${e} ports.${t}`;if(n&&n.pid===process.pid)return Ne(He,`${e} blocked: this TUI already holds port ${r}`,`Stop the proxy from the Proxy screen, or change ${o} in .devctl.`,e);if(n)return Ne(He,`${e} blocked: ${n.command} (pid ${n.pid}) is using port ${r}`,`Open Doctor and press enter to free port ${r}, or change ${o} in .devctl.`,e);return Ne(He,`${e} blocked: port ${r} is already in use`,`Something is listening on ${r}. Open Doctor to free it, or change ${o} in .devctl.`,e)}function iP(e,t){let r=e.split(`
625
- `).filter((n)=>n.trim()!==""&&!n.startsWith("COMMAND"));for(let n of r){let o=n.trim().split(/\s+/),i=o[0]??"",s=Number(o[1]);if(i!==""&&Number.isInteger(s)&&s>0)return{port:t,pid:s,command:i}}return}async function Vn(e){if(process.platform==="win32")return lP(e);let t=await sP(e);if(t)return t;return aP(e)}async function sP(e){try{let t=Rd({cmd:["lsof","-nP",`-iTCP:${e}`,"-sTCP:LISTEN"],stdout:"pipe",stderr:"ignore"}),r=await new Response(t.stdout).text();return await t.exited,iP(r,e)}catch{return}}async function aP(e){let t;try{let o=Rd({cmd:["fuser","-n","tcp",String(e)],stdout:"pipe",stderr:"pipe"});t=`${await new Response(o.stdout).text()} ${await new Response(o.stderr).text()}`,await o.exited}catch{return}let r=t.match(/(\d+)/),n=r?Number(r[1]):0;if(!Number.isInteger(n)||n<=0)return;return{port:e,pid:n,command:"process"}}function cP(e,t){for(let r of e.split(`
626
- `)){if(!r.includes("LISTENING")&&!r.includes("LISTEN"))continue;if(!r.includes(`:${t}`)&&!r.includes(` ${t} `))continue;let n=r.trim().match(/(\d+)\s*$/),o=n?Number(n[1]):0;if(Number.isInteger(o)&&o>0)return{port:t,pid:o,command:"process"}}return}async function lP(e){let t=Rd({cmd:["netstat","-ano"],stdout:"pipe",stderr:"ignore"}),r=t.stdout?await new Response(t.stdout).text():"";return await t.exited,cP(r,e)}var dP=400;async function fa(e){if(e.pid===process.pid)throw Ne(He,`port ${e.port} is held by this TUI`,"stop the proxy from the proxy screen, or quit the TUI");try{process.kill(e.pid,"SIGTERM")}catch(t){throw Re(He,`could not stop pid ${e.pid} on port ${e.port}`,t)}await uP(dP);try{process.kill(e.pid,0),process.kill(e.pid,"SIGKILL")}catch{return}}function uP(e){return new Promise((t)=>{setTimeout(t,e)})}Pa();te();import{readdirSync as x_,readFileSync as b_}from"fs";import{basename as uh,dirname as w_,extname as S_,join as Qn,resolve as k_}from"path";import{parse as C_}from"yaml";mt();function N(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function $d(e){return N(e)?new Set(Object.keys(e)):new Set}function J(e){if(typeof e==="string")return e;if(typeof e==="number"||typeof e==="boolean")return String(e);return""}function Pe(e){if(typeof e==="number")return e;if(typeof e==="string"){let t=Number(e);return Number.isNaN(t)?0:t}return 0}function gt(e){return e===!0}function Pt(e){if(!Array.isArray(e))return[];return e.map((t)=>J(t))}function Fd(e){if(!Array.isArray(e))return[];return e.map((t)=>N(t)?{service:J(t.service),condition:J(t.condition)||"service_started"}:J(t))}function bn(e){if(!N(e))return{};let t={};for(let[r,n]of Object.entries(e))t[r]=J(n);return t}function Cr(e){if(typeof e==="string")return{args:e.split(/\s+/).filter((t)=>t!==""),shell:!1};if(Array.isArray(e))return{args:e.map((t)=>J(t)),shell:!1};return No()}function Nd(e){if(e===void 0||e===null)return[];if(typeof e==="string"||typeof e==="number")return[Id(e,"http")];if(Array.isArray(e))return e.map((t,r)=>Id(t,`port_${r}`));if(N(e))return Object.entries(e).map(([t,r])=>Id(r,t));return[]}function Id(e,t){if(typeof e==="string"&&e.toLowerCase()==="auto")return{name:t,value:0,auto:!0};return{name:t,value:Pe(e),auto:!1}}function gg(e){if(!N(e))return _d();let t={},r=[],n={};for(let[o,i]of Object.entries(e))if(o==="required")r=Pt(i);else if(o==="defaults")n=bn(i);else t[o]=J(i);return{vars:t,required:r,defaults:n}}function wP(e){if(!N(e))return Td();return{type:J(e.type),mode:J(e.mode),service_account:J(e.service_account),config:N(e.config)?{...e.config}:{}}}function SP(e){if(!N(e))return Ed();return{type:J(e.type),url:J(e.url),address:J(e.address),command:Cr(e.command),interval_seconds:Pe(e.interval_seconds),timeout_seconds:Pe(e.timeout_seconds),start_period_seconds:Pe(e.start_period_seconds),unhealthy_threshold:e.unhealthy_threshold===void 0?3:Pe(e.unhealthy_threshold),healthy_reset_threshold:e.healthy_reset_threshold===void 0?10:Pe(e.healthy_reset_threshold)}}function kP(e){if(!N(e))return{policy:"",max_retries:0,backoff_seconds:0};return{enabled:e.enabled===void 0?void 0:gt(e.enabled),policy:J(e.policy),max_retries:Pe(e.max_retries),backoff_seconds:Pe(e.backoff_seconds)}}function CP(e){if(!N(e))return{wait_for_healthy:!1,timeout_seconds:0};return{wait_for_healthy:gt(e.wait_for_healthy),timeout_seconds:Pe(e.timeout_seconds)}}function Ii(e){if(!N(e))return gn();let t=N(e.logs)?e.logs:{};return{extends:J(e.extends),description:J(e.description),command:Cr(e.command),shell:gt(e.shell),working_dir:J(e.working_dir),dependencies:Fd(e.dependencies),ports:Nd(e.ports),environment:gg(e.environment),health:SP(e.health),identity:wP(e.identity),logs:{stdout:gt(t.stdout),stderr:gt(t.stderr)},restart:kP(e.restart),startup:CP(e.startup),capabilities:Pt(e.capabilities),proxy:Bd(e.proxy),container:Hd(e.container),watch:RP(e.watch),hooks:PP(e.hooks)}}function RP(e){if(!N(e))return Pd();return{enabled:gt(e.enabled),paths:Pt(e.paths),debounce_ms:ya(Pe(e.debounce_ms)),ignore:e.ignore===void 0?[...Di]:Pt(e.ignore)}}function PP(e){let t=N(e)?e:{};return{pre_start:Cr(t.pre_start),post_start:Cr(t.post_start)}}function hg(e){let t=N(e)?e:{};return{command:Cr(t.command),shell:gt(t.shell),working_dir:J(t.working_dir),dependencies:Pt(t.dependencies),environment:gg(t.environment)}}function Hd(e){if(!N(e))return;let t={};if(N(e.ports))for(let[r,n]of Object.entries(e.ports))t[r]=Pe(n);return{image:J(e.image),runtime:J(e.runtime),ports:t,env:bn(e.env),volumes:Pt(e.volumes)}}function Bd(e){if(e===void 0||e===null)return[];if(Array.isArray(e))return e.map((t)=>zn(t));if(N(e))return[zn(e)];return[]}function _a(e){if(!N(e))return{services:[],environment:{}};return{services:Pt(e.services),environment:bn(e.environment)}}function _P(e){if(typeof e==="string")return{type:e,service_account:""};if(!N(e))return{type:"",service_account:""};return{type:J(e.type),service_account:J(e.service_account)}}function EP(e){if(!N(e))return{type:"",identity:{type:"",service_account:""},audience:"",service_account:""};return{type:J(e.type),identity:_P(e.identity),audience:J(e.audience),service_account:J(e.service_account)}}function zn(e){if(!N(e))return{name:"",match:{host:"",path:""},upstream:{url:""},auth:{type:"",identity:{type:"",service_account:""},audience:"",service_account:""}};let t=N(e.match)?e.match:{},r=N(e.upstream)?e.upstream:{};return{name:J(e.name),match:{host:J(t.host),path:J(t.path)},upstream:{url:J(r.url)},auth:EP(e.auth)}}te();import{existsSync as TP,statSync as vg}from"fs";import{basename as yg,dirname as Ea,isAbsolute as AP,join as Ta,resolve as xg}from"path";function Yn(e,t){if(t!=="")return LP(t);let r=e===""?process.cwd():e;r=AP(r)?r:xg(r);for(;;){let n=Ta(r,Nt,lr);if($i(n))return{repoRoot:r,configPath:n};let o=Ta(r,"devctl.yaml");if($i(o))return{repoRoot:r,configPath:o};let i=Ea(r);if(i===r)break;r=i}throw Ne(Kn,"no devctl configuration found","run `devctl setup` or create a .devctl/config.yaml in the repository root")}function LP(e){let t=xg(e),r;try{r=vg(t)}catch(o){throw Re(Kn,"config path not found",o)}if(r.isDirectory()){if(yg(t)===Nt)return{repoRoot:Ea(t),configPath:Ta(t,lr)};return{repoRoot:t,configPath:Ta(t,lr)}}let n=Ea(t);if(yg(n)===Nt)n=Ea(n);return{repoRoot:n,configPath:t}}function $i(e){return TP(e)&&!vg(e).isDirectory()}mt();function Da(){return{services:{},templates:{},provenance:{}}}function wn(e,t,r,n,o=""){if(N(t)&&Object.keys(t).length>0){for(let[i,s]of Object.entries(t))wn(e,s,r,n,o===""?i:`${o}.${i}`);return}if(o==="")return;(e[o]??=[]).push({source:r,layer:n})}var DP=["environment","restart","startup","health","logs","identity","container","watch","hooks"];function Aa(e,t,r){let n=$d(r);if(N(r))for(let i of DP){let s=r[i];if(N(s))for(let a of Object.keys(s))n.add(`${i}.${a}`)}let o=e[t];e[t]=o?new Set([...o,...n]):n}function bg(e,t,r=Da(),n={source:"unknown",layer:"unknown"}){if(wn(r.provenance,t,n.source,n.layer),t.version!==void 0)e.version=Pe(t.version);if(N(t.project)&&t.project.name!==void 0)e.project.name=J(t.project.name);if(N(t.google)){if(t.google.project_id!==void 0)e.google.project_id=J(t.google.project_id);if(t.google.region!==void 0)e.google.region=J(t.google.region)}if(N(t.profiles))for(let[o,i]of Object.entries(t.profiles)){let s=e.profiles[o];e.profiles[o]=s?OP(s,i):_a(i)}if(N(t.templates))for(let[o,i]of Object.entries(t.templates)){let s=e.templates[o];e.templates[o]=s?La(s,i):Ii(i),Aa(r.templates,o,i)}if(N(t.services))for(let[o,i]of Object.entries(t.services)){let s=e.services[o];e.services[o]=s?La(s,i):Ii(i),Aa(r.services,o,i)}if(N(t.tasks))for(let[o,i]of Object.entries(t.tasks))e.tasks[o]=e.tasks[o]?IP(e.tasks[o],i):hg(i);if(N(t.proxy))MP(e.proxy,t.proxy);if(N(t.logs)){if(t.logs.max_memory_events!==void 0)e.logs.max_memory_events=Pe(t.logs.max_memory_events);if(N(t.logs.persistence)){let o=t.logs.persistence;if(o.enabled!==void 0)e.logs.persistence.enabled=gt(o.enabled);if(o.directory!==void 0)e.logs.persistence.directory=J(o.directory);if(o.retention_days!==void 0)e.logs.persistence.retention_days=Pe(o.retention_days);if(o.max_session_logs!==void 0)e.logs.persistence.max_session_logs=Pe(o.max_session_logs)}}if(N(t.auth)&&t.auth.refresh_threshold_seconds!==void 0)e.auth.refresh_threshold_seconds=Pe(t.auth.refresh_threshold_seconds);if(N(t.shutdown)){if(t.shutdown.stop_services_on_exit!==void 0)e.shutdown.stop_services_on_exit=gt(t.shutdown.stop_services_on_exit);if(t.shutdown.grace_seconds!==void 0)e.shutdown.grace_seconds=Pe(t.shutdown.grace_seconds)}if(N(t.ui)){if(t.ui.theme!==void 0)e.ui.theme=J(t.ui.theme);if(t.ui.keymap!==void 0)e.ui.keymap=bn(t.ui.keymap)}if(N(t.secrets)){if(t.secrets.extra_markers!==void 0)e.secrets.extra_markers=Pt(t.secrets.extra_markers);if(t.secrets.extra_patterns!==void 0)e.secrets.extra_patterns=Pt(t.secrets.extra_patterns)}if(N(t.doctor)&&Array.isArray(t.doctor.tools))e.doctor.tools=t.doctor.tools.filter(N).map((o)=>({name:J(o.name),command:J(o.command)}));if(Array.isArray(t.plugins))e.plugins=t.plugins.filter(N).map((o)=>({path:J(o.path)})).filter((o)=>o.path!=="");if(N(t.environment)){if(Array.isArray(t.environment.sources))e.environment.sources=Pt(t.environment.sources);if(N(t.environment.secrets))e.environment.secrets={...e.environment.secrets,...bn(t.environment.secrets)}}}function MP(e,t){if(t.enabled!==void 0)e.enabled=gt(t.enabled);if(N(t.listen)){if(t.listen.host!==void 0)e.listen.host=J(t.listen.host);if(t.listen.port!==void 0)e.listen.port=Pe(t.listen.port)}if(N(t.token_endpoint)){let r=t.token_endpoint;if(r.enabled!==void 0)e.token_endpoint.enabled=gt(r.enabled);if(r.host!==void 0)e.token_endpoint.host=J(r.host);if(r.port!==void 0)e.token_endpoint.port=Pe(r.port)}if(Array.isArray(t.routes))e.routes=t.routes.map((r)=>zn(r))}function OP(e,t){if(!N(t))return e;return{services:t.services!==void 0?Pt(t.services):e.services,environment:t.environment!==void 0?{...e.environment,...bn(t.environment)}:e.environment}}function IP(e,t){if(!N(t))return e;return{command:t.command!==void 0?Cr(t.command):e.command,shell:t.shell!==void 0?gt(t.shell):e.shell,working_dir:t.working_dir!==void 0?J(t.working_dir):e.working_dir,dependencies:t.dependencies!==void 0?Pt(t.dependencies):e.dependencies,environment:wg(e.environment,t.environment)}}function La(e,t){if(!N(t))return e;let r=$d(t),n={...e,environment:wg(e.environment,t.environment),health:NP(e.health,t.health),identity:HP(e.identity,t.identity),logs:BP(e.logs,t.logs),restart:WP(e.restart,t.restart),startup:jP(e.startup,t.startup),container:GP(e.container,t.container),watch:$P(e.watch,t.watch),hooks:FP(e.hooks,t.hooks)};if(r.has("extends"))n.extends=J(t.extends);if(r.has("description"))n.description=J(t.description);if(r.has("command"))n.command=Cr(t.command);if(r.has("shell"))n.shell=gt(t.shell);if(r.has("working_dir"))n.working_dir=J(t.working_dir);if(r.has("dependencies"))n.dependencies=Fd(t.dependencies);if(r.has("ports"))n.ports=Nd(t.ports);if(r.has("capabilities"))n.capabilities=Pt(t.capabilities);if(r.has("proxy"))n.proxy=Bd(t.proxy);return n}function $P(e,t){if(!N(t))return e;return{enabled:t.enabled!==void 0?gt(t.enabled):e.enabled,paths:t.paths!==void 0?Pt(t.paths):e.paths,debounce_ms:t.debounce_ms!==void 0?ya(Pe(t.debounce_ms)):e.debounce_ms,ignore:t.ignore!==void 0?Pt(t.ignore):e.ignore}}function FP(e,t){if(!N(t))return e;return{pre_start:t.pre_start!==void 0?Cr(t.pre_start):e.pre_start,post_start:t.post_start!==void 0?Cr(t.post_start):e.post_start}}function wg(e,t){if(!N(t))return e;let r={...e.vars},n={...e.defaults},o=e.required;for(let[i,s]of Object.entries(t))if(i==="required")o=Pt(s);else if(i==="defaults")Object.assign(n,bn(s));else r[i]=J(s);return{vars:r,required:o,defaults:n}}function NP(e,t){if(!N(t))return e;return{type:t.type!==void 0?J(t.type):e.type,url:t.url!==void 0?J(t.url):e.url,address:t.address!==void 0?J(t.address):e.address,command:t.command!==void 0?Cr(t.command):e.command,interval_seconds:t.interval_seconds!==void 0?Pe(t.interval_seconds):e.interval_seconds,timeout_seconds:t.timeout_seconds!==void 0?Pe(t.timeout_seconds):e.timeout_seconds,start_period_seconds:t.start_period_seconds!==void 0?Pe(t.start_period_seconds):e.start_period_seconds,unhealthy_threshold:t.unhealthy_threshold!==void 0?Pe(t.unhealthy_threshold):e.unhealthy_threshold,healthy_reset_threshold:t.healthy_reset_threshold!==void 0?Pe(t.healthy_reset_threshold):e.healthy_reset_threshold}}function HP(e,t){if(!N(t))return e;return{type:t.type!==void 0?J(t.type):e.type,mode:t.mode!==void 0?J(t.mode):e.mode,service_account:t.service_account!==void 0?J(t.service_account):e.service_account,config:t.config!==void 0&&N(t.config)?{...e.config??{},...t.config}:e.config}}function BP(e,t){if(!N(t))return e;return{stdout:t.stdout!==void 0?gt(t.stdout):e.stdout,stderr:t.stderr!==void 0?gt(t.stderr):e.stderr}}function WP(e,t){if(!N(t))return e;return{enabled:t.enabled!==void 0?gt(t.enabled):e.enabled,policy:t.policy!==void 0?J(t.policy):e.policy,max_retries:t.max_retries!==void 0?Pe(t.max_retries):e.max_retries,backoff_seconds:t.backoff_seconds!==void 0?Pe(t.backoff_seconds):e.backoff_seconds}}function jP(e,t){if(!N(t))return e;return{wait_for_healthy:t.wait_for_healthy!==void 0?gt(t.wait_for_healthy):e.wait_for_healthy,timeout_seconds:t.timeout_seconds!==void 0?Pe(t.timeout_seconds):e.timeout_seconds}}function GP(e,t){if(!N(t))return e;let r=Hd(t);if(!r)return e;return{image:t.image!==void 0?r.image:e?.image??"",runtime:t.runtime!==void 0?r.runtime:e?.runtime??"",ports:t.ports!==void 0?{...e?.ports??{},...r.ports}:e?.ports??{},env:t.env!==void 0?{...e?.env??{},...r.env}:e?.env??{},volumes:t.volumes!==void 0?r.volumes:e?.volumes??[]}}function Sg(e,t){let r=Object.keys(e.services).sort();for(let n of r){let o=e.services[n]?.proxy??[];if(o.length===0)continue;o.forEach((i,s)=>{let c={name:o.length===1?n:`${n}-${s+1}`,match:i.match,upstream:i.upstream,auth:i.auth},l=e.proxy.routes.length;if(e.proxy.routes.push(c),t)wn(t,c,`synthesized from services.${n}.proxy`,"synthesized",`proxy.routes.${l}`)})}}function kg(e,t=Da()){if(Object.keys(e.templates).length===0)return;let r={};for(let[n,o]of Object.entries(e.services))r[n]=Cg(e,o,t.services[n]??new Set,t,{});e.services=r}function Cg(e,t,r,n,o){if(t.extends==="")return t;if(o[t.extends])throw Error(`template cycle involving "${t.extends}"`);let i=e.templates[t.extends]??gn();if(!e.templates[t.extends])throw Error(`service extends unknown template "${t.extends}"`);let s={...o,[t.extends]:!0},a=n.templates[t.extends]??new Set,c=Cg(e,i,a,n,s),l=UP(c,t,r);return l.extends=t.extends,l}function UP(e,t,r){let n={...e};if(r.has("extends"))n.extends=t.extends;if(r.has("description"))n.description=t.description;if(r.has("command"))n.command=t.command;if(r.has("shell"))n.shell=t.shell;if(r.has("working_dir"))n.working_dir=t.working_dir;if(r.has("dependencies"))n.dependencies=t.dependencies;if(r.has("ports"))n.ports=t.ports;if(r.has("capabilities"))n.capabilities=t.capabilities;if(r.has("proxy"))n.proxy=t.proxy;if(r.has("container")){let o=t.container,i=e.container;n.container=o&&i?{image:r.has("container.image")?o.image:i.image,runtime:r.has("container.runtime")?o.runtime:i.runtime,ports:r.has("container.ports")?o.ports:i.ports,env:r.has("container.env")?o.env:i.env,volumes:r.has("container.volumes")?o.volumes:i.volumes}:o??i}if(r.has("watch"))n.watch=t.watch;return n.hooks={pre_start:r.has("hooks.pre_start")?t.hooks.pre_start:e.hooks.pre_start,post_start:r.has("hooks.post_start")?t.hooks.post_start:e.hooks.post_start},n.health={type:r.has("health.type")?t.health.type:e.health.type,url:r.has("health.url")?t.health.url:e.health.url,address:r.has("health.address")?t.health.address:e.health.address,command:r.has("health.command")?t.health.command:e.health.command,interval_seconds:r.has("health.interval_seconds")?t.health.interval_seconds:e.health.interval_seconds,timeout_seconds:r.has("health.timeout_seconds")?t.health.timeout_seconds:e.health.timeout_seconds,start_period_seconds:r.has("health.start_period_seconds")?t.health.start_period_seconds:e.health.start_period_seconds,unhealthy_threshold:r.has("health.unhealthy_threshold")?t.health.unhealthy_threshold:e.health.unhealthy_threshold,healthy_reset_threshold:r.has("health.healthy_reset_threshold")?t.health.healthy_reset_threshold:e.health.healthy_reset_threshold},n.identity={type:r.has("identity.type")?t.identity.type:e.identity.type,mode:r.has("identity.mode")?t.identity.mode:e.identity.mode,service_account:r.has("identity.service_account")?t.identity.service_account:e.identity.service_account,config:r.has("identity.config")?t.identity.config:e.identity.config},n.logs={stdout:r.has("logs.stdout")?t.logs.stdout:e.logs.stdout,stderr:r.has("logs.stderr")?t.logs.stderr:e.logs.stderr},n.restart={enabled:r.has("restart.enabled")?t.restart.enabled:e.restart.enabled,policy:r.has("restart.policy")?t.restart.policy:e.restart.policy,max_retries:r.has("restart.max_retries")?t.restart.max_retries:e.restart.max_retries,backoff_seconds:r.has("restart.backoff_seconds")?t.restart.backoff_seconds:e.restart.backoff_seconds},n.startup={wait_for_healthy:r.has("startup.wait_for_healthy")?t.startup.wait_for_healthy:e.startup.wait_for_healthy,timeout_seconds:r.has("startup.timeout_seconds")?t.startup.timeout_seconds:e.startup.timeout_seconds},n.environment={vars:{...e.environment.vars,...t.environment.vars},defaults:{...e.environment.defaults,...t.environment.defaults},required:r.has("environment.required")?t.environment.required:e.environment.required},n}te();mt();function Rg(e){if(e.version===0)throw M(ee,"version is required");if(e.version===Ho)return e;throw M(ee,`unsupported config version ${e.version} (expected ${Ho}); no migration is available`)}var Pg=["version","project","google","profiles","templates","services","tasks","proxy","logs","auth","shutdown","ui","secrets","doctor","plugins","environment"],_g=["extends","description","command","shell","working_dir","dependencies","ports","environment","health","identity","logs","restart","startup","capabilities","proxy","container","watch","hooks"],Eg=["type","url","address","command","interval_seconds","timeout_seconds","start_period_seconds","unhealthy_threshold","healthy_reset_threshold"],Tg=["service","condition"],Ag=["type","mode","service_account","config"],Lg=["enabled","policy","max_retries","backoff_seconds"],Dg=["wait_for_healthy","timeout_seconds"],Mg=["enabled","listen","token_endpoint","routes"],Og=["host","port"],Wd=["name","match","upstream","auth"],jd=["host","path"],Gd=["url"],Ud=["type","identity","audience","service_account"],Ig=["max_memory_events","persistence"],$g=["enabled","directory","retention_days","max_session_logs"],Fg=["refresh_threshold_seconds"],Ng=["stop_services_on_exit","grace_seconds"],Hg=["theme","keymap"],Bg=["name"],Wg=["project_id","region"],jg=["services","environment"],Kd=["required","defaults"],Gg=["stdout","stderr"],Ug=["extra_markers","extra_patterns"],Kg=["tools"],qg=["name","command"],Vg=["enabled","host","port"],Jg=["path"],zg=["sources","secrets"],Yg=["image","runtime","ports","env","volumes"],Xg=["enabled","paths","debounce_ms","ignore"],Qg=["pre_start","post_start"],Zg=["command","shell","working_dir","dependencies","environment"],eh=["google","google_api","iap","network","service_identity","local_http"],th=["|","||","&&",";",">",">>","<","&"];var KP=2;function Sn(e,t){if(Array.isArray(e))return e.flatMap((o,i)=>Sn(o,rh(t,String(i))));if(!QP(e))return[];let r=VP(t),n=[];for(let o of Object.keys(e)){let i=rh(t,o);if(r.length>0&&!r.includes(o)&&!qP(t))n.push(i);n.push(...Sn(e[o],i))}return n}function qP(e){if(e==="services"||e==="profiles"||e==="templates"||e==="tasks")return!0;if(e.endsWith(".environment")||e.endsWith(".defaults")||e.endsWith(".keymap"))return!0;if(e.endsWith(".container.ports")||e.endsWith(".container.env"))return!0;if(e.endsWith(".identity.config")||e.includes(".identity.config."))return!0;return e.includes(".environment.")&&!e.endsWith(".environment")}function VP(e){switch(e){case"":return Pg;case"project":return Bg;case"google":return Wg;case"proxy":return Mg;case"proxy.listen":return Og;case"proxy.token_endpoint":return Vg;case"logs":return Ig;case"logs.persistence":return $g;case"auth":return Fg;case"shutdown":return Ng;case"ui":return Hg;case"secrets":return Ug;case"doctor":return Kg;case"environment":return zg;default:return JP(e)}}function JP(e){if(e==="services"||e==="profiles"||e==="templates"||e==="tasks")return[];if(e.startsWith("services.")||e.startsWith("templates.")){if(e.includes(".dependencies."))return Tg;return zP(e)}if(e.startsWith("profiles.")){if(e.split(".").length===2)return jg}if(e.startsWith("tasks.")){let t=e.split(".");if(t.length===2)return Zg;if(t[2]==="environment")return Kd}if(e.includes("routes"))return XP(e);if(e.startsWith("doctor.tools")&&e.split(".").length>=3)return qg;if(e==="plugins")return[];if(e.startsWith("plugins.")&&e.split(".").length===2)return Jg;return[]}function zP(e){let t=e.split(".");if(t.length===2)return _g;if(t.length>=3)switch(t[2]){case"health":return Eg;case"identity":return Ag;case"restart":return Lg;case"startup":return Dg;case"logs":return Gg;case"environment":return Kd;case"proxy":return YP(t);case"container":return Yg;case"watch":return Xg;case"hooks":return Qg;default:return[]}return[]}function YP(e){let t=e.slice(3),r=t[0]!==void 0&&/^\d+$/.test(t[0])?1:0,n=t[r]??"";if(n==="match")return jd;if(n==="upstream")return Gd;if(n==="auth")return Ud;return Wd}function XP(e){if(e==="proxy.routes")return[];if(e.endsWith(".match"))return jd;if(e.endsWith(".upstream"))return Gd;if(e.endsWith(".auth"))return Ud;if(e.includes("proxy.routes")&&e.split(".").length===KP+1)return Wd;return[]}function rh(e,t){if(e==="")return t;return`${e}.${t}`}function QP(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function Bo(e){return`unknown fields: ${e.join(", ")}`}mt();function kn(e){let t=e.trim().toLowerCase();if(t==="")return!0;if(t==="localhost"||t==="::1")return!0;if(nh(t))return!0;return ZP(t)}function nh(e){let t=e.split(".");if(t.length!==4)return!1;let r=t.map((n)=>Number(n));if(r.some((n)=>!Number.isInteger(n)||n<0||n>255))return!1;return r[0]===127}function ZP(e){if(!e.startsWith("::ffff:"))return!1;return nh(e.slice(7))}import{existsSync as r_}from"fs";import{isAbsolute as n_,resolve as o_}from"path";import{fileURLToPath as i_}from"url";mt();function e_(e,t,r){let n=e,o="";for(;;){let i=n.indexOf("${");if(i<0)return o+n;o+=n.slice(0,i);let s=n.slice(i).indexOf("}");if(s<0)throw Error(`unclosed environment reference in "${e}"`);let a=n.slice(i+2,i+s);o+=t_(a,t,r),n=n.slice(i+s+1)}}function t_(e,t,r){let n=e.split(".");if(n.length<3||n[0]!=="services")throw Error(`unsupported reference \${${e}}`);let o=n[1]??"",i=t.services[o];if(!i)throw Error(`reference \${${e}}: unknown service`);let s=r[o];if(n[2]==="port"){if(s){if(s.http!==void 0)return String(s.http);let c=Object.values(s)[0];if(c!==void 0)return String(c)}let a=ug(i.ports);if(a&&!a.auto)return String(a.value)}if(n[2]==="ports"){let a=n[3];if(!a)throw Error(`reference \${${e}}: missing port name`);if(s&&s[a]!==void 0)return String(s[a]);let c=Ad(i.ports,a);if(c&&!c.auto)return String(c.value);let l=Number.parseInt(a,10);if(!Number.isNaN(l)&&l>=0&&l<i.ports.length){let d=i.ports[l];if(d&&!d.auto)return String(d.value)}}throw Error(`unresolvable reference \${${e}}`)}function qd(e,t,r){let n={};for(let[o,i]of Object.entries(e))n[o]=e_(i,t,r);return n}function oh(e){let t=[],r=e;for(;;){let n=r.indexOf("${");if(n<0)return t;let o=r.slice(n).indexOf("}");if(o<0)return t;t.push(r.slice(n+2,n+o)),r=r.slice(n+o+1)}}function ih(e,t){let r=e.split(".");if(r.length<2)return!1;if(r[0]!=="services"||r.length<3)return!1;let n=t.services[r[1]??""];if(!n)return!1;if(r[2]==="port"||r[2]==="ports"){if(r.length===3)return n.ports.length>0;if(r.length>=4){let o=r[3]??"";if(!Number.isNaN(Number.parseInt(o,10)))return!0;return Ad(n.ports,o)!==void 0}}return!0}mt();var Oa=65535,Ia=1,ch=["http","tcp","process","command"];function Vd(e){let t=[];for(let[r,n]of Object.entries(e.services)){let o=n.health.type;if(o!==""&&!ch.includes(o.toLowerCase()))t.push({service:r,type:o})}return t}var s_=0,sh=1,ah=2;function Xn(e){let t=[];if(e.version===0)t.push("version is required");else if(e.version!==Ho)t.push(`unsupported config version ${e.version} (expected ${Ho}); no migration is available`);if(Object.keys(e.services).length===0)t.push("at least one service must be defined");t.push(...c_(e)),t.push(...a_(e)),t.push(...p_(e)),t.push(...f_(e)),t.push(...m_(e));for(let[r,n]of e.plugins.entries())if(n.path==="")t.push(`plugins.${r}.path is required`);else try{let o=n.path.startsWith("file:")?i_(n.path):n_(n.path)?n.path:o_(e.repoRoot,n.path);if(!r_(o))t.push(`plugins.${r}.path does not exist: ${n.path}`)}catch{t.push(`plugins.${r}.path is invalid: ${n.path}`)}if(e.logs.max_memory_events<0)t.push("logs.max_memory_events must be >= 0");return t}function a_(e){let t=[];for(let[r,n]of Object.entries(e.tasks)){let o=`tasks.${r}`;if(hn(n.command))t.push(`${o}.command is required`);t.push(...Ma(o,n.command,n.shell));for(let i of n.dependencies)if(!e.services[i])t.push(`${o}.dependencies: unknown service "${i}"`);t.push(...lh(`${o}.environment`,n.environment,e))}return t}function c_(e){let t=[],r={};for(let[n,o]of Object.entries(e.services)){let i=`services.${n}`;if(hn(o.command)&&!o.container)t.push(`${i}.command is required`);t.push(...Ma(i,o.command,o.shell)),t.push(...Ma(`${i}.hooks.pre_start`,o.hooks.pre_start,o.shell)),t.push(...Ma(`${i}.hooks.post_start`,o.hooks.post_start,o.shell)),t.push(...h_(i,o.capabilities));for(let c of o.dependencies){let l=Jt(c);if(!e.services[l])t.push(`${i}.dependencies: unknown service "${l}"`);if(l===n)t.push(`${i}.dependencies: service cannot depend on itself`);if(!["service_started","service_healthy"].includes(mn(c)))t.push(`${i}.dependencies: condition must be service_started or service_healthy`);if(mn(c)==="service_healthy"&&e.services[l]?.health.type==="")t.push(`${i}.dependencies: service_healthy requires ${l} to define a health check`)}if(o.extends!==""&&!e.templates[o.extends])t.push(`${i}.extends: unknown template "${o.extends}"`);for(let c of o.ports.filter((l)=>!l.auto))if(c.value<Ia||c.value>Oa)t.push(`${i}.ports.${c.name}: invalid port ${c.value}`);else if(r[c.value])t.push(`duplicate port ${c.value} used by ${r[c.value]} and ${n}`);else r[c.value]=n;let s=u_(`${i}.identity`,o.identity,e.plugins.length>0);if(s!=="")t.push(s);if(t.push(...l_(i,o,e.plugins.length>0)),o.health.start_period_seconds<0)t.push(`${i}.health.start_period_seconds must be >= 0`);if(o.health.unhealthy_threshold<1)t.push(`${i}.health.unhealthy_threshold must be >= 1`);if(o.health.healthy_reset_threshold<1)t.push(`${i}.health.healthy_reset_threshold must be >= 1`);let a=ba(o.restart);if(a!==ma&&a!==ga&&a!==ha)t.push(`${i}.restart.policy must be never, on_failure, or always`);if(t.push(...lh(`${i}.environment`,o.environment,e)),o.container){if(o.container.image==="")t.push(`${i}.container.image is required`);if(o.container.runtime!==""&&o.container.runtime!=="docker"&&o.container.runtime!=="podman")t.push(`${i}.container.runtime must be docker or podman`);for(let[c,l]of Object.entries(o.container.ports)){if(!o.ports.some((d)=>d.name===c))t.push(`${i}.container.ports.${c}: no matching service port`);if(l<Ia||l>Oa)t.push(`${i}.container.ports.${c}: invalid container port ${l}`)}}}return t}function l_(e,t,r){let n=[];if(t.health.type==="")return n;let o=t.health.type.toLowerCase();if(!ch.includes(o)&&!r)n.push(`${e}.health.type must be http, tcp, process, or command`);if(o==="http"&&t.health.url==="")n.push(`${e}.health.url is required for http health checks`);if(o==="tcp"&&t.health.address===""&&t.ports.length===0)n.push(`${e}.health.address is required for tcp health checks without ports`);if(o==="command"&&hn({args:t.health.command.args,shell:!1}))n.push(`${e}.health.command is required for command health checks`);return n}var d_=["","user","service","service_account"];function u_(e,t,r){let n=Oi(t).toLowerCase();if(n===""||n==="user")return"";if(n==="service"||n==="service_account"){if(t.service_account==="")return`${e}.service_account is required for service identity`;if(!t.service_account.includes("@"))return`${e}.service_account must be an email`;return""}if(r)return"";return`${e}.type must be user or service_account`}function Jd(e){let t=[];for(let[r,n]of Object.entries(e.services)){let o=Oi(n.identity).toLowerCase();if(!d_.includes(o))t.push({service:r,type:o})}return t}function p_(e){let t={},r=[],n=[],o=(i)=>{let s=t[i]??s_;if(s===ah)return;if(s===sh){r.push(`dependency cycle: ${[...n,i].join(" \u2192 ")}`);return}t[i]=sh,n.push(i);let a=e.services[i];if(a)for(let c of a.dependencies){let l=Jt(c);if(e.services[l])o(l)}n.pop(),t[i]=ah};for(let i of Object.keys(e.services))o(i);return r}function lh(e,t,r){let n=[],o=(i,s)=>{for(let a of oh(s))if(!ih(a,r))n.push(`${e}.${i}: unresolvable reference \${${a}}`)};for(let[i,s]of Object.entries(t.vars))o(i,s);for(let[i,s]of Object.entries(t.defaults))o(i,s);return n}function f_(e){let t=[];for(let[r,n]of Object.entries(e.profiles))for(let o of n.services)if(!e.services[o])t.push(`profiles.${r} references unknown service "${o}"`);return t}function m_(e){let t=[];if(e.proxy.listen.host!==""){if(!y_(e.proxy.listen.host))t.push("proxy.listen.host must be an IP address or localhost");else if(!kn(e.proxy.listen.host))t.push("proxy.listen.host must be a loopback address")}if(e.proxy.enabled&&e.proxy.listen.port===0)t.push("proxy.listen.port is required when proxy.enabled is true");if(e.proxy.listen.port!==0&&(e.proxy.listen.port<Ia||e.proxy.listen.port>Oa))t.push("proxy.listen.port is invalid");let r={};return e.proxy.routes.forEach((n,o)=>{let i=`proxy.routes[${o}]`;if(n.name==="")t.push(`${i}.name is required`);else if(r[n.name])t.push(`${i}: duplicate route name ${n.name}`);if(r[n.name]=!0,n.upstream.url==="")t.push(`${i}.upstream.url is required`);if(n.auth.type.toLowerCase()==="iap"){if(n.auth.audience.trim()==="")t.push(`${i}.auth.audience is required when auth.type is iap`);if(n.auth.identity.type.trim()==="")t.push(`${i}.auth.identity.type is required when auth.type is iap`)}let s=n.auth.identity.type.toLowerCase();if(s==="service"||s==="service_account"||Zr({type:s,mode:"",service_account:""})){if((n.auth.identity.service_account||n.auth.service_account)==="")t.push(`${i}.auth.identity.service_account is required`)}}),t.push(...g_(e)),t}function g_(e){let t=[],r=e.proxy.token_endpoint;if(!r.enabled)return t;let n=r.host||va;if(!kn(n))t.push("proxy.token_endpoint.host must be a loopback address");if(r.port!==0&&(r.port<Ia||r.port>Oa))t.push("proxy.token_endpoint.port is invalid");return t}function Ma(e,t,r){if(t.shell||r)return[];for(let n of t.args)if(th.includes(n)||n.includes("|")||n.includes(";")||n.includes("&&"))return[`${e}.command contains shell metacharacters; set shell: true to run via a shell`];return[]}function h_(e,t){let r=[];for(let n of t)if(!eh.includes(n))r.push(`${e}.capabilities: unknown capability "${n}"`);return r}function y_(e){if(e==="localhost")return!0;return v_(e)}function v_(e){if(/^(\d{1,3}\.){3}\d{1,3}$/.test(e))return e.split(".").every((r)=>{let n=Number(r);return n>=0&&n<=255});return e.includes(":")}function Fi(e,t,r){try{return Ni(e,t,{candidateText:r}),[]}catch(n){if(n instanceof Qe){let o=`${n.kind}: `;return[n.message.startsWith(o)?n.message.slice(o.length):n.message]}return[n instanceof Error?n.message:String(n)]}}function tn(e,t){let{repoRoot:r,configPath:n}=Yn(e,t);return Ni(r,n)}function Zn(e,t){try{return tn(e,t)}catch(r){if(!ua(r,Kn))throw r;let n=e===""?process.cwd():k_(e),o=Jn();return o.repoRoot=n,o.configPath=Qn(n,Nt,lr),o}}function Ni(e,t,r){let n=Jn(),o=Da();ph(t,n,o,r?.candidateText,"main"),n.repoRoot=e,n.configPath=t;let i=w_(t);if(uh(i)===Nt)P_(i,n,o);n=R_(n,e,t,o),n=Rg(n);try{kg(n,o)}catch(a){throw Re(ee,"template merge failed",a)}Sg(n,o.provenance),n.provenance=o.provenance;let s=Xn(n);if(s.length>0)throw M(ee,s.join(`
627
- `));return n}function R_(e,t,r,n){let o=Qn(cr(),"config.local.yaml"),i=Qn(t,Nt,"config.local.yaml");for(let s of[o,i]){if(s===r||!$i(s))continue;ph(s,e,n,void 0,s===o?"home_local":"repo_local")}return e}function ph(e,t,r,n,o="main"){let i=n!==void 0?fh(n,e):zd(e),s=Sn(i,"");if(s.length>0)throw M(ee,Bo(s));bg(t,i,r,{source:e,layer:o})}function P_(e,t,r){dh(Qn(e,"services"),(o,i,s)=>{let a=Sn(i,`services.${o}`);if(a.length>0)throw M(ee,Bo(a));let c=t.services[o];t.services[o]=c?La(c,i):Ii(i),Aa(r.services,o,i),wn(r.provenance,i,s,"modular_service",`services.${o}`)}),dh(Qn(e,"profiles"),(o,i,s)=>{let a=Sn(i,`profiles.${o}`);if(a.length>0)throw M(ee,Bo(a));t.profiles[o]=_a(i),wn(r.provenance,i,s,"modular_profile",`profiles.${o}`)});let n=Qn(e,"proxy","routes.yaml");if($i(n))__(n,t,r)}function __(e,t,r){let n=zd(e);if(N(n.proxy))wn(r.provenance,n.proxy,e,"modular_proxy","proxy");if(Array.isArray(n.routes))wn(r.provenance,n.routes,e,"modular_proxy","proxy.routes");if(N(n.proxy)){let o=Sn({proxy:n.proxy},"");if(o.length>0)throw M(ee,Bo(o))}if(Array.isArray(n.routes))n.routes.forEach((o,i)=>{let s=Sn(o,`proxy.routes.${i}`);if(s.length>0)throw M(ee,Bo(s))});if(N(n.proxy)){if(Array.isArray(n.proxy.routes))t.proxy.routes.push(...n.proxy.routes.map((o)=>zn(o)));if(N(n.proxy.listen)){if(typeof n.proxy.listen.host==="string"&&n.proxy.listen.host!=="")t.proxy.listen.host=n.proxy.listen.host;if(typeof n.proxy.listen.port==="number"&&n.proxy.listen.port!==0)t.proxy.listen.port=n.proxy.listen.port}}if(Array.isArray(n.routes))t.proxy.routes.push(...n.routes.map((o)=>zn(o)))}function dh(e,t){let r;try{r=x_(e)}catch(n){if(E_(n))return;throw n}for(let n of r.sort()){let o=S_(n);if(o!==".yaml"&&o!==".yml")continue;let i=Qn(e,n),s=zd(i);t(uh(n,o),s,i)}}function zd(e){let t;try{t=b_(e,"utf8")}catch(r){throw Re(ee,"unable to read config",r)}return fh(t,e)}function fh(e,t){let r;try{r=C_(e)}catch(n){throw Re(ee,`invalid YAML in ${t}`,n)}if(r===null||r===void 0)return{};if(!N(r))throw M(ee,`unable to decode config: ${t} is not a mapping`);return r}function E_(e){return typeof e==="object"&&e!==null&&"code"in e&&e.code==="ENOENT"}mt();te();Fr();import{existsSync as Na,readFileSync as A_}from"fs";import{homedir as vh}from"os";import{join as Zd}from"path";var{spawn:eu}=globalThis.Bun;var L_="https://www.googleapis.com/auth/cloud-platform",xh=1500,D_=2000;function Uo(){let e=globalThis;if(e.window&&typeof e.window.fetch!=="function")e.window.fetch=fetch}function tu(){let e=process.env.GOOGLE_APPLICATION_CREDENTIALS||Zd(vh(),".config","gcloud","application_default_credentials.json");if(!Na(e))return"";try{let t=JSON.parse(A_(e,"utf8"));return typeof t.quota_project_id==="string"?t.quota_project_id:""}catch{return""}}async function ro(e){let t={gcloudInstalled:await ru("gcloud"),adcAvailable:!1,userEmail:"",projectID:"",projectSource:""};if(e!=="")t.projectID=e,t.projectSource="configuration";else{let r=F_("GOOGLE_CLOUD_PROJECT","GCLOUD_PROJECT","GOOGLE_PROJECT");if(r!=="")t.projectID=r,t.projectSource="environment variable";else if(t.gcloudInstalled){let n=await yh("core/project");if(n!=="")t.projectID=n,t.projectSource="gcloud configuration"}}if(Bi())try{await Ch(M_(t),D_)}catch(r){t.adcAvailable=!1,t.error=Ko(r)}if(t.userEmail===""&&t.gcloudInstalled)t.userEmail=await yh("core/account");return t}async function M_(e){Uo();let{GoogleAuth:t}=await import("google-auth-library"),r=new t({scopes:[L_]});await r.getClient(),e.adcAvailable=!0;let n=await r.getProjectId().catch(()=>"");if(e.projectID===""&&n)e.projectID=n,e.projectSource="application default credentials";let o=await O_(r);if(o!=="")e.userEmail=o}async function O_(e){try{let t=await e.getCredentials();if(t.client_email)return t.client_email}catch{}try{let t=await e.getClient(),r=typeof t.credentials?.id_token==="string"?t.credentials.id_token:"";if(r!==""){let n=I_(r);if(n!=="")return n}}catch{return""}return""}function I_(e){let t=e.split(".");if(t.length<2||!t[1])return"";try{let r=JSON.parse(Buffer.from(t[1],"base64url").toString("utf8"));return typeof r.email==="string"?r.email:""}catch{return""}}async function bh(e){let t=await ro(e);return dr({kind:"user",email:t.userEmail,project:t.projectID,projectSource:t.projectSource,adcAvailable:t.adcAvailable})}async function wh(){if(await eu({cmd:["gcloud","auth","application-default","login"],stdin:"inherit",stdout:"inherit",stderr:"inherit"}).exited!==0)throw Ne($o,"application-default login failed","run `gcloud auth application-default login`")}async function Sh(){if(await eu({cmd:["gcloud","auth","application-default","revoke","--quiet"],stdout:"ignore",stderr:"pipe"}).exited!==0)throw Ne($o,"failed to revoke application-default credentials","run `gcloud auth application-default revoke`")}function Ko(e){let t=$_(e).toLowerCase();if(Go(t,["unauth","invalid_grant","token has been expired","expired"]))return Ne($o,"credential expired or invalid","run `devctl auth login` or `gcloud auth application-default login`");if(Go(t,["api has not been used","has not been enabled","access not configured","service_disabled"]))return Ne(qn,"required Google API is not enabled","enable the API in the target service account project; devctl will not enable APIs automatically");if(Go(t,["permission","forbidden","iam"])){if(Go(t,["serviceaccounttoken","iamcredentials","token creator","getaccesstoken"]))return Ne(da,"cannot impersonate service account","ask an administrator to grant roles/iam.serviceAccountTokenCreator on the target service account");return Ne(qn,"authorization failure","verify IAM permissions for the current user on this project")}if(t.includes("audience"))return Ne(Sd,"IAP audience is incorrect","set auth.audience on the proxy route to the IAP OAuth client ID");if(t.includes("iap"))return Re(Sd,"IAP authentication failure",e);if(t.includes("project"))return Ne(ee,"wrong or missing Google project","set google.project_id in .devctl/config.yaml");if(Go(t,["timeout","network","connection refused","no such host"]))return Re(ct,"network problem reaching Google Cloud",e);if(Go(t,["adc","default credentials","could not find"]))return Ne($o,"application default credentials unavailable","run `gcloud auth application-default login`");return Re($o,"Google authentication failed",e)}function $_(e){let t=[],r=new Set,n=(o,i)=>{if(o===null||o===void 0||i>8||r.has(o))return;if(typeof o==="string"){t.push(o);return}if(typeof o!=="object")return;r.add(o);let s=o;for(let a of["message","reason","status","code","error_description","permission","service"])n(s[a],i+1);for(let a of["error","errors","details","response","data","cause","metadata"]){let c=s[a];if(Array.isArray(c))c.forEach((l)=>n(l,i+1));else n(c,i+1)}};return n(e,0),t.length>0?t.join(" "):String(e)}function Go(e,t){return t.some((r)=>e.includes(r))}function F_(...e){for(let t of e){let r=process.env[t];if(r)return r}return""}async function ru(e){try{return(await kh(process.platform==="win32"?["where",e]:["which",e],xh)).code===0}catch{return!1}}function Bi(){let e=process.env.GOOGLE_APPLICATION_CREDENTIALS;if(e&&Na(e))return!0;if(Na(Zd(vh(),".config","gcloud","application_default_credentials.json")))return!0;let t=process.env.APPDATA;return Boolean(t&&Na(Zd(t,"gcloud","application_default_credentials.json")))}async function yh(e){try{let t=await kh(["gcloud","config","get-value",e],xh);if(t.code!==0)return"";let r=t.stdout.trim();if(r===""||r==="(unset)")return"";return r}catch{return""}}async function kh(e,t){let r=eu({cmd:e,stdout:"pipe",stderr:"ignore",env:{...process.env,CLOUDSDK_CORE_DISABLE_PROMPTS:"1",CLOUDSDK_CORE_DISABLE_USAGE_REPORTING:"true"}}),n=r.stdout?new Response(r.stdout).text():Promise.resolve("");try{let[o,i]=await Ch(Promise.all([n,r.exited]),t);return{code:i??1,stdout:o}}catch{return r.kill(),{code:1,stdout:""}}}function Ch(e,t){return new Promise((r,n)=>{let o=setTimeout(()=>n(Error("timeout")),t);e.then((i)=>{clearTimeout(o),r(i)},(i)=>{clearTimeout(o),n(i)})})}import{createHash as Z_}from"crypto";import{join as eE}from"path";import{GoogleAuth as Ka,Impersonated as tE}from"google-auth-library";var{spawn:su}=globalThis.Bun;import{existsSync as Ba,readdirSync as N_,readFileSync as Rh,unlinkSync as H_}from"fs";import{join as Wi}from"path";var au="keychain-index.json",no="devctl.credentials",ou=".cred.json";function iu(e){return Wi(Sr(),`${ja(e)}${ou}`)}function Wa(e){if(e==="file"||process.env.DEVCTL_HOME&&e!=="keychain")return new Ha;if(e==="keychain"||process.platform==="darwin"||process.platform==="win32"||process.platform==="linux")return new Ph(new Ha);return new Ha}class Ha{backend="file";async get(e){let t=G_(e);if(!t||t.accessToken==="")return;return t}async set(e,t){kr(iu(e),`${JSON.stringify({...j_(t),key:e})}
628
- `)}async delete(e){let t=iu(e);if(Ba(t))H_(t)}async list(){let e=Sr();if(!Ba(e))return[];let t=[];for(let r of N_(e)){if(!r.endsWith(ou))continue;let n=r.slice(0,-ou.length),o=_h(Wi(Sr(),r));if(!o)continue;t.push(Eh(o.key??n,o.record))}return t.sort((r,n)=>r.identity.localeCompare(n.identity))}}class Ph{backend="keychain";fallback;constructor(e){this.fallback=e}async get(e){let t=await U_(e);if(t)return t;return this.fallback.get(e)}async set(e,t){if(!await K_(e,t))await this.fallback.set(e,t);B_(e)}async delete(e){await q_(e),await this.fallback.delete(e),W_(e)}async list(){let e=new Set(cu()),t=await this.fallback.list(),r=new Map;for(let n of t)r.set(n.key,n);for(let n of e){let o=await this.get(n);if(o)r.set(n,Eh(n,o))}return[...r.values()].sort((n,o)=>n.identity.localeCompare(o.identity))}}function B_(e){let t=cu();if(!t.includes(e))kr(Wi(Sr(),au),`${JSON.stringify([...t,e])}
629
- `)}function W_(e){let t=cu(),r=t.filter((n)=>n!==e);if(r.length!==t.length)kr(Wi(Sr(),au),`${JSON.stringify(r)}
630
- `)}function cu(){let e=Wi(Sr(),au);if(!Ba(e))return[];try{let t=JSON.parse(Rh(e,"utf8"));return Array.isArray(t)?t.filter((r)=>typeof r==="string"):[]}catch{return[]}}function j_(e){return{...e,accessToken:""}}function G_(e){return _h(iu(e))?.record}function _h(e){if(!Ba(e))return;try{let t=JSON.parse(Rh(e,"utf8"));if(typeof t.identity!=="string")return;return{key:typeof t.key==="string"&&t.key!==""?t.key:void 0,record:{identity:t.identity,audience:typeof t.audience==="string"?t.audience:"",scopes:Array.isArray(t.scopes)?t.scopes.filter((r)=>typeof r==="string"):[],accessToken:typeof t.accessToken==="string"?t.accessToken:"",tokenType:typeof t.tokenType==="string"?t.tokenType:"Bearer",expiresAt:typeof t.expiresAt==="string"?t.expiresAt:""}}}catch{return}}function Eh(e,t){let r=Date.parse(t.expiresAt);return{key:e,identity:t.identity,audience:t.audience,scopes:t.scopes,expires_at:t.expiresAt,valid:Number.isFinite(r)&&r-Date.now()>0}}function ja(e){return e.replace(/[^A-Za-z0-9._-]+/g,"_").slice(0,80)||"token"}async function U_(e){let t=await V_(e);if(!t)return;try{return JSON.parse(t)}catch{return}}async function K_(e,t){let r=JSON.stringify(t);if(process.platform==="darwin")return await qo(["security","delete-generic-password","-s",no,"-a",e]),await qo(["security","add-generic-password","-s",no,"-a",e,"-w",r,"-U"])===0;if(process.platform==="linux")return await J_(["secret-tool","store","--label",`devctl ${e}`,"service",no,"account",e],r)===0;if(process.platform==="win32"){let o=`Add-Type -AssemblyName System.Security; $bytes = [Convert]::FromBase64String('${Buffer.from(r,"utf8").toString("base64")}'); $enc = [System.Security.Cryptography.ProtectedData]::Protect($bytes, $null, 'CurrentUser'); [IO.File]::WriteAllBytes($env:LOCALAPPDATA + '\\devctl-cred-${ja(e)}.bin', $enc)`;return await qo(["powershell","-NoProfile","-Command",o])===0}return!1}async function q_(e){if(process.platform==="darwin"){await qo(["security","delete-generic-password","-s",no,"-a",e]);return}if(process.platform==="linux"){await qo(["secret-tool","clear","service",no,"account",e]);return}if(process.platform==="win32")await qo(["powershell","-NoProfile","-Command",`Remove-Item -ErrorAction SilentlyContinue ($env:LOCALAPPDATA + '\\devctl-cred-${ja(e)}.bin')`])}async function V_(e){if(process.platform==="darwin"){let t=await nu(["security","find-generic-password","-s",no,"-a",e,"-w"]);return t.code===0&&t.stdout!==""?t.stdout:void 0}if(process.platform==="linux"){let t=await nu(["secret-tool","lookup","service",no,"account",e]);return t.code===0&&t.stdout!==""?t.stdout:void 0}if(process.platform==="win32"){let t=`$p = $env:LOCALAPPDATA + '\\devctl-cred-${ja(e)}.bin'; if (-not (Test-Path $p)) { exit 1 }; Add-Type -AssemblyName System.Security; $raw = [IO.File]::ReadAllBytes($p); $dec = [System.Security.Cryptography.ProtectedData]::Unprotect($raw, $null, 'CurrentUser'); [Text.Encoding]::UTF8.GetString($dec)`,r=await nu(["powershell","-NoProfile","-Command",t]);return r.code===0&&r.stdout!==""?r.stdout:void 0}return}var lu=3000;async function qo(e){try{return await su({cmd:e,stdout:"ignore",stderr:"ignore",timeout:lu}).exited}catch{return 1}}async function J_(e,t){try{return await su({cmd:e,stdin:new TextEncoder().encode(t),stdout:"ignore",stderr:"ignore",timeout:lu}).exited}catch{return 1}}async function nu(e){try{let t=su({cmd:e,stdout:"pipe",stderr:"ignore",timeout:lu}),r=t.stdout?await new Response(t.stdout).text():"";return{code:await t.exited,stdout:r.trim()}}catch{return{code:1,stdout:""}}}te();te();var z_=3,Y_=200;async function Th(e,t={}){let r=t.attempts??z_,n=t.backoffMs??Y_,o=t.retry??X_,i=Error("retry exhausted");for(let s=0;s<r;s+=1)try{return await e()}catch(a){if(i=a,!o(a)||s===r-1)throw a;await Q_(n*(s+1))}throw i}function X_(e){if(e instanceof Qe)return e.kind!==ee&&e.kind!==qn;return!0}function Q_(e){return new Promise((t)=>{setTimeout(t,e)})}var Ga={now:()=>new Date,isoNow:()=>new Date().toISOString(),unixMs:()=>Date.now()};var du=300000,rE=3000000,nE=3,oE=200,Ua="https://www.googleapis.com/auth/cloud-platform",iE="https://www.googleapis.com/auth/userinfo.email";function Ah(e,t,r){return`${e}|${t}|${r.join(",")}`}function sE(e,t=du,r=Date.now()){if(e.accessToken==="")return!1;return e.expiresAt.getTime()-r>=t}function aE(e,t=du,r=Date.now()){if(e.accessToken==="")return!0;let n=e.expiresAt.getTime()-r;return n>0&&n<t}class oo{cache=new Map;inflight=new Map;providers;thresholdMs;bus;store;clock;constructor(e,t,r,n,o=Ga){this.thresholdMs=e>0?e:du,this.providers=[...t],this.bus=r,this.store=n??Wa(process.env.DEVCTL_CREDENTIAL_BACKEND==="file"?"file":void 0),this.clock=o}replaceProviders(e){this.providers.splice(0,this.providers.length,...e)}isValid(e){return sE(e,this.thresholdMs,this.clock.unixMs())}expiresSoon(e){return aE(e,this.thresholdMs,this.clock.unixMs())}invalidate(e){if(e===void 0||e===""){this.cache.clear(),this.clearStore();return}this.cache.delete(e),this.store.delete(e)}async listStatus(){return this.store.list()}storeBackend(){return this.store.backend}async get(e,t,r){let n=Ah(e,t,r),o=this.cache.get(n)??await this.loadStored(n);if(o&&this.isValid(o))return this.cache.set(n,o),o;return this.refresh(e,t,r)}async refresh(e,t,r){let n=Ah(e,t,r),o=this.inflight.get(n);if(o)return o;let i=this.refreshOnce(n,e,t,r);this.inflight.set(n,i);try{return await i}finally{this.inflight.delete(n)}}async refreshOnce(e,t,r,n){let o=this.providers.filter((s)=>!s.accepts||s.accepts(t,r,n));if(o.length===0)throw M(Qr,`no token provider accepts identity ${t}`);let i=M(Qr,"no token provider available");for(let s of o)try{let a=await Th(()=>s.fetch(t,r,n),{attempts:nE,backoffMs:oE,retry:wE});return this.cache.set(e,a),lE(e,a),await this.store.set(e,dE(a)),this.bus?.publish(Se(Lo,"",{identity:a.identity,audience:a.audience})),a}catch(a){i=a instanceof Error?a:Error(String(a))}throw this.bus?.publish(Se(jn,"",{identity:t,audience:r,error:A(i)})),i}async loadStored(e){let t=await this.store.get(e);if(!t||t.accessToken==="")return;return uE(t)}async clearStore(){let e=await this.store.list();await Promise.all(e.map((t)=>this.store.delete(t.key)))}}function cE(e){let t=Z_("sha256").update(e).digest("hex").slice(0,16);return eE(Sr(),`${t}.json`)}function lE(e,t){let r={identity:t.identity,audience:t.audience,expires_at:t.expiresAt.toISOString(),scopes:t.scopes};kr(cE(e),`${JSON.stringify(r)}
631
- `)}function dE(e){return{identity:e.identity,audience:e.audience,scopes:e.scopes,accessToken:e.accessToken,tokenType:e.tokenType,expiresAt:e.expiresAt.toISOString()}}function uE(e){return{accessToken:e.accessToken,tokenType:e.tokenType||"Bearer",expiresAt:new Date(e.expiresAt),audience:e.audience,identity:e.identity,scopes:e.scopes}}function Rn(){return[mE(),fE(),pE()]}function pE(){return{name:"user",accepts:(e,t)=>!e.startsWith("sa:")&&t==="",fetch:async(e,t,r)=>{if(e.startsWith("sa:"))throw M(Qr,"user provider cannot mint service-account tokens");return vE(e,t,r)}}}function fE(){return{name:"service_account",accepts:(e,t)=>e.startsWith("sa:")&&t==="",fetch:async(e,t,r)=>{if(!e.startsWith("sa:"))throw M(Qr,"service-account provider cannot mint user tokens");return gE(e,t,r)}}}function mE(){return{name:"iap",accepts:(e,t)=>t!=="",fetch:async(e,t,r)=>{if(t==="")throw M(Qr,"IAP audience is required");if(e.startsWith("sa:"))return hE(e,t,r);return yE(e,t,r)}}}async function gE(e,t,r){let n=e.slice(3);try{Uo();let i=await new Ka({scopes:r.length>0?r:[Ua]}).getClient(),s=new tE({sourceClient:i,targetPrincipal:n,targetScopes:r.length>0?r:[Ua],lifetime:3600}),a=await s.getAccessToken(),c=typeof a==="string"?a:a?.token;if(!c)throw M(Qr,"empty impersonated token");return{accessToken:c,tokenType:"Bearer",expiresAt:qa(c,s),audience:t,identity:e,scopes:r}}catch(o){throw Ko(o)}}async function hE(e,t,r){let n=e.slice(3);try{Uo();let i=await new Ka({scopes:[Ua]}).getClient(),s=`https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${encodeURIComponent(n)}:generateIdToken`,c=(await i.request({url:s,method:"POST",data:{audience:t,includeEmail:!0}})).data.token;if(!c)throw M(Qr,"empty impersonated IAP id token");return{accessToken:c,tokenType:"Bearer",expiresAt:qa(c),audience:t,identity:e,scopes:r}}catch(o){throw Ko(o)}}async function yE(e,t,r){try{Uo();let i=await(await new Ka({scopes:r.length>0?r:[iE]}).getIdTokenClient(t)).idTokenProvider.fetchIdToken(t);return{accessToken:i,tokenType:"Bearer",expiresAt:qa(i),audience:t,identity:e,scopes:r}}catch(n){throw Ko(n)}}async function vE(e,t,r){try{Uo();let o=await new Ka({scopes:r.length>0?r:[Ua]}).getClient(),i=await o.getAccessToken();if(!i.token)throw M(Qr,"empty access token");return{accessToken:i.token,tokenType:"Bearer",expiresAt:xE(o,i.token),audience:t,identity:e,scopes:r}}catch(n){throw Ko(n)}}function qa(e,t){if(t?.credentials?.expiry_date)return new Date(t.credentials.expiry_date);return bE(e)??new Date(Date.now()+rE)}function xE(e,t){return qa(t,e)}function bE(e){let t=e.split(".");if(t.length<2||!t[1])return;try{let r=JSON.parse(Buffer.from(t[1],"base64url").toString("utf8"));if(typeof r.exp==="number")return new Date(r.exp*1000)}catch{return}return}function wE(e){if(e instanceof Qe)return e.kind!==ee&&e.kind!==qn;let t=e instanceof Error?e.message.toLowerCase():String(e).toLowerCase();return t.includes("network")||t.includes("econnreset")||t.includes("etimedout")||t.includes("503")||t.includes("429")}te();Fr();var Gi=4000,SE=8000;function Vo(e){let t=e?.tokens,r=()=>(t??=new oo(60000,Rn(),void 0,Wa("file")),t);return{detectGoogle:ro,hasCommand:ru,portAvailable:Fo,hasLocalAdc:Bi,adcQuotaProject:tu,mintToken:async(n,o)=>{await r().get(n,o,[])},probeServiceUsage:async(n,o)=>{let i=await Ui(r().get("user","",[]),Gi),s=`https://serviceusage.googleapis.com/v1/projects/${n}/services/${o}`,a=await fetch(s,{headers:{Authorization:`Bearer ${i.accessToken}`},signal:AbortSignal.timeout(Gi)});if(!a.ok)return!1;return(await a.json().catch(()=>{return}))?.state==="ENABLED"},containerRuntimeAvailable:async(n)=>{try{return await Bun.spawn({cmd:[n,"info"],stdout:"ignore",stderr:"ignore",stdin:"ignore"}).exited===0}catch{return!1}}}}var Va=Vo();function Ja(e=Vo()){return{run:(t,r,n)=>kE(t,e,r,n)}}async function kE(e,t=Va,r,n){let o={checks:[],issues:0},i="Preparing diagnostics",s=()=>r?.({active:i,checks:[...o.checks]}),a=(p)=>{i=p,s()},c=(p)=>{if(o.checks.push(p),p.severity==="error")o.issues+=1;s()},l=[...new Set(Object.values(e.services).flatMap((p)=>p.container?[p.container.runtime||"docker"]:[]))];for(let p of l){a(`${p} container runtime`);let m=await t.hasCommand(p),g=m&&await(t.containerRuntimeAvailable??Va.containerRuntimeAvailable)(p);c(g?{name:`${p} container runtime`,severity:"ok",message:`${p} daemon reachable`}:{name:`${p} container runtime`,severity:"error",message:m?`${p} daemon is not reachable`:`${p} not found`,hint:`install and start ${p}`})}if(a("Google CLI installed"),await t.hasCommand("gcloud"))c({name:"Google CLI installed",severity:"ok",message:"gcloud found"});else c({name:"Google CLI installed",severity:Hi(e)?"error":"warn",message:"gcloud not installed",hint:"install the Google Cloud CLI from https://cloud.google.com/sdk/docs/install"});a("Google environment");let d=await t.detectGoogle(e.google.project_id);if(d.adcAvailable)c({name:"Google authentication available",severity:"ok",message:"Application Default Credentials found"});else c({name:"Google authentication available",severity:Hi(e)?"error":"warn",message:"ADC unavailable",hint:"run `gcloud auth application-default login`"});if(d.projectID!=="")c({name:"Project configured",severity:"ok",message:`${d.projectID} (source: ${d.projectSource})`});else c({name:"Project configured",severity:Hi(e)?"error":"warn",message:"no Google project configured",hint:"set google.project_id in .devctl/config.yaml"});if(Hi(e)||Object.values(e.services).some((p)=>p.capabilities.includes("google")||p.identity.type!=="")){a("Live Google access");let p=!0,m=(g)=>{if(p)c(g)};try{await Ui((async()=>{await RE(e,m,t,d.userEmail),await CE(e,m,t)})(),t.liveDeadlineMs??SE)}catch(g){m({name:"Live Google probes",severity:"warn",message:A(g),hint:"network or timeout \u2014 retry when online"})}finally{p=!1}}for(let p of e.doctor.tools){let m=p.command||p.name;if(a(`${p.name} installed`),await t.hasCommand(m))c({name:`${p.name} installed`,severity:"ok",message:`${m} found`});else c({name:`${p.name} installed`,severity:"error",message:`${m} not found`,hint:`install ${p.name} and ensure it is on PATH`})}if(a("Repository configuration"),n?.repositoryConfigError!==void 0)if(n.repositoryConfigError==="")c({name:"Repository configuration",severity:"ok",message:"valid"});else c({name:"Repository configuration",severity:"error",message:n.repositoryConfigError});else try{let p=Xn(e);if(p.length>0)c({name:"Repository configuration",severity:"error",message:p.join("; ")});else c({name:"Repository configuration",severity:"ok",message:"valid"})}catch(p){c({name:"Repository configuration",severity:"error",message:A(p)})}let f={};for(let[p,m]of Object.entries(e.services))for(let g of m.ports){if(g.auto)continue;let y=`Port ${g.value}`;if(a(y),f[g.value])c({name:y,severity:"error",message:`configured on both ${f[g.value]} and ${p}`});else{f[g.value]=p;let x=n?.services?.[p],h=Boolean(m.container&&x&&["STARTING","RUNNING","HEALTHY","UNHEALTHY"].includes(x.state??""));if(Boolean(x&&(x.pid>0||h)&&Object.values(x.ports).includes(g.value)))c({name:y,severity:"ok",message:`in use by running service ${p}`});else if(await t.portAvailable(g.value))c({name:y,severity:"ok",message:"available"});else c(await Dh(y,g.value,`services.${p}.ports`))}}if(e.proxy.listen.port>0){let p=`Port ${e.proxy.listen.port}`;if(a(p),n?.proxyRunning)c({name:p,severity:"ok",message:"in use by the running proxy"});else if(await t.portAvailable(e.proxy.listen.port))c({name:p,severity:"ok",message:"proxy listen port available"});else c(await Dh(p,e.proxy.listen.port,"proxy.listen.port"))}return i="Diagnostics complete",s(),o}async function CE(e,t,r){let n=e.google.project_id,o=r.hasLocalAdc??Bi;if(n===""||!o())return;let i=r.probeServiceUsage??Va.probeServiceUsage,s=[{name:"IAM Credentials API",service:"iamcredentials.googleapis.com"},{name:"Resource Manager API",service:"cloudresourcemanager.googleapis.com"},{name:"IAP API",service:"iap.googleapis.com"}];(await Promise.allSettled(s.map((c)=>Ui(i(n,c.service),Gi)))).forEach((c,l)=>{let d=s[l];if(!d)return;if(c.status==="fulfilled"){t({name:d.name,severity:c.value?"ok":"warn",message:c.value?"enabled":"not enabled or unreachable",hint:c.value?void 0:`enable ${d.service} in the Google Cloud console \u2014 doctor never auto-enables APIs`});return}t({name:d.name,severity:"warn",message:A(c.reason),hint:`enable ${d.service} in the Google Cloud console \u2014 doctor never auto-enables APIs`})})}async function RE(e,t,r,n){let o=$r(e),i=e.proxy.routes.filter((u)=>u.auth.type.toLowerCase()==="iap"),s=i.filter((u)=>u.auth.audience.trim()!=="");for(let u of i)if(u.auth.audience.trim()==="")t({name:`IAP audience ${u.name}`,severity:"error",message:"missing audience",hint:"set auth.audience to the IAP OAuth client ID"});if(o.length===0&&s.length===0)return;if(!(r.hasLocalAdc??Bi)())return;let c=r.mintToken??Va.mintToken,l=o.map(async(u)=>{try{await Ui(c(`sa:${u}`,""),Gi),t({name:`Impersonate ${u}`,severity:"ok",message:"token minted"})}catch(f){let p=(r.adcQuotaProject??tu)(),m=n||"the ADC principal",g=p===""?"unknown":p,y=Lh(`Impersonate ${u}`,f,`grant roles/iam.serviceAccountTokenCreator to ${m} on ${u}; ADC quota project: ${g}`);if(p!==""&&y.message.toLowerCase().includes("api is not enabled"))y.message=`IAM Credentials API is disabled in ADC quota project ${p}`,y.hint=`enable iamcredentials.googleapis.com in ${p}, or set the ADC quota project to ${e.google.project_id}`;else if(y.message.toLowerCase()==="google authentication failed")y.message=`access-token mint failed: ${m} \u2192 ${u}`,y.hint=`verify roles/iam.serviceAccountTokenCreator for ${m} on this service account; ADC quota project: ${g}`;t(y)}}),d=s.map(async(u)=>{let f=to(u.auth).kind===eo?`sa:${to(u.auth).serviceAccount}`:"user";try{await Ui(c(f,u.auth.audience),Gi),t({name:`IAP ${u.name}`,severity:"ok",message:"id token minted"})}catch(p){t(Lh(`IAP ${u.name}`,p,"check IAP OAuth client ID and ADC"))}});await Promise.all([...l,...d])}function Lh(e,t,r){let n=A(t),o=n.toLowerCase();if(o.includes("timeout")||o.includes("network")||o.includes("econnrefused")||o.includes("enotfound"))return{name:e,severity:"warn",message:n,hint:"network or timeout \u2014 retry when online"};if(t instanceof Qe&&t.hint!==""){let i=`${t.kind}: `,s=t.message.startsWith(i)?t.message.slice(i.length):t.message;return{name:e,severity:"error",message:s,hint:t.hint}}return{name:e,severity:"error",message:n,hint:r}}function Ui(e,t){return new Promise((r,n)=>{let o=setTimeout(()=>n(Error("timeout")),t);e.then((i)=>{clearTimeout(o),r(i)},(i)=>{clearTimeout(o),n(i)})})}async function Dh(e,t,r){let n=await Vn(t);if(!n)return{name:e,severity:"error",message:"already in use",hint:`stop the process using port ${t} or change ${r}`};if(n.pid===process.pid)return{name:e,severity:"error",message:`in use by this TUI (${n.command} pid ${n.pid})`,hint:"stop the proxy from the proxy screen (x), then rerun doctor"};return{name:e,severity:"error",message:`in use by ${n.command} (pid ${n.pid})`,hint:"enter stop that process after a confirmation",action:{kind:"free-port",holder:n}}}function Mh(e){let t=[`${er()} doctor`,""];for(let r of e.checks){let n=r.severity==="error"?"\u2717":r.severity==="warn"?"!":"\u2713";if(t.push(`${n} ${r.name}`),r.severity!=="ok"){if(t.push(` ${r.message}`),r.hint)t.push(` \u2192 ${r.hint}`)}}return t.push("",`${e.issues} issue(s) found.`),t.join(`
2166
+ `,{mode:384})}function rh(e){let t=Ii(e)&&kd(e).isDirectory()?e:Zm(e);if(Mr(t),process.platform==="darwin"){let r=Ii(e)&&kd(e).isFile()?["-R",e]:[t];wd("open",r,{detached:!0,stdio:"ignore"}).unref();return}if(process.platform==="win32"){wd("explorer",[t],{detached:!0,stdio:"ignore"}).unref();return}wd("xdg-open",[t],{detached:!0,stdio:"ignore"}).unref()}function nh(e=Js()){if(!Ii(e))return[];return Cd(e).filter((t)=>t.startsWith(Pd)).sort().reverse()}function oh(e,t=Js()){let r=qn(t,e);if(!Ii(r))return[];let n=[];for(let i of Cd(r)){if(!i.endsWith(".log"))continue;let s=lR(qn(r,i),"utf8");for(let a of s.split(`
2167
+ `)){if(a.trim()==="")continue;let c=a.split(" "),l=c.slice(3).join(" "),d=gd(l);n.push({timestamp:c[0]??"",service:c[1]??i.replace(/\.log$/,""),source:"history",level:c[2]??"INFO",message:d?.message??l,raw:d?l:void 0,pid:0,seq:0})}}let o=n.sort((i,s)=>i.timestamp.localeCompare(s.timestamp));return o.forEach((i,s)=>{i.seq=s+1}),o}function yR(e){let t=e.replace(/[^A-Za-z0-9._-]+/g,"_");return t===""?"service":t}function xR(e,t,r){if(!Ii(e))return;let n=Cd(e).filter((i)=>i.startsWith(Pd)).map((i)=>{let s=qn(e,i),a=kd(s);return{path:s,mtime:a.mtimeMs}}).sort((i,s)=>s.mtime-i.mtime),o=t>0?Date.now()-t*86400000:0;n.forEach((i,s)=>{let a=o>0&&i.mtime<o,c=r>0&&s>=r;if(a||c)dR(i.path,{recursive:!0,force:!0})})}te();var{spawn:Ad}=globalThis.Bun;import{createServer as uh}from"net";var bR=65535,Td=1;async function ph(e,t,r={}){let n={};for(let[i,s]of Object.entries(r))for(let a of Object.values(s))n[a]=i;let o={};for(let[i,s]of Object.entries(e.services)){if(t.length>0&&!t.includes(i))continue;let a={};for(let c of s.ports){let l=r[i]?.[c.name],d=l&&l>=Td?l:c.value;if(l===void 0&&c.auto)d=await wR();if(d<Td||d>bR)throw new Ve(Q,`invalid port ${d} on service ${i}`,{service:i});if(n[d]&&n[d]!==i)throw new Ve(Q,`duplicate port ${d} used by ${n[d]} and ${i}`,{service:i});if(l!==d&&!c.auto&&!await jo(d))throw await SR(i,c.name,d);n[d]=i,a[c.name]=d}o[i]=a}return o}function wR(){return new Promise((e,t)=>{let r=uh();r.listen(0,"127.0.0.1",()=>{let n=r.address();r.close(()=>{if(n&&typeof n==="object")e(n.port);else t(Pe(Q,"unable to allocate dynamic port",Error("no address")))})}),r.on("error",(n)=>t(Pe(Q,"unable to allocate dynamic port",n)))})}async function ya(e){let t=e.ports.filter((n)=>!n.auto&&n.value>=Td);if(t.length===0)return;let r={};for(let n of t){if(await jo(n.value))return;r[n.name]=n.value}return r}function jo(e){return new Promise((t)=>{let r=uh();r.listen(e,"127.0.0.1",()=>{r.close(()=>t(!0))}),r.on("error",()=>t(!1))})}async function SR(e,t,r){let n=await Yn(r);return kR(e,t,r,n)}function kR(e,t,r,n){let o=t===""?`${e} port`:`${e} ports.${t}`;if(n&&n.pid===process.pid)return Te(He,`${e} blocked: this TUI already holds port ${r}`,`Stop the proxy from the Proxy screen, or change ${o} in .devctl.`,e);if(n)return Te(He,`${e} blocked: ${n.command} (pid ${n.pid}) is using port ${r}`,`Open Doctor and press enter to free port ${r}, or change ${o} in .devctl.`,e);return Te(He,`${e} blocked: port ${r} is already in use`,`Something is listening on ${r}. Open Doctor to free it, or change ${o} in .devctl.`,e)}function CR(e,t){let r=e.split(`
2168
+ `).filter((n)=>n.trim()!==""&&!n.startsWith("COMMAND"));for(let n of r){let o=n.trim().split(/\s+/),i=o[0]??"",s=Number(o[1]);if(i!==""&&Number.isInteger(s)&&s>0)return{port:t,pid:s,command:i}}return}async function Yn(e){if(process.platform==="win32")return ER(e);let t=await PR(e);if(t)return t;return RR(e)}async function PR(e){try{let t=Ad({cmd:["lsof","-nP",`-iTCP:${e}`,"-sTCP:LISTEN"],stdout:"pipe",stderr:"ignore"}),r=await new Response(t.stdout).text();return await t.exited,CR(r,e)}catch{return}}async function RR(e){let t;try{let o=Ad({cmd:["fuser","-n","tcp",String(e)],stdout:"pipe",stderr:"pipe"});t=`${await new Response(o.stdout).text()} ${await new Response(o.stderr).text()}`,await o.exited}catch{return}let r=t.match(/(\d+)/),n=r?Number(r[1]):0;if(!Number.isInteger(n)||n<=0)return;return{port:e,pid:n,command:"process"}}function _R(e,t){for(let r of e.split(`
2169
+ `)){if(!r.includes("LISTENING")&&!r.includes("LISTEN"))continue;if(!r.includes(`:${t}`)&&!r.includes(` ${t} `))continue;let n=r.trim().match(/(\d+)\s*$/),o=n?Number(n[1]):0;if(Number.isInteger(o)&&o>0)return{port:t,pid:o,command:"process"}}return}async function ER(e){let t=Ad({cmd:["netstat","-ano"],stdout:"pipe",stderr:"ignore"}),r=t.stdout?await new Response(t.stdout).text():"";return await t.exited,_R(r,e)}var TR=400;async function xa(e){if(e.pid===process.pid)throw Te(He,`port ${e.port} is held by this TUI`,"stop the proxy from the proxy screen, or quit the TUI");try{process.kill(e.pid,"SIGTERM")}catch(t){throw Pe(He,`could not stop pid ${e.pid} on port ${e.port}`,t)}await AR(TR);try{process.kill(e.pid,0),process.kill(e.pid,"SIGKILL")}catch{return}}function AR(e){return new Promise((t)=>{setTimeout(t,e)})}Da();te();import{readdirSync as U_,readFileSync as W_}from"fs";import{basename as Cg,dirname as G_,extname as K_,join as to,resolve as q_}from"path";import{parse as V_}from"yaml";ht();function N(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function Bd(e){return N(e)?new Set(Object.keys(e)):new Set}function G(e){if(typeof e==="string")return e;if(typeof e==="number"||typeof e==="boolean")return String(e);return""}function Re(e){if(typeof e==="number")return e;if(typeof e==="string"){let t=Number(e);return Number.isNaN(t)?0:t}return 0}function ot(e){return e===!0}function Rt(e){if(!Array.isArray(e))return[];return e.map((t)=>G(t))}function Ud(e){if(!Array.isArray(e))return[];return e.map((t)=>N(t)?{service:G(t.service),condition:G(t.condition)||"service_started"}:G(t))}function Sn(e){if(!N(e))return{};let t={};for(let[r,n]of Object.entries(e))t[r]=G(n);return t}function Pr(e){if(typeof e==="string")return{args:e.split(/\s+/).filter((t)=>t!==""),shell:!1};if(Array.isArray(e))return{args:e.map((t)=>G(t)),shell:!1};return Bo()}function Wd(e){if(e===void 0||e===null)return[];if(typeof e==="string"||typeof e==="number")return[jd(e,"http")];if(Array.isArray(e))return e.map((t,r)=>jd(t,`port_${r}`));if(N(e))return Object.entries(e).map(([t,r])=>jd(r,t));return[]}function jd(e,t){if(typeof e==="string"&&e.toLowerCase()==="auto")return{name:t,value:0,auto:!0};return{name:t,value:Re(e),auto:!1}}function kh(e){if(!N(e))return Id();let t={},r=[],n={};for(let[o,i]of Object.entries(e))if(o==="required")r=Rt(i);else if(o==="defaults")n=Sn(i);else t[o]=G(i);return{vars:t,required:r,defaults:n}}function jR(e){if(!N(e))return Od();return{type:G(e.type),mode:G(e.mode),service_account:G(e.service_account),config:N(e.config)?{...e.config}:{}}}function BR(e){if(!N(e))return Md();return{type:G(e.type),url:G(e.url),address:G(e.address),command:Pr(e.command),interval_seconds:Re(e.interval_seconds),timeout_seconds:Re(e.timeout_seconds),start_period_seconds:Re(e.start_period_seconds),unhealthy_threshold:e.unhealthy_threshold===void 0?3:Re(e.unhealthy_threshold),healthy_reset_threshold:e.healthy_reset_threshold===void 0?10:Re(e.healthy_reset_threshold)}}function UR(e){if(!N(e))return{policy:"",max_retries:0,backoff_seconds:0};return{enabled:e.enabled===void 0?void 0:ot(e.enabled),policy:G(e.policy),max_retries:Re(e.max_retries),backoff_seconds:Re(e.backoff_seconds)}}function WR(e){if(!N(e))return{wait_for_healthy:!1,timeout_seconds:0};return{wait_for_healthy:ot(e.wait_for_healthy),timeout_seconds:Re(e.timeout_seconds)}}function Ni(e){if(!N(e))return vn();let t=N(e.logs)?e.logs:{};return{extends:G(e.extends),description:G(e.description),command:Pr(e.command),shell:ot(e.shell),working_dir:G(e.working_dir),dependencies:Ud(e.dependencies),ports:Wd(e.ports),environment:kh(e.environment),health:BR(e.health),identity:jR(e.identity),logs:{stdout:ot(t.stdout),stderr:ot(t.stderr)},restart:UR(e.restart),startup:WR(e.startup),capabilities:Rt(e.capabilities),proxy:qd(e.proxy),expose:Kd(e.expose),container:Gd(e.container),watch:GR(e.watch),hooks:KR(e.hooks)}}function GR(e){if(!N(e))return Ld();return{enabled:ot(e.enabled),paths:Rt(e.paths),debounce_ms:ka(Re(e.debounce_ms)),ignore:e.ignore===void 0?[...Mi]:Rt(e.ignore)}}function KR(e){let t=N(e)?e:{};return{pre_start:Pr(t.pre_start),post_start:Pr(t.post_start)}}function Ch(e){let t=N(e)?e:{};return{command:Pr(t.command),shell:ot(t.shell),working_dir:G(t.working_dir),dependencies:Rt(t.dependencies),environment:kh(t.environment)}}function Gd(e){if(!N(e))return;let t={};if(N(e.ports))for(let[r,n]of Object.entries(e.ports))t[r]=Re(n);return{image:G(e.image),runtime:G(e.runtime),ports:t,env:Sn(e.env),volumes:Rt(e.volumes)}}function Kd(e){if(e===!0)return{enabled:!0,host:"",port:""};if(e===!1)return{enabled:!1,host:"",port:""};if(N(e))return{enabled:e.enabled!==void 0?ot(e.enabled):!0,host:G(e.host),port:G(e.port)};return Dd()}function qd(e){if(e===void 0||e===null)return[];if(Array.isArray(e))return e.map((t)=>Qn(t));if(N(e))return[Qn(e)];return[]}function Ia(e){if(!N(e))return{services:[],environment:{}};return{services:Rt(e.services),environment:Sn(e.environment)}}function qR(e){if(typeof e==="string")return{type:e,service_account:""};if(!N(e))return{type:"",service_account:""};return{type:G(e.type),service_account:G(e.service_account)}}function VR(e){if(!N(e))return Oi();return{type:G(e.type),identity:qR(e.identity),audience:G(e.audience),service_account:G(e.service_account),client_id:G(e.client_id),client_secret:G(e.client_secret)}}function Qn(e){if(!N(e))return{name:"",match:{host:"",path:""},upstream:{url:""},auth:Oi()};let t=N(e.match)?e.match:{},r=N(e.upstream)?e.upstream:{};return{name:G(e.name),match:{host:G(t.host),path:G(t.path)},upstream:{url:G(r.url),service:G(r.service),port:G(r.port)},auth:VR(e.auth)}}te();import{existsSync as zR,statSync as Rh}from"fs";import{basename as Ph,dirname as Ma,isAbsolute as YR,join as Oa,resolve as _h}from"path";function Zn(e,t){if(t!=="")return JR(t);let r=e===""?process.cwd():e;r=YR(r)?r:_h(r);for(;;){let n=Oa(r,Nt,lr);if(Hi(n))return{repoRoot:r,configPath:n};let o=Oa(r,"devctl.yaml");if(Hi(o))return{repoRoot:r,configPath:o};let i=Ma(r);if(i===r)break;r=i}throw Te(Vn,"no devctl configuration found","run `devctl setup` or create a .devctl/config.yaml in the repository root")}function JR(e){let t=_h(e),r;try{r=Rh(t)}catch(o){throw Pe(Vn,"config path not found",o)}if(r.isDirectory()){if(Ph(t)===Nt)return{repoRoot:Ma(t),configPath:Oa(t,lr)};return{repoRoot:t,configPath:Oa(t,lr)}}let n=Ma(t);if(Ph(n)===Nt)n=Ma(n);return{repoRoot:n,configPath:t}}function Hi(e){return zR(e)&&!Rh(e).isDirectory()}ht();function Na(){return{services:{},templates:{},provenance:{}}}function rn(e,t,r,n,o=""){if(N(t)&&Object.keys(t).length>0){for(let[i,s]of Object.entries(t))rn(e,s,r,n,o===""?i:`${o}.${i}`);return}if(o==="")return;(e[o]??=[]).push({source:r,layer:n})}var XR=["environment","restart","startup","health","logs","identity","container","watch","hooks"];function $a(e,t,r){let n=Bd(r);if(N(r))for(let i of XR){let s=r[i];if(N(s))for(let a of Object.keys(s))n.add(`${i}.${a}`)}let o=e[t];e[t]=o?new Set([...o,...n]):n}function Eh(e,t,r=Na(),n={source:"unknown",layer:"unknown"}){if(rn(r.provenance,t,n.source,n.layer),t.version!==void 0)e.version=Re(t.version);if(N(t.project)&&t.project.name!==void 0)e.project.name=G(t.project.name);if(N(t.google)){if(t.google.project_id!==void 0)e.google.project_id=G(t.google.project_id);if(t.google.region!==void 0)e.google.region=G(t.google.region)}if(N(t.profiles))for(let[o,i]of Object.entries(t.profiles)){let s=e.profiles[o];e.profiles[o]=s?ZR(s,i):Ia(i)}if(N(t.templates))for(let[o,i]of Object.entries(t.templates)){let s=e.templates[o];e.templates[o]=s?Fa(s,i):Ni(i),$a(r.templates,o,i)}if(N(t.services))for(let[o,i]of Object.entries(t.services)){let s=e.services[o];e.services[o]=s?Fa(s,i):Ni(i),$a(r.services,o,i)}if(N(t.tasks))for(let[o,i]of Object.entries(t.tasks))e.tasks[o]=e.tasks[o]?e_(e.tasks[o],i):Ch(i);if(N(t.proxy))QR(e.proxy,t.proxy);if(N(t.logs)){if(t.logs.max_memory_events!==void 0)e.logs.max_memory_events=Re(t.logs.max_memory_events);if(N(t.logs.persistence)){let o=t.logs.persistence;if(o.enabled!==void 0)e.logs.persistence.enabled=ot(o.enabled);if(o.directory!==void 0)e.logs.persistence.directory=G(o.directory);if(o.retention_days!==void 0)e.logs.persistence.retention_days=Re(o.retention_days);if(o.max_session_logs!==void 0)e.logs.persistence.max_session_logs=Re(o.max_session_logs)}}if(N(t.auth)&&t.auth.refresh_threshold_seconds!==void 0)e.auth.refresh_threshold_seconds=Re(t.auth.refresh_threshold_seconds);if(N(t.shutdown)){if(t.shutdown.stop_services_on_exit!==void 0)e.shutdown.stop_services_on_exit=ot(t.shutdown.stop_services_on_exit);if(t.shutdown.grace_seconds!==void 0)e.shutdown.grace_seconds=Re(t.shutdown.grace_seconds)}if(N(t.ui)){if(t.ui.theme!==void 0)e.ui.theme=G(t.ui.theme);if(t.ui.keymap!==void 0)e.ui.keymap=Sn(t.ui.keymap)}if(N(t.secrets)){if(t.secrets.extra_markers!==void 0)e.secrets.extra_markers=Rt(t.secrets.extra_markers);if(t.secrets.extra_patterns!==void 0)e.secrets.extra_patterns=Rt(t.secrets.extra_patterns)}if(N(t.doctor)&&Array.isArray(t.doctor.tools))e.doctor.tools=t.doctor.tools.filter(N).map((o)=>({name:G(o.name),command:G(o.command)}));if(Array.isArray(t.plugins))e.plugins=t.plugins.filter(N).map((o)=>({path:G(o.path)})).filter((o)=>o.path!=="");if(N(t.environment)){if(Array.isArray(t.environment.sources))e.environment.sources=Rt(t.environment.sources);if(N(t.environment.secrets))e.environment.secrets={...e.environment.secrets,...Sn(t.environment.secrets)}}}function QR(e,t){if(t.enabled!==void 0)e.enabled=ot(t.enabled);if(t.gateway!==void 0)e.gateway=ot(t.gateway);if(N(t.listen)){if(t.listen.host!==void 0)e.listen.host=G(t.listen.host);if(t.listen.port!==void 0)e.listen.port=Re(t.listen.port)}if(N(t.token_endpoint)){let r=t.token_endpoint;if(r.enabled!==void 0)e.token_endpoint.enabled=ot(r.enabled);if(r.host!==void 0)e.token_endpoint.host=G(r.host);if(r.port!==void 0)e.token_endpoint.port=Re(r.port)}if(Array.isArray(t.routes))e.routes=t.routes.map((r)=>Qn(r))}function ZR(e,t){if(!N(t))return e;return{services:t.services!==void 0?Rt(t.services):e.services,environment:t.environment!==void 0?{...e.environment,...Sn(t.environment)}:e.environment}}function e_(e,t){if(!N(t))return e;return{command:t.command!==void 0?Pr(t.command):e.command,shell:t.shell!==void 0?ot(t.shell):e.shell,working_dir:t.working_dir!==void 0?G(t.working_dir):e.working_dir,dependencies:t.dependencies!==void 0?Rt(t.dependencies):e.dependencies,environment:Th(e.environment,t.environment)}}function Fa(e,t){if(!N(t))return e;let r=Bd(t),n={...e,environment:Th(e.environment,t.environment),health:n_(e.health,t.health),identity:o_(e.identity,t.identity),logs:i_(e.logs,t.logs),restart:s_(e.restart,t.restart),startup:a_(e.startup,t.startup),container:c_(e.container,t.container),watch:t_(e.watch,t.watch),hooks:r_(e.hooks,t.hooks)};if(r.has("extends"))n.extends=G(t.extends);if(r.has("description"))n.description=G(t.description);if(r.has("command"))n.command=Pr(t.command);if(r.has("shell"))n.shell=ot(t.shell);if(r.has("working_dir"))n.working_dir=G(t.working_dir);if(r.has("dependencies"))n.dependencies=Ud(t.dependencies);if(r.has("ports"))n.ports=Wd(t.ports);if(r.has("capabilities"))n.capabilities=Rt(t.capabilities);if(r.has("proxy"))n.proxy=qd(t.proxy);if(r.has("expose"))n.expose=Kd(t.expose);return n}function t_(e,t){if(!N(t))return e;return{enabled:t.enabled!==void 0?ot(t.enabled):e.enabled,paths:t.paths!==void 0?Rt(t.paths):e.paths,debounce_ms:t.debounce_ms!==void 0?ka(Re(t.debounce_ms)):e.debounce_ms,ignore:t.ignore!==void 0?Rt(t.ignore):e.ignore}}function r_(e,t){if(!N(t))return e;return{pre_start:t.pre_start!==void 0?Pr(t.pre_start):e.pre_start,post_start:t.post_start!==void 0?Pr(t.post_start):e.post_start}}function Th(e,t){if(!N(t))return e;let r={...e.vars},n={...e.defaults},o=e.required;for(let[i,s]of Object.entries(t))if(i==="required")o=Rt(s);else if(i==="defaults")Object.assign(n,Sn(s));else r[i]=G(s);return{vars:r,required:o,defaults:n}}function n_(e,t){if(!N(t))return e;return{type:t.type!==void 0?G(t.type):e.type,url:t.url!==void 0?G(t.url):e.url,address:t.address!==void 0?G(t.address):e.address,command:t.command!==void 0?Pr(t.command):e.command,interval_seconds:t.interval_seconds!==void 0?Re(t.interval_seconds):e.interval_seconds,timeout_seconds:t.timeout_seconds!==void 0?Re(t.timeout_seconds):e.timeout_seconds,start_period_seconds:t.start_period_seconds!==void 0?Re(t.start_period_seconds):e.start_period_seconds,unhealthy_threshold:t.unhealthy_threshold!==void 0?Re(t.unhealthy_threshold):e.unhealthy_threshold,healthy_reset_threshold:t.healthy_reset_threshold!==void 0?Re(t.healthy_reset_threshold):e.healthy_reset_threshold}}function o_(e,t){if(!N(t))return e;return{type:t.type!==void 0?G(t.type):e.type,mode:t.mode!==void 0?G(t.mode):e.mode,service_account:t.service_account!==void 0?G(t.service_account):e.service_account,config:t.config!==void 0&&N(t.config)?{...e.config??{},...t.config}:e.config}}function i_(e,t){if(!N(t))return e;return{stdout:t.stdout!==void 0?ot(t.stdout):e.stdout,stderr:t.stderr!==void 0?ot(t.stderr):e.stderr}}function s_(e,t){if(!N(t))return e;return{enabled:t.enabled!==void 0?ot(t.enabled):e.enabled,policy:t.policy!==void 0?G(t.policy):e.policy,max_retries:t.max_retries!==void 0?Re(t.max_retries):e.max_retries,backoff_seconds:t.backoff_seconds!==void 0?Re(t.backoff_seconds):e.backoff_seconds}}function a_(e,t){if(!N(t))return e;return{wait_for_healthy:t.wait_for_healthy!==void 0?ot(t.wait_for_healthy):e.wait_for_healthy,timeout_seconds:t.timeout_seconds!==void 0?Re(t.timeout_seconds):e.timeout_seconds}}function c_(e,t){if(!N(t))return e;let r=Gd(t);if(!r)return e;return{image:t.image!==void 0?r.image:e?.image??"",runtime:t.runtime!==void 0?r.runtime:e?.runtime??"",ports:t.ports!==void 0?{...e?.ports??{},...r.ports}:e?.ports??{},env:t.env!==void 0?{...e?.env??{},...r.env}:e?.env??{},volumes:t.volumes!==void 0?r.volumes:e?.volumes??[]}}function Ah(e,t){let r=Object.keys(e.services).sort();for(let n of r){let o=e.services[n]?.proxy??[];if(o.length===0)continue;o.forEach((i,s)=>{let c={name:o.length===1?n:`${n}-${s+1}`,match:i.match,upstream:i.upstream,auth:i.auth},l=e.proxy.routes.length;if(e.proxy.routes.push(c),t)rn(t,c,`synthesized from services.${n}.proxy`,"synthesized",`proxy.routes.${l}`)})}l_(e,t)}function l_(e,t){if(!e.proxy.enabled)return;let r=new Set(e.proxy.routes.map((n)=>n.name));for(let n of Object.keys(e.services).sort()){let o=e.services[n];if(!o)continue;if(!(o.expose.enabled!==!1&&(o.expose.enabled===!0||e.proxy.gateway&&Xn(o.ports,"http")!==void 0))||r.has(n))continue;let a={name:n,match:{host:o.expose.host||`${n}.local`,path:""},upstream:{url:"",service:n,port:o.expose.port||"http"},auth:Oi()},c=e.proxy.routes.length;if(e.proxy.routes.push(a),r.add(n),t){let l=o.expose.enabled===!0?`services.${n}.expose`:"proxy.gateway";rn(t,a,`synthesized from ${l}`,"synthesized",`proxy.routes.${c}`)}}}function Lh(e,t=Na()){if(Object.keys(e.templates).length===0)return;let r={};for(let[n,o]of Object.entries(e.services))r[n]=Dh(e,o,t.services[n]??new Set,t,{});e.services=r}function Dh(e,t,r,n,o){if(t.extends==="")return t;if(o[t.extends])throw Error(`template cycle involving "${t.extends}"`);let i=e.templates[t.extends]??vn();if(!e.templates[t.extends])throw Error(`service extends unknown template "${t.extends}"`);let s={...o,[t.extends]:!0},a=n.templates[t.extends]??new Set,c=Dh(e,i,a,n,s),l=d_(c,t,r);return l.extends=t.extends,l}function d_(e,t,r){let n={...e};if(r.has("extends"))n.extends=t.extends;if(r.has("description"))n.description=t.description;if(r.has("command"))n.command=t.command;if(r.has("shell"))n.shell=t.shell;if(r.has("working_dir"))n.working_dir=t.working_dir;if(r.has("dependencies"))n.dependencies=t.dependencies;if(r.has("ports"))n.ports=t.ports;if(r.has("capabilities"))n.capabilities=t.capabilities;if(r.has("proxy"))n.proxy=t.proxy;if(r.has("container")){let o=t.container,i=e.container;n.container=o&&i?{image:r.has("container.image")?o.image:i.image,runtime:r.has("container.runtime")?o.runtime:i.runtime,ports:r.has("container.ports")?o.ports:i.ports,env:r.has("container.env")?o.env:i.env,volumes:r.has("container.volumes")?o.volumes:i.volumes}:o??i}if(r.has("watch"))n.watch=t.watch;return n.hooks={pre_start:r.has("hooks.pre_start")?t.hooks.pre_start:e.hooks.pre_start,post_start:r.has("hooks.post_start")?t.hooks.post_start:e.hooks.post_start},n.health={type:r.has("health.type")?t.health.type:e.health.type,url:r.has("health.url")?t.health.url:e.health.url,address:r.has("health.address")?t.health.address:e.health.address,command:r.has("health.command")?t.health.command:e.health.command,interval_seconds:r.has("health.interval_seconds")?t.health.interval_seconds:e.health.interval_seconds,timeout_seconds:r.has("health.timeout_seconds")?t.health.timeout_seconds:e.health.timeout_seconds,start_period_seconds:r.has("health.start_period_seconds")?t.health.start_period_seconds:e.health.start_period_seconds,unhealthy_threshold:r.has("health.unhealthy_threshold")?t.health.unhealthy_threshold:e.health.unhealthy_threshold,healthy_reset_threshold:r.has("health.healthy_reset_threshold")?t.health.healthy_reset_threshold:e.health.healthy_reset_threshold},n.identity={type:r.has("identity.type")?t.identity.type:e.identity.type,mode:r.has("identity.mode")?t.identity.mode:e.identity.mode,service_account:r.has("identity.service_account")?t.identity.service_account:e.identity.service_account,config:r.has("identity.config")?t.identity.config:e.identity.config},n.logs={stdout:r.has("logs.stdout")?t.logs.stdout:e.logs.stdout,stderr:r.has("logs.stderr")?t.logs.stderr:e.logs.stderr},n.restart={enabled:r.has("restart.enabled")?t.restart.enabled:e.restart.enabled,policy:r.has("restart.policy")?t.restart.policy:e.restart.policy,max_retries:r.has("restart.max_retries")?t.restart.max_retries:e.restart.max_retries,backoff_seconds:r.has("restart.backoff_seconds")?t.restart.backoff_seconds:e.restart.backoff_seconds},n.startup={wait_for_healthy:r.has("startup.wait_for_healthy")?t.startup.wait_for_healthy:e.startup.wait_for_healthy,timeout_seconds:r.has("startup.timeout_seconds")?t.startup.timeout_seconds:e.startup.timeout_seconds},n.environment={vars:{...e.environment.vars,...t.environment.vars},defaults:{...e.environment.defaults,...t.environment.defaults},required:r.has("environment.required")?t.environment.required:e.environment.required},n}te();ht();function Ih(e){if(e.version===0)throw I(Q,"version is required");if(e.version===Uo)return e;throw I(Q,`unsupported config version ${e.version} (expected ${Uo}); no migration is available`)}var Mh=["version","project","google","profiles","templates","services","tasks","proxy","logs","auth","shutdown","ui","secrets","doctor","plugins","environment"],Oh=["extends","description","command","shell","working_dir","dependencies","ports","environment","health","identity","logs","restart","startup","capabilities","proxy","expose","container","watch","hooks"],$h=["type","url","address","command","interval_seconds","timeout_seconds","start_period_seconds","unhealthy_threshold","healthy_reset_threshold"],Fh=["service","condition"],Nh=["type","mode","service_account","config"],Hh=["enabled","policy","max_retries","backoff_seconds"],jh=["wait_for_healthy","timeout_seconds"],Bh=["enabled","host","port"],Uh=["enabled","gateway","listen","token_endpoint","routes"],Wh=["host","port"],Vd=["name","match","upstream","auth"],zd=["host","path"],Yd=["url","service","port"],Jd=["type","identity","audience","service_account","client_id","client_secret"],Gh=["max_memory_events","persistence"],Kh=["enabled","directory","retention_days","max_session_logs"],qh=["refresh_threshold_seconds"],Vh=["stop_services_on_exit","grace_seconds"],zh=["theme","keymap"],Yh=["name"],Jh=["project_id","region"],Xh=["services","environment"],Xd=["required","defaults"],Qh=["stdout","stderr"],Zh=["extra_markers","extra_patterns"],eg=["tools"],tg=["name","command"],rg=["enabled","host","port"],ng=["path"],og=["sources","secrets"],ig=["image","runtime","ports","env","volumes"],sg=["enabled","paths","debounce_ms","ignore"],ag=["pre_start","post_start"],cg=["command","shell","working_dir","dependencies","environment"],lg=["google","google_api","iap","network","service_identity","local_http"],dg=["|","||","&&",";",">",">>","<","&"];var u_=2;function kn(e,t){if(Array.isArray(e))return e.flatMap((o,i)=>kn(o,ug(t,String(i))));if(!y_(e))return[];let r=f_(t),n=[];for(let o of Object.keys(e)){let i=ug(t,o);if(r.length>0&&!r.includes(o)&&!p_(t))n.push(i);n.push(...kn(e[o],i))}return n}function p_(e){if(e==="services"||e==="profiles"||e==="templates"||e==="tasks")return!0;if(e.endsWith(".environment")||e.endsWith(".defaults")||e.endsWith(".keymap"))return!0;if(e.endsWith(".container.ports")||e.endsWith(".container.env"))return!0;if(e.endsWith(".identity.config")||e.includes(".identity.config."))return!0;return e.includes(".environment.")&&!e.endsWith(".environment")}function f_(e){switch(e){case"":return Mh;case"project":return Yh;case"google":return Jh;case"proxy":return Uh;case"proxy.listen":return Wh;case"proxy.token_endpoint":return rg;case"logs":return Gh;case"logs.persistence":return Kh;case"auth":return qh;case"shutdown":return Vh;case"ui":return zh;case"secrets":return Zh;case"doctor":return eg;case"environment":return og;default:return m_(e)}}function m_(e){if(e==="services"||e==="profiles"||e==="templates"||e==="tasks")return[];if(e.startsWith("services.")||e.startsWith("templates.")){if(e.includes(".dependencies."))return Fh;return h_(e)}if(e.startsWith("profiles.")){if(e.split(".").length===2)return Xh}if(e.startsWith("tasks.")){let t=e.split(".");if(t.length===2)return cg;if(t[2]==="environment")return Xd}if(e.includes("routes"))return v_(e);if(e.startsWith("doctor.tools")&&e.split(".").length>=3)return tg;if(e==="plugins")return[];if(e.startsWith("plugins.")&&e.split(".").length===2)return ng;return[]}function h_(e){let t=e.split(".");if(t.length===2)return Oh;if(t.length>=3)switch(t[2]){case"health":return $h;case"identity":return Nh;case"restart":return Hh;case"startup":return jh;case"logs":return Qh;case"environment":return Xd;case"proxy":return g_(t);case"expose":return Bh;case"container":return ig;case"watch":return sg;case"hooks":return ag;default:return[]}return[]}function g_(e){let t=e.slice(3),r=t[0]!==void 0&&/^\d+$/.test(t[0])?1:0,n=t[r]??"";if(n==="match")return zd;if(n==="upstream")return Yd;if(n==="auth")return Jd;return Vd}function v_(e){if(e==="proxy.routes")return[];if(e.endsWith(".match"))return zd;if(e.endsWith(".upstream"))return Yd;if(e.endsWith(".auth"))return Jd;if(e.includes("proxy.routes")&&e.split(".").length===u_+1)return Vd;return[]}function ug(e,t){if(e==="")return t;return`${e}.${t}`}function y_(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function Wo(e){return`unknown fields: ${e.join(", ")}`}ht();function Cn(e){let t=e.trim().toLowerCase();if(t==="")return!0;if(t==="localhost"||t==="::1")return!0;if(pg(t))return!0;return x_(t)}function pg(e){let t=e.split(".");if(t.length!==4)return!1;let r=t.map((n)=>Number(n));if(r.some((n)=>!Number.isInteger(n)||n<0||n>255))return!1;return r[0]===127}function x_(e){if(!e.startsWith("::ffff:"))return!1;return pg(e.slice(7))}import{existsSync as S_}from"fs";import{isAbsolute as k_,resolve as C_}from"path";import{fileURLToPath as P_}from"url";ht();function fg(e,t){if(t){if(t.http!==void 0)return t.http;let n=Object.values(t)[0];if(n!==void 0)return n}let r=xh(e.ports);if(r&&!r.auto)return r.value;return}function b_(e,t,r){let n=e,o="";for(;;){let i=n.indexOf("${");if(i<0)return o+n;o+=n.slice(0,i);let s=n.slice(i).indexOf("}");if(s<0)throw Error(`unclosed environment reference in "${e}"`);let a=n.slice(i+2,i+s);o+=w_(a,t,r),n=n.slice(i+s+1)}}function w_(e,t,r){let n=e.split(".");if(n.length<3||n[0]!=="services")throw Error(`unsupported reference \${${e}}`);let o=n[1]??"",i=t.services[o];if(!i)throw Error(`reference \${${e}}: unknown service`);let s=r[o];if(n[2]==="host"||n[2]==="url"){let a=t.proxy.enabled?t.proxy.routes.find((l)=>l.upstream.service===o):void 0;if(n[2]==="host")return a?a.match.host||"127.0.0.1":"127.0.0.1";if(a)return`http://${a.match.host||"127.0.0.1"}:${t.proxy.listen.port}${a.match.path}`;let c=fg(i,s);if(c===void 0)throw Error(`unresolvable reference \${${e}}`);return`http://127.0.0.1:${c}`}if(n[2]==="port"){let a=fg(i,s);if(a!==void 0)return String(a)}if(n[2]==="ports"){let a=n[3];if(!a)throw Error(`reference \${${e}}: missing port name`);if(s&&s[a]!==void 0)return String(s[a]);let c=Xn(i.ports,a);if(c&&!c.auto)return String(c.value);let l=Number.parseInt(a,10);if(!Number.isNaN(l)&&l>=0&&l<i.ports.length){let d=i.ports[l];if(d&&!d.auto)return String(d.value)}}throw Error(`unresolvable reference \${${e}}`)}function Qd(e,t,r){let n={};for(let[o,i]of Object.entries(e))n[o]=b_(i,t,r);return n}function mg(e){let t=[],r=e;for(;;){let n=r.indexOf("${");if(n<0)return t;let o=r.slice(n).indexOf("}");if(o<0)return t;t.push(r.slice(n+2,n+o)),r=r.slice(n+o+1)}}function hg(e,t){let r=e.split(".");if(r.length<2)return!1;if(r[0]!=="services"||r.length<3)return!1;let n=t.services[r[1]??""];if(!n)return!1;if(r[2]==="port"||r[2]==="ports"){if(r.length===3)return n.ports.length>0;if(r.length>=4){let o=r[3]??"";if(!Number.isNaN(Number.parseInt(o,10)))return!0;return Xn(n.ports,o)!==void 0}}return!0}ht();var Ba=65535,Ua=1,wg=["http","tcp","process","command"];function eu(e){let t=[];for(let[r,n]of Object.entries(e.services)){let o=n.health.type;if(o!==""&&!wg.includes(o.toLowerCase()))t.push({service:r,type:o})}return t}var R_=0,xg=1,bg=2;function eo(e){let t=[];if(e.version===0)t.push("version is required");else if(e.version!==Uo)t.push(`unsupported config version ${e.version} (expected ${Uo}); no migration is available`);if(Object.keys(e.services).length===0)t.push("at least one service must be defined");t.push(...E_(e)),t.push(...__(e)),t.push(...D_(e)),t.push(...I_(e)),t.push(...M_(e));for(let[r,n]of e.plugins.entries())if(n.path==="")t.push(`plugins.${r}.path is required`);else try{let o=n.path.startsWith("file:")?P_(n.path):k_(n.path)?n.path:C_(e.repoRoot,n.path);if(!S_(o))t.push(`plugins.${r}.path does not exist: ${n.path}`)}catch{t.push(`plugins.${r}.path is invalid: ${n.path}`)}if(e.logs.max_memory_events<0)t.push("logs.max_memory_events must be >= 0");return t}function __(e){let t=[];for(let[r,n]of Object.entries(e.tasks)){let o=`tasks.${r}`;if(yn(n.command))t.push(`${o}.command is required`);t.push(...ja(o,n.command,n.shell));for(let i of n.dependencies)if(!e.services[i])t.push(`${o}.dependencies: unknown service "${i}"`);t.push(...Sg(`${o}.environment`,n.environment,e))}return t}function E_(e){let t=[],r={};for(let[n,o]of Object.entries(e.services)){let i=`services.${n}`;if(yn(o.command)&&!o.container)t.push(`${i}.command is required`);t.push(...ja(i,o.command,o.shell)),t.push(...ja(`${i}.hooks.pre_start`,o.hooks.pre_start,o.shell)),t.push(...ja(`${i}.hooks.post_start`,o.hooks.post_start,o.shell)),t.push(...H_(i,o.capabilities));for(let c of o.dependencies){let l=zt(c);if(!e.services[l])t.push(`${i}.dependencies: unknown service "${l}"`);if(l===n)t.push(`${i}.dependencies: service cannot depend on itself`);if(!["service_started","service_healthy"].includes(gn(c)))t.push(`${i}.dependencies: condition must be service_started or service_healthy`);if(gn(c)==="service_healthy"&&e.services[l]?.health.type==="")t.push(`${i}.dependencies: service_healthy requires ${l} to define a health check`)}if(o.extends!==""&&!e.templates[o.extends])t.push(`${i}.extends: unknown template "${o.extends}"`);for(let c of o.ports.filter((l)=>!l.auto))if(c.value<Ua||c.value>Ba)t.push(`${i}.ports.${c.name}: invalid port ${c.value}`);else if(r[c.value])t.push(`duplicate port ${c.value} used by ${r[c.value]} and ${n}`);else r[c.value]=n;let s=L_(`${i}.identity`,o.identity,e.plugins.length>0);if(s!=="")t.push(s);if(t.push(...T_(i,o,e.plugins.length>0)),o.health.start_period_seconds<0)t.push(`${i}.health.start_period_seconds must be >= 0`);if(o.health.unhealthy_threshold<1)t.push(`${i}.health.unhealthy_threshold must be >= 1`);if(o.health.healthy_reset_threshold<1)t.push(`${i}.health.healthy_reset_threshold must be >= 1`);let a=Ra(o.restart);if(a!==ba&&a!==wa&&a!==Sa)t.push(`${i}.restart.policy must be never, on_failure, or always`);if(t.push(...Sg(`${i}.environment`,o.environment,e)),o.container){if(o.container.image==="")t.push(`${i}.container.image is required`);if(o.container.runtime!==""&&o.container.runtime!=="docker"&&o.container.runtime!=="podman")t.push(`${i}.container.runtime must be docker or podman`);for(let[c,l]of Object.entries(o.container.ports)){if(!o.ports.some((d)=>d.name===c))t.push(`${i}.container.ports.${c}: no matching service port`);if(l<Ua||l>Ba)t.push(`${i}.container.ports.${c}: invalid container port ${l}`)}}}return t}function T_(e,t,r){let n=[];if(t.health.type==="")return n;let o=t.health.type.toLowerCase();if(!wg.includes(o)&&!r)n.push(`${e}.health.type must be http, tcp, process, or command`);if(o==="http"&&t.health.url==="")n.push(`${e}.health.url is required for http health checks`);if(o==="tcp"&&t.health.address===""&&t.ports.length===0)n.push(`${e}.health.address is required for tcp health checks without ports`);if(o==="command"&&yn({args:t.health.command.args,shell:!1}))n.push(`${e}.health.command is required for command health checks`);return n}var A_=["","user","service","service_account"];function L_(e,t,r){let n=Fi(t).toLowerCase();if(n===""||n==="user")return"";if(n==="service"||n==="service_account"){if(t.service_account==="")return`${e}.service_account is required for service identity`;if(!t.service_account.includes("@"))return`${e}.service_account must be an email`;return""}if(r)return"";return`${e}.type must be user or service_account`}function tu(e){let t=[];for(let[r,n]of Object.entries(e.services)){let o=Fi(n.identity).toLowerCase();if(!A_.includes(o))t.push({service:r,type:o})}return t}function D_(e){let t={},r=[],n=[],o=(i)=>{let s=t[i]??R_;if(s===bg)return;if(s===xg){r.push(`dependency cycle: ${[...n,i].join(" \u2192 ")}`);return}t[i]=xg,n.push(i);let a=e.services[i];if(a)for(let c of a.dependencies){let l=zt(c);if(e.services[l])o(l)}n.pop(),t[i]=bg};for(let i of Object.keys(e.services))o(i);return r}function Sg(e,t,r){let n=[],o=(i,s)=>{for(let a of mg(s))if(!hg(a,r))n.push(`${e}.${i}: unresolvable reference \${${a}}`)};for(let[i,s]of Object.entries(t.vars))o(i,s);for(let[i,s]of Object.entries(t.defaults))o(i,s);return n}function I_(e){let t=[];for(let[r,n]of Object.entries(e.profiles))for(let o of n.services)if(!e.services[o])t.push(`profiles.${r} references unknown service "${o}"`);return t}function M_(e){let t=[];if(e.proxy.listen.host!==""){if(!j_(e.proxy.listen.host))t.push("proxy.listen.host must be an IP address or localhost");else if(!Cn(e.proxy.listen.host))t.push("proxy.listen.host must be a loopback address")}if(e.proxy.enabled&&e.proxy.listen.port===0)t.push("proxy.listen.port is required when proxy.enabled is true");if(e.proxy.listen.port!==0&&(e.proxy.listen.port<Ua||e.proxy.listen.port>Ba))t.push("proxy.listen.port is invalid");let r={};return e.proxy.routes.forEach((n,o)=>{let i=`proxy.routes[${o}]`;if(n.name==="")t.push(`${i}.name is required`);else if(r[n.name])t.push(`${i}: duplicate route name ${n.name}`);r[n.name]=!0,t.push(...O_(n,i,e)),t.push(...$_(n,i))}),t.push(...N_(e)),t}function O_(e,t,r){let n=[],o=e.upstream.url!=="",i=e.upstream.service??"";if(i===""){if(!o)n.push(`${t}.upstream requires either url or service`);return n}let s=r.services[i];if(!s)return n.push(`${t}.upstream.service references unknown service ${i}`),n;let a=e.upstream.port||"http";if(Xn(s.ports,a)===void 0)n.push(`${t}.upstream: service ${i} has no port named ${a}`);return n}function $_(e,t){let r=[];if(e.auth.type.toLowerCase()==="iap"){if(e.auth.audience.trim()==="")r.push(`${t}.auth.audience is required when auth.type is iap`);if(e.auth.identity.type.trim()==="")r.push(`${t}.auth.identity.type is required when auth.type is iap`)}r.push(...F_(e.auth,t));let n=e.auth.identity.type.toLowerCase();if(n==="service"||n==="service_account"||en({type:n,mode:"",service_account:""})){if((e.auth.identity.service_account||e.auth.service_account)==="")r.push(`${t}.auth.identity.service_account is required`)}return r}function F_(e,t){let r=(e.client_id??"").trim(),n=(e.client_secret??"").trim();if(r===""&&n==="")return[];if(e.type.toLowerCase()!=="iap")return[`${t}.auth.client_id is only valid when auth.type is iap`];let o=[];if(r==="")return o.push(`${t}.auth.client_id is required when client_secret is set`),o;if(n==="")o.push(`${t}.auth.client_secret is required when client_id is set`);else if(Zd(n).length>0&&!yg(n))o.push(`${t}.auth.client_secret must be a literal or a single \${NAME} / \${env.NAME} reference`);let i=e.identity.type.toLowerCase();if(i==="service"||i==="service_account")o.push(`${t}.auth.client_id is only valid with identity.type user`);return o}function N_(e){let t=[],r=e.proxy.token_endpoint;if(!r.enabled)return t;let n=r.host||Ca;if(!Cn(n))t.push("proxy.token_endpoint.host must be a loopback address");if(r.port!==0&&(r.port<Ua||r.port>Ba))t.push("proxy.token_endpoint.port is invalid");return t}function ja(e,t,r){if(t.shell||r)return[];for(let n of t.args)if(dg.includes(n)||n.includes("|")||n.includes(";")||n.includes("&&"))return[`${e}.command contains shell metacharacters; set shell: true to run via a shell`];return[]}function H_(e,t){let r=[];for(let n of t)if(!lg.includes(n))r.push(`${e}.capabilities: unknown capability "${n}"`);return r}function j_(e){if(e==="localhost")return!0;return B_(e)}function B_(e){if(/^(\d{1,3}\.){3}\d{1,3}$/.test(e))return e.split(".").every((r)=>{let n=Number(r);return n>=0&&n<=255});return e.includes(":")}function ji(e,t,r){try{return Bi(e,t,{candidateText:r}),[]}catch(n){if(n instanceof Ve){let o=`${n.kind}: `;return[n.message.startsWith(o)?n.message.slice(o.length):n.message]}return[n instanceof Error?n.message:String(n)]}}function nn(e,t){let{repoRoot:r,configPath:n}=Zn(e,t);return Bi(r,n)}function ro(e,t){try{return nn(e,t)}catch(r){if(!va(r,Vn))throw r;let n=e===""?process.cwd():q_(e),o=Jn();return o.repoRoot=n,o.configPath=to(n,Nt,lr),o}}function Bi(e,t,r){let n=Jn(),o=Na();Pg(t,n,o,r?.candidateText,"main"),n.repoRoot=e,n.configPath=t;let i=G_(t);if(Cg(i)===Nt)Y_(i,n,o);n=z_(n,e,t,o),n=Ih(n);try{Lh(n,o)}catch(a){throw Pe(Q,"template merge failed",a)}Ah(n,o.provenance),n.provenance=o.provenance;let s=eo(n);if(s.length>0)throw I(Q,s.join(`
2170
+ `));return n}function z_(e,t,r,n){let o=to(cr(),"config.local.yaml"),i=to(t,Nt,"config.local.yaml");for(let s of[o,i]){if(s===r||!Hi(s))continue;Pg(s,e,n,void 0,s===o?"home_local":"repo_local")}return e}function Pg(e,t,r,n,o="main"){let i=n!==void 0?Rg(n,e):ru(e),s=kn(i,"");if(s.length>0)throw I(Q,Wo(s));Eh(t,i,r,{source:e,layer:o})}function Y_(e,t,r){kg(to(e,"services"),(o,i,s)=>{let a=kn(i,`services.${o}`);if(a.length>0)throw I(Q,Wo(a));let c=t.services[o];t.services[o]=c?Fa(c,i):Ni(i),$a(r.services,o,i),rn(r.provenance,i,s,"modular_service",`services.${o}`)}),kg(to(e,"profiles"),(o,i,s)=>{let a=kn(i,`profiles.${o}`);if(a.length>0)throw I(Q,Wo(a));t.profiles[o]=Ia(i),rn(r.provenance,i,s,"modular_profile",`profiles.${o}`)});let n=to(e,"proxy","routes.yaml");if(Hi(n))J_(n,t,r)}function J_(e,t,r){let n=ru(e);if(N(n.proxy))rn(r.provenance,n.proxy,e,"modular_proxy","proxy");if(Array.isArray(n.routes))rn(r.provenance,n.routes,e,"modular_proxy","proxy.routes");if(N(n.proxy)){let o=kn({proxy:n.proxy},"");if(o.length>0)throw I(Q,Wo(o))}if(Array.isArray(n.routes))n.routes.forEach((o,i)=>{let s=kn(o,`proxy.routes.${i}`);if(s.length>0)throw I(Q,Wo(s))});if(N(n.proxy)){if(Array.isArray(n.proxy.routes))t.proxy.routes.push(...n.proxy.routes.map((o)=>Qn(o)));if(N(n.proxy.listen)){if(typeof n.proxy.listen.host==="string"&&n.proxy.listen.host!=="")t.proxy.listen.host=n.proxy.listen.host;if(typeof n.proxy.listen.port==="number"&&n.proxy.listen.port!==0)t.proxy.listen.port=n.proxy.listen.port}}if(Array.isArray(n.routes))t.proxy.routes.push(...n.routes.map((o)=>Qn(o)))}function kg(e,t){let r;try{r=U_(e)}catch(n){if(X_(n))return;throw n}for(let n of r.sort()){let o=K_(n);if(o!==".yaml"&&o!==".yml")continue;let i=to(e,n),s=ru(i);t(Cg(n,o),s,i)}}function ru(e){let t;try{t=W_(e,"utf8")}catch(r){throw Pe(Q,"unable to read config",r)}return Rg(t,e)}function Rg(e,t){let r;try{r=V_(e)}catch(n){throw Pe(Q,`invalid YAML in ${t}`,n)}if(r===null||r===void 0)return{};if(!N(r))throw I(Q,`unable to decode config: ${t} is not a mapping`);return r}function X_(e){return typeof e==="object"&&e!==null&&"code"in e&&e.code==="ENOENT"}ht();te();jr();import{existsSync as Ka,readFileSync as Z_}from"fs";import{homedir as Lg}from"os";import{join as su}from"path";var{spawn:au}=globalThis.Bun;var eE="https://www.googleapis.com/auth/cloud-platform",Dg=1500,tE=2000;function qo(){let e=globalThis;if(e.window&&typeof e.window.fetch!=="function")e.window.fetch=fetch}function cu(){let e=process.env.GOOGLE_APPLICATION_CREDENTIALS||su(Lg(),".config","gcloud","application_default_credentials.json");if(!Ka(e))return"";try{let t=JSON.parse(Z_(e,"utf8"));return typeof t.quota_project_id==="string"?t.quota_project_id:""}catch{return""}}async function so(e){let t={gcloudInstalled:await lu("gcloud"),adcAvailable:!1,userEmail:"",projectID:"",projectSource:""};if(e!=="")t.projectID=e,t.projectSource="configuration";else{let r=sE("GOOGLE_CLOUD_PROJECT","GCLOUD_PROJECT","GOOGLE_PROJECT");if(r!=="")t.projectID=r,t.projectSource="environment variable";else if(t.gcloudInstalled){let n=await Ag("core/project");if(n!=="")t.projectID=n,t.projectSource="gcloud configuration"}}if(Wi())try{await Fg(rE(t),tE)}catch(r){t.adcAvailable=!1,t.error=Vo(r)}if(t.userEmail===""&&t.gcloudInstalled)t.userEmail=await Ag("core/account");return t}async function rE(e){qo();let{GoogleAuth:t}=await import("google-auth-library"),r=new t({scopes:[eE]});await r.getClient(),e.adcAvailable=!0;let n=await r.getProjectId().catch(()=>"");if(e.projectID===""&&n)e.projectID=n,e.projectSource="application default credentials";let o=await nE(r);if(o!=="")e.userEmail=o}async function nE(e){try{let t=await e.getCredentials();if(t.client_email)return t.client_email}catch{}try{let t=await e.getClient(),r=typeof t.credentials?.id_token==="string"?t.credentials.id_token:"";if(r!==""){let n=oE(r);if(n!=="")return n}}catch{return""}return""}function oE(e){let t=e.split(".");if(t.length<2||!t[1])return"";try{let r=JSON.parse(Buffer.from(t[1],"base64url").toString("utf8"));return typeof r.email==="string"?r.email:""}catch{return""}}async function Ig(e){let t=await so(e);return dr({kind:"user",email:t.userEmail,project:t.projectID,projectSource:t.projectSource,adcAvailable:t.adcAvailable})}async function Mg(){if(await au({cmd:["gcloud","auth","application-default","login"],stdin:"inherit",stdout:"inherit",stderr:"inherit"}).exited!==0)throw Te(hn,"application-default login failed","run `gcloud auth application-default login`")}async function Og(){if(await au({cmd:["gcloud","auth","application-default","revoke","--quiet"],stdout:"ignore",stderr:"pipe"}).exited!==0)throw Te(hn,"failed to revoke application-default credentials","run `gcloud auth application-default revoke`")}function Vo(e){let t=iE(e).toLowerCase();if(io(t,["unauthorized_client"]))return Te(ga,"IAP OAuth client does not match the ADC refresh token","omit client_id to use ADC's default client, or login with an OAuth client that matches auth.client_id");if(io(t,["unauth","invalid_grant","token has been expired","expired"]))return Te(hn,"credential expired or invalid","run `devctl auth login` or `gcloud auth application-default login`");if(io(t,["api has not been used","has not been enabled","access not configured","service_disabled"]))return Te(zn,"required Google API is not enabled","enable the API in the target service account project; devctl will not enable APIs automatically");if(io(t,["permission","forbidden","iam"])){if(io(t,["serviceaccounttoken","iamcredentials","token creator","getaccesstoken"]))return Te(ha,"cannot impersonate service account","ask an administrator to grant roles/iam.serviceAccountTokenCreator on the target service account");return Te(zn,"authorization failure","verify IAM permissions for the current user on this project")}if(t.includes("audience"))return Te(ga,"IAP audience is incorrect","set auth.audience on the proxy route to the IAP OAuth client ID");if(t.includes("iap"))return Pe(ga,"IAP authentication failure",e);if(t.includes("project"))return Te(Q,"wrong or missing Google project","set google.project_id in .devctl/config.yaml");if(io(t,["timeout","network","connection refused","no such host"]))return Pe(lt,"network problem reaching Google Cloud",e);if(io(t,["adc","default credentials","could not find"]))return Te(hn,"application default credentials unavailable","run `gcloud auth application-default login`");return Pe(hn,"Google authentication failed",e)}function iE(e){let t=[],r=new Set,n=(o,i)=>{if(o===null||o===void 0||i>8||r.has(o))return;if(typeof o==="string"){t.push(o);return}if(typeof o!=="object")return;r.add(o);let s=o;for(let a of["message","reason","status","code","error_description","permission","service"])n(s[a],i+1);for(let a of["error","errors","details","response","data","cause","metadata"]){let c=s[a];if(Array.isArray(c))c.forEach((l)=>n(l,i+1));else n(c,i+1)}};return n(e,0),t.length>0?t.join(" "):String(e)}function io(e,t){return t.some((r)=>e.includes(r))}function sE(...e){for(let t of e){let r=process.env[t];if(r)return r}return""}async function lu(e){try{return(await $g(process.platform==="win32"?["where",e]:["which",e],Dg)).code===0}catch{return!1}}function Wi(){let e=process.env.GOOGLE_APPLICATION_CREDENTIALS;if(e&&Ka(e))return!0;if(Ka(su(Lg(),".config","gcloud","application_default_credentials.json")))return!0;let t=process.env.APPDATA;return Boolean(t&&Ka(su(t,"gcloud","application_default_credentials.json")))}async function Ag(e){try{let t=await $g(["gcloud","config","get-value",e],Dg);if(t.code!==0)return"";let r=t.stdout.trim();if(r===""||r==="(unset)")return"";return r}catch{return""}}async function $g(e,t){let r=au({cmd:e,stdout:"pipe",stderr:"ignore",env:{...process.env,CLOUDSDK_CORE_DISABLE_PROMPTS:"1",CLOUDSDK_CORE_DISABLE_USAGE_REPORTING:"true"}}),n=r.stdout?new Response(r.stdout).text():Promise.resolve("");try{let[o,i]=await Fg(Promise.all([n,r.exited]),t);return{code:i??1,stdout:o}}catch{return r.kill(),{code:1,stdout:""}}}function Fg(e,t){return new Promise((r,n)=>{let o=setTimeout(()=>n(Error("timeout")),t);e.then((i)=>{clearTimeout(o),r(i)},(i)=>{clearTimeout(o),n(i)})})}import{createHash as SE}from"crypto";import{join as kE}from"path";import{GoogleAuth as Ki,Impersonated as CE,UserRefreshClient as PE}from"google-auth-library";var{spawn:fu}=globalThis.Bun;import{existsSync as Va,readdirSync as aE,readFileSync as Ng,unlinkSync as cE}from"fs";import{join as Gi}from"path";var mu="keychain-index.json",ao="devctl.credentials",uu=".cred.json";function pu(e){return Gi(kr(),`${Ya(e)}${uu}`)}function za(e){if(e==="file"||process.env.DEVCTL_HOME&&e!=="keychain")return new qa;if(e==="keychain"||process.platform==="darwin"||process.platform==="win32"||process.platform==="linux")return new Hg(new qa);return new qa}class qa{backend="file";async get(e){let t=pE(e);if(!t||t.accessToken==="")return;return t}async set(e,t){Cr(pu(e),`${JSON.stringify({...uE(t),key:e})}
2171
+ `)}async delete(e){let t=pu(e);if(Va(t))cE(t)}async list(){let e=kr();if(!Va(e))return[];let t=[];for(let r of aE(e)){if(!r.endsWith(uu))continue;let n=r.slice(0,-uu.length),o=jg(Gi(kr(),r));if(!o)continue;t.push(Bg(o.key??n,o.record))}return t.sort((r,n)=>r.identity.localeCompare(n.identity))}}class Hg{backend="keychain";fallback;constructor(e){this.fallback=e}async get(e){let t=await fE(e);if(t)return t;return this.fallback.get(e)}async set(e,t){if(!await mE(e,t))await this.fallback.set(e,t);lE(e)}async delete(e){await hE(e),await this.fallback.delete(e),dE(e)}async list(){let e=new Set(hu()),t=await this.fallback.list(),r=new Map;for(let n of t)r.set(n.key,n);for(let n of e){let o=await this.get(n);if(o)r.set(n,Bg(n,o))}return[...r.values()].sort((n,o)=>n.identity.localeCompare(o.identity))}}function lE(e){let t=hu();if(!t.includes(e))Cr(Gi(kr(),mu),`${JSON.stringify([...t,e])}
2172
+ `)}function dE(e){let t=hu(),r=t.filter((n)=>n!==e);if(r.length!==t.length)Cr(Gi(kr(),mu),`${JSON.stringify(r)}
2173
+ `)}function hu(){let e=Gi(kr(),mu);if(!Va(e))return[];try{let t=JSON.parse(Ng(e,"utf8"));return Array.isArray(t)?t.filter((r)=>typeof r==="string"):[]}catch{return[]}}function uE(e){return{...e,accessToken:""}}function pE(e){return jg(pu(e))?.record}function jg(e){if(!Va(e))return;try{let t=JSON.parse(Ng(e,"utf8"));if(typeof t.identity!=="string")return;return{key:typeof t.key==="string"&&t.key!==""?t.key:void 0,record:{identity:t.identity,audience:typeof t.audience==="string"?t.audience:"",scopes:Array.isArray(t.scopes)?t.scopes.filter((r)=>typeof r==="string"):[],accessToken:typeof t.accessToken==="string"?t.accessToken:"",tokenType:typeof t.tokenType==="string"?t.tokenType:"Bearer",expiresAt:typeof t.expiresAt==="string"?t.expiresAt:""}}}catch{return}}function Bg(e,t){let r=Date.parse(t.expiresAt);return{key:e,identity:t.identity,audience:t.audience,scopes:t.scopes,expires_at:t.expiresAt,valid:Number.isFinite(r)&&r-Date.now()>0}}function Ya(e){return e.replace(/[^A-Za-z0-9._-]+/g,"_").slice(0,80)||"token"}async function fE(e){let t=await gE(e);if(!t)return;try{return JSON.parse(t)}catch{return}}async function mE(e,t){let r=JSON.stringify(t);if(process.platform==="darwin")return await zo(["security","delete-generic-password","-s",ao,"-a",e]),await zo(["security","add-generic-password","-s",ao,"-a",e,"-w",r,"-U"])===0;if(process.platform==="linux")return await vE(["secret-tool","store","--label",`devctl ${e}`,"service",ao,"account",e],r)===0;if(process.platform==="win32"){let o=`Add-Type -AssemblyName System.Security; $bytes = [Convert]::FromBase64String('${Buffer.from(r,"utf8").toString("base64")}'); $enc = [System.Security.Cryptography.ProtectedData]::Protect($bytes, $null, 'CurrentUser'); [IO.File]::WriteAllBytes($env:LOCALAPPDATA + '\\devctl-cred-${Ya(e)}.bin', $enc)`;return await zo(["powershell","-NoProfile","-Command",o])===0}return!1}async function hE(e){if(process.platform==="darwin"){await zo(["security","delete-generic-password","-s",ao,"-a",e]);return}if(process.platform==="linux"){await zo(["secret-tool","clear","service",ao,"account",e]);return}if(process.platform==="win32")await zo(["powershell","-NoProfile","-Command",`Remove-Item -ErrorAction SilentlyContinue ($env:LOCALAPPDATA + '\\devctl-cred-${Ya(e)}.bin')`])}async function gE(e){if(process.platform==="darwin"){let t=await du(["security","find-generic-password","-s",ao,"-a",e,"-w"]);return t.code===0&&t.stdout!==""?t.stdout:void 0}if(process.platform==="linux"){let t=await du(["secret-tool","lookup","service",ao,"account",e]);return t.code===0&&t.stdout!==""?t.stdout:void 0}if(process.platform==="win32"){let t=`$p = $env:LOCALAPPDATA + '\\devctl-cred-${Ya(e)}.bin'; if (-not (Test-Path $p)) { exit 1 }; Add-Type -AssemblyName System.Security; $raw = [IO.File]::ReadAllBytes($p); $dec = [System.Security.Cryptography.ProtectedData]::Unprotect($raw, $null, 'CurrentUser'); [Text.Encoding]::UTF8.GetString($dec)`,r=await du(["powershell","-NoProfile","-Command",t]);return r.code===0&&r.stdout!==""?r.stdout:void 0}return}var gu=3000;async function zo(e){try{return await fu({cmd:e,stdout:"ignore",stderr:"ignore",timeout:gu}).exited}catch{return 1}}async function vE(e,t){try{return await fu({cmd:e,stdin:new TextEncoder().encode(t),stdout:"ignore",stderr:"ignore",timeout:gu}).exited}catch{return 1}}async function du(e){try{let t=fu({cmd:e,stdout:"pipe",stderr:"ignore",timeout:gu}),r=t.stdout?await new Response(t.stdout).text():"";return{code:await t.exited,stdout:r.trim()}}catch{return{code:1,stdout:""}}}te();te();var yE=3,xE=200;async function Ug(e,t={}){let r=t.attempts??yE,n=t.backoffMs??xE,o=t.retry??bE,i=Error("retry exhausted");for(let s=0;s<r;s+=1)try{return await e()}catch(a){if(i=a,!o(a)||s===r-1)throw a;await wE(n*(s+1))}throw i}function bE(e){if(e instanceof Ve)return e.kind!==Q&&e.kind!==zn;return!0}function wE(e){return new Promise((t)=>{setTimeout(t,e)})}var Ja={now:()=>new Date,isoNow:()=>new Date().toISOString(),unixMs:()=>Date.now()};var vu=300000,RE=3000000,_E=3,EE=200,Xa="https://www.googleapis.com/auth/cloud-platform",Gg="https://www.googleapis.com/auth/userinfo.email";function Wg(e,t,r,n){let o=n??"",i=`${e}|${t}|${r.join(",")}`;return o===""?i:`${i}|oauth:${o}`}function TE(e,t=process.env){let r=(e.client_id??"").trim();if(r==="")return;let n=(e.client_secret??"").trim(),{value:o,missing:i}=vg(n,t);if(o===""){let s=i[0];throw I(Q,s?`IAP client_secret env ${s} is empty`:`IAP client_id ${r} requires client_secret`)}return{clientId:r,clientSecret:o}}function Qa(e,t=process.env){let r=(e.client_id??"").trim();if(r==="")return;return{clientId:r,resolve:()=>{let n=TE(e,t);if(!n)throw I(Q,`IAP client_id ${r} requires client_secret`);return n}}}function AE(e,t=vu,r=Date.now()){if(e.accessToken==="")return!1;return e.expiresAt.getTime()-r>=t}function LE(e,t=vu,r=Date.now()){if(e.accessToken==="")return!0;let n=e.expiresAt.getTime()-r;return n>0&&n<t}class co{cache=new Map;inflight=new Map;providers;thresholdMs;bus;store;clock;constructor(e,t,r,n,o=Ja){this.thresholdMs=e>0?e:vu,this.providers=[...t],this.bus=r,this.store=n??za(process.env.DEVCTL_CREDENTIAL_BACKEND==="file"?"file":void 0),this.clock=o}replaceProviders(e){this.providers.splice(0,this.providers.length,...e)}isValid(e){return AE(e,this.thresholdMs,this.clock.unixMs())}expiresSoon(e){return LE(e,this.thresholdMs,this.clock.unixMs())}invalidate(e){if(e===void 0||e===""){this.cache.clear(),this.clearStore();return}this.cache.delete(e),this.store.delete(e)}async listStatus(){return this.store.list()}storeBackend(){return this.store.backend}async get(e,t,r,n){let o=Wg(e,t,r,n?.clientId),i=this.cache.get(o)??await this.loadStored(o);if(i&&this.isValid(i))return this.cache.set(o,i),i;return this.refresh(e,t,r,n)}async refresh(e,t,r,n){let o=Wg(e,t,r,n?.clientId),i=this.inflight.get(o);if(i)return i;let s=this.refreshOnce(o,e,t,r,n);this.inflight.set(o,s);try{return await s}finally{this.inflight.delete(o)}}async refreshOnce(e,t,r,n,o){let i=o?.resolve(),s=this.providers.filter((c)=>!c.accepts||c.accepts(t,r,n,i));if(s.length===0)throw I(Fr,`no token provider accepts identity ${t}`);let a=I(Fr,"no token provider available");for(let c of s)try{let l=await Ug(()=>c.fetch(t,r,n,i),{attempts:_E,backoffMs:EE,retry:qE});return this.cache.set(e,l),IE(e,l),await this.store.set(e,ME(l)),this.bus?.publish(Se(Oo,"",{identity:l.identity,audience:l.audience})),l}catch(l){a=l instanceof Error?l:Error(String(l))}throw this.bus?.publish(Se(Gn,"",{identity:t,audience:r,error:A(a)})),a}async loadStored(e){let t=await this.store.get(e);if(!t||t.accessToken==="")return;return OE(t)}async clearStore(){let e=await this.store.list();await Promise.all(e.map((t)=>this.store.delete(t.key)))}}function DE(e){let t=SE("sha256").update(e).digest("hex").slice(0,16);return kE(kr(),`${t}.json`)}function IE(e,t){let r={identity:t.identity,audience:t.audience,expires_at:t.expiresAt.toISOString(),scopes:t.scopes};Cr(DE(e),`${JSON.stringify(r)}
2174
+ `)}function ME(e){return{identity:e.identity,audience:e.audience,scopes:e.scopes,accessToken:e.accessToken,tokenType:e.tokenType,expiresAt:e.expiresAt.toISOString()}}function OE(e){return{accessToken:e.accessToken,tokenType:e.tokenType||"Bearer",expiresAt:new Date(e.expiresAt),audience:e.audience,identity:e.identity,scopes:e.scopes}}function Rn(){return[NE(),FE(),$E()]}function $E(){return{name:"user",accepts:(e,t)=>!e.startsWith("sa:")&&t==="",fetch:async(e,t,r)=>{if(e.startsWith("sa:"))throw I(Fr,"user provider cannot mint service-account tokens");return WE(e,t,r)}}}function FE(){return{name:"service_account",accepts:(e,t)=>e.startsWith("sa:")&&t==="",fetch:async(e,t,r)=>{if(!e.startsWith("sa:"))throw I(Fr,"service-account provider cannot mint user tokens");return HE(e,t,r)}}}function NE(){return{name:"iap",accepts:(e,t)=>t!=="",fetch:async(e,t,r,n)=>{if(t==="")throw I(Fr,"IAP audience is required");if(e.startsWith("sa:"))return jE(e,t,r);return BE(e,t,r,n)}}}async function HE(e,t,r){let n=e.slice(3);try{qo();let i=await new Ki({scopes:r.length>0?r:[Xa]}).getClient(),s=new CE({sourceClient:i,targetPrincipal:n,targetScopes:r.length>0?r:[Xa],lifetime:3600}),a=await s.getAccessToken(),c=typeof a==="string"?a:a?.token;if(!c)throw I(Fr,"empty impersonated token");return{accessToken:c,tokenType:"Bearer",expiresAt:qi(c,s),audience:t,identity:e,scopes:r}}catch(o){throw Vo(o)}}async function jE(e,t,r){let n=e.slice(3);try{qo();let i=await new Ki({scopes:[Xa]}).getClient(),s=`https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${encodeURIComponent(n)}:generateIdToken`,c=(await i.request({url:s,method:"POST",data:{audience:t,includeEmail:!0}})).data.token;if(!c)throw I(Fr,"empty impersonated IAP id token");return{accessToken:c,tokenType:"Bearer",expiresAt:qi(c),audience:t,identity:e,scopes:r}}catch(o){throw Vo(o)}}async function BE(e,t,r,n){try{if(qo(),n)return await UE(e,t,r,n);let s=await(await new Ki({scopes:r.length>0?r:[Gg]}).getIdTokenClient(t)).idTokenProvider.fetchIdToken(t);return{accessToken:s,tokenType:"Bearer",expiresAt:qi(s),audience:t,identity:e,scopes:r}}catch(o){if(o instanceof Ve)throw o;throw Vo(o)}}async function UE(e,t,r,n){let s=(await new Ki({scopes:r.length>0?r:[Gg]}).getClient()).credentials.refresh_token;if(!s)throw Te(hn,"ADC has no refresh token for a custom IAP OAuth client","run `gcloud auth application-default login` with a user account; service-account ADC cannot mint IAP ID tokens for auth.client_id");let c=await new PE({clientId:n.clientId,clientSecret:n.clientSecret,refreshToken:s}).fetchIdToken(t);if(!c)throw I(Fr,"empty IAP id token");return{accessToken:c,tokenType:"Bearer",expiresAt:qi(c),audience:t,identity:e,scopes:r}}async function WE(e,t,r){try{qo();let o=await new Ki({scopes:r.length>0?r:[Xa]}).getClient(),i=await o.getAccessToken();if(!i.token)throw I(Fr,"empty access token");return{accessToken:i.token,tokenType:"Bearer",expiresAt:GE(o,i.token),audience:t,identity:e,scopes:r}}catch(n){throw Vo(n)}}function qi(e,t){if(t?.credentials?.expiry_date)return new Date(t.credentials.expiry_date);return KE(e)??new Date(Date.now()+RE)}function GE(e,t){return qi(t,e)}function KE(e){let t=e.split(".");if(t.length<2||!t[1])return;try{let r=JSON.parse(Buffer.from(t[1],"base64url").toString("utf8"));if(typeof r.exp==="number")return new Date(r.exp*1000)}catch{return}return}function qE(e){if(e instanceof Ve)return e.kind!==Q&&e.kind!==zn;let t=e instanceof Error?e.message.toLowerCase():String(e).toLowerCase();return t.includes("network")||t.includes("econnreset")||t.includes("etimedout")||t.includes("503")||t.includes("429")}te();jr();var zi=4000,VE=8000;function Yo(e){let t=e?.tokens,r=()=>(t??=new co(60000,Rn(),void 0,za("file")),t);return{detectGoogle:so,hasCommand:lu,portAvailable:jo,hasLocalAdc:Wi,adcQuotaProject:cu,mintToken:async(n,o,i)=>{await r().get(n,o,[],i)},probeServiceUsage:async(n,o)=>{let i=await Yi(r().get("user","",[]),zi),s=`https://serviceusage.googleapis.com/v1/projects/${n}/services/${o}`,a=await fetch(s,{headers:{Authorization:`Bearer ${i.accessToken}`},signal:AbortSignal.timeout(zi)});if(!a.ok)return!1;return(await a.json().catch(()=>{return}))?.state==="ENABLED"},containerRuntimeAvailable:async(n)=>{try{return await Bun.spawn({cmd:[n,"info"],stdout:"ignore",stderr:"ignore",stdin:"ignore"}).exited===0}catch{return!1}}}}var Za=Yo();function ec(e=Yo()){return{run:(t,r,n)=>zE(t,e,r,n)}}async function zE(e,t=Za,r,n){let o={checks:[],issues:0},i="Preparing diagnostics",s=()=>r?.({active:i,checks:[...o.checks]}),a=(p)=>{i=p,s()},c=(p)=>{if(o.checks.push(p),p.severity==="error")o.issues+=1;s()},l=[...new Set(Object.values(e.services).flatMap((p)=>p.container?[p.container.runtime||"docker"]:[]))];for(let p of l){a(`${p} container runtime`);let m=await t.hasCommand(p),h=m&&await(t.containerRuntimeAvailable??Za.containerRuntimeAvailable)(p);c(h?{name:`${p} container runtime`,severity:"ok",message:`${p} daemon reachable`}:{name:`${p} container runtime`,severity:"error",message:m?`${p} daemon is not reachable`:`${p} not found`,hint:`install and start ${p}`})}if(a("Google CLI installed"),await t.hasCommand("gcloud"))c({name:"Google CLI installed",severity:"ok",message:"gcloud found"});else c({name:"Google CLI installed",severity:Ui(e)?"error":"warn",message:"gcloud not installed",hint:"install the Google Cloud CLI from https://cloud.google.com/sdk/docs/install"});a("Google environment");let d=await t.detectGoogle(e.google.project_id);if(d.adcAvailable)c({name:"Google authentication available",severity:"ok",message:"Application Default Credentials found"});else c({name:"Google authentication available",severity:Ui(e)?"error":"warn",message:"ADC unavailable",hint:"run `gcloud auth application-default login`"});if(d.projectID!=="")c({name:"Project configured",severity:"ok",message:`${d.projectID} (source: ${d.projectSource})`});else c({name:"Project configured",severity:Ui(e)?"error":"warn",message:"no Google project configured",hint:"set google.project_id in .devctl/config.yaml"});if(Ui(e)||Object.values(e.services).some((p)=>p.capabilities.includes("google")||p.identity.type!=="")){a("Live Google access");let p=!0,m=(h)=>{if(p)c(h)};try{await Yi((async()=>{await JE(e,m,t,d.userEmail),await YE(e,m,t)})(),t.liveDeadlineMs??VE)}catch(h){m({name:"Live Google probes",severity:"warn",message:A(h),hint:"network or timeout \u2014 retry when online"})}finally{p=!1}}for(let p of e.doctor.tools){let m=p.command||p.name;if(a(`${p.name} installed`),await t.hasCommand(m))c({name:`${p.name} installed`,severity:"ok",message:`${m} found`});else c({name:`${p.name} installed`,severity:"error",message:`${m} not found`,hint:`install ${p.name} and ensure it is on PATH`})}if(a("Repository configuration"),n?.repositoryConfigError!==void 0)if(n.repositoryConfigError==="")c({name:"Repository configuration",severity:"ok",message:"valid"});else c({name:"Repository configuration",severity:"error",message:n.repositoryConfigError});else try{let p=eo(e);if(p.length>0)c({name:"Repository configuration",severity:"error",message:p.join("; ")});else c({name:"Repository configuration",severity:"ok",message:"valid"})}catch(p){c({name:"Repository configuration",severity:"error",message:A(p)})}let f={};for(let[p,m]of Object.entries(e.services))for(let h of m.ports){if(h.auto)continue;let v=`Port ${h.value}`;if(a(v),f[h.value])c({name:v,severity:"error",message:`configured on both ${f[h.value]} and ${p}`});else{f[h.value]=p;let x=n?.services?.[p],g=Boolean(m.container&&x&&["STARTING","RUNNING","HEALTHY","UNHEALTHY"].includes(x.state??""));if(Boolean(x&&(x.pid>0||g)&&Object.values(x.ports).includes(h.value)))c({name:v,severity:"ok",message:`in use by running service ${p}`});else if(await t.portAvailable(h.value))c({name:v,severity:"ok",message:"available"});else c(await qg(v,h.value,`services.${p}.ports`))}}if(e.proxy.listen.port>0){let p=`Port ${e.proxy.listen.port}`;if(a(p),n?.proxyRunning)c({name:p,severity:"ok",message:"in use by the running proxy"});else if(await t.portAvailable(e.proxy.listen.port))c({name:p,severity:"ok",message:"proxy listen port available"});else c(await qg(p,e.proxy.listen.port,"proxy.listen.port"))}return i="Diagnostics complete",s(),o}async function YE(e,t,r){let n=e.google.project_id,o=r.hasLocalAdc??Wi;if(n===""||!o())return;let i=r.probeServiceUsage??Za.probeServiceUsage,s=[{name:"IAM Credentials API",service:"iamcredentials.googleapis.com"},{name:"Resource Manager API",service:"cloudresourcemanager.googleapis.com"},{name:"IAP API",service:"iap.googleapis.com"}];(await Promise.allSettled(s.map((c)=>Yi(i(n,c.service),zi)))).forEach((c,l)=>{let d=s[l];if(!d)return;if(c.status==="fulfilled"){t({name:d.name,severity:c.value?"ok":"warn",message:c.value?"enabled":"not enabled or unreachable",hint:c.value?void 0:`enable ${d.service} in the Google Cloud console \u2014 doctor never auto-enables APIs`});return}t({name:d.name,severity:"warn",message:A(c.reason),hint:`enable ${d.service} in the Google Cloud console \u2014 doctor never auto-enables APIs`})})}async function JE(e,t,r,n){let o=Hr(e),i=e.proxy.routes.filter((u)=>u.auth.type.toLowerCase()==="iap"),s=i.filter((u)=>u.auth.audience.trim()!=="");for(let u of i)if(u.auth.audience.trim()==="")t({name:`IAP audience ${u.name}`,severity:"error",message:"missing audience",hint:"set auth.audience to the IAP OAuth client ID"});if(o.length===0&&s.length===0)return;if(!(r.hasLocalAdc??Wi)())return;let c=r.mintToken??Za.mintToken,l=o.map(async(u)=>{try{await Yi(c(`sa:${u}`,""),zi),t({name:`Impersonate ${u}`,severity:"ok",message:"token minted"})}catch(f){let p=(r.adcQuotaProject??cu)(),m=n||"the ADC principal",h=p===""?"unknown":p,v=Kg(`Impersonate ${u}`,f,`grant roles/iam.serviceAccountTokenCreator to ${m} on ${u}; ADC quota project: ${h}`);if(p!==""&&v.message.toLowerCase().includes("api is not enabled"))v.message=`IAM Credentials API is disabled in ADC quota project ${p}`,v.hint=`enable iamcredentials.googleapis.com in ${p}, or set the ADC quota project to ${e.google.project_id}`;else if(v.message.toLowerCase()==="google authentication failed")v.message=`access-token mint failed: ${m} \u2192 ${u}`,v.hint=`verify roles/iam.serviceAccountTokenCreator for ${m} on this service account; ADC quota project: ${h}`;t(v)}}),d=s.map(async(u)=>{let f=oo(u.auth).kind===no?`sa:${oo(u.auth).serviceAccount}`:"user";try{let p=Qa(u.auth);await Yi(c(f,u.auth.audience,p),zi),t({name:`IAP ${u.name}`,severity:"ok",message:"id token minted"})}catch(p){t(Kg(`IAP ${u.name}`,p,"check IAP OAuth client ID and ADC"))}});await Promise.all([...l,...d])}function Kg(e,t,r){let n=A(t),o=n.toLowerCase();if(o.includes("timeout")||o.includes("network")||o.includes("econnrefused")||o.includes("enotfound"))return{name:e,severity:"warn",message:n,hint:"network or timeout \u2014 retry when online"};if(t instanceof Ve&&t.hint!==""){let i=`${t.kind}: `,s=t.message.startsWith(i)?t.message.slice(i.length):t.message;return{name:e,severity:"error",message:s,hint:t.hint}}return{name:e,severity:"error",message:n,hint:r}}function Yi(e,t){return new Promise((r,n)=>{let o=setTimeout(()=>n(Error("timeout")),t);e.then((i)=>{clearTimeout(o),r(i)},(i)=>{clearTimeout(o),n(i)})})}async function qg(e,t,r){let n=await Yn(t);if(!n)return{name:e,severity:"error",message:"already in use",hint:`stop the process using port ${t} or change ${r}`};if(n.pid===process.pid)return{name:e,severity:"error",message:`in use by this TUI (${n.command} pid ${n.pid})`,hint:"stop the proxy from the proxy screen (x), then rerun doctor"};return{name:e,severity:"error",message:`in use by ${n.command} (pid ${n.pid})`,hint:"enter stop that process after a confirmation",action:{kind:"free-port",holder:n}}}function Vg(e){let t=[`${er()} doctor`,""];for(let r of e.checks){let n=r.severity==="error"?"\u2717":r.severity==="warn"?"!":"\u2713";if(t.push(`${n} ${r.name}`),r.severity!=="ok"){if(t.push(` ${r.message}`),r.hint)t.push(` \u2192 ${r.hint}`)}}return t.push("",`${e.issues} issue(s) found.`),t.join(`
632
2175
  `)+`
633
- `}Tt();class Bh{start;constructor(e){this.start=e}execute(e){return this.start({...e,profile:e.profile?ur(e.profile):void 0,services:e.services?.map((t)=>pu(t))})}}class Wh{stop;constructor(e){this.stop=e}execute(e){return this.stop(e.map((t)=>pu(t)))}}class jh{restart;constructor(e){this.restart=e}execute(e,t){return this.restart(e.map((r)=>pu(r)),t)}}class Gh{start;constructor(e){this.start=e}execute(e,t){return this.start.execute({profile:e,client_env:t})}}class Uh{host;constructor(e){this.host=e}execute(){return this.host.startProxy()}}class Kh{host;constructor(e){this.host=e}execute(){return this.host.stopProxy()}}class qh{host;constructor(e){this.host=e}execute(){return this.host.reload()}}class Vh{host;constructor(e){this.host=e}execute(){return this.host.refreshIdentity({probeServiceAccounts:!0})}}class ec{runner;constructor(e){this.runner=e}execute(e,t,r){return this.runner.run(e,t,r)}}class tc{execute(e,t,r){return za(e,t,r)}}class rc{execute(e,t){return Ya(e,t)}}class Jh{host;constructor(e){this.host=e}execute(){return this.host.snapshot()}}class nc{execute(e,t){return Za(e,t)}}function pu(e){return Oh(e)}function zh(e,t,r){let n=r??e,o=new Bh((i)=>n.start(i));return{startService:o,stopService:new Wh((i)=>n.stop(i)),restartService:new jh((i,s)=>n.restart(i,s)),startProfile:new Gh(o),startProxy:new Uh(e),stopProxy:new Kh(e),reloadConfig:new qh(e),refreshIdentity:new Vh(e),runDoctor:new ec(t),getServiceStatus:new Jh(e),getStartupPlan:new tc,getShutdownPlan:new rc,resolveStart:new nc}}import{createConnection as BE}from"net";var{spawn:WE}=globalThis.Bun;import{readdirSync as EE}from"fs";import{join as Yh,resolve as fu,sep as mu}from"path";function TE(e){let t=Yh(cr(),"state"),r;try{r=EE(t)}catch{return}let n=Xh(fu(e)),o;for(let i of r){let s=Yl(Yh(t,i,"state.json"));if(!s)continue;let a=Xh(fu(s.repo_root));if(!AE(a,n))continue;if(!o||a.length>o.length)o=a}return o}function oc(e,t,r=""){if(t!=="")return{repoRoot:fu(t),source:"explicit"};let n=e===""?process.cwd():e;try{let{repoRoot:i}=Yn(n,r);return{repoRoot:i,source:"config"}}catch{if(r!=="")return}let o=TE(n);return o?{repoRoot:o,source:"state-scan"}:void 0}function Xh(e){let t=e.length>1&&e.endsWith(mu)?e.slice(0,-1):e;return process.platform==="win32"?t.toLowerCase():t}function AE(e,t){return t===e||t.startsWith(e.endsWith(mu)?e:`${e}${mu}`)}import{existsSync as Zh,readFileSync as ey}from"fs";import{isAbsolute as LE,join as gu}from"path";import{parse as DE}from"dotenv";te();var Vi=["process","profile","dotenv","generated","keychain","secret_manager","defaults","vars","runtime"],ME=/^projects\/[^/]+\/secrets\/[^/]+(?:\/versions\/[^/]+)?$/,OE=["process","defaults","vars","runtime"];function IE(){return{name:"dotenv",load:(e)=>{let t=Qh(e.repoRoot,e.profile);if(e.workDir!=="")Object.assign(t,Qh(e.workDir,e.profile));return t}}}function $E(e){let t=e?.environment.sources??[];if(t.length===0)return[...Vi];let r=new Set([...OE,...t]),n=new Set(Vi),o=t.filter((s)=>!n.has(s)),i=[];for(let s of Vi){if(s==="defaults")i.push(...o);if(r.has(s))i.push(s)}return i}async function Ji(e,t){let r={},n=t.serviceCfg.working_dir;if(n!==""&&!LE(n))n=gu(e,n);let o={repoRoot:e,profile:t.profile,service:t.service,serviceCfg:t.serviceCfg,workDir:n,cfg:t.cfg},i=FE(t),s={process:t.includeProcess===!1?{}:t.clientEnv??zo(),profile:qi(t.profileEnv,t.cfg,i),dotenv:qi(await IE().load(o),t.cfg,i),generated:{},keychain:t.sourceValues?.keychain??NE(o),secret_manager:t.sourceValues?.secret_manager??await HE(o,t.fetchSecret),defaults:qi(t.serviceCfg.environment.defaults,t.cfg,i),vars:qi(t.serviceCfg.environment.vars,t.cfg,i),runtime:t.runtime};for(let a of $E(t.cfg)){if(s[a]!==void 0){Object.assign(r,s[a]);continue}let c=t.pluginSources?.find((l)=>l.name===a);if(c)Object.assign(r,qi(await c.load(o),t.cfg,i))}for(let a of t.serviceCfg.environment.required)if((r[a]??"").trim()==="")throw M(ee,`service ${t.service} missing required environment variable ${a}`);return r}function FE(e){let t={};if(e.cfg)for(let[r,n]of Object.entries(e.cfg.services)){let o={};for(let i of n.ports)if(!i.auto)o[i.name]=i.value;t[r]=o}if(e.assignedPorts)t[e.service]=e.assignedPorts;return t}function qi(e,t,r){if(!t||Object.keys(e).length===0)return e;return qd(e,t,r)}function NE(e){let t=e.cfg?.environment.sources??[];if(t.length>0&&!t.includes("keychain"))return{};let r={},n=new Set([...Object.keys(e.serviceCfg.environment.defaults),...Object.keys(e.serviceCfg.environment.vars),...e.serviceCfg.environment.required]);for(let o of n){let i=gu(Sr(),"env",o);if(!Zh(i))continue;try{r[o]=ey(i,"utf8").replace(/\n$/,"")}catch(s){throw Re(ee,`unable to read keychain env ${o}`,s)}}return r}async function HE(e,t){if(!(e.cfg?.environment.sources??[]).includes("secret_manager"))return{};let n=e.cfg?.environment.secrets??{},o={};for(let[i,s]of Object.entries(n)){if(!ME.test(s))throw M(ee,`environment.secrets.${i} is not a Secret Manager resource`);if(!t)throw M(ee,"environment source secret_manager is not configured \u2014 set credentials or remove it from environment.sources");o[i]=await t(s)}return o}function Qh(e,t){let r={},n=[".env",".env.development",".env.local"];if(t!=="")n.push(`.env.${t}`);for(let o of n){let i=gu(e,o);if(!Zh(i))continue;try{let s=DE(ey(i));Object.assign(r,s)}catch(s){throw Re(ee,`unable to read ${i}`,s)}}return r}function zo(){let e={};for(let[t,r]of Object.entries(process.env))if(r!==void 0)e[t]=r;return e}function zi(e,t,r,n,o){let i={DEVCTL_SERVICE_NAME:e,DEVCTL_ENVIRONMENT:o,SERVICE_HOST:t};if(n!=="")i.DEVCTL_PROXY_URL=n;if(r.http!==void 0)i.SERVICE_PORT=String(r.http);else{let s=Object.values(r)[0];if(s!==void 0)i.SERVICE_PORT=String(s)}for(let[s,a]of Object.entries(r))i[`${s.toUpperCase()}_PORT`]=String(a);return i}function Yo(e){return{...e}}te();var jE=50,GE=8000,UE=15000,KE=200,qE=30000,ty=86400000,VE=new Set(["logs","logs_page","logs_stats"]);function JE(e){if(e.legacy)return"attached daemon predates the client/daemon compatibility handshake";return`attached daemon speaks RPC protocol ${e.daemonProtocol??"unknown"}; this client speaks ${ji}`}function zE(e){if(e.compatible&&e.daemonVersion!==void 0&&e.daemonVersion!==Bt)return`attached daemon is devctl ${e.daemonVersion}; this client is ${Bt} (run \`devctl down\` then start again to update it)`;return}function hu(e,t){if(!e.compat.compatible&&!VE.has(t))throw Ne(ct,JE(e.compat),"run `devctl down` to stop it, then start again")}class ry{socket;buf="";pending=new Map;listeners=[];nextID=0;compat={compatible:!0,legacy:!1};session="";constructor(e){this.socket=e,e.on("data",(t)=>{this.buf+=t.toString("utf8");let r=this.buf.split(`
634
- `);this.buf=r.pop()??"";for(let n of r){if(n.trim()==="")continue;this.onLine(n)}}),e.on("error",(t)=>this.rejectPending(t instanceof Error?t:Error(String(t)))),e.on("close",()=>this.rejectPending(Error("supervisor connection closed")))}onLine(e){let t;try{t=JSON.parse(e)}catch{return}if(t.event){for(let n of this.listeners)n(t.event);return}let r=t.id?this.pending.get(t.id):void 0;if(!r)return;if(this.pending.delete(t.id??""),clearTimeout(r.timer),t.error){r.reject(rg({error:t.error,kind:t.kind,hint:t.hint,service:t.service}));return}r.resolve(t.result)}onEvent(e){return this.listeners.push(e),()=>{let t=this.listeners.indexOf(e);if(t>=0)this.listeners.splice(t,1)}}call(e,t,r=qE){this.nextID+=1;let n=String(this.nextID);return new Promise((o,i)=>{let s=setTimeout(()=>{this.pending.delete(n),i(Error(`${e} timed out after ${r}ms`))},r);this.pending.set(n,{resolve:o,reject:i,timer:s}),this.socket.write(JSON.stringify({id:n,method:e,params:t})+`
635
- `)})}close(){this.rejectPending(Error("supervisor connection closed")),this.socket.destroy()}rejectPending(e){for(let t of this.pending.values())clearTimeout(t.timer),t.reject(e);this.pending.clear()}}function ny(e,t){let r=Vs(e),n=Date.now()+t;return new Promise((o,i)=>{let s=()=>{let a=BE(r);a.once("connect",()=>{let c=new ry(a);YE(c).finally(()=>o(c))}),a.once("error",(c)=>{if(a.destroy(),Date.now()>=n){i(Ne(ct,"supervisor is not running","run `devctl start` or `devctl attach` after starting services"));return}setTimeout(s,jE)})};s()})}async function YE(e){let t;try{t=await e.call("ping",null,GE)}catch{return}let r=XE(t)?t:{};if(typeof r.session==="string")e.session=r.session;let n=typeof r.protocol==="number"?r.protocol:void 0,o=typeof r.version==="string"?r.version:void 0;if(n===void 0){e.compat={compatible:!1,legacy:!0,daemonVersion:o};return}e.compat={compatible:n===ji,legacy:!1,daemonVersion:o,daemonProtocol:n}}function XE(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}async function ao(e){try{return await ny(e,KE)}catch{return}}function QE(e,t,r,n){return r?[e,...n]:[e,t,...n]}async function oy(e,t){let r=await ao(e);if(r)return r;gm(e);let n=ki(e),o=QE(process.execPath,process.argv[1]??"",Bun.isStandaloneExecutable===!0,["--config",t,"_supervisor","--repo",e]),i=WE({cmd:o,env:{...process.env},stdout:"ignore",stderr:Bun.file(n),stdin:"ignore",detached:!0});try{let s=await e0(e,UE,i.exited);return i.unref(),s}catch{throw await t0(i),Ne(ct,"supervisor failed to start",`see ${n} for details`)}}var ZE=1000;function e0(e,t,r){return new Promise((n,o)=>{let i=!1,s=(a)=>{if(i)return;i=!0,a()};r.then((a)=>{s(()=>o(Error(`supervisor exited (code ${a}) before accepting connections`)))}),ny(e,t).then((a)=>s(()=>n(a)),(a)=>s(()=>o(a)))})}async function t0(e){let t=e.pid;try{e.kill("SIGKILL")}catch{}if(t!==void 0&&t>0&&process.platform!=="win32")try{process.kill(-t,"SIGKILL")}catch{try{process.kill(t,"SIGKILL")}catch{}}await Promise.race([e.exited.then(()=>{return}),new Promise((r)=>{setTimeout(r,ZE)})])}class ic{cfg;client;previousPersisted;constructor(e){this.cfg=e}async start(e){return await this.call("start",{...e,client_env:zo()})}async stop(e){await this.call("stop",{services:e})}async restart(e,t){await this.call("restart",{services:e,cascade:t===!0,client_env:zo()})}async runTask(e){return await this.call("run_task",{name:e,client_env:zo()},ty)}async execService(e,t,r=!1){return await this.call("exec",{service:e,command:t,print_env:r,client_env:zo()},ty)}async status(){return await this.call("status",null)}async refreshAuth(){return await this.call("auth_refresh",null)}async configSnapshot(){return await this.call("config_snapshot",null)}async logs(e){return(await this.call("logs",e)).events??[]}async logsPage(e){return await this.call("logs_page",e)}async logsStats(e){return await this.call("logs_stats",e)}async proxyStart(){await this.call("proxy_start",null)}async proxyStop(){await this.call("proxy_stop",null)}async mcpStart(e){await this.call("mcp_start",e??null)}async mcpStop(){await this.call("mcp_stop",null)}async mcpSetTools(e){return(await this.call("mcp_set_tools",{disabled:[...e]})).disabled_tools??[]}async reload(){return await this.call("reload",null)}async invalidateAuth(){await this.call("auth_invalidate",null)}onEvent(e){if(this.client)return this.client.onEvent(e);return()=>{return}}async shutdown(e){if(!this.client)return;let t=Math.max(5000,this.cfg.shutdown.grace_seconds*1000+2000);await this.client.call("shutdown",{stop_services:e.stopServices},t)}async close(e){if(!this.client)return;try{if(e?.shutdownSupervisor===!0&&e.detach!==!0)await this.shutdown({stopServices:!0})}finally{this.client.close()}}async call(e,t,r){if(!this.client)throw Re(ct,"supervisor is not running",Error("no client"));return hu(this.client,e),this.client.call(e,t,r)}}function Yi(e){let t=e&&zE(e.compat);if(t)process.stderr.write(`warning: ${t}
636
- `)}async function iy(e,t,r=""){let n=oc(e,t,r);if(!n)throw Ne(ct,"no devctl configuration found","run `devctl setup`, create a .devctl/config.yaml in the repository root, or pass --repo");let o=await ao(n.repoRoot);return Yi(o),{repoRoot:n.repoRoot,client:o}}async function sy(e,t,r,n){let o=n?.allowMissingConfig===!0?Zn(e,t):tn(e,t),i=new ic(o);if(!r)return i.client=await ao(o.repoRoot),Yi(i.client),i;return i.client=await oy(o.repoRoot,o.configPath),Yi(i.client),i}async function ay(e){let t=new ic(Jn());return t.client=e,Yi(t.client),t.cfg=await t.configSnapshot(),t}async function cy(e,t){let r=oc(e,"",t),n=r?await ao(r.repoRoot):void 0;if(!n)throw Ne(ct,"supervisor is not running","run `devctl start` before `devctl attach`");return ay(n)}async function ly(e,t){let r=oc(e,"",t),n=r?await ao(r.repoRoot):void 0;if(n)return ay(n);let o=tn(e,t),i=new ic(o),s=un(o.repoRoot);if(i.client=await oy(o.repoRoot,o.configPath),Yi(i.client),i.cfg=await i.configSnapshot(),s&&s.processes.length>0)i.previousPersisted=s;return i}import{realpathSync as n0}from"fs";var o0="https://api.github.com/repos/amr-m-abdelgawad/devctl/releases/latest";function i0(){let e=Bun.isStandaloneExecutable===!0,t=e?"":hy(process.argv[1]??"");return fy({scriptPath:t,execPath:hy(process.execPath),standalone:e})}async function s0(e=fetch,t=i0()){try{let r=await e(o0,{headers:{Accept:"application/vnd.github+json"}});if(!r.ok)return gy(t);let o=((await r.json()).tag_name??"").replace(/^v/,"");return{current:Bt,latest:o,newer:o!==""&&my(o,Bt)>0,hint:t.hint,kind:t.kind,command:t.command}}catch{return gy(t)}}async function a0(e,t=yy){return t(e)}async function yy(e,t=!1){let r=Bun.spawn([...e],{stdout:t?"inherit":"pipe",stderr:t?"inherit":"pipe",stdin:"ignore"});if(t)return{code:await r.exited,stdout:"",stderr:""};let n=await new Response(r.stdout).text(),o=await new Response(r.stderr).text();return{code:await r.exited,stdout:n,stderr:o}}function vy(e=fetch){return{check:()=>s0(e),apply:(t,r)=>r===!0?yy(t,!0):a0(t)}}function gy(e){return{current:Bt,latest:"",newer:!1,hint:e.hint,kind:e.kind,command:e.command}}function hy(e){if(e==="")return"";try{return n0(e)}catch{return e}}function xy(e){let t=e?.tokens??new oo(60000,Rn(),void 0),r=e?.doctorHost??Vo({tokens:t}),n=vy(),o={loadTuiConfig:To,saveTuiPreferences:Sm,resolveTuiOverridePath:ea,userTuiConfigPath:Zs,listSessions:zm,loadSessionEvents:Ym,loadPath:Ni,validateConfigText:Fi,discover:Yn,configDiff:Cn,openTui:ly,findDaemon:iy,tryDial:ao,assertMethodAllowed:hu,readPersistedState:un,bootstrapLogPath:ki,exportsDir:qs,resolveExportPath:Vm,writeLogExport:wd,openInFileManager:Jm,freePort:fa,createStarterConfig:fg,runSetup:(i,s,a)=>mg(o,i,s,a),readTextFile:(i)=>l0(i,"utf8"),writeTextFile:(i,s)=>d0(i,s),fileExists:c0,load:tn,loadOrEmpty:Zn,validate:Xn,detectGoogle:ro,loginGoogle:wh,logoutGoogle:Sh,refreshUserToken:async(i="user")=>t.refresh(i,"",[]),runDoctor:new ec(e?.doctorRunner??Ja(r)),getStartupPlan:new tc,getShutdownPlan:new rc,resolveStart:new nc,formatDoctor:Mh,openController:sy,openAttach:cy,checkUpdate:()=>n.check(),applyUpdate:(i,s)=>n.apply(i,s),formatUpdateStatus:py};return o}import{existsSync as vA,unlinkSync as xA}from"fs";import{connect as u0}from"net";Tt();var p0=2000,f0=200,m0=300;function by(e){return{lookup(t){let r=t.toLowerCase();return["","http","tcp","command","process"].includes(r)||e.some((o)=>o.name.toLowerCase()===r)?{check:(o,i)=>g0({...o,type:t},i.pid,i.ports,i.workDir,i.env,e)}:void 0}}}async function g0(e,t,r,n,o,i=[]){let s=e.timeout_seconds>0?e.timeout_seconds*1000:p0,a=e.type.toLowerCase(),c=i.find((l)=>l.name.toLowerCase()===a);if(c)try{let l=await c.check(e,{pid:t,ports:r,workDir:n,env:o});return{status:l.status,message:l.message}}catch(l){return{status:Me,message:l instanceof Error?l.message:String(l)}}if(a==="http")return h0(e.url,s);if(a==="tcp")return v0(y0(e.address,r),s);if(a==="command")return x0(e.command.args,e.command.shell,n,o,s);if(a==="process"||a===""){if(t>0&&Qt(t))return{status:Wt,message:"process running"};return{status:Me,message:"process not running"}}return{status:Me,message:`unknown health type ${e.type}`}}async function h0(e,t){let r=new AbortController,n=setTimeout(()=>r.abort(),t);try{let o=await fetch(e,{signal:r.signal});if(o.status>=f0&&o.status<m0)return{status:Wt,message:String(o.status)};return{status:Me,message:String(o.status)}}catch(o){return{status:Me,message:o instanceof Error?o.message:String(o)}}finally{clearTimeout(n)}}function y0(e,t){if(e!=="")return e;if(t.http!==void 0)return`127.0.0.1:${t.http}`;let r=Object.values(t)[0];return r!==void 0?`127.0.0.1:${r}`:""}function v0(e,t){return new Promise((r)=>{if(e===""){r({status:Me,message:"no tcp address"});return}let n=e.lastIndexOf(":"),o=Number.parseInt(e.slice(n+1),10),i=e.slice(0,n).replace(/^\[|\]$/g,"");if(n<1||!Number.isFinite(o)){r({status:Me,message:`invalid tcp address ${e}`});return}let s=u0({host:i,port:o},()=>{s.end(),r({status:Wt,message:"connected"})});s.setTimeout(t),s.on("error",(a)=>{r({status:Me,message:a.message})}),s.on("timeout",()=>{s.destroy(),r({status:Me,message:"timeout"})})})}async function x0(e,t,r,n,o){if(e.length===0)return{status:Me,message:"empty health command"};let i=t?process.platform==="win32"?["cmd.exe","/c",e.join(" ")]:["/bin/sh","-c",e.join(" ")]:e,s;try{s=Bun.spawn({cmd:i,cwd:r===""?void 0:r,env:n,stdout:"ignore",stderr:"ignore"})}catch(l){return{status:Me,message:l instanceof Error?l.message:String(l)}}let a=setTimeout(()=>s.kill("SIGKILL"),o),c=await s.exited;if(clearTimeout(a),c===0)return{status:Wt,message:"ok"};return{status:Me,message:`exit ${c}`}}te();var{spawn:Oy}=globalThis.Bun;var{spawn:b0}=globalThis.Bun;async function wy(e,t){if(e<=0)return;try{process.kill(-e,t);return}catch{try{process.kill(e,t)}catch{return}}}async function Sy(e){if(!Qt(e))return;let t=await Xi(["ps","-p",String(e),"-o","command="]),r=Date.now(),n=w0(await Xi(["ps","-p",String(e),"-o","etime="])),o=await S0(e);return{pid:e,command:t.trim(),cwd:o,startTime:n===void 0?void 0:new Date(r-n).toISOString()}}function w0(e){let t=e.trim();if(t==="")return;let r=t.indexOf("-"),n=r>=0?t.slice(0,r):"0",i=(r>=0?t.slice(r+1):t).split(":");if(i.length!==2&&i.length!==3)return;let s=Number(n),a=i.length===3?Number(i[0]):0,c=Number(i.length===3?i[1]:i[0]),l=Number(i.at(-1));if(!Number.isInteger(s)||s<0||!Number.isInteger(a)||a<0||a>23||!Number.isInteger(c)||c<0||c>59||!Number.isInteger(l)||l<0||l>59)return;return(((s*24+a)*60+c)*60+l)*1000}async function S0(e){let r=(await Xi(["lsof","-a","-p",String(e),"-d","cwd","-Fn"])).split(`
637
- `).find((i)=>i.startsWith("n"));if(r&&r.length>1)return r.slice(1).trim();let n=await Xi(["pwdx",String(e)]),o=n.indexOf(":");if(o>=0)return n.slice(o+1).trim();return""}async function Xi(e){try{let t=b0({cmd:e,stdout:"pipe",stderr:"ignore"}),r=t.stdout?await new Response(t.stdout).text():"";return await t.exited,r}catch{return""}}async function ky(e){let t=new Map;if(e.length===0)return t;let r=await Xi(["ps","-o","pid=,pcpu=,rss=","-p",e.join(",")]);for(let n of r.split(`
638
- `)){let o=n.trim();if(o==="")continue;let[i,s,a]=o.split(/\s+/),c=Number(i),l=Number(s),d=Number(a);if(Number.isFinite(c)&&Number.isFinite(l)&&Number.isFinite(d))t.set(c,{pid:c,cpuPercent:l,memoryKB:d})}return t}function Cy(e,t){if(e.length===0||t==="")return!1;let r=e.join(" ");if(t.includes(r))return!0;let o=(e[0]??"").split(/[/\\]/).pop()??"";if(o!==""&&t.includes(o))return!0;let i=e[e.length-1]??"";return i!==""&&t.includes(i)}var{spawn:Ry}=globalThis.Bun;var k0=1024;async function Py(e,t){if(e<=0)return;try{await Ry({cmd:["taskkill","/PID",String(e),"/T","/F"],stdout:"ignore",stderr:"ignore"}).exited}catch{return}}async function _y(e){let t=C0(Xl(e),e);if(t)return t;if(!Qt(e))return;let r=await yu(["powershell","-NoProfile","-Command",`Get-CimInstance Win32_Process -Filter "ProcessId=${e}" | Select-Object CommandLine,ExecutablePath,CreationDate | ConvertTo-Json -Compress`]),n=P0(r);if(n&&n.command!=="")return{pid:e,command:n.command,cwd:n.cwd,startTime:n.startTime};let o=await yu(["powershell","-NoProfile","-Command",`Get-Process -Id ${e} -ErrorAction SilentlyContinue | Select-Object Path,StartTime | ConvertTo-Json -Compress`]),i=R0(o);if(!i&&!n)return;return{pid:e,command:n?.command||i?.command||"",cwd:n?.cwd||i?.cwd||"",startTime:n?.startTime||i?.startTime}}function C0(e,t){let r=String(t);for(let n of e.split(/\r?\n/)){if(!n.includes(r))continue;let i=n.split(",").map((s)=>s.replace(/^"|"$/g,"").trim())[0]??"";if(i===""||i.toLowerCase().includes("info:"))continue;return{pid:t,command:i,cwd:""}}return}function R0(e){let t=e.trim();if(t==="")return;try{let r=JSON.parse(t),n=(r.Path||"").trim();if(n==="")return;return{command:n,cwd:cc(n),startTime:r.StartTime}}catch{let r=ac(t,"Path");if(r==="")return;return{command:r,cwd:cc(r)}}}function P0(e){let t=e.trim();if(t==="")return;try{let r=JSON.parse(t),n=(r.CommandLine||r.ExecutablePath||"").trim(),o=(r.ExecutablePath||"").trim(),i=cc(o);return{command:n,cwd:i,startTime:r.CreationDate||void 0}}catch{return{command:ac(t,"CommandLine")||ac(t,"ExecutablePath"),cwd:cc(ac(t,"ExecutablePath"))}}}async function Ey(e){let t=new Map;if(e.length===0)return t;let r=e.filter((o)=>o>0).join(",");if(r==="")return t;let n=await yu(["powershell","-NoProfile","-Command",`${r}.Split(',') | ForEach-Object { Get-Process -Id $_ -ErrorAction SilentlyContinue } | Select-Object Id,CPU,WorkingSet64 | ConvertTo-Json -Compress`]);return _0(n)}function _0(e){let t=new Map,r=e.trim();if(r==="")return t;try{let n=JSON.parse(r),o=Array.isArray(n)?n:[n];for(let i of o){if(!i||typeof i!=="object")continue;let s=i,a=Number(s.Id),c=Number(s.CPU??0),l=Number(s.WorkingSet64??0)/k0;if(Number.isFinite(a)&&a>0&&Number.isFinite(c)&&Number.isFinite(l))t.set(a,{pid:a,cpuPercent:c,memoryKB:l})}}catch{return t}return t}function cc(e){if(e==="")return"";let t=e.replace(/\//g,"\\"),r=t.lastIndexOf("\\");return r>0?t.slice(0,r):""}function ac(e,t){let r=`${t}=`,n=e.split(/\r?\n/).find((o)=>o.startsWith(r));return n?n.slice(r.length).trim():""}async function yu(e){try{let t=Ry({cmd:e,stdout:"pipe",stderr:"ignore"}),r=t.stdout?await new Response(t.stdout).text():"";return await t.exited,r}catch{return""}}te();var{spawn:vu}=globalThis.Bun;async function Ay(e){await A0(e.runtime,e.containerName);let t=E0(e),r=await Xo(e.runtime,t,e.env);if(r.code!==0||r.stdout.trim()==="")throw M(He,`failed to start ${e.containerName}: ${r.stderr.trim()||"container runtime returned no id"}`);let n=r.stdout.trim().split(/\s+/)[0]??"";return My(e.runtime,e.containerName,n,e.onLine,e.onExit)}function E0(e){let t=["run","--detach","--name",e.containerName,"--label","devctl.managed=true"];for(let[r,n]of Object.entries(e.ports)){let o=e.targetPorts[r]??n;t.push("--publish",`127.0.0.1:${n}:${o}`)}for(let r of Object.keys(e.env).sort())t.push("--env",r);for(let r of e.volumes)t.push("--volume",r);return t.push(e.image,...e.command),t}var T0=new Set(["PATH","HOME","HOSTNAME","PWD","OLDPWD","SHLVL","_","TMPDIR"]);function Ly(e){return Object.fromEntries(Object.entries(e).filter(([t])=>!T0.has(t)))}async function Dy(e,t,r,n){let o=await Xo(e,["inspect","--format","{{.Id}} {{.State.Running}}",t],process.env);if(o.code!==0)return;let[i,s]=o.stdout.trim().split(/\s+/);if(!i||s!=="true")return;return My(e,t,i,r,n)}function My(e,t,r,n,o){let i=!0,s=vu({cmd:[e,"logs","--follow",r],stdout:"pipe",stderr:"pipe",stdin:"ignore"});Ty(s.stdout,"stdout",n),Ty(s.stderr,"stderr",n);let a=vu({cmd:[e,"wait",r],stdout:"pipe",stderr:"pipe",stdin:"ignore"}),c=Promise.all([new Response(a.stdout).text(),a.exited]).then(async([l,d])=>{i=!1,s.kill();let u=Number.parseInt(l.trim(),10),f=Number.isFinite(u)?u:d??1,p=f===0?void 0:Error(`container exited with code ${f}`);return await Xo(e,["rm","--force",r],process.env),o?.(f,p),{code:f,err:p}});return{id:r,runtime:e,containerName:t,done:c,running:()=>i,stop:async(l)=>{let d=Math.max(1,Math.ceil(l/1000));await Xo(e,["stop","--time",String(d),r],process.env),await c}}}async function A0(e,t){let r=await Xo(e,["inspect","--format","{{.State.Running}}",t],process.env);if(r.code===0&&r.stdout.trim()==="false")await Xo(e,["rm","--force",t],process.env)}async function Xo(e,t,r){try{let n=vu({cmd:[e,...t],env:{...process.env,...r},stdout:"pipe",stderr:"pipe",stdin:"ignore"}),[o,i,s]=await Promise.all([new Response(n.stdout).text(),new Response(n.stderr).text(),n.exited]);return{code:s??1,stdout:o,stderr:i}}catch(n){throw Re(He,`unable to run ${e}`,n)}}async function Ty(e,t,r){if(!e||typeof e==="number"||!r)return;let n=e.getReader(),o=new TextDecoder,i="";for(;;){let s=await n.read();if(s.done)break;i+=o.decode(s.value,{stream:!0});let a=i.split(`
639
- `);i=a.pop()??"";for(let c of a)r(t,c.replace(/\r$/,""))}if(i!=="")r(t,i)}var L0=1e4,D0=2000,M0=500;class xu{isRunning(e){let t=this.running.get(e);return t!==void 0&&Qi(t)}running=new Map;async start(e){let t=this.running.get(e.name);if(t&&Qi(t))return t;if(this.running.delete(e.name),e.args.length===0)throw M(He,"empty command");let r=e.shell?Fy(e.args):e.args,n;try{n=Oy({cmd:r,cwd:e.workDir===""?void 0:e.workDir,env:e.env,stdout:e.captureStdout===!1?"ignore":"pipe",stderr:e.captureStderr===!1?"ignore":"pipe",stdin:"ignore",detached:process.platform!=="win32"})}catch(i){throw Re(He,`failed to start ${e.name}`,i)}let o={name:e.name,pid:n.pid??0,startTime:new Date,workDir:e.workDir,args:[...e.args],proc:n,done:Promise.resolve({code:0})};return lc(n.stdout,"stdout",e.onLine),lc(n.stderr,"stderr",e.onLine),o.done=n.exited.then((i)=>{let s=typeof i==="number"?i:0,a=s===0?void 0:Error(`exited with code ${s}`);if(this.running.get(e.name)===o)this.running.delete(e.name);if(e.onExit)e.onExit(s,a);return{code:s,err:a}}),this.running.set(e.name,o),o}async runOnce(e){if(e.args.length===0)throw M(He,"empty command");let t;try{t=Oy({cmd:e.shell?Fy(e.args):e.args,cwd:e.workDir||void 0,env:e.env,stdout:"pipe",stderr:"pipe",stdin:"ignore"})}catch(a){throw Re(He,`failed to run ${e.name}`,a)}let r="",n="",o=(a,c)=>{if(a==="stdout")r+=`${c}
2176
+ `}Tt();class ev{start;constructor(e){this.start=e}execute(e){return this.start({...e,profile:e.profile?ur(e.profile):void 0,services:e.services?.map((t)=>xu(t))})}}class tv{stop;constructor(e){this.stop=e}execute(e){return this.stop(e.map((t)=>xu(t)))}}class rv{restart;constructor(e){this.restart=e}execute(e,t){return this.restart(e.map((r)=>xu(r)),t)}}class nv{start;constructor(e){this.start=e}execute(e,t){return this.start.execute({profile:e,client_env:t})}}class ov{host;constructor(e){this.host=e}execute(){return this.host.startProxy()}}class iv{host;constructor(e){this.host=e}execute(){return this.host.stopProxy()}}class sv{host;constructor(e){this.host=e}execute(){return this.host.reload()}}class av{host;constructor(e){this.host=e}execute(){return this.host.refreshIdentity({probeServiceAccounts:!0})}}class sc{runner;constructor(e){this.runner=e}execute(e,t,r){return this.runner.run(e,t,r)}}class ac{execute(e,t,r){return tc(e,t,r)}}class cc{execute(e,t){return rc(e,t)}}class cv{host;constructor(e){this.host=e}execute(){return this.host.snapshot()}}class lc{execute(e,t){return ic(e,t)}}function xu(e){return zg(e)}function lv(e,t,r){let n=r??e,o=new ev((i)=>n.start(i));return{startService:o,stopService:new tv((i)=>n.stop(i)),restartService:new rv((i,s)=>n.restart(i,s)),startProfile:new nv(o),startProxy:new ov(e),stopProxy:new iv(e),reloadConfig:new sv(e),refreshIdentity:new av(e),runDoctor:new sc(t),getServiceStatus:new cv(e),getStartupPlan:new ac,getShutdownPlan:new cc,resolveStart:new lc}}import{createConnection as uT}from"net";var{spawn:pT}=globalThis.Bun;import{readdirSync as ZE}from"fs";import{join as dv,resolve as bu,sep as wu}from"path";function eT(e){let t=dv(cr(),"state"),r;try{r=ZE(t)}catch{return}let n=uv(bu(e)),o;for(let i of r){let s=rd(dv(t,i,"state.json"));if(!s)continue;let a=uv(bu(s.repo_root));if(!tT(a,n))continue;if(!o||a.length>o.length)o=a}return o}function dc(e,t,r=""){if(t!=="")return{repoRoot:bu(t),source:"explicit"};let n=e===""?process.cwd():e;try{let{repoRoot:i}=Zn(n,r);return{repoRoot:i,source:"config"}}catch{if(r!=="")return}let o=eT(n);return o?{repoRoot:o,source:"state-scan"}:void 0}function uv(e){let t=e.length>1&&e.endsWith(wu)?e.slice(0,-1):e;return process.platform==="win32"?t.toLowerCase():t}function tT(e,t){return t===e||t.startsWith(e.endsWith(wu)?e:`${e}${wu}`)}import{existsSync as fv,readFileSync as mv}from"fs";import{isAbsolute as rT,join as Su}from"path";import{parse as nT}from"dotenv";te();var Qi=["process","profile","dotenv","generated","keychain","secret_manager","defaults","vars","runtime"],oT=/^projects\/[^/]+\/secrets\/[^/]+(?:\/versions\/[^/]+)?$/,iT=["process","defaults","vars","runtime"];function sT(){return{name:"dotenv",load:(e)=>{let t=pv(e.repoRoot,e.profile);if(e.workDir!=="")Object.assign(t,pv(e.workDir,e.profile));return t}}}function aT(e){let t=e?.environment.sources??[];if(t.length===0)return[...Qi];let r=new Set([...iT,...t]),n=new Set(Qi),o=t.filter((s)=>!n.has(s)),i=[];for(let s of Qi){if(s==="defaults")i.push(...o);if(r.has(s))i.push(s)}return i}async function Zi(e,t){let r={},n=t.serviceCfg.working_dir;if(n!==""&&!rT(n))n=Su(e,n);let o={repoRoot:e,profile:t.profile,service:t.service,serviceCfg:t.serviceCfg,workDir:n,cfg:t.cfg},i=cT(t),s={process:t.includeProcess===!1?{}:t.clientEnv??Xo(),profile:Xi(t.profileEnv,t.cfg,i),dotenv:Xi(await sT().load(o),t.cfg,i),generated:{},keychain:t.sourceValues?.keychain??lT(o),secret_manager:t.sourceValues?.secret_manager??await dT(o,t.fetchSecret),defaults:Xi(t.serviceCfg.environment.defaults,t.cfg,i),vars:Xi(t.serviceCfg.environment.vars,t.cfg,i),runtime:t.runtime};for(let a of aT(t.cfg)){if(s[a]!==void 0){Object.assign(r,s[a]);continue}let c=t.pluginSources?.find((l)=>l.name===a);if(c)Object.assign(r,Xi(await c.load(o),t.cfg,i))}for(let a of t.serviceCfg.environment.required)if((r[a]??"").trim()==="")throw I(Q,`service ${t.service} missing required environment variable ${a}`);return r}function cT(e){let t={};if(e.cfg)for(let[r,n]of Object.entries(e.cfg.services)){let o={};for(let i of n.ports)if(!i.auto)o[i.name]=i.value;t[r]=o}if(e.assignedPorts)t[e.service]=e.assignedPorts;return t}function Xi(e,t,r){if(!t||Object.keys(e).length===0)return e;return Qd(e,t,r)}function lT(e){let t=e.cfg?.environment.sources??[];if(t.length>0&&!t.includes("keychain"))return{};let r={},n=new Set([...Object.keys(e.serviceCfg.environment.defaults),...Object.keys(e.serviceCfg.environment.vars),...e.serviceCfg.environment.required]);for(let o of n){let i=Su(kr(),"env",o);if(!fv(i))continue;try{r[o]=mv(i,"utf8").replace(/\n$/,"")}catch(s){throw Pe(Q,`unable to read keychain env ${o}`,s)}}return r}async function dT(e,t){if(!(e.cfg?.environment.sources??[]).includes("secret_manager"))return{};let n=e.cfg?.environment.secrets??{},o={};for(let[i,s]of Object.entries(n)){if(!oT.test(s))throw I(Q,`environment.secrets.${i} is not a Secret Manager resource`);if(!t)throw I(Q,"environment source secret_manager is not configured \u2014 set credentials or remove it from environment.sources");o[i]=await t(s)}return o}function pv(e,t){let r={},n=[".env",".env.development",".env.local"];if(t!=="")n.push(`.env.${t}`);for(let o of n){let i=Su(e,o);if(!fv(i))continue;try{let s=nT(mv(i));Object.assign(r,s)}catch(s){throw Pe(Q,`unable to read ${i}`,s)}}return r}function Xo(){let e={};for(let[t,r]of Object.entries(process.env))if(r!==void 0)e[t]=r;return e}function es(e,t,r,n,o){let i={DEVCTL_SERVICE_NAME:e,DEVCTL_ENVIRONMENT:o,SERVICE_HOST:t};if(n!=="")i.DEVCTL_PROXY_URL=n;if(r.http!==void 0)i.SERVICE_PORT=String(r.http);else{let s=Object.values(r)[0];if(s!==void 0)i.SERVICE_PORT=String(s)}for(let[s,a]of Object.entries(r))i[`${s.toUpperCase()}_PORT`]=String(a);return i}function Qo(e){return{...e}}te();var fT=50,mT=8000,hT=15000,gT=200,vT=30000,hv=86400000,yT=new Set(["logs","logs_page","logs_stats"]);function xT(e){if(e.legacy)return"attached daemon predates the client/daemon compatibility handshake";return`attached daemon speaks RPC protocol ${e.daemonProtocol??"unknown"}; this client speaks ${Vi}`}function bT(e){if(e.compatible&&e.daemonVersion!==void 0&&e.daemonVersion!==jt)return`attached daemon is devctl ${e.daemonVersion}; this client is ${jt} (run \`devctl down\` then start again to update it)`;return}function ku(e,t){if(!e.compat.compatible&&!yT.has(t))throw Te(lt,xT(e.compat),"run `devctl down` to stop it, then start again")}class gv{socket;buf="";pending=new Map;listeners=[];nextID=0;compat={compatible:!0,legacy:!1};session="";constructor(e){this.socket=e,e.on("data",(t)=>{this.buf+=t.toString("utf8");let r=this.buf.split(`
2177
+ `);this.buf=r.pop()??"";for(let n of r){if(n.trim()==="")continue;this.onLine(n)}}),e.on("error",(t)=>this.rejectPending(t instanceof Error?t:Error(String(t)))),e.on("close",()=>this.rejectPending(Error("supervisor connection closed")))}onLine(e){let t;try{t=JSON.parse(e)}catch{return}if(t.event){for(let n of this.listeners)n(t.event);return}let r=t.id?this.pending.get(t.id):void 0;if(!r)return;if(this.pending.delete(t.id??""),clearTimeout(r.timer),t.error){r.reject(dh({error:t.error,kind:t.kind,hint:t.hint,service:t.service}));return}r.resolve(t.result)}onEvent(e){return this.listeners.push(e),()=>{let t=this.listeners.indexOf(e);if(t>=0)this.listeners.splice(t,1)}}call(e,t,r=vT){this.nextID+=1;let n=String(this.nextID);return new Promise((o,i)=>{let s=setTimeout(()=>{this.pending.delete(n),i(Error(`${e} timed out after ${r}ms`))},r);this.pending.set(n,{resolve:o,reject:i,timer:s}),this.socket.write(JSON.stringify({id:n,method:e,params:t})+`
2178
+ `)})}close(){this.rejectPending(Error("supervisor connection closed")),this.socket.destroy()}rejectPending(e){for(let t of this.pending.values())clearTimeout(t.timer),t.reject(e);this.pending.clear()}}function vv(e,t){let r=Qs(e),n=Date.now()+t;return new Promise((o,i)=>{let s=()=>{let a=uT(r);a.once("connect",()=>{let c=new gv(a);wT(c).finally(()=>o(c))}),a.once("error",(c)=>{if(a.destroy(),Date.now()>=n){i(Te(lt,"supervisor is not running","run `devctl start` or `devctl attach` after starting services"));return}setTimeout(s,fT)})};s()})}async function wT(e){let t;try{t=await e.call("ping",null,mT)}catch{return}let r=ST(t)?t:{};if(typeof r.session==="string")e.session=r.session;let n=typeof r.protocol==="number"?r.protocol:void 0,o=typeof r.version==="string"?r.version:void 0;if(n===void 0){e.compat={compatible:!1,legacy:!0,daemonVersion:o};return}e.compat={compatible:n===Vi,legacy:!1,daemonVersion:o,daemonProtocol:n}}function ST(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}async function po(e){try{return await vv(e,gT)}catch{return}}function kT(e,t,r,n){return r?[e,...n]:[e,t,...n]}async function yv(e,t){let r=await po(e);if(r)return r;km(e);let n=Pi(e),o=kT(process.execPath,process.argv[1]??"",Bun.isStandaloneExecutable===!0,["--config",t,"_supervisor","--repo",e]),i=pT({cmd:o,env:{...process.env},stdout:"ignore",stderr:Bun.file(n),stdin:"ignore",detached:!0});try{let s=await PT(e,hT,i.exited);return i.unref(),s}catch{throw await RT(i),Te(lt,"supervisor failed to start",`see ${n} for details`)}}var CT=1000;function PT(e,t,r){return new Promise((n,o)=>{let i=!1,s=(a)=>{if(i)return;i=!0,a()};r.then((a)=>{s(()=>o(Error(`supervisor exited (code ${a}) before accepting connections`)))}),vv(e,t).then((a)=>s(()=>n(a)),(a)=>s(()=>o(a)))})}async function RT(e){let t=e.pid;try{e.kill("SIGKILL")}catch{}if(t!==void 0&&t>0&&process.platform!=="win32")try{process.kill(-t,"SIGKILL")}catch{try{process.kill(t,"SIGKILL")}catch{}}await Promise.race([e.exited.then(()=>{return}),new Promise((r)=>{setTimeout(r,CT)})])}class uc{cfg;client;previousPersisted;constructor(e){this.cfg=e}async start(e){return await this.call("start",{...e,client_env:Xo()})}async stop(e){await this.call("stop",{services:e})}async restart(e,t){await this.call("restart",{services:e,cascade:t===!0,client_env:Xo()})}async runTask(e){return await this.call("run_task",{name:e,client_env:Xo()},hv)}async execService(e,t,r=!1){return await this.call("exec",{service:e,command:t,print_env:r,client_env:Xo()},hv)}async status(){return await this.call("status",null)}async refreshAuth(){return await this.call("auth_refresh",null)}async configSnapshot(){return await this.call("config_snapshot",null)}async logs(e){return(await this.call("logs",e)).events??[]}async logsPage(e){return await this.call("logs_page",e)}async logsStats(e){return await this.call("logs_stats",e)}async proxyStart(){await this.call("proxy_start",null)}async proxyStop(){await this.call("proxy_stop",null)}async mcpStart(e){await this.call("mcp_start",e??null)}async mcpStop(){await this.call("mcp_stop",null)}async mcpSetTools(e){return(await this.call("mcp_set_tools",{disabled:[...e]})).disabled_tools??[]}async reload(){return await this.call("reload",null)}async invalidateAuth(){await this.call("auth_invalidate",null)}onEvent(e){if(this.client)return this.client.onEvent(e);return()=>{return}}async shutdown(e){if(!this.client)return;let t=Math.max(5000,this.cfg.shutdown.grace_seconds*1000+2000);await this.client.call("shutdown",{stop_services:e.stopServices},t)}async close(e){if(!this.client)return;try{if(e?.shutdownSupervisor===!0&&e.detach!==!0)await this.shutdown({stopServices:!0})}finally{this.client.close()}}async call(e,t,r){if(!this.client)throw Pe(lt,"supervisor is not running",Error("no client"));return ku(this.client,e),this.client.call(e,t,r)}}function ts(e){let t=e&&bT(e.compat);if(t)process.stderr.write(`warning: ${t}
2179
+ `)}async function xv(e,t,r=""){let n=dc(e,t,r);if(!n)throw Te(lt,"no devctl configuration found","run `devctl setup`, create a .devctl/config.yaml in the repository root, or pass --repo");let o=await po(n.repoRoot);return ts(o),{repoRoot:n.repoRoot,client:o}}async function bv(e,t,r,n){let o=n?.allowMissingConfig===!0?ro(e,t):nn(e,t),i=new uc(o);if(!r)return i.client=await po(o.repoRoot),ts(i.client),i;return i.client=await yv(o.repoRoot,o.configPath),ts(i.client),i}async function wv(e){let t=new uc(Jn());return t.client=e,ts(t.client),t.cfg=await t.configSnapshot(),t}async function Sv(e,t){let r=dc(e,"",t),n=r?await po(r.repoRoot):void 0;if(!n)throw Te(lt,"supervisor is not running","run `devctl start` before `devctl attach`");return wv(n)}async function kv(e,t){let r=dc(e,"",t),n=r?await po(r.repoRoot):void 0;if(n)return wv(n);let o=nn(e,t),i=new uc(o),s=fn(o.repoRoot);if(i.client=await yv(o.repoRoot,o.configPath),ts(i.client),i.cfg=await i.configSnapshot(),s&&s.processes.length>0)i.previousPersisted=s;return i}import{realpathSync as ET}from"fs";var TT="https://api.github.com/repos/amr-m-abdelgawad/devctl/releases/latest";function AT(){let e=Bun.isStandaloneExecutable===!0,t=e?"":Av(process.argv[1]??"");return _v({scriptPath:t,execPath:Av(process.execPath),standalone:e})}async function LT(e=fetch,t=AT()){try{let r=await e(TT,{headers:{Accept:"application/vnd.github+json"}});if(!r.ok)return Tv(t);let o=((await r.json()).tag_name??"").replace(/^v/,"");return{current:jt,latest:o,newer:o!==""&&Ev(o,jt)>0,hint:t.hint,kind:t.kind,command:t.command}}catch{return Tv(t)}}async function DT(e,t=Lv){return t(e)}async function Lv(e,t=!1){let r=Bun.spawn([...e],{stdout:t?"inherit":"pipe",stderr:t?"inherit":"pipe",stdin:"ignore"});if(t)return{code:await r.exited,stdout:"",stderr:""};let n=await new Response(r.stdout).text(),o=await new Response(r.stderr).text();return{code:await r.exited,stdout:n,stderr:o}}function Dv(e=fetch){return{check:()=>LT(e),apply:(t,r)=>r===!0?Lv(t,!0):DT(t)}}function Tv(e){return{current:jt,latest:"",newer:!1,hint:e.hint,kind:e.kind,command:e.command}}function Av(e){if(e==="")return"";try{return ET(e)}catch{return e}}function Iv(e){let t=e?.tokens??new co(60000,Rn(),void 0),r=e?.doctorHost??Yo({tokens:t}),n=Dv(),o={loadTuiConfig:Io,saveTuiPreferences:Am,resolveTuiOverridePath:ia,userTuiConfigPath:oa,listSessions:nh,loadSessionEvents:oh,loadPath:Bi,validateConfigText:ji,discover:Zn,configDiff:Pn,openTui:kv,findDaemon:xv,tryDial:po,assertMethodAllowed:ku,readPersistedState:fn,bootstrapLogPath:Pi,exportsDir:Xs,resolveExportPath:th,writeLogExport:_d,openInFileManager:rh,freePort:xa,createStarterConfig:wh,runSetup:(i,s,a)=>Sh(o,i,s,a),readTextFile:(i)=>MT(i,"utf8"),writeTextFile:(i,s)=>OT(i,s),fileExists:IT,load:nn,loadOrEmpty:ro,validate:eo,detectGoogle:so,loginGoogle:Mg,logoutGoogle:Og,refreshUserToken:async(i="user")=>t.refresh(i,"",[]),runDoctor:new sc(e?.doctorRunner??ec(r)),getStartupPlan:new ac,getShutdownPlan:new cc,resolveStart:new lc,formatDoctor:Vg,openController:bv,openAttach:Sv,checkUpdate:()=>n.check(),applyUpdate:(i,s)=>n.apply(i,s),formatUpdateStatus:Rv};return o}import{existsSync as nL,unlinkSync as oL}from"fs";import{connect as $T}from"net";Tt();var FT=2000,NT=200,HT=300;function Mv(e){return{lookup(t){let r=t.toLowerCase();return["","http","tcp","command","process"].includes(r)||e.some((o)=>o.name.toLowerCase()===r)?{check:(o,i)=>jT({...o,type:t},i.pid,i.ports,i.workDir,i.env,e)}:void 0}}}async function jT(e,t,r,n,o,i=[]){let s=e.timeout_seconds>0?e.timeout_seconds*1000:FT,a=e.type.toLowerCase(),c=i.find((l)=>l.name.toLowerCase()===a);if(c)try{let l=await c.check(e,{pid:t,ports:r,workDir:n,env:o});return{status:l.status,message:l.message}}catch(l){return{status:Oe,message:l instanceof Error?l.message:String(l)}}if(a==="http")return BT(e.url,s);if(a==="tcp")return WT(UT(e.address,r),s);if(a==="command")return GT(e.command.args,e.command.shell,n,o,s);if(a==="process"||a===""){if(t>0&&Qt(t))return{status:Bt,message:"process running"};return{status:Oe,message:"process not running"}}return{status:Oe,message:`unknown health type ${e.type}`}}async function BT(e,t){let r=new AbortController,n=setTimeout(()=>r.abort(),t);try{let o=await fetch(e,{signal:r.signal});if(o.status>=NT&&o.status<HT)return{status:Bt,message:String(o.status)};return{status:Oe,message:String(o.status)}}catch(o){return{status:Oe,message:o instanceof Error?o.message:String(o)}}finally{clearTimeout(n)}}function UT(e,t){if(e!=="")return e;if(t.http!==void 0)return`127.0.0.1:${t.http}`;let r=Object.values(t)[0];return r!==void 0?`127.0.0.1:${r}`:""}function WT(e,t){return new Promise((r)=>{if(e===""){r({status:Oe,message:"no tcp address"});return}let n=e.lastIndexOf(":"),o=Number.parseInt(e.slice(n+1),10),i=e.slice(0,n).replace(/^\[|\]$/g,"");if(n<1||!Number.isFinite(o)){r({status:Oe,message:`invalid tcp address ${e}`});return}let s=$T({host:i,port:o},()=>{s.end(),r({status:Bt,message:"connected"})});s.setTimeout(t),s.on("error",(a)=>{r({status:Oe,message:a.message})}),s.on("timeout",()=>{s.destroy(),r({status:Oe,message:"timeout"})})})}async function GT(e,t,r,n,o){if(e.length===0)return{status:Oe,message:"empty health command"};let i=t?process.platform==="win32"?["cmd.exe","/c",e.join(" ")]:["/bin/sh","-c",e.join(" ")]:e,s;try{s=Bun.spawn({cmd:i,cwd:r===""?void 0:r,env:n,stdout:"ignore",stderr:"ignore"})}catch(l){return{status:Oe,message:l instanceof Error?l.message:String(l)}}let a=setTimeout(()=>s.kill("SIGKILL"),o),c=await s.exited;if(clearTimeout(a),c===0)return{status:Bt,message:"ok"};return{status:Oe,message:`exit ${c}`}}te();var{spawn:zv}=globalThis.Bun;var{spawn:KT}=globalThis.Bun;async function Ov(e,t){if(e<=0)return;try{process.kill(-e,t);return}catch{try{process.kill(e,t)}catch{return}}}async function $v(e){if(!Qt(e))return;let t=await rs(["ps","-p",String(e),"-o","command="]),r=Date.now(),n=qT(await rs(["ps","-p",String(e),"-o","etime="])),o=await VT(e);return{pid:e,command:t.trim(),cwd:o,startTime:n===void 0?void 0:new Date(r-n).toISOString()}}function qT(e){let t=e.trim();if(t==="")return;let r=t.indexOf("-"),n=r>=0?t.slice(0,r):"0",i=(r>=0?t.slice(r+1):t).split(":");if(i.length!==2&&i.length!==3)return;let s=Number(n),a=i.length===3?Number(i[0]):0,c=Number(i.length===3?i[1]:i[0]),l=Number(i.at(-1));if(!Number.isInteger(s)||s<0||!Number.isInteger(a)||a<0||a>23||!Number.isInteger(c)||c<0||c>59||!Number.isInteger(l)||l<0||l>59)return;return(((s*24+a)*60+c)*60+l)*1000}async function VT(e){let r=(await rs(["lsof","-a","-p",String(e),"-d","cwd","-Fn"])).split(`
2180
+ `).find((i)=>i.startsWith("n"));if(r&&r.length>1)return r.slice(1).trim();let n=await rs(["pwdx",String(e)]),o=n.indexOf(":");if(o>=0)return n.slice(o+1).trim();return""}async function rs(e){try{let t=KT({cmd:e,stdout:"pipe",stderr:"ignore"}),r=t.stdout?await new Response(t.stdout).text():"";return await t.exited,r}catch{return""}}async function Fv(e){let t=new Map;if(e.length===0)return t;let r=await rs(["ps","-o","pid=,pcpu=,rss=","-p",e.join(",")]);for(let n of r.split(`
2181
+ `)){let o=n.trim();if(o==="")continue;let[i,s,a]=o.split(/\s+/),c=Number(i),l=Number(s),d=Number(a);if(Number.isFinite(c)&&Number.isFinite(l)&&Number.isFinite(d))t.set(c,{pid:c,cpuPercent:l,memoryKB:d})}return t}function Nv(e,t){if(e.length===0||t==="")return!1;let r=e.join(" ");if(t.includes(r))return!0;let o=(e[0]??"").split(/[/\\]/).pop()??"";if(o!==""&&t.includes(o))return!0;let i=e[e.length-1]??"";return i!==""&&t.includes(i)}var{spawn:Hv}=globalThis.Bun;var zT=1024;async function jv(e,t){if(e<=0)return;try{await Hv({cmd:["taskkill","/PID",String(e),"/T","/F"],stdout:"ignore",stderr:"ignore"}).exited}catch{return}}async function Bv(e){let t=YT(nd(e),e);if(t)return t;if(!Qt(e))return;let r=await Cu(["powershell","-NoProfile","-Command",`Get-CimInstance Win32_Process -Filter "ProcessId=${e}" | Select-Object CommandLine,ExecutablePath,CreationDate | ConvertTo-Json -Compress`]),n=XT(r);if(n&&n.command!=="")return{pid:e,command:n.command,cwd:n.cwd,startTime:n.startTime};let o=await Cu(["powershell","-NoProfile","-Command",`Get-Process -Id ${e} -ErrorAction SilentlyContinue | Select-Object Path,StartTime | ConvertTo-Json -Compress`]),i=JT(o);if(!i&&!n)return;return{pid:e,command:n?.command||i?.command||"",cwd:n?.cwd||i?.cwd||"",startTime:n?.startTime||i?.startTime}}function YT(e,t){let r=String(t);for(let n of e.split(/\r?\n/)){if(!n.includes(r))continue;let i=n.split(",").map((s)=>s.replace(/^"|"$/g,"").trim())[0]??"";if(i===""||i.toLowerCase().includes("info:"))continue;return{pid:t,command:i,cwd:""}}return}function JT(e){let t=e.trim();if(t==="")return;try{let r=JSON.parse(t),n=(r.Path||"").trim();if(n==="")return;return{command:n,cwd:mc(n),startTime:r.StartTime}}catch{let r=fc(t,"Path");if(r==="")return;return{command:r,cwd:mc(r)}}}function XT(e){let t=e.trim();if(t==="")return;try{let r=JSON.parse(t),n=(r.CommandLine||r.ExecutablePath||"").trim(),o=(r.ExecutablePath||"").trim(),i=mc(o);return{command:n,cwd:i,startTime:r.CreationDate||void 0}}catch{return{command:fc(t,"CommandLine")||fc(t,"ExecutablePath"),cwd:mc(fc(t,"ExecutablePath"))}}}async function Uv(e){let t=new Map;if(e.length===0)return t;let r=e.filter((o)=>o>0).join(",");if(r==="")return t;let n=await Cu(["powershell","-NoProfile","-Command",`${r}.Split(',') | ForEach-Object { Get-Process -Id $_ -ErrorAction SilentlyContinue } | Select-Object Id,CPU,WorkingSet64 | ConvertTo-Json -Compress`]);return QT(n)}function QT(e){let t=new Map,r=e.trim();if(r==="")return t;try{let n=JSON.parse(r),o=Array.isArray(n)?n:[n];for(let i of o){if(!i||typeof i!=="object")continue;let s=i,a=Number(s.Id),c=Number(s.CPU??0),l=Number(s.WorkingSet64??0)/zT;if(Number.isFinite(a)&&a>0&&Number.isFinite(c)&&Number.isFinite(l))t.set(a,{pid:a,cpuPercent:c,memoryKB:l})}}catch{return t}return t}function mc(e){if(e==="")return"";let t=e.replace(/\//g,"\\"),r=t.lastIndexOf("\\");return r>0?t.slice(0,r):""}function fc(e,t){let r=`${t}=`,n=e.split(/\r?\n/).find((o)=>o.startsWith(r));return n?n.slice(r.length).trim():""}async function Cu(e){try{let t=Hv({cmd:e,stdout:"pipe",stderr:"ignore"}),r=t.stdout?await new Response(t.stdout).text():"";return await t.exited,r}catch{return""}}te();var{spawn:Pu}=globalThis.Bun;async function Gv(e){await t0(e.runtime,e.containerName);let t=ZT(e),r=await Zo(e.runtime,t,e.env);if(r.code!==0||r.stdout.trim()==="")throw I(He,`failed to start ${e.containerName}: ${r.stderr.trim()||"container runtime returned no id"}`);let n=r.stdout.trim().split(/\s+/)[0]??"";return Vv(e.runtime,e.containerName,n,e.onLine,e.onExit)}function ZT(e){let t=["run","--detach","--name",e.containerName,"--label","devctl.managed=true"];for(let[r,n]of Object.entries(e.ports)){let o=e.targetPorts[r]??n;t.push("--publish",`127.0.0.1:${n}:${o}`)}for(let r of Object.keys(e.env).sort())t.push("--env",r);for(let r of e.volumes)t.push("--volume",r);return t.push(e.image,...e.command),t}var e0=new Set(["PATH","HOME","HOSTNAME","PWD","OLDPWD","SHLVL","_","TMPDIR"]);function Kv(e){return Object.fromEntries(Object.entries(e).filter(([t])=>!e0.has(t)))}async function qv(e,t,r,n){let o=await Zo(e,["inspect","--format","{{.Id}} {{.State.Running}}",t],process.env);if(o.code!==0)return;let[i,s]=o.stdout.trim().split(/\s+/);if(!i||s!=="true")return;return Vv(e,t,i,r,n)}function Vv(e,t,r,n,o){let i=!0,s=Pu({cmd:[e,"logs","--follow",r],stdout:"pipe",stderr:"pipe",stdin:"ignore"});Wv(s.stdout,"stdout",n),Wv(s.stderr,"stderr",n);let a=Pu({cmd:[e,"wait",r],stdout:"pipe",stderr:"pipe",stdin:"ignore"}),c=Promise.all([new Response(a.stdout).text(),a.exited]).then(async([l,d])=>{i=!1,s.kill();let u=Number.parseInt(l.trim(),10),f=Number.isFinite(u)?u:d??1,p=f===0?void 0:Error(`container exited with code ${f}`);return await Zo(e,["rm","--force",r],process.env),o?.(f,p),{code:f,err:p}});return{id:r,runtime:e,containerName:t,done:c,running:()=>i,stop:async(l)=>{let d=Math.max(1,Math.ceil(l/1000));await Zo(e,["stop","--time",String(d),r],process.env),await c}}}async function t0(e,t){let r=await Zo(e,["inspect","--format","{{.State.Running}}",t],process.env);if(r.code===0&&r.stdout.trim()==="false")await Zo(e,["rm","--force",t],process.env)}async function Zo(e,t,r){try{let n=Pu({cmd:[e,...t],env:{...process.env,...r},stdout:"pipe",stderr:"pipe",stdin:"ignore"}),[o,i,s]=await Promise.all([new Response(n.stdout).text(),new Response(n.stderr).text(),n.exited]);return{code:s??1,stdout:o,stderr:i}}catch(n){throw Pe(He,`unable to run ${e}`,n)}}async function Wv(e,t,r){if(!e||typeof e==="number"||!r)return;let n=e.getReader(),o=new TextDecoder,i="";for(;;){let s=await n.read();if(s.done)break;i+=o.decode(s.value,{stream:!0});let a=i.split(`
2182
+ `);i=a.pop()??"";for(let c of a)r(t,c.replace(/\r$/,""))}if(i!=="")r(t,i)}var r0=1e4,n0=2000,o0=500;class Ru{isRunning(e){let t=this.running.get(e);return t!==void 0&&ns(t)}running=new Map;async start(e){let t=this.running.get(e.name);if(t&&ns(t))return t;if(this.running.delete(e.name),e.args.length===0)throw I(He,"empty command");let r=e.shell?Xv(e.args):e.args,n;try{n=zv({cmd:r,cwd:e.workDir===""?void 0:e.workDir,env:e.env,stdout:e.captureStdout===!1?"ignore":"pipe",stderr:e.captureStderr===!1?"ignore":"pipe",stdin:"ignore",detached:process.platform!=="win32"})}catch(i){throw Pe(He,`failed to start ${e.name}`,i)}let o={name:e.name,pid:n.pid??0,startTime:new Date,workDir:e.workDir,args:[...e.args],proc:n,done:Promise.resolve({code:0})};return hc(n.stdout,"stdout",e.onLine),hc(n.stderr,"stderr",e.onLine),o.done=n.exited.then((i)=>{let s=typeof i==="number"?i:0,a=s===0?void 0:Error(`exited with code ${s}`);if(this.running.get(e.name)===o)this.running.delete(e.name);if(e.onExit)e.onExit(s,a);return{code:s,err:a}}),this.running.set(e.name,o),o}async runOnce(e){if(e.args.length===0)throw I(He,"empty command");let t;try{t=zv({cmd:e.shell?Xv(e.args):e.args,cwd:e.workDir||void 0,env:e.env,stdout:"pipe",stderr:"pipe",stdin:"ignore"})}catch(a){throw Pe(He,`failed to run ${e.name}`,a)}let r="",n="",o=(a,c)=>{if(a==="stdout")r+=`${c}
640
2183
  `;else n+=`${c}
641
- `;e.onLine?.(a,c)},i=[lc(t.stdout,"stdout",o),lc(t.stderr,"stderr",o)],s=await t.exited;return await Promise.all(i),{code:typeof s==="number"?s:0,stdout:r,stderr:n}}async startContainer(e){let t=this.running.get(e.name);if(t&&Qi(t))return t;let r=await Ay({...e,env:Ly(e.env)}),n={name:e.name,pid:0,startTime:new Date,workDir:e.workDir,args:[...e.command],done:r.done,container:r};return r.done.finally(()=>{if(this.running.get(e.name)===n)this.running.delete(e.name)}),this.running.set(e.name,n),n}async adoptContainer(e){let t=this.running.get(e.name);if(t&&Qi(t))return t;let r=await Dy(e.runtime,e.containerName,e.onLine,e.onExit);if(!r)return;let n={name:e.name,pid:0,startTime:new Date,workDir:e.workDir,args:[],done:r.done,container:r};return r.done.finally(()=>{if(this.running.get(e.name)===n)this.running.delete(e.name)}),this.running.set(e.name,n),n}adopt(e){let t=this.running.get(e.name);if(t&&Qi(t))return t;if(!Qt(e.pid))throw M(He,`cannot adopt ${e.name}: pid ${e.pid} is not running`);let r={name:e.name,pid:e.pid,startTime:e.startTime,workDir:e.workDir,args:[...e.args],done:Promise.resolve({code:0})};return r.done=I0(e.pid).then((n)=>{if(this.running.get(e.name)===r)this.running.delete(e.name);if(e.onExit)e.onExit(n,n===0?void 0:Error(`exited with code ${n}`));return{code:n}}),this.running.set(e.name,r),r}async stop(e,t){let r=this.running.get(e);if(!r)return;let n=t>0?t:L0;if(r.container){await r.container.stop(n),this.running.delete(e);return}if(await Iy(r.pid,"SIGTERM"),!await Hy(r.done,n)){if(await Iy(r.pid,"SIGKILL"),!await Hy(r.done,D0))throw M(He,`process ${e} did not exit after SIGKILL`)}this.running.delete(e)}get(e){return this.running.get(e)}all(){return[...this.running.values()]}}function Qi(e){if(e.container)return e.container.running();if(e.proc)return e.proc.exitCode===null&&!e.proc.killed;return Qt(e.pid)}async function Iy(e,t){if(process.platform==="win32"){await Py(e,t);return}await wy(e,t)}async function By(e){if(process.platform==="win32")return _y(e);return Sy(e)}async function Wy(e){if(process.platform==="win32")return Ey(e);return ky(e)}var O0=2000;function Zi(e,t){if(e.workDir!==""&&t.cwd!==""&&Ny(e.workDir)!==Ny(t.cwd))return!1;if(!Cy(e.args,t.command))return!1;let r=$y(e.startTime),n=$y(t.startTime);if(r!==void 0&&n!==void 0)return Math.abs(r-n)<=O0;return!0}function $y(e){if(e===void 0)return;if(e instanceof Date)return Number.isNaN(e.getTime())?void 0:e.getTime();let t=Date.parse(e);return Number.isNaN(t)?void 0:t}function Fy(e){if(process.platform==="win32")return["cmd.exe","/c",e.join(" ")];return["/bin/sh","-c",e.join(" ")]}function Ny(e){return e.replace(/\\/g,"/").replace(/\/+$/,"")}async function I0(e){while(Qt(e))await $0(M0);return 0}async function lc(e,t,r){if(!e||typeof e==="number"||!r)return;let n=e.getReader(),o=new TextDecoder,i="";for(;;){let{value:s,done:a}=await n.read();if(a){if(i!=="")r(t,i);return}i+=o.decode(s,{stream:!0});let c=i.split(`
642
- `);i=c.pop()??"";for(let l of c)r(t,l.replace(/\r$/,""))}}async function Hy(e,t){return Promise.race([e.then(()=>!0),new Promise((r)=>{setTimeout(()=>r(!1),t)})])}function $0(e){return new Promise((t)=>{setTimeout(t,e)})}import{existsSync as F0,readFileSync as N0,writeFileSync as H0}from"fs";var jy={exists:(e)=>F0(e),readText:(e)=>N0(e,"utf8"),writeText:(e,t)=>H0(e,t)};mt();var bu=3,B0=3,W0=10,es={shouldRestart(e){if(!e.enabled||e.policy===ma)return!1;if(e.retryCount>=e.maxRetries)return!1;if(e.policy===ha)return!0;return e.policy===ga&&e.exitCode!==0},fromService(e,t,r){return{policy:ba(e.restart),enabled:e.restart.enabled!==!1,exitCode:r,retryCount:t,maxRetries:e.restart.max_retries>0?e.restart.max_retries:bu}}},wu={shouldRestartUnhealthy(e,t=B0){return e>=t},shouldResetRestartBudget(e,t=W0){return e>=t}},dc={timeoutMs(e,t){let r=e.startup.timeout_seconds;return r>0?r*1000:t},waitForHealthy(e){return e.startup.wait_for_healthy}};Tt();te();var j0=2,G0=2000,U0=3,K0=10;class Su{host;processes;clock;actions;generation=new Map;healthTimers=new Map;restarts=new Map;unhealthyStreak=new Map;healthyStreak=new Map;restartTimers=new Map;constructor(e,t,r,n){this.host=e;this.processes=t;this.clock=r;this.actions=n}forget(e){this.clearHealthWatch(e),this.clearRestartTimer(e),this.restarts.delete(e),this.bumpGeneration(e),this.unhealthyStreak.delete(e),this.healthyStreak.delete(e)}dispose(){this.clearAllRestartTimers();for(let e of this.generation.keys())this.bumpGeneration(e);for(let e of this.healthTimers.keys())this.clearHealthWatch(e)}onExit(e,t,r,n){if(!this.isCurrentGeneration(e,t))return;this.clearHealthWatch(e);let o=this.host().runtimes.get(e);if(o?.state===lt)return;if(o&&(o.state===Rr||o.state===_t)){this.host().setState(e,_t,Et,0,""),this.host().bus.publish(Se(ta,e,{code:r}));return}let i=this.host().cfg.services[e],s=n?.message??`exited with code ${r}`,a=this.restarts.get(e)??0;if((i?es.shouldRestart(es.fromService(i,a,r)):!1)&&i){this.host().setState(e,fr,Et,0,s),this.bumpRestartCount(e,a+1),this.host().persistState(),this.armRestart(e,i,t,a,()=>{let l=this.host().serviceProfile.get(e)??this.host().profile,d=this.host().serviceProfileEnv.get(e)??this.host().profileEnv;this.actions.startOne(e,l,d,!1).catch((u)=>this.host().log(e,"ERROR",A(u)))});return}this.host().fail(e,M("process_start",s))}startHealth(e,t,r,n,o,i,s){let a=this.healthTimers.get(e);if(a)clearInterval(a);if(t.health.type===""){this.setHealth(e,Wt,"no health check");return}let c=t.health.interval_seconds>0?t.health.interval_seconds*1000:G0,l=Date.parse(this.host().runtimes.get(e)?.startTime??"")||this.clock.unixMs(),d=()=>{(t.container&&t.health.type.toLowerCase()==="process"?Promise.resolve(this.processes.isRunning(e)?{status:Wt,message:"container running"}:{status:Me,message:"container not running"}):Promise.resolve().then(()=>this.host().healthCheckers.lookup(t.health.type)?.check(t.health,{pid:r,ports:n,workDir:o,env:i})??{status:Me,message:`unknown health type ${t.health.type}`})).catch((f)=>({status:Me,message:A(f)})).then((f)=>{if(!this.isCurrentGeneration(e,s))return;let p=this.host().runtimes.get(e);if(p&&Gy(p.state))return;if(f.status===Me&&this.clock.unixMs()-l<t.health.start_period_seconds*1000){this.host().logs.append({timestamp:this.clock.isoNow(),service:e,source:"health",level:"INFO",message:`health check still in start period: ${f.message}`,pid:r});return}this.setHealth(e,f.status,f.message),this.host().logs.append({timestamp:this.clock.isoNow(),service:e,source:"health",level:q0(f.status),message:`health ${f.status} ${f.message}`,pid:r}),this.maybeRestartUnhealthy(e,t,f.status,s)})};d(),this.healthTimers.set(e,setInterval(d,c))}bumpRestartCount(e,t){this.restarts.set(e,t);let r=this.host().runtimes.get(e);if(r)r.restarts=t}resetRestartCount(e){this.healthyStreak.set(e,0),this.bumpRestartCount(e,0)}bumpGeneration(e){let t=(this.generation.get(e)??0)+1;return this.generation.set(e,t),this.healthyStreak.set(e,0),t}isCurrentGeneration(e,t){return this.generation.get(e)===t}scheduleRestart(e,t,r){this.clearRestartTimer(e);let n=setTimeout(()=>{this.restartTimers.delete(e),r()},t);this.restartTimers.set(e,n)}armRestart(e,t,r,n,o){let i=t.restart.backoff_seconds>0?t.restart.backoff_seconds:j0;this.scheduleRestart(e,i*2**n*1000,()=>{if(this.isCurrentGeneration(e,r))o()})}clearRestartTimer(e){let t=this.restartTimers.get(e);if(t)clearTimeout(t),this.restartTimers.delete(e)}clearAllRestartTimers(){for(let e of this.restartTimers.values())clearTimeout(e);this.restartTimers.clear()}setHealth(e,t,r){let n=this.host().runtimes.get(e);if(!n||Gy(n.state))return;if(n.health=t,n.state===Be||n.state===pr||n.state===Nr)n.state=t===Wt?pr:t===Me?Nr:n.state;this.host().bus.publish(Se(Rm,e,{health:t,message:r}))}maybeRestartUnhealthy(e,t,r,n){if(r!==Me){if(this.unhealthyStreak.set(e,0),r===Wt)this.maybeForgiveRestarts(e,t);return}this.healthyStreak.set(e,0);let o=t.restart.policy||(t.restart.enabled?"on_failure":"never");if(o!=="on_failure"&&o!=="always")return;if(this.restartTimers.has(e))return;let i=(this.unhealthyStreak.get(e)??0)+1;this.unhealthyStreak.set(e,i);let s=t.health.unhealthy_threshold>0?t.health.unhealthy_threshold:U0;if(!wu.shouldRestartUnhealthy(i,s))return;this.unhealthyStreak.set(e,0);let a=this.restarts.get(e)??0,c=t.restart.max_retries>0?t.restart.max_retries:bu;if(a>=c){this.host().log(e,"WARN",`unhealthy after ${i} consecutive checks but the restart limit (${c}) has already been reached; not restarting`);return}this.bumpRestartCount(e,a+1),this.host().log(e,"WARN",`restarting after ${i} consecutive unhealthy checks (attempt ${a+1}/${c})`),this.armRestart(e,t,n,a,()=>{this.actions.restart([e],{auto:!0}).catch((l)=>this.host().log(e,"ERROR",A(l)))})}maybeForgiveRestarts(e,t){let r=(this.healthyStreak.get(e)??0)+1;this.healthyStreak.set(e,r);let n=t.health.healthy_reset_threshold>0?t.health.healthy_reset_threshold:K0;if(wu.shouldResetRestartBudget(r,n)&&(this.restarts.get(e)??0)>0)this.host().log(e,"INFO",`restart count reset after ${r} consecutive healthy checks`),this.bumpRestartCount(e,0)}clearHealthWatch(e){let t=this.healthTimers.get(e);if(t)clearInterval(t),this.healthTimers.delete(e)}}function Gy(e){return e===fr||e===Rr||e===_t||e===lt}function q0(e){return e===Me?"WARN":e===Wt?"INFO":"DEBUG"}mt();te();Fr();te();Tt();var ku=[Rr,lt,fr,Be,_t],V0={[$h]:[_t,rn,Be,lt,fr,Rr],[_t]:[rn,Be,lt],[rn]:[Be,lt,_t,Rr],[Be]:ku,[pr]:ku,[Nr]:ku,[Rr]:[_t,lt],[lt]:[rn,_t,Be],[fr]:[rn,Be,lt,_t]};function ts(e,t){if(e===t)return!0;return(V0[e]??[]).includes(t)}function Uy(e,t){if(!ts(e,t))throw M(ee,`illegal service lifecycle transition ${e} \u2192 ${t}`);return t}Tt();var J0=100,Ky=30000;class Cu{processes;clock;session;health;constructor(e,t){this.processes=e;this.clock=t;this.health=new Su(()=>this.host(),e,t,{startOne:(r,n,o,i)=>this.startOne(r,n,o,i),restart:(r,n)=>this.restart(r,n)})}bind(e){this.session=e}host(){if(!this.session)throw M(He,"service orchestrator is not bound");return this.session}planStart(e,t,r){return za(e,t,r)}planStop(e,t,r=!1){return r?Xa(e,t):Ya(e,t)}applyLifecycle(e,t){return Uy(e,t)}isLegalLifecycle(e,t){return ts(e,t)}async start(e){let t=this.host();if(e.detach===!0)t.detached=!0;let r=Za(t.cfg,{services:e.services,profile:e.profile?ur(e.profile):void 0,activeProfile:ur(t.profile)});if(r.profile)t.profile=r.profile,t.profileEnv=r.env;if(e.client_env)for(let a of r.services)t.clientEnv.set(a,e.client_env);if(e.auto!==!0)for(let a of r.services)this.health.resetRestartCount(a);let n=this.planStart(t.cfg,r.services,r.profile),o=await t.detectGoogle(t.cfg.google.project_id);n.blockers=gh(t.cfg,n.waves.flat(),o.adcAvailable);let i=new Set(n.blockers.map((a)=>a.name));for(let a of n.blockers)await t.fail(a.name,M(He,a.message));if(t.cfg.proxy.enabled&&!t.proxySuppressed)await t.startProxy().catch((a)=>t.log("devctl","ERROR",A(a)));let s=[];for(let a of n.waves.flat()){if(i.has(a)||await t.claimIfAlreadyUp(a))continue;s.push(a)}for(let a of s)if(e.profile||!t.serviceProfile.has(a))t.serviceProfile.set(a,r.profile);if(s.length>0)await t.assignPendingPorts(s);for(let a of n.waves){let c=a.filter((l)=>s.includes(l));if(c.length>0){let l=await Promise.allSettled(c.map((u)=>this.startOne(u,r.profile,r.env,e.auto!==!0))),d=!1;for(let u of l)if(u.status==="rejected")d=!0,t.log("devctl","ERROR",A(u.reason));if(d)throw M(He,"one or more services failed to start")}try{await this.awaitWaveHealth(a,n.waves.flat())}catch(l){throw t.log("devctl","ERROR",A(l)),l}}return t.persistState(),n}async stop(e){let t=this.host(),r=e;if(r.length===0)r=[...t.runtimes.entries()].filter(([,s])=>s.state!==_t).map(([s])=>s);if(r.length===0)return;let n=r.filter((s)=>!t.cfg.services[s]&&t.runtimes.has(s)),o=r.filter((s)=>!t.cfg.services[s]&&!t.runtimes.has(s));if(o.length>0)throw M(Io,`unknown service "${o[0]}"`);let i=r.filter((s)=>t.cfg.services[s]!==void 0);if(i.length>0)await this.runStopPlan(this.planStop(t.cfg,i),{resetRestartCounts:!0});if(n.length>0){await this.runStopPlan({profile:t.profile,steps:[],waves:n.map((s)=>[s])},{resetRestartCounts:!0});for(let s of n)t.forgetService(s)}}async restart(e,t){let r=this.host(),n=t?.cascade===!0,o=n?io(r.cfg,e):e,i=n?this.planStop(r.cfg,e):this.planStop(r.cfg,e,!0),s=t?.auto!==!0;await this.runStopPlan(i,{resetRestartCounts:s}),await this.start({services:o,client_env:t?.clientEnv,auto:t?.auto})}async runStopPlan(e,t){let r=this.host();for(let i of e.waves.flat())if(this.health.clearRestartTimer(i),this.health.bumpGeneration(i),t?.resetRestartCounts)this.health.resetRestartCount(i);let n=vn(r.cfg.shutdown)*1000,o=[];for(let i of e.waves){let s=await Promise.allSettled(i.map(async(a)=>{r.setState(a,Rr,Et,0,""),this.health.clearHealthWatch(a);try{await this.processes.stop(a,n)}catch(c){throw r.setState(a,lt,Et,0,A(c)),r.bus.publish(Se(ra,a,{error:A(c)})),c}try{await r.releasePorts(a)}catch(c){r.log(a,"WARN",A(c))}r.setState(a,_t,Et,0,""),r.bus.publish(Se(ta,a,{}))}));for(let a=0;a<s.length;a+=1){let c=s[a];if(c?.status==="rejected"){let l=i[a]??"";o.push(l),r.log(l||"devctl","ERROR",A(c.reason))}}}if(r.persistState(),o.length>0)throw M(He,`failed to stop: ${o.join(", ")}`)}serviceIsActive(e){if(this.processes.isRunning(e))return!0;let t=this.host().runtimes.get(e);if(!t)return!1;if(t.state===fr||t.state===Rr||t.state===_t||t.state===lt)return!1;if(t.health===Wt||t.state===pr)return!0;return t.state===rn||t.state===Be}async startOne(e,t,r,n=!1){let o=this.host();if(this.serviceIsActive(e))return;let i=o.cfg.services[e];if(!i)throw M(ct,`unknown service ${e}`);o.setState(e,rn,Et,0,"");let s=this.health.bumpGeneration(e),a=o.serviceProfile.get(e)??t,c=a!==""?so(o.cfg,a):{...r};o.serviceProfileEnv.set(e,c);let l={},d={},u="",f;try{await o.prepareServiceIdentity(e,i),l=o.ports.get(e)??{};let p=await o.resolveServiceExecution(e,i,a,c,o.clientEnv.get(e),!i.container);if(d=p.env,u=p.workDir,n)await this.runTransient(`${e}:pre_start`,i.hooks.pre_start,i.shell,u,d);if(!this.health.isCurrentGeneration(e,s))return;let m=(y,x)=>{o.logs.append({timestamp:this.clock.isoNow(),service:e,source:y,stream:y,level:"",message:x,pid:f.pid})},g=(y,x)=>this.health.onExit(e,s,y,x);f=i.container?await this.processes.startContainer({name:e,runtime:i.container.runtime==="podman"?"podman":"docker",containerName:`${o.containerPrefix}${e.replace(/[^a-zA-Z0-9_.-]/g,"-")}`,image:i.container.image,command:[...i.command.args],env:{...d,...i.container.env},ports:l,targetPorts:i.container.ports,volumes:i.container.volumes,workDir:u,onLine:m,onExit:g}):await this.processes.start({name:e,args:[...i.command.args],shell:i.shell||i.command.shell,workDir:u,env:d,graceMs:vn(o.cfg.shutdown)*1000,captureStdout:lg(i),captureStderr:dg(i),onLine:m,onExit:g})}catch(p){if(this.health.isCurrentGeneration(e,s))await o.fail(e,p);throw p}if(!this.health.isCurrentGeneration(e,s)){if(this.processes.get(e)===f)await this.processes.stop(e,vn(o.cfg.shutdown)*1000).catch(()=>{});return}if(o.processMeta.set(e,{command:[...i.command.args],cwd:u,startTime:f.startTime}),o.setState(e,Be,Et,f.pid,""),o.bus.publish(Se(km,e,{pid:f.pid})),n)try{await this.runTransient(`${e}:post_start`,i.hooks.post_start,i.shell,u,d)}catch(p){throw await o.fail(e,p),p}if(!this.health.isCurrentGeneration(e,s))return;if(this.health.startHealth(e,i,f.pid,l,u,d,s),o.persistState(),o.clearRestartRequired([e]),dc.waitForHealthy(i)){let p=dc.timeoutMs(i,Ky);try{await this.waitHealthy(e,p)}catch(m){throw await o.fail(e,m),m}}}async awaitWaveHealth(e,t){for(let r of e){let n=this.host().cfg.services[r],o=t.some((s)=>(this.host().cfg.services[s]?.dependencies??[]).some((a)=>Jt(a)===r&&mn(a)==="service_healthy"));if(!n||n.health.type===""||!o)continue;let i=dc.timeoutMs(n,Ky);try{await this.waitHealthy(r,i)}catch(s){throw await this.host().fail(r,s),s}}}async waitHealthy(e,t){let r=this.clock.unixMs()+t;while(this.clock.unixMs()<r){let n=this.host().runtimes.get(e);if(n?.health===Wt)return;if(n?.state===lt)throw M(kd,`service ${e} failed while waiting for healthy`);await z0(J0)}throw M(kd,`service ${e} did not become healthy in time`)}async runTransient(e,t,r,n,o){if(hn(t))return{code:0,stdout:"",stderr:""};this.host().log(e,"INFO",`running ${t.args.join(" ")}`);let i=await this.processes.runOnce({name:e,args:[...t.args],shell:r||t.shell,workDir:n,env:o,graceMs:vn(this.host().cfg.shutdown)*1000,onLine:(s,a)=>this.host().logs.append({timestamp:this.clock.isoNow(),service:e,source:s,stream:s,level:"",message:a,pid:0})});if(i.code!==0)throw M(He,`${e} exited with code ${i.code}`);return i}}function z0(e){return new Promise((t)=>setTimeout(t,e))}te();function rs(e){return async(t)=>{let r=t.includes("/versions/")?t:`${t}/versions/latest`,n=await e(),o=await fetch(`https://secretmanager.googleapis.com/v1/${r}:access`,{headers:{Authorization:`Bearer ${n}`}});if(!o.ok)throw M(ee,`secret manager request failed for ${t}: HTTP ${o.status}`);let i=await o.json();if(!i.payload?.data)throw M(ee,`secret manager response for ${t} had no payload`);return Buffer.from(i.payload.data,"base64").toString("utf8")}}Tt();te();async function uc(e,t,r,n){let o="";if(e.proxy?.isRunning())o=`http://${e.proxy.address()}`;else if(e.cfg.proxy.enabled)o=`http://${yn(e.cfg.proxy.listen)}`;let i=zi(t,"127.0.0.1",n,o,e.cfg.project.name);if(i.DEVCTL_INTERNAL_TOKEN=e.internalTok,e.cfg.proxy.token_endpoint.enabled)i.DEVCTL_TOKEN_URL=e.boundTokenURL||`http://127.0.0.1:${e.tokenEP?.listenPort()||e.cfg.proxy.token_endpoint.port}/token`;try{let s=await Ji(e.cfg.repoRoot,{service:t,profile:e.serviceProfile.get(t)??e.profile,serviceCfg:r,profileEnv:e.serviceProfileEnv.get(t)??e.profileEnv,assignedPorts:n,runtime:i,cfg:e.cfg,fetchSecret:rs(async()=>(await e.tokens.get("user","",[])).accessToken),pluginSources:e.registry?.environmentSources});return Yo(s)}catch(s){return e.log(t,"WARN",`could not fully reconstruct environment for adopted service's health check (${A(s)}); using a baseline environment`),Yo(i)}}function qy(e,t,r,n,o,i){if(e.procs.get(t)&&e.processAliveFn(e.procs.get(t)?.pid??0))return;if(!e.processAliveFn(r)||r===process.pid)return;let s=e.orchestrator.health.bumpGeneration(t);try{e.procs.adopt({name:t,pid:r,args:n,workDir:o,startTime:i,onExit:(a,c)=>{e.orchestrator.health.onExit(t,s,a,c)}})}catch(a){let c=a instanceof Error?a.message:String(a);e.log(t,"WARN",`adopt pid ${r} failed (${c}); tracking leftover in snapshot only`)}return e.processMeta.set(t,{command:n,cwd:o,startTime:i}),e.persistState(),s}async function Vy(e,t){let r=e.cfg.services[t];if(e.orchestrator.serviceIsActive(t)){if(r&&!e.ports.has(t)){let a=await pa(r);if(a)e.ports.set(t,a)}return!0}if(!r)return!1;if(r.container){let a=e.orchestrator.health.bumpGeneration(t),c=r.container.runtime==="podman"?"podman":"docker",l=e.serviceWorkDir(r),d=await e.procs.adoptContainer({name:t,runtime:c,containerName:`devctl-${ar(e.cfg.repoRoot)}-${t.replace(/[^a-zA-Z0-9_.-]/g,"-")}`,workDir:l,onLine:(p,m)=>e.logs.append({timestamp:e.clock.isoNow(),service:t,source:p,stream:p,level:"",message:m,pid:0}),onExit:(p,m)=>e.orchestrator.health.onExit(t,a,p,m)});if(!d)return!1;let u=e.ports.get(t)??Object.fromEntries(r.ports.filter((p)=>!p.auto).map((p)=>[p.name,p.value]));e.ports.set(t,u),e.processMeta.set(t,{command:[...r.command.args],cwd:l,startTime:d.startTime}),pc(e,t,fc(e,un(e.cfg.repoRoot)?.processes.find((p)=>p.name===t))),e.setState(t,Be,Et,0,"");let f=await uc(e,t,r,u);return e.orchestrator.health.startHealth(t,r,0,u,l,f,a),e.persistState(),e.log(t,"INFO",`claimed running ${c} container ${d.container?.id??""}`),!0}let n=await pa(r);if(!n)return!1;let o=Object.values(n)[0],s=(o===void 0?void 0:await Vn(o))?.pid??0;if(s>0&&s!==process.pid){let a=un(e.cfg.repoRoot)?.processes.find((d)=>d.name===t&&d.pid===s);if(!a)return e.log(t,"WARN",`port ${o} is held by pid ${s} with no persisted record for ${t}; not adopting`),!1;let c=await e.inspectProcessFn(s);if(c!==void 0&&c.command!==""&&Zi({args:[...r.command.args],workDir:e.serviceWorkDir(r),startTime:new Date(a.startTime)},c)){e.ports.set(t,n);let d=qy(e,t,s,[...r.command.args],e.serviceWorkDir(r),new Date(a.startTime))??e.orchestrator.health.bumpGeneration(t);pc(e,t,fc(e,a)),e.setState(t,Be,Et,s,""),e.log(t,"INFO",`already listening on ${Object.values(n).join(", ")}; not starting again`);let u=e.serviceWorkDir(r),f=await uc(e,t,r,n);return e.orchestrator.health.startHealth(t,r,s,n,u,f,d),!0}e.log(t,"WARN",`port ${o} is in use by an unrelated process (pid ${s}); not adopting`)}return!1}async function Jy(e){let t=un(e.cfg.repoRoot);if(!t)return;Y0(e,t.profile);let r=[];for(let[n,o]of Object.entries(e.cfg.services)){if(!o.container)continue;let i=t.processes.find((u)=>u.name===n),s=e.orchestrator.health.bumpGeneration(n),a=o.container.runtime==="podman"?"podman":"docker",c=await e.procs.adoptContainer({name:n,runtime:a,containerName:`devctl-${ar(e.cfg.repoRoot)}-${n.replace(/[^a-zA-Z0-9_.-]/g,"-")}`,workDir:e.serviceWorkDir(o),onLine:(u,f)=>e.logs.append({timestamp:e.clock.isoNow(),service:n,source:u,stream:u,level:"",message:f,pid:0}),onExit:(u,f)=>e.orchestrator.health.onExit(n,s,u,f)});if(!c)continue;let l=i?.ports??Object.fromEntries(o.ports.filter((u)=>!u.auto).map((u)=>[u.name,u.value]));e.ports.set(n,l),e.processMeta.set(n,{command:[...o.command.args],cwd:e.serviceWorkDir(o),startTime:i?.startTime?new Date(i.startTime):c.startTime}),pc(e,n,fc(e,i)),e.setState(n,Be,Et,0,"");let d=await uc(e,n,o,l);e.orchestrator.health.startHealth(n,o,0,l,e.serviceWorkDir(o),d,s),e.log(n,"INFO",`adopted ${a} container ${c.container?.id??""}`),r.push(n)}for(let n of t.processes){if(!e.cfg.services[n.name]||e.cfg.services[n.name]?.container||n.pid<=0||n.pid===process.pid||!e.processAliveFn(n.pid))continue;let o=await e.inspectProcessFn(n.pid);if(!(o!==void 0&&o.command!==""&&Zi({args:n.command,workDir:n.cwd,startTime:n.startTime?new Date(n.startTime):void 0},o))){if(Object.values(n.ports).length>0&&await pa({ports:Object.entries(n.ports).map(([l,d])=>({name:l,value:d,auto:!1}))})!==void 0)e.log(n.name,"WARN",`pid ${n.pid} does not match stored command; leftover listener not adopted`);else e.log(n.name,"WARN",`pid ${n.pid} is a different process; not adopting`);continue}let s=qy(e,n.name,n.pid,n.command,n.cwd,new Date(n.startTime||e.clock.unixMs()))??e.orchestrator.health.bumpGeneration(n.name);if(Object.keys(n.ports).length>0)e.ports.set(n.name,n.ports);pc(e,n.name,fc(e,n)),e.setState(n.name,Be,Et,n.pid,"");let a=e.cfg.services[n.name];if(a){let c=n.cwd||e.serviceWorkDir(a),l=await uc(e,n.name,a,n.ports);e.orchestrator.health.startHealth(n.name,a,n.pid,n.ports,c,l,s)}e.log(n.name,"INFO","adopted leftover process; stdout/stderr from before adopt are not captured"),r.push(n.name)}if(r.length>0)e.bus.publish(Se(Em,"",{services:r,session_id:t.session_id})),e.log("devctl","INFO",`recovered session processes: ${r.join(", ")}`)}function Y0(e,t){e.profile=t||e.profile,e.profileEnv=so(e.cfg,e.profile)}function pc(e,t,r){e.serviceProfile.set(t,r),e.serviceProfileEnv.set(t,so(e.cfg,r))}function fc(e,t){return t?.profile??e.profile}import{statSync as uT,watch as pT}from"fs";import{isAbsolute as fT,join as mT,resolve as gT}from"path";function zy(e,t){return t}function Ru(e,t){let r={},n=new Set,o=new Set([...Object.keys(e.services),...Object.keys(t.services)]);for(let s of o){let a=e.services[s],c=t.services[s],l=[];if(!a||!c)l.push("presence"),n.add(s);else{if(a.command.args.join("\x00")!==c.command.args.join("\x00")||a.command.shell!==c.command.shell||a.shell!==c.shell)l.push("command"),n.add(s);if(a.working_dir!==c.working_dir)l.push("working_dir"),n.add(s);if(JSON.stringify(a.environment)!==JSON.stringify(c.environment))l.push("environment"),n.add(s);if(JSON.stringify(a.ports)!==JSON.stringify(c.ports))l.push("ports"),n.add(s);if(JSON.stringify(a.identity)!==JSON.stringify(c.identity))l.push("identity"),n.add(s);if(JSON.stringify(a.health)!==JSON.stringify(c.health))l.push("health"),n.add(s);if(JSON.stringify(a.restart)!==JSON.stringify(c.restart))l.push("restart"),n.add(s);if(JSON.stringify(a.startup)!==JSON.stringify(c.startup))l.push("startup"),n.add(s);if(JSON.stringify(a.logs)!==JSON.stringify(c.logs))l.push("logs"),n.add(s);if(JSON.stringify(a.container)!==JSON.stringify(c.container))l.push("container"),n.add(s);if(JSON.stringify(a.watch)!==JSON.stringify(c.watch))l.push("watch"),n.add(s)}if(l.length>0)r[s]=l}let i=[];if(JSON.stringify(e.logs)!==JSON.stringify(t.logs))i.push("logs");if(JSON.stringify(e.auth)!==JSON.stringify(t.auth))i.push("auth");return{restart_required:[...n].sort(),changes:r,supervisor_restart_required:i}}Tt();te();import{pathToFileURL as sT}from"url";import{isAbsolute as aT,resolve as cT}from"path";import{createServer as ev,request as X0}from"http";import{request as Q0}from"https";import{randomBytes as Yy}from"crypto";import{Readable as Z0}from"stream";te();Fr();var ns="x-devctl-request-id",eT="x-devctl-internal-token",Xy=100,Pu=new Set(["connection","keep-alive","proxy-authenticate","proxy-authorization","te","trailers","transfer-encoding","upgrade"]),tT=new Set(["gzip","deflate","br"]);class _u{cfg;tokens;logs;bus;detector;middleware;server;running=!1;addr="";upgradedSockets=new Set;recentRequests=[];constructor(e,t,r,n,o,i=[]){this.cfg=e,this.tokens=t,this.logs=r,this.bus=n,this.detector=o,this.middleware=i}address(){return this.addr||yn(this.cfg.listen)}setMiddleware(e){this.middleware=e}isRunning(){return this.running}stats(){let e=this.recentRequests.reduce((t,r)=>t+(r.status>=400||r.error?1:0),0);return{total:this.recentRequests.length,errors:e,recent:[...this.recentRequests].reverse()}}recordRequest(e){if(this.recentRequests.push(e),this.recentRequests.length>Xy)this.recentRequests.splice(0,this.recentRequests.length-Xy)}start(){let e=this.cfg.listen.host||"127.0.0.1";if(!kn(e))return Promise.reject(M(fn,`refusing to bind proxy to ${e}`));if(this.cfg.listen.port===0)return Promise.reject(M(fn,"proxy.listen.port is required when the proxy is enabled"));let t=this.cfg.listen.port;return new Promise((r,n)=>{this.server=ev((o,i)=>{this.serve(o,i)}),this.server.on("upgrade",(o,i,s)=>{this.serveUpgrade(o,i,s)}),this.server.on("error",(o)=>n(Re(fn,`unable to listen on ${e}:${t}`,o))),this.server.listen(t,e,()=>{this.running=!0,this.addr=`${e}:${t}`,this.bus?.publish(Se(Pm,"",{address:this.addr})),r()})})}stop(){let e=this.server;if(!e)return Promise.resolve();for(let t of this.upgradedSockets)t.destroy();return this.upgradedSockets.clear(),new Promise((t)=>{e.close(()=>{this.running=!1,this.bus?.publish(Se(_m,"",{})),t()})})}async serveUpgrade(e,t,r){let n=Date.now(),o=e.headers[ns]?.toString()||Yy(8).toString("hex"),i=e.method??"GET",s=e.url??"/",a=this.detector?this.detector.redactText(s):s,c=Zy(this.cfg.routes,e);if(!c){t.end(`HTTP/1.1 404 Not Found\r
2184
+ `;e.onLine?.(a,c)},i=[hc(t.stdout,"stdout",o),hc(t.stderr,"stderr",o)],s=await t.exited;return await Promise.all(i),{code:typeof s==="number"?s:0,stdout:r,stderr:n}}async startContainer(e){let t=this.running.get(e.name);if(t&&ns(t))return t;let r=await Gv({...e,env:Kv(e.env)}),n={name:e.name,pid:0,startTime:new Date,workDir:e.workDir,args:[...e.command],done:r.done,container:r};return r.done.finally(()=>{if(this.running.get(e.name)===n)this.running.delete(e.name)}),this.running.set(e.name,n),n}async adoptContainer(e){let t=this.running.get(e.name);if(t&&ns(t))return t;let r=await qv(e.runtime,e.containerName,e.onLine,e.onExit);if(!r)return;let n={name:e.name,pid:0,startTime:new Date,workDir:e.workDir,args:[],done:r.done,container:r};return r.done.finally(()=>{if(this.running.get(e.name)===n)this.running.delete(e.name)}),this.running.set(e.name,n),n}adopt(e){let t=this.running.get(e.name);if(t&&ns(t))return t;if(!Qt(e.pid))throw I(He,`cannot adopt ${e.name}: pid ${e.pid} is not running`);let r={name:e.name,pid:e.pid,startTime:e.startTime,workDir:e.workDir,args:[...e.args],done:Promise.resolve({code:0})};return r.done=s0(e.pid).then((n)=>{if(this.running.get(e.name)===r)this.running.delete(e.name);if(e.onExit)e.onExit(n,n===0?void 0:Error(`exited with code ${n}`));return{code:n}}),this.running.set(e.name,r),r}async stop(e,t){let r=this.running.get(e);if(!r)return;let n=t>0?t:r0;if(r.container){await r.container.stop(n),this.running.delete(e);return}if(await Yv(r.pid,"SIGTERM"),!await Zv(r.done,n)){if(await Yv(r.pid,"SIGKILL"),!await Zv(r.done,n0))throw I(He,`process ${e} did not exit after SIGKILL`)}this.running.delete(e)}get(e){return this.running.get(e)}all(){return[...this.running.values()]}}function ns(e){if(e.container)return e.container.running();if(e.proc)return e.proc.exitCode===null&&!e.proc.killed;return Qt(e.pid)}async function Yv(e,t){if(process.platform==="win32"){await jv(e,t);return}await Ov(e,t)}async function ey(e){if(process.platform==="win32")return Bv(e);return $v(e)}async function ty(e){if(process.platform==="win32")return Uv(e);return Fv(e)}var i0=2000;function os(e,t){if(e.workDir!==""&&t.cwd!==""&&Qv(e.workDir)!==Qv(t.cwd))return!1;if(!Nv(e.args,t.command))return!1;let r=Jv(e.startTime),n=Jv(t.startTime);if(r!==void 0&&n!==void 0)return Math.abs(r-n)<=i0;return!0}function Jv(e){if(e===void 0)return;if(e instanceof Date)return Number.isNaN(e.getTime())?void 0:e.getTime();let t=Date.parse(e);return Number.isNaN(t)?void 0:t}function Xv(e){if(process.platform==="win32")return["cmd.exe","/c",e.join(" ")];return["/bin/sh","-c",e.join(" ")]}function Qv(e){return e.replace(/\\/g,"/").replace(/\/+$/,"")}async function s0(e){while(Qt(e))await a0(o0);return 0}async function hc(e,t,r){if(!e||typeof e==="number"||!r)return;let n=e.getReader(),o=new TextDecoder,i="";for(;;){let{value:s,done:a}=await n.read();if(a){if(i!=="")r(t,i);return}i+=o.decode(s,{stream:!0});let c=i.split(`
2185
+ `);i=c.pop()??"";for(let l of c)r(t,l.replace(/\r$/,""))}}async function Zv(e,t){return Promise.race([e.then(()=>!0),new Promise((r)=>{setTimeout(()=>r(!1),t)})])}function a0(e){return new Promise((t)=>{setTimeout(t,e)})}import{existsSync as c0,readFileSync as l0,writeFileSync as d0}from"fs";var ry={exists:(e)=>c0(e),readText:(e)=>l0(e,"utf8"),writeText:(e,t)=>d0(e,t)};ht();var _u=3,u0=3,p0=10,is={shouldRestart(e){if(!e.enabled||e.policy===ba)return!1;if(e.retryCount>=e.maxRetries)return!1;if(e.policy===Sa)return!0;return e.policy===wa&&e.exitCode!==0},fromService(e,t,r){return{policy:Ra(e.restart),enabled:e.restart.enabled!==!1,exitCode:r,retryCount:t,maxRetries:e.restart.max_retries>0?e.restart.max_retries:_u}}},Eu={shouldRestartUnhealthy(e,t=u0){return e>=t},shouldResetRestartBudget(e,t=p0){return e>=t}},gc={timeoutMs(e,t){let r=e.startup.timeout_seconds;return r>0?r*1000:t},waitForHealthy(e){return e.startup.wait_for_healthy}};Tt();te();var f0=2,m0=2000,h0=3,g0=10;class Tu{host;processes;clock;actions;generation=new Map;healthTimers=new Map;restarts=new Map;unhealthyStreak=new Map;healthyStreak=new Map;restartTimers=new Map;constructor(e,t,r,n){this.host=e;this.processes=t;this.clock=r;this.actions=n}forget(e){this.clearHealthWatch(e),this.clearRestartTimer(e),this.restarts.delete(e),this.bumpGeneration(e),this.unhealthyStreak.delete(e),this.healthyStreak.delete(e)}dispose(){this.clearAllRestartTimers();for(let e of this.generation.keys())this.bumpGeneration(e);for(let e of this.healthTimers.keys())this.clearHealthWatch(e)}onExit(e,t,r,n){if(!this.isCurrentGeneration(e,t))return;this.clearHealthWatch(e);let o=this.host().runtimes.get(e);if(o?.state===dt)return;if(o&&(o.state===Rr||o.state===_t)){this.host().setState(e,_t,Et,0,""),this.host().bus.publish(Se(sa,e,{code:r}));return}let i=this.host().cfg.services[e],s=n?.message??`exited with code ${r}`,a=this.restarts.get(e)??0;if((i?is.shouldRestart(is.fromService(i,a,r)):!1)&&i){this.host().setState(e,fr,Et,0,s),this.bumpRestartCount(e,a+1),this.host().persistState(),this.armRestart(e,i,t,a,()=>{let l=this.host().serviceProfile.get(e)??this.host().profile,d=this.host().serviceProfileEnv.get(e)??this.host().profileEnv;this.actions.startOne(e,l,d,!1).catch((u)=>this.host().log(e,"ERROR",A(u)))});return}this.host().fail(e,I("process_start",s))}startHealth(e,t,r,n,o,i,s){let a=this.healthTimers.get(e);if(a)clearInterval(a);if(t.health.type===""){this.setHealth(e,Bt,"no health check");return}let c=t.health.interval_seconds>0?t.health.interval_seconds*1000:m0,l=Date.parse(this.host().runtimes.get(e)?.startTime??"")||this.clock.unixMs(),d=()=>{(t.container&&t.health.type.toLowerCase()==="process"?Promise.resolve(this.processes.isRunning(e)?{status:Bt,message:"container running"}:{status:Oe,message:"container not running"}):Promise.resolve().then(()=>this.host().healthCheckers.lookup(t.health.type)?.check(t.health,{pid:r,ports:n,workDir:o,env:i})??{status:Oe,message:`unknown health type ${t.health.type}`})).catch((f)=>({status:Oe,message:A(f)})).then((f)=>{if(!this.isCurrentGeneration(e,s))return;let p=this.host().runtimes.get(e);if(p&&ny(p.state))return;if(f.status===Oe&&this.clock.unixMs()-l<t.health.start_period_seconds*1000){this.host().logs.append({timestamp:this.clock.isoNow(),service:e,source:"health",level:"INFO",message:`health check still in start period: ${f.message}`,pid:r});return}this.setHealth(e,f.status,f.message),this.host().logs.append({timestamp:this.clock.isoNow(),service:e,source:"health",level:v0(f.status),message:`health ${f.status} ${f.message}`,pid:r}),this.maybeRestartUnhealthy(e,t,f.status,s)})};d(),this.healthTimers.set(e,setInterval(d,c))}bumpRestartCount(e,t){this.restarts.set(e,t);let r=this.host().runtimes.get(e);if(r)r.restarts=t}resetRestartCount(e){this.healthyStreak.set(e,0),this.bumpRestartCount(e,0)}bumpGeneration(e){let t=(this.generation.get(e)??0)+1;return this.generation.set(e,t),this.healthyStreak.set(e,0),t}isCurrentGeneration(e,t){return this.generation.get(e)===t}scheduleRestart(e,t,r){this.clearRestartTimer(e);let n=setTimeout(()=>{this.restartTimers.delete(e),r()},t);this.restartTimers.set(e,n)}armRestart(e,t,r,n,o){let i=t.restart.backoff_seconds>0?t.restart.backoff_seconds:f0;this.scheduleRestart(e,i*2**n*1000,()=>{if(this.isCurrentGeneration(e,r))o()})}clearRestartTimer(e){let t=this.restartTimers.get(e);if(t)clearTimeout(t),this.restartTimers.delete(e)}clearAllRestartTimers(){for(let e of this.restartTimers.values())clearTimeout(e);this.restartTimers.clear()}setHealth(e,t,r){let n=this.host().runtimes.get(e);if(!n||ny(n.state))return;if(n.health=t,n.state===je||n.state===pr||n.state===Br)n.state=t===Bt?pr:t===Oe?Br:n.state;this.host().bus.publish(Se(Im,e,{health:t,message:r}))}maybeRestartUnhealthy(e,t,r,n){if(r!==Oe){if(this.unhealthyStreak.set(e,0),r===Bt)this.maybeForgiveRestarts(e,t);return}this.healthyStreak.set(e,0);let o=t.restart.policy||(t.restart.enabled?"on_failure":"never");if(o!=="on_failure"&&o!=="always")return;if(this.restartTimers.has(e))return;let i=(this.unhealthyStreak.get(e)??0)+1;this.unhealthyStreak.set(e,i);let s=t.health.unhealthy_threshold>0?t.health.unhealthy_threshold:h0;if(!Eu.shouldRestartUnhealthy(i,s))return;this.unhealthyStreak.set(e,0);let a=this.restarts.get(e)??0,c=t.restart.max_retries>0?t.restart.max_retries:_u;if(a>=c){this.host().log(e,"WARN",`unhealthy after ${i} consecutive checks but the restart limit (${c}) has already been reached; not restarting`);return}this.bumpRestartCount(e,a+1),this.host().log(e,"WARN",`restarting after ${i} consecutive unhealthy checks (attempt ${a+1}/${c})`),this.armRestart(e,t,n,a,()=>{this.actions.restart([e],{auto:!0}).catch((l)=>this.host().log(e,"ERROR",A(l)))})}maybeForgiveRestarts(e,t){let r=(this.healthyStreak.get(e)??0)+1;this.healthyStreak.set(e,r);let n=t.health.healthy_reset_threshold>0?t.health.healthy_reset_threshold:g0;if(Eu.shouldResetRestartBudget(r,n)&&(this.restarts.get(e)??0)>0)this.host().log(e,"INFO",`restart count reset after ${r} consecutive healthy checks`),this.bumpRestartCount(e,0)}clearHealthWatch(e){let t=this.healthTimers.get(e);if(t)clearInterval(t),this.healthTimers.delete(e)}}function ny(e){return e===fr||e===Rr||e===_t||e===dt}function v0(e){return e===Oe?"WARN":e===Bt?"INFO":"DEBUG"}ht();te();jr();te();Tt();var Au=[Rr,dt,fr,je,_t],y0={[Jg]:[_t,on,je,dt,fr,Rr],[_t]:[on,je,dt],[on]:[je,dt,_t,Rr],[je]:Au,[pr]:Au,[Br]:Au,[Rr]:[_t,dt],[dt]:[on,_t,je],[fr]:[on,je,dt,_t]};function ss(e,t){if(e===t)return!0;return(y0[e]??[]).includes(t)}function oy(e,t){if(!ss(e,t))throw I(Q,`illegal service lifecycle transition ${e} \u2192 ${t}`);return t}Tt();var x0=100,iy=30000;class Lu{processes;clock;session;health;constructor(e,t){this.processes=e;this.clock=t;this.health=new Tu(()=>this.host(),e,t,{startOne:(r,n,o,i)=>this.startOne(r,n,o,i),restart:(r,n)=>this.restart(r,n)})}bind(e){this.session=e}host(){if(!this.session)throw I(He,"service orchestrator is not bound");return this.session}planStart(e,t,r){return tc(e,t,r)}planStop(e,t,r=!1){return r?nc(e,t):rc(e,t)}applyLifecycle(e,t){return oy(e,t)}isLegalLifecycle(e,t){return ss(e,t)}async start(e){let t=this.host();if(e.detach===!0)t.detached=!0;let r=ic(t.cfg,{services:e.services,profile:e.profile?ur(e.profile):void 0,activeProfile:ur(t.profile)});if(r.profile)t.profile=r.profile,t.profileEnv=r.env;if(e.client_env)for(let a of r.services)t.clientEnv.set(a,e.client_env);if(e.auto!==!0)for(let a of r.services)this.health.resetRestartCount(a);let n=this.planStart(t.cfg,r.services,r.profile),o=await t.detectGoogle(t.cfg.google.project_id);n.blockers=Eg(t.cfg,n.waves.flat(),o.adcAvailable);let i=new Set(n.blockers.map((a)=>a.name));for(let a of n.blockers)await t.fail(a.name,I(He,a.message));if(t.cfg.proxy.enabled&&!t.proxySuppressed)await t.startProxy().catch((a)=>t.log("devctl","ERROR",A(a)));let s=[];for(let a of n.waves.flat()){if(i.has(a)||await t.claimIfAlreadyUp(a))continue;s.push(a)}for(let a of s)if(e.profile||!t.serviceProfile.has(a))t.serviceProfile.set(a,r.profile);if(s.length>0)await t.assignPendingPorts(s);for(let a of n.waves){let c=a.filter((l)=>s.includes(l));if(c.length>0){let l=await Promise.allSettled(c.map((u)=>this.startOne(u,r.profile,r.env,e.auto!==!0))),d=!1;for(let u of l)if(u.status==="rejected")d=!0,t.log("devctl","ERROR",A(u.reason));if(d)throw I(He,"one or more services failed to start")}try{await this.awaitWaveHealth(a,n.waves.flat())}catch(l){throw t.log("devctl","ERROR",A(l)),l}}return t.persistState(),n}async stop(e){let t=this.host(),r=e;if(r.length===0)r=[...t.runtimes.entries()].filter(([,s])=>s.state!==_t).map(([s])=>s);if(r.length===0)return;let n=r.filter((s)=>!t.cfg.services[s]&&t.runtimes.has(s)),o=r.filter((s)=>!t.cfg.services[s]&&!t.runtimes.has(s));if(o.length>0)throw I(Ho,`unknown service "${o[0]}"`);let i=r.filter((s)=>t.cfg.services[s]!==void 0);if(i.length>0)await this.runStopPlan(this.planStop(t.cfg,i),{resetRestartCounts:!0});if(n.length>0){await this.runStopPlan({profile:t.profile,steps:[],waves:n.map((s)=>[s])},{resetRestartCounts:!0});for(let s of n)t.forgetService(s)}}async restart(e,t){let r=this.host(),n=t?.cascade===!0,o=n?lo(r.cfg,e):e,i=n?this.planStop(r.cfg,e):this.planStop(r.cfg,e,!0),s=t?.auto!==!0;await this.runStopPlan(i,{resetRestartCounts:s}),await this.start({services:o,client_env:t?.clientEnv,auto:t?.auto})}async runStopPlan(e,t){let r=this.host();for(let i of e.waves.flat())if(this.health.clearRestartTimer(i),this.health.bumpGeneration(i),t?.resetRestartCounts)this.health.resetRestartCount(i);let n=bn(r.cfg.shutdown)*1000,o=[];for(let i of e.waves){let s=await Promise.allSettled(i.map(async(a)=>{r.setState(a,Rr,Et,0,""),this.health.clearHealthWatch(a);try{await this.processes.stop(a,n)}catch(c){throw r.setState(a,dt,Et,0,A(c)),r.bus.publish(Se(aa,a,{error:A(c)})),c}try{await r.releasePorts(a)}catch(c){r.log(a,"WARN",A(c))}r.setState(a,_t,Et,0,""),r.bus.publish(Se(sa,a,{}))}));for(let a=0;a<s.length;a+=1){let c=s[a];if(c?.status==="rejected"){let l=i[a]??"";o.push(l),r.log(l||"devctl","ERROR",A(c.reason))}}}if(r.persistState(),o.length>0)throw I(He,`failed to stop: ${o.join(", ")}`)}serviceIsActive(e){if(this.processes.isRunning(e))return!0;let t=this.host().runtimes.get(e);if(!t)return!1;if(t.state===fr||t.state===Rr||t.state===_t||t.state===dt)return!1;if(t.health===Bt||t.state===pr)return!0;return t.state===on||t.state===je}async startOne(e,t,r,n=!1){let o=this.host();if(this.serviceIsActive(e))return;let i=o.cfg.services[e];if(!i)throw I(lt,`unknown service ${e}`);o.setState(e,on,Et,0,"");let s=this.health.bumpGeneration(e),a=o.serviceProfile.get(e)??t,c=a!==""?uo(o.cfg,a):{...r};o.serviceProfileEnv.set(e,c);let l={},d={},u="",f;try{await o.prepareServiceIdentity(e,i),l=o.ports.get(e)??{};let p=await o.resolveServiceExecution(e,i,a,c,o.clientEnv.get(e),!i.container);if(d=p.env,u=p.workDir,n)await this.runTransient(`${e}:pre_start`,i.hooks.pre_start,i.shell,u,d);if(!this.health.isCurrentGeneration(e,s))return;let m=(v,x)=>{o.logs.append({timestamp:this.clock.isoNow(),service:e,source:v,stream:v,level:"",message:x,pid:f.pid})},h=(v,x)=>this.health.onExit(e,s,v,x);f=i.container?await this.processes.startContainer({name:e,runtime:i.container.runtime==="podman"?"podman":"docker",containerName:`${o.containerPrefix}${e.replace(/[^a-zA-Z0-9_.-]/g,"-")}`,image:i.container.image,command:[...i.command.args],env:{...d,...i.container.env},ports:l,targetPorts:i.container.ports,volumes:i.container.volumes,workDir:u,onLine:m,onExit:h}):await this.processes.start({name:e,args:[...i.command.args],shell:i.shell||i.command.shell,workDir:u,env:d,graceMs:bn(o.cfg.shutdown)*1000,captureStdout:vh(i),captureStderr:yh(i),onLine:m,onExit:h})}catch(p){if(this.health.isCurrentGeneration(e,s))await o.fail(e,p);throw p}if(!this.health.isCurrentGeneration(e,s)){if(this.processes.get(e)===f)await this.processes.stop(e,bn(o.cfg.shutdown)*1000).catch(()=>{});return}if(o.processMeta.set(e,{command:[...i.command.args],cwd:u,startTime:f.startTime}),o.setState(e,je,Et,f.pid,""),o.bus.publish(Se(Lm,e,{pid:f.pid})),n)try{await this.runTransient(`${e}:post_start`,i.hooks.post_start,i.shell,u,d)}catch(p){throw await o.fail(e,p),p}if(!this.health.isCurrentGeneration(e,s))return;if(this.health.startHealth(e,i,f.pid,l,u,d,s),o.persistState(),o.clearRestartRequired([e]),gc.waitForHealthy(i)){let p=gc.timeoutMs(i,iy);try{await this.waitHealthy(e,p)}catch(m){throw await o.fail(e,m),m}}}async awaitWaveHealth(e,t){for(let r of e){let n=this.host().cfg.services[r],o=t.some((s)=>(this.host().cfg.services[s]?.dependencies??[]).some((a)=>zt(a)===r&&gn(a)==="service_healthy"));if(!n||n.health.type===""||!o)continue;let i=gc.timeoutMs(n,iy);try{await this.waitHealthy(r,i)}catch(s){throw await this.host().fail(r,s),s}}}async waitHealthy(e,t){let r=this.clock.unixMs()+t;while(this.clock.unixMs()<r){let n=this.host().runtimes.get(e);if(n?.health===Bt)return;if(n?.state===dt)throw I(Ed,`service ${e} failed while waiting for healthy`);await b0(x0)}throw I(Ed,`service ${e} did not become healthy in time`)}async runTransient(e,t,r,n,o){if(yn(t))return{code:0,stdout:"",stderr:""};this.host().log(e,"INFO",`running ${t.args.join(" ")}`);let i=await this.processes.runOnce({name:e,args:[...t.args],shell:r||t.shell,workDir:n,env:o,graceMs:bn(this.host().cfg.shutdown)*1000,onLine:(s,a)=>this.host().logs.append({timestamp:this.clock.isoNow(),service:e,source:s,stream:s,level:"",message:a,pid:0})});if(i.code!==0)throw I(He,`${e} exited with code ${i.code}`);return i}}function b0(e){return new Promise((t)=>setTimeout(t,e))}te();function as(e){return async(t)=>{let r=t.includes("/versions/")?t:`${t}/versions/latest`,n=await e(),o=await fetch(`https://secretmanager.googleapis.com/v1/${r}:access`,{headers:{Authorization:`Bearer ${n}`}});if(!o.ok)throw I(Q,`secret manager request failed for ${t}: HTTP ${o.status}`);let i=await o.json();if(!i.payload?.data)throw I(Q,`secret manager response for ${t} had no payload`);return Buffer.from(i.payload.data,"base64").toString("utf8")}}Tt();te();async function vc(e,t,r,n){let o="";if(e.proxy?.isRunning())o=`http://${e.proxy.address()}`;else if(e.cfg.proxy.enabled)o=`http://${xn(e.cfg.proxy.listen)}`;let i=es(t,"127.0.0.1",n,o,e.cfg.project.name);if(i.DEVCTL_INTERNAL_TOKEN=e.internalTok,e.cfg.proxy.token_endpoint.enabled)i.DEVCTL_TOKEN_URL=e.boundTokenURL||`http://127.0.0.1:${e.tokenEP?.listenPort()||e.cfg.proxy.token_endpoint.port}/token`;try{let s=await Zi(e.cfg.repoRoot,{service:t,profile:e.serviceProfile.get(t)??e.profile,serviceCfg:r,profileEnv:e.serviceProfileEnv.get(t)??e.profileEnv,assignedPorts:n,runtime:i,cfg:e.cfg,fetchSecret:as(async()=>(await e.tokens.get("user","",[])).accessToken),pluginSources:e.registry?.environmentSources});return Qo(s)}catch(s){return e.log(t,"WARN",`could not fully reconstruct environment for adopted service's health check (${A(s)}); using a baseline environment`),Qo(i)}}function sy(e,t,r,n,o,i){if(e.procs.get(t)&&e.processAliveFn(e.procs.get(t)?.pid??0))return;if(!e.processAliveFn(r)||r===process.pid)return;let s=e.orchestrator.health.bumpGeneration(t);try{e.procs.adopt({name:t,pid:r,args:n,workDir:o,startTime:i,onExit:(a,c)=>{e.orchestrator.health.onExit(t,s,a,c)}})}catch(a){let c=a instanceof Error?a.message:String(a);e.log(t,"WARN",`adopt pid ${r} failed (${c}); tracking leftover in snapshot only`)}return e.processMeta.set(t,{command:n,cwd:o,startTime:i}),e.persistState(),s}async function ay(e,t){let r=e.cfg.services[t];if(e.orchestrator.serviceIsActive(t)){if(r&&!e.ports.has(t)){let a=await ya(r);if(a)e.ports.set(t,a)}return!0}if(!r)return!1;if(r.container){let a=e.orchestrator.health.bumpGeneration(t),c=r.container.runtime==="podman"?"podman":"docker",l=e.serviceWorkDir(r),d=await e.procs.adoptContainer({name:t,runtime:c,containerName:`devctl-${ar(e.cfg.repoRoot)}-${t.replace(/[^a-zA-Z0-9_.-]/g,"-")}`,workDir:l,onLine:(p,m)=>e.logs.append({timestamp:e.clock.isoNow(),service:t,source:p,stream:p,level:"",message:m,pid:0}),onExit:(p,m)=>e.orchestrator.health.onExit(t,a,p,m)});if(!d)return!1;let u=e.ports.get(t)??Object.fromEntries(r.ports.filter((p)=>!p.auto).map((p)=>[p.name,p.value]));e.ports.set(t,u),e.processMeta.set(t,{command:[...r.command.args],cwd:l,startTime:d.startTime}),yc(e,t,xc(e,fn(e.cfg.repoRoot)?.processes.find((p)=>p.name===t))),e.setState(t,je,Et,0,"");let f=await vc(e,t,r,u);return e.orchestrator.health.startHealth(t,r,0,u,l,f,a),e.persistState(),e.log(t,"INFO",`claimed running ${c} container ${d.container?.id??""}`),!0}let n=await ya(r);if(!n)return!1;let o=Object.values(n)[0],s=(o===void 0?void 0:await Yn(o))?.pid??0;if(s>0&&s!==process.pid){let a=fn(e.cfg.repoRoot)?.processes.find((d)=>d.name===t&&d.pid===s);if(!a)return e.log(t,"WARN",`port ${o} is held by pid ${s} with no persisted record for ${t}; not adopting`),!1;let c=await e.inspectProcessFn(s);if(c!==void 0&&c.command!==""&&os({args:[...r.command.args],workDir:e.serviceWorkDir(r),startTime:new Date(a.startTime)},c)){e.ports.set(t,n);let d=sy(e,t,s,[...r.command.args],e.serviceWorkDir(r),new Date(a.startTime))??e.orchestrator.health.bumpGeneration(t);yc(e,t,xc(e,a)),e.setState(t,je,Et,s,""),e.log(t,"INFO",`already listening on ${Object.values(n).join(", ")}; not starting again`);let u=e.serviceWorkDir(r),f=await vc(e,t,r,n);return e.orchestrator.health.startHealth(t,r,s,n,u,f,d),!0}e.log(t,"WARN",`port ${o} is in use by an unrelated process (pid ${s}); not adopting`)}return!1}async function cy(e){let t=fn(e.cfg.repoRoot);if(!t)return;w0(e,t.profile);let r=[];for(let[n,o]of Object.entries(e.cfg.services)){if(!o.container)continue;let i=t.processes.find((u)=>u.name===n),s=e.orchestrator.health.bumpGeneration(n),a=o.container.runtime==="podman"?"podman":"docker",c=await e.procs.adoptContainer({name:n,runtime:a,containerName:`devctl-${ar(e.cfg.repoRoot)}-${n.replace(/[^a-zA-Z0-9_.-]/g,"-")}`,workDir:e.serviceWorkDir(o),onLine:(u,f)=>e.logs.append({timestamp:e.clock.isoNow(),service:n,source:u,stream:u,level:"",message:f,pid:0}),onExit:(u,f)=>e.orchestrator.health.onExit(n,s,u,f)});if(!c)continue;let l=i?.ports??Object.fromEntries(o.ports.filter((u)=>!u.auto).map((u)=>[u.name,u.value]));e.ports.set(n,l),e.processMeta.set(n,{command:[...o.command.args],cwd:e.serviceWorkDir(o),startTime:i?.startTime?new Date(i.startTime):c.startTime}),yc(e,n,xc(e,i)),e.setState(n,je,Et,0,"");let d=await vc(e,n,o,l);e.orchestrator.health.startHealth(n,o,0,l,e.serviceWorkDir(o),d,s),e.log(n,"INFO",`adopted ${a} container ${c.container?.id??""}`),r.push(n)}for(let n of t.processes){if(!e.cfg.services[n.name]||e.cfg.services[n.name]?.container||n.pid<=0||n.pid===process.pid||!e.processAliveFn(n.pid))continue;let o=await e.inspectProcessFn(n.pid);if(!(o!==void 0&&o.command!==""&&os({args:n.command,workDir:n.cwd,startTime:n.startTime?new Date(n.startTime):void 0},o))){if(Object.values(n.ports).length>0&&await ya({ports:Object.entries(n.ports).map(([l,d])=>({name:l,value:d,auto:!1}))})!==void 0)e.log(n.name,"WARN",`pid ${n.pid} does not match stored command; leftover listener not adopted`);else e.log(n.name,"WARN",`pid ${n.pid} is a different process; not adopting`);continue}let s=sy(e,n.name,n.pid,n.command,n.cwd,new Date(n.startTime||e.clock.unixMs()))??e.orchestrator.health.bumpGeneration(n.name);if(Object.keys(n.ports).length>0)e.ports.set(n.name,n.ports);yc(e,n.name,xc(e,n)),e.setState(n.name,je,Et,n.pid,"");let a=e.cfg.services[n.name];if(a){let c=n.cwd||e.serviceWorkDir(a),l=await vc(e,n.name,a,n.ports);e.orchestrator.health.startHealth(n.name,a,n.pid,n.ports,c,l,s)}e.log(n.name,"INFO","adopted leftover process; stdout/stderr from before adopt are not captured"),r.push(n.name)}if(r.length>0)e.bus.publish(Se($m,"",{services:r,session_id:t.session_id})),e.log("devctl","INFO",`recovered session processes: ${r.join(", ")}`)}function w0(e,t){e.profile=t||e.profile,e.profileEnv=uo(e.cfg,e.profile)}function yc(e,t,r){e.serviceProfile.set(t,r),e.serviceProfileEnv.set(t,uo(e.cfg,r))}function xc(e,t){return t?.profile??e.profile}import{statSync as $0,watch as F0}from"fs";import{isAbsolute as N0,join as H0,resolve as j0}from"path";function ly(e,t){return t}function Du(e,t){let r={},n=new Set,o=new Set([...Object.keys(e.services),...Object.keys(t.services)]);for(let s of o){let a=e.services[s],c=t.services[s],l=[];if(!a||!c)l.push("presence"),n.add(s);else{if(a.command.args.join("\x00")!==c.command.args.join("\x00")||a.command.shell!==c.command.shell||a.shell!==c.shell)l.push("command"),n.add(s);if(a.working_dir!==c.working_dir)l.push("working_dir"),n.add(s);if(JSON.stringify(a.environment)!==JSON.stringify(c.environment))l.push("environment"),n.add(s);if(JSON.stringify(a.ports)!==JSON.stringify(c.ports))l.push("ports"),n.add(s);if(JSON.stringify(a.identity)!==JSON.stringify(c.identity))l.push("identity"),n.add(s);if(JSON.stringify(a.health)!==JSON.stringify(c.health))l.push("health"),n.add(s);if(JSON.stringify(a.restart)!==JSON.stringify(c.restart))l.push("restart"),n.add(s);if(JSON.stringify(a.startup)!==JSON.stringify(c.startup))l.push("startup"),n.add(s);if(JSON.stringify(a.logs)!==JSON.stringify(c.logs))l.push("logs"),n.add(s);if(JSON.stringify(a.container)!==JSON.stringify(c.container))l.push("container"),n.add(s);if(JSON.stringify(a.watch)!==JSON.stringify(c.watch))l.push("watch"),n.add(s)}if(l.length>0)r[s]=l}let i=[];if(JSON.stringify(e.logs)!==JSON.stringify(t.logs))i.push("logs");if(JSON.stringify(e.auth)!==JSON.stringify(t.auth))i.push("auth");return{restart_required:[...n].sort(),changes:r,supervisor_restart_required:i}}Tt();te();import{pathToFileURL as L0}from"url";import{isAbsolute as D0,resolve as I0}from"path";import{createServer as my,request as S0}from"http";import{request as k0}from"https";import{randomBytes as dy}from"crypto";import{Readable as C0}from"stream";te();jr();var cs="x-devctl-request-id",P0="x-devctl-internal-token",uy=100,Iu=new Set(["connection","keep-alive","proxy-authenticate","proxy-authorization","te","trailers","transfer-encoding","upgrade"]),R0=new Set(["gzip","deflate","br"]);class Mu{cfg;tokens;logs;bus;detector;resolvePort;middleware;server;running=!1;addr="";upgradedSockets=new Set;recentRequests=[];constructor(e,t,r,n,o,i=[],s){this.cfg=e,this.tokens=t,this.logs=r,this.bus=n,this.detector=o,this.middleware=i,this.resolvePort=s}upstreamBase(e){let t=e.upstream.service??"";if(t==="")return e.upstream.url;if(!this.resolvePort)throw I(Nr,`route ${e.name} addresses service ${t} but this proxy has no port resolver configured`);let r=this.resolvePort(t,e.upstream.port??"");if(r===void 0)throw I(Nr,`upstream service ${t} is not running`);return`http://127.0.0.1:${r}`}address(){return this.addr||xn(this.cfg.listen)}setMiddleware(e){this.middleware=e}isRunning(){return this.running}stats(){let e=this.recentRequests.reduce((t,r)=>t+(r.status>=400||r.error?1:0),0);return{total:this.recentRequests.length,errors:e,recent:[...this.recentRequests].reverse()}}recordRequest(e){if(this.recentRequests.push(e),this.recentRequests.length>uy)this.recentRequests.splice(0,this.recentRequests.length-uy)}start(){let e=this.cfg.listen.host||"127.0.0.1";if(!Cn(e))return Promise.reject(I(Nr,`refusing to bind proxy to ${e}`));if(this.cfg.listen.port===0)return Promise.reject(I(Nr,"proxy.listen.port is required when the proxy is enabled"));let t=this.cfg.listen.port;return new Promise((r,n)=>{this.server=my((o,i)=>{this.serve(o,i)}),this.server.on("upgrade",(o,i,s)=>{this.serveUpgrade(o,i,s)}),this.server.on("error",(o)=>n(Pe(Nr,`unable to listen on ${e}:${t}`,o))),this.server.listen(t,e,()=>{this.running=!0,this.addr=`${e}:${t}`,this.bus?.publish(Se(Mm,"",{address:this.addr})),r()})})}stop(){let e=this.server;if(!e)return Promise.resolve();for(let t of this.upgradedSockets)t.destroy();return this.upgradedSockets.clear(),new Promise((t)=>{e.close(()=>{this.running=!1,this.bus?.publish(Se(Om,"",{})),t()})})}async serveUpgrade(e,t,r){let n=Date.now(),o=e.headers[cs]?.toString()||dy(8).toString("hex"),i=e.method??"GET",s=e.url??"/",a=this.detector?this.detector.redactText(s):s,c=fy(this.cfg.routes,e);if(!c){t.end(`HTTP/1.1 404 Not Found\r
643
2186
  Connection: close\r
644
2187
  Content-Length: 0\r
645
2188
  \r
646
- `),this.recordRequest({timestamp:new Date().toISOString(),requestId:o,method:i,path:a,route:"",identity:"",status:404,durationMs:Date.now()-n});return}let l=to(c.auth),d=jo(l),u,f=!1,p=(g,y)=>{if(f)return;f=!0;let x=Date.now()-n;this.recordRequest({timestamp:new Date().toISOString(),requestId:o,method:i,path:a,route:c.name,identity:d,status:g,durationMs:x,error:y}),this.logs?.append({timestamp:new Date().toISOString(),service:"proxy",source:"proxy",level:g>=400||y?"ERROR":"INFO",message:`${i} ${s} route=${c.name} identity=${d} status=${g} duration=${x}ms upgrade=true${y?` error=${y}`:""}`,pid:0,request_id:o,identity:d}),this.bus?.publish(Se(nd,c.name,{status:g,request_id:o,duration:x,identity:d}))},m=()=>{t.destroy(),u?.destroy()};try{let g={};for(let[w,v]of Object.entries(e.headers)){let b=w.toLowerCase();if(Pu.has(b)&&b!=="connection"&&b!=="upgrade")continue;if(typeof v==="string")g[w]=v;else if(Array.isArray(v))g[w]=v.join(",")}let y=e.socket.remoteAddress??"",x=g.host??"",h=g["x-forwarded-for"];if(g["x-forwarded-for"]=h?`${h}, ${y}`:y,g["x-forwarded-host"]=x,g["x-forwarded-proto"]="http",delete g.host,g[ns]=o,this.middleware.length===0)await mc(c,g,this.tokens);for(let w of this.middleware)await w.apply({route:c,headers:g,tokens:this.tokens,req:e,method:i,path:s,upgrade:!0});let P=Qy(c.upstream.url,s),_=nT(P)(P,{method:i,headers:g});_.on("upgrade",(w,v,b)=>{u=v,this.upgradedSockets.add(t),this.upgradedSockets.add(v);let S=()=>{this.upgradedSockets.delete(t),this.upgradedSockets.delete(v)};t.once("close",()=>{S(),v.destroy()}),v.once("close",()=>{S(),t.destroy()}),t.once("error",m),v.once("error",m);let k=`HTTP/${w.httpVersion} ${w.statusCode??101} ${w.statusMessage??"Switching Protocols"}\r
647
- `,L=w.rawHeaders.map((I,F)=>`${F%2===0?I+":":" "+I+`\r
2189
+ `),this.recordRequest({timestamp:new Date().toISOString(),requestId:o,method:i,path:a,route:"",identity:"",status:404,durationMs:Date.now()-n});return}let l=oo(c.auth),d=Ko(l),u,f=!1,p=(h,v)=>{if(f)return;f=!0;let x=Date.now()-n;this.recordRequest({timestamp:new Date().toISOString(),requestId:o,method:i,path:a,route:c.name,identity:d,status:h,durationMs:x,error:v}),this.logs?.append({timestamp:new Date().toISOString(),service:"proxy",source:"proxy",level:h>=400||v?"ERROR":"INFO",message:`${i} ${s} route=${c.name} identity=${d} status=${h} duration=${x}ms upgrade=true${v?` error=${v}`:""}`,pid:0,request_id:o,identity:d}),this.bus?.publish(Se(ld,c.name,{status:h,request_id:o,duration:x,identity:d}))},m=()=>{t.destroy(),u?.destroy()};try{let h={};for(let[S,y]of Object.entries(e.headers)){let b=S.toLowerCase();if(Iu.has(b)&&b!=="connection"&&b!=="upgrade")continue;if(typeof y==="string")h[S]=y;else if(Array.isArray(y))h[S]=y.join(",")}let v=e.socket.remoteAddress??"",x=h.host??"",g=h["x-forwarded-for"];if(h["x-forwarded-for"]=g?`${g}, ${v}`:v,h["x-forwarded-host"]=x,h["x-forwarded-proto"]="http",delete h.host,h[cs]=o,this.middleware.length===0)await bc(c,h,this.tokens);for(let S of this.middleware)await S.apply({route:c,headers:h,tokens:this.tokens,req:e,method:i,path:s,upgrade:!0});let R=py(this.upstreamBase(c),s),_=E0(R)(R,{method:i,headers:h});_.on("upgrade",(S,y,b)=>{u=y,this.upgradedSockets.add(t),this.upgradedSockets.add(y);let w=()=>{this.upgradedSockets.delete(t),this.upgradedSockets.delete(y)};t.once("close",()=>{w(),y.destroy()}),y.once("close",()=>{w(),t.destroy()}),t.once("error",m),y.once("error",m);let k=`HTTP/${S.httpVersion} ${S.statusCode??101} ${S.statusMessage??"Switching Protocols"}\r
2190
+ `,L=S.rawHeaders.map((O,F)=>`${F%2===0?O+":":" "+O+`\r
648
2191
  `}`).join("");if(t.write(`${k}${L}\r
649
- `),r.length>0)v.write(r);if(b.length>0)t.write(b);t.pipe(v).pipe(t),p(w.statusCode??101)}),_.on("response",(w)=>{w.resume();let v=w.statusCode??502;t.end(`HTTP/1.1 ${v} ${w.statusMessage??"Bad Gateway"}\r
2192
+ `),r.length>0)y.write(r);if(b.length>0)t.write(b);t.pipe(y).pipe(t),p(S.statusCode??101)}),_.on("response",(S)=>{S.resume();let y=S.statusCode??502;t.end(`HTTP/1.1 ${y} ${S.statusMessage??"Bad Gateway"}\r
650
2193
  Connection: close\r
651
2194
  Content-Length: 0\r
652
2195
  \r
653
- `),p(v,"upstream refused protocol upgrade")}),_.on("error",(w)=>{p(502,w.message),t.end(`HTTP/1.1 502 Bad Gateway\r
2196
+ `),p(y,"upstream refused protocol upgrade")}),_.on("error",(S)=>{p(502,S.message),t.end(`HTTP/1.1 502 Bad Gateway\r
654
2197
  Connection: close\r
655
2198
  Content-Length: 0\r
656
2199
  \r
657
- `)}),t.once("error",()=>_.destroy()),_.end()}catch(g){let y=g instanceof Error?g.message:"proxy upgrade error";p(502,y),t.end(`HTTP/1.1 502 Bad Gateway\r
2200
+ `)}),t.once("error",()=>_.destroy()),_.end()}catch(h){let v=h instanceof Error?h.message:"proxy upgrade error";p(502,v),t.end(`HTTP/1.1 502 Bad Gateway\r
658
2201
  Connection: close\r
659
2202
  Content-Length: 0\r
660
2203
  \r
661
- `)}}async serve(e,t){let r=Date.now(),n=e.headers[ns]?.toString()||Yy(8).toString("hex");t.setHeader(ns,n);let o=e.method??"GET",i=e.url??"/",s=this.detector?this.detector.redactText(i):i,a=Zy(this.cfg.routes,e);if(!a){Qo(t,404,"no matching proxy route"),this.recordRequest({timestamp:new Date().toISOString(),requestId:n,method:o,path:s,route:"",identity:"",status:404,durationMs:Date.now()-r});return}let c=to(a.auth),l=jo(c),d=0,u;try{let f={};for(let[v,b]of Object.entries(e.headers)){if(Pu.has(v.toLowerCase()))continue;if(typeof b==="string")f[v]=b;else if(Array.isArray(b))f[v]=b.join(",")}let p=e.socket.remoteAddress??"",m=f.host??"",g=f["x-forwarded-for"];if(f["x-forwarded-for"]=g?`${g}, ${p}`:p,f["x-forwarded-host"]=m,f["x-forwarded-proto"]="http",delete f.host,f["accept-encoding"]="gzip, deflate, br",f[ns]=n,this.middleware.length===0)await mc(a,f,this.tokens);for(let v of this.middleware)await v.apply({route:a,headers:f,tokens:this.tokens});let y=Qy(a.upstream.url,i),x=o!=="GET"&&o!=="HEAD"?e:void 0,h=await fetch(y,{method:o,headers:f,body:x,redirect:"manual",duplex:x?"half":void 0});t.statusCode=h.status,d=h.status;let P=(h.headers.get("content-encoding")??"").split(",").map((v)=>v.trim().toLowerCase()).filter((v)=>v!==""),_=P.length>0&&P.every((v)=>tT.has(v));h.headers.forEach((v,b)=>{let S=b.toLowerCase();if(Pu.has(S))return;if(_&&(S==="content-encoding"||S==="content-length"))return;t.setHeader(b,this.detector?this.detector.redactText(v):v)}),await rT(h,t);let w=Date.now()-r;this.logs?.append({timestamp:new Date().toISOString(),service:"proxy",source:"proxy",level:h.status>=400?"WARN":"INFO",message:`${o} ${i} route=${a.name} identity=${l} status=${h.status} duration=${w}ms`,pid:0,request_id:n,identity:l}),this.bus?.publish(Se(nd,a.name,{status:h.status,request_id:n,duration:w,identity:l}))}catch(f){let p=f instanceof Error?f.message:"proxy error";u=p,d=502,this.logs?.append({timestamp:new Date().toISOString(),service:"proxy",source:"proxy",level:"ERROR",message:`${o} ${i} route=${a.name} identity=${l} error=${p}`,pid:0,request_id:n,identity:l}),Qo(t,502,"proxy error")}finally{this.recordRequest({timestamp:new Date().toISOString(),requestId:n,method:o,path:s,route:a.name,identity:l,status:d,durationMs:Date.now()-r,error:u})}}}async function mc(e,t,r){let n=e.auth.type.toLowerCase();if(n===""||n==="none")return;if(!r)throw M(fn,"token manager unavailable");let o=to(e.auth),i=await r.get(jo(o),e.auth.audience,[]);t.authorization=`Bearer ${i.accessToken}`}async function rT(e,t){if(!e.body){t.end();return}let r=Z0.fromWeb(e.body);await new Promise((n,o)=>{r.on("error",o),t.on("error",o),t.on("finish",n),r.pipe(t)})}function nT(e){return e.protocol==="https:"?Q0:X0}function Qy(e,t){let r=new URL(e),n=new URL(t||"/",r),o=new URL(r.href);if(o.pathname=oT(n.pathname),o.search=n.search,o.hash="",o.origin!==r.origin)throw M(fn,"refusing to proxy to a different origin");return o}function oT(e){return(e.startsWith("/")?e:`/${e}`).replace(/^\/+/,"/")}function Qo(e,t,r){e.statusCode=t,e.setHeader("content-type","text/plain; charset=utf-8"),e.setHeader("x-content-type-options","nosniff"),e.end(r)}function Zy(e,t){let r=(t.headers.host??"").split(":")[0]??"",n=t.url??"/";return e.find((o)=>{let i=o.match.host===""||o.match.host===r,s=o.match.path===""||n.startsWith(o.match.path);return i&&s})}class Eu{host;port;secret;tokens;server;constructor(e,t,r,n){this.host=e;this.port=t;this.secret=r;this.tokens=n}listenPort(){let e=this.server?.address();if(typeof e==="object"&&e)return e.port;return this.port}start(){let e=this.host||"127.0.0.1";if(!kn(e))return Promise.reject(M(fn,`refusing to bind token endpoint to ${e}`));return new Promise((t,r)=>{this.server=ev((n,o)=>{this.serve(n,o)}),this.server.on("error",(n)=>r(Re(fn,"token endpoint listen failed",n))),this.server.listen(this.port,e,()=>t())})}stop(){let e=this.server;if(!e)return Promise.resolve();return new Promise((t)=>e.close(()=>t()))}async serve(e,t){if(!iT(e.socket.remoteAddress)){Qo(t,403,"forbidden");return}if((e.headers[eT]??"")!==this.secret){Qo(t,401,"unauthorized");return}if(e.url?.startsWith("/token")!==!0){Qo(t,404,"not found");return}let r=new URL(e.url,"http://127.0.0.1"),n=r.searchParams.get("identity")??"user",o=r.searchParams.get("audience")??"";try{let i=await this.tokens.get(n,o,[]);t.setHeader("content-type","application/json"),t.end(JSON.stringify({access_token:i.accessToken,token_type:i.tokenType,expires_at:i.expiresAt.toISOString(),identity:i.identity}))}catch{Qo(t,500,"token error")}}}function iT(e){if(!e)return!0;return e==="127.0.0.1"||e==="::1"||e==="::ffff:127.0.0.1"}Fr();var tv=1;class rv{tokenProviders=[];identityProviders=[];pluginIdentityProviders=[];environmentSources=[];healthChecks=[];logParsers=[];proxyMiddleware=[];loadErrors=[];pluginPaths=[];registerBuiltins(){this.tokenProviders.push(...Rn()),this.identityProviders.push(Xd(),Qd()),this.logParsers.push(Dm()),this.proxyMiddleware.push(dT())}register(e){lT(e),co(this.tokenProviders,e.tokenProviders),co(this.identityProviders,e.identityProviders),co(this.pluginIdentityProviders,e.identityProviders),co(this.environmentSources,e.environmentSources),co(this.healthChecks,e.healthChecks),co(this.logParsers,e.logParsers),co(this.proxyMiddleware,e.proxyMiddleware)}}async function gc(e,t=process.cwd()){let r=new rv;r.registerBuiltins();for(let n of e){if(n.trim()==="")continue;try{let o=n.startsWith("file:")?n:aT(n)?n:cT(t,n),s=await import(o.startsWith("file:")?o:sT(o).href);if(s.sdkVersion!==tv)throw Error(`plugin SDK version ${String(s.sdkVersion??"missing")} is incompatible; expected ${tv}`);r.register(s),r.pluginPaths.push(o)}catch(o){r.loadErrors.push({path:n,message:o instanceof Error?o.message:String(o)})}}return r}function lT(e){if(!e||typeof e!=="object")throw Error("plugin module must export an object");Zo("tokenProviders",e.tokenProviders,["fetch"]),Zo("identityProviders",e.identityProviders,["accepts","resolve"]),Zo("environmentSources",e.environmentSources,["load"]),Zo("healthChecks",e.healthChecks,["check"]),Zo("logParsers",e.logParsers,["parse"]),Zo("proxyMiddleware",e.proxyMiddleware,["apply"])}function Zo(e,t,r){if(t===void 0)return;if(!Array.isArray(t))throw Error(`${e} must be an array`);for(let[n,o]of t.entries()){if(!o||typeof o!=="object"||typeof o.name!=="string"||o.name==="")throw Error(`${e}[${n}] must have a name`);for(let i of r)if(typeof o[i]!=="function")throw Error(`${e}[${n}].${i} must be a function`)}}function dT(){return{name:"identity_inject",apply:async(e)=>{await mc(e.route,e.headers,e.tokens)}}}function co(e,t){if(!t)return;e.push(...t)}var hT=200;function Tu(e){if(!e.registry)return;if(e.registry.tokenProviders.length>0)e.tokens.replaceProviders(e.registry.tokenProviders);e.logs.setParsers(e.registry.logParsers,e.registry.pluginPaths),e.proxy?.setMiddleware?.(e.registry.proxyMiddleware)}function Au(e,t){let r=new Map;for(let n of e){let o=vT(n,t);try{r.set(o,uT(o).mtimeMs)}catch{}}return r}async function yT(e,t,r){let n=t.plugins.map((a)=>a.path),o=JSON.stringify(r)!==JSON.stringify(n),i=Au(n,t.repoRoot),s=[];for(let[a,c]of i){let l=e.pluginMtimes.get(a);if(l!==void 0&&l!==c){s.push("plugins");break}}if(o){let a=await gc(n,t.repoRoot);e.registry=a;for(let c of a.loadErrors)e.log("devctl","ERROR",`plugin ${c.path} skipped: ${c.message}`);Tu(e),e.log("devctl","INFO","plugin path list reloaded")}return e.pluginMtimes=i,s}function vT(e,t){if(e.startsWith("file:"))return e;return fT(e)?e:gT(t,e)}function Lu(e,t){let r=Vd(t);if(r.length===0)return;let n=new Set((e?.healthChecks??[]).map((i)=>i.name.toLowerCase())),o=r.filter((i)=>!n.has(i.type.toLowerCase()));if(o.length>0)throw M(ee,`unknown health check type(s): ${o.map((i)=>`${i.service}.health.type=${i.type}`).join(", ")}`)}function Du(e,t){let r=Jd(t);if(r.length===0)return;let n=e?.pluginIdentityProviders??[],o=r.filter(({service:i})=>{let s=t.services[i];return!s||!n.some((a)=>a.accepts(s.identity))});if(o.length>0)throw M(ee,`unknown identity type(s): ${o.map((i)=>`${i.service}.identity.type=${i.type}`).join(", ")}`)}function Mu(e,t){let r=new Set(Vi),n=new Set((e?.environmentSources??[]).map((i)=>i.name)),o=t.environment.sources.filter((i)=>!r.has(i)&&!n.has(i));if(o.length>0)throw M(ee,`unknown environment source(s): ${o.join(", ")}`)}function xT(e,t,r){for(let n of Object.keys(r))if(!t[n]&&!e.runtimes.has(n))e.runtimes.set(n,Ki(n));for(let n of Object.keys(t)){if(r[n])continue;if(e.orchestrator.serviceIsActive(n)){let o=e.runtimes.get(n);if(o)o.orphaned=!0;e.log(n,"WARN","removed from configuration while still running; now orphaned \u2014 stop it explicitly to clean it up");continue}e.forgetService(n)}}function Ou(e){let t=mT(e.cfg.repoRoot,".devctl");if(!e.fs.exists(t))return;try{e.configWatcher=pT(t,{recursive:!0},()=>{if(e.watchTimer)clearTimeout(e.watchTimer);e.watchTimer=setTimeout(()=>{e.reload().catch((r)=>e.log("devctl","WARN",A(r)))},hT)})}catch{e.log("devctl","WARN","unable to watch .devctl for configuration changes")}}async function nv(e){let t;try{t=tn(e.cfg.repoRoot,e.cfg.configPath)}catch(l){throw e.bus.publish(Se(Ri,"",{error:A(l)})),e.log("devctl","ERROR",`configuration reload failed: ${A(l)}`),l}try{Lu(e.registry,t),Du(e.registry,t),Mu(e.registry,t)}catch(l){throw e.bus.publish(Se(Ri,"",{error:A(l)})),e.log("devctl","ERROR",`configuration reload failed: ${A(l)}`),l}if(e.setupMode){if(e.setupMode=!1,e.log("devctl","INFO",`configuration created at ${e.cfg.configPath}; leaving setup mode`),!e.configWatcher)Ou(e)}let r=Ru(e.cfg,t),n=JSON.stringify(e.cfg.proxy)!==JSON.stringify(t.proxy),o=JSON.stringify(e.cfg.secrets)!==JSON.stringify(t.secrets),i=e.cfg.plugins.map((l)=>l.path),s=e.cfg.services;e.cfg=zy(e.cfg,t),xT(e,s,t.services);let a=bT(e.restartRequired,r.restart_required,Object.keys(t.services));e.restartRequired=a,r.restart_required=a;let c=await yT(e,t,i);if(c.length>0)r.supervisor_restart_required=[...r.supervisor_restart_required??[],...c];if(e.syncServiceWatchers(),o)e.detector.update(t.secrets.extra_markers,t.secrets.extra_patterns),e.logs.setSecrets(t.secrets.extra_markers,t.secrets.extra_patterns);if(n){let l=e.proxy?.isRunning()??!1;if(await e.stopProxy(),l&&e.cfg.proxy.enabled)await e.startProxy();e.log("devctl","INFO","proxy configuration changed; proxy restarted")}if(e.bus.publish(Se(na,"",{restart_required:r.restart_required,changes:r.changes,supervisor_restart_required:r.supervisor_restart_required})),e.log("devctl","INFO",r.restart_required.length===0?"configuration reloaded":`configuration reloaded; restart required: ${r.restart_required.join(", ")}`),r.supervisor_restart_required)e.log("devctl","WARN",Qa(r.supervisor_restart_required));return e.persistState(),e.refreshIdentity(),r}function bT(e,t,r){let n=new Set(r),o=new Set(e.filter((i)=>n.has(i)));for(let i of t)if(n.has(i))o.add(i);return[...o].sort()}import{watch as ST}from"fs";import{join as iv,relative as kT}from"path";mt();function ov(e,t){if(!e?.enabled)return!1;let r=t.replaceAll("\\","/").replace(/^\.\//,"");if(r===""||r.includes(".."))return!1;if((e.ignore.length>0?e.ignore:Di).some((o)=>wT(o,r)))return!1;if(e.paths.length===0)return!1;return e.paths.some((o)=>{let i=o.replaceAll("\\","/").replace(/\/$/,"");return r===i||r.startsWith(`${i}/`)})}function wT(e,t){let r=e.replace(/[.+^${}()|[\]\\]/g,"\\$&").replaceAll("**/","<<<DS>>>").replaceAll("**","<<<DS>>>").replaceAll("*","[^/]*").replaceAll("<<<DS>>>",".*");return new RegExp(`^${r}$`).test(t)}class Iu{host;entries=new Map;constructor(e){this.host=e}sync(e){let t=new Set;for(let[r,n]of Object.entries(e)){if(!n.watch.enabled||n.watch.paths.length===0){this.stop(r);continue}t.add(r);let o=JSON.stringify(n.watch);if(this.entries.get(r)?.signature===o)continue;this.stop(r),this.start(r,n.watch)}for(let r of[...this.entries.keys()])if(!t.has(r))this.stop(r)}watched(){return[...this.entries.keys()].sort()}close(){for(let e of[...this.entries.keys()])this.stop(e)}start(e,t){let r=[],n=this.host.repoRoot();for(let o of t.paths){let i=o.replaceAll("\\","/").replace(/\/$/,"");if(i===""||i==="."){this.host.log(e,"WARN","service.watch skipped a whole-repo path; list concrete directories");continue}let s=iv(n,i);try{let a=ST(s,{recursive:!0},(c,l)=>{let d=l?iv(s,l.toString()):s,u=kT(n,d).replaceAll("\\","/");if(!ov(t,u))return;this.schedule(e,t.debounce_ms)});r.push(a)}catch{this.host.log(e,"WARN",`unable to watch ${i}`)}}if(r.length===0)return;this.entries.set(e,{signature:JSON.stringify(t),watch:t,handles:r}),this.host.log(e,"INFO",`watching ${t.paths.join(", ")}`)}schedule(e,t){let r=this.entries.get(e);if(!r)return;if(r.timer)clearTimeout(r.timer);r.timer=setTimeout(()=>{if(r.timer=void 0,!this.host.isActive(e))return;this.host.log(e,"INFO","watch: restarting after file change"),this.host.restart(e).catch((n)=>{this.host.log(e,"WARN",n instanceof Error?n.message:String(n))})},t)}stop(e){let t=this.entries.get(e);if(!t)return;if(t.timer)clearTimeout(t.timer);for(let r of t.handles)r.close();this.entries.delete(e)}}import{isAbsolute as $u,join as Fu}from"path";class Nu{clientEnv=new Map;serviceProfile=new Map;serviceProfileEnv=new Map;profile="";profileEnv={};deps;constructor(e){this.deps=e}forget(e){this.clientEnv.delete(e),this.serviceProfile.delete(e),this.serviceProfileEnv.delete(e)}serviceWorkDir(e){if(e.working_dir!==""&&!$u(e.working_dir))return Fu(this.deps.cfg().repoRoot,e.working_dir);return e.working_dir}async resolveServiceExecution(e,t,r,n,o,i=!0){let s=this.deps.cfg(),a=this.deps.ports().get(e)??Object.fromEntries(t.ports.filter((p)=>!p.auto).map((p)=>[p.name,p.value])),c=this.deps.proxy(),l=c?.isRunning()?`http://${c.address()}`:s.proxy.enabled?`http://${yn(s.proxy.listen)}`:"",d=zi(e,"127.0.0.1",a,l,s.project.name);if(!t.container){if(d.DEVCTL_INTERNAL_TOKEN=this.deps.internalTok(),s.proxy.token_endpoint.enabled)d.DEVCTL_TOKEN_URL=this.deps.boundTokenURL()||`http://127.0.0.1:${this.deps.tokenEndpoint()?.listenPort()||s.proxy.token_endpoint.port}/token`}let u=await Ji(s.repoRoot,{service:e,profile:r,serviceCfg:t,profileEnv:n,assignedPorts:a,runtime:d,cfg:s,fetchSecret:rs(async()=>(await this.deps.tokens.get("user","",[])).accessToken),pluginSources:this.deps.environmentSources(),clientEnv:o,includeProcess:i}),f=t.working_dir&&!$u(t.working_dir)?Fu(s.repoRoot,t.working_dir):t.working_dir;return{env:Yo(u),workDir:f}}async resolveTaskEnvironment(e,t,r){let n=this.deps.cfg(),o=await Ji(n.repoRoot,{service:`task:${e}`,profile:this.profile,serviceCfg:t,profileEnv:this.profileEnv,assignedPorts:{},runtime:zi(`task:${e}`,"127.0.0.1",{},"",n.project.name),cfg:n,clientEnv:r,fetchSecret:rs(async()=>(await this.deps.tokens.get("user","",[])).accessToken),pluginSources:this.deps.environmentSources()}),i=t.working_dir&&!$u(t.working_dir)?Fu(n.repoRoot,t.working_dir):t.working_dir;return{env:Yo(o),workDir:i}}}Fr();te();Fr();Tt();import{cpus as MT,loadavg as OT,platform as IT,uptime as $T}from"os";import{readFileSync as CT}from"fs";import{freemem as RT,totalmem as PT}from"os";import{spawnSync as _T}from"child_process";var Hu=1024,ET=2000,TT=/page size of (\d+) bytes/i,ei;function AT(e){let t=e.match(/^MemAvailable:\s+(\d+)/m);if(!t)return;let r=Number(t[1]);return Number.isFinite(r)&&r>=0?r:void 0}function LT(e){let t=e.match(TT),r=t?Number(t[1]):0;if(!Number.isFinite(r)||r<=0)return;let n=hc(e,"free")+hc(e,"inactive")+hc(e,"speculative")+hc(e,"purgeable");return Math.round(n*r/Hu)}function hc(e,t){let r=e.match(new RegExp(`Pages ${t}:\\s+(\\d+)`,"i")),n=r?Number(r[1]):0;return Number.isFinite(n)&&n>0?n:0}function DT(){if(process.platform==="linux")try{return AT(CT("/proc/meminfo","utf8"))}catch{return}if(process.platform==="darwin"){let e=Date.now();if(ei&&e-ei.at<ET)return ei.leftoverKB;let t=_T("vm_stat",{encoding:"utf8"});if(t.status!==0||!t.stdout)return ei?.leftoverKB;let r=LT(t.stdout);if(r===void 0)return ei?.leftoverKB;return ei={at:e,leftoverKB:r},r}return}function sv(){let e=Math.max(0,Math.round(PT()/Hu)),t=Math.max(0,Math.round(RT()/Hu)),r=Math.max(0,Math.min(e,DT()??t));return{totalKB:e,unusedKB:t,leftoverKB:r}}function av(e){return{user:"",project:e?.google.project_id??"",project_source:e?.google.project_id?"configuration":"",adc:!1,service_accounts:{},service_account_status:Object.fromEntries(e?$r(e).map((t)=>[t,"unknown"]):[]),iap:e?.proxy.routes.some((t)=>t.auth.type.toLowerCase()==="iap")??!1}}function Bu(){let e=OT(),t=sv();return{platform:IT(),cpuCount:MT().length,loadAvg1:e[0]??0,loadAvg5:e[1]??0,loadAvg15:e[2]??0,memTotalKB:t.totalKB,memFreeKB:t.unusedKB,memAvailableKB:t.leftoverKB,hostUptimeSec:$T()}}function Wu(e,t){let r={},n={};for(let o of $r(e)){let i=t.get(o)??"unknown";if(n[o]=i,i!=="unknown")r[o]=i==="available"}return{service_accounts:r,service_account_status:n}}function cv(e){let t={};for(let[o,i]of e.runtimes)t[o]={...i,ports:e.ports.get(o)??i.ports,profile:e.serviceProfile.get(o)??i.profile,env_source:e.clientEnv.has(o)?"client":"daemon"};let r=e.proxy?.stats(),n={requestTotal:r?.total??0,requestErrors:r?.errors??0,recentRequests:r?.recent??[]};return{session_id:e.sessionID,repo_root:e.cfg.repoRoot,profile:e.profile,services:t,proxy:{running:e.proxy?.isRunning()??!1,address:e.proxy?.address(),routes:e.cfg.proxy.routes.map((o)=>{let i=o.match.host||"*",s=o.match.path,a=s===""?i:`${i}${s.startsWith("/")?s:`/${s}`}`;return{name:o.name,identity:o.auth.identity.service_account||o.auth.identity.type||o.auth.type,upstream:o.upstream.url,auth:o.auth.type,match:a}}),...n},mcp:{running:e.mcp?.isRunning()??!1,address:e.mcp?.isRunning()?`http://${e.mcp.address()}/mcp`:void 0,port:e.mcp?.isRunning()?e.mcp.listenPort():void 0,token:e.mcpToken,disabled_tools:[...e.mcpDisabledTools]},identity:{...e.identityCache,...Wu(e.cfg,e.serviceAccountStatus)},credentials:{backend:e.tokens.storeBackend(),entries:[...e.credentialEntries]},detached:e.detached,setup_mode:e.setupMode?!0:void 0,logs:e.logs.snapshot(),restart_required:[...e.restartRequired],system:Bu(),stats_series:e.statsSeries}}function lv(e){let t=[`PROFILE: ${e.profile||"(none)"}`,"","SERVICE\tSTATUS\tHEALTH\tPID"];for(let[r,n]of Object.entries(e.services))t.push(`${r} ${Jo(n)} ${n.health} ${n.pid}`);return t.push("",`PROXY ${e.proxy.running?"RUNNING":"STOPPED"} ${e.proxy.address??""}`),t.push(`MCP ${e.mcp?.running?"RUNNING":"STOPPED"} ${e.mcp?.address??""}`),t.join(`
2204
+ `)}}async serve(e,t){let r=Date.now(),n=e.headers[cs]?.toString()||dy(8).toString("hex");t.setHeader(cs,n);let o=e.method??"GET",i=e.url??"/",s=this.detector?this.detector.redactText(i):i,a=fy(this.cfg.routes,e);if(!a){ei(t,404,"no matching proxy route"),this.recordRequest({timestamp:new Date().toISOString(),requestId:n,method:o,path:s,route:"",identity:"",status:404,durationMs:Date.now()-r});return}let c=oo(a.auth),l=Ko(c),d=0,u;try{let f={};for(let[y,b]of Object.entries(e.headers)){if(Iu.has(y.toLowerCase()))continue;if(typeof b==="string")f[y]=b;else if(Array.isArray(b))f[y]=b.join(",")}let p=e.socket.remoteAddress??"",m=f.host??"",h=f["x-forwarded-for"];if(f["x-forwarded-for"]=h?`${h}, ${p}`:p,f["x-forwarded-host"]=m,f["x-forwarded-proto"]="http",delete f.host,f["accept-encoding"]="gzip, deflate, br",f[cs]=n,this.middleware.length===0)await bc(a,f,this.tokens);for(let y of this.middleware)await y.apply({route:a,headers:f,tokens:this.tokens});let v=py(this.upstreamBase(a),i),x=o!=="GET"&&o!=="HEAD"?e:void 0,g=await fetch(v,{method:o,headers:f,body:x,redirect:"manual",duplex:x?"half":void 0});t.statusCode=g.status,d=g.status;let R=(g.headers.get("content-encoding")??"").split(",").map((y)=>y.trim().toLowerCase()).filter((y)=>y!==""),_=R.length>0&&R.every((y)=>R0.has(y));g.headers.forEach((y,b)=>{let w=b.toLowerCase();if(Iu.has(w))return;if(_&&(w==="content-encoding"||w==="content-length"))return;t.setHeader(b,this.detector?this.detector.redactText(y):y)}),await _0(g,t);let S=Date.now()-r;this.logs?.append({timestamp:new Date().toISOString(),service:"proxy",source:"proxy",level:g.status>=400?"WARN":"INFO",message:`${o} ${i} route=${a.name} identity=${l} status=${g.status} duration=${S}ms`,pid:0,request_id:n,identity:l}),this.bus?.publish(Se(ld,a.name,{status:g.status,request_id:n,duration:S,identity:l}))}catch(f){let p=f instanceof Error?f.message:"proxy error";u=p,d=502,this.logs?.append({timestamp:new Date().toISOString(),service:"proxy",source:"proxy",level:"ERROR",message:`${o} ${i} route=${a.name} identity=${l} error=${p}`,pid:0,request_id:n,identity:l}),ei(t,502,"proxy error")}finally{this.recordRequest({timestamp:new Date().toISOString(),requestId:n,method:o,path:s,route:a.name,identity:l,status:d,durationMs:Date.now()-r,error:u})}}}async function bc(e,t,r){let n=e.auth.type.toLowerCase();if(n===""||n==="none")return;if(!r)throw I(Nr,"token manager unavailable");let o=oo(e.auth),i=await r.get(Ko(o),e.auth.audience,[],Qa(e.auth));t.authorization=`Bearer ${i.accessToken}`}async function _0(e,t){if(!e.body){t.end();return}let r=C0.fromWeb(e.body);await new Promise((n,o)=>{r.on("error",o),t.on("error",o),t.on("finish",n),r.pipe(t)})}function E0(e){return e.protocol==="https:"?k0:S0}function py(e,t){let r=new URL(e),n=new URL(t||"/",r),o=new URL(r.href);if(o.pathname=T0(n.pathname),o.search=n.search,o.hash="",o.origin!==r.origin)throw I(Nr,"refusing to proxy to a different origin");return o}function T0(e){return(e.startsWith("/")?e:`/${e}`).replace(/^\/+/,"/")}function ei(e,t,r){e.statusCode=t,e.setHeader("content-type","text/plain; charset=utf-8"),e.setHeader("x-content-type-options","nosniff"),e.end(r)}function fy(e,t){let r=(t.headers.host??"").split(":")[0]??"",n=t.url??"/";return e.find((o)=>{let i=o.match.host===""||o.match.host===r,s=o.match.path===""||n.startsWith(o.match.path);return i&&s})}class Ou{host;port;secret;tokens;server;constructor(e,t,r,n){this.host=e;this.port=t;this.secret=r;this.tokens=n}listenPort(){let e=this.server?.address();if(typeof e==="object"&&e)return e.port;return this.port}start(){let e=this.host||"127.0.0.1";if(!Cn(e))return Promise.reject(I(Nr,`refusing to bind token endpoint to ${e}`));return new Promise((t,r)=>{this.server=my((n,o)=>{this.serve(n,o)}),this.server.on("error",(n)=>r(Pe(Nr,"token endpoint listen failed",n))),this.server.listen(this.port,e,()=>t())})}stop(){let e=this.server;if(!e)return Promise.resolve();return new Promise((t)=>e.close(()=>t()))}async serve(e,t){if(!A0(e.socket.remoteAddress)){ei(t,403,"forbidden");return}if((e.headers[P0]??"")!==this.secret){ei(t,401,"unauthorized");return}if(e.url?.startsWith("/token")!==!0){ei(t,404,"not found");return}let r=new URL(e.url,"http://127.0.0.1"),n=r.searchParams.get("identity")??"user",o=r.searchParams.get("audience")??"";try{let i=await this.tokens.get(n,o,[]);t.setHeader("content-type","application/json"),t.end(JSON.stringify({access_token:i.accessToken,token_type:i.tokenType,expires_at:i.expiresAt.toISOString(),identity:i.identity}))}catch{ei(t,500,"token error")}}}function A0(e){if(!e)return!0;return e==="127.0.0.1"||e==="::1"||e==="::ffff:127.0.0.1"}jr();var hy=1;class gy{tokenProviders=[];identityProviders=[];pluginIdentityProviders=[];environmentSources=[];healthChecks=[];logParsers=[];proxyMiddleware=[];loadErrors=[];pluginPaths=[];registerBuiltins(){this.tokenProviders.push(...Rn()),this.identityProviders.push(ou(),iu()),this.logParsers.push(jm()),this.proxyMiddleware.push(O0())}register(e){M0(e),fo(this.tokenProviders,e.tokenProviders),fo(this.identityProviders,e.identityProviders),fo(this.pluginIdentityProviders,e.identityProviders),fo(this.environmentSources,e.environmentSources),fo(this.healthChecks,e.healthChecks),fo(this.logParsers,e.logParsers),fo(this.proxyMiddleware,e.proxyMiddleware)}}async function wc(e,t=process.cwd()){let r=new gy;r.registerBuiltins();for(let n of e){if(n.trim()==="")continue;try{let o=n.startsWith("file:")?n:D0(n)?n:I0(t,n),s=await import(o.startsWith("file:")?o:L0(o).href);if(s.sdkVersion!==hy)throw Error(`plugin SDK version ${String(s.sdkVersion??"missing")} is incompatible; expected ${hy}`);r.register(s),r.pluginPaths.push(o)}catch(o){r.loadErrors.push({path:n,message:o instanceof Error?o.message:String(o)})}}return r}function M0(e){if(!e||typeof e!=="object")throw Error("plugin module must export an object");ti("tokenProviders",e.tokenProviders,["fetch"]),ti("identityProviders",e.identityProviders,["accepts","resolve"]),ti("environmentSources",e.environmentSources,["load"]),ti("healthChecks",e.healthChecks,["check"]),ti("logParsers",e.logParsers,["parse"]),ti("proxyMiddleware",e.proxyMiddleware,["apply"])}function ti(e,t,r){if(t===void 0)return;if(!Array.isArray(t))throw Error(`${e} must be an array`);for(let[n,o]of t.entries()){if(!o||typeof o!=="object"||typeof o.name!=="string"||o.name==="")throw Error(`${e}[${n}] must have a name`);for(let i of r)if(typeof o[i]!=="function")throw Error(`${e}[${n}].${i} must be a function`)}}function O0(){return{name:"identity_inject",apply:async(e)=>{await bc(e.route,e.headers,e.tokens)}}}function fo(e,t){if(!t)return;e.push(...t)}var B0=200;function $u(e){if(!e.registry)return;if(e.registry.tokenProviders.length>0)e.tokens.replaceProviders(e.registry.tokenProviders);e.logs.setParsers(e.registry.logParsers,e.registry.pluginPaths),e.proxy?.setMiddleware?.(e.registry.proxyMiddleware)}function Fu(e,t){let r=new Map;for(let n of e){let o=W0(n,t);try{r.set(o,$0(o).mtimeMs)}catch{}}return r}async function U0(e,t,r){let n=t.plugins.map((a)=>a.path),o=JSON.stringify(r)!==JSON.stringify(n),i=Fu(n,t.repoRoot),s=[];for(let[a,c]of i){let l=e.pluginMtimes.get(a);if(l!==void 0&&l!==c){s.push("plugins");break}}if(o){let a=await wc(n,t.repoRoot);e.registry=a;for(let c of a.loadErrors)e.log("devctl","ERROR",`plugin ${c.path} skipped: ${c.message}`);$u(e),e.log("devctl","INFO","plugin path list reloaded")}return e.pluginMtimes=i,s}function W0(e,t){if(e.startsWith("file:"))return e;return N0(e)?e:j0(t,e)}function Nu(e,t){let r=eu(t);if(r.length===0)return;let n=new Set((e?.healthChecks??[]).map((i)=>i.name.toLowerCase())),o=r.filter((i)=>!n.has(i.type.toLowerCase()));if(o.length>0)throw I(Q,`unknown health check type(s): ${o.map((i)=>`${i.service}.health.type=${i.type}`).join(", ")}`)}function Hu(e,t){let r=tu(t);if(r.length===0)return;let n=e?.pluginIdentityProviders??[],o=r.filter(({service:i})=>{let s=t.services[i];return!s||!n.some((a)=>a.accepts(s.identity))});if(o.length>0)throw I(Q,`unknown identity type(s): ${o.map((i)=>`${i.service}.identity.type=${i.type}`).join(", ")}`)}function ju(e,t){let r=new Set(Qi),n=new Set((e?.environmentSources??[]).map((i)=>i.name)),o=t.environment.sources.filter((i)=>!r.has(i)&&!n.has(i));if(o.length>0)throw I(Q,`unknown environment source(s): ${o.join(", ")}`)}function G0(e,t,r){for(let n of Object.keys(r))if(!t[n]&&!e.runtimes.has(n))e.runtimes.set(n,Ji(n));for(let n of Object.keys(t)){if(r[n])continue;if(e.orchestrator.serviceIsActive(n)){let o=e.runtimes.get(n);if(o)o.orphaned=!0;e.log(n,"WARN","removed from configuration while still running; now orphaned \u2014 stop it explicitly to clean it up");continue}e.forgetService(n)}}function Bu(e){let t=H0(e.cfg.repoRoot,".devctl");if(!e.fs.exists(t))return;try{e.configWatcher=F0(t,{recursive:!0},()=>{if(e.watchTimer)clearTimeout(e.watchTimer);e.watchTimer=setTimeout(()=>{e.reload().catch((r)=>e.log("devctl","WARN",A(r)))},B0)})}catch{e.log("devctl","WARN","unable to watch .devctl for configuration changes")}}async function vy(e){let t;try{t=nn(e.cfg.repoRoot,e.cfg.configPath)}catch(l){throw e.bus.publish(Se(_i,"",{error:A(l)})),e.log("devctl","ERROR",`configuration reload failed: ${A(l)}`),l}try{Nu(e.registry,t),Hu(e.registry,t),ju(e.registry,t)}catch(l){throw e.bus.publish(Se(_i,"",{error:A(l)})),e.log("devctl","ERROR",`configuration reload failed: ${A(l)}`),l}if(e.setupMode){if(e.setupMode=!1,e.log("devctl","INFO",`configuration created at ${e.cfg.configPath}; leaving setup mode`),!e.configWatcher)Bu(e)}let r=Du(e.cfg,t),n=JSON.stringify(e.cfg.proxy)!==JSON.stringify(t.proxy),o=JSON.stringify(e.cfg.secrets)!==JSON.stringify(t.secrets),i=e.cfg.plugins.map((l)=>l.path),s=e.cfg.services;e.cfg=ly(e.cfg,t),G0(e,s,t.services);let a=K0(e.restartRequired,r.restart_required,Object.keys(t.services));e.restartRequired=a,r.restart_required=a;let c=await U0(e,t,i);if(c.length>0)r.supervisor_restart_required=[...r.supervisor_restart_required??[],...c];if(e.syncServiceWatchers(),o)e.detector.update(t.secrets.extra_markers,t.secrets.extra_patterns),e.logs.setSecrets(t.secrets.extra_markers,t.secrets.extra_patterns);if(n){let l=e.proxy?.isRunning()??!1;if(await e.stopProxy(),l&&e.cfg.proxy.enabled)await e.startProxy();e.log("devctl","INFO","proxy configuration changed; proxy restarted")}if(e.bus.publish(Se(ca,"",{restart_required:r.restart_required,changes:r.changes,supervisor_restart_required:r.supervisor_restart_required})),e.log("devctl","INFO",r.restart_required.length===0?"configuration reloaded":`configuration reloaded; restart required: ${r.restart_required.join(", ")}`),r.supervisor_restart_required)e.log("devctl","WARN",oc(r.supervisor_restart_required));return e.persistState(),e.refreshIdentity(),r}function K0(e,t,r){let n=new Set(r),o=new Set(e.filter((i)=>n.has(i)));for(let i of t)if(n.has(i))o.add(i);return[...o].sort()}import{watch as V0}from"fs";import{join as xy,relative as z0}from"path";ht();function yy(e,t){if(!e?.enabled)return!1;let r=t.replaceAll("\\","/").replace(/^\.\//,"");if(r===""||r.includes(".."))return!1;if((e.ignore.length>0?e.ignore:Mi).some((o)=>q0(o,r)))return!1;if(e.paths.length===0)return!1;return e.paths.some((o)=>{let i=o.replaceAll("\\","/").replace(/\/$/,"");return r===i||r.startsWith(`${i}/`)})}function q0(e,t){let r=e.replace(/[.+^${}()|[\]\\]/g,"\\$&").replaceAll("**/","<<<DS>>>").replaceAll("**","<<<DS>>>").replaceAll("*","[^/]*").replaceAll("<<<DS>>>",".*");return new RegExp(`^${r}$`).test(t)}class Uu{host;entries=new Map;constructor(e){this.host=e}sync(e){let t=new Set;for(let[r,n]of Object.entries(e)){if(!n.watch.enabled||n.watch.paths.length===0){this.stop(r);continue}t.add(r);let o=JSON.stringify(n.watch);if(this.entries.get(r)?.signature===o)continue;this.stop(r),this.start(r,n.watch)}for(let r of[...this.entries.keys()])if(!t.has(r))this.stop(r)}watched(){return[...this.entries.keys()].sort()}close(){for(let e of[...this.entries.keys()])this.stop(e)}start(e,t){let r=[],n=this.host.repoRoot();for(let o of t.paths){let i=o.replaceAll("\\","/").replace(/\/$/,"");if(i===""||i==="."){this.host.log(e,"WARN","service.watch skipped a whole-repo path; list concrete directories");continue}let s=xy(n,i);try{let a=V0(s,{recursive:!0},(c,l)=>{let d=l?xy(s,l.toString()):s,u=z0(n,d).replaceAll("\\","/");if(!yy(t,u))return;this.schedule(e,t.debounce_ms)});r.push(a)}catch{this.host.log(e,"WARN",`unable to watch ${i}`)}}if(r.length===0)return;this.entries.set(e,{signature:JSON.stringify(t),watch:t,handles:r}),this.host.log(e,"INFO",`watching ${t.paths.join(", ")}`)}schedule(e,t){let r=this.entries.get(e);if(!r)return;if(r.timer)clearTimeout(r.timer);r.timer=setTimeout(()=>{if(r.timer=void 0,!this.host.isActive(e))return;this.host.log(e,"INFO","watch: restarting after file change"),this.host.restart(e).catch((n)=>{this.host.log(e,"WARN",n instanceof Error?n.message:String(n))})},t)}stop(e){let t=this.entries.get(e);if(!t)return;if(t.timer)clearTimeout(t.timer);for(let r of t.handles)r.close();this.entries.delete(e)}}import{isAbsolute as Wu,join as Gu}from"path";class Ku{clientEnv=new Map;serviceProfile=new Map;serviceProfileEnv=new Map;profile="";profileEnv={};deps;constructor(e){this.deps=e}forget(e){this.clientEnv.delete(e),this.serviceProfile.delete(e),this.serviceProfileEnv.delete(e)}serviceWorkDir(e){if(e.working_dir!==""&&!Wu(e.working_dir))return Gu(this.deps.cfg().repoRoot,e.working_dir);return e.working_dir}async resolveServiceExecution(e,t,r,n,o,i=!0){let s=this.deps.cfg(),a=this.deps.ports().get(e)??Object.fromEntries(t.ports.filter((p)=>!p.auto).map((p)=>[p.name,p.value])),c=this.deps.proxy(),l=c?.isRunning()?`http://${c.address()}`:s.proxy.enabled?`http://${xn(s.proxy.listen)}`:"",d=es(e,"127.0.0.1",a,l,s.project.name);if(!t.container){if(d.DEVCTL_INTERNAL_TOKEN=this.deps.internalTok(),s.proxy.token_endpoint.enabled)d.DEVCTL_TOKEN_URL=this.deps.boundTokenURL()||`http://127.0.0.1:${this.deps.tokenEndpoint()?.listenPort()||s.proxy.token_endpoint.port}/token`}let u=await Zi(s.repoRoot,{service:e,profile:r,serviceCfg:t,profileEnv:n,assignedPorts:a,runtime:d,cfg:s,fetchSecret:as(async()=>(await this.deps.tokens.get("user","",[])).accessToken),pluginSources:this.deps.environmentSources(),clientEnv:o,includeProcess:i}),f=t.working_dir&&!Wu(t.working_dir)?Gu(s.repoRoot,t.working_dir):t.working_dir;return{env:Qo(u),workDir:f}}async resolveTaskEnvironment(e,t,r){let n=this.deps.cfg(),o=await Zi(n.repoRoot,{service:`task:${e}`,profile:this.profile,serviceCfg:t,profileEnv:this.profileEnv,assignedPorts:{},runtime:es(`task:${e}`,"127.0.0.1",{},"",n.project.name),cfg:n,clientEnv:r,fetchSecret:as(async()=>(await this.deps.tokens.get("user","",[])).accessToken),pluginSources:this.deps.environmentSources()}),i=t.working_dir&&!Wu(t.working_dir)?Gu(n.repoRoot,t.working_dir):t.working_dir;return{env:Qo(o),workDir:i}}}jr();te();jr();Tt();import{cpus as oA,loadavg as iA,platform as sA,uptime as aA}from"os";import{readFileSync as Y0}from"fs";import{freemem as J0,totalmem as X0}from"os";import{spawnSync as Q0}from"child_process";var qu=1024,Z0=2000,eA=/page size of (\d+) bytes/i,ri;function tA(e){let t=e.match(/^MemAvailable:\s+(\d+)/m);if(!t)return;let r=Number(t[1]);return Number.isFinite(r)&&r>=0?r:void 0}function rA(e){let t=e.match(eA),r=t?Number(t[1]):0;if(!Number.isFinite(r)||r<=0)return;let n=Sc(e,"free")+Sc(e,"inactive")+Sc(e,"speculative")+Sc(e,"purgeable");return Math.round(n*r/qu)}function Sc(e,t){let r=e.match(new RegExp(`Pages ${t}:\\s+(\\d+)`,"i")),n=r?Number(r[1]):0;return Number.isFinite(n)&&n>0?n:0}function nA(){if(process.platform==="linux")try{return tA(Y0("/proc/meminfo","utf8"))}catch{return}if(process.platform==="darwin"){let e=Date.now();if(ri&&e-ri.at<Z0)return ri.leftoverKB;let t=Q0("vm_stat",{encoding:"utf8"});if(t.status!==0||!t.stdout)return ri?.leftoverKB;let r=rA(t.stdout);if(r===void 0)return ri?.leftoverKB;return ri={at:e,leftoverKB:r},r}return}function by(){let e=Math.max(0,Math.round(X0()/qu)),t=Math.max(0,Math.round(J0()/qu)),r=Math.max(0,Math.min(e,nA()??t));return{totalKB:e,unusedKB:t,leftoverKB:r}}function wy(e){return{user:"",project:e?.google.project_id??"",project_source:e?.google.project_id?"configuration":"",adc:!1,service_accounts:{},service_account_status:Object.fromEntries(e?Hr(e).map((t)=>[t,"unknown"]):[]),iap:e?.proxy.routes.some((t)=>t.auth.type.toLowerCase()==="iap")??!1}}function Vu(){let e=iA(),t=by();return{platform:sA(),cpuCount:oA().length,loadAvg1:e[0]??0,loadAvg5:e[1]??0,loadAvg15:e[2]??0,memTotalKB:t.totalKB,memFreeKB:t.unusedKB,memAvailableKB:t.leftoverKB,hostUptimeSec:aA()}}function zu(e,t){let r={},n={};for(let o of Hr(e)){let i=t.get(o)??"unknown";if(n[o]=i,i!=="unknown")r[o]=i==="available"}return{service_accounts:r,service_account_status:n}}function Sy(e){let t={};for(let[o,i]of e.runtimes)t[o]={...i,ports:e.ports.get(o)??i.ports,profile:e.serviceProfile.get(o)??i.profile,env_source:e.clientEnv.has(o)?"client":"daemon"};let r=e.proxy?.stats(),n={requestTotal:r?.total??0,requestErrors:r?.errors??0,recentRequests:r?.recent??[]};return{session_id:e.sessionID,repo_root:e.cfg.repoRoot,profile:e.profile,services:t,proxy:{running:e.proxy?.isRunning()??!1,address:e.proxy?.address(),routes:e.cfg.proxy.routes.map((o)=>{let i=o.match.host||"*",s=o.match.path,a=s===""?i:`${i}${s.startsWith("/")?s:`/${s}`}`;return{name:o.name,identity:o.auth.identity.service_account||o.auth.identity.type||o.auth.type,upstream:o.upstream.url,auth:o.auth.type,match:a,client_id:o.auth.client_id.trim()||void 0}}),...n},mcp:{running:e.mcp?.isRunning()??!1,address:e.mcp?.isRunning()?`http://${e.mcp.address()}/mcp`:void 0,port:e.mcp?.isRunning()?e.mcp.listenPort():void 0,token:e.mcpToken,disabled_tools:[...e.mcpDisabledTools]},identity:{...e.identityCache,...zu(e.cfg,e.serviceAccountStatus)},credentials:{backend:e.tokens.storeBackend(),entries:[...e.credentialEntries]},detached:e.detached,setup_mode:e.setupMode?!0:void 0,logs:e.logs.snapshot(),restart_required:[...e.restartRequired],system:Vu(),stats_series:e.statsSeries}}function ky(e){let t=[`PROFILE: ${e.profile||"(none)"}`,"","SERVICE\tSTATUS\tHEALTH\tPID"];for(let[r,n]of Object.entries(e.services))t.push(`${r} ${Jo(n)} ${n.health} ${n.pid}`);return t.push("",`PROXY ${e.proxy.running?"RUNNING":"STOPPED"} ${e.proxy.address??""}`),t.push(`MCP ${e.mcp?.running?"RUNNING":"STOPPED"} ${e.mcp?.address??""}`),t.join(`
662
2205
  `)+`
663
- `}var FT=4000;class ju{cache;accounts=new Map;entries=[];deps;constructor(e){this.deps=e,this.cache=av(e.cfg()),this.deps.bus.subscribe((t)=>{let r=t.payload??{},n=t.type===Lo?`token refreshed identity=${String(r.identity??"")}`:t.type===jn?`token refresh failed identity=${String(r.identity??"")} audience=${String(r.audience??"")}: ${String(r.error??"")}`:`authentication changed user=${String(r.user??"")}`;if(this.deps.logs.append({timestamp:this.deps.clock.isoNow(),service:"auth",source:"auth",level:t.type===jn?"WARN":"INFO",message:n,pid:0}),t.type===Lo||t.type===jn)this.syncCredentialEntries()},[Lo,jn,rd])}get identityCache(){return this.cache}get serviceAccountStatus(){return this.accounts}get credentialEntries(){return this.entries}async prepareServiceIdentity(e,t){let r=Fa(t.identity);if(r.kind!=="none")try{if(r=await hh(t.identity,()=>bh(this.deps.cfg().google.project_id),this.deps.identityProviders()),r.kind==="service_account")await this.deps.tokens.get(jo(r),"",[]),this.accounts.set(r.serviceAccount,"available")}catch(n){if(r.kind==="service_account")this.accounts.set(r.serviceAccount,"unavailable");if(HT(t)||r.kind==="service_account"||r.kind!=="user"&&r.kind!=="none")throw await this.deps.fail(e,n),n;this.deps.log(e,"WARN","cloud identity unavailable; starting service locally")}}async syncCredentialEntries(){this.entries=(await this.deps.tokens.listStatus()).map((e)=>({identity:e.identity,audience:e.audience,expires_at:e.expires_at,valid:e.valid}))}async refreshIdentity(e){try{let t=this.deps.cfg(),r=await this.deps.detectGoogle(t.google.project_id);if(e?.probeServiceAccounts)for(let i of $r(t))await this.probeServiceAccount(i);let{service_accounts:n,service_account_status:o}=Wu(t,this.accounts);this.cache={user:r.userEmail,project:r.projectID||t.google.project_id,project_source:r.projectSource||(t.google.project_id?"configuration":""),adc:r.adcAvailable,service_accounts:n,service_account_status:o,iap:t.proxy.routes.some((i)=>i.auth.type.toLowerCase()==="iap")},await this.syncCredentialEntries(),this.deps.bus.publish(Se(rd,"",{user:this.cache.user,adc:this.cache.adc})),this.deps.log("auth","INFO",`authentication changed user=${this.cache.user||"(unknown)"} adc=${this.cache.adc}`),this.deps.persistState()}catch(t){this.deps.log("devctl","WARN",`identity refresh failed: ${A(t)}`)}}async probeServiceAccount(e){let t;try{await NT(this.deps.tokens.refresh(`sa:${e}`,"",[]),FT),t="available"}catch{t="unavailable"}return this.accounts.set(e,t),t}}function NT(e,t){return new Promise((r,n)=>{let o=setTimeout(()=>n(Error("identity probe timed out")),t);e.then((i)=>{clearTimeout(o),r(i)},(i)=>{clearTimeout(o),n(i)})})}function HT(e){return e.capabilities.some((t)=>["google_api","iap","service_identity"].includes(t))}te();te();Uu();var UT=600,KT=65535;async function gv(e,t){let r=Number.isInteger(t)&&(t??0)>0?t:lo(e);for(let n=0;n<UT;n+=1){let o=r+n;if(o>KT)break;if(await Fo(o))return o}throw M(ee,`no free MCP port starting at ${r}`)}class Ku{listener;disabled=[];token;deps;constructor(e){this.deps=e,this.token=vm(e.repoRoot())}get instance(){return this.listener}get disabledTools(){return this.disabled}async bootFromPreferences(){let e=To(this.deps.repoRoot());if(this.setDisabledTools(e.mcp_disabled_tools??[]),e.mcp_enabled)await this.start(e.mcp_port).catch((t)=>this.deps.log("devctl","ERROR",A(t)))}async start(e){if(this.listener?.isRunning())return;let t=await gv(this.deps.repoRoot(),e);this.listener=this.deps.createListener({port:t,token:this.token,hostApi:this.deps.hostApi(),onEvent:(r,n)=>this.deps.log("mcp",r,`mcp ${n}`),disabledTools:()=>this.disabled}),await this.listener.start(),this.deps.persistState()}setDisabledTools(e){let t=e.filter((n)=>this.deps.isKnownTool(n)),r=this.disabled.join(",");if(this.disabled=[...new Set(t)].sort(),this.disabled.join(",")===r)return;this.deps.log("devctl","INFO",this.disabled.length===0?"all MCP tools enabled":`MCP tools disabled: ${this.disabled.join(", ")}`)}async stop(){await this.listener?.stop(),this.listener=void 0,this.deps.persistState()}}class qu{server;tokenEP;boundURL="";suppressed=!1;deps;constructor(e){this.deps=e}get instance(){return this.server}get tokenEndpoint(){return this.tokenEP}get boundTokenURL(){return this.boundURL}get isSuppressed(){return this.suppressed}setSuppressed(e){this.suppressed=e}async start(){if(this.server?.isRunning())return;this.server=new _u(this.deps.cfg().proxy,this.deps.tokens,this.deps.logs,this.deps.bus,this.deps.detector,this.deps.middleware()),await this.server.start();let e=this.deps.cfg();if(e.proxy.token_endpoint.enabled)this.tokenEP=new Eu(e.proxy.token_endpoint.host||"127.0.0.1",e.proxy.token_endpoint.port,this.deps.internalTok(),this.deps.tokens),await this.tokenEP.start(),this.boundURL=`http://127.0.0.1:${this.tokenEP.listenPort()}/token`;this.deps.persistState()}async stop(){await this.server?.stop(),await this.tokenEP?.stop(),this.deps.persistState()}}Tt();var qT=3000,hv=5000,yv=60;class Vu{timer;cpu=[];mem=[];lastAt=0;deps;constructor(e){this.deps=e}start(){if(this.timer)return;this.timer=setInterval(()=>void this.poll(),qT)}stop(){if(this.timer)clearInterval(this.timer),this.timer=void 0}series(){return{interval_ms:hv,cpu:[...this.cpu],mem:[...this.mem]}}async poll(){this.recordStatsSample();let e=[];for(let r of this.deps.runtimes().values())if(r.pid>0&&(r.state===Be||r.state===pr||r.state===Nr))e.push(r.pid);if(e.length===0)return;let t=await Wy(e);for(let r of this.deps.runtimes().values()){let n=r.pid>0?t.get(r.pid):void 0;if(n)r.cpuPercent=n.cpuPercent,r.memoryKB=n.memoryKB}}recordStatsSample(){let e=this.deps.clock.unixMs();if(e-this.lastAt<hv)return;this.lastAt=e;let t=Bu(),r=t.cpuCount>0?t.loadAvg1/t.cpuCount:0,n=t.memTotalKB>0?1-t.memAvailableKB/t.memTotalKB:0;if(this.cpu.push(r),this.mem.push(n),this.cpu.length>yv)this.cpu.shift();if(this.mem.length>yv)this.mem.shift()}}function Ju(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function _n(e){if(!Array.isArray(e))return[];return e.filter((t)=>typeof t==="string")}function os(e){if(!Ju(e))return;let t={};for(let[r,n]of Object.entries(e))if(typeof n==="string")t[r]=n;return t}te();import{createServer as VT}from"net";var JT=2000;class zu{server;deps;constructor(e){this.deps=e}get nativeServer(){return this.server}removeStaleSocket(e){if(process.platform==="win32")return;if(this.deps.socketExists(e))try{this.deps.unlinkSocket(e)}catch{this.deps.log("devctl","WARN","unable to remove stale socket")}}listen(e){return new Promise((t,r)=>{this.server=VT((n)=>{this.handleConn(n)}),this.server.on("error",r),this.server.listen(e,()=>t())})}close(){this.server?.close(),this.server=void 0}handleConn(e){let t="",r=[],n=!1,o=()=>{while(r.length>0){let a=r[0],c=e.write(`${JSON.stringify(a)}
664
- `);if(r.shift(),!c){n=!0,e.once("drain",()=>{n=!1,o()});return}}},i=(a)=>{if(a.id===void 0&&a.event!==void 0&&r.length>=JT){let l=r.findIndex((d)=>d.id===void 0&&d.event!==void 0);if(l>=0)r.splice(l,1)}if(r.push(a),!n)o()},s=this.deps.subscribe((a)=>i({event:a}));e.on("error",(a)=>{this.deps.log("devctl","WARN",`client connection error: ${A(a)}`)}),e.on("data",(a)=>{t+=a.toString("utf8");let c=t.split(`
665
- `);t=c.pop()??"";for(let l of c){if(l.trim()==="")continue;this.dispatchLine(l,i)}}),e.on("close",()=>s())}async dispatchLine(e,t){let r;try{r=JSON.parse(e)}catch{t({error:"invalid json"});return}try{let n=await this.deps.dispatch(r.method??"",r.params);t({id:r.id,result:n})}catch(n){let o=tg(n);t({id:r.id,error:o.error,kind:o.kind,hint:o.hint,service:o.service})}}}te();Fr();Tt();class Yu{cfg;sessionID;internalTok;bus;logs;procs;tokens;detector;env;proxy;mcp;resources;runtimes=new Map;ports=new Map;lock;shuttingDown=!1;detached=!1;identity;rpc;detectGoogleFn;inspectProcessFn;processAliveFn;acquireLockFn;socketExistsFn;unlinkSocketFn;registry;restartRequired=[];processMeta=new Map;configWatcher;watchTimer;setupMode;serviceWatchers;pluginMtimes=new Map;orchestrator;commands;clock;fs;healthCheckers;constructor(e,t){this.healthCheckers=t.healthCheckers,this.cfg=e,this.sessionID=t.sessionID,this.internalTok=Ql(),this.clock=t.clock,this.fs=t.fs,this.bus=t.bus,this.detectGoogleFn=t.detectGoogle,this.inspectProcessFn=t.inspectProcess,this.processAliveFn=t.processAlive,this.acquireLockFn=t.acquireLock,this.socketExistsFn=t.socketExists,this.unlinkSocketFn=t.unlinkSocket,this.detector=t.detector,this.logs=t.logs,this.procs=t.procs,this.orchestrator=t.orchestrator,this.tokens=t.tokens,this.proxy=new qu({cfg:()=>this.cfg,tokens:this.tokens,logs:this.logs,bus:this.bus,detector:this.detector,internalTok:()=>this.internalTok,middleware:()=>this.registry?.proxyMiddleware??[],persistState:()=>this.persistState()}),this.env=new Nu({cfg:()=>this.cfg,ports:()=>this.ports,proxy:()=>this.proxy.instance,tokenEndpoint:()=>this.proxy.tokenEndpoint,boundTokenURL:()=>this.proxy.boundTokenURL,internalTok:()=>this.internalTok,tokens:this.tokens,environmentSources:()=>this.registry?.environmentSources}),this.mcp=new Ku({repoRoot:()=>this.cfg.repoRoot,createListener:t.createMcpListener,hostApi:()=>this.asMcpHost(),isKnownTool:t.isKnownTool,log:(r,n,o)=>this.log(r,n,o),persistState:()=>this.persistState()}),this.resources=new Vu({clock:this.clock,runtimes:()=>this.runtimes}),this.identity=new ju({cfg:()=>this.cfg,tokens:this.tokens,detectGoogle:(r)=>this.detectGoogleFn(r),clock:this.clock,bus:this.bus,logs:this.logs,persistState:()=>this.persistState(),fail:(r,n)=>this.fail(r,n),log:(r,n,o)=>this.log(r,n,o),identityProviders:()=>this.registry?.identityProviders}),this.rpc=new zu({dispatch:(r,n)=>this.dispatch(r,n),subscribe:(r,n)=>this.bus.subscribe(r,n),log:(r,n,o)=>this.log(r,n,o),socketExists:(r)=>this.socketExistsFn(r),unlinkSocket:(r)=>this.unlinkSocketFn(r)}),this.setupMode=!this.fs.exists(e.configPath);for(let r of Object.keys(e.services))this.runtimes.set(r,Ki(r));this.orchestrator.bind(this.lifecycleSession()),this.commands=t.createCommands(this),this.serviceWatchers=new Iu({repoRoot:()=>this.cfg.repoRoot,log:(r,n,o)=>this.log(r,n,o),isActive:(r)=>this.orchestrator.serviceIsActive(r),restart:(r)=>this.restart([r],{auto:!0})})}get profile(){return this.env.profile}set profile(e){this.env.profile=e}get profileEnv(){return this.env.profileEnv}set profileEnv(e){this.env.profileEnv=e}get clientEnv(){return this.env.clientEnv}get serviceProfile(){return this.env.serviceProfile}get serviceProfileEnv(){return this.env.serviceProfileEnv}snapshotHost(){let e=this;return{get sessionID(){return e.sessionID},get cfg(){return e.cfg},get profile(){return e.profile},get runtimes(){return e.runtimes},get ports(){return e.ports},get serviceProfile(){return e.serviceProfile},get clientEnv(){return e.clientEnv},get proxy(){return e.proxy.instance},get mcp(){return e.mcp.instance},get mcpToken(){return e.mcp.token},get mcpDisabledTools(){return e.mcp.disabledTools},get identityCache(){return e.identity.identityCache},get serviceAccountStatus(){return e.identity.serviceAccountStatus},get credentialEntries(){return e.identity.credentialEntries},get detached(){return e.detached},get setupMode(){return e.setupMode},get restartRequired(){return e.restartRequired},get statsSeries(){return e.resources.series()},logs:{snapshot:()=>e.logs.snapshot()},tokens:{storeBackend:()=>e.tokens.storeBackend()}}}reloadHost(){let e=this;return{get cfg(){return e.cfg},set cfg(t){e.cfg=t},get setupMode(){return e.setupMode},set setupMode(t){e.setupMode=t},get configWatcher(){return e.configWatcher},set configWatcher(t){e.configWatcher=t},get watchTimer(){return e.watchTimer},set watchTimer(t){e.watchTimer=t},get restartRequired(){return e.restartRequired},set restartRequired(t){e.restartRequired=t},get fs(){return e.fs},get registry(){return e.registry},set registry(t){e.registry=t},get pluginMtimes(){return e.pluginMtimes},set pluginMtimes(t){e.pluginMtimes=t},get detector(){return e.detector},get bus(){return e.bus},get orchestrator(){return e.orchestrator},get runtimes(){return e.runtimes},get tokens(){return e.tokens},get logs(){return e.logs},get proxy(){return e.proxy.instance},persistState:()=>e.persistState(),log:(t,r,n)=>e.log(t,r,n),refreshIdentity:()=>e.refreshIdentity(),startProxy:()=>e.startProxy(),stopProxy:()=>e.stopProxy(),reload:()=>e.reload(),forgetService:(t)=>e.forgetService(t),syncServiceWatchers:()=>e.serviceWatchers.sync(e.cfg.services)}}recoverHost(){let e=this;return{get cfg(){return e.cfg},set cfg(t){e.cfg=t},get profile(){return e.profile},set profile(t){e.profile=t},get profileEnv(){return e.profileEnv},set profileEnv(t){e.profileEnv=t},get runtimes(){return e.runtimes},get ports(){return e.ports},get processMeta(){return e.processMeta},get serviceProfile(){return e.serviceProfile},get serviceProfileEnv(){return e.serviceProfileEnv},get orchestrator(){return e.orchestrator},get procs(){return e.procs},logs:{append:(t)=>e.logs.append(t)},get clock(){return e.clock},get tokens(){return e.tokens},get registry(){return e.registry},get proxy(){return e.proxy.instance},get tokenEP(){return e.proxy.tokenEndpoint},get boundTokenURL(){return e.proxy.boundTokenURL},get internalTok(){return e.internalTok},get bus(){return e.bus},inspectProcessFn:(t)=>e.inspectProcessFn(t),processAliveFn:(t)=>e.processAliveFn(t),serviceWorkDir:(t)=>e.serviceWorkDir(t),persistState:()=>e.persistState(),setState:(t,r,n,o,i)=>e.setState(t,r,n,o,i),log:(t,r,n)=>e.log(t,r,n)}}async run(){let e=Vs(this.cfg.repoRoot);this.lock=this.acquireLockFn(this.cfg.repoRoot,e),this.rpc.removeStaleSocket(e),this.registry=await gc(this.cfg.plugins.map((t)=>t.path),this.cfg.repoRoot),this.pluginMtimes=Au(this.cfg.plugins.map((t)=>t.path),this.cfg.repoRoot);for(let t of this.registry.loadErrors)this.log("devctl","ERROR",`plugin ${t.path} skipped: ${t.message}`);Tu(this.reloadHost()),Lu(this.registry,this.cfg),Du(this.registry,this.cfg),Mu(this.registry,this.cfg),await this.recoverSession(),this.serviceWatchers.sync(this.cfg.services),Ou(this.reloadHost()),this.persistState(),this.log("devctl","INFO",`supervisor started session=${this.sessionID}`),this.refreshIdentity(),this.resources.start(),await this.mcp.bootFromPreferences(),this.rpc.removeStaleSocket(e),await this.rpc.listen(e)}async dispatch(e,t){let r=Ju(t)?t:{};switch(e){case"ping":return{session:this.sessionID,version:Bt,protocol:ji};case"start":return this.commands.startService.execute({services:_n(r.services),profile:typeof r.profile==="string"&&r.profile!==""?r.profile:void 0,detach:r.detach===!0,client_env:os(r.client_env)});case"stop":return await this.commands.stopService.execute(_n(r.services)),null;case"restart":return await this.commands.restartService.execute(_n(r.services),{cascade:r.cascade===!0,clientEnv:os(r.client_env)}),null;case"run_task":return this.runTask(typeof r.name==="string"?r.name:"",os(r.client_env)??{});case"exec":return this.execService(typeof r.service==="string"?r.service:"",_n(r.command),os(r.client_env)??{},r.print_env===!0);case"auth_refresh":return await this.commands.refreshIdentity.execute(),this.snapshot().identity;case"status":return this.snapshot();case"logs":return await this.queryLogs({services:_n(r.services),level:typeof r.level==="string"?r.level:"",search:typeof r.search==="string"?r.search:"",regex:r.regex===!0,source:typeof r.source==="string"?r.source:"",since:typeof r.since==="string"?r.since:"",until:typeof r.until==="string"?r.until:"",export:typeof r.export==="string"?r.export:""});case"logs_page":return await this.queryLogsPage({services:_n(r.services),level:typeof r.level==="string"?r.level:"",search:typeof r.search==="string"?r.search:"",regex:r.regex===!0,source:typeof r.source==="string"?r.source:"",since:typeof r.since==="string"?r.since:"",until:typeof r.until==="string"?r.until:"",cursor:typeof r.cursor==="string"?r.cursor:void 0,direction:r.direction==="forward"?"forward":"backward",limit:typeof r.limit==="number"?r.limit:void 0});case"logs_stats":return await this.queryLogsFacets({services:_n(r.services),level:typeof r.level==="string"?r.level:"",search:typeof r.search==="string"?r.search:"",regex:r.regex===!0,source:typeof r.source==="string"?r.source:"",since:typeof r.since==="string"?r.since:"",until:typeof r.until==="string"?r.until:""});case"proxy_start":return this.proxy.setSuppressed(!1),await this.commands.startProxy.execute(),null;case"proxy_stop":return this.proxy.setSuppressed(!0),await this.commands.stopProxy.execute(),null;case"mcp_start":{let o=typeof r.port==="number"?r.port:void 0;return await this.startMcp(o??To(this.cfg.repoRoot).mcp_port),null}case"mcp_stop":return await this.stopMcp(),null;case"mcp_set_tools":{let o=Array.isArray(r.disabled)?r.disabled.filter((i)=>typeof i==="string"):[];return this.setMcpDisabledTools(o),{disabled_tools:[...this.mcp.disabledTools]}}case"reload":return this.commands.reloadConfig.execute();case"config_snapshot":return this.cfg;case"auth_invalidate":return this.tokens.invalidate(),null;case"shutdown":let n=typeof r.stop_services==="boolean"?r.stop_services:wa(this.cfg.shutdown);return setTimeout(()=>{this.shutdown(n)},50),null;default:throw M(ct,`unknown method ${e}`)}}isDetached(){return this.detached}async start(e){return this.orchestrator.start(e)}lifecycleSession(){let e=this;return{get cfg(){return e.cfg},get profile(){return e.profile},set profile(t){e.profile=t},get profileEnv(){return e.profileEnv},set profileEnv(t){e.profileEnv=t},get detached(){return e.detached},set detached(t){e.detached=t},get proxySuppressed(){return e.proxy.isSuppressed},get runtimes(){return e.runtimes},get ports(){return e.ports},get clientEnv(){return e.clientEnv},get serviceProfile(){return e.serviceProfile},get serviceProfileEnv(){return e.serviceProfileEnv},healthCheckers:{lookup:(t)=>{let r=e.registry?.healthChecks.find((n)=>n.name.toLowerCase()===t.toLowerCase());return r?{check:async(n,o)=>{let i=await r.check(n,o);return{...i,status:i.status}}}:e.healthCheckers.lookup(t)}},logs:{append:(t)=>e.logs.append(t)},bus:e.bus,processMeta:e.processMeta,get containerPrefix(){return`devctl-${ar(e.cfg.repoRoot)}-`},prepareServiceIdentity:(t,r)=>e.identity.prepareServiceIdentity(t,r),resolveServiceExecution:(t,r,n,o,i,s)=>e.env.resolveServiceExecution(t,r,n,o,i,s),detectGoogle:(t)=>e.detectGoogleFn(t),startProxy:()=>e.startProxy(),fail:(t,r)=>e.fail(t,r),claimIfAlreadyUp:(t)=>e.claimIfAlreadyUp(t),assignPendingPorts:(t)=>e.assignPendingPorts(t),setState:(t,r,n,o,i)=>e.setState(t,r,n,o,i),persistState:()=>e.persistState(),log:(t,r,n)=>e.log(t,r,n),releasePorts:(t)=>e.releasePorts(t),forgetService:(t)=>e.forgetService(t),clearRestartRequired:(t)=>e.dropRestartRequired(t)}}async assignPendingPorts(e){try{let t=await og(this.cfg,e,Object.fromEntries(this.ports));for(let[r,n]of Object.entries(t)){this.ports.set(r,n);let o=Object.entries(n).map(([i,s])=>`${i}=${s}`).join(", ");this.log(r,"INFO",`assigned ports: ${o||"none"}`)}}catch(t){if(t instanceof Qe&&t.service!=="")await this.fail(t.service,t);else this.log("devctl","ERROR",A(t));throw t}}async runTask(e,t){let r=this.cfg.tasks[e];if(!r)throw M(ct,`unknown task ${e}`);if(r.dependencies.length>0)await this.start({services:r.dependencies,client_env:t});let n={...gn(),command:r.command,shell:r.shell,working_dir:r.working_dir,dependencies:r.dependencies,environment:r.environment},{env:o,workDir:i}=await this.env.resolveTaskEnvironment(e,n,t),s=await this.runTransient(`task:${e}`,r.command,r.shell,i,o);return{task:e,...s}}async execService(e,t,r,n=!1){let o=this.cfg.services[e];if(!o)throw M(Io,`unknown service ${e}`);let i=this.env.serviceProfile.get(e)??this.env.profile,s=i!==""?so(this.cfg,i):this.env.profileEnv,{env:a,workDir:c}=await this.env.resolveServiceExecution(e,o,i,s,r,!o.container);if(n)return{service:e,code:0,stdout:"",stderr:"",environment:a};if(t.length===0)throw M(ct,"exec command is required");let l=await this.runTransient(`${e}:exec`,{args:t,shell:!1},!1,c,a);return{service:e,...l}}async runTransient(e,t,r,n,o){if(hn(t))return{code:0,stdout:"",stderr:""};this.log(e,"INFO",`running ${t.args.join(" ")}`);let i=await this.procs.runOnce({name:e,args:[...t.args],shell:r||t.shell,workDir:n,env:o,graceMs:vn(this.cfg.shutdown)*1000,onLine:(s,a)=>this.logs.append({timestamp:this.clock.isoNow(),service:e,source:s,stream:s,level:"",message:a,pid:0})});if(i.code!==0)throw M(He,`${e} exited with code ${i.code}`);return i}serviceWorkDir(e){return this.env.serviceWorkDir(e)}async claimIfAlreadyUp(e){return Vy(this.recoverHost(),e)}async recoverSession(){return Jy(this.recoverHost())}async stop(e){return this.orchestrator.stop(e)}forgetService(e){this.orchestrator.health.forget(e),this.runtimes.delete(e),this.ports.delete(e),this.processMeta.delete(e),this.env.forget(e)}async restart(e,t){let r=t?.cascade===!0?io(this.cfg,e):e;await this.orchestrator.restart(e,t),this.dropRestartRequired(r)}dropRestartRequired(e){if(e.length===0)return;let t=new Set(e);this.restartRequired=this.restartRequired.filter((r)=>!t.has(r))}async startProxy(){return this.proxy.start()}async stopProxy(){return this.proxy.stop()}async startMcp(e){return this.mcp.start(e)}setMcpDisabledTools(e){this.mcp.setDisabledTools(e)}async stopMcp(){return this.mcp.stop()}asMcpHost(){return{status:()=>this.commands.getServiceStatus.execute(),logsPage:(e)=>this.queryLogsPage(e),config:()=>this.cfg,validateConfigText:(e)=>Fi(this.cfg.repoRoot,this.cfg.configPath,e),start:(e)=>this.commands.startService.execute(e),stop:(e)=>this.commands.stopService.execute(e),restart:(e,t)=>this.commands.restartService.execute(e,{cascade:t}),reload:()=>this.commands.reloadConfig.execute(),doctor:async()=>{for(let e of $r(this.cfg))await this.identity.probeServiceAccount(e);return this.commands.runDoctor.execute(this.cfg)},exec:(e,t,r)=>this.execService(e,t,void 0,r),runTask:(e)=>this.runTask(e,{}),startProxy:()=>this.startProxy(),stopProxy:()=>this.stopProxy()}}async reload(){return nv(this.reloadHost())}async shutdown(e){if(this.shuttingDown)return;if(this.shuttingDown=!0,this.persistState(),e)await this.stop([]);if(this.orchestrator.health.dispose(),await this.stopProxy(),await this.stopMcp(),this.configWatcher?.close(),this.serviceWatchers.close(),this.watchTimer)clearTimeout(this.watchTimer);this.resources.stop(),this.rpc.close(),this.lock?.release(),await this.logs.close()}snapshot(){return cv(this.snapshotHost())}async refreshIdentity(e){return this.identity.refreshIdentity(e)}async queryLogs(e){let t=await this.logs.query({services:e.services,level:e.level,search:e.search,regex:e.regex,source:e.source,since:e.since,until:e.until});if(e.export)await this.logs.exportTo(e.export,{services:e.services,level:e.level,search:e.search,regex:e.regex,source:e.source,since:e.since,until:e.until});return{events:t}}async queryLogsPage(e){return this.logs.queryPage({services:e.services,level:e.level,search:e.search,regex:e.regex,source:e.source,since:e.since,until:e.until},{cursor:e.cursor,direction:e.direction,limit:e.limit})}async queryLogsFacets(e){return this.logs.queryFacets({services:e.services,level:e.level,search:e.search,regex:e.regex,source:e.source,since:e.since,until:e.until})}subscribe(e){return this.bus.subscribe(e)}formatStatus(){return lv(this.snapshot())}setState(e,t,r,n,o){let i=this.runtimes.get(e)??Ki(e);if(!ts(i.state,t))this.log(e,"WARN",`unusual lifecycle ${i.state} \u2192 ${t}`);i.state=t,i.health=r,i.pid=n,i.last_error=o;let s=t===Be||t===pr||t===Nr;if(i.startTime=s?this.processMeta.get(e)?.startTime.toISOString():void 0,!s)i.cpuPercent=void 0,i.memoryKB=void 0;this.runtimes.set(e,i),this.bus.publish(Se(Cm,e,{state:t,health:r,pid:n}))}async releasePorts(e){let t=this.ports.get(e);if(!t)return;let r=this.cfg.services[e];if(r?.container){this.ports.delete(e);return}let n=this.processMeta.get(e);for(let o of Object.values(t)){let i=await Vn(o);if(!i||i.pid===process.pid)continue;if(r){let s=await this.inspectProcessFn(i.pid);if(!(s!==void 0&&s.command!==""&&Zi({args:n?.command??[...r.command.args],workDir:n?.cwd??this.serviceWorkDir(r),startTime:n?.startTime},s))){this.log(e,"WARN",`port ${o} is held by pid ${i.pid}, which does not match ${e}; leaving it running`);continue}}try{await fa(i),this.log(e,"INFO",`released port ${o} (pid ${i.pid})`)}catch(s){this.log(e,"WARN",A(s))}}this.ports.delete(e)}async fail(e,t){this.orchestrator.health.clearHealthWatch(e),this.orchestrator.health.clearRestartTimer(e),this.orchestrator.health.bumpGeneration(e),this.setState(e,lt,Et,0,A(t));try{await this.procs.stop(e,vn(this.cfg.shutdown)*1000)}catch(r){this.log(e,"WARN",A(r))}this.bus.publish(Se(ra,e,{error:A(t)})),this.log(e,"ERROR",A(t)),this.persistState()}log(e,t,r){this.logs.append({timestamp:this.clock.isoNow(),service:e,source:"devctl",level:t,message:r,pid:0})}persistState(){let e=[];for(let t of this.procs.all()){let r=this.processMeta.get(t.name);e.push({name:t.name,pid:t.pid,command:r?.command??t.args,cwd:r?.cwd??t.workDir,startTime:(r?.startTime??t.startTime).toISOString(),ports:this.ports.get(t.name)??{},profile:this.serviceProfile.get(t.name)??this.profile})}hm(this.cfg.repoRoot,{session_id:this.sessionID,repo_root:this.cfg.repoRoot,profile:this.profile,processes:e})}}var xv=500,zT=1e4,YT=2000,XT=new URL("./log-worker.ts",import.meta.url);class bv{worker;bus;pending=new Map;nextId=1;stats={total:0,errors:0,counts:{}};dead=!1;readySettled=!1;ready;resolveReady=()=>{return};rejectReady=()=>{return};constructor(e,t,r={}){this.bus=t,this.ready=new Promise((n,o)=>{this.resolveReady=n,this.rejectReady=o}),this.worker=new Worker(r.script??XT,{name:"devctl-logs"}),this.worker.addEventListener("message",(n)=>{this.onMessage(n.data)}),this.worker.addEventListener("error",(n)=>{this.markDead(Error(n.message||"log worker failed"))}),this.post({type:"init",config:e})}waitUntilReady(e=xv){if(this.dead)return Promise.reject(Error("log worker is not running"));if(this.readySettled)return Promise.resolve();return new Promise((t,r)=>{let n=setTimeout(()=>{this.markDead(Error("log worker init timed out"))},e);this.ready.then(()=>{clearTimeout(n),t()},(o)=>{clearTimeout(n),r(o)})})}append(e){if(this.dead)return;try{this.post({type:"append",event:e})}catch{}}async query(e){let t=await this.rpc({type:"query",filter:e});if(!Array.isArray(t))throw Error("log worker query returned a non-array");return t}async queryPage(e,t){let r=await this.rpc({type:"queryPage",filter:e,page:t});if(!QT(r))throw Error("log worker queryPage returned an unexpected payload");return r}async queryFacets(e){let t=await this.rpc({type:"queryFacets",filter:e});if(!ZT(t))throw Error("log worker queryFacets returned an unexpected payload");return t}snapshot(){return this.stats}async exportTo(e,t){await this.rpc({type:"exportTo",path:e,filter:t})}setParsers(e,t){if(this.dead)return;this.post({type:"setPluginPaths",paths:[...t??[]]})}setSecrets(e,t){if(this.dead)return;this.post({type:"setSecrets",extraMarkers:e,extraPatterns:t})}async close(){if(this.dead)return;try{await this.rpc({type:"close"},YT)}catch{}finally{this.markDead(Error("log worker closed"))}}rpc(e,t=zT){this.assertAlive();let r=this.nextId;this.nextId+=1;let n={...e,id:r};return new Promise((o,i)=>{let s=setTimeout(()=>{this.markDead(Error(`log worker timed out (${e.type})`))},t);this.pending.set(r,{resolve:o,reject:i,timer:s});try{this.post(n)}catch(a){clearTimeout(s),this.pending.delete(r),i(a instanceof Error?a:Error(String(a)))}})}post(e){this.assertAlive(),this.worker.postMessage(e)}onMessage(e){if(e.type==="ready"){this.settleReady();return}if(e.type==="appended"){this.stats=e.stats,this.bus?.publish(Se(Ao,e.event.service,{event:e.event,level:e.event.level}));return}let t=this.pending.get(e.id);if(!t)return;if(this.pending.delete(e.id),clearTimeout(t.timer),e.type==="error"){t.reject(Error(e.error));return}t.resolve(e.result)}settleReady(){if(this.readySettled||this.dead)return;this.readySettled=!0,this.resolveReady()}assertAlive(){if(this.dead)throw Error("log worker is not running")}markDead(e){if(this.dead)return;if(this.dead=!0,!this.readySettled)this.readySettled=!0,this.rejectReady(e);this.rejectAll(e);try{this.worker.terminate()}catch{}}rejectAll(e){for(let t of this.pending.values())clearTimeout(t.timer),t.reject(e);this.pending.clear()}}function QT(e){if(e===null||Array.isArray(e)||!("events"in e))return!1;return Array.isArray(e.events)}function ZT(e){return e!==null&&!Array.isArray(e)&&"byService"in e}function vv(e,t,r){return qm(new bd(e.max,t,r,e.persist,e.directory,e.sessionID,e.retentionDays,e.maxSessionLogs))}async function wv(e,t,r,n={}){if(n.standalone??Bun.isStandaloneExecutable===!0)return{logs:vv(e,t,r),usingWorker:!1};try{let i=new bv(e,t,{script:n.script});return await i.waitUntilReady(n.initTimeoutMs??xv),{logs:i,usingWorker:!0}}catch{return{logs:vv(e,t,r),usingWorker:!1}}}ti();import{createServer as dA}from"http";te();ss();var uA="2025-03-26",Fv="2.0",Mv=-32700,Ov=-32600,pA=-32601,Iv=-32602,fA=-32603;function mA(e){return e!==""&&kn(e)}class Zu{server;running=!1;addr="";boundPort=0;opts;constructor(e){this.opts=e}disabled(){return this.opts.disabledTools?.()??[]}address(){return this.addr}listenPort(){return this.boundPort}isRunning(){return this.running}emit(e,t){this.opts.onEvent?.(e,t)}start(){if(!mA(this.opts.host))return Promise.reject(M(ct,`refusing to bind MCP to ${this.opts.host}`));return new Promise((e,t)=>{this.server=dA((r,n)=>{this.serve(r,n)}),this.server.on("error",(r)=>t(Re(ct,`unable to listen on ${this.opts.host}:${this.opts.port}`,r))),this.server.listen(this.opts.port,this.opts.host,()=>{let r=this.server?.address();this.boundPort=typeof r==="object"&&r?r.port:this.opts.port,this.addr=`${this.opts.host}:${this.boundPort}`,this.running=!0,this.emit("INFO",`listening on ${this.addr}`),e()})})}stop(){let e=this.server,t=this.running;if(!e)return this.running=!1,Promise.resolve();return new Promise((r)=>{e.close(()=>{if(this.running=!1,this.addr="",this.boundPort=0,this.server=void 0,t)this.emit("INFO","stopped");r()})})}async serve(e,t){if(e.method==="OPTIONS"){t.writeHead(204,ls()),t.end();return}if(!this.authorized(e)){this.emit("WARN",`rejected unauthorized request from ${$v(e)}`),ri(t,401,{error:"unauthorized"});return}if(hA(e)!=="/mcp"){ri(t,404,{error:"not found"});return}if(e.method==="GET"||e.method==="DELETE"){t.writeHead(405,{Allow:"POST, OPTIONS",...ls()}),t.end();return}if(e.method!=="POST"){t.writeHead(405,{Allow:"POST, OPTIONS",...ls()}),t.end();return}let n;try{n=await yA(e)}catch(a){ri(t,400,as(null,Mv,a instanceof Error?a.message:"invalid body"));return}let o;try{o=JSON.parse(n)}catch{ri(t,400,as(null,Mv,"parse error"));return}let i=$v(e);if(Array.isArray(o)){let a=[];for(let c of o){let l=await this.dispatch(c,i);if(l!==void 0)a.push(l)}ri(t,200,a);return}let s=await this.dispatch(o,i);if(s===void 0){t.writeHead(202,ls()),t.end();return}ri(t,200,s)}authorized(e){if(this.opts.token==="")return!0;return(e.headers.authorization??"")===`Bearer ${this.opts.token}`}async dispatch(e,t){if(!wc(e))return as(null,Ov,"invalid request");let r=e,n=r.id??null,o=r.id===void 0;if(r.method===void 0||r.method==="")return o?void 0:as(n,Ov,"missing method");try{let i=await this.handleMethod(r.method,wc(r.params)?r.params:{},t);if(o)return;return{jsonrpc:Fv,id:n,result:i}}catch(i){if(o)return;let s=i instanceof cs?i.code:fA,a=i instanceof Error?i.message:"internal error";return this.emit("WARN",`rpc error method=${r.method} addr=${t}: ${a}`),as(n,s,a)}}async handleMethod(e,t,r){switch(e){case"initialize":{let n=gA(t);return this.emit("INFO",`client connected addr=${r}${n?` (${n})`:""}`),{protocolVersion:uA,capabilities:{tools:{},resources:{}},serverInfo:{name:"devctl",version:Bt}}}case"notifications/initialized":case"ping":return{};case"tools/list":return{tools:Rv(this.disabled()).map((n)=>({name:n.name,description:n.description,inputSchema:n.inputSchema}))};case"tools/call":return this.callTool(t);case"resources/list":return{resources:Xu.map((n)=>({uri:n,name:n.replace("devctl://",""),mimeType:"application/json"}))};case"resources/read":return this.readResource(t);default:throw new cs(pA,`method not found: ${e}`)}}async callTool(e){let t=typeof e.name==="string"?e.name:"";if(t==="")throw new cs(Iv,"tool name is required");if(bc(t)&&!En(t,this.disabled())){let o=`tool ${t} is disabled in devctl's MCP settings`;return this.emit("WARN",`tool call rejected name=${t}: disabled`),{content:[{type:"text",text:o}],isError:!0}}let r=wc(e.arguments)?e.arguments:{},n=Date.now();try{let o=await Av(this.opts.hostApi,t,r);return this.emit("INFO",`tool call name=${t} duration=${Date.now()-n}ms`),{content:[{type:"text",text:JSON.stringify(o,null,2)}]}}catch(o){let i=o instanceof Error?o.message:"tool failed";return this.emit("WARN",`tool call failed name=${t} duration=${Date.now()-n}ms: ${i}`),{content:[{type:"text",text:i}],isError:!0}}}async readResource(e){let t=typeof e.uri==="string"?e.uri:"";if(!Lv(t))throw new cs(Iv,`unknown resource ${t}`);let r=await Dv(this.opts.hostApi,t);return this.emit("INFO",`resource read uri=${t}`),{contents:[{uri:t,mimeType:"application/json",text:JSON.stringify(r,null,2)}]}}}function gA(e){let t=e.clientInfo;if(!wc(t))return"";let r=typeof t.name==="string"?t.name:"",n=typeof t.version==="string"?t.version:"";if(r==="")return"";return n===""?r:`${r} ${n}`}function $v(e){return e.socket.remoteAddress??"unknown"}class cs extends Error{code;constructor(e,t){super(t);this.code=e}}function ls(){return{"Access-Control-Allow-Origin":"*","Access-Control-Allow-Headers":"Authorization, Content-Type, Accept, MCP-Protocol-Version, Mcp-Session-Id","Access-Control-Allow-Methods":"POST, OPTIONS"}}function ri(e,t,r){let n=JSON.stringify(r);e.writeHead(t,{"Content-Type":"application/json","Content-Length":Buffer.byteLength(n),...ls()}),e.end(n)}function as(e,t,r){return{jsonrpc:Fv,id:e,error:{code:t,message:r}}}function hA(e){let t=e.url??"/",r=t.indexOf("?");return r>=0?t.slice(0,r):t}function yA(e){return new Promise((t,r)=>{let n=[];e.on("data",(o)=>{n.push(o)}),e.on("end",()=>{t(Buffer.concat(n).toString("utf8"))}),e.on("error",r)})}function wc(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}ss();var bA=(e)=>new Zu({host:"127.0.0.1",...e});async function wA(e,t={}){let r=t.clock??Ga,n=t.fs??jy,o=t.processes??new xu,i=t.bus??new od(2048),s=t.tokens??new oo(e.auth.refresh_threshold_seconds*1000,Rn(),i,void 0,r),a=new Cu(o,r),c=zl(),l=new mr(e.secrets.extra_markers,e.secrets.extra_patterns),d=Bun.isStandaloneExecutable===!0,{logs:u,usingWorker:f}=await wv({max:e.logs.max_memory_events,persist:e.logs.persistence.enabled,directory:e.logs.persistence.directory,sessionID:c,retentionDays:e.logs.persistence.retention_days,maxSessionLogs:e.logs.persistence.max_session_logs,extraMarkers:e.secrets.extra_markers,extraPatterns:e.secrets.extra_patterns},i,l,{standalone:d});if(!f&&!d)u.append({timestamp:r.isoNow(),service:"devctl",source:"devctl",level:"WARN",message:"log worker failed to start; using in-process log store",pid:0});return{supervisor:new Yu(e,{healthCheckers:t.healthCheckers??by([]),detectGoogle:t.detectGoogle??ro,tokens:s,inspectProcess:By,processAlive:Qt,acquireLock:ym,socketExists:vA,unlinkSocket:xA,procs:o,orchestrator:a,clock:r,fs:n,bus:i,logs:u,detector:l,sessionID:c,createMcpListener:t.createMcpListener??bA,isKnownTool:bc,createCommands:(m)=>zh(m,Ja(Vo({tokens:s})),a)}),orchestrator:a,clock:r,fs:n}}async function Nv(e,t){let r=Zn(e,t),{supervisor:n}=await wA(r),o=!1,i=()=>{if(o)return;o=!0,n.shutdown(wa(r.shutdown)).finally(()=>process.exit(0))};process.on("SIGINT",i),process.on("SIGTERM",i),await n.run()}mt();te();Lf();import{Command as ZO}from"commander";var Rk=["zsh","bash","fish"];function Pk(e){if(e==="zsh")return`#compdef devctl
2206
+ `}var cA=4000;class Yu{cache;accounts=new Map;entries=[];deps;constructor(e){this.deps=e,this.cache=wy(e.cfg()),this.deps.bus.subscribe((t)=>{let r=t.payload??{},n=t.type===Oo?`token refreshed identity=${String(r.identity??"")}`:t.type===Gn?`token refresh failed identity=${String(r.identity??"")} audience=${String(r.audience??"")}: ${String(r.error??"")}`:`authentication changed user=${String(r.user??"")}`;if(this.deps.logs.append({timestamp:this.deps.clock.isoNow(),service:"auth",source:"auth",level:t.type===Gn?"WARN":"INFO",message:n,pid:0}),t.type===Oo||t.type===Gn)this.syncCredentialEntries()},[Oo,Gn,cd])}get identityCache(){return this.cache}get serviceAccountStatus(){return this.accounts}get credentialEntries(){return this.entries}async prepareServiceIdentity(e,t){let r=Ga(t.identity);if(r.kind!=="none")try{if(r=await Tg(t.identity,()=>Ig(this.deps.cfg().google.project_id),this.deps.identityProviders()),r.kind==="service_account")await this.deps.tokens.get(Ko(r),"",[]),this.accounts.set(r.serviceAccount,"available")}catch(n){if(r.kind==="service_account")this.accounts.set(r.serviceAccount,"unavailable");if(dA(t)||r.kind==="service_account"||r.kind!=="user"&&r.kind!=="none")throw await this.deps.fail(e,n),n;this.deps.log(e,"WARN","cloud identity unavailable; starting service locally")}}async syncCredentialEntries(){this.entries=(await this.deps.tokens.listStatus()).map((e)=>({identity:e.identity,audience:e.audience,expires_at:e.expires_at,valid:e.valid}))}async refreshIdentity(e){try{let t=this.deps.cfg(),r=await this.deps.detectGoogle(t.google.project_id);if(e?.probeServiceAccounts)for(let i of Hr(t))await this.probeServiceAccount(i);let{service_accounts:n,service_account_status:o}=zu(t,this.accounts);this.cache={user:r.userEmail,project:r.projectID||t.google.project_id,project_source:r.projectSource||(t.google.project_id?"configuration":""),adc:r.adcAvailable,service_accounts:n,service_account_status:o,iap:t.proxy.routes.some((i)=>i.auth.type.toLowerCase()==="iap")},await this.syncCredentialEntries(),this.deps.bus.publish(Se(cd,"",{user:this.cache.user,adc:this.cache.adc})),this.deps.log("auth","INFO",`authentication changed user=${this.cache.user||"(unknown)"} adc=${this.cache.adc}`),this.deps.persistState()}catch(t){this.deps.log("devctl","WARN",`identity refresh failed: ${A(t)}`)}}async probeServiceAccount(e){let t;try{await lA(this.deps.tokens.refresh(`sa:${e}`,"",[]),cA),t="available"}catch{t="unavailable"}return this.accounts.set(e,t),t}}function lA(e,t){return new Promise((r,n)=>{let o=setTimeout(()=>n(Error("identity probe timed out")),t);e.then((i)=>{clearTimeout(o),r(i)},(i)=>{clearTimeout(o),n(i)})})}function dA(e){return e.capabilities.some((t)=>["google_api","iap","service_identity"].includes(t))}te();te();Xu();var hA=600,gA=65535;async function Ty(e,t){let r=Number.isInteger(t)&&(t??0)>0?t:mo(e);for(let n=0;n<hA;n+=1){let o=r+n;if(o>gA)break;if(await jo(o))return o}throw I(Q,`no free MCP port starting at ${r}`)}class Qu{listener;disabled=[];token;deps;constructor(e){this.deps=e,this.token=Rm(e.repoRoot())}get instance(){return this.listener}get disabledTools(){return this.disabled}async bootFromPreferences(){let e=Io(this.deps.repoRoot());if(this.setDisabledTools(e.mcp_disabled_tools??[]),e.mcp_enabled)await this.start(e.mcp_port).catch((t)=>this.deps.log("devctl","ERROR",A(t)))}async start(e){if(this.listener?.isRunning())return;let t=await Ty(this.deps.repoRoot(),e);this.listener=this.deps.createListener({port:t,token:this.token,hostApi:this.deps.hostApi(),onEvent:(r,n)=>this.deps.log("mcp",r,`mcp ${n}`),disabledTools:()=>this.disabled}),await this.listener.start(),this.deps.persistState()}setDisabledTools(e){let t=e.filter((n)=>this.deps.isKnownTool(n)),r=this.disabled.join(",");if(this.disabled=[...new Set(t)].sort(),this.disabled.join(",")===r)return;this.deps.log("devctl","INFO",this.disabled.length===0?"all MCP tools enabled":`MCP tools disabled: ${this.disabled.join(", ")}`)}async stop(){await this.listener?.stop(),this.listener=void 0,this.deps.persistState()}}class Zu{server;tokenEP;boundURL="";suppressed=!1;deps;constructor(e){this.deps=e}get instance(){return this.server}get tokenEndpoint(){return this.tokenEP}get boundTokenURL(){return this.boundURL}get isSuppressed(){return this.suppressed}setSuppressed(e){this.suppressed=e}async start(){if(this.server?.isRunning())return;this.server=new Mu(this.deps.cfg().proxy,this.deps.tokens,this.deps.logs,this.deps.bus,this.deps.detector,this.deps.middleware(),(t,r)=>this.deps.ports().get(t)?.[r||"http"]),await this.server.start();let e=this.deps.cfg();if(e.proxy.token_endpoint.enabled)this.tokenEP=new Ou(e.proxy.token_endpoint.host||"127.0.0.1",e.proxy.token_endpoint.port,this.deps.internalTok(),this.deps.tokens),await this.tokenEP.start(),this.boundURL=`http://127.0.0.1:${this.tokenEP.listenPort()}/token`;this.deps.persistState()}async stop(){await this.server?.stop(),await this.tokenEP?.stop(),this.deps.persistState()}}Tt();var vA=3000,Ay=5000,Ly=60;class ep{timer;cpu=[];mem=[];lastAt=0;deps;constructor(e){this.deps=e}start(){if(this.timer)return;this.timer=setInterval(()=>void this.poll(),vA)}stop(){if(this.timer)clearInterval(this.timer),this.timer=void 0}series(){return{interval_ms:Ay,cpu:[...this.cpu],mem:[...this.mem]}}async poll(){this.recordStatsSample();let e=[];for(let r of this.deps.runtimes().values())if(r.pid>0&&(r.state===je||r.state===pr||r.state===Br))e.push(r.pid);if(e.length===0)return;let t=await ty(e);for(let r of this.deps.runtimes().values()){let n=r.pid>0?t.get(r.pid):void 0;if(n)r.cpuPercent=n.cpuPercent,r.memoryKB=n.memoryKB}}recordStatsSample(){let e=this.deps.clock.unixMs();if(e-this.lastAt<Ay)return;this.lastAt=e;let t=Vu(),r=t.cpuCount>0?t.loadAvg1/t.cpuCount:0,n=t.memTotalKB>0?1-t.memAvailableKB/t.memTotalKB:0;if(this.cpu.push(r),this.mem.push(n),this.cpu.length>Ly)this.cpu.shift();if(this.mem.length>Ly)this.mem.shift()}}function tp(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function En(e){if(!Array.isArray(e))return[];return e.filter((t)=>typeof t==="string")}function ls(e){if(!tp(e))return;let t={};for(let[r,n]of Object.entries(e))if(typeof n==="string")t[r]=n;return t}te();import{createServer as yA}from"net";var xA=2000;class rp{server;deps;constructor(e){this.deps=e}get nativeServer(){return this.server}removeStaleSocket(e){if(process.platform==="win32")return;if(this.deps.socketExists(e))try{this.deps.unlinkSocket(e)}catch{this.deps.log("devctl","WARN","unable to remove stale socket")}}listen(e){return new Promise((t,r)=>{this.server=yA((n)=>{this.handleConn(n)}),this.server.on("error",r),this.server.listen(e,()=>t())})}close(){this.server?.close(),this.server=void 0}handleConn(e){let t="",r=[],n=!1,o=()=>{while(r.length>0){let a=r[0],c=e.write(`${JSON.stringify(a)}
2207
+ `);if(r.shift(),!c){n=!0,e.once("drain",()=>{n=!1,o()});return}}},i=(a)=>{if(a.id===void 0&&a.event!==void 0&&r.length>=xA){let l=r.findIndex((d)=>d.id===void 0&&d.event!==void 0);if(l>=0)r.splice(l,1)}if(r.push(a),!n)o()},s=this.deps.subscribe((a)=>i({event:a}));e.on("error",(a)=>{this.deps.log("devctl","WARN",`client connection error: ${A(a)}`)}),e.on("data",(a)=>{t+=a.toString("utf8");let c=t.split(`
2208
+ `);t=c.pop()??"";for(let l of c){if(l.trim()==="")continue;this.dispatchLine(l,i)}}),e.on("close",()=>s())}async dispatchLine(e,t){let r;try{r=JSON.parse(e)}catch{t({error:"invalid json"});return}try{let n=await this.deps.dispatch(r.method??"",r.params);t({id:r.id,result:n})}catch(n){let o=lh(n);t({id:r.id,error:o.error,kind:o.kind,hint:o.hint,service:o.service})}}}te();jr();Tt();class np{cfg;sessionID;internalTok;bus;logs;procs;tokens;detector;env;proxy;mcp;resources;runtimes=new Map;ports=new Map;lock;shuttingDown=!1;detached=!1;identity;rpc;detectGoogleFn;inspectProcessFn;processAliveFn;acquireLockFn;socketExistsFn;unlinkSocketFn;registry;restartRequired=[];processMeta=new Map;configWatcher;watchTimer;setupMode;serviceWatchers;pluginMtimes=new Map;orchestrator;commands;clock;fs;healthCheckers;constructor(e,t){this.healthCheckers=t.healthCheckers,this.cfg=e,this.sessionID=t.sessionID,this.internalTok=od(),this.clock=t.clock,this.fs=t.fs,this.bus=t.bus,this.detectGoogleFn=t.detectGoogle,this.inspectProcessFn=t.inspectProcess,this.processAliveFn=t.processAlive,this.acquireLockFn=t.acquireLock,this.socketExistsFn=t.socketExists,this.unlinkSocketFn=t.unlinkSocket,this.detector=t.detector,this.logs=t.logs,this.procs=t.procs,this.orchestrator=t.orchestrator,this.tokens=t.tokens,this.proxy=new Zu({cfg:()=>this.cfg,ports:()=>this.ports,tokens:this.tokens,logs:this.logs,bus:this.bus,detector:this.detector,internalTok:()=>this.internalTok,middleware:()=>this.registry?.proxyMiddleware??[],persistState:()=>this.persistState()}),this.env=new Ku({cfg:()=>this.cfg,ports:()=>this.ports,proxy:()=>this.proxy.instance,tokenEndpoint:()=>this.proxy.tokenEndpoint,boundTokenURL:()=>this.proxy.boundTokenURL,internalTok:()=>this.internalTok,tokens:this.tokens,environmentSources:()=>this.registry?.environmentSources}),this.mcp=new Qu({repoRoot:()=>this.cfg.repoRoot,createListener:t.createMcpListener,hostApi:()=>this.asMcpHost(),isKnownTool:t.isKnownTool,log:(r,n,o)=>this.log(r,n,o),persistState:()=>this.persistState()}),this.resources=new ep({clock:this.clock,runtimes:()=>this.runtimes}),this.identity=new Yu({cfg:()=>this.cfg,tokens:this.tokens,detectGoogle:(r)=>this.detectGoogleFn(r),clock:this.clock,bus:this.bus,logs:this.logs,persistState:()=>this.persistState(),fail:(r,n)=>this.fail(r,n),log:(r,n,o)=>this.log(r,n,o),identityProviders:()=>this.registry?.identityProviders}),this.rpc=new rp({dispatch:(r,n)=>this.dispatch(r,n),subscribe:(r,n)=>this.bus.subscribe(r,n),log:(r,n,o)=>this.log(r,n,o),socketExists:(r)=>this.socketExistsFn(r),unlinkSocket:(r)=>this.unlinkSocketFn(r)}),this.setupMode=!this.fs.exists(e.configPath);for(let r of Object.keys(e.services))this.runtimes.set(r,Ji(r));this.orchestrator.bind(this.lifecycleSession()),this.commands=t.createCommands(this),this.serviceWatchers=new Uu({repoRoot:()=>this.cfg.repoRoot,log:(r,n,o)=>this.log(r,n,o),isActive:(r)=>this.orchestrator.serviceIsActive(r),restart:(r)=>this.restart([r],{auto:!0})})}get profile(){return this.env.profile}set profile(e){this.env.profile=e}get profileEnv(){return this.env.profileEnv}set profileEnv(e){this.env.profileEnv=e}get clientEnv(){return this.env.clientEnv}get serviceProfile(){return this.env.serviceProfile}get serviceProfileEnv(){return this.env.serviceProfileEnv}snapshotHost(){let e=this;return{get sessionID(){return e.sessionID},get cfg(){return e.cfg},get profile(){return e.profile},get runtimes(){return e.runtimes},get ports(){return e.ports},get serviceProfile(){return e.serviceProfile},get clientEnv(){return e.clientEnv},get proxy(){return e.proxy.instance},get mcp(){return e.mcp.instance},get mcpToken(){return e.mcp.token},get mcpDisabledTools(){return e.mcp.disabledTools},get identityCache(){return e.identity.identityCache},get serviceAccountStatus(){return e.identity.serviceAccountStatus},get credentialEntries(){return e.identity.credentialEntries},get detached(){return e.detached},get setupMode(){return e.setupMode},get restartRequired(){return e.restartRequired},get statsSeries(){return e.resources.series()},logs:{snapshot:()=>e.logs.snapshot()},tokens:{storeBackend:()=>e.tokens.storeBackend()}}}reloadHost(){let e=this;return{get cfg(){return e.cfg},set cfg(t){e.cfg=t},get setupMode(){return e.setupMode},set setupMode(t){e.setupMode=t},get configWatcher(){return e.configWatcher},set configWatcher(t){e.configWatcher=t},get watchTimer(){return e.watchTimer},set watchTimer(t){e.watchTimer=t},get restartRequired(){return e.restartRequired},set restartRequired(t){e.restartRequired=t},get fs(){return e.fs},get registry(){return e.registry},set registry(t){e.registry=t},get pluginMtimes(){return e.pluginMtimes},set pluginMtimes(t){e.pluginMtimes=t},get detector(){return e.detector},get bus(){return e.bus},get orchestrator(){return e.orchestrator},get runtimes(){return e.runtimes},get tokens(){return e.tokens},get logs(){return e.logs},get proxy(){return e.proxy.instance},persistState:()=>e.persistState(),log:(t,r,n)=>e.log(t,r,n),refreshIdentity:()=>e.refreshIdentity(),startProxy:()=>e.startProxy(),stopProxy:()=>e.stopProxy(),reload:()=>e.reload(),forgetService:(t)=>e.forgetService(t),syncServiceWatchers:()=>e.serviceWatchers.sync(e.cfg.services)}}recoverHost(){let e=this;return{get cfg(){return e.cfg},set cfg(t){e.cfg=t},get profile(){return e.profile},set profile(t){e.profile=t},get profileEnv(){return e.profileEnv},set profileEnv(t){e.profileEnv=t},get runtimes(){return e.runtimes},get ports(){return e.ports},get processMeta(){return e.processMeta},get serviceProfile(){return e.serviceProfile},get serviceProfileEnv(){return e.serviceProfileEnv},get orchestrator(){return e.orchestrator},get procs(){return e.procs},logs:{append:(t)=>e.logs.append(t)},get clock(){return e.clock},get tokens(){return e.tokens},get registry(){return e.registry},get proxy(){return e.proxy.instance},get tokenEP(){return e.proxy.tokenEndpoint},get boundTokenURL(){return e.proxy.boundTokenURL},get internalTok(){return e.internalTok},get bus(){return e.bus},inspectProcessFn:(t)=>e.inspectProcessFn(t),processAliveFn:(t)=>e.processAliveFn(t),serviceWorkDir:(t)=>e.serviceWorkDir(t),persistState:()=>e.persistState(),setState:(t,r,n,o,i)=>e.setState(t,r,n,o,i),log:(t,r,n)=>e.log(t,r,n)}}async run(){let e=Qs(this.cfg.repoRoot);this.lock=this.acquireLockFn(this.cfg.repoRoot,e),this.rpc.removeStaleSocket(e),this.registry=await wc(this.cfg.plugins.map((t)=>t.path),this.cfg.repoRoot),this.pluginMtimes=Fu(this.cfg.plugins.map((t)=>t.path),this.cfg.repoRoot);for(let t of this.registry.loadErrors)this.log("devctl","ERROR",`plugin ${t.path} skipped: ${t.message}`);$u(this.reloadHost()),Nu(this.registry,this.cfg),Hu(this.registry,this.cfg),ju(this.registry,this.cfg),await this.recoverSession(),this.serviceWatchers.sync(this.cfg.services),Bu(this.reloadHost()),this.persistState(),this.log("devctl","INFO",`supervisor started session=${this.sessionID}`),this.refreshIdentity(),this.resources.start(),await this.mcp.bootFromPreferences(),this.rpc.removeStaleSocket(e),await this.rpc.listen(e)}async dispatch(e,t){let r=tp(t)?t:{};switch(e){case"ping":return{session:this.sessionID,version:jt,protocol:Vi};case"start":return this.commands.startService.execute({services:En(r.services),profile:typeof r.profile==="string"&&r.profile!==""?r.profile:void 0,detach:r.detach===!0,client_env:ls(r.client_env)});case"stop":return await this.commands.stopService.execute(En(r.services)),null;case"restart":return await this.commands.restartService.execute(En(r.services),{cascade:r.cascade===!0,clientEnv:ls(r.client_env)}),null;case"run_task":return this.runTask(typeof r.name==="string"?r.name:"",ls(r.client_env)??{});case"exec":return this.execService(typeof r.service==="string"?r.service:"",En(r.command),ls(r.client_env)??{},r.print_env===!0);case"auth_refresh":return await this.commands.refreshIdentity.execute(),this.snapshot().identity;case"status":return this.snapshot();case"logs":return await this.queryLogs({services:En(r.services),level:typeof r.level==="string"?r.level:"",search:typeof r.search==="string"?r.search:"",regex:r.regex===!0,source:typeof r.source==="string"?r.source:"",since:typeof r.since==="string"?r.since:"",until:typeof r.until==="string"?r.until:"",export:typeof r.export==="string"?r.export:""});case"logs_page":return await this.queryLogsPage({services:En(r.services),level:typeof r.level==="string"?r.level:"",search:typeof r.search==="string"?r.search:"",regex:r.regex===!0,source:typeof r.source==="string"?r.source:"",since:typeof r.since==="string"?r.since:"",until:typeof r.until==="string"?r.until:"",cursor:typeof r.cursor==="string"?r.cursor:void 0,direction:r.direction==="forward"?"forward":"backward",limit:typeof r.limit==="number"?r.limit:void 0});case"logs_stats":return await this.queryLogsFacets({services:En(r.services),level:typeof r.level==="string"?r.level:"",search:typeof r.search==="string"?r.search:"",regex:r.regex===!0,source:typeof r.source==="string"?r.source:"",since:typeof r.since==="string"?r.since:"",until:typeof r.until==="string"?r.until:""});case"proxy_start":return this.proxy.setSuppressed(!1),await this.commands.startProxy.execute(),null;case"proxy_stop":return this.proxy.setSuppressed(!0),await this.commands.stopProxy.execute(),null;case"mcp_start":{let o=typeof r.port==="number"?r.port:void 0;return await this.startMcp(o??Io(this.cfg.repoRoot).mcp_port),null}case"mcp_stop":return await this.stopMcp(),null;case"mcp_set_tools":{let o=Array.isArray(r.disabled)?r.disabled.filter((i)=>typeof i==="string"):[];return this.setMcpDisabledTools(o),{disabled_tools:[...this.mcp.disabledTools]}}case"reload":return this.commands.reloadConfig.execute();case"config_snapshot":return this.cfg;case"auth_invalidate":return this.tokens.invalidate(),null;case"shutdown":let n=typeof r.stop_services==="boolean"?r.stop_services:_a(this.cfg.shutdown);return setTimeout(()=>{this.shutdown(n)},50),null;default:throw I(lt,`unknown method ${e}`)}}isDetached(){return this.detached}async start(e){return this.orchestrator.start(e)}lifecycleSession(){let e=this;return{get cfg(){return e.cfg},get profile(){return e.profile},set profile(t){e.profile=t},get profileEnv(){return e.profileEnv},set profileEnv(t){e.profileEnv=t},get detached(){return e.detached},set detached(t){e.detached=t},get proxySuppressed(){return e.proxy.isSuppressed},get runtimes(){return e.runtimes},get ports(){return e.ports},get clientEnv(){return e.clientEnv},get serviceProfile(){return e.serviceProfile},get serviceProfileEnv(){return e.serviceProfileEnv},healthCheckers:{lookup:(t)=>{let r=e.registry?.healthChecks.find((n)=>n.name.toLowerCase()===t.toLowerCase());return r?{check:async(n,o)=>{let i=await r.check(n,o);return{...i,status:i.status}}}:e.healthCheckers.lookup(t)}},logs:{append:(t)=>e.logs.append(t)},bus:e.bus,processMeta:e.processMeta,get containerPrefix(){return`devctl-${ar(e.cfg.repoRoot)}-`},prepareServiceIdentity:(t,r)=>e.identity.prepareServiceIdentity(t,r),resolveServiceExecution:(t,r,n,o,i,s)=>e.env.resolveServiceExecution(t,r,n,o,i,s),detectGoogle:(t)=>e.detectGoogleFn(t),startProxy:()=>e.startProxy(),fail:(t,r)=>e.fail(t,r),claimIfAlreadyUp:(t)=>e.claimIfAlreadyUp(t),assignPendingPorts:(t)=>e.assignPendingPorts(t),setState:(t,r,n,o,i)=>e.setState(t,r,n,o,i),persistState:()=>e.persistState(),log:(t,r,n)=>e.log(t,r,n),releasePorts:(t)=>e.releasePorts(t),forgetService:(t)=>e.forgetService(t),clearRestartRequired:(t)=>e.dropRestartRequired(t)}}async assignPendingPorts(e){try{let t=await ph(this.cfg,e,Object.fromEntries(this.ports));for(let[r,n]of Object.entries(t)){this.ports.set(r,n);let o=Object.entries(n).map(([i,s])=>`${i}=${s}`).join(", ");this.log(r,"INFO",`assigned ports: ${o||"none"}`)}}catch(t){if(t instanceof Ve&&t.service!=="")await this.fail(t.service,t);else this.log("devctl","ERROR",A(t));throw t}}async runTask(e,t){let r=this.cfg.tasks[e];if(!r)throw I(lt,`unknown task ${e}`);if(r.dependencies.length>0)await this.start({services:r.dependencies,client_env:t});let n={...vn(),command:r.command,shell:r.shell,working_dir:r.working_dir,dependencies:r.dependencies,environment:r.environment},{env:o,workDir:i}=await this.env.resolveTaskEnvironment(e,n,t),s=await this.runTransient(`task:${e}`,r.command,r.shell,i,o);return{task:e,...s}}async execService(e,t,r,n=!1){let o=this.cfg.services[e];if(!o)throw I(Ho,`unknown service ${e}`);let i=this.env.serviceProfile.get(e)??this.env.profile,s=i!==""?uo(this.cfg,i):this.env.profileEnv,{env:a,workDir:c}=await this.env.resolveServiceExecution(e,o,i,s,r,!o.container);if(n)return{service:e,code:0,stdout:"",stderr:"",environment:a};if(t.length===0)throw I(lt,"exec command is required");let l=await this.runTransient(`${e}:exec`,{args:t,shell:!1},!1,c,a);return{service:e,...l}}async runTransient(e,t,r,n,o){if(yn(t))return{code:0,stdout:"",stderr:""};this.log(e,"INFO",`running ${t.args.join(" ")}`);let i=await this.procs.runOnce({name:e,args:[...t.args],shell:r||t.shell,workDir:n,env:o,graceMs:bn(this.cfg.shutdown)*1000,onLine:(s,a)=>this.logs.append({timestamp:this.clock.isoNow(),service:e,source:s,stream:s,level:"",message:a,pid:0})});if(i.code!==0)throw I(He,`${e} exited with code ${i.code}`);return i}serviceWorkDir(e){return this.env.serviceWorkDir(e)}async claimIfAlreadyUp(e){return ay(this.recoverHost(),e)}async recoverSession(){return cy(this.recoverHost())}async stop(e){return this.orchestrator.stop(e)}forgetService(e){this.orchestrator.health.forget(e),this.runtimes.delete(e),this.ports.delete(e),this.processMeta.delete(e),this.env.forget(e)}async restart(e,t){let r=t?.cascade===!0?lo(this.cfg,e):e;await this.orchestrator.restart(e,t),this.dropRestartRequired(r)}dropRestartRequired(e){if(e.length===0)return;let t=new Set(e);this.restartRequired=this.restartRequired.filter((r)=>!t.has(r))}async startProxy(){return this.proxy.start()}async stopProxy(){return this.proxy.stop()}async startMcp(e){return this.mcp.start(e)}setMcpDisabledTools(e){this.mcp.setDisabledTools(e)}async stopMcp(){return this.mcp.stop()}asMcpHost(){return{status:()=>this.commands.getServiceStatus.execute(),logsPage:(e)=>this.queryLogsPage(e),config:()=>this.cfg,validateConfigText:(e)=>ji(this.cfg.repoRoot,this.cfg.configPath,e),start:(e)=>this.commands.startService.execute(e),stop:(e)=>this.commands.stopService.execute(e),restart:(e,t)=>this.commands.restartService.execute(e,{cascade:t}),reload:()=>this.commands.reloadConfig.execute(),doctor:async()=>{for(let e of Hr(this.cfg))await this.identity.probeServiceAccount(e);return this.commands.runDoctor.execute(this.cfg)},exec:(e,t,r)=>this.execService(e,t,void 0,r),runTask:(e)=>this.runTask(e,{}),startProxy:()=>this.startProxy(),stopProxy:()=>this.stopProxy()}}async reload(){return vy(this.reloadHost())}async shutdown(e){if(this.shuttingDown)return;if(this.shuttingDown=!0,this.persistState(),e)await this.stop([]);if(this.orchestrator.health.dispose(),await this.stopProxy(),await this.stopMcp(),this.configWatcher?.close(),this.serviceWatchers.close(),this.watchTimer)clearTimeout(this.watchTimer);this.resources.stop(),this.rpc.close(),this.lock?.release(),await this.logs.close()}snapshot(){return Sy(this.snapshotHost())}async refreshIdentity(e){return this.identity.refreshIdentity(e)}async queryLogs(e){let t=await this.logs.query({services:e.services,level:e.level,search:e.search,regex:e.regex,source:e.source,since:e.since,until:e.until});if(e.export)await this.logs.exportTo(e.export,{services:e.services,level:e.level,search:e.search,regex:e.regex,source:e.source,since:e.since,until:e.until});return{events:t}}async queryLogsPage(e){return this.logs.queryPage({services:e.services,level:e.level,search:e.search,regex:e.regex,source:e.source,since:e.since,until:e.until},{cursor:e.cursor,direction:e.direction,limit:e.limit})}async queryLogsFacets(e){return this.logs.queryFacets({services:e.services,level:e.level,search:e.search,regex:e.regex,source:e.source,since:e.since,until:e.until})}subscribe(e){return this.bus.subscribe(e)}formatStatus(){return ky(this.snapshot())}setState(e,t,r,n,o){let i=this.runtimes.get(e)??Ji(e);if(!ss(i.state,t))this.log(e,"WARN",`unusual lifecycle ${i.state} \u2192 ${t}`);i.state=t,i.health=r,i.pid=n,i.last_error=o;let s=t===je||t===pr||t===Br;if(i.startTime=s?this.processMeta.get(e)?.startTime.toISOString():void 0,!s)i.cpuPercent=void 0,i.memoryKB=void 0;this.runtimes.set(e,i),this.bus.publish(Se(Dm,e,{state:t,health:r,pid:n}))}async releasePorts(e){let t=this.ports.get(e);if(!t)return;let r=this.cfg.services[e];if(r?.container){this.ports.delete(e);return}let n=this.processMeta.get(e);for(let o of Object.values(t)){let i=await Yn(o);if(!i||i.pid===process.pid)continue;if(r){let s=await this.inspectProcessFn(i.pid);if(!(s!==void 0&&s.command!==""&&os({args:n?.command??[...r.command.args],workDir:n?.cwd??this.serviceWorkDir(r),startTime:n?.startTime},s))){this.log(e,"WARN",`port ${o} is held by pid ${i.pid}, which does not match ${e}; leaving it running`);continue}}try{await xa(i),this.log(e,"INFO",`released port ${o} (pid ${i.pid})`)}catch(s){this.log(e,"WARN",A(s))}}this.ports.delete(e)}async fail(e,t){this.orchestrator.health.clearHealthWatch(e),this.orchestrator.health.clearRestartTimer(e),this.orchestrator.health.bumpGeneration(e),this.setState(e,dt,Et,0,A(t));try{await this.procs.stop(e,bn(this.cfg.shutdown)*1000)}catch(r){this.log(e,"WARN",A(r))}this.bus.publish(Se(aa,e,{error:A(t)})),this.log(e,"ERROR",A(t)),this.persistState()}log(e,t,r){this.logs.append({timestamp:this.clock.isoNow(),service:e,source:"devctl",level:t,message:r,pid:0})}persistState(){let e=[];for(let t of this.procs.all()){let r=this.processMeta.get(t.name);e.push({name:t.name,pid:t.pid,command:r?.command??t.args,cwd:r?.cwd??t.workDir,startTime:(r?.startTime??t.startTime).toISOString(),ports:this.ports.get(t.name)??{},profile:this.serviceProfile.get(t.name)??this.profile})}Cm(this.cfg.repoRoot,{session_id:this.sessionID,repo_root:this.cfg.repoRoot,profile:this.profile,processes:e})}}var Iy=500,bA=1e4,wA=2000,SA=new URL("./log-worker.ts",import.meta.url);class My{worker;bus;pending=new Map;nextId=1;stats={total:0,errors:0,counts:{}};dead=!1;readySettled=!1;ready;resolveReady=()=>{return};rejectReady=()=>{return};constructor(e,t,r={}){this.bus=t,this.ready=new Promise((n,o)=>{this.resolveReady=n,this.rejectReady=o}),this.worker=new Worker(r.script??SA,{name:"devctl-logs"}),this.worker.addEventListener("message",(n)=>{this.onMessage(n.data)}),this.worker.addEventListener("error",(n)=>{this.markDead(Error(n.message||"log worker failed"))}),this.post({type:"init",config:e})}waitUntilReady(e=Iy){if(this.dead)return Promise.reject(Error("log worker is not running"));if(this.readySettled)return Promise.resolve();return new Promise((t,r)=>{let n=setTimeout(()=>{this.markDead(Error("log worker init timed out"))},e);this.ready.then(()=>{clearTimeout(n),t()},(o)=>{clearTimeout(n),r(o)})})}append(e){if(this.dead)return;try{this.post({type:"append",event:e})}catch{}}async query(e){let t=await this.rpc({type:"query",filter:e});if(!Array.isArray(t))throw Error("log worker query returned a non-array");return t}async queryPage(e,t){let r=await this.rpc({type:"queryPage",filter:e,page:t});if(!kA(r))throw Error("log worker queryPage returned an unexpected payload");return r}async queryFacets(e){let t=await this.rpc({type:"queryFacets",filter:e});if(!CA(t))throw Error("log worker queryFacets returned an unexpected payload");return t}snapshot(){return this.stats}async exportTo(e,t){await this.rpc({type:"exportTo",path:e,filter:t})}setParsers(e,t){if(this.dead)return;this.post({type:"setPluginPaths",paths:[...t??[]]})}setSecrets(e,t){if(this.dead)return;this.post({type:"setSecrets",extraMarkers:e,extraPatterns:t})}async close(){if(this.dead)return;try{await this.rpc({type:"close"},wA)}catch{}finally{this.markDead(Error("log worker closed"))}}rpc(e,t=bA){this.assertAlive();let r=this.nextId;this.nextId+=1;let n={...e,id:r};return new Promise((o,i)=>{let s=setTimeout(()=>{this.markDead(Error(`log worker timed out (${e.type})`))},t);this.pending.set(r,{resolve:o,reject:i,timer:s});try{this.post(n)}catch(a){clearTimeout(s),this.pending.delete(r),i(a instanceof Error?a:Error(String(a)))}})}post(e){this.assertAlive(),this.worker.postMessage(e)}onMessage(e){if(e.type==="ready"){this.settleReady();return}if(e.type==="appended"){this.stats=e.stats,this.bus?.publish(Se(Mo,e.event.service,{event:e.event,level:e.event.level}));return}let t=this.pending.get(e.id);if(!t)return;if(this.pending.delete(e.id),clearTimeout(t.timer),e.type==="error"){t.reject(Error(e.error));return}t.resolve(e.result)}settleReady(){if(this.readySettled||this.dead)return;this.readySettled=!0,this.resolveReady()}assertAlive(){if(this.dead)throw Error("log worker is not running")}markDead(e){if(this.dead)return;if(this.dead=!0,!this.readySettled)this.readySettled=!0,this.rejectReady(e);this.rejectAll(e);try{this.worker.terminate()}catch{}}rejectAll(e){for(let t of this.pending.values())clearTimeout(t.timer),t.reject(e);this.pending.clear()}}function kA(e){if(e===null||Array.isArray(e)||!("events"in e))return!1;return Array.isArray(e.events)}function CA(e){return e!==null&&!Array.isArray(e)&&"byService"in e}function Dy(e,t,r){return eh(new Rd(e.max,t,r,e.persist,e.directory,e.sessionID,e.retentionDays,e.maxSessionLogs))}async function Oy(e,t,r,n={}){if(n.standalone??Bun.isStandaloneExecutable===!0)return{logs:Dy(e,t,r),usingWorker:!1};try{let i=new My(e,t,{script:n.script});return await i.waitUntilReady(n.initTimeoutMs??Iy),{logs:i,usingWorker:!0}}catch{return{logs:Dy(e,t,r),usingWorker:!1}}}ni();import{createServer as YA}from"http";te();us();var JA="2025-03-26",rx="2.0",Qy=-32700,Zy=-32600,XA=-32601,ex=-32602,QA=-32603;function ZA(e){return e!==""&&Cn(e)}class ap{server;running=!1;addr="";boundPort=0;opts;constructor(e){this.opts=e}disabled(){return this.opts.disabledTools?.()??[]}address(){return this.addr}listenPort(){return this.boundPort}isRunning(){return this.running}emit(e,t){this.opts.onEvent?.(e,t)}start(){if(!ZA(this.opts.host))return Promise.reject(I(lt,`refusing to bind MCP to ${this.opts.host}`));return new Promise((e,t)=>{this.server=YA((r,n)=>{this.serve(r,n)}),this.server.on("error",(r)=>t(Pe(lt,`unable to listen on ${this.opts.host}:${this.opts.port}`,r))),this.server.listen(this.opts.port,this.opts.host,()=>{let r=this.server?.address();this.boundPort=typeof r==="object"&&r?r.port:this.opts.port,this.addr=`${this.opts.host}:${this.boundPort}`,this.running=!0,this.emit("INFO",`listening on ${this.addr}`),e()})})}stop(){let e=this.server,t=this.running;if(!e)return this.running=!1,Promise.resolve();return new Promise((r)=>{e.close(()=>{if(this.running=!1,this.addr="",this.boundPort=0,this.server=void 0,t)this.emit("INFO","stopped");r()})})}async serve(e,t){if(e.method==="OPTIONS"){t.writeHead(204,ms()),t.end();return}if(!this.authorized(e)){this.emit("WARN",`rejected unauthorized request from ${tx(e)}`),oi(t,401,{error:"unauthorized"});return}if(tL(e)!=="/mcp"){oi(t,404,{error:"not found"});return}if(e.method==="GET"||e.method==="DELETE"){t.writeHead(405,{Allow:"POST, OPTIONS",...ms()}),t.end();return}if(e.method!=="POST"){t.writeHead(405,{Allow:"POST, OPTIONS",...ms()}),t.end();return}let n;try{n=await rL(e)}catch(a){oi(t,400,ps(null,Qy,a instanceof Error?a.message:"invalid body"));return}let o;try{o=JSON.parse(n)}catch{oi(t,400,ps(null,Qy,"parse error"));return}let i=tx(e);if(Array.isArray(o)){let a=[];for(let c of o){let l=await this.dispatch(c,i);if(l!==void 0)a.push(l)}oi(t,200,a);return}let s=await this.dispatch(o,i);if(s===void 0){t.writeHead(202,ms()),t.end();return}oi(t,200,s)}authorized(e){if(this.opts.token==="")return!0;return(e.headers.authorization??"")===`Bearer ${this.opts.token}`}async dispatch(e,t){if(!_c(e))return ps(null,Zy,"invalid request");let r=e,n=r.id??null,o=r.id===void 0;if(r.method===void 0||r.method==="")return o?void 0:ps(n,Zy,"missing method");try{let i=await this.handleMethod(r.method,_c(r.params)?r.params:{},t);if(o)return;return{jsonrpc:rx,id:n,result:i}}catch(i){if(o)return;let s=i instanceof fs?i.code:QA,a=i instanceof Error?i.message:"internal error";return this.emit("WARN",`rpc error method=${r.method} addr=${t}: ${a}`),ps(n,s,a)}}async handleMethod(e,t,r){switch(e){case"initialize":{let n=eL(t);return this.emit("INFO",`client connected addr=${r}${n?` (${n})`:""}`),{protocolVersion:JA,capabilities:{tools:{},resources:{}},serverInfo:{name:"devctl",version:jt}}}case"notifications/initialized":case"ping":return{};case"tools/list":return{tools:Gy(this.disabled()).map((n)=>({name:n.name,description:n.description,inputSchema:n.inputSchema}))};case"tools/call":return this.callTool(t);case"resources/list":return{resources:ip.map((n)=>({uri:n,name:n.replace("devctl://",""),mimeType:"application/json"}))};case"resources/read":return this.readResource(t);default:throw new fs(XA,`method not found: ${e}`)}}async callTool(e){let t=typeof e.name==="string"?e.name:"";if(t==="")throw new fs(ex,"tool name is required");if(Rc(t)&&!An(t,this.disabled())){let o=`tool ${t} is disabled in devctl's MCP settings`;return this.emit("WARN",`tool call rejected name=${t}: disabled`),{content:[{type:"text",text:o}],isError:!0}}let r=_c(e.arguments)?e.arguments:{},n=Date.now();try{let o=await Yy(this.opts.hostApi,t,r);return this.emit("INFO",`tool call name=${t} duration=${Date.now()-n}ms`),{content:[{type:"text",text:JSON.stringify(o,null,2)}]}}catch(o){let i=o instanceof Error?o.message:"tool failed";return this.emit("WARN",`tool call failed name=${t} duration=${Date.now()-n}ms: ${i}`),{content:[{type:"text",text:i}],isError:!0}}}async readResource(e){let t=typeof e.uri==="string"?e.uri:"";if(!Jy(t))throw new fs(ex,`unknown resource ${t}`);let r=await Xy(this.opts.hostApi,t);return this.emit("INFO",`resource read uri=${t}`),{contents:[{uri:t,mimeType:"application/json",text:JSON.stringify(r,null,2)}]}}}function eL(e){let t=e.clientInfo;if(!_c(t))return"";let r=typeof t.name==="string"?t.name:"",n=typeof t.version==="string"?t.version:"";if(r==="")return"";return n===""?r:`${r} ${n}`}function tx(e){return e.socket.remoteAddress??"unknown"}class fs extends Error{code;constructor(e,t){super(t);this.code=e}}function ms(){return{"Access-Control-Allow-Origin":"*","Access-Control-Allow-Headers":"Authorization, Content-Type, Accept, MCP-Protocol-Version, Mcp-Session-Id","Access-Control-Allow-Methods":"POST, OPTIONS"}}function oi(e,t,r){let n=JSON.stringify(r);e.writeHead(t,{"Content-Type":"application/json","Content-Length":Buffer.byteLength(n),...ms()}),e.end(n)}function ps(e,t,r){return{jsonrpc:rx,id:e,error:{code:t,message:r}}}function tL(e){let t=e.url??"/",r=t.indexOf("?");return r>=0?t.slice(0,r):t}function rL(e){return new Promise((t,r)=>{let n=[];e.on("data",(o)=>{n.push(o)}),e.on("end",()=>{t(Buffer.concat(n).toString("utf8"))}),e.on("error",r)})}function _c(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}us();var iL=(e)=>new ap({host:"127.0.0.1",...e});async function sL(e,t={}){let r=t.clock??Ja,n=t.fs??ry,o=t.processes??new Ru,i=t.bus??new dd(2048),s=t.tokens??new co(e.auth.refresh_threshold_seconds*1000,Rn(),i,void 0,r),a=new Lu(o,r),c=td(),l=new mr(e.secrets.extra_markers,e.secrets.extra_patterns),d=Bun.isStandaloneExecutable===!0,{logs:u,usingWorker:f}=await Oy({max:e.logs.max_memory_events,persist:e.logs.persistence.enabled,directory:e.logs.persistence.directory,sessionID:c,retentionDays:e.logs.persistence.retention_days,maxSessionLogs:e.logs.persistence.max_session_logs,extraMarkers:e.secrets.extra_markers,extraPatterns:e.secrets.extra_patterns},i,l,{standalone:d});if(!f&&!d)u.append({timestamp:r.isoNow(),service:"devctl",source:"devctl",level:"WARN",message:"log worker failed to start; using in-process log store",pid:0});return{supervisor:new np(e,{healthCheckers:t.healthCheckers??Mv([]),detectGoogle:t.detectGoogle??so,tokens:s,inspectProcess:ey,processAlive:Qt,acquireLock:Pm,socketExists:nL,unlinkSocket:oL,procs:o,orchestrator:a,clock:r,fs:n,bus:i,logs:u,detector:l,sessionID:c,createMcpListener:t.createMcpListener??iL,isKnownTool:Rc,createCommands:(m)=>lv(m,ec(Yo({tokens:s})),a)}),orchestrator:a,clock:r,fs:n}}async function nx(e,t){let r=ro(e,t),{supervisor:n}=await sL(r),o=!1,i=()=>{if(o)return;o=!0,n.shutdown(_a(r.shutdown)).finally(()=>process.exit(0))};process.on("SIGINT",i),process.on("SIGTERM",i),await n.run()}ht();te();Hf();import{Command as NO}from"commander";var Gk=["zsh","bash","fish"];function Kk(e){if(e==="zsh")return`#compdef devctl
666
2209
  _devctl() {
667
2210
  local -a opts
668
2211
  local line="\${words[*]}"
@@ -682,7 +2225,7 @@ complete -F _devctl devctl
682
2225
  devctl __complete (commandline -cp)
683
2226
  end
684
2227
  complete -c devctl -f -a "(__devctl_complete)"
685
- `;throw Error(`unsupported shell "${e}"; use ${Rk.join(", ")}`)}function Df(e,t){let r=e.trim().split(/\s+/).filter((l)=>l!==""),n=e.endsWith(" ")?"":r[r.length-1]??"",o=e.endsWith(" ")?r:r.slice(0,-1),i=o[1]??"",s=Object.keys(t.services).sort(),a=Object.keys(t.profiles).sort(),c=["start","run","exec","down","daemon","stop","restart","status","logs","doctor","setup","auth","proxy","mcp","config","reload","attach","completion","update","version"];if(i===""||o.length<2)return qr(c,n);if(i==="start"||i==="stop"||i==="restart"||i==="logs"){if(n==="--profile"||o[o.length-1]==="--profile")return qr(a,n==="--profile"?"":n);return qr([...s,"--profile","--detach","--json","--since","--until"],n)}if(i==="completion")return qr([...Rk].sort(),n);if(i==="run")return qr([...Object.keys(t.tasks).sort(),"--json"],n);if(i==="exec")return qr([...s,"--print-env","--reveal","--json"],n);if(i==="auth")return qr(["status","login","logout","refresh"],n);if(i==="config")return qr(["validate","show","diff"],n);if(i==="proxy")return qr(["status","start","stop"],n);return qr(c,n)}function qr(e,t){if(t==="")return e;return e.filter((r)=>r.startsWith(t))}function be(e){return e.optsWithGlobals().config??""}function E(e){try{process.stdout.write(e)}catch(t){if(t.code==="EPIPE")process.exit(0);throw t}}function _k(e,t){let r=e.command("auth").description("Google authentication");r.command("status").option("--json","machine-readable output").action(async(n)=>{let o="";try{o=t.load("",be(e)).google.project_id}catch{o=""}let i=await t.detectGoogle(o);if(n.json){E(JSON.stringify(i,null,2)+`
2228
+ `;throw Error(`unsupported shell "${e}"; use ${Gk.join(", ")}`)}function jf(e,t){let r=e.trim().split(/\s+/).filter((l)=>l!==""),n=e.endsWith(" ")?"":r[r.length-1]??"",o=e.endsWith(" ")?r:r.slice(0,-1),i=o[1]??"",s=Object.keys(t.services).sort(),a=Object.keys(t.profiles).sort(),c=["start","run","exec","down","daemon","stop","restart","status","logs","doctor","setup","auth","proxy","mcp","config","reload","attach","completion","update","version"];if(i===""||o.length<2)return zr(c,n);if(i==="start"||i==="stop"||i==="restart"||i==="logs"){if(n==="--profile"||o[o.length-1]==="--profile")return zr(a,n==="--profile"?"":n);return zr([...s,"--profile","--detach","--json","--since","--until"],n)}if(i==="completion")return zr([...Gk].sort(),n);if(i==="run")return zr([...Object.keys(t.tasks).sort(),"--json"],n);if(i==="exec")return zr([...s,"--print-env","--reveal","--json"],n);if(i==="auth")return zr(["status","login","logout","refresh"],n);if(i==="config")return zr(["validate","show","diff"],n);if(i==="proxy")return zr(["status","start","stop"],n);return zr(c,n)}function zr(e,t){if(t==="")return e;return e.filter((r)=>r.startsWith(t))}function be(e){return e.optsWithGlobals().config??""}function E(e){try{process.stdout.write(e)}catch(t){if(t.code==="EPIPE")process.exit(0);throw t}}function qk(e,t){let r=e.command("auth").description("Google authentication");r.command("status").option("--json","machine-readable output").action(async(n)=>{let o="";try{o=t.load("",be(e)).google.project_id}catch{o=""}let i=await t.detectGoogle(o);if(n.json){E(JSON.stringify(i,null,2)+`
686
2229
  `);return}if(E(`User: ${i.userEmail||"(unknown)"}
687
2230
  `),E(`Project: ${i.projectID||"(unset)"}
688
2231
  `),i.projectID!=="")E(`Source: ${i.projectSource}
@@ -691,10 +2234,10 @@ complete -c devctl -f -a "(__devctl_complete)"
691
2234
  `)}),r.command("login").action(async()=>{await t.loginGoogle()}),r.command("logout").action(async()=>{await t.logoutGoogle()}),r.command("refresh").option("--json","machine-readable output").action(async(n)=>{let o=await t.openController("",be(e),!1);try{let i=await t.refreshUserToken("user"),s=o.client?await o.refreshAuth():void 0;if(n.json){E(JSON.stringify({identity:i.identity,expires_at:i.expiresAt.toISOString(),service_account_status:s?.service_account_status??{}},null,2)+`
692
2235
  `);return}if(E(`refreshed credentials expire ${i.expiresAt.toISOString()}
693
2236
  `),s)for(let[a,c]of Object.entries(s.service_account_status).sort(([l],[d])=>l.localeCompare(d)))E(`service account ${a}: ${c}
694
- `)}finally{await o.close()}})}te();Tt();of();import{mkdirSync as KO}from"fs";import{basename as qO,dirname as VO}from"path";import{stringify as JO}from"yaml";Pa();function Ek(e,t){e.command("reload").description("reload configuration").action(async()=>{let r=await t.openController("",be(e),!0);try{let n=await r.reload();if(n.restart_required.length===0)E(`configuration reloaded
2237
+ `)}finally{await o.close()}})}te();Tt();ff();import{mkdirSync as AO}from"fs";import{basename as LO,dirname as DO}from"path";import{stringify as IO}from"yaml";Da();function Vk(e,t){e.command("reload").description("reload configuration").action(async()=>{let r=await t.openController("",be(e),!0);try{let n=await r.reload();if(n.restart_required.length===0)E(`configuration reloaded
695
2238
  `);else E(`configuration reloaded; restart required: ${n.restart_required.join(", ")}
696
- `);if(n.supervisor_restart_required&&n.supervisor_restart_required.length>0)E(`note: ${Qa(n.supervisor_restart_required)}
697
- `)}finally{await r.close()}})}function Tk(e,t){let r=e.command("config");r.command("validate").option("--json").action((n)=>{try{let o=t.load("",be(e)),i=t.validate(o);if(n.json){E(JSON.stringify({valid:i.length===0,issues:i},null,2)+`
2239
+ `);if(n.supervisor_restart_required&&n.supervisor_restart_required.length>0)E(`note: ${oc(n.supervisor_restart_required)}
2240
+ `)}finally{await r.close()}})}function zk(e,t){let r=e.command("config");r.command("validate").option("--json").action((n)=>{try{let o=t.load("",be(e)),i=t.validate(o);if(n.json){E(JSON.stringify({valid:i.length===0,issues:i},null,2)+`
698
2241
  `);return}if(i.length>0)throw Error(i.join(`
699
2242
  `));E(`configuration is valid
700
2243
  `)}catch(o){if(n.json){E(JSON.stringify({valid:!1,error:A(o)},null,2)+`
@@ -703,20 +2246,20 @@ complete -c devctl -f -a "(__devctl_complete)"
703
2246
  `),E(` winner: ${s.layer} (${s.source})
704
2247
  `);for(let a of s.shadowed)E(` shadowed: ${a.layer} (${a.source})
705
2248
  `)}}),r.command("show").option("--json").action((n)=>{let o=t.load("",be(e));if(n.json){E(JSON.stringify(o,null,2)+`
706
- `);return}E(JO(o))}),r.command("import").command("compose").argument("<file>","docker-compose.yml or compose.yaml").option("--write","write mapped fields under .devctl/config.yaml").action((n,o)=>{let i=t.readTextFile(n),{repo:s,cfgPath:a}=Od("",be(e)),c=ml(i,qO(s)),l=t.validateConfigText(s,a,c.yaml);if(E(gl(c)),l.length>0){if(E(`validation:
2249
+ `);return}E(IO(o))}),r.command("import").command("compose").argument("<file>","docker-compose.yml or compose.yaml").option("--write","write mapped fields under .devctl/config.yaml").action((n,o)=>{let i=t.readTextFile(n),{repo:s,cfgPath:a}=Hd("",be(e)),c=bl(i,LO(s)),l=t.validateConfigText(s,a,c.yaml);if(E(wl(c)),l.length>0){if(E(`validation:
707
2250
  ${l.map((d)=>` ${d}`).join(`
708
2251
  `)}
709
2252
  `),o.write===!0)throw Error("refusing to write an invalid mapping");return}if(o.write!==!0){E(`dry-run; pass --write to save under .devctl/config.yaml
710
- `);return}if(t.fileExists(a))throw Error(`configuration already exists at ${a}; not overwriting`);KO(VO(a),{recursive:!0}),t.writeTextFile(a,c.yaml),E(`wrote ${a}
711
- `)})}ti();Tt();import{setTimeout as zO}from"timers/promises";function Lk(e,t){e.command("exec").argument("<service>","service whose execution context to use").argument("[command...]","command and arguments").option("--print-env","print the resolved environment instead of running a command").option("--reveal","show secret values with --print-env").option("--json","machine-readable output").action(async(r,n,o)=>{if(!o.printEnv&&n.length===0)throw Error("exec command is required (or use --print-env)");let i=await t.openController("",be(e),!0);try{let s=await i.execService(r,n,o.printEnv===!0);if(s.environment){let a=o.reveal?s.environment:new mr(i.cfg.secrets.extra_markers,i.cfg.secrets.extra_patterns).redactMap(s.environment);if(o.json)E(JSON.stringify({...s,environment:a},null,2)+`
2253
+ `);return}if(t.fileExists(a))throw Error(`configuration already exists at ${a}; not overwriting`);AO(DO(a),{recursive:!0}),t.writeTextFile(a,c.yaml),E(`wrote ${a}
2254
+ `)})}ni();Tt();import{setTimeout as MO}from"timers/promises";function Jk(e,t){e.command("exec").argument("<service>","service whose execution context to use").argument("[command...]","command and arguments").option("--print-env","print the resolved environment instead of running a command").option("--reveal","show secret values with --print-env").option("--json","machine-readable output").action(async(r,n,o)=>{if(!o.printEnv&&n.length===0)throw Error("exec command is required (or use --print-env)");let i=await t.openController("",be(e),!0);try{let s=await i.execService(r,n,o.printEnv===!0);if(s.environment){let a=o.reveal?s.environment:new mr(i.cfg.secrets.extra_markers,i.cfg.secrets.extra_patterns).redactMap(s.environment);if(o.json)E(JSON.stringify({...s,environment:a},null,2)+`
712
2255
  `);else for(let c of Object.keys(a).sort())E(`${c}=${a[c]}
713
2256
  `)}else if(o.json)E(JSON.stringify(s,null,2)+`
714
- `);else{if(s.stdout)E(s.stdout);if(s.stderr)process.stderr.write(s.stderr)}}finally{await i.close()}})}function Dk(e,t){e.command("run").argument("<task>","task to run").option("--json","machine-readable output").action(async(r,n)=>{let o=await t.openController("",be(e),!0);try{let i=await o.runTask(r);if(n.json)E(JSON.stringify(i,null,2)+`
715
- `);else{if(i.stdout)E(i.stdout);if(i.stderr)process.stderr.write(i.stderr)}}finally{await o.close()}})}function Mk(e,t){e.command("start").argument("[services...]","services to start").option("--profile <name>","profile to start").option("--detach","deprecated, no longer changes behavior: the daemon already outlives this command; use `devctl down` to stop it").option("--json","machine-readable output").action(async(r,n)=>{if(n.detach)process.stderr.write("warning: --detach is deprecated and no longer changes behavior \u2014 the daemon already keeps running after `start` exits; use `devctl down` to stop it\n");let o=await t.openController("",be(e),!0);try{let i=await o.start({services:r,profile:n.profile,detach:n.detach===!0});if(n.json){E(JSON.stringify(i,null,2)+`
716
- `);return}if(E(Hh(i)),n.detach)E(`detached; services continue running
717
- `)}finally{await o.close()}})}function Ok(e,t){e.command("stop").argument("[services...]","services to stop").option("--json","machine-readable output").action(async(r,n)=>{let o=await t.openController("",be(e),!0);try{if(await o.stop(r),n.json)E(JSON.stringify({stopped:r},null,2)+`
718
- `)}finally{await o.close()}})}function Ik(e,t){e.command("restart").argument("[services...]").option("--cascade","also restart transitive dependents (default: only the named services)").option("--json","machine-readable output").action(async(r,n)=>{let o=await t.openController("",be(e),!0);try{if(await o.restart(r,n.cascade===!0),n.json)E(JSON.stringify({restarted:r,cascade:n.cascade===!0},null,2)+`
719
- `)}finally{await o.close()}})}async function Ak(e,t,r){let{repoRoot:n,client:o}=await e.findDaemon("",r.repo??"",be(t));try{if(!o){let s=e.readPersistedState(n);if(r.json){E(JSON.stringify({running:!1,persisted:s},null,2)+`
2257
+ `);else{if(s.stdout)E(s.stdout);if(s.stderr)process.stderr.write(s.stderr)}}finally{await i.close()}})}function Xk(e,t){e.command("run").argument("<task>","task to run").option("--json","machine-readable output").action(async(r,n)=>{let o=await t.openController("",be(e),!0);try{let i=await o.runTask(r);if(n.json)E(JSON.stringify(i,null,2)+`
2258
+ `);else{if(i.stdout)E(i.stdout);if(i.stderr)process.stderr.write(i.stderr)}}finally{await o.close()}})}function Qk(e,t){e.command("start").argument("[services...]","services to start").option("--profile <name>","profile to start").option("--detach","deprecated, no longer changes behavior: the daemon already outlives this command; use `devctl down` to stop it").option("--json","machine-readable output").action(async(r,n)=>{if(n.detach)process.stderr.write("warning: --detach is deprecated and no longer changes behavior \u2014 the daemon already keeps running after `start` exits; use `devctl down` to stop it\n");let o=await t.openController("",be(e),!0);try{let i=await o.start({services:r,profile:n.profile,detach:n.detach===!0});if(n.json){E(JSON.stringify(i,null,2)+`
2259
+ `);return}if(E(Zg(i)),n.detach)E(`detached; services continue running
2260
+ `)}finally{await o.close()}})}function Zk(e,t){e.command("stop").argument("[services...]","services to stop").option("--json","machine-readable output").action(async(r,n)=>{let o=await t.openController("",be(e),!0);try{if(await o.stop(r),n.json)E(JSON.stringify({stopped:r},null,2)+`
2261
+ `)}finally{await o.close()}})}function eC(e,t){e.command("restart").argument("[services...]").option("--cascade","also restart transitive dependents (default: only the named services)").option("--json","machine-readable output").action(async(r,n)=>{let o=await t.openController("",be(e),!0);try{if(await o.restart(r,n.cascade===!0),n.json)E(JSON.stringify({restarted:r,cascade:n.cascade===!0},null,2)+`
2262
+ `)}finally{await o.close()}})}async function Yk(e,t,r){let{repoRoot:n,client:o}=await e.findDaemon("",r.repo??"",be(t));try{if(!o){let s=e.readPersistedState(n);if(r.json){E(JSON.stringify({running:!1,persisted:s},null,2)+`
720
2263
  `);return}if(E(`supervisor is not running
721
2264
  `),s&&s.processes.length>0){E(`last session ${s.session_id} profile ${s.profile||"(none)"}
722
2265
  `);for(let a of s.processes)E(`${a.name} stopped UNKNOWN ${a.pid}
@@ -730,43 +2273,43 @@ PROXY ${i.proxy.running?"RUNNING":"STOPPED"} ${i.proxy.address??""}
730
2273
  `),E(`MCP ${i.mcp?.running?"RUNNING":"STOPPED"} ${i.mcp?.address??""}
731
2274
  `),E(`IDENTITY ${i.identity.user||"(unknown)"}
732
2275
  `),E(`CLOUD ${i.identity.project||"(unset)"}
733
- `)}finally{o?.close()}}var YO=2000;function $k(e,t){e.command("status").option("--repo <path>","target a repository directly, even without a loadable configuration").option("--json","machine-readable output").option("--watch","keep refreshing until interrupted").action(async(r)=>{if(!r.watch){await Ak(t,e,r);return}let n=new AbortController,o=()=>n.abort();process.on("SIGINT",o),process.on("SIGTERM",o);try{while(!n.signal.aborted){E(`--- ${new Date().toISOString()} ---
734
- `),await Ak(t,e,r),E(`
735
- `);try{await zO(YO,void 0,{signal:n.signal})}catch{break}}}finally{process.off("SIGINT",o),process.off("SIGTERM",o)}})}function Fk(e,t){e.command("down").description("stop the daemon (and, by default, its services)").option("--repo <path>","target a repository directly, even without a loadable configuration").option("--keep-services","stop only the daemon; its services keep running, detached").action(async(r)=>{let{repoRoot:n,client:o}=await t.findDaemon("",r.repo??"",be(e));if(!o){E(`no supervisor is running for ${n}
736
- `);return}let i=await QO(o);try{let s=r.keepServices!==!0;await o.call("shutdown",{stop_services:s},i)}finally{o.close()}await XO(t,n,i),E(r.keepServices!==!0?`stopped services and the supervisor for ${n}
2276
+ `)}finally{o?.close()}}var OO=2000;function tC(e,t){e.command("status").option("--repo <path>","target a repository directly, even without a loadable configuration").option("--json","machine-readable output").option("--watch","keep refreshing until interrupted").action(async(r)=>{if(!r.watch){await Yk(t,e,r);return}let n=new AbortController,o=()=>n.abort();process.on("SIGINT",o),process.on("SIGTERM",o);try{while(!n.signal.aborted){E(`--- ${new Date().toISOString()} ---
2277
+ `),await Yk(t,e,r),E(`
2278
+ `);try{await MO(OO,void 0,{signal:n.signal})}catch{break}}}finally{process.off("SIGINT",o),process.off("SIGTERM",o)}})}function rC(e,t){e.command("down").description("stop the daemon (and, by default, its services)").option("--repo <path>","target a repository directly, even without a loadable configuration").option("--keep-services","stop only the daemon; its services keep running, detached").action(async(r)=>{let{repoRoot:n,client:o}=await t.findDaemon("",r.repo??"",be(e));if(!o){E(`no supervisor is running for ${n}
2279
+ `);return}let i=await FO(o);try{let s=r.keepServices!==!0;await o.call("shutdown",{stop_services:s},i)}finally{o.close()}await $O(t,n,i),E(r.keepServices!==!0?`stopped services and the supervisor for ${n}
737
2280
  `:`stopped the supervisor for ${n}; its services keep running
738
- `)})}async function XO(e,t,r){let n=Date.now()+r;while(Date.now()<n){let o=await e.tryDial(t);if(!o)return;o.close()}}async function QO(e){try{let r=await e.call("config_snapshot",null),n=typeof r.shutdown?.grace_seconds==="number"?r.shutdown.grace_seconds:0;return Math.max(5000,n*1000+2000)}catch{return 30000}}function Nk(e,t){e.command("attach").action(async()=>{let r=await t.openAttach("",be(e)),{runTuiWithController:n}=await Promise.resolve().then(() => (Lf(),Ck));await n(t,r)})}import{setTimeout as Hk}from"timers/promises";function Mf(e){return`${e.timestamp.slice(11,19)} ${e.service.padEnd(10)} ${String(e.level).padEnd(6)} ${e.message}
739
- `}async function Bk(e,t,r,n=1000){let o=await e(void 0);o.events.forEach(t);let i=o.nextCursor;while(!r.aborted){try{await Hk(n,void 0,{signal:r})}catch{return}o=await e(i),o.events.forEach(t),i=o.nextCursor}}function Wk(e,t){let r=e.command("logs").argument("[services...]").enablePositionalOptions();r.option("--level <level>","minimum level").option("--search <text>","substring or regex search").option("--regex","treat search as regular expression").option("--source <source>","filter by source").option("--since <timestamp>","only events at or after this ISO timestamp").option("--until <timestamp>","only events at or before this ISO timestamp").option("--output <path>","export path").option("--json","machine-readable output").option("-f, --follow","keep printing new matching events until interrupted").option("--all","print the full matching history instead of the latest page").action(async(n,o)=>{let i=await t.openController("",be(e),!0);try{let s=o.output?t.resolveExportPath(o.output):void 0;if(o.follow&&!s){let c=new AbortController,l=()=>c.abort();process.on("SIGINT",l),process.on("SIGTERM",l);try{await Bk((d)=>i.logsPage({services:n,level:o.level,search:o.search,regex:o.regex,source:o.source,since:o.since,until:o.until,cursor:d,direction:"forward"}),(d)=>E(o.json?JSON.stringify(d)+`
740
- `:Mf(d)),c.signal)}finally{process.off("SIGINT",l),process.off("SIGTERM",l)}return}if(s||o.all===!0){let c=await i.logs({services:n,level:o.level,search:o.search,regex:o.regex,source:o.source,since:o.since,until:o.until,export:s});if(s){E(`exported ${s}
2281
+ `)})}async function $O(e,t,r){let n=Date.now()+r;while(Date.now()<n){let o=await e.tryDial(t);if(!o)return;o.close()}}async function FO(e){try{let r=await e.call("config_snapshot",null),n=typeof r.shutdown?.grace_seconds==="number"?r.shutdown.grace_seconds:0;return Math.max(5000,n*1000+2000)}catch{return 30000}}function nC(e,t){e.command("attach").action(async()=>{let r=await t.openAttach("",be(e)),{runTuiWithController:n}=await Promise.resolve().then(() => (Hf(),Wk));await n(t,r)})}import{setTimeout as oC}from"timers/promises";function Bf(e){return`${e.timestamp.slice(11,19)} ${e.service.padEnd(10)} ${String(e.level).padEnd(6)} ${e.message}
2282
+ `}async function iC(e,t,r,n=1000){let o=await e(void 0);o.events.forEach(t);let i=o.nextCursor;while(!r.aborted){try{await oC(n,void 0,{signal:r})}catch{return}o=await e(i),o.events.forEach(t),i=o.nextCursor}}function sC(e,t){let r=e.command("logs").argument("[services...]").enablePositionalOptions();r.option("--level <level>","minimum level").option("--search <text>","substring or regex search").option("--regex","treat search as regular expression").option("--source <source>","filter by source").option("--since <timestamp>","only events at or after this ISO timestamp").option("--until <timestamp>","only events at or before this ISO timestamp").option("--output <path>","export path").option("--json","machine-readable output").option("-f, --follow","keep printing new matching events until interrupted").option("--all","print the full matching history instead of the latest page").action(async(n,o)=>{let i=await t.openController("",be(e),!0);try{let s=o.output?t.resolveExportPath(o.output):void 0;if(o.follow&&!s){let c=new AbortController,l=()=>c.abort();process.on("SIGINT",l),process.on("SIGTERM",l);try{await iC((d)=>i.logsPage({services:n,level:o.level,search:o.search,regex:o.regex,source:o.source,since:o.since,until:o.until,cursor:d,direction:"forward"}),(d)=>E(o.json?JSON.stringify(d)+`
2283
+ `:Bf(d)),c.signal)}finally{process.off("SIGINT",l),process.off("SIGTERM",l)}return}if(s||o.all===!0){let c=await i.logs({services:n,level:o.level,search:o.search,regex:o.regex,source:o.source,since:o.since,until:o.until,export:s});if(s){E(`exported ${s}
741
2284
  `);return}if(o.json){E(JSON.stringify(c,null,2)+`
742
- `);return}for(let l of c)E(Mf(l));return}let a=await i.logsPage({services:n,level:o.level,search:o.search,regex:o.regex,source:o.source,since:o.since,until:o.until,direction:"backward"});if(o.json){E(JSON.stringify(a.events,null,2)+`
743
- `);return}for(let c of a.events)E(Mf(c));if(a.hasPrev)E(`\u2026 older matching events omitted; pass --all for the full history
2285
+ `);return}for(let l of c)E(Bf(l));return}let a=await i.logsPage({services:n,level:o.level,search:o.search,regex:o.regex,source:o.source,since:o.since,until:o.until,direction:"backward"});if(o.json){E(JSON.stringify(a.events,null,2)+`
2286
+ `);return}for(let c of a.events)E(Bf(c));if(a.hasPrev)E(`\u2026 older matching events omitted; pass --all for the full history
744
2287
  `)}finally{await i.close()}}),r.command("export").argument("[services...]").requiredOption("--output <path>","export path").option("--level <level>","minimum level").option("--search <text>","substring or regex search").option("--regex","treat search as regular expression").option("--source <source>","filter by source").action(async(n,o)=>{let i=await t.openController("",be(e),!0);try{let s=t.resolveExportPath(o.output);await i.logs({services:n,level:o.level,search:o.search,regex:o.regex,source:o.source,export:s}),E(`exported ${s}
745
- `)}finally{await i.close()}})}function jk(e,t){e.command("daemon").command("logs").option("-f, --follow","keep printing new lines until interrupted").action(async(r)=>{let{repoRoot:n}=t.discover("",be(e)),o=t.bootstrapLogPath(n),i=0,s=()=>{if(!t.fileExists(o))return;let l=t.readTextFile(o);if(l.length>i)E(l.slice(i)),i=l.length};if(!t.fileExists(o))E(`no daemon bootstrap log yet for this repository
746
- `);else s();if(!r.follow)return;let a=new AbortController,c=()=>a.abort();process.on("SIGINT",c),process.on("SIGTERM",c);try{while(!a.signal.aborted){try{await Hk(500,void 0,{signal:a.signal})}catch{break}s()}}finally{process.off("SIGINT",c),process.off("SIGTERM",c)}})}ul();function Gk(e,t){let r=e.command("proxy");r.command("status").option("--json").action(async(n)=>{let o=await t.openController("",be(e),!1);try{if(!o.client){E(`PROXY STOPPED
2288
+ `)}finally{await i.close()}})}function aC(e,t){e.command("daemon").command("logs").option("-f, --follow","keep printing new lines until interrupted").action(async(r)=>{let{repoRoot:n}=t.discover("",be(e)),o=t.bootstrapLogPath(n),i=0,s=()=>{if(!t.fileExists(o))return;let l=t.readTextFile(o);if(l.length>i)E(l.slice(i)),i=l.length};if(!t.fileExists(o))E(`no daemon bootstrap log yet for this repository
2289
+ `);else s();if(!r.follow)return;let a=new AbortController,c=()=>a.abort();process.on("SIGINT",c),process.on("SIGTERM",c);try{while(!a.signal.aborted){try{await oC(500,void 0,{signal:a.signal})}catch{break}s()}}finally{process.off("SIGINT",c),process.off("SIGTERM",c)}})}vl();function cC(e,t){let r=e.command("proxy");r.command("status").option("--json").action(async(n)=>{let o=await t.openController("",be(e),!1);try{if(!o.client){E(`PROXY STOPPED
747
2290
  `);return}let i=await o.status();if(n.json){E(JSON.stringify(i.proxy,null,2)+`
748
2291
  `);return}E(`PROXY ${i.proxy.running?"RUNNING":"STOPPED"} ${i.proxy.address??""}
749
- `);for(let s of i.proxy.routes??[]){let a=s.match?` match=${s.match}`:"";E(` ${s.name.padEnd(16)} identity=${s.identity}${a} ${s.upstream}
750
- `)}}finally{await o.close()}}),r.command("start").action(async()=>{let n=await t.openController("",be(e),!0);try{await n.proxyStart()}finally{await n.close()}}),r.command("stop").action(async()=>{let n=await t.openController("",be(e),!0);try{await n.proxyStop()}finally{await n.close()}})}function Uk(e,t){e.command("mcp").description("Local MCP server for coding agents").option("--on","start the MCP listener").option("--off","stop the MCP listener").option("--port <port>","listen port").option("--json","machine-readable output").action(async(r)=>{let n=r.port===void 0?void 0:Number(r.port);if(r.port!==void 0&&(!Number.isInteger(n)||(n??0)<=0))throw Error(`invalid --port ${r.port}`);let o=await t.openController("",be(e),r.on===!0,{allowMissingConfig:!0});try{if(r.off===!0&&o.client)await o.mcpStop();else if(r.on===!0)await o.mcpStart({port:n});let i=o.client?await o.status():void 0,s=t.loadTuiConfig(o.cfg.repoRoot,o.cfg.ui.keymap),a=i?.mcp?.port??n??s.mcp_port??lo(o.cfg.repoRoot),c=i?.mcp?.address??ui(a),l=i?.mcp?.token??"";if(r.json){E(JSON.stringify({running:i?.mcp?.running===!0,setup_mode:i?.setup_mode===!0,url:c,port:a,snippets:{claude:JSON.parse(Hp(c,l)),cursor:JSON.parse(Bp(c,l)),kilo:JSON.parse(Wp(c,l)),codex:jp(c,l)}},null,2)+`
2292
+ `);for(let s of i.proxy.routes??[]){let a=s.match?` match=${s.match}`:"",c=s.client_id?` client_id=${s.client_id}`:"";E(` ${s.name.padEnd(16)} identity=${s.identity}${a}${c} ${s.upstream}
2293
+ `)}}finally{await o.close()}}),r.command("start").action(async()=>{let n=await t.openController("",be(e),!0);try{await n.proxyStart()}finally{await n.close()}}),r.command("stop").action(async()=>{let n=await t.openController("",be(e),!0);try{await n.proxyStop()}finally{await n.close()}})}function lC(e,t){e.command("mcp").description("Local MCP server for coding agents").option("--on","start the MCP listener").option("--off","stop the MCP listener").option("--port <port>","listen port").option("--json","machine-readable output").action(async(r)=>{let n=r.port===void 0?void 0:Number(r.port);if(r.port!==void 0&&(!Number.isInteger(n)||(n??0)<=0))throw Error(`invalid --port ${r.port}`);let o=await t.openController("",be(e),r.on===!0,{allowMissingConfig:!0});try{if(r.off===!0&&o.client)await o.mcpStop();else if(r.on===!0)await o.mcpStart({port:n});let i=o.client?await o.status():void 0,s=t.loadTuiConfig(o.cfg.repoRoot,o.cfg.ui.keymap),a=i?.mcp?.port??n??s.mcp_port??mo(o.cfg.repoRoot),c=i?.mcp?.address??fi(a),l=i?.mcp?.token??"";if(r.json){E(JSON.stringify({running:i?.mcp?.running===!0,setup_mode:i?.setup_mode===!0,url:c,port:a,snippets:{claude:JSON.parse(Vp(c,l)),cursor:JSON.parse(zp(c,l)),kilo:JSON.parse(Yp(c,l)),codex:Jp(c,l)}},null,2)+`
751
2294
  `);return}if(E(`MCP ${i?.mcp?.running?"RUNNING":"STOPPED"} ${c}
752
2295
 
753
2296
  `),i?.setup_mode===!0)E(`This repository has no .devctl yet, so the daemon is in setup mode.
754
2297
  `),E(`Connect an agent with the config below and ask it to set devctl up for this repository.
755
- `),E(`It can call get_setup_guide and validate_config; nothing will run until a configuration exists.
2298
+ `),E(`It can call get_setup_guide, search_docs, and validate_config; nothing will run until a configuration exists.
756
2299
 
757
- `);E(Bb(c,l))}finally{await o.close()}})}te();function Kk(e,t){e.command("update").description("check GitHub Releases and install when the install method is known").option("--json","machine-readable output").option("--check","report only; do not install").action(async(r)=>{let n=await t.checkUpdate();if(r.json){E(JSON.stringify(n,null,2)+`
2300
+ `);E(iw(c,l))}finally{await o.close()}})}te();function dC(e,t){e.command("update").description("check GitHub Releases and install when the install method is known").option("--json","machine-readable output").option("--check","report only; do not install").action(async(r)=>{let n=await t.checkUpdate();if(r.json){E(JSON.stringify(n,null,2)+`
758
2301
  `);return}if(E(`current ${n.current}
759
2302
  `),E(`latest ${n.latest||"(unavailable)"}
760
2303
  `),E(`channel ${n.kind}
761
2304
  `),!n.newer){if(n.latest!=="")E(`up to date
762
2305
  `);return}if(E(`install ${n.hint}
763
2306
  `),r.check||!n.command)return;if(E(`installing via ${n.kind}\u2026
764
- `),(await t.applyUpdate(n.command,!0)).code!==0){process.exitCode=Qm;return}E(`updated to ${n.latest}; ${sc}
765
- `)})}function eI(e,t){let r=new ZO;return r.name("devctl").description("Local development orchestrator").version(er(),"-V, --version","print version").option("--config <path>","path to config file or .devctl directory").enablePositionalOptions(),r.command("version").description("print version").action(()=>{E(`${er()}
766
- `)}),r.action(async()=>{let n=r.opts();await Tf(e,n.config??"")}),Mk(r,e),Ok(r,e),Ik(r,e),Dk(r,e),Lk(r,e),$k(r,e),Fk(r,e),Wk(r,e),jk(r,e),tI(r,e),rI(r,e),_k(r,e),Gk(r,e),Uk(r,e),Tk(r,e),Ek(r,e),Nk(r,e),nI(r,e),Kk(r,e),oI(r,t),r}function tI(e,t){e.command("doctor").option("--json","machine-readable output").action(async(r)=>{let n=t.load("",be(e)),o=await t.runDoctor.execute(n);if(r.json)E(JSON.stringify(o,null,2)+`
767
- `);else E(t.formatDoctor(o));if(o.issues>0)process.exitCode=2})}function rI(e,t){e.command("setup").option("--force","overwrite an existing configuration").action(async(r)=>{await t.runSetup("",be(e),r.force===!0)})}function nI(e,t){e.command("completion").argument("[shell]","zsh, bash, or fish").description("print a shell completion script").action((r)=>{E(Pk(r||"zsh"))}),e.command("__complete",{hidden:!0}).argument("[line...]").description("internal completion helper").action((r)=>{let n=r.join(" "),o=n===""?"devctl ":n;try{let i=t.load("",be(e));E(Df(o,i).join(`
2307
+ `),(await t.applyUpdate(n.command,!0)).code!==0){process.exitCode=sh;return}E(`updated to ${n.latest}; ${pc}
2308
+ `)})}function HO(e,t){let r=new NO;return r.name("devctl").description("Local development orchestrator").version(er(),"-V, --version","print version").option("--config <path>","path to config file or .devctl directory").enablePositionalOptions(),r.command("version").description("print version").action(()=>{E(`${er()}
2309
+ `)}),r.action(async()=>{let n=r.opts();await Ff(e,n.config??"")}),Qk(r,e),Zk(r,e),eC(r,e),Xk(r,e),Jk(r,e),tC(r,e),rC(r,e),sC(r,e),aC(r,e),jO(r,e),BO(r,e),qk(r,e),cC(r,e),lC(r,e),zk(r,e),Vk(r,e),nC(r,e),UO(r,e),dC(r,e),WO(r,t),r}function jO(e,t){e.command("doctor").option("--json","machine-readable output").action(async(r)=>{let n=t.load("",be(e)),o=await t.runDoctor.execute(n);if(r.json)E(JSON.stringify(o,null,2)+`
2310
+ `);else E(t.formatDoctor(o));if(o.issues>0)process.exitCode=2})}function BO(e,t){e.command("setup").option("--force","overwrite an existing configuration").action(async(r)=>{await t.runSetup("",be(e),r.force===!0)})}function UO(e,t){e.command("completion").argument("[shell]","zsh, bash, or fish").description("print a shell completion script").action((r)=>{E(Kk(r||"zsh"))}),e.command("__complete",{hidden:!0}).argument("[line...]").description("internal completion helper").action((r)=>{let n=r.join(" "),o=n===""?"devctl ":n;try{let i=t.load("",be(e));E(jf(o,i).join(`
768
2311
  `)+`
769
- `)}catch{E(Df(o,Jn()).join(`
2312
+ `)}catch{E(jf(o,Jn()).join(`
770
2313
  `)+`
771
- `)}})}function oI(e,t){e.command("_supervisor").option("--repo <path>","repository root").action(async(r)=>{await t(r.repo??"",be(e))})}async function qk(e,t){process.stdout.on("error",(r)=>{if(r.code==="EPIPE")process.exit(0)});try{await eI(e,t).parseAsync(process.argv)}catch(r){process.stderr.write(A(r)+`
772
- `),process.exit(eg(r))}}Gs();await qk(xy(),Nv);
2314
+ `)}})}function WO(e,t){e.command("_supervisor").option("--repo <path>","repository root").action(async(r)=>{await t(r.repo??"",be(e))})}async function uC(e,t){process.stdout.on("error",(r)=>{if(r.code==="EPIPE")process.exit(0)});try{await HO(e,t).parseAsync(process.argv)}catch(r){process.stderr.write(A(r)+`
2315
+ `),process.exit(ch(r))}}zs();await uC(Iv(),nx);