@amr-m-abdelgawad/devctl 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/devctl.js +259 -134
- package/package.json +1 -1
package/dist/devctl.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
-
var iP=Object.defineProperty;var sP=(e)=>e;function aP(e,t){this[e]=sP.bind(null,t)}var cP=(e,t)=>{for(var r in t)iP(e,r,{get:t[r],enumerable:!0,configurable:!0,set:aP.bind(t,r)})};var I=(e,t)=>()=>(e&&(t=e(e=0)),t);var lP=import.meta.require;function _f(e,t=""){if(t==="MetadataLookupWarning")return!0;return e.includes("MetadataLookupWarning")||e.startsWith("received unexpected error =")}function ra(){if(Rf)return;Rf=!0;let e=process.emitWarning.bind(process);process.emitWarning=(t,...r)=>{let n=uP(r[0]),o=dP(t);if(_f(o,n))return;e(t,...r)}}function Ef(){let e=process.stderr.write.bind(process.stderr);return process.stderr.write=(t,r,n)=>{let o=pP(t);if(_f(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 dP(e){if(typeof e==="string")return e;if(e instanceof Error)return e.message;return String(e)}function uP(e){if(typeof e==="string")return e;if(e&&typeof e==="object"&&"type"in e&&typeof e.type==="string")return e.type;return""}function pP(e){if(typeof e==="string")return e;if(e instanceof Uint8Array)return new TextDecoder().decode(e);return""}var Rf=!1;import{createHash as mP}from"crypto";import{resolve as fP}from"path";function cr(e){let t=fP(e),r=process.platform==="win32"?t.toLowerCase():t;return mP("sha256").update(r).digest("hex").slice(0,hP)}var hP=16;var na=()=>{};function td(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 pd(e=process.platform){return e==="darwin"}function MP(e=process.platform){return pd(e)?"cmd":"ctrl"}function Do(e,t=process.platform){return`${MP(t)}+${e}`}function OP(e=process.platform){return pd(e)?"command":"ctrl"}function It(e,t=process.platform){return`${OP(t)}+${e}`}function Li(e){return e.replace(/\bcmd\b/gi,"command")}function Hr(e,t=process.platform){let r=pd(t),n=Boolean(e.meta||e.super),o=Boolean(e.ctrl);return r?n&&!o:o&&!n}function en(e=process.platform){return Do("c",e)}function $P(e=process.platform){return{leader:Do("x",e),command_list:Do("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:en(e)}}function Ff(){return{theme:"devctl",leader_timeout:2000,font_size:14,keybinds:{...FP},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 NP(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 vn(e,t){return NP(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 ud=2000,Io=14,$f="DEVCTL_TUI_CONFIG",FP;var aa=I(()=>{FP=$P()});function ye(e,t,r){return{type:e,timestamp:new Date().toISOString(),service:t,payload:r}}class hd{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 jf="ServiceStarted",fa="ServiceStopped",ha="ServiceFailed",Bf="ServiceStateChanged",Uf="ServiceHealthChanged",Oo="LogReceived",$o="TokenRefreshed",qn="TokenRefreshFailed",fd="AuthenticationChanged",Wf="ProxyStarted",Gf="ProxyStopped",Di="ProxyRequest",ga="ConfigurationChanged",Ii="ConfigurationReloadFailed",Kf="SessionRecovered";var qf=5000,Vf=500,gd=16384,zf=65536,va="TRACE",ya="DEBUG",ba="INFO",Mi="WARN",Vn="ERROR",Fo="FATAL",xa="UNKNOWN",vd;var Oi=I(()=>{vd={["TRACE"]:0,["DEBUG"]:1,["INFO"]:2,["WARN"]:3,["ERROR"]:4,["FATAL"]:5,["UNKNOWN"]:2}});function Yf(){return{name:"default",parse:(e)=>{let t=wd(e);if(t)return t;return{level:Sd(e),request_id:kd(e)||void 0}}}}function $i(e,t){for(let r of t){let n=e[r];if(typeof n==="string"&&n.trim()!=="")return n}return}function Xf(e){for(let t of qP){let r=e[t];if(typeof r==="number"){let n=tR[r];if(n)return n;continue}if(typeof r==="string"&&r.trim()!=="")return r.toUpperCase()}return}function Fi(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function er(e){if(typeof e==="string"&&e.trim()!=="")return e;if(typeof e==="number"&&Number.isFinite(e))return String(e);return}function No(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 jr(e,t){for(let r of t)if(e[r]!==void 0&&e[r]!==null)return e[r];return}function bd(e){return e.filter((t)=>t!==void 0&&t!=="").join(" ")}function rR(e){if(e>=zP)return Vn;if(e>=VP)return Mi;return}function nR(e){if(Fi(e))return e;return{}}function Qf(e){let t=er(jr(e,["http.method","method","request_method"])),r=er(jr(e,["http.route","url.path","path","route","request_uri"])),n=er(jr(e,["http.status_code","status","status_code"])),o=bd([t,r,n]);return o===""?void 0:o}function Zf(e){if(!Fi(e))return er(e);if(typeof e.stringValue==="string")return e.stringValue.trim()===""?void 0:e.stringValue;return er(e.intValue??e.doubleValue??e.boolValue)}function oR(e){if(typeof e==="string"&&e.trim()!=="")return e;return Zf(e)}function eh(e){if(!Array.isArray(e)||e.length===0)return!1;return e.every((t)=>Fi(t)&&typeof t.key==="string"&&t.value!==void 0)}function iR(e){let t={};if(!eh(e))return t;for(let r of e){let n=r.key;if(typeof n==="string"&&n!==""){let o=Zf(r.value);if(o!==void 0)t[n]=o}}return t}function sR(e){let t=Xf(e);if(t)return t;let r=No(e.severityNumber);if(r===void 0)return;if(r>=eR)return Fo;if(r>=ZP)return Vn;if(r>=QP)return Mi;if(r>=XP)return ba;if(r>=JP)return ya;if(r>=YP)return va;return}function aR(e){if(e.timeUnixNano!==void 0||e.observedTimeUnixNano!==void 0)return!0;if(Fi(e.body)&&(e.body.stringValue!==void 0||e.body.intValue!==void 0||e.body.doubleValue!==void 0))return!0;return eh(e.attributes)}function cR(e,t){if(!aR(e))return;let r=iR(e.attributes),n=oR(e.body)??Qf(r)??"log",o=$i(e,yd)??er(r.trace_id??r.traceId);return{message:n,level:sR(e),request_id:o,raw:t}}function lR(e,t){let r=er(e.metric_name);if(r===void 0)return;let n=er(e.metric_type),o=No(e.value);if(n===void 0&&o===void 0)return;let i=er(e.unit),s=o===void 0?void 0:i===void 0?String(o):`${o} ${i}`;return{message:bd([r,n,s,Qf(nR(e.attributes))]),raw:t}}function dR(e){let t=e.trim().split(/\s+/);return{method:t[0],uri:t[1]}}function uR(e,t){if($i(e,Jf)!==void 0)return;let r=er(e.request),n=r===void 0?{}:dR(r),o=er(jr(e,["request_method","method","http_method","verb"]))??n.method,i=er(jr(e,["request_uri","uri","url","path","request_path"]))??n.uri,s=No(jr(e,["status","status_code","statusCode","http_status"])),a=er(jr(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=No(jr(e,["body_bytes_sent","bytes_sent","bytes"])),l=No(jr(e,["request_time","duration"])),d=No(jr(e,["request_time_ms","latency_ms","duration_ms"])),u=c===void 0?void 0:`${c}B`,m=d!==void 0?`${d}ms`:l===void 0?void 0:`${l}s`;return{message:bd([a,o,i,s===void 0?void 0:String(s),u,m]),level:s===void 0?void 0:rR(s),request_id:$i(e,yd),raw:t}}function pR(e,t){let r=$i(e,Jf),n=Xf(e),o=$i(e,yd);if(r===void 0&&n===void 0&&o===void 0)return;return{message:r??t,level:n,request_id:o,raw:t}}function xd(e){if(e.length<=gd)return e;return e.slice(0,gd)}function wd(e){let t=e.trim();if(!t.startsWith("{")||!t.endsWith("}"))return;if(t.length>zf)return;let r;try{r=JSON.parse(t)}catch{return}if(!Fi(r))return;return cR(r,t)??lR(r,t)??uR(r,t)??pR(r,t)}function Sd(e){return fR.find((r)=>r.re.test(e))?.level??xa}function kd(e){return mR.exec(e)?.[1]??""}var Jf,qP,yd,VP=400,zP=500,YP=1,JP=5,XP=9,QP=13,ZP=17,eR=21,tR,mR,fR;var th=I(()=>{Oi();Jf=["message","msg","text","log","event"],qP=["level","severity","severityText","levelname","loglevel","lvl"],yd=["request_id","requestId","trace_id","traceId","correlation_id","correlationId"],tR={10:va,20:ya,30:ba,40:Mi,50:Vn,60:Fo};mR=/(?:x-devctl-request-id|request[_-]?id)[=: ]+([A-Za-z0-9-]+)/i,fR=[{re:/\b(fatal|critical)\b/i,level:Fo},{re:/\b(error|err)\b/i,level:Vn},{re:/\b(warn|warning)\b/i,level:Mi},{re:/\b(debug|dbg)\b/i,level:ya},{re:/\b(trace)\b/i,level:va},{re:/\b(info|information)\b/i,level:ba}]});function Ni(e){let t=hR(e);if(t===void 0)return;return new RegExp(t)}function hR(e){if(e.length===0||e.length>200)return;let t={raw:e,i:0,out:[],quantifiers:0,depth:0,lastAtom:"none",groupQuant:[]};if(!rh(t)||t.i!==e.length||t.depth!==0)return;return t.out.join("")}function rh(e){while(e.i<e.raw.length){if(e.raw[e.i]===")")return!0;if(!gR(e))return!1}return!0}function gR(e){let t=e.raw[e.i]??"";if(t==="|")return wa(e,"|","none");if(t==="^")return wa(e,"^","none");if(t==="$")return wa(e,"$","none");if(t===".")return wa(e,".","atom");if(t==="(")return vR(e);if(t==="[")return bR(e);if(t==="*"||t==="+"||t==="?"||t==="{")return yR(e);if(t==="\\")return nh(e);return e.out.push(RegExp.escape(t)),e.i+=1,e.lastAtom="atom",!0}function wa(e,t,r){return e.out.push(t),e.i+=1,e.lastAtom=r,!0}function vR(e){if(e.i+=1,e.depth+=1,e.depth>2)return!1;if(e.groupQuant.push(!1),e.out.push("(?:"),!rh(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 yR(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 bR(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(!nh(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 nh(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 Ho(e,t){if(e.services&&e.services.length>0&&!e.services.includes(t.service))return!1;if(e.level&&(vd[t.level]??2)<(vd[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 Cd(e){if(!e.search)return()=>!0;if(e.regex){let r=Ni(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 Pd(e){let t=Cd(e);return(r)=>Ho(e,r)&&t(r)}var oh=I(()=>{Oi()});function ih(e){if(!Number.isInteger(e)||(e??0)<=0)return Vf;return Math.min(e,qf)}var sh=I(()=>{Oi()});var Sa=I(()=>{Oi();th();oh();sh()});function P(e,t){return new Ve(e,t)}function Se(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 Pa(e,t){return e instanceof Ve&&e.kind===t}function vh(e){if(e instanceof Ve)return e.exitCode();return 1}function yh(e){if(e instanceof Ve)return{error:L(e),kind:e.kind,hint:e.hint,service:e.service};return{error:L(e)}}function bh(e){if(typeof e==="string")return P("general",e);if(e.kind)return new Ve(e.kind,e.error.replace(`${e.kind}: `,""),{hint:e.hint,service:e.service});return P("general",e.error)}function L(e){if(e instanceof Ve){if(e.hint!=="")return`${fh(e)} \u2014 ${e.hint}`;return fh(e)}if(e instanceof Error)return e.message;return String(e)}function fh(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 hh=1,Y="configuration",zn="configuration_missing",Bo="service_not_found",gh="dependency",He="process_start",yn="authentication",Yn="authorization",Rr="token",ka="impersonation",Ca="iap",zt="proxy",Id="health_check",lt="general",Ve;var te=I(()=>{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 La(e){return e>0?e:300}function $d(){return{enabled:!1,paths:[],debounce_ms:300,ignore:[...ji]}}function Fd(){return{enabled:void 0,host:"",port:""}}function Bi(){return{type:"",identity:{type:"",service_account:""},audience:"",service_account:"",client_id:"",client_secret:"",credentials:"",headers:{}}}function Ko(e){return(e.transport??"").toLowerCase()==="grpc"}function Wo(){return{args:[],shell:!1}}function Nd(){return{vars:{},required:[],defaults:{}}}function Hd(){return{type:"",url:"",address:"",command:Wo(),interval_seconds:0,timeout_seconds:0,start_period_seconds:0,unhealthy_threshold:3,healthy_reset_threshold:10}}function Yt(e){return typeof e==="string"?e:e.service}function bn(e){return typeof e==="string"?"service_started":e.condition||"service_started"}function Ia(e){return bn(e)==="service_healthy"?`${Yt(e)} (healthy)`:Yt(e)}function jd(){return{type:"",mode:"",service_account:"",config:{}}}function xn(){return{extends:"",description:"",command:Wo(),shell:!1,working_dir:"",dependencies:[],ports:[],environment:Nd(),health:Hd(),identity:jd(),logs:{stdout:!1,stderr:!1},restart:{policy:"",max_retries:0,backoff_seconds:0},startup:{wait_for_healthy:!1,timeout_seconds:0},capabilities:[],proxy:[],expose:Fd(),container:void 0,watch:$d(),hooks:{pre_start:Wo(),post_start:Wo()}}}function Xn(){return{version:1,project:{name:""},google:{project_id:"",region:""},profiles:{},templates:{},services:{},tasks:{},proxy:{enabled:!1,gateway:!1,credentials:"",listen:{host:Da,port:0},token_endpoint:{enabled:!1,host:"",port:0},routes:[]},logs:{max_memory_events:BR,persistence:{enabled:!0,directory:"~/.devctl/logs",retention_days:UR,max_session_logs:0}},auth:{refresh_threshold_seconds:kh},shutdown:{grace_seconds:Sh},ui:{theme:"system",keymap:{}},secrets:{extra_markers:[],extra_patterns:[]},doctor:{tools:[]},plugins:[],environment:{sources:[],secrets:{}},provenance:{},repoRoot:"",configPath:""}}function wn(e){return e.args.length===0||e.args.length===1&&(e.args[0]??"").trim()===""}function Wi(e){if(e.type!=="")return e.type;return e.mode}function tn(e){let t=Wi(e).toLowerCase();return t==="service"||t==="service_account"}function Ch(e){let t=Wi(e).toLowerCase();return t==="user"||t===""}function Ma(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 Sn(e){let t=e.host===""?Da:e.host;if(e.port===0)return t;return`${t}:${e.port}`}function Ph(e){if(e.refresh_threshold_seconds<=0)return kh;return e.refresh_threshold_seconds}function Oa(e){if(e.stop_services_on_exit===void 0)return!0;return e.stop_services_on_exit}function kn(e){if(e.grace_seconds<=0)return Sh;return e.grace_seconds}function Rh(e){if(!e.logs.stdout&&!e.logs.stderr)return!0;return e.logs.stdout}function _h(e){if(!e.logs.stdout&&!e.logs.stderr)return!0;return e.logs.stderr}function Qn(e,t){return e.find((r)=>r.name===t)}function Eh(e){return e[0]}var Go=1,Ea="never",Ta="on_failure",Aa="always",ji,Ui=8080,Sh=10,kh=300,BR=50000,UR=14,Da="127.0.0.1";var dt=I(()=>{ji=["**/node_modules/**","**/.git/**"]});var Nt=".devctl",dr="config.yaml";import{basename as WR}from"path";function Cn(e,t=""){return{name:WR(e),project:t,profile:"",sa:"",audience:"",proxyPort:Ui}}function $a(e,t=Ui){let r=Number(e);return Number.isFinite(r)&&r>0?r:t}function Th(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 rP=Object.defineProperty;var nP=(e)=>e;function oP(e,t){this[e]=nP.bind(null,t)}var sP=(e,t)=>{for(var r in t)rP(e,r,{get:t[r],enumerable:!0,configurable:!0,set:oP.bind(t,r)})};var A=(e,t)=>()=>(e&&(t=e(e=0)),t);var iP=import.meta.require;function fg(e,t=""){if(t==="MetadataLookupWarning")return!0;return e.includes("MetadataLookupWarning")||e.startsWith("received unexpected error =")}function Ba(){if(mg)return;mg=!0;let e=process.emitWarning.bind(process);process.emitWarning=(t,...r)=>{let n=cP(r[0]),o=aP(t);if(fg(o,n))return;e(t,...r)}}function hg(){let e=process.stderr.write.bind(process.stderr);return process.stderr.write=(t,r,n)=>{let o=lP(t);if(fg(o)){let s=typeof r==="function"?r:typeof n==="function"?n:void 0;if(typeof s==="function")s();return!0}if(typeof r==="function")return e(t,r);return e(t,r,n)},()=>{process.stderr.write=e}}function aP(e){if(typeof e==="string")return e;if(e instanceof Error)return e.message;return String(e)}function cP(e){if(typeof e==="string")return e;if(e&&typeof e==="object"&&"type"in e&&typeof e.type==="string")return e.type;return""}function lP(e){if(typeof e==="string")return e;if(e instanceof Uint8Array)return new TextDecoder().decode(e);return""}var mg=!1;import{createHash as dP}from"crypto";import{resolve as uP}from"path";function kr(e){let t=uP(e),r=process.platform==="win32"?t.toLowerCase():t;return dP("sha256").update(r).digest("hex").slice(0,pP)}var pP=16;var Wa=()=>{};function lu(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),s=r.slice(n+1,-1).replace(/-/g,":"),i=new Date(`${o}T${s}Z`);return Number.isNaN(i.getTime())?void 0:i}function xu(e=process.platform){return e==="darwin"}function IP(e=process.platform){return xu(e)?"cmd":"ctrl"}function cs(e,t=process.platform){return`${IP(t)}+${e}`}function LP(e=process.platform){return xu(e)?"command":"ctrl"}function Ht(e,t=process.platform){return`${LP(t)}+${e}`}function fo(e){return e.replace(/\bcmd\b/gi,"command")}function Qr(e,t=process.platform){let r=xu(t),n=Boolean(e.meta||e.super),o=Boolean(e.ctrl);return r?n&&!o:o&&!n}function yn(e=process.platform){return cs("c",e)}function DP(e=process.platform){return{leader:cs("x",e),command_list:cs("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:yn(e)}}function Cg(){return{theme:"devctl",leader_timeout:2000,font_size:14,keybinds:{...OP},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 MP(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 On(e,t){return MP(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 bu=2000,ls=14,kg="DEVCTL_TUI_CONFIG",OP;var Va=A(()=>{OP=DP()});function ke(e,t,r){return{type:e,timestamp:new Date().toISOString(),service:t,payload:r}}class ku{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 Tg="ServiceStarted",ec="ServiceStopped",tc="ServiceFailed",Pg="ServiceStateChanged",Eg="ServiceHealthChanged",us="LogReceived",ps="TokenRefreshed",go="TokenRefreshFailed",Su="AuthenticationChanged",Ag="ProxyStarted",Ig="ProxyStopped",hi="ProxyRequest",rc="ConfigurationChanged",gi="ConfigurationReloadFailed",Lg="SessionRecovered";var Dg=5000,Og=500,Cu=16384,Ru=65536,pr=1e6,_u="TRACE",Tu="DEBUG",Pu="INFO",Eu="WARN",Au="ERROR",Iu="FATAL",yo="UNKNOWN";function nc(e){return e.length>16384?e.slice(0,16384):e}function De(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function mr(e,t=0){if(t>=8)return Mg(e);if(e===void 0)return null;if(typeof e==="string")return nc(e);if(e===null||typeof e==="boolean")return e;if(typeof e==="number")return Number.isFinite(e)?e:null;if(typeof e==="bigint")return Number(e);if(Array.isArray(e))return e.slice(0,32).map((r)=>mr(r,t+1));if(De(e)){let r={},n=Object.entries(e).slice(0,64);for(let[o,s]of n)r[o]=mr(s,t+1);return r}return Mg(e)}function yi(e){if(!De(e))return{};let t={},r=Object.entries(e).slice(0,64);for(let[n,o]of r)t[n]=mr(o);return t}function fr(e){if(e===null)return"null";if(typeof e==="string")return e;if(typeof e==="number"||typeof e==="boolean")return String(e);try{return JSON.stringify(e)}catch{return"[unserializable]"}}function oc(e,t=0){if(t>=8)return fr(e);if(e===null||typeof e==="string"||typeof e==="number"||typeof e==="boolean")return fr(e);if(Array.isArray(e))return e.map((r)=>oc(r,t+1)).join(" ");return Object.entries(e).map(([r,n])=>`${r} ${oc(n,t+1)}`).join(" ")}function Du(e,t=64){let r=[],n=Object.entries(e).slice(0,t);for(let[o,s]of n)r.push(`${o}=${WP(fr(s))}`);return r.join(" ")}function WP(e){if(e===""||/[\s="]/.test(e))return`"${e.replace(/\\/g,"\\\\").replace(/"/g,"\\\"")}"`;return e}function Mg(e){if(typeof e==="string")return nc(e);try{return nc(JSON.stringify(e)??String(e))}catch{return nc(String(e))}}var Ng=8,Lu=64,$g=32;function hs(e){if(e>=bi&&e<=Ou)return Iu;if(e>=Mn)return Au;if(e>=vo)return Eu;if(e>=fs)return Pu;if(e>=vi)return Tu;if(e>=ms)return _u;return yo}function xi(e){let t=e.trim().toUpperCase();if(t===_u||t==="TRACE")return ms;if(t===Tu||t==="DBG"||t==="DEBUG")return vi;if(t===Pu||t==="INFORMATION"||t==="NOTICE"||t==="INFORMATIONAL")return fs;if(t===Eu||t==="WARNING"||t==="WARN")return vo;if(t===Au||t==="ERR"||t==="ERROR")return Mn;if(t===Iu||t==="CRITICAL"||t==="PANIC"||t==="EMERGENCY"||t==="ALERT"||t==="FATAL")return bi;return vt}function Hr(e){if(typeof e==="number"&&Number.isFinite(e))return Hg(e);if(typeof e==="string"&&e.trim()!==""){let t=xi(e);if(t!==vt)return t;let r=Number(e);if(Number.isFinite(r))return Hg(r)}return}function Ug(e){if(typeof e==="number"&&Number.isFinite(e))return Fg(Math.floor(e));if(typeof e==="string"&&e.trim()!==""){let t=e.trim().toUpperCase(),r=Number(t);if(Number.isFinite(r))return Fg(Math.floor(r));let n=t.startsWith("SEVERITY_NUMBER_")?t.slice(16):t,o=/^([A-Z]+?)([2-4])?$/.exec(n);if(o){let s=nE[o[1]??""];if(s!==void 0)return s+(o[2]?Number(o[2])-1:0)}}return vt}function Fg(e){return e>=ms&&e<=Ou?e:vt}function wi(e){return e>=Mn}function jg(e,t){let r=xi(t);if(r===vt)return!0;return(e===vt?fs:e)>=r}function Bg(e){return e===yo?"\u2014":e}function Hg(e){let t=rE[e];if(t!==void 0)return t;if(e>=ms&&e<=Ou)return Math.floor(e);return}function Wg(e){if(e<=JP)return bi;if(e<=XP)return Mn;if(e===QP)return vo;if(e===ZP||e===eE)return fs;if(e===tE)return vi;return}var vt=0,ms=1,vi=5,fs=9,vo=13,Mn=17,bi=21,Ou=24,GP=10,qP=20,KP=30,VP=40,zP=50,YP=60,JP=0,XP=3,QP=4,ZP=5,eE=6,tE=7,rE,nE;var gs=A(()=>{rE={[GP]:ms,[qP]:vi,[KP]:fs,[VP]:vo,[zP]:Mn,[YP]:bi};nE={TRACE:ms,DEBUG:vi,INFO:fs,WARN:vo,ERROR:Mn,FATAL:bi}});function rr(e){return qg(e,32)}function vn(e){return qg(e,16)}function Mu(){return Kg(16)}function ac(){return Kg(8)}function Nu(e){let t=e.trim().split("-");if(t.length!==4)return;let[r,n,o,s]=t;if(r!=="00"||!n||!o||!s)return;if(!rr(n)||!vn(o)||!/^[0-9a-f]{2}$/i.test(s))return;return{traceId:n.toLowerCase(),spanId:ac(),parentSpanId:o.toLowerCase(),traceFlags:Number.parseInt(s,16)}}function cc(e){let t=(e.traceFlags&255).toString(16).padStart(2,"0");return`00-${e.traceId}-${e.spanId}-${t}`}function Gg(e){if(e.traceparent){let n=Nu(e.traceparent);if(n){let o=e.requestId&&e.requestId!==""?e.requestId:n.traceId;return{...n,requestId:o}}}let t=(e.requestId??"").trim(),r=Mu();return{traceId:r,spanId:ac(),traceFlags:1,requestId:t===""?r:t}}function qg(e,t){if(e.length!==t)return!1;return/^[0-9a-f]+$/i.test(e)}function Kg(e){let t=new Uint8Array(e);crypto.getRandomValues(t);let r="";for(let n of t)r+="0123456789abcdef"[n>>4&15],r+="0123456789abcdef"[n&15];return r}var sc=32,ic=16,Wt="devctl.request_id";function ys(e){if(!De(e))return mr(e);if(typeof e.stringValue==="string")return e.stringValue;if(e.intValue!==void 0&&e.intValue!==null){let n=Number(e.intValue);if(Number.isSafeInteger(n))return n;return typeof e.intValue==="string"?e.intValue:String(e.intValue)}if(typeof e.doubleValue==="number")return e.doubleValue;if(typeof e.boolValue==="boolean")return e.boolValue;if(typeof e.bytesValue==="string")return e.bytesValue;let t=De(e.arrayValue)?e.arrayValue.values:void 0;if(Array.isArray(t))return t.map((n)=>ys(n));let r=De(e.kvlistValue)?e.kvlistValue.values:void 0;if(Array.isArray(r))return Nn(r);return mr(e)}function $u(e){if(!Array.isArray(e)||e.length===0)return!1;return e.every((t)=>De(t)&&typeof t.key==="string"&&t.value!==void 0)}function Nn(e){let t={};if(!$u(e))return t;for(let r of e){let n=r.key;if(typeof n==="string"&&n!=="")t[n]=ys(r.value)}return t}function Fu(e,t){let r=De(e)?Nn(e.attributes):{},n=typeof r["service.name"]==="string"&&r["service.name"]!==""?r["service.name"]:t;return{...r,"service.name":n}}function Hu(e){if(!De(e))return;let t=typeof e.name==="string"?e.name:"";if(t==="")return;let r=typeof e.version==="string"&&e.version!==""?e.version:void 0;return{name:t,version:r}}function Zr(e){if(typeof e==="number"&&Number.isFinite(e)&&e>0)return e;if(typeof e==="string"&&e.trim()!==""){let t=Number(e);if(Number.isFinite(t)&&t>0)return t}return}var lc=()=>{};function Yg(e){let t=e/pr,r=new Date(t);if(Number.isNaN(r.getTime()))return new Date(0).toISOString();return r.toISOString()}function Jg(e){let t=Date.parse(e);if(Number.isNaN(t))return Date.now()*pr;return t*pr}function Gt(e){return iE(e.body,e.attributes)}function iE(e,t){let r=aE(e);if(typeof e==="string"&&e!==""){let s=Vg(t,e);return s===""?e:`${e} ${s}`}if(r!=="")return r;let n=Vg(t,""),o=Du(Qg(t,dE()),zg);if(n!==""&&o!=="")return`${n} ${o}`;if(n!=="")return n;return o}function Xg(e,t){let r={};if(e!==""&&e!==null)r.body=e;if(Object.keys(t).length>0)r.attributes=t;try{return JSON.stringify(r,null,2)}catch{return fr(e)}}function Si(e){return[Gt(e),oc(e.attributes),e.raw??"",e.service,e.traceId??"",e.spanId??"",e.identity??"",vs(e)].join(" ")}function vs(e){let t=e.attributes[Wt];if(typeof t==="string"&&t!=="")return t;return e.traceId??""}function aE(e){if(typeof e==="string")return e;if(e===null)return"";if(typeof e==="number"||typeof e==="boolean")return String(e);if(Array.isArray(e))return fr(e);return Du(Qg(e),zg)}function Qg(e,t=new Set){let r={};for(let[n,o]of Object.entries(e))if(cE(n,o,t))r[n]=o;return r}function cE(e,t,r){if(r.has(e)||sE.has(e))return!1;if(t===null||t==="")return!1;if(typeof t==="object")return!1;if(typeof t==="string"&&(t.length>oE||rr(t)||vn(t)))return!1;return!0}function Uu(e){return{method:dc(e,Zg),route:dc(e,ey),status:dc(e,ty),namespaced:lE.some((t)=>dc(e,[t])!==void 0)}}function Vg(e,t){let{method:r,route:n,status:o,namespaced:s}=Uu(e),i=[r,n,o].filter((c)=>c!==void 0&&c!=="");if(!s&&i.length<2)return"";return i.filter((c)=>t===""||!t.includes(c)).join(" ")}function dE(){return new Set([...Zg,...ey,...ty])}function dc(e,t){for(let r of t){let n=e[r];if(typeof n==="string"&&n!=="")return n;if(typeof n==="number"&&Number.isFinite(n))return String(n)}return}var zg=6,oE=40,sE,Zg,ey,ty,lE;var ki=A(()=>{sE=new Set(["shape","seq","pid","ppid","tid","time","ts","timestamp","@timestamp","timeUnixNano","observedTimeUnixNano","trace_id","traceId","span_id","spanId","traceparent","ecs.version","version","host","facility","logger","caller","name","level","lvl","severity","severityText","severityNumber","severity_text",Wt]);Zg=["http.request.method","http.method","method","request_method"],ey=["http.route","url.path","path","route","request_uri"],ty=["http.status_code","http.response.status_code","status","status_code"],lE=["http.request.method","http.method","http.route","url.path","http.status_code","http.response.status_code"]});class Rr{nameMarkers;patterns;constructor(e,t){this.nameMarkers=[...ry,...e],this.patterns=t.flatMap((r)=>{try{return[new RegExp(r)]}catch{return[]}})}update(e,t){this.nameMarkers=[...ry,...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 uE(t)}}function uE(e){let t=e.toLowerCase(),r="authorization: bearer ",n=t.indexOf("authorization: bearer ");if(n>=0)return ny(e,n+22);let o=t.indexOf("bearer ");if(o<0)return e;return ny(e,o+7)}function ny(e,t){let r=t;while(r<e.length&&!pE(e[r]??""))r+=1;return e.slice(0,t)+"********"+e.slice(r)}function pE(e){return e===" "||e==="\t"||e===`
|
|
4
|
+
`||e==="\r"}var ry,uc="********";var bo=A(()=>{ry=["PASSWORD","SECRET","TOKEN","PRIVATE_KEY","CLIENT_SECRET","API_KEY","CREDENTIAL","ACCESS_KEY","AUTH_KEY"]});function fE(e,t){if(t==="")return!1;if(e.isSecretName(t))return!0;let r=t.split(/[./:_-]/).filter((n)=>n!=="").pop()??t;return e.isSecretName(r)||mE.has(r.toLowerCase())}function pc(e,t,r="",n=0){if(n>=Ng)return uc;if(fE(e,r))return uc;if(typeof t==="string")return e.redactText(t);if(t===null||typeof t==="number"||typeof t==="boolean")return t;if(Array.isArray(t))return t.slice(0,$g).map((i)=>pc(e,i,r,n+1));let o={},s=Object.entries(t).slice(0,Lu);for(let[i,a]of s)o[i]=pc(e,a,i,n+1);return o}function Ci(e,t){let r={},n=Object.entries(t).slice(0,Lu);for(let[o,s]of n)r[o]=pc(e,s,o);return r}function mc(e,t){return{...t,body:pc(e,t.body),attributes:Ci(e,t.attributes),resource:{...Ci(e,t.resource),"service.name":t.resource["service.name"]},raw:t.raw===void 0?void 0:e.redactText(t.raw),identity:t.identity}}function fc(e,t){return{...t,attributes:Ci(e,t.attributes),events:t.events.map((r)=>({...r,attributes:Ci(e,r.attributes)})),resource:{...Ci(e,t.resource),"service.name":t.resource["service.name"]},status:{...t.status,message:t.status.message===void 0?void 0:e.redactText(t.status.message)}}}var mE;var ju=A(()=>{bo();mE=new Set(["authorization","password","passwd","cookie","set-cookie","api_key","apikey","token","secret","access_token","refresh_token","private_key","client_secret"])});function Bu(e,t,r){let n=e.timestamp&&e.timestamp!==""?e.timestamp:Yg(t.timeUnixNano??e.timeUnixNano??Date.now()*1e6),o=t.timeUnixNano??e.timeUnixNano??Jg(n),s=hE(e,t),i={...yi(t.attributes??{}),...yi(e.attributes??{})},a=e.request_id||t.request_id;if(a&&i[Wt]===void 0)i[Wt]=a;let c=gE(e,t),l=(e.severityText||e.level||t.severityText||t.level||(c===vt?yo:hs(c))).toUpperCase(),d=yE(e.traceId||t.traceId||(a&&rr(a)?a:void 0)),u=vE(e.spanId||t.spanId),m=e.service,p=e.pid,f=e.resource??t.resource??{"service.name":m,...p>0?{"process.pid":p}:{}};if(f["service.name"]===void 0)f["service.name"]=m;return{seq:r,timeUnixNano:o,observedTimeUnixNano:t.observedTimeUnixNano??e.observedTimeUnixNano,severityNumber:c,severityText:l,body:mr(s),attributes:i,traceId:d,spanId:u,traceFlags:e.traceFlags??t.traceFlags,resource:f,scope:e.scope??t.scope,service:m,source:e.source,raw:t.raw??e.raw,timestamp:n,stream:e.stream,identity:e.identity}}function hE(e,t){if(e.body!==void 0)return e.body;if(t.body!==void 0)return t.body;if(t.message!==void 0)return t.message;return e.message??""}function gE(e,t){if(e.severityNumber!==void 0&&e.severityNumber!==vt)return e.severityNumber;let r=e.severityText||e.level;if(r){let o=xi(r);if(o!==vt)return o}if(t.severityNumber!==void 0&&t.severityNumber!==vt)return t.severityNumber;let n=t.severityText||t.level;if(n)return xi(n);return vt}function yE(e){if(!e)return;return rr(e)?e.toLowerCase():e}function vE(e){if(!e)return;return vn(e)?e.toLowerCase():e}var oy=A(()=>{ki();gs()});function cy(){return{name:"default",parse:(e)=>qu(e)}}function ly(e){if(e.length<=Cu)return e;return e.slice(0,Cu)}function qu(e){let t=Ku(e);if(t)return t;return{body:e,attributes:{},severityNumber:ay(e),severityText:hs(ay(e)),request_id:OE(e)||void 0,raw:e}}function Ku(e){let t=wE(e);if(!t)return;let r;try{r=JSON.parse(t.json)}catch{return}if(!De(r))return;return SE(r,t.raw)??RE(r,t.raw)??_E(r,t.raw)??TE(r,t.raw)}function wE(e){let t=e.trim();if(t.length>Ru)return;if(t.startsWith("{")&&t.endsWith("}"))return{json:t,raw:t};let r=t.indexOf("{"),n=t.lastIndexOf("}");if(r<=0||n<=r)return;let o=t.slice(r,n+1);if(o.length>Ru)return;try{let s=JSON.parse(o);if(!De(s))return}catch{return}return{json:o,raw:t}}function SE(e,t){if(!kE(e))return;let r=Nn(e.attributes),n=e.body===void 0?CE(r):ys(e.body),o=Hr(e.severityNumber)??Hr(e.severityText)??Hr(e.severity_text),s=vc(e,hc,sc)??iy(r,hc),i=vc(e,gc,ic)??iy(r,gc);return{body:n,attributes:r,severityNumber:o,severityText:typeof e.severityText==="string"?e.severityText:void 0,traceId:s,spanId:i,timeUnixNano:Zr(e.timeUnixNano),observedTimeUnixNano:Zr(e.observedTimeUnixNano),raw:t}}function kE(e){if(e.timeUnixNano!==void 0||e.observedTimeUnixNano!==void 0)return!0;if(De(e.body)&&(e.body.stringValue!==void 0||e.body.intValue!==void 0||e.body.doubleValue!==void 0||e.body.boolValue!==void 0))return!0;return $u(e.attributes)}function CE(e){return dy(e)??""}function RE(e,t){let r=wo(e.metric_name);if(r===void 0)return;let n=wo(e.metric_type),o=Ri(e.value);if(n===void 0&&o===void 0)return;let s=wo(e.unit),i=o===void 0?void 0:s===void 0?String(o):`${o} ${s}`,a=De(e.attributes)?yi(e.attributes):{},c=zu([r,n,i,dy(a)]),{attributes:l}=Vu(e,["metric_name","metric_type","value","unit","attributes"]);return{body:c,attributes:{...l,...a},raw:t}}function _E(e,t){if(yc(e,Gu)!==void 0)return;let r=wo(e.request),n=r===void 0?{}:IE(r),o=wo(xo(e,["request_method","method","http_method","verb"]))??n.method,s=wo(xo(e,["request_uri","uri","url","path","request_path"]))??n.uri,i=Ri(xo(e,["status","status_code","statusCode","http_status"])),a=wo(xo(e,["remote_ip","remote_addr","client_ip"]));if(o===void 0||s===void 0)return;if(i===void 0&&a===void 0&&r===void 0)return;let c=Ri(xo(e,["body_bytes_sent","bytes_sent","bytes"])),l=Ri(xo(e,["request_time","duration"])),d=Ri(xo(e,["request_time_ms","latency_ms","duration_ms"])),u=c===void 0?void 0:`${c}B`,m=d!==void 0?`${d}ms`:l===void 0?void 0:`${l}s`,{attributes:p,traceId:f,spanId:h,requestId:g,severityNumber:x}=Vu(e,[]);return{body:zu([a,o,s,i===void 0?void 0:String(i),u,m]),attributes:p,severityNumber:x??(i===void 0?void 0:AE(i)),traceId:f,spanId:h,request_id:g,raw:t}}function TE(e,t){let r=Vu(e,[]),n=uy(e,Gu);return{body:n!==void 0?mr(n):e.body!==void 0&&e.body!==null?mr(e.body):mr(r.remainder),attributes:r.attributes,severityNumber:r.severityNumber,severityText:r.severityText,traceId:r.traceId,spanId:r.spanId,request_id:r.requestId,timeUnixNano:r.timeUnixNano,raw:t}}function Vu(e,t){let r=new Set([...Gu,...Wu,...hc,...gc,...sy,"traceparent","trace_flags","traceFlags","timeUnixNano","observedTimeUnixNano","timestamp","time","@timestamp","ts","body",...t]),n=typeof e.short_message==="string",o=PE(e,n),s=yc(e,Wu),i=typeof e.traceparent==="string"?Nu(e.traceparent):void 0,a=vc(e,hc,sc)??i?.traceId,c=vc(e,gc,ic)??i?.parentSpanId,l=yc(e,sy),d=Zr(e.timeUnixNano)??EE(e),u={},m={};for(let[p,f]of Object.entries(e))if(!r.has(p)){let h=mr(f);u[p]=h,m[p]=h}if(l&&m[Wt]===void 0)m[Wt]=l;return{attributes:m,remainder:u,severityNumber:o,severityText:s,traceId:a,spanId:c,requestId:l,timeUnixNano:d}}function PE(e,t){for(let r of Wu){let n=e[r];if(n!==void 0&&n!==null){if(t&&typeof n==="number"){let s=Wg(n);if(s!==void 0)return s}let o=Hr(n);if(o!==void 0)return o}}return}function EE(e){for(let t of["timestamp","time","@timestamp","ts"]){let r=e[t];if(typeof r==="string"&&r!==""){let n=Date.parse(r);if(!Number.isNaN(n))return n*1e6}if(typeof r==="number"&&Number.isFinite(r))return r>1000000000000000?r:r*pr}return}function dy(e){let{method:t,route:r,status:n}=Uu(e),o=zu([t,r,n]);return o===""?void 0:o}function AE(e){if(e>=xE)return Mn;if(e>=bE)return vo;return}function IE(e){let t=e.trim().split(/\s+/);return{method:t[0],uri:t[1]}}function yc(e,t){for(let r of t){let n=e[r];if(typeof n==="string"&&n.trim()!=="")return n}return}function uy(e,t){for(let r of t)if(e[r]!==void 0&&e[r]!==null)return e[r];return}function xo(e,t){return uy(e,t)}function vc(e,t,r){let n=yc(e,t);if(!n)return;if(r===sc&&rr(n))return n.toLowerCase();if(r===ic&&vn(n))return n.toLowerCase();return n}function iy(e,t){for(let r of t){let n=e[r];if(typeof n==="string"&&n!=="")return n}return}function wo(e){if(typeof e==="string"&&e.trim()!=="")return e;if(typeof e==="number"&&Number.isFinite(e))return String(e);return}function Ri(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 zu(e){return e.filter((t)=>t!==void 0&&t!=="").join(" ")}function OE(e){return LE.exec(e)?.[1]??""}function ay(e){return DE.find((r)=>r.re.test(e))?.level??vt}var Gu,Wu,hc,gc,sy,bE=400,xE=500,LE,DE;var py=A(()=>{lc();ki();gs();Gu=["message","msg","text","log","event","short_message","shortMessage"],Wu=["level","severity","severityText","severity_text","levelname","loglevel","lvl"],hc=["trace_id","traceId","traceid"],gc=["span_id","spanId","spanid"],sy=["request_id","requestId","correlation_id","correlationId",Wt];LE=/(?:x-devctl-request-id|request[_-]?id)[=: ]+([A-Za-z0-9-]+)/i,DE=[{re:/\b(fatal|critical)\b/i,level:Hr("FATAL")??vt},{re:/\b(error|err)\b/i,level:Hr("ERROR")??vt},{re:/\b(warn|warning)\b/i,level:Hr("WARN")??vt},{re:/\b(debug|dbg)\b/i,level:Hr("DEBUG")??vt},{re:/\b(trace)\b/i,level:Hr("TRACE")??vt},{re:/\b(info|information)\b/i,level:Hr("INFO")??vt}]});function _i(e){let t=ME(e);if(t===void 0)return;return new RegExp(t)}function ME(e){if(e.length===0||e.length>200)return;let t={raw:e,i:0,out:[],quantifiers:0,depth:0,lastAtom:"none",groupQuant:[]};if(!my(t)||t.i!==e.length||t.depth!==0)return;return t.out.join("")}function my(e){while(e.i<e.raw.length){if(e.raw[e.i]===")")return!0;if(!NE(e))return!1}return!0}function NE(e){let t=e.raw[e.i]??"";if(t==="|")return bc(e,"|","none");if(t==="^")return bc(e,"^","none");if(t==="$")return bc(e,"$","none");if(t===".")return bc(e,".","atom");if(t==="(")return $E(e);if(t==="[")return HE(e);if(t==="*"||t==="+"||t==="?"||t==="{")return FE(e);if(t==="\\")return fy(e);return e.out.push(RegExp.escape(t)),e.i+=1,e.lastAtom="atom",!0}function bc(e,t,r){return e.out.push(t),e.i+=1,e.lastAtom=r,!0}function $E(e){if(e.i+=1,e.depth+=1,e.depth>2)return!1;if(e.groupQuant.push(!1),e.out.push("(?:"),!my(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 FE(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 HE(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(!fy(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 fy(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 bs(e,t){if(e.services&&e.services.length>0&&!e.services.includes(t.service))return!1;if(e.level&&!jg(t.severityNumber,e.level))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;if(e.traceId&&t.traceId!==e.traceId)return!1;if(e.requestId&&vs(t)!==e.requestId&&t.traceId!==e.requestId)return!1;if(e.attribute){let r=t.attributes[e.attribute.key];if(fr(r??null)!==e.attribute.value)return!1}return!0}function Yu(e){if(!e.search)return()=>!0;if(e.regex){let r=_i(e.search);if(r)return(n)=>r.test(Si(n))}let t=e.search.toLowerCase();return(r)=>Si(r).toLowerCase().includes(t)}function Ju(e){let t=Yu(e);return(r)=>bs(e,r)&&t(r)}var hy=A(()=>{ki();gs()});function gy(e){if(!Number.isInteger(e)||(e??0)<=0)return Og;return Math.min(e,Dg)}var yy=()=>{};var en=A(()=>{gs();lc();ki();ju();oy();py();hy();yy()});function R(e,t){return new Ze(e,t)}function xe(e,t,r){let n=r instanceof Error?r:Error(String(r));return new Ze(e,t,{cause:n})}function Ae(e,t,r,n=""){return new Ze(e,t,{hint:r,service:n})}function Sc(e,t){return e instanceof Ze&&e.kind===t}function Iy(e){if(e instanceof Ze)return e.exitCode();return 1}function Ly(e){if(e instanceof Ze)return{error:I(e),kind:e.kind,hint:e.hint,service:e.service};return{error:I(e)}}function Dy(e){if(typeof e==="string")return R("general",e);if(e.kind)return new Ze(e.kind,e.error.replace(`${e.kind}: `,""),{hint:e.hint,service:e.service});return R("general",e.error)}function I(e){if(e instanceof Ze){if(e.hint!=="")return`${Py(e)} \u2014 ${e.hint}`;return Py(e)}if(e instanceof Error)return e.message;return String(e)}function Py(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 Ey=1,ee="configuration",ko="configuration_missing",xs="service_not_found",Ay="dependency",qe="process_start",$n="authentication",Co="authorization",Ur="token",xc="impersonation",wc="iap",nr="proxy",op="health_check",Qe="general",Ze;var ne=A(()=>{Ze=class Ze 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 Pc(e){return e>0?e:300}function ap(){return{enabled:!1,paths:[],debounce_ms:300,ignore:[...Pi]}}function cp(){return{enabled:void 0,host:"",port:""}}function Ei(){return{type:"",identity:{type:"",service_account:""},audience:"",service_account:"",client_id:"",client_secret:"",credentials:"",headers:{}}}function _o(e){return(e.transport??"").toLowerCase()==="grpc"}function Ss(){return{args:[],shell:!1}}function lp(){return{vars:{},required:[],defaults:{}}}function dp(){return{type:"",url:"",address:"",command:Ss(),interval_seconds:0,timeout_seconds:0,start_period_seconds:0,unhealthy_threshold:3,healthy_reset_threshold:10}}function or(e){return typeof e==="string"?e:e.service}function Fn(e){return typeof e==="string"?"service_started":e.condition||"service_started"}function Ec(e){return Fn(e)==="service_healthy"?`${or(e)} (healthy)`:or(e)}function up(){return{type:"",mode:"",service_account:"",config:{}}}function Hn(){return{extends:"",description:"",command:Ss(),shell:!1,working_dir:"",dependencies:[],ports:[],environment:lp(),health:dp(),identity:up(),logs:{stdout:!1,stderr:!1},restart:{policy:"",max_retries:0,backoff_seconds:0},startup:{wait_for_healthy:!1,timeout_seconds:0},capabilities:[],proxy:[],expose:cp(),container:void 0,watch:ap(),hooks:{pre_start:Ss(),post_start:Ss()}}}function To(){return{version:1,project:{name:""},google:{project_id:"",region:""},profiles:{},templates:{},services:{},tasks:{},proxy:{enabled:!1,gateway:!1,credentials:"",listen:{host:jr,port:0},token_endpoint:{enabled:!1,host:"",port:0},routes:[]},logs:{max_memory_events:uA,persistence:{enabled:!0,directory:"~/.devctl/logs",retention_days:pA,max_session_logs:0}},telemetry:{otlp:{enabled:!1,listen:{host:jr,port:dA}}},auth:{refresh_threshold_seconds:$y},shutdown:{grace_seconds:Ny},ui:{theme:"system",keymap:{}},secrets:{extra_markers:[],extra_patterns:[]},doctor:{tools:[]},plugins:[],environment:{sources:[],secrets:{}},provenance:{},repoRoot:"",configPath:""}}function Un(e){return e.args.length===0||e.args.length===1&&(e.args[0]??"").trim()===""}function Ii(e){if(e.type!=="")return e.type;return e.mode}function xn(e){let t=Ii(e).toLowerCase();return t==="service"||t==="service_account"}function Fy(e){let t=Ii(e).toLowerCase();return t==="user"||t===""}function Ac(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 jn(e){let t=e.host===""?jr:e.host;if(e.port===0)return t;return`${t}:${e.port}`}function Hy(e){if(e.refresh_threshold_seconds<=0)return $y;return e.refresh_threshold_seconds}function Ic(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 Ny;return e.grace_seconds}function Uy(e){if(!e.logs.stdout&&!e.logs.stderr)return!0;return e.logs.stdout}function jy(e){if(!e.logs.stdout&&!e.logs.stderr)return!0;return e.logs.stderr}function Po(e,t){return e.find((r)=>r.name===t)}function By(e){return e[0]}var ks=1,Rc="never",_c="on_failure",Tc="always",Pi,dA=4318,Ai=8080,Ny=10,$y=300,uA=50000,pA=14,jr="127.0.0.1";var lt=A(()=>{Pi=["**/node_modules/**","**/.git/**"]});var qt=".devctl",_r="config.yaml";import{basename as mA}from"path";function Wn(e,t=""){return{name:mA(e),project:t,profile:"",sa:"",audience:"",proxyPort:Ai}}function Lc(e,t=Ai){let r=Number(e);return Number.isFinite(r)&&r>0?r:t}function Wy(e){let t=e;return`# yaml-language-server: $schema=https://raw.githubusercontent.com/amr-m-abdelgawad/devctl/main/schema/devctl.config.schema.json
|
|
4
5
|
version: 1
|
|
5
6
|
|
|
6
7
|
project:
|
|
@@ -47,8 +48,8 @@ auth:
|
|
|
47
48
|
shutdown:
|
|
48
49
|
stop_services_on_exit: true
|
|
49
50
|
grace_seconds: 10
|
|
50
|
-
`}var
|
|
51
|
-
`)}var
|
|
51
|
+
`}var Tt;var Dc=A(()=>{lt();Tt=[{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 fp,mkdirSync as pp,statSync as fA,writeFileSync as hA}from"fs";import{createInterface as gA}from"readline/promises";import{stdin as yA,stdout as vA}from"process";import{basename as Oc,dirname as mp,join as wn,resolve as bA}from"path";function hp(e,t){let r=e===""?process.cwd():e;if(t==="")return{repo:r,cfgPath:wn(r,qt,_r)};let n=bA(t);if(!(fp(n)?fA(n).isDirectory():!/\.ya?ml$/i.test(n))){let s=mp(n);return{repo:Oc(s)===qt?mp(s):s,cfgPath:n}}if(Oc(n)===qt)return{repo:mp(n),cfgPath:wn(n,_r)};return{repo:n,cfgPath:wn(n,qt,_r)}}function Gy(e,t=Oc(e),r="",n="",o=!1){let s=wn(e,qt,_r);if(!o&&fp(s))throw R(ee,`configuration already exists at ${s}; not overwriting`);return Mc(e,{...Wn(e,r),name:t,profile:n}),s}async function qy(e,t,r="",n=!1){let{repo:o,cfgPath:s}=hp(t,r);if(!n&&fp(s)){Kt(`Found existing configuration at ${s}; nothing written.`),Kt("Pass --force to overwrite it, or edit it directly.");return}let i=gA({input:yA,output:vA}),a=async(h,g)=>{let x=g!==""?` [${g}]`:"",y=(await i.question(`${h}${x}: `)).trim();return y===""?g:y},c=0,l=()=>(c+=1,c);if(Kt(`devctl setup \u2014 ${Tt.length} steps`),Kt(""),r==="")await a(`${l()}. Repository root`,o);let d=Wn(o,""),u=await a(`${l()}. ${Tt[1]?.prompt}`,Oc(o));d.name=u;let m=await e.detectGoogle(""),p=await a(`${l()}. ${Tt[2]?.prompt}`,m.projectID);if(d.project=p,Kt(`${l()}. ${Tt[3]?.title}`),!m.adcAvailable){if(Kt(" 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(g){Kt(` login failed: ${g instanceof Error?g.message:String(g)}`)}}else if(Kt(" \u2713 Application Default Credentials available"),m.userEmail!=="")Kt(` user: ${m.userEmail}`);d.sa=await a(`${l()}. ${Tt[4]?.prompt}`,""),d.audience=await a(`${l()}. ${Tt[5]?.prompt}`,"");let f=await a(`${l()}. ${Tt[6]?.prompt}`,String(Ai));d.proxyPort=Lc(f),d.profile=await a(`${l()}. ${Tt[7]?.prompt}`,""),i.close(),Mc(o,d),Kt(""),Kt(`Wrote starter configuration to ${s}`),Kt(`${l()}. Validation`);try{let h=e.load(o,""),g=await e.runDoctor.execute(h);Kt(e.formatDoctor(g))}catch(h){Kt(` configuration is not valid yet: ${h instanceof Error?h.message:String(h)}`),Kt(" edit .devctl/config.yaml and run `devctl config validate`")}}function Mc(e,t){let r=wn(e,".devctl");pp(wn(r,"services"),{recursive:!0}),pp(wn(r,"profiles"),{recursive:!0}),pp(wn(r,"proxy"),{recursive:!0}),hA(wn(r,"config.yaml"),Wy(t))}function Kt(e){process.stdout.write(`${e}
|
|
52
|
+
`)}var Nc=A(()=>{lt();Dc();ne()});function jv(){return/\$\{(?:env\.)?([A-Za-z_][A-Za-z0-9_]*)\}/g}function Ap(e){return[...e.matchAll(jv())].map((t)=>t[1]??"")}function Bv(e,t){let r=[];return{value:e.replace(jv(),(o,s)=>{let i=t[s];if(i===void 0||i==="")return r.push(s),"";return i}),missing:r}}function Wc(e){let t=e.trim();if(t===""||Ap(t).length===0)return;return t}function Wv(e){return/^\$\{(?:env\.)?[A-Za-z_][A-Za-z0-9_]*\}$/.test(e.trim())}function qn(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:D0(e,t),...n,shadowed:r.slice(0,-1)}]})}function D0(e,t){let r=e;for(let n of t.split(".")){if(typeof r!=="object"||r===null)return;r=r[n]}return r}function Kc(e){if(xn(e))return Tr({kind:Oo,serviceAccount:e.service_account});if(Fy(e)&&(e.type!==""||e.mode!==""))return Tr({kind:qc});let t=(e.type||e.mode).toLowerCase();if(t!=="")return Tr({kind:t,providerConfig:e.config??{}});return Tr({kind:Ts})}function Cn(e){if(e.type.toLowerCase()==="none")return Tr({kind:Ts});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 Tr({kind:Oo,serviceAccount:r});if(e.type.toLowerCase()==="iap"&&t==="")return Tr({kind:Ts});if(e.type===""&&t==="")return Tr({kind:Ts});return Tr({kind:qc})}function Zv(e){return e.kind===qc||e.kind===Oo}function Kn(e){if(e.tokenKey!=="")return e.tokenKey;if(e.kind===Oo)return`sa:${e.serviceAccount}`;if(e.email!=="")return`user:${e.email}`;return"user"}function Tr(e={}){return{kind:Ts,email:"",serviceAccount:"",project:"",projectSource:"",adcAvailable:!1,providerConfig:{},tokenKey:"",...e}}function Op(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(xn(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(),s=n.auth.identity.type.toLowerCase(),i=n.auth.identity.service_account||n.auth.service_account;if(s==="service"||s==="service_account"||o==="service_account")r(i,o!=="none")}return[...t.entries()].map(([n,o])=>({email:n,active:o})).sort((n,o)=>n.email.localeCompare(o.email))}function rn(e){return Op(e).filter((t)=>t.active).map((t)=>t.email)}function eb(e,t,r){let n=[];for(let o of t){let s=e.services[o];if(!s)continue;let i=Kc(s.identity);if(xn(s.identity)&&s.identity.service_account===""){n.push({name:o,message:"service account identity is not configured"});continue}if((Zv(i)||s.capabilities.some((c)=>c==="google_api"||c==="iap"||c==="service_identity"))&&!r)n.push({name:o,message:"ADC unavailable"})}return n}function $i(e){if(rn(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(Zv(Kc(t.identity)))return!0;return t.capabilities.some((r)=>r==="google_api"||r==="iap"||r==="service_identity")})}function Mp(){return{name:"user",accepts:(e)=>!xn(e),resolve:async(e,t)=>{if(e.type===""&&e.mode==="")return Tr({kind:Ts});return{...await t(),kind:qc}}}}function Np(){return{name:"service_account",accepts:(e)=>xn(e),resolve:async(e,t)=>{if(e.service_account==="")throw R(xc,"service account identity is not configured");return{...await t(),kind:Oo,serviceAccount:e.service_account}}}}async function tb(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(xn(e))return Np().resolve(e,t);return Mp().resolve(e,t)}var qc="user",Oo="service_account",Ts="none";var Wr=A(()=>{lt();ne()});function hr(){return"devctl 0.7.0"}var Vt="0.7.0",Bi=1;function xb(e){return e}function Pr(e){return e}function Ls(e){if(e.state===Ke&&e.health===zt)return Er;if(e.state===Ke&&e.health===$e)return nn;return e.state}function qi(e){return{name:e,state:Ot,health:Mt,pid:0,ports:{},restarts:0,last_error:"",identity:"",startTime:void 0,cpuPercent:void 0,memoryKB:void 0,profile:"",env_source:"daemon",orphaned:!1}}function kb(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=or(l);if(!t[d])continue;n[d]=[...n[d]??[],a],r[a]=(r[a]??0)+1}}let o=[],s=Object.keys(t).length,i={};while(s>0){let a=Object.entries(r).filter(([c,l])=>l===0&&!i[c]).map(([c])=>c).sort();if(a.length===0)throw R(Ay,"dependency cycle detected while planning startup");o.push(a);for(let c of a){i[c]=!0,s-=1;for(let l of n[c]??[])r[l]=(r[l]??0)-1}}return o}function Yp(e,t){if(!e.services[t])throw R(xs,`unknown service "${t}"`)}function nl(e,t,r){let n={},o=(a)=>{if(n[a])return;Yp(e,a),n[a]=!0;for(let c of e.services[a]?.dependencies??[])o(or(c))};for(let a of t)o(a);let s=kb(e,n),i=[];return s.forEach((a,c)=>{for(let l of a)i.push({name:l,wave:c+1,dependencies:[...e.services[l]?.dependencies??[]]})}),{profile:r,steps:i,waves:s,blockers:[]}}function Ho(e,t){let r={};for(let[s,i]of Object.entries(e.services))for(let a of i.dependencies){let c=or(a);r[c]=[...r[c]??[],s]}let n={},o=(s)=>{if(n[s])return;Yp(e,s),n[s]=!0;for(let i of r[s]??[])o(i)};for(let s of t)o(s);return Object.keys(n)}function Cb(e,t){let r=[...kb(e,t)].reverse().map((o)=>[...o].sort()),n=[];return r.forEach((o,s)=>{for(let i of o)n.push({name:i,wave:s+1,dependencies:[...e.services[i]?.dependencies??[]]})}),{steps:n,waves:r,profile:"",blockers:[]}}function ol(e,t){let r=Object.fromEntries(Ho(e,t).map((n)=>[n,!0]));return Cb(e,r)}function sl(e,t){let r={};for(let n of t)Yp(e,n),r[n]=!0;return Cb(e,r)}function Rb(e){let t="";if(e.profile!=="")t+=`Profile: ${e.profile}
|
|
52
53
|
|
|
53
54
|
`;t+=`Plan:
|
|
54
55
|
|
|
@@ -56,8 +57,7 @@ shutdown:
|
|
|
56
57
|
`,r+=1;if((e.blockers??[]).length>0){t+=`
|
|
57
58
|
Blocked:
|
|
58
59
|
`;for(let n of e.blockers??[])t+=`- ${n.name}: ${n.message}
|
|
59
|
-
`}return t}function
|
|
60
|
-
`||e==="\r"}var qy;var si=I(()=>{qy=["PASSWORD","SECRET","TOKEN","PRIVATE_KEY","CLIENT_SECRET","API_KEY","CREDENTIAL","ACCESS_KEY","AUTH_KEY"]});var Ln;var pp=I(()=>{Ln={procedure:`---
|
|
60
|
+
`}return t}function il(e){return`${e.join(", ")} changed and only take effect after a full \`devctl down && devctl start\`, not a reload`}function MI(e,t,r){let n={};if(t!==""){let o=e.profiles[t];if(!o)throw R("configuration",`unknown profile "${t}"`);Object.assign(n,o.environment)}if(r.length>0)return{services:wb(e,r),env:n};if(t==="")return{services:Object.keys(e.services),env:n};return{services:wb(e,e.profiles[t]?.services??[]),env:n}}function NI(e){let t=Object.keys(e.profiles).sort();return Pr(t[0]??"")}function Uo(e,t){if(t==="")return{};return{...e.profiles[Pr(t)]?.environment??{}}}function al(e,t){let r=t.services??[],n=t.profile??Pr("");if(r.length===0&&n===""){if(n=t.activeProfile||NI(e),n==="")throw R(ee,"no profile or services given; pass service names or add a profile")}let o=MI(e,n,r);return{services:o.services,env:o.env,profile:n}}function wb(e,t){let r={},n=[];for(let o of t){if(r[o])continue;if(!e.services[o])throw R("service_not_found",`unknown service "${o}"`);r[o]=!0,n.push(o)}return n}var Sb="UNKNOWN",Rn="STARTING",Ke="RUNNING",Er="HEALTHY",nn="UNHEALTHY",Gr="STOPPING",Ot="STOPPED",bt="FAILED",Ar="RESTARTING",Mt="UNKNOWN",zt="HEALTHY",$e="UNHEALTHY";var Nt=A(()=>{lt();ne()});function Qb(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 Zb(e){let t=Jb(e.scriptPath),r=Jb(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=uL();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 ex(e,t){let r=Xb(e),n=Xb(t);for(let o=0;o<3;o+=1){let s=(r[o]??0)-(n[o]??0);if(s!==0)return s>0?1:-1}return 0}function uL(){return[process.platform==="win32"?"npm.cmd":"npm","install","--global","@amr-m-abdelgawad/devctl@latest"]}function Jb(e){return e.replaceAll("\\","/")}function Xb(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 fl="restart the daemon with `devctl down` then start again";function Yn(e){if(!Number.isInteger(e))return Tm;return Math.min(ZD,Math.max(eO,e))}function Jx(e,t){if(t.length!==1||t<"0"||t>"9")return e;if(e.length>=tO)return t;return`${e}${t}`}function Xx(e){return e.slice(0,-1)}function Qx(e,t){if(e==="")return Yn(t);let r=Number(e);if(!Number.isInteger(r))return Yn(t);return Yn(r)}function Go(e){let t=Number.parseInt(kr(e).slice(0,QD),16),r=Number.isFinite(t)?t%Yx:0;return Tm+r}function Zx(e,t){return t===Go(e)}var Tm=18700,Yx=600,mq,QD=8,ZD=65535,eO=1024,tO=5;var Pm=A(()=>{Wa();mq=Tm+Yx-1});var Jn;var Um=A(()=>{Jn={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
|
---
|
|
@@ -367,7 +367,7 @@ the agent to set it up" a complete path. In that state \`get_status\` reports
|
|
|
367
367
|
arguments reports the missing configuration rather than a parse error. Draft,
|
|
368
368
|
validate the candidate text, write the files, then call \`reload_config\`: setup
|
|
369
369
|
mode clears on the reload that finds a valid configuration.
|
|
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` `credentials` `listen` `token_endpoint` `routes` |\n| `proxy.listen` | `host` `port` |\n| `proxy.token_endpoint` | `enabled` `host` `port` |\n| `route` | `name` `transport` `listen` `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` `credentials` `headers` |\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>}`, `${services.<name>.port}`,\n`${services.<name>.url}`, `${services.<name>.host}`, and `${identity.user}` (the\nrunning developer's detected Google email) are the supported forms. Anything\nelse \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
|
|
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` `telemetry`\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` `credentials` `listen` `token_endpoint` `routes` |\n| `proxy.listen` | `host` `port` |\n| `proxy.token_endpoint` | `enabled` `host` `port` |\n| `route` | `name` `transport` `listen` `match` `upstream` `auth` `response_headers` |\n| `route.match` | `host` `path` |\n| `route.upstream` | `url` `service` `port` |\n| `route.auth` | `type` `identity` `audience` `service_account` `client_id` `client_secret` `credentials` `headers` |\n| `logs` | `max_memory_events` `persistence` |\n| `logs.persistence` | `enabled` `directory` `retention_days` `max_session_logs` |\n| `telemetry` | `otlp` |\n| `telemetry.otlp` | `enabled` `listen` |\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>}`, `${services.<name>.port}`,\n`${services.<name>.url}`, `${services.<name>.host}`, and `${identity.user}` (the\nrunning developer's detected Google email) are the supported forms. Anything\nelse \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- `telemetry.otlp` is **off by default**. When `enabled: true`, `listen.host` must be loopback (`0.0.0.0` / `::` rejected, same as the proxy). Default listen port is **4318**. Host services (not containers) get `OTEL_EXPORTER_OTLP_ENDPOINT` / `OTEL_EXPORTER_OTLP_PROTOCOL=http/json` / `OTEL_SERVICE_NAME` only when those variables are unset. JSON only \u2014 no protobuf or gRPC.\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
|
|
371
371
|
|
|
372
372
|
How to turn what a repository contains into the service list you draft from.
|
|
373
373
|
Read the signals in roughly this order \u2014 the earlier ones describe *how the
|
|
@@ -594,7 +594,7 @@ Rules for turning that into YAML:
|
|
|
594
594
|
a full one. Remember that a bare \`devctl start\` falls back to the first
|
|
595
595
|
configured profile, and the TUI's default is the first profile
|
|
596
596
|
alphabetically \u2014 so name them with that in mind.
|
|
597
|
-
`}});var
|
|
597
|
+
`}});var fw;var hw=A(()=>{fw=[{path:"docs/README.md",title:"devctl wiki",body:`# devctl wiki
|
|
598
598
|
|
|
599
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
600
|
|
|
@@ -863,7 +863,7 @@ devctl exec <service> --print-env [--reveal] [--json]
|
|
|
863
863
|
devctl down [--repo <path>] [--keep-services]
|
|
864
864
|
devctl status [--repo <path>] [--json] [--watch]
|
|
865
865
|
devctl config import compose <file> [--write]
|
|
866
|
-
devctl logs [svc\u2026] [--level] [--search] [--regex] [--source] [--since] [--until] [--output] [--json] [-f|--follow] [--all]
|
|
866
|
+
devctl logs [svc\u2026] [--level] [--search] [--regex] [--source] [--since] [--until] [--trace] [--attribute key=value] [--output] [--json] [-f|--follow] [--all]
|
|
867
867
|
devctl logs export --output FILE
|
|
868
868
|
devctl daemon logs [-f|--follow]
|
|
869
869
|
devctl reload
|
|
@@ -1031,6 +1031,7 @@ TUI appearance is **not** this file. Theme, keys, mouse, and MCP listen live in
|
|
|
1031
1031
|
| \`profiles\` | Named service sets + extra env |
|
|
1032
1032
|
| \`proxy\` | Listen address, token endpoint, routes |
|
|
1033
1033
|
| \`logs\` | In-memory cap and persistence |
|
|
1034
|
+
| \`telemetry.otlp\` | Opt-in loopback OTLP/HTTP+JSON receiver (off by default) \u2014 see [Telemetry](telemetry.md) |
|
|
1034
1035
|
| \`auth.refresh_threshold_seconds\` | Token refresh window (default 300) |
|
|
1035
1036
|
| \`shutdown\` | \`stop_services_on_exit\`, \`grace_seconds\` |
|
|
1036
1037
|
| \`ui\` | Optional theme / keymap hints in YAML (TUI prefs still win from \`tui.json\`) |
|
|
@@ -1074,7 +1075,7 @@ entry includes the winning source file and layer (\`main\`, \`modular_service\`,
|
|
|
1074
1075
|
\`synthesized\`) and the ordered sources it shadowed. Use \`--json\` for structured
|
|
1075
1076
|
output.
|
|
1076
1077
|
|
|
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,
|
|
1078
|
+
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, optional \`plugins[].path\`, and \`telemetry.otlp.listen\` (loopback host, valid port, no collision with the proxy/token-endpoint/gRPC-route ports).
|
|
1078
1079
|
|
|
1079
1080
|
The TUI Config screen \`v\` / \`/buffer\` overlay validates this text before writing. Invalid YAML is not saved. \`e\` still opens \`$EDITOR\`.
|
|
1080
1081
|
|
|
@@ -1339,15 +1340,16 @@ onUnmounted(() => {
|
|
|
1339
1340
|
})
|
|
1340
1341
|
|
|
1341
1342
|
const copyLabel = ref('Copy command')
|
|
1342
|
-
const activeProfile = ref('
|
|
1343
|
+
const activeProfile = ref('minimal')
|
|
1343
1344
|
const profileExamples = {
|
|
1344
|
-
|
|
1345
|
-
|
|
1345
|
+
minimal: { label: 'Keep it focused', description: 'The profile shown in the demo above: identity, the invoices API, and telemetry.', services: ['identity', 'invoices-api', 'telemetry'] },
|
|
1346
|
+
backend: { label: 'Build an API', description: 'Focus on the backend. Bring up identity, the API, its worker, and telemetry as a single group.', services: ['identity', 'invoices-api', 'invoices-worker', 'telemetry'] },
|
|
1347
|
+
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', 'telemetry'] },
|
|
1346
1348
|
data: { label: 'Start with data', description: 'Run the demo\u2019s optional PostgreSQL container when you need a local database.', services: ['postgres'] }
|
|
1347
1349
|
}
|
|
1348
1350
|
async function copyInstall() {
|
|
1349
1351
|
try {
|
|
1350
|
-
await navigator.clipboard.writeText('
|
|
1352
|
+
await navigator.clipboard.writeText('npm install --global @amr-m-abdelgawad/devctl')
|
|
1351
1353
|
copyLabel.value = 'Copied!'
|
|
1352
1354
|
} catch {
|
|
1353
1355
|
copyLabel.value = 'Select the command to copy'
|
|
@@ -1358,7 +1360,7 @@ async function copyInstall() {
|
|
|
1358
1360
|
<div ref="landingRoot" class="landing vp-raw">
|
|
1359
1361
|
<section class="landing-hero" aria-labelledby="hero-title">
|
|
1360
1362
|
<div class="hero-copy">
|
|
1361
|
-
<p class="eyebrow"><span class="status-dot"></span>
|
|
1363
|
+
<p class="eyebrow"><span class="status-dot"></span> THE LOCAL DEVELOPMENT ORCHESTRATOR</p>
|
|
1362
1364
|
<h1 id="hero-title">More building.<br>Less <span>tab juggling.</span></h1>
|
|
1363
1365
|
<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>
|
|
1364
1366
|
<div class="hero-actions">
|
|
@@ -1370,7 +1372,6 @@ async function copyInstall() {
|
|
|
1370
1372
|
<div class="hero-preview" @pointermove="followPointer" @pointerleave="resetPreview" @pointercancel="resetPreview">
|
|
1371
1373
|
<div class="preview-caption"><span>ONE TERMINAL. THE WHOLE PICTURE.</span><span aria-hidden="true">\u2199</span></div>
|
|
1372
1374
|
<TerminalHero />
|
|
1373
|
-
<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>
|
|
1374
1375
|
</div>
|
|
1375
1376
|
</section>
|
|
1376
1377
|
|
|
@@ -1396,7 +1397,14 @@ async function copyInstall() {
|
|
|
1396
1397
|
<p class="eyebrow">DEMO PLATFORM / EXAMPLE PROFILE</p>
|
|
1397
1398
|
<p :key="activeProfile" class="workflow-command"><span aria-hidden="true">$ </span><code>devctl start --profile {{ activeProfile }}</code></p>
|
|
1398
1399
|
<p class="workflow-description">{{ profileExamples[activeProfile].description }}</p>
|
|
1399
|
-
<div class="workflow-
|
|
1400
|
+
<div class="workflow-tui" role="img" :aria-label="\`devctl profiles screen \u2014 \${activeProfile}: \${profileExamples[activeProfile].services.join(', ')}\`">
|
|
1401
|
+
<div class="wt-box" :key="activeProfile">
|
|
1402
|
+
<span class="wt-title">{{ activeProfile }}</span>
|
|
1403
|
+
<div class="wt-head"><span class="wt-dot" aria-hidden="true">\u25CF</span> current profile \xB7 {{ profileExamples[activeProfile].services.length }} {{ profileExamples[activeProfile].services.length === 1 ? 'service' : 'services' }}</div>
|
|
1404
|
+
<div class="wt-services"><span v-for="service in profileExamples[activeProfile].services" :key="service"><span class="wt-check" aria-hidden="true">\u2713</span>{{ service }}</span></div>
|
|
1405
|
+
</div>
|
|
1406
|
+
<div class="wt-hint" aria-hidden="true">space set current \xB7 enter set and start</div>
|
|
1407
|
+
</div>
|
|
1400
1408
|
<p class="workflow-note">Your repo, your names. Profiles are defined in your config.</p>
|
|
1401
1409
|
</div>
|
|
1402
1410
|
</div>
|
|
@@ -1405,8 +1413,9 @@ async function copyInstall() {
|
|
|
1405
1413
|
<section class="setup-section landing-section" aria-labelledby="setup-title">
|
|
1406
1414
|
<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>
|
|
1407
1415
|
<div class="setup-panel">
|
|
1408
|
-
<div class="install-heading"><span>
|
|
1409
|
-
<div class="install-command"><span aria-hidden="true">$</span><code>
|
|
1416
|
+
<div class="install-heading"><span>INSTALL ONCE. USE IN ANY REPO.</span><button type="button" @click="copyInstall" aria-live="polite">{{ copyLabel }}</button></div>
|
|
1417
|
+
<div class="install-command"><span aria-hidden="true">$</span><code>npm install --global @amr-m-abdelgawad/devctl</code></div>
|
|
1418
|
+
<p class="install-context">Then, from the repo you want to run:</p>
|
|
1410
1419
|
<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>
|
|
1411
1420
|
</div>
|
|
1412
1421
|
</section>
|
|
@@ -1537,7 +1546,7 @@ The npm package supports macOS arm64/x64, Linux arm64/x64 (glibc or musl), and W
|
|
|
1537
1546
|
- [npm publishing (maintainers)](npm-publishing.md)
|
|
1538
1547
|
- [How it fits together](overview.md)
|
|
1539
1548
|
- [Changelog](../CHANGELOG.md)
|
|
1540
|
-
`},{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 short snippets \u2014 pass a hit's `path` to `get_doc` to read the whole page |\n| `get_doc` | setup | Return the full text of one embedded doc page. Pass `path` from a `search_docs` hit (e.g. `docs/proxy.md`); an unambiguous basename like `proxy.md` also resolves |\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
|
|
1549
|
+
`},{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`, `proxy`, and `otlp` (when `telemetry.otlp.enabled` is on).\n\nEach line is stored as an OpenTelemetry-style record \u2014 body, attributes, severity, and optional `traceId`/`spanId` \u2014 so structured JSON keeps its fields and a line joins its trace. Enabling the OTLP receiver and viewing traces are covered in [Telemetry](telemetry.md).\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 wrap every line (default) \u2192 clip with ellipsis \u2192 unwrap only the selected row.\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- `enter` \u2014 details overlay (body summary, attributes table, severity number, `traceId`/`spanId`). A \u25CE marker on the list means the row has a trace; enter again (or **view trace**) opens a full-width waterfall. The solid block is the span; the dim track is unused time in the window. `j`/`k` selects a span; Enter or double-click opens that span's logs overlay (`esc` returns to the waterfall).\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- `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\nHeadlines wrap to the pane width with OpenTUI word wrap (`wrapMode=\"word\"` on the message cell; chrome columns stay fixed). Clip mode uses native ellipsis. `j`/`k` moves the highlight.\n\n## CLI\n\n```bash\ndevctl logs [svc\u2026] [--level] [--search] [--regex] [--source] [--since] [--until] [--trace] [--attribute key=value] [--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 logs --trace <id> # spans plus correlated logs for that trace\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 log records (body, attributes, severity), capped at 200 per page, secrets redacted. Filter by `trace_id`, `request_id`, or an `attribute` key/value in addition to service/level/source/time. 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| `get_trace` | logs | Span tree plus correlated log records for a W3C `trace_id`, secrets redacted |\n| `trace_request` | logs | Resolve a proxy `X-Devctl-Request-ID` to its trace, then return the span tree and correlated logs |\n| `get_requests` | logs | The proxy's recent requests \u2014 method, route, status, duration, identity, and request/trace ids |\n| `recent_errors` | logs | The latest error and fatal log records, capped at 200, same paging as `get_logs` |\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 short snippets \u2014 pass a hit's `path` to `get_doc` to read the whole page |\n| `get_doc` | setup | Return the full text of one embedded doc page. Pass `path` from a `search_docs` hit (e.g. `docs/proxy.md`); an unambiguous basename like `proxy.md` also resolves |\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
|
|
1541
1550
|
|
|
1542
1551
|
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.
|
|
1543
1552
|
|
|
@@ -1794,7 +1803,7 @@ The OIDC plugin is intentionally a reference implementation: it supports client
|
|
|
1794
1803
|
- [Environment](environment.md)
|
|
1795
1804
|
- [Authentication](authentication.md)
|
|
1796
1805
|
- [Security](security.md)
|
|
1797
|
-
`},{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### Custom OAuth client credentials (separate from ADC)\n\nA route can mint IAP tokens with a **custom OAuth client** via `auth.client_id` /\n`auth.client_secret`. By default the refresh token comes from gcloud ADC\n(`~/.config/gcloud/application_default_credentials.json`), which only works when\nADC was itself logged in with that same client \u2014 otherwise Google rejects the\nmint as `unauthorized_client` (\"client mismatch\").\n\nTo use a custom client without clobbering ADC (which GCS/Firestore and other\nGoogle SDKs depend on), point the route \u2014 or the whole proxy \u2014 at a **separate**\ngcloud *authorized_user* credentials file:\n\n```yaml\nproxy:\n credentials: ~/.devctl/iap-credentials.json # default for every IAP route\n routes:\n - name: orchestrator-api\n auth:\n type: iap\n audience: 507686272917-0dpd...\n client_id: 507686272917-4j6f...\n # client_secret optional here \u2014 the file can supply it\n credentials: ~/.devctl/iap-credentials.json # per-route override\n identity: { type: user }\n```\n\nThe file is a standard gcloud authorized_user JSON (the same shape as ADC):\n\n```json\n{ \"type\": \"authorized_user\", \"client_id\": \"\u2026\", \"client_secret\": \"\u2026\", \"refresh_token\": \"\u2026\" }\n```\n\nGenerate it with a scoped `gcloud auth application-default login` written to a\ncustom path (not the default ADC location). Notes:\n\n- The file's `refresh_token` must have been issued by the same `client_id` as the\n route \u2014 a mismatched `client_id` in the file is rejected.\n- `auth.credentials` wins over `proxy.credentials`; the path may start with `~`,\n be absolute, or be relative to the repository root.\n- The `client_secret` comes from the route when set, otherwise from the file.\n- ADC is **never read** for a file-backed route, so gcloud's default client stays\n usable for GCS/Firestore and everything else.\n- The file is read locally at mint time; its contents are never logged. It holds\n a long-lived refresh token and client secret \u2014 keep it private (`chmod 600`).\n\n### Extra token headers\n\nSome IAP-protected upstreams want the minted token under an additional header, not just `Authorization: Bearer \u2026`. `auth.headers` injects extra request headers on a token-minting route; `${token}` in a value is replaced with the same token used for the bearer:\n\n```yaml\n auth:\n type: iap\n audience: 507686272917-0dpd...\n headers:\n identity-token: \"${token}\" # same token, second header\n x-forwarded-client: gateway # a plain literal is passed through\n```\n\nApplied only on `iap` / `service_account` routes (there is no token on a `none` route). This lets the proxy fully satisfy an upstream's auth expectations without changing the upstream or the calling service.\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## gRPC routes (Temporal, and other h2 clients behind IAP)\n\nA `grpc` route is a dedicated loopback HTTP/2 (h2c) listener that forwards every gRPC stream to one upstream over HTTP/2 + TLS, injecting the route's IAP token per RPC. It's for clients that speak gRPC and can't go through the HTTP proxy \u2014 a Temporal worker, say \u2014 so they stay entirely token-free.\n\n```yaml\nproxy:\n enabled: true\n listen: { host: 127.0.0.1, port: 8080 } # the HTTP proxy (still required when enabled)\n routes:\n - name: temporal\n transport: grpc\n listen: { host: 127.0.0.1, port: 7233 } # the local address the client dials\n upstream: { url: \"https://temporal.internal.example.com:443\" }\n auth:\n type: iap\n audience: 507686272917-0dpd...\n client_id: 507686272917-4j6f...\n credentials: ~/.devctl/iap-credentials.json\n```\n\nThe client connects plaintext to the local port and does nothing else \u2014 no token, no refresh:\n\n```python\nclient = await Client.connect(\"127.0.0.1:7233\", namespace=\"prod\", tls=False)\n```\n\ndevctl adds `Authorization: Bearer <fresh id-token>` (plus any `auth.headers`) to each RPC's HTTP/2 headers, mints and refreshes it with the same machinery as HTTP routes (`audience` / `client_id` / `credentials`), and relays the response and gRPC trailers. Because every RPC carries the current token, expiry is handled with no timer in the app.\n\nNotes:\n\n- Each grpc route needs its own loopback `listen.port`, distinct from the HTTP proxy and every other grpc route, and an `https://` upstream (the IAP leg is TLS).\n- Injection only happens on an `iap` / `service_account` route; a `none` grpc route is a plain forwarder.\n- This targets a **self-hosted Temporal behind a GCP IAP HTTPS load balancer**. Temporal Cloud (mTLS + API key) is not covered by this route type.\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
|
|
1806
|
+
`},{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### Custom OAuth client credentials (separate from ADC)\n\nA route can mint IAP tokens with a **custom OAuth client** via `auth.client_id` /\n`auth.client_secret`. By default the refresh token comes from gcloud ADC\n(`~/.config/gcloud/application_default_credentials.json`), which only works when\nADC was itself logged in with that same client \u2014 otherwise Google rejects the\nmint as `unauthorized_client` (\"client mismatch\").\n\nTo use a custom client without clobbering ADC (which GCS/Firestore and other\nGoogle SDKs depend on), point the route \u2014 or the whole proxy \u2014 at a **separate**\ngcloud *authorized_user* credentials file:\n\n```yaml\nproxy:\n credentials: ~/.devctl/iap-credentials.json # default for every IAP route\n routes:\n - name: orchestrator-api\n auth:\n type: iap\n audience: 507686272917-0dpd...\n client_id: 507686272917-4j6f...\n # client_secret optional here \u2014 the file can supply it\n credentials: ~/.devctl/iap-credentials.json # per-route override\n identity: { type: user }\n```\n\nThe file is a standard gcloud authorized_user JSON (the same shape as ADC):\n\n```json\n{ \"type\": \"authorized_user\", \"client_id\": \"\u2026\", \"client_secret\": \"\u2026\", \"refresh_token\": \"\u2026\" }\n```\n\nGenerate it with a scoped `gcloud auth application-default login` written to a\ncustom path (not the default ADC location). Notes:\n\n- The file's `refresh_token` must have been issued by the same `client_id` as the\n route \u2014 a mismatched `client_id` in the file is rejected.\n- `auth.credentials` wins over `proxy.credentials`; the path may start with `~`,\n be absolute, or be relative to the repository root.\n- The `client_secret` comes from the route when set, otherwise from the file.\n- ADC is **never read** for a file-backed route, so gcloud's default client stays\n usable for GCS/Firestore and everything else.\n- The file is read locally at mint time; its contents are never logged. It holds\n a long-lived refresh token and client secret \u2014 keep it private (`chmod 600`).\n\n### Extra token headers\n\nSome IAP-protected upstreams want the minted token under an additional header, not just `Authorization: Bearer \u2026`. `auth.headers` injects extra request headers on a token-minting route; `${token}` in a value is replaced with the same token used for the bearer:\n\n```yaml\n auth:\n type: iap\n audience: 507686272917-0dpd...\n headers:\n identity-token: \"${token}\" # same token, second header\n x-forwarded-client: gateway # a plain literal is passed through\n```\n\nApplied only on `iap` / `service_account` routes (there is no token on a `none` route). This lets the proxy fully satisfy an upstream's auth expectations without changing the upstream or the calling service.\n\n### Response headers and CORS\n\n`route.response_headers` adds headers to every response on the route, overriding whatever the upstream sent \u2014 most often CORS headers for a browser that loads a micro-frontend, Module Federation remote, or iframe from another origin and then calls back through the proxy:\n\n```yaml\n response_headers:\n Access-Control-Allow-Origin: \"*\"\n Access-Control-Allow-Methods: \"GET, POST, PUT, DELETE, OPTIONS\"\n Access-Control-Allow-Headers: \"Authorization, Content-Type, X-Devctl-Request-ID\"\n```\n\nA CORS **preflight** (an `OPTIONS` carrying `Access-Control-Request-Method`) is answered directly with these headers and a `204` \u2014 it is **not** forwarded, since the upstream may not handle `OPTIONS` and IAP would reject an unauthenticated preflight. Any other `OPTIONS` is proxied normally, and the headers are still applied. This makes the proxy the single entry point for both CORS and auth, instead of a separate CORS sidecar that can't inject IAP tokens.\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## gRPC routes (Temporal, and other h2 clients behind IAP)\n\nA `grpc` route is a dedicated loopback HTTP/2 (h2c) listener that forwards every gRPC stream to one upstream over HTTP/2 + TLS, injecting the route's IAP token per RPC. It's for clients that speak gRPC and can't go through the HTTP proxy \u2014 a Temporal worker, say \u2014 so they stay entirely token-free.\n\n```yaml\nproxy:\n enabled: true\n listen: { host: 127.0.0.1, port: 8080 } # the HTTP proxy (still required when enabled)\n routes:\n - name: temporal\n transport: grpc\n listen: { host: 127.0.0.1, port: 7233 } # the local address the client dials\n upstream: { url: \"https://temporal.internal.example.com:443\" }\n auth:\n type: iap\n audience: 507686272917-0dpd...\n client_id: 507686272917-4j6f...\n credentials: ~/.devctl/iap-credentials.json\n```\n\nThe client connects plaintext to the local port and does nothing else \u2014 no token, no refresh:\n\n```python\nclient = await Client.connect(\"127.0.0.1:7233\", namespace=\"prod\", tls=False)\n```\n\ndevctl adds `Authorization: Bearer <fresh id-token>` (plus any `auth.headers`) to each RPC's HTTP/2 headers, mints and refreshes it with the same machinery as HTTP routes (`audience` / `client_id` / `credentials`), and relays the response and gRPC trailers. Because every RPC carries the current token, expiry is handled with no timer in the app.\n\nNotes:\n\n- Each grpc route needs its own loopback `listen.port`, distinct from the HTTP proxy and every other grpc route, and an `https://` upstream (the IAP leg is TLS).\n- Injection only happens on an `iap` / `service_account` route; a `none` grpc route is a plain forwarder.\n- This targets a **self-hosted Temporal behind a GCP IAP HTTPS load balancer**. Temporal Cloud (mTLS + API key) is not covered by this route type.\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## Tracing\n\nEach proxied request (HTTP and gRPC) is also recorded as an OpenTelemetry **span** \u2014 method, route, status, duration, identity \u2014 and the proxy propagates a `traceparent` and `X-Devctl-Request-ID` to the upstream, so a service's own spans and logs share the request's trace. An incoming `traceparent` is honored; a bare request-id header is not adopted as the trace id. Open the trace from a log row in the TUI, `devctl logs --trace <id>`, or the MCP `get_trace` / `trace_request` tools. See [Telemetry](telemetry.md).\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
|
|
1798
1807
|
|
|
1799
1808
|
From a repository that should be orchestrated:
|
|
1800
1809
|
|
|
@@ -2204,80 +2213,195 @@ Plugins can register extra health types. \`capabilities\` document intent (\`loc
|
|
|
2204
2213
|
- [Environment](environment.md)
|
|
2205
2214
|
- [Configuration](configuration.md)
|
|
2206
2215
|
- [Logs](logs.md)
|
|
2207
|
-
`},{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 QA(){return[{path:"skills/devctl-onboard/SKILL.md",title:"Onboard procedure",body:Ln.procedure},{path:"skills/devctl-onboard/references/authoring.md",title:"Onboard authoring",body:Ln.authoring},{path:"skills/devctl-onboard/references/discovery.md",title:"Onboard discovery",body:Ln.discovery}]}function Qy(){return[...zy,...QA()]}function Zy(e){let t=e.trim();if(t==="")throw Error("path is required");let r=Qy(),n=r.find((s)=>s.path===t),o=n?[n]:r.filter((s)=>s.path.endsWith(`/${t}`));if(o.length!==1)throw Error(`no unique doc for path "${t}"; available: ${r.map((s)=>s.path).join(", ")}`);let i=o[0];return{path:i.path,title:i.title,body:i.body}}function ZA(e){return e.toLowerCase().split(/[^a-z0-9_]+/).filter((t)=>t.length>=JA)}function eL(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+=qA;if(n.includes(a))i+=VA;let c=o.split(a).length-1;i+=Math.min(c,zA)}let s=t.join(" ");if(s.length>=XA&&o.includes(s))i+=YA;return i}function tL(e,t){let r=e.split(`
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
`).trim();if(i.length<=Jy)return i;return`${i.slice(0,Jy).trimEnd()}\u2026`}function rL(e){if(e===void 0||Number.isNaN(e))return GA;return Math.min(KA,Math.max(1,Math.floor(e)))}function eb(e,t){let r=e.trim(),n=ZA(r);if(n.length===0)throw Error("query is required");let o=rL(t),i=Qy().map((s)=>({path:s.path,title:s.title,score:eL(s,n),snippet:tL(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 GA=5,KA=10,qA=12,VA=8,zA=8,YA=15,Jy=400,Xy=5,JA=2,XA=4;var tb=I(()=>{pp();Yy()});import{existsSync as nL,readFileSync as oL}from"fs";function Dn(e,t){return!(t??[]).includes(e)}function rb(e){return Lc.filter((t)=>Dn(t.name,e))}function Dc(e){return Lc.some((t)=>t.name===e)}function ys(e){return new hr(e.secrets.extra_markers,e.secrets.extra_patterns)}function nb(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 sL(e,t){let r=e.config(),n=r.services[t];if(!n)throw Error(`unknown service ${t}`);let i=e.status().services[t],s=ys(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 ob(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 ib(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:iL}),s=ys(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 aL(e){return Object.entries(e.profiles).sort(([t],[r])=>t.localeCompare(r)).map(([t,r])=>({name:t,services:r.services}))}function ai(e){return e.trim()===""?void 0:e}function cL(e){let t={name:e.name,match:e.match,upstream:e.upstream.url!==""?e.upstream.url:ai(e.upstream.service??"")?`service:${e.upstream.service}${e.upstream.port?`/${e.upstream.port}`:""}`:"",auth:e.auth.type},r=ai(e.auth.identity.type),n=ai(e.auth.audience),o=ai(e.auth.identity.service_account||e.auth.service_account),i=ai(e.auth.client_id),s=za(e.auth.client_secret),a=ai(e.auth.credentials??"");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;if(a)t.credentials=a;let c=Object.keys(e.auth.headers??{});if(c.length>0)t.headers=c;return t}function sb(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,gateway:e.proxy.gateway,credentials:e.proxy.credentials,listen:{host:e.proxy.listen.host,port:e.proxy.listen.port},routes:e.proxy.routes.map((r)=>cL(r))}}}function lL(e){let t=ys(e);return{entries:Rn(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 Ac(e){if(!Array.isArray(e))return[];return e.filter((t)=>typeof t==="string")}async function ab(e,t,r){switch(t){case"list_services":return nb(e.status());case"get_service":if(typeof r.name!=="string"||r.name==="")throw Error("name is required");return sL(e,r.name);case"get_status":return ob(e.status());case"get_logs":return ib(e,r);case"list_profiles":return aL(e.config());case"get_config":return sb(e.config());case"get_config_sources":return lL(e.config());case"run_doctor":return e.doctor();case"start_services":return e.start({services:Ac(r.services),profile:typeof r.profile==="string"&&r.profile!==""?r.profile:void 0});case"stop_services":return await e.stop(Ac(r.services)),{ok:!0};case"restart_services":return await e.restart(Ac(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=ys(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,Ac(r.command),r.print_env===!0),o=ys(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 dL(r);case"search_docs":return eb(typeof r.query==="string"?r.query:"",typeof r.limit==="number"?r.limit:void 0);case"get_doc":return Zy(typeof r.path==="string"?r.path:"");case"validate_config":return uL(e,r);default:throw Error(`unknown tool ${t}`)}}function dL(e){let t=typeof e.section==="string"?e.section:"procedure",r=t in Ln?t:"procedure";return{section:r,text:Ln[r],sections:Object.keys(Ln)}}function uL(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(!nL(r.configPath))return{valid:!1,issues:[`no configuration at ${r.configPath}`],source:"disk",config_path:r.configPath,setup_mode:!0};let n=e.validateConfigText(oL(r.configPath,"utf8"));return{valid:n.length===0,issues:n,source:"disk",config_path:r.configPath}}function cb(e){return mp.includes(e)}async function lb(e,t){switch(t){case"devctl://status":return ob(e.status());case"devctl://services":return nb(e.status());case"devctl://logs":return ib(e,{});case"devctl://config":return sb(e.config());case"devctl://doctor":return e.doctor();default:throw Error(`unknown resource ${t}`)}}var iL=200,mp,fp,Lc;var bs=I(()=>{si();tb();pp();mp=["devctl://status","devctl://services","devctl://logs","devctl://config","devctl://doctor"],fp=["inspect","logs","diagnostics","control","setup"],Lc=[{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 for discovery. To read a full page, pass a hit's `path` to get_doc. 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:"get_doc",label:"Get doc",summary:"Read a full documentation page",category:"setup",description:"Return the complete text of one embedded documentation page. Pass the `path` from a search_docs hit (e.g. docs/proxy.md) \u2014 search_docs only returns short snippets, so use this to read the whole page. A basename like proxy.md also resolves when unambiguous.",inputSchema:{type:"object",properties:{path:{type:"string",description:"Doc path from a search_docs hit, e.g. docs/proxy.md"}},required:["path"],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 gb(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)=>dc(t,r),resolveStartRequest:(t,r)=>e.resolveStart.execute(t,{...r,profile:r.profile?pr(r.profile):void 0,activeProfile:r.activeProfile===void 0?void 0:pr(r.activeProfile)})}}var vb=I(()=>{At()});function Mc(e){let t=e.toLowerCase().trim(),r=CL[t];if(r)return r;return St.includes(t)?t:"devctl"}function PL(){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 yb(e){let t=Mc(e);if(t==="system"){if(PL())return{...gp["solarized-light"],name:"system"};return gp.system}return gp[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 go(e,t){return Cs(e)>=ks?Oc:t}function Cs(e){let t=e.startsWith("#")?e.slice(1):e,r=Number.parseInt(t,ML);if(Number.isNaN(r))return 0;let n=r>>OL&vp,o=r>>$L&vp,i=r&vp;return(n*AL+o*LL+i*DL)/IL}function FL(e){let t=EL;for(let r=0;r<e.length;r+=1)t^=e.charCodeAt(r),t=Math.imul(t,TL);return t>>>0}function $c(e){return Cs(e.background)>=ks}function bp(e){if(!e||!/^#[0-9a-f]{6}$/i.test(e))return!1;return Cs(e)<ks}function Fc(e,t){let r=NL[e];return $c(t)?r.light:r.dark}function bb(e,t){return Cs(Fc(e,t))>=ks?HL:jL}function Ze(e,t){if(e===""||e==="all")return t.muted;let r=Cs(t.background)>=ks?_L:RL;return r[FL(e)%r.length]??t.info}function BL(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 xb(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 BL(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,yp,gp,CL,RL,_L,EL=2166136261,TL=16777619,AL=299,LL=587,DL=114,IL=1000,ks=160,ML=16,vp=255,OL=16,$L=8,Oc="#1A1A1A",NL,HL="#1A1A1A",jL="#F5F5F5";var tt=I(()=>{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"],yp={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)"},gp={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"}},CL={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"};RL=["#7AA2F7","#9ECE6A","#E0AF68","#BB9AF7","#2AC3DE","#FF9E64","#F7768E","#73DACA","#C0CAF5","#FF79C6","#7DCFFF","#C3E88D"],_L=["#2E5AAC","#3D7A1F","#9A6B12","#6B3FA0","#0E7A86","#B85C1A","#B4234A","#1A7A6D","#4A5080","#9A3480","#1565C0","#4A7C19"];NL={claude:{dark:"#E08A6A",light:"#B54328"},cursor:{dark:"#B8D4FF",light:"#1E4EBF"},kilo:{dark:"#F0C94A",light:"#8A6400"},codex:{dark:"#4ADE80",light:"#0B7A4B"}}});var Lr=I(()=>{aa()});function kb(e){return e!==void 0}function wb(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 Cb(e,t){let r=[...St],n=r.includes(e)?e:wp;return Sp(r,n,t)}function Pb(e,t){let r=[...UL],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=[...xp];return t.reduce((r,n)=>Math.abs(n-e)<Math.abs(r-e)?n:r,t[0])}function Rs(e,t){return Sp([...xp],Ps(e),t)}function _s(e){let t=Ps(e),r=Sb[t]??Sb[Io];return{px:t,...r}}function vo(e){return e.label==="compact"}function Es(e){return e.steps<=2}function Rb(e){return xp.map((r,n)=>n<e?"\u2588":"\u2591").join(" ")}function Nc(e){return _s(e).label}function _b(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:Nc(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:wb(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:tr()},{id:"reset",group:"About",kind:"action",name:"Reset",value:"defaults",hint:"enter confirm reset",detail:`Restore theme ${wp}, display ${Nc(Io)}, mouse on, leader ${wb(ud)}. Enter asks first. ${t}`}]}function Hc(){return{theme:wp,mouse:!0,leader_timeout:ud,font_size:Io}}function Eb(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 kp(e,t){if(e.length===0)return 0;return Math.min(Math.max(t,0),e.length-1)}function In(e,t){return e[kp(e,t)]}function Cp(e,t){return e??t}var UL,xp,wp="devctl",Sb;var Dr=I(()=>{tt();Lr();UL=[1000,2000,3000],xp=[12,14,16,18,20,22];Sb={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 WL,useContext as GL}from"react";function be(){return GL(Pp)}var Pp;var Mt=I(()=>{Dr();Lr();Pp=WL(_s(Io))});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 li(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 Ts(e){if(!Number.isFinite(e)||e<Rp)return"< 1s";let t=Math.floor(e/Tb),r=Math.floor(e%Tb/Ep),n=Math.floor(e%Ep/_p),o=Math.floor(e%_p/Rp);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 As(e){if(!Number.isFinite(e)||e<0)return"\u2014";let t=e/KL;if(t>=Ab)return`${(t/Ab).toFixed(1)}G`;if(t>=1)return`${Math.round(t)}M`;return`${Math.round(e)}K`}function Lb(e){if(!Number.isFinite(e)||e<0)return"\u2014";return`${e.toFixed(1)}%`}var Rp=1000,_p,Ep,Tb,KL=1024,Ab=1024;var Ot=I(()=>{_p=60*Rp,Ep=60*_p,Tb=24*Ep});function Ap(e){return e>=zL}function Lp(e){return e>=YL}function Dp(e){return e>=JL}function Wc(e,t=0){return Math.max(1,e-Ib-t*2)}function Mb(e){let t=Db+Ir+$t;if(Ap(e))t+=Wr;if(Lp(e))t+=Bc;if(Dp(e))t+=ln;return Math.max(Uc,e-t)}function Gc(e,t,r){if(r)return e;let n=t.reduce((a,c)=>Math.max(a,c.length),0),o=Math.min(Tp,Math.max(Uc,n+qL)),i=Db+Ir+$t+o+Ib+VL,s=Math.max(jc,Math.floor(e*XL));return Math.min(s,Math.max(jc,i))}function Ob(e){if(!e)return"";return Object.keys(e.profiles).sort()[0]??""}function Kc(e){if(!e)return!0;let t=Object.values(e.services);if(t.length===0)return!0;return!t.some((r)=>bo(r))}function Ip(e,t){if(e.length>0)return e;return[...t]}function qc(e,t){if(e.length>0)return[...e];return t===""?[]:[t]}function Vc(e){return e.command.args.join(" ")||"\u2014"}function Ms(e,t){let r=yo(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 zc(e){let t=e.health.type||"none",r=e.health.url||e.health.address;return r===""?t:`${t} ${r}`}function Yc(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 Jc(e){let t=e.restart.policy||"none";if(e.restart.max_retries>0)return`${t} \xD7${e.restart.max_retries}`;return t}function $b(e,t,r,n,o){let i={...e.environment.defaults,...e.environment.vars},a=QL(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 Fb(e,t){if(!e||e.processes.length===0)return;if(t!==void 0&&t!==""&&e.session_id===t)return;return e}function yo(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 rr(e){if(!e)return"STOPPED";return Qo(e)}function QL(e,t,r,n){if(t)return e;return new hr(r,n).redactMap(e)}function Os(e){return ZL.has(e)}function bo(e){if(!e)return!1;return Os(e.state)}var Db=8,Ir=12,Wr=10,Bc=8,ln=8,Ls=9,Ds=7,Is=8,Uc=12,Tp=40,$t=2,qL=2,Ib=2,VL=2,jc=34,zL=48,YL=60,JL=72,XL=0.48,ZL;var Lt=I(()=>{At();si();ZL=new Set(["RUNNING","STARTING","RESTARTING","HEALTHY","UNHEALTHY"])});function el(e){return e===xa?"\u2014":e}function Mp(e,t,r){let n=r?Hb:eD+Hb+Math.max(0,t)*2;return Math.max(1,e-n)}function oD(e){if(e.startsWith('"')||e.startsWith("'"))return"string";if(/^\d+$/.test(e))return"number";return"keyword"}function tl(e){return e.replace(iD,"")}function Wb(e){try{return JSON.stringify(JSON.parse(e),null,2)}catch{return e}}function rl(e,t){let r=Math.max(1,t),n=tl(e).replace(/\r\n/g,`
|
|
2216
|
+
`},{path:"docs/telemetry.md",title:"Telemetry",body:`# Telemetry
|
|
2217
|
+
|
|
2218
|
+
devctl models logs, and now **traces**, on the OpenTelemetry data model, and can
|
|
2219
|
+
receive OTLP directly. This gives every log a structured shape, correlates a
|
|
2220
|
+
request across the proxy and your services, and lets a coding agent debug the
|
|
2221
|
+
running stack over MCP instead of grepping text. It is local-first: the receiver
|
|
2222
|
+
binds loopback only and is **off by default**.
|
|
2223
|
+
|
|
2224
|
+
## The record model
|
|
2225
|
+
|
|
2226
|
+
Every log is an OpenTelemetry-style record, not a flat string:
|
|
2227
|
+
|
|
2228
|
+
- **body** \u2014 the message; a string, or a structured object when the source emits one.
|
|
2229
|
+
- **attributes** \u2014 the structured fields, preserved as key/values (never flattened away).
|
|
2230
|
+
- **severityNumber** (1\u201324) + **severityText** \u2014 the level; filtering and coloring use the number.
|
|
2231
|
+
- **traceId / spanId** \u2014 set when the line carries them, so a log joins its trace.
|
|
2232
|
+
- **resource** \u2014 \`service.name\` (the devctl service) plus \`process.pid\` and any OTLP resource attributes.
|
|
2233
|
+
|
|
2234
|
+
Two ingestion lanes feed one model:
|
|
2235
|
+
|
|
2236
|
+
- **stdout / stderr (best-effort).** Plain text becomes the body; JSON from pino,
|
|
2237
|
+
bunyan, zap, logrus, structlog, ECS, GELF, or an OTLP-shaped line is mapped
|
|
2238
|
+
into body + attributes + severity + ids. A leading timestamp before the JSON
|
|
2239
|
+
is stripped and retried. An unrecognized JSON object is kept as structured
|
|
2240
|
+
data and shown as a \`key=value\` summary \u2014 never as raw braces.
|
|
2241
|
+
- **OTLP (lossless).** Anything sent to the receiver maps 1:1.
|
|
2242
|
+
|
|
2243
|
+
In the TUI, \`enter\` on a log opens the details overlay: the body, an
|
|
2244
|
+
**attributes table**, the severity, and \`traceId\`/\`spanId\`. See [Logs](logs.md).
|
|
2245
|
+
|
|
2246
|
+
## OTLP receiver
|
|
2247
|
+
|
|
2248
|
+
An opt-in loopback endpoint that accepts **OTLP/HTTP + JSON** for logs and
|
|
2249
|
+
traces. It is off until you enable it, and rejects any non-loopback bind (both
|
|
2250
|
+
at \`devctl config validate\` and at listen time).
|
|
2251
|
+
|
|
2252
|
+
\`\`\`yaml
|
|
2253
|
+
telemetry:
|
|
2254
|
+
otlp:
|
|
2255
|
+
enabled: true # default: false
|
|
2256
|
+
listen:
|
|
2257
|
+
host: 127.0.0.1 # loopback only; 0.0.0.0 / :: are rejected
|
|
2258
|
+
port: 4318 # default 4318 (standard OTLP/HTTP)
|
|
2259
|
+
\`\`\`
|
|
2260
|
+
|
|
2261
|
+
It serves \`POST /v1/logs\` and \`POST /v1/traces\` (JSON only; other methods and
|
|
2262
|
+
paths are rejected). Its port must differ from the proxy, token-endpoint, and
|
|
2263
|
+
any gRPC route port \u2014 a collision is reported at config-validation time.
|
|
2264
|
+
|
|
2265
|
+
When the receiver is enabled, devctl injects the standard exporter variables
|
|
2266
|
+
into every managed **host process** (not containers, whose loopback is
|
|
2267
|
+
isolated), and never overrides one you set yourself:
|
|
2268
|
+
|
|
2269
|
+
\`\`\`
|
|
2270
|
+
OTEL_EXPORTER_OTLP_ENDPOINT http://127.0.0.1:<port>
|
|
2271
|
+
OTEL_EXPORTER_OTLP_PROTOCOL http/json
|
|
2272
|
+
OTEL_SERVICE_NAME <service>
|
|
2273
|
+
\`\`\`
|
|
2274
|
+
|
|
2275
|
+
So a service instrumented with an OpenTelemetry SDK exports to devctl with no
|
|
2276
|
+
per-service configuration. Only OTLP/HTTP+JSON is accepted (no protobuf/gRPC).
|
|
2277
|
+
|
|
2278
|
+
## Traces and correlation
|
|
2279
|
+
|
|
2280
|
+
devctl's own signals are telemetry too. The proxy emits one **span per request**
|
|
2281
|
+
(method, route, status, duration, identity), and propagates a \`traceparent\` plus
|
|
2282
|
+
\`X-Devctl-Request-ID\` to the upstream \u2014 so a service's own spans and logs share
|
|
2283
|
+
the request's trace. A \`traceparent\` on an incoming request is honored; a bare
|
|
2284
|
+
request-id header is **not** adopted as the trace id, so unrelated requests are
|
|
2285
|
+
never merged into one trace.
|
|
2286
|
+
|
|
2287
|
+
View a trace two ways:
|
|
2288
|
+
|
|
2289
|
+
- **TUI** \u2014 a \u25CE marker on a log row means it has a trace; \`enter\` (or **view
|
|
2290
|
+
trace**) opens a full-width waterfall. \`j\`/\`k\` selects a span; Enter opens that
|
|
2291
|
+
span's logs.
|
|
2292
|
+
- **CLI** \u2014 \`devctl logs --trace <id>\` prints the span tree plus the correlated
|
|
2293
|
+
logs; add \`--json\` for JSONL. See [CLI](cli.md).
|
|
2294
|
+
|
|
2295
|
+
## Redaction
|
|
2296
|
+
|
|
2297
|
+
Records and spans are redacted at **ingestion**, before anything is stored,
|
|
2298
|
+
persisted, or served \u2014 the walk recurses into nested body/attributes, span
|
|
2299
|
+
events, and status messages, using the same markers as the rest of devctl (add
|
|
2300
|
+
your own under \`secrets\`). MCP output redacts again on top. So OTLP-received or
|
|
2301
|
+
structured stdout data cannot carry a secret into the TUI, CLI, MCP, or the
|
|
2302
|
+
on-disk log files. See [Security](security.md).
|
|
2303
|
+
|
|
2304
|
+
## Debugging with an agent (MCP)
|
|
2305
|
+
|
|
2306
|
+
The model is what makes "debug, don't grep" possible over [MCP](mcp.md):
|
|
2307
|
+
|
|
2308
|
+
- \`get_logs\` \u2014 filter by \`trace_id\`, \`request_id\`, or an \`attribute\` key/value, and receive body + attributes + severity.
|
|
2309
|
+
- \`get_trace <trace_id>\` / \`trace_request <request_id>\` \u2014 the span tree plus the correlated logs.
|
|
2310
|
+
- \`get_requests\` \u2014 the proxy's recent requests (with ids), and \`recent_errors\` \u2014 the latest error/fatal records.
|
|
2311
|
+
|
|
2312
|
+
An agent can ask "why did this request fail", resolve the request id to its
|
|
2313
|
+
trace, and read the responsible service's span and logs \u2014 all redacted.
|
|
2314
|
+
|
|
2315
|
+
## Notes
|
|
2316
|
+
|
|
2317
|
+
- \`*UnixNano\` timestamps are held as JS numbers, so precision is **millisecond**
|
|
2318
|
+
granular (fine for display, ordering, and durations \u2265 ~1 ms); do not rely on
|
|
2319
|
+
exact-nanosecond equality.
|
|
2320
|
+
- No new runtime dependency: the OTLP/JSON decode and the model are built in.
|
|
2321
|
+
|
|
2322
|
+
## Related
|
|
2323
|
+
|
|
2324
|
+
- [Logs](logs.md)
|
|
2325
|
+
- [MCP](mcp.md)
|
|
2326
|
+
- [Proxy](proxy.md)
|
|
2327
|
+
- [Configuration](configuration.md)
|
|
2328
|
+
- [Security](security.md)
|
|
2329
|
+
`},{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: wrap every line (default), clip with ellipsis, or unwrap only the selected row |\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; messages wrap to the pane with OpenTUI word wrap. `w` cycles wrap all / clip / wrap selected. `\\\\` / `/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 (all / clip / selected)\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 DO(){return[{path:"skills/devctl-onboard/SKILL.md",title:"Onboard procedure",body:Jn.procedure},{path:"skills/devctl-onboard/references/authoring.md",title:"Onboard authoring",body:Jn.authoring},{path:"skills/devctl-onboard/references/discovery.md",title:"Onboard discovery",body:Jn.discovery}]}function vw(){return[...fw,...DO()]}function bw(e){let t=e.trim();if(t==="")throw Error("path is required");let r=vw(),n=r.find((i)=>i.path===t),o=n?[n]:r.filter((i)=>i.path.endsWith(`/${t}`));if(o.length!==1)throw Error(`no unique doc for path "${t}"; available: ${r.map((i)=>i.path).join(", ")}`);let s=o[0];return{path:s.path,title:s.title,body:s.body}}function OO(e){return e.toLowerCase().split(/[^a-z0-9_]+/).filter((t)=>t.length>=IO)}function MO(e,t){let r=e.title.toLowerCase(),n=e.path.toLowerCase(),o=e.body.toLowerCase(),s=0;for(let a of t){if(r.includes(a))s+=TO;if(n.includes(a))s+=PO;let c=o.split(a).length-1;s+=Math.min(c,EO)}let i=t.join(" ");if(i.length>=LO&&o.includes(i))s+=AO;return s}function NO(e,t){let r=e.split(`
|
|
2330
|
+
`),n=0,o=-1;for(let i=0;i<r.length;i++){let a=r.slice(i,i+yw).join(`
|
|
2331
|
+
`).toLowerCase(),c=0;for(let l of t)if(a.includes(l))c+=1;if(c>o)o=c,n=i}let s=r.slice(n,n+yw).join(`
|
|
2332
|
+
`).trim();if(s.length<=gw)return s;return`${s.slice(0,gw).trimEnd()}\u2026`}function $O(e){if(e===void 0||Number.isNaN(e))return RO;return Math.min(_O,Math.max(1,Math.floor(e)))}function xw(e,t){let r=e.trim(),n=OO(r);if(n.length===0)throw Error("query is required");let o=$O(t),s=vw().map((i)=>({path:i.path,title:i.title,score:MO(i,n),snippet:NO(i.body,n)})).filter((i)=>i.score>0).sort((i,a)=>a.score-i.score||i.path.localeCompare(a.path)).slice(0,o);return{query:r,hits:s}}var RO=5,_O=10,TO=12,PO=8,EO=8,AO=15,gw=400,yw=5,IO=2,LO=4;var ww=A(()=>{Um();hw()});import{existsSync as FO,readFileSync as HO}from"fs";function Xn(e,t){return!(t??[]).includes(e)}function Sw(e){return Il.filter((t)=>Xn(t.name,e))}function Ll(e){return Il.some((t)=>t.name===e)}function Ko(e){return new Rr(e.secrets.extra_markers,e.secrets.extra_patterns)}function kw(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 jO(e,t){let r=e.config(),n=r.services[t];if(!n)throw Error(`unknown service ${t}`);let s=e.status().services[t],i=Ko(r);return{name:t,state:s?.state,health:s?.health,pid:s?.pid,last_error:s?.last_error,command:n.command.args,cwd:n.working_dir,ports:s?.ports??Object.fromEntries(n.ports.map((a)=>[a.name,a.value])),environment:i.redactMap({...n.environment.defaults,...n.environment.vars}),container:n.container?{...n.container,env:i.redactMap(n.container.env)}:void 0}}function Cw(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,requestTotal:e.proxy.requestTotal,requestErrors:e.proxy.requestErrors,recentRequests:(e.proxy.recentRequests??[]).slice(0,20).map((t)=>({timestamp:t.timestamp,request_id:t.requestId,trace_id:t.traceId,method:t.method,path:t.path,route:t.route,status:t.status,duration_ms:t.durationMs,error:t.error}))},logs:e.logs,mcp:e.mcp?{running:e.mcp.running,address:e.mcp.address,port:e.mcp.port}:{running:!1}}}async function jm(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,s=typeof t.attribute_key==="string"?t.attribute_key:"",i=typeof t.attribute_value==="string"?t.attribute_value:"",a=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:"",requestId:typeof t.request_id==="string"?t.request_id:void 0,traceId:typeof t.trace_id==="string"?t.trace_id:void 0,attribute:s!==""&&i!==""?{key:s,value:i}:void 0,cursor:o,direction:o?"forward":void 0,limit:UO}),c=Ko(e.config());return{events:a.events.map((l)=>Rw(c,l)),truncated:a.hasPrev,has_more:a.hasNext,next_since:a.events[a.events.length-1]?.timestamp??n,next_cursor:a.nextCursor,session_changed:a.sessionChanged}}function Rw(e,t){let r=mc(e,t),n=r.resource["process.pid"];return{timestamp:r.timestamp,service:r.service,source:r.source,severityText:r.severityText,severityNumber:r.severityNumber,body:r.body,attributes:r.attributes,traceId:r.traceId,spanId:r.spanId,seq:r.seq,level:r.severityText,message:Gt(r),pid:typeof n==="number"?n:0}}function _w(e,t){return{trace_id:t.traceId,request_id:t.requestId,spans:t.tree.spans.map((r)=>fc(e,r)),logs:t.events.map((r)=>Rw(e,r))}}async function BO(e,t){let r=typeof t.trace_id==="string"?t.trace_id:"";if(r==="")throw Error("trace_id is required");if(!e.getTrace)throw Error("trace store is unavailable");return _w(Ko(e.config()),await e.getTrace(r))}async function WO(e,t){let r=typeof t.request_id==="string"?t.request_id:"";if(r==="")throw Error("request_id is required");if(!e.traceRequest)throw Error("trace store is unavailable");return _w(Ko(e.config()),await e.traceRequest(r))}function GO(e){return{running:e.proxy.running,total:e.proxy.requestTotal??0,errors:e.proxy.requestErrors??0,requests:(e.proxy.recentRequests??[]).map((t)=>({timestamp:t.timestamp,request_id:t.requestId,trace_id:t.traceId,method:t.method,path:t.path,route:t.route,status:t.status,duration_ms:t.durationMs,error:t.error}))}}function qO(e){return Object.entries(e.profiles).sort(([t],[r])=>t.localeCompare(r)).map(([t,r])=>({name:t,services:r.services}))}function js(e){return e.trim()===""?void 0:e}function KO(e){let t={name:e.name,match:e.match,upstream:e.upstream.url!==""?e.upstream.url:js(e.upstream.service??"")?`service:${e.upstream.service}${e.upstream.port?`/${e.upstream.port}`:""}`:"",auth:e.auth.type},r=js(e.auth.identity.type),n=js(e.auth.audience),o=js(e.auth.identity.service_account||e.auth.service_account),s=js(e.auth.client_id),i=Wc(e.auth.client_secret),a=js(e.auth.credentials??"");if(r)t.identity=r;if(n)t.audience=n;if(o)t.service_account=o;if(s)t.client_id=s;if(i)t.client_secret=i;if(a)t.credentials=a;let c=Object.keys(e.auth.headers??{});if(c.length>0)t.headers=c;return t}function Tw(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,gateway:e.proxy.gateway,credentials:e.proxy.credentials,listen:{host:e.proxy.listen.host,port:e.proxy.listen.port},routes:e.proxy.routes.map((r)=>KO(r))}}}function VO(e){let t=Ko(e);return{entries:qn(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 Al(e){if(!Array.isArray(e))return[];return e.filter((t)=>typeof t==="string")}async function Pw(e,t,r){switch(t){case"list_services":return kw(e.status());case"get_service":if(typeof r.name!=="string"||r.name==="")throw Error("name is required");return jO(e,r.name);case"get_status":return Cw(e.status());case"get_logs":return jm(e,r);case"get_trace":return BO(e,r);case"trace_request":return WO(e,r);case"get_requests":return GO(e.status());case"recent_errors":return jm(e,{...r,level:"ERROR"});case"list_profiles":return qO(e.config());case"get_config":return Tw(e.config());case"get_config_sources":return VO(e.config());case"run_doctor":return e.doctor();case"start_services":return e.start({services:Al(r.services),profile:typeof r.profile==="string"&&r.profile!==""?r.profile:void 0});case"stop_services":return await e.stop(Al(r.services)),{ok:!0};case"restart_services":return await e.restart(Al(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=Ko(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,Al(r.command),r.print_env===!0),o=Ko(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 zO(r);case"search_docs":return xw(typeof r.query==="string"?r.query:"",typeof r.limit==="number"?r.limit:void 0);case"get_doc":return bw(typeof r.path==="string"?r.path:"");case"validate_config":return YO(e,r);default:throw Error(`unknown tool ${t}`)}}function zO(e){let t=typeof e.section==="string"?e.section:"procedure",r=t in Jn?t:"procedure";return{section:r,text:Jn[r],sections:Object.keys(Jn)}}function YO(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(!FO(r.configPath))return{valid:!1,issues:[`no configuration at ${r.configPath}`],source:"disk",config_path:r.configPath,setup_mode:!0};let n=e.validateConfigText(HO(r.configPath,"utf8"));return{valid:n.length===0,issues:n,source:"disk",config_path:r.configPath}}function Ew(e){return Bm.includes(e)}async function Aw(e,t){switch(t){case"devctl://status":return Cw(e.status());case"devctl://services":return kw(e.status());case"devctl://logs":return jm(e,{});case"devctl://config":return Tw(e.config());case"devctl://doctor":return e.doctor();default:throw Error(`unknown resource ${t}`)}}var UO=200,Bm,Wm,Il;var aa=A(()=>{bo();en();ww();Um();Bm=["devctl://status","devctl://services","devctl://logs","devctl://config","devctl://doctor"],Wm=["inspect","logs","diagnostics","control","setup"],Il=[{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 records, 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"},request_id:{type:"string",description:"Filter by X-Devctl-Request-ID / devctl.request_id"},trace_id:{type:"string",description:"Filter by W3C trace id"},attribute_key:{type:"string",description:"Attribute key to match (with attribute_value)"},attribute_value:{type:"string",description:"Attribute value to match (with attribute_key)"}},additionalProperties:!1}},{name:"get_trace",label:"Get trace",summary:"Span tree and correlated logs",category:"logs",description:"Return the span tree and correlated log records for a W3C trace id. Secrets are redacted.",inputSchema:{type:"object",properties:{trace_id:{type:"string"}},required:["trace_id"],additionalProperties:!1}},{name:"trace_request",label:"Trace request",summary:"Trace for a proxy request id",category:"logs",description:"Resolve a proxy X-Devctl-Request-ID to its trace, then return the span tree and correlated logs. Secrets are redacted.",inputSchema:{type:"object",properties:{request_id:{type:"string"}},required:["request_id"],additionalProperties:!1}},{name:"get_requests",label:"Recent requests",summary:"Proxy request ring with trace ids",category:"inspect",description:"Recent proxy requests with method, path, status, duration, request id, and trace id.",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"recent_errors",label:"Recent errors",summary:"Latest error-severity log records",category:"logs",description:"Latest error and fatal log records, capped at 200, secrets redacted. Same paging fields as get_logs.",inputSchema:{type:"object",properties:{service:{type:"string"},search:{type:"string"},source:{type:"string"},since:{type:"string"},until:{type:"string"},cursor:{type:"string"},request_id:{type:"string"},trace_id:{type:"string"}},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 for discovery. To read a full page, pass a hit's `path` to get_doc. 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:"get_doc",label:"Get doc",summary:"Read a full documentation page",category:"setup",description:"Return the complete text of one embedded documentation page. Pass the `path` from a search_docs hit (e.g. docs/proxy.md) \u2014 search_docs only returns short snippets, so use this to read the whole page. A basename like proxy.md also resolves when unambiguous.",inputSchema:{type:"object",properties:{path:{type:"string",description:"Doc path from a search_docs hit, e.g. docs/proxy.md"}},required:["path"],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 $w(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)=>sl(t,r),resolveStartRequest:(t,r)=>e.resolveStart.execute(t,{...r,profile:r.profile?Pr(r.profile):void 0,activeProfile:r.activeProfile===void 0?void 0:Pr(r.activeProfile)})}}var Fw=A(()=>{Nt()});function Ol(e){let t=e.toLowerCase().trim(),r=cM[t];if(r)return r;return Pt.includes(t)?t:"devctl"}function lM(){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 Hw(e){let t=Ol(e);if(t==="system"){if(lM())return{...qm["solarized-light"],name:"system"};return qm.system}return qm[t]}function Ir(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 Vo(e,t){return pa(e)>=ua?Ml:t}function pa(e){let t=e.startsWith("#")?e.slice(1):e,r=Number.parseInt(t,vM);if(Number.isNaN(r))return 0;let n=r>>bM&Km,o=r>>xM&Km,s=r&Km;return(n*fM+o*hM+s*gM)/yM}function wM(e){let t=pM;for(let r=0;r<e.length;r+=1)t^=e.charCodeAt(r),t=Math.imul(t,mM);return t>>>0}function Nl(e){return pa(e.background)>=ua}function zm(e){if(!e||!/^#[0-9a-f]{6}$/i.test(e))return!1;return pa(e)<ua}function $l(e,t){let r=SM[e];return Nl(t)?r.light:r.dark}function Uw(e,t){return pa($l(e,t))>=ua?kM:CM}function Ve(e,t){if(e===""||e==="all")return t.muted;let r=pa(t.background)>=ua?uM:dM;return r[wM(e)%r.length]??t.info}function RM(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 jw(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 RM(e,t)}function ut(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 Pt,Vm,qm,cM,dM,uM,pM=2166136261,mM=16777619,fM=299,hM=587,gM=114,yM=1000,ua=160,vM=16,Km=255,bM=16,xM=8,Ml="#1A1A1A",SM,kM="#1A1A1A",CM="#F5F5F5";var et=A(()=>{Pt=["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"],Vm={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)"},qm={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"}},cM={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"};dM=["#7AA2F7","#9ECE6A","#E0AF68","#BB9AF7","#2AC3DE","#FF9E64","#F7768E","#73DACA","#C0CAF5","#FF79C6","#7DCFFF","#C3E88D"],uM=["#2E5AAC","#3D7A1F","#9A6B12","#6B3FA0","#0E7A86","#B85C1A","#B4234A","#1A7A6D","#4A5080","#9A3480","#1565C0","#4A7C19"];SM={claude:{dark:"#E08A6A",light:"#B54328"},cursor:{dark:"#B8D4FF",light:"#1E4EBF"},kilo:{dark:"#F0C94A",light:"#8A6400"},codex:{dark:"#4ADE80",light:"#0B7A4B"}}});var qr=A(()=>{Va()});function Gw(e){return e!==void 0}function Bw(e){let t=e/1000;if(Number.isInteger(t))return`${t}s`;return`${e}ms`}function Xm(e,t,r){let n=e.indexOf(t),s=((n<0?0:n)+r+e.length)%e.length;return e[s]??e[0]}function qw(e,t){let r=[...Pt],n=r.includes(e)?e:Jm;return Xm(r,n,t)}function Kw(e,t){let r=[..._M],n=r.reduce((o,s)=>Math.abs(s-e)<Math.abs(o-e)?s:o,r[0]);return Xm(r,n,t)}function ma(e){let t=[...Ym];return t.reduce((r,n)=>Math.abs(n-e)<Math.abs(r-e)?n:r,t[0])}function fa(e,t){return Xm([...Ym],ma(e),t)}function ha(e){let t=ma(e),r=Ww[t]??Ww[ls];return{px:t,...r}}function _n(e){return e.label==="compact"}function ga(e){return e.steps<=2}function Vw(e){return Ym.map((r,n)=>n<e?"\u2588":"\u2591").join(" ")}function Fl(e){return ha(e).label}function zw(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:Fl(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:Bw(e.leaderMs),hint:"\u2190 \u2192 save",detail:`How long ${Ht("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:Vt,hint:"read-only",detail:hr()},{id:"reset",group:"About",kind:"action",name:"Reset",value:"defaults",hint:"enter confirm reset",detail:`Restore theme ${Jm}, display ${Fl(ls)}, mouse on, leader ${Bw(bu)}. Enter asks first. ${t}`}]}function Hl(){return{theme:Jm,mouse:!0,leader_timeout:bu,font_size:ls}}function Yw(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 Qm(e,t){if(e.length===0)return 0;return Math.min(Math.max(t,0),e.length-1)}function Qn(e,t){return e[Qm(e,t)]}function Zm(e,t){return e??t}var _M,Ym,Jm="devctl",Ww;var Lr=A(()=>{et();qr();_M=[1000,2000,3000],Ym=[12,14,16,18,20,22];Ww={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 TM,useContext as PM}from"react";function ge(){return PM(ef)}var ef;var $t=A(()=>{Lr();qr();ef=TM(ha(ls))});function we(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 U(e,t){if(t<=0)return"";if(t===1)return we(e,1);return we(e,t-1).padEnd(t)}function Ws(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 ya(e){if(!Number.isFinite(e)||e<tf)return"< 1s";let t=Math.floor(e/Jw),r=Math.floor(e%Jw/nf),n=Math.floor(e%nf/rf),o=Math.floor(e%rf/tf);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 va(e){if(!Number.isFinite(e)||e<0)return"\u2014";let t=e/EM;if(t>=Xw)return`${(t/Xw).toFixed(1)}G`;if(t>=1)return`${Math.round(t)}M`;return`${Math.round(e)}K`}function Qw(e){if(!Number.isFinite(e)||e<0)return"\u2014";return`${e.toFixed(1)}%`}var tf=1000,rf,nf,Jw,EM=1024,Xw=1024;var tt=A(()=>{rf=60*tf,nf=60*rf,Jw=24*nf});function sf(e){return e>=LM}function af(e){return e>=DM}function cf(e){return e>=OM}function Wl(e,t=0){return Math.max(1,e-eS-t*2)}function tS(e){let t=Zw+Kr+Ut;if(sf(e))t+=on;if(af(e))t+=jl;if(cf(e))t+=Tn;return Math.max(Bl,e-t)}function Gl(e,t,r){if(r)return e;let n=t.reduce((a,c)=>Math.max(a,c.length),0),o=Math.min(of,Math.max(Bl,n+AM)),s=Zw+Kr+Ut+o+eS+IM,i=Math.max(Ul,Math.floor(e*MM));return Math.min(i,Math.max(Ul,s))}function rS(e){if(!e)return"";return Object.keys(e.profiles).sort()[0]??""}function ql(e){if(!e)return!0;let t=Object.values(e.services);if(t.length===0)return!0;return!t.some((r)=>Yo(r))}function lf(e,t){if(e.length>0)return e;return[...t]}function Kl(e,t){if(e.length>0)return[...e];return t===""?[]:[t]}function Vl(e){return e.command.args.join(" ")||"\u2014"}function Sa(e,t){let r=zo(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 zl(e){let t=e.health.type||"none",r=e.health.url||e.health.address;return r===""?t:`${t} ${r}`}function Yl(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 Jl(e){let t=e.restart.policy||"none";if(e.restart.max_retries>0)return`${t} \xD7${e.restart.max_retries}`;return t}function nS(e,t,r,n,o){let s={...e.environment.defaults,...e.environment.vars},a=NM(o??s,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(s,l)})).sort((l,d)=>l.fromConfig===d.fromConfig?l.key.localeCompare(d.key):l.fromConfig?-1:1)}function oS(e,t){if(!e||e.processes.length===0)return;if(t!==void 0&&t!==""&&e.session_id===t)return;return e}function zo(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 gr(e){if(!e)return"STOPPED";return Ls(e)}function NM(e,t,r,n){if(t)return e;return new Rr(r,n).redactMap(e)}function ka(e){return $M.has(e)}function Yo(e){if(!e)return!1;return ka(e.state)}var Zw=8,Kr=12,on=10,jl=8,Tn=8,ba=9,xa=7,wa=8,Bl=12,of=40,Ut=2,AM=2,eS=2,IM=2,Ul=34,LM=48,DM=60,OM=72,MM=0.48,$M;var Ft=A(()=>{Nt();bo();$M=new Set(["RUNNING","STARTING","RESTARTING","HEALTHY","UNHEALTHY"])});function Gs(e){return Bg(e===""?yo:e)}function pf(e,t,r){let n=r?iS:HM+iS+Math.max(0,t)*2;return Math.max(1,e-n)}function WM(e){if(e.startsWith('"')||e.startsWith("'"))return"string";if(/^\d+$/.test(e))return"number";return"keyword"}function Ql(e){return e.replace(GM,"")}function cS(e){try{return JSON.stringify(JSON.parse(e),null,2)}catch{return e}}function lS(e){return Xg(e.body,e.attributes)}function dS(e,t){let r=Math.max(1,t),n=Ql(e).replace(/\r\n/g,`
|
|
2211
2333
|
`).replace(/\r/g,`
|
|
2212
2334
|
`).split(`
|
|
2213
|
-
`),o=[];for(let i of n)if(i.length<=r)o.push(i);else o.push(...sD(i,r));return o.length>0?o:[""]}function Gb(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=` ${tD}${o}`,s=Math.max(1,t-i.length);return{visible:[_e(n[0]??"",s)],mark:i,folded:!0,hidden:o}}function Kb(e,t){return e==="all"||e==="focus"&&t}function nl(e){if(e==="clip")return"focus";if(e==="focus")return"all";return"clip"}function Op(e,t,r,n=Mr){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 ol(e,t=Mr){return Math.max(0,e-t)}function $p(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 il(e){if(e==="all")return"wrap all";if(e==="focus")return"wrap selected";return"clip"}function sD(e,t){let r=[],n=e;while(n.length>t){let o=n.slice(0,t),i=aD(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 aD(e){let t=e.lastIndexOf(" "),r=e.lastIndexOf("\t"),n=e.lastIndexOf(","),o=Math.max(t,r,n);if(o<Math.floor(e.length*rD))return-1;return e[o]===","?o+1:o}function cD(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function lD(e,t=!1){let r=e.trim();if(r==="")return;if(t){let n=Ni(r);if(n)return new RegExp(n.source,n.flags.includes("g")?n.flags:`${n.flags}g`)}return new RegExp(cD(r),"gi")}function dD(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 m=Math.max(u.start,c),p=Math.min(u.end,l);if(p<=m)continue;let f=m-c,h=p-c;if(f>d)i.push({text:a.text.slice(d,f),kind:a.kind});i.push({text:a.text.slice(f,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 qb(e,t="",r=!1){let n=tl(e),o=[],i=new RegExp(nD.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:oD(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=lD(t,r);return l?dD(c,l):c}function Vb(e){let t=e.showTimestamps?Qc:0,r=e.showMeta?Xc:0;return Math.max(jb,e.width-t-e.serviceWidth-Zc-r-Bb-$s*Ub)}function zb(e,t){let r=t.reduce((s,a)=>Math.max(s,a.length),0),n=Qc+Zc+Bb+jb+$s*Ub,o=Math.max(Nb,e-n),i=Math.max(Nb,r);return Math.min(Tp,o,i)}function di(e){return uD.has(e)}function Jb(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 Xb(e,t){let r=t.length>0?t[t.length-1].seq:-1,n=e.filter((o)=>o.seq>r);return[...t,...n]}function Qb(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 Fp(e,t,r){return e&&t<=0&&r}function Zb(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=Ni(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&&di(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 pD(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 ex(e,t,r=[]){let n=sl(e,t,r);return[{name:"",count:t.length},...pD(t,n)]}function mD(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 tx(e,t,r=[]){let n=Object.keys(t.byService).map((i)=>({service:i})),o=sl(e,n,r);return[{name:"",count:t.total},...mD(o,t.byService)]}function rx(e,t){if(e<=0)return"none started";return`${e}/${t} running`}function sl(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 Np(e,t,r){let n=["",...e],o=n.indexOf(t),s=((o<0?0:o)+r+n.length)%n.length;return n[s]??""}var Xc=8,Qc=9,Zc=7,Nb=10,jb=16,Bb=2,$s=1,Mr=200,eD=2,Hb=1,tD="\u25B8",Ub=2,rD=0.4,nD,iD,uD,Yb;var vr=I(()=>{Sa();Ot();Lt();nD=/("[^"]*"|'[^']*'|\b(?:ERROR|FATAL|WARN(?:ING)?|FAIL(?:ED)?)\b|\b\d{3,5}\b)/gi;iD=/\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-Z\\-_]/g;uD=new Set(["auth","devctl","proxy"]),Yb=["devctl","mcp","auth"]});function ui(e){return e.length+hD}function nx(e,t){if(t<vD)return _e(e,3);if(t<gD)return _e(e,4);return e}function ox(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=Hp(e,o,r);if(i.start===0&&i.end===n-1)return i;let s=o>0?al:0,a=o<n-1?al:0,c=s+a,l=Hp(e,o,Math.max(e[o]??0,r-c)),d=(s>0&&l.start===0?al:0)+(a>0&&l.end===n-1?al:0);if(d===0)return l;return Hp(e,o,Math.max(e[o]??0,r-c+d))}function Hp(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),m=i+1-t;if(l&&(!d||u<=m))o-=1,s+=a;else if(d)i+=1,s+=c;else break}return{start:o,end:i}}function ix(e){if(e==="detail")return wo.findIndex((t)=>t.id==="services");return wo.findIndex((t)=>t.id===e)}function sx(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 fD;if(e==="config")return t.config??0;return 0}function ax(e){if(e.length===1&&e>="1"&&e<="9")return wo[Number(e)-1]?.id;return}var fD=9,hD=2,al=2,gD=80,vD=56,wo,Mn;var On=I(()=>{Ot();wo=[{id:"dashboard",label:"dashboard"},{id:"services",label:"services"},{id:"logs",label:"logs"},{id:"proxy",label:"proxy"}];Mn=wo.map((e)=>e.id)});function dx(e,t=!0){let r=e<cl?cx+1:cx,n=t?kD:0;return r+n+wD+n+SD+n}function mi(e){return Math.max(bD,e-xD*2)}function ux(e){let t=e.width<yD,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 lx(e){return e.key==="/"&&e.label==="command"}function CD(e){return e.key.length+e.label.length+3}function px(e,t){let r=e.filter(lx),n=e.filter((s)=>!lx(s)),o=[],i=0;for(let s of[...r,...n]){let a=CD(s);if(i+a>t)break;o.push(s),i+=a}return o}function ll(e,t,r,n,o="center",i=!0){let s=Math.min(r,Math.max(1,e-4)),a=Math.min(dx(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 mx(e){if(e==="")return"idle";if(RD.test(e))return"error";if(e.includes("session only"))return"warning";if(PD.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 hx(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 gx(e){let t=e.payload?.error;return typeof t==="string"&&t!==""?t:"configuration reload failed"}var pi=100,cl=90,yD=60,bD=8,xD=6,cx=1,wD=1,SD=1,kD=1,PW,PD,RD;var yr=I(()=>{Ot();vr();On();PW=dx(cl-1);PD=/^(started|stopped|restarted|refreshed|cleared|copied|exported|jumped|proxy started|proxy stopped|secrets|re-running|theme |mouse |leader |display |restored)/i,RD=/\b(fail|error|unknown|not found|no configuration|blocked|already in use|is using port)\b/i});function dl(){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 So(e){let t=e.trim().toLowerCase().replace(/^\//,""),r=t.indexOf(" ");return r===-1?t:t.slice(0,r)}function wx(e){let t=So(e);if(t==="")return dl();return dl().map((r)=>({command:r,score:BD(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 BD(e,t){if(t==="")return 1;let r=e.name.toLowerCase(),n=e.aliases.map((o)=>o.toLowerCase());return WD(r,n,t)||GD(r,n,t)||KD(r,n,t)||qD(r,n,t)||UD(e.suggest,t)||VD(e.desc.toLowerCase(),t)}function UD(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 yx;if(t.length<xx)return 0;return r.some((n)=>n.includes(t)||n.replace(/^-+/,"").startsWith(t))?bx:0}function WD(e,t,r){if(e===r)return _D;return t.includes(r)?ED:0}function GD(e,t,r){if(e.startsWith(r))return TD;if(t.some((i)=>i.startsWith(r)))return AD;let n=e.split("-");return n.length>1&&n.some((i)=>i.startsWith(r))?LD:0}function KD(e,t,r){if(r.length<xx)return 0;if(e.includes(r))return yx;return t.some((n)=>n.includes(r))?bx:0}function qD(e,t,r){if(r.length<$D)return 0;let n=vx(e,r);if(n>0)return DD+Math.min(n,jD);return t.some((o)=>vx(o,r)>0)?ID:0}function vx(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 VD(e,t){if(t.length<FD)return 0;if(e.split(/[^a-z0-9+]+/).filter((n)=>n.length>=ND).some((n)=>n.startsWith(t)))return MD;return t.length>=HD&&e.includes(t)?OD:0}function Gr(e){let t=e.trim().toLowerCase().replace(/^\//,"").split(" ")[0]??"";return dl().find((r)=>r.name===t||r.aliases.includes(t))}function jp(e){return e.trim().replace(/^\//,"").split(/\s+/).slice(1).filter((r)=>r!=="")}function Sx(e){return dl().find((r)=>r.leader===e)?.name??""}function kx(e){let t=e.includes("--cascade")||e.includes("-c");return{services:e.filter((n)=>n!=="--cascade"&&n!=="-c"),cascade:t}}function Cx(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 _D=100,ED=90,TD=80,AD=70,LD=60,yx=50,bx=45,DD=30,ID=28,MD=20,OD=10,xx=2,$D=3,FD=4,ND=4,HD=4,jD=9;function Ex(e){return e==="detail"?"services":e}function Tx(e){let t=Mn.indexOf(Ex(e));if(t<0)return"dashboard";return Mn[(t+1)%Mn.length]??"dashboard"}function Ax(e){let t=Mn.indexOf(Ex(e));if(t<0)return"dashboard";return Mn[(t-1+Mn.length)%Mn.length]??"dashboard"}function Up(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 ul(e){return e.hint?`${e.name} ${e.hint}`:e.name}function Wp(e){return e.name}function Lx(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 Dx(e){let t=e.reduce((n,o)=>Math.max(n,Wp(o).length),0),r=Math.min(Bp,Math.max(t,1));return zD+r+YD}function Ix(e){return e.hint?`${e.name} ${e.hint} `:`${e.name} `}function Mx(e,t){if(e.hint)return e.hint.split(/\s+/);return Gr(t)?.name===e.name?jp(t):[]}function Px(e,t){if(t==="")return!0;let r=t.toLowerCase(),n=e.toLowerCase();return n.startsWith(r)||n.includes(r)}function JD(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 Rx(e,t){return{...e,hint:t.token,desc:t.desc,usage:void 0,suggest:void 0}}function XD(e,t,r={}){let n=jp(t).join(" "),o=t.trim().includes(" ")||t.endsWith(" "),i=So(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(Px(d.token,l))s.push(Rx(a,d));if(!c||!o)continue;for(let d of JD(a,r))if(Px(d.token,n))s.push(Rx(a,d))}return s}function Ox(e,t={}){let r=wx(e),n=So(e)===""?Up(r).flatMap((o)=>o.items):r;return XD(n,e,t)}function Gp(e,t,r,n){let o=So(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 $x(e,t,r=en()){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[Kr,{key:"\u2191\u2193",label:"move"},{key:"enter",label:"save"},{key:"esc",label:"revert"}];if(t==="help")return[Kr,{key:"j/k",label:"scroll"},{key:"esc",label:"close"}];if(t==="log-details"||t==="scroll-text")return[Kr,{key:"j/k",label:"scroll"},{key:Li(r),label:"copy"},{key:"esc",label:"close"}];if(t==="confirm")return[{key:"enter",label:"confirm"},{key:"esc",label:"stay"}];if(t==="plan")return[Kr,{key:"esc",label:"back to dashboard"},{key:"enter",label:"done"}];if(t==="leader")return[Kr,...Kp()];if(t==="config-edit")return[{key:It("s"),label:"save"},{key:"esc",label:"discard"}];return QD(e,r)}function Kp(){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 QD(e,t){let r=[{key:Li(t),label:"copy"},Kr,{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 Fx(e,t,r){if(r<=t||e<t)return 0;return Math.min(e-t+1,r-t)}function _x(e,t,r){let n=e.slice(t,r+1),o=new Set(n.map((i)=>i.group));return n.length+o.size}function Nx(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&&_x(e,o,i+1)<=r,a=o>0&&_x(e,o-1,i)<=r;if(s)i+=1;else if(a)o-=1;else break}return e.slice(o,i+1)}function fi(e,t){if(e.length===0)return;let r=Math.min(Math.max(t,0),e.length-1);return e[r]}var Kr,zD=3,YD=2,Bp=22;var ko=I(()=>{Lr();On();Kr={key:"/",label:"command"}});function ml(e,t){let r=t??Object.keys(e?.services??{});return{running:r.filter((o)=>bo(e?.services[o])).length,total:r.length}}function Hx(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(Os(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 jx(e,t){if(!Number.isFinite(e)||!Number.isFinite(t)||t<=0)return 0;return e/t}function oI(e,t,r){if(!Number.isFinite(e))return"success";if(e>r)return"error";if(e>t)return"warning";return"success"}function iI(e,t){return oI(jx(e,t),ZD,eI)}function Yp(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 Fs[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(Fs.length-1,Math.round(i*(Fs.length-1)));return Fs[s]??Fs[0]}).join("")}function sI(e,t){let r=t>0?e/t:1;if(r<rI)return"error";if(r<tI)return"warning";return"success"}function qp(e){if(!Number.isFinite(e)||e<0)return"\u2014";return e.toFixed(2)}function aI(e){if(!Number.isFinite(e)||e<0)return"\u2014";return`${Math.round(e*100)}%`}function Bx(e,t=Date.now()){if(!e?.startTime)return"\u2014";let r=new Date(e.startTime).getTime();if(!Number.isFinite(r))return"\u2014";return Ts(t-r)}function Ux(e,t=nI){return Object.entries(e).sort((r,n)=>n[1]-r[1]||r[0].localeCompare(n[0])).slice(0,t)}function Wx(e,t=1){return Math.max(24,e-fI-t*2-hI-gI-vI)}function Gx(e){let t=e>=cI,r=e>=lI,n=e>=dI,o=e>=uI,i=e>=pI,s=e>=mI,a=Ir+$t;if(t)a+=Wr+$t;if(r)a+=Ds+$t;if(n)a+=Is+$t;if(o)a+=Ls+$t;if(i)a+=fl+$t;if(s)a+=ln;return{name:Math.max(Uc,e-a),health:t,cpu:r,mem:n,up:o,rst:i,pid:s}}function Kx(e){let t=Number.isFinite(e)&&e>0?e:0;return{ratio:Math.min(1,t),label:aI(t)}}function qx(e,t=xI){return`[${li(e.ratio,t)}] ${e.label.padStart(4)}`}function Jp(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)?wI:0,i=hi*(o>0?3:2),s=Math.min(yI,Math.max(Vp,r)),a=Math.min(bI,Math.max(zp,n)),c=t-s-a-o-i;if(c<pl&&a>zp)a=Math.max(zp,a-(pl-c)),c=t-s-a-o-i;if(c<pl&&s>Vp)s=Math.max(Vp,s-(pl-c)),c=t-s-a-o-i;return{what:s,reading:a,meaning:Math.max(0,c),meter:o}}function Vx(e){return SI.has((e?.health.type??"").toLowerCase())}function zx(e){if(e==="darwin")return"macOS";if(e==="win32")return"Windows";if(e==="linux")return"Linux";return e||"this computer"}function Yx(e,t){let r=Math.max(1,t),n=iI(e,r),o=n==="error"?"overloaded":n==="warning"?"busy":"not busy",i=n==="error"?`${r} cores are queued up \u2014 last minute ${qp(e)}`:n==="warning"?`${r} cores nearly full \u2014 last minute ${qp(e)}`:`${r} cores. Last minute ${qp(e)} (under ${r} is fine)`;return{what:"CPU work",reading:o,meaning:i,tone:n,meter:Kx(jx(e,r))}}function Jx(e,t){let r=sI(e,t),n=`${As(e)} of ${As(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:Kx(i)}}function Xx(e){let t=e?.state??"STOPPED";if(t==="FAILED")return"crashed";if(t==="STOPPING")return"stopping";if(t==="STARTING"||t==="RESTARTING")return"starting";if(Os(t))return"up";return"off"}function Qx(e){if(!e||!Os(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 Zx(e){if(e==="keychain")return"password store";if(e==="file")return"local file";return e||"unknown store"}function ew(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 ZD=0.85,eI=1.5,tI=0.2,rI=0.1,nI=5,Fs="\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588",cI=52,lI=64,dI=72,uI=82,pI=90,mI=100,fI=2,hI=2,gI=2,vI=1,fl=8,Vp=8,yI=22,zp=6,bI=28,pl=12,hi=2,xI=10,wI=18,SI;var hl=I(()=>{Ot();Lt();SI=new Set(["http","tcp","command"])});import{useEffect as kI,useRef as CI}from"react";import{jsxDEV as fe,Fragment as RI}from"@opentui/react/jsx-dev-runtime";function PI(e,t){switch(t){case"primary":return{bg:e.primary,fg:go(e.primary,e.inverse)};case"accent":return{bg:e.accent,fg:$c(e)?Oc:go(e.accent,e.inverse)};case"success":return{bg:e.success,fg:go(e.success,e.inverse)};case"warning":return{bg:e.warning,fg:$c(e)?Oc:go(e.warning,e.inverse)};case"error":return{bg:e.error,fg:go(e.error,e.inverse)};case"info":return{bg:e.info,fg:go(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 tw(e){let{palette:t,dir:r}=e;return fe("box",{width:2,height:1,flexShrink:0,overflow:"hidden",children:fe("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=be(),s=e.ruled??!vo(i),a=n??t.element,c=fe("box",{flexShrink:0,flexDirection:"column",backgroundColor:a,overflow:"hidden",children:r},void 0,!1,void 0,this);if(!s)return c;return fe("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 gl(e){let{palette:t,items:r,active:n,width:o,onPick:i,emphasis:s="ghost"}=e,a=r.map((u)=>ui(u.label)),c=ox(a,n,Math.max(1,o)),l=c.start>0,d=c.end>=0&&c.end<r.length-1;return fe("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:t.element,children:[l?fe(tw,{palette:t,dir:"left"},void 0,!1,void 0,this):null,r.map((u,m)=>{if(m<c.start||m>c.end)return null;let p=m===n,f=s==="fill"&&p;return fe(Be,{palette:t,label:u.label,tone:f?"primary":"ghost",bg:f?void 0:p?t.highlight:t.element,fg:f?void 0:p?t.primary:u.color??t.muted,onMouseDown:()=>i(m)},u.id,!1,void 0,this)}),d?fe(tw,{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=PI(t,n);return fe("box",{height:1,paddingLeft:1,paddingRight:1,backgroundColor:i??a.bg,overflow:"hidden",flexShrink:0,onMouseDown:s,children:fe("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 fe("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:r.map((n)=>fe("box",{height:1,paddingLeft:1,paddingRight:1,flexShrink:0,overflow:"hidden",children:fe("text",{wrapMode:"none",children:[fe("span",{fg:t.primary,children:n.key},void 0,!1,void 0,this),fe("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 pt(e){let{palette:t,items:r,hints:n,ruled:o=!0}=e,i=fe("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:t.element,children:[r.map((s,a)=>fe(Be,{palette:t,label:s.text,tone:s.tone,onMouseDown:s.onMouseDown},`${s.text}-${a}`,!1,void 0,this)),n&&n.length>0?fe(RI,{children:[fe("box",{flexGrow:1,backgroundColor:t.element},void 0,!1,void 0,this),fe(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 fe(Ut,{palette:t,backgroundColor:t.element,children:i},void 0,!1,void 0,this)}function mt(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,m=vo(be()),p=ll(n,o,i,s,a,!m);return fe("box",{position:"absolute",left:p.left,top:p.top,width:p.width,height:p.height,border:!0,borderStyle:Xp,borderColor:c??t.borderActive,backgroundColor:t.panel,padding:m?0:1,title:r,titleColor:t.primary,bottomTitle:l,gap:m?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 gi(e,t){let r=CI(null);return kI(()=>{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=be(),r=Es(t),n=e.scroll?fe("scrollbox",{focused:!1,stickyScroll:!1,scrollX:!1,style:Ue(e.palette),children:fe("box",{flexDirection:"column",overflow:"hidden",children:e.children},void 0,!1,void 0,this)},void 0,!1,void 0,this):e.children;return fe("box",{flexGrow:1,border:!0,borderStyle:Xp,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 Co(e){let{palette:t,label:r,value:n,tone:o="text"}=e,i=be(),s=o==="success"?t.success:o==="warning"?t.warning:o==="error"?t.error:o==="muted"?t.muted:t.text;return fe("box",{height:i.rowH,flexDirection:"row",overflow:"hidden",children:[fe("box",{width:14,flexShrink:0,overflow:"hidden",children:fe("text",{fg:t.muted,children:r},void 0,!1,void 0,this)},void 0,!1,void 0,this),fe("box",{flexGrow:1,overflow:"hidden",children:fe("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 vl(e){let{palette:t,title:r,body:n,hint:o}=e;return fe("box",{paddingLeft:1,paddingRight:1,flexDirection:"column",overflow:"hidden",border:!0,borderStyle:Xp,borderColor:t.borderActive,titleColor:t.primary,children:[fe("text",{fg:t.primary,children:r},void 0,!1,void 0,this),fe("text",{fg:t.text,wrapMode:"word",children:n},void 0,!1,void 0,this),o?fe("text",{fg:t.muted,wrapMode:"word",children:o},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}var Xp="rounded";var Pe=I(()=>{Mt();yr();On();Dr();tt()});import{jsxDEV as $e,Fragment as EI}from"@opentui/react/jsx-dev-runtime";function nw(e){let{palette:t,cfg:r,snap:n,google:o,profile:i,reveal:s,width:a}=e,c=ml(n,r?Object.keys(r.services):void 0),l=n?.proxy.running===!0,d=o?.adcAvailable===!0,u=a<cl,m=_e(r?.project.name||"local",u?18:22),p=_e(i||n?.profile||"no profile",16),f=ux({width:a,running:c.running,total:c.total,proxyOn:l,proxyAddress:n?.proxy.address??"",mcpOn:n?.mcp?.running===!0,adc:d,reveal:s}).filter((b)=>!b.hide&&b.label!=="").map((b)=>$e(Be,{palette:t,label:b.label,tone:b.tone},b.label,!1,void 0,this)),h=Es(be()),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:tr()},void 0,!1,void 0,this),$e("span",{fg:t.muted,children:` ${m}`},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:f]},void 0,!0,void 0,this),u?$e("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:t.panel,children:f},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}function ow(e){let{palette:t,screen:r,width:n,onSelect:o}=e,i=n>=rw+_I,s=i?Math.max(1,n-rw):n,a=wo.map((l)=>({id:l.id,label:nx(l.label,s)})),c=ix(r);return $e(Ut,{palette:t,backgroundColor:t.element,children:$e("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:t.element,children:[$e(gl,{palette:t,items:a,active:c,width:s,onPick:(l)=>{let d=wo[l];if(d)o(d.id)}},void 0,!1,void 0,this),i?$e(EI,{children:[$e("box",{flexGrow:1,backgroundColor:t.element},void 0,!1,void 0,this),$e(it,{palette:t,hints:[Kr]},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 iw(e){let{palette:t,screen:r,overlay:n,status:o,paused:i,errorOnly:s,width:a,copyKey:c}=e,l=Es(be()),d=Math.max(18,Math.floor(a*0.42)),u=px($x(r,n,c),d),m=mx(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(pt,{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:m}]],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(vl,{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 yl(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 sw(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 rw,_I=8;var Jt=I(()=>{Mt();yr();ko();Ot();On();hl();Pe();Dr();rw=Kr.key.length+Kr.label.length+3});function TI(){if(process.platform==="darwin")return[["pbcopy"]];if(process.platform==="win32")return[["clip"]];return[["wl-copy"],["xclip","-selection","clipboard"],["xsel","--clipboard","--input"]]}function AI(){return TI().filter((e)=>{let t=e[0];return typeof t==="string"&&Bun.which(t)!==null})}function LI(e){return`\x1B]52;c;${Buffer.from(e,"utf8").toString("base64")}\x07`}function aw(){if(process.platform==="linux")return"install wl-clipboard (Wayland) or xclip (X11)";return"no clipboard helper found"}async function bl(e){let t=AI(),r;for(let n of t)try{await II(n,e);return}catch(o){r=o instanceof Error?o:Error(String(o))}if(process.platform!=="win32")try{await DI(e);return}catch(n){r=n instanceof Error?n:Error(String(n))}throw Te(lt,"clipboard unavailable",r?`${aw()} (${r.message})`:aw())}async function DI(e){let t=LI(e);try{await Bun.write("/dev/tty",t)}catch{if(!process.stderr.write(t))throw Error("terminal clipboard write failed")}}async function II(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 Qp=I(()=>{te()});function Zp(e){return e==="slash"||e==="config-edit"||e==="setup-wizard"}function cw(e,t){return vn(e,t.keybinds.leader??Do("x"))}function lw(e,t){return vn(e,t.keybinds.command_list??Do("p"))}function dn(e,t){return vn(e,t.keybinds.command??"/")||e.name==="/"}function dw(e,t){return vn(e,t.keybinds.help??"?")||e.sequence==="?"}function uw(e,t){return vn(e,t.keybinds.search??"f")}function pw(e){return(e.name??"").toLowerCase()==="q"&&!e.ctrl&&!e.meta&&!e.super&&!e.alt&&!e.option}function mw(e){let t=e.name??"";return t==="R"||e.shift===!0&&t.toLowerCase()==="r"}function fw(e){return Hr(e)&&(e.name??"").toLowerCase()==="l"}function hw(e,t){return vn(e,t.keybinds.copy??en())}function gw(e,t,r=MI){return t>0&&e-t<=r}function $n(e,t,r,n){return vn(e,t.keybinds[r]??n)}function Ns(e){let t=(e.name??"").toLowerCase();return t==="pagedown"||Hr(e)&&t==="d"}function Hs(e){let t=(e.name??"").toLowerCase();return t==="pageup"||Hr(e)&&t==="u"}var MI=2000;var xl=I(()=>{Lr()});function vi(e){return`http://127.0.0.1:${e}/mcp`}function em(e){if(e==="")return{};return{Authorization:`Bearer ${e}`}}function tm(e,t){return JSON.stringify({mcpServers:{devctl:{type:"http",url:e,headers:em(t)}}},null,2)}function rm(e,t){return JSON.stringify({mcpServers:{devctl:{url:e,headers:em(t)}}},null,2)}function nm(e,t){return JSON.stringify({mcp:{devctl:{type:"remote",url:e,headers:em(t),enabled:!0}}},null,2)}function om(e,t){let r=t===""?"":`
|
|
2335
|
+
`),o=[];for(let s of n)if(s.length<=r)o.push(s);else o.push(...qM(s,r));return o.length>0?o:[""]}function uS(e,t){return e==="all"||e==="focus"&&t}function Zl(e){if(e==="clip")return"focus";if(e==="focus")return"all";return"clip"}function mf(e,t,r,n=Vr){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,s=Math.max(0,Math.min(r,o)),i=e.slice(s,s+n);return{start:s,items:i,newer:Math.max(0,e.length-s-i.length)}}function ed(e,t=Vr){return Math.max(0,e-t)}function ff(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 td(e){if(e==="all")return"wrap all";if(e==="focus")return"wrap selected";return"clip"}function qM(e,t){let r=[],n=e;while(n.length>t){let o=n.slice(0,t),s=KM(o);if(s>0)r.push(n.slice(0,s).trimEnd()),n=n.slice(s).trimStart();else r.push(n.slice(0,t)),n=n.slice(t)}if(n.length>0)r.push(n);return r}function KM(e){let t=e.lastIndexOf(" "),r=e.lastIndexOf("\t"),n=e.lastIndexOf(","),o=Math.max(t,r,n);if(o<Math.floor(e.length*jM))return-1;return e[o]===","?o+1:o}function VM(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function zM(e,t=!1){let r=e.trim();if(r==="")return;if(t){let n=_i(r);if(n)return new RegExp(n.source,n.flags.includes("g")?n.flags:`${n.flags}g`)}return new RegExp(VM(r),"gi")}function YM(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 s=[],i=0;for(let a of e){let c=i,l=i+a.text.length,d=0;for(let u of n){let m=Math.max(u.start,c),p=Math.min(u.end,l);if(p<=m)continue;let f=m-c,h=p-c;if(f>d)s.push({text:a.text.slice(d,f),kind:a.kind});s.push({text:a.text.slice(f,h),kind:"search"}),d=h}if(d<a.text.length)s.push({text:a.text.slice(d),kind:a.kind});i=l}return s.filter((a)=>a.text!=="")}function pS(e,t="",r=!1){let n=Ql(e),o=[],s=new RegExp(BM.source,"gi"),i=0,a=s.exec(n);while(a){let d=a[0]??"";if(a.index>i)o.push({text:n.slice(i,a.index),kind:"text"});o.push({text:d,kind:WM(d)}),i=a.index+d.length,a=s.exec(n)}if(i<n.length)o.push({text:n.slice(i),kind:"text"});let c=o.length===0?[{text:n,kind:"text"}]:o,l=zM(t,r);return l?YM(c,l):c}function mS(e,t){let r=t.reduce((i,a)=>Math.max(i,a.length),0),n=df+uf+FM+Xl*UM,o=Math.max(sS,e-n),s=Math.max(sS,r);return Math.min(of,o,s)}function qs(e){return JM.has(e)}function hS(e,t,r,n){let o=r===""?t:t.filter((a)=>a.timestamp>=r);if(o.length===0)return e;let s=Math.max(1,n);if(o.length>=s)return o.slice(-s);let i=Math.max(0,e.length+o.length-s);return e.slice(i).concat(o)}function gS(e,t){let r=t.length>0?t[t.length-1].seq:-1,n=e.filter((o)=>o.seq>r);return[...t,...n]}function yS(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 hf(e,t,r){return e&&t<=0&&r}function vS(e){return e.filter((t)=>wi(t.severityNumber)).length}function Jo(e,t){let r=t.services?.filter((l)=>l!=="")??[],n=t.service??"",o=(t.search??"").trim(),s=t.source??"",i=t.since??"",a=t.until??"",c;if(o!=="")if(t.regex===!0){let l=_i(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(s!==""&&l.source!==s)return!1;if(t.systemLogs===!1&&qs(l.source))return!1;if(i!==""&&l.timestamp<i)return!1;if(a!==""&&l.timestamp>a)return!1;if(t.traceId&&l.traceId!==t.traceId)return!1;if(t.errorOnly===!0&&!wi(l.severityNumber))return!1;if(!c)return!0;return c(Si(l))||c(l.service)})}function XM(e,t){let r={};for(let s of e)r[s.service]=(r[s.service]??0)+1;let n=t.map((s)=>({name:s,count:r[s]??0})),o=Object.keys(r).filter((s)=>!t.includes(s)).sort().map((s)=>({name:s,count:r[s]??0}));return[...n,...o]}function bS(e,t,r=[]){let n=rd(e,t,r);return[{name:"",count:t.length},...XM(t,n)]}function QM(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 xS(e,t,r=[]){let n=Object.keys(t.byService).map((s)=>({service:s})),o=rd(e,n,r);return[{name:"",count:t.total},...QM(o,t.byService)]}function wS(e,t){if(e<=0)return"none started";return`${e}/${t} running`}function rd(e,t,r=[]){let n=[...e],o=(s)=>{if(s!==""&&!n.includes(s))n.push(s)};for(let s of r)o(s);for(let s of t)o(s.service);return n}function gf(e,t,r){let n=["",...e],o=n.indexOf(t),i=((o<0?0:o)+r+n.length)%n.length;return n[i]??""}var aS=8,df=9,uf=8,sS=10,FM=16,Xl=1,Vr=200,HM=2,iS=1,UM=2,jM=0.4,BM,GM,JM,fS;var yr=A(()=>{en();tt();Ft();BM=/("[^"]*"|'[^']*'|\b(?:ERROR|FATAL|WARN(?:ING)?|FAIL(?:ED)?)\b|\b\d{3,5}\b)/gi;GM=/\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-Z\\-_]/g;JM=new Set(["auth","devctl","proxy"]),fS=["devctl","mcp","auth"]});function Ks(e){return e.length+eN}function SS(e,t){if(t<rN)return we(e,3);if(t<tN)return we(e,4);return e}function kS(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,s=yf(e,o,r);if(s.start===0&&s.end===n-1)return s;let i=o>0?nd:0,a=o<n-1?nd:0,c=i+a,l=yf(e,o,Math.max(e[o]??0,r-c)),d=(i>0&&l.start===0?nd:0)+(a>0&&l.end===n-1?nd:0);if(d===0)return l;return yf(e,o,Math.max(e[o]??0,r-c+d))}function yf(e,t,r){let n=e.length,o=t,s=t,i=e[t]??0;if(i>=r)return{start:t,end:t};while(o>0||s<n-1){let a=o>0?e[o-1]??0:Number.POSITIVE_INFINITY,c=s<n-1?e[s+1]??0:Number.POSITIVE_INFINITY,l=o>0&&i+a<=r,d=s<n-1&&i+c<=r;if(!l&&!d)break;let u=t-(o-1),m=s+1-t;if(l&&(!d||u<=m))o-=1,i+=a;else if(d)s+=1,i+=c;else break}return{start:o,end:s}}function CS(e){if(e==="detail")return Xo.findIndex((t)=>t.id==="services");return Xo.findIndex((t)=>t.id===e)}function RS(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 ZM;if(e==="config")return t.config??0;return 0}function _S(e){if(e.length===1&&e>="1"&&e<="9")return Xo[Number(e)-1]?.id;return}var ZM=9,eN=2,nd=2,tN=80,rN=56,Xo,Zn;var eo=A(()=>{tt();Xo=[{id:"dashboard",label:"dashboard"},{id:"services",label:"services"},{id:"logs",label:"logs"},{id:"proxy",label:"proxy"}];Zn=Xo.map((e)=>e.id)});function ES(e,t=!0){let r=e<od?TS+1:TS,n=t?cN:0;return r+n+iN+n+aN+n}function to(e){return Math.max(oN,e-sN*2)}function AS(e){let t=e.width<nN,r=e.proxyOn?`\u25CF ${we(e.proxyAddress,t?10:18)}`:"";return[{label:t?`${e.running}/${e.total}`:wS(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 PS(e){return e.key==="/"&&e.label==="command"}function lN(e){return e.key.length+e.label.length+3}function IS(e,t){let r=e.filter(PS),n=e.filter((i)=>!PS(i)),o=[],s=0;for(let i of[...r,...n]){let a=lN(i);if(s+a>t)break;o.push(i),s+=a}return o}function zs(e,t,r,n,o="center",s=!0){let i=Math.min(r,Math.max(1,e-4)),a=Math.min(ES(e,s),Math.max(0,t-1)),c=Math.min(n,Math.max(1,t-a)),l=Math.max(0,Math.min(Math.floor((e-i)/2),Math.max(0,e-i))),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:i,height:c}}function LS(e){if(e==="")return"idle";if(uN.test(e))return"error";if(e.includes("session only"))return"warning";if(dN.test(e))return"success";return"info"}function DS(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",s=r?.pid??0;return{title:`Free port ${n}`,body:`Stop ${o} (pid ${s}) 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 OS(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 MS(e){let t=e.payload?.error;return typeof t==="string"&&t!==""?t:"configuration reload failed"}var Vs=100,od=90,nN=60,oN=8,sN=6,TS=1,iN=1,aN=1,cN=1,ZV,dN,uN;var vr=A(()=>{tt();yr();eo();ZV=ES(od-1);dN=/^(started|stopped|restarted|refreshed|cleared|copied|exported|jumped|proxy started|proxy stopped|secrets|re-running|theme |mouse |leader |display |restored)/i,uN=/\b(fail|error|unknown|not found|no configuration|blocked|already in use|is using port)\b/i});function sd(){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: all lines, clip, or selected row",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 Qo(e){let t=e.trim().toLowerCase().replace(/^\//,""),r=t.indexOf(" ");return r===-1?t:t.slice(0,r)}function US(e){let t=Qo(e);if(t==="")return sd();return sd().map((r)=>({command:r,score:_N(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 _N(e,t){if(t==="")return 1;let r=e.name.toLowerCase(),n=e.aliases.map((o)=>o.toLowerCase());return PN(r,n,t)||EN(r,n,t)||AN(r,n,t)||IN(r,n,t)||TN(e.suggest,t)||LN(e.desc.toLowerCase(),t)}function TN(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 $S;if(t.length<HS)return 0;return r.some((n)=>n.includes(t)||n.replace(/^-+/,"").startsWith(t))?FS:0}function PN(e,t,r){if(e===r)return pN;return t.includes(r)?mN:0}function EN(e,t,r){if(e.startsWith(r))return fN;if(t.some((s)=>s.startsWith(r)))return hN;let n=e.split("-");return n.length>1&&n.some((s)=>s.startsWith(r))?gN:0}function AN(e,t,r){if(r.length<HS)return 0;if(e.includes(r))return $S;return t.some((n)=>n.includes(r))?FS:0}function IN(e,t,r){if(r.length<wN)return 0;let n=NS(e,r);if(n>0)return yN+Math.min(n,RN);return t.some((o)=>NS(o,r)>0)?vN:0}function NS(e,t){let r=0,n=0,o=0;for(let s=0;s<t.length;s+=1){let i=t[s];if(i===void 0)return 0;let a=e.indexOf(i,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 LN(e,t){if(t.length<SN)return 0;if(e.split(/[^a-z0-9+]+/).filter((n)=>n.length>=kN).some((n)=>n.startsWith(t)))return bN;return t.length>=CN&&e.includes(t)?xN:0}function sn(e){let t=e.trim().toLowerCase().replace(/^\//,"").split(" ")[0]??"";return sd().find((r)=>r.name===t||r.aliases.includes(t))}function vf(e){return e.trim().replace(/^\//,"").split(/\s+/).slice(1).filter((r)=>r!=="")}function jS(e){return sd().find((r)=>r.leader===e)?.name??""}function BS(e){let t=e.includes("--cascade")||e.includes("-c");return{services:e.filter((n)=>n!=="--cascade"&&n!=="-c"),cascade:t}}function WS(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 pN=100,mN=90,fN=80,hN=70,gN=60,$S=50,FS=45,yN=30,vN=28,bN=20,xN=10,HS=2,wN=3,SN=4,kN=4,CN=4,RN=9;function VS(e){return e==="detail"?"services":e}function zS(e){let t=Zn.indexOf(VS(e));if(t<0)return"dashboard";return Zn[(t+1)%Zn.length]??"dashboard"}function YS(e){let t=Zn.indexOf(VS(e));if(t<0)return"dashboard";return Zn[(t-1+Zn.length)%Zn.length]??"dashboard"}function xf(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 id(e){return e.hint?`${e.name} ${e.hint}`:e.name}function wf(e){return e.name}function JS(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 XS(e){let t=e.reduce((n,o)=>Math.max(n,wf(o).length),0),r=Math.min(bf,Math.max(t,1));return DN+r+ON}function QS(e){return e.hint?`${e.name} ${e.hint} `:`${e.name} `}function ZS(e,t){if(e.hint)return e.hint.split(/\s+/);return sn(t)?.name===e.name?vf(t):[]}function GS(e,t){if(t==="")return!0;let r=t.toLowerCase(),n=e.toLowerCase();return n.startsWith(r)||n.includes(r)}function MN(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 qS(e,t){return{...e,hint:t.token,desc:t.desc,usage:void 0,suggest:void 0}}function NN(e,t,r={}){let n=vf(t).join(" "),o=t.trim().includes(" ")||t.endsWith(" "),s=Qo(t),i=[];for(let a of e){if(i.push({...a,hint:void 0}),s==="")continue;let c=a.name===s||a.aliases.includes(s),l=c?n:s;for(let d of a.suggest??[])if(GS(d.token,l))i.push(qS(a,d));if(!c||!o)continue;for(let d of MN(a,r))if(GS(d.token,n))i.push(qS(a,d))}return i}function ek(e,t={}){let r=US(e),n=Qo(e)===""?xf(r).flatMap((o)=>o.items):r;return NN(n,e,t)}function Sf(e,t,r,n){let o=Qo(t),s=[...e].sort().map((i)=>({name:i,aliases:[],desc:n,leader:"",group:r}));if(o==="")return s;return s.filter((i)=>i.name.toLowerCase().includes(o))}function tk(e,t,r=yn()){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[Dr,{key:"\u2191\u2193",label:"move"},{key:"enter",label:"save"},{key:"esc",label:"revert"}];if(t==="help")return[Dr,{key:"j/k",label:"scroll"},{key:"esc",label:"close"}];if(t==="trace")return[Dr,{key:"j/k",label:"span"},{key:"enter",label:"logs"},{key:fo(r),label:"copy"},{key:"esc",label:"close"}];if(t==="span-details")return[Dr,{key:"j/k",label:"scroll"},{key:"esc",label:"back"},{key:fo(r),label:"copy"}];if(t==="log-details"||t==="scroll-text")return[Dr,{key:"j/k",label:"scroll"},{key:fo(r),label:"copy"},{key:"esc",label:"close"}];if(t==="confirm")return[{key:"enter",label:"confirm"},{key:"esc",label:"stay"}];if(t==="plan")return[Dr,{key:"esc",label:"back to dashboard"},{key:"enter",label:"done"}];if(t==="leader")return[Dr,...kf()];if(t==="config-edit")return[{key:Ht("s"),label:"save"},{key:"esc",label:"discard"}];return $N(e,r)}function kf(){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 $N(e,t){let r=[{key:fo(t),label:"copy"},Dr,{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:Ht("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:Ht("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 rk(e,t,r){if(r<=t||e<t)return 0;return Math.min(e-t+1,r-t)}function KS(e,t,r){let n=e.slice(t,r+1),o=new Set(n.map((s)=>s.group));return n.length+o.size}function nk(e,t,r){if(e.length===0)return[];let n=Math.min(Math.max(t,0),e.length-1),o=n,s=n;for(;;){let i=s+1<e.length&&KS(e,o,s+1)<=r,a=o>0&&KS(e,o-1,s)<=r;if(i)s+=1;else if(a)o-=1;else break}return e.slice(o,s+1)}function Ys(e,t){if(e.length===0)return;let r=Math.min(Math.max(t,0),e.length-1);return e[r]}var Dr,DN=3,ON=2,bf=22;var Zo=A(()=>{qr();eo();Dr={key:"/",label:"command"}});function cd(e,t){let r=t??Object.keys(e?.services??{});return{running:r.filter((o)=>Yo(e?.services[o])).length,total:r.length}}function ok(e,t){let r=0,n=0,o=0,s=0,i=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")s+=1;else if(d==="STOPPING")i+=1;else if(d==="STARTING"||d==="RESTARTING")n+=1;else if(ka(d))r+=1;else a+=1}return{total:e.length,live:r+n,running:r,starting:n,healthy:o,failed:s,stopping:i,stopped:a}}function sk(e,t){if(!Number.isFinite(e)||!Number.isFinite(t)||t<=0)return 0;return e/t}function WN(e,t,r){if(!Number.isFinite(e))return"success";if(e>r)return"error";if(e>t)return"warning";return"success"}function GN(e,t){return WN(sk(e,t),FN,HN)}function Tf(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 Ca[0]?.repeat(r.length)??"";return r.map((o)=>{let s=Number.isFinite(o)?Math.min(1,Math.max(0,o/n)):0,i=Math.min(Ca.length-1,Math.round(s*(Ca.length-1)));return Ca[i]??Ca[0]}).join("")}function qN(e,t){let r=t>0?e/t:1;if(r<jN)return"error";if(r<UN)return"warning";return"success"}function Cf(e){if(!Number.isFinite(e)||e<0)return"\u2014";return e.toFixed(2)}function KN(e){if(!Number.isFinite(e)||e<0)return"\u2014";return`${Math.round(e*100)}%`}function ik(e,t=Date.now()){if(!e?.startTime)return"\u2014";let r=new Date(e.startTime).getTime();if(!Number.isFinite(r))return"\u2014";return ya(t-r)}function ak(e,t=BN){return Object.entries(e).sort((r,n)=>n[1]-r[1]||r[0].localeCompare(n[0])).slice(0,t)}function ck(e,t=1){return Math.max(24,e-ZN-t*2-e$-t$-r$)}function lk(e){let t=e>=VN,r=e>=zN,n=e>=YN,o=e>=JN,s=e>=XN,i=e>=QN,a=Kr+Ut;if(t)a+=on+Ut;if(r)a+=xa+Ut;if(n)a+=wa+Ut;if(o)a+=ba+Ut;if(s)a+=ld+Ut;if(i)a+=Tn;return{name:Math.max(Bl,e-a),health:t,cpu:r,mem:n,up:o,rst:s,pid:i}}function dk(e){let t=Number.isFinite(e)&&e>0?e:0;return{ratio:Math.min(1,t),label:KN(t)}}function uk(e,t=s$){return`[${Ws(e.ratio,t)}] ${e.label.padStart(4)}`}function Pf(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)?i$:0,s=Js*(o>0?3:2),i=Math.min(n$,Math.max(Rf,r)),a=Math.min(o$,Math.max(_f,n)),c=t-i-a-o-s;if(c<ad&&a>_f)a=Math.max(_f,a-(ad-c)),c=t-i-a-o-s;if(c<ad&&i>Rf)i=Math.max(Rf,i-(ad-c)),c=t-i-a-o-s;return{what:i,reading:a,meaning:Math.max(0,c),meter:o}}function pk(e){return a$.has((e?.health.type??"").toLowerCase())}function mk(e){if(e==="darwin")return"macOS";if(e==="win32")return"Windows";if(e==="linux")return"Linux";return e||"this computer"}function fk(e,t){let r=Math.max(1,t),n=GN(e,r),o=n==="error"?"overloaded":n==="warning"?"busy":"not busy",s=n==="error"?`${r} cores are queued up \u2014 last minute ${Cf(e)}`:n==="warning"?`${r} cores nearly full \u2014 last minute ${Cf(e)}`:`${r} cores. Last minute ${Cf(e)} (under ${r} is fine)`;return{what:"CPU work",reading:o,meaning:s,tone:n,meter:dk(sk(e,r))}}function hk(e,t){let r=qN(e,t),n=`${va(e)} of ${va(t)}`,o=r==="error"?"Almost no RAM left for new work":r==="warning"?"RAM is getting tight":"RAM the computer can still give out",s=t>0?Math.max(0,(t-e)/t):0;return{what:"RAM leftover",reading:n,meaning:o,tone:r,meter:dk(s)}}function gk(e){let t=e?.state??"STOPPED";if(t==="FAILED")return"crashed";if(t==="STOPPING")return"stopping";if(t==="STARTING"||t==="RESTARTING")return"starting";if(ka(t))return"up";return"off"}function yk(e){if(!e||!ka(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 vk(e){if(e==="keychain")return"password store";if(e==="file")return"local file";return e||"unknown store"}function bk(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 FN=0.85,HN=1.5,UN=0.2,jN=0.1,BN=5,Ca="\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588",VN=52,zN=64,YN=72,JN=82,XN=90,QN=100,ZN=2,e$=2,t$=2,r$=1,ld=8,Rf=8,n$=22,_f=6,o$=28,ad=12,Js=2,s$=10,i$=18,a$;var dd=A(()=>{tt();Ft();a$=new Set(["http","tcp","command"])});import{useEffect as c$,useRef as l$}from"react";import{jsxDEV as ye,Fragment as u$}from"@opentui/react/jsx-dev-runtime";function d$(e,t){switch(t){case"primary":return{bg:e.primary,fg:Vo(e.primary,e.inverse)};case"accent":return{bg:e.accent,fg:Nl(e)?Ml:Vo(e.accent,e.inverse)};case"success":return{bg:e.success,fg:Vo(e.success,e.inverse)};case"warning":return{bg:e.warning,fg:Nl(e)?Ml:Vo(e.warning,e.inverse)};case"error":return{bg:e.error,fg:Vo(e.error,e.inverse)};case"info":return{bg:e.info,fg:Vo(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 xk(e){let{palette:t,dir:r}=e;return ye("box",{width:2,height:1,flexShrink:0,overflow:"hidden",children:ye("text",{fg:t.muted,children:r==="left"?"\u2039":"\u203A"},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function Yt(e){let{palette:t,children:r,backgroundColor:n,edge:o="bottom"}=e,s=ge(),i=e.ruled??!_n(s),a=n??t.element,c=ye("box",{flexShrink:0,flexDirection:"column",backgroundColor:a,overflow:"hidden",children:r},void 0,!1,void 0,this);if(!i)return c;return ye("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 ud(e){let{palette:t,items:r,active:n,width:o,onPick:s,emphasis:i="ghost"}=e,a=r.map((u)=>Ks(u.label)),c=kS(a,n,Math.max(1,o)),l=c.start>0,d=c.end>=0&&c.end<r.length-1;return ye("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:t.element,children:[l?ye(xk,{palette:t,dir:"left"},void 0,!1,void 0,this):null,r.map((u,m)=>{if(m<c.start||m>c.end)return null;let p=m===n,f=i==="fill"&&p;return ye(Fe,{palette:t,label:u.label,tone:f?"primary":"ghost",bg:f?void 0:p?t.highlight:t.element,fg:f?void 0:p?t.primary:u.color??t.muted,onMouseDown:()=>s(m)},u.id,!1,void 0,this)}),d?ye(xk,{palette:t,dir:"right"},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}function Fe(e){let{palette:t,label:r,tone:n="idle",fg:o,bg:s,onMouseDown:i}=e,a=d$(t,n);return ye("box",{height:1,paddingLeft:1,paddingRight:1,backgroundColor:s??a.bg,overflow:"hidden",flexShrink:0,onMouseDown:i,children:ye("text",{fg:o??a.fg,children:r},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function ft(e){let{palette:t,hints:r}=e;return ye("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:r.map((n)=>ye("box",{height:1,paddingLeft:1,paddingRight:1,flexShrink:0,overflow:"hidden",children:ye("text",{wrapMode:"none",children:[ye("span",{fg:t.primary,children:n.key},void 0,!1,void 0,this),ye("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 xt(e){let{palette:t,items:r,hints:n,ruled:o=!0}=e,s=ye("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:t.element,children:[r.map((i,a)=>ye(Fe,{palette:t,label:i.text,tone:i.tone,onMouseDown:i.onMouseDown},`${i.text}-${a}`,!1,void 0,this)),n&&n.length>0?ye(u$,{children:[ye("box",{flexGrow:1,backgroundColor:t.element},void 0,!1,void 0,this),ye(ft,{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 s;return ye(Yt,{palette:t,backgroundColor:t.element,children:s},void 0,!1,void 0,this)}function rt(e){let{palette:t,title:r,termW:n,termH:o,preferW:s,preferH:i,anchor:a="center",borderColor:c,bottomTitle:l,gap:d,children:u}=e,m=_n(ge()),p=zs(n,o,s,i,a,!m);return ye("box",{position:"absolute",left:p.left,top:p.top,width:p.width,height:p.height,border:!0,borderStyle:Ef,borderColor:c??t.borderActive,backgroundColor:t.panel,padding:m?0:1,title:r,titleColor:t.primary,bottomTitle:l,gap:m?0:d,flexDirection:"column",overflow:"hidden",children:u},void 0,!1,void 0,this)}function Oe(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 He(e,t){if(!e)return;e.scrollBy({x:0,y:t})}function Xs(e,t){let r=l$(null);return c$(()=>{let n=r.current;if(!n||e<0)return;let o=requestAnimationFrame(()=>{n.scrollChildIntoView(`${t}-${e}`)});return()=>cancelAnimationFrame(o)},[t,e]),r}function Et(e){let t=ge(),r=ga(t),n=e.scroll?ye("scrollbox",{focused:!1,stickyScroll:!1,scrollX:!1,style:Oe(e.palette),children:ye("box",{flexDirection:"column",overflow:"hidden",children:e.children},void 0,!1,void 0,this)},void 0,!1,void 0,this):e.children;return ye("box",{flexGrow:1,border:!0,borderStyle:Ef,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 es(e){let{palette:t,label:r,value:n,tone:o="text"}=e,s=ge(),i=o==="success"?t.success:o==="warning"?t.warning:o==="error"?t.error:o==="muted"?t.muted:t.text;return ye("box",{height:s.rowH,flexDirection:"row",overflow:"hidden",children:[ye("box",{width:14,flexShrink:0,overflow:"hidden",children:ye("text",{fg:t.muted,children:U(r,14)},void 0,!1,void 0,this)},void 0,!1,void 0,this),ye("box",{flexGrow:1,overflow:"hidden",children:ye("text",{fg:i,wrapMode:"none",children:n},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function pd(e){let{palette:t,title:r,body:n,hint:o}=e;return ye("box",{paddingLeft:1,paddingRight:1,flexDirection:"column",overflow:"hidden",border:!0,borderStyle:Ef,borderColor:t.borderActive,titleColor:t.primary,children:[ye("text",{fg:t.primary,children:r},void 0,!1,void 0,this),ye("text",{fg:t.text,wrapMode:"word",children:n},void 0,!1,void 0,this),o?ye("text",{fg:t.muted,wrapMode:"word",children:o},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}var Ef="rounded";var Te=A(()=>{$t();vr();tt();eo();Lr();et()});import{jsxDEV as Ue,Fragment as m$}from"@opentui/react/jsx-dev-runtime";function Sk(e){let{palette:t,cfg:r,snap:n,google:o,profile:s,reveal:i,width:a}=e,c=cd(n,r?Object.keys(r.services):void 0),l=n?.proxy.running===!0,d=o?.adcAvailable===!0,u=a<od,m=we(r?.project.name||"local",u?18:22),p=we(s||n?.profile||"no profile",16),f=AS({width:a,running:c.running,total:c.total,proxyOn:l,proxyAddress:n?.proxy.address??"",mcpOn:n?.mcp?.running===!0,adc:d,reveal:i}).filter((x)=>!x.hide&&x.label!=="").map((x)=>Ue(Fe,{palette:t,label:x.label,tone:x.tone},x.label,!1,void 0,this)),h=ga(ge()),g=Ue("box",{height:1,flexGrow:1,overflow:"hidden",backgroundColor:t.panel,paddingLeft:1,children:Ue("text",{wrapMode:"none",children:[Ue("span",{fg:t.primary,children:hr()},void 0,!1,void 0,this),Ue("span",{fg:t.muted,children:` ${m}`},void 0,!1,void 0,this),Ue("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 Ue(Yt,{palette:t,backgroundColor:t.panel,ruled:!h,children:Ue("box",{height:u?2:1,flexDirection:"column",backgroundColor:t.panel,overflow:"hidden",children:[Ue("box",{height:1,flexDirection:"row",overflow:"hidden",children:[g,u?null:f]},void 0,!0,void 0,this),u?Ue("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:t.panel,children:f},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}function kk(e){let{palette:t,screen:r,width:n,onSelect:o}=e,s=n>=wk+p$,i=s?Math.max(1,n-wk):n,a=Xo.map((l)=>({id:l.id,label:SS(l.label,i)})),c=CS(r);return Ue(Yt,{palette:t,backgroundColor:t.element,children:Ue("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:t.element,children:[Ue(ud,{palette:t,items:a,active:c,width:i,onPick:(l)=>{let d=Xo[l];if(d)o(d.id)}},void 0,!1,void 0,this),s?Ue(m$,{children:[Ue("box",{flexGrow:1,backgroundColor:t.element},void 0,!1,void 0,this),Ue(ft,{palette:t,hints:[Dr]},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 Ck(e){let{palette:t,screen:r,overlay:n,status:o,paused:s,errorOnly:i,width:a,copyKey:c}=e,l=ga(ge()),d=Math.max(18,Math.floor(a*0.42)),u=IS(tk(r,n,c),d),m=LS(o);return Ue(Yt,{palette:t,backgroundColor:t.panel,edge:"top",ruled:!l,children:Ue("box",{height:1,backgroundColor:t.panel,overflow:"hidden",children:Ue(xt,{ruled:!1,palette:t,items:[{text:r,tone:"ghost"},{text:s?"PAUSED":"LIVE",tone:s?"warning":"success"},...i?[{text:"ERROR+",tone:"error"}]:[],...o===""?[]:[{text:we(o,Math.max(16,a-d-28)),tone:m}]],hints:u},void 0,!1,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function nt(e){return Ue("box",{flexGrow:1,padding:1,overflow:"hidden",children:Ue(pd,{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 md(e){return Ue("box",{flexGrow:1,padding:1,overflow:"hidden",children:Ue("text",{fg:e.palette.text,children:e.label},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function Rk(e){return Ue("box",{flexGrow:1,padding:1,flexDirection:"column",overflow:"hidden",children:[Ue("text",{fg:e.palette.error,children:e.title},void 0,!1,void 0,this),Ue("text",{fg:e.palette.text,wrapMode:"word",children:e.body},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var wk,p$=8;var ir=A(()=>{$t();vr();Zo();tt();eo();dd();Te();Lr();wk=Dr.key.length+Dr.label.length+3});function f$(){if(process.platform==="darwin")return[["pbcopy"]];if(process.platform==="win32")return[["clip"]];return[["wl-copy"],["xclip","-selection","clipboard"],["xsel","--clipboard","--input"]]}function h$(){return f$().filter((e)=>{let t=e[0];return typeof t==="string"&&Bun.which(t)!==null})}function g$(e){return`\x1B]52;c;${Buffer.from(e,"utf8").toString("base64")}\x07`}function _k(){if(process.platform==="linux")return"install wl-clipboard (Wayland) or xclip (X11)";return"no clipboard helper found"}async function fd(e){let t=h$(),r;for(let n of t)try{await v$(n,e);return}catch(o){r=o instanceof Error?o:Error(String(o))}if(process.platform!=="win32")try{await y$(e);return}catch(n){r=n instanceof Error?n:Error(String(n))}throw Ae(Qe,"clipboard unavailable",r?`${_k()} (${r.message})`:_k())}async function y$(e){let t=g$(e);try{await Bun.write("/dev/tty",t)}catch{if(!process.stderr.write(t))throw Error("terminal clipboard write failed")}}async function v$(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 Af=A(()=>{ne()});function $f(e){let t=Math.min(e.startUnixNano,e.endUnixNano),r=Math.max(e.startUnixNano,e.endUnixNano);return Math.max(0,(r-t)/pr)}function ts(e){if(!Number.isFinite(e)||e<0)return"0ms";if(e>0&&e<1)return`${e.toFixed(1)}ms`;if(e<Ik)return`${Math.round(e)}ms`;return`${(e/Ik).toFixed(2)}s`}function Mk(e){if(e==="server")return"\u25C0";if(e==="client")return"\u25B6";if(e==="producer")return"\u25B2";if(e==="consumer")return"\u25BC";return"\u25CF"}function Ff(e){return`${Mk(e)} ${e}`}function Nk(e){if(e==="error")return"ERR";if(e==="ok")return"ok";return"\u2014"}function gd(e){let t=new Map,r=new Set(e.spans.map((i)=>i.spanId));for(let i of e.spans){let a=i.parentSpanId&&r.has(i.parentSpanId)?i.parentSpanId:"",c=t.get(a)??[];c.push(i),t.set(a,c)}for(let i of t.values())i.sort((a,c)=>a.startUnixNano-c.startUnixNano||a.seq-c.seq);let n=[],o=new Set;Df(t,"",0,o,n);let s=[...e.spans].filter((i)=>!o.has(i.seq)).sort((i,a)=>i.startUnixNano-a.startUnixNano||i.seq-a.seq);for(let i of s)if(!o.has(i.seq))o.add(i.seq),n.push({span:i,depth:0}),Df(t,i.spanId,1,o,n);return n}function Df(e,t,r,n,o){let s=(e.get(t)??[]).filter((i)=>!n.has(i.seq));for(let i of s)n.add(i.seq),o.push({span:i,depth:Math.min(r,Lk)}),Df(e,i.spanId,r+1,n,o)}function $k(e){if(e.spans.length===0)return{durationMs:0,spanCount:0,serviceCount:0,errorCount:0,services:[],startUnixNano:0,endUnixNano:0};let t=Math.min(e.spans[0].startUnixNano,e.spans[0].endUnixNano),r=Math.max(e.spans[0].startUnixNano,e.spans[0].endUnixNano),n=[],o=0;for(let s of e.spans){let i=Math.min(s.startUnixNano,s.endUnixNano),a=Math.max(s.startUnixNano,s.endUnixNano);if(i<t)t=i;if(a>r)r=a;let c=s.resource["service.name"];if(typeof c==="string"&&c!==""&&!n.includes(c))n.push(c);if(s.status.code==="error")o+=1}return{durationMs:Math.max(0,(r-t)/pr),spanCount:e.spans.length,serviceCount:n.length,errorCount:o,services:n,startUnixNano:t,endUnixNano:r}}function Fk(e){return e.reduce((t,r)=>Math.max(t,r.depth),0)}function Hk(e){return e.reduce((t,r)=>{let n=typeof r.span.resource["service.name"]==="string"?r.span.resource["service.name"]:"";return Math.max(t,n.length)},7)}function L$(e){let t=Math.max(0,Math.min(Lk,e));return Dk+Ok+t*Lf}function Uk(e,t=0,r=Pk){let n=Math.max(2,L$(t)),o=x$,s=e>=90?k$:e>=70?C$:Pk,i=Math.max(Tk,Math.min(s,r)),a=e>=Ek?R$:_$,c=e>=Ek?T$:P$,l=e-n-i-o;if(l<a+c){let f=a+c-l;i=Math.max(Tk,i-f)}let d=Math.max(2,e-n-i-o),u=Math.max(c,Math.round(d*E$)),m=d-u;if(m<a)m=Math.min(a,Math.max(1,d-c)),u=Math.max(1,d-m);let p=n+i+o+m+u;if(p<e)u+=e-p;return{tree:n,service:i,name:Math.max(1,m),duration:o,bar:Math.max(1,u)}}function jk(e){return e.tree+e.service+e.name+e.duration}function Bk(e,t,r=b$,n=!1){let o=Math.max(0,Math.floor((r-Dk-Ok)/Lf)),s=Math.min(Math.max(0,e),o)*Lf,i=`${n?"\u25B8":" "}${" ".repeat(s)}${Mk(t)}`;return U(i,r)}function Wk(e,t,r){if(r<=0)return{offset:0,width:1};let n=Math.min(e.startUnixNano,e.endUnixNano),o=Math.max(e.startUnixNano,e.endUnixNano),s=hd((n-t)/r),i=hd((o-t)/r);return{offset:s,width:Math.max(0,i-s)}}function D$(e,t,r){if(r<=0)return{start:0,end:0};let n=hd(e),o=hd(e+t),s=Math.floor(n*r),i=Math.ceil(o*r);if(i<=s)i=s+1;return s=Math.max(0,Math.min(r-1,s)),i=Math.max(s+1,Math.min(r,i)),{start:s,end:i}}function Gk(e,t,r){if(r<=0)return{lead:"",fill:"",trail:""};let{start:n,end:o}=D$(e,t,r);return{lead:"\u2591".repeat(n),fill:"\u2588".repeat(o-n),trail:"\u2591".repeat(r-o)}}function qk(e,t){if(t<=0)return"";let r="0",n=ts(e/2),o=ts(e);if(t<r.length+n.length+o.length+2)return O$(o,t);let s=Math.max(r.length+1,Math.floor((t-n.length)/2)),i=Array.from({length:t},()=>"\u2500");return If(i,0,r),If(i,s,n),If(i,t-o.length,o),i.join("")}function Kk(e){return{tree:U("",e.tree),service:U("SERVICE",e.service),name:U("SPAN",e.name),duration:U("DUR",e.duration),bar:U("TIME",e.bar)}}function yd(e,t){if(t<=0)return 0;return Math.max(0,Math.min(t-1,e))}function Vk(e,t,r,n,o=S$){return e===r&&n-t<=o}function zk(e,t){return Math.max(0,(t-e.startUnixNano)/pr)}function Yk(e){let t=e.spanCount===1?"1 span":`${e.spanCount} spans`,r=e.serviceCount===1?"1 service":`${e.serviceCount} services`,n=e.errorCount===0?"no errors":e.errorCount===1?"1 error":`${e.errorCount} errors`;return[{id:"dur",label:ts(e.durationMs),tone:"primary"},{id:"spans",label:t,tone:"ghost"},{id:"svc",label:r,tone:"ghost"},{id:"err",label:n,tone:e.errorCount===0?"success":"error"}]}function Jk(e,t){let r=[],n=0;for(let s of e){let i=s.label.length+Ak;if(n+i>t)break;r.push(s),n+=i}if(r.length>0)return r;let o=e[0];return o?[{...o,label:we(o.label,Math.max(1,t-Ak))}]:[]}function Xk(e,t){if(e.length===0||t<=0)return"";return we(e.join(" \xB7 "),t)}function Qk(e){return we(w$,e)}function Zk(e,t){return{w:Math.max(Of,e-2),h:Math.max(Mf,t-1)}}function eC(e,t){let r=t>=A$,n=t>=I$,o=1+(r?1:0)+(n?1:0);return{listWidth:e,headerLines:o,showServices:r,showLegend:n,listHeight:Math.max(6,t-o)}}function hd(e){if(!Number.isFinite(e))return 0;return Math.min(1,Math.max(0,e))}function If(e,t,r){for(let n=0;n<r.length;n+=1){let o=t+n;if(o>=0&&o<e.length)e[o]=r[n]??"\u2500"}}function O$(e,t){if(e.length>=t)return e.slice(0,t);return e.padStart(t)}var Of=64,Mf=16,b$=6,x$=7,Nf=5,w$="\u25C0 server \u25B6 client \u25CF internal \u25B2 producer \u25BC consumer",S$=400,Lk=16,Lf=2,Dk=1,Ok=1,Tk=6,k$=16,C$=14,Pk=10,R$=12,_$=6,T$=14,P$=8,E$=0.55,Ek=56,Ak=2,A$=20,I$=16,Ik=1000;var vd=A(()=>{tt()});function Hf(e){return e==="slash"||e==="config-edit"||e==="setup-wizard"}function tC(e,t){return On(e,t.keybinds.leader??cs("x"))}function rC(e,t){return On(e,t.keybinds.command_list??cs("p"))}function an(e,t){return On(e,t.keybinds.command??"/")||e.name==="/"}function nC(e,t){return On(e,t.keybinds.help??"?")||e.sequence==="?"}function oC(e,t){return On(e,t.keybinds.search??"f")}function sC(e){return(e.name??"").toLowerCase()==="q"&&!e.ctrl&&!e.meta&&!e.super&&!e.alt&&!e.option}function iC(e){let t=e.name??"";return t==="R"||e.shift===!0&&t.toLowerCase()==="r"}function aC(e){return Qr(e)&&(e.name??"").toLowerCase()==="l"}function cC(e,t){return On(e,t.keybinds.copy??yn())}function lC(e,t,r=M$){return t>0&&e-t<=r}function ro(e,t,r,n){return On(e,t.keybinds[r]??n)}function Qs(e){let t=(e.name??"").toLowerCase();return t==="pagedown"||Qr(e)&&t==="d"}function Zs(e){let t=(e.name??"").toLowerCase();return t==="pageup"||Qr(e)&&t==="u"}var M$=2000;var bd=A(()=>{qr()});function ei(e){return`http://127.0.0.1:${e}/mcp`}function Uf(e){if(e==="")return{};return{Authorization:`Bearer ${e}`}}function jf(e,t){return JSON.stringify({mcpServers:{devctl:{type:"http",url:e,headers:Uf(t)}}},null,2)}function Bf(e,t){return JSON.stringify({mcpServers:{devctl:{url:e,headers:Uf(t)}}},null,2)}function Wf(e,t){return JSON.stringify({mcp:{devctl:{type:"remote",url:e,headers:Uf(t),enabled:!0}}},null,2)}function Gf(e,t){let r=t===""?"":`
|
|
2214
2336
|
http_headers = { Authorization = "Bearer ${t}" }`;return`[mcp_servers.devctl]
|
|
2215
2337
|
url = "${e}"${r}
|
|
2216
|
-
`}function
|
|
2217
|
-
`)}import{jsxDEV as q}from"@opentui/react/jsx-dev-runtime";function cm(){return fp.flatMap((e)=>Lc.filter((t)=>t.category===e))}function Sl(){return wl+cm().length}function xw(){return Sl()+bw}function Bs(e){if(e<wl||e>=Sl())return;return cm()[e-wl]}function lm(e){let t=e-Sl();return t>=0&&t<bw?t:void 0}function ww(e){let{palette:t,snap:r,port:n,portDraft:o="",selected:i,onPick:s,onToggle:a,onCopy:c,onToggleTool:l}=e,d=be(),u=r?.mcp?.running===!0,m=r?.mcp?.port??n,p=r?.mcp?.address??vi(m),f=r?.mcp?.token??"",h=js(p,f),v=r?.mcp?.disabled_tools??[],b=cm(),g=b.filter((S)=>!Dn(S.name,v)).length,C=h[lm(i)??-1],E=C??h[0];return q(kt,{palette:t,title:"mcp settings",children:[q(pt,{palette:t,items:[{text:u?"RUNNING":"STOPPED",tone:u?"success":"idle"},{text:`127.0.0.1:${m}`,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(im,{palette:t,text:"Server"},void 0,!1,void 0,this),q(yw,{palette:t,active:i===sm,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(sm),onActivate:a},void 0,!1,void 0,this),q(yw,{palette:t,active:i===am,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(am)},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(im,{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),fp.map((S)=>{let y=b.filter((x)=>x.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:$I[S]??S},void 0,!1,void 0,this)},void 0,!1,void 0,this),y.map((x)=>{let w=wl+b.indexOf(x);return q(jI,{palette:t,tool:x,enabled:Dn(x.name,v),active:i===w,rowH:d.rowH,onPick:()=>s(w),onToggle:()=>l(x)},x.name,!1,void 0,this)})]},S,!0,void 0,this)}),q(im,{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 x=Sl()+y;return q(HI,{palette:t,snippet:S,active:i===x,rowH:d.rowH,onPick:()=>s(x),onCopy:()=>c(S)},S.kind,!1,void 0,this)}),q(BI,{palette:t,snippet:E,selected:Boolean(C),brand:E?Fc(E.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:NI(i,u)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function NI(e,t){if(e===sm)return[{key:"space",label:t?"stop server":"start server"},{key:"j/k",label:"move"}];if(e===am)return[{key:"\u2190\u2192",label:"change port"},{key:"0-9",label:"type port"},{key:"enter",label:"apply port"},{key:"j/k",label:"move"}];if(Bs(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 im(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 yw(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:dm,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:FI,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 HI(e){let{palette:t,snippet:r,active:n,rowH:o,onPick:i,onCopy:s}=e,a=Fc(r.kind,t),c=bb(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:dm,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 jI(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:dm+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 BI(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 sm=0,am=1,wl=2,bw=4,$I,dm=14,FI=14;var Us=I(()=>{Mt();Pe();tt();bs();$I={inspect:"Inspect",logs:"Logs",diagnostics:"Diagnostics",control:"Control",setup:"Setup"}});import{jsxDEV as Fe,Fragment as kw}from"@opentui/react/jsx-dev-runtime";function YI(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:Li(e),label:"copy selection"},{key:"/export",label:"write ~/.devctl/exports"},{key:"/exports",label:"open export folder"}]}function Nn(e){return e+GI}function Cw(e){let t=Nn(pm.length),r=Nn(mm.length),n=Nn(e.logCount),o=Nn(fm.length),i=Nn(hm.length),s=gm+e.pad*2;if(e.stacked)return s+t+r+n+o+i+um+(VI-1)*e.gap;let a=Math.max(t,r),c=Math.max(o,i);return s+a+n+c+um+(zI-1)*e.gap}function JI(e){return Cw(e)-gm-e.pad*2}function XI(e){return e-gm*2<WI}function Pw(e){let{palette:t,termW:r,termH:n,copyKey:o=en(),scrollRef:i}=e,s=vo(be()),a=XI(r),c=s?0:1,l=s?0:1,d=YI(o),u={stacked:a,logCount:d.length,pad:c,gap:l},m=Cw(u),p=JI(u);return Fe(mt,{palette:t,title:"help",bottomTitle:`${tr()} \xB7 j/k scroll \xB7 esc close`,termW:r,termH:n,preferW:UI,preferH:m,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(kw,{children:[Fe(Fn,{palette:t,title:"navigation",bindings:pm},void 0,!1,void 0,this),Fe(Fn,{palette:t,title:"services",bindings:mm},void 0,!1,void 0,this),Fe(Fn,{palette:t,title:"logs",bindings:d},void 0,!1,void 0,this),Fe(Fn,{palette:t,title:"commands",bindings:fm},void 0,!1,void 0,this),Fe(Fn,{palette:t,title:"display",bindings:hm},void 0,!1,void 0,this)]},void 0,!0,void 0,this):Fe(kw,{children:[Fe(Sw,{palette:t,left:{title:"navigation",bindings:pm},right:{title:"services",bindings:mm}},void 0,!1,void 0,this),Fe(Fn,{palette:t,title:"logs",bindings:d},void 0,!1,void 0,this),Fe(Sw,{palette:t,left:{title:"commands",bindings:fm},right:{title:"display",bindings:hm}},void 0,!1,void 0,this)]},void 0,!0,void 0,this),Fe("box",{height:um,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 Sw(e){let{palette:t,left:r,right:n}=e,o=Math.max(Nn(r.bindings.length),Nn(n.bindings.length));return Fe("box",{height:o,flexDirection:"row",flexShrink:0,overflow:"hidden",gap:qI,children:[Fe(Fn,{palette:t,title:r.title,bindings:r.bindings,height:o,stretch:!0},void 0,!1,void 0,this),Fe(Fn,{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 Fn(e){let{palette:t,title:r,bindings:n,stretch:o=!1}=e,i=e.height??Nn(n.length),s=QI(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(ZI,{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 QI(e){return e.reduce((r,n)=>Math.max(r,n.key.length),0)+KI}function ZI(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 UI=78,WI=56,GI=2,gm=2,um=1,vm=8,KI=2,qI=2,VI=6,zI=4,pm,mm,fm,hm;var ym=I(()=>{Mt();Pe();Dr();Lr();pm=[{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"}],mm=[{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)"}];fm=[{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"}],hm=[{key:It("="),label:"larger"},{key:It("-"),label:"smaller"},{key:"?",label:"this overlay"}]});function yi(e){e.setQuery(""),e.setSlashIndex(0),e.setSlashPicker("commands"),e.setOverlay("slash")}function Rw(e,t){let{overlay:r,tui:n,height:o,closeOverlay:i,confirmKind:s,onQuit:a,confirmAction:c,planBusy:l,logDetailsScrollRef:d,scrollTextScrollRef:u,routeDetailsScrollRef:m,planScrollRef:p,helpScrollRef:f,revertThemePreview:h,setPaletteIndex:v,setThemeName:b,paletteIndex:g,applyTheme:C,leaderTimer:E,setOverlay:S,runCommand:y,setConfigEditError:x,saveConfigBuffer:w,setSlashIndex:k,filtered:D,setQuery:O,slashIndex:F,submitSlash:Z,advanceWizard:G}=e,R=(t.name??"").toLowerCase();if(r==="none")return!1;if(r==="setup-wizard"){if(R==="escape")return i(),!0;if(R==="return")return G?.(),!0;return Zp(r)}if(r==="confirm"){if(R==="escape")return i(),!0;if(s==="quit"&&R==="d")return a(!0),!0;if(s==="quit"&&R==="k")return e.onDown(!0),!0;if(s==="restart-cascade"&&R==="c")return c("cascade"),!0;if(R==="return")c();return!0}if(r==="log-details"||r==="scroll-text"){if(dn(t,n))return yi(e),!0;if(R==="escape")return i(),!0;if(r==="log-details"&&R==="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(R==="down"||R==="j")return rt(j,n.scroll_speed),!0;if(R==="up"||R==="k")return rt(j,-n.scroll_speed),!0;if(Ns(t))return rt(j,mi(o)),!0;if(Hs(t))return rt(j,-mi(o)),!0;return!0}if(r==="route-details"){if(dn(t,n))return yi(e),!0;if(R==="escape"||R==="return")return i(),!0;if(R==="down"||R==="j")return rt(m.current,n.scroll_speed),!0;if(R==="up"||R==="k")return rt(m.current,-n.scroll_speed),!0;return!0}if(r==="plan"){if(dn(t,n))return yi(e),!0;if(R==="escape"||R==="return"&&!l)return i(),!0;if(R==="down"||R==="j")return rt(p.current,n.scroll_speed),!0;if(R==="up"||R==="k")return rt(p.current,-n.scroll_speed),!0;return!0}if(r==="help"){if(dn(t,n))return yi(e),!0;if(R==="escape")return i(),!0;if(R==="down"||R==="j")return rt(f.current,1),!0;if(R==="up"||R==="k")return rt(f.current,-1),!0;if(R==="pagedown"||Ns(t))return rt(f.current,vm),!0;if(R==="pageup"||Hs(t))return rt(f.current,-vm),!0;return!0}if(r==="themes"){if(dn(t,n))return h(),yi(e),!0;if(R==="escape")return h(),i(),!0;if(R==="down"||R==="j")return v((j)=>{let B=Math.min(j+1,St.length-1),z=St[B];if(z)b(z);return B}),!0;if(R==="up"||R==="k")return v((j)=>{let B=Math.max(0,j-1),z=St[B];if(z)b(z);return B}),!0;if(R==="return"){let j=St[g%St.length];if(j)C(j)}return!0}if(r==="leader"){if(dn(t,n)){if(E.current)clearTimeout(E.current);return yi(e),!0}if(E.current)clearTimeout(E.current);S("none");let j=Sx(R),B=j?Gr(j):void 0;if(B)y(B,[]);return!0}if(r==="config-edit"){if(R==="escape")return x(""),i(),!0;if(Hr(t)&&R==="s")return w(),!0;return!0}if(Zp(r)){if(R==="escape")return i(),!0;if(r==="slash"&&R==="down")return k((j)=>Math.min(Math.max(D.length-1,0),j+1)),!0;if(r==="slash"&&R==="up")return k((j)=>Math.max(0,j-1)),!0;if(r==="slash"&&R==="tab"){if(t.shift)return k((B)=>Math.max(0,B-1)),!0;let j=fi(D,F);if(j)O(Ix(j));return!0}if(r==="slash"&&R==="return")return Z(),!0;return!0}return!0}var _w=I(()=>{yr();ko();xl();Lr();Pe();ym();tt()});var kl=I(()=>{np()});function Ew(e,t){if(t.length===0)return[];let r=new Set(t);return lo(e,t).filter((n)=>!r.has(n))}function Tw(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 Cl(e){return e.waves.flat().join(" \u2192 ")}function Aw(e){let t=Cl(e);if(t==="")return"Started selected services";return`Started ${t}`}function Lw(e){let t=Cl(e);if(t==="")return"Stopped running services";return`Stopped ${t}`}function Dw(e,t,r,n="start"){let o=r?.services[e],i=rr(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=rr(r?.services[Yt(c)]);return bn(c)==="service_healthy"?l!=="HEALTHY":l==="STOPPED"||l==="FAILED"}).map(Yt);if(a.length>0)return`waits for ${a.join(", ")}`;return"queued"}function Ws(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 Iw(e,t,r="start"){let o=e.waves.flat().length,i=0,s=0,a=0,c=0;for(let f=0;f<e.waves.length;f++){let h=e.waves[f]??[],v=Ws(h,t,r);if(v==="active"||v==="unhealthy"||v==="failed")c=f;else if(v==="completed"&&c===f&&f<e.waves.length-1)c=f+1;for(let b of h){let g=t?.services[b];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),m="\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:m,currentWaveIndex:c,totalWaves:e.waves.length,isComplete:p}}function Mw(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 $w(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 Pl=I(()=>{dt();At();Lt()});function Fw(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)=>My(n,r)),!0;return!1}function Nw(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:m,checked:p,detailName:f,bootErrorMissing:h,bootError:v,logSearchFocused:b,logSources:g,logService:C,logShowTimestamps:E,logShowMeta:S,logWrap:y,showSystemLogs:x,logPinned:w,dashboardLogCursor:k,applyLogCursor:D,applyDashboardLogCursor:O,jumpToLatestLogs:F,toggleSystemLogs:Z,clearLogs:G,setLogService:R,setLogShowTimestamps:j,setLogShowMeta:B,setPaused:z,setErrorOnly:de,setLogWrap:Ee,beginStart:Ne,beginStop:ne,beginRestart:H,applyMcpPortDraft:ae,toggleMcp:Q,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:nr,setScreen:Qe,setChecked:Qt,setStatus:qe,setSelected:ht,setProfile:xr,refresh:Kt,toggleChecked:wr,createStarterConfig:or,startWizard:qt,setConfirmKind:ir,setConfirmDetail:sr,setOverlay:ar,openConfigBuffer:Vt,runCommand:ce}=e,K=(n==="logs"||n==="dashboard")&&!b;if(K&&(r==="left"||r==="["||n==="logs"&&r==="h")){R(Np(g,C,-1));return}if(K&&(r==="right"||r==="]"||n==="logs"&&r==="l")){R(Np(g,C,1));return}if((n==="dashboard"||n==="setup")&&r==="o"){Qe("profiles");return}if((n==="dashboard"||n==="services")&&(r==="*"||t.shift&&r==="8")){Qt([...d]),qe(`Selected ${d.length} services`);return}if((n==="dashboard"||n==="services")&&(r==="-"||r==="_"||r==="minus")){Qt([]),qe("Selection cleared");return}if((n==="dashboard"||n==="services")&&r==="n"){Ne(qc(p,d[l]??""),c);return}if((n==="dashboard"||n==="services")&&r==="x"){ne(qc(p,d[l]??""));return}if((n==="dashboard"||n==="services"||n==="detail")&&mw(t)){let A=qc(p,d[l]??f);if(s&&Ew(s,A).length>0){ir("restart-cascade"),sr({services:A}),ar("confirm");return}H(A,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 A=!E;j(A),gt({log_timestamps:A},A?"timestamps on":"timestamps off");return}if(n==="logs"&&r==="m"){let A=!S;B(A),gt({log_metadata:A},A?"metadata on":"metadata off");return}if(r==="down"||r==="j"){if(n==="mcp"&&l===1)ae();if(n==="config"){if(u>0)ht((A)=>Math.min(Math.max(u-1,0),A+1));rt(at.current,i.scroll_speed);return}if(n==="detail"){rt(yt.current,i.scroll_speed);return}if(n==="logs"){D(l+1);return}if(n==="dashboard"&&k>=0){O(k+1);return}ht((A)=>Math.min(Math.max(u-1,0),A+1));return}if(r==="up"||r==="k"){if(n==="mcp"&&l===1)ae();if(n==="config"){if(u>0)ht((A)=>Math.max(0,Math.min(A,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"){D(l-1);return}if(n==="dashboard"&&k>=0){O(k-1);return}ht((A)=>Math.max(0,Math.min(A,Math.max(u-1,0))-1));return}if(Ns(t)){let A=mi(m);if(n==="config"){rt(at.current,A);return}if(n==="detail"){rt(yt.current,A);return}if(n==="logs"){D(l+A);return}if(n==="dashboard"&&(w||k>=0)){O(Math.max(0,k)+A);return}ht((ve)=>Math.min(Math.max(u-1,0),ve+A));return}if(Hs(t)){let A=mi(m);if(n==="config"){rt(at.current,-A);return}if(n==="detail"){rt(yt.current,-A);return}if(n==="logs"){D(l-A);return}if(n==="dashboard"&&(w||k>=0)){O(Math.max(0,k)-A);return}ht((ve)=>Math.max(0,ve-A));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"&&f!==""){if(r==="n"){Ne([f],c);return}if(r==="x"){ne([f]);return}if(r==="o"){Qe("config");return}if(r==="l"){R(f),Qe("logs");return}}if(n==="mcp"&&l===1&&(r==="left"||r==="h")){let A=Tn(ae()-1);ie(A),Le(A);return}if(n==="mcp"&&l===1&&(r==="right"||r==="l")){let A=Tn(ae()+1);ie(A),Le(A);return}if(n==="mcp"&&l===1&&(r==="backspace"||r==="delete")){Me((A)=>Oy(A));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 J=Object.keys(s?.profiles??{}).sort()[l];if(J)xr(J),qe(`Profile ${J}`);return}if(n==="mcp"){if(l===0){Q();return}let ve=Bs(l);if(ve){nt(ve);return}ft(l);return}if(n==="logs")return;if(n==="settings"){if(In(Ke,l)?.id==="mouse")Ct();return}let A=d[l];if(A)wr(A);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}nr();return}if($n(t,i,"services","s")&&o==="none"&&n!=="logs"){Qe("services");return}if($n(t,i,"logs","l")&&o==="none"&&n!=="detail"&&n!=="logs"&&n!=="settings"){Qe("logs");return}if($n(t,i,"auth","a")&&o==="none"){Qe("auth");return}if($n(t,i,"proxy","p")&&o==="none"&&n!=="logs"){Qe("proxy");return}if($n(t,i,"doctor","d")&&o==="none"&&n!=="doctor"){Qe("doctor");return}if($n(t,i,"config","c")&&o==="none"){Qe("config");return}if($n(t,i,"setup","u")&&o==="none"){Qe("setup");return}if(n==="config"&&r==="e"){ce(Gr("/edit")??{name:"edit",aliases:[],desc:"",leader:"",group:"ui"},[]);return}if(n==="config"&&r==="v"){Vt();return}if(n==="doctor"&&r==="r"){ge((A)=>A+1),qe("Re-running doctor");return}if(n==="logs"&&r==="p"){z((A)=>!A);return}if((n==="logs"||n==="dashboard")&&r==="g"){F();return}if((n==="logs"||n==="dashboard")&&r==="w"){let A=nl(y);Ee(A),qe(`Log ${il(A)}`);return}if((n==="logs"||n==="dashboard")&&r==="e"){de((A)=>!A);return}if((n==="logs"||n==="dashboard")&&r==="i"){Z(),qe(x?"Hiding internal auth/mcp/devctl/proxy logs":"Showing internal logs");return}if((n==="logs"||n==="dashboard")&&fw(t))G()}var Hw=I(()=>{kl();yr();vr();Pl();Lt();xl();Pe();Us();Dr()});import{useKeyboard as eM}from"@opentui/react";import{useCallback as bm}from"react";function jw({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:m,closeOverlay:p,confirmKind:f,confirmDetail:h,portTarget:v,profile:b,listCursor:g,names:C,runCommand:E,copySelection:S,setOverlay:y,setConfirmKind:x,setConfirmDetail:w,setPortTarget:k,setLogDetail:D,setProfile:O,setStatus:F,setSlashIndex:Z,setQuery:G,setSlashPicker:R,setScreen:j,freePort:B,openDetail:z}=o,{logSlice:de,logSliceB:Ee,logSearchFocused:Ne,logsFullscreen:ne,setLogSearchFocused:H,setLogsFullscreen:ae,splitLogs:Q,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:nr,activateSetting:Qe,applyFont:Qt,applyReset:qe,fontSize:ht,revertThemePreview:xr,setThemeName:Kt,leaderMs:wr}=c,{doctor:or}=l,{interruptArmedAt:qt,leaderTimer:ir}=d,sr=bm(()=>{if(r?.shutdown.stop_services_on_exit===void 0){x("quit"),y("confirm");return}m(r.shutdown.stop_services_on_exit===!1)},[r,m,x,y]),ar=bm((ce)=>{if(f==="quit"){m(!1);return}if(f==="reload"){p();let K=n?.restart_required??[];if(K.length>0)Xe(K,b);return}if(f==="restart-cascade"){let K=h?.services??[];p(),Xe(K,b,ce==="cascade");return}if(f==="reset-prefs"){p(),qe();return}if(f==="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((A)=>A+1)}).catch((A)=>{F(L(A))});return}p(),gt([],b)},[qe,Xe,gt,p,h,f,l,B,m,v,b,F,n]),Vt=bm(()=>{if(u==="logs"){let K=(Q&&nt===1?Ee:de)[g];if(K)D(K),y("log-details");return}if(u==="profiles"){let A=Object.keys(r?.profiles??{}).sort()[g];if(A)O(A),x("start-profile"),y("confirm");return}if(u==="doctor"){let K=or?.checks[g];if(K?.action?.kind==="free-port"){let A=K.action.holder;k(A),w({port:A.port,pid:A.pid,process:A.command}),x("free-port"),y("confirm")}return}if(u==="mcp"){if(g===0){vt();return}if(g===1){ge();return}let K=Bs(g);if(K){at(K);return}yt(g);return}if(u==="settings"){let K=In(nr,g);if(K)Qe(K);return}if(u==="config"){let K=Object.keys(r?.tasks??{}).sort()[g],A=Gr("run");if(K&&A)E(A,[K]);return}if(u==="dashboard"&&Kc(n)){gt([],b);return}let ce=C[g];if(ce&&(u==="dashboard"||u==="services"))z(ce)},[Qe,ge,gt,r,yt,or,g,de,Ee,C,z,b,E,u,w,x,D,y,k,O,nr,n,nt,Q,vt,at]);eM((ce)=>{let K=(ce.name??"").toLowerCase();if(hw(ce,e)){S();return}if(Hr(ce)&&(K==="="||K==="+"||K==="plus")){Qt(Rs(ht,1));return}if(Hr(ce)&&(K==="-"||K==="_"||K==="minus")){Qt(Rs(ht,-1));return}if(Hr(ce)&&K==="0"){Qt(Hc().font_size);return}if(u==="logs"&&!Ne&&(ce.sequence==="\\"||K==="\\")){ft(),F(Q?"Single log pane":"Split log panes");return}if(u==="logs"&&!Ne&&(ce.sequence==="|"||K==="|")){ie();return}if(Rw({...o,tui:e,confirmAction:ar,planBusy:Ct,leaderTimer:ir,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(cw(ce,e)){y("leader"),ir.current=setTimeout(()=>y("none"),wr);return}if(lw(ce,e)||dn(ce,e)){G(""),Z(0),R("commands"),y("slash");return}if(dw(ce,e)){y("help");return}if(uw(ce,e)||u==="logs"&&K==="f"&&!ce.ctrl&&!ce.meta){j("logs"),H(!0);return}if(K==="escape"||pw(ce)){if(K==="escape"){if(ne){ae(!1);return}if(u==="detail"){j("services");return}if(u==="setup"&&t){j("dashboard");return}}let J=Date.now();if(gw(J,qt.current)){qt.current=0,sr();return}qt.current=J,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((J)=>u==="logs"?!J:!0);return}if(K==="tab"){j(ce.shift?Ax(u):Tx(u));return}let A={...o,tui:e,controller:t,cfg:r,snap:n,...i,...s,...a,...c,logService:Q&&nt===1?Ke:Me,setLogService:Le,setDoctorTick:l.setDoctorTick,refreshAuth:l.refreshAuth,configScrollRef:d.configScrollRef,detailScrollRef:d.detailScrollRef,handleEnter:Vt};if(Fw(A,ce))return;let ve=ax(K);if(ve){j(ve);return}Nw(A,ce)})}var Bw=I(()=>{te();ko();On();Lt();xl();Us();Dr();Lr();_w();Hw()});import{parse as tM,stringify as rM}from"yaml";function Rl(e,t="imported"){let r=tM(e);if(!r||typeof r!=="object"||Array.isArray(r))throw Error("compose file is not a mapping");let n=r,o=bi(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(!bi(d))continue;let u=nM(l,d,i);if(s[l]=u.body,!u.container)a.push(l)}return{yaml:rM({version:1,project:{name:t},services:s,profiles:{default:{services:a}}}),dropped:i,services:Object.keys(s),profileServices:a}}function _l(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(`
|
|
2338
|
+
`}function N$(e){return`codex mcp add --url ${e}`}function Ra(e,t){return[{kind:"claude",title:"Claude",path:".mcp.json",language:"json",text:jf(e,t),hint:"claude mcp add-json"},{kind:"cursor",title:"Cursor",path:".cursor/mcp.json",language:"json",text:Bf(e,t)},{kind:"kilo",title:"Kilo Code",path:"kilo.jsonc",language:"json",text:Wf(e,t)},{kind:"codex",title:"Codex",path:"~/.codex/config.toml",language:"toml",text:Gf(e,t),hint:N$(e)}]}function dC(e,t){let r=[`URL ${e}`,""];for(let n of Ra(e,t))r.push(`# ${n.title} ${n.path}${n.hint?` (${n.hint})`:""}`),r.push(n.text.trimEnd()),r.push("");return r.join(`
|
|
2339
|
+
`)}import{jsxDEV as z}from"@opentui/react/jsx-dev-runtime";function zf(){return Wm.flatMap((e)=>Il.filter((t)=>t.category===e))}function wd(){return xd+zf().length}function fC(){return wd()+mC}function _a(e){if(e<xd||e>=wd())return;return zf()[e-xd]}function Yf(e){let t=e-wd();return t>=0&&t<mC?t:void 0}function hC(e){let{palette:t,snap:r,port:n,portDraft:o="",selected:s,onPick:i,onToggle:a,onCopy:c,onToggleTool:l}=e,d=ge(),u=r?.mcp?.running===!0,m=r?.mcp?.port??n,p=r?.mcp?.address??ei(m),f=r?.mcp?.token??"",h=Ra(p,f),g=r?.mcp?.disabled_tools??[],x=zf(),y=x.filter((E)=>!Xn(E.name,g)).length,C=h[Yf(s)??-1],_=C??h[0];return z(Et,{palette:t,title:"mcp settings",children:[z(xt,{palette:t,items:[{text:u?"RUNNING":"STOPPED",tone:u?"success":"idle"},{text:`127.0.0.1:${m}`,tone:"info"}]},void 0,!1,void 0,this),z("box",{flexGrow:1,overflow:"hidden",children:z("scrollbox",{focused:!1,stickyScroll:!1,scrollX:!1,style:Oe(t),children:z("box",{flexDirection:"column",overflow:"hidden",children:[z(qf,{palette:t,text:"Server"},void 0,!1,void 0,this),z(pC,{palette:t,active:s===Kf,rowH:d.rowH,label:"Listen",value:u?"[ ON ]":"[ OFF ]",valueTone:u?"success":"muted",hint:u?"space or enter stop":"space or enter start",onPick:()=>i(Kf),onActivate:a},void 0,!1,void 0,this),z(pC,{palette:t,active:s===Vf,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:()=>i(Vf)},void 0,!1,void 0,this),z("box",{height:1,overflow:"hidden",children:z("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),z(qf,{palette:t,text:"Tools",extra:y===0?"all enabled":`${y} disabled`},void 0,!1,void 0,this),z("box",{height:1,overflow:"hidden",children:z("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),Wm.map((E)=>{let v=x.filter((b)=>b.category===E);if(v.length===0)return null;return z("box",{flexDirection:"column",overflow:"hidden",children:[z("box",{height:1,marginTop:1,overflow:"hidden",children:z("text",{fg:t.muted,wrapMode:"none",children:$$[E]??E},void 0,!1,void 0,this)},void 0,!1,void 0,this),v.map((b)=>{let S=xd+x.indexOf(b);return z(U$,{palette:t,tool:b,enabled:Xn(b.name,g),active:s===S,rowH:d.rowH,onPick:()=>i(S),onToggle:()=>l(b)},b.name,!1,void 0,this)})]},E,!0,void 0,this)}),z(qf,{palette:t,text:"Copy agent config",extra:"highlight a row, then enter or click Copy"},void 0,!1,void 0,this),h.map((E,v)=>{let b=wd()+v;return z(H$,{palette:t,snippet:E,active:s===b,rowH:d.rowH,onPick:()=>i(b),onCopy:()=>c(E)},E.kind,!1,void 0,this)}),z(j$,{palette:t,snippet:_,selected:Boolean(C),brand:_?$l(_.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),z(Yt,{palette:t,backgroundColor:t.element,edge:"top",children:z(ft,{palette:t,hints:F$(s,u)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function F$(e,t){if(e===Kf)return[{key:"space",label:t?"stop server":"start server"},{key:"j/k",label:"move"}];if(e===Vf)return[{key:"\u2190\u2192",label:"change port"},{key:"0-9",label:"type port"},{key:"enter",label:"apply port"},{key:"j/k",label:"move"}];if(_a(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 qf(e){let t=e.extra?` \xB7 ${e.extra}`:"";return z("box",{height:1,marginTop:1,overflow:"hidden",children:z("text",{wrapMode:"none",children:[z("span",{fg:e.palette.primary,children:e.text},void 0,!1,void 0,this),z("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 pC(e){let{palette:t,active:r,rowH:n,label:o,value:s,valueTone:i,hint:a,onPick:c,onActivate:l}=e,d=i==="success"?t.success:i==="info"?t.info:t.muted;return z("box",{height:n,flexDirection:"row",overflow:"hidden",backgroundColor:r?t.highlight:t.panel,onMouseDown:()=>{if(r&&l){l();return}c()},children:[z("box",{width:2,flexShrink:0,overflow:"hidden",children:z("text",{fg:t.primary,children:r?"\u203A":" "},void 0,!1,void 0,this)},void 0,!1,void 0,this),z("box",{width:ti,flexShrink:0,overflow:"hidden",children:z("text",{fg:r?t.primary:t.text,wrapMode:"none",children:U(o,ti)},void 0,!1,void 0,this)},void 0,!1,void 0,this),z("box",{width:uC,flexShrink:0,overflow:"hidden",children:z("text",{fg:d,wrapMode:"none",children:U(s,uC)},void 0,!1,void 0,this)},void 0,!1,void 0,this),z("box",{flexGrow:1,overflow:"hidden",children:z("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 H$(e){let{palette:t,snippet:r,active:n,rowH:o,onPick:s,onCopy:i}=e,a=$l(r.kind,t),c=Uw(r.kind,t),l=r.language==="toml"?"Copy TOML":"Copy JSON";return z("box",{height:o,flexDirection:"row",overflow:"hidden",backgroundColor:n?t.highlight:t.panel,onMouseDown:s,children:[z("box",{width:2,flexShrink:0,overflow:"hidden",children:z("text",{fg:a,children:n?"\u203A":" "},void 0,!1,void 0,this)},void 0,!1,void 0,this),z("box",{width:ti,flexShrink:0,overflow:"hidden",children:z("text",{fg:a,wrapMode:"none",children:U(r.title,ti)},void 0,!1,void 0,this)},void 0,!1,void 0,this),z("box",{flexGrow:1,overflow:"hidden",children:z("text",{fg:t.muted,wrapMode:"none",children:r.path},void 0,!1,void 0,this)},void 0,!1,void 0,this),z(Fe,{palette:t,label:l,bg:a,fg:c,onMouseDown:()=>{s(),i()}},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function U$(e){let{palette:t,tool:r,enabled:n,active:o,rowH:s,onPick:i,onToggle:a}=e;return z("box",{height:s,flexDirection:"row",overflow:"hidden",backgroundColor:o?t.highlight:t.panel,onMouseDown:()=>{if(o){a();return}i()},children:[z("box",{width:2,flexShrink:0,overflow:"hidden",children:z("text",{fg:t.primary,children:o?"\u203A":" "},void 0,!1,void 0,this)},void 0,!1,void 0,this),z("box",{width:5,flexShrink:0,overflow:"hidden",children:z("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),z("box",{width:ti+4,flexShrink:0,overflow:"hidden",children:z("text",{fg:o?t.primary:n?t.text:t.muted,wrapMode:"none",children:U(r.label,ti+4)},void 0,!1,void 0,this)},void 0,!1,void 0,this),z("box",{width:7,flexShrink:0,overflow:"hidden",children:z("text",{fg:r.mutates?t.warning:t.muted,wrapMode:"none",children:U(r.mutates?"write":"read",7)},void 0,!1,void 0,this)},void 0,!1,void 0,this),z("box",{flexGrow:1,overflow:"hidden",children:z("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 j$(e){let{palette:t,snippet:r,selected:n,brand:o}=e;if(!r)return null;let s=r.hint?` \xB7 ${r.hint}`:"";return z("box",{marginTop:1,flexDirection:"column",overflow:"hidden",children:[z("text",{fg:o,wrapMode:"none",children:n?`${r.title} \xB7 paste into ${r.path}${s}`:`Preview ${r.title} \xB7 j/k to a copy row, then enter`},void 0,!1,void 0,this),z("text",{fg:t.text,children:r.text},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var Kf=0,Vf=1,xd=2,mC=4,$$,ti=14,uC=14;var Ta=A(()=>{$t();tt();Te();et();aa();$$={inspect:"Inspect",logs:"Logs",diagnostics:"Diagnostics",control:"Control",setup:"Setup"}});import{jsxDEV as je,Fragment as yC}from"@opentui/react/jsx-dev-runtime";function Y$(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:"wrap all / clip / wrap selected"},{key:"j/k",label:"move selection"},{key:fo(e),label:"copy selection"},{key:"/export",label:"write ~/.devctl/exports"},{key:"/exports",label:"open export folder"}]}function oo(e){return e+G$}function vC(e){let t=oo(Xf.length),r=oo(Qf.length),n=oo(e.logCount),o=oo(Zf.length),s=oo(eh.length),i=th+e.pad*2;if(e.stacked)return i+t+r+n+o+s+Jf+(V$-1)*e.gap;let a=Math.max(t,r),c=Math.max(o,s);return i+a+n+c+Jf+(z$-1)*e.gap}function J$(e){return vC(e)-th-e.pad*2}function X$(e){return e-th*2<W$}function bC(e){let{palette:t,termW:r,termH:n,copyKey:o=yn(),scrollRef:s}=e,i=_n(ge()),a=X$(r),c=i?0:1,l=i?0:1,d=Y$(o),u={stacked:a,logCount:d.length,pad:c,gap:l},m=vC(u),p=J$(u);return je(rt,{palette:t,title:"help",bottomTitle:`${hr()} \xB7 j/k scroll \xB7 esc close`,termW:r,termH:n,preferW:B$,preferH:m,gap:0,children:je("scrollbox",{ref:s,focused:!1,stickyScroll:!1,scrollX:!1,style:Oe(t),children:je("box",{height:p,flexDirection:"column",flexShrink:0,overflow:"hidden",gap:l,children:[a?je(yC,{children:[je(no,{palette:t,title:"navigation",bindings:Xf},void 0,!1,void 0,this),je(no,{palette:t,title:"services",bindings:Qf},void 0,!1,void 0,this),je(no,{palette:t,title:"logs",bindings:d},void 0,!1,void 0,this),je(no,{palette:t,title:"commands",bindings:Zf},void 0,!1,void 0,this),je(no,{palette:t,title:"display",bindings:eh},void 0,!1,void 0,this)]},void 0,!0,void 0,this):je(yC,{children:[je(gC,{palette:t,left:{title:"navigation",bindings:Xf},right:{title:"services",bindings:Qf}},void 0,!1,void 0,this),je(no,{palette:t,title:"logs",bindings:d},void 0,!1,void 0,this),je(gC,{palette:t,left:{title:"commands",bindings:Zf},right:{title:"display",bindings:eh}},void 0,!1,void 0,this)]},void 0,!0,void 0,this),je("box",{height:Jf,flexShrink:0,overflow:"hidden",paddingLeft:1,children:je("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 gC(e){let{palette:t,left:r,right:n}=e,o=Math.max(oo(r.bindings.length),oo(n.bindings.length));return je("box",{height:o,flexDirection:"row",flexShrink:0,overflow:"hidden",gap:K$,children:[je(no,{palette:t,title:r.title,bindings:r.bindings,height:o,stretch:!0},void 0,!1,void 0,this),je(no,{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 no(e){let{palette:t,title:r,bindings:n,stretch:o=!1}=e,s=e.height??oo(n.length),i=Q$(n);return je("box",{height:s,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)=>je("box",{height:1,flexShrink:0,overflow:"hidden",paddingLeft:1,paddingRight:1,children:je(Z$,{palette:t,binding:a,keyWidth:i},void 0,!1,void 0,this)},`${a.key}-${a.label}`,!1,void 0,this))},void 0,!1,void 0,this)}function Q$(e){return e.reduce((r,n)=>Math.max(r,n.key.length),0)+q$}function Z$(e){let{palette:t,binding:r,keyWidth:n}=e;return je("box",{height:1,flexDirection:"row",overflow:"hidden",children:[je("box",{width:n,flexShrink:0,overflow:"hidden",children:je("text",{fg:t.primary,wrapMode:"none",children:U(r.key,n)},void 0,!1,void 0,this)},void 0,!1,void 0,this),je("box",{flexGrow:1,overflow:"hidden",children:je("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 B$=78,W$=56,G$=2,th=2,Jf=1,rh=8,q$=2,K$=2,V$=6,z$=4,Xf,Qf,Zf,eh;var nh=A(()=>{$t();tt();Te();Lr();qr();Xf=[{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"}],Qf=[{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)"}];Zf=[{key:Ht("c"),label:"copy highlighted selection"},{key:"/",label:"command overlay"},{key:Ht("p"),label:"same overlay"},{key:Ht("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"}],eh=[{key:Ht("="),label:"larger"},{key:Ht("-"),label:"smaller"},{key:"?",label:"this overlay"}]});function rs(e){e.setQuery(""),e.setSlashIndex(0),e.setSlashPicker("commands"),e.setOverlay("slash")}function xC(e,t){let{overlay:r,tui:n,height:o,closeOverlay:s,confirmKind:i,onQuit:a,confirmAction:c,planBusy:l,logDetailsScrollRef:d,traceScrollRef:u,traceDetailScrollRef:m,scrollTextScrollRef:p,routeDetailsScrollRef:f,planScrollRef:h,helpScrollRef:g,revertThemePreview:x,setPaletteIndex:y,setThemeName:C,paletteIndex:_,applyTheme:E,leaderTimer:v,setOverlay:b,runCommand:S,setConfigEditError:w,saveConfigBuffer:D,setSlashIndex:O,filtered:B,setQuery:J,slashIndex:V,submitSlash:X,advanceWizard:te}=e,k=(t.name??"").toLowerCase();if(r==="none")return!1;if(r==="setup-wizard"){if(k==="escape")return s(),!0;if(k==="return")return te?.(),!0;return Hf(r)}if(r==="confirm"){if(k==="escape")return s(),!0;if(i==="quit"&&k==="d")return a(!0),!0;if(i==="quit"&&k==="k")return e.onDown(!0),!0;if(i==="restart-cascade"&&k==="c")return c("cascade"),!0;if(k==="return")c();return!0}if(r==="span-details"){if(an(t,n))return rs(e),!0;if(k==="escape")return e.setOverlay("trace"),!0;if(k==="down"||k==="j")return He(m.current,n.scroll_speed),!0;if(k==="up"||k==="k")return He(m.current,-n.scroll_speed),!0;if(Qs(t))return He(m.current,to(o)),!0;if(Zs(t))return He(m.current,-to(o)),!0;return!0}if(r==="log-details"||r==="scroll-text"||r==="trace"){if(an(t,n))return rs(e),!0;if(k==="escape")return s(),!0;if(r==="log-details"&&k==="return"){let K=e.logDetail?.traceId?.trim()??"";if(K!=="")return e.openTrace(K),!0;let ue=e.logDetail?vs(e.logDetail).trim():"";if(ue!=="")e.setLogSearch(ue),e.setScreen("logs"),e.closeOverlay(),e.setStatus(`tracing ${ue}`);return!0}if(r==="trace"&&k==="return")return e.openSpanLogs(),!0;if(r==="trace"&&(k==="down"||k==="j"))return e.setTraceSpanIndex((K)=>Math.min(Math.max(0,e.traceSpanCount-1),K+1)),!0;if(r==="trace"&&(k==="up"||k==="k"))return e.setTraceSpanIndex((K)=>Math.max(0,K-1)),!0;let N=r==="log-details"?d.current:r==="trace"?u.current:p.current;if(r!=="trace"&&(k==="down"||k==="j"))return He(N,n.scroll_speed),!0;if(r!=="trace"&&(k==="up"||k==="k"))return He(N,-n.scroll_speed),!0;if(Qs(t)){if(r==="trace")return e.setTraceSpanIndex((K)=>Math.min(Math.max(0,e.traceSpanCount-1),K+Nf)),!0;return He(N,to(o)),!0}if(Zs(t)){if(r==="trace")return e.setTraceSpanIndex((K)=>Math.max(0,K-Nf)),!0;return He(N,-to(o)),!0}return!0}if(r==="route-details"){if(an(t,n))return rs(e),!0;if(k==="escape"||k==="return")return s(),!0;if(k==="down"||k==="j")return He(f.current,n.scroll_speed),!0;if(k==="up"||k==="k")return He(f.current,-n.scroll_speed),!0;return!0}if(r==="plan"){if(an(t,n))return rs(e),!0;if(k==="escape"||k==="return"&&!l)return s(),!0;if(k==="down"||k==="j")return He(h.current,n.scroll_speed),!0;if(k==="up"||k==="k")return He(h.current,-n.scroll_speed),!0;return!0}if(r==="help"){if(an(t,n))return rs(e),!0;if(k==="escape")return s(),!0;if(k==="down"||k==="j")return He(g.current,1),!0;if(k==="up"||k==="k")return He(g.current,-1),!0;if(k==="pagedown"||Qs(t))return He(g.current,rh),!0;if(k==="pageup"||Zs(t))return He(g.current,-rh),!0;return!0}if(r==="themes"){if(an(t,n))return x(),rs(e),!0;if(k==="escape")return x(),s(),!0;if(k==="down"||k==="j")return y((N)=>{let K=Math.min(N+1,Pt.length-1),ue=Pt[K];if(ue)C(ue);return K}),!0;if(k==="up"||k==="k")return y((N)=>{let K=Math.max(0,N-1),ue=Pt[K];if(ue)C(ue);return K}),!0;if(k==="return"){let N=Pt[_%Pt.length];if(N)E(N)}return!0}if(r==="leader"){if(an(t,n)){if(v.current)clearTimeout(v.current);return rs(e),!0}if(v.current)clearTimeout(v.current);b("none");let N=jS(k),K=N?sn(N):void 0;if(K)S(K,[]);return!0}if(r==="config-edit"){if(k==="escape")return w(""),s(),!0;if(Qr(t)&&k==="s")return D(),!0;return!0}if(Hf(r)){if(k==="escape")return s(),!0;if(r==="slash"&&k==="down")return O((N)=>Math.min(Math.max(B.length-1,0),N+1)),!0;if(r==="slash"&&k==="up")return O((N)=>Math.max(0,N-1)),!0;if(r==="slash"&&k==="tab"){if(t.shift)return O((K)=>Math.max(0,K-1)),!0;let N=Ys(B,V);if(N)J(QS(N));return!0}if(r==="slash"&&k==="return")return X(),!0;return!0}return!0}var wC=A(()=>{vd();vr();Zo();bd();qr();Te();nh();et();en()});var Sd=A(()=>{Pm()});function SC(e,t){if(t.length===0)return[];let r=new Set(t);return Ho(e,t).filter((n)=>!r.has(n))}function kC(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 kd(e){return e.waves.flat().join(" \u2192 ")}function CC(e){let t=kd(e);if(t==="")return"Started selected services";return`Started ${t}`}function RC(e){let t=kd(e);if(t==="")return"Stopped running services";return`Stopped ${t}`}function _C(e,t,r,n="start"){let o=r?.services[e],s=gr(o);if(o?.last_error&&n!=="stop")return o.last_error;if(n==="stop"){if(s==="STOPPED")return"stopped";if(s==="STOPPING")return"stopping now";if(s==="FAILED")return"already failed";return"queued"}if(s==="HEALTHY")return"ready";if(s==="STARTING")return"starting now";if(s==="RUNNING")return"up, waiting for health";if(s==="UNHEALTHY")return"unhealthy \u2014 retrying health check";if(s==="STOPPING")return"stopping first";if(s==="FAILED")return"failed \u2014 later waves will not start";let a=(t.steps.find((c)=>c.name===e)?.dependencies??[]).filter((c)=>{let l=gr(r?.services[or(c)]);return Fn(c)==="service_healthy"?l!=="HEALTHY":l==="STOPPED"||l==="FAILED"}).map(or);if(a.length>0)return`waits for ${a.join(", ")}`;return"queued"}function Pa(e,t,r="start"){if(e.length===0)return"completed";let n=e.map((s)=>t?.services[s]);if(n.some((s)=>s?.state==="FAILED"))return"failed";if(r==="stop"){if(n.every((s)=>!s||s.state==="STOPPED"||s.state==="UNKNOWN"))return"completed";if(n.some((s)=>s?.state==="STOPPING"))return"active";return"queued"}let o=(s)=>Boolean(s&&(s.state==="HEALTHY"||s.health==="HEALTHY"||s.state==="RUNNING"&&s.health!=="UNHEALTHY"));if(n.every(o))return"completed";if(n.some((s)=>s?.state==="UNHEALTHY"||s?.health==="UNHEALTHY"))return"unhealthy";if(n.some((s)=>s?.state==="STARTING"||s?.state==="RUNNING"||s?.state==="RESTARTING"))return"active";return"queued"}function TC(e,t,r="start"){let o=e.waves.flat().length,s=0,i=0,a=0,c=0;for(let f=0;f<e.waves.length;f++){let h=e.waves[f]??[],g=Pa(h,t,r);if(g==="active"||g==="unhealthy"||g==="failed")c=f;else if(g==="completed"&&c===f&&f<e.waves.length-1)c=f+1;for(let x of h){let y=t?.services[x];if(r==="stop"?!y||y.state==="STOPPED"||y.state==="UNKNOWN":Boolean(y&&(y.state==="HEALTHY"||y.health==="HEALTHY"||y.state==="RUNNING"&&y.health!=="UNHEALTHY")))s++;else if(y?.state==="FAILED")i++;else if(y?.state==="STARTING"||y?.state==="RUNNING"||y?.state==="UNHEALTHY"||y?.health==="UNHEALTHY"||y?.state==="STOPPING"||y?.state==="RESTARTING")a++}}let l=o>0?Math.round(s/o*100):100,d=16,u=Math.round(l/100*d),m="\u2588".repeat(u)+"\u2591".repeat(Math.max(0,d-u)),p=s===o&&o>0;return{total:o,ready:s,active:a,failed:i,percent:l,progressBar:m,currentWaveIndex:c,totalWaves:e.waves.length,isComplete:p}}function PC(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 EC(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 AC(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 Cd=A(()=>{lt();Nt();Ft()});function IC(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)=>Jx(n,r)),!0;return!1}function LC(e,t){let r=(t.name??"").toLowerCase(),{screen:n,overlay:o,tui:s,cfg:i,controller:a,profile:c,listCursor:l,names:d,listCount:u,height:m,checked:p,detailName:f,bootErrorMissing:h,bootError:g,logSearchFocused:x,logSources:y,logService:C,logShowTimestamps:_,logShowMeta:E,logWrap:v,showSystemLogs:b,logPinned:S,dashboardLogCursor:w,applyLogCursor:D,applyDashboardLogCursor:O,jumpToLatestLogs:B,toggleSystemLogs:J,clearLogs:V,setLogService:X,setLogShowTimestamps:te,setLogShowMeta:k,setPaused:N,setErrorOnly:K,setLogWrap:ue,beginStart:We,beginStop:ie,beginRestart:W,applyMcpPortDraft:le,toggleMcp:re,toggleMcpTool:pt,copyFocusedMcpSnippet:wt,persistMcpPort:ae,restartMcpOnPort:Le,setMcpPortDraft:Ne,settingRows:Je,persistPrefs:St,cycleSetting:st,toggleMouse:At,setDoctorTick:Se,refreshAuth:kt,configScrollRef:gt,detailScrollRef:Ct,handleEnter:br,setScreen:it,setChecked:It,setStatus:Xe,setSelected:Rt,setProfile:ar,refresh:cr,toggleChecked:Yr,createStarterConfig:lr,startWizard:Zt,setConfirmKind:xr,setConfirmDetail:wr,setOverlay:Sr,openConfigBuffer:Bt,runCommand:de}=e,Z=(n==="logs"||n==="dashboard")&&!x;if(Z&&(r==="left"||r==="["||n==="logs"&&r==="h")){X(gf(y,C,-1));return}if(Z&&(r==="right"||r==="]"||n==="logs"&&r==="l")){X(gf(y,C,1));return}if((n==="dashboard"||n==="setup")&&r==="o"){it("profiles");return}if((n==="dashboard"||n==="services")&&(r==="*"||t.shift&&r==="8")){It([...d]),Xe(`Selected ${d.length} services`);return}if((n==="dashboard"||n==="services")&&(r==="-"||r==="_"||r==="minus")){It([]),Xe("Selection cleared");return}if((n==="dashboard"||n==="services")&&r==="n"){We(Kl(p,d[l]??""),c);return}if((n==="dashboard"||n==="services")&&r==="x"){ie(Kl(p,d[l]??""));return}if((n==="dashboard"||n==="services"||n==="detail")&&iC(t)){let L=Kl(p,d[l]??f);if(i&&SC(i,L).length>0){xr("restart-cascade"),wr({services:L}),Sr("confirm");return}W(L,c);return}if((n==="dashboard"||n==="services")&&r==="r"){cr(),Xe("Refreshed");return}if(n==="auth"&&r==="r"){kt();return}if(n==="logs"&&r==="t"){let L=!_;te(L),St({log_timestamps:L},L?"timestamps on":"timestamps off");return}if(n==="logs"&&r==="m"){let L=!E;k(L),St({log_metadata:L},L?"metadata on":"metadata off");return}if(r==="down"||r==="j"){if(n==="mcp"&&l===1)le();if(n==="config"){if(u>0)Rt((L)=>Math.min(Math.max(u-1,0),L+1));He(gt.current,s.scroll_speed);return}if(n==="detail"){He(Ct.current,s.scroll_speed);return}if(n==="logs"){D(l+1);return}if(n==="dashboard"&&w>=0){O(w+1);return}Rt((L)=>Math.min(Math.max(u-1,0),L+1));return}if(r==="up"||r==="k"){if(n==="mcp"&&l===1)le();if(n==="config"){if(u>0)Rt((L)=>Math.max(0,Math.min(L,Math.max(u-1,0))-1));He(gt.current,-s.scroll_speed);return}if(n==="detail"){He(Ct.current,-s.scroll_speed);return}if(n==="logs"){D(l-1);return}if(n==="dashboard"&&w>=0){O(w-1);return}Rt((L)=>Math.max(0,Math.min(L,Math.max(u-1,0))-1));return}if(Qs(t)){let L=to(m);if(n==="config"){He(gt.current,L);return}if(n==="detail"){He(Ct.current,L);return}if(n==="logs"){D(l+L);return}if(n==="dashboard"&&(S||w>=0)){O(Math.max(0,w)+L);return}Rt((ve)=>Math.min(Math.max(u-1,0),ve+L));return}if(Zs(t)){let L=to(m);if(n==="config"){He(gt.current,-L);return}if(n==="detail"){He(Ct.current,-L);return}if(n==="logs"){D(l-L);return}if(n==="dashboard"&&(S||w>=0)){O(Math.max(0,w)-L);return}Rt((ve)=>Math.max(0,ve-L));return}if(n==="proxy"&&r==="n"){a?.proxyStart().then(async()=>{Xe("Proxy started"),await cr()});return}if(n==="proxy"&&r==="x"){a?.proxyStop().then(async()=>{Xe("Proxy stopped"),await cr()});return}if(n==="detail"&&f!==""){if(r==="n"){We([f],c);return}if(r==="x"){ie([f]);return}if(r==="o"){it("config");return}if(r==="l"){X(f),it("logs");return}}if(n==="mcp"&&l===1&&(r==="left"||r==="h")){let L=Yn(le()-1);ae(L),Le(L);return}if(n==="mcp"&&l===1&&(r==="right"||r==="l")){let L=Yn(le()+1);ae(L),Le(L);return}if(n==="mcp"&&l===1&&(r==="backspace"||r==="delete")){Ne((L)=>Xx(L));return}if(n==="settings"&&(r==="left"||r==="h")){st(-1,l);return}if(n==="settings"&&(r==="right"||r==="l")){st(1,l);return}if(r==="space"){if(n==="profiles"){let mt=Object.keys(i?.profiles??{}).sort()[l];if(mt)ar(mt),Xe(`Profile ${mt}`);return}if(n==="mcp"){if(l===0){re();return}let ve=_a(l);if(ve){pt(ve);return}wt(l);return}if(n==="logs")return;if(n==="settings"){if(Qn(Je,l)?.id==="mouse")At();return}let L=d[l];if(L)Yr(L);return}if(r==="return"){if(n==="setup"&&!a){if(!h){Xe(g||"Existing configuration is invalid \u2014 fix it and restart devctl.");return}Zt();return}if(n==="setup"&&a){We([],c);return}br();return}if(ro(t,s,"services","s")&&o==="none"&&n!=="logs"){it("services");return}if(ro(t,s,"logs","l")&&o==="none"&&n!=="detail"&&n!=="logs"&&n!=="settings"){it("logs");return}if(ro(t,s,"auth","a")&&o==="none"){it("auth");return}if(ro(t,s,"proxy","p")&&o==="none"&&n!=="logs"){it("proxy");return}if(ro(t,s,"doctor","d")&&o==="none"&&n!=="doctor"){it("doctor");return}if(ro(t,s,"config","c")&&o==="none"){it("config");return}if(ro(t,s,"setup","u")&&o==="none"){it("setup");return}if(n==="config"&&r==="e"){de(sn("/edit")??{name:"edit",aliases:[],desc:"",leader:"",group:"ui"},[]);return}if(n==="config"&&r==="v"){Bt();return}if(n==="doctor"&&r==="r"){Se((L)=>L+1),Xe("Re-running doctor");return}if(n==="logs"&&r==="p"){N((L)=>!L);return}if((n==="logs"||n==="dashboard")&&r==="g"){B();return}if((n==="logs"||n==="dashboard")&&r==="w"){let L=Zl(v);ue(L),Xe(`Log ${td(L)}`);return}if((n==="logs"||n==="dashboard")&&r==="e"){K((L)=>!L);return}if((n==="logs"||n==="dashboard")&&r==="i"){J(),Xe(b?"Hiding internal auth/mcp/devctl/proxy logs":"Showing internal logs");return}if((n==="logs"||n==="dashboard")&&aC(t))V()}var DC=A(()=>{Sd();vr();yr();Cd();Ft();bd();Te();Ta();Lr()});import{useKeyboard as eF}from"@opentui/react";import{useCallback as oh}from"react";function OC({tui:e,controller:t,cfg:r,snap:n,ui:o,logView:s,lifecycleActions:i,mcp:a,preferences:c,diagnostics:l,refs:d}){let{screen:u,onQuit:m,closeOverlay:p,confirmKind:f,confirmDetail:h,portTarget:g,profile:x,listCursor:y,names:C,runCommand:_,copySelection:E,setOverlay:v,setConfirmKind:b,setConfirmDetail:S,setPortTarget:w,setLogDetail:D,setProfile:O,setStatus:B,setSlashIndex:J,setQuery:V,setSlashPicker:X,setScreen:te,freePort:k,openDetail:N}=o,{logSlice:K,logSliceB:ue,logSearchFocused:We,logsFullscreen:ie,setLogSearchFocused:W,setLogsFullscreen:le,splitLogs:re,splitFocus:pt,toggleSplitLogs:wt,cycleSplitFocus:ae,setActiveLogService:Le,logService:Ne,logServiceB:Je}=s,{beginStart:St,beginRestart:st,planBusy:At}=i,{applyMcpPortDraft:Se,toggleMcp:kt,toggleMcpTool:gt,copyFocusedMcpSnippet:Ct}=a,{settingRows:br,activateSetting:it,applyFont:It,applyReset:Xe,fontSize:Rt,revertThemePreview:ar,setThemeName:cr,leaderMs:Yr}=c,{doctor:lr}=l,{interruptArmedAt:Zt,leaderTimer:xr}=d,wr=oh(()=>{if(r?.shutdown.stop_services_on_exit===void 0){b("quit"),v("confirm");return}m(r.shutdown.stop_services_on_exit===!1)},[r,m,b,v]),Sr=oh((de)=>{if(f==="quit"){m(!1);return}if(f==="reload"){p();let Z=n?.restart_required??[];if(Z.length>0)st(Z,x);return}if(f==="restart-cascade"){let Z=h?.services??[];p(),st(Z,x,de==="cascade");return}if(f==="reset-prefs"){p(),Xe();return}if(f==="free-port"){let Z=g;if(p(),!Z)return;k(Z).then(()=>{B(`Stopped ${Z.command} (pid ${Z.pid}) on port ${Z.port}`),l.setDoctorTick((L)=>L+1)}).catch((L)=>{B(I(L))});return}p(),St([],x)},[Xe,st,St,p,h,f,l,k,m,g,x,B,n]),Bt=oh(()=>{if(u==="logs"){let Z=(re&&pt===1?ue:K)[y];if(Z)D(Z),v("log-details");return}if(u==="profiles"){let L=Object.keys(r?.profiles??{}).sort()[y];if(L)O(L),b("start-profile"),v("confirm");return}if(u==="doctor"){let Z=lr?.checks[y];if(Z?.action?.kind==="free-port"){let L=Z.action.holder;w(L),S({port:L.port,pid:L.pid,process:L.command}),b("free-port"),v("confirm")}return}if(u==="mcp"){if(y===0){kt();return}if(y===1){Se();return}let Z=_a(y);if(Z){gt(Z);return}Ct(y);return}if(u==="settings"){let Z=Qn(br,y);if(Z)it(Z);return}if(u==="config"){let Z=Object.keys(r?.tasks??{}).sort()[y],L=sn("run");if(Z&&L)_(L,[Z]);return}if(u==="dashboard"&&ql(n)){St([],x);return}let de=C[y];if(de&&(u==="dashboard"||u==="services"))N(de)},[it,Se,St,r,Ct,lr,y,K,ue,C,N,x,_,u,S,b,D,v,w,O,br,n,pt,re,kt,gt]);eF((de)=>{let Z=(de.name??"").toLowerCase();if(cC(de,e)){E();return}if(Qr(de)&&(Z==="="||Z==="+"||Z==="plus")){It(fa(Rt,1));return}if(Qr(de)&&(Z==="-"||Z==="_"||Z==="minus")){It(fa(Rt,-1));return}if(Qr(de)&&Z==="0"){It(Hl().font_size);return}if(u==="logs"&&!We&&(de.sequence==="\\"||Z==="\\")){wt(),B(re?"Single log pane":"Split log panes");return}if(u==="logs"&&!We&&(de.sequence==="|"||Z==="|")){ae();return}if(xC({...o,tui:e,confirmAction:Sr,planBusy:At,leaderTimer:xr,logDetailsScrollRef:d.logDetailsScrollRef,traceScrollRef:d.traceScrollRef,traceDetailScrollRef:d.traceDetailScrollRef,scrollTextScrollRef:d.scrollTextScrollRef,routeDetailsScrollRef:d.routeDetailsScrollRef,planScrollRef:d.planScrollRef,helpScrollRef:d.helpScrollRef,revertThemePreview:ar,setThemeName:cr},de))return;if(We){if(Z==="escape")W(!1);return}if(tC(de,e)){v("leader"),xr.current=setTimeout(()=>v("none"),Yr);return}if(rC(de,e)||an(de,e)){V(""),J(0),X("commands"),v("slash");return}if(nC(de,e)){v("help");return}if(oC(de,e)||u==="logs"&&Z==="f"&&!de.ctrl&&!de.meta){te("logs"),W(!0);return}if(Z==="escape"||sC(de)){if(Z==="escape"){if(ie){le(!1);return}if(u==="detail"){te("services");return}if(u==="setup"&&t){te("dashboard");return}}let mt=Date.now();if(lC(mt,Zt.current)){Zt.current=0,wr();return}Zt.current=mt,B(Z==="q"?"q again to quit":"Esc again to quit");return}if(Z==="z"&&(u==="logs"||u==="dashboard")&&!de.ctrl&&!de.meta){te("logs"),le((mt)=>u==="logs"?!mt:!0);return}if(Z==="tab"){te(de.shift?YS(u):zS(u));return}let L={...o,tui:e,controller:t,cfg:r,snap:n,...s,...i,...a,...c,logService:re&&pt===1?Je:Ne,setLogService:Le,setDoctorTick:l.setDoctorTick,refreshAuth:l.refreshAuth,configScrollRef:d.configScrollRef,detailScrollRef:d.detailScrollRef,handleEnter:Bt};if(IC(L,de))return;let ve=_S(Z);if(ve){te(ve);return}LC(L,de)})}var MC=A(()=>{ne();Zo();eo();Ft();bd();Ta();Lr();qr();wC();DC()});import{parse as tF,stringify as rF}from"yaml";function Rd(e,t="imported"){let r=tF(e);if(!r||typeof r!=="object"||Array.isArray(r))throw Error("compose file is not a mapping");let n=r,o=ri(n.services)?n.services:{},s=[];if(n.networks!==void 0)s.push({service:"(root)",field:"networks",reason:NC.networks??"not modeled"});let i={},a=[];for(let[l,d]of Object.entries(o)){if(!ri(d))continue;let u=nF(l,d,s);if(i[l]=u.body,!u.container)a.push(l)}return{yaml:rF({version:1,project:{name:t},services:i,profiles:{default:{services:a}}}),dropped:s,services:Object.keys(i),profileServices:a}}function _d(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(`
|
|
2218
2340
|
`)}
|
|
2219
|
-
`}function
|
|
2341
|
+
`}function nF(e,t,r){for(let m of Object.keys(t)){let p=NC[m];if(p)r.push({service:e,field:m,reason:p})}let n={},o=$C(t.command);if(o)n.command=o;let s=typeof t.working_dir==="string"?t.working_dir:typeof t.workingDir==="string"?t.workingDir:"";if(s!=="")n.working_dir=s;let i=oF(t.depends_on);if(i.length>0)n.dependencies=i;let a=sF(t.environment);if(Object.keys(a).length>0)n.environment={defaults:a};let c=iF(t.ports);if(c.host.length>0)n.ports=c.host;let l=cF(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 $C(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 oF(e){if(Array.isArray(e))return e.filter((t)=>typeof t==="string");if(ri(e))return Object.keys(e);return[]}function sF(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(ri(e))for(let[r,n]of Object.entries(e))t[r]=n===void 0||n===null?"":String(n);return t}function iF(e){let t=[],r={};if(!Array.isArray(e))return{host:t,container:r};let n=0;for(let o of e){let s=aF(o);if(!s)continue;n+=1;let i=n===1?"http":`port${n}`;t.push({name:i,value:s.host}),r[i]=s.container}return{host:t,container:r}}function aF(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(ri(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 cF(e){if(!ri(e)||e.disable===!0)return;let t=$C(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 ri(e){return Boolean(e)&&typeof e==="object"&&!Array.isArray(e)}var NC;var sh=A(()=>{NC={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 FC(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,s=[{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)s.push({text:`${n} route${n===1?"":"s"}`,tone:"info"});if(o>0)s.push({text:`${o} task${o===1?"":"s"}`,tone:"info"});return s}function HC(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 UC(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 jC(e){let t=lF(e);return[{label:"exit",value:t,tone:t==="ask on quit"?"warning":"text"},{label:"grace",value:`${e.shutdown.grace_seconds}s`},{label:"refresh",value:`${Hy(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 BC(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 WC(e){return[{label:"listen",value:jn(e.proxy.listen),tone:e.proxy.enabled?"text":"muted"},{label:"token",value:pF(e),tone:e.proxy.token_endpoint.enabled?"text":"muted"}]}function GC(e){return Object.keys(e.services).sort().map((t)=>{let r=e.services[t]??Hn();return{name:t,extends:r.extends||"\u2014",command:Vl(r),ports:Sa(r),identity:Yl(r),health:zl(r),depends:r.dependencies.map(Ec).join(", ")||"\u2014",restart:Jl(r)}})}function qC(e){return e.proxy.routes.map((t)=>({name:t.name||t.match.host||"(unnamed)",match:mF(t),upstream:t.upstream.url||"\u2014",auth:t.auth.type||"none",identity:fF(t)}))}function KC(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 VC(e){return Object.keys(e.templates).sort().map((t)=>({name:t,summary:hF(e.templates[t])}))}function zC(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 YC(e,t){let r=qn(e);if(r.length===0)return"No provenance recorded for this configuration.";let n=new Rr(e.secrets.extra_markers,e.secrets.extra_patterns);return r.map((o)=>{let s=typeof o.value==="string"?o.value:JSON.stringify(o.value),i=t?s:n.redactMap({[o.path]:s})[o.path],a=o.shadowed.length===0?"":`
|
|
2220
2342
|
shadowed ${o.shadowed.map((c)=>`${c.layer} \xB7 ${c.source}`).join("; ")}`;return`${o.path}
|
|
2221
2343
|
${o.layer} \xB7 ${o.source}
|
|
2222
|
-
${
|
|
2344
|
+
${i??""}${a}`}).join(`
|
|
2223
2345
|
|
|
2224
|
-
`)}function
|
|
2346
|
+
`)}function JC(e){let t=e.doctor.tools.map((s)=>s.name||s.command).filter((s)=>s!==""),r=e.plugins.map((s)=>s.path).filter((s)=>s!==""),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 lF(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 XC(e){let t=e.reduce((r,n)=>Math.max(r,n.name.length),0);return Math.min(uF,Math.max(dF,t))}function pF(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 mF(e){let t=e.match.host||"*",r=e.match.path;if(r==="")return t;return`${t}${r.startsWith("/")?r:`/${r}`}`}function fF(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 hF(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 dF=8,uF=20,QC=80,ni=10;var ih=A(()=>{lt();bo();Ft()});async function Td(e,t){e.suspend();try{return await t()}finally{e.resume(),e.intermediateRender()}}import{useCallback as gF,useRef as yF}from"react";function ZC({setOverlay:e,setQuery:t,setSlashPicker:r,checked:n,setConfirmKind:o,setStatus:s,persistTheme:i,setPaletteIndex:a,themeName:c,setScreen:l,setSelected:d,screen:u,renderer:m,cfg:p,setScrollText:f,reveal:h,controller:g,refresh:x,profile:y,setReveal:C,resolveEnvironment:_,openDetail:E,copySelection:v,lastExportPath:b,openConfigBuffer:S,onDown:w,workspace:D,logView:O,diagnostics:B,lifecycleActions:J}){let{loginGoogle:V,detectGoogle:X,logoutGoogle:te,bootstrapLogPath:k,fileExists:N,readTextFile:K,writeTextFile:ue,validateConfigText:We,resolveExportPath:ie,writeLogExport:W,exportsDir:le,openInFileManager:re,listSessions:pt,loadSessionEvents:wt,checkUpdate:ae,applyUpdate:Le,formatUpdateStatus:Ne}=D,{setLogService:Je,setLogsFullscreen:St,setPaused:st,setErrorOnly:At,toggleSystemLogs:Se,clearLogs:kt,logWrap:gt,setLogWrap:Ct,logServices:br,errorOnly:it,logLevel:It,logSearch:Xe,logRegex:Rt,logSource:ar,filteredLogs:cr,setLogRegex:Yr,setLogSince:lr,setLogUntil:Zt,setLogs:xr,setLogSearch:wr,toggleSplitLogs:Sr}=O,{refreshAuth:Bt,setGoogle:de}=B,{beginStart:Z,beginStop:L,beginRestart:ve}=J,mt=yF(!1);return{runCommand:gF(async(at,Ee)=>{if(mt.current)return;if(mt.current=!0,at.name!=="start"&&at.name!=="stop"&&at.name!=="restart")e("none");t("");let pn=lf(Ee,n);try{switch(at.name){case"exit":o("quit"),e("confirm");return;case"down":{let P=Ee.includes("--keep-services")||Ee.includes("-k");w(P);return}case"help":e("help");return;case"version":s(hr()),ae().then((P)=>s(Ne(P))).catch((P)=>s(I(P)));return;case"update":s("checking for update\u2026"),ae().then(async(P)=>{if(!P.newer||!P.command){s(Ne(P));return}s(`installing ${P.current} \u2192 ${P.latest} via ${P.kind}\u2026`);let F=await Le(P.command);if(F.code!==0){s(`update failed (${F.code}): ${(F.stderr||F.stdout).trim()||P.hint}`);return}s(`updated to ${P.latest}; ${fl}`)}).catch((P)=>s(I(P)));return;case"themes":if(Ee[0]){i(Ee[0]);return}a(Math.max(0,Pt.indexOf(c))),t(""),e("themes");return;case"settings":l("settings"),d(0);return;case"dashboard":case"services":l(at.name);return;case"logs":if(Ee[0])Je(Ee[0]);l("logs");return;case"fullscreen":l("logs"),St((P)=>u==="logs"?!P:!0);return;case"split":l("logs"),Sr(),s("Split log panes");return;case"trace":{let P=Ee.join(" ").trim();if(P===""){s("usage: /trace <request-or-trace-id>");return}wr(P),l("logs"),s(`tracing ${P}`);return}case"auth":{let P=(Ee[0]??"").toLowerCase();if(P==="refresh"){l("auth"),await Bt();return}if(P==="login"){l("auth"),s("starting gcloud ADC login\u2026");try{await Td(m,()=>V()),de(await X(p?.google.project_id??"")),await Bt(),s("ADC login complete")}catch(F){s(I(F))}return}if(P==="logout"){l("auth"),s("revoking ADC\u2026");try{await te(),de(await X(p?.google.project_id??"")),await Bt(),s("ADC revoked")}catch(F){s(I(F))}return}l("auth");return}case"credentials":case"proxy":case"mcp":case"config":case"profiles":case"setup":l(at.name);return;case"import":{let P=Ee[0]??"",F=Ee.includes("--write"),tr=Ee.find((dr)=>dr!=="compose"&&dr!=="--write")??"compose.yaml";if(P!=="compose"){s("usage: /import compose [path] [--write]");return}if(!N(tr)){s(`compose file not found: ${tr}`);return}let lo=p?.repoRoot||process.cwd(),Nr=p?.configPath||`${lo}/.devctl/config.yaml`;try{let dr=Rd(K(tr),lo.split("/").pop()??"imported"),mn=We(lo,Nr,dr.yaml),fn=`${_d(dr)}${mn.length>0?`
|
|
2225
2347
|
validation:
|
|
2226
|
-
${
|
|
2348
|
+
${mn.map((uo)=>` ${uo}`).join(`
|
|
2227
2349
|
`)}
|
|
2228
|
-
`:""}`;if(
|
|
2229
|
-
Enter is preview only. /import compose ${
|
|
2230
|
-
`)),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:m,setConfigEditError:p,openConfigBuffer:f,saveConfigBuffer:h}}var dS=I(()=>{te()});import{useEffect as xM}from"react";function uS({controller:e,cfg:t,paused:r,logSince:n,refresh:o,setLogs:i,setCfg:s,setConfigReloadError:a,setStatus:c}){return xM(()=>{if(!e)return;let l,d=!1,u=[],m=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)=>Jb(v,h,n,m))}if(d)d=!1,o()},f=e.onEvent((h)=>{if(h.type===Oo&&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===Ii){a(gx(h));return}if(h.type===ga)a(void 0),e.configSnapshot().then(s).catch((v)=>c(L(v)));if(d=!0,!l)l=setTimeout(p,30)});return()=>{if(f(),l)clearTimeout(l)}},[e,n,r,o]),{}}var pS=I(()=>{te();yr();vr()});import{useCallback as wM,useEffect as mS,useRef as fS,useState as wi}from"react";function hS({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,m]=wi(),[p,f]=wi(),[h,v]=wi(!1),[b,g]=wi(""),[C,E]=wi(0),[S,y]=wi({active:"Preparing diagnostics",checks:[]}),x=fS(void 0),w=fS(0),k=wM(async()=>{if(!e){a("Supervisor is not running");return}a("Probing configured identities\u2026");try{let D=await e.refreshAuth();s((G)=>G?{...G,identity:D}:G);let O=Object.values(D.service_account_status),F=O.filter((G)=>G==="available").length,Z=O.filter((G)=>G==="unavailable").length;a(O.length===0?"Identity refreshed \u2014 no service accounts configured":`Identity refreshed \u2014 ${F} available${Z>0?`, ${Z} unavailable`:""}`)}catch(D){a(L(D))}},[e]);return mS(()=>{c(r?.google.project_id??"").then(m).catch((D)=>{a(L(D))})},[r?.google.project_id,n?.identity.project]),mS(()=>{if(o!=="doctor"||!r)return;let D=x.current,O=n?.session_id??"";if(D?.cfg===r&&D.tick===C&&D.sessionID===O&&D.reloadError===i)return;x.current={cfg:r,tick:C,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(G){Z=L(G)}}l(r,(G)=>{if(F===w.current)y(G)},{services:n?.services,proxyRunning:n?.proxy.running,repositoryConfigError:Z}).then((G)=>{if(F!==w.current)return;f(G)}).catch((G)=>{if(F!==w.current)return;g(L(G))}).finally(()=>{if(F===w.current)v(!1)})},[o,r,C,n?.session_id,i]),{google:u,setGoogle:m,doctor:p,doctorLoading:h,doctorError:b,doctorProgress:S,setDoctorTick:E,refreshAuth:k}}var gS=I(()=>{te()});import{useCallback as Sm,useState as Tl}from"react";function vS({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,m]=Tl(),[p,f]=Tl([]),[h,v]=Tl(!1),[b,g]=Tl("start"),C=Sm(async(y,x)=>{if(!e||!r){i("no configuration loaded");return}try{let w=y.length>0,k=a(r,{services:y,profile:w?void 0:x}),D=c(r,k.services,k.profile);g("start"),f(D.waves.flat().filter((Z)=>bo(n?.services[Z]))),m(D),s("plan"),v(!0);let O=k.services.filter((Z)=>!bo(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(Aw(F))}catch(w){v(!1),await o(),i(L(w))}},[r,e,o,n]),E=Sm(async(y)=>{if(!e||!r){i("no configuration loaded");return}let x=y.length>0?y:Object.entries(n?.services??{}).filter(([,w])=>w.state!=="STOPPED"&&w.state!=="UNKNOWN").map(([w])=>w);if(x.length===0){i("nothing to stop");return}try{let w=l(r,x);g("stop"),f([]),m(w),s("plan"),v(!0),await e.stop(y),await o(),v(!1),i(Lw(w))}catch(w){v(!1),await o(),i(L(w))}},[r,e,o,n]),S=Sm(async(y,x,w=!1)=>{if(!e||!r){i("no configuration loaded");return}try{let k=Tw(r,y,x),D=w?l(r,k.services):d(r,k.services);g("restart"),f([]),m(D),s("plan"),v(!0),await e.restart(k.services,w),await o(),v(!1);let O=Cl(D);i(O===""?"Restarted selected services":`Restarted ${O}`)}catch(k){v(!1),await o(),i(L(k))}},[r,e,o]);return{plan:u,planInitiallyRunning:p,planBusy:h,lifecycle:b,beginStart:C,beginStop:E,beginRestart:S}}var yS=I(()=>{te();Pl();Lt()});import{useCallback as Or,useEffect as qr,useMemo as Hn,useRef as SM,useState as xe}from"react";function xS(e,t,r,n,o){let i=$p(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(ol(t.filteredLen));n(a),o(i.selected)}function wS({controller:e,tui:t,names:r,screen:n,refresh:o,setStatus:i}){let[s,a]=xe([]),[c,l]=xe(!1),[d,u]=xe(!1),[m,p]=xe(!0),[f,h]=xe(""),[v,b]=xe(!1),[g,C]=xe(0),[E,S]=xe(""),[y,x]=xe(""),[w,k]=xe(""),[D,O]=xe(""),[F,Z]=xe(!1),[G,R]=xe(0),[j,B]=xe(Mr-1),[z,de]=xe(!1),[Ee,Ne]=xe(0),ne=bS,[H,ae]=xe(t.log_timestamps!==!1),[Q,nt]=xe(t.log_metadata!==!1),[ft,ie]=xe(()=>[...Yb]),[Le]=xe(""),[Me]=xe(""),[Ke,gt]=xe(!1),[Xe,Ct]=xe("focus"),[ge,vt]=xe(!1),[at,yt]=xe(Mr-1),[nr,Qe]=xe(!1),[Qt,qe]=xe(-1),[ht,xr]=xe(),[Kt,wr]=xe(""),[or,qt]=xe(!1),[ir,sr]=xe(!1),ar=SM([]),[Vt,ce]=xe(0),K=Hn(()=>sl(r,s,ft),[r,s,ft]);qr(()=>{ie((le)=>{let bt=new Set(le),Pt=!1;for(let Qr of s)if(Qr.service!==""&&!r.includes(Qr.service)&&!bt.has(Qr.service))bt.add(Qr.service),Pt=!0;return Pt?[...bt].sort():le})},[s,r]);let A=Hn(()=>xo(s,{service:w,services:ne,errorOnly:d,search:f,regex:Ke,source:Me,since:E,until:y,systemLogs:m}),[d,Ke,f,w,ne,E,Me,y,s,m]),ve=Hn(()=>Op(A,ge,Vt),[A,ge,Vt]),J=ve.items,Dt=Hn(()=>xo(s,{service:D,services:ne,errorOnly:d,search:f,regex:Ke,source:Me,since:E,until:y,systemLogs:m}),[d,Ke,f,D,ne,E,Me,y,s,m]),ct=Hn(()=>Op(Dt,z,Ee),[Dt,z,Ee]),Re=ct.items;qr(()=>{qe(-1),vt(!1),yt(Math.max(0,Math.min(Mr,A.length)-1))},[d,f,w,m]),qr(()=>{de(!1),B(Math.max(0,Math.min(Mr,Dt.length)-1))},[d,f,D,m]),qr(()=>{if(n!=="logs")Qe(!1)},[n]),qr(()=>{if(n!=="logs"||ge)return;yt(Math.max(0,Math.min(Mr,A.length)-1))},[A.length,ge,n]),qr(()=>{if(n!=="logs"||z)return;B(Math.max(0,Math.min(Mr,Dt.length)-1))},[Dt.length,z,n]);let _o=Or(()=>{if(!ge)ce(ol(A.length)),vt(!0)},[A.length,ge]),_=Or(()=>{if(!z)Ne(ol(Dt.length)),de(!0)},[Dt.length,z]),ue=Hn(()=>({sliceLen:J.length,windowStart:ve.start,windowNewer:ve.newer,filteredLen:A.length,pinned:ge}),[A.length,ge,J.length,ve.newer,ve.start]),Jr=Hn(()=>({sliceLen:Re.length,windowStart:ct.start,windowNewer:ct.newer,filteredLen:Dt.length,pinned:z}),[Dt.length,z,Re.length,ct.newer,ct.start]),Xr=Or((le)=>xS(le,ue,ce,vt,yt),[ue]),Sr=Or((le)=>xS(le,Jr,Ne,de,B),[Jr]),se=Or((le)=>{if(F&&G===1){Sr(le);return}Xr(le)},[Xr,Sr,G,F]),Wn=Or((le)=>{let bt=J.length,Pt=$p(le,bt,ve.start,ve.newer);if(Pt.startDelta!==0)ce(Math.max(0,ve.start+Pt.startDelta)),vt(!0);qe(Pt.selected)},[J.length,ve.newer,ve.start]),Gn=Or(()=>{if(vt(!1),de(!1),qe(-1),n==="logs")yt(Math.max(0,Math.min(Mr,A.length)-1)),B(Math.max(0,Math.min(Mr,Dt.length)-1));C((bt)=>bt+1);let le=Math.max(ve.newer,ct.newer);i(le>0?`Jumped to latest (+${le} new)`:"Jumped to latest logs")},[A.length,Dt.length,ve.newer,ct.newer,n]),$r=Hn(()=>({services:F?bS:w!==""?[w]:ne,level:d?"ERROR":Le,search:f,regex:Ke,source:Me,since:E,until:y}),[d,Le,Ke,f,w,ne,E,Me,y,F]),mn=Or(async()=>{if(!e)return;try{xr(await e.logsStats($r))}catch(le){i(L(le))}},[e,$r]),Ys=Or(async()=>{if(!e||c)return;try{let le=await e.logsPage($r);a((bt)=>Xb(bt,le.events)),wr(le.prevCursor),qt(le.hasPrev),await mn()}catch(le){i(L(le))}},[e,$r,c,mn]),_i=Or(()=>{a([]),S(new Date().toISOString()),vt(!1),de(!1),i("Cleared on-screen log buffer")},[]),Ei=Or(()=>{p((le)=>!le)},[]);return qr(()=>{o().then(()=>Ys())},[o,Ys]),qr(()=>{ar.current=s},[s]),qr(()=>{let le=Fp(ge,ve.start,or),bt=F&&Fp(z,ct.start,or);if(!e||ir||!le&&!bt)return;sr(!0),e.logsPage({...$r,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 Qr=ar.current,Js=Qb(Qr,Pt.events);a(Js);let Eo=Js.length-Qr.length;if(Eo>0){if(ge)ce((kr)=>kr+Eo);if(z)Ne((kr)=>kr+Eo)}wr(Pt.prevCursor),qt(Pt.hasPrev)}).catch((Pt)=>i(L(Pt))).finally(()=>sr(!1))},[e,$r,ir,or,ge,z,Kt,ve.start,ct.start,F]),qr(()=>{if(!e||c||n!=="logs")return;let le=setInterval(()=>{mn()},kM);return()=>clearInterval(le)},[e,c,mn,n]),{logs:s,setLogs:a,paused:c,setPaused:l,errorOnly:d,setErrorOnly:u,showSystemLogs:m,logSearch:f,setLogSearch:h,logSearchFocused:v,setLogSearchFocused:b,logFollow:g,logSince:E,setLogSince:S,setLogUntil:x,logService:w,setLogService:k,logServiceB:D,setLogServiceB:O,splitLogs:F,splitFocus:G,setSplitFocus:R,toggleSplitLogs:()=>{if(F){R(0),Z(!1);return}if(D===""){let le=r.find((bt)=>bt!==w);if(le!==void 0)O(le)}Z(!0)},cycleSplitFocus:()=>R((le)=>le===0?1:0),setActiveLogService:(le)=>{let bt=(Pt)=>typeof le==="function"?le(Pt):le;if(F&&G===1){O(bt);return}k(bt)},logSliceB:Re,logWindowB:ct,logSelectedB:j,logPinnedB:z,logServices:ne,logShowTimestamps:H,setLogShowTimestamps:ae,logShowMeta:Q,setLogShowMeta:nt,logLevel:Le,logSource:Me,logRegex:Ke,setLogRegex:gt,logWrap:Xe,setLogWrap:Ct,logPinned:ge,logSelected:at,logsFullscreen:nr,setLogsFullscreen:Qe,dashboardLogCursor:Qt,setDashboardLogCursor:qe,logFacets:ht,logSources:K,filteredLogs:A,filteredLogsB:Dt,logWindow:ve,logSlice:J,pinLogView:_o,pinLogViewB:_,applyLogCursor:se,applyLogCursorA:Xr,applyLogCursorB:Sr,applyDashboardLogCursor:Wn,jumpToLatestLogs:Gn,clearLogs:_i,toggleSystemLogs:Ei}}var bS,kM=2000;var SS=I(()=>{te();vr();bS=[]});import{useCallback as Po,useState as kS}from"react";function CS({tui:e,controller:t,cfg:r,persistPrefs:n,snap:o,refresh:i,setStatus:s}){let[a,c]=kS(""),[l,d]=kS(()=>e.mcp_port??fo(t?.cfg.repoRoot??process.cwd())),u=Po((g)=>{let C=Tn(g);d(C);let E=r?.repoRoot??process.cwd();if(Fy(E,C)){n({mcp_port:null},`MCP port ${C} (default)`);return}n({mcp_port:C},`MCP port ${C}`)},[r,n]),m=Po(()=>{if(a==="")return l;let g=$y(a,l);return c(""),u(g),g},[l,a,u]),p=Po(async(g)=>{if(!t||o?.mcp?.running!==!0)return;await t.mcpStop(),await t.mcpStart({port:g}),await i()},[t,i,o?.mcp?.running]),f=Po(async(g)=>{if(!t){s("Supervisor is not running");return}let C=o?.mcp?.disabled_tools??[],E=Dn(g.name,C),S=E?[...C,g.name]:C.filter((y)=>y!==g.name);try{let y=await t.mcpSetTools(S);n({mcp_disabled_tools:y},`${g.label} ${E?"disabled":"enabled"}`),await i()}catch(y){s(L(y))}},[t,n,i,o]),h=Po(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(L(g))}},[t,l,n,i,o?.mcp?.running]),v=Po((g)=>{bl(g.text).then(()=>{s(`Copied ${g.title} snippet`)}).catch((C)=>{s(L(C))})},[]),b=Po((g)=>{let C=lm(g);if(C===void 0)return!1;let E=o?.mcp?.address??vi(o?.mcp?.port??l),S=js(E,o?.mcp?.token??"")[C];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:m,restartMcpOnPort:p,toggleMcpTool:f,toggleMcp:h,copyMcpSnippet:v,copyFocusedMcpSnippet:b}}var PS=I(()=>{te();kl();bs();Qp();Us()});import{RGBA as CM}from"@opentui/core";import{useCallback as un,useEffect as PM,useMemo as RS,useRef as Al,useState as Ll}from"react";function _S({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:m,screen:p}){let[f,h]=Ll(e.theme||t?.cfg.ui.theme||"devctl"),v=Al(f),[b,g]=Ll(e.mouse),C=Al(e.mouse),[E,S]=Ll(e.leader_timeout),y=Al(e.leader_timeout),[x,w]=Ll(()=>Ps(e.font_size)),k=Al(x),D=kb(r()),O=RS(()=>{let H=yb(f);if(Mc(f)!=="terminal"||!bp(n))return H;let ae=n??H.background;return{...H,background:ae,panel:ae,element:ae,inverse:ae}},[n,f]),F=Mc(f)==="terminal"&&bp(n)?CM.defaultBackground(n??void 0):O.background,Z=RS(()=>_b({themeName:f,fontSize:x,mouse:b,leaderMs:E,locked:D,configPath:D?e.path||Cp(r(),o()):Cp(r(),o()),mcpRunning:i?.mcp?.running===!0}),[x,E,b,D,i?.mcp?.running,f,e.path]),G=un((H,ae)=>{if(D){s(`${ae} session only`);return}let Q=a(H);s(`${ae} saved ${Q}`)},[D]),R=un((H)=>{h(H),v.current=H,G({theme:H},`theme ${H}`)},[G]),j=un(()=>{let H=!b;g(H),C.current=H,G({mouse:H},`mouse ${H?"on":"off"} restart TUI to apply clicks`)},[b,G]),B=un((H)=>{S(H),y.current=H,G({leader_timeout:H},`leader ${H}ms`)},[G]),z=un((H)=>{let ae=Ps(H);w(ae),k.current=ae,G({font_size:ae},`display ${Nc(ae)}`)},[G]),de=un(()=>{let H=Hc();h(H.theme),v.current=H.theme,g(H.mouse),C.current=H.mouse,S(H.leader_timeout),y.current=H.leader_timeout,w(H.font_size),k.current=H.font_size,G(H,"restored default preferences")},[G]),Ee=un((H)=>{if(H.id==="theme"){c(Math.max(0,St.indexOf(f))),l("themes");return}if(H.id==="mouse"){j();return}if(H.id==="leader"){B(E);return}if(H.id==="font"){z(x);return}if(H.id==="reset"){d("reset-prefs"),l("confirm");return}if(H.id==="mcp"){u("mcp"),m(0);return}s(H.detail)},[z,B,x,E,f,j]),Ne=un((H,ae)=>{let Q=In(Z,ae);if(!Q)return;if(Q.id==="theme"){R(Cb(f,H));return}if(Q.id==="leader"){B(Pb(E,H));return}if(Q.id==="font"){z(Rs(x,H));return}if(Q.id==="mouse")j()},[z,B,x,E,R,Z,f,j]),ne=un(()=>{h(v.current)},[]);return PM(()=>{if(p==="settings")return;h(v.current),w(k.current),S(y.current),g(C.current)},[p]),{themeName:f,setThemeName:h,leaderMs:E,fontSize:x,prefsLocked:D,palette:O,rootBackground:F,settingRows:Z,persistPrefs:G,persistTheme:R,toggleMouse:j,applyFont:z,applyReset:de,activateSetting:Ee,cycleSetting:Ne,revertThemePreview:ne}}var ES=I(()=>{Dr();tt()});import{jsxDEV as Gs}from"@opentui/react/jsx-dev-runtime";function TS(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],m=wt.length,p=RM(u.id,i,s);return Gs(mt,{palette:t,title:`setup ${o+1}/${m} ${u.title}`,bottomTitle:"enter next \xB7 esc cancel",termW:r,termH:n,preferW:64,preferH:12,borderColor:t.primary,gap:1,children:[Gs("text",{fg:t.muted,wrapMode:"word",children:p},void 0,!1,void 0,this),u.id==="auth"||u.id==="write"||u.id==="repo"?Gs("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):Gs("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),Gs(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 km(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 AS(e,t,r,n){let o={...t};if(e==="name")o.name=r.trim()||Cn(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=$a(r,t.proxyPort);else if(e==="profile")o.profile=r.trim();return o}function RM(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 Cm=I(()=>{Fa();Pe()});import{useCallback as Pm,useState as Dl}from"react";function LS({workspace:e,renderer:t,setOverlay:r,setStatus:n,setScreen:o,onAttached:i,setController:s,setCfg:a,setBootError:c}){let l=process.cwd(),[d,u]=Dl(0),[m,p]=Dl(()=>Cn(l)),[f,h]=Dl(""),[v,b]=Dl("Checking Application Default Credentials\u2026"),g=Pm(()=>{let S=Cn(l);u(0),p(S),h(km("repo",S,l)),r("setup-wizard"),e.detectGoogle("").then((y)=>{p((x)=>({...x,project:y.projectID||x.project})),b(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]),C=Pm(async(S)=>{try{Ha(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(L(y))}},[i,l,c,a,s,r,o,n,e]),E=Pm(async()=>{let S=wt[d];if(!S)return;let y=AS(S.id,m,f,l);if(S.id==="auth"){if(!(await e.detectGoogle(y.project)).adcAvailable)try{await El(t,()=>e.loginGoogle())}catch(D){n(L(D))}}if(p(y),S.id==="write"){await C(y);return}let x=Math.min(d+1,wt.length-1),w=wt[x];u(x),h(w?km(w.id,y,l):"")},[m,f,C,t,l,d,e,n]);return{step:d,answers:m,draft:f,authStatus:v,repo:l,setDraft:h,startWizard:g,advanceWizard:E}}var DS=I(()=>{Fa();ja();te();Cm()});import{useCallback as _M,useEffect as IS,useState as Rm}from"react";function MS({controller:e,cfg:t,envService:r}){let[n,o]=Rm(),[i,s]=Rm(!1),[a,c]=Rm(""),l=n?.name===r,d=l?n?.env:void 0,u=r===""?"config":i&&!l?"loading":l?"resolved":a!==""?"error":"config",m=l||r===""?"":a;IS(()=>{o(void 0),c("")},[t]),IS(()=>{if(!e||r===""||!t?.services[r])return;if(n?.name===r)return;let f=!1;return s(!0),c(""),e.execService(r,[],!0).then((h)=>{if(f)return;o({name:r,env:h.environment??{}}),c("")}).catch((h)=>{if(f)return;c(L(h))}).finally(()=>{s(!1)}),()=>{f=!0}},[t,e,r,n?.name]);let p=_M(async(f)=>{if(!e)return;try{let h=await e.execService(f,[],!0);o({name:f,env:h.environment??{}}),c("")}catch(h){throw c(L(h)),h}},[e]);return{inspectorEnv:d,inspectorEnvStatus:u,inspectorEnvError:m,resolveEnvironment:p}}var OS=I(()=>{te()});import{jsxDEV as jn}from"@opentui/react/jsx-dev-runtime";function $S(e){let{palette:t,path:r,initialValue:n,error:o,termW:i,termH:s,textareaRef:a}=e;return jn(mt,{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:[jn("box",{height:1,overflow:"hidden",children:jn("text",{fg:t.muted,wrapMode:"none",children:r},void 0,!1,void 0,this)},void 0,!1,void 0,this),jn("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!==""?jn("box",{height:2,overflow:"hidden",children:jn("text",{fg:t.error,wrapMode:"word",children:o},void 0,!1,void 0,this)},void 0,!1,void 0,this):jn("box",{height:1,overflow:"hidden",children:jn("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 FS=I(()=>{Pe();Lr()});import{jsxDEV as _m}from"@opentui/react/jsx-dev-runtime";function NS(e){let{palette:t,title:r,body:n,kind:o,termW:i,termH:s}=e,a=hx(o),c=a.map((l)=>`${l.key} ${l.label}`).join(" \xB7 ");return _m(mt,{palette:t,title:r,bottomTitle:c,termW:i,termH:s,preferW:52,preferH:8,borderColor:t.warning,gap:1,children:[_m("text",{fg:t.text,wrapMode:"word",children:n},void 0,!1,void 0,this),_m(it,{palette:t,hints:a},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var HS=I(()=>{yr();Pe()});import{jsxDEV as jS}from"@opentui/react/jsx-dev-runtime";function BS(e){let{palette:t,termW:r,termH:n}=e,o=Kp();return jS(mt,{palette:t,title:"leader",termW:r,termH:n,preferW:68,preferH:5,anchor:"bottom",children:jS(it,{palette:t,hints:o},void 0,!1,void 0,this)},void 0,!1,void 0,this)}var US=I(()=>{ko();Pe()});import{jsxDEV as Xt,Fragment as EM}from"@opentui/react/jsx-dev-runtime";function WS(e){let{palette:t,event:r,termW:n,termH:o,scrollRef:i}=e;if(!r)return null;let s=r.raw?Wb(r.raw):void 0,a=r.message.length>120||r.message.includes(`
|
|
2231
|
-
`)||s!==void 0;return Xt(mt,{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:Xt("scrollbox",{ref:i,focused:!1,stickyScroll:!1,scrollX:!1,style:Ue(t),children:Xt("box",{flexDirection:"column",overflow:"hidden",children:[Xt("text",{fg:t.text,wrapMode:"word",children:tl(r.message)},void 0,!1,void 0,this),Xt("text",{fg:t.muted,children:`time ${r.timestamp}`},void 0,!1,void 0,this),Xt("text",{fg:Ze(r.service,t),children:`service ${r.service}`},void 0,!1,void 0,this),Xt("text",{fg:t.muted,children:`source ${r.source}${r.stream?` / ${r.stream}`:""}`},void 0,!1,void 0,this),Xt("text",{fg:t.muted,children:`level ${el(r.level)}`},void 0,!1,void 0,this),Xt("text",{fg:t.muted,children:`pid ${r.pid||"\u2014"}`},void 0,!1,void 0,this),Xt("text",{fg:t.muted,children:`request ${r.request_id||"\u2014"}`},void 0,!1,void 0,this),Xt("text",{fg:t.muted,children:`identity ${r.identity||"\u2014"}`},void 0,!1,void 0,this),s===void 0?null:Xt(EM,{children:[Xt("text",{fg:t.muted,children:"raw json"},void 0,!1,void 0,this),Xt("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 GS=I(()=>{vr();Pe();tt()});import{jsxDEV as W}from"@opentui/react/jsx-dev-runtime";function Em(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 TM(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 KS(e){return W("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:W("text",{wrapMode:"none",children:[W("span",{fg:e.palette.primary,children:e.label.toUpperCase()},void 0,!1,void 0,this),e.detail?W("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 qS(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,m=new Set(d),p=r.waves.map((R,j)=>({wave:R.filter((B)=>!m.has(B)),waveIdx:j})).filter(({wave:R})=>R.length>0),f=Iw(r,n,s),h=r.waves[f.currentWaveIndex]??[],v=Ws(h,n,s),b=Em(v,t),g=TM(o,i,v,t),C=Ow(s,o,i,r.profile),E=$w(o,i),S=i?t.inverse:t.text,y=i?t.error:t.element,x=Math.min(92,Math.max(40,a-8)),w=x>=82?22:x>=66?17:13,k=12,D=9,O=x>=56,F=x>=72,Z=x<72||r.waves.length>3,G=r.profile?`PROFILE ${r.profile}`:s.toUpperCase();return W(mt,{palette:t,title:C,bottomTitle:"enter close \xB7 esc hide (operation continues)",termW:a,termH:c,preferW:98,preferH:28,borderColor:g.color,gap:0,children:[W("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:t.element,children:[W(Be,{palette:t,label:`${g.icon} ${g.label.toUpperCase()}`,tone:g.tone},void 0,!1,void 0,this),W("box",{paddingLeft:1,flexGrow:1,overflow:"hidden",children:W("text",{fg:t.text,wrapMode:"none",children:G},void 0,!1,void 0,this)},void 0,!1,void 0,this),W(Be,{palette:t,label:`${f.ready}/${f.total} READY`,tone:f.isComplete?"success":"info"},void 0,!1,void 0,this),W(Be,{palette:t,label:`WAVE ${Math.min(f.currentWaveIndex+1,Math.max(1,f.totalWaves))}/${Math.max(1,f.totalWaves)}`,tone:b.tone},void 0,!1,void 0,this)]},void 0,!0,void 0,this),W("box",{height:2,flexDirection:"column",overflow:"hidden",flexShrink:0,paddingLeft:1,paddingRight:1,children:[W("box",{height:1,flexDirection:"row",overflow:"hidden",children:[W("text",{wrapMode:"none",children:[W("span",{fg:t.muted,children:"PROGRESS "},void 0,!1,void 0,this),W("span",{fg:g.color,children:`[${f.progressBar}]`},void 0,!1,void 0,this),W("span",{fg:t.text,children:` ${f.percent}%`},void 0,!1,void 0,this)]},void 0,!0,void 0,this),W("box",{flexGrow:1},void 0,!1,void 0,this),W("text",{wrapMode:"none",children:W("span",{fg:b.color,children:`${b.icon} ${b.label}`},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this),W("box",{height:1,overflow:"hidden",children:W("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.":E.primary},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this),W(KS,{palette:t,label:"Pipeline",detail:`${r.waves.length} execution wave${r.waves.length===1?"":"s"}`},void 0,!1,void 0,this),W("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:t.element,children:r.waves.map((R,j)=>{let B=Ws(R,n,s),z=Em(B,t),de=Z?`W${j+1} ${z.icon} ${R.length}`:`W${j+1} ${z.icon} ${z.label} \xB7 ${R.length}`;return W("box",{flexDirection:"row",overflow:"hidden",flexShrink:0,children:[W(Be,{palette:t,label:de,tone:z.tone},void 0,!1,void 0,this),j<r.waves.length-1?W("box",{width:3,flexShrink:0,overflow:"hidden",children:W("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?W("box",{minHeight:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:t.element,paddingLeft:1,children:W("text",{fg:t.error,wrapMode:"word",children:`BLOCKED ${r.blockers?.map((R)=>`${R.name}: ${R.message}`).join(" \xB7 ")}`},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,d.length>0?W("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,paddingLeft:1,children:W("text",{wrapMode:"none",children:[W("span",{fg:t.success,children:"\u2713 ALREADY READY"},void 0,!1,void 0,this),W("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,W(KS,{palette:t,label:"Wave details",detail:"live process and health state"},void 0,!1,void 0,this),W("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:t.element,paddingLeft:1,children:[W("box",{width:2,flexShrink:0},void 0,!1,void 0,this),W("box",{width:w,flexShrink:0,overflow:"hidden",children:W("text",{fg:t.muted,children:oe("SERVICE",w)},void 0,!1,void 0,this)},void 0,!1,void 0,this),W("box",{width:12,flexShrink:0,overflow:"hidden",children:W("text",{fg:t.muted,children:oe("STATE",12)},void 0,!1,void 0,this)},void 0,!1,void 0,this),O?W("box",{width:9,flexShrink:0,overflow:"hidden",children:W("text",{fg:t.muted,children:oe("PORT",9)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,F?W("box",{flexGrow:1,overflow:"hidden",children:W("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),W("scrollbox",{ref:l,focused:!1,stickyScroll:!1,scrollX:!1,style:Ue(t),children:W("box",{flexDirection:"column",overflow:"hidden",gap:1,children:p.map(({wave:R,waveIdx:j})=>{let B=Ws(R,n,s),z=Em(B,t),de=`${Mw(s,j,B)} \xB7 ${R.length} service${R.length===1?"":"s"}`;return W("box",{border:!0,borderStyle:"rounded",borderColor:z.color,title:de,titleColor:z.color,flexDirection:"column",paddingLeft:1,paddingRight:1,flexShrink:0,overflow:"hidden",children:R.map((Ee)=>{let Ne=n?.services[Ee],ne=rr(Ne),H=Dw(Ee,r,n,s),ae=Ee===i||ne==="FAILED",Q=ne==="UNHEALTHY",nt=ne==="HEALTHY"||s==="stop"&&ne==="STOPPED",ft=yo(Ne),ie=ae?"\u2717":nt?"\u2713":Q?"\u26A0":ne==="STARTING"||ne==="RUNNING"||ne==="STOPPING"||ne==="RESTARTING"?"\u25CF":"\u25CB",Le=ae?t.error:et(t,ne),Me=ae?t.error:Q?t.warning:nt?t.success:t.muted;return W("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:[W("box",{width:2,flexShrink:0,overflow:"hidden",children:W("text",{fg:Le,children:ie},void 0,!1,void 0,this)},void 0,!1,void 0,this),W("box",{width:w,flexShrink:0,overflow:"hidden",children:W("text",{fg:Ze(Ee,t),wrapMode:"none",children:oe(Ee,w)},void 0,!1,void 0,this)},void 0,!1,void 0,this),W("box",{width:12,flexShrink:0,overflow:"hidden",children:W("text",{fg:et(t,ne),wrapMode:"none",children:oe(ne,12)},void 0,!1,void 0,this)},void 0,!1,void 0,this),O?W("box",{width:9,flexShrink:0,overflow:"hidden",children:W("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?W("box",{flexGrow:1,overflow:"hidden",children:W("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),W("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:[W("text",{fg:S,wrapMode:"none",children:E.primary},void 0,!1,void 0,this),W("text",{fg:i?t.inverse:t.muted,wrapMode:"none",children:E.secondary},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}var VS=I(()=>{Ot();Pl();Lt();Pe();tt()});import{jsxDEV as br}from"@opentui/react/jsx-dev-runtime";function zS(e){let{palette:t,route:r,termW:n,termH:o,scrollRef:i}=e;if(!r)return null;return br(mt,{palette:t,title:r.name,bottomTitle:"j/k scroll \xB7 esc close",termW:n,termH:o,preferW:78,preferH:18,gap:1,children:br("scrollbox",{ref:i,focused:!1,stickyScroll:!1,scrollX:!1,style:Ue(t),children:br("box",{flexDirection:"column",overflow:"hidden",children:[br("text",{fg:t.muted,children:`auth ${r.authType||"none"}`},void 0,!1,void 0,this),br("text",{fg:t.muted,children:`identity ${r.identityType||"\u2014"}`},void 0,!1,void 0,this),br("text",{fg:t.text,wrapMode:"word",children:`account ${r.serviceAccount||"\u2014"}`},void 0,!1,void 0,this),br("text",{fg:t.text,wrapMode:"word",children:`audience ${r.audience||"\u2014"}`},void 0,!1,void 0,this),br("text",{fg:t.muted,wrapMode:"word",children:`client id ${r.clientId||"ADC default"}`},void 0,!1,void 0,this),br("text",{fg:t.muted,wrapMode:"word",children:`client secret ${r.clientSecret||"\u2014"}`},void 0,!1,void 0,this),br("text",{fg:t.muted,children:`match host ${r.matchHost||"*"}`},void 0,!1,void 0,this),br("text",{fg:t.muted,children:`match path ${r.matchPath||"*"}`},void 0,!1,void 0,this),br("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 YS=I(()=>{Pe()});import{jsxDEV as Il}from"@opentui/react/jsx-dev-runtime";function JS(e){let{palette:t,title:r,body:n,termW:o,termH:i,scrollRef:s}=e,a=n.length>400||n.split(`
|
|
2232
|
-
`).length>16;return Il(mt,{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:Il("scrollbox",{ref:s,focused:!1,stickyScroll:!1,scrollX:!1,style:Ue(t),children:Il("box",{flexDirection:"column",overflow:"hidden",children:Il("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 XS=I(()=>{Pe()});import{jsxDEV as Wt}from"@opentui/react/jsx-dev-runtime";function QS(e){let{palette:t,items:r,query:n,selected:o,onQuery:i,onSubmit:s,title:a="commands"}=e,c=So(n)!=="",l=Fx(o,Tm,r.length),d=c?r.slice(l,l+Tm):Nx(r,o,Tm),u=c?[{group:"",items:d}]:Up(d),m=Math.max(d.length+(c?0:u.length),1),p=fi(r,o),f=Dx(r);return Wt("box",{height:m+AM,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((b)=>{let g=ul(b)===(p?ul(p):"");return Wt("box",{height:1,flexDirection:"row",overflow:"hidden",paddingLeft:1,paddingRight:1,backgroundColor:g?t.highlight:t.panel,children:[Wt("box",{width:f,flexShrink:0,overflow:"hidden",children:Wt("text",{fg:g?t.primary:t.text,wrapMode:"none",children:`${g?"\u203A":" "} /${Wp(b).slice(0,Bp)}`},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:Lx(b)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},ul(b),!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 Tm=10,AM=3;var ZS=I(()=>{ko()});import{jsxDEV as rk}from"@opentui/react/jsx-dev-runtime";function nk(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?`${yp[u]} \xB7 current`:yp[u],value:u})),d=ll(o,i,ek,tk,"center",!vo(be()));return rk(mt,{palette:t,title:"/themes",bottomTitle:"enter save \xB7 esc revert",termW:o,termH:i,preferW:ek,preferH:tk,children:rk("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 m=St[u];if(m)a(m)},onSelect:(u,m)=>{let p=m?.value;if(typeof p==="string"&&p!=="")c(p)}},void 0,!1,void 0,this)},void 0,!1,void 0,this)}var ek=52,tk=22;var ok=I(()=>{Mt();yr();Pe();Dr();tt()});function Ml(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 Ol(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 ik(e){let{palette:t,cfg:r,google:n,identity:o}=e;if(!n&&!o)return st(yl,{palette:t,label:"Checking Application Default Credentials\u2026"},void 0,!1,void 0,this);let i=LM(r,o),s=o?.user||n?.userEmail||"",{project:a,source:c}=Ml(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),m=u.some((h)=>h.ok===!0),p=u.some((h)=>h.ok===!1),f=m?"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(Ol,{palette:t,title:"Google identity",tone:l?"success":"warning",children:[st(Co,{palette:t,label:"user",value:s||"(unknown)"},void 0,!1,void 0,this),st(Co,{palette:t,label:"project",value:a||"(unset)"},void 0,!1,void 0,this),st(Co,{palette:t,label:"source",value:c||"\u2014"},void 0,!1,void 0,this),st(Co,{palette:t,label:"ADC",value:l?"\u2713 available":"\u2717 missing",tone:l?"success":"error"},void 0,!1,void 0,this),st(Co,{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(Ol,{palette:t,title:`Service accounts (${i.length})`,tone:u.length===0?"muted":m?"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(Co,{palette:t,label:"impersonation",value:f,tone:m?"success":u.length>0?"warning":"muted"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),st(Ol,{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(Ol,{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 LM(e,t){let r=new Map((e?lu(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 sk=I(()=>{Tr();Jt();Pe()});import{jsxDEV as M}from"@opentui/react/jsx-dev-runtime";function dk(e){let{palette:t,cfg:r,width:n,selectedTask:o=0}=e,i=be();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(xi+8,n-6-i.pad*2),a=s>=oS,c=a?Math.floor((s-ck)/2):s,l=Jw(r),d=Xw(r),u=Qw(r),m=Zw(r),p=eS(r);return M(kt,{palette:t,title:`config ${r.project.name||"unnamed"}`,children:[M(pt,{palette:t,items:Gw(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:lk,children:[M(Am,{wide:a,left:M(Vr,{palette:t,title:"project",children:M(Si,{palette:t,facts:Kw(r,jt),width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this),right:M(Vr,{palette:t,title:"google",children:M(Si,{palette:t,facts:qw(r),width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this),M(Am,{wide:a,left:M(Vr,{palette:t,title:"runtime",children:M(Si,{palette:t,facts:Vw(r),width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this),right:M(Vr,{palette:t,title:"logs",children:M(Si,{palette:t,facts:zw(r),width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this),M(Vr,{palette:t,title:`proxy ${r.proxy.enabled?"enabled":"disabled"}`,children:[M(Si,{palette:t,facts:Yw(r),width:s},void 0,!1,void 0,this),M(IM,{palette:t,routes:d,width:s},void 0,!1,void 0,this)]},void 0,!0,void 0,this),M(Vr,{palette:t,title:`services ${l.length}`,children:M(DM,{palette:t,rows:l,width:s},void 0,!1,void 0,this)},void 0,!1,void 0,this),M(Vr,{palette:t,title:`tasks ${p.length}`,children:M(ak,{palette:t,rows:p,empty:"no tasks",width:s,selected:o},void 0,!1,void 0,this)},void 0,!1,void 0,this),M(Am,{wide:a,left:M(Vr,{palette:t,title:`profiles ${u.length}`,children:M(MM,{palette:t,rows:u,width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this),right:M(Vr,{palette:t,title:`templates ${m.length}`,children:M(ak,{palette:t,rows:m,empty:"no templates",width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this),M(Vr,{palette:t,title:"extras",children:M(Si,{palette:t,facts:rS(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 Am(e){let{wide:t,left:r,right:n}=e;if(!t)return M("box",{flexDirection:"column",overflow:"hidden",gap:lk,flexShrink:0,children:[r,n]},void 0,!0,void 0,this);return M("box",{flexDirection:"row",overflow:"hidden",gap:ck,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 Vr(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 Si(e){let{palette:t,facts:r,width:n}=e,o=Math.max(4,n-xi-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:xi,flexShrink:0,overflow:"hidden",children:M("text",{fg:t.muted,wrapMode:"none",children:oe(i.label,xi)},void 0,!1,void 0,this)},void 0,!1,void 0,this),M("box",{width:o,flexShrink:0,overflow:"hidden",children:M("text",{fg:FM(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 DM(e){let{palette:t,rows:r,width:n}=e;if(r.length===0)return M($l,{palette:t,text:"no services"},void 0,!1,void 0,this);let o=nS(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(OM(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 IM(e){let{palette:t,routes:r,width:n}=e;if(r.length===0)return M($l,{palette:t,text:"no routes"},void 0,!1,void 0,this);let o=Math.min(xi+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($M(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 MM(e){let{palette:t,rows:r,width:n}=e;if(r.length===0)return M($l,{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 ak(e){let{palette:t,rows:r,empty:n,width:o,selected:i}=e;if(r.length===0)return M($l,{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 $l(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 OM(e){let t=e.extends==="\u2014"?"":` ${e.extends}`;return` ${e.ports} ${e.identity} ${e.health} ${e.restart} \u2190 ${e.depends}${t}`}function $M(e){return` ${e.auth} ${e.identity} ${e.match} \u2192 ${e.upstream}`}function FM(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 ck=1,lk=1;var uk=I(()=>{Jt();wm();Mt();Ot();Pe();tt()});import{jsxDEV as Ft}from"@opentui/react/jsx-dev-runtime";function pk(e){let{palette:t,credentials:r}=e;if(!r)return Ft(yl,{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(vl,{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 mk=I(()=>{Jt();Pe()});import{jsxDEV as Fl}from"@opentui/react/jsx-dev-runtime";function ki(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 Fl("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:Fl("text",{wrapMode:"none",children:[Fl("span",{fg:e.palette.primary,children:"g"},void 0,!1,void 0,this),Fl("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 fk=()=>{};import{jsxDEV as Ks}from"@opentui/react/jsx-dev-runtime";function Ci(e){let{palette:t,logs:r,names:n,service:o,errorOnly:i,width:s,onService:a,onToggleErrors:c,facets:l}=e,d=l?tx(n,l):ex(n,r),u=s<80,m=d.map((v)=>{let b=v.name===""?"all":v.name;return{id:b,label:u?b:`${b} \xB7 ${v.count}`,color:Ze(v.name,t)}}),p=Math.max(0,d.findIndex((v)=>v.name===o)),f=i?"ERROR+":u?"lvls":"all levels",h=Math.max(ui(m[p]?.label??"all"),s-ui(f));return Ks(Ut,{palette:t,backgroundColor:t.element,children:Ks("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:t.element,children:[Ks("box",{flexGrow:1,overflow:"hidden",children:Ks(gl,{palette:t,items:m,active:p,width:h,emphasis:"fill",onPick:(v)=>{let b=d[v];if(b)a(b.name)}},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ks(Be,{palette:t,label:f,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 hk=I(()=>{vr();On();Pe();tt()});import{jsxDEV as NM}from"@opentui/react/jsx-dev-runtime";function qs(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 NM(pt,{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 gk=I(()=>{Pe()});import{useEffect as Lm,useRef as vk}from"react";import{jsxDEV as Ye}from"@opentui/react/jsx-dev-runtime";function Pi(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:m,onLeaveLatest:p,viewStart:f=0,search:h="",regex:v=!1}=e,b=vk(null),g=vk(""),C=n===void 0?r:r.slice(-n),E=[...new Set(r.map((k)=>k.service))],S=zb(o,E),y=Vb({width:o,serviceWidth:S,showTimestamps:d,showMeta:u}),x=C[C.length-1],w=x===void 0?"empty":String(x.seq);return Lm(()=>{let k=b.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]),Lm(()=>{let k=`${f}:${c}:${a}`;if(g.current===k)return;if(g.current=k,l&&(c<0||c>=C.length-1))return;let D=b.current;if(!D||c<0)return;let O=`log-row-${c}`,F=requestAnimationFrame(()=>{D.stickyScroll=!1,D.scrollChildIntoView(O)});return()=>cancelAnimationFrame(F)},[l,c,C.length,f,a]),Lm(()=>{if(!l||!p)return;let k=!1,D=0,O=setInterval(()=>{let F=b.current;if(!F)return;let Z=F.viewport.height;if(Z<=0)return;let G=F.scrollHeight;if(F.scrollTop+Z>=G-jM){k=!0,D=G;return}if(G>D){F.stickyScroll=!0,F.stickyStart="bottom",F.scrollTo({x:F.scrollLeft,y:Math.max(0,G)}),D=G;return}if(k)p()},HM);return()=>{clearInterval(O)}},[l,i,p]),Ye("box",{flexGrow:1,height:"100%",overflow:"hidden",children:Ye("scrollbox",{ref:b,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:C.map((k,D)=>Ye(BM,{id:`log-row-${D}`,palette:t,event:k,serviceWidth:S,msgWidth:y,expanded:Kb(a,D===c),active:D===c,showTimestamps:d,showMeta:u,onPick:m?()=>m(D):void 0,search:h,regex:v},`${k.timestamp}-${k.service}-${D}`,!1,void 0,this))},void 0,!1,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function BM(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:m=!1}=e,p=Gb(rl(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((f,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:$s,flexShrink:0,overflow:"hidden",children:Ye("text",{children:" "},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ye("box",{width:Zc,flexShrink:0,overflow:"hidden",children:Ye("text",{fg:h===0?et(r,n.level):r.muted,children:h===0?el(String(n.level)):""},void 0,!1,void 0,this)},void 0,!1,void 0,this),l?Ye("box",{width:Xc,flexShrink:0,overflow:"hidden",children:Ye("text",{fg:r.muted,children:h===0&&n.source?oe(n.source,Xc):""},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,Ye("box",{width:$s,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(UM,{palette:r,level:n.level,text:f,mark:h===0?p.mark:"",search:u,regex:m},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 UM(e){let{palette:t,level:r,text:n,mark:o,search:i="",regex:s=!1}=e;return Ye("text",{wrapMode:"none",children:[qb(n,i,s).map((a,c)=>Ye("span",{fg:xb(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 HM=200,jM=2;var yk=I(()=>{Ot();vr();tt()});var Dm=I(()=>{fk();hk();gk();yk()});import{useRef as WM}from"react";import{jsxDEV as Ie}from"@opentui/react/jsx-dev-runtime";function Nl(e){let{palette:t,names:r,snap:n,selected:o,checked:i,width:s,onOpen:a,onSelectIndex:c,onToggle:l}=e,d=be(),u=WM(!1),m=new Set(i),p=Dp(s),f=Lp(s),h=Ap(s),v=Mb(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:Ir,fg:t.muted,text:"state"},void 0,!1,void 0,this),h?Ie(Gt,{width:Wr,fg:t.muted,text:"health"},void 0,!1,void 0,this):null,f?Ie(Gt,{width:Bc,fg:t.muted,text:"port"},void 0,!1,void 0,this):null,p?Ie(Gt,{width:ln,fg:t.muted,text:"pid"},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),r.map((b,g)=>{let C=n?.services[b],E=rr(C),S=C?.health??"UNKNOWN",y=m.has(b),x=g===o,w=y?"[x]":"[ ]",k=x?"\u203A":" ";return Ie("box",{height:d.rowH,flexDirection:"row",overflow:"hidden",backgroundColor:x?t.highlight:y?t.element:void 0,onMouseDown:()=>{if(u.current)return;if(x){a(b);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?.(b),setTimeout(()=>{u.current=!1},0)},children:Ie("text",{fg:GM(t,y,x),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,E),children:gr(E)},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ie(Gt,{width:v,fg:x?t.primary:Ze(b,t),text:oe(b,v)},void 0,!1,void 0,this),Ie(Gt,{width:$t,fg:t.muted,text:""},void 0,!1,void 0,this),Ie(Gt,{width:Ir,fg:et(t,E),text:E},void 0,!1,void 0,this),h?Ie(Gt,{width:Wr,fg:et(t,S),text:S},void 0,!1,void 0,this):null,f?Ie(Gt,{width:Bc,fg:t.text,text:yo(C)},void 0,!1,void 0,this):null,p?Ie(Gt,{width:ln,fg:t.text,text:C?.pid?String(C.pid):""},void 0,!1,void 0,this):null]},b,!0,void 0,this)})]},void 0,!0,void 0,this)}function Hl(e){let{palette:t,checked:r}=e,n=r.length;if(n>0)return Ie(pt,{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 GM(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 Im=I(()=>{Mt();Ot();Lt();Pe();tt()});import{jsxDEV as me}from"@opentui/react/jsx-dev-runtime";function xk(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:m,errorOnly:p,showSystemLogs:f,onOpen:h,onSelectIndex:v,onToggle:b,onFilterService:g,onToggleErrors:C,onShowErrors:E,wrapMode:S="clip",view:y,follow:x=!0,onLeaveLatest:w,onPickLog:k,viewStart:D=0,viewTotal:O,newer:F=0,onJumpLatest:Z,facets:G,leftover:R,search:j="",regex:B=!1}=e,z=be();if(!r||i.length===0)return me(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<pi,Ee=Kc(n),Ne=ml(n,i),ne=i.filter((ge)=>n?.services[ge]?.state==="FAILED").length,H=Zb(o),ae=Ee?Fb(R,n?.session_id):void 0,Q=Gc(l,i,de),nt=Math.max(24,de?l-4:l-Q-4),ft=f?o:o.filter((ge)=>!di(ge.source)),ie=xo(o,{service:u,errorOnly:p,systemLogs:f,search:j,regex:B}),Le=y??ie,Me=O??ie.length,Ke=Math.min(Me,D+Le.length),gt=Me===0?"empty":`${D+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:E}]:[]];return me("box",{flexGrow:1,flexDirection:de?"column":"row",overflow:"hidden",children:[me("box",{flexGrow:de?1:0,flexShrink:0,minWidth:de?void 0:Q,width:de?void 0:Q,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?me(pt,{palette:t,items:Ct},void 0,!1,void 0,this):null,me(Hl,{palette:t,checked:c},void 0,!1,void 0,this),me("box",{flexGrow:1,paddingLeft:z.pad,paddingRight:z.pad,overflow:"hidden",children:me(Nl,{palette:t,names:i,snap:n,selected:s,checked:c,width:Wc(Q,z.pad),onOpen:h,onSelectIndex:v,onToggle:b},void 0,!1,void 0,this)},void 0,!1,void 0,this),me(zM,{palette:t,names:i,snap:n,width:Q,onOpen:h},void 0,!1,void 0,this),ae?me(JM,{palette:t,leftover:ae,width:Q},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),me("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:[me(Ci,{palette:t,logs:ft,names:m??i,service:u,errorOnly:p,width:nt,onService:g,onToggleErrors:C,facets:G},void 0,!1,void 0,this),Me>Le.length?me(qs,{palette:t,start:D,count:Le.length,total:Me},void 0,!1,void 0,this):null,ie.length===0?me(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):me(Pi,{palette:t,logs:Le,width:nt,followTick:d,focused:!1,wrapMode:S,selected:a,follow:x,onLeaveLatest:w,onPick:k,viewStart:D,search:j,regex:B},d,!1,void 0,this),!x?me(ki,{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 qM(e){if(!e)return!1;return e.state===ut||e.health===Oe||e.state===fr||e.restarts>0||e.last_error!==""}function bk(e){if(e.state===ut)return 0;if(e.health===Oe)return 1;if(e.state===fr)return 2;return 3}function VM(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 zM(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)=>qM(d.rt)).sort((d,u)=>bk(d.rt)-bk(u.rt));if(s.length===0)return null;let a=s.slice(0,KM),c=s.length-a.length,l=Math.max(8,o-Mm-4);return me("box",{flexDirection:"column",flexShrink:0,paddingLeft:1,paddingRight:1,overflow:"hidden",children:[me("box",{height:1,overflow:"hidden",children:me("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 m=rr(u);return me("box",{height:1,flexDirection:"row",overflow:"hidden",onMouseDown:()=>i(d),children:[me("box",{width:2,flexShrink:0,children:me("text",{fg:et(t,m),children:gr(m)},void 0,!1,void 0,this)},void 0,!1,void 0,this),me("box",{width:Mm,flexShrink:0,overflow:"hidden",children:me("text",{fg:Ze(d,t),wrapMode:"none",children:oe(d,Mm)},void 0,!1,void 0,this)},void 0,!1,void 0,this),me("box",{width:l,flexShrink:0,overflow:"hidden",children:me("text",{fg:t.muted,wrapMode:"none",children:_e(VM(u),l)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},d,!0,void 0,this)}),c>0?me("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,YM),i=r.processes.length-o.length,s=Math.max(8,n-Om-4);return me("box",{flexDirection:"column",flexShrink:0,paddingLeft:1,paddingRight:1,overflow:"hidden",children:[me("box",{height:1,overflow:"hidden",children:me("text",{fg:t.warning,wrapMode:"none",children:"last session"},void 0,!1,void 0,this)},void 0,!1,void 0,this),me("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)=>me("box",{height:1,flexDirection:"row",overflow:"hidden",children:[me("box",{width:Om,flexShrink:0,overflow:"hidden",children:me("text",{fg:t.text,wrapMode:"none",children:oe(a.name,Om)},void 0,!1,void 0,this)},void 0,!1,void 0,this),me("box",{width:s,flexShrink:0,overflow:"hidden",children:me("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?me("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 KM=4,Mm=14,YM=4,Om=14;var wk=I(()=>{At();Jt();Mt();yr();Ot();vr();Lt();hl();Pe();tt();Dm();Im()});import XM from"cli-spinners";import{useEffect as QM,useState as ZM}from"react";import{jsxDEV as re}from"@opentui/react/jsx-dev-runtime";function Sk(e){let{palette:t,report:r,loading:n,progress:o,error:i,selected:s,onPick:a,onReload:c}=e,l=gi(s,Fm);if(n)return re(eO,{palette:t,progress:o},void 0,!1,void 0,this);if(i)return re(sw,{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,m=r.checks.filter((v)=>v.severity==="error").length,p=r.checks.length,f=li(p>0?d/p:0),h=m>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:`[${f}]`},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,m>0?re("span",{fg:t.error,children:` \xB7 ${m} error`},void 0,!1,void 0,this):null,u===0&&m===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,b)=>re(tO,{palette:t,check:v,index:b,active:b===s,onPick:a},`${v.name}-${b}`,!1,void 0,this))},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function eO({palette:e,progress:t}){let[r,n]=ZM(0);return QM(()=>{let o=setInterval(()=>n((i)=>(i+1)%$m.frames.length),$m.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:`${$m.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 tO(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:`${Fm}-${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:`${Fm}-${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 Fm="doctor-row",$m;var kk=I(()=>{Jt();Ot();Pe();tt();$m=XM.bouncingBall});import{jsxDEV as We,Fragment as rO}from"@opentui/react/jsx-dev-runtime";function Pk(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:m,onService:p,onToggleErrors:f,onSelect:h,wrapMode:v="focus",selected:b=-1,follow:g=!0,newer:C=0,viewStart:E=0,viewTotal:S,onLeaveLatest:y,onJumpLatest:x,view:w,fullscreen:k=!1,facets:D,split:O=!1,splitFocus:F=0,serviceB:Z="",viewB:G,selectedB:R=-1,followB:j=!0,newerB:B=0,viewStartB:z=0,viewTotalB:de,onServiceB:Ee,onSelectB:Ne,onLeaveLatestB:ne,onFocusPane:H}=e,ae=be(),Q=Mp(u,ae.pad,k),nt=a?r:r.filter((ge)=>!di(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,E+ie.length),Ke=Le===0?"empty":`${E+1}\u2013${Me} of ${Le}`,gt=e.services??[],Xe=gt.length>0?gt.join(","):i===""?"all services":i,Ct=[...g?[]:[{text:C>0?`${O?"left ":""}pinned \xB7 +${C} 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=G??[],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:m,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(pt,{palette:t,items:Ct},void 0,!1,void 0,this),We("box",{flexGrow:1,flexDirection:"row",overflow:"hidden",children:[We(Ck,{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:b,follow:g,newer:C,viewStart:E,wrapMode:v,showTimestamps:e.showTimestamps!==!1,showMeta:e.showMeta!==!1,followTick:d,facets:D,focused:F===0,onService:p,onToggleErrors:f,onSelect:h,onLeaveLatest:y,onFocus:()=>H?.(0)},void 0,!1,void 0,this),We(Ck,{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:R,follow:j,newer:B,viewStart:z,wrapMode:v,showTimestamps:e.showTimestamps!==!1,showMeta:e.showMeta!==!1,followTick:d,facets:D,focused:F===1,onService:Ee??p,onToggleErrors:f,onSelect:Ne,onLeaveLatest:ne,onFocus:()=>H?.(1)},void 0,!1,void 0,this)]},void 0,!0,void 0,this),!g||!j?We(ki,{palette:t,width:u,newer:Math.max(C,B),onJump:x},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(rO,{children:[Ct.length>0?We(pt,{palette:t,items:Ct},void 0,!1,void 0,this):null,We(Ci,{palette:t,logs:nt,names:o??n,service:i,errorOnly:s,width:u,onService:p,onToggleErrors:f,facets:D},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:m,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(qs,{palette:t,start:E,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(Pi,{palette:t,logs:ie,width:Q,followTick:d,focused:!1,wrapMode:v,selected:b,follow:g,showTimestamps:e.showTimestamps!==!1,showMeta:e.showMeta!==!1,onPick:h,onLeaveLatest:y,viewStart:E,search:c,regex:e.regex===!0},d,!1,void 0,this)},void 0,!1,void 0,this),!g?We(ki,{palette:t,width:u,newer:C,onJump:x},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}function Ck(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)=>!di(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(Ci,{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(Pi,{palette:t,logs:o,width:Mp(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 Rk=I(()=>{Dm();Jt();Mt();vr();Pe();Lr()});import{jsxDEV as Ge}from"@opentui/react/jsx-dev-runtime";function Ek(e){let{palette:t,cfg:r,snap:n,profile:o,selected:i,onPick:s}=e,a=be(),c=gi(i,_k),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 m=d===o,p=u===i,f=r?.profiles[d]?.services??[],h=Object.keys(r?.profiles[d]?.environment??{}).length,v=p?t.borderActive:m?t.success:t.border,b=p?t.primary:m?t.success:t.muted;return Ge("box",{id:`${_k}-${u}`,border:!0,borderStyle:"rounded",borderColor:v,title:d,titleColor:b,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:[m?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 ${f.length} service${f.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:f.length===0?Ge("text",{fg:t.muted,wrapMode:"none",children:"(no services)"},void 0,!1,void 0,this):Ge("text",{wrapMode:"word",children:f.map((g,C)=>{let E=rr(n?.services[g]);return Ge("span",{children:[Ge("span",{fg:et(t,E),children:gr(E)},void 0,!1,void 0,this),Ge("span",{fg:Ze(g,t),children:` ${g}`},void 0,!1,void 0,this),C<f.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 _k="profile-row";var Tk=I(()=>{Jt();Mt();Lt();Pe();tt()});import{jsxDEV as V,Fragment as Ik}from"@opentui/react/jsx-dev-runtime";function nO(e){let t=e.toLowerCase();if(t==="iap")return"info";if(t==="service"||t==="service_account")return"primary";return"idle"}function oO(e,t,r){if(r||t>=500)return e.error;if(t===0||t>=400)return e.warning;return e.success}function iO(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 lO(e){let{palette:t,req:r,cols:n}=e,o=oO(t,r.status,r.error),i=r.status>0?String(r.status):"ERR",s=iO(t,r.identity),a=r.route||Mk,c=r.error?`${r.path} \u2014 ${_e(r.error,cO)}`:r.path;return V("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:[V("box",{width:Nm,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:jl,flexShrink:0,overflow:"hidden",children:V("text",{fg:t.text,wrapMode:"none",children:oe(r.method,jl)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,V("box",{width:Bl,flexShrink:0,overflow:"hidden",children:V("text",{fg:o,wrapMode:"none",children:oe(i,Bl)},void 0,!1,void 0,this)},void 0,!1,void 0,this),n.showDur?V("box",{width:Ul,flexShrink:0,overflow:"hidden",children:V("text",{fg:t.muted,wrapMode:"none",children:oe(`${r.durationMs}ms`,Ul)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,V("box",{width:Wl,flexShrink:0,overflow:"hidden",children:V("text",{fg:s.color,wrapMode:"none",children:oe(s.label,Wl)},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 dO(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:Nm,flexShrink:0,overflow:"hidden",children:V("text",{fg:t.muted,children:oe("TIME",Nm)},void 0,!1,void 0,this)},void 0,!1,void 0,this),r.showMethod?V("box",{width:jl,flexShrink:0,overflow:"hidden",children:V("text",{fg:t.muted,children:oe("METHOD",jl)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,V("box",{width:Bl,flexShrink:0,overflow:"hidden",children:V("text",{fg:t.muted,children:oe("ST",Bl)},void 0,!1,void 0,this)},void 0,!1,void 0,this),r.showDur?V("box",{width:Ul,flexShrink:0,overflow:"hidden",children:V("text",{fg:t.muted,children:oe("DUR",Ul)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,V("box",{width:Wl,flexShrink:0,overflow:"hidden",children:V("text",{fg:t.muted,children:oe("WHO",Wl)},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 uO(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:nO(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 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((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,m=o<pi,p=Math.max(Ak,Math.min(pO,Math.floor(o*0.36))),f=m?o-Lk:o-p-Lk,h=Math.max(20,f-2),v=h>=Dk,b=h>=mO,g=h>=Dk?fO:hO,C=l.reduce((S,y)=>Math.min(g,Math.max(S,(y.route||Mk).length+aO)),sO),E={showMethod:v,showDur:b,routeCol:C};return V(kt,{palette:t,title:"proxy",children:[V(pt,{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:m?"column":"row",overflow:"hidden",children:[V("box",{flexGrow:m?1:0,flexBasis:m?0:void 0,flexShrink:0,minWidth:m?void 0:Ak,width:m?void 0:p,minHeight:m?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(Ik,{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),x=y&&(y.host||y.path)?`${y.host||"*"}${y.path?y.path:""}`:void 0,w=a.get(S.name);return V(uO,{palette:t,name:S.name,auth:S.auth,identity:S.identity,upstream:S.upstream,match:x,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:za(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:m?void 0:30,minHeight:m?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(Ik,{children:[V(dO,{palette:t,cols:E},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(lO,{palette:t,req:S,cols:E},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 Nm=9,jl=8,Bl=4,Ul=8,Wl=4,sO=8,aO=2,cO=64,Mk="(none)",Ak=26,pO=56,Lk=2,Dk=46,mO=54,fO=20,hO=12;var $k=I(()=>{Jt();yr();Ot();Pe()});import{jsxDEV as pe}from"@opentui/react/jsx-dev-runtime";function Hk(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(Hm,{...e,compact:!1},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function Hm(e){let{palette:t,cfg:r,snap:n,name:o,reveal:i,width:s,compact:a=!0,envScrollRef:c}=e,l=be(),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],m=rr(u),p=u?.health??"UNKNOWN",f=yo(u)||Ms(d,u),h=$b(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",b=e.envStatus==="resolved"?"resolved":e.envStatus==="loading"?"resolving":e.envStatus==="error"?"config fallback":"config",g=s>=vO,C=[{label:"health",value:zc(d)},{label:"workdir",value:d.working_dir||"."},{label:"restart",value:Jc(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"}]:[]],E=[{label:"identity",value:Yc(d,u)},{label:"depends",value:d.dependencies.map(Ia).join(", ")||"\u2014"},{label:"ports",value:Ms(d,u)},{label:"tries",value:String(u?.restarts??0),tone:u?.restarts?"warning":"muted"}],S=Math.max(Ro+8,s-2),y=g?Math.floor(S/2):S;return pe("box",{padding:l.pad,flexGrow:1,flexDirection:"column",overflow:"hidden",children:[pe(pt,{palette:t,items:[{text:m,tone:Nk(m)},{text:p,tone:Nk(p)},{text:u?.pid?`pid ${u.pid}`:"pid \u2014",tone:u?.pid?"info":"idle"},{text:f==="\u2014"?"port \u2014":`port ${f}`,tone:f==="\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,gO)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,pe(jm,{palette:t,item:{label:"command",value:Vc(d)},valueWidth:Math.max(8,S-Ro)},void 0,!1,void 0,this),pe(yO,{palette:t,left:C,right:E,wide:g,colWidth:y},void 0,!1,void 0,this),pe(bO,{palette:t,entries:h,reveal:i,width:s,focused:!a,scrollRef:c,source:b,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 yO(e){let{palette:t,left:r,right:n,wide:o,colWidth:i}=e,s=Math.max(4,i-Ro);if(!o)return pe("box",{width:i,flexShrink:0,flexDirection:"column",overflow:"hidden",children:[...r,...n].map((a)=>pe(jm,{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(Fk,{palette:t,items:r,width:i},void 0,!1,void 0,this),pe(Fk,{palette:t,items:n,width:i},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function Fk(e){let t=Math.max(4,e.width-Ro);return pe("box",{width:e.width,flexShrink:0,flexDirection:"column",overflow:"hidden",children:e.items.map((r)=>pe(jm,{palette:e.palette,item:r,valueWidth:t},r.label,!1,void 0,this))},void 0,!1,void 0,this)}function jm(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:Ro+n,flexDirection:"row",overflow:"hidden",children:[pe("box",{width:Ro,flexShrink:0,overflow:"hidden",children:pe("text",{fg:t.muted,wrapMode:"none",children:oe(r.label,Ro)},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 bO(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(pt,{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 m=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:m,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 Nk(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 gO=72,vO=56,Ro=9;var Bm=I(()=>{dt();Jt();Mt();Ot();Lt();Pe();tt()});import{jsxDEV as Bn}from"@opentui/react/jsx-dev-runtime";function jk(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:m,envStatus:p,envError:f,onSelectEnv:h}=e;if(n.length===0)return Bn(ze,{palette:t,title:"No services",body:"This configuration does not define any services."},void 0,!1,void 0,this);let v=a<pi,b=n[i]??"",g=Gc(a,n,v),C=4,E=v?Math.max(Um,a-C):Math.max(Um,a-g-C);return Bn("box",{flexGrow:1,flexDirection:v?"column":"row",overflow:"hidden",children:[Bn("box",{flexGrow:v?1:0,flexShrink:0,minWidth:v?void 0:jc,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:[Bn(Hl,{palette:t,checked:s},void 0,!1,void 0,this),Bn("box",{flexGrow:1,overflow:"hidden",children:Bn(Nl,{palette:t,names:n,snap:o,selected:i,checked:s,width:Wc(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),Bn("box",{flexGrow:2,minWidth:v?void 0:Um,minHeight:v?xO:void 0,border:!0,borderStyle:"rounded",borderColor:t.border,backgroundColor:t.panel,title:b||"detail",titleColor:b?Ze(b,t):t.primary,overflow:"hidden",flexDirection:"column",children:Bn(Hm,{palette:t,cfg:r,snap:o,name:b,reveal:c,width:E,resolvedEnv:m,envStatus:p,envError:f,onSelectEnv:h},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var Um=36,xO=12;var Bk=I(()=>{Jt();yr();Lt();tt();Bm();Im()});import{jsxDEV as ke}from"@opentui/react/jsx-dev-runtime";function SO(e){switch(e){case"cycle":return"\u2194";case"toggle":return"\u23FB";case"page":return"\u25B8";case"action":return"!";default:return"\xB7"}}function Kk(e){let{palette:t,items:r,selected:n,locked:o,width:i,onPick:s,onActivate:a}=e,c=be(),l=kp(r,n),d=In(r,l);return ke("box",{flexGrow:1,border:!0,borderStyle:"rounded",borderColor:t.borderActive,backgroundColor:t.panel,title:"settings",titleColor:t.primary,flexDirection:"column",overflow:"hidden",children:[ke("box",{height:2,paddingLeft:c.pad,flexShrink:0,flexDirection:"column",overflow:"hidden",children:[ke("text",{fg:t.primary,wrapMode:"none",children:"Preferences"},void 0,!1,void 0,this),ke("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),ke("box",{flexGrow:1,paddingLeft:c.pad,paddingRight:c.pad,overflow:"hidden",flexShrink:1,children:ke(kO,{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),ke(PO,{palette:t,item:d,scale:c},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function kO(e){let{palette:t,items:r,active:n,scale:o,width:i,onPick:s,onActivate:a}=e,c=gi(n,Gk),l=Math.max(8,i-Uk-Wk-10),d=0,u=Eb(r);return ke("scrollbox",{ref:c,focused:!1,stickyScroll:!1,scrollX:!1,style:Ue(t),children:ke("box",{flexDirection:"column",overflow:"hidden",children:u.map((m,p)=>{let f=d;d+=m.items.length;let h=n>=f&&n<f+m.items.length;return ke("box",{flexDirection:"column",flexShrink:0,marginBottom:p<u.length-1?Math.max(1,o.gap):0,overflow:"hidden",children:[ke("box",{height:1,overflow:"hidden",children:ke("text",{fg:h?t.primary:t.muted,wrapMode:"none",children:m.group},void 0,!1,void 0,this)},void 0,!1,void 0,this),m.group==="MCP"?ke("box",{height:1,overflow:"hidden",children:ke("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,m.items.map((v,b)=>{let g=f+b;return ke(CO,{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)})]},m.group,!0,void 0,this)})},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function CO(e){let{palette:t,index:r,item:n,active:o,rowH:i,valueWidth:s,onPick:a,onActivate:c}=e;return ke("box",{id:`${Gk}-${r}`,height:i,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:o?t.highlight:void 0,onMouseDown:()=>{if(o){c();return}a()},children:[ke("box",{width:2,flexShrink:0,overflow:"hidden",children:ke("text",{fg:t.primary,children:o?"\u203A":" "},void 0,!1,void 0,this)},void 0,!1,void 0,this),ke("box",{width:Wk,flexShrink:0,overflow:"hidden",children:ke("text",{fg:o?t.primary:t.muted,children:SO(n.kind)},void 0,!1,void 0,this)},void 0,!1,void 0,this),ke("box",{width:Uk,flexShrink:0,overflow:"hidden",children:ke("text",{fg:o?t.primary:t.text,wrapMode:"none",children:n.name},void 0,!1,void 0,this)},void 0,!1,void 0,this),ke("box",{flexGrow:1,overflow:"hidden",children:ke("text",{fg:RO(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 PO(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 ke("box",{height:wO,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:[ke("box",{height:1,overflow:"hidden",children:ke("text",{fg:t.primary,wrapMode:"none",children:o?`${Rb(n.steps)} ${n.label}`:i},void 0,!1,void 0,this)},void 0,!1,void 0,this),ke("box",{height:1,overflow:"hidden",children:ke("text",{fg:t.text,wrapMode:"word",children:r?.detail??""},void 0,!1,void 0,this)},void 0,!1,void 0,this),ke("box",{height:1,overflow:"hidden",children:ke("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 RO(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,Wk=2,wO=5,Gk="settings-row";var qk=I(()=>{Mt();Ot();Pe();Dr()});import{jsxDEV as Ae}from"@opentui/react/jsx-dev-runtime";function Vk(e){let{palette:t,cfg:r,google:n,bootError:o,bootErrorMissing:i=!1,step:s=0}=e,a=be();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=_O(r,n,c),d=l.filter((g)=>g.ok).length,u=l.length,m=u>0?Math.round(d/u*100):0,p=20,f=Math.round(m/100*p),h="\u2588".repeat(f)+"\u2591".repeat(Math.max(0,p-f)),v=m===100?t.success:m===0?t.muted:t.primary,b=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:` (${m}%)`},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,C)=>{let E=C===s;return Ae("box",{height:a.rowH,flexDirection:"row",overflow:"hidden",backgroundColor:E?t.highlight:void 0,paddingLeft:1,children:[Ae("box",{width:2,flexShrink:0,children:Ae("text",{fg:et(t,g.ok?"OK":"WARN"),children:gr(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:E?t.primary:t.muted,children:String(C+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:E?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:E?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),b?Ae("box",{border:!0,borderStyle:"rounded",borderColor:b.ok?t.success:t.warning,title:b.name,titleColor:b.ok?t.success:t.warning,flexDirection:"column",paddingLeft:1,paddingRight:1,flexShrink:0,overflow:"hidden",children:Ae("text",{fg:b.ok?t.text:t.warning,wrapMode:"word",children:b.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 _O(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=Ml(e,void 0,t).project;return[{name:pn[0],ok:Boolean(e?.repoRoot),detail:e?.repoRoot||"unknown"},{name:pn[1],ok:Boolean(e?.project.name),detail:e?.project.name||"set project.name"},{name:pn[2],ok:Boolean(i),detail:i||"optional for local-only"},{name:pn[3],ok:t?.adcAvailable===!0,detail:t?.adcAvailable?"ADC available":"needed only for cloud identity / IAP"},{name:pn[4],ok:!0,detail:"read from configuration; never hard-coded"},{name:pn[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:pn[6],ok:n.length===0||Boolean(e),detail:`${n.length} fixed ports from configuration`},{name:pn[7],ok:Object.keys(e?.profiles??{}).length>0,detail:`${Object.keys(e?.profiles??{}).length} profile(s)`},{name:pn[8],ok:r.length===0,detail:r.length===0?"configuration is valid":r[0]??"invalid"}]}var pn;var zk=I(()=>{Jt();Mt();Pe();tt();pn=["Repository","Environment","Google project","Authentication","Service accounts","IAP","Ports","Profiles","Validation"]});import{useEffect as EO,useState as TO}from"react";import{jsxDEV as ee}from"@opentui/react/jsx-dev-runtime";function Ri(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 Wm(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 zs(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:Wm(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 Yk(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 Vs(e){let{palette:t,facts:r,width:n}=e,o=Jp(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(zs,{palette:t,header:!0,gap:hi,cells:i},void 0,!1,void 0,this),ee(Yk,{palette:t,width:n},void 0,!1,void 0,this),r.map((s,a)=>{let c=s.meaning===""?[""]:rl(s.meaning,Math.max(1,o.meaning)),l=s.meter?qx(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(zs,{palette:t,gap:hi,stripe:a%2===1,cells:d},void 0,!1,void 0,this),c.slice(1).map((u,m)=>{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(zs,{palette:t,gap:hi,stripe:a%2===1,cells:p},`${s.what}-more-${m}`,!1,void 0,this)})]},`${s.what}-${s.reading}-${a}`,!0,void 0,this)})]},void 0,!0,void 0,this)}function Jk(e){let{palette:t,cfg:r,snap:n,width:o,onRefresh:i}=e,s=be(),[,a]=TO(0);if(EO(()=>{let B=setInterval(()=>{a((z)=>{let de=z+1;if(i&&de%LO===0)i();return de})},AO);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)=>Vx(r.services[B])),d=Hx(c,n),u=ew(d,l),m=l&&d.live>0?d.healthy/d.live:d.total>0?d.live/d.total:0,p=li(m,DO),f=d.failed>0?"error":l&&d.live>0&&d.healthy<d.live?"warning":"success",h=Wx(o,s.pad),v=n?.session_id?td(n.session_id):void 0,b=v?Ts(Date.now()-v.getTime()):"just started",g=n?.logs.total??0,C=n?.logs.errors??0,E=Ux(n?.logs.counts??{}),S=n?.proxy.routes??[],y=n?.credentials,x=(y?.entries??[]).filter((B)=>B.valid).length,w=n?.system,k=w?w.memAvailableKB??w.memFreeKB:0,D=w?Yx(w.loadAvg1,w.cpuCount):void 0,O=w?Jx(k,w.memTotalKB):void 0,F=[];if(w&&D&&O){F.push(D,O,{what:"Computer",reading:zx(w.platform),meaning:`on for ${Ts(w.hostUptimeSec*IO)}`,tone:"text"});let B=n?.stats_series;if(B&&B.cpu.length>1)F.push({what:"Load trend",reading:Yp(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:Yp(B.mem),meaning:"used memory over the same window",tone:"text"})}let Z=d.failed>0||D?.tone==="error"||O?.tone==="error"?"error":f==="warning"||D?.tone==="warning"||O?.tone==="warning"?"warning":"success",G=[{what:"This session",reading:b,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"}],R=[{what:"Lines kept",reading:String(g),meaning:"log lines still in memory",tone:"text"},{what:"Error lines",reading:String(C),meaning:C>0?"open Logs and press e to see only errors":"no ERROR or FATAL lines",tone:C>0?"error":"muted"},...E.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?`${x} of ${y.entries.length} good`:"none",meaning:y?Zx(y.backend):"nothing stored yet",tone:y&&x<y.entries.length?"warning":y?"success":"muted"}];return ee(kt,{palette:t,title:"stats",scroll:!0,children:[ee(Ri,{palette:t,title:`Your services ${d.live} of ${d.total} started`,tone:Z,children:[ee(Vs,{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",Jp(u,h).what)},void 0,!1,void 0,this),ee("span",{children:" ".repeat(hi)},void 0,!1,void 0,this),ee("span",{fg:Wm(t,{text:"",width:1,tone:f}),children:`[${p}]`},void 0,!1,void 0,this),ee("span",{fg:Wm(t,{text:"",width:1,tone:f}),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(Ri,{palette:t,title:"This computer",tone:D?.tone==="error"||O?.tone==="error"?"error":D?.tone==="warning"||O?.tone==="warning"?"warning":"success",children:ee(Vs,{palette:t,facts:F,width:h},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,ee(Ri,{palette:t,title:"This session",children:ee(Vs,{palette:t,facts:G,width:h},void 0,!1,void 0,this)},void 0,!1,void 0,this),ee(Ri,{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(MO,{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(Ri,{palette:t,title:"Log lines",tone:C>0?"warning":"muted",children:ee(Vs,{palette:t,facts:R,width:h},void 0,!1,void 0,this)},void 0,!1,void 0,this),ee(Ri,{palette:t,title:"Other parts",children:ee(Vs,{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 MO(e){let{palette:t,names:r,snap:n,width:o}=e,i=Gx(o),s=[{text:"service",width:i.name},{text:"status",width:Ir}];if(i.health)s.push({text:"check",width:Wr});if(i.cpu)s.push({text:"cpu",width:Ds});if(i.mem)s.push({text:"ram",width:Is});if(i.up)s.push({text:"up",width:Ls});if(i.rst)s.push({text:"restarts",width:fl});if(i.pid)s.push({text:"pid",width:ln});return ee("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:[ee(zs,{palette:t,header:!0,gap:$t,cells:s},void 0,!1,void 0,this),ee(Yk,{palette:t,width:o},void 0,!1,void 0,this),r.map((a,c)=>ee(NO,{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 FO(e){if(e===void 0)return"muted";if(e>$O)return"error";if(e>OO)return"warning";return"text"}function NO(e){let{palette:t,name:r,rt:n,cols:o,stripe:i}=e,s=Xx(n),a=Qx(n),c=n?.restarts??0,l=[{text:r,width:o.name,color:Ze(r,t)},{text:s,width:Ir,color:et(t,n?.state??"STOPPED")}];if(o.health)l.push({text:a,width:Wr,color:et(t,n?.health??"UNKNOWN")});if(o.cpu)l.push({text:n?.cpuPercent!==void 0?Lb(n.cpuPercent):"\u2014",width:Ds,tone:FO(n?.cpuPercent)});if(o.mem)l.push({text:n?.memoryKB!==void 0?As(n.memoryKB):"\u2014",width:Is,tone:"text"});if(o.up)l.push({text:Bx(n),width:Ls,tone:"text"});if(o.rst)l.push({text:String(c),width:fl,tone:c>0?"warning":"muted"});if(o.pid)l.push({text:n?.pid?String(n.pid):"\u2014",width:ln,tone:"muted"});return ee("box",{flexDirection:"column",flexShrink:0,overflow:"hidden",children:[ee(zs,{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+Ir+Wr))},void 0,!1,void 0,this)},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}var AO=1000,LO=5,DO=16,IO=1000,OO=80,$O=100;var Xk=I(()=>{Jt();Mt();Ot();vr();Lt();hl();Pe();tt()});import{useRenderer as HO,useTerminalDimensions as jO}from"@opentui/react";import{useCallback as Un,useEffect as BO,useMemo as Qk,useRef as zr,useState as Je}from"react";import{jsxDEV as he,Fragment as UO}from"@opentui/react/jsx-dev-runtime";function Zk({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:m,freePort:p,readTextFile:f,writeTextFile:h,createStarterConfig:v,validate:b}=c,[g,C]=Je(e),[E,S]=Je(i),y=HO(),{width:x,height:w}=jO(),[k,D]=Je(g?"dashboard":"setup"),[O,F]=Je("none"),[Z,G]=Je(""),[R,j]=Je("commands"),[B,z]=Je(0),[de,Ee]=Je(0),[Ne,ne]=Je(0),[H,ae]=Je([]),[Q,nt]=Je(),[ft,ie]=Je(E??""),[Le,Me]=Je({}),[Ke,gt]=Je(),[Xe,Ct]=Je(Ob(g?.cfg)),[ge,vt]=Je(""),[at,yt]=Je(!1),[nr,Qe]=Je(),[Qt,qe]=Je(),[ht,xr]=Je(),Kt=zr(null),wr=zr(null),or=zr(null),qt=zr(null),ir=zr(null),sr=zr(null),ar=zr(null),Vt=zr(""),[ce,K]=Je("quit"),A=zr(void 0),ve=zr(0),[J,Dt]=Je(g?.cfg),ct=g?.previousPersisted,[Re,_o]=Je(void 0),_=_S({tui:t,controller:g,resolveTuiOverridePath:d,terminalBackground:a,userTuiConfigPath:u,snap:Q,setStatus:ie,saveTuiPreferences:l,setPaletteIndex:z,setOverlay:F,setConfirmKind:K,setScreen:D,setSelected:ne,screen:k}),{themeName:ue,setThemeName:Jr,fontSize:Xr,prefsLocked:Sr,palette:se,rootBackground:Wn,settingRows:Gn,persistPrefs:$r,persistTheme:mn,activateSetting:Ys}=_,_i=hS({controller:g,workspace:c,cfg:J,snap:Q,screen:k,configReloadError:Re,setSnap:nt,setStatus:ie}),{google:Ei,doctor:le,doctorLoading:bt,doctorError:Pt,doctorProgress:Qr,setDoctorTick:Js}=_i,Eo=t.keybinds.copy??en(),kr=Qk(()=>Object.keys(J?.services??{}).sort(),[J]),fn=Un(async()=>{if(!g)return;try{let X=await g.status();if(nt(X),X.profile&&Xe==="")Ct(X.profile);return X}catch(X){ie(L(X));return}},[g,Xe]),Gl=wS({controller:g,tui:t,names:kr,screen:k,refresh:fn,setStatus:ie}),{logs:Ym,setLogs:CC,paused:Jm,errorOnly:Kl,setErrorOnly:ql,showSystemLogs:Xm,logSearch:Qm,setLogSearch:Zm,logSearchFocused:PC,setLogSearchFocused:RC,logFollow:ef,logSince:_C,logService:tf,setLogService:Vl,logServiceB:EC,setLogServiceB:TC,splitLogs:zl,splitFocus:Yl,setSplitFocus:AC,logSliceB:rf,logWindowB:nf,logSelectedB:of,logPinnedB:LC,pinLogViewB:DC,logServices:IC,logShowTimestamps:MC,logShowMeta:OC,logSource:$C,logRegex:sf,logWrap:af,logPinned:cf,logSelected:lf,logsFullscreen:Jl,dashboardLogCursor:FC,setDashboardLogCursor:NC,logFacets:df,logSources:uf,filteredLogs:pf,filteredLogsB:HC,logWindow:Xs,logSlice:Qs,pinLogView:Xl,applyLogCursorA:jC,applyLogCursorB:BC,jumpToLatestLogs:mf}=Gl,To=Qk(()=>{if(R==="tasks")return Gp(Object.keys(J?.tasks??{}),Z,"tasks","run this task");if(R==="services")return Gp(Object.keys(J?.services??{}),Z,"services","exec in this service");return Ox(Z,{services:Object.keys(J?.services??{}),tasks:Object.keys(J?.tasks??{})})},[J,Z,R]);BO(()=>{Ee(0)},[Z]);let Ql=sx(k,{doctor:le?.checks.length??0,settings:Gn.length,profiles:Object.keys(J?.profiles??{}).length,services:kr.length,logs:zl&&Yl===1?rf.length:Qs.length,mcp:xw(),config:Object.keys(J?.tasks??{}).length}),ff=k==="logs"?zl&&Yl===1?of:lf:Ne,Fr=Ql<=0?Math.max(0,ff):Math.max(0,Math.min(ff,Ql-1)),UC=k==="detail"?ge:k==="services"?kr[Fr]??"":"",{inspectorEnv:hf,inspectorEnvStatus:gf,inspectorEnvError:vf,resolveEnvironment:WC}=MS({controller:g,cfg:J,envService:UC}),Ao=Un(()=>{if(F("none"),G(""),j("commands"),RC(!1),A.current)clearTimeout(A.current)},[]),Zl=Un((X)=>{ae((xt)=>xt.includes(X)?xt.filter((oP)=>oP!==X):[...xt,X])},[]),yf=CS({tui:t,controller:g,cfg:J,persistPrefs:$r,snap:Q,refresh:fn,setStatus:ie}),{mcpPortDraft:GC,mcpPort:KC,toggleMcpTool:qC,toggleMcp:VC,copyMcpSnippet:zC}=yf;uS({controller:g,cfg:J,paused:Jm,logSince:_C,refresh:fn,setLogs:CC,setCfg:Dt,setConfigReloadError:_o,setStatus:ie});let Zs=Un((X)=>{vt(X),D("detail")},[]),bf=Un((X)=>{let xt=X.value!==""?X.value:X.required?"(required, not set)":"(empty)";xr({title:X.key,body:xt}),F("scroll-text")},[]),ed=vS({controller:g,workspace:c,cfg:J,snap:Q,refresh:fn,setStatus:ie,setOverlay:F}),{plan:ea,planInitiallyRunning:YC,planBusy:JC,lifecycle:xf}=ed,Zr=LS({workspace:c,renderer:y,setOverlay:F,setStatus:ie,setScreen:D,onAttached:o,setController:C,setCfg:Dt,setBootError:S}),{configEditRef:XC,configEditText:QC,configEditError:ZC,setConfigEditError:eP,openConfigBuffer:wf,saveConfigBuffer:tP}=lS({cfg:J,readTextFile:f,setOverlay:F,setStatus:ie,validateConfigText:m,writeTextFile:h,controller:g,setConfirmKind:K,refresh:fn}),Sf=Un(async()=>{let X=y.getSelection()?.getSelectedText()??"";if(X.trim()===""){ie("Nothing selected \u2014 drag to highlight text");return}try{await bl(X);let xt=X.split(`
|
|
2233
|
-
`).length;ie(xt===1?"Copied selection":`Copied ${xt} selected lines`)}catch(xt){ie(L(xt))}},[y]),{runCommand:ta}=iS({setOverlay:F,setQuery:G,setSlashPicker:j,checked:H,setConfirmKind:K,setStatus:ie,persistTheme:mn,setPaletteIndex:z,themeName:ue,setScreen:D,setSelected:ne,screen:k,renderer:y,cfg:J,setScrollText:xr,reveal:at,controller:g,refresh:fn,profile:Xe,setReveal:yt,resolveEnvironment:WC,openDetail:Zs,copySelection:Sf,lastExportPath:Vt,openConfigBuffer:wf,onDown:n??((X)=>r(X)),workspace:c,logView:Gl,diagnostics:_i,lifecycleActions:ed}),kf=Un(()=>{let X=fi(To,de);if(R==="tasks"){if(!X){ie(To.length===0?"no tasks configured":"pick a task");return}j("commands");let xt=Gr("run");if(xt)ta(xt,[X.name]);return}if(R==="services"){if(!X){ie(To.length===0?"no services configured":"pick a service");return}j("commands"),G(`exec ${X.name} -- `);return}if(!X){ie(Z.trim()===""?"pick a command from the list":`unknown command /${Z}`),Ao();return}ta(X,Mx(X,Z))},[Ao,To,Z,ta,de,R]),Cf=Un((X)=>{mn(X),Ao()},[Ao,mn]);jw({tui:t,controller:g,cfg:J,snap:Q,ui:{screen:k,overlay:O,onQuit:r,onDown:n??((X)=>r(X)),closeOverlay:Ao,confirmKind:ce,confirmDetail:Le,portTarget:Ke,profile:Xe,listCursor:Fr,names:kr,listCount:Ql,openDetail:Zs,copySelection:Sf,height:w,paletteIndex:B,applyTheme:Cf,runCommand:ta,saveConfigBuffer:tP,filtered:To,slashIndex:de,submitSlash:kf,advanceWizard:()=>void Zr.advanceWizard(),bootErrorMissing:s,bootError:E,createStarterConfig:v,startWizard:Zr.startWizard,toggleChecked:Zl,checked:H,detailName:ge,refresh:fn,openConfigBuffer:wf,setOverlay:F,setConfirmKind:K,setConfirmDetail:Me,setPortTarget:gt,setLogDetail:Qe,logDetail:nr,setLogSearch:Zm,setProfile:Ct,setStatus:ie,setPaletteIndex:z,setSlashIndex:Ee,setQuery:G,setSlashPicker:j,setScreen:D,setSelected:ne,setChecked:ae,setConfigEditError:eP,freePort:p},logView:Gl,lifecycleActions:ed,mcp:yf,preferences:_,diagnostics:_i,refs:{interruptArmedAt:ve,leaderTimer:A,logDetailsScrollRef:qt,scrollTextScrollRef:sr,routeDetailsScrollRef:ir,planScrollRef:ar,helpScrollRef:wr,configScrollRef:Kt,detailScrollRef:or}});let Pf=fx(ce,Xe,Le),rP=new Set(ea?.waves.flat()??[]),nP=ea===void 0||xf==="stop"?"":Object.entries(Q?.services??{}).find(([X,xt])=>rP.has(X)&&xt.state==="FAILED")?.[0]??"";return he(Pp.Provider,{value:_s(Xr),children:he("box",{flexDirection:"column",width:x,height:w,backgroundColor:Wn,overflow:"hidden",children:[Jl?null:he(UO,{children:[he(nw,{palette:se,cfg:J,snap:Q,google:Ei,profile:Xe,reveal:at,width:x},void 0,!1,void 0,this),he(ow,{palette:se,screen:k,width:x,onSelect:D},void 0,!1,void 0,this),Re?he("box",{height:1,paddingLeft:1,backgroundColor:se.panel,overflow:"hidden",children:he("text",{fg:se.error,wrapMode:"none",children:`\u26A0 configuration reload failed: ${Re}`},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(xk,{palette:se,cfg:J,snap:Q,logs:Ym,names:kr,selected:Fr,checked:H,width:x,followTick:ef,logService:tf,errorOnly:Kl,showSystemLogs:Xm,onOpen:Zs,onSelectIndex:ne,selectedLog:FC,onToggle:Zl,logSources:uf,onFilterService:Vl,onToggleErrors:()=>ql((X)=>!X),onShowErrors:()=>{Vl(""),ql(!0)},wrapMode:af,view:Qs,follow:!cf,onLeaveLatest:Xl,onPickLog:(X)=>{let xt=Qs[X];if(!xt)return;NC(X),Qe(xt),Xl()},viewStart:Xs.start,viewTotal:pf.length,newer:Xs.newer,onJumpLatest:mf,facets:df,leftover:ct,search:Qm,regex:sf},void 0,!1,void 0,this):null,k==="services"?he(jk,{palette:se,cfg:J,names:kr,snap:Q,selected:Fr,checked:H,width:x,reveal:at,onOpen:Zs,onSelectIndex:ne,onToggle:Zl,resolvedEnv:hf,envStatus:gf,envError:vf,onSelectEnv:bf},void 0,!1,void 0,this):null,k==="detail"?he(Hk,{palette:se,cfg:J,snap:Q,name:ge,reveal:at,width:x,envScrollRef:or,resolvedEnv:hf,envStatus:gf,envError:vf,onSelectEnv:bf},void 0,!1,void 0,this):null,k==="logs"?he(Pk,{palette:se,logs:Ym,names:kr,logSources:uf,service:tf,errorOnly:Kl,showSystemLogs:Xm,search:Qm,searchFocused:PC,followTick:ef,width:x,onSearch:Zm,onService:Vl,onToggleErrors:()=>ql((X)=>!X),source:$C,regex:sf,services:IC,showTimestamps:MC,showMeta:OC,view:Qs,wrapMode:af,selected:lf,follow:!cf,newer:Xs.newer,viewStart:Xs.start,viewTotal:pf.length,onSelect:jC,fullscreen:Jl,onLeaveLatest:Xl,onJumpLatest:mf,facets:df,split:zl,splitFocus:Yl,serviceB:EC,viewB:rf,selectedB:of,followB:!LC,newerB:nf.newer,viewStartB:nf.start,viewTotalB:HC.length,onServiceB:TC,onSelectB:BC,onLeaveLatestB:DC,onFocusPane:AC},void 0,!1,void 0,this):null,k==="auth"?he(ik,{palette:se,cfg:J,google:Ei,identity:Q?.identity},void 0,!1,void 0,this):null,k==="credentials"?he(pk,{palette:se,credentials:Q?.credentials},void 0,!1,void 0,this):null,k==="proxy"?he(Ok,{palette:se,cfg:J,snap:Q,width:x,onSelectRoute:(X)=>{qe(X),F("route-details")}},void 0,!1,void 0,this):null,k==="mcp"?he(ww,{palette:se,snap:Q,port:KC,portDraft:GC,selected:Fr,onPick:ne,onToggle:()=>{VC()},onCopy:zC,onToggleTool:(X)=>{qC(X)}},void 0,!1,void 0,this):null,k==="doctor"?he(Sk,{palette:se,report:le,loading:bt,progress:Qr,error:Pt,selected:Fr,onPick:ne,onReload:()=>{Js((X)=>X+1),ie("Re-running doctor")}},void 0,!1,void 0,this):null,k==="stats"?he(Jk,{palette:se,cfg:J,snap:Q,width:x,onRefresh:fn},void 0,!1,void 0,this):null,k==="config"?he(dk,{palette:se,cfg:J,width:x,selectedTask:Fr,scrollRef:Kt},void 0,!1,void 0,this):null,k==="profiles"?he(Ek,{palette:se,cfg:J,snap:Q,profile:Xe,selected:Fr,onPick:ne},void 0,!1,void 0,this):null,k==="setup"?he(Vk,{issues:J?b(J):["configuration not loaded"],palette:se,cfg:J,google:Ei,bootError:E,bootErrorMissing:s,step:Fr},void 0,!1,void 0,this):null,k==="settings"?he(Kk,{palette:se,items:Gn,selected:Fr,locked:Sr,width:x,onPick:ne,onActivate:Ys},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),O==="slash"?he(QS,{palette:se,items:To,query:Z,selected:de,title:R==="tasks"?"tasks":R==="services"?"services":"commands",onQuery:G,onSubmit:kf},void 0,!1,void 0,this):null,O==="themes"?he(nk,{palette:se,themeName:ue,selected:B%St.length,termW:x,termH:w,onIndex:z,onPreview:Jr,onPick:Cf},void 0,!1,void 0,this):null,O==="help"?he(Pw,{palette:se,termW:x,termH:w,copyKey:Eo,scrollRef:wr},void 0,!1,void 0,this):null,O==="leader"?he(BS,{palette:se,termW:x,termH:w},void 0,!1,void 0,this):null,O==="confirm"?he(NS,{palette:se,title:Pf.title,body:Pf.body,kind:ce,termW:x,termH:w},void 0,!1,void 0,this):null,O==="setup-wizard"?he(TS,{palette:se,termW:x,termH:w,step:Zr.step,answers:Zr.answers,repo:Zr.repo,draft:Zr.draft,authStatus:Zr.authStatus,onDraft:Zr.setDraft,onSubmit:()=>void Zr.advanceWizard()},void 0,!1,void 0,this):null,O==="log-details"?he(WS,{palette:se,event:nr,termW:x,termH:w,scrollRef:qt},void 0,!1,void 0,this):null,O==="route-details"?he(zS,{palette:se,route:Qt,termW:x,termH:w,scrollRef:ir},void 0,!1,void 0,this):null,O==="scroll-text"&&ht?he(JS,{palette:se,title:ht.title,body:ht.body,termW:x,termH:w,scrollRef:sr},void 0,!1,void 0,this):null,O==="config-edit"&&J?he($S,{palette:se,path:J.configPath,initialValue:QC,error:ZC,termW:x,termH:w,textareaRef:XC},void 0,!1,void 0,this):null,O==="plan"&&ea?he(qS,{palette:se,plan:ea,snap:Q,busy:JC,failed:nP,kind:xf,termW:x,termH:w,scrollRef:ar,initiallyRunning:YC,onDismiss:Ao},void 0,!1,void 0,this):null,Jl?null:he(iw,{palette:se,screen:k,overlay:O,status:ft,paused:Jm,errorOnly:Kl,width:x,copyKey:Eo},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}var eC=I(()=>{te();Jt();Qp();Mt();yr();ko();On();Lt();Bw();sS();dS();pS();gS();yS();SS();PS();ES();DS();OS();FS();HS();ym();US();GS();VS();YS();XS();Cm();ZS();ok();sk();uk();mk();wk();kk();Rk();Us();Tk();$k();Bm();Bk();qk();zk();Xk();Dr();tt();Lr()});var tC={};cP(tC,{renderApp:()=>Km,runTui:()=>Gm,runTuiWithController:()=>KO});import{createCliRenderer as WO}from"@opentui/core";import{createRoot as GO}from"@opentui/react";import{jsxDEV as qO}from"@opentui/react/jsx-dev-runtime";async function KO(e,t){let r=e.loadTuiConfig(t.cfg.repoRoot,t.cfg.ui.keymap);await Km(e,t,r)}async function Gm(e,t){let r,n,o=!1;try{r=await e.openTui("",t)}catch(s){n=L(s),o=Pa(s,zn)}let i=e.loadTuiConfig(r?.cfg.repoRoot??process.cwd(),r?.cfg.ui.keymap);await Km(e,r,i,n,o)}async function Km(e,t,r=e.loadTuiConfig(t?.cfg.repoRoot??process.cwd(),t?.cfg.ui.keymap),n,o=!1){ra();let i=Ef(),s=await WO({exitOnCtrlC:!1,useMouse:r.mouse,enableMouseMovement:r.mouse,useKittyKeyboard:{}}),a=GO(s),c=null;try{c=(await s.getPalette({timeout:120,size:16})).defaultBackground}catch{}await new Promise((l)=>{let d=(m)=>{let p=()=>{a.unmount(),i(),s.destroy(),l(),process.exit(0)};if(!t){p();return}t.close({detach:m,shutdownSupervisor:!0}).finally(p)},u=(m)=>{let p=()=>{a.unmount(),i(),s.destroy(),l(),process.exit(0)};if(!t){p();return}t.shutdown({stopServices:!m}).then(()=>t?.close({detach:!0})).finally(p)};a.render(qO(Zk,{workspace:gb(e),controller:t,tui:r,onQuit:d,onDown:u,onAttached:(m)=>{t=m},bootError:n,bootErrorMissing:o,terminalBackground:c},void 0,!1,void 0,this))})}var qm=I(()=>{vb();te();eC()});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 ua,readFileSync as ca}from"fs";import{dirname as HP,isAbsolute as Nf,join as da,resolve as jP}from"path";na();na();import{spawnSync as gP}from"child_process";import{randomBytes as od}from"crypto";import{copyFileSync as vP,existsSync as hn,mkdirSync as yP,readdirSync as bP,readFileSync as id,renameSync as sd,statSync as xP,unlinkSync as rd,writeFileSync as wP}from"fs";import{homedir as Tf}from"os";import{basename as SP,dirname as nd,isAbsolute as kP,join as Rt,resolve as CP}from"path";var PP=448,RP=384;function Ti(e,t){let r=e.startsWith("~/")?Rt(Tf(),e.slice(2)):e;return kP(r)?r:CP(t,r)}function lr(){let e=process.env.DEVCTL_HOME;if(e&&e!=="")return e;return Rt(Tf(),".devctl")}function Nr(e){yP(e,{recursive:!0,mode:PP})}function Lo(e){let t=Rt(lr(),"state",cr(e)),r=Rt(lr(),"sessions",cr(e));if(!hn(t)&&hn(r)){Nr(nd(t));try{sd(r,t)}catch{Nr(t);for(let n of["state.json","devctl.lock","devctl.sock"]){let o=Rt(r,n);if(hn(o))vP(o,Rt(t,n))}}}return Nr(t),t}function oa(){let e=Rt(lr(),"logs");return Nr(e),e}function ia(){let e=Rt(lr(),"exports");return Nr(e),e}function Cr(){let e=Rt(lr(),"credentials");return Nr(e),e}function sa(e,t=process.platform){if(t==="win32")return`\\\\.\\pipe\\devctl-${cr(e)}`;return Rt(Lo(e),"devctl.sock")}function _P(e){return Rt(Lo(e),"devctl.lock")}function Af(e){return Rt(Lo(e),"state.json")}function Ai(e){return Rt(Lo(e),"bootstrap.log")}var EP=5,Lf="bootstrap-";function Df(e){let t=Ai(e);if(!hn(t))return;let r=Lo(e),n=Rt(r,`${Lf}${ad()}.log`);try{sd(t,n)}catch{return}TP(r)}function TP(e){let t;try{t=bP(e).filter((n)=>n.startsWith(Lf)&&n.endsWith(".log"))}catch{return}let r=t.map((n)=>{let o=Rt(e,n);try{return{path:o,mtime:xP(o).mtimeMs}}catch{return}}).filter((n)=>n!==void 0).sort((n,o)=>o.mtime-n.mtime);for(let n=EP;n<r.length;n++)try{rd(r[n].path)}catch{}}function Pr(e,t){Nr(nd(e));let r=Rt(nd(e),`.${SP(e)}.tmp-${process.pid}-${od(4).toString("hex")}`);wP(r,t,{mode:RP}),sd(r,e)}function ad(e=new Date){return`${e.toISOString().slice(0,19).replace(/:/g,"-")+"Z"}-${od(3).toString("hex")}`}function gn(e){return cd(Af(e))}function cd(e){if(!hn(e))return;try{let t=JSON.parse(id(e,"utf8"));if(Array.isArray(t.processes)&&typeof t.repo_root==="string")return t;return}catch{return}}function If(e,t){Pr(Af(e),`${JSON.stringify(t,null,2)}
|
|
2234
|
-
`)}function
|
|
2235
|
-
`),t}function
|
|
2236
|
-
`,
|
|
2350
|
+
`:""}`;if(F){if(mn.length>0){f({title:"compose import",body:fn}),e("scroll-text"),s("import is invalid; not written");return}if(N(Nr)){s(`configuration already exists at ${Nr}; not overwriting`);return}ue(Nr,dr.yaml),s(`wrote ${Nr}; /reload to apply`);return}f({title:"compose import (dry-run)",body:`${fn}
|
|
2351
|
+
Enter is preview only. /import compose ${tr} --write to save.`}),e("scroll-text")}catch(dr){s(I(dr))}return}case"diff":if(!p){s("no configuration loaded");return}f({title:"config sources",body:YC(p,h)}),e("scroll-text");return;case"daemon":{let P=p?.repoRoot;if(!P){s("no repository");return}let F=k(P);if(!N(F)){s("no daemon bootstrap log yet for this repository");return}f({title:"daemon bootstrap",body:K(F)}),e("scroll-text");return}case"reload":if(!g)return;g.reload().then((P)=>{if(s(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 Z(pn,y);return;case"stop":await L(pn);return;case"restart":{let P=BS(Ee),F=lf(P.services,n);await ve(F,y,P.cascade);return}case"run":{let P=Ee[0]??"";if(!P){r("tasks"),t(""),e("slash"),s(Object.keys(p?.tasks??{}).length===0?"no tasks configured":"pick a task");return}if(!g){s("no daemon attached");return}try{let F=await g.runTask(P);Je(`task:${P}`),l("logs"),s(`task ${P} exited ${F.code}`)}catch(F){throw Je(`task:${P}`),l("logs"),F}return}case"exec":{let P=WS(Ee);if(!P.service){r("services"),t(""),e("slash"),s(Object.keys(p?.services??{}).length===0?"no services configured":"pick a service");return}if(!g){s("no daemon attached");return}if(P.printEnv){if(!p?.services[P.service]){s(`unknown service ${P.service}`);return}if(P.reveal)C(!0);s(`Resolving environment for ${P.service}\u2026`);try{await _(P.service),E(P.service),s(`Resolved environment for ${P.service}`)}catch(F){E(P.service),s(I(F))}return}if(P.command.length===0){s("usage: /exec <service> -- <command\u2026>");return}try{let F=await g.execService(P.service,P.command);Je(`${P.service}:exec`),l("logs"),s(`${P.service}:exec exited ${F.code}`)}catch(F){throw Je(`${P.service}:exec`),l("logs"),F}return}case"refresh":if(u==="auth"){await Bt();return}await x(),s("Refreshed status and logs");return;case"pause":st((P)=>!P),l("logs");return;case"filter":At((P)=>!P),l("logs");return;case"system":Se(),l("logs");return;case"clear":kt();return;case"reveal":{let P=!h;C(P),s(P?"Secrets visible this session":"Secrets hidden");return}case"wrap":{let P=Zl(gt);Ct(P),s(`Log ${td(P)}`);return}case"copy":await v();return;case"export":{let P=ie(Ee[0]);if(g)await g.logs({export:P,services:br,level:it?"ERROR":It,search:Xe,regex:Rt,source:ar});else W(P,cr);b.current=P,s(`Exported ${P}`);return}case"exports":{let P=b.current||le();re(P),s(`Opened ${le()}`);return}case"regex":Yr((P)=>!P),s(Rt?"Substring search":"Regex search");return;case"since":lr(Ee[0]||new Date(Date.now()-3600000).toISOString()),s(`Logs since ${Ee[0]||"1h"}`);return;case"until":Zt(Ee[0]||new Date().toISOString()),s(`Logs until ${Ee[0]||"now"}`);return;case"history":{let P=pt(),F=Ee[0]||P[0];if(!F){s("No persisted log sessions");return}xr(wt(F)),s(`Loaded session ${F}`);return}case"buffer":S();return;case"edit":{if(!p)return;let P=process.env.DEVCTL_EDITOR||process.env.EDITOR,F=P?[P,p.configPath]:process.platform==="darwin"?["open",p.configPath]:["xdg-open",p.configPath];Bun.spawn({cmd:F,stdout:"ignore",stderr:"ignore",stdin:"ignore"}),s(`Opened ${p.configPath} in ${F[0]}. Run /reload after saving.`);return}default:s(`/${at.name}`)}}catch(P){s(I(P))}finally{setTimeout(()=>{mt.current=!1},vF)}},[ve,Z,L,n,p,kt,g,v,it,cr,It,Rt,Xe,br,ar,gt,w,S,E,i,y,x,Bt,m,h,u,wr,r,c,Sr,Se])}}var vF=50;var eR=A(()=>{ne();sh();ih();yr();Ft();et()});import{useCallback as tR,useRef as bF,useState as rR}from"react";function nR({cfg:e,readTextFile:t,setOverlay:r,setStatus:n,validateConfigText:o,writeTextFile:s,controller:i,setConfirmKind:a,refresh:c}){let l=bF(null),[d,u]=rR(""),[m,p]=rR(""),f=tR(()=>{if(!e)return;try{u(t(e.configPath)),p(""),r("config-edit")}catch(g){n(I(g))}},[e]),h=tR(()=>{if(!e)return;let g=l.current?.plainText??d,x=o(e.repoRoot,e.configPath,g);if(x.length>0){p(x.join(`
|
|
2352
|
+
`)),n("Config buffer not saved");return}if(s(e.configPath,g),p(""),r("none"),!i){n(`Wrote ${e.configPath}`);return}i.reload().then((y)=>{if(n(y.restart_required.length===0?"Configuration saved and reloaded":`Saved; restart required: ${y.restart_required.join(", ")}`),y.restart_required.length>0)a("reload"),r("confirm");c()})},[e,d,i,c]);return{configEditRef:l,configEditText:d,configEditError:m,setConfigEditError:p,openConfigBuffer:f,saveConfigBuffer:h}}var oR=A(()=>{ne()});import{useEffect as xF}from"react";function sR({controller:e,cfg:t,paused:r,logSince:n,refresh:o,setLogs:s,setCfg:i,setConfigReloadError:a,setStatus:c}){return xF(()=>{if(!e)return;let l,d=!1,u=[],m=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);s((g)=>hS(g,h,n,m))}if(d)d=!1,o()},f=e.onEvent((h)=>{if(h.type===us&&h.payload&&typeof h.payload==="object"&&"event"in h.payload){let g=h.payload.event;if(!r)u.push(g);if(!l)l=setTimeout(p,30);return}if(h.type===gi){a(MS(h));return}if(h.type===rc)a(void 0),e.configSnapshot().then(i).catch((g)=>c(I(g)));if(d=!0,!l)l=setTimeout(p,30)});return()=>{if(f(),l)clearTimeout(l)}},[e,n,r,o]),{}}var iR=A(()=>{ne();vr();yr()});import{useCallback as wF,useEffect as aR,useRef as cR,useState as oi}from"react";function lR({controller:e,workspace:t,cfg:r,snap:n,screen:o,configReloadError:s,setSnap:i,setStatus:a}){let{detectGoogle:c,runDoctor:l,loadPath:d}=t,[u,m]=oi(),[p,f]=oi(),[h,g]=oi(!1),[x,y]=oi(""),[C,_]=oi(0),[E,v]=oi({active:"Preparing diagnostics",checks:[]}),b=cR(void 0),S=cR(0),w=wF(async()=>{if(!e){a("Supervisor is not running");return}a("Probing configured identities\u2026");try{let D=await e.refreshAuth();i((V)=>V?{...V,identity:D}:V);let O=Object.values(D.service_account_status),B=O.filter((V)=>V==="available").length,J=O.filter((V)=>V==="unavailable").length;a(O.length===0?"Identity refreshed \u2014 no service accounts configured":`Identity refreshed \u2014 ${B} available${J>0?`, ${J} unavailable`:""}`)}catch(D){a(I(D))}},[e]);return aR(()=>{c(r?.google.project_id??"").then(m).catch((D)=>{a(I(D))})},[r?.google.project_id,n?.identity.project]),aR(()=>{if(o!=="doctor"||!r)return;let D=b.current,O=n?.session_id??"";if(D?.cfg===r&&D.tick===C&&D.sessionID===O&&D.reloadError===s)return;b.current={cfg:r,tick:C,sessionID:O,reloadError:s};let B=++S.current;g(!0),y(""),v({active:"Preparing diagnostics",checks:[]});let J;if(r.configPath!==""){J="";try{d(r.repoRoot,r.configPath)}catch(V){J=I(V)}}l(r,(V)=>{if(B===S.current)v(V)},{services:n?.services,proxyRunning:n?.proxy.running,repositoryConfigError:J}).then((V)=>{if(B!==S.current)return;f(V)}).catch((V)=>{if(B!==S.current)return;y(I(V))}).finally(()=>{if(B===S.current)g(!1)})},[o,r,C,n?.session_id,s]),{google:u,setGoogle:m,doctor:p,doctorLoading:h,doctorError:x,doctorProgress:E,setDoctorTick:_,refreshAuth:w}}var dR=A(()=>{ne()});import{useCallback as ah,useState as Pd}from"react";function uR({controller:e,workspace:t,cfg:r,snap:n,refresh:o,setStatus:s,setOverlay:i}){let{resolveStartRequest:a,startupPlan:c,shutdownPlan:l,shutdownPlanExact:d}=t,[u,m]=Pd(),[p,f]=Pd([]),[h,g]=Pd(!1),[x,y]=Pd("start"),C=ah(async(v,b)=>{if(!e||!r){s("no configuration loaded");return}try{let S=v.length>0,w=a(r,{services:v,profile:S?void 0:b}),D=c(r,w.services,w.profile);y("start"),f(D.waves.flat().filter((J)=>Yo(n?.services[J]))),m(D),i("plan"),g(!0);let O=w.services.filter((J)=>!Yo(n?.services[J])),B=await e.start({services:O.length>0?O:w.services,profile:S?void 0:w.profile});await o(),g(!1),s(CC(B))}catch(S){g(!1),await o(),s(I(S))}},[r,e,o,n]),_=ah(async(v)=>{if(!e||!r){s("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){s("nothing to stop");return}try{let S=l(r,b);y("stop"),f([]),m(S),i("plan"),g(!0),await e.stop(v),await o(),g(!1),s(RC(S))}catch(S){g(!1),await o(),s(I(S))}},[r,e,o,n]),E=ah(async(v,b,S=!1)=>{if(!e||!r){s("no configuration loaded");return}try{let w=kC(r,v,b),D=S?l(r,w.services):d(r,w.services);y("restart"),f([]),m(D),i("plan"),g(!0),await e.restart(w.services,S),await o(),g(!1);let O=kd(D);s(O===""?"Restarted selected services":`Restarted ${O}`)}catch(w){g(!1),await o(),s(I(w))}},[r,e,o]);return{plan:u,planInitiallyRunning:p,planBusy:h,lifecycle:x,beginStart:C,beginStop:_,beginRestart:E}}var pR=A(()=>{ne();Cd();Ft()});import{useCallback as zr,useEffect as cn,useMemo as so,useRef as Ea,useState as Ce}from"react";function fR(e,t,r,n,o){let s=ff(e,t.sliceLen,t.windowStart,t.windowNewer);if(s.startDelta!==0){r(Math.max(0,t.windowStart+s.startDelta)),n(!0),o(s.selected);return}let i=Math.max(t.sliceLen-1,0),a=t.sliceLen>0&&(s.selected<i||t.windowNewer>0);if(a&&!t.pinned)r(ed(t.filteredLen));n(a),o(s.selected)}function hR({controller:e,tui:t,names:r,screen:n,refresh:o,setStatus:s}){let[i,a]=Ce([]),[c,l]=Ce(!1),[d,u]=Ce(!1),[m,p]=Ce(!0),[f,h]=Ce(""),[g,x]=Ce(!1),[y,C]=Ce(0),[_,E]=Ce(""),[v,b]=Ce(""),[S,w]=Ce(""),[D,O]=Ce(""),[B,J]=Ce(!1),[V,X]=Ce(0),[te,k]=Ce(Vr-1),[N,K]=Ce(!1),[ue,We]=Ce(0),ie=mR,[W,le]=Ce(t.log_timestamps!==!1),[re,pt]=Ce(t.log_metadata!==!1),[wt,ae]=Ce(()=>[...fS]),[Le]=Ce(""),[Ne]=Ce(""),[Je,St]=Ce(!1),[st,At]=Ce("all"),[Se,kt]=Ce(!1),[gt,Ct]=Ce(Vr-1),[br,it]=Ce(!1),[It,Xe]=Ce(-1),[Rt,ar]=Ce(),[cr,Yr]=Ce(""),[lr,Zt]=Ce(!1),[xr,wr]=Ce(!1),Sr=Ea([]),[Bt,de]=Ce(0),Z=so(()=>rd(r,i,wt),[r,i,wt]);cn(()=>{ae((ce)=>{let yt=new Set(ce),_t=!1;for(let hn of i)if(hn.service!==""&&!r.includes(hn.service)&&!yt.has(hn.service))yt.add(hn.service),_t=!0;return _t?[...yt].sort():ce})},[i,r]);let L=so(()=>Jo(i,{service:S,services:ie,errorOnly:d,search:f,regex:Je,source:Ne,since:_,until:v,systemLogs:m}),[d,Je,f,S,ie,_,Ne,v,i,m]),ve=so(()=>mf(L,Se,Bt),[L,Se,Bt]),mt=ve.items,er=so(()=>Jo(i,{service:D,services:ie,errorOnly:d,search:f,regex:Je,source:Ne,since:_,until:v,systemLogs:m}),[d,Je,f,D,ie,_,Ne,v,i,m]),at=so(()=>mf(er,N,ue),[er,N,ue]),Ee=at.items,pn=Ea(0);pn.current=L.length;let P=Ea(0);P.current=er.length;let F=Ea(!1);F.current=Se;let tr=Ea(!1);tr.current=N,cn(()=>{Xe(-1),F.current=!1,kt(!1),Ct(Math.max(0,Math.min(Vr,L.length)-1))},[d,f,S,m]),cn(()=>{tr.current=!1,K(!1),k(Math.max(0,Math.min(Vr,er.length)-1))},[d,f,D,m]),cn(()=>{if(n!=="logs")it(!1)},[n]),cn(()=>{if(n!=="logs"||Se)return;Ct(Math.max(0,Math.min(Vr,L.length)-1))},[L.length,Se,n]),cn(()=>{if(n!=="logs"||N)return;k(Math.max(0,Math.min(Vr,er.length)-1))},[er.length,N,n]);let lo=zr(()=>{if(F.current)return;F.current=!0,de(ed(pn.current)),kt(!0)},[]),Nr=zr(()=>{if(tr.current)return;tr.current=!0,We(ed(P.current)),K(!0)},[]),dr=so(()=>({sliceLen:mt.length,windowStart:ve.start,windowNewer:ve.newer,filteredLen:L.length,pinned:Se}),[L.length,Se,mt.length,ve.newer,ve.start]),mn=so(()=>({sliceLen:Ee.length,windowStart:at.start,windowNewer:at.newer,filteredLen:er.length,pinned:N}),[er.length,N,Ee.length,at.newer,at.start]),fn=zr((ce)=>fR(ce,dr,de,kt,Ct),[dr]),uo=zr((ce)=>fR(ce,mn,We,K,k),[mn]),Vd=zr((ce)=>{if(B&&V===1){uo(ce);return}fn(ce)},[fn,uo,V,B]),zd=zr((ce)=>{let yt=mt.length,_t=ff(ce,yt,ve.start,ve.newer);if(_t.startDelta!==0)de(Math.max(0,ve.start+_t.startDelta)),kt(!0);Xe(_t.selected)},[mt.length,ve.newer,ve.start]),he=zr(()=>{if(F.current=!1,tr.current=!1,kt(!1),K(!1),Xe(-1),n==="logs")Ct(Math.max(0,Math.min(Vr,L.length)-1)),k(Math.max(0,Math.min(Vr,er.length)-1));C((yt)=>yt+1);let ce=Math.max(ve.newer,at.newer);s(ce>0?`Jumped to latest (+${ce} new)`:"Jumped to latest logs")},[L.length,er.length,ve.newer,at.newer,n]),An=so(()=>({services:B?mR:S!==""?[S]:ie,level:d?"ERROR":Le,search:f,regex:Je,source:Ne,since:_,until:v}),[d,Le,Je,f,S,ie,_,Ne,v,B]),po=zr(async()=>{if(!e)return;try{ar(await e.logsStats(An))}catch(ce){s(I(ce))}},[e,An]),Ma=zr(async()=>{if(!e||c)return;try{let ce=await e.logsPage(An);a((yt)=>gS(yt,ce.events)),Yr(ce.prevCursor),Zt(ce.hasPrev),await po()}catch(ce){s(I(ce))}},[e,An,c,po]),di=zr(()=>{F.current=!1,tr.current=!1,a([]),E(new Date().toISOString()),kt(!1),K(!1),s("Cleared on-screen log buffer")},[]),Yd=zr(()=>{p((ce)=>!ce)},[]);return cn(()=>{o().then(()=>Ma())},[o,Ma]),cn(()=>{Sr.current=i},[i]),cn(()=>{let ce=hf(Se,ve.start,lr),yt=B&&hf(N,at.start,lr);if(!e||xr||!ce&&!yt)return;wr(!0),e.logsPage({...An,cursor:cr,direction:"backward"}).then((_t)=>{if(_t.sessionChanged){s("Daemon session changed \u2014 older log history is no longer available"),Zt(!1);return}let hn=Sr.current,Na=yS(hn,_t.events);a(Na);let ui=Na.length-hn.length;if(ui>0){if(Se)de((pi)=>pi+ui);if(N)We((pi)=>pi+ui)}Yr(_t.prevCursor),Zt(_t.hasPrev)}).catch((_t)=>s(I(_t))).finally(()=>wr(!1))},[e,An,xr,lr,Se,N,cr,ve.start,at.start,B]),cn(()=>{if(!e||c||n!=="logs")return;let ce=setInterval(()=>{po()},SF);return()=>clearInterval(ce)},[e,c,po,n]),{logs:i,setLogs:a,paused:c,setPaused:l,errorOnly:d,setErrorOnly:u,showSystemLogs:m,logSearch:f,setLogSearch:h,logSearchFocused:g,setLogSearchFocused:x,logFollow:y,logSince:_,setLogSince:E,setLogUntil:b,logService:S,setLogService:w,logServiceB:D,setLogServiceB:O,splitLogs:B,splitFocus:V,setSplitFocus:X,toggleSplitLogs:()=>{if(B){X(0),J(!1);return}if(D===""){let ce=r.find((yt)=>yt!==S);if(ce!==void 0)O(ce)}J(!0)},cycleSplitFocus:()=>X((ce)=>ce===0?1:0),setActiveLogService:(ce)=>{let yt=(_t)=>typeof ce==="function"?ce(_t):ce;if(B&&V===1){O(yt);return}w(yt)},logSliceB:Ee,logWindowB:at,logSelectedB:te,logPinnedB:N,logServices:ie,logShowTimestamps:W,setLogShowTimestamps:le,logShowMeta:re,setLogShowMeta:pt,logLevel:Le,logSource:Ne,logRegex:Je,setLogRegex:St,logWrap:st,setLogWrap:At,logPinned:Se,logSelected:gt,logsFullscreen:br,setLogsFullscreen:it,dashboardLogCursor:It,setDashboardLogCursor:Xe,logFacets:Rt,logSources:Z,filteredLogs:L,filteredLogsB:er,logWindow:ve,logSlice:mt,pinLogView:lo,pinLogViewB:Nr,applyLogCursor:Vd,applyLogCursorA:fn,applyLogCursorB:uo,applyDashboardLogCursor:zd,jumpToLatestLogs:he,clearLogs:di,toggleSystemLogs:Yd}}var mR,SF=2000;var gR=A(()=>{ne();yr();mR=[]});import{useCallback as ns,useState as yR}from"react";function vR({tui:e,controller:t,cfg:r,persistPrefs:n,snap:o,refresh:s,setStatus:i}){let[a,c]=yR(""),[l,d]=yR(()=>e.mcp_port??Go(t?.cfg.repoRoot??process.cwd())),u=ns((y)=>{let C=Yn(y);d(C);let _=r?.repoRoot??process.cwd();if(Zx(_,C)){n({mcp_port:null},`MCP port ${C} (default)`);return}n({mcp_port:C},`MCP port ${C}`)},[r,n]),m=ns(()=>{if(a==="")return l;let y=Qx(a,l);return c(""),u(y),y},[l,a,u]),p=ns(async(y)=>{if(!t||o?.mcp?.running!==!0)return;await t.mcpStop(),await t.mcpStart({port:y}),await s()},[t,s,o?.mcp?.running]),f=ns(async(y)=>{if(!t){i("Supervisor is not running");return}let C=o?.mcp?.disabled_tools??[],_=Xn(y.name,C),E=_?[...C,y.name]:C.filter((v)=>v!==y.name);try{let v=await t.mcpSetTools(E);n({mcp_disabled_tools:v},`${y.label} ${_?"disabled":"enabled"}`),await s()}catch(v){i(I(v))}},[t,n,s,o]),h=ns(async()=>{if(!t){i("Supervisor is not running");return}try{if(o?.mcp?.running)await t.mcpStop(),n({mcp_enabled:!1},"MCP off"),i("MCP stopped");else await t.mcpStart({port:l}),n({mcp_enabled:!0},"MCP on"),i("MCP started");await s()}catch(y){i(I(y))}},[t,l,n,s,o?.mcp?.running]),g=ns((y)=>{fd(y.text).then(()=>{i(`Copied ${y.title} snippet`)}).catch((C)=>{i(I(C))})},[]),x=ns((y)=>{let C=Yf(y);if(C===void 0)return!1;let _=o?.mcp?.address??ei(o?.mcp?.port??l),E=Ra(_,o?.mcp?.token??"")[C];if(!E)return!1;return g(E),!0},[g,l,o?.mcp?.address,o?.mcp?.port,o?.mcp?.token]);return{mcpPortDraft:a,setMcpPortDraft:c,mcpPort:l,persistMcpPort:u,applyMcpPortDraft:m,restartMcpOnPort:p,toggleMcpTool:f,toggleMcp:h,copyMcpSnippet:g,copyFocusedMcpSnippet:x}}var bR=A(()=>{ne();Sd();aa();Af();Ta()});import{RGBA as kF}from"@opentui/core";import{useCallback as Pn,useEffect as CF,useMemo as xR,useRef as Ed,useState as Ad}from"react";function wR({tui:e,controller:t,resolveTuiOverridePath:r,terminalBackground:n,userTuiConfigPath:o,snap:s,setStatus:i,saveTuiPreferences:a,setPaletteIndex:c,setOverlay:l,setConfirmKind:d,setScreen:u,setSelected:m,screen:p}){let[f,h]=Ad(e.theme||t?.cfg.ui.theme||"devctl"),g=Ed(f),[x,y]=Ad(e.mouse),C=Ed(e.mouse),[_,E]=Ad(e.leader_timeout),v=Ed(e.leader_timeout),[b,S]=Ad(()=>ma(e.font_size)),w=Ed(b),D=Gw(r()),O=xR(()=>{let W=Hw(f);if(Ol(f)!=="terminal"||!zm(n))return W;let le=n??W.background;return{...W,background:le,panel:le,element:le,inverse:le}},[n,f]),B=Ol(f)==="terminal"&&zm(n)?kF.defaultBackground(n??void 0):O.background,J=xR(()=>zw({themeName:f,fontSize:b,mouse:x,leaderMs:_,locked:D,configPath:D?e.path||Zm(r(),o()):Zm(r(),o()),mcpRunning:s?.mcp?.running===!0}),[b,_,x,D,s?.mcp?.running,f,e.path]),V=Pn((W,le)=>{if(D){i(`${le} session only`);return}let re=a(W);i(`${le} saved ${re}`)},[D]),X=Pn((W)=>{h(W),g.current=W,V({theme:W},`theme ${W}`)},[V]),te=Pn(()=>{let W=!x;y(W),C.current=W,V({mouse:W},`mouse ${W?"on":"off"} restart TUI to apply clicks`)},[x,V]),k=Pn((W)=>{E(W),v.current=W,V({leader_timeout:W},`leader ${W}ms`)},[V]),N=Pn((W)=>{let le=ma(W);S(le),w.current=le,V({font_size:le},`display ${Fl(le)}`)},[V]),K=Pn(()=>{let W=Hl();h(W.theme),g.current=W.theme,y(W.mouse),C.current=W.mouse,E(W.leader_timeout),v.current=W.leader_timeout,S(W.font_size),w.current=W.font_size,V(W,"restored default preferences")},[V]),ue=Pn((W)=>{if(W.id==="theme"){c(Math.max(0,Pt.indexOf(f))),l("themes");return}if(W.id==="mouse"){te();return}if(W.id==="leader"){k(_);return}if(W.id==="font"){N(b);return}if(W.id==="reset"){d("reset-prefs"),l("confirm");return}if(W.id==="mcp"){u("mcp"),m(0);return}i(W.detail)},[N,k,b,_,f,te]),We=Pn((W,le)=>{let re=Qn(J,le);if(!re)return;if(re.id==="theme"){X(qw(f,W));return}if(re.id==="leader"){k(Kw(_,W));return}if(re.id==="font"){N(fa(b,W));return}if(re.id==="mouse")te()},[N,k,b,_,X,J,f,te]),ie=Pn(()=>{h(g.current)},[]);return CF(()=>{if(p==="settings")return;h(g.current),S(w.current),E(v.current),y(C.current)},[p]),{themeName:f,setThemeName:h,leaderMs:_,fontSize:b,prefsLocked:D,palette:O,rootBackground:B,settingRows:J,persistPrefs:V,persistTheme:X,toggleMouse:te,applyFont:N,applyReset:K,activateSetting:ue,cycleSetting:We,revertThemePreview:ie}}var SR=A(()=>{Lr();et()});import{jsxDEV as Aa}from"@opentui/react/jsx-dev-runtime";function kR(e){let{palette:t,termW:r,termH:n,step:o,answers:s,repo:i,draft:a,authStatus:c,onDraft:l,onSubmit:d}=e,u=Tt[o]??Tt[0],m=Tt.length,p=RF(u.id,s,i);return Aa(rt,{palette:t,title:`setup ${o+1}/${m} ${u.title}`,bottomTitle:"enter next \xB7 esc cancel",termW:r,termH:n,preferW:64,preferH:12,borderColor:t.primary,gap:1,children:[Aa("text",{fg:t.muted,wrapMode:"word",children:p},void 0,!1,void 0,this),u.id==="auth"||u.id==="write"||u.id==="repo"?Aa("text",{fg:t.text,wrapMode:"word",children:u.id==="auth"?c:u.id==="repo"?i:"Write .devctl/config.yaml and attach the daemon."},void 0,!1,void 0,this):Aa("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),Aa(ft,{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 ch(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 CR(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=Lc(r,t.proxyPort);else if(e==="profile")o.profile=r.trim();return o}function RF(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 Tt.find((n)=>n.id===e)?.prompt??""}var lh=A(()=>{Dc();Te()});import{useCallback as dh,useState as Id}from"react";function RR({workspace:e,renderer:t,setOverlay:r,setStatus:n,setScreen:o,onAttached:s,setController:i,setCfg:a,setBootError:c}){let l=process.cwd(),[d,u]=Id(0),[m,p]=Id(()=>Wn(l)),[f,h]=Id(""),[g,x]=Id("Checking Application Default Credentials\u2026"),y=dh(()=>{let E=Wn(l);u(0),p(E),h(ch("repo",E,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]),C=dh(async(E)=>{try{Mc(l,E);let v=await e.openTui(l,"");s?.(v),i(v),a(v.cfg),c(void 0),r("none"),o("dashboard"),n("Wrote .devctl/config.yaml and attached the daemon")}catch(v){n(I(v))}},[s,l,c,a,i,r,o,n,e]),_=dh(async()=>{let E=Tt[d];if(!E)return;let v=CR(E.id,m,f,l);if(E.id==="auth"){if(!(await e.detectGoogle(v.project)).adcAvailable)try{await Td(t,()=>e.loginGoogle())}catch(D){n(I(D))}}if(p(v),E.id==="write"){await C(v);return}let b=Math.min(d+1,Tt.length-1),S=Tt[b];u(b),h(S?ch(S.id,v,l):"")},[m,f,C,t,l,d,e,n]);return{step:d,answers:m,draft:f,authStatus:g,repo:l,setDraft:h,startWizard:y,advanceWizard:_}}var _R=A(()=>{Dc();Nc();ne();lh()});import{useCallback as _F,useEffect as TR,useState as uh}from"react";function PR({controller:e,cfg:t,envService:r}){let[n,o]=uh(),[s,i]=uh(!1),[a,c]=uh(""),l=n?.name===r,d=l?n?.env:void 0,u=r===""?"config":s&&!l?"loading":l?"resolved":a!==""?"error":"config",m=l||r===""?"":a;TR(()=>{o(void 0),c("")},[t]),TR(()=>{if(!e||r===""||!t?.services[r])return;if(n?.name===r)return;let f=!1;return i(!0),c(""),e.execService(r,[],!0).then((h)=>{if(f)return;o({name:r,env:h.environment??{}}),c("")}).catch((h)=>{if(f)return;c(I(h))}).finally(()=>{i(!1)}),()=>{f=!0}},[t,e,r,n?.name]);let p=_F(async(f)=>{if(!e)return;try{let h=await e.execService(f,[],!0);o({name:f,env:h.environment??{}}),c("")}catch(h){throw c(I(h)),h}},[e]);return{inspectorEnv:d,inspectorEnvStatus:u,inspectorEnvError:m,resolveEnvironment:p}}var ER=A(()=>{ne()});import{jsxDEV as io}from"@opentui/react/jsx-dev-runtime";function AR(e){let{palette:t,path:r,initialValue:n,error:o,termW:s,termH:i,textareaRef:a}=e;return io(rt,{palette:t,title:"config buffer",bottomTitle:`${Ht("s")} save \xB7 esc discard \xB7 e still opens $EDITOR`,termW:s,termH:i,preferW:88,preferH:28,gap:1,children:[io("box",{height:1,overflow:"hidden",children:io("text",{fg:t.muted,wrapMode:"none",children:r},void 0,!1,void 0,this)},void 0,!1,void 0,this),io("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!==""?io("box",{height:2,overflow:"hidden",children:io("text",{fg:t.error,wrapMode:"word",children:o},void 0,!1,void 0,this)},void 0,!1,void 0,this):io("box",{height:1,overflow:"hidden",children:io("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 IR=A(()=>{Te();qr()});import{jsxDEV as ph}from"@opentui/react/jsx-dev-runtime";function LR(e){let{palette:t,title:r,body:n,kind:o,termW:s,termH:i}=e,a=OS(o),c=a.map((l)=>`${l.key} ${l.label}`).join(" \xB7 ");return ph(rt,{palette:t,title:r,bottomTitle:c,termW:s,termH:i,preferW:52,preferH:8,borderColor:t.warning,gap:1,children:[ph("text",{fg:t.text,wrapMode:"word",children:n},void 0,!1,void 0,this),ph(ft,{palette:t,hints:a},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var DR=A(()=>{vr();Te()});import{jsxDEV as OR}from"@opentui/react/jsx-dev-runtime";function MR(e){let{palette:t,termW:r,termH:n}=e,o=kf();return OR(rt,{palette:t,title:"leader",termW:r,termH:n,preferW:68,preferH:5,anchor:"bottom",children:OR(ft,{palette:t,hints:o},void 0,!1,void 0,this)},void 0,!1,void 0,this)}var NR=A(()=>{Zo();Te()});import{jsxDEV as ot,Fragment as mh}from"@opentui/react/jsx-dev-runtime";function $R(e){let{palette:t,event:r,termW:n,termH:o,scrollRef:s,onViewTrace:i}=e;if(!r)return null;let a=r.traceId,c=typeof r.body==="object"&&r.body!==null?lS(r):void 0,l=r.raw&&r.raw!==Gt(r)?cS(r.raw):void 0,d=Object.entries(r.attributes),u=Gt(r).length>120||Gt(r).includes(`
|
|
2353
|
+
`)||c!==void 0||d.length>0,m=r.resource["process.pid"];return ot(rt,{palette:t,title:"log details",bottomTitle:a?"enter view trace \xB7 j/k scroll \xB7 esc close":"j/k scroll \xB7 esc close",termW:n,termH:o,preferW:u?84:72,preferH:u?24:16,gap:1,children:ot("scrollbox",{ref:s,focused:!1,stickyScroll:!1,scrollX:!1,style:Oe(t),children:ot("box",{flexDirection:"column",overflow:"hidden",children:[ot("text",{fg:t.text,wrapMode:"word",children:Ql(Gt(r))},void 0,!1,void 0,this),ot("text",{fg:t.muted,children:`time ${r.timestamp}`},void 0,!1,void 0,this),ot("text",{fg:Ve(r.service,t),children:`service ${r.service}`},void 0,!1,void 0,this),ot("text",{fg:t.muted,children:`source ${r.source}${r.stream?` / ${r.stream}`:""}`},void 0,!1,void 0,this),ot("text",{fg:t.muted,children:`level ${Gs(r.severityText)} (${r.severityNumber})`},void 0,!1,void 0,this),ot("text",{fg:t.muted,children:`pid ${typeof m==="number"&&m>0?m:"\u2014"}`},void 0,!1,void 0,this),ot("text",{fg:t.muted,children:`trace ${a||"\u2014"}`},void 0,!1,void 0,this),ot("text",{fg:t.muted,children:`span ${r.spanId||"\u2014"}`},void 0,!1,void 0,this),ot("text",{fg:t.muted,children:`identity ${r.identity||"\u2014"}`},void 0,!1,void 0,this),r.scope?ot("text",{fg:t.muted,children:`scope ${r.scope.name}${r.scope.version?` ${r.scope.version}`:""}`},void 0,!1,void 0,this):null,d.length===0?null:ot(mh,{children:[ot("text",{fg:t.muted,children:"attributes"},void 0,!1,void 0,this),d.map(([p,f])=>ot("text",{fg:t.text,wrapMode:"word",children:`${p} ${fr(f)}`},p,!1,void 0,this))]},void 0,!0,void 0,this),c===void 0?null:ot(mh,{children:[ot("text",{fg:t.muted,children:"body"},void 0,!1,void 0,this),ot("text",{fg:t.text,wrapMode:"word",children:c},void 0,!1,void 0,this)]},void 0,!0,void 0,this),l===void 0||l===c?null:ot(mh,{children:[ot("text",{fg:t.muted,children:"raw"},void 0,!1,void 0,this),ot("text",{fg:t.text,wrapMode:"word",children:l},void 0,!1,void 0,this)]},void 0,!0,void 0,this),a&&i?ot("text",{fg:t.primary,onMouseDown:()=>i(a),children:"view trace \u21B5"},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this)}var FR=A(()=>{en();yr();Te();et()});import{jsxDEV as q}from"@opentui/react/jsx-dev-runtime";function fh(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 TF(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 HR(e){return q("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:q("text",{wrapMode:"none",children:[q("span",{fg:e.palette.primary,children:e.label.toUpperCase()},void 0,!1,void 0,this),e.detail?q("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 UR(e){let{palette:t,plan:r,snap:n,busy:o,failed:s,kind:i,termW:a=80,termH:c=24,scrollRef:l,initiallyRunning:d=[],onDismiss:u}=e,m=new Set(d),p=r.waves.map((X,te)=>({wave:X.filter((k)=>!m.has(k)),waveIdx:te})).filter(({wave:X})=>X.length>0),f=TC(r,n,i),h=r.waves[f.currentWaveIndex]??[],g=Pa(h,n,i),x=fh(g,t),y=TF(o,s,g,t),C=EC(i,o,s,r.profile),_=AC(o,s),E=s?t.inverse:t.text,v=s?t.error:t.element,b=Math.min(92,Math.max(40,a-8)),S=b>=82?22:b>=66?17:13,w=12,D=9,O=b>=56,B=b>=72,J=b<72||r.waves.length>3,V=r.profile?`PROFILE ${r.profile}`:i.toUpperCase();return q(rt,{palette:t,title:C,bottomTitle:"enter close \xB7 esc hide (operation continues)",termW:a,termH:c,preferW:98,preferH:28,borderColor:y.color,gap:0,children:[q("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:t.element,children:[q(Fe,{palette:t,label:`${y.icon} ${y.label.toUpperCase()}`,tone:y.tone},void 0,!1,void 0,this),q("box",{paddingLeft:1,flexGrow:1,overflow:"hidden",children:q("text",{fg:t.text,wrapMode:"none",children:V},void 0,!1,void 0,this)},void 0,!1,void 0,this),q(Fe,{palette:t,label:`${f.ready}/${f.total} READY`,tone:f.isComplete?"success":"info"},void 0,!1,void 0,this),q(Fe,{palette:t,label:`WAVE ${Math.min(f.currentWaveIndex+1,Math.max(1,f.totalWaves))}/${Math.max(1,f.totalWaves)}`,tone:x.tone},void 0,!1,void 0,this)]},void 0,!0,void 0,this),q("box",{height:2,flexDirection:"column",overflow:"hidden",flexShrink:0,paddingLeft:1,paddingRight:1,children:[q("box",{height:1,flexDirection:"row",overflow:"hidden",children:[q("text",{wrapMode:"none",children:[q("span",{fg:t.muted,children:"PROGRESS "},void 0,!1,void 0,this),q("span",{fg:y.color,children:`[${f.progressBar}]`},void 0,!1,void 0,this),q("span",{fg:t.text,children:` ${f.percent}%`},void 0,!1,void 0,this)]},void 0,!0,void 0,this),q("box",{flexGrow:1},void 0,!1,void 0,this),q("text",{wrapMode:"none",children:q("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),q("box",{height:1,overflow:"hidden",children:q("text",{fg:t.muted,wrapMode:"none",children:o?i==="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),q(HR,{palette:t,label:"Pipeline",detail:`${r.waves.length} execution wave${r.waves.length===1?"":"s"}`},void 0,!1,void 0,this),q("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:t.element,children:r.waves.map((X,te)=>{let k=Pa(X,n,i),N=fh(k,t),K=J?`W${te+1} ${N.icon} ${X.length}`:`W${te+1} ${N.icon} ${N.label} \xB7 ${X.length}`;return q("box",{flexDirection:"row",overflow:"hidden",flexShrink:0,children:[q(Fe,{palette:t,label:K,tone:N.tone},void 0,!1,void 0,this),te<r.waves.length-1?q("box",{width:3,flexShrink:0,overflow:"hidden",children:q("text",{fg:t.muted,children:" \u2500\u25BA"},void 0,!1,void 0,this)},void 0,!1,void 0,this):null]},`step-${te}`,!0,void 0,this)})},void 0,!1,void 0,this),(r.blockers??[]).length>0?q("box",{minHeight:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:t.element,paddingLeft:1,children:q("text",{fg:t.error,wrapMode:"word",children:`BLOCKED ${r.blockers?.map((X)=>`${X.name}: ${X.message}`).join(" \xB7 ")}`},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,d.length>0?q("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,paddingLeft:1,children:q("text",{wrapMode:"none",children:[q("span",{fg:t.success,children:"\u2713 ALREADY READY"},void 0,!1,void 0,this),q("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,q(HR,{palette:t,label:"Wave details",detail:"live process and health state"},void 0,!1,void 0,this),q("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:t.element,paddingLeft:1,children:[q("box",{width:2,flexShrink:0},void 0,!1,void 0,this),q("box",{width:S,flexShrink:0,overflow:"hidden",children:q("text",{fg:t.muted,children:U("SERVICE",S)},void 0,!1,void 0,this)},void 0,!1,void 0,this),q("box",{width:12,flexShrink:0,overflow:"hidden",children:q("text",{fg:t.muted,children:U("STATE",12)},void 0,!1,void 0,this)},void 0,!1,void 0,this),O?q("box",{width:9,flexShrink:0,overflow:"hidden",children:q("text",{fg:t.muted,children:U("PORT",9)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,B?q("box",{flexGrow:1,overflow:"hidden",children:q("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),q("scrollbox",{ref:l,focused:!1,stickyScroll:!1,scrollX:!1,style:Oe(t),children:q("box",{flexDirection:"column",overflow:"hidden",gap:1,children:p.map(({wave:X,waveIdx:te})=>{let k=Pa(X,n,i),N=fh(k,t),K=`${PC(i,te,k)} \xB7 ${X.length} service${X.length===1?"":"s"}`;return q("box",{border:!0,borderStyle:"rounded",borderColor:N.color,title:K,titleColor:N.color,flexDirection:"column",paddingLeft:1,paddingRight:1,flexShrink:0,overflow:"hidden",children:X.map((ue)=>{let We=n?.services[ue],ie=gr(We),W=_C(ue,r,n,i),le=ue===s||ie==="FAILED",re=ie==="UNHEALTHY",pt=ie==="HEALTHY"||i==="stop"&&ie==="STOPPED",wt=zo(We),ae=le?"\u2717":pt?"\u2713":re?"\u26A0":ie==="STARTING"||ie==="RUNNING"||ie==="STOPPING"||ie==="RESTARTING"?"\u25CF":"\u25CB",Le=le?t.error:ut(t,ie),Ne=le?t.error:re?t.warning:pt?t.success:t.muted;return q("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:[q("box",{width:2,flexShrink:0,overflow:"hidden",children:q("text",{fg:Le,children:ae},void 0,!1,void 0,this)},void 0,!1,void 0,this),q("box",{width:S,flexShrink:0,overflow:"hidden",children:q("text",{fg:Ve(ue,t),wrapMode:"none",children:U(ue,S)},void 0,!1,void 0,this)},void 0,!1,void 0,this),q("box",{width:12,flexShrink:0,overflow:"hidden",children:q("text",{fg:ut(t,ie),wrapMode:"none",children:U(ie,12)},void 0,!1,void 0,this)},void 0,!1,void 0,this),O?q("box",{width:9,flexShrink:0,overflow:"hidden",children:q("text",{fg:wt?t.info:t.muted,wrapMode:"none",children:U(wt?`:${wt}`:"\u2014",9)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,B?q("box",{flexGrow:1,overflow:"hidden",children:q("text",{fg:Ne,wrapMode:"none",children:W},void 0,!1,void 0,this)},void 0,!1,void 0,this):null]},ue,!0,void 0,this)})},`wave-card-${te}`,!1,void 0,this)})},void 0,!1,void 0,this)},void 0,!1,void 0,this),q("box",{height:3,flexShrink:0,flexDirection:"column",backgroundColor:v,border:["top"],borderStyle:"single",borderColor:y.color,paddingLeft:1,paddingRight:1,overflow:"hidden",onMouseDown:u,children:[q("text",{fg:E,wrapMode:"none",children:_.primary},void 0,!1,void 0,this),q("text",{fg:s?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 jR=A(()=>{tt();Cd();Ft();Te();et()});import{jsxDEV as Or}from"@opentui/react/jsx-dev-runtime";function BR(e){let{palette:t,route:r,termW:n,termH:o,scrollRef:s}=e;if(!r)return null;return Or(rt,{palette:t,title:r.name,bottomTitle:"j/k scroll \xB7 esc close",termW:n,termH:o,preferW:78,preferH:18,gap:1,children:Or("scrollbox",{ref:s,focused:!1,stickyScroll:!1,scrollX:!1,style:Oe(t),children:Or("box",{flexDirection:"column",overflow:"hidden",children:[Or("text",{fg:t.muted,children:`auth ${r.authType||"none"}`},void 0,!1,void 0,this),Or("text",{fg:t.muted,children:`identity ${r.identityType||"\u2014"}`},void 0,!1,void 0,this),Or("text",{fg:t.text,wrapMode:"word",children:`account ${r.serviceAccount||"\u2014"}`},void 0,!1,void 0,this),Or("text",{fg:t.text,wrapMode:"word",children:`audience ${r.audience||"\u2014"}`},void 0,!1,void 0,this),Or("text",{fg:t.muted,wrapMode:"word",children:`client id ${r.clientId||"ADC default"}`},void 0,!1,void 0,this),Or("text",{fg:t.muted,wrapMode:"word",children:`client secret ${r.clientSecret||"\u2014"}`},void 0,!1,void 0,this),Or("text",{fg:t.muted,children:`match host ${r.matchHost||"*"}`},void 0,!1,void 0,this),Or("text",{fg:t.muted,children:`match path ${r.matchPath||"*"}`},void 0,!1,void 0,this),Or("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 WR=A(()=>{Te()});import{jsxDEV as Ld}from"@opentui/react/jsx-dev-runtime";function GR(e){let{palette:t,title:r,body:n,termW:o,termH:s,scrollRef:i}=e,a=n.length>400||n.split(`
|
|
2354
|
+
`).length>16;return Ld(rt,{palette:t,title:r,bottomTitle:"j/k scroll \xB7 esc close",termW:o,termH:s,preferW:a?88:76,preferH:a?24:16,gap:1,children:Ld("scrollbox",{ref:i,focused:!1,stickyScroll:!1,scrollX:!1,style:Oe(t),children:Ld("box",{flexDirection:"column",overflow:"hidden",children:Ld("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 qR=A(()=>{Te()});import{jsxDEV as Jt}from"@opentui/react/jsx-dev-runtime";function KR(e){let{palette:t,items:r,query:n,selected:o,onQuery:s,onSubmit:i,title:a="commands"}=e,c=Qo(n)!=="",l=rk(o,hh,r.length),d=c?r.slice(l,l+hh):nk(r,o,hh),u=c?[{group:"",items:d}]:xf(d),m=Math.max(d.length+(c?0:u.length),1),p=Ys(r,o),f=XS(r);return Jt("box",{height:m+PF,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?Jt("box",{height:1,paddingLeft:1,overflow:"hidden",children:Jt("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===""?[]:[Jt("box",{height:1,paddingLeft:1,overflow:"hidden",backgroundColor:t.panel,children:Jt("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 y=id(x)===(p?id(p):"");return Jt("box",{height:1,flexDirection:"row",overflow:"hidden",paddingLeft:1,paddingRight:1,backgroundColor:y?t.highlight:t.panel,children:[Jt("box",{width:f,flexShrink:0,overflow:"hidden",children:Jt("text",{fg:y?t.primary:t.text,wrapMode:"none",children:U(`${y?"\u203A":" "} /${wf(x).slice(0,bf)}`,f)},void 0,!1,void 0,this)},void 0,!1,void 0,this),Jt("box",{flexGrow:1,overflow:"hidden",children:Jt("text",{fg:y?t.accent:t.muted,wrapMode:"none",children:JS(x)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},id(x),!0,void 0,this)})]),Jt("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:t.highlight,paddingLeft:1,children:[Jt("box",{width:2,flexShrink:0,children:Jt("text",{fg:t.primary,children:"/"},void 0,!1,void 0,this)},void 0,!1,void 0,this),Jt("box",{flexGrow:1,overflow:"hidden",children:Jt("input",{focused:!0,value:n,placeholder:"start logs settings",onInput:s,onSubmit:()=>i(),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 hh=10,PF=3;var VR=A(()=>{Zo();tt()});import{jsxDEV as JR}from"@opentui/react/jsx-dev-runtime";function XR(e){let{palette:t,themeName:r,selected:n,termW:o,termH:s,onIndex:i,onPreview:a,onPick:c}=e,l=Pt.map((u)=>({name:u===r?`${u} (active)`:u,description:u===r?`${Vm[u]} \xB7 current`:Vm[u],value:u})),d=zs(o,s,zR,YR,"center",!_n(ge()));return JR(rt,{palette:t,title:"/themes",bottomTitle:"enter save \xB7 esc revert",termW:o,termH:s,preferW:zR,preferH:YR,children:JR("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)=>{i(u);let m=Pt[u];if(m)a(m)},onSelect:(u,m)=>{let p=m?.value;if(typeof p==="string"&&p!=="")c(p)}},void 0,!1,void 0,this)},void 0,!1,void 0,this)}var zR=52,YR=22;var QR=A(()=>{$t();vr();Te();Lr();et()});import{useEffect as EF,useRef as AF}from"react";import{jsxDEV as j}from"@opentui/react/jsx-dev-runtime";function ZR(e){let{palette:t,trace:r,termW:n,termH:o,onSelect:s,onOpenLogs:i,scrollRef:a}=e,c=_n(ge()),l=Zk(n,o),d=zs(n,o,l.w,l.h,"center",!c),u=c?0:1,m=Math.max(Of-8,d.width-2-u*2),p=Math.max(Mf-4,d.height-2-u*2),f=r?gd(r):[],h=r?$k(r):void 0,g=eC(m,p),x=Uk(g.listWidth,Fk(f),Hk(f)),y=yd(e.selected??0,f.length),C=AF({index:-1,at:0});if(EF(()=>{let E=a&&"current"in a?a.current:null;if(!E||f.length===0)return;let v=requestAnimationFrame(()=>{E.scrollChildIntoView(`trace-span-${y}`)});return()=>cancelAnimationFrame(v)},[f.length,a,y]),!r||!h)return null;let _=(E)=>{let v=Date.now(),b=Vk(C.current.index,C.current.at,E,v);if(C.current={index:E,at:v},s?.(E),b)i?.(E)};return j(rt,{palette:t,title:d.width>=48?`trace ${r.traceId}`:`trace ${KF(r.traceId,12)}`,bottomTitle:"j/k span \xB7 enter or double-click logs \xB7 esc close",termW:n,termH:o,preferW:l.w,preferH:l.h,gap:0,children:[j(IF,{palette:t,facts:h,width:m,showServices:g.showServices,showLegend:g.showLegend},void 0,!1,void 0,this),j("box",{flexGrow:1,height:g.listHeight,flexDirection:"column",overflow:"hidden",children:[j(DF,{palette:t,cols:x,durationMs:h.durationMs},void 0,!1,void 0,this),j("scrollbox",{ref:a,focused:!1,stickyScroll:!1,scrollX:!1,style:Oe(t),children:j("box",{flexDirection:"column",overflow:"hidden",children:f.length===0?j("text",{fg:t.muted,children:"no spans in this trace yet"},void 0,!1,void 0,this):f.map((E,v)=>j(OF,{id:`trace-span-${v}`,palette:t,row:E,cols:x,facts:h,active:v===y,onPick:()=>_(v)},`${E.span.spanId}-${E.span.seq}`,!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 e_(e){let{palette:t,span:r,records:n=[],termW:o,termH:s,scrollRef:i}=e;if(!r)return null;return j(rt,{palette:t,title:VF(`${Ff(r.kind)} ${r.name}`),bottomTitle:"j/k scroll \xB7 esc back to trace",termW:o,termH:s,preferW:84,preferH:28,gap:0,children:j(MF,{palette:t,span:r,records:n,scrollRef:i},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function IF(e){let{palette:t,facts:r,width:n,showServices:o,showLegend:s}=e,i=Jk(Yk(r),n),a=Xk(r.services,n);return j("box",{flexShrink:0,flexDirection:"column",overflow:"hidden",children:[j("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:t.element,children:i.map((c)=>j(LF,{palette:t,chip:c},c.id,!1,void 0,this))},void 0,!1,void 0,this),o&&a!==""?j("text",{fg:t.muted,wrapMode:"none",children:a},void 0,!1,void 0,this):null,s?j("text",{fg:t.muted,wrapMode:"none",children:Qk(n)},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}function LF(e){let{palette:t,chip:r}=e;return j(Fe,{palette:t,label:r.label,tone:r.tone},void 0,!1,void 0,this)}function DF(e){let{palette:t,cols:r,durationMs:n}=e,o=Kk(r),s=jk(r);return j("box",{flexShrink:0,flexDirection:"column",overflow:"hidden",children:[j("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:[j(Xt,{width:r.tree,fg:t.muted,text:o.tree,raw:!0},void 0,!1,void 0,this),j(Xt,{width:r.service,fg:t.muted,text:o.service,raw:!0},void 0,!1,void 0,this),j(Xt,{width:r.name,fg:t.muted,text:o.name,raw:!0},void 0,!1,void 0,this),j(Xt,{width:r.duration,fg:t.muted,text:o.duration,raw:!0},void 0,!1,void 0,this),j(Xt,{width:r.bar,fg:t.muted,text:o.bar,raw:!0},void 0,!1,void 0,this)]},void 0,!0,void 0,this),j("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:[j(Xt,{width:s,fg:t.muted,text:""},void 0,!1,void 0,this),j(Xt,{width:r.bar,fg:t.muted,text:qk(n,r.bar),raw:!0},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}function OF(e){let{id:t,palette:r,row:n,cols:o,facts:s,active:i,onPick:a}=e,c=typeof n.span.resource["service.name"]==="string"?n.span.resource["service.name"]:"\u2014",l=$f(n.span),d=Math.max(1,s.endUnixNano-s.startUnixNano),u=Wk(n.span,s.startUnixNano,d),m=Gk(u.offset,u.width,o.bar),p=n.span.status.code==="error",f=p?r.error:qF(r,n.span.kind);return j("box",{id:t,height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:i?r.highlight:void 0,onMouseDown:a,children:[j(Xt,{width:o.tree,fg:f,text:Bk(n.depth,n.span.kind,o.tree,i),raw:!0},void 0,!1,void 0,this),j(Xt,{width:o.service,fg:Ve(c,r),text:c},void 0,!1,void 0,this),j(Xt,{width:o.name,fg:p?r.error:r.text,text:n.span.name},void 0,!1,void 0,this),j(Xt,{width:o.duration,fg:r.muted,text:ts(l)},void 0,!1,void 0,this),j(WF,{width:o.bar,trackFg:r.muted,fillFg:f,lead:m.lead,fill:m.fill,trail:m.trail},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function MF(e){let{palette:t,span:r,records:n,scrollRef:o}=e,s=typeof r.resource["service.name"]==="string"?r.resource["service.name"]:"\u2014",i=Object.entries(r.attributes),a=r.status.code==="error";return j("box",{flexGrow:1,flexDirection:"column",overflow:"hidden",children:j("scrollbox",{ref:o,focused:!1,stickyScroll:!1,scrollX:!1,style:Oe(t),children:j("box",{flexDirection:"column",overflow:"hidden",children:[j(ao,{palette:t,label:"status",value:GF(r),tone:a?"error":"text"},void 0,!1,void 0,this),j(ao,{palette:t,label:"kind",value:Ff(r.kind)},void 0,!1,void 0,this),j(ao,{palette:t,label:"service",value:s},void 0,!1,void 0,this),j(ao,{palette:t,label:"duration",value:ts($f(r))},void 0,!1,void 0,this),j(ao,{palette:t,label:"trace",value:r.traceId},void 0,!1,void 0,this),j(ao,{palette:t,label:"span",value:r.spanId},void 0,!1,void 0,this),j(ao,{palette:t,label:"parent",value:r.parentSpanId||"\u2014"},void 0,!1,void 0,this),r.scope?j(ao,{palette:t,label:"scope",value:`${r.scope.name}${r.scope.version?` ${r.scope.version}`:""}`},void 0,!1,void 0,this):null,i.length===0?null:j("text",{fg:t.muted,children:"attributes"},void 0,!1,void 0,this),i.map(([c,l])=>j("text",{fg:t.text,wrapMode:"word",children:`${c} ${fr(l)}`},c,!1,void 0,this)),r.events.length===0?null:j("text",{fg:t.muted,children:`events ${r.events.length}`},void 0,!1,void 0,this),r.events.map((c,l)=>j("box",{flexDirection:"column",overflow:"hidden",children:[j("text",{fg:t.info,wrapMode:"word",children:`+${ts(zk(r,c.timeUnixNano))} ${c.name}`},void 0,!1,void 0,this),Object.entries(c.attributes).map(([d,u])=>j("text",{fg:t.muted,wrapMode:"word",children:` ${d} ${fr(u)}`},d,!1,void 0,this))]},`${c.name}-${l}`,!0,void 0,this)),j(jF,{palette:t,records:n,spanId:r.spanId},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 jF(e){let{palette:t,records:r,spanId:n}=e;if(r.length===0)return j("text",{fg:t.muted,children:"logs none"},void 0,!1,void 0,this);let o=BF(r);return j("box",{flexDirection:"column",overflow:"hidden",children:[j("text",{fg:t.muted,children:`logs ${r.length} \xB7 \u2192 this span`},void 0,!1,void 0,this),r.map((s)=>{let i=n!==void 0&&s.spanId===n,a=Gt(s);return j("box",{flexShrink:0,flexDirection:"row",alignItems:"flex-start",overflow:"hidden",children:[j(Xt,{width:NF,fg:i?t.primary:t.muted,text:i?"\u2192":""},void 0,!1,void 0,this),j(Xt,{width:$F,fg:t.muted,text:s.timestamp.slice(11,19)},void 0,!1,void 0,this),j(Xt,{width:o,fg:i?t.primary:t.text,text:s.service},void 0,!1,void 0,this),j(Xt,{width:FF,fg:i?t.primary:t.muted,text:Gs(s.severityText)},void 0,!1,void 0,this),j("box",{flexGrow:1,flexShrink:1,minWidth:0,overflow:"hidden",children:j("text",{fg:i?t.primary:t.text,wrapMode:"word",flexShrink:0,width:"100%",children:a},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},s.seq,!0,void 0,this)})]},void 0,!0,void 0,this)}function BF(e){return Math.min(UF,Math.max(HF,e.reduce((t,r)=>Math.max(t,r.service.length),0)))}function ao(e){let{palette:t,label:r,value:n,tone:o="text"}=e;return j("text",{fg:o==="error"?t.error:t.text,wrapMode:"word",children:`${U(r,10)}${n}`},void 0,!1,void 0,this)}function WF(e){let{width:t,trackFg:r,fillFg:n,lead:o,fill:s,trail:i}=e;return j("box",{width:t,flexShrink:0,overflow:"hidden",children:j("text",{wrapMode:"none",children:[j("span",{fg:r,children:o},void 0,!1,void 0,this),j("span",{fg:n,children:s},void 0,!1,void 0,this),j("span",{fg:r,children:i},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}function Xt(e){let{width:t,fg:r,text:n,raw:o}=e;return j("box",{width:t,flexShrink:0,overflow:"hidden",children:j("text",{fg:r,wrapMode:"none",children:o?n:U(n,t)},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function GF(e){let t=Nk(e.status.code);return e.status.message?`${t} ${e.status.message}`:t}function qF(e,t){if(t==="server")return e.info;if(t==="client")return e.primary;if(t==="producer"||t==="consumer")return e.accent;return e.muted}function KF(e,t){if(e.length<=t)return e;return`${e.slice(0,t)}\u2026`}function VF(e){return e.length<=42?e:`${e.slice(0,41)}\u2026`}var NF=2,$F=9,FF=8,HF=8,UF=14;var t_=A(()=>{en();vr();tt();yr();vd();Te();Lr();et();$t()});function Dd(e,t,r){let n=e?.google?.project_id??"";if(n!=="")return{project:n,source:"configuration"};let o=t?.project||r?.projectID||"",s=t?.project_source||r?.projectSource||"";return{project:o,source:s}}import{jsxDEV as ht}from"@opentui/react/jsx-dev-runtime";function Od(e){let{palette:t,title:r,tone:n="muted",children:o}=e,s=n==="success"?t.success:n==="warning"?t.warning:n==="error"?t.error:t.border;return ht("box",{border:!0,borderStyle:"rounded",borderColor:s,title:r,titleColor:s,flexDirection:"column",flexShrink:0,paddingLeft:1,paddingRight:1,marginBottom:1,overflow:"hidden",children:o},void 0,!1,void 0,this)}function r_(e){let{palette:t,cfg:r,google:n,identity:o}=e;if(!n&&!o)return ht(md,{palette:t,label:"Checking Application Default Credentials\u2026"},void 0,!1,void 0,this);let s=zF(r,o),i=o?.user||n?.userEmail||"",{project:a,source:c}=Dd(r,o,n),l=o?.adc??n?.adcAvailable===!0,d=o?.iap??(r?.proxy.routes??[]).some((h)=>h.auth.type.toLowerCase()==="iap"),u=s.filter((h)=>h.inactive!==!0),m=u.some((h)=>h.ok===!0),p=u.some((h)=>h.ok===!1),f=m?"AVAILABLE":p?"UNAVAILABLE":u.length>0?"NOT PROBED":s.length>0?"not required":"not configured";return ht(Et,{palette:t,title:"identity",scroll:!0,children:[ht(Od,{palette:t,title:"Google identity",tone:l?"success":"warning",children:[ht(es,{palette:t,label:"user",value:i||"(unknown)"},void 0,!1,void 0,this),ht(es,{palette:t,label:"project",value:a||"(unset)"},void 0,!1,void 0,this),ht(es,{palette:t,label:"source",value:c||"\u2014"},void 0,!1,void 0,this),ht(es,{palette:t,label:"ADC",value:l?"\u2713 available":"\u2717 missing",tone:l?"success":"error"},void 0,!1,void 0,this),ht(es,{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),ht(Od,{palette:t,title:`Service accounts (${s.length})`,tone:u.length===0?"muted":m?"success":"warning",children:[s.length===0?ht("text",{fg:t.muted,wrapMode:"word",children:"none configured \u2014 identities come from config, never hard-coded"},void 0,!1,void 0,this):s.map((h)=>ht("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)),ht("box",{height:1,flexShrink:0},void 0,!1,void 0,this),ht(es,{palette:t,label:"impersonation",value:f,tone:m?"success":u.length>0?"warning":"muted"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),ht(Od,{palette:t,title:"IAP",tone:d?"success":"muted",children:[ht("box",{flexDirection:"row",overflow:"hidden",flexShrink:0,children:ht(Fe,{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?ht("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?ht(Od,{palette:t,title:"ADC unavailable",tone:"warning",children:[ht("text",{fg:t.text,wrapMode:"word",children:"Local services still start. Cloud identity, impersonation, and IAP need ADC."},void 0,!1,void 0,this),ht("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 zF(e,t){let r=new Map((e?Op(e):[]).map((s)=>[s.email,s.active])),n=t?.service_account_status??{};return[...new Set([...r.keys(),...Object.keys(n)])].sort((s,i)=>s.localeCompare(i)).map((s)=>{let i=n[s]??"unknown",a=r.get(s)===!1;return{email:s,inactive:a,ok:a||i==="unknown"?void 0:i==="available"}})}var n_=A(()=>{Wr();ir();Te()});import{jsxDEV as M}from"@opentui/react/jsx-dev-runtime";function a_(e){let{palette:t,cfg:r,width:n,selectedTask:o=0}=e,s=ge();if(!r)return M(nt,{palette:t,title:"No configuration",body:"Create .devctl/config.yaml or run setup."},void 0,!1,void 0,this);let i=Math.max(ni+8,n-6-s.pad*2),a=i>=QC,c=a?Math.floor((i-s_)/2):i,l=GC(r),d=qC(r),u=KC(r),m=VC(r),p=zC(r);return M(Et,{palette:t,title:`config ${r.project.name||"unnamed"}`,children:[M(xt,{palette:t,items:FC(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:Oe(t),children:M("box",{flexDirection:"column",overflow:"hidden",gap:i_,children:[M(gh,{wide:a,left:M(ln,{palette:t,title:"project",children:M(si,{palette:t,facts:HC(r,Vt),width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this),right:M(ln,{palette:t,title:"google",children:M(si,{palette:t,facts:UC(r),width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this),M(gh,{wide:a,left:M(ln,{palette:t,title:"runtime",children:M(si,{palette:t,facts:jC(r),width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this),right:M(ln,{palette:t,title:"logs",children:M(si,{palette:t,facts:BC(r),width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this),M(ln,{palette:t,title:`proxy ${r.proxy.enabled?"enabled":"disabled"}`,children:[M(si,{palette:t,facts:WC(r),width:i},void 0,!1,void 0,this),M(JF,{palette:t,routes:d,width:i},void 0,!1,void 0,this)]},void 0,!0,void 0,this),M(ln,{palette:t,title:`services ${l.length}`,children:M(YF,{palette:t,rows:l,width:i},void 0,!1,void 0,this)},void 0,!1,void 0,this),M(ln,{palette:t,title:`tasks ${p.length}`,children:M(o_,{palette:t,rows:p,empty:"no tasks",width:i,selected:o},void 0,!1,void 0,this)},void 0,!1,void 0,this),M(gh,{wide:a,left:M(ln,{palette:t,title:`profiles ${u.length}`,children:M(XF,{palette:t,rows:u,width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this),right:M(ln,{palette:t,title:`templates ${m.length}`,children:M(o_,{palette:t,rows:m,empty:"no templates",width:c},void 0,!1,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this),M(ln,{palette:t,title:"extras",children:M(si,{palette:t,facts:JC(r),width:i},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(Yt,{palette:t,backgroundColor:t.element,edge:"top",children:M(ft,{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 gh(e){let{wide:t,left:r,right:n}=e;if(!t)return M("box",{flexDirection:"column",overflow:"hidden",gap:i_,flexShrink:0,children:[r,n]},void 0,!0,void 0,this);return M("box",{flexDirection:"row",overflow:"hidden",gap:s_,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 ln(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 si(e){let{palette:t,facts:r,width:n}=e,o=Math.max(4,n-ni-4);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:ni,flexShrink:0,overflow:"hidden",children:M("text",{fg:t.muted,wrapMode:"none",children:U(s.label,ni)},void 0,!1,void 0,this)},void 0,!1,void 0,this),M("box",{width:o,flexShrink:0,overflow:"hidden",children:M("text",{fg:e1(t,s.tone),wrapMode:"none",children:U(s.value,o)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},s.label,!0,void 0,this))},void 0,!1,void 0,this)}function YF(e){let{palette:t,rows:r,width:n}=e;if(r.length===0)return M(Md,{palette:t,text:"no services"},void 0,!1,void 0,this);let o=XC(r),s=Math.max(8,n-o-6);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:o,flexShrink:0,overflow:"hidden",children:M("text",{fg:Ve(i.name,t),wrapMode:"none",children:U(i.name,o)},void 0,!1,void 0,this)},void 0,!1,void 0,this),M("box",{width:s,flexShrink:0,overflow:"hidden",children:M("text",{fg:t.text,wrapMode:"none",children:we(QF(i),s)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},i.name,!0,void 0,this))},void 0,!1,void 0,this)}function JF(e){let{palette:t,routes:r,width:n}=e;if(r.length===0)return M(Md,{palette:t,text:"no routes"},void 0,!1,void 0,this);let o=Math.min(ni+4,Math.max(8,...r.map((i)=>i.name.length))),s=Math.max(8,n-o-6);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:o,flexShrink:0,overflow:"hidden",children:M("text",{fg:t.primary,wrapMode:"none",children:U(i.name,o)},void 0,!1,void 0,this)},void 0,!1,void 0,this),M("box",{width:s,flexShrink:0,overflow:"hidden",children:M("text",{fg:t.text,wrapMode:"none",children:we(ZF(i),s)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},`${i.name}-${i.match}`,!0,void 0,this))},void 0,!1,void 0,this)}function XF(e){let{palette:t,rows:r,width:n}=e;if(r.length===0)return M(Md,{palette:t,text:"no profiles"},void 0,!1,void 0,this);let o=Math.min(12,Math.max(8,...r.map((i)=>i.name.length))),s=Math.max(8,n-o-6);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:o,flexShrink:0,overflow:"hidden",children:M("text",{fg:t.primary,wrapMode:"none",children:U(i.name,o)},void 0,!1,void 0,this)},void 0,!1,void 0,this),M("box",{width:s,flexShrink:0,overflow:"hidden",children:M("text",{fg:t.text,wrapMode:"none",children:we(i.env==="\u2014"?i.services:`${i.services} env ${i.env}`,s)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},i.name,!0,void 0,this))},void 0,!1,void 0,this)}function o_(e){let{palette:t,rows:r,empty:n,width:o,selected:s}=e;if(r.length===0)return M(Md,{palette:t,text:n},void 0,!1,void 0,this);let i=Math.min(14,Math.max(8,...r.map((c)=>c.name.length))),a=Math.max(8,o-i-6);return M("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:r.map((c,l)=>{let d=s===l;return M("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:d?t.highlight:void 0,children:[M("box",{width:i,flexShrink:0,overflow:"hidden",children:M("text",{fg:d?t.primary:t.text,wrapMode:"none",children:U(c.name,i)},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:we(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 Md(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 QF(e){let t=e.extends==="\u2014"?"":` ${e.extends}`;return` ${e.ports} ${e.identity} ${e.health} ${e.restart} \u2190 ${e.depends}${t}`}function ZF(e){return` ${e.auth} ${e.identity} ${e.match} \u2192 ${e.upstream}`}function e1(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 s_=1,i_=1;var c_=A(()=>{ir();ih();$t();tt();Te();et()});import{jsxDEV as jt}from"@opentui/react/jsx-dev-runtime";function l_(e){let{palette:t,credentials:r}=e;if(!r)return jt(md,{palette:t,label:"Reading credential store\u2026"},void 0,!1,void 0,this);let n=r.entries.filter((s)=>s.valid).length,o=r.entries.length-n;return jt(Et,{palette:t,title:"credentials",scroll:!0,children:[jt("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:[jt(Fe,{palette:t,label:`backend: ${r.backend}`,tone:"info"},void 0,!1,void 0,this),r.entries.length>0?jt(Fe,{palette:t,label:`${n} valid`,tone:"success"},void 0,!1,void 0,this):null,o>0?jt(Fe,{palette:t,label:`${o} expired`,tone:"warning"},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),jt("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),jt("box",{height:1,flexShrink:0},void 0,!1,void 0,this),r.entries.length===0?jt("text",{fg:t.muted,children:"no cached credentials"},void 0,!1,void 0,this):jt("box",{flexDirection:"column",overflow:"hidden",gap:1,children:r.entries.map((s)=>jt("box",{border:!0,borderStyle:"rounded",borderColor:s.valid?t.success:t.warning,title:s.identity,titleColor:s.valid?t.success:t.warning,flexDirection:"column",flexShrink:0,paddingLeft:1,paddingRight:1,overflow:"hidden",children:[jt("text",{wrapMode:"none",children:[jt("span",{fg:s.valid?t.success:t.warning,children:s.valid?"\u2713 valid":"\u2717 expired"},void 0,!1,void 0,this),jt("span",{fg:t.muted,children:` \xB7 expires ${s.expires_at}`},void 0,!1,void 0,this)]},void 0,!0,void 0,this),jt("text",{fg:t.muted,wrapMode:"none",children:`audience: ${s.audience||"(none)"}`},void 0,!1,void 0,this)]},`${s.identity}|${s.audience}`,!0,void 0,this))},void 0,!1,void 0,this),jt(pd,{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 d_=A(()=>{ir();Te()});import{jsxDEV as Nd}from"@opentui/react/jsx-dev-runtime";function ii(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 Nd("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:Nd("text",{wrapMode:"none",children:[Nd("span",{fg:e.palette.primary,children:"g"},void 0,!1,void 0,this),Nd("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 u_=()=>{};import{jsxDEV as Ia}from"@opentui/react/jsx-dev-runtime";function ai(e){let{palette:t,logs:r,names:n,service:o,errorOnly:s,width:i,onService:a,onToggleErrors:c,facets:l}=e,d=l?xS(n,l):bS(n,r),u=i<80,m=d.map((g)=>{let x=g.name===""?"all":g.name;return{id:x,label:u?x:`${x} \xB7 ${g.count}`,color:Ve(g.name,t)}}),p=Math.max(0,d.findIndex((g)=>g.name===o)),f=s?"ERROR+":u?"lvls":"all levels",h=Math.max(Ks(m[p]?.label??"all"),i-Ks(f));return Ia(Yt,{palette:t,backgroundColor:t.element,children:Ia("box",{height:1,flexDirection:"row",overflow:"hidden",backgroundColor:t.element,children:[Ia("box",{flexGrow:1,overflow:"hidden",children:Ia(ud,{palette:t,items:m,active:p,width:h,emphasis:"fill",onPick:(g)=>{let x=d[g];if(x)a(x.name)}},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ia(Fe,{palette:t,label:f,tone:s?"error":"muted",onMouseDown:c},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}var p_=A(()=>{yr();eo();Te();et()});import{jsxDEV as t1}from"@opentui/react/jsx-dev-runtime";function La(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 t1(xt,{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 m_=A(()=>{Te()});import{useEffect as $d,useRef as yh}from"react";import{jsxDEV as Be}from"@opentui/react/jsx-dev-runtime";function ci(e){let{palette:t,logs:r,limit:n,width:o=80,followTick:s=0,focused:i=!1,wrapMode:a="all",selected:c=-1,follow:l=!0,showTimestamps:d=!0,showMeta:u=!0,onPick:m,onLeaveLatest:p,viewStart:f=0,search:h="",regex:g=!1}=e,x=yh(null),y=yh(""),C=yh(p);$d(()=>{C.current=p},[p]);let _=n===void 0?r:r.slice(-n),E=[...new Set(r.map((w)=>w.service))],v=mS(o,E),b=_[_.length-1],S=b===void 0?"empty":String(b.seq);return $d(()=>{let w=x.current;if(!w)return;if(w.stickyScroll=l,l)w.stickyStart="bottom",w.scrollTo({x:w.scrollLeft,y:Math.max(0,w.scrollHeight)})},[l,s,S]),$d(()=>{let w=`${f}:${c}:${a}`;if(y.current===w)return;if(y.current=w,l&&(c<0||c>=_.length-1))return;let D=x.current;if(!D||c<0)return;let O=`log-row-${c}`,B=requestAnimationFrame(()=>{D.stickyScroll=!1,D.scrollChildIntoView(O)});return()=>cancelAnimationFrame(B)},[l,c,_.length,f,a]),$d(()=>{if(!l||!p)return;let w=!1,D=0,O=0,B=setInterval(()=>{let J=x.current;if(!J)return;let V=J.viewport.height;if(V<=0)return;let{scrollHeight:X,scrollTop:te}=J;if(te<O){J.stickyScroll=!1,O=te,D=X,C.current?.();return}if(O=te,te+V>=X-n1){w=!0,D=X;return}if(X>D){J.stickyScroll=!0,J.stickyStart="bottom",J.scrollTo({x:J.scrollLeft,y:Math.max(0,X)}),D=X;return}if(w)C.current?.()},r1);return()=>{clearInterval(B)}},[l,s,p]),Be("box",{flexGrow:1,height:"100%",flexDirection:"column",overflow:"hidden",children:[Be(s1,{palette:t,serviceWidth:v,showTimestamps:d,showMeta:u},void 0,!1,void 0,this),Be("scrollbox",{ref:x,focused:i,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:Be("box",{flexDirection:"column",overflow:"hidden",children:_.map((w,D)=>Be(i1,{id:`log-row-${D}`,palette:t,event:w,serviceWidth:v,expanded:uS(a,D===c),active:D===c,showTimestamps:d,showMeta:u,onPick:m?()=>m(D):void 0,search:h,regex:g},`${w.timestamp}-${w.service}-${D}`,!1,void 0,this))},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function s1(e){let{palette:t,serviceWidth:r,showTimestamps:n,showMeta:o}=e;return Be("box",{height:1,flexShrink:0,flexDirection:"row",overflow:"hidden",backgroundColor:t.element,children:[Be(f_,{palette:t,serviceWidth:r,showTimestamps:n,showMeta:o,time:"TIME",service:"SERVICE",level:"LEVEL",meta:"SRC",header:!0},void 0,!1,void 0,this),Be("box",{flexGrow:1,flexShrink:1,minWidth:0,overflow:"hidden",children:Be("text",{fg:t.muted,wrapMode:"none",children:"MESSAGE"},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function i1(e){let{id:t,palette:r,event:n,serviceWidth:o,expanded:s,active:i,showTimestamps:a=!0,showMeta:c=!0,onPick:l,search:d="",regex:u=!1}=e,m=Gt(n),p=n.severityText,f=n.source?n.traceId?`${o1}${n.source}`:n.source:"";return Be("box",{id:t,flexShrink:0,flexDirection:"row",alignItems:"flex-start",overflow:"hidden",height:s?"auto":1,backgroundColor:i?r.highlight:void 0,onMouseDown:l,children:[Be(f_,{palette:r,serviceWidth:o,showTimestamps:a,showMeta:c,time:n.timestamp.slice(11,19),service:n.service,level:p,meta:f},void 0,!1,void 0,this),Be("box",{flexGrow:1,flexShrink:1,minWidth:0,overflow:"hidden",children:Be(a1,{palette:r,level:p,text:m,wrap:s,search:d,regex:u},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function f_(e){let{palette:t,serviceWidth:r,showTimestamps:n,showMeta:o,time:s,service:i,level:a,meta:c,header:l=!1}=e,d=l?t.muted:void 0;return Be("box",{flexShrink:0,flexDirection:"row",overflow:"hidden",children:[n?Be(Fd,{width:df,fg:d??t.muted,text:s},void 0,!1,void 0,this):null,Be(Fd,{width:r,fg:d??Ve(i,t),text:i},void 0,!1,void 0,this),Be("box",{width:Xl,flexShrink:0,overflow:"hidden",children:Be("text",{children:" "},void 0,!1,void 0,this)},void 0,!1,void 0,this),Be(Fd,{width:uf,fg:d??ut(t,a),text:l?a:Gs(a)},void 0,!1,void 0,this),o?Be(Fd,{width:aS,fg:d??t.muted,text:c},void 0,!1,void 0,this):null,Be("box",{width:Xl,flexShrink:0,overflow:"hidden",children:Be("text",{children:" "},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function Fd(e){let{width:t,fg:r,text:n}=e;return Be("box",{width:t,flexShrink:0,overflow:"hidden",children:Be("text",{fg:r,wrapMode:"none",children:U(n,t)},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function a1(e){let{palette:t,level:r,text:n,wrap:o,search:s="",regex:i=!1}=e;return Be("text",{wrapMode:o?"word":"none",truncate:!o,flexShrink:0,width:"100%",children:pS(n,s,i).map((a,c)=>Be("span",{fg:jw(t,r,a.kind),children:a.text},`${a.kind}-${c}`,!1,void 0,this))},void 0,!1,void 0,this)}var r1=200,n1=2,o1="\u25CE";var h_=A(()=>{en();tt();yr();et()});var vh=A(()=>{u_();p_();m_();h_()});import{useRef as c1}from"react";import{jsxDEV as Me}from"@opentui/react/jsx-dev-runtime";function Hd(e){let{palette:t,names:r,snap:n,selected:o,checked:s,width:i,onOpen:a,onSelectIndex:c,onToggle:l}=e,d=ge(),u=c1(!1),m=new Set(s),p=cf(i),f=af(i),h=sf(i),g=tS(i);return Me("box",{flexDirection:"column",flexGrow:1,overflow:"hidden",children:[Me("box",{height:1,flexDirection:"row",overflow:"hidden",children:[Me(Qt,{width:2,fg:t.muted,text:""},void 0,!1,void 0,this),Me(Qt,{width:4,fg:t.accent,text:"sel"},void 0,!1,void 0,this),Me(Qt,{width:2,fg:t.muted,text:""},void 0,!1,void 0,this),Me(Qt,{width:g,fg:t.muted,text:"name"},void 0,!1,void 0,this),Me(Qt,{width:Ut,fg:t.muted,text:""},void 0,!1,void 0,this),Me(Qt,{width:Kr,fg:t.muted,text:"state"},void 0,!1,void 0,this),h?Me(Qt,{width:on,fg:t.muted,text:"health"},void 0,!1,void 0,this):null,f?Me(Qt,{width:jl,fg:t.muted,text:"port"},void 0,!1,void 0,this):null,p?Me(Qt,{width:Tn,fg:t.muted,text:"pid"},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),r.map((x,y)=>{let C=n?.services[x],_=gr(C),E=C?.health??"UNKNOWN",v=m.has(x),b=y===o,S=v?"[x]":"[ ]",w=b?"\u203A":" ";return Me("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(y)},children:[Me("box",{width:2,flexShrink:0,children:Me("text",{fg:t.primary,children:w},void 0,!1,void 0,this)},void 0,!1,void 0,this),Me("box",{width:4,flexShrink:0,onMouseDown:()=>{u.current=!0,l?.(x),setTimeout(()=>{u.current=!1},0)},children:Me("text",{fg:l1(t,v,b),children:S},void 0,!1,void 0,this)},void 0,!1,void 0,this),Me("box",{width:2,flexShrink:0,children:Me("text",{fg:ut(t,_),children:Ir(_)},void 0,!1,void 0,this)},void 0,!1,void 0,this),Me(Qt,{width:g,fg:b?t.primary:Ve(x,t),text:x},void 0,!1,void 0,this),Me(Qt,{width:Ut,fg:t.muted,text:""},void 0,!1,void 0,this),Me(Qt,{width:Kr,fg:ut(t,_),text:_},void 0,!1,void 0,this),h?Me(Qt,{width:on,fg:ut(t,E),text:E},void 0,!1,void 0,this):null,f?Me(Qt,{width:jl,fg:t.text,text:zo(C)},void 0,!1,void 0,this):null,p?Me(Qt,{width:Tn,fg:t.text,text:C?.pid?String(C.pid):""},void 0,!1,void 0,this):null]},x,!0,void 0,this)})]},void 0,!0,void 0,this)}function Ud(e){let{palette:t,checked:r}=e,n=r.length;if(n>0)return Me(xt,{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 l1(e,t,r){if(t)return e.success;if(r)return e.primary;return e.muted}function Qt(e){return Me("box",{width:e.width,flexShrink:0,overflow:"hidden",children:Me("text",{fg:e.fg,wrapMode:"none",children:U(e.text,e.width)},void 0,!1,void 0,this)},void 0,!1,void 0,this)}var bh=A(()=>{$t();tt();Ft();Te();et()});import{jsxDEV as me}from"@opentui/react/jsx-dev-runtime";function y_(e){let{palette:t,cfg:r,snap:n,logs:o,names:s,selected:i,selectedLog:a=-1,checked:c,width:l,followTick:d,logService:u,logSources:m,errorOnly:p,showSystemLogs:f,onOpen:h,onSelectIndex:g,onToggle:x,onFilterService:y,onToggleErrors:C,onShowErrors:_,wrapMode:E="all",view:v,follow:b=!0,onLeaveLatest:S,onPickLog:w,viewStart:D=0,viewTotal:O,newer:B=0,onJumpLatest:J,facets:V,leftover:X,search:te="",regex:k=!1}=e,N=ge();if(!r||s.length===0)return me(nt,{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 K=l<Vs,ue=ql(n),We=cd(n,s),ie=s.filter((Se)=>n?.services[Se]?.state==="FAILED").length,W=vS(o),le=ue?oS(X,n?.session_id):void 0,re=Gl(l,s,K),pt=Math.max(24,K?l-4:l-re-4),wt=f?o:o.filter((Se)=>!qs(Se.source)),ae=Jo(o,{service:u,errorOnly:p,systemLogs:f,search:te,regex:k}),Le=v??ae,Ne=O??ae.length,Je=Math.min(Ne,D+Le.length),St=Ne===0?"empty":`${D+1}\u2013${Je} of ${Ne}`,st=u===""?"all":u,At=[...We.running>0?[{text:`${We.running}/${s.length}`,tone:"success"}]:[],...ie>0?[{text:`${ie} failed`,tone:"error"}]:[],...c.length>0?[{text:`${c.length} selected`,tone:"primary"}]:[],...W>0?[{text:`${W} errors`,tone:"error",onMouseDown:_}]:[]];return me("box",{flexGrow:1,flexDirection:K?"column":"row",overflow:"hidden",children:[me("box",{flexGrow:K?1:0,flexShrink:0,minWidth:K?void 0:re,width:K?void 0:re,border:!0,borderStyle:"rounded",borderColor:ue?t.border:t.borderActive,backgroundColor:t.panel,title:"services",titleColor:t.primary,flexDirection:"column",overflow:"hidden",children:[At.length>0?me(xt,{palette:t,items:At},void 0,!1,void 0,this):null,me(Ud,{palette:t,checked:c},void 0,!1,void 0,this),me("box",{flexGrow:1,paddingLeft:N.pad,paddingRight:N.pad,overflow:"hidden",children:me(Hd,{palette:t,names:s,snap:n,selected:i,checked:c,width:Wl(re,N.pad),onOpen:h,onSelectIndex:g,onToggle:x},void 0,!1,void 0,this)},void 0,!1,void 0,this),me(m1,{palette:t,names:s,snap:n,width:re,onOpen:h},void 0,!1,void 0,this),le?me(h1,{palette:t,leftover:le,width:re},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),me("box",{position:"relative",flexGrow:2,minWidth:K?void 0:32,minHeight:K?8:void 0,border:!0,borderStyle:"rounded",borderColor:t.border,backgroundColor:t.panel,title:`logs \xB7 ${st} \xB7 ${St}`,titleColor:t.primary,overflow:"hidden",flexDirection:"column",children:[me(ai,{palette:t,logs:wt,names:m??s,service:u,errorOnly:p,width:pt,onService:y,onToggleErrors:C,facets:V},void 0,!1,void 0,this),Ne>Le.length?me(La,{palette:t,start:D,count:Le.length,total:Ne},void 0,!1,void 0,this):null,ae.length===0?me(nt,{palette:t,title:ue?"Waiting for logs":o.length===0?"No events yet":"No events in this filter",body:ue?"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):me(ci,{palette:t,logs:Le,width:pt,followTick:d,focused:!1,wrapMode:E,selected:a,follow:b,onLeaveLatest:S,onPick:w,viewStart:D,search:te,regex:k},d,!1,void 0,this),!b?me(ii,{palette:t,width:pt,newer:B,bottom:0,onJump:J},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}function u1(e){if(!e)return!1;return e.state===bt||e.health===$e||e.state===Ar||e.restarts>0||e.last_error!==""}function g_(e){if(e.state===bt)return 0;if(e.health===$e)return 1;if(e.state===Ar)return 2;return 3}function p1(e){if(e.last_error!=="")return e.last_error;if(e.restarts>0)return`restarted ${e.restarts}x, no error recorded`;return e.health===$e?"failing health check":e.state.toLowerCase()}function m1(e){let{palette:t,names:r,snap:n,width:o,onOpen:s}=e,i=r.map((d)=>({name:d,rt:n?.services[d]})).filter((d)=>u1(d.rt)).sort((d,u)=>g_(d.rt)-g_(u.rt));if(i.length===0)return null;let a=i.slice(0,d1),c=i.length-a.length,l=Math.max(8,o-xh-4);return me("box",{flexDirection:"column",flexShrink:0,paddingLeft:1,paddingRight:1,overflow:"hidden",children:[me("box",{height:1,overflow:"hidden",children:me("text",{fg:t.error,wrapMode:"none",children:`issues (${i.length})`},void 0,!1,void 0,this)},void 0,!1,void 0,this),a.map(({name:d,rt:u})=>{let m=gr(u);return me("box",{height:1,flexDirection:"row",overflow:"hidden",onMouseDown:()=>s(d),children:[me("box",{width:2,flexShrink:0,children:me("text",{fg:ut(t,m),children:Ir(m)},void 0,!1,void 0,this)},void 0,!1,void 0,this),me("box",{width:xh,flexShrink:0,overflow:"hidden",children:me("text",{fg:Ve(d,t),wrapMode:"none",children:U(d,xh)},void 0,!1,void 0,this)},void 0,!1,void 0,this),me("box",{width:l,flexShrink:0,overflow:"hidden",children:me("text",{fg:t.muted,wrapMode:"none",children:we(p1(u),l)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},d,!0,void 0,this)}),c>0?me("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 h1(e){let{palette:t,leftover:r,width:n}=e,o=r.processes.slice(0,f1),s=r.processes.length-o.length,i=Math.max(8,n-wh-4);return me("box",{flexDirection:"column",flexShrink:0,paddingLeft:1,paddingRight:1,overflow:"hidden",children:[me("box",{height:1,overflow:"hidden",children:me("text",{fg:t.warning,wrapMode:"none",children:"last session"},void 0,!1,void 0,this)},void 0,!1,void 0,this),me("text",{fg:t.muted,wrapMode:"none",children:we(`${r.session_id} ${r.profile||"(none)"}`,Math.max(8,n-4))},void 0,!1,void 0,this),o.map((a)=>me("box",{height:1,flexDirection:"row",overflow:"hidden",children:[me("box",{width:wh,flexShrink:0,overflow:"hidden",children:me("text",{fg:t.text,wrapMode:"none",children:U(a.name,wh)},void 0,!1,void 0,this)},void 0,!1,void 0,this),me("box",{width:i,flexShrink:0,overflow:"hidden",children:me("text",{fg:t.muted,wrapMode:"none",children:we(`pid ${a.pid} leftover`,i)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},`${a.name}-${a.pid}`,!0,void 0,this)),s>0?me("text",{fg:t.muted,wrapMode:"none",children:`+${s} more leftover pids`},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}var d1=4,xh=14,f1=4,wh=14;var v_=A(()=>{Nt();ir();$t();vr();tt();yr();Ft();dd();Te();et();vh();bh()});import g1 from"cli-spinners";import{useEffect as y1,useState as v1}from"react";import{jsxDEV as se}from"@opentui/react/jsx-dev-runtime";function b_(e){let{palette:t,report:r,loading:n,progress:o,error:s,selected:i,onPick:a,onReload:c}=e,l=Xs(i,kh);if(n)return se(b1,{palette:t,progress:o},void 0,!1,void 0,this);if(s)return se(Rk,{palette:t,title:"Doctor failed",body:s},void 0,!1,void 0,this);if(!r)return se(nt,{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((g)=>g.severity==="ok").length,u=r.checks.filter((g)=>g.severity==="warn").length,m=r.checks.filter((g)=>g.severity==="error").length,p=r.checks.length,f=Ws(p>0?d/p:0),h=m>0?t.error:u>0?t.warning:t.success;return se(Et,{palette:t,title:`doctor ${r.issues} issue(s)`,children:[se("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:[se("text",{wrapMode:"none",children:[se("span",{fg:t.inverse,bg:t.primary,children:" r "},void 0,!1,void 0,this),se("span",{fg:t.primary,children:" RUN DOCTOR AGAIN"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),se("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),se("box",{height:1,flexShrink:0},void 0,!1,void 0,this),se("box",{height:1,flexShrink:0,overflow:"hidden",children:se("text",{wrapMode:"none",children:[se("span",{fg:h,children:`[${f}]`},void 0,!1,void 0,this),se("span",{fg:t.success,children:` ${d} ok`},void 0,!1,void 0,this),u>0?se("span",{fg:t.warning,children:` \xB7 ${u} warn`},void 0,!1,void 0,this):null,m>0?se("span",{fg:t.error,children:` \xB7 ${m} error`},void 0,!1,void 0,this):null,u===0&&m===0?se("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),se("box",{height:1,flexShrink:0},void 0,!1,void 0,this),se("scrollbox",{ref:l,focused:!1,stickyScroll:!1,scrollX:!1,style:Oe(t),children:se("box",{flexDirection:"column",overflow:"hidden",gap:1,children:r.checks.map((g,x)=>se(x1,{palette:t,check:g,index:x,active:x===i,onPick:a},`${g.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 b1({palette:e,progress:t}){let[r,n]=v1(0);return y1(()=>{let o=setInterval(()=>n((s)=>(s+1)%Sh.frames.length),Sh.interval);return()=>clearInterval(o)},[]),se("box",{flexGrow:1,alignItems:"center",justifyContent:"center",overflow:"hidden",children:se("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:[se("text",{wrapMode:"none",children:[se("span",{fg:e.primary,children:`${Sh.frames[r]} `},void 0,!1,void 0,this),se("span",{fg:e.text,children:"devctl doctor"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),se("box",{height:1,flexShrink:0},void 0,!1,void 0,this),se("text",{fg:e.text,wrapMode:"none",children:`Checking ${t.active}`},void 0,!1,void 0,this),se("box",{height:1,flexShrink:0},void 0,!1,void 0,this),t.checks.slice(-4).map((o,s)=>se("text",{wrapMode:"none",children:[se("span",{fg:ut(e,o.severity==="error"?"ERROR":o.severity==="warn"?"WARN":"OK"),children:`${Ir(o.severity==="error"?"ERROR":o.severity==="warn"?"WARN":"OK")} `},void 0,!1,void 0,this),se("span",{fg:e.muted,children:o.name},void 0,!1,void 0,this)]},`${o.name}-${s}`,!0,void 0,this)),t.checks.length===0?se("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 x1(e){let{palette:t,check:r,index:n,active:o,onPick:s}=e,i=r.severity==="error"?"ERROR":r.severity==="warn"?"WARN":"OK";if(r.severity==="ok")return se("box",{id:`${kh}-${n}`,flexDirection:"row",flexShrink:0,paddingLeft:1,overflow:"hidden",backgroundColor:o?t.highlight:void 0,onMouseDown:()=>s(n),children:se("text",{wrapMode:"none",children:[se("span",{fg:t.primary,children:o?"\u203A ":" "},void 0,!1,void 0,this),se("span",{fg:ut(t,i),children:Ir(i)},void 0,!1,void 0,this),se("span",{fg:o?t.primary:t.text,children:` ${r.name}`},void 0,!1,void 0,this),se("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 se("box",{id:`${kh}-${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:()=>s(n),children:[se("text",{wrapMode:"word",children:[se("span",{fg:ut(t,i),children:`${Ir(i)} `},void 0,!1,void 0,this),se("span",{fg:t.text,children:r.message},void 0,!1,void 0,this)]},void 0,!0,void 0,this),r.hint?se("text",{fg:t.info,wrapMode:"word",children:`\u2192 ${r.hint}`},void 0,!1,void 0,this):null,o&&r.action?.kind==="free-port"?se("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 kh="doctor-row",Sh;var x_=A(()=>{ir();tt();Te();et();Sh=g1.bouncingBall});import{jsxDEV as ze,Fragment as w1}from"@opentui/react/jsx-dev-runtime";function S_(e){let{palette:t,logs:r,names:n,logSources:o,service:s,errorOnly:i,showSystemLogs:a,search:c,searchFocused:l,followTick:d,width:u,onSearch:m,onService:p,onToggleErrors:f,onSelect:h,wrapMode:g="all",selected:x=-1,follow:y=!0,newer:C=0,viewStart:_=0,viewTotal:E,onLeaveLatest:v,onJumpLatest:b,view:S,fullscreen:w=!1,facets:D,split:O=!1,splitFocus:B=0,serviceB:J="",viewB:V,selectedB:X=-1,followB:te=!0,newerB:k=0,viewStartB:N=0,viewTotalB:K,onServiceB:ue,onSelectB:We,onLeaveLatestB:ie,onFocusPane:W}=e,le=ge(),re=pf(u,le.pad,w),pt=a?r:r.filter((Se)=>!qs(Se.source)),wt=Jo(r,{service:s,services:e.services,errorOnly:i,search:c,regex:e.regex,source:e.source,systemLogs:a}),ae=S??wt,Le=E??wt.length,Ne=Math.min(Le,_+ae.length),Je=Le===0?"empty":`${_+1}\u2013${Ne} of ${Le}`,St=e.services??[],st=St.length>0?St.join(","):s===""?"all services":s,At=[...y?[]:[{text:C>0?`${O?"left ":""}pinned \xB7 +${C} new`:`${O?"left ":""}pinned`,tone:"warning"}],...O&&!te?[{text:k>0?`right pinned \xB7 +${k} 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:B===0?"left pane":"right pane",tone:"accent"}]:[]];if(O){let Se=Math.max(20,Math.floor(u/2)),kt=Math.max(20,u-Se),gt=V??[],Ct=K??gt.length;return ze("box",{flexGrow:1,flexDirection:"column",overflow:"hidden",children:[l?ze("box",{height:1,paddingLeft:1,backgroundColor:t.highlight,overflow:"hidden",children:ze("input",{focused:!0,value:c,placeholder:"search messages or service names",onInput:m,backgroundColor:t.highlight,focusedBackgroundColor:t.highlight,textColor:t.text,cursorColor:t.primary},void 0,!1,void 0,this)},void 0,!1,void 0,this):ze(xt,{palette:t,items:At},void 0,!1,void 0,this),ze("box",{flexGrow:1,flexDirection:"row",overflow:"hidden",children:[ze(w_,{palette:t,names:o??n,logs:r,service:s,errorOnly:i,showSystemLogs:a,search:c,regex:e.regex,source:e.source,width:Se,shown:ae,shownTotal:Le,selected:x,follow:y,newer:C,viewStart:_,wrapMode:g,showTimestamps:e.showTimestamps!==!1,showMeta:e.showMeta!==!1,followTick:d,facets:D,focused:B===0,onService:p,onToggleErrors:f,onSelect:h,onLeaveLatest:v,onFocus:()=>W?.(0)},void 0,!1,void 0,this),ze(w_,{palette:t,names:o??n,logs:r,service:J,errorOnly:i,showSystemLogs:a,search:c,regex:e.regex,source:e.source,width:kt,shown:gt,shownTotal:Ct,selected:X,follow:te,newer:k,viewStart:N,wrapMode:g,showTimestamps:e.showTimestamps!==!1,showMeta:e.showMeta!==!1,followTick:d,facets:D,focused:B===1,onService:ue??p,onToggleErrors:f,onSelect:We,onLeaveLatest:ie,onFocus:()=>W?.(1)},void 0,!1,void 0,this)]},void 0,!0,void 0,this),!y||!te?ze(ii,{palette:t,width:u,newer:Math.max(C,k),onJump:b},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}return ze("box",{flexGrow:1,flexDirection:"column",overflow:"hidden",children:[w?null:ze(w1,{children:[At.length>0?ze(xt,{palette:t,items:At},void 0,!1,void 0,this):null,ze(ai,{palette:t,logs:pt,names:o??n,service:s,errorOnly:i,width:u,onService:p,onToggleErrors:f,facets:D},void 0,!1,void 0,this)]},void 0,!0,void 0,this),l?ze("box",{height:1,paddingLeft:1,backgroundColor:t.highlight,overflow:"hidden",children:ze("input",{focused:!0,value:c,placeholder:"search messages or service names",onInput:m,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&&Le>ae.length?ze(La,{palette:t,start:_,count:ae.length,total:Le},void 0,!1,void 0,this):null,ze("box",{flexGrow:1,border:!w,borderStyle:"rounded",borderColor:t.border,title:w?void 0:`logs ${st} \xB7 ${Je}`,titleColor:t.primary,padding:w?0:le.pad,overflow:"hidden",children:ae.length===0?ze(nt,{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 ${Ht("l")} clear`},void 0,!1,void 0,this):ze(ci,{palette:t,logs:ae,width:re,followTick:d,focused:!1,wrapMode:g,selected:x,follow:y,showTimestamps:e.showTimestamps!==!1,showMeta:e.showMeta!==!1,onPick:h,onLeaveLatest:v,viewStart:_,search:c,regex:e.regex===!0},d,!1,void 0,this)},void 0,!1,void 0,this),!y?ze(ii,{palette:t,width:u,newer:C,onJump:b},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}function w_(e){let{palette:t,width:r,service:n,shown:o,shownTotal:s,viewStart:i,focused:a}=e,c=e.showSystemLogs?e.logs:e.logs.filter((u)=>!qs(u.source)),l=n===""?"all services":n,d=s===0?"empty":`${i+1}\u2013${Math.min(s,i+o.length)} of ${s}`;return ze("box",{width:r,flexGrow:0,flexShrink:0,flexDirection:"column",overflow:"hidden",onMouseDown:e.onFocus,children:[ze(ai,{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),ze("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?ze(nt,{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):ze(ci,{palette:t,logs:o,width:pf(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:i,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 k_=A(()=>{vh();ir();$t();yr();Te();qr()});import{jsxDEV as Ye}from"@opentui/react/jsx-dev-runtime";function R_(e){let{palette:t,cfg:r,snap:n,profile:o,selected:s,onPick:i}=e,a=ge(),c=Xs(s,C_),l=Object.keys(r?.profiles??{}).sort();if(l.length===0)return Ye(nt,{palette:t,title:"No profiles",body:"Add profiles.<name>.services in config.yaml."},void 0,!1,void 0,this);return Ye(Et,{palette:t,title:"profiles",children:[Ye("box",{height:1,flexShrink:0,overflow:"hidden",children:Ye("text",{fg:t.muted,wrapMode:"none",children:[`${l.length} profile${l.length===1?"":"s"} \xB7 current: `,Ye("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),Ye("box",{height:1,flexShrink:0},void 0,!1,void 0,this),Ye("scrollbox",{ref:c,focused:!1,stickyScroll:!1,scrollX:!1,style:Oe(t),children:Ye("box",{flexDirection:"column",overflow:"hidden",gap:a.gap||1,children:l.map((d,u)=>{let m=d===o,p=u===s,f=r?.profiles[d]?.services??[],h=Object.keys(r?.profiles[d]?.environment??{}).length,g=p?t.borderActive:m?t.success:t.border,x=p?t.primary:m?t.success:t.muted;return Ye("box",{id:`${C_}-${u}`,border:!0,borderStyle:"rounded",borderColor:g,title:d,titleColor:x,flexDirection:"column",flexShrink:0,paddingLeft:1,paddingRight:1,overflow:"hidden",backgroundColor:p?t.highlight:void 0,onMouseDown:()=>i(u),children:[Ye("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:Ye("text",{wrapMode:"none",children:[m?Ye("span",{fg:t.success,children:"\u25CF current profile"},void 0,!1,void 0,this):Ye("span",{fg:t.muted,children:"\u25CB not active"},void 0,!1,void 0,this),Ye("span",{fg:t.muted,children:` \xB7 ${f.length} service${f.length===1?"":"s"}`},void 0,!1,void 0,this),h>0?Ye("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),Ye("box",{flexDirection:"row",overflow:"hidden",flexShrink:0,children:f.length===0?Ye("text",{fg:t.muted,wrapMode:"none",children:"(no services)"},void 0,!1,void 0,this):Ye("text",{wrapMode:"word",children:f.map((y,C)=>{let _=gr(n?.services[y]);return Ye("span",{children:[Ye("span",{fg:ut(t,_),children:Ir(_)},void 0,!1,void 0,this),Ye("span",{fg:Ve(y,t),children:` ${y}`},void 0,!1,void 0,this),C<f.length-1?Ye("span",{fg:t.muted,children:" "},void 0,!1,void 0,this):null]},y,!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),Ye(Yt,{palette:t,backgroundColor:t.element,edge:"top",children:Ye(ft,{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 C_="profile-row";var __=A(()=>{ir();$t();Ft();Te();et()});import{jsxDEV as Y,Fragment as A_}from"@opentui/react/jsx-dev-runtime";function S1(e){let t=e.toLowerCase();if(t==="iap")return"info";if(t==="service"||t==="service_account")return"primary";return"idle"}function k1(e,t,r){if(r||t>=500)return e.error;if(t===0||t>=400)return e.warning;return e.success}function C1(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 P1(e){let{palette:t,req:r,cols:n,onOpenTrace:o}=e,s=k1(t,r.status,r.error),i=r.status>0?String(r.status):"ERR",a=C1(t,r.identity),c=r.route||I_,l=r.error?`${r.path} \u2014 ${we(r.error,T1)}`:r.path,d=r.traceId;return Y("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,onMouseDown:d&&o?()=>o(d):void 0,children:[Y("box",{width:jd,flexShrink:0,overflow:"hidden",children:Y("text",{fg:t.muted,children:U(r.timestamp.slice(11,19),jd)},void 0,!1,void 0,this)},void 0,!1,void 0,this),n.showMethod?Y("box",{width:Bd,flexShrink:0,overflow:"hidden",children:Y("text",{fg:t.text,wrapMode:"none",children:U(r.method,Bd)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,Y("box",{width:Wd,flexShrink:0,overflow:"hidden",children:Y("text",{fg:s,wrapMode:"none",children:U(i,Wd)},void 0,!1,void 0,this)},void 0,!1,void 0,this),n.showDur?Y("box",{width:Gd,flexShrink:0,overflow:"hidden",children:Y("text",{fg:t.muted,wrapMode:"none",children:U(`${r.durationMs}ms`,Gd)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,Y("box",{width:qd,flexShrink:0,overflow:"hidden",children:Y("text",{fg:a.color,wrapMode:"none",children:U(a.label,qd)},void 0,!1,void 0,this)},void 0,!1,void 0,this),Y("box",{width:n.routeCol,flexShrink:0,overflow:"hidden",children:Y("text",{fg:r.route?t.info:t.muted,wrapMode:"none",children:U(c,n.routeCol)},void 0,!1,void 0,this)},void 0,!1,void 0,this),Y("box",{flexGrow:1,overflow:"hidden",children:Y("text",{fg:r.error?t.error:t.text,wrapMode:"none",children:l},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function E1(e){let{palette:t,cols:r}=e;return Y("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:t.element,children:[Y("box",{width:jd,flexShrink:0,overflow:"hidden",children:Y("text",{fg:t.muted,children:U("TIME",jd)},void 0,!1,void 0,this)},void 0,!1,void 0,this),r.showMethod?Y("box",{width:Bd,flexShrink:0,overflow:"hidden",children:Y("text",{fg:t.muted,children:U("METHOD",Bd)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,Y("box",{width:Wd,flexShrink:0,overflow:"hidden",children:Y("text",{fg:t.muted,children:U("ST",Wd)},void 0,!1,void 0,this)},void 0,!1,void 0,this),r.showDur?Y("box",{width:Gd,flexShrink:0,overflow:"hidden",children:Y("text",{fg:t.muted,children:U("DUR",Gd)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,Y("box",{width:qd,flexShrink:0,overflow:"hidden",children:Y("text",{fg:t.muted,children:U("WHO",qd)},void 0,!1,void 0,this)},void 0,!1,void 0,this),Y("box",{width:r.routeCol,flexShrink:0,overflow:"hidden",children:Y("text",{fg:t.muted,children:U("ROUTE",r.routeCol)},void 0,!1,void 0,this)},void 0,!1,void 0,this),Y("box",{flexGrow:1,overflow:"hidden",children:Y("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 A1(e){let{palette:t,name:r,auth:n,identity:o,upstream:s,match:i,onMouseDown:a}=e;return Y("box",{flexDirection:"column",flexShrink:0,overflow:"hidden",onMouseDown:a,children:[Y("text",{fg:t.text,wrapMode:"none",children:r},void 0,!1,void 0,this),Y("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:[Y(Fe,{palette:t,label:n||"no auth",tone:S1(n)},void 0,!1,void 0,this),o?Y(Fe,{palette:t,label:o,tone:"idle"},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),i?Y("text",{fg:t.muted,wrapMode:"word",children:`match: ${i}`},void 0,!1,void 0,this):null,Y("text",{fg:t.muted,wrapMode:"word",children:`\u2192 ${s}`},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function L_(e){let{palette:t,cfg:r,snap:n,width:o,onSelectRoute:s,onOpenTrace:i}=e,a=n?.proxy.routes??[],c=new Map((r?.proxy.routes??[]).map((v)=>[v.name,v])),l=new Map((r?.proxy.routes??[]).map((v)=>[v.name,v.match])),d=n?.proxy.recentRequests??[],u=n?.proxy.requestTotal??0,m=n?.proxy.requestErrors??0,p=o<Vs,f=Math.max(T_,Math.min(I1,Math.floor(o*0.36))),h=p?o-P_:o-f-P_,g=Math.max(20,h-2),x=g>=E_,y=g>=L1,C=g>=E_?D1:O1,_=d.reduce((v,b)=>Math.min(C,Math.max(v,(b.route||I_).length+_1)),R1),E={showMethod:x,showDur:y,routeCol:_};return Y(Et,{palette:t,title:"proxy",children:[Y(xt,{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:`${a.length} routes`,tone:a.length>0?"primary":"idle"},...u>0?[{text:`${u} requests`,tone:"idle"},...m>0?[{text:`${m} errors`,tone:"warning"}]:[]]:[]]},void 0,!1,void 0,this),Y("box",{flexGrow:1,flexDirection:p?"column":"row",overflow:"hidden",children:[Y("box",{flexGrow:p?1:0,flexBasis:p?0:void 0,flexShrink:0,minWidth:p?void 0:T_,width:p?void 0:f,minHeight:p?8:void 0,border:!0,borderStyle:"rounded",borderColor:t.border,title:"routes",titleColor:t.primary,paddingLeft:1,paddingRight:1,flexDirection:"column",overflow:"hidden",children:a.length===0?Y(nt,{palette:t,title:"No proxy routes",body:"Add routes under proxy.routes or .devctl/proxy/routes.yaml."},void 0,!1,void 0,this):Y(A_,{children:[s?Y("text",{fg:t.muted,wrapMode:"word",children:"click a route for full details"},void 0,!1,void 0,this):null,Y("scrollbox",{focused:!1,stickyScroll:!1,scrollX:!1,style:Oe(t),children:Y("box",{flexDirection:"column",overflow:"hidden",gap:1,children:a.map((v)=>{let b=l.get(v.name),S=b&&(b.host||b.path)?`${b.host||"*"}${b.path?b.path:""}`:void 0,w=c.get(v.name);return Y(A1,{palette:t,name:v.name,auth:v.auth,identity:v.identity,upstream:v.upstream,match:S,onMouseDown:s?()=>s({name:v.name,authType:w?.auth.type||v.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:Wc(w?.auth.client_secret??"")??((w?.auth.client_secret??"").trim()?"inline":""),matchHost:w?.match.host??"",matchPath:w?.match.path??"",upstream:v.upstream}):void 0},v.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),Y("box",{flexGrow:1,flexBasis:0,minWidth:p?void 0:30,minHeight:p?10:void 0,border:!0,borderStyle:"rounded",borderColor:t.borderActive,title:"requests",titleColor:t.primary,flexDirection:"column",overflow:"hidden",children:d.length===0?Y("box",{paddingLeft:1,paddingRight:1,children:Y("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):Y(A_,{children:[Y(E1,{palette:t,cols:E},void 0,!1,void 0,this),Y("scrollbox",{focused:!1,stickyScroll:!1,scrollX:!1,style:Oe(t),children:Y("box",{flexDirection:"column",overflow:"hidden",children:d.map((v)=>Y(P1,{palette:t,req:v,cols:E,onOpenTrace:i},v.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),Y(Yt,{palette:t,backgroundColor:t.element,edge:"top",children:Y(ft,{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 jd=9,Bd=8,Wd=4,Gd=8,qd=4,R1=8,_1=2,T1=64,I_="(none)",T_=26,I1=56,P_=2,E_=46,L1=54,D1=20,O1=12;var D_=A(()=>{ir();vr();tt();Te()});import{jsxDEV as pe}from"@opentui/react/jsx-dev-runtime";function N_(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:Ve(r,t),flexDirection:"column",overflow:"hidden",children:pe(Ch,{...e,compact:!1},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function Ch(e){let{palette:t,cfg:r,snap:n,name:o,reveal:s,width:i,compact:a=!0,envScrollRef:c}=e,l=ge(),d=r?.services[o];if(!d)return pe(nt,{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],m=gr(u),p=u?.health??"UNKNOWN",f=zo(u)||Sa(d,u),h=nS(d,s,r?.secrets.extra_markers??[],r?.secrets.extra_patterns??[],e.resolvedEnv),g=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",y=i>=N1,C=[{label:"health",value:zl(d)},{label:"workdir",value:d.working_dir||"."},{label:"restart",value:Jl(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:Yl(d,u)},{label:"depends",value:d.dependencies.map(Ec).join(", ")||"\u2014"},{label:"ports",value:Sa(d,u)},{label:"tries",value:String(u?.restarts??0),tone:u?.restarts?"warning":"muted"}],E=Math.max(os+8,i-2),v=y?Math.floor(E/2):E;return pe("box",{padding:l.pad,flexGrow:1,flexDirection:"column",overflow:"hidden",children:[pe(xt,{palette:t,items:[{text:m,tone:M_(m)},{text:p,tone:M_(p)},{text:u?.pid?`pid ${u.pid}`:"pid \u2014",tone:u?.pid?"info":"idle"},{text:f==="\u2014"?"port \u2014":`port ${f}`,tone:f==="\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:we(d.description,Math.max(8,i-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:we(u.last_error,M1)},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,pe(Rh,{palette:t,item:{label:"command",value:Vl(d)},valueWidth:Math.max(8,E-os)},void 0,!1,void 0,this),pe($1,{palette:t,left:C,right:_,wide:y,colWidth:v},void 0,!1,void 0,this),pe(F1,{palette:t,entries:h,reveal:s,width:i,focused:!a,scrollRef:c,source:x,sourceTone:g,error:e.envError,onSelect:e.onSelectEnv},void 0,!1,void 0,this),a?null:pe(ft,{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 $1(e){let{palette:t,left:r,right:n,wide:o,colWidth:s}=e,i=Math.max(4,s-os);if(!o)return pe("box",{width:s,flexShrink:0,flexDirection:"column",overflow:"hidden",children:[...r,...n].map((a)=>pe(Rh,{palette:t,item:a,valueWidth:i},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(O_,{palette:t,items:r,width:s},void 0,!1,void 0,this),pe(O_,{palette:t,items:n,width:s},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function O_(e){let t=Math.max(4,e.width-os);return pe("box",{width:e.width,flexShrink:0,flexDirection:"column",overflow:"hidden",children:e.items.map((r)=>pe(Rh,{palette:e.palette,item:r,valueWidth:t},r.label,!1,void 0,this))},void 0,!1,void 0,this)}function Rh(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:os+n,flexDirection:"row",overflow:"hidden",children:[pe("box",{width:os,flexShrink:0,overflow:"hidden",children:pe("text",{fg:t.muted,wrapMode:"none",children:U(r.label,os)},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:U(r.value,n)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function F1(e){let{palette:t,entries:r,reveal:n,width:o,focused:s,scrollRef:i,source:a,sourceTone:c,error:l,onSelect:d}=e;return pe("box",{flexGrow:1,flexDirection:"column",overflow:"hidden",children:[pe(xt,{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:s?[{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:we(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:i,focused:s,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 m=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:m,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 M_(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 M1=72,N1=56,os=9;var _h=A(()=>{lt();ir();$t();tt();Ft();Te();et()});import{jsxDEV as co}from"@opentui/react/jsx-dev-runtime";function $_(e){let{palette:t,cfg:r,names:n,snap:o,selected:s,checked:i,width:a,reveal:c,onOpen:l,onSelectIndex:d,onToggle:u,resolvedEnv:m,envStatus:p,envError:f,onSelectEnv:h}=e;if(n.length===0)return co(nt,{palette:t,title:"No services",body:"This configuration does not define any services."},void 0,!1,void 0,this);let g=a<Vs,x=n[s]??"",y=Gl(a,n,g),C=4,_=g?Math.max(Th,a-C):Math.max(Th,a-y-C);return co("box",{flexGrow:1,flexDirection:g?"column":"row",overflow:"hidden",children:[co("box",{flexGrow:g?1:0,flexShrink:0,minWidth:g?void 0:Ul,width:g?void 0:y,border:!0,borderStyle:"rounded",borderColor:t.borderActive,backgroundColor:t.panel,title:"services",titleColor:t.primary,flexDirection:"column",overflow:"hidden",children:[co(Ud,{palette:t,checked:i},void 0,!1,void 0,this),co("box",{flexGrow:1,overflow:"hidden",children:co(Hd,{palette:t,names:n,snap:o,selected:s,checked:i,width:Wl(y),onOpen:l,onSelectIndex:d,onToggle:u},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this),co("box",{flexGrow:2,minWidth:g?void 0:Th,minHeight:g?H1:void 0,border:!0,borderStyle:"rounded",borderColor:t.border,backgroundColor:t.panel,title:x||"detail",titleColor:x?Ve(x,t):t.primary,overflow:"hidden",flexDirection:"column",children:co(Ch,{palette:t,cfg:r,snap:o,name:x,reveal:c,width:_,resolvedEnv:m,envStatus:p,envError:f,onSelectEnv:h},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var Th=36,H1=12;var F_=A(()=>{ir();vr();Ft();et();_h();bh()});import{jsxDEV as Pe}from"@opentui/react/jsx-dev-runtime";function j1(e){switch(e){case"cycle":return"\u2194";case"toggle":return"\u23FB";case"page":return"\u25B8";case"action":return"!";default:return"\xB7"}}function j_(e){let{palette:t,items:r,selected:n,locked:o,width:s,onPick:i,onActivate:a}=e,c=ge(),l=Qm(r,n),d=Qn(r,l);return Pe("box",{flexGrow:1,border:!0,borderStyle:"rounded",borderColor:t.borderActive,backgroundColor:t.panel,title:"settings",titleColor:t.primary,flexDirection:"column",overflow:"hidden",children:[Pe("box",{height:2,paddingLeft:c.pad,flexShrink:0,flexDirection:"column",overflow:"hidden",children:[Pe("text",{fg:t.primary,wrapMode:"none",children:"Preferences"},void 0,!1,void 0,this),Pe("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),Pe("box",{flexGrow:1,paddingLeft:c.pad,paddingRight:c.pad,overflow:"hidden",flexShrink:1,children:Pe(B1,{palette:t,items:r,active:l,scale:c,width:s,onPick:i,onActivate:a},void 0,!1,void 0,this)},void 0,!1,void 0,this),Pe(G1,{palette:t,item:d,scale:c},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function B1(e){let{palette:t,items:r,active:n,scale:o,width:s,onPick:i,onActivate:a}=e,c=Xs(n,U_),l=Math.max(8,s-Ph-H_-10),d=0,u=Yw(r);return Pe("scrollbox",{ref:c,focused:!1,stickyScroll:!1,scrollX:!1,style:Oe(t),children:Pe("box",{flexDirection:"column",overflow:"hidden",children:u.map((m,p)=>{let f=d;d+=m.items.length;let h=n>=f&&n<f+m.items.length;return Pe("box",{flexDirection:"column",flexShrink:0,marginBottom:p<u.length-1?Math.max(1,o.gap):0,overflow:"hidden",children:[Pe("box",{height:1,overflow:"hidden",children:Pe("text",{fg:h?t.primary:t.muted,wrapMode:"none",children:m.group},void 0,!1,void 0,this)},void 0,!1,void 0,this),m.group==="MCP"?Pe("box",{height:1,overflow:"hidden",children:Pe("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,m.items.map((g,x)=>{let y=f+x;return Pe(W1,{index:y,palette:t,item:g,active:y===n,rowH:o.rowH,valueWidth:l,onPick:()=>i(y),onActivate:()=>a(g)},g.id,!1,void 0,this)})]},m.group,!0,void 0,this)})},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function W1(e){let{palette:t,index:r,item:n,active:o,rowH:s,valueWidth:i,onPick:a,onActivate:c}=e;return Pe("box",{id:`${U_}-${r}`,height:s,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:o?t.highlight:void 0,onMouseDown:()=>{if(o){c();return}a()},children:[Pe("box",{width:2,flexShrink:0,overflow:"hidden",children:Pe("text",{fg:t.primary,children:o?"\u203A":" "},void 0,!1,void 0,this)},void 0,!1,void 0,this),Pe("box",{width:H_,flexShrink:0,overflow:"hidden",children:Pe("text",{fg:o?t.primary:t.muted,children:j1(n.kind)},void 0,!1,void 0,this)},void 0,!1,void 0,this),Pe("box",{width:Ph,flexShrink:0,overflow:"hidden",children:Pe("text",{fg:o?t.primary:t.text,wrapMode:"none",children:U(n.name,Ph)},void 0,!1,void 0,this)},void 0,!1,void 0,this),Pe("box",{flexGrow:1,overflow:"hidden",children:Pe("text",{fg:q1(t,n,o),wrapMode:"none",children:we(n.value,i)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function G1(e){let{palette:t,item:r,scale:n}=e,o=r?.id==="font",s=r?.kind==="page"?`${r.name} \u2192 dedicated page`:r?.name??"";return Pe("box",{height:U1,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:[Pe("box",{height:1,overflow:"hidden",children:Pe("text",{fg:t.primary,wrapMode:"none",children:o?`${Vw(n.steps)} ${n.label}`:s},void 0,!1,void 0,this)},void 0,!1,void 0,this),Pe("box",{height:1,overflow:"hidden",children:Pe("text",{fg:t.text,wrapMode:"word",children:r?.detail??""},void 0,!1,void 0,this)},void 0,!1,void 0,this),Pe("box",{height:1,overflow:"hidden",children:Pe("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 q1(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 Ph=14,H_=2,U1=5,U_="settings-row";var B_=A(()=>{$t();tt();Te();Lr()});import{jsxDEV as Ie}from"@opentui/react/jsx-dev-runtime";function W_(e){let{palette:t,cfg:r,google:n,bootError:o,bootErrorMissing:s=!1,step:i=0}=e,a=ge();if(o&&!r)return s?Ie(nt,{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):Ie(nt,{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=K1(r,n,c),d=l.filter((y)=>y.ok).length,u=l.length,m=u>0?Math.round(d/u*100):0,p=20,f=Math.round(m/100*p),h="\u2588".repeat(f)+"\u2591".repeat(Math.max(0,p-f)),g=m===100?t.success:m===0?t.muted:t.primary,x=l[i];return Ie(Et,{palette:t,title:"setup",scroll:!0,children:[Ie("box",{flexDirection:"row",overflow:"hidden",flexShrink:0,children:Ie("text",{fg:t.primary,children:"Developer onboarding"},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ie("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:Ie("text",{wrapMode:"none",children:[Ie("span",{fg:g,children:`[${h}]`},void 0,!1,void 0,this),Ie("span",{fg:t.text,children:` ${d}/${u} ready`},void 0,!1,void 0,this),Ie("span",{fg:t.muted,children:` (${m}%)`},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this),Ie("box",{height:1,flexShrink:0},void 0,!1,void 0,this),l.map((y,C)=>{let _=C===i;return Ie("box",{height:a.rowH,flexDirection:"row",overflow:"hidden",backgroundColor:_?t.highlight:void 0,paddingLeft:1,children:[Ie("box",{width:2,flexShrink:0,children:Ie("text",{fg:ut(t,y.ok?"OK":"WARN"),children:Ir(y.ok?"OK":"WARN")},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ie("box",{width:3,flexShrink:0,children:Ie("text",{fg:_?t.primary:t.muted,children:String(C+1).padStart(2," ")},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ie("box",{width:20,flexShrink:0,overflow:"hidden",children:Ie("text",{fg:_?t.primary:t.text,children:U(y.name,20)},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ie("box",{flexGrow:1,overflow:"hidden",children:Ie("text",{fg:_?t.text:t.muted,wrapMode:"none",children:y.detail},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},y.name,!0,void 0,this)}),Ie("box",{height:1,flexShrink:0},void 0,!1,void 0,this),x?Ie("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:Ie("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,Ie("box",{height:1,flexShrink:0},void 0,!1,void 0,this),Ie("box",{flexDirection:"row",overflow:"hidden",flexShrink:0,children:Ie(Fe,{palette:t,label:"never hard-coded",tone:"idle"},void 0,!1,void 0,this)},void 0,!1,void 0,this),Ie("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),Ie(ft,{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 K1(e,t,r=[]){let n=Object.values(e?.services??{}).flatMap((i)=>i.ports.filter((a)=>!a.auto)),o=(e?.proxy.routes??[]).some((i)=>i.auth.type.toLowerCase()==="iap"),s=Dd(e,void 0,t).project;return[{name:En[0],ok:Boolean(e?.repoRoot),detail:e?.repoRoot||"unknown"},{name:En[1],ok:Boolean(e?.project.name),detail:e?.project.name||"set project.name"},{name:En[2],ok:Boolean(s),detail:s||"optional for local-only"},{name:En[3],ok:t?.adcAvailable===!0,detail:t?.adcAvailable?"ADC available":"needed only for cloud identity / IAP"},{name:En[4],ok:!0,detail:"read from configuration; never hard-coded"},{name:En[5],ok:!o||(e?.proxy.routes??[]).every((i)=>i.auth.type.toLowerCase()!=="iap"||i.auth.audience!==""),detail:o?"IAP routes present":"no IAP routes"},{name:En[6],ok:n.length===0||Boolean(e),detail:`${n.length} fixed ports from configuration`},{name:En[7],ok:Object.keys(e?.profiles??{}).length>0,detail:`${Object.keys(e?.profiles??{}).length} profile(s)`},{name:En[8],ok:r.length===0,detail:r.length===0?"configuration is valid":r[0]??"invalid"}]}var En;var G_=A(()=>{ir();$t();tt();Te();et();En=["Repository","Environment","Google project","Authentication","Service accounts","IAP","Ports","Profiles","Validation"]});import{useEffect as V1,useState as z1}from"react";import{jsxDEV as oe}from"@opentui/react/jsx-dev-runtime";function li(e){let{palette:t,title:r,tone:n="muted",children:o}=e,s=n==="success"?t.success:n==="warning"?t.warning:n==="error"?t.error:t.border;return oe("box",{border:!0,borderStyle:"rounded",borderColor:s,title:r,titleColor:s,flexDirection:"column",flexShrink:0,paddingLeft:1,paddingRight:1,marginBottom:1,overflow:"hidden",children:o},void 0,!1,void 0,this)}function Eh(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 Oa(e){let{palette:t,cells:r,header:n,gap:o=0,stripe:s}=e;return oe("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,backgroundColor:s?t.element:void 0,children:r.map((i,a)=>oe("box",{flexDirection:"row",flexShrink:0,overflow:"hidden",children:[a>0&&o>0?oe("box",{width:o,flexShrink:0},void 0,!1,void 0,this):null,oe("box",{width:i.width,flexShrink:0,overflow:"hidden",children:oe("text",{fg:n?t.primary:Eh(t,i),wrapMode:"none",children:U(i.text,i.width)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},`${i.text}-${a}`,!0,void 0,this))},void 0,!1,void 0,this)}function q_(e){let{palette:t,width:r}=e;return oe("box",{height:1,flexShrink:0,overflow:"hidden",children:oe("text",{fg:t.border,wrapMode:"none",children:U("\u2500".repeat(Math.max(1,r)),r)},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function Da(e){let{palette:t,facts:r,width:n}=e,o=Pf(r,n),s=[{text:"what",width:o.what},{text:"reading",width:o.reading}];if(o.meter>0)s.push({text:"used",width:o.meter});return s.push({text:"meaning",width:o.meaning}),oe("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:[oe(Oa,{palette:t,header:!0,gap:Js,cells:s},void 0,!1,void 0,this),oe(q_,{palette:t,width:n},void 0,!1,void 0,this),r.map((i,a)=>{let c=i.meaning===""?[""]:dS(i.meaning,Math.max(1,o.meaning)),l=i.meter?uk(i.meter):o.meter>0?"":void 0,d=[{text:i.what,width:o.what,tone:"text"},{text:i.reading,width:o.reading,tone:i.tone??"text"}];if(o.meter>0)d.push({text:l??"",width:o.meter,tone:i.meter?i.tone??"text":"muted"});return d.push({text:c[0]??"",width:o.meaning,tone:"muted"}),oe("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:[oe(Oa,{palette:t,gap:Js,stripe:a%2===1,cells:d},void 0,!1,void 0,this),c.slice(1).map((u,m)=>{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"}),oe(Oa,{palette:t,gap:Js,stripe:a%2===1,cells:p},`${i.what}-more-${m}`,!1,void 0,this)})]},`${i.what}-${i.reading}-${a}`,!0,void 0,this)})]},void 0,!0,void 0,this)}function K_(e){let{palette:t,cfg:r,snap:n,width:o,onRefresh:s}=e,i=ge(),[,a]=z1(0);if(V1(()=>{let k=setInterval(()=>{a((N)=>{let K=N+1;if(s&&K%J1===0)s();return K})},Y1);return()=>clearInterval(k)},[s]),!r)return oe(nt,{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((k)=>pk(r.services[k])),d=ok(c,n),u=bk(d,l),m=l&&d.live>0?d.healthy/d.live:d.total>0?d.live/d.total:0,p=Ws(m,X1),f=d.failed>0?"error":l&&d.live>0&&d.healthy<d.live?"warning":"success",h=ck(o,i.pad),g=n?.session_id?lu(n.session_id):void 0,x=g?ya(Date.now()-g.getTime()):"just started",y=n?.logs.total??0,C=n?.logs.errors??0,_=ak(n?.logs.counts??{}),E=n?.proxy.routes??[],v=n?.credentials,b=(v?.entries??[]).filter((k)=>k.valid).length,S=n?.system,w=S?S.memAvailableKB??S.memFreeKB:0,D=S?fk(S.loadAvg1,S.cpuCount):void 0,O=S?hk(w,S.memTotalKB):void 0,B=[];if(S&&D&&O){B.push(D,O,{what:"Computer",reading:mk(S.platform),meaning:`on for ${ya(S.hostUptimeSec*Q1)}`,tone:"text"});let k=n?.stats_series;if(k&&k.cpu.length>1)B.push({what:"Load trend",reading:Tf(k.cpu),meaning:`${Math.round(k.interval_ms/1000)}s samples`,tone:"text"});if(k&&k.mem.length>1)B.push({what:"RAM trend",reading:Tf(k.mem),meaning:"used memory over the same window",tone:"text"})}let J=d.failed>0||D?.tone==="error"||O?.tone==="error"?"error":f==="warning"||D?.tone==="warning"||O?.tone==="warning"?"warning":"success",V=[{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"}],X=[{what:"Lines kept",reading:String(y),meaning:"log lines still in memory",tone:"text"},{what:"Error lines",reading:String(C),meaning:C>0?"open Logs and press e to see only errors":"no ERROR or FATAL lines",tone:C>0?"error":"muted"},..._.map(([k,N])=>({what:k,reading:String(N),meaning:"lines from this service",tone:"text"}))],te=[{what:"Web proxy",reading:n?.proxy.running?"on":"off",meaning:n?.proxy.running?`${E.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?vk(v.backend):"nothing stored yet",tone:v&&b<v.entries.length?"warning":v?"success":"muted"}];return oe(Et,{palette:t,title:"stats",scroll:!0,children:[oe(li,{palette:t,title:`Your services ${d.live} of ${d.total} started`,tone:J,children:[oe(Da,{palette:t,facts:u,width:h},void 0,!1,void 0,this),oe("box",{height:1,flexShrink:0},void 0,!1,void 0,this),oe("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:oe("text",{wrapMode:"none",children:[oe("span",{fg:t.muted,children:U(l?"Ready":"Started",Pf(u,h).what)},void 0,!1,void 0,this),oe("span",{children:" ".repeat(Js)},void 0,!1,void 0,this),oe("span",{fg:Eh(t,{text:"",width:1,tone:f}),children:`[${p}]`},void 0,!1,void 0,this),oe("span",{fg:Eh(t,{text:"",width:1,tone:f}),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?oe(li,{palette:t,title:"This computer",tone:D?.tone==="error"||O?.tone==="error"?"error":D?.tone==="warning"||O?.tone==="warning"?"warning":"success",children:oe(Da,{palette:t,facts:B,width:h},void 0,!1,void 0,this)},void 0,!1,void 0,this):null,oe(li,{palette:t,title:"This session",children:oe(Da,{palette:t,facts:V,width:h},void 0,!1,void 0,this)},void 0,!1,void 0,this),oe(li,{palette:t,title:"Each service",tone:d.failed>0?"error":l&&d.live>0&&d.healthy<d.live?"warning":"success",children:d.total===0?oe("text",{fg:t.muted,children:"No services in this project yet."},void 0,!1,void 0,this):oe("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:[oe(Z1,{palette:t,names:c,snap:n,width:h},void 0,!1,void 0,this),oe("box",{height:1,flexShrink:0},void 0,!1,void 0,this),oe("text",{fg:t.muted,wrapMode:"none",children:U("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),oe(li,{palette:t,title:"Log lines",tone:C>0?"warning":"muted",children:oe(Da,{palette:t,facts:X,width:h},void 0,!1,void 0,this)},void 0,!1,void 0,this),oe(li,{palette:t,title:"Other parts",children:oe(Da,{palette:t,facts:te,width:h},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function Z1(e){let{palette:t,names:r,snap:n,width:o}=e,s=lk(o),i=[{text:"service",width:s.name},{text:"status",width:Kr}];if(s.health)i.push({text:"check",width:on});if(s.cpu)i.push({text:"cpu",width:xa});if(s.mem)i.push({text:"ram",width:wa});if(s.up)i.push({text:"up",width:ba});if(s.rst)i.push({text:"restarts",width:ld});if(s.pid)i.push({text:"pid",width:Tn});return oe("box",{flexDirection:"column",overflow:"hidden",flexShrink:0,children:[oe(Oa,{palette:t,header:!0,gap:Ut,cells:i},void 0,!1,void 0,this),oe(q_,{palette:t,width:o},void 0,!1,void 0,this),r.map((a,c)=>oe(nH,{palette:t,name:a,rt:n?.services[a],cols:s,stripe:c%2===1},a,!1,void 0,this))]},void 0,!0,void 0,this)}function rH(e){if(e===void 0)return"muted";if(e>tH)return"error";if(e>eH)return"warning";return"text"}function nH(e){let{palette:t,name:r,rt:n,cols:o,stripe:s}=e,i=gk(n),a=yk(n),c=n?.restarts??0,l=[{text:r,width:o.name,color:Ve(r,t)},{text:i,width:Kr,color:ut(t,n?.state??"STOPPED")}];if(o.health)l.push({text:a,width:on,color:ut(t,n?.health??"UNKNOWN")});if(o.cpu)l.push({text:n?.cpuPercent!==void 0?Qw(n.cpuPercent):"\u2014",width:xa,tone:rH(n?.cpuPercent)});if(o.mem)l.push({text:n?.memoryKB!==void 0?va(n.memoryKB):"\u2014",width:wa,tone:"text"});if(o.up)l.push({text:ik(n),width:ba,tone:"text"});if(o.rst)l.push({text:String(c),width:ld,tone:c>0?"warning":"muted"});if(o.pid)l.push({text:n?.pid?String(n.pid):"\u2014",width:Tn,tone:"muted"});return oe("box",{flexDirection:"column",flexShrink:0,overflow:"hidden",children:[oe(Oa,{palette:t,gap:Ut,stripe:s,cells:l},void 0,!1,void 0,this),n?.last_error?oe("box",{height:1,flexDirection:"row",overflow:"hidden",flexShrink:0,children:oe("text",{fg:t.error,wrapMode:"none",children:U(` ${n.last_error}`,Math.max(24,o.name+Ut+Kr+on))},void 0,!1,void 0,this)},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)}var Y1=1000,J1=5,X1=16,Q1=1000,eH=80,tH=100;var V_=A(()=>{ir();$t();tt();yr();Ft();dd();Te();et()});import{useRenderer as oH,useTerminalDimensions as sH}from"@opentui/react";import{useCallback as dn,useEffect as iH,useMemo as Ah,useRef as Mr,useState as Ge}from"react";import{jsxDEV as fe,Fragment as aH}from"@opentui/react/jsx-dev-runtime";function z_({controller:e,tui:t,onQuit:r,onDown:n,onAttached:o,bootError:s,bootErrorMissing:i=!1,terminalBackground:a,workspace:c}){let{saveTuiPreferences:l,resolveTuiOverridePath:d,userTuiConfigPath:u,validateConfigText:m,freePort:p,readTextFile:f,writeTextFile:h,createStarterConfig:g,validate:x}=c,[y,C]=Ge(e),[_,E]=Ge(s),v=oH(),{width:b,height:S}=sH(),[w,D]=Ge(y?"dashboard":"setup"),[O,B]=Ge("none"),[J,V]=Ge(""),[X,te]=Ge("commands"),[k,N]=Ge(0),[K,ue]=Ge(0),[We,ie]=Ge(0),[W,le]=Ge([]),[re,pt]=Ge(),[wt,ae]=Ge(_??""),[Le,Ne]=Ge({}),[Je,St]=Ge(),[st,At]=Ge(rS(y?.cfg)),[Se,kt]=Ge(""),[gt,Ct]=Ge(!1),[br,it]=Ge(),[It,Xe]=Ge(),[Rt,ar]=Ge(0),[cr,Yr]=Ge(),[lr,Zt]=Ge(),xr=Mr(null),wr=Mr(null),Sr=Mr(null),Bt=Mr(null),de=Mr(null),Z=Mr(null),L=Mr(null),ve=Mr(null),mt=Mr(null),er=Mr(""),[at,Ee]=Ge("quit"),pn=Mr(void 0),P=Mr(0),[F,tr]=Ge(y?.cfg),lo=y?.previousPersisted,[Nr,dr]=Ge(void 0),mn=wR({tui:t,controller:y,resolveTuiOverridePath:d,terminalBackground:a,userTuiConfigPath:u,snap:re,setStatus:ae,saveTuiPreferences:l,setPaletteIndex:N,setOverlay:B,setConfirmKind:Ee,setScreen:D,setSelected:ie,screen:w}),{themeName:fn,setThemeName:uo,fontSize:Vd,prefsLocked:zd,palette:he,rootBackground:An,settingRows:po,persistPrefs:Ma,persistTheme:di,activateSetting:Yd}=mn,ce=lR({controller:y,workspace:c,cfg:F,snap:re,screen:w,configReloadError:Nr,setSnap:pt,setStatus:ae}),{google:yt,doctor:_t,doctorLoading:hn,doctorError:Na,doctorProgress:ui,setDoctorTick:pi}=ce,Mh=t.keybinds.copy??yn(),mo=Ah(()=>Object.keys(F?.services??{}).sort(),[F]),In=dn(async()=>{if(!y)return;try{let Q=await y.status();if(pt(Q),Q.profile&&st==="")At(Q.profile);return Q}catch(Q){ae(I(Q));return}},[y,st]),Jd=hR({controller:y,tui:t,names:mo,screen:w,refresh:In,setStatus:ae}),{logs:Nh,setLogs:xT,paused:$h,errorOnly:Xd,setErrorOnly:Qd,showSystemLogs:Fh,logSearch:Hh,setLogSearch:Uh,logSearchFocused:wT,setLogSearchFocused:ST,logFollow:jh,logSince:kT,logService:Bh,setLogService:Zd,logServiceB:CT,setLogServiceB:RT,splitLogs:eu,splitFocus:tu,setSplitFocus:_T,logSliceB:Wh,logWindowB:Gh,logSelectedB:qh,logPinnedB:TT,pinLogViewB:PT,logServices:ET,logShowTimestamps:AT,logShowMeta:IT,logSource:LT,logRegex:Kh,logWrap:Vh,logPinned:zh,logSelected:Yh,logsFullscreen:ru,dashboardLogCursor:DT,setDashboardLogCursor:OT,logFacets:Jh,logSources:Xh,filteredLogs:Qh,filteredLogsB:MT,logWindow:$a,logSlice:Fa,pinLogView:nu,applyLogCursorA:NT,applyLogCursorB:$T,jumpToLatestLogs:Zh}=Jd,ss=Ah(()=>{if(X==="tasks")return Sf(Object.keys(F?.tasks??{}),J,"tasks","run this task");if(X==="services")return Sf(Object.keys(F?.services??{}),J,"services","exec in this service");return ek(J,{services:Object.keys(F?.services??{}),tasks:Object.keys(F?.tasks??{})})},[F,J,X]);iH(()=>{ue(0)},[J]);let ou=RS(w,{doctor:_t?.checks.length??0,settings:po.length,profiles:Object.keys(F?.profiles??{}).length,services:mo.length,logs:eu&&tu===1?Wh.length:Fa.length,mcp:fC(),config:Object.keys(F?.tasks??{}).length}),eg=w==="logs"?eu&&tu===1?qh:Yh:We,Jr=ou<=0?Math.max(0,eg):Math.max(0,Math.min(eg,ou-1)),FT=w==="detail"?Se:w==="services"?mo[Jr]??"":"",{inspectorEnv:tg,inspectorEnvStatus:rg,inspectorEnvError:ng,resolveEnvironment:HT}=PR({controller:y,cfg:F,envService:FT}),su=Ah(()=>It?gd(It.tree):[],[It]),UT=su[yd(Rt,su.length)]?.span,is=dn(()=>{if(B("none"),V(""),te("commands"),ST(!1),pn.current)clearTimeout(pn.current)},[]),iu=dn((Q)=>{le((ct)=>ct.includes(Q)?ct.filter((tP)=>tP!==Q):[...ct,Q])},[]),og=vR({tui:t,controller:y,cfg:F,persistPrefs:Ma,snap:re,refresh:In,setStatus:ae}),{mcpPortDraft:jT,mcpPort:BT,toggleMcpTool:WT,toggleMcp:GT,copyMcpSnippet:qT}=og;sR({controller:y,cfg:F,paused:$h,logSince:kT,refresh:In,setLogs:xT,setCfg:tr,setConfigReloadError:dr,setStatus:ae});let Ha=dn((Q)=>{kt(Q),D("detail")},[]),au=dn((Q)=>{if(Q===""||!y)return;y.getTrace(Q).then((ct)=>{ar(0),Xe(ct),B("trace")}).catch((ct)=>{ae(I(ct))})},[y]),sg=dn((Q)=>{if(Q!==void 0)ar(Q);B("span-details")},[]),ig=dn((Q)=>{let ct=Q.value!==""?Q.value:Q.required?"(required, not set)":"(empty)";Zt({title:Q.key,body:ct}),B("scroll-text")},[]),cu=uR({controller:y,workspace:c,cfg:F,snap:re,refresh:In,setStatus:ae,setOverlay:B}),{plan:Ua,planInitiallyRunning:KT,planBusy:VT,lifecycle:ag}=cu,gn=RR({workspace:c,renderer:v,setOverlay:B,setStatus:ae,setScreen:D,onAttached:o,setController:C,setCfg:tr,setBootError:E}),{configEditRef:zT,configEditText:YT,configEditError:JT,setConfigEditError:XT,openConfigBuffer:cg,saveConfigBuffer:QT}=nR({cfg:F,readTextFile:f,setOverlay:B,setStatus:ae,validateConfigText:m,writeTextFile:h,controller:y,setConfirmKind:Ee,refresh:In}),lg=dn(async()=>{let Q=v.getSelection()?.getSelectedText()??"";if(Q.trim()===""){ae("Nothing selected \u2014 drag to highlight text");return}try{await fd(Q);let ct=Q.split(`
|
|
2355
|
+
`).length;ae(ct===1?"Copied selection":`Copied ${ct} selected lines`)}catch(ct){ae(I(ct))}},[v]),{runCommand:ja}=ZC({setOverlay:B,setQuery:V,setSlashPicker:te,checked:W,setConfirmKind:Ee,setStatus:ae,persistTheme:di,setPaletteIndex:N,themeName:fn,setScreen:D,setSelected:ie,screen:w,renderer:v,cfg:F,setScrollText:Zt,reveal:gt,controller:y,refresh:In,profile:st,setReveal:Ct,resolveEnvironment:HT,openDetail:Ha,copySelection:lg,lastExportPath:er,openConfigBuffer:cg,onDown:n??((Q)=>r(Q)),workspace:c,logView:Jd,diagnostics:ce,lifecycleActions:cu}),dg=dn(()=>{let Q=Ys(ss,K);if(X==="tasks"){if(!Q){ae(ss.length===0?"no tasks configured":"pick a task");return}te("commands");let ct=sn("run");if(ct)ja(ct,[Q.name]);return}if(X==="services"){if(!Q){ae(ss.length===0?"no services configured":"pick a service");return}te("commands"),V(`exec ${Q.name} -- `);return}if(!Q){ae(J.trim()===""?"pick a command from the list":`unknown command /${J}`),is();return}ja(Q,ZS(Q,J))},[is,ss,J,ja,K,X]),ug=dn((Q)=>{di(Q),is()},[is,di]);OC({tui:t,controller:y,cfg:F,snap:re,ui:{screen:w,overlay:O,onQuit:r,onDown:n??((Q)=>r(Q)),closeOverlay:is,confirmKind:at,confirmDetail:Le,portTarget:Je,profile:st,listCursor:Jr,names:mo,listCount:ou,openDetail:Ha,copySelection:lg,height:S,paletteIndex:k,applyTheme:ug,runCommand:ja,saveConfigBuffer:QT,filtered:ss,slashIndex:K,submitSlash:dg,advanceWizard:()=>void gn.advanceWizard(),bootErrorMissing:i,bootError:_,createStarterConfig:g,startWizard:gn.startWizard,toggleChecked:iu,checked:W,detailName:Se,refresh:In,openConfigBuffer:cg,setOverlay:B,setConfirmKind:Ee,setConfirmDetail:Ne,setPortTarget:St,setLogDetail:it,logDetail:br,openTrace:au,openSpanLogs:sg,traceSpanCount:su.length,setTraceSpanIndex:ar,setLogSearch:Uh,setProfile:At,setStatus:ae,setPaletteIndex:N,setSlashIndex:ue,setQuery:V,setSlashPicker:te,setScreen:D,setSelected:ie,setChecked:le,setConfigEditError:XT,freePort:p},logView:Jd,lifecycleActions:cu,mcp:og,preferences:mn,diagnostics:ce,refs:{interruptArmedAt:P,leaderTimer:pn,logDetailsScrollRef:Bt,traceScrollRef:de,traceDetailScrollRef:Z,scrollTextScrollRef:ve,routeDetailsScrollRef:L,planScrollRef:mt,helpScrollRef:wr,configScrollRef:xr,detailScrollRef:Sr}});let pg=DS(at,st,Le),ZT=new Set(Ua?.waves.flat()??[]),eP=Ua===void 0||ag==="stop"?"":Object.entries(re?.services??{}).find(([Q,ct])=>ZT.has(Q)&&ct.state==="FAILED")?.[0]??"";return fe(ef.Provider,{value:ha(Vd),children:fe("box",{flexDirection:"column",width:b,height:S,backgroundColor:An,overflow:"hidden",children:[ru?null:fe(aH,{children:[fe(Sk,{palette:he,cfg:F,snap:re,google:yt,profile:st,reveal:gt,width:b},void 0,!1,void 0,this),fe(kk,{palette:he,screen:w,width:b,onSelect:D},void 0,!1,void 0,this),Nr?fe("box",{height:1,paddingLeft:1,backgroundColor:he.panel,overflow:"hidden",children:fe("text",{fg:he.error,wrapMode:"none",children:`\u26A0 configuration reload failed: ${Nr}`},void 0,!1,void 0,this)},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),fe("box",{flexGrow:1,overflow:"hidden",children:[w==="dashboard"?fe(y_,{palette:he,cfg:F,snap:re,logs:Nh,names:mo,selected:Jr,checked:W,width:b,followTick:jh,logService:Bh,errorOnly:Xd,showSystemLogs:Fh,onOpen:Ha,onSelectIndex:ie,selectedLog:DT,onToggle:iu,logSources:Xh,onFilterService:Zd,onToggleErrors:()=>Qd((Q)=>!Q),onShowErrors:()=>{Zd(""),Qd(!0)},wrapMode:Vh,view:Fa,follow:!zh,onLeaveLatest:nu,onPickLog:(Q)=>{let ct=Fa[Q];if(!ct)return;OT(Q),it(ct),nu()},viewStart:$a.start,viewTotal:Qh.length,newer:$a.newer,onJumpLatest:Zh,facets:Jh,leftover:lo,search:Hh,regex:Kh},void 0,!1,void 0,this):null,w==="services"?fe($_,{palette:he,cfg:F,names:mo,snap:re,selected:Jr,checked:W,width:b,reveal:gt,onOpen:Ha,onSelectIndex:ie,onToggle:iu,resolvedEnv:tg,envStatus:rg,envError:ng,onSelectEnv:ig},void 0,!1,void 0,this):null,w==="detail"?fe(N_,{palette:he,cfg:F,snap:re,name:Se,reveal:gt,width:b,envScrollRef:Sr,resolvedEnv:tg,envStatus:rg,envError:ng,onSelectEnv:ig},void 0,!1,void 0,this):null,w==="logs"?fe(S_,{palette:he,logs:Nh,names:mo,logSources:Xh,service:Bh,errorOnly:Xd,showSystemLogs:Fh,search:Hh,searchFocused:wT,followTick:jh,width:b,onSearch:Uh,onService:Zd,onToggleErrors:()=>Qd((Q)=>!Q),source:LT,regex:Kh,services:ET,showTimestamps:AT,showMeta:IT,view:Fa,wrapMode:Vh,selected:Yh,follow:!zh,newer:$a.newer,viewStart:$a.start,viewTotal:Qh.length,onSelect:NT,fullscreen:ru,onLeaveLatest:nu,onJumpLatest:Zh,facets:Jh,split:eu,splitFocus:tu,serviceB:CT,viewB:Wh,selectedB:qh,followB:!TT,newerB:Gh.newer,viewStartB:Gh.start,viewTotalB:MT.length,onServiceB:RT,onSelectB:$T,onLeaveLatestB:PT,onFocusPane:_T},void 0,!1,void 0,this):null,w==="auth"?fe(r_,{palette:he,cfg:F,google:yt,identity:re?.identity},void 0,!1,void 0,this):null,w==="credentials"?fe(l_,{palette:he,credentials:re?.credentials},void 0,!1,void 0,this):null,w==="proxy"?fe(L_,{palette:he,cfg:F,snap:re,width:b,onSelectRoute:(Q)=>{Yr(Q),B("route-details")},onOpenTrace:au},void 0,!1,void 0,this):null,w==="mcp"?fe(hC,{palette:he,snap:re,port:BT,portDraft:jT,selected:Jr,onPick:ie,onToggle:()=>{GT()},onCopy:qT,onToggleTool:(Q)=>{WT(Q)}},void 0,!1,void 0,this):null,w==="doctor"?fe(b_,{palette:he,report:_t,loading:hn,progress:ui,error:Na,selected:Jr,onPick:ie,onReload:()=>{pi((Q)=>Q+1),ae("Re-running doctor")}},void 0,!1,void 0,this):null,w==="stats"?fe(K_,{palette:he,cfg:F,snap:re,width:b,onRefresh:In},void 0,!1,void 0,this):null,w==="config"?fe(a_,{palette:he,cfg:F,width:b,selectedTask:Jr,scrollRef:xr},void 0,!1,void 0,this):null,w==="profiles"?fe(R_,{palette:he,cfg:F,snap:re,profile:st,selected:Jr,onPick:ie},void 0,!1,void 0,this):null,w==="setup"?fe(W_,{issues:F?x(F):["configuration not loaded"],palette:he,cfg:F,google:yt,bootError:_,bootErrorMissing:i,step:Jr},void 0,!1,void 0,this):null,w==="settings"?fe(j_,{palette:he,items:po,selected:Jr,locked:zd,width:b,onPick:ie,onActivate:Yd},void 0,!1,void 0,this):null]},void 0,!0,void 0,this),O==="slash"?fe(KR,{palette:he,items:ss,query:J,selected:K,title:X==="tasks"?"tasks":X==="services"?"services":"commands",onQuery:V,onSubmit:dg},void 0,!1,void 0,this):null,O==="themes"?fe(XR,{palette:he,themeName:fn,selected:k%Pt.length,termW:b,termH:S,onIndex:N,onPreview:uo,onPick:ug},void 0,!1,void 0,this):null,O==="help"?fe(bC,{palette:he,termW:b,termH:S,copyKey:Mh,scrollRef:wr},void 0,!1,void 0,this):null,O==="leader"?fe(MR,{palette:he,termW:b,termH:S},void 0,!1,void 0,this):null,O==="confirm"?fe(LR,{palette:he,title:pg.title,body:pg.body,kind:at,termW:b,termH:S},void 0,!1,void 0,this):null,O==="setup-wizard"?fe(kR,{palette:he,termW:b,termH:S,step:gn.step,answers:gn.answers,repo:gn.repo,draft:gn.draft,authStatus:gn.authStatus,onDraft:gn.setDraft,onSubmit:()=>void gn.advanceWizard()},void 0,!1,void 0,this):null,O==="log-details"?fe($R,{palette:he,event:br,termW:b,termH:S,scrollRef:Bt,onViewTrace:au},void 0,!1,void 0,this):null,O==="trace"||O==="span-details"?fe(ZR,{palette:he,trace:It?.tree,selected:Rt,onSelect:ar,onOpenLogs:sg,termW:b,termH:S,scrollRef:de},void 0,!1,void 0,this):null,O==="span-details"?fe(e_,{palette:he,span:UT,records:It?.events,termW:b,termH:S,scrollRef:Z},void 0,!1,void 0,this):null,O==="route-details"?fe(BR,{palette:he,route:cr,termW:b,termH:S,scrollRef:L},void 0,!1,void 0,this):null,O==="scroll-text"&&lr?fe(GR,{palette:he,title:lr.title,body:lr.body,termW:b,termH:S,scrollRef:ve},void 0,!1,void 0,this):null,O==="config-edit"&&F?fe(AR,{palette:he,path:F.configPath,initialValue:YT,error:JT,termW:b,termH:S,textareaRef:zT},void 0,!1,void 0,this):null,O==="plan"&&Ua?fe(UR,{palette:he,plan:Ua,snap:re,busy:VT,failed:eP,kind:ag,termW:b,termH:S,scrollRef:mt,initiallyRunning:KT,onDismiss:is},void 0,!1,void 0,this):null,ru?null:fe(Ck,{palette:he,screen:w,overlay:O,status:wt,paused:$h,errorOnly:Xd,width:b,copyKey:Mh},void 0,!1,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this)}var Y_=A(()=>{ne();ir();Af();$t();vr();Zo();eo();Ft();vd();MC();eR();oR();iR();dR();pR();gR();bR();SR();_R();ER();IR();DR();nh();NR();FR();jR();WR();qR();lh();VR();QR();t_();n_();c_();d_();v_();x_();k_();Ta();__();D_();_h();F_();B_();G_();V_();Lr();et();qr()});var J_={};sP(J_,{renderApp:()=>Lh,runTui:()=>Ih,runTuiWithController:()=>dH});import{createCliRenderer as cH}from"@opentui/core";import{createRoot as lH}from"@opentui/react";import{jsxDEV as uH}from"@opentui/react/jsx-dev-runtime";async function dH(e,t){let r=e.loadTuiConfig(t.cfg.repoRoot,t.cfg.ui.keymap);await Lh(e,t,r)}async function Ih(e,t){let r,n,o=!1;try{r=await e.openTui("",t)}catch(i){n=I(i),o=Sc(i,ko)}let s=e.loadTuiConfig(r?.cfg.repoRoot??process.cwd(),r?.cfg.ui.keymap);await Lh(e,r,s,n,o)}async function Lh(e,t,r=e.loadTuiConfig(t?.cfg.repoRoot??process.cwd(),t?.cfg.ui.keymap),n,o=!1){Ba();let s=hg(),i=await cH({exitOnCtrlC:!1,useMouse:r.mouse,enableMouseMovement:r.mouse,useKittyKeyboard:{}}),a=lH(i),c=null;try{c=(await i.getPalette({timeout:120,size:16})).defaultBackground}catch{}await new Promise((l)=>{let d=(m)=>{let p=()=>{a.unmount(),s(),i.destroy(),l(),process.exit(0)};if(!t){p();return}t.close({detach:m,shutdownSupervisor:!0}).finally(p)},u=(m)=>{let p=()=>{a.unmount(),s(),i.destroy(),l(),process.exit(0)};if(!t){p();return}t.shutdown({stopServices:!m}).then(()=>t?.close({detach:!0})).finally(p)};a.render(uH(z_,{workspace:$w(e),controller:t,tui:r,onQuit:d,onDown:u,onAttached:(m)=>{t=m},bootError:n,bootErrorMissing:o,terminalBackground:c},void 0,!1,void 0,this))})}var Dh=A(()=>{Fw();ne();Y_()});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 Xa,readFileSync as za}from"fs";import{dirname as NP,isAbsolute as Rg,join as Ja,resolve as $P}from"path";Wa();Wa();import{spawnSync as mP}from"child_process";import{randomBytes as pu}from"crypto";import{copyFileSync as fP,existsSync as Ln,mkdirSync as hP,readdirSync as gP,readFileSync as mu,renameSync as fu,statSync as yP,unlinkSync as du,writeFileSync as vP}from"fs";import{homedir as gg}from"os";import{basename as bP,dirname as uu,isAbsolute as xP,join as Lt,resolve as wP}from"path";var SP=448,kP=384;function mi(e,t){let r=e.startsWith("~/")?Lt(gg(),e.slice(2)):e;return xP(r)?r:wP(t,r)}function Cr(){let e=process.env.DEVCTL_HOME;if(e&&e!=="")return e;return Lt(gg(),".devctl")}function Xr(e){hP(e,{recursive:!0,mode:SP})}function as(e){let t=Lt(Cr(),"state",kr(e)),r=Lt(Cr(),"sessions",kr(e));if(!Ln(t)&&Ln(r)){Xr(uu(t));try{fu(r,t)}catch{Xr(t);for(let n of["state.json","devctl.lock","devctl.sock"]){let o=Lt(r,n);if(Ln(o))fP(o,Lt(t,n))}}}return Xr(t),t}function Ga(){let e=Lt(Cr(),"logs");return Xr(e),e}function qa(){let e=Lt(Cr(),"exports");return Xr(e),e}function $r(){let e=Lt(Cr(),"credentials");return Xr(e),e}function Ka(e,t=process.platform){if(t==="win32")return`\\\\.\\pipe\\devctl-${kr(e)}`;return Lt(as(e),"devctl.sock")}function CP(e){return Lt(as(e),"devctl.lock")}function yg(e){return Lt(as(e),"state.json")}function fi(e){return Lt(as(e),"bootstrap.log")}var RP=5,vg="bootstrap-";function bg(e){let t=fi(e);if(!Ln(t))return;let r=as(e),n=Lt(r,`${vg}${hu()}.log`);try{fu(t,n)}catch{return}_P(r)}function _P(e){let t;try{t=gP(e).filter((n)=>n.startsWith(vg)&&n.endsWith(".log"))}catch{return}let r=t.map((n)=>{let o=Lt(e,n);try{return{path:o,mtime:yP(o).mtimeMs}}catch{return}}).filter((n)=>n!==void 0).sort((n,o)=>o.mtime-n.mtime);for(let n=RP;n<r.length;n++)try{du(r[n].path)}catch{}}function Fr(e,t){Xr(uu(e));let r=Lt(uu(e),`.${bP(e)}.tmp-${process.pid}-${pu(4).toString("hex")}`);vP(r,t,{mode:kP}),fu(r,e)}function hu(e=new Date){return`${e.toISOString().slice(0,19).replace(/:/g,"-")+"Z"}-${pu(3).toString("hex")}`}function Dn(e){return gu(yg(e))}function gu(e){if(!Ln(e))return;try{let t=JSON.parse(mu(e,"utf8"));if(Array.isArray(t.processes)&&typeof t.repo_root==="string")return t;return}catch{return}}function xg(e,t){Fr(yg(e),`${JSON.stringify(t,null,2)}
|
|
2356
|
+
`)}function wg(e,t){let r=CP(e);if(Ln(r)){let n=TP(r);if(n&&ur(n.pid))throw Error(`supervisor already running (pid ${n.pid})`);try{du(r)}catch{}}return Fr(r,JSON.stringify({pid:process.pid,socket:t})),{release:()=>{try{du(r)}catch{}}}}function TP(e){if(!Ln(e))return;try{let t=JSON.parse(mu(e,"utf8"));if(typeof t.pid==="number"&&typeof t.socket==="string")return{pid:t.pid,socket:t.socket}}catch{return}return}function ur(e){if(e<=0)return!1;if(process.platform==="win32")return PP(e);try{return process.kill(e,0),!0}catch{return!1}}function yu(e){try{let t=mP("cmd.exe",["/d","/c",`tasklist /FO CSV /NH /FI "PID eq ${e}"`],{encoding:"buffer",windowsHide:!0,timeout:5000});return EP(t.stdout)}catch{return""}}function PP(e){try{return process.kill(e,0),!0}catch{}let t=yu(e).toLowerCase();if(t===""||t.includes("no tasks")||t.includes("no matching"))return!1;return t.includes(String(e))}function EP(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 vu(){return pu(24).toString("hex")}function AP(e){return Lt(as(e),"mcp-token")}function Sg(e){let t=AP(e);if(Ln(t)){let n=mu(t,"utf8").trim();if(n!=="")return n}let r=vu();return Fr(t,r),r}Va();Va();var FP="OPENCODE_TUI_CONFIG";function Qa(){return Ja(Cr(),"tui.json")}function Za(e=""){for(let t of[kg,FP]){let r=process.env[t];if(!r||r==="")continue;let n=Rg(r)?r:$P(e||process.cwd(),r);if(Xa(n))return n}return}function HP(e,t){if(!t||Object.keys(t).length===0)return e;return{...e,keybinds:{...e.keybinds,...t}}}function ds(e,t){let r=HP(Cg(),t),n=Za(e);if(n)return wu(r,Ya(za(n,"utf8")),n);let o=UP(e),s=o?wu(r,Ya(za(o,"utf8")),o):r,i=Qa();if(!Xa(i)||o===i)return s;return wu(s,Ya(za(i,"utf8")),i)}function _g(e){let t=Qa(),r={};if(Xa(t)){let o=Ya(za(t,"utf8"));if(ho(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 Fr(t,`${JSON.stringify(n,null,2)}
|
|
2357
|
+
`),t}function UP(e){let t=Za(e);if(t)return t;let r=[e||process.cwd(),Ja(e||process.cwd(),".devctl"),Cr()],n=["tui.json","tui.jsonc"];for(let o of r)for(let s of n){let i=Ja(o,s);if(Xa(i))return i}return}function wu(e,t,r){let n=ho(t)?t:{},o={...e.keybinds};if(ho(n.keybinds)){for(let[c,l]of Object.entries(n.keybinds))if(typeof l==="string")o[c]=l}let s={...e.cursor};if(ho(n.cursor)){if(BP(n.cursor.style))s.style=n.cursor.style;if(typeof n.cursor.blinking==="boolean")s.blinking=n.cursor.blinking}let i={...e.scroll_acceleration};if(ho(n.scroll_acceleration)&&typeof n.scroll_acceleration.enabled==="boolean")i.enabled=n.scroll_acceleration.enabled;let a={...e.attention,sounds:{...e.attention.sounds}};if(ho(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(ho(n.attention.sounds)){for(let[c,l]of Object.entries(n.attention.sounds))if(typeof l==="string")a.sounds[c]=jP(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:i,diff_style:n.diff_style==="stacked"?"stacked":"auto",cursor:s,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 jP(e,t){if(e.startsWith("file://")||Rg(e))return e;return Ja(NP(t),e)}function BP(e){return e==="block"||e==="underline"||e==="line"||e==="default"}function ho(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function Ya(e){let t=e.replace(/\/\*[\s\S]*?\*\//g,"").replace(/^\s*\/\/.*$/gm,"");return JSON.parse(t)}import{existsSync as yL,readFileSync as vL,writeFileSync as bL}from"fs";import{spawn as Xu}from"child_process";import{createWriteStream as UE,existsSync as So,mkdirSync as jE,readdirSync as Ti,readFileSync as ep,rmSync as BE,statSync as Zu,writeFileSync as by}from"fs";import{dirname as xy,join as bn}from"path";en();en();var WE=50000,tp="session-",wy="FORMAT",Sy="jsonl";function vy(e){return Buffer.from(JSON.stringify(e),"utf8").toString("base64url")}function GE(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 Qu(e,t){let r={...e};return r[t]=void 0,r}class rp{events=[];eventStart=0;nextSeq=1;max;bus;detector;persistDir;persist;sessionID;streams=new Map;lastWrite=new Map;parsers=[];constructor(e,t,r,n,o,s,i=0,a=0){this.max=e>0?e:WE,this.bus=t,this.detector=r,this.sessionID=s;let c=o===""||o.startsWith("~/")?Ga():o;if(this.persist=n&&s!=="",this.persistDir=this.persist?bn(c,`${tp}${s}`):"",this.persist)jE(this.persistDir,{recursive:!0,mode:448}),by(bn(this.persistDir,wy),`${Sy}
|
|
2358
|
+
`,{mode:384}),QE(c,i,a)}sessionDir(){return this.persistDir}setParsers(e){this.parsers=e}append(e){let t=e.body!==void 0||e.source==="otlp",r=ly(e.message??(typeof e.body==="string"?e.body:"")),n=t?qE(e):this.parseLine(r),o=Bu(e,n,this.nextSeq);this.nextSeq+=1;let s=this.detector?mc(this.detector,o):o;if(this.events.length<this.max)this.events.push(s);else this.events[this.eventStart]=s,this.eventStart=(this.eventStart+1)%this.max;if(this.bus?.publish(ke(us,s.service,{event:s,level:s.severityText})),this.persist){let i=`${JSON.stringify(s)}
|
|
2359
|
+
`,a=XE(s.service),c=this.streamFor(a);this.lastWrite.set(a,new Promise((l)=>{c.write(i,()=>l())}))}return s}async flush(){await Promise.all([...this.lastWrite.values()])}parseLine(e){let t=qu(e);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=UE(bn(this.persistDir,`${e}.jsonl`),{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=Ju(e),r=[];return this.forEachEvent((n)=>{if(t(n))r.push(n)}),r}queryPage(e,t={}){let r=gy(t.limit),n=t.cursor?GE(t.cursor):void 0,o=n!==void 0&&n.session!==this.sessionID,s=o?void 0:n,i=s?t.direction??"backward":"backward",a=Ju(e),c=[];this.forEachEvent((f)=>{if(a(f))c.push(f)});let l;if(!s)l=c.slice(Math.max(0,c.length-r));else if(i==="forward")l=c.filter((f)=>f.seq>s.seq).slice(0,r);else{let f=c.filter((h)=>h.seq<s.seq);l=f.slice(Math.max(0,f.length-r))}let d=l[0]?.seq,u=l[l.length-1]?.seq,m=d!==void 0&&c.some((f)=>f.seq<d),p=u!==void 0&&c.some((f)=>f.seq>u);return{events:l,prevCursor:vy({session:this.sessionID,seq:d??s?.seq??0}),nextCursor:vy({session:this.sessionID,seq:u??s?.seq??this.nextSeq-1}),hasNext:p,hasPrev:m,sessionChanged:o}}queryFacets(e){let t=Qu(e,"services"),r=Qu(e,"level"),n=Qu(e,"source"),o=Yu(e),s=0,i={},a={},c={};return this.forEachEvent((l)=>{if(!o(l))return;if(bs(e,l))s+=1;if(bs(t,l))i[l.service]=(i[l.service]??0)+1;if(bs(r,l))a[l.severityText]=(a[l.severityText]??0)+1;if(bs(n,l))c[l.source]=(c[l.source]??0)+1}),{total:s,byService:i,byLevel:a,bySource:c}}snapshot(){let e={},t=0;return this.forEachEvent((r)=>{if(e[r.service]=(e[r.service]??0)+1,wi(r.severityNumber))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){np(e,this.query(t))}}function qE(e){return{body:e.body,attributes:e.attributes,severityNumber:e.severityNumber,severityText:e.severityText??e.level,traceId:e.traceId,spanId:e.spanId,traceFlags:e.traceFlags,timeUnixNano:e.timeUnixNano,observedTimeUnixNano:e.observedTimeUnixNano,scope:e.scope,resource:e.resource,raw:e.raw,message:e.message,request_id:e.request_id}}function ky(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 KE(e=new Date){let t=e.toISOString().replace(/[:.]/g,"-");return bn(qa(),`devctl-logs-${t}.jsonl`)}function Cy(e=""){if(e==="")return KE();return mi(e,process.cwd())}function np(e,t){Xr(xy(e));let r=t.map((n)=>JSON.stringify(n));by(e,`${r.join(`
|
|
2237
2360
|
`)}
|
|
2238
|
-
`,{mode:384})}function
|
|
2239
|
-
`)){if(
|
|
2240
|
-
`)
|
|
2241
|
-
`)){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 NR(e){let t=Od({cmd:["netstat","-ano"],stdout:"pipe",stderr:"ignore"}),r=t.stdout?await new Response(t.stdout).text():"";return await t.exited,FR(r,e)}var HR=400;async function _a(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 Se(He,`could not stop pid ${e.pid} on port ${e.port}`,t)}await jR(HR);try{process.kill(e.pid,0),process.kill(e.pid,"SIGKILL")}catch{return}}function jR(e){return new Promise((t)=>{setTimeout(t,e)})}ja();te();import{readdirSync as X_,readFileSync as Q_}from"fs";import{basename as Mg,dirname as Z_,extname as eE,join as ro,resolve as tE}from"path";import{parse as rE}from"yaml";dt();function N(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function qd(e){return N(e)?new Set(Object.keys(e)):new Set}function U(e){if(typeof e==="string")return e;if(typeof e==="number"||typeof e==="boolean")return String(e);return""}function Ce(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 _t(e){if(!Array.isArray(e))return[];return e.map((t)=>U(t))}function Vd(e){if(!Array.isArray(e))return[];return e.map((t)=>N(t)?{service:U(t.service),condition:U(t.condition)||"service_started"}:U(t))}function nn(e){if(!N(e))return{};let t={};for(let[r,n]of Object.entries(e))t[r]=U(n);return t}function _r(e){if(typeof e==="string")return{args:e.split(/\s+/).filter((t)=>t!==""),shell:!1};if(Array.isArray(e))return{args:e.map((t)=>U(t)),shell:!1};return Wo()}function zd(e){if(e===void 0||e===null)return[];if(typeof e==="string"||typeof e==="number")return[Kd(e,"http")];if(Array.isArray(e))return e.map((t,r)=>Kd(t,`port_${r}`));if(N(e))return Object.entries(e).map(([t,r])=>Kd(r,t));return[]}function Kd(e,t){if(typeof e==="string"&&e.toLowerCase()==="auto")return{name:t,value:0,auto:!0};return{name:t,value:Ce(e),auto:!1}}function Dh(e){if(!N(e))return Nd();let t={},r=[],n={};for(let[o,i]of Object.entries(e))if(o==="required")r=_t(i);else if(o==="defaults")n=nn(i);else t[o]=U(i);return{vars:t,required:r,defaults:n}}function JR(e){if(!N(e))return jd();return{type:U(e.type),mode:U(e.mode),service_account:U(e.service_account),config:N(e.config)?{...e.config}:{}}}function XR(e){if(!N(e))return Hd();return{type:U(e.type),url:U(e.url),address:U(e.address),command:_r(e.command),interval_seconds:Ce(e.interval_seconds),timeout_seconds:Ce(e.timeout_seconds),start_period_seconds:Ce(e.start_period_seconds),unhealthy_threshold:e.unhealthy_threshold===void 0?3:Ce(e.unhealthy_threshold),healthy_reset_threshold:e.healthy_reset_threshold===void 0?10:Ce(e.healthy_reset_threshold)}}function QR(e){if(!N(e))return{policy:"",max_retries:0,backoff_seconds:0};return{enabled:e.enabled===void 0?void 0:ot(e.enabled),policy:U(e.policy),max_retries:Ce(e.max_retries),backoff_seconds:Ce(e.backoff_seconds)}}function ZR(e){if(!N(e))return{wait_for_healthy:!1,timeout_seconds:0};return{wait_for_healthy:ot(e.wait_for_healthy),timeout_seconds:Ce(e.timeout_seconds)}}function Gi(e){if(!N(e))return xn();let t=N(e.logs)?e.logs:{};return{extends:U(e.extends),description:U(e.description),command:_r(e.command),shell:ot(e.shell),working_dir:U(e.working_dir),dependencies:Vd(e.dependencies),ports:zd(e.ports),environment:Dh(e.environment),health:XR(e.health),identity:JR(e.identity),logs:{stdout:ot(t.stdout),stderr:ot(t.stderr)},restart:QR(e.restart),startup:ZR(e.startup),capabilities:_t(e.capabilities),proxy:Xd(e.proxy),expose:Jd(e.expose),container:Yd(e.container),watch:e_(e.watch),hooks:t_(e.hooks)}}function e_(e){if(!N(e))return $d();return{enabled:ot(e.enabled),paths:_t(e.paths),debounce_ms:La(Ce(e.debounce_ms)),ignore:e.ignore===void 0?[...ji]:_t(e.ignore)}}function t_(e){let t=N(e)?e:{};return{pre_start:_r(t.pre_start),post_start:_r(t.post_start)}}function Ih(e){let t=N(e)?e:{};return{command:_r(t.command),shell:ot(t.shell),working_dir:U(t.working_dir),dependencies:_t(t.dependencies),environment:Dh(t.environment)}}function Yd(e){if(!N(e))return;let t={};if(N(e.ports))for(let[r,n]of Object.entries(e.ports))t[r]=Ce(n);return{image:U(e.image),runtime:U(e.runtime),ports:t,env:nn(e.env),volumes:_t(e.volumes)}}function Jd(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:U(e.host),port:U(e.port)};return Fd()}function Xd(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 Ba(e){if(!N(e))return{services:[],environment:{}};return{services:_t(e.services),environment:nn(e.environment)}}function r_(e){if(typeof e==="string")return{type:e,service_account:""};if(!N(e))return{type:"",service_account:""};return{type:U(e.type),service_account:U(e.service_account)}}function n_(e){if(!N(e))return Bi();return{type:U(e.type),identity:r_(e.identity),audience:U(e.audience),service_account:U(e.service_account),client_id:U(e.client_id),client_secret:U(e.client_secret),credentials:U(e.credentials),headers:nn(e.headers)}}function Zn(e){if(!N(e))return{name:"",match:{host:"",path:""},upstream:{url:""},auth:Bi()};let t=N(e.match)?e.match:{},r=N(e.upstream)?e.upstream:{};return{name:U(e.name),transport:U(e.transport),match:{host:U(t.host),path:U(t.path)},upstream:{url:U(r.url),service:U(r.service),port:U(r.port)},auth:n_(e.auth),listen:N(e.listen)?{host:U(e.listen.host),port:Ce(e.listen.port)}:void 0}}te();import{existsSync as o_,statSync as Oh}from"fs";import{basename as Mh,dirname as Ua,isAbsolute as i_,join as Wa,resolve as $h}from"path";function eo(e,t){if(t!=="")return s_(t);let r=e===""?process.cwd():e;r=i_(r)?r:$h(r);for(;;){let n=Wa(r,Nt,dr);if(Ki(n))return{repoRoot:r,configPath:n};let o=Wa(r,"devctl.yaml");if(Ki(o))return{repoRoot:r,configPath:o};let i=Ua(r);if(i===r)break;r=i}throw Te(zn,"no devctl configuration found","run `devctl setup` or create a .devctl/config.yaml in the repository root")}function s_(e){let t=$h(e),r;try{r=Oh(t)}catch(o){throw Se(zn,"config path not found",o)}if(r.isDirectory()){if(Mh(t)===Nt)return{repoRoot:Ua(t),configPath:Wa(t,dr)};return{repoRoot:t,configPath:Wa(t,dr)}}let n=Ua(t);if(Mh(n)===Nt)n=Ua(n);return{repoRoot:n,configPath:t}}function Ki(e){return o_(e)&&!Oh(e).isDirectory()}dt();function qa(){return{services:{},templates:{},provenance:{}}}function on(e,t,r,n,o=""){if(N(t)&&Object.keys(t).length>0){for(let[i,s]of Object.entries(t))on(e,s,r,n,o===""?i:`${o}.${i}`);return}if(o==="")return;(e[o]??=[]).push({source:r,layer:n})}var a_=["environment","restart","startup","health","logs","identity","container","watch","hooks"];function Ga(e,t,r){let n=qd(r);if(N(r))for(let i of a_){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 Fh(e,t,r=qa(),n={source:"unknown",layer:"unknown"}){if(on(r.provenance,t,n.source,n.layer),t.version!==void 0)e.version=Ce(t.version);if(N(t.project)&&t.project.name!==void 0)e.project.name=U(t.project.name);if(N(t.google)){if(t.google.project_id!==void 0)e.google.project_id=U(t.google.project_id);if(t.google.region!==void 0)e.google.region=U(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?c_(s,i):Ba(i)}if(N(t.templates))for(let[o,i]of Object.entries(t.templates)){let s=e.templates[o];e.templates[o]=s?Ka(s,i):Gi(i),Ga(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?Ka(s,i):Gi(i),Ga(r.services,o,i)}if(N(t.tasks))for(let[o,i]of Object.entries(t.tasks))e.tasks[o]=e.tasks[o]?l_(e.tasks[o],i):Ih(i);if(N(t.proxy))Qd(e.proxy,t.proxy);if(N(t.logs)){if(t.logs.max_memory_events!==void 0)e.logs.max_memory_events=Ce(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=U(o.directory);if(o.retention_days!==void 0)e.logs.persistence.retention_days=Ce(o.retention_days);if(o.max_session_logs!==void 0)e.logs.persistence.max_session_logs=Ce(o.max_session_logs)}}if(N(t.auth)&&t.auth.refresh_threshold_seconds!==void 0)e.auth.refresh_threshold_seconds=Ce(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=Ce(t.shutdown.grace_seconds)}if(N(t.ui)){if(t.ui.theme!==void 0)e.ui.theme=U(t.ui.theme);if(t.ui.keymap!==void 0)e.ui.keymap=nn(t.ui.keymap)}if(N(t.secrets)){if(t.secrets.extra_markers!==void 0)e.secrets.extra_markers=_t(t.secrets.extra_markers);if(t.secrets.extra_patterns!==void 0)e.secrets.extra_patterns=_t(t.secrets.extra_patterns)}if(N(t.doctor)&&Array.isArray(t.doctor.tools))e.doctor.tools=t.doctor.tools.filter(N).map((o)=>({name:U(o.name),command:U(o.command)}));if(Array.isArray(t.plugins))e.plugins=t.plugins.filter(N).map((o)=>({path:U(o.path)})).filter((o)=>o.path!=="");if(N(t.environment)){if(Array.isArray(t.environment.sources))e.environment.sources=_t(t.environment.sources);if(N(t.environment.secrets))e.environment.secrets={...e.environment.secrets,...nn(t.environment.secrets)}}}function Qd(e,t){if(t.enabled!==void 0)e.enabled=ot(t.enabled);if(t.gateway!==void 0)e.gateway=ot(t.gateway);if(t.credentials!==void 0)e.credentials=U(t.credentials);if(N(t.listen)){if(t.listen.host!==void 0)e.listen.host=U(t.listen.host);if(t.listen.port!==void 0)e.listen.port=Ce(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=U(r.host);if(r.port!==void 0)e.token_endpoint.port=Ce(r.port)}if(Array.isArray(t.routes))e.routes=t.routes.map((r)=>Zn(r))}function c_(e,t){if(!N(t))return e;return{services:t.services!==void 0?_t(t.services):e.services,environment:t.environment!==void 0?{...e.environment,...nn(t.environment)}:e.environment}}function l_(e,t){if(!N(t))return e;return{command:t.command!==void 0?_r(t.command):e.command,shell:t.shell!==void 0?ot(t.shell):e.shell,working_dir:t.working_dir!==void 0?U(t.working_dir):e.working_dir,dependencies:t.dependencies!==void 0?_t(t.dependencies):e.dependencies,environment:Nh(e.environment,t.environment)}}function Ka(e,t){if(!N(t))return e;let r=qd(t),n={...e,environment:Nh(e.environment,t.environment),health:p_(e.health,t.health),identity:m_(e.identity,t.identity),logs:f_(e.logs,t.logs),restart:h_(e.restart,t.restart),startup:g_(e.startup,t.startup),container:v_(e.container,t.container),watch:d_(e.watch,t.watch),hooks:u_(e.hooks,t.hooks)};if(r.has("extends"))n.extends=U(t.extends);if(r.has("description"))n.description=U(t.description);if(r.has("command"))n.command=_r(t.command);if(r.has("shell"))n.shell=ot(t.shell);if(r.has("working_dir"))n.working_dir=U(t.working_dir);if(r.has("dependencies"))n.dependencies=Vd(t.dependencies);if(r.has("ports"))n.ports=zd(t.ports);if(r.has("capabilities"))n.capabilities=_t(t.capabilities);if(r.has("proxy"))n.proxy=Xd(t.proxy);if(r.has("expose"))n.expose=Jd(t.expose);return n}function d_(e,t){if(!N(t))return e;return{enabled:t.enabled!==void 0?ot(t.enabled):e.enabled,paths:t.paths!==void 0?_t(t.paths):e.paths,debounce_ms:t.debounce_ms!==void 0?La(Ce(t.debounce_ms)):e.debounce_ms,ignore:t.ignore!==void 0?_t(t.ignore):e.ignore}}function u_(e,t){if(!N(t))return e;return{pre_start:t.pre_start!==void 0?_r(t.pre_start):e.pre_start,post_start:t.post_start!==void 0?_r(t.post_start):e.post_start}}function Nh(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=_t(s);else if(i==="defaults")Object.assign(n,nn(s));else r[i]=U(s);return{vars:r,required:o,defaults:n}}function p_(e,t){if(!N(t))return e;return{type:t.type!==void 0?U(t.type):e.type,url:t.url!==void 0?U(t.url):e.url,address:t.address!==void 0?U(t.address):e.address,command:t.command!==void 0?_r(t.command):e.command,interval_seconds:t.interval_seconds!==void 0?Ce(t.interval_seconds):e.interval_seconds,timeout_seconds:t.timeout_seconds!==void 0?Ce(t.timeout_seconds):e.timeout_seconds,start_period_seconds:t.start_period_seconds!==void 0?Ce(t.start_period_seconds):e.start_period_seconds,unhealthy_threshold:t.unhealthy_threshold!==void 0?Ce(t.unhealthy_threshold):e.unhealthy_threshold,healthy_reset_threshold:t.healthy_reset_threshold!==void 0?Ce(t.healthy_reset_threshold):e.healthy_reset_threshold}}function m_(e,t){if(!N(t))return e;return{type:t.type!==void 0?U(t.type):e.type,mode:t.mode!==void 0?U(t.mode):e.mode,service_account:t.service_account!==void 0?U(t.service_account):e.service_account,config:t.config!==void 0&&N(t.config)?{...e.config??{},...t.config}:e.config}}function f_(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 h_(e,t){if(!N(t))return e;return{enabled:t.enabled!==void 0?ot(t.enabled):e.enabled,policy:t.policy!==void 0?U(t.policy):e.policy,max_retries:t.max_retries!==void 0?Ce(t.max_retries):e.max_retries,backoff_seconds:t.backoff_seconds!==void 0?Ce(t.backoff_seconds):e.backoff_seconds}}function g_(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?Ce(t.timeout_seconds):e.timeout_seconds}}function v_(e,t){if(!N(t))return e;let r=Yd(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 Hh(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)on(t,c,`synthesized from services.${n}.proxy`,"synthesized",`proxy.routes.${l}`)})}y_(e,t)}function jh(e){let t=e.repoRoot||process.cwd(),r=e.proxy.credentials.trim()===""?"":Ti(e.proxy.credentials.trim(),t);e.proxy.credentials=r;for(let n of e.proxy.routes){if(n.auth.client_id.trim()==="")continue;let o=(n.auth.credentials??"").trim();n.auth.credentials=o===""?r:Ti(o,t)}}function y_(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&&Qn(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:Bi()},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";on(t,a,`synthesized from ${l}`,"synthesized",`proxy.routes.${c}`)}}}function Bh(e,t=qa()){if(Object.keys(e.templates).length===0)return;let r={};for(let[n,o]of Object.entries(e.services))r[n]=Uh(e,o,t.services[n]??new Set,t,{});e.services=r}function Uh(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]??xn();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=Uh(e,i,a,n,s),l=b_(c,t,r);return l.extends=t.extends,l}function b_(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();dt();function Wh(e){if(e.version===0)throw P(Y,"version is required");if(e.version===Go)return e;throw P(Y,`unsupported config version ${e.version} (expected ${Go}); no migration is available`)}var Gh=["version","project","google","profiles","templates","services","tasks","proxy","logs","auth","shutdown","ui","secrets","doctor","plugins","environment"],Kh=["extends","description","command","shell","working_dir","dependencies","ports","environment","health","identity","logs","restart","startup","capabilities","proxy","expose","container","watch","hooks"],qh=["type","url","address","command","interval_seconds","timeout_seconds","start_period_seconds","unhealthy_threshold","healthy_reset_threshold"],Vh=["service","condition"],zh=["type","mode","service_account","config"],Yh=["enabled","policy","max_retries","backoff_seconds"],Jh=["wait_for_healthy","timeout_seconds"],Xh=["enabled","host","port"],Qh=["enabled","gateway","credentials","listen","token_endpoint","routes"],Va=["host","port"],Zd=["name","transport","listen","match","upstream","auth"],eu=["host","path"],tu=["url","service","port"],ru=["type","identity","audience","service_account","client_id","client_secret","credentials","headers"],Zh=["max_memory_events","persistence"],eg=["enabled","directory","retention_days","max_session_logs"],tg=["refresh_threshold_seconds"],rg=["stop_services_on_exit","grace_seconds"],ng=["theme","keymap"],og=["name"],ig=["project_id","region"],sg=["services","environment"],nu=["required","defaults"],ag=["stdout","stderr"],cg=["extra_markers","extra_patterns"],lg=["tools"],dg=["name","command"],ug=["enabled","host","port"],pg=["path"],mg=["sources","secrets"],fg=["image","runtime","ports","env","volumes"],hg=["enabled","paths","debounce_ms","ignore"],gg=["pre_start","post_start"],vg=["command","shell","working_dir","dependencies","environment"],yg=["google","google_api","iap","network","service_identity","local_http"],bg=["|","||","&&",";",">",">>","<","&"];var x_=2;function Pn(e,t){if(Array.isArray(e))return e.flatMap((o,i)=>Pn(o,xg(t,String(i))));if(!__(e))return[];let r=S_(t),n=[];for(let o of Object.keys(e)){let i=xg(t,o);if(r.length>0&&!r.includes(o)&&!w_(t))n.push(i);n.push(...Pn(e[o],i))}return n}function w_(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(".auth.headers"))return!0;if(e.endsWith(".identity.config")||e.includes(".identity.config."))return!0;return e.includes(".environment.")&&!e.endsWith(".environment")}function S_(e){switch(e){case"":return Gh;case"project":return og;case"google":return ig;case"proxy":return Qh;case"proxy.listen":return Va;case"proxy.token_endpoint":return ug;case"logs":return Zh;case"logs.persistence":return eg;case"auth":return tg;case"shutdown":return rg;case"ui":return ng;case"secrets":return cg;case"doctor":return lg;case"environment":return mg;default:return k_(e)}}function k_(e){if(e==="services"||e==="profiles"||e==="templates"||e==="tasks")return[];if(e.startsWith("services.")||e.startsWith("templates.")){if(e.includes(".dependencies."))return Vh;return C_(e)}if(e.startsWith("profiles.")){if(e.split(".").length===2)return sg}if(e.startsWith("tasks.")){let t=e.split(".");if(t.length===2)return vg;if(t[2]==="environment")return nu}if(e.includes("routes"))return R_(e);if(e.startsWith("doctor.tools")&&e.split(".").length>=3)return dg;if(e==="plugins")return[];if(e.startsWith("plugins.")&&e.split(".").length===2)return pg;return[]}function C_(e){let t=e.split(".");if(t.length===2)return Kh;if(t.length>=3)switch(t[2]){case"health":return qh;case"identity":return zh;case"restart":return Yh;case"startup":return Jh;case"logs":return ag;case"environment":return nu;case"proxy":return P_(t);case"expose":return Xh;case"container":return fg;case"watch":return hg;case"hooks":return gg;default:return[]}return[]}function P_(e){let t=e.slice(3),r=t[0]!==void 0&&/^\d+$/.test(t[0])?1:0,n=t[r]??"";if(n==="match")return eu;if(n==="upstream")return tu;if(n==="auth")return ru;if(n==="listen")return Va;return Zd}function R_(e){if(e==="proxy.routes")return[];if(e.endsWith(".match"))return eu;if(e.endsWith(".upstream"))return tu;if(e.endsWith(".auth"))return ru;if(e.endsWith(".listen"))return Va;if(e.includes("proxy.routes")&&e.split(".").length===x_+1)return Zd;return[]}function xg(e,t){if(e==="")return t;return`${e}.${t}`}function __(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function qo(e){return`unknown fields: ${e.join(", ")}`}dt();function Er(e){let t=e.trim().toLowerCase();if(t==="")return!0;if(t==="localhost"||t==="::1")return!0;if(wg(t))return!0;return E_(t)}function wg(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 E_(e){if(!e.startsWith("::ffff:"))return!1;return wg(e.slice(7))}import{existsSync as L_}from"fs";import{isAbsolute as D_,resolve as I_}from"path";import{fileURLToPath as M_}from"url";dt();function Sg(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=Eh(e.ports);if(r&&!r.auto)return r.value;return}function T_(e,t,r,n=""){let o=e,i="";for(;;){let s=o.indexOf("${");if(s<0)return i+o;i+=o.slice(0,s);let a=o.slice(s).indexOf("}");if(a<0)throw Error(`unclosed environment reference in "${e}"`);let c=o.slice(s+2,s+a);i+=A_(c,t,r,n),o=o.slice(s+a+1)}}function A_(e,t,r,n){let o=e.split(".");if(o[0]==="identity"){if(o.length===2&&o[1]==="user")return n;throw Error(`unsupported reference \${${e}}`)}if(o.length<3||o[0]!=="services")throw Error(`unsupported reference \${${e}}`);let i=o[1]??"",s=t.services[i];if(!s)throw Error(`reference \${${e}}: unknown service`);let a=r[i];if(o[2]==="host"||o[2]==="url"){let c=t.proxy.enabled?t.proxy.routes.find((d)=>d.upstream.service===i):void 0;if(o[2]==="host")return c?c.match.host||"127.0.0.1":"127.0.0.1";if(c)return`http://${c.match.host||"127.0.0.1"}:${t.proxy.listen.port}${c.match.path}`;let l=Sg(s,a);if(l===void 0)throw Error(`unresolvable reference \${${e}}`);return`http://127.0.0.1:${l}`}if(o[2]==="port"){let c=Sg(s,a);if(c!==void 0)return String(c)}if(o[2]==="ports"){let c=o[3];if(!c)throw Error(`reference \${${e}}: missing port name`);if(a&&a[c]!==void 0)return String(a[c]);let l=Qn(s.ports,c);if(l&&!l.auto)return String(l.value);let d=Number.parseInt(c,10);if(!Number.isNaN(d)&&d>=0&&d<s.ports.length){let u=s.ports[d];if(u&&!u.auto)return String(u.value)}}throw Error(`unresolvable reference \${${e}}`)}function ou(e,t,r,n=""){let o={};for(let[i,s]of Object.entries(e))o[i]=T_(s,t,r,n);return o}function kg(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 Cg(e,t){let r=e.split(".");if(r.length<2)return!1;if(r[0]==="identity")return r.length===2&&r[1]==="user";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 Qn(n.ports,o)!==void 0}}return!0}dt();var qi=65535,Vi=1,Ag=["http","tcp","process","command"];function su(e){let t=[];for(let[r,n]of Object.entries(e.services)){let o=n.health.type;if(o!==""&&!Ag.includes(o.toLowerCase()))t.push({service:r,type:o})}return t}var O_=0,Eg=1,Tg=2;function to(e){let t=[];if(e.version===0)t.push("version is required");else if(e.version!==Go)t.push(`unsupported config version ${e.version} (expected ${Go}); no migration is available`);if(Object.keys(e.services).length===0)t.push("at least one service must be defined");t.push(...F_(e)),t.push(...$_(e)),t.push(...B_(e)),t.push(...U_(e)),t.push(...W_(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:")?M_(n.path):D_(n.path)?n.path:I_(e.repoRoot,n.path);if(!L_(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(wn(n.command))t.push(`${o}.command is required`);t.push(...Ya(o,n.command,n.shell));for(let i of n.dependencies)if(!e.services[i])t.push(`${o}.dependencies: unknown service "${i}"`);t.push(...Lg(`${o}.environment`,n.environment,e))}return t}function F_(e){let t=[],r={};for(let[n,o]of Object.entries(e.services)){let i=`services.${n}`;if(wn(o.command)&&!o.container)t.push(`${i}.command is required`);t.push(...Ya(i,o.command,o.shell)),t.push(...Ya(`${i}.hooks.pre_start`,o.hooks.pre_start,o.shell)),t.push(...Ya(`${i}.hooks.post_start`,o.hooks.post_start,o.shell)),t.push(...Y_(i,o.capabilities));for(let c of o.dependencies){let l=Yt(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(bn(c)))t.push(`${i}.dependencies: condition must be service_started or service_healthy`);if(bn(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<Vi||c.value>qi)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=j_(`${i}.identity`,o.identity,e.plugins.length>0);if(s!=="")t.push(s);if(t.push(...N_(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=Ma(o.restart);if(a!==Ea&&a!==Ta&&a!==Aa)t.push(`${i}.restart.policy must be never, on_failure, or always`);if(t.push(...Lg(`${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<Vi||l>qi)t.push(`${i}.container.ports.${c}: invalid container port ${l}`)}}}return t}function N_(e,t,r){let n=[];if(t.health.type==="")return n;let o=t.health.type.toLowerCase();if(!Ag.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"&&wn({args:t.health.command.args,shell:!1}))n.push(`${e}.health.command is required for command health checks`);return n}var H_=["","user","service","service_account"];function j_(e,t,r){let n=Wi(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 au(e){let t=[];for(let[r,n]of Object.entries(e.services)){let o=Wi(n.identity).toLowerCase();if(!H_.includes(o))t.push({service:r,type:o})}return t}function B_(e){let t={},r=[],n=[],o=(i)=>{let s=t[i]??O_;if(s===Tg)return;if(s===Eg){r.push(`dependency cycle: ${[...n,i].join(" \u2192 ")}`);return}t[i]=Eg,n.push(i);let a=e.services[i];if(a)for(let c of a.dependencies){let l=Yt(c);if(e.services[l])o(l)}n.pop(),t[i]=Tg};for(let i of Object.keys(e.services))o(i);return r}function Lg(e,t,r){let n=[],o=(i,s)=>{for(let a of kg(s))if(!Cg(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 U_(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 W_(e){let t=[];if(e.proxy.listen.host!==""){if(!Dg(e.proxy.listen.host))t.push("proxy.listen.host must be an IP address or localhost");else if(!Er(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<Vi||e.proxy.listen.port>qi))t.push("proxy.listen.port is invalid");let r={},n=new Set;return e.proxy.routes.forEach((o,i)=>{let s=`proxy.routes[${i}]`;if(o.name==="")t.push(`${s}.name is required`);else if(r[o.name])t.push(`${s}: duplicate route name ${o.name}`);r[o.name]=!0;let a=(o.transport??"").toLowerCase();if(a!==""&&a!=="http"&&a!=="grpc")t.push(`${s}.transport must be "http" or "grpc"`);if(t.push(...K_(o,s,e)),t.push(...q_(o,s)),Ko(o))t.push(...G_(o,s,n,e))}),t.push(...z_(e)),t}function G_(e,t,r,n){let o=[],i=e.listen;if(!i||i.port===0)o.push(`${t}.listen.port is required for a grpc route`);else{if(i.port<Vi||i.port>qi)o.push(`${t}.listen.port is invalid`);if(r.has(i.port))o.push(`${t}.listen.port ${i.port} is already used by another grpc route`);if(r.add(i.port),i.port===n.proxy.listen.port)o.push(`${t}.listen.port must differ from proxy.listen.port`);if(n.proxy.token_endpoint.enabled&&i.port===n.proxy.token_endpoint.port)o.push(`${t}.listen.port must differ from proxy.token_endpoint.port`);if(i.host!==""&&(!Dg(i.host)||!Er(i.host)))o.push(`${t}.listen.host must be a loopback address`)}if(!(e.upstream.url??"").trim().toLowerCase().startsWith("https://"))o.push(`${t}.upstream.url must be an https:// address for a grpc route`);if(e.match.host!==""||e.match.path!=="")o.push(`${t}.match is not supported on a grpc route`);return o}function K_(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(Qn(s.ports,a)===void 0)n.push(`${t}.upstream: service ${i} has no port named ${a}`);return n}function q_(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(...V_(e.auth,t));let n=e.auth.identity.type.toLowerCase();if(n==="service"||n==="service_account"||tn({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 V_(e,t){let r=(e.client_id??"").trim(),n=(e.client_secret??"").trim(),o=(e.credentials??"").trim();if(r===""&&n===""&&o==="")return[];if(e.type.toLowerCase()!=="iap")return[`${t}.auth.${r!==""?"client_id":n!==""?"client_secret":"credentials"} is only valid when auth.type is iap`];let i=[];if(r===""){if(n!=="")i.push(`${t}.auth.client_id is required when client_secret is set`);if(o!=="")i.push(`${t}.auth.credentials requires auth.client_id`);return i}if(n===""&&o==="")i.push(`${t}.auth.client_secret is required when client_id is set`);else if(n!==""&&iu(n).length>0&&!_g(n))i.push(`${t}.auth.client_secret must be a literal or a single \${NAME} / \${env.NAME} reference`);let s=e.identity.type.toLowerCase();if(s==="service"||s==="service_account")i.push(`${t}.auth.client_id is only valid with identity.type user`);return i}function z_(e){let t=[],r=e.proxy.token_endpoint;if(!r.enabled)return t;let n=r.host||Da;if(!Er(n))t.push("proxy.token_endpoint.host must be a loopback address");if(r.port!==0&&(r.port<Vi||r.port>qi))t.push("proxy.token_endpoint.port is invalid");return t}function Ya(e,t,r){if(t.shell||r)return[];for(let n of t.args)if(bg.includes(n)||n.includes("|")||n.includes(";")||n.includes("&&"))return[`${e}.command contains shell metacharacters; set shell: true to run via a shell`];return[]}function Y_(e,t){let r=[];for(let n of t)if(!yg.includes(n))r.push(`${e}.capabilities: unknown capability "${n}"`);return r}function Dg(e){if(e==="localhost")return!0;return J_(e)}function J_(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 zi(e,t,r){try{return Yi(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 sn(e,t){let{repoRoot:r,configPath:n}=eo(e,t);return Yi(r,n)}function no(e,t){try{return sn(e,t)}catch(r){if(!Pa(r,zn))throw r;let n=e===""?process.cwd():tE(e),o=Xn();return o.repoRoot=n,o.configPath=ro(n,Nt,dr),o}}function Yi(e,t,r){let n=Xn(),o=qa();Og(t,n,o,r?.candidateText,"main"),n.repoRoot=e,n.configPath=t;let i=Z_(t);if(Mg(i)===Nt)oE(i,n,o);n=nE(n,e,t,o),n=Wh(n);try{Bh(n,o)}catch(a){throw Se(Y,"template merge failed",a)}Hh(n,o.provenance),jh(n),n.provenance=o.provenance;let s=to(n);if(s.length>0)throw P(Y,s.join(`
|
|
2242
|
-
`));return n}function nE(e,t,r,n){let o=ro(lr(),"config.local.yaml"),i=ro(t,Nt,"config.local.yaml");for(let s of[o,i]){if(s===r||!Ki(s))continue;Og(s,e,n,void 0,s===o?"home_local":"repo_local")}return e}function Og(e,t,r,n,o="main"){let i=n!==void 0?$g(n,e):cu(e),s=Pn(i,"");if(s.length>0)throw P(Y,qo(s));Fh(t,i,r,{source:e,layer:o})}function oE(e,t,r){Ig(ro(e,"services"),(o,i,s)=>{let a=Pn(i,`services.${o}`);if(a.length>0)throw P(Y,qo(a));let c=t.services[o];t.services[o]=c?Ka(c,i):Gi(i),Ga(r.services,o,i),on(r.provenance,i,s,"modular_service",`services.${o}`)}),Ig(ro(e,"profiles"),(o,i,s)=>{let a=Pn(i,`profiles.${o}`);if(a.length>0)throw P(Y,qo(a));t.profiles[o]=Ba(i),on(r.provenance,i,s,"modular_profile",`profiles.${o}`)});let n=ro(e,"proxy","routes.yaml");if(Ki(n))iE(n,t,r)}function iE(e,t,r){let n=cu(e);if(N(n.proxy))on(r.provenance,n.proxy,e,"modular_proxy","proxy");if(Array.isArray(n.routes))on(r.provenance,n.routes,e,"modular_proxy","proxy.routes");if(N(n.proxy)){let o=Pn({proxy:n.proxy},"");if(o.length>0)throw P(Y,qo(o))}if(Array.isArray(n.routes))n.routes.forEach((o,i)=>{let s=Pn(o,`proxy.routes.${i}`);if(s.length>0)throw P(Y,qo(s))});if(N(n.proxy)){let o={...n.proxy};if(delete o.routes,Qd(t.proxy,o),Array.isArray(n.proxy.routes))t.proxy.routes.push(...n.proxy.routes.map((i)=>Zn(i)))}if(Array.isArray(n.routes))t.proxy.routes.push(...n.routes.map((o)=>Zn(o)))}function Ig(e,t){let r;try{r=X_(e)}catch(n){if(sE(n))return;throw n}for(let n of r.sort()){let o=eE(n);if(o!==".yaml"&&o!==".yml")continue;let i=ro(e,n),s=cu(i);t(Mg(n,o),s,i)}}function cu(e){let t;try{t=Q_(e,"utf8")}catch(r){throw Se(Y,"unable to read config",r)}return $g(t,e)}function $g(e,t){let r;try{r=rE(e)}catch(n){throw Se(Y,`invalid YAML in ${t}`,n)}if(r===null||r===void 0)return{};if(!N(r))throw P(Y,`unable to decode config: ${t} is not a mapping`);return r}function sE(e){return typeof e==="object"&&e!==null&&"code"in e&&e.code==="ENOENT"}dt();te();Tr();import{existsSync as Qa,readFileSync as cE}from"fs";import{homedir as Bg}from"os";import{join as pu}from"path";var{spawn:mu}=globalThis.Bun;var lE="https://www.googleapis.com/auth/cloud-platform",Ug=1500,dE=2000;function zo(){let e=globalThis;if(e.window&&typeof e.window.fetch!=="function")e.window.fetch=fetch}function fu(){let e=process.env.GOOGLE_APPLICATION_CREDENTIALS||pu(Bg(),".config","gcloud","application_default_credentials.json");if(!Qa(e))return"";try{let t=JSON.parse(cE(e,"utf8"));return typeof t.quota_project_id==="string"?t.quota_project_id:""}catch{return""}}async function so(e){let t={gcloudInstalled:await hu("gcloud"),adcAvailable:!1,userEmail:"",projectID:"",projectSource:""};if(e!=="")t.projectID=e,t.projectSource="configuration";else{let r=hE("GOOGLE_CLOUD_PROJECT","GCLOUD_PROJECT","GOOGLE_PROJECT");if(r!=="")t.projectID=r,t.projectSource="environment variable";else if(t.gcloudInstalled){let n=await jg("core/project");if(n!=="")t.projectID=n,t.projectSource="gcloud configuration"}}if(Xi())try{await Vg(uE(t),dE)}catch(r){t.adcAvailable=!1,t.error=Yo(r)}if(t.userEmail===""&&t.gcloudInstalled)t.userEmail=await jg("core/account");return t}async function uE(e){zo();let{GoogleAuth:t}=await import("google-auth-library"),r=new t({scopes:[lE]});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 pE(r);if(o!=="")e.userEmail=o}async function pE(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=mE(r);if(n!=="")return n}}catch{return""}return""}function mE(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 Wg(e){let t=await so(e);return ur({kind:"user",email:t.userEmail,project:t.projectID,projectSource:t.projectSource,adcAvailable:t.adcAvailable})}async function Gg(){if(await mu({cmd:["gcloud","auth","application-default","login"],stdin:"inherit",stdout:"inherit",stderr:"inherit"}).exited!==0)throw Te(yn,"application-default login failed","run `gcloud auth application-default login`")}async function Kg(){if(await mu({cmd:["gcloud","auth","application-default","revoke","--quiet"],stdout:"ignore",stderr:"pipe"}).exited!==0)throw Te(yn,"failed to revoke application-default credentials","run `gcloud auth application-default revoke`")}function Yo(e){let t=fE(e).toLowerCase();if(io(t,["unauthorized_client"]))return Te(Ca,"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(yn,"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(Yn,"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(ka,"cannot impersonate service account","ask an administrator to grant roles/iam.serviceAccountTokenCreator on the target service account");return Te(Yn,"authorization failure","verify IAM permissions for the current user on this project")}if(t.includes("audience"))return Te(Ca,"IAP audience is incorrect","set auth.audience on the proxy route to the IAP OAuth client ID");if(t.includes("iap"))return Se(Ca,"IAP authentication failure",e);if(t.includes("project"))return Te(Y,"wrong or missing Google project","set google.project_id in .devctl/config.yaml");if(io(t,["timeout","network","connection refused","no such host"]))return Se(lt,"network problem reaching Google Cloud",e);if(io(t,["adc","default credentials","could not find"]))return Te(yn,"application default credentials unavailable","run `gcloud auth application-default login`");return Se(yn,"Google authentication failed",e)}function fE(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 hE(...e){for(let t of e){let r=process.env[t];if(r)return r}return""}async function hu(e){try{return(await qg(process.platform==="win32"?["where",e]:["which",e],Ug)).code===0}catch{return!1}}function Xi(){let e=process.env.GOOGLE_APPLICATION_CREDENTIALS;if(e&&Qa(e))return!0;if(Qa(pu(Bg(),".config","gcloud","application_default_credentials.json")))return!0;let t=process.env.APPDATA;return Boolean(t&&Qa(pu(t,"gcloud","application_default_credentials.json")))}async function jg(e){try{let t=await qg(["gcloud","config","get-value",e],Ug);if(t.code!==0)return"";let r=t.stdout.trim();if(r===""||r==="(unset)")return"";return r}catch{return""}}async function qg(e,t){let r=mu({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 Vg(Promise.all([n,r.exited]),t);return{code:i??1,stdout:o}}catch{return r.kill(),{code:1,stdout:""}}}function Vg(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 LE}from"crypto";import{readFileSync as DE}from"fs";import{join as IE}from"path";import{GoogleAuth as Zi,Impersonated as ME}from"google-auth-library";var{spawn:bu}=globalThis.Bun;import{existsSync as ec,readdirSync as gE,readFileSync as zg,unlinkSync as vE}from"fs";import{join as Qi}from"path";var xu="keychain-index.json",ao="devctl.credentials",vu=".cred.json";function yu(e){return Qi(Cr(),`${rc(e)}${vu}`)}function tc(e){if(e==="file"||process.env.DEVCTL_HOME&&e!=="keychain")return new Za;if(e==="keychain"||process.platform==="darwin"||process.platform==="win32"||process.platform==="linux")return new Yg(new Za);return new Za}class Za{backend="file";async get(e){let t=wE(e);if(!t||t.accessToken==="")return;return t}async set(e,t){Pr(yu(e),`${JSON.stringify({...xE(t),key:e})}
|
|
2243
|
-
`)}
|
|
2244
|
-
`)}
|
|
2245
|
-
`)}function
|
|
2246
|
-
`)}function KE(e){return{identity:e.identity,audience:e.audience,scopes:e.scopes,accessToken:e.accessToken,tokenType:e.tokenType,expiresAt:e.expiresAt.toISOString()}}function qE(e){return{accessToken:e.accessToken,tokenType:e.tokenType||"Bearer",expiresAt:new Date(e.expiresAt),audience:e.audience,identity:e.identity,scopes:e.scopes}}function En(){return[YE(),zE(),VE()]}function VE(){return{name:"user",accepts:(e,t)=>!e.startsWith("sa:")&&t==="",fetch:async(e,t,r)=>{if(e.startsWith("sa:"))throw P(Rr,"user provider cannot mint service-account tokens");return tT(e,t,r)}}}function zE(){return{name:"service_account",accepts:(e,t)=>e.startsWith("sa:")&&t==="",fetch:async(e,t,r)=>{if(!e.startsWith("sa:"))throw P(Rr,"service-account provider cannot mint user tokens");return JE(e,t,r)}}}function YE(){return{name:"iap",accepts:(e,t)=>t!=="",fetch:async(e,t,r,n)=>{if(t==="")throw P(Rr,"IAP audience is required");if(e.startsWith("sa:"))return XE(e,t,r);return QE(e,t,r,n)}}}async function JE(e,t,r){let n=e.slice(3);try{zo();let i=await new Zi({scopes:r.length>0?r:[oc]}).getClient(),s=new ME({sourceClient:i,targetPrincipal:n,targetScopes:r.length>0?r:[oc],lifetime:3600}),a=await s.getAccessToken(),c=typeof a==="string"?a:a?.token;if(!c)throw P(Rr,"empty impersonated token");return{accessToken:c,tokenType:"Bearer",expiresAt:es(c,s),audience:t,identity:e,scopes:r}}catch(o){throw Yo(o)}}async function XE(e,t,r){let n=e.slice(3);try{zo();let i=await new Zi({scopes:[oc]}).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 P(Rr,"empty impersonated IAP id token");return{accessToken:c,tokenType:"Bearer",expiresAt:es(c),audience:t,identity:e,scopes:r}}catch(o){throw Yo(o)}}async function QE(e,t,r,n){try{if(zo(),n)return await ZE(e,t,r,n);let s=await(await new Zi({scopes:r.length>0?r:[ev]}).getIdTokenClient(t)).idTokenProvider.fetchIdToken(t);return{accessToken:s,tokenType:"Bearer",expiresAt:es(s),audience:t,identity:e,scopes:r}}catch(o){if(o instanceof Ve)throw o;throw Yo(o)}}async function ZE(e,t,r,n){let o=n.refreshToken;if(!o){if(o=(await new Zi({scopes:r.length>0?r:[ev]}).getClient()).credentials.refresh_token??void 0,!o)throw Te(yn,"ADC has no refresh token for a custom IAP OAuth client","set auth.credentials (or proxy.credentials) to an authorized_user file whose refresh_token was issued by this client_id, or run `gcloud auth application-default login` with that client")}let i=await fetch(NE,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:eT(n,o,t)}),s=await i.text();if(!i.ok)throw Error(`IAP token exchange failed (${i.status}): ${s}`);let a;try{a=JSON.parse(s)}catch{throw P(Rr,"IAP token endpoint returned a non-JSON response")}let c=a.id_token;if(!c)throw P(Rr,"empty IAP id token");return{accessToken:c,tokenType:"Bearer",expiresAt:es(c),audience:t,identity:e,scopes:r}}function eT(e,t,r){return new URLSearchParams({grant_type:"refresh_token",client_id:e.clientId,client_secret:e.clientSecret,refresh_token:t,audience:r})}async function tT(e,t,r){try{zo();let o=await new Zi({scopes:r.length>0?r:[oc]}).getClient(),i=await o.getAccessToken();if(!i.token)throw P(Rr,"empty access token");return{accessToken:i.token,tokenType:"Bearer",expiresAt:rT(o,i.token),audience:t,identity:e,scopes:r}}catch(n){throw Yo(n)}}function es(e,t){if(t?.credentials?.expiry_date)return new Date(t.credentials.expiry_date);return nT(e)??new Date(Date.now()+OE)}function rT(e,t){return es(t,e)}function nT(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 oT(e){if(e instanceof Ve)return e.kind!==Y&&e.kind!==Yn;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();Tr();var rs=4000,iT=8000;function Xo(e){let t=e?.tokens,r=()=>(t??=new co(60000,En(),void 0,tc("file")),t);return{detectGoogle:so,hasCommand:hu,portAvailable:Uo,hasLocalAdc:Xi,adcQuotaProject:fu,mintToken:async(n,o,i)=>{await r().get(n,o,[],i)},probeServiceUsage:async(n,o)=>{let i=await ns(r().get("user","",[]),rs),s=`https://serviceusage.googleapis.com/v1/projects/${n}/services/${o}`,a=await fetch(s,{headers:{Authorization:`Bearer ${i.accessToken}`},signal:AbortSignal.timeout(rs)});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 sc=Xo();function ac(e=Xo()){return{run:(t,r,n)=>sT(t,e,r,n)}}async function sT(e,t=sc,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 f=await t.hasCommand(p),h=f&&await(t.containerRuntimeAvailable??sc.containerRuntimeAvailable)(p);c(h?{name:`${p} container runtime`,severity:"ok",message:`${p} daemon reachable`}:{name:`${p} container runtime`,severity:"error",message:f?`${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:Ji(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:Ji(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:Ji(e)?"error":"warn",message:"no Google project configured",hint:"set google.project_id in .devctl/config.yaml"});if(Ji(e)||Object.values(e.services).some((p)=>p.capabilities.includes("google")||p.identity.type!=="")){a("Live Google access");let p=!0,f=(h)=>{if(p)c(h)};try{await ns((async()=>{await cT(e,f,t,d.userEmail),await aT(e,f,t)})(),t.liveDeadlineMs??iT)}catch(h){f({name:"Live Google probes",severity:"warn",message:L(h),hint:"network or timeout \u2014 retry when online"})}finally{p=!1}}for(let p of e.doctor.tools){let f=p.command||p.name;if(a(`${p.name} installed`),await t.hasCommand(f))c({name:`${p.name} installed`,severity:"ok",message:`${f} found`});else c({name:`${p.name} installed`,severity:"error",message:`${f} 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=to(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:L(p)})}let m={};for(let[p,f]of Object.entries(e.services))for(let h of f.ports){if(h.auto)continue;let v=`Port ${h.value}`;if(a(v),m[h.value])c({name:v,severity:"error",message:`configured on both ${m[h.value]} and ${p}`});else{m[h.value]=p;let b=n?.services?.[p],g=Boolean(f.container&&b&&["STARTING","RUNNING","HEALTHY","UNHEALTHY"].includes(b.state??""));if(Boolean(b&&(b.pid>0||g)&&Object.values(b.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 rv(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 rv(p,e.proxy.listen.port,"proxy.listen.port"))}return i="Diagnostics complete",s(),o}async function aT(e,t,r){let n=e.google.project_id,o=r.hasLocalAdc??Xi;if(n===""||!o())return;let i=r.probeServiceUsage??sc.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)=>ns(i(n,c.service),rs)))).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:L(c.reason),hint:`enable ${d.service} in the Google Cloud console \u2014 doctor never auto-enables APIs`})})}async function cT(e,t,r,n){let o=Br(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??Xi)())return;let c=r.mintToken??sc.mintToken,l=o.map(async(u)=>{try{await ns(c(`sa:${u}`,""),rs),t({name:`Impersonate ${u}`,severity:"ok",message:"token minted"})}catch(m){let p=(r.adcQuotaProject??fu)(),f=n||"the ADC principal",h=p===""?"unknown":p,v=tv(`Impersonate ${u}`,m,`grant roles/iam.serviceAccountTokenCreator to ${f} 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: ${f} \u2192 ${u}`,v.hint=`verify roles/iam.serviceAccountTokenCreator for ${f} on this service account; ADC quota project: ${h}`;t(v)}}),d=s.map(async(u)=>{let m=an(u.auth).kind===oo?`sa:${an(u.auth).serviceAccount}`:"user";try{let p=ic(u.auth);await ns(c(m,u.auth.audience,p),rs),t({name:`IAP ${u.name}`,severity:"ok",message:"id token minted"})}catch(p){t(tv(`IAP ${u.name}`,p,"check IAP OAuth client ID and ADC"))}});await Promise.all([...l,...d])}function tv(e,t,r){let n=L(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 ns(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 rv(e,t,r){let n=await Jn(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 nv(e){let t=[`${tr()} 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(`
|
|
2361
|
+
`,{mode:384})}function Ry(e){let t=So(e)&&Zu(e).isDirectory()?e:xy(e);if(Xr(t),process.platform==="darwin"){let r=So(e)&&Zu(e).isFile()?["-R",e]:[t];Xu("open",r,{detached:!0,stdio:"ignore"}).unref();return}if(process.platform==="win32"){Xu("explorer",[t],{detached:!0,stdio:"ignore"}).unref();return}Xu("xdg-open",[t],{detached:!0,stdio:"ignore"}).unref()}function _y(e=Ga()){if(!So(e))return[];return Ti(e).filter((t)=>t.startsWith(tp)).sort().reverse()}function VE(e){let t=bn(e,wy);if(So(t))return ep(t,"utf8").trim()===Sy;if(!So(e))return!1;return Ti(e).some((r)=>r.endsWith(".jsonl"))}function Ty(e,t=Ga()){let r=bn(t,e);if(!So(r))return[];if(VE(r))return zE(r);return YE(r)}function zE(e){let t=[];for(let r of Ti(e)){if(!r.endsWith(".jsonl"))continue;let n=ep(bn(e,r),"utf8");for(let o of n.split(`
|
|
2362
|
+
`)){if(o.trim()==="")continue;let s=JE(o);if(s)t.push(s)}}return t.sort((r,n)=>r.seq-n.seq||r.timestamp.localeCompare(n.timestamp))}function YE(e){let t=[];for(let n of Ti(e)){if(!n.endsWith(".log"))continue;let o=ep(bn(e,n),"utf8");for(let s of o.split(`
|
|
2363
|
+
`)){if(s.trim()==="")continue;let i=s.split(" "),a=i.slice(3).join(" "),c=Ku(a);t.push(Bu({timestamp:i[0]??"",service:i[1]??n.replace(/\.log$/,""),source:"history",pid:0,level:i[2]??"INFO",message:a},c??{body:a,raw:a},0))}}let r=t.sort((n,o)=>n.timestamp.localeCompare(o.timestamp));return r.forEach((n,o)=>{n.seq=o+1}),r}function JE(e){try{let t=JSON.parse(e);if(!De(t)||typeof t.service!=="string"||typeof t.seq!=="number")return;return t}catch{return}}function XE(e){let t=e.replace(/[^A-Za-z0-9._-]+/g,"_");return t===""?"service":t}function QE(e,t,r){if(!So(e))return;let n=Ti(e).filter((s)=>s.startsWith(tp)).map((s)=>{let i=bn(e,s),a=Zu(i);return{path:i,mtime:a.mtimeMs}}).sort((s,i)=>i.mtime-s.mtime),o=t>0?Date.now()-t*86400000:0;n.forEach((s,i)=>{let a=o>0&&s.mtime<o,c=r>0&&i>=r;if(a||c)BE(s.path,{recursive:!0,force:!0})})}ne();var{spawn:ip}=globalThis.Bun;import{createServer as Oy}from"net";var ZE=65535,sp=1;async function My(e,t,r={}){let n={};for(let[s,i]of Object.entries(r))for(let a of Object.values(i))n[a]=s;let o={};for(let[s,i]of Object.entries(e.services)){if(t.length>0&&!t.includes(s))continue;let a={};for(let c of i.ports){let l=r[s]?.[c.name],d=l&&l>=sp?l:c.value;if(l===void 0&&c.auto)d=await eA();if(d<sp||d>ZE)throw new Ze(ee,`invalid port ${d} on service ${s}`,{service:s});if(n[d]&&n[d]!==s)throw new Ze(ee,`duplicate port ${d} used by ${n[d]} and ${s}`,{service:s});if(l!==d&&!c.auto&&!await ws(d))throw await tA(s,c.name,d);n[d]=s,a[c.name]=d}o[s]=a}return o}function eA(){return new Promise((e,t)=>{let r=Oy();r.listen(0,"127.0.0.1",()=>{let n=r.address();r.close(()=>{if(n&&typeof n==="object")e(n.port);else t(xe(ee,"unable to allocate dynamic port",Error("no address")))})}),r.on("error",(n)=>t(xe(ee,"unable to allocate dynamic port",n)))})}async function kc(e){let t=e.ports.filter((n)=>!n.auto&&n.value>=sp);if(t.length===0)return;let r={};for(let n of t){if(await ws(n.value))return;r[n.name]=n.value}return r}function ws(e){return new Promise((t)=>{let r=Oy();r.listen(e,"127.0.0.1",()=>{r.close(()=>t(!0))}),r.on("error",()=>t(!1))})}async function tA(e,t,r){let n=await Ro(r);return rA(e,t,r,n)}function rA(e,t,r,n){let o=t===""?`${e} port`:`${e} ports.${t}`;if(n&&n.pid===process.pid)return Ae(qe,`${e} blocked: this TUI already holds port ${r}`,`Stop the proxy from the Proxy screen, or change ${o} in .devctl.`,e);if(n)return Ae(qe,`${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 Ae(qe,`${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 nA(e,t){let r=e.split(`
|
|
2364
|
+
`).filter((n)=>n.trim()!==""&&!n.startsWith("COMMAND"));for(let n of r){let o=n.trim().split(/\s+/),s=o[0]??"",i=Number(o[1]);if(s!==""&&Number.isInteger(i)&&i>0)return{port:t,pid:i,command:s}}return}async function Ro(e){if(process.platform==="win32")return aA(e);let t=await oA(e);if(t)return t;return sA(e)}async function oA(e){try{let t=ip({cmd:["lsof","-nP",`-iTCP:${e}`,"-sTCP:LISTEN"],stdout:"pipe",stderr:"ignore"}),r=await new Response(t.stdout).text();return await t.exited,nA(r,e)}catch{return}}async function sA(e){let t;try{let o=ip({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 iA(e,t){for(let r of e.split(`
|
|
2365
|
+
`)){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 aA(e){let t=ip({cmd:["netstat","-ano"],stdout:"pipe",stderr:"ignore"}),r=t.stdout?await new Response(t.stdout).text():"";return await t.exited,iA(r,e)}var cA=400;async function Cc(e){if(e.pid===process.pid)throw Ae(qe,`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 xe(qe,`could not stop pid ${e.pid} on port ${e.port}`,t)}await lA(cA);try{process.kill(e.pid,0),process.kill(e.pid,"SIGKILL")}catch{return}}function lA(e){return new Promise((t)=>{setTimeout(t,e)})}Nc();ne();import{readdirSync as k0,readFileSync as C0}from"fs";import{basename as Jv,dirname as R0,extname as _0,join as Lo,resolve as T0}from"path";import{parse as P0}from"yaml";lt();function H(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function yp(e){return H(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 _e(e){if(typeof e==="number")return e;if(typeof e==="string"){let t=Number(e);return Number.isNaN(t)?0:t}return 0}function dt(e){return e===!0}function Dt(e){if(!Array.isArray(e))return[];return e.map((t)=>G(t))}function vp(e){if(!Array.isArray(e))return[];return e.map((t)=>H(t)?{service:G(t.service),condition:G(t.condition)||"service_started"}:G(t))}function tn(e){if(!H(e))return{};let t={};for(let[r,n]of Object.entries(e))t[r]=G(n);return t}function Br(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 Ss()}function bp(e){if(e===void 0||e===null)return[];if(typeof e==="string"||typeof e==="number")return[gp(e,"http")];if(Array.isArray(e))return e.map((t,r)=>gp(t,`port_${r}`));if(H(e))return Object.entries(e).map(([t,r])=>gp(r,t));return[]}function gp(e,t){if(typeof e==="string"&&e.toLowerCase()==="auto")return{name:t,value:0,auto:!0};return{name:t,value:_e(e),auto:!1}}function Ky(e){if(!H(e))return lp();let t={},r=[],n={};for(let[o,s]of Object.entries(e))if(o==="required")r=Dt(s);else if(o==="defaults")n=tn(s);else t[o]=G(s);return{vars:t,required:r,defaults:n}}function xA(e){if(!H(e))return up();return{type:G(e.type),mode:G(e.mode),service_account:G(e.service_account),config:H(e.config)?{...e.config}:{}}}function wA(e){if(!H(e))return dp();return{type:G(e.type),url:G(e.url),address:G(e.address),command:Br(e.command),interval_seconds:_e(e.interval_seconds),timeout_seconds:_e(e.timeout_seconds),start_period_seconds:_e(e.start_period_seconds),unhealthy_threshold:e.unhealthy_threshold===void 0?3:_e(e.unhealthy_threshold),healthy_reset_threshold:e.healthy_reset_threshold===void 0?10:_e(e.healthy_reset_threshold)}}function SA(e){if(!H(e))return{policy:"",max_retries:0,backoff_seconds:0};return{enabled:e.enabled===void 0?void 0:dt(e.enabled),policy:G(e.policy),max_retries:_e(e.max_retries),backoff_seconds:_e(e.backoff_seconds)}}function kA(e){if(!H(e))return{wait_for_healthy:!1,timeout_seconds:0};return{wait_for_healthy:dt(e.wait_for_healthy),timeout_seconds:_e(e.timeout_seconds)}}function Li(e){if(!H(e))return Hn();let t=H(e.logs)?e.logs:{};return{extends:G(e.extends),description:G(e.description),command:Br(e.command),shell:dt(e.shell),working_dir:G(e.working_dir),dependencies:vp(e.dependencies),ports:bp(e.ports),environment:Ky(e.environment),health:wA(e.health),identity:xA(e.identity),logs:{stdout:dt(t.stdout),stderr:dt(t.stderr)},restart:SA(e.restart),startup:kA(e.startup),capabilities:Dt(e.capabilities),proxy:Sp(e.proxy),expose:wp(e.expose),container:xp(e.container),watch:CA(e.watch),hooks:RA(e.hooks)}}function CA(e){if(!H(e))return ap();return{enabled:dt(e.enabled),paths:Dt(e.paths),debounce_ms:Pc(_e(e.debounce_ms)),ignore:e.ignore===void 0?[...Pi]:Dt(e.ignore)}}function RA(e){let t=H(e)?e:{};return{pre_start:Br(t.pre_start),post_start:Br(t.post_start)}}function Vy(e){let t=H(e)?e:{};return{command:Br(t.command),shell:dt(t.shell),working_dir:G(t.working_dir),dependencies:Dt(t.dependencies),environment:Ky(t.environment)}}function xp(e){if(!H(e))return;let t={};if(H(e.ports))for(let[r,n]of Object.entries(e.ports))t[r]=_e(n);return{image:G(e.image),runtime:G(e.runtime),ports:t,env:tn(e.env),volumes:Dt(e.volumes)}}function wp(e){if(e===!0)return{enabled:!0,host:"",port:""};if(e===!1)return{enabled:!1,host:"",port:""};if(H(e))return{enabled:e.enabled!==void 0?dt(e.enabled):!0,host:G(e.host),port:G(e.port)};return cp()}function Sp(e){if(e===void 0||e===null)return[];if(Array.isArray(e))return e.map((t)=>Eo(t));if(H(e))return[Eo(e)];return[]}function $c(e){if(!H(e))return{services:[],environment:{}};return{services:Dt(e.services),environment:tn(e.environment)}}function _A(e){if(typeof e==="string")return{type:e,service_account:""};if(!H(e))return{type:"",service_account:""};return{type:G(e.type),service_account:G(e.service_account)}}function TA(e){if(!H(e))return Ei();return{type:G(e.type),identity:_A(e.identity),audience:G(e.audience),service_account:G(e.service_account),client_id:G(e.client_id),client_secret:G(e.client_secret),credentials:G(e.credentials),headers:tn(e.headers)}}function Eo(e){if(!H(e))return{name:"",match:{host:"",path:""},upstream:{url:""},auth:Ei()};let t=H(e.match)?e.match:{},r=H(e.upstream)?e.upstream:{};return{name:G(e.name),transport:G(e.transport),match:{host:G(t.host),path:G(t.path)},upstream:{url:G(r.url),service:G(r.service),port:G(r.port)},auth:TA(e.auth),response_headers:tn(e.response_headers),listen:H(e.listen)?{host:G(e.listen.host),port:_e(e.listen.port)}:void 0}}ne();import{existsSync as PA,statSync as Yy}from"fs";import{basename as zy,dirname as Fc,isAbsolute as EA,join as Hc,resolve as Jy}from"path";function Ao(e,t){if(t!=="")return AA(t);let r=e===""?process.cwd():e;r=EA(r)?r:Jy(r);for(;;){let n=Hc(r,qt,_r);if(Di(n))return{repoRoot:r,configPath:n};let o=Hc(r,"devctl.yaml");if(Di(o))return{repoRoot:r,configPath:o};let s=Fc(r);if(s===r)break;r=s}throw Ae(ko,"no devctl configuration found","run `devctl setup` or create a .devctl/config.yaml in the repository root")}function AA(e){let t=Jy(e),r;try{r=Yy(t)}catch(o){throw xe(ko,"config path not found",o)}if(r.isDirectory()){if(zy(t)===qt)return{repoRoot:Fc(t),configPath:Hc(t,_r)};return{repoRoot:t,configPath:Hc(t,_r)}}let n=Fc(t);if(zy(n)===qt)n=Fc(n);return{repoRoot:n,configPath:t}}function Di(e){return PA(e)&&!Yy(e).isDirectory()}lt();function Bc(){return{services:{},templates:{},provenance:{}}}function Sn(e,t,r,n,o=""){if(H(t)&&Object.keys(t).length>0){for(let[s,i]of Object.entries(t))Sn(e,i,r,n,o===""?s:`${o}.${s}`);return}if(o==="")return;(e[o]??=[]).push({source:r,layer:n})}var IA=["environment","restart","startup","health","logs","identity","container","watch","hooks"];function Uc(e,t,r){let n=yp(r);if(H(r))for(let s of IA){let i=r[s];if(H(i))for(let a of Object.keys(i))n.add(`${s}.${a}`)}let o=e[t];e[t]=o?new Set([...o,...n]):n}function Xy(e,t,r=Bc(),n={source:"unknown",layer:"unknown"}){if(Sn(r.provenance,t,n.source,n.layer),t.version!==void 0)e.version=_e(t.version);if(H(t.project)&&t.project.name!==void 0)e.project.name=G(t.project.name);if(H(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(H(t.profiles))for(let[o,s]of Object.entries(t.profiles)){let i=e.profiles[o];e.profiles[o]=i?DA(i,s):$c(s)}if(H(t.templates))for(let[o,s]of Object.entries(t.templates)){let i=e.templates[o];e.templates[o]=i?jc(i,s):Li(s),Uc(r.templates,o,s)}if(H(t.services))for(let[o,s]of Object.entries(t.services)){let i=e.services[o];e.services[o]=i?jc(i,s):Li(s),Uc(r.services,o,s)}if(H(t.tasks))for(let[o,s]of Object.entries(t.tasks))e.tasks[o]=e.tasks[o]?OA(e.tasks[o],s):Vy(s);if(H(t.proxy))kp(e.proxy,t.proxy);if(H(t.logs)){if(t.logs.max_memory_events!==void 0)e.logs.max_memory_events=_e(t.logs.max_memory_events);if(H(t.logs.persistence)){let o=t.logs.persistence;if(o.enabled!==void 0)e.logs.persistence.enabled=dt(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=_e(o.retention_days);if(o.max_session_logs!==void 0)e.logs.persistence.max_session_logs=_e(o.max_session_logs)}}if(H(t.auth)&&t.auth.refresh_threshold_seconds!==void 0)e.auth.refresh_threshold_seconds=_e(t.auth.refresh_threshold_seconds);if(H(t.shutdown)){if(t.shutdown.stop_services_on_exit!==void 0)e.shutdown.stop_services_on_exit=dt(t.shutdown.stop_services_on_exit);if(t.shutdown.grace_seconds!==void 0)e.shutdown.grace_seconds=_e(t.shutdown.grace_seconds)}if(H(t.ui)){if(t.ui.theme!==void 0)e.ui.theme=G(t.ui.theme);if(t.ui.keymap!==void 0)e.ui.keymap=tn(t.ui.keymap)}if(H(t.secrets)){if(t.secrets.extra_markers!==void 0)e.secrets.extra_markers=Dt(t.secrets.extra_markers);if(t.secrets.extra_patterns!==void 0)e.secrets.extra_patterns=Dt(t.secrets.extra_patterns)}if(H(t.doctor)&&Array.isArray(t.doctor.tools))e.doctor.tools=t.doctor.tools.filter(H).map((o)=>({name:G(o.name),command:G(o.command)}));if(Array.isArray(t.plugins))e.plugins=t.plugins.filter(H).map((o)=>({path:G(o.path)})).filter((o)=>o.path!=="");if(H(t.environment)){if(Array.isArray(t.environment.sources))e.environment.sources=Dt(t.environment.sources);if(H(t.environment.secrets))e.environment.secrets={...e.environment.secrets,...tn(t.environment.secrets)}}if(H(t.telemetry))LA(e.telemetry,t.telemetry)}function LA(e,t){if(!H(t.otlp))return;let r=t.otlp;if(r.enabled!==void 0)e.otlp.enabled=dt(r.enabled);if(H(r.listen)){if(r.listen.host!==void 0)e.otlp.listen.host=G(r.listen.host);if(r.listen.port!==void 0)e.otlp.listen.port=_e(r.listen.port)}}function kp(e,t){if(t.enabled!==void 0)e.enabled=dt(t.enabled);if(t.gateway!==void 0)e.gateway=dt(t.gateway);if(t.credentials!==void 0)e.credentials=G(t.credentials);if(H(t.listen)){if(t.listen.host!==void 0)e.listen.host=G(t.listen.host);if(t.listen.port!==void 0)e.listen.port=_e(t.listen.port)}if(H(t.token_endpoint)){let r=t.token_endpoint;if(r.enabled!==void 0)e.token_endpoint.enabled=dt(r.enabled);if(r.host!==void 0)e.token_endpoint.host=G(r.host);if(r.port!==void 0)e.token_endpoint.port=_e(r.port)}if(Array.isArray(t.routes))e.routes=t.routes.map((r)=>Eo(r))}function DA(e,t){if(!H(t))return e;return{services:t.services!==void 0?Dt(t.services):e.services,environment:t.environment!==void 0?{...e.environment,...tn(t.environment)}:e.environment}}function OA(e,t){if(!H(t))return e;return{command:t.command!==void 0?Br(t.command):e.command,shell:t.shell!==void 0?dt(t.shell):e.shell,working_dir:t.working_dir!==void 0?G(t.working_dir):e.working_dir,dependencies:t.dependencies!==void 0?Dt(t.dependencies):e.dependencies,environment:Qy(e.environment,t.environment)}}function jc(e,t){if(!H(t))return e;let r=yp(t),n={...e,environment:Qy(e.environment,t.environment),health:$A(e.health,t.health),identity:FA(e.identity,t.identity),logs:HA(e.logs,t.logs),restart:UA(e.restart,t.restart),startup:jA(e.startup,t.startup),container:BA(e.container,t.container),watch:MA(e.watch,t.watch),hooks:NA(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=Br(t.command);if(r.has("shell"))n.shell=dt(t.shell);if(r.has("working_dir"))n.working_dir=G(t.working_dir);if(r.has("dependencies"))n.dependencies=vp(t.dependencies);if(r.has("ports"))n.ports=bp(t.ports);if(r.has("capabilities"))n.capabilities=Dt(t.capabilities);if(r.has("proxy"))n.proxy=Sp(t.proxy);if(r.has("expose"))n.expose=wp(t.expose);return n}function MA(e,t){if(!H(t))return e;return{enabled:t.enabled!==void 0?dt(t.enabled):e.enabled,paths:t.paths!==void 0?Dt(t.paths):e.paths,debounce_ms:t.debounce_ms!==void 0?Pc(_e(t.debounce_ms)):e.debounce_ms,ignore:t.ignore!==void 0?Dt(t.ignore):e.ignore}}function NA(e,t){if(!H(t))return e;return{pre_start:t.pre_start!==void 0?Br(t.pre_start):e.pre_start,post_start:t.post_start!==void 0?Br(t.post_start):e.post_start}}function Qy(e,t){if(!H(t))return e;let r={...e.vars},n={...e.defaults},o=e.required;for(let[s,i]of Object.entries(t))if(s==="required")o=Dt(i);else if(s==="defaults")Object.assign(n,tn(i));else r[s]=G(i);return{vars:r,required:o,defaults:n}}function $A(e,t){if(!H(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?Br(t.command):e.command,interval_seconds:t.interval_seconds!==void 0?_e(t.interval_seconds):e.interval_seconds,timeout_seconds:t.timeout_seconds!==void 0?_e(t.timeout_seconds):e.timeout_seconds,start_period_seconds:t.start_period_seconds!==void 0?_e(t.start_period_seconds):e.start_period_seconds,unhealthy_threshold:t.unhealthy_threshold!==void 0?_e(t.unhealthy_threshold):e.unhealthy_threshold,healthy_reset_threshold:t.healthy_reset_threshold!==void 0?_e(t.healthy_reset_threshold):e.healthy_reset_threshold}}function FA(e,t){if(!H(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&&H(t.config)?{...e.config??{},...t.config}:e.config}}function HA(e,t){if(!H(t))return e;return{stdout:t.stdout!==void 0?dt(t.stdout):e.stdout,stderr:t.stderr!==void 0?dt(t.stderr):e.stderr}}function UA(e,t){if(!H(t))return e;return{enabled:t.enabled!==void 0?dt(t.enabled):e.enabled,policy:t.policy!==void 0?G(t.policy):e.policy,max_retries:t.max_retries!==void 0?_e(t.max_retries):e.max_retries,backoff_seconds:t.backoff_seconds!==void 0?_e(t.backoff_seconds):e.backoff_seconds}}function jA(e,t){if(!H(t))return e;return{wait_for_healthy:t.wait_for_healthy!==void 0?dt(t.wait_for_healthy):e.wait_for_healthy,timeout_seconds:t.timeout_seconds!==void 0?_e(t.timeout_seconds):e.timeout_seconds}}function BA(e,t){if(!H(t))return e;let r=xp(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 Zy(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((s,i)=>{let c={name:o.length===1?n:`${n}-${i+1}`,match:s.match,upstream:s.upstream,auth:s.auth},l=e.proxy.routes.length;if(e.proxy.routes.push(c),t)Sn(t,c,`synthesized from services.${n}.proxy`,"synthesized",`proxy.routes.${l}`)})}WA(e,t)}function ev(e){let t=e.repoRoot||process.cwd(),r=e.proxy.credentials.trim()===""?"":mi(e.proxy.credentials.trim(),t);e.proxy.credentials=r;for(let n of e.proxy.routes){if(n.auth.client_id.trim()==="")continue;let o=(n.auth.credentials??"").trim();n.auth.credentials=o===""?r:mi(o,t)}}function WA(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&&Po(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:Ei()},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";Sn(t,a,`synthesized from ${l}`,"synthesized",`proxy.routes.${c}`)}}}function tv(e,t=Bc()){if(Object.keys(e.templates).length===0)return;let r={};for(let[n,o]of Object.entries(e.services))r[n]=rv(e,o,t.services[n]??new Set,t,{});e.services=r}function rv(e,t,r,n,o){if(t.extends==="")return t;if(o[t.extends])throw Error(`template cycle involving "${t.extends}"`);let s=e.templates[t.extends]??Hn();if(!e.templates[t.extends])throw Error(`service extends unknown template "${t.extends}"`);let i={...o,[t.extends]:!0},a=n.templates[t.extends]??new Set,c=rv(e,s,a,n,i),l=GA(c,t,r);return l.extends=t.extends,l}function GA(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,s=e.container;n.container=o&&s?{image:r.has("container.image")?o.image:s.image,runtime:r.has("container.runtime")?o.runtime:s.runtime,ports:r.has("container.ports")?o.ports:s.ports,env:r.has("container.env")?o.env:s.env,volumes:r.has("container.volumes")?o.volumes:s.volumes}:o??s}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}ne();lt();function nv(e){if(e.version===0)throw R(ee,"version is required");if(e.version===ks)return e;throw R(ee,`unsupported config version ${e.version} (expected ${ks}); no migration is available`)}var ov=["version","project","google","profiles","templates","services","tasks","proxy","logs","auth","shutdown","ui","secrets","doctor","plugins","environment","telemetry"],sv=["extends","description","command","shell","working_dir","dependencies","ports","environment","health","identity","logs","restart","startup","capabilities","proxy","expose","container","watch","hooks"],iv=["type","url","address","command","interval_seconds","timeout_seconds","start_period_seconds","unhealthy_threshold","healthy_reset_threshold"],av=["service","condition"],cv=["type","mode","service_account","config"],lv=["enabled","policy","max_retries","backoff_seconds"],dv=["wait_for_healthy","timeout_seconds"],uv=["enabled","host","port"],pv=["enabled","gateway","credentials","listen","token_endpoint","routes"],Oi=["host","port"],Cp=["name","transport","listen","match","upstream","auth","response_headers"],Rp=["host","path"],_p=["url","service","port"],Tp=["type","identity","audience","service_account","client_id","client_secret","credentials","headers"],mv=["max_memory_events","persistence"],fv=["enabled","directory","retention_days","max_session_logs"],hv=["refresh_threshold_seconds"],gv=["stop_services_on_exit","grace_seconds"],yv=["theme","keymap"],vv=["name"],bv=["project_id","region"],xv=["services","environment"],Pp=["required","defaults"],wv=["stdout","stderr"],Sv=["extra_markers","extra_patterns"],kv=["tools"],Cv=["name","command"],Rv=["enabled","host","port"],_v=["path"],Tv=["sources","secrets"],Pv=["image","runtime","ports","env","volumes"],Ev=["enabled","paths","debounce_ms","ignore"],Av=["pre_start","post_start"],Iv=["command","shell","working_dir","dependencies","environment"],Lv=["otlp"],Dv=["enabled","listen"],Ov=["google","google_api","iap","network","service_identity","local_http"],Mv=["|","||","&&",";",">",">>","<","&"];var qA=2;function Gn(e,t){if(Array.isArray(e))return e.flatMap((o,s)=>Gn(o,Nv(t,String(s))));if(!QA(e))return[];let r=VA(t),n=[];for(let o of Object.keys(e)){let s=Nv(t,o);if(r.length>0&&!r.includes(o)&&!KA(t))n.push(s);n.push(...Gn(e[o],s))}return n}function KA(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(".auth.headers")||e.endsWith(".response_headers"))return!0;if(e.endsWith(".identity.config")||e.includes(".identity.config."))return!0;return e.includes(".environment.")&&!e.endsWith(".environment")}function VA(e){switch(e){case"":return ov;case"project":return vv;case"google":return bv;case"proxy":return pv;case"proxy.listen":return Oi;case"proxy.token_endpoint":return Rv;case"logs":return mv;case"logs.persistence":return fv;case"auth":return hv;case"shutdown":return gv;case"ui":return yv;case"secrets":return Sv;case"doctor":return kv;case"environment":return Tv;case"telemetry":return Lv;case"telemetry.otlp":return Dv;case"telemetry.otlp.listen":return Oi;default:return zA(e)}}function zA(e){if(e==="services"||e==="profiles"||e==="templates"||e==="tasks")return[];if(e.startsWith("services.")||e.startsWith("templates.")){if(e.includes(".dependencies."))return av;return YA(e)}if(e.startsWith("profiles.")){if(e.split(".").length===2)return xv}if(e.startsWith("tasks.")){let t=e.split(".");if(t.length===2)return Iv;if(t[2]==="environment")return Pp}if(e.includes("routes"))return XA(e);if(e.startsWith("doctor.tools")&&e.split(".").length>=3)return Cv;if(e==="plugins")return[];if(e.startsWith("plugins.")&&e.split(".").length===2)return _v;return[]}function YA(e){let t=e.split(".");if(t.length===2)return sv;if(t.length>=3)switch(t[2]){case"health":return iv;case"identity":return cv;case"restart":return lv;case"startup":return dv;case"logs":return wv;case"environment":return Pp;case"proxy":return JA(t);case"expose":return uv;case"container":return Pv;case"watch":return Ev;case"hooks":return Av;default:return[]}return[]}function JA(e){let t=e.slice(3),r=t[0]!==void 0&&/^\d+$/.test(t[0])?1:0,n=t[r]??"";if(n==="match")return Rp;if(n==="upstream")return _p;if(n==="auth")return Tp;if(n==="listen")return Oi;return Cp}function XA(e){if(e==="proxy.routes")return[];if(e.endsWith(".match"))return Rp;if(e.endsWith(".upstream"))return _p;if(e.endsWith(".auth"))return Tp;if(e.endsWith(".listen"))return Oi;if(e.includes("proxy.routes")&&e.split(".").length===qA+1)return Cp;return[]}function Nv(e,t){if(e==="")return t;return`${e}.${t}`}function QA(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function Cs(e){return`unknown fields: ${e.join(", ")}`}lt();function sr(e){let t=e.trim().toLowerCase();if(t==="")return!0;if(t==="localhost"||t==="::1")return!0;if($v(t))return!0;return ZA(t)}function $v(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 ZA(e){if(!e.startsWith("::ffff:"))return!1;return $v(e.slice(7))}import{existsSync as r0}from"fs";import{isAbsolute as n0,resolve as o0}from"path";import{fileURLToPath as s0}from"url";lt();function Fv(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=By(e.ports);if(r&&!r.auto)return r.value;return}function e0(e,t,r,n=""){let o=e,s="";for(;;){let i=o.indexOf("${");if(i<0)return s+o;s+=o.slice(0,i);let a=o.slice(i).indexOf("}");if(a<0)throw Error(`unclosed environment reference in "${e}"`);let c=o.slice(i+2,i+a);s+=t0(c,t,r,n),o=o.slice(i+a+1)}}function t0(e,t,r,n){let o=e.split(".");if(o[0]==="identity"){if(o.length===2&&o[1]==="user")return n;throw Error(`unsupported reference \${${e}}`)}if(o.length<3||o[0]!=="services")throw Error(`unsupported reference \${${e}}`);let s=o[1]??"",i=t.services[s];if(!i)throw Error(`reference \${${e}}: unknown service`);let a=r[s];if(o[2]==="host"||o[2]==="url"){let c=t.proxy.enabled?t.proxy.routes.find((d)=>d.upstream.service===s):void 0;if(o[2]==="host")return c?c.match.host||"127.0.0.1":"127.0.0.1";if(c)return`http://${c.match.host||"127.0.0.1"}:${t.proxy.listen.port}${c.match.path}`;let l=Fv(i,a);if(l===void 0)throw Error(`unresolvable reference \${${e}}`);return`http://127.0.0.1:${l}`}if(o[2]==="port"){let c=Fv(i,a);if(c!==void 0)return String(c)}if(o[2]==="ports"){let c=o[3];if(!c)throw Error(`reference \${${e}}: missing port name`);if(a&&a[c]!==void 0)return String(a[c]);let l=Po(i.ports,c);if(l&&!l.auto)return String(l.value);let d=Number.parseInt(c,10);if(!Number.isNaN(d)&&d>=0&&d<i.ports.length){let u=i.ports[d];if(u&&!u.auto)return String(u.value)}}throw Error(`unresolvable reference \${${e}}`)}function Ep(e,t,r,n=""){let o={};for(let[s,i]of Object.entries(e))o[s]=e0(i,t,r,n);return o}function Hv(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 Uv(e,t){let r=e.split(".");if(r.length<2)return!1;if(r[0]==="identity")return r.length===2&&r[1]==="user";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 Po(n.ports,o)!==void 0}}return!0}lt();var Rs=65535,_s=1,Kv=["http","tcp","process","command"];function Ip(e){let t=[];for(let[r,n]of Object.entries(e.services)){let o=n.health.type;if(o!==""&&!Kv.includes(o.toLowerCase()))t.push({service:r,type:o})}return t}var i0=0,Gv=1,qv=2;function Io(e){let t=[];if(e.version===0)t.push("version is required");else if(e.version!==ks)t.push(`unsupported config version ${e.version} (expected ${ks}); no migration is available`);if(Object.keys(e.services).length===0)t.push("at least one service must be defined");t.push(...c0(e)),t.push(...a0(e)),t.push(...p0(e)),t.push(...m0(e)),t.push(...f0(e)),t.push(...b0(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:")?s0(n.path):n0(n.path)?n.path:o0(e.repoRoot,n.path);if(!r0(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 a0(e){let t=[];for(let[r,n]of Object.entries(e.tasks)){let o=`tasks.${r}`;if(Un(n.command))t.push(`${o}.command is required`);t.push(...Gc(o,n.command,n.shell));for(let s of n.dependencies)if(!e.services[s])t.push(`${o}.dependencies: unknown service "${s}"`);t.push(...Vv(`${o}.environment`,n.environment,e))}return t}function c0(e){let t=[],r={};for(let[n,o]of Object.entries(e.services)){let s=`services.${n}`;if(Un(o.command)&&!o.container)t.push(`${s}.command is required`);t.push(...Gc(s,o.command,o.shell)),t.push(...Gc(`${s}.hooks.pre_start`,o.hooks.pre_start,o.shell)),t.push(...Gc(`${s}.hooks.post_start`,o.hooks.post_start,o.shell)),t.push(...w0(s,o.capabilities));for(let c of o.dependencies){let l=or(c);if(!e.services[l])t.push(`${s}.dependencies: unknown service "${l}"`);if(l===n)t.push(`${s}.dependencies: service cannot depend on itself`);if(!["service_started","service_healthy"].includes(Fn(c)))t.push(`${s}.dependencies: condition must be service_started or service_healthy`);if(Fn(c)==="service_healthy"&&e.services[l]?.health.type==="")t.push(`${s}.dependencies: service_healthy requires ${l} to define a health check`)}if(o.extends!==""&&!e.templates[o.extends])t.push(`${s}.extends: unknown template "${o.extends}"`);for(let c of o.ports.filter((l)=>!l.auto))if(c.value<_s||c.value>Rs)t.push(`${s}.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 i=u0(`${s}.identity`,o.identity,e.plugins.length>0);if(i!=="")t.push(i);if(t.push(...l0(s,o,e.plugins.length>0)),o.health.start_period_seconds<0)t.push(`${s}.health.start_period_seconds must be >= 0`);if(o.health.unhealthy_threshold<1)t.push(`${s}.health.unhealthy_threshold must be >= 1`);if(o.health.healthy_reset_threshold<1)t.push(`${s}.health.healthy_reset_threshold must be >= 1`);let a=Ac(o.restart);if(a!==Rc&&a!==_c&&a!==Tc)t.push(`${s}.restart.policy must be never, on_failure, or always`);if(t.push(...Vv(`${s}.environment`,o.environment,e)),o.container){if(o.container.image==="")t.push(`${s}.container.image is required`);if(o.container.runtime!==""&&o.container.runtime!=="docker"&&o.container.runtime!=="podman")t.push(`${s}.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(`${s}.container.ports.${c}: no matching service port`);if(l<_s||l>Rs)t.push(`${s}.container.ports.${c}: invalid container port ${l}`)}}}return t}function l0(e,t,r){let n=[];if(t.health.type==="")return n;let o=t.health.type.toLowerCase();if(!Kv.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"&&Un({args:t.health.command.args,shell:!1}))n.push(`${e}.health.command is required for command health checks`);return n}var d0=["","user","service","service_account"];function u0(e,t,r){let n=Ii(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 Lp(e){let t=[];for(let[r,n]of Object.entries(e.services)){let o=Ii(n.identity).toLowerCase();if(!d0.includes(o))t.push({service:r,type:o})}return t}function p0(e){let t={},r=[],n=[],o=(s)=>{let i=t[s]??i0;if(i===qv)return;if(i===Gv){r.push(`dependency cycle: ${[...n,s].join(" \u2192 ")}`);return}t[s]=Gv,n.push(s);let a=e.services[s];if(a)for(let c of a.dependencies){let l=or(c);if(e.services[l])o(l)}n.pop(),t[s]=qv};for(let s of Object.keys(e.services))o(s);return r}function Vv(e,t,r){let n=[],o=(s,i)=>{for(let a of Hv(i))if(!Uv(a,r))n.push(`${e}.${s}: unresolvable reference \${${a}}`)};for(let[s,i]of Object.entries(t.vars))o(s,i);for(let[s,i]of Object.entries(t.defaults))o(s,i);return n}function m0(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 f0(e){let t=[];if(e.proxy.listen.host!==""){if(!zv(e.proxy.listen.host))t.push("proxy.listen.host must be an IP address or localhost");else if(!sr(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<_s||e.proxy.listen.port>Rs))t.push("proxy.listen.port is invalid");let r={},n=new Set;return e.proxy.routes.forEach((o,s)=>{let i=`proxy.routes[${s}]`;if(o.name==="")t.push(`${i}.name is required`);else if(r[o.name])t.push(`${i}: duplicate route name ${o.name}`);r[o.name]=!0;let a=(o.transport??"").toLowerCase();if(a!==""&&a!=="http"&&a!=="grpc")t.push(`${i}.transport must be "http" or "grpc"`);if(t.push(...g0(o,i,e)),t.push(...y0(o,i)),_o(o))t.push(...h0(o,i,n,e))}),t.push(...x0(e)),t}function h0(e,t,r,n){let o=[],s=e.listen;if(!s||s.port===0)o.push(`${t}.listen.port is required for a grpc route`);else{if(s.port<_s||s.port>Rs)o.push(`${t}.listen.port is invalid`);if(r.has(s.port))o.push(`${t}.listen.port ${s.port} is already used by another grpc route`);if(r.add(s.port),s.port===n.proxy.listen.port)o.push(`${t}.listen.port must differ from proxy.listen.port`);if(n.proxy.token_endpoint.enabled&&s.port===n.proxy.token_endpoint.port)o.push(`${t}.listen.port must differ from proxy.token_endpoint.port`);if(s.host!==""&&(!zv(s.host)||!sr(s.host)))o.push(`${t}.listen.host must be a loopback address`)}if(!(e.upstream.url??"").trim().toLowerCase().startsWith("https://"))o.push(`${t}.upstream.url must be an https:// address for a grpc route`);if(e.match.host!==""||e.match.path!=="")o.push(`${t}.match is not supported on a grpc route`);if(e.response_headers&&Object.keys(e.response_headers).length>0)o.push(`${t}.response_headers is not supported on a grpc route`);return o}function g0(e,t,r){let n=[],o=e.upstream.url!=="",s=e.upstream.service??"";if(s===""){if(!o)n.push(`${t}.upstream requires either url or service`);return n}let i=r.services[s];if(!i)return n.push(`${t}.upstream.service references unknown service ${s}`),n;let a=e.upstream.port||"http";if(Po(i.ports,a)===void 0)n.push(`${t}.upstream: service ${s} has no port named ${a}`);return n}function y0(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(...v0(e.auth,t));let n=e.auth.identity.type.toLowerCase();if(n==="service"||n==="service_account"||xn({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 v0(e,t){let r=(e.client_id??"").trim(),n=(e.client_secret??"").trim(),o=(e.credentials??"").trim();if(r===""&&n===""&&o==="")return[];if(e.type.toLowerCase()!=="iap")return[`${t}.auth.${r!==""?"client_id":n!==""?"client_secret":"credentials"} is only valid when auth.type is iap`];let s=[];if(r===""){if(n!=="")s.push(`${t}.auth.client_id is required when client_secret is set`);if(o!=="")s.push(`${t}.auth.credentials requires auth.client_id`);return s}if(n===""&&o==="")s.push(`${t}.auth.client_secret is required when client_id is set`);else if(n!==""&&Ap(n).length>0&&!Wv(n))s.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")s.push(`${t}.auth.client_id is only valid with identity.type user`);return s}function b0(e){let t=[],r=e.telemetry.otlp.listen.host||jr;if(!sr(r))t.push("telemetry.otlp.listen.host must be a loopback address");let n=e.telemetry.otlp.listen.port;if(n!==0&&(n<_s||n>Rs))t.push("telemetry.otlp.listen.port is invalid");if(n!==0){if(e.proxy.listen.port===n)t.push("telemetry.otlp.listen.port must differ from proxy.listen.port");if(e.proxy.token_endpoint.enabled&&e.proxy.token_endpoint.port===n)t.push("telemetry.otlp.listen.port must differ from proxy.token_endpoint.port");e.proxy.routes.forEach((o,s)=>{if(_o(o)&&o.listen&&o.listen.port===n)t.push(`telemetry.otlp.listen.port must differ from proxy.routes[${s}].listen.port`)})}return t}function x0(e){let t=[],r=e.proxy.token_endpoint;if(!r.enabled)return t;let n=r.host||jr;if(!sr(n))t.push("proxy.token_endpoint.host must be a loopback address");if(r.port!==0&&(r.port<_s||r.port>Rs))t.push("proxy.token_endpoint.port is invalid");return t}function Gc(e,t,r){if(t.shell||r)return[];for(let n of t.args)if(Mv.includes(n)||n.includes("|")||n.includes(";")||n.includes("&&"))return[`${e}.command contains shell metacharacters; set shell: true to run via a shell`];return[]}function w0(e,t){let r=[];for(let n of t)if(!Ov.includes(n))r.push(`${e}.capabilities: unknown capability "${n}"`);return r}function zv(e){if(e==="localhost")return!0;return S0(e)}function S0(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 Mi(e,t,r){try{return Ni(e,t,{candidateText:r}),[]}catch(n){if(n instanceof Ze){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 kn(e,t){let{repoRoot:r,configPath:n}=Ao(e,t);return Ni(r,n)}function Do(e,t){try{return kn(e,t)}catch(r){if(!Sc(r,ko))throw r;let n=e===""?process.cwd():T0(e),o=To();return o.repoRoot=n,o.configPath=Lo(n,qt,_r),o}}function Ni(e,t,r){let n=To(),o=Bc();Xv(t,n,o,r?.candidateText,"main"),n.repoRoot=e,n.configPath=t;let s=R0(t);if(Jv(s)===qt)A0(s,n,o);n=E0(n,e,t,o),n=nv(n);try{tv(n,o)}catch(a){throw xe(ee,"template merge failed",a)}Zy(n,o.provenance),ev(n),n.provenance=o.provenance;let i=Io(n);if(i.length>0)throw R(ee,i.join(`
|
|
2366
|
+
`));return n}function E0(e,t,r,n){let o=Lo(Cr(),"config.local.yaml"),s=Lo(t,qt,"config.local.yaml");for(let i of[o,s]){if(i===r||!Di(i))continue;Xv(i,e,n,void 0,i===o?"home_local":"repo_local")}return e}function Xv(e,t,r,n,o="main"){let s=n!==void 0?Qv(n,e):Dp(e),i=Gn(s,"");if(i.length>0)throw R(ee,Cs(i));Xy(t,s,r,{source:e,layer:o})}function A0(e,t,r){Yv(Lo(e,"services"),(o,s,i)=>{let a=Gn(s,`services.${o}`);if(a.length>0)throw R(ee,Cs(a));let c=t.services[o];t.services[o]=c?jc(c,s):Li(s),Uc(r.services,o,s),Sn(r.provenance,s,i,"modular_service",`services.${o}`)}),Yv(Lo(e,"profiles"),(o,s,i)=>{let a=Gn(s,`profiles.${o}`);if(a.length>0)throw R(ee,Cs(a));t.profiles[o]=$c(s),Sn(r.provenance,s,i,"modular_profile",`profiles.${o}`)});let n=Lo(e,"proxy","routes.yaml");if(Di(n))I0(n,t,r)}function I0(e,t,r){let n=Dp(e);if(H(n.proxy))Sn(r.provenance,n.proxy,e,"modular_proxy","proxy");if(Array.isArray(n.routes))Sn(r.provenance,n.routes,e,"modular_proxy","proxy.routes");if(H(n.proxy)){let o=Gn({proxy:n.proxy},"");if(o.length>0)throw R(ee,Cs(o))}if(Array.isArray(n.routes))n.routes.forEach((o,s)=>{let i=Gn(o,`proxy.routes.${s}`);if(i.length>0)throw R(ee,Cs(i))});if(H(n.proxy)){let o={...n.proxy};if(delete o.routes,kp(t.proxy,o),Array.isArray(n.proxy.routes))t.proxy.routes.push(...n.proxy.routes.map((s)=>Eo(s)))}if(Array.isArray(n.routes))t.proxy.routes.push(...n.routes.map((o)=>Eo(o)))}function Yv(e,t){let r;try{r=k0(e)}catch(n){if(L0(n))return;throw n}for(let n of r.sort()){let o=_0(n);if(o!==".yaml"&&o!==".yml")continue;let s=Lo(e,n),i=Dp(s);t(Jv(n,o),i,s)}}function Dp(e){let t;try{t=C0(e,"utf8")}catch(r){throw xe(ee,"unable to read config",r)}return Qv(t,e)}function Qv(e,t){let r;try{r=P0(e)}catch(n){throw xe(ee,`invalid YAML in ${t}`,n)}if(r===null||r===void 0)return{};if(!H(r))throw R(ee,`unable to decode config: ${t} is not a mapping`);return r}function L0(e){return typeof e==="object"&&e!==null&&"code"in e&&e.code==="ENOENT"}lt();ne();Wr();import{existsSync as Vc,readFileSync as O0}from"fs";import{homedir as nb}from"os";import{join as $p}from"path";var{spawn:Fp}=globalThis.Bun;var M0="https://www.googleapis.com/auth/cloud-platform",ob=1500,N0=2000;function Ps(){let e=globalThis;if(e.window&&typeof e.window.fetch!=="function")e.window.fetch=fetch}function Hp(){let e=process.env.GOOGLE_APPLICATION_CREDENTIALS||$p(nb(),".config","gcloud","application_default_credentials.json");if(!Vc(e))return"";try{let t=JSON.parse(O0(e,"utf8"));return typeof t.quota_project_id==="string"?t.quota_project_id:""}catch{return""}}async function No(e){let t={gcloudInstalled:await Up("gcloud"),adcAvailable:!1,userEmail:"",projectID:"",projectSource:""};if(e!=="")t.projectID=e,t.projectSource="configuration";else{let r=j0("GOOGLE_CLOUD_PROJECT","GCLOUD_PROJECT","GOOGLE_PROJECT");if(r!=="")t.projectID=r,t.projectSource="environment variable";else if(t.gcloudInstalled){let n=await rb("core/project");if(n!=="")t.projectID=n,t.projectSource="gcloud configuration"}}if(Fi())try{await lb($0(t),N0)}catch(r){t.adcAvailable=!1,t.error=Es(r)}if(t.userEmail===""&&t.gcloudInstalled)t.userEmail=await rb("core/account");return t}async function $0(e){Ps();let{GoogleAuth:t}=await import("google-auth-library"),r=new t({scopes:[M0]});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 F0(r);if(o!=="")e.userEmail=o}async function F0(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=H0(r);if(n!=="")return n}}catch{return""}return""}function H0(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 sb(e){let t=await No(e);return Tr({kind:"user",email:t.userEmail,project:t.projectID,projectSource:t.projectSource,adcAvailable:t.adcAvailable})}async function ib(){if(await Fp({cmd:["gcloud","auth","application-default","login"],stdin:"inherit",stdout:"inherit",stderr:"inherit"}).exited!==0)throw Ae($n,"application-default login failed","run `gcloud auth application-default login`")}async function ab(){if(await Fp({cmd:["gcloud","auth","application-default","revoke","--quiet"],stdout:"ignore",stderr:"pipe"}).exited!==0)throw Ae($n,"failed to revoke application-default credentials","run `gcloud auth application-default revoke`")}function Es(e){let t=U0(e).toLowerCase();if(Mo(t,["unauthorized_client"]))return Ae(wc,"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(Mo(t,["unauth","invalid_grant","token has been expired","expired"]))return Ae($n,"credential expired or invalid","run `devctl auth login` or `gcloud auth application-default login`");if(Mo(t,["api has not been used","has not been enabled","access not configured","service_disabled"]))return Ae(Co,"required Google API is not enabled","enable the API in the target service account project; devctl will not enable APIs automatically");if(Mo(t,["permission","forbidden","iam"])){if(Mo(t,["serviceaccounttoken","iamcredentials","token creator","getaccesstoken"]))return Ae(xc,"cannot impersonate service account","ask an administrator to grant roles/iam.serviceAccountTokenCreator on the target service account");return Ae(Co,"authorization failure","verify IAM permissions for the current user on this project")}if(t.includes("audience"))return Ae(wc,"IAP audience is incorrect","set auth.audience on the proxy route to the IAP OAuth client ID");if(t.includes("iap"))return xe(wc,"IAP authentication failure",e);if(t.includes("project"))return Ae(ee,"wrong or missing Google project","set google.project_id in .devctl/config.yaml");if(Mo(t,["timeout","network","connection refused","no such host"]))return xe(Qe,"network problem reaching Google Cloud",e);if(Mo(t,["adc","default credentials","could not find"]))return Ae($n,"application default credentials unavailable","run `gcloud auth application-default login`");return xe($n,"Google authentication failed",e)}function U0(e){let t=[],r=new Set,n=(o,s)=>{if(o===null||o===void 0||s>8||r.has(o))return;if(typeof o==="string"){t.push(o);return}if(typeof o!=="object")return;r.add(o);let i=o;for(let a of["message","reason","status","code","error_description","permission","service"])n(i[a],s+1);for(let a of["error","errors","details","response","data","cause","metadata"]){let c=i[a];if(Array.isArray(c))c.forEach((l)=>n(l,s+1));else n(c,s+1)}};return n(e,0),t.length>0?t.join(" "):String(e)}function Mo(e,t){return t.some((r)=>e.includes(r))}function j0(...e){for(let t of e){let r=process.env[t];if(r)return r}return""}async function Up(e){try{return(await cb(process.platform==="win32"?["where",e]:["which",e],ob)).code===0}catch{return!1}}function Fi(){let e=process.env.GOOGLE_APPLICATION_CREDENTIALS;if(e&&Vc(e))return!0;if(Vc($p(nb(),".config","gcloud","application_default_credentials.json")))return!0;let t=process.env.APPDATA;return Boolean(t&&Vc($p(t,"gcloud","application_default_credentials.json")))}async function rb(e){try{let t=await cb(["gcloud","config","get-value",e],ob);if(t.code!==0)return"";let r=t.stdout.trim();if(r===""||r==="(unset)")return"";return r}catch{return""}}async function cb(e,t){let r=Fp({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,s]=await lb(Promise.all([n,r.exited]),t);return{code:s??1,stdout:o}}catch{return r.kill(),{code:1,stdout:""}}}function lb(e,t){return new Promise((r,n)=>{let o=setTimeout(()=>n(Error("timeout")),t);e.then((s)=>{clearTimeout(o),r(s)},(s)=>{clearTimeout(o),n(s)})})}import{createHash as nI}from"crypto";import{readFileSync as oI}from"fs";import{join as sI}from"path";import{GoogleAuth as Ui,Impersonated as iI}from"google-auth-library";var{spawn:Gp}=globalThis.Bun;import{existsSync as Yc,readdirSync as B0,readFileSync as db,unlinkSync as W0}from"fs";import{join as Hi}from"path";var qp="keychain-index.json",$o="devctl.credentials",Bp=".cred.json";function Wp(e){return Hi($r(),`${Xc(e)}${Bp}`)}function Jc(e){if(e==="file"||process.env.DEVCTL_HOME&&e!=="keychain")return new zc;if(e==="keychain"||process.platform==="darwin"||process.platform==="win32"||process.platform==="linux")return new ub(new zc);return new zc}class zc{backend="file";async get(e){let t=V0(e);if(!t||t.accessToken==="")return;return t}async set(e,t){Fr(Wp(e),`${JSON.stringify({...K0(t),key:e})}
|
|
2367
|
+
`)}async delete(e){let t=Wp(e);if(Yc(t))W0(t)}async list(){let e=$r();if(!Yc(e))return[];let t=[];for(let r of B0(e)){if(!r.endsWith(Bp))continue;let n=r.slice(0,-Bp.length),o=pb(Hi($r(),r));if(!o)continue;t.push(mb(o.key??n,o.record))}return t.sort((r,n)=>r.identity.localeCompare(n.identity))}}class ub{backend="keychain";fallback;constructor(e){this.fallback=e}async get(e){let t=await z0(e);if(t)return t;return this.fallback.get(e)}async set(e,t){if(!await Y0(e,t))await this.fallback.set(e,t);G0(e)}async delete(e){await J0(e),await this.fallback.delete(e),q0(e)}async list(){let e=new Set(Kp()),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,mb(n,o))}return[...r.values()].sort((n,o)=>n.identity.localeCompare(o.identity))}}function G0(e){let t=Kp();if(!t.includes(e))Fr(Hi($r(),qp),`${JSON.stringify([...t,e])}
|
|
2368
|
+
`)}function q0(e){let t=Kp(),r=t.filter((n)=>n!==e);if(r.length!==t.length)Fr(Hi($r(),qp),`${JSON.stringify(r)}
|
|
2369
|
+
`)}function Kp(){let e=Hi($r(),qp);if(!Yc(e))return[];try{let t=JSON.parse(db(e,"utf8"));return Array.isArray(t)?t.filter((r)=>typeof r==="string"):[]}catch{return[]}}function K0(e){return{...e,accessToken:""}}function V0(e){return pb(Wp(e))?.record}function pb(e){if(!Yc(e))return;try{let t=JSON.parse(db(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 mb(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 Xc(e){return e.replace(/[^A-Za-z0-9._-]+/g,"_").slice(0,80)||"token"}async function z0(e){let t=await X0(e);if(!t)return;try{return JSON.parse(t)}catch{return}}async function Y0(e,t){let r=JSON.stringify(t);if(process.platform==="darwin")return await As(["security","delete-generic-password","-s",$o,"-a",e]),await As(["security","add-generic-password","-s",$o,"-a",e,"-w",r,"-U"])===0;if(process.platform==="linux")return await Q0(["secret-tool","store","--label",`devctl ${e}`,"service",$o,"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-${Xc(e)}.bin', $enc)`;return await As(["powershell","-NoProfile","-Command",o])===0}return!1}async function J0(e){if(process.platform==="darwin"){await As(["security","delete-generic-password","-s",$o,"-a",e]);return}if(process.platform==="linux"){await As(["secret-tool","clear","service",$o,"account",e]);return}if(process.platform==="win32")await As(["powershell","-NoProfile","-Command",`Remove-Item -ErrorAction SilentlyContinue ($env:LOCALAPPDATA + '\\devctl-cred-${Xc(e)}.bin')`])}async function X0(e){if(process.platform==="darwin"){let t=await jp(["security","find-generic-password","-s",$o,"-a",e,"-w"]);return t.code===0&&t.stdout!==""?t.stdout:void 0}if(process.platform==="linux"){let t=await jp(["secret-tool","lookup","service",$o,"account",e]);return t.code===0&&t.stdout!==""?t.stdout:void 0}if(process.platform==="win32"){let t=`$p = $env:LOCALAPPDATA + '\\devctl-cred-${Xc(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 jp(["powershell","-NoProfile","-Command",t]);return r.code===0&&r.stdout!==""?r.stdout:void 0}return}var Vp=3000;async function As(e){try{return await Gp({cmd:e,stdout:"ignore",stderr:"ignore",timeout:Vp}).exited}catch{return 1}}async function Q0(e,t){try{return await Gp({cmd:e,stdin:new TextEncoder().encode(t),stdout:"ignore",stderr:"ignore",timeout:Vp}).exited}catch{return 1}}async function jp(e){try{let t=Gp({cmd:e,stdout:"pipe",stderr:"ignore",timeout:Vp}),r=t.stdout?await new Response(t.stdout).text():"";return{code:await t.exited,stdout:r.trim()}}catch{return{code:1,stdout:""}}}ne();ne();var Z0=3,eI=200;async function fb(e,t={}){let r=t.attempts??Z0,n=t.backoffMs??eI,o=t.retry??tI,s=Error("retry exhausted");for(let i=0;i<r;i+=1)try{return await e()}catch(a){if(s=a,!o(a)||i===r-1)throw a;await rI(n*(i+1))}throw s}function tI(e){if(e instanceof Ze)return e.kind!==ee&&e.kind!==Co;return!0}function rI(e){return new Promise((t)=>{setTimeout(t,e)})}var Qc={now:()=>new Date,isoNow:()=>new Date().toISOString(),unixMs:()=>Date.now()};var zp=300000,aI=3000000,cI=3,lI=200,Zc="https://www.googleapis.com/auth/cloud-platform",gb="https://www.googleapis.com/auth/userinfo.email",dI="https://oauth2.googleapis.com/token";function hb(e,t,r,n){let o=n??"",s=`${e}|${t}|${r.join(",")}`;return o===""?s:`${s}|oauth:${o}`}function uI(e){let t;try{t=oI(e,"utf8")}catch{throw R(ee,`IAP credentials file not found: ${e}`)}let r;try{r=JSON.parse(t)}catch{throw R(ee,`IAP credentials file is not valid JSON: ${e}`)}if(typeof r!=="object"||r===null)throw R(ee,`IAP credentials file is malformed: ${e}`);let n=r,o=typeof n.refresh_token==="string"?n.refresh_token:"";if(o==="")throw R(ee,`IAP credentials file has no refresh_token: ${e}`);let s=typeof n.client_id==="string"?n.client_id:"";if(s==="")throw R(ee,`IAP credentials file has no client_id: ${e}`);return{clientId:s,clientSecret:typeof n.client_secret==="string"?n.client_secret:"",refreshToken:o}}function pI(e,t=process.env){let r=(e.client_id??"").trim();if(r==="")return;let n=(e.client_secret??"").trim(),{value:o,missing:s}=Bv(n,t),i=(e.credentials??"").trim();if(i!==""){let a=uI(i);if(a.clientId!==r)throw R(ee,`IAP credentials file ${i} is for client_id ${a.clientId}, not the route's ${r}`);let c=o!==""?o:a.clientSecret;if(c==="")throw R(ee,`IAP client_id ${r} requires a client_secret (in the route or ${i})`);return{clientId:r,clientSecret:c,refreshToken:a.refreshToken}}if(o===""){let a=s[0];throw R(ee,a?`IAP client_secret env ${a} is empty`:`IAP client_id ${r} requires client_secret`)}return{clientId:r,clientSecret:o}}function el(e,t=process.env){let r=(e.client_id??"").trim();if(r==="")return;return{clientId:r,resolve:()=>{let n=pI(e,t);if(!n)throw R(ee,`IAP client_id ${r} requires client_secret`);return n}}}function mI(e,t=zp,r=Date.now()){if(e.accessToken==="")return!1;return e.expiresAt.getTime()-r>=t}function fI(e,t=zp,r=Date.now()){if(e.accessToken==="")return!0;let n=e.expiresAt.getTime()-r;return n>0&&n<t}class Fo{cache=new Map;inflight=new Map;providers;thresholdMs;bus;store;clock;constructor(e,t,r,n,o=Qc){this.thresholdMs=e>0?e:zp,this.providers=[...t],this.bus=r,this.store=n??Jc(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 mI(e,this.thresholdMs,this.clock.unixMs())}expiresSoon(e){return fI(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=hb(e,t,r,n?.clientId),s=this.cache.get(o)??await this.loadStored(o);if(s&&this.isValid(s))return this.cache.set(o,s),s;return this.refresh(e,t,r,n)}async refresh(e,t,r,n){let o=hb(e,t,r,n?.clientId),s=this.inflight.get(o);if(s)return s;let i=this.refreshOnce(o,e,t,r,n);this.inflight.set(o,i);try{return await i}finally{this.inflight.delete(o)}}async refreshOnce(e,t,r,n,o){let s=o?.resolve(),i=this.providers.filter((c)=>!c.accepts||c.accepts(t,r,n,s));if(i.length===0)throw R(Ur,`no token provider accepts identity ${t}`);let a=R(Ur,"no token provider available");for(let c of i)try{let l=await fb(()=>c.fetch(t,r,n,s),{attempts:cI,backoffMs:lI,retry:AI});return this.cache.set(e,l),gI(e,l),await this.store.set(e,yI(l)),this.bus?.publish(ke(ps,"",{identity:l.identity,audience:l.audience})),l}catch(l){a=l instanceof Error?l:Error(String(l))}throw this.bus?.publish(ke(go,"",{identity:t,audience:r,error:I(a)})),a}async loadStored(e){let t=await this.store.get(e);if(!t||t.accessToken==="")return;return vI(t)}async clearStore(){let e=await this.store.list();await Promise.all(e.map((t)=>this.store.delete(t.key)))}}function hI(e){let t=nI("sha256").update(e).digest("hex").slice(0,16);return sI($r(),`${t}.json`)}function gI(e,t){let r={identity:t.identity,audience:t.audience,expires_at:t.expiresAt.toISOString(),scopes:t.scopes};Fr(hI(e),`${JSON.stringify(r)}
|
|
2370
|
+
`)}function yI(e){return{identity:e.identity,audience:e.audience,scopes:e.scopes,accessToken:e.accessToken,tokenType:e.tokenType,expiresAt:e.expiresAt.toISOString()}}function vI(e){return{accessToken:e.accessToken,tokenType:e.tokenType||"Bearer",expiresAt:new Date(e.expiresAt),audience:e.audience,identity:e.identity,scopes:e.scopes}}function Vn(){return[wI(),xI(),bI()]}function bI(){return{name:"user",accepts:(e,t)=>!e.startsWith("sa:")&&t==="",fetch:async(e,t,r)=>{if(e.startsWith("sa:"))throw R(Ur,"user provider cannot mint service-account tokens");return TI(e,t,r)}}}function xI(){return{name:"service_account",accepts:(e,t)=>e.startsWith("sa:")&&t==="",fetch:async(e,t,r)=>{if(!e.startsWith("sa:"))throw R(Ur,"service-account provider cannot mint user tokens");return SI(e,t,r)}}}function wI(){return{name:"iap",accepts:(e,t)=>t!=="",fetch:async(e,t,r,n)=>{if(t==="")throw R(Ur,"IAP audience is required");if(e.startsWith("sa:"))return kI(e,t,r);return CI(e,t,r,n)}}}async function SI(e,t,r){let n=e.slice(3);try{Ps();let s=await new Ui({scopes:r.length>0?r:[Zc]}).getClient(),i=new iI({sourceClient:s,targetPrincipal:n,targetScopes:r.length>0?r:[Zc],lifetime:3600}),a=await i.getAccessToken(),c=typeof a==="string"?a:a?.token;if(!c)throw R(Ur,"empty impersonated token");return{accessToken:c,tokenType:"Bearer",expiresAt:ji(c,i),audience:t,identity:e,scopes:r}}catch(o){throw Es(o)}}async function kI(e,t,r){let n=e.slice(3);try{Ps();let s=await new Ui({scopes:[Zc]}).getClient(),i=`https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${encodeURIComponent(n)}:generateIdToken`,c=(await s.request({url:i,method:"POST",data:{audience:t,includeEmail:!0}})).data.token;if(!c)throw R(Ur,"empty impersonated IAP id token");return{accessToken:c,tokenType:"Bearer",expiresAt:ji(c),audience:t,identity:e,scopes:r}}catch(o){throw Es(o)}}async function CI(e,t,r,n){try{if(Ps(),n)return await RI(e,t,r,n);let i=await(await new Ui({scopes:r.length>0?r:[gb]}).getIdTokenClient(t)).idTokenProvider.fetchIdToken(t);return{accessToken:i,tokenType:"Bearer",expiresAt:ji(i),audience:t,identity:e,scopes:r}}catch(o){if(o instanceof Ze)throw o;throw Es(o)}}async function RI(e,t,r,n){let o=n.refreshToken;if(!o){if(o=(await new Ui({scopes:r.length>0?r:[gb]}).getClient()).credentials.refresh_token??void 0,!o)throw Ae($n,"ADC has no refresh token for a custom IAP OAuth client","set auth.credentials (or proxy.credentials) to an authorized_user file whose refresh_token was issued by this client_id, or run `gcloud auth application-default login` with that client")}let s=await fetch(dI,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:_I(n,o,t)}),i=await s.text();if(!s.ok)throw Error(`IAP token exchange failed (${s.status}): ${i}`);let a;try{a=JSON.parse(i)}catch{throw R(Ur,"IAP token endpoint returned a non-JSON response")}let c=a.id_token;if(!c)throw R(Ur,"empty IAP id token");return{accessToken:c,tokenType:"Bearer",expiresAt:ji(c),audience:t,identity:e,scopes:r}}function _I(e,t,r){return new URLSearchParams({grant_type:"refresh_token",client_id:e.clientId,client_secret:e.clientSecret,refresh_token:t,audience:r})}async function TI(e,t,r){try{Ps();let o=await new Ui({scopes:r.length>0?r:[Zc]}).getClient(),s=await o.getAccessToken();if(!s.token)throw R(Ur,"empty access token");return{accessToken:s.token,tokenType:"Bearer",expiresAt:PI(o,s.token),audience:t,identity:e,scopes:r}}catch(n){throw Es(n)}}function ji(e,t){if(t?.credentials?.expiry_date)return new Date(t.credentials.expiry_date);return EI(e)??new Date(Date.now()+aI)}function PI(e,t){return ji(t,e)}function EI(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 AI(e){if(e instanceof Ze)return e.kind!==ee&&e.kind!==Co;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")}ne();Wr();var Wi=4000,II=8000;function Is(e){let t=e?.tokens,r=()=>(t??=new Fo(60000,Vn(),void 0,Jc("file")),t);return{detectGoogle:No,hasCommand:Up,portAvailable:ws,hasLocalAdc:Fi,adcQuotaProject:Hp,mintToken:async(n,o,s)=>{await r().get(n,o,[],s)},probeServiceUsage:async(n,o)=>{let s=await Gi(r().get("user","",[]),Wi),i=`https://serviceusage.googleapis.com/v1/projects/${n}/services/${o}`,a=await fetch(i,{headers:{Authorization:`Bearer ${s.accessToken}`},signal:AbortSignal.timeout(Wi)});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 tl=Is();function rl(e=Is()){return{run:(t,r,n)=>LI(t,e,r,n)}}async function LI(e,t=tl,r,n){let o={checks:[],issues:0},s="Preparing diagnostics",i=()=>r?.({active:s,checks:[...o.checks]}),a=(p)=>{s=p,i()},c=(p)=>{if(o.checks.push(p),p.severity==="error")o.issues+=1;i()},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 f=await t.hasCommand(p),h=f&&await(t.containerRuntimeAvailable??tl.containerRuntimeAvailable)(p);c(h?{name:`${p} container runtime`,severity:"ok",message:`${p} daemon reachable`}:{name:`${p} container runtime`,severity:"error",message:f?`${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:$i(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:$i(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:$i(e)?"error":"warn",message:"no Google project configured",hint:"set google.project_id in .devctl/config.yaml"});if($i(e)||Object.values(e.services).some((p)=>p.capabilities.includes("google")||p.identity.type!=="")){a("Live Google access");let p=!0,f=(h)=>{if(p)c(h)};try{await Gi((async()=>{await OI(e,f,t,d.userEmail),await DI(e,f,t)})(),t.liveDeadlineMs??II)}catch(h){f({name:"Live Google probes",severity:"warn",message:I(h),hint:"network or timeout \u2014 retry when online"})}finally{p=!1}}for(let p of e.doctor.tools){let f=p.command||p.name;if(a(`${p.name} installed`),await t.hasCommand(f))c({name:`${p.name} installed`,severity:"ok",message:`${f} found`});else c({name:`${p.name} installed`,severity:"error",message:`${f} 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=Io(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:I(p)})}let m={};for(let[p,f]of Object.entries(e.services))for(let h of f.ports){if(h.auto)continue;let g=`Port ${h.value}`;if(a(g),m[h.value])c({name:g,severity:"error",message:`configured on both ${m[h.value]} and ${p}`});else{m[h.value]=p;let x=n?.services?.[p],y=Boolean(f.container&&x&&["STARTING","RUNNING","HEALTHY","UNHEALTHY"].includes(x.state??""));if(Boolean(x&&(x.pid>0||y)&&Object.values(x.ports).includes(h.value)))c({name:g,severity:"ok",message:`in use by running service ${p}`});else if(await t.portAvailable(h.value))c({name:g,severity:"ok",message:"available"});else c(await vb(g,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 vb(p,e.proxy.listen.port,"proxy.listen.port"))}return s="Diagnostics complete",i(),o}async function DI(e,t,r){let n=e.google.project_id,o=r.hasLocalAdc??Fi;if(n===""||!o())return;let s=r.probeServiceUsage??tl.probeServiceUsage,i=[{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(i.map((c)=>Gi(s(n,c.service),Wi)))).forEach((c,l)=>{let d=i[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:I(c.reason),hint:`enable ${d.service} in the Google Cloud console \u2014 doctor never auto-enables APIs`})})}async function OI(e,t,r,n){let o=rn(e),s=e.proxy.routes.filter((u)=>u.auth.type.toLowerCase()==="iap"),i=s.filter((u)=>u.auth.audience.trim()!=="");for(let u of s)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&&i.length===0)return;if(!(r.hasLocalAdc??Fi)())return;let c=r.mintToken??tl.mintToken,l=o.map(async(u)=>{try{await Gi(c(`sa:${u}`,""),Wi),t({name:`Impersonate ${u}`,severity:"ok",message:"token minted"})}catch(m){let p=(r.adcQuotaProject??Hp)(),f=n||"the ADC principal",h=p===""?"unknown":p,g=yb(`Impersonate ${u}`,m,`grant roles/iam.serviceAccountTokenCreator to ${f} on ${u}; ADC quota project: ${h}`);if(p!==""&&g.message.toLowerCase().includes("api is not enabled"))g.message=`IAM Credentials API is disabled in ADC quota project ${p}`,g.hint=`enable iamcredentials.googleapis.com in ${p}, or set the ADC quota project to ${e.google.project_id}`;else if(g.message.toLowerCase()==="google authentication failed")g.message=`access-token mint failed: ${f} \u2192 ${u}`,g.hint=`verify roles/iam.serviceAccountTokenCreator for ${f} on this service account; ADC quota project: ${h}`;t(g)}}),d=i.map(async(u)=>{let m=Cn(u.auth).kind===Oo?`sa:${Cn(u.auth).serviceAccount}`:"user";try{let p=el(u.auth);await Gi(c(m,u.auth.audience,p),Wi),t({name:`IAP ${u.name}`,severity:"ok",message:"id token minted"})}catch(p){t(yb(`IAP ${u.name}`,p,"check IAP OAuth client ID and ADC"))}});await Promise.all([...l,...d])}function yb(e,t,r){let n=I(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 Ze&&t.hint!==""){let s=`${t.kind}: `,i=t.message.startsWith(s)?t.message.slice(s.length):t.message;return{name:e,severity:"error",message:i,hint:t.hint}}return{name:e,severity:"error",message:n,hint:r}}function Gi(e,t){return new Promise((r,n)=>{let o=setTimeout(()=>n(Error("timeout")),t);e.then((s)=>{clearTimeout(o),r(s)},(s)=>{clearTimeout(o),n(s)})})}async function vb(e,t,r){let n=await Ro(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 bb(e){let t=[`${hr()} 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(`
|
|
2247
2371
|
`)+`
|
|
2248
|
-
`}
|
|
2249
|
-
`);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(
|
|
2250
|
-
`)})}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
|
|
2251
|
-
`)}async function
|
|
2252
|
-
`).find((
|
|
2253
|
-
`)){let o=n.trim();if(o==="")continue;let[i,
|
|
2254
|
-
`);
|
|
2372
|
+
`}Nt();class _b{start;constructor(e){this.start=e}execute(e){return this.start({...e,profile:e.profile?Pr(e.profile):void 0,services:e.services?.map((t)=>Jp(t))})}}class Tb{stop;constructor(e){this.stop=e}execute(e){return this.stop(e.map((t)=>Jp(t)))}}class Pb{restart;constructor(e){this.restart=e}execute(e,t){return this.restart(e.map((r)=>Jp(r)),t)}}class Eb{start;constructor(e){this.start=e}execute(e,t){return this.start.execute({profile:e,client_env:t})}}class Ab{host;constructor(e){this.host=e}execute(){return this.host.startProxy()}}class Ib{host;constructor(e){this.host=e}execute(){return this.host.stopProxy()}}class Lb{host;constructor(e){this.host=e}execute(){return this.host.reload()}}class Db{host;constructor(e){this.host=e}execute(){return this.host.refreshIdentity({probeServiceAccounts:!0})}}class cl{runner;constructor(e){this.runner=e}execute(e,t,r){return this.runner.run(e,t,r)}}class ll{execute(e,t,r){return nl(e,t,r)}}class dl{execute(e,t){return ol(e,t)}}class Ob{host;constructor(e){this.host=e}execute(){return this.host.snapshot()}}class ul{execute(e,t){return al(e,t)}}function Jp(e){return xb(e)}function Mb(e,t,r){let n=r??e,o=new _b((s)=>n.start(s));return{startService:o,stopService:new Tb((s)=>n.stop(s)),restartService:new Pb((s,i)=>n.restart(s,i)),startProfile:new Eb(o),startProxy:new Ab(e),stopProxy:new Ib(e),reloadConfig:new Lb(e),refreshIdentity:new Db(e),runDoctor:new cl(t),getServiceStatus:new Ob(e),getStartupPlan:new ll,getShutdownPlan:new dl,resolveStart:new ul}}import{createConnection as YI}from"net";var{spawn:JI}=globalThis.Bun;import{readdirSync as $I}from"fs";import{join as Nb,resolve as Xp,sep as Qp}from"path";function FI(e){let t=Nb(Cr(),"state"),r;try{r=$I(t)}catch{return}let n=$b(Xp(e)),o;for(let s of r){let i=gu(Nb(t,s,"state.json"));if(!i)continue;let a=$b(Xp(i.repo_root));if(!HI(a,n))continue;if(!o||a.length>o.length)o=a}return o}function pl(e,t,r=""){if(t!=="")return{repoRoot:Xp(t),source:"explicit"};let n=e===""?process.cwd():e;try{let{repoRoot:s}=Ao(n,r);return{repoRoot:s,source:"config"}}catch{if(r!=="")return}let o=FI(n);return o?{repoRoot:o,source:"state-scan"}:void 0}function $b(e){let t=e.length>1&&e.endsWith(Qp)?e.slice(0,-1):e;return process.platform==="win32"?t.toLowerCase():t}function HI(e,t){return t===e||t.startsWith(e.endsWith(Qp)?e:`${e}${Qp}`)}import{existsSync as Hb,readFileSync as Ub}from"fs";import{isAbsolute as UI,join as Zp}from"path";import{parse as jI}from"dotenv";ne();var Vi=["process","profile","dotenv","generated","keychain","secret_manager","defaults","vars","runtime"],BI=/^projects\/[^/]+\/secrets\/[^/]+(?:\/versions\/[^/]+)?$/,WI=["process","defaults","vars","runtime"];function GI(){return{name:"dotenv",load:(e)=>{let t=Fb(e.repoRoot,e.profile);if(e.workDir!=="")Object.assign(t,Fb(e.workDir,e.profile));return t}}}function qI(e){let t=e?.environment.sources??[];if(t.length===0)return[...Vi];let r=new Set([...WI,...t]),n=new Set(Vi),o=t.filter((i)=>!n.has(i)),s=[];for(let i of Vi){if(i==="defaults")s.push(...o);if(r.has(i))s.push(i)}return s}async function zi(e,t){let r={},n=t.serviceCfg.working_dir;if(n!==""&&!UI(n))n=Zp(e,n);let o={repoRoot:e,profile:t.profile,service:t.service,serviceCfg:t.serviceCfg,workDir:n,cfg:t.cfg},s=KI(t),i=t.userEmail??"",a={process:t.includeProcess===!1?{}:t.clientEnv??Ds(),profile:Ki(t.profileEnv,t.cfg,s,i),dotenv:Ki(await GI().load(o),t.cfg,s,i),generated:{},keychain:t.sourceValues?.keychain??VI(o),secret_manager:t.sourceValues?.secret_manager??await zI(o,t.fetchSecret),defaults:Ki(t.serviceCfg.environment.defaults,t.cfg,s,i),vars:Ki(t.serviceCfg.environment.vars,t.cfg,s,i),runtime:t.runtime};for(let c of qI(t.cfg)){if(a[c]!==void 0){Object.assign(r,a[c]);continue}let l=t.pluginSources?.find((d)=>d.name===c);if(l)Object.assign(r,Ki(await l.load(o),t.cfg,s,i))}for(let c of t.serviceCfg.environment.required)if((r[c]??"").trim()==="")throw R(ee,`service ${t.service} missing required environment variable ${c}`);return r}function KI(e){let t={};if(e.cfg)for(let[r,n]of Object.entries(e.cfg.services)){let o={};for(let s of n.ports)if(!s.auto)o[s.name]=s.value;t[r]=o}if(e.assignedPorts)t[e.service]=e.assignedPorts;return t}function Ki(e,t,r,n=""){if(!t||Object.keys(e).length===0)return e;return Ep(e,t,r,n)}function VI(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 s=Zp($r(),"env",o);if(!Hb(s))continue;try{r[o]=Ub(s,"utf8").replace(/\n$/,"")}catch(i){throw xe(ee,`unable to read keychain env ${o}`,i)}}return r}async function zI(e,t){if(!(e.cfg?.environment.sources??[]).includes("secret_manager"))return{};let n=e.cfg?.environment.secrets??{},o={};for(let[s,i]of Object.entries(n)){if(!BI.test(i))throw R(ee,`environment.secrets.${s} is not a Secret Manager resource`);if(!t)throw R(ee,"environment source secret_manager is not configured \u2014 set credentials or remove it from environment.sources");o[s]=await t(i)}return o}function Fb(e,t){let r={},n=[".env",".env.development",".env.local"];if(t!=="")n.push(`.env.${t}`);for(let o of n){let s=Zp(e,o);if(!Hb(s))continue;try{let i=jI(Ub(s));Object.assign(r,i)}catch(i){throw xe(ee,`unable to read ${s}`,i)}}return r}function Ds(){let e={};for(let[t,r]of Object.entries(process.env))if(r!==void 0)e[t]=r;return e}function Yi(e,t,r,n,o,s=""){let i={DEVCTL_SERVICE_NAME:e,DEVCTL_ENVIRONMENT:o,SERVICE_HOST:t};if(n!=="")i.DEVCTL_PROXY_URL=n;if(s!=="")i.DEVCTL_USER_EMAIL=s;if(r.http!==void 0)i.SERVICE_PORT=String(r.http);else{let a=Object.values(r)[0];if(a!==void 0)i.SERVICE_PORT=String(a)}for(let[a,c]of Object.entries(r))i[`${a.toUpperCase()}_PORT`]=String(c);return i}function Os(e){return{...e}}ne();var XI=50,QI=8000,ZI=15000,eL=200,tL=30000,jb=86400000,rL=new Set(["logs","logs_page","logs_stats"]);function nL(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 ${Bi}`}function oL(e){if(e.compatible&&e.daemonVersion!==void 0&&e.daemonVersion!==Vt)return`attached daemon is devctl ${e.daemonVersion}; this client is ${Vt} (run \`devctl down\` then start again to update it)`;return}function em(e,t){if(!e.compat.compatible&&!rL.has(t))throw Ae(Qe,nL(e.compat),"run `devctl down` to stop it, then start again")}class Bb{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(`
|
|
2373
|
+
`);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(Dy({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=tL){this.nextID+=1;let n=String(this.nextID);return new Promise((o,s)=>{let i=setTimeout(()=>{this.pending.delete(n),s(Error(`${e} timed out after ${r}ms`))},r);this.pending.set(n,{resolve:o,reject:s,timer:i}),this.socket.write(JSON.stringify({id:n,method:e,params:t})+`
|
|
2374
|
+
`)})}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 Wb(e,t){let r=Ka(e),n=Date.now()+t;return new Promise((o,s)=>{let i=()=>{let a=YI(r);a.once("connect",()=>{let c=new Bb(a);sL(c).finally(()=>o(c))}),a.once("error",(c)=>{if(a.destroy(),Date.now()>=n){s(Ae(Qe,"supervisor is not running","run `devctl start` or `devctl attach` after starting services"));return}setTimeout(i,XI)})};i()})}async function sL(e){let t;try{t=await e.call("ping",null,QI)}catch{return}let r=iL(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===Bi,legacy:!1,daemonVersion:o,daemonProtocol:n}}function iL(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}async function jo(e){try{return await Wb(e,eL)}catch{return}}function aL(e,t,r,n){return r?[e,...n]:[e,t,...n]}async function Gb(e,t){let r=await jo(e);if(r)return r;bg(e);let n=fi(e),o=aL(process.execPath,process.argv[1]??"",Bun.isStandaloneExecutable===!0,["--config",t,"_supervisor","--repo",e]),s=JI({cmd:o,env:{...process.env},stdout:"ignore",stderr:Bun.file(n),stdin:"ignore",detached:!0});try{let i=await lL(e,ZI,s.exited);return s.unref(),i}catch{throw await dL(s),Ae(Qe,"supervisor failed to start",`see ${n} for details`)}}var cL=1000;function lL(e,t,r){return new Promise((n,o)=>{let s=!1,i=(a)=>{if(s)return;s=!0,a()};r.then((a)=>{i(()=>o(Error(`supervisor exited (code ${a}) before accepting connections`)))}),Wb(e,t).then((a)=>i(()=>n(a)),(a)=>i(()=>o(a)))})}async function dL(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,cL)})])}class ml{cfg;client;previousPersisted;constructor(e){this.cfg=e}async start(e){return await this.call("start",{...e,client_env:Ds()})}async stop(e){await this.call("stop",{services:e})}async restart(e,t){await this.call("restart",{services:e,cascade:t===!0,client_env:Ds()})}async runTask(e){return await this.call("run_task",{name:e,client_env:Ds()},jb)}async execService(e,t,r=!1){return await this.call("exec",{service:e,command:t,print_env:r,client_env:Ds()},jb)}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 getTrace(e){return await this.call("get_trace",{trace_id:e})}async traceRequest(e){return await this.call("trace_request",{request_id: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 xe(Qe,"supervisor is not running",Error("no client"));return em(this.client,e),this.client.call(e,t,r)}}function Ji(e){let t=e&&oL(e.compat);if(t)process.stderr.write(`warning: ${t}
|
|
2375
|
+
`)}async function qb(e,t,r=""){let n=pl(e,t,r);if(!n)throw Ae(Qe,"no devctl configuration found","run `devctl setup`, create a .devctl/config.yaml in the repository root, or pass --repo");let o=await jo(n.repoRoot);return Ji(o),{repoRoot:n.repoRoot,client:o}}async function Kb(e,t,r,n){let o=n?.allowMissingConfig===!0?Do(e,t):kn(e,t),s=new ml(o);if(!r)return s.client=await jo(o.repoRoot),Ji(s.client),s;return s.client=await Gb(o.repoRoot,o.configPath),Ji(s.client),s}async function Vb(e){let t=new ml(To());return t.client=e,Ji(t.client),t.cfg=await t.configSnapshot(),t}async function zb(e,t){let r=pl(e,"",t),n=r?await jo(r.repoRoot):void 0;if(!n)throw Ae(Qe,"supervisor is not running","run `devctl start` before `devctl attach`");return Vb(n)}async function Yb(e,t){let r=pl(e,"",t),n=r?await jo(r.repoRoot):void 0;if(n)return Vb(n);let o=kn(e,t),s=new ml(o),i=Dn(o.repoRoot);if(s.client=await Gb(o.repoRoot,o.configPath),Ji(s.client),s.cfg=await s.configSnapshot(),i&&i.processes.length>0)s.previousPersisted=i;return s}import{realpathSync as pL}from"fs";var mL="https://api.github.com/repos/amr-m-abdelgawad/devctl/releases/latest";function fL(){let e=Bun.isStandaloneExecutable===!0,t=e?"":rx(process.argv[1]??"");return Zb({scriptPath:t,execPath:rx(process.execPath),standalone:e})}async function hL(e=fetch,t=fL()){try{let r=await e(mL,{headers:{Accept:"application/vnd.github+json"}});if(!r.ok)return tx(t);let o=((await r.json()).tag_name??"").replace(/^v/,"");return{current:Vt,latest:o,newer:o!==""&&ex(o,Vt)>0,hint:t.hint,kind:t.kind,command:t.command}}catch{return tx(t)}}async function gL(e,t=nx){return t(e)}async function nx(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 ox(e=fetch){return{check:()=>hL(e),apply:(t,r)=>r===!0?nx(t,!0):gL(t)}}function tx(e){return{current:Vt,latest:"",newer:!1,hint:e.hint,kind:e.kind,command:e.command}}function rx(e){if(e==="")return"";try{return pL(e)}catch{return e}}function sx(e){let t=e?.tokens??new Fo(60000,Vn(),void 0),r=e?.doctorHost??Is({tokens:t}),n=ox(),o={loadTuiConfig:ds,saveTuiPreferences:_g,resolveTuiOverridePath:Za,userTuiConfigPath:Qa,listSessions:_y,loadSessionEvents:Ty,loadPath:Ni,validateConfigText:Mi,discover:Ao,configDiff:qn,openTui:Yb,findDaemon:qb,tryDial:jo,assertMethodAllowed:em,readPersistedState:Dn,bootstrapLogPath:fi,exportsDir:qa,resolveExportPath:Cy,writeLogExport:np,openInFileManager:Ry,freePort:Cc,createStarterConfig:Gy,runSetup:(s,i,a)=>qy(o,s,i,a),readTextFile:(s)=>vL(s,"utf8"),writeTextFile:(s,i)=>bL(s,i),fileExists:yL,load:kn,loadOrEmpty:Do,validate:Io,detectGoogle:No,loginGoogle:ib,logoutGoogle:ab,refreshUserToken:async(s="user")=>t.refresh(s,"",[]),runDoctor:new cl(e?.doctorRunner??rl(r)),getStartupPlan:new ll,getShutdownPlan:new dl,resolveStart:new ul,formatDoctor:bb,openController:Kb,openAttach:zb,checkUpdate:()=>n.check(),applyUpdate:(s,i)=>n.apply(s,i),formatUpdateStatus:Qb};return o}import{existsSync as oM,unlinkSync as sM}from"fs";import{connect as xL}from"net";Nt();var wL=2000,SL=200,kL=300;function ix(e){return{lookup(t){let r=t.toLowerCase();return["","http","tcp","command","process"].includes(r)||e.some((o)=>o.name.toLowerCase()===r)?{check:(o,s)=>CL({...o,type:t},s.pid,s.ports,s.workDir,s.env,e)}:void 0}}}async function CL(e,t,r,n,o,s=[]){let i=e.timeout_seconds>0?e.timeout_seconds*1000:wL,a=e.type.toLowerCase(),c=s.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:$e,message:l instanceof Error?l.message:String(l)}}if(a==="http")return RL(e.url,i);if(a==="tcp")return TL(_L(e.address,r),i);if(a==="command")return PL(e.command.args,e.command.shell,n,o,i);if(a==="process"||a===""){if(t>0&&ur(t))return{status:zt,message:"process running"};return{status:$e,message:"process not running"}}return{status:$e,message:`unknown health type ${e.type}`}}async function RL(e,t){let r=new AbortController,n=setTimeout(()=>r.abort(),t);try{let o=await fetch(e,{signal:r.signal});if(o.status>=SL&&o.status<kL)return{status:zt,message:String(o.status)};return{status:$e,message:String(o.status)}}catch(o){return{status:$e,message:o instanceof Error?o.message:String(o)}}finally{clearTimeout(n)}}function _L(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 TL(e,t){return new Promise((r)=>{if(e===""){r({status:$e,message:"no tcp address"});return}let n=e.lastIndexOf(":"),o=Number.parseInt(e.slice(n+1),10),s=e.slice(0,n).replace(/^\[|\]$/g,"");if(n<1||!Number.isFinite(o)){r({status:$e,message:`invalid tcp address ${e}`});return}let i=xL({host:s,port:o},()=>{i.end(),r({status:zt,message:"connected"})});i.setTimeout(t),i.on("error",(a)=>{r({status:$e,message:a.message})}),i.on("timeout",()=>{i.destroy(),r({status:$e,message:"timeout"})})})}async function PL(e,t,r,n,o){if(e.length===0)return{status:$e,message:"empty health command"};let s=t?process.platform==="win32"?["cmd.exe","/c",e.join(" ")]:["/bin/sh","-c",e.join(" ")]:e,i;try{i=Bun.spawn({cmd:s,cwd:r===""?void 0:r,env:n,stdout:"ignore",stderr:"ignore"})}catch(l){return{status:$e,message:l instanceof Error?l.message:String(l)}}let a=setTimeout(()=>i.kill("SIGKILL"),o),c=await i.exited;if(clearTimeout(a),c===0)return{status:zt,message:"ok"};return{status:$e,message:`exit ${c}`}}ne();var{spawn:xx}=globalThis.Bun;var{spawn:EL}=globalThis.Bun;async function ax(e,t){if(e<=0)return;try{process.kill(-e,t);return}catch{try{process.kill(e,t)}catch{return}}}async function cx(e){if(!ur(e))return;let t=await Xi(["ps","-p",String(e),"-o","command="]),r=Date.now(),n=AL(await Xi(["ps","-p",String(e),"-o","etime="])),o=await IL(e);return{pid:e,command:t.trim(),cwd:o,startTime:n===void 0?void 0:new Date(r-n).toISOString()}}function AL(e){let t=e.trim();if(t==="")return;let r=t.indexOf("-"),n=r>=0?t.slice(0,r):"0",s=(r>=0?t.slice(r+1):t).split(":");if(s.length!==2&&s.length!==3)return;let i=Number(n),a=s.length===3?Number(s[0]):0,c=Number(s.length===3?s[1]:s[0]),l=Number(s.at(-1));if(!Number.isInteger(i)||i<0||!Number.isInteger(a)||a<0||a>23||!Number.isInteger(c)||c<0||c>59||!Number.isInteger(l)||l<0||l>59)return;return(((i*24+a)*60+c)*60+l)*1000}async function IL(e){let r=(await Xi(["lsof","-a","-p",String(e),"-d","cwd","-Fn"])).split(`
|
|
2376
|
+
`).find((s)=>s.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=EL({cmd:e,stdout:"pipe",stderr:"ignore"}),r=t.stdout?await new Response(t.stdout).text():"";return await t.exited,r}catch{return""}}async function lx(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(`
|
|
2377
|
+
`)){let o=n.trim();if(o==="")continue;let[s,i,a]=o.split(/\s+/),c=Number(s),l=Number(i),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 dx(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 s=e[e.length-1]??"";return s!==""&&t.includes(s)}var{spawn:ux}=globalThis.Bun;var LL=1024,DL=3000;async function px(e,t){if(e<=0)return;try{await ux({cmd:["taskkill","/PID",String(e),"/T","/F"],stdout:"ignore",stderr:"ignore"}).exited}catch{return}}async function mx(e){let t=OL(yu(e),e);if(t)return t;if(!ur(e))return;let r=await tm(["powershell","-NoProfile","-Command",`Get-CimInstance Win32_Process -Filter "ProcessId=${e}" | Select-Object CommandLine,ExecutablePath,CreationDate | ConvertTo-Json -Compress`]),n=NL(r);if(n&&n.command!=="")return{pid:e,command:n.command,cwd:n.cwd,startTime:n.startTime};let o=await tm(["powershell","-NoProfile","-Command",`Get-Process -Id ${e} -ErrorAction SilentlyContinue | Select-Object Path,StartTime | ConvertTo-Json -Compress`]),s=ML(o);if(!s&&!n)return;return{pid:e,command:n?.command||s?.command||"",cwd:n?.cwd||s?.cwd||"",startTime:n?.startTime||s?.startTime}}function OL(e,t){let r=String(t);for(let n of e.split(/\r?\n/)){if(!n.includes(r))continue;let s=n.split(",").map((i)=>i.replace(/^"|"$/g,"").trim())[0]??"";if(s===""||s.toLowerCase().includes("info:"))continue;return{pid:t,command:s,cwd:""}}return}function ML(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:gl(n),startTime:r.StartTime}}catch{let r=hl(t,"Path");if(r==="")return;return{command:r,cwd:gl(r)}}}function NL(e){let t=e.trim();if(t==="")return;try{let r=JSON.parse(t),n=(r.CommandLine||r.ExecutablePath||"").trim(),o=(r.ExecutablePath||"").trim(),s=gl(o);return{command:n,cwd:s,startTime:r.CreationDate||void 0}}catch{return{command:hl(t,"CommandLine")||hl(t,"ExecutablePath"),cwd:gl(hl(t,"ExecutablePath"))}}}async function fx(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 tm(["powershell","-NoProfile","-Command",`${r}.Split(',') | ForEach-Object { Get-Process -Id $_ -ErrorAction SilentlyContinue } | Select-Object Id,CPU,WorkingSet64 | ConvertTo-Json -Compress`]);return $L(n)}function $L(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 s of o){if(!s||typeof s!=="object")continue;let i=s,a=Number(i.Id),c=Number(i.CPU??0),l=Number(i.WorkingSet64??0)/LL;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 gl(e){if(e==="")return"";let t=e.replace(/\//g,"\\"),r=t.lastIndexOf("\\");return r>0?t.slice(0,r):""}function hl(e,t){let r=`${t}=`,n=e.split(/\r?\n/).find((o)=>o.startsWith(r));return n?n.slice(r.length).trim():""}async function tm(e){let t,r=setTimeout(()=>{try{t?.kill()}catch{}},DL);try{t=ux({cmd:e,stdout:"pipe",stderr:"ignore"});let n=t.stdout?await new Response(t.stdout).text():"";return await t.exited,n}catch{return""}finally{clearTimeout(r)}}ne();var{spawn:rm}=globalThis.Bun;async function gx(e){await UL(e.runtime,e.containerName);let t=FL(e),r=await Ms(e.runtime,t,e.env);if(r.code!==0||r.stdout.trim()==="")throw R(qe,`failed to start ${e.containerName}: ${r.stderr.trim()||"container runtime returned no id"}`);let n=r.stdout.trim().split(/\s+/)[0]??"";return bx(e.runtime,e.containerName,n,e.onLine,e.onExit)}function FL(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 HL=new Set(["PATH","HOME","HOSTNAME","PWD","OLDPWD","SHLVL","_","TMPDIR"]);function yx(e){return Object.fromEntries(Object.entries(e).filter(([t])=>!HL.has(t)))}async function vx(e,t,r,n){let o=await Ms(e,["inspect","--format","{{.Id}} {{.State.Running}}",t],process.env);if(o.code!==0)return;let[s,i]=o.stdout.trim().split(/\s+/);if(!s||i!=="true")return;return bx(e,t,s,r,n)}function bx(e,t,r,n,o){let s=!0,i=rm({cmd:[e,"logs","--follow",r],stdout:"pipe",stderr:"pipe",stdin:"ignore"});hx(i.stdout,"stdout",n),hx(i.stderr,"stderr",n);let a=rm({cmd:[e,"wait",r],stdout:"pipe",stderr:"pipe",stdin:"ignore"}),c=Promise.all([new Response(a.stdout).text(),a.exited]).then(async([l,d])=>{s=!1,i.kill();let u=Number.parseInt(l.trim(),10),m=Number.isFinite(u)?u:d??1,p=m===0?void 0:Error(`container exited with code ${m}`);return await Ms(e,["rm","--force",r],process.env),o?.(m,p),{code:m,err:p}});return{id:r,runtime:e,containerName:t,done:c,running:()=>s,stop:async(l)=>{let d=Math.max(1,Math.ceil(l/1000));await Ms(e,["stop","--time",String(d),r],process.env),await c}}}async function UL(e,t){let r=await Ms(e,["inspect","--format","{{.State.Running}}",t],process.env);if(r.code===0&&r.stdout.trim()==="false")await Ms(e,["rm","--force",t],process.env)}async function Ms(e,t,r){try{let n=rm({cmd:[e,...t],env:{...process.env,...r},stdout:"pipe",stderr:"pipe",stdin:"ignore"}),[o,s,i]=await Promise.all([new Response(n.stdout).text(),new Response(n.stderr).text(),n.exited]);return{code:i??1,stdout:o,stderr:s}}catch(n){throw xe(qe,`unable to run ${e}`,n)}}async function hx(e,t,r){if(!e||typeof e==="number"||!r)return;let n=e.getReader(),o=new TextDecoder,s="";for(;;){let i=await n.read();if(i.done)break;s+=o.decode(i.value,{stream:!0});let a=s.split(`
|
|
2378
|
+
`);s=a.pop()??"";for(let c of a)r(t,c.replace(/\r$/,""))}if(s!=="")r(t,s)}var jL=1e4,BL=2000,WL=500;class nm{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 R(qe,"empty command");let r=e.shell?kx(e.args):e.args,n;try{n=xx({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(s){throw xe(qe,`failed to start ${e.name}`,s)}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 yl(n.stdout,"stdout",e.onLine),yl(n.stderr,"stderr",e.onLine),o.done=n.exited.then((s)=>{let i=typeof s==="number"?s:0,a=i===0?void 0:Error(`exited with code ${i}`);if(this.running.get(e.name)===o)this.running.delete(e.name);if(e.onExit)e.onExit(i,a);return{code:i,err:a}}),this.running.set(e.name,o),o}async runOnce(e){if(e.args.length===0)throw R(qe,"empty command");let t;try{t=xx({cmd:e.shell?kx(e.args):e.args,cwd:e.workDir||void 0,env:e.env,stdout:"pipe",stderr:"pipe",stdin:"ignore"})}catch(a){throw xe(qe,`failed to run ${e.name}`,a)}let r="",n="",o=(a,c)=>{if(a==="stdout")r+=`${c}
|
|
2255
2379
|
`;else n+=`${c}
|
|
2256
|
-
`;e.onLine?.(a,c)},
|
|
2257
|
-
`);i=c.pop()??"";for(let l of c)r(t,l.replace(/\r$/,""))}}async function ly(e,t){return Promise.race([e.then(()=>!0),new Promise((r)=>{setTimeout(()=>r(!1),t)})])}function x0(e){return new Promise((t)=>{setTimeout(t,e)})}import{existsSync as w0,readFileSync as S0,writeFileSync as k0}from"fs";var py={exists:(e)=>w0(e),readText:(e)=>S0(e,"utf8"),writeText:(e,t)=>k0(e,t)};dt();var Iu=3,C0=3,P0=10,ms={shouldRestart(e){if(!e.enabled||e.policy===Ea)return!1;if(e.retryCount>=e.maxRetries)return!1;if(e.policy===Aa)return!0;return e.policy===Ta&&e.exitCode!==0},fromService(e,t,r){return{policy:Ma(e.restart),enabled:e.restart.enabled!==!1,exitCode:r,retryCount:t,maxRetries:e.restart.max_retries>0?e.restart.max_retries:Iu}}},Mu={shouldRestartUnhealthy(e,t=C0){return e>=t},shouldResetRestartBudget(e,t=P0){return e>=t}},kc={timeoutMs(e,t){let r=e.startup.timeout_seconds;return r>0?r*1000:t},waitForHealthy(e){return e.startup.wait_for_healthy}};At();te();var R0=2,_0=2000,E0=3,T0=10;class Ou{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===ut)return;if(o&&(o.state===Ar||o.state===Et)){this.host().setState(e,Et,Tt,0,""),this.host().bus.publish(ye(fa,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?ms.shouldRestart(ms.fromService(i,a,r)):!1)&&i){this.host().setState(e,fr,Tt,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",L(u)))});return}this.host().fail(e,P("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:_0,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((m)=>({status:Oe,message:L(m)})).then((m)=>{if(!this.isCurrentGeneration(e,s))return;let p=this.host().runtimes.get(e);if(p&&my(p.state))return;if(m.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: ${m.message}`,pid:r});return}this.setHealth(e,m.status,m.message),this.host().logs.append({timestamp:this.clock.isoNow(),service:e,source:"health",level:A0(m.status),message:`health ${m.status} ${m.message}`,pid:r}),this.maybeRestartUnhealthy(e,t,m.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:R0;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||my(n.state))return;if(n.health=t,n.state===je||n.state===mr||n.state===Ur)n.state=t===Bt?mr:t===Oe?Ur:n.state;this.host().bus.publish(ye(Uf,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:E0;if(!Mu.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:Iu;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",L(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:T0;if(Mu.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 my(e){return e===fr||e===Ar||e===Et||e===ut}function A0(e){return e===Oe?"WARN":e===Bt?"INFO":"DEBUG"}dt();te();Tr();te();At();var $u=[Ar,ut,fr,je,Et],L0={[sv]:[Et,cn,je,ut,fr,Ar],[Et]:[cn,je,ut],[cn]:[je,ut,Et,Ar],[je]:$u,[mr]:$u,[Ur]:$u,[Ar]:[Et,ut],[ut]:[cn,Et,je],[fr]:[cn,je,ut,Et]};function fs(e,t){if(e===t)return!0;return(L0[e]??[]).includes(t)}function fy(e,t){if(!fs(e,t))throw P(Y,`illegal service lifecycle transition ${e} \u2192 ${t}`);return t}At();var D0=100,hy=30000;class Fu{processes;clock;session;health;constructor(e,t){this.processes=e;this.clock=t;this.health=new Ou(()=>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 P(He,"service orchestrator is not bound");return this.session}planStart(e,t,r){return cc(e,t,r)}planStop(e,t,r=!1){return r?dc(e,t):lc(e,t)}applyLifecycle(e,t){return fy(e,t)}isLegalLifecycle(e,t){return fs(e,t)}async start(e){let t=this.host();if(e.detach===!0)t.detached=!0;let r=pc(t.cfg,{services:e.services,profile:e.profile?pr(e.profile):void 0,activeProfile:pr(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=Ng(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,P(He,a.message));if(t.cfg.proxy.enabled&&!t.proxySuppressed)await t.startProxy().catch((a)=>t.log("devctl","ERROR",L(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",L(u.reason));if(d)throw P(He,"one or more services failed to start")}try{await this.awaitWaveHealth(a,n.waves.flat())}catch(l){throw t.log("devctl","ERROR",L(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!==Et).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 P(Bo,`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=kn(r.cfg.shutdown)*1000,o=[];for(let i of e.waves){let s=await Promise.allSettled(i.map(async(a)=>{r.setState(a,Ar,Tt,0,""),this.health.clearHealthWatch(a);try{await this.processes.stop(a,n)}catch(c){throw r.setState(a,ut,Tt,0,L(c)),r.bus.publish(ye(ha,a,{error:L(c)})),c}try{await r.releasePorts(a)}catch(c){r.log(a,"WARN",L(c))}r.setState(a,Et,Tt,0,""),r.bus.publish(ye(fa,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",L(c.reason))}}}if(r.persistState(),o.length>0)throw P(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===Ar||t.state===Et||t.state===ut)return!1;if(t.health===Bt||t.state===mr)return!0;return t.state===cn||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 P(lt,`unknown service ${e}`);o.setState(e,cn,Tt,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="",m;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 f=(v,b)=>{o.logs.append({timestamp:this.clock.isoNow(),service:e,source:v,stream:v,level:"",message:b,pid:m.pid})},h=(v,b)=>this.health.onExit(e,s,v,b);m=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:f,onExit:h}):await this.processes.start({name:e,args:[...i.command.args],shell:i.shell||i.command.shell,workDir:u,env:d,graceMs:kn(o.cfg.shutdown)*1000,captureStdout:Rh(i),captureStderr:_h(i),onLine:f,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)===m)await this.processes.stop(e,kn(o.cfg.shutdown)*1000).catch(()=>{});return}if(o.processMeta.set(e,{command:[...i.command.args],cwd:u,startTime:m.startTime}),o.setState(e,je,Tt,m.pid,""),o.bus.publish(ye(jf,e,{pid:m.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,m.pid,l,u,d,s),o.persistState(),o.clearRestartRequired([e]),kc.waitForHealthy(i)){let p=kc.timeoutMs(i,hy);try{await this.waitHealthy(e,p)}catch(f){throw await o.fail(e,f),f}}}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)=>Yt(a)===r&&bn(a)==="service_healthy"));if(!n||n.health.type===""||!o)continue;let i=kc.timeoutMs(n,hy);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===ut)throw P(Id,`service ${e} failed while waiting for healthy`);await I0(D0)}throw P(Id,`service ${e} did not become healthy in time`)}async runTransient(e,t,r,n,o){if(wn(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:kn(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 P(He,`${e} exited with code ${i.code}`);return i}}function I0(e){return new Promise((t)=>setTimeout(t,e))}te();function hs(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 P(Y,`secret manager request failed for ${t}: HTTP ${o.status}`);let i=await o.json();if(!i.payload?.data)throw P(Y,`secret manager response for ${t} had no payload`);return Buffer.from(i.payload.data,"base64").toString("utf8")}}At();te();async function Cc(e,t,r,n){let o="";if(e.proxy?.isRunning())o=`http://${e.proxy.address()}`;else if(e.cfg.proxy.enabled)o=`http://${Sn(e.cfg.proxy.listen)}`;let i=cs(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 as(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:hs(async()=>(await e.tokens.get("user","",[])).accessToken),pluginSources:e.registry?.environmentSources});return ei(s)}catch(s){return e.log(t,"WARN",`could not fully reconstruct environment for adopted service's health check (${L(s)}); using a baseline environment`),ei(i)}}function gy(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 Ra(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-${cr(e.cfg.repoRoot)}-${t.replace(/[^a-zA-Z0-9_.-]/g,"-")}`,workDir:l,onLine:(p,f)=>e.logs.append({timestamp:e.clock.isoNow(),service:t,source:p,stream:p,level:"",message:f,pid:0}),onExit:(p,f)=>e.orchestrator.health.onExit(t,a,p,f)});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,Rc(e,gn(e.cfg.repoRoot)?.processes.find((p)=>p.name===t))),e.setState(t,je,Tt,0,"");let m=await Cc(e,t,r,u);return e.orchestrator.health.startHealth(t,r,0,u,l,m,a),e.persistState(),e.log(t,"INFO",`claimed running ${c} container ${d.container?.id??""}`),!0}let n=await Ra(r);if(!n)return!1;let o=Object.values(n)[0],s=(o===void 0?void 0:await Jn(o))?.pid??0;if(s>0&&s!==process.pid){let a=gn(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!==""&&ps({args:[...r.command.args],workDir:e.serviceWorkDir(r),startTime:new Date(a.startTime)},c)){e.ports.set(t,n);let d=gy(e,t,s,[...r.command.args],e.serviceWorkDir(r),new Date(a.startTime))??e.orchestrator.health.bumpGeneration(t);Pc(e,t,Rc(e,a)),e.setState(t,je,Tt,s,""),e.log(t,"INFO",`already listening on ${Object.values(n).join(", ")}; not starting again`);let u=e.serviceWorkDir(r),m=await Cc(e,t,r,n);return e.orchestrator.health.startHealth(t,r,s,n,u,m,d),!0}e.log(t,"WARN",`port ${o} is in use by an unrelated process (pid ${s}); not adopting`)}return!1}async function yy(e){let t=gn(e.cfg.repoRoot);if(!t)return;M0(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-${cr(e.cfg.repoRoot)}-${n.replace(/[^a-zA-Z0-9_.-]/g,"-")}`,workDir:e.serviceWorkDir(o),onLine:(u,m)=>e.logs.append({timestamp:e.clock.isoNow(),service:n,source:u,stream:u,level:"",message:m,pid:0}),onExit:(u,m)=>e.orchestrator.health.onExit(n,s,u,m)});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,Rc(e,i)),e.setState(n,je,Tt,0,"");let d=await Cc(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!==""&&ps({args:n.command,workDir:n.cwd,startTime:n.startTime?new Date(n.startTime):void 0},o))){if(Object.values(n.ports).length>0&&await Ra({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=gy(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,Rc(e,n)),e.setState(n.name,je,Tt,n.pid,"");let a=e.cfg.services[n.name];if(a){let c=n.cwd||e.serviceWorkDir(a),l=await Cc(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(ye(Kf,"",{services:r,session_id:t.session_id})),e.log("devctl","INFO",`recovered session processes: ${r.join(", ")}`)}function M0(e,t){e.profile=t||e.profile,e.profileEnv=uo(e.cfg,e.profile)}function Pc(e,t,r){e.serviceProfile.set(t,r),e.serviceProfileEnv.set(t,uo(e.cfg,r))}function Rc(e,t){return t?.profile??e.profile}import{statSync as J0,watch as X0}from"fs";import{isAbsolute as Q0,join as Z0,resolve as eA}from"path";function by(e,t){return t}function Nu(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}}At();te();import{pathToFileURL as K0}from"url";import{isAbsolute as q0,resolve as V0}from"path";import{createServer as ky,request as O0}from"http";import{request as $0}from"https";import{randomBytes as xy}from"crypto";import{Readable as F0}from"stream";te();Tr();var gs="x-devctl-request-id",N0="x-devctl-internal-token",H0=100;class _c{cap;items=[];constructor(e=H0){this.cap=e}record(e){if(this.items.push(e),this.items.length>this.cap)this.items.splice(0,this.items.length-this.cap)}stats(){let e=this.items.reduce((t,r)=>t+(r.status>=400||r.error?1:0),0);return{total:this.items.length,errors:e,recent:[...this.items].reverse()}}}var Hu=new Set(["connection","keep-alive","proxy-authenticate","proxy-authorization","te","trailers","transfer-encoding","upgrade"]),j0=new Set(["gzip","deflate","br"]);class ju{cfg;tokens;logs;bus;detector;resolvePort;middleware;server;running=!1;addr="";upgradedSockets=new Set;requests=new _c;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 P(zt,`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 P(zt,`upstream service ${t} is not running`);return`http://127.0.0.1:${r}`}address(){return this.addr||Sn(this.cfg.listen)}setMiddleware(e){this.middleware=e}isRunning(){return this.running}stats(){return this.requests.stats()}recordRequest(e){this.requests.record(e)}start(){let e=this.cfg.listen.host||"127.0.0.1";if(!Er(e))return Promise.reject(P(zt,`refusing to bind proxy to ${e}`));if(this.cfg.listen.port===0)return Promise.reject(P(zt,"proxy.listen.port is required when the proxy is enabled"));let t=this.cfg.listen.port;return new Promise((r,n)=>{this.server=ky((o,i)=>{this.serve(o,i)}),this.server.on("upgrade",(o,i,s)=>{this.serveUpgrade(o,i,s)}),this.server.on("error",(o)=>n(Se(zt,`unable to listen on ${e}:${t}`,o))),this.server.listen(t,e,()=>{this.running=!0,this.addr=`${e}:${t}`,this.bus?.publish(ye(Wf,"",{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(ye(Gf,"",{})),t()})})}async serveUpgrade(e,t,r){let n=Date.now(),o=e.headers[gs]?.toString()||xy(8).toString("hex"),i=e.method??"GET",s=e.url??"/",a=this.detector?this.detector.redactText(s):s,c=Sy(this.cfg.routes,e);if(!c){t.end(`HTTP/1.1 404 Not Found\r
|
|
2380
|
+
`;e.onLine?.(a,c)},s=[yl(t.stdout,"stdout",o),yl(t.stderr,"stderr",o)],i=await t.exited;return await Promise.all(s),{code:typeof i==="number"?i:0,stdout:r,stderr:n}}async startContainer(e){let t=this.running.get(e.name);if(t&&Qi(t))return t;let r=await gx({...e,env:yx(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 vx(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(!ur(e.pid))throw R(qe,`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=qL(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:jL;if(r.container){await r.container.stop(n),this.running.delete(e);return}if(await wx(r.pid,"SIGTERM"),!await Rx(r.done,n)){if(await wx(r.pid,"SIGKILL"),!await Rx(r.done,BL))throw R(qe,`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 ur(e.pid)}async function wx(e,t){if(process.platform==="win32"){await px(e,t);return}await ax(e,t)}async function _x(e){if(process.platform==="win32")return mx(e);return cx(e)}async function Tx(e){if(process.platform==="win32")return fx(e);return lx(e)}var GL=2000;function Zi(e,t){if(e.workDir!==""&&t.cwd!==""&&Cx(e.workDir)!==Cx(t.cwd))return!1;if(!dx(e.args,t.command))return!1;let r=Sx(e.startTime),n=Sx(t.startTime);if(r!==void 0&&n!==void 0)return Math.abs(r-n)<=GL;return!0}function Sx(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 kx(e){if(process.platform==="win32")return["cmd.exe","/c",e.join(" ")];return["/bin/sh","-c",e.join(" ")]}function Cx(e){return e.replace(/\\/g,"/").replace(/\/+$/,"")}async function qL(e){while(ur(e))await KL(WL);return 0}async function yl(e,t,r){if(!e||typeof e==="number"||!r)return;let n=e.getReader(),o=new TextDecoder,s="";for(;;){let{value:i,done:a}=await n.read();if(a){if(s!=="")r(t,s);return}s+=o.decode(i,{stream:!0});let c=s.split(`
|
|
2381
|
+
`);s=c.pop()??"";for(let l of c)r(t,l.replace(/\r$/,""))}}async function Rx(e,t){return Promise.race([e.then(()=>!0),new Promise((r)=>{setTimeout(()=>r(!1),t)})])}function KL(e){return new Promise((t)=>{setTimeout(t,e)})}import{existsSync as VL,readFileSync as zL,writeFileSync as YL}from"fs";var Px={exists:(e)=>VL(e),readText:(e)=>zL(e,"utf8"),writeText:(e,t)=>YL(e,t)};lt();var om=3,JL=3,XL=10,ea={shouldRestart(e){if(!e.enabled||e.policy===Rc)return!1;if(e.retryCount>=e.maxRetries)return!1;if(e.policy===Tc)return!0;return e.policy===_c&&e.exitCode!==0},fromService(e,t,r){return{policy:Ac(e.restart),enabled:e.restart.enabled!==!1,exitCode:r,retryCount:t,maxRetries:e.restart.max_retries>0?e.restart.max_retries:om}}},sm={shouldRestartUnhealthy(e,t=JL){return e>=t},shouldResetRestartBudget(e,t=XL){return e>=t}},vl={timeoutMs(e,t){let r=e.startup.timeout_seconds;return r>0?r*1000:t},waitForHealthy(e){return e.startup.wait_for_healthy}};Nt();ne();var QL=2,ZL=2000,eD=3,tD=10;class im{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===bt)return;if(o&&(o.state===Gr||o.state===Ot)){this.host().setState(e,Ot,Mt,0,""),this.host().bus.publish(ke(ec,e,{code:r}));return}let s=this.host().cfg.services[e],i=n?.message??`exited with code ${r}`,a=this.restarts.get(e)??0;if((s?ea.shouldRestart(ea.fromService(s,a,r)):!1)&&s){this.host().setState(e,Ar,Mt,0,i),this.bumpRestartCount(e,a+1),this.host().persistState(),this.armRestart(e,s,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",I(u)))});return}this.host().fail(e,R("process_start",i))}startHealth(e,t,r,n,o,s,i){let a=this.healthTimers.get(e);if(a)clearInterval(a);if(t.health.type===""){this.setHealth(e,zt,"no health check");return}let c=t.health.interval_seconds>0?t.health.interval_seconds*1000:ZL,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:zt,message:"container running"}:{status:$e,message:"container not running"}):Promise.resolve().then(()=>this.host().healthCheckers.lookup(t.health.type)?.check(t.health,{pid:r,ports:n,workDir:o,env:s})??{status:$e,message:`unknown health type ${t.health.type}`})).catch((m)=>({status:$e,message:I(m)})).then((m)=>{if(!this.isCurrentGeneration(e,i))return;let p=this.host().runtimes.get(e);if(p&&Ex(p.state))return;if(m.status===$e&&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: ${m.message}`,pid:r});return}this.setHealth(e,m.status,m.message),this.host().logs.append({timestamp:this.clock.isoNow(),service:e,source:"health",level:rD(m.status),message:`health ${m.status} ${m.message}`,pid:r}),this.maybeRestartUnhealthy(e,t,m.status,i)})};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 s=t.restart.backoff_seconds>0?t.restart.backoff_seconds:QL;this.scheduleRestart(e,s*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||Ex(n.state))return;if(n.health=t,n.state===Ke||n.state===Er||n.state===nn)n.state=t===zt?Er:t===$e?nn:n.state;this.host().bus.publish(ke(Eg,e,{health:t,message:r}))}maybeRestartUnhealthy(e,t,r,n){if(r!==$e){if(this.unhealthyStreak.set(e,0),r===zt)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 s=(this.unhealthyStreak.get(e)??0)+1;this.unhealthyStreak.set(e,s);let i=t.health.unhealthy_threshold>0?t.health.unhealthy_threshold:eD;if(!sm.shouldRestartUnhealthy(s,i))return;this.unhealthyStreak.set(e,0);let a=this.restarts.get(e)??0,c=t.restart.max_retries>0?t.restart.max_retries:om;if(a>=c){this.host().log(e,"WARN",`unhealthy after ${s} 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 ${s} 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",I(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:tD;if(sm.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 Ex(e){return e===Ar||e===Gr||e===Ot||e===bt}function rD(e){return e===$e?"WARN":e===zt?"INFO":"DEBUG"}lt();ne();Wr();ne();Nt();var am=[Gr,bt,Ar,Ke,Ot],nD={[Sb]:[Ot,Rn,Ke,bt,Ar,Gr],[Ot]:[Rn,Ke,bt],[Rn]:[Ke,bt,Ot,Gr],[Ke]:am,[Er]:am,[nn]:am,[Gr]:[Ot,bt],[bt]:[Rn,Ot,Ke],[Ar]:[Rn,Ke,bt,Ot]};function ta(e,t){if(e===t)return!0;return(nD[e]??[]).includes(t)}function Ax(e,t){if(!ta(e,t))throw R(ee,`illegal service lifecycle transition ${e} \u2192 ${t}`);return t}Nt();var oD=100,Ix=30000;class cm{processes;clock;session;health;constructor(e,t){this.processes=e;this.clock=t;this.health=new im(()=>this.host(),e,t,{startOne:(r,n,o,s)=>this.startOne(r,n,o,s),restart:(r,n)=>this.restart(r,n)})}bind(e){this.session=e}host(){if(!this.session)throw R(qe,"service orchestrator is not bound");return this.session}planStart(e,t,r){return nl(e,t,r)}planStop(e,t,r=!1){return r?sl(e,t):ol(e,t)}applyLifecycle(e,t){return Ax(e,t)}isLegalLifecycle(e,t){return ta(e,t)}async start(e){let t=this.host();if(e.detach===!0)t.detached=!0;let r=al(t.cfg,{services:e.services,profile:e.profile?Pr(e.profile):void 0,activeProfile:Pr(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=eb(t.cfg,n.waves.flat(),o.adcAvailable);let s=new Set(n.blockers.map((a)=>a.name));for(let a of n.blockers)await t.fail(a.name,R(qe,a.message));if(t.cfg.proxy.enabled&&!t.proxySuppressed)await t.startProxy().catch((a)=>t.log("devctl","ERROR",I(a)));let i=[];for(let a of n.waves.flat()){if(s.has(a)||await t.claimIfAlreadyUp(a))continue;i.push(a)}for(let a of i)if(e.profile||!t.serviceProfile.has(a))t.serviceProfile.set(a,r.profile);if(i.length>0)await t.assignPendingPorts(i);for(let a of n.waves){let c=a.filter((l)=>i.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",I(u.reason));if(d)throw R(qe,"one or more services failed to start")}try{await this.awaitWaveHealth(a,n.waves.flat())}catch(l){throw t.log("devctl","ERROR",I(l)),l}}return t.persistState(),n}async stop(e){let t=this.host(),r=e;if(r.length===0)r=[...t.runtimes.entries()].filter(([,i])=>i.state!==Ot).map(([i])=>i);if(r.length===0)return;let n=r.filter((i)=>!t.cfg.services[i]&&t.runtimes.has(i)),o=r.filter((i)=>!t.cfg.services[i]&&!t.runtimes.has(i));if(o.length>0)throw R(xs,`unknown service "${o[0]}"`);let s=r.filter((i)=>t.cfg.services[i]!==void 0);if(s.length>0)await this.runStopPlan(this.planStop(t.cfg,s),{resetRestartCounts:!0});if(n.length>0){await this.runStopPlan({profile:t.profile,steps:[],waves:n.map((i)=>[i])},{resetRestartCounts:!0});for(let i of n)t.forgetService(i)}}async restart(e,t){let r=this.host(),n=t?.cascade===!0,o=n?Ho(r.cfg,e):e,s=n?this.planStop(r.cfg,e):this.planStop(r.cfg,e,!0),i=t?.auto!==!0;await this.runStopPlan(s,{resetRestartCounts:i}),await this.start({services:o,client_env:t?.clientEnv,auto:t?.auto})}async runStopPlan(e,t){let r=this.host();for(let s of e.waves.flat())if(this.health.clearRestartTimer(s),this.health.bumpGeneration(s),t?.resetRestartCounts)this.health.resetRestartCount(s);let n=Bn(r.cfg.shutdown)*1000,o=[];for(let s of e.waves){let i=await Promise.allSettled(s.map(async(a)=>{r.setState(a,Gr,Mt,0,""),this.health.clearHealthWatch(a);try{await this.processes.stop(a,n)}catch(c){throw r.setState(a,bt,Mt,0,I(c)),r.bus.publish(ke(tc,a,{error:I(c)})),c}try{await r.releasePorts(a)}catch(c){r.log(a,"WARN",I(c))}r.setState(a,Ot,Mt,0,""),r.bus.publish(ke(ec,a,{}))}));for(let a=0;a<i.length;a+=1){let c=i[a];if(c?.status==="rejected"){let l=s[a]??"";o.push(l),r.log(l||"devctl","ERROR",I(c.reason))}}}if(r.persistState(),o.length>0)throw R(qe,`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===Ar||t.state===Gr||t.state===Ot||t.state===bt)return!1;if(t.health===zt||t.state===Er)return!0;return t.state===Rn||t.state===Ke}async startOne(e,t,r,n=!1){let o=this.host();if(this.serviceIsActive(e))return;let s=o.cfg.services[e];if(!s)throw R(Qe,`unknown service ${e}`);o.setState(e,Rn,Mt,0,"");let i=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="",m;try{await o.prepareServiceIdentity(e,s),l=o.ports.get(e)??{};let p=await o.resolveServiceExecution(e,s,a,c,o.clientEnv.get(e),!s.container);if(d=p.env,u=p.workDir,n)await this.runTransient(`${e}:pre_start`,s.hooks.pre_start,s.shell,u,d);if(!this.health.isCurrentGeneration(e,i))return;let f=(g,x)=>{o.logs.append({timestamp:this.clock.isoNow(),service:e,source:g,stream:g,level:"",message:x,pid:m.pid})},h=(g,x)=>this.health.onExit(e,i,g,x);m=s.container?await this.processes.startContainer({name:e,runtime:s.container.runtime==="podman"?"podman":"docker",containerName:`${o.containerPrefix}${e.replace(/[^a-zA-Z0-9_.-]/g,"-")}`,image:s.container.image,command:[...s.command.args],env:{...d,...s.container.env},ports:l,targetPorts:s.container.ports,volumes:s.container.volumes,workDir:u,onLine:f,onExit:h}):await this.processes.start({name:e,args:[...s.command.args],shell:s.shell||s.command.shell,workDir:u,env:d,graceMs:Bn(o.cfg.shutdown)*1000,captureStdout:Uy(s),captureStderr:jy(s),onLine:f,onExit:h})}catch(p){if(this.health.isCurrentGeneration(e,i))await o.fail(e,p);throw p}if(!this.health.isCurrentGeneration(e,i)){if(this.processes.get(e)===m)await this.processes.stop(e,Bn(o.cfg.shutdown)*1000).catch(()=>{});return}if(o.processMeta.set(e,{command:[...s.command.args],cwd:u,startTime:m.startTime}),o.setState(e,Ke,Mt,m.pid,""),o.bus.publish(ke(Tg,e,{pid:m.pid})),n)try{await this.runTransient(`${e}:post_start`,s.hooks.post_start,s.shell,u,d)}catch(p){throw await o.fail(e,p),p}if(!this.health.isCurrentGeneration(e,i))return;if(this.health.startHealth(e,s,m.pid,l,u,d,i),o.persistState(),o.clearRestartRequired([e]),vl.waitForHealthy(s)){let p=vl.timeoutMs(s,Ix);try{await this.waitHealthy(e,p)}catch(f){throw await o.fail(e,f),f}}}async awaitWaveHealth(e,t){for(let r of e){let n=this.host().cfg.services[r],o=t.some((i)=>(this.host().cfg.services[i]?.dependencies??[]).some((a)=>or(a)===r&&Fn(a)==="service_healthy"));if(!n||n.health.type===""||!o)continue;let s=vl.timeoutMs(n,Ix);try{await this.waitHealthy(r,s)}catch(i){throw await this.host().fail(r,i),i}}}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===zt)return;if(n?.state===bt)throw R(op,`service ${e} failed while waiting for healthy`);await sD(oD)}throw R(op,`service ${e} did not become healthy in time`)}async runTransient(e,t,r,n,o){if(Un(t))return{code:0,stdout:"",stderr:""};this.host().log(e,"INFO",`running ${t.args.join(" ")}`);let s=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:(i,a)=>this.host().logs.append({timestamp:this.clock.isoNow(),service:e,source:i,stream:i,level:"",message:a,pid:0})});if(s.code!==0)throw R(qe,`${e} exited with code ${s.code}`);return s}}function sD(e){return new Promise((t)=>setTimeout(t,e))}ne();function ra(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 R(ee,`secret manager request failed for ${t}: HTTP ${o.status}`);let s=await o.json();if(!s.payload?.data)throw R(ee,`secret manager response for ${t} had no payload`);return Buffer.from(s.payload.data,"base64").toString("utf8")}}Nt();ne();async function bl(e,t,r,n){let o="";if(e.proxy?.isRunning())o=`http://${e.proxy.address()}`;else if(e.cfg.proxy.enabled)o=`http://${jn(e.cfg.proxy.listen)}`;let s=Yi(t,"127.0.0.1",n,o,e.cfg.project.name);if(s.DEVCTL_INTERNAL_TOKEN=e.internalTok,e.cfg.proxy.token_endpoint.enabled)s.DEVCTL_TOKEN_URL=e.boundTokenURL||`http://127.0.0.1:${e.tokenEP?.listenPort()||e.cfg.proxy.token_endpoint.port}/token`;try{let i=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:s,cfg:e.cfg,fetchSecret:ra(async()=>(await e.tokens.get("user","",[])).accessToken),pluginSources:e.registry?.environmentSources});return Os(i)}catch(i){return e.log(t,"WARN",`could not fully reconstruct environment for adopted service's health check (${I(i)}); using a baseline environment`),Os(s)}}function Lx(e,t,r,n,o,s){if(e.procs.get(t)&&e.processAliveFn(e.procs.get(t)?.pid??0))return;if(!e.processAliveFn(r)||r===process.pid)return;let i=e.orchestrator.health.bumpGeneration(t);try{e.procs.adopt({name:t,pid:r,args:n,workDir:o,startTime:s,onExit:(a,c)=>{e.orchestrator.health.onExit(t,i,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:s}),e.persistState(),i}async function Dx(e,t){let r=e.cfg.services[t];if(e.orchestrator.serviceIsActive(t)){if(r&&!e.ports.has(t)){let a=await kc(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-${kr(e.cfg.repoRoot)}-${t.replace(/[^a-zA-Z0-9_.-]/g,"-")}`,workDir:l,onLine:(p,f)=>e.logs.append({timestamp:e.clock.isoNow(),service:t,source:p,stream:p,level:"",message:f,pid:0}),onExit:(p,f)=>e.orchestrator.health.onExit(t,a,p,f)});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}),xl(e,t,wl(e,Dn(e.cfg.repoRoot)?.processes.find((p)=>p.name===t))),e.setState(t,Ke,Mt,0,"");let m=await bl(e,t,r,u);return e.orchestrator.health.startHealth(t,r,0,u,l,m,a),e.persistState(),e.log(t,"INFO",`claimed running ${c} container ${d.container?.id??""}`),!0}let n=await kc(r);if(!n)return!1;let o=Object.values(n)[0],i=(o===void 0?void 0:await Ro(o))?.pid??0;if(i>0&&i!==process.pid){let a=Dn(e.cfg.repoRoot)?.processes.find((d)=>d.name===t&&d.pid===i);if(!a)return e.log(t,"WARN",`port ${o} is held by pid ${i} with no persisted record for ${t}; not adopting`),!1;let c=await e.inspectProcessFn(i);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=Lx(e,t,i,[...r.command.args],e.serviceWorkDir(r),new Date(a.startTime))??e.orchestrator.health.bumpGeneration(t);xl(e,t,wl(e,a)),e.setState(t,Ke,Mt,i,""),e.log(t,"INFO",`already listening on ${Object.values(n).join(", ")}; not starting again`);let u=e.serviceWorkDir(r),m=await bl(e,t,r,n);return e.orchestrator.health.startHealth(t,r,i,n,u,m,d),!0}e.log(t,"WARN",`port ${o} is in use by an unrelated process (pid ${i}); not adopting`)}return!1}async function Ox(e){let t=Dn(e.cfg.repoRoot);if(!t)return;iD(e,t.profile);let r=[];for(let[n,o]of Object.entries(e.cfg.services)){if(!o.container)continue;let s=t.processes.find((u)=>u.name===n),i=e.orchestrator.health.bumpGeneration(n),a=o.container.runtime==="podman"?"podman":"docker",c=await e.procs.adoptContainer({name:n,runtime:a,containerName:`devctl-${kr(e.cfg.repoRoot)}-${n.replace(/[^a-zA-Z0-9_.-]/g,"-")}`,workDir:e.serviceWorkDir(o),onLine:(u,m)=>e.logs.append({timestamp:e.clock.isoNow(),service:n,source:u,stream:u,level:"",message:m,pid:0}),onExit:(u,m)=>e.orchestrator.health.onExit(n,i,u,m)});if(!c)continue;let l=s?.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:s?.startTime?new Date(s.startTime):c.startTime}),xl(e,n,wl(e,s)),e.setState(n,Ke,Mt,0,"");let d=await bl(e,n,o,l);e.orchestrator.health.startHealth(n,o,0,l,e.serviceWorkDir(o),d,i),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 kc({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 i=Lx(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);xl(e,n.name,wl(e,n)),e.setState(n.name,Ke,Mt,n.pid,"");let a=e.cfg.services[n.name];if(a){let c=n.cwd||e.serviceWorkDir(a),l=await bl(e,n.name,a,n.ports);e.orchestrator.health.startHealth(n.name,a,n.pid,n.ports,c,l,i)}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(ke(Lg,"",{services:r,session_id:t.session_id})),e.log("devctl","INFO",`recovered session processes: ${r.join(", ")}`)}function iD(e,t){e.profile=t||e.profile,e.profileEnv=Uo(e.cfg,e.profile)}function xl(e,t,r){e.serviceProfile.set(t,r),e.serviceProfileEnv.set(t,Uo(e.cfg,r))}function wl(e,t){return t?.profile??e.profile}import{statSync as kD,watch as CD}from"fs";import{isAbsolute as RD,join as _D,resolve as TD}from"path";function Mx(e,t){return t}function lm(e,t){let r={},n=new Set,o=new Set([...Object.keys(e.services),...Object.keys(t.services)]);for(let i of o){let a=e.services[i],c=t.services[i],l=[];if(!a||!c)l.push("presence"),n.add(i);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(i);if(a.working_dir!==c.working_dir)l.push("working_dir"),n.add(i);if(JSON.stringify(a.environment)!==JSON.stringify(c.environment))l.push("environment"),n.add(i);if(JSON.stringify(a.ports)!==JSON.stringify(c.ports))l.push("ports"),n.add(i);if(JSON.stringify(a.identity)!==JSON.stringify(c.identity))l.push("identity"),n.add(i);if(JSON.stringify(a.health)!==JSON.stringify(c.health))l.push("health"),n.add(i);if(JSON.stringify(a.restart)!==JSON.stringify(c.restart))l.push("restart"),n.add(i);if(JSON.stringify(a.startup)!==JSON.stringify(c.startup))l.push("startup"),n.add(i);if(JSON.stringify(a.logs)!==JSON.stringify(c.logs))l.push("logs"),n.add(i);if(JSON.stringify(a.container)!==JSON.stringify(c.container))l.push("container"),n.add(i);if(JSON.stringify(a.watch)!==JSON.stringify(c.watch))l.push("watch"),n.add(i)}if(l.length>0)r[i]=l}let s=[];if(JSON.stringify(e.logs)!==JSON.stringify(t.logs))s.push("logs");if(JSON.stringify(e.telemetry)!==JSON.stringify(t.telemetry))s.push("telemetry");if(JSON.stringify(e.auth)!==JSON.stringify(t.auth))s.push("auth");return{restart_required:[...n].sort(),changes:r,supervisor_restart_required:s}}Nt();ne();import{pathToFileURL as vD}from"url";import{isAbsolute as bD,resolve as xD}from"path";import{createServer as Fx,request as cD}from"http";import{request as lD}from"https";import{Readable as dD}from"stream";ne();Wr();var Bo="traceparent";function aD(e){let t=Date.parse(e.timestamp),r=Number.isNaN(t)?Date.now():t,n=Number.isFinite(e.durationMs)?Math.max(0,e.durationMs):0;return{startUnixNano:(r-n)*pr,endUnixNano:r*pr}}function na(e){return Gg(e)}function oa(e,t,r){e[r]=t.requestId,e[Bo]=cc(t)}function Sl(e,t){let{startUnixNano:r,endUnixNano:n}=aD(e),o=e.status>=500||Boolean(e.error);return{traceId:e.traceId??t?.traceId??(rr(e.requestId)?e.requestId.toLowerCase():Mu()),spanId:e.spanId??t?.spanId??ac(),parentSpanId:e.parentSpanId??t?.parentSpanId,name:`${e.method} ${e.route||e.path}`,kind:"server",startUnixNano:r,endUnixNano:n,status:{code:o?"error":"ok",message:e.error},attributes:{"http.request.method":e.method,"url.path":e.path,"http.route":e.route,"http.response.status_code":e.status,[Wt]:e.requestId,...e.identity?{"enduser.id":e.identity}:{}},events:[],links:[],resource:{"service.name":"proxy"}}}var zn="x-devctl-request-id",uD="x-devctl-internal-token",pD=100;class kl{cap;items=[];constructor(e=pD){this.cap=e}record(e){if(this.items.push(e),this.items.length>this.cap)this.items.splice(0,this.items.length-this.cap)}stats(){let e=this.items.reduce((t,r)=>t+(r.status>=400||r.error?1:0),0);return{total:this.items.length,errors:e,recent:[...this.items].reverse()}}}var dm=new Set(["connection","keep-alive","proxy-authenticate","proxy-authorization","te","trailers","transfer-encoding","upgrade"]),mD=new Set(["gzip","deflate","br"]);class um{cfg;tokens;logs;spans;bus;detector;resolvePort;middleware;server;running=!1;addr="";upgradedSockets=new Set;requests=new kl;constructor(e,t,r,n,o,s=[],i,a){this.cfg=e,this.tokens=t,this.logs=r,this.bus=n,this.detector=o,this.middleware=s,this.resolvePort=i,this.spans=a}upstreamBase(e){let t=e.upstream.service??"";if(t==="")return e.upstream.url;if(!this.resolvePort)throw R(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 R(nr,`upstream service ${t} is not running`);return`http://127.0.0.1:${r}`}address(){return this.addr||jn(this.cfg.listen)}setMiddleware(e){this.middleware=e}isRunning(){return this.running}stats(){return this.requests.stats()}recordRequest(e){if(this.requests.record(e),this.spans&&(e.traceId||e.requestId))this.spans.append(Sl(e))}applyResponseHeaders(e,t){for(let[r,n]of Object.entries(t.response_headers??{}))e.setHeader(r,n)}start(){let e=this.cfg.listen.host||"127.0.0.1";if(!sr(e))return Promise.reject(R(nr,`refusing to bind proxy to ${e}`));if(this.cfg.listen.port===0)return Promise.reject(R(nr,"proxy.listen.port is required when the proxy is enabled"));let t=this.cfg.listen.port;return new Promise((r,n)=>{this.server=Fx((o,s)=>{this.serve(o,s)}),this.server.on("upgrade",(o,s,i)=>{this.serveUpgrade(o,s,i)}),this.server.on("error",(o)=>n(xe(nr,`unable to listen on ${e}:${t}`,o))),this.server.listen(t,e,()=>{this.running=!0,this.addr=`${e}:${t}`,this.bus?.publish(ke(Ag,"",{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(ke(Ig,"",{})),t()})})}async serveUpgrade(e,t,r){let n=Date.now(),o=na({traceparent:e.headers[Bo]?.toString(),requestId:e.headers[zn]?.toString()}),s=o.requestId,i=e.method??"GET",a=e.url??"/",c=this.detector?this.detector.redactText(a):a,l=$x(this.cfg.routes,e);if(!l){t.end(`HTTP/1.1 404 Not Found\r
|
|
2258
2382
|
Connection: close\r
|
|
2259
2383
|
Content-Length: 0\r
|
|
2260
2384
|
\r
|
|
2261
|
-
`),this.recordRequest({timestamp:new Date().toISOString(),requestId:
|
|
2262
|
-
`,
|
|
2263
|
-
`}`).join("");if(t.write(`${
|
|
2264
|
-
`),r.length>0)
|
|
2385
|
+
`),this.recordRequest({timestamp:new Date().toISOString(),requestId:s,method:i,path:c,route:"",identity:"",status:404,durationMs:Date.now()-n,traceId:o.traceId,spanId:o.spanId,parentSpanId:o.parentSpanId});return}let d=Cn(l.auth),u=Kn(d),m,p=!1,f=(g,x)=>{if(p)return;p=!0;let y=Date.now()-n;this.recordRequest({timestamp:new Date().toISOString(),requestId:s,method:i,path:c,route:l.name,identity:u,status:g,durationMs:y,error:x,traceId:o.traceId,spanId:o.spanId,parentSpanId:o.parentSpanId}),this.logs?.append({timestamp:new Date().toISOString(),service:"proxy",source:"proxy",level:g>=400||x?"ERROR":"INFO",message:`${i} ${a} route=${l.name} identity=${u} status=${g} duration=${y}ms upgrade=true${x?` error=${x}`:""}`,pid:0,request_id:s,identity:u}),this.bus?.publish(ke(hi,l.name,{status:g,request_id:s,duration:y,identity:u}))},h=()=>{t.destroy(),m?.destroy()};try{let g={};for(let[v,b]of Object.entries(e.headers)){let S=v.toLowerCase();if(dm.has(S)&&S!=="connection"&&S!=="upgrade")continue;if(typeof b==="string")g[v]=b;else if(Array.isArray(b))g[v]=b.join(",")}let x=e.socket.remoteAddress??"",y=g.host??"",C=g["x-forwarded-for"];if(g["x-forwarded-for"]=C?`${C}, ${x}`:x,g["x-forwarded-host"]=y,g["x-forwarded-proto"]="http",delete g.host,oa(g,o,zn),this.middleware.length===0)await $s(l,g,this.tokens);for(let v of this.middleware)await v.apply({route:l,headers:g,tokens:this.tokens,req:e,method:i,path:a,upgrade:!0});let _=Nx(this.upstreamBase(l),a),E=hD(_)(_,{method:i,headers:g});E.on("upgrade",(v,b,S)=>{m=b,this.upgradedSockets.add(t),this.upgradedSockets.add(b);let w=()=>{this.upgradedSockets.delete(t),this.upgradedSockets.delete(b)};t.once("close",()=>{w(),b.destroy()}),b.once("close",()=>{w(),t.destroy()}),t.once("error",h),b.once("error",h);let D=`HTTP/${v.httpVersion} ${v.statusCode??101} ${v.statusMessage??"Switching Protocols"}\r
|
|
2386
|
+
`,O=v.rawHeaders.map((B,J)=>`${J%2===0?B+":":" "+B+`\r
|
|
2387
|
+
`}`).join("");if(t.write(`${D}${O}\r
|
|
2388
|
+
`),r.length>0)b.write(r);if(S.length>0)t.write(S);t.pipe(b).pipe(t),f(v.statusCode??101)}),E.on("response",(v)=>{v.resume();let b=v.statusCode??502;t.end(`HTTP/1.1 ${b} ${v.statusMessage??"Bad Gateway"}\r
|
|
2265
2389
|
Connection: close\r
|
|
2266
2390
|
Content-Length: 0\r
|
|
2267
2391
|
\r
|
|
2268
|
-
`),
|
|
2392
|
+
`),f(b,"upstream refused protocol upgrade")}),E.on("error",(v)=>{f(502,v.message),t.end(`HTTP/1.1 502 Bad Gateway\r
|
|
2269
2393
|
Connection: close\r
|
|
2270
2394
|
Content-Length: 0\r
|
|
2271
2395
|
\r
|
|
2272
|
-
`)}),t.once("error",()=>E.destroy()),E.end()}catch(
|
|
2396
|
+
`)}),t.once("error",()=>E.destroy()),E.end()}catch(g){let x=g instanceof Error?g.message:"proxy upgrade error";f(502,x),t.end(`HTTP/1.1 502 Bad Gateway\r
|
|
2273
2397
|
Connection: close\r
|
|
2274
2398
|
Content-Length: 0\r
|
|
2275
2399
|
\r
|
|
2276
|
-
`)}}async serve(e,t){let r=Date.now(),n=e.headers[gs]?.toString()||xy(8).toString("hex");t.setHeader(gs,n);let o=e.method??"GET",i=e.url??"/",s=this.detector?this.detector.redactText(i):i,a=Sy(this.cfg.routes,e);if(!a){ri(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=an(a.auth),l=_n(c),d=0,u;try{let m={};for(let[y,x]of Object.entries(e.headers)){if(Hu.has(y.toLowerCase()))continue;if(typeof x==="string")m[y]=x;else if(Array.isArray(x))m[y]=x.join(",")}let p=e.socket.remoteAddress??"",f=m.host??"",h=m["x-forwarded-for"];if(m["x-forwarded-for"]=h?`${h}, ${p}`:p,m["x-forwarded-host"]=f,m["x-forwarded-proto"]="http",delete m.host,m["accept-encoding"]="gzip, deflate, br",m[gs]=n,this.middleware.length===0)await ni(a,m,this.tokens);for(let y of this.middleware)await y.apply({route:a,headers:m,tokens:this.tokens});let v=wy(this.upstreamBase(a),i),b=o!=="GET"&&o!=="HEAD"?e:void 0,g=await fetch(v,{method:o,headers:m,body:b,redirect:"manual",duplex:b?"half":void 0});t.statusCode=g.status,d=g.status;let C=(g.headers.get("content-encoding")??"").split(",").map((y)=>y.trim().toLowerCase()).filter((y)=>y!==""),E=C.length>0&&C.every((y)=>j0.has(y));g.headers.forEach((y,x)=>{let w=x.toLowerCase();if(Hu.has(w))return;if(E&&(w==="content-encoding"||w==="content-length"))return;t.setHeader(x,this.detector?this.detector.redactText(y):y)}),await B0(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(ye(Di,a.name,{status:g.status,request_id:n,duration:S,identity:l}))}catch(m){let p=m instanceof Error?m.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}),ri(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 ni(e,t,r){let n=e.auth.type.toLowerCase();if(n===""||n==="none")return;if(!r)throw P(zt,"token manager unavailable");let o=an(e.auth),i=await r.get(_n(o),e.auth.audience,[],ic(e.auth));t.authorization=`Bearer ${i.accessToken}`;for(let[s,a]of Object.entries(e.auth.headers??{}))t[s]=a.includes("${token}")?a.replaceAll("${token}",i.accessToken):a}async function B0(e,t){if(!e.body){t.end();return}let r=F0.fromWeb(e.body);await new Promise((n,o)=>{r.on("error",o),t.on("error",o),t.on("finish",n),r.pipe(t)})}function U0(e){return e.protocol==="https:"?$0:O0}function wy(e,t){let r=new URL(e),n=new URL(t||"/",r),o=new URL(r.href);if(o.pathname=W0(n.pathname),o.search=n.search,o.hash="",o.origin!==r.origin)throw P(zt,"refusing to proxy to a different origin");return o}function W0(e){return(e.startsWith("/")?e:`/${e}`).replace(/^\/+/,"/")}function ri(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 Sy(e,t){let r=(t.headers.host??"").split(":")[0]??"",n=t.url??"/";return e.find((o)=>{if(Ko(o))return!1;let i=o.match.host===""||o.match.host===r,s=o.match.path===""||n.startsWith(o.match.path);return i&&s})}class Bu{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(!Er(e))return Promise.reject(P(zt,`refusing to bind token endpoint to ${e}`));return new Promise((t,r)=>{this.server=ky((n,o)=>{this.serve(n,o)}),this.server.on("error",(n)=>r(Se(zt,"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(!G0(e.socket.remoteAddress)){ri(t,403,"forbidden");return}if((e.headers[N0]??"")!==this.secret){ri(t,401,"unauthorized");return}if(e.url?.startsWith("/token")!==!0){ri(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{ri(t,500,"token error")}}}function G0(e){if(!e)return!0;return e==="127.0.0.1"||e==="::1"||e==="::ffff:127.0.0.1"}Tr();var Cy=1;class Py{tokenProviders=[];identityProviders=[];pluginIdentityProviders=[];environmentSources=[];healthChecks=[];logParsers=[];proxyMiddleware=[];loadErrors=[];pluginPaths=[];registerBuiltins(){this.tokenProviders.push(...En()),this.identityProviders.push(du(),uu()),this.logParsers.push(Yf()),this.proxyMiddleware.push(Y0())}register(e){z0(e),mo(this.tokenProviders,e.tokenProviders),mo(this.identityProviders,e.identityProviders),mo(this.pluginIdentityProviders,e.identityProviders),mo(this.environmentSources,e.environmentSources),mo(this.healthChecks,e.healthChecks),mo(this.logParsers,e.logParsers),mo(this.proxyMiddleware,e.proxyMiddleware)}}async function Ec(e,t=process.cwd()){let r=new Py;r.registerBuiltins();for(let n of e){if(n.trim()==="")continue;try{let o=n.startsWith("file:")?n:q0(n)?n:V0(t,n),s=await import(o.startsWith("file:")?o:K0(o).href);if(s.sdkVersion!==Cy)throw Error(`plugin SDK version ${String(s.sdkVersion??"missing")} is incompatible; expected ${Cy}`);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 z0(e){if(!e||typeof e!=="object")throw Error("plugin module must export an object");oi("tokenProviders",e.tokenProviders,["fetch"]),oi("identityProviders",e.identityProviders,["accepts","resolve"]),oi("environmentSources",e.environmentSources,["load"]),oi("healthChecks",e.healthChecks,["check"]),oi("logParsers",e.logParsers,["parse"]),oi("proxyMiddleware",e.proxyMiddleware,["apply"])}function oi(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 Y0(){return{name:"identity_inject",apply:async(e)=>{await ni(e.route,e.headers,e.tokens)}}}function mo(e,t){if(!t)return;e.push(...t)}var tA=200;function Uu(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 Wu(e,t){let r=new Map;for(let n of e){let o=nA(n,t);try{r.set(o,J0(o).mtimeMs)}catch{}}return r}async function rA(e,t,r){let n=t.plugins.map((a)=>a.path),o=JSON.stringify(r)!==JSON.stringify(n),i=Wu(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 Ec(n,t.repoRoot);e.registry=a;for(let c of a.loadErrors)e.log("devctl","ERROR",`plugin ${c.path} skipped: ${c.message}`);Uu(e),e.log("devctl","INFO","plugin path list reloaded")}return e.pluginMtimes=i,s}function nA(e,t){if(e.startsWith("file:"))return e;return Q0(e)?e:eA(t,e)}function Gu(e,t){let r=su(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 P(Y,`unknown health check type(s): ${o.map((i)=>`${i.service}.health.type=${i.type}`).join(", ")}`)}function Ku(e,t){let r=au(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 P(Y,`unknown identity type(s): ${o.map((i)=>`${i.service}.identity.type=${i.type}`).join(", ")}`)}function qu(e,t){let r=new Set(ss),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 P(Y,`unknown environment source(s): ${o.join(", ")}`)}function oA(e,t,r){for(let n of Object.keys(r))if(!t[n]&&!e.runtimes.has(n))e.runtimes.set(n,os(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 Vu(e){let t=Z0(e.cfg.repoRoot,".devctl");if(!e.fs.exists(t))return;try{e.configWatcher=X0(t,{recursive:!0},()=>{if(e.watchTimer)clearTimeout(e.watchTimer);e.watchTimer=setTimeout(()=>{e.reload().catch((r)=>e.log("devctl","WARN",L(r)))},tA)})}catch{e.log("devctl","WARN","unable to watch .devctl for configuration changes")}}async function Ry(e){let t;try{t=sn(e.cfg.repoRoot,e.cfg.configPath)}catch(l){throw e.bus.publish(ye(Ii,"",{error:L(l)})),e.log("devctl","ERROR",`configuration reload failed: ${L(l)}`),l}try{Gu(e.registry,t),Ku(e.registry,t),qu(e.registry,t)}catch(l){throw e.bus.publish(ye(Ii,"",{error:L(l)})),e.log("devctl","ERROR",`configuration reload failed: ${L(l)}`),l}if(e.setupMode){if(e.setupMode=!1,e.log("devctl","INFO",`configuration created at ${e.cfg.configPath}; leaving setup mode`),!e.configWatcher)Vu(e)}let r=Nu(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=by(e.cfg,t),oA(e,s,t.services);let a=iA(e.restartRequired,r.restart_required,Object.keys(t.services));e.restartRequired=a,r.restart_required=a;let c=await rA(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(ye(ga,"",{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",uc(r.supervisor_restart_required));return e.persistState(),e.refreshIdentity(),r}function iA(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 aA}from"fs";import{join as Ey,relative as cA}from"path";dt();function _y(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:ji).some((o)=>sA(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 sA(e,t){let r=e.replace(/[.+^${}()|[\]\\]/g,"\\$&").replaceAll("**/","<<<DS>>>").replaceAll("**","<<<DS>>>").replaceAll("*","[^/]*").replaceAll("<<<DS>>>",".*");return new RegExp(`^${r}$`).test(t)}class zu{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=Ey(n,i);try{let a=aA(s,{recursive:!0},(c,l)=>{let d=l?Ey(s,l.toString()):s,u=cA(n,d).replaceAll("\\","/");if(!_y(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 Yu,join as Ju}from"path";class Xu{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!==""&&!Yu(e.working_dir))return Ju(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((f)=>!f.auto).map((f)=>[f.name,f.value])),c=this.deps.proxy(),l=c?.isRunning()?`http://${c.address()}`:s.proxy.enabled?`http://${Sn(s.proxy.listen)}`:"",d=this.deps.userEmail(),u=cs(e,"127.0.0.1",a,l,s.project.name,d);if(!t.container){if(u.DEVCTL_INTERNAL_TOKEN=this.deps.internalTok(),s.proxy.token_endpoint.enabled)u.DEVCTL_TOKEN_URL=this.deps.boundTokenURL()||`http://127.0.0.1:${this.deps.tokenEndpoint()?.listenPort()||s.proxy.token_endpoint.port}/token`}let m=await as(s.repoRoot,{service:e,profile:r,serviceCfg:t,profileEnv:n,assignedPorts:a,runtime:u,userEmail:d,cfg:s,fetchSecret:hs(async()=>(await this.deps.tokens.get("user","",[])).accessToken),pluginSources:this.deps.environmentSources(),clientEnv:o,includeProcess:i}),p=t.working_dir&&!Yu(t.working_dir)?Ju(s.repoRoot,t.working_dir):t.working_dir;return{env:ei(m),workDir:p}}async resolveTaskEnvironment(e,t,r){let n=this.deps.cfg(),o=this.deps.userEmail(),i=await as(n.repoRoot,{service:`task:${e}`,profile:this.profile,serviceCfg:t,profileEnv:this.profileEnv,assignedPorts:{},runtime:cs(`task:${e}`,"127.0.0.1",{},"",n.project.name,o),userEmail:o,cfg:n,clientEnv:r,fetchSecret:hs(async()=>(await this.deps.tokens.get("user","",[])).accessToken),pluginSources:this.deps.environmentSources()}),s=t.working_dir&&!Yu(t.working_dir)?Ju(n.repoRoot,t.working_dir):t.working_dir;return{env:ei(i),workDir:s}}}Tr();te();Tr();At();import{cpus as yA,loadavg as bA,platform as xA,uptime as wA}from"os";import{readFileSync as lA}from"fs";import{freemem as dA,totalmem as uA}from"os";import{spawnSync as pA}from"child_process";var Qu=1024,mA=2000,fA=/page size of (\d+) bytes/i,ii;function hA(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 gA(e){let t=e.match(fA),r=t?Number(t[1]):0;if(!Number.isFinite(r)||r<=0)return;let n=Tc(e,"free")+Tc(e,"inactive")+Tc(e,"speculative")+Tc(e,"purgeable");return Math.round(n*r/Qu)}function Tc(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 vA(){if(process.platform==="linux")try{return hA(lA("/proc/meminfo","utf8"))}catch{return}if(process.platform==="darwin"){let e=Date.now();if(ii&&e-ii.at<mA)return ii.leftoverKB;let t=pA("vm_stat",{encoding:"utf8"});if(t.status!==0||!t.stdout)return ii?.leftoverKB;let r=gA(t.stdout);if(r===void 0)return ii?.leftoverKB;return ii={at:e,leftoverKB:r},r}return}function Ty(){let e=Math.max(0,Math.round(uA()/Qu)),t=Math.max(0,Math.round(dA()/Qu)),r=Math.max(0,Math.min(e,vA()??t));return{totalKB:e,unusedKB:t,leftoverKB:r}}function Ay(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?Br(e).map((t)=>[t,"unknown"]):[]),iap:e?.proxy.routes.some((t)=>t.auth.type.toLowerCase()==="iap")??!1}}function Zu(){let e=bA(),t=Ty();return{platform:xA(),cpuCount:yA().length,loadAvg1:e[0]??0,loadAvg5:e[1]??0,loadAvg15:e[2]??0,memTotalKB:t.totalKB,memFreeKB:t.unusedKB,memAvailableKB:t.leftoverKB,hostUptimeSec:wA()}}function ep(e,t){let r={},n={};for(let o of Br(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 Ly(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,...ep(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:Zu(),stats_series:e.statsSeries}}function Dy(e){let t=[`PROFILE: ${e.profile||"(none)"}`,"","SERVICE\tSTATUS\tHEALTH\tPID"];for(let[r,n]of Object.entries(e.services))t.push(`${r} ${Qo(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(`
|
|
2400
|
+
`)}}async serve(e,t){let r=Date.now(),n=na({traceparent:e.headers[Bo]?.toString(),requestId:e.headers[zn]?.toString()}),o=n.requestId;t.setHeader(zn,o),t.setHeader(Bo,cc(n));let s=e.method??"GET",i=e.url??"/",a=this.detector?this.detector.redactText(i):i,c=$x(this.cfg.routes,e);if(!c){Ns(t,404,"no matching proxy route"),this.recordRequest({timestamp:new Date().toISOString(),requestId:o,method:s,path:a,route:"",identity:"",status:404,durationMs:Date.now()-r,traceId:n.traceId,spanId:n.spanId,parentSpanId:n.parentSpanId});return}if(s==="OPTIONS"&&e.headers["access-control-request-method"]!==void 0&&Object.keys(c.response_headers??{}).length>0){this.applyResponseHeaders(t,c),t.statusCode=204,t.setHeader("content-length","0"),t.end(),this.recordRequest({timestamp:new Date().toISOString(),requestId:o,method:s,path:a,route:c.name,identity:"",status:204,durationMs:Date.now()-r,traceId:n.traceId,spanId:n.spanId,parentSpanId:n.parentSpanId});return}let l=Cn(c.auth),d=Kn(l),u=0,m;try{let p={};for(let[b,S]of Object.entries(e.headers)){if(dm.has(b.toLowerCase()))continue;if(typeof S==="string")p[b]=S;else if(Array.isArray(S))p[b]=S.join(",")}let f=e.socket.remoteAddress??"",h=p.host??"",g=p["x-forwarded-for"];if(p["x-forwarded-for"]=g?`${g}, ${f}`:f,p["x-forwarded-host"]=h,p["x-forwarded-proto"]="http",delete p.host,p["accept-encoding"]="gzip, deflate, br",oa(p,n,zn),this.middleware.length===0)await $s(c,p,this.tokens);for(let b of this.middleware)await b.apply({route:c,headers:p,tokens:this.tokens});let x=Nx(this.upstreamBase(c),i),y=s!=="GET"&&s!=="HEAD"?e:void 0,C=await fetch(x,{method:s,headers:p,body:y,redirect:"manual",duplex:y?"half":void 0});t.statusCode=C.status,u=C.status;let _=(C.headers.get("content-encoding")??"").split(",").map((b)=>b.trim().toLowerCase()).filter((b)=>b!==""),E=_.length>0&&_.every((b)=>mD.has(b));C.headers.forEach((b,S)=>{let w=S.toLowerCase();if(dm.has(w))return;if(E&&(w==="content-encoding"||w==="content-length"))return;t.setHeader(S,this.detector?this.detector.redactText(b):b)}),this.applyResponseHeaders(t,c),await fD(C,t);let v=Date.now()-r;this.logs?.append({timestamp:new Date().toISOString(),service:"proxy",source:"proxy",level:C.status>=400?"WARN":"INFO",message:`${s} ${i} route=${c.name} identity=${d} status=${C.status} duration=${v}ms`,pid:0,request_id:o,identity:d}),this.bus?.publish(ke(hi,c.name,{status:C.status,request_id:o,duration:v,identity:d}))}catch(p){let f=p instanceof Error?p.message:"proxy error";m=f,u=502,this.logs?.append({timestamp:new Date().toISOString(),service:"proxy",source:"proxy",level:"ERROR",message:`${s} ${i} route=${c.name} identity=${d} error=${f}`,pid:0,request_id:o,identity:d}),this.applyResponseHeaders(t,c),Ns(t,502,"proxy error")}finally{this.recordRequest({timestamp:new Date().toISOString(),requestId:o,method:s,path:a,route:c.name,identity:d,status:u,durationMs:Date.now()-r,error:m,traceId:n.traceId,spanId:n.spanId,parentSpanId:n.parentSpanId})}}}async function $s(e,t,r){let n=e.auth.type.toLowerCase();if(n===""||n==="none")return;if(!r)throw R(nr,"token manager unavailable");let o=Cn(e.auth),s=await r.get(Kn(o),e.auth.audience,[],el(e.auth));t.authorization=`Bearer ${s.accessToken}`;for(let[i,a]of Object.entries(e.auth.headers??{}))t[i]=a.includes("${token}")?a.replaceAll("${token}",s.accessToken):a}async function fD(e,t){if(!e.body){t.end();return}let r=dD.fromWeb(e.body);await new Promise((n,o)=>{r.on("error",o),t.on("error",o),t.on("finish",n),r.pipe(t)})}function hD(e){return e.protocol==="https:"?lD:cD}function Nx(e,t){let r=new URL(e),n=new URL(t||"/",r),o=new URL(r.href);if(o.pathname=gD(n.pathname),o.search=n.search,o.hash="",o.origin!==r.origin)throw R(nr,"refusing to proxy to a different origin");return o}function gD(e){return(e.startsWith("/")?e:`/${e}`).replace(/^\/+/,"/")}function Ns(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 $x(e,t){let r=(t.headers.host??"").split(":")[0]??"",n=t.url??"/";return e.find((o)=>{if(_o(o))return!1;let s=o.match.host===""||o.match.host===r,i=o.match.path===""||n.startsWith(o.match.path);return s&&i})}class pm{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(!sr(e))return Promise.reject(R(nr,`refusing to bind token endpoint to ${e}`));return new Promise((t,r)=>{this.server=Fx((n,o)=>{this.serve(n,o)}),this.server.on("error",(n)=>r(xe(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(!yD(e.socket.remoteAddress)){Ns(t,403,"forbidden");return}if((e.headers[uD]??"")!==this.secret){Ns(t,401,"unauthorized");return}if(e.url?.startsWith("/token")!==!0){Ns(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 s=await this.tokens.get(n,o,[]);t.setHeader("content-type","application/json"),t.end(JSON.stringify({access_token:s.accessToken,token_type:s.tokenType,expires_at:s.expiresAt.toISOString(),identity:s.identity}))}catch{Ns(t,500,"token error")}}}function yD(e){if(!e)return!0;return e==="127.0.0.1"||e==="::1"||e==="::ffff:127.0.0.1"}Wr();var Hx=1;class Ux{tokenProviders=[];identityProviders=[];pluginIdentityProviders=[];environmentSources=[];healthChecks=[];logParsers=[];proxyMiddleware=[];loadErrors=[];pluginPaths=[];registerBuiltins(){this.tokenProviders.push(...Vn()),this.identityProviders.push(Mp(),Np()),this.logParsers.push(cy()),this.proxyMiddleware.push(SD())}register(e){wD(e),Wo(this.tokenProviders,e.tokenProviders),Wo(this.identityProviders,e.identityProviders),Wo(this.pluginIdentityProviders,e.identityProviders),Wo(this.environmentSources,e.environmentSources),Wo(this.healthChecks,e.healthChecks),Wo(this.logParsers,e.logParsers),Wo(this.proxyMiddleware,e.proxyMiddleware)}}async function Cl(e,t=process.cwd()){let r=new Ux;r.registerBuiltins();for(let n of e){if(n.trim()==="")continue;try{let o=n.startsWith("file:")?n:bD(n)?n:xD(t,n),i=await import(o.startsWith("file:")?o:vD(o).href);if(i.sdkVersion!==Hx)throw Error(`plugin SDK version ${String(i.sdkVersion??"missing")} is incompatible; expected ${Hx}`);r.register(i),r.pluginPaths.push(o)}catch(o){r.loadErrors.push({path:n,message:o instanceof Error?o.message:String(o)})}}return r}function wD(e){if(!e||typeof e!=="object")throw Error("plugin module must export an object");Fs("tokenProviders",e.tokenProviders,["fetch"]),Fs("identityProviders",e.identityProviders,["accepts","resolve"]),Fs("environmentSources",e.environmentSources,["load"]),Fs("healthChecks",e.healthChecks,["check"]),Fs("logParsers",e.logParsers,["parse"]),Fs("proxyMiddleware",e.proxyMiddleware,["apply"])}function Fs(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 s of r)if(typeof o[s]!=="function")throw Error(`${e}[${n}].${s} must be a function`)}}function SD(){return{name:"identity_inject",apply:async(e)=>{await $s(e.route,e.headers,e.tokens)}}}function Wo(e,t){if(!t)return;e.push(...t)}var PD=200;function mm(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 fm(e,t){let r=new Map;for(let n of e){let o=AD(n,t);try{r.set(o,kD(o).mtimeMs)}catch{}}return r}async function ED(e,t,r){let n=t.plugins.map((a)=>a.path),o=JSON.stringify(r)!==JSON.stringify(n),s=fm(n,t.repoRoot),i=[];for(let[a,c]of s){let l=e.pluginMtimes.get(a);if(l!==void 0&&l!==c){i.push("plugins");break}}if(o){let a=await Cl(n,t.repoRoot);e.registry=a;for(let c of a.loadErrors)e.log("devctl","ERROR",`plugin ${c.path} skipped: ${c.message}`);mm(e),e.log("devctl","INFO","plugin path list reloaded")}return e.pluginMtimes=s,i}function AD(e,t){if(e.startsWith("file:"))return e;return RD(e)?e:TD(t,e)}function hm(e,t){let r=Ip(t);if(r.length===0)return;let n=new Set((e?.healthChecks??[]).map((s)=>s.name.toLowerCase())),o=r.filter((s)=>!n.has(s.type.toLowerCase()));if(o.length>0)throw R(ee,`unknown health check type(s): ${o.map((s)=>`${s.service}.health.type=${s.type}`).join(", ")}`)}function gm(e,t){let r=Lp(t);if(r.length===0)return;let n=e?.pluginIdentityProviders??[],o=r.filter(({service:s})=>{let i=t.services[s];return!i||!n.some((a)=>a.accepts(i.identity))});if(o.length>0)throw R(ee,`unknown identity type(s): ${o.map((s)=>`${s.service}.identity.type=${s.type}`).join(", ")}`)}function ym(e,t){let r=new Set(Vi),n=new Set((e?.environmentSources??[]).map((s)=>s.name)),o=t.environment.sources.filter((s)=>!r.has(s)&&!n.has(s));if(o.length>0)throw R(ee,`unknown environment source(s): ${o.join(", ")}`)}function ID(e,t,r){for(let n of Object.keys(r))if(!t[n]&&!e.runtimes.has(n))e.runtimes.set(n,qi(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 vm(e){let t=_D(e.cfg.repoRoot,".devctl");if(!e.fs.exists(t))return;try{e.configWatcher=CD(t,{recursive:!0},()=>{if(e.watchTimer)clearTimeout(e.watchTimer);e.watchTimer=setTimeout(()=>{e.reload().catch((r)=>e.log("devctl","WARN",I(r)))},PD)})}catch{e.log("devctl","WARN","unable to watch .devctl for configuration changes")}}async function jx(e){let t;try{t=kn(e.cfg.repoRoot,e.cfg.configPath)}catch(l){throw e.bus.publish(ke(gi,"",{error:I(l)})),e.log("devctl","ERROR",`configuration reload failed: ${I(l)}`),l}try{hm(e.registry,t),gm(e.registry,t),ym(e.registry,t)}catch(l){throw e.bus.publish(ke(gi,"",{error:I(l)})),e.log("devctl","ERROR",`configuration reload failed: ${I(l)}`),l}if(e.setupMode){if(e.setupMode=!1,e.log("devctl","INFO",`configuration created at ${e.cfg.configPath}; leaving setup mode`),!e.configWatcher)vm(e)}let r=lm(e.cfg,t),n=JSON.stringify(e.cfg.proxy)!==JSON.stringify(t.proxy),o=JSON.stringify(e.cfg.secrets)!==JSON.stringify(t.secrets),s=e.cfg.plugins.map((l)=>l.path),i=e.cfg.services;e.cfg=Mx(e.cfg,t),ID(e,i,t.services);let a=LD(e.restartRequired,r.restart_required,Object.keys(t.services));e.restartRequired=a,r.restart_required=a;let c=await ED(e,t,s);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(ke(rc,"",{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",il(r.supervisor_restart_required));return e.persistState(),e.refreshIdentity(),r}function LD(e,t,r){let n=new Set(r),o=new Set(e.filter((s)=>n.has(s)));for(let s of t)if(n.has(s))o.add(s);return[...o].sort()}import{watch as OD}from"fs";import{join as Wx,relative as MD}from"path";lt();function Bx(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:Pi).some((o)=>DD(o,r)))return!1;if(e.paths.length===0)return!1;return e.paths.some((o)=>{let s=o.replaceAll("\\","/").replace(/\/$/,"");return r===s||r.startsWith(`${s}/`)})}function DD(e,t){let r=e.replace(/[.+^${}()|[\]\\]/g,"\\$&").replaceAll("**/","<<<DS>>>").replaceAll("**","<<<DS>>>").replaceAll("*","[^/]*").replaceAll("<<<DS>>>",".*");return new RegExp(`^${r}$`).test(t)}class bm{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 s=o.replaceAll("\\","/").replace(/\/$/,"");if(s===""||s==="."){this.host.log(e,"WARN","service.watch skipped a whole-repo path; list concrete directories");continue}let i=Wx(n,s);try{let a=OD(i,{recursive:!0},(c,l)=>{let d=l?Wx(i,l.toString()):i,u=MD(n,d).replaceAll("\\","/");if(!Bx(t,u))return;this.schedule(e,t.debounce_ms)});r.push(a)}catch{this.host.log(e,"WARN",`unable to watch ${s}`)}}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 xm,join as wm}from"path";function Gx(e,t,r){if(r.trim()==="")return;if((e.OTEL_EXPORTER_OTLP_ENDPOINT??"").trim()==="")e.OTEL_EXPORTER_OTLP_ENDPOINT=r;if((e.OTEL_EXPORTER_OTLP_PROTOCOL??"").trim()==="")e.OTEL_EXPORTER_OTLP_PROTOCOL="http/json";if((e.OTEL_SERVICE_NAME??"").trim()==="")e.OTEL_SERVICE_NAME=t}class Sm{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!==""&&!xm(e.working_dir))return wm(this.deps.cfg().repoRoot,e.working_dir);return e.working_dir}async resolveServiceExecution(e,t,r,n,o,s=!0){let i=this.deps.cfg(),a=this.deps.ports().get(e)??Object.fromEntries(t.ports.filter((h)=>!h.auto).map((h)=>[h.name,h.value])),c=this.deps.proxy(),l=c?.isRunning()?`http://${c.address()}`:i.proxy.enabled?`http://${jn(i.proxy.listen)}`:"",d=this.deps.userEmail(),u=Yi(e,"127.0.0.1",a,l,i.project.name,d);if(!t.container){if(u.DEVCTL_INTERNAL_TOKEN=this.deps.internalTok(),i.proxy.token_endpoint.enabled)u.DEVCTL_TOKEN_URL=this.deps.boundTokenURL()||`http://127.0.0.1:${this.deps.tokenEndpoint()?.listenPort()||i.proxy.token_endpoint.port}/token`}let m=await zi(i.repoRoot,{service:e,profile:r,serviceCfg:t,profileEnv:n,assignedPorts:a,runtime:u,userEmail:d,cfg:i,fetchSecret:ra(async()=>(await this.deps.tokens.get("user","",[])).accessToken),pluginSources:this.deps.environmentSources(),clientEnv:o,includeProcess:s}),p=t.working_dir&&!xm(t.working_dir)?wm(i.repoRoot,t.working_dir):t.working_dir,f=Os(m);if(!t.container)Gx(f,e,this.deps.otlpEndpoint());return{env:f,workDir:p}}async resolveTaskEnvironment(e,t,r){let n=this.deps.cfg(),o=this.deps.userEmail(),s=await zi(n.repoRoot,{service:`task:${e}`,profile:this.profile,serviceCfg:t,profileEnv:this.profileEnv,assignedPorts:{},runtime:Yi(`task:${e}`,"127.0.0.1",{},"",n.project.name,o),userEmail:o,cfg:n,clientEnv:r,fetchSecret:ra(async()=>(await this.deps.tokens.get("user","",[])).accessToken),pluginSources:this.deps.environmentSources()}),i=t.working_dir&&!xm(t.working_dir)?wm(n.repoRoot,t.working_dir):t.working_dir;return{env:Os(s),workDir:i}}}Wr();ne();Wr();Nt();import{cpus as qD,loadavg as KD,platform as VD,uptime as zD}from"os";import{readFileSync as ND}from"fs";import{freemem as $D,totalmem as FD}from"os";import{spawnSync as HD}from"child_process";var km=1024,UD=2000,jD=/page size of (\d+) bytes/i,Hs;function BD(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 WD(e){let t=e.match(jD),r=t?Number(t[1]):0;if(!Number.isFinite(r)||r<=0)return;let n=Rl(e,"free")+Rl(e,"inactive")+Rl(e,"speculative")+Rl(e,"purgeable");return Math.round(n*r/km)}function Rl(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 GD(){if(process.platform==="linux")try{return BD(ND("/proc/meminfo","utf8"))}catch{return}if(process.platform==="darwin"){let e=Date.now();if(Hs&&e-Hs.at<UD)return Hs.leftoverKB;let t=HD("vm_stat",{encoding:"utf8"});if(t.status!==0||!t.stdout)return Hs?.leftoverKB;let r=WD(t.stdout);if(r===void 0)return Hs?.leftoverKB;return Hs={at:e,leftoverKB:r},r}return}function qx(){let e=Math.max(0,Math.round(FD()/km)),t=Math.max(0,Math.round($D()/km)),r=Math.max(0,Math.min(e,GD()??t));return{totalKB:e,unusedKB:t,leftoverKB:r}}function Kx(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?rn(e).map((t)=>[t,"unknown"]):[]),iap:e?.proxy.routes.some((t)=>t.auth.type.toLowerCase()==="iap")??!1}}function Cm(){let e=KD(),t=qx();return{platform:VD(),cpuCount:qD().length,loadAvg1:e[0]??0,loadAvg5:e[1]??0,loadAvg15:e[2]??0,memTotalKB:t.totalKB,memFreeKB:t.unusedKB,memAvailableKB:t.leftoverKB,hostUptimeSec:zD()}}function Rm(e,t){let r={},n={};for(let o of rn(e)){let s=t.get(o)??"unknown";if(n[o]=s,s!=="unknown")r[o]=s==="available"}return{service_accounts:r,service_account_status:n}}function Vx(e){let t={};for(let[o,s]of e.runtimes)t[o]={...s,ports:e.ports.get(o)??s.ports,profile:e.serviceProfile.get(o)??s.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 s=o.match.host||"*",i=o.match.path,a=i===""?s:`${s}${i.startsWith("/")?i:`/${i}`}`;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,...Rm(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:Cm(),stats_series:e.statsSeries}}function zx(e){let t=[`PROFILE: ${e.profile||"(none)"}`,"","SERVICE\tSTATUS\tHEALTH\tPID"];for(let[r,n]of Object.entries(e.services))t.push(`${r} ${Ls(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(`
|
|
2277
2401
|
`)+`
|
|
2278
|
-
`}var SA=4000;class tp{cache;accounts=new Map;entries=[];deps;constructor(e){this.deps=e,this.cache=Ay(e.cfg()),this.deps.bus.subscribe((t)=>{let r=t.payload??{},n=t.type===$o?`token refreshed identity=${String(r.identity??"")}`:t.type===qn?`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===qn?"WARN":"INFO",message:n,pid:0}),t.type===$o||t.type===qn)this.syncCredentialEntries()},[$o,qn,fd])}get identityCache(){return this.cache}get serviceAccountStatus(){return this.accounts}get credentialEntries(){return this.entries}async prepareServiceIdentity(e,t){let r=Xa(t.identity);if(r.kind!=="none")try{if(r=await Hg(t.identity,()=>Wg(this.deps.cfg().google.project_id),this.deps.identityProviders()),r.kind==="service_account")await this.deps.tokens.get(_n(r),"",[]),this.accounts.set(r.serviceAccount,"available")}catch(n){if(r.kind==="service_account")this.accounts.set(r.serviceAccount,"unavailable");if(CA(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 Br(t))await this.probeServiceAccount(i);let{service_accounts:n,service_account_status:o}=ep(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(ye(fd,"",{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: ${L(t)}`)}}async probeServiceAccount(e){let t;try{await kA(this.deps.tokens.refresh(`sa:${e}`,"",[]),SA),t="available"}catch{t="unavailable"}return this.accounts.set(e,t),t}}function kA(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 CA(e){return e.capabilities.some((t)=>["google_api","iap","service_identity"].includes(t))}te();te();np();var TA=600,AA=65535;async function Ny(e,t){let r=Number.isInteger(t)&&(t??0)>0?t:fo(e);for(let n=0;n<TA;n+=1){let o=r+n;if(o>AA)break;if(await Uo(o))return o}throw P(Y,`no free MCP port starting at ${r}`)}class op{listener;disabled=[];token;deps;constructor(e){this.deps=e,this.token=Of(e.repoRoot())}get instance(){return this.listener}get disabledTools(){return this.disabled}async bootFromPreferences(){let e=Mo(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",L(t)))}async start(e){if(this.listener?.isRunning())return;let t=await Ny(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()}}dt();import*as ho from"http2";import{randomBytes as LA}from"crypto";te();Tr();var ip="14",DA="16",IA="1",Hy=new Set(["host","connection","keep-alive","transfer-encoding","upgrade","proxy-connection","authorization"]);class sp{route;tokens;logs;bus;detector;server;client;running=!1;addr="";requests=new _c;constructor(e,t,r,n,o){this.route=e;this.tokens=t;this.logs=r;this.bus=n;this.detector=o}address(){return this.addr}isRunning(){return this.running}stats(){return this.requests.stats()}start(){let e=this.route.listen?.host||"127.0.0.1",t=this.route.listen?.port??0;if(!Er(e))return Promise.reject(P(zt,`refusing to bind grpc route ${this.route.name} to ${e}`));if(t===0)return Promise.reject(P(zt,`grpc route ${this.route.name} requires listen.port`));return new Promise((r,n)=>{let o=ho.createServer();o.on("stream",(i,s)=>void this.serve(i,s)),o.on("sessionError",()=>{}),o.on("error",(i)=>{if(this.running){this.log("ERROR",`grpc route ${this.route.name} listener error: ${i.message}`);return}n(Se(zt,`grpc route ${this.route.name} unable to listen on ${e}:${t}`,i))}),o.listen(t,e,()=>{this.server=o,this.running=!0,this.addr=`${e}:${t}`,this.log("INFO",`grpc route ${this.route.name} listening on ${this.addr} \u2192 ${this.route.upstream.url}`),r()})})}stop(){let e=this.server;if(this.client?.close(),this.client=void 0,!e)return Promise.resolve();return new Promise((t)=>{e.close(()=>{this.running=!1,t()})})}upstream(){if(this.client&&!this.client.closed&&!this.client.destroyed)return this.client;let e=ho.connect(this.route.upstream.url);return e.on("error",()=>{if(this.client===e)this.client=void 0}),e.on("goaway",()=>{if(this.client===e)this.client=void 0}),e.on("close",()=>{if(this.client===e)this.client=void 0}),this.client=e,e}async serve(e,t){let r=Date.now(),n=LA(8).toString("hex"),o=String(t[":path"]??"/"),i=an(this.route.auth),s=_n(i),a=!1,c=(f,h,v)=>{if(a)return;a=!0;let b=Date.now()-r,g=this.detector?this.detector.redactText(o):o,C=v??(h!=="0"?`grpc-status ${h}`:void 0);this.requests.record({timestamp:new Date().toISOString(),requestId:n,method:"POST",path:g,route:this.route.name,identity:s,status:f,durationMs:b,error:C}),this.log(C?"WARN":"INFO",`grpc ${o} route=${this.route.name} identity=${s} grpc-status=${h} duration=${b}ms${C?` error=${C}`:""}`,n,s),this.bus?.publish(ye(Di,this.route.name,{status:f,request_id:n,duration:b,identity:s}))},l=(f,h)=>{try{e.respond({":status":200,"content-type":"application/grpc","grpc-status":f,"grpc-message":h},{endStream:!0})}catch{e.close(ho.constants.NGHTTP2_INTERNAL_ERROR)}c(200,f,h)},d;try{d=this.buildUpstreamHeaders(t),await ni(this.route,d,this.tokens)}catch(f){l(DA,f instanceof Error?f.message:"token injection failed");return}let u;try{u=this.upstream().request(d)}catch(f){l(ip,f instanceof Error?f.message:"upstream unavailable");return}let m=!1,p={};u.on("response",(f)=>{if(m)return;if(m=!0,f["grpc-status"]!==void 0){e.respond(this.frontResponseHeaders(f),{endStream:!0}),c(Number(f[":status"]??200),String(f["grpc-status"]));return}e.respond(this.frontResponseHeaders(f),{waitForTrailers:!0}),e.on("wantTrailers",()=>{try{e.sendTrailers(this.sanitizeTrailers(p))}catch{}c(Number(f[":status"]??200),String(p["grpc-status"]??"0"))}),u.pipe(e,{end:!1}),u.on("end",()=>{try{e.end()}catch{}})}),u.on("trailers",(f)=>{p=f}),u.on("error",(f)=>{if(!m)l(ip,f.message);else c(502,ip,f.message),e.close(ho.constants.NGHTTP2_INTERNAL_ERROR)}),e.on("error",()=>u.destroy()),e.on("close",()=>{if(!a)c(499,IA,"client closed before completion"),u.destroy()}),e.pipe(u)}buildUpstreamHeaders(e){let t={};for(let[n,o]of Object.entries(e)){if(n.startsWith(":")||Hy.has(n.toLowerCase())||o===void 0)continue;t[n]=Array.isArray(o)?o.join(", "):String(o)}let r=new URL(this.route.upstream.url);return t[":method"]=String(e[":method"]??"POST"),t[":path"]=String(e[":path"]??"/"),t[":scheme"]=r.protocol==="https:"?"https":"http",t[":authority"]=r.host,t}frontResponseHeaders(e){let t={":status":e[":status"]??200};for(let[r,n]of Object.entries(e)){if(r.startsWith(":")||Hy.has(r.toLowerCase())||n===void 0)continue;t[r]=n}return t}sanitizeTrailers(e){let t={};for(let[r,n]of Object.entries(e)){if(r.startsWith(":")||n===void 0)continue;t[r]=n}if(t["grpc-status"]===void 0)t["grpc-status"]="0";return t}log(e,t,r="",n=""){this.logs?.append({timestamp:new Date().toISOString(),service:"proxy",source:"proxy",level:e,message:t,pid:0,request_id:r,identity:n})}}class ap{server;tokenEP;grpc=[];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 ju(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 Bu(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`;for(let t of e.proxy.routes.filter(Ko)){let r=new sp(t,this.deps.tokens,this.deps.logs,this.deps.bus,this.deps.detector);await r.start(),this.grpc.push(r)}this.deps.persistState()}async stop(){await this.server?.stop(),await this.tokenEP?.stop(),await Promise.all(this.grpc.map((e)=>e.stop())),this.grpc=[],this.deps.persistState()}}At();var MA=3000,jy=5000,By=60;class cp{timer;cpu=[];mem=[];lastAt=0;deps;constructor(e){this.deps=e}start(){if(this.timer)return;this.timer=setInterval(()=>void this.poll(),MA)}stop(){if(this.timer)clearInterval(this.timer),this.timer=void 0}series(){return{interval_ms:jy,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===mr||r.state===Ur))e.push(r.pid);if(e.length===0)return;let t=await uy(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<jy)return;this.lastAt=e;let t=Zu(),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>By)this.cpu.shift();if(this.mem.length>By)this.mem.shift()}}function lp(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function An(e){if(!Array.isArray(e))return[];return e.filter((t)=>typeof t==="string")}function vs(e){if(!lp(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 OA}from"net";var $A=2000;class dp{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=OA((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)}
|
|
2279
|
-
`);if(r.shift(),!c){n=!0,e.once("drain",()=>{n=!1,o()});return}}},
|
|
2280
|
-
`);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=yh(n);t({id:r.id,error:o.error,kind:o.kind,hint:o.hint,service:o.service})}}}te();Tr();At();class up{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=dd(),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 ap({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 Xu({cfg:()=>this.cfg,ports:()=>this.ports,userEmail:()=>this.identity.identityCache.user,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 op({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 cp({clock:this.clock,runtimes:()=>this.runtimes}),this.identity=new tp({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 dp({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,os(r));this.orchestrator.bind(this.lifecycleSession()),this.commands=t.createCommands(this),this.serviceWatchers=new zu({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=sa(this.cfg.repoRoot);this.lock=this.acquireLockFn(this.cfg.repoRoot,e),this.rpc.removeStaleSocket(e),this.registry=await Ec(this.cfg.plugins.map((t)=>t.path),this.cfg.repoRoot),this.pluginMtimes=Wu(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}`);Uu(this.reloadHost()),Gu(this.registry,this.cfg),Ku(this.registry,this.cfg),qu(this.registry,this.cfg),await this.recoverSession(),this.serviceWatchers.sync(this.cfg.services),Vu(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=lp(t)?t:{};switch(e){case"ping":return{session:this.sessionID,version:jt,protocol:ts};case"start":return this.commands.startService.execute({services:An(r.services),profile:typeof r.profile==="string"&&r.profile!==""?r.profile:void 0,detach:r.detach===!0,client_env:vs(r.client_env)});case"stop":return await this.commands.stopService.execute(An(r.services)),null;case"restart":return await this.commands.restartService.execute(An(r.services),{cascade:r.cascade===!0,clientEnv:vs(r.client_env)}),null;case"run_task":return this.runTask(typeof r.name==="string"?r.name:"",vs(r.client_env)??{});case"exec":return this.execService(typeof r.service==="string"?r.service:"",An(r.command),vs(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:An(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:An(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:An(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??Mo(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:Oa(this.cfg.shutdown);return setTimeout(()=>{this.shutdown(n)},50),null;default:throw P(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-${cr(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 wh(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",L(t));throw t}}async runTask(e,t){let r=this.cfg.tasks[e];if(!r)throw P(lt,`unknown task ${e}`);if(r.dependencies.length>0)await this.start({services:r.dependencies,client_env:t});let n={...xn(),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 P(Bo,`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 P(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(wn(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:kn(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 P(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 yy(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)=>zi(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 Br(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 Ry(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 Ly(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 Dy(this.snapshot())}setState(e,t,r,n,o){let i=this.runtimes.get(e)??os(e);if(!fs(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===mr||t===Ur;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(ye(Bf,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 Jn(o);if(!i||i.pid===process.pid)continue;if(r){let s=await this.inspectProcessFn(i.pid);if(!(s!==void 0&&s.command!==""&&ps({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 _a(i),this.log(e,"INFO",`released port ${o} (pid ${i.pid})`)}catch(s){this.log(e,"WARN",L(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,ut,Tt,0,L(t));try{await this.procs.stop(e,kn(this.cfg.shutdown)*1000)}catch(r){this.log(e,"WARN",L(r))}this.bus.publish(ye(ha,e,{error:L(t)})),this.log(e,"ERROR",L(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})}If(this.cfg.repoRoot,{session_id:this.sessionID,repo_root:this.cfg.repoRoot,profile:this.profile,processes:e})}}var Wy=500,FA=1e4,NA=2000,HA=new URL("./log-worker.ts",import.meta.url);class Gy{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??HA,{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=Wy){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(!jA(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(!BA(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"},NA)}catch{}finally{this.markDead(Error("log worker closed"))}}rpc(e,t=FA){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(ye(Oo,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 jA(e){if(e===null||Array.isArray(e)||!("events"in e))return!1;return Array.isArray(e.events)}function BA(e){return e!==null&&!Array.isArray(e)&&"byService"in e}function Uy(e,t,r){return lh(new Ld(e.max,t,r,e.persist,e.directory,e.sessionID,e.retentionDays,e.maxSessionLogs))}async function Ky(e,t,r,n={}){if(n.standalone??Bun.isStandaloneExecutable===!0)return{logs:Uy(e,t,r),usingWorker:!1};try{let i=new Gy(e,t,{script:n.script});return await i.waitUntilReady(n.initTimeoutMs??Wy),{logs:i,usingWorker:!0}}catch{return{logs:Uy(e,t,r),usingWorker:!1}}}si();import{createServer as pL}from"http";te();bs();var mL="2025-03-26",fb="2.0",db=-32700,ub=-32600,fL=-32601,pb=-32602,hL=-32603;function gL(e){return e!==""&&Er(e)}class hp{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(!gL(this.opts.host))return Promise.reject(P(lt,`refusing to bind MCP to ${this.opts.host}`));return new Promise((e,t)=>{this.server=pL((r,n)=>{this.serve(r,n)}),this.server.on("error",(r)=>t(Se(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,Ss()),t.end();return}if(!this.authorized(e)){this.emit("WARN",`rejected unauthorized request from ${mb(e)}`),ci(t,401,{error:"unauthorized"});return}if(yL(e)!=="/mcp"){ci(t,404,{error:"not found"});return}if(e.method==="GET"||e.method==="DELETE"){t.writeHead(405,{Allow:"POST, OPTIONS",...Ss()}),t.end();return}if(e.method!=="POST"){t.writeHead(405,{Allow:"POST, OPTIONS",...Ss()}),t.end();return}let n;try{n=await bL(e)}catch(a){ci(t,400,xs(null,db,a instanceof Error?a.message:"invalid body"));return}let o;try{o=JSON.parse(n)}catch{ci(t,400,xs(null,db,"parse error"));return}let i=mb(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)}ci(t,200,a);return}let s=await this.dispatch(o,i);if(s===void 0){t.writeHead(202,Ss()),t.end();return}ci(t,200,s)}authorized(e){if(this.opts.token==="")return!0;return(e.headers.authorization??"")===`Bearer ${this.opts.token}`}async dispatch(e,t){if(!Ic(e))return xs(null,ub,"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:xs(n,ub,"missing method");try{let i=await this.handleMethod(r.method,Ic(r.params)?r.params:{},t);if(o)return;return{jsonrpc:fb,id:n,result:i}}catch(i){if(o)return;let s=i instanceof ws?i.code:hL,a=i instanceof Error?i.message:"internal error";return this.emit("WARN",`rpc error method=${r.method} addr=${t}: ${a}`),xs(n,s,a)}}async handleMethod(e,t,r){switch(e){case"initialize":{let n=vL(t);return this.emit("INFO",`client connected addr=${r}${n?` (${n})`:""}`),{protocolVersion:mL,capabilities:{tools:{},resources:{}},serverInfo:{name:"devctl",version:jt}}}case"notifications/initialized":case"ping":return{};case"tools/list":return{tools:rb(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:mp.map((n)=>({uri:n,name:n.replace("devctl://",""),mimeType:"application/json"}))};case"resources/read":return this.readResource(t);default:throw new ws(fL,`method not found: ${e}`)}}async callTool(e){let t=typeof e.name==="string"?e.name:"";if(t==="")throw new ws(pb,"tool name is required");if(Dc(t)&&!Dn(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=Ic(e.arguments)?e.arguments:{},n=Date.now();try{let o=await ab(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(!cb(t))throw new ws(pb,`unknown resource ${t}`);let r=await lb(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 vL(e){let t=e.clientInfo;if(!Ic(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 mb(e){return e.socket.remoteAddress??"unknown"}class ws extends Error{code;constructor(e,t){super(t);this.code=e}}function Ss(){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 ci(e,t,r){let n=JSON.stringify(r);e.writeHead(t,{"Content-Type":"application/json","Content-Length":Buffer.byteLength(n),...Ss()}),e.end(n)}function xs(e,t,r){return{jsonrpc:fb,id:e,error:{code:t,message:r}}}function yL(e){let t=e.url??"/",r=t.indexOf("?");return r>=0?t.slice(0,r):t}function bL(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 Ic(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}bs();var SL=(e)=>new hp({host:"127.0.0.1",...e});async function kL(e,t={}){let r=t.clock??nc,n=t.fs??py,o=t.processes??new Du,i=t.bus??new hd(2048),s=t.tokens??new co(e.auth.refresh_threshold_seconds*1000,En(),i,void 0,r),a=new Fu(o,r),c=ad(),l=new hr(e.secrets.extra_markers,e.secrets.extra_patterns),d=Bun.isStandaloneExecutable===!0,{logs:u,usingWorker:m}=await Ky({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(!m&&!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 up(e,{healthCheckers:t.healthCheckers??Gv([]),detectGoogle:t.detectGoogle??so,tokens:s,inspectProcess:dy,processAlive:Zt,acquireLock:Mf,socketExists:xL,unlinkSocket:wL,procs:o,orchestrator:a,clock:r,fs:n,bus:i,logs:u,detector:l,sessionID:c,createMcpListener:t.createMcpListener??SL,isKnownTool:Dc,createCommands:(f)=>bv(f,ac(Xo({tokens:s})),a)}),orchestrator:a,clock:r,fs:n}}async function hb(e,t){let r=no(e,t),{supervisor:n}=await kL(r),o=!1,i=()=>{if(o)return;o=!0,n.shutdown(Oa(r.shutdown)).finally(()=>process.exit(0))};process.on("SIGINT",i),process.on("SIGTERM",i),await n.run()}dt();te();qm();import{Command as t$}from"commander";var rC=["zsh","bash","fish"];function nC(e){if(e==="zsh")return`#compdef devctl
|
|
2402
|
+
`}var YD=4000;class _m{cache;accounts=new Map;entries=[];deps;constructor(e){this.deps=e,this.cache=Kx(e.cfg()),this.deps.bus.subscribe((t)=>{let r=t.payload??{},n=t.type===ps?`token refreshed identity=${String(r.identity??"")}`:t.type===go?`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===go?"WARN":"INFO",message:n,pid:0}),t.type===ps||t.type===go)this.syncCredentialEntries()},[ps,go,Su])}get identityCache(){return this.cache}get serviceAccountStatus(){return this.accounts}get credentialEntries(){return this.entries}async prepareServiceIdentity(e,t){let r=Kc(t.identity);if(r.kind!=="none")try{if(r=await tb(t.identity,()=>sb(this.deps.cfg().google.project_id),this.deps.identityProviders()),r.kind==="service_account")await this.deps.tokens.get(Kn(r),"",[]),this.accounts.set(r.serviceAccount,"available")}catch(n){if(r.kind==="service_account")this.accounts.set(r.serviceAccount,"unavailable");if(XD(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 s of rn(t))await this.probeServiceAccount(s);let{service_accounts:n,service_account_status:o}=Rm(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((s)=>s.auth.type.toLowerCase()==="iap")},await this.syncCredentialEntries(),this.deps.bus.publish(ke(Su,"",{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: ${I(t)}`)}}async probeServiceAccount(e){let t;try{await JD(this.deps.tokens.refresh(`sa:${e}`,"",[]),YD),t="available"}catch{t="unavailable"}return this.accounts.set(e,t),t}}function JD(e,t){return new Promise((r,n)=>{let o=setTimeout(()=>n(Error("identity probe timed out")),t);e.then((s)=>{clearTimeout(o),r(s)},(s)=>{clearTimeout(o),n(s)})})}function XD(e){return e.capabilities.some((t)=>["google_api","iap","service_identity"].includes(t))}ne();ne();Pm();var rO=600,nO=65535;async function ew(e,t){let r=Number.isInteger(t)&&(t??0)>0?t:Go(e);for(let n=0;n<rO;n+=1){let o=r+n;if(o>nO)break;if(await ws(o))return o}throw R(ee,`no free MCP port starting at ${r}`)}class Em{listener;disabled=[];token;deps;constructor(e){this.deps=e,this.token=Sg(e.repoRoot())}get instance(){return this.listener}get disabledTools(){return this.disabled}async bootFromPreferences(){let e=ds(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",I(t)))}async start(e){if(this.listener?.isRunning())return;let t=await ew(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()}}lt();import*as qo from"http2";ne();Wr();var Am="14",oO="16",sO="1",tw=new Set(["host","connection","keep-alive","transfer-encoding","upgrade","proxy-connection","authorization"]);class Im{route;tokens;logs;bus;detector;spans;server;client;running=!1;addr="";requests=new kl;constructor(e,t,r,n,o,s){this.route=e;this.tokens=t;this.logs=r;this.bus=n;this.detector=o;this.spans=s}address(){return this.addr}isRunning(){return this.running}stats(){return this.requests.stats()}start(){let e=this.route.listen?.host||"127.0.0.1",t=this.route.listen?.port??0;if(!sr(e))return Promise.reject(R(nr,`refusing to bind grpc route ${this.route.name} to ${e}`));if(t===0)return Promise.reject(R(nr,`grpc route ${this.route.name} requires listen.port`));return new Promise((r,n)=>{let o=qo.createServer();o.on("stream",(s,i)=>void this.serve(s,i)),o.on("sessionError",()=>{}),o.on("error",(s)=>{if(this.running){this.log("ERROR",`grpc route ${this.route.name} listener error: ${s.message}`);return}n(xe(nr,`grpc route ${this.route.name} unable to listen on ${e}:${t}`,s))}),o.listen(t,e,()=>{this.server=o,this.running=!0,this.addr=`${e}:${t}`,this.log("INFO",`grpc route ${this.route.name} listening on ${this.addr} \u2192 ${this.route.upstream.url}`),r()})})}stop(){let e=this.server;if(this.client?.close(),this.client=void 0,!e)return Promise.resolve();return new Promise((t)=>{e.close(()=>{this.running=!1,t()})})}upstream(){if(this.client&&!this.client.closed&&!this.client.destroyed)return this.client;let e=qo.connect(this.route.upstream.url);return e.on("error",()=>{if(this.client===e)this.client=void 0}),e.on("goaway",()=>{if(this.client===e)this.client=void 0}),e.on("close",()=>{if(this.client===e)this.client=void 0}),this.client=e,e}async serve(e,t){let r=Date.now(),n=na({traceparent:rw(t,Bo),requestId:rw(t,zn)}),o=n.requestId,s=String(t[":path"]??"/"),i=Cn(this.route.auth),a=Kn(i),c=!1,l=(h,g,x)=>{if(c)return;c=!0;let y=Date.now()-r,C=this.detector?this.detector.redactText(s):s,_=x??(g!=="0"?`grpc-status ${g}`:void 0),E={timestamp:new Date().toISOString(),requestId:o,method:"POST",path:C,route:this.route.name,identity:a,status:h,durationMs:y,error:_,traceId:n.traceId,spanId:n.spanId,parentSpanId:n.parentSpanId};this.requests.record(E),this.spans?.append(Sl(E)),this.log(_?"WARN":"INFO",`grpc ${s} route=${this.route.name} identity=${a} grpc-status=${g} duration=${y}ms${_?` error=${_}`:""}`,o,a),this.bus?.publish(ke(hi,this.route.name,{status:h,request_id:o,duration:y,identity:a}))},d=(h,g)=>{try{e.respond({":status":200,"content-type":"application/grpc","grpc-status":h,"grpc-message":g},{endStream:!0})}catch{e.close(qo.constants.NGHTTP2_INTERNAL_ERROR)}l(200,h,g)},u;try{u=this.buildUpstreamHeaders(t),oa(u,n,zn),await $s(this.route,u,this.tokens)}catch(h){d(oO,h instanceof Error?h.message:"token injection failed");return}let m;try{m=this.upstream().request(u)}catch(h){d(Am,h instanceof Error?h.message:"upstream unavailable");return}let p=!1,f={};m.on("response",(h)=>{if(p)return;if(p=!0,h["grpc-status"]!==void 0){e.respond(this.frontResponseHeaders(h),{endStream:!0}),l(Number(h[":status"]??200),String(h["grpc-status"]));return}e.respond(this.frontResponseHeaders(h),{waitForTrailers:!0}),e.on("wantTrailers",()=>{try{e.sendTrailers(this.sanitizeTrailers(f))}catch{}l(Number(h[":status"]??200),String(f["grpc-status"]??"0"))}),m.pipe(e,{end:!1}),m.on("end",()=>{try{e.end()}catch{}})}),m.on("trailers",(h)=>{f=h}),m.on("error",(h)=>{if(!p)d(Am,h.message);else l(502,Am,h.message),e.close(qo.constants.NGHTTP2_INTERNAL_ERROR)}),e.on("error",()=>m.destroy()),e.on("close",()=>{if(!c)l(499,sO,"client closed before completion"),m.destroy()}),e.pipe(m)}buildUpstreamHeaders(e){let t={};for(let[n,o]of Object.entries(e)){if(n.startsWith(":")||tw.has(n.toLowerCase())||o===void 0)continue;t[n]=Array.isArray(o)?o.join(", "):String(o)}let r=new URL(this.route.upstream.url);return t[":method"]=String(e[":method"]??"POST"),t[":path"]=String(e[":path"]??"/"),t[":scheme"]=r.protocol==="https:"?"https":"http",t[":authority"]=r.host,t}frontResponseHeaders(e){let t={":status":e[":status"]??200};for(let[r,n]of Object.entries(e)){if(r.startsWith(":")||tw.has(r.toLowerCase())||n===void 0)continue;t[r]=n}return t}sanitizeTrailers(e){let t={};for(let[r,n]of Object.entries(e)){if(r.startsWith(":")||n===void 0)continue;t[r]=n}if(t["grpc-status"]===void 0)t["grpc-status"]="0";return t}log(e,t,r="",n=""){this.logs?.append({timestamp:new Date().toISOString(),service:"proxy",source:"proxy",level:e,message:t,pid:0,request_id:r,identity:n})}}function rw(e,t){let r=e[t];if(typeof r==="string"&&r!=="")return r;if(Array.isArray(r)&&typeof r[0]==="string"&&r[0]!=="")return r[0];return}class Lm{server;tokenEP;grpc=[];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 um(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"],this.deps.spans),await this.server.start();let e=this.deps.cfg();if(e.proxy.token_endpoint.enabled)this.tokenEP=new pm(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`;for(let t of e.proxy.routes.filter(_o)){let r=new Im(t,this.deps.tokens,this.deps.logs,this.deps.bus,this.deps.detector,this.deps.spans);await r.start(),this.grpc.push(r)}this.deps.persistState()}async stop(){await this.server?.stop(),await this.tokenEP?.stop(),await Promise.all(this.grpc.map((e)=>e.stop())),this.grpc=[],this.deps.persistState()}}Nt();var iO=3000,nw=5000,ow=60;class Dm{timer;cpu=[];mem=[];lastAt=0;deps;constructor(e){this.deps=e}start(){if(this.timer)return;this.timer=setInterval(()=>void this.poll(),iO)}stop(){if(this.timer)clearInterval(this.timer),this.timer=void 0}series(){return{interval_ms:nw,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===Ke||r.state===Er||r.state===nn))e.push(r.pid);if(e.length===0)return;let t=await Tx(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<nw)return;this.lastAt=e;let t=Cm(),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>ow)this.cpu.shift();if(this.mem.length>ow)this.mem.shift()}}function Tl(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function Pl(e){return{services:Us(e.services),level:typeof e.level==="string"?e.level:"",search:typeof e.search==="string"?e.search:"",regex:e.regex===!0,source:typeof e.source==="string"?e.source:"",since:typeof e.since==="string"?e.since:"",until:typeof e.until==="string"?e.until:"",traceId:_l(e.traceId)??_l(e.trace_id),requestId:_l(e.requestId)??_l(e.request_id),attribute:aO(e.attribute)}}function _l(e){return typeof e==="string"&&e!==""?e:void 0}function aO(e){if(!Tl(e)||typeof e.key!=="string"||typeof e.value!=="string")return;return{key:e.key,value:e.value}}function Us(e){if(!Array.isArray(e))return[];return e.filter((t)=>typeof t==="string")}function sa(e){if(!Tl(e))return;let t={};for(let[r,n]of Object.entries(e))if(typeof n==="string")t[r]=n;return t}ne();import{createServer as cO}from"net";var lO=2000;class Om{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=cO((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)}
|
|
2403
|
+
`);if(r.shift(),!c){n=!0,e.once("drain",()=>{n=!1,o()});return}}},s=(a)=>{if(a.id===void 0&&a.event!==void 0&&r.length>=lO){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()},i=this.deps.subscribe((a)=>s({event:a}));e.on("error",(a)=>{this.deps.log("devctl","WARN",`client connection error: ${I(a)}`)}),e.on("data",(a)=>{t+=a.toString("utf8");let c=t.split(`
|
|
2404
|
+
`);t=c.pop()??"";for(let l of c){if(l.trim()==="")continue;this.dispatchLine(l,s)}}),e.on("close",()=>i())}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=Ly(n);t({id:r.id,error:o.error,kind:o.kind,hint:o.hint,service:o.service})}}}ne();Wr();Nt();ju();var sw=1e4;class Mm{items=[];start=0;nextSeq=1;max;detector;byTrace=new Map;requestToTrace=new Map;constructor(e=sw,t){this.max=e>0?e:sw,this.detector=t}append(e){let t={...e,seq:this.nextSeq};this.nextSeq+=1;let r=this.detector?fc(this.detector,t):t;if(this.items.length<this.max)this.items.push(r);else{let n=this.items[this.start];if(n)this.dropFromIndex(n);this.items[this.start]=r,this.start=(this.start+1)%this.max}return this.index(r),r}getTrace(e){let t=[...this.byTrace.get(e)??[]].sort((o,s)=>o.startUnixNano-s.startUnixNano),r=new Set(t.map((o)=>o.spanId)),n=t.filter((o)=>!o.parentSpanId||!r.has(o.parentSpanId));return{traceId:e,spans:t,roots:n}}findTraceIdByRequestId(e){return this.requestToTrace.get(e)}recent(e=100){let t=[];return this.forEach((r)=>{t.push(r)}),t.slice(Math.max(0,t.length-e)).reverse()}close(){this.items.length=0,this.byTrace.clear(),this.requestToTrace.clear()}index(e){let t=this.byTrace.get(e.traceId)??[];t.push(e),this.byTrace.set(e.traceId,t);let r=e.attributes[Wt];if(typeof r==="string"&&r!=="")this.requestToTrace.set(r,e.traceId)}dropFromIndex(e){let t=this.byTrace.get(e.traceId);if(!t)return;let r=t.filter((n)=>n.seq!==e.seq);if(r.length===0){this.byTrace.delete(e.traceId);let n=e.attributes[Wt];if(typeof n==="string"&&this.requestToTrace.get(n)===e.traceId)this.requestToTrace.delete(n)}else this.byTrace.set(e.traceId,r)}forEach(e){let t=this.items.length;for(let r=0;r<t;r+=1){let n=this.items[(this.start+r)%t];if(n)e(n)}}}lt();import{createServer as gO}from"http";lc();gs();function iw(e){if(typeof e==="string"){let t=e.replace(/^span_kind_/i,"").toLowerCase();if(t==="server")return"server";if(t==="client")return"client";if(t==="producer")return"producer";if(t==="consumer")return"consumer";return"internal"}if(e===2)return"server";if(e===3)return"client";if(e===4)return"producer";if(e===5)return"consumer";if(e===1)return"internal";return"internal"}function aw(e){if(typeof e!=="object"||e===null)return{code:"unset"};let t=e,r=typeof t.message==="string"&&t.message!==""?t.message:void 0;if(t.code===2||t.code==="STATUS_CODE_ERROR"||t.code==="error")return{code:"error",message:r};if(t.code===1||t.code==="STATUS_CODE_OK"||t.code==="ok")return{code:"ok",message:r};return{code:"unset",message:r}}function cw(e,t){if(!De(e)||!Array.isArray(e.resourceLogs))return[];return e.resourceLogs.flatMap((r)=>dO(r,t))}function lw(e,t){if(!De(e)||!Array.isArray(e.resourceSpans))return[];return e.resourceSpans.flatMap((r)=>pO(r,t))}function dO(e,t){if(!De(e)||!Array.isArray(e.scopeLogs))return[];let r=Fu(e.resource,t),n=r["service.name"];return e.scopeLogs.flatMap((o)=>uO(o,r,n))}function uO(e,t,r){if(!De(e)||!Array.isArray(e.logRecords))return[];let n=Hu(e.scope),o=typeof t?.["process.pid"]==="number"?t["process.pid"]:0;return e.logRecords.flatMap((s)=>{if(!De(s)||t===void 0)return[];let i=Ug(s.severityNumber);return[{service:r,source:"otlp",pid:o,body:s.body===void 0?"":ys(s.body),attributes:Nn(s.attributes),severityNumber:i,severityText:typeof s.severityText==="string"&&s.severityText!==""?s.severityText:hs(i),traceId:Nm(s.traceId),spanId:El(s.spanId),traceFlags:typeof s.flags==="number"?s.flags:void 0,timeUnixNano:Zr(s.timeUnixNano),observedTimeUnixNano:Zr(s.observedTimeUnixNano),resource:t,scope:n}]})}function pO(e,t){if(!De(e)||!Array.isArray(e.scopeSpans))return[];let r=Fu(e.resource,t);return e.scopeSpans.flatMap((n)=>mO(n,r))}function mO(e,t){if(!De(e)||!Array.isArray(e.spans))return[];let r=Hu(e.scope);return e.spans.flatMap((n)=>{if(!De(n))return[];let o=Nm(n.traceId),s=El(n.spanId);if(!o||!s)return[];return[{traceId:o,spanId:s,parentSpanId:El(n.parentSpanId),name:typeof n.name==="string"&&n.name!==""?n.name:"span",kind:iw(n.kind),startUnixNano:Zr(n.startTimeUnixNano)??0,endUnixNano:Zr(n.endTimeUnixNano)??0,status:aw(n.status),attributes:Nn(n.attributes),events:fO(n.events),links:hO(n.links),resource:t,scope:r}]})}function fO(e){if(!Array.isArray(e))return[];return e.flatMap((t)=>{if(!De(t)||typeof t.name!=="string")return[];return[{timeUnixNano:Zr(t.timeUnixNano)??0,name:t.name,attributes:Nn(t.attributes)}]})}function hO(e){if(!Array.isArray(e))return[];return e.flatMap((t)=>{if(!De(t))return[];let r=Nm(t.traceId),n=El(t.spanId);if(!r||!n)return[];return[{traceId:r,spanId:n}]})}function Nm(e){if(typeof e!=="string")return;return rr(e)?e.toLowerCase():void 0}function El(e){if(typeof e!=="string")return;return vn(e)?e.toLowerCase():void 0}ne();lt();var yO="application/json",vO=4194304;class $m{server;running=!1;addr="";opts;constructor(e){this.opts=e}isRunning(){return this.running}address(){return this.addr}listenPort(){let e=this.server?.address();if(e&&typeof e==="object")return e.port;return this.opts.port}endpoint(){return`http://${this.opts.host||jr}:${this.listenPort()}`}start(){let e=this.opts.host||jr;if(!sr(e))return Promise.reject(R(Qe,"OTLP receiver must bind to a loopback address"));return new Promise((t,r)=>{this.server=gO((n,o)=>{this.handle(n,o)}),this.server.on("error",(n)=>r(xe(Qe,`unable to listen on ${e}:${this.opts.port}`,n))),this.server.listen(this.opts.port,e,()=>{let n=this.server?.address(),o=n&&typeof n==="object"?n.port:this.opts.port;this.addr=`${e}:${o}`,this.running=!0,t()})})}stop(){let e=this.server;if(this.running=!1,this.server=void 0,!e)return Promise.resolve();return new Promise((t)=>{e.close(()=>t())})}async handle(e,t){let n=(e.url??"/").split("?")[0]??"/";if(e.method!=="POST"){ia(t,405,{error:"method not allowed"});return}let o;try{o=JSON.parse(await bO(e))}catch{ia(t,400,{error:"invalid json"});return}let s=this.opts.fallbackService??"otlp";if(n==="/v1/logs"){for(let i of cw(o,s))this.opts.logs.append(i);ia(t,200,{partialSuccess:{}});return}if(n==="/v1/traces"){for(let i of lw(o,s))this.opts.spans.append(i);ia(t,200,{partialSuccess:{}});return}ia(t,404,{error:"not found"})}}function ia(e,t,r){let n=JSON.stringify(r);e.writeHead(t,{"content-type":yO,"content-length":Buffer.byteLength(n)}),e.end(n)}function bO(e){return new Promise((t,r)=>{let n=[],o=0;e.on("data",(s)=>{if(o+=s.length,o>vO){e.destroy(),r(Error("payload too large"));return}n.push(s)}),e.on("end",()=>t(Buffer.concat(n).toString("utf8"))),e.on("error",r)})}class Fm{server;deps;constructor(e){this.deps=e}endpoint(){if(!this.server?.isRunning())return"";return this.server.endpoint()}async start(){let e=this.deps.cfg().telemetry.otlp;if(!e.enabled)return;if(this.server?.isRunning())return;this.server=new $m({host:e.listen.host||jr,port:e.listen.port,logs:this.deps.logs,spans:this.deps.spans}),await this.server.start(),this.deps.log("devctl","INFO",`OTLP/HTTP+JSON listening on ${this.server.address()}`)}async stop(){await this.server?.stop(),this.server=void 0}}class Hm{cfg;sessionID;internalTok;bus;logs;spans;procs;tokens;detector;env;proxy;telemetry;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=vu(),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.spans=new Mm(void 0,this.detector),this.procs=t.procs,this.orchestrator=t.orchestrator,this.tokens=t.tokens,this.telemetry=new Fm({cfg:()=>this.cfg,logs:this.logs,spans:this.spans,log:(r,n,o)=>this.log(r,n,o)}),this.proxy=new Lm({cfg:()=>this.cfg,ports:()=>this.ports,tokens:this.tokens,logs:this.logs,spans:this.spans,bus:this.bus,detector:this.detector,internalTok:()=>this.internalTok,middleware:()=>this.registry?.proxyMiddleware??[],persistState:()=>this.persistState()}),this.env=new Sm({cfg:()=>this.cfg,ports:()=>this.ports,userEmail:()=>this.identity.identityCache.user,proxy:()=>this.proxy.instance,tokenEndpoint:()=>this.proxy.tokenEndpoint,boundTokenURL:()=>this.proxy.boundTokenURL,internalTok:()=>this.internalTok,tokens:this.tokens,environmentSources:()=>this.registry?.environmentSources,otlpEndpoint:()=>this.telemetry.endpoint()}),this.mcp=new Em({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 Dm({clock:this.clock,runtimes:()=>this.runtimes}),this.identity=new _m({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 Om({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,qi(r));this.orchestrator.bind(this.lifecycleSession()),this.commands=t.createCommands(this),this.serviceWatchers=new bm({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,s)=>e.setState(t,r,n,o,s),log:(t,r,n)=>e.log(t,r,n)}}async run(){let e=Ka(this.cfg.repoRoot);this.lock=this.acquireLockFn(this.cfg.repoRoot,e),this.rpc.removeStaleSocket(e),this.registry=await Cl(this.cfg.plugins.map((t)=>t.path),this.cfg.repoRoot),this.pluginMtimes=fm(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}`);mm(this.reloadHost()),hm(this.registry,this.cfg),gm(this.registry,this.cfg),ym(this.registry,this.cfg),await this.recoverSession(),this.serviceWatchers.sync(this.cfg.services),vm(this.reloadHost()),this.persistState(),this.log("devctl","INFO",`supervisor started session=${this.sessionID}`),this.refreshIdentity(),this.resources.start(),await this.telemetry.start(),await this.mcp.bootFromPreferences(),this.rpc.removeStaleSocket(e),await this.rpc.listen(e)}async dispatch(e,t){let r=Tl(t)?t:{};switch(e){case"ping":return{session:this.sessionID,version:Vt,protocol:Bi};case"start":return this.commands.startService.execute({services:Us(r.services),profile:typeof r.profile==="string"&&r.profile!==""?r.profile:void 0,detach:r.detach===!0,client_env:sa(r.client_env)});case"stop":return await this.commands.stopService.execute(Us(r.services)),null;case"restart":return await this.commands.restartService.execute(Us(r.services),{cascade:r.cascade===!0,clientEnv:sa(r.client_env)}),null;case"run_task":return this.runTask(typeof r.name==="string"?r.name:"",sa(r.client_env)??{});case"exec":return this.execService(typeof r.service==="string"?r.service:"",Us(r.command),sa(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({...Pl(r),export:typeof r.export==="string"?r.export:""});case"logs_page":return await this.queryLogsPage({...Pl(r),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(Pl(r));case"get_trace":return await this.queryTrace(typeof r.trace_id==="string"?r.trace_id:typeof r.traceId==="string"?r.traceId:"");case"trace_request":return await this.queryTraceByRequest(typeof r.request_id==="string"?r.request_id:typeof r.requestId==="string"?r.requestId:"");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??ds(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((s)=>typeof s==="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:Ic(this.cfg.shutdown);return setTimeout(()=>{this.shutdown(n)},50),null;default:throw R(Qe,`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 s=await r.check(n,o);return{...s,status:s.status}}}:e.healthCheckers.lookup(t)}},logs:{append:(t)=>e.logs.append(t)},bus:e.bus,processMeta:e.processMeta,get containerPrefix(){return`devctl-${kr(e.cfg.repoRoot)}-`},prepareServiceIdentity:(t,r)=>e.identity.prepareServiceIdentity(t,r),resolveServiceExecution:(t,r,n,o,s,i)=>e.env.resolveServiceExecution(t,r,n,o,s,i),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,s)=>e.setState(t,r,n,o,s),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 My(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(([s,i])=>`${s}=${i}`).join(", ");this.log(r,"INFO",`assigned ports: ${o||"none"}`)}}catch(t){if(t instanceof Ze&&t.service!=="")await this.fail(t.service,t);else this.log("devctl","ERROR",I(t));throw t}}async runTask(e,t){let r=this.cfg.tasks[e];if(!r)throw R(Qe,`unknown task ${e}`);if(r.dependencies.length>0)await this.start({services:r.dependencies,client_env:t});let n={...Hn(),command:r.command,shell:r.shell,working_dir:r.working_dir,dependencies:r.dependencies,environment:r.environment},{env:o,workDir:s}=await this.env.resolveTaskEnvironment(e,n,t),i=await this.runTransient(`task:${e}`,r.command,r.shell,s,o);return{task:e,...i}}async execService(e,t,r,n=!1){let o=this.cfg.services[e];if(!o)throw R(xs,`unknown service ${e}`);let s=this.env.serviceProfile.get(e)??this.env.profile,i=s!==""?Uo(this.cfg,s):this.env.profileEnv,{env:a,workDir:c}=await this.env.resolveServiceExecution(e,o,s,i,r,!o.container);if(n)return{service:e,code:0,stdout:"",stderr:"",environment:a};if(t.length===0)throw R(Qe,"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(Un(t))return{code:0,stdout:"",stderr:""};this.log(e,"INFO",`running ${t.args.join(" ")}`);let s=await this.procs.runOnce({name:e,args:[...t.args],shell:r||t.shell,workDir:n,env:o,graceMs:Bn(this.cfg.shutdown)*1000,onLine:(i,a)=>this.logs.append({timestamp:this.clock.isoNow(),service:e,source:i,stream:i,level:"",message:a,pid:0})});if(s.code!==0)throw R(qe,`${e} exited with code ${s.code}`);return s}serviceWorkDir(e){return this.env.serviceWorkDir(e)}async claimIfAlreadyUp(e){return Dx(this.recoverHost(),e)}async recoverSession(){return Ox(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?Ho(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)=>Mi(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 rn(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(),getTrace:(e)=>this.queryTrace(e),traceRequest:(e)=>this.queryTraceByRequest(e)}}async reload(){return jx(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.telemetry.stop(),this.spans.close(),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 Vx(this.snapshotHost())}async refreshIdentity(e){return this.identity.refreshIdentity(e)}async queryLogs(e){let t=this.logFilter(e),r=await this.logs.query(t);if(e.export)await this.logs.exportTo(e.export,t);return{events:r}}async queryLogsPage(e){return this.logs.queryPage(this.logFilter(e),{cursor:e.cursor,direction:e.direction,limit:e.limit})}async queryLogsFacets(e){return this.logs.queryFacets(this.logFilter(e))}async queryTrace(e){let t=this.spans.getTrace(e),r=e===""?[]:await this.logs.query({traceId:e});return{traceId:e,tree:t,events:r}}async queryTraceByRequest(e){let r=this.spans.findTraceIdByRequestId(e)??(rr(e)?e.toLowerCase():"");return{...await this.queryTrace(r),requestId:e}}logFilter(e){return{services:e.services,level:e.level,search:e.search,regex:e.regex,source:e.source,since:e.since,until:e.until,traceId:e.traceId,requestId:e.requestId,attribute:e.attribute}}subscribe(e){return this.bus.subscribe(e)}formatStatus(){return zx(this.snapshot())}setState(e,t,r,n,o){let s=this.runtimes.get(e)??qi(e);if(!ta(s.state,t))this.log(e,"WARN",`unusual lifecycle ${s.state} \u2192 ${t}`);s.state=t,s.health=r,s.pid=n,s.last_error=o;let i=t===Ke||t===Er||t===nn;if(s.startTime=i?this.processMeta.get(e)?.startTime.toISOString():void 0,!i)s.cpuPercent=void 0,s.memoryKB=void 0;this.runtimes.set(e,s),this.bus.publish(ke(Pg,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 s=await Ro(o);if(!s||s.pid===process.pid)continue;if(r){let i=await this.inspectProcessFn(s.pid);if(!(i!==void 0&&i.command!==""&&Zi({args:n?.command??[...r.command.args],workDir:n?.cwd??this.serviceWorkDir(r),startTime:n?.startTime},i))){this.log(e,"WARN",`port ${o} is held by pid ${s.pid}, which does not match ${e}; leaving it running`);continue}}try{await Cc(s),this.log(e,"INFO",`released port ${o} (pid ${s.pid})`)}catch(i){this.log(e,"WARN",I(i))}}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,bt,Mt,0,I(t));try{await this.procs.stop(e,Bn(this.cfg.shutdown)*1000)}catch(r){this.log(e,"WARN",I(r))}this.bus.publish(ke(tc,e,{error:I(t)})),this.log(e,"ERROR",I(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})}xg(this.cfg.repoRoot,{session_id:this.sessionID,repo_root:this.cfg.repoRoot,profile:this.profile,processes:e})}}var uw=500,xO=1e4,wO=2000,SO=new URL("./log-worker.ts",import.meta.url);class pw{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??SO,{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=uw){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(!kO(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(!CO(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"},wO)}catch{}finally{this.markDead(Error("log worker closed"))}}rpc(e,t=xO){this.assertAlive();let r=this.nextId;this.nextId+=1;let n={...e,id:r};return new Promise((o,s)=>{let i=setTimeout(()=>{this.markDead(Error(`log worker timed out (${e.type})`))},t);this.pending.set(r,{resolve:o,reject:s,timer:i});try{this.post(n)}catch(a){clearTimeout(i),this.pending.delete(r),s(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(ke(us,e.event.service,{event:e.event,level:e.event.severityText}));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 kO(e){if(e===null||Array.isArray(e)||!("events"in e))return!1;return Array.isArray(e.events)}function CO(e){return e!==null&&!Array.isArray(e)&&"byService"in e}function dw(e,t,r){return ky(new rp(e.max,t,r,e.persist,e.directory,e.sessionID,e.retentionDays,e.maxSessionLogs))}async function mw(e,t,r,n={}){if(n.standalone??Bun.isStandaloneExecutable===!0)return{logs:dw(e,t,r),usingWorker:!1};try{let s=new pw(e,t,{script:n.script});return await s.waitUntilReady(n.initTimeoutMs??uw),{logs:s,usingWorker:!0}}catch{return{logs:dw(e,t,r),usingWorker:!1}}}bo();import{createServer as JO}from"http";ne();aa();var XO="2025-03-26",Mw="2.0",Iw=-32700,Lw=-32600,QO=-32601,Dw=-32602,ZO=-32603;function eM(e){return e!==""&&sr(e)}class Gm{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(!eM(this.opts.host))return Promise.reject(R(Qe,`refusing to bind MCP to ${this.opts.host}`));return new Promise((e,t)=>{this.server=JO((r,n)=>{this.serve(r,n)}),this.server.on("error",(r)=>t(xe(Qe,`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,da()),t.end();return}if(!this.authorized(e)){this.emit("WARN",`rejected unauthorized request from ${Ow(e)}`),Bs(t,401,{error:"unauthorized"});return}if(rM(e)!=="/mcp"){Bs(t,404,{error:"not found"});return}if(e.method==="GET"||e.method==="DELETE"){t.writeHead(405,{Allow:"POST, OPTIONS",...da()}),t.end();return}if(e.method!=="POST"){t.writeHead(405,{Allow:"POST, OPTIONS",...da()}),t.end();return}let n;try{n=await nM(e)}catch(a){Bs(t,400,ca(null,Iw,a instanceof Error?a.message:"invalid body"));return}let o;try{o=JSON.parse(n)}catch{Bs(t,400,ca(null,Iw,"parse error"));return}let s=Ow(e);if(Array.isArray(o)){let a=[];for(let c of o){let l=await this.dispatch(c,s);if(l!==void 0)a.push(l)}Bs(t,200,a);return}let i=await this.dispatch(o,s);if(i===void 0){t.writeHead(202,da()),t.end();return}Bs(t,200,i)}authorized(e){if(this.opts.token==="")return!0;return(e.headers.authorization??"")===`Bearer ${this.opts.token}`}async dispatch(e,t){if(!Dl(e))return ca(null,Lw,"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:ca(n,Lw,"missing method");try{let s=await this.handleMethod(r.method,Dl(r.params)?r.params:{},t);if(o)return;return{jsonrpc:Mw,id:n,result:s}}catch(s){if(o)return;let i=s instanceof la?s.code:ZO,a=s instanceof Error?s.message:"internal error";return this.emit("WARN",`rpc error method=${r.method} addr=${t}: ${a}`),ca(n,i,a)}}async handleMethod(e,t,r){switch(e){case"initialize":{let n=tM(t);return this.emit("INFO",`client connected addr=${r}${n?` (${n})`:""}`),{protocolVersion:XO,capabilities:{tools:{},resources:{}},serverInfo:{name:"devctl",version:Vt}}}case"notifications/initialized":case"ping":return{};case"tools/list":return{tools:Sw(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:Bm.map((n)=>({uri:n,name:n.replace("devctl://",""),mimeType:"application/json"}))};case"resources/read":return this.readResource(t);default:throw new la(QO,`method not found: ${e}`)}}async callTool(e){let t=typeof e.name==="string"?e.name:"";if(t==="")throw new la(Dw,"tool name is required");if(Ll(t)&&!Xn(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=Dl(e.arguments)?e.arguments:{},n=Date.now();try{let o=await Pw(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 s=o instanceof Error?o.message:"tool failed";return this.emit("WARN",`tool call failed name=${t} duration=${Date.now()-n}ms: ${s}`),{content:[{type:"text",text:s}],isError:!0}}}async readResource(e){let t=typeof e.uri==="string"?e.uri:"";if(!Ew(t))throw new la(Dw,`unknown resource ${t}`);let r=await Aw(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 tM(e){let t=e.clientInfo;if(!Dl(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 Ow(e){return e.socket.remoteAddress??"unknown"}class la extends Error{code;constructor(e,t){super(t);this.code=e}}function da(){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 Bs(e,t,r){let n=JSON.stringify(r);e.writeHead(t,{"Content-Type":"application/json","Content-Length":Buffer.byteLength(n),...da()}),e.end(n)}function ca(e,t,r){return{jsonrpc:Mw,id:e,error:{code:t,message:r}}}function rM(e){let t=e.url??"/",r=t.indexOf("?");return r>=0?t.slice(0,r):t}function nM(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 Dl(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}aa();var iM=(e)=>new Gm({host:"127.0.0.1",...e});async function aM(e,t={}){let r=t.clock??Qc,n=t.fs??Px,o=t.processes??new nm,s=t.bus??new ku(2048),i=t.tokens??new Fo(e.auth.refresh_threshold_seconds*1000,Vn(),s,void 0,r),a=new cm(o,r),c=hu(),l=new Rr(e.secrets.extra_markers,e.secrets.extra_patterns),d=Bun.isStandaloneExecutable===!0,{logs:u,usingWorker:m}=await mw({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},s,l,{standalone:d});if(!m&&!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 Hm(e,{healthCheckers:t.healthCheckers??ix([]),detectGoogle:t.detectGoogle??No,tokens:i,inspectProcess:_x,processAlive:ur,acquireLock:wg,socketExists:oM,unlinkSocket:sM,procs:o,orchestrator:a,clock:r,fs:n,bus:s,logs:u,detector:l,sessionID:c,createMcpListener:t.createMcpListener??iM,isKnownTool:Ll,createCommands:(f)=>Mb(f,rl(Is({tokens:i})),a)}),orchestrator:a,clock:r,fs:n}}async function Nw(e,t){let r=Do(e,t),{supervisor:n}=await aM(r),o=!1,s=()=>{if(o)return;o=!0,n.shutdown(Ic(r.shutdown)).finally(()=>process.exit(0))};process.on("SIGINT",s),process.on("SIGTERM",s),await n.run()}lt();ne();Dh();import{Command as wH}from"commander";var X_=["zsh","bash","fish"];function Q_(e){if(e==="zsh")return`#compdef devctl
|
|
2281
2405
|
_devctl() {
|
|
2282
2406
|
local -a opts
|
|
2283
2407
|
local line="\${words[*]}"
|
|
@@ -2297,91 +2421,92 @@ complete -F _devctl devctl
|
|
|
2297
2421
|
devctl __complete (commandline -cp)
|
|
2298
2422
|
end
|
|
2299
2423
|
complete -c devctl -f -a "(__devctl_complete)"
|
|
2300
|
-
`;throw Error(`unsupported shell "${e}"; use ${
|
|
2301
|
-
`);return}if(T(`User: ${
|
|
2302
|
-
`),T(`Project: ${
|
|
2303
|
-
`),
|
|
2304
|
-
`);T(`ADC: ${
|
|
2305
|
-
`),T(`gcloud: ${
|
|
2306
|
-
`)}),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("",
|
|
2307
|
-
`);return}if(T(`refreshed credentials expire ${
|
|
2308
|
-
`),
|
|
2309
|
-
`)}finally{await o.close()}})}
|
|
2424
|
+
`;throw Error(`unsupported shell "${e}"; use ${X_.join(", ")}`)}function Oh(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),s=o[1]??"",i=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(s===""||o.length<2)return un(c,n);if(s==="start"||s==="stop"||s==="restart"||s==="logs"){if(n==="--profile"||o[o.length-1]==="--profile")return un(a,n==="--profile"?"":n);return un([...i,"--profile","--detach","--json","--since","--until"],n)}if(s==="completion")return un([...X_].sort(),n);if(s==="run")return un([...Object.keys(t.tasks).sort(),"--json"],n);if(s==="exec")return un([...i,"--print-env","--reveal","--json"],n);if(s==="auth")return un(["status","login","logout","refresh"],n);if(s==="config")return un(["validate","show","diff"],n);if(s==="proxy")return un(["status","start","stop"],n);return un(c,n)}function un(e,t){if(t==="")return e;return e.filter((r)=>r.startsWith(t))}function Re(e){return e.optsWithGlobals().config??""}function T(e){try{process.stdout.write(e)}catch(t){if(t.code==="EPIPE")process.exit(0);throw t}}function Z_(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("",Re(e)).google.project_id}catch{o=""}let s=await t.detectGoogle(o);if(n.json){T(JSON.stringify(s,null,2)+`
|
|
2425
|
+
`);return}if(T(`User: ${s.userEmail||"(unknown)"}
|
|
2426
|
+
`),T(`Project: ${s.projectID||"(unset)"}
|
|
2427
|
+
`),s.projectID!=="")T(`Source: ${s.projectSource}
|
|
2428
|
+
`);T(`ADC: ${s.adcAvailable}
|
|
2429
|
+
`),T(`gcloud: ${s.gcloudInstalled}
|
|
2430
|
+
`)}),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("",Re(e),!1);try{let s=await t.refreshUserToken("user"),i=o.client?await o.refreshAuth():void 0;if(n.json){T(JSON.stringify({identity:s.identity,expires_at:s.expiresAt.toISOString(),service_account_status:i?.service_account_status??{}},null,2)+`
|
|
2431
|
+
`);return}if(T(`refreshed credentials expire ${s.expiresAt.toISOString()}
|
|
2432
|
+
`),i)for(let[a,c]of Object.entries(i.service_account_status).sort(([l],[d])=>l.localeCompare(d)))T(`service account ${a}: ${c}
|
|
2433
|
+
`)}finally{await o.close()}})}ne();Nt();sh();import{mkdirSync as pH}from"fs";import{basename as mH,dirname as fH}from"path";import{stringify as hH}from"yaml";Nc();function eT(e,t){e.command("reload").description("reload configuration").action(async()=>{let r=await t.openController("",Re(e),!0);try{let n=await r.reload();if(n.restart_required.length===0)T(`configuration reloaded
|
|
2310
2434
|
`);else T(`configuration reloaded; restart required: ${n.restart_required.join(", ")}
|
|
2311
|
-
`);if(n.supervisor_restart_required&&n.supervisor_restart_required.length>0)T(`note: ${
|
|
2312
|
-
`)}finally{await r.close()}})}function
|
|
2313
|
-
`);return}if(
|
|
2435
|
+
`);if(n.supervisor_restart_required&&n.supervisor_restart_required.length>0)T(`note: ${il(n.supervisor_restart_required)}
|
|
2436
|
+
`)}finally{await r.close()}})}function tT(e,t){let r=e.command("config");r.command("validate").option("--json").action((n)=>{try{let o=t.load("",Re(e)),s=t.validate(o);if(n.json){T(JSON.stringify({valid:s.length===0,issues:s},null,2)+`
|
|
2437
|
+
`);return}if(s.length>0)throw Error(s.join(`
|
|
2314
2438
|
`));T(`configuration is valid
|
|
2315
|
-
`)}catch(o){if(n.json){T(JSON.stringify({valid:!1,error:
|
|
2316
|
-
`);return}throw o}}),r.command("diff").description("show where effective configuration values came from").option("--json").action((n)=>{let o=t.load("",
|
|
2317
|
-
`);return}for(let
|
|
2318
|
-
`),T(` winner: ${
|
|
2319
|
-
`);for(let a of
|
|
2320
|
-
`)}}),r.command("show").option("--json").action((n)=>{let o=t.load("",
|
|
2321
|
-
`);return}T(
|
|
2439
|
+
`)}catch(o){if(n.json){T(JSON.stringify({valid:!1,error:I(o)},null,2)+`
|
|
2440
|
+
`);return}throw o}}),r.command("diff").description("show where effective configuration values came from").option("--json").action((n)=>{let o=t.load("",Re(e)),s=t.configDiff(o);if(n.json){T(JSON.stringify({entries:s},null,2)+`
|
|
2441
|
+
`);return}for(let i of s){T(`${i.path} = ${JSON.stringify(i.value)}
|
|
2442
|
+
`),T(` winner: ${i.layer} (${i.source})
|
|
2443
|
+
`);for(let a of i.shadowed)T(` shadowed: ${a.layer} (${a.source})
|
|
2444
|
+
`)}}),r.command("show").option("--json").action((n)=>{let o=t.load("",Re(e));if(n.json){T(JSON.stringify(o,null,2)+`
|
|
2445
|
+
`);return}T(hH(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 s=t.readTextFile(n),{repo:i,cfgPath:a}=hp("",Re(e)),c=Rd(s,mH(i)),l=t.validateConfigText(i,a,c.yaml);if(T(_d(c)),l.length>0){if(T(`validation:
|
|
2322
2446
|
${l.map((d)=>` ${d}`).join(`
|
|
2323
2447
|
`)}
|
|
2324
2448
|
`),o.write===!0)throw Error("refusing to write an invalid mapping");return}if(o.write!==!0){T(`dry-run; pass --write to save under .devctl/config.yaml
|
|
2325
|
-
`);return}if(t.fileExists(a))throw Error(`configuration already exists at ${a}; not overwriting`);
|
|
2326
|
-
`)})}
|
|
2449
|
+
`);return}if(t.fileExists(a))throw Error(`configuration already exists at ${a}; not overwriting`);pH(fH(a),{recursive:!0}),t.writeTextFile(a,c.yaml),T(`wrote ${a}
|
|
2450
|
+
`)})}bo();Nt();import{setTimeout as gH}from"timers/promises";function nT(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 s=await t.openController("",Re(e),!0);try{let i=await s.execService(r,n,o.printEnv===!0);if(i.environment){let a=o.reveal?i.environment:new Rr(s.cfg.secrets.extra_markers,s.cfg.secrets.extra_patterns).redactMap(i.environment);if(o.json)T(JSON.stringify({...i,environment:a},null,2)+`
|
|
2327
2451
|
`);else for(let c of Object.keys(a).sort())T(`${c}=${a[c]}
|
|
2328
|
-
`)}else if(o.json)T(JSON.stringify(
|
|
2329
|
-
`);else{if(
|
|
2330
|
-
`);else{if(
|
|
2331
|
-
`);return}if(T(
|
|
2332
|
-
`)}finally{await o.close()}})}function
|
|
2333
|
-
`)}finally{await o.close()}})}function
|
|
2334
|
-
`)}finally{await o.close()}})}async function
|
|
2452
|
+
`)}else if(o.json)T(JSON.stringify(i,null,2)+`
|
|
2453
|
+
`);else{if(i.stdout)T(i.stdout);if(i.stderr)process.stderr.write(i.stderr)}}finally{await s.close()}})}function oT(e,t){e.command("run").argument("<task>","task to run").option("--json","machine-readable output").action(async(r,n)=>{let o=await t.openController("",Re(e),!0);try{let s=await o.runTask(r);if(n.json)T(JSON.stringify(s,null,2)+`
|
|
2454
|
+
`);else{if(s.stdout)T(s.stdout);if(s.stderr)process.stderr.write(s.stderr)}}finally{await o.close()}})}function sT(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("",Re(e),!0);try{let s=await o.start({services:r,profile:n.profile,detach:n.detach===!0});if(n.json){T(JSON.stringify(s,null,2)+`
|
|
2455
|
+
`);return}if(T(Rb(s)),n.detach)T(`detached; services continue running
|
|
2456
|
+
`)}finally{await o.close()}})}function iT(e,t){e.command("stop").argument("[services...]","services to stop").option("--json","machine-readable output").action(async(r,n)=>{let o=await t.openController("",Re(e),!0);try{if(await o.stop(r),n.json)T(JSON.stringify({stopped:r},null,2)+`
|
|
2457
|
+
`)}finally{await o.close()}})}function aT(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("",Re(e),!0);try{if(await o.restart(r,n.cascade===!0),n.json)T(JSON.stringify({restarted:r,cascade:n.cascade===!0},null,2)+`
|
|
2458
|
+
`)}finally{await o.close()}})}async function rT(e,t,r){let{repoRoot:n,client:o}=await e.findDaemon("",r.repo??"",Re(t));try{if(!o){let i=e.readPersistedState(n);if(r.json){T(JSON.stringify({running:!1,persisted:i},null,2)+`
|
|
2335
2459
|
`);return}if(T(`supervisor is not running
|
|
2336
|
-
`),
|
|
2337
|
-
`);for(let a of
|
|
2338
|
-
`)}return}e.assertMethodAllowed(o,"status");let
|
|
2339
|
-
`);return}T(`PROFILE: ${
|
|
2460
|
+
`),i&&i.processes.length>0){T(`last session ${i.session_id} profile ${i.profile||"(none)"}
|
|
2461
|
+
`);for(let a of i.processes)T(`${a.name} stopped UNKNOWN ${a.pid}
|
|
2462
|
+
`)}return}e.assertMethodAllowed(o,"status");let s=await o.call("status",null);if(r.json){T(JSON.stringify(s,null,2)+`
|
|
2463
|
+
`);return}T(`PROFILE: ${s.profile||"(none)"}
|
|
2340
2464
|
|
|
2341
2465
|
SERVICE STATUS HEALTH PID
|
|
2342
|
-
`);for(let[
|
|
2466
|
+
`);for(let[i,a]of Object.entries(s.services))T(`${i} ${Ls(a)} ${a.health} ${a.pid}
|
|
2343
2467
|
`);T(`
|
|
2344
|
-
PROXY ${
|
|
2345
|
-
`),T(`MCP ${
|
|
2346
|
-
`),T(`IDENTITY ${
|
|
2347
|
-
`),T(`CLOUD ${
|
|
2348
|
-
`)}finally{o?.close()}}var
|
|
2349
|
-
`),await
|
|
2350
|
-
`);try{await
|
|
2351
|
-
`);return}let
|
|
2468
|
+
PROXY ${s.proxy.running?"RUNNING":"STOPPED"} ${s.proxy.address??""}
|
|
2469
|
+
`),T(`MCP ${s.mcp?.running?"RUNNING":"STOPPED"} ${s.mcp?.address??""}
|
|
2470
|
+
`),T(`IDENTITY ${s.identity.user||"(unknown)"}
|
|
2471
|
+
`),T(`CLOUD ${s.identity.project||"(unset)"}
|
|
2472
|
+
`)}finally{o?.close()}}var yH=2000;function cT(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 rT(t,e,r);return}let n=new AbortController,o=()=>n.abort();process.on("SIGINT",o),process.on("SIGTERM",o);try{while(!n.signal.aborted){T(`--- ${new Date().toISOString()} ---
|
|
2473
|
+
`),await rT(t,e,r),T(`
|
|
2474
|
+
`);try{await gH(yH,void 0,{signal:n.signal})}catch{break}}}finally{process.off("SIGINT",o),process.off("SIGTERM",o)}})}function lT(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??"",Re(e));if(!o){T(`no supervisor is running for ${n}
|
|
2475
|
+
`);return}let s=await bH(o);try{let i=r.keepServices!==!0;await o.call("shutdown",{stop_services:i},s)}finally{o.close()}await vH(t,n,s),T(r.keepServices!==!0?`stopped services and the supervisor for ${n}
|
|
2352
2476
|
`:`stopped the supervisor for ${n}; its services keep running
|
|
2353
|
-
`)})}async function
|
|
2354
|
-
`}
|
|
2355
|
-
|
|
2356
|
-
`);return}
|
|
2357
|
-
`);
|
|
2358
|
-
|
|
2359
|
-
`)}
|
|
2360
|
-
`)}finally{await
|
|
2361
|
-
`)
|
|
2362
|
-
`);return
|
|
2363
|
-
`);return}T(
|
|
2364
|
-
`);
|
|
2365
|
-
`)
|
|
2366
|
-
`);
|
|
2367
|
-
|
|
2368
|
-
|
|
2477
|
+
`)})}async function vH(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 bH(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 dT(e,t){e.command("attach").action(async()=>{let r=await t.openAttach("",Re(e)),{runTuiWithController:n}=await Promise.resolve().then(() => (Dh(),J_));await n(t,r)})}en();import{setTimeout as pT}from"timers/promises";function Kd(e){return`${e.timestamp.slice(11,19)} ${e.service.padEnd(10)} ${e.severityText.padEnd(6)} ${Gt(e)}
|
|
2478
|
+
`}function uT(e){for(let t of e)T(`${JSON.stringify(t)}
|
|
2479
|
+
`)}function xH(e){if(!e)return;let t=e.indexOf("=");if(t<=0)return;return{key:e.slice(0,t),value:e.slice(t+1)}}async function mT(e,t,r,n=1000){let o=await e(void 0);o.events.forEach(t);let s=o.nextCursor;while(!r.aborted){try{await pT(n,void 0,{signal:r})}catch{return}o=await e(s),o.events.forEach(t),s=o.nextCursor}}function fT(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("--trace <id>","print a trace's spans and correlated logs").option("--request-id <id>","filter by request id").option("--attribute <key=value>","filter by a single attribute").option("--output <path>","export path").option("--json","JSONL LogRecord 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 s=await t.openController("",Re(e),!0);try{if(o.trace){let d=await s.getTrace(o.trace);if(o.json){T(`${JSON.stringify(d)}
|
|
2480
|
+
`);return}for(let u of d.tree.spans)T(`${u.name} ${u.kind} ${u.status.code} ${u.spanId}
|
|
2481
|
+
`);for(let u of d.events)T(Kd(u));return}let i=xH(o.attribute),a={services:n,level:o.level,search:o.search,regex:o.regex,source:o.source,since:o.since,until:o.until,requestId:o.requestId,traceId:o.trace,attribute:i},c=o.output?t.resolveExportPath(o.output):void 0;if(o.follow&&!c){let d=new AbortController,u=()=>d.abort();process.on("SIGINT",u),process.on("SIGTERM",u);try{await mT((m)=>s.logsPage({...a,cursor:m,direction:"forward"}),(m)=>T(o.json?JSON.stringify(m)+`
|
|
2482
|
+
`:Kd(m)),d.signal)}finally{process.off("SIGINT",u),process.off("SIGTERM",u)}return}if(c||o.all===!0){let d=await s.logs({...a,export:c});if(c){T(`exported ${c}
|
|
2483
|
+
`);return}if(o.json){uT(d);return}for(let u of d)T(Kd(u));return}let l=await s.logsPage({...a,direction:"backward"});if(o.json){uT(l.events);return}for(let d of l.events)T(Kd(d));if(l.hasPrev)T(`\u2026 older matching events omitted; pass --all for the full history
|
|
2484
|
+
`)}finally{await s.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 s=await t.openController("",Re(e),!0);try{let i=t.resolveExportPath(o.output);await s.logs({services:n,level:o.level,search:o.search,regex:o.regex,source:o.source,export:i}),T(`exported ${i}
|
|
2485
|
+
`)}finally{await s.close()}})}function hT(e,t){e.command("daemon").command("logs").option("-f, --follow","keep printing new lines until interrupted").action(async(r)=>{let{repoRoot:n}=t.discover("",Re(e)),o=t.bootstrapLogPath(n),s=0,i=()=>{if(!t.fileExists(o))return;let l=t.readTextFile(o);if(l.length>s)T(l.slice(s)),s=l.length};if(!t.fileExists(o))T(`no daemon bootstrap log yet for this repository
|
|
2486
|
+
`);else i();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 pT(500,void 0,{signal:a.signal})}catch{break}i()}}finally{process.off("SIGINT",c),process.off("SIGTERM",c)}})}Sd();function gT(e,t){let r=e.command("proxy");r.command("status").option("--json").action(async(n)=>{let o=await t.openController("",Re(e),!1);try{if(!o.client){T(`PROXY STOPPED
|
|
2487
|
+
`);return}let s=await o.status();if(n.json){T(JSON.stringify(s.proxy,null,2)+`
|
|
2488
|
+
`);return}T(`PROXY ${s.proxy.running?"RUNNING":"STOPPED"} ${s.proxy.address??""}
|
|
2489
|
+
`);for(let i of s.proxy.routes??[]){let a=i.match?` match=${i.match}`:"",c=i.client_id?` client_id=${i.client_id}`:"";T(` ${i.name.padEnd(16)} identity=${i.identity}${a}${c} ${i.upstream}
|
|
2490
|
+
`)}}finally{await o.close()}}),r.command("start").action(async()=>{let n=await t.openController("",Re(e),!0);try{await n.proxyStart()}finally{await n.close()}}),r.command("stop").action(async()=>{let n=await t.openController("",Re(e),!0);try{await n.proxyStop()}finally{await n.close()}})}function yT(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("",Re(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 s=o.client?await o.status():void 0,i=t.loadTuiConfig(o.cfg.repoRoot,o.cfg.ui.keymap),a=s?.mcp?.port??n??i.mcp_port??Go(o.cfg.repoRoot),c=s?.mcp?.address??ei(a),l=s?.mcp?.token??"";if(r.json){T(JSON.stringify({running:s?.mcp?.running===!0,setup_mode:s?.setup_mode===!0,url:c,port:a,snippets:{claude:JSON.parse(jf(c,l)),cursor:JSON.parse(Bf(c,l)),kilo:JSON.parse(Wf(c,l)),codex:Gf(c,l)}},null,2)+`
|
|
2491
|
+
`);return}if(T(`MCP ${s?.mcp?.running?"RUNNING":"STOPPED"} ${c}
|
|
2492
|
+
|
|
2493
|
+
`),s?.setup_mode===!0)T(`This repository has no .devctl yet, so the daemon is in setup mode.
|
|
2369
2494
|
`),T(`Connect an agent with the config below and ask it to set devctl up for this repository.
|
|
2370
2495
|
`),T(`It can call get_setup_guide, search_docs, and validate_config; nothing will run until a configuration exists.
|
|
2371
2496
|
|
|
2372
|
-
`);T(
|
|
2497
|
+
`);T(dC(c,l))}finally{await o.close()}})}ne();function vT(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){T(JSON.stringify(n,null,2)+`
|
|
2373
2498
|
`);return}if(T(`current ${n.current}
|
|
2374
2499
|
`),T(`latest ${n.latest||"(unavailable)"}
|
|
2375
2500
|
`),T(`channel ${n.kind}
|
|
2376
2501
|
`),!n.newer){if(n.latest!=="")T(`up to date
|
|
2377
2502
|
`);return}if(T(`install ${n.hint}
|
|
2378
2503
|
`),r.check||!n.command)return;if(T(`installing via ${n.kind}\u2026
|
|
2379
|
-
`),(await t.applyUpdate(n.command,!0)).code!==0){process.exitCode=
|
|
2380
|
-
`)})}function
|
|
2381
|
-
`)}),r.action(async()=>{let n=r.opts();await
|
|
2382
|
-
`);else T(t.formatDoctor(o));if(o.issues>0)process.exitCode=2})}function
|
|
2504
|
+
`),(await t.applyUpdate(n.command,!0)).code!==0){process.exitCode=Ey;return}T(`updated to ${n.latest}; ${fl}
|
|
2505
|
+
`)})}function SH(e,t){let r=new wH;return r.name("devctl").description("Local development orchestrator").version(hr(),"-V, --version","print version").option("--config <path>","path to config file or .devctl directory").enablePositionalOptions(),r.command("version").description("print version").action(()=>{T(`${hr()}
|
|
2506
|
+
`)}),r.action(async()=>{let n=r.opts();await Ih(e,n.config??"")}),sT(r,e),iT(r,e),aT(r,e),oT(r,e),nT(r,e),cT(r,e),lT(r,e),fT(r,e),hT(r,e),kH(r,e),CH(r,e),Z_(r,e),gT(r,e),yT(r,e),tT(r,e),eT(r,e),dT(r,e),RH(r,e),vT(r,e),_H(r,t),r}function kH(e,t){e.command("doctor").option("--json","machine-readable output").action(async(r)=>{let n=t.load("",Re(e)),o=await t.runDoctor.execute(n);if(r.json)T(JSON.stringify(o,null,2)+`
|
|
2507
|
+
`);else T(t.formatDoctor(o));if(o.issues>0)process.exitCode=2})}function CH(e,t){e.command("setup").option("--force","overwrite an existing configuration").action(async(r)=>{await t.runSetup("",Re(e),r.force===!0)})}function RH(e,t){e.command("completion").argument("[shell]","zsh, bash, or fish").description("print a shell completion script").action((r)=>{T(Q_(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 s=t.load("",Re(e));T(Oh(o,s).join(`
|
|
2383
2508
|
`)+`
|
|
2384
|
-
`)}catch{T(
|
|
2509
|
+
`)}catch{T(Oh(o,To()).join(`
|
|
2385
2510
|
`)+`
|
|
2386
|
-
`)}})}function
|
|
2387
|
-
`),process.exit(
|
|
2511
|
+
`)}})}function _H(e,t){e.command("_supervisor").option("--repo <path>","repository root").action(async(r)=>{await t(r.repo??"",Re(e))})}async function bT(e,t){process.stdout.on("error",(r)=>{if(r.code==="EPIPE")process.exit(0)});try{await SH(e,t).parseAsync(process.argv)}catch(r){process.stderr.write(I(r)+`
|
|
2512
|
+
`),process.exit(Iy(r))}}Ba();await bT(sx(),Nw);
|