omnish 1.6.5 → 1.6.6

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/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- var Cb=Object.defineProperty;var Yt=(e,t)=>()=>(e&&(t=e(e=0)),t);var kd=(e,t)=>{for(var n in t)Cb(e,n,{get:t[n],enumerable:!0})};import Rb from"node:crypto";import cl from"node:fs";import Sd from"node:os";import V from"node:path";function Tb(){let e=process.env.OMNISH_HOME?.trim();if(e)return V.resolve(e);let t=Sd.homedir(),n=V.join(t,".omnish"),o=V.join(t,".whatslive");try{if(cl.existsSync(n))return n;if(cl.existsSync(o))return o}catch{}return n}function vr(e){let t=Rb.createHash("sha1").update(e,"utf8").digest("hex").slice(0,8);return V.join(vd,t)}function D(e){cl.mkdirSync(e,{recursive:!0,mode:448})}function se(){D(W),D(le),D(wt),D(vd),D(xd),D(Jn),D(bt),D(It),D(Vt),D(ko),D(pl)}var W,le,wt,vd,xd,He,de,bo,br,At,bs,ks,U,Ss,vs,xs,Jn,Cd,Cs,Gn,bt,Rd,It,ul,dl,Td,Vt,kr,Rs,ko,kt,So,Sr,pl,j=Yt(()=>{"use strict";W=Tb(),le=V.join(W,"auth"),wt=V.join(W,"jobs"),vd=V.join(W,"apps"),xd=V.join(W,"logs"),He=V.join(xd,"gateway.log"),de=V.join(W,"gateway.pid"),bo=V.join(W,"gateway-control.json"),br=V.join(W,"config-ui.json"),At=V.join(W,"ui.json"),bs=V.join(W,"tunnel-auth.json"),ks=V.join(W,"ui-server.json"),U=V.join(W,"config.json"),Ss=V.join(W,"shortcuts.json"),vs=V.join(W,"recipes.json"),xs=V.join(W,"recipes-user.json"),Jn=V.join(W,"board"),Cd=V.join(Jn,"board.sqlite"),Cs=V.join(Jn,"governor.json"),Gn=V.join(Jn,"employees.json"),bt=V.join(Jn,"employees"),Rd=V.join(Sd.homedir(),"Cowork","jobs"),It=V.join(W,"cowork"),ul=V.join(It,"tasks.json"),dl=V.join(It,"pending-runs.json"),Td=V.join(It,"completions.sqlite"),Vt=V.join(W,"watch"),kr=V.join(Vt,"rules.json"),Rs=V.join(Vt,"events.sqlite"),ko=V.join(W,"bin"),kt=V.join(W,"venvs","whisper"),So=V.join(W,"node-transcribe"),Sr=V.join(W,"models","transformers"),pl=V.join(W,"media","pull")});function Ad(e){let t=e.trim();for(;;){let n=t;if(t=t.replace(/^whatsapp:/i,"").trim(),t===n)return t}}function Eb(e){let t=Ad(e);if(!t.toLowerCase().endsWith("@g.us"))return!1;let o=t.slice(0,t.length-5);return!o||o.includes("@")?!1:/^[0-9]+(-[0-9]+)*$/.test(o)}function Pb(e){let t=e.match(Pd);if(t)return t[1]??null;let n=e.match($d);if(n)return n[1]??null;let o=e.match(Md);return o?o[1]??null:null}function Ed(e){let t=e.replace(/\D/g,"");return t?`+${t}`:""}function mn(e){return`${e.replace(/\D/g,"")}@s.whatsapp.net`}function te(e){let t=Ad(e);if(!t||Eb(t))return null;if(Pd.test(t)||$d.test(t)||Md.test(t)){let o=Pb(t);if(!o)return null;let r=Ed(o);return r.length>1?r:null}if(t.includes("@"))return null;let n=Ed(t);return n.length>1?n:null}function Ts(e){return e.map(t=>String(t).trim()).filter(t=>!!t).map(t=>t==="*"?t:te(t)).filter(t=>!!t)}function Es(e){let t=new Set;for(let n of e){if(n==="*")continue;let o=te(String(n));o&&t.add(o)}return t}function Fe(e){let t=e.trim();for(;;){let n=t.toLowerCase();if(n.startsWith("tg:")){t=t.slice(3).trimStart();continue}if(n.startsWith("telegram:")){t=t.slice(9).trimStart();continue}break}return/^\d+$/.test(t)?t:null}function Ps(e){let t=new Set;for(let n of e){let o=Fe(String(n));o&&t.add(o)}return t}function ml(e){let t=e.trim();if(!t)return null;let n=t.toLowerCase();if(n.startsWith("tg:")||n.startsWith("telegram:")){let r=Fe(t);return r?{kind:"tg",id:r}:null}let o=te(t);return o?{kind:"wa",normalized:o}:null}function Id(e,t){let n=te(t);return n?e.has(n):!1}var Pd,$d,Md,dt=Yt(()=>{"use strict";Pd=/^(\d+)(?::\d+)?@s\.whatsapp\.net$/i,$d=/^(\d+)@c\.us$/i,Md=/^(\d+)@lid$/i});import $s from"node:fs";function $b(e){return typeof e.mediaSendFiles=="boolean"?e.mediaSendFiles:typeof e.pullAutoSend=="boolean"?e.pullAutoSend:T.mediaSendFiles}function Mb(e){return typeof e.mediaUrlAutoDl=="boolean"?e.mediaUrlAutoDl:typeof e.pullUrlAutoDetect=="boolean"?e.pullUrlAutoDetect:T.mediaUrlAutoDl}function Ab(e){return typeof e.mediaInstallFromChat=="boolean"?e.mediaInstallFromChat:typeof e.pullInstallFromChat=="boolean"?e.pullInstallFromChat:T.mediaInstallFromChat}function Ib(e){return typeof e.mediaOutputDir=="string"?e.mediaOutputDir.trim().slice(0,4096):typeof e.pullOutputDir=="string"?e.pullOutputDir.trim().slice(0,4096):T.mediaOutputDir}function Ob(e){let t=typeof e.mediaMaxBytes=="number"?e.mediaMaxBytes:typeof e.pullMaxBytes=="number"?e.pullMaxBytes:T.mediaMaxBytes;return!Number.isFinite(t)||t<0?T.mediaMaxBytes:t===0?0:Math.min(2e9,Math.floor(t))}function Lb(e){let t=typeof e.mediaWhisperModel=="string"&&e.mediaWhisperModel.trim().length>0?e.mediaWhisperModel:typeof e.pullWhisperModel=="string"&&e.pullWhisperModel.trim().length>0?e.pullWhisperModel:T.mediaWhisperModel;return String(t).trim().slice(0,64)}function Nb(e){let t=typeof e.mediaWhisperDevice=="string"&&e.mediaWhisperDevice.trim().length>0?e.mediaWhisperDevice:T.mediaWhisperDevice,n=String(t).trim().toLowerCase().slice(0,16);return n==="cpu"||n==="cuda"||n==="auto"?n:T.mediaWhisperDevice}function _b(e){let t=typeof e.mediaTranscribeTimeoutMs=="number"?e.mediaTranscribeTimeoutMs:T.mediaTranscribeTimeoutMs;return!Number.isFinite(t)||t<=0?T.mediaTranscribeTimeoutMs:Math.min(9e5,Math.max(6e4,Math.floor(t)))}function Fb(e){let t=typeof e.mediaTranscribeEngine=="string"&&e.mediaTranscribeEngine.trim().length>0?e.mediaTranscribeEngine:T.mediaTranscribeEngine,n=String(t).trim().toLowerCase().slice(0,32);return n==="whisper"||n==="transformers"||n==="auto"?n:T.mediaTranscribeEngine}function Db(e){return typeof e.mediaTranscribeFallback=="boolean"?e.mediaTranscribeFallback:T.mediaTranscribeFallback}function Wb(e){return e==="downloads"||e==="omnishData"||e==="sessionCwd"||e==="processCwd"||e==="fixed"?e:T.fileReceiveRootMode}function Ub(e){return e==="primary"?"primary":"secondary"}function Bb(e){if(!e||typeof e!="object")return{};let t={};for(let[n,o]of Object.entries(e)){if(typeof o!="string")continue;let r=o.trim();if(!r)continue;let s=n.trim(),i=s.toLowerCase();if(i.startsWith("wa:")){let l=te(s.slice(3));l&&(t[`wa:${l}`]=r.slice(0,64));continue}if(i.startsWith("tg:")||i.startsWith("telegram:")){let l=Fe(s);l&&(t[`tg:${l}`]=r.slice(0,64));continue}let a=te(s);a&&(t[`wa:${a}`]=r.slice(0,64))}return t}function xr(e){let t=typeof e.appsFlushMs=="number"&&e.appsFlushMs>=0?e.appsFlushMs:T.appsFlushMs,n=e.gatewayMode==="telegram"||e.gatewayMode==="both"||e.gatewayMode==="whatsapp"?e.gatewayMode:T.gatewayMode,o=typeof e.telegramBotToken=="string"?e.telegramBotToken:T.telegramBotToken,r=Array.isArray(e.telegramAllowFrom)?[...new Set(e.telegramAllowFrom.map(s=>Fe(String(s))).filter(s=>!!s))].sort():T.telegramAllowFrom;return{...T,...e,gatewayMode:n,telegramBotToken:o,telegramAllowFrom:r,allowFrom:Array.isArray(e.allowFrom)?Ts(e.allowFrom.map(String)).filter(s=>s!=="*"):T.allowFrom,commandPrefix:(()=>{let s=typeof e.commandPrefix=="string"&&e.commandPrefix.length>0?e.commandPrefix:T.commandPrefix;return s==="! "?"!":s})(),syncTimeoutMs:typeof e.syncTimeoutMs=="number"&&e.syncTimeoutMs>0?e.syncTimeoutMs:T.syncTimeoutMs,syncMaxBytes:typeof e.syncMaxBytes=="number"&&e.syncMaxBytes>0?e.syncMaxBytes:T.syncMaxBytes,jobLogTailLines:typeof e.jobLogTailLines=="number"&&e.jobLogTailLines>0?e.jobLogTailLines:T.jobLogTailLines,shell:typeof e.shell=="string"&&e.shell.length>0?e.shell:T.shell,appsCols:typeof e.appsCols=="number"&&e.appsCols>0&&e.appsCols<=500?Math.floor(e.appsCols):T.appsCols,appsRows:typeof e.appsRows=="number"&&e.appsRows>0&&e.appsRows<=200?Math.floor(e.appsRows):T.appsRows,appsFlushMs:t,appsMinIntervalMs:typeof e.appsMinIntervalMs=="number"&&e.appsMinIntervalMs>=0?e.appsMinIntervalMs:T.appsMinIntervalMs,appsMaxFlushBytes:typeof e.appsMaxFlushBytes=="number"&&e.appsMaxFlushBytes>256?Math.floor(e.appsMaxFlushBytes):T.appsMaxFlushBytes,appsMaxSessions:typeof e.appsMaxSessions=="number"&&e.appsMaxSessions>0?Math.min(50,Math.floor(e.appsMaxSessions)):T.appsMaxSessions,appsMaxSessionsTotal:typeof e.appsMaxSessionsTotal=="number"&&e.appsMaxSessionsTotal>0?Math.min(200,Math.floor(e.appsMaxSessionsTotal)):T.appsMaxSessionsTotal,appsMaxWaChars:typeof e.appsMaxWaChars=="number"&&e.appsMaxWaChars>256?Math.floor(e.appsMaxWaChars):T.appsMaxWaChars,appsLogTailLines:typeof e.appsLogTailLines=="number"&&e.appsLogTailLines>0?Math.min(500,Math.floor(e.appsLogTailLines)):T.appsLogTailLines,appsSubmitDelayMs:typeof e.appsSubmitDelayMs=="number"&&e.appsSubmitDelayMs>=0?Math.min(500,Math.floor(e.appsSubmitDelayMs)):T.appsSubmitDelayMs,appsClearInput:typeof e.appsClearInput=="boolean"?e.appsClearInput:T.appsClearInput,appsClearInputDelayMs:typeof e.appsClearInputDelayMs=="number"&&e.appsClearInputDelayMs>=0?Math.min(200,Math.floor(e.appsClearInputDelayMs)):T.appsClearInputDelayMs,appsClearInputSequence:typeof e.appsClearInputSequence=="string"&&e.appsClearInputSequence.length>0?e.appsClearInputSequence.slice(0,200):T.appsClearInputSequence,appsSkipClearOnPasswordPrompt:typeof e.appsSkipClearOnPasswordPrompt=="boolean"?e.appsSkipClearOnPasswordPrompt:T.appsSkipClearOnPasswordPrompt,appsPasswordPromptHint:typeof e.appsPasswordPromptHint=="boolean"?e.appsPasswordPromptHint:T.appsPasswordPromptHint,fileSendMaxBytes:typeof e.fileSendMaxBytes=="number"&&e.fileSendMaxBytes>=0?e.fileSendMaxBytes===0?0:Math.min(2e9,Math.floor(e.fileSendMaxBytes)):T.fileSendMaxBytes,fileReceiveMaxBytes:typeof e.fileReceiveMaxBytes=="number"&&e.fileReceiveMaxBytes>=0?e.fileReceiveMaxBytes===0?0:Math.min(2e9,Math.floor(e.fileReceiveMaxBytes)):T.fileReceiveMaxBytes,fileInboxSubdir:typeof e.fileInboxSubdir=="string"&&e.fileInboxSubdir.length>0&&e.fileInboxSubdir.replace(/[/\\]/g,"").slice(0,80)||T.fileInboxSubdir,fileReceiveRootMode:Wb(e.fileReceiveRootMode),fileReceiveRootPath:typeof e.fileReceiveRootPath=="string"?e.fileReceiveRootPath.trim().slice(0,4096):T.fileReceiveRootPath,recipesAllowDangerousBuiltins:typeof e.recipesAllowDangerousBuiltins=="boolean"?e.recipesAllowDangerousBuiltins:T.recipesAllowDangerousBuiltins,recipesMaxTaskChars:typeof e.recipesMaxTaskChars=="number"&&e.recipesMaxTaskChars>=0?e.recipesMaxTaskChars===0?0:Math.min(Number.MAX_SAFE_INTEGER,Math.floor(e.recipesMaxTaskChars)):T.recipesMaxTaskChars,recipesMacroDefaultCommand:typeof e.recipesMacroDefaultCommand=="string"&&e.recipesMacroDefaultCommand.trim().length>0?e.recipesMacroDefaultCommand.trim().slice(0,4096):T.recipesMacroDefaultCommand,recipesRunAttach:typeof e.recipesRunAttach=="boolean"?e.recipesRunAttach:T.recipesRunAttach,recipesNotifyEnabled:typeof e.recipesNotifyEnabled=="boolean"?e.recipesNotifyEnabled:T.recipesNotifyEnabled,clusterEnabled:typeof e.clusterEnabled=="boolean"?e.clusterEnabled:T.clusterEnabled,clusterLabel:typeof e.clusterLabel=="string"?e.clusterLabel.trim().slice(0,128):T.clusterLabel,clusterRole:Ub(e.clusterRole),clusterSenderBindings:Bb(e.clusterSenderBindings),serviceInstallFromChat:typeof e.serviceInstallFromChat=="boolean"?e.serviceInstallFromChat:T.serviceInstallFromChat,updateCheckEnabled:typeof e.updateCheckEnabled=="boolean"?e.updateCheckEnabled:T.updateCheckEnabled,updateCheckIntervalMs:typeof e.updateCheckIntervalMs=="number"&&e.updateCheckIntervalMs>0?Math.min(6048e5,Math.max(36e5,Math.floor(e.updateCheckIntervalMs))):T.updateCheckIntervalMs,updateCheckPackageName:typeof e.updateCheckPackageName=="string"&&e.updateCheckPackageName.trim().length>0?e.updateCheckPackageName.trim().slice(0,214):T.updateCheckPackageName,updateInfoUrl:typeof e.updateInfoUrl=="string"?e.updateInfoUrl.trim().slice(0,2048):T.updateInfoUrl,chatLlmFallbackEnabled:typeof e.chatLlmFallbackEnabled=="boolean"?e.chatLlmFallbackEnabled:T.chatLlmFallbackEnabled,chatLlmShellCommand:typeof e.chatLlmShellCommand=="string"?e.chatLlmShellCommand.trim().slice(0,8192):T.chatLlmShellCommand,chatLlmTimeoutMs:typeof e.chatLlmTimeoutMs=="number"&&e.chatLlmTimeoutMs>0?Math.min(9e5,Math.floor(e.chatLlmTimeoutMs)):T.chatLlmTimeoutMs,chatLlmMaxInputChars:typeof e.chatLlmMaxInputChars=="number"&&e.chatLlmMaxInputChars>0?Math.min(5e5,Math.floor(e.chatLlmMaxInputChars)):T.chatLlmMaxInputChars,chatLlmMaxOutputChars:typeof e.chatLlmMaxOutputChars=="number"&&e.chatLlmMaxOutputChars>0?Math.min(2e6,Math.floor(e.chatLlmMaxOutputChars)):T.chatLlmMaxOutputChars,chatLlmNeedsTty:typeof e.chatLlmNeedsTty=="boolean"?e.chatLlmNeedsTty:T.chatLlmNeedsTty,chatLlmWorkDir:typeof e.chatLlmWorkDir=="string"?e.chatLlmWorkDir.trim().slice(0,4096):T.chatLlmWorkDir,chatAgentEnabled:typeof e.chatAgentEnabled=="boolean"?e.chatAgentEnabled:T.chatAgentEnabled,chatAgentCommand:typeof e.chatAgentCommand=="string"?e.chatAgentCommand.trim().slice(0,8192):T.chatAgentCommand,chatAgentPerPeer:typeof e.chatAgentPerPeer=="boolean"?e.chatAgentPerPeer:T.chatAgentPerPeer,chatAgentMaxQueue:typeof e.chatAgentMaxQueue=="number"&&e.chatAgentMaxQueue>0?Math.min(1e4,Math.floor(e.chatAgentMaxQueue)):T.chatAgentMaxQueue,tunnelEnabled:typeof e.tunnelEnabled=="boolean"?e.tunnelEnabled:T.tunnelEnabled,tunnelRelayUrl:typeof e.tunnelRelayUrl=="string"&&e.tunnelRelayUrl.trim().length>0?e.tunnelRelayUrl.trim().slice(0,2048):T.tunnelRelayUrl,tunnelMaxActive:typeof e.tunnelMaxActive=="number"&&e.tunnelMaxActive>0?Math.min(50,Math.floor(e.tunnelMaxActive)):T.tunnelMaxActive,platformToken:typeof e.platformToken=="string"?e.platformToken.trim():T.platformToken,platformDeviceId:typeof e.platformDeviceId=="string"?e.platformDeviceId.trim().slice(0,128):T.platformDeviceId,webhookEnabled:typeof e.webhookEnabled=="boolean"?e.webhookEnabled:T.webhookEnabled,webhookPort:typeof e.webhookPort=="number"&&e.webhookPort>=0?Math.min(65535,Math.floor(e.webhookPort)):T.webhookPort,webhookHost:typeof e.webhookHost=="string"&&e.webhookHost.trim().length>0?e.webhookHost.trim():T.webhookHost,webhookToken:typeof e.webhookToken=="string"?e.webhookToken.trim():T.webhookToken,watchEnabled:typeof e.watchEnabled=="boolean"?e.watchEnabled:T.watchEnabled,watchDebounceMs:typeof e.watchDebounceMs=="number"&&Number.isFinite(e.watchDebounceMs)?Math.max(500,Math.min(6e4,Math.floor(e.watchDebounceMs))):T.watchDebounceMs,watchMaxEventsPerMinute:typeof e.watchMaxEventsPerMinute=="number"&&Number.isFinite(e.watchMaxEventsPerMinute)?Math.max(1,Math.min(120,Math.floor(e.watchMaxEventsPerMinute))):T.watchMaxEventsPerMinute,watchAutoRestore:typeof e.watchAutoRestore=="boolean"?e.watchAutoRestore:T.watchAutoRestore,boardCoordinatorEnabled:typeof e.boardCoordinatorEnabled=="boolean"?e.boardCoordinatorEnabled:T.boardCoordinatorEnabled,boardCoordinatorIntervalMs:typeof e.boardCoordinatorIntervalMs=="number"&&Number.isFinite(e.boardCoordinatorIntervalMs)?Math.max(2e3,Math.min(12e4,Math.floor(e.boardCoordinatorIntervalMs))):T.boardCoordinatorIntervalMs,boardMaxRework:typeof e.boardMaxRework=="number"&&Number.isFinite(e.boardMaxRework)?Math.max(0,Math.min(10,Math.floor(e.boardMaxRework))):T.boardMaxRework,boardNotifyProgress:e.boardNotifyProgress==="all"||e.boardNotifyProgress==="milestones"||e.boardNotifyProgress==="none"?e.boardNotifyProgress:T.boardNotifyProgress,boardNotifyJobStatus:typeof e.boardNotifyJobStatus=="boolean"?e.boardNotifyJobStatus:T.boardNotifyJobStatus,boardCoordinatorAgentEnabled:typeof e.boardCoordinatorAgentEnabled=="boolean"?e.boardCoordinatorAgentEnabled:T.boardCoordinatorAgentEnabled,boardCoordinatorAgentCommand:typeof e.boardCoordinatorAgentCommand=="string"?e.boardCoordinatorAgentCommand.trim():T.boardCoordinatorAgentCommand,boardCoordinatorFinetuneEnabled:typeof e.boardCoordinatorFinetuneEnabled=="boolean"?e.boardCoordinatorFinetuneEnabled:T.boardCoordinatorFinetuneEnabled,boardSkillResearchEnabled:typeof e.boardSkillResearchEnabled=="boolean"?e.boardSkillResearchEnabled:T.boardSkillResearchEnabled,boardCoordinatorFileSendEnabled:typeof e.boardCoordinatorFileSendEnabled=="boolean"?e.boardCoordinatorFileSendEnabled:T.boardCoordinatorFileSendEnabled,boardFeedbackReminderEnabled:typeof e.boardFeedbackReminderEnabled=="boolean"?e.boardFeedbackReminderEnabled:T.boardFeedbackReminderEnabled,boardPrimaryAgent:typeof e.boardPrimaryAgent=="string"?e.boardPrimaryAgent.trim().toLowerCase():T.boardPrimaryAgent,boardAgentPermissionMode:e.boardAgentPermissionMode==="safe"||e.boardAgentPermissionMode==="yolo"||e.boardAgentPermissionMode==="dangerous"?e.boardAgentPermissionMode:T.boardAgentPermissionMode,boardApplyPrimaryAgentToRoles:Array.isArray(e.boardApplyPrimaryAgentToRoles)?e.boardApplyPrimaryAgentToRoles.filter(s=>typeof s=="string"&&s.trim().length>0).map(s=>s.trim().toLowerCase()):T.boardApplyPrimaryAgentToRoles,mediaSendFiles:$b(e),mediaUrlAutoDl:Mb(e),mediaInstallFromChat:Ab(e),mediaOutputDir:Ib(e),mediaMaxBytes:Ob(e),mediaWhisperModel:Lb(e),mediaWhisperDevice:Nb(e),mediaTranscribeTimeoutMs:_b(e),mediaTranscribeEngine:Fb(e),mediaTranscribeFallback:Db(e),progressUpdates:typeof e.progressUpdates=="boolean"?e.progressUpdates:T.progressUpdates,pullYtDlpPath:typeof e.pullYtDlpPath=="string"?e.pullYtDlpPath.trim().slice(0,4096):T.pullYtDlpPath,pullFfmpegPath:typeof e.pullFfmpegPath=="string"?e.pullFfmpegPath.trim().slice(0,4096):T.pullFfmpegPath,pullWhisperPath:typeof e.pullWhisperPath=="string"?e.pullWhisperPath.trim().slice(0,4096):T.pullWhisperPath}}function $(e){let t=v(),n=xr({...t,...e});return Ve(n),n}function v(){if(se(),!$s.existsSync(U)){let e=xr({});return Ve(e),e}try{let e=$s.readFileSync(U,"utf8"),t=JSON.parse(e);return xr(t)}catch{return xr({})}}function Ve(e){se();let t=xr(e);$s.writeFileSync(U,JSON.stringify(t,null,2)+`
3
- `,{mode:384})}function Ms(e){let t=ml(e);if(!t)throw new Error("Invalid entry. Use +E164 (WhatsApp) or tg:<user_id> (Telegram).");let n=v();if(t.kind==="wa"){if(t.normalized==="*")throw new Error("Wildcards are not allowed.");let o=new Set(n.allowFrom);o.add(t.normalized),n.allowFrom=[...o].sort()}else{let o=new Set(n.telegramAllowFrom);o.add(t.id),n.telegramAllowFrom=[...o].sort()}return Ve(n),n}function As(e){let t=ml(e);if(!t)throw new Error("Invalid entry. Use +E164 (WhatsApp) or tg:<user_id> (Telegram).");let n=v();return t.kind==="wa"?n.allowFrom=n.allowFrom.filter(o=>o!==t.normalized):n.telegramAllowFrom=n.telegramAllowFrom.filter(o=>Fe(o)!==t.id),Ve(n),n}function Ie(e){let t=typeof process.env.TELEGRAM_BOT_TOKEN=="string"?process.env.TELEGRAM_BOT_TOKEN.trim():"";return t||(e.telegramBotToken??"").trim()}function Ot(e){let t=e.trim();return/^\d{6,}:[A-Za-z0-9_-]{20,}$/.test(t)}function fn(e){let t=v();return t.telegramBotToken=e.trim(),Ve(t),v()}function vo(e){let t=e.trim().toLowerCase();return t==="whatsapp"||t==="wa"||t==="w"?"whatsapp":t==="telegram"||t==="tg"||t==="t"?"telegram":t==="both"||t==="all"||t==="b"?"both":null}function Is(e){let t=v();return t.gatewayMode=e,Ve(t),v()}function Os(e){let t=v();return t.clusterEnabled=e,Ve(t),v()}function St(){try{return $s.readdirSync(le).length>0}catch{return!1}}var T,ue=Yt(()=>{"use strict";j();dt();T={gatewayMode:"whatsapp",telegramBotToken:"",telegramAllowFrom:[],allowFrom:[],commandPrefix:"!",syncTimeoutMs:3e4,syncMaxBytes:32768,jobLogTailLines:80,shell:"/bin/bash",appsCols:120,appsRows:40,appsFlushMs:300,appsMinIntervalMs:800,appsMaxFlushBytes:8192,appsMaxSessions:5,appsMaxSessionsTotal:20,appsMaxWaChars:3500,appsLogTailLines:80,appsSubmitDelayMs:50,appsClearInput:!0,appsClearInputDelayMs:20,appsClearInputSequence:"^A,^K",appsSkipClearOnPasswordPrompt:!0,appsPasswordPromptHint:!0,fileSendMaxBytes:0,fileReceiveMaxBytes:0,fileInboxSubdir:"inbox",fileReceiveRootMode:"downloads",fileReceiveRootPath:"",recipesAllowDangerousBuiltins:!1,recipesMaxTaskChars:0,recipesMacroDefaultCommand:'claude -p "$OMNISH_TASK"',recipesRunAttach:!1,recipesNotifyEnabled:!0,clusterEnabled:!1,clusterLabel:"",clusterRole:"secondary",clusterSenderBindings:{},serviceInstallFromChat:!1,updateCheckEnabled:!1,updateCheckIntervalMs:864e5,updateCheckPackageName:"omnish",updateInfoUrl:"",chatLlmFallbackEnabled:!1,chatLlmShellCommand:"",chatLlmTimeoutMs:12e4,chatLlmMaxInputChars:16e3,chatLlmMaxOutputChars:24e3,chatLlmNeedsTty:!1,chatLlmWorkDir:"",chatAgentEnabled:!1,chatAgentCommand:"",chatAgentPerPeer:!0,chatAgentMaxQueue:64,tunnelEnabled:!1,tunnelRelayUrl:"https://tunnel.omnish.dev",platformToken:"",platformDeviceId:"",tunnelMaxActive:5,webhookEnabled:!1,webhookPort:0,webhookHost:"127.0.0.1",webhookToken:"",watchEnabled:!1,watchDebounceMs:2e3,watchMaxEventsPerMinute:30,watchAutoRestore:!0,boardCoordinatorEnabled:!0,boardCoordinatorIntervalMs:5e3,boardMaxRework:3,boardNotifyProgress:"milestones",boardNotifyJobStatus:!0,boardCoordinatorAgentEnabled:!1,boardCoordinatorAgentCommand:"",boardCoordinatorFinetuneEnabled:!1,boardSkillResearchEnabled:!0,boardCoordinatorFileSendEnabled:!0,boardFeedbackReminderEnabled:!1,boardPrimaryAgent:"",boardAgentPermissionMode:"safe",boardApplyPrimaryAgentToRoles:["researcher","implementer","reviewer"],mediaSendFiles:!0,mediaUrlAutoDl:!0,mediaInstallFromChat:!1,mediaOutputDir:"",mediaMaxBytes:0,mediaWhisperModel:"small",mediaWhisperDevice:"auto",mediaTranscribeTimeoutMs:6e5,mediaTranscribeEngine:"whisper",mediaTranscribeFallback:!0,progressUpdates:!0,pullYtDlpPath:"",pullFfmpegPath:"",pullWhisperPath:""}});import jb from"pino";function xo(){return process.env.OMNISH_VERBOSE==="1"||process.env.WHATSVERBOSE==="1"}function Od(){return xo()?"info":"silent"}function fl(e){e?process.env.OMNISH_VERBOSE="1":delete process.env.OMNISH_VERBOSE,P.level=Od()}function Ld(){return P.child({module:"baileys"})}var P,be=Yt(()=>{"use strict";P=jb({level:Od(),base:{app:"omnish"}})});import Pl from"node:fs";function Zt(){try{let e=Pl.readFileSync(bs,"utf8"),t=JSON.parse(e);return!t||typeof t.token!="string"||!t.token.trim()?null:{token:t.token.trim(),relayUrl:typeof t.relayUrl=="string"?t.relayUrl.trim():void 0}}catch{return null}}function Ft(e){se(),Pl.writeFileSync(bs,JSON.stringify({token:e.token.trim(),...e.relayUrl?{relayUrl:e.relayUrl.trim()}:{}},null,2)+`
4
- `,{mode:384})}function Ks(){try{Pl.unlinkSync(bs)}catch{}}function Uk(){return process.env.OMNISH_TOKEN?.trim()||process.env.OMNISH_TUNNEL_TOKEN?.trim()||process.env.OMNISH_DEVICE_TOKEN?.trim()||""}function Dt(){let e=Uk();if(e)return e;let t=v().platformToken.trim();return t||(Zt()?.token??"")}function $l(e){let t=process.env.OMNISH_PLATFORM_URL?.trim()||process.env.OMNISH_COMM_LAYER_URL?.trim()||process.env.OMNISH_TUNNEL_RELAY?.trim();if(t)return t.replace(/\/$/,"");let n=v().tunnelRelayUrl.trim();if(n)return n.replace(/\/$/,"");let o=Zt()?.relayUrl?.trim();return o?o.replace(/\/$/,""):e.replace(/\/$/,"")}function vt(e){return $l(e)}var Kn=Yt(()=>{"use strict";ue();j()});var Le,Yn=Yt(()=>{"use strict";Le="https://tunnel.omnish.dev"});var Dm={};kd(Dm,{chunksToSrt:()=>Fm,isNodeTranscribeInstalled:()=>Tn,mapWhisperModelToXenova:()=>_m,transcribeWithTransformers:()=>kS,transformersPackagePath:()=>Zn});import{spawnSync as hS}from"node:child_process";import Rn from"node:fs";import Ut from"node:path";import{pathToFileURL as gS}from"node:url";function Zn(){return Ut.join(So,"node_modules","@xenova","transformers")}function Tn(){try{let e=Ut.join(Zn(),"package.json");return Rn.existsSync(e)}catch{return!1}}function _m(e){let t=e.trim().toLowerCase();return t in oc?oc[t]:t.startsWith("xenova/")?e.trim():oc.small}async function yS(){if(!Tn())throw new Error("Transformers.js not installed. Run: omnish pull install --transcribe-node");let e=JSON.parse(Rn.readFileSync(Ut.join(Zn(),"package.json"),"utf8")),t=e.exports?.["."],n=(typeof t=="string"?t:null)??e.main??"./dist/transformers.node.mjs",o=Ut.join(Zn(),n.replace(/^\.\//,""));return await import(gS(o).href)}function Nm(e){let t=Math.max(0,Math.round(e*1e3)),n=Math.floor(t/36e5),o=Math.floor(t%36e5/6e4),r=Math.floor(t%6e4/1e3),s=t%1e3;return`${String(n).padStart(2,"0")}:${String(o).padStart(2,"0")}:${String(r).padStart(2,"0")},${String(s).padStart(3,"0")}`}function Fm(e){let t=[],n=1;for(let o of e){let r=o.text?.trim(),s=o.timestamp;!r||!s||s.length<2||(t.push(String(n++)),t.push(`${Nm(s[0])} --> ${Nm(s[1])}`),t.push(r),t.push(""))}return t.join(`
5
- `).trimEnd()}function wS(e,t,n,o){let r=Ut.join(n,"_transformers_audio.wav");return hS(e,["-y","-i",t,"-vn","-ar","16000","-ac","1",r],{encoding:"utf8",timeout:o,windowsHide:!0}).status===0&&Rn.existsSync(r)?r:null}async function bS(e,t,n,o){let r=Ut.extname(t).toLowerCase();if([".wav",".mp3",".m4a",".flac",".ogg",".opus"].includes(r))return t;if(!e.ffmpeg)throw new Error("ffmpeg required to extract audio for Transformers.js (omnish pull install)");let s=wS(e.ffmpeg,t,n,o);if(!s)throw new Error("ffmpeg audio extract failed");return s}async function kS(e){let{cfg:t,tools:n,inputPath:o,outputDir:r,timeoutMs:s}=e;if(!Rn.existsSync(o))throw new Error(`File not found: ${o}`);let i=await yS();i.env.cacheDir=Sr,i.env.allowLocalModels=!0;let a=_m(t.mediaWhisperModel.trim()||"small"),l=await bS(n,o,r,s),u=await(await i.pipeline("automatic-speech-recognition",a,{quantized:!0}))(l,{return_timestamps:!0,chunk_length_s:30,stride_length_s:5}),d=(u.text??"").trim(),m=u.chunks??[],f=Ut.basename(o,Ut.extname(o)),h=Ut.join(r,`${f}.txt`),g=Ut.join(r,`${f}.srt`);Rn.writeFileSync(h,d,"utf8"),m.length>0?Rn.writeFileSync(g,Fm(m),"utf8"):d&&Rn.writeFileSync(g,`1
2
+ var Pb=Object.defineProperty;var Yt=(e,t)=>()=>(e&&(t=e(e=0)),t);var xd=(e,t)=>{for(var n in t)Pb(e,n,{get:t[n],enumerable:!0})};import $b from"node:crypto";import ul from"node:fs";import Cd from"node:os";import V from"node:path";function Mb(){let e=process.env.OMNISH_HOME?.trim();if(e)return V.resolve(e);let t=Cd.homedir(),n=V.join(t,".omnish"),o=V.join(t,".whatslive");try{if(ul.existsSync(n))return n;if(ul.existsSync(o))return o}catch{}return n}function vr(e){let t=$b.createHash("sha1").update(e,"utf8").digest("hex").slice(0,8);return V.join(Rd,t)}function D(e){ul.mkdirSync(e,{recursive:!0,mode:448})}function se(){D(W),D(le),D(wt),D(Rd),D(Td),D(Jn),D(bt),D(It),D(Vt),D(ko),D(ml)}var W,le,wt,Rd,Td,He,de,bo,br,At,bs,ks,U,Ss,vs,xs,Jn,Ed,Cs,Gn,bt,Pd,It,dl,pl,$d,Vt,kr,Rs,ko,kt,So,Sr,ml,j=Yt(()=>{"use strict";W=Mb(),le=V.join(W,"auth"),wt=V.join(W,"jobs"),Rd=V.join(W,"apps"),Td=V.join(W,"logs"),He=V.join(Td,"gateway.log"),de=V.join(W,"gateway.pid"),bo=V.join(W,"gateway-control.json"),br=V.join(W,"config-ui.json"),At=V.join(W,"ui.json"),bs=V.join(W,"tunnel-auth.json"),ks=V.join(W,"ui-server.json"),U=V.join(W,"config.json"),Ss=V.join(W,"shortcuts.json"),vs=V.join(W,"recipes.json"),xs=V.join(W,"recipes-user.json"),Jn=V.join(W,"board"),Ed=V.join(Jn,"board.sqlite"),Cs=V.join(Jn,"governor.json"),Gn=V.join(Jn,"employees.json"),bt=V.join(Jn,"employees"),Pd=V.join(Cd.homedir(),"Cowork","jobs"),It=V.join(W,"cowork"),dl=V.join(It,"tasks.json"),pl=V.join(It,"pending-runs.json"),$d=V.join(It,"completions.sqlite"),Vt=V.join(W,"watch"),kr=V.join(Vt,"rules.json"),Rs=V.join(Vt,"events.sqlite"),ko=V.join(W,"bin"),kt=V.join(W,"venvs","whisper"),So=V.join(W,"node-transcribe"),Sr=V.join(W,"models","transformers"),ml=V.join(W,"media","pull")});function Ld(e){let t=e.trim();for(;;){let n=t;if(t=t.replace(/^whatsapp:/i,"").trim(),t===n)return t}}function Ab(e){let t=Ld(e);if(!t.toLowerCase().endsWith("@g.us"))return!1;let o=t.slice(0,t.length-5);return!o||o.includes("@")?!1:/^[0-9]+(-[0-9]+)*$/.test(o)}function Ib(e){let t=e.match(Ad);if(t)return t[1]??null;let n=e.match(Id);if(n)return n[1]??null;let o=e.match(Od);return o?o[1]??null:null}function Md(e){let t=e.replace(/\D/g,"");return t?`+${t}`:""}function mn(e){return`${e.replace(/\D/g,"")}@s.whatsapp.net`}function te(e){let t=Ld(e);if(!t||Ab(t))return null;if(Ad.test(t)||Id.test(t)||Od.test(t)){let o=Ib(t);if(!o)return null;let r=Md(o);return r.length>1?r:null}if(t.includes("@"))return null;let n=Md(t);return n.length>1?n:null}function Ts(e){return e.map(t=>String(t).trim()).filter(t=>!!t).map(t=>t==="*"?t:te(t)).filter(t=>!!t)}function Es(e){let t=new Set;for(let n of e){if(n==="*")continue;let o=te(String(n));o&&t.add(o)}return t}function Fe(e){let t=e.trim();for(;;){let n=t.toLowerCase();if(n.startsWith("tg:")){t=t.slice(3).trimStart();continue}if(n.startsWith("telegram:")){t=t.slice(9).trimStart();continue}break}return/^\d+$/.test(t)?t:null}function Ps(e){let t=new Set;for(let n of e){let o=Fe(String(n));o&&t.add(o)}return t}function fl(e){let t=e.trim();if(!t)return null;let n=t.toLowerCase();if(n.startsWith("tg:")||n.startsWith("telegram:")){let r=Fe(t);return r?{kind:"tg",id:r}:null}let o=te(t);return o?{kind:"wa",normalized:o}:null}function Nd(e,t){let n=te(t);return n?e.has(n):!1}var Ad,Id,Od,dt=Yt(()=>{"use strict";Ad=/^(\d+)(?::\d+)?@s\.whatsapp\.net$/i,Id=/^(\d+)@c\.us$/i,Od=/^(\d+)@lid$/i});import $s from"node:fs";function Ob(e){return typeof e.mediaSendFiles=="boolean"?e.mediaSendFiles:typeof e.pullAutoSend=="boolean"?e.pullAutoSend:T.mediaSendFiles}function Lb(e){return typeof e.mediaUrlAutoDl=="boolean"?e.mediaUrlAutoDl:typeof e.pullUrlAutoDetect=="boolean"?e.pullUrlAutoDetect:T.mediaUrlAutoDl}function Nb(e){return typeof e.mediaInstallFromChat=="boolean"?e.mediaInstallFromChat:typeof e.pullInstallFromChat=="boolean"?e.pullInstallFromChat:T.mediaInstallFromChat}function _b(e){return typeof e.mediaOutputDir=="string"?e.mediaOutputDir.trim().slice(0,4096):typeof e.pullOutputDir=="string"?e.pullOutputDir.trim().slice(0,4096):T.mediaOutputDir}function Fb(e){let t=typeof e.mediaMaxBytes=="number"?e.mediaMaxBytes:typeof e.pullMaxBytes=="number"?e.pullMaxBytes:T.mediaMaxBytes;return!Number.isFinite(t)||t<0?T.mediaMaxBytes:t===0?0:Math.min(2e9,Math.floor(t))}function Db(e){let t=typeof e.mediaWhisperModel=="string"&&e.mediaWhisperModel.trim().length>0?e.mediaWhisperModel:typeof e.pullWhisperModel=="string"&&e.pullWhisperModel.trim().length>0?e.pullWhisperModel:T.mediaWhisperModel;return String(t).trim().slice(0,64)}function Wb(e){let t=typeof e.mediaWhisperDevice=="string"&&e.mediaWhisperDevice.trim().length>0?e.mediaWhisperDevice:T.mediaWhisperDevice,n=String(t).trim().toLowerCase().slice(0,16);return n==="cpu"||n==="cuda"||n==="auto"?n:T.mediaWhisperDevice}function Ub(e){let t=typeof e.mediaTranscribeTimeoutMs=="number"?e.mediaTranscribeTimeoutMs:T.mediaTranscribeTimeoutMs;return!Number.isFinite(t)||t<=0?T.mediaTranscribeTimeoutMs:Math.min(9e5,Math.max(6e4,Math.floor(t)))}function Bb(e){let t=typeof e.mediaTranscribeEngine=="string"&&e.mediaTranscribeEngine.trim().length>0?e.mediaTranscribeEngine:T.mediaTranscribeEngine,n=String(t).trim().toLowerCase().slice(0,32);return n==="whisper"||n==="transformers"||n==="auto"?n:T.mediaTranscribeEngine}function jb(e){return typeof e.mediaTranscribeFallback=="boolean"?e.mediaTranscribeFallback:T.mediaTranscribeFallback}function Hb(e){return e==="downloads"||e==="omnishData"||e==="sessionCwd"||e==="processCwd"||e==="fixed"?e:T.fileReceiveRootMode}function Jb(e){return e==="primary"?"primary":"secondary"}function Gb(e){if(!e||typeof e!="object")return{};let t={};for(let[n,o]of Object.entries(e)){if(typeof o!="string")continue;let r=o.trim();if(!r)continue;let s=n.trim(),i=s.toLowerCase();if(i.startsWith("wa:")){let l=te(s.slice(3));l&&(t[`wa:${l}`]=r.slice(0,64));continue}if(i.startsWith("tg:")||i.startsWith("telegram:")){let l=Fe(s);l&&(t[`tg:${l}`]=r.slice(0,64));continue}let a=te(s);a&&(t[`wa:${a}`]=r.slice(0,64))}return t}function xr(e){let t=typeof e.appsFlushMs=="number"&&e.appsFlushMs>=0?e.appsFlushMs:T.appsFlushMs,n=e.gatewayMode==="telegram"||e.gatewayMode==="both"||e.gatewayMode==="whatsapp"?e.gatewayMode:T.gatewayMode,o=typeof e.telegramBotToken=="string"?e.telegramBotToken:T.telegramBotToken,r=Array.isArray(e.telegramAllowFrom)?[...new Set(e.telegramAllowFrom.map(s=>Fe(String(s))).filter(s=>!!s))].sort():T.telegramAllowFrom;return{...T,...e,gatewayMode:n,telegramBotToken:o,telegramAllowFrom:r,allowFrom:Array.isArray(e.allowFrom)?Ts(e.allowFrom.map(String)).filter(s=>s!=="*"):T.allowFrom,commandPrefix:(()=>{let s=typeof e.commandPrefix=="string"&&e.commandPrefix.length>0?e.commandPrefix:T.commandPrefix;return s==="! "?"!":s})(),syncTimeoutMs:typeof e.syncTimeoutMs=="number"&&e.syncTimeoutMs>0?e.syncTimeoutMs:T.syncTimeoutMs,syncMaxBytes:typeof e.syncMaxBytes=="number"&&e.syncMaxBytes>0?e.syncMaxBytes:T.syncMaxBytes,jobLogTailLines:typeof e.jobLogTailLines=="number"&&e.jobLogTailLines>0?e.jobLogTailLines:T.jobLogTailLines,shell:typeof e.shell=="string"&&e.shell.length>0?e.shell:T.shell,appsCols:typeof e.appsCols=="number"&&e.appsCols>0&&e.appsCols<=500?Math.floor(e.appsCols):T.appsCols,appsRows:typeof e.appsRows=="number"&&e.appsRows>0&&e.appsRows<=200?Math.floor(e.appsRows):T.appsRows,appsFlushMs:t,appsMinIntervalMs:typeof e.appsMinIntervalMs=="number"&&e.appsMinIntervalMs>=0?e.appsMinIntervalMs:T.appsMinIntervalMs,appsMaxFlushBytes:typeof e.appsMaxFlushBytes=="number"&&e.appsMaxFlushBytes>256?Math.floor(e.appsMaxFlushBytes):T.appsMaxFlushBytes,appsMaxSessions:typeof e.appsMaxSessions=="number"&&e.appsMaxSessions>0?Math.min(50,Math.floor(e.appsMaxSessions)):T.appsMaxSessions,appsMaxSessionsTotal:typeof e.appsMaxSessionsTotal=="number"&&e.appsMaxSessionsTotal>0?Math.min(200,Math.floor(e.appsMaxSessionsTotal)):T.appsMaxSessionsTotal,appsMaxWaChars:typeof e.appsMaxWaChars=="number"&&e.appsMaxWaChars>256?Math.floor(e.appsMaxWaChars):T.appsMaxWaChars,appsLogTailLines:typeof e.appsLogTailLines=="number"&&e.appsLogTailLines>0?Math.min(500,Math.floor(e.appsLogTailLines)):T.appsLogTailLines,appsSubmitDelayMs:typeof e.appsSubmitDelayMs=="number"&&e.appsSubmitDelayMs>=0?Math.min(500,Math.floor(e.appsSubmitDelayMs)):T.appsSubmitDelayMs,appsClearInput:typeof e.appsClearInput=="boolean"?e.appsClearInput:T.appsClearInput,appsClearInputDelayMs:typeof e.appsClearInputDelayMs=="number"&&e.appsClearInputDelayMs>=0?Math.min(200,Math.floor(e.appsClearInputDelayMs)):T.appsClearInputDelayMs,appsClearInputSequence:typeof e.appsClearInputSequence=="string"&&e.appsClearInputSequence.length>0?e.appsClearInputSequence.slice(0,200):T.appsClearInputSequence,appsSkipClearOnPasswordPrompt:typeof e.appsSkipClearOnPasswordPrompt=="boolean"?e.appsSkipClearOnPasswordPrompt:T.appsSkipClearOnPasswordPrompt,appsPasswordPromptHint:typeof e.appsPasswordPromptHint=="boolean"?e.appsPasswordPromptHint:T.appsPasswordPromptHint,fileSendMaxBytes:typeof e.fileSendMaxBytes=="number"&&e.fileSendMaxBytes>=0?e.fileSendMaxBytes===0?0:Math.min(2e9,Math.floor(e.fileSendMaxBytes)):T.fileSendMaxBytes,fileReceiveMaxBytes:typeof e.fileReceiveMaxBytes=="number"&&e.fileReceiveMaxBytes>=0?e.fileReceiveMaxBytes===0?0:Math.min(2e9,Math.floor(e.fileReceiveMaxBytes)):T.fileReceiveMaxBytes,fileInboxSubdir:typeof e.fileInboxSubdir=="string"&&e.fileInboxSubdir.length>0&&e.fileInboxSubdir.replace(/[/\\]/g,"").slice(0,80)||T.fileInboxSubdir,fileReceiveRootMode:Hb(e.fileReceiveRootMode),fileReceiveRootPath:typeof e.fileReceiveRootPath=="string"?e.fileReceiveRootPath.trim().slice(0,4096):T.fileReceiveRootPath,recipesAllowDangerousBuiltins:typeof e.recipesAllowDangerousBuiltins=="boolean"?e.recipesAllowDangerousBuiltins:T.recipesAllowDangerousBuiltins,recipesMaxTaskChars:typeof e.recipesMaxTaskChars=="number"&&e.recipesMaxTaskChars>=0?e.recipesMaxTaskChars===0?0:Math.min(Number.MAX_SAFE_INTEGER,Math.floor(e.recipesMaxTaskChars)):T.recipesMaxTaskChars,recipesMacroDefaultCommand:typeof e.recipesMacroDefaultCommand=="string"&&e.recipesMacroDefaultCommand.trim().length>0?e.recipesMacroDefaultCommand.trim().slice(0,4096):T.recipesMacroDefaultCommand,recipesRunAttach:typeof e.recipesRunAttach=="boolean"?e.recipesRunAttach:T.recipesRunAttach,recipesNotifyEnabled:typeof e.recipesNotifyEnabled=="boolean"?e.recipesNotifyEnabled:T.recipesNotifyEnabled,clusterEnabled:typeof e.clusterEnabled=="boolean"?e.clusterEnabled:T.clusterEnabled,clusterLabel:typeof e.clusterLabel=="string"?e.clusterLabel.trim().slice(0,128):T.clusterLabel,clusterRole:Jb(e.clusterRole),clusterSenderBindings:Gb(e.clusterSenderBindings),serviceInstallFromChat:typeof e.serviceInstallFromChat=="boolean"?e.serviceInstallFromChat:T.serviceInstallFromChat,updateCheckEnabled:typeof e.updateCheckEnabled=="boolean"?e.updateCheckEnabled:T.updateCheckEnabled,updateCheckIntervalMs:typeof e.updateCheckIntervalMs=="number"&&e.updateCheckIntervalMs>0?Math.min(6048e5,Math.max(36e5,Math.floor(e.updateCheckIntervalMs))):T.updateCheckIntervalMs,updateCheckPackageName:typeof e.updateCheckPackageName=="string"&&e.updateCheckPackageName.trim().length>0?e.updateCheckPackageName.trim().slice(0,214):T.updateCheckPackageName,updateInfoUrl:typeof e.updateInfoUrl=="string"?e.updateInfoUrl.trim().slice(0,2048):T.updateInfoUrl,chatLlmFallbackEnabled:typeof e.chatLlmFallbackEnabled=="boolean"?e.chatLlmFallbackEnabled:T.chatLlmFallbackEnabled,chatLlmShellCommand:typeof e.chatLlmShellCommand=="string"?e.chatLlmShellCommand.trim().slice(0,8192):T.chatLlmShellCommand,chatLlmTimeoutMs:typeof e.chatLlmTimeoutMs=="number"&&e.chatLlmTimeoutMs>0?Math.min(9e5,Math.floor(e.chatLlmTimeoutMs)):T.chatLlmTimeoutMs,chatLlmMaxInputChars:typeof e.chatLlmMaxInputChars=="number"&&e.chatLlmMaxInputChars>0?Math.min(5e5,Math.floor(e.chatLlmMaxInputChars)):T.chatLlmMaxInputChars,chatLlmMaxOutputChars:typeof e.chatLlmMaxOutputChars=="number"&&e.chatLlmMaxOutputChars>0?Math.min(2e6,Math.floor(e.chatLlmMaxOutputChars)):T.chatLlmMaxOutputChars,chatLlmNeedsTty:typeof e.chatLlmNeedsTty=="boolean"?e.chatLlmNeedsTty:T.chatLlmNeedsTty,chatLlmWorkDir:typeof e.chatLlmWorkDir=="string"?e.chatLlmWorkDir.trim().slice(0,4096):T.chatLlmWorkDir,chatAgentEnabled:typeof e.chatAgentEnabled=="boolean"?e.chatAgentEnabled:T.chatAgentEnabled,chatAgentCommand:typeof e.chatAgentCommand=="string"?e.chatAgentCommand.trim().slice(0,8192):T.chatAgentCommand,chatAgentPerPeer:typeof e.chatAgentPerPeer=="boolean"?e.chatAgentPerPeer:T.chatAgentPerPeer,chatAgentMaxQueue:typeof e.chatAgentMaxQueue=="number"&&e.chatAgentMaxQueue>0?Math.min(1e4,Math.floor(e.chatAgentMaxQueue)):T.chatAgentMaxQueue,tunnelEnabled:typeof e.tunnelEnabled=="boolean"?e.tunnelEnabled:T.tunnelEnabled,tunnelRelayUrl:typeof e.tunnelRelayUrl=="string"&&e.tunnelRelayUrl.trim().length>0?e.tunnelRelayUrl.trim().slice(0,2048):T.tunnelRelayUrl,tunnelMaxActive:typeof e.tunnelMaxActive=="number"&&e.tunnelMaxActive>0?Math.min(50,Math.floor(e.tunnelMaxActive)):T.tunnelMaxActive,platformToken:typeof e.platformToken=="string"?e.platformToken.trim():T.platformToken,platformDeviceId:typeof e.platformDeviceId=="string"?e.platformDeviceId.trim().slice(0,128):T.platformDeviceId,webhookEnabled:typeof e.webhookEnabled=="boolean"?e.webhookEnabled:T.webhookEnabled,webhookPort:typeof e.webhookPort=="number"&&e.webhookPort>=0?Math.min(65535,Math.floor(e.webhookPort)):T.webhookPort,webhookHost:typeof e.webhookHost=="string"&&e.webhookHost.trim().length>0?e.webhookHost.trim():T.webhookHost,webhookToken:typeof e.webhookToken=="string"?e.webhookToken.trim():T.webhookToken,watchEnabled:typeof e.watchEnabled=="boolean"?e.watchEnabled:T.watchEnabled,watchDebounceMs:typeof e.watchDebounceMs=="number"&&Number.isFinite(e.watchDebounceMs)?Math.max(500,Math.min(6e4,Math.floor(e.watchDebounceMs))):T.watchDebounceMs,watchMaxEventsPerMinute:typeof e.watchMaxEventsPerMinute=="number"&&Number.isFinite(e.watchMaxEventsPerMinute)?Math.max(1,Math.min(120,Math.floor(e.watchMaxEventsPerMinute))):T.watchMaxEventsPerMinute,watchAutoRestore:typeof e.watchAutoRestore=="boolean"?e.watchAutoRestore:T.watchAutoRestore,boardCoordinatorEnabled:typeof e.boardCoordinatorEnabled=="boolean"?e.boardCoordinatorEnabled:T.boardCoordinatorEnabled,boardCoordinatorIntervalMs:typeof e.boardCoordinatorIntervalMs=="number"&&Number.isFinite(e.boardCoordinatorIntervalMs)?Math.max(2e3,Math.min(12e4,Math.floor(e.boardCoordinatorIntervalMs))):T.boardCoordinatorIntervalMs,boardMaxRework:typeof e.boardMaxRework=="number"&&Number.isFinite(e.boardMaxRework)?Math.max(0,Math.min(10,Math.floor(e.boardMaxRework))):T.boardMaxRework,boardNotifyProgress:e.boardNotifyProgress==="all"||e.boardNotifyProgress==="milestones"||e.boardNotifyProgress==="none"?e.boardNotifyProgress:T.boardNotifyProgress,boardNotifyJobStatus:typeof e.boardNotifyJobStatus=="boolean"?e.boardNotifyJobStatus:T.boardNotifyJobStatus,boardCoordinatorAgentEnabled:typeof e.boardCoordinatorAgentEnabled=="boolean"?e.boardCoordinatorAgentEnabled:T.boardCoordinatorAgentEnabled,boardCoordinatorAgentCommand:typeof e.boardCoordinatorAgentCommand=="string"?e.boardCoordinatorAgentCommand.trim():T.boardCoordinatorAgentCommand,boardCoordinatorFinetuneEnabled:typeof e.boardCoordinatorFinetuneEnabled=="boolean"?e.boardCoordinatorFinetuneEnabled:T.boardCoordinatorFinetuneEnabled,boardSkillResearchEnabled:typeof e.boardSkillResearchEnabled=="boolean"?e.boardSkillResearchEnabled:T.boardSkillResearchEnabled,boardCoordinatorFileSendEnabled:typeof e.boardCoordinatorFileSendEnabled=="boolean"?e.boardCoordinatorFileSendEnabled:T.boardCoordinatorFileSendEnabled,boardFeedbackReminderEnabled:typeof e.boardFeedbackReminderEnabled=="boolean"?e.boardFeedbackReminderEnabled:T.boardFeedbackReminderEnabled,boardPrimaryAgent:typeof e.boardPrimaryAgent=="string"?e.boardPrimaryAgent.trim().toLowerCase():T.boardPrimaryAgent,boardAgentPermissionMode:e.boardAgentPermissionMode==="safe"||e.boardAgentPermissionMode==="yolo"||e.boardAgentPermissionMode==="dangerous"?e.boardAgentPermissionMode:T.boardAgentPermissionMode,boardApplyPrimaryAgentToRoles:Array.isArray(e.boardApplyPrimaryAgentToRoles)?e.boardApplyPrimaryAgentToRoles.filter(s=>typeof s=="string"&&s.trim().length>0).map(s=>s.trim().toLowerCase()):T.boardApplyPrimaryAgentToRoles,mediaSendFiles:Ob(e),mediaUrlAutoDl:Lb(e),mediaInstallFromChat:Nb(e),mediaOutputDir:_b(e),mediaMaxBytes:Fb(e),mediaWhisperModel:Db(e),mediaWhisperDevice:Wb(e),mediaTranscribeTimeoutMs:Ub(e),mediaTranscribeEngine:Bb(e),mediaTranscribeFallback:jb(e),progressUpdates:typeof e.progressUpdates=="boolean"?e.progressUpdates:T.progressUpdates,pullYtDlpPath:typeof e.pullYtDlpPath=="string"?e.pullYtDlpPath.trim().slice(0,4096):T.pullYtDlpPath,pullFfmpegPath:typeof e.pullFfmpegPath=="string"?e.pullFfmpegPath.trim().slice(0,4096):T.pullFfmpegPath,pullWhisperPath:typeof e.pullWhisperPath=="string"?e.pullWhisperPath.trim().slice(0,4096):T.pullWhisperPath}}function $(e){let t=v(),n=xr({...t,...e});return Ve(n),n}function v(){if(se(),!$s.existsSync(U)){let e=xr({});return Ve(e),e}try{let e=$s.readFileSync(U,"utf8"),t=JSON.parse(e);return xr(t)}catch{return xr({})}}function Ve(e){se();let t=xr(e);$s.writeFileSync(U,JSON.stringify(t,null,2)+`
3
+ `,{mode:384})}function Ms(e){let t=fl(e);if(!t)throw new Error("Invalid entry. Use +E164 (WhatsApp) or tg:<user_id> (Telegram).");let n=v();if(t.kind==="wa"){if(t.normalized==="*")throw new Error("Wildcards are not allowed.");let o=new Set(n.allowFrom);o.add(t.normalized),n.allowFrom=[...o].sort()}else{let o=new Set(n.telegramAllowFrom);o.add(t.id),n.telegramAllowFrom=[...o].sort()}return Ve(n),n}function As(e){let t=fl(e);if(!t)throw new Error("Invalid entry. Use +E164 (WhatsApp) or tg:<user_id> (Telegram).");let n=v();return t.kind==="wa"?n.allowFrom=n.allowFrom.filter(o=>o!==t.normalized):n.telegramAllowFrom=n.telegramAllowFrom.filter(o=>Fe(o)!==t.id),Ve(n),n}function Ie(e){let t=typeof process.env.TELEGRAM_BOT_TOKEN=="string"?process.env.TELEGRAM_BOT_TOKEN.trim():"";return t||(e.telegramBotToken??"").trim()}function Ot(e){let t=e.trim();return/^\d{6,}:[A-Za-z0-9_-]{20,}$/.test(t)}function fn(e){let t=v();return t.telegramBotToken=e.trim(),Ve(t),v()}function vo(e){let t=e.trim().toLowerCase();return t==="whatsapp"||t==="wa"||t==="w"?"whatsapp":t==="telegram"||t==="tg"||t==="t"?"telegram":t==="both"||t==="all"||t==="b"?"both":null}function Is(e){let t=v();return t.gatewayMode=e,Ve(t),v()}function Os(e){let t=v();return t.clusterEnabled=e,Ve(t),v()}function St(){try{return $s.readdirSync(le).length>0}catch{return!1}}var T,ue=Yt(()=>{"use strict";j();dt();T={gatewayMode:"whatsapp",telegramBotToken:"",telegramAllowFrom:[],allowFrom:[],commandPrefix:"!",syncTimeoutMs:3e4,syncMaxBytes:32768,jobLogTailLines:80,shell:"/bin/bash",appsCols:120,appsRows:40,appsFlushMs:300,appsMinIntervalMs:800,appsMaxFlushBytes:8192,appsMaxSessions:5,appsMaxSessionsTotal:20,appsMaxWaChars:3500,appsLogTailLines:80,appsSubmitDelayMs:50,appsClearInput:!0,appsClearInputDelayMs:20,appsClearInputSequence:"^A,^K",appsSkipClearOnPasswordPrompt:!0,appsPasswordPromptHint:!0,fileSendMaxBytes:0,fileReceiveMaxBytes:0,fileInboxSubdir:"inbox",fileReceiveRootMode:"downloads",fileReceiveRootPath:"",recipesAllowDangerousBuiltins:!1,recipesMaxTaskChars:0,recipesMacroDefaultCommand:'claude -p "$OMNISH_TASK"',recipesRunAttach:!1,recipesNotifyEnabled:!0,clusterEnabled:!1,clusterLabel:"",clusterRole:"secondary",clusterSenderBindings:{},serviceInstallFromChat:!1,updateCheckEnabled:!1,updateCheckIntervalMs:864e5,updateCheckPackageName:"omnish",updateInfoUrl:"",chatLlmFallbackEnabled:!1,chatLlmShellCommand:"",chatLlmTimeoutMs:12e4,chatLlmMaxInputChars:16e3,chatLlmMaxOutputChars:24e3,chatLlmNeedsTty:!1,chatLlmWorkDir:"",chatAgentEnabled:!1,chatAgentCommand:"",chatAgentPerPeer:!0,chatAgentMaxQueue:64,tunnelEnabled:!1,tunnelRelayUrl:"https://tunnel.omnish.dev",platformToken:"",platformDeviceId:"",tunnelMaxActive:5,webhookEnabled:!1,webhookPort:0,webhookHost:"127.0.0.1",webhookToken:"",watchEnabled:!1,watchDebounceMs:2e3,watchMaxEventsPerMinute:30,watchAutoRestore:!0,boardCoordinatorEnabled:!0,boardCoordinatorIntervalMs:5e3,boardMaxRework:3,boardNotifyProgress:"milestones",boardNotifyJobStatus:!0,boardCoordinatorAgentEnabled:!1,boardCoordinatorAgentCommand:"",boardCoordinatorFinetuneEnabled:!1,boardSkillResearchEnabled:!0,boardCoordinatorFileSendEnabled:!0,boardFeedbackReminderEnabled:!1,boardPrimaryAgent:"",boardAgentPermissionMode:"safe",boardApplyPrimaryAgentToRoles:["researcher","implementer","reviewer"],mediaSendFiles:!0,mediaUrlAutoDl:!0,mediaInstallFromChat:!1,mediaOutputDir:"",mediaMaxBytes:0,mediaWhisperModel:"small",mediaWhisperDevice:"auto",mediaTranscribeTimeoutMs:6e5,mediaTranscribeEngine:"whisper",mediaTranscribeFallback:!0,progressUpdates:!0,pullYtDlpPath:"",pullFfmpegPath:"",pullWhisperPath:""}});import qb from"pino";function xo(){return process.env.OMNISH_VERBOSE==="1"||process.env.WHATSVERBOSE==="1"}function _d(){return xo()?"info":"silent"}function hl(e){e?process.env.OMNISH_VERBOSE="1":delete process.env.OMNISH_VERBOSE,E.level=_d()}function Fd(){return E.child({module:"baileys"})}var E,ye=Yt(()=>{"use strict";E=qb({level:_d(),base:{app:"omnish"}})});import $l from"node:fs";function Zt(){try{let e=$l.readFileSync(bs,"utf8"),t=JSON.parse(e);return!t||typeof t.token!="string"||!t.token.trim()?null:{token:t.token.trim(),relayUrl:typeof t.relayUrl=="string"?t.relayUrl.trim():void 0}}catch{return null}}function Ft(e){se(),$l.writeFileSync(bs,JSON.stringify({token:e.token.trim(),...e.relayUrl?{relayUrl:e.relayUrl.trim()}:{}},null,2)+`
4
+ `,{mode:384})}function Ks(){try{$l.unlinkSync(bs)}catch{}}function Jk(){return process.env.OMNISH_TOKEN?.trim()||process.env.OMNISH_TUNNEL_TOKEN?.trim()||process.env.OMNISH_DEVICE_TOKEN?.trim()||""}function Dt(){let e=Jk();if(e)return e;let t=v().platformToken.trim();return t||(Zt()?.token??"")}function Ml(e){let t=process.env.OMNISH_PLATFORM_URL?.trim()||process.env.OMNISH_COMM_LAYER_URL?.trim()||process.env.OMNISH_TUNNEL_RELAY?.trim();if(t)return t.replace(/\/$/,"");let n=v().tunnelRelayUrl.trim();if(n)return n.replace(/\/$/,"");let o=Zt()?.relayUrl?.trim();return o?o.replace(/\/$/,""):e.replace(/\/$/,"")}function vt(e){return Ml(e)}var Kn=Yt(()=>{"use strict";ue();j()});var Le,Yn=Yt(()=>{"use strict";Le="https://tunnel.omnish.dev"});var Bm={};xd(Bm,{chunksToSrt:()=>Um,isNodeTranscribeInstalled:()=>Tn,mapWhisperModelToXenova:()=>Wm,transcribeWithTransformers:()=>CS,transformersPackagePath:()=>Zn});import{spawnSync as bS}from"node:child_process";import Rn from"node:fs";import Ut from"node:path";import{pathToFileURL as kS}from"node:url";function Zn(){return Ut.join(So,"node_modules","@xenova","transformers")}function Tn(){try{let e=Ut.join(Zn(),"package.json");return Rn.existsSync(e)}catch{return!1}}function Wm(e){let t=e.trim().toLowerCase();return t in rc?rc[t]:t.startsWith("xenova/")?e.trim():rc.small}async function SS(){if(!Tn())throw new Error("Transformers.js not installed. Run: omnish pull install --transcribe-node");let e=JSON.parse(Rn.readFileSync(Ut.join(Zn(),"package.json"),"utf8")),t=e.exports?.["."],n=(typeof t=="string"?t:null)??e.main??"./dist/transformers.node.mjs",o=Ut.join(Zn(),n.replace(/^\.\//,""));return await import(kS(o).href)}function Dm(e){let t=Math.max(0,Math.round(e*1e3)),n=Math.floor(t/36e5),o=Math.floor(t%36e5/6e4),r=Math.floor(t%6e4/1e3),s=t%1e3;return`${String(n).padStart(2,"0")}:${String(o).padStart(2,"0")}:${String(r).padStart(2,"0")},${String(s).padStart(3,"0")}`}function Um(e){let t=[],n=1;for(let o of e){let r=o.text?.trim(),s=o.timestamp;!r||!s||s.length<2||(t.push(String(n++)),t.push(`${Dm(s[0])} --> ${Dm(s[1])}`),t.push(r),t.push(""))}return t.join(`
5
+ `).trimEnd()}function vS(e,t,n,o){let r=Ut.join(n,"_transformers_audio.wav");return bS(e,["-y","-i",t,"-vn","-ar","16000","-ac","1",r],{encoding:"utf8",timeout:o,windowsHide:!0}).status===0&&Rn.existsSync(r)?r:null}async function xS(e,t,n,o){let r=Ut.extname(t).toLowerCase();if([".wav",".mp3",".m4a",".flac",".ogg",".opus"].includes(r))return t;if(!e.ffmpeg)throw new Error("ffmpeg required to extract audio for Transformers.js (omnish pull install)");let s=vS(e.ffmpeg,t,n,o);if(!s)throw new Error("ffmpeg audio extract failed");return s}async function CS(e){let{cfg:t,tools:n,inputPath:o,outputDir:r,timeoutMs:s}=e;if(!Rn.existsSync(o))throw new Error(`File not found: ${o}`);let i=await SS();i.env.cacheDir=Sr,i.env.allowLocalModels=!0;let a=Wm(t.mediaWhisperModel.trim()||"small"),l=await xS(n,o,r,s),u=await(await i.pipeline("automatic-speech-recognition",a,{quantized:!0}))(l,{return_timestamps:!0,chunk_length_s:30,stride_length_s:5}),d=(u.text??"").trim(),m=u.chunks??[],f=Ut.basename(o,Ut.extname(o)),h=Ut.join(r,`${f}.txt`),g=Ut.join(r,`${f}.srt`);Rn.writeFileSync(h,d,"utf8"),m.length>0?Rn.writeFileSync(g,Um(m),"utf8"):d&&Rn.writeFileSync(g,`1
6
6
  00:00:00,000 --> 00:00:30,000
7
7
  ${d}
8
- `,"utf8");let y=[h,g].filter(k=>Rn.existsSync(k));if(y.length===0)throw new Error("Transformers.js produced no transcript files");return{files:y,log:`transformers.js (${a})`}}var oc,ri=Yt(()=>{"use strict";j();oc={tiny:"Xenova/whisper-tiny",base:"Xenova/whisper-base",small:"Xenova/whisper-small",medium:"Xenova/whisper-medium",large:"Xenova/whisper-large-v2","large-v2":"Xenova/whisper-large-v2","large-v3":"Xenova/whisper-large-v2"}});function Wn(e){return(e??"").trim()}function jR(){return Wn(process.env.OMNISH_PLATFORM_URL)||Wn(process.env.OMNISH_COMM_LAYER_URL)||Wn(process.env.OMNISH_TUNNEL_RELAY)}function HR(){return Wn(process.env.OMNISH_TOKEN)||Wn(process.env.OMNISH_DEVICE_TOKEN)||Wn(process.env.OMNISH_TUNNEL_TOKEN)}function mu(){return Dt()}function ir(){return HR()?"env":v().platformToken.trim()?"config":Zt()?.token?"file":"default"}function os(){let e=v();return $l(e.tunnelRelayUrl||Le)}function ar(){return jR()?"env":v().tunnelRelayUrl.trim()?"config":Zt()?.relayUrl?.trim()?"file":"default"}function fu(){let e=Wn(process.env.OMNISH_DEVICE_ID);if(e)return e;let t=v().platformDeviceId.trim();if(t)return t}function hu(){return Wn(process.env.OMNISH_DEVICE_ID)?"env":v().platformDeviceId.trim()?"config":"default"}function ge(){let e=os(),t=mu();if(!e||!t)return null;let n=fu();return{platformUrl:e.replace(/\/$/,""),token:t,deviceId:n}}var qt=Yt(()=>{"use strict";ue();Kn();Yn()});var Ug={};kd(Ug,{fetchPlatformAccount:()=>po,getAttachedConfig:()=>bu,getAttachedPlatformSnapshot:()=>qR,loadConfigForSendtoBroadcast:()=>ku,mergeAttachedPlatformPolicy:()=>wu,parsePlatformMeResponse:()=>Dg,setAttachedPlatformSnapshot:()=>gu,setPlatformDefaultDevice:()=>Su,snapshotFromRegisteredAccount:()=>Wg,syncAttachedPlatformPolicy:()=>Sa,updatePlatformAllowlists:()=>ka});function yu(e){let t=e.replace(/\/$/,"");return/^https?:\/\//i.test(t)?t:`http://${t}`}function JR(e){return e==="telegram"||e==="both"||e==="whatsapp"?e:"whatsapp"}function GR(e){if(!e||typeof e!="object")return{};let t={};for(let[n,o]of Object.entries(e)){if(!o||typeof o!="object")continue;let r=o,s=typeof r.status=="string"?r.status:"idle";t[n]={status:s,linked:r.linked===!0||s==="linked",...typeof r.tokenConfigured=="boolean"?{tokenConfigured:r.tokenConfigured}:{}}}return t}function Dg(e){let t=e.routing,n=t&&typeof t=="object"?t:null,o=Array.isArray(n?.onlineDeviceIds)?n.onlineDeviceIds.map(String):[],r=n?.defaultDeviceId!=null?String(n.defaultDeviceId):e.defaultDeviceId!=null?String(e.defaultDeviceId):null;return{allowFrom:Array.isArray(e.allowFrom)?e.allowFrom.map(String):[],telegramAllowFrom:Array.isArray(e.telegramAllowFrom)?e.telegramAllowFrom.map(String):[],gatewayMode:JR(e.gatewayMode),connectors:GR(e.connectors),defaultDeviceId:r,routing:{defaultDeviceId:r,onlineDeviceIds:o,onlineCount:typeof n?.onlineCount=="number"?n.onlineCount:o.length}}}function Wg(e,t=[],n=[]){let o=e.defaultDeviceId??null;return{allowFrom:t,telegramAllowFrom:n,gatewayMode:e.gatewayMode,connectors:e.connectors,defaultDeviceId:o,routing:{defaultDeviceId:o,onlineDeviceIds:[],onlineCount:0}}}function wu(e,t){return{...e,allowFrom:[...t.allowFrom],telegramAllowFrom:[...t.telegramAllowFrom],gatewayMode:t.gatewayMode}}function gu(e){ba=e}function qR(){return ba}function bu(){let e=v();return ba?wu(e,ba):e}async function ku(){let e=v(),t=ge();if(!t)return e;try{return wu(e,await po(t))}catch(n){return P.warn({err:String(n)},"sendto: could not fetch platform allowlists; using local config.json"),e}}async function po(e){let t=await fetch(`${yu(e.platformUrl)}/v1/me`,{headers:{Authorization:`Bearer ${e.token}`}});if(!t.ok)throw new Error(`Platform GET /v1/me failed: HTTP ${t.status}`);let n=await t.json();return Dg(n)}async function ka(e,t){let n=await fetch(`${yu(e.platformUrl)}/v1/me/allowlists`,{method:"PUT",headers:{"content-type":"application/json",Authorization:`Bearer ${e.token}`},body:JSON.stringify(t)}),o=await n.json().catch(()=>({}));if(!n.ok)throw new Error(o.error||`Platform PUT /v1/me/allowlists failed: HTTP ${n.status}`);return{allowFrom:Array.isArray(o.allowFrom)?o.allowFrom.map(String):[],telegramAllowFrom:Array.isArray(o.telegramAllowFrom)?o.telegramAllowFrom.map(String):[]}}async function Su(e,t){let n=await fetch(`${yu(e.platformUrl)}/v1/me/default-device`,{method:"PUT",headers:{"content-type":"application/json",Authorization:`Bearer ${e.token}`},body:JSON.stringify({deviceId:t})});if(!n.ok){let o=await n.json().catch(()=>({}));throw new Error(o.error||`Platform PUT /v1/me/default-device failed: HTTP ${n.status}`)}}async function Sa(e,t){try{let n=await po(e);return gu(n),P.info({gatewayMode:n.gatewayMode,waLinked:n.connectors.whatsapp?.linked===!0,tgLinked:n.connectors.telegram?.linked===!0,allowFromCount:n.allowFrom.length,telegramAllowFromCount:n.telegramAllowFrom.length},"attached platform policy loaded"),n}catch(n){if(t){let o=Wg(t);return gu(o),P.warn({err:String(n)},"platform GET /v1/me failed; using register ack for gatewayMode/connectors only (allowlists from local config until /v1/me works)"),o}return P.warn({err:String(n)},"platform GET /v1/me failed; attached inbound uses local config.json allowlists"),null}}var ba,rs=Yt(()=>{"use strict";ue();be();qt();ba=null});ue();import TP from"node:dns";import EP from"node:crypto";import Hn from"node:fs";import wd from"node:path";import hb from"node:os";ue();be();import Pk from"node:os";import $k from"node:fs";j();import Nd from"node:crypto";import Cr from"node:fs";import hl from"node:os";import Co from"node:path";var Hb=/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,31}$/;function yl(e){let t=e.trim().toLowerCase();return Hb.test(t)?{ok:!0,name:t}:{ok:!1,error:"Name must be alphanumeric with _ or -, max 32 chars."}}function at(e,t){let n=e.trim();return n===""||n==="."?Co.resolve(t):n==="~"?hl.homedir():n.startsWith("~/")||n.startsWith("~\\")?Co.join(hl.homedir(),n.slice(2)):Co.isAbsolute(n)?n:Co.resolve(t,n)}function Ls(e){return Co.join(hl.homedir(),"Cowork",e)}function Jb(e){if(!e||typeof e!="object")return null;let t=e,n=typeof t.id=="string"&&t.id.length>=4?t.id.slice(0,32):"",o=typeof t.name=="string"?t.name.trim().toLowerCase():"",r=typeof t.ownerPeerKey=="string"?t.ownerPeerKey:"",s=typeof t.command=="string"?t.command:"";if(!n||!o||!r||!s)return null;let i=typeof t.cwd=="string"?t.cwd:"",a=typeof t.outputDir=="string"&&t.outputDir.trim()?t.outputDir:Ls(o),l=typeof t.enabled=="boolean"?t.enabled:!0,c=typeof t.notify=="string"?t.notify.toLowerCase():"self",u=c==="wa"||c==="whatsapp"?"wa":c==="tg"||c==="telegram"?"tg":c==="all"?"all":c==="none"?"none":"self",d={kind:"ondemand"};if(t.schedule&&typeof t.schedule=="object"){let b=t.schedule,x=typeof b.kind=="string"?b.kind:"";if(x==="ondemand")d={kind:"ondemand"};else if(x==="daily"){let C=Number(b.hour),M=Number(b.minute);Number.isFinite(C)&&Number.isFinite(M)&&(d={kind:"daily",hour:C,minute:M})}else if(x==="weekdays"){let C=Number(b.hour),M=Number(b.minute);Number.isFinite(C)&&Number.isFinite(M)&&(d={kind:"weekdays",hour:C,minute:M})}else if(x==="hourly"){let C=Number(b.minute);Number.isFinite(C)&&Number.isInteger(C)&&C>=0&&C<=59&&(d={kind:"hourly",minute:C})}else if(x==="weekly"){let C=Number(b.hour),M=Number(b.minute),R=Number(b.weekday);Number.isFinite(C)&&Number.isFinite(M)&&Number.isInteger(R)&&(d={kind:"weekly",weekday:R,hour:C,minute:M})}else if(x==="heartbeat"){let C=Number(b.intervalMs),M=Number(b.graceMs);Number.isFinite(C)&&C>0&&Number.isFinite(M)&&M>0&&(d={kind:"heartbeat",intervalMs:C,graceMs:M})}}let m=null;typeof t.lastCompletedSlotMs=="number"&&Number.isFinite(t.lastCompletedSlotMs)&&(m=t.lastCompletedSlotMs);let f=typeof t.createdAtMs=="number"&&Number.isFinite(t.createdAtMs)?t.createdAtMs:Date.now(),h=typeof t.attachLog=="boolean"?t.attachLog:!1,g=Array.isArray(t.attachFiles)?t.attachFiles.filter(b=>typeof b=="string"&&b.trim().length>0):[],y=typeof t.notifyWhen=="string"?t.notifyWhen.toLowerCase():"always";return{id:n,name:o,ownerPeerKey:r,command:s,cwd:i,outputDir:a,schedule:d,enabled:l,notify:u,notifyWhen:y==="failure"?"failure":y==="state-change"?"state-change":"always",attachLog:h,attachFiles:g,lastCompletedSlotMs:m,createdAtMs:f}}function Oe(){try{let e=Cr.readFileSync(ul,"utf8"),t=JSON.parse(e);if(!t||!Array.isArray(t.tasks))return[];let n=[];for(let o of t.tasks){let r=Jb(o);r&&n.push(r)}return n}catch{return[]}}function Je(e){D(It);let t={tasks:e},n=Co.join(It,`.tasks.${process.pid}.${Nd.randomBytes(4).toString("hex")}.tmp`);Cr.writeFileSync(n,JSON.stringify(t,null,2)+`
9
- `,{mode:384}),Cr.renameSync(n,ul)}function pt(e,t,n){let o=t.trim().toLowerCase();return e.find(r=>r.name===o&&r.ownerPeerKey===n)}function Rr(){return Nd.randomBytes(4).toString("hex")}function _d(e){D(It),Cr.writeFileSync(dl,JSON.stringify(e,null,2)+`
10
- `,{mode:384})}function Fd(e){let t=gl();t.push(e),_d(t)}function gl(){try{let e=Cr.readFileSync(dl,"utf8"),t=JSON.parse(e);return Array.isArray(t)?t.filter(n=>n&&typeof n=="object"&&typeof n.ownerPeerKey=="string"&&typeof n.name=="string"):[]}catch{return[]}}function Dd(e){if(e<=0)return{batch:[],remainingAfter:gl().length};let t=gl();if(t.length===0)return{batch:[],remainingAfter:0};let n=t.slice(0,e),o=t.slice(e);return _d(o),{batch:n,remainingAfter:o.length}}j();j();import Gb from"better-sqlite3";var Wd=1,Ud=200,Ro=null;function qb(){D(Vt);let e=new Gb(Rs);e.pragma("journal_mode = WAL"),e.exec(`
8
+ `,"utf8");let y=[h,g].filter(b=>Rn.existsSync(b));if(y.length===0)throw new Error("Transformers.js produced no transcript files");return{files:y,log:`transformers.js (${a})`}}var rc,ri=Yt(()=>{"use strict";j();rc={tiny:"Xenova/whisper-tiny",base:"Xenova/whisper-base",small:"Xenova/whisper-small",medium:"Xenova/whisper-medium",large:"Xenova/whisper-large-v2","large-v2":"Xenova/whisper-large-v2","large-v3":"Xenova/whisper-large-v2"}});function Wn(e){return(e??"").trim()}function VR(){return Wn(process.env.OMNISH_PLATFORM_URL)||Wn(process.env.OMNISH_COMM_LAYER_URL)||Wn(process.env.OMNISH_TUNNEL_RELAY)}function QR(){return Wn(process.env.OMNISH_TOKEN)||Wn(process.env.OMNISH_DEVICE_TOKEN)||Wn(process.env.OMNISH_TUNNEL_TOKEN)}function gu(){return Dt()}function ir(){return QR()?"env":v().platformToken.trim()?"config":Zt()?.token?"file":"default"}function os(){let e=v();return Ml(e.tunnelRelayUrl||Le)}function ar(){return VR()?"env":v().tunnelRelayUrl.trim()?"config":Zt()?.relayUrl?.trim()?"file":"default"}function yu(){let e=Wn(process.env.OMNISH_DEVICE_ID);if(e)return e;let t=v().platformDeviceId.trim();if(t)return t}function wu(){return Wn(process.env.OMNISH_DEVICE_ID)?"env":v().platformDeviceId.trim()?"config":"default"}function ge(){let e=os(),t=gu();if(!e||!t)return null;let n=yu();return{platformUrl:e.replace(/\/$/,""),token:t,deviceId:n}}var qt=Yt(()=>{"use strict";ue();Kn();Yn()});var Jg={};xd(Jg,{fetchPlatformAccount:()=>po,getAttachedConfig:()=>vu,getAttachedPlatformSnapshot:()=>eT,loadConfigForSendtoBroadcast:()=>xu,mergeAttachedPlatformPolicy:()=>Su,parsePlatformMeResponse:()=>jg,setAttachedPlatformSnapshot:()=>bu,setPlatformDefaultDevice:()=>Cu,snapshotFromRegisteredAccount:()=>Hg,syncAttachedPlatformPolicy:()=>va,updatePlatformAllowlists:()=>Sa});function ku(e){let t=e.replace(/\/$/,"");return/^https?:\/\//i.test(t)?t:`http://${t}`}function XR(e){return e==="telegram"||e==="both"||e==="whatsapp"?e:"whatsapp"}function ZR(e){if(!e||typeof e!="object")return{};let t={};for(let[n,o]of Object.entries(e)){if(!o||typeof o!="object")continue;let r=o,s=typeof r.status=="string"?r.status:"idle";t[n]={status:s,linked:r.linked===!0||s==="linked",...typeof r.tokenConfigured=="boolean"?{tokenConfigured:r.tokenConfigured}:{}}}return t}function jg(e){let t=e.routing,n=t&&typeof t=="object"?t:null,o=Array.isArray(n?.onlineDeviceIds)?n.onlineDeviceIds.map(String):[],r=n?.defaultDeviceId!=null?String(n.defaultDeviceId):e.defaultDeviceId!=null?String(e.defaultDeviceId):null;return{allowFrom:Array.isArray(e.allowFrom)?e.allowFrom.map(String):[],telegramAllowFrom:Array.isArray(e.telegramAllowFrom)?e.telegramAllowFrom.map(String):[],gatewayMode:XR(e.gatewayMode),connectors:ZR(e.connectors),defaultDeviceId:r,routing:{defaultDeviceId:r,onlineDeviceIds:o,onlineCount:typeof n?.onlineCount=="number"?n.onlineCount:o.length}}}function Hg(e,t=[],n=[]){let o=e.defaultDeviceId??null;return{allowFrom:t,telegramAllowFrom:n,gatewayMode:e.gatewayMode,connectors:e.connectors,defaultDeviceId:o,routing:{defaultDeviceId:o,onlineDeviceIds:[],onlineCount:0}}}function Su(e,t){return{...e,allowFrom:[...t.allowFrom],telegramAllowFrom:[...t.telegramAllowFrom],gatewayMode:t.gatewayMode}}function bu(e){ka=e}function eT(){return ka}function vu(){let e=v();return ka?Su(e,ka):e}async function xu(){let e=v(),t=ge();if(!t)return e;try{return Su(e,await po(t))}catch(n){return E.warn({err:String(n)},"sendto: could not fetch platform allowlists; using local config.json"),e}}async function po(e){let t=await fetch(`${ku(e.platformUrl)}/v1/me`,{headers:{Authorization:`Bearer ${e.token}`}});if(!t.ok)throw new Error(`Platform GET /v1/me failed: HTTP ${t.status}`);let n=await t.json();return jg(n)}async function Sa(e,t){let n=await fetch(`${ku(e.platformUrl)}/v1/me/allowlists`,{method:"PUT",headers:{"content-type":"application/json",Authorization:`Bearer ${e.token}`},body:JSON.stringify(t)}),o=await n.json().catch(()=>({}));if(!n.ok)throw new Error(o.error||`Platform PUT /v1/me/allowlists failed: HTTP ${n.status}`);return{allowFrom:Array.isArray(o.allowFrom)?o.allowFrom.map(String):[],telegramAllowFrom:Array.isArray(o.telegramAllowFrom)?o.telegramAllowFrom.map(String):[]}}async function Cu(e,t){let n=await fetch(`${ku(e.platformUrl)}/v1/me/default-device`,{method:"PUT",headers:{"content-type":"application/json",Authorization:`Bearer ${e.token}`},body:JSON.stringify({deviceId:t})});if(!n.ok){let o=await n.json().catch(()=>({}));throw new Error(o.error||`Platform PUT /v1/me/default-device failed: HTTP ${n.status}`)}}async function va(e,t){try{let n=await po(e);return bu(n),E.info({gatewayMode:n.gatewayMode,waLinked:n.connectors.whatsapp?.linked===!0,tgLinked:n.connectors.telegram?.linked===!0,allowFromCount:n.allowFrom.length,telegramAllowFromCount:n.telegramAllowFrom.length},"attached platform policy loaded"),n}catch(n){if(t){let o=Hg(t);return bu(o),E.warn({err:String(n)},"platform GET /v1/me failed; using register ack for gatewayMode/connectors only (allowlists from local config until /v1/me works)"),o}return E.warn({err:String(n)},"platform GET /v1/me failed; attached inbound uses local config.json allowlists"),null}}var ka,rs=Yt(()=>{"use strict";ue();ye();qt();ka=null});ue();import LP from"node:dns";import NP from"node:crypto";import Hn from"node:fs";import Sd from"node:path";import bb from"node:os";ue();ye();import Ik from"node:os";import Ok from"node:fs";j();import Dd from"node:crypto";import Cr from"node:fs";import gl from"node:os";import Co from"node:path";var zb=/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,31}$/;function wl(e){let t=e.trim().toLowerCase();return zb.test(t)?{ok:!0,name:t}:{ok:!1,error:"Name must be alphanumeric with _ or -, max 32 chars."}}function at(e,t){let n=e.trim();return n===""||n==="."?Co.resolve(t):n==="~"?gl.homedir():n.startsWith("~/")||n.startsWith("~\\")?Co.join(gl.homedir(),n.slice(2)):Co.isAbsolute(n)?n:Co.resolve(t,n)}function Ls(e){return Co.join(gl.homedir(),"Cowork",e)}function Kb(e){if(!e||typeof e!="object")return null;let t=e,n=typeof t.id=="string"&&t.id.length>=4?t.id.slice(0,32):"",o=typeof t.name=="string"?t.name.trim().toLowerCase():"",r=typeof t.ownerPeerKey=="string"?t.ownerPeerKey:"",s=typeof t.command=="string"?t.command:"";if(!n||!o||!r||!s)return null;let i=typeof t.cwd=="string"?t.cwd:"",a=typeof t.outputDir=="string"&&t.outputDir.trim()?t.outputDir:Ls(o),l=typeof t.enabled=="boolean"?t.enabled:!0,c=typeof t.notify=="string"?t.notify.toLowerCase():"self",u=c==="wa"||c==="whatsapp"?"wa":c==="tg"||c==="telegram"?"tg":c==="all"?"all":c==="none"?"none":"self",d={kind:"ondemand"};if(t.schedule&&typeof t.schedule=="object"){let k=t.schedule,x=typeof k.kind=="string"?k.kind:"";if(x==="ondemand")d={kind:"ondemand"};else if(x==="daily"){let C=Number(k.hour),M=Number(k.minute);Number.isFinite(C)&&Number.isFinite(M)&&(d={kind:"daily",hour:C,minute:M})}else if(x==="weekdays"){let C=Number(k.hour),M=Number(k.minute);Number.isFinite(C)&&Number.isFinite(M)&&(d={kind:"weekdays",hour:C,minute:M})}else if(x==="hourly"){let C=Number(k.minute);Number.isFinite(C)&&Number.isInteger(C)&&C>=0&&C<=59&&(d={kind:"hourly",minute:C})}else if(x==="weekly"){let C=Number(k.hour),M=Number(k.minute),R=Number(k.weekday);Number.isFinite(C)&&Number.isFinite(M)&&Number.isInteger(R)&&(d={kind:"weekly",weekday:R,hour:C,minute:M})}else if(x==="heartbeat"){let C=Number(k.intervalMs),M=Number(k.graceMs);Number.isFinite(C)&&C>0&&Number.isFinite(M)&&M>0&&(d={kind:"heartbeat",intervalMs:C,graceMs:M})}}let m=null;typeof t.lastCompletedSlotMs=="number"&&Number.isFinite(t.lastCompletedSlotMs)&&(m=t.lastCompletedSlotMs);let f=typeof t.createdAtMs=="number"&&Number.isFinite(t.createdAtMs)?t.createdAtMs:Date.now(),h=typeof t.attachLog=="boolean"?t.attachLog:!1,g=Array.isArray(t.attachFiles)?t.attachFiles.filter(k=>typeof k=="string"&&k.trim().length>0):[],y=typeof t.notifyWhen=="string"?t.notifyWhen.toLowerCase():"always";return{id:n,name:o,ownerPeerKey:r,command:s,cwd:i,outputDir:a,schedule:d,enabled:l,notify:u,notifyWhen:y==="failure"?"failure":y==="state-change"?"state-change":"always",attachLog:h,attachFiles:g,lastCompletedSlotMs:m,createdAtMs:f}}function Oe(){try{let e=Cr.readFileSync(dl,"utf8"),t=JSON.parse(e);if(!t||!Array.isArray(t.tasks))return[];let n=[];for(let o of t.tasks){let r=Kb(o);r&&n.push(r)}return n}catch{return[]}}function Je(e){D(It);let t={tasks:e},n=Co.join(It,`.tasks.${process.pid}.${Dd.randomBytes(4).toString("hex")}.tmp`);Cr.writeFileSync(n,JSON.stringify(t,null,2)+`
9
+ `,{mode:384}),Cr.renameSync(n,dl)}function pt(e,t,n){let o=t.trim().toLowerCase();return e.find(r=>r.name===o&&r.ownerPeerKey===n)}function Rr(){return Dd.randomBytes(4).toString("hex")}function Wd(e){D(It),Cr.writeFileSync(pl,JSON.stringify(e,null,2)+`
10
+ `,{mode:384})}function Ud(e){let t=yl();t.push(e),Wd(t)}function yl(){try{let e=Cr.readFileSync(pl,"utf8"),t=JSON.parse(e);return Array.isArray(t)?t.filter(n=>n&&typeof n=="object"&&typeof n.ownerPeerKey=="string"&&typeof n.name=="string"):[]}catch{return[]}}function Bd(e){if(e<=0)return{batch:[],remainingAfter:yl().length};let t=yl();if(t.length===0)return{batch:[],remainingAfter:0};let n=t.slice(0,e),o=t.slice(e);return Wd(o),{batch:n,remainingAfter:o.length}}j();j();import Yb from"better-sqlite3";var jd=1,Hd=200,Ro=null;function Vb(){D(Vt);let e=new Yb(Rs);e.pragma("journal_mode = WAL"),e.exec(`
11
11
  CREATE TABLE IF NOT EXISTS watch_meta (
12
12
  key TEXT PRIMARY KEY,
13
13
  value TEXT NOT NULL
@@ -26,30 +26,30 @@ ${d}
26
26
  state_key TEXT NOT NULL,
27
27
  updated_at_ms INTEGER NOT NULL
28
28
  );
29
- `);let t=e.prepare("SELECT value FROM watch_meta WHERE key = 'schema_version'").get();return(t?Number(t.value):0)<Wd&&e.prepare("INSERT OR REPLACE INTO watch_meta (key, value) VALUES ('schema_version', ?)").run(String(Wd)),e}function Tr(){return Ro||(Ro=qb()),Ro}function Ns(){Tr()}function Bd(){if(Ro){try{Ro.close()}catch{}Ro=null}}function jd(e){let t=Tr();t.prepare("INSERT INTO watch_recent (rule_id, rule_name, kind, summary, ts_ms) VALUES (?, ?, ?, ?, ?)").run(e.ruleId,e.ruleName,e.kind,e.summary.slice(0,2e3),e.tsMs);let n=t.prepare("SELECT COUNT(*) AS c FROM watch_recent").get();n.c>Ud&&t.prepare(`DELETE FROM watch_recent WHERE id IN (
29
+ `);let t=e.prepare("SELECT value FROM watch_meta WHERE key = 'schema_version'").get();return(t?Number(t.value):0)<jd&&e.prepare("INSERT OR REPLACE INTO watch_meta (key, value) VALUES ('schema_version', ?)").run(String(jd)),e}function Tr(){return Ro||(Ro=Vb()),Ro}function Ns(){Tr()}function Jd(){if(Ro){try{Ro.close()}catch{}Ro=null}}function Gd(e){let t=Tr();t.prepare("INSERT INTO watch_recent (rule_id, rule_name, kind, summary, ts_ms) VALUES (?, ?, ?, ?, ?)").run(e.ruleId,e.ruleName,e.kind,e.summary.slice(0,2e3),e.tsMs);let n=t.prepare("SELECT COUNT(*) AS c FROM watch_recent").get();n.c>Hd&&t.prepare(`DELETE FROM watch_recent WHERE id IN (
30
30
  SELECT id FROM watch_recent ORDER BY ts_ms ASC LIMIT ?
31
- )`).run(n.c-Ud)}function Hd(e,t){let n=Tr(),o=Math.min(Math.max(1,e),50),r=n.prepare("SELECT rule_id, rule_name, kind, summary, ts_ms FROM watch_recent ORDER BY ts_ms DESC LIMIT ?").all(o*3),s=[];for(let i of r)if(!(t&&!t.has(i.rule_id))&&(s.push({ruleId:i.rule_id,ruleName:i.rule_name,kind:i.kind,stateKey:"",summary:i.summary,tsMs:i.ts_ms}),s.length>=o))break;return s}function Jd(e){return Tr().prepare("SELECT state_key FROM watch_rule_state WHERE rule_id = ?").get(e)?.state_key??null}function Gd(e,t,n){Tr().prepare("INSERT OR REPLACE INTO watch_rule_state (rule_id, state_key, updated_at_ms) VALUES (?, ?, ?)").run(e,t,n)}import Vd from"node:path";import Qt from"node:path";function zb(e,t){let n=Qt.normalize(e);for(let o of t){let r=Qt.normalize(o);if(n===r||n.startsWith(r+Qt.sep))return!0}return!1}function Kb(e,t,n){let o=Qt.relative(t,e);if(o.startsWith("..")||Qt.isAbsolute(o))return!1;let r=o.split(Qt.sep).join("/"),s=n.replace(/\\/g,"/").replace(/^\//,"");if(s.includes("**")){let i=s.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*\*/g,"___GLOBSTAR___").replace(/\*/g,"[^/]*").replace(/___GLOBSTAR___/g,".*");try{if(new RegExp(`^${i}$`).test(r))return!0;if(s.startsWith("**/")){let l=s.slice(3);if(l&&!l.includes("*"))return r.split("/").includes(l)}return!1}catch{return!1}}if(s.includes("*")){let i=s.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,"[^/]*");try{return new RegExp(`^${i}$`).test(r)}catch{return!1}}return r===s||r.endsWith("/"+s)||r.includes("/"+s+"/")}function _s(e,t){if(t.excludePaths.length&&zb(e,t.excludePaths))return!0;let n=t.path;if(n&&t.excludeGlobs.length){for(let o of t.excludeGlobs)if(Kb(e,n,o))return!0}return!1}function qd(e){let t=[];for(let n of e.excludeGlobs){let o=n.replace(/\\/g,"/");o.startsWith("**/")?t.push(o):o.includes("*")?t.push(`**/${o}`):t.push(`**/${o}/**`)}for(let n of e.excludePaths)if(e.path)try{let o=Qt.relative(e.path,n);!o.startsWith("..")&&!Qt.isAbsolute(o)&&t.push(o.split(Qt.sep).join("/")+"/**")}catch{}return t}dt();function To(e,t,n){if(e==="none")return[];let o=new Set;if((e==="self"||e==="all")&&o.add(t),e==="wa"||e==="all")for(let r of n.allowFrom){let s=te(String(r));s&&o.add(`wa:${mn(s)}`)}if(e==="tg"||e==="all")for(let r of n.telegramAllowFrom){let s=Fe(String(r));s&&o.add(`tg:${s}`)}return[...o]}function zd(e,t,n){return To(e,t,n)}be();j();import Yb from"node:crypto";import wl from"node:fs";var Vb=/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,31}$/,Qb=1,bl=20;function Fs(e){let t=e.trim().toLowerCase();return Vb.test(t)?{ok:!0,name:t}:{ok:!1,error:"Name must be alphanumeric with _ or -, max 32 chars."}}function Ds(){return Yb.randomBytes(8).toString("hex")}function Xb(e){if(!Array.isArray(e)||e.length===0)return["create","delete","rename"];let t=new Set(["create","delete","rename","update"]),n=[];for(let o of e)typeof o=="string"&&t.has(o)&&n.push(o);return n.length?n:["create","delete","rename"]}function Zb(e){if(!e||typeof e!="object")return null;let t=e,n=typeof t.id=="string"&&t.id.length>=4?t.id.slice(0,32):"",o=typeof t.name=="string"?t.name.trim().toLowerCase():"",r=typeof t.ownerPeerKey=="string"?t.ownerPeerKey:"",s=typeof t.kind=="string"?t.kind:"",i=s==="fs"||s==="pkg"||s==="svc"?s:null;if(!n||!o||!r||!i)return null;let a=typeof t.notify=="string"?t.notify.toLowerCase():"self",l=a==="wa"||a==="whatsapp"?"wa":a==="tg"||a==="telegram"?"tg":a==="all"?"all":a==="none"?"none":"self",c=typeof t.notifyWhen=="string"?t.notifyWhen:"always",u=c==="failure"||c==="state-change"?c:"always",d=Array.isArray(t.units)?t.units.filter(h=>typeof h=="string"&&h.trim().length>0).map(h=>h.trim()):[],m=Array.isArray(t.excludePaths)?t.excludePaths.filter(h=>typeof h=="string"&&h.trim().length>0):[],f=Array.isArray(t.excludeGlobs)?t.excludeGlobs.filter(h=>typeof h=="string"&&h.trim().length>0):[];return{id:n,name:o,ownerPeerKey:r,kind:i,enabled:typeof t.enabled=="boolean"?t.enabled:!0,paused:typeof t.paused=="boolean"?t.paused:!1,notify:l,notifyWhen:u,path:typeof t.path=="string"?t.path:"",events:Xb(t.events),units:d,excludePaths:m,excludeGlobs:f,adapterStatus:typeof t.adapterStatus=="string"?t.adapterStatus:"",createdAtMs:typeof t.createdAtMs=="number"&&Number.isFinite(t.createdAtMs)?t.createdAtMs:Date.now()}}function ek(e){let t=JSON.parse(e);return!t||!Array.isArray(t.rules)?[]:t.rules.map(Zb).filter(n=>n!==null)}function tk(e){let t=[...e].sort((s,i)=>s.createdAtMs-i.createdAtMs),n=new Set,o=!1,r=[];for(let s of t){if(!n.has(s.name)){n.add(s.name),r.push(s);continue}let i=s.id.slice(0,8),a=`${s.name}-${i}`;a.length>32&&(a=`${s.name.slice(0,32-i.length-1)}-${i}`);let l=0;for(;n.has(a);){l+=1;let c=String(l);a=`${s.name.slice(0,Math.max(1,32-i.length-c.length-1))}-${i}${c}`}n.add(a),P.warn({oldName:s.name,newName:a,id:s.id},"watch: renamed duplicate rule for device-wide namespace"),r.push({...s,name:a}),o=!0}return{rules:r,changed:o}}function Ge(){try{let e=wl.readFileSync(kr,"utf8"),t=ek(e),{rules:n,changed:o}=tk(t);return o&&Lt(n),n}catch{return[]}}function nk(e){let t=0,n=0,o=0;for(let r of e)r.enabled?r.paused?n+=1:t+=1:o+=1;return{total:e.length,active:t,paused:n,disabled:o}}function Er(){let e=Ge();return{rules:e,summary:nk(e)}}function Lt(e){D(Vt);let n=`${JSON.stringify({version:Qb,rules:e},null,2)}
32
- `,o=`${kr}.tmp`;wl.writeFileSync(o,n,{mode:384}),wl.renameSync(o,kr)}function kl(){return kr}function qn(e,t){let n=t.trim().toLowerCase();return e.find(o=>o.name===n)}function Kd(e,t){return e.find(n=>n.id===t)}function Eo(e,t){let n=e.findIndex(o=>o.id===t.id);if(n>=0){let o=[...e];return o[n]=t,o}return[...e,t]}function Yd(e,t){let n=t.trim().toLowerCase();return e.filter(o=>o.name!==n)}import ok from"node:os";import Nt from"node:path";var rk=new Set([".env","id_rsa","id_ed25519","id_ecdsa","known_hosts","credentials.json","secrets.json",".netrc","shadow","passwd"]);function mt(e){let t=Nt.normalize(e),n=t.toLowerCase(),o=ok.homedir().toLowerCase();if(n.includes(`${Nt.sep}.ssh${Nt.sep}`)||n.endsWith(`${Nt.sep}.ssh`)||n.includes(`${Nt.sep}browser${Nt.sep}`)&&n.includes("profile")||o&&(n===`${o}/.gnupg`||n.startsWith(`${o}/.gnupg${Nt.sep}`))||n.includes(`${Nt.sep}keychains${Nt.sep}`))return!0;let r=Nt.basename(t);return!!(rk.has(r)||r.startsWith(".env.")||r.endsWith(".pem")||r.endsWith(".key"))}var sk=["node_modules",".git",".svn",".hg","__pycache__",".cache",".next","dist","build"],ik=[".tmp",".swp",".swx","~",".part"];function Qd(e,t){let n=new Map,o=new Map;function r(u,d){let m=u.split(Vd.sep);for(let h of m)if(sk.includes(h))return!0;let f=Vd.basename(u);for(let h of ik)if(f.endsWith(h))return!0;return!!_s(u,d)}function s(u){let d=Kd(Ge(),u);return!d||!d.enabled||d.paused?null:d}function i(u){let d=Date.now(),m=o.get(u);return!m||d-m.windowStart>=6e4?(o.set(u,{windowStart:d,count:1}),!1):m.count>=t.maxEventsPerMinute?!0:(m.count+=1,!1)}async function a(u,d){let m=e.getConfig();if(!m.watchEnabled)return;let f=s(u.id);if(!f||d.kind==="fs"&&d.meta?.path&&(mt(d.meta.path)||r(d.meta.path,f)))return;if(jd(d),(f.notifyWhen??"always")==="state-change"){if(Jd(f.id)===d.stateKey)return;Gd(f.id,d.stateKey,d.tsMs)}let g=zd(f.notify,f.ownerPeerKey,m);if(g.length===0)return;let y=`[watch:${f.name}] ${d.summary}`;await Promise.all(g.map(k=>e.sendToPeer(k,y).catch(()=>{})))}function l(u){let d=n.get(u);if(!d)return;n.delete(u);let m=s(d.ruleId);m&&(i(d.ruleId)||a(m,d.event))}function c(u){for(let[d,m]of n)m.ruleId===u&&(clearTimeout(m.timer),n.delete(d))}return{ingest(u,d){if(!u.enabled||u.paused||d.meta?.path&&(mt(d.meta.path)||r(d.meta.path,u)))return;let m=`${u.id}:${d.stateKey||d.summary}`,f=n.get(m);f&&clearTimeout(f.timer);let h=setTimeout(()=>l(m),t.debounceMs);h.unref?.(),n.set(m,{timer:h,event:d,ruleId:u.id})},cancelForRule:c,dispose(){for(let u of n.values())clearTimeout(u.timer);n.clear(),o.clear()}}}function Xd(e,t){let n=e.toLowerCase();return!!(n==="create"&&t.has("create")||n==="delete"&&t.has("delete")||(n==="update"||n==="rename")&&(t.has("update")||t.has("rename")))}be();import Zd from"node:fs";import ak from"node:path";import{subscribe as lk}from"@parcel/watcher";var ck=["**/node_modules/**","**/.git/**","**/.svn/**","**/.hg/**","**/__pycache__/**","**/.cache/**"];function uk(e){return e==="create"?"create":e==="delete"?"delete":e==="update"?"update":e}function dk(e,t){try{let n=Zd.statSync(t),o=n.isFile()&&n.size<1024*1024?` (${n.size<1024?`${n.size} B`:`${(n.size/1024).toFixed(1)} KB`})`:"";return`fs: ${e} ${t}${o}`}catch{return`fs: ${e} ${t}`}}async function ep(e,t){let n=e.path;if(!n||!Zd.existsSync(n))return{stop:async()=>{},status:()=>"error: path missing or not found"};if(mt(n))return{stop:async()=>{},status:()=>"error: sensitive path denied"};let o=new Set(e.events.map(i=>i.toLowerCase())),r="ok",s=null;try{s=await lk(n,(i,a)=>{if(i){r=`error: ${i.message}`,P.warn({rule:e.name,err:i.message},"watch fs adapter");return}for(let l of a){let c=uk(l.type);if(!Xd(c,o))continue;let u=ak.resolve(l.path);if(!u.startsWith(n)||mt(u)||_s(u,e))continue;let d=dk(c,u);t({ruleId:e.id,ruleName:e.name,kind:"fs",stateKey:`${c}:${u}`,summary:d,tsMs:Date.now(),meta:{path:u,type:c}})}},{ignore:[...ck,...qd(e)]})}catch(i){r=`error: ${String(i)}`,P.warn({rule:e.name,err:String(i)},"watch fs subscribe failed")}return{stop:async()=>{s&&(await s.unsubscribe().catch(()=>{}),s=null)},status:()=>r}}import bk from"node:os";be();import hn from"node:fs";function pk(e,t,n){let o=n?.pollMs??2e3,r=0,s=!1,i=null,a=null,l="starting";function c(){if(!s)try{let u=hn.statSync(e);if(u.size<r&&(r=0),u.size<=r)return;let d=hn.openSync(e,"r");try{let m=u.size-r,f=Buffer.alloc(m);hn.readSync(d,f,0,m,r),r=u.size;let h=f.toString("utf8");for(let g of h.split(`
33
- `)){let y=g.trim();y&&t(y)}}finally{hn.closeSync(d)}l="ok"}catch(u){l=`error: ${String(u)}`}}try{hn.existsSync(e)&&(r=hn.statSync(e).size),i=hn.watch(e,()=>c()),i.on("error",()=>{i?.close(),i=null}),l="ok (watch)"}catch{l="ok (poll)",a=setInterval(c,o),a.unref?.(),c()}return{stop(){s=!0,i?.close(),a&&clearInterval(a)},status:()=>l}}function Ws(e,t){for(let n of e)try{if(hn.existsSync(n))return pk(n,t)}catch(o){P.debug({path:n,err:String(o)},"watch log-tail skip path")}return null}var mk=["/var/log/install.log"];function fk(e){let t=e.toLowerCase();return t.includes("installed")||t.includes("upgraded")||t.includes("removed")?e.length>240?`pkg: ${e.slice(0,240)}\u2026`:`pkg: ${e}`:null}function tp(e,t){return Ws(mk,n=>{let o=fk(n);o&&t({ruleId:e.id,ruleName:e.name,kind:"pkg",stateKey:o.slice(0,200),summary:o,tsMs:Date.now()})})}var hk=["/var/log/dpkg.log","/var/log/apt/history.log"];function gk(e){if(e.includes("status installed")){let t=e.match(/install\s+(\S+):/);if(t)return`pkg: installed ${t[1]} (dpkg)`}if(e.includes("status removed")||e.includes("remove ")){let t=e.match(/remove\s+(\S+):/);if(t)return`pkg: removed ${t[1]} (dpkg)`}return null}function yk(e){return e.startsWith("Install:")||e.startsWith("Upgrade:")?`pkg: ${e.slice(0,120)} (apt)`:e.startsWith("Remove:")?`pkg: ${e.slice(0,120)} (apt)`:null}function np(e,t){return Ws(hk,n=>{let o=gk(n)??yk(n);o&&t({ruleId:e.id,ruleName:e.name,kind:"pkg",stateKey:o,summary:o,tsMs:Date.now()})})}import{spawn as wk}from"node:child_process";function op(e,t){let n=!1,o="ok",r=0,s=()=>{if(n)return;let a=wk("powershell.exe",["-NoProfile","-NonInteractive","-Command","Get-WinEvent -FilterHashtable @{LogName='Application'; ProviderName=@('MsiInstaller','Windows Installer')} -MaxEvents 5 -ErrorAction SilentlyContinue | Select-Object -Property RecordId,Message | ConvertTo-Json -Compress"],{windowsHide:!0}),l="";a.stdout?.on("data",c=>{l+=String(c)}),a.on("close",c=>{if(n||c!==0||!l.trim()){c!==0&&(o="ok (no events or access denied)"),i();return}try{let u=JSON.parse(l),d=Array.isArray(u)?u:[u];for(let m of d.sort((f,h)=>f.RecordId-h.RecordId)){if(m.RecordId<=r)continue;r=m.RecordId;let f=(m.Message??"").replace(/\s+/g," ").trim().slice(0,300);if(!f)continue;let h=`pkg: ${f}`;t({ruleId:e.id,ruleName:e.name,kind:"pkg",stateKey:`win:${m.RecordId}`,summary:h,tsMs:Date.now()})}o="ok"}catch(u){o=`parse error: ${String(u)}`}i()}),a.on("error",c=>{o=`error: ${String(c)}`,i()})},i=()=>{n||setTimeout(s,3e4).unref?.()};return s(),{stop(){n=!0},status:()=>o}}function rp(e,t){let n=bk.platform();if(n==="win32"){let r=op(e,t);return{stop:()=>r.stop(),status:r.status}}if(n==="darwin"){let r=tp(e,t);return r?{stop:()=>r.stop(),status:r.status}:{stop:()=>{},status:()=>"error: cannot read /var/log/install.log (try sudo or adm group)"}}let o=np(e,t);return o?{stop:()=>o.stop(),status:o.status}:{stop:()=>{},status:()=>"error: cannot read dpkg/apt logs (add user to adm or run gateway with read access)"}}import Ek from"node:os";import{spawnSync as sp}from"node:child_process";var kk=3e4;function Sk(e){let t=new Map;for(let n of e){let o=sp("launchctl",["print",`system/${n}`],{encoding:"utf8",timeout:1e4});if(o.status!==0){let s=process.getuid?.()??501,i=sp("launchctl",["print",`gui/${s}/${n}`],{encoding:"utf8",timeout:1e4});if(i.status!==0){t.set(n,"not-found");continue}let a=(i.stdout??"").includes("state = running")?"running":"stopped";t.set(n,a);continue}let r=(o.stdout??"").includes("state = running")?"running":"stopped";t.set(n,r)}return t}function ip(e,t){let n=e.units,o=!1,r="ok",s=new Map,i=()=>{if(o)return;let a=Sk(n);if(a.size===0)r="error: no launchd labels configured";else{r="ok";for(let[l,c]of a){let u=s.get(l);if(u!==void 0&&u!==c){let d=`svc: ${l} ${u} \u2192 ${c}`;t({ruleId:e.id,ruleName:e.name,kind:"svc",stateKey:`${l}:${c}`,summary:d,tsMs:Date.now(),meta:{unit:l,state:c}})}}s=a}o||setTimeout(i,kk).unref?.()};return i(),{stop(){o=!0},status:()=>r}}import{spawnSync as ap}from"node:child_process";var vk=3e4;function xk(e){let t=new Map;if(e.length===0)return t;let n=["show",...e,"--property=ActiveState,SubState,UnitFileState","--no-pager"],o=ap("systemctl",n,{encoding:"utf8",timeout:15e3});if(o.status!==0)return t;let r="";for(let s of(o.stdout??"").split(`
34
- `)){let i=s.match(/^Unit=(.+)$/);if(i){r=i[1];continue}let a=s.match(/^ActiveState=(.+)$/);a&&r&&t.set(r,a[1].trim())}if(t.size===0)for(let s of e){let i=s.endsWith(".service")?s:`${s}.service`,a=ap("systemctl",["is-active",i],{encoding:"utf8",timeout:5e3}),l=(a.stdout??a.stderr??"unknown").trim();t.set(i,l)}return t}function lp(e,t){let n=e.units.map(a=>a.endsWith(".service")?a:`${a}.service`),o=!1,r="ok",s=new Map,i=()=>{if(o)return;let a=xk(n);if(a.size===0)r="error: systemctl unavailable or units not found";else{r="ok";for(let[l,c]of a){let u=s.get(l);if(u!==void 0&&u!==c){let d=`svc: ${l} ${u} \u2192 ${c}`;t({ruleId:e.id,ruleName:e.name,kind:"svc",stateKey:`${l}:${c}`,summary:d,tsMs:Date.now(),meta:{unit:l,state:c}})}}s=a}o||setTimeout(i,vk).unref?.()};return i(),{stop(){o=!0},status:()=>r}}import{spawnSync as Ck}from"node:child_process";var Rk=3e4;function Tk(e){let t=new Map;for(let n of e){let o=Ck("sc",["query",n],{encoding:"utf8",timeout:1e4,windowsHide:!0});if(o.status!==0){t.set(n,"not-found");continue}let r=o.stdout??"",s="unknown";r.includes("RUNNING")?s="running":r.includes("STOPPED")?s="stopped":r.includes("START_PENDING")?s="start-pending":r.includes("STOP_PENDING")&&(s="stop-pending"),t.set(n,s)}return t}function cp(e,t){let n=e.units,o=!1,r="ok",s=new Map,i=()=>{if(o)return;let a=Tk(n);if(a.size===0)r="error: no service names configured";else{r="ok";for(let[l,c]of a){let u=s.get(l);if(u!==void 0&&u!==c){let d=`svc: ${l} ${u} \u2192 ${c}`;t({ruleId:e.id,ruleName:e.name,kind:"svc",stateKey:`${l}:${c}`,summary:d,tsMs:Date.now(),meta:{unit:l,state:c}})}}s=a}o||setTimeout(i,Rk).unref?.()};return i(),{stop(){o=!0},status:()=>r}}function up(e,t){let n=Ek.platform();return n==="win32"?cp(e,t):n==="darwin"?ip(e,t):lp(e,t)}var ft=null,gn=null;function dp(e){let t=e.getConfig();return Qd(e,{debounceMs:Math.max(500,t.watchDebounceMs??2e3),maxEventsPerMinute:Math.max(1,t.watchMaxEventsPerMinute??30)})}function Mk(e){return e.watchEnabled&&e.watchAutoRestore}var Sl=class{deps;pipeline;runtimes=new Map;stopped=!1;constructor(t){this.deps=t,this.pipeline=dp(t)}onEvent=(t,n)=>{this.pipeline.ingest(t,n)};cancelPendingForRule(t){this.pipeline.cancelForRule(t)}async reload(){if(this.pipeline.dispose(),this.pipeline=dp(this.deps),await this.stopAdapters(),this.stopped||!this.deps.getConfig().watchEnabled)return 0;let n=Ge().filter(o=>o.enabled&&!o.paused);for(let o of n)await this.startRule(o);return this.runtimes.size}async startRule(t){try{let n;if(t.kind==="fs"){let o=at(t.path,Pk.homedir()),r={...t,path:o};if(mt(o)){t.adapterStatus="denied: sensitive path",this.persistRuleStatus(t);return}if(!$k.existsSync(o)){t.adapterStatus="error: path not found",this.persistRuleStatus(t);return}let s=await ep(r,i=>this.onEvent(r,i));n={stop:()=>s.stop(),status:s.status}}else if(t.kind==="pkg")n=rp(t,r=>this.onEvent(t,r));else if(t.kind==="svc"){if(t.units.length===0){t.adapterStatus="error: no units",this.persistRuleStatus(t);return}n=up(t,r=>this.onEvent(t,r))}else return;t.adapterStatus=n.status(),this.persistRuleStatus(t),this.runtimes.set(t.id,{rule:t,adapter:n})}catch(n){t.adapterStatus=`error: ${String(n)}`,this.persistRuleStatus(t),P.warn({rule:t.name,err:String(n)},"watch rule start failed")}}persistRuleStatus(t){let n=Ge(),o=n.findIndex(r=>r.id===t.id);o>=0&&(n[o]={...n[o],adapterStatus:t.adapterStatus},Lt(n))}async stopAdapters(){for(let t of this.runtimes.values())try{await t.adapter.stop()}catch{}this.runtimes.clear()}async stop(){this.stopped=!0,await this.stopAdapters(),this.pipeline.dispose(),Bd()}getStatusLines(){let t=Ge();return t.length===0?["(no watch rules)"]:t.map(n=>{let r=this.runtimes.get(n.id)?.adapter.status()??n.adapterStatus,s=n.enabled?n.paused?"paused":"on":"disabled",i=n.kind==="fs"?n.path:n.kind==="svc"?n.units.join(","):"system",a=n.kind==="fs"&&(n.excludePaths.length||n.excludeGlobs.length)?` excludes:${n.excludePaths.length+n.excludeGlobs.length}`:"";return`${n.name} [${n.kind}] ${s} notify=${n.notify} when=${n.notifyWhen} \u2014 ${i}${a} \u2014 ${r}`})}getRunningCount(){return this.runtimes.size}isRuleRunning(t){return this.runtimes.has(t)}};function pp(){return ft}function vl(e){ft?.cancelPendingForRule(e)}function Ak(){D(Vt),Ns()}function xl(){gn&&(ft||(ft=new Sl(gn)),ft.reload())}function Us(e){gn=e,Ak();let t=e.getConfig(),{summary:n}=Er();return n.total>0&&P.info({rules:n.total,active:n.active,paused:n.paused,disabled:n.disabled},"watch rules loaded from disk"),Mk(t)&&xl(),()=>{ft?.stop(),ft=null,gn=null}}async function mp(){return gn?gn.getConfig().watchEnabled?(xl(),ft?.getRunningCount()??0):(ft?.stop(),ft=null,-1):-1}function ot(){if(!gn)return;if(!gn.getConfig().watchEnabled){ft?.stop(),ft=null;return}xl()}j();ue();j();import Lk from"node:os";import Hs from"node:path";j();import Cl from"node:fs";import hp from"node:os";import yn from"node:path";var gp=yn.join(W,"sessions.json"),Po=new Map;function yp(){return{cwd:yn.resolve(hp.homedir())}}function Ik(e){return e.startsWith("wa:")||e.startsWith("tg:")?e:`wa:${e}`}function Ok(){try{let e=Cl.readFileSync(gp,"utf8"),t=JSON.parse(e);for(let[n,o]of Object.entries(t)){if(!o||typeof o!="object")continue;let r=Ik(n),i={cwd:typeof o.cwd=="string"&&o.cwd.length>0?yn.resolve(o.cwd):yp().cwd};o.fileReceiveRoot==="sessionCwd"&&(i.fileReceiveRoot="sessionCwd"),Po.set(r,i)}}catch{}}function wp(){D(W);let e={};for(let[t,n]of Po){let o={cwd:n.cwd};n.fileReceiveRoot==="sessionCwd"&&(o.fileReceiveRoot="sessionCwd"),e[t]=o}Cl.writeFileSync(gp,JSON.stringify(e,null,2)+`
35
- `,{mode:384})}var fp=!1;function Rl(){fp||(Ok(),fp=!0)}function oe(e){Rl();let t=Po.get(e);return t||(t=yp(),Po.set(e,t)),t}function Bs(e,t){Rl();let n=yn.resolve(t),o=oe(e);o.cwd=n,Po.set(e,o),wp()}function js(e){return oe(e).fileReceiveRoot==="sessionCwd"?"sessionCwd":"default"}function Tl(e,t){Rl();let n=oe(e);t==="default"?delete n.fileReceiveRoot:n.fileReceiveRoot="sessionCwd",Po.set(e,n),wp()}function bp(e){let t=e.trim();if(/[&|;\n]/.test(t)||!t.startsWith("cd"))return null;if(t==="cd")return{kind:"home"};if(t.length>2&&t[2]!==" "&&t[2]!==" ")return null;let n=t.slice(2).trimStart();return n?{kind:"path",value:n}:{kind:"home"}}function kp(e,t){if(t.kind==="home")return yn.resolve(hp.homedir());let n=t.value.replace(/^['"]|['"]$/g,"");return yn.isAbsolute(n)?yn.normalize(n):yn.resolve(e,n)}function Sp(e){try{return Cl.statSync(e).isDirectory()?{ok:!0}:{ok:!1,error:`Not a directory: ${e}`}}catch(t){return{ok:!1,error:String(t)}}}var Nk="Omnish";function Js(){return Hs.join(Lk.homedir(),"Downloads",Nk)}function wn(e,t){let n=oe(t);if(n.fileReceiveRoot==="sessionCwd")return n.cwd;switch(e.fileReceiveRootMode){case"downloads":return Js();case"omnishData":return Hs.join(W,e.fileInboxSubdir);case"sessionCwd":return n.cwd;case"processCwd":return process.cwd();case"fixed":{let o=(e.fileReceiveRootPath??"").trim();if(!o)throw new Error('fileReceiveRootPath is required when fileReceiveRootMode is "fixed".');if(!Hs.isAbsolute(o))throw new Error('fileReceiveRootPath must be an absolute path when fileReceiveRootMode is "fixed".');return Hs.resolve(o)}default:return Js()}}j();import en from"node:fs";import xp from"node:path";import vn from"node:process";var zn="\x1B";function _k(e){return e.replace(/\u001B\[[\d;]*m/g,"")}function Gs(e){return _k(e).length}function El(e,t,n,o,r=2){let s=Math.max(0,t-Gs(n));return`${e}${n}${" ".repeat(s)}${" ".repeat(r)}${o}`}function _t(e,t,n,o){if(n.length===0)return[];let r=n.map(i=>o(i.left)),s=Math.max(...r.map(Gs));return n.map((i,a)=>El(t,s,r[a],S(e,i.right)))}var bn={primary:"#b4ff24",foreground:"#eef2f4",muted:"#8a9199",border:"#2a3139",error:"#ef4444",warn:"#a0e614",warnAlt:"#8cce04"};function Fk(e){let t=e.replace(/^#/,"");return t.length!==6||!/^[0-9a-fA-F]+$/.test(t)?null:{r:parseInt(t.slice(0,2),16),g:parseInt(t.slice(2,4),16),b:parseInt(t.slice(4,6),16)}}function kn(e){let t=Fk(e);return t?`${zn}[38;2;${t.r};${t.g};${t.b}m`:""}function qs(e){if(process.env.NO_COLOR!==void 0&&process.env.NO_COLOR!=="")return!1;let t=process.env.FORCE_COLOR;return t==="1"||t==="true"?!0:t==="0"||t==="false"?!1:e.isTTY===!0}function Xt(e,t,n){return!t||!qs(e)?n:`${t}${n}${zn}[0m`}function K(e,t){return Xt(e,`${zn}[1m`,t)}function Dk(e,t){return qs(e)?`${kn(bn.foreground)}${zn}[1m${t}${zn}[0m`:t}function Q(e,t){return Xt(e,`${zn}[2m`,t)}function Re(e,t){return Xt(e,`${kn(bn.primary)}${zn}[1m`,t)}function he(e,t){return Xt(e,kn(bn.primary),t)}function S(e,t){return Xt(e,kn(bn.foreground),t)}function w(e,t){return Xt(e,kn(bn.muted),t)}function Wk(e,t){return Xt(e,kn(bn.border),t)}function zs(e,t){return Xt(e,kn(bn.error),t)}function ke(e,t){return Xt(e,kn(bn.warn),t)}function Sn(e,t=60,n="\u2500"){let o=n.repeat(Math.max(1,t));return Wk(e,o)}function H(e,t){return qs(e)?`${`${w(e,"[")}${he(e,"omnish")}${w(e,"]")}`} ${t}`:`[omnish] ${t}`}function E(e,t){return qs(e)?`${`${zs(e,"[omnish]")}`} ${t}`:`[omnish] ${t}`}function vp(e,t){let n=[];for(let o of e)switch(o.kind){case"title":n.push("",Re(t,o.text),"");break;case"sub":n.push("",Dk(t,o.text),"");break;case"gap":n.push("");break;case"p":n.push(S(t,o.text));break;case"bullet":n.push(`${w(t,"\u2022")} ${S(t,o.text)}`);break}return n.join(`
36
- `).replace(/^\n+/,"").trimEnd()}ue();j();Kn();Yn();function Ys(e){return(e&4)!==0}function Ml(e){return(e&2)!==0}var Cp={error:3,warn:2,info:1};function Rp(e,t){let n=Cp[t];return e.filter(o=>Cp[o.severity]>=n)}function tn(e,t={}){let n=[],o=t.gatewayMode??e.gatewayMode,r=o==="whatsapp"||o==="both",s=o==="telegram"||o==="both";if(e.allowFrom.includes("*")&&n.push({severity:"error",code:"allow-wildcard",message:'allowFrom contains "*" \u2014 this must never be used; remove it from config immediately.',detail:`Edit ${U} and delete wildcard entries.`,fixHint:`Edit ${U} and remove any "*" entries from allowFrom.`}),r&&e.allowFrom.length===0&&n.push({severity:"warn",code:"allow-wa-empty",message:"allowFrom is empty \u2014 no WhatsApp identity can run commands.",detail:"Add your number: omnish allow +<E164>",fixHint:"omnish allow +<your_E164_number>"}),s&&e.telegramAllowFrom.length===0&&n.push({severity:"warn",code:"allow-tg-empty",message:"telegramAllowFrom is empty \u2014 no Telegram user can run commands.",detail:"Add your user id: omnish allow tg:<id>",fixHint:"omnish allow tg:<your_telegram_user_id>"}),e.recipesAllowDangerousBuiltins&&n.push({severity:"warn",code:"recipes-dangerous",message:"recipesAllowDangerousBuiltins is enabled \u2014 built-in recipe helpers may add permissive Claude Code flags.",fixHint:`Set recipesAllowDangerousBuiltins to false in ${U} unless you trust every recipe.`}),e.serviceInstallFromChat&&n.push({severity:"warn",code:"service-install-from-chat",message:"serviceInstallFromChat is enabled \u2014 allowlisted users can run /service install and write user-level systemd or LaunchAgent units.",fixHint:`Set serviceInstallFromChat to false in ${U} unless you trust every allowlisted identity.`}),e.mediaInstallFromChat&&n.push({severity:"warn",code:"media-install-from-chat",message:"mediaInstallFromChat is enabled \u2014 allowlisted users can run /dl install and download binaries into the omnish data directory.",fixHint:`Set mediaInstallFromChat to false in ${U} unless you trust every allowlisted identity.`}),!xp.isAbsolute(e.shell))n.push({severity:"warn",code:"shell-not-absolute",message:`Shell path is not absolute: ${e.shell}`,fixHint:`Set "shell" to an absolute path (e.g. /bin/bash) in ${U}.`});else try{en.existsSync(e.shell)||n.push({severity:"error",code:"shell-missing",message:`Configured shell does not exist: ${e.shell}`,fixHint:`Install the shell or update "shell" in ${U} to a valid binary.`})}catch{n.push({severity:"warn",code:"shell-stat-failed",message:`Could not verify shell path: ${e.shell}`,fixHint:`Check permissions and that "shell" in ${U} points to a real file.`})}if(e.fileReceiveRootMode==="fixed"){let a=e.fileReceiveRootPath.trim();a?xp.isAbsolute(a)||n.push({severity:"error",code:"receive-fixed-not-absolute",message:`fileReceiveRootPath must be absolute when fileReceiveRootMode is "fixed": ${a}`,fixHint:`Use an absolute path for fileReceiveRootPath in ${U}.`}):n.push({severity:"error",code:"receive-fixed-empty",message:'fileReceiveRootMode is "fixed" but fileReceiveRootPath is empty.',fixHint:`Set fileReceiveRootPath to an absolute directory in ${U}, or change fileReceiveRootMode.`})}if(vn.platform!=="win32"){try{if(en.existsSync(U)){let c=en.statSync(U);(Ys(c.mode)||Ml(c.mode))&&n.push({severity:"warn",code:"config-permissions",message:"config.json is accessible to users other than the owner (group/world).",detail:`Recommended: chmod 600 ${U}`,fixHint:`chmod 600 ${U}`})}}catch{}(typeof vn.getuid=="function"?vn.getuid():null)===0&&n.push({severity:"warn",code:"run-as-root",message:"Process is running as root \u2014 allowlisted remote access has full system privileges.",fixHint:"Run omnish as a non-root user (systemd user service, container user, etc.)."});let l=!1;try{if(en.existsSync(W)){let c=en.statSync(W);(Ys(c.mode)||Ml(c.mode))&&(l=!0,n.push({severity:"warn",code:"data-dir-permissive",message:"Omnish data directory is accessible to group or other users \u2014 auth, jobs, and logs may be exposed.",detail:`Recommended: chmod 700 ${W}`,fixHint:`chmod 700 ${W}`}))}}catch{}try{if(!l&&en.existsSync(wt)){let c=en.statSync(wt);(Ys(c.mode)||Ml(c.mode))&&n.push({severity:"warn",code:"jobs-dir-permissive",message:"Jobs log directory is accessible to group or other users \u2014 command output may leak.",detail:`Recommended: chmod 700 ${wt}`,fixHint:`chmod 700 ${wt}`})}}catch{}try{if(en.existsSync(le)){let c=en.statSync(le);Ys(c.mode)&&n.push({severity:"warn",code:"auth-dir-world-readable",message:"WhatsApp auth directory is readable by others \u2014 session material may be exposed.",detail:`Recommended: chmod 700 ${le}`,fixHint:`chmod 700 ${le}`})}}catch{}}return(typeof vn.env.TELEGRAM_BOT_TOKEN=="string"?vn.env.TELEGRAM_BOT_TOKEN.trim():"")&&n.push({severity:"info",code:"telegram-token-env",message:"TELEGRAM_BOT_TOKEN is set in the environment; it overrides config.json until unset.",fixHint:"Unset TELEGRAM_BOT_TOKEN in the shell/service env if you want config.json to apply."}),s&&!Ie(e)&&n.push({severity:"error",code:"telegram-no-token",message:"Telegram is enabled for this gateway but no bot token is configured.",detail:"Set telegramBotToken in config or TELEGRAM_BOT_TOKEN.",fixHint:`Set telegramBotToken in ${U} or export TELEGRAM_BOT_TOKEN=<token>.`}),e.clusterEnabled&&s&&n.push({severity:"info",code:"cluster-telegram-tokens",message:"Cluster mode with Telegram: use a distinct BotFather bot/token per omnish host if several gateways run Telegram \u2014 the same token cannot be long-polled twice.",fixHint:"Create separate bots for each machine or run Telegram on fewer hosts."}),e.platformToken.trim()&&n.push({severity:"info",code:"platform-token-config",message:"platformToken is stored in config.json (same trust boundary as your shell account).",fixHint:"Use OMNISH_TOKEN in the environment for CI/ephemeral hosts; env overrides config."}),(vn.env.OMNISH_TOKEN?.trim()||vn.env.OMNISH_TUNNEL_TOKEN?.trim()||vn.env.OMNISH_DEVICE_TOKEN?.trim())&&n.push({severity:"info",code:"platform-token-env",message:"OMNISH_TOKEN (or OMNISH_TUNNEL_TOKEN / OMNISH_DEVICE_TOKEN) is set in the environment.",fixHint:"Unset platform token env vars if you want config.json / tunnel-auth.json to apply."}),e.tunnelEnabled&&(n.push({severity:"warn",code:"tunnel-enabled",message:"Chat tunneling is enabled \u2014 allowlisted users can publish public URLs to local HTTP/TCP services.",fixHint:"Disable tunnelEnabled or restrict allowlists if you do not want remote tunnel control."}),Dt()||n.push({severity:"warn",code:"tunnel-no-token",message:"Chat tunneling is enabled but no tunnel token is configured.",fixHint:"Run `omnish tunnel login` or set OMNISH_TUNNEL_TOKEN for the gateway process."})),e.tunnelRelayUrl.trim()&&e.tunnelRelayUrl.trim()!==Le&&n.push({severity:"info",code:"tunnel-relay-custom",message:`Custom tunnel relay configured: ${e.tunnelRelayUrl.trim()}`,fixHint:"Use the default relay only if you trust the operator of that endpoint."}),n}function Pr(e){return e.some(t=>t.severity==="error")}function Bk(e,t){switch(t){case"error":return zs(e,"[ERROR]");case"warn":return ke(e,"[WARN]");case"info":return w(e,"[INFO]")}}function Al(e,t,n,o){if(o.length!==0){t.push(K(e,`${n} (${o.length}):`));for(let r of o)t.push(` ${Bk(e,r.severity)} ${w(e,`${r.code}:`)} ${S(e,r.message)}`),r.detail&&t.push(` ${w(e,r.detail)}`),r.fixHint&&t.push(` ${w(e,`Fix: ${r.fixHint}`)}`);t.push("")}}function Il(e,t){if(e.length===0)return[`${Re(t,"Security check:")} ${S(t,"no issues reported by automated rules.")}`,"",w(t,"Note: allowlisted remote shell access is still equivalent to sharing credentials with those identities.")].join(`
37
- `);let n=e.filter(a=>a.severity==="error"),o=e.filter(a=>a.severity==="warn"),r=e.filter(a=>a.severity==="info"),i=[`${Re(t,"Security check:")} `+S(t,`${n.length} error(s), ${o.length} warning(s), ${r.length} note(s).`),""];return Al(t,i,"Errors",n),Al(t,i,"Warnings",o),Al(t,i,"Notes",r),i.push(w(t,"Allowlisted identities can run commands as this user; treat them like passwords.")),i.join(`
38
- `).trimEnd()}function Ol(e){return{errors:e.filter(t=>t.severity==="error").length,warns:e.filter(t=>t.severity==="warn").length,infos:e.filter(t=>t.severity==="info").length}}function Tp(e){return`${JSON.stringify({findings:e,summary:Ol(e)},null,2)}
39
- `}function Ep(e,t){let n=e.filter(a=>a.severity==="error").length,o=e.filter(a=>a.severity==="warn").length,r=e.filter(a=>a.severity==="info").length;if(n===0&&o===0&&r===0)return"security: ok (no automated findings)";let s=[];n&&s.push(`${n} error(s)`),o&&s.push(`${o} warning(s)`),r&&s.push(`${r} note(s)`);let i=t??"run `omnish security` for details";return`security: ${s.join(", ")} \u2014 ${i}`}function Pp(e,t,n){let o=e.filter(l=>l.severity==="error").length,r=e.filter(l=>l.severity==="warn").length,s=e.filter(l=>l.severity==="info").length;if(o===0&&r===0&&s===0)return`${Re(t,"security:")} ${S(t,"ok (no automated findings)")}`;let i=[];o&&i.push(zs(t,`${o} error(s)`)),r&&i.push(ke(t,`${r} warning(s)`)),s&&i.push(w(t,`${s} note(s)`));let a=n??"run `omnish security` for details";return`${Re(t,"security:")} ${i.join(", ")} ${w(t,`\u2014 ${a}`)}`}var Ne="- ";function p(e){return{wa:e,tg:e}}function ye(e,t){return{wa:e,tg:t,tgHtml:!0}}function pe(e,t){return t==="whatsapp"?{text:e.wa}:e.tgHtml?{text:e.tg,parseModeHtml:!0}:{text:e.tg}}function Z(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function $e(e){return e.replace(/[*_~`]/g,t=>({"*":"\u2217",_:"\uFF3F","~":"\u02DC","`":"\u2032"})[t]??t)}function jk(e){let t=[];for(let n of e)switch(n.kind){case"title":t.push("",`*${n.text}*`,"");break;case"sub":t.push("",`_${n.text}_`,"");break;case"gap":t.push("");break;case"p":t.push(n.text);break;case"bullet":t.push(`${Ne}${n.text}`);break}return t.join(`
40
- `).replace(/^\n+/,"").trimEnd()}function Hk(e){let t=[];for(let n of e)switch(n.kind){case"title":t.push("",`<b>${Z(n.text)}</b>`,"");break;case"sub":t.push("",`<i>${Z(n.text)}</i>`,"");break;case"gap":t.push("");break;case"p":t.push(Z(n.text));break;case"bullet":t.push(`\u2022 ${Z(n.text)}`);break}return t.join(`
41
- `).replace(/^\n+/,"").trimEnd()}function X(e){return{wa:jk(e),tg:Hk(e),tgHtml:!0}}function $o(e){return[{kind:"title",text:"Omnish \u2014 quick help"},{kind:"p",text:"Per-chat shell cwd is stored under your data dir (see /wa help)."},{kind:"gap"},{kind:"sub",text:"Run commands"},{kind:"bullet",text:`${e.commandPrefix}<command> \u2014 sync shell in session cwd (timeout ${e.syncTimeoutMs} ms)`},{kind:"bullet",text:`${e.commandPrefix}cd <dir> \u2014 change session cwd (${e.commandPrefix}cd alone \u2192 home)`},{kind:"bullet",text:"!!start | !!stop \u2014 free shell (plain \u2192 sync shell only when no focused /apps session); space optional"},{kind:"bullet",text:"/bg <cmd> \u2014 background job; optional -n name; /jobs, /log, /tail, /kill (id or name)"},{kind:"bullet",text:e.tunnelEnabled?"/tunnel login|logout|status|http|tcp \u2014 login/status anytime; /tunnels needs tunnelEnabled":"/tunnel expose/list off until tunnelEnabled true (login/status still work)"},{kind:"bullet",text:"/apps \u2026 \u2014 interactive app sessions (/apps help)"},{kind:"bullet",text:"/send selectors \u2014 host files \u2192 chat (/file); caption: selectors -- note"},{kind:"bullet",text:"/files \u2014 full help for /send and saving inbound media"},{kind:"bullet",text:"/receive \u2014 set this chat\u2019s inbound folder (e.g. session cwd)"},{kind:"bullet",text:"/computers \xB7 /pcs \xB7 /c \u2014 chat-driven cluster (/c here to take over, /c help)"},{kind:"bullet",text:"/config \u2014 view or change server settings (/config help, /config keys)"},{kind:"bullet",text:"/service \u2014 background service status, install hints, logs (/service help)"},{kind:"bullet",text:"/dl <url> \u2014 auto file, video, or page\u2192markdown (background; /dl help)"},{kind:"bullet",text:"/dlf <url> \u2014 force file \xB7 /dlv <url> \u2014 force video (yt-dlp)"},{kind:"bullet",text:"/tr <url|path> \u2014 transcribe (background job)"},{kind:"bullet",text:"/edit <url|path> \u2014 trim or convert (background job; /edit help)"},{kind:"bullet",text:"/shortcut \u2014 this chat & shared shortcuts (/shortcut help); !name or /name expands chat override first, then shared"},{kind:"bullet",text:"/run \u2014 recipe-based task runs (/r); per-chat or gateway-shared /run add \u2014 /run list, /run help"},{kind:"bullet",text:"/cowork | /cw \u2014 scheduled & on-demand shell tasks; logs to ~/Cowork/\u2026 (/cowork help)"},{kind:"bullet",text:"/watch \u2014 OS event eye: fs/pkg/svc alerts; pause/stop/rm, excludes (/watch help; docs/features/watch.md)"},{kind:"gap"},{kind:"sub",text:"Setup & gateway"},{kind:"bullet",text:"/wa help \u2014 WhatsApp link, allowlist"},{kind:"bullet",text:"/tg help \u2014 Telegram; /tg token <paste>"},{kind:"bullet",text:"/reload | /restart \u2014 apply config"},{kind:"bullet",text:"/updates \u2014 npm latest + optional notice URL; /updates cached \u2014 last snapshot"},{kind:"bullet",text:"/security \u2014 posture report; /security summary; /security tips; /security help"},{kind:"bullet",text:"/gateway | /gw | /mode \u2014 show or set gatewayMode"},{kind:"bullet",text:"/allow +E164 | /allow tg:id \u2014 allowlist; /deny \u2026; /allowlist"},{kind:"bullet",text:"/s q <topic> \u2014 find guides and next commands (/s help, same as /search)"},{kind:"bullet",text:"/help \u2014 this message"},{kind:"gap"},{kind:"sub",text:"Terms"},{kind:"bullet",text:"Gateway = omnish on this host; shell runs here. Standalone = messengers on this host; attached = communication layer routes chat here."},{kind:"bullet",text:'Communication layer (when shipped) = link chats once, device token on this CLI; shell stays here. Standalone = no layer. /service "platform" = OS.'}]}function $r(e){let t=e.mediaInstallFromChat?[{kind:"bullet",text:"/dl install [--whisper] \u2014 download tools into ~/.omnish/bin"}]:[{kind:"bullet",text:"/dl install \u2014 off from chat. Host: omnish pull install \xB7 or /config set mediaInstallFromChat true"}];return[{kind:"title",text:"Media \u2014 /dl, /tr, /edit"},{kind:"p",text:"Files are sent to chat by default. Opt out: /config set mediaSendFiles false (paths only)."},{kind:"bullet",text:"/dl <url> \u2014 auto: file (HTTP), video (yt-dlp ~1000 sites), or HTML\u2192markdown"},{kind:"bullet",text:"/dlf <url> \u2014 force HTTP file \xB7 /dlv <url> \u2014 force yt-dlp video"},{kind:"bullet",text:"/tr <url|path> \u2014 Whisper transcript + .srt (+ video if URL); background job"},{kind:"bullet",text:"/edit <url|path> [--from 1:30] [--to 2:00] [--format mp3] [--audio-only] (background job)"},{kind:"bullet",text:"/dl doctor \xB7 /dl setup \xB7 /dl install"},...t,{kind:"bullet",text:e.mediaUrlAutoDl?"Lone URL in chat \u2192 auto /dl (file, video, or markdown)":"mediaUrlAutoDl off \u2014 use /dl <url>"},{kind:"bullet",text:"All media jobs run in background \u2014 /log, /tail; optional --notify on /dl, /tr, /edit"},{kind:"bullet",text:"Step progress on by default (progressUpdates); disable via /config set progressUpdates false"},{kind:"gap"},{kind:"p",text:"Host: omnish pull doctor \xB7 omnish pull install [--whisper]"},{kind:"p",text:"docs/features/media-commands.md"}]}function $p(e){let t=e.serviceInstallFromChat?[{kind:"bullet",text:"/service install \u2014 user-level systemd (Linux) or LaunchAgent (macOS)"},{kind:"bullet",text:"/service uninstall \u2014 remove that unit"}]:[{kind:"bullet",text:"/service install / uninstall \u2014 off by default. Enable: /config set serviceInstallFromChat true (same trust as shell)"}];return[{kind:"title",text:"Service and boot"},{kind:"p",text:"Inspect the gateway, get OS-specific install steps, or (if enabled) write the user service from chat. Same from the host terminal: omnish service help."},{kind:"p",text:'"platform" in status means your OS (Linux/macOS/Windows), not a hosted omnish account.'},{kind:"bullet",text:"/service status \u2014 platform, data dir, pidfile, node + entry script"},{kind:"bullet",text:"/service instructions \u2014 copy-paste commands for this machine"},{kind:"bullet",text:"/service logs [n] \u2014 tail default gateway log (default 80 lines)"},...t,{kind:"gap"},{kind:"p",text:"See https://omnish.dev and docs/guides/background-and-boot.md."}]}function Mp(){return[{kind:"title",text:"Chat config"},{kind:"p",text:`Same trust as shell \u2014 allowlisted senders can change many keys saved to ${U}.`},{kind:"gap"},{kind:"bullet",text:"/config show \u2014 full snapshot (telegramBotToken masked)"},{kind:"bullet",text:"/config get <key> \u2014 single field"},{kind:"bullet",text:"/config set <key> <value> \u2014 values can be quoted for paths with spaces"},{kind:"bullet",text:"/config keys \u2014 allowed keys for /config set (includes tunnel*, webhook*, watch*, pull*, chatLlm*, cluster*, \u2026)"},{kind:"gap"},{kind:"p",text:"After edits: /reload while omnish run is active (needed for gatewayMode and telegramBotToken)."}]}function Ll(){return[{kind:"title",text:"Gateway mode"},{kind:"p",text:"Which transports omnish uses:"},{kind:"bullet",text:"whatsapp (wa, w) \u2014 WhatsApp DMs only"},{kind:"bullet",text:"telegram (tg, t) \u2014 Telegram bot only"},{kind:"bullet",text:"both (all, b) \u2014 WhatsApp + Telegram"},{kind:"gap"},{kind:"sub",text:"Commands"},{kind:"bullet",text:"/gateway \u2014 current mode + hints (includes last update snapshot if any)"},{kind:"bullet",text:"/gateway telegram \u2014 save (auto-reloads Telegram if gateway is up)"},{kind:"bullet",text:"/gw both \u2014 short form; /mode wa \u2014 same"},{kind:"gap"},{kind:"p",text:`Config: ${U}`}]}function Ap(e){let t=["*Gateway status*","",`Mode: *${e.gatewayMode}*`,"- whatsapp \u2014 WhatsApp only","- telegram \u2014 Telegram only","- both \u2014 both channels","",`WhatsApp auth: ${e.authPresent?"present":"missing (omnish link)"}`,`Telegram token: ${e.tokenSet?"set":"not set"}`,`Allowlists: ${e.allowN} WA \xB7 ${e.tgAllowN} Telegram`,""];e.updateBrief&&t.push(`Updates: ${$e(e.updateBrief)}`,""),t.push("Change: /gateway both \xB7 /gw tg","/updates \u2014 npm + optional notice URL",`Config: ${$e(U)}`);let n=["<b>Gateway status</b>","",`<b>${Z(e.gatewayMode)}</b> <i>current mode</i>`,"\u2022 whatsapp \u2014 WhatsApp only","\u2022 telegram \u2014 Telegram only","\u2022 both \u2014 both channels","",`${e.authPresent?"\u2713":"\u26A0"} WhatsApp auth: ${e.authPresent?"present":"missing \u2014 run omnish link"}`,`${e.tokenSet?"\u2713":"\u26A0"} Telegram token: ${e.tokenSet?"set":"not set"}`,`Allowlists: ${e.allowN} WA \xB7 ${e.tgAllowN} Telegram`,""];return e.updateBrief&&n.push(`<b>Updates</b> ${Z(e.updateBrief)}`,""),n.push("<i>Change:</i> /gateway both \xB7 /gw tg","<i>/updates</i> \u2014 npm + optional notice URL",Z(`Config: ${U}`)),ye(t.join(`
31
+ )`).run(n.c-Hd)}function qd(e,t){let n=Tr(),o=Math.min(Math.max(1,e),50),r=n.prepare("SELECT rule_id, rule_name, kind, summary, ts_ms FROM watch_recent ORDER BY ts_ms DESC LIMIT ?").all(o*3),s=[];for(let i of r)if(!(t&&!t.has(i.rule_id))&&(s.push({ruleId:i.rule_id,ruleName:i.rule_name,kind:i.kind,stateKey:"",summary:i.summary,tsMs:i.ts_ms}),s.length>=o))break;return s}function zd(e){return Tr().prepare("SELECT state_key FROM watch_rule_state WHERE rule_id = ?").get(e)?.state_key??null}function Kd(e,t,n){Tr().prepare("INSERT OR REPLACE INTO watch_rule_state (rule_id, state_key, updated_at_ms) VALUES (?, ?, ?)").run(e,t,n)}import Zd from"node:path";import Qt from"node:path";function Qb(e,t){let n=Qt.normalize(e);for(let o of t){let r=Qt.normalize(o);if(n===r||n.startsWith(r+Qt.sep))return!0}return!1}function Xb(e,t,n){let o=Qt.relative(t,e);if(o.startsWith("..")||Qt.isAbsolute(o))return!1;let r=o.split(Qt.sep).join("/"),s=n.replace(/\\/g,"/").replace(/^\//,"");if(s.includes("**")){let i=s.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*\*/g,"___GLOBSTAR___").replace(/\*/g,"[^/]*").replace(/___GLOBSTAR___/g,".*");try{if(new RegExp(`^${i}$`).test(r))return!0;if(s.startsWith("**/")){let l=s.slice(3);if(l&&!l.includes("*"))return r.split("/").includes(l)}return!1}catch{return!1}}if(s.includes("*")){let i=s.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,"[^/]*");try{return new RegExp(`^${i}$`).test(r)}catch{return!1}}return r===s||r.endsWith("/"+s)||r.includes("/"+s+"/")}function _s(e,t){if(t.excludePaths.length&&Qb(e,t.excludePaths))return!0;let n=t.path;if(n&&t.excludeGlobs.length){for(let o of t.excludeGlobs)if(Xb(e,n,o))return!0}return!1}function Yd(e){let t=[];for(let n of e.excludeGlobs){let o=n.replace(/\\/g,"/");o.startsWith("**/")?t.push(o):o.includes("*")?t.push(`**/${o}`):t.push(`**/${o}/**`)}for(let n of e.excludePaths)if(e.path)try{let o=Qt.relative(e.path,n);!o.startsWith("..")&&!Qt.isAbsolute(o)&&t.push(o.split(Qt.sep).join("/")+"/**")}catch{}return t}dt();function To(e,t,n){if(e==="none")return[];let o=new Set;if((e==="self"||e==="all")&&o.add(t),e==="wa"||e==="all")for(let r of n.allowFrom){let s=te(String(r));s&&o.add(`wa:${mn(s)}`)}if(e==="tg"||e==="all")for(let r of n.telegramAllowFrom){let s=Fe(String(r));s&&o.add(`tg:${s}`)}return[...o]}function Vd(e,t,n){return To(e,t,n)}ye();j();import Zb from"node:crypto";import bl from"node:fs";var ek=/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,31}$/,tk=1,kl=20;function Fs(e){let t=e.trim().toLowerCase();return ek.test(t)?{ok:!0,name:t}:{ok:!1,error:"Name must be alphanumeric with _ or -, max 32 chars."}}function Ds(){return Zb.randomBytes(8).toString("hex")}function nk(e){if(!Array.isArray(e)||e.length===0)return["create","delete","rename"];let t=new Set(["create","delete","rename","update"]),n=[];for(let o of e)typeof o=="string"&&t.has(o)&&n.push(o);return n.length?n:["create","delete","rename"]}function ok(e){if(!e||typeof e!="object")return null;let t=e,n=typeof t.id=="string"&&t.id.length>=4?t.id.slice(0,32):"",o=typeof t.name=="string"?t.name.trim().toLowerCase():"",r=typeof t.ownerPeerKey=="string"?t.ownerPeerKey:"",s=typeof t.kind=="string"?t.kind:"",i=s==="fs"||s==="pkg"||s==="svc"?s:null;if(!n||!o||!r||!i)return null;let a=typeof t.notify=="string"?t.notify.toLowerCase():"self",l=a==="wa"||a==="whatsapp"?"wa":a==="tg"||a==="telegram"?"tg":a==="all"?"all":a==="none"?"none":"self",c=typeof t.notifyWhen=="string"?t.notifyWhen:"always",u=c==="failure"||c==="state-change"?c:"always",d=Array.isArray(t.units)?t.units.filter(h=>typeof h=="string"&&h.trim().length>0).map(h=>h.trim()):[],m=Array.isArray(t.excludePaths)?t.excludePaths.filter(h=>typeof h=="string"&&h.trim().length>0):[],f=Array.isArray(t.excludeGlobs)?t.excludeGlobs.filter(h=>typeof h=="string"&&h.trim().length>0):[];return{id:n,name:o,ownerPeerKey:r,kind:i,enabled:typeof t.enabled=="boolean"?t.enabled:!0,paused:typeof t.paused=="boolean"?t.paused:!1,notify:l,notifyWhen:u,path:typeof t.path=="string"?t.path:"",events:nk(t.events),units:d,excludePaths:m,excludeGlobs:f,adapterStatus:typeof t.adapterStatus=="string"?t.adapterStatus:"",createdAtMs:typeof t.createdAtMs=="number"&&Number.isFinite(t.createdAtMs)?t.createdAtMs:Date.now()}}function rk(e){let t=JSON.parse(e);return!t||!Array.isArray(t.rules)?[]:t.rules.map(ok).filter(n=>n!==null)}function sk(e){let t=[...e].sort((s,i)=>s.createdAtMs-i.createdAtMs),n=new Set,o=!1,r=[];for(let s of t){if(!n.has(s.name)){n.add(s.name),r.push(s);continue}let i=s.id.slice(0,8),a=`${s.name}-${i}`;a.length>32&&(a=`${s.name.slice(0,32-i.length-1)}-${i}`);let l=0;for(;n.has(a);){l+=1;let c=String(l);a=`${s.name.slice(0,Math.max(1,32-i.length-c.length-1))}-${i}${c}`}n.add(a),E.warn({oldName:s.name,newName:a,id:s.id},"watch: renamed duplicate rule for device-wide namespace"),r.push({...s,name:a}),o=!0}return{rules:r,changed:o}}function Ge(){try{let e=bl.readFileSync(kr,"utf8"),t=rk(e),{rules:n,changed:o}=sk(t);return o&&Lt(n),n}catch{return[]}}function ik(e){let t=0,n=0,o=0;for(let r of e)r.enabled?r.paused?n+=1:t+=1:o+=1;return{total:e.length,active:t,paused:n,disabled:o}}function Er(){let e=Ge();return{rules:e,summary:ik(e)}}function Lt(e){D(Vt);let n=`${JSON.stringify({version:tk,rules:e},null,2)}
32
+ `,o=`${kr}.tmp`;bl.writeFileSync(o,n,{mode:384}),bl.renameSync(o,kr)}function Sl(){return kr}function qn(e,t){let n=t.trim().toLowerCase();return e.find(o=>o.name===n)}function Qd(e,t){return e.find(n=>n.id===t)}function Eo(e,t){let n=e.findIndex(o=>o.id===t.id);if(n>=0){let o=[...e];return o[n]=t,o}return[...e,t]}function Xd(e,t){let n=t.trim().toLowerCase();return e.filter(o=>o.name!==n)}import ak from"node:os";import Nt from"node:path";var lk=new Set([".env","id_rsa","id_ed25519","id_ecdsa","known_hosts","credentials.json","secrets.json",".netrc","shadow","passwd"]);function mt(e){let t=Nt.normalize(e),n=t.toLowerCase(),o=ak.homedir().toLowerCase();if(n.includes(`${Nt.sep}.ssh${Nt.sep}`)||n.endsWith(`${Nt.sep}.ssh`)||n.includes(`${Nt.sep}browser${Nt.sep}`)&&n.includes("profile")||o&&(n===`${o}/.gnupg`||n.startsWith(`${o}/.gnupg${Nt.sep}`))||n.includes(`${Nt.sep}keychains${Nt.sep}`))return!0;let r=Nt.basename(t);return!!(lk.has(r)||r.startsWith(".env.")||r.endsWith(".pem")||r.endsWith(".key"))}var ck=["node_modules",".git",".svn",".hg","__pycache__",".cache",".next","dist","build"],uk=[".tmp",".swp",".swx","~",".part"];function ep(e,t){let n=new Map,o=new Map;function r(u,d){let m=u.split(Zd.sep);for(let h of m)if(ck.includes(h))return!0;let f=Zd.basename(u);for(let h of uk)if(f.endsWith(h))return!0;return!!_s(u,d)}function s(u){let d=Qd(Ge(),u);return!d||!d.enabled||d.paused?null:d}function i(u){let d=Date.now(),m=o.get(u);return!m||d-m.windowStart>=6e4?(o.set(u,{windowStart:d,count:1}),!1):m.count>=t.maxEventsPerMinute?!0:(m.count+=1,!1)}async function a(u,d){let m=e.getConfig();if(!m.watchEnabled)return;let f=s(u.id);if(!f||d.kind==="fs"&&d.meta?.path&&(mt(d.meta.path)||r(d.meta.path,f)))return;if(Gd(d),(f.notifyWhen??"always")==="state-change"){if(zd(f.id)===d.stateKey)return;Kd(f.id,d.stateKey,d.tsMs)}let g=Vd(f.notify,f.ownerPeerKey,m);if(g.length===0)return;let y=`[watch:${f.name}] ${d.summary}`;await Promise.all(g.map(b=>e.sendToPeer(b,y).catch(()=>{})))}function l(u){let d=n.get(u);if(!d)return;n.delete(u);let m=s(d.ruleId);m&&(i(d.ruleId)||a(m,d.event))}function c(u){for(let[d,m]of n)m.ruleId===u&&(clearTimeout(m.timer),n.delete(d))}return{ingest(u,d){if(!u.enabled||u.paused||d.meta?.path&&(mt(d.meta.path)||r(d.meta.path,u)))return;let m=`${u.id}:${d.stateKey||d.summary}`,f=n.get(m);f&&clearTimeout(f.timer);let h=setTimeout(()=>l(m),t.debounceMs);h.unref?.(),n.set(m,{timer:h,event:d,ruleId:u.id})},cancelForRule:c,dispose(){for(let u of n.values())clearTimeout(u.timer);n.clear(),o.clear()}}}function tp(e,t){let n=e.toLowerCase();return!!(n==="create"&&t.has("create")||n==="delete"&&t.has("delete")||(n==="update"||n==="rename")&&(t.has("update")||t.has("rename")))}ye();import np from"node:fs";import dk from"node:path";import{subscribe as pk}from"@parcel/watcher";var mk=["**/node_modules/**","**/.git/**","**/.svn/**","**/.hg/**","**/__pycache__/**","**/.cache/**"];function fk(e){return e==="create"?"create":e==="delete"?"delete":e==="update"?"update":e}function hk(e,t){try{let n=np.statSync(t),o=n.isFile()&&n.size<1024*1024?` (${n.size<1024?`${n.size} B`:`${(n.size/1024).toFixed(1)} KB`})`:"";return`fs: ${e} ${t}${o}`}catch{return`fs: ${e} ${t}`}}async function op(e,t){let n=e.path;if(!n||!np.existsSync(n))return{stop:async()=>{},status:()=>"error: path missing or not found"};if(mt(n))return{stop:async()=>{},status:()=>"error: sensitive path denied"};let o=new Set(e.events.map(i=>i.toLowerCase())),r="ok",s=null;try{s=await pk(n,(i,a)=>{if(i){r=`error: ${i.message}`,E.warn({rule:e.name,err:i.message},"watch fs adapter");return}for(let l of a){let c=fk(l.type);if(!tp(c,o))continue;let u=dk.resolve(l.path);if(!u.startsWith(n)||mt(u)||_s(u,e))continue;let d=hk(c,u);t({ruleId:e.id,ruleName:e.name,kind:"fs",stateKey:`${c}:${u}`,summary:d,tsMs:Date.now(),meta:{path:u,type:c}})}},{ignore:[...mk,...Yd(e)]})}catch(i){r=`error: ${String(i)}`,E.warn({rule:e.name,err:String(i)},"watch fs subscribe failed")}return{stop:async()=>{s&&(await s.unsubscribe().catch(()=>{}),s=null)},status:()=>r}}import xk from"node:os";ye();import hn from"node:fs";function gk(e,t,n){let o=n?.pollMs??2e3,r=0,s=!1,i=null,a=null,l="starting";function c(){if(!s)try{let u=hn.statSync(e);if(u.size<r&&(r=0),u.size<=r)return;let d=hn.openSync(e,"r");try{let m=u.size-r,f=Buffer.alloc(m);hn.readSync(d,f,0,m,r),r=u.size;let h=f.toString("utf8");for(let g of h.split(`
33
+ `)){let y=g.trim();y&&t(y)}}finally{hn.closeSync(d)}l="ok"}catch(u){l=`error: ${String(u)}`}}try{hn.existsSync(e)&&(r=hn.statSync(e).size),i=hn.watch(e,()=>c()),i.on("error",()=>{i?.close(),i=null}),l="ok (watch)"}catch{l="ok (poll)",a=setInterval(c,o),a.unref?.(),c()}return{stop(){s=!0,i?.close(),a&&clearInterval(a)},status:()=>l}}function Ws(e,t){for(let n of e)try{if(hn.existsSync(n))return gk(n,t)}catch(o){E.debug({path:n,err:String(o)},"watch log-tail skip path")}return null}var yk=["/var/log/install.log"];function wk(e){let t=e.toLowerCase();return t.includes("installed")||t.includes("upgraded")||t.includes("removed")?e.length>240?`pkg: ${e.slice(0,240)}\u2026`:`pkg: ${e}`:null}function rp(e,t){return Ws(yk,n=>{let o=wk(n);o&&t({ruleId:e.id,ruleName:e.name,kind:"pkg",stateKey:o.slice(0,200),summary:o,tsMs:Date.now()})})}var bk=["/var/log/dpkg.log","/var/log/apt/history.log"];function kk(e){if(e.includes("status installed")){let t=e.match(/install\s+(\S+):/);if(t)return`pkg: installed ${t[1]} (dpkg)`}if(e.includes("status removed")||e.includes("remove ")){let t=e.match(/remove\s+(\S+):/);if(t)return`pkg: removed ${t[1]} (dpkg)`}return null}function Sk(e){return e.startsWith("Install:")||e.startsWith("Upgrade:")?`pkg: ${e.slice(0,120)} (apt)`:e.startsWith("Remove:")?`pkg: ${e.slice(0,120)} (apt)`:null}function sp(e,t){return Ws(bk,n=>{let o=kk(n)??Sk(n);o&&t({ruleId:e.id,ruleName:e.name,kind:"pkg",stateKey:o,summary:o,tsMs:Date.now()})})}import{spawn as vk}from"node:child_process";function ip(e,t){let n=!1,o="ok",r=0,s=()=>{if(n)return;let a=vk("powershell.exe",["-NoProfile","-NonInteractive","-Command","Get-WinEvent -FilterHashtable @{LogName='Application'; ProviderName=@('MsiInstaller','Windows Installer')} -MaxEvents 5 -ErrorAction SilentlyContinue | Select-Object -Property RecordId,Message | ConvertTo-Json -Compress"],{windowsHide:!0}),l="";a.stdout?.on("data",c=>{l+=String(c)}),a.on("close",c=>{if(n||c!==0||!l.trim()){c!==0&&(o="ok (no events or access denied)"),i();return}try{let u=JSON.parse(l),d=Array.isArray(u)?u:[u];for(let m of d.sort((f,h)=>f.RecordId-h.RecordId)){if(m.RecordId<=r)continue;r=m.RecordId;let f=(m.Message??"").replace(/\s+/g," ").trim().slice(0,300);if(!f)continue;let h=`pkg: ${f}`;t({ruleId:e.id,ruleName:e.name,kind:"pkg",stateKey:`win:${m.RecordId}`,summary:h,tsMs:Date.now()})}o="ok"}catch(u){o=`parse error: ${String(u)}`}i()}),a.on("error",c=>{o=`error: ${String(c)}`,i()})},i=()=>{n||setTimeout(s,3e4).unref?.()};return s(),{stop(){n=!0},status:()=>o}}function ap(e,t){let n=xk.platform();if(n==="win32"){let r=ip(e,t);return{stop:()=>r.stop(),status:r.status}}if(n==="darwin"){let r=rp(e,t);return r?{stop:()=>r.stop(),status:r.status}:{stop:()=>{},status:()=>"error: cannot read /var/log/install.log (try sudo or adm group)"}}let o=sp(e,t);return o?{stop:()=>o.stop(),status:o.status}:{stop:()=>{},status:()=>"error: cannot read dpkg/apt logs (add user to adm or run gateway with read access)"}}import Ak from"node:os";import{spawnSync as lp}from"node:child_process";var Ck=3e4;function Rk(e){let t=new Map;for(let n of e){let o=lp("launchctl",["print",`system/${n}`],{encoding:"utf8",timeout:1e4});if(o.status!==0){let s=process.getuid?.()??501,i=lp("launchctl",["print",`gui/${s}/${n}`],{encoding:"utf8",timeout:1e4});if(i.status!==0){t.set(n,"not-found");continue}let a=(i.stdout??"").includes("state = running")?"running":"stopped";t.set(n,a);continue}let r=(o.stdout??"").includes("state = running")?"running":"stopped";t.set(n,r)}return t}function cp(e,t){let n=e.units,o=!1,r="ok",s=new Map,i=()=>{if(o)return;let a=Rk(n);if(a.size===0)r="error: no launchd labels configured";else{r="ok";for(let[l,c]of a){let u=s.get(l);if(u!==void 0&&u!==c){let d=`svc: ${l} ${u} \u2192 ${c}`;t({ruleId:e.id,ruleName:e.name,kind:"svc",stateKey:`${l}:${c}`,summary:d,tsMs:Date.now(),meta:{unit:l,state:c}})}}s=a}o||setTimeout(i,Ck).unref?.()};return i(),{stop(){o=!0},status:()=>r}}import{spawnSync as up}from"node:child_process";var Tk=3e4;function Ek(e){let t=new Map;if(e.length===0)return t;let n=["show",...e,"--property=ActiveState,SubState,UnitFileState","--no-pager"],o=up("systemctl",n,{encoding:"utf8",timeout:15e3});if(o.status!==0)return t;let r="";for(let s of(o.stdout??"").split(`
34
+ `)){let i=s.match(/^Unit=(.+)$/);if(i){r=i[1];continue}let a=s.match(/^ActiveState=(.+)$/);a&&r&&t.set(r,a[1].trim())}if(t.size===0)for(let s of e){let i=s.endsWith(".service")?s:`${s}.service`,a=up("systemctl",["is-active",i],{encoding:"utf8",timeout:5e3}),l=(a.stdout??a.stderr??"unknown").trim();t.set(i,l)}return t}function dp(e,t){let n=e.units.map(a=>a.endsWith(".service")?a:`${a}.service`),o=!1,r="ok",s=new Map,i=()=>{if(o)return;let a=Ek(n);if(a.size===0)r="error: systemctl unavailable or units not found";else{r="ok";for(let[l,c]of a){let u=s.get(l);if(u!==void 0&&u!==c){let d=`svc: ${l} ${u} \u2192 ${c}`;t({ruleId:e.id,ruleName:e.name,kind:"svc",stateKey:`${l}:${c}`,summary:d,tsMs:Date.now(),meta:{unit:l,state:c}})}}s=a}o||setTimeout(i,Tk).unref?.()};return i(),{stop(){o=!0},status:()=>r}}import{spawnSync as Pk}from"node:child_process";var $k=3e4;function Mk(e){let t=new Map;for(let n of e){let o=Pk("sc",["query",n],{encoding:"utf8",timeout:1e4,windowsHide:!0});if(o.status!==0){t.set(n,"not-found");continue}let r=o.stdout??"",s="unknown";r.includes("RUNNING")?s="running":r.includes("STOPPED")?s="stopped":r.includes("START_PENDING")?s="start-pending":r.includes("STOP_PENDING")&&(s="stop-pending"),t.set(n,s)}return t}function pp(e,t){let n=e.units,o=!1,r="ok",s=new Map,i=()=>{if(o)return;let a=Mk(n);if(a.size===0)r="error: no service names configured";else{r="ok";for(let[l,c]of a){let u=s.get(l);if(u!==void 0&&u!==c){let d=`svc: ${l} ${u} \u2192 ${c}`;t({ruleId:e.id,ruleName:e.name,kind:"svc",stateKey:`${l}:${c}`,summary:d,tsMs:Date.now(),meta:{unit:l,state:c}})}}s=a}o||setTimeout(i,$k).unref?.()};return i(),{stop(){o=!0},status:()=>r}}function mp(e,t){let n=Ak.platform();return n==="win32"?pp(e,t):n==="darwin"?cp(e,t):dp(e,t)}var ft=null,gn=null;function fp(e){let t=e.getConfig();return ep(e,{debounceMs:Math.max(500,t.watchDebounceMs??2e3),maxEventsPerMinute:Math.max(1,t.watchMaxEventsPerMinute??30)})}function Lk(e){return e.watchEnabled&&e.watchAutoRestore}var vl=class{deps;pipeline;runtimes=new Map;stopped=!1;constructor(t){this.deps=t,this.pipeline=fp(t)}onEvent=(t,n)=>{this.pipeline.ingest(t,n)};cancelPendingForRule(t){this.pipeline.cancelForRule(t)}async reload(){if(this.pipeline.dispose(),this.pipeline=fp(this.deps),await this.stopAdapters(),this.stopped||!this.deps.getConfig().watchEnabled)return 0;let n=Ge().filter(o=>o.enabled&&!o.paused);for(let o of n)await this.startRule(o);return this.runtimes.size}async startRule(t){try{let n;if(t.kind==="fs"){let o=at(t.path,Ik.homedir()),r={...t,path:o};if(mt(o)){t.adapterStatus="denied: sensitive path",this.persistRuleStatus(t);return}if(!Ok.existsSync(o)){t.adapterStatus="error: path not found",this.persistRuleStatus(t);return}let s=await op(r,i=>this.onEvent(r,i));n={stop:()=>s.stop(),status:s.status}}else if(t.kind==="pkg")n=ap(t,r=>this.onEvent(t,r));else if(t.kind==="svc"){if(t.units.length===0){t.adapterStatus="error: no units",this.persistRuleStatus(t);return}n=mp(t,r=>this.onEvent(t,r))}else return;t.adapterStatus=n.status(),this.persistRuleStatus(t),this.runtimes.set(t.id,{rule:t,adapter:n})}catch(n){t.adapterStatus=`error: ${String(n)}`,this.persistRuleStatus(t),E.warn({rule:t.name,err:String(n)},"watch rule start failed")}}persistRuleStatus(t){let n=Ge(),o=n.findIndex(r=>r.id===t.id);o>=0&&(n[o]={...n[o],adapterStatus:t.adapterStatus},Lt(n))}async stopAdapters(){for(let t of this.runtimes.values())try{await t.adapter.stop()}catch{}this.runtimes.clear()}async stop(){this.stopped=!0,await this.stopAdapters(),this.pipeline.dispose(),Jd()}getStatusLines(){let t=Ge();return t.length===0?["(no watch rules)"]:t.map(n=>{let r=this.runtimes.get(n.id)?.adapter.status()??n.adapterStatus,s=n.enabled?n.paused?"paused":"on":"disabled",i=n.kind==="fs"?n.path:n.kind==="svc"?n.units.join(","):"system",a=n.kind==="fs"&&(n.excludePaths.length||n.excludeGlobs.length)?` excludes:${n.excludePaths.length+n.excludeGlobs.length}`:"";return`${n.name} [${n.kind}] ${s} notify=${n.notify} when=${n.notifyWhen} \u2014 ${i}${a} \u2014 ${r}`})}getRunningCount(){return this.runtimes.size}isRuleRunning(t){return this.runtimes.has(t)}};function hp(){return ft}function xl(e){ft?.cancelPendingForRule(e)}function Nk(){D(Vt),Ns()}function Cl(){gn&&(ft||(ft=new vl(gn)),ft.reload())}function Us(e){gn=e,Nk();let t=e.getConfig(),{summary:n}=Er();return n.total>0&&E.info({rules:n.total,active:n.active,paused:n.paused,disabled:n.disabled},"watch rules loaded from disk"),Lk(t)&&Cl(),()=>{ft?.stop(),ft=null,gn=null}}async function gp(){return gn?gn.getConfig().watchEnabled?(Cl(),ft?.getRunningCount()??0):(ft?.stop(),ft=null,-1):-1}function ot(){if(!gn)return;if(!gn.getConfig().watchEnabled){ft?.stop(),ft=null;return}Cl()}j();ue();j();import Dk from"node:os";import Hs from"node:path";j();import Rl from"node:fs";import wp from"node:os";import yn from"node:path";var bp=yn.join(W,"sessions.json"),Po=new Map;function kp(){return{cwd:yn.resolve(wp.homedir())}}function _k(e){return e.startsWith("wa:")||e.startsWith("tg:")?e:`wa:${e}`}function Fk(){try{let e=Rl.readFileSync(bp,"utf8"),t=JSON.parse(e);for(let[n,o]of Object.entries(t)){if(!o||typeof o!="object")continue;let r=_k(n),i={cwd:typeof o.cwd=="string"&&o.cwd.length>0?yn.resolve(o.cwd):kp().cwd};o.fileReceiveRoot==="sessionCwd"&&(i.fileReceiveRoot="sessionCwd"),Po.set(r,i)}}catch{}}function Sp(){D(W);let e={};for(let[t,n]of Po){let o={cwd:n.cwd};n.fileReceiveRoot==="sessionCwd"&&(o.fileReceiveRoot="sessionCwd"),e[t]=o}Rl.writeFileSync(bp,JSON.stringify(e,null,2)+`
35
+ `,{mode:384})}var yp=!1;function Tl(){yp||(Fk(),yp=!0)}function oe(e){Tl();let t=Po.get(e);return t||(t=kp(),Po.set(e,t)),t}function Bs(e,t){Tl();let n=yn.resolve(t),o=oe(e);o.cwd=n,Po.set(e,o),Sp()}function js(e){return oe(e).fileReceiveRoot==="sessionCwd"?"sessionCwd":"default"}function El(e,t){Tl();let n=oe(e);t==="default"?delete n.fileReceiveRoot:n.fileReceiveRoot="sessionCwd",Po.set(e,n),Sp()}function vp(e){let t=e.trim();if(/[&|;\n]/.test(t)||!t.startsWith("cd"))return null;if(t==="cd")return{kind:"home"};if(t.length>2&&t[2]!==" "&&t[2]!==" ")return null;let n=t.slice(2).trimStart();return n?{kind:"path",value:n}:{kind:"home"}}function xp(e,t){if(t.kind==="home")return yn.resolve(wp.homedir());let n=t.value.replace(/^['"]|['"]$/g,"");return yn.isAbsolute(n)?yn.normalize(n):yn.resolve(e,n)}function Cp(e){try{return Rl.statSync(e).isDirectory()?{ok:!0}:{ok:!1,error:`Not a directory: ${e}`}}catch(t){return{ok:!1,error:String(t)}}}var Wk="Omnish";function Js(){return Hs.join(Dk.homedir(),"Downloads",Wk)}function wn(e,t){let n=oe(t);if(n.fileReceiveRoot==="sessionCwd")return n.cwd;switch(e.fileReceiveRootMode){case"downloads":return Js();case"omnishData":return Hs.join(W,e.fileInboxSubdir);case"sessionCwd":return n.cwd;case"processCwd":return process.cwd();case"fixed":{let o=(e.fileReceiveRootPath??"").trim();if(!o)throw new Error('fileReceiveRootPath is required when fileReceiveRootMode is "fixed".');if(!Hs.isAbsolute(o))throw new Error('fileReceiveRootPath must be an absolute path when fileReceiveRootMode is "fixed".');return Hs.resolve(o)}default:return Js()}}j();import en from"node:fs";import Tp from"node:path";import vn from"node:process";var zn="\x1B";function Uk(e){return e.replace(/\u001B\[[\d;]*m/g,"")}function Gs(e){return Uk(e).length}function Pl(e,t,n,o,r=2){let s=Math.max(0,t-Gs(n));return`${e}${n}${" ".repeat(s)}${" ".repeat(r)}${o}`}function _t(e,t,n,o){if(n.length===0)return[];let r=n.map(i=>o(i.left)),s=Math.max(...r.map(Gs));return n.map((i,a)=>Pl(t,s,r[a],S(e,i.right)))}var bn={primary:"#b4ff24",foreground:"#eef2f4",muted:"#8a9199",border:"#2a3139",error:"#ef4444",warn:"#a0e614",warnAlt:"#8cce04"};function Bk(e){let t=e.replace(/^#/,"");return t.length!==6||!/^[0-9a-fA-F]+$/.test(t)?null:{r:parseInt(t.slice(0,2),16),g:parseInt(t.slice(2,4),16),b:parseInt(t.slice(4,6),16)}}function kn(e){let t=Bk(e);return t?`${zn}[38;2;${t.r};${t.g};${t.b}m`:""}function qs(e){if(process.env.NO_COLOR!==void 0&&process.env.NO_COLOR!=="")return!1;let t=process.env.FORCE_COLOR;return t==="1"||t==="true"?!0:t==="0"||t==="false"?!1:e.isTTY===!0}function Xt(e,t,n){return!t||!qs(e)?n:`${t}${n}${zn}[0m`}function K(e,t){return Xt(e,`${zn}[1m`,t)}function jk(e,t){return qs(e)?`${kn(bn.foreground)}${zn}[1m${t}${zn}[0m`:t}function Q(e,t){return Xt(e,`${zn}[2m`,t)}function Re(e,t){return Xt(e,`${kn(bn.primary)}${zn}[1m`,t)}function he(e,t){return Xt(e,kn(bn.primary),t)}function S(e,t){return Xt(e,kn(bn.foreground),t)}function w(e,t){return Xt(e,kn(bn.muted),t)}function Hk(e,t){return Xt(e,kn(bn.border),t)}function zs(e,t){return Xt(e,kn(bn.error),t)}function ke(e,t){return Xt(e,kn(bn.warn),t)}function Sn(e,t=60,n="\u2500"){let o=n.repeat(Math.max(1,t));return Hk(e,o)}function H(e,t){return qs(e)?`${`${w(e,"[")}${he(e,"omnish")}${w(e,"]")}`} ${t}`:`[omnish] ${t}`}function P(e,t){return qs(e)?`${`${zs(e,"[omnish]")}`} ${t}`:`[omnish] ${t}`}function Rp(e,t){let n=[];for(let o of e)switch(o.kind){case"title":n.push("",Re(t,o.text),"");break;case"sub":n.push("",jk(t,o.text),"");break;case"gap":n.push("");break;case"p":n.push(S(t,o.text));break;case"bullet":n.push(`${w(t,"\u2022")} ${S(t,o.text)}`);break}return n.join(`
36
+ `).replace(/^\n+/,"").trimEnd()}ue();j();Kn();Yn();function Ys(e){return(e&4)!==0}function Al(e){return(e&2)!==0}var Ep={error:3,warn:2,info:1};function Pp(e,t){let n=Ep[t];return e.filter(o=>Ep[o.severity]>=n)}function tn(e,t={}){let n=[],o=t.gatewayMode??e.gatewayMode,r=o==="whatsapp"||o==="both",s=o==="telegram"||o==="both";if(e.allowFrom.includes("*")&&n.push({severity:"error",code:"allow-wildcard",message:'allowFrom contains "*" \u2014 this must never be used; remove it from config immediately.',detail:`Edit ${U} and delete wildcard entries.`,fixHint:`Edit ${U} and remove any "*" entries from allowFrom.`}),r&&e.allowFrom.length===0&&n.push({severity:"warn",code:"allow-wa-empty",message:"allowFrom is empty \u2014 no WhatsApp identity can run commands.",detail:"Add your number: omnish allow +<E164>",fixHint:"omnish allow +<your_E164_number>"}),s&&e.telegramAllowFrom.length===0&&n.push({severity:"warn",code:"allow-tg-empty",message:"telegramAllowFrom is empty \u2014 no Telegram user can run commands.",detail:"Add your user id: omnish allow tg:<id>",fixHint:"omnish allow tg:<your_telegram_user_id>"}),e.recipesAllowDangerousBuiltins&&n.push({severity:"warn",code:"recipes-dangerous",message:"recipesAllowDangerousBuiltins is enabled \u2014 built-in recipe helpers may add permissive Claude Code flags.",fixHint:`Set recipesAllowDangerousBuiltins to false in ${U} unless you trust every recipe.`}),e.serviceInstallFromChat&&n.push({severity:"warn",code:"service-install-from-chat",message:"serviceInstallFromChat is enabled \u2014 allowlisted users can run /service install and write user-level systemd or LaunchAgent units.",fixHint:`Set serviceInstallFromChat to false in ${U} unless you trust every allowlisted identity.`}),e.mediaInstallFromChat&&n.push({severity:"warn",code:"media-install-from-chat",message:"mediaInstallFromChat is enabled \u2014 allowlisted users can run /dl install and download binaries into the omnish data directory.",fixHint:`Set mediaInstallFromChat to false in ${U} unless you trust every allowlisted identity.`}),!Tp.isAbsolute(e.shell))n.push({severity:"warn",code:"shell-not-absolute",message:`Shell path is not absolute: ${e.shell}`,fixHint:`Set "shell" to an absolute path (e.g. /bin/bash) in ${U}.`});else try{en.existsSync(e.shell)||n.push({severity:"error",code:"shell-missing",message:`Configured shell does not exist: ${e.shell}`,fixHint:`Install the shell or update "shell" in ${U} to a valid binary.`})}catch{n.push({severity:"warn",code:"shell-stat-failed",message:`Could not verify shell path: ${e.shell}`,fixHint:`Check permissions and that "shell" in ${U} points to a real file.`})}if(e.fileReceiveRootMode==="fixed"){let a=e.fileReceiveRootPath.trim();a?Tp.isAbsolute(a)||n.push({severity:"error",code:"receive-fixed-not-absolute",message:`fileReceiveRootPath must be absolute when fileReceiveRootMode is "fixed": ${a}`,fixHint:`Use an absolute path for fileReceiveRootPath in ${U}.`}):n.push({severity:"error",code:"receive-fixed-empty",message:'fileReceiveRootMode is "fixed" but fileReceiveRootPath is empty.',fixHint:`Set fileReceiveRootPath to an absolute directory in ${U}, or change fileReceiveRootMode.`})}if(vn.platform!=="win32"){try{if(en.existsSync(U)){let c=en.statSync(U);(Ys(c.mode)||Al(c.mode))&&n.push({severity:"warn",code:"config-permissions",message:"config.json is accessible to users other than the owner (group/world).",detail:`Recommended: chmod 600 ${U}`,fixHint:`chmod 600 ${U}`})}}catch{}(typeof vn.getuid=="function"?vn.getuid():null)===0&&n.push({severity:"warn",code:"run-as-root",message:"Process is running as root \u2014 allowlisted remote access has full system privileges.",fixHint:"Run omnish as a non-root user (systemd user service, container user, etc.)."});let l=!1;try{if(en.existsSync(W)){let c=en.statSync(W);(Ys(c.mode)||Al(c.mode))&&(l=!0,n.push({severity:"warn",code:"data-dir-permissive",message:"Omnish data directory is accessible to group or other users \u2014 auth, jobs, and logs may be exposed.",detail:`Recommended: chmod 700 ${W}`,fixHint:`chmod 700 ${W}`}))}}catch{}try{if(!l&&en.existsSync(wt)){let c=en.statSync(wt);(Ys(c.mode)||Al(c.mode))&&n.push({severity:"warn",code:"jobs-dir-permissive",message:"Jobs log directory is accessible to group or other users \u2014 command output may leak.",detail:`Recommended: chmod 700 ${wt}`,fixHint:`chmod 700 ${wt}`})}}catch{}try{if(en.existsSync(le)){let c=en.statSync(le);Ys(c.mode)&&n.push({severity:"warn",code:"auth-dir-world-readable",message:"WhatsApp auth directory is readable by others \u2014 session material may be exposed.",detail:`Recommended: chmod 700 ${le}`,fixHint:`chmod 700 ${le}`})}}catch{}}return(typeof vn.env.TELEGRAM_BOT_TOKEN=="string"?vn.env.TELEGRAM_BOT_TOKEN.trim():"")&&n.push({severity:"info",code:"telegram-token-env",message:"TELEGRAM_BOT_TOKEN is set in the environment; it overrides config.json until unset.",fixHint:"Unset TELEGRAM_BOT_TOKEN in the shell/service env if you want config.json to apply."}),s&&!Ie(e)&&n.push({severity:"error",code:"telegram-no-token",message:"Telegram is enabled for this gateway but no bot token is configured.",detail:"Set telegramBotToken in config or TELEGRAM_BOT_TOKEN.",fixHint:`Set telegramBotToken in ${U} or export TELEGRAM_BOT_TOKEN=<token>.`}),e.clusterEnabled&&s&&n.push({severity:"info",code:"cluster-telegram-tokens",message:"Cluster mode with Telegram: use a distinct BotFather bot/token per omnish host if several gateways run Telegram \u2014 the same token cannot be long-polled twice.",fixHint:"Create separate bots for each machine or run Telegram on fewer hosts."}),e.platformToken.trim()&&n.push({severity:"info",code:"platform-token-config",message:"platformToken is stored in config.json (same trust boundary as your shell account).",fixHint:"Use OMNISH_TOKEN in the environment for CI/ephemeral hosts; env overrides config."}),(vn.env.OMNISH_TOKEN?.trim()||vn.env.OMNISH_TUNNEL_TOKEN?.trim()||vn.env.OMNISH_DEVICE_TOKEN?.trim())&&n.push({severity:"info",code:"platform-token-env",message:"OMNISH_TOKEN (or OMNISH_TUNNEL_TOKEN / OMNISH_DEVICE_TOKEN) is set in the environment.",fixHint:"Unset platform token env vars if you want config.json / tunnel-auth.json to apply."}),e.tunnelEnabled&&(n.push({severity:"warn",code:"tunnel-enabled",message:"Chat tunneling is enabled \u2014 allowlisted users can publish public URLs to local HTTP/TCP services.",fixHint:"Disable tunnelEnabled or restrict allowlists if you do not want remote tunnel control."}),Dt()||n.push({severity:"warn",code:"tunnel-no-token",message:"Chat tunneling is enabled but no tunnel token is configured.",fixHint:"Run `omnish tunnel login` or set OMNISH_TUNNEL_TOKEN for the gateway process."})),e.tunnelRelayUrl.trim()&&e.tunnelRelayUrl.trim()!==Le&&n.push({severity:"info",code:"tunnel-relay-custom",message:`Custom tunnel relay configured: ${e.tunnelRelayUrl.trim()}`,fixHint:"Use the default relay only if you trust the operator of that endpoint."}),n}function Pr(e){return e.some(t=>t.severity==="error")}function Gk(e,t){switch(t){case"error":return zs(e,"[ERROR]");case"warn":return ke(e,"[WARN]");case"info":return w(e,"[INFO]")}}function Il(e,t,n,o){if(o.length!==0){t.push(K(e,`${n} (${o.length}):`));for(let r of o)t.push(` ${Gk(e,r.severity)} ${w(e,`${r.code}:`)} ${S(e,r.message)}`),r.detail&&t.push(` ${w(e,r.detail)}`),r.fixHint&&t.push(` ${w(e,`Fix: ${r.fixHint}`)}`);t.push("")}}function Ol(e,t){if(e.length===0)return[`${Re(t,"Security check:")} ${S(t,"no issues reported by automated rules.")}`,"",w(t,"Note: allowlisted remote shell access is still equivalent to sharing credentials with those identities.")].join(`
37
+ `);let n=e.filter(a=>a.severity==="error"),o=e.filter(a=>a.severity==="warn"),r=e.filter(a=>a.severity==="info"),i=[`${Re(t,"Security check:")} `+S(t,`${n.length} error(s), ${o.length} warning(s), ${r.length} note(s).`),""];return Il(t,i,"Errors",n),Il(t,i,"Warnings",o),Il(t,i,"Notes",r),i.push(w(t,"Allowlisted identities can run commands as this user; treat them like passwords.")),i.join(`
38
+ `).trimEnd()}function Ll(e){return{errors:e.filter(t=>t.severity==="error").length,warns:e.filter(t=>t.severity==="warn").length,infos:e.filter(t=>t.severity==="info").length}}function $p(e){return`${JSON.stringify({findings:e,summary:Ll(e)},null,2)}
39
+ `}function Mp(e,t){let n=e.filter(a=>a.severity==="error").length,o=e.filter(a=>a.severity==="warn").length,r=e.filter(a=>a.severity==="info").length;if(n===0&&o===0&&r===0)return"security: ok (no automated findings)";let s=[];n&&s.push(`${n} error(s)`),o&&s.push(`${o} warning(s)`),r&&s.push(`${r} note(s)`);let i=t??"run `omnish security` for details";return`security: ${s.join(", ")} \u2014 ${i}`}function Ap(e,t,n){let o=e.filter(l=>l.severity==="error").length,r=e.filter(l=>l.severity==="warn").length,s=e.filter(l=>l.severity==="info").length;if(o===0&&r===0&&s===0)return`${Re(t,"security:")} ${S(t,"ok (no automated findings)")}`;let i=[];o&&i.push(zs(t,`${o} error(s)`)),r&&i.push(ke(t,`${r} warning(s)`)),s&&i.push(w(t,`${s} note(s)`));let a=n??"run `omnish security` for details";return`${Re(t,"security:")} ${i.join(", ")} ${w(t,`\u2014 ${a}`)}`}var Ne="- ";function p(e){return{wa:e,tg:e}}function we(e,t){return{wa:e,tg:t,tgHtml:!0}}function pe(e,t){return t==="whatsapp"?{text:e.wa}:e.tgHtml?{text:e.tg,parseModeHtml:!0}:{text:e.tg}}function Z(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function $e(e){return e.replace(/[*_~`]/g,t=>({"*":"\u2217",_:"\uFF3F","~":"\u02DC","`":"\u2032"})[t]??t)}function qk(e){let t=[];for(let n of e)switch(n.kind){case"title":t.push("",`*${n.text}*`,"");break;case"sub":t.push("",`_${n.text}_`,"");break;case"gap":t.push("");break;case"p":t.push(n.text);break;case"bullet":t.push(`${Ne}${n.text}`);break}return t.join(`
40
+ `).replace(/^\n+/,"").trimEnd()}function zk(e){let t=[];for(let n of e)switch(n.kind){case"title":t.push("",`<b>${Z(n.text)}</b>`,"");break;case"sub":t.push("",`<i>${Z(n.text)}</i>`,"");break;case"gap":t.push("");break;case"p":t.push(Z(n.text));break;case"bullet":t.push(`\u2022 ${Z(n.text)}`);break}return t.join(`
41
+ `).replace(/^\n+/,"").trimEnd()}function X(e){return{wa:qk(e),tg:zk(e),tgHtml:!0}}function $o(e){return[{kind:"title",text:"Omnish \u2014 quick help"},{kind:"p",text:"Per-chat shell cwd is stored under your data dir (see /wa help)."},{kind:"gap"},{kind:"sub",text:"Run commands"},{kind:"bullet",text:`${e.commandPrefix}<command> \u2014 sync shell in session cwd (timeout ${e.syncTimeoutMs} ms)`},{kind:"bullet",text:`${e.commandPrefix}cd <dir> \u2014 change session cwd (${e.commandPrefix}cd alone \u2192 home)`},{kind:"bullet",text:"!!start | !!stop \u2014 free shell (plain \u2192 sync shell only when no focused /apps session); space optional"},{kind:"bullet",text:"/bg <cmd> \u2014 background job; optional -n name; /jobs, /log, /tail, /kill (id or name)"},{kind:"bullet",text:e.tunnelEnabled?"/tunnel login|logout|status|http|tcp \u2014 login/status anytime; /tunnels needs tunnelEnabled":"/tunnel expose/list off until tunnelEnabled true (login/status still work)"},{kind:"bullet",text:"/apps \u2026 \u2014 interactive app sessions (/apps help)"},{kind:"bullet",text:"/send selectors \u2014 host files \u2192 chat (/file); caption: selectors -- note"},{kind:"bullet",text:"/files \u2014 full help for /send and saving inbound media"},{kind:"bullet",text:"/receive \u2014 set this chat\u2019s inbound folder (e.g. session cwd)"},{kind:"bullet",text:"/computers \xB7 /pcs \xB7 /c \u2014 chat-driven cluster (/c here to take over, /c help)"},{kind:"bullet",text:"/config \u2014 view or change server settings (/config help, /config keys)"},{kind:"bullet",text:"/service \u2014 background service status, install hints, logs (/service help)"},{kind:"bullet",text:"/dl <url> \u2014 auto file, video, or page\u2192markdown (background; /dl help)"},{kind:"bullet",text:"/dlf <url> \u2014 force file \xB7 /dlv <url> \u2014 force video (yt-dlp)"},{kind:"bullet",text:"/tr <url|path> \u2014 transcribe (background job)"},{kind:"bullet",text:"/edit <url|path> \u2014 trim or convert (background job; /edit help)"},{kind:"bullet",text:"/shortcut \u2014 this chat & shared shortcuts (/shortcut help); !name or /name expands chat override first, then shared"},{kind:"bullet",text:"/run \u2014 recipe-based task runs (/r); per-chat or gateway-shared /run add \u2014 /run list, /run help"},{kind:"bullet",text:"/cowork | /cw \u2014 scheduled & on-demand shell tasks; logs to ~/Cowork/\u2026 (/cowork help)"},{kind:"bullet",text:"/watch \u2014 OS event eye: fs/pkg/svc alerts; pause/stop/rm, excludes (/watch help; docs/features/watch.md)"},{kind:"gap"},{kind:"sub",text:"Setup & gateway"},{kind:"bullet",text:"/wa help \u2014 WhatsApp link, allowlist"},{kind:"bullet",text:"/tg help \u2014 Telegram; /tg token <paste>"},{kind:"bullet",text:"/reload | /restart \u2014 apply config"},{kind:"bullet",text:"/updates \u2014 npm latest + optional notice URL; /updates cached \u2014 last snapshot"},{kind:"bullet",text:"/security \u2014 posture report; /security summary; /security tips; /security help"},{kind:"bullet",text:"/gateway | /gw | /mode \u2014 show or set gatewayMode"},{kind:"bullet",text:"/allow +E164 | /allow tg:id \u2014 allowlist; /deny \u2026; /allowlist"},{kind:"bullet",text:"/s q <topic> \u2014 find guides and next commands (/s help, same as /search)"},{kind:"bullet",text:"/help \u2014 this message"},{kind:"gap"},{kind:"sub",text:"Terms"},{kind:"bullet",text:"Gateway = omnish on this host; shell runs here. Standalone = messengers on this host; attached = communication layer routes chat here."},{kind:"bullet",text:'Communication layer (when shipped) = link chats once, device token on this CLI; shell stays here. Standalone = no layer. /service "platform" = OS.'}]}function $r(e){let t=e.mediaInstallFromChat?[{kind:"bullet",text:"/dl install [--whisper] \u2014 download tools into ~/.omnish/bin"}]:[{kind:"bullet",text:"/dl install \u2014 off from chat. Host: omnish pull install \xB7 or /config set mediaInstallFromChat true"}];return[{kind:"title",text:"Media \u2014 /dl, /tr, /edit"},{kind:"p",text:"Files are sent to chat by default. Opt out: /config set mediaSendFiles false (paths only)."},{kind:"bullet",text:"/dl <url> \u2014 auto: file (HTTP), video (yt-dlp ~1000 sites), or HTML\u2192markdown"},{kind:"bullet",text:"/dlf <url> \u2014 force HTTP file \xB7 /dlv <url> \u2014 force yt-dlp video"},{kind:"bullet",text:"/tr <url|path> \u2014 Whisper transcript + .srt (+ video if URL); background job"},{kind:"bullet",text:"/edit <url|path> [--from 1:30] [--to 2:00] [--format mp3] [--audio-only] (background job)"},{kind:"bullet",text:"/dl doctor \xB7 /dl setup \xB7 /dl install"},...t,{kind:"bullet",text:e.mediaUrlAutoDl?"Lone URL in chat \u2192 auto /dl (file, video, or markdown)":"mediaUrlAutoDl off \u2014 use /dl <url>"},{kind:"bullet",text:"All media jobs run in background \u2014 /log, /tail; optional --notify on /dl, /tr, /edit"},{kind:"bullet",text:"Step progress on by default (progressUpdates); disable via /config set progressUpdates false"},{kind:"gap"},{kind:"p",text:"Host: omnish pull doctor \xB7 omnish pull install [--whisper]"},{kind:"p",text:"docs/features/media-commands.md"}]}function Ip(e){let t=e.serviceInstallFromChat?[{kind:"bullet",text:"/service install \u2014 user-level systemd (Linux) or LaunchAgent (macOS)"},{kind:"bullet",text:"/service uninstall \u2014 remove that unit"}]:[{kind:"bullet",text:"/service install / uninstall \u2014 off by default. Enable: /config set serviceInstallFromChat true (same trust as shell)"}];return[{kind:"title",text:"Service and boot"},{kind:"p",text:"Inspect the gateway, get OS-specific install steps, or (if enabled) write the user service from chat. Same from the host terminal: omnish service help."},{kind:"p",text:'"platform" in status means your OS (Linux/macOS/Windows), not a hosted omnish account.'},{kind:"bullet",text:"/service status \u2014 platform, data dir, pidfile, node + entry script"},{kind:"bullet",text:"/service instructions \u2014 copy-paste commands for this machine"},{kind:"bullet",text:"/service logs [n] \u2014 tail default gateway log (default 80 lines)"},...t,{kind:"gap"},{kind:"p",text:"See https://omnish.dev and docs/guides/background-and-boot.md."}]}function Op(){return[{kind:"title",text:"Chat config"},{kind:"p",text:`Same trust as shell \u2014 allowlisted senders can change many keys saved to ${U}.`},{kind:"gap"},{kind:"bullet",text:"/config show \u2014 full snapshot (telegramBotToken masked)"},{kind:"bullet",text:"/config get <key> \u2014 single field"},{kind:"bullet",text:"/config set <key> <value> \u2014 values can be quoted for paths with spaces"},{kind:"bullet",text:"/config keys \u2014 allowed keys for /config set (includes tunnel*, webhook*, watch*, pull*, chatLlm*, cluster*, \u2026)"},{kind:"gap"},{kind:"p",text:"After edits: /reload while omnish run is active (needed for gatewayMode and telegramBotToken)."}]}function Nl(){return[{kind:"title",text:"Gateway mode"},{kind:"p",text:"Which transports omnish uses:"},{kind:"bullet",text:"whatsapp (wa, w) \u2014 WhatsApp DMs only"},{kind:"bullet",text:"telegram (tg, t) \u2014 Telegram bot only"},{kind:"bullet",text:"both (all, b) \u2014 WhatsApp + Telegram"},{kind:"gap"},{kind:"sub",text:"Commands"},{kind:"bullet",text:"/gateway \u2014 current mode + hints (includes last update snapshot if any)"},{kind:"bullet",text:"/gateway telegram \u2014 save (auto-reloads Telegram if gateway is up)"},{kind:"bullet",text:"/gw both \u2014 short form; /mode wa \u2014 same"},{kind:"gap"},{kind:"p",text:`Config: ${U}`}]}function Lp(e){let t=["*Gateway status*","",`Mode: *${e.gatewayMode}*`,"- whatsapp \u2014 WhatsApp only","- telegram \u2014 Telegram only","- both \u2014 both channels","",`WhatsApp auth: ${e.authPresent?"present":"missing (omnish link)"}`,`Telegram token: ${e.tokenSet?"set":"not set"}`,`Allowlists: ${e.allowN} WA \xB7 ${e.tgAllowN} Telegram`,""];e.updateBrief&&t.push(`Updates: ${$e(e.updateBrief)}`,""),t.push("Change: /gateway both \xB7 /gw tg","/updates \u2014 npm + optional notice URL",`Config: ${$e(U)}`);let n=["<b>Gateway status</b>","",`<b>${Z(e.gatewayMode)}</b> <i>current mode</i>`,"\u2022 whatsapp \u2014 WhatsApp only","\u2022 telegram \u2014 Telegram only","\u2022 both \u2014 both channels","",`${e.authPresent?"\u2713":"\u26A0"} WhatsApp auth: ${e.authPresent?"present":"missing \u2014 run omnish link"}`,`${e.tokenSet?"\u2713":"\u26A0"} Telegram token: ${e.tokenSet?"set":"not set"}`,`Allowlists: ${e.allowN} WA \xB7 ${e.tgAllowN} Telegram`,""];return e.updateBrief&&n.push(`<b>Updates</b> ${Z(e.updateBrief)}`,""),n.push("<i>Change:</i> /gateway both \xB7 /gw tg","<i>/updates</i> \u2014 npm + optional notice URL",Z(`Config: ${U}`)),we(t.join(`
42
42
  `),n.join(`
43
- `))}function Mr(e){let t=["*Update check*","",`Running: *${$e(e.runningVersion)}*`,`Checked (UTC): ${e.checkedAtIso}`,`npm package: ${$e(e.registryPackage)}`];e.registryError?t.push("",`Registry: ${$e(e.registryError)}`):e.registryLatest&&t.push("",e.updateAvailable?`*Newer version on npm:* ${$e(e.registryLatest)} (upgrade when ready).`:`npm latest: ${$e(e.registryLatest)} (this install is current or newer).`),e.infoError?t.push("",`Notice URL: ${$e(e.infoError)}`):e.infoMessage&&(t.push("",`Notice: ${$e(e.infoMessage)}`),e.infoLink&&t.push($e(e.infoLink))),t.push("","Config: updateCheckEnabled, updateCheckIntervalMs, updateCheckPackageName, updateInfoUrl");let n=["<b>Update check</b>","",`<b>Running</b> <code>${Z(e.runningVersion)}</code>`,`<b>Checked (UTC)</b> ${Z(e.checkedAtIso)}`,`<b>npm package</b> <code>${Z(e.registryPackage)}</code>`];return e.registryError?n.push("",`<b>Registry</b> ${Z(e.registryError)}`):e.registryLatest&&n.push("",e.updateAvailable?`<b>Newer on npm</b> <code>${Z(e.registryLatest)}</code>`:`<b>npm latest</b> <code>${Z(e.registryLatest)}</code> (current or newer here)`),e.infoError?n.push("",`<b>Notice URL</b> ${Z(e.infoError)}`):e.infoMessage&&(n.push("",`<b>Notice</b> ${Z(e.infoMessage)}`),e.infoLink&&n.push(Z(e.infoLink))),n.push("","<i>Config keys:</i> updateCheckEnabled, updateCheckIntervalMs, updateCheckPackageName, updateInfoUrl"),ye(t.join(`
43
+ `))}function Mr(e){let t=["*Update check*","",`Running: *${$e(e.runningVersion)}*`,`Checked (UTC): ${e.checkedAtIso}`,`npm package: ${$e(e.registryPackage)}`];e.registryError?t.push("",`Registry: ${$e(e.registryError)}`):e.registryLatest&&t.push("",e.updateAvailable?`*Newer version on npm:* ${$e(e.registryLatest)} (upgrade when ready).`:`npm latest: ${$e(e.registryLatest)} (this install is current or newer).`),e.infoError?t.push("",`Notice URL: ${$e(e.infoError)}`):e.infoMessage&&(t.push("",`Notice: ${$e(e.infoMessage)}`),e.infoLink&&t.push($e(e.infoLink))),t.push("","Config: updateCheckEnabled, updateCheckIntervalMs, updateCheckPackageName, updateInfoUrl");let n=["<b>Update check</b>","",`<b>Running</b> <code>${Z(e.runningVersion)}</code>`,`<b>Checked (UTC)</b> ${Z(e.checkedAtIso)}`,`<b>npm package</b> <code>${Z(e.registryPackage)}</code>`];return e.registryError?n.push("",`<b>Registry</b> ${Z(e.registryError)}`):e.registryLatest&&n.push("",e.updateAvailable?`<b>Newer on npm</b> <code>${Z(e.registryLatest)}</code>`:`<b>npm latest</b> <code>${Z(e.registryLatest)}</code> (current or newer here)`),e.infoError?n.push("",`<b>Notice URL</b> ${Z(e.infoError)}`):e.infoMessage&&(n.push("",`<b>Notice</b> ${Z(e.infoMessage)}`),e.infoLink&&n.push(Z(e.infoLink))),n.push("","<i>Config keys:</i> updateCheckEnabled, updateCheckIntervalMs, updateCheckPackageName, updateInfoUrl"),we(t.join(`
44
44
  `),n.join(`
45
- `))}function Ip(e){return[{kind:"title",text:"WhatsApp setup"},{kind:"p",text:"Do these on the machine running omnish (QR is shown in the terminal, not in chat)."},{kind:"gap"},{kind:"sub",text:"Steps"},{kind:"bullet",text:"Install omnish; build native deps (node-pty, Baileys)."},{kind:"bullet",text:`Data dir: ${W} (override: OMNISH_HOME). Auth: <data-dir>/auth/`},{kind:"bullet",text:"Run: omnish link \u2014 scan QR in WhatsApp \u2192 Linked devices."},{kind:"bullet",text:"Allow your number: omnish allow +<E164>"},{kind:"bullet",text:`Set gatewayMode to "whatsapp" or "both" in ${U}`},{kind:"bullet",text:"Run: omnish run \u2014 then use !cmd or /help here."},{kind:"gap"},{kind:"sub",text:"Troubleshooting"},{kind:"bullet",text:"Denied in logs but allowlist OK: may be LID mapping \u2014 try omnish run --verbose."},{kind:"bullet",text:"401 on link: omnish link --force after pnpm approve-builds && pnpm install"},{kind:"gap"},{kind:"p",text:`Current gatewayMode: ${e.gatewayMode}`}]}function Op(e){let t=!!Ie(e);return[{kind:"title",text:"Telegram setup"},{kind:"p",text:"Configure on the host that runs omnish."},{kind:"gap"},{kind:"sub",text:"Steps"},{kind:"bullet",text:"@BotFather \u2192 /newbot \u2014 copy the API token."},{kind:"bullet",text:`Token: /tg token <paste>, or ${U}, or TELEGRAM_BOT_TOKEN env (env wins).`},{kind:"bullet",text:'Your numeric id: @userinfobot / @getidsbot, or logs on "telegram denied".'},{kind:"bullet",text:"Allow: omnish allow tg:<user_id>"},{kind:"bullet",text:`gatewayMode: "telegram" or "both" in ${U}; list ids in telegramAllowFrom.`},{kind:"bullet",text:"omnish run \u2014 then DM the bot with !cmd or /help."},{kind:"bullet",text:"After edits: /reload or /gateway both (saves + reloads when running)."},{kind:"gap"},{kind:"sub",text:"Notes"},{kind:"bullet",text:"Private text DMs only; groups ignored. Token stays secret."},{kind:"bullet",text:"/tg token stores token in config; chat history may retain it \u2014 prefer SSH/editor if worried."},{kind:"gap"},{kind:"p",text:`Token on host: ${t?"set (env or config)":"not set \u2014 add telegramBotToken or TELEGRAM_BOT_TOKEN"}`},{kind:"p",text:`gatewayMode: ${e.gatewayMode} \xB7 telegramAllowFrom: ${e.telegramAllowFrom.length} entries`}]}function Lp(e){let t=["*Allowlists*",""],n=["<b>Allowlists</b>",""];for(let o of e){let r=o.items.length?o.items.join(", "):"(none)";t.push(`*${o.label}* (${o.items.length})`,$e(r),""),n.push(`<b>${Z(o.label)}</b> (${o.items.length})`,Z(r),"")}return ye(t.join(`
45
+ `))}function Np(e){return[{kind:"title",text:"WhatsApp setup"},{kind:"p",text:"Do these on the machine running omnish (QR is shown in the terminal, not in chat)."},{kind:"gap"},{kind:"sub",text:"Steps"},{kind:"bullet",text:"Install omnish; build native deps (node-pty, Baileys)."},{kind:"bullet",text:`Data dir: ${W} (override: OMNISH_HOME). Auth: <data-dir>/auth/`},{kind:"bullet",text:"Run: omnish link \u2014 scan QR in WhatsApp \u2192 Linked devices."},{kind:"bullet",text:"Allow your number: omnish allow +<E164>"},{kind:"bullet",text:`Set gatewayMode to "whatsapp" or "both" in ${U}`},{kind:"bullet",text:"Run: omnish run \u2014 then use !cmd or /help here."},{kind:"gap"},{kind:"sub",text:"Troubleshooting"},{kind:"bullet",text:"Denied in logs but allowlist OK: may be LID mapping \u2014 try omnish run --verbose."},{kind:"bullet",text:"401 on link: omnish link --force after pnpm approve-builds && pnpm install"},{kind:"gap"},{kind:"p",text:`Current gatewayMode: ${e.gatewayMode}`}]}function _p(e){let t=!!Ie(e);return[{kind:"title",text:"Telegram setup"},{kind:"p",text:"Configure on the host that runs omnish."},{kind:"gap"},{kind:"sub",text:"Steps"},{kind:"bullet",text:"@BotFather \u2192 /newbot \u2014 copy the API token."},{kind:"bullet",text:`Token: /tg token <paste>, or ${U}, or TELEGRAM_BOT_TOKEN env (env wins).`},{kind:"bullet",text:'Your numeric id: @userinfobot / @getidsbot, or logs on "telegram denied".'},{kind:"bullet",text:"Allow: omnish allow tg:<user_id>"},{kind:"bullet",text:`gatewayMode: "telegram" or "both" in ${U}; list ids in telegramAllowFrom.`},{kind:"bullet",text:"omnish run \u2014 then DM the bot with !cmd or /help."},{kind:"bullet",text:"After edits: /reload or /gateway both (saves + reloads when running)."},{kind:"gap"},{kind:"sub",text:"Notes"},{kind:"bullet",text:"Private text DMs only; groups ignored. Token stays secret."},{kind:"bullet",text:"/tg token stores token in config; chat history may retain it \u2014 prefer SSH/editor if worried."},{kind:"gap"},{kind:"p",text:`Token on host: ${t?"set (env or config)":"not set \u2014 add telegramBotToken or TELEGRAM_BOT_TOKEN"}`},{kind:"p",text:`gatewayMode: ${e.gatewayMode} \xB7 telegramAllowFrom: ${e.telegramAllowFrom.length} entries`}]}function Fp(e){let t=["*Allowlists*",""],n=["<b>Allowlists</b>",""];for(let o of e){let r=o.items.length?o.items.join(", "):"(none)";t.push(`*${o.label}* (${o.items.length})`,$e(r),""),n.push(`<b>${Z(o.label)}</b> (${o.items.length})`,Z(r),"")}return we(t.join(`
46
46
  `).trimEnd(),n.join(`
47
- `).trimEnd())}function Nl(e){let t=[{label:"allowFrom (WhatsApp)",items:e.allowFrom},{label:"telegramAllowFrom",items:e.telegramAllowFrom}],n=["*Allowlist updated*","","Saved to config.",""],o=["<b>Allowlist updated</b>","","Saved to config.",""];for(let r of t){let s=r.items.length?r.items.join(", "):"(none)";n.push(`*${r.label}* (${r.items.length})`,$e(s),""),o.push(`<b>${Z(r.label)}</b> (${r.items.length})`,Z(s),"")}return ye(n.join(`
47
+ `).trimEnd())}function _l(e){let t=[{label:"allowFrom (WhatsApp)",items:e.allowFrom},{label:"telegramAllowFrom",items:e.telegramAllowFrom}],n=["*Allowlist updated*","","Saved to config.",""],o=["<b>Allowlist updated</b>","","Saved to config.",""];for(let r of t){let s=r.items.length?r.items.join(", "):"(none)";n.push(`*${r.label}* (${r.items.length})`,$e(s),""),o.push(`<b>${Z(r.label)}</b> (${r.items.length})`,Z(s),"")}return we(n.join(`
48
48
  `).trimEnd(),o.join(`
49
- `).trimEnd())}function Np(e){let t=["*Token saved*","","telegramBotToken written to config (not echoed).",`Config: ${$e(U)}`,...e.flatMap(o=>["",o]),"","Send /reload so the gateway picks it up."].join(`
49
+ `).trimEnd())}function Dp(e){let t=["*Token saved*","","telegramBotToken written to config (not echoed).",`Config: ${$e(U)}`,...e.flatMap(o=>["",o]),"","Send /reload so the gateway picks it up."].join(`
50
50
  `),n=["<b>Token saved</b>","","telegramBotToken written to config (not echoed).",Z(`Config: ${U}`),...e.map(o=>`<i>${Z(o)}</i>`),"","Send /reload so the gateway picks it up."].join(`
51
51
  `)+`
52
- `;return ye(t.trimEnd(),n.trimEnd())}function _p(){return X([{kind:"title",text:"That does not look like a bot token"},{kind:"p",text:"Expected from @BotFather: one token, no spaces, like 123456789:AA_heG\u2026"},{kind:"bullet",text:"Example: /tg token 123456789:AAAbcd\u2026"}])}function Fp(e,t,n){let o=t?`
52
+ `;return we(t.trimEnd(),n.trimEnd())}function Wp(){return X([{kind:"title",text:"That does not look like a bot token"},{kind:"p",text:"Expected from @BotFather: one token, no spaces, like 123456789:AA_heG\u2026"},{kind:"bullet",text:"Example: /tg token 123456789:AAAbcd\u2026"}])}function Up(e,t,n){let o=t?`
53
53
 
54
54
  ${t}`:n?`
55
55
 
@@ -63,54 +63,54 @@ Start omnish run on the host to apply (or /reload from a running gateway).`:"",s
63
63
  ${$e(U)}${o}`,i=`<b>gatewayMode saved</b>
64
64
 
65
65
  <code>${Z(e)}</code>
66
- ${Z(U)}${r}`;return ye(s,i)}function Dp(){return X([{kind:"title",text:"/allow \u2014 add to allowlist"},{kind:"bullet",text:"/allow +<E164> \u2014 WhatsApp (country code, no spaces)"},{kind:"bullet",text:"/allow tg:<user_id> \u2014 Telegram numeric id"},{kind:"gap"},{kind:"p",text:"Examples: /allow +15551234567 \xB7 /allow tg:987654321"}])}function Wp(){return X([{kind:"title",text:"/deny \u2014 remove from allowlist"},{kind:"p",text:"Same forms as /allow: +E164 or tg:id"}])}function Up(){return X([{kind:"title",text:"/bg \u2014 background job"},{kind:"p",text:"Usage: /bg [flags] <shell command>"},{kind:"bullet",text:"Optional name: /bg -n mybuild npm run build \xB7 /bg --name mybuild \u2026 \xB7 /bg --name=mybuild \u2026"},{kind:"bullet",text:"Notify on completion: /bg --notify <cmd> or /bg -N <cmd> \u2014 sends a message when the job finishes (with exit status)."},{kind:"bullet",text:"Combine flags: /bg -N -n deploy git pull && docker compose up -d"},{kind:"bullet",text:"Then use /log mybuild, /tail mybuild, /kill mybuild (or the 8-char id). Names: letters, digits, . _ - up to 64 chars."},{kind:"bullet",text:"Example: /bg sleep 30 && echo done"}])}function _l(){return X([{kind:"title",text:"/send \u2014 push a host file to this chat"},{kind:"p",text:"Usage: /send <selectors> [-- caption] \u2014 selectors resolve from session cwd."},{kind:"bullet",text:"/send ./photo.png"},{kind:"bullet",text:"/send ./clip1.mp4,./clip2.mp4 -- Launch set"},{kind:"bullet",text:"/send **/*.mp4"},{kind:"bullet",text:"/send /abs/path/report.pdf -- Q4 draft"},{kind:"bullet",text:"Selectors: file1,file2 | *.ext | **/*.ext"},{kind:"bullet",text:"Alias: /file \u2026"},{kind:"gap"},{kind:"p",text:"Caps: fileSendMaxBytes / fileReceiveMaxBytes (0 = no omnish cap). Where inbound media is saved: fileReceiveRootMode + fileReceiveRootPath / fileInboxSubdir \u2014 send /files for details."}])}function Fl(){return X([{kind:"title",text:"Files \u2014 send & receive"},{kind:"sub",text:"Host \u2192 chat"},{kind:"bullet",text:"/send <selectors> or /file \u2026 \u2014 selectors support file1,file2, *.ext, **/*.ext from this chat\u2019s session cwd (same as ! shell); optional caption after -- "},{kind:"bullet",text:"fileSendMaxBytes in config caps outbound size (0 = no omnish cap)."},{kind:"gap"},{kind:"sub",text:"Chat \u2192 host"},{kind:"bullet",text:"Send a photo, video, document, audio, etc. in this DM; omnish saves it and replies with Saved: <path> (or an error)."},{kind:"bullet",text:"Folders: <root>/<peer>/<YYYY-MM-DD>/<filename> \u2014 root comes from config below."},{kind:"bullet",text:"fileReceiveRootMode: downloads (home/Downloads/Omnish) \xB7 omnishData (data dir + fileInboxSubdir) \xB7 sessionCwd \xB7 processCwd \xB7 fixed (needs absolute fileReceiveRootPath)."},{kind:"bullet",text:"fileReceiveMaxBytes \u2014 inbound size cap (0 = no omnish cap)."},{kind:"gap"},{kind:"p",text:`Edit config on the host (${U}) then use /reload. Tip: omnish status shows the data directory.`},{kind:"gap"},{kind:"p",text:"Per-chat folder: /receive here saves inbound files under your session cwd (!cd); /receive default clears."}])}function Dl(){return X([{kind:"title",text:"/receive \u2014 inbound files for this chat"},{kind:"p",text:"Stored on the host with your session (sessions.json). Does not edit config.json."},{kind:"bullet",text:"/receive \u2014 show current setting and resolved save folder"},{kind:"bullet",text:"/receive here \u2014 save uploads under this chat\u2019s session cwd (same as !cd); subfolders: peer / date / file"},{kind:"bullet",text:"/receive default \u2014 clear per-chat rule; use global fileReceiveRootMode (/files, config.json)"},{kind:"gap"},{kind:"p",text:"Aliases: here = cwd = session = dir \xB7 default = global = reset"}])}function Bp(e,t){let n=js(t),o=oe(t),r="";try{r=wn(e,t)}catch(s){r=`(${String(s)})`}return X(n==="sessionCwd"?[{kind:"title",text:"/receive \u2014 this chat"},{kind:"p",text:"Per-chat: inbound media saves under your session folder (updated when you !cd)."},{kind:"bullet",text:`Session cwd: ${o.cwd}`},{kind:"bullet",text:`Next file root: ${r}`},{kind:"p",text:"Send /receive default to follow server config instead."}]:[{kind:"title",text:"/receive \u2014 this chat"},{kind:"p",text:"Per-chat override: off \u2014 using global fileReceiveRootMode from config."},{kind:"bullet",text:`Global mode: ${e.fileReceiveRootMode}`},{kind:"bullet",text:`Next file root: ${r}`},{kind:"p",text:"Send /receive here to pin saves to your current session folder."}])}function jp(){return X([{kind:"title",text:"Unknown /wa command"},{kind:"p",text:"Send /wa help for setup."}])}function Hp(){return X([{kind:"title",text:"Unknown /tg command"},{kind:"p",text:"Send /tg help or /tg token <botfather_token>."}])}function Jp(){return X([{kind:"title",text:"Free shell mode on"},{kind:"p",text:"Plain messages run as sync shell (no command prefix)."},{kind:"bullet",text:"Send !!stop to turn off."},{kind:"bullet",text:"Apps: plain DMs no longer go to the focused app \u2014 use >name text or /apps send."}])}function Gp(e){return X([{kind:"title",text:"Unknown command"},{kind:"p",text:`Try /help or ${e.commandPrefix}<command>.`},{kind:"gap"},...$o(e)])}function Jk(e){let t=e.trim();return t.length<4||/^[/!>]/.test(t)?null:t.includes("?")||/\s/.test(t)?`Looking for how to do something? Try /s q ${t.length>48?`${t.slice(0,48)}\u2026`:t}`:null}function qp(e,t){let n=t?Jk(t):null,o=[{kind:"title",text:"No command matched"},{kind:"p",text:`Use ${JSON.stringify(e.commandPrefix)}, a slash command, or /apps help.`}];return n&&o.push({kind:"p",text:n}),o.push({kind:"gap"},...$o(e)),X(o)}function zp(){let e=[{kind:"title",text:"Unknown mode"},{kind:"p",text:"Use: whatsapp | telegram | both (short: wa, tg, b)"},{kind:"bullet",text:"/gateway both \xB7 /gw wa \xB7 /mode telegram"},{kind:"gap"},...Ll()];return X(e)}function Wl(){return[{kind:"title",text:"Shortcuts (this gateway)"},{kind:"p",text:"Stored per-chat or shared for every chat on this gateway. Expansion: this chat wins, then shared. Use $OMNISH_INPUT in the body for runtime text; for PTY agents and $OMNISH_TASK env injection use /run recipes."},{kind:"p",text:"Scope flags: -g or --global = shared on this gateway; -p or --chat = private to this chat."},{kind:"gap"},{kind:"sub",text:"Manage"},{kind:"bullet",text:"/shortcut add <name> <command\u2026> \u2014 private by default; add -g|--global to share on this gateway"},{kind:"bullet",text:"/shortcut add -p|--chat <name> <command\u2026> \u2014 explicit private (this chat)"},{kind:"bullet",text:"/shortcut set <name> <command\u2026> \u2014 overwrite in chosen bucket; scope-only (same line): /shortcut set -g <name> or /shortcut set <name> -g (share); /shortcut set -p <name> or /shortcut set <name> -p (private)"},{kind:"bullet",text:"/shortcut list \u2014 merged view (/shortcuts); list --chat | -p | list --global | -g"},{kind:"bullet",text:"/shortcut show <name>; show --global|-g|--chat|-p to read one bucket"},{kind:"bullet",text:"/shortcut remove <name> \u2014 private bucket; remove --global|-g drops shared \xB7 rm, del \xB7 --chat|-p"},{kind:"bullet",text:"/shortcut <name> publish \u2014 share to online catalog (platform login)"},{kind:"bullet",text:"/shortcut online trending | show <publicId> | <publicId> download \u2014 shortcuts only"},{kind:"bullet",text:"/alias \u2026 \u2014 same as /shortcut \u2026 (including /aliases \u2026)"},{kind:"gap"},{kind:"sub",text:"Run"},{kind:"bullet",text:"!name \u2014 expands once to the saved line (e.g. !cd \u2026 updates session cwd)"},{kind:"bullet",text:"/name \u2014 same expansion for slash-style navigation"},{kind:"bullet",text:"!name <input\u2026> or /name <input\u2026> \u2014 when the body contains $OMNISH_INPUT, text after the name replaces every $OMNISH_INPUT (required if the placeholder is present)"},{kind:"bullet",text:"Example: /shortcut add remosh /run remosh $OMNISH_INPUT \u2192 /remosh generate top 10 vid"},{kind:"p",text:"Shortcut bodies may start with !, /, etc.; nested shortcuts are not expanded."}]}function Kp(e){if(e.length===0)return p("(no shortcuts \u2014 /shortcut add <name> <command\u2026> or /shortcut add --global <name> <command\u2026>)");let t=e.every(l=>l.scope==="global"),n=e.every(l=>l.scope==="chat"),o=t?"_Shared (every chat)_":n?"_This chat only_":"_This chat + shared_",r=t?"<i>Shared (every chat)</i>":n?"<i>This chat only</i>":"<i>This chat + shared</i>",s=e.some(l=>l.scope==="chat")&&e.some(l=>l.scope==="global"),i=["*Shortcuts*",o,"",...e.map(l=>{let c=s?l.scope==="chat"?"[chat] ":"[global] ":"";return`${Ne}\`${c}${l.name}\` \u2192 ${l.body}`})].join(`
66
+ ${Z(U)}${r}`;return we(s,i)}function Bp(){return X([{kind:"title",text:"/allow \u2014 add to allowlist"},{kind:"bullet",text:"/allow +<E164> \u2014 WhatsApp (country code, no spaces)"},{kind:"bullet",text:"/allow tg:<user_id> \u2014 Telegram numeric id"},{kind:"gap"},{kind:"p",text:"Examples: /allow +15551234567 \xB7 /allow tg:987654321"}])}function jp(){return X([{kind:"title",text:"/deny \u2014 remove from allowlist"},{kind:"p",text:"Same forms as /allow: +E164 or tg:id"}])}function Hp(){return X([{kind:"title",text:"/bg \u2014 background job"},{kind:"p",text:"Usage: /bg [flags] <shell command>"},{kind:"bullet",text:"Optional name: /bg -n mybuild npm run build \xB7 /bg --name mybuild \u2026 \xB7 /bg --name=mybuild \u2026"},{kind:"bullet",text:"Notify on completion: /bg --notify <cmd> or /bg -N <cmd> \u2014 sends a message when the job finishes (with exit status)."},{kind:"bullet",text:"Combine flags: /bg -N -n deploy git pull && docker compose up -d"},{kind:"bullet",text:"Then use /log mybuild, /tail mybuild, /kill mybuild (or the 8-char id). Names: letters, digits, . _ - up to 64 chars."},{kind:"bullet",text:"Example: /bg sleep 30 && echo done"}])}function Fl(){return X([{kind:"title",text:"/send \u2014 push a host file to this chat"},{kind:"p",text:"Usage: /send <selectors> [-- caption] \u2014 selectors resolve from session cwd."},{kind:"bullet",text:"/send ./photo.png"},{kind:"bullet",text:"/send ./clip1.mp4,./clip2.mp4 -- Launch set"},{kind:"bullet",text:"/send **/*.mp4"},{kind:"bullet",text:"/send /abs/path/report.pdf -- Q4 draft"},{kind:"bullet",text:"Selectors: file1,file2 | *.ext | **/*.ext"},{kind:"bullet",text:"Alias: /file \u2026"},{kind:"gap"},{kind:"p",text:"Caps: fileSendMaxBytes / fileReceiveMaxBytes (0 = no omnish cap). Where inbound media is saved: fileReceiveRootMode + fileReceiveRootPath / fileInboxSubdir \u2014 send /files for details."}])}function Dl(){return X([{kind:"title",text:"Files \u2014 send & receive"},{kind:"sub",text:"Host \u2192 chat"},{kind:"bullet",text:"/send <selectors> or /file \u2026 \u2014 selectors support file1,file2, *.ext, **/*.ext from this chat\u2019s session cwd (same as ! shell); optional caption after -- "},{kind:"bullet",text:"fileSendMaxBytes in config caps outbound size (0 = no omnish cap)."},{kind:"gap"},{kind:"sub",text:"Chat \u2192 host"},{kind:"bullet",text:"Send a photo, video, document, audio, etc. in this DM; omnish saves it and replies with Saved: <path> (or an error)."},{kind:"bullet",text:"Folders: <root>/<peer>/<YYYY-MM-DD>/<filename> \u2014 root comes from config below."},{kind:"bullet",text:"fileReceiveRootMode: downloads (home/Downloads/Omnish) \xB7 omnishData (data dir + fileInboxSubdir) \xB7 sessionCwd \xB7 processCwd \xB7 fixed (needs absolute fileReceiveRootPath)."},{kind:"bullet",text:"fileReceiveMaxBytes \u2014 inbound size cap (0 = no omnish cap)."},{kind:"gap"},{kind:"p",text:`Edit config on the host (${U}) then use /reload. Tip: omnish status shows the data directory.`},{kind:"gap"},{kind:"p",text:"Per-chat folder: /receive here saves inbound files under your session cwd (!cd); /receive default clears."}])}function Wl(){return X([{kind:"title",text:"/receive \u2014 inbound files for this chat"},{kind:"p",text:"Stored on the host with your session (sessions.json). Does not edit config.json."},{kind:"bullet",text:"/receive \u2014 show current setting and resolved save folder"},{kind:"bullet",text:"/receive here \u2014 save uploads under this chat\u2019s session cwd (same as !cd); subfolders: peer / date / file"},{kind:"bullet",text:"/receive default \u2014 clear per-chat rule; use global fileReceiveRootMode (/files, config.json)"},{kind:"gap"},{kind:"p",text:"Aliases: here = cwd = session = dir \xB7 default = global = reset"}])}function Jp(e,t){let n=js(t),o=oe(t),r="";try{r=wn(e,t)}catch(s){r=`(${String(s)})`}return X(n==="sessionCwd"?[{kind:"title",text:"/receive \u2014 this chat"},{kind:"p",text:"Per-chat: inbound media saves under your session folder (updated when you !cd)."},{kind:"bullet",text:`Session cwd: ${o.cwd}`},{kind:"bullet",text:`Next file root: ${r}`},{kind:"p",text:"Send /receive default to follow server config instead."}]:[{kind:"title",text:"/receive \u2014 this chat"},{kind:"p",text:"Per-chat override: off \u2014 using global fileReceiveRootMode from config."},{kind:"bullet",text:`Global mode: ${e.fileReceiveRootMode}`},{kind:"bullet",text:`Next file root: ${r}`},{kind:"p",text:"Send /receive here to pin saves to your current session folder."}])}function Gp(){return X([{kind:"title",text:"Unknown /wa command"},{kind:"p",text:"Send /wa help for setup."}])}function qp(){return X([{kind:"title",text:"Unknown /tg command"},{kind:"p",text:"Send /tg help or /tg token <botfather_token>."}])}function zp(){return X([{kind:"title",text:"Free shell mode on"},{kind:"p",text:"Plain messages run as sync shell (no command prefix)."},{kind:"bullet",text:"Send !!stop to turn off."},{kind:"bullet",text:"Apps: plain DMs no longer go to the focused app \u2014 use >name text or /apps send."}])}function Kp(e){return X([{kind:"title",text:"Unknown command"},{kind:"p",text:`Try /help or ${e.commandPrefix}<command>.`},{kind:"gap"},...$o(e)])}function Kk(e){let t=e.trim();return t.length<4||/^[/!>]/.test(t)?null:t.includes("?")||/\s/.test(t)?`Looking for how to do something? Try /s q ${t.length>48?`${t.slice(0,48)}\u2026`:t}`:null}function Yp(e,t){let n=t?Kk(t):null,o=[{kind:"title",text:"No command matched"},{kind:"p",text:`Use ${JSON.stringify(e.commandPrefix)}, a slash command, or /apps help.`}];return n&&o.push({kind:"p",text:n}),o.push({kind:"gap"},...$o(e)),X(o)}function Vp(){let e=[{kind:"title",text:"Unknown mode"},{kind:"p",text:"Use: whatsapp | telegram | both (short: wa, tg, b)"},{kind:"bullet",text:"/gateway both \xB7 /gw wa \xB7 /mode telegram"},{kind:"gap"},...Nl()];return X(e)}function Ul(){return[{kind:"title",text:"Shortcuts (this gateway)"},{kind:"p",text:"Stored per-chat or shared for every chat on this gateway. Expansion: this chat wins, then shared. Use $OMNISH_INPUT in the body for runtime text; for PTY agents and $OMNISH_TASK env injection use /run recipes."},{kind:"p",text:"Scope flags: -g or --global = shared on this gateway; -p or --chat = private to this chat."},{kind:"gap"},{kind:"sub",text:"Manage"},{kind:"bullet",text:"/shortcut add <name> <command\u2026> \u2014 private by default; add -g|--global to share on this gateway"},{kind:"bullet",text:"/shortcut add -p|--chat <name> <command\u2026> \u2014 explicit private (this chat)"},{kind:"bullet",text:"/shortcut set <name> <command\u2026> \u2014 overwrite in chosen bucket; scope-only (same line): /shortcut set -g <name> or /shortcut set <name> -g (share); /shortcut set -p <name> or /shortcut set <name> -p (private)"},{kind:"bullet",text:"/shortcut list \u2014 merged view (/shortcuts); list --chat | -p | list --global | -g"},{kind:"bullet",text:"/shortcut show <name>; show --global|-g|--chat|-p to read one bucket"},{kind:"bullet",text:"/shortcut remove <name> \u2014 private bucket; remove --global|-g drops shared \xB7 rm, del \xB7 --chat|-p"},{kind:"bullet",text:"/shortcut <name> publish \u2014 share to online catalog (platform login)"},{kind:"bullet",text:"/shortcut online trending | show <publicId> | <publicId> download \u2014 shortcuts only"},{kind:"bullet",text:"/alias \u2026 \u2014 same as /shortcut \u2026 (including /aliases \u2026)"},{kind:"gap"},{kind:"sub",text:"Run"},{kind:"bullet",text:"!name \u2014 expands once to the saved line (e.g. !cd \u2026 updates session cwd)"},{kind:"bullet",text:"/name \u2014 same expansion for slash-style navigation"},{kind:"bullet",text:"!name <input\u2026> or /name <input\u2026> \u2014 when the body contains $OMNISH_INPUT, text after the name replaces every $OMNISH_INPUT (required if the placeholder is present)"},{kind:"bullet",text:"Example: /shortcut add remosh /run remosh $OMNISH_INPUT \u2192 /remosh generate top 10 vid"},{kind:"p",text:"Shortcut bodies may start with !, /, etc.; nested shortcuts are not expanded."}]}function Qp(e){if(e.length===0)return p("(no shortcuts \u2014 /shortcut add <name> <command\u2026> or /shortcut add --global <name> <command\u2026>)");let t=e.every(l=>l.scope==="global"),n=e.every(l=>l.scope==="chat"),o=t?"_Shared (every chat)_":n?"_This chat only_":"_This chat + shared_",r=t?"<i>Shared (every chat)</i>":n?"<i>This chat only</i>":"<i>This chat + shared</i>",s=e.some(l=>l.scope==="chat")&&e.some(l=>l.scope==="global"),i=["*Shortcuts*",o,"",...e.map(l=>{let c=s?l.scope==="chat"?"[chat] ":"[global] ":"";return`${Ne}\`${c}${l.name}\` \u2192 ${l.body}`})].join(`
67
67
  `),a=["<b>Shortcuts</b>",r,"",...e.map(l=>{let u=`${s?l.scope==="chat"?"[chat] ":"[global] ":""}${l.name}`;return`\u2022 <code>${Z(u)}</code> \u2192 ${Z(l.body)}`})].join(`
68
- `);return ye(i,a)}function Ar(e,t,n="chat"){return p(`Shortcut saved: ${e}
68
+ `);return we(i,a)}function Ar(e,t,n="chat"){return p(`Shortcut saved: ${e}
69
69
  \u2192 ${t}
70
- (${n==="global"?"Shared on this gateway (every chat unless this chat overrides the name).":"Stored for this chat only."})`)}function Yp(e,t="chat"){return p(`Shortcut removed (${t==="global"?"shared":"this chat"}): ${e}`)}function Vp(e){return p(`Unknown shortcut: ${e}`)}function Qp(e,t){return p(`Unknown shortcut "${e}" in ${t==="global"?"shared shortcuts":"this chat"}.`)}function Ul(e,t,n){let o=n?`
70
+ (${n==="global"?"Shared on this gateway (every chat unless this chat overrides the name).":"Stored for this chat only."})`)}function Xp(e,t="chat"){return p(`Shortcut removed (${t==="global"?"shared":"this chat"}): ${e}`)}function Zp(e){return p(`Unknown shortcut: ${e}`)}function em(e,t){return p(`Unknown shortcut "${e}" in ${t==="global"?"shared shortcuts":"this chat"}.`)}function Bl(e,t,n){let o=n?`
71
71
 
72
72
  ${n}`:"";return p(`${e}
73
- \u2192 ${t}${o}`)}function Xp(){return[{kind:"title",text:"/run \u2014 recipe templates"},{kind:"sub",text:"Invoke"},{kind:"bullet",text:"/run <name> <task\u2026> \u2014 start detached session by default (output muted; /apps attach or >name for input)"},{kind:"bullet",text:"/run <name> --attach|-a <task\u2026> \u2014 attach on start (plain DMs go to session until /apps detach); --detach|-d forces detached"},{kind:"bullet",text:"/r <name> <task\u2026> \u2014 short alias for /run"},{kind:"gap"},{kind:"sub",text:"Queue"},{kind:"bullet",text:"/run <name> -q <task\u2026> or /run <name> --queue <task\u2026> \u2014 FIFO per chat: head starts immediately; others wait in memory until the head exits cleanly (code=0, signal=0)"},{kind:"bullet",text:"Pending counts only jobs not yet started \u2014 the first queued item becomes Active right away, so /run queue can show Pending: 0 while a queued run is still executing"},{kind:"bullet",text:"/run queue \u2014 active session + recipe, numbered waiting list, paused flag"},{kind:"bullet",text:"/run queue resume \u2014 after a pause or non-clean exit, clear pause and start the next waiting item"},{kind:"bullet",text:"/run queue load <file.json> \u2014 enqueue jobs from JSON (paths relative to session cwd); /run queue load json [\u2026] \u2014 same payload inline; attach a file with caption /run queue load"},{kind:"bullet",text:'Queue JSON: [ { "recipe": "<name>", "task": "<text>" }, \u2026 ] or { "tasks": [ \u2026 ] } (max 64 jobs per load; same rules as /run <name> -q)'},{kind:"gap"},{kind:"sub",text:"Discover"},{kind:"bullet",text:"/run list \u2014 featured, gateway-shared, this chat, host templates; list --chat | -p | list --global | -g"},{kind:"bullet",text:"/run show <name> \u2014 merged resolution; show --global|-g|--chat|-p reads one user bucket"},{kind:"gap"},{kind:"sub",text:"Manage"},{kind:"p",text:"Recipe scope flags: -g or --global = shared on this gateway; -p or --chat = private to this chat."},{kind:"bullet",text:'/run add <name> <command\u2026> [--template "\u2026"] \u2014 this chat; add --global|-g share on this gateway'},{kind:"bullet",text:"/run set <name> <command\u2026> \u2014 overwrite in chosen bucket (--global|-g|--chat|-p); scope-only (same body): /run set -g <name> or /run set <name> -g (share); /run set -p <name> or /run set <name> -p (this chat only)"},{kind:"bullet",text:"/run remove <name> \u2014 this chat storage; remove --global|-g clears gateway-shared \xB7 rm, del \xB7 --chat|-p"},{kind:"gap"},{kind:"sub",text:"Notes"},{kind:"bullet",text:"Commands must reference the task env variable (default `$OMNISH_TASK`)."},{kind:"bullet",text:"Template placeholders `<<<OMNISH_TASK>>>`, `<<<```$OMNISH_TASK```>>>`, and `$OMNISH_TASK` are replaced with task text."},{kind:"bullet",text:"Host overrides file: "+vs},{kind:"bullet",text:"Built-in Claude recipes omit dangerous flags unless recipesAllowDangerousBuiltins=true."},{kind:"gap"},{kind:"sub",text:"Online catalog"},{kind:"bullet",text:"/run online trending | search <query> | show <publicId> | <publicId> download \u2014 all kinds"},{kind:"bullet",text:"/run <recipe> publish \u2014 share to platform (requires omnish platform login)"}]}function Gk(e){switch(e){case"builtin":return"built-in";case"global":return"host recipes.json";case"shared":return"gateway-shared (/run add --global)";case"peer":return"this chat (/run add)";default:return e}}function Zp(e){let t=["*Recipes*","`/run <name> <task>`",""],n=["<b>Recipes</b>","<code>/run &lt;name&gt; &lt;task&gt;</code>",""],o=(r,s)=>{let i=r.description?` \u2014 ${r.description}`:"",a=s&&r.dangerous?" [dangerous flags]":"";t.push(`${Ne}${r.name} \u2014 ${r.label??r.name}${i}${a}`);let l=r.description?` \u2014 ${Z(r.description)}`:"",c=s&&r.dangerous?Z(" [dangerous flags]"):"";n.push(`\u2022 ${Z(r.name)} \u2014 ${Z(r.label??r.name)}${l}${c}`)};if(e.featured.length>0){t.push("_Featured:_"),n.push("<i>Featured:</i>");for(let r of e.featured)o(r,!0);t.push(""),n.push("")}if(e.shared.length>0){t.push("_Shared (every chat):_"),n.push("<i>Shared (every chat):</i>");for(let r of e.shared)o(r,!1);t.push(""),n.push("")}if(e.yours.length>0){t.push("_This chat:_"),n.push("<i>This chat:</i>");for(let r of e.yours)o(r,!1);t.push(""),n.push("")}if(e.more.length>0){t.push("_More:_"),n.push("<i>More:</i>");for(let r of e.more)o(r,!1)}return e.featured.length===0&&e.shared.length===0&&e.yours.length===0&&e.more.length===0&&(t.push("(no recipes \u2014 add host file or /run add)"),n.push(Z("(no recipes \u2014 add host file or /run add)"))),ye(t.join(`
73
+ \u2192 ${t}${o}`)}function tm(){return[{kind:"title",text:"/run \u2014 recipe templates"},{kind:"sub",text:"Invoke"},{kind:"bullet",text:"/run <name> <task\u2026> \u2014 start detached session by default (output muted; /apps attach or >name for input)"},{kind:"bullet",text:"/run <name> --attach|-a <task\u2026> \u2014 attach on start (plain DMs go to session until /apps detach); --detach|-d forces detached"},{kind:"bullet",text:"/r <name> <task\u2026> \u2014 short alias for /run"},{kind:"gap"},{kind:"sub",text:"Queue"},{kind:"bullet",text:"/run <name> -q <task\u2026> or /run <name> --queue <task\u2026> \u2014 FIFO per chat: head starts immediately; others wait in memory until the head exits cleanly (code=0, signal=0)"},{kind:"bullet",text:"Pending counts only jobs not yet started \u2014 the first queued item becomes Active right away, so /run queue can show Pending: 0 while a queued run is still executing"},{kind:"bullet",text:"/run queue \u2014 active session + recipe, numbered waiting list, paused flag"},{kind:"bullet",text:"/run queue resume \u2014 after a pause or non-clean exit, clear pause and start the next waiting item"},{kind:"bullet",text:"/run queue load <file.json> \u2014 enqueue jobs from JSON (paths relative to session cwd); /run queue load json [\u2026] \u2014 same payload inline; attach a file with caption /run queue load"},{kind:"bullet",text:'Queue JSON: [ { "recipe": "<name>", "task": "<text>" }, \u2026 ] or { "tasks": [ \u2026 ] } (max 64 jobs per load; same rules as /run <name> -q)'},{kind:"gap"},{kind:"sub",text:"Discover"},{kind:"bullet",text:"/run list \u2014 featured, gateway-shared, this chat, host templates; list --chat | -p | list --global | -g"},{kind:"bullet",text:"/run show <name> \u2014 merged resolution; show --global|-g|--chat|-p reads one user bucket"},{kind:"gap"},{kind:"sub",text:"Manage"},{kind:"p",text:"Recipe scope flags: -g or --global = shared on this gateway; -p or --chat = private to this chat."},{kind:"bullet",text:'/run add <name> <command\u2026> [--template "\u2026"] \u2014 this chat; add --global|-g share on this gateway'},{kind:"bullet",text:"/run set <name> <command\u2026> \u2014 overwrite in chosen bucket (--global|-g|--chat|-p); scope-only (same body): /run set -g <name> or /run set <name> -g (share); /run set -p <name> or /run set <name> -p (this chat only)"},{kind:"bullet",text:"/run remove <name> \u2014 this chat storage; remove --global|-g clears gateway-shared \xB7 rm, del \xB7 --chat|-p"},{kind:"gap"},{kind:"sub",text:"Notes"},{kind:"bullet",text:"Commands must reference the task env variable (default `$OMNISH_TASK`)."},{kind:"bullet",text:"Template placeholders `<<<OMNISH_TASK>>>`, `<<<```$OMNISH_TASK```>>>`, and `$OMNISH_TASK` are replaced with task text."},{kind:"bullet",text:"Host overrides file: "+vs},{kind:"bullet",text:"Built-in Claude recipes omit dangerous flags unless recipesAllowDangerousBuiltins=true."},{kind:"gap"},{kind:"sub",text:"Online catalog"},{kind:"bullet",text:"/run online trending | search <query> | show <publicId> | <publicId> download \u2014 all kinds"},{kind:"bullet",text:"/run <recipe> publish \u2014 share to platform (requires omnish platform login)"}]}function Yk(e){switch(e){case"builtin":return"built-in";case"global":return"host recipes.json";case"shared":return"gateway-shared (/run add --global)";case"peer":return"this chat (/run add)";default:return e}}function nm(e){let t=["*Recipes*","`/run <name> <task>`",""],n=["<b>Recipes</b>","<code>/run &lt;name&gt; &lt;task&gt;</code>",""],o=(r,s)=>{let i=r.description?` \u2014 ${r.description}`:"",a=s&&r.dangerous?" [dangerous flags]":"";t.push(`${Ne}${r.name} \u2014 ${r.label??r.name}${i}${a}`);let l=r.description?` \u2014 ${Z(r.description)}`:"",c=s&&r.dangerous?Z(" [dangerous flags]"):"";n.push(`\u2022 ${Z(r.name)} \u2014 ${Z(r.label??r.name)}${l}${c}`)};if(e.featured.length>0){t.push("_Featured:_"),n.push("<i>Featured:</i>");for(let r of e.featured)o(r,!0);t.push(""),n.push("")}if(e.shared.length>0){t.push("_Shared (every chat):_"),n.push("<i>Shared (every chat):</i>");for(let r of e.shared)o(r,!1);t.push(""),n.push("")}if(e.yours.length>0){t.push("_This chat:_"),n.push("<i>This chat:</i>");for(let r of e.yours)o(r,!1);t.push(""),n.push("")}if(e.more.length>0){t.push("_More:_"),n.push("<i>More:</i>");for(let r of e.more)o(r,!1)}return e.featured.length===0&&e.shared.length===0&&e.yours.length===0&&e.more.length===0&&(t.push("(no recipes \u2014 add host file or /run add)"),n.push(Z("(no recipes \u2014 add host file or /run add)"))),we(t.join(`
74
74
  `).trimEnd(),n.join(`
75
- `).trimEnd())}function Bl(e,t){let n=e.taskEnv??"OMNISH_TASK",o=[`Recipe: ${e.name}`,`Source: ${Gk(e.source)}`,`Label: ${e.label??"(none)"}`];if(e.steps&&e.steps.length>0){o.push(`Type: runbook (${e.steps.length} steps)`);for(let r=0;r<e.steps.length;r++){let s=e.steps[r],i=s.label?` (${s.label})`:"",a=s.continueOnFail?" [continue-on-fail]":"";o.push(` ${r+1}. ${s.cmd}${i}${a}`)}}else o.push(`Task env: ${n}`),o.push(`Command: ${e.command}`);if(e.promptTemplate){o.push(`Template: ${e.promptTemplate.length} chars`);let s=e.promptTemplate.replace(/\s+/g," ").trim().slice(0,200);o.push(`Preview: ${s}${e.promptTemplate.length>200?"\u2026":""}`)}return e.category&&o.push(`Category: ${e.category}`),e.description&&o.push(`Description: ${e.description}`),e.dangerous&&o.push("Note: built-in includes gated dangerous CLI flags."),t&&o.push("",t),p(o.join(`
75
+ `).trimEnd())}function jl(e,t){let n=e.taskEnv??"OMNISH_TASK",o=[`Recipe: ${e.name}`,`Source: ${Yk(e.source)}`,`Label: ${e.label??"(none)"}`];if(e.steps&&e.steps.length>0){o.push(`Type: runbook (${e.steps.length} steps)`);for(let r=0;r<e.steps.length;r++){let s=e.steps[r],i=s.label?` (${s.label})`:"",a=s.continueOnFail?" [continue-on-fail]":"";o.push(` ${r+1}. ${s.cmd}${i}${a}`)}}else o.push(`Task env: ${n}`),o.push(`Command: ${e.command}`);if(e.promptTemplate){o.push(`Template: ${e.promptTemplate.length} chars`);let s=e.promptTemplate.replace(/\s+/g," ").trim().slice(0,200);o.push(`Preview: ${s}${e.promptTemplate.length>200?"\u2026":""}`)}return e.category&&o.push(`Category: ${e.category}`),e.description&&o.push(`Description: ${e.description}`),e.dangerous&&o.push("Note: built-in includes gated dangerous CLI flags."),t&&o.push("",t),p(o.join(`
76
76
  `))}function Ir(e,t,n="chat"){let o=n==="global"?"Shared on this gateway (every chat unless this chat overrides the name).":"Stored for this chat only.",r=[`Recipe saved: ${e}`,`\u2192 ${t.command}`,`(task env: ${t.taskEnv??"OMNISH_TASK"})`];return t.promptTemplate&&r.push(`Template stored: ${t.promptTemplate.length} chars`),r.push(`(${o})`),p(r.join(`
77
- `))}function em(e,t="chat"){return p(`Recipe removed (${t==="global"?"gateway-shared storage":"this chat"}): ${e}`)}function tm(e,t){return p(`Unknown recipe "${e}" in ${t==="global"?"gateway-shared storage":"this chat storage"}.`)}function nm(e,t){let n=t==="global"?"_Gateway-shared recipes_":"_This chat recipes_",o=t==="global"?"<i>Gateway-shared recipes</i>":"<i>This chat recipes</i>";if(e.length===0)return p(t==="global"?"(no gateway-shared recipes \u2014 /run add --global <name> <command\u2026>)":"(no recipes in this chat \u2014 /run add <name> <command\u2026>)");let r=["*Recipes*",n,""],s=["<b>Recipes</b>",o,""];for(let i of e){let a=i.description?` \u2014 ${i.description}`:"";r.push(`${Ne}${i.name} \u2014 ${i.label??i.name}${a}`);let l=i.description?` \u2014 ${Z(i.description)}`:"";s.push(`\u2022 ${Z(i.name)} \u2014 ${Z(i.label??i.name)}${l}`)}return ye(r.join(`
77
+ `))}function om(e,t="chat"){return p(`Recipe removed (${t==="global"?"gateway-shared storage":"this chat"}): ${e}`)}function rm(e,t){return p(`Unknown recipe "${e}" in ${t==="global"?"gateway-shared storage":"this chat storage"}.`)}function sm(e,t){let n=t==="global"?"_Gateway-shared recipes_":"_This chat recipes_",o=t==="global"?"<i>Gateway-shared recipes</i>":"<i>This chat recipes</i>";if(e.length===0)return p(t==="global"?"(no gateway-shared recipes \u2014 /run add --global <name> <command\u2026>)":"(no recipes in this chat \u2014 /run add <name> <command\u2026>)");let r=["*Recipes*",n,""],s=["<b>Recipes</b>",o,""];for(let i of e){let a=i.description?` \u2014 ${i.description}`:"";r.push(`${Ne}${i.name} \u2014 ${i.label??i.name}${a}`);let l=i.description?` \u2014 ${Z(i.description)}`:"";s.push(`\u2022 ${Z(i.name)} \u2014 ${Z(i.label??i.name)}${l}`)}return we(r.join(`
78
78
  `).trimEnd(),s.join(`
79
- `).trimEnd())}function jl(e){return p(`Unknown recipe: ${e}
80
- /run list`)}function Hl(){return[{kind:"title",text:"Apps (interactive CLI)"},{kind:"sub",text:"Sessions"},{kind:"bullet",text:"/apps start <name> <command\u2026> \u2014 spawn in session cwd; auto-attaches"},{kind:"bullet",text:"/apps attach <name> | /apps detach"},{kind:"bullet",text:"/apps list | /apps info <name>"},{kind:"gap"},{kind:"sub",text:"Input"},{kind:"bullet",text:"/apps send <name> <text> \u2014 text + newline"},{kind:"bullet",text:">name text \u2014 shorthand for send"},{kind:"bullet",text:"/apps key <name> <KEY[,KEY\u2026]> \u2014 Enter, ^C, Up, Esc, \\x1b, \u2026"},{kind:"gap"},{kind:"sub",text:"Output & control"},{kind:"bullet",text:"/apps tail <name> [lines] | /apps since <name>"},{kind:"bullet",text:"/apps mute|unmute <name> | /apps raw <name> on|off"},{kind:"bullet",text:"/apps resize <name> <cols> <rows>"},{kind:"bullet",text:"/apps stop <name> | /apps kill <name> | /apps rm <name>"},{kind:"bullet",text:"/apps <session> publish \u2014 share session command to online catalog"},{kind:"bullet",text:"/apps online trending | show <publicId> | <publicId> download \u2014 browse app templates only"},{kind:"gap"},{kind:"p",text:"Attached: plain DMs go to the focused app. Escaped by ! prefix, /commands, or >other."},{kind:"p",text:"Free shell: !!start \u2014 plain DMs run as sync shell when no focused PTY; !!stop \u2014 off. Attached session wins over free shell for plain text."}]}function qk(e){let{errors:t,warns:n,infos:o}=Ol(e),r=[{kind:"title",text:"Security check"}];if(e.length===0)return r.push({kind:"p",text:"No issues reported by automated rules."},{kind:"gap"},{kind:"p",text:"Allowlisted remote shell access is still equivalent to sharing credentials with those identities."}),r;r.push({kind:"p",text:`${t} error(s), ${n} warning(s), ${o} note(s).`}),r.push({kind:"gap"});let s=(i,a)=>{if(a.length!==0){r.push({kind:"sub",text:`${i} (${a.length})`});for(let l of a){let c=l.severity.toUpperCase();r.push({kind:"bullet",text:$e(`[${c}] ${l.code}: ${l.message}`)}),l.detail&&r.push({kind:"bullet",text:$e(l.detail)}),l.fixHint&&r.push({kind:"bullet",text:$e(`Fix: ${l.fixHint}`)})}r.push({kind:"gap"})}};return s("Errors",e.filter(i=>i.severity==="error")),s("Warnings",e.filter(i=>i.severity==="warn")),s("Notes",e.filter(i=>i.severity==="info")),r.push({kind:"p",text:"Allowlisted identities can run commands as this user; treat them like passwords."}),r}function om(e){return X(qk(e))}function rm(){return[{kind:"title",text:"Security tips"},{kind:"p",text:"Practical hardening for this gateway:"},{kind:"bullet",text:"Keep allowlists minimal \u2014 only numbers and tg: ids you trust."},{kind:"bullet",text:'Never use "*" in allowFrom; treat allowed identities like passwords.'},{kind:"bullet",text:"chmod 600 config.json and chmod 700 your data dir (~/.omnish or OMNISH_HOME)."},{kind:"bullet",text:"Do not paste bot tokens in group chats; revoke at @BotFather if leaked."},{kind:"bullet",text:"Prefer TELEGRAM_BOT_TOKEN via systemd/env over chat if your Telegram logs worry you."},{kind:"bullet",text:"Run omnish as a normal user, not root, unless you fully accept that risk."},{kind:"gap"},{kind:"p",text:"Send /security on this chat or run omnish security on the host for automated checks."}]}function sm(){return[{kind:"title",text:"/security commands"},{kind:"bullet",text:"/security \u2014 full report (same checks as omnish security)"},{kind:"bullet",text:"/security summary \u2014 one-line counts"},{kind:"bullet",text:"/security tips \u2014 short hardening checklist"},{kind:"bullet",text:"CLI: omnish security [--json] for scripts and monitoring"}]}function Me(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function Mo(e){let t=e.trim();return t?t.length<=8?"(set)":`${t.slice(0,4)}\u2026${t.slice(-4)}`:"(empty)"}function im(e){let t=e.trim();return(t.startsWith('"')&&t.endsWith('"')&&t.length>=2||t.startsWith("'")&&t.endsWith("'")&&t.length>=2)&&(t=t.slice(1,-1)),t}function Se(e){let t=e.trim().toLowerCase();return t==="true"||t==="1"||t==="yes"||t==="on"?!0:t==="false"||t==="0"||t==="no"||t==="off"?!1:null}var am=new Set(["downloads","omnishData","sessionCwd","processCwd","fixed"]),xn=["gatewayMode","clusterEnabled","clusterRole","clusterLabel","clusterSenderBindings","commandPrefix","syncTimeoutMs","syncMaxBytes","jobLogTailLines","shell","appsCols","appsRows","appsFlushMs","appsMinIntervalMs","appsMaxFlushBytes","appsMaxSessions","appsMaxSessionsTotal","appsMaxWaChars","appsLogTailLines","appsSubmitDelayMs","appsClearInput","appsClearInputDelayMs","appsClearInputSequence","appsSkipClearOnPasswordPrompt","appsPasswordPromptHint","fileSendMaxBytes","fileReceiveMaxBytes","fileInboxSubdir","fileReceiveRootMode","fileReceiveRootPath","recipesAllowDangerousBuiltins","recipesMaxTaskChars","recipesMacroDefaultCommand","recipesRunAttach","recipesNotifyEnabled","telegramBotToken","serviceInstallFromChat","updateCheckEnabled","updateCheckIntervalMs","updateCheckPackageName","updateInfoUrl","chatLlmFallbackEnabled","chatLlmShellCommand","chatLlmTimeoutMs","chatLlmMaxInputChars","chatLlmMaxOutputChars","chatLlmNeedsTty","chatLlmWorkDir","chatAgentEnabled","chatAgentCommand","chatAgentPerPeer","chatAgentMaxQueue","tunnelEnabled","tunnelRelayUrl","tunnelMaxActive","webhookEnabled","webhookPort","webhookHost","webhookToken","watchEnabled","watchDebounceMs","watchMaxEventsPerMinute","watchAutoRestore","mediaSendFiles","mediaUrlAutoDl","mediaInstallFromChat","mediaOutputDir","mediaMaxBytes","mediaWhisperModel","mediaWhisperDevice","mediaTranscribeTimeoutMs","mediaTranscribeEngine","mediaTranscribeFallback","progressUpdates","pullYtDlpPath","pullFfmpegPath","pullWhisperPath"];function lm(e){return xn.includes(e)}function zk(e){let t=Ie(e);return["*Config* (secrets masked)","",`gatewayMode: ${e.gatewayMode}`,`commandPrefix: ${e.commandPrefix}`,`shell: ${e.shell}`,`syncTimeoutMs: ${e.syncTimeoutMs}`,`syncMaxBytes: ${e.syncMaxBytes}`,`jobLogTailLines: ${e.jobLogTailLines}`,"","*Cluster*",`clusterEnabled: ${e.clusterEnabled}`,`clusterRole: ${e.clusterRole}`,`clusterLabel: ${e.clusterLabel||"(hostname)"}`,`clusterSenderBindings: ${Object.keys(e.clusterSenderBindings??{}).length} entries`,"","*Telegram*",`telegramBotToken: ${Mo(t)}`,`telegramAllowFrom: ${e.telegramAllowFrom.length} entries`,"","*WhatsApp allowFrom*",`${e.allowFrom.length} entries`,"","*Apps*",`appsCols \xD7 appsRows: ${e.appsCols}\xD7${e.appsRows}`,`appsFlushMs / appsMinIntervalMs: ${e.appsFlushMs} / ${e.appsMinIntervalMs}`,`appsMaxFlushBytes: ${e.appsMaxFlushBytes}`,`appsMaxSessions / total: ${e.appsMaxSessions} / ${e.appsMaxSessionsTotal}`,`appsMaxWaChars: ${e.appsMaxWaChars}`,`appsLogTailLines: ${e.appsLogTailLines}`,`appsSubmitDelayMs: ${e.appsSubmitDelayMs}`,`appsClearInput: ${e.appsClearInput}`,`appsClearInputDelayMs: ${e.appsClearInputDelayMs}`,`appsClearInputSequence: ${e.appsClearInputSequence}`,`appsSkipClearOnPasswordPrompt: ${e.appsSkipClearOnPasswordPrompt}`,`appsPasswordPromptHint: ${e.appsPasswordPromptHint}`,"","*Files*",`fileSendMaxBytes / fileReceiveMaxBytes: ${e.fileSendMaxBytes} / ${e.fileReceiveMaxBytes}`,`fileInboxSubdir: ${e.fileInboxSubdir}`,`fileReceiveRootMode: ${e.fileReceiveRootMode}`,`fileReceiveRootPath: ${e.fileReceiveRootPath||"(empty)"}`,"","*Recipes*",`recipesAllowDangerousBuiltins: ${e.recipesAllowDangerousBuiltins}`,`recipesMaxTaskChars: ${e.recipesMaxTaskChars}`,`recipesMacroDefaultCommand: ${e.recipesMacroDefaultCommand}`,`recipesRunAttach: ${e.recipesRunAttach}`,`recipesNotifyEnabled: ${e.recipesNotifyEnabled}`,"","*Service (chat)*",`serviceInstallFromChat: ${e.serviceInstallFromChat}`,"","*Updates (optional)*",`updateCheckEnabled: ${e.updateCheckEnabled}`,`updateCheckIntervalMs: ${e.updateCheckIntervalMs}`,`updateCheckPackageName: ${e.updateCheckPackageName}`,`updateInfoUrl: ${e.updateInfoUrl?"(set)":"(empty)"}`,"","*Tunneling (chat /tunnel)*",`tunnelEnabled: ${e.tunnelEnabled}`,`tunnelRelayUrl: ${e.tunnelRelayUrl}`,`tunnelMaxActive: ${e.tunnelMaxActive}`,"","*Chat LLM fallback (optional)*",`chatLlmFallbackEnabled: ${e.chatLlmFallbackEnabled}`,`chatLlmShellCommand: ${e.chatLlmShellCommand?"(set)":"(empty)"}`,`chatLlmTimeoutMs: ${e.chatLlmTimeoutMs}`,`chatLlmMaxInputChars / chatLlmMaxOutputChars: ${e.chatLlmMaxInputChars} / ${e.chatLlmMaxOutputChars}`,`chatLlmNeedsTty: ${e.chatLlmNeedsTty}`,`chatLlmWorkDir: ${e.chatLlmWorkDir||"(empty \u2014 temp dir per run)"}`,`chatAgentEnabled: ${e.chatAgentEnabled===!0}`,`chatAgentCommand: ${e.chatAgentCommand?"(set)":"(empty)"}`,`chatAgentPerPeer: ${e.chatAgentPerPeer!==!1}`,`chatAgentMaxQueue: ${e.chatAgentMaxQueue??64}`,"","*Webhook receiver (optional)*",`webhookEnabled: ${e.webhookEnabled}`,`webhookPort: ${e.webhookPort} (0 = random)`,`webhookHost: ${e.webhookHost}`,`webhookToken: ${Mo(e.webhookToken)}`,"","*Watch (OS event eye)*",`watchEnabled: ${e.watchEnabled}`,`watchDebounceMs: ${e.watchDebounceMs}`,`watchMaxEventsPerMinute: ${e.watchMaxEventsPerMinute}`,`watchAutoRestore: ${e.watchAutoRestore}`,"","*Media (/dl, /tr, /edit)*",`mediaSendFiles: ${e.mediaSendFiles}`,`mediaInstallFromChat: ${e.mediaInstallFromChat}`,`mediaUrlAutoDl: ${e.mediaUrlAutoDl}`,`mediaOutputDir: ${e.mediaOutputDir||"(Downloads/Omnish or session cwd via /receive here)"}`,`mediaMaxBytes: ${e.mediaMaxBytes}`,`mediaWhisperModel: ${e.mediaWhisperModel}`,`mediaWhisperDevice: ${e.mediaWhisperDevice}`,`mediaTranscribeTimeoutMs: ${e.mediaTranscribeTimeoutMs}`,`mediaTranscribeEngine: ${e.mediaTranscribeEngine}`,`mediaTranscribeFallback: ${e.mediaTranscribeFallback}`,`progressUpdates: ${e.progressUpdates}`,"",`File: ${U}`].join(`
81
- `)}function Kk(e){let t=Ie(e);return["<b>Config</b> (secrets masked)","",`<b>gatewayMode</b> ${Me(e.gatewayMode)}`,`<b>commandPrefix</b> ${Me(e.commandPrefix)}`,`<b>shell</b> ${Me(e.shell)}`,`<b>syncTimeoutMs</b> ${e.syncTimeoutMs}`,`<b>syncMaxBytes</b> ${e.syncMaxBytes}`,`<b>jobLogTailLines</b> ${e.jobLogTailLines}`,"","<b>Cluster</b>",`clusterEnabled: ${e.clusterEnabled} \xB7 clusterRole: ${Me(e.clusterRole)}`,`clusterLabel: ${Me(e.clusterLabel||"(hostname)")}`,`clusterSenderBindings: ${Object.keys(e.clusterSenderBindings??{}).length} entries`,"","<b>Telegram</b>",`telegramBotToken: ${Me(Mo(t))}`,`telegramAllowFrom: ${e.telegramAllowFrom.length} entries`,"",`<b>WhatsApp allowFrom</b> ${e.allowFrom.length} entries`,"","<b>Apps</b>",`${e.appsCols}\xD7${e.appsRows} flush ${e.appsFlushMs}/${e.appsMinIntervalMs} ms \u2026`,"","<b>Files</b>",`${Me(e.fileReceiveRootMode)} \xB7 inbox ${Me(e.fileInboxSubdir)}`,"","<b>Service (chat)</b>",`serviceInstallFromChat: ${e.serviceInstallFromChat}`,"","<b>Updates (optional)</b>",`updateCheckEnabled: ${e.updateCheckEnabled} \xB7 interval ms: ${e.updateCheckIntervalMs}`,`package: <code>${Me(e.updateCheckPackageName)}</code> \xB7 info URL: ${e.updateInfoUrl?"set":"empty"}`,"","<b>Tunneling (chat /tunnel)</b>",`tunnelEnabled: ${e.tunnelEnabled} \xB7 relay <code>${Me(e.tunnelRelayUrl)}</code> \xB7 max active: ${e.tunnelMaxActive}`,"","<b>Chat LLM fallback</b>",`enabled: ${e.chatLlmFallbackEnabled} \xB7 command: ${e.chatLlmShellCommand?"set":"empty"}`,`timeout ms: ${e.chatLlmTimeoutMs} \xB7 in/out chars: ${e.chatLlmMaxInputChars} / ${e.chatLlmMaxOutputChars}`,`needsTty: ${e.chatLlmNeedsTty} \xB7 workDir: ${Me(e.chatLlmWorkDir||"(empty)")}`,"","<b>Webhook receiver</b>",`enabled: ${e.webhookEnabled} \xB7 ${Me(e.webhookHost)}:${e.webhookPort} \xB7 token: ${Me(Mo(e.webhookToken))}`,"","<b>Watch (OS event eye)</b>",`enabled: ${e.watchEnabled} \xB7 debounce ms: ${e.watchDebounceMs} \xB7 max/min: ${e.watchMaxEventsPerMinute} \xB7 autoRestore: ${e.watchAutoRestore}`,"",`<code>${Me(U)}</code>`].join(`
82
- `)}function cm(e,t){if(!lm(t))return null;let n=t;if(n==="telegramBotToken")return`telegramBotToken: ${Mo(Ie(e))}`;if(n==="webhookToken")return`webhookToken: ${Mo(e.webhookToken)}`;let o=e[n];return`${t}: ${typeof o=="object"?JSON.stringify(o):String(o)}`}function Yk(e,t){let n=cm(e,t);if(!n)return null;let o=n.split(": ");return o.length<2?Me(n):`<b>${Me(o[0])}</b> ${Me(o.slice(1).join(": "))}`}function Vs(e,t){let n=im(t),o=!1,r=!1,s=!1;if(e==="telegramBotToken"){if(!Ot(n))throw new Error("Invalid bot token format (expect digits:secret from BotFather).");return fn(n),o=!0,s=!0,{reloadSuggested:o,shellWarning:r,tokenSaved:s}}let i=v();switch(e){case"gatewayMode":{let a=vo(n);if(!a)throw new Error("gatewayMode: use whatsapp | telegram | both (or wa, tg, b)");i.gatewayMode=a,o=!0;break}case"clusterEnabled":{let a=Se(n);if(a===null)throw new Error("clusterEnabled: true or false");i.clusterEnabled=a;break}case"clusterRole":{let a=n.trim().toLowerCase();if(a!=="primary"&&a!=="secondary")throw new Error("clusterRole: primary | secondary");i.clusterRole=a;break}case"clusterLabel":i.clusterLabel=n.trim().slice(0,128);break;case"clusterSenderBindings":{let a=n.trim();if(a===""||a==="{}"||a.toLowerCase()==="clear"){i.clusterSenderBindings={};break}let l;try{l=JSON.parse(a)}catch{throw new Error('clusterSenderBindings: pass a JSON object, e.g. {"wa:+15551234567":"workshop-box"}')}if(!l||typeof l!="object"||Array.isArray(l))throw new Error("clusterSenderBindings must be a JSON object of sender \u2192 label/id");let c={};for(let[u,d]of Object.entries(l)){if(typeof d!="string"||!d.trim())throw new Error(`clusterSenderBindings.${u}: value must be a non-empty string`);c[u]=d.trim()}i.clusterSenderBindings=c;break}case"commandPrefix":if(!n.trim())throw new Error("commandPrefix cannot be empty");i.commandPrefix=n.trim().slice(0,32);break;case"syncTimeoutMs":{let a=Number.parseInt(n,10);if(!Number.isFinite(a)||a<=0)throw new Error("syncTimeoutMs: positive integer (ms)");i.syncTimeoutMs=a;break}case"syncMaxBytes":{let a=Number.parseInt(n,10);if(!Number.isFinite(a)||a<=0)throw new Error("syncMaxBytes: positive integer");i.syncMaxBytes=a;break}case"jobLogTailLines":{let a=Number.parseInt(n,10);if(!Number.isFinite(a)||a<=0)throw new Error("jobLogTailLines: positive integer");i.jobLogTailLines=a;break}case"shell":if(!n.trim())throw new Error("shell: non-empty path");i.shell=n.trim().slice(0,4096),r=!0;break;case"recipesMacroDefaultCommand":if(!n.trim())throw new Error('recipesMacroDefaultCommand: non-empty shell snippet with "$OMNISH_TASK"');i.recipesMacroDefaultCommand=im(n).trim().slice(0,4096);break;case"appsCols":case"appsRows":case"appsFlushMs":case"appsMinIntervalMs":case"appsMaxFlushBytes":case"appsMaxSessions":case"appsMaxSessionsTotal":case"appsMaxWaChars":case"appsLogTailLines":case"appsSubmitDelayMs":case"appsClearInputDelayMs":case"recipesMaxTaskChars":case"fileSendMaxBytes":case"fileReceiveMaxBytes":{let a=Number.parseInt(n,10);if(!Number.isFinite(a)||a<0)throw new Error(`${e}: non-negative integer`);i[e]=a;break}case"appsClearInput":{let a=Se(n);if(a===null)throw new Error("appsClearInput: true or false");i.appsClearInput=a;break}case"appsClearInputSequence":i.appsClearInputSequence=n.trim().slice(0,200);break;case"appsSkipClearOnPasswordPrompt":{let a=Se(n);if(a===null)throw new Error("appsSkipClearOnPasswordPrompt: true or false");i.appsSkipClearOnPasswordPrompt=a;break}case"appsPasswordPromptHint":{let a=Se(n);if(a===null)throw new Error("appsPasswordPromptHint: true or false");i.appsPasswordPromptHint=a;break}case"fileInboxSubdir":i.fileInboxSubdir=n.trim().slice(0,80);break;case"fileReceiveRootMode":{let a=n.trim();if(!am.has(a))throw new Error(`fileReceiveRootMode: ${[...am].join(" | ")}`);i.fileReceiveRootMode=a;break}case"fileReceiveRootPath":i.fileReceiveRootPath=n.trim().slice(0,4096);break;case"recipesRunAttach":{let a=Se(n);if(a===null)throw new Error("recipesRunAttach: true or false");i.recipesRunAttach=a;break}case"recipesNotifyEnabled":{let a=Se(n);if(a===null)throw new Error("recipesNotifyEnabled: true or false");i.recipesNotifyEnabled=a;break}case"recipesAllowDangerousBuiltins":{let a=Se(n);if(a===null)throw new Error("recipesAllowDangerousBuiltins: true or false");i.recipesAllowDangerousBuiltins=a;break}case"serviceInstallFromChat":{let a=Se(n);if(a===null)throw new Error("serviceInstallFromChat: true or false");i.serviceInstallFromChat=a;break}case"updateCheckEnabled":{let a=Se(n);if(a===null)throw new Error("updateCheckEnabled: true or false");i.updateCheckEnabled=a;break}case"updateCheckIntervalMs":{let a=Number.parseInt(n,10);if(!Number.isFinite(a)||a<36e5)throw new Error("updateCheckIntervalMs: integer ms, minimum 3600000 (1 hour)");i.updateCheckIntervalMs=Math.min(6048e5,a);break}case"updateCheckPackageName":if(!n.trim())throw new Error("updateCheckPackageName: non-empty npm package name");i.updateCheckPackageName=n.trim().slice(0,214);break;case"updateInfoUrl":i.updateInfoUrl=n.trim().slice(0,2048);break;case"tunnelEnabled":{let a=Se(n);if(a===null)throw new Error("tunnelEnabled: true or false");return $({tunnelEnabled:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"tunnelRelayUrl":{let a=n.trim();if(!a)throw new Error("tunnelRelayUrl: non-empty URL");let l;try{l=new URL(a)}catch{throw new Error("tunnelRelayUrl: invalid URL")}if(l.protocol!=="http:"&&l.protocol!=="https:")throw new Error("tunnelRelayUrl: use http:// or https://");return $({tunnelRelayUrl:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"tunnelMaxActive":{let a=Number.parseInt(n.trim(),10);if(!Number.isFinite(a)||a<1||a>50)throw new Error("tunnelMaxActive: integer 1\u201350");return $({tunnelMaxActive:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"chatLlmFallbackEnabled":{let a=Se(n);if(a===null)throw new Error("chatLlmFallbackEnabled: true or false");return $({chatLlmFallbackEnabled:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"chatLlmShellCommand":return $({chatLlmShellCommand:n}),{reloadSuggested:o,shellWarning:r,tokenSaved:s};case"chatLlmTimeoutMs":{let a=Number.parseInt(n.trim(),10);if(!Number.isFinite(a)||a<=0)throw new Error("chatLlmTimeoutMs: positive integer (ms)");return $({chatLlmTimeoutMs:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"chatLlmMaxInputChars":{let a=Number.parseInt(n.trim(),10);if(!Number.isFinite(a)||a<=0)throw new Error("chatLlmMaxInputChars: positive integer");return $({chatLlmMaxInputChars:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"chatLlmMaxOutputChars":{let a=Number.parseInt(n.trim(),10);if(!Number.isFinite(a)||a<=0)throw new Error("chatLlmMaxOutputChars: positive integer");return $({chatLlmMaxOutputChars:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"chatLlmNeedsTty":{let a=Se(n);if(a===null)throw new Error("chatLlmNeedsTty: true or false");return $({chatLlmNeedsTty:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"chatLlmWorkDir":return $({chatLlmWorkDir:n.trim()}),{reloadSuggested:o,shellWarning:r,tokenSaved:s};case"chatAgentEnabled":{let a=Se(n);if(a===null)throw new Error("chatAgentEnabled: true or false");return $({chatAgentEnabled:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"chatAgentCommand":return $({chatAgentCommand:n.trim()}),{reloadSuggested:o,shellWarning:r,tokenSaved:s};case"chatAgentPerPeer":{let a=Se(n);if(a===null)throw new Error("chatAgentPerPeer: true or false");return $({chatAgentPerPeer:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"chatAgentMaxQueue":{let a=Number.parseInt(n.trim(),10);if(!Number.isFinite(a)||a<=0)throw new Error("chatAgentMaxQueue: positive integer");return $({chatAgentMaxQueue:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"webhookEnabled":{let a=Se(n);if(a===null)throw new Error("webhookEnabled: true or false");return $({webhookEnabled:a}),o=!0,{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"webhookPort":{let a=Number.parseInt(n.trim(),10);if(!Number.isFinite(a)||a<0||a>65535)throw new Error("webhookPort: integer 0\u201365535 (0 = random free port)");return $({webhookPort:a}),o=!0,{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"webhookHost":if(!n.trim())throw new Error("webhookHost: non-empty bind address");return $({webhookHost:n.trim().slice(0,256)}),o=!0,{reloadSuggested:o,shellWarning:r,tokenSaved:s};case"webhookToken":return $({webhookToken:n.trim()}),o=!0,{reloadSuggested:o,shellWarning:r,tokenSaved:s};case"watchEnabled":{let a=Se(n);if(a===null)throw new Error("watchEnabled: true or false");return $({watchEnabled:a}),ot(),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"watchDebounceMs":{let a=Number.parseInt(n.trim(),10);if(!Number.isFinite(a)||a<500||a>6e4)throw new Error("watchDebounceMs: integer 500\u201360000");return $({watchDebounceMs:a}),ot(),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"watchMaxEventsPerMinute":{let a=Number.parseInt(n.trim(),10);if(!Number.isFinite(a)||a<1||a>120)throw new Error("watchMaxEventsPerMinute: integer 1\u2013120");return $({watchMaxEventsPerMinute:a}),ot(),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"watchAutoRestore":{let a=Se(n);if(a===null)throw new Error("watchAutoRestore: true or false");return $({watchAutoRestore:a}),ot(),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"mediaSendFiles":{let a=Se(n);if(a===null)throw new Error("mediaSendFiles: true or false");return $({mediaSendFiles:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"mediaInstallFromChat":{let a=Se(n);if(a===null)throw new Error("mediaInstallFromChat: true or false");return $({mediaInstallFromChat:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"mediaUrlAutoDl":{let a=Se(n);if(a===null)throw new Error("mediaUrlAutoDl: true or false");return $({mediaUrlAutoDl:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"mediaOutputDir":return $({mediaOutputDir:n.trim()}),{reloadSuggested:o,shellWarning:r,tokenSaved:s};case"mediaMaxBytes":{let a=Number.parseInt(n.trim(),10);if(!Number.isFinite(a)||a<0)throw new Error("mediaMaxBytes: non-negative integer");return $({mediaMaxBytes:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"mediaWhisperModel":return $({mediaWhisperModel:n.trim()}),{reloadSuggested:o,shellWarning:r,tokenSaved:s};case"mediaWhisperDevice":{let a=n.trim().toLowerCase();if(a!=="auto"&&a!=="cpu"&&a!=="cuda")throw new Error("mediaWhisperDevice: auto, cpu, or cuda");return $({mediaWhisperDevice:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"mediaTranscribeTimeoutMs":{let a=Number.parseInt(n.trim(),10);if(!Number.isFinite(a)||a<=0)throw new Error("mediaTranscribeTimeoutMs: positive integer (ms)");return $({mediaTranscribeTimeoutMs:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"mediaTranscribeEngine":{let a=n.trim().toLowerCase();if(a!=="whisper"&&a!=="transformers"&&a!=="auto")throw new Error("mediaTranscribeEngine: whisper, transformers, or auto");return $({mediaTranscribeEngine:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"mediaTranscribeFallback":{let a=Se(n);if(a===null)throw new Error("mediaTranscribeFallback: true or false");return $({mediaTranscribeFallback:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"progressUpdates":{let a=Se(n);if(a===null)throw new Error("progressUpdates: true or false");return $({progressUpdates:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"pullYtDlpPath":return $({pullYtDlpPath:n.trim()}),{reloadSuggested:o,shellWarning:r,tokenSaved:s};case"pullFfmpegPath":return $({pullFfmpegPath:n.trim()}),{reloadSuggested:o,shellWarning:r,tokenSaved:s};case"pullWhisperPath":return $({pullWhisperPath:n.trim()}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}return Ve(i),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}async function um(e,t){let n=e.trim(),o=n.toLowerCase();if(!n||o==="help")return X(Mp());if(o==="keys"||o==="help keys")return p(["*Configurable keys* (/config set)","",xn.join(", "),"","Examples:","/config set clusterLabel work-laptop",'/config set fileReceiveRootPath "/path/with spaces"',"/config set gatewayMode both","/config set tunnelEnabled true","/config set chatLlmFallbackEnabled false","/config set webhookEnabled true","/config set watchEnabled true"].join(`
83
- `));if(o==="show"){let i=v();return ye(zk(i),Kk(i))}let r=n.match(/^get\s+(\S+)\s*$/i);if(r){let i=r[1],a=v(),l=cm(a,i);if(!l)return p(`Unknown key "${i}". /config keys`);let c=Yk(a,i);return ye(`${l}
79
+ `).trimEnd())}function Hl(e){return p(`Unknown recipe: ${e}
80
+ /run list`)}function Jl(){return[{kind:"title",text:"Apps (interactive CLI)"},{kind:"sub",text:"Sessions"},{kind:"bullet",text:"/apps start <name> <command\u2026> \u2014 spawn in session cwd; auto-attaches"},{kind:"bullet",text:"/apps attach <name> | /apps detach"},{kind:"bullet",text:"/apps list | /apps info <name>"},{kind:"gap"},{kind:"sub",text:"Input"},{kind:"bullet",text:"/apps send <name> <text> \u2014 text + newline"},{kind:"bullet",text:">name text \u2014 shorthand for send"},{kind:"bullet",text:"/apps key <name> <KEY[,KEY\u2026]> \u2014 Enter, ^C, Up, Esc, \\x1b, \u2026"},{kind:"gap"},{kind:"sub",text:"Output & control"},{kind:"bullet",text:"/apps tail <name> [lines] | /apps since <name>"},{kind:"bullet",text:"/apps mute|unmute <name> | /apps raw <name> on|off"},{kind:"bullet",text:"/apps resize <name> <cols> <rows>"},{kind:"bullet",text:"/apps stop <name> | /apps kill <name> | /apps rm <name>"},{kind:"bullet",text:"/apps <session> publish \u2014 share session command to online catalog"},{kind:"bullet",text:"/apps online trending | show <publicId> | <publicId> download \u2014 browse app templates only"},{kind:"gap"},{kind:"p",text:"Attached: plain DMs go to the focused app. Escaped by ! prefix, /commands, or >other."},{kind:"p",text:"Free shell: !!start \u2014 plain DMs run as sync shell when no focused PTY; !!stop \u2014 off. Attached session wins over free shell for plain text."}]}function Vk(e){let{errors:t,warns:n,infos:o}=Ll(e),r=[{kind:"title",text:"Security check"}];if(e.length===0)return r.push({kind:"p",text:"No issues reported by automated rules."},{kind:"gap"},{kind:"p",text:"Allowlisted remote shell access is still equivalent to sharing credentials with those identities."}),r;r.push({kind:"p",text:`${t} error(s), ${n} warning(s), ${o} note(s).`}),r.push({kind:"gap"});let s=(i,a)=>{if(a.length!==0){r.push({kind:"sub",text:`${i} (${a.length})`});for(let l of a){let c=l.severity.toUpperCase();r.push({kind:"bullet",text:$e(`[${c}] ${l.code}: ${l.message}`)}),l.detail&&r.push({kind:"bullet",text:$e(l.detail)}),l.fixHint&&r.push({kind:"bullet",text:$e(`Fix: ${l.fixHint}`)})}r.push({kind:"gap"})}};return s("Errors",e.filter(i=>i.severity==="error")),s("Warnings",e.filter(i=>i.severity==="warn")),s("Notes",e.filter(i=>i.severity==="info")),r.push({kind:"p",text:"Allowlisted identities can run commands as this user; treat them like passwords."}),r}function im(e){return X(Vk(e))}function am(){return[{kind:"title",text:"Security tips"},{kind:"p",text:"Practical hardening for this gateway:"},{kind:"bullet",text:"Keep allowlists minimal \u2014 only numbers and tg: ids you trust."},{kind:"bullet",text:'Never use "*" in allowFrom; treat allowed identities like passwords.'},{kind:"bullet",text:"chmod 600 config.json and chmod 700 your data dir (~/.omnish or OMNISH_HOME)."},{kind:"bullet",text:"Do not paste bot tokens in group chats; revoke at @BotFather if leaked."},{kind:"bullet",text:"Prefer TELEGRAM_BOT_TOKEN via systemd/env over chat if your Telegram logs worry you."},{kind:"bullet",text:"Run omnish as a normal user, not root, unless you fully accept that risk."},{kind:"gap"},{kind:"p",text:"Send /security on this chat or run omnish security on the host for automated checks."}]}function lm(){return[{kind:"title",text:"/security commands"},{kind:"bullet",text:"/security \u2014 full report (same checks as omnish security)"},{kind:"bullet",text:"/security summary \u2014 one-line counts"},{kind:"bullet",text:"/security tips \u2014 short hardening checklist"},{kind:"bullet",text:"CLI: omnish security [--json] for scripts and monitoring"}]}function Me(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function Mo(e){let t=e.trim();return t?t.length<=8?"(set)":`${t.slice(0,4)}\u2026${t.slice(-4)}`:"(empty)"}function cm(e){let t=e.trim();return(t.startsWith('"')&&t.endsWith('"')&&t.length>=2||t.startsWith("'")&&t.endsWith("'")&&t.length>=2)&&(t=t.slice(1,-1)),t}function Se(e){let t=e.trim().toLowerCase();return t==="true"||t==="1"||t==="yes"||t==="on"?!0:t==="false"||t==="0"||t==="no"||t==="off"?!1:null}var um=new Set(["downloads","omnishData","sessionCwd","processCwd","fixed"]),xn=["gatewayMode","clusterEnabled","clusterRole","clusterLabel","clusterSenderBindings","commandPrefix","syncTimeoutMs","syncMaxBytes","jobLogTailLines","shell","appsCols","appsRows","appsFlushMs","appsMinIntervalMs","appsMaxFlushBytes","appsMaxSessions","appsMaxSessionsTotal","appsMaxWaChars","appsLogTailLines","appsSubmitDelayMs","appsClearInput","appsClearInputDelayMs","appsClearInputSequence","appsSkipClearOnPasswordPrompt","appsPasswordPromptHint","fileSendMaxBytes","fileReceiveMaxBytes","fileInboxSubdir","fileReceiveRootMode","fileReceiveRootPath","recipesAllowDangerousBuiltins","recipesMaxTaskChars","recipesMacroDefaultCommand","recipesRunAttach","recipesNotifyEnabled","telegramBotToken","serviceInstallFromChat","updateCheckEnabled","updateCheckIntervalMs","updateCheckPackageName","updateInfoUrl","chatLlmFallbackEnabled","chatLlmShellCommand","chatLlmTimeoutMs","chatLlmMaxInputChars","chatLlmMaxOutputChars","chatLlmNeedsTty","chatLlmWorkDir","chatAgentEnabled","chatAgentCommand","chatAgentPerPeer","chatAgentMaxQueue","tunnelEnabled","tunnelRelayUrl","tunnelMaxActive","webhookEnabled","webhookPort","webhookHost","webhookToken","watchEnabled","watchDebounceMs","watchMaxEventsPerMinute","watchAutoRestore","mediaSendFiles","mediaUrlAutoDl","mediaInstallFromChat","mediaOutputDir","mediaMaxBytes","mediaWhisperModel","mediaWhisperDevice","mediaTranscribeTimeoutMs","mediaTranscribeEngine","mediaTranscribeFallback","progressUpdates","pullYtDlpPath","pullFfmpegPath","pullWhisperPath"];function dm(e){return xn.includes(e)}function Qk(e){let t=Ie(e);return["*Config* (secrets masked)","",`gatewayMode: ${e.gatewayMode}`,`commandPrefix: ${e.commandPrefix}`,`shell: ${e.shell}`,`syncTimeoutMs: ${e.syncTimeoutMs}`,`syncMaxBytes: ${e.syncMaxBytes}`,`jobLogTailLines: ${e.jobLogTailLines}`,"","*Cluster*",`clusterEnabled: ${e.clusterEnabled}`,`clusterRole: ${e.clusterRole}`,`clusterLabel: ${e.clusterLabel||"(hostname)"}`,`clusterSenderBindings: ${Object.keys(e.clusterSenderBindings??{}).length} entries`,"","*Telegram*",`telegramBotToken: ${Mo(t)}`,`telegramAllowFrom: ${e.telegramAllowFrom.length} entries`,"","*WhatsApp allowFrom*",`${e.allowFrom.length} entries`,"","*Apps*",`appsCols \xD7 appsRows: ${e.appsCols}\xD7${e.appsRows}`,`appsFlushMs / appsMinIntervalMs: ${e.appsFlushMs} / ${e.appsMinIntervalMs}`,`appsMaxFlushBytes: ${e.appsMaxFlushBytes}`,`appsMaxSessions / total: ${e.appsMaxSessions} / ${e.appsMaxSessionsTotal}`,`appsMaxWaChars: ${e.appsMaxWaChars}`,`appsLogTailLines: ${e.appsLogTailLines}`,`appsSubmitDelayMs: ${e.appsSubmitDelayMs}`,`appsClearInput: ${e.appsClearInput}`,`appsClearInputDelayMs: ${e.appsClearInputDelayMs}`,`appsClearInputSequence: ${e.appsClearInputSequence}`,`appsSkipClearOnPasswordPrompt: ${e.appsSkipClearOnPasswordPrompt}`,`appsPasswordPromptHint: ${e.appsPasswordPromptHint}`,"","*Files*",`fileSendMaxBytes / fileReceiveMaxBytes: ${e.fileSendMaxBytes} / ${e.fileReceiveMaxBytes}`,`fileInboxSubdir: ${e.fileInboxSubdir}`,`fileReceiveRootMode: ${e.fileReceiveRootMode}`,`fileReceiveRootPath: ${e.fileReceiveRootPath||"(empty)"}`,"","*Recipes*",`recipesAllowDangerousBuiltins: ${e.recipesAllowDangerousBuiltins}`,`recipesMaxTaskChars: ${e.recipesMaxTaskChars}`,`recipesMacroDefaultCommand: ${e.recipesMacroDefaultCommand}`,`recipesRunAttach: ${e.recipesRunAttach}`,`recipesNotifyEnabled: ${e.recipesNotifyEnabled}`,"","*Service (chat)*",`serviceInstallFromChat: ${e.serviceInstallFromChat}`,"","*Updates (optional)*",`updateCheckEnabled: ${e.updateCheckEnabled}`,`updateCheckIntervalMs: ${e.updateCheckIntervalMs}`,`updateCheckPackageName: ${e.updateCheckPackageName}`,`updateInfoUrl: ${e.updateInfoUrl?"(set)":"(empty)"}`,"","*Tunneling (chat /tunnel)*",`tunnelEnabled: ${e.tunnelEnabled}`,`tunnelRelayUrl: ${e.tunnelRelayUrl}`,`tunnelMaxActive: ${e.tunnelMaxActive}`,"","*Chat LLM fallback (optional)*",`chatLlmFallbackEnabled: ${e.chatLlmFallbackEnabled}`,`chatLlmShellCommand: ${e.chatLlmShellCommand?"(set)":"(empty)"}`,`chatLlmTimeoutMs: ${e.chatLlmTimeoutMs}`,`chatLlmMaxInputChars / chatLlmMaxOutputChars: ${e.chatLlmMaxInputChars} / ${e.chatLlmMaxOutputChars}`,`chatLlmNeedsTty: ${e.chatLlmNeedsTty}`,`chatLlmWorkDir: ${e.chatLlmWorkDir||"(empty \u2014 temp dir per run)"}`,`chatAgentEnabled: ${e.chatAgentEnabled===!0}`,`chatAgentCommand: ${e.chatAgentCommand?"(set)":"(empty)"}`,`chatAgentPerPeer: ${e.chatAgentPerPeer!==!1}`,`chatAgentMaxQueue: ${e.chatAgentMaxQueue??64}`,"","*Webhook receiver (optional)*",`webhookEnabled: ${e.webhookEnabled}`,`webhookPort: ${e.webhookPort} (0 = random)`,`webhookHost: ${e.webhookHost}`,`webhookToken: ${Mo(e.webhookToken)}`,"","*Watch (OS event eye)*",`watchEnabled: ${e.watchEnabled}`,`watchDebounceMs: ${e.watchDebounceMs}`,`watchMaxEventsPerMinute: ${e.watchMaxEventsPerMinute}`,`watchAutoRestore: ${e.watchAutoRestore}`,"","*Media (/dl, /tr, /edit)*",`mediaSendFiles: ${e.mediaSendFiles}`,`mediaInstallFromChat: ${e.mediaInstallFromChat}`,`mediaUrlAutoDl: ${e.mediaUrlAutoDl}`,`mediaOutputDir: ${e.mediaOutputDir||"(Downloads/Omnish or session cwd via /receive here)"}`,`mediaMaxBytes: ${e.mediaMaxBytes}`,`mediaWhisperModel: ${e.mediaWhisperModel}`,`mediaWhisperDevice: ${e.mediaWhisperDevice}`,`mediaTranscribeTimeoutMs: ${e.mediaTranscribeTimeoutMs}`,`mediaTranscribeEngine: ${e.mediaTranscribeEngine}`,`mediaTranscribeFallback: ${e.mediaTranscribeFallback}`,`progressUpdates: ${e.progressUpdates}`,"",`File: ${U}`].join(`
81
+ `)}function Xk(e){let t=Ie(e);return["<b>Config</b> (secrets masked)","",`<b>gatewayMode</b> ${Me(e.gatewayMode)}`,`<b>commandPrefix</b> ${Me(e.commandPrefix)}`,`<b>shell</b> ${Me(e.shell)}`,`<b>syncTimeoutMs</b> ${e.syncTimeoutMs}`,`<b>syncMaxBytes</b> ${e.syncMaxBytes}`,`<b>jobLogTailLines</b> ${e.jobLogTailLines}`,"","<b>Cluster</b>",`clusterEnabled: ${e.clusterEnabled} \xB7 clusterRole: ${Me(e.clusterRole)}`,`clusterLabel: ${Me(e.clusterLabel||"(hostname)")}`,`clusterSenderBindings: ${Object.keys(e.clusterSenderBindings??{}).length} entries`,"","<b>Telegram</b>",`telegramBotToken: ${Me(Mo(t))}`,`telegramAllowFrom: ${e.telegramAllowFrom.length} entries`,"",`<b>WhatsApp allowFrom</b> ${e.allowFrom.length} entries`,"","<b>Apps</b>",`${e.appsCols}\xD7${e.appsRows} flush ${e.appsFlushMs}/${e.appsMinIntervalMs} ms \u2026`,"","<b>Files</b>",`${Me(e.fileReceiveRootMode)} \xB7 inbox ${Me(e.fileInboxSubdir)}`,"","<b>Service (chat)</b>",`serviceInstallFromChat: ${e.serviceInstallFromChat}`,"","<b>Updates (optional)</b>",`updateCheckEnabled: ${e.updateCheckEnabled} \xB7 interval ms: ${e.updateCheckIntervalMs}`,`package: <code>${Me(e.updateCheckPackageName)}</code> \xB7 info URL: ${e.updateInfoUrl?"set":"empty"}`,"","<b>Tunneling (chat /tunnel)</b>",`tunnelEnabled: ${e.tunnelEnabled} \xB7 relay <code>${Me(e.tunnelRelayUrl)}</code> \xB7 max active: ${e.tunnelMaxActive}`,"","<b>Chat LLM fallback</b>",`enabled: ${e.chatLlmFallbackEnabled} \xB7 command: ${e.chatLlmShellCommand?"set":"empty"}`,`timeout ms: ${e.chatLlmTimeoutMs} \xB7 in/out chars: ${e.chatLlmMaxInputChars} / ${e.chatLlmMaxOutputChars}`,`needsTty: ${e.chatLlmNeedsTty} \xB7 workDir: ${Me(e.chatLlmWorkDir||"(empty)")}`,"","<b>Webhook receiver</b>",`enabled: ${e.webhookEnabled} \xB7 ${Me(e.webhookHost)}:${e.webhookPort} \xB7 token: ${Me(Mo(e.webhookToken))}`,"","<b>Watch (OS event eye)</b>",`enabled: ${e.watchEnabled} \xB7 debounce ms: ${e.watchDebounceMs} \xB7 max/min: ${e.watchMaxEventsPerMinute} \xB7 autoRestore: ${e.watchAutoRestore}`,"",`<code>${Me(U)}</code>`].join(`
82
+ `)}function pm(e,t){if(!dm(t))return null;let n=t;if(n==="telegramBotToken")return`telegramBotToken: ${Mo(Ie(e))}`;if(n==="webhookToken")return`webhookToken: ${Mo(e.webhookToken)}`;let o=e[n];return`${t}: ${typeof o=="object"?JSON.stringify(o):String(o)}`}function Zk(e,t){let n=pm(e,t);if(!n)return null;let o=n.split(": ");return o.length<2?Me(n):`<b>${Me(o[0])}</b> ${Me(o.slice(1).join(": "))}`}function Vs(e,t){let n=cm(t),o=!1,r=!1,s=!1;if(e==="telegramBotToken"){if(!Ot(n))throw new Error("Invalid bot token format (expect digits:secret from BotFather).");return fn(n),o=!0,s=!0,{reloadSuggested:o,shellWarning:r,tokenSaved:s}}let i=v();switch(e){case"gatewayMode":{let a=vo(n);if(!a)throw new Error("gatewayMode: use whatsapp | telegram | both (or wa, tg, b)");i.gatewayMode=a,o=!0;break}case"clusterEnabled":{let a=Se(n);if(a===null)throw new Error("clusterEnabled: true or false");i.clusterEnabled=a;break}case"clusterRole":{let a=n.trim().toLowerCase();if(a!=="primary"&&a!=="secondary")throw new Error("clusterRole: primary | secondary");i.clusterRole=a;break}case"clusterLabel":i.clusterLabel=n.trim().slice(0,128);break;case"clusterSenderBindings":{let a=n.trim();if(a===""||a==="{}"||a.toLowerCase()==="clear"){i.clusterSenderBindings={};break}let l;try{l=JSON.parse(a)}catch{throw new Error('clusterSenderBindings: pass a JSON object, e.g. {"wa:+15551234567":"workshop-box"}')}if(!l||typeof l!="object"||Array.isArray(l))throw new Error("clusterSenderBindings must be a JSON object of sender \u2192 label/id");let c={};for(let[u,d]of Object.entries(l)){if(typeof d!="string"||!d.trim())throw new Error(`clusterSenderBindings.${u}: value must be a non-empty string`);c[u]=d.trim()}i.clusterSenderBindings=c;break}case"commandPrefix":if(!n.trim())throw new Error("commandPrefix cannot be empty");i.commandPrefix=n.trim().slice(0,32);break;case"syncTimeoutMs":{let a=Number.parseInt(n,10);if(!Number.isFinite(a)||a<=0)throw new Error("syncTimeoutMs: positive integer (ms)");i.syncTimeoutMs=a;break}case"syncMaxBytes":{let a=Number.parseInt(n,10);if(!Number.isFinite(a)||a<=0)throw new Error("syncMaxBytes: positive integer");i.syncMaxBytes=a;break}case"jobLogTailLines":{let a=Number.parseInt(n,10);if(!Number.isFinite(a)||a<=0)throw new Error("jobLogTailLines: positive integer");i.jobLogTailLines=a;break}case"shell":if(!n.trim())throw new Error("shell: non-empty path");i.shell=n.trim().slice(0,4096),r=!0;break;case"recipesMacroDefaultCommand":if(!n.trim())throw new Error('recipesMacroDefaultCommand: non-empty shell snippet with "$OMNISH_TASK"');i.recipesMacroDefaultCommand=cm(n).trim().slice(0,4096);break;case"appsCols":case"appsRows":case"appsFlushMs":case"appsMinIntervalMs":case"appsMaxFlushBytes":case"appsMaxSessions":case"appsMaxSessionsTotal":case"appsMaxWaChars":case"appsLogTailLines":case"appsSubmitDelayMs":case"appsClearInputDelayMs":case"recipesMaxTaskChars":case"fileSendMaxBytes":case"fileReceiveMaxBytes":{let a=Number.parseInt(n,10);if(!Number.isFinite(a)||a<0)throw new Error(`${e}: non-negative integer`);i[e]=a;break}case"appsClearInput":{let a=Se(n);if(a===null)throw new Error("appsClearInput: true or false");i.appsClearInput=a;break}case"appsClearInputSequence":i.appsClearInputSequence=n.trim().slice(0,200);break;case"appsSkipClearOnPasswordPrompt":{let a=Se(n);if(a===null)throw new Error("appsSkipClearOnPasswordPrompt: true or false");i.appsSkipClearOnPasswordPrompt=a;break}case"appsPasswordPromptHint":{let a=Se(n);if(a===null)throw new Error("appsPasswordPromptHint: true or false");i.appsPasswordPromptHint=a;break}case"fileInboxSubdir":i.fileInboxSubdir=n.trim().slice(0,80);break;case"fileReceiveRootMode":{let a=n.trim();if(!um.has(a))throw new Error(`fileReceiveRootMode: ${[...um].join(" | ")}`);i.fileReceiveRootMode=a;break}case"fileReceiveRootPath":i.fileReceiveRootPath=n.trim().slice(0,4096);break;case"recipesRunAttach":{let a=Se(n);if(a===null)throw new Error("recipesRunAttach: true or false");i.recipesRunAttach=a;break}case"recipesNotifyEnabled":{let a=Se(n);if(a===null)throw new Error("recipesNotifyEnabled: true or false");i.recipesNotifyEnabled=a;break}case"recipesAllowDangerousBuiltins":{let a=Se(n);if(a===null)throw new Error("recipesAllowDangerousBuiltins: true or false");i.recipesAllowDangerousBuiltins=a;break}case"serviceInstallFromChat":{let a=Se(n);if(a===null)throw new Error("serviceInstallFromChat: true or false");i.serviceInstallFromChat=a;break}case"updateCheckEnabled":{let a=Se(n);if(a===null)throw new Error("updateCheckEnabled: true or false");i.updateCheckEnabled=a;break}case"updateCheckIntervalMs":{let a=Number.parseInt(n,10);if(!Number.isFinite(a)||a<36e5)throw new Error("updateCheckIntervalMs: integer ms, minimum 3600000 (1 hour)");i.updateCheckIntervalMs=Math.min(6048e5,a);break}case"updateCheckPackageName":if(!n.trim())throw new Error("updateCheckPackageName: non-empty npm package name");i.updateCheckPackageName=n.trim().slice(0,214);break;case"updateInfoUrl":i.updateInfoUrl=n.trim().slice(0,2048);break;case"tunnelEnabled":{let a=Se(n);if(a===null)throw new Error("tunnelEnabled: true or false");return $({tunnelEnabled:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"tunnelRelayUrl":{let a=n.trim();if(!a)throw new Error("tunnelRelayUrl: non-empty URL");let l;try{l=new URL(a)}catch{throw new Error("tunnelRelayUrl: invalid URL")}if(l.protocol!=="http:"&&l.protocol!=="https:")throw new Error("tunnelRelayUrl: use http:// or https://");return $({tunnelRelayUrl:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"tunnelMaxActive":{let a=Number.parseInt(n.trim(),10);if(!Number.isFinite(a)||a<1||a>50)throw new Error("tunnelMaxActive: integer 1\u201350");return $({tunnelMaxActive:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"chatLlmFallbackEnabled":{let a=Se(n);if(a===null)throw new Error("chatLlmFallbackEnabled: true or false");return $({chatLlmFallbackEnabled:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"chatLlmShellCommand":return $({chatLlmShellCommand:n}),{reloadSuggested:o,shellWarning:r,tokenSaved:s};case"chatLlmTimeoutMs":{let a=Number.parseInt(n.trim(),10);if(!Number.isFinite(a)||a<=0)throw new Error("chatLlmTimeoutMs: positive integer (ms)");return $({chatLlmTimeoutMs:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"chatLlmMaxInputChars":{let a=Number.parseInt(n.trim(),10);if(!Number.isFinite(a)||a<=0)throw new Error("chatLlmMaxInputChars: positive integer");return $({chatLlmMaxInputChars:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"chatLlmMaxOutputChars":{let a=Number.parseInt(n.trim(),10);if(!Number.isFinite(a)||a<=0)throw new Error("chatLlmMaxOutputChars: positive integer");return $({chatLlmMaxOutputChars:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"chatLlmNeedsTty":{let a=Se(n);if(a===null)throw new Error("chatLlmNeedsTty: true or false");return $({chatLlmNeedsTty:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"chatLlmWorkDir":return $({chatLlmWorkDir:n.trim()}),{reloadSuggested:o,shellWarning:r,tokenSaved:s};case"chatAgentEnabled":{let a=Se(n);if(a===null)throw new Error("chatAgentEnabled: true or false");return $({chatAgentEnabled:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"chatAgentCommand":return $({chatAgentCommand:n.trim()}),{reloadSuggested:o,shellWarning:r,tokenSaved:s};case"chatAgentPerPeer":{let a=Se(n);if(a===null)throw new Error("chatAgentPerPeer: true or false");return $({chatAgentPerPeer:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"chatAgentMaxQueue":{let a=Number.parseInt(n.trim(),10);if(!Number.isFinite(a)||a<=0)throw new Error("chatAgentMaxQueue: positive integer");return $({chatAgentMaxQueue:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"webhookEnabled":{let a=Se(n);if(a===null)throw new Error("webhookEnabled: true or false");return $({webhookEnabled:a}),o=!0,{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"webhookPort":{let a=Number.parseInt(n.trim(),10);if(!Number.isFinite(a)||a<0||a>65535)throw new Error("webhookPort: integer 0\u201365535 (0 = random free port)");return $({webhookPort:a}),o=!0,{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"webhookHost":if(!n.trim())throw new Error("webhookHost: non-empty bind address");return $({webhookHost:n.trim().slice(0,256)}),o=!0,{reloadSuggested:o,shellWarning:r,tokenSaved:s};case"webhookToken":return $({webhookToken:n.trim()}),o=!0,{reloadSuggested:o,shellWarning:r,tokenSaved:s};case"watchEnabled":{let a=Se(n);if(a===null)throw new Error("watchEnabled: true or false");return $({watchEnabled:a}),ot(),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"watchDebounceMs":{let a=Number.parseInt(n.trim(),10);if(!Number.isFinite(a)||a<500||a>6e4)throw new Error("watchDebounceMs: integer 500\u201360000");return $({watchDebounceMs:a}),ot(),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"watchMaxEventsPerMinute":{let a=Number.parseInt(n.trim(),10);if(!Number.isFinite(a)||a<1||a>120)throw new Error("watchMaxEventsPerMinute: integer 1\u2013120");return $({watchMaxEventsPerMinute:a}),ot(),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"watchAutoRestore":{let a=Se(n);if(a===null)throw new Error("watchAutoRestore: true or false");return $({watchAutoRestore:a}),ot(),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"mediaSendFiles":{let a=Se(n);if(a===null)throw new Error("mediaSendFiles: true or false");return $({mediaSendFiles:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"mediaInstallFromChat":{let a=Se(n);if(a===null)throw new Error("mediaInstallFromChat: true or false");return $({mediaInstallFromChat:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"mediaUrlAutoDl":{let a=Se(n);if(a===null)throw new Error("mediaUrlAutoDl: true or false");return $({mediaUrlAutoDl:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"mediaOutputDir":return $({mediaOutputDir:n.trim()}),{reloadSuggested:o,shellWarning:r,tokenSaved:s};case"mediaMaxBytes":{let a=Number.parseInt(n.trim(),10);if(!Number.isFinite(a)||a<0)throw new Error("mediaMaxBytes: non-negative integer");return $({mediaMaxBytes:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"mediaWhisperModel":return $({mediaWhisperModel:n.trim()}),{reloadSuggested:o,shellWarning:r,tokenSaved:s};case"mediaWhisperDevice":{let a=n.trim().toLowerCase();if(a!=="auto"&&a!=="cpu"&&a!=="cuda")throw new Error("mediaWhisperDevice: auto, cpu, or cuda");return $({mediaWhisperDevice:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"mediaTranscribeTimeoutMs":{let a=Number.parseInt(n.trim(),10);if(!Number.isFinite(a)||a<=0)throw new Error("mediaTranscribeTimeoutMs: positive integer (ms)");return $({mediaTranscribeTimeoutMs:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"mediaTranscribeEngine":{let a=n.trim().toLowerCase();if(a!=="whisper"&&a!=="transformers"&&a!=="auto")throw new Error("mediaTranscribeEngine: whisper, transformers, or auto");return $({mediaTranscribeEngine:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"mediaTranscribeFallback":{let a=Se(n);if(a===null)throw new Error("mediaTranscribeFallback: true or false");return $({mediaTranscribeFallback:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"progressUpdates":{let a=Se(n);if(a===null)throw new Error("progressUpdates: true or false");return $({progressUpdates:a}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}case"pullYtDlpPath":return $({pullYtDlpPath:n.trim()}),{reloadSuggested:o,shellWarning:r,tokenSaved:s};case"pullFfmpegPath":return $({pullFfmpegPath:n.trim()}),{reloadSuggested:o,shellWarning:r,tokenSaved:s};case"pullWhisperPath":return $({pullWhisperPath:n.trim()}),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}return Ve(i),{reloadSuggested:o,shellWarning:r,tokenSaved:s}}async function mm(e,t){let n=e.trim(),o=n.toLowerCase();if(!n||o==="help")return X(Op());if(o==="keys"||o==="help keys")return p(["*Configurable keys* (/config set)","",xn.join(", "),"","Examples:","/config set clusterLabel work-laptop",'/config set fileReceiveRootPath "/path/with spaces"',"/config set gatewayMode both","/config set tunnelEnabled true","/config set chatLlmFallbackEnabled false","/config set webhookEnabled true","/config set watchEnabled true"].join(`
83
+ `));if(o==="show"){let i=v();return we(Qk(i),Xk(i))}let r=n.match(/^get\s+(\S+)\s*$/i);if(r){let i=r[1],a=v(),l=pm(a,i);if(!l)return p(`Unknown key "${i}". /config keys`);let c=Zk(a,i);return we(`${l}
84
84
 
85
- ${U}`,`${c}<br/><br/><code>${Me(U)}</code>`)}let s=n.match(/^set\s+(\S+)\s+([\s\S]+)$/i);if(s){let i=s[1],a=s[2]??"";if(!lm(i))return p(`Unknown key "${i}". /config keys`);try{let{reloadSuggested:l,shellWarning:c,tokenSaved:u}=Vs(i,a),d="";if(t?.reload&&l){let y=await t.reload();d=y.ok?`
85
+ ${U}`,`${c}<br/><br/><code>${Me(U)}</code>`)}let s=n.match(/^set\s+(\S+)\s+([\s\S]+)$/i);if(s){let i=s[1],a=s[2]??"";if(!dm(i))return p(`Unknown key "${i}". /config keys`);try{let{reloadSuggested:l,shellWarning:c,tokenSaved:u}=Vs(i,a),d="";if(t?.reload&&l){let y=await t.reload();d=y.ok?`
86
86
  Reload: ${y.summary}`:`
87
87
  Reload failed: ${y.error}`}else l?d=`
88
88
  Send /reload while omnish run is active (gatewayMode / Telegram).`:d=`
89
89
  Send /reload to pick up changes where applicable.`;let m=c?`
90
90
  \u26A0 shell changed \u2014 affects all commands run via omnish.`:"",f=u?`
91
- telegramBotToken saved (not echoed).`:"",h=`Set *${i}* (saved).${f}${m}${d}`,g=`<b>Set ${Me(i)}</b> (saved).${f?"<br/>token saved (not echoed).":""}${c?"<br/>\u26A0 shell path changed.":""}${Me(d)}`;return ye(h,g)}catch(l){return p(`Error: ${String(l)}`)}}return p("Unknown /config command. Try /config help or /config show")}ue();var Jl=[...xn,"platformToken","platformDeviceId"],Gl={platform_url:"tunnelRelayUrl",tunnel_relay_url:"tunnelRelayUrl",platform_token:"platformToken",token:"platformToken",omnish_token:"platformToken",platform_device_id:"platformDeviceId",device_id:"platformDeviceId"};for(let e of Jl)Gl[e]=e;function Qs(e){let t=e.trim().toLowerCase().replace(/-/g,"_");return Gl[t]??null}function dm(){return Object.keys(Gl).sort()}function ve(e){let t=e.trim().toLowerCase();return t==="true"||t==="1"||t==="yes"||t==="on"?!0:t==="false"||t==="0"||t==="no"||t==="off"?!1:null}function Vk(e){let t=e.trim();return(t.startsWith('"')&&t.endsWith('"')&&t.length>=2||t.startsWith("'")&&t.endsWith("'")&&t.length>=2)&&(t=t.slice(1,-1)),t}function Qk(e){return T[e]}function Ao(e,t){let n=Vk(t),o=v();switch(e){case"platformToken":return $({platformToken:n});case"platformDeviceId":return $({platformDeviceId:n});case"gatewayMode":{let r=vo(n);if(!r)throw new Error('gatewayMode: "whatsapp", "telegram", or "both"');return o.gatewayMode=r,Ve(o),o}case"telegramBotToken":if(!Ot(n))throw new Error("telegramBotToken: invalid bot token format");return fn(n);case"tunnelRelayUrl":{let r=n.trim();if(!r)throw new Error("tunnelRelayUrl: non-empty URL");let s;try{s=new URL(r)}catch{throw new Error("tunnelRelayUrl: invalid URL")}if(s.protocol!=="http:"&&s.protocol!=="https:")throw new Error("tunnelRelayUrl: use http:// or https://");return $({tunnelRelayUrl:r})}case"tunnelEnabled":{let r=ve(n);if(r===null)throw new Error("tunnelEnabled: true or false");return $({tunnelEnabled:r})}case"tunnelMaxActive":{let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<1||r>50)throw new Error("tunnelMaxActive: integer 1\u201350");return $({tunnelMaxActive:r})}case"clusterSenderBindings":{let r;try{r=JSON.parse(n)}catch{throw new Error("clusterSenderBindings: valid JSON object")}if(!r||typeof r!="object"||Array.isArray(r))throw new Error("clusterSenderBindings: JSON object required");return $({clusterSenderBindings:r})}default:break}if(e==="clusterEnabled"){let r=ve(n);if(r===null)throw new Error("clusterEnabled: true or false");o.clusterEnabled=r}else if(e==="clusterRole"){if(n!=="primary"&&n!=="secondary")throw new Error('clusterRole: "primary" or "secondary"');o.clusterRole=n}else if(e==="clusterLabel")o.clusterLabel=n.trim().slice(0,64);else if(e==="commandPrefix"){if(!n)throw new Error("commandPrefix: non-empty");o.commandPrefix=n}else if(e==="shell"){if(!n)throw new Error("shell: non-empty path");o.shell=n}else if(e==="syncTimeoutMs"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<=0)throw new Error("syncTimeoutMs: positive integer");o.syncTimeoutMs=r}else if(e==="syncMaxBytes"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<=0)throw new Error("syncMaxBytes: positive integer");o.syncMaxBytes=r}else if(e==="jobLogTailLines"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<=0)throw new Error("jobLogTailLines: positive integer");o.jobLogTailLines=r}else if(e==="appsCols"||e==="appsRows"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<20)throw new Error(`${e}: integer >= 20`);o[e]=r}else if(e==="appsFlushMs"||e==="appsMinIntervalMs"||e==="appsMaxFlushBytes"||e==="appsMaxWaChars"||e==="appsLogTailLines"||e==="appsSubmitDelayMs"||e==="appsClearInputDelayMs"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<0)throw new Error(`${e}: non-negative integer`);o[e]=r}else if(e==="appsMaxSessions"||e==="appsMaxSessionsTotal"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<1)throw new Error(`${e}: positive integer`);o[e]=r}else if(e==="appsClearInput"){let r=ve(n);if(r===null)throw new Error("appsClearInput: true or false");o.appsClearInput=r}else if(e==="appsClearInputSequence")o.appsClearInputSequence=n;else if(e==="appsSkipClearOnPasswordPrompt"){let r=ve(n);if(r===null)throw new Error("appsSkipClearOnPasswordPrompt: true or false");o.appsSkipClearOnPasswordPrompt=r}else if(e==="appsPasswordPromptHint"){let r=ve(n);if(r===null)throw new Error("appsPasswordPromptHint: true or false");o.appsPasswordPromptHint=r}else if(e==="fileSendMaxBytes"||e==="fileReceiveMaxBytes"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<0)throw new Error(`${e}: non-negative integer`);o[e]=r}else if(e==="fileInboxSubdir")o.fileInboxSubdir=n.trim().slice(0,128);else if(e==="fileReceiveRootMode"){if(!new Set(["downloads","omnishData","sessionCwd","processCwd","fixed"]).has(n))throw new Error("fileReceiveRootMode: downloads|omnishData|sessionCwd|processCwd|fixed");o.fileReceiveRootMode=n}else if(e==="fileReceiveRootPath")o.fileReceiveRootPath=n.trim().slice(0,4096);else if(e==="recipesAllowDangerousBuiltins"){let r=ve(n);if(r===null)throw new Error("recipesAllowDangerousBuiltins: true or false");o.recipesAllowDangerousBuiltins=r}else if(e==="recipesMaxTaskChars"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<0)throw new Error("recipesMaxTaskChars: non-negative integer");o.recipesMaxTaskChars=r}else if(e==="recipesMacroDefaultCommand"){if(!n.includes("$OMNISH_TASK"))throw new Error('recipesMacroDefaultCommand: must include "$OMNISH_TASK"');o.recipesMacroDefaultCommand=n}else if(e==="recipesRunAttach"){let r=ve(n);if(r===null)throw new Error("recipesRunAttach: true or false");o.recipesRunAttach=r}else if(e==="recipesNotifyEnabled"){let r=ve(n);if(r===null)throw new Error("recipesNotifyEnabled: true or false");o.recipesNotifyEnabled=r}else if(e==="serviceInstallFromChat"){let r=ve(n);if(r===null)throw new Error("serviceInstallFromChat: true or false");o.serviceInstallFromChat=r}else if(e==="updateCheckEnabled"){let r=ve(n);if(r===null)throw new Error("updateCheckEnabled: true or false");o.updateCheckEnabled=r}else if(e==="updateCheckIntervalMs"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<36e5)throw new Error("updateCheckIntervalMs: min 3600000");o.updateCheckIntervalMs=Math.min(6048e5,r)}else if(e==="updateCheckPackageName"){if(!n.trim())throw new Error("updateCheckPackageName: non-empty");o.updateCheckPackageName=n.trim().slice(0,214)}else if(e==="updateInfoUrl")o.updateInfoUrl=n.trim().slice(0,2048);else if(e==="chatLlmFallbackEnabled"){let r=ve(n);if(r===null)throw new Error("chatLlmFallbackEnabled: true or false");return $({chatLlmFallbackEnabled:r})}else{if(e==="chatLlmShellCommand")return $({chatLlmShellCommand:n});if(e==="chatLlmTimeoutMs"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<=0)throw new Error("chatLlmTimeoutMs: positive integer");return $({chatLlmTimeoutMs:r})}else if(e==="chatLlmMaxInputChars"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<=0)throw new Error("chatLlmMaxInputChars: positive integer");return $({chatLlmMaxInputChars:r})}else if(e==="chatLlmMaxOutputChars"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<=0)throw new Error("chatLlmMaxOutputChars: positive integer");return $({chatLlmMaxOutputChars:r})}else if(e==="chatLlmNeedsTty"){let r=ve(n);if(r===null)throw new Error("chatLlmNeedsTty: true or false");return $({chatLlmNeedsTty:r})}else{if(e==="chatLlmWorkDir")return $({chatLlmWorkDir:n.trim()});if(e==="chatAgentEnabled"){let r=ve(n);if(r===null)throw new Error("chatAgentEnabled: true or false");return $({chatAgentEnabled:r})}else{if(e==="chatAgentCommand")return $({chatAgentCommand:n.trim()});if(e==="chatAgentPerPeer"){let r=ve(n);if(r===null)throw new Error("chatAgentPerPeer: true or false");return $({chatAgentPerPeer:r})}else if(e==="chatAgentMaxQueue"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<=0)throw new Error("chatAgentMaxQueue: positive integer");return $({chatAgentMaxQueue:r})}else if(e==="webhookEnabled"){let r=ve(n);if(r===null)throw new Error("webhookEnabled: true or false");return $({webhookEnabled:r})}else if(e==="webhookPort"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<0||r>65535)throw new Error("webhookPort: integer 0\u201365535");return $({webhookPort:r})}else if(e==="webhookHost"){if(!n.trim())throw new Error("webhookHost: non-empty");return $({webhookHost:n.trim().slice(0,256)})}else{if(e==="webhookToken")return $({webhookToken:n.trim()});if(e==="watchEnabled"){let r=ve(n);if(r===null)throw new Error("watchEnabled: true or false");return $({watchEnabled:r})}else if(e==="watchDebounceMs"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<500||r>6e4)throw new Error("watchDebounceMs: integer 500\u201360000");return $({watchDebounceMs:r})}else if(e==="watchMaxEventsPerMinute"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<1||r>120)throw new Error("watchMaxEventsPerMinute: integer 1\u2013120");return $({watchMaxEventsPerMinute:r})}else if(e==="watchAutoRestore"){let r=ve(n);if(r===null)throw new Error("watchAutoRestore: true or false");return $({watchAutoRestore:r})}else if(e==="mediaSendFiles"){let r=ve(n);if(r===null)throw new Error("mediaSendFiles: true or false");return $({mediaSendFiles:r})}else if(e==="mediaInstallFromChat"){let r=ve(n);if(r===null)throw new Error("mediaInstallFromChat: true or false");return $({mediaInstallFromChat:r})}else if(e==="mediaUrlAutoDl"){let r=ve(n);if(r===null)throw new Error("mediaUrlAutoDl: true or false");return $({mediaUrlAutoDl:r})}else{if(e==="mediaOutputDir")return $({mediaOutputDir:n.trim()});if(e==="mediaMaxBytes"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<0)throw new Error("mediaMaxBytes: non-negative integer");return $({mediaMaxBytes:r})}else{if(e==="mediaWhisperModel")return $({mediaWhisperModel:n.trim()});if(e==="mediaWhisperDevice"){let r=n.trim().toLowerCase();if(r!=="auto"&&r!=="cpu"&&r!=="cuda")throw new Error("mediaWhisperDevice: auto, cpu, or cuda");return $({mediaWhisperDevice:r})}else if(e==="mediaTranscribeTimeoutMs"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<=0)throw new Error("mediaTranscribeTimeoutMs: positive integer");return $({mediaTranscribeTimeoutMs:r})}else if(e==="mediaTranscribeEngine"){let r=n.trim().toLowerCase();if(r!=="whisper"&&r!=="transformers"&&r!=="auto")throw new Error("mediaTranscribeEngine: whisper, transformers, or auto");return $({mediaTranscribeEngine:r})}else if(e==="mediaTranscribeFallback"){let r=ve(n);if(r===null)throw new Error("mediaTranscribeFallback: true or false");return $({mediaTranscribeFallback:r})}else if(e==="progressUpdates"){let r=ve(n);if(r===null)throw new Error("progressUpdates: true or false");return $({progressUpdates:r})}else{if(e==="pullYtDlpPath")return $({pullYtDlpPath:n.trim()});if(e==="pullFfmpegPath")return $({pullFfmpegPath:n.trim()});if(e==="pullWhisperPath")return $({pullWhisperPath:n.trim()});throw new Error(`Unsupported key: ${e}`)}}}}}}}return Ve(o),o}function pm(e){let t=Qk(e);if(e==="platformToken"||e==="platformDeviceId")return $({[e]:t});if(e==="tunnelRelayUrl")return $({tunnelRelayUrl:t});let n=v();return n[e]=t,Ve(n),n}function Vn(e,t){if(e==="platformToken"||e==="telegramBotToken"||e==="webhookToken"){let n=t.trim();return n?n.length<=8?"(set)":`${n.slice(0,4)}\u2026${n.slice(-4)}`:"(empty)"}return t}ue();j();import Io from"node:fs";import Yl from"node:os";import Or from"node:path";import gm from"node:crypto";var zl="\u2063omnish/c v1",Xk=/([nlra])=([^\s\]]*)/g;function xt(e){return e.replace(/-/g,"").slice(0,8)}function ql(e){return e.replace(/[\s\]\r\n]/g,"_").slice(0,64)}function Zk(e){let t=ql(xt(e.nodeId)),n=ql(e.label||""),o=e.role==="primary"?"p":"s",r=ql(e.activeNodeId?xt(e.activeNodeId):"");return`${zl} [n=${t} l=${n} r=${o} a=${r}]`}function mm(e,t){let n=Zk(t);if(e.includes(n))return e;let o=e.replace(/\s+$/,"");return o.length===0?n:`${o}
91
+ telegramBotToken saved (not echoed).`:"",h=`Set *${i}* (saved).${f}${m}${d}`,g=`<b>Set ${Me(i)}</b> (saved).${f?"<br/>token saved (not echoed).":""}${c?"<br/>\u26A0 shell path changed.":""}${Me(d)}`;return we(h,g)}catch(l){return p(`Error: ${String(l)}`)}}return p("Unknown /config command. Try /config help or /config show")}ue();var Gl=[...xn,"platformToken","platformDeviceId"],ql={platform_url:"tunnelRelayUrl",tunnel_relay_url:"tunnelRelayUrl",platform_token:"platformToken",token:"platformToken",omnish_token:"platformToken",platform_device_id:"platformDeviceId",device_id:"platformDeviceId"};for(let e of Gl)ql[e]=e;function Qs(e){let t=e.trim().toLowerCase().replace(/-/g,"_");return ql[t]??null}function fm(){return Object.keys(ql).sort()}function ve(e){let t=e.trim().toLowerCase();return t==="true"||t==="1"||t==="yes"||t==="on"?!0:t==="false"||t==="0"||t==="no"||t==="off"?!1:null}function eS(e){let t=e.trim();return(t.startsWith('"')&&t.endsWith('"')&&t.length>=2||t.startsWith("'")&&t.endsWith("'")&&t.length>=2)&&(t=t.slice(1,-1)),t}function tS(e){return T[e]}function Ao(e,t){let n=eS(t),o=v();switch(e){case"platformToken":return $({platformToken:n});case"platformDeviceId":return $({platformDeviceId:n});case"gatewayMode":{let r=vo(n);if(!r)throw new Error('gatewayMode: "whatsapp", "telegram", or "both"');return o.gatewayMode=r,Ve(o),o}case"telegramBotToken":if(!Ot(n))throw new Error("telegramBotToken: invalid bot token format");return fn(n);case"tunnelRelayUrl":{let r=n.trim();if(!r)throw new Error("tunnelRelayUrl: non-empty URL");let s;try{s=new URL(r)}catch{throw new Error("tunnelRelayUrl: invalid URL")}if(s.protocol!=="http:"&&s.protocol!=="https:")throw new Error("tunnelRelayUrl: use http:// or https://");return $({tunnelRelayUrl:r})}case"tunnelEnabled":{let r=ve(n);if(r===null)throw new Error("tunnelEnabled: true or false");return $({tunnelEnabled:r})}case"tunnelMaxActive":{let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<1||r>50)throw new Error("tunnelMaxActive: integer 1\u201350");return $({tunnelMaxActive:r})}case"clusterSenderBindings":{let r;try{r=JSON.parse(n)}catch{throw new Error("clusterSenderBindings: valid JSON object")}if(!r||typeof r!="object"||Array.isArray(r))throw new Error("clusterSenderBindings: JSON object required");return $({clusterSenderBindings:r})}default:break}if(e==="clusterEnabled"){let r=ve(n);if(r===null)throw new Error("clusterEnabled: true or false");o.clusterEnabled=r}else if(e==="clusterRole"){if(n!=="primary"&&n!=="secondary")throw new Error('clusterRole: "primary" or "secondary"');o.clusterRole=n}else if(e==="clusterLabel")o.clusterLabel=n.trim().slice(0,64);else if(e==="commandPrefix"){if(!n)throw new Error("commandPrefix: non-empty");o.commandPrefix=n}else if(e==="shell"){if(!n)throw new Error("shell: non-empty path");o.shell=n}else if(e==="syncTimeoutMs"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<=0)throw new Error("syncTimeoutMs: positive integer");o.syncTimeoutMs=r}else if(e==="syncMaxBytes"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<=0)throw new Error("syncMaxBytes: positive integer");o.syncMaxBytes=r}else if(e==="jobLogTailLines"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<=0)throw new Error("jobLogTailLines: positive integer");o.jobLogTailLines=r}else if(e==="appsCols"||e==="appsRows"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<20)throw new Error(`${e}: integer >= 20`);o[e]=r}else if(e==="appsFlushMs"||e==="appsMinIntervalMs"||e==="appsMaxFlushBytes"||e==="appsMaxWaChars"||e==="appsLogTailLines"||e==="appsSubmitDelayMs"||e==="appsClearInputDelayMs"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<0)throw new Error(`${e}: non-negative integer`);o[e]=r}else if(e==="appsMaxSessions"||e==="appsMaxSessionsTotal"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<1)throw new Error(`${e}: positive integer`);o[e]=r}else if(e==="appsClearInput"){let r=ve(n);if(r===null)throw new Error("appsClearInput: true or false");o.appsClearInput=r}else if(e==="appsClearInputSequence")o.appsClearInputSequence=n;else if(e==="appsSkipClearOnPasswordPrompt"){let r=ve(n);if(r===null)throw new Error("appsSkipClearOnPasswordPrompt: true or false");o.appsSkipClearOnPasswordPrompt=r}else if(e==="appsPasswordPromptHint"){let r=ve(n);if(r===null)throw new Error("appsPasswordPromptHint: true or false");o.appsPasswordPromptHint=r}else if(e==="fileSendMaxBytes"||e==="fileReceiveMaxBytes"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<0)throw new Error(`${e}: non-negative integer`);o[e]=r}else if(e==="fileInboxSubdir")o.fileInboxSubdir=n.trim().slice(0,128);else if(e==="fileReceiveRootMode"){if(!new Set(["downloads","omnishData","sessionCwd","processCwd","fixed"]).has(n))throw new Error("fileReceiveRootMode: downloads|omnishData|sessionCwd|processCwd|fixed");o.fileReceiveRootMode=n}else if(e==="fileReceiveRootPath")o.fileReceiveRootPath=n.trim().slice(0,4096);else if(e==="recipesAllowDangerousBuiltins"){let r=ve(n);if(r===null)throw new Error("recipesAllowDangerousBuiltins: true or false");o.recipesAllowDangerousBuiltins=r}else if(e==="recipesMaxTaskChars"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<0)throw new Error("recipesMaxTaskChars: non-negative integer");o.recipesMaxTaskChars=r}else if(e==="recipesMacroDefaultCommand"){if(!n.includes("$OMNISH_TASK"))throw new Error('recipesMacroDefaultCommand: must include "$OMNISH_TASK"');o.recipesMacroDefaultCommand=n}else if(e==="recipesRunAttach"){let r=ve(n);if(r===null)throw new Error("recipesRunAttach: true or false");o.recipesRunAttach=r}else if(e==="recipesNotifyEnabled"){let r=ve(n);if(r===null)throw new Error("recipesNotifyEnabled: true or false");o.recipesNotifyEnabled=r}else if(e==="serviceInstallFromChat"){let r=ve(n);if(r===null)throw new Error("serviceInstallFromChat: true or false");o.serviceInstallFromChat=r}else if(e==="updateCheckEnabled"){let r=ve(n);if(r===null)throw new Error("updateCheckEnabled: true or false");o.updateCheckEnabled=r}else if(e==="updateCheckIntervalMs"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<36e5)throw new Error("updateCheckIntervalMs: min 3600000");o.updateCheckIntervalMs=Math.min(6048e5,r)}else if(e==="updateCheckPackageName"){if(!n.trim())throw new Error("updateCheckPackageName: non-empty");o.updateCheckPackageName=n.trim().slice(0,214)}else if(e==="updateInfoUrl")o.updateInfoUrl=n.trim().slice(0,2048);else if(e==="chatLlmFallbackEnabled"){let r=ve(n);if(r===null)throw new Error("chatLlmFallbackEnabled: true or false");return $({chatLlmFallbackEnabled:r})}else{if(e==="chatLlmShellCommand")return $({chatLlmShellCommand:n});if(e==="chatLlmTimeoutMs"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<=0)throw new Error("chatLlmTimeoutMs: positive integer");return $({chatLlmTimeoutMs:r})}else if(e==="chatLlmMaxInputChars"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<=0)throw new Error("chatLlmMaxInputChars: positive integer");return $({chatLlmMaxInputChars:r})}else if(e==="chatLlmMaxOutputChars"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<=0)throw new Error("chatLlmMaxOutputChars: positive integer");return $({chatLlmMaxOutputChars:r})}else if(e==="chatLlmNeedsTty"){let r=ve(n);if(r===null)throw new Error("chatLlmNeedsTty: true or false");return $({chatLlmNeedsTty:r})}else{if(e==="chatLlmWorkDir")return $({chatLlmWorkDir:n.trim()});if(e==="chatAgentEnabled"){let r=ve(n);if(r===null)throw new Error("chatAgentEnabled: true or false");return $({chatAgentEnabled:r})}else{if(e==="chatAgentCommand")return $({chatAgentCommand:n.trim()});if(e==="chatAgentPerPeer"){let r=ve(n);if(r===null)throw new Error("chatAgentPerPeer: true or false");return $({chatAgentPerPeer:r})}else if(e==="chatAgentMaxQueue"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<=0)throw new Error("chatAgentMaxQueue: positive integer");return $({chatAgentMaxQueue:r})}else if(e==="webhookEnabled"){let r=ve(n);if(r===null)throw new Error("webhookEnabled: true or false");return $({webhookEnabled:r})}else if(e==="webhookPort"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<0||r>65535)throw new Error("webhookPort: integer 0\u201365535");return $({webhookPort:r})}else if(e==="webhookHost"){if(!n.trim())throw new Error("webhookHost: non-empty");return $({webhookHost:n.trim().slice(0,256)})}else{if(e==="webhookToken")return $({webhookToken:n.trim()});if(e==="watchEnabled"){let r=ve(n);if(r===null)throw new Error("watchEnabled: true or false");return $({watchEnabled:r})}else if(e==="watchDebounceMs"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<500||r>6e4)throw new Error("watchDebounceMs: integer 500\u201360000");return $({watchDebounceMs:r})}else if(e==="watchMaxEventsPerMinute"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<1||r>120)throw new Error("watchMaxEventsPerMinute: integer 1\u2013120");return $({watchMaxEventsPerMinute:r})}else if(e==="watchAutoRestore"){let r=ve(n);if(r===null)throw new Error("watchAutoRestore: true or false");return $({watchAutoRestore:r})}else if(e==="mediaSendFiles"){let r=ve(n);if(r===null)throw new Error("mediaSendFiles: true or false");return $({mediaSendFiles:r})}else if(e==="mediaInstallFromChat"){let r=ve(n);if(r===null)throw new Error("mediaInstallFromChat: true or false");return $({mediaInstallFromChat:r})}else if(e==="mediaUrlAutoDl"){let r=ve(n);if(r===null)throw new Error("mediaUrlAutoDl: true or false");return $({mediaUrlAutoDl:r})}else{if(e==="mediaOutputDir")return $({mediaOutputDir:n.trim()});if(e==="mediaMaxBytes"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<0)throw new Error("mediaMaxBytes: non-negative integer");return $({mediaMaxBytes:r})}else{if(e==="mediaWhisperModel")return $({mediaWhisperModel:n.trim()});if(e==="mediaWhisperDevice"){let r=n.trim().toLowerCase();if(r!=="auto"&&r!=="cpu"&&r!=="cuda")throw new Error("mediaWhisperDevice: auto, cpu, or cuda");return $({mediaWhisperDevice:r})}else if(e==="mediaTranscribeTimeoutMs"){let r=Number.parseInt(n,10);if(!Number.isFinite(r)||r<=0)throw new Error("mediaTranscribeTimeoutMs: positive integer");return $({mediaTranscribeTimeoutMs:r})}else if(e==="mediaTranscribeEngine"){let r=n.trim().toLowerCase();if(r!=="whisper"&&r!=="transformers"&&r!=="auto")throw new Error("mediaTranscribeEngine: whisper, transformers, or auto");return $({mediaTranscribeEngine:r})}else if(e==="mediaTranscribeFallback"){let r=ve(n);if(r===null)throw new Error("mediaTranscribeFallback: true or false");return $({mediaTranscribeFallback:r})}else if(e==="progressUpdates"){let r=ve(n);if(r===null)throw new Error("progressUpdates: true or false");return $({progressUpdates:r})}else{if(e==="pullYtDlpPath")return $({pullYtDlpPath:n.trim()});if(e==="pullFfmpegPath")return $({pullFfmpegPath:n.trim()});if(e==="pullWhisperPath")return $({pullWhisperPath:n.trim()});throw new Error(`Unsupported key: ${e}`)}}}}}}}return Ve(o),o}function hm(e){let t=tS(e);if(e==="platformToken"||e==="platformDeviceId")return $({[e]:t});if(e==="tunnelRelayUrl")return $({tunnelRelayUrl:t});let n=v();return n[e]=t,Ve(n),n}function Vn(e,t){if(e==="platformToken"||e==="telegramBotToken"||e==="webhookToken"){let n=t.trim();return n?n.length<=8?"(set)":`${n.slice(0,4)}\u2026${n.slice(-4)}`:"(empty)"}return t}ue();j();import Io from"node:fs";import Vl from"node:os";import Or from"node:path";import bm from"node:crypto";var Kl="\u2063omnish/c v1",nS=/([nlra])=([^\s\]]*)/g;function xt(e){return e.replace(/-/g,"").slice(0,8)}function zl(e){return e.replace(/[\s\]\r\n]/g,"_").slice(0,64)}function oS(e){let t=zl(xt(e.nodeId)),n=zl(e.label||""),o=e.role==="primary"?"p":"s",r=zl(e.activeNodeId?xt(e.activeNodeId):"");return`${Kl} [n=${t} l=${n} r=${o} a=${r}]`}function gm(e,t){let n=oS(t);if(e.includes(n))return e;let o=e.replace(/\s+$/,"");return o.length===0?n:`${o}
92
92
 
93
- ${n}`}function fm(e){if(!e||!e.includes(zl))return null;let t=e.indexOf(zl),n=e.slice(t),o=n.indexOf("["),r=n.indexOf("]");if(o<0||r<0||r<o)return null;let s=n.slice(o+1,r),i={};for(let l of s.matchAll(Xk))i[l[1]]=l[2]??"";if(!i.n)return null;let a=i.r==="p"?"primary":"secondary";return{nodeId:i.n,label:i.l??"",role:a,activeNodeId:i.a??""}}var ym=3,eS="node-id",tS="cluster-local.json",hm=8;function wm(){return Or.join(W,tS)}function Vl(){return Or.join(W,eS)}function ht(){D(W);let e=Vl();try{if(Io.existsSync(e)){let n=Io.readFileSync(e,"utf8").trim();if(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(n))return n}}catch{}let t=gm.randomUUID();return Io.writeFileSync(e,`${t}
94
- `,{mode:384}),t}function bm(){return{schemaVersion:ym,updatedAt:new Date().toISOString(),peers:[],senderBindings:{}}}function nS(e){if(!e||typeof e!="object")return{};let t={};for(let[n,o]of Object.entries(e)){if(!o||typeof o!="object")continue;let r=o;if(typeof r.nodeId!="string"||!r.nodeId)continue;let s=r.source==="config"?"config":"chat";t[n]={senderKey:typeof r.senderKey=="string"?r.senderKey:n,nodeId:r.nodeId,sinceIso:typeof r.sinceIso=="string"&&r.sinceIso?r.sinceIso:new Date(0).toISOString(),source:s}}return t}function Te(){let e=wm();try{let t=Io.readFileSync(e,"utf8"),n=JSON.parse(t),o=Array.isArray(n.peers)?n.peers.filter(s=>typeof s=="object"&&s!==null&&typeof s.nodeId=="string"&&typeof s.label=="string").map(s=>({nodeId:s.nodeId,label:s.label,role:s.role==="primary"?"primary":"secondary",lastSeenIso:typeof s.lastSeenIso=="string"?s.lastSeenIso:new Date(0).toISOString()})):[],r=nS(n.senderBindings);return{schemaVersion:ym,updatedAt:typeof n.updatedAt=="string"?n.updatedAt:new Date().toISOString(),peers:o,senderBindings:r}}catch{return bm()}}function oS(e,t){let n=Or.dirname(e);D(n);let o=`${JSON.stringify(t,null,2)}
95
- `,r=Or.join(n,`.${Or.basename(e)}.tmp.${process.pid}.${gm.randomBytes(4).toString("hex")}`);Io.writeFileSync(r,o,{mode:384}),Io.renameSync(r,e)}function Xl(e){let t=wm(),n=bm();for(let o=0;o<hm;o++){n=Te(),e(n),n.updatedAt=new Date().toISOString();try{return oS(t,n),n}catch{}}throw new Error(`Could not write cluster local state after ${hm} attempts: ${t}`)}function km(e){return(e.clusterLabel??"").trim()||Yl.hostname()}function Ee(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function Sm(e,t){let n=e.peers.findIndex(o=>o.nodeId===t.nodeId);n>=0?e.peers[n]=t:e.peers.push(t)}function Lr(e){return{nodeId:xt(ht()),label:km(e),role:e.clusterRole,lastSeenIso:new Date().toISOString()}}function vm(e,t,n){let o=n.trim();if(!o)return{ok:!1,reason:"not-found"};let r=Lr(t),s=[r];for(let l of e.peers)l.nodeId!==r.nodeId&&s.push(l);let i=o.toLowerCase();if(/^[0-9a-f]{8}$/i.test(o)){let l=s.find(c=>c.nodeId.toLowerCase()===i);if(l)return{ok:!0,peer:l}}let a=s.filter(l=>l.label.toLowerCase()===i);return a.length===1?{ok:!0,peer:a[0]}:a.length>1?{ok:!1,reason:"ambiguous-label",matches:a}:{ok:!1,reason:"not-found"}}function nn(e,t){let n=Te(),o=n.senderBindings[t];if(o&&o.nodeId)return o;let r=e.clusterSenderBindings?.[t];if(typeof r=="string"&&r.trim()){let s=vm(n,e,r);if(s.ok)return{senderKey:t,nodeId:s.peer.nodeId,sinceIso:new Date(0).toISOString(),source:"config"}}return null}function Ql(e,t,n="chat"){let o=v(),r=Te(),s=vm(r,o,t);if(!s.ok)return{state:r,resolved:s};let i=new Date().toISOString();return{state:Xl(l=>{l.senderBindings[e]={senderKey:e,nodeId:s.peer.nodeId,sinceIso:i,source:n},Sm(l,{...s.peer,lastSeenIso:i})}),resolved:s}}function rS(e){let t=null;return{state:Xl(o=>{o.senderBindings[e]&&(t=o.senderBindings[e]??null,delete o.senderBindings[e])}),removed:t}}function xm(e,t){let n=xt(ht()),o=new Date().toISOString();return Xl(r=>{if(Sm(r,{nodeId:e.nodeId,label:e.label||e.nodeId,role:e.role,lastSeenIso:o}),e.nodeId!==n&&t&&e.activeNodeId){let s=r.senderBindings[t];(!s||s.nodeId!==e.activeNodeId)&&(r.senderBindings[t]={senderKey:t,nodeId:e.activeNodeId,sinceIso:o,source:"chat"})}})}function Cm(e,t){let n=nn(t,e);return n?n.nodeId===xt(ht()):!1}function sS(e,t){let n=Lr(t).nodeId,o=new Set([n]);for(let s of e.peers)o.add(s.nodeId);return[...o].sort()[0]??n}function rt(e,t){return sS(e,t)===xt(ht())}function Xs(e,t,n){let o=xt(ht()),r=["*Computers*",""],s=n?nn(t,n):null;n&&(s?r.push(`Your binding: \`${s.nodeId}\` (${s.source})`):r.push("Your binding: (none \u2014 send /c use <label-or-id>)"),r.push(""));let i=new Map;i.set(o,Lr(t));for(let l of e.peers)i.set(l.nodeId,l);let a=[...i.values()].sort((l,c)=>l.label.localeCompare(c.label));if(a.length===0)return r.push("(no peers observed yet \u2014 run /c status from this chat)"),r.join(`
93
+ ${n}`}function ym(e){if(!e||!e.includes(Kl))return null;let t=e.indexOf(Kl),n=e.slice(t),o=n.indexOf("["),r=n.indexOf("]");if(o<0||r<0||r<o)return null;let s=n.slice(o+1,r),i={};for(let l of s.matchAll(nS))i[l[1]]=l[2]??"";if(!i.n)return null;let a=i.r==="p"?"primary":"secondary";return{nodeId:i.n,label:i.l??"",role:a,activeNodeId:i.a??""}}var km=3,rS="node-id",sS="cluster-local.json",wm=8;function Sm(){return Or.join(W,sS)}function Ql(){return Or.join(W,rS)}function ht(){D(W);let e=Ql();try{if(Io.existsSync(e)){let n=Io.readFileSync(e,"utf8").trim();if(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(n))return n}}catch{}let t=bm.randomUUID();return Io.writeFileSync(e,`${t}
94
+ `,{mode:384}),t}function vm(){return{schemaVersion:km,updatedAt:new Date().toISOString(),peers:[],senderBindings:{}}}function iS(e){if(!e||typeof e!="object")return{};let t={};for(let[n,o]of Object.entries(e)){if(!o||typeof o!="object")continue;let r=o;if(typeof r.nodeId!="string"||!r.nodeId)continue;let s=r.source==="config"?"config":"chat";t[n]={senderKey:typeof r.senderKey=="string"?r.senderKey:n,nodeId:r.nodeId,sinceIso:typeof r.sinceIso=="string"&&r.sinceIso?r.sinceIso:new Date(0).toISOString(),source:s}}return t}function Te(){let e=Sm();try{let t=Io.readFileSync(e,"utf8"),n=JSON.parse(t),o=Array.isArray(n.peers)?n.peers.filter(s=>typeof s=="object"&&s!==null&&typeof s.nodeId=="string"&&typeof s.label=="string").map(s=>({nodeId:s.nodeId,label:s.label,role:s.role==="primary"?"primary":"secondary",lastSeenIso:typeof s.lastSeenIso=="string"?s.lastSeenIso:new Date(0).toISOString()})):[],r=iS(n.senderBindings);return{schemaVersion:km,updatedAt:typeof n.updatedAt=="string"?n.updatedAt:new Date().toISOString(),peers:o,senderBindings:r}}catch{return vm()}}function aS(e,t){let n=Or.dirname(e);D(n);let o=`${JSON.stringify(t,null,2)}
95
+ `,r=Or.join(n,`.${Or.basename(e)}.tmp.${process.pid}.${bm.randomBytes(4).toString("hex")}`);Io.writeFileSync(r,o,{mode:384}),Io.renameSync(r,e)}function Zl(e){let t=Sm(),n=vm();for(let o=0;o<wm;o++){n=Te(),e(n),n.updatedAt=new Date().toISOString();try{return aS(t,n),n}catch{}}throw new Error(`Could not write cluster local state after ${wm} attempts: ${t}`)}function xm(e){return(e.clusterLabel??"").trim()||Vl.hostname()}function Ee(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function Cm(e,t){let n=e.peers.findIndex(o=>o.nodeId===t.nodeId);n>=0?e.peers[n]=t:e.peers.push(t)}function Lr(e){return{nodeId:xt(ht()),label:xm(e),role:e.clusterRole,lastSeenIso:new Date().toISOString()}}function Rm(e,t,n){let o=n.trim();if(!o)return{ok:!1,reason:"not-found"};let r=Lr(t),s=[r];for(let l of e.peers)l.nodeId!==r.nodeId&&s.push(l);let i=o.toLowerCase();if(/^[0-9a-f]{8}$/i.test(o)){let l=s.find(c=>c.nodeId.toLowerCase()===i);if(l)return{ok:!0,peer:l}}let a=s.filter(l=>l.label.toLowerCase()===i);return a.length===1?{ok:!0,peer:a[0]}:a.length>1?{ok:!1,reason:"ambiguous-label",matches:a}:{ok:!1,reason:"not-found"}}function nn(e,t){let n=Te(),o=n.senderBindings[t];if(o&&o.nodeId)return o;let r=e.clusterSenderBindings?.[t];if(typeof r=="string"&&r.trim()){let s=Rm(n,e,r);if(s.ok)return{senderKey:t,nodeId:s.peer.nodeId,sinceIso:new Date(0).toISOString(),source:"config"}}return null}function Xl(e,t,n="chat"){let o=v(),r=Te(),s=Rm(r,o,t);if(!s.ok)return{state:r,resolved:s};let i=new Date().toISOString();return{state:Zl(l=>{l.senderBindings[e]={senderKey:e,nodeId:s.peer.nodeId,sinceIso:i,source:n},Cm(l,{...s.peer,lastSeenIso:i})}),resolved:s}}function lS(e){let t=null;return{state:Zl(o=>{o.senderBindings[e]&&(t=o.senderBindings[e]??null,delete o.senderBindings[e])}),removed:t}}function Tm(e,t){let n=xt(ht()),o=new Date().toISOString();return Zl(r=>{if(Cm(r,{nodeId:e.nodeId,label:e.label||e.nodeId,role:e.role,lastSeenIso:o}),e.nodeId!==n&&t&&e.activeNodeId){let s=r.senderBindings[t];(!s||s.nodeId!==e.activeNodeId)&&(r.senderBindings[t]={senderKey:t,nodeId:e.activeNodeId,sinceIso:o,source:"chat"})}})}function Em(e,t){let n=nn(t,e);return n?n.nodeId===xt(ht()):!1}function cS(e,t){let n=Lr(t).nodeId,o=new Set([n]);for(let s of e.peers)o.add(s.nodeId);return[...o].sort()[0]??n}function rt(e,t){return cS(e,t)===xt(ht())}function Xs(e,t,n){let o=xt(ht()),r=["*Computers*",""],s=n?nn(t,n):null;n&&(s?r.push(`Your binding: \`${s.nodeId}\` (${s.source})`):r.push("Your binding: (none \u2014 send /c use <label-or-id>)"),r.push(""));let i=new Map;i.set(o,Lr(t));for(let l of e.peers)i.set(l.nodeId,l);let a=[...i.values()].sort((l,c)=>l.label.localeCompare(c.label));if(a.length===0)return r.push("(no peers observed yet \u2014 run /c status from this chat)"),r.join(`
96
96
  `);for(let l of a){let c=l.nodeId===o,d=[(s?l.nodeId===s.nodeId:!1)?"your binding":null,c?"you":null].filter(Boolean).join(", "),m=d?` (${d})`:"";r.push(`${Ne}*${l.label}*${m}
97
97
  id \`${l.nodeId}\` \xB7 role ${l.role}
98
98
  seen ${l.lastSeenIso}`)}return r.push(""),r.push(`Updated: ${e.updatedAt}`),r.join(`
99
- `)}function Kl(e,t,n){let o=xt(ht()),r=["<b>Computers</b>",""],s=n?nn(t,n):null;n&&(s?r.push(`Your binding: <code>${Ee(s.nodeId)}</code> (${Ee(s.source)})`):r.push("Your binding: (none \u2014 send /c use &lt;label-or-id&gt;)"),r.push(""));let i=new Map;i.set(o,Lr(t));for(let l of e.peers)i.set(l.nodeId,l);let a=[...i.values()].sort((l,c)=>l.label.localeCompare(c.label));if(a.length===0)return r.push("(no peers observed yet \u2014 run /c status from this chat)"),r.join(`
99
+ `)}function Yl(e,t,n){let o=xt(ht()),r=["<b>Computers</b>",""],s=n?nn(t,n):null;n&&(s?r.push(`Your binding: <code>${Ee(s.nodeId)}</code> (${Ee(s.source)})`):r.push("Your binding: (none \u2014 send /c use &lt;label-or-id&gt;)"),r.push(""));let i=new Map;i.set(o,Lr(t));for(let l of e.peers)i.set(l.nodeId,l);let a=[...i.values()].sort((l,c)=>l.label.localeCompare(c.label));if(a.length===0)return r.push("(no peers observed yet \u2014 run /c status from this chat)"),r.join(`
100
100
  `);for(let l of a){let c=l.nodeId===o,d=[(s?l.nodeId===s.nodeId:!1)?"your binding":null,c?"you":null].filter(Boolean).join(", "),m=d?` (${Ee(d)})`:"";r.push(`\u2022 <b>${Ee(l.label)}</b>${m}<br/> id <code>${Ee(l.nodeId)}</code> \xB7 role ${Ee(l.role)}<br/> seen ${Ee(l.lastSeenIso)}`)}return r.push(""),r.push(`Updated: ${Ee(e.updatedAt)}`),r.join(`
101
- `)}function Zl(e,t,n){return Xs(e,t,n??null).replace(/\*([^*]+)\*/g,"$1").replace(/`([^`]+)`/g,"$1")}function ec(e,t){let n=ht(),o=xt(n),r=Te(),s=e.clusterRole,i=km(e),a=t?nn(e,t):null,l=a?a.nodeId===o:!1,c=t?a?`your binding: ${a.nodeId} (${a.source}${l?", here":""})`:"your binding: (none)":"",u=["*This computer*","",`label: ${i}`,`node id: \`${o}\` (full id in ${Vl()})`,`host: ${Yl.hostname()}`,`clusterRole: ${s}`,c,`peers known: ${r.peers.length}`,`senderBindings (chat): ${Object.keys(r.senderBindings).length}`].filter(Boolean).join(`
102
- `),d=["<b>This computer</b>","",`label: ${Ee(i)}`,`node id: <code>${Ee(o)}</code> (full id in ${Ee(Vl())})`,`host: ${Ee(Yl.hostname())}`,`clusterRole: ${Ee(s)}`,t?Ee(c):"",`peers known: ${r.peers.length}`,`senderBindings (chat): ${Object.keys(r.senderBindings).length}`].filter(Boolean).join(`
103
- `);return{wa:u,tg:d}}function iS(e){let t=e.clusterRole,n=["*Computers* (per-sender bindings)","","Each allowlisted phone picks one machine to talk to. Other senders are not affected.","Selection persists; defaults can be set in config (clusterSenderBindings).","","Shorthand: /pcs \u2026 \xB7 /c \u2026","",`${Ne}/c use <label-or-id> \u2014 bind your messages to that machine`,`${Ne}/c here \u2014 bind your messages to THIS machine`,`${Ne}/c using \u2014 show your current binding`,`${Ne}/c unuse \u2014 clear your chat-set binding (config default still applies, if any)`,`${Ne}/c status \u2014 every online host replies with its own paragraph`,`${Ne}/c list \u2014 locally known roster (single responder)`,`${Ne}/c help \u2014 this help`,"",`clusterRole on this host: ${t}`].join(`
101
+ `)}function ec(e,t,n){return Xs(e,t,n??null).replace(/\*([^*]+)\*/g,"$1").replace(/`([^`]+)`/g,"$1")}function tc(e,t){let n=ht(),o=xt(n),r=Te(),s=e.clusterRole,i=xm(e),a=t?nn(e,t):null,l=a?a.nodeId===o:!1,c=t?a?`your binding: ${a.nodeId} (${a.source}${l?", here":""})`:"your binding: (none)":"",u=["*This computer*","",`label: ${i}`,`node id: \`${o}\` (full id in ${Ql()})`,`host: ${Vl.hostname()}`,`clusterRole: ${s}`,c,`peers known: ${r.peers.length}`,`senderBindings (chat): ${Object.keys(r.senderBindings).length}`].filter(Boolean).join(`
102
+ `),d=["<b>This computer</b>","",`label: ${Ee(i)}`,`node id: <code>${Ee(o)}</code> (full id in ${Ee(Ql())})`,`host: ${Ee(Vl.hostname())}`,`clusterRole: ${Ee(s)}`,t?Ee(c):"",`peers known: ${r.peers.length}`,`senderBindings (chat): ${Object.keys(r.senderBindings).length}`].filter(Boolean).join(`
103
+ `);return{wa:u,tg:d}}function uS(e){let t=e.clusterRole,n=["*Computers* (per-sender bindings)","","Each allowlisted phone picks one machine to talk to. Other senders are not affected.","Selection persists; defaults can be set in config (clusterSenderBindings).","","Shorthand: /pcs \u2026 \xB7 /c \u2026","",`${Ne}/c use <label-or-id> \u2014 bind your messages to that machine`,`${Ne}/c here \u2014 bind your messages to THIS machine`,`${Ne}/c using \u2014 show your current binding`,`${Ne}/c unuse \u2014 clear your chat-set binding (config default still applies, if any)`,`${Ne}/c status \u2014 every online host replies with its own paragraph`,`${Ne}/c list \u2014 locally known roster (single responder)`,`${Ne}/c help \u2014 this help`,"",`clusterRole on this host: ${t}`].join(`
104
104
  `),o=["<b>Computers</b> (per-sender bindings)","","Each allowlisted phone picks one machine to talk to. Other senders are not affected.","Selection persists; defaults can be set in config (clusterSenderBindings).","","Shorthand: /pcs \u2026 \xB7 /c \u2026","","\u2022 /c use &lt;label-or-id&gt; \u2014 bind your messages to that machine","\u2022 /c here \u2014 bind your messages to THIS machine","\u2022 /c using \u2014 show your current binding","\u2022 /c unuse \u2014 clear your chat-set binding","\u2022 /c status \u2014 every online host replies with its own paragraph","\u2022 /c list \u2014 locally known roster (single responder)","\u2022 /c help \u2014 this help","",`clusterRole on this host: ${Ee(t)}`].join(`
105
- `);return ye(n,o)}function aS(e,t){if(t.ok)return p("");if(t.reason==="ambiguous-label"){let n=(t.matches??[]).map(o=>`\`${o.nodeId}\` (${o.label})`).join(", ");return p(`Label "${e}" matches multiple machines: ${n}. Use the 8-character id with /c use <id>.`)}return p(`No machine matches "${e}". Send /c list to see ids and labels, or /c status to refresh the roster.`)}function Rm(e,t,n){let o=t.trim().split(/\s+/),r=o[0]?.toLowerCase()??"";if(!r||r==="help"){if(!e.clusterEnabled&&r!=="help"){let l=Te();return rt(l,e)?p("Cluster is disabled. Enable with: /config set clusterEnabled true (then /c use <label-or-id>). /c help for the new commands."):null}let a=Te();return rt(a,e)?iS(e):null}let s=xt(ht());if(r==="use"||r==="bind"){let a=o.slice(1).join(" ").trim();if(!n){let y=Te();return rt(y,e)?p("/c use is only available on chats with a known sender (allowlisted WhatsApp/Telegram)."):null}if(!a){let y=Te();return rt(y,e)?p("Usage: /c use <label-or-id>"):null}e.clusterEnabled||Os(!0);let c=Te().senderBindings[n]??null,{state:u,resolved:d}=Ql(n,a,"chat");if(!d.ok)return rt(u,e)?aS(a,d):null;let m=d.peer.nodeId===s,f=c?c.nodeId===s:!1;if(!m)return null;let h=[`*Bound to ${d.peer.label}*`,`id \`${d.peer.nodeId}\``,"","Your messages now route to this machine. Other senders are not affected.","",Xs(u,e,n)].join(`
106
- `),g=[`<b>Bound to ${Ee(d.peer.label)}</b>`,`id <code>${Ee(d.peer.nodeId)}</code>`,"","Your messages now route to this machine. Other senders are not affected.","",Kl(u,e,n)].join(`
107
- `);return ye(h,g)}if(r==="here"||r==="take"){if(!n){let d=Te();return rt(d,e)?p("/c here is only available on chats with a known sender (allowlisted WhatsApp/Telegram)."):null}e.clusterEnabled||Os(!0);let{state:a,resolved:l}=Ql(n,s,"chat");if(!l.ok)return rt(a,e)?p("Could not bind to this machine."):null;let c=["*Bound to this machine.*","","Your messages now route here. Other senders are not affected.","",Xs(a,e,n)].join(`
108
- `),u=["<b>Bound to this machine.</b>","","Your messages now route here. Other senders are not affected.","",Kl(a,e,n)].join(`
109
- `);return ye(c,u)}if(r==="using"){if(!n){let c=Te();return rt(c,e)?p("/c using is only available on chats with a known sender (allowlisted WhatsApp/Telegram)."):null}let a=nn(e,n);if(a){if(a.nodeId!==s)return null;let d=[...Te().peers,Lr(e)].find(h=>h.nodeId===a.nodeId)?.label??"(unknown label)",m=[`*Bound to ${d}*`,`id \`${a.nodeId}\` \xB7 source ${a.source}`,a.source==="chat"?`since ${a.sinceIso}`:"(from config)"].join(`
105
+ `);return we(n,o)}function dS(e,t){if(t.ok)return p("");if(t.reason==="ambiguous-label"){let n=(t.matches??[]).map(o=>`\`${o.nodeId}\` (${o.label})`).join(", ");return p(`Label "${e}" matches multiple machines: ${n}. Use the 8-character id with /c use <id>.`)}return p(`No machine matches "${e}". Send /c list to see ids and labels, or /c status to refresh the roster.`)}function Pm(e,t,n){let o=t.trim().split(/\s+/),r=o[0]?.toLowerCase()??"";if(!r||r==="help"){if(!e.clusterEnabled&&r!=="help"){let l=Te();return rt(l,e)?p("Cluster is disabled. Enable with: /config set clusterEnabled true (then /c use <label-or-id>). /c help for the new commands."):null}let a=Te();return rt(a,e)?uS(e):null}let s=xt(ht());if(r==="use"||r==="bind"){let a=o.slice(1).join(" ").trim();if(!n){let y=Te();return rt(y,e)?p("/c use is only available on chats with a known sender (allowlisted WhatsApp/Telegram)."):null}if(!a){let y=Te();return rt(y,e)?p("Usage: /c use <label-or-id>"):null}e.clusterEnabled||Os(!0);let c=Te().senderBindings[n]??null,{state:u,resolved:d}=Xl(n,a,"chat");if(!d.ok)return rt(u,e)?dS(a,d):null;let m=d.peer.nodeId===s,f=c?c.nodeId===s:!1;if(!m)return null;let h=[`*Bound to ${d.peer.label}*`,`id \`${d.peer.nodeId}\``,"","Your messages now route to this machine. Other senders are not affected.","",Xs(u,e,n)].join(`
106
+ `),g=[`<b>Bound to ${Ee(d.peer.label)}</b>`,`id <code>${Ee(d.peer.nodeId)}</code>`,"","Your messages now route to this machine. Other senders are not affected.","",Yl(u,e,n)].join(`
107
+ `);return we(h,g)}if(r==="here"||r==="take"){if(!n){let d=Te();return rt(d,e)?p("/c here is only available on chats with a known sender (allowlisted WhatsApp/Telegram)."):null}e.clusterEnabled||Os(!0);let{state:a,resolved:l}=Xl(n,s,"chat");if(!l.ok)return rt(a,e)?p("Could not bind to this machine."):null;let c=["*Bound to this machine.*","","Your messages now route here. Other senders are not affected.","",Xs(a,e,n)].join(`
108
+ `),u=["<b>Bound to this machine.</b>","","Your messages now route here. Other senders are not affected.","",Yl(a,e,n)].join(`
109
+ `);return we(c,u)}if(r==="using"){if(!n){let c=Te();return rt(c,e)?p("/c using is only available on chats with a known sender (allowlisted WhatsApp/Telegram)."):null}let a=nn(e,n);if(a){if(a.nodeId!==s)return null;let d=[...Te().peers,Lr(e)].find(h=>h.nodeId===a.nodeId)?.label??"(unknown label)",m=[`*Bound to ${d}*`,`id \`${a.nodeId}\` \xB7 source ${a.source}`,a.source==="chat"?`since ${a.sinceIso}`:"(from config)"].join(`
110
110
  `),f=[`<b>Bound to ${Ee(d)}</b>`,`id <code>${Ee(a.nodeId)}</code> \xB7 source ${Ee(a.source)}`,a.source==="chat"?`since ${Ee(a.sinceIso)}`:"(from config)"].join(`
111
- `);return ye(m,f)}let l=Te();return rt(l,e)?p("You are not bound to any machine. Send /c use <label-or-id> to bind, or set a default in config.json (clusterSenderBindings)."):null}if(r==="unuse"||r==="unbind"||r==="release"){if(!n){let m=Te();return rt(m,e)?p("/c unuse is only available on chats with a known sender (allowlisted WhatsApp/Telegram)."):null}let l=Te().senderBindings[n]??null,{state:c}=rS(n);if(l){if(l.nodeId!==s)return null}else if(!rt(c,e))return null;let u=nn(e,n),d=u?`
111
+ `);return we(m,f)}let l=Te();return rt(l,e)?p("You are not bound to any machine. Send /c use <label-or-id> to bind, or set a default in config.json (clusterSenderBindings)."):null}if(r==="unuse"||r==="unbind"||r==="release"){if(!n){let m=Te();return rt(m,e)?p("/c unuse is only available on chats with a known sender (allowlisted WhatsApp/Telegram)."):null}let l=Te().senderBindings[n]??null,{state:c}=lS(n);if(l){if(l.nodeId!==s)return null}else if(!rt(c,e))return null;let u=nn(e,n),d=u?`
112
112
  Config default still applies: \`${u.nodeId}\`.`:`
113
- No config default is set; nobody will answer until you /c use <label-or-id>.`;return p(`Cleared your chat binding.${d}`)}if(r==="step-down"||r==="stepdown"){let a=Te();return rt(a,e)?p("/c step-down is no longer used. The cluster is per-sender: send /c unuse to clear your binding."):null}if(r==="status"){let a=ec(e,n);return ye(a.wa,a.tg)}if(r==="list"){let a=Te(),l=n?nn(e,n):null;if(l){if(l.nodeId!==s)return null}else if(!rt(a,e))return null;return ye(Xs(a,e,n??null),Kl(a,e,n??null))}let i=Te();return rt(i,e)?p(`Unknown /c subcommand "${r}". Try /c help`):null}function Tm(e,t){return Os(!0),Ql(e,t,"chat")}dt();be();function lS(e){return`wa:${e}`}function tc(e){return`tg:${e}`}function Em(e){return{peerKey:lS(e.fromJid),text:e.text,waMessageId:e.messageId,mediaSavedPath:e.mediaSavedPath,mediaError:e.mediaError}}j();function Zs(e){let t=e.nodePath,n=e.scriptPath,o=e.omnishHome,r=["Linux (systemd --user), copy-paste on the host:","","mkdir -p ~/.config/systemd/user","# create ~/.config/systemd/user/omnish.service with ExecStart using:",`# ${t} ${n} run`,`# and Environment=OMNISH_HOME=${o}`,"# The generated unit uses Restart=on-failure and RestartSec=5 (tunable).","","systemctl --user daemon-reload","systemctl --user enable --now omnish.service","",'Optional (run without interactive login): loginctl enable-linger "$USER"',"","Full guide: docs/guides/background-and-boot.md \xB7 https://omnish.dev"].join(`
113
+ No config default is set; nobody will answer until you /c use <label-or-id>.`;return p(`Cleared your chat binding.${d}`)}if(r==="step-down"||r==="stepdown"){let a=Te();return rt(a,e)?p("/c step-down is no longer used. The cluster is per-sender: send /c unuse to clear your binding."):null}if(r==="status"){let a=tc(e,n);return we(a.wa,a.tg)}if(r==="list"){let a=Te(),l=n?nn(e,n):null;if(l){if(l.nodeId!==s)return null}else if(!rt(a,e))return null;return we(Xs(a,e,n??null),Yl(a,e,n??null))}let i=Te();return rt(i,e)?p(`Unknown /c subcommand "${r}". Try /c help`):null}function $m(e,t){return Os(!0),Xl(e,t,"chat")}dt();ye();function pS(e){return`wa:${e}`}function nc(e){return`tg:${e}`}function Mm(e){return{peerKey:pS(e.fromJid),text:e.text,waMessageId:e.messageId,mediaSavedPath:e.mediaSavedPath,mediaError:e.mediaError}}j();function Zs(e){let t=e.nodePath,n=e.scriptPath,o=e.omnishHome,r=["Linux (systemd --user), copy-paste on the host:","","mkdir -p ~/.config/systemd/user","# create ~/.config/systemd/user/omnish.service with ExecStart using:",`# ${t} ${n} run`,`# and Environment=OMNISH_HOME=${o}`,"# The generated unit uses Restart=on-failure and RestartSec=5 (tunable).","","systemctl --user daemon-reload","systemctl --user enable --now omnish.service","",'Optional (run without interactive login): loginctl enable-linger "$USER"',"","Full guide: docs/guides/background-and-boot.md \xB7 https://omnish.dev"].join(`
114
114
  `),s=["macOS (LaunchAgent), copy-paste on the host:","","# Use contrib/dev.omnish.gateway.plist from the repo with paths filled as:",`# Node: ${t}`,`# Script: ${n}`,`# OMNISH_HOME: ${o}`,"# Generated plist uses KeepAlive for crash restart.","","cp \u2026/dev.omnish.gateway.plist ~/Library/LaunchAgents/","launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/dev.omnish.gateway.plist","launchctl kickstart -k gui/$(id -u)/dev.omnish.gateway","","https://omnish.dev"].join(`
115
115
  `),i=["Windows: Task Scheduler \u2014 Action = Start a program:","",`Program: ${t}`,`Arguments: "${n}" run`,"","Set user env OMNISH_HOME if needed. Optional XML: contrib/omnish-windows-task.xml","","https://omnish.dev"].join(`
116
116
  `);return process.platform==="linux"?r:process.platform==="darwin"?s:process.platform==="win32"?i:[r,"","---","",s].join(`
@@ -118,23 +118,23 @@ No config default is set; nobody will answer until you /c use <label-or-id>.`;re
118
118
  `),o=["macOS:",""," omnish pull install"," omnish pull install --whisper","","Or: brew install yt-dlp ffmpeg python",` python3 -m venv ${t} && ${t}/bin/pip install -U openai-whisper`,"",`Bundled path: ${e}`].join(`
119
119
  `),r=["Windows:",""," omnish pull install"," omnish pull install --whisper","","Or: winget install yt-dlp \xB7 winget install Gyan.FFmpeg"," py -3 -m venv %USERPROFILE%\\.omnish\\venvs\\whisper"," %USERPROFILE%\\.omnish\\venvs\\whisper\\Scripts\\pip install -U openai-whisper","",`Bundled path: ${e}`].join(`
120
120
  `);return process.platform==="linux"?n:process.platform==="darwin"?o:process.platform==="win32"?r:[n,"","---","",o,"","---","",r].join(`
121
- `)}ue();import Sv from"node:path";j();import cS from"node:net";import uS from"node:fs";function dS(){try{let e=uS.readFileSync(bo,"utf8"),t=JSON.parse(e);return typeof t.host!="string"||typeof t.port!="number"||typeof t.token!="string"||!Number.isFinite(t.port)?null:{host:t.host,port:t.port,token:t.token}}catch{return null}}async function Wt(e){let t=dS();if(!t)return"No gateway control endpoint \u2014 is `omnish run` active? (control metadata missing.)";let n={...e,token:t.token},o=`${JSON.stringify(n)}
122
- `;return new Promise(r=>{let s=!1,i="";function a(c){s||(s=!0,r(c))}let l=cS.connect({host:t.host,port:t.port},()=>{l.write(o)});l.setTimeout(6e5),l.on("data",c=>{i+=c.toString("utf8");let u=i.indexOf(`
123
- `);if(u>=0){let d=i.slice(0,u).trim();try{let m=JSON.parse(d);m.ok?a(null):a(m.error||"Unknown error from gateway control.")}catch{a("Invalid response from gateway control.")}l.destroy()}}),l.on("error",c=>{a(`Control connection failed: ${String(c)}`)}),l.on("timeout",()=>{l.destroy(),a("Gateway control timed out.")}),l.on("close",()=>{!s&&i.trim()===""&&a("Gateway closed the control connection without a response.")})})}function Pm(e,t,n){return`Step ${e+1}/${t}: ${n}\u2026`}function Cn(){return{async stepStart(){},async stepFail(){}}}async function $m(e,t){let n=e.peerKey?.trim();if(n){if(e.sendToPeer){try{await e.sendToPeer(n,t)}catch{}return}Wt({op:"sendPeerText",peerKey:n,text:t}).catch(()=>{})}}function ti(e){return!e.enabled||!e.peerKey?.trim()?Cn():{async stepStart(t,n,o){await $m(e,Pm(t,n,o.label))},async stepFail(t){await $m(e,`Step failed: ${String(t)}`)}}}function Mm(){return process.env.OMNISH_PEER_KEY?.trim()||null}import oi from"node:fs";import _r from"node:path";j();import ni from"node:fs";import st from"node:path";var pS=160;function Am(e){let t=st.basename(e.replace(/\\/g,"/")),n=st.extname(t);return{stem:n?t.slice(0,-n.length):t,ext:n.toLowerCase().slice(0,20)}}function Nr(e){let{stem:t,ext:n}=Am(e),o=t.normalize("NFKD").replace(new RegExp("\\p{M}","gu"),"").replace(/\s+/g,"_").replace(/[^A-Za-z0-9._-]+/g,"_").replace(/_+/g,"_").replace(/^[_.]+|[_.]+$/g,"").slice(0,pS);return o||(o="download"),`${o}${n}`}function Qn(e,t,n){D(e);let o=Nr(t),r=n?st.resolve(n):null,s=st.join(e,o);if(!ni.existsSync(s)||st.resolve(s)===r)return s;let{stem:i,ext:a}=Am(o);for(let l=1;l<1e4;l+=1){let c=`${i}-${l}${a}`;if(s=st.join(e,c),!ni.existsSync(s)||st.resolve(s)===r)return s}return st.join(e,`${i}-${Date.now()}${a}`)}function nc(e,t){let n=st.resolve(e);if(!ni.existsSync(n))return n;let o=st.dirname(n),r=st.basename(n),s=st.extname(n).toLowerCase(),i=t?`${Nr(t)}${s}`:Nr(r);if(i===r)return n;let a=Qn(o,i,n);return st.resolve(a)===n?n:(ni.renameSync(n,a),st.resolve(a))}function Xn(e,t,n){let o=[],r=new Set,s=n&&t.length===1?n:void 0;for(let i of t){if(!i)continue;let a=nc(i,s);r.has(a)||(r.add(a),o.push(a))}return o}import{spawn as mS}from"node:child_process";function Oo(e,t,n){return new Promise((o,r)=>{let s=mS(e,t,{windowsHide:!0}),i="",a="";s.stdout?.on("data",c=>{i+=String(c)}),s.stderr?.on("data",c=>{a+=String(c)});let l=setTimeout(()=>{s.kill("SIGTERM"),r(new Error(`yt-dlp timed out after ${n}ms`))},n);s.on("error",c=>{clearTimeout(l),r(c)}),s.on("close",c=>{clearTimeout(l),o({code:c,stdout:i,stderr:a})})})}function Im(e){return _r.join(e,"%(title).200B.%(ext)s")}function Om(e,t){let n=[],o=r=>{let s;try{s=oi.readdirSync(r,{withFileTypes:!0})}catch{return}for(let i of s){let a=_r.join(r,i.name);if(i.isDirectory())o(a);else if(i.isFile())try{oi.statSync(a).mtimeMs>=t-2e3&&n.push(a)}catch{}}};return o(e),n.sort()}async function fS(e,t){let n=await Oo(e,["--no-download","--print","title",t],6e4);if(n.code===0)return n.stdout.trim().split(`
124
- `)[0]?.trim()||void 0}async function on(e){let{cfg:t,tools:n,url:o,mode:r,outputDir:s}=e,i=n.ytDlp;if(!i)throw new Error("yt-dlp not found. Run: omnish pull install");oi.mkdirSync(s,{recursive:!0});let a=Date.now(),l=Im(s),c=Math.min(9e5,Math.max(6e4,t.syncTimeoutMs*3)),u=await fS(i,o),d=[],m=[],f=[];t.mediaMaxBytes>0&&f.push("--max-filesize",String(Math.floor(t.mediaMaxBytes)));let h=async b=>{let x=await Oo(i,b,c);if(m.push(x.stderr.trim()||x.stdout.trim()),x.code!==0)throw new Error(`yt-dlp failed (exit ${x.code}):
125
- ${(x.stderr||x.stdout).slice(-2e3)}`)},g=r==="all"?["video","audio","subs"]:[r];for(let b of g){let x=Date.now();if(b==="video"){if(!n.ffmpeg)throw new Error("ffmpeg required for video. Run: omnish pull install");await h(["-f","bv*+ba/b","--merge-output-format","mp4","--restrict-filenames","--ffmpeg-location",_r.dirname(n.ffmpeg),...f,"-o",l,o])}else if(b==="audio"){let C=n.ffmpeg?["--ffmpeg-location",_r.dirname(n.ffmpeg)]:[];await h(["-x","--audio-format","m4a","--restrict-filenames",...C,...f,"-o",l,o])}else b==="subs"&&await h(["--skip-download","--write-subs","--write-auto-subs","--sub-langs","en.*,en","--restrict-filenames","-o",l,o]);d.push(...Om(s,x))}let y=[...new Set(d)],k=Xn(s,y,u);return{title:u,outputDir:s,files:k.length>0?k:y,log:m.join(`
121
+ `)}ue();import Rv from"node:path";j();import mS from"node:net";import fS from"node:fs";function hS(){try{let e=fS.readFileSync(bo,"utf8"),t=JSON.parse(e);return typeof t.host!="string"||typeof t.port!="number"||typeof t.token!="string"||!Number.isFinite(t.port)?null:{host:t.host,port:t.port,token:t.token}}catch{return null}}async function Wt(e){let t=hS();if(!t)return"No gateway control endpoint \u2014 is `omnish run` active? (control metadata missing.)";let n={...e,token:t.token},o=`${JSON.stringify(n)}
122
+ `;return new Promise(r=>{let s=!1,i="";function a(c){s||(s=!0,r(c))}let l=mS.connect({host:t.host,port:t.port},()=>{l.write(o)});l.setTimeout(6e5),l.on("data",c=>{i+=c.toString("utf8");let u=i.indexOf(`
123
+ `);if(u>=0){let d=i.slice(0,u).trim();try{let m=JSON.parse(d);m.ok?a(null):a(m.error||"Unknown error from gateway control.")}catch{a("Invalid response from gateway control.")}l.destroy()}}),l.on("error",c=>{a(`Control connection failed: ${String(c)}`)}),l.on("timeout",()=>{l.destroy(),a("Gateway control timed out.")}),l.on("close",()=>{!s&&i.trim()===""&&a("Gateway closed the control connection without a response.")})})}function Am(e,t,n){return`Step ${e+1}/${t}: ${n}\u2026`}function Cn(){return{async stepStart(){},async stepFail(){}}}async function Im(e,t){let n=e.peerKey?.trim();if(n){if(e.sendToPeer){try{await e.sendToPeer(n,t)}catch{}return}Wt({op:"sendPeerText",peerKey:n,text:t}).catch(()=>{})}}function ti(e){return!e.enabled||!e.peerKey?.trim()?Cn():{async stepStart(t,n,o){await Im(e,Am(t,n,o.label))},async stepFail(t){await Im(e,`Step failed: ${String(t)}`)}}}function Om(){return process.env.OMNISH_PEER_KEY?.trim()||null}import oi from"node:fs";import _r from"node:path";j();import ni from"node:fs";import st from"node:path";var gS=160;function Lm(e){let t=st.basename(e.replace(/\\/g,"/")),n=st.extname(t);return{stem:n?t.slice(0,-n.length):t,ext:n.toLowerCase().slice(0,20)}}function Nr(e){let{stem:t,ext:n}=Lm(e),o=t.normalize("NFKD").replace(new RegExp("\\p{M}","gu"),"").replace(/\s+/g,"_").replace(/[^A-Za-z0-9._-]+/g,"_").replace(/_+/g,"_").replace(/^[_.]+|[_.]+$/g,"").slice(0,gS);return o||(o="download"),`${o}${n}`}function Qn(e,t,n){D(e);let o=Nr(t),r=n?st.resolve(n):null,s=st.join(e,o);if(!ni.existsSync(s)||st.resolve(s)===r)return s;let{stem:i,ext:a}=Lm(o);for(let l=1;l<1e4;l+=1){let c=`${i}-${l}${a}`;if(s=st.join(e,c),!ni.existsSync(s)||st.resolve(s)===r)return s}return st.join(e,`${i}-${Date.now()}${a}`)}function oc(e,t){let n=st.resolve(e);if(!ni.existsSync(n))return n;let o=st.dirname(n),r=st.basename(n),s=st.extname(n).toLowerCase(),i=t?`${Nr(t)}${s}`:Nr(r);if(i===r)return n;let a=Qn(o,i,n);return st.resolve(a)===n?n:(ni.renameSync(n,a),st.resolve(a))}function Xn(e,t,n){let o=[],r=new Set,s=n&&t.length===1?n:void 0;for(let i of t){if(!i)continue;let a=oc(i,s);r.has(a)||(r.add(a),o.push(a))}return o}import{spawn as yS}from"node:child_process";function Oo(e,t,n){return new Promise((o,r)=>{let s=yS(e,t,{windowsHide:!0}),i="",a="";s.stdout?.on("data",c=>{i+=String(c)}),s.stderr?.on("data",c=>{a+=String(c)});let l=setTimeout(()=>{s.kill("SIGTERM"),r(new Error(`yt-dlp timed out after ${n}ms`))},n);s.on("error",c=>{clearTimeout(l),r(c)}),s.on("close",c=>{clearTimeout(l),o({code:c,stdout:i,stderr:a})})})}function Nm(e){return _r.join(e,"%(title).200B.%(ext)s")}function _m(e,t){let n=[],o=r=>{let s;try{s=oi.readdirSync(r,{withFileTypes:!0})}catch{return}for(let i of s){let a=_r.join(r,i.name);if(i.isDirectory())o(a);else if(i.isFile())try{oi.statSync(a).mtimeMs>=t-2e3&&n.push(a)}catch{}}};return o(e),n.sort()}async function wS(e,t){let n=await Oo(e,["--no-download","--print","title",t],6e4);if(n.code===0)return n.stdout.trim().split(`
124
+ `)[0]?.trim()||void 0}async function on(e){let{cfg:t,tools:n,url:o,mode:r,outputDir:s}=e,i=n.ytDlp;if(!i)throw new Error("yt-dlp not found. Run: omnish pull install");oi.mkdirSync(s,{recursive:!0});let a=Date.now(),l=Nm(s),c=Math.min(9e5,Math.max(6e4,t.syncTimeoutMs*3)),u=await wS(i,o),d=[],m=[],f=[];t.mediaMaxBytes>0&&f.push("--max-filesize",String(Math.floor(t.mediaMaxBytes)));let h=async k=>{let x=await Oo(i,k,c);if(m.push(x.stderr.trim()||x.stdout.trim()),x.code!==0)throw new Error(`yt-dlp failed (exit ${x.code}):
125
+ ${(x.stderr||x.stdout).slice(-2e3)}`)},g=r==="all"?["video","audio","subs"]:[r];for(let k of g){let x=Date.now();if(k==="video"){if(!n.ffmpeg)throw new Error("ffmpeg required for video. Run: omnish pull install");await h(["-f","bv*+ba/b","--merge-output-format","mp4","--restrict-filenames","--ffmpeg-location",_r.dirname(n.ffmpeg),...f,"-o",l,o])}else if(k==="audio"){let C=n.ffmpeg?["--ffmpeg-location",_r.dirname(n.ffmpeg)]:[];await h(["-x","--audio-format","m4a","--restrict-filenames",...C,...f,"-o",l,o])}else k==="subs"&&await h(["--skip-download","--write-subs","--write-auto-subs","--sub-langs","en.*,en","--restrict-filenames","-o",l,o]);d.push(..._m(s,x))}let y=[...new Set(d)],b=Xn(s,y,u);return{title:u,outputDir:s,files:b.length>0?b:y,log:m.join(`
126
126
  ---
127
- `)}}async function Lm(e){let{tools:t,url:n,outputDir:o,timeoutMs:r}=e,s=t.ytDlp;if(!s)throw new Error("yt-dlp not found.");oi.mkdirSync(o,{recursive:!0});let i=Im(o),a=Date.now(),l=t.ffmpeg?["--ffmpeg-location",_r.dirname(t.ffmpeg)]:[],c=await Oo(s,["-f","ba/b","-x","--audio-format","m4a","--restrict-filenames",...l,"-o",i,n],r);if(c.code!==0)throw new Error(`yt-dlp audio failed: ${(c.stderr||c.stdout).slice(-1500)}`);let u=Om(o,a).filter(d=>/\.(m4a|mp3|opus|webm|wav)$/i.test(d));if(u.length===0)throw new Error("No audio file produced.");return Xn(o,u)[0]??u[0]}j();ri();import{spawnSync as ic}from"node:child_process";import me from"node:fs";import SS from"node:os";import Qe from"node:path";var vS="https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp",xS="https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe";function Um(){return process.platform==="win32"?"yt-dlp.exe":"yt-dlp"}function Bm(){return process.platform==="win32"?"ffmpeg.exe":"ffmpeg"}function rc(){return process.platform==="win32"?"whisper.exe":"whisper"}function No(e){return Qe.join(ko,e)}function sc(){return process.platform==="win32"?Qe.join(kt,"Scripts","whisper.exe"):Qe.join(kt,"bin","whisper")}function to(e){try{return!me.existsSync(e)||!me.statSync(e).isFile()?!1:(process.platform==="win32"||me.accessSync(e,me.constants.X_OK),!0)}catch{return!1}}function eo(e){let t=process.platform==="win32"?"where":"which",n=ic(t,[e],{encoding:"utf8",timeout:8e3,windowsHide:!0});if(n.status!==0||!n.stdout?.trim())return null;let o=n.stdout.trim().split(/\r?\n/)[0]?.trim();return o&&to(o)?o:null}function Wm(e,t,n){let o=e.trim();if(o&&to(o))return o;let r=No(t);if(to(r))return r;for(let s of n){let i=eo(s);if(i)return i}return null}function lt(e){let t=Wm(e.pullYtDlpPath,Um(),["yt-dlp","yt-dlp.exe"]),n=Wm(e.pullFfmpegPath,Bm(),["ffmpeg","ffmpeg.exe"]),o=e.pullWhisperPath.trim(),r=null;o&&to(o)?r=o:to(No(rc()))?r=No(rc()):to(sc())?r=sc():r=eo(process.platform==="win32"?"whisper.exe":"whisper");let s=process.platform==="win32"?eo("python")??eo("py"):eo("python3")??eo("python");return{ytDlp:t,ffmpeg:n,whisper:r,python:s}}function ii(e){let t=lt(e),n=[{name:"yt-dlp",ok:!!t.ytDlp,detail:t.ytDlp??"missing \u2014 run: omnish pull install"},{name:"ffmpeg",ok:!!t.ffmpeg,detail:t.ffmpeg??"missing \u2014 run: omnish pull install (needed for video merge / audio extract)"},{name:"python",ok:!!t.python,detail:t.python??"missing \u2014 required for Whisper (omnish pull install --whisper)"},{name:"whisper",ok:!!t.whisper,detail:t.whisper??"missing \u2014 optional: omnish pull install --whisper"},{name:"transformers.js",ok:Tn(),detail:Tn()?`${Zn()} (cache: ${Sr})`:"missing \u2014 optional: omnish pull install --transcribe-node"},{name:"mediaSendFiles",ok:e.mediaSendFiles,detail:e.mediaSendFiles?"on (files sent to chat)":"off \u2014 paths only; /config set mediaSendFiles true"},{name:"data dir",ok:!0,detail:W}],o=n.map(r=>`${r.ok?"\u2713":"\u2717"} ${r.name}: ${r.detail}`).join(`
128
- `);return{lines:n,text:o}}async function si(e,t){D(Qe.dirname(t));let n=await fetch(e,{redirect:"follow"});if(!n.ok)throw new Error(`Download failed (${n.status}): ${e}`);let o=Buffer.from(await n.arrayBuffer());me.writeFileSync(t,o),process.platform!=="win32"&&me.chmodSync(t,493)}function Lo(e,t,n={}){if(ic(e,t,{stdio:"inherit",cwd:n.cwd,timeout:6e5,windowsHide:!0}).status!==0)throw new Error(`Command failed: ${e} ${t.join(" ")}`)}async function CS(){let e=No(Um()),t=process.platform==="win32"?xS:vS;return await si(t,e),e}async function RS(){let e=No(Bm());D(ko);let t=Qe.join(W,"tmp","pull-install");if(D(t),process.platform==="win32"){let l="https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip",c=Qe.join(t,"ffmpeg.zip");await si(l,c),Lo("powershell",["-NoProfile","-Command",`Expand-Archive -Force -Path '${c.replace(/'/g,"''")}' -DestinationPath '${t.replace(/'/g,"''")}'`],{cwd:t});let d=me.readdirSync(t,{withFileTypes:!0}).find(f=>f.isDirectory()&&f.name.startsWith("ffmpeg"))?.name;if(!d)throw new Error("Could not find ffmpeg folder in archive.");let m=Qe.join(t,d,"bin","ffmpeg.exe");if(!me.existsSync(m))throw new Error("ffmpeg.exe not found in archive.");return me.copyFileSync(m,e),e}if(process.platform==="darwin"){let l="https://evermeet.cx/ffmpeg/getrelease/ffmpeg/zip",c=Qe.join(t,"ffmpeg.zip");await si(l,c),Lo("unzip",["-o",c,"-d",t],{cwd:t});let u=Qe.join(t,"ffmpeg");if(!me.existsSync(u))throw new Error("ffmpeg binary not found after unzip.");return me.copyFileSync(u,e),me.chmodSync(e,493),e}let n=SS.arch();if(n!=="x64"&&n!=="amd64")throw new Error(`Automatic ffmpeg install is not supported on linux/${n}. Install ffmpeg via your package manager and ensure it is on PATH.`);let o="https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-amd64-static.tar.xz",r=Qe.join(t,"ffmpeg.tar.xz");await si(o,r),Lo("tar",["-xf",r,"-C",t],{cwd:t});let i=me.readdirSync(t).filter(l=>l.startsWith("ffmpeg"))[0];if(!i)throw new Error("Could not find ffmpeg directory in tarball.");let a=Qe.join(t,i,"ffmpeg");if(!me.existsSync(a))throw new Error("ffmpeg binary not found in tarball.");return me.copyFileSync(a,e),me.chmodSync(e,493),e}function TS(){let e=process.platform==="win32"?[{cmd:"py",args:["-3"]},{cmd:"python",args:[]},{cmd:"python3",args:[]}]:[{cmd:"python3",args:[]},{cmd:"python",args:[]}];for(let t of e)if(ic(t.cmd,[...t.args,"--version"],{encoding:"utf8",timeout:15e3,windowsHide:!0}).status===0)return t;throw new Error("Python 3 not found. Install Python 3.10+ then run: omnish pull install --whisper")}async function ES(){D(Qe.dirname(kt));let e=TS();if(me.existsSync(kt))try{me.rmSync(kt,{recursive:!0,force:!0})}catch{}Lo(e.cmd,[...e.args,"-m","venv",kt]);let t=process.platform==="win32"?Qe.join(kt,"Scripts","pip.exe"):Qe.join(kt,"bin","pip");Lo(t,["install","-U","pip","openai-whisper"]);let n=sc();if(!to(n))throw new Error("Whisper install finished but whisper executable was not found.");let o=No(rc());try{me.existsSync(o)&&me.unlinkSync(o),me.symlinkSync(n,o)}catch{me.copyFileSync(n,o),process.platform!=="win32"&&me.chmodSync(o,493)}return n}function PS(){let e=eo(process.platform==="win32"?"npm.cmd":"npm");if(e)return e;throw new Error("npm not found. Install Node.js/npm then run: omnish pull install --transcribe-node")}async function $S(){D(So),D(Sr);let e=Qe.join(So,"package.json");me.existsSync(e)||me.writeFileSync(e,JSON.stringify({name:"omnish-transcribe",private:!0,type:"module",dependencies:{"@xenova/transformers":"^2.17.2"}},null,2),"utf8");let t=PS();if(Lo(t,["install","--omit=dev"],{cwd:So}),!Tn())throw new Error("Transformers.js install finished but @xenova/transformers was not found.");return Zn()}async function ai(e){let t=[],o=2+((e.whisper?1:0)+(e.transcribeNode?1:0)),r=0;e.progress&&await e.progress.stepStart(r++,o,"Installing yt-dlp");let s=await CS();t.push(`yt-dlp \u2192 ${s}`);let i;e.progress&&await e.progress.stepStart(r++,o,"Installing ffmpeg");try{i=await RS(),t.push(`ffmpeg \u2192 ${i}`)}catch(c){t.push(`ffmpeg: ${String(c)} (install manually; video merge needs it)`)}let a;e.whisper&&(e.progress&&await e.progress.stepStart(r++,o,"Installing whisper"),a=await ES(),t.push(`whisper \u2192 ${a}`));let l;return e.transcribeNode&&(e.progress&&await e.progress.stepStart(r++,o,"Installing Transformers.js"),l=await $S(),t.push(`transformers.js \u2192 ${l}`)),{ytDlp:s,ffmpeg:i,whisper:a,transcribeNode:l,messages:t}}var MS=/^https?:\/\/\S+$/i,AS=/[.,;:!?)}\]"']+$/;function ac(e){let t=e.trim();if(!t||(t=t.replace(/^["']+|["']+$/g,""),t=t.replace(AS,""),!MS.test(t)))return null;try{return qe(t),t}catch{return null}}function li(e){return ac(e)!==null}function qe(e){let t;try{t=new URL(e.trim())}catch{throw new Error("Invalid URL.")}if(t.protocol!=="http:"&&t.protocol!=="https:")throw new Error("Only http:// and https:// URLs are supported.");let n=t.hostname.toLowerCase();if(n==="localhost"||n.endsWith(".localhost")||n==="127.0.0.1"||n==="::1"||n==="0.0.0.0"||n.startsWith("127.")||n.startsWith("10.")||n.startsWith("192.168.")||/^172\.(1[6-9]|2\d|3[01])\./.test(n)||n.startsWith("169.254.")||n==="[::1]")throw new Error("Private or local URLs are not allowed.");return t}import ui from"node:fs";import Vm from"node:path";import qm from"node:fs";function Fr(){let e=process.env.PLATFORM_REPLY_FILE_MAX_BYTES;if(e===void 0||e==="")return 0;let t=Number(e);return t===0?0:Number.isFinite(t)&&t>0?Math.floor(t):0}function jm(){let e=Number(process.env.PLATFORM_INBOUND_MEDIA_MAX_BYTES);return Number.isFinite(e)&&e>0?Math.floor(e):52*1024*1024}var e0=jm();function Hm(){let e=jm(),t=Fr(),n=Math.max(e,t>0?t:e);return Math.ceil(n*1.4)+512*1024}var Jm=50;function Gm(e){try{return JSON.parse(e)}catch{return null}}var cc="OMNISH_ATTACHED_GATEWAY";function zm(){return process.env[cc]==="1"}function oo(e){return zm()?dc(e):e.fileSendMaxBytes}function uc(e,t){return t?dc(e):oo(e)}function no(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KiB`:e<1024*1024*1024?`${(e/(1024*1024)).toFixed(1)} MiB`:`${(e/(1024*1024*1024)).toFixed(2)} GiB`}function Km(e){if(zm()){let t=dc(e),n=Fr();return t<=0?"outbound size cap":e.fileSendMaxBytes>0&&n>0&&e.fileSendMaxBytes<n?`fileSendMaxBytes (${no(e.fileSendMaxBytes)}) and attached platform hop (${no(t)})`:n>0?`attached platform hop (max ${no(t)} per file)`:`fileSendMaxBytes (max ${no(t)} per file)`}return e.fileSendMaxBytes>0?`fileSendMaxBytes (max ${no(e.fileSendMaxBytes)} per file)`:"outbound size cap"}function dc(e){let t=Fr();return t<=0?e.fileSendMaxBytes>0?e.fileSendMaxBytes:0:e.fileSendMaxBytes<=0?t:Math.min(e.fileSendMaxBytes,t)}function lc(e){let t=qm.statSync(e.absPath),n=Fr();if(n>0&&t.size>n)throw new Error(`File too large for attached mode (max ${n} bytes).`);let o=qm.readFileSync(e.absPath).toString("base64");return{name:e.displayName,mimetype:e.mimetype,category:e.category,dataBase64:o,...e.caption?{caption:e.caption}:{}}}function ci(e,t,n,o){if(t.kind==="texts")return{body:t.bodies.map(s=>pe(s,o).text).filter(s=>s.trim()).join(`
127
+ `)}}async function Fm(e){let{tools:t,url:n,outputDir:o,timeoutMs:r}=e,s=t.ytDlp;if(!s)throw new Error("yt-dlp not found.");oi.mkdirSync(o,{recursive:!0});let i=Nm(o),a=Date.now(),l=t.ffmpeg?["--ffmpeg-location",_r.dirname(t.ffmpeg)]:[],c=await Oo(s,["-f","ba/b","-x","--audio-format","m4a","--restrict-filenames",...l,"-o",i,n],r);if(c.code!==0)throw new Error(`yt-dlp audio failed: ${(c.stderr||c.stdout).slice(-1500)}`);let u=_m(o,a).filter(d=>/\.(m4a|mp3|opus|webm|wav)$/i.test(d));if(u.length===0)throw new Error("No audio file produced.");return Xn(o,u)[0]??u[0]}j();ri();import{spawnSync as ac}from"node:child_process";import me from"node:fs";import RS from"node:os";import Qe from"node:path";var TS="https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp",ES="https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe";function Hm(){return process.platform==="win32"?"yt-dlp.exe":"yt-dlp"}function Jm(){return process.platform==="win32"?"ffmpeg.exe":"ffmpeg"}function sc(){return process.platform==="win32"?"whisper.exe":"whisper"}function No(e){return Qe.join(ko,e)}function ic(){return process.platform==="win32"?Qe.join(kt,"Scripts","whisper.exe"):Qe.join(kt,"bin","whisper")}function to(e){try{return!me.existsSync(e)||!me.statSync(e).isFile()?!1:(process.platform==="win32"||me.accessSync(e,me.constants.X_OK),!0)}catch{return!1}}function eo(e){let t=process.platform==="win32"?"where":"which",n=ac(t,[e],{encoding:"utf8",timeout:8e3,windowsHide:!0});if(n.status!==0||!n.stdout?.trim())return null;let o=n.stdout.trim().split(/\r?\n/)[0]?.trim();return o&&to(o)?o:null}function jm(e,t,n){let o=e.trim();if(o&&to(o))return o;let r=No(t);if(to(r))return r;for(let s of n){let i=eo(s);if(i)return i}return null}function lt(e){let t=jm(e.pullYtDlpPath,Hm(),["yt-dlp","yt-dlp.exe"]),n=jm(e.pullFfmpegPath,Jm(),["ffmpeg","ffmpeg.exe"]),o=e.pullWhisperPath.trim(),r=null;o&&to(o)?r=o:to(No(sc()))?r=No(sc()):to(ic())?r=ic():r=eo(process.platform==="win32"?"whisper.exe":"whisper");let s=process.platform==="win32"?eo("python")??eo("py"):eo("python3")??eo("python");return{ytDlp:t,ffmpeg:n,whisper:r,python:s}}function ii(e){let t=lt(e),n=[{name:"yt-dlp",ok:!!t.ytDlp,detail:t.ytDlp??"missing \u2014 run: omnish pull install"},{name:"ffmpeg",ok:!!t.ffmpeg,detail:t.ffmpeg??"missing \u2014 run: omnish pull install (needed for video merge / audio extract)"},{name:"python",ok:!!t.python,detail:t.python??"missing \u2014 required for Whisper (omnish pull install --whisper)"},{name:"whisper",ok:!!t.whisper,detail:t.whisper??"missing \u2014 optional: omnish pull install --whisper"},{name:"transformers.js",ok:Tn(),detail:Tn()?`${Zn()} (cache: ${Sr})`:"missing \u2014 optional: omnish pull install --transcribe-node"},{name:"mediaSendFiles",ok:e.mediaSendFiles,detail:e.mediaSendFiles?"on (files sent to chat)":"off \u2014 paths only; /config set mediaSendFiles true"},{name:"data dir",ok:!0,detail:W}],o=n.map(r=>`${r.ok?"\u2713":"\u2717"} ${r.name}: ${r.detail}`).join(`
128
+ `);return{lines:n,text:o}}async function si(e,t){D(Qe.dirname(t));let n=await fetch(e,{redirect:"follow"});if(!n.ok)throw new Error(`Download failed (${n.status}): ${e}`);let o=Buffer.from(await n.arrayBuffer());me.writeFileSync(t,o),process.platform!=="win32"&&me.chmodSync(t,493)}function Lo(e,t,n={}){if(ac(e,t,{stdio:"inherit",cwd:n.cwd,timeout:6e5,windowsHide:!0}).status!==0)throw new Error(`Command failed: ${e} ${t.join(" ")}`)}async function PS(){let e=No(Hm()),t=process.platform==="win32"?ES:TS;return await si(t,e),e}async function $S(){let e=No(Jm());D(ko);let t=Qe.join(W,"tmp","pull-install");if(D(t),process.platform==="win32"){let l="https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip",c=Qe.join(t,"ffmpeg.zip");await si(l,c),Lo("powershell",["-NoProfile","-Command",`Expand-Archive -Force -Path '${c.replace(/'/g,"''")}' -DestinationPath '${t.replace(/'/g,"''")}'`],{cwd:t});let d=me.readdirSync(t,{withFileTypes:!0}).find(f=>f.isDirectory()&&f.name.startsWith("ffmpeg"))?.name;if(!d)throw new Error("Could not find ffmpeg folder in archive.");let m=Qe.join(t,d,"bin","ffmpeg.exe");if(!me.existsSync(m))throw new Error("ffmpeg.exe not found in archive.");return me.copyFileSync(m,e),e}if(process.platform==="darwin"){let l="https://evermeet.cx/ffmpeg/getrelease/ffmpeg/zip",c=Qe.join(t,"ffmpeg.zip");await si(l,c),Lo("unzip",["-o",c,"-d",t],{cwd:t});let u=Qe.join(t,"ffmpeg");if(!me.existsSync(u))throw new Error("ffmpeg binary not found after unzip.");return me.copyFileSync(u,e),me.chmodSync(e,493),e}let n=RS.arch();if(n!=="x64"&&n!=="amd64")throw new Error(`Automatic ffmpeg install is not supported on linux/${n}. Install ffmpeg via your package manager and ensure it is on PATH.`);let o="https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-amd64-static.tar.xz",r=Qe.join(t,"ffmpeg.tar.xz");await si(o,r),Lo("tar",["-xf",r,"-C",t],{cwd:t});let i=me.readdirSync(t).filter(l=>l.startsWith("ffmpeg"))[0];if(!i)throw new Error("Could not find ffmpeg directory in tarball.");let a=Qe.join(t,i,"ffmpeg");if(!me.existsSync(a))throw new Error("ffmpeg binary not found in tarball.");return me.copyFileSync(a,e),me.chmodSync(e,493),e}function MS(){let e=process.platform==="win32"?[{cmd:"py",args:["-3"]},{cmd:"python",args:[]},{cmd:"python3",args:[]}]:[{cmd:"python3",args:[]},{cmd:"python",args:[]}];for(let t of e)if(ac(t.cmd,[...t.args,"--version"],{encoding:"utf8",timeout:15e3,windowsHide:!0}).status===0)return t;throw new Error("Python 3 not found. Install Python 3.10+ then run: omnish pull install --whisper")}async function AS(){D(Qe.dirname(kt));let e=MS();if(me.existsSync(kt))try{me.rmSync(kt,{recursive:!0,force:!0})}catch{}Lo(e.cmd,[...e.args,"-m","venv",kt]);let t=process.platform==="win32"?Qe.join(kt,"Scripts","pip.exe"):Qe.join(kt,"bin","pip");Lo(t,["install","-U","pip","openai-whisper"]);let n=ic();if(!to(n))throw new Error("Whisper install finished but whisper executable was not found.");let o=No(sc());try{me.existsSync(o)&&me.unlinkSync(o),me.symlinkSync(n,o)}catch{me.copyFileSync(n,o),process.platform!=="win32"&&me.chmodSync(o,493)}return n}function IS(){let e=eo(process.platform==="win32"?"npm.cmd":"npm");if(e)return e;throw new Error("npm not found. Install Node.js/npm then run: omnish pull install --transcribe-node")}async function OS(){D(So),D(Sr);let e=Qe.join(So,"package.json");me.existsSync(e)||me.writeFileSync(e,JSON.stringify({name:"omnish-transcribe",private:!0,type:"module",dependencies:{"@xenova/transformers":"^2.17.2"}},null,2),"utf8");let t=IS();if(Lo(t,["install","--omit=dev"],{cwd:So}),!Tn())throw new Error("Transformers.js install finished but @xenova/transformers was not found.");return Zn()}async function ai(e){let t=[],o=2+((e.whisper?1:0)+(e.transcribeNode?1:0)),r=0;e.progress&&await e.progress.stepStart(r++,o,"Installing yt-dlp");let s=await PS();t.push(`yt-dlp \u2192 ${s}`);let i;e.progress&&await e.progress.stepStart(r++,o,"Installing ffmpeg");try{i=await $S(),t.push(`ffmpeg \u2192 ${i}`)}catch(c){t.push(`ffmpeg: ${String(c)} (install manually; video merge needs it)`)}let a;e.whisper&&(e.progress&&await e.progress.stepStart(r++,o,"Installing whisper"),a=await AS(),t.push(`whisper \u2192 ${a}`));let l;return e.transcribeNode&&(e.progress&&await e.progress.stepStart(r++,o,"Installing Transformers.js"),l=await OS(),t.push(`transformers.js \u2192 ${l}`)),{ytDlp:s,ffmpeg:i,whisper:a,transcribeNode:l,messages:t}}var LS=/^https?:\/\/\S+$/i,NS=/[.,;:!?)}\]"']+$/;function lc(e){let t=e.trim();if(!t||(t=t.replace(/^["']+|["']+$/g,""),t=t.replace(NS,""),!LS.test(t)))return null;try{return qe(t),t}catch{return null}}function li(e){return lc(e)!==null}function qe(e){let t;try{t=new URL(e.trim())}catch{throw new Error("Invalid URL.")}if(t.protocol!=="http:"&&t.protocol!=="https:")throw new Error("Only http:// and https:// URLs are supported.");let n=t.hostname.toLowerCase();if(n==="localhost"||n.endsWith(".localhost")||n==="127.0.0.1"||n==="::1"||n==="0.0.0.0"||n.startsWith("127.")||n.startsWith("10.")||n.startsWith("192.168.")||/^172\.(1[6-9]|2\d|3[01])\./.test(n)||n.startsWith("169.254.")||n==="[::1]")throw new Error("Private or local URLs are not allowed.");return t}import ui from"node:fs";import Zm from"node:path";import Ym from"node:fs";function Fr(){let e=process.env.PLATFORM_REPLY_FILE_MAX_BYTES;if(e===void 0||e==="")return 0;let t=Number(e);return t===0?0:Number.isFinite(t)&&t>0?Math.floor(t):0}function Gm(){let e=Number(process.env.PLATFORM_INBOUND_MEDIA_MAX_BYTES);return Number.isFinite(e)&&e>0?Math.floor(e):52*1024*1024}var l0=Gm();function qm(){let e=Gm(),t=Fr(),n=Math.max(e,t>0?t:e);return Math.ceil(n*1.4)+512*1024}var zm=50;function Km(e){try{return JSON.parse(e)}catch{return null}}var uc="OMNISH_ATTACHED_GATEWAY";function Vm(){return process.env[uc]==="1"}function oo(e){return Vm()?pc(e):e.fileSendMaxBytes}function dc(e,t){return t?pc(e):oo(e)}function no(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KiB`:e<1024*1024*1024?`${(e/(1024*1024)).toFixed(1)} MiB`:`${(e/(1024*1024*1024)).toFixed(2)} GiB`}function Qm(e){if(Vm()){let t=pc(e),n=Fr();return t<=0?"outbound size cap":e.fileSendMaxBytes>0&&n>0&&e.fileSendMaxBytes<n?`fileSendMaxBytes (${no(e.fileSendMaxBytes)}) and attached platform hop (${no(t)})`:n>0?`attached platform hop (max ${no(t)} per file)`:`fileSendMaxBytes (max ${no(t)} per file)`}return e.fileSendMaxBytes>0?`fileSendMaxBytes (max ${no(e.fileSendMaxBytes)} per file)`:"outbound size cap"}function pc(e){let t=Fr();return t<=0?e.fileSendMaxBytes>0?e.fileSendMaxBytes:0:e.fileSendMaxBytes<=0?t:Math.min(e.fileSendMaxBytes,t)}function cc(e){let t=Ym.statSync(e.absPath),n=Fr();if(n>0&&t.size>n)throw new Error(`File too large for attached mode (max ${n} bytes).`);let o=Ym.readFileSync(e.absPath).toString("base64");return{name:e.displayName,mimetype:e.mimetype,category:e.category,dataBase64:o,...e.caption?{caption:e.caption}:{}}}function ci(e,t,n,o){if(t.kind==="texts")return{body:t.bodies.map(s=>pe(s,o).text).filter(s=>s.trim()).join(`
129
129
 
130
- `),...n?{messageId:n}:{}};if(t.kind==="text")return{body:pe(t.body,o).text,...n?{messageId:n}:{}};if(t.kind==="bundle"){let r=(t.texts??[]).map(i=>pe(i,o).text).filter(i=>i.trim()),s=(t.files??[]).map(lc);return{body:r.length?r.join(`
130
+ `),...n?{messageId:n}:{}};if(t.kind==="text")return{body:pe(t.body,o).text,...n?{messageId:n}:{}};if(t.kind==="bundle"){let r=(t.texts??[]).map(i=>pe(i,o).text).filter(i=>i.trim()),s=(t.files??[]).map(cc);return{body:r.length?r.join(`
131
131
 
132
- `):void 0,...n?{messageId:n}:{},...s.length?{files:s}:{}}}return t.kind==="file"?{...n?{messageId:n}:{},files:[lc(t.spec)]}:{...n?{messageId:n}:{},files:t.specs.map(lc)}}var pc=new Set(["jpg","jpeg","png","gif","webp","bmp","tif","tiff","heic","avif","svg","mp4","mov","webm","mkv","avi","m4v","3gp","mp3","ogg","opus","wav","m4a","flac","aac","wma","pdf","zip","gz","tgz","tar","bz2","xz","7z","rar","doc","docx","xls","xlsx","ppt","pptx","txt","md","json","xml","csv","epub","apk","dmg","iso","deb","rpm","exe","msi","bin"]),IS=new Set(["html","htm","php","asp","aspx","jsp","cgi"]);function Dr(e){let t=e.pathname.split("/").pop()??"",n=t.lastIndexOf(".");if(n<=0||n===t.length-1)return null;let o=t.slice(n+1).toLowerCase();return!o||!/^[a-z0-9]{1,12}$/i.test(o)?null:o}function Ym(e){let t=typeof e=="string"?qe(e):e,n=Dr(t);return!n||IS.has(n)?!1:pc.has(n)}var OS="omnish/1.0 (+https://omnish.dev)";function LS(e){let t=[];if(e.mediaMaxBytes>0&&t.push(e.mediaMaxBytes),e.mediaSendFiles){let n=oo(e);n>0&&t.push(n)}return t.length===0?0:Math.min(...t)}function di(e){let t=Nr(e);return(!t||t==="download")&&!Vm.basename(e.replace(/\\/g,"/")).replace(/[^A-Za-z0-9._-]+/g,"").length?"":t}function NS(e){if(!e)return null;let t=/filename\*\s*=\s*[^']*'[^']*'([^;]+)/i.exec(e);if(t?.[1])try{return di(decodeURIComponent(t[1].trim()))}catch{return di(t[1].trim())}let n=/filename\s*=\s*("([^"]+)"|([^;\s]+))/i.exec(e),o=n?.[2]??n?.[3];return o?di(o.trim()):null}function _S(e,t){let n=NS(t);if(n)return n;let o=di(decodeURIComponent(e.pathname.split("/").pop()??""));if(o)return o;let r=Dr(e);return`download-${Date.now()}${r?`.${r}`:""}`}async function Qm(e){let t=qe(e.url),n=LS(e.cfg),o=await fetch(t.href,{redirect:"follow",headers:{"User-Agent":OS,Accept:"*/*"}});if(!o.ok)throw new Error(`Download failed (HTTP ${o.status}): ${e.url}`);let r=o.headers.get("content-length");if(n>0&&r){let d=Number(r);if(Number.isFinite(d)&&d>n)throw new Error(`File too large (${d} bytes; max ${n}).`)}let s=_S(t,o.headers.get("content-disposition"));ui.mkdirSync(e.outputDir,{recursive:!0});let i=Qn(e.outputDir,s),a=o.body;if(!a)throw new Error("Empty response body.");let l=ui.createWriteStream(i,{mode:384}),c=a.getReader(),u=0;try{for(;;){let{done:d,value:m}=await c.read();if(d)break;if(m?.length){if(u+=m.length,n>0&&u>n)throw await c.cancel().catch(()=>{}),new Error(`File too large (max ${n} bytes).`);await new Promise((f,h)=>{l.write(Buffer.from(m),g=>g?h(g):f())})}}}catch(d){l.close();try{ui.unlinkSync(i)}catch{}throw d}if(await new Promise((d,m)=>{l.end(f=>f?m(f):d())}),u===0){try{ui.unlinkSync(i)}catch{}throw new Error("Download returned no data.")}return Vm.resolve(i)}async function rn(e,t){let n=[],o=t.length;for(let r=0;r<t.length;r++){let s=t[r];await e.stepStart(r,o,{label:s.label});try{n.push(await s.run())}catch(i){throw e.stepFail&&await e.stepFail(i),i}}return n}import BS from"node:fs";import Xm from"node:fs";import Zm from"node:path";var FS=new Set(["jpg","jpeg","png","gif","webp","bmp","tif","tiff","heic","avif"]),DS=new Set(["mp4","mov","webm","mkv","avi","m4v","3gp"]),WS=new Set(["mp3","ogg","opus","wav","m4a","flac","aac","wma"]),pi={jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",bmp:"image/bmp",tif:"image/tiff",tiff:"image/tiff",heic:"image/heic",avif:"image/avif",mp4:"video/mp4",mov:"video/quicktime",webm:"video/webm",mkv:"video/x-matroska",avi:"video/x-msvideo",m4v:"video/x-m4v","3gp":"video/3gpp",mp3:"audio/mpeg",ogg:"audio/ogg",opus:"audio/opus",wav:"audio/wav",m4a:"audio/mp4",flac:"audio/flac",aac:"audio/aac",wma:"audio/x-ms-wma",pdf:"application/pdf",zip:"application/zip",gz:"application/gzip",txt:"text/plain",json:"application/json",xml:"application/xml",csv:"text/csv"};function US(e){let t=e.replace(/^\./,"").toLowerCase();return FS.has(t)?{category:"image",mimetype:pi[t]??"image/jpeg"}:DS.has(t)?{category:"video",mimetype:pi[t]??"video/mp4"}:WS.has(t)?{category:"audio",mimetype:pi[t]??"audio/mpeg"}:{category:"document",mimetype:pi[t]??"application/octet-stream"}}function Ct(e,t){let n;try{n=Xm.realpathSync(e)}catch{return{error:"File not found or unreadable."}}let o;try{o=Xm.lstatSync(n)}catch{return{error:"Cannot stat file."}}if(!o.isFile())return{error:"Not a regular file (directories and special files are not supported)."};if(t>0&&o.size>t)return{error:`File too large (max ${t} bytes).`};let r=Zm.basename(n),s=Zm.extname(n).slice(1),{category:i,mimetype:a}=US(s);return{absPath:n,category:i,mimetype:a,displayName:r}}function jS(e,t){let n=oo(e),o=[],r=[];for(let s of t){let i=Ct(s,n);if("error"in i){let a=0;try{a=BS.statSync(s).size}catch{}r.push({absPath:s,sizeBytes:a});continue}o.push(i)}return{specs:o,skipped:r}}function HS(e,t){let n=Km(e),o=t.map(r=>` ${r.absPath} (${no(r.sizeBytes)})`);return`${t.length} file(s) not sent (${n}):
132
+ `):void 0,...n?{messageId:n}:{},...s.length?{files:s}:{}}}return t.kind==="file"?{...n?{messageId:n}:{},files:[cc(t.spec)]}:{...n?{messageId:n}:{},files:t.specs.map(cc)}}var mc=new Set(["jpg","jpeg","png","gif","webp","bmp","tif","tiff","heic","avif","svg","mp4","mov","webm","mkv","avi","m4v","3gp","mp3","ogg","opus","wav","m4a","flac","aac","wma","pdf","zip","gz","tgz","tar","bz2","xz","7z","rar","doc","docx","xls","xlsx","ppt","pptx","txt","md","json","xml","csv","epub","apk","dmg","iso","deb","rpm","exe","msi","bin"]),_S=new Set(["html","htm","php","asp","aspx","jsp","cgi"]);function Dr(e){let t=e.pathname.split("/").pop()??"",n=t.lastIndexOf(".");if(n<=0||n===t.length-1)return null;let o=t.slice(n+1).toLowerCase();return!o||!/^[a-z0-9]{1,12}$/i.test(o)?null:o}function Xm(e){let t=typeof e=="string"?qe(e):e,n=Dr(t);return!n||_S.has(n)?!1:mc.has(n)}var FS="omnish/1.0 (+https://omnish.dev)";function DS(e){let t=[];if(e.mediaMaxBytes>0&&t.push(e.mediaMaxBytes),e.mediaSendFiles){let n=oo(e);n>0&&t.push(n)}return t.length===0?0:Math.min(...t)}function di(e){let t=Nr(e);return(!t||t==="download")&&!Zm.basename(e.replace(/\\/g,"/")).replace(/[^A-Za-z0-9._-]+/g,"").length?"":t}function WS(e){if(!e)return null;let t=/filename\*\s*=\s*[^']*'[^']*'([^;]+)/i.exec(e);if(t?.[1])try{return di(decodeURIComponent(t[1].trim()))}catch{return di(t[1].trim())}let n=/filename\s*=\s*("([^"]+)"|([^;\s]+))/i.exec(e),o=n?.[2]??n?.[3];return o?di(o.trim()):null}function US(e,t){let n=WS(t);if(n)return n;let o=di(decodeURIComponent(e.pathname.split("/").pop()??""));if(o)return o;let r=Dr(e);return`download-${Date.now()}${r?`.${r}`:""}`}async function ef(e){let t=qe(e.url),n=DS(e.cfg),o=await fetch(t.href,{redirect:"follow",headers:{"User-Agent":FS,Accept:"*/*"}});if(!o.ok)throw new Error(`Download failed (HTTP ${o.status}): ${e.url}`);let r=o.headers.get("content-length");if(n>0&&r){let d=Number(r);if(Number.isFinite(d)&&d>n)throw new Error(`File too large (${d} bytes; max ${n}).`)}let s=US(t,o.headers.get("content-disposition"));ui.mkdirSync(e.outputDir,{recursive:!0});let i=Qn(e.outputDir,s),a=o.body;if(!a)throw new Error("Empty response body.");let l=ui.createWriteStream(i,{mode:384}),c=a.getReader(),u=0;try{for(;;){let{done:d,value:m}=await c.read();if(d)break;if(m?.length){if(u+=m.length,n>0&&u>n)throw await c.cancel().catch(()=>{}),new Error(`File too large (max ${n} bytes).`);await new Promise((f,h)=>{l.write(Buffer.from(m),g=>g?h(g):f())})}}}catch(d){l.close();try{ui.unlinkSync(i)}catch{}throw d}if(await new Promise((d,m)=>{l.end(f=>f?m(f):d())}),u===0){try{ui.unlinkSync(i)}catch{}throw new Error("Download returned no data.")}return Zm.resolve(i)}async function rn(e,t){let n=[],o=t.length;for(let r=0;r<t.length;r++){let s=t[r];await e.stepStart(r,o,{label:s.label});try{n.push(await s.run())}catch(i){throw e.stepFail&&await e.stepFail(i),i}}return n}import GS from"node:fs";import tf from"node:fs";import nf from"node:path";var BS=new Set(["jpg","jpeg","png","gif","webp","bmp","tif","tiff","heic","avif"]),jS=new Set(["mp4","mov","webm","mkv","avi","m4v","3gp"]),HS=new Set(["mp3","ogg","opus","wav","m4a","flac","aac","wma"]),pi={jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",bmp:"image/bmp",tif:"image/tiff",tiff:"image/tiff",heic:"image/heic",avif:"image/avif",mp4:"video/mp4",mov:"video/quicktime",webm:"video/webm",mkv:"video/x-matroska",avi:"video/x-msvideo",m4v:"video/x-m4v","3gp":"video/3gpp",mp3:"audio/mpeg",ogg:"audio/ogg",opus:"audio/opus",wav:"audio/wav",m4a:"audio/mp4",flac:"audio/flac",aac:"audio/aac",wma:"audio/x-ms-wma",pdf:"application/pdf",zip:"application/zip",gz:"application/gzip",txt:"text/plain",json:"application/json",xml:"application/xml",csv:"text/csv"};function JS(e){let t=e.replace(/^\./,"").toLowerCase();return BS.has(t)?{category:"image",mimetype:pi[t]??"image/jpeg"}:jS.has(t)?{category:"video",mimetype:pi[t]??"video/mp4"}:HS.has(t)?{category:"audio",mimetype:pi[t]??"audio/mpeg"}:{category:"document",mimetype:pi[t]??"application/octet-stream"}}function Ct(e,t){let n;try{n=tf.realpathSync(e)}catch{return{error:"File not found or unreadable."}}let o;try{o=tf.lstatSync(n)}catch{return{error:"Cannot stat file."}}if(!o.isFile())return{error:"Not a regular file (directories and special files are not supported)."};if(t>0&&o.size>t)return{error:`File too large (max ${t} bytes).`};let r=nf.basename(n),s=nf.extname(n).slice(1),{category:i,mimetype:a}=JS(s);return{absPath:n,category:i,mimetype:a,displayName:r}}function qS(e,t){let n=oo(e),o=[],r=[];for(let s of t){let i=Ct(s,n);if("error"in i){let a=0;try{a=GS.statSync(s).size}catch{}r.push({absPath:s,sizeBytes:a});continue}o.push(i)}return{specs:o,skipped:r}}function zS(e,t){let n=Qm(e),o=t.map(r=>` ${r.absPath} (${no(r.sizeBytes)})`);return`${t.length} file(s) not sent (${n}):
133
133
  ${o.join(`
134
134
  `)}`}function En(e){let{cfg:t,files:n,textLines:o,skippedNote:r}=e,s=n.filter(c=>typeof c=="string"&&c.length>0),i=[...o];if(r&&i.push(r),!t.mediaSendFiles){let c=s.length?s.map(u=>` ${u}`):[" (no files)"];return{kind:"text",body:p([...i,"Files:",...c].filter(Boolean).join(`
135
- `))}}let{specs:a,skipped:l}=jS(t,s);return l.length>0&&i.push(HS(t,l)),a.length===0?{kind:"text",body:p(i.join(`
135
+ `))}}let{specs:a,skipped:l}=qS(t,s);return l.length>0&&i.push(zS(t,l)),a.length===0?{kind:"text",body:p(i.join(`
136
136
 
137
- `)||"(done)")}:{kind:"bundle",texts:i.length?i.map(c=>p(c)):void 0,files:a}}import ef from"node:fs";var JS="omnish/1.0 (+https://omnish.dev)",tf=2*1024*1024,nf=48e3;function GS(e){return e.replace(/<script[\s\S]*?<\/script>/gi,"").replace(/<style[\s\S]*?<\/style>/gi,"").replace(/<nav[\s\S]*?<\/nav>/gi,"").replace(/<footer[\s\S]*?<\/footer>/gi,"").replace(/<header[\s\S]*?<\/header>/gi,"")}function _o(e){return e.replace(/&nbsp;/gi," ").replace(/&amp;/gi,"&").replace(/&lt;/gi,"<").replace(/&gt;/gi,">").replace(/&quot;/gi,'"').replace(/&#39;/gi,"'")}function qS(e){let t=GS(e);t=t.replace(/<title[^>]*>([\s\S]*?)<\/title>/gi,(n,o)=>`# ${_o(o.trim())}
137
+ `)||"(done)")}:{kind:"bundle",texts:i.length?i.map(c=>p(c)):void 0,files:a}}import of from"node:fs";var KS="omnish/1.0 (+https://omnish.dev)",rf=2*1024*1024,sf=48e3;function YS(e){return e.replace(/<script[\s\S]*?<\/script>/gi,"").replace(/<style[\s\S]*?<\/style>/gi,"").replace(/<nav[\s\S]*?<\/nav>/gi,"").replace(/<footer[\s\S]*?<\/footer>/gi,"").replace(/<header[\s\S]*?<\/header>/gi,"")}function _o(e){return e.replace(/&nbsp;/gi," ").replace(/&amp;/gi,"&").replace(/&lt;/gi,"<").replace(/&gt;/gi,">").replace(/&quot;/gi,'"').replace(/&#39;/gi,"'")}function VS(e){let t=YS(e);t=t.replace(/<title[^>]*>([\s\S]*?)<\/title>/gi,(n,o)=>`# ${_o(o.trim())}
138
138
 
139
139
  `);for(let n=1;n<=6;n+=1){let o=new RegExp(`<h${n}[^>]*>([\\s\\S]*?)<\\/h${n}>`,"gi");t=t.replace(o,(r,s)=>`${"#".repeat(n)} ${_o(s.replace(/<[^>]+>/g,"").trim())}
140
140
 
@@ -144,46 +144,46 @@ ${o.join(`
144
144
  `),t=t.replace(/<br\s*\/?>/gi,`
145
145
  `),t=t.replace(/<[^>]+>/g,""),t=_o(t),t=t.replace(/\n{3,}/g,`
146
146
 
147
- `).trim(),t.length>nf&&(t=`${t.slice(0,nf)}
147
+ `).trim(),t.length>sf&&(t=`${t.slice(0,sf)}
148
148
 
149
- \u2026 (truncated)`),t}async function of(e){let t=qe(e.url),n=await fetch(t.href,{redirect:"follow",headers:{"User-Agent":JS,Accept:"text/html,application/xhtml+xml"}});if(!n.ok)throw new Error(`Page fetch failed (HTTP ${n.status}): ${e.url}`);let o=n.body?.getReader();if(!o)throw new Error("Empty response body.");let r=[],s=0;for(;;){let{done:c,value:u}=await o.read();if(c)break;if(u?.length){if(s+=u.length,s>tf)throw await o.cancel().catch(()=>{}),new Error(`Page too large (max ${tf} bytes).`);r.push(u)}}let i=Buffer.concat(r).toString("utf8"),a=qS(i);if(!a.trim())throw new Error("Could not extract readable content from page.");let l;if(a.length>12e3){let c=`${t.hostname.replace(/\./g,"-")}.md`;l=Qn(e.outputDir,c),ef.mkdirSync(e.outputDir,{recursive:!0}),ef.writeFileSync(l,a,{encoding:"utf8",mode:384})}return{markdown:a,savedPath:l}}function rf(e,t){if(e.length<=t)return[e];let n=[];for(let o=0;o<e.length;o+=t)n.push(e.slice(o,o+t));return n}import zS from"node:fs";import sf from"node:path";function Pn(e,t,n){let o=e.mediaOutputDir.trim(),r;return o?r=sf.isAbsolute(o)?o:sf.resolve(t,o):n&&js(n)==="sessionCwd"?r=t:r=Js(),zS.mkdirSync(r,{recursive:!0}),r}var KS=2e4,YS=new Set(["generic","genericembed","htm5","html5","html5media","mime"]);function VS(e){let t=e.trim().toLowerCase();return!t||YS.has(t)}async function af(e,t,n=KS){let r=lt(e).ytDlp;if(!r)return{supported:!1,reason:"no_tool"};try{let s=await Oo(r,["--simulate","--no-warnings","--no-playlist","--print","%(extractor_key)s",t],n);if(s.code!==0)return{supported:!1,reason:"unsupported",detail:(s.stderr||s.stdout).trim().slice(-500)};let i=s.stdout.trim().split(`
150
- `).pop()?.trim()??"";return i?VS(i)?{supported:!1,reason:"weak",detail:i}:{supported:!0,extractorKey:i}:{supported:!1,reason:"unsupported"}}catch(s){return{supported:!1,reason:"error",detail:String(s)}}}var QS=[/^application\/pdf\b/i,/^application\/zip\b/i,/^application\/x-zip/i,/^application\/octet-stream\b/i,/^application\/vnd\./i,/^application\/msword\b/i,/^application\/json\b/i,/^image\//i,/^audio\//i,/^video\/(?!text)/i],XS="omnish/1.0 (+https://omnish.dev)";function ZS(e){let t=e.pathname.toLowerCase();if(/\/pdf(?:\/|$)/i.test(t)||/\/download(?:\/|$)/i.test(t))return!0;let n=Dr(e);return!!(n&&pc.has(n))}function ev(e){if(!e)return!1;let t=e.split(";")[0]?.trim()??"";return QS.some(n=>n.test(t))}function tv(e){if(!e)return!1;let t=e.split(";")[0]?.trim().toLowerCase()??"";return t==="text/html"||t==="application/xhtml+xml"}async function nv(e,t){let n=qe(e),o=new AbortController,r=setTimeout(()=>o.abort(),t);try{return(await fetch(n.href,{method:"HEAD",redirect:"follow",headers:{"User-Agent":XS,Accept:"*/*"},signal:o.signal})).headers.get("content-type")}catch{return null}finally{clearTimeout(r)}}async function lf(e,t,n={}){if(n.force==="file")return"file";if(n.force==="video")return"video";let o=qe(t);if(Ym(o)||ZS(o))return"file";let r=n.allowHead!==!1,s=null;return r&&(s=await nv(t,5e3),ev(s))?"file":(await af(e,t)).supported?"video":tv(s)||s===null&&r?"html":"file"}async function cf(e){qe(e.url);let t=Pn(e.cfg,e.sessionCwd,e.peerKey),n=e.reporter??Cn(),o=await lf(e.cfg,e.url,e.classify??{});if(o==="file"){let[c]=await rn(n,[{label:"Downloading file",run:async()=>Qm({cfg:e.cfg,url:e.url,outputDir:t})}]),u=Xn(t,[c]),d=u[0]??c;return En({cfg:e.cfg,files:u,textLines:[`Saved: ${d}`,`Output: ${t}`]})}if(o==="html"){let[c]=await rn(n,[{label:"Fetching page",run:async()=>of({url:e.url,outputDir:t})}]),u=Math.min(e.cfg.appsMaxWaChars||3500,4e3),d=rf(c.markdown,u),m=[`Source: ${e.url}`];c.savedPath&&m.push(`Saved: ${c.savedPath}`);let f=d.map((h,g)=>p(g===0?`${m.join(`
149
+ \u2026 (truncated)`),t}async function af(e){let t=qe(e.url),n=await fetch(t.href,{redirect:"follow",headers:{"User-Agent":KS,Accept:"text/html,application/xhtml+xml"}});if(!n.ok)throw new Error(`Page fetch failed (HTTP ${n.status}): ${e.url}`);let o=n.body?.getReader();if(!o)throw new Error("Empty response body.");let r=[],s=0;for(;;){let{done:c,value:u}=await o.read();if(c)break;if(u?.length){if(s+=u.length,s>rf)throw await o.cancel().catch(()=>{}),new Error(`Page too large (max ${rf} bytes).`);r.push(u)}}let i=Buffer.concat(r).toString("utf8"),a=VS(i);if(!a.trim())throw new Error("Could not extract readable content from page.");let l;if(a.length>12e3){let c=`${t.hostname.replace(/\./g,"-")}.md`;l=Qn(e.outputDir,c),of.mkdirSync(e.outputDir,{recursive:!0}),of.writeFileSync(l,a,{encoding:"utf8",mode:384})}return{markdown:a,savedPath:l}}function lf(e,t){if(e.length<=t)return[e];let n=[];for(let o=0;o<e.length;o+=t)n.push(e.slice(o,o+t));return n}import QS from"node:fs";import cf from"node:path";function Pn(e,t,n){let o=e.mediaOutputDir.trim(),r;return o?r=cf.isAbsolute(o)?o:cf.resolve(t,o):n&&js(n)==="sessionCwd"?r=t:r=Js(),QS.mkdirSync(r,{recursive:!0}),r}var XS=2e4,ZS=new Set(["generic","genericembed","htm5","html5","html5media","mime"]);function ev(e){let t=e.trim().toLowerCase();return!t||ZS.has(t)}async function uf(e,t,n=XS){let r=lt(e).ytDlp;if(!r)return{supported:!1,reason:"no_tool"};try{let s=await Oo(r,["--simulate","--no-warnings","--no-playlist","--print","%(extractor_key)s",t],n);if(s.code!==0)return{supported:!1,reason:"unsupported",detail:(s.stderr||s.stdout).trim().slice(-500)};let i=s.stdout.trim().split(`
150
+ `).pop()?.trim()??"";return i?ev(i)?{supported:!1,reason:"weak",detail:i}:{supported:!0,extractorKey:i}:{supported:!1,reason:"unsupported"}}catch(s){return{supported:!1,reason:"error",detail:String(s)}}}var tv=[/^application\/pdf\b/i,/^application\/zip\b/i,/^application\/x-zip/i,/^application\/octet-stream\b/i,/^application\/vnd\./i,/^application\/msword\b/i,/^application\/json\b/i,/^image\//i,/^audio\//i,/^video\/(?!text)/i],nv="omnish/1.0 (+https://omnish.dev)";function ov(e){let t=e.pathname.toLowerCase();if(/\/pdf(?:\/|$)/i.test(t)||/\/download(?:\/|$)/i.test(t))return!0;let n=Dr(e);return!!(n&&mc.has(n))}function rv(e){if(!e)return!1;let t=e.split(";")[0]?.trim()??"";return tv.some(n=>n.test(t))}function sv(e){if(!e)return!1;let t=e.split(";")[0]?.trim().toLowerCase()??"";return t==="text/html"||t==="application/xhtml+xml"}async function iv(e,t){let n=qe(e),o=new AbortController,r=setTimeout(()=>o.abort(),t);try{return(await fetch(n.href,{method:"HEAD",redirect:"follow",headers:{"User-Agent":nv,Accept:"*/*"},signal:o.signal})).headers.get("content-type")}catch{return null}finally{clearTimeout(r)}}async function df(e,t,n={}){if(n.force==="file")return"file";if(n.force==="video")return"video";let o=qe(t);if(Xm(o)||ov(o))return"file";let r=n.allowHead!==!1,s=null;return r&&(s=await iv(t,5e3),rv(s))?"file":(await uf(e,t)).supported?"video":sv(s)||s===null&&r?"html":"file"}async function pf(e){qe(e.url);let t=Pn(e.cfg,e.sessionCwd,e.peerKey),n=e.reporter??Cn(),o=await df(e.cfg,e.url,e.classify??{});if(o==="file"){let[c]=await rn(n,[{label:"Downloading file",run:async()=>ef({cfg:e.cfg,url:e.url,outputDir:t})}]),u=Xn(t,[c]),d=u[0]??c;return En({cfg:e.cfg,files:u,textLines:[`Saved: ${d}`,`Output: ${t}`]})}if(o==="html"){let[c]=await rn(n,[{label:"Fetching page",run:async()=>af({url:e.url,outputDir:t})}]),u=Math.min(e.cfg.appsMaxWaChars||3500,4e3),d=lf(c.markdown,u),m=[`Source: ${e.url}`];c.savedPath&&m.push(`Saved: ${c.savedPath}`);let f=d.map((h,g)=>p(g===0?`${m.join(`
151
151
  `)}
152
152
 
153
- ${h}`:h));if(c.savedPath&&e.cfg.mediaSendFiles){let h=Xn(t,[c.savedPath]);return En({cfg:e.cfg,files:h,textLines:m})}return{kind:"bundle",texts:f}}let r=lt(e.cfg);if(!r.ytDlp)return{kind:"text",body:p("yt-dlp missing. Run: omnish pull install")};if(!r.ffmpeg)return{kind:"text",body:p("ffmpeg missing. Run: omnish pull install")};let[s]=await rn(n,[{label:"Downloading video",run:async()=>on({cfg:e.cfg,tools:r,url:e.url,mode:"video",outputDir:t})}]),i=s.files.filter(c=>/\.(mp4|mkv|webm|mov)$/i.test(c)),a=Xn(t,i.length?i:s.files,s.title),l=[s.title?`Title: ${s.title}`:"",`Saved under: ${t}`,...e.cfg.mediaSendFiles&&a.length?["Files:",...a.map(c=>` ${c}`)]:[]].filter(Boolean);return En({cfg:e.cfg,files:a,textLines:l})}import{spawnSync as ov}from"node:child_process";import rv from"node:fs";import Wr from"node:path";import uf from"node:fs";import df from"node:path";function mi(e,t){let n=e.trim();if(!n)throw new Error("URL or file path required.");if(/^https?:\/\//i.test(n))return qe(n),{kind:"url",url:n};let o=df.isAbsolute(n)?n:df.resolve(t,n);if(!uf.existsSync(o))throw new Error(`File not found: ${o}`);if(!uf.statSync(o).isFile())throw new Error(`Not a file: ${o}`);return{kind:"file",absPath:o}}function Fo(e){let t=e.trim();if(!t)throw new Error("Empty time value.");if(/^\d+(\.\d+)?$/.test(t))return Number.parseFloat(t);let n=t.split(":").map(o=>Number.parseFloat(o));if(n.some(o=>!Number.isFinite(o)))throw new Error(`Invalid time: ${e}`);if(n.length===2)return n[0]*60+n[1];if(n.length===3)return n[0]*3600+n[1]*60+n[2];throw new Error(`Invalid time: ${e}`)}function pf(e){let t=e.trim(),n={fromSec:null,toSec:null,durationSec:null,format:null,audioOnly:!1},o=!0;for(;o;){o=!1;let r=t.trim(),s=r.match(/^(?:--from|--start|-ss)\s+(\S+)(?:\s+([\s\S]+))?$/i);if(s){n.fromSec=Fo(s[1]),t=(s[2]??"").trim(),o=!0;continue}let i=r.match(/^(?:--to|--end)\s+(\S+)(?:\s+([\s\S]+))?$/i);if(i){n.toSec=Fo(i[1]),t=(i[2]??"").trim(),o=!0;continue}let a=r.match(/^(?:-t|--duration)\s+(\S+)(?:\s+([\s\S]+))?$/i);if(a){n.durationSec=Fo(a[1]),t=(a[2]??"").trim(),o=!0;continue}let l=r.match(/^(?:--format|-f)\s+(\S+)(?:\s+([\s\S]+))?$/i);if(l){n.format=l[1].replace(/^\./,"").toLowerCase(),t=(l[2]??"").trim(),o=!0;continue}if(/^--audio-only\b/i.test(r)){n.audioOnly=!0,t=r.replace(/^--audio-only\s*/i,"").trim(),o=!0;continue}let c=r.match(/^([\s\S]+?)\s+(?:--from|--start|-ss)\s+(\S+)\s*$/i);if(c){n.fromSec=Fo(c[2]),t=c[1].trim(),o=!0;continue}let u=r.match(/^([\s\S]+?)\s+(?:--to|--end)\s+(\S+)\s*$/i);if(u){n.toSec=Fo(u[2]),t=u[1].trim(),o=!0;continue}let d=r.match(/^([\s\S]+?)\s+(?:-t|--duration)\s+(\S+)\s*$/i);if(d){n.durationSec=Fo(d[2]),t=d[1].trim(),o=!0;continue}let m=r.match(/^([\s\S]+?)\s+(?:--format|-f)\s+(\S+)\s*$/i);if(m){n.format=m[2].replace(/^\./,"").toLowerCase(),t=m[1].trim(),o=!0;continue}let f=r.match(/^([\s\S]+?)\s+--audio-only\s*$/i);f&&(n.audioOnly=!0,t=f[1].trim(),o=!0)}if(!t)throw new Error("Missing URL or file path.");return{targetRaw:t,opts:n}}function sv(e,t){return t.format?t.format.replace(/^\./,""):t.audioOnly?"m4a":Wr.extname(e).replace(/^\./,"")||"mp4"}function iv(e,t,n){let o=Wr.extname(e).toLowerCase(),r=Wr.extname(t).toLowerCase(),s=["-y"];return n.fromSec!==null&&n.fromSec>0&&s.push("-ss",String(n.fromSec)),s.push("-i",e),n.durationSec!==null?s.push("-t",String(n.durationSec)):n.toSec!==null&&(n.fromSec!==null?s.push("-t",String(Math.max(.1,n.toSec-n.fromSec))):s.push("-to",String(n.toSec))),n.audioOnly||[".mp3",".m4a",".wav",".opus"].includes(r)?(s.push("-vn"),r===".mp3"?s.push("-acodec","libmp3lame"):r===".wav"&&s.push("-acodec","pcm_s16le")):o===r&&!n.audioOnly&&n.format===null&&s.push("-c","copy"),s.push(t),s}function av(e,t,n,o,r){let s=iv(t,n,o),i=ov(e,s,{encoding:"utf8",timeout:r,windowsHide:!0});if(i.status!==0)throw new Error(`ffmpeg failed:
154
- ${(i.stderr||i.stdout).slice(-2e3)}`);if(!rv.existsSync(n))throw new Error("ffmpeg did not produce output file.")}async function mf(e){let t=lt(e.cfg),n=t.ffmpeg;if(!n)return{kind:"text",body:p("ffmpeg missing. Run: omnish pull install")};let{targetRaw:o,opts:r}=pf(e.args),s=Pn(e.cfg,e.sessionCwd,e.peerKey),i=e.reporter??Cn(),a=mi(o,e.sessionCwd),l=Math.min(9e5,Math.max(6e4,e.cfg.syncTimeoutMs*3)),c;if(a.kind==="url"){if(!t.ytDlp)throw new Error("yt-dlp and ffmpeg required for URL input. Run: omnish pull install");let[h]=await rn(i,[{label:"Downloading",run:async()=>{let g=await on({cfg:e.cfg,tools:t,url:a.url,mode:"video",outputDir:s}),y=g.files.find(k=>/\.(mp4|mkv|webm|mov|m4a|mp3)$/i.test(k))??g.files[0];if(!y)throw new Error("Download produced no file.");return y}}]);c=h}else c=a.absPath;let u=sv(c,r),d=Wr.basename(c,Wr.extname(c)),m=Qn(s,`${d}-edit.${u}`),[f]=await rn(i,[{label:"Editing",run:async()=>(av(n,c,m,r,l),nc(m))}]);return En({cfg:e.cfg,files:[f],textLines:[`Edited: ${f}`]})}import wv from"node:fs";import{spawnSync as yf}from"node:child_process";import Ur from"node:fs";import Rt from"node:path";ri();function lv(e){let t=e.trim().toLowerCase()||"auto";return t==="cpu"?["cpu"]:t==="cuda"?["cuda"]:[void 0,"cpu"]}function cv(e,t,n,o){let r=[e,"--model",t,"--output_dir",n,"--output_format","all"];return o&&r.push("--device",o),r}function uv(e){return!!(/CUDA error:\s*out of memory/i.test(e)||/cuda.*out of memory/i.test(e)||/out of memory/i.test(e)&&/cuda/i.test(e))}function dv(e,t){let n=t?` (signal ${t})`:"";return`Process timed out after ${e}ms${n}`}function pv(e){let t=e.stderr.slice(-2e3),n=e.triedDevices.length>0?e.triedDevices.join(" \u2192 "):"(none)",o=['Try mediaWhisperDevice: "cpu" in config.json',"or a smaller mediaWhisperModel (tiny, base)."];e.timeoutMs&&o.push(`or increase mediaTranscribeTimeoutMs (currently ${e.timeoutMs} ms per attempt).`),o.push("For a Python-free fallback: omnish pull install --transcribe-node");let r=e.cpuFallbackAttempted?"CPU fallback after ffmpeg audio extract was attempted.":"";return[`whisper failed (devices tried: ${n}).`,r,...o,"",t].filter(Boolean).join(`
155
- `)}function wf(e,t){let n=(typeof e.stderr=="string"?e.stderr:"")||"",o=(typeof e.stdout=="string"?e.stdout:"")||"",r=e.error&&typeof e.error=="object"&&"code"in e.error?String(e.error.code):"",s=e.status===null&&r==="ETIMEDOUT";if(e.status===null){let i=dv(t,e.signal);n=n?`${n}
156
- ${i}`:i}return{status:e.status,stderr:n,stdout:o,signal:e.signal,timedOut:s||e.status===null}}function mv(e,t,n){let o=yf(e,t,{encoding:"utf8",timeout:n,windowsHide:!0});return wf(o,n)}function fv(e){return e??"default"}function ff(e,t){let n=Rt.basename(t,Rt.extname(t));return[".txt",".srt",".vtt",".json"].map(r=>Rt.join(e,n+r)).filter(r=>Ur.existsSync(r))}function hf(e,t,n){let o=Rt.basename(t,Rt.extname(t)),r=Rt.basename(n,Rt.extname(n));if(o===r)return ff(e,n);for(let s of[".txt",".srt",".vtt",".json"]){let i=Rt.join(e,o+s),a=Rt.join(e,r+s);if(Ur.existsSync(i))try{Ur.copyFileSync(i,a)}catch{}}return ff(e,n)}function hv(e,t,n,o,r=".m4a"){let s=Rt.join(n,`_whisper_audio${r||Rt.extname(t)||".m4a"}`),i=yf(e,["-y","-i",t,"-vn","-ar","16000","-ac","1",s],{encoding:"utf8",timeout:o,windowsHide:!0}),a=wf(i,o);return i.status===0&&Ur.existsSync(s)?s:(a.timedOut,null)}function gf(e){let t=e.runWhisper??mv,n=lv(e.deviceConfig),o=e.deviceConfig.trim().toLowerCase()||"auto",r=[],s="";for(let i=0;i<n.length;i++){let a=n[i];r.push(fv(a));let l=cv(e.inputPath,e.model,e.outputDir,a),c=t(e.whisper,l,e.timeoutMs);if(c.status===0)return{ok:!0,log:c.stderr||c.stdout||"",whisperInputPath:e.inputPath};if(s=c.stderr||c.stdout||"",!(i<n.length-1&&(o==="auto"||uv(s))))break}return{ok:!1,stderr:s,triedDevices:r}}function mc(e){return Math.min(9e5,Math.max(6e4,e.mediaTranscribeTimeoutMs))}function fi(e,t){let n=e.mediaTranscribeEngine.trim().toLowerCase()||"whisper",o=!!t.whisper,r=Tn();return n==="transformers"?r?null:"Transformers.js missing. Run: omnish pull install --transcribe-node":n==="whisper"?o?null:"whisper missing. Run: omnish pull install --whisper":!o&&!r?"No transcription backend. Run: omnish pull install --whisper and/or --transcribe-node":null}async function gv(e){let{cfg:t,tools:n,inputPath:o,outputDir:r,runWhisper:s}=e,i=n.whisper;if(!i)throw new Error("whisper not found. Run: omnish pull install --whisper");if(!Ur.existsSync(o))throw new Error(`File not found: ${o}`);let a=mc(t),l=t.mediaWhisperModel.trim()||"small",c=t.mediaWhisperDevice.trim()||"auto",u=[],d="",m=!1,f=gf({whisper:i,inputPath:o,model:l,outputDir:r,deviceConfig:c,timeoutMs:a,runWhisper:s});if(f.ok)return{files:hf(r,f.whisperInputPath,o),log:f.log};if(u=f.triedDevices,d=f.stderr,n.ffmpeg){let h=hv(n.ffmpeg,o,r,a);if(h){m=!0;let g=gf({whisper:i,inputPath:h,model:l,outputDir:r,deviceConfig:"cpu",timeoutMs:a,runWhisper:s});if(g.ok)return{files:hf(r,g.whisperInputPath,o),log:g.log};u=[...u,...g.triedDevices.map(y=>`extract+${y}`)],d=g.stderr}}throw new Error(pv({stderr:d,triedDevices:u,cpuFallbackAttempted:m,timeoutMs:a}))}function yv(e,t){let n=e.mediaTranscribeEngine.trim().toLowerCase()||"whisper";return n==="transformers"||n==="auto"&&t?!0:t&&e.mediaTranscribeFallback}async function fc(e){let{cfg:t,tools:n,inputPath:o,outputDir:r,runWhisper:s}=e,i=t.mediaTranscribeEngine.trim().toLowerCase()||"whisper",a=mc(t),l=await Promise.resolve().then(()=>(ri(),Dm));if(i==="transformers")return l.transcribeWithTransformers({cfg:t,tools:n,inputPath:o,outputDir:r,timeoutMs:a});try{return await gv({cfg:t,tools:n,inputPath:o,outputDir:r,runWhisper:s})}catch(c){if(!yv(t,!0))throw c;if(!l.isNodeTranscribeInstalled()){let u=" Transformers.js fallback unavailable (run: omnish pull install --transcribe-node).";throw new Error(String(c)+u)}try{let u=await l.transcribeWithTransformers({cfg:t,tools:n,inputPath:o,outputDir:r,timeoutMs:a});return{files:u.files,log:`${u.log} (whisper failed; used fallback)`}}catch(u){throw new Error(`${String(c)}
153
+ ${h}`:h));if(c.savedPath&&e.cfg.mediaSendFiles){let h=Xn(t,[c.savedPath]);return En({cfg:e.cfg,files:h,textLines:m})}return{kind:"bundle",texts:f}}let r=lt(e.cfg);if(!r.ytDlp)return{kind:"text",body:p("yt-dlp missing. Run: omnish pull install")};if(!r.ffmpeg)return{kind:"text",body:p("ffmpeg missing. Run: omnish pull install")};let[s]=await rn(n,[{label:"Downloading video",run:async()=>on({cfg:e.cfg,tools:r,url:e.url,mode:"video",outputDir:t})}]),i=s.files.filter(c=>/\.(mp4|mkv|webm|mov)$/i.test(c)),a=Xn(t,i.length?i:s.files,s.title),l=[s.title?`Title: ${s.title}`:"",`Saved under: ${t}`,...e.cfg.mediaSendFiles&&a.length?["Files:",...a.map(c=>` ${c}`)]:[]].filter(Boolean);return En({cfg:e.cfg,files:a,textLines:l})}import{spawnSync as av}from"node:child_process";import lv from"node:fs";import Wr from"node:path";import mf from"node:fs";import ff from"node:path";function mi(e,t){let n=e.trim();if(!n)throw new Error("URL or file path required.");if(/^https?:\/\//i.test(n))return qe(n),{kind:"url",url:n};let o=ff.isAbsolute(n)?n:ff.resolve(t,n);if(!mf.existsSync(o))throw new Error(`File not found: ${o}`);if(!mf.statSync(o).isFile())throw new Error(`Not a file: ${o}`);return{kind:"file",absPath:o}}function Fo(e){let t=e.trim();if(!t)throw new Error("Empty time value.");if(/^\d+(\.\d+)?$/.test(t))return Number.parseFloat(t);let n=t.split(":").map(o=>Number.parseFloat(o));if(n.some(o=>!Number.isFinite(o)))throw new Error(`Invalid time: ${e}`);if(n.length===2)return n[0]*60+n[1];if(n.length===3)return n[0]*3600+n[1]*60+n[2];throw new Error(`Invalid time: ${e}`)}function hf(e){let t=e.trim(),n={fromSec:null,toSec:null,durationSec:null,format:null,audioOnly:!1},o=!0;for(;o;){o=!1;let r=t.trim(),s=r.match(/^(?:--from|--start|-ss)\s+(\S+)(?:\s+([\s\S]+))?$/i);if(s){n.fromSec=Fo(s[1]),t=(s[2]??"").trim(),o=!0;continue}let i=r.match(/^(?:--to|--end)\s+(\S+)(?:\s+([\s\S]+))?$/i);if(i){n.toSec=Fo(i[1]),t=(i[2]??"").trim(),o=!0;continue}let a=r.match(/^(?:-t|--duration)\s+(\S+)(?:\s+([\s\S]+))?$/i);if(a){n.durationSec=Fo(a[1]),t=(a[2]??"").trim(),o=!0;continue}let l=r.match(/^(?:--format|-f)\s+(\S+)(?:\s+([\s\S]+))?$/i);if(l){n.format=l[1].replace(/^\./,"").toLowerCase(),t=(l[2]??"").trim(),o=!0;continue}if(/^--audio-only\b/i.test(r)){n.audioOnly=!0,t=r.replace(/^--audio-only\s*/i,"").trim(),o=!0;continue}let c=r.match(/^([\s\S]+?)\s+(?:--from|--start|-ss)\s+(\S+)\s*$/i);if(c){n.fromSec=Fo(c[2]),t=c[1].trim(),o=!0;continue}let u=r.match(/^([\s\S]+?)\s+(?:--to|--end)\s+(\S+)\s*$/i);if(u){n.toSec=Fo(u[2]),t=u[1].trim(),o=!0;continue}let d=r.match(/^([\s\S]+?)\s+(?:-t|--duration)\s+(\S+)\s*$/i);if(d){n.durationSec=Fo(d[2]),t=d[1].trim(),o=!0;continue}let m=r.match(/^([\s\S]+?)\s+(?:--format|-f)\s+(\S+)\s*$/i);if(m){n.format=m[2].replace(/^\./,"").toLowerCase(),t=m[1].trim(),o=!0;continue}let f=r.match(/^([\s\S]+?)\s+--audio-only\s*$/i);f&&(n.audioOnly=!0,t=f[1].trim(),o=!0)}if(!t)throw new Error("Missing URL or file path.");return{targetRaw:t,opts:n}}function cv(e,t){return t.format?t.format.replace(/^\./,""):t.audioOnly?"m4a":Wr.extname(e).replace(/^\./,"")||"mp4"}function uv(e,t,n){let o=Wr.extname(e).toLowerCase(),r=Wr.extname(t).toLowerCase(),s=["-y"];return n.fromSec!==null&&n.fromSec>0&&s.push("-ss",String(n.fromSec)),s.push("-i",e),n.durationSec!==null?s.push("-t",String(n.durationSec)):n.toSec!==null&&(n.fromSec!==null?s.push("-t",String(Math.max(.1,n.toSec-n.fromSec))):s.push("-to",String(n.toSec))),n.audioOnly||[".mp3",".m4a",".wav",".opus"].includes(r)?(s.push("-vn"),r===".mp3"?s.push("-acodec","libmp3lame"):r===".wav"&&s.push("-acodec","pcm_s16le")):o===r&&!n.audioOnly&&n.format===null&&s.push("-c","copy"),s.push(t),s}function dv(e,t,n,o,r){let s=uv(t,n,o),i=av(e,s,{encoding:"utf8",timeout:r,windowsHide:!0});if(i.status!==0)throw new Error(`ffmpeg failed:
154
+ ${(i.stderr||i.stdout).slice(-2e3)}`);if(!lv.existsSync(n))throw new Error("ffmpeg did not produce output file.")}async function gf(e){let t=lt(e.cfg),n=t.ffmpeg;if(!n)return{kind:"text",body:p("ffmpeg missing. Run: omnish pull install")};let{targetRaw:o,opts:r}=hf(e.args),s=Pn(e.cfg,e.sessionCwd,e.peerKey),i=e.reporter??Cn(),a=mi(o,e.sessionCwd),l=Math.min(9e5,Math.max(6e4,e.cfg.syncTimeoutMs*3)),c;if(a.kind==="url"){if(!t.ytDlp)throw new Error("yt-dlp and ffmpeg required for URL input. Run: omnish pull install");let[h]=await rn(i,[{label:"Downloading",run:async()=>{let g=await on({cfg:e.cfg,tools:t,url:a.url,mode:"video",outputDir:s}),y=g.files.find(b=>/\.(mp4|mkv|webm|mov|m4a|mp3)$/i.test(b))??g.files[0];if(!y)throw new Error("Download produced no file.");return y}}]);c=h}else c=a.absPath;let u=cv(c,r),d=Wr.basename(c,Wr.extname(c)),m=Qn(s,`${d}-edit.${u}`),[f]=await rn(i,[{label:"Editing",run:async()=>(dv(n,c,m,r,l),oc(m))}]);return En({cfg:e.cfg,files:[f],textLines:[`Edited: ${f}`]})}import vv from"node:fs";import{spawnSync as kf}from"node:child_process";import Ur from"node:fs";import Rt from"node:path";ri();function pv(e){let t=e.trim().toLowerCase()||"auto";return t==="cpu"?["cpu"]:t==="cuda"?["cuda"]:[void 0,"cpu"]}function mv(e,t,n,o){let r=[e,"--model",t,"--output_dir",n,"--output_format","all"];return o&&r.push("--device",o),r}function fv(e){return!!(/CUDA error:\s*out of memory/i.test(e)||/cuda.*out of memory/i.test(e)||/out of memory/i.test(e)&&/cuda/i.test(e))}function hv(e,t){let n=t?` (signal ${t})`:"";return`Process timed out after ${e}ms${n}`}function gv(e){let t=e.stderr.slice(-2e3),n=e.triedDevices.length>0?e.triedDevices.join(" \u2192 "):"(none)",o=['Try mediaWhisperDevice: "cpu" in config.json',"or a smaller mediaWhisperModel (tiny, base)."];e.timeoutMs&&o.push(`or increase mediaTranscribeTimeoutMs (currently ${e.timeoutMs} ms per attempt).`),o.push("For a Python-free fallback: omnish pull install --transcribe-node");let r=e.cpuFallbackAttempted?"CPU fallback after ffmpeg audio extract was attempted.":"";return[`whisper failed (devices tried: ${n}).`,r,...o,"",t].filter(Boolean).join(`
155
+ `)}function Sf(e,t){let n=(typeof e.stderr=="string"?e.stderr:"")||"",o=(typeof e.stdout=="string"?e.stdout:"")||"",r=e.error&&typeof e.error=="object"&&"code"in e.error?String(e.error.code):"",s=e.status===null&&r==="ETIMEDOUT";if(e.status===null){let i=hv(t,e.signal);n=n?`${n}
156
+ ${i}`:i}return{status:e.status,stderr:n,stdout:o,signal:e.signal,timedOut:s||e.status===null}}function yv(e,t,n){let o=kf(e,t,{encoding:"utf8",timeout:n,windowsHide:!0});return Sf(o,n)}function wv(e){return e??"default"}function yf(e,t){let n=Rt.basename(t,Rt.extname(t));return[".txt",".srt",".vtt",".json"].map(r=>Rt.join(e,n+r)).filter(r=>Ur.existsSync(r))}function wf(e,t,n){let o=Rt.basename(t,Rt.extname(t)),r=Rt.basename(n,Rt.extname(n));if(o===r)return yf(e,n);for(let s of[".txt",".srt",".vtt",".json"]){let i=Rt.join(e,o+s),a=Rt.join(e,r+s);if(Ur.existsSync(i))try{Ur.copyFileSync(i,a)}catch{}}return yf(e,n)}function bv(e,t,n,o,r=".m4a"){let s=Rt.join(n,`_whisper_audio${r||Rt.extname(t)||".m4a"}`),i=kf(e,["-y","-i",t,"-vn","-ar","16000","-ac","1",s],{encoding:"utf8",timeout:o,windowsHide:!0}),a=Sf(i,o);return i.status===0&&Ur.existsSync(s)?s:(a.timedOut,null)}function bf(e){let t=e.runWhisper??yv,n=pv(e.deviceConfig),o=e.deviceConfig.trim().toLowerCase()||"auto",r=[],s="";for(let i=0;i<n.length;i++){let a=n[i];r.push(wv(a));let l=mv(e.inputPath,e.model,e.outputDir,a),c=t(e.whisper,l,e.timeoutMs);if(c.status===0)return{ok:!0,log:c.stderr||c.stdout||"",whisperInputPath:e.inputPath};if(s=c.stderr||c.stdout||"",!(i<n.length-1&&(o==="auto"||fv(s))))break}return{ok:!1,stderr:s,triedDevices:r}}function fc(e){return Math.min(9e5,Math.max(6e4,e.mediaTranscribeTimeoutMs))}function fi(e,t){let n=e.mediaTranscribeEngine.trim().toLowerCase()||"whisper",o=!!t.whisper,r=Tn();return n==="transformers"?r?null:"Transformers.js missing. Run: omnish pull install --transcribe-node":n==="whisper"?o?null:"whisper missing. Run: omnish pull install --whisper":!o&&!r?"No transcription backend. Run: omnish pull install --whisper and/or --transcribe-node":null}async function kv(e){let{cfg:t,tools:n,inputPath:o,outputDir:r,runWhisper:s}=e,i=n.whisper;if(!i)throw new Error("whisper not found. Run: omnish pull install --whisper");if(!Ur.existsSync(o))throw new Error(`File not found: ${o}`);let a=fc(t),l=t.mediaWhisperModel.trim()||"small",c=t.mediaWhisperDevice.trim()||"auto",u=[],d="",m=!1,f=bf({whisper:i,inputPath:o,model:l,outputDir:r,deviceConfig:c,timeoutMs:a,runWhisper:s});if(f.ok)return{files:wf(r,f.whisperInputPath,o),log:f.log};if(u=f.triedDevices,d=f.stderr,n.ffmpeg){let h=bv(n.ffmpeg,o,r,a);if(h){m=!0;let g=bf({whisper:i,inputPath:h,model:l,outputDir:r,deviceConfig:"cpu",timeoutMs:a,runWhisper:s});if(g.ok)return{files:wf(r,g.whisperInputPath,o),log:g.log};u=[...u,...g.triedDevices.map(y=>`extract+${y}`)],d=g.stderr}}throw new Error(gv({stderr:d,triedDevices:u,cpuFallbackAttempted:m,timeoutMs:a}))}function Sv(e,t){let n=e.mediaTranscribeEngine.trim().toLowerCase()||"whisper";return n==="transformers"||n==="auto"&&t?!0:t&&e.mediaTranscribeFallback}async function hc(e){let{cfg:t,tools:n,inputPath:o,outputDir:r,runWhisper:s}=e,i=t.mediaTranscribeEngine.trim().toLowerCase()||"whisper",a=fc(t),l=await Promise.resolve().then(()=>(ri(),Bm));if(i==="transformers")return l.transcribeWithTransformers({cfg:t,tools:n,inputPath:o,outputDir:r,timeoutMs:a});try{return await kv({cfg:t,tools:n,inputPath:o,outputDir:r,runWhisper:s})}catch(c){if(!Sv(t,!0))throw c;if(!l.isNodeTranscribeInstalled()){let u=" Transformers.js fallback unavailable (run: omnish pull install --transcribe-node).";throw new Error(String(c)+u)}try{let u=await l.transcribeWithTransformers({cfg:t,tools:n,inputPath:o,outputDir:r,timeoutMs:a});return{files:u.files,log:`${u.log} (whisper failed; used fallback)`}}catch(u){throw new Error(`${String(c)}
157
157
 
158
- transformers fallback failed: ${String(u)}`)}}}async function hc(e){let{cfg:t,tools:n,url:o,outputDir:r}=e,s=mc(t),i=await Lm({tools:n,url:o,outputDir:r,timeoutMs:s});return fc({cfg:t,tools:n,inputPath:i,outputDir:r})}function bv(e){try{return wv.readFileSync(e,"utf8").trim()}catch{return""}}function kv(e,t){if(e.length<=t)return[e];let n=[];for(let o=0;o<e.length;o+=t)n.push(e.slice(o,o+t));return n}async function bf(e){let t=lt(e.cfg),n=fi(e.cfg,t);if(n)return{kind:"text",body:p(n)};let o=mi(e.targetRaw,e.sessionCwd),r=Pn(e.cfg,e.sessionCwd,e.peerKey),s=e.reporter??Cn(),i="",a=null,l=[];if(o.kind==="url"){if(!t.ytDlp)return{kind:"text",body:p("yt-dlp missing. Run: omnish pull install")};if(!t.ffmpeg)return{kind:"text",body:p("ffmpeg missing. Run: omnish pull install")};l.push({label:"Downloading video",run:async()=>{let k=await on({cfg:e.cfg,tools:t,url:o.url,mode:"video",outputDir:r});if(a=k.files.filter(x=>/\.(mp4|mkv|webm|mov)$/i.test(x))[0]??k.files[0]??null,!a)throw new Error("No video file downloaded.");return i=a,k}})}else i=o.absPath;l.push({label:"Transcribing",run:async()=>fc({cfg:e.cfg,tools:t,inputPath:i,outputDir:r})}),l.push({label:"Preparing results",run:async()=>{}});let c=await rn(s,l),u=c[c.length-2],d=u.files.find(k=>k.endsWith(".txt")),m=u.files.find(k=>k.endsWith(".srt")||k.endsWith(".vtt")),f=d?bv(d):"",h=Math.min(e.cfg.appsMaxWaChars,e.cfg.syncMaxBytes)||3500,g=f?kv(f,h):["(no transcript text produced)"],y=[];return m&&y.push(m),o.kind==="url"&&a&&y.push(a),En({cfg:e.cfg,files:y,textLines:g})}async function Do(e,t){let n=await Wt({op:"sendPeerText",peerKey:e,text:t});if(n)throw new Error(n)}async function gc(e,t,n){let o=await Wt({op:"sendPeerMedia",peerKey:e,absPath:t,...n?{caption:n}:{}});if(o)throw new Error(o)}async function kf(e,t,n){let o=e.trim();if(o){if(t.kind==="text"){await Do(o,t.body.wa);return}if(t.kind==="file"){n.mediaSendFiles?await gc(o,t.spec.absPath,t.spec.caption):await Do(o,`Saved: ${t.spec.absPath}`);return}if(t.kind==="files"){if(!n.mediaSendFiles){let r=t.specs.map(s=>` ${s.absPath}`).join(`
158
+ transformers fallback failed: ${String(u)}`)}}}async function gc(e){let{cfg:t,tools:n,url:o,outputDir:r}=e,s=fc(t),i=await Fm({tools:n,url:o,outputDir:r,timeoutMs:s});return hc({cfg:t,tools:n,inputPath:i,outputDir:r})}function xv(e){try{return vv.readFileSync(e,"utf8").trim()}catch{return""}}function Cv(e,t){if(e.length<=t)return[e];let n=[];for(let o=0;o<e.length;o+=t)n.push(e.slice(o,o+t));return n}async function vf(e){let t=lt(e.cfg),n=fi(e.cfg,t);if(n)return{kind:"text",body:p(n)};let o=mi(e.targetRaw,e.sessionCwd),r=Pn(e.cfg,e.sessionCwd,e.peerKey),s=e.reporter??Cn(),i="",a=null,l=[];if(o.kind==="url"){if(!t.ytDlp)return{kind:"text",body:p("yt-dlp missing. Run: omnish pull install")};if(!t.ffmpeg)return{kind:"text",body:p("ffmpeg missing. Run: omnish pull install")};l.push({label:"Downloading video",run:async()=>{let b=await on({cfg:e.cfg,tools:t,url:o.url,mode:"video",outputDir:r});if(a=b.files.filter(x=>/\.(mp4|mkv|webm|mov)$/i.test(x))[0]??b.files[0]??null,!a)throw new Error("No video file downloaded.");return i=a,b}})}else i=o.absPath;l.push({label:"Transcribing",run:async()=>hc({cfg:e.cfg,tools:t,inputPath:i,outputDir:r})}),l.push({label:"Preparing results",run:async()=>{}});let c=await rn(s,l),u=c[c.length-2],d=u.files.find(b=>b.endsWith(".txt")),m=u.files.find(b=>b.endsWith(".srt")||b.endsWith(".vtt")),f=d?xv(d):"",h=Math.min(e.cfg.appsMaxWaChars,e.cfg.syncMaxBytes)||3500,g=f?Cv(f,h):["(no transcript text produced)"],y=[];return m&&y.push(m),o.kind==="url"&&a&&y.push(a),En({cfg:e.cfg,files:y,textLines:g})}async function Do(e,t){let n=await Wt({op:"sendPeerText",peerKey:e,text:t});if(n)throw new Error(n)}async function yc(e,t,n){let o=await Wt({op:"sendPeerMedia",peerKey:e,absPath:t,...n?{caption:n}:{}});if(o)throw new Error(o)}async function xf(e,t,n){let o=e.trim();if(o){if(t.kind==="text"){await Do(o,t.body.wa);return}if(t.kind==="file"){n.mediaSendFiles?await yc(o,t.spec.absPath,t.spec.caption):await Do(o,`Saved: ${t.spec.absPath}`);return}if(t.kind==="files"){if(!n.mediaSendFiles){let r=t.specs.map(s=>` ${s.absPath}`).join(`
159
159
  `);await Do(o,`Files:
160
- ${r}`);return}for(let r of t.specs)await gc(o,r.absPath,r.caption);return}if(t.kind==="bundle"){for(let r of t.texts??[])await Do(o,r.wa);if(!n.mediaSendFiles){let r=(t.files??[]).map(s=>` ${s.absPath}`).join(`
160
+ ${r}`);return}for(let r of t.specs)await yc(o,r.absPath,r.caption);return}if(t.kind==="bundle"){for(let r of t.texts??[])await Do(o,r.wa);if(!n.mediaSendFiles){let r=(t.files??[]).map(s=>` ${s.absPath}`).join(`
161
161
  `);r&&await Do(o,`Files:
162
- ${r}`);return}for(let r of t.files??[])try{await gc(o,r.absPath,r.caption)}catch{await Do(o,`Could not send: ${r.absPath}`)}}}}function Sf(e){if(e.kind==="text"){process.stdout.write(`${e.body.wa}
162
+ ${r}`);return}for(let r of t.files??[])try{await yc(o,r.absPath,r.caption)}catch{await Do(o,`Could not send: ${r.absPath}`)}}}}function Cf(e){if(e.kind==="text"){process.stdout.write(`${e.body.wa}
163
163
  `);return}if(e.kind==="bundle"){for(let t of e.texts??[])process.stdout.write(`${t.wa}
164
164
 
165
165
  `);for(let t of e.files??[])process.stdout.write(`FILE: ${t.absPath}
166
166
  `);return}if(e.kind==="file"){process.stdout.write(`FILE: ${e.spec.absPath}
167
167
  `);return}if(e.kind==="files")for(let t of e.specs)process.stdout.write(`FILE: ${t.absPath}
168
- `)}async function yc(e,t,n){if(Sf(e),t)try{await kf(t,e,n)}catch(o){process.stderr.write(`Peer delivery failed: ${String(o)}
169
- `)}}function vv(e){let t=e.toLowerCase();return t==="dlf"?{force:"file"}:t==="dlv"?{force:"video"}:{}}async function wc(e){let[t,n,o]=e,r=(t??"").toLowerCase(),s=v(),i=o?.trim()?Sv.resolve(o):process.cwd(),a=Mm(),l=ti({peerKey:a,enabled:s.progressUpdates});if(r==="dl"||r==="dlf"||r==="dlv"){await yc(await cf({cfg:s,url:n??"",sessionCwd:i,peerKey:a??void 0,reporter:l,classify:vv(r)}),a,s);return}if(r==="tr"){await yc(await bf({cfg:s,targetRaw:n??"",sessionCwd:i,peerKey:a??void 0,reporter:l}),a,s);return}if(r==="edit"){await yc(await mf({cfg:s,args:n??"",sessionCwd:i,peerKey:a??void 0,reporter:l}),a,s);return}process.stderr.write(`Unknown media-exec command: ${r}
170
- `),process.exitCode=1}ue();j();import xv from"node:fs";import Cv from"node:path";function Rv(e,t){return Pn(e,t)}async function Tv(e){qe(e.url);let t=lt(e.cfg),n=Rv(e.cfg,e.sessionCwd),o=[],r;if(e.mode==="transcript"){let a=await hc({cfg:e.cfg,tools:t,url:e.url,outputDir:n});o.push(...a.files)}else if(e.mode==="all"){let a=await on({cfg:e.cfg,tools:t,url:e.url,mode:"all",outputDir:n});r=a.title,o.push(...a.files);try{let l=await hc({cfg:e.cfg,tools:t,url:e.url,outputDir:n});o.push(...l.files)}catch{}}else{let a=await on({cfg:e.cfg,tools:t,url:e.url,mode:e.mode,outputDir:n});r=a.title,o.push(...a.files)}let s=o.filter(a=>typeof a=="string"&&xv.existsSync(a)),i=[r?`Title: ${r}`:"",`Saved under: ${n}`,s.length?"Files:":"(no files listed)",...s.map(a=>` ${a}`)].filter(Boolean);return{title:r,outputDir:n,files:s,summary:i.join(`
171
- `)}}async function vf(e){let[t,n,o]=e,r=t??"audio",s=v(),i=o?.trim()?Cv.resolve(o):pl,a=await Tv({cfg:s,mode:r,url:n??"",sessionCwd:i});process.stdout.write(a.summary+`
172
- `)}function bc(){let e=Tt.stdout;console.log(Re(e,"omnish pull"),w(e,"\u2014 download media from URLs (yt-dlp + ffmpeg + optional Whisper)")),console.log(""),console.log(K(e,"Usage:")),console.log(` ${S(e,"omnish pull doctor")}`),console.log(` ${S(e,"omnish pull install")} ${w(e,"[--whisper] [--transcribe-node]")}`),console.log(` ${S(e,"omnish pull instructions")}`),console.log(` ${S(e,"omnish media-exec <dl|dlf|dlv|tr|edit> <payload> <cwd>")} ${Q(e,"(internal / background)")}`),console.log(""),console.log(w(e,"Chat: /dl help \xB7 /tr \xB7 /edit (/pull aliases older commands)")),console.log(Q(e,"Docs: docs/features/media-commands.md"))}async function xf(e){let t=(e[0]??"").toLowerCase(),n=e.slice(1);if(!t||t==="help"||t==="-h"||t==="--help"){bc();return}if(t==="doctor"){let o=v(),{text:r}=ii(o);console.log(H(Tt.stdout,r));return}if(t==="instructions"||t==="setup"){console.log(ei());return}if(t==="install"){let o=n.some(i=>i==="--whisper"),r=n.some(i=>i==="--transcribe-node"),s=Tt.stdout;console.log(H(s,"Installing pull tools into ~/.omnish/bin \u2026"));try{let i=await ai({whisper:o,transcribeNode:r});for(let a of i.messages)console.log(H(s,a))}catch(i){console.error(E(Tt.stderr,String(i))),Tt.exitCode=1}return}console.error(E(Tt.stderr,`Unknown subcommand "${t}". Try: omnish pull help`)),Tt.exitCode=1}async function Cf(e){try{let t=(e[0]??"").toLowerCase();if(t==="dl"||t==="tr"||t==="edit"){await wc(e);return}await vf(e)}catch(t){console.error(E(Tt.stderr,String(t))),Tt.exitCode=1}}async function Rf(e){try{await wc(e)}catch(t){console.error(E(Tt.stderr,String(t))),Tt.exitCode=1}}j();import{execFileSync as $n}from"node:child_process";import Wo from"node:fs";import jr from"node:os";import Et from"node:path";import Xe from"node:process";function sn(){let e=Xe.execPath,t=Xe.argv[1];if(!t||!t.trim())return{nodePath:e,scriptPath:"",omnishHome:W,error:"Cannot resolve gateway entry script (missing argv[1]). Run omnish from its CLI entry."};let n=Et.resolve(t),o=Xe.env.OMNISH_HOME?.trim(),r=o?Et.resolve(o):W;return{nodePath:e,scriptPath:n,omnishHome:r}}function Tf(e){return/[ "'\\\s]/.test(e)?`"${e.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`:e}function Ev(e){return`Environment="OMNISH_HOME=${e.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`}function Pv(e){return`[Unit]
168
+ `)}async function wc(e,t,n){if(Cf(e),t)try{await xf(t,e,n)}catch(o){process.stderr.write(`Peer delivery failed: ${String(o)}
169
+ `)}}function Tv(e){let t=e.toLowerCase();return t==="dlf"?{force:"file"}:t==="dlv"?{force:"video"}:{}}async function bc(e){let[t,n,o]=e,r=(t??"").toLowerCase(),s=v(),i=o?.trim()?Rv.resolve(o):process.cwd(),a=Om(),l=ti({peerKey:a,enabled:s.progressUpdates});if(r==="dl"||r==="dlf"||r==="dlv"){await wc(await pf({cfg:s,url:n??"",sessionCwd:i,peerKey:a??void 0,reporter:l,classify:Tv(r)}),a,s);return}if(r==="tr"){await wc(await vf({cfg:s,targetRaw:n??"",sessionCwd:i,peerKey:a??void 0,reporter:l}),a,s);return}if(r==="edit"){await wc(await gf({cfg:s,args:n??"",sessionCwd:i,peerKey:a??void 0,reporter:l}),a,s);return}process.stderr.write(`Unknown media-exec command: ${r}
170
+ `),process.exitCode=1}ue();j();import Ev from"node:fs";import Pv from"node:path";function $v(e,t){return Pn(e,t)}async function Mv(e){qe(e.url);let t=lt(e.cfg),n=$v(e.cfg,e.sessionCwd),o=[],r;if(e.mode==="transcript"){let a=await gc({cfg:e.cfg,tools:t,url:e.url,outputDir:n});o.push(...a.files)}else if(e.mode==="all"){let a=await on({cfg:e.cfg,tools:t,url:e.url,mode:"all",outputDir:n});r=a.title,o.push(...a.files);try{let l=await gc({cfg:e.cfg,tools:t,url:e.url,outputDir:n});o.push(...l.files)}catch{}}else{let a=await on({cfg:e.cfg,tools:t,url:e.url,mode:e.mode,outputDir:n});r=a.title,o.push(...a.files)}let s=o.filter(a=>typeof a=="string"&&Ev.existsSync(a)),i=[r?`Title: ${r}`:"",`Saved under: ${n}`,s.length?"Files:":"(no files listed)",...s.map(a=>` ${a}`)].filter(Boolean);return{title:r,outputDir:n,files:s,summary:i.join(`
171
+ `)}}async function Rf(e){let[t,n,o]=e,r=t??"audio",s=v(),i=o?.trim()?Pv.resolve(o):ml,a=await Mv({cfg:s,mode:r,url:n??"",sessionCwd:i});process.stdout.write(a.summary+`
172
+ `)}function kc(){let e=Tt.stdout;console.log(Re(e,"omnish pull"),w(e,"\u2014 download media from URLs (yt-dlp + ffmpeg + optional Whisper)")),console.log(""),console.log(K(e,"Usage:")),console.log(` ${S(e,"omnish pull doctor")}`),console.log(` ${S(e,"omnish pull install")} ${w(e,"[--whisper] [--transcribe-node]")}`),console.log(` ${S(e,"omnish pull instructions")}`),console.log(` ${S(e,"omnish media-exec <dl|dlf|dlv|tr|edit> <payload> <cwd>")} ${Q(e,"(internal / background)")}`),console.log(""),console.log(w(e,"Chat: /dl help \xB7 /tr \xB7 /edit (/pull aliases older commands)")),console.log(Q(e,"Docs: docs/features/media-commands.md"))}async function Tf(e){let t=(e[0]??"").toLowerCase(),n=e.slice(1);if(!t||t==="help"||t==="-h"||t==="--help"){kc();return}if(t==="doctor"){let o=v(),{text:r}=ii(o);console.log(H(Tt.stdout,r));return}if(t==="instructions"||t==="setup"){console.log(ei());return}if(t==="install"){let o=n.some(i=>i==="--whisper"),r=n.some(i=>i==="--transcribe-node"),s=Tt.stdout;console.log(H(s,"Installing pull tools into ~/.omnish/bin \u2026"));try{let i=await ai({whisper:o,transcribeNode:r});for(let a of i.messages)console.log(H(s,a))}catch(i){console.error(P(Tt.stderr,String(i))),Tt.exitCode=1}return}console.error(P(Tt.stderr,`Unknown subcommand "${t}". Try: omnish pull help`)),Tt.exitCode=1}async function Ef(e){try{let t=(e[0]??"").toLowerCase();if(t==="dl"||t==="tr"||t==="edit"){await bc(e);return}await Rf(e)}catch(t){console.error(P(Tt.stderr,String(t))),Tt.exitCode=1}}async function Pf(e){try{await bc(e)}catch(t){console.error(P(Tt.stderr,String(t))),Tt.exitCode=1}}j();import{execFileSync as $n}from"node:child_process";import Wo from"node:fs";import jr from"node:os";import Et from"node:path";import Xe from"node:process";function sn(){let e=Xe.execPath,t=Xe.argv[1];if(!t||!t.trim())return{nodePath:e,scriptPath:"",omnishHome:W,error:"Cannot resolve gateway entry script (missing argv[1]). Run omnish from its CLI entry."};let n=Et.resolve(t),o=Xe.env.OMNISH_HOME?.trim(),r=o?Et.resolve(o):W;return{nodePath:e,scriptPath:n,omnishHome:r}}function $f(e){return/[ "'\\\s]/.test(e)?`"${e.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`:e}function Av(e){return`Environment="OMNISH_HOME=${e.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`}function Iv(e){return`[Unit]
173
173
  Description=omnish gateway (WhatsApp/Telegram)
174
174
  After=network-online.target
175
175
  Wants=network-online.target
176
176
 
177
177
  [Service]
178
178
  Type=simple
179
- ExecStart=${`${Tf(e.nodePath)} ${Tf(e.scriptPath)} run`}
180
- ${Ev(e.omnishHome)}
179
+ ExecStart=${`${$f(e.nodePath)} ${$f(e.scriptPath)} run`}
180
+ ${Av(e.omnishHome)}
181
181
  Restart=on-failure
182
182
  RestartSec=5
183
183
 
184
184
  [Install]
185
185
  WantedBy=default.target
186
- `}function Br(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function $v(e){let t=jr.homedir(),n=Et.join(e.omnishHome,"logs","launchd-stdout.log"),o=Et.join(e.omnishHome,"logs","launchd-stderr.log");D(Et.dirname(n));let r=Br(e.nodePath),s=Br(e.scriptPath),i=Br(e.omnishHome),a=Br(n),l=Br(o);return`<?xml version="1.0" encoding="UTF-8"?>
186
+ `}function Br(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function Ov(e){let t=jr.homedir(),n=Et.join(e.omnishHome,"logs","launchd-stdout.log"),o=Et.join(e.omnishHome,"logs","launchd-stderr.log");D(Et.dirname(n));let r=Br(e.nodePath),s=Br(e.scriptPath),i=Br(e.omnishHome),a=Br(n),l=Br(o);return`<?xml version="1.0" encoding="UTF-8"?>
187
187
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
188
188
  <plist version="1.0">
189
189
  <dict>
@@ -210,26 +210,27 @@ WantedBy=default.target
210
210
  <string>${l}</string>
211
211
  </dict>
212
212
  </plist>
213
- `}function hi(){let e=sn();if(e.error)return{ok:!1,detail:e.error};if(Xe.platform==="win32")return{ok:!1,detail:"Automatic install is not supported on Windows from chat. Use Task Scheduler (see /service instructions) or omnish-windows-task.xml in the repo contrib folder."};if(Xe.platform==="darwin")try{let t=Et.join(jr.homedir(),"Library/LaunchAgents/dev.omnish.gateway.plist");D(Et.dirname(t));let n=$v(e);Wo.writeFileSync(t,n,{mode:384});let o=typeof Xe.getuid=="function"?Xe.getuid():null;if(o===null||o<0)return{ok:!1,detail:"Could not read user id for launchctl."};let r=`gui/${o}`;try{$n("launchctl",["bootout",r,t],{stdio:"pipe"})}catch{}return $n("launchctl",["bootstrap",r,t],{stdio:"inherit"}),$n("launchctl",["kickstart","-k",`${r}/dev.omnish.gateway`],{stdio:"inherit"}),{ok:!0,detail:"LaunchAgent installed: ~/Library/LaunchAgents/dev.omnish.gateway.plist (label dev.omnish.gateway)."}}catch(t){return{ok:!1,detail:String(t)}}if(Xe.platform==="linux")try{let t=Et.join(jr.homedir(),".config","systemd","user");D(t);let n=Et.join(t,"omnish.service"),o=Pv(e);return Wo.writeFileSync(n,o,{mode:420}),$n("systemctl",["--user","daemon-reload"],{stdio:"inherit"}),$n("systemctl",["--user","enable","--now","omnish.service"],{stdio:"inherit"}),{ok:!0,detail:`User systemd unit installed: ${n} \u2014 enabled and started.`}}catch(t){return{ok:!1,detail:String(t)}}return{ok:!1,detail:`Unsupported platform: ${Xe.platform}`}}function gi(){if(Xe.platform==="win32")return{ok:!0,detail:"Windows: remove the omnish task from Task Scheduler manually on this PC. See /service instructions."};if(Xe.platform==="darwin")try{let e=Et.join(jr.homedir(),"Library/LaunchAgents/dev.omnish.gateway.plist"),n=`gui/${typeof Xe.getuid=="function"?Xe.getuid():0}`;if(Wo.existsSync(e)){try{$n("launchctl",["bootout",n,e],{stdio:"pipe"})}catch{}Wo.unlinkSync(e)}return{ok:!0,detail:"LaunchAgent dev.omnish.gateway removed (if it was present)."}}catch(e){return{ok:!1,detail:String(e)}}if(Xe.platform==="linux")try{let e=Et.join(jr.homedir(),".config","systemd","user","omnish.service");try{$n("systemctl",["--user","disable","--now","omnish.service"],{stdio:"pipe"})}catch{}Wo.existsSync(e)&&Wo.unlinkSync(e);try{$n("systemctl",["--user","daemon-reload"],{stdio:"pipe"})}catch{}return{ok:!0,detail:"User systemd unit omnish.service removed (if it was present)."}}catch(e){return{ok:!1,detail:String(e)}}return{ok:!1,detail:`Unsupported platform: ${Xe.platform}`}}import Ef from"node:fs";var yi=48e3;function wi(e,t){try{if(!Ef.existsSync(e))return"(no log file yet \u2014 start with `omnish start` or the systemd/LaunchAgent service.)";let n=Ef.readFileSync(e),r=(n.length>yi?n.subarray(n.length-yi):n).toString("utf8");return n.length>yi&&(r=`\u2026(truncated to last ${yi} bytes)
213
+ `}function hi(){let e=sn();if(e.error)return{ok:!1,detail:e.error};if(Xe.platform==="win32")return{ok:!1,detail:"Automatic install is not supported on Windows from chat. Use Task Scheduler (see /service instructions) or omnish-windows-task.xml in the repo contrib folder."};if(Xe.platform==="darwin")try{let t=Et.join(jr.homedir(),"Library/LaunchAgents/dev.omnish.gateway.plist");D(Et.dirname(t));let n=Ov(e);Wo.writeFileSync(t,n,{mode:384});let o=typeof Xe.getuid=="function"?Xe.getuid():null;if(o===null||o<0)return{ok:!1,detail:"Could not read user id for launchctl."};let r=`gui/${o}`;try{$n("launchctl",["bootout",r,t],{stdio:"pipe"})}catch{}return $n("launchctl",["bootstrap",r,t],{stdio:"inherit"}),$n("launchctl",["kickstart","-k",`${r}/dev.omnish.gateway`],{stdio:"inherit"}),{ok:!0,detail:"LaunchAgent installed: ~/Library/LaunchAgents/dev.omnish.gateway.plist (label dev.omnish.gateway)."}}catch(t){return{ok:!1,detail:String(t)}}if(Xe.platform==="linux")try{let t=Et.join(jr.homedir(),".config","systemd","user");D(t);let n=Et.join(t,"omnish.service"),o=Iv(e);return Wo.writeFileSync(n,o,{mode:420}),$n("systemctl",["--user","daemon-reload"],{stdio:"inherit"}),$n("systemctl",["--user","enable","--now","omnish.service"],{stdio:"inherit"}),{ok:!0,detail:`User systemd unit installed: ${n} \u2014 enabled and started.`}}catch(t){return{ok:!1,detail:String(t)}}return{ok:!1,detail:`Unsupported platform: ${Xe.platform}`}}function gi(){if(Xe.platform==="win32")return{ok:!0,detail:"Windows: remove the omnish task from Task Scheduler manually on this PC. See /service instructions."};if(Xe.platform==="darwin")try{let e=Et.join(jr.homedir(),"Library/LaunchAgents/dev.omnish.gateway.plist"),n=`gui/${typeof Xe.getuid=="function"?Xe.getuid():0}`;if(Wo.existsSync(e)){try{$n("launchctl",["bootout",n,e],{stdio:"pipe"})}catch{}Wo.unlinkSync(e)}return{ok:!0,detail:"LaunchAgent dev.omnish.gateway removed (if it was present)."}}catch(e){return{ok:!1,detail:String(e)}}if(Xe.platform==="linux")try{let e=Et.join(jr.homedir(),".config","systemd","user","omnish.service");try{$n("systemctl",["--user","disable","--now","omnish.service"],{stdio:"pipe"})}catch{}Wo.existsSync(e)&&Wo.unlinkSync(e);try{$n("systemctl",["--user","daemon-reload"],{stdio:"pipe"})}catch{}return{ok:!0,detail:"User systemd unit omnish.service removed (if it was present)."}}catch(e){return{ok:!1,detail:String(e)}}return{ok:!1,detail:`Unsupported platform: ${Xe.platform}`}}import Mf from"node:fs";var yi=48e3;function wi(e,t){try{if(!Mf.existsSync(e))return"(no log file yet \u2014 start with `omnish start` or the systemd/LaunchAgent service.)";let n=Mf.readFileSync(e),r=(n.length>yi?n.subarray(n.length-yi):n).toString("utf8");return n.length>yi&&(r=`\u2026(truncated to last ${yi} bytes)
214
214
  ${r}`),r.split(/\r?\n/).slice(-t).join(`
215
- `).trimEnd()||"(empty)"}catch(n){return`Could not read log: ${String(n)}`}}import Mn from"node:fs";import Vv from"node:path";j();import Mv from"node:crypto";import vc from"node:fs";import Av from"node:path";var xc=/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,31}$/,Bo="__omnish_recipes_global__",Cc=new Set(["help","list","show","add","set","remove","rm","del","run","r","online"]),Uo=new Map,Pf=!1;function Iv(){try{let e=vc.readFileSync(xs,"utf8"),t=JSON.parse(e);if(t&&typeof t=="object")for(let[n,o]of Object.entries(t)){if(!o||typeof o!="object")continue;let r={};for(let[s,i]of Object.entries(o)){let a=String(s).toLowerCase();i&&typeof i=="object"&&typeof i.command=="string"&&(r[a]=it(i))}Uo.set(n,r)}}catch{}}function Ov(e){if(!Array.isArray(e)||e.length===0)return;let t=[];for(let n of e)if(typeof n=="string"&&n.trim())t.push({cmd:n.trim()});else if(n&&typeof n=="object"){let o=n,r=typeof o.cmd=="string"?o.cmd.trim():"";if(!r)continue;t.push({cmd:r,label:typeof o.label=="string"?o.label.trim().slice(0,80):void 0,continueOnFail:typeof o.continueOnFail=="boolean"?o.continueOnFail:void 0})}return t.length>0?t:void 0}function it(e){let t=typeof e.taskEnv=="string"&&/^[A-Za-z_][A-Za-z0-9_]*$/.test(e.taskEnv)?e.taskEnv:"OMNISH_TASK",n=typeof e.promptTemplate=="string"&&e.promptTemplate.trim().length>0?e.promptTemplate.trim():void 0,o=Ov(e.steps),r={command:String(e.command).trim(),taskEnv:t,label:typeof e.label=="string"?e.label.trim().slice(0,80):void 0,description:typeof e.description=="string"?e.description.trim().slice(0,200):void 0,category:typeof e.category=="string"?e.category.trim().slice(0,40):void 0,featured:typeof e.featured=="boolean"?e.featured:void 0,dangerous:typeof e.dangerous=="boolean"?e.dangerous:void 0};return n!==void 0&&(r.promptTemplate=n),o!==void 0&&(r.steps=o),r}function ki(){D(Av.dirname(xs));let e={};for(let[t,n]of Uo)Object.entries(n).length>0&&(e[t]={...n});vc.writeFileSync(xs,JSON.stringify(e,null,2)+`
216
- `,{mode:384})}function Si(){Pf||(Iv(),Pf=!0)}function $f(e){return Si(),Uo.get(e)??{}}function kc(e){Si();let t=Uo.get(e);return t||(t={},Uo.set(e,t)),t}function Lv(e,t){let n=e.taskEnv??"OMNISH_TASK",o=t.recipesMacroDefaultCommand.trim();return ro(o,n).ok?it({...e,command:o,promptTemplate:e.command}):e}function Nv(e,t){if(e.promptTemplate||ro(e.command,t).ok||!Ho(e.command,t))return!1;let n=e.command;return n.includes("```")||n.length>2e3||/^"Create\b/i.test(n)||n.includes("<<<")||n.includes(`
217
- ### `)}function Rc(e,t){let n=it(e),o=n.taskEnv??"OMNISH_TASK";return!n.promptTemplate&&Nv(n,o)&&(n=Lv(n,t)),n}function _v(e){try{let t=vc.readFileSync(vs,"utf8"),n=JSON.parse(t);if(!n||typeof n!="object")return{};let o={};for(let[r,s]of Object.entries(n)){let i=r.trim().toLowerCase();if(!(!xc.test(i)||Cc.has(i))&&s&&typeof s=="object"&&typeof s.command=="string"){let l=Rc(s,e),c=so(l);if(!c.ok){console.warn(`[omnish] recipes.json: skipping "${i}": ${c.error}`);continue}o[i]=l}}return o}catch{return{}}}function Fv(e){return{claude:{command:'claude -p "$OMNISH_TASK" --allowedTools all --dangerously-skip-permissions',taskEnv:"OMNISH_TASK",label:"Claude Code",description:"Anthropic Claude Code CLI (requires `claude` on PATH)",category:"agents",featured:!0,dangerous:e.recipesAllowDangerousBuiltins},codex:{command:'codex "$OMNISH_TASK"',taskEnv:"OMNISH_TASK",label:"Codex CLI",description:"OpenAI Codex CLI if installed; override in recipes.json",category:"agents",featured:!1},gemini:{command:'gemini -p "$OMNISH_TASK"',taskEnv:"OMNISH_TASK",label:"Gemini CLI",description:"Google Gemini CLI if installed; override in recipes.json",category:"agents",featured:!1},cursor:{command:"agent --yolo --force -p ```Before writing any code:\n1. Analyze the codebase and state your full implementation plan\n2. List every file you will touch\n3. Identify any risks or ambiguities\n4. Then execute the plan step by step, <task> $OMNISH_TASK </task>```",taskEnv:"OMNISH_TASK",label:"Cursor Agent",description:"Cursor Agent if installed; override in recipes.json",category:"agents",featured:!0}}}function bi(e,t,n){for(let[o,r]of Object.entries(t)){let s=o.toLowerCase();!xc.test(s)||Cc.has(s)||r.command.trim()&&e.set(s,{...r,name:s,source:n})}}function Sc(e,t){let n=$f(e),o={};for(let[r,s]of Object.entries(n)){let i=Rc(s,t);so(i).ok&&(o[r]=i)}return o}function Mf(e,t){let n=new Map;return bi(n,Fv(t),"builtin"),bi(n,_v(t),"global"),bi(n,Sc(Bo,t),"shared"),bi(n,Sc(e,t),"peer"),n}function ze(e,t,n){let o=n.trim().toLowerCase();return Mf(e,t).get(o)}function Af(e){let t=e.trim();if(/^--global$/i.test(t))return{mode:"global",remainder:""};if(/^-g$/i.test(t))return{mode:"global",remainder:""};if(/^--chat$/i.test(t))return{mode:"chat",remainder:""};if(/^-p$/i.test(t))return{mode:"chat",remainder:""};let n=/^--global\s+/i.exec(t);if(n)return{mode:"global",remainder:t.slice(n[0].length).trimStart()};let o=/^-g\s+/i.exec(t);if(o)return{mode:"global",remainder:t.slice(o[0].length).trimStart()};let r=/^--chat\s+/i.exec(t);if(r)return{mode:"chat",remainder:t.slice(r[0].length).trimStart()};let s=/^-p\s+/i.exec(t);return s?{mode:"chat",remainder:t.slice(s[0].length).trimStart()}:{mode:"resolved",remainder:t}}function Tc(e){let t=e.trim();if(/^--global$/i.test(t))return{scope:"global",remainder:"",explicit:!0};if(/^-g$/i.test(t))return{scope:"global",remainder:"",explicit:!0};if(/^--chat$/i.test(t))return{scope:"chat",remainder:"",explicit:!0};if(/^-p$/i.test(t))return{scope:"chat",remainder:"",explicit:!0};let n=/^--global\s+([\s\S]*)$/i.exec(t);if(n?.[1]!==void 0)return{scope:"global",remainder:n[1].trimStart(),explicit:!0};let o=/^-g\s+([\s\S]*)$/i.exec(t);if(o?.[1]!==void 0)return{scope:"global",remainder:o[1].trimStart(),explicit:!0};let r=/^--chat\s+([\s\S]*)$/i.exec(t);if(r?.[1]!==void 0)return{scope:"chat",remainder:r[1].trimStart(),explicit:!0};let s=/^-p\s+([\s\S]*)$/i.exec(t);return s?.[1]!==void 0?{scope:"chat",remainder:s[1].trimStart(),explicit:!0}:{scope:"chat",remainder:t,explicit:!1}}function Ec(e){let t=Tc(e);return{scope:t.scope,remainder:t.remainder}}function If(e){let t=e.trim();return!t||/^-+$/i.test(t)?{filter:"merged"}:/^(?:--global|-g)$/i.test(t)?{filter:"global"}:/^(?:--chat|-p)$/i.test(t)?{filter:"chat"}:{filter:"merged",bad:t}}function Dv(e){let t=e.trim().toLowerCase();if(t==="--global"||t==="-g")return"global";if(t==="--chat"||t==="-p")return"chat"}function Of(e){let t=e.trim().match(/^(\S+)\s+(--global|-g|--chat|-p)\s*$/i);if(!t?.[1]||!t[2])return;let n=Dv(t[2]);if(n)return{name:t[1],target:n}}function an(e,t,n,o){let r=o.trim().toLowerCase(),s=e==="global"?Bo:t,i=$f(s)[r];if(i===void 0)return;let a=Rc(i,n);if(so(a).ok)return{...a,name:r,source:e==="global"?"shared":"peer"}}function Lf(e,t,n){if(n==="merged")return[];let o=n==="global"?Bo:e,r=n==="global"?"shared":"peer",s=Sc(o,t);return Object.entries(s).map(([i,a])=>({...a,name:i,source:r})).sort((i,a)=>i.name.localeCompare(a.name))}function Bt(e){let t=e.trim();if(!t)return{ok:!1,error:"Name is empty."};let n=t.toLowerCase();return xc.test(n)?Cc.has(n)?{ok:!1,error:`Reserved name: ${n}`}:{ok:!0,normalized:n}:{ok:!1,error:`Invalid name (use letters, digits, _ or -; max 32 chars): ${t}`}}function jo(e,t){let n=e.replace(/\r\n/g,`
218
- `).trim();return n?t>0&&n.length>t?{ok:!1,error:`Task too long (max ${t} characters).`}:{ok:!0,task:n}:{ok:!1,error:"Task is empty."}}function Ho(e,t){return e.includes("$")?e.includes(t):!1}function ro(e,t){let n=e.trim();if(!n)return{ok:!1,error:"Command is empty."};if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(t))return{ok:!1,error:"Invalid taskEnv (use letters, digits, _)."};if(!Ho(n,t))return{ok:!1,error:`Command must reference "$${t}" so the task is passed via the environment.`};let o=`"$${t}"`;return n.includes(o)?n.includes("```")?{ok:!1,error:"Recipe command must not contain markdown code fences (```). Use promptTemplate or split with a --- line (see /run help)."}:{ok:!0}:{ok:!1,error:`Command must include ${o} (quoted).`}}function so(e){let t=e.taskEnv??"OMNISH_TASK",n=ro(e.command,t);return n.ok?e.promptTemplate!==void 0&&e.promptTemplate.trim().length===0?{ok:!1,error:"promptTemplate cannot be empty."}:{ok:!0}:n}function Wv(e){let t=e,n=null,o=!1,r=-1,s=-1;for(let l=0;l<t.length;l+=1){let c=t[l];if(o){o=!1;continue}if(c==="\\"){o=!0;continue}if(n){c===n&&(n=null);continue}if(c==='"'||c==="'"){n=c;continue}if(c!=="-"||t[l+1]!=="-")continue;let u=l===0?"":t[l-1];if(u&&!/\s/.test(u))continue;let d=t.slice(l+2),m=/^(template|tamplate)\b/i.exec(d);if(!m)continue;let f=l+2+m[0].length,h=t[f]??"";if(!h||!/\s/.test(h))continue;r=l;let g=f;for(;g<t.length&&/\s/.test(t[g]);)g+=1;s=g;break}if(r<0||s<0)return{command:t.trim()};let i=t.slice(0,r).trim(),a=t.slice(s).trim();return(a.startsWith('"')&&a.endsWith('"')&&a.length>=2||a.startsWith("'")&&a.endsWith("'")&&a.length>=2)&&(a=a.slice(1,-1)),{command:i,template:a}}function Pc(e,t){let n=e.replace(/\r\n/g,`
219
- `).trim();if(!n)throw new Error("Recipe body is empty.");let o=n.match(/^--steps\s+([\s\S]+)$/i);if(o){let m=o[1].trim().split(/\s*;\s*|\n/).filter(h=>h.trim());if(m.length===0)throw new Error("No steps found. Separate steps with ; or newlines.");let f=m.map(h=>{let g=h.startsWith("+"),y=g?h.slice(1).trim():h.trim();if(!y)throw new Error("Empty step in recipe.");return{cmd:y,...g?{continueOnFail:!0}:{}}});return it({command:f[0].cmd,steps:f})}let r="OMNISH_TASK",{command:s,template:i}=Wv(n);if(i!==void 0){if(!s)throw new Error("Command part (before --template) is empty.");if(!i.trim())throw new Error("Template part (after --template) is empty.");let d=ro(s,r);if(!d.ok)throw new Error(d.error);return it({command:s,promptTemplate:i})}let a=/\n---\n/,l=n.search(a);if(l>=0){let d=n.slice(0,l).trim(),m=n.slice(l).replace(/^\n---\n/,"").trim();if(!d)throw new Error("Command part (before ---) is empty.");if(!m)throw new Error("Template part (after ---) is empty.");let f=ro(d,r);if(!f.ok)throw new Error(f.error);return it({command:d,promptTemplate:m})}if(ro(n,r).ok)return it({command:n});let c=t.trim(),u=ro(c,r);if(!u.ok)throw new Error(`recipesMacroDefaultCommand invalid (${u.error}). Fix config or paste runner then --- then template.`);return it({command:c,promptTemplate:n})}function vi(e,t,n){let o="`".repeat(3),r=`<<<${o}$${t}${o}>>>`,s=e;s.includes(r)&&(s=s.split(r).join(n));let i=`<<<${t}>>>`;s.includes(i)&&(s=s.split(i).join(n));let a=new RegExp(`\\$${t}\\b`,"g");return s.replace(a,n)}function Jo(e,t,n,o="chat",r){let s=Bt(t);if(!s.ok)throw new Error(s.error);let i=it({...n,command:n.command});if(r?.skipCommandValidation){if(!i.command.trim())throw new Error("Command is empty.")}else{let c=so(i);if(!c.ok)throw new Error(c.error)}let a=o==="global"?Bo:e,l=kc(a);l[s.normalized]=i,ki()}function Nf(e,t,n="chat"){let o=t.trim().toLowerCase(),r=n==="global"?Bo:e;Si();let s=Uo.get(r);return!s||!(o in s)?!1:(delete s[o],ki(),!0)}function $c(e,t,n,o){let r=Bt(t);if(!r.ok)return{ok:!1,error:r.error};let s=r.normalized,i=e,a=Bo;Si();let l=kc(i),c=kc(a),u=l[s],d=c[s],m=f=>{let h=it({...f}),g=so(h);if(!g.ok)throw new Error(g.error);return h};try{if(n==="global"){if(u!==void 0){let g=m(u);return c[s]=g,delete l[s],ki(),{ok:!0,kind:"moved",target:"global",name:s}}if(d!==void 0)return{ok:!0,kind:"noop",message:`Recipe "${s}" is already gateway-shared.`};let h=ze(e,o,s);return h?.source==="builtin"||h?.source==="global"?{ok:!1,error:`Recipe "${s}" is built-in or from host recipes.json \u2014 not moved via /run set. Edit recipes.json or use a different name.`}:{ok:!1,error:`No user recipe "${s}" in this chat to promote. Add with /run add ${s} \u2026 or demote from global with /run set -p ${s}.`}}if(d!==void 0){let h=m(d);return l[s]=h,delete c[s],ki(),{ok:!0,kind:"moved",target:"chat",name:s}}if(u!==void 0)return{ok:!0,kind:"noop",message:`Recipe "${s}" is already stored for this chat only.`};let f=ze(e,o,s);return f?.source==="builtin"||f?.source==="global"?{ok:!1,error:`Recipe "${s}" is built-in or from host recipes.json \u2014 not moved via /run set.`}:{ok:!1,error:`No gateway-shared user recipe "${s}" to demote. Add with /run add --global ${s} \u2026 or promote from this chat with /run set -g ${s}.`}}catch(f){return{ok:!1,error:String(f)}}}function _f(e,t){let n=[...Mf(e,t).values()],o=n.filter(a=>a.featured).sort((a,l)=>a.name.localeCompare(l.name)),r=n.filter(a=>a.source==="peer").sort((a,l)=>a.name.localeCompare(l.name)),s=n.filter(a=>a.source==="shared").sort((a,l)=>a.name.localeCompare(l.name)),i=n.filter(a=>!a.featured&&a.source!=="peer"&&a.source!=="shared").sort((a,l)=>a.name.localeCompare(l.name));return{featured:o,shared:s,yours:r,more:i}}function xi(e){let t=Mv.randomBytes(4).toString("hex");return`r-${e.replace(/[^a-zA-Z0-9_-]/g,"").slice(0,12)}-${t}`.slice(0,32)}function Ff(e,t,n,o){let r=t.skipped?"skip":t.exitCode===0?"ok":"FAIL",s=t.label||`step ${n+1}`,i=`[${r}] ${e} ${n+1}/${o}: ${s}`;if(t.skipped)return i;let a=t.timedOut?" (timeout)":t.exitCode!==0?` (exit ${t.exitCode})`:"",l=t.output.trim();if(!l)return`${i}${a}`;let c=l.length>300?l.slice(0,300)+"\u2026":l;return`${i}${a}
220
- ${c}`}function Df(e,t){let n=[`runbook: ${e}`];for(let r of t){let s=r.skipped?"skip":r.exitCode===0?"ok":"FAIL",i=r.label||`step ${r.index+1}`;if(r.skipped)n.push(`[${s}] ${i}`);else{n.push(`[${s}] ${i}${r.timedOut?" (timeout)":r.exitCode!==0?` (exit ${r.exitCode})`:""}`);let a=r.output.trim();if(a){let l=a.length>300?a.slice(0,300)+"\u2026":a;n.push(l)}}}let o=t.every(r=>r.skipped||r.exitCode===0);return n.push(o?"All steps completed successfully.":"Runbook stopped on failure."),n.join(`
221
- `)}j();var Wf={enter:"\r",cr:"\r",lf:`
222
- `,return:"\r",tab:" ",esc:"\x1B",escape:"\x1B",space:" ",backspace:"\x7F",bs:"\x7F",delete:"\x1B[3~",up:"\x1B[A",down:"\x1B[B",right:"\x1B[C",left:"\x1B[D",home:"\x1B[H",end:"\x1B[F",pageup:"\x1B[5~",pagedown:"\x1B[6~","ctrl+c":"","ctrl+d":"","ctrl+z":"","ctrl+l":"\f","ctrl+u":"","ctrl+k":"\v"};function Uv(e){let t="";for(let n=0;n<e.length;n++){let o=e[n];if(o==="\\"&&n+1<e.length){let r=e[n+1];if(r==="x"&&n+3<e.length){let s=e.slice(n+2,n+4);if(/^[0-9a-fA-F]{2}$/.test(s)){t+=String.fromCharCode(Number.parseInt(s,16)),n+=3;continue}}if(r==="r"){t+="\r",n++;continue}if(r==="n"){t+=`
223
- `,n++;continue}if(r==="t"){t+=" ",n++;continue}if(r==="\\"){t+="\\",n++;continue}}t+=o}return t}function Bv(e){let t=e.trim();if(!t)return"";if(t.startsWith("\\"))return Uv(t);let n=t.toLowerCase();if(Wf[n])return Wf[n];let o=t.match(/^\^([A-Za-z])$/);if(o){let s=o[1].toUpperCase().charCodeAt(0);if(s>=64&&s<=95)return String.fromCharCode(s-64)}let r=n.match(/^ctrl\+(.+)$/);if(r){let s=r[1];if(s.length===1){let i=s.toUpperCase().charCodeAt(0);if(i>=64&&i<=95)return String.fromCharCode(i-64)}}return t}function Ci(e){let t=e.split(",").map(n=>n.trim()).filter(Boolean);return t.length===0?"":t.map(n=>Bv(n)).join("")}var Hr="\x1B";function Ri(e){let t=e;return t=t.replace(new RegExp(`${Hr}\\[[\\d;?]*[ -/]*[@-~]`,"g"),""),t=t.replace(new RegExp(`${Hr}\\][^${Hr}\\u0007]*(?:\\u0007|${Hr}\\\\)`,"g"),""),t=t.replace(new RegExp(`${Hr}[@-Z\\\\-_]`,"g"),""),t=t.replace(/\u0007/g,""),t}function jv(e,t){if(e.length<=t)return e?[e]:[];let n=[];for(let o=0;o<e.length;o+=t)n.push(e.slice(o,o+t));return n}function Hv(e,t){return`${e}${t}`}var Ti=class{constructor(t){this.opts=t}pending=new Map;timers=new Map;lastFlushEnd=new Map;flushing=new Set;closed=!1;dispose(){this.closed=!0;for(let t of this.timers.values())clearTimeout(t);this.timers.clear(),this.pending.clear(),this.lastFlushEnd.clear(),this.flushing.clear()}push(t,n,o){if(this.closed||this.opts.isMuted(t,n))return;let r=Hv(t,n);this.pending.set(r,(this.pending.get(r)??"")+o);let s=this.timers.get(r);s&&clearTimeout(s);let i=this.opts.getCfg().appsFlushMs,a=setTimeout(()=>{this.timers.delete(r),this.flushNow(r,t,n)},i);this.timers.set(r,a)}async flushNow(t,n,o){if(!(this.closed||this.flushing.has(t))){this.flushing.add(t);try{let r=this.opts.getCfg(),s=r.appsMinIntervalMs,i=this.lastFlushEnd.get(t)??0,a=Math.max(0,i+s-Date.now());if(a>0&&await new Promise(f=>setTimeout(f,a)),this.closed)return;let l=this.pending.get(t)??"";if(this.pending.delete(t),!l||(this.opts.isRaw(n,o)||(l=Ri(l)),!l.trim()))return;let c=r.appsMaxFlushBytes;if(l.length>c){let f=l.slice(c);l=l.slice(0,c)+`
224
- [\u2026+${f.length} chars; /apps since ${o} to read more]`,this.pending.set(t,(this.pending.get(t)??"")+f)}let d=(this.opts.getRunningCount(n)>1?`[${o}] `:"")+l,m=jv(d,r.appsMaxWaChars);for(let f of m){if(this.closed)break;try{await this.opts.send(n,f)}catch{break}}this.lastFlushEnd.set(t,Date.now())}finally{this.flushing.delete(t),!this.closed&&(this.pending.get(t)??"").length>0&&queueMicrotask(()=>void this.flushNow(t,n,o))}}}};var Ei=4096,Jv=/\[sudo\]\s+password\s+for\s+/i,Gv=/passphrase\s+for\s+/i,qv=/(?:^|\n)\s*(?:Password|password):\s*$/;function Pi(e){let n=Ri(e).replace(/\r/g,"").slice(-512);return!!(Jv.test(n)||Gv.test(n)||qv.test(n))}j();import zv from"node:fs";import Kv from"node:path";import*as Uf from"node-pty";var Yv=1024*1024,$i=class e{peerKey;name;command;cwd;envKeysCount;logPath;term=null;logStream=null;disposeData=null;disposeExit=null;exited=!1;ringChunks=[];ringBytes=0;constructor(t){this.peerKey=t.peerKey,this.name=t.name,this.command=t.command,this.cwd=t.cwd,this.envKeysCount=t.envKeysCount,this.logPath=t.logPath}appendRing(t){for(this.ringChunks.push(t),this.ringBytes+=t.length;this.ringBytes>Yv&&this.ringChunks.length>0;){let n=this.ringChunks.shift();this.ringBytes-=n.length}}static start(t){D(vr(t.peerKey));let n=Kv.join(vr(t.peerKey),`${t.name}.log`),o=zv.createWriteStream(n,{flags:"a",mode:384}),r={...process.env,TERM:"xterm-256color",COLORTERM:"truecolor",...t.cwd?{PWD:t.cwd}:{},...t.extraEnv??{}},s=Uf.spawn(t.shell,["-lc",t.command],{name:"xterm-256color",cols:t.cols,rows:t.rows,cwd:t.cwd,env:r}),i=new e({...t,logPath:n});return i.term=s,i.logStream=o,i.disposeData=s.onData(a=>{let l=Buffer.from(a,"utf8");i.appendRing(l),o.write(a),t.onOutput?.(a),t.router.push(t.peerKey,t.name,a)}),i.disposeExit=s.onExit(a=>{i.exited||(i.exited=!0,i.cleanupTerm(),t.onExit({exitCode:a.exitCode,signal:a.signal}))}),i}get alive(){return this.term!==null&&!this.exited}get ringByteCount(){return this.ringBytes}recentOutputTail(t=4096){if(this.ringBytes===0)return"";let n=Math.max(1,t),o=[],r=Math.min(n,this.ringBytes);for(let s=this.ringChunks.length-1;s>=0&&r>0;s--){let i=this.ringChunks[s];i.length<=r?(o.unshift(i),r-=i.length):(o.unshift(i.subarray(i.length-r)),r=0)}return Buffer.concat(o).toString("utf8")}write(t){this.term?.write(t)}resize(t,n){this.term?.resize(t,n)}kill(t){if(this.term)try{this.term.kill(t)}catch{}}cleanupTerm(){this.disposeData?.dispose(),this.disposeData=null,this.disposeExit?.dispose(),this.disposeExit=null,this.term=null,this.logStream?.end(),this.logStream=null}destroy(){if(this.exited){this.cleanupTerm();return}this.exited=!0,this.kill("SIGHUP"),this.cleanupTerm()}};function Mc(e){return new Promise(t=>setTimeout(t,e))}async function Mi(e,t,n){let o=n.appsSubmitDelayMs,r=n.appsClearInputDelayMs,s=n.appsClearInput!==!1,a=n.appsSkipClearOnPasswordPrompt!==!1&&Pi(e.recentOutputTail(Ei)),l=s&&!a?Ci(n.appsClearInputSequence||"^A,^K"):"",u=t.replace(/\r\n/g,`
215
+ `).trimEnd()||"(empty)"}catch(n){return`Could not read log: ${String(n)}`}}import Mn from"node:fs";import rx from"node:path";j();import Lv from"node:crypto";import xc from"node:fs";import Nv from"node:path";var Cc=/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,31}$/,Bo="__omnish_recipes_global__",Rc=new Set(["help","list","show","add","set","remove","rm","del","run","r","online"]),Uo=new Map,Af=!1;function _v(){try{let e=xc.readFileSync(xs,"utf8"),t=JSON.parse(e);if(t&&typeof t=="object")for(let[n,o]of Object.entries(t)){if(!o||typeof o!="object")continue;let r={};for(let[s,i]of Object.entries(o)){let a=String(s).toLowerCase();i&&typeof i=="object"&&typeof i.command=="string"&&(r[a]=it(i))}Uo.set(n,r)}}catch{}}function Fv(e){if(!Array.isArray(e)||e.length===0)return;let t=[];for(let n of e)if(typeof n=="string"&&n.trim())t.push({cmd:n.trim()});else if(n&&typeof n=="object"){let o=n,r=typeof o.cmd=="string"?o.cmd.trim():"";if(!r)continue;t.push({cmd:r,label:typeof o.label=="string"?o.label.trim().slice(0,80):void 0,continueOnFail:typeof o.continueOnFail=="boolean"?o.continueOnFail:void 0})}return t.length>0?t:void 0}function it(e){let t=typeof e.taskEnv=="string"&&/^[A-Za-z_][A-Za-z0-9_]*$/.test(e.taskEnv)?e.taskEnv:"OMNISH_TASK",n=typeof e.promptTemplate=="string"&&e.promptTemplate.trim().length>0?e.promptTemplate.trim():void 0,o=Fv(e.steps),r={command:String(e.command).trim(),taskEnv:t,label:typeof e.label=="string"?e.label.trim().slice(0,80):void 0,description:typeof e.description=="string"?e.description.trim().slice(0,200):void 0,category:typeof e.category=="string"?e.category.trim().slice(0,40):void 0,featured:typeof e.featured=="boolean"?e.featured:void 0,dangerous:typeof e.dangerous=="boolean"?e.dangerous:void 0};return n!==void 0&&(r.promptTemplate=n),o!==void 0&&(r.steps=o),r}function ki(){D(Nv.dirname(xs));let e={};for(let[t,n]of Uo)Object.entries(n).length>0&&(e[t]={...n});xc.writeFileSync(xs,JSON.stringify(e,null,2)+`
216
+ `,{mode:384})}function Si(){Af||(_v(),Af=!0)}function If(e){return Si(),Uo.get(e)??{}}function Sc(e){Si();let t=Uo.get(e);return t||(t={},Uo.set(e,t)),t}function Dv(e,t){let n=e.taskEnv??"OMNISH_TASK",o=t.recipesMacroDefaultCommand.trim();return ro(o,n).ok?it({...e,command:o,promptTemplate:e.command}):e}function Wv(e,t){if(e.promptTemplate||ro(e.command,t).ok||!Ho(e.command,t))return!1;let n=e.command;return n.includes("```")||n.length>2e3||/^"Create\b/i.test(n)||n.includes("<<<")||n.includes(`
217
+ ### `)}function Tc(e,t){let n=it(e),o=n.taskEnv??"OMNISH_TASK";return!n.promptTemplate&&Wv(n,o)&&(n=Dv(n,t)),n}function Uv(e){try{let t=xc.readFileSync(vs,"utf8"),n=JSON.parse(t);if(!n||typeof n!="object")return{};let o={};for(let[r,s]of Object.entries(n)){let i=r.trim().toLowerCase();if(!(!Cc.test(i)||Rc.has(i))&&s&&typeof s=="object"&&typeof s.command=="string"){let l=Tc(s,e),c=so(l);if(!c.ok){console.warn(`[omnish] recipes.json: skipping "${i}": ${c.error}`);continue}o[i]=l}}return o}catch{return{}}}function Bv(e){return{claude:{command:'claude -p "$OMNISH_TASK" --allowedTools all --dangerously-skip-permissions',taskEnv:"OMNISH_TASK",label:"Claude Code",description:"Anthropic Claude Code CLI (requires `claude` on PATH)",category:"agents",featured:!0,dangerous:e.recipesAllowDangerousBuiltins},codex:{command:'codex "$OMNISH_TASK"',taskEnv:"OMNISH_TASK",label:"Codex CLI",description:"OpenAI Codex CLI if installed; override in recipes.json",category:"agents",featured:!1},gemini:{command:'gemini -p "$OMNISH_TASK"',taskEnv:"OMNISH_TASK",label:"Gemini CLI",description:"Google Gemini CLI if installed; override in recipes.json",category:"agents",featured:!1},cursor:{command:"agent --yolo --force -p ```Before writing any code:\n1. Analyze the codebase and state your full implementation plan\n2. List every file you will touch\n3. Identify any risks or ambiguities\n4. Then execute the plan step by step, <task> $OMNISH_TASK </task>```",taskEnv:"OMNISH_TASK",label:"Cursor Agent",description:"Cursor Agent if installed; override in recipes.json",category:"agents",featured:!0}}}function bi(e,t,n){for(let[o,r]of Object.entries(t)){let s=o.toLowerCase();!Cc.test(s)||Rc.has(s)||r.command.trim()&&e.set(s,{...r,name:s,source:n})}}function vc(e,t){let n=If(e),o={};for(let[r,s]of Object.entries(n)){let i=Tc(s,t);so(i).ok&&(o[r]=i)}return o}function Of(e,t){let n=new Map;return bi(n,Bv(t),"builtin"),bi(n,Uv(t),"global"),bi(n,vc(Bo,t),"shared"),bi(n,vc(e,t),"peer"),n}function ze(e,t,n){let o=n.trim().toLowerCase();return Of(e,t).get(o)}function Lf(e){let t=e.trim();if(/^--global$/i.test(t))return{mode:"global",remainder:""};if(/^-g$/i.test(t))return{mode:"global",remainder:""};if(/^--chat$/i.test(t))return{mode:"chat",remainder:""};if(/^-p$/i.test(t))return{mode:"chat",remainder:""};let n=/^--global\s+/i.exec(t);if(n)return{mode:"global",remainder:t.slice(n[0].length).trimStart()};let o=/^-g\s+/i.exec(t);if(o)return{mode:"global",remainder:t.slice(o[0].length).trimStart()};let r=/^--chat\s+/i.exec(t);if(r)return{mode:"chat",remainder:t.slice(r[0].length).trimStart()};let s=/^-p\s+/i.exec(t);return s?{mode:"chat",remainder:t.slice(s[0].length).trimStart()}:{mode:"resolved",remainder:t}}function Ec(e){let t=e.trim();if(/^--global$/i.test(t))return{scope:"global",remainder:"",explicit:!0};if(/^-g$/i.test(t))return{scope:"global",remainder:"",explicit:!0};if(/^--chat$/i.test(t))return{scope:"chat",remainder:"",explicit:!0};if(/^-p$/i.test(t))return{scope:"chat",remainder:"",explicit:!0};let n=/^--global\s+([\s\S]*)$/i.exec(t);if(n?.[1]!==void 0)return{scope:"global",remainder:n[1].trimStart(),explicit:!0};let o=/^-g\s+([\s\S]*)$/i.exec(t);if(o?.[1]!==void 0)return{scope:"global",remainder:o[1].trimStart(),explicit:!0};let r=/^--chat\s+([\s\S]*)$/i.exec(t);if(r?.[1]!==void 0)return{scope:"chat",remainder:r[1].trimStart(),explicit:!0};let s=/^-p\s+([\s\S]*)$/i.exec(t);return s?.[1]!==void 0?{scope:"chat",remainder:s[1].trimStart(),explicit:!0}:{scope:"chat",remainder:t,explicit:!1}}function Pc(e){let t=Ec(e);return{scope:t.scope,remainder:t.remainder}}function Nf(e){let t=e.trim();return!t||/^-+$/i.test(t)?{filter:"merged"}:/^(?:--global|-g)$/i.test(t)?{filter:"global"}:/^(?:--chat|-p)$/i.test(t)?{filter:"chat"}:{filter:"merged",bad:t}}function jv(e){let t=e.trim().toLowerCase();if(t==="--global"||t==="-g")return"global";if(t==="--chat"||t==="-p")return"chat"}function _f(e){let t=e.trim().match(/^(\S+)\s+(--global|-g|--chat|-p)\s*$/i);if(!t?.[1]||!t[2])return;let n=jv(t[2]);if(n)return{name:t[1],target:n}}function an(e,t,n,o){let r=o.trim().toLowerCase(),s=e==="global"?Bo:t,i=If(s)[r];if(i===void 0)return;let a=Tc(i,n);if(so(a).ok)return{...a,name:r,source:e==="global"?"shared":"peer"}}function Ff(e,t,n){if(n==="merged")return[];let o=n==="global"?Bo:e,r=n==="global"?"shared":"peer",s=vc(o,t);return Object.entries(s).map(([i,a])=>({...a,name:i,source:r})).sort((i,a)=>i.name.localeCompare(a.name))}function Bt(e){let t=e.trim();if(!t)return{ok:!1,error:"Name is empty."};let n=t.toLowerCase();return Cc.test(n)?Rc.has(n)?{ok:!1,error:`Reserved name: ${n}`}:{ok:!0,normalized:n}:{ok:!1,error:`Invalid name (use letters, digits, _ or -; max 32 chars): ${t}`}}function jo(e,t){let n=e.replace(/\r\n/g,`
218
+ `).trim();return n?t>0&&n.length>t?{ok:!1,error:`Task too long (max ${t} characters).`}:{ok:!0,task:n}:{ok:!1,error:"Task is empty."}}function Ho(e,t){return e.includes("$")?e.includes(t):!1}function ro(e,t){let n=e.trim();if(!n)return{ok:!1,error:"Command is empty."};if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(t))return{ok:!1,error:"Invalid taskEnv (use letters, digits, _)."};if(!Ho(n,t))return{ok:!1,error:`Command must reference "$${t}" so the task is passed via the environment.`};let o=`"$${t}"`;return n.includes(o)?n.includes("```")?{ok:!1,error:"Recipe command must not contain markdown code fences (```). Use promptTemplate or split with a --- line (see /run help)."}:{ok:!0}:{ok:!1,error:`Command must include ${o} (quoted).`}}function so(e){let t=e.taskEnv??"OMNISH_TASK",n=ro(e.command,t);return n.ok?e.promptTemplate!==void 0&&e.promptTemplate.trim().length===0?{ok:!1,error:"promptTemplate cannot be empty."}:{ok:!0}:n}function Hv(e){let t=e,n=null,o=!1,r=-1,s=-1;for(let l=0;l<t.length;l+=1){let c=t[l];if(o){o=!1;continue}if(c==="\\"){o=!0;continue}if(n){c===n&&(n=null);continue}if(c==='"'||c==="'"){n=c;continue}if(c!=="-"||t[l+1]!=="-")continue;let u=l===0?"":t[l-1];if(u&&!/\s/.test(u))continue;let d=t.slice(l+2),m=/^(template|tamplate)\b/i.exec(d);if(!m)continue;let f=l+2+m[0].length,h=t[f]??"";if(!h||!/\s/.test(h))continue;r=l;let g=f;for(;g<t.length&&/\s/.test(t[g]);)g+=1;s=g;break}if(r<0||s<0)return{command:t.trim()};let i=t.slice(0,r).trim(),a=t.slice(s).trim();return(a.startsWith('"')&&a.endsWith('"')&&a.length>=2||a.startsWith("'")&&a.endsWith("'")&&a.length>=2)&&(a=a.slice(1,-1)),{command:i,template:a}}function $c(e,t){let n=e.replace(/\r\n/g,`
219
+ `).trim();if(!n)throw new Error("Recipe body is empty.");let o=n.match(/^--steps\s+([\s\S]+)$/i);if(o){let m=o[1].trim().split(/\s*;\s*|\n/).filter(h=>h.trim());if(m.length===0)throw new Error("No steps found. Separate steps with ; or newlines.");let f=m.map(h=>{let g=h.startsWith("+"),y=g?h.slice(1).trim():h.trim();if(!y)throw new Error("Empty step in recipe.");return{cmd:y,...g?{continueOnFail:!0}:{}}});return it({command:f[0].cmd,steps:f})}let r="OMNISH_TASK",{command:s,template:i}=Hv(n);if(i!==void 0){if(!s)throw new Error("Command part (before --template) is empty.");if(!i.trim())throw new Error("Template part (after --template) is empty.");let d=ro(s,r);if(!d.ok)throw new Error(d.error);return it({command:s,promptTemplate:i})}let a=/\n---\n/,l=n.search(a);if(l>=0){let d=n.slice(0,l).trim(),m=n.slice(l).replace(/^\n---\n/,"").trim();if(!d)throw new Error("Command part (before ---) is empty.");if(!m)throw new Error("Template part (after ---) is empty.");let f=ro(d,r);if(!f.ok)throw new Error(f.error);return it({command:d,promptTemplate:m})}if(ro(n,r).ok)return it({command:n});let c=t.trim(),u=ro(c,r);if(!u.ok)throw new Error(`recipesMacroDefaultCommand invalid (${u.error}). Fix config or paste runner then --- then template.`);return it({command:c,promptTemplate:n})}function vi(e,t,n){let o="`".repeat(3),r=`<<<${o}$${t}${o}>>>`,s=e;s.includes(r)&&(s=s.split(r).join(n));let i=`<<<${t}>>>`;s.includes(i)&&(s=s.split(i).join(n));let a=new RegExp(`\\$${t}\\b`,"g");return s.replace(a,n)}function Jo(e,t,n,o="chat",r){let s=Bt(t);if(!s.ok)throw new Error(s.error);let i=it({...n,command:n.command});if(r?.skipCommandValidation){if(!i.command.trim())throw new Error("Command is empty.")}else{let c=so(i);if(!c.ok)throw new Error(c.error)}let a=o==="global"?Bo:e,l=Sc(a);l[s.normalized]=i,ki()}function Df(e,t,n="chat"){let o=t.trim().toLowerCase(),r=n==="global"?Bo:e;Si();let s=Uo.get(r);return!s||!(o in s)?!1:(delete s[o],ki(),!0)}function Mc(e,t,n,o){let r=Bt(t);if(!r.ok)return{ok:!1,error:r.error};let s=r.normalized,i=e,a=Bo;Si();let l=Sc(i),c=Sc(a),u=l[s],d=c[s],m=f=>{let h=it({...f}),g=so(h);if(!g.ok)throw new Error(g.error);return h};try{if(n==="global"){if(u!==void 0){let g=m(u);return c[s]=g,delete l[s],ki(),{ok:!0,kind:"moved",target:"global",name:s}}if(d!==void 0)return{ok:!0,kind:"noop",message:`Recipe "${s}" is already gateway-shared.`};let h=ze(e,o,s);return h?.source==="builtin"||h?.source==="global"?{ok:!1,error:`Recipe "${s}" is built-in or from host recipes.json \u2014 not moved via /run set. Edit recipes.json or use a different name.`}:{ok:!1,error:`No user recipe "${s}" in this chat to promote. Add with /run add ${s} \u2026 or demote from global with /run set -p ${s}.`}}if(d!==void 0){let h=m(d);return l[s]=h,delete c[s],ki(),{ok:!0,kind:"moved",target:"chat",name:s}}if(u!==void 0)return{ok:!0,kind:"noop",message:`Recipe "${s}" is already stored for this chat only.`};let f=ze(e,o,s);return f?.source==="builtin"||f?.source==="global"?{ok:!1,error:`Recipe "${s}" is built-in or from host recipes.json \u2014 not moved via /run set.`}:{ok:!1,error:`No gateway-shared user recipe "${s}" to demote. Add with /run add --global ${s} \u2026 or promote from this chat with /run set -g ${s}.`}}catch(f){return{ok:!1,error:String(f)}}}function Wf(e,t){let n=[...Of(e,t).values()],o=n.filter(a=>a.featured).sort((a,l)=>a.name.localeCompare(l.name)),r=n.filter(a=>a.source==="peer").sort((a,l)=>a.name.localeCompare(l.name)),s=n.filter(a=>a.source==="shared").sort((a,l)=>a.name.localeCompare(l.name)),i=n.filter(a=>!a.featured&&a.source!=="peer"&&a.source!=="shared").sort((a,l)=>a.name.localeCompare(l.name));return{featured:o,shared:s,yours:r,more:i}}function xi(e){let t=Lv.randomBytes(4).toString("hex");return`r-${e.replace(/[^a-zA-Z0-9_-]/g,"").slice(0,12)}-${t}`.slice(0,32)}function Uf(e,t,n,o){let r=t.skipped?"skip":t.exitCode===0?"ok":"FAIL",s=t.label||`step ${n+1}`,i=`[${r}] ${e} ${n+1}/${o}: ${s}`;if(t.skipped)return i;let a=t.timedOut?" (timeout)":t.exitCode!==0?` (exit ${t.exitCode})`:"",l=t.output.trim();if(!l)return`${i}${a}`;let c=l.length>300?l.slice(0,300)+"\u2026":l;return`${i}${a}
220
+ ${c}`}function Bf(e,t){let n=[`runbook: ${e}`];for(let r of t){let s=r.skipped?"skip":r.exitCode===0?"ok":"FAIL",i=r.label||`step ${r.index+1}`;if(r.skipped)n.push(`[${s}] ${i}`);else{n.push(`[${s}] ${i}${r.timedOut?" (timeout)":r.exitCode!==0?` (exit ${r.exitCode})`:""}`);let a=r.output.trim();if(a){let l=a.length>300?a.slice(0,300)+"\u2026":a;n.push(l)}}}let o=t.every(r=>r.skipped||r.exitCode===0);return n.push(o?"All steps completed successfully.":"Runbook stopped on failure."),n.join(`
221
+ `)}j();var jf={enter:"\r",cr:"\r",lf:`
222
+ `,return:"\r",tab:" ",esc:"\x1B",escape:"\x1B",space:" ",backspace:"\x7F",bs:"\x7F",delete:"\x1B[3~",up:"\x1B[A",down:"\x1B[B",right:"\x1B[C",left:"\x1B[D",home:"\x1B[H",end:"\x1B[F",pageup:"\x1B[5~",pagedown:"\x1B[6~","ctrl+c":"","ctrl+d":"","ctrl+z":"","ctrl+l":"\f","ctrl+u":"","ctrl+k":"\v"};function Jv(e){let t="";for(let n=0;n<e.length;n++){let o=e[n];if(o==="\\"&&n+1<e.length){let r=e[n+1];if(r==="x"&&n+3<e.length){let s=e.slice(n+2,n+4);if(/^[0-9a-fA-F]{2}$/.test(s)){t+=String.fromCharCode(Number.parseInt(s,16)),n+=3;continue}}if(r==="r"){t+="\r",n++;continue}if(r==="n"){t+=`
223
+ `,n++;continue}if(r==="t"){t+=" ",n++;continue}if(r==="\\"){t+="\\",n++;continue}}t+=o}return t}function Gv(e){let t=e.trim();if(!t)return"";if(t.startsWith("\\"))return Jv(t);let n=t.toLowerCase();if(jf[n])return jf[n];let o=t.match(/^\^([A-Za-z])$/);if(o){let s=o[1].toUpperCase().charCodeAt(0);if(s>=64&&s<=95)return String.fromCharCode(s-64)}let r=n.match(/^ctrl\+(.+)$/);if(r){let s=r[1];if(s.length===1){let i=s.toUpperCase().charCodeAt(0);if(i>=64&&i<=95)return String.fromCharCode(i-64)}}return t}function Ci(e){let t=e.split(",").map(n=>n.trim()).filter(Boolean);return t.length===0?"":t.map(n=>Gv(n)).join("")}var Hr="\x1B";function Ri(e){let t=e;return t=t.replace(new RegExp(`${Hr}\\[[\\d;?]*[ -/]*[@-~]`,"g"),""),t=t.replace(new RegExp(`${Hr}\\][^${Hr}\\u0007]*(?:\\u0007|${Hr}\\\\)`,"g"),""),t=t.replace(new RegExp(`${Hr}[@-Z\\\\-_]`,"g"),""),t=t.replace(/\u0007/g,""),t}function qv(e,t){if(e.length<=t)return e?[e]:[];let n=[];for(let o=0;o<e.length;o+=t)n.push(e.slice(o,o+t));return n}function zv(e,t){return`${e}${t}`}var Ti=class{constructor(t){this.opts=t}pending=new Map;timers=new Map;lastFlushEnd=new Map;flushing=new Set;closed=!1;dispose(){this.closed=!0;for(let t of this.timers.values())clearTimeout(t);this.timers.clear(),this.pending.clear(),this.lastFlushEnd.clear(),this.flushing.clear()}push(t,n,o){if(this.closed||this.opts.isMuted(t,n))return;let r=zv(t,n);this.pending.set(r,(this.pending.get(r)??"")+o);let s=this.timers.get(r);s&&clearTimeout(s);let i=this.opts.getCfg().appsFlushMs,a=setTimeout(()=>{this.timers.delete(r),this.flushNow(r,t,n)},i);this.timers.set(r,a)}async flushNow(t,n,o){if(!(this.closed||this.flushing.has(t))){this.flushing.add(t);try{let r=this.opts.getCfg(),s=r.appsMinIntervalMs,i=this.lastFlushEnd.get(t)??0,a=Math.max(0,i+s-Date.now());if(a>0&&await new Promise(f=>setTimeout(f,a)),this.closed)return;let l=this.pending.get(t)??"";if(this.pending.delete(t),!l||(this.opts.isRaw(n,o)||(l=Ri(l)),!l.trim()))return;let c=r.appsMaxFlushBytes;if(l.length>c){let f=l.slice(c);l=l.slice(0,c)+`
224
+ [\u2026+${f.length} chars; /apps since ${o} to read more]`,this.pending.set(t,(this.pending.get(t)??"")+f)}let d=(this.opts.getRunningCount(n)>1?`[${o}] `:"")+l,m=qv(d,r.appsMaxWaChars);for(let f of m){if(this.closed)break;try{await this.opts.send(n,f)}catch{break}}this.lastFlushEnd.set(t,Date.now())}finally{this.flushing.delete(t),!this.closed&&(this.pending.get(t)??"").length>0&&queueMicrotask(()=>void this.flushNow(t,n,o))}}}};var Ei=4096,Kv=/\[sudo\]\s+password\s+for\s+/i,Yv=/passphrase\s+for\s+/i,Vv=/(?:^|\n)\s*(?:Password|password):\s*$/;function Pi(e){let n=Ri(e).replace(/\r/g,"").slice(-512);return!!(Kv.test(n)||Yv.test(n)||Vv.test(n))}import tx from"node:fs";import nx from"node:path";import*as Jf from"node-pty";ye();import{createRequire as Qv}from"node:module";import Ac from"node:fs";import Ic from"node:path";var Hf=!1;function Xv(e){return[Ic.join(e,"prebuilds",`darwin-${process.arch}`,"spawn-helper"),Ic.join(e,"build","Release","spawn-helper")]}function Zv(){try{let e=Qv(import.meta.url);return Ic.dirname(e.resolve("node-pty/package.json"))}catch{return null}}function ex(e){if(!Ac.existsSync(e))return;let t=Ac.statSync(e).mode;t&73||(Ac.chmodSync(e,t|493),E.debug({helperPath:e},"fixed node-pty spawn-helper execute permission"))}function $i(){if(process.platform!=="darwin"||Hf)return;Hf=!0;let e=Zv();if(e)try{for(let t of Xv(e))ex(t)}catch(t){E.debug({err:t},"could not fix node-pty spawn-helper permissions")}}j();var ox=1024*1024,Mi=class e{peerKey;name;command;cwd;envKeysCount;logPath;term=null;logStream=null;disposeData=null;disposeExit=null;exited=!1;ringChunks=[];ringBytes=0;constructor(t){this.peerKey=t.peerKey,this.name=t.name,this.command=t.command,this.cwd=t.cwd,this.envKeysCount=t.envKeysCount,this.logPath=t.logPath}appendRing(t){for(this.ringChunks.push(t),this.ringBytes+=t.length;this.ringBytes>ox&&this.ringChunks.length>0;){let n=this.ringChunks.shift();this.ringBytes-=n.length}}static start(t){D(vr(t.peerKey));let n=nx.join(vr(t.peerKey),`${t.name}.log`),o=tx.createWriteStream(n,{flags:"a",mode:384}),r={...process.env,TERM:"xterm-256color",COLORTERM:"truecolor",...t.cwd?{PWD:t.cwd}:{},...t.extraEnv??{}};$i();let s=Jf.spawn(t.shell,["-lc",t.command],{name:"xterm-256color",cols:t.cols,rows:t.rows,cwd:t.cwd,env:r}),i=new e({...t,logPath:n});return i.term=s,i.logStream=o,i.disposeData=s.onData(a=>{let l=Buffer.from(a,"utf8");i.appendRing(l),o.write(a),t.onOutput?.(a),t.router.push(t.peerKey,t.name,a)}),i.disposeExit=s.onExit(a=>{i.exited||(i.exited=!0,i.cleanupTerm(),t.onExit({exitCode:a.exitCode,signal:a.signal}))}),i}get alive(){return this.term!==null&&!this.exited}get ringByteCount(){return this.ringBytes}recentOutputTail(t=4096){if(this.ringBytes===0)return"";let n=Math.max(1,t),o=[],r=Math.min(n,this.ringBytes);for(let s=this.ringChunks.length-1;s>=0&&r>0;s--){let i=this.ringChunks[s];i.length<=r?(o.unshift(i),r-=i.length):(o.unshift(i.subarray(i.length-r)),r=0)}return Buffer.concat(o).toString("utf8")}write(t){this.term?.write(t)}resize(t,n){this.term?.resize(t,n)}kill(t){if(this.term)try{this.term.kill(t)}catch{}}cleanupTerm(){this.disposeData?.dispose(),this.disposeData=null,this.disposeExit?.dispose(),this.disposeExit=null,this.term=null,this.logStream?.end(),this.logStream=null}destroy(){if(this.exited){this.cleanupTerm();return}this.exited=!0,this.kill("SIGHUP"),this.cleanupTerm()}};function Oc(e){return new Promise(t=>setTimeout(t,e))}async function Ai(e,t,n){let o=n.appsSubmitDelayMs,r=n.appsClearInputDelayMs,s=n.appsClearInput!==!1,a=n.appsSkipClearOnPasswordPrompt!==!1&&Pi(e.recentOutputTail(Ei)),l=s&&!a?Ci(n.appsClearInputSequence||"^A,^K"):"",u=t.replace(/\r\n/g,`
225
225
  `).replace(/\r/g,"").split(`
226
- `);l&&(r>0&&await Mc(r),e.write(l));for(let d of u)d.length>0&&e.write(d),o>0&&await Mc(o),e.write("\r"),l&&(r>0&&await Mc(r),e.write(l))}var Qv=/^[a-zA-Z0-9_-]{1,32}$/;function Ze(e){return Qv.test(e)?null:"Session name must be 1\u201332 chars: letters, digits, _ or -."}function We(e,t){return`${e}:${t}`}function Xv(e){let t=e.lastIndexOf(":");return t<=0?null:{peerKey:e.slice(0,t),name:e.slice(t+1)}}function Zv(e){return/\bstarted\b/i.test(e)&&!/^Session "/.test(e)&&!/^Per-chat app/.test(e)&&!/^Global app/.test(e)}function Ac(e,t){let n=e??t.recipesRunAttach;return{attach:n,mute:!n}}function ex(e,t){return e===0&&(t===0||t==null)}function tx(e,t){try{let o=Mn.readFileSync(e,"utf8").split(/\r?\n/);return o.slice(Math.max(0,o.length-t)).join(`
227
- `).trimEnd()}catch{return""}}var An=class{constructor(t,n){this.getCfg=t;this.send=n,this.router=new Ti({getCfg:()=>this.getCfg(),send:n,isMuted:(o,r)=>this.muted.has(We(o,r)),isRaw:(o,r)=>this.rawMode.has(We(o,r)),getRunningCount:o=>this.countPeerRunning(o)})}sessions=new Map;focus=new Map;muted=new Set;rawMode=new Set;router;killTimers=new Set;send;runQueue=new Map;activeQueuedRunHead=new Map;runQueuePaused=new Map;passwordHintSent=new Set;countPeerRunning(t){let n=`${t}:`,o=0;for(let[r,s]of this.sessions)r.startsWith(n)&&s.alive&&o++;return o}countTotalRunning(){let t=0;for(let n of this.sessions.values())n.alive&&t++;return t}dispose(){for(let t of this.killTimers)clearTimeout(t);this.killTimers.clear(),this.stopAll(),this.router.dispose()}stopAll(){for(let t of this.sessions.values())t.destroy();this.sessions.clear(),this.focus.clear(),this.runQueue.clear(),this.activeQueuedRunHead.clear(),this.runQueuePaused.clear()}getFocus(t){return this.focus.get(t)??null}logPath(t,n){return Vv.join(vr(t),`${n}.log`)}getSession(t,n){return this.sessions.get(We(t,n))}removeSessionRecord(t,n){let o=We(t,n);this.sessions.delete(o),this.passwordHintSent.delete(o),this.focus.get(t)===n&&this.focus.set(t,null)}onSessionOutput(t,n){let o=We(t,n),r=this.getSession(t,n);if(!r?.alive)return;let s=this.getCfg(),i=r.recentOutputTail(Ei);if(!Pi(i)){this.passwordHintSent.delete(o);return}if(s.appsPasswordPromptHint===!1||this.passwordHintSent.has(o))return;this.passwordHintSent.add(o);let l=`Password prompt in app "${n}" \u2014 reply with your password only (no line-clear keys). It is not echoed in chat but is written to the session log.`;this.send(t,l).catch(()=>{})}async writeFocusedLine(t,n){let o=this.focus.get(t);if(!o)return!1;let r=this.getSession(t,o);return r?.alive?(await Mi(r,n,this.getCfg()),!0):!1}async writeNamedLine(t,n,o){let r=Ze(n);if(r)return r;let s=this.getSession(t,n);return s?.alive?(await Mi(s,o,this.getCfg()),null):`No app session "${n}". /apps list`}enqueueQueuedRun(t,n,o){let r=this.runQueue.get(t)??[];r.push(n),this.runQueue.set(t,r);let s=r.length,i=this.runQueuePaused.get(t)??!1,a=this.activeQueuedRunHead.get(t)??null,l=a?.sessionName??null;if(l?this.getSession(t,l)?.alive??!1:!1){let u=a?.recipeLabel?` (recipe: ${a.recipeLabel})`:"",d=s-1,m=d>0?`${d} other job(s) in line before this one.`:"Next in line after the active run finishes.";return`Queued "${n.recipeLabel}" (wait slot ${s} behind active ${l}${u}). ${m}`}return i?`Queued "${n.recipeLabel}" (position ${s}). Run queue is paused \u2014 /run queue resume to continue.`:this.drainNextQueuedRun(t,o)}resumeRunQueue(t,n){this.runQueuePaused.set(t,!1);let o=this.activeQueuedRunHead.get(t)??null,r=o?.sessionName??null;if(r?this.getSession(t,r)?.alive??!1:!1){let a=o?.recipeLabel?` (recipe: ${o.recipeLabel})`:"";return`Run queue: session "${r}"${a} is still running. Wait for exit code=0 and signal=0 before the next starts.`}return(this.runQueue.get(t)??[]).length===0?"Run queue is empty.":(this.activeQueuedRunHead.set(t,null),this.drainNextQueuedRun(t,n))}runQueueStatus(t){let n=this.runQueue.get(t)??[],o=this.activeQueuedRunHead.get(t)??null,r=o?.sessionName??null,s=this.runQueuePaused.get(t)??!1,i=r?this.getSession(t,r)?.alive??!1:!1,a=["Run queue (this chat)"];if(r&&i){let l=o?.recipeLabel?` \xB7 recipe: ${o.recipeLabel}`:"";a.push(`Active: ${r}${l}`)}else r?a.push(`Active: ${r} (exiting or stale)`):a.push("Active: (none)");if(a.push(`Pending: ${n.length} (waiting only \u2014 not counting the active run above)`),a.push(`Paused: ${s}`),n.length>0){a.push("Waiting (FIFO order):");for(let c=0;c<Math.min(n.length,20);c++)a.push(`${c+1}) ${n[c].recipeLabel}`);n.length>20&&a.push(`\u2026 and ${n.length-20} more`)}return a.push("Next auto-starts only after exit code=0 and signal=0."),a.push("Commands: /run queue resume"),a.join(`
228
- `)}drainNextQueuedRun(t,n){let o=this.runQueue.get(t);if(!o||o.length===0)return"";let r=o.shift();o.length===0?this.runQueue.delete(t):this.runQueue.set(t,o);let s=xi(r.recipeLabel),i=this.runQueue.get(t)?.length??0,a=r.startOptions??Ac(null,n),l=this.start(t,s,r.command,n,r.extraEnv,a);if(!Zv(l))return o.unshift(r),this.runQueue.set(t,o),this.runQueuePaused.set(t,!0),this.activeQueuedRunHead.set(t,null),`${l}
226
+ `);l&&(r>0&&await Oc(r),e.write(l));for(let d of u)d.length>0&&e.write(d),o>0&&await Oc(o),e.write("\r"),l&&(r>0&&await Oc(r),e.write(l))}var sx=/^[a-zA-Z0-9_-]{1,32}$/;function Ze(e){return sx.test(e)?null:"Session name must be 1\u201332 chars: letters, digits, _ or -."}function We(e,t){return`${e}:${t}`}function ix(e){let t=e.lastIndexOf(":");return t<=0?null:{peerKey:e.slice(0,t),name:e.slice(t+1)}}function ax(e){return/\bstarted\b/i.test(e)&&!/^Session "/.test(e)&&!/^Per-chat app/.test(e)&&!/^Global app/.test(e)}function Lc(e,t){let n=e??t.recipesRunAttach;return{attach:n,mute:!n}}function lx(e,t){return e===0&&(t===0||t==null)}function cx(e,t){try{let o=Mn.readFileSync(e,"utf8").split(/\r?\n/);return o.slice(Math.max(0,o.length-t)).join(`
227
+ `).trimEnd()}catch{return""}}var An=class{constructor(t,n){this.getCfg=t;this.send=n,this.router=new Ti({getCfg:()=>this.getCfg(),send:n,isMuted:(o,r)=>this.muted.has(We(o,r)),isRaw:(o,r)=>this.rawMode.has(We(o,r)),getRunningCount:o=>this.countPeerRunning(o)})}sessions=new Map;focus=new Map;muted=new Set;rawMode=new Set;router;killTimers=new Set;send;runQueue=new Map;activeQueuedRunHead=new Map;runQueuePaused=new Map;passwordHintSent=new Set;countPeerRunning(t){let n=`${t}:`,o=0;for(let[r,s]of this.sessions)r.startsWith(n)&&s.alive&&o++;return o}countTotalRunning(){let t=0;for(let n of this.sessions.values())n.alive&&t++;return t}dispose(){for(let t of this.killTimers)clearTimeout(t);this.killTimers.clear(),this.stopAll(),this.router.dispose()}stopAll(){for(let t of this.sessions.values())t.destroy();this.sessions.clear(),this.focus.clear(),this.runQueue.clear(),this.activeQueuedRunHead.clear(),this.runQueuePaused.clear()}getFocus(t){return this.focus.get(t)??null}logPath(t,n){return rx.join(vr(t),`${n}.log`)}getSession(t,n){return this.sessions.get(We(t,n))}removeSessionRecord(t,n){let o=We(t,n);this.sessions.delete(o),this.passwordHintSent.delete(o),this.focus.get(t)===n&&this.focus.set(t,null)}onSessionOutput(t,n){let o=We(t,n),r=this.getSession(t,n);if(!r?.alive)return;let s=this.getCfg(),i=r.recentOutputTail(Ei);if(!Pi(i)){this.passwordHintSent.delete(o);return}if(s.appsPasswordPromptHint===!1||this.passwordHintSent.has(o))return;this.passwordHintSent.add(o);let l=`Password prompt in app "${n}" \u2014 reply with your password only (no line-clear keys). It is not echoed in chat but is written to the session log.`;this.send(t,l).catch(()=>{})}async writeFocusedLine(t,n){let o=this.focus.get(t);if(!o)return!1;let r=this.getSession(t,o);return r?.alive?(await Ai(r,n,this.getCfg()),!0):!1}async writeNamedLine(t,n,o){let r=Ze(n);if(r)return r;let s=this.getSession(t,n);return s?.alive?(await Ai(s,o,this.getCfg()),null):`No app session "${n}". /apps list`}enqueueQueuedRun(t,n,o){let r=this.runQueue.get(t)??[];r.push(n),this.runQueue.set(t,r);let s=r.length,i=this.runQueuePaused.get(t)??!1,a=this.activeQueuedRunHead.get(t)??null,l=a?.sessionName??null;if(l?this.getSession(t,l)?.alive??!1:!1){let u=a?.recipeLabel?` (recipe: ${a.recipeLabel})`:"",d=s-1,m=d>0?`${d} other job(s) in line before this one.`:"Next in line after the active run finishes.";return`Queued "${n.recipeLabel}" (wait slot ${s} behind active ${l}${u}). ${m}`}return i?`Queued "${n.recipeLabel}" (position ${s}). Run queue is paused \u2014 /run queue resume to continue.`:this.drainNextQueuedRun(t,o)}resumeRunQueue(t,n){this.runQueuePaused.set(t,!1);let o=this.activeQueuedRunHead.get(t)??null,r=o?.sessionName??null;if(r?this.getSession(t,r)?.alive??!1:!1){let a=o?.recipeLabel?` (recipe: ${o.recipeLabel})`:"";return`Run queue: session "${r}"${a} is still running. Wait for exit code=0 and signal=0 before the next starts.`}return(this.runQueue.get(t)??[]).length===0?"Run queue is empty.":(this.activeQueuedRunHead.set(t,null),this.drainNextQueuedRun(t,n))}runQueueStatus(t){let n=this.runQueue.get(t)??[],o=this.activeQueuedRunHead.get(t)??null,r=o?.sessionName??null,s=this.runQueuePaused.get(t)??!1,i=r?this.getSession(t,r)?.alive??!1:!1,a=["Run queue (this chat)"];if(r&&i){let l=o?.recipeLabel?` \xB7 recipe: ${o.recipeLabel}`:"";a.push(`Active: ${r}${l}`)}else r?a.push(`Active: ${r} (exiting or stale)`):a.push("Active: (none)");if(a.push(`Pending: ${n.length} (waiting only \u2014 not counting the active run above)`),a.push(`Paused: ${s}`),n.length>0){a.push("Waiting (FIFO order):");for(let c=0;c<Math.min(n.length,20);c++)a.push(`${c+1}) ${n[c].recipeLabel}`);n.length>20&&a.push(`\u2026 and ${n.length-20} more`)}return a.push("Next auto-starts only after exit code=0 and signal=0."),a.push("Commands: /run queue resume"),a.join(`
228
+ `)}drainNextQueuedRun(t,n){let o=this.runQueue.get(t);if(!o||o.length===0)return"";let r=o.shift();o.length===0?this.runQueue.delete(t):this.runQueue.set(t,o);let s=xi(r.recipeLabel),i=this.runQueue.get(t)?.length??0,a=r.startOptions??Lc(null,n),l=this.start(t,s,r.command,n,r.extraEnv,a);if(!ax(l))return o.unshift(r),this.runQueue.set(t,o),this.runQueuePaused.set(t,!0),this.activeQueuedRunHead.set(t,null),`${l}
229
229
  Run queue paused; fix the issue, then /run queue resume.`;this.activeQueuedRunHead.set(t,{sessionName:s,recipeLabel:r.recipeLabel});let u=i>0?`
230
230
  Run queue: started head above; ${i} job(s) still waiting in FIFO.`:`
231
231
  Run queue: started head above; no further queued jobs.`;return`${l}${u}`}start(t,n,o,r,s,i){let a=Ze(n);if(a)return a;if(!o.trim())return`Usage: /apps start <name> <command\u2026>
232
- Example: /apps start sh bash`;if(this.sessions.has(We(t,n)))return`Session "${n}" already exists. /apps stop ${n} or pick another name.`;if(this.countPeerRunning(t)>=r.appsMaxSessions)return`Per-chat app limit (${r.appsMaxSessions}) reached. /apps stop or /apps rm an old session.`;if(this.countTotalRunning()>=r.appsMaxSessionsTotal)return`Global app limit (${r.appsMaxSessionsTotal}) reached. Stop sessions in other chats first.`;se();let l=oe(t).cwd,c={...process.env,TERM:"xterm-256color",COLORTERM:"truecolor",...l?{PWD:l}:{},...s??{}},u;try{u=$i.start({peerKey:t,name:n,shell:r.shell,command:o.trim(),cwd:l,cols:r.appsCols,rows:r.appsRows,envKeysCount:Object.keys(c).length,extraEnv:s,router:this.router,onOutput:()=>{this.onSessionOutput(t,n)},onExit:g=>{this.handleSessionExit(t,n,g)}})}catch(g){return`App spawn failed: ${String(g)}
232
+ Example: /apps start sh bash`;if(this.sessions.has(We(t,n)))return`Session "${n}" already exists. /apps stop ${n} or pick another name.`;if(this.countPeerRunning(t)>=r.appsMaxSessions)return`Per-chat app limit (${r.appsMaxSessions}) reached. /apps stop or /apps rm an old session.`;if(this.countTotalRunning()>=r.appsMaxSessionsTotal)return`Global app limit (${r.appsMaxSessionsTotal}) reached. Stop sessions in other chats first.`;se();let l=oe(t).cwd,c={...process.env,TERM:"xterm-256color",COLORTERM:"truecolor",...l?{PWD:l}:{},...s??{}},u;try{u=Mi.start({peerKey:t,name:n,shell:r.shell,command:o.trim(),cwd:l,cols:r.appsCols,rows:r.appsRows,envKeysCount:Object.keys(c).length,extraEnv:s,router:this.router,onOutput:()=>{this.onSessionOutput(t,n)},onExit:g=>{this.handleSessionExit(t,n,g)}})}catch(g){let y=String(g),b=/posix_spawnp/i.test(y)?`
233
+ On macOS: node-pty spawn-helper may lack execute permission \u2014 update omnish or run: chmod +x "$(npm root -g)/omnish/node_modules/node-pty/prebuilds/darwin-"*"/spawn-helper"`:"";return`App spawn failed: ${y}${b}
233
234
  If install skipped native builds: pnpm approve-builds && pnpm install (needs @whiskeysockets/baileys, sharp, protobufjs, esbuild, node-pty).`}let d=We(t,n);this.sessions.set(d,u);let m=i?.attach!==!1,f=i?.mute??!m;if(m&&this.focus.set(t,n),f&&this.muted.add(d),m)return`App "${n}" started and attached.
234
235
  [cwd: ${l}]
235
236
  Plain DMs go to this session until /apps detach. Use >othername text for another session.`;let h=f?`
@@ -237,12 +238,12 @@ Output muted \u2014 /apps unmute `+n+" or /apps attach "+n+" to stream to chat."
237
238
  [cwd: ${l}]`+h+`
238
239
  /apps attach ${n} \u2014 focus for plain DMs
239
240
  >${n} <text> \u2014 send a line
240
- /apps tail ${n} \u2014 recent output`}async handleSessionExit(t,n,o){let r=We(t,n);if(!this.sessions.has(r))return;let s=this.activeQueuedRunHead.get(t)?.sessionName===n,i=this.focus.get(t)===n;this.sessions.delete(r),i&&this.focus.set(t,null),this.muted.delete(r),this.rawMode.delete(r),this.passwordHintSent.delete(r);let a=o.signal!=null?` signal=${o.signal}`:"",l=i?" (detached)":"",c=`[${n}] exited code=${o.exitCode}${a}${l}`;try{await this.send(t,c)}catch{}if(!s)return;this.activeQueuedRunHead.set(t,null);let u=this.getCfg(),d=ex(o.exitCode,o.signal),m=this.runQueue.get(t)?.length??0;if(d){if(this.runQueuePaused.set(t,!1),m>0){let f=this.drainNextQueuedRun(t,u);if(f)try{await this.send(t,f)}catch{}}return}if(this.runQueuePaused.set(t,!0),m>0){let f=`Run queue paused (exit code=${o.exitCode}${o.signal!=null?` signal=${o.signal}`:""}). ${m} run(s) waiting. /run queue resume to continue.`;try{await this.send(t,f)}catch{}}}attach(t,n){let o=Ze(n);return o||(this.getSession(t,n)?.alive?(this.focus.set(t,n),`Attached to "${n}". Plain messages (no ! or / or >) go to this app. /apps detach to stop.`):`No session "${n}". /apps list`)}detach(t){return this.focus.set(t,null),"Detached (attach mode off)."}list(t){let n=[];for(let[s,i]of this.sessions){let a=Xv(s);if(!a||a.peerKey!==t||!i.alive)continue;let l=this.focus.get(t)===a.name?" *":"";n.push(`${a.name}${l}`)}if(n.length===0)return"(no app sessions; /apps start <name> <cmd>)";let o=this.focus.get(t);return`${o?`attached: ${o}`:"(no focus)"}
241
+ /apps tail ${n} \u2014 recent output`}async handleSessionExit(t,n,o){let r=We(t,n);if(!this.sessions.has(r))return;let s=this.activeQueuedRunHead.get(t)?.sessionName===n,i=this.focus.get(t)===n;this.sessions.delete(r),i&&this.focus.set(t,null),this.muted.delete(r),this.rawMode.delete(r),this.passwordHintSent.delete(r);let a=o.signal!=null?` signal=${o.signal}`:"",l=i?" (detached)":"",c=`[${n}] exited code=${o.exitCode}${a}${l}`;try{await this.send(t,c)}catch{}if(!s)return;this.activeQueuedRunHead.set(t,null);let u=this.getCfg(),d=lx(o.exitCode,o.signal),m=this.runQueue.get(t)?.length??0;if(d){if(this.runQueuePaused.set(t,!1),m>0){let f=this.drainNextQueuedRun(t,u);if(f)try{await this.send(t,f)}catch{}}return}if(this.runQueuePaused.set(t,!0),m>0){let f=`Run queue paused (exit code=${o.exitCode}${o.signal!=null?` signal=${o.signal}`:""}). ${m} run(s) waiting. /run queue resume to continue.`;try{await this.send(t,f)}catch{}}}attach(t,n){let o=Ze(n);return o||(this.getSession(t,n)?.alive?(this.focus.set(t,n),`Attached to "${n}". Plain messages (no ! or / or >) go to this app. /apps detach to stop.`):`No session "${n}". /apps list`)}detach(t){return this.focus.set(t,null),"Detached (attach mode off)."}list(t){let n=[];for(let[s,i]of this.sessions){let a=ix(s);if(!a||a.peerKey!==t||!i.alive)continue;let l=this.focus.get(t)===a.name?" *":"";n.push(`${a.name}${l}`)}if(n.length===0)return"(no app sessions; /apps start <name> <cmd>)";let o=this.focus.get(t);return`${o?`attached: ${o}`:"(no focus)"}
241
242
  App sessions:
242
243
  ${n.join(`
243
244
  `)}`}getSessionCommand(t,n){if(Ze(n))return null;let r=this.getSession(t,n);return r?.command?.trim()?r.command.trim():null}info(t,n){let o=this.getCfg(),r=n??this.focus.get(t)??"";if(!r)return"Usage: /apps info <name> (or /apps get <name>, or attach first)";let s=Ze(r);if(s)return s;let i=this.getSession(t,r),a=this.logPath(t,r),l=0;try{l=Mn.statSync(a).size}catch{}let c=this.muted.has(We(t,r)),u=this.rawMode.has(We(t,r));return[`session: ${r}`,`alive: ${i?.alive??!1}`,`cmd: ${i?.command??"(unknown)"}`,`cwd: ${i?.cwd??"(unknown)"}`,`env keys: ${i?.envKeysCount??"?"}`,`terminal size: ${o.appsCols}x${o.appsRows}`,`ring bytes (approx): ${i?.ringByteCount??0}`,`log: ${a} (${l} bytes)`,`mute outbound: ${c}`,`raw outbound: ${u}`].join(`
244
245
  `)}async sendText(t,n,o){let r=Ze(n);if(r)return r;let s=this.getSession(t,n);if(!s?.alive)return`No session "${n}".`;let i=o.replace(/\r\n/g,`
245
- `);return await Mi(s,i,this.getCfg()),`Sent to "${n}" (${i.length} chars + Enter per line).`}sendKey(t,n,o){let r=Ze(n);if(r)return r;let s=this.getSession(t,n);if(!s?.alive)return`No session "${n}".`;let i=Ci(o);return i?(s.write(i),`Sent keys to "${n}".`):"Empty key sequence. Example: /apps key sh Enter or ^C,Up,Enter"}tail(t,n,o){let r=Ze(n);if(r)return r;let s=this.logPath(t,n);if(!Mn.existsSync(s))return`(no log file for ${n})`;let i=this.getCfg(),a=Number.isFinite(o)&&o>0?Math.min(500,o):i.appsLogTailLines;return tx(s,a)||"(empty log)"}readSince(t,n,o){let r=this.logPath(t,n);try{let i=Mn.statSync(r).size,a=Mn.openSync(r,"r");try{let l=Math.min(o,i),c=i-l,u=Buffer.alloc(c);return c>0&&Mn.readSync(a,u,0,c,l),{text:u.toString("utf8"),nextOffset:i}}finally{Mn.closeSync(a)}}catch{return{text:"",nextOffset:o}}}mute(t,n){let o=Ze(n);return o||(this.muted.add(We(t,n)),`Muted chat output for "${n}" (log still grows). /apps unmute ${n}`)}unmute(t,n){let o=Ze(n);return o||(this.muted.delete(We(t,n)),`Unmuted "${n}".`)}setRaw(t,n,o){let r=Ze(n);if(r)return r;let s=We(t,n);return o?this.rawMode.add(s):this.rawMode.delete(s),`raw chat output for "${n}": ${o?"on (ANSI kept)":"off (stripped)"}.`}resize(t,n,o,r){let s=Ze(n);if(s)return s;let i=this.getSession(t,n);if(!i?.alive)return`No session "${n}".`;let a=Math.min(500,Math.max(20,Math.floor(o))),l=Math.min(200,Math.max(5,Math.floor(r)));return i.resize(a,l),`Resized "${n}" to ${a}x${l}.`}stop(t,n){let o=Ze(n);if(o)return o;let r=this.getSession(t,n);if(!r?.alive)return`No running session "${n}".`;r.kill("SIGTERM");let s=We(t,n),i=setTimeout(()=>{this.killTimers.delete(i);let a=this.getSession(t,n);a?.alive&&a.kill("SIGKILL")},5e3);return this.killTimers.add(i),`SIGTERM sent to "${n}". SIGKILL in 5s if still running.`}kill(t,n){let o=Ze(n);if(o)return o;let r=this.getSession(t,n);return r?.alive?(r.kill("SIGKILL"),`SIGKILL sent to "${n}".`):`No running session "${n}".`}rm(t,n){let o=Ze(n);if(o)return o;if(this.getSession(t,n)?.alive)return`Session "${n}" is still running. /apps stop ${n} first, then /apps rm ${n}.`;this.removeSessionRecord(t,n),this.muted.delete(We(t,n)),this.rawMode.delete(We(t,n)),this.passwordHintSent.delete(We(t,n));let s=this.logPath(t,n);try{Mn.rmSync(s,{force:!0})}catch{}return`Removed "${n}" metadata and log.`}};be();j();import nx from"better-sqlite3";var Bf=3,In=null;function jf(e){try{let t=JSON.parse(e);return Array.isArray(t)?t.map(String):[]}catch{return[]}}function Hf(e){return{id:String(e.id),parentId:e.parent_id!=null?String(e.parent_id):null,title:String(e.title),plan:String(e.plan),assignee:e.assignee!=null?String(e.assignee):null,status:String(e.status),dependsOn:jf(String(e.depends_on??"[]")),blocks:jf(String(e.blocks??"[]")),priority:Number(e.priority)||0,resourceProfile:String(e.resource_profile||"standard")||"standard",createdAtMs:Number(e.created_at_ms),startedAtMs:e.started_at_ms!=null?Number(e.started_at_ms):null,completedAtMs:e.completed_at_ms!=null?Number(e.completed_at_ms):null,reportPath:e.report_path!=null?String(e.report_path):null,supervisorVerdict:e.supervisor_verdict??null,supervisorNotes:String(e.supervisor_notes??""),reworkCount:Number(e.rework_count)||0,ownerPeerKey:String(e.owner_peer_key),workDir:String(e.work_dir)}}function Jf(){D(Jn);let e=new nx(Cd);e.pragma("journal_mode = WAL"),e.exec(`
246
+ `);return await Ai(s,i,this.getCfg()),`Sent to "${n}" (${i.length} chars + Enter per line).`}sendKey(t,n,o){let r=Ze(n);if(r)return r;let s=this.getSession(t,n);if(!s?.alive)return`No session "${n}".`;let i=Ci(o);return i?(s.write(i),`Sent keys to "${n}".`):"Empty key sequence. Example: /apps key sh Enter or ^C,Up,Enter"}tail(t,n,o){let r=Ze(n);if(r)return r;let s=this.logPath(t,n);if(!Mn.existsSync(s))return`(no log file for ${n})`;let i=this.getCfg(),a=Number.isFinite(o)&&o>0?Math.min(500,o):i.appsLogTailLines;return cx(s,a)||"(empty log)"}readSince(t,n,o){let r=this.logPath(t,n);try{let i=Mn.statSync(r).size,a=Mn.openSync(r,"r");try{let l=Math.min(o,i),c=i-l,u=Buffer.alloc(c);return c>0&&Mn.readSync(a,u,0,c,l),{text:u.toString("utf8"),nextOffset:i}}finally{Mn.closeSync(a)}}catch{return{text:"",nextOffset:o}}}mute(t,n){let o=Ze(n);return o||(this.muted.add(We(t,n)),`Muted chat output for "${n}" (log still grows). /apps unmute ${n}`)}unmute(t,n){let o=Ze(n);return o||(this.muted.delete(We(t,n)),`Unmuted "${n}".`)}setRaw(t,n,o){let r=Ze(n);if(r)return r;let s=We(t,n);return o?this.rawMode.add(s):this.rawMode.delete(s),`raw chat output for "${n}": ${o?"on (ANSI kept)":"off (stripped)"}.`}resize(t,n,o,r){let s=Ze(n);if(s)return s;let i=this.getSession(t,n);if(!i?.alive)return`No session "${n}".`;let a=Math.min(500,Math.max(20,Math.floor(o))),l=Math.min(200,Math.max(5,Math.floor(r)));return i.resize(a,l),`Resized "${n}" to ${a}x${l}.`}stop(t,n){let o=Ze(n);if(o)return o;let r=this.getSession(t,n);if(!r?.alive)return`No running session "${n}".`;r.kill("SIGTERM");let s=We(t,n),i=setTimeout(()=>{this.killTimers.delete(i);let a=this.getSession(t,n);a?.alive&&a.kill("SIGKILL")},5e3);return this.killTimers.add(i),`SIGTERM sent to "${n}". SIGKILL in 5s if still running.`}kill(t,n){let o=Ze(n);if(o)return o;let r=this.getSession(t,n);return r?.alive?(r.kill("SIGKILL"),`SIGKILL sent to "${n}".`):`No running session "${n}".`}rm(t,n){let o=Ze(n);if(o)return o;if(this.getSession(t,n)?.alive)return`Session "${n}" is still running. /apps stop ${n} first, then /apps rm ${n}.`;this.removeSessionRecord(t,n),this.muted.delete(We(t,n)),this.rawMode.delete(We(t,n)),this.passwordHintSent.delete(We(t,n));let s=this.logPath(t,n);try{Mn.rmSync(s,{force:!0})}catch{}return`Removed "${n}" metadata and log.`}};ye();j();import ux from"better-sqlite3";var Gf=3,In=null;function qf(e){try{let t=JSON.parse(e);return Array.isArray(t)?t.map(String):[]}catch{return[]}}function zf(e){return{id:String(e.id),parentId:e.parent_id!=null?String(e.parent_id):null,title:String(e.title),plan:String(e.plan),assignee:e.assignee!=null?String(e.assignee):null,status:String(e.status),dependsOn:qf(String(e.depends_on??"[]")),blocks:qf(String(e.blocks??"[]")),priority:Number(e.priority)||0,resourceProfile:String(e.resource_profile||"standard")||"standard",createdAtMs:Number(e.created_at_ms),startedAtMs:e.started_at_ms!=null?Number(e.started_at_ms):null,completedAtMs:e.completed_at_ms!=null?Number(e.completed_at_ms):null,reportPath:e.report_path!=null?String(e.report_path):null,supervisorVerdict:e.supervisor_verdict??null,supervisorNotes:String(e.supervisor_notes??""),reworkCount:Number(e.rework_count)||0,ownerPeerKey:String(e.owner_peer_key),workDir:String(e.work_dir)}}function Kf(){D(Jn);let e=new ux(Ed);e.pragma("journal_mode = WAL"),e.exec(`
246
247
  CREATE TABLE IF NOT EXISTS board_meta (
247
248
  key TEXT PRIMARY KEY,
248
249
  value TEXT NOT NULL
@@ -320,7 +321,7 @@ ${n.join(`
320
321
  status TEXT NOT NULL DEFAULT 'pending',
321
322
  created_at_ms INTEGER NOT NULL
322
323
  );
323
- `);let t=e.prepare("SELECT value FROM board_meta WHERE key = 'schema_version'").get(),n=t?Number(t.value):0;return n<Bf&&(n<2&&e.exec(`
324
+ `);let t=e.prepare("SELECT value FROM board_meta WHERE key = 'schema_version'").get(),n=t?Number(t.value):0;return n<Gf&&(n<2&&e.exec(`
324
325
  CREATE TABLE IF NOT EXISTS board_job_progress (
325
326
  job_id TEXT PRIMARY KEY,
326
327
  completed_step_ids TEXT NOT NULL DEFAULT '[]',
@@ -359,7 +360,7 @@ ${n.join(`
359
360
  status TEXT NOT NULL DEFAULT 'pending',
360
361
  created_at_ms INTEGER NOT NULL
361
362
  );
362
- `),e.prepare("INSERT OR REPLACE INTO board_meta (key, value) VALUES ('schema_version', ?)").run(String(Bf))),e}function Ue(){In||(In=Jf())}function Gf(){if(In){try{In.close()}catch{}In=null}}function xe(){return In||(In=Jf()),In}function qf(e){xe().prepare(`INSERT INTO board_job (
363
+ `),e.prepare("INSERT OR REPLACE INTO board_meta (key, value) VALUES ('schema_version', ?)").run(String(Gf))),e}function Ue(){In||(In=Kf())}function Yf(){if(In){try{In.close()}catch{}In=null}}function xe(){return In||(In=Kf()),In}function Vf(e){xe().prepare(`INSERT INTO board_job (
363
364
  id, parent_id, title, plan, assignee, status, depends_on, blocks, priority,
364
365
  resource_profile, created_at_ms, started_at_ms, completed_at_ms, report_path,
365
366
  supervisor_verdict, supervisor_notes, rework_count, owner_peer_key, work_dir
@@ -374,47 +375,47 @@ ${n.join(`
374
375
  completed_at_ms = @completedAtMs, report_path = @reportPath,
375
376
  supervisor_verdict = @supervisorVerdict, supervisor_notes = @supervisorNotes,
376
377
  rework_count = @reworkCount, work_dir = @workDir
377
- WHERE id = @id`).run({id:e.id,parentId:e.parentId,title:e.title,plan:e.plan,assignee:e.assignee,status:e.status,dependsOn:JSON.stringify(e.dependsOn),blocks:JSON.stringify(e.blocks),priority:e.priority,resourceProfile:e.resourceProfile,startedAtMs:e.startedAtMs,completedAtMs:e.completedAtMs,reportPath:e.reportPath,supervisorVerdict:e.supervisorVerdict,supervisorNotes:e.supervisorNotes,reworkCount:e.reworkCount,workDir:e.workDir})}function Be(e){let t=xe().prepare("SELECT * FROM board_job WHERE id = ?").get(e);return t?Hf(t):null}function et(e){let t="SELECT * FROM board_job WHERE 1=1",n={};return e?.status&&(t+=" AND status = @status",n.status=e.status),e?.ownerPeerKey&&(t+=" AND owner_peer_key = @ownerPeerKey",n.ownerPeerKey=e.ownerPeerKey),t+=" ORDER BY priority DESC, created_at_ms ASC",xe().prepare(t).all(n).map(Hf)}function Ht(e,t,n){xe().prepare("INSERT INTO board_audit (job_id, at_ms, event, detail) VALUES (?, ?, ?, ?)").run(e,Date.now(),t,n.slice(0,4e3))}function zf(e,t){xe().prepare("INSERT OR REPLACE INTO board_slot (job_id, acquired_at_ms, profile) VALUES (?, ?, ?)").run(e,Date.now(),t)}function Go(e){xe().prepare("DELETE FROM board_slot WHERE job_id = ?").run(e)}function Kf(e){let t=xe().prepare("SELECT COUNT(*) AS c FROM board_slot WHERE profile = ?").get(e);return Number(t.c)||0}function Yf(){let e=xe().prepare("SELECT COUNT(*) AS c FROM board_slot").get();return Number(e.c)||0}function Vf(e){let t=xe().prepare("SELECT completed_step_ids FROM board_job_progress WHERE job_id = ?").get(e);if(!t)return[];try{let n=JSON.parse(t.completed_step_ids);return Array.isArray(n)?n.map(String):[]}catch{return[]}}function Jr(e,t){xe().prepare(`INSERT OR REPLACE INTO board_job_progress (job_id, completed_step_ids, updated_at_ms)
378
- VALUES (?, ?, ?)`).run(e,JSON.stringify(t),Date.now())}import qi from"node:fs";import zi from"node:path";import ox from"node:os";import*as Xf from"node-pty";function rx(e,t){return e.length<=t?e:`${e.slice(0,t)}
379
- [...truncated]`}function sx(e){if(e===void 0||e===0)return null;let t=ox.constants.signals;for(let[n,o]of Object.entries(t))if(o===e)return n;return null}function Qf(e){try{process.platform==="win32"?e.kill():e.kill("SIGTERM")}catch{e.kill()}}function ct(e,t,n){return new Promise(o=>{let r=Date.now(),s=!1,i="",a=n.cwd,l=null,c=!1,u=null,d=null,m,f=y=>{if(c)return;c=!0,m!==void 0&&clearTimeout(m),u?.dispose(),u=null,o(y);let k=d;d=null,queueMicrotask(()=>k?.dispose())},g={...n.env??process.env,TERM:"xterm-256color",COLORTERM:"truecolor",...a?{PWD:a}:{}};try{l=Xf.spawn(e,["-c",t],{name:"xterm-256color",cols:120,rows:40,cwd:a,env:g})}catch(y){f({code:null,stdout:"",stderr:String(y),durationMs:Date.now()-r,timedOut:!1,signal:null});return}m=setTimeout(()=>{s=!0,l&&Qf(l)},n.timeoutMs),u=l.onData(y=>{i+=y,i.length>n.maxBytes&&(i=rx(i,n.maxBytes),l&&Qf(l))}),d=l.onExit(y=>{f({code:y.exitCode,stdout:i,stderr:"",durationMs:Date.now()-r,timedOut:s,signal:sx(y.signal)})})})}import Ai from"node:fs";import Ic from"node:path";var eh="omnish i -c '/sendto peer -t <title> -- <brief>'",th="omnish i -c '/sendto peer <filepath> -- <caption>'",Gr=["While working: send progress with exactly:",` ${eh}`,"(requires omnish run on this host). Do not send final deliverable files; the coordinator does that after review with:",` ${th}`,"On handoff: fill communicationSummary and handoffMessage in report.json."].join(`
380
- `),Zf=/\.(mp4|mov|webm|m4v|pdf|png|jpe?g|gif|zip|wav|mp3)$/i;function ix(e){return`'${e.replace(/'/g,"'\\''")}'`}function nh(e,t){let n=e.trim(),o=t.trim();if(!n)throw new Error("buildFileSendOmnishCommand: filepath required");let r=`/sendto peer ${n} -- ${o||"Deliverable"}`;return`omnish i -c ${ix(r)}`}function oh(e,t){return{OMNISH_JOB_OWNER_PEER:e.ownerPeerKey,OMNISH_STATUS_CMD:eh,OMNISH_FILE_SEND_CMD:t==="coordinator"?th:""}}function Oc(e){let t=e.toLowerCase();return/\bsendto\b/.test(t)||/\/sendto/.test(t)||/\bdistribut/.test(t)||/\battach\b/.test(t)}function rh(e,t){let n=[];for(let o of e.split(/\r?\n/)){let r=o.trim().match(/^CHECK:file:(.+)$/i);if(!r)continue;let s=r[1].trim(),i=Ic.isAbsolute(s)?s:Ic.join(t,s);Ai.existsSync(i)&&Zf.test(s)&&n.push(i)}if(n.length===0&&Oc(e)&&Ai.existsSync(t))for(let o of Ai.readdirSync(t)){let r=Ic.join(t,o);try{Ai.statSync(r).isFile()&&Zf.test(o)&&n.push(r)}catch{}}return n}function sh(e){return e.length===0?null:e.find(n=>/\.mp4$/i.test(n))??e[0]??null}import Jc from"node:fs";import Gc from"node:path";be();j();import{spawn as ax}from"node:child_process";import ih from"node:fs";import ah from"node:path";var lx=new Set(["PATH","HOME","USER","LOGNAME","SHELL","LANG","LC_ALL","LC_CTYPE","LC_MESSAGES","TMPDIR","TZ"]),cx=new Set(["OPENAI_API_KEY","ANTHROPIC_API_KEY","GOOGLE_API_KEY","GEMINI_API_KEY","MISTRAL_API_KEY","GROQ_API_KEY","COHERE_API_KEY","HUGGINGFACE_TOKEN","TOGETHER_API_KEY","FIREWORKS_API_KEY","PERPLEXITY_API_KEY","DEEPSEEK_API_KEY","XAI_API_KEY","CURSOR_API_KEY"]);function Lc(e,t){let n={OMNISH_PEER_KEY:e,OMNISH_CHAT_MESSAGE:t};for(let o of lx){let r=process.env[o];r!==void 0&&(n[o]=r)}for(let[o,r]of Object.entries(process.env))r&&(o.startsWith("OMNISH_")||cx.has(o))&&(n[o]=r);return n}var lh=new Map;function ux(e,t){let o=(lh.get(e)??Promise.resolve()).then(t).catch(r=>{P.warn({peerKey:e,err:String(r)},"chat LLM fallback queue task failed")});lh.set(e,o)}function dx(e){se();let t=e.chatLlmWorkDir.trim();if(t){let o=ah.resolve(t);return D(o),{cwd:o,cleanup:()=>{}}}let n=ih.mkdtempSync(ah.join(W,"chat-llm-"));return{cwd:n,cleanup:()=>{try{ih.rmSync(n,{recursive:!0,force:!0})}catch{}}}}function px(e,t){return e.length<=t?e:`${e.slice(0,t)}
381
- [...input truncated]`}function mx(e,t){let n=[],o=e.stdout.trimEnd(),r=e.stderr.trimEnd();return o&&n.push(o.length>t?`${o.slice(0,t)}
378
+ WHERE id = @id`).run({id:e.id,parentId:e.parentId,title:e.title,plan:e.plan,assignee:e.assignee,status:e.status,dependsOn:JSON.stringify(e.dependsOn),blocks:JSON.stringify(e.blocks),priority:e.priority,resourceProfile:e.resourceProfile,startedAtMs:e.startedAtMs,completedAtMs:e.completedAtMs,reportPath:e.reportPath,supervisorVerdict:e.supervisorVerdict,supervisorNotes:e.supervisorNotes,reworkCount:e.reworkCount,workDir:e.workDir})}function Be(e){let t=xe().prepare("SELECT * FROM board_job WHERE id = ?").get(e);return t?zf(t):null}function et(e){let t="SELECT * FROM board_job WHERE 1=1",n={};return e?.status&&(t+=" AND status = @status",n.status=e.status),e?.ownerPeerKey&&(t+=" AND owner_peer_key = @ownerPeerKey",n.ownerPeerKey=e.ownerPeerKey),t+=" ORDER BY priority DESC, created_at_ms ASC",xe().prepare(t).all(n).map(zf)}function Ht(e,t,n){xe().prepare("INSERT INTO board_audit (job_id, at_ms, event, detail) VALUES (?, ?, ?, ?)").run(e,Date.now(),t,n.slice(0,4e3))}function Qf(e,t){xe().prepare("INSERT OR REPLACE INTO board_slot (job_id, acquired_at_ms, profile) VALUES (?, ?, ?)").run(e,Date.now(),t)}function Go(e){xe().prepare("DELETE FROM board_slot WHERE job_id = ?").run(e)}function Xf(e){let t=xe().prepare("SELECT COUNT(*) AS c FROM board_slot WHERE profile = ?").get(e);return Number(t.c)||0}function Zf(){let e=xe().prepare("SELECT COUNT(*) AS c FROM board_slot").get();return Number(e.c)||0}function eh(e){let t=xe().prepare("SELECT completed_step_ids FROM board_job_progress WHERE job_id = ?").get(e);if(!t)return[];try{let n=JSON.parse(t.completed_step_ids);return Array.isArray(n)?n.map(String):[]}catch{return[]}}function Jr(e,t){xe().prepare(`INSERT OR REPLACE INTO board_job_progress (job_id, completed_step_ids, updated_at_ms)
379
+ VALUES (?, ?, ?)`).run(e,JSON.stringify(t),Date.now())}import zi from"node:fs";import Ki from"node:path";import dx from"node:os";import*as nh from"node-pty";function px(e,t){return e.length<=t?e:`${e.slice(0,t)}
380
+ [...truncated]`}function mx(e){if(e===void 0||e===0)return null;let t=dx.constants.signals;for(let[n,o]of Object.entries(t))if(o===e)return n;return null}function th(e){try{process.platform==="win32"?e.kill():e.kill("SIGTERM")}catch{e.kill()}}function ct(e,t,n){return new Promise(o=>{let r=Date.now(),s=!1,i="",a=n.cwd,l=null,c=!1,u=null,d=null,m,f=y=>{if(c)return;c=!0,m!==void 0&&clearTimeout(m),u?.dispose(),u=null,o(y);let b=d;d=null,queueMicrotask(()=>b?.dispose())},g={...n.env??process.env,TERM:"xterm-256color",COLORTERM:"truecolor",...a?{PWD:a}:{}};try{$i(),l=nh.spawn(e,["-c",t],{name:"xterm-256color",cols:120,rows:40,cwd:a,env:g})}catch(y){f({code:null,stdout:"",stderr:String(y),durationMs:Date.now()-r,timedOut:!1,signal:null});return}m=setTimeout(()=>{s=!0,l&&th(l)},n.timeoutMs),u=l.onData(y=>{i+=y,i.length>n.maxBytes&&(i=px(i,n.maxBytes),l&&th(l))}),d=l.onExit(y=>{f({code:y.exitCode,stdout:i,stderr:"",durationMs:Date.now()-r,timedOut:s,signal:mx(y.signal)})})})}import Ii from"node:fs";import Nc from"node:path";var rh="omnish i -c '/sendto peer -t <title> -- <brief>'",sh="omnish i -c '/sendto peer <filepath> -- <caption>'",Gr=["While working: send progress with exactly:",` ${rh}`,"(requires omnish run on this host). Do not send final deliverable files; the coordinator does that after review with:",` ${sh}`,"On handoff: fill communicationSummary and handoffMessage in report.json."].join(`
381
+ `),oh=/\.(mp4|mov|webm|m4v|pdf|png|jpe?g|gif|zip|wav|mp3)$/i;function fx(e){return`'${e.replace(/'/g,"'\\''")}'`}function ih(e,t){let n=e.trim(),o=t.trim();if(!n)throw new Error("buildFileSendOmnishCommand: filepath required");let r=`/sendto peer ${n} -- ${o||"Deliverable"}`;return`omnish i -c ${fx(r)}`}function ah(e,t){return{OMNISH_JOB_OWNER_PEER:e.ownerPeerKey,OMNISH_STATUS_CMD:rh,OMNISH_FILE_SEND_CMD:t==="coordinator"?sh:""}}function _c(e){let t=e.toLowerCase();return/\bsendto\b/.test(t)||/\/sendto/.test(t)||/\bdistribut/.test(t)||/\battach\b/.test(t)}function lh(e,t){let n=[];for(let o of e.split(/\r?\n/)){let r=o.trim().match(/^CHECK:file:(.+)$/i);if(!r)continue;let s=r[1].trim(),i=Nc.isAbsolute(s)?s:Nc.join(t,s);Ii.existsSync(i)&&oh.test(s)&&n.push(i)}if(n.length===0&&_c(e)&&Ii.existsSync(t))for(let o of Ii.readdirSync(t)){let r=Nc.join(t,o);try{Ii.statSync(r).isFile()&&oh.test(o)&&n.push(r)}catch{}}return n}function ch(e){return e.length===0?null:e.find(n=>/\.mp4$/i.test(n))??e[0]??null}import zc from"node:fs";import Kc from"node:path";ye();j();import{spawn as hx}from"node:child_process";import uh from"node:fs";import dh from"node:path";var gx=new Set(["PATH","HOME","USER","LOGNAME","SHELL","LANG","LC_ALL","LC_CTYPE","LC_MESSAGES","TMPDIR","TZ"]),yx=new Set(["OPENAI_API_KEY","ANTHROPIC_API_KEY","GOOGLE_API_KEY","GEMINI_API_KEY","MISTRAL_API_KEY","GROQ_API_KEY","COHERE_API_KEY","HUGGINGFACE_TOKEN","TOGETHER_API_KEY","FIREWORKS_API_KEY","PERPLEXITY_API_KEY","DEEPSEEK_API_KEY","XAI_API_KEY","CURSOR_API_KEY"]);function Fc(e,t){let n={OMNISH_PEER_KEY:e,OMNISH_CHAT_MESSAGE:t};for(let o of gx){let r=process.env[o];r!==void 0&&(n[o]=r)}for(let[o,r]of Object.entries(process.env))r&&(o.startsWith("OMNISH_")||yx.has(o))&&(n[o]=r);return n}var ph=new Map;function wx(e,t){let o=(ph.get(e)??Promise.resolve()).then(t).catch(r=>{E.warn({peerKey:e,err:String(r)},"chat LLM fallback queue task failed")});ph.set(e,o)}function bx(e){se();let t=e.chatLlmWorkDir.trim();if(t){let o=dh.resolve(t);return D(o),{cwd:o,cleanup:()=>{}}}let n=uh.mkdtempSync(dh.join(W,"chat-llm-"));return{cwd:n,cleanup:()=>{try{uh.rmSync(n,{recursive:!0,force:!0})}catch{}}}}function kx(e,t){return e.length<=t?e:`${e.slice(0,t)}
382
+ [...input truncated]`}function Sx(e,t){let n=[],o=e.stdout.trimEnd(),r=e.stderr.trimEnd();return o&&n.push(o.length>t?`${o.slice(0,t)}
382
383
  [...truncated]`:o),r&&(n.push("\u2014 stderr \u2014"),n.push(r.length>t?`${r.slice(0,t)}
383
384
  [...truncated]`:r)),n.length===0&&n.push("(no output)"),e.timedOut?n.push(`(timed out after ${Math.round(e.durationMs/1e3)}s)`):e.code!==0&&e.code!==null?n.push(`(exit ${e.code})`):e.signal&&n.push(`(signal ${e.signal})`),n.join(`
384
- `)}function Ii(e){let t=e&&typeof e=="object"&&"code"in e?String(e.code):"";return t==="EPIPE"||t==="EOF"}function ch(e){try{e?.stdin?.end()}catch(t){if(!Ii(t))throw t}}function fx(e,t,n,o){return new Promise(r=>{let s=Date.now(),i=!1,a="",l="",c=!1,u=null,d,m=k=>{c||(c=!0,clearTimeout(d),r(k))},f=o.maxOutChars,h=(k,b)=>{k==="out"?a+=b:l+=b;let x=a.length+l.length;if(x>f){let C=x-f;if(l.length>=C)l=`${l.slice(0,Math.max(0,l.length-C))}
385
+ `)}function Oi(e){let t=e&&typeof e=="object"&&"code"in e?String(e.code):"";return t==="EPIPE"||t==="EOF"}function mh(e){try{e?.stdin?.end()}catch(t){if(!Oi(t))throw t}}function vx(e,t,n,o){return new Promise(r=>{let s=Date.now(),i=!1,a="",l="",c=!1,u=null,d,m=b=>{c||(c=!0,clearTimeout(d),r(b))},f=o.maxOutChars,h=(b,k)=>{b==="out"?a+=k:l+=k;let x=a.length+l.length;if(x>f){let C=x-f;if(l.length>=C)l=`${l.slice(0,Math.max(0,l.length-C))}
385
386
  [...truncated]`;else{let M=C-l.length;l="",a=`${a.slice(0,Math.max(0,a.length-M))}
386
- [...truncated]`}try{u?.kill("SIGTERM")}catch{}}};try{u=ax(e,["-c",t],{cwd:o.cwd,env:o.env,stdio:["pipe","pipe","pipe"]})}catch(k){m({code:null,stdout:"",stderr:String(k),durationMs:Date.now()-s,timedOut:!1,signal:null});return}d=setTimeout(()=>{i=!0;try{u?.kill("SIGTERM")}catch{}},o.timeoutMs),u.stdout?.setEncoding("utf8"),u.stderr?.setEncoding("utf8"),u.stdout?.on("data",k=>h("out",k)),u.stderr?.on("data",k=>h("err",k));let g=u.stdin;g&&g.on("error",k=>{Ii(k)||m({code:null,stdout:a,stderr:`${l}
387
- ${String(k)}`,durationMs:Date.now()-s,timedOut:i,signal:null})}),(()=>{if(!(!g||c))try{g.write(n,"utf8",k=>{if(k&&!Ii(k)){try{u?.kill("SIGTERM")}catch{}m({code:null,stdout:a,stderr:`${l}
388
- ${String(k)}`,durationMs:Date.now()-s,timedOut:i,signal:null});return}ch(u)})}catch(k){if(!Ii(k)){m({code:null,stdout:a,stderr:`${l}
389
- ${String(k)}`,durationMs:Date.now()-s,timedOut:i,signal:null});return}ch(u)}})(),u.on("error",k=>{m({code:null,stdout:a,stderr:`${l}
390
- ${String(k)}`,durationMs:Date.now()-s,timedOut:i,signal:null})}),u.on("close",(k,b)=>{m({code:k,stdout:a,stderr:l,durationMs:Date.now()-s,timedOut:i,signal:b??null})})})}async function hx(e,t,n,o){let r=e.chatLlmShellCommand.trim();if(!r){await o("(chat LLM fallback: chatLlmShellCommand is empty)");return}let{cwd:s,cleanup:i}=dx(e);try{let a=px(n,e.chatLlmMaxInputChars),l=Lc(t,a),c=e.chatLlmMaxOutputChars,u;e.chatLlmNeedsTty?u=await ct(e.shell,r,{cwd:s,timeoutMs:e.chatLlmTimeoutMs,maxBytes:c,env:l}):u=await fx(e.shell,r,a,{cwd:s,timeoutMs:e.chatLlmTimeoutMs,maxOutChars:c,env:l});let d=mx(u,c);await o(d)}catch(a){P.warn({peerKey:t,err:String(a)},"chat LLM fallback run failed"),await o(`(assistant error) ${String(a)}`)}finally{i()}}function qo(e,t,n,o){P.info({peerKey:t,len:n.length},"chat LLM fallback enqueued"),ux(t,async()=>{await hx(e,t,n,o),P.info({peerKey:t},"chat LLM fallback completed")})}j();import Vo from"node:fs";import zr from"node:path";function uh(e){Ue();let t=e.startedAtMs!=null&&e.completedAtMs!=null?e.completedAtMs-e.startedAtMs:null;xe().prepare(`INSERT INTO board_performance (
387
+ [...truncated]`}try{u?.kill("SIGTERM")}catch{}}};try{u=hx(e,["-c",t],{cwd:o.cwd,env:o.env,stdio:["pipe","pipe","pipe"]})}catch(b){m({code:null,stdout:"",stderr:String(b),durationMs:Date.now()-s,timedOut:!1,signal:null});return}d=setTimeout(()=>{i=!0;try{u?.kill("SIGTERM")}catch{}},o.timeoutMs),u.stdout?.setEncoding("utf8"),u.stderr?.setEncoding("utf8"),u.stdout?.on("data",b=>h("out",b)),u.stderr?.on("data",b=>h("err",b));let g=u.stdin;g&&g.on("error",b=>{Oi(b)||m({code:null,stdout:a,stderr:`${l}
388
+ ${String(b)}`,durationMs:Date.now()-s,timedOut:i,signal:null})}),(()=>{if(!(!g||c))try{g.write(n,"utf8",b=>{if(b&&!Oi(b)){try{u?.kill("SIGTERM")}catch{}m({code:null,stdout:a,stderr:`${l}
389
+ ${String(b)}`,durationMs:Date.now()-s,timedOut:i,signal:null});return}mh(u)})}catch(b){if(!Oi(b)){m({code:null,stdout:a,stderr:`${l}
390
+ ${String(b)}`,durationMs:Date.now()-s,timedOut:i,signal:null});return}mh(u)}})(),u.on("error",b=>{m({code:null,stdout:a,stderr:`${l}
391
+ ${String(b)}`,durationMs:Date.now()-s,timedOut:i,signal:null})}),u.on("close",(b,k)=>{m({code:b,stdout:a,stderr:l,durationMs:Date.now()-s,timedOut:i,signal:k??null})})})}async function xx(e,t,n,o){let r=e.chatLlmShellCommand.trim();if(!r){await o("(chat LLM fallback: chatLlmShellCommand is empty)");return}let{cwd:s,cleanup:i}=bx(e);try{let a=kx(n,e.chatLlmMaxInputChars),l=Fc(t,a),c=e.chatLlmMaxOutputChars,u;e.chatLlmNeedsTty?u=await ct(e.shell,r,{cwd:s,timeoutMs:e.chatLlmTimeoutMs,maxBytes:c,env:l}):u=await vx(e.shell,r,a,{cwd:s,timeoutMs:e.chatLlmTimeoutMs,maxOutChars:c,env:l});let d=Sx(u,c);await o(d)}catch(a){E.warn({peerKey:t,err:String(a)},"chat LLM fallback run failed"),await o(`(assistant error) ${String(a)}`)}finally{i()}}function qo(e,t,n,o){E.info({peerKey:t,len:n.length},"chat LLM fallback enqueued"),wx(t,async()=>{await xx(e,t,n,o),E.info({peerKey:t},"chat LLM fallback completed")})}j();import Vo from"node:fs";import zr from"node:path";function fh(e){Ue();let t=e.startedAtMs!=null&&e.completedAtMs!=null?e.completedAtMs-e.startedAtMs:null;xe().prepare(`INSERT INTO board_performance (
391
392
  job_id, employee_id, started_at_ms, completed_at_ms,
392
393
  supervisor_verdict, rework_count, duration_ms, competency_failures, created_at_ms
393
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(e.jobId,e.employeeId,e.startedAtMs,e.completedAtMs,e.supervisorVerdict,e.reworkCount,t,JSON.stringify(e.competencyFailures),Date.now())}function dh(e,t=10){return Ue(),xe().prepare("SELECT * FROM board_performance WHERE employee_id = ? ORDER BY created_at_ms DESC LIMIT ?").all(e.toLowerCase(),t).map(o=>({id:Number(o.id),jobId:String(o.job_id),employeeId:String(o.employee_id),supervisorVerdict:String(o.supervisor_verdict),reworkCount:Number(o.rework_count)||0,durationMs:o.duration_ms!=null?Number(o.duration_ms):null,competencyFailures:gx(String(o.competency_failures??"[]")),createdAtMs:Number(o.created_at_ms)}))}function gx(e){try{let t=JSON.parse(e);return Array.isArray(t)?t.map(String):[]}catch{return[]}}function Oi(e){Ue();let t=xe().prepare(`SELECT AVG(rating) AS avg FROM board_feedback f
394
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(e.jobId,e.employeeId,e.startedAtMs,e.completedAtMs,e.supervisorVerdict,e.reworkCount,t,JSON.stringify(e.competencyFailures),Date.now())}function hh(e,t=10){return Ue(),xe().prepare("SELECT * FROM board_performance WHERE employee_id = ? ORDER BY created_at_ms DESC LIMIT ?").all(e.toLowerCase(),t).map(o=>({id:Number(o.id),jobId:String(o.job_id),employeeId:String(o.employee_id),supervisorVerdict:String(o.supervisor_verdict),reworkCount:Number(o.rework_count)||0,durationMs:o.duration_ms!=null?Number(o.duration_ms):null,competencyFailures:Cx(String(o.competency_failures??"[]")),createdAtMs:Number(o.created_at_ms)}))}function Cx(e){try{let t=JSON.parse(e);return Array.isArray(t)?t.map(String):[]}catch{return[]}}function Li(e){Ue();let t=xe().prepare(`SELECT AVG(rating) AS avg FROM board_feedback f
394
395
  JOIN board_performance p ON p.job_id = f.job_id
395
- WHERE p.employee_id = ? AND f.rating IS NOT NULL`).get(e.toLowerCase());return t?.avg==null?null:Number(t.avg)}import bx from"node:crypto";var Nc=`bash -lc 'echo "Set innerCommand in ~/.omnish/board/employees.json"'`,On="OMNISH_JOB_PLAN";function yx(e){return e.replaceAll("$OMNISH_TASK",`$${On}`).replaceAll("OMNISH_TASK",On)}function wx(e,t,n){let o=e.toLowerCase();return o==="claude"?t==="dangerous"&&n.recipesAllowDangerousBuiltins?`claude -p "$${On}" --allowedTools all --dangerously-skip-permissions`:`claude -p "$${On}"`:o==="codex"?`codex "$${On}"`:o==="gemini"?`gemini -p "$${On}"`:o==="cursor"?t==="yolo"||t==="dangerous"?`agent --yolo --force -p "$${On}"`:`agent -p "$${On}"`:null}function Li(e,t,n=t.boardAgentPermissionMode,o=""){let r=e.trim().toLowerCase();if(!r)return{ok:!1,error:"Agent recipe name is required."};let s=wx(r,n,t);if(s)return{ok:!0,command:s};let i=ze(o,t,r);if(!i)return{ok:!1,error:`Unknown agent recipe "${r}". Try claude, cursor, codex, or /run list.`};let a=yx(i.command);return r==="claude"&&n==="dangerous"&&!t.recipesAllowDangerousBuiltins&&(a=a.replace(/\s+--allowedTools\s+all/gi,"").replace(/\s+--dangerously-skip-permissions/gi,"")),r==="cursor"&&n==="yolo"&&!/\b--yolo\b/.test(a)&&(a=a.replace(/^agent\b/,"agent --yolo --force")),{ok:!0,command:a}}function ph(e){let t=e.trim();return t===Nc||t.includes("Set innerCommand")||t.includes("Configure innerCommand")}function _c(e,t){let n=e.boardPrimaryAgent.trim().toLowerCase();if(!n)return{ok:!1,error:"boardPrimaryAgent is not set. Use /board agent set <recipe>."};let o=Li(n,e,e.boardAgentPermissionMode,t);if(!o.ok)return o;let r=Pt(),s=e.boardApplyPrimaryAgentToRoles.length?e.boardApplyPrimaryAgentToRoles:["researcher","implementer","reviewer"],i=[];for(let a of s)r[a]&&(r[a]={...r[a],innerCommand:o.command},i.push(a));return i.length===0?{ok:!1,error:"No matching roles in employees.json to update."}:(qr(r),{ok:!0,updated:i})}j();import Ln from"node:fs";import Dc from"node:path";var Fc={maxConcurrentAgents:2,maxConcurrentHeavy:1,cpuMaxPercent:75,memMinFreeMb:2048,gpuMaxUtilPercent:90,dispatchIntervalMs:5e3};var kx=/^[a-z0-9][a-z0-9_-]{0,31}$/i;function Sx(){return`job-${bx.randomBytes(4).toString("hex")}`}function Wc(e){let t=e.trim().toLowerCase();return kx.test(t)?null:"Job id must be 1\u201332 chars: letters, digits, _ or -."}function vx(e){return Dc.join(Rd,e)}function xx(e){Ln.mkdirSync(e,{recursive:!0,mode:448})}function Ni(){try{let e=Ln.readFileSync(Cs,"utf8"),t=JSON.parse(e);return{...Fc,...t}}catch{return{...Fc}}}function Uc(e){let t=(e.innerCommand??e.command??"").trim();return{...e,innerCommand:t||void 0,command:void 0}}function mh(e){try{for(let t of Ln.readdirSync(bt,{withFileTypes:!0})){if(!t.isDirectory())continue;let n=t.name;if(!e[n]?.innerCommand)try{let o=JSON.parse(Ln.readFileSync(Dc.join(bt,n,"profile.json"),"utf8"));e[n]={label:n,role:o.role,description:o.description,innerCommand:e[n]?.innerCommand}}catch{}}}catch{}return e}function Pt(){try{let e=Ln.readFileSync(Gn,"utf8"),t=JSON.parse(e),n=t.employees&&typeof t.employees=="object"?t.employees:{},o={};for(let[r,s]of Object.entries(n)){let i=Uc(s);i.innerCommand&&(o[r]=i)}return mh(o)}catch{return mh({})}}function qr(e){Ln.mkdirSync(Dc.dirname(Gn),{recursive:!0,mode:448}),Ln.writeFileSync(Gn,JSON.stringify({employees:e},null,2)+`
396
- `,{mode:384})}function _i(e){Ue();let t=(e.id??Sx()).toLowerCase(),n=Wc(t);if(n)throw new Error(n);if(Be(t))throw new Error(`Job "${t}" already exists.`);let o=vx(t);xx(o);let r={id:t,parentId:e.parentId??null,title:e.title.trim(),plan:e.plan.trim(),assignee:e.assignee?.trim().toLowerCase()??null,status:"pending",dependsOn:e.dependsOn??[],blocks:e.blocks??[],priority:e.priority??0,resourceProfile:e.resourceProfile??"standard",createdAtMs:Date.now(),startedAtMs:null,completedAtMs:null,reportPath:null,supervisorVerdict:null,supervisorNotes:"",reworkCount:0,ownerPeerKey:e.ownerPeerKey,workDir:o};return qf(r),Ht(t,"created",e.title),r}function Jt(e,t,n=""){return e.status=t,jt(e),Ht(e.id,`status:${t}`,n),e}function zo(e){if(e.dependsOn.length===0)return!0;for(let t of e.dependsOn){let n=Be(t);if(!n||n.status!=="done")return!1}return!0}function hh(){let e=0;for(let t of et({status:"blocked"}))zo(t)&&(Jt(t,"pending","dependencies satisfied"),e+=1);return e}function gh(){let e=et().filter(t=>(t.status==="pending"||t.status==="assigned"||t.status==="rework")&&zo(t)&&t.assignee);return e.length===0?et().filter(n=>(n.status==="pending"||n.status==="rework")&&zo(n)&&!n.assignee).sort((n,o)=>o.priority-n.priority)[0]??null:e.sort((t,n)=>n.priority-t.priority)[0]??null}function Cx(){return"awaiting supervisor"}function fh(e,t){let n=e.title.length>36?`${e.title.slice(0,33)}\u2026`:e.title;return` \xB7 ${e.id} \xB7 ${e.assignee??"?"} \xB7 "${n}" \xB7 ${t}`}function Fi(){let e=et(),t=new Map;for(let r of e)t.set(r.status,(t.get(r.status)??0)+1);let n=["Job board"];if(e.length===0)return n.push("(empty)"),n.join(`
397
- `);for(let[r,s]of[...t.entries()].sort((i,a)=>i[0].localeCompare(a[0]))){if(n.push(` ${r}: ${s}`),r==="review")for(let i of e.filter(a=>a.status==="review").slice(0,8))n.push(fh(i,Cx()));if(r==="pending"||r==="rework")for(let i of e.filter(a=>a.status===r).slice(0,5))n.push(fh(i,r==="rework"?`rework ${i.reworkCount}`:"queued"))}let o=e.filter(r=>r.status==="running");for(let r of o.slice(0,5))n.push(` \u2192 ${r.id} ${r.assignee??"?"} ${r.title.slice(0,40)}`);return n.join(`
398
- `)}function Bc(e){let t=[`Job ${e.id}`,` title: ${e.title}`,` status: ${e.status}`,` assignee: ${e.assignee??"(unassigned)"}`,` plan: ${e.plan.slice(0,200)}${e.plan.length>200?"\u2026":""}`,` deps: ${e.dependsOn.length?e.dependsOn.join(", "):"none"}`,` blocks: ${e.blocks.length?e.blocks.join(", "):"none"}`,` profile: ${e.resourceProfile}`,` rework: ${e.reworkCount}`,` workDir: ${e.workDir}`];return e.reportPath&&t.push(` report: ${e.reportPath}`),e.supervisorNotes&&t.push(` supervisor: ${e.supervisorNotes.slice(0,120)}`),t.join(`
399
- `)}function Di(){try{Ln.accessSync(Gn);return}catch{}let e=Nc;qr({researcher:{label:"Researcher",description:"Inner agent only \u2014 omnish harness adds plan, phases, and notify.",innerCommand:e},implementer:{label:"Implementer",description:"Inner agent only \u2014 omnish harness adds plan, phases, and notify.",innerCommand:e},reviewer:{label:"Reviewer",description:"Inner agent only \u2014 omnish harness adds plan, phases, and notify.",innerCommand:e}})}j();import Hc from"node:fs";import vh from"node:path";import yh from"node:fs";import Rx from"node:path";var io=["plan","implement","execute","test","review"],Tx="work-plan.json";function Ko(e){return Rx.join(e,Tx)}function ln(e){try{let t=yh.readFileSync(Ko(e),"utf8");return JSON.parse(t)}catch{return null}}function Wi(e,t){yh.writeFileSync(Ko(e),JSON.stringify(t,null,2)+`
400
- `,{mode:384})}function wh(e,t){return[{id:"communicate-plan",phase:"plan",title:"Confirm understanding with coordinator communication brief",completed:!1},{id:"plan-draft",phase:"plan",title:"Draft step-by-step plan (implement, execute, test, review)",completed:!1},{id:"plan-structure",phase:"plan",title:"Review overall structure before implementation",completed:!1},{id:"implement",phase:"implement",title:"Implementation",completed:!1},{id:"execute",phase:"execute",title:"Execution / integration",completed:!1},{id:"test",phase:"test",title:"Testing and verification",completed:!1},{id:"review-final",phase:"review",title:"Final self-review and outcome check",completed:!1},{id:"handoff",phase:"review",title:"Mark all steps complete and prepare handoff",completed:!1}]}function bh(e){let t=[];if(!e)return t.push({name:"work-plan.json",ok:!1,detail:"missing or invalid"}),t;if(t.push({name:"workPlan.jobId",ok:typeof e.jobId=="string"&&e.jobId.length>0,detail:e.jobId}),!Array.isArray(e.steps)||e.steps.length===0)return t.push({name:"workPlan.steps",ok:!1,detail:"no steps defined"}),t;for(let r of io){let s=e.steps.filter(i=>i.phase===r);t.push({name:`workPlan.phase.${r}`,ok:s.length>0,detail:s.length>0?`${s.length} step(s)`:"missing phase"})}let n=e.steps.filter(r=>!r.completed);t.push({name:"workPlan.allStepsCompleted",ok:n.length===0,detail:n.length===0?"all complete":`incomplete: ${n.map(r=>r.id).join(", ")}`});let o=new Set(["implement","execute","test","review-final"]);for(let r of e.steps){if(!o.has(r.id))continue;let s=typeof r.selfReviewNotes=="string"&&r.selfReviewNotes.trim().length>0;r.completed&&!s&&t.push({name:`workPlan.selfReview.${r.id}`,ok:!1,detail:"completed without selfReviewNotes"})}return t.push({name:"workPlan.overallStructureReview",ok:typeof e.overallStructureReview=="string"&&e.overallStructureReview.trim().length>0,detail:"structure review before major work"}),t.push({name:"workPlan.readyForHandoff",ok:e.readyForHandoff===!0,detail:e.readyForHandoff?"yes":"not ready"}),t.push({name:"workPlan.outcomeStatement",ok:typeof e.outcomeStatement=="string"&&e.outcomeStatement.trim().length>0,detail:"specialist owns outcome"}),t}var jc=[{id:"fivePhases",label:"Five phases (plan \u2192 implement \u2192 execute \u2192 test \u2192 review)",checkPrefixes:["workPlan.phase."]},{id:"planBeforeWork",label:"Plan before work (work-plan.json seeded and completed)",checkPrefixes:["work-plan.json","workPlan.allStepsCompleted"]},{id:"structureReview",label:"Overall structure review before implementation",checkPrefixes:["workPlan.overallStructureReview"]},{id:"selfReviewMajorSteps",label:"Self-review before major steps",checkPrefixes:["workPlan.selfReview."]},{id:"outcomeOwnership",label:"Outcome ownership (outcomeStatement + readyForHandoff)",checkPrefixes:["workPlan.outcomeStatement","workPlan.readyForHandoff"]},{id:"structuredReport",label:"Structured report (report.json, planFollowed)",checkPrefixes:["report.","planFollowed"]},{id:"progressVisibility",label:"Progress visibility (phase and status notifications)",checkPrefixes:[]},{id:"clearCommunication",label:"Clear communication (coordinator brief + handoff message in report.json)",checkPrefixes:["communicationSummary","handoffMessage"]},{id:"liveStatusUpdates",label:"Live status (omnish i -c '/sendto peer -t <title> -- <brief>' while running; requires omnish run)",checkPrefixes:[]},{id:"coordinatorDeliversFiles",label:"Coordinator file delivery (omnish i -c '/sendto peer <filepath> -- <caption>' after supervisor pass)",checkPrefixes:[]}],kh=io.join(",");function Ex(e){for(let t of jc)if(t.checkPrefixes.some(n=>e===n||e.startsWith(n)))return t.label;return e.startsWith("workPlan.")?"Work plan discipline":null}function Sh(e){let t=new Set;for(let n of e){let o=Ex(n);o&&t.add(o)}return t.size>0?[...t].join("; "):"Core competency checks failed"}function xh(e){return vh.join(bt,e.toLowerCase(),"skills.json")}function Px(){return jc.map(e=>({id:e.id,label:e.label,source:"core"}))}function Yo(e){try{let t=Hc.readFileSync(xh(e),"utf8");return JSON.parse(t)}catch{return null}}function Ch(e){let t=vh.join(bt,e.employeeId.toLowerCase());Hc.mkdirSync(t,{recursive:!0,mode:448}),Hc.writeFileSync(xh(e.employeeId),JSON.stringify(e,null,2)+`
401
- `,{mode:384})}function Ui(e,t){let n={employeeId:e.toLowerCase(),skills:[...Px()],roleContext:t};return Ch(n),n}function $x(e,t){let n=`${e} ${t}`.toLowerCase(),o=[],r=[{id:"research",label:"Research and analysis",words:["research","analyze","investigate"]},{id:"implement",label:"Implementation",words:["implement","build","code","develop"]},{id:"test",label:"Testing",words:["test","verify","qa"]},{id:"review",label:"Review",words:["review","audit"]},{id:"deploy",label:"Deployment",words:["deploy","release","ship"]}];for(let s of r)s.words.some(i=>n.includes(i))&&o.push({id:s.id,label:s.label,source:"learned"});return o}function Rh(e,t,n){let o=Yo(e)??Ui(e,n),r=new Set(o.skills.map(i=>i.id));return $x(t,n).filter(i=>!r.has(i.id))}function Th(e,t,n){let o=Yo(e)??Ui(e,n??""),r=new Set(o.skills.map(s=>s.id));for(let s of t)r.has(s.id)||(o.skills.push(s),r.add(s.id));return n&&(o.roleContext=n),Ch(o),o}function Eh(e){return e.skills.map(t=>t.label).join("; ")}function Nn(e){return zr.join(bt,e.toLowerCase())}function Ph(e){return zr.join(Nn(e),"profile.json")}function Qo(e){try{let t=Vo.readFileSync(Ph(e),"utf8");return JSON.parse(t)}catch{return null}}function Bi(e){let t=Nn(e.id);Vo.mkdirSync(t,{recursive:!0,mode:448}),Vo.writeFileSync(Ph(e.id),JSON.stringify(e,null,2)+`
402
- `,{mode:384})}function $h(e){let t=e.name.trim().toLowerCase(),n=Wc(t);if(n)throw new Error(n);if(!e.innerCommand.trim())throw new Error("innerCommand is required.");let o={id:t,role:e.role.trim(),description:e.description?.trim()??"",status:"onboarding",createdAtMs:Date.now(),onboardedBy:e.onboardedBy,satisfactionScore:null,jobCount:0};Bi(o),Ui(t,o.role);let r=Uc({innerCommand:e.innerCommand.trim(),label:e.label??t,description:e.description,role:o.role}),s=Pt();return s[t]=r,qr(s),o.status="active",Bi(o),{profile:o,entry:r}}function Mh(e){let t=Qo(e);if(!t)return`Employee not found: ${e}`;let n=Yo(e),o=dh(e,5),r=Oi(e),s=[`Employee ${t.id}`,` role: ${t.role}`,` status: ${t.status}`,` jobs: ${t.jobCount}`,` satisfaction avg: ${r!=null?r.toFixed(1):"n/a"}`,` skills: ${n?.skills.length??0}`];for(let i of o)s.push(` \xB7 ${i.jobId} ${i.supervisorVerdict} rework=${i.reworkCount}`);return s.join(`
403
- `)}function Ah(e){let t=e.toLowerCase(),n=zr.join(Nn(t),"proposed");if(!Vo.existsSync(n))return{ok:!1,error:"No proposed/ directory \u2014 run finetune first."};for(let o of["skills.json","flow.json"]){let r=zr.join(n,o);Vo.existsSync(r)&&Vo.copyFileSync(r,zr.join(Nn(t),o))}return{ok:!0}}async function Mx(e,t,n,o){let r=e.boardCoordinatorAgentCommand.trim();if(!r||!e.boardCoordinatorAgentEnabled)return{ok:!1,output:"coordinator agent disabled"};Jc.mkdirSync(o,{recursive:!0,mode:448});let s=Gc.join(o,"coordinator-prompt.txt");Jc.writeFileSync(s,t,"utf8");let i={...Lc(n,t),OMNISH_COORDINATOR_PROMPT_FILE:s,OMNISH_COORDINATOR_WORKDIR:o},a=`${r.replace(/"/g,'\\"')}`,l=await ct(e.shell,a,{timeoutMs:e.chatLlmTimeoutMs||e.syncTimeoutMs,maxBytes:e.syncMaxBytes,cwd:o,env:i}),c=`${l.stdout}
404
- ${l.stderr}`.trim();return{ok:(l.code??1)===0,output:c}}function Ax(e,t,n,o){let r=n.map(i=>({...i,source:"learned",evidence:`inferred from plan: ${o.slice(0,80)}`}));Th(e,r,t);let s=Gc.join(Nn(e),"research.md");Jc.writeFileSync(s,`# Role research (deterministic)
396
+ WHERE p.employee_id = ? AND f.rating IS NOT NULL`).get(e.toLowerCase());return t?.avg==null?null:Number(t.avg)}import Ex from"node:crypto";var Dc=`bash -lc 'echo "Set innerCommand in ~/.omnish/board/employees.json"'`,On="OMNISH_JOB_PLAN";function Rx(e){return e.replaceAll("$OMNISH_TASK",`$${On}`).replaceAll("OMNISH_TASK",On)}function Tx(e,t,n){let o=e.toLowerCase();return o==="claude"?t==="dangerous"&&n.recipesAllowDangerousBuiltins?`claude -p "$${On}" --allowedTools all --dangerously-skip-permissions`:`claude -p "$${On}"`:o==="codex"?`codex "$${On}"`:o==="gemini"?`gemini -p "$${On}"`:o==="cursor"?t==="yolo"||t==="dangerous"?`agent --yolo --force -p "$${On}"`:`agent -p "$${On}"`:null}function Ni(e,t,n=t.boardAgentPermissionMode,o=""){let r=e.trim().toLowerCase();if(!r)return{ok:!1,error:"Agent recipe name is required."};let s=Tx(r,n,t);if(s)return{ok:!0,command:s};let i=ze(o,t,r);if(!i)return{ok:!1,error:`Unknown agent recipe "${r}". Try claude, cursor, codex, or /run list.`};let a=Rx(i.command);return r==="claude"&&n==="dangerous"&&!t.recipesAllowDangerousBuiltins&&(a=a.replace(/\s+--allowedTools\s+all/gi,"").replace(/\s+--dangerously-skip-permissions/gi,"")),r==="cursor"&&n==="yolo"&&!/\b--yolo\b/.test(a)&&(a=a.replace(/^agent\b/,"agent --yolo --force")),{ok:!0,command:a}}function gh(e){let t=e.trim();return t===Dc||t.includes("Set innerCommand")||t.includes("Configure innerCommand")}function Wc(e,t){let n=e.boardPrimaryAgent.trim().toLowerCase();if(!n)return{ok:!1,error:"boardPrimaryAgent is not set. Use /board agent set <recipe>."};let o=Ni(n,e,e.boardAgentPermissionMode,t);if(!o.ok)return o;let r=Pt(),s=e.boardApplyPrimaryAgentToRoles.length?e.boardApplyPrimaryAgentToRoles:["researcher","implementer","reviewer"],i=[];for(let a of s)r[a]&&(r[a]={...r[a],innerCommand:o.command},i.push(a));return i.length===0?{ok:!1,error:"No matching roles in employees.json to update."}:(qr(r),{ok:!0,updated:i})}j();import Ln from"node:fs";import Bc from"node:path";var Uc={maxConcurrentAgents:2,maxConcurrentHeavy:1,cpuMaxPercent:75,memMinFreeMb:2048,gpuMaxUtilPercent:90,dispatchIntervalMs:5e3};var Px=/^[a-z0-9][a-z0-9_-]{0,31}$/i;function $x(){return`job-${Ex.randomBytes(4).toString("hex")}`}function jc(e){let t=e.trim().toLowerCase();return Px.test(t)?null:"Job id must be 1\u201332 chars: letters, digits, _ or -."}function Mx(e){return Bc.join(Pd,e)}function Ax(e){Ln.mkdirSync(e,{recursive:!0,mode:448})}function _i(){try{let e=Ln.readFileSync(Cs,"utf8"),t=JSON.parse(e);return{...Uc,...t}}catch{return{...Uc}}}function Hc(e){let t=(e.innerCommand??e.command??"").trim();return{...e,innerCommand:t||void 0,command:void 0}}function yh(e){try{for(let t of Ln.readdirSync(bt,{withFileTypes:!0})){if(!t.isDirectory())continue;let n=t.name;if(!e[n]?.innerCommand)try{let o=JSON.parse(Ln.readFileSync(Bc.join(bt,n,"profile.json"),"utf8"));e[n]={label:n,role:o.role,description:o.description,innerCommand:e[n]?.innerCommand}}catch{}}}catch{}return e}function Pt(){try{let e=Ln.readFileSync(Gn,"utf8"),t=JSON.parse(e),n=t.employees&&typeof t.employees=="object"?t.employees:{},o={};for(let[r,s]of Object.entries(n)){let i=Hc(s);i.innerCommand&&(o[r]=i)}return yh(o)}catch{return yh({})}}function qr(e){Ln.mkdirSync(Bc.dirname(Gn),{recursive:!0,mode:448}),Ln.writeFileSync(Gn,JSON.stringify({employees:e},null,2)+`
397
+ `,{mode:384})}function Fi(e){Ue();let t=(e.id??$x()).toLowerCase(),n=jc(t);if(n)throw new Error(n);if(Be(t))throw new Error(`Job "${t}" already exists.`);let o=Mx(t);Ax(o);let r={id:t,parentId:e.parentId??null,title:e.title.trim(),plan:e.plan.trim(),assignee:e.assignee?.trim().toLowerCase()??null,status:"pending",dependsOn:e.dependsOn??[],blocks:e.blocks??[],priority:e.priority??0,resourceProfile:e.resourceProfile??"standard",createdAtMs:Date.now(),startedAtMs:null,completedAtMs:null,reportPath:null,supervisorVerdict:null,supervisorNotes:"",reworkCount:0,ownerPeerKey:e.ownerPeerKey,workDir:o};return Vf(r),Ht(t,"created",e.title),r}function Jt(e,t,n=""){return e.status=t,jt(e),Ht(e.id,`status:${t}`,n),e}function zo(e){if(e.dependsOn.length===0)return!0;for(let t of e.dependsOn){let n=Be(t);if(!n||n.status!=="done")return!1}return!0}function bh(){let e=0;for(let t of et({status:"blocked"}))zo(t)&&(Jt(t,"pending","dependencies satisfied"),e+=1);return e}function kh(){let e=et().filter(t=>(t.status==="pending"||t.status==="assigned"||t.status==="rework")&&zo(t)&&t.assignee);return e.length===0?et().filter(n=>(n.status==="pending"||n.status==="rework")&&zo(n)&&!n.assignee).sort((n,o)=>o.priority-n.priority)[0]??null:e.sort((t,n)=>n.priority-t.priority)[0]??null}function Ix(){return"awaiting supervisor"}function wh(e,t){let n=e.title.length>36?`${e.title.slice(0,33)}\u2026`:e.title;return` \xB7 ${e.id} \xB7 ${e.assignee??"?"} \xB7 "${n}" \xB7 ${t}`}function Di(){let e=et(),t=new Map;for(let r of e)t.set(r.status,(t.get(r.status)??0)+1);let n=["Job board"];if(e.length===0)return n.push("(empty)"),n.join(`
398
+ `);for(let[r,s]of[...t.entries()].sort((i,a)=>i[0].localeCompare(a[0]))){if(n.push(` ${r}: ${s}`),r==="review")for(let i of e.filter(a=>a.status==="review").slice(0,8))n.push(wh(i,Ix()));if(r==="pending"||r==="rework")for(let i of e.filter(a=>a.status===r).slice(0,5))n.push(wh(i,r==="rework"?`rework ${i.reworkCount}`:"queued"))}let o=e.filter(r=>r.status==="running");for(let r of o.slice(0,5))n.push(` \u2192 ${r.id} ${r.assignee??"?"} ${r.title.slice(0,40)}`);return n.join(`
399
+ `)}function Jc(e){let t=[`Job ${e.id}`,` title: ${e.title}`,` status: ${e.status}`,` assignee: ${e.assignee??"(unassigned)"}`,` plan: ${e.plan.slice(0,200)}${e.plan.length>200?"\u2026":""}`,` deps: ${e.dependsOn.length?e.dependsOn.join(", "):"none"}`,` blocks: ${e.blocks.length?e.blocks.join(", "):"none"}`,` profile: ${e.resourceProfile}`,` rework: ${e.reworkCount}`,` workDir: ${e.workDir}`];return e.reportPath&&t.push(` report: ${e.reportPath}`),e.supervisorNotes&&t.push(` supervisor: ${e.supervisorNotes.slice(0,120)}`),t.join(`
400
+ `)}function Wi(){try{Ln.accessSync(Gn);return}catch{}let e=Dc;qr({researcher:{label:"Researcher",description:"Inner agent only \u2014 omnish harness adds plan, phases, and notify.",innerCommand:e},implementer:{label:"Implementer",description:"Inner agent only \u2014 omnish harness adds plan, phases, and notify.",innerCommand:e},reviewer:{label:"Reviewer",description:"Inner agent only \u2014 omnish harness adds plan, phases, and notify.",innerCommand:e}})}j();import qc from"node:fs";import Th from"node:path";import Sh from"node:fs";import Ox from"node:path";var io=["plan","implement","execute","test","review"],Lx="work-plan.json";function Ko(e){return Ox.join(e,Lx)}function ln(e){try{let t=Sh.readFileSync(Ko(e),"utf8");return JSON.parse(t)}catch{return null}}function Ui(e,t){Sh.writeFileSync(Ko(e),JSON.stringify(t,null,2)+`
401
+ `,{mode:384})}function vh(e,t){return[{id:"communicate-plan",phase:"plan",title:"Confirm understanding with coordinator communication brief",completed:!1},{id:"plan-draft",phase:"plan",title:"Draft step-by-step plan (implement, execute, test, review)",completed:!1},{id:"plan-structure",phase:"plan",title:"Review overall structure before implementation",completed:!1},{id:"implement",phase:"implement",title:"Implementation",completed:!1},{id:"execute",phase:"execute",title:"Execution / integration",completed:!1},{id:"test",phase:"test",title:"Testing and verification",completed:!1},{id:"review-final",phase:"review",title:"Final self-review and outcome check",completed:!1},{id:"handoff",phase:"review",title:"Mark all steps complete and prepare handoff",completed:!1}]}function xh(e){let t=[];if(!e)return t.push({name:"work-plan.json",ok:!1,detail:"missing or invalid"}),t;if(t.push({name:"workPlan.jobId",ok:typeof e.jobId=="string"&&e.jobId.length>0,detail:e.jobId}),!Array.isArray(e.steps)||e.steps.length===0)return t.push({name:"workPlan.steps",ok:!1,detail:"no steps defined"}),t;for(let r of io){let s=e.steps.filter(i=>i.phase===r);t.push({name:`workPlan.phase.${r}`,ok:s.length>0,detail:s.length>0?`${s.length} step(s)`:"missing phase"})}let n=e.steps.filter(r=>!r.completed);t.push({name:"workPlan.allStepsCompleted",ok:n.length===0,detail:n.length===0?"all complete":`incomplete: ${n.map(r=>r.id).join(", ")}`});let o=new Set(["implement","execute","test","review-final"]);for(let r of e.steps){if(!o.has(r.id))continue;let s=typeof r.selfReviewNotes=="string"&&r.selfReviewNotes.trim().length>0;r.completed&&!s&&t.push({name:`workPlan.selfReview.${r.id}`,ok:!1,detail:"completed without selfReviewNotes"})}return t.push({name:"workPlan.overallStructureReview",ok:typeof e.overallStructureReview=="string"&&e.overallStructureReview.trim().length>0,detail:"structure review before major work"}),t.push({name:"workPlan.readyForHandoff",ok:e.readyForHandoff===!0,detail:e.readyForHandoff?"yes":"not ready"}),t.push({name:"workPlan.outcomeStatement",ok:typeof e.outcomeStatement=="string"&&e.outcomeStatement.trim().length>0,detail:"specialist owns outcome"}),t}var Gc=[{id:"fivePhases",label:"Five phases (plan \u2192 implement \u2192 execute \u2192 test \u2192 review)",checkPrefixes:["workPlan.phase."]},{id:"planBeforeWork",label:"Plan before work (work-plan.json seeded and completed)",checkPrefixes:["work-plan.json","workPlan.allStepsCompleted"]},{id:"structureReview",label:"Overall structure review before implementation",checkPrefixes:["workPlan.overallStructureReview"]},{id:"selfReviewMajorSteps",label:"Self-review before major steps",checkPrefixes:["workPlan.selfReview."]},{id:"outcomeOwnership",label:"Outcome ownership (outcomeStatement + readyForHandoff)",checkPrefixes:["workPlan.outcomeStatement","workPlan.readyForHandoff"]},{id:"structuredReport",label:"Structured report (report.json, planFollowed)",checkPrefixes:["report.","planFollowed"]},{id:"progressVisibility",label:"Progress visibility (phase and status notifications)",checkPrefixes:[]},{id:"clearCommunication",label:"Clear communication (coordinator brief + handoff message in report.json)",checkPrefixes:["communicationSummary","handoffMessage"]},{id:"liveStatusUpdates",label:"Live status (omnish i -c '/sendto peer -t <title> -- <brief>' while running; requires omnish run)",checkPrefixes:[]},{id:"coordinatorDeliversFiles",label:"Coordinator file delivery (omnish i -c '/sendto peer <filepath> -- <caption>' after supervisor pass)",checkPrefixes:[]}],Ch=io.join(",");function Nx(e){for(let t of Gc)if(t.checkPrefixes.some(n=>e===n||e.startsWith(n)))return t.label;return e.startsWith("workPlan.")?"Work plan discipline":null}function Rh(e){let t=new Set;for(let n of e){let o=Nx(n);o&&t.add(o)}return t.size>0?[...t].join("; "):"Core competency checks failed"}function Eh(e){return Th.join(bt,e.toLowerCase(),"skills.json")}function _x(){return Gc.map(e=>({id:e.id,label:e.label,source:"core"}))}function Yo(e){try{let t=qc.readFileSync(Eh(e),"utf8");return JSON.parse(t)}catch{return null}}function Ph(e){let t=Th.join(bt,e.employeeId.toLowerCase());qc.mkdirSync(t,{recursive:!0,mode:448}),qc.writeFileSync(Eh(e.employeeId),JSON.stringify(e,null,2)+`
402
+ `,{mode:384})}function Bi(e,t){let n={employeeId:e.toLowerCase(),skills:[..._x()],roleContext:t};return Ph(n),n}function Fx(e,t){let n=`${e} ${t}`.toLowerCase(),o=[],r=[{id:"research",label:"Research and analysis",words:["research","analyze","investigate"]},{id:"implement",label:"Implementation",words:["implement","build","code","develop"]},{id:"test",label:"Testing",words:["test","verify","qa"]},{id:"review",label:"Review",words:["review","audit"]},{id:"deploy",label:"Deployment",words:["deploy","release","ship"]}];for(let s of r)s.words.some(i=>n.includes(i))&&o.push({id:s.id,label:s.label,source:"learned"});return o}function $h(e,t,n){let o=Yo(e)??Bi(e,n),r=new Set(o.skills.map(i=>i.id));return Fx(t,n).filter(i=>!r.has(i.id))}function Mh(e,t,n){let o=Yo(e)??Bi(e,n??""),r=new Set(o.skills.map(s=>s.id));for(let s of t)r.has(s.id)||(o.skills.push(s),r.add(s.id));return n&&(o.roleContext=n),Ph(o),o}function Ah(e){return e.skills.map(t=>t.label).join("; ")}function Nn(e){return zr.join(bt,e.toLowerCase())}function Ih(e){return zr.join(Nn(e),"profile.json")}function Qo(e){try{let t=Vo.readFileSync(Ih(e),"utf8");return JSON.parse(t)}catch{return null}}function ji(e){let t=Nn(e.id);Vo.mkdirSync(t,{recursive:!0,mode:448}),Vo.writeFileSync(Ih(e.id),JSON.stringify(e,null,2)+`
403
+ `,{mode:384})}function Oh(e){let t=e.name.trim().toLowerCase(),n=jc(t);if(n)throw new Error(n);if(!e.innerCommand.trim())throw new Error("innerCommand is required.");let o={id:t,role:e.role.trim(),description:e.description?.trim()??"",status:"onboarding",createdAtMs:Date.now(),onboardedBy:e.onboardedBy,satisfactionScore:null,jobCount:0};ji(o),Bi(t,o.role);let r=Hc({innerCommand:e.innerCommand.trim(),label:e.label??t,description:e.description,role:o.role}),s=Pt();return s[t]=r,qr(s),o.status="active",ji(o),{profile:o,entry:r}}function Lh(e){let t=Qo(e);if(!t)return`Employee not found: ${e}`;let n=Yo(e),o=hh(e,5),r=Li(e),s=[`Employee ${t.id}`,` role: ${t.role}`,` status: ${t.status}`,` jobs: ${t.jobCount}`,` satisfaction avg: ${r!=null?r.toFixed(1):"n/a"}`,` skills: ${n?.skills.length??0}`];for(let i of o)s.push(` \xB7 ${i.jobId} ${i.supervisorVerdict} rework=${i.reworkCount}`);return s.join(`
404
+ `)}function Nh(e){let t=e.toLowerCase(),n=zr.join(Nn(t),"proposed");if(!Vo.existsSync(n))return{ok:!1,error:"No proposed/ directory \u2014 run finetune first."};for(let o of["skills.json","flow.json"]){let r=zr.join(n,o);Vo.existsSync(r)&&Vo.copyFileSync(r,zr.join(Nn(t),o))}return{ok:!0}}async function Dx(e,t,n,o){let r=e.boardCoordinatorAgentCommand.trim();if(!r||!e.boardCoordinatorAgentEnabled)return{ok:!1,output:"coordinator agent disabled"};zc.mkdirSync(o,{recursive:!0,mode:448});let s=Kc.join(o,"coordinator-prompt.txt");zc.writeFileSync(s,t,"utf8");let i={...Fc(n,t),OMNISH_COORDINATOR_PROMPT_FILE:s,OMNISH_COORDINATOR_WORKDIR:o},a=`${r.replace(/"/g,'\\"')}`,l=await ct(e.shell,a,{timeoutMs:e.chatLlmTimeoutMs||e.syncTimeoutMs,maxBytes:e.syncMaxBytes,cwd:o,env:i}),c=`${l.stdout}
405
+ ${l.stderr}`.trim();return{ok:(l.code??1)===0,output:c}}function Wx(e,t,n,o){let r=n.map(i=>({...i,source:"learned",evidence:`inferred from plan: ${o.slice(0,80)}`}));Mh(e,r,t);let s=Kc.join(Nn(e),"research.md");zc.writeFileSync(s,`# Role research (deterministic)
405
406
 
406
407
  Role: ${t}
407
408
 
408
409
  Gaps filled: ${n.map(i=>i.label).join(", ")}
409
- `,"utf8")}async function Ih(e,t,n,o,r){let s=Gc.join(Nn(n),"research",t.id);if(e.boardCoordinatorAgentEnabled&&e.boardCoordinatorAgentCommand.trim()){let i=[`Research skills for digital employee role: ${o}`,`Job plan: ${t.plan}`,`Missing skills: ${r.map(a=>a.label).join(", ")}`,`Write updated skills to ${Nn(n)}/skills.json and notes to research.md in workdir.`].join(`
410
- `);await Mx(e,i,t.ownerPeerKey,s);return}e.boardSkillResearchEnabled&&Ax(n,o,r,t.plan)}function Oh(e){let t=e.trim().split(/\s+/);if(t.length<3)return{error:"Usage: /board feedback <jobId> satisfied yes|no [1-5] [notes\u2026]"};let n=t[0].toLowerCase();if(t[1]?.toLowerCase()!=="satisfied")return{error:'Expected "satisfied yes" or "satisfied no".'};let o=t[2].toLowerCase();if(o!=="yes"&&o!=="no")return{error:"satisfied must be yes or no."};let r,s=3,i=Number(t[3]);t[3]&&Number.isFinite(i)&&i>=1&&i<=5&&(r=Math.floor(i),s=4);let a=t.slice(s).join(" ").trim()||void 0;return{jobId:n,ownerPeerKey:"",satisfied:o==="yes",rating:r,notes:a}}function Lh(e){Ue();let t=Be(e.jobId);if(!t)return{ok:!1,error:`Job not found: ${e.jobId}`};if(xe().prepare(`INSERT INTO board_feedback (job_id, owner_peer_key, rating, satisfied, notes, created_at_ms)
410
+ `,"utf8")}async function _h(e,t,n,o,r){let s=Kc.join(Nn(n),"research",t.id);if(e.boardCoordinatorAgentEnabled&&e.boardCoordinatorAgentCommand.trim()){let i=[`Research skills for digital employee role: ${o}`,`Job plan: ${t.plan}`,`Missing skills: ${r.map(a=>a.label).join(", ")}`,`Write updated skills to ${Nn(n)}/skills.json and notes to research.md in workdir.`].join(`
411
+ `);await Dx(e,i,t.ownerPeerKey,s);return}e.boardSkillResearchEnabled&&Wx(n,o,r,t.plan)}function Fh(e){let t=e.trim().split(/\s+/);if(t.length<3)return{error:"Usage: /board feedback <jobId> satisfied yes|no [1-5] [notes\u2026]"};let n=t[0].toLowerCase();if(t[1]?.toLowerCase()!=="satisfied")return{error:'Expected "satisfied yes" or "satisfied no".'};let o=t[2].toLowerCase();if(o!=="yes"&&o!=="no")return{error:"satisfied must be yes or no."};let r,s=3,i=Number(t[3]);t[3]&&Number.isFinite(i)&&i>=1&&i<=5&&(r=Math.floor(i),s=4);let a=t.slice(s).join(" ").trim()||void 0;return{jobId:n,ownerPeerKey:"",satisfied:o==="yes",rating:r,notes:a}}function Dh(e){Ue();let t=Be(e.jobId);if(!t)return{ok:!1,error:`Job not found: ${e.jobId}`};if(xe().prepare(`INSERT INTO board_feedback (job_id, owner_peer_key, rating, satisfied, notes, created_at_ms)
411
412
  VALUES (?, ?, ?, ?, ?, ?)`).run(e.jobId,e.ownerPeerKey,e.rating??null,e.satisfied?1:0,e.notes??"",Date.now()),t.assignee){let n=Qo(t.assignee);if(n){n.jobCount+=0;let o=xe().prepare(`SELECT AVG(rating) AS a, AVG(satisfied) AS s FROM board_feedback f
412
- JOIN board_performance p ON p.job_id = f.job_id WHERE p.employee_id = ?`).get(t.assignee);o?.a!=null&&(n.satisfactionScore=Number(o.a)),Bi(n)}}return{ok:!0}}function Nh(e){return`/board feedback ${e} satisfied yes|no [1-5] [notes\u2026]`}function Ix(e){return e.actorFrom&&e.actorTo?` \xB7 ${e.actorFrom} \u2192 ${e.actorTo}`:""}function Ox(e){return e.boardNotifyProgress!=="none"}function Lx(e){return e.boardNotifyJobStatus}function Nx(e,t){if(e.kind==="jobStatus"){let n=e.assignee&&!e.actorTo?.includes(e.assignee)?` \xB7 ${e.assignee}`:"",o=e.detail?` (${e.detail})`:"",r=Ix(e);switch(e.to){case"running":return`[board] ${e.jobId}${r} (started${n}${o})`;case"review":return`[board] ${e.jobId}${r||" \xB7 supervisor"} (reviewing report)`;case"done":{let s=e.detail?`[board] ${e.jobId}${r||" \xB7 done"} \xB7 ${e.detail}`:`[board] ${e.jobId}${r||" \xB7 done"}`;if(e.handoffBody?.trim()){let i=e.handoffBody.trim(),a=i.length>400?`${i.slice(0,397)}\u2026`:i;return`${s}
413
- ${a}`}return s}case"failed":return`[board] ${e.jobId}${r} \xB7 failed${o}`;case"rework":return`[board] ${e.jobId}${r} \xB7 rework${o}`;case"blocked":return`[board] ${e.jobId}${r} blocked${o}`;case"cancelled":return`[board] ${e.jobId} cancelled`;default:return t==="all"?`[board] ${e.jobId} \u2192 ${e.to}${n}`:null}}return e.kind==="phase"?e.completed?`[board] ${e.jobId} \xB7 ${e.phase} \u2713 ${e.stepTitle}`:t==="all"?`[board] ${e.jobId} \xB7 phase: ${e.phase}`:null:null}async function Gt(e,t,n,o){if(n.kind==="jobStatus"&&!Lx(o)||n.kind==="phase"&&!Ox(o))return;let r=Nx(n,o.boardNotifyProgress);r&&await e(t,r)}import _x from"node:crypto";var Fx=[["research","analyze","investigate"],["implement","build","code"],["deploy","release","ops"],["review","audit","legal"],["test","qa","verify"]];function Dx(){return`rec-${_x.randomBytes(3).toString("hex")}`}function _h(e){Ue(),xe().prepare(`INSERT INTO board_recruitment (
413
+ JOIN board_performance p ON p.job_id = f.job_id WHERE p.employee_id = ?`).get(t.assignee);o?.a!=null&&(n.satisfactionScore=Number(o.a)),ji(n)}}return{ok:!0}}function Wh(e){return`/board feedback ${e} satisfied yes|no [1-5] [notes\u2026]`}function Ux(e){return e.actorFrom&&e.actorTo?` \xB7 ${e.actorFrom} \u2192 ${e.actorTo}`:""}function Bx(e){return e.boardNotifyProgress!=="none"}function jx(e){return e.boardNotifyJobStatus}function Hx(e,t){if(e.kind==="jobStatus"){let n=e.assignee&&!e.actorTo?.includes(e.assignee)?` \xB7 ${e.assignee}`:"",o=e.detail?` (${e.detail})`:"",r=Ux(e);switch(e.to){case"running":return`[board] ${e.jobId}${r} (started${n}${o})`;case"review":return`[board] ${e.jobId}${r||" \xB7 supervisor"} (reviewing report)`;case"done":{let s=e.detail?`[board] ${e.jobId}${r||" \xB7 done"} \xB7 ${e.detail}`:`[board] ${e.jobId}${r||" \xB7 done"}`;if(e.handoffBody?.trim()){let i=e.handoffBody.trim(),a=i.length>400?`${i.slice(0,397)}\u2026`:i;return`${s}
414
+ ${a}`}return s}case"failed":return`[board] ${e.jobId}${r} \xB7 failed${o}`;case"rework":return`[board] ${e.jobId}${r} \xB7 rework${o}`;case"blocked":return`[board] ${e.jobId}${r} blocked${o}`;case"cancelled":return`[board] ${e.jobId} cancelled`;default:return t==="all"?`[board] ${e.jobId} \u2192 ${e.to}${n}`:null}}return e.kind==="phase"?e.completed?`[board] ${e.jobId} \xB7 ${e.phase} \u2713 ${e.stepTitle}`:t==="all"?`[board] ${e.jobId} \xB7 phase: ${e.phase}`:null:null}async function Gt(e,t,n,o){if(n.kind==="jobStatus"&&!jx(o)||n.kind==="phase"&&!Bx(o))return;let r=Hx(n,o.boardNotifyProgress);r&&await e(t,r)}import Jx from"node:crypto";var Gx=[["research","analyze","investigate"],["implement","build","code"],["deploy","release","ops"],["review","audit","legal"],["test","qa","verify"]];function qx(){return`rec-${Jx.randomBytes(3).toString("hex")}`}function Uh(e){Ue(),xe().prepare(`INSERT INTO board_recruitment (
414
415
  id, job_id, suggested_name, suggested_role, reason, status, created_at_ms
415
- ) VALUES (?, ?, ?, ?, ?, ?, ?)`).run(e.id,e.jobId,e.suggestedName,e.suggestedRole,e.reason,e.status,e.createdAtMs)}function Fh(e){Ue();let t=xe().prepare("SELECT * FROM board_recruitment WHERE id = ?").get(e);return t?{id:String(t.id),jobId:String(t.job_id),suggestedName:String(t.suggested_name),suggestedRole:String(t.suggested_role),reason:String(t.reason),status:String(t.status),createdAtMs:Number(t.created_at_ms)}:null}function qc(e,t){xe().prepare("UPDATE board_recruitment SET status = ? WHERE id = ?").run(t,e)}function Wx(e,t){let n=Date.now()-t;return et().filter(o=>o.assignee===e&&o.createdAtMs>=n&&o.status!=="cancelled").length}function Ux(e){let t=e.toLowerCase(),n=0;for(let o of Fx)o.some(r=>t.includes(r))&&(n+=1);return n>=3}function Dh(e){let t=e.assignee;if(!t)return null;let n=[];if(Wx(t,24*60*60*1e3)>3&&n.push(`${t} has >3 jobs in 24h`),Ux(e.plan)&&n.push("job plan spans multiple specialist domains"),e.reworkCount>=2&&n.push("repeated rework on related work"),n.length===0)return null;let o=t.includes("implement")?"researcher":"specialist-helper";return{id:Dx(),jobId:e.id,suggestedName:`${o}-${e.id.slice(4,8)}`,suggestedRole:`Support role for ${e.title.slice(0,40)}`,reason:n.join("; "),status:"pending",createdAtMs:Date.now()}}import Bx from"node:fs";import Wh from"node:path";function zc(e){let t=Yo(e),n=Qo(e);return{communicationBrief:Gr,skillsSummary:t?Eh(t):"",roleContext:t?.roleContext??n?.role??""}}async function Uh(e,t,n){let o=e.assignee??"",s=Qo(o)?.role??o;if(t.boardSkillResearchEnabled&&o){let i=Rh(o,e.plan,s);i.length>0&&(Ih(t,e,o,s,i),n.sendToPeer&&await Gt(n.sendToPeer,e.ownerPeerKey,{kind:"jobStatus",jobId:e.id,title:e.title,from:e.status,to:e.status,assignee:o,detail:`skills updated (${i.length} gap(s))`},t))}return zc(o)}async function Bh(e,t,n){if(!n.sendToPeer||e.status!=="done")return;let o;try{let s=e.reportPath??Wh.join(e.workDir,"report.json");o=JSON.parse(Bx.readFileSync(s,"utf8")).handoffMessage?.trim()||void 0}catch{o=void 0}if(await Gt(n.sendToPeer,e.ownerPeerKey,{kind:"jobStatus",jobId:e.id,title:e.title,from:"review",to:"done",assignee:e.assignee,detail:Nh(e.id),actorFrom:"supervisor",actorTo:"you",handoffBody:o},t),t.boardCoordinatorFileSendEnabled&&Oc(e.plan)){let s=rh(e.plan,e.workDir),i=sh(s);if(i){let a=nh(i,e.title);(await ct(t.shell,a,{timeoutMs:Math.min(t.syncTimeoutMs,12e4),maxBytes:t.syncMaxBytes,cwd:e.workDir})).code===0&&await Gt(n.sendToPeer,e.ownerPeerKey,{kind:"jobStatus",jobId:e.id,title:e.title,from:"done",to:"done",assignee:"coordinator",detail:`delivered ${Wh.basename(i)} via omnish i`},t)}}let r=Dh(e);r&&(_h(r),await Gt(n.sendToPeer,e.ownerPeerKey,{kind:"jobStatus",jobId:e.id,title:e.title,from:null,to:e.status,detail:`recruit: ${r.suggestedName} \u2014 ${r.reason}`},t))}function jh(e,t){return{OMNISH_COMMUNICATION_BRIEF:e.communicationBrief,OMNISH_EMPLOYEE_SKILLS:e.skillsSummary,OMNISH_ROLE_CONTEXT:e.roleContext,OMNISH_BOARD_SPECIALIST:t}}import Kc from"node:fs";import ji from"node:path";import{fileURLToPath as qx}from"node:url";j();import jx from"node:fs";import Hx from"node:path";function Jx(e){return Hx.join(bt,e.toLowerCase(),"flow.json")}function Gx(e){try{let t=jx.readFileSync(Jx(e),"utf8");return JSON.parse(t)}catch{return null}}function Hh(e,t){let n=Gx(t),o=[...e];if(n?.extraSteps?.length)for(let s of n.extraSteps)io.includes(s.phase)&&(o.some(i=>i.id===s.id)||o.push({...s,completed:!1}));let r=new Set(["communicate-plan","plan-draft","plan-structure","implement","execute","test","review-final","handoff"]);for(let s of o)!r.has(s.id)&&io.includes(s.phase);for(let s of io)if(!o.some(i=>i.phase===s))throw new Error(`Flow merge would drop required phase: ${s}`);return o.some(s=>s.id==="communicate-plan")||o.unshift({id:"communicate-plan",phase:"plan",title:"Confirm understanding with coordinator communication brief",completed:!1}),o}function Jh(e){let t=(e.innerCommand??e.command??"").trim();if(!t)throw new Error("Employee has no innerCommand (or legacy command).");return t}function Gh(e,t,n=!1){Kc.mkdirSync(e.workDir,{recursive:!0,mode:448});let o=Ko(e.workDir);if(!n&&Kc.existsSync(o))return;let r={jobId:e.id,specialist:t,createdAtMs:Date.now(),overallStructureReview:"",steps:Hh(wh(e.id,t),t),readyForHandoff:!1,outcomeStatement:""};Wi(e.workDir,r)}function zx(){let e=ji.dirname(qx(import.meta.url)),t=[ji.join(e,"board-employee-run.mjs"),ji.join(e,"..","board-employee-run.mjs"),ji.join(e,"..","..","scripts","board-employee-run.mjs")];for(let n of t)try{return Kc.accessSync(n),n}catch{}throw new Error("board-employee-run.mjs not found (reinstall omnish or run from repo).")}function qh(e,t){let n=zx(),o=t.shell.replace(/'/g,"'\\''"),r=n.replace(/'/g,"'\\''"),s=e.replace(/'/g,"'\\''");return`${o} -lc 'exec node "${r}" "${s}"'`}function zh(e,t,n){let o={OMNISH_BOARD_PHASES:kh,OMNISH_BOARD_STEP_COMPLETE_CMD:`/board step ${e.id} complete`,OMNISH_BOARD_JOB_ID:e.id,OMNISH_BOARD_SPECIALIST:t};return n&&(o.OMNISH_COMMUNICATION_BRIEF=n.communicationBrief,o.OMNISH_EMPLOYEE_SKILLS=n.skillsSummary,o.OMNISH_ROLE_CONTEXT=n.roleContext),Object.assign(o,oh(e,"specialist")),o}import Kx from"node:fs";import Hi from"node:os";import{execSync as Yx}from"node:child_process";function Vx(){try{let e=Hi.loadavg()[0]??0,t=Hi.cpus().length||1;return Math.min(100,Math.round(e/t*100))}catch{return null}}function Qx(){try{let n=Kx.readFileSync("/proc/meminfo","utf8"),o=n.match(/^MemAvailable:\s+(\d+)/m);if(o)return Math.floor(Number(o[1])/1024);let r=n.match(/^MemFree:\s+(\d+)/m);if(r)return Math.floor(Number(r[1])/1024)}catch{}let e=Hi.totalmem(),t=Hi.freemem();return Math.floor(t/(1024*1024))}function Xx(){try{let t=Yx("nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits",{encoding:"utf8",timeout:3e3}).trim().split(/\r?\n/)[0],n=Number(t);return Number.isFinite(n)?n:null}catch{return null}}function Kh(){let e=Ni(),t=Vx(),n=Qx(),o=Xx(),r=Yf(),s=Kf("heavy"),i=[];return r>=e.maxConcurrentAgents&&i.push(`slots full (${r}/${e.maxConcurrentAgents})`),t!=null&&t>e.cpuMaxPercent&&i.push(`cpu ${t}% > ${e.cpuMaxPercent}%`),n!=null&&n<e.memMinFreeMb&&i.push(`mem free ${n}MB < ${e.memMinFreeMb}MB`),o!=null&&o>e.gpuMaxUtilPercent&&i.push(`gpu ${o}% > ${e.gpuMaxUtilPercent}%`),{ok:i.length===0,reason:i.length?i.join("; "):"ok",cpuPercent:t,memFreeMb:n,gpuUtilPercent:o,slotsUsed:r,heavySlotsUsed:s,config:e}}function Yc(e){let t=Kh();if(!t.ok)return{ok:!1,reason:t.reason};let{config:n}=t;return t.slotsUsed>=n.maxConcurrentAgents?{ok:!1,reason:`maxConcurrentAgents (${n.maxConcurrentAgents})`}:e==="heavy"&&t.heavySlotsUsed>=n.maxConcurrentHeavy?{ok:!1,reason:`maxConcurrentHeavy (${n.maxConcurrentHeavy})`}:{ok:!0,reason:"ok"}}function Yh(){let e=Kh();return[`slots ${e.slotsUsed}/${e.config.maxConcurrentAgents}`,e.cpuPercent!=null?`cpu ~${e.cpuPercent}%`:null,e.memFreeMb!=null?`mem free ${e.memFreeMb}MB`:null,e.gpuUtilPercent!=null?`gpu ${e.gpuUtilPercent}%`:null,e.ok?"ready":`wait: ${e.reason}`].filter(Boolean).join(" \xB7 ")}function Vc(e){return e.filter(t=>t.completed).map(t=>t.id)}function Zx(e,t){let n=ln(e.workDir);if(!n)return[];let o=new Set(t),r=[];for(let s of n.steps)s.completed&&!o.has(s.id)&&r.push({kind:"phase",jobId:e.id,title:e.title,phase:s.phase,stepId:s.id,stepTitle:s.title,completed:!0});return r}async function Vh(e,t,n){let o=0;for(let r of e){if(r.status!=="running")continue;let s=Vf(r.id),i=Zx(r,s),a=ln(r.workDir);if(!a)continue;for(let c of i)await t(r.ownerPeerKey,c,n),o+=1;let l=Vc(a.steps);(l.length!==s.length||i.length>0)&&Jr(r.id,l)}return o}import Ji from"node:fs";import Gi from"node:path";var eC=3;function tC(e){try{let t=Ji.readFileSync(e,"utf8");return JSON.parse(t)}catch{return null}}function nC(e,t){let n=[],o=e.split(/\r?\n/);for(let r of o){let s=r.trim().match(/^CHECK:file:(.+)$/i);if(!s)continue;let i=s[1].trim(),a=Gi.isAbsolute(i)?i:Gi.join(t,i),l=Ji.existsSync(a);n.push({name:`file:${i}`,ok:l,detail:l?"exists":"missing"})}return n}async function oC(e,t,n,o,r,s){if(!r.sendToPeer)return;let i=e.assignee??"specialist",a="supervisor",l=n==="done"?"you":i;await Gt(r.sendToPeer,e.ownerPeerKey,{kind:"jobStatus",jobId:e.id,title:e.title,from:t,to:n,assignee:e.assignee,detail:s,actorFrom:a,actorTo:l},o)}function rC(e,t){let n=t.maxRework??eC,o=e.reportPath??Gi.join(e.workDir,"report.json"),r=tC(o),s=e.status,i=[];if(Ji.existsSync(Ko(e.workDir))||i.push({name:"work-plan.json",ok:!1,detail:"missing \u2014 harness should have seeded core plan"}),!r)i.push({name:"report.json",ok:!1,detail:"missing or invalid JSON"});else{i.push({name:"jobId",ok:r.jobId===e.id,detail:r.jobId}),i.push({name:"summary",ok:typeof r.summary=="string"&&r.summary.trim().length>0,detail:"non-empty summary"}),i.push({name:"exitCode",ok:r.exitCode===0,detail:`exit ${r.exitCode}`}),i.push({name:"planFollowed",ok:r.planFollowed===!0,detail:r.planFollowed===!0?"yes":"planFollowed must be true"}),i.push({name:"communicationSummary",ok:typeof r.communicationSummary=="string"&&r.communicationSummary.trim().length>0,detail:"non-empty communicationSummary"}),i.push({name:"handoffMessage",ok:typeof r.handoffMessage=="string"&&r.handoffMessage.trim().length>0,detail:"non-empty handoffMessage for owner"}),i.push(...nC(e.plan,e.workDir));let h=ln(e.workDir);if(i.push(...bh(h)),h&&r){let g=new Set(r.stepsCompleted??[]),y=h.steps.filter(k=>k.completed&&!g.has(k.id));i.push({name:"report.stepsMatchWorkPlan",ok:y.length===0,detail:y.length===0?"aligned":`report missing completed ids: ${y.map(k=>k.id).join(", ")}`})}}let a="pass",l="Accepted.",c=i.filter(h=>!h.ok);c.length>0&&(a=e.reworkCount+1>=n?"fail":"rework",l=`${Sh(c.map(g=>g.name))}: ${c.map(g=>`${g.name}: ${g.detail}`).join("; ")}`);let u={jobId:e.id,verdict:a,notes:l,checks:i},d=Gi.join(e.workDir,"verdict.json");Ji.writeFileSync(d,JSON.stringify(u,null,2)+`
416
- `,{mode:384}),e.supervisorVerdict=a==="pass"?"pass":"fail",e.supervisorNotes=l,Ht(e.id,`supervisor:${a}`,l),Go(e.id);let m=!1;a==="pass"?(e.status="done",e.completedAtMs=Date.now()):a==="rework"?(e.status="rework",e.reworkCount+=1):(e.status="failed",e.completedAtMs=Date.now(),m=!0),jt(e),Ht(e.id,`status:${e.status}`,l);let f=c.map(h=>h.name);if(e.assignee&&uh({jobId:e.id,employeeId:e.assignee,startedAtMs:e.startedAtMs,completedAtMs:e.completedAtMs,supervisorVerdict:a,reworkCount:e.reworkCount,competencyFailures:f}),t.cfg&&t.deps){let h=a==="rework"?`${e.reworkCount}/${n}`:a==="fail"?l.slice(0,120):void 0;oC(e,s,e.status,t.cfg,t.deps,h),a==="pass"&&Bh(e,t.cfg,t.deps)}return{job:e,verdict:u,escalate:m}}function Qc(e,t,n={}){let o=[],r=0;for(let s of et({status:"review"})){let{job:i,escalate:a}=rC(s,{maxRework:e,cfg:t,deps:n});r+=1,a&&o.push(i)}return{processed:r,escalations:o}}var sC=2;function Qh(e,t){let n=e.toLowerCase();return n.includes("review")&&t.reviewer?"reviewer":(n.includes("implement")||n.includes("build")||n.includes("code"))&&t.implementer?"implementer":t.researcher?"researcher":Object.keys(t)[0]??null}async function Kr(e,t,n,o,r,s={}){r.sendToPeer&&await Gt(r.sendToPeer,e.ownerPeerKey,{kind:"jobStatus",jobId:e.id,title:e.title,from:t,to:n,assignee:e.assignee,detail:s.detail,actorFrom:s.actorFrom,actorTo:s.actorTo,handoffBody:s.handoffBody},o)}function Xc(e,t,n,o){let r=zi.join(e.workDir,"report.json"),s={jobId:e.id,specialist:t,summary:o.summary??"",stepsCompleted:o.stepsCompleted??[],artifacts:o.artifacts??["work-plan.json"],exitCode:n,blockers:o.blockers??[],selfAssessment:o.selfAssessment??"",workPlanPath:"work-plan.json",planFollowed:o.planFollowed??!1,communicationSummary:o.communicationSummary??"",handoffMessage:o.handoffMessage??"",requestedPrerequisites:o.requestedPrerequisites};qi.writeFileSync(r,JSON.stringify(s,null,2)+`
417
- `,{mode:384}),e.reportPath=r}function iC(e,t){let n=[],o=Pt();for(let r of t){let s=r.suggestedAssignee?.toLowerCase()??Qh(r.plan,o)??e.assignee??"researcher",i=_i({title:r.title,plan:r.plan,ownerPeerKey:e.ownerPeerKey,parentId:e.id,assignee:s,blocks:[e.id],priority:e.priority+1});n.push(i.id);let a=Be(e.id);a&&(a.dependsOn.includes(i.id)||(a.dependsOn=[...a.dependsOn,i.id]),Jt(a,"blocked",`prerequisite ${i.id}`),jt(a))}return n}async function aC(e,t,n){let o=Pt(),r=e.assignee??"",s=o[r],i=e.status;if(!s){Xc(e,r,1,{summary:`No employee "${r}" in registry.`,blockers:["configure ~/.omnish/board/employees.json"]}),Jt(e,"review","missing employee"),e.reportPath=zi.join(e.workDir,"report.json"),jt(e),Go(e.id),await Kr(e,i,"review",t,n,{actorFrom:r||"specialist",actorTo:"supervisor"});return}let a=zc(r);Gh(e,r,!0),Jr(e.id,[]);let l;try{l=Jh(s),ph(l)&&n._stubWarn?.(`${e.id}: assignee ${r} uses stub innerCommand \u2014 /board agent set <claude|cursor|codex>`)}catch(y){Xc(e,r,1,{summary:String(y instanceof Error?y.message:y),blockers:["invalid employee registry entry"]}),Jt(e,"review","bad employee config"),jt(e),Go(e.id);return}let c=oe(e.ownerPeerKey),u={...process.env,OMNISH_JOB_ID:e.id,OMNISH_JOB_PLAN:e.plan,OMNISH_JOB_TITLE:e.title,OMNISH_JOB_WORKDIR:e.workDir,OMNISH_JOB_WORK_PLAN:zi.join(e.workDir,"work-plan.json"),OMNISH_JOB_ASSIGNEE:r,...zh(e,r,a),...jh(a,r)},d=qh(l,t);Ht(e.id,"dispatch",`${r} (harness)`);let m=await ct(t.shell,d,{timeoutMs:t.syncTimeoutMs,maxBytes:t.syncMaxBytes,cwd:c.cwd,env:u}),f=zi.join(e.workDir,"report.json");qi.existsSync(f)?e.reportPath=f:Xc(e,r,m.code??1,{summary:`Specialist exited ${m.code??"?"}. No report.json written.`,stepsCompleted:[],blockers:m.timedOut?["timed out"]:[]});let h=qi.existsSync(f)?JSON.parse(qi.readFileSync(f,"utf8")):null;if(m.code===sC&&h?.requestedPrerequisites?.length){let y=iC(e,h.requestedPrerequisites);Jt(e,"blocked",`prerequisites: ${y.join(", ")}`),Go(e.id),await Kr(e,"running","blocked",t,n,{detail:`waiting on ${y.join(", ")}`,actorFrom:r,actorTo:"blocked"});return}e.reportPath=f;let g=e.status;Jt(e,"review",`exit ${m.code??"?"}`),jt(e),await Kr(e,g,"review",t,n,{actorFrom:r,actorTo:"supervisor"})}async function Ki(e,t={}){Ue(),Di();let n=t.sendToPeer?(b,x,C)=>Gt(t.sendToPeer,b,x,C):null,o=[],r=[],s={...t,_stubWarn:b=>r.push(b)},i=hh();for(let b of et({status:"pending"}))b.dependsOn.length>0&&zo(b)&&await Kr(b,"blocked","pending",e,s);let a=0;n&&(a=await Vh(et(),n,e));let l=et({status:"review"}).map(b=>b.id),c=Qc(e.boardMaxRework,e,s);for(let b of l){let x=Be(b);x&&x.status!=="review"&&o.push({jobId:b,from:"review",to:x.status,note:x.supervisorNotes?.slice(0,80)})}let u=c.escalations,d=c.processed,m=0;for(let b of et())if(b.status==="done"||b.status==="failed"||b.status==="rework"){let x=ln(b.workDir);x&&Jr(b.id,Vc(x.steps))}let f=Yc("standard"),h=0,g=f.reason;if(f.ok){let b=gh();if(b&&!b.assignee){let x=Pt();b.assignee=Qh(b.plan,x),jt(b)}if(b&&b.assignee&&zo(b)){let x=b.resourceProfile,C=Yc(x);if(C.ok){zf(b.id,x),b.startedAtMs=Date.now();let M=b.status;Jt(b,"running",b.assignee),await Uh(b,e,t),await Kr(b,M,"running",e,s,{detail:`role ${b.assignee}`,actorFrom:"coordinator",actorTo:b.assignee??"specialist"});let R=M;if(await aC(b,e,s),h=1,g="ok",Be(b.id)?.status==="review"){let A=Qc(e.boardMaxRework,e,s);d+=A.processed,m+=A.processed,u=[...u,...A.escalations];let q=Be(b.id);q&&o.push({jobId:q.id,from:R,to:q.status,note:q.status==="done"?"supervisor pass":q.supervisorNotes?.slice(0,80)??void 0})}}else g=C.reason}else b||(g="no runnable jobs")}let y=u.map(b=>b.id),k=et({status:"review"}).length;return{unblocked:i,reviewed:d,reviewedAfterDispatch:m,dispatched:h,escalations:y,waiting:g,progressNotified:a,outcomes:o,warnings:r,pendingReviewCount:k}}function Yi(e){Ue();let t=!1,n,o=async()=>{let s=e.getConfig();if(s.boardCoordinatorEnabled&&!t){t=!0;try{let i=await Ki(s,{sendToPeer:e.sendToPeer});if(i.escalations.length>0)for(let a of i.escalations){let l=`[board] Job ${a} failed after max rework.`;P.warn({jobId:a},"board escalation");try{let c=Be(a);c&&await e.sendToPeer(c.ownerPeerKey,l)}catch(c){P.warn({err:String(c)},"board escalation notify failed")}}}catch(i){P.warn({err:String(i)},"board coordinator tick failed")}finally{t=!1}}},r=()=>{let s=e.getConfig();return s.boardCoordinatorEnabled?s.boardCoordinatorIntervalMs:Ni().dispatchIntervalMs};return o(),n=setInterval(()=>{o()},r()),()=>{n!==void 0&&(clearInterval(n),n=void 0),Gf()}}dt();be();import Vr from"node:fs";import Qr from"node:path";function Yr(e,t,n){let o=new Date(e),r=o.getFullYear(),s=o.getMonth(),i=o.getDate(),a=new Date(r,s,i,t,n,0,0).getTime();return a<=e&&(a=new Date(r,s,i+1,t,n,0,0).getTime()),a}function lC(e,t,n){let o=e;for(let r=0;r<14;r++){let s=Yr(o,t,n),i=new Date(s).getDay();if(i>=1&&i<=5)return s;o=s}return Yr(o,t,n)}function cC(e,t,n,o){let r=e;for(let s=0;s<370;s++){let i=Yr(r,n,o);if(new Date(i).getDay()===t)return i;r=i}return Yr(r,n,o)}function uC(e,t){let n=new Date(e),o=n.getFullYear(),r=n.getMonth(),s=n.getDate(),i=n.getHours(),a=new Date(o,r,s,i,t,0,0).getTime();return a<=e&&(a=new Date(o,r,s,i+1,t,0,0).getTime()),a}function dC(e,t){return e.kind==="ondemand"||e.kind==="heartbeat"?Number.POSITIVE_INFINITY:e.kind==="daily"?Yr(t,e.hour,e.minute):e.kind==="weekdays"?lC(t,e.hour,e.minute):e.kind==="hourly"?uC(t,e.minute):cC(t,e.weekday,e.hour,e.minute)}function eg(e,t,n,o,r=32){if(e.kind==="ondemand"||e.kind==="heartbeat")return[];let s=t??n-1,i=[],a=s;for(;i.length<r;){let l=dC(e,a);if(l>o)break;i.push(l),a=l}return i}var pC={sun:0,sunday:0,mon:1,monday:1,tue:2,tues:2,tuesday:2,wed:3,wednesday:3,thu:4,thur:4,thurs:4,thursday:4,fri:5,friday:5,sat:6,saturday:6};function Zc(e){let t=/^(\d{1,2}):(\d{2})$/.exec(e.trim());if(!t)return null;let n=Number(t[1]),o=Number(t[2]);return!Number.isFinite(n)||!Number.isFinite(o)||n>23||o>59?null:{hour:n,minute:o}}function mC(e){let t=e.trim(),n=/^:(\d{1,2})$/.exec(t),o=/^(\d{1,2})$/.exec(t),r=n?n[1]:o?o[1]:null;if(r===null)return null;let s=Number(r);return!Number.isInteger(s)||s<0||s>59?null:s}function Vi(e){if(e.length===0)return{ok:!1,error:"Missing schedule (try: ondemand, hourly [:MM], daily HH:MM, weekdays HH:MM, weekly <dow> HH:MM)."};let t=e[0].toLowerCase();if(t==="ondemand"||t==="manual")return e.length!==1?{ok:!1,error:"ondemand takes no extra tokens."}:{ok:!0,schedule:{kind:"ondemand"}};if(t==="daily"){if(e.length!==2)return{ok:!1,error:"Usage: daily HH:MM"};let n=Zc(e[1]);return n?{ok:!0,schedule:{kind:"daily",hour:n.hour,minute:n.minute}}:{ok:!1,error:"daily needs HH:MM (24h)."}}if(t==="weekdays"){if(e.length!==2)return{ok:!1,error:"Usage: weekdays HH:MM"};let n=Zc(e[1]);return n?{ok:!0,schedule:{kind:"weekdays",hour:n.hour,minute:n.minute}}:{ok:!1,error:"weekdays needs HH:MM (24h)."}}if(t==="hourly"){if(e.length===1)return{ok:!0,schedule:{kind:"hourly",minute:0}};if(e.length!==2)return{ok:!1,error:"Usage: hourly | hourly :MM | hourly MM (minute 0\u201359)."};let n=mC(e[1]);return n===null?{ok:!1,error:"hourly needs :MM or MM (minute 0\u201359), e.g. hourly :30 or hourly 15."}:{ok:!0,schedule:{kind:"hourly",minute:n}}}if(t==="weekly"){if(e.length!==3)return{ok:!1,error:"Usage: weekly <mon|tue|\u2026|sun> HH:MM"};let n=e[1].toLowerCase(),o=pC[n];if(o===void 0){let s=Number(n);Number.isInteger(s)&&s>=0&&s<=6&&(o=s)}if(o===void 0)return{ok:!1,error:`Unknown weekday "${e[1]}". Use mon\u2026sun or 0\u20136 (Sun=0).`};let r=Zc(e[2]);return r?{ok:!0,schedule:{kind:"weekly",weekday:o,hour:r.hour,minute:r.minute}}:{ok:!1,error:"weekly needs HH:MM (24h) after weekday."}}if(t==="heartbeat"){if(e.length<2||e.length>3)return{ok:!1,error:"Usage: heartbeat <interval> [grace] \u2014 e.g. heartbeat 1h, heartbeat 30m 10m"};let n=Xh(e[1]);if(n===null||n<6e4)return{ok:!1,error:"heartbeat interval must be >= 1m. Use 5m, 1h, 2h, 1d, etc."};let o=Math.floor(n*.5);if(e.length===3){let r=Xh(e[2]);if(r===null||r<3e4)return{ok:!1,error:"heartbeat grace must be >= 30s. Use 1m, 5m, etc."};o=r}return{ok:!0,schedule:{kind:"heartbeat",intervalMs:n,graceMs:o}}}return{ok:!1,error:`Unknown schedule kind "${t}".`}}function Xh(e){let t=e.trim().toLowerCase();if(!t)return null;let n=t.match(/^(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/);if(!n||t===""||!n[1]&&!n[2]&&!n[3]&&!n[4])return null;let o=n[1]?Number(n[1]):0,r=n[2]?Number(n[2]):0,s=n[3]?Number(n[3]):0,i=n[4]?Number(n[4]):0;return((o*24+r)*60+s)*6e4+i*1e3}function Zh(e){let t=Math.floor(e/1e3),n=Math.floor(t/86400),o=Math.floor(t%86400/3600),r=Math.floor(t%3600/60),s=[];return n>0&&s.push(`${n}d`),o>0&&s.push(`${o}h`),r>0&&s.push(`${r}m`),s.length>0?s.join(""):`${t}s`}function Xo(e){let t=o=>o<10?`0${o}`:String(o),n=(o,r)=>`${t(o)}:${t(r)}`;switch(e.kind){case"ondemand":return"ondemand";case"daily":return`daily ${n(e.hour,e.minute)}`;case"weekdays":return`weekdays ${n(e.hour,e.minute)}`;case"hourly":return e.minute===0?"hourly":`hourly :${t(e.minute)}`;case"weekly":return`weekly ${["sun","mon","tue","wed","thu","fri","sat"][e.weekday]??e.weekday} ${n(e.hour,e.minute)}`;case"heartbeat":return`heartbeat every ${Zh(e.intervalMs)} grace ${Zh(e.graceMs)}`}}j();be();import fC from"better-sqlite3";var tg=2,_n=null;function ng(){D(It);let e=new fC(Td);e.pragma("journal_mode = WAL"),e.exec(`
416
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)`).run(e.id,e.jobId,e.suggestedName,e.suggestedRole,e.reason,e.status,e.createdAtMs)}function Bh(e){Ue();let t=xe().prepare("SELECT * FROM board_recruitment WHERE id = ?").get(e);return t?{id:String(t.id),jobId:String(t.job_id),suggestedName:String(t.suggested_name),suggestedRole:String(t.suggested_role),reason:String(t.reason),status:String(t.status),createdAtMs:Number(t.created_at_ms)}:null}function Yc(e,t){xe().prepare("UPDATE board_recruitment SET status = ? WHERE id = ?").run(t,e)}function zx(e,t){let n=Date.now()-t;return et().filter(o=>o.assignee===e&&o.createdAtMs>=n&&o.status!=="cancelled").length}function Kx(e){let t=e.toLowerCase(),n=0;for(let o of Gx)o.some(r=>t.includes(r))&&(n+=1);return n>=3}function jh(e){let t=e.assignee;if(!t)return null;let n=[];if(zx(t,24*60*60*1e3)>3&&n.push(`${t} has >3 jobs in 24h`),Kx(e.plan)&&n.push("job plan spans multiple specialist domains"),e.reworkCount>=2&&n.push("repeated rework on related work"),n.length===0)return null;let o=t.includes("implement")?"researcher":"specialist-helper";return{id:qx(),jobId:e.id,suggestedName:`${o}-${e.id.slice(4,8)}`,suggestedRole:`Support role for ${e.title.slice(0,40)}`,reason:n.join("; "),status:"pending",createdAtMs:Date.now()}}import Yx from"node:fs";import Hh from"node:path";function Vc(e){let t=Yo(e),n=Qo(e);return{communicationBrief:Gr,skillsSummary:t?Ah(t):"",roleContext:t?.roleContext??n?.role??""}}async function Jh(e,t,n){let o=e.assignee??"",s=Qo(o)?.role??o;if(t.boardSkillResearchEnabled&&o){let i=$h(o,e.plan,s);i.length>0&&(_h(t,e,o,s,i),n.sendToPeer&&await Gt(n.sendToPeer,e.ownerPeerKey,{kind:"jobStatus",jobId:e.id,title:e.title,from:e.status,to:e.status,assignee:o,detail:`skills updated (${i.length} gap(s))`},t))}return Vc(o)}async function Gh(e,t,n){if(!n.sendToPeer||e.status!=="done")return;let o;try{let s=e.reportPath??Hh.join(e.workDir,"report.json");o=JSON.parse(Yx.readFileSync(s,"utf8")).handoffMessage?.trim()||void 0}catch{o=void 0}if(await Gt(n.sendToPeer,e.ownerPeerKey,{kind:"jobStatus",jobId:e.id,title:e.title,from:"review",to:"done",assignee:e.assignee,detail:Wh(e.id),actorFrom:"supervisor",actorTo:"you",handoffBody:o},t),t.boardCoordinatorFileSendEnabled&&_c(e.plan)){let s=lh(e.plan,e.workDir),i=ch(s);if(i){let a=ih(i,e.title);(await ct(t.shell,a,{timeoutMs:Math.min(t.syncTimeoutMs,12e4),maxBytes:t.syncMaxBytes,cwd:e.workDir})).code===0&&await Gt(n.sendToPeer,e.ownerPeerKey,{kind:"jobStatus",jobId:e.id,title:e.title,from:"done",to:"done",assignee:"coordinator",detail:`delivered ${Hh.basename(i)} via omnish i`},t)}}let r=jh(e);r&&(Uh(r),await Gt(n.sendToPeer,e.ownerPeerKey,{kind:"jobStatus",jobId:e.id,title:e.title,from:null,to:e.status,detail:`recruit: ${r.suggestedName} \u2014 ${r.reason}`},t))}function qh(e,t){return{OMNISH_COMMUNICATION_BRIEF:e.communicationBrief,OMNISH_EMPLOYEE_SKILLS:e.skillsSummary,OMNISH_ROLE_CONTEXT:e.roleContext,OMNISH_BOARD_SPECIALIST:t}}import Qc from"node:fs";import Hi from"node:path";import{fileURLToPath as eC}from"node:url";j();import Vx from"node:fs";import Qx from"node:path";function Xx(e){return Qx.join(bt,e.toLowerCase(),"flow.json")}function Zx(e){try{let t=Vx.readFileSync(Xx(e),"utf8");return JSON.parse(t)}catch{return null}}function zh(e,t){let n=Zx(t),o=[...e];if(n?.extraSteps?.length)for(let s of n.extraSteps)io.includes(s.phase)&&(o.some(i=>i.id===s.id)||o.push({...s,completed:!1}));let r=new Set(["communicate-plan","plan-draft","plan-structure","implement","execute","test","review-final","handoff"]);for(let s of o)!r.has(s.id)&&io.includes(s.phase);for(let s of io)if(!o.some(i=>i.phase===s))throw new Error(`Flow merge would drop required phase: ${s}`);return o.some(s=>s.id==="communicate-plan")||o.unshift({id:"communicate-plan",phase:"plan",title:"Confirm understanding with coordinator communication brief",completed:!1}),o}function Kh(e){let t=(e.innerCommand??e.command??"").trim();if(!t)throw new Error("Employee has no innerCommand (or legacy command).");return t}function Yh(e,t,n=!1){Qc.mkdirSync(e.workDir,{recursive:!0,mode:448});let o=Ko(e.workDir);if(!n&&Qc.existsSync(o))return;let r={jobId:e.id,specialist:t,createdAtMs:Date.now(),overallStructureReview:"",steps:zh(vh(e.id,t),t),readyForHandoff:!1,outcomeStatement:""};Ui(e.workDir,r)}function tC(){let e=Hi.dirname(eC(import.meta.url)),t=[Hi.join(e,"board-employee-run.mjs"),Hi.join(e,"..","board-employee-run.mjs"),Hi.join(e,"..","..","scripts","board-employee-run.mjs")];for(let n of t)try{return Qc.accessSync(n),n}catch{}throw new Error("board-employee-run.mjs not found (reinstall omnish or run from repo).")}function Vh(e,t){let n=tC(),o=t.shell.replace(/'/g,"'\\''"),r=n.replace(/'/g,"'\\''"),s=e.replace(/'/g,"'\\''");return`${o} -lc 'exec node "${r}" "${s}"'`}function Qh(e,t,n){let o={OMNISH_BOARD_PHASES:Ch,OMNISH_BOARD_STEP_COMPLETE_CMD:`/board step ${e.id} complete`,OMNISH_BOARD_JOB_ID:e.id,OMNISH_BOARD_SPECIALIST:t};return n&&(o.OMNISH_COMMUNICATION_BRIEF=n.communicationBrief,o.OMNISH_EMPLOYEE_SKILLS=n.skillsSummary,o.OMNISH_ROLE_CONTEXT=n.roleContext),Object.assign(o,ah(e,"specialist")),o}import nC from"node:fs";import Ji from"node:os";import{execSync as oC}from"node:child_process";function rC(){try{let e=Ji.loadavg()[0]??0,t=Ji.cpus().length||1;return Math.min(100,Math.round(e/t*100))}catch{return null}}function sC(){try{let n=nC.readFileSync("/proc/meminfo","utf8"),o=n.match(/^MemAvailable:\s+(\d+)/m);if(o)return Math.floor(Number(o[1])/1024);let r=n.match(/^MemFree:\s+(\d+)/m);if(r)return Math.floor(Number(r[1])/1024)}catch{}let e=Ji.totalmem(),t=Ji.freemem();return Math.floor(t/(1024*1024))}function iC(){try{let t=oC("nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits",{encoding:"utf8",timeout:3e3}).trim().split(/\r?\n/)[0],n=Number(t);return Number.isFinite(n)?n:null}catch{return null}}function Xh(){let e=_i(),t=rC(),n=sC(),o=iC(),r=Zf(),s=Xf("heavy"),i=[];return r>=e.maxConcurrentAgents&&i.push(`slots full (${r}/${e.maxConcurrentAgents})`),t!=null&&t>e.cpuMaxPercent&&i.push(`cpu ${t}% > ${e.cpuMaxPercent}%`),n!=null&&n<e.memMinFreeMb&&i.push(`mem free ${n}MB < ${e.memMinFreeMb}MB`),o!=null&&o>e.gpuMaxUtilPercent&&i.push(`gpu ${o}% > ${e.gpuMaxUtilPercent}%`),{ok:i.length===0,reason:i.length?i.join("; "):"ok",cpuPercent:t,memFreeMb:n,gpuUtilPercent:o,slotsUsed:r,heavySlotsUsed:s,config:e}}function Xc(e){let t=Xh();if(!t.ok)return{ok:!1,reason:t.reason};let{config:n}=t;return t.slotsUsed>=n.maxConcurrentAgents?{ok:!1,reason:`maxConcurrentAgents (${n.maxConcurrentAgents})`}:e==="heavy"&&t.heavySlotsUsed>=n.maxConcurrentHeavy?{ok:!1,reason:`maxConcurrentHeavy (${n.maxConcurrentHeavy})`}:{ok:!0,reason:"ok"}}function Zh(){let e=Xh();return[`slots ${e.slotsUsed}/${e.config.maxConcurrentAgents}`,e.cpuPercent!=null?`cpu ~${e.cpuPercent}%`:null,e.memFreeMb!=null?`mem free ${e.memFreeMb}MB`:null,e.gpuUtilPercent!=null?`gpu ${e.gpuUtilPercent}%`:null,e.ok?"ready":`wait: ${e.reason}`].filter(Boolean).join(" \xB7 ")}function Zc(e){return e.filter(t=>t.completed).map(t=>t.id)}function aC(e,t){let n=ln(e.workDir);if(!n)return[];let o=new Set(t),r=[];for(let s of n.steps)s.completed&&!o.has(s.id)&&r.push({kind:"phase",jobId:e.id,title:e.title,phase:s.phase,stepId:s.id,stepTitle:s.title,completed:!0});return r}async function eg(e,t,n){let o=0;for(let r of e){if(r.status!=="running")continue;let s=eh(r.id),i=aC(r,s),a=ln(r.workDir);if(!a)continue;for(let c of i)await t(r.ownerPeerKey,c,n),o+=1;let l=Zc(a.steps);(l.length!==s.length||i.length>0)&&Jr(r.id,l)}return o}import Gi from"node:fs";import qi from"node:path";var lC=3;function cC(e){try{let t=Gi.readFileSync(e,"utf8");return JSON.parse(t)}catch{return null}}function uC(e,t){let n=[],o=e.split(/\r?\n/);for(let r of o){let s=r.trim().match(/^CHECK:file:(.+)$/i);if(!s)continue;let i=s[1].trim(),a=qi.isAbsolute(i)?i:qi.join(t,i),l=Gi.existsSync(a);n.push({name:`file:${i}`,ok:l,detail:l?"exists":"missing"})}return n}async function dC(e,t,n,o,r,s){if(!r.sendToPeer)return;let i=e.assignee??"specialist",a="supervisor",l=n==="done"?"you":i;await Gt(r.sendToPeer,e.ownerPeerKey,{kind:"jobStatus",jobId:e.id,title:e.title,from:t,to:n,assignee:e.assignee,detail:s,actorFrom:a,actorTo:l},o)}function pC(e,t){let n=t.maxRework??lC,o=e.reportPath??qi.join(e.workDir,"report.json"),r=cC(o),s=e.status,i=[];if(Gi.existsSync(Ko(e.workDir))||i.push({name:"work-plan.json",ok:!1,detail:"missing \u2014 harness should have seeded core plan"}),!r)i.push({name:"report.json",ok:!1,detail:"missing or invalid JSON"});else{i.push({name:"jobId",ok:r.jobId===e.id,detail:r.jobId}),i.push({name:"summary",ok:typeof r.summary=="string"&&r.summary.trim().length>0,detail:"non-empty summary"}),i.push({name:"exitCode",ok:r.exitCode===0,detail:`exit ${r.exitCode}`}),i.push({name:"planFollowed",ok:r.planFollowed===!0,detail:r.planFollowed===!0?"yes":"planFollowed must be true"}),i.push({name:"communicationSummary",ok:typeof r.communicationSummary=="string"&&r.communicationSummary.trim().length>0,detail:"non-empty communicationSummary"}),i.push({name:"handoffMessage",ok:typeof r.handoffMessage=="string"&&r.handoffMessage.trim().length>0,detail:"non-empty handoffMessage for owner"}),i.push(...uC(e.plan,e.workDir));let h=ln(e.workDir);if(i.push(...xh(h)),h&&r){let g=new Set(r.stepsCompleted??[]),y=h.steps.filter(b=>b.completed&&!g.has(b.id));i.push({name:"report.stepsMatchWorkPlan",ok:y.length===0,detail:y.length===0?"aligned":`report missing completed ids: ${y.map(b=>b.id).join(", ")}`})}}let a="pass",l="Accepted.",c=i.filter(h=>!h.ok);c.length>0&&(a=e.reworkCount+1>=n?"fail":"rework",l=`${Rh(c.map(g=>g.name))}: ${c.map(g=>`${g.name}: ${g.detail}`).join("; ")}`);let u={jobId:e.id,verdict:a,notes:l,checks:i},d=qi.join(e.workDir,"verdict.json");Gi.writeFileSync(d,JSON.stringify(u,null,2)+`
417
+ `,{mode:384}),e.supervisorVerdict=a==="pass"?"pass":"fail",e.supervisorNotes=l,Ht(e.id,`supervisor:${a}`,l),Go(e.id);let m=!1;a==="pass"?(e.status="done",e.completedAtMs=Date.now()):a==="rework"?(e.status="rework",e.reworkCount+=1):(e.status="failed",e.completedAtMs=Date.now(),m=!0),jt(e),Ht(e.id,`status:${e.status}`,l);let f=c.map(h=>h.name);if(e.assignee&&fh({jobId:e.id,employeeId:e.assignee,startedAtMs:e.startedAtMs,completedAtMs:e.completedAtMs,supervisorVerdict:a,reworkCount:e.reworkCount,competencyFailures:f}),t.cfg&&t.deps){let h=a==="rework"?`${e.reworkCount}/${n}`:a==="fail"?l.slice(0,120):void 0;dC(e,s,e.status,t.cfg,t.deps,h),a==="pass"&&Gh(e,t.cfg,t.deps)}return{job:e,verdict:u,escalate:m}}function eu(e,t,n={}){let o=[],r=0;for(let s of et({status:"review"})){let{job:i,escalate:a}=pC(s,{maxRework:e,cfg:t,deps:n});r+=1,a&&o.push(i)}return{processed:r,escalations:o}}var mC=2;function tg(e,t){let n=e.toLowerCase();return n.includes("review")&&t.reviewer?"reviewer":(n.includes("implement")||n.includes("build")||n.includes("code"))&&t.implementer?"implementer":t.researcher?"researcher":Object.keys(t)[0]??null}async function Kr(e,t,n,o,r,s={}){r.sendToPeer&&await Gt(r.sendToPeer,e.ownerPeerKey,{kind:"jobStatus",jobId:e.id,title:e.title,from:t,to:n,assignee:e.assignee,detail:s.detail,actorFrom:s.actorFrom,actorTo:s.actorTo,handoffBody:s.handoffBody},o)}function tu(e,t,n,o){let r=Ki.join(e.workDir,"report.json"),s={jobId:e.id,specialist:t,summary:o.summary??"",stepsCompleted:o.stepsCompleted??[],artifacts:o.artifacts??["work-plan.json"],exitCode:n,blockers:o.blockers??[],selfAssessment:o.selfAssessment??"",workPlanPath:"work-plan.json",planFollowed:o.planFollowed??!1,communicationSummary:o.communicationSummary??"",handoffMessage:o.handoffMessage??"",requestedPrerequisites:o.requestedPrerequisites};zi.writeFileSync(r,JSON.stringify(s,null,2)+`
418
+ `,{mode:384}),e.reportPath=r}function fC(e,t){let n=[],o=Pt();for(let r of t){let s=r.suggestedAssignee?.toLowerCase()??tg(r.plan,o)??e.assignee??"researcher",i=Fi({title:r.title,plan:r.plan,ownerPeerKey:e.ownerPeerKey,parentId:e.id,assignee:s,blocks:[e.id],priority:e.priority+1});n.push(i.id);let a=Be(e.id);a&&(a.dependsOn.includes(i.id)||(a.dependsOn=[...a.dependsOn,i.id]),Jt(a,"blocked",`prerequisite ${i.id}`),jt(a))}return n}async function hC(e,t,n){let o=Pt(),r=e.assignee??"",s=o[r],i=e.status;if(!s){tu(e,r,1,{summary:`No employee "${r}" in registry.`,blockers:["configure ~/.omnish/board/employees.json"]}),Jt(e,"review","missing employee"),e.reportPath=Ki.join(e.workDir,"report.json"),jt(e),Go(e.id),await Kr(e,i,"review",t,n,{actorFrom:r||"specialist",actorTo:"supervisor"});return}let a=Vc(r);Yh(e,r,!0),Jr(e.id,[]);let l;try{l=Kh(s),gh(l)&&n._stubWarn?.(`${e.id}: assignee ${r} uses stub innerCommand \u2014 /board agent set <claude|cursor|codex>`)}catch(y){tu(e,r,1,{summary:String(y instanceof Error?y.message:y),blockers:["invalid employee registry entry"]}),Jt(e,"review","bad employee config"),jt(e),Go(e.id);return}let c=oe(e.ownerPeerKey),u={...process.env,OMNISH_JOB_ID:e.id,OMNISH_JOB_PLAN:e.plan,OMNISH_JOB_TITLE:e.title,OMNISH_JOB_WORKDIR:e.workDir,OMNISH_JOB_WORK_PLAN:Ki.join(e.workDir,"work-plan.json"),OMNISH_JOB_ASSIGNEE:r,...Qh(e,r,a),...qh(a,r)},d=Vh(l,t);Ht(e.id,"dispatch",`${r} (harness)`);let m=await ct(t.shell,d,{timeoutMs:t.syncTimeoutMs,maxBytes:t.syncMaxBytes,cwd:c.cwd,env:u}),f=Ki.join(e.workDir,"report.json");zi.existsSync(f)?e.reportPath=f:tu(e,r,m.code??1,{summary:`Specialist exited ${m.code??"?"}. No report.json written.`,stepsCompleted:[],blockers:m.timedOut?["timed out"]:[]});let h=zi.existsSync(f)?JSON.parse(zi.readFileSync(f,"utf8")):null;if(m.code===mC&&h?.requestedPrerequisites?.length){let y=fC(e,h.requestedPrerequisites);Jt(e,"blocked",`prerequisites: ${y.join(", ")}`),Go(e.id),await Kr(e,"running","blocked",t,n,{detail:`waiting on ${y.join(", ")}`,actorFrom:r,actorTo:"blocked"});return}e.reportPath=f;let g=e.status;Jt(e,"review",`exit ${m.code??"?"}`),jt(e),await Kr(e,g,"review",t,n,{actorFrom:r,actorTo:"supervisor"})}async function Yi(e,t={}){Ue(),Wi();let n=t.sendToPeer?(k,x,C)=>Gt(t.sendToPeer,k,x,C):null,o=[],r=[],s={...t,_stubWarn:k=>r.push(k)},i=bh();for(let k of et({status:"pending"}))k.dependsOn.length>0&&zo(k)&&await Kr(k,"blocked","pending",e,s);let a=0;n&&(a=await eg(et(),n,e));let l=et({status:"review"}).map(k=>k.id),c=eu(e.boardMaxRework,e,s);for(let k of l){let x=Be(k);x&&x.status!=="review"&&o.push({jobId:k,from:"review",to:x.status,note:x.supervisorNotes?.slice(0,80)})}let u=c.escalations,d=c.processed,m=0;for(let k of et())if(k.status==="done"||k.status==="failed"||k.status==="rework"){let x=ln(k.workDir);x&&Jr(k.id,Zc(x.steps))}let f=Xc("standard"),h=0,g=f.reason;if(f.ok){let k=kh();if(k&&!k.assignee){let x=Pt();k.assignee=tg(k.plan,x),jt(k)}if(k&&k.assignee&&zo(k)){let x=k.resourceProfile,C=Xc(x);if(C.ok){Qf(k.id,x),k.startedAtMs=Date.now();let M=k.status;Jt(k,"running",k.assignee),await Jh(k,e,t),await Kr(k,M,"running",e,s,{detail:`role ${k.assignee}`,actorFrom:"coordinator",actorTo:k.assignee??"specialist"});let R=M;if(await hC(k,e,s),h=1,g="ok",Be(k.id)?.status==="review"){let A=eu(e.boardMaxRework,e,s);d+=A.processed,m+=A.processed,u=[...u,...A.escalations];let q=Be(k.id);q&&o.push({jobId:q.id,from:R,to:q.status,note:q.status==="done"?"supervisor pass":q.supervisorNotes?.slice(0,80)??void 0})}}else g=C.reason}else k||(g="no runnable jobs")}let y=u.map(k=>k.id),b=et({status:"review"}).length;return{unblocked:i,reviewed:d,reviewedAfterDispatch:m,dispatched:h,escalations:y,waiting:g,progressNotified:a,outcomes:o,warnings:r,pendingReviewCount:b}}function Vi(e){Ue();let t=!1,n,o=async()=>{let s=e.getConfig();if(s.boardCoordinatorEnabled&&!t){t=!0;try{let i=await Yi(s,{sendToPeer:e.sendToPeer});if(i.escalations.length>0)for(let a of i.escalations){let l=`[board] Job ${a} failed after max rework.`;E.warn({jobId:a},"board escalation");try{let c=Be(a);c&&await e.sendToPeer(c.ownerPeerKey,l)}catch(c){E.warn({err:String(c)},"board escalation notify failed")}}}catch(i){E.warn({err:String(i)},"board coordinator tick failed")}finally{t=!1}}},r=()=>{let s=e.getConfig();return s.boardCoordinatorEnabled?s.boardCoordinatorIntervalMs:_i().dispatchIntervalMs};return o(),n=setInterval(()=>{o()},r()),()=>{n!==void 0&&(clearInterval(n),n=void 0),Yf()}}dt();ye();import Vr from"node:fs";import Qr from"node:path";function Yr(e,t,n){let o=new Date(e),r=o.getFullYear(),s=o.getMonth(),i=o.getDate(),a=new Date(r,s,i,t,n,0,0).getTime();return a<=e&&(a=new Date(r,s,i+1,t,n,0,0).getTime()),a}function gC(e,t,n){let o=e;for(let r=0;r<14;r++){let s=Yr(o,t,n),i=new Date(s).getDay();if(i>=1&&i<=5)return s;o=s}return Yr(o,t,n)}function yC(e,t,n,o){let r=e;for(let s=0;s<370;s++){let i=Yr(r,n,o);if(new Date(i).getDay()===t)return i;r=i}return Yr(r,n,o)}function wC(e,t){let n=new Date(e),o=n.getFullYear(),r=n.getMonth(),s=n.getDate(),i=n.getHours(),a=new Date(o,r,s,i,t,0,0).getTime();return a<=e&&(a=new Date(o,r,s,i+1,t,0,0).getTime()),a}function bC(e,t){return e.kind==="ondemand"||e.kind==="heartbeat"?Number.POSITIVE_INFINITY:e.kind==="daily"?Yr(t,e.hour,e.minute):e.kind==="weekdays"?gC(t,e.hour,e.minute):e.kind==="hourly"?wC(t,e.minute):yC(t,e.weekday,e.hour,e.minute)}function rg(e,t,n,o,r=32){if(e.kind==="ondemand"||e.kind==="heartbeat")return[];let s=t??n-1,i=[],a=s;for(;i.length<r;){let l=bC(e,a);if(l>o)break;i.push(l),a=l}return i}var kC={sun:0,sunday:0,mon:1,monday:1,tue:2,tues:2,tuesday:2,wed:3,wednesday:3,thu:4,thur:4,thurs:4,thursday:4,fri:5,friday:5,sat:6,saturday:6};function nu(e){let t=/^(\d{1,2}):(\d{2})$/.exec(e.trim());if(!t)return null;let n=Number(t[1]),o=Number(t[2]);return!Number.isFinite(n)||!Number.isFinite(o)||n>23||o>59?null:{hour:n,minute:o}}function SC(e){let t=e.trim(),n=/^:(\d{1,2})$/.exec(t),o=/^(\d{1,2})$/.exec(t),r=n?n[1]:o?o[1]:null;if(r===null)return null;let s=Number(r);return!Number.isInteger(s)||s<0||s>59?null:s}function Qi(e){if(e.length===0)return{ok:!1,error:"Missing schedule (try: ondemand, hourly [:MM], daily HH:MM, weekdays HH:MM, weekly <dow> HH:MM)."};let t=e[0].toLowerCase();if(t==="ondemand"||t==="manual")return e.length!==1?{ok:!1,error:"ondemand takes no extra tokens."}:{ok:!0,schedule:{kind:"ondemand"}};if(t==="daily"){if(e.length!==2)return{ok:!1,error:"Usage: daily HH:MM"};let n=nu(e[1]);return n?{ok:!0,schedule:{kind:"daily",hour:n.hour,minute:n.minute}}:{ok:!1,error:"daily needs HH:MM (24h)."}}if(t==="weekdays"){if(e.length!==2)return{ok:!1,error:"Usage: weekdays HH:MM"};let n=nu(e[1]);return n?{ok:!0,schedule:{kind:"weekdays",hour:n.hour,minute:n.minute}}:{ok:!1,error:"weekdays needs HH:MM (24h)."}}if(t==="hourly"){if(e.length===1)return{ok:!0,schedule:{kind:"hourly",minute:0}};if(e.length!==2)return{ok:!1,error:"Usage: hourly | hourly :MM | hourly MM (minute 0\u201359)."};let n=SC(e[1]);return n===null?{ok:!1,error:"hourly needs :MM or MM (minute 0\u201359), e.g. hourly :30 or hourly 15."}:{ok:!0,schedule:{kind:"hourly",minute:n}}}if(t==="weekly"){if(e.length!==3)return{ok:!1,error:"Usage: weekly <mon|tue|\u2026|sun> HH:MM"};let n=e[1].toLowerCase(),o=kC[n];if(o===void 0){let s=Number(n);Number.isInteger(s)&&s>=0&&s<=6&&(o=s)}if(o===void 0)return{ok:!1,error:`Unknown weekday "${e[1]}". Use mon\u2026sun or 0\u20136 (Sun=0).`};let r=nu(e[2]);return r?{ok:!0,schedule:{kind:"weekly",weekday:o,hour:r.hour,minute:r.minute}}:{ok:!1,error:"weekly needs HH:MM (24h) after weekday."}}if(t==="heartbeat"){if(e.length<2||e.length>3)return{ok:!1,error:"Usage: heartbeat <interval> [grace] \u2014 e.g. heartbeat 1h, heartbeat 30m 10m"};let n=ng(e[1]);if(n===null||n<6e4)return{ok:!1,error:"heartbeat interval must be >= 1m. Use 5m, 1h, 2h, 1d, etc."};let o=Math.floor(n*.5);if(e.length===3){let r=ng(e[2]);if(r===null||r<3e4)return{ok:!1,error:"heartbeat grace must be >= 30s. Use 1m, 5m, etc."};o=r}return{ok:!0,schedule:{kind:"heartbeat",intervalMs:n,graceMs:o}}}return{ok:!1,error:`Unknown schedule kind "${t}".`}}function ng(e){let t=e.trim().toLowerCase();if(!t)return null;let n=t.match(/^(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/);if(!n||t===""||!n[1]&&!n[2]&&!n[3]&&!n[4])return null;let o=n[1]?Number(n[1]):0,r=n[2]?Number(n[2]):0,s=n[3]?Number(n[3]):0,i=n[4]?Number(n[4]):0;return((o*24+r)*60+s)*6e4+i*1e3}function og(e){let t=Math.floor(e/1e3),n=Math.floor(t/86400),o=Math.floor(t%86400/3600),r=Math.floor(t%3600/60),s=[];return n>0&&s.push(`${n}d`),o>0&&s.push(`${o}h`),r>0&&s.push(`${r}m`),s.length>0?s.join(""):`${t}s`}function Xo(e){let t=o=>o<10?`0${o}`:String(o),n=(o,r)=>`${t(o)}:${t(r)}`;switch(e.kind){case"ondemand":return"ondemand";case"daily":return`daily ${n(e.hour,e.minute)}`;case"weekdays":return`weekdays ${n(e.hour,e.minute)}`;case"hourly":return e.minute===0?"hourly":`hourly :${t(e.minute)}`;case"weekly":return`weekly ${["sun","mon","tue","wed","thu","fri","sat"][e.weekday]??e.weekday} ${n(e.hour,e.minute)}`;case"heartbeat":return`heartbeat every ${og(e.intervalMs)} grace ${og(e.graceMs)}`}}j();ye();import vC from"better-sqlite3";var sg=2,_n=null;function ig(){D(It);let e=new vC($d);e.pragma("journal_mode = WAL"),e.exec(`
418
419
  CREATE TABLE IF NOT EXISTS cowork_meta (
419
420
  key TEXT PRIMARY KEY,
420
421
  value TEXT NOT NULL
@@ -433,129 +434,129 @@ ${a}`}return s}case"failed":return`[board] ${e.jobId}${r} \xB7 failed${o}`;case"
433
434
  last_ok INTEGER NOT NULL,
434
435
  updated_at_ms INTEGER NOT NULL
435
436
  );
436
- `);let t=e.prepare("SELECT value FROM cowork_meta WHERE key = 'schema_version'").get(),n=t?Number(t.value):0;return n<tg&&(n<2&&e.exec(`
437
+ `);let t=e.prepare("SELECT value FROM cowork_meta WHERE key = 'schema_version'").get(),n=t?Number(t.value):0;return n<sg&&(n<2&&e.exec(`
437
438
  CREATE TABLE IF NOT EXISTS cowork_task_state (
438
439
  task_id TEXT PRIMARY KEY,
439
440
  last_ok INTEGER NOT NULL,
440
441
  updated_at_ms INTEGER NOT NULL
441
442
  );
442
- `),e.prepare("INSERT OR REPLACE INTO cowork_meta (key, value) VALUES ('schema_version', ?)").run(String(tg))),e}function ao(e){_n||(_n=ng()),gC(e)}function og(){if(_n){try{_n.close()}catch{}_n=null}}function lo(){return _n||(_n=ng()),_n}function hC(e){let n=lo().prepare("SELECT MAX(slot_ms) AS m FROM cowork_slot_completion WHERE task_id = ?").get(e)?.m;return n==null||!Number.isFinite(n)?null:n}function Qi(e){let t=hC(e.id);return t??e.lastCompletedSlotMs}function Xi(e,t){if(t.length===0)return;let n=lo(),o=n.prepare(`
443
+ `),e.prepare("INSERT OR REPLACE INTO cowork_meta (key, value) VALUES ('schema_version', ?)").run(String(sg))),e}function ao(e){_n||(_n=ig()),CC(e)}function ag(){if(_n){try{_n.close()}catch{}_n=null}}function lo(){return _n||(_n=ig()),_n}function xC(e){let n=lo().prepare("SELECT MAX(slot_ms) AS m FROM cowork_slot_completion WHERE task_id = ?").get(e)?.m;return n==null||!Number.isFinite(n)?null:n}function Xi(e){let t=xC(e.id);return t??e.lastCompletedSlotMs}function Zi(e,t){if(t.length===0)return;let n=lo(),o=n.prepare(`
443
444
  INSERT INTO cowork_slot_completion (task_id, slot_ms, kind, completed_at_ms, log_path)
444
445
  VALUES (?, ?, ?, ?, ?)
445
- `);n.transaction(()=>{for(let s of t)o.run(e,s.slotMs,s.kind,s.completedAtMs,s.logPath)})()}function eu(e){lo().prepare("INSERT OR REPLACE INTO cowork_task_state (task_id, last_ok, updated_at_ms) VALUES (?, 1, ?)").run(e,Date.now())}function Zi(e){return lo().prepare("SELECT updated_at_ms FROM cowork_task_state WHERE task_id = ?").get(e)?.updated_at_ms??null}function ea(e){let t=lo().prepare("SELECT last_ok FROM cowork_task_state WHERE task_id = ?").get(e);return t==null?null:t.last_ok===1}function ta(e,t){lo().prepare("INSERT OR REPLACE INTO cowork_task_state (task_id, last_ok, updated_at_ms) VALUES (?, ?, ?)").run(e,t?1:0,Date.now())}function gC(e){let n=lo().prepare("SELECT COUNT(*) AS c FROM cowork_slot_completion WHERE task_id = ?"),o=Date.now();for(let r of e)if(!(r.lastCompletedSlotMs==null||!Number.isFinite(r.lastCompletedSlotMs)||n.get(r.id).c>0))try{Xi(r.id,[{slotMs:r.lastCompletedSlotMs,kind:"migrated",logPath:null,completedAtMs:o}]),P.info({taskId:r.id,slotMs:r.lastCompletedSlotMs},"cowork seeded completion from tasks.json")}catch(i){P.warn({err:String(i),taskId:r.id},"cowork seed from tasks.json failed")}}function tu(e,t){if(e.startsWith("tg:")){let n=e.slice(3);return Ps(t.telegramAllowFrom).has(n)}return Id(Es(t.allowFrom),e.replace(/^wa:/,""))}var yC=8,rg=12e4,sg=10,wC=1e3;function bC(e){return e.toISOString().replace(/[:.]/g,"-")}function kC(e){let t="";for(let n of e)n==="*"?t+="[^/\\\\]*":n==="?"?t+="[^/\\\\]":/[.+^${}()|[\]\\]/.test(n)?t+=`\\${n}`:t+=n;return new RegExp(`^${t}$`)}function SC(e,t,n){let o=at(e,t),r=Qr.dirname(o),s=Qr.basename(o);if(r.includes("*")||r.includes("?"))return[];if(!s.includes("*")&&!s.includes("?")){try{let c=Vr.statSync(o);if(c.isFile()&&c.mtimeMs>=n)return[o]}catch{}return[]}let i;try{i=Vr.readdirSync(r)}catch{return[]}let a=kC(s),l=[];for(let c of i){if(!a.test(c))continue;let u=Qr.join(r,c);try{let d=Vr.statSync(u);d.isFile()&&d.mtimeMs>=n&&l.push(u)}catch{}}return l}function ig(e,t,n){let o=Ct(e,t.fileSendMaxBytes);return"error"in o?{ok:!1,displayName:n??Qr.basename(e),reason:o.error.replace(/\.$/,"").toLowerCase()}:{ok:!0,spec:{absPath:o.absPath,category:o.category,mimetype:o.mimetype,displayName:n??o.displayName}}}async function ag(e,t,n,o){let r=oe(t.ownerPeerKey),s=t.cwd.trim()?t.cwd:r.cwd,i=at(t.outputDir,r.cwd);try{Vr.mkdirSync(i,{recursive:!0,mode:448})}catch(B){P.warn({err:String(B),outDir:i},"cowork mkdir outputDir")}let a=n.slotMs!==null?new Date(n.slotMs).toLocaleString(void 0,{dateStyle:"short",timeStyle:"short"}):"on-demand",l=n.onDemand?"on-demand":n.catchUp?"catch-up":"scheduled",c=Date.now(),u=await ct(e.shell,t.command,{timeoutMs:e.syncTimeoutMs,maxBytes:e.syncMaxBytes,cwd:s}),d=Date.now()-c,m=`${bC(new Date)}-${t.id}-${l}.log`,f=Qr.join(i,m),g=[`cowork task=${t.name} id=${t.id}`,`slot=${a} kind=${l}`,`cwd=${s}`,`exit=${u.code} timedOut=${u.timedOut} durationMs=${d}`,"---",""].join(`
446
+ `);n.transaction(()=>{for(let s of t)o.run(e,s.slotMs,s.kind,s.completedAtMs,s.logPath)})()}function ou(e){lo().prepare("INSERT OR REPLACE INTO cowork_task_state (task_id, last_ok, updated_at_ms) VALUES (?, 1, ?)").run(e,Date.now())}function ea(e){return lo().prepare("SELECT updated_at_ms FROM cowork_task_state WHERE task_id = ?").get(e)?.updated_at_ms??null}function ta(e){let t=lo().prepare("SELECT last_ok FROM cowork_task_state WHERE task_id = ?").get(e);return t==null?null:t.last_ok===1}function na(e,t){lo().prepare("INSERT OR REPLACE INTO cowork_task_state (task_id, last_ok, updated_at_ms) VALUES (?, ?, ?)").run(e,t?1:0,Date.now())}function CC(e){let n=lo().prepare("SELECT COUNT(*) AS c FROM cowork_slot_completion WHERE task_id = ?"),o=Date.now();for(let r of e)if(!(r.lastCompletedSlotMs==null||!Number.isFinite(r.lastCompletedSlotMs)||n.get(r.id).c>0))try{Zi(r.id,[{slotMs:r.lastCompletedSlotMs,kind:"migrated",logPath:null,completedAtMs:o}]),E.info({taskId:r.id,slotMs:r.lastCompletedSlotMs},"cowork seeded completion from tasks.json")}catch(i){E.warn({err:String(i),taskId:r.id},"cowork seed from tasks.json failed")}}function ru(e,t){if(e.startsWith("tg:")){let n=e.slice(3);return Ps(t.telegramAllowFrom).has(n)}return Nd(Es(t.allowFrom),e.replace(/^wa:/,""))}var RC=8,lg=12e4,cg=10,TC=1e3;function EC(e){return e.toISOString().replace(/[:.]/g,"-")}function PC(e){let t="";for(let n of e)n==="*"?t+="[^/\\\\]*":n==="?"?t+="[^/\\\\]":/[.+^${}()|[\]\\]/.test(n)?t+=`\\${n}`:t+=n;return new RegExp(`^${t}$`)}function $C(e,t,n){let o=at(e,t),r=Qr.dirname(o),s=Qr.basename(o);if(r.includes("*")||r.includes("?"))return[];if(!s.includes("*")&&!s.includes("?")){try{let c=Vr.statSync(o);if(c.isFile()&&c.mtimeMs>=n)return[o]}catch{}return[]}let i;try{i=Vr.readdirSync(r)}catch{return[]}let a=PC(s),l=[];for(let c of i){if(!a.test(c))continue;let u=Qr.join(r,c);try{let d=Vr.statSync(u);d.isFile()&&d.mtimeMs>=n&&l.push(u)}catch{}}return l}function ug(e,t,n){let o=Ct(e,t.fileSendMaxBytes);return"error"in o?{ok:!1,displayName:n??Qr.basename(e),reason:o.error.replace(/\.$/,"").toLowerCase()}:{ok:!0,spec:{absPath:o.absPath,category:o.category,mimetype:o.mimetype,displayName:n??o.displayName}}}async function dg(e,t,n,o){let r=oe(t.ownerPeerKey),s=t.cwd.trim()?t.cwd:r.cwd,i=at(t.outputDir,r.cwd);try{Vr.mkdirSync(i,{recursive:!0,mode:448})}catch(B){E.warn({err:String(B),outDir:i},"cowork mkdir outputDir")}let a=n.slotMs!==null?new Date(n.slotMs).toLocaleString(void 0,{dateStyle:"short",timeStyle:"short"}):"on-demand",l=n.onDemand?"on-demand":n.catchUp?"catch-up":"scheduled",c=Date.now(),u=await ct(e.shell,t.command,{timeoutMs:e.syncTimeoutMs,maxBytes:e.syncMaxBytes,cwd:s}),d=Date.now()-c,m=`${EC(new Date)}-${t.id}-${l}.log`,f=Qr.join(i,m),g=[`cowork task=${t.name} id=${t.id}`,`slot=${a} kind=${l}`,`cwd=${s}`,`exit=${u.code} timedOut=${u.timedOut} durationMs=${d}`,"---",""].join(`
446
447
  `)+(u.stdout||"")+(u.stderr?`
447
448
  --- stderr ---
448
- ${u.stderr}`:"");try{Vr.writeFileSync(f,g,{mode:384})}catch(B){P.warn({err:String(B),logPath:f},"cowork write log")}let y=u.code===0&&!u.timedOut&&u.signal===null,b=Oe().find(B=>B.id===t.id&&B.ownerPeerKey===t.ownerPeerKey),x=b?.notify??t.notify,C=b?.notifyWhen??t.notifyWhen??"always",M=b?.attachLog??t.attachLog,R=b?.attachFiles??t.attachFiles,N=!0;if(C==="failure")N=!y;else if(C==="state-change"){let B=ea(t.id);N=B===null||B!==y}ta(t.id,y);let A=u.timedOut?"timeout":u.signal?`signal ${u.signal}`:u.code!==0&&u.code!==null?`exit ${u.code}`:null,q=`slot: ${a} \xB7 ${l}${A?` \xB7 ${A}`:""}`,ee=(u.stdout||"").replace(/\s+$/,""),ce=(u.stderr||"").trim(),Pe=ee||(ce?`(stderr) ${ce}`:"(no output)"),Ke=C==="state-change"?y?" [recovered]":" [failing]":"",F=`${t.name}${Ke} : ${A?`[${A}] `:""}${Pe}`,ie=n.onDemand?[q,`output: ${Pe}`]:[F];if(N){let B=[],I=[],z=c-wC,G=[],Y=new Set;if(Array.isArray(R))for(let re of R){let De;try{De=SC(re,s,z)}catch(tt){P.warn({err:String(tt),pat:re},"cowork attach glob"),B.push(`attach: ${re} skipped (glob error)`);continue}if(De.length!==0)for(let tt of De)Y.has(tt)||(Y.add(tt),G.push(tt))}if(M){let re=ig(f,e,`${t.name}-${l}.log`);re.ok?I.push(re.spec):B.push(`attach: ${re.displayName} skipped (${re.reason})`)}let ae=0;for(let re of G){if(I.length>=sg){ae+=1;continue}let De=ig(re,e);De.ok?I.push(De.spec):B.push(`attach: ${De.displayName} skipped (${De.reason})`)}ae>0&&B.push(`attached: skipped ${ae} file(s) over cap ${sg}`),B.length>0&&ie.push(...B);let ne=ie.join(`
449
- `),Ye=p(ne),we=To(x,t.ownerPeerKey,e);for(let re of we){let De=pe(Ye,re.startsWith("tg:")?"telegram":"whatsapp").text;try{await o.sendToPeer(re,De)}catch(tt){P.warn({err:String(tt),pk:re},"cowork notify failed")}for(let tt of I)try{await o.sendMediaToPeer(re,tt)}catch(O){P.warn({err:String(O),pk:re,file:tt.displayName},"cowork media notify failed")}}}return{commandOk:y,logPath:f}}function na(e){let t=!1;ao(Oe());let n=async()=>{if(t)return;t=!0;let s=Date.now(),i=0,a=0,l=0,c=0,u=0;try{let d=e.getConfig(),{batch:m,remainingAfter:f}=Dd(yC);a=m.length,l=f,i=m.length+f;for(let y of m)try{let b=Oe().find(x=>x.name===y.name.toLowerCase()&&x.ownerPeerKey===y.ownerPeerKey);if(b&&b.enabled){if(!tu(b.ownerPeerKey,d)){P.warn({task:b.name,peer:b.ownerPeerKey},"cowork: skipping on-demand run \u2014 owner no longer on allowlist");continue}c+=1,await ag(d,b,{slotMs:null,catchUp:!1,onDemand:!0},e)}}catch(k){P.warn({err:String(k),pending:y.name},"cowork on-demand run failed")}let h=Oe();ao(h);let g=Date.now();for(let y of h){if(!y.enabled||y.schedule.kind!=="heartbeat"||!tu(y.ownerPeerKey,d))continue;let k=Zi(y.id);if(k===null)continue;let b=k+y.schedule.intervalMs+y.schedule.graceMs;if(g>b){if(ea(y.id)===!1)continue;ta(y.id,!1);let C=Math.round((g-k)/6e4),M=`${y.name} [heartbeat missed] \u2014 last check-in ${C}m ago`,R=y.notify,N=To(R,y.ownerPeerKey,d);for(let A of N)try{await e.sendToPeer(A,M)}catch(q){P.warn({err:String(q),pk:A},"cowork heartbeat notify failed")}}else if(g<=b&&ea(y.id)===!1){ta(y.id,!0);let C=`${y.name} [heartbeat recovered]`,M=y.notify,R=To(M,y.ownerPeerKey,d);for(let N of R)try{await e.sendToPeer(N,C)}catch(A){P.warn({err:String(A),pk:N},"cowork heartbeat recovery notify failed")}}}for(let y of h){if(!y.enabled||y.schedule.kind==="ondemand"||y.schedule.kind==="heartbeat")continue;if(!tu(y.ownerPeerKey,d)){P.warn({task:y.name,peer:y.ownerPeerKey},"cowork: skipping scheduled run \u2014 owner no longer on allowlist");continue}let k=Qi(y),b=eg(y.schedule,k,y.createdAtMs,g);if(b.length===0)continue;let x=b[b.length-1],C=g-x>rg;try{u+=1;let{commandOk:M,logPath:R}=await ag(d,y,{slotMs:x,catchUp:C,onDemand:!1},e);if(M){let N=Date.now(),A=g-x<=rg?"on_time":"catch_up";if(b.length===1)Xi(y.id,[{slotMs:x,kind:A,logPath:R,completedAtMs:N}]);else{let ee=b.slice(0,-1).map(ce=>({slotMs:ce,kind:"coalesced",logPath:null,completedAtMs:N}));ee.push({slotMs:x,kind:A,logPath:R,completedAtMs:N}),Xi(y.id,ee)}}}catch(M){P.warn({err:String(M),task:y.name},"cowork scheduled run failed")}}}finally{let d=Date.now()-s;P.info({tickMs:d,pendingDepthStart:i,pendingDequeued:a,pendingRemainingAfter:l,pendingRunsStarted:c,scheduledRan:u},"cowork tick"),t=!1}},o=setInterval(()=>void n(),3e4),r=setTimeout(()=>void n(),5e3);return()=>{clearInterval(o),clearTimeout(r),og()}}import vC from"node:fs";import lg from"node:path";import{fileURLToPath as xC}from"node:url";var oa=null;function gt(){if(oa!==null)return oa;let e=lg.dirname(xC(import.meta.url)),t=lg.join(e,"..","package.json"),n=vC.readFileSync(t,"utf8"),o=JSON.parse(n);return oa=typeof o.version=="string"&&o.version.trim()?o.version.trim():"0.0.0",oa}import{spawn as CC}from"node:child_process";var cg="omnish i -c '/sendto peer -t <title> -- <brief>'";function Xr(e,t,n){let o={OMNISH_PEER_KEY:e,OMNISH_NOTIFY_CMD:cg};if(!t.recipesNotifyEnabled)return o;let r=[Gr,"","For mobile-friendly updates use:",` ${cg}`,n?.mutedByDefault?"Session started muted: send concise status updates instead of streaming logs.":"Prefer concise status updates for long-running work."].join(`
450
- `);return o.OMNISH_COMMUNICATION_BRIEF=r,o}be();var Fn=new Map;function nu(e,t){return e.chatAgentPerPeer===!1?"__shared__":t}function RC(e,t){let n=e.trim();if(!n)return["/bin/bash",["-lc",t]];let o=n.split(/\s+/).filter(Boolean),r=o[0]??"/bin/bash";return o.length===1?[r,["-lc",t]]:[r,[...o.slice(1),"-c",t]]}function TC(e,t){let n=nu(e,t),o=Fn.get(n);if(o)return o;let r=Date.now(),s={key:n,peerHint:t,proc:null,queue:[],running:!1,startedAtMs:0,lastInputAtMs:r,lastOutputAtMs:0,lastError:""};return Fn.set(n,s),s}function EC(e,t){let n=(e.chatAgentCommand??"").trim();if(!n||t.proc)return;let[o,r]=RC(e.shell,n),s={...process.env,...Xr(t.peerHint,e,{mutedByDefault:!0})},i=CC(o,r,{stdio:"pipe",env:s});t.proc=i,t.running=!0,t.startedAtMs=Date.now(),t.lastError="",i.stdout.on("data",()=>{t.lastOutputAtMs=Date.now()}),i.stderr.on("data",()=>{t.lastOutputAtMs=Date.now()}),i.on("error",a=>{t.lastError=String(a),P.warn({err:String(a),key:t.key},"chat agent daemon process error")}),i.on("exit",(a,l)=>{t.running=!1,t.proc=null,t.lastError=`exited code=${a??"null"} signal=${l??"null"}`})}function PC(e,t){if(EC(e,t),!(!t.proc||!t.running))for(;t.queue.length>0;){let n=t.queue.shift();try{t.proc.stdin.write(n+`
451
- `),t.lastInputAtMs=Date.now()}catch(o){t.lastError=String(o);break}}}function ra(e,t,n){if(!(e.chatAgentCommand??"").trim())return{ok:!1,error:"chat agent command is empty"};let r=TC(e,t),s=Math.max(1,e.chatAgentMaxQueue??64);return r.queue.length>=s?{ok:!1,error:`agent queue full (${s})`}:(r.queue.push(n),PC(e,r),{ok:!0})}async function Zr(e,t,n){let o=nu(e,t),r=Fn.get(o);if(!r)return!1;try{r.proc?.kill("SIGTERM")}catch{}return r.proc=null,r.running=!1,r.queue.length=0,n&&await n(t,`[agent] stopped ${o==="__shared__"?"shared":"peer"} daemon`),!0}async function sa(e,t,n){let o=nu(e,t),r=await Zr(e,t);return Fn.delete(o),r&&n&&await n(t,"[agent] reset complete"),r}function ia(){if(Fn.size===0)return"No agent daemons started.";let e=["Agent daemons:"];for(let[t,n]of Fn){let o=n.startedAtMs>0?Math.max(0,Math.floor((Date.now()-n.startedAtMs)/1e3)):0;e.push(`- ${t}: running=${n.running} queued=${n.queue.length} age=${o}s peer=${n.peerHint}${n.lastError?` error=${n.lastError}`:""}`)}return e.join(`
452
- `)}function aa(){for(let[,e]of Fn)try{e.proc?.kill("SIGTERM")}catch{}Fn.clear()}j();import{spawn as $C}from"node:child_process";import MC from"node:crypto";import ut from"node:fs";import ou from"node:path";var ug=64,AC=/^[a-zA-Z0-9._-]+$/;function IC(e){let t=e.trim();return t?t.length>ug?`Job name must be at most ${ug} characters.`:AC.test(t)?null:"Job name may only use letters, digits, and . _ -":"Job name must not be empty."}function dg(e){let t=e.trim();if(!t)return{error:"empty"};let n=null,o=!1,r=!0;for(;r;){r=!1;let i=t.trim();if(/^--notify(?:\s|$)/i.test(i)){o=!0,t=i.slice(8).trim(),r=!0;continue}if(/^-N(?:\s|$)/.test(i)){o=!0,t=i.slice(2).trim(),r=!0;continue}let a=[[/^--name\s*=\s*(\S+)\s+([\s\S]+)$/i,1,2],[/^--name\s+(\S+)\s+([\s\S]+)$/i,1,2],[/^-n\s+(\S+)\s+([\s\S]+)$/i,1,2]];for(let[l,c,u]of a){let d=i.match(l);if(d){n=d[c],t=d[u].trim(),r=!0;break}}}let s=t.trim();if(n===null&&[/^-n\s+\S+\s*$/i,/^--name\s+\S+\s*$/i,/^--name=\S+\s*$/i,/^-n\s*$/i,/^--name\s*$/i].some(a=>a.test(s)))return{error:"Add a shell command after the name."};if(n!==null){let i=IC(n);if(i)return{error:i}}return s?{cmd:s,name:n,notify:o}:{error:"Add a shell command after the flags."}}function OC(e,t){let n=t.trim();if(!n)return{ok:!1,error:"Missing job id or name."};let o=n.toLowerCase(),r=/^[a-f0-9]{8}$/;if(r.test(o)&&e.find(i=>i.id===o))return{ok:!0,id:o};for(let s of e)if(s.name&&s.name.toLowerCase()===o)return{ok:!0,id:s.id};return r.test(o)?{ok:!1,error:`Unknown job id: ${o}`}:{ok:!1,error:`Unknown job name: ${n}`}}function es(e,t){ut.writeFileSync(e,JSON.stringify(t,null,2)+`
453
- `,{mode:384})}function ts(e){try{return JSON.parse(ut.readFileSync(e,"utf8"))}catch{return null}}function la(e){let t=e.name?`${e.id} (${e.name})`:e.id,n=e.finishedAt&&e.startedAt?Date.parse(e.finishedAt)-Date.parse(e.startedAt):null,o=n!==null?`${(n/1e3).toFixed(1)}s`:"?",r=e.exitCode===0&&!e.signal,s=e.status==="killed"?`killed (signal ${e.signal??"?"})`:r?"completed successfully":e.signal?`signal ${e.signal}`:`exit ${e.exitCode??"?"}`,i=e.cmd.length>100?`${e.cmd.slice(0,100)}\u2026`:e.cmd;return[`Job ${t} ${s}`,`duration: ${o}`,`$ ${i}`,`/log ${e.name??e.id}`].join(`
454
- `)}var cn=class{running=new Map;onJobExit;constructor(t={}){this.onJobExit=t.onJobExit}finishJob(t,n,o,r){es(t,o),r.onComplete&&r.onComplete(o),this.onJobExit&&this.onJobExit(o)}metaPath(t){return ou.join(wt,`${t}.meta.json`)}logPath(t){return ou.join(wt,`${t}.log`)}spawnJob(t,n,o={}){se();let r=MC.randomBytes(4).toString("hex"),s=this.logPath(r),i=this.metaPath(r);ut.writeFileSync(s,"",{flag:"w",mode:384});let a=ut.createWriteStream(s,{flags:"a"}),l=new Date().toISOString(),c=o.cwd,u=$C(t,["-c",n],{stdio:["ignore","pipe","pipe"],env:{...process.env,TERM:"dumb",...c?{PWD:c}:{}},...c?{cwd:c}:{}});this.running.set(r,u);let d={id:r,cmd:n,...o.name?{name:o.name}:{},pid:u.pid??null,startedAt:l,status:"running",exitCode:null,signal:null,...o.notifyPeerKey?{notifyPeerKey:o.notifyPeerKey}:{}};return es(i,d),u.stdout?.on("data",m=>{a.write(m)}),u.stderr?.on("data",m=>{a.write(m)}),u.on("close",(m,f)=>{this.running.delete(r),a.end();let h=ts(i)??d,g={...h,status:h.status==="killed"?"killed":"done",exitCode:m,signal:f??null,finishedAt:new Date().toISOString()};this.finishJob(i,d,g,o)}),u.on("error",m=>{this.running.delete(r),a.end(()=>{try{ut.appendFileSync(s,`
449
+ ${u.stderr}`:"");try{Vr.writeFileSync(f,g,{mode:384})}catch(B){E.warn({err:String(B),logPath:f},"cowork write log")}let y=u.code===0&&!u.timedOut&&u.signal===null,k=Oe().find(B=>B.id===t.id&&B.ownerPeerKey===t.ownerPeerKey),x=k?.notify??t.notify,C=k?.notifyWhen??t.notifyWhen??"always",M=k?.attachLog??t.attachLog,R=k?.attachFiles??t.attachFiles,N=!0;if(C==="failure")N=!y;else if(C==="state-change"){let B=ta(t.id);N=B===null||B!==y}na(t.id,y);let A=u.timedOut?"timeout":u.signal?`signal ${u.signal}`:u.code!==0&&u.code!==null?`exit ${u.code}`:null,q=`slot: ${a} \xB7 ${l}${A?` \xB7 ${A}`:""}`,ee=(u.stdout||"").replace(/\s+$/,""),ce=(u.stderr||"").trim(),Pe=ee||(ce?`(stderr) ${ce}`:"(no output)"),Ke=C==="state-change"?y?" [recovered]":" [failing]":"",F=`${t.name}${Ke} : ${A?`[${A}] `:""}${Pe}`,ie=n.onDemand?[q,`output: ${Pe}`]:[F];if(N){let B=[],I=[],z=c-TC,G=[],Y=new Set;if(Array.isArray(R))for(let re of R){let De;try{De=$C(re,s,z)}catch(tt){E.warn({err:String(tt),pat:re},"cowork attach glob"),B.push(`attach: ${re} skipped (glob error)`);continue}if(De.length!==0)for(let tt of De)Y.has(tt)||(Y.add(tt),G.push(tt))}if(M){let re=ug(f,e,`${t.name}-${l}.log`);re.ok?I.push(re.spec):B.push(`attach: ${re.displayName} skipped (${re.reason})`)}let ae=0;for(let re of G){if(I.length>=cg){ae+=1;continue}let De=ug(re,e);De.ok?I.push(De.spec):B.push(`attach: ${De.displayName} skipped (${De.reason})`)}ae>0&&B.push(`attached: skipped ${ae} file(s) over cap ${cg}`),B.length>0&&ie.push(...B);let ne=ie.join(`
450
+ `),Ye=p(ne),be=To(x,t.ownerPeerKey,e);for(let re of be){let De=pe(Ye,re.startsWith("tg:")?"telegram":"whatsapp").text;try{await o.sendToPeer(re,De)}catch(tt){E.warn({err:String(tt),pk:re},"cowork notify failed")}for(let tt of I)try{await o.sendMediaToPeer(re,tt)}catch(O){E.warn({err:String(O),pk:re,file:tt.displayName},"cowork media notify failed")}}}return{commandOk:y,logPath:f}}function oa(e){let t=!1;ao(Oe());let n=async()=>{if(t)return;t=!0;let s=Date.now(),i=0,a=0,l=0,c=0,u=0;try{let d=e.getConfig(),{batch:m,remainingAfter:f}=Bd(RC);a=m.length,l=f,i=m.length+f;for(let y of m)try{let k=Oe().find(x=>x.name===y.name.toLowerCase()&&x.ownerPeerKey===y.ownerPeerKey);if(k&&k.enabled){if(!ru(k.ownerPeerKey,d)){E.warn({task:k.name,peer:k.ownerPeerKey},"cowork: skipping on-demand run \u2014 owner no longer on allowlist");continue}c+=1,await dg(d,k,{slotMs:null,catchUp:!1,onDemand:!0},e)}}catch(b){E.warn({err:String(b),pending:y.name},"cowork on-demand run failed")}let h=Oe();ao(h);let g=Date.now();for(let y of h){if(!y.enabled||y.schedule.kind!=="heartbeat"||!ru(y.ownerPeerKey,d))continue;let b=ea(y.id);if(b===null)continue;let k=b+y.schedule.intervalMs+y.schedule.graceMs;if(g>k){if(ta(y.id)===!1)continue;na(y.id,!1);let C=Math.round((g-b)/6e4),M=`${y.name} [heartbeat missed] \u2014 last check-in ${C}m ago`,R=y.notify,N=To(R,y.ownerPeerKey,d);for(let A of N)try{await e.sendToPeer(A,M)}catch(q){E.warn({err:String(q),pk:A},"cowork heartbeat notify failed")}}else if(g<=k&&ta(y.id)===!1){na(y.id,!0);let C=`${y.name} [heartbeat recovered]`,M=y.notify,R=To(M,y.ownerPeerKey,d);for(let N of R)try{await e.sendToPeer(N,C)}catch(A){E.warn({err:String(A),pk:N},"cowork heartbeat recovery notify failed")}}}for(let y of h){if(!y.enabled||y.schedule.kind==="ondemand"||y.schedule.kind==="heartbeat")continue;if(!ru(y.ownerPeerKey,d)){E.warn({task:y.name,peer:y.ownerPeerKey},"cowork: skipping scheduled run \u2014 owner no longer on allowlist");continue}let b=Xi(y),k=rg(y.schedule,b,y.createdAtMs,g);if(k.length===0)continue;let x=k[k.length-1],C=g-x>lg;try{u+=1;let{commandOk:M,logPath:R}=await dg(d,y,{slotMs:x,catchUp:C,onDemand:!1},e);if(M){let N=Date.now(),A=g-x<=lg?"on_time":"catch_up";if(k.length===1)Zi(y.id,[{slotMs:x,kind:A,logPath:R,completedAtMs:N}]);else{let ee=k.slice(0,-1).map(ce=>({slotMs:ce,kind:"coalesced",logPath:null,completedAtMs:N}));ee.push({slotMs:x,kind:A,logPath:R,completedAtMs:N}),Zi(y.id,ee)}}}catch(M){E.warn({err:String(M),task:y.name},"cowork scheduled run failed")}}}finally{let d=Date.now()-s;E.info({tickMs:d,pendingDepthStart:i,pendingDequeued:a,pendingRemainingAfter:l,pendingRunsStarted:c,scheduledRan:u},"cowork tick"),t=!1}},o=setInterval(()=>void n(),3e4),r=setTimeout(()=>void n(),5e3);return()=>{clearInterval(o),clearTimeout(r),ag()}}import MC from"node:fs";import pg from"node:path";import{fileURLToPath as AC}from"node:url";var ra=null;function gt(){if(ra!==null)return ra;let e=pg.dirname(AC(import.meta.url)),t=pg.join(e,"..","package.json"),n=MC.readFileSync(t,"utf8"),o=JSON.parse(n);return ra=typeof o.version=="string"&&o.version.trim()?o.version.trim():"0.0.0",ra}import{spawn as IC}from"node:child_process";var mg="omnish i -c '/sendto peer -t <title> -- <brief>'";function Xr(e,t,n){let o={OMNISH_PEER_KEY:e,OMNISH_NOTIFY_CMD:mg};if(!t.recipesNotifyEnabled)return o;let r=[Gr,"","For mobile-friendly updates use:",` ${mg}`,n?.mutedByDefault?"Session started muted: send concise status updates instead of streaming logs.":"Prefer concise status updates for long-running work."].join(`
451
+ `);return o.OMNISH_COMMUNICATION_BRIEF=r,o}ye();var Fn=new Map;function su(e,t){return e.chatAgentPerPeer===!1?"__shared__":t}function OC(e,t){let n=e.trim();if(!n)return["/bin/bash",["-lc",t]];let o=n.split(/\s+/).filter(Boolean),r=o[0]??"/bin/bash";return o.length===1?[r,["-lc",t]]:[r,[...o.slice(1),"-c",t]]}function LC(e,t){let n=su(e,t),o=Fn.get(n);if(o)return o;let r=Date.now(),s={key:n,peerHint:t,proc:null,queue:[],running:!1,startedAtMs:0,lastInputAtMs:r,lastOutputAtMs:0,lastError:""};return Fn.set(n,s),s}function NC(e,t){let n=(e.chatAgentCommand??"").trim();if(!n||t.proc)return;let[o,r]=OC(e.shell,n),s={...process.env,...Xr(t.peerHint,e,{mutedByDefault:!0})},i=IC(o,r,{stdio:"pipe",env:s});t.proc=i,t.running=!0,t.startedAtMs=Date.now(),t.lastError="",i.stdout.on("data",()=>{t.lastOutputAtMs=Date.now()}),i.stderr.on("data",()=>{t.lastOutputAtMs=Date.now()}),i.on("error",a=>{t.lastError=String(a),E.warn({err:String(a),key:t.key},"chat agent daemon process error")}),i.on("exit",(a,l)=>{t.running=!1,t.proc=null,t.lastError=`exited code=${a??"null"} signal=${l??"null"}`})}function _C(e,t){if(NC(e,t),!(!t.proc||!t.running))for(;t.queue.length>0;){let n=t.queue.shift();try{t.proc.stdin.write(n+`
452
+ `),t.lastInputAtMs=Date.now()}catch(o){t.lastError=String(o);break}}}function sa(e,t,n){if(!(e.chatAgentCommand??"").trim())return{ok:!1,error:"chat agent command is empty"};let r=LC(e,t),s=Math.max(1,e.chatAgentMaxQueue??64);return r.queue.length>=s?{ok:!1,error:`agent queue full (${s})`}:(r.queue.push(n),_C(e,r),{ok:!0})}async function Zr(e,t,n){let o=su(e,t),r=Fn.get(o);if(!r)return!1;try{r.proc?.kill("SIGTERM")}catch{}return r.proc=null,r.running=!1,r.queue.length=0,n&&await n(t,`[agent] stopped ${o==="__shared__"?"shared":"peer"} daemon`),!0}async function ia(e,t,n){let o=su(e,t),r=await Zr(e,t);return Fn.delete(o),r&&n&&await n(t,"[agent] reset complete"),r}function aa(){if(Fn.size===0)return"No agent daemons started.";let e=["Agent daemons:"];for(let[t,n]of Fn){let o=n.startedAtMs>0?Math.max(0,Math.floor((Date.now()-n.startedAtMs)/1e3)):0;e.push(`- ${t}: running=${n.running} queued=${n.queue.length} age=${o}s peer=${n.peerHint}${n.lastError?` error=${n.lastError}`:""}`)}return e.join(`
453
+ `)}function la(){for(let[,e]of Fn)try{e.proc?.kill("SIGTERM")}catch{}Fn.clear()}j();import{spawn as FC}from"node:child_process";import DC from"node:crypto";import ut from"node:fs";import iu from"node:path";var fg=64,WC=/^[a-zA-Z0-9._-]+$/;function UC(e){let t=e.trim();return t?t.length>fg?`Job name must be at most ${fg} characters.`:WC.test(t)?null:"Job name may only use letters, digits, and . _ -":"Job name must not be empty."}function hg(e){let t=e.trim();if(!t)return{error:"empty"};let n=null,o=!1,r=!0;for(;r;){r=!1;let i=t.trim();if(/^--notify(?:\s|$)/i.test(i)){o=!0,t=i.slice(8).trim(),r=!0;continue}if(/^-N(?:\s|$)/.test(i)){o=!0,t=i.slice(2).trim(),r=!0;continue}let a=[[/^--name\s*=\s*(\S+)\s+([\s\S]+)$/i,1,2],[/^--name\s+(\S+)\s+([\s\S]+)$/i,1,2],[/^-n\s+(\S+)\s+([\s\S]+)$/i,1,2]];for(let[l,c,u]of a){let d=i.match(l);if(d){n=d[c],t=d[u].trim(),r=!0;break}}}let s=t.trim();if(n===null&&[/^-n\s+\S+\s*$/i,/^--name\s+\S+\s*$/i,/^--name=\S+\s*$/i,/^-n\s*$/i,/^--name\s*$/i].some(a=>a.test(s)))return{error:"Add a shell command after the name."};if(n!==null){let i=UC(n);if(i)return{error:i}}return s?{cmd:s,name:n,notify:o}:{error:"Add a shell command after the flags."}}function BC(e,t){let n=t.trim();if(!n)return{ok:!1,error:"Missing job id or name."};let o=n.toLowerCase(),r=/^[a-f0-9]{8}$/;if(r.test(o)&&e.find(i=>i.id===o))return{ok:!0,id:o};for(let s of e)if(s.name&&s.name.toLowerCase()===o)return{ok:!0,id:s.id};return r.test(o)?{ok:!1,error:`Unknown job id: ${o}`}:{ok:!1,error:`Unknown job name: ${n}`}}function es(e,t){ut.writeFileSync(e,JSON.stringify(t,null,2)+`
454
+ `,{mode:384})}function ts(e){try{return JSON.parse(ut.readFileSync(e,"utf8"))}catch{return null}}function ca(e){let t=e.name?`${e.id} (${e.name})`:e.id,n=e.finishedAt&&e.startedAt?Date.parse(e.finishedAt)-Date.parse(e.startedAt):null,o=n!==null?`${(n/1e3).toFixed(1)}s`:"?",r=e.exitCode===0&&!e.signal,s=e.status==="killed"?`killed (signal ${e.signal??"?"})`:r?"completed successfully":e.signal?`signal ${e.signal}`:`exit ${e.exitCode??"?"}`,i=e.cmd.length>100?`${e.cmd.slice(0,100)}\u2026`:e.cmd;return[`Job ${t} ${s}`,`duration: ${o}`,`$ ${i}`,`/log ${e.name??e.id}`].join(`
455
+ `)}var cn=class{running=new Map;onJobExit;constructor(t={}){this.onJobExit=t.onJobExit}finishJob(t,n,o,r){es(t,o),r.onComplete&&r.onComplete(o),this.onJobExit&&this.onJobExit(o)}metaPath(t){return iu.join(wt,`${t}.meta.json`)}logPath(t){return iu.join(wt,`${t}.log`)}spawnJob(t,n,o={}){se();let r=DC.randomBytes(4).toString("hex"),s=this.logPath(r),i=this.metaPath(r);ut.writeFileSync(s,"",{flag:"w",mode:384});let a=ut.createWriteStream(s,{flags:"a"}),l=new Date().toISOString(),c=o.cwd,u=FC(t,["-c",n],{stdio:["ignore","pipe","pipe"],env:{...process.env,TERM:"dumb",...c?{PWD:c}:{}},...c?{cwd:c}:{}});this.running.set(r,u);let d={id:r,cmd:n,...o.name?{name:o.name}:{},pid:u.pid??null,startedAt:l,status:"running",exitCode:null,signal:null,...o.notifyPeerKey?{notifyPeerKey:o.notifyPeerKey}:{}};return es(i,d),u.stdout?.on("data",m=>{a.write(m)}),u.stderr?.on("data",m=>{a.write(m)}),u.on("close",(m,f)=>{this.running.delete(r),a.end();let h=ts(i)??d,g={...h,status:h.status==="killed"?"killed":"done",exitCode:m,signal:f??null,finishedAt:new Date().toISOString()};this.finishJob(i,d,g,o)}),u.on("error",m=>{this.running.delete(r),a.end(()=>{try{ut.appendFileSync(s,`
455
456
  [spawn error] ${String(m)}
456
- `)}catch{}});let h={...ts(i)??d,status:"done",exitCode:null,signal:null,finishedAt:new Date().toISOString()};this.finishJob(i,d,h,o)}),{id:r,meta:d}}list(){se();let t=[];try{t=ut.readdirSync(wt)}catch{return[]}let n=[];for(let o of t){if(!o.endsWith(".meta.json"))continue;let r=ts(ou.join(wt,o));r&&n.push(r)}return n.sort((o,r)=>o.startedAt<r.startedAt?1:-1),n}tailLog(t,n){let o=this.logPath(t);if(!ut.existsSync(o))return"(no log file)";let s=ut.readFileSync(o,"utf8").split(`
457
+ `)}catch{}});let h={...ts(i)??d,status:"done",exitCode:null,signal:null,finishedAt:new Date().toISOString()};this.finishJob(i,d,h,o)}),{id:r,meta:d}}list(){se();let t=[];try{t=ut.readdirSync(wt)}catch{return[]}let n=[];for(let o of t){if(!o.endsWith(".meta.json"))continue;let r=ts(iu.join(wt,o));r&&n.push(r)}return n.sort((o,r)=>o.startedAt<r.startedAt?1:-1),n}tailLog(t,n){let o=this.logPath(t);if(!ut.existsSync(o))return"(no log file)";let s=ut.readFileSync(o,"utf8").split(`
457
458
  `);return s.slice(Math.max(0,s.length-n)).join(`
458
- `).trimEnd()||"(empty log)"}readSince(t,n){let o=this.logPath(t);if(!ut.existsSync(o))return{text:"",nextOffset:n};let s=ut.statSync(o).size;if(n>=s)return{text:"",nextOffset:s};let i=Buffer.alloc(s-n),a=ut.openSync(o,"r");try{ut.readSync(a,i,0,i.length,n)}finally{ut.closeSync(a)}return{text:i.toString("utf8"),nextOffset:s}}resolveJobRef(t){return OC(this.list(),t)}kill(t){let n=this.metaPath(t),o=ts(n);if(!o)return`Unknown job id: ${t}`;let r=this.running.get(t);if(r&&!r.killed)return r.kill("SIGTERM"),es(n,{...o,status:"killed",signal:"SIGTERM"}),`Sent SIGTERM to job ${t} (pid ${o.pid??"?"})`;if(o.pid)try{return process.kill(o.pid,"SIGTERM"),es(n,{...o,status:"killed",signal:"SIGTERM"}),`Sent SIGTERM to pid ${o.pid} (${t})`}catch{return`Job ${t} is not running (no live process).`}return`Job ${t} is not running.`}killAllRunning(){for(let[t,n]of this.running){n.killed||n.kill("SIGTERM");let o=ts(this.metaPath(t));o&&es(this.metaPath(t),{...o,status:"killed",signal:"SIGTERM"})}this.running.clear()}};import mR from"node:fs";import{Bot as fR,InputFile as hR}from"grammy";j();import pg from"node:fs";import Zo from"node:path";function LC(e){return e.replace(/[^a-zA-Z0-9._-]+/g,"_").slice(0,120)||"unknown"}function NC(e){let o=Zo.basename(e.trim()||"file").replace(/[^a-zA-Z0-9._-]+/g,"_").replace(/^\.+/,"").slice(0,180);return o.length>0?o:"file"}function _C(e,t){let n=new Date().toISOString().slice(0,10),o=LC(t);return Zo.join(e,o,n)}function er(e,t,n){let o=_C(e,t);D(o);let r=NC(n),s=Zo.join(o,r);if(!pg.existsSync(s))return s;let i=Zo.extname(r),a=i?r.slice(0,-i.length):r;for(let l=1;l<1e4;l+=1){let c=`${a}-${l}${i}`;if(s=Zo.join(o,c),!pg.existsSync(s))return s}return Zo.join(o,`${a}-${Date.now()}${i}`)}be();import FC from"node:dns";import DC from"node:https";import{URL as WC}from"node:url";function tr(e){if(e instanceof Error){let t=e.cause;return t!=null?`${e.message} (${String(t)})`:e.message}return String(e)}be();function mg(e){if("photo"in e&&e.photo&&e.photo.length>0)return{fileId:e.photo[e.photo.length-1].file_id,baseName:"photo.jpg"};if("document"in e&&e.document){let t=e.document,n=t.file_name?.trim();return{fileId:t.file_id,baseName:n&&n.length>0?n:"document.bin"}}if("video"in e&&e.video){let t=e.video,n=t.file_name?.trim();return{fileId:t.file_id,baseName:n&&n.length>0?n:"video.mp4"}}if("audio"in e&&e.audio){let t=e.audio,n=t.file_name?.trim();return{fileId:t.file_id,baseName:n&&n.length>0?n:"audio.m4a"}}if("voice"in e&&e.voice)return{fileId:e.voice.file_id,baseName:"voice.ogg"};if("video_note"in e&&e.video_note)return{fileId:e.video_note.file_id,baseName:"video_note.mp4"};if("sticker"in e&&e.sticker){let t=e.sticker,n=t.is_video?"webm":"webp";return{fileId:t.file_id,baseName:`sticker.${n}`}}return null}function UC(e,t){let o=t.split("/").filter(r=>r.length>0).map(encodeURIComponent).join("/");return`https://api.telegram.org/file/bot${e}/${o}`}var fg="omnish (Telegram bot; https://github.com/labKnowledge/whatsLive)";function BC(e){let t=new WC(e);return new Promise((n,o)=>{let r=DC.request({hostname:t.hostname,port:t.port||443,path:t.pathname+t.search,method:"GET",headers:{"User-Agent":fg,Accept:"*/*"},lookup(s,i,a){FC.lookup(s,{family:4},a)}},s=>{let i=s.statusCode??0,a=[];s.on("data",l=>a.push(l)),s.on("end",()=>n({status:i,buffer:Buffer.concat(a)})),s.on("error",o)});r.on("error",o),r.end()})}async function hg(e,t,n){let o;try{o=await e.api.getFile(t)}catch(i){return P.warn({err:String(i),fileId:t},"telegram getFile failed"),{error:"Could not resolve file on Telegram."}}if(!o.file_path)return{error:"Telegram did not return a file path."};let r=UC(e.token,o.file_path),s;try{let i=await fetch(r,{headers:{"User-Agent":fg,Accept:"*/*"}});if(!i.ok)return P.warn({status:i.status,statusText:i.statusText,fileId:t},"telegram file fetch HTTP error"),{error:`Could not download file from Telegram (HTTP ${i.status}${i.statusText?` ${i.statusText}`:""}).`};try{s=Buffer.from(await i.arrayBuffer())}catch(a){return P.warn({err:String(a),fileId:t},"telegram file read body failed"),{error:`Could not read file from Telegram (${String(a)}).`}}}catch(i){P.warn({err:String(i),fileId:t},"telegram file fetch failed; retrying via HTTPS IPv4");try{let a=await BC(r);if(a.status<200||a.status>=300)return P.warn({status:a.status,fileId:t},"telegram HTTPS IPv4 fallback HTTP error"),{error:`Could not download file from Telegram (HTTP ${a.status}).`};s=a.buffer}catch(a){return P.warn({err:String(i),err2:String(a),fileId:t},"telegram file download failed (fetch and IPv4 fallback)"),{error:`Could not download file from Telegram (network: ${tr(i)}; IPv4 fallback: ${tr(a)}).`}}}return n>0&&s.byteLength>n?{error:`Media too large (max ${n} bytes).`}:{buffer:s}}ue();dt();import{downloadMediaMessage as GC,extensionForMediaMessage as qC,getContentType as ca,isJidGroup as bg,isLidUser as zC}from"@whiskeysockets/baileys";import KC from"node:fs";be();var Dn=new Map;function jC(){let e=Date.now();for(let[t,n]of Dn)e-n>6e5&&Dn.delete(t);for(;Dn.size>500;){let t=Dn.keys().next().value;if(t===void 0)break;Dn.delete(t)}}function HC(e){!e||typeof e!="string"||(jC(),Dn.set(e,Date.now()))}function ru(e){HC(e?.key?.id??void 0)}function JC(e){if(!e)return!1;let t=Dn.get(e);return t===void 0?!1:Date.now()-t>6e5?(Dn.delete(e),!1):!0}function gg(e){return!JC(e)}function YC(e){if(!e||typeof e!="object")return"";let t=e;if(typeof t.conversation=="string")return t.conversation;let n=t.extendedTextMessage;return n&&typeof n.text=="string"?n.text:""}function VC(e){if(!e||typeof e!="object")return"";let t=e;for(let n of["imageMessage","videoMessage","documentMessage","audioMessage","stickerMessage"]){let o=t[n];if(o&&typeof o.caption=="string"&&o.caption.trim())return o.caption}return""}function kg(e){return[YC(e),VC(e)].filter(n=>n.trim().length>0).join(`
459
- `).trim()}function su(e){return e.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g,"").replace(/\uFEFF/g,"").trim()}function Sg(e){let t=ca(e??void 0);return t==="imageMessage"||t==="videoMessage"||t==="audioMessage"||t==="documentMessage"||t==="stickerMessage"}function QC(e){let t=ca(e??void 0);if(!t)return;let o=e?.[t]?.fileLength;if(typeof o=="number"&&Number.isFinite(o))return o;if(o&&typeof o=="object"&&"toNumber"in o&&typeof o.toNumber=="function")try{let r=o.toNumber();return Number.isFinite(r)?r:void 0}catch{return}}function yg(e){try{if(!e)return"file.bin";let t=qC(e);return`file${t&&t.length>0?t.startsWith(".")?t:`.${t}`:".bin"}`}catch{return`${(ca(e??void 0)??"file").replace("Message","")||"file"}.bin`}}function XC(e){let t=ca(e??void 0);if(!t)return yg(e);let n=e?.[t];return n?.fileName&&typeof n.fileName=="string"&&n.fileName.trim()?n.fileName.trim():yg(e)}async function wg(e,t,n,o){let r=t.message??void 0;if(!Sg(r))return{};let s=o.fileReceiveMaxBytes,i=QC(r);if(s>0&&i!==void 0&&i>s)return{error:`Media too large (max ${s} bytes).`};let a;try{a=await GC(t,"buffer",{},{logger:e.logger,reuploadRequest:e.updateMediaMessage})}catch(d){return P.warn({err:String(d),detail:tr(d)},"whatsapp downloadMediaMessage failed"),{error:`Could not download media from WhatsApp (${tr(d)}).`}}if(s>0&&a.length>s)return{error:`Media too large (max ${s} bytes).`};let l;try{l=wn(o,n)}catch(d){return{error:String(d)}}let c=XC(r),u=er(l,n,c);try{KC.writeFileSync(u,a,{mode:384})}catch{return{error:"Could not write media to inbox."}}return{path:u}}async function ZC(e,t){let n=t.remoteJid??"";if(!n)return{fromJid:"",fromE164:""};if(t.remoteJidAlt){let o=te(t.remoteJidAlt)??te(n)??"";return{fromJid:n,fromE164:o}}if(zC(n))try{let o=await e.signalRepository?.lidMapping?.getPNForLID?.(n);if(o){let r=te(o)??"";if(r)return{fromJid:n,fromE164:r}}}catch{}return{fromJid:n,fromE164:te(n)??""}}function eR(e){try{if(!v().clusterEnabled)return;let n=kg(e.message??void 0);if(!n)return;let o=fm(n);if(!o)return;let r=e.key?.remoteJid??"",s=null;if(r&&!bg(r)){let i=te(r);i&&(s=`wa:${i}`)}xm(o,s)}catch(t){P.warn({err:String(t)},"cluster footer observation failed")}}function vg(e,t){let n=o=>{o.type==="notify"&&(async()=>{for(let r of o.messages){let s=r.key;if(!s||s.fromMe&&(eR(r),!gg(s.id)))continue;let i=s.remoteJid;if(!i||bg(i)||i.toLowerCase().endsWith("@status")||i==="status@broadcast")continue;let l=su(kg(r.message??void 0)),{fromJid:c,fromE164:u}=await ZC(e,s),d=`wa:${c}`,m=Sg(r.message??void 0);if(m&&!l){(async()=>{try{let g=v(),y=await wg(e,r,d,g);await t({fromJid:c,fromE164:u,text:"",messageId:s.id??void 0,mediaSavedPath:y.path,mediaError:y.error})}catch(g){P.error({err:String(g),fromJid:c},"whatsapp media-only background task failed")}})();continue}let f,h;if(m){let g=v(),y=await wg(e,r,d,g);f=y.path,h=y.error}!l&&!f&&!h||await t({fromJid:c,fromE164:u,text:l,messageId:s.id??void 0,mediaSavedPath:f,mediaError:h})}})()};return e.ev.on("messages.upsert",n),()=>{e.ev.off("messages.upsert",n)}}be();import lR from"node:fs";import tR from"node:process";import nR,{DisconnectReason as co,fetchLatestBaileysVersion as oR,makeCacheableSignalKeyStore as rR,useMultiFileAuthState as sR}from"@whiskeysockets/baileys";import iR from"qrcode-terminal";j();be();var aR="0.1.0";function iu(e){return e?.error?.output?.statusCode}async function ua(e={}){se();let t=e.authDir??le,n=e.verbose===!0,o=Ld(),{state:r,saveCreds:s}=await sR(t),{version:i}=await oR(),a=nR({version:i,logger:o,printQRInTerminal:!1,browser:["omnish","cli",aR],auth:{creds:r.creds,keys:rR(r.keys,o)},syncFullHistory:!1,markOnlineOnConnect:!1});return a.ev.on("creds.update",s),a.ev.on("connection.update",l=>{let{connection:c,lastDisconnect:u,qr:d}=l;if(d&&(e.onQr?.(d),e.printQr)){let m=tR.stdout,f=w(m,"\xB7".repeat(42));console.log(Q(m,"Scan with WhatsApp \u2192 Linked devices")),console.log(f),iR.generate(d,{small:!0}),console.log(f)}if(c==="close"){let m=iu(u);n&&m===co.loggedOut&&o.warn("WhatsApp session logged out (401).")}c==="open"&&n&&o.info("WhatsApp Web connected.")}),a.ws&&typeof a.ws.on=="function"&&a.ws.on("error",l=>{o.error({err:String(l)},"WebSocket error")}),a}function nr(e){try{e.end(new Error("omnish: socket closed"))}catch{e.ws?.close()}}function au(e){return e?.output?.statusCode??e?.status??e?.error?.output?.statusCode}function da(e){return e.user?.id?Promise.resolve():new Promise((t,n)=>{let o=r=>{if(r.connection==="open"){e.ev.off("connection.update",o),t();return}if(r.connection==="close"){e.ev.off("connection.update",o);let i=r.lastDisconnect?.error??r.lastDisconnect??new Error("Connection closed");n(i)}};e.ev.on("connection.update",o)})}function uo(e,t,n){return new Promise((o,r)=>{let s=setTimeout(()=>r(new Error(n)),t);e.then(i=>{clearTimeout(s),o(i)},i=>{clearTimeout(s),r(i)})})}var Eg=3500,cR=400,xg=18e4,Cg=9e4,Rg=3e5;function lu(e,t=Eg){if(e.length<=t)return[e];let n=[],o=0;for(;o<e.length;){let r=Math.min(o+t,e.length);if(r<e.length){let i=e.slice(o,r).lastIndexOf(`
459
+ `).trimEnd()||"(empty log)"}readSince(t,n){let o=this.logPath(t);if(!ut.existsSync(o))return{text:"",nextOffset:n};let s=ut.statSync(o).size;if(n>=s)return{text:"",nextOffset:s};let i=Buffer.alloc(s-n),a=ut.openSync(o,"r");try{ut.readSync(a,i,0,i.length,n)}finally{ut.closeSync(a)}return{text:i.toString("utf8"),nextOffset:s}}resolveJobRef(t){return BC(this.list(),t)}kill(t){let n=this.metaPath(t),o=ts(n);if(!o)return`Unknown job id: ${t}`;let r=this.running.get(t);if(r&&!r.killed)return r.kill("SIGTERM"),es(n,{...o,status:"killed",signal:"SIGTERM"}),`Sent SIGTERM to job ${t} (pid ${o.pid??"?"})`;if(o.pid)try{return process.kill(o.pid,"SIGTERM"),es(n,{...o,status:"killed",signal:"SIGTERM"}),`Sent SIGTERM to pid ${o.pid} (${t})`}catch{return`Job ${t} is not running (no live process).`}return`Job ${t} is not running.`}killAllRunning(){for(let[t,n]of this.running){n.killed||n.kill("SIGTERM");let o=ts(this.metaPath(t));o&&es(this.metaPath(t),{...o,status:"killed",signal:"SIGTERM"})}this.running.clear()}};import SR from"node:fs";import{Bot as vR,InputFile as xR}from"grammy";j();import gg from"node:fs";import Zo from"node:path";function jC(e){return e.replace(/[^a-zA-Z0-9._-]+/g,"_").slice(0,120)||"unknown"}function HC(e){let o=Zo.basename(e.trim()||"file").replace(/[^a-zA-Z0-9._-]+/g,"_").replace(/^\.+/,"").slice(0,180);return o.length>0?o:"file"}function JC(e,t){let n=new Date().toISOString().slice(0,10),o=jC(t);return Zo.join(e,o,n)}function er(e,t,n){let o=JC(e,t);D(o);let r=HC(n),s=Zo.join(o,r);if(!gg.existsSync(s))return s;let i=Zo.extname(r),a=i?r.slice(0,-i.length):r;for(let l=1;l<1e4;l+=1){let c=`${a}-${l}${i}`;if(s=Zo.join(o,c),!gg.existsSync(s))return s}return Zo.join(o,`${a}-${Date.now()}${i}`)}ye();import GC from"node:dns";import qC from"node:https";import{URL as zC}from"node:url";function tr(e){if(e instanceof Error){let t=e.cause;return t!=null?`${e.message} (${String(t)})`:e.message}return String(e)}ye();function yg(e){if("photo"in e&&e.photo&&e.photo.length>0)return{fileId:e.photo[e.photo.length-1].file_id,baseName:"photo.jpg"};if("document"in e&&e.document){let t=e.document,n=t.file_name?.trim();return{fileId:t.file_id,baseName:n&&n.length>0?n:"document.bin"}}if("video"in e&&e.video){let t=e.video,n=t.file_name?.trim();return{fileId:t.file_id,baseName:n&&n.length>0?n:"video.mp4"}}if("audio"in e&&e.audio){let t=e.audio,n=t.file_name?.trim();return{fileId:t.file_id,baseName:n&&n.length>0?n:"audio.m4a"}}if("voice"in e&&e.voice)return{fileId:e.voice.file_id,baseName:"voice.ogg"};if("video_note"in e&&e.video_note)return{fileId:e.video_note.file_id,baseName:"video_note.mp4"};if("sticker"in e&&e.sticker){let t=e.sticker,n=t.is_video?"webm":"webp";return{fileId:t.file_id,baseName:`sticker.${n}`}}return null}function KC(e,t){let o=t.split("/").filter(r=>r.length>0).map(encodeURIComponent).join("/");return`https://api.telegram.org/file/bot${e}/${o}`}var wg="omnish (Telegram bot; https://github.com/labKnowledge/whatsLive)";function YC(e){let t=new zC(e);return new Promise((n,o)=>{let r=qC.request({hostname:t.hostname,port:t.port||443,path:t.pathname+t.search,method:"GET",headers:{"User-Agent":wg,Accept:"*/*"},lookup(s,i,a){GC.lookup(s,{family:4},a)}},s=>{let i=s.statusCode??0,a=[];s.on("data",l=>a.push(l)),s.on("end",()=>n({status:i,buffer:Buffer.concat(a)})),s.on("error",o)});r.on("error",o),r.end()})}async function bg(e,t,n){let o;try{o=await e.api.getFile(t)}catch(i){return E.warn({err:String(i),fileId:t},"telegram getFile failed"),{error:"Could not resolve file on Telegram."}}if(!o.file_path)return{error:"Telegram did not return a file path."};let r=KC(e.token,o.file_path),s;try{let i=await fetch(r,{headers:{"User-Agent":wg,Accept:"*/*"}});if(!i.ok)return E.warn({status:i.status,statusText:i.statusText,fileId:t},"telegram file fetch HTTP error"),{error:`Could not download file from Telegram (HTTP ${i.status}${i.statusText?` ${i.statusText}`:""}).`};try{s=Buffer.from(await i.arrayBuffer())}catch(a){return E.warn({err:String(a),fileId:t},"telegram file read body failed"),{error:`Could not read file from Telegram (${String(a)}).`}}}catch(i){E.warn({err:String(i),fileId:t},"telegram file fetch failed; retrying via HTTPS IPv4");try{let a=await YC(r);if(a.status<200||a.status>=300)return E.warn({status:a.status,fileId:t},"telegram HTTPS IPv4 fallback HTTP error"),{error:`Could not download file from Telegram (HTTP ${a.status}).`};s=a.buffer}catch(a){return E.warn({err:String(i),err2:String(a),fileId:t},"telegram file download failed (fetch and IPv4 fallback)"),{error:`Could not download file from Telegram (network: ${tr(i)}; IPv4 fallback: ${tr(a)}).`}}}return n>0&&s.byteLength>n?{error:`Media too large (max ${n} bytes).`}:{buffer:s}}ue();dt();import{downloadMediaMessage as ZC,extensionForMediaMessage as eR,getContentType as ua,isJidGroup as xg,isLidUser as tR}from"@whiskeysockets/baileys";import nR from"node:fs";ye();var Dn=new Map;function VC(){let e=Date.now();for(let[t,n]of Dn)e-n>6e5&&Dn.delete(t);for(;Dn.size>500;){let t=Dn.keys().next().value;if(t===void 0)break;Dn.delete(t)}}function QC(e){!e||typeof e!="string"||(VC(),Dn.set(e,Date.now()))}function au(e){QC(e?.key?.id??void 0)}function XC(e){if(!e)return!1;let t=Dn.get(e);return t===void 0?!1:Date.now()-t>6e5?(Dn.delete(e),!1):!0}function kg(e){return!XC(e)}function oR(e){if(!e||typeof e!="object")return"";let t=e;if(typeof t.conversation=="string")return t.conversation;let n=t.extendedTextMessage;return n&&typeof n.text=="string"?n.text:""}function rR(e){if(!e||typeof e!="object")return"";let t=e;for(let n of["imageMessage","videoMessage","documentMessage","audioMessage","stickerMessage"]){let o=t[n];if(o&&typeof o.caption=="string"&&o.caption.trim())return o.caption}return""}function Cg(e){return[oR(e),rR(e)].filter(n=>n.trim().length>0).join(`
460
+ `).trim()}function lu(e){return e.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g,"").replace(/\uFEFF/g,"").trim()}function Rg(e){let t=ua(e??void 0);return t==="imageMessage"||t==="videoMessage"||t==="audioMessage"||t==="documentMessage"||t==="stickerMessage"}function sR(e){let t=ua(e??void 0);if(!t)return;let o=e?.[t]?.fileLength;if(typeof o=="number"&&Number.isFinite(o))return o;if(o&&typeof o=="object"&&"toNumber"in o&&typeof o.toNumber=="function")try{let r=o.toNumber();return Number.isFinite(r)?r:void 0}catch{return}}function Sg(e){try{if(!e)return"file.bin";let t=eR(e);return`file${t&&t.length>0?t.startsWith(".")?t:`.${t}`:".bin"}`}catch{return`${(ua(e??void 0)??"file").replace("Message","")||"file"}.bin`}}function iR(e){let t=ua(e??void 0);if(!t)return Sg(e);let n=e?.[t];return n?.fileName&&typeof n.fileName=="string"&&n.fileName.trim()?n.fileName.trim():Sg(e)}async function vg(e,t,n,o){let r=t.message??void 0;if(!Rg(r))return{};let s=o.fileReceiveMaxBytes,i=sR(r);if(s>0&&i!==void 0&&i>s)return{error:`Media too large (max ${s} bytes).`};let a;try{a=await ZC(t,"buffer",{},{logger:e.logger,reuploadRequest:e.updateMediaMessage})}catch(d){return E.warn({err:String(d),detail:tr(d)},"whatsapp downloadMediaMessage failed"),{error:`Could not download media from WhatsApp (${tr(d)}).`}}if(s>0&&a.length>s)return{error:`Media too large (max ${s} bytes).`};let l;try{l=wn(o,n)}catch(d){return{error:String(d)}}let c=iR(r),u=er(l,n,c);try{nR.writeFileSync(u,a,{mode:384})}catch{return{error:"Could not write media to inbox."}}return{path:u}}async function aR(e,t){let n=t.remoteJid??"";if(!n)return{fromJid:"",fromE164:""};if(t.remoteJidAlt){let o=te(t.remoteJidAlt)??te(n)??"";return{fromJid:n,fromE164:o}}if(tR(n))try{let o=await e.signalRepository?.lidMapping?.getPNForLID?.(n);if(o){let r=te(o)??"";if(r)return{fromJid:n,fromE164:r}}}catch{}return{fromJid:n,fromE164:te(n)??""}}function lR(e){try{if(!v().clusterEnabled)return;let n=Cg(e.message??void 0);if(!n)return;let o=ym(n);if(!o)return;let r=e.key?.remoteJid??"",s=null;if(r&&!xg(r)){let i=te(r);i&&(s=`wa:${i}`)}Tm(o,s)}catch(t){E.warn({err:String(t)},"cluster footer observation failed")}}function Tg(e,t){let n=o=>{o.type==="notify"&&(async()=>{for(let r of o.messages){let s=r.key;if(!s||s.fromMe&&(lR(r),!kg(s.id)))continue;let i=s.remoteJid;if(!i||xg(i)||i.toLowerCase().endsWith("@status")||i==="status@broadcast")continue;let l=lu(Cg(r.message??void 0)),{fromJid:c,fromE164:u}=await aR(e,s),d=`wa:${c}`,m=Rg(r.message??void 0);if(m&&!l){(async()=>{try{let g=v(),y=await vg(e,r,d,g);await t({fromJid:c,fromE164:u,text:"",messageId:s.id??void 0,mediaSavedPath:y.path,mediaError:y.error})}catch(g){E.error({err:String(g),fromJid:c},"whatsapp media-only background task failed")}})();continue}let f,h;if(m){let g=v(),y=await vg(e,r,d,g);f=y.path,h=y.error}!l&&!f&&!h||await t({fromJid:c,fromE164:u,text:l,messageId:s.id??void 0,mediaSavedPath:f,mediaError:h})}})()};return e.ev.on("messages.upsert",n),()=>{e.ev.off("messages.upsert",n)}}ye();import gR from"node:fs";import cR from"node:process";import uR,{DisconnectReason as co,fetchLatestBaileysVersion as dR,makeCacheableSignalKeyStore as pR,useMultiFileAuthState as mR}from"@whiskeysockets/baileys";import fR from"qrcode-terminal";j();ye();var hR="0.1.0";function cu(e){return e?.error?.output?.statusCode}async function da(e={}){se();let t=e.authDir??le,n=e.verbose===!0,o=Fd(),{state:r,saveCreds:s}=await mR(t),{version:i}=await dR(),a=uR({version:i,logger:o,printQRInTerminal:!1,browser:["omnish","cli",hR],auth:{creds:r.creds,keys:pR(r.keys,o)},syncFullHistory:!1,markOnlineOnConnect:!1});return a.ev.on("creds.update",s),a.ev.on("connection.update",l=>{let{connection:c,lastDisconnect:u,qr:d}=l;if(d&&(e.onQr?.(d),e.printQr)){let m=cR.stdout,f=w(m,"\xB7".repeat(42));console.log(Q(m,"Scan with WhatsApp \u2192 Linked devices")),console.log(f),fR.generate(d,{small:!0}),console.log(f)}if(c==="close"){let m=cu(u);n&&m===co.loggedOut&&o.warn("WhatsApp session logged out (401).")}c==="open"&&n&&o.info("WhatsApp Web connected.")}),a.ws&&typeof a.ws.on=="function"&&a.ws.on("error",l=>{o.error({err:String(l)},"WebSocket error")}),a}function nr(e){try{e.end(new Error("omnish: socket closed"))}catch{e.ws?.close()}}function uu(e){return e?.output?.statusCode??e?.status??e?.error?.output?.statusCode}function pa(e){return e.user?.id?Promise.resolve():new Promise((t,n)=>{let o=r=>{if(r.connection==="open"){e.ev.off("connection.update",o),t();return}if(r.connection==="close"){e.ev.off("connection.update",o);let i=r.lastDisconnect?.error??r.lastDisconnect??new Error("Connection closed");n(i)}};e.ev.on("connection.update",o)})}function uo(e,t,n){return new Promise((o,r)=>{let s=setTimeout(()=>r(new Error(n)),t);e.then(i=>{clearTimeout(s),o(i)},i=>{clearTimeout(s),r(i)})})}var Ag=3500,yR=400,Eg=18e4,Pg=9e4,$g=3e5;function du(e,t=Ag){if(e.length<=t)return[e];let n=[],o=0;for(;o<e.length;){let r=Math.min(o+t,e.length);if(r<e.length){let i=e.slice(o,r).lastIndexOf(`
460
461
 
461
- `);i>Math.floor(t*.35)&&(r=o+i+2)}n.push(e.slice(o,r)),o=r}return n}function pa(e){return new Promise(t=>setTimeout(t,e))}async function Tg(e,t){await Promise.race([e,pa(xg).then(()=>{P.warn({jid:t,ms:xg},"whatsapp outbound self-heal: prior send chain waited too long; continuing")})])}async function uR(e,t,n,o=3){let r;for(let s=1;s<=o;s+=1)try{let i=await uo(e.sendMessage(t,{text:n}),Cg,`whatsapp sendMessage timed out after ${Cg}ms`);ru(i);return}catch(i){r=i;let a=String(i);if(/not connected|closed|timed out|timeout/i.test(a)&&s<o){await pa(800*s);continue}throw i}throw r}function dR(e,t){let n=e.caption;switch(e.category){case"image":return{image:t,caption:n,mimetype:e.mimetype};case"video":return{video:t,caption:n,mimetype:e.mimetype};case"audio":return{audio:t,mimetype:e.mimetype,ptt:!1};case"document":return{document:t,mimetype:e.mimetype,fileName:e.displayName,caption:n}}}async function pR(e,t,n,o=3){let r;for(let s=1;s<=o;s+=1)try{let i=await uo(e.sendMessage(t,n),Rg,`whatsapp sendMedia timed out after ${Rg}ms`);ru(i);return}catch(i){r=i;let a=String(i);if(/not connected|closed|timed out|timeout/i.test(a)&&s<o){await pa(800*s);continue}throw i}throw r}function Pg(e,t={}){let n=new Map,o=t.decorate??(r=>r);return{async sendText(r,s){let i=o(s,r),a=n.get(r)??Promise.resolve(),l=Tg(a,r).then(async()=>{let c=lu(i,Eg);for(let u=0;u<c.length;u+=1)await uR(e,r,c[u]??""),u<c.length-1&&await pa(cR)}).catch(c=>{P.error({err:String(c),jid:r},"sendText failed")});n.set(r,l),await l.finally(()=>{n.get(r)===l&&n.delete(r)})},async sendMedia(r,s){let i=lR.readFileSync(s.absPath),a=dR(s,i),l=n.get(r)??Promise.resolve(),c=Tg(l,r).then(async()=>{await pR(e,r,a)}).catch(u=>{P.error({err:String(u),jid:r},"sendMedia failed")});n.set(r,c),await c.finally(()=>{n.get(r)===c&&n.delete(r)})}}}function $g(e){let t=e.trim();return t?/^\/id(?:@[\w_]+)?$/i.test(t):!1}function Mg(e){let t=String(e).replace(/\D/g,"");return`Your Telegram user id: ${t}
462
+ `);i>Math.floor(t*.35)&&(r=o+i+2)}n.push(e.slice(o,r)),o=r}return n}function ma(e){return new Promise(t=>setTimeout(t,e))}async function Mg(e,t){await Promise.race([e,ma(Eg).then(()=>{E.warn({jid:t,ms:Eg},"whatsapp outbound self-heal: prior send chain waited too long; continuing")})])}async function wR(e,t,n,o=3){let r;for(let s=1;s<=o;s+=1)try{let i=await uo(e.sendMessage(t,{text:n}),Pg,`whatsapp sendMessage timed out after ${Pg}ms`);au(i);return}catch(i){r=i;let a=String(i);if(/not connected|closed|timed out|timeout/i.test(a)&&s<o){await ma(800*s);continue}throw i}throw r}function bR(e,t){let n=e.caption;switch(e.category){case"image":return{image:t,caption:n,mimetype:e.mimetype};case"video":return{video:t,caption:n,mimetype:e.mimetype};case"audio":return{audio:t,mimetype:e.mimetype,ptt:!1};case"document":return{document:t,mimetype:e.mimetype,fileName:e.displayName,caption:n}}}async function kR(e,t,n,o=3){let r;for(let s=1;s<=o;s+=1)try{let i=await uo(e.sendMessage(t,n),$g,`whatsapp sendMedia timed out after ${$g}ms`);au(i);return}catch(i){r=i;let a=String(i);if(/not connected|closed|timed out|timeout/i.test(a)&&s<o){await ma(800*s);continue}throw i}throw r}function Ig(e,t={}){let n=new Map,o=t.decorate??(r=>r);return{async sendText(r,s){let i=o(s,r),a=n.get(r)??Promise.resolve(),l=Mg(a,r).then(async()=>{let c=du(i,Ag);for(let u=0;u<c.length;u+=1)await wR(e,r,c[u]??""),u<c.length-1&&await ma(yR)}).catch(c=>{E.error({err:String(c),jid:r},"sendText failed")});n.set(r,l),await l.finally(()=>{n.get(r)===l&&n.delete(r)})},async sendMedia(r,s){let i=gR.readFileSync(s.absPath),a=bR(s,i),l=n.get(r)??Promise.resolve(),c=Mg(l,r).then(async()=>{await kR(e,r,a)}).catch(u=>{E.error({err:String(u),jid:r},"sendMedia failed")});n.set(r,c),await c.finally(()=>{n.get(r)===c&&n.delete(r)})}}}function Og(e){let t=e.trim();return t?/^\/id(?:@[\w_]+)?$/i.test(t):!1}function Lg(e){let t=String(e).replace(/\D/g,"");return`Your Telegram user id: ${t}
462
463
  Add to allowlist on this gateway host:
463
- omnish allow tg:${t}`}var gR=400;async function Ag(e,t,n,o){let r=t(),s=await hg(e,o.fileId,r.fileReceiveMaxBytes);if("error"in s)return{mediaError:s.error};let i;try{i=wn(r,n)}catch(l){return{mediaError:String(l)}}let a=er(i,n,o.baseName);try{return mR.writeFileSync(a,s.buffer,{mode:384}),{mediaSavedPath:a}}catch{return{mediaError:"Could not write media to inbox."}}}function yR(e){return new Promise(t=>setTimeout(t,e))}async function cu(e,t,n,o={}){let r=new fR(e);await r.api.deleteWebhook({drop_pending_updates:!1});let s=new Map,i=o.decorate??(u=>u);async function a(u,d){let m=Math.min(t().appsMaxWaChars,4096),f=pe(d,"telegram"),h=i(f.text,tc(u)),g=f.parseModeHtml,k=(s.get(u)??Promise.resolve()).then(async()=>{let b=lu(h,m);for(let x=0;x<b.length;x+=1){let C=b[x]??"",M=g?{parse_mode:"HTML"}:void 0;try{await r.api.sendMessage(u,C,M)}catch(R){if(g){P.warn({err:String(R),chatId:u},"telegram HTML send failed; retrying plain");let N=C.replace(/<[^>]+>/g,"");await r.api.sendMessage(u,N)}else throw R}x<b.length-1&&await yR(gR)}}).catch(b=>{P.error({err:String(b),chatId:u},"telegram sendText failed")});s.set(u,k),await k.finally(()=>{s.get(u)===k&&s.delete(u)})}async function l(u,d){let m=new hR(d.absPath,d.displayName),f=d.caption,g=(s.get(u)??Promise.resolve()).then(async()=>{switch(d.category){case"image":await r.api.sendPhoto(u,m,f?{caption:f}:void 0);break;case"video":await r.api.sendVideo(u,m,f?{caption:f}:void 0);break;case"audio":await r.api.sendAudio(u,m,f?{caption:f}:void 0);break;case"document":await r.api.sendDocument(u,m,f?{caption:f}:void 0);break}}).catch(y=>{P.error({err:String(y),chatId:u},"telegram sendMedia failed")});s.set(u,g),await g.finally(()=>{s.get(u)===g&&s.delete(u)})}r.on("message",async u=>{let d=u.chat,m=u.message;if(!d||d.type!=="private"||!u.from||!m)return;let f="text"in m&&m.text?m.text:"",h="caption"in m&&m.caption?m.caption:"",g=su([f,h].filter(Boolean).join(`
464
- `));if(g&&$g(g)){let R=u.from.id;await r.api.sendMessage(d.id,Mg(R));return}let y=mg(m),k=tc(d.id),b=d.id,x=async R=>{if(R.kind==="file")await l(b,R.spec);else if(R.kind==="files")for(let N of R.specs)await l(b,N);else if(R.kind==="texts")for(let N of R.bodies)await a(b,N);else if(R.kind==="bundle"){for(let N of R.texts??[])await a(b,N);for(let N of R.files??[])await l(b,N)}else R.kind==="text"&&await a(b,R.body)};if(y&&!g){(async()=>{try{let R=await Ag(r,t,k,y);if(!R.mediaSavedPath&&!R.mediaError)return;await n({peerKey:k,text:"",tgChatId:d.id,tgReplyToMessageId:m.message_id,mediaSavedPath:R.mediaSavedPath,mediaError:R.mediaError},x)}catch(R){P.error({err:String(R),chatId:b},"telegram media-only background task failed")}})();return}let C,M;if(y){let R=await Ag(r,t,k,y);C=R.mediaSavedPath,M=R.mediaError}!g&&!C&&!M||await n({peerKey:k,text:g,tgChatId:d.id,tgReplyToMessageId:m.message_id,mediaSavedPath:C,mediaError:M},x)}),r.catch(u=>{P.error({err:String(u)},"telegram bot error")});let c=r.start();return{bot:r,sendText:a,sendMedia:l,stop:async()=>{await r.stop(),await c.catch(()=>{})}}}dt();import wR from"node:fs";import bR from"node:path";function kR(e){let t=bR.join(e,"creds.json");try{let n=wR.readFileSync(t,"utf8"),o=JSON.parse(n);if(!o.me||typeof o.me!="object"||o.me===null)return null;let r=o.me,s=typeof r.id=="string"?r.id:void 0,i=typeof r.phoneNumber=="string"?r.phoneNumber:void 0;return!s&&!i?null:{id:s,phoneNumber:i}}catch{return null}}function Ig(e){let t=kR(e);if(!t)return null;let n=t.phoneNumber?.trim();if(n){let s=te(n);return s?`phone ${s}`:`phone ${n}`}let o=t.id?.trim();if(!o)return null;let r=o.toLowerCase();if(r.endsWith("@s.whatsapp.net")||r.endsWith("@c.us")){let s=te(o);return s?`phone ${s}`:`id ${o}`}return r.endsWith("@lid")?`device ${o} (LID \u2014 not an E.164; your number may appear after the gateway runs)`:`id ${o}`}import fa from"node:fs";import yt from"node:process";j();j();import Og from"node:fs";var SR="Timed out after 10 minutes. Phone stuck on \u201CLogging in\u201D usually means: run `pnpm approve-builds && pnpm install`, check network/firewall, then try `omnish link --force` again.";function ma(e){let t=String(e).toLowerCase();return t.includes("401")||t.includes("logged out")?!0:au(e)===co.loggedOut}function vR(e){return new Promise((t,n)=>{let o=()=>{n(new Error("Pairing cancelled."))};if(e.aborted){o();return}e.addEventListener("abort",o,{once:!0})})}async function uu(e){let t=!1;for(let n=0;n<3;n++){if(e.signal?.aborted)throw new Error("Pairing cancelled.");let o=await ua({authDir:e.authDir,printQr:e.printQr===!0,verbose:e.verbose===!0,onQr:e.onQr});e.onSocketReady?.(o);try{let r=uo(da(o),6e5,SR);e.signal?await Promise.race([r,vR(e.signal)]):await r;return}catch(r){if(r instanceof Error&&r.message==="Pairing cancelled.")throw r;if(au(r)===co.restartRequired&&!t){t=!0,e.onRestart515?.(),await new Promise(i=>setTimeout(i,1500));continue}throw r}finally{e.onSocketClosed?.(),nr(o)}}throw new Error("Pairing failed after restart (515) retries.")}async function Lg(e){let t=e.authDir??le;se();for(let n=1;n<=2;n++)try{await uu({...e,authDir:t});return}catch(o){if(o instanceof Error&&o.message==="Pairing cancelled.")throw o;if(n===1&&ma(o)){Og.rmSync(t,{recursive:!0,force:!0}),Og.mkdirSync(t,{recursive:!0,mode:448});continue}throw o}}async function xR(e,t){let n=yt.stdout;console.log(`
464
+ omnish allow tg:${t}`}var CR=400;async function Ng(e,t,n,o){let r=t(),s=await bg(e,o.fileId,r.fileReceiveMaxBytes);if("error"in s)return{mediaError:s.error};let i;try{i=wn(r,n)}catch(l){return{mediaError:String(l)}}let a=er(i,n,o.baseName);try{return SR.writeFileSync(a,s.buffer,{mode:384}),{mediaSavedPath:a}}catch{return{mediaError:"Could not write media to inbox."}}}function RR(e){return new Promise(t=>setTimeout(t,e))}async function pu(e,t,n,o={}){let r=new vR(e);await r.api.deleteWebhook({drop_pending_updates:!1});let s=new Map,i=o.decorate??(u=>u);async function a(u,d){let m=Math.min(t().appsMaxWaChars,4096),f=pe(d,"telegram"),h=i(f.text,nc(u)),g=f.parseModeHtml,b=(s.get(u)??Promise.resolve()).then(async()=>{let k=du(h,m);for(let x=0;x<k.length;x+=1){let C=k[x]??"",M=g?{parse_mode:"HTML"}:void 0;try{await r.api.sendMessage(u,C,M)}catch(R){if(g){E.warn({err:String(R),chatId:u},"telegram HTML send failed; retrying plain");let N=C.replace(/<[^>]+>/g,"");await r.api.sendMessage(u,N)}else throw R}x<k.length-1&&await RR(CR)}}).catch(k=>{E.error({err:String(k),chatId:u},"telegram sendText failed")});s.set(u,b),await b.finally(()=>{s.get(u)===b&&s.delete(u)})}async function l(u,d){let m=new xR(d.absPath,d.displayName),f=d.caption,g=(s.get(u)??Promise.resolve()).then(async()=>{switch(d.category){case"image":await r.api.sendPhoto(u,m,f?{caption:f}:void 0);break;case"video":await r.api.sendVideo(u,m,f?{caption:f}:void 0);break;case"audio":await r.api.sendAudio(u,m,f?{caption:f}:void 0);break;case"document":await r.api.sendDocument(u,m,f?{caption:f}:void 0);break}}).catch(y=>{E.error({err:String(y),chatId:u},"telegram sendMedia failed")});s.set(u,g),await g.finally(()=>{s.get(u)===g&&s.delete(u)})}r.on("message",async u=>{let d=u.chat,m=u.message;if(!d||d.type!=="private"||!u.from||!m)return;let f="text"in m&&m.text?m.text:"",h="caption"in m&&m.caption?m.caption:"",g=lu([f,h].filter(Boolean).join(`
465
+ `));if(g&&Og(g)){let R=u.from.id;await r.api.sendMessage(d.id,Lg(R));return}let y=yg(m),b=nc(d.id),k=d.id,x=async R=>{if(R.kind==="file")await l(k,R.spec);else if(R.kind==="files")for(let N of R.specs)await l(k,N);else if(R.kind==="texts")for(let N of R.bodies)await a(k,N);else if(R.kind==="bundle"){for(let N of R.texts??[])await a(k,N);for(let N of R.files??[])await l(k,N)}else R.kind==="text"&&await a(k,R.body)};if(y&&!g){(async()=>{try{let R=await Ng(r,t,b,y);if(!R.mediaSavedPath&&!R.mediaError)return;await n({peerKey:b,text:"",tgChatId:d.id,tgReplyToMessageId:m.message_id,mediaSavedPath:R.mediaSavedPath,mediaError:R.mediaError},x)}catch(R){E.error({err:String(R),chatId:k},"telegram media-only background task failed")}})();return}let C,M;if(y){let R=await Ng(r,t,b,y);C=R.mediaSavedPath,M=R.mediaError}!g&&!C&&!M||await n({peerKey:b,text:g,tgChatId:d.id,tgReplyToMessageId:m.message_id,mediaSavedPath:C,mediaError:M},x)}),r.catch(u=>{E.error({err:String(u)},"telegram bot error")});let c=r.start();return{bot:r,sendText:a,sendMedia:l,stop:async()=>{await r.stop(),await c.catch(()=>{})}}}dt();import TR from"node:fs";import ER from"node:path";function PR(e){let t=ER.join(e,"creds.json");try{let n=TR.readFileSync(t,"utf8"),o=JSON.parse(n);if(!o.me||typeof o.me!="object"||o.me===null)return null;let r=o.me,s=typeof r.id=="string"?r.id:void 0,i=typeof r.phoneNumber=="string"?r.phoneNumber:void 0;return!s&&!i?null:{id:s,phoneNumber:i}}catch{return null}}function _g(e){let t=PR(e);if(!t)return null;let n=t.phoneNumber?.trim();if(n){let s=te(n);return s?`phone ${s}`:`phone ${n}`}let o=t.id?.trim();if(!o)return null;let r=o.toLowerCase();if(r.endsWith("@s.whatsapp.net")||r.endsWith("@c.us")){let s=te(o);return s?`phone ${s}`:`id ${o}`}return r.endsWith("@lid")?`device ${o} (LID \u2014 not an E.164; your number may appear after the gateway runs)`:`id ${o}`}import ha from"node:fs";import yt from"node:process";j();j();import Fg from"node:fs";var $R="Timed out after 10 minutes. Phone stuck on \u201CLogging in\u201D usually means: run `pnpm approve-builds && pnpm install`, check network/firewall, then try `omnish link --force` again.";function fa(e){let t=String(e).toLowerCase();return t.includes("401")||t.includes("logged out")?!0:uu(e)===co.loggedOut}function MR(e){return new Promise((t,n)=>{let o=()=>{n(new Error("Pairing cancelled."))};if(e.aborted){o();return}e.addEventListener("abort",o,{once:!0})})}async function mu(e){let t=!1;for(let n=0;n<3;n++){if(e.signal?.aborted)throw new Error("Pairing cancelled.");let o=await da({authDir:e.authDir,printQr:e.printQr===!0,verbose:e.verbose===!0,onQr:e.onQr});e.onSocketReady?.(o);try{let r=uo(pa(o),6e5,$R);e.signal?await Promise.race([r,MR(e.signal)]):await r;return}catch(r){if(r instanceof Error&&r.message==="Pairing cancelled.")throw r;if(uu(r)===co.restartRequired&&!t){t=!0,e.onRestart515?.(),await new Promise(i=>setTimeout(i,1500));continue}throw r}finally{e.onSocketClosed?.(),nr(o)}}throw new Error("Pairing failed after restart (515) retries.")}async function Dg(e){let t=e.authDir??le;se();for(let n=1;n<=2;n++)try{await mu({...e,authDir:t});return}catch(o){if(o instanceof Error&&o.message==="Pairing cancelled.")throw o;if(n===1&&fa(o)){Fg.rmSync(t,{recursive:!0,force:!0}),Fg.mkdirSync(t,{recursive:!0,mode:448});continue}throw o}}async function AR(e,t){let n=yt.stdout;console.log(`
465
466
  ${Re(n,"omnish link")} ${w(n,"\u2014 QR appears below; scan from WhatsApp \u2192 Linked devices.")}
466
- `),await uu({authDir:e,verbose:t,printQr:!0,onRestart515:()=>{console.warn(`
467
+ `),await mu({authDir:e,verbose:t,printQr:!0,onRestart515:()=>{console.warn(`
467
468
  ${H(yt.stderr,"WhatsApp requested a restart after pairing (code 515). This is normal. Opening a new connection\u2026")}
468
- `)}})}async function Ng(e={}){let t=e.authDir??le,n=e.verbose===!0;se(),e.force&&(fa.rmSync(t,{recursive:!0,force:!0}),fa.mkdirSync(t,{recursive:!0,mode:448}),console.log(`${ke(yt.stdout,"Cleared saved session (--force).")} ${w(yt.stdout,"Requesting a new QR\u2026")}
469
- `));for(let o=1;o<=2;o++)try{await xR(t,n),console.log(`
469
+ `)}})}async function Wg(e={}){let t=e.authDir??le,n=e.verbose===!0;se(),e.force&&(ha.rmSync(t,{recursive:!0,force:!0}),ha.mkdirSync(t,{recursive:!0,mode:448}),console.log(`${ke(yt.stdout,"Cleared saved session (--force).")} ${w(yt.stdout,"Requesting a new QR\u2026")}
470
+ `));for(let o=1;o<=2;o++)try{await AR(t,n),console.log(`
470
471
  ${he(yt.stdout,"Linked.")} ${S(yt.stdout,"Session saved. You can run")} ${he(yt.stdout,"omnish run")} ${S(yt.stdout,"now.")}
471
- `);return}catch(r){if(o===1&&ma(r)){console.warn(`
472
+ `);return}catch(r){if(o===1&&fa(r)){console.warn(`
472
473
  ${H(yt.stderr,"WhatsApp returned logged-out (401). This often happens after Ctrl+C during link or corrupt auth files.")}
473
474
  ${H(yt.stderr,"Clearing auth dir and retrying once with a fresh QR\u2026")}
474
- `),fa.rmSync(t,{recursive:!0,force:!0}),fa.mkdirSync(t,{recursive:!0,mode:448});continue}throw ma(r)&&console.error(`
475
- ${E(yt.stderr,"Still failing after a clean auth directory. Try:")}
475
+ `),ha.rmSync(t,{recursive:!0,force:!0}),ha.mkdirSync(t,{recursive:!0,mode:448});continue}throw fa(r)&&console.error(`
476
+ ${P(yt.stderr,"Still failing after a clean auth directory. Try:")}
476
477
  ${w(yt.stderr,` pnpm approve-builds && pnpm install
477
478
  (pnpm may have skipped Baileys/sharp/protobuf build scripts.)
478
479
  Then: omnish link --force
479
- `)}`),r}}j();import{spawn as CR,spawnSync as RR}from"node:child_process";import or from"node:fs";import TR from"node:path";import rr from"node:process";function ha(e,t={}){se(),D(TR.dirname(e));let n=rr.argv[1];if(!n)return{ok:!1,message:"Cannot resolve entry script; invoke via node path/to/dist/index.js."};let o=["run"];t.verbose&&o.push("-vb");let r=or.openSync(e,"a"),s=CR(rr.execPath,[n,...o],{detached:!0,stdio:["ignore",r,r],env:{...rr.env,OMNISH_BACKGROUND_GATEWAY:"1"}});return or.closeSync(r),s.unref(),s.pid?{ok:!0,pid:s.pid}:{ok:!1,message:"Failed to start background gateway."}}function ga(){if(se(),!or.existsSync(de))return{outcome:"no_pidfile"};let e=or.readFileSync(de,"utf8").trim(),t=Number(e);if(!Number.isFinite(t)||t<=0){try{or.unlinkSync(de)}catch{}return{outcome:"invalid_pidfile"}}try{rr.kill(t,0)}catch{try{or.unlinkSync(de)}catch{}return{outcome:"stale_cleaned",pid:t}}try{return rr.kill(t,"SIGTERM"),{outcome:"sent_signal",pid:t}}catch(n){return rr.platform==="win32"&&RR("taskkill",["/PID",String(t),"/T"],{windowsHide:!0}).status===0?{outcome:"taskkill_ok",pid:t}:{outcome:"failed",message:`could not signal process: ${String(n)}`}}}import _g from"node:crypto";import du from"node:fs";import ER from"node:net";dt();be();j();var ns=null;function PR(){try{let e=du.readFileSync(bo,"utf8"),t=JSON.parse(e);return typeof t.token=="string"?t.token:""}catch{return""}}function $R(e){du.writeFileSync(bo,JSON.stringify(e,null,2)+`
480
- `,{mode:384})}function MR(){try{du.unlinkSync(bo)}catch{}}async function AR(e,t){let n=t.getCfg(),o=typeof e.absPath=="string"?e.absPath.trim():"";if(!o)return{ok:!1,error:"Missing absPath."};let r=typeof e.caption=="string"&&e.caption.length>0?e.caption:void 0,s=!!t.sendPlatformMedia&&!t.getWaOutbound()&&!t.getTgSendMedia(),i=uc(n,s),a=Ct(o,i);if("error"in a)return{ok:!1,error:a.error};let l={absPath:a.absPath,category:a.category,mimetype:a.mimetype,displayName:a.displayName,caption:r};if(e.channel==="whatsapp"){let c=t.getWaOutbound(),u=typeof e.e164=="string"?e.e164.trim():"";if(!u.startsWith("+"))return{ok:!1,error:"WhatsApp destination must be E.164 (e.g. +15551234567)."};let d=mn(u);if(!c){let f=t.sendPlatformMedia;if(!f)return{ok:!1,error:"WhatsApp outbound is not connected."};try{return await f(`wa:${d}`,l),{ok:!0}}catch(h){return{ok:!1,error:String(h)}}}let m=n.gatewayMode;if(m!=="whatsapp"&&m!=="both")return{ok:!1,error:"gatewayMode does not include WhatsApp."};try{return await c.sendMedia(d,l),{ok:!0}}catch(f){return{ok:!1,error:String(f)}}}if(e.channel==="telegram"){let c=t.getTgSendMedia();if(!c){let d=t.sendPlatformMedia;if(!d)return{ok:!1,error:"Telegram outbound is not connected."};if(!Number.isFinite(e.chatId))return{ok:!1,error:"Invalid Telegram chat id."};let m=`tg:${e.chatId}`;try{return await d(m,l),{ok:!0}}catch(f){return{ok:!1,error:String(f)}}}let u=n.gatewayMode;if(u!=="telegram"&&u!=="both")return{ok:!1,error:"gatewayMode does not include Telegram."};if(!Number.isFinite(e.chatId))return{ok:!1,error:"Invalid Telegram chat id."};try{return await c(e.chatId,l),{ok:!0}}catch(d){return{ok:!1,error:String(d)}}}return{ok:!1,error:"Unknown channel."}}async function IR(e,t){let n=t.getCfg(),o=typeof e.text=="string"?e.text.trim():"";if(!o)return{ok:!1,error:"Missing or empty text."};if(e.channel==="whatsapp"){let r=t.getWaOutbound();if(!r){let l=t.sendPlatformText;if(!l)return{ok:!1,error:"WhatsApp outbound is not connected."};let c=typeof e.e164=="string"?e.e164.trim():"";if(!c.startsWith("+"))return{ok:!1,error:"WhatsApp destination must be E.164 (e.g. +15551234567)."};let u=mn(c);try{return await l(`wa:${u}`,o),{ok:!0}}catch(d){return{ok:!1,error:String(d)}}}let s=n.gatewayMode;if(s!=="whatsapp"&&s!=="both")return{ok:!1,error:"gatewayMode does not include WhatsApp."};let i=typeof e.e164=="string"?e.e164.trim():"";if(!i.startsWith("+"))return{ok:!1,error:"WhatsApp destination must be E.164 (e.g. +15551234567)."};let a=mn(i);try{return await r.sendText(a,o),{ok:!0}}catch(l){return{ok:!1,error:String(l)}}}if(e.channel==="telegram"){let r=t.getTgSendText();if(!r){let i=t.sendPlatformText;if(!i)return{ok:!1,error:"Telegram outbound is not connected."};if(!Number.isFinite(e.chatId))return{ok:!1,error:"Invalid Telegram chat id."};try{return await i(`tg:${e.chatId}`,o),{ok:!0}}catch(a){return{ok:!1,error:String(a)}}}let s=n.gatewayMode;if(s!=="telegram"&&s!=="both")return{ok:!1,error:"gatewayMode does not include Telegram."};if(!Number.isFinite(e.chatId))return{ok:!1,error:"Invalid Telegram chat id."};try{return await r(e.chatId,p(o)),{ok:!0}}catch(i){return{ok:!1,error:String(i)}}}return{ok:!1,error:"Unknown channel."}}async function OR(e,t,n){let o;try{o=JSON.parse(e)}catch{return{ok:!1,error:"Invalid JSON."}}if(!o||typeof o!="object")return{ok:!1,error:"Invalid request."};if(typeof o.token!="string"||!n)return{ok:!1,error:"Unauthorized."};try{let r=Buffer.from(o.token,"utf8"),s=Buffer.from(n,"utf8");if(r.length!==s.length||!_g.timingSafeEqual(r,s))return{ok:!1,error:"Unauthorized."}}catch{return{ok:!1,error:"Unauthorized."}}return o.op==="sendMedia"?AR(o,t):o.op==="sendText"?IR(o,t):o.op==="sendPeerText"?NR(o,t):o.op==="sendPeerMedia"?LR(o,t):{ok:!1,error:"Unsupported operation."}}async function LR(e,t){let n=typeof e.peerKey=="string"?e.peerKey.trim():"",o=typeof e.absPath=="string"?e.absPath.trim():"";if(!n)return{ok:!1,error:"Missing peerKey."};if(!o)return{ok:!1,error:"Missing absPath."};let r=t.getCfg(),s=typeof e.caption=="string"&&e.caption.length>0?e.caption:void 0,i=!!t.sendPlatformMedia&&!t.getWaOutbound()&&!t.getTgSendMedia(),a=uc(r,i),l=Ct(o,a);if("error"in l)return{ok:!1,error:l.error};let c={absPath:l.absPath,category:l.category,mimetype:l.mimetype,displayName:l.displayName,caption:s};if(t.sendPeerMedia)try{return await t.sendPeerMedia(n,c),{ok:!0}}catch(u){return{ok:!1,error:String(u)}}if(t.sendPlatformMedia)try{return await t.sendPlatformMedia(n,c),{ok:!0}}catch(u){return{ok:!1,error:String(u)}}if(n.startsWith("wa:")){let u=t.getWaOutbound();if(!u)return{ok:!1,error:"WhatsApp outbound is not connected."};let d=r.gatewayMode;if(d!=="whatsapp"&&d!=="both")return{ok:!1,error:"gatewayMode does not include WhatsApp."};try{return await u.sendMedia(n.slice(3),c),{ok:!0}}catch(m){return{ok:!1,error:String(m)}}}if(n.startsWith("tg:")){let u=t.getTgSendMedia();if(!u)return{ok:!1,error:"Telegram outbound is not connected."};let d=r.gatewayMode;if(d!=="telegram"&&d!=="both")return{ok:!1,error:"gatewayMode does not include Telegram."};let m=Number(n.slice(3));if(!Number.isFinite(m))return{ok:!1,error:"Invalid Telegram peer key."};try{return await u(m,c),{ok:!0}}catch(f){return{ok:!1,error:String(f)}}}return{ok:!1,error:"Unknown peer key prefix."}}async function NR(e,t){let n=typeof e.peerKey=="string"?e.peerKey.trim():"",o=typeof e.text=="string"?e.text.trim():"";if(!n)return{ok:!1,error:"Missing peerKey."};if(!o)return{ok:!1,error:"Missing or empty text."};if(t.sendPlatformText)try{return await t.sendPlatformText(n,o),{ok:!0}}catch(s){return{ok:!1,error:String(s)}}let r=t.getCfg();if(n.startsWith("wa:")){let s=t.getWaOutbound();if(!s)return{ok:!1,error:"WhatsApp outbound is not connected."};let i=r.gatewayMode;if(i!=="whatsapp"&&i!=="both")return{ok:!1,error:"gatewayMode does not include WhatsApp."};try{return await s.sendText(n.slice(3),o),{ok:!0}}catch(a){return{ok:!1,error:String(a)}}}if(n.startsWith("tg:")){let s=t.getTgSendText();if(!s)return{ok:!1,error:"Telegram outbound is not connected."};let i=r.gatewayMode;if(i!=="telegram"&&i!=="both")return{ok:!1,error:"gatewayMode does not include Telegram."};let a=Number(n.slice(3));if(!Number.isFinite(a))return{ok:!1,error:"Invalid Telegram peer key."};try{return await s(a,p(o)),{ok:!0}}catch(l){return{ok:!1,error:String(l)}}}return{ok:!1,error:"Unknown peer key prefix."}}function ya(e){if(ns)return;let t=_g.randomBytes(32).toString("hex"),n=ER.createServer(o=>{let r="";o.setTimeout(12e4),o.on("data",s=>{r+=s.toString("utf8");let i=r.indexOf(`
481
- `);if(i===-1)return;let a=r.slice(0,i).trim();r=r.slice(i+1);let l=PR();OR(a,e,l).then(c=>{o.write(`${JSON.stringify(c)}
482
- `),o.end()})}),o.on("error",()=>{})});n.listen(0,"127.0.0.1",()=>{let o=n.address();if(!o||typeof o=="string"){P.error("gateway control: could not read listen address");return}let r={token:t,host:o.address,port:o.port};$R(r),P.info({port:o.port},"gateway control listening")}),n.on("error",o=>{P.error({err:String(o)},"gateway control server error")}),ns=n}function sr(){if(ns){try{ns.close()}catch{}ns=null,MR()}}be();import _R from"node:crypto";import FR from"node:http";var DR=256*1024;function WR(e,t){if(!e||!t)return!1;try{let n=Buffer.from(e,"utf8"),o=Buffer.from(t,"utf8");return n.length===o.length&&_R.timingSafeEqual(n,o)}catch{return!1}}function UR(e){let t=typeof e.source=="string"?e.source:"webhook";if(e.action==="completed"&&e.workflow_run&&typeof e.workflow_run=="object"){let a=e.workflow_run,l=a.name??"?",c=a.conclusion??"?",u=a.head_branch??"?",d=a.html_url??"",m=e.repository?.full_name??"?";return`[${t}] ${m} \u2014 ${l}
480
+ `)}`),r}}j();import{spawn as IR,spawnSync as OR}from"node:child_process";import or from"node:fs";import LR from"node:path";import rr from"node:process";function ga(e,t={}){se(),D(LR.dirname(e));let n=rr.argv[1];if(!n)return{ok:!1,message:"Cannot resolve entry script; invoke via node path/to/dist/index.js."};let o=["run"];t.verbose&&o.push("-vb");let r=or.openSync(e,"a"),s=IR(rr.execPath,[n,...o],{detached:!0,stdio:["ignore",r,r],env:{...rr.env,OMNISH_BACKGROUND_GATEWAY:"1"}});return or.closeSync(r),s.unref(),s.pid?{ok:!0,pid:s.pid}:{ok:!1,message:"Failed to start background gateway."}}function ya(){if(se(),!or.existsSync(de))return{outcome:"no_pidfile"};let e=or.readFileSync(de,"utf8").trim(),t=Number(e);if(!Number.isFinite(t)||t<=0){try{or.unlinkSync(de)}catch{}return{outcome:"invalid_pidfile"}}try{rr.kill(t,0)}catch{try{or.unlinkSync(de)}catch{}return{outcome:"stale_cleaned",pid:t}}try{return rr.kill(t,"SIGTERM"),{outcome:"sent_signal",pid:t}}catch(n){return rr.platform==="win32"&&OR("taskkill",["/PID",String(t),"/T"],{windowsHide:!0}).status===0?{outcome:"taskkill_ok",pid:t}:{outcome:"failed",message:`could not signal process: ${String(n)}`}}}import Ug from"node:crypto";import fu from"node:fs";import NR from"node:net";dt();ye();j();var ns=null;function _R(){try{let e=fu.readFileSync(bo,"utf8"),t=JSON.parse(e);return typeof t.token=="string"?t.token:""}catch{return""}}function FR(e){fu.writeFileSync(bo,JSON.stringify(e,null,2)+`
481
+ `,{mode:384})}function DR(){try{fu.unlinkSync(bo)}catch{}}async function WR(e,t){let n=t.getCfg(),o=typeof e.absPath=="string"?e.absPath.trim():"";if(!o)return{ok:!1,error:"Missing absPath."};let r=typeof e.caption=="string"&&e.caption.length>0?e.caption:void 0,s=!!t.sendPlatformMedia&&!t.getWaOutbound()&&!t.getTgSendMedia(),i=dc(n,s),a=Ct(o,i);if("error"in a)return{ok:!1,error:a.error};let l={absPath:a.absPath,category:a.category,mimetype:a.mimetype,displayName:a.displayName,caption:r};if(e.channel==="whatsapp"){let c=t.getWaOutbound(),u=typeof e.e164=="string"?e.e164.trim():"";if(!u.startsWith("+"))return{ok:!1,error:"WhatsApp destination must be E.164 (e.g. +15551234567)."};let d=mn(u);if(!c){let f=t.sendPlatformMedia;if(!f)return{ok:!1,error:"WhatsApp outbound is not connected."};try{return await f(`wa:${d}`,l),{ok:!0}}catch(h){return{ok:!1,error:String(h)}}}let m=n.gatewayMode;if(m!=="whatsapp"&&m!=="both")return{ok:!1,error:"gatewayMode does not include WhatsApp."};try{return await c.sendMedia(d,l),{ok:!0}}catch(f){return{ok:!1,error:String(f)}}}if(e.channel==="telegram"){let c=t.getTgSendMedia();if(!c){let d=t.sendPlatformMedia;if(!d)return{ok:!1,error:"Telegram outbound is not connected."};if(!Number.isFinite(e.chatId))return{ok:!1,error:"Invalid Telegram chat id."};let m=`tg:${e.chatId}`;try{return await d(m,l),{ok:!0}}catch(f){return{ok:!1,error:String(f)}}}let u=n.gatewayMode;if(u!=="telegram"&&u!=="both")return{ok:!1,error:"gatewayMode does not include Telegram."};if(!Number.isFinite(e.chatId))return{ok:!1,error:"Invalid Telegram chat id."};try{return await c(e.chatId,l),{ok:!0}}catch(d){return{ok:!1,error:String(d)}}}return{ok:!1,error:"Unknown channel."}}async function UR(e,t){let n=t.getCfg(),o=typeof e.text=="string"?e.text.trim():"";if(!o)return{ok:!1,error:"Missing or empty text."};if(e.channel==="whatsapp"){let r=t.getWaOutbound();if(!r){let l=t.sendPlatformText;if(!l)return{ok:!1,error:"WhatsApp outbound is not connected."};let c=typeof e.e164=="string"?e.e164.trim():"";if(!c.startsWith("+"))return{ok:!1,error:"WhatsApp destination must be E.164 (e.g. +15551234567)."};let u=mn(c);try{return await l(`wa:${u}`,o),{ok:!0}}catch(d){return{ok:!1,error:String(d)}}}let s=n.gatewayMode;if(s!=="whatsapp"&&s!=="both")return{ok:!1,error:"gatewayMode does not include WhatsApp."};let i=typeof e.e164=="string"?e.e164.trim():"";if(!i.startsWith("+"))return{ok:!1,error:"WhatsApp destination must be E.164 (e.g. +15551234567)."};let a=mn(i);try{return await r.sendText(a,o),{ok:!0}}catch(l){return{ok:!1,error:String(l)}}}if(e.channel==="telegram"){let r=t.getTgSendText();if(!r){let i=t.sendPlatformText;if(!i)return{ok:!1,error:"Telegram outbound is not connected."};if(!Number.isFinite(e.chatId))return{ok:!1,error:"Invalid Telegram chat id."};try{return await i(`tg:${e.chatId}`,o),{ok:!0}}catch(a){return{ok:!1,error:String(a)}}}let s=n.gatewayMode;if(s!=="telegram"&&s!=="both")return{ok:!1,error:"gatewayMode does not include Telegram."};if(!Number.isFinite(e.chatId))return{ok:!1,error:"Invalid Telegram chat id."};try{return await r(e.chatId,p(o)),{ok:!0}}catch(i){return{ok:!1,error:String(i)}}}return{ok:!1,error:"Unknown channel."}}async function BR(e,t,n){let o;try{o=JSON.parse(e)}catch{return{ok:!1,error:"Invalid JSON."}}if(!o||typeof o!="object")return{ok:!1,error:"Invalid request."};if(typeof o.token!="string"||!n)return{ok:!1,error:"Unauthorized."};try{let r=Buffer.from(o.token,"utf8"),s=Buffer.from(n,"utf8");if(r.length!==s.length||!Ug.timingSafeEqual(r,s))return{ok:!1,error:"Unauthorized."}}catch{return{ok:!1,error:"Unauthorized."}}return o.op==="sendMedia"?WR(o,t):o.op==="sendText"?UR(o,t):o.op==="sendPeerText"?HR(o,t):o.op==="sendPeerMedia"?jR(o,t):{ok:!1,error:"Unsupported operation."}}async function jR(e,t){let n=typeof e.peerKey=="string"?e.peerKey.trim():"",o=typeof e.absPath=="string"?e.absPath.trim():"";if(!n)return{ok:!1,error:"Missing peerKey."};if(!o)return{ok:!1,error:"Missing absPath."};let r=t.getCfg(),s=typeof e.caption=="string"&&e.caption.length>0?e.caption:void 0,i=!!t.sendPlatformMedia&&!t.getWaOutbound()&&!t.getTgSendMedia(),a=dc(r,i),l=Ct(o,a);if("error"in l)return{ok:!1,error:l.error};let c={absPath:l.absPath,category:l.category,mimetype:l.mimetype,displayName:l.displayName,caption:s};if(t.sendPeerMedia)try{return await t.sendPeerMedia(n,c),{ok:!0}}catch(u){return{ok:!1,error:String(u)}}if(t.sendPlatformMedia)try{return await t.sendPlatformMedia(n,c),{ok:!0}}catch(u){return{ok:!1,error:String(u)}}if(n.startsWith("wa:")){let u=t.getWaOutbound();if(!u)return{ok:!1,error:"WhatsApp outbound is not connected."};let d=r.gatewayMode;if(d!=="whatsapp"&&d!=="both")return{ok:!1,error:"gatewayMode does not include WhatsApp."};try{return await u.sendMedia(n.slice(3),c),{ok:!0}}catch(m){return{ok:!1,error:String(m)}}}if(n.startsWith("tg:")){let u=t.getTgSendMedia();if(!u)return{ok:!1,error:"Telegram outbound is not connected."};let d=r.gatewayMode;if(d!=="telegram"&&d!=="both")return{ok:!1,error:"gatewayMode does not include Telegram."};let m=Number(n.slice(3));if(!Number.isFinite(m))return{ok:!1,error:"Invalid Telegram peer key."};try{return await u(m,c),{ok:!0}}catch(f){return{ok:!1,error:String(f)}}}return{ok:!1,error:"Unknown peer key prefix."}}async function HR(e,t){let n=typeof e.peerKey=="string"?e.peerKey.trim():"",o=typeof e.text=="string"?e.text.trim():"";if(!n)return{ok:!1,error:"Missing peerKey."};if(!o)return{ok:!1,error:"Missing or empty text."};if(t.sendPlatformText)try{return await t.sendPlatformText(n,o),{ok:!0}}catch(s){return{ok:!1,error:String(s)}}let r=t.getCfg();if(n.startsWith("wa:")){let s=t.getWaOutbound();if(!s)return{ok:!1,error:"WhatsApp outbound is not connected."};let i=r.gatewayMode;if(i!=="whatsapp"&&i!=="both")return{ok:!1,error:"gatewayMode does not include WhatsApp."};try{return await s.sendText(n.slice(3),o),{ok:!0}}catch(a){return{ok:!1,error:String(a)}}}if(n.startsWith("tg:")){let s=t.getTgSendText();if(!s)return{ok:!1,error:"Telegram outbound is not connected."};let i=r.gatewayMode;if(i!=="telegram"&&i!=="both")return{ok:!1,error:"gatewayMode does not include Telegram."};let a=Number(n.slice(3));if(!Number.isFinite(a))return{ok:!1,error:"Invalid Telegram peer key."};try{return await s(a,p(o)),{ok:!0}}catch(l){return{ok:!1,error:String(l)}}}return{ok:!1,error:"Unknown peer key prefix."}}function wa(e){if(ns)return;let t=Ug.randomBytes(32).toString("hex"),n=NR.createServer(o=>{let r="";o.setTimeout(12e4),o.on("data",s=>{r+=s.toString("utf8");let i=r.indexOf(`
482
+ `);if(i===-1)return;let a=r.slice(0,i).trim();r=r.slice(i+1);let l=_R();BR(a,e,l).then(c=>{o.write(`${JSON.stringify(c)}
483
+ `),o.end()})}),o.on("error",()=>{})});n.listen(0,"127.0.0.1",()=>{let o=n.address();if(!o||typeof o=="string"){E.error("gateway control: could not read listen address");return}let r={token:t,host:o.address,port:o.port};FR(r),E.info({port:o.port},"gateway control listening")}),n.on("error",o=>{E.error({err:String(o)},"gateway control server error")}),ns=n}function sr(){if(ns){try{ns.close()}catch{}ns=null,DR()}}ye();import JR from"node:crypto";import GR from"node:http";var qR=256*1024;function zR(e,t){if(!e||!t)return!1;try{let n=Buffer.from(e,"utf8"),o=Buffer.from(t,"utf8");return n.length===o.length&&JR.timingSafeEqual(n,o)}catch{return!1}}function KR(e){let t=typeof e.source=="string"?e.source:"webhook";if(e.action==="completed"&&e.workflow_run&&typeof e.workflow_run=="object"){let a=e.workflow_run,l=a.name??"?",c=a.conclusion??"?",u=a.head_branch??"?",d=a.html_url??"",m=e.repository?.full_name??"?";return`[${t}] ${m} \u2014 ${l}
483
484
  result: ${c}
484
485
  branch: ${u}${d?`
485
486
  ${d}`:""}`}if(e.object_kind==="pipeline"&&e.object_attributes&&typeof e.object_attributes=="object"){let a=e.object_attributes,l=a.status??"?",c=a.ref??"?",u=a.id??"?",d=e.project?.path_with_namespace??"?";return`[${t}] ${d} \u2014 pipeline #${u}
486
487
  status: ${l}
487
488
  ref: ${c}`}let n=typeof e.text=="string"?e.text:null,o=typeof e.message=="string"?e.message:null,r=typeof e.title=="string"?e.title:null,s=typeof e.status=="string"?e.status:null;if(n)return`[${t}] ${n}`;let i=[`[${t}]`];if(r&&i.push(r),o&&i.push(o),s&&i.push(`status: ${s}`),i.length===1){let a=JSON.stringify(e).slice(0,500);i.push(a)}return i.join(`
488
- `)}var pu=null;function wa(e,t){if(pu)return{stop:()=>{}};let n=FR.createServer((o,r)=>{if(o.method!=="POST"){r.writeHead(405,{"Content-Type":"application/json"}),r.end(JSON.stringify({ok:!1,error:"Method not allowed"}));return}let s=o.headers.authorization,i=s?.startsWith("Bearer ")?s.slice(7):void 0,a=new URL(o.url??"/",`http://${o.headers.host??"localhost"}`).searchParams.get("token")??void 0;if(!WR(i??a,e.token)){r.writeHead(401,{"Content-Type":"application/json"}),r.end(JSON.stringify({ok:!1,error:"Unauthorized"}));return}let c="",u=0;o.on("data",d=>{if(u+=d.length,u>DR){r.writeHead(413,{"Content-Type":"application/json"}),r.end(JSON.stringify({ok:!1,error:"Payload too large"})),o.destroy();return}c+=d.toString("utf8")}),o.on("end",()=>{let d;try{d=JSON.parse(c)}catch{r.writeHead(400,{"Content-Type":"application/json"}),r.end(JSON.stringify({ok:!1,error:"Invalid JSON"}));return}let f=new URL(o.url??"/",`http://${o.headers.host??"localhost"}`).searchParams.get("source")??o.headers["x-webhook-source"]??void 0;f&&(d.source=f);let h=typeof d.peerKey=="string"&&d.peerKey||t.getDefaultPeerKey();if(!h){r.writeHead(400,{"Content-Type":"application/json"}),r.end(JSON.stringify({ok:!1,error:"No target peer. Set peerKey in body or configure an allowlisted identity."}));return}let g=UR(d);t.sendToPeer(h,g).then(()=>{r.writeHead(200,{"Content-Type":"application/json"}),r.end(JSON.stringify({ok:!0}))},y=>{P.warn({err:String(y)},"webhook: sendToPeer failed"),r.writeHead(502,{"Content-Type":"application/json"}),r.end(JSON.stringify({ok:!1,error:"Failed to deliver message"}))})})});return n.listen(e.port,e.host,()=>{let o=n.address(),r=typeof o=="object"&&o?o.port:e.port;P.info({port:r,host:e.host},"webhook receiver listening")}),n.on("error",o=>{P.error({err:String(o)},"webhook receiver error")}),pu=n,{stop:()=>{try{n.close()}catch{}pu=null}}}ue();import AE from"node:crypto";import Xu from"node:fs";be();import BR from"node:fs";function Fg(e,t,n){let o=e.fileReceiveMaxBytes,r;try{r=Buffer.from(n.dataBase64,"base64")}catch{return{mediaError:"Invalid inbound media payload."}}if(o>0&&r.length>o)return{mediaError:`Media too large (max ${o} bytes).`};let s;try{s=wn(e,t)}catch(a){return{mediaError:String(a)}}let i=er(s,t,n.name);try{return BR.writeFileSync(i,r,{mode:384}),{mediaSavedPath:i}}catch{return{mediaError:"Could not write media to inbox."}}}j();rs();be();import vu from"ws";import KR from"ws";var zR=["/platform/device","/control/device"];function va(e){let t=e.replace(/\/$/,"");return zR.map(n=>{let o=new URL(n,t);return o.protocol=o.protocol==="https:"?"wss:":"ws:",o.toString()})}function YR(e){let t=String(e instanceof Error?e.message:e);return/Unexpected server response:\s*400/.test(t)||/Unexpected server response:\s*404/.test(t)}async function Bg(e,t){let n=va(e),o=null;for(let s of n)try{return{ws:await new Promise((a,l)=>{let c=new KR(s,{headers:{Authorization:`Bearer ${t}`},maxPayload:Hm()});c.once("open",()=>a(c)),c.once("error",l)}),pathname:new URL(s).pathname}}catch(i){if(o=i instanceof Error?i:new Error(String(i)),YR(i))continue;throw o}let r="Could not open a device WebSocket. Redeploy the relay (for /control/device fallback) and ensure /platform/* or /control/* route to port 8788.";throw o&&/Unexpected server response:\s*400/.test(o.message)?new Error(`${o.message} \u2014 ${r}`):new Error(o?`${o.message} \u2014 ${r}`:r)}var VR=1e3,QR=6e4,xa=class{constructor(t){this.options=t}ws=null;stopped=!1;pingTimer=null;registerResolve=null;registerReject=null;registeredAccount=null;registeredDeviceId=null;reconnectAttempt=0;reconnectTimer=null;outboundQueue=[];getRegisteredAccount(){return this.registeredAccount}async connect(){return this.connectOnce()}async connectOnce(){let{env:t}=this.options,n=new Promise((a,l)=>{this.registerResolve=a,this.registerReject=l}),{ws:o,pathname:r}=await Bg(t.platformUrl,t.token);this.ws=o,r!=="/platform/device"&&P.info({pathname:r},"platform device websocket connected via fallback path"),o.on("message",a=>{this.handleMessage(a.toString())}),o.on("close",()=>{this.stopped||(P.warn("platform device websocket closed; scheduling reconnect"),this.scheduleReconnect())}),o.on("error",a=>{P.warn({err:String(a)},"platform device websocket error")});let s=setTimeout(()=>{this.registerReject?.(new Error("Platform device register timeout (15s)"))},15e3);this.sendRaw({type:"register",...t.deviceId?{deviceId:t.deviceId}:{},label:process.env.OMNISH_DEVICE_LABEL?.trim()||"default"});let i=await n;return clearTimeout(s),this.registeredDeviceId=i.deviceId,this.reconnectAttempt=0,this.pingTimer&&clearInterval(this.pingTimer),this.pingTimer=setInterval(()=>{this.sendRaw({type:"ping"})},3e4),this.flushOutboundQueue(),i}scheduleReconnect(){if(this.stopped||this.reconnectTimer)return;this.pingTimer&&(clearInterval(this.pingTimer),this.pingTimer=null),this.ws=null;let t=Math.min(VR*2**this.reconnectAttempt,QR);this.reconnectAttempt+=1,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,!this.stopped&&this.connectOnce().catch(n=>{P.warn({err:String(n)},"platform device reconnect failed"),this.scheduleReconnect()})},t),this.reconnectTimer.unref?.()}async handleMessage(t){let n=Gm(t);if(n){if(n.type==="registered"&&"deviceId"in n){n.account&&(this.registeredAccount=n.account),this.registerResolve?.({deviceId:n.deviceId,...n.account?{account:n.account}:{}}),this.registerResolve=null,this.registerReject=null;return}if(n.type==="message"){await this.options.onMessage(n);return}if(n.type==="reply_error"){await this.options.onReplyError?.(n.peerKey,n.error,n.messageId);return}n.type==="error"&&(P.warn({message:n.message},"platform error"),this.registerReject?.(new Error(n.message)),this.registerResolve=null,this.registerReject=null)}}sendReply(t,n,o,r){this.enqueue({type:"reply",peerKey:t,...n?{body:n}:{},...o?{messageId:o}:{},...r?.length?{files:r}:{}})}sendRoutedReply(t,n){this.enqueue({type:"reply",peerKey:t,...n})}enqueue(t){if(this.ws?.readyState===vu.OPEN){this.sendRaw(t);return}if(this.outboundQueue.length>=Jm){P.warn("platform outbound queue full; dropping reply");return}this.outboundQueue.push(t)}flushOutboundQueue(){for(;this.outboundQueue.length>0&&this.ws?.readyState===vu.OPEN;){let t=this.outboundQueue.shift();t&&this.sendRaw(t)}}sendRaw(t){this.ws?.readyState===vu.OPEN&&this.ws.send(JSON.stringify(t))}stop(){this.stopped=!0,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.pingTimer&&(clearInterval(this.pingTimer),this.pingTimer=null);try{this.ws?.close()}catch{}this.ws=null,this.outboundQueue.length=0}};import Tu from"node:readline/promises";import{stdin as Eu,stdout as Ma}from"node:process";ue();var XR=/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;function Hg(e){let t=e.trim().toLowerCase();return t?t.length>63?"Tunnel name must be at most 63 characters.":XR.test(t)?null:"Tunnel name may only use letters, digits, and hyphens.":"Tunnel name must not be empty."}function Jg(e){let t=Number.parseInt(e,10);return!Number.isInteger(t)||t<1||t>65535?null:t}function jg(e,t){let n="127.0.0.1",o,r,s=!1,i=[];for(let c=0;c<t.length;c++){let u=t[c];if(u==="--host"){let d=t[++c];if(!d)return{kind:"error",message:"--host requires an address."};n=d;continue}if(u.startsWith("--host=")){n=u.slice(7);continue}if(u==="--relay"){let d=t[++c];if(!d)return{kind:"error",message:"--relay requires a URL."};o=d;continue}if(u.startsWith("--relay=")){o=u.slice(8);continue}if(u==="--name"){let d=t[++c];if(!d)return{kind:"error",message:"--name requires a slug."};r=d;continue}if(u.startsWith("--name=")){r=u.slice(7);continue}if(u==="--background"||u==="-b"){s=!0;continue}i.push(u)}let a=i[0];if(!a)return{kind:"error",message:`Usage: omnish tunnel ${e} <port> [--host <addr>] [--name <slug>] [--relay <url>] [--background]`};let l=Jg(a);if(l===null)return{kind:"error",message:"Port must be an integer between 1 and 65535."};if(r){let c=Hg(r);if(c)return{kind:"error",message:c}}return{kind:"expose",options:{kind:e,port:l,host:n,relayUrl:o??"",name:r,background:s}}}function xu(e){let[t,...n]=e,o=(t??"").trim().toLowerCase();if(!o||o==="help"||o==="-h"||o==="--help")return{kind:"help"};if(o==="signup"){let r,s,i,a;for(let l=0;l<n.length;l++){let c=n[l];if(c==="--email"){r=n[++l];continue}if(c.startsWith("--email=")){r=c.slice(8);continue}if(c==="--phone"){s=n[++l];continue}if(c.startsWith("--phone=")){s=c.slice(8);continue}if(c==="--password"){i=n[++l];continue}if(c.startsWith("--password=")){i=c.slice(11);continue}if(c==="--relay"){a=n[++l];continue}if(c.startsWith("--relay=")){a=c.slice(8);continue}}return{kind:"signup",email:r,phone:s,password:i,relayUrl:a}}if(o==="login"){let r,s,i,a,l;for(let c=0;c<n.length;c++){let u=n[c];if(u==="--token"){r=n[++c];continue}if(u.startsWith("--token=")){r=u.slice(8);continue}if(u==="--email"){s=n[++c];continue}if(u.startsWith("--email=")){s=u.slice(8);continue}if(u==="--phone"){i=n[++c];continue}if(u.startsWith("--phone=")){i=u.slice(8);continue}if(u==="--password"){a=n[++c];continue}if(u.startsWith("--password=")){a=u.slice(11);continue}if(u==="--relay"){l=n[++c];continue}if(u.startsWith("--relay=")){l=u.slice(8);continue}r||(r=u)}return{kind:"login",token:r,email:s,phone:i,password:a,relayUrl:l}}if(o==="logout")return{kind:"logout"};if(o==="list")return{kind:"list"};if(o==="status"){let r;for(let s=0;s<n.length;s++){let i=n[s];if(i==="--relay"){r=n[s+1],s++;continue}i.startsWith("--relay=")&&(r=i.slice(8))}return{kind:"status",relayUrl:r}}if(o==="stop"){let r=n[0]?.trim();return r?{kind:"stop",target:r}:{kind:"error",message:"Usage: omnish tunnel stop <id|slug>"}}return o==="http"?jg("http",n):o==="tcp"?jg("tcp",n):{kind:"error",message:`Unknown tunnel subcommand "${t}". Try: omnish tunnel help`}}function ZR(e){let t=[],n="",o=null;for(let r=0;r<e.length;r++){let s=e[r];if(o){s===o?o=null:n+=s;continue}if(s==='"'||s==="'"){o=s;continue}if(/\s/.test(s)){n.length&&(t.push(n),n="");continue}n+=s}return n.length&&t.push(n),t}function Gg(e){let t=e.trim();if(!t||t==="help")return{kind:"help"};let n=ZR(t),o=n[0]?.toLowerCase();if(o==="login"||o==="logout"||o==="status"||o==="signup")return xu(n);if(t.toLowerCase()==="list"||t.toLowerCase()==="ls")return{kind:"list"};let r=t.match(/^stop\s+(\S+)\s*$/i);if(r)return{kind:"stop",target:r[1]};let s=t.match(/^(http|tcp)\s+(\d+)(?:\s+--name\s+(\S+))?(?:\s+--host\s+(\S+))?\s*$/i);if(s){let i=s[1].toLowerCase(),a=Jg(s[2]);if(a===null)return{kind:"error",message:"Port must be an integer between 1 and 65535."};let l=s[3],c=s[4]??"127.0.0.1";if(l){let u=Hg(l);if(u)return{kind:"error",message:u}}return{kind:"expose",options:{kind:i,port:a,host:c,relayUrl:"",name:l,background:!0}}}return{kind:"error",message:"Usage: /tunnel login \u2026 | /tunnel status | /tunnel http <port> | /tunnel tcp <port> | /tunnels | /tunnel stop <id>"}}import eT from"ws";import{URL as qg}from"node:url";function lr(e){let t=new qg(e);return t.protocol=t.protocol==="https:"?"wss:":"ws:",(!t.pathname||t.pathname==="/")&&(t.pathname="/control"),t.toString()}function zg(e){return new qg("/health",e).toString()}async function Ca(e,t,n=1e4){let o=t.trim();if(!o)return{ok:!1,healthOk:!1,controlOk:!1,error:"Tunnel token is missing."};let r=zg(e),s=!1,i;try{let c=await fetch(r,{method:"GET",signal:AbortSignal.timeout(n)});if(!c.ok)return{ok:!1,healthOk:!1,controlOk:!1,error:`Health HTTP ${c.status} (${r})`};let u=await c.json();if(!u?.ok)return{ok:!1,healthOk:!1,controlOk:!1,error:"Health JSON missing ok:true"};s=!0,typeof u.version=="string"&&(i=u.version)}catch(c){return{ok:!1,healthOk:!1,controlOk:!1,error:`Health fetch failed: ${String(c)}`}}let a=lr(e),l=!1;try{await new Promise((c,u)=>{let d=new eT(a,{headers:{Authorization:`Bearer ${o}`}}),m=setTimeout(()=>{d.terminate(),u(new Error("WSS auth timeout"))},n);d.once("message",f=>{clearTimeout(m);try{let h=JSON.parse(f.toString());if(h.type!=="auth_ok"){u(new Error(`Expected auth_ok, got ${h.type??"?"}`));return}d.close(),c()}catch(h){u(h)}}),d.once("error",f=>{clearTimeout(m),u(f)})}),l=!0}catch(c){return{ok:!1,healthOk:!0,healthVersion:i,controlOk:!1,error:`Control WebSocket: ${String(c)}`}}return{ok:!0,healthOk:s,healthVersion:i,controlOk:!0}}Kn();Yn();Yn();Kn();import Vg from"node:crypto";import tT from"node:http";import nT from"node:net";import Un from"ws";function Ra(e,t,n=Buffer.alloc(0)){let o=Buffer.allocUnsafe(9+n.length);return o.writeUInt8(e,0),o.writeUInt32BE(t,1),o.writeUInt32BE(n.length,5),n.copy(o,9),o}function Kg(e){if(e.length<9)return null;let t=e.readUInt8(0),n=e.readUInt32BE(1),o=e.readUInt32BE(5);return e.length<9+o?null:{frameType:t,streamId:n,payload:e.subarray(9,9+o)}}function Cu(e){try{let t=JSON.parse(e);return!t||typeof t!="object"||typeof t.type!="string"?null:t}catch{return null}}function cr(e){return JSON.stringify(e)}function oT(e){let t=Vg.createHash("sha1").update(e,"utf8").digest("hex").slice(0,8);return Number.parseInt(t,16)>>>0}var Ta=class{constructor(t){this.opts=t;let n=Vg.randomBytes(4).toString("hex");this.record={id:n,kind:t.expose.kind,slug:t.expose.name?.trim().toLowerCase()??"",localHost:t.expose.host,localPort:t.expose.port,publicUrl:"",status:"connecting",startedAt:new Date().toISOString()}}ws=null;record;tcpStreams=new Map;tcpStreamIds=new Map;stopped=!1;pingTimer=null;getRecord(){return{...this.record}}setStatus(t,n){this.record.status=t,this.record.error=n,this.opts.onStatus?.(this.getRecord())}async start(){if(this.stopped)throw new Error("Tunnel client already stopped.");let t=lr(this.opts.relayUrl),n=new Un(t,{headers:{Authorization:`Bearer ${this.opts.token}`}});this.ws=n,await new Promise((r,s)=>{let i=c=>{l(),s(c)},a=()=>{l(),r()},l=()=>{n.off("error",i),n.off("open",a)};n.once("error",i),n.once("open",a)}),n.on("message",(r,s)=>{if(s){this.handleBinary(Buffer.isBuffer(r)?r:Buffer.from(r));return}let i=typeof r=="string"?r:r.toString("utf8");this.handleControl(i)}),n.on("close",()=>{this.stopped||this.setStatus("error","Relay connection closed."),this.cleanupTcpStreams()}),n.on("error",r=>{this.stopped||this.setStatus("error",String(r))}),this.pingTimer=setInterval(()=>{n.readyState===Un.OPEN&&n.send(cr({type:"ping"}))},3e4),n.send(cr({type:"register",id:this.record.id,kind:this.opts.expose.kind,localHost:this.opts.expose.host,localPort:this.opts.expose.port,...this.opts.expose.name?{name:this.opts.expose.name}:{}}));let o=await this.waitForRegistered();return this.record.slug=o.slug,this.record.publicUrl=o.publicUrl,this.setStatus("active"),this.getRecord()}waitForRegistered(){let t=this.ws;return t?new Promise((n,o)=>{let r=s=>{if(typeof s!="string"&&!Buffer.isBuffer(s))return;let i=typeof s=="string"?s:s.toString("utf8"),a=Cu(i);if(a){if(a.type==="registered"&&a.id===this.record.id){t.off("message",r),n(a);return}a.type==="error"&&(t.off("message",r),o(new Error(a.message)))}};t.on("message",r)}):Promise.reject(new Error("WebSocket not connected."))}async handleControl(t){let n=Cu(t);if(n&&!(n.type==="pong"||n.type==="auth_ok")){if(n.type==="error"){this.setStatus("error",n.message);return}if(n.type==="http"){await this.handleHttpRequest(n);return}if(n.type==="tcp_open"){await this.handleTcpOpen(n.streamId);return}n.type==="tcp_close"&&this.closeTcpStream(n.streamId)}}handleBinary(t){let n=Kg(t);if(!n)return;let o=[...this.tcpStreamIds.entries()].find(([,s])=>s===n.streamId)?.[0];if(!o)return;let r=this.tcpStreams.get(o);if(r){if(n.frameType===2){r.write(n.payload);return}n.frameType===3&&(r.end(),this.tcpStreams.delete(o),this.tcpStreamIds.delete(o))}}async handleHttpRequest(t){let n=this.ws;if(!n||n.readyState!==Un.OPEN)return;let o=t.bodyBase64?Buffer.from(t.bodyBase64,"base64"):void 0,r={host:this.opts.expose.host,port:this.opts.expose.port,method:t.method,path:t.path,headers:{...t.headers}};await new Promise(s=>{let i=tT.request(r,a=>{let l=[];a.on("data",c=>l.push(Buffer.isBuffer(c)?c:Buffer.from(c))),a.on("end",()=>{let c=Buffer.concat(l);n.send(cr({type:"http_res",requestId:t.requestId,status:a.statusCode??502,headers:a.headers,...c.length>0?{bodyBase64:c.toString("base64")}:{}})),s()})});i.on("error",a=>{n.send(cr({type:"http_res",requestId:t.requestId,status:502,headers:{"content-type":"text/plain"},bodyBase64:Buffer.from(String(a)).toString("base64")})),s()}),o&&o.length>0&&i.write(o),i.end()})}async handleTcpOpen(t){let n=this.ws;if(!n||n.readyState!==Un.OPEN)return;let o=oT(t);this.tcpStreamIds.set(t,o);let r=nT.connect({host:this.opts.expose.host,port:this.opts.expose.port});this.tcpStreams.set(t,r),r.on("data",s=>{n.readyState===Un.OPEN&&n.send(Ra(2,o,Buffer.isBuffer(s)?s:Buffer.from(s)))}),r.on("close",()=>{n.readyState===Un.OPEN&&n.send(Ra(3,o)),this.tcpStreams.delete(t),this.tcpStreamIds.delete(t)}),r.on("error",()=>{n.readyState===Un.OPEN&&n.send(Ra(3,o)),this.tcpStreams.delete(t),this.tcpStreamIds.delete(t)})}closeTcpStream(t){let n=this.tcpStreams.get(t);n&&n.destroy(),this.tcpStreams.delete(t),this.tcpStreamIds.delete(t)}cleanupTcpStreams(){for(let t of this.tcpStreams.values())t.destroy();this.tcpStreams.clear(),this.tcpStreamIds.clear()}async stop(){if(this.stopped)return;this.stopped=!0,this.pingTimer&&(clearInterval(this.pingTimer),this.pingTimer=null);let t=this.ws;t&&t.readyState===Un.OPEN&&(t.send(cr({type:"unregister",id:this.record.id})),t.close()),this.cleanupTcpStreams(),this.setStatus("stopped")}};var Ea=class{clients=new Map;list(){return[...new Set(this.clients.values())].map(t=>t.getRecord())}getActiveCount(){return[...new Set(this.clients.values())].filter(t=>t.getRecord().status==="active").length}async expose(t,n){let o=Dt();if(!o)throw new Error("No tunnel token configured. Run `omnish tunnel login` or set OMNISH_TUNNEL_TOKEN.");let r=t.tunnelMaxActive>0?t.tunnelMaxActive:5;if(this.getActiveCount()>=r)throw new Error(`Active tunnel limit reached (${r}). Stop one with \`omnish tunnel stop <id>\`.`);let s=n.relayUrl||vt(t.tunnelRelayUrl||Le),i=new Ta({relayUrl:s,token:o,expose:n,onStatus:l=>{(l.status==="stopped"||l.status==="error")&&this.clients.delete(l.id)}}),a=await i.start();return this.clients.set(a.id,i),a.slug&&this.clients.set(a.slug,i),a}async stop(t){let n=t.trim().toLowerCase(),o=this.clients.get(n)??this.clients.get(t.trim());if(!o)return null;let r=o.getRecord();return await o.stop(),this.clients.delete(r.id),r.slug&&this.clients.delete(r.slug),o.getRecord()}async stopAll(){let t=[...new Set(this.clients.values())];await Promise.all(t.map(n=>n.stop())),this.clients.clear()}};async function Qg(e,t){let n=await fetch(e,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t),signal:AbortSignal.timeout(15e3)}),o=await n.json();return!n.ok||!o.token?{ok:!1,error:o.error??`HTTP ${n.status}`}:{ok:!0,token:o.token}}function Pa(e,t){let n=new URL("/auth/signup",e).toString();return Qg(n,{...t.email?{email:t.email}:{},...t.phone?{phone:t.phone}:{},password:t.password})}function $a(e,t){let n=new URL("/auth/login",e).toString();return Qg(n,{...t.email?{email:t.email}:{},...t.phone?{phone:t.phone}:{},password:t.password})}var ur=new Ea;function mo(){return ur}function rT(e){let t=[K(e,"omnish tunnel"),w(e,"Expose local HTTP or TCP ports through the omnish relay."),"",K(e,"Usage:"),` ${S(e,"omnish tunnel signup [--email <email>] [--phone <phone>] [--password <pass>] [--relay <url>]")}`,` ${S(e,"omnish tunnel login [--token <token>] [--relay <url>]")}`,` ${S(e,"omnish tunnel login --email <email> --password <pass> [--relay <url>]")}`,` ${S(e,"omnish tunnel logout")}`,` ${S(e,"omnish tunnel status [--relay <url>]")}`,` ${S(e,"omnish tunnel http <port> [--host <addr>] [--name <slug>] [--relay <url>] [--background]")}`,` ${S(e,"omnish tunnel tcp <port> [--host <addr>] [--name <slug>] [--relay <url>] [--background]")}`,` ${S(e,"omnish tunnel list")}`,` ${S(e,"omnish tunnel stop <id|slug>")}`,"",w(e,"Secrets live in ~/.omnish/tunnel-auth.json or OMNISH_TUNNEL_TOKEN."),w(e,`Default relay: ${Le}`),""];console.log(t.join(`
489
- `))}async function sT(){let e=Tu.createInterface({input:Eu,output:Ma});try{return(await e.question("Tunnel token: ")).trim()}finally{e.close()}}async function Xg(e){let t=xu(e),n=Ma,o=process.stderr;if(t.kind==="help"){rT(n);return}if(t.kind==="error"){console.error(E(o,t.message)),process.exitCode=1;return}let r=v();if(t.kind==="signup"){let s=t.relayUrl||vt(r.tunnelRelayUrl||Le),i=Tu.createInterface({input:Eu,output:Ma});try{let a=t.email?.trim()||(await i.question("Email (or leave empty for phone): ")).trim(),l=t.phone?.trim()||"";if(a||(l=l||(await i.question("Phone: ")).trim()),!a&&!l){console.error(E(o,"Email or phone is required.")),process.exitCode=1;return}let c=t.password||(await i.question("Password (min 8 chars): ")).trim();if(c.length<8){console.error(E(o,"Password must be at least 8 characters.")),process.exitCode=1;return}let u=await Pa(s,{...a?{email:a}:{},...l?{phone:l}:{},password:c});if(!u.ok){console.error(E(o,u.error)),process.exitCode=1;return}Ft({token:u.token,...t.relayUrl?{relayUrl:t.relayUrl}:{}}),console.log(H(n,"Account created. Token saved."))}finally{i.close()}return}if(t.kind==="login"){if(t.email||t.phone){let a=t.relayUrl||vt(r.tunnelRelayUrl||Le),l=Tu.createInterface({input:Eu,output:Ma});try{let c=t.password||(await l.question("Password: ")).trim(),u=await $a(a,{...t.email?{email:t.email}:{},...t.phone?{phone:t.phone}:{},password:c});if(!u.ok){console.error(E(o,u.error)),process.exitCode=1;return}Ft({token:u.token,...t.relayUrl?{relayUrl:t.relayUrl}:{}}),console.log(H(n,"Logged in. Token saved."))}finally{l.close()}return}let i=t.token?.trim()||await sT();if(!i){console.error(E(o,"Tunnel token is required.")),process.exitCode=1;return}Ft({token:i,...t.relayUrl?{relayUrl:t.relayUrl}:{}}),console.log(H(n,"Tunnel token saved."));return}if(t.kind==="logout"){Ks(),console.log(H(n,"Tunnel token removed."));return}if(t.kind==="status"){let s=t.relayUrl||vt(r.tunnelRelayUrl||Le),i=Dt(),a=!!process.env.OMNISH_TUNNEL_TOKEN?.trim(),l=await Ca(s,i);console.log(`${w(n,"relay:")} ${S(n,s)}`),console.log(`${w(n,"token:")} ${S(n,i?`configured${a?" (OMNISH_TUNNEL_TOKEN)":""}`:E(o,"missing"))}`),console.log(`${w(n,"health:")} ${l.healthOk?S(n,`ok${l.healthVersion?` (${l.healthVersion})`:""}`):E(o,"fail")}`),console.log(`${w(n,"control:")} ${l.controlOk?S(n,"auth ok"):E(o,"fail")}${l.error&&!l.ok?` \u2014 ${l.error}`:""}`),console.log(`${w(n,"active:")} ${S(n,String(ur.getActiveCount()))}`);return}if(t.kind==="list"){let s=ur.list();if(s.length===0){console.log(H(n,"(no active tunnels)"));return}for(let i of s)console.log(`${S(n,i.id)} ${i.kind} ${i.status} ${i.publicUrl||"(pending)"}
490
- ${w(n,`${i.localHost}:${i.localPort}`)}`);return}if(t.kind==="stop"){let s=await ur.stop(t.target);if(!s){console.error(E(o,`No active tunnel matched "${t.target}".`)),process.exitCode=1;return}console.log(H(n,`Stopped tunnel ${s.id}.`));return}if(t.kind==="expose"){let s=t.options.relayUrl||vt(r.tunnelRelayUrl||Le),i=await ur.expose(r,{...t.options,relayUrl:s});console.log(H(n,`${i.kind.toUpperCase()} tunnel active`)),console.log(`${w(n,"public:")} ${S(n,i.publicUrl)}`),console.log(`${w(n,"local:")} ${S(n,`${i.localHost}:${i.localPort}`)}`),console.log(`${w(n,"id:")} ${S(n,i.id)}`),t.options.background||(console.log(w(n,"Press Ctrl+C to stop.")),await new Promise(a=>{let l=async()=>{await ur.stop(i.id),a()};process.once("SIGINT",l),process.once("SIGTERM",l)}))}}function Zg(e){let t=e.trim().match(/^(\d+)\.(\d+)\.(\d+)/);return t?[Number(t[1]),Number(t[2]),Number(t[3])]:null}function ey(e,t){let n=Zg(e),o=Zg(t);if(!n||!o)return e.trim().localeCompare(t.trim());for(let r=0;r<3;r++)if(n[r]!==o[r])return n[r]<o[r]?-1:1;return 0}var iT="https://registry.npmjs.org",ty=null,Pu=0;function ss(){return ty}function aT(e){let t=e.trim();return t.length<1||t.length>214?!1:!/\s/.test(t)}function ny(e){let t=e.trim();if(!t||t.length>2048)return null;try{let n=new URL(t);return n.protocol!=="https:"?null:n.href}catch{return null}}async function lT(e){let t=e.trim(),n=`${iT}/${encodeURIComponent(t)}/latest`;try{let o=await fetch(n,{headers:{Accept:"application/json"},signal:AbortSignal.timeout(2e4)});if(!o.ok)return{error:`npm registry: HTTP ${o.status}`};let r=await o.json(),s=typeof r.version=="string"?r.version.trim():"";return s?{version:s}:{error:"npm registry: missing version in response"}}catch(o){return{error:`npm registry: ${String(o)}`}}}async function cT(e){try{let t=await fetch(e,{headers:{Accept:"application/json"},signal:AbortSignal.timeout(15e3)});if(!t.ok)return{error:`updateInfoUrl: HTTP ${t.status}`};let n=await t.arrayBuffer(),o=n.byteLength>65536?n.slice(0,65536):n,r=new TextDecoder("utf-8",{fatal:!1}).decode(o),s;try{s=JSON.parse(r)}catch{return{error:"updateInfoUrl: body is not JSON"}}if(!s||typeof s!="object"||Array.isArray(s))return{error:"updateInfoUrl: JSON must be an object"};let i=s,a=null;typeof i.message=="string"&&i.message.trim()&&(a=i.message.trim().slice(0,1500));let l=null,c=i.link??i.url;return typeof c=="string"&&c.trim()&&(l=ny(c)),{message:a,link:l}}catch(t){return{error:`updateInfoUrl: ${String(t)}`}}}function uT(e){return!Number.isFinite(e)||e<36e5?36e5:e>6048e5?6048e5:Math.floor(e)}async function is(e,t){let n=aT(t.updateCheckPackageName)?t.updateCheckPackageName.trim():"omnish",o=await lT(n),r="version"in o?o.version:null,s="error"in o?o.error:null,i=!1;r&&!s&&(i=ey(e,r)<0);let a=null,l=null,c=null,u=ny(t.updateInfoUrl);if(u){let m=await cT(u);"error"in m?c=m.error:(a=m.message,l=m.link)}let d={runningVersion:e,checkedAtIso:new Date().toISOString(),registryPackage:n,registryLatest:r,registryError:s,updateAvailable:i,infoMessage:a,infoLink:l,infoError:c};return ty=d,d}function Aa(e){if(!e)return;let t=[];if(e.registryError?t.push(`npm check: ${e.registryError}`):e.registryLatest&&t.push(e.updateAvailable?`npm latest *${e.registryLatest}* (running ${e.runningVersion})`:`npm latest ${e.registryLatest} (up to date)`),e.infoError?t.push(`info URL: ${e.infoError}`):e.infoMessage&&t.push(`notice: ${e.infoMessage}`),t.length!==0)return t.join(" \xB7 ")}function Ia(e){let t=!1,n=async()=>{if(t)return;let s=e.getConfig();if(!s.updateCheckEnabled)return;let i=uT(s.updateCheckIntervalMs),a=Date.now();if(!(Pu!==0&&a-Pu<i)){Pu=a;try{let l=await is(e.getRunningVersion(),s);l.updateAvailable?e.log.info({updateAvailable:!0,running:l.runningVersion,npmLatest:l.registryLatest,pkg:l.registryPackage},"omnish update check: newer npm version available"):e.log.info({running:l.runningVersion,npmLatest:l.registryLatest,pkg:l.registryPackage},"omnish update check ok"),l.infoMessage&&e.log.info({len:l.infoMessage.length},"omnish update info message present")}catch(l){e.log.warn({err:String(l)},"omnish update check failed")}}},o=setInterval(()=>void n(),6e4),r=setTimeout(()=>void n(),3e4);return()=>{t=!0,clearInterval(o),clearTimeout(r)}}ue();import oy from"node:path";import{glob as dT,stat as pT}from"node:fs/promises";function Oa(e){let t=e.trim();if(!t||t.startsWith("-- ")||t==="--")return null;let n=t.indexOf(" -- "),o,r;return n!==-1?(o=t.slice(0,n).trim(),r=t.slice(n+4).trim()||void 0):o=t,(o.startsWith('"')&&o.endsWith('"')||o.startsWith("'")&&o.endsWith("'"))&&(o=o.slice(1,-1)),o.trim()?{selectorPart:o,caption:r}:null}function mT(e){return/[*?[]/.test(e)}function fT(e){return e.split(",").map(t=>t.trim()).filter(t=>t.length>0)}async function as(e,t){let n=fT(t),o=new Set,r=[];for(let s of n){if(mT(s)){for await(let a of dT(s,{cwd:e,withFileTypes:!1})){let l=oy.resolve(e,a);o.has(l)||(o.add(l),r.push(l))}continue}let i=oy.resolve(e,s);o.has(i)||(o.add(i),r.push(i))}return r}async function ls(e){for(let t of e)try{if(!(await pT(t)).isFile())return{ok:!1,error:`Not a file: ${t}`}}catch{return{ok:!1,error:`File not found: ${t}`}}return{ok:!0}}j();import iy from"node:fs";import hT from"node:path";var fo="__omnish_shortcuts_global__",ry=500,gT=/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,31}$/,yT=new Set(["help","apps","bg","jobs","log","tail","kill","send","file","files","receive","reload","restart","updates","gateway","gw","mode","allow","deny","allowlist","whatsapp","wa","telegram","tg","cowork","cw","shortcut","shortcuts","alias","aliases"]),cs=new Map,sy=!1;function wT(){try{let e=iy.readFileSync(Ss,"utf8"),t=JSON.parse(e);if(t&&typeof t=="object")for(let[n,o]of Object.entries(t)){if(!o||typeof o!="object")continue;let r={};for(let[s,i]of Object.entries(o)){let a=String(s).toLowerCase();typeof i=="string"&&i.length>0&&(r[a]=i.trim())}cs.set(n,r)}}catch{}}function La(){D(hT.dirname(Ss));let e={};for(let[t,n]of cs)Object.entries(n).length>0&&(e[t]={...n});iy.writeFileSync(Ss,JSON.stringify(e,null,2)+`
491
- `,{mode:384})}function Na(){sy||(wT(),sy=!0)}function bT(e){return yT.has(e.trim().toLowerCase())}function $t(e){let t=e.trim();if(!t)return{ok:!1,error:"Name is empty."};let n=t.toLowerCase();return gT.test(n)?bT(n)?{ok:!1,error:`Reserved name: ${n}`}:{ok:!0,normalized:n}:{ok:!1,error:`Invalid name (use letters, digits, _ or -; max 32 chars): ${t}`}}function $u(e){let t=e.replace(/\r\n/g,`
492
- `).replace(/\n/g," ").trim();return t?t.length>ry?{ok:!1,error:`Body too long (max ${ry} characters).`}:{ok:!0,body:t}:{ok:!1,error:"Body is empty."}}function zt(e,t){Na();let n=cs.get(e);return!n&&t&&(n={},cs.set(e,n)),n??{}}function ay(e){let t=e.trim();if(/^--global$/i.test(t))return{mode:"global",remainder:""};if(/^-g$/i.test(t))return{mode:"global",remainder:""};if(/^--chat$/i.test(t))return{mode:"chat",remainder:""};if(/^-p$/i.test(t))return{mode:"chat",remainder:""};let n=/^--global\s+/i.exec(t);if(n)return{mode:"global",remainder:t.slice(n[0].length).trimStart()};let o=/^-g\s+/i.exec(t);if(o)return{mode:"global",remainder:t.slice(o[0].length).trimStart()};let r=/^--chat\s+/i.exec(t);if(r)return{mode:"chat",remainder:t.slice(r[0].length).trimStart()};let s=/^-p\s+/i.exec(t);return s?{mode:"chat",remainder:t.slice(s[0].length).trimStart()}:{mode:"resolved",remainder:t}}function Mu(e){let t=e.trim();if(/^--global$/i.test(t))return{scope:"global",remainder:"",explicit:!0};if(/^-g$/i.test(t))return{scope:"global",remainder:"",explicit:!0};if(/^--chat$/i.test(t))return{scope:"chat",remainder:"",explicit:!0};if(/^-p$/i.test(t))return{scope:"chat",remainder:"",explicit:!0};let n=/^--global\s+([\s\S]*)$/i.exec(t);if(n?.[1]!==void 0)return{scope:"global",remainder:n[1].trimStart(),explicit:!0};let o=/^-g\s+([\s\S]*)$/i.exec(t);if(o?.[1]!==void 0)return{scope:"global",remainder:o[1].trimStart(),explicit:!0};let r=/^--chat\s+([\s\S]*)$/i.exec(t);if(r?.[1]!==void 0)return{scope:"chat",remainder:r[1].trimStart(),explicit:!0};let s=/^-p\s+([\s\S]*)$/i.exec(t);return s?.[1]!==void 0?{scope:"chat",remainder:s[1].trimStart(),explicit:!0}:{scope:"chat",remainder:t,explicit:!1}}function Au(e){let t=Mu(e);return{scope:t.scope,remainder:t.remainder}}function ly(e){let t=e.trim();return!t||/^-+$/i.test(t)?{filter:"merged"}:/^(?:--global|-g)$/i.test(t)?{filter:"global"}:/^(?:--chat|-p)$/i.test(t)?{filter:"chat"}:{filter:"merged",bad:t}}function kT(e){let t=e.trim().toLowerCase();if(t==="--global"||t==="-g")return"global";if(t==="--chat"||t==="-p")return"chat"}function cy(e){let t=e.trim().match(/^(\S+)\s+(--global|-g|--chat|-p)\s*$/i);if(!t?.[1]||!t[2])return;let n=kT(t[2]);if(n)return{name:t[1],target:n}}function ST(e,t){Na();let n=e,o=zt(n,!1),r=zt(fo,!1);if(t==="chat")return Object.entries(o).map(([a,l])=>({name:a,body:l,scope:"chat"})).sort((a,l)=>a.name.localeCompare(l.name));if(t==="global")return Object.entries(r).map(([a,l])=>({name:a,body:l,scope:"global"})).sort((a,l)=>a.name.localeCompare(l.name));let s=new Set([...Object.keys(o),...Object.keys(r)]),i=[];for(let a of[...s].sort()){if(a in o){let c=o[a];c!==void 0&&i.push({name:a,body:c,scope:"chat"});continue}let l=r[a];l!==void 0&&i.push({name:a,body:l,scope:"global"})}return i}function uy(e,t="merged"){return ST(e,t)}function vT(e,t){let n=t.trim().toLowerCase(),o=zt(e,!1)[n];return o!==void 0?o:zt(fo,!1)[n]}function _a(e,t){let n=$t(t);if(!n.ok)return;let o=n.normalized,r=zt(e,!1)[o];if(r!==void 0)return{name:o,body:r,scope:"chat"};let s=zt(fo,!1)[o];if(s!==void 0)return{name:o,body:s,scope:"global"}}function un(e,t,n){let o=n.trim().toLowerCase(),r=e==="global"?fo:t;return zt(r,!1)[o]}function us(e,t,n,o="chat"){let r=$t(t);if(!r.ok)throw new Error(r.error);let s=$u(n);if(!s.ok)throw new Error(s.error);let i=o==="global"?fo:e,a=zt(i,!0);a[r.normalized]=s.body,La()}function dy(e,t,n="chat"){let o=t.trim().toLowerCase(),r=n==="global"?fo:e;Na();let s=cs.get(r);return!s||!(o in s)?!1:(delete s[o],La(),!0)}function Iu(e,t,n){let o=$t(t);if(!o.ok)return{ok:!1,error:o.error};let r=o.normalized,s=e;Na();let i=zt(s,!0),a=zt(fo,!0),l=i[r],c=a[r];if(n==="global"){if(l!==void 0){let u=$u(l);return u.ok?(a[r]=u.body,delete i[r],La(),{ok:!0,kind:"moved",target:"global",name:r}):{ok:!1,error:u.error}}return c!==void 0?{ok:!0,kind:"noop",message:`Shortcut "${r}" is already shared on this gateway.`}:{ok:!1,error:`No shortcut "${r}" in this chat to share. Add with /shortcut add ${r} \u2026 or make the shared copy private with /shortcut set -p ${r}.`}}if(c!==void 0){let u=$u(c);return u.ok?(i[r]=u.body,delete a[r],La(),{ok:!0,kind:"moved",target:"chat",name:r}):{ok:!1,error:u.error}}return l!==void 0?{ok:!0,kind:"noop",message:`Shortcut "${r}" is already private to this chat.`}:{ok:!1,error:`No shared shortcut "${r}" to make private. Add with /shortcut add --global ${r} \u2026 or share from this chat with /shortcut set -g ${r}.`}}var py="OMNISH_INPUT",xT=new RegExp(`\\$${py}\\b`,"g");function CT(e){return e.includes(`$${py}`)}function RT(e,t){return e.replace(xT,t)}function TT(e){let t=e.trim();if(!t)return null;let n=/^(\S+)(?:\s+([\s\S]+))?$/.exec(t);if(!n?.[1])return null;let o=$t(n[1]);if(!o.ok)return null;let r=n[2]?.trim();return r!==void 0&&r.length>0?{name:o.normalized,input:r}:{name:o.normalized}}function my(e,t,n=0){let o=TT(t);if(!o)return null;let r=vT(e,o.name);if(r===void 0)return null;if(CT(r)){if(o.input===void 0)return{ok:!1,error:`Shortcut "${o.name}" expects input (text after the name on the same line). Example: !${o.name} <text\u2026> or /${o.name} <text\u2026>`};let s=jo(o.input,n);return s.ok?{ok:!0,text:RT(r,s.task)}:{ok:!1,error:s.error}}return{ok:!0,text:r}}import fy from"node:fs";var hy=64,ET=1024*1024;function gy(e){return e.fileReceiveMaxBytes>0?e.fileReceiveMaxBytes:ET}function yy(e){let t;try{t=JSON.parse(e)}catch(r){return{ok:!1,error:`Invalid JSON: ${String(r)}`}}let n;if(Array.isArray(t))n=t;else if(t&&typeof t=="object"&&!Array.isArray(t)){let r=t,s=Object.keys(r);if(s.length!==1||s[0]!=="tasks")return{ok:!1,error:'JSON must be an array or a single-key object: { "tasks": [ \u2026 ] }.'};let i=r.tasks;if(!Array.isArray(i))return{ok:!1,error:'"tasks" must be an array.'};n=i}else return{ok:!1,error:'JSON must be an array or { "tasks": [ \u2026 ] }.'};if(n.length===0)return{ok:!1,error:"Queue JSON must contain at least one job."};if(n.length>hy)return{ok:!1,error:`Too many jobs (max ${hy}).`};let o=[];for(let r=0;r<n.length;r++){let s=n[r];if(!s||typeof s!="object"||Array.isArray(s))return{ok:!1,error:`Job ${r+1}: must be an object with "recipe" and "task".`};let i=s;if(Object.keys(i).length!==2||typeof i.recipe!="string"||typeof i.task!="string")return{ok:!1,error:`Job ${r+1}: must contain only "recipe" and "task" string fields.`};o.push({recipe:i.recipe,task:i.task})}return{ok:!0,jobs:o}}function wy(e,t,n){let o=[];for(let r=0;r<n.length;r++){let{recipe:s,task:i}=n[r],a=ze(e,t,s);if(!a)return{ok:!1,error:`Job ${r+1}: unknown recipe "${s}".`};let l=a.taskEnv??"OMNISH_TASK";if(!Ho(a.command,l))return{ok:!1,error:`Job ${r+1}: recipe "${s}" command must reference "$${l}".`};let c=jo(i,t.recipesMaxTaskChars);if(!c.ok)return{ok:!1,error:`Job ${r+1}: ${c.error}`};let u=a.promptTemplate?vi(a.promptTemplate,l,c.task):c.task,d={[l]:u};o.push({command:a.command,extraEnv:d,recipeLabel:s})}return{ok:!0,items:o}}function Ou(e,t){let n;try{n=fy.statSync(e)}catch{return{ok:!1,error:`Cannot read file: ${e}`}}if(!n.isFile())return{ok:!1,error:`Not a file: ${e}`};if(n.size>t)return{ok:!1,error:`File too large (max ${t} bytes for queue load).`};try{return{ok:!0,text:fy.readFileSync(e,"utf8")}}catch(o){return{ok:!1,error:String(o)}}}j();import by from"node:fs";import dr from"node:process";var PT=120;function dn(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function ky(e){let t=e.trim();return t==="/service"||t.startsWith("/service ")?t.slice(8).trim():null}async function Sy(e,t){let n=t.trim().split(/\s+/),o=(n[0]??"").toLowerCase();if(!t.trim()||o==="help")return X($p(e));if(o==="status"){let s=sn(),i=(()=>{try{return by.existsSync(de)?`gateway.pid: ${by.readFileSync(de,"utf8").trim()}`:"gateway.pid: (missing)"}catch(m){return`gateway.pid: (read error: ${String(m)})`}})(),a=dr.env.OMNISH_BACKGROUND_GATEWAY==="1"?"This process: background gateway (OMNISH_BACKGROUND_GATEWAY=1).":"This process: foreground gateway session.",l=typeof dr.env.OMNISH_HOME=="string"&&dr.env.OMNISH_HOME.trim()?`OMNISH_HOME env: ${dr.env.OMNISH_HOME.trim()}`:"OMNISH_HOME env: (not set \u2014 using default data dir)",c=s.error?s.error:`Node: ${s.nodePath}
489
+ `)}var hu=null;function ba(e,t){if(hu)return{stop:()=>{}};let n=GR.createServer((o,r)=>{if(o.method!=="POST"){r.writeHead(405,{"Content-Type":"application/json"}),r.end(JSON.stringify({ok:!1,error:"Method not allowed"}));return}let s=o.headers.authorization,i=s?.startsWith("Bearer ")?s.slice(7):void 0,a=new URL(o.url??"/",`http://${o.headers.host??"localhost"}`).searchParams.get("token")??void 0;if(!zR(i??a,e.token)){r.writeHead(401,{"Content-Type":"application/json"}),r.end(JSON.stringify({ok:!1,error:"Unauthorized"}));return}let c="",u=0;o.on("data",d=>{if(u+=d.length,u>qR){r.writeHead(413,{"Content-Type":"application/json"}),r.end(JSON.stringify({ok:!1,error:"Payload too large"})),o.destroy();return}c+=d.toString("utf8")}),o.on("end",()=>{let d;try{d=JSON.parse(c)}catch{r.writeHead(400,{"Content-Type":"application/json"}),r.end(JSON.stringify({ok:!1,error:"Invalid JSON"}));return}let f=new URL(o.url??"/",`http://${o.headers.host??"localhost"}`).searchParams.get("source")??o.headers["x-webhook-source"]??void 0;f&&(d.source=f);let h=typeof d.peerKey=="string"&&d.peerKey||t.getDefaultPeerKey();if(!h){r.writeHead(400,{"Content-Type":"application/json"}),r.end(JSON.stringify({ok:!1,error:"No target peer. Set peerKey in body or configure an allowlisted identity."}));return}let g=KR(d);t.sendToPeer(h,g).then(()=>{r.writeHead(200,{"Content-Type":"application/json"}),r.end(JSON.stringify({ok:!0}))},y=>{E.warn({err:String(y)},"webhook: sendToPeer failed"),r.writeHead(502,{"Content-Type":"application/json"}),r.end(JSON.stringify({ok:!1,error:"Failed to deliver message"}))})})});return n.listen(e.port,e.host,()=>{let o=n.address(),r=typeof o=="object"&&o?o.port:e.port;E.info({port:r,host:e.host},"webhook receiver listening")}),n.on("error",o=>{E.error({err:String(o)},"webhook receiver error")}),hu=n,{stop:()=>{try{n.close()}catch{}hu=null}}}ue();import WE from"node:crypto";import td from"node:fs";ye();import YR from"node:fs";function Bg(e,t,n){let o=e.fileReceiveMaxBytes,r;try{r=Buffer.from(n.dataBase64,"base64")}catch{return{mediaError:"Invalid inbound media payload."}}if(o>0&&r.length>o)return{mediaError:`Media too large (max ${o} bytes).`};let s;try{s=wn(e,t)}catch(a){return{mediaError:String(a)}}let i=er(s,t,n.name);try{return YR.writeFileSync(i,r,{mode:384}),{mediaSavedPath:i}}catch{return{mediaError:"Could not write media to inbox."}}}j();rs();ye();import Ru from"ws";import nT from"ws";var tT=["/platform/device","/control/device"];function xa(e){let t=e.replace(/\/$/,"");return tT.map(n=>{let o=new URL(n,t);return o.protocol=o.protocol==="https:"?"wss:":"ws:",o.toString()})}function oT(e){let t=String(e instanceof Error?e.message:e);return/Unexpected server response:\s*400/.test(t)||/Unexpected server response:\s*404/.test(t)}async function Gg(e,t){let n=xa(e),o=null;for(let s of n)try{return{ws:await new Promise((a,l)=>{let c=new nT(s,{headers:{Authorization:`Bearer ${t}`},maxPayload:qm()});c.once("open",()=>a(c)),c.once("error",l)}),pathname:new URL(s).pathname}}catch(i){if(o=i instanceof Error?i:new Error(String(i)),oT(i))continue;throw o}let r="Could not open a device WebSocket. Redeploy the relay (for /control/device fallback) and ensure /platform/* or /control/* route to port 8788.";throw o&&/Unexpected server response:\s*400/.test(o.message)?new Error(`${o.message} \u2014 ${r}`):new Error(o?`${o.message} \u2014 ${r}`:r)}var rT=1e3,sT=6e4,Ca=class{constructor(t){this.options=t}ws=null;stopped=!1;pingTimer=null;registerResolve=null;registerReject=null;registeredAccount=null;registeredDeviceId=null;reconnectAttempt=0;reconnectTimer=null;outboundQueue=[];getRegisteredAccount(){return this.registeredAccount}async connect(){return this.connectOnce()}async connectOnce(){let{env:t}=this.options,n=new Promise((a,l)=>{this.registerResolve=a,this.registerReject=l}),{ws:o,pathname:r}=await Gg(t.platformUrl,t.token);this.ws=o,r!=="/platform/device"&&E.info({pathname:r},"platform device websocket connected via fallback path"),o.on("message",a=>{this.handleMessage(a.toString())}),o.on("close",()=>{this.stopped||(E.warn("platform device websocket closed; scheduling reconnect"),this.scheduleReconnect())}),o.on("error",a=>{E.warn({err:String(a)},"platform device websocket error")});let s=setTimeout(()=>{this.registerReject?.(new Error("Platform device register timeout (15s)"))},15e3);this.sendRaw({type:"register",...t.deviceId?{deviceId:t.deviceId}:{},label:process.env.OMNISH_DEVICE_LABEL?.trim()||"default"});let i=await n;return clearTimeout(s),this.registeredDeviceId=i.deviceId,this.reconnectAttempt=0,this.pingTimer&&clearInterval(this.pingTimer),this.pingTimer=setInterval(()=>{this.sendRaw({type:"ping"})},3e4),this.flushOutboundQueue(),i}scheduleReconnect(){if(this.stopped||this.reconnectTimer)return;this.pingTimer&&(clearInterval(this.pingTimer),this.pingTimer=null),this.ws=null;let t=Math.min(rT*2**this.reconnectAttempt,sT);this.reconnectAttempt+=1,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,!this.stopped&&this.connectOnce().catch(n=>{E.warn({err:String(n)},"platform device reconnect failed"),this.scheduleReconnect()})},t),this.reconnectTimer.unref?.()}async handleMessage(t){let n=Km(t);if(n){if(n.type==="registered"&&"deviceId"in n){n.account&&(this.registeredAccount=n.account),this.registerResolve?.({deviceId:n.deviceId,...n.account?{account:n.account}:{}}),this.registerResolve=null,this.registerReject=null;return}if(n.type==="message"){await this.options.onMessage(n);return}if(n.type==="reply_error"){await this.options.onReplyError?.(n.peerKey,n.error,n.messageId);return}n.type==="error"&&(E.warn({message:n.message},"platform error"),this.registerReject?.(new Error(n.message)),this.registerResolve=null,this.registerReject=null)}}sendReply(t,n,o,r){this.enqueue({type:"reply",peerKey:t,...n?{body:n}:{},...o?{messageId:o}:{},...r?.length?{files:r}:{}})}sendRoutedReply(t,n){this.enqueue({type:"reply",peerKey:t,...n})}enqueue(t){if(this.ws?.readyState===Ru.OPEN){this.sendRaw(t);return}if(this.outboundQueue.length>=zm){E.warn("platform outbound queue full; dropping reply");return}this.outboundQueue.push(t)}flushOutboundQueue(){for(;this.outboundQueue.length>0&&this.ws?.readyState===Ru.OPEN;){let t=this.outboundQueue.shift();t&&this.sendRaw(t)}}sendRaw(t){this.ws?.readyState===Ru.OPEN&&this.ws.send(JSON.stringify(t))}stop(){this.stopped=!0,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.pingTimer&&(clearInterval(this.pingTimer),this.pingTimer=null);try{this.ws?.close()}catch{}this.ws=null,this.outboundQueue.length=0}};import $u from"node:readline/promises";import{stdin as Mu,stdout as Aa}from"node:process";ue();var iT=/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;function zg(e){let t=e.trim().toLowerCase();return t?t.length>63?"Tunnel name must be at most 63 characters.":iT.test(t)?null:"Tunnel name may only use letters, digits, and hyphens.":"Tunnel name must not be empty."}function Kg(e){let t=Number.parseInt(e,10);return!Number.isInteger(t)||t<1||t>65535?null:t}function qg(e,t){let n="127.0.0.1",o,r,s=!1,i=[];for(let c=0;c<t.length;c++){let u=t[c];if(u==="--host"){let d=t[++c];if(!d)return{kind:"error",message:"--host requires an address."};n=d;continue}if(u.startsWith("--host=")){n=u.slice(7);continue}if(u==="--relay"){let d=t[++c];if(!d)return{kind:"error",message:"--relay requires a URL."};o=d;continue}if(u.startsWith("--relay=")){o=u.slice(8);continue}if(u==="--name"){let d=t[++c];if(!d)return{kind:"error",message:"--name requires a slug."};r=d;continue}if(u.startsWith("--name=")){r=u.slice(7);continue}if(u==="--background"||u==="-b"){s=!0;continue}i.push(u)}let a=i[0];if(!a)return{kind:"error",message:`Usage: omnish tunnel ${e} <port> [--host <addr>] [--name <slug>] [--relay <url>] [--background]`};let l=Kg(a);if(l===null)return{kind:"error",message:"Port must be an integer between 1 and 65535."};if(r){let c=zg(r);if(c)return{kind:"error",message:c}}return{kind:"expose",options:{kind:e,port:l,host:n,relayUrl:o??"",name:r,background:s}}}function Tu(e){let[t,...n]=e,o=(t??"").trim().toLowerCase();if(!o||o==="help"||o==="-h"||o==="--help")return{kind:"help"};if(o==="signup"){let r,s,i,a;for(let l=0;l<n.length;l++){let c=n[l];if(c==="--email"){r=n[++l];continue}if(c.startsWith("--email=")){r=c.slice(8);continue}if(c==="--phone"){s=n[++l];continue}if(c.startsWith("--phone=")){s=c.slice(8);continue}if(c==="--password"){i=n[++l];continue}if(c.startsWith("--password=")){i=c.slice(11);continue}if(c==="--relay"){a=n[++l];continue}if(c.startsWith("--relay=")){a=c.slice(8);continue}}return{kind:"signup",email:r,phone:s,password:i,relayUrl:a}}if(o==="login"){let r,s,i,a,l;for(let c=0;c<n.length;c++){let u=n[c];if(u==="--token"){r=n[++c];continue}if(u.startsWith("--token=")){r=u.slice(8);continue}if(u==="--email"){s=n[++c];continue}if(u.startsWith("--email=")){s=u.slice(8);continue}if(u==="--phone"){i=n[++c];continue}if(u.startsWith("--phone=")){i=u.slice(8);continue}if(u==="--password"){a=n[++c];continue}if(u.startsWith("--password=")){a=u.slice(11);continue}if(u==="--relay"){l=n[++c];continue}if(u.startsWith("--relay=")){l=u.slice(8);continue}r||(r=u)}return{kind:"login",token:r,email:s,phone:i,password:a,relayUrl:l}}if(o==="logout")return{kind:"logout"};if(o==="list")return{kind:"list"};if(o==="status"){let r;for(let s=0;s<n.length;s++){let i=n[s];if(i==="--relay"){r=n[s+1],s++;continue}i.startsWith("--relay=")&&(r=i.slice(8))}return{kind:"status",relayUrl:r}}if(o==="stop"){let r=n[0]?.trim();return r?{kind:"stop",target:r}:{kind:"error",message:"Usage: omnish tunnel stop <id|slug>"}}return o==="http"?qg("http",n):o==="tcp"?qg("tcp",n):{kind:"error",message:`Unknown tunnel subcommand "${t}". Try: omnish tunnel help`}}function aT(e){let t=[],n="",o=null;for(let r=0;r<e.length;r++){let s=e[r];if(o){s===o?o=null:n+=s;continue}if(s==='"'||s==="'"){o=s;continue}if(/\s/.test(s)){n.length&&(t.push(n),n="");continue}n+=s}return n.length&&t.push(n),t}function Yg(e){let t=e.trim();if(!t||t==="help")return{kind:"help"};let n=aT(t),o=n[0]?.toLowerCase();if(o==="login"||o==="logout"||o==="status"||o==="signup")return Tu(n);if(t.toLowerCase()==="list"||t.toLowerCase()==="ls")return{kind:"list"};let r=t.match(/^stop\s+(\S+)\s*$/i);if(r)return{kind:"stop",target:r[1]};let s=t.match(/^(http|tcp)\s+(\d+)(?:\s+--name\s+(\S+))?(?:\s+--host\s+(\S+))?\s*$/i);if(s){let i=s[1].toLowerCase(),a=Kg(s[2]);if(a===null)return{kind:"error",message:"Port must be an integer between 1 and 65535."};let l=s[3],c=s[4]??"127.0.0.1";if(l){let u=zg(l);if(u)return{kind:"error",message:u}}return{kind:"expose",options:{kind:i,port:a,host:c,relayUrl:"",name:l,background:!0}}}return{kind:"error",message:"Usage: /tunnel login \u2026 | /tunnel status | /tunnel http <port> | /tunnel tcp <port> | /tunnels | /tunnel stop <id>"}}import lT from"ws";import{URL as Vg}from"node:url";function lr(e){let t=new Vg(e);return t.protocol=t.protocol==="https:"?"wss:":"ws:",(!t.pathname||t.pathname==="/")&&(t.pathname="/control"),t.toString()}function Qg(e){return new Vg("/health",e).toString()}async function Ra(e,t,n=1e4){let o=t.trim();if(!o)return{ok:!1,healthOk:!1,controlOk:!1,error:"Tunnel token is missing."};let r=Qg(e),s=!1,i;try{let c=await fetch(r,{method:"GET",signal:AbortSignal.timeout(n)});if(!c.ok)return{ok:!1,healthOk:!1,controlOk:!1,error:`Health HTTP ${c.status} (${r})`};let u=await c.json();if(!u?.ok)return{ok:!1,healthOk:!1,controlOk:!1,error:"Health JSON missing ok:true"};s=!0,typeof u.version=="string"&&(i=u.version)}catch(c){return{ok:!1,healthOk:!1,controlOk:!1,error:`Health fetch failed: ${String(c)}`}}let a=lr(e),l=!1;try{await new Promise((c,u)=>{let d=new lT(a,{headers:{Authorization:`Bearer ${o}`}}),m=setTimeout(()=>{d.terminate(),u(new Error("WSS auth timeout"))},n);d.once("message",f=>{clearTimeout(m);try{let h=JSON.parse(f.toString());if(h.type!=="auth_ok"){u(new Error(`Expected auth_ok, got ${h.type??"?"}`));return}d.close(),c()}catch(h){u(h)}}),d.once("error",f=>{clearTimeout(m),u(f)})}),l=!0}catch(c){return{ok:!1,healthOk:!0,healthVersion:i,controlOk:!1,error:`Control WebSocket: ${String(c)}`}}return{ok:!0,healthOk:s,healthVersion:i,controlOk:!0}}Kn();Yn();Yn();Kn();import ey from"node:crypto";import cT from"node:http";import uT from"node:net";import Un from"ws";function Ta(e,t,n=Buffer.alloc(0)){let o=Buffer.allocUnsafe(9+n.length);return o.writeUInt8(e,0),o.writeUInt32BE(t,1),o.writeUInt32BE(n.length,5),n.copy(o,9),o}function Xg(e){if(e.length<9)return null;let t=e.readUInt8(0),n=e.readUInt32BE(1),o=e.readUInt32BE(5);return e.length<9+o?null:{frameType:t,streamId:n,payload:e.subarray(9,9+o)}}function Eu(e){try{let t=JSON.parse(e);return!t||typeof t!="object"||typeof t.type!="string"?null:t}catch{return null}}function cr(e){return JSON.stringify(e)}function dT(e){let t=ey.createHash("sha1").update(e,"utf8").digest("hex").slice(0,8);return Number.parseInt(t,16)>>>0}var Ea=class{constructor(t){this.opts=t;let n=ey.randomBytes(4).toString("hex");this.record={id:n,kind:t.expose.kind,slug:t.expose.name?.trim().toLowerCase()??"",localHost:t.expose.host,localPort:t.expose.port,publicUrl:"",status:"connecting",startedAt:new Date().toISOString()}}ws=null;record;tcpStreams=new Map;tcpStreamIds=new Map;stopped=!1;pingTimer=null;getRecord(){return{...this.record}}setStatus(t,n){this.record.status=t,this.record.error=n,this.opts.onStatus?.(this.getRecord())}async start(){if(this.stopped)throw new Error("Tunnel client already stopped.");let t=lr(this.opts.relayUrl),n=new Un(t,{headers:{Authorization:`Bearer ${this.opts.token}`}});this.ws=n,await new Promise((r,s)=>{let i=c=>{l(),s(c)},a=()=>{l(),r()},l=()=>{n.off("error",i),n.off("open",a)};n.once("error",i),n.once("open",a)}),n.on("message",(r,s)=>{if(s){this.handleBinary(Buffer.isBuffer(r)?r:Buffer.from(r));return}let i=typeof r=="string"?r:r.toString("utf8");this.handleControl(i)}),n.on("close",()=>{this.stopped||this.setStatus("error","Relay connection closed."),this.cleanupTcpStreams()}),n.on("error",r=>{this.stopped||this.setStatus("error",String(r))}),this.pingTimer=setInterval(()=>{n.readyState===Un.OPEN&&n.send(cr({type:"ping"}))},3e4),n.send(cr({type:"register",id:this.record.id,kind:this.opts.expose.kind,localHost:this.opts.expose.host,localPort:this.opts.expose.port,...this.opts.expose.name?{name:this.opts.expose.name}:{}}));let o=await this.waitForRegistered();return this.record.slug=o.slug,this.record.publicUrl=o.publicUrl,this.setStatus("active"),this.getRecord()}waitForRegistered(){let t=this.ws;return t?new Promise((n,o)=>{let r=s=>{if(typeof s!="string"&&!Buffer.isBuffer(s))return;let i=typeof s=="string"?s:s.toString("utf8"),a=Eu(i);if(a){if(a.type==="registered"&&a.id===this.record.id){t.off("message",r),n(a);return}a.type==="error"&&(t.off("message",r),o(new Error(a.message)))}};t.on("message",r)}):Promise.reject(new Error("WebSocket not connected."))}async handleControl(t){let n=Eu(t);if(n&&!(n.type==="pong"||n.type==="auth_ok")){if(n.type==="error"){this.setStatus("error",n.message);return}if(n.type==="http"){await this.handleHttpRequest(n);return}if(n.type==="tcp_open"){await this.handleTcpOpen(n.streamId);return}n.type==="tcp_close"&&this.closeTcpStream(n.streamId)}}handleBinary(t){let n=Xg(t);if(!n)return;let o=[...this.tcpStreamIds.entries()].find(([,s])=>s===n.streamId)?.[0];if(!o)return;let r=this.tcpStreams.get(o);if(r){if(n.frameType===2){r.write(n.payload);return}n.frameType===3&&(r.end(),this.tcpStreams.delete(o),this.tcpStreamIds.delete(o))}}async handleHttpRequest(t){let n=this.ws;if(!n||n.readyState!==Un.OPEN)return;let o=t.bodyBase64?Buffer.from(t.bodyBase64,"base64"):void 0,r={host:this.opts.expose.host,port:this.opts.expose.port,method:t.method,path:t.path,headers:{...t.headers}};await new Promise(s=>{let i=cT.request(r,a=>{let l=[];a.on("data",c=>l.push(Buffer.isBuffer(c)?c:Buffer.from(c))),a.on("end",()=>{let c=Buffer.concat(l);n.send(cr({type:"http_res",requestId:t.requestId,status:a.statusCode??502,headers:a.headers,...c.length>0?{bodyBase64:c.toString("base64")}:{}})),s()})});i.on("error",a=>{n.send(cr({type:"http_res",requestId:t.requestId,status:502,headers:{"content-type":"text/plain"},bodyBase64:Buffer.from(String(a)).toString("base64")})),s()}),o&&o.length>0&&i.write(o),i.end()})}async handleTcpOpen(t){let n=this.ws;if(!n||n.readyState!==Un.OPEN)return;let o=dT(t);this.tcpStreamIds.set(t,o);let r=uT.connect({host:this.opts.expose.host,port:this.opts.expose.port});this.tcpStreams.set(t,r),r.on("data",s=>{n.readyState===Un.OPEN&&n.send(Ta(2,o,Buffer.isBuffer(s)?s:Buffer.from(s)))}),r.on("close",()=>{n.readyState===Un.OPEN&&n.send(Ta(3,o)),this.tcpStreams.delete(t),this.tcpStreamIds.delete(t)}),r.on("error",()=>{n.readyState===Un.OPEN&&n.send(Ta(3,o)),this.tcpStreams.delete(t),this.tcpStreamIds.delete(t)})}closeTcpStream(t){let n=this.tcpStreams.get(t);n&&n.destroy(),this.tcpStreams.delete(t),this.tcpStreamIds.delete(t)}cleanupTcpStreams(){for(let t of this.tcpStreams.values())t.destroy();this.tcpStreams.clear(),this.tcpStreamIds.clear()}async stop(){if(this.stopped)return;this.stopped=!0,this.pingTimer&&(clearInterval(this.pingTimer),this.pingTimer=null);let t=this.ws;t&&t.readyState===Un.OPEN&&(t.send(cr({type:"unregister",id:this.record.id})),t.close()),this.cleanupTcpStreams(),this.setStatus("stopped")}};var Pa=class{clients=new Map;list(){return[...new Set(this.clients.values())].map(t=>t.getRecord())}getActiveCount(){return[...new Set(this.clients.values())].filter(t=>t.getRecord().status==="active").length}async expose(t,n){let o=Dt();if(!o)throw new Error("No tunnel token configured. Run `omnish tunnel login` or set OMNISH_TUNNEL_TOKEN.");let r=t.tunnelMaxActive>0?t.tunnelMaxActive:5;if(this.getActiveCount()>=r)throw new Error(`Active tunnel limit reached (${r}). Stop one with \`omnish tunnel stop <id>\`.`);let s=n.relayUrl||vt(t.tunnelRelayUrl||Le),i=new Ea({relayUrl:s,token:o,expose:n,onStatus:l=>{(l.status==="stopped"||l.status==="error")&&this.clients.delete(l.id)}}),a=await i.start();return this.clients.set(a.id,i),a.slug&&this.clients.set(a.slug,i),a}async stop(t){let n=t.trim().toLowerCase(),o=this.clients.get(n)??this.clients.get(t.trim());if(!o)return null;let r=o.getRecord();return await o.stop(),this.clients.delete(r.id),r.slug&&this.clients.delete(r.slug),o.getRecord()}async stopAll(){let t=[...new Set(this.clients.values())];await Promise.all(t.map(n=>n.stop())),this.clients.clear()}};async function ty(e,t){let n=await fetch(e,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t),signal:AbortSignal.timeout(15e3)}),o=await n.json();return!n.ok||!o.token?{ok:!1,error:o.error??`HTTP ${n.status}`}:{ok:!0,token:o.token}}function $a(e,t){let n=new URL("/auth/signup",e).toString();return ty(n,{...t.email?{email:t.email}:{},...t.phone?{phone:t.phone}:{},password:t.password})}function Ma(e,t){let n=new URL("/auth/login",e).toString();return ty(n,{...t.email?{email:t.email}:{},...t.phone?{phone:t.phone}:{},password:t.password})}var ur=new Pa;function mo(){return ur}function pT(e){let t=[K(e,"omnish tunnel"),w(e,"Expose local HTTP or TCP ports through the omnish relay."),"",K(e,"Usage:"),` ${S(e,"omnish tunnel signup [--email <email>] [--phone <phone>] [--password <pass>] [--relay <url>]")}`,` ${S(e,"omnish tunnel login [--token <token>] [--relay <url>]")}`,` ${S(e,"omnish tunnel login --email <email> --password <pass> [--relay <url>]")}`,` ${S(e,"omnish tunnel logout")}`,` ${S(e,"omnish tunnel status [--relay <url>]")}`,` ${S(e,"omnish tunnel http <port> [--host <addr>] [--name <slug>] [--relay <url>] [--background]")}`,` ${S(e,"omnish tunnel tcp <port> [--host <addr>] [--name <slug>] [--relay <url>] [--background]")}`,` ${S(e,"omnish tunnel list")}`,` ${S(e,"omnish tunnel stop <id|slug>")}`,"",w(e,"Secrets live in ~/.omnish/tunnel-auth.json or OMNISH_TUNNEL_TOKEN."),w(e,`Default relay: ${Le}`),""];console.log(t.join(`
490
+ `))}async function mT(){let e=$u.createInterface({input:Mu,output:Aa});try{return(await e.question("Tunnel token: ")).trim()}finally{e.close()}}async function ny(e){let t=Tu(e),n=Aa,o=process.stderr;if(t.kind==="help"){pT(n);return}if(t.kind==="error"){console.error(P(o,t.message)),process.exitCode=1;return}let r=v();if(t.kind==="signup"){let s=t.relayUrl||vt(r.tunnelRelayUrl||Le),i=$u.createInterface({input:Mu,output:Aa});try{let a=t.email?.trim()||(await i.question("Email (or leave empty for phone): ")).trim(),l=t.phone?.trim()||"";if(a||(l=l||(await i.question("Phone: ")).trim()),!a&&!l){console.error(P(o,"Email or phone is required.")),process.exitCode=1;return}let c=t.password||(await i.question("Password (min 8 chars): ")).trim();if(c.length<8){console.error(P(o,"Password must be at least 8 characters.")),process.exitCode=1;return}let u=await $a(s,{...a?{email:a}:{},...l?{phone:l}:{},password:c});if(!u.ok){console.error(P(o,u.error)),process.exitCode=1;return}Ft({token:u.token,...t.relayUrl?{relayUrl:t.relayUrl}:{}}),console.log(H(n,"Account created. Token saved."))}finally{i.close()}return}if(t.kind==="login"){if(t.email||t.phone){let a=t.relayUrl||vt(r.tunnelRelayUrl||Le),l=$u.createInterface({input:Mu,output:Aa});try{let c=t.password||(await l.question("Password: ")).trim(),u=await Ma(a,{...t.email?{email:t.email}:{},...t.phone?{phone:t.phone}:{},password:c});if(!u.ok){console.error(P(o,u.error)),process.exitCode=1;return}Ft({token:u.token,...t.relayUrl?{relayUrl:t.relayUrl}:{}}),console.log(H(n,"Logged in. Token saved."))}finally{l.close()}return}let i=t.token?.trim()||await mT();if(!i){console.error(P(o,"Tunnel token is required.")),process.exitCode=1;return}Ft({token:i,...t.relayUrl?{relayUrl:t.relayUrl}:{}}),console.log(H(n,"Tunnel token saved."));return}if(t.kind==="logout"){Ks(),console.log(H(n,"Tunnel token removed."));return}if(t.kind==="status"){let s=t.relayUrl||vt(r.tunnelRelayUrl||Le),i=Dt(),a=!!process.env.OMNISH_TUNNEL_TOKEN?.trim(),l=await Ra(s,i);console.log(`${w(n,"relay:")} ${S(n,s)}`),console.log(`${w(n,"token:")} ${S(n,i?`configured${a?" (OMNISH_TUNNEL_TOKEN)":""}`:P(o,"missing"))}`),console.log(`${w(n,"health:")} ${l.healthOk?S(n,`ok${l.healthVersion?` (${l.healthVersion})`:""}`):P(o,"fail")}`),console.log(`${w(n,"control:")} ${l.controlOk?S(n,"auth ok"):P(o,"fail")}${l.error&&!l.ok?` \u2014 ${l.error}`:""}`),console.log(`${w(n,"active:")} ${S(n,String(ur.getActiveCount()))}`);return}if(t.kind==="list"){let s=ur.list();if(s.length===0){console.log(H(n,"(no active tunnels)"));return}for(let i of s)console.log(`${S(n,i.id)} ${i.kind} ${i.status} ${i.publicUrl||"(pending)"}
491
+ ${w(n,`${i.localHost}:${i.localPort}`)}`);return}if(t.kind==="stop"){let s=await ur.stop(t.target);if(!s){console.error(P(o,`No active tunnel matched "${t.target}".`)),process.exitCode=1;return}console.log(H(n,`Stopped tunnel ${s.id}.`));return}if(t.kind==="expose"){let s=t.options.relayUrl||vt(r.tunnelRelayUrl||Le),i=await ur.expose(r,{...t.options,relayUrl:s});console.log(H(n,`${i.kind.toUpperCase()} tunnel active`)),console.log(`${w(n,"public:")} ${S(n,i.publicUrl)}`),console.log(`${w(n,"local:")} ${S(n,`${i.localHost}:${i.localPort}`)}`),console.log(`${w(n,"id:")} ${S(n,i.id)}`),t.options.background||(console.log(w(n,"Press Ctrl+C to stop.")),await new Promise(a=>{let l=async()=>{await ur.stop(i.id),a()};process.once("SIGINT",l),process.once("SIGTERM",l)}))}}function oy(e){let t=e.trim().match(/^(\d+)\.(\d+)\.(\d+)/);return t?[Number(t[1]),Number(t[2]),Number(t[3])]:null}function ry(e,t){let n=oy(e),o=oy(t);if(!n||!o)return e.trim().localeCompare(t.trim());for(let r=0;r<3;r++)if(n[r]!==o[r])return n[r]<o[r]?-1:1;return 0}var fT="https://registry.npmjs.org",sy=null,Au=0;function ss(){return sy}function hT(e){let t=e.trim();return t.length<1||t.length>214?!1:!/\s/.test(t)}function iy(e){let t=e.trim();if(!t||t.length>2048)return null;try{let n=new URL(t);return n.protocol!=="https:"?null:n.href}catch{return null}}async function gT(e){let t=e.trim(),n=`${fT}/${encodeURIComponent(t)}/latest`;try{let o=await fetch(n,{headers:{Accept:"application/json"},signal:AbortSignal.timeout(2e4)});if(!o.ok)return{error:`npm registry: HTTP ${o.status}`};let r=await o.json(),s=typeof r.version=="string"?r.version.trim():"";return s?{version:s}:{error:"npm registry: missing version in response"}}catch(o){return{error:`npm registry: ${String(o)}`}}}async function yT(e){try{let t=await fetch(e,{headers:{Accept:"application/json"},signal:AbortSignal.timeout(15e3)});if(!t.ok)return{error:`updateInfoUrl: HTTP ${t.status}`};let n=await t.arrayBuffer(),o=n.byteLength>65536?n.slice(0,65536):n,r=new TextDecoder("utf-8",{fatal:!1}).decode(o),s;try{s=JSON.parse(r)}catch{return{error:"updateInfoUrl: body is not JSON"}}if(!s||typeof s!="object"||Array.isArray(s))return{error:"updateInfoUrl: JSON must be an object"};let i=s,a=null;typeof i.message=="string"&&i.message.trim()&&(a=i.message.trim().slice(0,1500));let l=null,c=i.link??i.url;return typeof c=="string"&&c.trim()&&(l=iy(c)),{message:a,link:l}}catch(t){return{error:`updateInfoUrl: ${String(t)}`}}}function wT(e){return!Number.isFinite(e)||e<36e5?36e5:e>6048e5?6048e5:Math.floor(e)}async function is(e,t){let n=hT(t.updateCheckPackageName)?t.updateCheckPackageName.trim():"omnish",o=await gT(n),r="version"in o?o.version:null,s="error"in o?o.error:null,i=!1;r&&!s&&(i=ry(e,r)<0);let a=null,l=null,c=null,u=iy(t.updateInfoUrl);if(u){let m=await yT(u);"error"in m?c=m.error:(a=m.message,l=m.link)}let d={runningVersion:e,checkedAtIso:new Date().toISOString(),registryPackage:n,registryLatest:r,registryError:s,updateAvailable:i,infoMessage:a,infoLink:l,infoError:c};return sy=d,d}function Ia(e){if(!e)return;let t=[];if(e.registryError?t.push(`npm check: ${e.registryError}`):e.registryLatest&&t.push(e.updateAvailable?`npm latest *${e.registryLatest}* (running ${e.runningVersion})`:`npm latest ${e.registryLatest} (up to date)`),e.infoError?t.push(`info URL: ${e.infoError}`):e.infoMessage&&t.push(`notice: ${e.infoMessage}`),t.length!==0)return t.join(" \xB7 ")}function Oa(e){let t=!1,n=async()=>{if(t)return;let s=e.getConfig();if(!s.updateCheckEnabled)return;let i=wT(s.updateCheckIntervalMs),a=Date.now();if(!(Au!==0&&a-Au<i)){Au=a;try{let l=await is(e.getRunningVersion(),s);l.updateAvailable?e.log.info({updateAvailable:!0,running:l.runningVersion,npmLatest:l.registryLatest,pkg:l.registryPackage},"omnish update check: newer npm version available"):e.log.info({running:l.runningVersion,npmLatest:l.registryLatest,pkg:l.registryPackage},"omnish update check ok"),l.infoMessage&&e.log.info({len:l.infoMessage.length},"omnish update info message present")}catch(l){e.log.warn({err:String(l)},"omnish update check failed")}}},o=setInterval(()=>void n(),6e4),r=setTimeout(()=>void n(),3e4);return()=>{t=!0,clearInterval(o),clearTimeout(r)}}ue();import ay from"node:path";import{glob as bT,stat as kT}from"node:fs/promises";function La(e){let t=e.trim();if(!t||t.startsWith("-- ")||t==="--")return null;let n=t.indexOf(" -- "),o,r;return n!==-1?(o=t.slice(0,n).trim(),r=t.slice(n+4).trim()||void 0):o=t,(o.startsWith('"')&&o.endsWith('"')||o.startsWith("'")&&o.endsWith("'"))&&(o=o.slice(1,-1)),o.trim()?{selectorPart:o,caption:r}:null}function ST(e){return/[*?[]/.test(e)}function vT(e){return e.split(",").map(t=>t.trim()).filter(t=>t.length>0)}async function as(e,t){let n=vT(t),o=new Set,r=[];for(let s of n){if(ST(s)){for await(let a of bT(s,{cwd:e,withFileTypes:!1})){let l=ay.resolve(e,a);o.has(l)||(o.add(l),r.push(l))}continue}let i=ay.resolve(e,s);o.has(i)||(o.add(i),r.push(i))}return r}async function ls(e){for(let t of e)try{if(!(await kT(t)).isFile())return{ok:!1,error:`Not a file: ${t}`}}catch{return{ok:!1,error:`File not found: ${t}`}}return{ok:!0}}j();import uy from"node:fs";import xT from"node:path";var fo="__omnish_shortcuts_global__",ly=500,CT=/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,31}$/,RT=new Set(["help","apps","bg","jobs","log","tail","kill","send","file","files","receive","reload","restart","updates","gateway","gw","mode","allow","deny","allowlist","whatsapp","wa","telegram","tg","cowork","cw","shortcut","shortcuts","alias","aliases"]),cs=new Map,cy=!1;function TT(){try{let e=uy.readFileSync(Ss,"utf8"),t=JSON.parse(e);if(t&&typeof t=="object")for(let[n,o]of Object.entries(t)){if(!o||typeof o!="object")continue;let r={};for(let[s,i]of Object.entries(o)){let a=String(s).toLowerCase();typeof i=="string"&&i.length>0&&(r[a]=i.trim())}cs.set(n,r)}}catch{}}function Na(){D(xT.dirname(Ss));let e={};for(let[t,n]of cs)Object.entries(n).length>0&&(e[t]={...n});uy.writeFileSync(Ss,JSON.stringify(e,null,2)+`
492
+ `,{mode:384})}function _a(){cy||(TT(),cy=!0)}function ET(e){return RT.has(e.trim().toLowerCase())}function $t(e){let t=e.trim();if(!t)return{ok:!1,error:"Name is empty."};let n=t.toLowerCase();return CT.test(n)?ET(n)?{ok:!1,error:`Reserved name: ${n}`}:{ok:!0,normalized:n}:{ok:!1,error:`Invalid name (use letters, digits, _ or -; max 32 chars): ${t}`}}function Iu(e){let t=e.replace(/\r\n/g,`
493
+ `).replace(/\n/g," ").trim();return t?t.length>ly?{ok:!1,error:`Body too long (max ${ly} characters).`}:{ok:!0,body:t}:{ok:!1,error:"Body is empty."}}function zt(e,t){_a();let n=cs.get(e);return!n&&t&&(n={},cs.set(e,n)),n??{}}function dy(e){let t=e.trim();if(/^--global$/i.test(t))return{mode:"global",remainder:""};if(/^-g$/i.test(t))return{mode:"global",remainder:""};if(/^--chat$/i.test(t))return{mode:"chat",remainder:""};if(/^-p$/i.test(t))return{mode:"chat",remainder:""};let n=/^--global\s+/i.exec(t);if(n)return{mode:"global",remainder:t.slice(n[0].length).trimStart()};let o=/^-g\s+/i.exec(t);if(o)return{mode:"global",remainder:t.slice(o[0].length).trimStart()};let r=/^--chat\s+/i.exec(t);if(r)return{mode:"chat",remainder:t.slice(r[0].length).trimStart()};let s=/^-p\s+/i.exec(t);return s?{mode:"chat",remainder:t.slice(s[0].length).trimStart()}:{mode:"resolved",remainder:t}}function Ou(e){let t=e.trim();if(/^--global$/i.test(t))return{scope:"global",remainder:"",explicit:!0};if(/^-g$/i.test(t))return{scope:"global",remainder:"",explicit:!0};if(/^--chat$/i.test(t))return{scope:"chat",remainder:"",explicit:!0};if(/^-p$/i.test(t))return{scope:"chat",remainder:"",explicit:!0};let n=/^--global\s+([\s\S]*)$/i.exec(t);if(n?.[1]!==void 0)return{scope:"global",remainder:n[1].trimStart(),explicit:!0};let o=/^-g\s+([\s\S]*)$/i.exec(t);if(o?.[1]!==void 0)return{scope:"global",remainder:o[1].trimStart(),explicit:!0};let r=/^--chat\s+([\s\S]*)$/i.exec(t);if(r?.[1]!==void 0)return{scope:"chat",remainder:r[1].trimStart(),explicit:!0};let s=/^-p\s+([\s\S]*)$/i.exec(t);return s?.[1]!==void 0?{scope:"chat",remainder:s[1].trimStart(),explicit:!0}:{scope:"chat",remainder:t,explicit:!1}}function Lu(e){let t=Ou(e);return{scope:t.scope,remainder:t.remainder}}function py(e){let t=e.trim();return!t||/^-+$/i.test(t)?{filter:"merged"}:/^(?:--global|-g)$/i.test(t)?{filter:"global"}:/^(?:--chat|-p)$/i.test(t)?{filter:"chat"}:{filter:"merged",bad:t}}function PT(e){let t=e.trim().toLowerCase();if(t==="--global"||t==="-g")return"global";if(t==="--chat"||t==="-p")return"chat"}function my(e){let t=e.trim().match(/^(\S+)\s+(--global|-g|--chat|-p)\s*$/i);if(!t?.[1]||!t[2])return;let n=PT(t[2]);if(n)return{name:t[1],target:n}}function $T(e,t){_a();let n=e,o=zt(n,!1),r=zt(fo,!1);if(t==="chat")return Object.entries(o).map(([a,l])=>({name:a,body:l,scope:"chat"})).sort((a,l)=>a.name.localeCompare(l.name));if(t==="global")return Object.entries(r).map(([a,l])=>({name:a,body:l,scope:"global"})).sort((a,l)=>a.name.localeCompare(l.name));let s=new Set([...Object.keys(o),...Object.keys(r)]),i=[];for(let a of[...s].sort()){if(a in o){let c=o[a];c!==void 0&&i.push({name:a,body:c,scope:"chat"});continue}let l=r[a];l!==void 0&&i.push({name:a,body:l,scope:"global"})}return i}function fy(e,t="merged"){return $T(e,t)}function MT(e,t){let n=t.trim().toLowerCase(),o=zt(e,!1)[n];return o!==void 0?o:zt(fo,!1)[n]}function Fa(e,t){let n=$t(t);if(!n.ok)return;let o=n.normalized,r=zt(e,!1)[o];if(r!==void 0)return{name:o,body:r,scope:"chat"};let s=zt(fo,!1)[o];if(s!==void 0)return{name:o,body:s,scope:"global"}}function un(e,t,n){let o=n.trim().toLowerCase(),r=e==="global"?fo:t;return zt(r,!1)[o]}function us(e,t,n,o="chat"){let r=$t(t);if(!r.ok)throw new Error(r.error);let s=Iu(n);if(!s.ok)throw new Error(s.error);let i=o==="global"?fo:e,a=zt(i,!0);a[r.normalized]=s.body,Na()}function hy(e,t,n="chat"){let o=t.trim().toLowerCase(),r=n==="global"?fo:e;_a();let s=cs.get(r);return!s||!(o in s)?!1:(delete s[o],Na(),!0)}function Nu(e,t,n){let o=$t(t);if(!o.ok)return{ok:!1,error:o.error};let r=o.normalized,s=e;_a();let i=zt(s,!0),a=zt(fo,!0),l=i[r],c=a[r];if(n==="global"){if(l!==void 0){let u=Iu(l);return u.ok?(a[r]=u.body,delete i[r],Na(),{ok:!0,kind:"moved",target:"global",name:r}):{ok:!1,error:u.error}}return c!==void 0?{ok:!0,kind:"noop",message:`Shortcut "${r}" is already shared on this gateway.`}:{ok:!1,error:`No shortcut "${r}" in this chat to share. Add with /shortcut add ${r} \u2026 or make the shared copy private with /shortcut set -p ${r}.`}}if(c!==void 0){let u=Iu(c);return u.ok?(i[r]=u.body,delete a[r],Na(),{ok:!0,kind:"moved",target:"chat",name:r}):{ok:!1,error:u.error}}return l!==void 0?{ok:!0,kind:"noop",message:`Shortcut "${r}" is already private to this chat.`}:{ok:!1,error:`No shared shortcut "${r}" to make private. Add with /shortcut add --global ${r} \u2026 or share from this chat with /shortcut set -g ${r}.`}}var gy="OMNISH_INPUT",AT=new RegExp(`\\$${gy}\\b`,"g");function IT(e){return e.includes(`$${gy}`)}function OT(e,t){return e.replace(AT,t)}function LT(e){let t=e.trim();if(!t)return null;let n=/^(\S+)(?:\s+([\s\S]+))?$/.exec(t);if(!n?.[1])return null;let o=$t(n[1]);if(!o.ok)return null;let r=n[2]?.trim();return r!==void 0&&r.length>0?{name:o.normalized,input:r}:{name:o.normalized}}function yy(e,t,n=0){let o=LT(t);if(!o)return null;let r=MT(e,o.name);if(r===void 0)return null;if(IT(r)){if(o.input===void 0)return{ok:!1,error:`Shortcut "${o.name}" expects input (text after the name on the same line). Example: !${o.name} <text\u2026> or /${o.name} <text\u2026>`};let s=jo(o.input,n);return s.ok?{ok:!0,text:OT(r,s.task)}:{ok:!1,error:s.error}}return{ok:!0,text:r}}import wy from"node:fs";var by=64,NT=1024*1024;function ky(e){return e.fileReceiveMaxBytes>0?e.fileReceiveMaxBytes:NT}function Sy(e){let t;try{t=JSON.parse(e)}catch(r){return{ok:!1,error:`Invalid JSON: ${String(r)}`}}let n;if(Array.isArray(t))n=t;else if(t&&typeof t=="object"&&!Array.isArray(t)){let r=t,s=Object.keys(r);if(s.length!==1||s[0]!=="tasks")return{ok:!1,error:'JSON must be an array or a single-key object: { "tasks": [ \u2026 ] }.'};let i=r.tasks;if(!Array.isArray(i))return{ok:!1,error:'"tasks" must be an array.'};n=i}else return{ok:!1,error:'JSON must be an array or { "tasks": [ \u2026 ] }.'};if(n.length===0)return{ok:!1,error:"Queue JSON must contain at least one job."};if(n.length>by)return{ok:!1,error:`Too many jobs (max ${by}).`};let o=[];for(let r=0;r<n.length;r++){let s=n[r];if(!s||typeof s!="object"||Array.isArray(s))return{ok:!1,error:`Job ${r+1}: must be an object with "recipe" and "task".`};let i=s;if(Object.keys(i).length!==2||typeof i.recipe!="string"||typeof i.task!="string")return{ok:!1,error:`Job ${r+1}: must contain only "recipe" and "task" string fields.`};o.push({recipe:i.recipe,task:i.task})}return{ok:!0,jobs:o}}function vy(e,t,n){let o=[];for(let r=0;r<n.length;r++){let{recipe:s,task:i}=n[r],a=ze(e,t,s);if(!a)return{ok:!1,error:`Job ${r+1}: unknown recipe "${s}".`};let l=a.taskEnv??"OMNISH_TASK";if(!Ho(a.command,l))return{ok:!1,error:`Job ${r+1}: recipe "${s}" command must reference "$${l}".`};let c=jo(i,t.recipesMaxTaskChars);if(!c.ok)return{ok:!1,error:`Job ${r+1}: ${c.error}`};let u=a.promptTemplate?vi(a.promptTemplate,l,c.task):c.task,d={[l]:u};o.push({command:a.command,extraEnv:d,recipeLabel:s})}return{ok:!0,items:o}}function _u(e,t){let n;try{n=wy.statSync(e)}catch{return{ok:!1,error:`Cannot read file: ${e}`}}if(!n.isFile())return{ok:!1,error:`Not a file: ${e}`};if(n.size>t)return{ok:!1,error:`File too large (max ${t} bytes for queue load).`};try{return{ok:!0,text:wy.readFileSync(e,"utf8")}}catch(o){return{ok:!1,error:String(o)}}}j();import xy from"node:fs";import dr from"node:process";var _T=120;function dn(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function Cy(e){let t=e.trim();return t==="/service"||t.startsWith("/service ")?t.slice(8).trim():null}async function Ry(e,t){let n=t.trim().split(/\s+/),o=(n[0]??"").toLowerCase();if(!t.trim()||o==="help")return X(Ip(e));if(o==="status"){let s=sn(),i=(()=>{try{return xy.existsSync(de)?`gateway.pid: ${xy.readFileSync(de,"utf8").trim()}`:"gateway.pid: (missing)"}catch(m){return`gateway.pid: (read error: ${String(m)})`}})(),a=dr.env.OMNISH_BACKGROUND_GATEWAY==="1"?"This process: background gateway (OMNISH_BACKGROUND_GATEWAY=1).":"This process: foreground gateway session.",l=typeof dr.env.OMNISH_HOME=="string"&&dr.env.OMNISH_HOME.trim()?`OMNISH_HOME env: ${dr.env.OMNISH_HOME.trim()}`:"OMNISH_HOME env: (not set \u2014 using default data dir)",c=s.error?s.error:`Node: ${s.nodePath}
493
494
  Script: ${s.scriptPath}`,u=["*Service status*","",`platform: ${dr.platform}`,a,l,`data dir: ${W}`,i,`default log: ${He}`,"",c,"",e.serviceInstallFromChat?"Install from chat: enabled (/service install).":"Install from chat: off \u2014 `/config set serviceInstallFromChat true` to allow /service install."].join(`
494
495
  `),d=["<b>Service status</b>","",`<code>${dn(dr.platform)}</code>`,`<br/><code>${dn(a)}</code>`,`<br/><code>${dn(l)}</code>`,`<br/>data dir: <code>${dn(W)}</code>`,`<br/><code>${dn(i)}</code>`,`<br/>default log: <code>${dn(He)}</code>`,"",`<pre>${dn(c)}</pre>`,"",e.serviceInstallFromChat?"Install from chat: enabled.":"Install from chat: off \u2014 <code>/config set serviceInstallFromChat true</code>."].join(`
495
- `);return ye(u,d)}if(o==="instructions"){let s=sn();if(s.error)return p(s.error);let i=Zs(s);return p(`*Install hints*
496
+ `);return we(u,d)}if(o==="instructions"){let s=sn();if(s.error)return p(s.error);let i=Zs(s);return p(`*Install hints*
496
497
 
497
- ${i}`)}if(o==="logs"){let s=n.length>=2?Number.parseInt(n[1],10):80,i=Number.isFinite(s)&&s>0?Math.min(s,PT):80,a=wi(He,i),l=[`*Gateway log* (last ${i} lines)
498
+ ${i}`)}if(o==="logs"){let s=n.length>=2?Number.parseInt(n[1],10):80,i=Number.isFinite(s)&&s>0?Math.min(s,_T):80,a=wi(He,i),l=[`*Gateway log* (last ${i} lines)
498
499
  ${He}
499
500
  `,"```",a,"```"].join(`
500
- `),c=`<b>Gateway log</b> (last ${i} lines)<br/><code>${dn(He)}</code><pre>${dn(a)}</pre>`;return ye(l,c)}if(o==="install"){if(!e.serviceInstallFromChat)return p("Install from chat is disabled. Same trust as shell \u2014 enable with:\n`/config set serviceInstallFromChat true`\nThen `/service install` again.");let s=hi();return p(s.ok?`*Installed*
501
+ `),c=`<b>Gateway log</b> (last ${i} lines)<br/><code>${dn(He)}</code><pre>${dn(a)}</pre>`;return we(l,c)}if(o==="install"){if(!e.serviceInstallFromChat)return p("Install from chat is disabled. Same trust as shell \u2014 enable with:\n`/config set serviceInstallFromChat true`\nThen `/service install` again.");let s=hi();return p(s.ok?`*Installed*
501
502
  ${s.detail}`:`*Install failed*
502
503
  ${s.detail}`)}if(o==="uninstall"){if(!e.serviceInstallFromChat)return p("Uninstall from chat is disabled. Enable with `/config set serviceInstallFromChat true` or remove files on the host manually.");let s=gi();return p(`*Uninstall*
503
- ${s.detail}`)}return p("Unknown /service command. Try /service help")}function mr(e,t){let n=e.trim(),o=`/${t}`;return n===o||n.startsWith(`${o} `)?n.slice(o.length).trim():null}var vy=e=>mr(e,"dl"),xy=e=>mr(e,"dlf"),Cy=e=>mr(e,"dlv"),Ry=e=>mr(e,"tr"),Ty=e=>mr(e,"edit"),Ey=e=>mr(e,"pull");function ds(e){let t=e.trim(),n=!1,o=!0;for(;o;){if(o=!1,/^--bg\b/i.test(t)||/^--background\b/i.test(t)){t=t.replace(/^--(?:bg|background)\s*/i,"").trim(),o=!0;continue}(/^--notify\b/i.test(t)||/^-N\b/i.test(t))&&(n=!0,t=t.replace(/^--notify\s*/i,"").replace(/^-N\s*/i,"").trim(),o=!0)}return{body:t,notify:n}}function $T(e,t,n,o){let r=sn();if(r.error)throw new Error(r.error);let s=i=>JSON.stringify(i);return`OMNISH_PEER_KEY=${s(o)} ${r.nodePath} ${r.scriptPath} media-exec ${e} ${s(t)} ${s(n)}`}function MT(e,t,n){return ti({peerKey:t,enabled:e.progressUpdates,sendToPeer:n?.sendToPeer})}function fr(e,t,n){let o=oe(n.peerKey).cwd,r=$T(n.sub,n.body,o,n.peerKey),{id:s,meta:i}=t.spawnJob(e.shell,r,{cwd:o,name:n.jobName,notifyPeerKey:n.notify?n.peerKey:null});if(n.friendlyReply)return{kind:"text",body:p(`${n.friendlyReply}
504
+ ${s.detail}`)}return p("Unknown /service command. Try /service help")}function mr(e,t){let n=e.trim(),o=`/${t}`;return n===o||n.startsWith(`${o} `)?n.slice(o.length).trim():null}var Ty=e=>mr(e,"dl"),Ey=e=>mr(e,"dlf"),Py=e=>mr(e,"dlv"),$y=e=>mr(e,"tr"),My=e=>mr(e,"edit"),Ay=e=>mr(e,"pull");function ds(e){let t=e.trim(),n=!1,o=!0;for(;o;){if(o=!1,/^--bg\b/i.test(t)||/^--background\b/i.test(t)){t=t.replace(/^--(?:bg|background)\s*/i,"").trim(),o=!0;continue}(/^--notify\b/i.test(t)||/^-N\b/i.test(t))&&(n=!0,t=t.replace(/^--notify\s*/i,"").replace(/^-N\s*/i,"").trim(),o=!0)}return{body:t,notify:n}}function FT(e,t,n,o){let r=sn();if(r.error)throw new Error(r.error);let s=i=>JSON.stringify(i);return`OMNISH_PEER_KEY=${s(o)} ${r.nodePath} ${r.scriptPath} media-exec ${e} ${s(t)} ${s(n)}`}function DT(e,t,n){return ti({peerKey:t,enabled:e.progressUpdates,sendToPeer:n?.sendToPeer})}function fr(e,t,n){let o=oe(n.peerKey).cwd,r=FT(n.sub,n.body,o,n.peerKey),{id:s,meta:i}=t.spawnJob(e.shell,r,{cwd:o,name:n.jobName,notifyPeerKey:n.notify?n.peerKey:null});if(n.friendlyReply)return{kind:"text",body:p(`${n.friendlyReply}
504
505
  /log ${i.name??s}`)};let a=n.notify?`
505
506
  Notify on completion: on`:"";return{kind:"text",body:p(`${n.label} job ${s} started.
506
507
  /log ${i.name??s}
507
- /tail ${i.name??s}${a}`)}}async function AT(e,t,n,o){if(!e.mediaInstallFromChat)return{kind:"text",body:p("Install from chat is off. Host: omnish pull install \xB7 or /config set mediaInstallFromChat true")};let r=/\b--whisper\b/i.test(t),s=/\b--transcribe-node\b/i.test(t),i=MT(e,n,o);try{let a=await ai({whisper:r,transcribeNode:s,progress:{stepStart:(l,c,u)=>i.stepStart(l,c,{label:u})}});return{kind:"text",body:p(`*Install*
508
+ /tail ${i.name??s}${a}`)}}async function WT(e,t,n,o){if(!e.mediaInstallFromChat)return{kind:"text",body:p("Install from chat is off. Host: omnish pull install \xB7 or /config set mediaInstallFromChat true")};let r=/\b--whisper\b/i.test(t),s=/\b--transcribe-node\b/i.test(t),i=DT(e,n,o);try{let a=await ai({whisper:r,transcribeNode:s,progress:{stepStart:(l,c,u)=>i.stepStart(l,c,{label:u})}});return{kind:"text",body:p(`*Install*
508
509
 
509
510
  ${a.messages.join(`
510
511
  `)}`)}}catch(a){return{kind:"text",body:p(`Install failed: ${String(a)}`)}}}async function pr(e,t,n,o,r){let s=t.trim(),i=(s.split(/\s+/)[0]??"").toLowerCase();if(!s||i==="help")return{kind:"text",body:X($r(e))};if(i==="doctor"){let{text:c}=ii(e);return{kind:"text",body:p(`*Media tools*
511
512
 
512
- ${c}`)}}if(i==="setup"||i==="instructions")return{kind:"text",body:p(ei())};if(i==="install")return AT(e,s,n,r);let{body:a,notify:l}=ds(s);return a?fr(e,o,{sub:"dl",body:a,peerKey:n,jobName:"dl",label:"Download",notify:l}):{kind:"text",body:X($r(e))}}async function Py(e,t,n,o,r){let{body:s,notify:i}=ds(t.trim());return s?fr(e,o,{sub:"dlf",body:s,peerKey:n,jobName:"dlf",label:"File download",notify:i}):{kind:"text",body:X($r(e))}}async function $y(e,t,n,o,r){let{body:s,notify:i}=ds(t.trim());return s?fr(e,o,{sub:"dlv",body:s,peerKey:n,jobName:"dlv",label:"Video download",notify:i}):{kind:"text",body:X($r(e))}}async function Lu(e,t,n,o,r){let s=t.trim(),i=(s.split(/\s+/)[0]??"").toLowerCase();if(!s||i==="help")return{kind:"text",body:p(`*Transcribe (/tr)*
513
+ ${c}`)}}if(i==="setup"||i==="instructions")return{kind:"text",body:p(ei())};if(i==="install")return WT(e,s,n,r);let{body:a,notify:l}=ds(s);return a?fr(e,o,{sub:"dl",body:a,peerKey:n,jobName:"dl",label:"Download",notify:l}):{kind:"text",body:X($r(e))}}async function Iy(e,t,n,o,r){let{body:s,notify:i}=ds(t.trim());return s?fr(e,o,{sub:"dlf",body:s,peerKey:n,jobName:"dlf",label:"File download",notify:i}):{kind:"text",body:X($r(e))}}async function Oy(e,t,n,o,r){let{body:s,notify:i}=ds(t.trim());return s?fr(e,o,{sub:"dlv",body:s,peerKey:n,jobName:"dlv",label:"Video download",notify:i}):{kind:"text",body:X($r(e))}}async function Fu(e,t,n,o,r){let s=t.trim(),i=(s.split(/\s+/)[0]??"").toLowerCase();if(!s||i==="help")return{kind:"text",body:p(`*Transcribe (/tr)*
513
514
 
514
515
  /tr <url|path>
515
516
 
516
517
  URL: downloads video, transcribes, sends text + .srt + video.
517
518
  Path: transcribes local file, sends text + .srt.
518
519
 
519
- Runs in background. Use /tr --notify for a completion ping when the shell job ends.`)};if(i==="doctor"||i==="setup"||i==="install")return pr(e,s,n,o,r);let{body:a,notify:l}=ds(s);if(!a)return{kind:"text",body:p("Usage: /tr <url|filepath>")};let c=lt(e),u=fi(e,c);return u?{kind:"text",body:p(u)}:fr(e,o,{sub:"tr",body:a,peerKey:n,jobName:"tr",label:"Transcribe",notify:l})}async function My(e,t,n,o,r){let s=t.trim(),i=(s.split(/\s+/)[0]??"").toLowerCase();if(!s||i==="help")return{kind:"text",body:p(["*Edit (/edit)*","","/edit <url|path> [--from 1:30] [--to 2:00] [-t 30]","/edit clip.mp4 --format mp3 --audio-only","","Flags: --from/--start, --to/--end, -t/--duration, --format/-f, --audio-only","","Runs in background. Use /edit --notify for a completion ping when the job ends."].join(`
520
- `))};if(i==="doctor"||i==="setup"||i==="install")return pr(e,s,n,o,r);let{body:a,notify:l}=ds(s);return a?fr(e,o,{sub:"edit",body:a,peerKey:n,jobName:"edit",label:"Edit",notify:l}):{kind:"text",body:p("Usage: /edit <url|path> [flags]")}}async function Ay(e,t,n,o,r){let s=t.trim(),i=(s.split(/\s+/)[0]??"").toLowerCase(),a=s.slice(i.length).trim();return i==="video"||i==="audio"||i==="all"||!i&&li(s)?pr(e,a||s,n,o,r):i==="transcript"||i==="subs"?Lu(e,a,n,o,r):i==="doctor"||i==="setup"||i==="install"||i==="help"||!s?pr(e,s,n,o,r):{kind:"text",body:p(`/pull is deprecated. Use:
520
+ Runs in background. Use /tr --notify for a completion ping when the shell job ends.`)};if(i==="doctor"||i==="setup"||i==="install")return pr(e,s,n,o,r);let{body:a,notify:l}=ds(s);if(!a)return{kind:"text",body:p("Usage: /tr <url|filepath>")};let c=lt(e),u=fi(e,c);return u?{kind:"text",body:p(u)}:fr(e,o,{sub:"tr",body:a,peerKey:n,jobName:"tr",label:"Transcribe",notify:l})}async function Ly(e,t,n,o,r){let s=t.trim(),i=(s.split(/\s+/)[0]??"").toLowerCase();if(!s||i==="help")return{kind:"text",body:p(["*Edit (/edit)*","","/edit <url|path> [--from 1:30] [--to 2:00] [-t 30]","/edit clip.mp4 --format mp3 --audio-only","","Flags: --from/--start, --to/--end, -t/--duration, --format/-f, --audio-only","","Runs in background. Use /edit --notify for a completion ping when the job ends."].join(`
521
+ `))};if(i==="doctor"||i==="setup"||i==="install")return pr(e,s,n,o,r);let{body:a,notify:l}=ds(s);return a?fr(e,o,{sub:"edit",body:a,peerKey:n,jobName:"edit",label:"Edit",notify:l}):{kind:"text",body:p("Usage: /edit <url|path> [flags]")}}async function Ny(e,t,n,o,r){let s=t.trim(),i=(s.split(/\s+/)[0]??"").toLowerCase(),a=s.slice(i.length).trim();return i==="video"||i==="audio"||i==="all"||!i&&li(s)?pr(e,a||s,n,o,r):i==="transcript"||i==="subs"?Fu(e,a,n,o,r):i==="doctor"||i==="setup"||i==="install"||i==="help"||!s?pr(e,s,n,o,r):{kind:"text",body:p(`/pull is deprecated. Use:
521
522
  /dl <url> \u2014 download video
522
523
  /tr <url|path> \u2014 transcribe
523
524
  /edit <url|path> \u2014 trim/convert
524
525
 
525
- /dl help for full usage.`)}}async function Iy(e,t,n,o,r){if(!(e.mediaUrlAutoDl||!e.chatLlmFallbackEnabled&&!e.chatLlmShellCommand.trim()))return null;let i=ac(t);return i?fr(e,o,{sub:"dl",body:i,peerKey:n,jobName:"dl",label:"Download",notify:!1,friendlyReply:"Downloading link\u2026 I'll send the file when it's ready."}):null}ue();qt();Yn();function Fa(){let e=ge();return e?.platformUrl?e.platformUrl.replace(/\/$/,""):(v().tunnelRelayUrl||Le).trim().replace(/\/$/,"")}function Da(){let e=ge();return e?.token?{Authorization:`Bearer ${e.token}`}:{}}async function ps(e){try{return await e.json()}catch{return{error:`HTTP ${e.status}`}}}function IT(){return`Set platform URL: omnish config add tunnelRelayUrl ${Le} \u2014 publish: omnish platform login`}function Nu(){return ge()?.token?{ok:!0}:{ok:!1,error:`Publish requires a platform account token. ${IT()}`}}async function Oy(e){let t=new URLSearchParams;e?.kind&&t.set("kind",e.kind),e?.limit&&t.set("limit",String(e.limit));let n=t.toString(),o=`${Fa()}/v1/catalog/trending${n?`?${n}`:""}`,r=await fetch(o,{headers:Da()}),s=await ps(r);return r.ok?s:{error:s.error??`HTTP ${r.status}`}}async function Ly(e,t){let n=new URLSearchParams;n.set("q",e),t?.kind&&n.set("kind",t.kind),t?.category&&n.set("category",t.category),t?.limit&&n.set("limit",String(t.limit));let o=`${Fa()}/v1/catalog/search?${n}`,r=await fetch(o,{headers:Da()}),s=await ps(r);return r.ok?s:{error:s.error??`HTTP ${r.status}`}}async function Ny(e){let t=encodeURIComponent(e.trim()),n=`${Fa()}/v1/catalog/${t}`,o=await fetch(n,{headers:Da()}),r=await ps(o);return o.ok?r:{error:r.error??`HTTP ${o.status}`}}async function _y(e){let t=encodeURIComponent(e.trim()),n=`${Fa()}/v1/catalog/${t}/download`,o=await fetch(n,{method:"POST",headers:Da()}),r=await ps(o);return o.ok?r:{error:r.error??`HTTP ${o.status}`}}async function Fy(e){let t=Nu();if(!t.ok)return{error:t.error};let n=ge(),o=`${n.platformUrl.replace(/\/$/,"")}/v1/catalog`,r=await fetch(o,{method:"POST",headers:{"content-type":"application/json",Authorization:`Bearer ${n.token}`},body:JSON.stringify(e)}),s=await ps(r);return r.ok?s:{error:s.error??`HTTP ${r.status}`}}function Dy(e){return e==="global"?"global":"chat"}function Wy(e,t,n,o="global"){switch(e.kind){case"recipe":{let r=e.payload,s=it({...r,dangerous:void 0}),i=so(s);if(!i.ok)return{ok:!1,error:i.error};let a=Bt(e.name);if(!a.ok)return{ok:!1,error:a.error};try{Jo(t,a.normalized,s,Dy(o))}catch(c){return{ok:!1,error:String(c.message??c)}}let l=o==="global"?`/run ${a.normalized} <task>`:`/run ${a.normalized} <task> (this chat)`;return{ok:!0,message:`Installed recipe "${a.normalized}" (${o}). ${l}`}}case"app":{let r=e.payload;if(!r.command?.trim())return{ok:!1,error:"App payload has no command."};let s=it({command:r.command.trim(),label:r.label??e.title,description:r.description??e.description,category:e.category||"app"}),i=Bt(e.name);if(!i.ok)return{ok:!1,error:i.error};try{Jo(t,i.normalized,s,Dy(o),{skipCommandValidation:!0})}catch(a){return{ok:!1,error:String(a.message??a)}}return{ok:!0,message:`Installed app template "${i.normalized}" (${o}). /apps start <session> ${r.command.trim()}`}}case"shortcut":{let r=e.payload,s=$t(e.name);if(!s.ok)return{ok:!1,error:s.error};try{us(t,s.normalized,r.body,o==="global"?"global":"chat")}catch(i){return{ok:!1,error:String(i.message??i)}}return{ok:!0,message:`Installed shortcut "${s.normalized}" (${o}). Type ${s.normalized} to expand.`}}case"cowork":{let r=e.payload,s=Oe();if(pt(s,e.name,t))return{ok:!1,error:`Cowork task "${e.name}" already exists for this chat. Remove it first or rename.`};let i=Date.now(),a={id:Rr(),name:e.name,ownerPeerKey:t,command:r.command,cwd:r.cwd??"",outputDir:r.outputDir??"",schedule:r.schedule,enabled:r.enabled??!0,notify:r.notify??"self",notifyWhen:r.notifyWhen??"always",attachLog:r.attachLog??!1,attachFiles:r.attachFiles??[],lastCompletedSlotMs:null,createdAtMs:i};return s.push(a),Je(s),{ok:!0,message:`Installed cowork task "${e.name}" for this chat. /cowork show ${e.name}`}}default:return{ok:!1,error:"Unknown catalog kind."}}}function Uy(e){return{name:e.name,command:e.command,cwd:e.cwd,outputDir:e.outputDir,schedule:e.schedule,enabled:e.enabled,notify:e.notify,notifyWhen:e.notifyWhen,attachLog:e.attachLog,attachFiles:e.attachFiles}}function By(e){let{dangerous:t,...n}=e;return it(n)}var _u=new Map;function Fu(e,t){return`${e}:${t}`}function Du(e,t,n){_u.set(Fu(e,t),n)}function jy(e,t){return _u.get(Fu(e,t))}function Wa(e,t,n){let o=Number.parseInt(n,10);if(!Number.isFinite(o)||o<1)return null;let r=_u.get(Fu(e,t));return!r||o>r.length?null:r[o-1].publicId}function ho(e){return!!(e&&typeof e=="object"&&typeof e.error=="string")}function Ua(e,t){return`${e.commandPrefix} online ${t}`}function Hy(e){let t=r=>Ua(e,r),n=e.defaultKind===void 0?" [recipe|app|cowork|shortcut]":` (${e.defaultKind} only)`,o=[`Online catalog \u2014 ${e.defaultKind??"all kinds"}`,"",t(`trending${e.defaultKind===void 0?" [kind]":""}`),t(`search <query>${n}`),t("show <publicId>"),t("<publicId> download [--chat|-p for this chat only]"),t("trending <n> download \u2014 install #n from last list"),t("list \u2014 repeat last trending/search list")];return e.commandPrefix==="/run"&&o.push("","Also: /apps online \u2026 \xB7 /cowork online \u2026 \xB7 /shortcut online \u2026","","Publish (platform account):","/run <recipe> publish [--title \u2026] [--category \u2026]","/apps <session> publish","/cowork <name> publish","/shortcut <name> publish"),p(o.join(`
526
- `))}function Ba(e,t,n){if(e.length===0)return p(`${t}
527
- (no results)`);let o=[t,""];return e.forEach((r,s)=>{let i=r.category?` \xB7 ${r.category}`:"";o.push(`${s+1}. ${r.publicId} (${r.kind}${i}) \u2193${r.downloadCount} \u2014 ${r.title||r.name}`),r.description&&o.push(` ${r.description.slice(0,120)}`)}),o.push("",`Install: ${Ua(n,"<publicId> download")} \u2014 or ${Ua(n,"trending <n> download")}`),p(o.join(`
528
- `))}function Jy(e,t){let n=[`${e.publicId} (${e.kind})`,`title: ${e.title||e.name}`,e.description?`description: ${e.description}`:"",e.category?`category: ${e.category}`:"",e.tags?.length?`tags: ${e.tags.join(", ")}`:"",`downloads: ${e.downloadCount}`,`author: ${e.authorLabel||"(unknown)"}`,"","payload:"].filter(Boolean),o=e.payload;if(e.kind==="recipe"||e.kind==="app"){let r=o;if(n.push(` command: ${r.command??""}`),r.promptTemplate&&n.push(` template: ${r.promptTemplate.slice(0,400)}${r.promptTemplate.length>400?"\u2026":""}`),Array.isArray(r.steps)&&r.steps.length>0){n.push(` steps: ${r.steps.length}`);for(let[s,i]of r.steps.entries()){let a=typeof i=="string"?i:i.cmd;n.push(` ${s+1}. ${a??""}`)}}}else if(e.kind==="shortcut")n.push(` body: ${o.body}`);else if(e.kind==="cowork"){let r=o;n.push(` command: ${r.command??""}`),n.push(` schedule: ${r.schedule?.kind??"?"}`)}return n.push("",Ua(t,`${e.publicId} download`)),p(n.join(`
529
- `))}var Wu=new Set(["recipe","app","cowork","shortcut"]),OT={commandPrefix:"/run",listScope:"run"},Gy={commandPrefix:"/apps",defaultKind:"app",listScope:"apps"},qy={commandPrefix:"/cowork",defaultKind:"cowork",listScope:"cowork"},zy={commandPrefix:"/shortcut",defaultKind:"shortcut",listScope:"shortcut"};function LT(e){if(!e)return;let t=e.toLowerCase();return Wu.has(t)?t:void 0}function pn(e,t){return`${e.commandPrefix} online ${t}`}function NT(e,t){if(e.defaultKind)return t&&t.toLowerCase()!==e.defaultKind?{error:`This command only lists ${e.defaultKind} entries. Omit the kind suffix.`}:e.defaultKind;let n=LT(t);return t&&!n?{error:`Unknown kind "${t}". Use: recipe, app, cowork, shortcut`}:n}function _T(e,t){if(e.defaultKind){if(t.length>1&&Wu.has(t[t.length-1].toLowerCase())){if(t[t.length-1].toLowerCase()!==e.defaultKind)return{error:`This command only searches ${e.defaultKind} entries. Omit the kind suffix.`};t.pop()}return{kind:e.defaultKind,query:t.join(" ")}}let n;return t.length>1&&Wu.has(t[t.length-1].toLowerCase())&&(n=t.pop().toLowerCase()),{kind:n,query:t.join(" ")}}async function hr(e,t,n,o){let r=e.trim();if(!r||/^help$/i.test(r))return Hy(o);let s=r.match(/^(.+?)\s+download(?:\s+(.*))?$/i);if(s){let c=s[1].trim(),u=(s[2]??"").trim(),d=/--chat|-p/i.test(u)?"chat":"global",m=null,f=/^trending\s+(\d+)$/i.exec(c),h=/^search\s+(\S+)\s+(\d+)$/i.exec(c);if(f){if(m=Wa(t,o.listScope,f[1]),!m)return p(`No trending list in this chat \u2014 run ${pn(o,"trending")} first, or use ${pn(o,"<publicId> download")}.`)}else if(h){if(m=Wa(t,o.listScope,h[2]),!m)return p(`No search list in this chat \u2014 run ${pn(o,"search <query>")} first, or use ${pn(o,"<publicId> download")}.`)}else if(/^\d+$/.test(c)){if(m=Wa(t,o.listScope,c),!m)return p(`No list item #${c}. Run trending or search first, or use ${pn(o,"<publicId> download")}.`)}else m=c;let g=await _y(m);if(ho(g))return p(`Download failed: ${g.error}`);if(o.defaultKind&&g.kind!==o.defaultKind)return p(`Entry "${m}" is kind=${g.kind}, not ${o.defaultKind}. Use /run online show ${m} from /run.`);let y=Wy(g,t,n,d);return y.ok?p(y.message):p(`Download failed: ${y.error}`)}let i=/^show\s+(\S+)\s*$/i.exec(r);if(i){let c=await Ny(i[1]);return ho(c)?p(`Not found: ${c.error}`):o.defaultKind&&c.kind!==o.defaultKind?p(`Entry "${i[1]}" is kind=${c.kind}, not ${o.defaultKind}. Use /run online show ${i[1]}.`):Jy(c,o)}let a=/^trending(?:\s+(\S+))?\s*$/i.exec(r);if(a){let c=NT(o,a[1]);if(typeof c=="object"&&"error"in c)return p(c.error);let u=await Oy(c?{kind:c}:void 0);if(ho(u))return p(`Trending failed: ${u.error}`);Du(t,o.listScope,u.items);let d=o.defaultKind?`Trending (${o.defaultKind})`:"Trending";return Ba(u.items,d,o)}let l=/^search\s+([\s\S]+)$/i.exec(r);if(l){let c=l[1].trim().split(/\s+/),u=_T(o,c);if("error"in u)return p(u.error);let{kind:d,query:m}=u;if(!m)return p(`Usage: ${pn(o,"search <query>")}`);let f=await Ly(m,d?{kind:d}:void 0);return ho(f)?p(`Search failed: ${f.error}`):(Du(t,o.listScope,f.items),Ba(f.items,`Search: ${m}`,o))}if(/^list\s*$/i.test(r)){let c=jy(t,o.listScope);return c?.length?Ba(c,"Last list",o):p(`No cached list. ${pn(o,"trending")} or ${pn(o,"search <query>")}`)}return p(`Unknown ${o.commandPrefix} online command. ${pn(o,"help")}`)}async function Ky(e,t,n){return hr(e,t,n,OT)}function ms(e){let t=e.trim(),n=[],o,r,s,i=a=>{let l=t.match(a);if(!l?.[1])return;let c=l[1].trim();if(t=(t.slice(0,l.index)+t.slice(l.index+l[0].length)).trim(),a.source.includes("title"))o=c.slice(0,120);else if(a.source.includes("description"))r=c.slice(0,500);else if(a.source.includes("category"))s=c.slice(0,40);else if(a.source.includes("tag"))for(let u of c.split(/[,;]/)){let d=u.trim();d&&n.push(d.slice(0,32))}};for(let a=0;a<8;a+=1){let l=t;if(i(/--title\s+"([^"]+)"/i),i(/--title\s+(\S+)/i),i(/--description\s+"([^"]+)"/i),i(/--description\s+(\S+)/i),i(/--category\s+"([^"]+)"/i),i(/--category\s+(\S+)/i),i(/--tag\s+"([^"]+)"/i),i(/--tag\s+(\S+)/i),t===l)break}return{title:o,description:r,category:s,tags:n,remainder:t.trim()}}function FT(e,t,n){return{kind:"recipe",name:e,title:n.title??t.label??e,description:n.description??t.description,category:n.category??t.category,tags:n.tags,payload:By(t)}}function Yy(e,t,n,o){let r=ms(o),s=n.trim().toLowerCase(),i=ze(e,t,s);return i?i.source==="builtin"?{ok:!1,error:"Built-in recipes cannot be published. Copy with /run add first."}:{ok:!0,body:FT(i.name,i,r)}:{ok:!1,error:`Unknown recipe "${n}".`}}function Vy(e,t,n){let o=ms(n),r=_a(e,t);return r?{ok:!0,body:{kind:"shortcut",name:r.name,title:o.title??r.name,description:o.description,category:o.category??"shortcut",tags:o.tags,payload:{body:r.body}}}:{ok:!1,error:`Unknown shortcut "${t}".`}}function Qy(e,t,n){let o=ms(n),r=Oe(),s=pt(r,t,e);return s?{ok:!0,body:{kind:"cowork",name:s.name,title:o.title??s.name,description:o.description,category:o.category??"cowork",tags:o.tags,payload:Uy(s)}}:{ok:!1,error:`Unknown cowork task "${t}" for this chat.`}}function Xy(e,t,n){let o=ms(n),r=e.trim().toLowerCase();return t.trim()?{ok:!0,body:{kind:"app",name:r,title:o.title??r,description:o.description,category:o.category??"app",tags:o.tags,payload:{command:t.trim(),label:o.title}}}:{ok:!1,error:"Session has no command to publish."}}async function gr(e){let t=Nu();if(!t.ok)return{ok:!1,error:t.error};let n=await Fy(e);if(ho(n))return{ok:!1,error:n.error};let o=n;return{ok:!0,message:`${o.updated?"Updated":"Published"}: ${o.publicId} (${e.kind}). Others: /run online ${o.publicId} download`}}ue();function Uu(e){return $h(e)}function Zy(e){let t=[`tick: unblocked=${e.unblocked} reviewed=${e.reviewed} dispatched=${e.dispatched}`,e.reviewedAfterDispatch>0?`reviewed-after-dispatch=${e.reviewedAfterDispatch}`:"",`waiting: ${e.waiting}`,e.escalations.length?`escalations: ${e.escalations.join(", ")}`:""];for(let n of e.outcomes)t.push(` ${n.jobId}: ${n.from} \u2192 ${n.to}${n.note?` (${n.note})`:""}`);for(let n of e.warnings)t.push(` warn: ${n}`);if(e.reviewed===0&&e.reviewedAfterDispatch===0&&e.dispatched===0){let n=e.pendingReviewCount??0;n>0&&t.push(`note: ${n} job(s) in review \u2014 coordinator will supervise on next tick`)}return t.push(Fi()),t.filter(Boolean).join(`
530
- `)}function ew(e,t,n){let o=ln(e.workDir);if(!o)return{ok:!1,error:"work-plan.json missing (harness should have seeded it)."};let r=o.steps.find(s=>s.id===t);return r?(r.completed=!0,r.completedAtMs=Date.now(),n?.trim()&&(r.selfReviewNotes=n.trim()),Wi(e.workDir,o),Ht(e.id,"step:complete",t),{ok:!0,stepId:t}):{ok:!1,error:`Unknown step id: ${t}`}}j();function tw(){return p(["Job board \u2014 digital employees (coordinator + supervisor while gateway runs).","Specialists must write work-plan.json (plan before work) then report.json.","","add <title> --plan <acceptance criteria\u2026>"," optional: assignee <name> | priority <n> | profile light|standard|heavy","list | show <id> | cancel <id> | tick | capacity","step <jobId> complete <stepId> [self-review notes\u2026]",'employee add <name> --role "\u2026" --inner <cmd\u2026>',"employee show <name> | employee list","feedback <jobId> satisfied yes|no [1-5] [notes\u2026]","recruit accept <suggestionId> | recruit dismiss <suggestionId>","employees list","agent show | agent set <claude|cursor|codex|\u2026> [--yolo|--dangerous] | agent apply","","Coordinator runs automatically while the gateway is up (boardCoordinatorEnabled, default true).","Set boardCoordinatorEnabled false in config.json for manual /board tick only.",`Registry: ${Gn}`,`Governor: ${Cs}`].join(`
531
- `))}function DT(e){let t=e.search(/\s+--plan\s+/i);if(t===-1)return{error:"Usage: /board add <title> --plan <criteria\u2026>"};let n=e.slice(0,t).trim(),o=e.slice(t).replace(/^\s+--plan\s+/i,"").trim();if(!n)return{error:"Title is required."};let r,s,i,a=o.match(/\s+assignee\s+(\S+)/i);a&&(r=a[1].toLowerCase(),o=o.replace(a[0],"").trim());let l=o.match(/\s+priority\s+(\d+)/i);l&&(s=Number(l[1]),o=o.replace(l[0],"").trim());let c=o.match(/\s+profile\s+(light|standard|heavy)/i);return c&&(i=c[1].toLowerCase(),o=o.replace(c[0],"").trim()),o?{title:n,plan:o,assignee:r,priority:s,profile:i}:{error:"Plan (acceptance criteria) is required after --plan."}}function WT(e){let t=e.trim();if(!t)return{error:"Usage: /board agent set <claude|cursor|codex|\u2026> [--yolo|--dangerous]"};let n="safe",o=t;/\s--yolo\b/i.test(o)&&(n="yolo",o=o.replace(/\s--yolo\b/i,"").trim()),/\s--dangerous\b/i.test(o)&&(n="dangerous",o=o.replace(/\s--dangerous\b/i,"").trim());let r=o.split(/\s+/)[0]?.toLowerCase()??"";return r?{agent:r,mode:n}:{error:"Agent recipe name is required."}}async function nw(e,t,n,o){Ue(),Di();let r=e.trim();if(!r||/^help$/i.test(r))return tw();let s=r.split(/\s+/),i=s[0].toLowerCase();if(i==="add"){let a=DT(r.slice(3).trim());if("error"in a)return p(a.error);try{let l=_i({title:a.title,plan:a.plan,ownerPeerKey:t,assignee:a.assignee??null,priority:a.priority,resourceProfile:a.profile});return p(`Job ${l.id} created (pending).
532
- ${Bc(l)}`)}catch(l){return p(String(l instanceof Error?l.message:l))}}if(i==="list"||i==="board")return p(Fi());if(i==="show"&&s[1]){let a=Be(s[1].toLowerCase());return a?p(Bc(a)):p(`Job not found: ${s[1]}`)}if(i==="cancel"&&s[1]){let a=Be(s[1].toLowerCase());return a?(Jt(a,"cancelled","user cancel"),p(`Job ${a.id} cancelled.`)):p(`Job not found: ${s[1]}`)}if(i==="step"&&s[1]&&s[2]==="complete"&&s[3]){let a=Be(s[1].toLowerCase());if(!a)return p(`Job not found: ${s[1]}`);let l=s[3],c=s.slice(4).join(" ").trim()||void 0,u=ew(a,l,c);return u.ok?p(`Step ${l} marked complete on ${a.id}. Progress notify on next coordinator tick.`):p(u.error)}if(i==="tick"){let a=await Ki(n,{sendToPeer:o?.sendToPeer});if(a.escalations.length&&o?.sendToPeer)for(let l of a.escalations){let c=Be(l);c&&await o.sendToPeer(c.ownerPeerKey,`[board] Job ${l} failed after max rework.`)}return p(Zy(a))}if(i==="agent"){let a=s[1]?.toLowerCase();if(a==="show"){let l=n.boardPrimaryAgent||"(not set)",c=n.boardPrimaryAgent?Li(n.boardPrimaryAgent,n,n.boardAgentPermissionMode,t):null,u=[`Primary agent: ${l}`,`Permission mode: ${n.boardAgentPermissionMode}`,`Roles: ${n.boardApplyPrimaryAgentToRoles.join(", ")}`];return c?.ok?u.push(`Command: ${c.command}`):c&&!c.ok&&u.push(`Error: ${c.error}`),p(u.join(`
533
- `))}if(a==="set"){let l=WT(r.slice(r.indexOf("set")+3).trim());if("error"in l)return p(l.error);let c=Li(l.agent,n,l.mode,t);if(!c.ok)return p(c.error);let u=$({boardPrimaryAgent:l.agent,boardAgentPermissionMode:l.mode}),d=_c(u,t);return d.ok?p(`[board] primary agent ${l.agent} (${l.mode}) \xB7 updated ${d.updated.join(", ")}
526
+ /dl help for full usage.`)}}async function _y(e,t,n,o,r){if(!(e.mediaUrlAutoDl||!e.chatLlmFallbackEnabled&&!e.chatLlmShellCommand.trim()))return null;let i=lc(t);return i?fr(e,o,{sub:"dl",body:i,peerKey:n,jobName:"dl",label:"Download",notify:!1,friendlyReply:"Downloading link\u2026 I'll send the file when it's ready."}):null}ue();qt();Yn();function Da(){let e=ge();return e?.platformUrl?e.platformUrl.replace(/\/$/,""):(v().tunnelRelayUrl||Le).trim().replace(/\/$/,"")}function Wa(){let e=ge();return e?.token?{Authorization:`Bearer ${e.token}`}:{}}async function ps(e){try{return await e.json()}catch{return{error:`HTTP ${e.status}`}}}function UT(){return`Set platform URL: omnish config add tunnelRelayUrl ${Le} \u2014 publish: omnish platform login`}function Du(){return ge()?.token?{ok:!0}:{ok:!1,error:`Publish requires a platform account token. ${UT()}`}}async function Fy(e){let t=new URLSearchParams;e?.kind&&t.set("kind",e.kind),e?.limit&&t.set("limit",String(e.limit));let n=t.toString(),o=`${Da()}/v1/catalog/trending${n?`?${n}`:""}`,r=await fetch(o,{headers:Wa()}),s=await ps(r);return r.ok?s:{error:s.error??`HTTP ${r.status}`}}async function Dy(e,t){let n=new URLSearchParams;n.set("q",e),t?.kind&&n.set("kind",t.kind),t?.category&&n.set("category",t.category),t?.limit&&n.set("limit",String(t.limit));let o=`${Da()}/v1/catalog/search?${n}`,r=await fetch(o,{headers:Wa()}),s=await ps(r);return r.ok?s:{error:s.error??`HTTP ${r.status}`}}async function Wy(e){let t=encodeURIComponent(e.trim()),n=`${Da()}/v1/catalog/${t}`,o=await fetch(n,{headers:Wa()}),r=await ps(o);return o.ok?r:{error:r.error??`HTTP ${o.status}`}}async function Uy(e){let t=encodeURIComponent(e.trim()),n=`${Da()}/v1/catalog/${t}/download`,o=await fetch(n,{method:"POST",headers:Wa()}),r=await ps(o);return o.ok?r:{error:r.error??`HTTP ${o.status}`}}async function By(e){let t=Du();if(!t.ok)return{error:t.error};let n=ge(),o=`${n.platformUrl.replace(/\/$/,"")}/v1/catalog`,r=await fetch(o,{method:"POST",headers:{"content-type":"application/json",Authorization:`Bearer ${n.token}`},body:JSON.stringify(e)}),s=await ps(r);return r.ok?s:{error:s.error??`HTTP ${r.status}`}}function jy(e){return e==="global"?"global":"chat"}function Hy(e,t,n,o="global"){switch(e.kind){case"recipe":{let r=e.payload,s=it({...r,dangerous:void 0}),i=so(s);if(!i.ok)return{ok:!1,error:i.error};let a=Bt(e.name);if(!a.ok)return{ok:!1,error:a.error};try{Jo(t,a.normalized,s,jy(o))}catch(c){return{ok:!1,error:String(c.message??c)}}let l=o==="global"?`/run ${a.normalized} <task>`:`/run ${a.normalized} <task> (this chat)`;return{ok:!0,message:`Installed recipe "${a.normalized}" (${o}). ${l}`}}case"app":{let r=e.payload;if(!r.command?.trim())return{ok:!1,error:"App payload has no command."};let s=it({command:r.command.trim(),label:r.label??e.title,description:r.description??e.description,category:e.category||"app"}),i=Bt(e.name);if(!i.ok)return{ok:!1,error:i.error};try{Jo(t,i.normalized,s,jy(o),{skipCommandValidation:!0})}catch(a){return{ok:!1,error:String(a.message??a)}}return{ok:!0,message:`Installed app template "${i.normalized}" (${o}). /apps start <session> ${r.command.trim()}`}}case"shortcut":{let r=e.payload,s=$t(e.name);if(!s.ok)return{ok:!1,error:s.error};try{us(t,s.normalized,r.body,o==="global"?"global":"chat")}catch(i){return{ok:!1,error:String(i.message??i)}}return{ok:!0,message:`Installed shortcut "${s.normalized}" (${o}). Type ${s.normalized} to expand.`}}case"cowork":{let r=e.payload,s=Oe();if(pt(s,e.name,t))return{ok:!1,error:`Cowork task "${e.name}" already exists for this chat. Remove it first or rename.`};let i=Date.now(),a={id:Rr(),name:e.name,ownerPeerKey:t,command:r.command,cwd:r.cwd??"",outputDir:r.outputDir??"",schedule:r.schedule,enabled:r.enabled??!0,notify:r.notify??"self",notifyWhen:r.notifyWhen??"always",attachLog:r.attachLog??!1,attachFiles:r.attachFiles??[],lastCompletedSlotMs:null,createdAtMs:i};return s.push(a),Je(s),{ok:!0,message:`Installed cowork task "${e.name}" for this chat. /cowork show ${e.name}`}}default:return{ok:!1,error:"Unknown catalog kind."}}}function Jy(e){return{name:e.name,command:e.command,cwd:e.cwd,outputDir:e.outputDir,schedule:e.schedule,enabled:e.enabled,notify:e.notify,notifyWhen:e.notifyWhen,attachLog:e.attachLog,attachFiles:e.attachFiles}}function Gy(e){let{dangerous:t,...n}=e;return it(n)}var Wu=new Map;function Uu(e,t){return`${e}:${t}`}function Bu(e,t,n){Wu.set(Uu(e,t),n)}function qy(e,t){return Wu.get(Uu(e,t))}function Ua(e,t,n){let o=Number.parseInt(n,10);if(!Number.isFinite(o)||o<1)return null;let r=Wu.get(Uu(e,t));return!r||o>r.length?null:r[o-1].publicId}function ho(e){return!!(e&&typeof e=="object"&&typeof e.error=="string")}function Ba(e,t){return`${e.commandPrefix} online ${t}`}function zy(e){let t=r=>Ba(e,r),n=e.defaultKind===void 0?" [recipe|app|cowork|shortcut]":` (${e.defaultKind} only)`,o=[`Online catalog \u2014 ${e.defaultKind??"all kinds"}`,"",t(`trending${e.defaultKind===void 0?" [kind]":""}`),t(`search <query>${n}`),t("show <publicId>"),t("<publicId> download [--chat|-p for this chat only]"),t("trending <n> download \u2014 install #n from last list"),t("list \u2014 repeat last trending/search list")];return e.commandPrefix==="/run"&&o.push("","Also: /apps online \u2026 \xB7 /cowork online \u2026 \xB7 /shortcut online \u2026","","Publish (platform account):","/run <recipe> publish [--title \u2026] [--category \u2026]","/apps <session> publish","/cowork <name> publish","/shortcut <name> publish"),p(o.join(`
527
+ `))}function ja(e,t,n){if(e.length===0)return p(`${t}
528
+ (no results)`);let o=[t,""];return e.forEach((r,s)=>{let i=r.category?` \xB7 ${r.category}`:"";o.push(`${s+1}. ${r.publicId} (${r.kind}${i}) \u2193${r.downloadCount} \u2014 ${r.title||r.name}`),r.description&&o.push(` ${r.description.slice(0,120)}`)}),o.push("",`Install: ${Ba(n,"<publicId> download")} \u2014 or ${Ba(n,"trending <n> download")}`),p(o.join(`
529
+ `))}function Ky(e,t){let n=[`${e.publicId} (${e.kind})`,`title: ${e.title||e.name}`,e.description?`description: ${e.description}`:"",e.category?`category: ${e.category}`:"",e.tags?.length?`tags: ${e.tags.join(", ")}`:"",`downloads: ${e.downloadCount}`,`author: ${e.authorLabel||"(unknown)"}`,"","payload:"].filter(Boolean),o=e.payload;if(e.kind==="recipe"||e.kind==="app"){let r=o;if(n.push(` command: ${r.command??""}`),r.promptTemplate&&n.push(` template: ${r.promptTemplate.slice(0,400)}${r.promptTemplate.length>400?"\u2026":""}`),Array.isArray(r.steps)&&r.steps.length>0){n.push(` steps: ${r.steps.length}`);for(let[s,i]of r.steps.entries()){let a=typeof i=="string"?i:i.cmd;n.push(` ${s+1}. ${a??""}`)}}}else if(e.kind==="shortcut")n.push(` body: ${o.body}`);else if(e.kind==="cowork"){let r=o;n.push(` command: ${r.command??""}`),n.push(` schedule: ${r.schedule?.kind??"?"}`)}return n.push("",Ba(t,`${e.publicId} download`)),p(n.join(`
530
+ `))}var ju=new Set(["recipe","app","cowork","shortcut"]),BT={commandPrefix:"/run",listScope:"run"},Yy={commandPrefix:"/apps",defaultKind:"app",listScope:"apps"},Vy={commandPrefix:"/cowork",defaultKind:"cowork",listScope:"cowork"},Qy={commandPrefix:"/shortcut",defaultKind:"shortcut",listScope:"shortcut"};function jT(e){if(!e)return;let t=e.toLowerCase();return ju.has(t)?t:void 0}function pn(e,t){return`${e.commandPrefix} online ${t}`}function HT(e,t){if(e.defaultKind)return t&&t.toLowerCase()!==e.defaultKind?{error:`This command only lists ${e.defaultKind} entries. Omit the kind suffix.`}:e.defaultKind;let n=jT(t);return t&&!n?{error:`Unknown kind "${t}". Use: recipe, app, cowork, shortcut`}:n}function JT(e,t){if(e.defaultKind){if(t.length>1&&ju.has(t[t.length-1].toLowerCase())){if(t[t.length-1].toLowerCase()!==e.defaultKind)return{error:`This command only searches ${e.defaultKind} entries. Omit the kind suffix.`};t.pop()}return{kind:e.defaultKind,query:t.join(" ")}}let n;return t.length>1&&ju.has(t[t.length-1].toLowerCase())&&(n=t.pop().toLowerCase()),{kind:n,query:t.join(" ")}}async function hr(e,t,n,o){let r=e.trim();if(!r||/^help$/i.test(r))return zy(o);let s=r.match(/^(.+?)\s+download(?:\s+(.*))?$/i);if(s){let c=s[1].trim(),u=(s[2]??"").trim(),d=/--chat|-p/i.test(u)?"chat":"global",m=null,f=/^trending\s+(\d+)$/i.exec(c),h=/^search\s+(\S+)\s+(\d+)$/i.exec(c);if(f){if(m=Ua(t,o.listScope,f[1]),!m)return p(`No trending list in this chat \u2014 run ${pn(o,"trending")} first, or use ${pn(o,"<publicId> download")}.`)}else if(h){if(m=Ua(t,o.listScope,h[2]),!m)return p(`No search list in this chat \u2014 run ${pn(o,"search <query>")} first, or use ${pn(o,"<publicId> download")}.`)}else if(/^\d+$/.test(c)){if(m=Ua(t,o.listScope,c),!m)return p(`No list item #${c}. Run trending or search first, or use ${pn(o,"<publicId> download")}.`)}else m=c;let g=await Uy(m);if(ho(g))return p(`Download failed: ${g.error}`);if(o.defaultKind&&g.kind!==o.defaultKind)return p(`Entry "${m}" is kind=${g.kind}, not ${o.defaultKind}. Use /run online show ${m} from /run.`);let y=Hy(g,t,n,d);return y.ok?p(y.message):p(`Download failed: ${y.error}`)}let i=/^show\s+(\S+)\s*$/i.exec(r);if(i){let c=await Wy(i[1]);return ho(c)?p(`Not found: ${c.error}`):o.defaultKind&&c.kind!==o.defaultKind?p(`Entry "${i[1]}" is kind=${c.kind}, not ${o.defaultKind}. Use /run online show ${i[1]}.`):Ky(c,o)}let a=/^trending(?:\s+(\S+))?\s*$/i.exec(r);if(a){let c=HT(o,a[1]);if(typeof c=="object"&&"error"in c)return p(c.error);let u=await Fy(c?{kind:c}:void 0);if(ho(u))return p(`Trending failed: ${u.error}`);Bu(t,o.listScope,u.items);let d=o.defaultKind?`Trending (${o.defaultKind})`:"Trending";return ja(u.items,d,o)}let l=/^search\s+([\s\S]+)$/i.exec(r);if(l){let c=l[1].trim().split(/\s+/),u=JT(o,c);if("error"in u)return p(u.error);let{kind:d,query:m}=u;if(!m)return p(`Usage: ${pn(o,"search <query>")}`);let f=await Dy(m,d?{kind:d}:void 0);return ho(f)?p(`Search failed: ${f.error}`):(Bu(t,o.listScope,f.items),ja(f.items,`Search: ${m}`,o))}if(/^list\s*$/i.test(r)){let c=qy(t,o.listScope);return c?.length?ja(c,"Last list",o):p(`No cached list. ${pn(o,"trending")} or ${pn(o,"search <query>")}`)}return p(`Unknown ${o.commandPrefix} online command. ${pn(o,"help")}`)}async function Xy(e,t,n){return hr(e,t,n,BT)}function ms(e){let t=e.trim(),n=[],o,r,s,i=a=>{let l=t.match(a);if(!l?.[1])return;let c=l[1].trim();if(t=(t.slice(0,l.index)+t.slice(l.index+l[0].length)).trim(),a.source.includes("title"))o=c.slice(0,120);else if(a.source.includes("description"))r=c.slice(0,500);else if(a.source.includes("category"))s=c.slice(0,40);else if(a.source.includes("tag"))for(let u of c.split(/[,;]/)){let d=u.trim();d&&n.push(d.slice(0,32))}};for(let a=0;a<8;a+=1){let l=t;if(i(/--title\s+"([^"]+)"/i),i(/--title\s+(\S+)/i),i(/--description\s+"([^"]+)"/i),i(/--description\s+(\S+)/i),i(/--category\s+"([^"]+)"/i),i(/--category\s+(\S+)/i),i(/--tag\s+"([^"]+)"/i),i(/--tag\s+(\S+)/i),t===l)break}return{title:o,description:r,category:s,tags:n,remainder:t.trim()}}function GT(e,t,n){return{kind:"recipe",name:e,title:n.title??t.label??e,description:n.description??t.description,category:n.category??t.category,tags:n.tags,payload:Gy(t)}}function Zy(e,t,n,o){let r=ms(o),s=n.trim().toLowerCase(),i=ze(e,t,s);return i?i.source==="builtin"?{ok:!1,error:"Built-in recipes cannot be published. Copy with /run add first."}:{ok:!0,body:GT(i.name,i,r)}:{ok:!1,error:`Unknown recipe "${n}".`}}function ew(e,t,n){let o=ms(n),r=Fa(e,t);return r?{ok:!0,body:{kind:"shortcut",name:r.name,title:o.title??r.name,description:o.description,category:o.category??"shortcut",tags:o.tags,payload:{body:r.body}}}:{ok:!1,error:`Unknown shortcut "${t}".`}}function tw(e,t,n){let o=ms(n),r=Oe(),s=pt(r,t,e);return s?{ok:!0,body:{kind:"cowork",name:s.name,title:o.title??s.name,description:o.description,category:o.category??"cowork",tags:o.tags,payload:Jy(s)}}:{ok:!1,error:`Unknown cowork task "${t}" for this chat.`}}function nw(e,t,n){let o=ms(n),r=e.trim().toLowerCase();return t.trim()?{ok:!0,body:{kind:"app",name:r,title:o.title??r,description:o.description,category:o.category??"app",tags:o.tags,payload:{command:t.trim(),label:o.title}}}:{ok:!1,error:"Session has no command to publish."}}async function gr(e){let t=Du();if(!t.ok)return{ok:!1,error:t.error};let n=await By(e);if(ho(n))return{ok:!1,error:n.error};let o=n;return{ok:!0,message:`${o.updated?"Updated":"Published"}: ${o.publicId} (${e.kind}). Others: /run online ${o.publicId} download`}}ue();function Hu(e){return Oh(e)}function ow(e){let t=[`tick: unblocked=${e.unblocked} reviewed=${e.reviewed} dispatched=${e.dispatched}`,e.reviewedAfterDispatch>0?`reviewed-after-dispatch=${e.reviewedAfterDispatch}`:"",`waiting: ${e.waiting}`,e.escalations.length?`escalations: ${e.escalations.join(", ")}`:""];for(let n of e.outcomes)t.push(` ${n.jobId}: ${n.from} \u2192 ${n.to}${n.note?` (${n.note})`:""}`);for(let n of e.warnings)t.push(` warn: ${n}`);if(e.reviewed===0&&e.reviewedAfterDispatch===0&&e.dispatched===0){let n=e.pendingReviewCount??0;n>0&&t.push(`note: ${n} job(s) in review \u2014 coordinator will supervise on next tick`)}return t.push(Di()),t.filter(Boolean).join(`
531
+ `)}function rw(e,t,n){let o=ln(e.workDir);if(!o)return{ok:!1,error:"work-plan.json missing (harness should have seeded it)."};let r=o.steps.find(s=>s.id===t);return r?(r.completed=!0,r.completedAtMs=Date.now(),n?.trim()&&(r.selfReviewNotes=n.trim()),Ui(e.workDir,o),Ht(e.id,"step:complete",t),{ok:!0,stepId:t}):{ok:!1,error:`Unknown step id: ${t}`}}j();function sw(){return p(["Job board \u2014 digital employees (coordinator + supervisor while gateway runs).","Specialists must write work-plan.json (plan before work) then report.json.","","add <title> --plan <acceptance criteria\u2026>"," optional: assignee <name> | priority <n> | profile light|standard|heavy","list | show <id> | cancel <id> | tick | capacity","step <jobId> complete <stepId> [self-review notes\u2026]",'employee add <name> --role "\u2026" --inner <cmd\u2026>',"employee show <name> | employee list","feedback <jobId> satisfied yes|no [1-5] [notes\u2026]","recruit accept <suggestionId> | recruit dismiss <suggestionId>","employees list","agent show | agent set <claude|cursor|codex|\u2026> [--yolo|--dangerous] | agent apply","","Coordinator runs automatically while the gateway is up (boardCoordinatorEnabled, default true).","Set boardCoordinatorEnabled false in config.json for manual /board tick only.",`Registry: ${Gn}`,`Governor: ${Cs}`].join(`
532
+ `))}function qT(e){let t=e.search(/\s+--plan\s+/i);if(t===-1)return{error:"Usage: /board add <title> --plan <criteria\u2026>"};let n=e.slice(0,t).trim(),o=e.slice(t).replace(/^\s+--plan\s+/i,"").trim();if(!n)return{error:"Title is required."};let r,s,i,a=o.match(/\s+assignee\s+(\S+)/i);a&&(r=a[1].toLowerCase(),o=o.replace(a[0],"").trim());let l=o.match(/\s+priority\s+(\d+)/i);l&&(s=Number(l[1]),o=o.replace(l[0],"").trim());let c=o.match(/\s+profile\s+(light|standard|heavy)/i);return c&&(i=c[1].toLowerCase(),o=o.replace(c[0],"").trim()),o?{title:n,plan:o,assignee:r,priority:s,profile:i}:{error:"Plan (acceptance criteria) is required after --plan."}}function zT(e){let t=e.trim();if(!t)return{error:"Usage: /board agent set <claude|cursor|codex|\u2026> [--yolo|--dangerous]"};let n="safe",o=t;/\s--yolo\b/i.test(o)&&(n="yolo",o=o.replace(/\s--yolo\b/i,"").trim()),/\s--dangerous\b/i.test(o)&&(n="dangerous",o=o.replace(/\s--dangerous\b/i,"").trim());let r=o.split(/\s+/)[0]?.toLowerCase()??"";return r?{agent:r,mode:n}:{error:"Agent recipe name is required."}}async function iw(e,t,n,o){Ue(),Wi();let r=e.trim();if(!r||/^help$/i.test(r))return sw();let s=r.split(/\s+/),i=s[0].toLowerCase();if(i==="add"){let a=qT(r.slice(3).trim());if("error"in a)return p(a.error);try{let l=Fi({title:a.title,plan:a.plan,ownerPeerKey:t,assignee:a.assignee??null,priority:a.priority,resourceProfile:a.profile});return p(`Job ${l.id} created (pending).
533
+ ${Jc(l)}`)}catch(l){return p(String(l instanceof Error?l.message:l))}}if(i==="list"||i==="board")return p(Di());if(i==="show"&&s[1]){let a=Be(s[1].toLowerCase());return a?p(Jc(a)):p(`Job not found: ${s[1]}`)}if(i==="cancel"&&s[1]){let a=Be(s[1].toLowerCase());return a?(Jt(a,"cancelled","user cancel"),p(`Job ${a.id} cancelled.`)):p(`Job not found: ${s[1]}`)}if(i==="step"&&s[1]&&s[2]==="complete"&&s[3]){let a=Be(s[1].toLowerCase());if(!a)return p(`Job not found: ${s[1]}`);let l=s[3],c=s.slice(4).join(" ").trim()||void 0,u=rw(a,l,c);return u.ok?p(`Step ${l} marked complete on ${a.id}. Progress notify on next coordinator tick.`):p(u.error)}if(i==="tick"){let a=await Yi(n,{sendToPeer:o?.sendToPeer});if(a.escalations.length&&o?.sendToPeer)for(let l of a.escalations){let c=Be(l);c&&await o.sendToPeer(c.ownerPeerKey,`[board] Job ${l} failed after max rework.`)}return p(ow(a))}if(i==="agent"){let a=s[1]?.toLowerCase();if(a==="show"){let l=n.boardPrimaryAgent||"(not set)",c=n.boardPrimaryAgent?Ni(n.boardPrimaryAgent,n,n.boardAgentPermissionMode,t):null,u=[`Primary agent: ${l}`,`Permission mode: ${n.boardAgentPermissionMode}`,`Roles: ${n.boardApplyPrimaryAgentToRoles.join(", ")}`];return c?.ok?u.push(`Command: ${c.command}`):c&&!c.ok&&u.push(`Error: ${c.error}`),p(u.join(`
534
+ `))}if(a==="set"){let l=zT(r.slice(r.indexOf("set")+3).trim());if("error"in l)return p(l.error);let c=Ni(l.agent,n,l.mode,t);if(!c.ok)return p(c.error);let u=$({boardPrimaryAgent:l.agent,boardAgentPermissionMode:l.mode}),d=Wc(u,t);return d.ok?p(`[board] primary agent ${l.agent} (${l.mode}) \xB7 updated ${d.updated.join(", ")}
534
535
  ${c.command}`):p(`Saved ${l.agent} (${l.mode}) but registry not updated: ${d.error}
535
- Command: ${c.command}`)}if(a==="apply"){let l=_c(n,t);return l.ok?p(`[board] applied primary agent to ${l.updated.join(", ")}`):p(l.error)}return p("agent: show | set <recipe> [--yolo|--dangerous] | apply")}if(i==="capacity")return p(Yh());if(i==="employee"){let a=s[1]?.toLowerCase();if(a==="add"){let l=s[2];if(!l)return p('Usage: /board employee add <name> --role "\u2026" --inner <cmd\u2026>');let c=r.slice(r.indexOf(l)+l.length).trim(),u=c.match(/--role\s+"([^"]+)"/i)??c.match(/--role\s+(\S+)/i),d=c.match(/--inner\s+(.+)$/i);if(!u||!d)return p('Usage: /board employee add <name> --role "\u2026" --inner <cmd\u2026>');try{let{profile:m}=Uu({name:l,role:u[1],innerCommand:d[1].trim(),onboardedBy:t});return p(`[board] employee ${m.id} onboarded \xB7 role ${m.role}`)}catch(m){return p(String(m instanceof Error?m.message:m))}}if(a==="show"&&s[2])return p(Mh(s[2].toLowerCase()));if(a==="apply"&&s[2]){let l=Ah(s[2].toLowerCase());return l.ok?p(`Applied proposed changes for ${s[2]}.`):p(l.error)}if(a==="list"){let l=Pt(),c=["Employees:"];for(let[u,d]of Object.entries(l)){let m=Oi(u),f=m!=null?` \xB7 sat ${m.toFixed(1)}`:"";c.push(` ${u}${d.label?` (${d.label})`:""}${d.role?` \xB7 ${d.role}`:""}${f}`)}return c.length===1&&c.push(" (none)"),p(c.join(`
536
- `))}return p("employee: add | show | list")}if(i==="feedback"){let a=Oh(r.slice(8).trim());if("error"in a)return p(a.error);a.ownerPeerKey=t;let l=Lh(a);return l.ok?p(`Feedback recorded for ${a.jobId}.`):p(l.error)}if(i==="recruit"){let a=s[1]?.toLowerCase(),l=s[2];if(!a||!l)return p("Usage: /board recruit accept|dismiss <suggestionId>");let c=Fh(l);if(!c)return p(`Suggestion not found: ${l}`);if(a==="dismiss")return qc(l,"dismissed"),p(`Recruitment suggestion ${l} dismissed.`);if(a==="accept"){qc(l,"accepted");let u=`bash -lc 'echo "Configure innerCommand for new specialist"'`;try{let{profile:d}=Uu({name:c.suggestedName,role:c.suggestedRole,innerCommand:u,onboardedBy:t,description:c.reason});return p(`[board] recruit accepted \xB7 employee ${d.id} created (set innerCommand in registry).`)}catch(d){return p(String(d instanceof Error?d.message:d))}}return p("recruit: accept | dismiss")}if(i==="employees"&&s[1]==="list"){let a=Pt(),l=["Employees:"];for(let[c,u]of Object.entries(a))l.push(` ${c}${u.label?` (${u.label})`:""}`);return l.length===1&&l.push(" (none \u2014 edit employees.json)"),p(l.join(`
537
- `))}return tw()}function UT(){return p(["Cowork \u2014 scheduled shell tasks (gateway must be running).","","add <name> <schedule> -- <command\u2026>"," schedule: ondemand | hourly [:MM] | daily HH:MM | weekdays HH:MM | weekly <mon\u2026sun> HH:MM","add <name> heartbeat <interval> [grace] \u2014 dead-man's-switch (no command needed)"," e.g. /cowork add backup heartbeat 1h 10m","set <name> cmd -- <command\u2026> | schedule <\u2026> | out <path> | cwd <path>"," notify self|wa|tg|all|none"," when always|failure|state-change \u2014 notify condition (default: always)"," attach on|off \u2014 send run log as a file with notify messages"," files clear | <glob/path \u2026> \u2014 optional artifacts (basename * ?); quote paths with spaces","list | show <name> | run <name> | checkin <name> | enable <name> | disable <name> | remove <name>","publish <name> \u2014 share task to online catalog (platform login)","online trending | show <publicId> | <publicId> download \u2014 cowork tasks only","","Output logs: task outputDir (default ~/Cowork/<name>). Missed times catch up when the gateway is back."].join(`
538
- `))}function ow(e){let t=[],n=0;for(;n<e.length;){for(;n<e.length&&/\s/.test(e[n]);)n+=1;if(n>=e.length)break;if(e[n]==='"'){let r=n+1,s=e.indexOf('"',r);if(s===-1){t.push(e.slice(r));break}t.push(e.slice(r,s)),n=s+1;continue}let o=n;for(;o<e.length&&!/\s/.test(e[o]);)o+=1;t.push(e.slice(n,o)),n=o}return t}function BT(e){let t=" -- ",n=e.indexOf(t);if(n===-1)return{error:'Missing " -- " before the command. Example: /cowork add tick weekdays 09:00 -- date'};let o=e.slice(0,n).trim(),r=e.slice(n+t.length).trim();if(!r)return{error:"Command after -- is empty."};let s=o.split(/\s+/).filter(Boolean);if(s.length<2)return{error:"Usage: /cowork add <name> <schedule\u2026> -- <command\u2026>"};let i=s[0],a=s.slice(1);return{name:i,scheduleWords:a,command:r}}async function sw(e,t,n){let o=e.trim();if(!o||/^help$/i.test(o))return UT();let r=/^online\b([\s\S]*)$/i.exec(o);if(r)return hr((r[1]??"").trim(),t,n,qy);let s=/^(\S+)\s+publish\b([\s\S]*)$/i.exec(o);if(s){let f=Qy(t,s[1],s[2]??"");if(!f.ok)return p(f.error);let h=await gr(f.body);return p(h.ok?h.message:h.error)}let i=o.split(/\s+/)[0].toLowerCase();if(/^list$/i.test(i)){let f=Oe().filter(g=>g.ownerPeerKey===t);if(f.length===0)return p("(no cowork tasks for this chat)");let h=f.map(g=>{let y=g.enabled?"":" (disabled)",k=g.notifyWhen&&g.notifyWhen!=="always"?` when=${g.notifyWhen}`:"";return`${Ne}${g.name} ${Xo(g.schedule)} notify=${g.notify}${k}${y}`});return p(h.join(`
539
- `))}let a=o.match(/^show\s+(\S+)\s*$/i);if(a){let f=a[1],h=Oe();ao(h);let g=pt(h,f,t);if(!g)return p(`Unknown task "${f}". /cowork list`);let y=Qi(g),k=[`name: ${g.name}`,`id: ${g.id}`,`schedule: ${Xo(g.schedule)}`,`enabled: ${g.enabled}`,`notify: ${g.notify}`,`notifyWhen: ${g.notifyWhen??"always"}`];if(g.schedule.kind==="heartbeat"){let b=Zi(g.id);k.push(`lastCheckin: ${b?new Date(b).toLocaleString():"(never \u2014 send /cowork checkin "+g.name+")"}`)}else k.push(`attachLog: ${g.attachLog}`),k.push(`files: ${g.attachFiles.length?g.attachFiles.join(", "):"(none)"}`),k.push(`cwd: ${g.cwd||"(session cwd)"}`),k.push(`out: ${g.outputDir}`),k.push(`cmd: ${g.command}`),k.push(`last slot: ${y?new Date(y).toLocaleString():"(never)"}`);return p(k.join(`
540
- `))}if(/^add$/i.test(i)){let f=o.slice(3).trim(),h=f.match(/^(\S+)\s+(heartbeat\s+.+)$/i);if(h){let R=yl(h[1]);if(!R.ok)return p(R.error);let N=h[2].split(/\s+/).filter(Boolean),A=Vi(N);if(!A.ok)return p(A.error);let q=Oe();if(pt(q,R.name,t))return p(`Task "${R.name}" already exists. Remove it first or pick another name.`);let ee=Ls(R.name),ce={id:Rr(),name:R.name,ownerPeerKey:t,command:"",cwd:"",outputDir:ee,schedule:A.schedule,enabled:!0,notify:"self",notifyWhen:"always",attachLog:!1,attachFiles:[],lastCompletedSlotMs:null,createdAtMs:Date.now()};return q.push(ce),Je(q),ao(q),eu(ce.id),p([`Saved heartbeat task "${R.name}" (${Xo(A.schedule)}).`,`Send /cowork checkin ${R.name} to record a heartbeat.`,"Alerts if no check-in arrives within the expected interval + grace period."].join(`
541
- `))}let g=BT(f);if("error"in g)return p(g.error);let y=yl(g.name);if(!y.ok)return p(y.error);let k=Vi(g.scheduleWords);if(!k.ok)return p(k.error);let b=Oe();if(pt(b,y.name,t))return p(`Task "${y.name}" already exists. Remove it first or pick another name.`);let x=oe(t),C=Ls(y.name),M={id:Rr(),name:y.name,ownerPeerKey:t,command:g.command,cwd:"",outputDir:C,schedule:k.schedule,enabled:!0,notify:"self",notifyWhen:"always",attachLog:!1,attachFiles:[],lastCompletedSlotMs:null,createdAtMs:Date.now()};return b.push(M),Je(b),p([`Saved cowork task "${y.name}" (${Xo(k.schedule)}).`,`Output: ${C}`,`Notify: self \u2014 change with /cowork set ${y.name} notify wa|tg|all|none`].join(`
542
- `))}let l=o.match(/^run\s+(\S+)\s*$/i);if(l){let f=l[1].toLowerCase(),h=pt(Oe(),f,t);return h?h.enabled?(Fd({ownerPeerKey:t,name:h.name,at:Date.now()}),p(`On-demand run queued for "${h.name}" (runs within ~30s while omnish run is active).`)):p(`Cowork "${h.name}" is disabled. /cowork enable ${h.name}`):p(`Unknown task "${l[1]}". /cowork list`)}let c=o.match(/^checkin\s+(\S+)\s*$/i);if(c){let f=c[1].toLowerCase(),h=Oe();ao(h);let g=pt(h,f,t);return g?g.schedule.kind!=="heartbeat"?p(`"${g.name}" is not a heartbeat task.`):(eu(g.id),p(`Heartbeat recorded for "${g.name}".`)):p(`Unknown task "${c[1]}". /cowork list`)}let u=o.match(/^(?:remove|rm|del)\s+(\S+)\s*$/i);if(u){let f=u[1],h=Oe(),g=h.findIndex(y=>y.name===f.toLowerCase()&&y.ownerPeerKey===t);return g===-1?p(`Unknown task "${f}".`):(h.splice(g,1),Je(h),p(`Removed cowork task "${f.toLowerCase()}".`))}let d=o.match(/^enable\s+(\S+)\s*$/i);if(d)return rw(d[1],t,!0);let m=o.match(/^disable\s+(\S+)\s*$/i);return m?rw(m[1],t,!1):/^set$/i.test(i)?GT(o.slice(3).trim(),t):p("Unknown /cowork command. Try /cowork help")}function rw(e,t,n){let o=Oe(),r=pt(o,e,t);return r?(r.enabled=n,Je(o),p(`Cowork "${r.name}" ${n?"enabled":"disabled"}.`)):p(`Unknown task "${e}".`)}function jT(e){let t=e.toLowerCase();return t==="self"?"self":t==="wa"||t==="whatsapp"?"wa":t==="tg"||t==="telegram"?"tg":t==="all"?"all":t==="none"?"none":null}function HT(e){let t=e.toLowerCase();return t==="always"||t==="all"?"always":t==="failure"||t==="fail"||t==="failures"?"failure":t==="state-change"||t==="statechange"||t==="change"||t==="transition"?"state-change":null}function JT(e){let t=e.toLowerCase();return t==="on"||t==="true"||t==="1"||t==="yes"?!0:t==="off"||t==="false"||t==="0"||t==="no"?!1:null}function GT(e,t){let n=e.match(/^(\S+)\s+([\s\S]+)$/);if(!n)return p("Usage: /cowork set <name> cmd -- \u2026 | schedule \u2026 | out <path> | cwd <path> | notify \u2026 | when \u2026 | attach <on|off> | files \u2026");let o=n[1],r=n[2].trim(),s=Oe(),i=pt(s,o,t);if(!i)return p(`Unknown task "${o}".`);if(r.toLowerCase().startsWith("cmd ")){let a=r.slice(4).trim(),l=" -- ",c=a.indexOf(l),u;if(c!==-1)u=a.slice(c+l.length).trim();else if(a.startsWith("--"))u=a.slice(2).trim();else return p("Usage: /cowork set <name> cmd -- <command\u2026>");return u?(i.command=u,Je(s),p(`Updated command for "${i.name}".`)):p("Command is empty.")}if(r.toLowerCase().startsWith("schedule ")){let a=r.slice(9).trim().split(/\s+/).filter(Boolean),l=Vi(a);return l.ok?(i.schedule=l.schedule,Je(s),p(`Schedule for "${i.name}": ${Xo(l.schedule)}`)):p(l.error)}if(r.toLowerCase().startsWith("out ")){let a=r.slice(4).trim(),l=oe(t);return i.outputDir=at(a,l.cwd),Je(s),p(`outputDir: ${i.outputDir}`)}if(r.toLowerCase().startsWith("cwd ")){let a=r.slice(4).trim(),l=oe(t);return i.cwd=a?at(a,l.cwd):"",Je(s),p(`cwd: ${i.cwd||"(session cwd at run time)"}`)}if(r.toLowerCase().startsWith("notify ")){let a=r.slice(7).trim(),l=jT(a);return l?(i.notify=l,Je(s),p(`notify: ${l}`)):p("notify must be self, wa, tg, all, or none.")}if(r.toLowerCase().startsWith("when ")){let a=r.slice(5).trim(),l=HT(a);return l?(i.notifyWhen=l,Je(s),p(`notifyWhen: ${l}`)):p("when must be always, failure, or state-change.")}if(r.toLowerCase().startsWith("attach ")){let a=r.slice(7).trim(),l=ow(a);if(l.length!==1)return p("Usage: /cowork set <name> attach on|off");let c=JT(l[0]);return c===null?p("attach must be on or off"):(i.attachLog=c,Je(s),p(`attachLog: ${c}`))}if(r.toLowerCase().startsWith("files ")){let a=r.slice(6).trim(),l=ow(a);return l.length===1&&l[0].toLowerCase()==="clear"?(i.attachFiles=[],Je(s),p("files: (cleared)")):l.length===0?p("Usage: /cowork set <name> files clear | <pattern \u2026> \u2014 quote paths with spaces"):(i.attachFiles=l,Je(s),p(`files: ${i.attachFiles.join(", ")}`))}return p("Unknown set field. Try: cmd, schedule, out, cwd, notify, when, attach, files")}ue();import pE from"node:fs";j();import iw from"node:os";import qT from"node:path";var zT=new Set(["create","delete","rename","update"]);function KT(e){let t=[],n=0;for(;n<e.length;){for(;n<e.length&&/\s/.test(e[n]);)n+=1;if(n>=e.length)break;if(e[n]==='"'){let r=n+1,s=e.indexOf('"',r);if(s===-1){t.push(e.slice(r));break}t.push(e.slice(r,s)),n=s+1;continue}let o=n;for(;o<e.length&&!/\s/.test(e[o]);)o+=1;t.push(e.slice(n,o)),n=o}return t}function YT(e){let t=e.split(",").map(n=>n.trim().toLowerCase());return t.length===0?!1:t.every(n=>zT.has(n))}function VT(e){return e.split(",").map(t=>t.trim().toLowerCase())}function Bu(e,t,n){let o=e.trim();if(o.startsWith("-")&&(o=o.slice(1)),!o)return{};let r=at(o,n);return o.includes("*")||o.includes("?")?{glob:o.replace(/\\/g,"/")}:qT.isAbsolute(r)||o.startsWith("~")||o.startsWith("./")||o.startsWith("../")?{path:r}:o.startsWith("/")?{path:r}:{glob:o.includes("/")?o:`**/${o}`}}function aw(e,t=iw.homedir()){let n=e.replace(/\s+&&\s+/g," ").trim(),o=KT(n);if(o.length===0)return{ok:!1,error:"Usage: /watch add fs <name> <path> [events] [-exclude \u2026] [--exclude \u2026]"};let r=0,s=o[r];r+=1;let i=["create","delete","rename"],a=[],l=[];for(;r<o.length;){let u=o[r];if(u==="--exclude"){if(r+=1,r>=o.length)return{ok:!1,error:"--exclude requires a pattern."};let d=o[r];r+=1;let m=at(s,t),f=Bu(d.startsWith("-")?d:`-${d}`,m,t);f.path?a.push(f.path):f.glob&&l.push(f.glob);continue}if(u.startsWith("--"))return{ok:!1,error:`Unknown flag: ${u}`};if(u.startsWith("-")){let d=at(s,t),m=Bu(u,d,t);m.path?a.push(m.path):m.glob&&l.push(m.glob),r+=1;continue}if(YT(u)){i=VT(u),r+=1;continue}return{ok:!1,error:`Unexpected token "${u}". Put path first, then events or -excludes.`}}return{ok:!0,value:{rootPath:at(s,t),events:i,excludePaths:a,excludeGlobs:l}}}function ju(e,t,n=iw.homedir()){let o=Bu(e.startsWith("-")?e:`-${e}`,t,n);return!o.path&&!o.glob?{error:"Empty exclude pattern."}:o}import{spawnSync as ja}from"node:child_process";import lw from"node:fs";import QT from"node:os";import XT from"node:path";var ZT=40,eE=10,Ha=15e3,tE=["~/Projects","~/deploy","~/Downloads","~/src","/var/www","/srv"],cw={linux:"/var/log/dpkg.log (also /var/log/apt/history.log)",darwin:"/var/log/install.log",win32:"Windows Application event log (install/remove)"};function nE(e){return e.startsWith("~/")?XT.join(QT.homedir(),e.slice(2)):e}function oE(e,t){return t?.trim()?e.toLowerCase().includes(t.trim().toLowerCase()):!0}function rE(e){let t=n=>n==="running"||n==="active";return[...e].sort((n,o)=>{let r=t(n.state),s=t(o.state);return r!==s?r?-1:1:n.name.localeCompare(o.name)})}function sE(e){let t=[];for(let n of e.split(`
543
- `)){let o=n.trim();if(!o)continue;let r=o.split(/\s+/);if(r.length<4)continue;let s=r[0];if(!s.endsWith(".service"))continue;let i=r[2].toLowerCase(),a=s.slice(0,-8);t.push({name:a,state:i})}return t}function iE(e){let t=[],n=e.split(`
544
- `);for(let o=0;o<n.length;o++){let r=n[o].trim();if(!r||r.startsWith("PID"))continue;let s=r.split(/\s+/);if(s.length<3)continue;let i=s[s.length-1];if(!i.includes("."))continue;let l=s[0]==="-"?"stopped":"running";t.push({name:i,state:l})}return t}function aE(e){let t=[],n="";for(let o of e.split(`
545
- `)){let r=o.match(/SERVICE_NAME:\s*(.+)/i);if(r){n=r[1].trim();continue}let s=o.match(/^\s*STATE\s*:\s*\d+\s+(\S+)/i);if(s&&n){let i=s[1].toUpperCase(),a=i.toLowerCase();i==="RUNNING"?a="running":i==="STOPPED"&&(a="stopped"),t.push({name:n,state:a}),n=""}}return t}function lE(){let e=ja("systemctl",["list-units","--type=service","--all","--no-pager","--plain","--no-legend"],{encoding:"utf8",timeout:Ha});return e.error||e.status!==0?{entries:[],error:"systemctl unavailable \u2014 install systemd or run on Linux."}:{entries:sE(e.stdout??"")}}function cE(){let e=ja("launchctl",["list"],{encoding:"utf8",timeout:Ha});return e.error||e.status!==0?{entries:[],error:"launchctl unavailable."}:{entries:iE(e.stdout??"")}}function uE(){let e=ja("sc",["query","type=","service","state=","all"],{encoding:"utf8",timeout:Ha,windowsHide:!0});if(!e.error&&e.status===0&&(e.stdout??"").includes("SERVICE_NAME"))return{entries:aE(e.stdout??"")};let t=ja("powershell",["-NoProfile","-Command","Get-Service | ForEach-Object { $_.Name + ' ' + $_.Status }"],{encoding:"utf8",timeout:Ha,windowsHide:!0});if(t.error||t.status!==0)return{entries:[],error:"sc query and Get-Service unavailable."};let n=[];for(let o of(t.stdout??"").split(`
546
- `)){let r=o.trim();if(!r)continue;let s=r.lastIndexOf(" ");if(s<=0)continue;let i=r.slice(0,s).trim(),a=r.slice(s+1).trim().toLowerCase();n.push({name:i,state:a})}return{entries:n}}function Ja(e){let t=e?.limit??ZT,n=e?.filter,o,r=process.platform;if(r==="linux")o=lE();else if(r==="darwin")o=cE();else if(r==="win32")o=uE();else return{entries:[],truncated:!1,totalMatched:0,error:`Service discovery not supported on ${r}.`};if(o.error)return{entries:[],truncated:!1,totalMatched:0,error:o.error};let s=rE(o.entries.filter(l=>oE(l.name,n))),i=s.length,a=i>t;return{entries:s.slice(0,t),truncated:a,totalMatched:i}}function dE(e,t=eE){let n=[];for(let o=0;o<e.length;o+=t)n.push(e.slice(o,o+t));return n}function Hu(e,t){if(e.error)return p(e.error);if(e.entries.length===0){let r=t?.trim()?`No services match "${t}". Try /watch svc list without a filter.`:"No services found on this host.";return p(r)}let n=e.entries.map(r=>`${Ne}${r.name} \u2014 ${r.state}`),o=t?.trim()?`Services matching "${t}" (${e.entries.length} shown):`:`Services on ${process.platform} (${e.entries.length} shown):`;if(n.unshift(o),e.truncated){let r=e.totalMatched-e.entries.length;n.push("",`${r} more \u2014 narrow with /watch svc list <filter>`)}return n.push("","Copy-paste templates follow in the next message."),p(n.join(`
547
- `))}function Ju(e,t){let n=t?.trim()||"<name>";if(e.length===0)return p("No services to template \u2014 run /watch svc list first.");let o=e.map(i=>i.name),r=dE(o),s=[t?.trim()?`Copy-paste (/watch add svc ${n} \u2026):`:"Copy-paste (replace <name>):",""];for(let i of r)s.push(`/watch add svc ${n} ${i.join(" ")}`);return p(s.join(`
548
- `))}function uw(){let e=process.platform,t=e==="linux"?"linux":e==="darwin"?"darwin":"win32",n=["Watch hints",""];n.push("Package log (pkg watches):"),n.push(` ${cw[t]??cw.linux}`),n.push("","Filesystem roots that exist on this host:");let o=[];for(let r of tE){let s=nE(r);try{lw.existsSync(s)&&lw.statSync(s).isDirectory()&&o.push(r)}catch{}}if(o.length===0)n.push(" (none of the usual paths found \u2014 use any directory you own)");else for(let r of o)n.push(` ${r}`),n.push(` /watch add fs <name> ${r} create,delete,rename`);return n.push("","Service names: /watch svc list [filter]"),p(n.join(`
549
- `))}function J(e){return{replies:[e]}}function mE(...e){return{replies:e}}function fE(e){let t=e.trim();if(!t)return{};let n=t.split(/\s+/);if(n.length>=2){let s=Fs(n[0]);return s.ok?{ruleName:s.name,filter:n.slice(1).join(" ")}:{filter:t}}let o=n[0],r=Fs(o);if(r.ok){let s=Ja({filter:o,limit:1});if(s.totalMatched===0&&!s.error)return{ruleName:r.name}}return{filter:o}}function hE(){return p(["Watch \u2014 OS event eye (watchEnabled + omnish run). Docs: docs/features/watch.md","","add fs <name> <path> [events] [-/exclude \u2026] [--exclude glob]",' e.g. /watch add fs home ~/Projects create,delete -~/Projects/tmp --exclude "**/.keyfolder"',"add pkg <name> | add svc <name> <unit\u2026>","svc list [filter] \u2014 services on this host (+ templates in next message)","svc templates [ruleName] [filter] \u2014 copy-paste /watch add svc lines only","hints \u2014 suggested FS paths and package log location","list | status | reload | show <name> | recent [N]","pause|stop <name> \u2014 stop alerts, keep rule","resume <name> | enable <name> | disable <name> | rm <name>","exclude <name> list|add <pattern>|rm <pattern>","set <name> notify self|wa|tg|all|none","set <name> when always|state-change","on | off \u2014 global watchEnabled in config","Rules are device-wide (~/.omnish/watch/rules.json); any allowlisted peer can manage.","notify:self alerts the peer who created the rule."].join(`
550
- `))}function dw(){return{excludePaths:[],excludeGlobs:[]}}function gE(e){return e.notify==="self"?`notify=self (${e.ownerPeerKey})`:`notify=${e.notify}`}function yE(e,t){vl(e.id),t==="pause"||t==="stop"?e.paused=!0:t==="resume"?e.paused=!1:t==="enable"?(e.enabled=!0,e.paused=!1):t==="disable"&&(e.enabled=!1,e.paused=!1)}function pw(e,t){let n=e.trim();if(!n||/^help$/i.test(n))return J(hE());let o=n.split(/\s+/)[0].toLowerCase();if(Ns(),/^hints$/i.test(o))return J(uw());if(/^svc$/i.test(o)){let u=n.slice(3).trim(),m=(u.split(/\s+/)[0]??"").toLowerCase();if(m==="list"){let f=u.slice(4).trim()||void 0,h=Ja({filter:f});return h.error?J(p(h.error)):h.entries.length===0?J(Hu(h,f)):mE(Hu(h,f),Ju(h.entries))}if(m==="templates"){let f=u.slice(9).trim(),{ruleName:h,filter:g}=fE(f),y=Ja({filter:g});return y.error?J(p(y.error)):J(Ju(y.entries,h))}return J(p("svc subcommands: list [filter] | templates [ruleName] [filter]"))}if(/^status$/i.test(o)){let u=v(),{summary:d}=Er(),m=[`watchEnabled: ${u.watchEnabled} watchAutoRestore: ${u.watchAutoRestore}`,`debounce: ${u.watchDebounceMs}ms max/min: ${u.watchMaxEventsPerMinute}`,`rules file: ${kl()} (${d.total} saved, ${d.active} active, ${d.paused} paused, ${d.disabled} disabled)`,`events db: ${Rs}`];d.total>0&&!u.watchEnabled?m.push("Rules are on disk; adapters stopped \u2014 /watch on to start."):d.total>0&&u.watchEnabled&&!u.watchAutoRestore?m.push("watchAutoRestore is off \u2014 /watch reload to start adapters without restarting gateway."):d.paused>0&&m.push(`${d.paused} paused rule(s) stay stopped until /watch resume <name>.`);let f=pp();return f?(m.push("","Adapters:"),m.push(...f.getStatusLines())):u.watchEnabled&&u.watchAutoRestore?m.push("","(manager starting \u2014 try /watch status again)"):u.watchEnabled?m.push("","(manager idle \u2014 /watch reload)"):m.push("","(manager off \u2014 /watch on)"),J(p(m.join(`
551
- `)))}if(/^reload$/i.test(o)){let{summary:u}=Er();return v().watchEnabled?(mp(),J(p(`Reloading from ${kl()}: ${u.total} rule(s) on disk, ${u.active} eligible to run. /watch status for adapters.`))):J(p(`Rules on disk: ${u.total} (${u.active} active). watchEnabled is false \u2014 /watch on first.`))}if(/^list$/i.test(o)){let u=Ge();if(u.length===0)return J(p("(no watch rules on this device)"));let d=u.map(m=>{let f=m.enabled?m.paused?"paused":"on":"disabled",h=m.kind==="fs"?m.path:m.kind==="svc"?m.units.join(","):"system";return`${Ne}${m.name} [${m.kind}] ${f} ${gE(m)} when=${m.notifyWhen} \u2014 ${h}`});return J(p(d.join(`
552
- `)))}let r=n.match(/^recent(?:\s+(\d+))?\s*$/i);if(r){let u=r[1]?Number(r[1]):15,d=Ge(),m=new Set(d.map(g=>g.id)),f=Hd(u,m);if(f.length===0)return J(p("(no recent watch events)"));let h=f.map(g=>`${new Date(g.tsMs).toLocaleString()} ${g.ruleName} ${g.summary}`);return J(p(h.join(`
536
+ Command: ${c.command}`)}if(a==="apply"){let l=Wc(n,t);return l.ok?p(`[board] applied primary agent to ${l.updated.join(", ")}`):p(l.error)}return p("agent: show | set <recipe> [--yolo|--dangerous] | apply")}if(i==="capacity")return p(Zh());if(i==="employee"){let a=s[1]?.toLowerCase();if(a==="add"){let l=s[2];if(!l)return p('Usage: /board employee add <name> --role "\u2026" --inner <cmd\u2026>');let c=r.slice(r.indexOf(l)+l.length).trim(),u=c.match(/--role\s+"([^"]+)"/i)??c.match(/--role\s+(\S+)/i),d=c.match(/--inner\s+(.+)$/i);if(!u||!d)return p('Usage: /board employee add <name> --role "\u2026" --inner <cmd\u2026>');try{let{profile:m}=Hu({name:l,role:u[1],innerCommand:d[1].trim(),onboardedBy:t});return p(`[board] employee ${m.id} onboarded \xB7 role ${m.role}`)}catch(m){return p(String(m instanceof Error?m.message:m))}}if(a==="show"&&s[2])return p(Lh(s[2].toLowerCase()));if(a==="apply"&&s[2]){let l=Nh(s[2].toLowerCase());return l.ok?p(`Applied proposed changes for ${s[2]}.`):p(l.error)}if(a==="list"){let l=Pt(),c=["Employees:"];for(let[u,d]of Object.entries(l)){let m=Li(u),f=m!=null?` \xB7 sat ${m.toFixed(1)}`:"";c.push(` ${u}${d.label?` (${d.label})`:""}${d.role?` \xB7 ${d.role}`:""}${f}`)}return c.length===1&&c.push(" (none)"),p(c.join(`
537
+ `))}return p("employee: add | show | list")}if(i==="feedback"){let a=Fh(r.slice(8).trim());if("error"in a)return p(a.error);a.ownerPeerKey=t;let l=Dh(a);return l.ok?p(`Feedback recorded for ${a.jobId}.`):p(l.error)}if(i==="recruit"){let a=s[1]?.toLowerCase(),l=s[2];if(!a||!l)return p("Usage: /board recruit accept|dismiss <suggestionId>");let c=Bh(l);if(!c)return p(`Suggestion not found: ${l}`);if(a==="dismiss")return Yc(l,"dismissed"),p(`Recruitment suggestion ${l} dismissed.`);if(a==="accept"){Yc(l,"accepted");let u=`bash -lc 'echo "Configure innerCommand for new specialist"'`;try{let{profile:d}=Hu({name:c.suggestedName,role:c.suggestedRole,innerCommand:u,onboardedBy:t,description:c.reason});return p(`[board] recruit accepted \xB7 employee ${d.id} created (set innerCommand in registry).`)}catch(d){return p(String(d instanceof Error?d.message:d))}}return p("recruit: accept | dismiss")}if(i==="employees"&&s[1]==="list"){let a=Pt(),l=["Employees:"];for(let[c,u]of Object.entries(a))l.push(` ${c}${u.label?` (${u.label})`:""}`);return l.length===1&&l.push(" (none \u2014 edit employees.json)"),p(l.join(`
538
+ `))}return sw()}function KT(){return p(["Cowork \u2014 scheduled shell tasks (gateway must be running).","","add <name> <schedule> -- <command\u2026>"," schedule: ondemand | hourly [:MM] | daily HH:MM | weekdays HH:MM | weekly <mon\u2026sun> HH:MM","add <name> heartbeat <interval> [grace] \u2014 dead-man's-switch (no command needed)"," e.g. /cowork add backup heartbeat 1h 10m","set <name> cmd -- <command\u2026> | schedule <\u2026> | out <path> | cwd <path>"," notify self|wa|tg|all|none"," when always|failure|state-change \u2014 notify condition (default: always)"," attach on|off \u2014 send run log as a file with notify messages"," files clear | <glob/path \u2026> \u2014 optional artifacts (basename * ?); quote paths with spaces","list | show <name> | run <name> | checkin <name> | enable <name> | disable <name> | remove <name>","publish <name> \u2014 share task to online catalog (platform login)","online trending | show <publicId> | <publicId> download \u2014 cowork tasks only","","Output logs: task outputDir (default ~/Cowork/<name>). Missed times catch up when the gateway is back."].join(`
539
+ `))}function aw(e){let t=[],n=0;for(;n<e.length;){for(;n<e.length&&/\s/.test(e[n]);)n+=1;if(n>=e.length)break;if(e[n]==='"'){let r=n+1,s=e.indexOf('"',r);if(s===-1){t.push(e.slice(r));break}t.push(e.slice(r,s)),n=s+1;continue}let o=n;for(;o<e.length&&!/\s/.test(e[o]);)o+=1;t.push(e.slice(n,o)),n=o}return t}function YT(e){let t=" -- ",n=e.indexOf(t);if(n===-1)return{error:'Missing " -- " before the command. Example: /cowork add tick weekdays 09:00 -- date'};let o=e.slice(0,n).trim(),r=e.slice(n+t.length).trim();if(!r)return{error:"Command after -- is empty."};let s=o.split(/\s+/).filter(Boolean);if(s.length<2)return{error:"Usage: /cowork add <name> <schedule\u2026> -- <command\u2026>"};let i=s[0],a=s.slice(1);return{name:i,scheduleWords:a,command:r}}async function cw(e,t,n){let o=e.trim();if(!o||/^help$/i.test(o))return KT();let r=/^online\b([\s\S]*)$/i.exec(o);if(r)return hr((r[1]??"").trim(),t,n,Vy);let s=/^(\S+)\s+publish\b([\s\S]*)$/i.exec(o);if(s){let f=tw(t,s[1],s[2]??"");if(!f.ok)return p(f.error);let h=await gr(f.body);return p(h.ok?h.message:h.error)}let i=o.split(/\s+/)[0].toLowerCase();if(/^list$/i.test(i)){let f=Oe().filter(g=>g.ownerPeerKey===t);if(f.length===0)return p("(no cowork tasks for this chat)");let h=f.map(g=>{let y=g.enabled?"":" (disabled)",b=g.notifyWhen&&g.notifyWhen!=="always"?` when=${g.notifyWhen}`:"";return`${Ne}${g.name} ${Xo(g.schedule)} notify=${g.notify}${b}${y}`});return p(h.join(`
540
+ `))}let a=o.match(/^show\s+(\S+)\s*$/i);if(a){let f=a[1],h=Oe();ao(h);let g=pt(h,f,t);if(!g)return p(`Unknown task "${f}". /cowork list`);let y=Xi(g),b=[`name: ${g.name}`,`id: ${g.id}`,`schedule: ${Xo(g.schedule)}`,`enabled: ${g.enabled}`,`notify: ${g.notify}`,`notifyWhen: ${g.notifyWhen??"always"}`];if(g.schedule.kind==="heartbeat"){let k=ea(g.id);b.push(`lastCheckin: ${k?new Date(k).toLocaleString():"(never \u2014 send /cowork checkin "+g.name+")"}`)}else b.push(`attachLog: ${g.attachLog}`),b.push(`files: ${g.attachFiles.length?g.attachFiles.join(", "):"(none)"}`),b.push(`cwd: ${g.cwd||"(session cwd)"}`),b.push(`out: ${g.outputDir}`),b.push(`cmd: ${g.command}`),b.push(`last slot: ${y?new Date(y).toLocaleString():"(never)"}`);return p(b.join(`
541
+ `))}if(/^add$/i.test(i)){let f=o.slice(3).trim(),h=f.match(/^(\S+)\s+(heartbeat\s+.+)$/i);if(h){let R=wl(h[1]);if(!R.ok)return p(R.error);let N=h[2].split(/\s+/).filter(Boolean),A=Qi(N);if(!A.ok)return p(A.error);let q=Oe();if(pt(q,R.name,t))return p(`Task "${R.name}" already exists. Remove it first or pick another name.`);let ee=Ls(R.name),ce={id:Rr(),name:R.name,ownerPeerKey:t,command:"",cwd:"",outputDir:ee,schedule:A.schedule,enabled:!0,notify:"self",notifyWhen:"always",attachLog:!1,attachFiles:[],lastCompletedSlotMs:null,createdAtMs:Date.now()};return q.push(ce),Je(q),ao(q),ou(ce.id),p([`Saved heartbeat task "${R.name}" (${Xo(A.schedule)}).`,`Send /cowork checkin ${R.name} to record a heartbeat.`,"Alerts if no check-in arrives within the expected interval + grace period."].join(`
542
+ `))}let g=YT(f);if("error"in g)return p(g.error);let y=wl(g.name);if(!y.ok)return p(y.error);let b=Qi(g.scheduleWords);if(!b.ok)return p(b.error);let k=Oe();if(pt(k,y.name,t))return p(`Task "${y.name}" already exists. Remove it first or pick another name.`);let x=oe(t),C=Ls(y.name),M={id:Rr(),name:y.name,ownerPeerKey:t,command:g.command,cwd:"",outputDir:C,schedule:b.schedule,enabled:!0,notify:"self",notifyWhen:"always",attachLog:!1,attachFiles:[],lastCompletedSlotMs:null,createdAtMs:Date.now()};return k.push(M),Je(k),p([`Saved cowork task "${y.name}" (${Xo(b.schedule)}).`,`Output: ${C}`,`Notify: self \u2014 change with /cowork set ${y.name} notify wa|tg|all|none`].join(`
543
+ `))}let l=o.match(/^run\s+(\S+)\s*$/i);if(l){let f=l[1].toLowerCase(),h=pt(Oe(),f,t);return h?h.enabled?(Ud({ownerPeerKey:t,name:h.name,at:Date.now()}),p(`On-demand run queued for "${h.name}" (runs within ~30s while omnish run is active).`)):p(`Cowork "${h.name}" is disabled. /cowork enable ${h.name}`):p(`Unknown task "${l[1]}". /cowork list`)}let c=o.match(/^checkin\s+(\S+)\s*$/i);if(c){let f=c[1].toLowerCase(),h=Oe();ao(h);let g=pt(h,f,t);return g?g.schedule.kind!=="heartbeat"?p(`"${g.name}" is not a heartbeat task.`):(ou(g.id),p(`Heartbeat recorded for "${g.name}".`)):p(`Unknown task "${c[1]}". /cowork list`)}let u=o.match(/^(?:remove|rm|del)\s+(\S+)\s*$/i);if(u){let f=u[1],h=Oe(),g=h.findIndex(y=>y.name===f.toLowerCase()&&y.ownerPeerKey===t);return g===-1?p(`Unknown task "${f}".`):(h.splice(g,1),Je(h),p(`Removed cowork task "${f.toLowerCase()}".`))}let d=o.match(/^enable\s+(\S+)\s*$/i);if(d)return lw(d[1],t,!0);let m=o.match(/^disable\s+(\S+)\s*$/i);return m?lw(m[1],t,!1):/^set$/i.test(i)?ZT(o.slice(3).trim(),t):p("Unknown /cowork command. Try /cowork help")}function lw(e,t,n){let o=Oe(),r=pt(o,e,t);return r?(r.enabled=n,Je(o),p(`Cowork "${r.name}" ${n?"enabled":"disabled"}.`)):p(`Unknown task "${e}".`)}function VT(e){let t=e.toLowerCase();return t==="self"?"self":t==="wa"||t==="whatsapp"?"wa":t==="tg"||t==="telegram"?"tg":t==="all"?"all":t==="none"?"none":null}function QT(e){let t=e.toLowerCase();return t==="always"||t==="all"?"always":t==="failure"||t==="fail"||t==="failures"?"failure":t==="state-change"||t==="statechange"||t==="change"||t==="transition"?"state-change":null}function XT(e){let t=e.toLowerCase();return t==="on"||t==="true"||t==="1"||t==="yes"?!0:t==="off"||t==="false"||t==="0"||t==="no"?!1:null}function ZT(e,t){let n=e.match(/^(\S+)\s+([\s\S]+)$/);if(!n)return p("Usage: /cowork set <name> cmd -- \u2026 | schedule \u2026 | out <path> | cwd <path> | notify \u2026 | when \u2026 | attach <on|off> | files \u2026");let o=n[1],r=n[2].trim(),s=Oe(),i=pt(s,o,t);if(!i)return p(`Unknown task "${o}".`);if(r.toLowerCase().startsWith("cmd ")){let a=r.slice(4).trim(),l=" -- ",c=a.indexOf(l),u;if(c!==-1)u=a.slice(c+l.length).trim();else if(a.startsWith("--"))u=a.slice(2).trim();else return p("Usage: /cowork set <name> cmd -- <command\u2026>");return u?(i.command=u,Je(s),p(`Updated command for "${i.name}".`)):p("Command is empty.")}if(r.toLowerCase().startsWith("schedule ")){let a=r.slice(9).trim().split(/\s+/).filter(Boolean),l=Qi(a);return l.ok?(i.schedule=l.schedule,Je(s),p(`Schedule for "${i.name}": ${Xo(l.schedule)}`)):p(l.error)}if(r.toLowerCase().startsWith("out ")){let a=r.slice(4).trim(),l=oe(t);return i.outputDir=at(a,l.cwd),Je(s),p(`outputDir: ${i.outputDir}`)}if(r.toLowerCase().startsWith("cwd ")){let a=r.slice(4).trim(),l=oe(t);return i.cwd=a?at(a,l.cwd):"",Je(s),p(`cwd: ${i.cwd||"(session cwd at run time)"}`)}if(r.toLowerCase().startsWith("notify ")){let a=r.slice(7).trim(),l=VT(a);return l?(i.notify=l,Je(s),p(`notify: ${l}`)):p("notify must be self, wa, tg, all, or none.")}if(r.toLowerCase().startsWith("when ")){let a=r.slice(5).trim(),l=QT(a);return l?(i.notifyWhen=l,Je(s),p(`notifyWhen: ${l}`)):p("when must be always, failure, or state-change.")}if(r.toLowerCase().startsWith("attach ")){let a=r.slice(7).trim(),l=aw(a);if(l.length!==1)return p("Usage: /cowork set <name> attach on|off");let c=XT(l[0]);return c===null?p("attach must be on or off"):(i.attachLog=c,Je(s),p(`attachLog: ${c}`))}if(r.toLowerCase().startsWith("files ")){let a=r.slice(6).trim(),l=aw(a);return l.length===1&&l[0].toLowerCase()==="clear"?(i.attachFiles=[],Je(s),p("files: (cleared)")):l.length===0?p("Usage: /cowork set <name> files clear | <pattern \u2026> \u2014 quote paths with spaces"):(i.attachFiles=l,Je(s),p(`files: ${i.attachFiles.join(", ")}`))}return p("Unknown set field. Try: cmd, schedule, out, cwd, notify, when, attach, files")}ue();import kE from"node:fs";j();import uw from"node:os";import eE from"node:path";var tE=new Set(["create","delete","rename","update"]);function nE(e){let t=[],n=0;for(;n<e.length;){for(;n<e.length&&/\s/.test(e[n]);)n+=1;if(n>=e.length)break;if(e[n]==='"'){let r=n+1,s=e.indexOf('"',r);if(s===-1){t.push(e.slice(r));break}t.push(e.slice(r,s)),n=s+1;continue}let o=n;for(;o<e.length&&!/\s/.test(e[o]);)o+=1;t.push(e.slice(n,o)),n=o}return t}function oE(e){let t=e.split(",").map(n=>n.trim().toLowerCase());return t.length===0?!1:t.every(n=>tE.has(n))}function rE(e){return e.split(",").map(t=>t.trim().toLowerCase())}function Ju(e,t,n){let o=e.trim();if(o.startsWith("-")&&(o=o.slice(1)),!o)return{};let r=at(o,n);return o.includes("*")||o.includes("?")?{glob:o.replace(/\\/g,"/")}:eE.isAbsolute(r)||o.startsWith("~")||o.startsWith("./")||o.startsWith("../")?{path:r}:o.startsWith("/")?{path:r}:{glob:o.includes("/")?o:`**/${o}`}}function dw(e,t=uw.homedir()){let n=e.replace(/\s+&&\s+/g," ").trim(),o=nE(n);if(o.length===0)return{ok:!1,error:"Usage: /watch add fs <name> <path> [events] [-exclude \u2026] [--exclude \u2026]"};let r=0,s=o[r];r+=1;let i=["create","delete","rename"],a=[],l=[];for(;r<o.length;){let u=o[r];if(u==="--exclude"){if(r+=1,r>=o.length)return{ok:!1,error:"--exclude requires a pattern."};let d=o[r];r+=1;let m=at(s,t),f=Ju(d.startsWith("-")?d:`-${d}`,m,t);f.path?a.push(f.path):f.glob&&l.push(f.glob);continue}if(u.startsWith("--"))return{ok:!1,error:`Unknown flag: ${u}`};if(u.startsWith("-")){let d=at(s,t),m=Ju(u,d,t);m.path?a.push(m.path):m.glob&&l.push(m.glob),r+=1;continue}if(oE(u)){i=rE(u),r+=1;continue}return{ok:!1,error:`Unexpected token "${u}". Put path first, then events or -excludes.`}}return{ok:!0,value:{rootPath:at(s,t),events:i,excludePaths:a,excludeGlobs:l}}}function Gu(e,t,n=uw.homedir()){let o=Ju(e.startsWith("-")?e:`-${e}`,t,n);return!o.path&&!o.glob?{error:"Empty exclude pattern."}:o}import{spawnSync as Ha}from"node:child_process";import pw from"node:fs";import sE from"node:os";import iE from"node:path";var aE=40,lE=10,Ja=15e3,cE=["~/Projects","~/deploy","~/Downloads","~/src","/var/www","/srv"],mw={linux:"/var/log/dpkg.log (also /var/log/apt/history.log)",darwin:"/var/log/install.log",win32:"Windows Application event log (install/remove)"};function uE(e){return e.startsWith("~/")?iE.join(sE.homedir(),e.slice(2)):e}function dE(e,t){return t?.trim()?e.toLowerCase().includes(t.trim().toLowerCase()):!0}function pE(e){let t=n=>n==="running"||n==="active";return[...e].sort((n,o)=>{let r=t(n.state),s=t(o.state);return r!==s?r?-1:1:n.name.localeCompare(o.name)})}function mE(e){let t=[];for(let n of e.split(`
544
+ `)){let o=n.trim();if(!o)continue;let r=o.split(/\s+/);if(r.length<4)continue;let s=r[0];if(!s.endsWith(".service"))continue;let i=r[2].toLowerCase(),a=s.slice(0,-8);t.push({name:a,state:i})}return t}function fE(e){let t=[],n=e.split(`
545
+ `);for(let o=0;o<n.length;o++){let r=n[o].trim();if(!r||r.startsWith("PID"))continue;let s=r.split(/\s+/);if(s.length<3)continue;let i=s[s.length-1];if(!i.includes("."))continue;let l=s[0]==="-"?"stopped":"running";t.push({name:i,state:l})}return t}function hE(e){let t=[],n="";for(let o of e.split(`
546
+ `)){let r=o.match(/SERVICE_NAME:\s*(.+)/i);if(r){n=r[1].trim();continue}let s=o.match(/^\s*STATE\s*:\s*\d+\s+(\S+)/i);if(s&&n){let i=s[1].toUpperCase(),a=i.toLowerCase();i==="RUNNING"?a="running":i==="STOPPED"&&(a="stopped"),t.push({name:n,state:a}),n=""}}return t}function gE(){let e=Ha("systemctl",["list-units","--type=service","--all","--no-pager","--plain","--no-legend"],{encoding:"utf8",timeout:Ja});return e.error||e.status!==0?{entries:[],error:"systemctl unavailable \u2014 install systemd or run on Linux."}:{entries:mE(e.stdout??"")}}function yE(){let e=Ha("launchctl",["list"],{encoding:"utf8",timeout:Ja});return e.error||e.status!==0?{entries:[],error:"launchctl unavailable."}:{entries:fE(e.stdout??"")}}function wE(){let e=Ha("sc",["query","type=","service","state=","all"],{encoding:"utf8",timeout:Ja,windowsHide:!0});if(!e.error&&e.status===0&&(e.stdout??"").includes("SERVICE_NAME"))return{entries:hE(e.stdout??"")};let t=Ha("powershell",["-NoProfile","-Command","Get-Service | ForEach-Object { $_.Name + ' ' + $_.Status }"],{encoding:"utf8",timeout:Ja,windowsHide:!0});if(t.error||t.status!==0)return{entries:[],error:"sc query and Get-Service unavailable."};let n=[];for(let o of(t.stdout??"").split(`
547
+ `)){let r=o.trim();if(!r)continue;let s=r.lastIndexOf(" ");if(s<=0)continue;let i=r.slice(0,s).trim(),a=r.slice(s+1).trim().toLowerCase();n.push({name:i,state:a})}return{entries:n}}function Ga(e){let t=e?.limit??aE,n=e?.filter,o,r=process.platform;if(r==="linux")o=gE();else if(r==="darwin")o=yE();else if(r==="win32")o=wE();else return{entries:[],truncated:!1,totalMatched:0,error:`Service discovery not supported on ${r}.`};if(o.error)return{entries:[],truncated:!1,totalMatched:0,error:o.error};let s=pE(o.entries.filter(l=>dE(l.name,n))),i=s.length,a=i>t;return{entries:s.slice(0,t),truncated:a,totalMatched:i}}function bE(e,t=lE){let n=[];for(let o=0;o<e.length;o+=t)n.push(e.slice(o,o+t));return n}function qu(e,t){if(e.error)return p(e.error);if(e.entries.length===0){let r=t?.trim()?`No services match "${t}". Try /watch svc list without a filter.`:"No services found on this host.";return p(r)}let n=e.entries.map(r=>`${Ne}${r.name} \u2014 ${r.state}`),o=t?.trim()?`Services matching "${t}" (${e.entries.length} shown):`:`Services on ${process.platform} (${e.entries.length} shown):`;if(n.unshift(o),e.truncated){let r=e.totalMatched-e.entries.length;n.push("",`${r} more \u2014 narrow with /watch svc list <filter>`)}return n.push("","Copy-paste templates follow in the next message."),p(n.join(`
548
+ `))}function zu(e,t){let n=t?.trim()||"<name>";if(e.length===0)return p("No services to template \u2014 run /watch svc list first.");let o=e.map(i=>i.name),r=bE(o),s=[t?.trim()?`Copy-paste (/watch add svc ${n} \u2026):`:"Copy-paste (replace <name>):",""];for(let i of r)s.push(`/watch add svc ${n} ${i.join(" ")}`);return p(s.join(`
549
+ `))}function fw(){let e=process.platform,t=e==="linux"?"linux":e==="darwin"?"darwin":"win32",n=["Watch hints",""];n.push("Package log (pkg watches):"),n.push(` ${mw[t]??mw.linux}`),n.push("","Filesystem roots that exist on this host:");let o=[];for(let r of cE){let s=uE(r);try{pw.existsSync(s)&&pw.statSync(s).isDirectory()&&o.push(r)}catch{}}if(o.length===0)n.push(" (none of the usual paths found \u2014 use any directory you own)");else for(let r of o)n.push(` ${r}`),n.push(` /watch add fs <name> ${r} create,delete,rename`);return n.push("","Service names: /watch svc list [filter]"),p(n.join(`
550
+ `))}function J(e){return{replies:[e]}}function SE(...e){return{replies:e}}function vE(e){let t=e.trim();if(!t)return{};let n=t.split(/\s+/);if(n.length>=2){let s=Fs(n[0]);return s.ok?{ruleName:s.name,filter:n.slice(1).join(" ")}:{filter:t}}let o=n[0],r=Fs(o);if(r.ok){let s=Ga({filter:o,limit:1});if(s.totalMatched===0&&!s.error)return{ruleName:r.name}}return{filter:o}}function xE(){return p(["Watch \u2014 OS event eye (watchEnabled + omnish run). Docs: docs/features/watch.md","","add fs <name> <path> [events] [-/exclude \u2026] [--exclude glob]",' e.g. /watch add fs home ~/Projects create,delete -~/Projects/tmp --exclude "**/.keyfolder"',"add pkg <name> | add svc <name> <unit\u2026>","svc list [filter] \u2014 services on this host (+ templates in next message)","svc templates [ruleName] [filter] \u2014 copy-paste /watch add svc lines only","hints \u2014 suggested FS paths and package log location","list | status | reload | show <name> | recent [N]","pause|stop <name> \u2014 stop alerts, keep rule","resume <name> | enable <name> | disable <name> | rm <name>","exclude <name> list|add <pattern>|rm <pattern>","set <name> notify self|wa|tg|all|none","set <name> when always|state-change","on | off \u2014 global watchEnabled in config","Rules are device-wide (~/.omnish/watch/rules.json); any allowlisted peer can manage.","notify:self alerts the peer who created the rule."].join(`
551
+ `))}function hw(){return{excludePaths:[],excludeGlobs:[]}}function CE(e){return e.notify==="self"?`notify=self (${e.ownerPeerKey})`:`notify=${e.notify}`}function RE(e,t){xl(e.id),t==="pause"||t==="stop"?e.paused=!0:t==="resume"?e.paused=!1:t==="enable"?(e.enabled=!0,e.paused=!1):t==="disable"&&(e.enabled=!1,e.paused=!1)}function gw(e,t){let n=e.trim();if(!n||/^help$/i.test(n))return J(xE());let o=n.split(/\s+/)[0].toLowerCase();if(Ns(),/^hints$/i.test(o))return J(fw());if(/^svc$/i.test(o)){let u=n.slice(3).trim(),m=(u.split(/\s+/)[0]??"").toLowerCase();if(m==="list"){let f=u.slice(4).trim()||void 0,h=Ga({filter:f});return h.error?J(p(h.error)):h.entries.length===0?J(qu(h,f)):SE(qu(h,f),zu(h.entries))}if(m==="templates"){let f=u.slice(9).trim(),{ruleName:h,filter:g}=vE(f),y=Ga({filter:g});return y.error?J(p(y.error)):J(zu(y.entries,h))}return J(p("svc subcommands: list [filter] | templates [ruleName] [filter]"))}if(/^status$/i.test(o)){let u=v(),{summary:d}=Er(),m=[`watchEnabled: ${u.watchEnabled} watchAutoRestore: ${u.watchAutoRestore}`,`debounce: ${u.watchDebounceMs}ms max/min: ${u.watchMaxEventsPerMinute}`,`rules file: ${Sl()} (${d.total} saved, ${d.active} active, ${d.paused} paused, ${d.disabled} disabled)`,`events db: ${Rs}`];d.total>0&&!u.watchEnabled?m.push("Rules are on disk; adapters stopped \u2014 /watch on to start."):d.total>0&&u.watchEnabled&&!u.watchAutoRestore?m.push("watchAutoRestore is off \u2014 /watch reload to start adapters without restarting gateway."):d.paused>0&&m.push(`${d.paused} paused rule(s) stay stopped until /watch resume <name>.`);let f=hp();return f?(m.push("","Adapters:"),m.push(...f.getStatusLines())):u.watchEnabled&&u.watchAutoRestore?m.push("","(manager starting \u2014 try /watch status again)"):u.watchEnabled?m.push("","(manager idle \u2014 /watch reload)"):m.push("","(manager off \u2014 /watch on)"),J(p(m.join(`
552
+ `)))}if(/^reload$/i.test(o)){let{summary:u}=Er();return v().watchEnabled?(gp(),J(p(`Reloading from ${Sl()}: ${u.total} rule(s) on disk, ${u.active} eligible to run. /watch status for adapters.`))):J(p(`Rules on disk: ${u.total} (${u.active} active). watchEnabled is false \u2014 /watch on first.`))}if(/^list$/i.test(o)){let u=Ge();if(u.length===0)return J(p("(no watch rules on this device)"));let d=u.map(m=>{let f=m.enabled?m.paused?"paused":"on":"disabled",h=m.kind==="fs"?m.path:m.kind==="svc"?m.units.join(","):"system";return`${Ne}${m.name} [${m.kind}] ${f} ${CE(m)} when=${m.notifyWhen} \u2014 ${h}`});return J(p(d.join(`
553
+ `)))}let r=n.match(/^recent(?:\s+(\d+))?\s*$/i);if(r){let u=r[1]?Number(r[1]):15,d=Ge(),m=new Set(d.map(g=>g.id)),f=qd(u,m);if(f.length===0)return J(p("(no recent watch events)"));let h=f.map(g=>`${new Date(g.tsMs).toLocaleString()} ${g.ruleName} ${g.summary}`);return J(p(h.join(`
553
554
  `)))}let s=n.match(/^show\s+(\S+)\s*$/i);if(s){let u=qn(Ge(),s[1]);if(!u)return J(p(`Unknown rule "${s[1]}". /watch list`));let d=[`name: ${u.name}`,`kind: ${u.kind}`,`creator: ${u.ownerPeerKey}`,`enabled: ${u.enabled}`,`paused: ${u.paused}`,`notify: ${u.notify}`,`notifyWhen: ${u.notifyWhen}`,`adapter: ${u.adapterStatus||"(unknown)"}`];return u.kind==="fs"&&(d.push(`path: ${u.path}`),d.push(`events: ${u.events.join(",")}`),d.push(`excludePaths: ${u.excludePaths.length?u.excludePaths.join(", "):"(none)"}`),d.push(`excludeGlobs: ${u.excludeGlobs.length?u.excludeGlobs.join(", "):"(none)"}`)),u.kind==="svc"&&d.push(`units: ${u.units.join(", ")||"(none)"}`),J(p(d.join(`
554
555
  `)))}if(/^on$/i.test(o))return $({watchEnabled:!0}),ot(),J(p("watchEnabled: true (gateway picks up watchers when running)."));if(/^off$/i.test(o))return $({watchEnabled:!1}),ot(),J(p("watchEnabled: false \u2014 all adapters stopped."));let i=n.match(/^exclude\s+(\S+)\s+(\S+)(?:\s+([\s\S]+))?\s*$/i);if(i){let u=i[1],d=i[2].toLowerCase(),m=(i[3]??"").trim(),f=Ge(),h=qn(f,u);if(!h)return J(p(`Unknown rule "${u}".`));if(h.kind!=="fs")return J(p("exclude applies to filesystem rules only."));if(d==="list"){let g=[`excludePaths: ${h.excludePaths.length?h.excludePaths.join(`
555
556
  `):"(none)"}`,`excludeGlobs: ${h.excludeGlobs.length?h.excludeGlobs.join(`
556
557
  `):"(none)"}`];return J(p(g.join(`
557
- `)))}if(d==="add"){if(!m)return J(p("Usage: /watch exclude <name> add <pattern>"));let g=ju(m,h.path);if("error"in g)return J(p(g.error));if(g.path){if(mt(g.path))return J(p("That path is blocked."));h.excludePaths.includes(g.path)||h.excludePaths.push(g.path)}return g.glob&&!h.excludeGlobs.includes(g.glob)&&h.excludeGlobs.push(g.glob),Lt(Eo(f,h)),ot(),J(p(`Added exclude to "${h.name}".`))}if(d==="rm"||d==="remove"){if(!m)return J(p("Usage: /watch exclude <name> rm <pattern>"));let g=ju(m,h.path);return"error"in g?J(p(g.error)):(g.path&&(h.excludePaths=h.excludePaths.filter(y=>y!==g.path)),g.glob&&(h.excludeGlobs=h.excludeGlobs.filter(y=>y!==g.glob)),Lt(Eo(f,h)),ot(),J(p(`Removed exclude from "${h.name}".`)))}return J(p("exclude subcommands: list | add | rm"))}if(/^add$/i.test(o)){let u=n.slice(3).trim(),d=u.split(/\s+/);if(d.length<2)return J(p("Usage: /watch add fs|pkg|svc <name> \u2026"));let m=d[0].toLowerCase(),f=m==="fs"||m==="pkg"||m==="svc"?m:null;if(!f)return J(p("Kind must be fs, pkg, or svc."));let h=d[1],g=Fs(h);if(!g.ok)return J(p(g.error));let y=Ge();if(y.length>=bl)return J(p(`Max ${bl} watch rules on this device.`));if(qn(y,g.name))return J(p(`Rule "${g.name}" already exists.`));let k;if(f==="fs"){let C=u.slice(m.length+1+h.length).trim(),M=aw(C);if(!M.ok)return J(p(M.error));let{rootPath:R,events:N,excludePaths:A,excludeGlobs:q}=M.value;if(mt(R))return J(p("That path is blocked (sensitive). Choose another directory."));if(!pE.existsSync(R))return J(p(`Path not found: ${R}`));for(let ee of A)if(mt(ee))return J(p(`Excluded path blocked: ${ee}`));k={id:Ds(),name:g.name,ownerPeerKey:t,kind:"fs",enabled:!0,paused:!1,notify:"self",notifyWhen:"always",path:R,events:N,units:[],excludePaths:A,excludeGlobs:q,adapterStatus:"",createdAtMs:Date.now()}}else if(f==="pkg")k={id:Ds(),name:g.name,ownerPeerKey:t,kind:"pkg",enabled:!0,paused:!1,notify:"self",notifyWhen:"always",path:"",events:[],units:[],...dw(),adapterStatus:"",createdAtMs:Date.now()};else{let C=d.slice(2);if(C.length===0)return J(p("Usage: /watch add svc <name> <unit\u2026>"));k={id:Ds(),name:g.name,ownerPeerKey:t,kind:"svc",enabled:!0,paused:!1,notify:"self",notifyWhen:"always",path:"",events:[],units:C,...dw(),adapterStatus:"",createdAtMs:Date.now()}}Lt(Eo(y,k)),ot();let x=v().watchEnabled?"":" Rule saved to disk. /watch on to start adapters.";return J(p(`Watch rule "${k.name}" added (${k.kind}).${x}`))}let a=n.match(/^set\s+(\S+)\s+(\S+)\s+(\S+)\s*$/i);if(a){let[,u,d,m]=a,f=Ge(),h=qn(f,u);if(!h)return J(p(`Unknown rule "${u}".`));let g=d.toLowerCase(),y=m.toLowerCase();if(g==="notify"){let k=y==="wa"||y==="whatsapp"?"wa":y==="tg"||y==="telegram"?"tg":y==="all"?"all":y==="none"?"none":y==="self"?"self":null;if(!k)return J(p("notify must be self, wa, tg, all, or none."));h.notify=k}else if(g==="when"){let k=y==="state-change"?"state-change":y==="always"?"always":null;if(!k)return J(p("when must be always or state-change."));h.notifyWhen=k}else return J(p("set supports: notify, when"));return Lt(Eo(f,h)),ot(),J(p(`Updated ${h.name} ${g}=${y}.`))}let l=n.match(/^(?:rm|remove)\s+(\S+)\s*$/i);if(l){let u=l[1],d=Ge(),m=qn(d,u);return m?(vl(m.id),Lt(Yd(d,u)),ot(),J(p(`Removed watch rule "${u}".`))):J(p(`Unknown rule "${u}".`))}let c=n.match(/^(pause|stop|resume|enable|disable)\s+(\S+)\s*$/i);if(c){let u=c[1].toLowerCase(),d=c[2],m=Ge(),f=qn(m,d);if(!f)return J(p(`Unknown rule "${d}".`));yE(f,u==="stop"?"pause":u),Lt(Eo(m,f)),ot();let g=u==="stop"?"paused (stop)":`${u}d`;return J(p(`${f.name}: ${g}.`))}return J(p("Unknown /watch command. Try /watch help"))}ue();Kn();Yn();function mw(){return p(["Tunnel commands:",'/tunnel signup --email "you@example.com" --password "yourpass"','/tunnel signup --phone "+1234567890" --password "yourpass"','/tunnel login --email "you@example.com" --password "yourpass"','/tunnel login --token "<token>" [--relay <url>]',"/tunnel logout","/tunnel status [--relay <url>]","/tunnel http <port> [--name <slug>] [--host <addr>]","/tunnel tcp <port> [--name <slug>] [--host <addr>]","/tunnels","/tunnel stop <id|slug>"].join(`
558
- `))}async function Gu(e,t,n){let o=Gg(e);if(o.kind==="help")return mw();if(o.kind==="error")return p(o.message);if(o.kind==="signup"){let r=o.email?.trim()||"",s=o.phone?.trim()||"",i=o.password||"";if(!r&&!s)return p('Usage: /tunnel signup --email "you@example.com" --password "yourpass"');if(i.length<8)return p("Password must be at least 8 characters.");let a=v(),l=o.relayUrl?.trim()||vt(a.tunnelRelayUrl||Le),c=await Pa(l,{...r?{email:r}:{},...s?{phone:s}:{},password:i});return c.ok?(Ft({token:c.token,...o.relayUrl?.trim()?{relayUrl:o.relayUrl.trim()}:{}}),p("Account created. Token saved.")):p(`Signup failed: ${c.error}`)}if(o.kind==="login"){if(o.email||o.phone){let i=o.password||"";if(!i)return p('Usage: /tunnel login --email "you@example.com" --password "yourpass"');let a=v(),l=o.relayUrl?.trim()||vt(a.tunnelRelayUrl||Le),c=await $a(l,{...o.email?{email:o.email}:{},...o.phone?{phone:o.phone}:{},password:i});return c.ok?(Ft({token:c.token,...o.relayUrl?.trim()?{relayUrl:o.relayUrl.trim()}:{}}),p("Logged in. Token saved.")):p(`Login failed: ${c.error}`)}let s=o.token?.trim();return s?(Ft({token:s,...o.relayUrl?.trim()?{relayUrl:o.relayUrl.trim()}:{}}),p("Tunnel token saved on this host (not shown). If OMNISH_TUNNEL_TOKEN is set in the gateway environment, it overrides the file until unset.")):p('Usage: /tunnel login --token "<token>" [--relay <url>]')}if(o.kind==="logout")return Ks(),p("Tunnel token file removed. Unset OMNISH_TUNNEL_TOKEN in the gateway environment if you rely on it.");if(o.kind==="status"){let r=v(),s=o.relayUrl?.trim()||vt(r.tunnelRelayUrl||Le),i=Dt(),a=!!process.env.OMNISH_TUNNEL_TOKEN?.trim(),c=!!Zt()?.token?.trim(),u=await Ca(s,i),d=[`Relay: ${s}`,`Token: ${i?"configured":"missing"}${a?" (OMNISH_TUNNEL_TOKEN)":c?" (tunnel-auth.json)":""}`,`Health: ${u.healthOk?`ok${u.healthVersion?` (version ${u.healthVersion})`:""}`:"fail"}`,`Control (WSS): ${u.controlOk?"auth ok":"fail"}`,`Active tunnels: ${n.getActiveCount()}`];return!u.ok&&u.error&&d.push(`Detail: ${u.error}`),p(d.join(`
558
+ `)))}if(d==="add"){if(!m)return J(p("Usage: /watch exclude <name> add <pattern>"));let g=Gu(m,h.path);if("error"in g)return J(p(g.error));if(g.path){if(mt(g.path))return J(p("That path is blocked."));h.excludePaths.includes(g.path)||h.excludePaths.push(g.path)}return g.glob&&!h.excludeGlobs.includes(g.glob)&&h.excludeGlobs.push(g.glob),Lt(Eo(f,h)),ot(),J(p(`Added exclude to "${h.name}".`))}if(d==="rm"||d==="remove"){if(!m)return J(p("Usage: /watch exclude <name> rm <pattern>"));let g=Gu(m,h.path);return"error"in g?J(p(g.error)):(g.path&&(h.excludePaths=h.excludePaths.filter(y=>y!==g.path)),g.glob&&(h.excludeGlobs=h.excludeGlobs.filter(y=>y!==g.glob)),Lt(Eo(f,h)),ot(),J(p(`Removed exclude from "${h.name}".`)))}return J(p("exclude subcommands: list | add | rm"))}if(/^add$/i.test(o)){let u=n.slice(3).trim(),d=u.split(/\s+/);if(d.length<2)return J(p("Usage: /watch add fs|pkg|svc <name> \u2026"));let m=d[0].toLowerCase(),f=m==="fs"||m==="pkg"||m==="svc"?m:null;if(!f)return J(p("Kind must be fs, pkg, or svc."));let h=d[1],g=Fs(h);if(!g.ok)return J(p(g.error));let y=Ge();if(y.length>=kl)return J(p(`Max ${kl} watch rules on this device.`));if(qn(y,g.name))return J(p(`Rule "${g.name}" already exists.`));let b;if(f==="fs"){let C=u.slice(m.length+1+h.length).trim(),M=dw(C);if(!M.ok)return J(p(M.error));let{rootPath:R,events:N,excludePaths:A,excludeGlobs:q}=M.value;if(mt(R))return J(p("That path is blocked (sensitive). Choose another directory."));if(!kE.existsSync(R))return J(p(`Path not found: ${R}`));for(let ee of A)if(mt(ee))return J(p(`Excluded path blocked: ${ee}`));b={id:Ds(),name:g.name,ownerPeerKey:t,kind:"fs",enabled:!0,paused:!1,notify:"self",notifyWhen:"always",path:R,events:N,units:[],excludePaths:A,excludeGlobs:q,adapterStatus:"",createdAtMs:Date.now()}}else if(f==="pkg")b={id:Ds(),name:g.name,ownerPeerKey:t,kind:"pkg",enabled:!0,paused:!1,notify:"self",notifyWhen:"always",path:"",events:[],units:[],...hw(),adapterStatus:"",createdAtMs:Date.now()};else{let C=d.slice(2);if(C.length===0)return J(p("Usage: /watch add svc <name> <unit\u2026>"));b={id:Ds(),name:g.name,ownerPeerKey:t,kind:"svc",enabled:!0,paused:!1,notify:"self",notifyWhen:"always",path:"",events:[],units:C,...hw(),adapterStatus:"",createdAtMs:Date.now()}}Lt(Eo(y,b)),ot();let x=v().watchEnabled?"":" Rule saved to disk. /watch on to start adapters.";return J(p(`Watch rule "${b.name}" added (${b.kind}).${x}`))}let a=n.match(/^set\s+(\S+)\s+(\S+)\s+(\S+)\s*$/i);if(a){let[,u,d,m]=a,f=Ge(),h=qn(f,u);if(!h)return J(p(`Unknown rule "${u}".`));let g=d.toLowerCase(),y=m.toLowerCase();if(g==="notify"){let b=y==="wa"||y==="whatsapp"?"wa":y==="tg"||y==="telegram"?"tg":y==="all"?"all":y==="none"?"none":y==="self"?"self":null;if(!b)return J(p("notify must be self, wa, tg, all, or none."));h.notify=b}else if(g==="when"){let b=y==="state-change"?"state-change":y==="always"?"always":null;if(!b)return J(p("when must be always or state-change."));h.notifyWhen=b}else return J(p("set supports: notify, when"));return Lt(Eo(f,h)),ot(),J(p(`Updated ${h.name} ${g}=${y}.`))}let l=n.match(/^(?:rm|remove)\s+(\S+)\s*$/i);if(l){let u=l[1],d=Ge(),m=qn(d,u);return m?(xl(m.id),Lt(Xd(d,u)),ot(),J(p(`Removed watch rule "${u}".`))):J(p(`Unknown rule "${u}".`))}let c=n.match(/^(pause|stop|resume|enable|disable)\s+(\S+)\s*$/i);if(c){let u=c[1].toLowerCase(),d=c[2],m=Ge(),f=qn(m,d);if(!f)return J(p(`Unknown rule "${d}".`));RE(f,u==="stop"?"pause":u),Lt(Eo(m,f)),ot();let g=u==="stop"?"paused (stop)":`${u}d`;return J(p(`${f.name}: ${g}.`))}return J(p("Unknown /watch command. Try /watch help"))}ue();Kn();Yn();function yw(){return p(["Tunnel commands:",'/tunnel signup --email "you@example.com" --password "yourpass"','/tunnel signup --phone "+1234567890" --password "yourpass"','/tunnel login --email "you@example.com" --password "yourpass"','/tunnel login --token "<token>" [--relay <url>]',"/tunnel logout","/tunnel status [--relay <url>]","/tunnel http <port> [--name <slug>] [--host <addr>]","/tunnel tcp <port> [--name <slug>] [--host <addr>]","/tunnels","/tunnel stop <id|slug>"].join(`
559
+ `))}async function Ku(e,t,n){let o=Yg(e);if(o.kind==="help")return yw();if(o.kind==="error")return p(o.message);if(o.kind==="signup"){let r=o.email?.trim()||"",s=o.phone?.trim()||"",i=o.password||"";if(!r&&!s)return p('Usage: /tunnel signup --email "you@example.com" --password "yourpass"');if(i.length<8)return p("Password must be at least 8 characters.");let a=v(),l=o.relayUrl?.trim()||vt(a.tunnelRelayUrl||Le),c=await $a(l,{...r?{email:r}:{},...s?{phone:s}:{},password:i});return c.ok?(Ft({token:c.token,...o.relayUrl?.trim()?{relayUrl:o.relayUrl.trim()}:{}}),p("Account created. Token saved.")):p(`Signup failed: ${c.error}`)}if(o.kind==="login"){if(o.email||o.phone){let i=o.password||"";if(!i)return p('Usage: /tunnel login --email "you@example.com" --password "yourpass"');let a=v(),l=o.relayUrl?.trim()||vt(a.tunnelRelayUrl||Le),c=await Ma(l,{...o.email?{email:o.email}:{},...o.phone?{phone:o.phone}:{},password:i});return c.ok?(Ft({token:c.token,...o.relayUrl?.trim()?{relayUrl:o.relayUrl.trim()}:{}}),p("Logged in. Token saved.")):p(`Login failed: ${c.error}`)}let s=o.token?.trim();return s?(Ft({token:s,...o.relayUrl?.trim()?{relayUrl:o.relayUrl.trim()}:{}}),p("Tunnel token saved on this host (not shown). If OMNISH_TUNNEL_TOKEN is set in the gateway environment, it overrides the file until unset.")):p('Usage: /tunnel login --token "<token>" [--relay <url>]')}if(o.kind==="logout")return Ks(),p("Tunnel token file removed. Unset OMNISH_TUNNEL_TOKEN in the gateway environment if you rely on it.");if(o.kind==="status"){let r=v(),s=o.relayUrl?.trim()||vt(r.tunnelRelayUrl||Le),i=Dt(),a=!!process.env.OMNISH_TUNNEL_TOKEN?.trim(),c=!!Zt()?.token?.trim(),u=await Ra(s,i),d=[`Relay: ${s}`,`Token: ${i?"configured":"missing"}${a?" (OMNISH_TUNNEL_TOKEN)":c?" (tunnel-auth.json)":""}`,`Health: ${u.healthOk?`ok${u.healthVersion?` (version ${u.healthVersion})`:""}`:"fail"}`,`Control (WSS): ${u.controlOk?"auth ok":"fail"}`,`Active tunnels: ${n.getActiveCount()}`];return!u.ok&&u.error&&d.push(`Detail: ${u.error}`),p(d.join(`
559
560
  `))}if(!t.tunnelEnabled)return p("Chat tunneling is disabled. Use /config set tunnelEnabled true (then expose with /tunnel http \u2026).");if(o.kind==="list"){let r=n.list();return r.length===0?p("(no active tunnels)"):p(r.map(s=>`${s.id} ${s.kind} ${s.status}
560
561
  ${s.publicUrl||"(pending)"}
561
562
  ${s.localHost}:${s.localPort}`).join(`
@@ -563,39 +564,39 @@ Command: ${c.command}`)}if(a==="apply"){let l=_c(n,t);return l.ok?p(`[board] app
563
564
  `))}if(o.kind==="stop"){let r=await n.stop(o.target);return r?p(`Stopped tunnel ${r.id}.`):p(`No active tunnel matched "${o.target}".`)}if(o.kind==="expose"){let r=await n.expose(t,o.options);return p(`${r.kind.toUpperCase()} tunnel active
564
565
  public: ${r.publicUrl}
565
566
  local: ${r.localHost}:${r.localPort}
566
- id: ${r.id}`)}return mw()}var fw={version:1,generatedAt:"2026-05-30T23:29:30.757Z",repoUrl:"https://github.com/eligapris/omnish/blob/main",entries:[{id:"docs-advanced-implementation",path:"docs/advanced/implementation.md",title:"Implementation Guide - omnish",summary:"For contributors and developers who want to understand and extend omnish.",sections:[{title:"Development Setup",body:"Prerequisites Node.js >= 20 pnpm package manager Native build tools (make, g++, etc.) Getting Started ```bash git clone https://github.com/eligapris/omnish.git cd omnish pnpm install"}],keywords:["implementation","guide","omnish","for","contributors","and","developers","who","want","to","understand","extend","development","setup"],relatedCommands:["/omnish","/github","/eligapris","/wa","/inbound","/tg","/gateway","/signal","/outbound","/index","/config","/stats"]},{id:"docs-advanced-troubleshooting",path:"docs/advanced/troubleshooting.md",title:"Troubleshooting Guide - omnish",summary:"Common issues and solutions for omnish.",sections:[{title:"Quick Reference",body:`Symptom Common Cause Solution ---------------------------------- ----------------------------------------- -------------------------------------------------------------------------- Connection failed Network issues Check connectivity "Not in allowlist" Wrong format Use E.164 format Sessions won't start Resource limits Check session limits Messages cut off Size limits Adjust config Telegram not working Bot token Verify token Attached: probe fails / WS 400 Proxy routes to wrong port Route , to control port 8788; Attached: online but no commands Platform allowlist / routing Dashboard allowFrom; relay logs Message yourself: complete silence Older builds dropped all traffic Update omnish + relay; send after upgrade`},{title:"Attached / platform mode",body:"Symptom: fails or errors with WebSocket Solutions: Reverse proxy must forward , , , , to relay control port (8788), not the HTTP edge (8787). See tunnel relay README. Run before . Confirm URL and token: . Symptom: Device shows online on dashboard but WhatsApp commands do nothing Solutions: Set allowFrom on the platform dashboard (attached mode uses platform policy, not local ). Check relay logs for with your device id. Only one should hold the device WebSocket per token. WhatsApp LID chats: ensure relay and CLI are up to date; use on the device and look for vs . with correct in logs but lists that number: the platform stores allowlist phones as digits only (CLI shows for display). Older CLI builds compared to raw digits and always denied \u2014 update omnish so normalizes platform entries. Symptom: Message yourself (or self-chat) \u2014 commands get no reply at all (not even \u201CNot allowlisted\u201D) Cause: WhatsApp marks messages you send from your phone as on linked devices. Older omnish builds ignored all upserts, so the documented Message yourself flow never reached the gateway. Solutions: Upgrade both the CLI ( ) and the platform relay ( ) to a version that tracks outbound message ids and routes allowlisted non-echo traffic. Confirm your number is on the platform allowlist (dashboard \u2192 Save allowlists). Run , then ; send or in Message yourself. Relay logs should show after you send a command. If you see nothing, WhatsApp may still be disconnected on the platform \u2014 check dashboard WhatsApp status. Standalone mode: same behavior applies; use and on the linked host. Symptom: warning at attach Solutions: Fix token, URL, or platform database (MongoDB on self-hosted relay). Until works, attached mode falls back to local allowlists. Full setup: Platform attached mode. Complete API/CLI/dashboard: Platform reference. WhatsApp link / unlink on platform Symptom Fix ------------------------------- ------------------------------------------------------------------------------------------------- QR never appears Dashboard Link WhatsApp or ; check relay logs Stuck after logout Reconnect on dashboard or then link again Import fails Stop local ; use Linked but dashboard shows idle with autostart; ensure volume persists Allowlists and persistence Symptom Fix -------------------------------------- ------------------------------------------------------------------------------------- Dashboard does not remember allowlists Relay needs ; click Save allowlists CLI changes not reflected on device Wait up to 5 min or restart (policy refresh) Telegram token missing in UI Expected \u2014 token is not returned; leave field empty and Link to reuse saved token"},{title:"Installation Issues",body:"Native Module Build Failures Symptom: Build errors during Solutions: ```bash"}],keywords:["troubleshooting","guide","omnish","common","issues","and","solutions","for","quick","reference","attached","platform","mode","installation"],relatedCommands:["/help","/wa help","/tg help","/platform","/v1","/control","/dashboard","/auth","/contrib","/tunnel-relay","/readme","/me failed","/me","/guides","/platform-attached-mode"]},{id:"docs-architecture-communication-layer-model",path:"docs/architecture/communication-layer-model.md",title:"Communication layer and omnish CLI \u2014 unified model",summary:"Single source of truth for how omnish works today and how the optional hosted communication layer fits. Implementation sketches: Communication layer \u2014 API & ops, Gateway config precedence.",sections:[{title:"Summary",body:"CLI installs anywhere ( ) and works without the hosted layer (standalone mode). Optional hosted communication layer exposes WhatsApp, Telegram, and future surfaces through an API. Users link messengers once on the platform. Many devices (Docker, VM, VPS, bare metal) run the CLI with a device token and talk to that layer over a long-lived channel. One messenger connection \u2192 many devices: the platform routes chat to the right attached CLI; replies return through the same layer. That single connection is what makes the platform useful for fleets and containers. Shell execution stays on each device. The communication layer is not a remote shell; the CLI is the agent (shell, PTY, files, tunnels) on the box."},{title:"Two modes",body:"Mode When Where messengers connect CLI role -------------- ------------------------------------------------------------ ------------------------------------- ----------------------------------------------------------------------------------------- Standalone No platform URL + token (default) On the same host as Full gateway: , , Attached + (or config / aliases) On the hosted communication layer Local executor + WebSocket client: register device, receive routed messages, send replies Both modes: allowlist and shell authority on the device. The platform may store policy in a dashboard, but the CLI enforces who may trigger commands before running shell on that host. Standalone (today) Documented in the README and Quick start. Baileys / Telegram bot clients run inside the gateway process; credentials live under . Attached (implemented) User links WhatsApp/Telegram on the platform dashboard (or ) \u2014 once per account. User sets platform URL + account token on each machine ( or env). On the target: , then . CLI opens WebSocket (fallback ), handles inbound messages like the standalone router, executes locally, posts replies. Setup: Platform attached mode. Full API/CLI/dashboard: Platform reference. No per-container WhatsApp QR is required for the default path."},{title:"Docker example (attached mode)",body:"A team runs an app in Docker and wants chat-driven ops inside that container without running Baileys in the image: Messengers stay on the communication layer; the container only needs outbound HTTPS to the layer API. See Docker gateway golden path."},{title:"Environment contract",body:"Purpose Canonical Also accepted --------------------------------- --------------------- ---------------------------------------------- Platform base URL , Account token (tunnel + attached) , Optional device id in config Config file keys: ( ), ( ), ( ). Precedence: env \u2192 \u2192 tunnel auth file. Implementation: ."},{title:"Security boundaries",body:"Concern Standalone Attached -------------------------------- ----------------------------- --------------------------------------------------------------- Who runs shell Local gateway Local CLI on device Messenger secrets on gateway host Communication layer (connectors) Stolen device token N/A Risk to that attach point; revoke in dashboard Platform sees message content N/A (direct to gateway) Yes, for routing \u2014 document retention and policy Remote shell from platform alone No No \u2014 requires valid device token + allowlisted sender on device Never echo full tokens in chat replies (same as tunnel tokens today)."},{title:"Relation to this repository",body:"Component Status --------------------------------------------------------------- -------------------------------------------------------------- Standalone gateway ( , ) Implemented in Attached mode client ( , platform WebSocket) Implemented in , Hosted communication layer (relay + dashboard) Implemented in (deploy separately)"},{title:"See also",body:"Platform attached mode \u2014 connect, link, configure, run Platform reference \u2014 complete API, CLI, dashboard, persistence Vision \u2014 hosted communication layer \u2014 short product summary Platform layer spec \u2014 API sketch, tokens, threat model Gateway config precedence \u2014 config merge in attached mode Relay operator README \u2014 deploy, paths, dashboard docs/ideas/ \u2014 historical voice-note inputs"}],keywords:["communication","layer","and","omnish","cli","unified","model","single","source","of","truth","for","how","works","today","the","optional","hosted","fits","implementation","sketches","api","ops","gateway","config","precedence","summary","two","modes","docker","example","attached","mode","environment","contract","security","boundaries","relation","to","this","repository","see","also"],relatedCommands:["/readme","/guides","/quick-start","/telegram on","/platform","/device","/control","/platform-attached-mode","/cli","/dashboard","/platform-reference","/tunnel"]},{id:"docs-architecture-gateway-config-precedence",path:"docs/architecture/gateway-config-precedence.md",title:"Gateway configuration precedence",summary:"How effective configuration is computed. See Communication layer model for standalone vs attached modes.",sections:[{title:"Sources (ordered low \u2192 high priority)",body:"Standalone ( without platform token) Compiled defaults \u2014 values baked into the binary when a key is unset. Local \u2014 under . Environment variables \u2014 e.g. , . Chat \u2014 allowlisted senders; same trust as shell. Higher layers win per key. Attached ( with platform URL + token) Compiled defaults Local \u2014 host paths, shell, tunnel, jobs, etc. Platform account ( ) \u2014 wins for policy keys: , , (derived from linked connectors on the platform). Not written back to disk by default. Environment variables \u2014 still override local file for host keys; do not replace platform allowlists unless you also change them on the dashboard. Chat \u2014 updates local file only in attached mode; platform allowlists remain authoritative for inbound from messengers on the layer. If fails at connect, the CLI logs a warning and uses local for allowlists (or partial data from the WebSocket ack: + connectors only)."},{title:"Keys that stay host-only (never taken from platform)",body:", job limits, sync paths, webhook ports/tokens on the device Baileys auth paths, local tunnel client settings WhatsApp session blobs and Telegram bot tokens in attached mode (connectors run on the platform)"},{title:"Keys owned by the platform in attached mode",body:", \u2014 set on the dashboard; device enforces the platform copy on inbound WebSocket messages. \u2014 derived from which connectors are linked on the platform ( , , or )."},{title:"Merge algorithm (attached inbound)",body:"```text effective = loadConfig() # local host + defaults if platform snapshot loaded: effective.allowFrom = platform.allowFrom effective.telegramAllowFrom = platform.telegramAllowFrom effective.gatewayMode = platform.gatewayMode"}],keywords:["gateway","configuration","precedence","how","effective","is","computed","see","communication","layer","model","for","standalone","vs","attached","modes","sources","ordered","low","high","priority","keys","that","stay","host-only","never","taken","from","platform","owned","by","the","in","mode","merge","algorithm","inbound"],relatedCommands:["/v1","/me","/config","/guides","/platform-reference","/config set","/tokens on","/platform","/account-sync","/default-device"]},{id:"docs-architecture-overview",path:"docs/architecture/overview.md",title:"Architecture Overview - omnish",summary:"",sections:[{title:"Executive Summary",body:"omnish is a secure, deterministic messaging-to-shell gateway that bridges messaging platforms (WhatsApp, Telegram) to system shell access. The architecture follows a thin transport adapter pattern with a unified core, explicit security controls, and comprehensive session management. Local control plane: When the gateway process ( ) is up, it may expose a localhost-only control endpoint (metadata in ) so a separate process can request outbound file sends that reuse the same Baileys/grammY sessions\u2014avoiding a second login. This is optional machinery for CLI ergonomics; inbound chat traffic still flows through the transports above."},{title:"Key Architectural Principles",body:"Thin Transport Adapter Pattern Transport Layer: Platform-specific implementations (WhatsApp, Telegram) Adapter Layer: Normalizes all inputs to format Core Layer: Unified business logic and message routing Persistence Layer: Configuration and state management Deterministic Behavior No AI or agent layer Rule-based message routing Predictable command precedence Explicit state management Security-First Design Explicit allowlists required No anonymous access Per-peer session isolation Minimal attack surface"},{title:"System Architecture",body:"High-Level Diagram Component Responsibilities Layer Component Responsibility --------------- --------------- -------------------------------------------- Transport WhatsApp WebSocket lifecycle, QR auth, media handling Telegram Long polling, bot API, formatting Adapter Inbound Normalize messages to Outbound Platform-specific message sending Core Gateway Transport multiplexing, lifecycle management Router Command dispatch with precedence rules Session Manager Per-peer working directories and state Apps Manager Interactive PTY session lifecycle Job Manager Background process execution Persistence Config Configuration loading and validation Sessions JSON-based state persistence Files Log files, session output, job output"},{title:"Data Flow Architecture",body:"Message Processing Pipeline Command Precedence Flow Messages are processed in strict precedence order: Free Shell Toggle: / Sync Commands: (e.g., ) System Commands: (e.g., , ) App Shorthand: (e.g., ) Free Shell Mode: Plain text execution Attached App: Forward to focused PTY session Help Fallback: Display help message"},{title:"Security Architecture",body:"Allowlist System Peer Key Model WhatsApp: or normalized phone number Telegram: Hashed for storage: SHA-1 prefix for log directories Session isolation: Each peer has separate state Security Boundaries Transport Boundary: Platform-specific authentication Authorization Boundary: Allowlist validation Execution Boundary: Command and process isolation Data Boundary: Per-peer state separation"},{title:"Session Management Architecture",body:"Session Context Persistence Session Lifecycle Load: From JSON storage or create default Update: Persist changes immediately Isolate: Per-peer separation with migration support Cleanup: Remove on session destruction"},{title:"Transport Architecture",body:"Transport Interface All transports implement the same core interface: Transport-Specific Features Transport Authentication Message Limit Features --------- -------------- ------------- -------------------------------- WhatsApp QR code 3500 chars Media download, auto-reconnect Telegram Bot token 4096 chars HTML formatting, webhook support"},{title:"Output Processing Architecture",body:"Output Flow Output Processing Pipeline Buffer: Collect output chunks Debounce: Wait for output completion ( ) Chunk: Split into transport-sized pieces Format: Strip ANSI, add prefixes, etc. Send: Deliver via appropriate transport"},{title:"State Management Architecture",body:"State Types Configuration: Global settings Session Context: Per-peer state Job State: Background job metadata App State: Interactive session state Persistence Strategy State Type Format Location Access Pattern ---------- ------ ------------------------- --------------------------------- Config JSON Load at startup, update on change Sessions JSON Load on demand, append updates Jobs Files Streaming write, read on demand Apps Files Streaming write, read on demand"},{title:"Error Handling Architecture",body:"Error Categories Transport Errors: Connection issues, timeouts Validation Errors: Invalid input, malformed commands Execution Errors: Command failures, process timeouts Resource Errors: Memory limits, session limits Error Handling Flow Capture: Error detected at source Log: Structured logging with context Notify: User-friendly error message Recover: Graceful degradation where possible"},{title:"Extension Points",body:"Transport Extension Command Extension Configuration Extension"},{title:"Performance Considerations",body:"Memory Management Session Limits: Prevent unbounded growth Output Buffering: Size-limited buffers Cleanup: Proper resource disposal Network Optimization Message Chunking: Minimize API calls Connection Reuse: Persistent connections Backoff Strategy: Exponential backoff for failures I/O Optimization Async Operations: Non-blocking file I/O Lazy Loading: Load data on demand Output Streaming: Real-time output delivery This architecture overview provides the foundation for understanding omnish's design principles and implementation patterns."}],keywords:["architecture","overview","omnish","executive","summary","key","architectural","principles","system","data","flow","security","session","management","transport","output","processing","state","error","handling","extension","points","performance","considerations"],relatedCommands:["/grammy sessions","/inbound","/gateway","/outbound","/jobs","/apps","/command","/bg","/job","/pty","/config","/sessions"]},{id:"docs-architecture-platform-layer-spec",path:"docs/architecture/platform-layer-spec.md",title:"Communication layer \u2014 API and operations sketch",summary:"Implementation detail for the unified model in Communication layer model. The attached CLI client is implemented in and . The hosted relay (connectors, dashboard, routing) lives in .",sections:[{title:"Goals",body:"Messenger connectors on the layer: WhatsApp, Telegram, others \u2014 linked once per workspace from a dashboard. Device registry and routing: many CLIs attach with ; inbound chat is routed to the correct device; replies return through the layer. Token issuance and lifecycle from the dashboard (create, rotate, revoke). Minimal env on each device: + (legacy aliases: , , ). Non-goals: Running user shell or PTY in the cloud. Replacing local enforcement of allowlists before command execution on each device. Requiring the hosted layer for open-source standalone use ( on the host without env)."},{title:"Operating modes (messenger termination)",body:"Mode Messenger clients Device (CLI) -------------- ------------------------------------------------------------- -------------------------------------------- Standalone On gateway host ( , Baileys / Telegram bot today) Same process as messengers Attached On communication layer (connectors) CLI only: API/WebSocket client + local shell See Communication layer model \u2014 Two modes."},{title:"Threat model (summary)",body:"Asset Risk Mitigation ----------------------------- ----------------------------------- -------------------------------------------------------------------------------------------------- Device token Theft \u2192 control of one attach point Short TTL, rotation, revoke; scoped to one device slot Workspace / dashboard session Account takeover Strong auth, audit log on connector and device changes Message content on layer Privacy / retention Policy, encryption in transit, minimal logging; no training on content by default (product policy) Signed config blob Forged defaults on device Signing keys, , host-only keys; see Gateway config precedence Chat as secret channel Token pasted in DM Never echo full secrets in replies Trust boundary: The communication layer routes messages and holds connector credentials in attached mode. Each CLI enforces allowlists and runs shell locally. Layer compromise must not run shell on a device without a valid device token and allowlisted sender on that device."},{title:"Token scopes (recommended)",body:"Scope Purpose Typical lifetime ------------------- ------------------------------------------------------ --------------------- Long-lived channel for one CLI attach point Rotatable; per device One-time or short exchange when creating a device slot Minutes Fetch non-secret config subset for a device Hours\u2013days Relay bearer (may be issued by layer) Per relay policy Human dashboard (not for CLI) Session Rules: narrow scopes, stable token ids for revocation, no user-facing \u201Cgod\u201D token."},{title:"Implemented API (this repository)",body:"The relay implements a single-account model (not multi-workspace). Authoritative route list, request bodies, and CLI commands: Platform reference Summary: Area Implemented paths -------- ------------------------------------------------------------------ Auth , Account , , Devices , Telegram WhatsApp , , , Attach , UI"},{title:"Future API sketch (not implemented)",body:"The following were early design notes; do not assume they exist on the relay today: Multi-workspace ( ) Separate device tokens per slot (today: one account bearer token) , scoped bootstrap tokens SSE alternative to WebSocket"},{title:"Compliance and logging",body:"Attached mode: layer may process message content for delivery; document retention and access in privacy policy. Avoid logging message bodies in application logs by default."},{title:"Relation to this repository",body:"Communication layer service: (deploy separately; MongoDB + dashboard). CLI attached mode: , \u2014 WebSocket client + local shell. Standalone: / on the same host (unchanged when platform env is unset). Operator guides: Platform attached mode, Platform reference."}],keywords:["communication","layer","api","and","operations","sketch","implementation","detail","for","the","unified","model","in","attached","cli","client","is","implemented","hosted","relay","connectors","dashboard","routing","lives","goals","operating","modes","messenger","termination","threat","summary","token","scopes","recommended","this","repository","future","not","compliance","logging","relation","to"],relatedCommands:["/platform","/gateway","/attached","/tunnel-relay","/contrib","/guides","/platform-reference","/platform-attached-mode","/websocket client","/auth","/signup","/login"]},{id:"docs-architecture-routing",path:"docs/architecture/routing.md",title:"Message Routing Architecture - omnish",summary:"",sections:[{title:"Introduction",body:"The message routing system is the heart of omnish, responsible for determining how incoming messages are processed and executed. It follows a deterministic precedence model with clear rules for each message type."},{title:"Routing Overview",body:"Message Flow Core Components Allowlist Validator: Ensures user has permission Message Normalizer: Converts transport-specific formats Session Loader: Retrieves per-peer state Router: Applies precedence rules and dispatches Executors: Command-specific handlers"},{title:"Message Precedence Rules",body:"Precedence Hierarchy Messages are evaluated in strict order from highest to lowest precedence: Free Shell Mode Toggle ( / ) Synchronous Commands ( ) System Commands ( ) App Session Shorthand ( ) Attached App Sessions (when focused and running) Lone-URL auto-download ( or LLM off \u2192 background ) Free Shell Mode (when enabled) Chat LLM fallback (optional plain-text handler when configured) Help Fallback ( ) Rule Implementation"},{title:"Command Types and Handling",body:"Free Shell Mode Toggle Purpose: Enable/disable direct shell execution Pattern: or Synchronous Commands Purpose: Execute shell commands immediately Pattern: System Commands Purpose: Control omnish functionality Pattern: App Session Shorthand Purpose: Quick interaction with app sessions Pattern: Free Shell Mode Purpose: Execute any text as shell command Pattern: Plain text (when enabled) Attached App Sessions Purpose: Send input to focused PTY session Pattern: Plain text (when session focused)"},{title:"Special Cases and Edge Handling",body:"Shortcut Expansion After stripping the sync prefix ( by default) or when an unknown slash is not a built-in command, omnish calls : only \u2014 expand the stored body (legacy bare shortcuts). \u2014 if the body contains , substitute trimmed input for every placeholder; if the placeholder is present but input is missing, reply with a usage error. Bodies without ignore extra words after the name. Expansion re-enters once with (no nested shortcut expansion). Command Precedence Override Certain commands always take precedence: Error Handling"},{title:"Session Integration",body:"Session Context The router uses session context for state: Session Management"},{title:"Transport Integration",body:"Peer Key Normalization Transport-Specific Handling"},{title:"Performance Considerations",body:"Optimization Strategies Session Caching: Cache frequently accessed sessions Command Caching: Cache command results for identical inputs Output Debouncing: Buffer output to reduce message spam Lazy Loading: Load sessions only when needed Concurrency Handling"},{title:"Extension Points",body:"Adding New Command Types Custom Command Handlers"},{title:"Fleet and config slash commands",body:"The authoritative dispatch order for messages that start with is implemented in (not the pseudocode snippets earlier in this doc). Highlights: runs before fleet aliases so paths like never collide with . Fleet commands accept , , or ( ): only a standalone token or + rest matches \u2014 paths such as do not match the shortcut. (also , ) sets ; reapplies config while is active. See Cluster and chat configuration for behavior and Configuration guide for fields."},{title:"Testing and Validation",body:"Unit Tests Integration Tests This message routing architecture ensures predictable, efficient processing of all incoming messages while maintaining security and session isolation."}],keywords:["message","routing","architecture","omnish","introduction","overview","precedence","rules","command","types","and","handling","special","cases","edge","session","integration","transport","performance","considerations","extension","points","fleet","config","slash","commands","testing","validation"],relatedCommands:["/command","/dl","/dl job","/disable direct","/name","/new","/path","/router","/config","/config set","/computers","/pcs"]},{id:"docs-architecture-security",path:"docs/architecture/security.md",title:"Security model",summary:"omnish bridges WhatsApp and/or Telegram direct messages to shell commands on your machine. Security is explicit allowlists plus local filesystem and process boundaries. There is no cloud relay for commands: whoever can send messages as an allowed identity can execute code as the OS user running .",sections:[{title:"Trust boundaries",body:"Allowlisted identities are credentials. If an attacker can spoof or compromise an allowed WhatsApp number or Telegram user id, they get the same power as you gave on that host. Wildcards are rejected. must never contain . Secrets on disk: WhatsApp session material lives under the data directory ( / / legacy ). Telegram bot tokens live in or . Restrict permissions so other Unix users cannot read them. Chat config: lets allowlisted users edit from chat (same trust as running shell commands). Do not treat DMs as \u201Clow privilege.\u201D Multiple Telegram bots: If and several hosts run Telegram, use a different bot token per host; one token cannot be long-polled twice (see finding in ). Interactive terminal ( ): Same trust as running a shell on that machine. It is not gated by WhatsApp/Telegram inbound allowlists\u2014those lists still apply to messages arriving through the gateways. Gateway control ( ): Written only while is active; carries localhost connection info and a token so can request outbound media through the existing gateway. Anyone who can talk to 127.0.0.1 as your user could misuse it\u2014treat host-local access like filesystem access. : Sends files to arbitrary WhatsApp numbers or Telegram chats through your linked session, comparable to sending those files manually from the linked WhatsApp device or bot\u2014powerful and intentional. Tunneling ( , when ): Publishes public URLs to local HTTP/TCP ports. Anyone with the URL can reach the forwarded service; chat tunneling is gated by the same allowlists as shell commands."},{title:"Automated posture checks",body:"The same rules run in three places: Surface How ------- ------------------------------------------------------------------------------------------------------------------------- CLI (plain text) or Chat , , Startup refuses to start if any error-severity finding is present; warnings are printed but do not block Automated checks cover configuration and local Unix permissions. They do not replace firewall rules, SSH hardening, malware scanning, or review of who physically accesses the machine. Severity levels error \u2014 Blocks until fixed (or until you change / token so the check no longer applies). warn \u2014 Shown on startup and in reports; you should understand and usually fix. info \u2014 Informational (for example, env var overrides)."},{title:"Finding codes and remediation",body:"Code Severity Meaning What to do ---------------------------- -------- --------------------------------------------------------------------------------- --------------------------------------------------------------- error contains Remove from in . error Telegram transport enabled but no token Set in config or . error Configured binary does not exist Install the shell or point at a valid absolute path. error is but path empty Set or change mode. error Fixed receive path not absolute Use an absolute . warn WhatsApp enabled, empty warn Telegram enabled, empty warn is true Set to unless you trust every recipe. warn is not an absolute path Use e.g. in config. warn Could not stat Fix permissions/path. warn group/world readable or writable on config file. warn Process UID is root Run as a normal user when possible. warn Data directory group/world accessible on data dir ( or default ). warn Jobs directory group/world accessible (only checked if data dir is already tight) on the jobs directory under the data dir. warn WhatsApp auth dir readable by others on under the data dir. info set; overrides config Unset env if you want config file to win. info Cluster + Telegram: same token must not be used on two running gateways Use one bot per host or run Telegram on fewer machines. Unix permission checks are skipped on Windows."},{title:"JSON output for automation",body:"prints: Exit code if any error-severity finding exists (same as plain text)."},{title:"Related commands",body:"\u2014 Includes a one-line security summary. In chat: for a short hardening checklist. See also the user guide Security section in User Guide."}],keywords:["security","model","omnish","bridges","whatsapp","and/or","telegram","direct","messages","to","shell","commands","on","your","machine","is","explicit","allowlists","plus","local","filesystem","and","process","boundaries","there","no","cloud","relay","for","whoever","can","send","as","an","allowed","identity","execute","code","the","os","user","running","trust","automated","posture","checks","finding","codes","remediation","json","output","automation","related"],relatedCommands:["/security help","/security summary","/or telegram","/config set","/telegram","/sendto","/tunnel","/tcp ports","/security","/security tips","/run","/bin","/bash"]},{id:"docs-features-background-jobs",path:"docs/features/background-jobs.md",title:"Background jobs \u2014 omnish",summary:"Background jobs run a single shell command asynchronously in the current chat\u2019s working directory while you keep using the chat. Output is appended to a log file under your data directory; you can pull the last N lines or only new bytes since your last for that job in this chat.",sections:[{title:"Commands (implemented)",body:"Command Meaning ----------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------- Start a background job; reply includes an 8-char job id and hints for and . \xB7 \xB7 Same as , but stores a name you can use instead of the hex id with , , and . \xB7 Start a job that sends a completion notification to your chat when it exits (includes exit code, duration, and command summary). Combine flags: named job with completion notification. List recent jobs (up to 20), newest first: id (and name if set), status ( \\ \\ ), exit code, duration, command preview. Last lines of the log (default from ; max 500 if you pass a number: or ). Incremental: new log bytes since your last of this job (resolved id) in this chat (per-chat cursor). Send SIGTERM to the running process (or best-effort to the recorded pid if the child already detached). Job ids are 8-character hex (e.g. ). Names are optional labels (letters, digits, , , , up to 64 characters). If several jobs share the same name, / / resolve to the newest (most recently started) matching job. Resolving -shaped tokens: if a job with that id exists on disk, the token is treated as an id; otherwise it is treated as a name (so a name that looks like 8 hex digits still works when no job file uses that id). There is no , , or in chat \u2014 use and / instead. ( exists for gateway shutdown; it is not exposed as a slash command.)"},{title:"Behavior details",body:"Working directory: Same as session cwd ( applies before ). Environment: Inherited from the omnish gateway process (the Node process running ), not from a prior in chat \u2014 each and runs a new shell. Set env vars in the shell that starts the gateway, or put them in the job command line. Timeouts: applies to synchronous commands, not to children. Long-running jobs are not auto-killed by that setting. Logs: and (see )."},{title:"Configuration",body:""},{title:"Job lifecycle (actual statuses)",body:"From : running \u2014 child spawned; log growing. done \u2014 process exited (or spawn error); / recorded in meta. killed \u2014 user ran (SIGTERM); meta updated. Optional field is stored in when you start a job with / ."},{title:"Completion notifications",body:"When you start a job with (or ), the gateway sends a message to your chat as soon as the process exits: The notification includes: Exit code (0 for success, non-zero for failure) or signal name (e.g. SIGTERM) Duration of the run Command that was executed This is useful for long builds, deployments, or test suites where you want to walk away and get notified when it finishes. Combine with for readability: Notifications are sent via the same transport as the chat (WhatsApp or Telegram). If the gateway shuts down before the job finishes, the notification is lost."},{title:"Examples",body:"Same using the printed id:"},{title:"Compared to Cowork",body:"---------------- --------------------------- -------------------------------------- Scheduling One-shot , , , etc. Notifications Opt-in per job ( ) Per task ( + ) Queue / catch-up No Yes (SQLite + pending queue) Typical use Ad hoc long command Recurring or on-demand saved tasks See cowork.md."},{title:"Troubleshooting",body:"No output in chat: Streaming behavior depends on the gateway; use or for the log file. Lost jobs on gateway restart: In-memory handles are cleared; log and meta files on disk may still be present under . Verbose gateway logs: ---"},{title:"Change log \u2014 named background jobs (2026-05-08)",body:"Area Change -------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ; , , , ; . Parse named ; , , accept id or name via ; lists name when set. help and main help bullet updated for names. Unit tests for parsing, validation, and name/id resolution. User-facing docs This file, user-guide.md, quick-start.md, README.md, comprehensive-documentation.md, practical-guide-for-agents.md, CHANGELOG.md."}],keywords:["background","jobs","omnish","run","single","shell","command","asynchronously","in","the","current","chat","working","directory","while","you","keep","using","output","is","appended","to","log","file","under","your","data","can","pull","last","lines","or","only","new","bytes","since","for","that","job","this","commands","implemented","behavior","details","configuration","lifecycle","actual","statuses","completion","notifications","examples","compared","cowork","troubleshooting","change","named","2026-05-08"],relatedCommands:["/bg","/jobs","/tail","/log","/kill","/log abcdef12","/log mybuild","/stats","/history","/kill all","/shell","/src"]},{id:"docs-features-chat-agent-daemon",path:"docs/features/chat-agent-daemon.md",title:"Chat agent daemon (optional)",summary:"When is true and is set, plain inbound chat (that would otherwise hit \u201CNo command matched\u201D or LLM fallback) is queued to a long-lived process on stdin\u2014one daemon per peer by default. The gateway does not stream the child\u2019s stdout/stderr to mobile; the process should send curated updates with (same model as detached ).",sections:[{title:"Enable",body:"Edit on the gateway host: Copy and customize , or point at your own wrapper. ---"},{title:"Notify environment (parity with `/run`)",body:"On spawn, omnish merges the same notify env as recipe/app starts: Variable Purpose ---------------------------- ----------------------------------------------------------------------------------------------- Initiator peer ( / ) Template: Board-style guidance when is true (daemon sessions are treated as muted) Use or on milestones (ack, progress, question, done, blocked)\u2014not every log line. See also Curated mobile notifications. ---"},{title:"Chat commands",body:"Command Action --------------- -------------------------------------------- List running daemons and queue depth Stop daemon for current peer Stop and clear daemon state for current peer Short help ---"},{title:"Related keys",body:"Key Default Purpose ---------------------- ------- ---------------------------------------------------------- Master switch Shell command for (long-lived; reads stdin) One process per peer; = shared daemon Max queued lines per daemon before reject Include on spawn ---"},{title:"See also",body:"Chat LLM fallback \u2014 one-shot subprocess per message System agents and"}],keywords:["chat","agent","daemon","optional","when","is","true","and","set","plain","inbound","that","would","otherwise","hit","no","command","matched","or","llm","fallback","queued","to","long-lived","process","on","stdin","one","per","peer","by","default","the","gateway","does","not","stream","child","stdout/stderr","mobile","should","send","curated","updates","with","same","model","as","detached","enable","notify","environment","parity","/run","commands","related","keys","see","also"],relatedCommands:["/stderr to","/sendto peer","/run","/config","/absolute","/path","/to","/your-chat-agent-wrapper","/chat-agent-wrapper","/scripts","/app starts","/agent-notify"]},{id:"docs-features-chat-llm-fallback",path:"docs/features/chat-llm-fallback.md",title:"Chat LLM fallback (optional)",summary:"When this feature is on, a plain inbound message that would normally get \u201CNo command matched\u201D is handled silently: omnish runs your in the background (with limits and a restricted environment), then sends the subprocess output back to the same WhatsApp or Telegram chat. In , the reply is printed to the terminal instead.",sections:[{title:"Where to change settings",body:"What Where -------------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------- Config file . If is unset, omnish uses (or the legacy tree if does not exist). Template / all keys Repo root \u2014 copy the entries into your real . Confirm data directory Run on the gateway host, or . Edit the file with any text editor on the gateway host (SSH, local console, or your usual config workflow). Valid JSON is required (trailing commas are not allowed). Reload behavior: The gateway calls when handling each inbound message, so changes to fields in normally apply on the next message without restarting . API keys and environment variables: The subprocess receives a filtered copy of the gateway process environment (see below). Keys such as are only present if they were set when the gateway was started. If you add or change API keys in the shell profile or systemd unit, restart so the parent process picks them up. ---"},{title:"How to enable (step by step)",body:"Open (or ) on the machine that runs . Add or merge these fields (defaults shown; enable only when ready): Set to a single shell command passed as the argument to (same pattern as sync commands). Use an absolute path to a wrapper script if the default in the sandbox is too minimal. Smoke test: With the gateway running, send a plain line from an allowlisted chat (not starting with or your , not text). You should get no immediate \u201CNo command matched\u201D reply; after the subprocess finishes, the combined stdout/stderr (trimmed and capped) is sent back. Sandbox / real isolation: omnish runs the command in a dedicated working directory (temp dir under the data directory unless is set) and a reduced env. For Docker, , or other tools, put that logic inside your wrapper and point at it. ---"},{title:"What the subprocess receives",body:"Shell: Config key (e.g. ). Working directory: if non-empty (created if needed); otherwise a new temp directory under the omnish data dir for that run (removed afterward). Stdin (default): The inbound chat text (truncated to ). If is true, stdin is not piped the same way; use (and a PTY) instead. Environment (high level): \u2014 e.g. or \u2014 same payload as stdin (piped mode) Common vars: , , , , , , , , All vars from the parent process Parent vars whose names end with or Place API keys in the gateway environment (systemd , shell before , etc.) so they are inherited and forwarded by the allowlist above. ---"},{title:"When the fallback runs (and when it does not)",body:"Runs only if all of the following hold: is true and is non-empty after trim. The message is plain text that reached the router\u2019s final \u201Cno match\u201D branch: not a command, not shell, not / , not app line, not consumed by a focused session, and free shell mode is off for that peer. Does not run (examples): unknown , , , media-only messages, messages dropped by cluster binding on non-primary hosts, or any path that already returns a normal reply. ---"},{title:"Related keys (reference)",body:"Key Purpose ------------------------ ------------------------------------------------------------------------------------------------ Master switch ( by default). Full command string for . Wall-clock limit (ms). Max size of text passed in (stdin / ). Max captured output (stdout+stderr in piped mode). Use PTY execution (for CLIs that require a TTY); prefer in the script. Fixed cwd; empty = ephemeral dir per run. Implementation checklist and code pointers: chat-llm-fallback-implementation-plan.md. ---"},{title:"Security note",body:"Allowlisted chats already have shell-level trust on the gateway host. Turning this on means accidental plain messages can invoke your command and any API usage inside it. Keep allowlists small, use timeouts and caps, and wrap external agents in a policy you control."}],keywords:["chat","llm","fallback","optional","when","this","feature","is","on","plain","inbound","message","that","would","normally","get","no","command","matched","handled","silently","omnish","runs","your","in","the","background","with","limits","and","restricted","environment","then","sends","subprocess","output","back","to","same","whatsapp","or","telegram","reply","printed","terminal","instead","where","change","settings","how","enable","step","by","what","receives","it","does","not","related","keys","reference","security","note"],relatedCommands:["/config help","/config","/absolute","/path","/to","/your-wrapper","/stderr","/bin","/bash","/apps","/foo","/help","/chat-llm-fallback-implementation-plan"]},{id:"docs-features-cluster-and-chat-config",path:"docs/features/cluster-and-chat-config.md",title:"Multi-host cluster and chat configuration",summary:"This document describes the chat-driven cluster ( , , ) and for viewing or editing from WhatsApp or Telegram.",sections:[{title:"Cluster (per-sender bindings)",body:`Use one WhatsApp phone number with multiple linked devices \u2014 each machine runs once. Every machine that runs will receive the same DMs through WhatsApp multi-device. There is no shared file. There is no Syncthing/NFS to set up. Coordination flows entirely through the WhatsApp chat itself: every reply omnish sends carries an invisible footer that other linked omnish hosts read to learn who is online and which machine each sender is currently bound to. Telegram cannot carry this coordination (different bot tokens cannot see each other), so the per-sender binding still applies on Telegram, but you only get convergence between linked hosts on WhatsApp. How it routes Each allowlisted sender (one phone number / one Telegram id) picks one machine to talk to. Only that machine processes the sender's normal traffic; other linked hosts stay silent for that sender. Two different controllers can independently bind to two different machines without affecting each other. A's only runs on . B's only runs on . Neither sees the other's output. Fleet commands ( , , ) are processed by every linked host so that bindings, status, and help stay reachable even before a sender has bound. Stable identity Each data directory has a file ( ) with a UUID. Display names in the roster come from , or the OS hostname when empty. When you , "alpha" matches against the (or the 8-character node id). Local state Each host keeps a private file at (schema v3): \u2014 short id, label, role, and last-seen timestamp for every host this machine has observed in chat traffic. \u2014 map of sender key ( or ) to the bound machine's short node id, when it was set, and whether it came from a chat command ( ) or config defaults ( ). This file is per host and never shared. Disagreements between linked hosts are resolved by the next footer-stamped message anyone sends to the chat. Commands (WhatsApp / Telegram) may be shortened to or (the bare token matches; does not). Command Action ---------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------ Overview of the per-sender model Bind YOUR messages to that machine. Resolves either an 8-character node id or a . Other senders are not affected. Bind YOUR messages to the local machine (shorthand for ). Show YOUR current binding (machine, source: chat or config). Clear YOUR chat binding. The config default in (if any) takes over again. Every online host replies with its own one-paragraph status (this is the discovery moment \u2014 each host parses the others' footers and updates its peer list). Locally known roster \u2014 only one host responds (the bound one, or a deterministic fallback). Convergence When you send , every linked host runs locally. Only the resolved target host (the one labelled ) sends the confirmation reply; siblings stay silent. The reply carries the cluster footer with , and every sibling observes it via WhatsApp upserts and converges on the same binding for your sender key. Defaults via config You can pre-seed bindings so a controller never has to type on first contact: Keys are sender keys ( or ). Values are the target machine's or its 8-character node id. Chat-set bindings ( ) always win over config defaults; clearing a chat binding ( ) falls back to the config default. CLI prints a short cluster line when is true. The legacy was removed \u2014 bindings need a sender, so use the chat ( ) or the new CLI form. Configuration keys (cluster) See the Configuration guide. Relevant fields: \u2014 display name for THIS machine in the roster \u2014 sender \u2192 machine defaults \u2014 kept for backwards compat, but no longer used for traffic gating Migration from v2 (single global active host) is automatically migrated on first read. The previous field is dropped \u2014 every sender now binds individually. If you relied on the old global-active model, set the equivalent entry for each allowlisted sender in , or send from each controller's chat. The foote`},{title:"Chat configuration (`/config`)",body:"Allowlisted chat users can change many settings without SSH. This is as sensitive as shell access: anyone who can DM as an allowed identity can modify config. Commands Command Action --------------------------- -------------------------------------------------------------- or Help (overview + hint) Snapshot of effective config; telegram bot token masked One whitelisted key Update one key; values may be quoted for paths with spaces List all keys allowed for After changes Save goes to immediately (same normalization as the rest of the app). For or , the gateway runs -style Telegram restart when is up; otherwise send after editing. Changing returns an explicit warning in the reply. Whitelist ( ) The canonical allowlist is in . In chat, prints the same comma-separated list your build supports. Groups include: gateway ( , \u2026); cluster; shell / sync / jobs; apps; files; recipes (including , ); Telegram ( ); service ( ); update checks ( , , , ); tunneling ( , , ); chat LLM fallback ( , , , , , , ). accepts a JSON object value (or / to wipe), e.g. . Not exposed via : , \u2014 use and in chat (or / on the host). Tunnel token is not in : run on the gateway host (or set ). Chat can turn tunneling on with after the token exists. Examples ---"},{title:"Related documentation",body:"Configuration guide \u2014 full file layout and fields Security model \u2014 trust boundaries and Message routing \u2014 slash-command ordering (high level)"}],keywords:["multi-host","cluster","and","chat","configuration","this","document","describes","the","chat-driven","for","viewing","or","editing","from","whatsapp","telegram","per-sender","bindings","/config","related","documentation"],relatedCommands:["/c help","/config help","/computers","/pcs","/config","/nfs","/node-id","/c use","/cluster-local","/cluster","/c here","/c using","/c unuse"]},{id:"docs-features-cowork",path:"docs/features/cowork.md",title:"Cowork \u2014 scheduled and on-demand shell tasks",summary:"Cowork runs saved shell commands on a timer while is active. It uses the same execution model as sync commands ( via your configured ), with and from . There is no AI layer.",sections:[{title:"Prerequisites",body:"A running gateway: (foreground or background). You must be allowlisted (same trust as ). With cluster enabled, only the host bound to your sender processes chat commands; tasks you create are stored on that machine\u2019s data directory."},{title:"Commands",body:"Use or . Send for the short form. Action Example -------------------------------- ---------------------------------------------------------------------------- Add task List Show Run now (on-demand or scheduled) Enable / disable / Remove (also , ) Check in (heartbeat only) Update \u2014 notify condition (see below) \u2014 also send the run log file with each notify \u2014 optional artifact paths (see below) Add syntax Scheduled / on-demand tasks (run a command): Heartbeat tasks (dead-man's-switch, no command): Name: letters, digits, , ; max 32 characters (stored lowercased). Schedule: (or ), , , , (weekday names or \u2013 , Sunday = ), or . For scheduled/on-demand tasks, the command must come after a literal separator. Heartbeat tasks have no command. Duration format (heartbeat): , , , . Minimum interval: 1m. Minimum grace: 30s. Grace defaults to 50% of interval if omitted. Schedules and time zones Fire times use the gateway host\u2019s local timezone (Node\u2019s on that machine). Output logs Each run writes one UTF-8 log file under the task\u2019s (default ). Filenames include a timestamp, task id, and whether the run was scheduled, catch-up, or on-demand. Catch-up Successful scheduled runs are recorded in (per task id and slot time). The scheduler uses that database as the source of truth for what is already done; may still contain a legacy field, which is seeded into SQLite on first open if the DB has no rows for that task. If several slots were missed (gateway down), the next tick runs the command once for the newest due slot and records coalesced rows for older missed slots so the backlog clears in a single execution instead of one run every 30 seconds per missed slot. A slot is only recorded after exit code 0, no timeout, and no terminating signal; failed runs do not advance the watermark (same retry behavior as before). Kinds stored for successful scheduled work: (within 2 minutes of the slot), (late), (satisfied without a separate run during backlog coalescing), and (imported from legacy on upgrade). On-demand queue appends a request to . The scheduler dequeues up to eight entries per tick, one read/write of the queue file per batch, so a crash mid-run does not drop the rest of the queue. Notifications After each run, a short summary can be sent (same routing as other replies: WhatsApp vs Telegram formatting). For scheduled and catch-up runs, the message is one line: The bracketed status appears only when the run did not finish cleanly (timeout, non-zero exit, or signal). For on-demand runs ( ), the message is two lines: Optional lines may follow when attachments fail or when too many artifact files match (see below). Mode Recipients ---------------- ----------------------------------------- (default) Task owner only All entries in (WhatsApp) All entries in Owner plus both allowlists No messages Trust: , , and can message every allowlisted identity. Anyone who can edit tasks (allowlisted senders) can point shell and notifications at powerful actions\u2014same overall model as remote shell. WhatsApp notification targets use normalized JIDs so delivery matches Baileys . Conditional notifications ( ) Control when a notification is sent with : Mode Behavior ------------------ -------------------------------------------------------------------------------------------------------------------------------------------------- (default) Notify after every run Notify only when the command exits with a non-zero code, times out, or is killed by a signal Notify only when the result flips (e.g. ok to fail, or fail to ok). The last-known state is tracked in SQLite so it survives gateway restarts. is useful for tasks that run frequently (e.g. hourly health checks) where you only want to know when something breaks or recovers, not on every successful run. Heartbeat (dead-man's-switch) A heartbeat task does not run a command. Instead, it expects periodic check-ins and alerts when they stop arriving. This creates a task that expects a check-in at least every 1 hour, with a 10-mi"},{title:"Data files",body:"Under (default unless overridden): \u2014 task definitions (atomic replace on save). \u2014 successful scheduled slot completions (watermark for catch-up); WAL mode. \u2014 queued on-demand runs. Directory mode is restrictive ( for where created by the app; task file )."},{title:"Limitations and caveats",body:"Gateway must be up at fire time for scheduled runs; catch-up handles downtime afterward. WhatsApp-only: the cowork scheduler starts before the first successful WhatsApp connection; very early completion notifications to WhatsApp may no-op until outbound is ready. Telegram outbound is typically ready earlier when the bot is enabled. Shared : if several gateways share the same data directory (unusual), each running process could execute the same schedules\u2014use one data dir per machine for normal setups. Cluster: tasks live on disk for the gateway that received commands; they do not sync across hosts."},{title:"See also",body:"Cowork implementation plan (design and maintainer notes) User guide \u2014 Cowork section Security model"}],keywords:["cowork","scheduled","and","on-demand","shell","tasks","runs","saved","commands","on","timer","while","is","active","it","uses","the","same","execution","model","as","sync","via","your","configured","with","from","there","no","ai","layer","prerequisites","data","files","limitations","caveats","see","also"],relatedCommands:["/cowork help","/cw help","/cowork","/cw","/cowork add","/data","/backup","/cowork list","/cowork show","/cowork run","/cowork enable","/cowork disable","/cowork remove"]},{id:"docs-features-device-update-delivery",path:"docs/features/device-update-delivery.md",title:"Update information on installed Omnish devices",summary:"This document is the reference plan for how operators and maintainers can reach already-installed Omnish gateways with version and notice information. It matches what the CLI and gateway implement today.",sections:[{title:"Reality check: there is no silent \u201Cpush\u201D to every machine",body:"Omnish is self-hosted: each install is a process on a user\u2019s machine, talking to WhatsApp/Telegram. There is no central fleet server and no always-on back channel from the project to those hosts unless the host initiates outbound traffic (or an allowlisted user sends a chat command). So 100% delivery in the literal sense (every device, every time, with no user action) is not possible: machines can be offline, firewalled, on air-gapped networks, or running an old binary forever. What is achievable is a reliable, predictable path that works whenever the network and registry are reachable\u2014same practical bar as other CLIs ( , , etc.)."},{title:"Options that were considered",body:"Approach Pros Cons ------------------------------------------------------ ------------------------------------------------------------------------------ ----------------------------------------------------------------------------------------------- npm registry Canonical published version; no custom infra; HTTPS; already used for installs Needs outbound HTTPS; scoped/unpublished forks differ GitHub Releases / API Rich metadata Rate limits; not identical to \u201Cwhat gets\u201D Static JSON on a URL you control ( ) Arbitrary maintainer text (security, migration) You must host and secure expectations (HTTPS only in omnish) In-chat broadcast from \u201Cthe project\u201D Uses existing DM surface There is no project-owned chat to all installs; only allowlisted users can command their host Auto-upgrade in place Hands-off for users High risk (native deps, , Baileys); out of scope for this design doc Chosen combination: npm registry for semver discovery + optional HTTPS JSON for human notices, exposed in the product as , (cached one-liner), , optional background checks when is true, and for the same keys."},{title:"Implemented behavior (source of truth)",body:"(allowlisted chat, gateway running): performs a live GET to (default package name ). If is set to an https URL, fetches JSON (link must also be ). : shows the last in-memory snapshot from the last live or scheduled check (no network). : gateway runs a timer (checks at most every 1 minute internally, but only performs a registry+info fetch when has elapsed, clamped 1h\u20137d). Results are logged with and stored for / . : CLI one-shot check (same fetches as ). : completion text may append \u201CUpdates (last check): \u2026\u201D if a snapshot exists. Configuration keys (also in and ): (boolean, default false) \u2014 privacy-first default. (default 86400000) \u2014 clamped between 1 hour and 7 days. (default ) \u2014 for forks or scoped packages. (default empty) \u2014 optional maintainer notice JSON over HTTPS."},{title:"Maintainer playbook",body:"Publish a new version to npm as today ( bump, publish). Devices that run or have background checks enabled will see the new latest when the registry updates. Optional notice (security advisory, breaking change): host a static JSON file at an HTTPS URL you control; set in docs or tell users to set it via . Do not rely on chat alone to \u201Creach\u201D every install; treat registry + optional URL as the scalable channel."},{title:"Code map",body:"Piece Role ------------------------------------------------------ -------------------------------------------------------------------------------- npm + optional info URL fetch, snapshot, scheduler Numeric compare for \u201Cnewer on npm\u201D Reads running from package root (dev + bundled ) , Schedules checks; ; reload footer status lines, formatted reply Schema defaults and merge"},{title:"Future extensions (not implemented)",body:"Signed notices (e.g. minisign) over . Deprecation warnings via (visible on , not parsed here)."}],keywords:["update","information","on","installed","omnish","devices","this","document","is","the","reference","plan","for","how","operators","and","maintainers","can","reach","already-installed","gateways","with","version","notice","it","matches","what","cli","gateway","implement","today","reality","check","there","no","silent","push","to","every","machine","options","that","were","considered","implemented","behavior","source","of","truth","maintainer","playbook","code","map","future","extensions","not"],relatedCommands:["/updates","/updates cached","/telegram","/latest","/unpublished forks","/gateway","/config set","/registry","/reload","/config keys","/omnish-notice","/check"]},{id:"docs-features-docs-search-from-chat",path:"docs/features/docs-search-from-chat.md",title:"Documentation search from chat",summary:"Find omnish guides by topic from WhatsApp, Telegram, or \u2014without hunting the repo or omnish.dev first. Search is offline (bundled index at build time; no AI layer).",sections:[{title:"Commands",body:"Command Purpose --------------------------- --------------------------------------------------------- or Subcommand list Ranked results (keywords + headings) Repeat the last search list in this chat or Excerpt, GitHub link, and Try: related slash commands Run the primary related help (e.g. ) Legacy aliases: (same as ), \u2192 , . Host terminal (same index): ( still works.) ---"},{title:"Example flow",body:"You are not sure which command exposes HTTP: Pick a result: You get a short excerpt, the doc path, a GitHub link, and lines like . Jump to live help: Same as sending in that chat. ---"},{title:"When nothing matches a slash command",body:"Plain text that looks like a question (contains a space or ) gets a hint on No command matched: Use with your own keywords if the auto-filled phrase is too long. ---"},{title:"Index scope",body:"The build includes guides, features, architecture, and advanced troubleshooting under (not , , or maintainer runbooks). Rebuild the index with: ( and run this automatically.) Overrides for related commands: . ---"},{title:"Related",body:"User guide Message routing Online catalog \u2014 community recipes/apps (separate from docs search)"}],keywords:["documentation","search","from","chat","find","omnish","guides","by","topic","whatsapp","telegram","or","without","hunting","the","repo","dev","first","is","offline","bundled","index","at","build","time","no","ai","layer","commands","example","flow","when","nothing","matches","slash","command","scope","related"],relatedCommands:["/s help","/search help","/s q","/s list","/s show","/s follow","/tunnel help","/search","/docs search","/help search","/features","/tunneling"]},{id:"docs-features-implementation-101",path:"docs/features/implementation-101.md",title:"Tunneling implementation 101",summary:"This document explains how omnish tunneling is built: relay edge, client, CLI, chat integration, configuration, security, and how to exercise it locally.",sections:[{title:"Big picture",body:"Tunneling is a relay + client design. The relay is the public edge; the omnish client on the user machine keeps an outbound WebSocket and forwards traffic to a local port. HTTP uses JSON control messages on the WebSocket. TCP uses JSON for stream open/close and length-prefixed binary frames for payload. Default production relay: . The relay service itself lives under and is deployed separately from the npm CLI package."},{title:"Repository map",body:"Path Role ------------------------------------------------------------------------------ ------------------------------------------------------- Deployable relay: HTTP edge, WSS control, TCP listeners Relay package ( dependency) Shared control message types and binary frame codec Tunnel kinds, records, default relay URL One tunnel: WSS session, local forwarding Active tunnels, limits, stop/stopAll Token and relay URL resolution CLI and chat argument parsing subcommands handlers for the gateway CLI dispatch; gateway shutdown stops tunnels Chat routing for and , , under the data dir Posture findings for tunneling lines when tunneling is enabled Tests: , , , ."},{title:"Relay (`contrib/tunnel-relay/`)",body:"is the tunnel process. Production ships it in one Docker image with Caddy (TLS, wildcard DNS-01) via and . proxies public / to and . Listeners Listener Default Role ----------- --------------- -------------------------------- HTTP edge Public HTTP for tunneled apps Control WSS Authenticated client connections Environment variables: \u2014 base URL shown to users (default ) \u2014 HTTP edge bind port \u2014 control WebSocket bind port / \u2014 TCP tunnel port range \u2014 comma-separated bearer tokens allowed to connect \u2014 per-token tunnel quota Authentication Clients connect with on the WebSocket upgrade. Invalid or missing tokens close the socket. Registration After , the client sends with ( ), , , and optional . The relay assigns a slug (from or random) and replies with including . HTTP routing Local dev: Production-style: when matches that host pattern Fallback: Each slug is bound to the WebSocket session that registered it (not \u201Cany session with the same token\u201D), so multiple tunnels sharing one token each get correct routing. Incoming requests are turned into control messages to the owning client; the client returns . TCP For , the relay binds a port in the configured range and returns . New public TCP connections emit on the control socket; bytes flow over binary frames. State In-memory maps ( , , ). Tunnels disappear when the client disconnects. No cross-replica sticky routing in v1."},{title:"Shared protocol (`src/tunnel/protocol.ts`)",body:"Control messages (JSON on the WebSocket): Session: , , Lifecycle: , , , HTTP: , (optional ) TCP: , Keepalive: , Binary frames (TCP payload): 1 byte frame type ( , , , ) 4 byte stream id (big-endian) 4 byte payload length payload bytes and implement the codec on client and relay."},{title:"Client (`src/tunnel/client.ts`)",body:"represents one active tunnel. Derives from the relay URL ( \u2192 , default path ). Connects with the bearer token. Sends with a random 8-hex . Waits for and stores and . HTTP path: On , issues to , then sends with status, headers, and optional base64 body. TCP path: On , connects locally and pumps bytes via binary frames; or relay close tears down the stream. Lifecycle: Periodic ; on stop, sends and closes the socket. Default target host is unless overrides it."},{title:"Manager (`src/tunnel/manager.ts`)",body:"holds live instances keyed by tunnel id and slug. Resolves relay URL via and token via Enforces from config / for CLI, chat, and gateway shutdown The gateway and CLI share one manager instance from ( )."},{title:"Configuration and secrets",body:"In (non-secret): \u2014 gate chat commands (default ) \u2014 default relay origin (default ) \u2014 max concurrent tunnels on this host (default ) Secrets (not in ): or ( ) via Optional relay override: or in the auth file See and ."},{title:"CLI (`omnish tunnel`)",body:"Implemented in ; wired from . Subcommand Behavior ----------------- ------------------------------------------------------ Save token (and optional relay) to Remove saved token Register HTTP tunnel; foreground unless Register TCP tunnel List active tunnels on this machine Stop one tunnel Relay reachability and auth presence Flags: , , , (see )."},{title:"Chat integration",body:"When is true, handles: / (optional , ) delegates to the shared . Tunnels run inside ; standalone still works without the gateway. Trust model matches : allowlisted chat users can open tunnels as the gateway OS user. Public URL possession is the visitor credential."},{title:"Security",body:"findings: \u2014 chat tunneling on \u2014 chat tunneling on without a token \u2014 non-default documents tunnel URLs as capabilities and the gateway shutdown path that stops active tunnels."},{title:"Local exercise",body:"Install relay deps: Start relay, for example: - - Run a local HTTP server on a port (for example ) Open the printed (path-based on loopback: ) Automated coverage: spins the relay and asserts HTTP end-to-end."},{title:"Out of scope (v1)",body:"Mandatory omnish.dev account or billing Tunnel persistence across client disconnect or HA relay fleet UDP, mesh VPN, or bundled ngrok/cloudflared as the primary path Setup UI for tunnel login (CLI is the v1 configuration surface)"},{title:"Success criteria (from product plan)",body:"+ yields a public URL that serves the local app (with a running relay) exposes a public TCP endpoint to the local port With and the gateway running, allowlisted users get the same URLs from chat and docs describe capability risk clearly"}],keywords:["tunneling","implementation","101","this","document","explains","how","omnish","is","built","relay","edge","client","cli","chat","integration","configuration","security","and","to","exercise","it","locally","big","picture","repository","map","contrib/tunnel-relay/","shared","protocol","src/tunnel/protocol","ts","src/tunnel/client","manager","src/tunnel/manager","secrets","tunnel","local","out","of","scope","v1","success","criteria","from","product","plan"],relatedCommands:["/tunneling","/architecture","/security","/close and","/tunnel","/tunnel-relay","/server","/contrib","/package","/protocol","/src","/types"]},{id:"docs-features-job-board",path:"docs/features/job-board.md",title:"Job board \u2014 digital employee coordination",summary:"The job board is omnish\u2019s work queue for autonomous digital employees: dependencies, coordinator dispatch, supervisor accountability, and host-wide resource limits.",sections:[{title:"Prerequisites",body:"(gateway active). Allowlisted sender for commands. with specialist commands (see example pack)."},{title:"Coordinator (on by default)",body:"While the gateway runs, the coordinator ticks automatically ( defaults to ). Set it to for manual only. Configure specialists: (or edit )."},{title:"Commands",body:"Command Action -------------------------------------------- ------------------------------------------------------------------------------ Human intake; optional , , Status counts plus per-job lines for review/pending/rework Job detail One coordinator + supervisor cycle (also sends status DMs when hooked to chat) Primary agent CLI for registry roles Resource governor snapshot Cancel job Registry names Coordinator onboarding and profiles Human satisfaction after Role-split suggestions Mark work-plan step done (for agent CLIs) is an alias for . Notification config Key Default Meaning ---------------------- ------------ ---------------------------------------- Phase completion messages Start / review / done / failed / blocked"},{title:"Data paths",body:"Path Purpose ----------------------------------- ---------------------------------------- Jobs, audit, slots Specialist commands Profile, skills, flow overrides Concurrency and CPU/RAM/GPU limits , , artifacts"},{title:"Coordinator (each tick)",body:"Unblock jobs whose dependencies are . Run supervisor on jobs in . If capacity allows, dispatch highest-priority runnable job to its specialist command."},{title:"Core competencies (non-optional)",body:"All employees run through the harness ( ). Registry entries use only (legacy alias). omnish seeds , enforces five phases, and notifies on progress. See Digital employees \u2014 core competencies."},{title:"Work plan (specialist discipline)",body:"The harness seeds before the inner agent runs. Specialists mark todos via scripts or . Handoff requires full completion, self-reviews on major steps, and in ."},{title:"Supervisor",body:"After a specialist finishes, the job moves to . The supervisor checks , , and job-plan lines. Outcomes: pass \u2192 ; dependents may unblock. rework \u2192 then reassigned (until ). fail \u2192 ; owner notified."},{title:"Separation of duties",body:"The coordinator does not approve quality; the supervisor does not choose the next assignee or create prerequisite jobs. That split keeps accountability auditable (see in SQLite)."}],keywords:["job","board","digital","employee","coordination","the","is","omnish","work","queue","for","autonomous","employees","dependencies","coordinator","dispatch","supervisor","accountability","and","host-wide","resource","limits","prerequisites","on","by","default","commands","data","paths","each","tick","core","competencies","non-optional","plan","specialist","discipline","separation","of","duties"],relatedCommands:["/guides","/employee-task-flow","/digital-employees","/coordinator-workforce","/board","/employees","/board tick","/board agent","/board add","/board list","/pending","/rework"]},{id:"docs-features-media-commands",path:"docs/features/media-commands.md",title:"Media commands (`/dl`, `/dlf`, `/dlv`, `/tr`, `/edit`)",summary:"Download, transcribe, and edit media from chat \u2014 using yt-dlp, ffmpeg, and optionally openai-whisper on the gateway host.",sections:[{title:"Defaults",body:"Files are sent to chat by default ( ). Background jobs deliver results over the gateway control channel when is active. To only list paths, set:"},{title:"Install tools (host)",body:"Binaries are stored under . Whisper uses . From chat (same trust as shell \u2014 off by default):"},{title:"Chat commands",body:"Command Action --------------------------------------------- ------------------------------------------------------------ Auto: file (HTTP), video (yt-dlp), or HTML\u2192markdown Force HTTP file download (never yt-dlp) Force yt-dlp video download Tool status OS-specific manual install steps Install tools into Whisper transcript + (+ video if URL); background job Trim or convert with ffmpeg; background job flags: / , / , / , / , . How classifies URLs File signals (always HTTP, never yt-dlp): pathname extension ( , , \u2026), path heuristics ( for arxiv, etc.), or from HEAD. Video: yt-dlp probe ( ) \u2014 if yt-dlp has a named site extractor (1000+ sites), download with yt-dlp. HTML page: fetch page and send markdown in chat (large pages may also save a file). Use when auto-detect might guess wrong (e.g. force HTTP on a video URL). Use to force yt-dlp. Save location Config / chat Where files go ----------------------------- --------------------------- set That directory (flat) for this chat Session cwd ( ) Default (flat) No dated subfolders. Share-friendly filenames All download paths normalize saved basenames for easy sharing in chat: ASCII-safe names (unicode stripped or transliterated, emoji removed) Spaces and unsafe characters become Collisions get numeric suffixes ( , ) yt-dlp uses plus a post-download rename pass Whisper sidecars ( , , etc.) inherit the normalized video basename. Background jobs , , , , and run as background jobs ( ). You get a job id immediately; finished files and markdown are sent to chat when the job completes (if is true). Optional flags (stripped before the payload): / \u2014 extra ping when the shell job finishes (exit status) / \u2014 no-op (already background) Step progress When is true (default), multi-step work sends chat messages such as while it runs. Auto-detect URLs When is true (default), a message that is only an or URL runs in the background \u2014 even when free shell ( ) is on. Focused PTY still wins over auto-dl. When chat LLM fallback is off and no LLM shell command is configured, lone URLs also auto-download even if was set to false (paste-a-link intent). Trailing punctuation from chat clients (e.g. ) is stripped before detection. Auto-dl replies with a short \u201CDownloading link\u2026\u201D message instead of a verbose job block. Legacy still works as aliases: / \u2192 , / \u2192 . Other modes show a deprecation hint."},{title:"Config keys",body:"Key Default Purpose -------------------------- ----------- ----------------------------------------------------------------------------------------- Send files to chat after download Allow from chat Lone URL \u2192 auto Output root (empty \u2192 Downloads/Omnish or cwd) yt-dlp cap (0 = none) Max size when sending downloads to chat (0 = no omnish cap; WA/TG limits still apply) Whisper / Transformers model size ( , , , \u2026) (default device, then CPU), , or (whisper CLI only) Per-attempt timeout for whisper/ffmpeg (10 min; max 15 min) , , or (whisper then Transformers.js fallback) When engine fails, retry with Transformers.js if installed On hosts with a busy or small GPU, set to or use a smaller ( , ). For long files that hit timeouts, raise . Transformers.js (no Python): installs under . Set to to use it exclusively, or keep / with (default) to fall back when openai-whisper fails. Step-by-step chat messages during multi-step work / / Binary overrides Older keys in are migrated on load (e.g. \u2192 ). Size limits ( , , ) Two different caps: Key When it applies ------------------ ---------------------------------------------------------------------------------------------------------- Download only (yt-dlp ). = no cap. Sending the saved file to chat after the job finishes. = no omnish cap on standalone . If the file downloads but chat says \u201CN file(s) not sent\u201D with a size, the job succeeded on disk; outbound delivery hit a cap \u2014 not . Standalone (local WhatsApp/Telegram on the host): only (and messenger limits). Attached ( with platform credentials, messengers on the relay): no default platform hop cap. Optional env (bytes; = unlimited) on both the relay and the device gateway caps outbound files over the WebSocket. WhatsApp/Telegram limits still apply upstream. Check values: (or ) for and ."},{title:"Legal and safety",body:"Same allowlist trust as shell commands. Private/local URLs are blocked ( , RFC1918, etc.) when the job runs. Respect copyright and platform terms; omnish only runs tools you install locally."},{title:"See also",body:"Files send/receive \u2014 for manual delivery Background jobs \u2014 , ,"}],keywords:["media","commands","/dl","/dlf","/dlv","/tr","/edit","download","transcribe","and","edit","from","chat","using","yt-dlp","ffmpeg","optionally","openai-whisper","on","the","gateway","host","defaults","install","tools","config","keys","legal","safety","see","also"],relatedCommands:["/dl","/dlf","/dlv","/tr","/edit","/bin","/venvs","/whisper","/config set","/dl install","/dl doctor","/dl setup"]},{id:"docs-features-media-pull",path:"docs/features/media-pull.md",title:"Media pull (`/pull`) \u2014 deprecated",summary:"> Superseded by media commands ( , , ). This page is kept for older releases.",sections:[{title:"Enable",body:"In (or from an allowlisted chat):"},{title:"Install tools (host)",body:"Binaries are stored under . Whisper uses . From chat (same trust as shell \u2014 off by default):"},{title:"Chat commands",body:"Command Action ------------------------ -------------------------------------------------- Usage Tool status OS-specific manual install steps Best video (needs ffmpeg) Audio extract (m4a) Subtitles (en + auto) Whisper speech-to-text video + audio + subs + transcript (background job) Flags: / \u2014 run as job / \u2014 notify in chat when the background job finishes and always run in the background. / / run synchronously unless is set. Auto-detect URLs When is true, a message that is only an or URL runs with (default ). Send results back to chat When is true, files under (and any cap in attached mode) are sent with automatically. Otherwise the reply lists paths \u2014 use manually."},{title:"Config keys",body:"Key Default Purpose ------------------------------------------------------ --------- ------------------------------------------------------ Master switch Allow Lone URL \u2192 pull Default mode Output root (empty \u2192 ) yt-dlp cap (0 = none) Push files to chat Whisper model / / Binary overrides"},{title:"Legal and safety",body:"Same allowlist trust as shell commands. Private/local URLs are blocked ( , RFC1918, etc.). Respect copyright and platform terms; omnish only runs tools you install locally."},{title:"See also",body:"Files send/receive \u2014 after a pull Background jobs \u2014 , ,"}],keywords:["media","pull","/pull","deprecated","superseded","by","commands","this","page","is","kept","for","older","releases","enable","install","tools","host","chat","config","keys","legal","and","safety","see","also"],relatedCommands:["/pull","/dl","/tr","/edit","/config","/config set","/bin","/venvs","/whisper","/pull install","/pull help","/pull doctor"]},{id:"docs-features-monetization",path:"docs/features/monetization.md",title:"Monetization \u2014 device-first omnish",summary:"omnish keeps WhatsApp and Telegram as the free control plane: allowlisted messages run on the user\u2019s machine. Paid tiers charge for reachability, identity, team boundaries, and safety nets\u2014not for sending a command.",sections:[{title:"Who pays at ~$10/month",body:"Someone who already runs on a home server, laptop, or Mac mini and wants the inbox to stay useful when they are away from the desk: show a dev server, start a long job, drive a TUI agent, tail logs, or hand a link to someone else. Free omnish sells my phone is a remote control for my computer. Paid omnish sells that control still works when I need a stable public URL, more than one tunnel, or light team structure\u2014without being my own ngrok admin."},{title:"Strongest first paid wedge",body:"Hosted tunneling on , integrated with the same chat and CLI, is the most natural first paid feature. Execution stays on their box; omnish runs the edge they would otherwise assemble (TLS, wildcard subdomains, relay uptime, abuse limits). For agent builds on the user\u2019s machine, steered from chat: the agent runs locally; Plus provides a shareable HTTPS preview via or , while iteration stays in WhatsApp or Telegram. That is not a cloud IDE\u2014it is a link that works while code and processes stay on their hardware. Plus ($10) \u2014 draft shape Several concurrent HTTP tunnels (free: self-hosted relay only, or one hosted tunnel with ephemeral names). Reserved slugs so does not change every session. Caps on bandwidth, tunnel lifetime, and concurrent tunnels so hosted relay cost stays bounded. TCP tunnels optional on Plus or stricter on free (TCP is riskier to operate). Comparable spend to ngrok-class tools; the hook is already inside omnish, not a separate dashboard."},{title:"Second layer: team and accountability",body:"Solo users may stay on free plus self-hosted relay. Small teams pay when my phone controls our box needs structure: More than one allowlisted identity with clearer roles (run vs read-only vs tunnel-only). Audit trail: who ran what, which tunnels opened, when\u2014exportable for a client or cofounder. Named machines in cluster mode without ambiguous shared binding. Still device-first: gateway on their hardware; paid layer is policy and visibility."},{title:"Third layer: reliability",body:"Weaker as the only $10 hook unless they depend on the gateway daily; pairs well with hosted tunnels: Offline alerts when stops heartbeating. Guided boot / service setup with a simple health view (gateway up, last command, disk, tunnel count). Optional backup / restore of omnish data (shortcuts, cowork defs, session cwd maps)\u2014not the whole disk."},{title:"What not to lead with at $10",body:"Hosted agent sandbox \u2014 fights device-first positioning; competes with Cursor, Replit, etc. Generic AI \u2014 conflicts with no-AI, your-shell positioning. Per-message chat fees \u2014 trains workarounds. Security theater \u2014 erodes trust on remote shell access."},{title:"Packaging sketch",body:"Tier Free Plus $10 Pro (later) --------------------------- ----------------- ----------------- ------------------- Chat \u2192 your shell Yes Yes Yes Self-hosted relay Yes Yes Yes Omnish-hosted HTTPS tunnels No / very limited Yes, with caps Higher caps Stable / custom names No Reserved slug Custom domain Team / audit Basic allowlist Small team + logs More seats, exports Gateway monitoring DIY Optional alerts SLA-style support One-liner: Keep controlling your machine from WhatsApp and Telegram for free; pay for public preview links, stable names, and team guardrails when that is how you work."},{title:"When someone will pay",body:"They pay when hosted tunnels remove a recurring pain: client demos, \u201Copen this while I change it from chat,\u201D or not maintaining Caddy, Cloudflare, and a relay on a VPS. Remote shell alone may stay free forever\u2014that supports adoption, not revenue."},{title:"Sequencing",body:"Plus = hosted tunnel + limits + account/token; chat stays unlimited. Team audit when paying users need shared access. Custom domains when reserved slugs feel tight. The preview-from-chat loop on their machine is credible paid value only if the URL is stable, HTTPS, and boring\u2014that is worth money while the shell stays free and local."}],keywords:["monetization","device-first","omnish","keeps","whatsapp","and","telegram","as","the","free","control","plane","allowlisted","messages","run","on","user","machine","paid","tiers","charge","for","reachability","identity","team","boundaries","safety","nets","not","sending","command","who","pays","at","10/month","strongest","first","wedge","second","layer","accountability","third","reliability","what","to","lead","with","10","packaging","sketch","when","someone","will","pay","sequencing"],relatedCommands:["/month someone","/tunnel","/theirname","/token"]},{id:"docs-features-online-catalog",path:"docs/features/online-catalog.md",title:"Online catalog",summary:"Share and install recipes, app templates, cowork tasks, and shortcuts across the omnish community. Browse and download from chat; publish when logged into the platform.",sections:[{title:"Discover and install (chat)",body:"Browse from the command family you care about \u2014 each prefix filters the catalog to the matching kind in MongoDB: Prefix Kind filter Example -------------------- ------------------------------------------------ -------------------------------------- all kinds (optional on trending/search) only only only Shared subcommands (replace with , , , or ): Command Purpose --------------------------------------- ---------------------------------------------------- Catalog subcommands for that family Most downloaded (kind-scoped when not ) Text search Full payload (review before install) Install to gateway-shared storage Install item #n from the last list in that family Repeat the last trending/search list for that family Examples: Use or on download to install for this chat only instead of gateway-shared: Numbered lists are per family \u2014 uses the last list, not a prior list. Platform URL: set in config (default hosted relay). Browse does not require a token; the CLI uses your configured relay origin. ---"},{title:"Publish (requires platform account)",body:"Sign up on the relay dashboard or run , then publish from chat: Command What is published Catalog -------------------------------------------------------------- --------------------------------------------------- -------------- User or gateway-shared recipe (not built-ins) Running PTY session command (session must be alive) Cowork task for this chat Shortcut (chat or shared) On success you get a (e.g. ). Others install with the matching prefix, e.g. or . Flags: , , , ---"},{title:"What gets installed",body:"Kind Local storage ---------- ---------------------------------------------------------------------- gateway-shared bucket Same as recipe (category ; use with stored command) gateway-shared bucket (owned by the importing chat) ---"},{title:"Security",body:"Recipes and app commands can run shell on your machine. Always before download. The platform rejects dangerous recipe flags and invalid command shapes at publish time. Download does not auto-run \u2014 it only adds the template locally. ---"},{title:"API",body:"Hosted relay routes are documented in Platform reference \u2014 Catalog. Related: Recipes / \xB7 Cowork \xB7 Shortcuts"}],keywords:["online","catalog","share","and","install","recipes","app","templates","cowork","tasks","shortcuts","across","the","omnish","community","browse","download","from","chat","publish","when","logged","into","platform","discover","requires","account","what","gets","installed","security","api"],relatedCommands:["/run online help","/apps online help","/guides","/platform-reference","/run online","/search","/apps online","/cowork online","/shortcut online","/run","/apps","/cowork","/shortcut","/search list"]},{id:"docs-features-run-queue",path:"docs/features/run-queue.md",title:"`/run` queue (`-q` / `--queue`)",summary:"The run queue runs recipe launches one at a time per chat, in FIFO order. It is meant for back-to-back agent jobs (e.g. several tasks) without starting multiple PTYs at once.",sections:[{title:"Syntax",body:"and are equivalent (case-insensitive). Short form works the same way. Combine with attach flags after the recipe name (same as non-queued ): attaches the queue head on start; without / , the head starts detached (default; see in Configuration). Not the same as the slash subcommand (status) or ."},{title:"Loading many jobs from JSON (`/run queue load`)",body:"You can enqueue multiple recipe tasks in one step from a JSON payload. Each row is validated like a separate (same recipe resolution, task length limits, and behavior). The queue still runs them one at a time in order. Ways to provide the JSON Method What to send -------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Host file path \u2014 path is resolved from this chat\u2019s session cwd (same idea as ): relative segments, globs, and quoted paths work like file selection elsewhere. Exactly one file must match. Inline JSON \u2014 everything after the word (with separating whitespace) is parsed as a single JSON value. Keep the payload on one message line if your client splits on newlines. Inbound file + caption Attach a document (e.g. ) and set the message text to (no path). Omnish uses the saved upload path from that same inbound turn (see Files \u2014 send & receive). You still get the usual \u201CSaved: \u2026\u201D line before the queue result. JSON format The file or inline text must be valid JSON in one of these shapes: Array of jobs (most common): Object with a single property (optional wrapper): Rules: Each job object must contain only two keys: and , both strings. Extra keys are rejected (catches typos like vs ). Top-level object form must be exactly \u2014 no other top-level keys. There must be at least one job. Empty arrays are rejected. Maximum 64 jobs per load. Not supported: raw , , or arbitrary shell in JSON. That would bypass recipe validation; only + are accepted, and omnish builds each run the same way as . File size limit for When reading a JSON file from disk (path argument or saved attachment path), the read is capped: If in is greater than zero, that value is used. If it is zero (no inbound cap), queue-load still uses a 1 MiB ceiling so a huge file cannot be pulled into memory unintentionally. Batch behavior and replies Jobs are enqueued in array order (first element becomes the next head when the queue is idle, or waits behind the current head). Omnish returns one reply that concatenates the status line from each enqueue step (started head, \u201Cwait slot \u2026\u201D, paused, etc.), same semantics as sending several messages in sequence. , , pauses, and clean-exit rules are unchanged \u2014 see Status and control and Pauses and failures. See also (files) Files \u2014 send & receive \u2014 where uploads land, , per-chat ."},{title:"What actually happens",body:"In memory only \u2014 Each waiting item is a small record (command + env + recipe label). Nothing runs until it becomes the head of the queue. Head starts immediately \u2014 When you enqueue and nothing else is running as the queue head, omnish shifts the first item off the FIFO and starts one app session (PTY), same as a normal . Others wait \u2014 Additional / calls append to the waiting list. No extra processes are spawned for those rows; the next job starts only when the previous head session exits with exit code 0 and signal 0 (clean exit). Per chat \u2014 Queue state is keyed by the chat ( ), not global across all users."},{title:"Why `/run queue` can show `Pending: 0` right after you queued something",body:"counts only jobs that have not started yet. The first item you add with / is removed from the pending list as soon as it starts; it then appears under Active with the session name and recipe label. Example: you send three queued runs in a row while the gateway is up: After message 1: Active = new session, Pending = 0 (nothing left waiting). After message 2: Active still the first session, Pending = 1 (second job waiting). After message 3: Pending = 2 (second and third waiting). So after a single enqueue is normal \u2014 it means the one job is already running, not that the queue \u201Clost\u201D your task. If you used before omnish accepted , the old parser treated as part of the task text and ran non-queued instead; the queue stayed empty. Use or after the recipe name (current omnish supports both)."},{title:"Status and control",body:"\u2014 Shows Active (session + recipe), Pending (with a short numbered list of waiting recipe labels), Paused, and a reminder that the next item auto-starts only after a clean exit. \u2014 Clears the paused flag and tries to start the next waiting item (e.g. after you fixed the host or session limits). If a head session is still running, resume tells you to wait until it finishes cleanly. \u2014 Enqueue many jobs from JSON (file path, inline , or attachment + caption); see Loading many jobs from JSON."},{title:"Pauses and failures",body:"Non-clean exit (non-zero exit code, or non-zero signal, e.g. SIGKILL) on the queue head \u2192 the queue pauses; waiting items stay in the FIFO until you (or fix limits and resume). Failed spawn (e.g. per-chat app limit reached) when trying to start the head \u2192 queue pauses and the item is put back; fix the error, then ."},{title:"Resource model",body:"Waiting rows: RAM only (no polling timers for the queue itself). Running head: one PTY + child process, same cost as a normal . Not persisted \u2014 If the gateway process ( ) restarts, the in-memory queue is cleared. Long-term scheduling belongs in Cowork or your own job runner."},{title:"See also",body:"System agents and User guide \u2014 shortcuts vs Interactive sessions ( )"}],keywords:["/run","queue","-q","--queue","the","run","runs","recipe","launches","one","at","time","per","chat","in","fifo","order","it","is","meant","for","back-to-back","agent","jobs","several","tasks","without","starting","multiple","ptys","once","syntax","loading","many","from","json","load","what","actually","happens","why","can","show","pending","right","after","you","queued","something","status","and","control","pauses","failures","resource","model","see","also"],relatedCommands:["/run help","/run queue","/run","/run remosh","/guides","/configuration","/to","/file","/send","/files-send-receive","/receive here","/run name","/system-agents-and-run"]},{id:"docs-features-service-from-chat",path:"docs/features/service-from-chat.md",title:"Service commands from chat (`/service`)",summary:"After WhatsApp or Telegram is connected and your identity is allowlisted, you can manage background gateway setup from the same DM thread \u2014 without SSH.",sections:[{title:"Commands",body:"Command Purpose ----------------------- -------------------------------------------------------------------------------------------------------------------------- Overview OS, data directory, , resolved Node + entry script paths Copy-paste steps for this host (paths filled by the running gateway) Last n lines of the default gateway log (default 80, max 120) Writes a user-level unit (Linux systemd or macOS LaunchAgent). Requires . Removes that unit (same gate). Windows: instructions only."},{title:"Trust model",body:", , and are safe to use like any other slash command (same allowlist as ). and modify login/boot integration files under your home directory. That is equivalent to shell access: anyone who can DM as an allowed identity can trigger them once is true in . Default: is . Enable only when you trust every entry on / as much as SSH. When enabled, reports a warning for ."},{title:"Relation to `CHANGE_ME` templates",body:"The contrib plist/service/XML files use placeholders for manual edits. bypasses that by injecting live paths from the running process. writes generated units with those paths automatically."},{title:"See also",body:"Background gateway and start on boot Cluster and chat configuration ( )"}],keywords:["service","commands","from","chat","/service","after","whatsapp","or","telegram","is","connected","and","your","identity","allowlisted","you","can","manage","background","gateway","setup","the","same","dm","thread","without","ssh","trust","model","relation","to","change","me","templates","see","also"],relatedCommands:["/service help","/service","/service status","/service instructions","/service logs","/service install","/config set","/service uninstall","/help","/boot integration","/contrib","/xml files"]},{id:"docs-features-sessions",path:"docs/features/sessions.md",title:"Interactive Sessions - omnish",summary:"Interactive sessions provide full terminal access within your messaging chats, enabling you to run TUI applications, REPLs, and interactive tools directly from WhatsApp or Telegram.",sections:[{title:"Overview",body:"Interactive sessions use to create pseudo-terminal sessions that mimic real terminal behavior. Each chat can maintain multiple named sessions with independent state. Key Features PTY-based: Full terminal emulation Named sessions: Multiple sessions per chat Focus management: One session attached at a time Output streaming: Real-time output with debouncing ANSI support: Color output and formatting Session persistence: State maintained across chats"},{title:"Session Management",body:"Starting Sessions Session Limits Per chat: Default 5 sessions (configurable) Global: Default 20 sessions (configurable) Named: Each session has a unique name per chat Plain messages and free shell For a single incoming line with no , , or , the router sends text to the attached session first when it is running; free shell mode ( ) applies only if nothing is attached (or the focused session is not running). Use for a one-off sync shell line while attached. Session States Created: Session initialized but not started Running: Session active and accepting input Attached: Session is focused for input Detached: Session running but not focused Stopped: Graceful shutdown requested Killed: Force termination Exited: Process terminated normally"},{title:"Session Commands",body:"Basic Operations Input Control Output Management Session Information"},{title:"Configuration",body:"Session Limits Terminal Settings Behavior Settings When , , or similar shows a password prompt, omnish detects it in recent terminal output and does not send the readline clear keys ( ) for your next reply \u2014 those keys break no-echo password readers and often appear as literal in chat. A one-time hint is sent unless is false. Passwords are still written to the session log on disk."},{title:"Use Cases",body:"Development Workflows ```text"}],keywords:["interactive","sessions","omnish","provide","full","terminal","access","within","your","messaging","chats","enabling","you","to","run","tui","applications","repls","and","tools","directly","from","whatsapp","or","telegram","overview","session","management","commands","configuration","use","cases"],relatedCommands:["/apps help","/apps start","/apps list","/apps attach","/apps detach","/apps stop","/apps kill","/apps send","/apps key","/apps tail","/apps since","/apps mute","/apps raw"]},{id:"docs-features-tunneling",path:"docs/features/tunneling.md",title:"Tunneling \u2014 omnish",summary:"omnish tunneling publishes a public URL that forwards to a local HTTP or TCP port on the machine running the tunnel client. The default relay is .",sections:[{title:"CLI (primary)",body:"Secrets are stored in (mode ) or . Override the relay with , in , or on expose commands."},{title:"Chat (optional)",body:"Login, logout, and status from chat work whenever the gateway runs (even if is false): , , . The bot reply does not echo your token; the inbound chat message still contains it (WhatsApp/Telegram history) \u2014 prefer on the host for highly sensitive tokens. When is in , allowlisted users can also run: Chat tunnels run inside the gateway process ( ) and share the same relay token as the CLI."},{title:"Security",body:"A tunnel URL is a capability: anyone who can open the URL can reach the forwarded service. Dev servers are often unauthenticated; treat tunneling like exposing a port on the public internet. Chat tunneling uses the same trust model as : allowlisted identities can open tunnels as the gateway OS user. in chat stores the bearer token on the gateway host but leaves a copy in the messaging transcript; use host CLI login if that risk matters for your threat model. See Security model."},{title:"Self-hosted relay",body:"For development or private deployments, run the relay in and point omnish at it with or . Operators: testing and operations (health checks, smoke, production VPS layout)."}],keywords:["tunneling","omnish","publishes","public","url","that","forwards","to","local","http","or","tcp","port","on","the","machine","running","tunnel","client","default","relay","is","cli","primary","chat","optional","security","self-hosted"],relatedCommands:["/tunnel help","/tunnels","/config help","/tunnel","/guides","/tunnel-setup-from-zero","/tunnel-auth","/tunnel login","/tunnel logout","/tunnel status","/telegram history","/tunnel http","/tunnel tcp","/tunnel stop"]},{id:"docs-features-watch",path:"docs/features/watch.md",title:"Watch \u2014 OS event eye",summary:"Lightweight OS event subscriptions that notify you on WhatsApp or Telegram when something changes on the machine running .",sections:[{title:"Quick start",body:"Enable watching: Or: or Run . Add rules: You should receive debounced messages like:"},{title:"Device-wide rules",body:"Watch rules are shared on the host, not private to one chat: One namespace per machine in (max 20 rules per device). Any allowlisted peer can , edit, pause, or remove any rule. Rule names must be unique on the device (two peers cannot each have a rule named ). (the default on new rules) alerts the peer who created the rule. Use , , or to reach more recipients. shows the creator peer key. If you had duplicate names from an older per-chat layout, omnish renames extras to on load."},{title:"Runtime model",body:"Watch is not a separate daemon or timed session. It runs inside the gateway process ( , foreground or ). Topic Behavior ----------------- ------------------------------------------------------------------------------------------------------------------------------------------------------- How long Indefinite while the gateway runs, is true, and the rule is enabled and not paused. There is no session timeout. Debounce (default 2s, range 500ms\u201360s) coalesces bursts before chat notify. Rate cap per rule (default 30). Service polls Each rule runs / / about every 30 seconds. Background Same Node process as the gateway; keeps Watch alive like foreground. Not a separate OS service unless you installed the gateway as one."},{title:"Cowork, `/bg`, and Watch",body:"Feature What it does Relation to Watch ---------- ---------------------------------------------------------------- ------------------------------------------------------------------------------------------------- Watch OS events (FS, package logs, services) \u2192 chat alerts \u2014 Cowork Scheduled or on-demand shell commands while the gateway runs Parallel \u2014 same gateway, shared notify routing only. Does not trigger or consume watch rules. Background shell jobs from chat Unrelated \u2014 no integration with watch adapters. See Cowork for task schedules and heartbeats."},{title:"Persistence and restart",body:'Watch rules are not memory-only. They are saved on disk and survive gateway and service restarts. Data Path ------------------------------------------ -------------------------------------------------------------------------------- Rules (paths, excludes, paused, notify, \u2026) Recent events and state-change keys Global on/off and tuning ( , , debounce, rate cap) On gateway boot: If and (default), omnish reloads and starts adapters for rules that are enabled and not paused. If , rules remain on disk but adapters do not run until . If , rules persist but you must (or change a rule) to start adapters without restarting the whole gateway. Use to see file paths, saved rule counts, and adapter health. Troubleshooting persistence Symptom Check --------------------------------- --------------------------------------------------------------------------------- "Rules gone" after restart \u2014 they should still be there No alerts after restart in config; paused/disabled rules; Rules on disk but nothing running or ; if'},{title:"Rule lifecycle",body:"State What it means Command -------------- ------------------------------------- ------------------------------------------------ Active Adapter running, alerts on or Paused Rule kept, adapter stopped, no alerts or Disabled Rule kept in list, Removed Deleted from Global off All adapters stopped After pause, stop, disable, or rm, pending debounced messages are cancelled so you should not get a late alert. pause / stop \u2014 same effect: stop watching, keep the rule for later. resume \u2014 start again if the rule is enabled. enable / disable \u2014 per-rule on/off (distinct from global and ). Check state: , , ."},{title:"Filesystem watches",body:"Add Root path \u2014 first path after the name ( , ). Events \u2014 optional comma list (default: ). Excludes (optional, both supported): Syntax Example ------------------ -------------------------------------- or You can combine them: between exclude clauses is also accepted as a separator. Manage excludes later Excludes are applied twice for efficiency: native watcher ignore list + post-filter before notify."},{title:"Package and service watches",body:"Kind Command -------- ----------------------------------------------------------------------- Packages \u2014 install/remove from OS logs Services \u2014 state changes on named units Use on noisy service checks. Discover services Finding the right unit name is easier with discovery commands (read-only; no rules are created): Command What you get ------------------------------------------ ------------------------------------------------------------------------------------------------ Bulleted services with state, then a second message with copy-paste lines Template lines only (no list) Package log path for this OS + existing FS directories you can watch caps at 40 matches; narrow with a filter (e.g. ). Running units are listed first. Example second message after :"},{title:"Notifications",body:""},{title:"Efficiency and noise control",body:"Watch narrow directories when possible; use excludes for , caches, build output. Built-in ignores: , , , swap files, etc. Debounce \u2014 (default 2s) coalesces bursts. Rate cap \u2014 per rule (default 30). Sensitive paths ( , , keys) are blocked. Resource use: Adapter Cost ------- -------------------------------------------------------------------------------------------------------------------- fs Kernel-native watcher (inotify / FSEvents) \u2014 low for small trees; high if you watch all of without excludes. pkg Tails OS install logs (file watch or 2s poll fallback). svc One subprocess poll every 30s per rule \u2014 many service rules add steady CPU. Timers use so pending debounce/poll timers alone will not keep Node alive. Avoid watching all of without excludes."},{title:"Permissions",body:"Platform Packages Services -------- --------------------------------------- ----------------------------- Linux (often group) for named units macOS labels Windows Application log for named services"},{title:"Troubleshooting",body:"Problem What to do ----------------------- ------------------------------------------------------------------------------------------------- No alerts true? Gateway running? Alerts after pause Should be fixed \u2014 if you paused mid-debounce, wait one debounce window; report if alerts continue unknown Use (name required) Path blocked Sensitive path denylist \u2014 pick another root Adapter error on status Fix log permissions or service names"},{title:"Data files",body:"Rules: Recent events:"},{title:"Config keys (chat-editable)",body:", , , \u2014 see ."},{title:"Related",body:"Cowork \u2014 scheduled tasks and heartbeat Webhook receiver \u2014 CI/CD to chat"}],keywords:["watch","os","event","eye","lightweight","subscriptions","that","notify","you","on","whatsapp","or","telegram","when","something","changes","the","machine","running","quick","start","device-wide","rules","runtime","model","cowork","/bg","and","persistence","restart","rule","lifecycle","filesystem","watches","package","service","notifications","efficiency","noise","control","permissions","troubleshooting","data","files","config","keys","chat-editable","related"],relatedCommands:["/watch help","/config","/watch on","/config set","/watch add","/deploy create","/projects","/tmp","/watch list","/home","/you","/deploy"]},{id:"docs-features-webhook-receiver",path:"docs/features/webhook-receiver.md",title:"Webhook receiver \u2014 CI/CD notifications via chat",summary:"The webhook receiver is a lightweight HTTP server built into the omnish gateway. It accepts requests with JSON payloads, formats them into concise messages, and delivers them to your WhatsApp or Telegram chat via the existing pipeline.",sections:[{title:"Prerequisites",body:"A running gateway: . set to in ."},{title:"Configuration",body:"Key Type Default Description ---------------- ------- ------------- --------------------------------------------------------------------------------------------------------------------------------------- boolean Enable the webhook HTTP server number (random) Port to listen on. picks a random available port. string Bind address. Use to accept external connections (see security note). string Bearer token for authentication. If empty when the receiver starts, a random 32-byte hex token is generated and saved to . Example :"},{title:"Endpoint",body:"The token can also be passed as a query parameter: . Request body Any valid JSON object. The receiver formats the payload into a chat message using built-in formatters for known CI systems, or a generic format for everything else. Optional fields in the JSON body: Field Type Description --------- ------ ------------------------------------------------------------------------------------------------------------------------------------------ string Target chat identity (e.g. or ). If omitted, the message goes to the first allowlisted peer. string Label for the source system (shown in the formatted message). Can also be set via query parameter or header. string Simple text message (used as-is when present). string Fallback message text. string Title line for generic payloads. string Status line for generic payloads. Response Status Body Meaning ------ -------------------------------------------------- ------------------------------ Message delivered Invalid JSON or no target peer Missing or invalid token Not a POST request Body exceeds 256 KB failed"},{title:"Built-in CI formatters",body:"GitHub Actions When the payload contains and a object, the receiver formats: GitLab CI When the payload contains and , the receiver formats: Generic payloads For any other JSON, the receiver uses , , , and fields if present, or falls back to a truncated JSON preview."},{title:"Examples",body:"GitHub Actions workflow Add a step at the end of your workflow to notify on completion: GitLab CI Simple notification from a script"},{title:"Security",body:"The webhook server binds to 127.0.0.1 by default \u2014 only local processes can reach it. If you set to , the server is accessible from the network. Use a firewall or reverse proxy with TLS in production. The bearer token is compared using to prevent timing attacks. Maximum payload size is 256 KB."},{title:"Default peer resolution",body:"When the incoming payload does not include a , the receiver picks the first available peer from the gateway's allowlist: WhatsApp peers are checked first, then Telegram. If no peer is available, the request returns a error."},{title:"See also",body:"Configuration guide \u2014 webhook config keys Cowork heartbeat \u2014 combine with heartbeat tasks for dead-man's-switch monitoring Background jobs \u2014 per-job completion notifications"}],keywords:["webhook","receiver","ci/cd","notifications","via","chat","the","is","lightweight","http","server","built","into","omnish","gateway","it","accepts","requests","with","json","payloads","formats","them","concise","messages","and","delivers","to","your","whatsapp","or","telegram","existing","pipeline","prerequisites","configuration","endpoint","built-in","ci","formatters","examples","security","default","peer","resolution","see","also"],relatedCommands:["/help","/webhook authorization","/json","/repo","/github","/owner","/actions","/runs","/project","/checkout","/your-server","/webhook","/localhost"]},{id:"docs-guides-background-and-boot",path:"docs/guides/background-and-boot.md",title:"Background gateway and start on boot",summary:"Portable CLI (all platforms): starts the gateway detached by default; logs default to ; pass (alias: ) to append elsewhere; reads and stops the process. Equivalent: (or ). Foreground: or . Use these for ad-hoc background runs without installing a system integration.",sections:[{title:"Linux (systemd --user)",body:"Copy contrib/omnish.service to . Set to your Node path and absolute path to , and if the data directory is not . Run: For a user service to run without an active login session, enable lingering:"},{title:"macOS (launchd)",body:"The LaunchAgent label is (reverse-DNS for omnish.dev). Older templates used ; if you already installed that, boot it out before installing the new plist (see below). Edit contrib/dev.omnish.gateway.plist: set Node path, path, and (and log paths if you use them). Copy to . Load (replace with output): To unload: Migrating from : remove the old job first, then install the new file: Alternative: add a small script that runs to System Settings \u2192 General \u2192 Login Items (simpler, no auto-restart on crash)."},{title:"Windows (Task Scheduler)",body:"Open Task Scheduler \u2192 Create Task\u2026 (not a basic task). General: run only when user is logged on (typical for WhatsApp session). Triggers: At log on for your user. Actions: Start a program Program: path to (e.g. from in ). Add arguments: (adjust the path; quotes if it contains spaces). Start in (optional): install folder. Set OMNISHHOME under user environment variables if you do not use the default data directory, or use a wrapper that then runs . Optional: import contrib/omnish-windows-task.xml after editing paths (import may need tweaks per account; the GUI is more reliable if XML import fails). Advanced: NSSM or WinSW can install Node as a Windows Service for machine-wide or headless scenarios \u2014 not maintained by this repo."},{title:"Stopping the gateway",body:"(any OS): uses the pidfile from or . On Windows, if signaling the process fails, omnish may fall back to (best-effort shutdown). systemd / launchd / Task Scheduler: use the manager\u2019s stop / disable as usual; do not rely on from a different start method."},{title:"Environment",body:": data directory (same as elsewhere in omnish). For services, set it in the unit / plist / task environment, not only in the shell profile."}],keywords:["background","gateway","and","start","on","boot","portable","cli","all","platforms","starts","the","detached","by","default","logs","to","pass","alias","append","elsewhere","reads","stops","process","equivalent","or","foreground","use","these","for","ad-hoc","runs","without","installing","system","integration","linux","systemd","--user","macos","launchd","windows","task","scheduler","stopping","environment"],relatedCommands:["/service help","/service instructions","/logs","/gateway","/service","/service logs","/service install","/features","/service-from-chat","/dist","/index","/omnish","/contrib"]},{id:"docs-guides-configuration",path:"docs/guides/configuration.md",title:"Configuration Guide - omnish",summary:"Complete configuration reference and customization guide.",sections:[{title:"Configuration Overview",body:"omnish uses a JSON configuration file with sensible defaults. You can customize every aspect of the system's behavior. Configuration File Location Default: Legacy: (if upgrading) Override: Set Check your configuration location: ```bash omnish status"}],keywords:["configuration","guide","omnish","complete","reference","and","customization","overview"],relatedCommands:["/config help","/config keys","/config","/path","/to","/dir","/bin","/bash","/features","/chat-llm-fallback","/tunnel","/login","/v1","/me"]},{id:"docs-guides-coordinator-workforce",path:"docs/guides/coordinator-workforce.md",title:"Coordinator workforce manager",summary:"The job board Coordinator is a deterministic workforce layer: onboard employees, assess skills, customize work flow, dispatch through the harness, record performance, collect human feedback, and suggest recruitment when roles are overloaded.",sections:[{title:"Communication (all employees)",body:"Every specialist run receives , , , and (coordinator-only) . Do not document communication as alone \u2014 always use the full terminal commands below (requires on the host). Role When Full command ------------------ ------------------------------------------------------------- ------------------------------------------------- Any specialist While job is (at least once per phase; on blockers) Coordinator After supervisor pass, when the plan requires file delivery Helper script (employee pack): runs the specialist command. Specialists must not send final CHECK deliverables via sendto before review. The coordinator has final say on files. At handoff, must still include: \u2014 what was communicated to the coordinator/human \u2014 plain-language outcome for the job owner Work plans include a step under the plan phase. The supervisor rejects handoffs missing these fields. When is true (default), the coordinator runs the file-send command automatically after if the plan mentions / distribution and a media CHECK file exists in the work dir."},{title:"Onboarding",body:"Creates with , , and registry entry. Coordinator seeds core competencies and activates the employee when the skill pack exists."},{title:"Skills and flow",body:"Before dispatch (when is true), the Coordinator assesses gaps vs the job plan. With , it runs your agent to fill ; otherwise a deterministic template fills gaps and writes . Per-employee may add extra steps in allowed phases; required phases and cannot be removed. Harness env: , ."},{title:"Performance and feedback",body:"After each supervisor verdict, a row is stored in . When a job passes:"},{title:"Recruitment",body:"Heuristics (overload, multi-domain plans, rework) create suggestions. Humans accept before a new employee exists:"},{title:"Optional finetuning",body:"When and the coordinator agent are enabled, poor feedback can produce files under . Promote only after review:"},{title:"Config",body:"Key Default Purpose --------------------------------- ------- ----------------------------------------------------------------------------------- External agent for research/finetune Shell command Allow proposed tuning Gap assessment before dispatch Coordinator runs after done when plan requires delivery One-shot satisfaction reminder See also Employee task flow, digital-employees.md, and job-board.md."}],keywords:["coordinator","workforce","manager","the","job","board","is","deterministic","layer","onboard","employees","assess","skills","customize","work","flow","dispatch","through","harness","record","performance","collect","human","feedback","and","suggest","recruitment","when","roles","are","overloaded","communication","all","onboarding","optional","finetuning","config"],relatedCommands:["/sendto","/employee-pack-example","/scripts","/board-status","/human","/board employee","/board","/employees","/board feedback","/board recruit","/proposed","/finetune"]},{id:"docs-guides-digital-employees",path:"docs/guides/digital-employees.md",title:"Digital employees \u2014 job board, coordinator, supervisor",summary:"omnish stays local execution infrastructure. Digital employees are external agent CLIs (or shell wrappers) coordinated by three meta-roles plus specialists.",sections:[{title:"Roles",body:"Role Responsibility Human? --------------- ------------------------------------------------------------------------------------------------- ----------- Human Posts parent jobs to the job board Intake only Coordinator Assigns specialists, creates prerequisite jobs, unblocks dependents, enforces resource limits No Supervisor Verifies work against the job plan; pass, rework, or fail No Specialist Runs assigned work; writes No The coordinator and supervisor run inside the gateway while it is up ( defaults to true). Use for a one-shot cycle or set to for fully manual mode."},{title:"Job board",body:"Storage: Artifacts: (report, verdict, audit via DB) Status flow Full pipeline (human intake through supervisor and feedback): Employee task flow. Chat commands and are aliases. Acceptance criteria in the plan Supervisor runs deterministic checks from the plan: Optional LLM review can wrap the same contract later; the built-in supervisor is deterministic first."},{title:"Core competencies (always on)",body:"No matter how vague an is (e.g. ), omnish never runs it directly. The employee harness always: Seeds with canonical steps: plan \u2192 implement \u2192 execute \u2192 test \u2192 review Runs the inner command via Tracks phase progress and notifies the job owner (configurable) Enforces handoff via the supervisor (work plan + report contract) You define what runs ( ). omnish defines how employees work. Competency Enforcement ----------------------- ----------------------------------------------------------- Five phases Pre-seeded work plan Plan before work Harness seeds plan before inner CLI Structure + self-review Supervisor + step Outcome ownership , , Progress visibility Chat notifications on phases and status"},{title:"Specialist contract",body:"Each employee is configured in : Legacy is accepted as an alias for . Environment injected at run time: , , , \u2014 harness-seeded \u2014 \u2014 e.g. Plan before work (harness + supervisor) The harness creates the work plan before your agent runs. Specialists mark steps complete (in script or via ). Before handoff: All steps and set; with and matching The supervisor rejects handoffs that miss any core competency (with human-readable labels in verdict notes). Schema: work-plan.schema.json. Example scripts: contrib/employee-pack-example/specialists/. report.json (required) See contrib/employee-pack-example/schemas/report.schema.json. Must reference and with matching completed ids in the work plan. Prerequisite requests (exit code 2) If work cannot start, exit 2 and set in the report. The coordinator creates child jobs, blocks the parent, and unblocks when children reach . Specialists must not spawn parallel agents themselves."},{title:"Resource governor",body:"(defaults created on first coordinator run): Field Default Meaning --------------------- ------- ---------------------------------- 2 Host-wide running specialists 1 Extra cap for jobs 75 Pause dispatch when load is high 2048 Pause when free RAM is low 90 Optional gate Check live status: ."},{title:"Progress notifications",body:"In : Setting Values Effect ---------------------- ------------------------------- -------------------------------------------------- \\ \\ Phase step completion (and for minor status) \\ Start, review, done, failed, blocked, rework Examples: , ."},{title:"Coordinator workforce",body:"Onboarding, skills, performance history, human feedback, and recruitment suggestions are documented in Coordinator workforce manager."},{title:"Configuration",body:"In : Primary agent for all registry roles: Escalations (job after max rework) always notify the job owner on the allowlisted chat surface."},{title:"Example pack",body:"contrib/employee-pack-example \u2014 stub , , scripts and sample . Point your registry at real agent CLIs when ready: Then reference that wrapper from instead of the stub scripts."},{title:"Related",body:"Employee task flow \u2014 human \u2192 coordinator \u2192 specialist \u2192 accomplishment Job board feature reference System agents and Cowork \u2014 scheduled shell shifts (complementary)"}],keywords:["digital","employees","job","board","coordinator","supervisor","omnish","stays","local","execution","infrastructure","are","external","agent","clis","or","shell","wrappers","coordinated","by","three","meta-roles","plus","specialists","roles","core","competencies","always","on","specialist","contract","resource","governor","progress","notifications","workforce","configuration","example","pack","related"],relatedCommands:["/board tick","/board","/cowork","/jobs","/employee-task-flow","/board add","/ok","/board list","/board show","/board capacity","/board cancel","/board employees"]},{id:"docs-guides-docker-gateway-golden-path",path:"docs/guides/docker-gateway-golden-path.md",title:"Docker gateway \u2014 golden path (reference)",summary:"Run omnish inside a container so a deployed app gains chat-driven shell access on that box. Reference files: .",sections:[{title:"Two ways to use omnish in Docker",body:"Path When Setup in container -------------------------- ------------------------------------------------ ----------------------------------------------------------------------------------------------- Attached (recommended) Platform account; messengers linked on dashboard , , , \u2014 no QR in container Standalone No hosted layer; expert / air-gapped Persistent volume + + inside container (or on host) The compose file in implements standalone by default. Use attached env vars for the platform path (see Platform attached mode, Platform reference)."},{title:"Attached mode (implemented)",body:"Add chat-driven ops to an existing app container without Baileys inside the image: Link WhatsApp/Telegram on the platform dashboard (once per account). Set allowlist on the dashboard. Deploy with the env above; outbound HTTPS to the platform only. Legacy env aliases: , . Messengers do not run inside the container; the platform routes chat to this CLI."},{title:"Standalone reference (implemented today)",body:"What you get Pinned npm version ( ). Non-root user (uid 1000), as PID 1. HEALTHCHECK ( ) \u2014 process up only; not messenger connectivity. Build and run First-time pairing (empty volume): Then as above."},{title:"Networking",body:"Concern Guidance -------------- -------------------------------------------------------------------------------------------------- Attached Outbound HTTPS to communication layer; no Baileys in container Standalone Outbound to WhatsApp/Telegram APIs from container Tunnels Tunnel client runs in gateway namespace; see Tunnel setup from zero"},{title:"Environment",body:"Standalone today: \u2014 volume mount (compose: ) , \u2014 optional overrides Attached: , (aliases: , ) Optional /"},{title:"Security",body:"Allowlisted inbox \u2192 real shell as the container user. Protect volumes and tokens. Do not expose without TLS and auth \u2014 Webhook receiver."},{title:"See also",body:"Communication layer model Background gateway and start on boot"}],keywords:["docker","gateway","golden","path","reference","run","omnish","inside","container","so","deployed","app","gains","chat-driven","shell","access","on","that","box","files","two","ways","to","use","in","attached","mode","implemented","standalone","today","networking","environment","security","see","also"],relatedCommands:["/gateway-docker","/contrib","/architecture","/communication-layer-model","/tunnel","/telegram on","/docker-compose","/telegram apis","/tunnel-setup-from-zero","/home","/node","/features"]},{id:"docs-guides-employee-task-flow",path:"docs/guides/employee-task-flow.md",title:"Human \u2192 Coordinator \u2192 Employee \u2192 Task accomplishment",summary:"End-to-end flow for the omnish job board: how work moves from a human through the coordinator and a harnessed digital employee to supervised completion.",sections:[{title:"Four roles (not one \u201Cemployee\u201D)",body:"Role Who Responsibility --------------------------------- -------------------------------------------------- -------------------------------------------------------------------------- Human Allowlisted chat peer (job owner) Creates jobs, cancels, optional feedback after completion Coordinator Gateway loop ( ) Unblocks deps, picks assignee, acquires resource slots, dispatches harness Specialist (digital employee) External CLI from Runs inside harness; writes + Supervisor Deterministic QA: pass \u2192 , rework, or fail The coordinator does not judge quality. The supervisor does not choose the next assignee or create prerequisite jobs. That split is intentional \u2014 see Job board \u2014 separation of duties."},{title:"High-level sequence",body:""},{title:"Phase 1 \u2014 Human intake",body:"Entry: Chat \u2192 ( ) \u2192 ( ). Example: What happens: Job in with Work directory: = the human who posted the job Optional ; otherwise the coordinator picks on dispatch via plan heuristics Human can also: , , , (one coordinator cycle), , , and post-completion ."},{title:"Phase 2 \u2014 Coordinator tick (automation)",body:"Trigger: when is true, or . Each tick runs once, in this order: \u2014 \u2192 when all jobs are Notify pending jobs whose dependencies just became satisfied \u2014 diff and notify owner of phase changes (if configured) \u2014 every job \u2192 (may finish work from a previous dispatch) Dispatch at most one new job (if governor slot available): \u2014 or , deps satisfied Auto-assign via if no assignee (review \u2192 reviewer; implement/build/code \u2192 implementer; else researcher) + \u2014 skills research, communication brief, role context \u2014 harness execution \u2192 again \u2014 supervises jobs that entered during this tick (so one can dispatch and finish) Implementation sets directly from / . The status exists in the type system but is not used on the main dispatch path. Default: is while the gateway runs; status and handoff messages go to the job owner on chat."},{title:"Phase 3 \u2014 Digital employee execution (harness)",body:"The coordinator never runs directly. Load specialist from ( only; legacy alias) \u2014 phases: plan \u2192 implement \u2192 execute \u2192 test \u2192 review Env: , , , , briefing extras \u2192 Specialist marks steps (env hints or ) Specialist writes ( , , , , \u2026) Outcomes after harness exits: Exit / report Job status Next step ------------------------------------- ---------------------- ----------------------------------------------- Normal (not exit 2) Supervisor on this or next tick Exit 2 + Child jobs created; parent waits on Missing registry / bad config (stub report) Supervisor likely rework / fail When children reach , the parent unblocks and is dispatched again."},{title:"Phase 4 \u2014 Supervisor (quality gate)",body:"calls for each job. Checks include: exists and validates (steps complete, , competencies) contract (summary, , , communication, handoff) matches completed step ids in the work plan Plan lines \u2014 files must exist under the work dir Verdict \u2192 status: Verdict New status Notes ---------- ---------- --------------------------------------------------------- pass prompts feedback; may suggest recruitment rework Re-enters queue until (default 3) fail Owner notified; slot released Writes , audit rows, and performance history per assignee. Technical accomplishment = supervisor pass. Human satisfaction is separate ( )."},{title:"Phase 5 \u2014 Human closure (after `done`)",body:"Optional but closes the workforce loop: if a role-split was suggested Optional coordinator agent ( ) can propose / under \u2014 promote only after review via ."},{title:"Job status state machine",body:"Statuses: , , (reserved), , , , , , \u2014 see ."},{title:"Artifacts per job",body:"Path Writer ----------------------------------- -------------------- Harness + specialist Specialist (or stub) Supervisor Status and audit"},{title:"Not this flow",body:"is scheduled shell shifts \u2014 complementary, not the human \u2192 coordinator \u2192 specialist \u2192 supervisor pipeline."},{title:"Summary",body:"The human posts criteria to the board; the coordinator ticks to unblock, review prior handoffs, and dispatch one runnable job through the harnessed specialist CLI; the supervisor accepts or rejects into / / ; the human optionally rates satisfaction after ."}],keywords:["human","coordinator","employee","task","accomplishment","end-to-end","flow","for","the","omnish","job","board","how","work","moves","from","through","and","harnessed","digital","to","supervised","completion","four","roles","not","one","high-level","sequence","phase","intake","tick","automation","execution","harness","supervisor","quality","gate","closure","after","done","status","state","machine","artifacts","per","this","summary"],relatedCommands:["/digital-employees","/features","/job-board","/coordinator-workforce","/board","/coordinator","/employees","/supervisor","/board add","/board tick","/board feedback","/chat"]},{id:"docs-guides-interactive-cli",path:"docs/guides/interactive-cli.md",title:"Interactive terminal (`omnish i`)",summary:"Run the same commands you use in chat, but from your local terminal.",sections:[{title:"Commands",body:"Run from any directory. Your CLI session\u2019s working directory starts at (change it with or using your configured command prefix). Flags Flag Meaning ------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------- Sender key for chat-driven cluster commands ( or ). If omitted, a synthetic sender tied to the CLI session is used ( ). / Run a single line and exit (non-interactive). Useful for scripts."},{title:"Trust model",body:"is not gated by the inbox allowlist. Anyone who can run commands on your machine can use it\u2014it has the same trust as your login shell. Chat interfaces remain allowlisted as before."},{title:"`/sendto`: push files or plain text to WhatsApp or Telegram",body:"When is active on this machine, you can use from to choose exactly who gets files, or to send a plain chat message (not an attachment). Syntax Files: selectors are checked from the current folder. Add an optional caption after . Plain text: same destinations; message is everything after the flag (or the value). To send a file whose name looks like a flag, use an explicit path (e.g. ). Destination forms: \u2014 all WhatsApp recipients from \u2014 all Telegram recipients from \u2014 both channels (all allowlisted WA + TG recipients) \u2014 one explicit WhatsApp recipient \u2014 explicit WhatsApp recipient list \u2014 one explicit Telegram recipient (compatibility form) Selector forms: \u2014 explicit list \u2014 cwd glob \u2014 recursive cwd glob Compatibility aliases also accepted: , , , , . Examples: Common patterns ```text"}],keywords:["interactive","terminal","omnish","run","the","same","commands","you","use","in","chat","but","from","your","local","trust","model","/sendto","push","files","or","plain","text","to","whatsapp","telegram"],relatedCommands:["/help","/sendto","/sendto wa","/photo","/sendto tg","/report","/promo","/downloads","/telegram outbound","/send","/files-send-receive","/bg","/reload"]},{id:"docs-guides-platform-attached-mode",path:"docs/guides/platform-attached-mode.md",title:"Platform attached mode",summary:"Run on a laptop, server, or container while WhatsApp and Telegram stay on the hosted omnish platform (tunnel relay). You link messengers once on the dashboard; each machine attaches with an account token and executes shell commands locally.",sections:[{title:"Standalone vs attached",body:"Standalone (default) Attached (platform) ---------------------------- ---------------------------------------- --------------------------------------------------------------- When No platform URL + token + (or env) set Where messengers connect Same host as Hosted relay (dashboard) Link WhatsApp on the device Dashboard QR or Link Telegram on the device Dashboard bot token Allowlist \u2192 local Dashboard allowFrom / telegramAllowFrom (authoritative) Shell execution Local Local (unchanged) If platform credentials are set, does not start local Baileys/Telegram clients \u2014 it opens a WebSocket to the platform and runs commands on this host only."},{title:"Prerequisites",body:"A platform account (signup/login on your relay \u2014 see tunnel relay README). Messengers linked on the platform (not via on the device, unless you use import \u2014 see below). Your phone or Telegram id on the platform allowlist (dashboard). Outbound HTTPS from the device to the platform URL (and correct reverse-proxy routing if you self-host)."},{title:"Step 1 \u2014 Account and token",body:"On the hosted relay (e.g. ): Sign up or log in via / , or use . Copy the account token from the response or dashboard. The same token works for: Attached gateway: + Persisted config:"},{title:"Step 2 \u2014 Link messengers on the platform",body:"Do this on the platform, not on the machine that will run shell commands (unless noted). WhatsApp Option A \u2014 Dashboard (recommended, 1 minute) Open the platform dashboard ( on your relay origin). Click Link WhatsApp \u2014 a QR appears automatically. Scan with WhatsApp \u2192 Linked devices. The phone may show \u201CLogging in\u201D for a few seconds while the platform reconnects after scan (515 restart \u2014 dashboard shows Finishing link\u2026; no second QR). When connected, use Add my number to allowlist (one click) if shown. Run on your machine. Option A2 \u2014 CLI QR in terminal Scan the ASCII QR, then: To disconnect: dashboard Unlink WhatsApp or . Option B \u2014 Import from a machine that already linked locally On a machine where you ran successfully, stop . Set platform URL + token, then: ```bash omnish platform import-whatsapp"}],keywords:["platform","attached","mode","run","on","laptop","server","or","container","while","whatsapp","and","telegram","stay","the","hosted","omnish","tunnel","relay","you","link","messengers","once","dashboard","each","machine","attaches","with","an","account","token","executes","shell","commands","locally","standalone","vs","prerequisites","step"],relatedCommands:["/help","/architecture","/communication-layer-model","/gateway-config-precedence","/contrib","/tunnel-relay","/readme","/telegram clients","/login on","/tunnel","/auth","/signup","/login"]},{id:"docs-guides-platform-reference",path:"docs/guides/platform-reference.md",title:"Platform reference (complete)",summary:"Consolidated documentation for the omnish hosted platform (tunnel relay + dashboard + attached ). For a guided walkthrough, start with Platform attached mode. For relay deployment, see contrib/tunnel-relay/README.md.",sections:[{title:"One-minute checklist",body:"Step Action ---- --------------------------------------------------------------------------------------------------------------------------------------- 1 Sign up / log in at \u2014 copy account token 2 Link WhatsApp: dashboard Link WhatsApp \u2192 scan QR \u2192 Add my number to allowlist (if offered) 3 Link Telegram: paste bot token \u2192 Link / restart Telegram \u2192 DM bot \u2192 add id under Allowlists \u2192 Save allowlists 4 On your machine: (or ) 5 then 6 From allowlisted chat: or ---"},{title:"How it works",body:"Messengers terminate on the relay (Baileys + grammY). Shell runs only on machines where is attached. Policy (allowlists, ) is stored on the platform and merged into the attached CLI via (refreshed every 5 minutes and on connect). ---"},{title:"What is persisted",body:"Requires on the relay (account data). Without MongoDB, only static relay tokens work \u2014 no dashboard accounts or persistence. Data Storage Survives relay restart --------------------------------- --------------------------------------------- ------------------------- Account token, email MongoDB Yes , MongoDB Yes MongoDB Yes Telegram bot token MongoDB ( ) Yes (not returned by API) WhatsApp Baileys auth files Disk Yes Connector status, linked WA phone MongoDB Yes Peer \u2192 device bindings MongoDB Yes Device slots MongoDB Yes Community catalog entries MongoDB Yes On MongoDB connect, linked connectors are restored automatically ( ). Volumes (self-hosted): \u2014 WhatsApp sessions MongoDB \u2014 accounts, allowlists, connector sessions ---"},{title:"Dashboard (`/dashboard/`)",body:"After login, the dashboard loads and hydrates all forms. Account status Shows , WhatsApp/Telegram connector state, allowlist counts, default device, online device count. Devices Create device slots, set default device, see online/offline status. The first attached may auto-create a device. Routing Peer bindings map a (e.g. , ) to a specific . Routing order when a message arrives: Peer binding (if set) Single online device (if exactly one) Default device (if online) Any online device Allowlists Field Format Notes -------- ---------------------- ------------------------------------------- WhatsApp E.164, comma-separated e.g. Telegram Numeric user ids Use bot in DM to discover your id Save allowlists \u2192 . Changes apply immediately (connectors reload allowlists per message). Warning: empty allowlist = any sender can run commands. Telegram connector Paste bot token from @BotFather. Link / restart Telegram \u2014 token required on first link; if already linked, empty token field reuses saved token. Users DM the bot before allowlisting to learn their numeric id. WhatsApp connector State UI ------------------ -------------------------------------------------------------------------------------------------------------- Not linked Link WhatsApp \u2014 shows QR, auto-polls until connected; Refresh / Restart pairing while QR is active After scan, WhatsApp closes with 515 \u2014 QR hidden, \u201CFinishing link\u2026\u201D (10s); normal Runtime reconnect after a drop \u2014 QR hidden Linked Green \u201Cconnected\u201D, optional Add my number to allowlist, Unlink WhatsApp Logged out / error Reconnect (unlink + new QR) Advanced: import from local via (see CLI below). Attached CLI snippet Shows , , optional , and . ---"},{title:"CLI reference (`omnish platform \u2026`)",body:"All commands require + (config or env). Run for the latest help text. Setup and diagnostics Command Purpose ------------------------------------------------------------------ --------------------------------------------------------- Save credentials to Same (config CLI aliases) Account, connectors, allowlist counts, online devices URL, token source, effective platform block Test WebSocket paths before Attach device (attached mode when platform creds resolve) Allowlists (platform policy) Command Purpose --------------------------------------------------- ------------------------------------- Show WhatsApp and Telegram allowlists Merge entries into allowlists Replace one or both lists Examples: Wildcard is rejected on the platform (same security model as standalone). WhatsApp on platform Command Purpose --------------------------------------------------------------- ---------------------------------------------------------------------------------------- Start pairing, print ASCII QR, poll until linked (includes after scan) Remove session and auth files on platform Upload local Baileys auth from on this host Stop local before import to avoid WhatsApp session conflicts. Environment variables Canonical Also accepted Purpose --------------------- ---------------------------------------------- ------------------------- , Relay base URL , Account bearer token in config Pin device slot on attach Precedence: environment \u2192 \u2192 (if used). Config keys: ( ), ( ), ( ). ---"},{title:"HTTP API reference",body:"Base URL: relay origin (e.g. ). Auth: for most routes. Catalog browse/download routes are public (no bearer required); publish requires a bearer token. Chat commands for the catalog: Online catalog. Auth (no bearer on signup/login body) Method Path Body Response ------ -------------- --------------------- ----------------- POST (201) POST Account Method Path Body Response ------ ----------------------- ------------------------------------ --------------------------------------------------------------------------------------------------------------------- GET \u2014 , , , , , , , , , \u2026 PUT PUT GET \u2014 shape (example): means a bot token is stored; the token is never returned. Devices and routing Method Path Body Response ------ ------------------ ----------------------- ---------------------------------------------------- GET \u2014 POST (201) PUT DELETE Telegram connector Method Path Body Response ------ ------------------------- ----------------------------------- -------------------------- PUT required on first link; omit on later calls to reuse stored token. optional; can also use . WhatsApp connector Method Path Body Response ------ -------------------------------- ---------------------------------------------------- --------------------------------------------------------------------------- POST (optional legacy ) GET \u2014 POST GET \u2014 Legacy; prefer POST Status values: , , , , , , , . After a successful QR scan, Baileys typically closes with (515) and reconnects using saved creds (no second QR). The connector reports (no ) until the new socket opens as . Catalog (community templates) Public read (no bearer). Publish requires account bearer token. Method Path Auth Query / body Response ------ -------------------------------- ------------ ----------------------------------------------------------------- ------------------------------------------------------------------------------------------------- GET optional GET optional GET optional \u2014 Full entry including POST required POST optional \u2014 Full entry; increments values: , , , . Upsert on publish is per . Max payload 32 KiB. Recipe payloads must include quoted (or custom ) in the command. WebSocket (attached CLI) Path Purpose --------------------- ------------------------------------------------------- Primary attach path Fallback when not proxied to control port Register frame: Inbound: Outbound: Reverse proxy (required paths \u2192 control port 8788) , , , , If these hit the HTTP edge (8787) instead, attach fails with WebSocket 400. Run . ---"},{title:"Telegram `/id` command",body:"Works on platform-linked and standalone bots, before the user is on the allowlist. Mode Reply hints ---------- ------------------------------------------------------------ Platform Numeric id + + dashboard / Standalone Numeric id + See Telegram integration notes. ---"},{title:"Policy in attached mode",body:"When attaches successfully: loads , , . These override local for inbound messenger policy. Host-only settings (shell, jobs, tunnels, webhooks) still come from local config. Policy refreshes every 5 minutes while attached. If fails at startup, the CLI may fall back to local allowlists until the next successful sync. You do not need on the device for platform-routed chat when platform policy loads successfully. ---"},{title:"Troubleshooting index",body:"Symptom See -------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- WebSocket 400 on attach Platform attached mode \u2014 Troubleshooting, relay README Device online, no command output Platform allowlist; relay logs; Troubleshooting \u2014 Attached Dashboard fields empty after login Requires MongoDB; hard-refresh; re-save allowlists Telegram token \u201Cgone\u201D after reload By design \u2014 token not shown; use empty field + Link to reuse Phone stuck on \u201CLogging in\u201D after scan Wait 15s \u2014 platform should show then connected; see Troubleshooting \u2014 Platform WhatsApp WhatsApp stuck / logged out Dashboard Reconnect or then link-whatsapp with correct allowlist WhatsApp LID resolution \u2014 update relay + CLI Empty allowlist surprises Empty = allow all senders ---"},{title:"See also",body:"Platform attached mode \u2014 guided setup Quick start \u2014 Path C Configuration \u2014 Platform / attached mode Docker gateway golden path Tunnel setup from zero \u2014 same account token for tunnels Communication layer \u2014 API sketch (future notes; implemented API is in this doc)"}],keywords:["platform","reference","complete","consolidated","documentation","for","the","omnish","hosted","tunnel","relay","dashboard","attached","guided","walkthrough","start","with","mode","deployment","see","contrib/tunnel-relay/readme","md","one-minute","checklist","how","it","works","what","is","persisted","/dashboard/","cli","http","api","telegram","/id","command","policy","in","troubleshooting","index","also"],relatedCommands:["/tunnel-relay","/readme","/contrib","/architecture","/communication-layer-model","/gateway-config-precedence","/telegram-integration-notes","/dashboard","/id","/v1","/me","/whatsapp"]},{id:"docs-guides-quick-start",path:"docs/guides/quick-start.md",title:"Quick Start Guide - omnish",summary:"",sections:[{title:"What you\u2019ll get",body:"By the end of this page you will have: A working gateway \u2014 listening for your DMs. A first win \u2014 a successful sync command (e.g. or ) with output back in chat. A path to deep control \u2014 optional: for a PTY smoke test (see the main README)."},{title:"What is omnish?",body:"A secure CLI tool that bridges messaging platforms (WhatsApp, Telegram) to your system shell. It allows allowlisted users to: Execute shell commands directly from chat Run background jobs with streaming output Start interactive terminal sessions Transfer files between your system and chats Key differentiator: No AI layer - direct, deterministic shell access with explicit security controls."},{title:"One phone with WhatsApp",body:"You do not need a second phone. Host \u2014 omnish runs on a laptop, desktop, home server, or VPS (Linux, macOS, or Windows). It is a CLI gateway, not a phone app. Phone \u2014 you use the same WhatsApp account: scan the QR once under Linked devices, then send command DMs from that app (for example Message yourself). Linking works like WhatsApp Web: your account is the phone plus linked devices. Session files live under (see Comprehensive documentation). The phone stays the primary device; omnish on the host is an extra linked device. Keep the phone online as WhatsApp expects for multi-device; Meta\u2019s caps on linked devices still apply. Follow Path A: WhatsApp below for install, , , and . Private chats only \u2014 groups are ignored. No computer handy? You still need a machine that stays on to run ; a small VPS is typical. If you prefer not to use WhatsApp linked devices, you can use Telegram instead ( ); see Telegram integration notes. Security Treat allowlisted numbers (and Telegram ids) like remote shell passwords \u2014 only add identities you fully trust. See the main README and Security model."},{title:"Installation",body:"From npm (recommended) From source ```bash git clone https://github.com/eligapris/omnish.git cd omnish pnpm install"}],keywords:["quick","start","guide","omnish","what","you","ll","get","is","one","phone","with","whatsapp","installation"],relatedCommands:["/help","/wa help","/tg help","/omnish","/media","/logo-horizontal","/apps start","/readme","/auth","/comprehensive-documentation","/telegram-integration-notes","/architecture","/security","/github","/eligapris"]},{id:"docs-guides-system-agents-and-run",path:"docs/guides/system-agents-and-run.md",title:"System agents, multi-agent, and `/run`",summary:"omnish is built for agents on your machine \u2014 not another hosted model or subscription. You run on hardware you control; from chat you drive system agents: CLIs and TUIs such as , , your own scripts, or multi-step orchestrators. Omnish forwards deterministic shell and PTY to those tools; it does not replace them.",sections:[{title:"Curated mobile notifications",body:"When running coding agents from mobile, keep sessions detached/muted and send only curated updates. Use the status format: Recommended update types: Ack (task accepted) Progress (phase milestone) Question (human input needed) Done (result summary) Blocked (failure or blocker) The repository includes a helper script: It targets , then , and falls back to . For a persistent chat agent (plain DMs queued to one long-lived process), enable and set to a wrapper that calls on milestones. The daemon receives the same notify env as ( , , ). See Chat agent daemon and ."},{title:"Default macro command (`recipesMacroDefaultCommand`)",body:"In , is the shell command used when a macro-style recipe stores a long body as a . The default targets the Claude CLI agent: Point it at any agent or orchestrator that reads the task from the environment, for example a wrapper that fans out to multiple local agents: Restart the gateway after editing (or use when supported)."},{title:"Per-chat recipes with `/run add`",body:"The stored command must reference (or your custom ). Example: Cursor / other CLI agents Example: Claude (default-class agent CLI) Patterns are the same for any binary on the gateway host: omnish only spawns the process you configure."},{title:"Optional: local model CLIs",body:"If your agent stack sometimes calls a local inference CLI (e.g. Ollama), you can still register it in a recipe \u2014 same pattern. That is optional plumbing, not the core story: Prefer scripts on disk for awkward HTTP/JSON so you are not pasting payloads into chat."},{title:"Limits and safety",body:"\u2014 see configuration. \u2014 gated built-ins; leave off unless you understand the risk. Secrets: use env vars on the gateway host or your secret store; do not paste keys into chat."},{title:"Discoverability",body:", Cowork: \u2014 scheduled and on-demand tasks, notifications, logs (name unchanged)."},{title:"See also",body:"User guide \u2014 shortcuts vs Configuration Interactive sessions Background jobs vs Cowork"}],keywords:["system","agents","multi-agent","and","/run","omnish","is","built","for","on","your","machine","not","another","hosted","model","or","subscription","you","run","hardware","control","from","chat","drive","clis","tuis","such","as","own","scripts","multi-step","orchestrators","forwards","deterministic","shell","pty","to","those","tools","it","does","replace","them","curated","mobile","notifications","default","macro","command","recipesmacrodefaultcommand","per-chat","recipes","with","add","optional","local","limits","safety","discoverability","see","also"],relatedCommands:["/run help","/apps help","/run","/apps start","/apps attach","/jobs","/cowork","/cw","/features","/sessions","/apps","/run queue","/run-queue"]},{id:"docs-guides-tunnel-setup-from-zero",path:"docs/guides/tunnel-setup-from-zero.md",title:"Tunnel setup from zero (gateway host)",summary:"This guide is for someone who wants public URLs for apps running on the machine where executes, using WhatsApp or Telegram (optional commands) or the CLI.",sections:[{title:"What runs where",body:"Piece Where Role ------------------ ------------------------------------------------------ --------------------------------------------------------------- Relay VPS (e.g. ) or your own Docker host Accepts HTTPS/WSS, forwards to connected clients omnish gateway Your PC / server \u2014 runs shell, chat commands, and tunnel client Your app Same host as the gateway (usually) , etc. The default public relay is . You can self-host from and point clients at your origin."},{title:"Checklist",body:"Install omnish on the gateway host Native modules may require a normal install (not ). See the project README. Link WhatsApp and/or Telegram, allow yourself ```bash omnish link omnish allow +YOURE164"}],keywords:["tunnel","setup","from","zero","gateway","host","this","guide","is","for","someone","who","wants","public","urls","apps","running","on","the","machine","where","executes","using","whatsapp","or","telegram","optional","commands","cli","what","runs","checklist"],relatedCommands:["/tunnel help","/tunnel login","/tunnel","/features","/tunneling","/contrib","/tunnel-relay","/docs","/testing-and-operations","/docker-gateway-golden-path","/wss","/or telegram","/auth","/signup"]},{id:"docs-guides-ui",path:"docs/guides/ui.md",title:"Browser setup UI (`omnish ui`)",summary:"Local-first configuration panel that edits the same as the CLI and chat commands. Use it when you want to finish basics from a phone on your LAN before touching WhatsApp/Telegram.",sections:[{title:"Quick start",body:"Default bind is (reachable on your LAN). The CLI prints: A setup token (saved under your data dir as ; legacy installs may have had , which is migrated automatically) URLs on localhost and discovered IPv4 LAN addresses A quick link that includes so your phone can authenticate in one tap Then open the UI in a browser, unlock with the token, edit core settings, and Save."},{title:"Run the gateway from the UI",body:"After configuration (and WhatsApp pairing if you use it), you can start the chat gateway without going back to the terminal: Under Host snapshot, use Start gateway. This is equivalent to (or ): a detached process runs with the same data directory, and stdout/stderr append to the default log file ( \u2014 the panel shows the resolved path). Stop gateway sends SIGTERM to the background gateway tracked by , same idea as (including stale pidfile cleanup when the process is already gone). Starting the gateway does not stop the setup UI. You can Stop setup server and leave the gateway running in the background. Authenticated session cookies are required (same as the rest of the panel): and . Port reclaim and stopping the UI The host writes in your data dir with the last process PID and bind port. When you start again on the same (default 3789), the new process terminates the previous one if it is still running, so the port is usually free without manual . A different does not kill another UI instance on a different port (the state file is overwritten when the new server starts). In the browser, Stop setup server (Core settings) ends the HTTP process after your session authenticates \u2014 the page will disconnect; run on the machine again to continue. Same trust as the rest of the panel: only someone with the setup token can unlock a session that can call ."},{title:"WhatsApp pairing (QR in the browser)",body:"You can link this host to WhatsApp without a terminal QR: Stop the gateway on this machine if it is running: use Stop gateway in the UI, , or stop your service. The UI checks for a live process and refuses browser pairing while it is present \u2014 two Baileys clients must not use the same directory at once. Unlock the UI with your setup token. Under Host snapshot, open WhatsApp pairing (QR) and choose Start QR pairing. On your phone: WhatsApp \u2192 Settings \u2192 Linked devices \u2192 Link a device, then scan the QR shown in the browser. If this host is already linked and you need a fresh login, enable Replace session (clears saved WhatsApp auth on disk, same idea as ) and confirm. You can still use from a shell on the host if you prefer the terminal QR. Pairing events are delivered over an authenticated same-origin event stream ( ) after you call . Traffic remains plain HTTP on the LAN \u2014 same trust model as the rest of the UI."},{title:"Security model (read this)",body:"Same trust as editing config on disk. Anyone who can change settings here could affect gateway behavior after reload/restart. LAN exposure: binding to all interfaces means anyone on your Wi\u2011Fi/Ethernet who can reach the port must not guess the token. Treat the token like a password. Not HTTPS: traffic is plain HTTP on your network segment v1. Run behind a reverse proxy with TLS only if you extend exposure beyond the LAN. Reduce exposure \u2014 loopback only (no phone from LAN). Firewall \u2014 block TCP 3789 (or your ) from the WAN side of your router. Guest Wi\u2011Fi \u2014 avoid running on networks shared with untrusted devices."},{title:"Flags",body:"Flag Meaning ---------------------------------- ----------------------------------------------------------- / Bind address (default ). / TCP port (default ). / Set or rotate the setup token (stored in )."},{title:"Environment",body:"Variable Meaning ------------------------- ------------------------------------------------------------------------------------------- Absolute path to a directory containing (advanced override for custom builds). Legacy alias for ."},{title:"Build note for contributors",body:"The UI is built into during / . If static files are missing, run the repo build from the project root so Vite runs before the esbuild CLI bundle."}],keywords:["browser","setup","ui","omnish","local-first","configuration","panel","that","edits","the","same","as","cli","and","chat","commands","use","it","when","you","want","to","finish","basics","from","phone","on","your","lan","before","touching","whatsapp/telegram","quick","start","run","gateway","whatsapp","pairing","qr","in","security","model","read","this","flags","environment","build","note","for","contributors"],relatedCommands:["/help","/configuration","/config set","/telegram","/stderr append","/logs","/gateway","/api","/start","/stop","/shutdown","/wa","/link"]},{id:"docs-guides-user-guide",path:"docs/guides/user-guide.md",title:"User Guide - omnish",summary:"Complete manual for using omnish's features.",sections:[{title:"Table of Contents",body:"Introduction Basic Shell Commands Background Jobs Interactive Sessions Terminal interactive ( ) File Transfer System Commands User Shortcuts User shortcuts vs recipes Cowork (scheduled tasks) Working with Multiple Platforms Configuration Management Chat LLM fallback (optional) Best Practices"},{title:"Introduction",body:"omnish bridges your messaging chats to your system shell with these main execution surfaces: Sync Shell: Immediate command execution (prefix: ) Background Jobs: Asynchronous commands with streaming output ( ) Cowork: Saved commands on a schedule or on demand ( ) while the gateway runs \u2014 see Cowork (scheduled tasks) Interactive Sessions: Terminal sessions via PTY ( ) Terminal REPL ( ): The same slash/ interface in your local shell\u2014see Interactive terminal (not gated by the inbox allowlist; same trust as your login session). Each chat maintains its own working directory and state, making it perfect for team workflows and personal use. The CLI REPL uses a dedicated session key and starts in your current directory when you launch ."},{title:"Basic Shell Commands",body:"Synchronous Execution Commands prefixed with execute immediately in the shell: Working Directories Each chat maintains its own working directory: Use to change directories Changes persist across commands View current directory with Command Prefix The prefix can be customized in : Without prefix, use free shell mode (see below)."},{title:"Background Jobs",body:"Start long-running commands without blocking your chat. Full detail: Background jobs. Starting Jobs Job Management Job IDs are 8-character hex strings; you can also assign a name with (or ). / / accept either form. Use (or ) to get a chat message when the job exits. Job Output and limits Logs persist under with matching . Jobs use the chat session cwd; they inherit the gateway process environment (not a prior from another shell). applies to sync commands only, not to children."},{title:"Interactive Sessions",body:'Start full terminal sessions in your chat: Starting Sessions Session Management Session Features Maximum 5 sessions per chat (configurable) One session "attached" at a time Plain text goes to attached session Output debounced and chunked ANSI codes can be preserved Session Commands Session Lifecycle'},{title:"Terminal interactive (`omnish i`)",body:"From a terminal on the same machine, run (or ) to type the same commands you would send in WhatsApp or Telegram: , , , , , etc. Trust: The inbox allowlist does not apply\u2014only local OS users who can run processes as you can start . Outbound files: Use or while is active; see Interactive terminal and Files send/receive. Media: , , \u2014 download, transcribe, and edit media (yt-dlp + ffmpeg + optional Whisper) \u2014 see media commands. Jobs: in this REPL belongs only to this process, not to the gateway\u2019s job list. Full reference: Interactive terminal."},{title:"File Transfer",body:'Sending Files Receiving Files When someone sends media files: Files are automatically downloaded Saved to organized directory structure Reply with "Saved: /path/to/file" File Location Control'},{title:"System Commands",body:"Gateway Control Notes: Aliases: and work like . is accepted as an alias for . prints command help. Service management from chat and are gated by (same trust as shell). Allowlist Management Cluster (optional, chat-driven) When is true on multiple machines linked to the same WhatsApp number, only the active host replies. Coordination flows through the chat itself \u2014 no shared file, no Syncthing. Shorthand: \u2026 and the long form \u2026 (only the exact token matches; does not). See Cluster and chat configuration. Config from chat View or change many keys without SSH (same trust as shell): Allowlists stay / , not . Full list: Cluster and chat configuration. Other Commands"},{title:"User Shortcuts",body:"Create chat-specific command aliases: Creating Shortcuts Using Shortcuts Parameterized shortcuts ( ) Put in the saved command where runtime text should go. Everything after the shortcut name on the same line is substituted in (trimmed; not shell-parsed). Shortcuts without still work as bare or only (extra words on the line are ignored). Shortcuts with require input; bare or returns a usage hint. Input length follows the same cap as tasks when is set. Managing Shortcuts Scope model: / : private to this chat. / : shared on this gateway across chats. Resolution order for or : chat shortcut first, then shared."},{title:"User shortcuts vs `/run` recipes",body:"Both features are per-chat (stored on the gateway host), but they solve different problems: Shortcuts ( , , ) recipes ( , ) --------------- ---------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Purpose One-line alias: expand to a saved message once (e.g. ). Optional substitutes chat text inline. Parameterized runner: inject your task text into a CLI via (and optional ), then start a detached app session (PTY) by default \u2014 use or when you want plain DMs to go to the agent. still attaches on start. Typical use Jump to a directory, repeat a favorite sync command, short macros, custom wrappers with varying text. Drive system agents (e.g. , , your orchestrators) from chat with a task each time. Invocation or ; with in the body: or . \u2014 task is required for named recipes. Shortcuts substitute in the expanded line only (no shell env). Recipes require the stored command to reference (or a custom ) and run in a PTY session. For system agents, multi-agent PTY, and , see System agents and . Queued recipe runs (same chat, one PTY at a time): or , then for status. You can also batch-enqueue from JSON with (file path, inline JSON, or an uploaded file with that caption); see Run queue for the exact JSON shape and limits. Multi-step runbooks: create a recipe with sequential steps using : Steps are separated by or newlines. Each step runs in order; if any step fails (non-zero exit), remaining steps are skipped. Prefix a step with to continue on failure: When you invoke a runbook ( ), it runs in the background and sends a formatted per-step summary when complete. Use to see the step listing. Useful management forms:"},{title:"Cowork (scheduled tasks)",body:"Cowork saves shell commands and runs them on a schedule or on demand while is active. Commands use the same shell, timeout, and byte limits as sync commands. Alias: . Default log directory: (override with ). Missed scheduled times catch up when the gateway returns (one oldest slot per tick). Disabled tasks reject until re-enabled. Conditional notifications: only sends a notification when the task fails. tracks ok/fail transitions and only alerts on flips (useful for frequent health checks). Full reference: Cowork feature doc (notifications, data files, cluster and WhatsApp caveats)."},{title:"Working with Multiple Platforms",body:"Multi-Platform Setup Configure for both platforms: Platform Differences Feature WhatsApp Telegram -------------- --------------- ----------------------------- Message Limit 3500 chars 4096 chars Format Plain text HTML support File Types All media types Photo, document, video, audio Authentication QR code Bot token Best Practices Use different users for different platforms Keep allowlists minimal Monitor usage across platforms Use platform-specific features appropriately"},{title:"Chat LLM fallback (optional)",body:"If is true in on the gateway host, plain messages that would otherwise show \u201CNo command matched\u201D are answered asynchronously by a subprocess you configure ( ). Replies go back to the same WhatsApp/Telegram chat (or to the terminal in ). Where to configure: the same file as the rest of omnish \u2014 default , or if set. You edit JSON on the machine where runs; API keys must be available in that process\u2019s environment if your wrapper needs them (restart the gateway after changing systemd/shell env). Full step-by-step instructions, stdin vs PTY, and environment forwarding: Chat LLM fallback."},{title:"Configuration Management",body:"Configuration File Location Default: Override with environment variable Legacy: Uses if doesn't exist Editing from chat Most tunables support (whitelist). See Cluster and chat configuration and Configuration guide. Key Configuration Options Environment Variables : Override data directory : Enable debug logging (legacy: ) : Override bot token"},{title:"Best Practices",body:`Security Minimal Allowlists: Only add trusted users No Wildcards: Explicit phone numbers only Monitor Usage: Regularly check allowlists Session Isolation: Each chat is isolated Performance Output Chunking: Large outputs automatically split Session Limits: Don't exceed max sessions Timeout Settings: Adjust for your use case Log Rotation: Monitor log file sizes Usage Patterns Use Sessions for Interactive Apps: Vim, Python REPL, etc. Use Jobs for Long Operations: Builds, data processing Use Shortcuts for Common Tasks: Reduce typing Organize with Working Directories: Keep related work together Troubleshooting "Not in allowlist": Check E.164 format (+15551234567) "Maximum sessions reached": Stop unused sessions Messages cut off: Check Connection issues: Use for debug logs`},{title:"Next Steps",body:"Configuration: Customize for your workflow Security: Learn about the security model Features: Explore advanced features Integration: Set up for your team"}],keywords:["user","guide","omnish","complete","manual","for","using","features","table","of","contents","introduction","basic","shell","commands","background","jobs","interactive","sessions","terminal","file","transfer","system","shortcuts","vs","/run","recipes","cowork","scheduled","tasks","working","with","multiple","platforms","chat","llm","fallback","optional","configuration","management","best","practices","next","steps"],relatedCommands:["/help","/docs help","/run","/bg","/cowork","/apps","/tmp","/path","/var","/log","/system","/features","/background-jobs","/bg npm"]}]};var Bn=fw;function Ga(e,t){return`${e.replace(/\/$/,"")}/${t}`}function bE(e){return e.toLowerCase().replace(/[^a-z0-9\s/-]/g," ").split(/\s+/).filter(t=>t.length>1)}function kE(e,t,n){if(t.length===0)return null;let o=e.title.toLowerCase(),r=e.summary.toLowerCase(),s=new Set(e.keywords),i=0,a,l=n.toLowerCase().trim();l.length>=3&&o.includes(l)&&(i+=40);for(let c of t){o.includes(c)&&(i+=12),r.includes(c)&&(i+=4),s.has(c)&&(i+=6),e.path.toLowerCase().includes(c)&&(i+=3);for(let u of e.sections){let d=u.title.toLowerCase(),m=u.body.toLowerCase();d.includes(c)&&(i+=5,a??=u.title),m.includes(c)&&(i+=2,a??=u.title)}}return i<=0?null:{entry:e,score:i,matchedSection:a}}function qa(e,t=12){let n=e.trim();if(!n)return[];let o=bE(n),r=[];for(let s of Bn.entries){let i=kE(s,o,n);i&&r.push(i)}return r.sort((s,i)=>i.score-s.score||s.entry.title.localeCompare(i.entry.title)),r.slice(0,t)}function za(e){let{entry:t,matchedSection:n}=e,o=n?`${t.summary.slice(0,120)} (${n})`:t.summary.slice(0,160);return{id:t.id,path:t.path,title:t.title,summary:o,relatedCommands:t.relatedCommands}}function fs(e){return Bn.entries.find(t=>t.id===e)}function Ka(e){let t=e.replace(/\\/g,"/").replace(/^\.\//,"");return Bn.entries.find(n=>n.path===t||n.id===t)}function Ya(e,t=1800){let n=[];e.summary&&n.push(e.summary);for(let r of e.sections){if(n.join(`
567
+ id: ${r.id}`)}return yw()}var ww={version:1,generatedAt:"2026-05-31T00:31:51.537Z",repoUrl:"https://github.com/eligapris/omnish/blob/main",entries:[{id:"docs-advanced-implementation",path:"docs/advanced/implementation.md",title:"Implementation Guide - omnish",summary:"For contributors and developers who want to understand and extend omnish.",sections:[{title:"Development Setup",body:"Prerequisites Node.js >= 20 pnpm package manager Native build tools (make, g++, etc.) Getting Started ```bash git clone https://github.com/eligapris/omnish.git cd omnish pnpm install"}],keywords:["implementation","guide","omnish","for","contributors","and","developers","who","want","to","understand","extend","development","setup"],relatedCommands:["/omnish","/github","/eligapris","/wa","/inbound","/tg","/gateway","/signal","/outbound","/index","/config","/stats"]},{id:"docs-advanced-troubleshooting",path:"docs/advanced/troubleshooting.md",title:"Troubleshooting Guide - omnish",summary:"Common issues and solutions for omnish.",sections:[{title:"Quick Reference",body:`Symptom Common Cause Solution ---------------------------------- ----------------------------------------- -------------------------------------------------------------------------- Connection failed Network issues Check connectivity "Not in allowlist" Wrong format Use E.164 format Sessions won't start Resource limits Check session limits Messages cut off Size limits Adjust config Telegram not working Bot token Verify token Attached: probe fails / WS 400 Proxy routes to wrong port Route , to control port 8788; Attached: online but no commands Platform allowlist / routing Dashboard allowFrom; relay logs Message yourself: complete silence Older builds dropped all traffic Update omnish + relay; send after upgrade`},{title:"Attached / platform mode",body:"Symptom: fails or errors with WebSocket Solutions: Reverse proxy must forward , , , , to relay control port (8788), not the HTTP edge (8787). See tunnel relay README. Run before . Confirm URL and token: . Symptom: Device shows online on dashboard but WhatsApp commands do nothing Solutions: Set allowFrom on the platform dashboard (attached mode uses platform policy, not local ). Check relay logs for with your device id. Only one should hold the device WebSocket per token. WhatsApp LID chats: ensure relay and CLI are up to date; use on the device and look for vs . with correct in logs but lists that number: the platform stores allowlist phones as digits only (CLI shows for display). Older CLI builds compared to raw digits and always denied \u2014 update omnish so normalizes platform entries. Symptom: Message yourself (or self-chat) \u2014 commands get no reply at all (not even \u201CNot allowlisted\u201D) Cause: WhatsApp marks messages you send from your phone as on linked devices. Older omnish builds ignored all upserts, so the documented Message yourself flow never reached the gateway. Solutions: Upgrade both the CLI ( ) and the platform relay ( ) to a version that tracks outbound message ids and routes allowlisted non-echo traffic. Confirm your number is on the platform allowlist (dashboard \u2192 Save allowlists). Run , then ; send or in Message yourself. Relay logs should show after you send a command. If you see nothing, WhatsApp may still be disconnected on the platform \u2014 check dashboard WhatsApp status. Standalone mode: same behavior applies; use and on the linked host. Symptom: warning at attach Solutions: Fix token, URL, or platform database (MongoDB on self-hosted relay). Until works, attached mode falls back to local allowlists. Full setup: Platform attached mode. Complete API/CLI/dashboard: Platform reference. WhatsApp link / unlink on platform Symptom Fix ------------------------------- ------------------------------------------------------------------------------------------------- QR never appears Dashboard Link WhatsApp or ; check relay logs Stuck after logout Reconnect on dashboard or then link again Import fails Stop local ; use Linked but dashboard shows idle with autostart; ensure volume persists Allowlists and persistence Symptom Fix -------------------------------------- ------------------------------------------------------------------------------------- Dashboard does not remember allowlists Relay needs ; click Save allowlists CLI changes not reflected on device Wait up to 5 min or restart (policy refresh) Telegram token missing in UI Expected \u2014 token is not returned; leave field empty and Link to reuse saved token"},{title:"Installation Issues",body:"macOS: commands fail / posixspawnp failed Symptom: and linking work, but the first (or ) fails. Logs or chat output may show or the gateway stops responding to shell commands. Cause: publishes the macOS binary without the execute permission bit in the npm tarball. omnish uses for every sync shell command and interactive PTY session. See microsoft/node-pty#850. Fix (automatic): omnish 1.6.6+ runs a script and a runtime guard that before the first PTY spawn. Update: Manual workaround (older installs): Replace with your global modules path if needed ( \u2192 ). npm 11+ allow-scripts warnings Symptom: After , npm prints warnings listing , , , etc., and mentions . Cause: npm 11.16+ Phase 1 install-script policy \u2014 advisory only; scripts still run by default. What to do: No action required for a successful install. Do not run from your home directory for global installs (it targets a project , not the global omnish tree). omnish ships an field covering its native dependencies. A future npm release may block unreviewed dependency scripts; omnish's manifest is prepared for that. Native Module Build Failures Symptom: Build errors during Solutions: ```bash"}],keywords:["troubleshooting","guide","omnish","common","issues","and","solutions","for","quick","reference","attached","platform","mode","installation"],relatedCommands:["/help","/wa help","/tg help","/platform","/v1","/control","/dashboard","/auth","/contrib","/tunnel-relay","/readme","/me failed","/me","/guides","/platform-attached-mode"]},{id:"docs-architecture-communication-layer-model",path:"docs/architecture/communication-layer-model.md",title:"Communication layer and omnish CLI \u2014 unified model",summary:"Single source of truth for how omnish works today and how the optional hosted communication layer fits. Implementation sketches: Communication layer \u2014 API & ops, Gateway config precedence.",sections:[{title:"Summary",body:"CLI installs anywhere ( ) and works without the hosted layer (standalone mode). Optional hosted communication layer exposes WhatsApp, Telegram, and future surfaces through an API. Users link messengers once on the platform. Many devices (Docker, VM, VPS, bare metal) run the CLI with a device token and talk to that layer over a long-lived channel. One messenger connection \u2192 many devices: the platform routes chat to the right attached CLI; replies return through the same layer. That single connection is what makes the platform useful for fleets and containers. Shell execution stays on each device. The communication layer is not a remote shell; the CLI is the agent (shell, PTY, files, tunnels) on the box."},{title:"Two modes",body:"Mode When Where messengers connect CLI role -------------- ------------------------------------------------------------ ------------------------------------- ----------------------------------------------------------------------------------------- Standalone No platform URL + token (default) On the same host as Full gateway: , , Attached + (or config / aliases) On the hosted communication layer Local executor + WebSocket client: register device, receive routed messages, send replies Both modes: allowlist and shell authority on the device. The platform may store policy in a dashboard, but the CLI enforces who may trigger commands before running shell on that host. Standalone (today) Documented in the README and Quick start. Baileys / Telegram bot clients run inside the gateway process; credentials live under . Attached (implemented) User links WhatsApp/Telegram on the platform dashboard (or ) \u2014 once per account. User sets platform URL + account token on each machine ( or env). On the target: , then . CLI opens WebSocket (fallback ), handles inbound messages like the standalone router, executes locally, posts replies. Setup: Platform attached mode. Full API/CLI/dashboard: Platform reference. No per-container WhatsApp QR is required for the default path."},{title:"Docker example (attached mode)",body:"A team runs an app in Docker and wants chat-driven ops inside that container without running Baileys in the image: Messengers stay on the communication layer; the container only needs outbound HTTPS to the layer API. See Docker gateway golden path."},{title:"Environment contract",body:"Purpose Canonical Also accepted --------------------------------- --------------------- ---------------------------------------------- Platform base URL , Account token (tunnel + attached) , Optional device id in config Config file keys: ( ), ( ), ( ). Precedence: env \u2192 \u2192 tunnel auth file. Implementation: ."},{title:"Security boundaries",body:"Concern Standalone Attached -------------------------------- ----------------------------- --------------------------------------------------------------- Who runs shell Local gateway Local CLI on device Messenger secrets on gateway host Communication layer (connectors) Stolen device token N/A Risk to that attach point; revoke in dashboard Platform sees message content N/A (direct to gateway) Yes, for routing \u2014 document retention and policy Remote shell from platform alone No No \u2014 requires valid device token + allowlisted sender on device Never echo full tokens in chat replies (same as tunnel tokens today)."},{title:"Relation to this repository",body:"Component Status --------------------------------------------------------------- -------------------------------------------------------------- Standalone gateway ( , ) Implemented in Attached mode client ( , platform WebSocket) Implemented in , Hosted communication layer (relay + dashboard) Implemented in (deploy separately)"},{title:"See also",body:"Platform attached mode \u2014 connect, link, configure, run Platform reference \u2014 complete API, CLI, dashboard, persistence Vision \u2014 hosted communication layer \u2014 short product summary Platform layer spec \u2014 API sketch, tokens, threat model Gateway config precedence \u2014 config merge in attached mode Relay operator README \u2014 deploy, paths, dashboard docs/ideas/ \u2014 historical voice-note inputs"}],keywords:["communication","layer","and","omnish","cli","unified","model","single","source","of","truth","for","how","works","today","the","optional","hosted","fits","implementation","sketches","api","ops","gateway","config","precedence","summary","two","modes","docker","example","attached","mode","environment","contract","security","boundaries","relation","to","this","repository","see","also"],relatedCommands:["/readme","/guides","/quick-start","/telegram on","/platform","/device","/control","/platform-attached-mode","/cli","/dashboard","/platform-reference","/tunnel"]},{id:"docs-architecture-gateway-config-precedence",path:"docs/architecture/gateway-config-precedence.md",title:"Gateway configuration precedence",summary:"How effective configuration is computed. See Communication layer model for standalone vs attached modes.",sections:[{title:"Sources (ordered low \u2192 high priority)",body:"Standalone ( without platform token) Compiled defaults \u2014 values baked into the binary when a key is unset. Local \u2014 under . Environment variables \u2014 e.g. , . Chat \u2014 allowlisted senders; same trust as shell. Higher layers win per key. Attached ( with platform URL + token) Compiled defaults Local \u2014 host paths, shell, tunnel, jobs, etc. Platform account ( ) \u2014 wins for policy keys: , , (derived from linked connectors on the platform). Not written back to disk by default. Environment variables \u2014 still override local file for host keys; do not replace platform allowlists unless you also change them on the dashboard. Chat \u2014 updates local file only in attached mode; platform allowlists remain authoritative for inbound from messengers on the layer. If fails at connect, the CLI logs a warning and uses local for allowlists (or partial data from the WebSocket ack: + connectors only)."},{title:"Keys that stay host-only (never taken from platform)",body:", job limits, sync paths, webhook ports/tokens on the device Baileys auth paths, local tunnel client settings WhatsApp session blobs and Telegram bot tokens in attached mode (connectors run on the platform)"},{title:"Keys owned by the platform in attached mode",body:", \u2014 set on the dashboard; device enforces the platform copy on inbound WebSocket messages. \u2014 derived from which connectors are linked on the platform ( , , or )."},{title:"Merge algorithm (attached inbound)",body:"```text effective = loadConfig() # local host + defaults if platform snapshot loaded: effective.allowFrom = platform.allowFrom effective.telegramAllowFrom = platform.telegramAllowFrom effective.gatewayMode = platform.gatewayMode"}],keywords:["gateway","configuration","precedence","how","effective","is","computed","see","communication","layer","model","for","standalone","vs","attached","modes","sources","ordered","low","high","priority","keys","that","stay","host-only","never","taken","from","platform","owned","by","the","in","mode","merge","algorithm","inbound"],relatedCommands:["/v1","/me","/config","/guides","/platform-reference","/config set","/tokens on","/platform","/account-sync","/default-device"]},{id:"docs-architecture-overview",path:"docs/architecture/overview.md",title:"Architecture Overview - omnish",summary:"",sections:[{title:"Executive Summary",body:"omnish is a secure, deterministic messaging-to-shell gateway that bridges messaging platforms (WhatsApp, Telegram) to system shell access. The architecture follows a thin transport adapter pattern with a unified core, explicit security controls, and comprehensive session management. Local control plane: When the gateway process ( ) is up, it may expose a localhost-only control endpoint (metadata in ) so a separate process can request outbound file sends that reuse the same Baileys/grammY sessions\u2014avoiding a second login. This is optional machinery for CLI ergonomics; inbound chat traffic still flows through the transports above."},{title:"Key Architectural Principles",body:"Thin Transport Adapter Pattern Transport Layer: Platform-specific implementations (WhatsApp, Telegram) Adapter Layer: Normalizes all inputs to format Core Layer: Unified business logic and message routing Persistence Layer: Configuration and state management Deterministic Behavior No AI or agent layer Rule-based message routing Predictable command precedence Explicit state management Security-First Design Explicit allowlists required No anonymous access Per-peer session isolation Minimal attack surface"},{title:"System Architecture",body:"High-Level Diagram Component Responsibilities Layer Component Responsibility --------------- --------------- -------------------------------------------- Transport WhatsApp WebSocket lifecycle, QR auth, media handling Telegram Long polling, bot API, formatting Adapter Inbound Normalize messages to Outbound Platform-specific message sending Core Gateway Transport multiplexing, lifecycle management Router Command dispatch with precedence rules Session Manager Per-peer working directories and state Apps Manager Interactive PTY session lifecycle Job Manager Background process execution Persistence Config Configuration loading and validation Sessions JSON-based state persistence Files Log files, session output, job output"},{title:"Data Flow Architecture",body:"Message Processing Pipeline Command Precedence Flow Messages are processed in strict precedence order: Free Shell Toggle: / Sync Commands: (e.g., ) System Commands: (e.g., , ) App Shorthand: (e.g., ) Free Shell Mode: Plain text execution Attached App: Forward to focused PTY session Help Fallback: Display help message"},{title:"Security Architecture",body:"Allowlist System Peer Key Model WhatsApp: or normalized phone number Telegram: Hashed for storage: SHA-1 prefix for log directories Session isolation: Each peer has separate state Security Boundaries Transport Boundary: Platform-specific authentication Authorization Boundary: Allowlist validation Execution Boundary: Command and process isolation Data Boundary: Per-peer state separation"},{title:"Session Management Architecture",body:"Session Context Persistence Session Lifecycle Load: From JSON storage or create default Update: Persist changes immediately Isolate: Per-peer separation with migration support Cleanup: Remove on session destruction"},{title:"Transport Architecture",body:"Transport Interface All transports implement the same core interface: Transport-Specific Features Transport Authentication Message Limit Features --------- -------------- ------------- -------------------------------- WhatsApp QR code 3500 chars Media download, auto-reconnect Telegram Bot token 4096 chars HTML formatting, webhook support"},{title:"Output Processing Architecture",body:"Output Flow Output Processing Pipeline Buffer: Collect output chunks Debounce: Wait for output completion ( ) Chunk: Split into transport-sized pieces Format: Strip ANSI, add prefixes, etc. Send: Deliver via appropriate transport"},{title:"State Management Architecture",body:"State Types Configuration: Global settings Session Context: Per-peer state Job State: Background job metadata App State: Interactive session state Persistence Strategy State Type Format Location Access Pattern ---------- ------ ------------------------- --------------------------------- Config JSON Load at startup, update on change Sessions JSON Load on demand, append updates Jobs Files Streaming write, read on demand Apps Files Streaming write, read on demand"},{title:"Error Handling Architecture",body:"Error Categories Transport Errors: Connection issues, timeouts Validation Errors: Invalid input, malformed commands Execution Errors: Command failures, process timeouts Resource Errors: Memory limits, session limits Error Handling Flow Capture: Error detected at source Log: Structured logging with context Notify: User-friendly error message Recover: Graceful degradation where possible"},{title:"Extension Points",body:"Transport Extension Command Extension Configuration Extension"},{title:"Performance Considerations",body:"Memory Management Session Limits: Prevent unbounded growth Output Buffering: Size-limited buffers Cleanup: Proper resource disposal Network Optimization Message Chunking: Minimize API calls Connection Reuse: Persistent connections Backoff Strategy: Exponential backoff for failures I/O Optimization Async Operations: Non-blocking file I/O Lazy Loading: Load data on demand Output Streaming: Real-time output delivery This architecture overview provides the foundation for understanding omnish's design principles and implementation patterns."}],keywords:["architecture","overview","omnish","executive","summary","key","architectural","principles","system","data","flow","security","session","management","transport","output","processing","state","error","handling","extension","points","performance","considerations"],relatedCommands:["/grammy sessions","/inbound","/gateway","/outbound","/jobs","/apps","/command","/bg","/job","/pty","/config","/sessions"]},{id:"docs-architecture-platform-layer-spec",path:"docs/architecture/platform-layer-spec.md",title:"Communication layer \u2014 API and operations sketch",summary:"Implementation detail for the unified model in Communication layer model. The attached CLI client is implemented in and . The hosted relay (connectors, dashboard, routing) lives in .",sections:[{title:"Goals",body:"Messenger connectors on the layer: WhatsApp, Telegram, others \u2014 linked once per workspace from a dashboard. Device registry and routing: many CLIs attach with ; inbound chat is routed to the correct device; replies return through the layer. Token issuance and lifecycle from the dashboard (create, rotate, revoke). Minimal env on each device: + (legacy aliases: , , ). Non-goals: Running user shell or PTY in the cloud. Replacing local enforcement of allowlists before command execution on each device. Requiring the hosted layer for open-source standalone use ( on the host without env)."},{title:"Operating modes (messenger termination)",body:"Mode Messenger clients Device (CLI) -------------- ------------------------------------------------------------- -------------------------------------------- Standalone On gateway host ( , Baileys / Telegram bot today) Same process as messengers Attached On communication layer (connectors) CLI only: API/WebSocket client + local shell See Communication layer model \u2014 Two modes."},{title:"Threat model (summary)",body:"Asset Risk Mitigation ----------------------------- ----------------------------------- -------------------------------------------------------------------------------------------------- Device token Theft \u2192 control of one attach point Short TTL, rotation, revoke; scoped to one device slot Workspace / dashboard session Account takeover Strong auth, audit log on connector and device changes Message content on layer Privacy / retention Policy, encryption in transit, minimal logging; no training on content by default (product policy) Signed config blob Forged defaults on device Signing keys, , host-only keys; see Gateway config precedence Chat as secret channel Token pasted in DM Never echo full secrets in replies Trust boundary: The communication layer routes messages and holds connector credentials in attached mode. Each CLI enforces allowlists and runs shell locally. Layer compromise must not run shell on a device without a valid device token and allowlisted sender on that device."},{title:"Token scopes (recommended)",body:"Scope Purpose Typical lifetime ------------------- ------------------------------------------------------ --------------------- Long-lived channel for one CLI attach point Rotatable; per device One-time or short exchange when creating a device slot Minutes Fetch non-secret config subset for a device Hours\u2013days Relay bearer (may be issued by layer) Per relay policy Human dashboard (not for CLI) Session Rules: narrow scopes, stable token ids for revocation, no user-facing \u201Cgod\u201D token."},{title:"Implemented API (this repository)",body:"The relay implements a single-account model (not multi-workspace). Authoritative route list, request bodies, and CLI commands: Platform reference Summary: Area Implemented paths -------- ------------------------------------------------------------------ Auth , Account , , Devices , Telegram WhatsApp , , , Attach , UI"},{title:"Future API sketch (not implemented)",body:"The following were early design notes; do not assume they exist on the relay today: Multi-workspace ( ) Separate device tokens per slot (today: one account bearer token) , scoped bootstrap tokens SSE alternative to WebSocket"},{title:"Compliance and logging",body:"Attached mode: layer may process message content for delivery; document retention and access in privacy policy. Avoid logging message bodies in application logs by default."},{title:"Relation to this repository",body:"Communication layer service: (deploy separately; MongoDB + dashboard). CLI attached mode: , \u2014 WebSocket client + local shell. Standalone: / on the same host (unchanged when platform env is unset). Operator guides: Platform attached mode, Platform reference."}],keywords:["communication","layer","api","and","operations","sketch","implementation","detail","for","the","unified","model","in","attached","cli","client","is","implemented","hosted","relay","connectors","dashboard","routing","lives","goals","operating","modes","messenger","termination","threat","summary","token","scopes","recommended","this","repository","future","not","compliance","logging","relation","to"],relatedCommands:["/platform","/gateway","/attached","/tunnel-relay","/contrib","/guides","/platform-reference","/platform-attached-mode","/websocket client","/auth","/signup","/login"]},{id:"docs-architecture-routing",path:"docs/architecture/routing.md",title:"Message Routing Architecture - omnish",summary:"",sections:[{title:"Introduction",body:"The message routing system is the heart of omnish, responsible for determining how incoming messages are processed and executed. It follows a deterministic precedence model with clear rules for each message type."},{title:"Routing Overview",body:"Message Flow Core Components Allowlist Validator: Ensures user has permission Message Normalizer: Converts transport-specific formats Session Loader: Retrieves per-peer state Router: Applies precedence rules and dispatches Executors: Command-specific handlers"},{title:"Message Precedence Rules",body:"Precedence Hierarchy Messages are evaluated in strict order from highest to lowest precedence: Free Shell Mode Toggle ( / ) Synchronous Commands ( ) System Commands ( ) App Session Shorthand ( ) Attached App Sessions (when focused and running) Lone-URL auto-download ( or LLM off \u2192 background ) Free Shell Mode (when enabled) Chat LLM fallback (optional plain-text handler when configured) Help Fallback ( ) Rule Implementation"},{title:"Command Types and Handling",body:"Free Shell Mode Toggle Purpose: Enable/disable direct shell execution Pattern: or Synchronous Commands Purpose: Execute shell commands immediately Pattern: System Commands Purpose: Control omnish functionality Pattern: App Session Shorthand Purpose: Quick interaction with app sessions Pattern: Free Shell Mode Purpose: Execute any text as shell command Pattern: Plain text (when enabled) Attached App Sessions Purpose: Send input to focused PTY session Pattern: Plain text (when session focused)"},{title:"Special Cases and Edge Handling",body:"Shortcut Expansion After stripping the sync prefix ( by default) or when an unknown slash is not a built-in command, omnish calls : only \u2014 expand the stored body (legacy bare shortcuts). \u2014 if the body contains , substitute trimmed input for every placeholder; if the placeholder is present but input is missing, reply with a usage error. Bodies without ignore extra words after the name. Expansion re-enters once with (no nested shortcut expansion). Command Precedence Override Certain commands always take precedence: Error Handling"},{title:"Session Integration",body:"Session Context The router uses session context for state: Session Management"},{title:"Transport Integration",body:"Peer Key Normalization Transport-Specific Handling"},{title:"Performance Considerations",body:"Optimization Strategies Session Caching: Cache frequently accessed sessions Command Caching: Cache command results for identical inputs Output Debouncing: Buffer output to reduce message spam Lazy Loading: Load sessions only when needed Concurrency Handling"},{title:"Extension Points",body:"Adding New Command Types Custom Command Handlers"},{title:"Fleet and config slash commands",body:"The authoritative dispatch order for messages that start with is implemented in (not the pseudocode snippets earlier in this doc). Highlights: runs before fleet aliases so paths like never collide with . Fleet commands accept , , or ( ): only a standalone token or + rest matches \u2014 paths such as do not match the shortcut. (also , ) sets ; reapplies config while is active. See Cluster and chat configuration for behavior and Configuration guide for fields."},{title:"Testing and Validation",body:"Unit Tests Integration Tests This message routing architecture ensures predictable, efficient processing of all incoming messages while maintaining security and session isolation."}],keywords:["message","routing","architecture","omnish","introduction","overview","precedence","rules","command","types","and","handling","special","cases","edge","session","integration","transport","performance","considerations","extension","points","fleet","config","slash","commands","testing","validation"],relatedCommands:["/command","/dl","/dl job","/disable direct","/name","/new","/path","/router","/config","/config set","/computers","/pcs"]},{id:"docs-architecture-security",path:"docs/architecture/security.md",title:"Security model",summary:"omnish bridges WhatsApp and/or Telegram direct messages to shell commands on your machine. Security is explicit allowlists plus local filesystem and process boundaries. There is no cloud relay for commands: whoever can send messages as an allowed identity can execute code as the OS user running .",sections:[{title:"Trust boundaries",body:"Allowlisted identities are credentials. If an attacker can spoof or compromise an allowed WhatsApp number or Telegram user id, they get the same power as you gave on that host. Wildcards are rejected. must never contain . Secrets on disk: WhatsApp session material lives under the data directory ( / / legacy ). Telegram bot tokens live in or . Restrict permissions so other Unix users cannot read them. Chat config: lets allowlisted users edit from chat (same trust as running shell commands). Do not treat DMs as \u201Clow privilege.\u201D Multiple Telegram bots: If and several hosts run Telegram, use a different bot token per host; one token cannot be long-polled twice (see finding in ). Interactive terminal ( ): Same trust as running a shell on that machine. It is not gated by WhatsApp/Telegram inbound allowlists\u2014those lists still apply to messages arriving through the gateways. Gateway control ( ): Written only while is active; carries localhost connection info and a token so can request outbound media through the existing gateway. Anyone who can talk to 127.0.0.1 as your user could misuse it\u2014treat host-local access like filesystem access. : Sends files to arbitrary WhatsApp numbers or Telegram chats through your linked session, comparable to sending those files manually from the linked WhatsApp device or bot\u2014powerful and intentional. Tunneling ( , when ): Publishes public URLs to local HTTP/TCP ports. Anyone with the URL can reach the forwarded service; chat tunneling is gated by the same allowlists as shell commands."},{title:"Automated posture checks",body:"The same rules run in three places: Surface How ------- ------------------------------------------------------------------------------------------------------------------------- CLI (plain text) or Chat , , Startup refuses to start if any error-severity finding is present; warnings are printed but do not block Automated checks cover configuration and local Unix permissions. They do not replace firewall rules, SSH hardening, malware scanning, or review of who physically accesses the machine. Severity levels error \u2014 Blocks until fixed (or until you change / token so the check no longer applies). warn \u2014 Shown on startup and in reports; you should understand and usually fix. info \u2014 Informational (for example, env var overrides)."},{title:"Finding codes and remediation",body:"Code Severity Meaning What to do ---------------------------- -------- --------------------------------------------------------------------------------- --------------------------------------------------------------- error contains Remove from in . error Telegram transport enabled but no token Set in config or . error Configured binary does not exist Install the shell or point at a valid absolute path. error is but path empty Set or change mode. error Fixed receive path not absolute Use an absolute . warn WhatsApp enabled, empty warn Telegram enabled, empty warn is true Set to unless you trust every recipe. warn is not an absolute path Use e.g. in config. warn Could not stat Fix permissions/path. warn group/world readable or writable on config file. warn Process UID is root Run as a normal user when possible. warn Data directory group/world accessible on data dir ( or default ). warn Jobs directory group/world accessible (only checked if data dir is already tight) on the jobs directory under the data dir. warn WhatsApp auth dir readable by others on under the data dir. info set; overrides config Unset env if you want config file to win. info Cluster + Telegram: same token must not be used on two running gateways Use one bot per host or run Telegram on fewer machines. Unix permission checks are skipped on Windows."},{title:"JSON output for automation",body:"prints: Exit code if any error-severity finding exists (same as plain text)."},{title:"Related commands",body:"\u2014 Includes a one-line security summary. In chat: for a short hardening checklist. See also the user guide Security section in User Guide."}],keywords:["security","model","omnish","bridges","whatsapp","and/or","telegram","direct","messages","to","shell","commands","on","your","machine","is","explicit","allowlists","plus","local","filesystem","and","process","boundaries","there","no","cloud","relay","for","whoever","can","send","as","an","allowed","identity","execute","code","the","os","user","running","trust","automated","posture","checks","finding","codes","remediation","json","output","automation","related"],relatedCommands:["/security help","/security summary","/or telegram","/config set","/telegram","/sendto","/tunnel","/tcp ports","/security","/security tips","/run","/bin","/bash"]},{id:"docs-features-background-jobs",path:"docs/features/background-jobs.md",title:"Background jobs \u2014 omnish",summary:"Background jobs run a single shell command asynchronously in the current chat\u2019s working directory while you keep using the chat. Output is appended to a log file under your data directory; you can pull the last N lines or only new bytes since your last for that job in this chat.",sections:[{title:"Commands (implemented)",body:"Command Meaning ----------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------- Start a background job; reply includes an 8-char job id and hints for and . \xB7 \xB7 Same as , but stores a name you can use instead of the hex id with , , and . \xB7 Start a job that sends a completion notification to your chat when it exits (includes exit code, duration, and command summary). Combine flags: named job with completion notification. List recent jobs (up to 20), newest first: id (and name if set), status ( \\ \\ ), exit code, duration, command preview. Last lines of the log (default from ; max 500 if you pass a number: or ). Incremental: new log bytes since your last of this job (resolved id) in this chat (per-chat cursor). Send SIGTERM to the running process (or best-effort to the recorded pid if the child already detached). Job ids are 8-character hex (e.g. ). Names are optional labels (letters, digits, , , , up to 64 characters). If several jobs share the same name, / / resolve to the newest (most recently started) matching job. Resolving -shaped tokens: if a job with that id exists on disk, the token is treated as an id; otherwise it is treated as a name (so a name that looks like 8 hex digits still works when no job file uses that id). There is no , , or in chat \u2014 use and / instead. ( exists for gateway shutdown; it is not exposed as a slash command.)"},{title:"Behavior details",body:"Working directory: Same as session cwd ( applies before ). Environment: Inherited from the omnish gateway process (the Node process running ), not from a prior in chat \u2014 each and runs a new shell. Set env vars in the shell that starts the gateway, or put them in the job command line. Timeouts: applies to synchronous commands, not to children. Long-running jobs are not auto-killed by that setting. Logs: and (see )."},{title:"Configuration",body:""},{title:"Job lifecycle (actual statuses)",body:"From : running \u2014 child spawned; log growing. done \u2014 process exited (or spawn error); / recorded in meta. killed \u2014 user ran (SIGTERM); meta updated. Optional field is stored in when you start a job with / ."},{title:"Completion notifications",body:"When you start a job with (or ), the gateway sends a message to your chat as soon as the process exits: The notification includes: Exit code (0 for success, non-zero for failure) or signal name (e.g. SIGTERM) Duration of the run Command that was executed This is useful for long builds, deployments, or test suites where you want to walk away and get notified when it finishes. Combine with for readability: Notifications are sent via the same transport as the chat (WhatsApp or Telegram). If the gateway shuts down before the job finishes, the notification is lost."},{title:"Examples",body:"Same using the printed id:"},{title:"Compared to Cowork",body:"---------------- --------------------------- -------------------------------------- Scheduling One-shot , , , etc. Notifications Opt-in per job ( ) Per task ( + ) Queue / catch-up No Yes (SQLite + pending queue) Typical use Ad hoc long command Recurring or on-demand saved tasks See cowork.md."},{title:"Troubleshooting",body:"No output in chat: Streaming behavior depends on the gateway; use or for the log file. Lost jobs on gateway restart: In-memory handles are cleared; log and meta files on disk may still be present under . Verbose gateway logs: ---"},{title:"Change log \u2014 named background jobs (2026-05-08)",body:"Area Change -------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ; , , , ; . Parse named ; , , accept id or name via ; lists name when set. help and main help bullet updated for names. Unit tests for parsing, validation, and name/id resolution. User-facing docs This file, user-guide.md, quick-start.md, README.md, comprehensive-documentation.md, practical-guide-for-agents.md, CHANGELOG.md."}],keywords:["background","jobs","omnish","run","single","shell","command","asynchronously","in","the","current","chat","working","directory","while","you","keep","using","output","is","appended","to","log","file","under","your","data","can","pull","last","lines","or","only","new","bytes","since","for","that","job","this","commands","implemented","behavior","details","configuration","lifecycle","actual","statuses","completion","notifications","examples","compared","cowork","troubleshooting","change","named","2026-05-08"],relatedCommands:["/bg","/jobs","/tail","/log","/kill","/log abcdef12","/log mybuild","/stats","/history","/kill all","/shell","/src"]},{id:"docs-features-chat-agent-daemon",path:"docs/features/chat-agent-daemon.md",title:"Chat agent daemon (optional)",summary:"When is true and is set, plain inbound chat (that would otherwise hit \u201CNo command matched\u201D or LLM fallback) is queued to a long-lived process on stdin\u2014one daemon per peer by default. The gateway does not stream the child\u2019s stdout/stderr to mobile; the process should send curated updates with (same model as detached ).",sections:[{title:"Enable",body:"Edit on the gateway host: Copy and customize , or point at your own wrapper. ---"},{title:"Notify environment (parity with `/run`)",body:"On spawn, omnish merges the same notify env as recipe/app starts: Variable Purpose ---------------------------- ----------------------------------------------------------------------------------------------- Initiator peer ( / ) Template: Board-style guidance when is true (daemon sessions are treated as muted) Use or on milestones (ack, progress, question, done, blocked)\u2014not every log line. See also Curated mobile notifications. ---"},{title:"Chat commands",body:"Command Action --------------- -------------------------------------------- List running daemons and queue depth Stop daemon for current peer Stop and clear daemon state for current peer Short help ---"},{title:"Related keys",body:"Key Default Purpose ---------------------- ------- ---------------------------------------------------------- Master switch Shell command for (long-lived; reads stdin) One process per peer; = shared daemon Max queued lines per daemon before reject Include on spawn ---"},{title:"See also",body:"Chat LLM fallback \u2014 one-shot subprocess per message System agents and"}],keywords:["chat","agent","daemon","optional","when","is","true","and","set","plain","inbound","that","would","otherwise","hit","no","command","matched","or","llm","fallback","queued","to","long-lived","process","on","stdin","one","per","peer","by","default","the","gateway","does","not","stream","child","stdout/stderr","mobile","should","send","curated","updates","with","same","model","as","detached","enable","notify","environment","parity","/run","commands","related","keys","see","also"],relatedCommands:["/stderr to","/sendto peer","/run","/config","/absolute","/path","/to","/your-chat-agent-wrapper","/chat-agent-wrapper","/scripts","/app starts","/agent-notify"]},{id:"docs-features-chat-llm-fallback",path:"docs/features/chat-llm-fallback.md",title:"Chat LLM fallback (optional)",summary:"When this feature is on, a plain inbound message that would normally get \u201CNo command matched\u201D is handled silently: omnish runs your in the background (with limits and a restricted environment), then sends the subprocess output back to the same WhatsApp or Telegram chat. In , the reply is printed to the terminal instead.",sections:[{title:"Where to change settings",body:"What Where -------------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------- Config file . If is unset, omnish uses (or the legacy tree if does not exist). Template / all keys Repo root \u2014 copy the entries into your real . Confirm data directory Run on the gateway host, or . Edit the file with any text editor on the gateway host (SSH, local console, or your usual config workflow). Valid JSON is required (trailing commas are not allowed). Reload behavior: The gateway calls when handling each inbound message, so changes to fields in normally apply on the next message without restarting . API keys and environment variables: The subprocess receives a filtered copy of the gateway process environment (see below). Keys such as are only present if they were set when the gateway was started. If you add or change API keys in the shell profile or systemd unit, restart so the parent process picks them up. ---"},{title:"How to enable (step by step)",body:"Open (or ) on the machine that runs . Add or merge these fields (defaults shown; enable only when ready): Set to a single shell command passed as the argument to (same pattern as sync commands). Use an absolute path to a wrapper script if the default in the sandbox is too minimal. Smoke test: With the gateway running, send a plain line from an allowlisted chat (not starting with or your , not text). You should get no immediate \u201CNo command matched\u201D reply; after the subprocess finishes, the combined stdout/stderr (trimmed and capped) is sent back. Sandbox / real isolation: omnish runs the command in a dedicated working directory (temp dir under the data directory unless is set) and a reduced env. For Docker, , or other tools, put that logic inside your wrapper and point at it. ---"},{title:"What the subprocess receives",body:"Shell: Config key (e.g. ). Working directory: if non-empty (created if needed); otherwise a new temp directory under the omnish data dir for that run (removed afterward). Stdin (default): The inbound chat text (truncated to ). If is true, stdin is not piped the same way; use (and a PTY) instead. Environment (high level): \u2014 e.g. or \u2014 same payload as stdin (piped mode) Common vars: , , , , , , , , All vars from the parent process Parent vars whose names end with or Place API keys in the gateway environment (systemd , shell before , etc.) so they are inherited and forwarded by the allowlist above. ---"},{title:"When the fallback runs (and when it does not)",body:"Runs only if all of the following hold: is true and is non-empty after trim. The message is plain text that reached the router\u2019s final \u201Cno match\u201D branch: not a command, not shell, not / , not app line, not consumed by a focused session, and free shell mode is off for that peer. Does not run (examples): unknown , , , media-only messages, messages dropped by cluster binding on non-primary hosts, or any path that already returns a normal reply. ---"},{title:"Related keys (reference)",body:"Key Purpose ------------------------ ------------------------------------------------------------------------------------------------ Master switch ( by default). Full command string for . Wall-clock limit (ms). Max size of text passed in (stdin / ). Max captured output (stdout+stderr in piped mode). Use PTY execution (for CLIs that require a TTY); prefer in the script. Fixed cwd; empty = ephemeral dir per run. Implementation checklist and code pointers: chat-llm-fallback-implementation-plan.md. ---"},{title:"Security note",body:"Allowlisted chats already have shell-level trust on the gateway host. Turning this on means accidental plain messages can invoke your command and any API usage inside it. Keep allowlists small, use timeouts and caps, and wrap external agents in a policy you control."}],keywords:["chat","llm","fallback","optional","when","this","feature","is","on","plain","inbound","message","that","would","normally","get","no","command","matched","handled","silently","omnish","runs","your","in","the","background","with","limits","and","restricted","environment","then","sends","subprocess","output","back","to","same","whatsapp","or","telegram","reply","printed","terminal","instead","where","change","settings","how","enable","step","by","what","receives","it","does","not","related","keys","reference","security","note"],relatedCommands:["/config help","/config","/absolute","/path","/to","/your-wrapper","/stderr","/bin","/bash","/apps","/foo","/help","/chat-llm-fallback-implementation-plan"]},{id:"docs-features-cluster-and-chat-config",path:"docs/features/cluster-and-chat-config.md",title:"Multi-host cluster and chat configuration",summary:"This document describes the chat-driven cluster ( , , ) and for viewing or editing from WhatsApp or Telegram.",sections:[{title:"Cluster (per-sender bindings)",body:`Use one WhatsApp phone number with multiple linked devices \u2014 each machine runs once. Every machine that runs will receive the same DMs through WhatsApp multi-device. There is no shared file. There is no Syncthing/NFS to set up. Coordination flows entirely through the WhatsApp chat itself: every reply omnish sends carries an invisible footer that other linked omnish hosts read to learn who is online and which machine each sender is currently bound to. Telegram cannot carry this coordination (different bot tokens cannot see each other), so the per-sender binding still applies on Telegram, but you only get convergence between linked hosts on WhatsApp. How it routes Each allowlisted sender (one phone number / one Telegram id) picks one machine to talk to. Only that machine processes the sender's normal traffic; other linked hosts stay silent for that sender. Two different controllers can independently bind to two different machines without affecting each other. A's only runs on . B's only runs on . Neither sees the other's output. Fleet commands ( , , ) are processed by every linked host so that bindings, status, and help stay reachable even before a sender has bound. Stable identity Each data directory has a file ( ) with a UUID. Display names in the roster come from , or the OS hostname when empty. When you , "alpha" matches against the (or the 8-character node id). Local state Each host keeps a private file at (schema v3): \u2014 short id, label, role, and last-seen timestamp for every host this machine has observed in chat traffic. \u2014 map of sender key ( or ) to the bound machine's short node id, when it was set, and whether it came from a chat command ( ) or config defaults ( ). This file is per host and never shared. Disagreements between linked hosts are resolved by the next footer-stamped message anyone sends to the chat. Commands (WhatsApp / Telegram) may be shortened to or (the bare token matches; does not). Command Action ---------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------ Overview of the per-sender model Bind YOUR messages to that machine. Resolves either an 8-character node id or a . Other senders are not affected. Bind YOUR messages to the local machine (shorthand for ). Show YOUR current binding (machine, source: chat or config). Clear YOUR chat binding. The config default in (if any) takes over again. Every online host replies with its own one-paragraph status (this is the discovery moment \u2014 each host parses the others' footers and updates its peer list). Locally known roster \u2014 only one host responds (the bound one, or a deterministic fallback). Convergence When you send , every linked host runs locally. Only the resolved target host (the one labelled ) sends the confirmation reply; siblings stay silent. The reply carries the cluster footer with , and every sibling observes it via WhatsApp upserts and converges on the same binding for your sender key. Defaults via config You can pre-seed bindings so a controller never has to type on first contact: Keys are sender keys ( or ). Values are the target machine's or its 8-character node id. Chat-set bindings ( ) always win over config defaults; clearing a chat binding ( ) falls back to the config default. CLI prints a short cluster line when is true. The legacy was removed \u2014 bindings need a sender, so use the chat ( ) or the new CLI form. Configuration keys (cluster) See the Configuration guide. Relevant fields: \u2014 display name for THIS machine in the roster \u2014 sender \u2192 machine defaults \u2014 kept for backwards compat, but no longer used for traffic gating Migration from v2 (single global active host) is automatically migrated on first read. The previous field is dropped \u2014 every sender now binds individually. If you relied on the old global-active model, set the equivalent entry for each allowlisted sender in , or send from each controller's chat. The foote`},{title:"Chat configuration (`/config`)",body:"Allowlisted chat users can change many settings without SSH. This is as sensitive as shell access: anyone who can DM as an allowed identity can modify config. Commands Command Action --------------------------- -------------------------------------------------------------- or Help (overview + hint) Snapshot of effective config; telegram bot token masked One whitelisted key Update one key; values may be quoted for paths with spaces List all keys allowed for After changes Save goes to immediately (same normalization as the rest of the app). For or , the gateway runs -style Telegram restart when is up; otherwise send after editing. Changing returns an explicit warning in the reply. Whitelist ( ) The canonical allowlist is in . In chat, prints the same comma-separated list your build supports. Groups include: gateway ( , \u2026); cluster; shell / sync / jobs; apps; files; recipes (including , ); Telegram ( ); service ( ); update checks ( , , , ); tunneling ( , , ); chat LLM fallback ( , , , , , , ). accepts a JSON object value (or / to wipe), e.g. . Not exposed via : , \u2014 use and in chat (or / on the host). Tunnel token is not in : run on the gateway host (or set ). Chat can turn tunneling on with after the token exists. Examples ---"},{title:"Related documentation",body:"Configuration guide \u2014 full file layout and fields Security model \u2014 trust boundaries and Message routing \u2014 slash-command ordering (high level)"}],keywords:["multi-host","cluster","and","chat","configuration","this","document","describes","the","chat-driven","for","viewing","or","editing","from","whatsapp","telegram","per-sender","bindings","/config","related","documentation"],relatedCommands:["/c help","/config help","/computers","/pcs","/config","/nfs","/node-id","/c use","/cluster-local","/cluster","/c here","/c using","/c unuse"]},{id:"docs-features-cowork",path:"docs/features/cowork.md",title:"Cowork \u2014 scheduled and on-demand shell tasks",summary:"Cowork runs saved shell commands on a timer while is active. It uses the same execution model as sync commands ( via your configured ), with and from . There is no AI layer.",sections:[{title:"Prerequisites",body:"A running gateway: (foreground or background). You must be allowlisted (same trust as ). With cluster enabled, only the host bound to your sender processes chat commands; tasks you create are stored on that machine\u2019s data directory."},{title:"Commands",body:"Use or . Send for the short form. Action Example -------------------------------- ---------------------------------------------------------------------------- Add task List Show Run now (on-demand or scheduled) Enable / disable / Remove (also , ) Check in (heartbeat only) Update \u2014 notify condition (see below) \u2014 also send the run log file with each notify \u2014 optional artifact paths (see below) Add syntax Scheduled / on-demand tasks (run a command): Heartbeat tasks (dead-man's-switch, no command): Name: letters, digits, , ; max 32 characters (stored lowercased). Schedule: (or ), , , , (weekday names or \u2013 , Sunday = ), or . For scheduled/on-demand tasks, the command must come after a literal separator. Heartbeat tasks have no command. Duration format (heartbeat): , , , . Minimum interval: 1m. Minimum grace: 30s. Grace defaults to 50% of interval if omitted. Schedules and time zones Fire times use the gateway host\u2019s local timezone (Node\u2019s on that machine). Output logs Each run writes one UTF-8 log file under the task\u2019s (default ). Filenames include a timestamp, task id, and whether the run was scheduled, catch-up, or on-demand. Catch-up Successful scheduled runs are recorded in (per task id and slot time). The scheduler uses that database as the source of truth for what is already done; may still contain a legacy field, which is seeded into SQLite on first open if the DB has no rows for that task. If several slots were missed (gateway down), the next tick runs the command once for the newest due slot and records coalesced rows for older missed slots so the backlog clears in a single execution instead of one run every 30 seconds per missed slot. A slot is only recorded after exit code 0, no timeout, and no terminating signal; failed runs do not advance the watermark (same retry behavior as before). Kinds stored for successful scheduled work: (within 2 minutes of the slot), (late), (satisfied without a separate run during backlog coalescing), and (imported from legacy on upgrade). On-demand queue appends a request to . The scheduler dequeues up to eight entries per tick, one read/write of the queue file per batch, so a crash mid-run does not drop the rest of the queue. Notifications After each run, a short summary can be sent (same routing as other replies: WhatsApp vs Telegram formatting). For scheduled and catch-up runs, the message is one line: The bracketed status appears only when the run did not finish cleanly (timeout, non-zero exit, or signal). For on-demand runs ( ), the message is two lines: Optional lines may follow when attachments fail or when too many artifact files match (see below). Mode Recipients ---------------- ----------------------------------------- (default) Task owner only All entries in (WhatsApp) All entries in Owner plus both allowlists No messages Trust: , , and can message every allowlisted identity. Anyone who can edit tasks (allowlisted senders) can point shell and notifications at powerful actions\u2014same overall model as remote shell. WhatsApp notification targets use normalized JIDs so delivery matches Baileys . Conditional notifications ( ) Control when a notification is sent with : Mode Behavior ------------------ -------------------------------------------------------------------------------------------------------------------------------------------------- (default) Notify after every run Notify only when the command exits with a non-zero code, times out, or is killed by a signal Notify only when the result flips (e.g. ok to fail, or fail to ok). The last-known state is tracked in SQLite so it survives gateway restarts. is useful for tasks that run frequently (e.g. hourly health checks) where you only want to know when something breaks or recovers, not on every successful run. Heartbeat (dead-man's-switch) A heartbeat task does not run a command. Instead, it expects periodic check-ins and alerts when they stop arriving. This creates a task that expects a check-in at least every 1 hour, with a 10-mi"},{title:"Data files",body:"Under (default unless overridden): \u2014 task definitions (atomic replace on save). \u2014 successful scheduled slot completions (watermark for catch-up); WAL mode. \u2014 queued on-demand runs. Directory mode is restrictive ( for where created by the app; task file )."},{title:"Limitations and caveats",body:"Gateway must be up at fire time for scheduled runs; catch-up handles downtime afterward. WhatsApp-only: the cowork scheduler starts before the first successful WhatsApp connection; very early completion notifications to WhatsApp may no-op until outbound is ready. Telegram outbound is typically ready earlier when the bot is enabled. Shared : if several gateways share the same data directory (unusual), each running process could execute the same schedules\u2014use one data dir per machine for normal setups. Cluster: tasks live on disk for the gateway that received commands; they do not sync across hosts."},{title:"See also",body:"Cowork implementation plan (design and maintainer notes) User guide \u2014 Cowork section Security model"}],keywords:["cowork","scheduled","and","on-demand","shell","tasks","runs","saved","commands","on","timer","while","is","active","it","uses","the","same","execution","model","as","sync","via","your","configured","with","from","there","no","ai","layer","prerequisites","data","files","limitations","caveats","see","also"],relatedCommands:["/cowork help","/cw help","/cowork","/cw","/cowork add","/data","/backup","/cowork list","/cowork show","/cowork run","/cowork enable","/cowork disable","/cowork remove"]},{id:"docs-features-device-update-delivery",path:"docs/features/device-update-delivery.md",title:"Update information on installed Omnish devices",summary:"This document is the reference plan for how operators and maintainers can reach already-installed Omnish gateways with version and notice information. It matches what the CLI and gateway implement today.",sections:[{title:"Reality check: there is no silent \u201Cpush\u201D to every machine",body:"Omnish is self-hosted: each install is a process on a user\u2019s machine, talking to WhatsApp/Telegram. There is no central fleet server and no always-on back channel from the project to those hosts unless the host initiates outbound traffic (or an allowlisted user sends a chat command). So 100% delivery in the literal sense (every device, every time, with no user action) is not possible: machines can be offline, firewalled, on air-gapped networks, or running an old binary forever. What is achievable is a reliable, predictable path that works whenever the network and registry are reachable\u2014same practical bar as other CLIs ( , , etc.)."},{title:"Options that were considered",body:"Approach Pros Cons ------------------------------------------------------ ------------------------------------------------------------------------------ ----------------------------------------------------------------------------------------------- npm registry Canonical published version; no custom infra; HTTPS; already used for installs Needs outbound HTTPS; scoped/unpublished forks differ GitHub Releases / API Rich metadata Rate limits; not identical to \u201Cwhat gets\u201D Static JSON on a URL you control ( ) Arbitrary maintainer text (security, migration) You must host and secure expectations (HTTPS only in omnish) In-chat broadcast from \u201Cthe project\u201D Uses existing DM surface There is no project-owned chat to all installs; only allowlisted users can command their host Auto-upgrade in place Hands-off for users High risk (native deps, , Baileys); out of scope for this design doc Chosen combination: npm registry for semver discovery + optional HTTPS JSON for human notices, exposed in the product as , (cached one-liner), , optional background checks when is true, and for the same keys."},{title:"Implemented behavior (source of truth)",body:"(allowlisted chat, gateway running): performs a live GET to (default package name ). If is set to an https URL, fetches JSON (link must also be ). : shows the last in-memory snapshot from the last live or scheduled check (no network). : gateway runs a timer (checks at most every 1 minute internally, but only performs a registry+info fetch when has elapsed, clamped 1h\u20137d). Results are logged with and stored for / . : CLI one-shot check (same fetches as ). : completion text may append \u201CUpdates (last check): \u2026\u201D if a snapshot exists. Configuration keys (also in and ): (boolean, default false) \u2014 privacy-first default. (default 86400000) \u2014 clamped between 1 hour and 7 days. (default ) \u2014 for forks or scoped packages. (default empty) \u2014 optional maintainer notice JSON over HTTPS."},{title:"Maintainer playbook",body:"Publish a new version to npm as today ( bump, publish). Devices that run or have background checks enabled will see the new latest when the registry updates. Optional notice (security advisory, breaking change): host a static JSON file at an HTTPS URL you control; set in docs or tell users to set it via . Do not rely on chat alone to \u201Creach\u201D every install; treat registry + optional URL as the scalable channel."},{title:"Code map",body:"Piece Role ------------------------------------------------------ -------------------------------------------------------------------------------- npm + optional info URL fetch, snapshot, scheduler Numeric compare for \u201Cnewer on npm\u201D Reads running from package root (dev + bundled ) , Schedules checks; ; reload footer status lines, formatted reply Schema defaults and merge"},{title:"Future extensions (not implemented)",body:"Signed notices (e.g. minisign) over . Deprecation warnings via (visible on , not parsed here)."}],keywords:["update","information","on","installed","omnish","devices","this","document","is","the","reference","plan","for","how","operators","and","maintainers","can","reach","already-installed","gateways","with","version","notice","it","matches","what","cli","gateway","implement","today","reality","check","there","no","silent","push","to","every","machine","options","that","were","considered","implemented","behavior","source","of","truth","maintainer","playbook","code","map","future","extensions","not"],relatedCommands:["/updates","/updates cached","/telegram","/latest","/unpublished forks","/gateway","/config set","/registry","/reload","/config keys","/omnish-notice","/check"]},{id:"docs-features-docs-search-from-chat",path:"docs/features/docs-search-from-chat.md",title:"Documentation search from chat",summary:"Find omnish guides by topic from WhatsApp, Telegram, or \u2014without hunting the repo or omnish.dev first. Search is offline (bundled index at build time; no AI layer).",sections:[{title:"Commands",body:"Command Purpose --------------------------- --------------------------------------------------------- or Subcommand list Ranked results (keywords + headings) Repeat the last search list in this chat or Excerpt, GitHub link, and Try: related slash commands Run the primary related help (e.g. ) Legacy aliases: (same as ), \u2192 , . Host terminal (same index): ( still works.) ---"},{title:"Example flow",body:"You are not sure which command exposes HTTP: Pick a result: You get a short excerpt, the doc path, a GitHub link, and lines like . Jump to live help: Same as sending in that chat. ---"},{title:"When nothing matches a slash command",body:"Plain text that looks like a question (contains a space or ) gets a hint on No command matched: Use with your own keywords if the auto-filled phrase is too long. ---"},{title:"Index scope",body:"The build includes guides, features, architecture, and advanced troubleshooting under (not , , or maintainer runbooks). Rebuild the index with: ( and run this automatically.) Overrides for related commands: . ---"},{title:"Related",body:"User guide Message routing Online catalog \u2014 community recipes/apps (separate from docs search)"}],keywords:["documentation","search","from","chat","find","omnish","guides","by","topic","whatsapp","telegram","or","without","hunting","the","repo","dev","first","is","offline","bundled","index","at","build","time","no","ai","layer","commands","example","flow","when","nothing","matches","slash","command","scope","related"],relatedCommands:["/s help","/search help","/s q","/s list","/s show","/s follow","/tunnel help","/search","/docs search","/help search","/features","/tunneling"]},{id:"docs-features-implementation-101",path:"docs/features/implementation-101.md",title:"Tunneling implementation 101",summary:"This document explains how omnish tunneling is built: relay edge, client, CLI, chat integration, configuration, security, and how to exercise it locally.",sections:[{title:"Big picture",body:"Tunneling is a relay + client design. The relay is the public edge; the omnish client on the user machine keeps an outbound WebSocket and forwards traffic to a local port. HTTP uses JSON control messages on the WebSocket. TCP uses JSON for stream open/close and length-prefixed binary frames for payload. Default production relay: . The relay service itself lives under and is deployed separately from the npm CLI package."},{title:"Repository map",body:"Path Role ------------------------------------------------------------------------------ ------------------------------------------------------- Deployable relay: HTTP edge, WSS control, TCP listeners Relay package ( dependency) Shared control message types and binary frame codec Tunnel kinds, records, default relay URL One tunnel: WSS session, local forwarding Active tunnels, limits, stop/stopAll Token and relay URL resolution CLI and chat argument parsing subcommands handlers for the gateway CLI dispatch; gateway shutdown stops tunnels Chat routing for and , , under the data dir Posture findings for tunneling lines when tunneling is enabled Tests: , , , ."},{title:"Relay (`contrib/tunnel-relay/`)",body:"is the tunnel process. Production ships it in one Docker image with Caddy (TLS, wildcard DNS-01) via and . proxies public / to and . Listeners Listener Default Role ----------- --------------- -------------------------------- HTTP edge Public HTTP for tunneled apps Control WSS Authenticated client connections Environment variables: \u2014 base URL shown to users (default ) \u2014 HTTP edge bind port \u2014 control WebSocket bind port / \u2014 TCP tunnel port range \u2014 comma-separated bearer tokens allowed to connect \u2014 per-token tunnel quota Authentication Clients connect with on the WebSocket upgrade. Invalid or missing tokens close the socket. Registration After , the client sends with ( ), , , and optional . The relay assigns a slug (from or random) and replies with including . HTTP routing Local dev: Production-style: when matches that host pattern Fallback: Each slug is bound to the WebSocket session that registered it (not \u201Cany session with the same token\u201D), so multiple tunnels sharing one token each get correct routing. Incoming requests are turned into control messages to the owning client; the client returns . TCP For , the relay binds a port in the configured range and returns . New public TCP connections emit on the control socket; bytes flow over binary frames. State In-memory maps ( , , ). Tunnels disappear when the client disconnects. No cross-replica sticky routing in v1."},{title:"Shared protocol (`src/tunnel/protocol.ts`)",body:"Control messages (JSON on the WebSocket): Session: , , Lifecycle: , , , HTTP: , (optional ) TCP: , Keepalive: , Binary frames (TCP payload): 1 byte frame type ( , , , ) 4 byte stream id (big-endian) 4 byte payload length payload bytes and implement the codec on client and relay."},{title:"Client (`src/tunnel/client.ts`)",body:"represents one active tunnel. Derives from the relay URL ( \u2192 , default path ). Connects with the bearer token. Sends with a random 8-hex . Waits for and stores and . HTTP path: On , issues to , then sends with status, headers, and optional base64 body. TCP path: On , connects locally and pumps bytes via binary frames; or relay close tears down the stream. Lifecycle: Periodic ; on stop, sends and closes the socket. Default target host is unless overrides it."},{title:"Manager (`src/tunnel/manager.ts`)",body:"holds live instances keyed by tunnel id and slug. Resolves relay URL via and token via Enforces from config / for CLI, chat, and gateway shutdown The gateway and CLI share one manager instance from ( )."},{title:"Configuration and secrets",body:"In (non-secret): \u2014 gate chat commands (default ) \u2014 default relay origin (default ) \u2014 max concurrent tunnels on this host (default ) Secrets (not in ): or ( ) via Optional relay override: or in the auth file See and ."},{title:"CLI (`omnish tunnel`)",body:"Implemented in ; wired from . Subcommand Behavior ----------------- ------------------------------------------------------ Save token (and optional relay) to Remove saved token Register HTTP tunnel; foreground unless Register TCP tunnel List active tunnels on this machine Stop one tunnel Relay reachability and auth presence Flags: , , , (see )."},{title:"Chat integration",body:"When is true, handles: / (optional , ) delegates to the shared . Tunnels run inside ; standalone still works without the gateway. Trust model matches : allowlisted chat users can open tunnels as the gateway OS user. Public URL possession is the visitor credential."},{title:"Security",body:"findings: \u2014 chat tunneling on \u2014 chat tunneling on without a token \u2014 non-default documents tunnel URLs as capabilities and the gateway shutdown path that stops active tunnels."},{title:"Local exercise",body:"Install relay deps: Start relay, for example: - - Run a local HTTP server on a port (for example ) Open the printed (path-based on loopback: ) Automated coverage: spins the relay and asserts HTTP end-to-end."},{title:"Out of scope (v1)",body:"Mandatory omnish.dev account or billing Tunnel persistence across client disconnect or HA relay fleet UDP, mesh VPN, or bundled ngrok/cloudflared as the primary path Setup UI for tunnel login (CLI is the v1 configuration surface)"},{title:"Success criteria (from product plan)",body:"+ yields a public URL that serves the local app (with a running relay) exposes a public TCP endpoint to the local port With and the gateway running, allowlisted users get the same URLs from chat and docs describe capability risk clearly"}],keywords:["tunneling","implementation","101","this","document","explains","how","omnish","is","built","relay","edge","client","cli","chat","integration","configuration","security","and","to","exercise","it","locally","big","picture","repository","map","contrib/tunnel-relay/","shared","protocol","src/tunnel/protocol","ts","src/tunnel/client","manager","src/tunnel/manager","secrets","tunnel","local","out","of","scope","v1","success","criteria","from","product","plan"],relatedCommands:["/tunneling","/architecture","/security","/close and","/tunnel","/tunnel-relay","/server","/contrib","/package","/protocol","/src","/types"]},{id:"docs-features-job-board",path:"docs/features/job-board.md",title:"Job board \u2014 digital employee coordination",summary:"The job board is omnish\u2019s work queue for autonomous digital employees: dependencies, coordinator dispatch, supervisor accountability, and host-wide resource limits.",sections:[{title:"Prerequisites",body:"(gateway active). Allowlisted sender for commands. with specialist commands (see example pack)."},{title:"Coordinator (on by default)",body:"While the gateway runs, the coordinator ticks automatically ( defaults to ). Set it to for manual only. Configure specialists: (or edit )."},{title:"Commands",body:"Command Action -------------------------------------------- ------------------------------------------------------------------------------ Human intake; optional , , Status counts plus per-job lines for review/pending/rework Job detail One coordinator + supervisor cycle (also sends status DMs when hooked to chat) Primary agent CLI for registry roles Resource governor snapshot Cancel job Registry names Coordinator onboarding and profiles Human satisfaction after Role-split suggestions Mark work-plan step done (for agent CLIs) is an alias for . Notification config Key Default Meaning ---------------------- ------------ ---------------------------------------- Phase completion messages Start / review / done / failed / blocked"},{title:"Data paths",body:"Path Purpose ----------------------------------- ---------------------------------------- Jobs, audit, slots Specialist commands Profile, skills, flow overrides Concurrency and CPU/RAM/GPU limits , , artifacts"},{title:"Coordinator (each tick)",body:"Unblock jobs whose dependencies are . Run supervisor on jobs in . If capacity allows, dispatch highest-priority runnable job to its specialist command."},{title:"Core competencies (non-optional)",body:"All employees run through the harness ( ). Registry entries use only (legacy alias). omnish seeds , enforces five phases, and notifies on progress. See Digital employees \u2014 core competencies."},{title:"Work plan (specialist discipline)",body:"The harness seeds before the inner agent runs. Specialists mark todos via scripts or . Handoff requires full completion, self-reviews on major steps, and in ."},{title:"Supervisor",body:"After a specialist finishes, the job moves to . The supervisor checks , , and job-plan lines. Outcomes: pass \u2192 ; dependents may unblock. rework \u2192 then reassigned (until ). fail \u2192 ; owner notified."},{title:"Separation of duties",body:"The coordinator does not approve quality; the supervisor does not choose the next assignee or create prerequisite jobs. That split keeps accountability auditable (see in SQLite)."}],keywords:["job","board","digital","employee","coordination","the","is","omnish","work","queue","for","autonomous","employees","dependencies","coordinator","dispatch","supervisor","accountability","and","host-wide","resource","limits","prerequisites","on","by","default","commands","data","paths","each","tick","core","competencies","non-optional","plan","specialist","discipline","separation","of","duties"],relatedCommands:["/guides","/employee-task-flow","/digital-employees","/coordinator-workforce","/board","/employees","/board tick","/board agent","/board add","/board list","/pending","/rework"]},{id:"docs-features-media-commands",path:"docs/features/media-commands.md",title:"Media commands (`/dl`, `/dlf`, `/dlv`, `/tr`, `/edit`)",summary:"Download, transcribe, and edit media from chat \u2014 using yt-dlp, ffmpeg, and optionally openai-whisper on the gateway host.",sections:[{title:"Defaults",body:"Files are sent to chat by default ( ). Background jobs deliver results over the gateway control channel when is active. To only list paths, set:"},{title:"Install tools (host)",body:"Binaries are stored under . Whisper uses . From chat (same trust as shell \u2014 off by default):"},{title:"Chat commands",body:"Command Action --------------------------------------------- ------------------------------------------------------------ Auto: file (HTTP), video (yt-dlp), or HTML\u2192markdown Force HTTP file download (never yt-dlp) Force yt-dlp video download Tool status OS-specific manual install steps Install tools into Whisper transcript + (+ video if URL); background job Trim or convert with ffmpeg; background job flags: / , / , / , / , . How classifies URLs File signals (always HTTP, never yt-dlp): pathname extension ( , , \u2026), path heuristics ( for arxiv, etc.), or from HEAD. Video: yt-dlp probe ( ) \u2014 if yt-dlp has a named site extractor (1000+ sites), download with yt-dlp. HTML page: fetch page and send markdown in chat (large pages may also save a file). Use when auto-detect might guess wrong (e.g. force HTTP on a video URL). Use to force yt-dlp. Save location Config / chat Where files go ----------------------------- --------------------------- set That directory (flat) for this chat Session cwd ( ) Default (flat) No dated subfolders. Share-friendly filenames All download paths normalize saved basenames for easy sharing in chat: ASCII-safe names (unicode stripped or transliterated, emoji removed) Spaces and unsafe characters become Collisions get numeric suffixes ( , ) yt-dlp uses plus a post-download rename pass Whisper sidecars ( , , etc.) inherit the normalized video basename. Background jobs , , , , and run as background jobs ( ). You get a job id immediately; finished files and markdown are sent to chat when the job completes (if is true). Optional flags (stripped before the payload): / \u2014 extra ping when the shell job finishes (exit status) / \u2014 no-op (already background) Step progress When is true (default), multi-step work sends chat messages such as while it runs. Auto-detect URLs When is true (default), a message that is only an or URL runs in the background \u2014 even when free shell ( ) is on. Focused PTY still wins over auto-dl. When chat LLM fallback is off and no LLM shell command is configured, lone URLs also auto-download even if was set to false (paste-a-link intent). Trailing punctuation from chat clients (e.g. ) is stripped before detection. Auto-dl replies with a short \u201CDownloading link\u2026\u201D message instead of a verbose job block. Legacy still works as aliases: / \u2192 , / \u2192 . Other modes show a deprecation hint."},{title:"Config keys",body:"Key Default Purpose -------------------------- ----------- ----------------------------------------------------------------------------------------- Send files to chat after download Allow from chat Lone URL \u2192 auto Output root (empty \u2192 Downloads/Omnish or cwd) yt-dlp cap (0 = none) Max size when sending downloads to chat (0 = no omnish cap; WA/TG limits still apply) Whisper / Transformers model size ( , , , \u2026) (default device, then CPU), , or (whisper CLI only) Per-attempt timeout for whisper/ffmpeg (10 min; max 15 min) , , or (whisper then Transformers.js fallback) When engine fails, retry with Transformers.js if installed On hosts with a busy or small GPU, set to or use a smaller ( , ). For long files that hit timeouts, raise . Transformers.js (no Python): installs under . Set to to use it exclusively, or keep / with (default) to fall back when openai-whisper fails. Step-by-step chat messages during multi-step work / / Binary overrides Older keys in are migrated on load (e.g. \u2192 ). Size limits ( , , ) Two different caps: Key When it applies ------------------ ---------------------------------------------------------------------------------------------------------- Download only (yt-dlp ). = no cap. Sending the saved file to chat after the job finishes. = no omnish cap on standalone . If the file downloads but chat says \u201CN file(s) not sent\u201D with a size, the job succeeded on disk; outbound delivery hit a cap \u2014 not . Standalone (local WhatsApp/Telegram on the host): only (and messenger limits). Attached ( with platform credentials, messengers on the relay): no default platform hop cap. Optional env (bytes; = unlimited) on both the relay and the device gateway caps outbound files over the WebSocket. WhatsApp/Telegram limits still apply upstream. Check values: (or ) for and ."},{title:"Legal and safety",body:"Same allowlist trust as shell commands. Private/local URLs are blocked ( , RFC1918, etc.) when the job runs. Respect copyright and platform terms; omnish only runs tools you install locally."},{title:"See also",body:"Files send/receive \u2014 for manual delivery Background jobs \u2014 , ,"}],keywords:["media","commands","/dl","/dlf","/dlv","/tr","/edit","download","transcribe","and","edit","from","chat","using","yt-dlp","ffmpeg","optionally","openai-whisper","on","the","gateway","host","defaults","install","tools","config","keys","legal","safety","see","also"],relatedCommands:["/dl","/dlf","/dlv","/tr","/edit","/bin","/venvs","/whisper","/config set","/dl install","/dl doctor","/dl setup"]},{id:"docs-features-media-pull",path:"docs/features/media-pull.md",title:"Media pull (`/pull`) \u2014 deprecated",summary:"> Superseded by media commands ( , , ). This page is kept for older releases.",sections:[{title:"Enable",body:"In (or from an allowlisted chat):"},{title:"Install tools (host)",body:"Binaries are stored under . Whisper uses . From chat (same trust as shell \u2014 off by default):"},{title:"Chat commands",body:"Command Action ------------------------ -------------------------------------------------- Usage Tool status OS-specific manual install steps Best video (needs ffmpeg) Audio extract (m4a) Subtitles (en + auto) Whisper speech-to-text video + audio + subs + transcript (background job) Flags: / \u2014 run as job / \u2014 notify in chat when the background job finishes and always run in the background. / / run synchronously unless is set. Auto-detect URLs When is true, a message that is only an or URL runs with (default ). Send results back to chat When is true, files under (and any cap in attached mode) are sent with automatically. Otherwise the reply lists paths \u2014 use manually."},{title:"Config keys",body:"Key Default Purpose ------------------------------------------------------ --------- ------------------------------------------------------ Master switch Allow Lone URL \u2192 pull Default mode Output root (empty \u2192 ) yt-dlp cap (0 = none) Push files to chat Whisper model / / Binary overrides"},{title:"Legal and safety",body:"Same allowlist trust as shell commands. Private/local URLs are blocked ( , RFC1918, etc.). Respect copyright and platform terms; omnish only runs tools you install locally."},{title:"See also",body:"Files send/receive \u2014 after a pull Background jobs \u2014 , ,"}],keywords:["media","pull","/pull","deprecated","superseded","by","commands","this","page","is","kept","for","older","releases","enable","install","tools","host","chat","config","keys","legal","and","safety","see","also"],relatedCommands:["/pull","/dl","/tr","/edit","/config","/config set","/bin","/venvs","/whisper","/pull install","/pull help","/pull doctor"]},{id:"docs-features-monetization",path:"docs/features/monetization.md",title:"Monetization \u2014 device-first omnish",summary:"omnish keeps WhatsApp and Telegram as the free control plane: allowlisted messages run on the user\u2019s machine. Paid tiers charge for reachability, identity, team boundaries, and safety nets\u2014not for sending a command.",sections:[{title:"Who pays at ~$10/month",body:"Someone who already runs on a home server, laptop, or Mac mini and wants the inbox to stay useful when they are away from the desk: show a dev server, start a long job, drive a TUI agent, tail logs, or hand a link to someone else. Free omnish sells my phone is a remote control for my computer. Paid omnish sells that control still works when I need a stable public URL, more than one tunnel, or light team structure\u2014without being my own ngrok admin."},{title:"Strongest first paid wedge",body:"Hosted tunneling on , integrated with the same chat and CLI, is the most natural first paid feature. Execution stays on their box; omnish runs the edge they would otherwise assemble (TLS, wildcard subdomains, relay uptime, abuse limits). For agent builds on the user\u2019s machine, steered from chat: the agent runs locally; Plus provides a shareable HTTPS preview via or , while iteration stays in WhatsApp or Telegram. That is not a cloud IDE\u2014it is a link that works while code and processes stay on their hardware. Plus ($10) \u2014 draft shape Several concurrent HTTP tunnels (free: self-hosted relay only, or one hosted tunnel with ephemeral names). Reserved slugs so does not change every session. Caps on bandwidth, tunnel lifetime, and concurrent tunnels so hosted relay cost stays bounded. TCP tunnels optional on Plus or stricter on free (TCP is riskier to operate). Comparable spend to ngrok-class tools; the hook is already inside omnish, not a separate dashboard."},{title:"Second layer: team and accountability",body:"Solo users may stay on free plus self-hosted relay. Small teams pay when my phone controls our box needs structure: More than one allowlisted identity with clearer roles (run vs read-only vs tunnel-only). Audit trail: who ran what, which tunnels opened, when\u2014exportable for a client or cofounder. Named machines in cluster mode without ambiguous shared binding. Still device-first: gateway on their hardware; paid layer is policy and visibility."},{title:"Third layer: reliability",body:"Weaker as the only $10 hook unless they depend on the gateway daily; pairs well with hosted tunnels: Offline alerts when stops heartbeating. Guided boot / service setup with a simple health view (gateway up, last command, disk, tunnel count). Optional backup / restore of omnish data (shortcuts, cowork defs, session cwd maps)\u2014not the whole disk."},{title:"What not to lead with at $10",body:"Hosted agent sandbox \u2014 fights device-first positioning; competes with Cursor, Replit, etc. Generic AI \u2014 conflicts with no-AI, your-shell positioning. Per-message chat fees \u2014 trains workarounds. Security theater \u2014 erodes trust on remote shell access."},{title:"Packaging sketch",body:"Tier Free Plus $10 Pro (later) --------------------------- ----------------- ----------------- ------------------- Chat \u2192 your shell Yes Yes Yes Self-hosted relay Yes Yes Yes Omnish-hosted HTTPS tunnels No / very limited Yes, with caps Higher caps Stable / custom names No Reserved slug Custom domain Team / audit Basic allowlist Small team + logs More seats, exports Gateway monitoring DIY Optional alerts SLA-style support One-liner: Keep controlling your machine from WhatsApp and Telegram for free; pay for public preview links, stable names, and team guardrails when that is how you work."},{title:"When someone will pay",body:"They pay when hosted tunnels remove a recurring pain: client demos, \u201Copen this while I change it from chat,\u201D or not maintaining Caddy, Cloudflare, and a relay on a VPS. Remote shell alone may stay free forever\u2014that supports adoption, not revenue."},{title:"Sequencing",body:"Plus = hosted tunnel + limits + account/token; chat stays unlimited. Team audit when paying users need shared access. Custom domains when reserved slugs feel tight. The preview-from-chat loop on their machine is credible paid value only if the URL is stable, HTTPS, and boring\u2014that is worth money while the shell stays free and local."}],keywords:["monetization","device-first","omnish","keeps","whatsapp","and","telegram","as","the","free","control","plane","allowlisted","messages","run","on","user","machine","paid","tiers","charge","for","reachability","identity","team","boundaries","safety","nets","not","sending","command","who","pays","at","10/month","strongest","first","wedge","second","layer","accountability","third","reliability","what","to","lead","with","10","packaging","sketch","when","someone","will","pay","sequencing"],relatedCommands:["/month someone","/tunnel","/theirname","/token"]},{id:"docs-features-online-catalog",path:"docs/features/online-catalog.md",title:"Online catalog",summary:"Share and install recipes, app templates, cowork tasks, and shortcuts across the omnish community. Browse and download from chat; publish when logged into the platform.",sections:[{title:"Discover and install (chat)",body:"Browse from the command family you care about \u2014 each prefix filters the catalog to the matching kind in MongoDB: Prefix Kind filter Example -------------------- ------------------------------------------------ -------------------------------------- all kinds (optional on trending/search) only only only Shared subcommands (replace with , , , or ): Command Purpose --------------------------------------- ---------------------------------------------------- Catalog subcommands for that family Most downloaded (kind-scoped when not ) Text search Full payload (review before install) Install to gateway-shared storage Install item #n from the last list in that family Repeat the last trending/search list for that family Examples: Use or on download to install for this chat only instead of gateway-shared: Numbered lists are per family \u2014 uses the last list, not a prior list. Platform URL: set in config (default hosted relay). Browse does not require a token; the CLI uses your configured relay origin. ---"},{title:"Publish (requires platform account)",body:"Sign up on the relay dashboard or run , then publish from chat: Command What is published Catalog -------------------------------------------------------------- --------------------------------------------------- -------------- User or gateway-shared recipe (not built-ins) Running PTY session command (session must be alive) Cowork task for this chat Shortcut (chat or shared) On success you get a (e.g. ). Others install with the matching prefix, e.g. or . Flags: , , , ---"},{title:"What gets installed",body:"Kind Local storage ---------- ---------------------------------------------------------------------- gateway-shared bucket Same as recipe (category ; use with stored command) gateway-shared bucket (owned by the importing chat) ---"},{title:"Security",body:"Recipes and app commands can run shell on your machine. Always before download. The platform rejects dangerous recipe flags and invalid command shapes at publish time. Download does not auto-run \u2014 it only adds the template locally. ---"},{title:"API",body:"Hosted relay routes are documented in Platform reference \u2014 Catalog. Related: Recipes / \xB7 Cowork \xB7 Shortcuts"}],keywords:["online","catalog","share","and","install","recipes","app","templates","cowork","tasks","shortcuts","across","the","omnish","community","browse","download","from","chat","publish","when","logged","into","platform","discover","requires","account","what","gets","installed","security","api"],relatedCommands:["/run online help","/apps online help","/guides","/platform-reference","/run online","/search","/apps online","/cowork online","/shortcut online","/run","/apps","/cowork","/shortcut","/search list"]},{id:"docs-features-run-queue",path:"docs/features/run-queue.md",title:"`/run` queue (`-q` / `--queue`)",summary:"The run queue runs recipe launches one at a time per chat, in FIFO order. It is meant for back-to-back agent jobs (e.g. several tasks) without starting multiple PTYs at once.",sections:[{title:"Syntax",body:"and are equivalent (case-insensitive). Short form works the same way. Combine with attach flags after the recipe name (same as non-queued ): attaches the queue head on start; without / , the head starts detached (default; see in Configuration). Not the same as the slash subcommand (status) or ."},{title:"Loading many jobs from JSON (`/run queue load`)",body:"You can enqueue multiple recipe tasks in one step from a JSON payload. Each row is validated like a separate (same recipe resolution, task length limits, and behavior). The queue still runs them one at a time in order. Ways to provide the JSON Method What to send -------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Host file path \u2014 path is resolved from this chat\u2019s session cwd (same idea as ): relative segments, globs, and quoted paths work like file selection elsewhere. Exactly one file must match. Inline JSON \u2014 everything after the word (with separating whitespace) is parsed as a single JSON value. Keep the payload on one message line if your client splits on newlines. Inbound file + caption Attach a document (e.g. ) and set the message text to (no path). Omnish uses the saved upload path from that same inbound turn (see Files \u2014 send & receive). You still get the usual \u201CSaved: \u2026\u201D line before the queue result. JSON format The file or inline text must be valid JSON in one of these shapes: Array of jobs (most common): Object with a single property (optional wrapper): Rules: Each job object must contain only two keys: and , both strings. Extra keys are rejected (catches typos like vs ). Top-level object form must be exactly \u2014 no other top-level keys. There must be at least one job. Empty arrays are rejected. Maximum 64 jobs per load. Not supported: raw , , or arbitrary shell in JSON. That would bypass recipe validation; only + are accepted, and omnish builds each run the same way as . File size limit for When reading a JSON file from disk (path argument or saved attachment path), the read is capped: If in is greater than zero, that value is used. If it is zero (no inbound cap), queue-load still uses a 1 MiB ceiling so a huge file cannot be pulled into memory unintentionally. Batch behavior and replies Jobs are enqueued in array order (first element becomes the next head when the queue is idle, or waits behind the current head). Omnish returns one reply that concatenates the status line from each enqueue step (started head, \u201Cwait slot \u2026\u201D, paused, etc.), same semantics as sending several messages in sequence. , , pauses, and clean-exit rules are unchanged \u2014 see Status and control and Pauses and failures. See also (files) Files \u2014 send & receive \u2014 where uploads land, , per-chat ."},{title:"What actually happens",body:"In memory only \u2014 Each waiting item is a small record (command + env + recipe label). Nothing runs until it becomes the head of the queue. Head starts immediately \u2014 When you enqueue and nothing else is running as the queue head, omnish shifts the first item off the FIFO and starts one app session (PTY), same as a normal . Others wait \u2014 Additional / calls append to the waiting list. No extra processes are spawned for those rows; the next job starts only when the previous head session exits with exit code 0 and signal 0 (clean exit). Per chat \u2014 Queue state is keyed by the chat ( ), not global across all users."},{title:"Why `/run queue` can show `Pending: 0` right after you queued something",body:"counts only jobs that have not started yet. The first item you add with / is removed from the pending list as soon as it starts; it then appears under Active with the session name and recipe label. Example: you send three queued runs in a row while the gateway is up: After message 1: Active = new session, Pending = 0 (nothing left waiting). After message 2: Active still the first session, Pending = 1 (second job waiting). After message 3: Pending = 2 (second and third waiting). So after a single enqueue is normal \u2014 it means the one job is already running, not that the queue \u201Clost\u201D your task. If you used before omnish accepted , the old parser treated as part of the task text and ran non-queued instead; the queue stayed empty. Use or after the recipe name (current omnish supports both)."},{title:"Status and control",body:"\u2014 Shows Active (session + recipe), Pending (with a short numbered list of waiting recipe labels), Paused, and a reminder that the next item auto-starts only after a clean exit. \u2014 Clears the paused flag and tries to start the next waiting item (e.g. after you fixed the host or session limits). If a head session is still running, resume tells you to wait until it finishes cleanly. \u2014 Enqueue many jobs from JSON (file path, inline , or attachment + caption); see Loading many jobs from JSON."},{title:"Pauses and failures",body:"Non-clean exit (non-zero exit code, or non-zero signal, e.g. SIGKILL) on the queue head \u2192 the queue pauses; waiting items stay in the FIFO until you (or fix limits and resume). Failed spawn (e.g. per-chat app limit reached) when trying to start the head \u2192 queue pauses and the item is put back; fix the error, then ."},{title:"Resource model",body:"Waiting rows: RAM only (no polling timers for the queue itself). Running head: one PTY + child process, same cost as a normal . Not persisted \u2014 If the gateway process ( ) restarts, the in-memory queue is cleared. Long-term scheduling belongs in Cowork or your own job runner."},{title:"See also",body:"System agents and User guide \u2014 shortcuts vs Interactive sessions ( )"}],keywords:["/run","queue","-q","--queue","the","run","runs","recipe","launches","one","at","time","per","chat","in","fifo","order","it","is","meant","for","back-to-back","agent","jobs","several","tasks","without","starting","multiple","ptys","once","syntax","loading","many","from","json","load","what","actually","happens","why","can","show","pending","right","after","you","queued","something","status","and","control","pauses","failures","resource","model","see","also"],relatedCommands:["/run help","/run queue","/run","/run remosh","/guides","/configuration","/to","/file","/send","/files-send-receive","/receive here","/run name","/system-agents-and-run"]},{id:"docs-features-service-from-chat",path:"docs/features/service-from-chat.md",title:"Service commands from chat (`/service`)",summary:"After WhatsApp or Telegram is connected and your identity is allowlisted, you can manage background gateway setup from the same DM thread \u2014 without SSH.",sections:[{title:"Commands",body:"Command Purpose ----------------------- -------------------------------------------------------------------------------------------------------------------------- Overview OS, data directory, , resolved Node + entry script paths Copy-paste steps for this host (paths filled by the running gateway) Last n lines of the default gateway log (default 80, max 120) Writes a user-level unit (Linux systemd or macOS LaunchAgent). Requires . Removes that unit (same gate). Windows: instructions only."},{title:"Trust model",body:", , and are safe to use like any other slash command (same allowlist as ). and modify login/boot integration files under your home directory. That is equivalent to shell access: anyone who can DM as an allowed identity can trigger them once is true in . Default: is . Enable only when you trust every entry on / as much as SSH. When enabled, reports a warning for ."},{title:"Relation to `CHANGE_ME` templates",body:"The contrib plist/service/XML files use placeholders for manual edits. bypasses that by injecting live paths from the running process. writes generated units with those paths automatically."},{title:"See also",body:"Background gateway and start on boot Cluster and chat configuration ( )"}],keywords:["service","commands","from","chat","/service","after","whatsapp","or","telegram","is","connected","and","your","identity","allowlisted","you","can","manage","background","gateway","setup","the","same","dm","thread","without","ssh","trust","model","relation","to","change","me","templates","see","also"],relatedCommands:["/service help","/service","/service status","/service instructions","/service logs","/service install","/config set","/service uninstall","/help","/boot integration","/contrib","/xml files"]},{id:"docs-features-sessions",path:"docs/features/sessions.md",title:"Interactive Sessions - omnish",summary:"Interactive sessions provide full terminal access within your messaging chats, enabling you to run TUI applications, REPLs, and interactive tools directly from WhatsApp or Telegram.",sections:[{title:"Overview",body:"Interactive sessions use to create pseudo-terminal sessions that mimic real terminal behavior. Each chat can maintain multiple named sessions with independent state. Key Features PTY-based: Full terminal emulation Named sessions: Multiple sessions per chat Focus management: One session attached at a time Output streaming: Real-time output with debouncing ANSI support: Color output and formatting Session persistence: State maintained across chats"},{title:"Session Management",body:"Starting Sessions Session Limits Per chat: Default 5 sessions (configurable) Global: Default 20 sessions (configurable) Named: Each session has a unique name per chat Plain messages and free shell For a single incoming line with no , , or , the router sends text to the attached session first when it is running; free shell mode ( ) applies only if nothing is attached (or the focused session is not running). Use for a one-off sync shell line while attached. Session States Created: Session initialized but not started Running: Session active and accepting input Attached: Session is focused for input Detached: Session running but not focused Stopped: Graceful shutdown requested Killed: Force termination Exited: Process terminated normally"},{title:"Session Commands",body:"Basic Operations Input Control Output Management Session Information"},{title:"Configuration",body:"Session Limits Terminal Settings Behavior Settings When , , or similar shows a password prompt, omnish detects it in recent terminal output and does not send the readline clear keys ( ) for your next reply \u2014 those keys break no-echo password readers and often appear as literal in chat. A one-time hint is sent unless is false. Passwords are still written to the session log on disk."},{title:"Use Cases",body:"Development Workflows ```text"}],keywords:["interactive","sessions","omnish","provide","full","terminal","access","within","your","messaging","chats","enabling","you","to","run","tui","applications","repls","and","tools","directly","from","whatsapp","or","telegram","overview","session","management","commands","configuration","use","cases"],relatedCommands:["/apps help","/apps start","/apps list","/apps attach","/apps detach","/apps stop","/apps kill","/apps send","/apps key","/apps tail","/apps since","/apps mute","/apps raw"]},{id:"docs-features-tunneling",path:"docs/features/tunneling.md",title:"Tunneling \u2014 omnish",summary:"omnish tunneling publishes a public URL that forwards to a local HTTP or TCP port on the machine running the tunnel client. The default relay is .",sections:[{title:"CLI (primary)",body:"Secrets are stored in (mode ) or . Override the relay with , in , or on expose commands."},{title:"Chat (optional)",body:"Login, logout, and status from chat work whenever the gateway runs (even if is false): , , . The bot reply does not echo your token; the inbound chat message still contains it (WhatsApp/Telegram history) \u2014 prefer on the host for highly sensitive tokens. When is in , allowlisted users can also run: Chat tunnels run inside the gateway process ( ) and share the same relay token as the CLI."},{title:"Security",body:"A tunnel URL is a capability: anyone who can open the URL can reach the forwarded service. Dev servers are often unauthenticated; treat tunneling like exposing a port on the public internet. Chat tunneling uses the same trust model as : allowlisted identities can open tunnels as the gateway OS user. in chat stores the bearer token on the gateway host but leaves a copy in the messaging transcript; use host CLI login if that risk matters for your threat model. See Security model."},{title:"Self-hosted relay",body:"For development or private deployments, run the relay in and point omnish at it with or . Operators: testing and operations (health checks, smoke, production VPS layout)."}],keywords:["tunneling","omnish","publishes","public","url","that","forwards","to","local","http","or","tcp","port","on","the","machine","running","tunnel","client","default","relay","is","cli","primary","chat","optional","security","self-hosted"],relatedCommands:["/tunnel help","/tunnels","/config help","/tunnel","/guides","/tunnel-setup-from-zero","/tunnel-auth","/tunnel login","/tunnel logout","/tunnel status","/telegram history","/tunnel http","/tunnel tcp","/tunnel stop"]},{id:"docs-features-watch",path:"docs/features/watch.md",title:"Watch \u2014 OS event eye",summary:"Lightweight OS event subscriptions that notify you on WhatsApp or Telegram when something changes on the machine running .",sections:[{title:"Quick start",body:"Enable watching: Or: or Run . Add rules: You should receive debounced messages like:"},{title:"Device-wide rules",body:"Watch rules are shared on the host, not private to one chat: One namespace per machine in (max 20 rules per device). Any allowlisted peer can , edit, pause, or remove any rule. Rule names must be unique on the device (two peers cannot each have a rule named ). (the default on new rules) alerts the peer who created the rule. Use , , or to reach more recipients. shows the creator peer key. If you had duplicate names from an older per-chat layout, omnish renames extras to on load."},{title:"Runtime model",body:"Watch is not a separate daemon or timed session. It runs inside the gateway process ( , foreground or ). Topic Behavior ----------------- ------------------------------------------------------------------------------------------------------------------------------------------------------- How long Indefinite while the gateway runs, is true, and the rule is enabled and not paused. There is no session timeout. Debounce (default 2s, range 500ms\u201360s) coalesces bursts before chat notify. Rate cap per rule (default 30). Service polls Each rule runs / / about every 30 seconds. Background Same Node process as the gateway; keeps Watch alive like foreground. Not a separate OS service unless you installed the gateway as one."},{title:"Cowork, `/bg`, and Watch",body:"Feature What it does Relation to Watch ---------- ---------------------------------------------------------------- ------------------------------------------------------------------------------------------------- Watch OS events (FS, package logs, services) \u2192 chat alerts \u2014 Cowork Scheduled or on-demand shell commands while the gateway runs Parallel \u2014 same gateway, shared notify routing only. Does not trigger or consume watch rules. Background shell jobs from chat Unrelated \u2014 no integration with watch adapters. See Cowork for task schedules and heartbeats."},{title:"Persistence and restart",body:'Watch rules are not memory-only. They are saved on disk and survive gateway and service restarts. Data Path ------------------------------------------ -------------------------------------------------------------------------------- Rules (paths, excludes, paused, notify, \u2026) Recent events and state-change keys Global on/off and tuning ( , , debounce, rate cap) On gateway boot: If and (default), omnish reloads and starts adapters for rules that are enabled and not paused. If , rules remain on disk but adapters do not run until . If , rules persist but you must (or change a rule) to start adapters without restarting the whole gateway. Use to see file paths, saved rule counts, and adapter health. Troubleshooting persistence Symptom Check --------------------------------- --------------------------------------------------------------------------------- "Rules gone" after restart \u2014 they should still be there No alerts after restart in config; paused/disabled rules; Rules on disk but nothing running or ; if'},{title:"Rule lifecycle",body:"State What it means Command -------------- ------------------------------------- ------------------------------------------------ Active Adapter running, alerts on or Paused Rule kept, adapter stopped, no alerts or Disabled Rule kept in list, Removed Deleted from Global off All adapters stopped After pause, stop, disable, or rm, pending debounced messages are cancelled so you should not get a late alert. pause / stop \u2014 same effect: stop watching, keep the rule for later. resume \u2014 start again if the rule is enabled. enable / disable \u2014 per-rule on/off (distinct from global and ). Check state: , , ."},{title:"Filesystem watches",body:"Add Root path \u2014 first path after the name ( , ). Events \u2014 optional comma list (default: ). Excludes (optional, both supported): Syntax Example ------------------ -------------------------------------- or You can combine them: between exclude clauses is also accepted as a separator. Manage excludes later Excludes are applied twice for efficiency: native watcher ignore list + post-filter before notify."},{title:"Package and service watches",body:"Kind Command -------- ----------------------------------------------------------------------- Packages \u2014 install/remove from OS logs Services \u2014 state changes on named units Use on noisy service checks. Discover services Finding the right unit name is easier with discovery commands (read-only; no rules are created): Command What you get ------------------------------------------ ------------------------------------------------------------------------------------------------ Bulleted services with state, then a second message with copy-paste lines Template lines only (no list) Package log path for this OS + existing FS directories you can watch caps at 40 matches; narrow with a filter (e.g. ). Running units are listed first. Example second message after :"},{title:"Notifications",body:""},{title:"Efficiency and noise control",body:"Watch narrow directories when possible; use excludes for , caches, build output. Built-in ignores: , , , swap files, etc. Debounce \u2014 (default 2s) coalesces bursts. Rate cap \u2014 per rule (default 30). Sensitive paths ( , , keys) are blocked. Resource use: Adapter Cost ------- -------------------------------------------------------------------------------------------------------------------- fs Kernel-native watcher (inotify / FSEvents) \u2014 low for small trees; high if you watch all of without excludes. pkg Tails OS install logs (file watch or 2s poll fallback). svc One subprocess poll every 30s per rule \u2014 many service rules add steady CPU. Timers use so pending debounce/poll timers alone will not keep Node alive. Avoid watching all of without excludes."},{title:"Permissions",body:"Platform Packages Services -------- --------------------------------------- ----------------------------- Linux (often group) for named units macOS labels Windows Application log for named services"},{title:"Troubleshooting",body:"Problem What to do ----------------------- ------------------------------------------------------------------------------------------------- No alerts true? Gateway running? Alerts after pause Should be fixed \u2014 if you paused mid-debounce, wait one debounce window; report if alerts continue unknown Use (name required) Path blocked Sensitive path denylist \u2014 pick another root Adapter error on status Fix log permissions or service names"},{title:"Data files",body:"Rules: Recent events:"},{title:"Config keys (chat-editable)",body:", , , \u2014 see ."},{title:"Related",body:"Cowork \u2014 scheduled tasks and heartbeat Webhook receiver \u2014 CI/CD to chat"}],keywords:["watch","os","event","eye","lightweight","subscriptions","that","notify","you","on","whatsapp","or","telegram","when","something","changes","the","machine","running","quick","start","device-wide","rules","runtime","model","cowork","/bg","and","persistence","restart","rule","lifecycle","filesystem","watches","package","service","notifications","efficiency","noise","control","permissions","troubleshooting","data","files","config","keys","chat-editable","related"],relatedCommands:["/watch help","/config","/watch on","/config set","/watch add","/deploy create","/projects","/tmp","/watch list","/home","/you","/deploy"]},{id:"docs-features-webhook-receiver",path:"docs/features/webhook-receiver.md",title:"Webhook receiver \u2014 CI/CD notifications via chat",summary:"The webhook receiver is a lightweight HTTP server built into the omnish gateway. It accepts requests with JSON payloads, formats them into concise messages, and delivers them to your WhatsApp or Telegram chat via the existing pipeline.",sections:[{title:"Prerequisites",body:"A running gateway: . set to in ."},{title:"Configuration",body:"Key Type Default Description ---------------- ------- ------------- --------------------------------------------------------------------------------------------------------------------------------------- boolean Enable the webhook HTTP server number (random) Port to listen on. picks a random available port. string Bind address. Use to accept external connections (see security note). string Bearer token for authentication. If empty when the receiver starts, a random 32-byte hex token is generated and saved to . Example :"},{title:"Endpoint",body:"The token can also be passed as a query parameter: . Request body Any valid JSON object. The receiver formats the payload into a chat message using built-in formatters for known CI systems, or a generic format for everything else. Optional fields in the JSON body: Field Type Description --------- ------ ------------------------------------------------------------------------------------------------------------------------------------------ string Target chat identity (e.g. or ). If omitted, the message goes to the first allowlisted peer. string Label for the source system (shown in the formatted message). Can also be set via query parameter or header. string Simple text message (used as-is when present). string Fallback message text. string Title line for generic payloads. string Status line for generic payloads. Response Status Body Meaning ------ -------------------------------------------------- ------------------------------ Message delivered Invalid JSON or no target peer Missing or invalid token Not a POST request Body exceeds 256 KB failed"},{title:"Built-in CI formatters",body:"GitHub Actions When the payload contains and a object, the receiver formats: GitLab CI When the payload contains and , the receiver formats: Generic payloads For any other JSON, the receiver uses , , , and fields if present, or falls back to a truncated JSON preview."},{title:"Examples",body:"GitHub Actions workflow Add a step at the end of your workflow to notify on completion: GitLab CI Simple notification from a script"},{title:"Security",body:"The webhook server binds to 127.0.0.1 by default \u2014 only local processes can reach it. If you set to , the server is accessible from the network. Use a firewall or reverse proxy with TLS in production. The bearer token is compared using to prevent timing attacks. Maximum payload size is 256 KB."},{title:"Default peer resolution",body:"When the incoming payload does not include a , the receiver picks the first available peer from the gateway's allowlist: WhatsApp peers are checked first, then Telegram. If no peer is available, the request returns a error."},{title:"See also",body:"Configuration guide \u2014 webhook config keys Cowork heartbeat \u2014 combine with heartbeat tasks for dead-man's-switch monitoring Background jobs \u2014 per-job completion notifications"}],keywords:["webhook","receiver","ci/cd","notifications","via","chat","the","is","lightweight","http","server","built","into","omnish","gateway","it","accepts","requests","with","json","payloads","formats","them","concise","messages","and","delivers","to","your","whatsapp","or","telegram","existing","pipeline","prerequisites","configuration","endpoint","built-in","ci","formatters","examples","security","default","peer","resolution","see","also"],relatedCommands:["/help","/webhook authorization","/json","/repo","/github","/owner","/actions","/runs","/project","/checkout","/your-server","/webhook","/localhost"]},{id:"docs-guides-background-and-boot",path:"docs/guides/background-and-boot.md",title:"Background gateway and start on boot",summary:"Portable CLI (all platforms): starts the gateway detached by default; logs default to ; pass (alias: ) to append elsewhere; reads and stops the process. Equivalent: (or ). Foreground: or . Use these for ad-hoc background runs without installing a system integration.",sections:[{title:"Linux (systemd --user)",body:"Copy contrib/omnish.service to . Set to your Node path and absolute path to , and if the data directory is not . Run: For a user service to run without an active login session, enable lingering:"},{title:"macOS (launchd)",body:"The LaunchAgent label is (reverse-DNS for omnish.dev). Older templates used ; if you already installed that, boot it out before installing the new plist (see below). Edit contrib/dev.omnish.gateway.plist: set Node path, path, and (and log paths if you use them). Copy to . Load (replace with output): To unload: Migrating from : remove the old job first, then install the new file: Alternative: add a small script that runs to System Settings \u2192 General \u2192 Login Items (simpler, no auto-restart on crash)."},{title:"Windows (Task Scheduler)",body:"Open Task Scheduler \u2192 Create Task\u2026 (not a basic task). General: run only when user is logged on (typical for WhatsApp session). Triggers: At log on for your user. Actions: Start a program Program: path to (e.g. from in ). Add arguments: (adjust the path; quotes if it contains spaces). Start in (optional): install folder. Set OMNISHHOME under user environment variables if you do not use the default data directory, or use a wrapper that then runs . Optional: import contrib/omnish-windows-task.xml after editing paths (import may need tweaks per account; the GUI is more reliable if XML import fails). Advanced: NSSM or WinSW can install Node as a Windows Service for machine-wide or headless scenarios \u2014 not maintained by this repo."},{title:"Stopping the gateway",body:"(any OS): uses the pidfile from or . On Windows, if signaling the process fails, omnish may fall back to (best-effort shutdown). systemd / launchd / Task Scheduler: use the manager\u2019s stop / disable as usual; do not rely on from a different start method."},{title:"Environment",body:": data directory (same as elsewhere in omnish). For services, set it in the unit / plist / task environment, not only in the shell profile."}],keywords:["background","gateway","and","start","on","boot","portable","cli","all","platforms","starts","the","detached","by","default","logs","to","pass","alias","append","elsewhere","reads","stops","process","equivalent","or","foreground","use","these","for","ad-hoc","runs","without","installing","system","integration","linux","systemd","--user","macos","launchd","windows","task","scheduler","stopping","environment"],relatedCommands:["/service help","/service instructions","/logs","/gateway","/service","/service logs","/service install","/features","/service-from-chat","/dist","/index","/omnish","/contrib"]},{id:"docs-guides-configuration",path:"docs/guides/configuration.md",title:"Configuration Guide - omnish",summary:"Complete configuration reference and customization guide.",sections:[{title:"Configuration Overview",body:"omnish uses a JSON configuration file with sensible defaults. You can customize every aspect of the system's behavior. Configuration File Location Default: Legacy: (if upgrading) Override: Set Check your configuration location: ```bash omnish status"}],keywords:["configuration","guide","omnish","complete","reference","and","customization","overview"],relatedCommands:["/config help","/config keys","/config","/path","/to","/dir","/bin","/bash","/features","/chat-llm-fallback","/tunnel","/login","/v1","/me"]},{id:"docs-guides-coordinator-workforce",path:"docs/guides/coordinator-workforce.md",title:"Coordinator workforce manager",summary:"The job board Coordinator is a deterministic workforce layer: onboard employees, assess skills, customize work flow, dispatch through the harness, record performance, collect human feedback, and suggest recruitment when roles are overloaded.",sections:[{title:"Communication (all employees)",body:"Every specialist run receives , , , and (coordinator-only) . Do not document communication as alone \u2014 always use the full terminal commands below (requires on the host). Role When Full command ------------------ ------------------------------------------------------------- ------------------------------------------------- Any specialist While job is (at least once per phase; on blockers) Coordinator After supervisor pass, when the plan requires file delivery Helper script (employee pack): runs the specialist command. Specialists must not send final CHECK deliverables via sendto before review. The coordinator has final say on files. At handoff, must still include: \u2014 what was communicated to the coordinator/human \u2014 plain-language outcome for the job owner Work plans include a step under the plan phase. The supervisor rejects handoffs missing these fields. When is true (default), the coordinator runs the file-send command automatically after if the plan mentions / distribution and a media CHECK file exists in the work dir."},{title:"Onboarding",body:"Creates with , , and registry entry. Coordinator seeds core competencies and activates the employee when the skill pack exists."},{title:"Skills and flow",body:"Before dispatch (when is true), the Coordinator assesses gaps vs the job plan. With , it runs your agent to fill ; otherwise a deterministic template fills gaps and writes . Per-employee may add extra steps in allowed phases; required phases and cannot be removed. Harness env: , ."},{title:"Performance and feedback",body:"After each supervisor verdict, a row is stored in . When a job passes:"},{title:"Recruitment",body:"Heuristics (overload, multi-domain plans, rework) create suggestions. Humans accept before a new employee exists:"},{title:"Optional finetuning",body:"When and the coordinator agent are enabled, poor feedback can produce files under . Promote only after review:"},{title:"Config",body:"Key Default Purpose --------------------------------- ------- ----------------------------------------------------------------------------------- External agent for research/finetune Shell command Allow proposed tuning Gap assessment before dispatch Coordinator runs after done when plan requires delivery One-shot satisfaction reminder See also Employee task flow, digital-employees.md, and job-board.md."}],keywords:["coordinator","workforce","manager","the","job","board","is","deterministic","layer","onboard","employees","assess","skills","customize","work","flow","dispatch","through","harness","record","performance","collect","human","feedback","and","suggest","recruitment","when","roles","are","overloaded","communication","all","onboarding","optional","finetuning","config"],relatedCommands:["/sendto","/employee-pack-example","/scripts","/board-status","/human","/board employee","/board","/employees","/board feedback","/board recruit","/proposed","/finetune"]},{id:"docs-guides-digital-employees",path:"docs/guides/digital-employees.md",title:"Digital employees \u2014 job board, coordinator, supervisor",summary:"omnish stays local execution infrastructure. Digital employees are external agent CLIs (or shell wrappers) coordinated by three meta-roles plus specialists.",sections:[{title:"Roles",body:"Role Responsibility Human? --------------- ------------------------------------------------------------------------------------------------- ----------- Human Posts parent jobs to the job board Intake only Coordinator Assigns specialists, creates prerequisite jobs, unblocks dependents, enforces resource limits No Supervisor Verifies work against the job plan; pass, rework, or fail No Specialist Runs assigned work; writes No The coordinator and supervisor run inside the gateway while it is up ( defaults to true). Use for a one-shot cycle or set to for fully manual mode."},{title:"Job board",body:"Storage: Artifacts: (report, verdict, audit via DB) Status flow Full pipeline (human intake through supervisor and feedback): Employee task flow. Chat commands and are aliases. Acceptance criteria in the plan Supervisor runs deterministic checks from the plan: Optional LLM review can wrap the same contract later; the built-in supervisor is deterministic first."},{title:"Core competencies (always on)",body:"No matter how vague an is (e.g. ), omnish never runs it directly. The employee harness always: Seeds with canonical steps: plan \u2192 implement \u2192 execute \u2192 test \u2192 review Runs the inner command via Tracks phase progress and notifies the job owner (configurable) Enforces handoff via the supervisor (work plan + report contract) You define what runs ( ). omnish defines how employees work. Competency Enforcement ----------------------- ----------------------------------------------------------- Five phases Pre-seeded work plan Plan before work Harness seeds plan before inner CLI Structure + self-review Supervisor + step Outcome ownership , , Progress visibility Chat notifications on phases and status"},{title:"Specialist contract",body:"Each employee is configured in : Legacy is accepted as an alias for . Environment injected at run time: , , , \u2014 harness-seeded \u2014 \u2014 e.g. Plan before work (harness + supervisor) The harness creates the work plan before your agent runs. Specialists mark steps complete (in script or via ). Before handoff: All steps and set; with and matching The supervisor rejects handoffs that miss any core competency (with human-readable labels in verdict notes). Schema: work-plan.schema.json. Example scripts: contrib/employee-pack-example/specialists/. report.json (required) See contrib/employee-pack-example/schemas/report.schema.json. Must reference and with matching completed ids in the work plan. Prerequisite requests (exit code 2) If work cannot start, exit 2 and set in the report. The coordinator creates child jobs, blocks the parent, and unblocks when children reach . Specialists must not spawn parallel agents themselves."},{title:"Resource governor",body:"(defaults created on first coordinator run): Field Default Meaning --------------------- ------- ---------------------------------- 2 Host-wide running specialists 1 Extra cap for jobs 75 Pause dispatch when load is high 2048 Pause when free RAM is low 90 Optional gate Check live status: ."},{title:"Progress notifications",body:"In : Setting Values Effect ---------------------- ------------------------------- -------------------------------------------------- \\ \\ Phase step completion (and for minor status) \\ Start, review, done, failed, blocked, rework Examples: , ."},{title:"Coordinator workforce",body:"Onboarding, skills, performance history, human feedback, and recruitment suggestions are documented in Coordinator workforce manager."},{title:"Configuration",body:"In : Primary agent for all registry roles: Escalations (job after max rework) always notify the job owner on the allowlisted chat surface."},{title:"Example pack",body:"contrib/employee-pack-example \u2014 stub , , scripts and sample . Point your registry at real agent CLIs when ready: Then reference that wrapper from instead of the stub scripts."},{title:"Related",body:"Employee task flow \u2014 human \u2192 coordinator \u2192 specialist \u2192 accomplishment Job board feature reference System agents and Cowork \u2014 scheduled shell shifts (complementary)"}],keywords:["digital","employees","job","board","coordinator","supervisor","omnish","stays","local","execution","infrastructure","are","external","agent","clis","or","shell","wrappers","coordinated","by","three","meta-roles","plus","specialists","roles","core","competencies","always","on","specialist","contract","resource","governor","progress","notifications","workforce","configuration","example","pack","related"],relatedCommands:["/board tick","/board","/cowork","/jobs","/employee-task-flow","/board add","/ok","/board list","/board show","/board capacity","/board cancel","/board employees"]},{id:"docs-guides-docker-gateway-golden-path",path:"docs/guides/docker-gateway-golden-path.md",title:"Docker gateway \u2014 golden path (reference)",summary:"Run omnish inside a container so a deployed app gains chat-driven shell access on that box. Reference files: .",sections:[{title:"Two ways to use omnish in Docker",body:"Path When Setup in container -------------------------- ------------------------------------------------ ----------------------------------------------------------------------------------------------- Attached (recommended) Platform account; messengers linked on dashboard , , , \u2014 no QR in container Standalone No hosted layer; expert / air-gapped Persistent volume + + inside container (or on host) The compose file in implements standalone by default. Use attached env vars for the platform path (see Platform attached mode, Platform reference)."},{title:"Attached mode (implemented)",body:"Add chat-driven ops to an existing app container without Baileys inside the image: Link WhatsApp/Telegram on the platform dashboard (once per account). Set allowlist on the dashboard. Deploy with the env above; outbound HTTPS to the platform only. Legacy env aliases: , . Messengers do not run inside the container; the platform routes chat to this CLI."},{title:"Standalone reference (implemented today)",body:"What you get Pinned npm version ( ). Non-root user (uid 1000), as PID 1. HEALTHCHECK ( ) \u2014 process up only; not messenger connectivity. Build and run First-time pairing (empty volume): Then as above."},{title:"Networking",body:"Concern Guidance -------------- -------------------------------------------------------------------------------------------------- Attached Outbound HTTPS to communication layer; no Baileys in container Standalone Outbound to WhatsApp/Telegram APIs from container Tunnels Tunnel client runs in gateway namespace; see Tunnel setup from zero"},{title:"Environment",body:"Standalone today: \u2014 volume mount (compose: ) , \u2014 optional overrides Attached: , (aliases: , ) Optional /"},{title:"Security",body:"Allowlisted inbox \u2192 real shell as the container user. Protect volumes and tokens. Do not expose without TLS and auth \u2014 Webhook receiver."},{title:"See also",body:"Communication layer model Background gateway and start on boot"}],keywords:["docker","gateway","golden","path","reference","run","omnish","inside","container","so","deployed","app","gains","chat-driven","shell","access","on","that","box","files","two","ways","to","use","in","attached","mode","implemented","standalone","today","networking","environment","security","see","also"],relatedCommands:["/gateway-docker","/contrib","/architecture","/communication-layer-model","/tunnel","/telegram on","/docker-compose","/telegram apis","/tunnel-setup-from-zero","/home","/node","/features"]},{id:"docs-guides-employee-task-flow",path:"docs/guides/employee-task-flow.md",title:"Human \u2192 Coordinator \u2192 Employee \u2192 Task accomplishment",summary:"End-to-end flow for the omnish job board: how work moves from a human through the coordinator and a harnessed digital employee to supervised completion.",sections:[{title:"Four roles (not one \u201Cemployee\u201D)",body:"Role Who Responsibility --------------------------------- -------------------------------------------------- -------------------------------------------------------------------------- Human Allowlisted chat peer (job owner) Creates jobs, cancels, optional feedback after completion Coordinator Gateway loop ( ) Unblocks deps, picks assignee, acquires resource slots, dispatches harness Specialist (digital employee) External CLI from Runs inside harness; writes + Supervisor Deterministic QA: pass \u2192 , rework, or fail The coordinator does not judge quality. The supervisor does not choose the next assignee or create prerequisite jobs. That split is intentional \u2014 see Job board \u2014 separation of duties."},{title:"High-level sequence",body:""},{title:"Phase 1 \u2014 Human intake",body:"Entry: Chat \u2192 ( ) \u2192 ( ). Example: What happens: Job in with Work directory: = the human who posted the job Optional ; otherwise the coordinator picks on dispatch via plan heuristics Human can also: , , , (one coordinator cycle), , , and post-completion ."},{title:"Phase 2 \u2014 Coordinator tick (automation)",body:"Trigger: when is true, or . Each tick runs once, in this order: \u2014 \u2192 when all jobs are Notify pending jobs whose dependencies just became satisfied \u2014 diff and notify owner of phase changes (if configured) \u2014 every job \u2192 (may finish work from a previous dispatch) Dispatch at most one new job (if governor slot available): \u2014 or , deps satisfied Auto-assign via if no assignee (review \u2192 reviewer; implement/build/code \u2192 implementer; else researcher) + \u2014 skills research, communication brief, role context \u2014 harness execution \u2192 again \u2014 supervises jobs that entered during this tick (so one can dispatch and finish) Implementation sets directly from / . The status exists in the type system but is not used on the main dispatch path. Default: is while the gateway runs; status and handoff messages go to the job owner on chat."},{title:"Phase 3 \u2014 Digital employee execution (harness)",body:"The coordinator never runs directly. Load specialist from ( only; legacy alias) \u2014 phases: plan \u2192 implement \u2192 execute \u2192 test \u2192 review Env: , , , , briefing extras \u2192 Specialist marks steps (env hints or ) Specialist writes ( , , , , \u2026) Outcomes after harness exits: Exit / report Job status Next step ------------------------------------- ---------------------- ----------------------------------------------- Normal (not exit 2) Supervisor on this or next tick Exit 2 + Child jobs created; parent waits on Missing registry / bad config (stub report) Supervisor likely rework / fail When children reach , the parent unblocks and is dispatched again."},{title:"Phase 4 \u2014 Supervisor (quality gate)",body:"calls for each job. Checks include: exists and validates (steps complete, , competencies) contract (summary, , , communication, handoff) matches completed step ids in the work plan Plan lines \u2014 files must exist under the work dir Verdict \u2192 status: Verdict New status Notes ---------- ---------- --------------------------------------------------------- pass prompts feedback; may suggest recruitment rework Re-enters queue until (default 3) fail Owner notified; slot released Writes , audit rows, and performance history per assignee. Technical accomplishment = supervisor pass. Human satisfaction is separate ( )."},{title:"Phase 5 \u2014 Human closure (after `done`)",body:"Optional but closes the workforce loop: if a role-split was suggested Optional coordinator agent ( ) can propose / under \u2014 promote only after review via ."},{title:"Job status state machine",body:"Statuses: , , (reserved), , , , , , \u2014 see ."},{title:"Artifacts per job",body:"Path Writer ----------------------------------- -------------------- Harness + specialist Specialist (or stub) Supervisor Status and audit"},{title:"Not this flow",body:"is scheduled shell shifts \u2014 complementary, not the human \u2192 coordinator \u2192 specialist \u2192 supervisor pipeline."},{title:"Summary",body:"The human posts criteria to the board; the coordinator ticks to unblock, review prior handoffs, and dispatch one runnable job through the harnessed specialist CLI; the supervisor accepts or rejects into / / ; the human optionally rates satisfaction after ."}],keywords:["human","coordinator","employee","task","accomplishment","end-to-end","flow","for","the","omnish","job","board","how","work","moves","from","through","and","harnessed","digital","to","supervised","completion","four","roles","not","one","high-level","sequence","phase","intake","tick","automation","execution","harness","supervisor","quality","gate","closure","after","done","status","state","machine","artifacts","per","this","summary"],relatedCommands:["/digital-employees","/features","/job-board","/coordinator-workforce","/board","/coordinator","/employees","/supervisor","/board add","/board tick","/board feedback","/chat"]},{id:"docs-guides-interactive-cli",path:"docs/guides/interactive-cli.md",title:"Interactive terminal (`omnish i`)",summary:"Run the same commands you use in chat, but from your local terminal.",sections:[{title:"Commands",body:"Run from any directory. Your CLI session\u2019s working directory starts at (change it with or using your configured command prefix). Flags Flag Meaning ------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------- Sender key for chat-driven cluster commands ( or ). If omitted, a synthetic sender tied to the CLI session is used ( ). / Run a single line and exit (non-interactive). Useful for scripts."},{title:"Trust model",body:"is not gated by the inbox allowlist. Anyone who can run commands on your machine can use it\u2014it has the same trust as your login shell. Chat interfaces remain allowlisted as before."},{title:"`/sendto`: push files or plain text to WhatsApp or Telegram",body:"When is active on this machine, you can use from to choose exactly who gets files, or to send a plain chat message (not an attachment). Syntax Files: selectors are checked from the current folder. Add an optional caption after . Plain text: same destinations; message is everything after the flag (or the value). To send a file whose name looks like a flag, use an explicit path (e.g. ). Destination forms: \u2014 all WhatsApp recipients from \u2014 all Telegram recipients from \u2014 both channels (all allowlisted WA + TG recipients) \u2014 one explicit WhatsApp recipient \u2014 explicit WhatsApp recipient list \u2014 one explicit Telegram recipient (compatibility form) Selector forms: \u2014 explicit list \u2014 cwd glob \u2014 recursive cwd glob Compatibility aliases also accepted: , , , , . Examples: Common patterns ```text"}],keywords:["interactive","terminal","omnish","run","the","same","commands","you","use","in","chat","but","from","your","local","trust","model","/sendto","push","files","or","plain","text","to","whatsapp","telegram"],relatedCommands:["/help","/sendto","/sendto wa","/photo","/sendto tg","/report","/promo","/downloads","/telegram outbound","/send","/files-send-receive","/bg","/reload"]},{id:"docs-guides-platform-attached-mode",path:"docs/guides/platform-attached-mode.md",title:"Platform attached mode",summary:"Run on a laptop, server, or container while WhatsApp and Telegram stay on the hosted omnish platform (tunnel relay). You link messengers once on the dashboard; each machine attaches with an account token and executes shell commands locally.",sections:[{title:"Standalone vs attached",body:"Standalone (default) Attached (platform) ---------------------------- ---------------------------------------- --------------------------------------------------------------- When No platform URL + token + (or env) set Where messengers connect Same host as Hosted relay (dashboard) Link WhatsApp on the device Dashboard QR or Link Telegram on the device Dashboard bot token Allowlist \u2192 local Dashboard allowFrom / telegramAllowFrom (authoritative) Shell execution Local Local (unchanged) If platform credentials are set, does not start local Baileys/Telegram clients \u2014 it opens a WebSocket to the platform and runs commands on this host only."},{title:"Prerequisites",body:"A platform account (signup/login on your relay \u2014 see tunnel relay README). Messengers linked on the platform (not via on the device, unless you use import \u2014 see below). Your phone or Telegram id on the platform allowlist (dashboard). Outbound HTTPS from the device to the platform URL (and correct reverse-proxy routing if you self-host)."},{title:"Step 1 \u2014 Account and token",body:"On the hosted relay (e.g. ): Sign up or log in via / , or use . Copy the account token from the response or dashboard. The same token works for: Attached gateway: + Persisted config:"},{title:"Step 2 \u2014 Link messengers on the platform",body:"Do this on the platform, not on the machine that will run shell commands (unless noted). WhatsApp Option A \u2014 Dashboard (recommended, 1 minute) Open the platform dashboard ( on your relay origin). Click Link WhatsApp \u2014 a QR appears automatically. Scan with WhatsApp \u2192 Linked devices. The phone may show \u201CLogging in\u201D for a few seconds while the platform reconnects after scan (515 restart \u2014 dashboard shows Finishing link\u2026; no second QR). When connected, use Add my number to allowlist (one click) if shown. Run on your machine. Option A2 \u2014 CLI QR in terminal Scan the ASCII QR, then: To disconnect: dashboard Unlink WhatsApp or . Option B \u2014 Import from a machine that already linked locally On a machine where you ran successfully, stop . Set platform URL + token, then: ```bash omnish platform import-whatsapp"}],keywords:["platform","attached","mode","run","on","laptop","server","or","container","while","whatsapp","and","telegram","stay","the","hosted","omnish","tunnel","relay","you","link","messengers","once","dashboard","each","machine","attaches","with","an","account","token","executes","shell","commands","locally","standalone","vs","prerequisites","step"],relatedCommands:["/help","/architecture","/communication-layer-model","/gateway-config-precedence","/contrib","/tunnel-relay","/readme","/telegram clients","/login on","/tunnel","/auth","/signup","/login"]},{id:"docs-guides-platform-reference",path:"docs/guides/platform-reference.md",title:"Platform reference (complete)",summary:"Consolidated documentation for the omnish hosted platform (tunnel relay + dashboard + attached ). For a guided walkthrough, start with Platform attached mode. For relay deployment, see contrib/tunnel-relay/README.md.",sections:[{title:"One-minute checklist",body:"Step Action ---- --------------------------------------------------------------------------------------------------------------------------------------- 1 Sign up / log in at \u2014 copy account token 2 Link WhatsApp: dashboard Link WhatsApp \u2192 scan QR \u2192 Add my number to allowlist (if offered) 3 Link Telegram: paste bot token \u2192 Link / restart Telegram \u2192 DM bot \u2192 add id under Allowlists \u2192 Save allowlists 4 On your machine: (or ) 5 then 6 From allowlisted chat: or ---"},{title:"How it works",body:"Messengers terminate on the relay (Baileys + grammY). Shell runs only on machines where is attached. Policy (allowlists, ) is stored on the platform and merged into the attached CLI via (refreshed every 5 minutes and on connect). ---"},{title:"What is persisted",body:"Requires on the relay (account data). Without MongoDB, only static relay tokens work \u2014 no dashboard accounts or persistence. Data Storage Survives relay restart --------------------------------- --------------------------------------------- ------------------------- Account token, email MongoDB Yes , MongoDB Yes MongoDB Yes Telegram bot token MongoDB ( ) Yes (not returned by API) WhatsApp Baileys auth files Disk Yes Connector status, linked WA phone MongoDB Yes Peer \u2192 device bindings MongoDB Yes Device slots MongoDB Yes Community catalog entries MongoDB Yes On MongoDB connect, linked connectors are restored automatically ( ). Volumes (self-hosted): \u2014 WhatsApp sessions MongoDB \u2014 accounts, allowlists, connector sessions ---"},{title:"Dashboard (`/dashboard/`)",body:"After login, the dashboard loads and hydrates all forms. Account status Shows , WhatsApp/Telegram connector state, allowlist counts, default device, online device count. Devices Create device slots, set default device, see online/offline status. The first attached may auto-create a device. Routing Peer bindings map a (e.g. , ) to a specific . Routing order when a message arrives: Peer binding (if set) Single online device (if exactly one) Default device (if online) Any online device Allowlists Field Format Notes -------- ---------------------- ------------------------------------------- WhatsApp E.164, comma-separated e.g. Telegram Numeric user ids Use bot in DM to discover your id Save allowlists \u2192 . Changes apply immediately (connectors reload allowlists per message). Warning: empty allowlist = any sender can run commands. Telegram connector Paste bot token from @BotFather. Link / restart Telegram \u2014 token required on first link; if already linked, empty token field reuses saved token. Users DM the bot before allowlisting to learn their numeric id. WhatsApp connector State UI ------------------ -------------------------------------------------------------------------------------------------------------- Not linked Link WhatsApp \u2014 shows QR, auto-polls until connected; Refresh / Restart pairing while QR is active After scan, WhatsApp closes with 515 \u2014 QR hidden, \u201CFinishing link\u2026\u201D (10s); normal Runtime reconnect after a drop \u2014 QR hidden Linked Green \u201Cconnected\u201D, optional Add my number to allowlist, Unlink WhatsApp Logged out / error Reconnect (unlink + new QR) Advanced: import from local via (see CLI below). Attached CLI snippet Shows , , optional , and . ---"},{title:"CLI reference (`omnish platform \u2026`)",body:"All commands require + (config or env). Run for the latest help text. Setup and diagnostics Command Purpose ------------------------------------------------------------------ --------------------------------------------------------- Save credentials to Same (config CLI aliases) Account, connectors, allowlist counts, online devices URL, token source, effective platform block Test WebSocket paths before Attach device (attached mode when platform creds resolve) Allowlists (platform policy) Command Purpose --------------------------------------------------- ------------------------------------- Show WhatsApp and Telegram allowlists Merge entries into allowlists Replace one or both lists Examples: Wildcard is rejected on the platform (same security model as standalone). WhatsApp on platform Command Purpose --------------------------------------------------------------- ---------------------------------------------------------------------------------------- Start pairing, print ASCII QR, poll until linked (includes after scan) Remove session and auth files on platform Upload local Baileys auth from on this host Stop local before import to avoid WhatsApp session conflicts. Environment variables Canonical Also accepted Purpose --------------------- ---------------------------------------------- ------------------------- , Relay base URL , Account bearer token in config Pin device slot on attach Precedence: environment \u2192 \u2192 (if used). Config keys: ( ), ( ), ( ). ---"},{title:"HTTP API reference",body:"Base URL: relay origin (e.g. ). Auth: for most routes. Catalog browse/download routes are public (no bearer required); publish requires a bearer token. Chat commands for the catalog: Online catalog. Auth (no bearer on signup/login body) Method Path Body Response ------ -------------- --------------------- ----------------- POST (201) POST Account Method Path Body Response ------ ----------------------- ------------------------------------ --------------------------------------------------------------------------------------------------------------------- GET \u2014 , , , , , , , , , \u2026 PUT PUT GET \u2014 shape (example): means a bot token is stored; the token is never returned. Devices and routing Method Path Body Response ------ ------------------ ----------------------- ---------------------------------------------------- GET \u2014 POST (201) PUT DELETE Telegram connector Method Path Body Response ------ ------------------------- ----------------------------------- -------------------------- PUT required on first link; omit on later calls to reuse stored token. optional; can also use . WhatsApp connector Method Path Body Response ------ -------------------------------- ---------------------------------------------------- --------------------------------------------------------------------------- POST (optional legacy ) GET \u2014 POST GET \u2014 Legacy; prefer POST Status values: , , , , , , , . After a successful QR scan, Baileys typically closes with (515) and reconnects using saved creds (no second QR). The connector reports (no ) until the new socket opens as . Catalog (community templates) Public read (no bearer). Publish requires account bearer token. Method Path Auth Query / body Response ------ -------------------------------- ------------ ----------------------------------------------------------------- ------------------------------------------------------------------------------------------------- GET optional GET optional GET optional \u2014 Full entry including POST required POST optional \u2014 Full entry; increments values: , , , . Upsert on publish is per . Max payload 32 KiB. Recipe payloads must include quoted (or custom ) in the command. WebSocket (attached CLI) Path Purpose --------------------- ------------------------------------------------------- Primary attach path Fallback when not proxied to control port Register frame: Inbound: Outbound: Reverse proxy (required paths \u2192 control port 8788) , , , , If these hit the HTTP edge (8787) instead, attach fails with WebSocket 400. Run . ---"},{title:"Telegram `/id` command",body:"Works on platform-linked and standalone bots, before the user is on the allowlist. Mode Reply hints ---------- ------------------------------------------------------------ Platform Numeric id + + dashboard / Standalone Numeric id + See Telegram integration notes. ---"},{title:"Policy in attached mode",body:"When attaches successfully: loads , , . These override local for inbound messenger policy. Host-only settings (shell, jobs, tunnels, webhooks) still come from local config. Policy refreshes every 5 minutes while attached. If fails at startup, the CLI may fall back to local allowlists until the next successful sync. You do not need on the device for platform-routed chat when platform policy loads successfully. ---"},{title:"Troubleshooting index",body:"Symptom See -------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- WebSocket 400 on attach Platform attached mode \u2014 Troubleshooting, relay README Device online, no command output Platform allowlist; relay logs; Troubleshooting \u2014 Attached Dashboard fields empty after login Requires MongoDB; hard-refresh; re-save allowlists Telegram token \u201Cgone\u201D after reload By design \u2014 token not shown; use empty field + Link to reuse Phone stuck on \u201CLogging in\u201D after scan Wait 15s \u2014 platform should show then connected; see Troubleshooting \u2014 Platform WhatsApp WhatsApp stuck / logged out Dashboard Reconnect or then link-whatsapp with correct allowlist WhatsApp LID resolution \u2014 update relay + CLI Empty allowlist surprises Empty = allow all senders ---"},{title:"See also",body:"Platform attached mode \u2014 guided setup Quick start \u2014 Path C Configuration \u2014 Platform / attached mode Docker gateway golden path Tunnel setup from zero \u2014 same account token for tunnels Communication layer \u2014 API sketch (future notes; implemented API is in this doc)"}],keywords:["platform","reference","complete","consolidated","documentation","for","the","omnish","hosted","tunnel","relay","dashboard","attached","guided","walkthrough","start","with","mode","deployment","see","contrib/tunnel-relay/readme","md","one-minute","checklist","how","it","works","what","is","persisted","/dashboard/","cli","http","api","telegram","/id","command","policy","in","troubleshooting","index","also"],relatedCommands:["/tunnel-relay","/readme","/contrib","/architecture","/communication-layer-model","/gateway-config-precedence","/telegram-integration-notes","/dashboard","/id","/v1","/me","/whatsapp"]},{id:"docs-guides-quick-start",path:"docs/guides/quick-start.md",title:"Quick Start Guide - omnish",summary:"",sections:[{title:"What you\u2019ll get",body:"By the end of this page you will have: A working gateway \u2014 listening for your DMs. A first win \u2014 a successful sync command (e.g. or ) with output back in chat. A path to deep control \u2014 optional: for a PTY smoke test (see the main README)."},{title:"What is omnish?",body:"A secure CLI tool that bridges messaging platforms (WhatsApp, Telegram) to your system shell. It allows allowlisted users to: Execute shell commands directly from chat Run background jobs with streaming output Start interactive terminal sessions Transfer files between your system and chats Key differentiator: No AI layer - direct, deterministic shell access with explicit security controls."},{title:"One phone with WhatsApp",body:"You do not need a second phone. Host \u2014 omnish runs on a laptop, desktop, home server, or VPS (Linux, macOS, or Windows). It is a CLI gateway, not a phone app. Phone \u2014 you use the same WhatsApp account: scan the QR once under Linked devices, then send command DMs from that app (for example Message yourself). Linking works like WhatsApp Web: your account is the phone plus linked devices. Session files live under (see Comprehensive documentation). The phone stays the primary device; omnish on the host is an extra linked device. Keep the phone online as WhatsApp expects for multi-device; Meta\u2019s caps on linked devices still apply. Follow Path A: WhatsApp below for install, , , and . Private chats only \u2014 groups are ignored. No computer handy? You still need a machine that stays on to run ; a small VPS is typical. If you prefer not to use WhatsApp linked devices, you can use Telegram instead ( ); see Telegram integration notes. Security Treat allowlisted numbers (and Telegram ids) like remote shell passwords \u2014 only add identities you fully trust. See the main README and Security model."},{title:"Installation",body:"From npm (recommended) npm 11+: Advisory warnings during install are expected and non-blocking. omnish declares trusted native deps in its package manifest. macOS: If shell commands fail with , update omnish (recent versions fix permissions automatically). Manual fix: . See Troubleshooting \u2014 macOS spawn-helper. From source ```bash git clone https://github.com/eligapris/omnish.git cd omnish pnpm install"}],keywords:["quick","start","guide","omnish","what","you","ll","get","is","one","phone","with","whatsapp","installation"],relatedCommands:["/help","/wa help","/tg help","/omnish","/media","/logo-horizontal","/apps start","/readme","/auth","/comprehensive-documentation","/telegram-integration-notes","/architecture","/security","/node_modules","/node-pty"]},{id:"docs-guides-system-agents-and-run",path:"docs/guides/system-agents-and-run.md",title:"System agents, multi-agent, and `/run`",summary:"omnish is built for agents on your machine \u2014 not another hosted model or subscription. You run on hardware you control; from chat you drive system agents: CLIs and TUIs such as , , your own scripts, or multi-step orchestrators. Omnish forwards deterministic shell and PTY to those tools; it does not replace them.",sections:[{title:"Curated mobile notifications",body:"When running coding agents from mobile, keep sessions detached/muted and send only curated updates. Use the status format: Recommended update types: Ack (task accepted) Progress (phase milestone) Question (human input needed) Done (result summary) Blocked (failure or blocker) The repository includes a helper script: It targets , then , and falls back to . For a persistent chat agent (plain DMs queued to one long-lived process), enable and set to a wrapper that calls on milestones. The daemon receives the same notify env as ( , , ). See Chat agent daemon and ."},{title:"Default macro command (`recipesMacroDefaultCommand`)",body:"In , is the shell command used when a macro-style recipe stores a long body as a . The default targets the Claude CLI agent: Point it at any agent or orchestrator that reads the task from the environment, for example a wrapper that fans out to multiple local agents: Restart the gateway after editing (or use when supported)."},{title:"Per-chat recipes with `/run add`",body:"The stored command must reference (or your custom ). Example: Cursor / other CLI agents Example: Claude (default-class agent CLI) Patterns are the same for any binary on the gateway host: omnish only spawns the process you configure."},{title:"Optional: local model CLIs",body:"If your agent stack sometimes calls a local inference CLI (e.g. Ollama), you can still register it in a recipe \u2014 same pattern. That is optional plumbing, not the core story: Prefer scripts on disk for awkward HTTP/JSON so you are not pasting payloads into chat."},{title:"Limits and safety",body:"\u2014 see configuration. \u2014 gated built-ins; leave off unless you understand the risk. Secrets: use env vars on the gateway host or your secret store; do not paste keys into chat."},{title:"Discoverability",body:", Cowork: \u2014 scheduled and on-demand tasks, notifications, logs (name unchanged)."},{title:"See also",body:"User guide \u2014 shortcuts vs Configuration Interactive sessions Background jobs vs Cowork"}],keywords:["system","agents","multi-agent","and","/run","omnish","is","built","for","on","your","machine","not","another","hosted","model","or","subscription","you","run","hardware","control","from","chat","drive","clis","tuis","such","as","own","scripts","multi-step","orchestrators","forwards","deterministic","shell","pty","to","those","tools","it","does","replace","them","curated","mobile","notifications","default","macro","command","recipesmacrodefaultcommand","per-chat","recipes","with","add","optional","local","limits","safety","discoverability","see","also"],relatedCommands:["/run help","/apps help","/run","/apps start","/apps attach","/jobs","/cowork","/cw","/features","/sessions","/apps","/run queue","/run-queue"]},{id:"docs-guides-tunnel-setup-from-zero",path:"docs/guides/tunnel-setup-from-zero.md",title:"Tunnel setup from zero (gateway host)",summary:"This guide is for someone who wants public URLs for apps running on the machine where executes, using WhatsApp or Telegram (optional commands) or the CLI.",sections:[{title:"What runs where",body:"Piece Where Role ------------------ ------------------------------------------------------ --------------------------------------------------------------- Relay VPS (e.g. ) or your own Docker host Accepts HTTPS/WSS, forwards to connected clients omnish gateway Your PC / server \u2014 runs shell, chat commands, and tunnel client Your app Same host as the gateway (usually) , etc. The default public relay is . You can self-host from and point clients at your origin."},{title:"Checklist",body:"Install omnish on the gateway host Native modules may require a normal install (not ). See the project README. Link WhatsApp and/or Telegram, allow yourself ```bash omnish link omnish allow +YOURE164"}],keywords:["tunnel","setup","from","zero","gateway","host","this","guide","is","for","someone","who","wants","public","urls","apps","running","on","the","machine","where","executes","using","whatsapp","or","telegram","optional","commands","cli","what","runs","checklist"],relatedCommands:["/tunnel help","/tunnel login","/tunnel","/features","/tunneling","/contrib","/tunnel-relay","/docs","/testing-and-operations","/docker-gateway-golden-path","/wss","/or telegram","/auth","/signup"]},{id:"docs-guides-ui",path:"docs/guides/ui.md",title:"Browser setup UI (`omnish ui`)",summary:"Local-first configuration panel that edits the same as the CLI and chat commands. Use it when you want to finish basics from a phone on your LAN before touching WhatsApp/Telegram.",sections:[{title:"Quick start",body:"Default bind is (reachable on your LAN). The CLI prints: A setup token (saved under your data dir as ; legacy installs may have had , which is migrated automatically) URLs on localhost and discovered IPv4 LAN addresses A quick link that includes so your phone can authenticate in one tap Then open the UI in a browser, unlock with the token, edit core settings, and Save."},{title:"Run the gateway from the UI",body:"After configuration (and WhatsApp pairing if you use it), you can start the chat gateway without going back to the terminal: Under Host snapshot, use Start gateway. This is equivalent to (or ): a detached process runs with the same data directory, and stdout/stderr append to the default log file ( \u2014 the panel shows the resolved path). Stop gateway sends SIGTERM to the background gateway tracked by , same idea as (including stale pidfile cleanup when the process is already gone). Starting the gateway does not stop the setup UI. You can Stop setup server and leave the gateway running in the background. Authenticated session cookies are required (same as the rest of the panel): and . Port reclaim and stopping the UI The host writes in your data dir with the last process PID and bind port. When you start again on the same (default 3789), the new process terminates the previous one if it is still running, so the port is usually free without manual . A different does not kill another UI instance on a different port (the state file is overwritten when the new server starts). In the browser, Stop setup server (Core settings) ends the HTTP process after your session authenticates \u2014 the page will disconnect; run on the machine again to continue. Same trust as the rest of the panel: only someone with the setup token can unlock a session that can call ."},{title:"WhatsApp pairing (QR in the browser)",body:"You can link this host to WhatsApp without a terminal QR: Stop the gateway on this machine if it is running: use Stop gateway in the UI, , or stop your service. The UI checks for a live process and refuses browser pairing while it is present \u2014 two Baileys clients must not use the same directory at once. Unlock the UI with your setup token. Under Host snapshot, open WhatsApp pairing (QR) and choose Start QR pairing. On your phone: WhatsApp \u2192 Settings \u2192 Linked devices \u2192 Link a device, then scan the QR shown in the browser. If this host is already linked and you need a fresh login, enable Replace session (clears saved WhatsApp auth on disk, same idea as ) and confirm. You can still use from a shell on the host if you prefer the terminal QR. Pairing events are delivered over an authenticated same-origin event stream ( ) after you call . Traffic remains plain HTTP on the LAN \u2014 same trust model as the rest of the UI."},{title:"Security model (read this)",body:"Same trust as editing config on disk. Anyone who can change settings here could affect gateway behavior after reload/restart. LAN exposure: binding to all interfaces means anyone on your Wi\u2011Fi/Ethernet who can reach the port must not guess the token. Treat the token like a password. Not HTTPS: traffic is plain HTTP on your network segment v1. Run behind a reverse proxy with TLS only if you extend exposure beyond the LAN. Reduce exposure \u2014 loopback only (no phone from LAN). Firewall \u2014 block TCP 3789 (or your ) from the WAN side of your router. Guest Wi\u2011Fi \u2014 avoid running on networks shared with untrusted devices."},{title:"Flags",body:"Flag Meaning ---------------------------------- ----------------------------------------------------------- / Bind address (default ). / TCP port (default ). / Set or rotate the setup token (stored in )."},{title:"Environment",body:"Variable Meaning ------------------------- ------------------------------------------------------------------------------------------- Absolute path to a directory containing (advanced override for custom builds). Legacy alias for ."},{title:"Build note for contributors",body:"The UI is built into during / . If static files are missing, run the repo build from the project root so Vite runs before the esbuild CLI bundle."}],keywords:["browser","setup","ui","omnish","local-first","configuration","panel","that","edits","the","same","as","cli","and","chat","commands","use","it","when","you","want","to","finish","basics","from","phone","on","your","lan","before","touching","whatsapp/telegram","quick","start","run","gateway","whatsapp","pairing","qr","in","security","model","read","this","flags","environment","build","note","for","contributors"],relatedCommands:["/help","/configuration","/config set","/telegram","/stderr append","/logs","/gateway","/api","/start","/stop","/shutdown","/wa","/link"]},{id:"docs-guides-user-guide",path:"docs/guides/user-guide.md",title:"User Guide - omnish",summary:"Complete manual for using omnish's features.",sections:[{title:"Table of Contents",body:"Introduction Basic Shell Commands Background Jobs Interactive Sessions Terminal interactive ( ) File Transfer System Commands User Shortcuts User shortcuts vs recipes Cowork (scheduled tasks) Working with Multiple Platforms Configuration Management Chat LLM fallback (optional) Best Practices"},{title:"Introduction",body:"omnish bridges your messaging chats to your system shell with these main execution surfaces: Sync Shell: Immediate command execution (prefix: ) Background Jobs: Asynchronous commands with streaming output ( ) Cowork: Saved commands on a schedule or on demand ( ) while the gateway runs \u2014 see Cowork (scheduled tasks) Interactive Sessions: Terminal sessions via PTY ( ) Terminal REPL ( ): The same slash/ interface in your local shell\u2014see Interactive terminal (not gated by the inbox allowlist; same trust as your login session). Each chat maintains its own working directory and state, making it perfect for team workflows and personal use. The CLI REPL uses a dedicated session key and starts in your current directory when you launch ."},{title:"Basic Shell Commands",body:"Synchronous Execution Commands prefixed with execute immediately in the shell: Working Directories Each chat maintains its own working directory: Use to change directories Changes persist across commands View current directory with Command Prefix The prefix can be customized in : Without prefix, use free shell mode (see below)."},{title:"Background Jobs",body:"Start long-running commands without blocking your chat. Full detail: Background jobs. Starting Jobs Job Management Job IDs are 8-character hex strings; you can also assign a name with (or ). / / accept either form. Use (or ) to get a chat message when the job exits. Job Output and limits Logs persist under with matching . Jobs use the chat session cwd; they inherit the gateway process environment (not a prior from another shell). applies to sync commands only, not to children."},{title:"Interactive Sessions",body:'Start full terminal sessions in your chat: Starting Sessions Session Management Session Features Maximum 5 sessions per chat (configurable) One session "attached" at a time Plain text goes to attached session Output debounced and chunked ANSI codes can be preserved Session Commands Session Lifecycle'},{title:"Terminal interactive (`omnish i`)",body:"From a terminal on the same machine, run (or ) to type the same commands you would send in WhatsApp or Telegram: , , , , , etc. Trust: The inbox allowlist does not apply\u2014only local OS users who can run processes as you can start . Outbound files: Use or while is active; see Interactive terminal and Files send/receive. Media: , , \u2014 download, transcribe, and edit media (yt-dlp + ffmpeg + optional Whisper) \u2014 see media commands. Jobs: in this REPL belongs only to this process, not to the gateway\u2019s job list. Full reference: Interactive terminal."},{title:"File Transfer",body:'Sending Files Receiving Files When someone sends media files: Files are automatically downloaded Saved to organized directory structure Reply with "Saved: /path/to/file" File Location Control'},{title:"System Commands",body:"Gateway Control Notes: Aliases: and work like . is accepted as an alias for . prints command help. Service management from chat and are gated by (same trust as shell). Allowlist Management Cluster (optional, chat-driven) When is true on multiple machines linked to the same WhatsApp number, only the active host replies. Coordination flows through the chat itself \u2014 no shared file, no Syncthing. Shorthand: \u2026 and the long form \u2026 (only the exact token matches; does not). See Cluster and chat configuration. Config from chat View or change many keys without SSH (same trust as shell): Allowlists stay / , not . Full list: Cluster and chat configuration. Other Commands"},{title:"User Shortcuts",body:"Create chat-specific command aliases: Creating Shortcuts Using Shortcuts Parameterized shortcuts ( ) Put in the saved command where runtime text should go. Everything after the shortcut name on the same line is substituted in (trimmed; not shell-parsed). Shortcuts without still work as bare or only (extra words on the line are ignored). Shortcuts with require input; bare or returns a usage hint. Input length follows the same cap as tasks when is set. Managing Shortcuts Scope model: / : private to this chat. / : shared on this gateway across chats. Resolution order for or : chat shortcut first, then shared."},{title:"User shortcuts vs `/run` recipes",body:"Both features are per-chat (stored on the gateway host), but they solve different problems: Shortcuts ( , , ) recipes ( , ) --------------- ---------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Purpose One-line alias: expand to a saved message once (e.g. ). Optional substitutes chat text inline. Parameterized runner: inject your task text into a CLI via (and optional ), then start a detached app session (PTY) by default \u2014 use or when you want plain DMs to go to the agent. still attaches on start. Typical use Jump to a directory, repeat a favorite sync command, short macros, custom wrappers with varying text. Drive system agents (e.g. , , your orchestrators) from chat with a task each time. Invocation or ; with in the body: or . \u2014 task is required for named recipes. Shortcuts substitute in the expanded line only (no shell env). Recipes require the stored command to reference (or a custom ) and run in a PTY session. For system agents, multi-agent PTY, and , see System agents and . Queued recipe runs (same chat, one PTY at a time): or , then for status. You can also batch-enqueue from JSON with (file path, inline JSON, or an uploaded file with that caption); see Run queue for the exact JSON shape and limits. Multi-step runbooks: create a recipe with sequential steps using : Steps are separated by or newlines. Each step runs in order; if any step fails (non-zero exit), remaining steps are skipped. Prefix a step with to continue on failure: When you invoke a runbook ( ), it runs in the background and sends a formatted per-step summary when complete. Use to see the step listing. Useful management forms:"},{title:"Cowork (scheduled tasks)",body:"Cowork saves shell commands and runs them on a schedule or on demand while is active. Commands use the same shell, timeout, and byte limits as sync commands. Alias: . Default log directory: (override with ). Missed scheduled times catch up when the gateway returns (one oldest slot per tick). Disabled tasks reject until re-enabled. Conditional notifications: only sends a notification when the task fails. tracks ok/fail transitions and only alerts on flips (useful for frequent health checks). Full reference: Cowork feature doc (notifications, data files, cluster and WhatsApp caveats)."},{title:"Working with Multiple Platforms",body:"Multi-Platform Setup Configure for both platforms: Platform Differences Feature WhatsApp Telegram -------------- --------------- ----------------------------- Message Limit 3500 chars 4096 chars Format Plain text HTML support File Types All media types Photo, document, video, audio Authentication QR code Bot token Best Practices Use different users for different platforms Keep allowlists minimal Monitor usage across platforms Use platform-specific features appropriately"},{title:"Chat LLM fallback (optional)",body:"If is true in on the gateway host, plain messages that would otherwise show \u201CNo command matched\u201D are answered asynchronously by a subprocess you configure ( ). Replies go back to the same WhatsApp/Telegram chat (or to the terminal in ). Where to configure: the same file as the rest of omnish \u2014 default , or if set. You edit JSON on the machine where runs; API keys must be available in that process\u2019s environment if your wrapper needs them (restart the gateway after changing systemd/shell env). Full step-by-step instructions, stdin vs PTY, and environment forwarding: Chat LLM fallback."},{title:"Configuration Management",body:"Configuration File Location Default: Override with environment variable Legacy: Uses if doesn't exist Editing from chat Most tunables support (whitelist). See Cluster and chat configuration and Configuration guide. Key Configuration Options Environment Variables : Override data directory : Enable debug logging (legacy: ) : Override bot token"},{title:"Best Practices",body:`Security Minimal Allowlists: Only add trusted users No Wildcards: Explicit phone numbers only Monitor Usage: Regularly check allowlists Session Isolation: Each chat is isolated Performance Output Chunking: Large outputs automatically split Session Limits: Don't exceed max sessions Timeout Settings: Adjust for your use case Log Rotation: Monitor log file sizes Usage Patterns Use Sessions for Interactive Apps: Vim, Python REPL, etc. Use Jobs for Long Operations: Builds, data processing Use Shortcuts for Common Tasks: Reduce typing Organize with Working Directories: Keep related work together Troubleshooting "Not in allowlist": Check E.164 format (+15551234567) "Maximum sessions reached": Stop unused sessions Messages cut off: Check Connection issues: Use for debug logs`},{title:"Next Steps",body:"Configuration: Customize for your workflow Security: Learn about the security model Features: Explore advanced features Integration: Set up for your team"}],keywords:["user","guide","omnish","complete","manual","for","using","features","table","of","contents","introduction","basic","shell","commands","background","jobs","interactive","sessions","terminal","file","transfer","system","shortcuts","vs","/run","recipes","cowork","scheduled","tasks","working","with","multiple","platforms","chat","llm","fallback","optional","configuration","management","best","practices","next","steps"],relatedCommands:["/help","/docs help","/run","/bg","/cowork","/apps","/tmp","/path","/var","/log","/system","/features","/background-jobs","/bg npm"]}]};var Bn=ww;function qa(e,t){return`${e.replace(/\/$/,"")}/${t}`}function EE(e){return e.toLowerCase().replace(/[^a-z0-9\s/-]/g," ").split(/\s+/).filter(t=>t.length>1)}function PE(e,t,n){if(t.length===0)return null;let o=e.title.toLowerCase(),r=e.summary.toLowerCase(),s=new Set(e.keywords),i=0,a,l=n.toLowerCase().trim();l.length>=3&&o.includes(l)&&(i+=40);for(let c of t){o.includes(c)&&(i+=12),r.includes(c)&&(i+=4),s.has(c)&&(i+=6),e.path.toLowerCase().includes(c)&&(i+=3);for(let u of e.sections){let d=u.title.toLowerCase(),m=u.body.toLowerCase();d.includes(c)&&(i+=5,a??=u.title),m.includes(c)&&(i+=2,a??=u.title)}}return i<=0?null:{entry:e,score:i,matchedSection:a}}function za(e,t=12){let n=e.trim();if(!n)return[];let o=EE(n),r=[];for(let s of Bn.entries){let i=PE(s,o,n);i&&r.push(i)}return r.sort((s,i)=>i.score-s.score||s.entry.title.localeCompare(i.entry.title)),r.slice(0,t)}function Ka(e){let{entry:t,matchedSection:n}=e,o=n?`${t.summary.slice(0,120)} (${n})`:t.summary.slice(0,160);return{id:t.id,path:t.path,title:t.title,summary:o,relatedCommands:t.relatedCommands}}function fs(e){return Bn.entries.find(t=>t.id===e)}function Ya(e){let t=e.replace(/\\/g,"/").replace(/^\.\//,"");return Bn.entries.find(n=>n.path===t||n.id===t)}function Va(e,t=1800){let n=[];e.summary&&n.push(e.summary);for(let r of e.sections){if(n.join(`
567
568
 
568
569
  `).length>=t)break;n.push(`${r.title}
569
570
  ${r.body.slice(0,600)}`)}let o=n.join(`
570
571
 
571
- `).trim();return o.length>t&&(o=`${o.slice(0,t-1)}\u2026`),o}function Va(e){return e.relatedCommands.find(n=>/\bhelp\b/i.test(n))??e.relatedCommands[0]}function hw(){return p(["Documentation search (offline)","","/s q <topic> \u2014 find guides by keyword","/s list \u2014 repeat last search results","/s <n> \u2014 excerpt + links for result #n","/s show <n> \u2014 same as /s <n>","/s follow <n> \u2014 run primary related slash help","","Same as /search \u2026 \xB7 legacy: /docs \u2026, /help search <topic>","","Host: omnish search q <topic> (omnish docs \u2026 still works)"].join(`
572
- `))}function qu(e,t){if(e.length===0)return p(`${t}
572
+ `).trim();return o.length>t&&(o=`${o.slice(0,t-1)}\u2026`),o}function Qa(e){return e.relatedCommands.find(n=>/\bhelp\b/i.test(n))??e.relatedCommands[0]}function bw(){return p(["Documentation search (offline)","","/s q <topic> \u2014 find guides by keyword","/s list \u2014 repeat last search results","/s <n> \u2014 excerpt + links for result #n","/s show <n> \u2014 same as /s <n>","/s follow <n> \u2014 run primary related slash help","","Same as /search \u2026 \xB7 legacy: /docs \u2026, /help search <topic>","","Host: omnish search q <topic> (omnish docs \u2026 still works)"].join(`
573
+ `))}function Yu(e,t){if(e.length===0)return p(`${t}
573
574
  (no results)`);let n=[t,""];return e.forEach((o,r)=>{let s=o.relatedCommands[0],i=s?` \u2014 try ${s}`:"";n.push(`${r+1}. ${o.title}${i}`),n.push(` ${o.path}`),o.summary&&n.push(` ${o.summary.slice(0,140)}`)}),n.push("","Read: /s <n> \xB7 Run help: /s follow <n>"),p(n.join(`
574
- `))}function zu(e,t){let n=Ga(Bn.repoUrl,e.path),o=Ya(e),r=e.relatedCommands.slice(0,8),s=Va(e),a=[t!==void 0?`${t}. ${e.title}`:e.title,e.path,n,"",o,"","Try:",...r.length?r.map(l=>` ${l}`):[" /help \u2014 general commands"],"",s?`Follow: /s follow ${t??"<n>"} (runs ${s})`:"Follow: /s follow <n> when a related command is listed"].map(l=>l.startsWith("http")||l.includes("/")?$e(l):l).join(`
575
- `);return p(a)}var Ku=new Map;function gw(e,t){Ku.set(e,t)}function yw(e){return Ku.get(e)}function Yu(e,t){let n=Number.parseInt(t,10);if(!Number.isFinite(n)||n<1)return null;let o=Ku.get(e);return!o||n>o.length?null:o[n-1]}var Qa;function ww(e){Qa=e}function bw(e){let t=Number.parseInt(e,10);return!Number.isFinite(t)||t<1||!Qa||t>Qa.length?null:Qa[t-1]}function Vu(e){let t=e.trim(),n=/^search\s+([\s\S]*)$/i.exec(t);return n?`q ${n[1].trim()}`:t}async function Xa(e,t,n){let o=Vu(e);if(!o||/^help$/i.test(o))return{kind:"text",body:hw()};if(/^list\s*$/i.test(o)){let l=yw(t);return l?.length?{kind:"text",body:qu(l,"Last search")}:{kind:"text",body:p("No cached list. /s q <topic>")}}let r=/^q\s+([\s\S]+)$/i.exec(o);if(r){let l=r[1].trim();if(!l)return{kind:"text",body:p("Usage: /s q <topic>")};let u=qa(l).map(za);return gw(t,u),{kind:"text",body:qu(u,`Search: ${l}`)}}let s=/^follow\s+(\d+)\s*$/i.exec(o);if(s){let l=Yu(t,s[1]);if(!l)return{kind:"text",body:p(`No result #${s[1]}. Run /s q <topic> first.`)};let c=fs(l.id);if(!c)return{kind:"text",body:p("Entry missing from index.")};let u=Va(c);if(!u)return{kind:"text",body:p(`No related command for "${l.title}". Try /s ${s[1]} for the doc excerpt.`)};let d=u.startsWith("/")?u:`/${u}`,m=await n(d);return m?.kind==="text",m}let i=/^(?:show\s+)?(\d+)\s*$/i.exec(o);if(i){let l=Yu(t,i[1]);if(!l)return{kind:"text",body:p(`No result #${i[1]}. Run /s q <topic> first.`)};let c=fs(l.id);return c?{kind:"text",body:zu(c,Number.parseInt(i[1],10))}:{kind:"text",body:p("Entry missing from index.")}}let a=Ka(o);return a?{kind:"text",body:zu(a)}:{kind:"text",body:p("Unknown command. /s help")}}function Qu(e){let t=e.trim();if(t==="/computers"||t.startsWith("/computers "))return t.slice(10).trim();if(t==="/pcs"||t.startsWith("/pcs "))return t.slice(4).trim();let n=t.match(/^\/c(?:$|\s+(.*))/);return n?(n[1]??"").trim():null}function L(e){return{kind:"text",body:e}}function SE(e,t){return`${e}:${t}`}function vE(e,t){return`${e}:apps:${t}`}async function xE(e,t){let n=e.trim();if(n===""||/^status$/i.test(n)||/^show$/i.test(n)||/^get$/i.test(n)){let a=v();return Ap({gatewayMode:a.gatewayMode,authPresent:St(),tokenSet:!!Ie(a),allowN:a.allowFrom.length,tgAllowN:a.telegramAllowFrom.length,updateBrief:Aa(ss())})}if(/^help$/i.test(n))return X(Ll());let o=vo(n);if(!o)return zp();Is(o);let r=v(),s,i=!1;if(t?.reload){let a=await t.reload();s=a.ok?a.summary:`Reload failed: ${a.error}`}else i=!0;return Fp(r.gatewayMode,s,i)}function CE(e){let t=e.trim();if(!Ot(t))return _p();let n=fn(t),o=[];return typeof process.env.TELEGRAM_BOT_TOKEN=="string"&&process.env.TELEGRAM_BOT_TOKEN.trim()&&o.push("TELEGRAM_BOT_TOKEN is set in the environment and overrides config until unset."),n.gatewayMode==="whatsapp"&&o.push('Set gatewayMode to "telegram" or "both" for Telegram to receive messages.'),Np(o)}async function kw(e,t,n){let o=oe(t),r=bp(n);if(r!==null){let a=kp(o.cwd,r),l=Sp(a);return l.ok?(Bs(t,a),p(`cwd: ${a}`)):p(`cd: ${l.error}`)}let s=await ct(e.shell,n,{timeoutMs:e.syncTimeoutMs,maxBytes:e.syncMaxBytes,cwd:o.cwd}),i=[];return i.push(`$ ${n}`),s.stdout.trim()&&i.push(s.stdout.trimEnd()),s.stderr.trim()&&(i.push("\u2014 stderr \u2014"),i.push(s.stderr.trimEnd())),s.timedOut?i.push(`Timed out (${Math.round(e.syncTimeoutMs/1e3)}s limit).`):s.code!==0&&s.code!==null&&i.push(`Exit ${s.code}`),p(i.join(`
576
- `))}async function Sw(e,t,n,o,r,s,i,a,l,c,u,d,m){if(u)return null;let f=my(s,m,e.recipesMaxTaskChars);return f?f.ok?await yr(e,t,n,o,r,{...i,text:f.text},a,l,c,!0,d):L(p(f.error)):null}async function yr(e,t,n,o,r,s,i,a,l=null,c=!1,u){let d=s.text.trim(),m=s.peerKey,f=d.match(/^!!\s*(start|stop)\s*$/i);if(f)return f[1].toLowerCase()==="start"?(r.set(m,!0),L(Jp())):(r.set(m,!1),L(p("Free shell mode off.")));if(d.startsWith(e.commandPrefix)){let C=d.slice(e.commandPrefix.length).trim();if(!C)return L(p(`Send ${e.commandPrefix}<command> or /help`));let M=await Sw(e,t,n,o,r,m,s,i,a,l,c,u,C);return M!==null?M:L(await kw(e,m,C))}if(/^\/help\s+files$/i.test(d.trim()))return L(Fl());let h=C=>yr(e,t,n,o,r,{...s,text:C},i,a,l,c,u),g=d.match(/^\/(?:search|s)\b(?:\s+([\s\S]*))?$/i);if(g)return await Xa(g[1]??"help",m,h);let y=d.match(/^\/help\s+search\b(?:\s+([\s\S]*))?$/i);if(y){let C=(y[1]??"").trim();return await Xa(C?`q ${C}`:"help",m,h)}let k=d.match(/^\/docs\b(?:\s+([\s\S]*))?$/i);if(k)return await Xa(k[1]??"help",m,h);if(d==="/help"||d==="help")return L(X($o(e)));if(d.startsWith("/")){if(/^\/files(?:\s+help)?$/i.test(d.trim()))return L(Fl());let C=d.match(/^\/receive\b(?:\s+(\S+))?$/i);if(C){let O=(C[1]??"status").toLowerCase(),_=new Set(["here","cwd","session","dir"]),Ce=new Set(["default","global","reset"]);if(_.has(O)){Tl(m,"sessionCwd");let Ae=oe(m).cwd;return L(p(`Inbound files will save under this chat\u2019s session folder:
575
+ `))}function Vu(e,t){let n=qa(Bn.repoUrl,e.path),o=Va(e),r=e.relatedCommands.slice(0,8),s=Qa(e),a=[t!==void 0?`${t}. ${e.title}`:e.title,e.path,n,"",o,"","Try:",...r.length?r.map(l=>` ${l}`):[" /help \u2014 general commands"],"",s?`Follow: /s follow ${t??"<n>"} (runs ${s})`:"Follow: /s follow <n> when a related command is listed"].map(l=>l.startsWith("http")||l.includes("/")?$e(l):l).join(`
576
+ `);return p(a)}var Qu=new Map;function kw(e,t){Qu.set(e,t)}function Sw(e){return Qu.get(e)}function Xu(e,t){let n=Number.parseInt(t,10);if(!Number.isFinite(n)||n<1)return null;let o=Qu.get(e);return!o||n>o.length?null:o[n-1]}var Xa;function vw(e){Xa=e}function xw(e){let t=Number.parseInt(e,10);return!Number.isFinite(t)||t<1||!Xa||t>Xa.length?null:Xa[t-1]}function Zu(e){let t=e.trim(),n=/^search\s+([\s\S]*)$/i.exec(t);return n?`q ${n[1].trim()}`:t}async function Za(e,t,n){let o=Zu(e);if(!o||/^help$/i.test(o))return{kind:"text",body:bw()};if(/^list\s*$/i.test(o)){let l=Sw(t);return l?.length?{kind:"text",body:Yu(l,"Last search")}:{kind:"text",body:p("No cached list. /s q <topic>")}}let r=/^q\s+([\s\S]+)$/i.exec(o);if(r){let l=r[1].trim();if(!l)return{kind:"text",body:p("Usage: /s q <topic>")};let u=za(l).map(Ka);return kw(t,u),{kind:"text",body:Yu(u,`Search: ${l}`)}}let s=/^follow\s+(\d+)\s*$/i.exec(o);if(s){let l=Xu(t,s[1]);if(!l)return{kind:"text",body:p(`No result #${s[1]}. Run /s q <topic> first.`)};let c=fs(l.id);if(!c)return{kind:"text",body:p("Entry missing from index.")};let u=Qa(c);if(!u)return{kind:"text",body:p(`No related command for "${l.title}". Try /s ${s[1]} for the doc excerpt.`)};let d=u.startsWith("/")?u:`/${u}`,m=await n(d);return m?.kind==="text",m}let i=/^(?:show\s+)?(\d+)\s*$/i.exec(o);if(i){let l=Xu(t,i[1]);if(!l)return{kind:"text",body:p(`No result #${i[1]}. Run /s q <topic> first.`)};let c=fs(l.id);return c?{kind:"text",body:Vu(c,Number.parseInt(i[1],10))}:{kind:"text",body:p("Entry missing from index.")}}let a=Ya(o);return a?{kind:"text",body:Vu(a)}:{kind:"text",body:p("Unknown command. /s help")}}function ed(e){let t=e.trim();if(t==="/computers"||t.startsWith("/computers "))return t.slice(10).trim();if(t==="/pcs"||t.startsWith("/pcs "))return t.slice(4).trim();let n=t.match(/^\/c(?:$|\s+(.*))/);return n?(n[1]??"").trim():null}function L(e){return{kind:"text",body:e}}function $E(e,t){return`${e}:${t}`}function ME(e,t){return`${e}:apps:${t}`}async function AE(e,t){let n=e.trim();if(n===""||/^status$/i.test(n)||/^show$/i.test(n)||/^get$/i.test(n)){let a=v();return Lp({gatewayMode:a.gatewayMode,authPresent:St(),tokenSet:!!Ie(a),allowN:a.allowFrom.length,tgAllowN:a.telegramAllowFrom.length,updateBrief:Ia(ss())})}if(/^help$/i.test(n))return X(Nl());let o=vo(n);if(!o)return Vp();Is(o);let r=v(),s,i=!1;if(t?.reload){let a=await t.reload();s=a.ok?a.summary:`Reload failed: ${a.error}`}else i=!0;return Up(r.gatewayMode,s,i)}function IE(e){let t=e.trim();if(!Ot(t))return Wp();let n=fn(t),o=[];return typeof process.env.TELEGRAM_BOT_TOKEN=="string"&&process.env.TELEGRAM_BOT_TOKEN.trim()&&o.push("TELEGRAM_BOT_TOKEN is set in the environment and overrides config until unset."),n.gatewayMode==="whatsapp"&&o.push('Set gatewayMode to "telegram" or "both" for Telegram to receive messages.'),Dp(o)}async function Cw(e,t,n){let o=oe(t),r=vp(n);if(r!==null){let a=xp(o.cwd,r),l=Cp(a);return l.ok?(Bs(t,a),p(`cwd: ${a}`)):p(`cd: ${l.error}`)}let s=await ct(e.shell,n,{timeoutMs:e.syncTimeoutMs,maxBytes:e.syncMaxBytes,cwd:o.cwd}),i=[];return i.push(`$ ${n}`),s.stdout.trim()&&i.push(s.stdout.trimEnd()),s.stderr.trim()&&(i.push("\u2014 stderr \u2014"),i.push(s.stderr.trimEnd())),s.timedOut?i.push(`Timed out (${Math.round(e.syncTimeoutMs/1e3)}s limit).`):s.code!==0&&s.code!==null&&i.push(`Exit ${s.code}`),p(i.join(`
577
+ `))}async function Rw(e,t,n,o,r,s,i,a,l,c,u,d,m){if(u)return null;let f=yy(s,m,e.recipesMaxTaskChars);return f?f.ok?await yr(e,t,n,o,r,{...i,text:f.text},a,l,c,!0,d):L(p(f.error)):null}async function yr(e,t,n,o,r,s,i,a,l=null,c=!1,u){let d=s.text.trim(),m=s.peerKey,f=d.match(/^!!\s*(start|stop)\s*$/i);if(f)return f[1].toLowerCase()==="start"?(r.set(m,!0),L(zp())):(r.set(m,!1),L(p("Free shell mode off.")));if(d.startsWith(e.commandPrefix)){let C=d.slice(e.commandPrefix.length).trim();if(!C)return L(p(`Send ${e.commandPrefix}<command> or /help`));let M=await Rw(e,t,n,o,r,m,s,i,a,l,c,u,C);return M!==null?M:L(await Cw(e,m,C))}if(/^\/help\s+files$/i.test(d.trim()))return L(Dl());let h=C=>yr(e,t,n,o,r,{...s,text:C},i,a,l,c,u),g=d.match(/^\/(?:search|s)\b(?:\s+([\s\S]*))?$/i);if(g)return await Za(g[1]??"help",m,h);let y=d.match(/^\/help\s+search\b(?:\s+([\s\S]*))?$/i);if(y){let C=(y[1]??"").trim();return await Za(C?`q ${C}`:"help",m,h)}let b=d.match(/^\/docs\b(?:\s+([\s\S]*))?$/i);if(b)return await Za(b[1]??"help",m,h);if(d==="/help"||d==="help")return L(X($o(e)));if(d.startsWith("/")){if(/^\/files(?:\s+help)?$/i.test(d.trim()))return L(Dl());let C=d.match(/^\/receive\b(?:\s+(\S+))?$/i);if(C){let O=(C[1]??"status").toLowerCase(),_=new Set(["here","cwd","session","dir"]),Ce=new Set(["default","global","reset"]);if(_.has(O)){El(m,"sessionCwd");let Ae=oe(m).cwd;return L(p(`Inbound files will save under this chat\u2019s session folder:
577
578
  ${Ae}
578
579
  (layout: \u2026/<peer>/<date>/<file> under that root).
579
580
 
580
- Change folder with ${e.commandPrefix}cd \u2026 Send /receive default to use the server config again.`))}return Ce.has(O)?(Tl(m,"default"),L(p("Per-chat inbound folder cleared. Uploads now follow fileReceiveRootMode in config.json (/files)."))):L(O==="help"?Dl():O==="status"?Bp(e,m):Dl())}if(d==="/reload"||d==="/restart"){if(!a?.reload)return L(p("Reload is only available while the omnish gateway (omnish run) is running."));let O=await a.reload();return L(p(O.ok?O.summary:`Reload failed: ${O.error}`))}if(d==="/updates"||d.startsWith("/updates ")){let O=d.slice(8).trim().toLowerCase();if(O==="cached"||O==="last"){let Ce=ss();return L(Ce?Mr(Ce):p("No update snapshot yet. Send /updates (live check) once, or enable updateCheckEnabled and wait for the scheduled check."))}let _=await is(gt(),v());return L(Mr(_))}let M=d.match(/^\/security(?:\s+(\S+))?\s*$/i);if(M){let O=(M[1]??"").toLowerCase(),_=v(),Ce=tn(_);return L(O==="help"||O==="?"?X(sm()):O==="summary"||O==="brief"?p(Ep(Ce,"Send /security for the full report.")):O==="tips"?X(rm()):O===""||O==="full"||O==="report"?om(Ce):p("Unknown /security subcommand. Try /security, /security summary, /security tips, or /security help"))}let R=d.match(/^\/(gateway|gw|mode)\b(?:\s+(.*))?$/i);if(R){let O=(R[2]??"").trim();return L(await xE(O,a))}if(d==="/config"||d.startsWith("/config ")){let O=d.slice(7).trim();return L(await um(O,a))}let N=ky(d);if(N!==null)return L(await Sy(e,N));let A=xy(d);if(A!==null)return await Py(e,A,m,t,u);let q=Cy(d);if(q!==null)return await $y(e,q,m,t,u);let ee=vy(d);if(ee!==null)return await pr(e,ee,m,t,u);let ce=Ry(d);if(ce!==null)return await Lu(e,ce,m,t,u);let Pe=Ty(d);if(Pe!==null)return await My(e,Pe,m,t,u);let Ke=Ey(d);if(Ke!==null)return await Ay(e,Ke,m,t,u);let F=Qu(d);if(F!==null){let O=Rm(e,F,l);return O===null?null:L(O)}let ie=d.match(/^\/(send|file)\b(?:\s+([\s\S]+))?$/i);if(ie){let O=(ie[2]??"").trim();if(!O)return L(_l());let _=Oa(O);if(!_)return L(_l());let Ce=oe(m).cwd,Ae=await as(Ce,_.selectorPart);if(Ae.length===0)return L(p(`No files matched: ${_.selectorPart}`));let nt=await ls(Ae);if(!nt.ok)return L(p(nt.error));let yo=oo(e),Kt=[];for(let xb of Ae){let wo=Ct(xb,yo);if("error"in wo)return L(p(wo.error));Kt.push({absPath:wo.absPath,category:wo.category,mimetype:wo.mimetype,displayName:wo.displayName,caption:_.caption})}return Kt.length===1?{kind:"file",spec:Kt[0]}:{kind:"files",specs:Kt}}if(d==="/allowlist"){let O=v();return L(Lp([{label:"allowFrom (WhatsApp)",items:O.allowFrom},{label:"telegramAllowFrom",items:O.telegramAllowFrom}]))}let B=d.match(/^\/allow\b\s+(.+)$/i);if(B)try{let O=Ms(B[1].trim());return L(Nl(O))}catch(O){return L(p(String(O)))}if(/^\/allow\b\s*$/i.test(d))return L(Dp());let I=d.match(/^\/deny\b\s+(.+)$/i);if(I)try{let O=As(I[1].trim());return L(Nl(O))}catch(O){return L(p(String(O)))}if(/^\/deny\b\s*$/i.test(d))return L(Wp());let z=d.match(/^\/(whatsapp|wa|telegram|tg)\b(?:\s+(.*))?$/i);if(z){let O=z[1].toLowerCase(),_=(z[2]??"").trim(),Ce=O==="whatsapp"||O==="wa";if(O==="telegram"||O==="tg"){let nt=_.match(/^token\s+(\S+)\s*$/i);return nt?L(CE(nt[1]??"")):_===""||/^help$/i.test(_)?L(X(Op(v()))):L(Hp())}if(Ce)return _===""||/^help$/i.test(_)?L(X(Ip(e))):L(jp())}let G=d.match(/^\/(board|jobs)\b(?:\s+([\s\S]*))?$/i);if(G){let O=(G[2]??"").trim();return L(await nw(O,m,e,{sendToPeer:u?.sendToPeer}))}let Y=d.match(/^\/(cowork|cw)\b(?:\s+([\s\S]*))?$/i);if(Y){let O=(Y[2]??"").trim();return L(await sw(O,m,e))}let ae=d.match(/^\/watch\b(?:\s+([\s\S]*))?$/i);if(ae){let O=(ae[1]??"").trim(),_=pw(O,m);return _.replies.length===1?L(_.replies[0]):{kind:"texts",bodies:_.replies}}if(d==="/apps"||d.startsWith("/apps "))return L(await ME(d,m,e,i,o));if(d==="/agent"||d.startsWith("/agent ")){let O=d.slice(6).trim().toLowerCase();if(!O||O==="help")return L(p(["Agent daemon commands","/agent status","/agent stop","/agent reset"].join(`
581
- `)));if(O==="status")return L(p(u?.getAgentDaemonStatus?.()??"Agent daemon hooks not configured."));if(O==="stop"){let _=await u?.stopAgentDaemon?.(m)??!1;return L(p(_?"Agent daemon stopped.":"No running agent daemon."))}if(O==="reset"){let _=await u?.resetAgentDaemon?.(m)??!1;return L(p(_?"Agent daemon reset.":"No running agent daemon."))}return L(p('Unknown /agent command. Use "/agent help".'))}let ne=RE(d);if(ne!==null)return L(await PE(ne,m,e,i,s.mediaSavedPath,u));if(d.startsWith("/bg")){let O=d.slice(3).trim();if(!O)return L(Up());let _=dg(O);if("error"in _)return L(p(_.error==="empty"?"Usage: /bg <command> or /bg -n <name> <command>.":_.error));let Ce=oe(m).cwd,{id:Ae,meta:nt}=t.spawnJob(e.shell,_.cmd,{cwd:Ce,name:_.name,notifyPeerKey:_.notify?m:null}),yo=nt.name?`${Ae} (${nt.name})`:Ae,Kt=_.notify?`
581
+ Change folder with ${e.commandPrefix}cd \u2026 Send /receive default to use the server config again.`))}return Ce.has(O)?(El(m,"default"),L(p("Per-chat inbound folder cleared. Uploads now follow fileReceiveRootMode in config.json (/files)."))):L(O==="help"?Wl():O==="status"?Jp(e,m):Wl())}if(d==="/reload"||d==="/restart"){if(!a?.reload)return L(p("Reload is only available while the omnish gateway (omnish run) is running."));let O=await a.reload();return L(p(O.ok?O.summary:`Reload failed: ${O.error}`))}if(d==="/updates"||d.startsWith("/updates ")){let O=d.slice(8).trim().toLowerCase();if(O==="cached"||O==="last"){let Ce=ss();return L(Ce?Mr(Ce):p("No update snapshot yet. Send /updates (live check) once, or enable updateCheckEnabled and wait for the scheduled check."))}let _=await is(gt(),v());return L(Mr(_))}let M=d.match(/^\/security(?:\s+(\S+))?\s*$/i);if(M){let O=(M[1]??"").toLowerCase(),_=v(),Ce=tn(_);return L(O==="help"||O==="?"?X(lm()):O==="summary"||O==="brief"?p(Mp(Ce,"Send /security for the full report.")):O==="tips"?X(am()):O===""||O==="full"||O==="report"?im(Ce):p("Unknown /security subcommand. Try /security, /security summary, /security tips, or /security help"))}let R=d.match(/^\/(gateway|gw|mode)\b(?:\s+(.*))?$/i);if(R){let O=(R[2]??"").trim();return L(await AE(O,a))}if(d==="/config"||d.startsWith("/config ")){let O=d.slice(7).trim();return L(await mm(O,a))}let N=Cy(d);if(N!==null)return L(await Ry(e,N));let A=Ey(d);if(A!==null)return await Iy(e,A,m,t,u);let q=Py(d);if(q!==null)return await Oy(e,q,m,t,u);let ee=Ty(d);if(ee!==null)return await pr(e,ee,m,t,u);let ce=$y(d);if(ce!==null)return await Fu(e,ce,m,t,u);let Pe=My(d);if(Pe!==null)return await Ly(e,Pe,m,t,u);let Ke=Ay(d);if(Ke!==null)return await Ny(e,Ke,m,t,u);let F=ed(d);if(F!==null){let O=Pm(e,F,l);return O===null?null:L(O)}let ie=d.match(/^\/(send|file)\b(?:\s+([\s\S]+))?$/i);if(ie){let O=(ie[2]??"").trim();if(!O)return L(Fl());let _=La(O);if(!_)return L(Fl());let Ce=oe(m).cwd,Ae=await as(Ce,_.selectorPart);if(Ae.length===0)return L(p(`No files matched: ${_.selectorPart}`));let nt=await ls(Ae);if(!nt.ok)return L(p(nt.error));let yo=oo(e),Kt=[];for(let Eb of Ae){let wo=Ct(Eb,yo);if("error"in wo)return L(p(wo.error));Kt.push({absPath:wo.absPath,category:wo.category,mimetype:wo.mimetype,displayName:wo.displayName,caption:_.caption})}return Kt.length===1?{kind:"file",spec:Kt[0]}:{kind:"files",specs:Kt}}if(d==="/allowlist"){let O=v();return L(Fp([{label:"allowFrom (WhatsApp)",items:O.allowFrom},{label:"telegramAllowFrom",items:O.telegramAllowFrom}]))}let B=d.match(/^\/allow\b\s+(.+)$/i);if(B)try{let O=Ms(B[1].trim());return L(_l(O))}catch(O){return L(p(String(O)))}if(/^\/allow\b\s*$/i.test(d))return L(Bp());let I=d.match(/^\/deny\b\s+(.+)$/i);if(I)try{let O=As(I[1].trim());return L(_l(O))}catch(O){return L(p(String(O)))}if(/^\/deny\b\s*$/i.test(d))return L(jp());let z=d.match(/^\/(whatsapp|wa|telegram|tg)\b(?:\s+(.*))?$/i);if(z){let O=z[1].toLowerCase(),_=(z[2]??"").trim(),Ce=O==="whatsapp"||O==="wa";if(O==="telegram"||O==="tg"){let nt=_.match(/^token\s+(\S+)\s*$/i);return nt?L(IE(nt[1]??"")):_===""||/^help$/i.test(_)?L(X(_p(v()))):L(qp())}if(Ce)return _===""||/^help$/i.test(_)?L(X(Np(e))):L(Gp())}let G=d.match(/^\/(board|jobs)\b(?:\s+([\s\S]*))?$/i);if(G){let O=(G[2]??"").trim();return L(await iw(O,m,e,{sendToPeer:u?.sendToPeer}))}let Y=d.match(/^\/(cowork|cw)\b(?:\s+([\s\S]*))?$/i);if(Y){let O=(Y[2]??"").trim();return L(await cw(O,m,e))}let ae=d.match(/^\/watch\b(?:\s+([\s\S]*))?$/i);if(ae){let O=(ae[1]??"").trim(),_=gw(O,m);return _.replies.length===1?L(_.replies[0]):{kind:"texts",bodies:_.replies}}if(d==="/apps"||d.startsWith("/apps "))return L(await DE(d,m,e,i,o));if(d==="/agent"||d.startsWith("/agent ")){let O=d.slice(6).trim().toLowerCase();if(!O||O==="help")return L(p(["Agent daemon commands","/agent status","/agent stop","/agent reset"].join(`
582
+ `)));if(O==="status")return L(p(u?.getAgentDaemonStatus?.()??"Agent daemon hooks not configured."));if(O==="stop"){let _=await u?.stopAgentDaemon?.(m)??!1;return L(p(_?"Agent daemon stopped.":"No running agent daemon."))}if(O==="reset"){let _=await u?.resetAgentDaemon?.(m)??!1;return L(p(_?"Agent daemon reset.":"No running agent daemon."))}return L(p('Unknown /agent command. Use "/agent help".'))}let ne=OE(d);if(ne!==null)return L(await _E(ne,m,e,i,s.mediaSavedPath,u));if(d.startsWith("/bg")){let O=d.slice(3).trim();if(!O)return L(Hp());let _=hg(O);if("error"in _)return L(p(_.error==="empty"?"Usage: /bg <command> or /bg -n <name> <command>.":_.error));let Ce=oe(m).cwd,{id:Ae,meta:nt}=t.spawnJob(e.shell,_.cmd,{cwd:Ce,name:_.name,notifyPeerKey:_.notify?m:null}),yo=nt.name?`${Ae} (${nt.name})`:Ae,Kt=_.notify?`
582
583
  Notify on completion: on`:"";return L(p(`Job ${yo} started.
583
584
  [cwd: ${Ce}]
584
585
  /log ${nt.name??Ae}
585
- /tail ${nt.name??Ae}${Kt}`))}if(d==="/tunnels")return L(await Gu("list",e,mo()));let Ye=d.match(/^\/tunnel\b(?:\s+([\s\S]*))?$/i);if(Ye){let O=(Ye[1]??"").trim();return L(await Gu(O||"help",e,mo()))}if(d==="/jobs"){let O=t.list().slice(0,20);return O.length===0?L(p("(no jobs yet)")):L(p(O.map(_=>{let Ce=_.finishedAt&&_.startedAt?`${((Date.parse(_.finishedAt)-Date.parse(_.startedAt))/1e3).toFixed(1)}s`:"\u2026";return`${_.name?`${_.id} ${_.name}`:_.id} ${_.status} exit=${_.exitCode??"?"} ${Ce}
586
+ /tail ${nt.name??Ae}${Kt}`))}if(d==="/tunnels")return L(await Ku("list",e,mo()));let Ye=d.match(/^\/tunnel\b(?:\s+([\s\S]*))?$/i);if(Ye){let O=(Ye[1]??"").trim();return L(await Ku(O||"help",e,mo()))}if(d==="/jobs"){let O=t.list().slice(0,20);return O.length===0?L(p("(no jobs yet)")):L(p(O.map(_=>{let Ce=_.finishedAt&&_.startedAt?`${((Date.parse(_.finishedAt)-Date.parse(_.startedAt))/1e3).toFixed(1)}s`:"\u2026";return`${_.name?`${_.id} ${_.name}`:_.id} ${_.status} exit=${_.exitCode??"?"} ${Ce}
586
587
  ${_.cmd.slice(0,120)}${_.cmd.length>120?"\u2026":""}`}).join(`
587
588
 
588
- `)))}let we=d.match(/^\/log\s+(\S+)(?:\s+(\d+))?\s*$/i);if(we){let O=we[1],_=t.resolveJobRef(O);if(!_.ok)return L(p(_.error));let Ce=_.id,Ae=we[2]?Number.parseInt(we[2],10):e.jobLogTailLines,nt=Number.isFinite(Ae)&&Ae>0?Math.min(Ae,500):e.jobLogTailLines;return L(p(t.tailLog(Ce,nt)))}let re=d.match(/^\/tail\s+(\S+)\s*$/i);if(re){let O=re[1],_=t.resolveJobRef(O);if(!_.ok)return L(p(_.error));let Ce=_.id,Ae=SE(m,Ce),nt=n.get(Ae)??0,{text:yo,nextOffset:Kt}=t.readSince(Ce,nt);return n.set(Ae,Kt),L(yo?p(yo.trimEnd()||"(no new output)"):p(`(no new output; offset ${Kt})`))}let De=d.match(/^\/kill\s+(\S+)\s*$/i);if(De){let O=De[1],_=t.resolveJobRef(O);return _.ok?L(p(t.kill(_.id))):L(p(_.error))}let tt=d.match(/^\/(shortcut|shortcuts|alias|aliases)\b(?:\s+([\s\S]*))?$/i);if(tt){let O=tt[1].toLowerCase(),_=(tt[2]??"").trim();return O==="shortcuts"&&!_?_="list":((O==="alias"||O==="aliases")&&!_||O==="shortcut"&&!_)&&(_="help"),L(await $E(_,m,e))}if(!c){let O=d.trim().replace(/^\//,""),_=await Sw(e,t,n,o,r,m,s,i,a,l,c,u,O);if(_!==null)return _}return L(Gp(e))}let b=d.match(/^>(\S+)\s*(.*)$/s);if(b){let C=b[1],M=b[2]??"",R=await i.writeNamedLine(m,C,M);return R?L(p(R)):null}if(await i.writeFocusedLine(m,d))return null;let x=await Iy(e,d,m,t,u);return x!==null?x:r.get(m)&&d?L(await kw(e,m,d)):e.chatAgentEnabled===!0&&(e.chatAgentCommand??"").trim().length>0&&u?.onPlainTextAgentDaemon?(u.onPlainTextAgentDaemon(m,d),null):e.chatLlmFallbackEnabled&&e.chatLlmShellCommand.trim().length>0&&u?.onPlainTextLlmFallback?(u.onPlainTextLlmFallback(m,d),null):L(qp(e,d))}function RE(e){return e==="/run"||e.startsWith("/run ")?e.slice(4).trim():e==="/r"||e.startsWith("/r ")?e.slice(2).trim():null}function TE(e){let t=e.trim();if(!t)return null;let n=!1,o=null,r=!0;for(;r;){r=!1;let s=t.trim(),i=s.match(/^(?:--queue|-q)\s+([\s\S]+)$/i);if(i){n=!0,t=i[1].trim(),r=!0;continue}if(/^(?:--attach|-a)(?:\s+|$)/i.test(s)){o=!0,t=s.replace(/^(?:--attach|-a)\s*/i,"").trim(),r=!0;continue}if(/^(?:--detach|-d)(?:\s+|$)/i.test(s)){o=!1,t=s.replace(/^(?:--detach|-d)\s*/i,"").trim(),r=!0;continue}}return t?{task:t,queued:n,attach:o}:null}function EE(e){let n=e.trim().match(/^(\S+)\s+([\s\S]+)$/);if(!n)return null;let o=TE(n[2]);return o?{recipe:n[1],task:o.task,queued:o.queued,attach:o.attach}:null}async function PE(e,t,n,o,r,s){let i=e.trim();if(!i||/^help$/i.test(i))return X(Xp());let a=/^online\b([\s\S]*)$/i.exec(i);if(a)return Ky((a[1]??"").trim(),t,n);let l=/^(\S+)\s+publish\b([\s\S]*)$/i.exec(i);if(l){let k=Yy(t,n,l[1],l[2]??"");if(!k.ok)return p(k.error);let b=await gr(k.body);return p(b.ok?b.message:b.error)}let c=/^list\b([\s\S]*)$/i.exec(i);if(c){let k=(c[1]??"").trim(),{filter:b,bad:x}=If(k);if(x)return p(`Unknown /run list suffix: "${x}". Use: list | list --chat | list -p | list --global | list -g`);if(b==="merged")return Zp(_f(t,n));let C=Lf(t,n,b);return nm(C,b)}let u=/^show\b([\s\S]*)$/i.exec(i);if(u){let k=(u[1]??"").trim(),{mode:b,remainder:x}=Af(k),C=/^(\S+)\s*$/i.exec(x);if(!C?.[1])return p("Usage: /run show <name> \u2014 or show --global|-g|--chat|-p <name> (-g shared, -p private)");let M=C[1];if(b==="resolved"){let ce=ze(t,n,M);return ce?Bl(ce):jl(M)}let R=b==="global"?"global":"chat",N=an(R,t,n,M);if(N)return Bl(N,R==="global"?"From gateway-shared recipes (--global).":"From this chat only (--chat).");let A=R==="chat"&&an("global",t,n,M)?`
589
+ `)))}let be=d.match(/^\/log\s+(\S+)(?:\s+(\d+))?\s*$/i);if(be){let O=be[1],_=t.resolveJobRef(O);if(!_.ok)return L(p(_.error));let Ce=_.id,Ae=be[2]?Number.parseInt(be[2],10):e.jobLogTailLines,nt=Number.isFinite(Ae)&&Ae>0?Math.min(Ae,500):e.jobLogTailLines;return L(p(t.tailLog(Ce,nt)))}let re=d.match(/^\/tail\s+(\S+)\s*$/i);if(re){let O=re[1],_=t.resolveJobRef(O);if(!_.ok)return L(p(_.error));let Ce=_.id,Ae=$E(m,Ce),nt=n.get(Ae)??0,{text:yo,nextOffset:Kt}=t.readSince(Ce,nt);return n.set(Ae,Kt),L(yo?p(yo.trimEnd()||"(no new output)"):p(`(no new output; offset ${Kt})`))}let De=d.match(/^\/kill\s+(\S+)\s*$/i);if(De){let O=De[1],_=t.resolveJobRef(O);return _.ok?L(p(t.kill(_.id))):L(p(_.error))}let tt=d.match(/^\/(shortcut|shortcuts|alias|aliases)\b(?:\s+([\s\S]*))?$/i);if(tt){let O=tt[1].toLowerCase(),_=(tt[2]??"").trim();return O==="shortcuts"&&!_?_="list":((O==="alias"||O==="aliases")&&!_||O==="shortcut"&&!_)&&(_="help"),L(await FE(_,m,e))}if(!c){let O=d.trim().replace(/^\//,""),_=await Rw(e,t,n,o,r,m,s,i,a,l,c,u,O);if(_!==null)return _}return L(Kp(e))}let k=d.match(/^>(\S+)\s*(.*)$/s);if(k){let C=k[1],M=k[2]??"",R=await i.writeNamedLine(m,C,M);return R?L(p(R)):null}if(await i.writeFocusedLine(m,d))return null;let x=await _y(e,d,m,t,u);return x!==null?x:r.get(m)&&d?L(await Cw(e,m,d)):e.chatAgentEnabled===!0&&(e.chatAgentCommand??"").trim().length>0&&u?.onPlainTextAgentDaemon?(u.onPlainTextAgentDaemon(m,d),null):e.chatLlmFallbackEnabled&&e.chatLlmShellCommand.trim().length>0&&u?.onPlainTextLlmFallback?(u.onPlainTextLlmFallback(m,d),null):L(Yp(e,d))}function OE(e){return e==="/run"||e.startsWith("/run ")?e.slice(4).trim():e==="/r"||e.startsWith("/r ")?e.slice(2).trim():null}function LE(e){let t=e.trim();if(!t)return null;let n=!1,o=null,r=!0;for(;r;){r=!1;let s=t.trim(),i=s.match(/^(?:--queue|-q)\s+([\s\S]+)$/i);if(i){n=!0,t=i[1].trim(),r=!0;continue}if(/^(?:--attach|-a)(?:\s+|$)/i.test(s)){o=!0,t=s.replace(/^(?:--attach|-a)\s*/i,"").trim(),r=!0;continue}if(/^(?:--detach|-d)(?:\s+|$)/i.test(s)){o=!1,t=s.replace(/^(?:--detach|-d)\s*/i,"").trim(),r=!0;continue}}return t?{task:t,queued:n,attach:o}:null}function NE(e){let n=e.trim().match(/^(\S+)\s+([\s\S]+)$/);if(!n)return null;let o=LE(n[2]);return o?{recipe:n[1],task:o.task,queued:o.queued,attach:o.attach}:null}async function _E(e,t,n,o,r,s){let i=e.trim();if(!i||/^help$/i.test(i))return X(tm());let a=/^online\b([\s\S]*)$/i.exec(i);if(a)return Xy((a[1]??"").trim(),t,n);let l=/^(\S+)\s+publish\b([\s\S]*)$/i.exec(i);if(l){let b=Zy(t,n,l[1],l[2]??"");if(!b.ok)return p(b.error);let k=await gr(b.body);return p(k.ok?k.message:k.error)}let c=/^list\b([\s\S]*)$/i.exec(i);if(c){let b=(c[1]??"").trim(),{filter:k,bad:x}=Nf(b);if(x)return p(`Unknown /run list suffix: "${x}". Use: list | list --chat | list -p | list --global | list -g`);if(k==="merged")return nm(Wf(t,n));let C=Ff(t,n,k);return sm(C,k)}let u=/^show\b([\s\S]*)$/i.exec(i);if(u){let b=(u[1]??"").trim(),{mode:k,remainder:x}=Lf(b),C=/^(\S+)\s*$/i.exec(x);if(!C?.[1])return p("Usage: /run show <name> \u2014 or show --global|-g|--chat|-p <name> (-g shared, -p private)");let M=C[1];if(k==="resolved"){let ce=ze(t,n,M);return ce?jl(ce):Hl(M)}let R=k==="global"?"global":"chat",N=an(R,t,n,M);if(N)return jl(N,R==="global"?"From gateway-shared recipes (--global).":"From this chat only (--chat).");let A=R==="chat"&&an("global",t,n,M)?`
589
590
  (Gateway-shared recipe exists: /run show --global ${M})`:"",q=R==="global"&&an("chat",t,n,M)?`
590
- (This chat stores an override: /run show --chat ${M})`:"",ee=R==="global"?`Unknown recipe "${M}" in gateway-shared storage.`:`Unknown recipe "${M}" in this chat storage.`;return p(`${ee}${A}${q}`)}let d=/^add\b([\s\S]*)$/i.exec(i);if(d){let{scope:k,remainder:b}=Ec((d[1]??"").trim()),x=b.match(/^(\S+)\s+([\s\S]+)$/);if(!x)return p(k==="global"?'Usage: /run add --global <name> <command\u2026> [--template "\u2026"] \u2014 cmd must reference "$OMNISH_TASK"':"Usage: /run add [--global|-g|--chat|-p] <name> <command\u2026> \u2014 see /run help");try{let C=Pc(x[2],n.recipesMacroDefaultCommand);Jo(t,x[1],C,k);let M=Bt(x[1]),R=M.ok?M.normalized:x[1].toLowerCase(),N=an(k,t,n,R)??ze(t,n,R);return N?Ir(R,N,k):p("Recipe save failed.")}catch(C){return p(String(C))}}let m=/^set\b([\s\S]*)$/i.exec(i);if(m){let{scope:k,remainder:b,explicit:x}=Tc((m[1]??"").trim()),C=Of(b);if(C){if(x)return p("Cannot combine a leading scope flag (-g, --global, --chat, -p) with a trailing scope flag on the same line.");let N=Bt(C.name);if(!N.ok)return p(N.error);let A=$c(t,N.normalized,C.target,n);if(!A.ok)return p(A.error);if(A.kind==="noop")return p(A.message);let q=an(A.target,t,n,N.normalized)??ze(t,n,N.normalized);return q?Ir(N.normalized,q,A.target):p("Recipe scope update failed.")}let M=b.match(/^(\S+)\s*$/);if(M?.[1]&&x){let N=Bt(M[1]);if(!N.ok)return p(N.error);let A=$c(t,N.normalized,k,n);if(!A.ok)return p(A.error);if(A.kind==="noop")return p(A.message);let q=an(A.target,t,n,N.normalized)??ze(t,n,N.normalized);return q?Ir(N.normalized,q,A.target):p("Recipe scope update failed.")}let R=b.match(/^(\S+)\s+([\s\S]+)$/);if(!R)return p(k==="global"?'Usage: /run set --global <name> <command\u2026> [--template "\u2026"] \u2014 or scope-only: /run set --global <name> | /run set <name> -g':"Usage: /run set [--global|-g|--chat|-p] <name> <command\u2026> \u2014 or move scope without changing the body: /run set -g <name> | /run set <name> -p \u2014 see /run help");try{let N=Pc(R[2],n.recipesMacroDefaultCommand);Jo(t,R[1],N,k);let A=Bt(R[1]),q=A.ok?A.normalized:R[1].toLowerCase(),ee=an(k,t,n,q)??ze(t,n,q);return ee?Ir(q,ee,k):p("Recipe save failed.")}catch(N){return p(String(N))}}let f=/^(?:remove|rm|del)\b([\s\S]*)$/i.exec(i);if(f){let{scope:k,remainder:b}=Ec((f[1]??"").trim()),x=b.match(/^(\S+)\s*$/);if(!x?.[1])return p(k==="global"?"Usage: /run remove --global <name> (aliases: rm, del)":"Usage: /run remove [--global|-g|--chat|-p] <name>");let C=x[1];return Nf(t,C,k)?em(C,k):k==="chat"&&an("global",t,n,C)?p(`No recipe "${C}" in this chat. There is a gateway-shared recipe with that name \u2014 remove it with:
591
- /run remove --global ${C}`):tm(C,k)}let h=/^queue\s+load\b([\s\S]*)$/i.exec(i);if(h){let k=(h[1]??"").trim(),b=gy(n),x=null,C=/^json(?:\s+([\s\S]+))?$/i.exec(k);if(C){let A=(C[1]??"").trim();if(!A)return p('Usage: /run queue load json [{"recipe":"\u2026","task":"\u2026"}, \u2026] \u2014 or { "tasks": [ \u2026 ] }');x=A}else if(k.length>0){let A=k;(A.startsWith('"')&&A.endsWith('"')||A.startsWith("'")&&A.endsWith("'"))&&(A=A.slice(1,-1));let q=oe(t).cwd,ee=await as(q,A);if(ee.length===0)return p(`No files matched: ${A}`);if(ee.length>1)return p("Queue load: specify a single JSON file.");let ce=await ls(ee);if(!ce.ok)return p(ce.error);let Pe=Ou(ee[0],b);if(!Pe.ok)return p(Pe.error);x=Pe.text}else if(r){let A=Ou(r,b);if(!A.ok)return p(A.error);x=A.text}else return p("Usage: /run queue load <file.json> \u2014 or /run queue load json [\u2026] \u2014 or attach a file with caption /run queue load");let M=yy(x);if(!M.ok)return p(M.error);let R=wy(t,n,M.jobs);if(!R.ok)return p(R.error);let N=[];for(let A of R.items)N.push(o.enqueueQueuedRun(t,A,n));return p(N.join(`
592
- `))}if(/^queue$/i.test(i))return p(o.runQueueStatus(t));if(/^queue\s+resume\s*$/i.test(i))return p(o.resumeRunQueue(t,n));let g=EE(i);if(g){let{recipe:k,task:b,queued:x,attach:C}=g,M=Ac(C,n),R=ze(t,n,k);if(!R)return jl(k);if(R.steps&&R.steps.length>0){let Pe=oe(t).cwd,Ke=R.steps.length,F=R.steps;if(s?.sendToPeer){let ie=s.sendToPeer;(async()=>{let B=[],I=!1;for(let G=0;G<F.length;G++){let Y=F[G];if(I){B.push({index:G,label:Y.label??`step ${G+1}`,cmd:Y.cmd,exitCode:null,timedOut:!1,skipped:!0,output:""});continue}n.progressUpdates&&await ie(t,`Step ${G+1}/${Ke}: ${Y.label??`step ${G+1}`}\u2026`).catch(()=>{});let ae=await ct(n.shell,Y.cmd,{timeoutMs:n.syncTimeoutMs,maxBytes:n.syncMaxBytes,cwd:Pe}),ne=[ae.stdout,ae.stderr].filter(Boolean).join(`
593
- `).trim(),Ye=ae.code===0&&!ae.timedOut,we={index:G,label:Y.label??`step ${G+1}`,cmd:Y.cmd,exitCode:ae.code,timedOut:ae.timedOut,skipped:!1,output:ne};B.push(we),n.progressUpdates&&await ie(t,Ff(k,we,G,Ke)).catch(()=>{}),!Ye&&!Y.continueOnFail&&(I=!0)}let z=Df(k,B);await ie(t,z)})().catch(()=>{})}return p(`Runbook "${k}" started (${Ke} steps). Results will be sent when complete.`)}let N=R.taskEnv??"OMNISH_TASK";if(!Ho(R.command,N))return p(`Recipe "${k}" command must reference "$${N}".`);let A=jo(b,n.recipesMaxTaskChars);if(!A.ok)return p(A.error);let q=R.promptTemplate?vi(R.promptTemplate,N,A.task):A.task,ee={[N]:q,...Xr(t,n,{mutedByDefault:!!M.mute})};if(x){let Pe={command:R.command,extraEnv:ee,recipeLabel:k,startOptions:M};return p(o.enqueueQueuedRun(t,Pe,n))}let ce=xi(k);return p(o.start(t,ce,R.command,n,ee,M))}let y=i.match(/^(\S+)$/);if(y){let k=y[1],b=k.toLowerCase();return b==="add"||b==="set"?p('Usage: /run add <name> cmd [--template "\u2026"] \u2014 cmd must include "$OMNISH_TASK"'):b==="show"?p("Usage: /run show <name> \u2014 optional show --global|-g|--chat|-p <name>"):b==="remove"||b==="rm"||b==="del"?p("Usage: /run remove [--global|-g|--chat|-p] <name>"):ze(t,n,b)?p(`Usage: /run ${k} <task text\u2026> \u2014 replaces <<<OMNISH_TASK>>> / $OMNISH_TASK in stored templates`):p(`Unknown recipe "${k}". /run list`)}return p("/run: could not parse. /run help")}async function $E(e,t,n){let o=e.trim();if(!o||/^help$/i.test(o))return X(Wl());let r=/^online\b([\s\S]*)$/i.exec(o);if(r)return hr((r[1]??"").trim(),t,n,zy);let s=/^(\S+)\s+publish\b([\s\S]*)$/i.exec(o);if(s){let d=Vy(t,s[1],s[2]??"");if(!d.ok)return p(d.error);let m=await gr(d.body);return p(m.ok?m.message:m.error)}let i=/^list\b([\s\S]*)$/i.exec(o);if(i){let d=(i[1]??"").trim(),{filter:m,bad:f}=ly(d);return f?p(`Unknown /shortcut list suffix: "${f}". Use: list | list --chat | list -p | list --global | list -g`):Kp(uy(t,m))}let a=/^show\b([\s\S]*)$/i.exec(o);if(a){let d=(a[1]??"").trim(),{mode:m,remainder:f}=ay(d),h=/^(\S+)\s*$/i.exec(f);if(!h?.[1])return p("Usage: /shortcut show <name> \u2014 or show --global|-g|--chat|-p <name> (-g shared, -p private)");let g=h[1];if(m==="resolved"){let M=_a(t,g);if(!M)return Vp(g);let R=M.scope==="global"?"Shared shortcut (all chats use it unless this chat overrides the name).":"This chat only.";return Ul(g,M.body,R)}let y=m==="global"?"global":"chat",k=un(y,t,g);if(k!==void 0)return Ul(g,k,y==="global"?"From the shared shortcut list (--global / -g).":"From this chat only (--chat / -p).");let b=y==="chat"&&un("global",t,g)!==void 0?`
591
+ (This chat stores an override: /run show --chat ${M})`:"",ee=R==="global"?`Unknown recipe "${M}" in gateway-shared storage.`:`Unknown recipe "${M}" in this chat storage.`;return p(`${ee}${A}${q}`)}let d=/^add\b([\s\S]*)$/i.exec(i);if(d){let{scope:b,remainder:k}=Pc((d[1]??"").trim()),x=k.match(/^(\S+)\s+([\s\S]+)$/);if(!x)return p(b==="global"?'Usage: /run add --global <name> <command\u2026> [--template "\u2026"] \u2014 cmd must reference "$OMNISH_TASK"':"Usage: /run add [--global|-g|--chat|-p] <name> <command\u2026> \u2014 see /run help");try{let C=$c(x[2],n.recipesMacroDefaultCommand);Jo(t,x[1],C,b);let M=Bt(x[1]),R=M.ok?M.normalized:x[1].toLowerCase(),N=an(b,t,n,R)??ze(t,n,R);return N?Ir(R,N,b):p("Recipe save failed.")}catch(C){return p(String(C))}}let m=/^set\b([\s\S]*)$/i.exec(i);if(m){let{scope:b,remainder:k,explicit:x}=Ec((m[1]??"").trim()),C=_f(k);if(C){if(x)return p("Cannot combine a leading scope flag (-g, --global, --chat, -p) with a trailing scope flag on the same line.");let N=Bt(C.name);if(!N.ok)return p(N.error);let A=Mc(t,N.normalized,C.target,n);if(!A.ok)return p(A.error);if(A.kind==="noop")return p(A.message);let q=an(A.target,t,n,N.normalized)??ze(t,n,N.normalized);return q?Ir(N.normalized,q,A.target):p("Recipe scope update failed.")}let M=k.match(/^(\S+)\s*$/);if(M?.[1]&&x){let N=Bt(M[1]);if(!N.ok)return p(N.error);let A=Mc(t,N.normalized,b,n);if(!A.ok)return p(A.error);if(A.kind==="noop")return p(A.message);let q=an(A.target,t,n,N.normalized)??ze(t,n,N.normalized);return q?Ir(N.normalized,q,A.target):p("Recipe scope update failed.")}let R=k.match(/^(\S+)\s+([\s\S]+)$/);if(!R)return p(b==="global"?'Usage: /run set --global <name> <command\u2026> [--template "\u2026"] \u2014 or scope-only: /run set --global <name> | /run set <name> -g':"Usage: /run set [--global|-g|--chat|-p] <name> <command\u2026> \u2014 or move scope without changing the body: /run set -g <name> | /run set <name> -p \u2014 see /run help");try{let N=$c(R[2],n.recipesMacroDefaultCommand);Jo(t,R[1],N,b);let A=Bt(R[1]),q=A.ok?A.normalized:R[1].toLowerCase(),ee=an(b,t,n,q)??ze(t,n,q);return ee?Ir(q,ee,b):p("Recipe save failed.")}catch(N){return p(String(N))}}let f=/^(?:remove|rm|del)\b([\s\S]*)$/i.exec(i);if(f){let{scope:b,remainder:k}=Pc((f[1]??"").trim()),x=k.match(/^(\S+)\s*$/);if(!x?.[1])return p(b==="global"?"Usage: /run remove --global <name> (aliases: rm, del)":"Usage: /run remove [--global|-g|--chat|-p] <name>");let C=x[1];return Df(t,C,b)?om(C,b):b==="chat"&&an("global",t,n,C)?p(`No recipe "${C}" in this chat. There is a gateway-shared recipe with that name \u2014 remove it with:
592
+ /run remove --global ${C}`):rm(C,b)}let h=/^queue\s+load\b([\s\S]*)$/i.exec(i);if(h){let b=(h[1]??"").trim(),k=ky(n),x=null,C=/^json(?:\s+([\s\S]+))?$/i.exec(b);if(C){let A=(C[1]??"").trim();if(!A)return p('Usage: /run queue load json [{"recipe":"\u2026","task":"\u2026"}, \u2026] \u2014 or { "tasks": [ \u2026 ] }');x=A}else if(b.length>0){let A=b;(A.startsWith('"')&&A.endsWith('"')||A.startsWith("'")&&A.endsWith("'"))&&(A=A.slice(1,-1));let q=oe(t).cwd,ee=await as(q,A);if(ee.length===0)return p(`No files matched: ${A}`);if(ee.length>1)return p("Queue load: specify a single JSON file.");let ce=await ls(ee);if(!ce.ok)return p(ce.error);let Pe=_u(ee[0],k);if(!Pe.ok)return p(Pe.error);x=Pe.text}else if(r){let A=_u(r,k);if(!A.ok)return p(A.error);x=A.text}else return p("Usage: /run queue load <file.json> \u2014 or /run queue load json [\u2026] \u2014 or attach a file with caption /run queue load");let M=Sy(x);if(!M.ok)return p(M.error);let R=vy(t,n,M.jobs);if(!R.ok)return p(R.error);let N=[];for(let A of R.items)N.push(o.enqueueQueuedRun(t,A,n));return p(N.join(`
593
+ `))}if(/^queue$/i.test(i))return p(o.runQueueStatus(t));if(/^queue\s+resume\s*$/i.test(i))return p(o.resumeRunQueue(t,n));let g=NE(i);if(g){let{recipe:b,task:k,queued:x,attach:C}=g,M=Lc(C,n),R=ze(t,n,b);if(!R)return Hl(b);if(R.steps&&R.steps.length>0){let Pe=oe(t).cwd,Ke=R.steps.length,F=R.steps;if(s?.sendToPeer){let ie=s.sendToPeer;(async()=>{let B=[],I=!1;for(let G=0;G<F.length;G++){let Y=F[G];if(I){B.push({index:G,label:Y.label??`step ${G+1}`,cmd:Y.cmd,exitCode:null,timedOut:!1,skipped:!0,output:""});continue}n.progressUpdates&&await ie(t,`Step ${G+1}/${Ke}: ${Y.label??`step ${G+1}`}\u2026`).catch(()=>{});let ae=await ct(n.shell,Y.cmd,{timeoutMs:n.syncTimeoutMs,maxBytes:n.syncMaxBytes,cwd:Pe}),ne=[ae.stdout,ae.stderr].filter(Boolean).join(`
594
+ `).trim(),Ye=ae.code===0&&!ae.timedOut,be={index:G,label:Y.label??`step ${G+1}`,cmd:Y.cmd,exitCode:ae.code,timedOut:ae.timedOut,skipped:!1,output:ne};B.push(be),n.progressUpdates&&await ie(t,Uf(b,be,G,Ke)).catch(()=>{}),!Ye&&!Y.continueOnFail&&(I=!0)}let z=Bf(b,B);await ie(t,z)})().catch(()=>{})}return p(`Runbook "${b}" started (${Ke} steps). Results will be sent when complete.`)}let N=R.taskEnv??"OMNISH_TASK";if(!Ho(R.command,N))return p(`Recipe "${b}" command must reference "$${N}".`);let A=jo(k,n.recipesMaxTaskChars);if(!A.ok)return p(A.error);let q=R.promptTemplate?vi(R.promptTemplate,N,A.task):A.task,ee={[N]:q,...Xr(t,n,{mutedByDefault:!!M.mute})};if(x){let Pe={command:R.command,extraEnv:ee,recipeLabel:b,startOptions:M};return p(o.enqueueQueuedRun(t,Pe,n))}let ce=xi(b);return p(o.start(t,ce,R.command,n,ee,M))}let y=i.match(/^(\S+)$/);if(y){let b=y[1],k=b.toLowerCase();return k==="add"||k==="set"?p('Usage: /run add <name> cmd [--template "\u2026"] \u2014 cmd must include "$OMNISH_TASK"'):k==="show"?p("Usage: /run show <name> \u2014 optional show --global|-g|--chat|-p <name>"):k==="remove"||k==="rm"||k==="del"?p("Usage: /run remove [--global|-g|--chat|-p] <name>"):ze(t,n,k)?p(`Usage: /run ${b} <task text\u2026> \u2014 replaces <<<OMNISH_TASK>>> / $OMNISH_TASK in stored templates`):p(`Unknown recipe "${b}". /run list`)}return p("/run: could not parse. /run help")}async function FE(e,t,n){let o=e.trim();if(!o||/^help$/i.test(o))return X(Ul());let r=/^online\b([\s\S]*)$/i.exec(o);if(r)return hr((r[1]??"").trim(),t,n,Qy);let s=/^(\S+)\s+publish\b([\s\S]*)$/i.exec(o);if(s){let d=ew(t,s[1],s[2]??"");if(!d.ok)return p(d.error);let m=await gr(d.body);return p(m.ok?m.message:m.error)}let i=/^list\b([\s\S]*)$/i.exec(o);if(i){let d=(i[1]??"").trim(),{filter:m,bad:f}=py(d);return f?p(`Unknown /shortcut list suffix: "${f}". Use: list | list --chat | list -p | list --global | list -g`):Qp(fy(t,m))}let a=/^show\b([\s\S]*)$/i.exec(o);if(a){let d=(a[1]??"").trim(),{mode:m,remainder:f}=dy(d),h=/^(\S+)\s*$/i.exec(f);if(!h?.[1])return p("Usage: /shortcut show <name> \u2014 or show --global|-g|--chat|-p <name> (-g shared, -p private)");let g=h[1];if(m==="resolved"){let M=Fa(t,g);if(!M)return Zp(g);let R=M.scope==="global"?"Shared shortcut (all chats use it unless this chat overrides the name).":"This chat only.";return Bl(g,M.body,R)}let y=m==="global"?"global":"chat",b=un(y,t,g);if(b!==void 0)return Bl(g,b,y==="global"?"From the shared shortcut list (--global / -g).":"From this chat only (--chat / -p).");let k=y==="chat"&&un("global",t,g)!==void 0?`
594
595
  (Shared shortcut exists: /shortcut show --global ${g})`:"",x=y==="global"&&un("chat",t,g)!==void 0?`
595
- (This chat overrides the name: /shortcut show --chat ${g})`:"",C=y==="global"?`Unknown shortcut "${g}" in shared shortcuts.`:`Unknown shortcut "${g}" in this chat.`;return p(`${C}${b}${x}`)}let l=/^add\b([\s\S]*)$/i.exec(o);if(l){let{scope:d,remainder:m}=Au((l[1]??"").trim()),f=m.match(/^(\S+)\s+([\s\S]+)$/);if(!f)return p(d==="global"?"Usage: /shortcut add --global <name> <command\u2026> (short: add -g <name> <command\u2026>) \u2014 -g shared, -p private":"Usage: /shortcut add [--global|-g|--chat|-p] <name> <command\u2026> \u2014 -g shared, -p private");try{us(t,f[1],f[2],d);let h=$t(f[1]),g=h.ok?h.normalized:f[1].trim().toLowerCase(),y=un(d,t,g)??"";return Ar(g,y,d)}catch(h){return p(String(h))}}let c=/^set\b([\s\S]*)$/i.exec(o);if(c){let{scope:d,remainder:m,explicit:f}=Mu((c[1]??"").trim()),h=cy(m);if(h){if(f)return p("Cannot combine a leading scope flag (-g, --global, --chat, -p) with a trailing flag on the same line.");let k=$t(h.name);if(!k.ok)return p(k.error);let b=Iu(t,k.normalized,h.target);if(!b.ok)return p(b.error);if(b.kind==="noop")return p(b.message);let x=un(b.target,t,k.normalized)??"";return Ar(k.normalized,x,b.target)}let g=m.match(/^(\S+)\s*$/);if(g?.[1]&&f){let k=$t(g[1]);if(!k.ok)return p(k.error);let b=Iu(t,k.normalized,d);if(!b.ok)return p(b.error);if(b.kind==="noop")return p(b.message);let x=un(b.target,t,k.normalized)??"";return Ar(k.normalized,x,b.target)}let y=m.match(/^(\S+)\s+([\s\S]+)$/);if(!y)return p(d==="global"?"Usage: /shortcut set --global <name> <command\u2026> \u2014 or scope-only: /shortcut set -g <name> | /shortcut set <name> -g (-g shared)":"Usage: /shortcut set [--global|-g|--chat|-p] <name> <command\u2026> \u2014 or move scope: /shortcut set -g <name> | /shortcut set <name> -p (-g shared, -p private)");try{us(t,y[1],y[2],d);let k=$t(y[1]),b=k.ok?k.normalized:y[1].trim().toLowerCase(),x=un(d,t,b)??"";return Ar(b,x,d)}catch(k){return p(String(k))}}let u=/^(?:remove|rm|del)\b([\s\S]*)$/i.exec(o);if(u){let{scope:d,remainder:m}=Au((u[1]??"").trim()),f=m.match(/^(\S+)\s*$/);if(!f?.[1])return p(d==="global"?"Usage: /shortcut remove --global <name> (aliases: rm, del)":"Usage: /shortcut remove [--global|-g|--chat|-p] <name>");let h=f[1];return dy(t,h,d)?Yp(h,d):d==="chat"&&un("global",t,h)!==void 0?p(`No shortcut "${h}" in this chat. There is a shared shortcut with that name \u2014 remove it with:
596
- /shortcut remove --global ${h}`):Qp(h,d)}return X(Wl())}async function ME(e,t,n,o,r){let s=e.slice(5).trim(),i=s.toLowerCase();if(!i||i==="help")return X(Hl());let a=s.match(/^(\S+)\s*(.*)$/s);if(!a)return X(Hl());let l=a[1].toLowerCase(),c=(a[2]??"").trim();if(l==="online")return hr(c,t,n,Gy);let u=c.match(/^publish\b([\s\S]*)$/i);if(u){let d=l,m=o.getSessionCommand(t,d);if(!m)return p(`No running session "${d}" with a command. /apps list`);let f=Xy(d,m,u[1]??"");if(!f.ok)return p(f.error);let h=await gr(f.body);return p(h.ok?h.message:h.error)}switch(l){case"start":{let d=c.match(/^(\S+)\s+([\s\S]+)$/);return d?p(o.start(t,d[1],d[2],n,Xr(t,n))):p("Usage: /apps start <name> <command\u2026>")}case"attach":{let d=c.split(/\s+/)[0];return d?p(o.attach(t,d)):p("Usage: /apps attach <name>")}case"detach":return p(o.detach(t));case"list":return p(o.list(t));case"info":case"get":{let d=c.split(/\s+/)[0];return p(o.info(t,d||void 0))}case"send":{let d=c.match(/^(\S+)\s+([\s\S]+)$/);return d?p(await o.sendText(t,d[1],d[2])):p("Usage: /apps send <name> <text\u2026>")}case"key":{let d=c.match(/^(\S+)\s+([\s\S]+)$/);return d?p(o.sendKey(t,d[1],d[2].trim())):p("Usage: /apps key <name> <KEY[,KEY\u2026]>")}case"tail":{let d=c.match(/^(\S+)(?:\s+(\d+))?\s*$/);if(!d)return p("Usage: /apps tail <name> [lines]");let m=d[2]?Number.parseInt(d[2],10):n.appsLogTailLines;return p(o.tail(t,d[1],m))}case"since":{let d=c.split(/\s+/)[0];if(!d)return p("Usage: /apps since <name>");let m=vE(t,d),f=r.get(m)??0,{text:h,nextOffset:g}=o.readSince(t,d,f);return r.set(m,g),p(h.trimEnd()||"(no new log bytes)")}case"mute":{let d=c.split(/\s+/)[0];return d?p(o.mute(t,d)):p("Usage: /apps mute <name>")}case"unmute":{let d=c.split(/\s+/)[0];return d?p(o.unmute(t,d)):p("Usage: /apps unmute <name>")}case"raw":{let d=c.match(/^(\S+)\s+(on|off)\s*$/i);return d?p(o.setRaw(t,d[1],d[2].toLowerCase()==="on")):p("Usage: /apps raw <name> on|off")}case"resize":{let d=c.trim().split(/\s+/).filter(Boolean);if(d.length<3)return p("Usage: /apps resize <name> <cols> <rows>");let m=d[0],f=Number(d[1]),h=Number(d[2]);return!Number.isFinite(f)||!Number.isFinite(h)?p("cols and rows must be numbers."):p(o.resize(t,m,f,h))}case"stop":{let d=c.split(/\s+/)[0];return d?p(o.stop(t,d)):p("Usage: /apps stop <name>")}case"kill":{let d=c.split(/\s+/)[0];return d?p(o.kill(t,d)):p("Usage: /apps kill <name>")}case"rm":{let d=c.split(/\s+/)[0];return d?p(o.rm(t,d)):p("Usage: /apps rm <name>")}default:return p(`Unknown /apps subcommand "${l}". /apps help`)}}function vw(e,t,n){return!e.clusterEnabled||Qu(t.trim())!==null?!0:n?Cm(n,e):!1}async function xw(e,t){if(t.kind==="bundle"){for(let n of t.texts??[])await e({kind:"text",body:n});for(let n of t.files??[])await e({kind:"file",spec:n});return}if(t.kind==="texts"){for(let n of t.bodies)await e({kind:"text",body:n});return}await e(t)}be();dt();function Cw(){return p("Not allowlisted on this omnish device.")}async function hs(e,t,n,o,r,s,i,a,l,c,u,d){if((d?.surface??(s.peerKey.startsWith("tg:")?"telegram":"whatsapp"))==="telegram"){let f=Ps(e.telegramAllowFrom),h=s.peerKey.startsWith("tg:")?s.peerKey.slice(3):"";if(!h||!f.has(h)){P.warn({denied:s.peerKey,uid:h},"telegram denied"),await u({kind:"text",body:Cw()});return}}else{let f=Es(e.allowFrom),h=l.startsWith("wa:")&&/^wa:\+\d+$/.test(l)?l.slice(3):s.peerKey.replace(/^wa:/,""),g=te(h)||"";if(!g||!f.has(g)){P.warn({denied:s.peerKey,phone:g,senderKey:l},"denied"),await u({kind:"text",body:Cw()});return}}try{if(!!!(s.mediaSavedPath||s.mediaError)&&!vw(e,s.text,l))return;if(s.mediaError&&await u({kind:"text",body:p(s.mediaError)}),s.mediaSavedPath&&await u({kind:"text",body:p(`Saved: ${s.mediaSavedPath}`)}),s.text.trim()){let h=await yr(e,t,n,o,r,s,i,a,l,!1,c);h!==null&&await xw(u,h)}}catch(f){P.error({err:String(f)},"inbound handler error"),await u({kind:"text",body:p(`Error: ${String(f)}`)}).catch(()=>{})}}function IE(){if(process.env.OMNISH_BACKGROUND_GATEWAY==="1")try{Xu.readFileSync(de,"utf8").trim()===String(process.pid)&&Xu.unlinkSync(de)}catch{}}async function Rw(e){process.env[cc]="1";let t=new Map,n=new Map,o=new Map,r=null,s=new Map,i=async(F,ie)=>{let B=s.get(F);r?.sendReply(F,ie,B)},a=new cn({onJobExit(F){F.notifyPeerKey&&i(F.notifyPeerKey,la(F))}}),l=async(F,ie)=>{let B=F.startsWith("tg:")?"telegram":"whatsapp",I=s.get(F),z=ci(F,{kind:"file",spec:ie},I,B);r?.sendRoutedReply(F,z)},c=async(F,ie)=>{let B=s.get(F),I=F.startsWith("tg:")?"telegram":"whatsapp";r?.sendReply(F,pe(p(ie),I).text,B)},u={onPlainTextAgentDaemon(F,ie){let B=v(),I=ra(B,F,ie);I.ok||i(F,`[agent] ${I.error}`)},onPlainTextLlmFallback(F,ie){qo(v(),F,ie,B=>i(F,B))},getAgentDaemonStatus(){return ia()},stopAgentDaemon(F){return Zr(v(),F)},resetAgentDaemon(F){return sa(v(),F)},sendToPeer:i},d=new An(()=>v(),i),m={async reload(){return{ok:!0,summary:"Attached mode: config reloaded from disk. Messengers run on the omnish platform; this device executes commands locally."}}};if(r=new xa({env:e,onReplyError:async(F,ie,B)=>{let I=F.startsWith("tg:")?"telegram":"whatsapp";r?.sendReply(F,pe(p(`Error sending: ${ie}`),I).text,B)},onMessage:async F=>{let ie=bu(),B=F.peerKey;s.set(B,F.messageId);let I=F.surface==="telegram"?"telegram":"whatsapp",z=F.senderE164&&I==="whatsapp"?`wa:${F.senderE164}`:B;xo()&&P.info({peerKey:B,senderKey:z,surface:I},"platform inbound message");let G=F.mediaSavedPath,Y=F.mediaError;if(F.inboundMedia){let ne=Fg(ie,B,F.inboundMedia);G=ne.mediaSavedPath??G,Y=ne.mediaError??Y}let ae={peerKey:B,text:F.text,...G?{mediaSavedPath:G}:{},...Y?{mediaError:Y}:{}};await hs(ie,a,t,n,o,ae,d,m,z,u,async ne=>{try{if(ne.kind==="bundle"){for(let we of ne.texts??[]){let re=pe(we,I);r?.sendReply(B,re.text,F.messageId)}for(let we of ne.files??[]){let re=ci(B,{kind:"file",spec:we},F.messageId,I);r?.sendRoutedReply(B,re)}return}if(ne.kind==="texts"){for(let we of ne.bodies){let re=pe(we,I);r?.sendReply(B,re.text,F.messageId)}return}if(ne.kind==="text"){let we=pe(ne.body,I);r?.sendReply(B,we.text,F.messageId);return}let Ye=ci(B,ne,F.messageId,I);r?.sendRoutedReply(B,Ye)}catch(Ye){r?.sendReply(B,`Error sending file: ${String(Ye)}`,F.messageId)}},{surface:I})}}),process.env.OMNISH_BACKGROUND_GATEWAY==="1")try{Xu.writeFileSync(de,`${process.pid}
597
- `,{mode:384})}catch(F){P.warn({err:String(F)},"could not write gateway pidfile")}ya({getCfg:()=>v(),getWaOutbound:()=>null,getTgSendMedia:()=>null,getTgSendText:()=>null,sendPlatformMedia:l,sendPlatformText:c});let f=null,h=v();if(h.webhookEnabled){let F=h.webhookToken||AE.randomBytes(32).toString("hex");h.webhookToken||$({webhookToken:F}),f=wa({port:h.webhookPort,host:h.webhookHost,token:F},{sendToPeer:i,getDefaultPeerKey:()=>null}).stop}let g=Ia({getRunningVersion:gt,getConfig:v,log:P}),y=na({getConfig:v,sendToPeer:i,sendMediaToPeer:l}),k=Yi({getConfig:v,sendToPeer:i}),b=Us({getConfig:v,sendToPeer:i}),{deviceId:x,account:C}=await r.connect(),M=await Sa(e,C??null),R=5*60*1e3,N=setInterval(()=>{Sa(e,null).catch(()=>{})},R);N.unref?.();let A=e.deviceId?.trim();if(A){A!==x&&P.warn({configuredDeviceId:A,registeredDeviceId:x},"platform_device_id does not match registered device id");try{await Su(e,A)}catch(F){P.warn({err:String(F)},"could not set platform default device")}}let q=M?.gatewayMode??C?.gatewayMode,ee=M?.connectors.whatsapp??C?.connectors?.whatsapp,ce=M?.connectors.telegram??C?.connectors?.telegram,Pe=[ee?`whatsapp:${ee.linked?"linked":ee.status}`:null,ce?`telegram:${ce.linked?"linked":ce.status}`:null].filter(Boolean).join(", ");console.error(`omnish attached to platform (device ${x})`+(q?` \u2014 gatewayMode=${q}`:"")+(Pe?` \u2014 ${Pe}`:""));let Ke=()=>{clearInterval(N),y(),k(),b(),g?.(),f?.(),IE(),sr(),aa(),r?.stop(),d.dispose(),a.killAllRunning(),mo().stopAll().catch(()=>{}),console.error(`
598
- ${E(process.stderr,"shutting down\u2026")}`),process.exit(0)};process.on("SIGINT",Ke),process.on("SIGTERM",Ke),await new Promise(()=>{})}function Zu(e,t){return async n=>{if(t.surface==="whatsapp"&&t.waJid){if(n.kind==="bundle"){for(let o of n.texts??[])await e.sendWaText?.(t.waJid,pe(o,"whatsapp").text);for(let o of n.files??[])await e.sendWaMedia?.(t.waJid,o)}else if(n.kind==="file")await e.sendWaMedia?.(t.waJid,n.spec);else if(n.kind==="files")for(let o of n.specs)await e.sendWaMedia?.(t.waJid,o);else if(n.kind==="texts")for(let o of n.bodies)await e.sendWaText?.(t.waJid,pe(o,"whatsapp").text);else n.kind==="text"&&await e.sendWaText?.(t.waJid,pe(n.body,"whatsapp").text);return}if(t.surface==="telegram"&&e.sendTg)if(n.kind==="bundle"){for(let o of n.texts??[])await e.sendTg({kind:"text",body:o});for(let o of n.files??[])await e.sendTg({kind:"file",spec:o})}else if(n.kind==="texts")for(let o of n.bodies)await e.sendTg({kind:"text",body:o});else await e.sendTg(n)}}ue();qt();j();function Za(){if(ge()){let i=v(),a=tn(i);return Pr(a)?{ok:!1,message:`Fix security errors before starting the gateway.
596
+ (This chat overrides the name: /shortcut show --chat ${g})`:"",C=y==="global"?`Unknown shortcut "${g}" in shared shortcuts.`:`Unknown shortcut "${g}" in this chat.`;return p(`${C}${k}${x}`)}let l=/^add\b([\s\S]*)$/i.exec(o);if(l){let{scope:d,remainder:m}=Lu((l[1]??"").trim()),f=m.match(/^(\S+)\s+([\s\S]+)$/);if(!f)return p(d==="global"?"Usage: /shortcut add --global <name> <command\u2026> (short: add -g <name> <command\u2026>) \u2014 -g shared, -p private":"Usage: /shortcut add [--global|-g|--chat|-p] <name> <command\u2026> \u2014 -g shared, -p private");try{us(t,f[1],f[2],d);let h=$t(f[1]),g=h.ok?h.normalized:f[1].trim().toLowerCase(),y=un(d,t,g)??"";return Ar(g,y,d)}catch(h){return p(String(h))}}let c=/^set\b([\s\S]*)$/i.exec(o);if(c){let{scope:d,remainder:m,explicit:f}=Ou((c[1]??"").trim()),h=my(m);if(h){if(f)return p("Cannot combine a leading scope flag (-g, --global, --chat, -p) with a trailing flag on the same line.");let b=$t(h.name);if(!b.ok)return p(b.error);let k=Nu(t,b.normalized,h.target);if(!k.ok)return p(k.error);if(k.kind==="noop")return p(k.message);let x=un(k.target,t,b.normalized)??"";return Ar(b.normalized,x,k.target)}let g=m.match(/^(\S+)\s*$/);if(g?.[1]&&f){let b=$t(g[1]);if(!b.ok)return p(b.error);let k=Nu(t,b.normalized,d);if(!k.ok)return p(k.error);if(k.kind==="noop")return p(k.message);let x=un(k.target,t,b.normalized)??"";return Ar(b.normalized,x,k.target)}let y=m.match(/^(\S+)\s+([\s\S]+)$/);if(!y)return p(d==="global"?"Usage: /shortcut set --global <name> <command\u2026> \u2014 or scope-only: /shortcut set -g <name> | /shortcut set <name> -g (-g shared)":"Usage: /shortcut set [--global|-g|--chat|-p] <name> <command\u2026> \u2014 or move scope: /shortcut set -g <name> | /shortcut set <name> -p (-g shared, -p private)");try{us(t,y[1],y[2],d);let b=$t(y[1]),k=b.ok?b.normalized:y[1].trim().toLowerCase(),x=un(d,t,k)??"";return Ar(k,x,d)}catch(b){return p(String(b))}}let u=/^(?:remove|rm|del)\b([\s\S]*)$/i.exec(o);if(u){let{scope:d,remainder:m}=Lu((u[1]??"").trim()),f=m.match(/^(\S+)\s*$/);if(!f?.[1])return p(d==="global"?"Usage: /shortcut remove --global <name> (aliases: rm, del)":"Usage: /shortcut remove [--global|-g|--chat|-p] <name>");let h=f[1];return hy(t,h,d)?Xp(h,d):d==="chat"&&un("global",t,h)!==void 0?p(`No shortcut "${h}" in this chat. There is a shared shortcut with that name \u2014 remove it with:
597
+ /shortcut remove --global ${h}`):em(h,d)}return X(Ul())}async function DE(e,t,n,o,r){let s=e.slice(5).trim(),i=s.toLowerCase();if(!i||i==="help")return X(Jl());let a=s.match(/^(\S+)\s*(.*)$/s);if(!a)return X(Jl());let l=a[1].toLowerCase(),c=(a[2]??"").trim();if(l==="online")return hr(c,t,n,Yy);let u=c.match(/^publish\b([\s\S]*)$/i);if(u){let d=l,m=o.getSessionCommand(t,d);if(!m)return p(`No running session "${d}" with a command. /apps list`);let f=nw(d,m,u[1]??"");if(!f.ok)return p(f.error);let h=await gr(f.body);return p(h.ok?h.message:h.error)}switch(l){case"start":{let d=c.match(/^(\S+)\s+([\s\S]+)$/);return d?p(o.start(t,d[1],d[2],n,Xr(t,n))):p("Usage: /apps start <name> <command\u2026>")}case"attach":{let d=c.split(/\s+/)[0];return d?p(o.attach(t,d)):p("Usage: /apps attach <name>")}case"detach":return p(o.detach(t));case"list":return p(o.list(t));case"info":case"get":{let d=c.split(/\s+/)[0];return p(o.info(t,d||void 0))}case"send":{let d=c.match(/^(\S+)\s+([\s\S]+)$/);return d?p(await o.sendText(t,d[1],d[2])):p("Usage: /apps send <name> <text\u2026>")}case"key":{let d=c.match(/^(\S+)\s+([\s\S]+)$/);return d?p(o.sendKey(t,d[1],d[2].trim())):p("Usage: /apps key <name> <KEY[,KEY\u2026]>")}case"tail":{let d=c.match(/^(\S+)(?:\s+(\d+))?\s*$/);if(!d)return p("Usage: /apps tail <name> [lines]");let m=d[2]?Number.parseInt(d[2],10):n.appsLogTailLines;return p(o.tail(t,d[1],m))}case"since":{let d=c.split(/\s+/)[0];if(!d)return p("Usage: /apps since <name>");let m=ME(t,d),f=r.get(m)??0,{text:h,nextOffset:g}=o.readSince(t,d,f);return r.set(m,g),p(h.trimEnd()||"(no new log bytes)")}case"mute":{let d=c.split(/\s+/)[0];return d?p(o.mute(t,d)):p("Usage: /apps mute <name>")}case"unmute":{let d=c.split(/\s+/)[0];return d?p(o.unmute(t,d)):p("Usage: /apps unmute <name>")}case"raw":{let d=c.match(/^(\S+)\s+(on|off)\s*$/i);return d?p(o.setRaw(t,d[1],d[2].toLowerCase()==="on")):p("Usage: /apps raw <name> on|off")}case"resize":{let d=c.trim().split(/\s+/).filter(Boolean);if(d.length<3)return p("Usage: /apps resize <name> <cols> <rows>");let m=d[0],f=Number(d[1]),h=Number(d[2]);return!Number.isFinite(f)||!Number.isFinite(h)?p("cols and rows must be numbers."):p(o.resize(t,m,f,h))}case"stop":{let d=c.split(/\s+/)[0];return d?p(o.stop(t,d)):p("Usage: /apps stop <name>")}case"kill":{let d=c.split(/\s+/)[0];return d?p(o.kill(t,d)):p("Usage: /apps kill <name>")}case"rm":{let d=c.split(/\s+/)[0];return d?p(o.rm(t,d)):p("Usage: /apps rm <name>")}default:return p(`Unknown /apps subcommand "${l}". /apps help`)}}function Tw(e,t,n){return!e.clusterEnabled||ed(t.trim())!==null?!0:n?Em(n,e):!1}async function Ew(e,t){if(t.kind==="bundle"){for(let n of t.texts??[])await e({kind:"text",body:n});for(let n of t.files??[])await e({kind:"file",spec:n});return}if(t.kind==="texts"){for(let n of t.bodies)await e({kind:"text",body:n});return}await e(t)}ye();dt();function Pw(){return p("Not allowlisted on this omnish device.")}async function hs(e,t,n,o,r,s,i,a,l,c,u,d){if((d?.surface??(s.peerKey.startsWith("tg:")?"telegram":"whatsapp"))==="telegram"){let f=Ps(e.telegramAllowFrom),h=s.peerKey.startsWith("tg:")?s.peerKey.slice(3):"";if(!h||!f.has(h)){E.warn({denied:s.peerKey,uid:h},"telegram denied"),await u({kind:"text",body:Pw()});return}}else{let f=Es(e.allowFrom),h=l.startsWith("wa:")&&/^wa:\+\d+$/.test(l)?l.slice(3):s.peerKey.replace(/^wa:/,""),g=te(h)||"";if(!g||!f.has(g)){E.warn({denied:s.peerKey,phone:g,senderKey:l},"denied"),await u({kind:"text",body:Pw()});return}}try{if(!!!(s.mediaSavedPath||s.mediaError)&&!Tw(e,s.text,l))return;if(s.mediaError&&await u({kind:"text",body:p(s.mediaError)}),s.mediaSavedPath&&await u({kind:"text",body:p(`Saved: ${s.mediaSavedPath}`)}),s.text.trim()){let h=await yr(e,t,n,o,r,s,i,a,l,!1,c);h!==null&&await Ew(u,h)}}catch(f){E.error({err:String(f)},"inbound handler error"),await u({kind:"text",body:p(`Error: ${String(f)}`)}).catch(()=>{})}}function UE(){if(process.env.OMNISH_BACKGROUND_GATEWAY==="1")try{td.readFileSync(de,"utf8").trim()===String(process.pid)&&td.unlinkSync(de)}catch{}}async function $w(e){process.env[uc]="1";let t=new Map,n=new Map,o=new Map,r=null,s=new Map,i=async(F,ie)=>{let B=s.get(F);r?.sendReply(F,ie,B)},a=new cn({onJobExit(F){F.notifyPeerKey&&i(F.notifyPeerKey,ca(F))}}),l=async(F,ie)=>{let B=F.startsWith("tg:")?"telegram":"whatsapp",I=s.get(F),z=ci(F,{kind:"file",spec:ie},I,B);r?.sendRoutedReply(F,z)},c=async(F,ie)=>{let B=s.get(F),I=F.startsWith("tg:")?"telegram":"whatsapp";r?.sendReply(F,pe(p(ie),I).text,B)},u={onPlainTextAgentDaemon(F,ie){let B=v(),I=sa(B,F,ie);I.ok||i(F,`[agent] ${I.error}`)},onPlainTextLlmFallback(F,ie){qo(v(),F,ie,B=>i(F,B))},getAgentDaemonStatus(){return aa()},stopAgentDaemon(F){return Zr(v(),F)},resetAgentDaemon(F){return ia(v(),F)},sendToPeer:i},d=new An(()=>v(),i),m={async reload(){return{ok:!0,summary:"Attached mode: config reloaded from disk. Messengers run on the omnish platform; this device executes commands locally."}}};if(r=new Ca({env:e,onReplyError:async(F,ie,B)=>{let I=F.startsWith("tg:")?"telegram":"whatsapp";r?.sendReply(F,pe(p(`Error sending: ${ie}`),I).text,B)},onMessage:async F=>{let ie=vu(),B=F.peerKey;s.set(B,F.messageId);let I=F.surface==="telegram"?"telegram":"whatsapp",z=F.senderE164&&I==="whatsapp"?`wa:${F.senderE164}`:B;xo()&&E.info({peerKey:B,senderKey:z,surface:I},"platform inbound message");let G=F.mediaSavedPath,Y=F.mediaError;if(F.inboundMedia){let ne=Bg(ie,B,F.inboundMedia);G=ne.mediaSavedPath??G,Y=ne.mediaError??Y}let ae={peerKey:B,text:F.text,...G?{mediaSavedPath:G}:{},...Y?{mediaError:Y}:{}};await hs(ie,a,t,n,o,ae,d,m,z,u,async ne=>{try{if(ne.kind==="bundle"){for(let be of ne.texts??[]){let re=pe(be,I);r?.sendReply(B,re.text,F.messageId)}for(let be of ne.files??[]){let re=ci(B,{kind:"file",spec:be},F.messageId,I);r?.sendRoutedReply(B,re)}return}if(ne.kind==="texts"){for(let be of ne.bodies){let re=pe(be,I);r?.sendReply(B,re.text,F.messageId)}return}if(ne.kind==="text"){let be=pe(ne.body,I);r?.sendReply(B,be.text,F.messageId);return}let Ye=ci(B,ne,F.messageId,I);r?.sendRoutedReply(B,Ye)}catch(Ye){r?.sendReply(B,`Error sending file: ${String(Ye)}`,F.messageId)}},{surface:I})}}),process.env.OMNISH_BACKGROUND_GATEWAY==="1")try{td.writeFileSync(de,`${process.pid}
598
+ `,{mode:384})}catch(F){E.warn({err:String(F)},"could not write gateway pidfile")}wa({getCfg:()=>v(),getWaOutbound:()=>null,getTgSendMedia:()=>null,getTgSendText:()=>null,sendPlatformMedia:l,sendPlatformText:c});let f=null,h=v();if(h.webhookEnabled){let F=h.webhookToken||WE.randomBytes(32).toString("hex");h.webhookToken||$({webhookToken:F}),f=ba({port:h.webhookPort,host:h.webhookHost,token:F},{sendToPeer:i,getDefaultPeerKey:()=>null}).stop}let g=Oa({getRunningVersion:gt,getConfig:v,log:E}),y=oa({getConfig:v,sendToPeer:i,sendMediaToPeer:l}),b=Vi({getConfig:v,sendToPeer:i}),k=Us({getConfig:v,sendToPeer:i}),{deviceId:x,account:C}=await r.connect(),M=await va(e,C??null),R=5*60*1e3,N=setInterval(()=>{va(e,null).catch(()=>{})},R);N.unref?.();let A=e.deviceId?.trim();if(A){A!==x&&E.warn({configuredDeviceId:A,registeredDeviceId:x},"platform_device_id does not match registered device id");try{await Cu(e,A)}catch(F){E.warn({err:String(F)},"could not set platform default device")}}let q=M?.gatewayMode??C?.gatewayMode,ee=M?.connectors.whatsapp??C?.connectors?.whatsapp,ce=M?.connectors.telegram??C?.connectors?.telegram,Pe=[ee?`whatsapp:${ee.linked?"linked":ee.status}`:null,ce?`telegram:${ce.linked?"linked":ce.status}`:null].filter(Boolean).join(", ");console.error(`omnish attached to platform (device ${x})`+(q?` \u2014 gatewayMode=${q}`:"")+(Pe?` \u2014 ${Pe}`:""));let Ke=()=>{clearInterval(N),y(),b(),k(),g?.(),f?.(),UE(),sr(),la(),r?.stop(),d.dispose(),a.killAllRunning(),mo().stopAll().catch(()=>{}),console.error(`
599
+ ${P(process.stderr,"shutting down\u2026")}`),process.exit(0)};process.on("SIGINT",Ke),process.on("SIGTERM",Ke),await new Promise(()=>{})}function nd(e,t){return async n=>{if(t.surface==="whatsapp"&&t.waJid){if(n.kind==="bundle"){for(let o of n.texts??[])await e.sendWaText?.(t.waJid,pe(o,"whatsapp").text);for(let o of n.files??[])await e.sendWaMedia?.(t.waJid,o)}else if(n.kind==="file")await e.sendWaMedia?.(t.waJid,n.spec);else if(n.kind==="files")for(let o of n.specs)await e.sendWaMedia?.(t.waJid,o);else if(n.kind==="texts")for(let o of n.bodies)await e.sendWaText?.(t.waJid,pe(o,"whatsapp").text);else n.kind==="text"&&await e.sendWaText?.(t.waJid,pe(n.body,"whatsapp").text);return}if(t.surface==="telegram"&&e.sendTg)if(n.kind==="bundle"){for(let o of n.texts??[])await e.sendTg({kind:"text",body:o});for(let o of n.files??[])await e.sendTg({kind:"file",spec:o})}else if(n.kind==="texts")for(let o of n.bodies)await e.sendTg({kind:"text",body:o});else await e.sendTg(n)}}ue();qt();j();function el(){if(ge()){let i=v(),a=tn(i);return Pr(a)?{ok:!1,message:`Fix security errors before starting the gateway.
599
600
 
600
601
  ${a.filter(u=>u.severity==="error").map(u=>{let d=[u.message];return u.detail&&d.push(u.detail),u.fixHint&&d.push(`Fix: ${u.fixHint}`),d.join(`
601
602
  `)}).join(`
@@ -606,31 +607,31 @@ config: ${U}`};if(n&&!St())return{ok:!1,message:"WhatsApp enabled but no session
606
607
  ${s.filter(l=>l.severity==="error").map(l=>{let c=[l.message];return l.detail&&c.push(l.detail),l.fixHint&&c.push(`Fix: ${l.fixHint}`),c.join(`
607
608
  `)}).join(`
608
609
 
609
- `)}`}:{ok:!0}}qt();j();qt();import ol from"node:fs";import rl from"node:path";ue();import{spawn as OE}from"node:child_process";import Tw from"node:fs";import{stdout as LE}from"node:process";qt();j();Kn();var ed=["tunnelRelayUrl","platformToken","platformDeviceId"],Ew=[{label:"Platform (attached gateway + tunnel)",keys:["tunnelRelayUrl","platformToken","platformDeviceId"]},{label:"Tunnel",keys:["tunnelEnabled","tunnelMaxActive"]},{label:"Gateway",keys:["gatewayMode","commandPrefix","shell"]},{label:"Cluster",keys:["clusterEnabled","clusterRole","clusterLabel","clusterSenderBindings"]},{label:"Telegram",keys:["telegramBotToken"]},{label:"Apps",keys:["appsCols","appsRows","appsFlushMs","appsMinIntervalMs","appsMaxFlushBytes","appsMaxSessions","appsMaxSessionsTotal","appsMaxWaChars","appsLogTailLines","appsSubmitDelayMs","appsClearInput","appsClearInputDelayMs","appsClearInputSequence","appsSkipClearOnPasswordPrompt","appsPasswordPromptHint"]},{label:"Files",keys:["fileSendMaxBytes","fileReceiveMaxBytes","fileInboxSubdir","fileReceiveRootMode","fileReceiveRootPath"]},{label:"Recipes",keys:["recipesAllowDangerousBuiltins","recipesMaxTaskChars","recipesMacroDefaultCommand","recipesRunAttach"]},{label:"Sync / jobs",keys:["syncTimeoutMs","syncMaxBytes","jobLogTailLines"]},{label:"Service / updates",keys:["serviceInstallFromChat","updateCheckEnabled","updateCheckIntervalMs","updateCheckPackageName","updateInfoUrl"]},{label:"Chat LLM fallback",keys:["chatLlmFallbackEnabled","chatLlmShellCommand","chatLlmTimeoutMs","chatLlmMaxInputChars","chatLlmMaxOutputChars","chatLlmNeedsTty","chatLlmWorkDir"]}];function tl(e){let t=[K(e,"omnish config"),w(e,"Manage ~/.omnish/config.json (env vars still override for platform credentials)."),"",K(e,"Usage:"),` ${S(e,"omnish config add <key> <value> [key value ...]")}`,` ${S(e,"omnish config show <key>[,<key>|*]")}`,` ${S(e,"omnish config edit")}`,` ${S(e,"omnish config edit <key> <value>")}`,` ${S(e,"omnish config delete <key>[,<key>]")}`,"",w(e,"Platform aliases: platform_url \u2192 tunnelRelayUrl, platform_token \u2192 platformToken,"),w(e," platform_device_id \u2192 platformDeviceId"),` ${S(e,"omnish config show platform")} ${w(e,"\u2014 attached-mode diagnostics (URL, token, devices)")}`,w(e," Full setup: omnish help platform"),w(e,"add sets/overwrites scalar keys (not array append). show * lists all keys."),""];console.log(t.join(`
610
- `))}function Pw(e){return e.split(/[,\s]+/).map(t=>t.trim()).filter(Boolean)}function td(e){if(e.length===0)return[];let t=e.join(" ");if(t.includes(",")){let n=[];for(let o of t.split(",")){let r=o.trim();if(!r)continue;let s=r.split(/\s+/);if(s.length<2)throw new Error(`Invalid pair "${r}" \u2014 expected "key value"`);n.push({key:s[0],value:s.slice(1).join(" ")})}return n}if(e.length===2)return[{key:e[0],value:e[1]}];if(e.length%2===0){let n=[];for(let o=0;o<e.length;o+=2)n.push({key:e[o],value:e[o+1]});return n}return[{key:e[0],value:e.slice(1).join(" ")}]}function el(e,t){if(e==="clusterSenderBindings")return JSON.stringify(t.clusterSenderBindings);let n=String(t[e]??"");return e==="platformToken"||e==="telegramBotToken"?Vn(e,n):n}function $w(e){return e==="tunnelRelayUrl"?{value:os(),source:ar()}:e==="platformToken"?{value:Vn(e,mu()),source:ir()}:e==="platformDeviceId"?{value:(fu()??"")||"(empty)",source:hu()}:null}function NE(e){if(e!=="platformToken"&&e!=="tunnelRelayUrl")return;let n=v().platformToken.trim()||Zt()?.token?.trim()||"";n&&Ft({token:n,relayUrl:os()})}async function _E(){let e=process.env.VISUAL?.trim()||process.env.EDITOR?.trim()||(process.platform==="win32"?"notepad":"nano"),t=Tw.readFileSync(U,"utf8");await new Promise((n,o)=>{let r=OE(e,[U],{stdio:"inherit"});r.on("error",o),r.on("exit",s=>{s===0?n():o(new Error(`Editor exited with code ${s??1}`))})});try{v()}catch(n){throw Tw.writeFileSync(U,t,{mode:384}),new Error(`Invalid config after edit: ${n instanceof Error?n.message:String(n)}`)}}async function Mw(e){let t=LE,n=process.stderr,o=(e[0]??"").trim().toLowerCase(),r=e.slice(1);if(!o||o==="help"||o==="--help"||o==="-h"){tl(t);return}if(o==="add"||o==="edit"){try{if(o==="edit"&&r.length===0){await _E(),console.log(H(t,`Updated ${U}`));return}let s=td(r);if(s.length===0){console.error(E(n,"Expected at least one key/value pair.")),process.exitCode=1;return}for(let{key:i,value:a}of s){let l=Qs(i);if(!l){console.error(E(n,`Unknown key "${i}". Try: ${dm().slice(0,8).join(", ")}\u2026 (omnish config show *)`)),process.exitCode=1;return}Ao(l,a),NE(l);let c=v(),u=l==="tunnelRelayUrl"?c.tunnelRelayUrl:ed.includes(l)?Vn(l,String(c[l]??"")):el(l,c);console.log(H(t,`${i} \u2192 ${u}`))}console.log(H(t,U))}catch(s){console.error(E(n,s instanceof Error?s.message:String(s))),process.exitCode=1}return}if(o==="show"){let s=r.join(" ").trim()||"*";if(s==="*"){let l=v(),c=[K(t,"Config"),w(t,U),""];for(let m of Ew){c.push(K(t,m.label));for(let f of m.keys){let h=el(f,l),g=$w(f);g&&ed.includes(f)?c.push(` ${S(t,f)}: ${h} ${w(t,`(effective: ${g.value}, source: ${g.source})`)}`):c.push(` ${S(t,f)}: ${h}`)}c.push("")}let u=new Set(Ew.flatMap(m=>m.keys)),d=Jl.filter(m=>!u.has(m));if(d.length>0){c.push(K(t,"Other"));for(let m of d)c.push(` ${S(t,m)}: ${el(m,l)}`)}console.log(c.join(`
611
- `));return}let i=Pw(s),a=v();for(let l of i){let c=Qs(l);if(!c){console.error(E(n,`Unknown key "${l}".`)),process.exitCode=1;return}let u=el(c,a),d=$w(c);d&&ed.includes(c)?console.log(`${l}: ${u} (effective: ${d.value}, source: ${d.source})`):console.log(`${l}: ${u}`)}return}if(o==="delete"){let s=r.join(" ").trim();if(!s){console.error(E(n,"Expected at least one key to delete.")),process.exitCode=1;return}if(s==="*"){console.error(E(n,'Refusing "delete *". List keys explicitly.')),process.exitCode=1;return}try{for(let i of Pw(s)){let a=Qs(i);if(!a){console.error(E(n,`Unknown key "${i}".`)),process.exitCode=1;return}pm(a),console.log(H(t,`cleared ${i}`))}console.log(H(t,U))}catch(i){console.error(E(n,i instanceof Error?i.message:String(i))),process.exitCode=1}return}console.error(E(n,`Unknown subcommand "${o}".`)),tl(t),process.exitCode=1}async function Aw(e){let t=ge(),n=[];if(n.push(K(e,"platform")),!t){n.push(` ${w(e,"attached:")} ${ke(e,"no")} \u2014 set platform_url + platform_token (omnish config add) or env`);let a=ar(),l=ir();return(a==="env"||l==="env")&&n.push(` ${w(e,"note:")} ${Q(e,"partial env override (need URL + token)")}`),n}let o=ar(),r=ir(),s=hu(),i=o==="env"||r==="env"||s==="env"?` ${w(e,"(env overrides config)")}`:"";n.push(` ${w(e,"attached:")} ${S(e,"yes")}${i}`),n.push(` ${w(e,"url:")} ${S(e,t.platformUrl)} ${Q(e,`[${o}]`)}`),n.push(` ${w(e,"token:")} ${S(e,Vn("platformToken",t.token))} ${Q(e,`[${r}]`)}`),t.deviceId&&n.push(` ${w(e,"device:")} ${S(e,t.deviceId)} ${Q(e,`[${s}]`)}`);try{let{fetchPlatformAccount:a}=await Promise.resolve().then(()=>(rs(),Ug)),l=await a(t);n.push(` ${w(e,"gatewayMode:")} ${S(e,l.gatewayMode)} ${Q(e,"(platform)")}`);let c=d=>{let m=l.connectors[d];return m?m.linked?S(e,"linked"):Q(e,m.status):ke(e,"idle")};n.push(` ${w(e,"whatsapp:")} ${c("whatsapp")}`),n.push(` ${w(e,"telegram:")} ${c("telegram")}`);let u=l.routing.onlineCount;n.push(` ${w(e,"routing:")} default=${l.routing.defaultDeviceId??Q(e,"(none)")} online=${u}`),t.deviceId&&l.routing.defaultDeviceId&&t.deviceId!==l.routing.defaultDeviceId&&n.push(` ${w(e,"warn:")} ${ke(e,"platform_device_id \u2260 platform defaultDeviceId \u2014 run omnish run or set default on dashboard")}`),u===0&&n.push(` ${w(e,"warn:")} ${ke(e,"no device online on platform")}`)}catch{n.push(` ${w(e,"account:")} ${Q(e,"(could not fetch /v1/me \u2014 check token and URL)")}`)}return n}rs();qt();function Ow(){let e=ge();return e||(console.error(E(process.stderr,"Set platform_url + platform_token (omnish config add) or OMNISH_PLATFORM_URL + OMNISH_TOKEN.")),process.exitCode=1,null)}function FE(e){let t=e.trim();return t?t.startsWith("wa:")?{kind:"wa",value:t.slice(3).trim()}:t.startsWith("tg:")?{kind:"tg",value:t.slice(3).trim()}:/^\+?\d{8,}$/.test(t.replace(/\s/g,""))?{kind:"wa",value:t}:/^\d+$/.test(t)?{kind:"tg",value:t}:null:null}function Iw(e){return e.replace(/\D/g,"")}async function Lw(){let e=Ow();if(!e)return;let t=await po(e),n=t.connectors.whatsapp,o=t.connectors.telegram;console.log(H(process.stdout,"Platform account")),console.log(` gatewayMode: ${t.gatewayMode}`),console.log(` whatsapp: ${n?.linked?"linked":n?.status??"idle"} (allowlist: ${t.allowFrom.length})`);let r=o?.tokenConfigured?"token saved":"no token";console.log(` telegram: ${o?.linked?"linked":o?.status??"idle"} (${r}, allowlist: ${t.telegramAllowFrom.length})`),console.log(` defaultDeviceId: ${t.defaultDeviceId??"\u2014"}`),console.log(` online devices: ${t.routing.onlineCount}`),t.allowFrom.length&&console.log(` allowFrom: ${t.allowFrom.map(s=>`+${s}`).join(", ")}`),t.telegramAllowFrom.length&&console.log(` telegramAllowFrom: ${t.telegramAllowFrom.join(", ")}`)}async function Nw(e){let t=Ow();if(!t)return;let n=(e[0]??"").toLowerCase();if(!n||n==="-h"||n==="--help"){console.log(["Usage:"," omnish platform allow list"," omnish platform allow add wa:+15551234567 tg:123456789"," omnish platform allow set --wa +1555,... --tg 123,...",""].join(`
612
- `));return}if(n==="list"){let o=await po(t);console.log(H(process.stdout,"Platform allowlists")),console.log(` WhatsApp (${o.allowFrom.length}): ${o.allowFrom.length?o.allowFrom.map(r=>`+${r}`).join(", "):"(empty \u2014 any sender)"}`),console.log(` Telegram (${o.telegramAllowFrom.length}): ${o.telegramAllowFrom.length?o.telegramAllowFrom.join(", "):"(empty \u2014 any sender)"}`);return}if(n==="add"){let o=await po(t),r=[...o.allowFrom],s=[...o.telegramAllowFrom];for(let a of e.slice(1)){let l=FE(a);if(!l){console.error(E(process.stderr,`Unrecognized entry: ${a}`)),process.exitCode=1;return}if(l.kind==="wa"){let c=Iw(l.value);c&&!r.includes(c)&&r.push(c)}else{let c=l.value.replace(/\D/g,"");c&&!s.includes(c)&&s.push(c)}}let i=await ka(t,{allowFrom:r,telegramAllowFrom:s});console.log(H(process.stdout,`Allowlists updated (${i.allowFrom.length} WhatsApp, ${i.telegramAllowFrom.length} Telegram).`));return}if(n==="set"){let o,r;for(let a=1;a<e.length;a++){let l=e[a];l==="--wa"&&e[a+1]?o=e[++a].split(",").map(c=>Iw(c.trim())).filter(Boolean):l==="--tg"&&e[a+1]&&(r=e[++a].split(",").map(c=>c.trim().replace(/^tg:/i,"").replace(/\D/g,"")).filter(Boolean))}if(o===void 0&&r===void 0){console.error(E(process.stderr,"Use --wa and/or --tg with comma-separated values.")),process.exitCode=1;return}let s={};o!==void 0&&(s.allowFrom=o),r!==void 0&&(s.telegramAllowFrom=r);let i=await ka(t,s);console.log(H(process.stdout,`Allowlists set (${i.allowFrom.length} WhatsApp, ${i.telegramAllowFrom.length} Telegram).`));return}console.error(E(process.stderr,`Unknown allow subcommand: ${e[0]}`)),process.exitCode=1}async function _w(e){let t="",n="",o="";for(let r=0;r<e.length;r++){let s=e[r];(s==="--url"||s==="-u")&&e[r+1]?t=e[++r].trim():(s==="--token"||s==="-t")&&e[r+1]?n=e[++r].trim():s==="--device-id"&&e[r+1]&&(o=e[++r].trim())}if(!t||!n){let r=td(e);for(let s of r)s.key==="platform_url"&&(t=s.value),s.key==="platform_token"&&(n=s.value),s.key==="platform_device_id"&&(o=s.value)}if(!t||!n){console.error(E(process.stderr,"Usage: omnish platform login --url <url> --token <token> [--device-id <id>]")),process.exitCode=1;return}Ao("tunnelRelayUrl",t.replace(/\/$/,"")),Ao("platformToken",n),o&&Ao("platformDeviceId",o),console.log(H(process.stdout,"Platform credentials saved to config.json.")),console.log(" Run: omnish platform status && omnish run")}import DE from"qrcode-terminal";qt();var WE=1e3,UE=120;function Fw(){let e=ge();return e||(console.error(E(process.stderr,"Set platform_url + platform_token (omnish config add) or OMNISH_PLATFORM_URL + OMNISH_TOKEN.")),process.exitCode=1,null)}function od(e){let t=e.replace(/\/$/,"");return/^https?:\/\//i.test(t)?t:`http://${t}`}async function Dw(e){let t=`${od(e.platformUrl)}/v1/connectors/whatsapp/status`,n=await fetch(t,{headers:{Authorization:`Bearer ${e.token}`}}),o=await n.json().catch(()=>({}));if(!n.ok)throw new Error(o.error||`HTTP ${n.status}`);return o}async function BE(e){let t=await fetch(`${od(e.platformUrl)}/v1/connectors/whatsapp/start`,{method:"POST",headers:{"content-type":"application/json",Authorization:`Bearer ${e.token}`},body:JSON.stringify({})}),n=await t.json().catch(()=>({}));if(!t.ok)throw new Error(n.error||`HTTP ${t.status}`);return n}function Ww(e){let t=process.stdout,n=w(t,"\xB7".repeat(42));console.log(Q(t,"Scan with WhatsApp \u2192 Linked devices")),console.log(n),DE.generate(e,{small:!0}),console.log(n)}var nl="",nd=!1;function jE(e){let t=e.status??"";return t==="qr"||t==="connecting"||t==="reconnecting"||t==="pairing_restart"}async function HE(e){for(let t=0;t<UE;t++){let n=await Dw(e);if(n.status==="linked"||n.linked)return n;if(n.status==="pairing_restart"&&!nd&&(nd=!0,console.log(w(process.stdout,"Finishing WhatsApp link after scan (server restart) \u2014 wait a few seconds\u2026"))),n.qr&&n.qr!==nl&&(nl=n.qr,Ww(n.qr)),!jE(n))throw new Error(n.error||n.statusMessage||`Unexpected status: ${n.status}`);await new Promise(o=>setTimeout(o,WE))}throw new Error("Timed out waiting for WhatsApp to link (scan the QR within ~2 minutes).")}async function Uw(){let e=Fw();if(!e)return;let t=await Dw(e);if(t.status==="linked"||t.linked){console.log(H(process.stdout,"WhatsApp already linked on the platform.")),t.linkedPhoneE164&&(console.log(` Phone: +${t.linkedPhoneE164}`),console.log(` Hint: omnish platform allow add wa:+${t.linkedPhoneE164}`));return}console.log(H(process.stdout,"Starting WhatsApp pairing on the platform\u2026")),nd=!1,nl="",t=await BE(e),t.qr&&(Ww(t.qr),nl=t.qr),t=await HE(e),console.log(H(process.stdout,"WhatsApp linked on the platform.")),t.linkedPhoneE164&&(console.log(` Phone: +${t.linkedPhoneE164}`),console.log(` Next: omnish platform allow add wa:+${t.linkedPhoneE164}`),console.log(" Then: omnish run"))}async function Bw(){let e=Fw();if(!e)return;let t=await fetch(`${od(e.platformUrl)}/v1/connectors/whatsapp/unlink`,{method:"POST",headers:{"content-type":"application/json",Authorization:`Bearer ${e.token}`},body:JSON.stringify({})}),n=await t.json().catch(()=>({}));if(!t.ok){console.error(E(process.stderr,n.error||`HTTP ${t.status}`)),process.exitCode=1;return}console.log(H(process.stdout,`WhatsApp unlinked (status: ${n.status??"idle"}).`))}import JE from"ws";function jw(e,t,n){return new Promise(o=>{let r=new JE(e,{headers:{Authorization:`Bearer ${t}`}}),s=setTimeout(()=>{r.terminate(),o({ok:!1,message:"timeout"})},n);r.once("open",()=>{clearTimeout(s),r.close(),o({ok:!0})}),r.once("unexpected-response",(i,a)=>{clearTimeout(s),o({ok:!1,status:a.statusCode,message:`HTTP ${a.statusCode} ${a.statusMessage}`})}),r.once("error",i=>{clearTimeout(s),o({ok:!1,message:String(i?.message??i)})})})}async function Hw(e,t,n=12e3){let o=e.replace(/\/$/,""),r=lr(o),s=await jw(r,t,n),i,a,l="";for(let c of va(o)){let u=await jw(c,t,n);if(u.ok)return i=new URL(c).pathname,{ok:!0,controlWsOk:s.ok,deviceWsOk:!0,deviceWsPath:i};a=u.status,l=u.message}return a===400?{ok:!1,controlWsOk:s.ok,deviceWsOk:!1,deviceWsStatus:400,error:"Device WebSocket returned HTTP 400 on /platform/device and could not use /control/device. Redeploy the latest relay image (adds /control/device), or route /platform/* to port 8788 in Caddy/EasyPanel."}:{ok:!1,controlWsOk:s.ok,deviceWsOk:!1,deviceWsStatus:a,error:`Device WebSocket failed: ${l}`}}var GE=400,qE=8*1024*1024,zE=2*1024*1024;function KE(e){let t=le,n=!1,o=!1;for(let r=0;r<e.length;r++){let s=e[r];s==="-h"||s==="--help"?o=!0:s==="--force"?n=!0:(s==="--auth-dir"||s==="--authDir")&&e[r+1]&&(t=rl.resolve(e[++r]))}return{authDir:t,force:n,help:o}}function YE(e){let t={},n=0,o=0;function r(s,i){let a;try{a=ol.readdirSync(i,{withFileTypes:!0})}catch{return}for(let l of a){let c=l.name;if(c==="."||c==="..")continue;let u=rl.join(i,c),m=(s?`${s}/${c}`:c).split(rl.sep).join("/");if(!l.isSymbolicLink()){if(l.isDirectory())r(m,u);else if(l.isFile()){let f=ol.readFileSync(u);if(f.length>zE)throw new Error(`file too large: ${m}`);if(n+=f.length,n>qE)throw new Error("total auth size exceeds limit (8 MiB)");if(o+=1,o>GE)throw new Error("too many files (max 400)");t[m]=f.toString("base64")}}}}return r("",e),t}function sl(e){console.log([he(e,"omnish platform"),w(e,"Attached mode: messengers on the hosted platform; shell on this machine (omnish run)."),"",K(e,"When to use"),w(e," Link WhatsApp/Telegram once on the platform dashboard. Run omnish run on laptops, servers, or containers without local Baileys."),"",K(e,"Prerequisites"),w(e," 1. Platform account token (signup/login on relay dashboard)."),w(e," 2. Messengers linked on the platform (dashboard QR or import-whatsapp)."),w(e," 3. allowFrom / telegramAllowFrom on dashboard Allowlists (Telegram: DM bot /id for your user id)."),"",K(e,"Configure"),` ${S(e,"omnish config add platform_url <url> platform_token <token>")}`,w(e," Optional: platform_device_id <id>"),w(e," Env overrides: OMNISH_PLATFORM_URL, OMNISH_TOKEN, OMNISH_DEVICE_ID"),w(e," (aliases: OMNISH_COMM_LAYER_URL, OMNISH_DEVICE_TOKEN, OMNISH_TUNNEL_*)"),"",K(e,"Workflow"),` ${S(e,"omnish platform login --url <url> --token <t>")} ${w(e,"\u2014 save credentials (or config add)")}`,` ${S(e,"omnish platform status")} ${w(e,"\u2014 account, connectors, allowlists")}`,` ${S(e,"omnish platform allow add tg:<id>")} ${w(e,"\u2014 or dashboard + bot /id")}`,` ${S(e,"omnish platform probe")} ${w(e,"\u2014 test WebSocket routing")}`,` ${S(e,"omnish run")} ${w(e,"\u2014 attach device; expect 'omnish attached to platform'")}`,"",K(e,"Subcommands"),S(e,"omnish platform login --url <url> --token <token> [--device-id <id>]"),w(e," Save platform URL and token to config.json."),"",S(e,"omnish platform status"),w(e," Show gatewayMode, connector status, allowlists, online devices."),"",S(e,"omnish platform allow list|add|set"),w(e," Manage platform allowlists (PUT /v1/me/allowlists)."),"",S(e,"omnish platform probe"),w(e," Test control-plane WebSocket routing (diagnose attached omnish run 400 errors)."),"",S(e,"omnish platform link-whatsapp"),w(e," Pair WhatsApp on the platform (terminal QR + poll until linked)."),"",S(e,"omnish platform unlink-whatsapp"),w(e," Remove platform WhatsApp session and auth files."),"",S(e,"omnish platform import-whatsapp [--auth-dir <path>] [--force]"),w(e," Upload local Baileys auth (after omnish link on this host). Use link-whatsapp or dashboard QR when possible."),w(e," Stop local omnish run before import to avoid WhatsApp session conflicts."),"",w(e,"Standalone omnish link on this host is not used for platform WhatsApp when attached."),w(e,"Docs: docs/guides/platform-reference.md (full API/CLI)"),w(e," docs/guides/platform-attached-mode.md (walkthrough)"),""].join(`
613
- `))}async function VE(e){let{authDir:t,force:n,help:o}=KE(e);if(o){console.log(["Usage: omnish platform import-whatsapp [--auth-dir <path>] [--force]","","Requires OMNISH_PLATFORM_URL and OMNISH_TOKEN.","Default auth directory: ~/.omnish/auth (Baileys useMultiFileAuthState).","Stop `omnish run` on this machine before importing to avoid WhatsApp session conflicts.",""].join(`
614
- `));return}let r=ge();if(!r){console.error(E(process.stderr,"Set OMNISH_PLATFORM_URL (control plane URL) and OMNISH_TOKEN (from dashboard signup/login).")),process.exitCode=1;return}if(!ol.existsSync(t)){console.error(E(process.stderr,`Auth directory not found: ${t}`)),process.exitCode=1;return}se();let s=rl.join(t,"creds.json");if(!ol.existsSync(s)){console.error(E(process.stderr,`No creds.json in ${t} \u2014 link WhatsApp locally first (omnish link).`)),process.exitCode=1;return}let i;try{i=YE(t)}catch(f){console.error(E(process.stderr,String(f))),process.exitCode=1;return}if(Object.keys(i).length===0){console.error(E(process.stderr,"No files collected (empty auth directory?).")),process.exitCode=1;return}let a=r.platformUrl.replace(/\/$/,""),c=`${/^https?:\/\//i.test(a)?a:`http://${a}`}/v1/connectors/whatsapp/import`,u=await fetch(c,{method:"POST",headers:{"content-type":"application/json",Authorization:`Bearer ${r.token}`},body:JSON.stringify({files:i,force:n})}),d=await u.json().catch(()=>({}));if(!u.ok){console.error(E(process.stderr,d.error||`HTTP ${u.status}`)),process.exitCode=1;return}let m=d.qr?" QR emitted \u2014 open the platform dashboard if you need to scan.":"";console.log(H(process.stdout,`Uploaded Baileys auth. Platform connector status: ${d.status??"unknown"}.${m}`))}async function QE(){let e=process.stdout,t=process.stderr,n=ge();if(!n){console.error(E(t,"Set platform_url + platform_token (omnish config add) or OMNISH_PLATFORM_URL + OMNISH_TOKEN.")),process.exitCode=1;return}console.log(`${he(e,"Platform probe")} ${w(e,n.platformUrl)}`);let o=await Hw(n.platformUrl,n.token);console.log(` ${w(e,"wss /control:")} ${o.controlWsOk?S(e,"ok"):ke(e,"fail")}`);for(let r of["/platform/device","/control/device"]){if(o.ok&&o.deviceWsPath&&o.deviceWsPath!==r){console.log(` ${w(e,`wss ${r}:`)} ${Q(e,"skipped (fallback used)")}`);continue}if(o.ok&&o.deviceWsPath===r){console.log(` ${w(e,`wss ${r}:`)} ${S(e,"ok")}`);continue}!o.ok&&r==="/platform/device"&&console.log(` ${w(e,`wss ${r}:`)} ${ke(e,"fail")}${o.deviceWsStatus?` (HTTP ${o.deviceWsStatus})`:""}`),!o.ok&&r==="/control/device"&&console.log(` ${w(e,`wss ${r}:`)} ${ke(e,"fail")}`)}if(o.ok){o.deviceWsPath==="/control/device"&&console.log(w(e," Using /control/device fallback (/platform/* not on control port \u2014 redeploy Caddy when convenient).")),console.log(H(e,"Attached omnish run should be able to connect."));return}console.error(E(t,o.error??"Platform probe failed.")),o.controlWsOk&&!o.deviceWsOk&&console.error(E(t,"Fix: redeploy the latest tunnel-relay image (enables wss /control/device), update omnish CLI, then retry. Also route /platform/* to 8788 when you can (Caddyfile.standalone).")),process.exitCode=1}async function Jw(e){let t=(e[0]??"").toLowerCase();if(!t||t==="-h"||t==="--help"){sl(process.stdout);return}if(t==="login"){await _w(e.slice(1));return}if(t==="status"){await Lw();return}if(t==="allow"){await Nw(e.slice(1));return}if(t==="probe"){await QE();return}if(t==="link-whatsapp"){await Uw();return}if(t==="unlink-whatsapp"){await Bw();return}if(t==="import-whatsapp"){await VE(e.slice(1));return}console.error(E(process.stderr,`Unknown platform subcommand: ${e[0]}`)),sl(process.stderr),process.exitCode=1}j();import zw from"node:crypto";import go from"node:fs";import Kw from"node:path";function XE(){return zw.randomBytes(24).toString("hex")}function Gw(){return zw.randomBytes(32).toString("hex")}function qw(e){try{let t=go.readFileSync(e,"utf8"),n=JSON.parse(t);if(typeof n.token=="string"&&n.token.length>=16&&typeof n.secret=="string"&&n.secret.length>=16)return{token:n.token,secret:n.secret}}catch{}return null}function ZE(){let e=qw(At);if(e)return e;if(e=qw(br),e){D(Kw.dirname(At)),go.writeFileSync(At,JSON.stringify(e,null,2)+`
615
- `,{mode:384});try{go.unlinkSync(br)}catch{}return e}return null}function Yw(e){D(Kw.dirname(At));let t=ZE(),n=(e??"").trim();if(n){let r={token:n,secret:t?.secret??Gw()};go.writeFileSync(At,JSON.stringify(r,null,2)+`
616
- `,{mode:384});try{go.existsSync(br)&&go.unlinkSync(br)}catch{}return r}if(t)return t;let o={token:XE(),secret:Gw()};return go.writeFileSync(At,JSON.stringify(o,null,2)+`
617
- `,{mode:384}),o}ue();import jn from"node:fs";import sP from"node:http";import je from"node:path";import Mt from"node:process";import{fileURLToPath as iP}from"node:url";import aP from"node:os";dt();j();import rd from"node:crypto";var sd="omnish_cfg_sess",Vw=7*24*60*60*1e3;function id(e){let t=Date.now()+Vw,n=Buffer.from(JSON.stringify({exp:t}),"utf8").toString("base64url"),o=rd.createHmac("sha256",e).update(n).digest("hex");return`${n}.${o}`}function Qw(e,t){let n=eP(t??"")[sd];if(!n||!n.includes("."))return!1;let o=n.lastIndexOf("."),r=n.slice(0,o),s=n.slice(o+1),i=rd.createHmac("sha256",e).update(r).digest("hex");try{let a=Buffer.from(s,"hex"),l=Buffer.from(i,"hex");if(a.length!==l.length||!rd.timingSafeEqual(a,l))return!1}catch{return!1}try{let a=JSON.parse(Buffer.from(r,"base64url").toString("utf8"));return!(typeof a.exp!="number"||a.exp<Date.now())}catch{return!1}}function ad(e){let t=Math.floor(Vw/1e3);return`${sd}=${e}; HttpOnly; Path=/; SameSite=Lax; Max-Age=${t}`}function Xw(){return`${sd}=; HttpOnly; Path=/; SameSite=Lax; Max-Age=0`}function eP(e){let t={};for(let n of e.split(";")){let o=n.indexOf("=");if(o===-1)continue;let r=n.slice(0,o).trim(),s=n.slice(o+1).trim();r&&(t[r]=decodeURIComponent(s))}return t}j();import ld from"node:fs";import il from"node:process";function Zw(e){try{return il.kill(e,0),!0}catch{return!1}}function eb(e){let t=Date.now()+e;for(;Date.now()<t;);}function tP(){try{let e=ld.readFileSync(ks,"utf8"),t=JSON.parse(e);if(!t||typeof t!="object")return null;let n=t,o=typeof n.pid=="number"?n.pid:Number(n.pid),r=typeof n.port=="number"?n.port:Number(n.port),s=typeof n.host=="string"?n.host:"",i=typeof n.startedAt=="string"?n.startedAt:"";return!Number.isFinite(o)||o<=0||!Number.isFinite(r)||r<=0||r>65535?null:{pid:o,port:r,host:s,startedAt:i}}catch{return null}}function tb(e){ld.writeFileSync(ks,`${JSON.stringify(e)}
618
- `,{mode:384})}function gs(){try{ld.unlinkSync(ks)}catch{}}function nb(e){let t=tP();if(t&&t.port===e&&t.pid!==il.pid){if(!Zw(t.pid)){gs();return}try{il.kill(t.pid,"SIGTERM")}catch{}if(eb(350),Zw(t.pid)){try{il.kill(t.pid,"SIGKILL")}catch{}eb(100)}gs()}}ue();j();import cd from"node:fs";import nP from"node:process";function oP(e){try{return nP.kill(e,0),!0}catch{return!1}}function rP(){try{let e=cd.readFileSync(de,"utf8").trim(),t=Number.parseInt(e,10);return Number.isFinite(t)&&t>0?t:null}catch{return null}}function al(){let e=rP();return e===null?!1:oP(e)}var ud=class{busy=!1;subscribers=new Set;abort=null;currentSock=null;subscribe(t){return this.subscribers.add(t),()=>{this.subscribers.delete(t)}}emit(t){for(let n of this.subscribers)try{n(t)}catch{}}requestCancel(){this.abort?.abort(),this.currentSock&&(nr(this.currentSock),this.currentSock=null)}beginPairing(t){if(this.busy)throw new Error("Pairing already in progress.");if(al())throw new Error("Gateway appears to be running (gateway.pid). Stop `omnish run` or your service before pairing from the browser.");if(!t.force&&St())throw new Error("WhatsApp session already linked. Use \u201CReplace session\u201D or run `omnish link --force` from the CLI.");se(),t.force&&(cd.rmSync(le,{recursive:!0,force:!0}),cd.mkdirSync(le,{recursive:!0,mode:448})),this.busy=!0,this.abort=new AbortController;let n=this.abort.signal;(async()=>{try{await Lg({authDir:le,verbose:!1,printQr:!1,signal:n,onQr:o=>this.emit({type:"qr",payload:o}),onRestart515:()=>this.emit({type:"restart"}),onSocketReady:o=>{this.currentSock=o},onSocketClosed:()=>{this.currentSock=null}}),this.emit({type:"open"}),this.emit({type:"done",ok:!0})}catch(o){let r=o instanceof Error?o.message:String(o);r==="Pairing cancelled."?this.emit({type:"error",message:"Cancelled."}):this.emit({type:"error",message:r}),this.emit({type:"done",ok:!1})}finally{this.busy=!1,this.abort=null,this.currentSock=null}})()}},ys=new ud;var fe="application/json; charset=utf-8",ws=null;function lP(){gs();let e=ws;ws=null,e?e.close(()=>{Mt.exit(0)}):Mt.exit(0),setTimeout(()=>Mt.exit(0),4e3).unref()}function cP(){let e=Mt.env.OMNISH_CONFIG_UI_STATIC?.trim(),t=Mt.env.OMNISH_UI_STATIC?.trim()||e;if(t&&jn.existsSync(je.join(t,"index.html")))return t;let n=je.dirname(iP(import.meta.url)),o=je.join(n,"ui");if(jn.existsSync(je.join(o,"index.html")))return o;let r=je.join(n,"..","..","dist","ui");if(jn.existsSync(je.join(r,"index.html")))return r;let s=je.join(Mt.cwd(),"dist","ui");if(jn.existsSync(je.join(s,"index.html")))return s;throw new Error("omnish ui static files not found (expected dist/ui/index.html). Run `pnpm build`.")}function uP(e){let t=je.extname(e).toLowerCase();return{".html":"text/html; charset=utf-8",".js":"text/javascript; charset=utf-8",".css":"text/css; charset=utf-8",".svg":"image/svg+xml",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".webp":"image/webp",".ico":"image/x-icon",".woff2":"font/woff2",".json":"application/json; charset=utf-8"}[t]??"application/octet-stream"}function dP(e,t){let o=decodeURIComponent(t.split("?")[0]??"").replace(/^\/+/,""),r=je.normalize(je.join(e,o));return!r.startsWith(je.normalize(e+je.sep))&&r!==je.normalize(e)?null:r}async function dd(e,t=512e3){let n=[],o=0;for await(let s of e){if(o+=s.length,o>t)throw new Error("Body too large.");n.push(s)}if(n.length===0)return;let r=Buffer.concat(n).toString("utf8");return JSON.parse(r)}function pP(e){return xn.includes(e)}function pd(e){let t=(e.telegramBotToken??"").trim(),n=t.length===0?"":t.length<=8?"(set)":`${t.slice(0,4)}\u2026${t.slice(-4)}`;return{...e,telegramBotToken:n,telegramBotTokenConfigured:!!Ie(e),telegramBotTokenEnvOverride:typeof Mt.env.TELEGRAM_BOT_TOKEN=="string"&&Mt.env.TELEGRAM_BOT_TOKEN.trim().length>0}}function mP(){let e=v();return{version:gt(),dataDir:W,configPath:U,waAuthDir:le,whatsappLinked:St(),gatewayPidHint:jn.existsSync(je.join(W,"gateway.pid")),gatewayRunning:al(),gatewayLogFile:He,gatewayMode:e.gatewayMode,telegramBotTokenMasked:Ie(e).length===0?"":pd(e).telegramBotToken,telegramBotTokenEnvOverride:typeof Mt.env.TELEGRAM_BOT_TOKEN=="string"&&Mt.env.TELEGRAM_BOT_TOKEN.trim().length>0}}function fP(e,t){if(!Array.isArray(e)||!Array.isArray(t))throw new Error("allowFrom and telegramAllowFrom must be arrays of strings.");let n=e.map(a=>String(a).trim()).filter(Boolean),o=t.map(a=>String(a).trim()).filter(Boolean),r=Ts(n).filter(a=>a!=="*"),s=[];for(let a of o){let l=Fe(a);if(!l)throw new Error(`Invalid Telegram allow entry: ${a}`);s.push(l)}let i=v();i.allowFrom=r.sort(),i.telegramAllowFrom=[...new Set(s)].sort(),Ve(i)}function hP(e){return typeof e=="string"?e:typeof e=="boolean"||typeof e=="number"?String(e):JSON.stringify(e)}async function ob(e){let t=cP(),{meta:n}=e;nb(e.port);let o=sP.createServer(async(r,s)=>{try{let i=new URL(r.url??"/",`http://${r.headers.host??"localhost"}`),a=i.pathname;if(r.method==="GET"&&a==="/"){let m=i.searchParams.get("token")?.trim();if(m&&m===n.token){let f=id(n.secret);s.statusCode=302,s.setHeader("Location","/"),s.setHeader("Set-Cookie",ad(f)),s.end();return}}if(a.startsWith("/api/")){let m=r.headers.cookie,f=Qw(n.secret,m);if(r.method==="POST"&&a==="/api/session"){let h=await dd(r),g=typeof h?.token=="string"?h.token.trim():"";if(!g||g!==n.token){s.statusCode=401,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:"Invalid token."}));return}let y=id(n.secret);s.statusCode=200,s.setHeader("Content-Type",fe),s.setHeader("Set-Cookie",ad(y)),s.end(JSON.stringify({ok:!0}));return}if(r.method==="GET"&&a==="/api/me"){s.statusCode=f?200:401,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:f}));return}if(!f){s.statusCode=401,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:"Unauthorized."}));return}if(r.method==="GET"&&a==="/api/status"){s.statusCode=200,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!0,...mP()}));return}if(r.method==="GET"&&a==="/api/config"){s.statusCode=200,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!0,config:pd(v())}));return}if(r.method==="PUT"&&a==="/api/config"){let h=await dd(r);if(!h||typeof h!="object"){s.statusCode=400,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:"Expected JSON object."}));return}let g=h;for(let[y,k]of Object.entries(g))if(k!==void 0&&!(y==="allowFrom"||y==="telegramAllowFrom")){if(y==="telegramBotToken"){let b=typeof k=="string"?k.trim():"";if(!b||b==="(set)"||b.includes("\u2026"))continue;if(!Ot(b))throw new Error("Invalid Telegram bot token format.");Vs("telegramBotToken",b);continue}if(!pP(y))throw new Error(`Unknown or unsupported config key: ${y}`);Vs(y,hP(k))}if("allowFrom"in g||"telegramAllowFrom"in g){let y=v();fP("allowFrom"in g?g.allowFrom:y.allowFrom,"telegramAllowFrom"in g?g.telegramAllowFrom:y.telegramAllowFrom)}s.statusCode=200,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!0,config:pd(v())}));return}if(r.method==="POST"&&a==="/api/logout"){s.statusCode=200,s.setHeader("Content-Type",fe),s.setHeader("Set-Cookie",Xw()),s.end(JSON.stringify({ok:!0}));return}if(r.method==="POST"&&a==="/api/shutdown"){s.statusCode=200,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!0})),setImmediate(()=>{ys.requestCancel(),lP()});return}if(r.method==="POST"&&a==="/api/gateway/start"){if(al()){s.statusCode=409,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:"Gateway already appears to be running (pidfile + live process). Stop it first if you need to restart."}));return}let h=Za();if(!h.ok){s.statusCode=400,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:h.message}));return}let g=He,y=ha(g);if(!y.ok){s.statusCode=500,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:y.message}));return}s.statusCode=200,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!0,pid:y.pid,logFile:g}));return}if(r.method==="POST"&&a==="/api/gateway/stop"){let h=ga();switch(h.outcome){case"no_pidfile":s.statusCode=400,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:"No gateway pidfile \u2014 background gateway does not appear to be running."}));return;case"invalid_pidfile":s.statusCode=400,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:"Invalid pidfile (removed)."}));return;case"stale_cleaned":s.statusCode=200,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!0,stale:!0,pid:h.pid,message:"Process was not running; stale pidfile removed."}));return;case"sent_signal":s.statusCode=200,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!0,pid:h.pid}));return;case"taskkill_ok":s.statusCode=200,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!0,pid:h.pid,taskkill:!0}));return;case"failed":s.statusCode=500,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:h.message}));return}}if(r.method==="GET"&&a==="/api/wa/link/events"){s.writeHead(200,{"Content-Type":"text/event-stream; charset=utf-8","Cache-Control":"no-store",Connection:"keep-alive"}),s.write(`: connected
610
+ `)}`}:{ok:!0}}qt();j();qt();import rl from"node:fs";import sl from"node:path";ue();import{spawn as BE}from"node:child_process";import Mw from"node:fs";import{stdout as jE}from"node:process";qt();j();Kn();var od=["tunnelRelayUrl","platformToken","platformDeviceId"],Aw=[{label:"Platform (attached gateway + tunnel)",keys:["tunnelRelayUrl","platformToken","platformDeviceId"]},{label:"Tunnel",keys:["tunnelEnabled","tunnelMaxActive"]},{label:"Gateway",keys:["gatewayMode","commandPrefix","shell"]},{label:"Cluster",keys:["clusterEnabled","clusterRole","clusterLabel","clusterSenderBindings"]},{label:"Telegram",keys:["telegramBotToken"]},{label:"Apps",keys:["appsCols","appsRows","appsFlushMs","appsMinIntervalMs","appsMaxFlushBytes","appsMaxSessions","appsMaxSessionsTotal","appsMaxWaChars","appsLogTailLines","appsSubmitDelayMs","appsClearInput","appsClearInputDelayMs","appsClearInputSequence","appsSkipClearOnPasswordPrompt","appsPasswordPromptHint"]},{label:"Files",keys:["fileSendMaxBytes","fileReceiveMaxBytes","fileInboxSubdir","fileReceiveRootMode","fileReceiveRootPath"]},{label:"Recipes",keys:["recipesAllowDangerousBuiltins","recipesMaxTaskChars","recipesMacroDefaultCommand","recipesRunAttach"]},{label:"Sync / jobs",keys:["syncTimeoutMs","syncMaxBytes","jobLogTailLines"]},{label:"Service / updates",keys:["serviceInstallFromChat","updateCheckEnabled","updateCheckIntervalMs","updateCheckPackageName","updateInfoUrl"]},{label:"Chat LLM fallback",keys:["chatLlmFallbackEnabled","chatLlmShellCommand","chatLlmTimeoutMs","chatLlmMaxInputChars","chatLlmMaxOutputChars","chatLlmNeedsTty","chatLlmWorkDir"]}];function nl(e){let t=[K(e,"omnish config"),w(e,"Manage ~/.omnish/config.json (env vars still override for platform credentials)."),"",K(e,"Usage:"),` ${S(e,"omnish config add <key> <value> [key value ...]")}`,` ${S(e,"omnish config show <key>[,<key>|*]")}`,` ${S(e,"omnish config edit")}`,` ${S(e,"omnish config edit <key> <value>")}`,` ${S(e,"omnish config delete <key>[,<key>]")}`,"",w(e,"Platform aliases: platform_url \u2192 tunnelRelayUrl, platform_token \u2192 platformToken,"),w(e," platform_device_id \u2192 platformDeviceId"),` ${S(e,"omnish config show platform")} ${w(e,"\u2014 attached-mode diagnostics (URL, token, devices)")}`,w(e," Full setup: omnish help platform"),w(e,"add sets/overwrites scalar keys (not array append). show * lists all keys."),""];console.log(t.join(`
611
+ `))}function Iw(e){return e.split(/[,\s]+/).map(t=>t.trim()).filter(Boolean)}function rd(e){if(e.length===0)return[];let t=e.join(" ");if(t.includes(",")){let n=[];for(let o of t.split(",")){let r=o.trim();if(!r)continue;let s=r.split(/\s+/);if(s.length<2)throw new Error(`Invalid pair "${r}" \u2014 expected "key value"`);n.push({key:s[0],value:s.slice(1).join(" ")})}return n}if(e.length===2)return[{key:e[0],value:e[1]}];if(e.length%2===0){let n=[];for(let o=0;o<e.length;o+=2)n.push({key:e[o],value:e[o+1]});return n}return[{key:e[0],value:e.slice(1).join(" ")}]}function tl(e,t){if(e==="clusterSenderBindings")return JSON.stringify(t.clusterSenderBindings);let n=String(t[e]??"");return e==="platformToken"||e==="telegramBotToken"?Vn(e,n):n}function Ow(e){return e==="tunnelRelayUrl"?{value:os(),source:ar()}:e==="platformToken"?{value:Vn(e,gu()),source:ir()}:e==="platformDeviceId"?{value:(yu()??"")||"(empty)",source:wu()}:null}function HE(e){if(e!=="platformToken"&&e!=="tunnelRelayUrl")return;let n=v().platformToken.trim()||Zt()?.token?.trim()||"";n&&Ft({token:n,relayUrl:os()})}async function JE(){let e=process.env.VISUAL?.trim()||process.env.EDITOR?.trim()||(process.platform==="win32"?"notepad":"nano"),t=Mw.readFileSync(U,"utf8");await new Promise((n,o)=>{let r=BE(e,[U],{stdio:"inherit"});r.on("error",o),r.on("exit",s=>{s===0?n():o(new Error(`Editor exited with code ${s??1}`))})});try{v()}catch(n){throw Mw.writeFileSync(U,t,{mode:384}),new Error(`Invalid config after edit: ${n instanceof Error?n.message:String(n)}`)}}async function Lw(e){let t=jE,n=process.stderr,o=(e[0]??"").trim().toLowerCase(),r=e.slice(1);if(!o||o==="help"||o==="--help"||o==="-h"){nl(t);return}if(o==="add"||o==="edit"){try{if(o==="edit"&&r.length===0){await JE(),console.log(H(t,`Updated ${U}`));return}let s=rd(r);if(s.length===0){console.error(P(n,"Expected at least one key/value pair.")),process.exitCode=1;return}for(let{key:i,value:a}of s){let l=Qs(i);if(!l){console.error(P(n,`Unknown key "${i}". Try: ${fm().slice(0,8).join(", ")}\u2026 (omnish config show *)`)),process.exitCode=1;return}Ao(l,a),HE(l);let c=v(),u=l==="tunnelRelayUrl"?c.tunnelRelayUrl:od.includes(l)?Vn(l,String(c[l]??"")):tl(l,c);console.log(H(t,`${i} \u2192 ${u}`))}console.log(H(t,U))}catch(s){console.error(P(n,s instanceof Error?s.message:String(s))),process.exitCode=1}return}if(o==="show"){let s=r.join(" ").trim()||"*";if(s==="*"){let l=v(),c=[K(t,"Config"),w(t,U),""];for(let m of Aw){c.push(K(t,m.label));for(let f of m.keys){let h=tl(f,l),g=Ow(f);g&&od.includes(f)?c.push(` ${S(t,f)}: ${h} ${w(t,`(effective: ${g.value}, source: ${g.source})`)}`):c.push(` ${S(t,f)}: ${h}`)}c.push("")}let u=new Set(Aw.flatMap(m=>m.keys)),d=Gl.filter(m=>!u.has(m));if(d.length>0){c.push(K(t,"Other"));for(let m of d)c.push(` ${S(t,m)}: ${tl(m,l)}`)}console.log(c.join(`
612
+ `));return}let i=Iw(s),a=v();for(let l of i){let c=Qs(l);if(!c){console.error(P(n,`Unknown key "${l}".`)),process.exitCode=1;return}let u=tl(c,a),d=Ow(c);d&&od.includes(c)?console.log(`${l}: ${u} (effective: ${d.value}, source: ${d.source})`):console.log(`${l}: ${u}`)}return}if(o==="delete"){let s=r.join(" ").trim();if(!s){console.error(P(n,"Expected at least one key to delete.")),process.exitCode=1;return}if(s==="*"){console.error(P(n,'Refusing "delete *". List keys explicitly.')),process.exitCode=1;return}try{for(let i of Iw(s)){let a=Qs(i);if(!a){console.error(P(n,`Unknown key "${i}".`)),process.exitCode=1;return}hm(a),console.log(H(t,`cleared ${i}`))}console.log(H(t,U))}catch(i){console.error(P(n,i instanceof Error?i.message:String(i))),process.exitCode=1}return}console.error(P(n,`Unknown subcommand "${o}".`)),nl(t),process.exitCode=1}async function Nw(e){let t=ge(),n=[];if(n.push(K(e,"platform")),!t){n.push(` ${w(e,"attached:")} ${ke(e,"no")} \u2014 set platform_url + platform_token (omnish config add) or env`);let a=ar(),l=ir();return(a==="env"||l==="env")&&n.push(` ${w(e,"note:")} ${Q(e,"partial env override (need URL + token)")}`),n}let o=ar(),r=ir(),s=wu(),i=o==="env"||r==="env"||s==="env"?` ${w(e,"(env overrides config)")}`:"";n.push(` ${w(e,"attached:")} ${S(e,"yes")}${i}`),n.push(` ${w(e,"url:")} ${S(e,t.platformUrl)} ${Q(e,`[${o}]`)}`),n.push(` ${w(e,"token:")} ${S(e,Vn("platformToken",t.token))} ${Q(e,`[${r}]`)}`),t.deviceId&&n.push(` ${w(e,"device:")} ${S(e,t.deviceId)} ${Q(e,`[${s}]`)}`);try{let{fetchPlatformAccount:a}=await Promise.resolve().then(()=>(rs(),Jg)),l=await a(t);n.push(` ${w(e,"gatewayMode:")} ${S(e,l.gatewayMode)} ${Q(e,"(platform)")}`);let c=d=>{let m=l.connectors[d];return m?m.linked?S(e,"linked"):Q(e,m.status):ke(e,"idle")};n.push(` ${w(e,"whatsapp:")} ${c("whatsapp")}`),n.push(` ${w(e,"telegram:")} ${c("telegram")}`);let u=l.routing.onlineCount;n.push(` ${w(e,"routing:")} default=${l.routing.defaultDeviceId??Q(e,"(none)")} online=${u}`),t.deviceId&&l.routing.defaultDeviceId&&t.deviceId!==l.routing.defaultDeviceId&&n.push(` ${w(e,"warn:")} ${ke(e,"platform_device_id \u2260 platform defaultDeviceId \u2014 run omnish run or set default on dashboard")}`),u===0&&n.push(` ${w(e,"warn:")} ${ke(e,"no device online on platform")}`)}catch{n.push(` ${w(e,"account:")} ${Q(e,"(could not fetch /v1/me \u2014 check token and URL)")}`)}return n}rs();qt();function Fw(){let e=ge();return e||(console.error(P(process.stderr,"Set platform_url + platform_token (omnish config add) or OMNISH_PLATFORM_URL + OMNISH_TOKEN.")),process.exitCode=1,null)}function GE(e){let t=e.trim();return t?t.startsWith("wa:")?{kind:"wa",value:t.slice(3).trim()}:t.startsWith("tg:")?{kind:"tg",value:t.slice(3).trim()}:/^\+?\d{8,}$/.test(t.replace(/\s/g,""))?{kind:"wa",value:t}:/^\d+$/.test(t)?{kind:"tg",value:t}:null:null}function _w(e){return e.replace(/\D/g,"")}async function Dw(){let e=Fw();if(!e)return;let t=await po(e),n=t.connectors.whatsapp,o=t.connectors.telegram;console.log(H(process.stdout,"Platform account")),console.log(` gatewayMode: ${t.gatewayMode}`),console.log(` whatsapp: ${n?.linked?"linked":n?.status??"idle"} (allowlist: ${t.allowFrom.length})`);let r=o?.tokenConfigured?"token saved":"no token";console.log(` telegram: ${o?.linked?"linked":o?.status??"idle"} (${r}, allowlist: ${t.telegramAllowFrom.length})`),console.log(` defaultDeviceId: ${t.defaultDeviceId??"\u2014"}`),console.log(` online devices: ${t.routing.onlineCount}`),t.allowFrom.length&&console.log(` allowFrom: ${t.allowFrom.map(s=>`+${s}`).join(", ")}`),t.telegramAllowFrom.length&&console.log(` telegramAllowFrom: ${t.telegramAllowFrom.join(", ")}`)}async function Ww(e){let t=Fw();if(!t)return;let n=(e[0]??"").toLowerCase();if(!n||n==="-h"||n==="--help"){console.log(["Usage:"," omnish platform allow list"," omnish platform allow add wa:+15551234567 tg:123456789"," omnish platform allow set --wa +1555,... --tg 123,...",""].join(`
613
+ `));return}if(n==="list"){let o=await po(t);console.log(H(process.stdout,"Platform allowlists")),console.log(` WhatsApp (${o.allowFrom.length}): ${o.allowFrom.length?o.allowFrom.map(r=>`+${r}`).join(", "):"(empty \u2014 any sender)"}`),console.log(` Telegram (${o.telegramAllowFrom.length}): ${o.telegramAllowFrom.length?o.telegramAllowFrom.join(", "):"(empty \u2014 any sender)"}`);return}if(n==="add"){let o=await po(t),r=[...o.allowFrom],s=[...o.telegramAllowFrom];for(let a of e.slice(1)){let l=GE(a);if(!l){console.error(P(process.stderr,`Unrecognized entry: ${a}`)),process.exitCode=1;return}if(l.kind==="wa"){let c=_w(l.value);c&&!r.includes(c)&&r.push(c)}else{let c=l.value.replace(/\D/g,"");c&&!s.includes(c)&&s.push(c)}}let i=await Sa(t,{allowFrom:r,telegramAllowFrom:s});console.log(H(process.stdout,`Allowlists updated (${i.allowFrom.length} WhatsApp, ${i.telegramAllowFrom.length} Telegram).`));return}if(n==="set"){let o,r;for(let a=1;a<e.length;a++){let l=e[a];l==="--wa"&&e[a+1]?o=e[++a].split(",").map(c=>_w(c.trim())).filter(Boolean):l==="--tg"&&e[a+1]&&(r=e[++a].split(",").map(c=>c.trim().replace(/^tg:/i,"").replace(/\D/g,"")).filter(Boolean))}if(o===void 0&&r===void 0){console.error(P(process.stderr,"Use --wa and/or --tg with comma-separated values.")),process.exitCode=1;return}let s={};o!==void 0&&(s.allowFrom=o),r!==void 0&&(s.telegramAllowFrom=r);let i=await Sa(t,s);console.log(H(process.stdout,`Allowlists set (${i.allowFrom.length} WhatsApp, ${i.telegramAllowFrom.length} Telegram).`));return}console.error(P(process.stderr,`Unknown allow subcommand: ${e[0]}`)),process.exitCode=1}async function Uw(e){let t="",n="",o="";for(let r=0;r<e.length;r++){let s=e[r];(s==="--url"||s==="-u")&&e[r+1]?t=e[++r].trim():(s==="--token"||s==="-t")&&e[r+1]?n=e[++r].trim():s==="--device-id"&&e[r+1]&&(o=e[++r].trim())}if(!t||!n){let r=rd(e);for(let s of r)s.key==="platform_url"&&(t=s.value),s.key==="platform_token"&&(n=s.value),s.key==="platform_device_id"&&(o=s.value)}if(!t||!n){console.error(P(process.stderr,"Usage: omnish platform login --url <url> --token <token> [--device-id <id>]")),process.exitCode=1;return}Ao("tunnelRelayUrl",t.replace(/\/$/,"")),Ao("platformToken",n),o&&Ao("platformDeviceId",o),console.log(H(process.stdout,"Platform credentials saved to config.json.")),console.log(" Run: omnish platform status && omnish run")}import qE from"qrcode-terminal";qt();var zE=1e3,KE=120;function Bw(){let e=ge();return e||(console.error(P(process.stderr,"Set platform_url + platform_token (omnish config add) or OMNISH_PLATFORM_URL + OMNISH_TOKEN.")),process.exitCode=1,null)}function id(e){let t=e.replace(/\/$/,"");return/^https?:\/\//i.test(t)?t:`http://${t}`}async function jw(e){let t=`${id(e.platformUrl)}/v1/connectors/whatsapp/status`,n=await fetch(t,{headers:{Authorization:`Bearer ${e.token}`}}),o=await n.json().catch(()=>({}));if(!n.ok)throw new Error(o.error||`HTTP ${n.status}`);return o}async function YE(e){let t=await fetch(`${id(e.platformUrl)}/v1/connectors/whatsapp/start`,{method:"POST",headers:{"content-type":"application/json",Authorization:`Bearer ${e.token}`},body:JSON.stringify({})}),n=await t.json().catch(()=>({}));if(!t.ok)throw new Error(n.error||`HTTP ${t.status}`);return n}function Hw(e){let t=process.stdout,n=w(t,"\xB7".repeat(42));console.log(Q(t,"Scan with WhatsApp \u2192 Linked devices")),console.log(n),qE.generate(e,{small:!0}),console.log(n)}var ol="",sd=!1;function VE(e){let t=e.status??"";return t==="qr"||t==="connecting"||t==="reconnecting"||t==="pairing_restart"}async function QE(e){for(let t=0;t<KE;t++){let n=await jw(e);if(n.status==="linked"||n.linked)return n;if(n.status==="pairing_restart"&&!sd&&(sd=!0,console.log(w(process.stdout,"Finishing WhatsApp link after scan (server restart) \u2014 wait a few seconds\u2026"))),n.qr&&n.qr!==ol&&(ol=n.qr,Hw(n.qr)),!VE(n))throw new Error(n.error||n.statusMessage||`Unexpected status: ${n.status}`);await new Promise(o=>setTimeout(o,zE))}throw new Error("Timed out waiting for WhatsApp to link (scan the QR within ~2 minutes).")}async function Jw(){let e=Bw();if(!e)return;let t=await jw(e);if(t.status==="linked"||t.linked){console.log(H(process.stdout,"WhatsApp already linked on the platform.")),t.linkedPhoneE164&&(console.log(` Phone: +${t.linkedPhoneE164}`),console.log(` Hint: omnish platform allow add wa:+${t.linkedPhoneE164}`));return}console.log(H(process.stdout,"Starting WhatsApp pairing on the platform\u2026")),sd=!1,ol="",t=await YE(e),t.qr&&(Hw(t.qr),ol=t.qr),t=await QE(e),console.log(H(process.stdout,"WhatsApp linked on the platform.")),t.linkedPhoneE164&&(console.log(` Phone: +${t.linkedPhoneE164}`),console.log(` Next: omnish platform allow add wa:+${t.linkedPhoneE164}`),console.log(" Then: omnish run"))}async function Gw(){let e=Bw();if(!e)return;let t=await fetch(`${id(e.platformUrl)}/v1/connectors/whatsapp/unlink`,{method:"POST",headers:{"content-type":"application/json",Authorization:`Bearer ${e.token}`},body:JSON.stringify({})}),n=await t.json().catch(()=>({}));if(!t.ok){console.error(P(process.stderr,n.error||`HTTP ${t.status}`)),process.exitCode=1;return}console.log(H(process.stdout,`WhatsApp unlinked (status: ${n.status??"idle"}).`))}import XE from"ws";function qw(e,t,n){return new Promise(o=>{let r=new XE(e,{headers:{Authorization:`Bearer ${t}`}}),s=setTimeout(()=>{r.terminate(),o({ok:!1,message:"timeout"})},n);r.once("open",()=>{clearTimeout(s),r.close(),o({ok:!0})}),r.once("unexpected-response",(i,a)=>{clearTimeout(s),o({ok:!1,status:a.statusCode,message:`HTTP ${a.statusCode} ${a.statusMessage}`})}),r.once("error",i=>{clearTimeout(s),o({ok:!1,message:String(i?.message??i)})})})}async function zw(e,t,n=12e3){let o=e.replace(/\/$/,""),r=lr(o),s=await qw(r,t,n),i,a,l="";for(let c of xa(o)){let u=await qw(c,t,n);if(u.ok)return i=new URL(c).pathname,{ok:!0,controlWsOk:s.ok,deviceWsOk:!0,deviceWsPath:i};a=u.status,l=u.message}return a===400?{ok:!1,controlWsOk:s.ok,deviceWsOk:!1,deviceWsStatus:400,error:"Device WebSocket returned HTTP 400 on /platform/device and could not use /control/device. Redeploy the latest relay image (adds /control/device), or route /platform/* to port 8788 in Caddy/EasyPanel."}:{ok:!1,controlWsOk:s.ok,deviceWsOk:!1,deviceWsStatus:a,error:`Device WebSocket failed: ${l}`}}var ZE=400,eP=8*1024*1024,tP=2*1024*1024;function nP(e){let t=le,n=!1,o=!1;for(let r=0;r<e.length;r++){let s=e[r];s==="-h"||s==="--help"?o=!0:s==="--force"?n=!0:(s==="--auth-dir"||s==="--authDir")&&e[r+1]&&(t=sl.resolve(e[++r]))}return{authDir:t,force:n,help:o}}function oP(e){let t={},n=0,o=0;function r(s,i){let a;try{a=rl.readdirSync(i,{withFileTypes:!0})}catch{return}for(let l of a){let c=l.name;if(c==="."||c==="..")continue;let u=sl.join(i,c),m=(s?`${s}/${c}`:c).split(sl.sep).join("/");if(!l.isSymbolicLink()){if(l.isDirectory())r(m,u);else if(l.isFile()){let f=rl.readFileSync(u);if(f.length>tP)throw new Error(`file too large: ${m}`);if(n+=f.length,n>eP)throw new Error("total auth size exceeds limit (8 MiB)");if(o+=1,o>ZE)throw new Error("too many files (max 400)");t[m]=f.toString("base64")}}}}return r("",e),t}function il(e){console.log([he(e,"omnish platform"),w(e,"Attached mode: messengers on the hosted platform; shell on this machine (omnish run)."),"",K(e,"When to use"),w(e," Link WhatsApp/Telegram once on the platform dashboard. Run omnish run on laptops, servers, or containers without local Baileys."),"",K(e,"Prerequisites"),w(e," 1. Platform account token (signup/login on relay dashboard)."),w(e," 2. Messengers linked on the platform (dashboard QR or import-whatsapp)."),w(e," 3. allowFrom / telegramAllowFrom on dashboard Allowlists (Telegram: DM bot /id for your user id)."),"",K(e,"Configure"),` ${S(e,"omnish config add platform_url <url> platform_token <token>")}`,w(e," Optional: platform_device_id <id>"),w(e," Env overrides: OMNISH_PLATFORM_URL, OMNISH_TOKEN, OMNISH_DEVICE_ID"),w(e," (aliases: OMNISH_COMM_LAYER_URL, OMNISH_DEVICE_TOKEN, OMNISH_TUNNEL_*)"),"",K(e,"Workflow"),` ${S(e,"omnish platform login --url <url> --token <t>")} ${w(e,"\u2014 save credentials (or config add)")}`,` ${S(e,"omnish platform status")} ${w(e,"\u2014 account, connectors, allowlists")}`,` ${S(e,"omnish platform allow add tg:<id>")} ${w(e,"\u2014 or dashboard + bot /id")}`,` ${S(e,"omnish platform probe")} ${w(e,"\u2014 test WebSocket routing")}`,` ${S(e,"omnish run")} ${w(e,"\u2014 attach device; expect 'omnish attached to platform'")}`,"",K(e,"Subcommands"),S(e,"omnish platform login --url <url> --token <token> [--device-id <id>]"),w(e," Save platform URL and token to config.json."),"",S(e,"omnish platform status"),w(e," Show gatewayMode, connector status, allowlists, online devices."),"",S(e,"omnish platform allow list|add|set"),w(e," Manage platform allowlists (PUT /v1/me/allowlists)."),"",S(e,"omnish platform probe"),w(e," Test control-plane WebSocket routing (diagnose attached omnish run 400 errors)."),"",S(e,"omnish platform link-whatsapp"),w(e," Pair WhatsApp on the platform (terminal QR + poll until linked)."),"",S(e,"omnish platform unlink-whatsapp"),w(e," Remove platform WhatsApp session and auth files."),"",S(e,"omnish platform import-whatsapp [--auth-dir <path>] [--force]"),w(e," Upload local Baileys auth (after omnish link on this host). Use link-whatsapp or dashboard QR when possible."),w(e," Stop local omnish run before import to avoid WhatsApp session conflicts."),"",w(e,"Standalone omnish link on this host is not used for platform WhatsApp when attached."),w(e,"Docs: docs/guides/platform-reference.md (full API/CLI)"),w(e," docs/guides/platform-attached-mode.md (walkthrough)"),""].join(`
614
+ `))}async function rP(e){let{authDir:t,force:n,help:o}=nP(e);if(o){console.log(["Usage: omnish platform import-whatsapp [--auth-dir <path>] [--force]","","Requires OMNISH_PLATFORM_URL and OMNISH_TOKEN.","Default auth directory: ~/.omnish/auth (Baileys useMultiFileAuthState).","Stop `omnish run` on this machine before importing to avoid WhatsApp session conflicts.",""].join(`
615
+ `));return}let r=ge();if(!r){console.error(P(process.stderr,"Set OMNISH_PLATFORM_URL (control plane URL) and OMNISH_TOKEN (from dashboard signup/login).")),process.exitCode=1;return}if(!rl.existsSync(t)){console.error(P(process.stderr,`Auth directory not found: ${t}`)),process.exitCode=1;return}se();let s=sl.join(t,"creds.json");if(!rl.existsSync(s)){console.error(P(process.stderr,`No creds.json in ${t} \u2014 link WhatsApp locally first (omnish link).`)),process.exitCode=1;return}let i;try{i=oP(t)}catch(f){console.error(P(process.stderr,String(f))),process.exitCode=1;return}if(Object.keys(i).length===0){console.error(P(process.stderr,"No files collected (empty auth directory?).")),process.exitCode=1;return}let a=r.platformUrl.replace(/\/$/,""),c=`${/^https?:\/\//i.test(a)?a:`http://${a}`}/v1/connectors/whatsapp/import`,u=await fetch(c,{method:"POST",headers:{"content-type":"application/json",Authorization:`Bearer ${r.token}`},body:JSON.stringify({files:i,force:n})}),d=await u.json().catch(()=>({}));if(!u.ok){console.error(P(process.stderr,d.error||`HTTP ${u.status}`)),process.exitCode=1;return}let m=d.qr?" QR emitted \u2014 open the platform dashboard if you need to scan.":"";console.log(H(process.stdout,`Uploaded Baileys auth. Platform connector status: ${d.status??"unknown"}.${m}`))}async function sP(){let e=process.stdout,t=process.stderr,n=ge();if(!n){console.error(P(t,"Set platform_url + platform_token (omnish config add) or OMNISH_PLATFORM_URL + OMNISH_TOKEN.")),process.exitCode=1;return}console.log(`${he(e,"Platform probe")} ${w(e,n.platformUrl)}`);let o=await zw(n.platformUrl,n.token);console.log(` ${w(e,"wss /control:")} ${o.controlWsOk?S(e,"ok"):ke(e,"fail")}`);for(let r of["/platform/device","/control/device"]){if(o.ok&&o.deviceWsPath&&o.deviceWsPath!==r){console.log(` ${w(e,`wss ${r}:`)} ${Q(e,"skipped (fallback used)")}`);continue}if(o.ok&&o.deviceWsPath===r){console.log(` ${w(e,`wss ${r}:`)} ${S(e,"ok")}`);continue}!o.ok&&r==="/platform/device"&&console.log(` ${w(e,`wss ${r}:`)} ${ke(e,"fail")}${o.deviceWsStatus?` (HTTP ${o.deviceWsStatus})`:""}`),!o.ok&&r==="/control/device"&&console.log(` ${w(e,`wss ${r}:`)} ${ke(e,"fail")}`)}if(o.ok){o.deviceWsPath==="/control/device"&&console.log(w(e," Using /control/device fallback (/platform/* not on control port \u2014 redeploy Caddy when convenient).")),console.log(H(e,"Attached omnish run should be able to connect."));return}console.error(P(t,o.error??"Platform probe failed.")),o.controlWsOk&&!o.deviceWsOk&&console.error(P(t,"Fix: redeploy the latest tunnel-relay image (enables wss /control/device), update omnish CLI, then retry. Also route /platform/* to 8788 when you can (Caddyfile.standalone).")),process.exitCode=1}async function Kw(e){let t=(e[0]??"").toLowerCase();if(!t||t==="-h"||t==="--help"){il(process.stdout);return}if(t==="login"){await Uw(e.slice(1));return}if(t==="status"){await Dw();return}if(t==="allow"){await Ww(e.slice(1));return}if(t==="probe"){await sP();return}if(t==="link-whatsapp"){await Jw();return}if(t==="unlink-whatsapp"){await Gw();return}if(t==="import-whatsapp"){await rP(e.slice(1));return}console.error(P(process.stderr,`Unknown platform subcommand: ${e[0]}`)),il(process.stderr),process.exitCode=1}j();import Qw from"node:crypto";import go from"node:fs";import Xw from"node:path";function iP(){return Qw.randomBytes(24).toString("hex")}function Yw(){return Qw.randomBytes(32).toString("hex")}function Vw(e){try{let t=go.readFileSync(e,"utf8"),n=JSON.parse(t);if(typeof n.token=="string"&&n.token.length>=16&&typeof n.secret=="string"&&n.secret.length>=16)return{token:n.token,secret:n.secret}}catch{}return null}function aP(){let e=Vw(At);if(e)return e;if(e=Vw(br),e){D(Xw.dirname(At)),go.writeFileSync(At,JSON.stringify(e,null,2)+`
616
+ `,{mode:384});try{go.unlinkSync(br)}catch{}return e}return null}function Zw(e){D(Xw.dirname(At));let t=aP(),n=(e??"").trim();if(n){let r={token:n,secret:t?.secret??Yw()};go.writeFileSync(At,JSON.stringify(r,null,2)+`
617
+ `,{mode:384});try{go.existsSync(br)&&go.unlinkSync(br)}catch{}return r}if(t)return t;let o={token:iP(),secret:Yw()};return go.writeFileSync(At,JSON.stringify(o,null,2)+`
618
+ `,{mode:384}),o}ue();import jn from"node:fs";import mP from"node:http";import je from"node:path";import Mt from"node:process";import{fileURLToPath as fP}from"node:url";import hP from"node:os";dt();j();import ad from"node:crypto";var ld="omnish_cfg_sess",eb=7*24*60*60*1e3;function cd(e){let t=Date.now()+eb,n=Buffer.from(JSON.stringify({exp:t}),"utf8").toString("base64url"),o=ad.createHmac("sha256",e).update(n).digest("hex");return`${n}.${o}`}function tb(e,t){let n=lP(t??"")[ld];if(!n||!n.includes("."))return!1;let o=n.lastIndexOf("."),r=n.slice(0,o),s=n.slice(o+1),i=ad.createHmac("sha256",e).update(r).digest("hex");try{let a=Buffer.from(s,"hex"),l=Buffer.from(i,"hex");if(a.length!==l.length||!ad.timingSafeEqual(a,l))return!1}catch{return!1}try{let a=JSON.parse(Buffer.from(r,"base64url").toString("utf8"));return!(typeof a.exp!="number"||a.exp<Date.now())}catch{return!1}}function ud(e){let t=Math.floor(eb/1e3);return`${ld}=${e}; HttpOnly; Path=/; SameSite=Lax; Max-Age=${t}`}function nb(){return`${ld}=; HttpOnly; Path=/; SameSite=Lax; Max-Age=0`}function lP(e){let t={};for(let n of e.split(";")){let o=n.indexOf("=");if(o===-1)continue;let r=n.slice(0,o).trim(),s=n.slice(o+1).trim();r&&(t[r]=decodeURIComponent(s))}return t}j();import dd from"node:fs";import al from"node:process";function ob(e){try{return al.kill(e,0),!0}catch{return!1}}function rb(e){let t=Date.now()+e;for(;Date.now()<t;);}function cP(){try{let e=dd.readFileSync(ks,"utf8"),t=JSON.parse(e);if(!t||typeof t!="object")return null;let n=t,o=typeof n.pid=="number"?n.pid:Number(n.pid),r=typeof n.port=="number"?n.port:Number(n.port),s=typeof n.host=="string"?n.host:"",i=typeof n.startedAt=="string"?n.startedAt:"";return!Number.isFinite(o)||o<=0||!Number.isFinite(r)||r<=0||r>65535?null:{pid:o,port:r,host:s,startedAt:i}}catch{return null}}function sb(e){dd.writeFileSync(ks,`${JSON.stringify(e)}
619
+ `,{mode:384})}function gs(){try{dd.unlinkSync(ks)}catch{}}function ib(e){let t=cP();if(t&&t.port===e&&t.pid!==al.pid){if(!ob(t.pid)){gs();return}try{al.kill(t.pid,"SIGTERM")}catch{}if(rb(350),ob(t.pid)){try{al.kill(t.pid,"SIGKILL")}catch{}rb(100)}gs()}}ue();j();import pd from"node:fs";import uP from"node:process";function dP(e){try{return uP.kill(e,0),!0}catch{return!1}}function pP(){try{let e=pd.readFileSync(de,"utf8").trim(),t=Number.parseInt(e,10);return Number.isFinite(t)&&t>0?t:null}catch{return null}}function ll(){let e=pP();return e===null?!1:dP(e)}var md=class{busy=!1;subscribers=new Set;abort=null;currentSock=null;subscribe(t){return this.subscribers.add(t),()=>{this.subscribers.delete(t)}}emit(t){for(let n of this.subscribers)try{n(t)}catch{}}requestCancel(){this.abort?.abort(),this.currentSock&&(nr(this.currentSock),this.currentSock=null)}beginPairing(t){if(this.busy)throw new Error("Pairing already in progress.");if(ll())throw new Error("Gateway appears to be running (gateway.pid). Stop `omnish run` or your service before pairing from the browser.");if(!t.force&&St())throw new Error("WhatsApp session already linked. Use \u201CReplace session\u201D or run `omnish link --force` from the CLI.");se(),t.force&&(pd.rmSync(le,{recursive:!0,force:!0}),pd.mkdirSync(le,{recursive:!0,mode:448})),this.busy=!0,this.abort=new AbortController;let n=this.abort.signal;(async()=>{try{await Dg({authDir:le,verbose:!1,printQr:!1,signal:n,onQr:o=>this.emit({type:"qr",payload:o}),onRestart515:()=>this.emit({type:"restart"}),onSocketReady:o=>{this.currentSock=o},onSocketClosed:()=>{this.currentSock=null}}),this.emit({type:"open"}),this.emit({type:"done",ok:!0})}catch(o){let r=o instanceof Error?o.message:String(o);r==="Pairing cancelled."?this.emit({type:"error",message:"Cancelled."}):this.emit({type:"error",message:r}),this.emit({type:"done",ok:!1})}finally{this.busy=!1,this.abort=null,this.currentSock=null}})()}},ys=new md;var fe="application/json; charset=utf-8",ws=null;function gP(){gs();let e=ws;ws=null,e?e.close(()=>{Mt.exit(0)}):Mt.exit(0),setTimeout(()=>Mt.exit(0),4e3).unref()}function yP(){let e=Mt.env.OMNISH_CONFIG_UI_STATIC?.trim(),t=Mt.env.OMNISH_UI_STATIC?.trim()||e;if(t&&jn.existsSync(je.join(t,"index.html")))return t;let n=je.dirname(fP(import.meta.url)),o=je.join(n,"ui");if(jn.existsSync(je.join(o,"index.html")))return o;let r=je.join(n,"..","..","dist","ui");if(jn.existsSync(je.join(r,"index.html")))return r;let s=je.join(Mt.cwd(),"dist","ui");if(jn.existsSync(je.join(s,"index.html")))return s;throw new Error("omnish ui static files not found (expected dist/ui/index.html). Run `pnpm build`.")}function wP(e){let t=je.extname(e).toLowerCase();return{".html":"text/html; charset=utf-8",".js":"text/javascript; charset=utf-8",".css":"text/css; charset=utf-8",".svg":"image/svg+xml",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".webp":"image/webp",".ico":"image/x-icon",".woff2":"font/woff2",".json":"application/json; charset=utf-8"}[t]??"application/octet-stream"}function bP(e,t){let o=decodeURIComponent(t.split("?")[0]??"").replace(/^\/+/,""),r=je.normalize(je.join(e,o));return!r.startsWith(je.normalize(e+je.sep))&&r!==je.normalize(e)?null:r}async function fd(e,t=512e3){let n=[],o=0;for await(let s of e){if(o+=s.length,o>t)throw new Error("Body too large.");n.push(s)}if(n.length===0)return;let r=Buffer.concat(n).toString("utf8");return JSON.parse(r)}function kP(e){return xn.includes(e)}function hd(e){let t=(e.telegramBotToken??"").trim(),n=t.length===0?"":t.length<=8?"(set)":`${t.slice(0,4)}\u2026${t.slice(-4)}`;return{...e,telegramBotToken:n,telegramBotTokenConfigured:!!Ie(e),telegramBotTokenEnvOverride:typeof Mt.env.TELEGRAM_BOT_TOKEN=="string"&&Mt.env.TELEGRAM_BOT_TOKEN.trim().length>0}}function SP(){let e=v();return{version:gt(),dataDir:W,configPath:U,waAuthDir:le,whatsappLinked:St(),gatewayPidHint:jn.existsSync(je.join(W,"gateway.pid")),gatewayRunning:ll(),gatewayLogFile:He,gatewayMode:e.gatewayMode,telegramBotTokenMasked:Ie(e).length===0?"":hd(e).telegramBotToken,telegramBotTokenEnvOverride:typeof Mt.env.TELEGRAM_BOT_TOKEN=="string"&&Mt.env.TELEGRAM_BOT_TOKEN.trim().length>0}}function vP(e,t){if(!Array.isArray(e)||!Array.isArray(t))throw new Error("allowFrom and telegramAllowFrom must be arrays of strings.");let n=e.map(a=>String(a).trim()).filter(Boolean),o=t.map(a=>String(a).trim()).filter(Boolean),r=Ts(n).filter(a=>a!=="*"),s=[];for(let a of o){let l=Fe(a);if(!l)throw new Error(`Invalid Telegram allow entry: ${a}`);s.push(l)}let i=v();i.allowFrom=r.sort(),i.telegramAllowFrom=[...new Set(s)].sort(),Ve(i)}function xP(e){return typeof e=="string"?e:typeof e=="boolean"||typeof e=="number"?String(e):JSON.stringify(e)}async function ab(e){let t=yP(),{meta:n}=e;ib(e.port);let o=mP.createServer(async(r,s)=>{try{let i=new URL(r.url??"/",`http://${r.headers.host??"localhost"}`),a=i.pathname;if(r.method==="GET"&&a==="/"){let m=i.searchParams.get("token")?.trim();if(m&&m===n.token){let f=cd(n.secret);s.statusCode=302,s.setHeader("Location","/"),s.setHeader("Set-Cookie",ud(f)),s.end();return}}if(a.startsWith("/api/")){let m=r.headers.cookie,f=tb(n.secret,m);if(r.method==="POST"&&a==="/api/session"){let h=await fd(r),g=typeof h?.token=="string"?h.token.trim():"";if(!g||g!==n.token){s.statusCode=401,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:"Invalid token."}));return}let y=cd(n.secret);s.statusCode=200,s.setHeader("Content-Type",fe),s.setHeader("Set-Cookie",ud(y)),s.end(JSON.stringify({ok:!0}));return}if(r.method==="GET"&&a==="/api/me"){s.statusCode=f?200:401,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:f}));return}if(!f){s.statusCode=401,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:"Unauthorized."}));return}if(r.method==="GET"&&a==="/api/status"){s.statusCode=200,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!0,...SP()}));return}if(r.method==="GET"&&a==="/api/config"){s.statusCode=200,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!0,config:hd(v())}));return}if(r.method==="PUT"&&a==="/api/config"){let h=await fd(r);if(!h||typeof h!="object"){s.statusCode=400,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:"Expected JSON object."}));return}let g=h;for(let[y,b]of Object.entries(g))if(b!==void 0&&!(y==="allowFrom"||y==="telegramAllowFrom")){if(y==="telegramBotToken"){let k=typeof b=="string"?b.trim():"";if(!k||k==="(set)"||k.includes("\u2026"))continue;if(!Ot(k))throw new Error("Invalid Telegram bot token format.");Vs("telegramBotToken",k);continue}if(!kP(y))throw new Error(`Unknown or unsupported config key: ${y}`);Vs(y,xP(b))}if("allowFrom"in g||"telegramAllowFrom"in g){let y=v();vP("allowFrom"in g?g.allowFrom:y.allowFrom,"telegramAllowFrom"in g?g.telegramAllowFrom:y.telegramAllowFrom)}s.statusCode=200,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!0,config:hd(v())}));return}if(r.method==="POST"&&a==="/api/logout"){s.statusCode=200,s.setHeader("Content-Type",fe),s.setHeader("Set-Cookie",nb()),s.end(JSON.stringify({ok:!0}));return}if(r.method==="POST"&&a==="/api/shutdown"){s.statusCode=200,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!0})),setImmediate(()=>{ys.requestCancel(),gP()});return}if(r.method==="POST"&&a==="/api/gateway/start"){if(ll()){s.statusCode=409,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:"Gateway already appears to be running (pidfile + live process). Stop it first if you need to restart."}));return}let h=el();if(!h.ok){s.statusCode=400,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:h.message}));return}let g=He,y=ga(g);if(!y.ok){s.statusCode=500,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:y.message}));return}s.statusCode=200,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!0,pid:y.pid,logFile:g}));return}if(r.method==="POST"&&a==="/api/gateway/stop"){let h=ya();switch(h.outcome){case"no_pidfile":s.statusCode=400,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:"No gateway pidfile \u2014 background gateway does not appear to be running."}));return;case"invalid_pidfile":s.statusCode=400,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:"Invalid pidfile (removed)."}));return;case"stale_cleaned":s.statusCode=200,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!0,stale:!0,pid:h.pid,message:"Process was not running; stale pidfile removed."}));return;case"sent_signal":s.statusCode=200,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!0,pid:h.pid}));return;case"taskkill_ok":s.statusCode=200,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!0,pid:h.pid,taskkill:!0}));return;case"failed":s.statusCode=500,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:h.message}));return}}if(r.method==="GET"&&a==="/api/wa/link/events"){s.writeHead(200,{"Content-Type":"text/event-stream; charset=utf-8","Cache-Control":"no-store",Connection:"keep-alive"}),s.write(`: connected
619
620
 
620
621
  `);let h=ys.subscribe(g=>{s.write(`data: ${JSON.stringify(g)}
621
622
 
622
- `)});r.on("close",()=>{h()});return}if(r.method==="POST"&&a==="/api/wa/link/start"){let g=(await dd(r))?.force===!0;try{ys.beginPairing({force:g})}catch(y){let k=String(y),b=k.includes("already in progress")||k.includes("Gateway appears")?409:400;s.statusCode=b,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:k}));return}s.statusCode=202,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!0}));return}if(r.method==="POST"&&a==="/api/wa/link/cancel"){ys.requestCancel(),s.statusCode=200,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!0}));return}s.statusCode=404,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:"Not found."}));return}let l=a==="/"?"index.html":a.replace(/^\/+/,""),c=dP(t,l),u=je.join(t,"index.html");c&&jn.existsSync(c)&&jn.statSync(c).isFile()&&(u=c);let d=jn.readFileSync(u);s.statusCode=200,s.setHeader("Content-Type",uP(u)),s.setHeader("Cache-Control",je.basename(u)==="index.html"?"no-store":"public, max-age=3600"),s.end(d)}catch(i){s.statusCode=500,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:String(i)}))}});await new Promise((r,s)=>{o.once("error",s),o.listen(e.port,e.host,()=>r())}),ws=o,tb({pid:Mt.pid,port:e.port,host:e.host,startedAt:new Date().toISOString()}),o.on("close",()=>{gs(),ws===o&&(ws=null)})}function rb(e){let t=aP.networkInterfaces(),n=[];for(let o of Object.values(t))if(o)for(let r of o)r.family==="IPv4"&&!r.internal&&n.push(`http://${r.address}:${e}/`);return[...new Set(n)].sort()}import bP from"node:readline";import ib from"node:path";import _e from"node:process";ue();dt();dt();function gP(e){let t=e.trim();if(!t)return null;let n=t.toLowerCase();if(n.startsWith("tg:")){let o=Fe(t);if(!o)return null;let r=Number(o);return Number.isFinite(r)?{channel:"telegram",chatId:r}:null}if(n.startsWith("wa:")){let o=t.slice(3).trim(),r=o.split("@")[0]??o,s=te(o)??te(r);return s?{channel:"whatsapp",e164s:[s]}:null}return null}function md(e){return e.channel==="all"||e.channel==="whatsapp-all"||e.channel==="telegram-all"}function yP(e){let t=e.trimStart();if(/^--text=/i.test(t)){let n=t.slice(t.indexOf("=")+1).trimEnd();return n.length>0?n:null}if(/^--text(\s+|$)/i.test(t)){let n=t.replace(/^--text\s*/i,"").trim();return n.length>0?n:null}if(/^-t=/i.test(t)){let n=t.slice(t.indexOf("=")+1).trimEnd();return n.length>0?n:null}if(/^-t\s+/i.test(t)){let n=t.replace(/^-t\s+/i,"").trim(),o=n.indexOf(" -- ");if(o!==-1){let r=n.slice(0,o).trim(),s=n.slice(o+4).trim();if(r.length>0&&s.length>0)return`${r}
623
+ `)});r.on("close",()=>{h()});return}if(r.method==="POST"&&a==="/api/wa/link/start"){let g=(await fd(r))?.force===!0;try{ys.beginPairing({force:g})}catch(y){let b=String(y),k=b.includes("already in progress")||b.includes("Gateway appears")?409:400;s.statusCode=k,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:b}));return}s.statusCode=202,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!0}));return}if(r.method==="POST"&&a==="/api/wa/link/cancel"){ys.requestCancel(),s.statusCode=200,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!0}));return}s.statusCode=404,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:"Not found."}));return}let l=a==="/"?"index.html":a.replace(/^\/+/,""),c=bP(t,l),u=je.join(t,"index.html");c&&jn.existsSync(c)&&jn.statSync(c).isFile()&&(u=c);let d=jn.readFileSync(u);s.statusCode=200,s.setHeader("Content-Type",wP(u)),s.setHeader("Cache-Control",je.basename(u)==="index.html"?"no-store":"public, max-age=3600"),s.end(d)}catch(i){s.statusCode=500,s.setHeader("Content-Type",fe),s.end(JSON.stringify({ok:!1,error:String(i)}))}});await new Promise((r,s)=>{o.once("error",s),o.listen(e.port,e.host,()=>r())}),ws=o,sb({pid:Mt.pid,port:e.port,host:e.host,startedAt:new Date().toISOString()}),o.on("close",()=>{gs(),ws===o&&(ws=null)})}function lb(e){let t=hP.networkInterfaces(),n=[];for(let o of Object.values(t))if(o)for(let r of o)r.family==="IPv4"&&!r.internal&&n.push(`http://${r.address}:${e}/`);return[...new Set(n)].sort()}import EP from"node:readline";import ub from"node:path";import _e from"node:process";ue();dt();dt();function CP(e){let t=e.trim();if(!t)return null;let n=t.toLowerCase();if(n.startsWith("tg:")){let o=Fe(t);if(!o)return null;let r=Number(o);return Number.isFinite(r)?{channel:"telegram",chatId:r}:null}if(n.startsWith("wa:")){let o=t.slice(3).trim(),r=o.split("@")[0]??o,s=te(o)??te(r);return s?{channel:"whatsapp",e164s:[s]}:null}return null}function gd(e){return e.channel==="all"||e.channel==="whatsapp-all"||e.channel==="telegram-all"}function RP(e){let t=e.trimStart();if(/^--text=/i.test(t)){let n=t.slice(t.indexOf("=")+1).trimEnd();return n.length>0?n:null}if(/^--text(\s+|$)/i.test(t)){let n=t.replace(/^--text\s*/i,"").trim();return n.length>0?n:null}if(/^-t=/i.test(t)){let n=t.slice(t.indexOf("=")+1).trimEnd();return n.length>0?n:null}if(/^-t\s+/i.test(t)){let n=t.replace(/^-t\s+/i,"").trim(),o=n.indexOf(" -- ");if(o!==-1){let r=n.slice(0,o).trim(),s=n.slice(o+4).trim();if(r.length>0&&s.length>0)return`${r}
623
624
 
624
- ${s}`}return n.length>0?n:null}if(/^-t$/i.test(t.trim()))return null}function fd(e){let t=e.trim();return/^sendto(\s|$)/i.test(t)&&!/^\/sendto/i.test(t)?`/${t}`:t}function wP(e){let t=e.toLowerCase();if(t==="*"||t==="all")return{channel:"all"};if(t==="peer"||t==="self"){let n=process.env.OMNISH_PEER_KEY||process.env.OMNISH_JOB_OWNER_PEER||"";return gP(n)??{channel:"all"}}if(t==="wa"||t==="whatsapp"||t==="wa:all"||t==="whatsapp:all")return{channel:"whatsapp-all"};if(t==="tg"||t==="telegram"||t==="tg:all"||t==="telegram:all")return{channel:"telegram-all"};if(t.startsWith("tg:")||t.startsWith("telegram:")){let n=Fe(e);if(!n)return null;let o=Number(n);return Number.isFinite(o)?{channel:"telegram",chatId:o}:null}if(t.startsWith("wa:")){let n=e.slice(3).trim();if(!n)return null;let o=n.split(",").map(r=>te(r.trim())).filter(r=>!!r);return o.length===0?null:{channel:"whatsapp",e164s:o}}if(e.includes(",")){let n=e.split(",").map(o=>te(o.trim())).filter(o=>!!o);return n.length===0?null:{channel:"whatsapp",e164s:n}}if(e.startsWith("+")){let n=te(e);return n?{channel:"whatsapp",e164s:[n]}:null}return null}function sb(e){let t=fd(e);if(!/^\/sendto(\s|$)/i.test(t))return null;let n=t.replace(/^\/sendto\s+/i,"").trim();if(!n)return null;let o=n.indexOf(" ");if(o===-1)return null;let r=n.slice(0,o).trim(),s=n.slice(o+1).trim();if(!s)return null;let i=wP(r);if(!i)return null;let a=yP(s);if(a===null)return null;if(a!==void 0)return{...i,mode:"text",body:a};let l=Oa(s);if(!l)return null;let{selectorPart:c,caption:u}=l;return{...i,mode:"media",selectorPart:c,caption:u}}function hd(){return["/sendto wa|tg|* <selectors> [-- caption]","/sendto +E164 or +E164,+E164 <selectors> [-- caption]","/sendto <dest> --text <message> or --text=<msg> or -t <msg>","/sendto peer <selectors>|--text ... (resolve from OMNISH_PEER_KEY / OMNISH_JOB_OWNER_PEER)","/sendto <dest> -t <title> -- <brief> (employee status; also: sendto without leading /)","omnish i -c '/sendto peer -t <title> -- <brief>' specialist progress (full command)","omnish i -c '/sendto peer <path> -- <caption>' coordinator file delivery (full command)","/sendto tg:<chat_id> <selectors> [-- caption] (compat: single Telegram id)","selectors: file1,file2 or *.mp4 or **/*.mp4 (from current session cwd); a path ./--text sends a file named --text","examples: /sendto wa ./promo.mp4 | /sendto * **/*.mp4 -- Daily clips","examples: /sendto +15551234567 --text Hello | /sendto tg:123 -t=Status OK","examples: /sendto +15550000001,+15550000002 intro.png,deck.pdf -- Launch","Also supports compat aliases: wa:all, whatsapp:all, tg:all, telegram:all.","Requires `omnish run` on this machine."].join(`
625
- `)}rs();qt();j();var wr="wa:cli:interactive",kP={onPlainTextLlmFallback(e,t){qo(v(),e,t,async n=>{n.trim()&&console.log(H(_e.stdout,n))})}};function SP(e){let t=e.trim();if(!t)return null;let n=t.toLowerCase();if(n.startsWith("tg:")||n.startsWith("telegram:")){let s=Fe(t);return s?`tg:${s}`:null}let o=n.startsWith("wa:")?t.slice(3):t,r=te(o);return r?`wa:${r}`:null}function vP(e){let t=null,n=null;for(let o=0;o<e.length;){let r=e[o]??"";if(r==="--as"){let s=e[o+1];if(!s||s.startsWith("-"))return{opts:{senderKey:null,oneShot:null},error:"--as requires a sender (wa:+E164 or tg:<id>)."};let i=SP(s);if(!i)return{opts:{senderKey:null,oneShot:null},error:`Could not parse sender "${s}". Use +E164, wa:+E164, or tg:<user_id>.`};t=i,o+=2;continue}if(r==="-c"||r==="--command"){let s=e[o+1];if(typeof s!="string")return{opts:{senderKey:null,oneShot:null},error:`${r} requires a command string.`};n=s,o+=2;continue}return r==="--help"||r==="-h"?{opts:{senderKey:null,oneShot:null},error:"help"}:{opts:{senderKey:null,oneShot:null},error:`Unknown argument: ${r}`}}return{opts:{senderKey:t,oneShot:n},error:null}}function gd(e){let t=_e.cwd(),n=[`${he(e,"omnish i")} ${w(e,"[options]")}`,Q(e,"Interactive shell \u2014 same commands as WhatsApp/Telegram chat."),"",K(e,"Usage:"),` ${S(e,"omnish i [options]")}`,` ${S(e,"omnish interactive [options]")}`,"",K(e,"Options:"),..._t(e," ",[{left:"--as <sender>",right:"Sender key for cluster commands (wa:+E164 or tg:id). Default: synthetic wa:cli:interactive."},{left:"-c, --command <line>",right:"Run one line and exit (non-interactive)."},{left:"-h, --help",right:"Show this help."}],o=>w(e,o)),"",`${w(e,"Trust:")} ${S(e,"Full local access like your shell; not gated by inbox allowlist.")}`,`${w(e,"Jobs:")} ${S(e,"/bg and /jobs apply only to this REPL session (not the gateway process).")}`,`${w(e,"Files:")} ${S(e,"Use /sendto for files or --text for plain messages through the gateway; plain /send needs a chat peer.")}`,`${w(e,"Gateway:")} ${S(e,"/reload and /updates require omnish run; /sendto requires omnish run for WA/TG delivery.")}`,"",hd(),"",`${w(e,"cwd:")} ${S(e,`session starts at ${t} (change with !cd or ${v().commandPrefix}cd).`)}`];console.log(n.join(`
626
- `))}function ab(e,t){let n=new Set,o=new Set;if(e.channel==="whatsapp-all"||e.channel==="all"){let r=new Set;for(let s of t.allowFrom){let i=te(String(s));i&&!r.has(i)&&(r.add(i),n.add(i))}}if(e.channel==="telegram-all"||e.channel==="all")for(let r of t.telegramAllowFrom){let s=Fe(String(r));if(!s)continue;let i=Number(s);Number.isFinite(i)&&o.add(i)}if(e.channel==="whatsapp")for(let r of e.e164s)n.add(r);return e.channel==="telegram"&&o.add(e.chatId),{waTargets:n,tgTargets:o}}function lb(e){let t="No recipients matched the requested /sendto destination.";return md(e)?ge()?`${t} Check platform dashboard allowlists (or omnish platform status), or use explicit +E164 / tg:<id>.`:`${t} Set allowFrom / telegramAllowFrom in config.json, or use explicit +E164 / tg:<id>.`:t}async function xP(e){let t=md(e)?await ku():v();if(e.mode==="text"){let{waTargets:d,tgTargets:m}=ab(e,t);if(d.size===0&&m.size===0)return{error:lb(e)};let f=[];for(let h of d){let g=await Wt({op:"sendText",channel:"whatsapp",e164:h,text:e.body});g&&f.push(`[wa:${h}] ${g}`)}for(let h of m){let g=await Wt({op:"sendText",channel:"telegram",chatId:h,text:e.body});g&&f.push(`[tg:${h}] ${g}`)}return f.length>0?{error:f.join(`
627
- `)}:{error:null,kind:"text",recipientsSent:d.size+m.size}}let n=oe(wr).cwd,o=await as(n,e.selectorPart);if(o.length===0)return{error:`No files matched: ${e.selectorPart}`};let r=await ls(o);if(!r.ok)return{error:r.error};let s=o.map(d=>Ct(d,t.fileSendMaxBytes));for(let d of s)if("error"in d)return{error:d.error};let{waTargets:i,tgTargets:a}=ab(e,t);if(i.size===0&&a.size===0)return{error:lb(e)};let l=[];for(let d of i)for(let m of s){if("error"in m)continue;let f=await Wt({op:"sendMedia",channel:"whatsapp",e164:d,absPath:m.absPath,caption:e.caption});f&&l.push(`[wa:${d}] ${m.displayName}: ${f}`)}for(let d of a)for(let m of s){if("error"in m)continue;let f=await Wt({op:"sendMedia",channel:"telegram",chatId:d,absPath:m.absPath,caption:e.caption});f&&l.push(`[tg:${d}] ${m.displayName}: ${f}`)}if(l.length>0)return{error:l.join(`
628
- `)};let c=i.size+a.size,u=s.length;return{error:null,kind:"media",recipientsSent:c,filesSent:u,messagesSent:c*u}}async function CP(e,t,n,o,r,s,i){let a=e.trim();if(!a)return;let l=fd(a),c=sb(l);if(c!==null||/^\/sendto(\s|$)/i.test(l)||/^sendto(\s|$)/i.test(a)){if(c===null){console.log(E(_e.stderr,`Invalid /sendto.
629
- `+hd()));return}let m=await xP(c);if(m.error!==null)console.log(E(_e.stderr,m.error));else if(m.kind==="text")console.log(H(_e.stdout,`Sent text to ${m.recipientsSent} recipient(s).`));else{let f=`Sent ${m.filesSent} file(s) to ${m.recipientsSent} recipient(s) (${m.messagesSent} message(s)).`;console.log(H(_e.stdout,f))}return}let u={peerKey:wr,text:e},d=await yr(v(),t,n,o,r,u,s,void 0,i,!1,kP);d!==null&&await RP(d)}async function RP(e){if(e===null)return;if(e.kind==="file"||e.kind==="files"){console.log(H(_e.stdout,["This CLI session has no chat peer to attach to.","Push through the gateway instead, for example:"," /sendto wa:+15551234567 ./my.pdf"," /sendto tg:123456789 ~/photo.jpg -- optional caption","(Requires `omnish run` on this machine.)"].join(`
630
- `)));return}if(e.kind==="bundle"){for(let n of e.texts??[]){let o=pe(n,"whatsapp").text;o.trim()&&(console.log(o),console.log(""))}for(let n of e.files??[])console.log(H(_e.stdout,`File: ${n.absPath}`));return}if(e.kind==="texts"){for(let n=0;n<e.bodies.length;n++){let o=pe(e.bodies[n],"whatsapp").text;o.trim()&&(n>0&&console.log(""),console.log(o))}return}if(e.kind!=="text")return;let t=pe(e.body,"whatsapp").text;t.trim()&&console.log(t)}async function cb(e){let t=vP(e);if(t.error==="help"){gd(_e.stdout);return}if(t.error&&t.error!==null){console.error(E(_e.stderr,t.error)),console.error(w(_e.stderr,"Try: omnish i --help")),_e.exitCode=1;return}let{senderKey:n,oneShot:o}=t.opts,r=n??wr;se(),Bs(wr,_e.cwd());let s=new cn,i=new Map,a=new Map,l=new Map,c=new An(()=>v(),async(f,h)=>{_e.stdout.write(h),h.endsWith(`
625
+ ${s}`}return n.length>0?n:null}if(/^-t$/i.test(t.trim()))return null}function yd(e){let t=e.trim();return/^sendto(\s|$)/i.test(t)&&!/^\/sendto/i.test(t)?`/${t}`:t}function TP(e){let t=e.toLowerCase();if(t==="*"||t==="all")return{channel:"all"};if(t==="peer"||t==="self"){let n=process.env.OMNISH_PEER_KEY||process.env.OMNISH_JOB_OWNER_PEER||"";return CP(n)??{channel:"all"}}if(t==="wa"||t==="whatsapp"||t==="wa:all"||t==="whatsapp:all")return{channel:"whatsapp-all"};if(t==="tg"||t==="telegram"||t==="tg:all"||t==="telegram:all")return{channel:"telegram-all"};if(t.startsWith("tg:")||t.startsWith("telegram:")){let n=Fe(e);if(!n)return null;let o=Number(n);return Number.isFinite(o)?{channel:"telegram",chatId:o}:null}if(t.startsWith("wa:")){let n=e.slice(3).trim();if(!n)return null;let o=n.split(",").map(r=>te(r.trim())).filter(r=>!!r);return o.length===0?null:{channel:"whatsapp",e164s:o}}if(e.includes(",")){let n=e.split(",").map(o=>te(o.trim())).filter(o=>!!o);return n.length===0?null:{channel:"whatsapp",e164s:n}}if(e.startsWith("+")){let n=te(e);return n?{channel:"whatsapp",e164s:[n]}:null}return null}function cb(e){let t=yd(e);if(!/^\/sendto(\s|$)/i.test(t))return null;let n=t.replace(/^\/sendto\s+/i,"").trim();if(!n)return null;let o=n.indexOf(" ");if(o===-1)return null;let r=n.slice(0,o).trim(),s=n.slice(o+1).trim();if(!s)return null;let i=TP(r);if(!i)return null;let a=RP(s);if(a===null)return null;if(a!==void 0)return{...i,mode:"text",body:a};let l=La(s);if(!l)return null;let{selectorPart:c,caption:u}=l;return{...i,mode:"media",selectorPart:c,caption:u}}function wd(){return["/sendto wa|tg|* <selectors> [-- caption]","/sendto +E164 or +E164,+E164 <selectors> [-- caption]","/sendto <dest> --text <message> or --text=<msg> or -t <msg>","/sendto peer <selectors>|--text ... (resolve from OMNISH_PEER_KEY / OMNISH_JOB_OWNER_PEER)","/sendto <dest> -t <title> -- <brief> (employee status; also: sendto without leading /)","omnish i -c '/sendto peer -t <title> -- <brief>' specialist progress (full command)","omnish i -c '/sendto peer <path> -- <caption>' coordinator file delivery (full command)","/sendto tg:<chat_id> <selectors> [-- caption] (compat: single Telegram id)","selectors: file1,file2 or *.mp4 or **/*.mp4 (from current session cwd); a path ./--text sends a file named --text","examples: /sendto wa ./promo.mp4 | /sendto * **/*.mp4 -- Daily clips","examples: /sendto +15551234567 --text Hello | /sendto tg:123 -t=Status OK","examples: /sendto +15550000001,+15550000002 intro.png,deck.pdf -- Launch","Also supports compat aliases: wa:all, whatsapp:all, tg:all, telegram:all.","Requires `omnish run` on this machine."].join(`
626
+ `)}rs();qt();j();var wr="wa:cli:interactive",PP={onPlainTextLlmFallback(e,t){qo(v(),e,t,async n=>{n.trim()&&console.log(H(_e.stdout,n))})}};function $P(e){let t=e.trim();if(!t)return null;let n=t.toLowerCase();if(n.startsWith("tg:")||n.startsWith("telegram:")){let s=Fe(t);return s?`tg:${s}`:null}let o=n.startsWith("wa:")?t.slice(3):t,r=te(o);return r?`wa:${r}`:null}function MP(e){let t=null,n=null;for(let o=0;o<e.length;){let r=e[o]??"";if(r==="--as"){let s=e[o+1];if(!s||s.startsWith("-"))return{opts:{senderKey:null,oneShot:null},error:"--as requires a sender (wa:+E164 or tg:<id>)."};let i=$P(s);if(!i)return{opts:{senderKey:null,oneShot:null},error:`Could not parse sender "${s}". Use +E164, wa:+E164, or tg:<user_id>.`};t=i,o+=2;continue}if(r==="-c"||r==="--command"){let s=e[o+1];if(typeof s!="string")return{opts:{senderKey:null,oneShot:null},error:`${r} requires a command string.`};n=s,o+=2;continue}return r==="--help"||r==="-h"?{opts:{senderKey:null,oneShot:null},error:"help"}:{opts:{senderKey:null,oneShot:null},error:`Unknown argument: ${r}`}}return{opts:{senderKey:t,oneShot:n},error:null}}function bd(e){let t=_e.cwd(),n=[`${he(e,"omnish i")} ${w(e,"[options]")}`,Q(e,"Interactive shell \u2014 same commands as WhatsApp/Telegram chat."),"",K(e,"Usage:"),` ${S(e,"omnish i [options]")}`,` ${S(e,"omnish interactive [options]")}`,"",K(e,"Options:"),..._t(e," ",[{left:"--as <sender>",right:"Sender key for cluster commands (wa:+E164 or tg:id). Default: synthetic wa:cli:interactive."},{left:"-c, --command <line>",right:"Run one line and exit (non-interactive)."},{left:"-h, --help",right:"Show this help."}],o=>w(e,o)),"",`${w(e,"Trust:")} ${S(e,"Full local access like your shell; not gated by inbox allowlist.")}`,`${w(e,"Jobs:")} ${S(e,"/bg and /jobs apply only to this REPL session (not the gateway process).")}`,`${w(e,"Files:")} ${S(e,"Use /sendto for files or --text for plain messages through the gateway; plain /send needs a chat peer.")}`,`${w(e,"Gateway:")} ${S(e,"/reload and /updates require omnish run; /sendto requires omnish run for WA/TG delivery.")}`,"",wd(),"",`${w(e,"cwd:")} ${S(e,`session starts at ${t} (change with !cd or ${v().commandPrefix}cd).`)}`];console.log(n.join(`
627
+ `))}function db(e,t){let n=new Set,o=new Set;if(e.channel==="whatsapp-all"||e.channel==="all"){let r=new Set;for(let s of t.allowFrom){let i=te(String(s));i&&!r.has(i)&&(r.add(i),n.add(i))}}if(e.channel==="telegram-all"||e.channel==="all")for(let r of t.telegramAllowFrom){let s=Fe(String(r));if(!s)continue;let i=Number(s);Number.isFinite(i)&&o.add(i)}if(e.channel==="whatsapp")for(let r of e.e164s)n.add(r);return e.channel==="telegram"&&o.add(e.chatId),{waTargets:n,tgTargets:o}}function pb(e){let t="No recipients matched the requested /sendto destination.";return gd(e)?ge()?`${t} Check platform dashboard allowlists (or omnish platform status), or use explicit +E164 / tg:<id>.`:`${t} Set allowFrom / telegramAllowFrom in config.json, or use explicit +E164 / tg:<id>.`:t}async function AP(e){let t=gd(e)?await xu():v();if(e.mode==="text"){let{waTargets:d,tgTargets:m}=db(e,t);if(d.size===0&&m.size===0)return{error:pb(e)};let f=[];for(let h of d){let g=await Wt({op:"sendText",channel:"whatsapp",e164:h,text:e.body});g&&f.push(`[wa:${h}] ${g}`)}for(let h of m){let g=await Wt({op:"sendText",channel:"telegram",chatId:h,text:e.body});g&&f.push(`[tg:${h}] ${g}`)}return f.length>0?{error:f.join(`
628
+ `)}:{error:null,kind:"text",recipientsSent:d.size+m.size}}let n=oe(wr).cwd,o=await as(n,e.selectorPart);if(o.length===0)return{error:`No files matched: ${e.selectorPart}`};let r=await ls(o);if(!r.ok)return{error:r.error};let s=o.map(d=>Ct(d,t.fileSendMaxBytes));for(let d of s)if("error"in d)return{error:d.error};let{waTargets:i,tgTargets:a}=db(e,t);if(i.size===0&&a.size===0)return{error:pb(e)};let l=[];for(let d of i)for(let m of s){if("error"in m)continue;let f=await Wt({op:"sendMedia",channel:"whatsapp",e164:d,absPath:m.absPath,caption:e.caption});f&&l.push(`[wa:${d}] ${m.displayName}: ${f}`)}for(let d of a)for(let m of s){if("error"in m)continue;let f=await Wt({op:"sendMedia",channel:"telegram",chatId:d,absPath:m.absPath,caption:e.caption});f&&l.push(`[tg:${d}] ${m.displayName}: ${f}`)}if(l.length>0)return{error:l.join(`
629
+ `)};let c=i.size+a.size,u=s.length;return{error:null,kind:"media",recipientsSent:c,filesSent:u,messagesSent:c*u}}async function IP(e,t,n,o,r,s,i){let a=e.trim();if(!a)return;let l=yd(a),c=cb(l);if(c!==null||/^\/sendto(\s|$)/i.test(l)||/^sendto(\s|$)/i.test(a)){if(c===null){console.log(P(_e.stderr,`Invalid /sendto.
630
+ `+wd()));return}let m=await AP(c);if(m.error!==null)console.log(P(_e.stderr,m.error));else if(m.kind==="text")console.log(H(_e.stdout,`Sent text to ${m.recipientsSent} recipient(s).`));else{let f=`Sent ${m.filesSent} file(s) to ${m.recipientsSent} recipient(s) (${m.messagesSent} message(s)).`;console.log(H(_e.stdout,f))}return}let u={peerKey:wr,text:e},d=await yr(v(),t,n,o,r,u,s,void 0,i,!1,PP);d!==null&&await OP(d)}async function OP(e){if(e===null)return;if(e.kind==="file"||e.kind==="files"){console.log(H(_e.stdout,["This CLI session has no chat peer to attach to.","Push through the gateway instead, for example:"," /sendto wa:+15551234567 ./my.pdf"," /sendto tg:123456789 ~/photo.jpg -- optional caption","(Requires `omnish run` on this machine.)"].join(`
631
+ `)));return}if(e.kind==="bundle"){for(let n of e.texts??[]){let o=pe(n,"whatsapp").text;o.trim()&&(console.log(o),console.log(""))}for(let n of e.files??[])console.log(H(_e.stdout,`File: ${n.absPath}`));return}if(e.kind==="texts"){for(let n=0;n<e.bodies.length;n++){let o=pe(e.bodies[n],"whatsapp").text;o.trim()&&(n>0&&console.log(""),console.log(o))}return}if(e.kind!=="text")return;let t=pe(e.body,"whatsapp").text;t.trim()&&console.log(t)}async function mb(e){let t=MP(e);if(t.error==="help"){bd(_e.stdout);return}if(t.error&&t.error!==null){console.error(P(_e.stderr,t.error)),console.error(w(_e.stderr,"Try: omnish i --help")),_e.exitCode=1;return}let{senderKey:n,oneShot:o}=t.opts,r=n??wr;se(),Bs(wr,_e.cwd());let s=new cn,i=new Map,a=new Map,l=new Map,c=new An(()=>v(),async(f,h)=>{_e.stdout.write(h),h.endsWith(`
631
632
  `)||_e.stdout.write(`
632
- `)}),u=async f=>{try{await CP(f,s,i,a,l,c,r)}catch(h){console.error(E(_e.stderr,String(h)))}};if(o!==null){await u(o),c.dispose(),s.killAllRunning();return}let d=bP.createInterface({input:_e.stdin,output:_e.stdout}),m=ib.basename(oe(wr).cwd);d.setPrompt(`${m}> `),d.on("line",f=>{u(f).then(()=>{let h=ib.basename(oe(wr).cwd);d.setPrompt(`${h}> `),d.prompt()})}),d.on("close",()=>{c.dispose(),s.killAllRunning(),_e.stdout.write(`
633
- `)}),d.prompt()}function ll(){console.log(`omnish search \u2014 search bundled documentation (offline)
633
+ `)}),u=async f=>{try{await IP(f,s,i,a,l,c,r)}catch(h){console.error(P(_e.stderr,String(h)))}};if(o!==null){await u(o),c.dispose(),s.killAllRunning();return}let d=EP.createInterface({input:_e.stdin,output:_e.stdout}),m=ub.basename(oe(wr).cwd);d.setPrompt(`${m}> `),d.on("line",f=>{u(f).then(()=>{let h=ub.basename(oe(wr).cwd);d.setPrompt(`${h}> `),d.prompt()})}),d.on("close",()=>{c.dispose(),s.killAllRunning(),_e.stdout.write(`
634
+ `)}),d.prompt()}function cl(){console.log(`omnish search \u2014 search bundled documentation (offline)
634
635
 
635
636
  omnish search help
636
637
  omnish search q <topic>
@@ -638,23 +639,23 @@ ${s}`}return n.length>0?n:null}if(/^-t$/i.test(t.trim()))return null}function fd
638
639
  omnish search show <path> e.g. docs/features/tunneling.md
639
640
 
640
641
  Chat: /s q <topic> \xB7 /s <n> \xB7 /s follow <n> (same as /search)
641
- `)}function db(){ll()}function yd(e){let t=(e[0]??"help").toLowerCase(),n=Vu(e.slice(1).join(" "));if(t==="help"||t==="-h"||t==="--help"){ll();return}if(t==="q"||t==="search"){if(!n||/^help$/i.test(n)){console.error("[omnish] Usage: omnish search q <topic>"),process.exitCode=1;return}let o=/^q\s+([\s\S]+)$/i.exec(n),r=(o?o[1]:n).trim();if(!r){console.error("[omnish] Usage: omnish search q <topic>"),process.exitCode=1;return}let i=qa(r).map(za);if(ww(i),i.length===0){console.log(`Search: ${r}
642
+ `)}function hb(){cl()}function kd(e){let t=(e[0]??"help").toLowerCase(),n=Zu(e.slice(1).join(" "));if(t==="help"||t==="-h"||t==="--help"){cl();return}if(t==="q"||t==="search"){if(!n||/^help$/i.test(n)){console.error("[omnish] Usage: omnish search q <topic>"),process.exitCode=1;return}let o=/^q\s+([\s\S]+)$/i.exec(n),r=(o?o[1]:n).trim();if(!r){console.error("[omnish] Usage: omnish search q <topic>"),process.exitCode=1;return}let i=za(r).map(Ka);if(vw(i),i.length===0){console.log(`Search: ${r}
642
643
  (no results)`);return}console.log(`Search: ${r}
643
644
  `),i.forEach((a,l)=>{let c=a.relatedCommands[0];console.log(`${l+1}. ${a.title}${c?` \u2014 ${c}`:""}`),console.log(` ${a.path}`),a.summary&&console.log(` ${a.summary.slice(0,120)}`)}),console.log(`
644
- Show: omnish search show <n>`);return}if(t==="show"){let o=e.slice(1).join(" ").trim();if(!o){console.error("[omnish] Usage: omnish search show <n> | <doc-path>"),process.exitCode=1;return}if(/^\d+$/.test(o)){let s=bw(o);if(!s){console.error("[omnish] No result #"+o+". Run omnish search q first."),process.exitCode=1;return}let i=fs(s.id);if(!i){console.error("[omnish] Entry missing from index."),process.exitCode=1;return}ub(i,Number.parseInt(o,10));return}let r=Ka(o);if(!r){console.error(`[omnish] No doc at path "${o}".`),process.exitCode=1;return}ub(r);return}console.error(`[omnish] Unknown search subcommand "${t}". Try: omnish search help`),process.exitCode=1}function pb(e){yd(e)}function ub(e,t){if(!e)return;let n=Ga(Bn.repoUrl,e.path),o=t!==void 0?`${t}. ${e.title}`:e.title;if(console.log(o),console.log(e.path),console.log(n),console.log(""),console.log(Ya(e)),console.log(""),e.relatedCommands.length){console.log("Try:");for(let r of e.relatedCommands.slice(0,8))console.log(` ${r}`)}}TP.setDefaultResultOrder("ipv4first");function gb(e){let t=process.stdout,n=ge();n?e.push(K(t,"Attached mode (platform credentials detected):"),` ${w(t,"url:")} ${S(t,n.platformUrl)} ${Q(t,`[${ar()}]`)}`,` ${w(t,"token:")} ${S(t,Vn("platformToken",n.token))} ${Q(t,`[${ir()}]`)}`,w(t," Messengers run on the platform; allowlist is set on the dashboard. Run: omnish platform probe"),""):e.push(w(t,"Platform attached mode: omnish config add platform_url <url> platform_token <token>"),w(t," then omnish platform probe && omnish run \u2014 see omnish help platform"),"")}function yb(){let e=process.stdout,t=[`${Re(e,"omnish run")} ${w(e,"[options]")}`,Q(e,"Listen for DMs and run shell commands from allowlisted chats."),"",K(e,"Usage:"),` ${S(e,"omnish run [options]")}`,"",K(e,"Options:"),..._t(e," ",[{left:"-d, --background",right:"Start the gateway detached; log to --log-file (default: <data>/logs/gateway.log)."},{left:"-f, --foreground",right:"Run in the foreground (default for run; same as omitting -d)."},{left:"--log-file <path>",right:`Append stdout/stderr when background (default: ${He}).`},{left:"-vb, --verbose",right:"Baileys/gateway debug logs on stderr (legacy: OMNISH_VERBOSE=1)."},{left:"-h, --help",right:"Show this help."}],n=>w(e,n)),"",w(e,"Background by default: omnish start \u2014 stop with omnish stop."),"",`${S(e,"Config reload:")} ${w(e,"while the gateway runs, edit config then send /reload or /restart from an allowlisted chat (no restart needed for many keys). /updates checks npm (and optional updateInfoUrl).")}`,""];gb(t),console.log(t.join(`
645
- `))}function wb(){let e=process.stdout,t=[`${Re(e,"omnish start")} ${w(e,"[options]")}`,Q(e,"Start the gateway in the background (detached). Pair with omnish stop."),"",K(e,"Usage:"),` ${S(e,"omnish start [options]")}`,"",K(e,"Options:"),..._t(e," ",[{left:"-f, --foreground",right:"Run in the foreground instead of detached (same as omnish run)."},{left:"-d, --background",right:"Start detached (default for start; same as omitting -f)."},{left:"--log-file <path>",right:`Append stdout/stderr when background (default: ${He}).`},{left:"-vb, --verbose",right:"Baileys/gateway debug logs on stderr (legacy: OMNISH_VERBOSE=1)."},{left:"-h, --help",right:"Show this help."}],n=>w(e,n)),"",w(e,"Equivalent background launch: omnish run -d. Foreground default: omnish run."),"",`${S(e,"Stop:")} ${w(e,"omnish stop reads")} ${S(e,de)} ${w(e,"and signals the background process.")}`,"",`${S(e,"Config reload:")} ${w(e,"while the gateway runs, edit config then send /reload or /restart from an allowlisted chat (no restart needed for many keys). /updates checks npm (and optional updateInfoUrl).")}`,""];gb(t),console.log(t.join(`
646
- `))}function bb(){let e=process.stdout,t=[{left:"omnish link [--force]",right:"WhatsApp: scan QR (Linked devices). --force wipes session first."},{left:"omnish link --tg <bot_token>",right:"Telegram: save token to config; gatewayMode telegram or both if WhatsApp is linked."}],n=t.map(i=>w(e,i.left)),o=Math.max(...n.map(Gs)),r=t.map((i,a)=>El(" ",o,n[a],S(e,i.right))),s=[`${Re(e,"omnish link")} ${w(e,"[options]")}`,Q(e,"Connect WhatsApp (QR) or save a Telegram bot token."),"",K(e,"Usage:"),` ${S(e,"omnish link [--force]")}`,` ${S(e,"omnish link --tg <bot_token>")}`,"",K(e,"Modes:"),...r,` ${Q(e,"Do not combine --tg with --force.")}`,"",K(e,"Options:"),..._t(e," ",[{left:"-f, --force",right:"WhatsApp only: delete saved session before pairing."},{left:"-vb, --verbose",right:"Baileys debug logs on stderr during pairing (legacy: OMNISH_VERBOSE=1)."},{left:"-h, --help",right:"Show this help."}],i=>w(e,i)),"",`${S(e,"Next:")} ${he(e,"omnish allow tg:<your_user_id>")} ${w(e,"then")} ${he(e,"omnish run")}`,`${w(e,"Config:")} ${S(e,U)}`,""];ge()&&s.push(K(e,"Platform attached mode:"),w(e," omnish link on this host is for standalone only. Link WhatsApp on the platform dashboard or:"),` ${S(e,"omnish platform import-whatsapp")} ${w(e,"(after a local omnish link, with omnish run stopped)")}`,w(e," Telegram: set bot token on the platform dashboard, not --tg here."),""),console.log(s.join(`
647
- `))}function PP(e){let t=!1,n=null;for(let o=0;o<e.length;o++){let r=e[o]??"";if(r==="--help"||r==="-h")return{kind:"help"};if(r==="--force"||r==="-f"){t=!0;continue}if(r==="-vb"||r==="--verbose"){fl(!0);continue}if(r==="--tg"||r==="--telegram"){let s=e[o+1];if(!s||s.startsWith("-"))return{kind:"error",message:"[omnish] --tg requires a bot token (from @BotFather)."};n=s,o++;continue}if(r.startsWith("--tg=")||r.startsWith("--telegram=")){let s=r.indexOf("="),i=r.slice(s+1).trim();if(!i)return{kind:"error",message:"[omnish] --tg= requires a non-empty bot token."};n=i;continue}return{kind:"error",message:`[omnish] unknown link argument: ${r}
648
- Try: omnish link --help`}}return n!==null?t?{kind:"error",message:"[omnish] --force applies to WhatsApp only; do not combine with --tg."}:{kind:"tg",token:n}:{kind:"wa",force:t}}function bd(){let e=process.stdout,t=`${Re(e,"omnish")} ${w(e,`v${gt()}`)}`,n=[{left:"link [--force] [--tg <token>]",right:"WhatsApp (QR) or Telegram bot token \u2014 omnish link --help"},{left:"run [options]",right:"Listen for DMs in foreground; -d for background \u2014 omnish run --help"},{left:"start [options]",right:"Start gateway in background (pair with stop); -f for foreground \u2014 omnish help start"},{left:"stop",right:`Stop background gateway (pidfile: ${de})`},{left:"service <subcommand>",right:"Boot install hints, logs, systemd/LaunchAgent \u2014 omnish service help"},{left:"pull <subcommand>",right:"Media URL tools (yt-dlp, ffmpeg, Whisper) \u2014 omnish pull help"},{left:"logout",right:"Delete saved WhatsApp session"},{left:"allow +<E164> | tg:<id>",right:"Add allowlist entry"},{left:"deny +<E164> | tg:<id>",right:"Remove allowlist entry"},{left:"status [--check-updates]",right:"Channels, identity, allowlists, jobs, security, cluster (if enabled)"},{left:"commands",right:"Chat commands for allowlisted users (same as /help)"},{left:"security [--json]",right:"Configuration security report (JSON for scripts)"},{left:"cluster [status | use <sender> <label-or-id>]",right:"Per-sender machine bindings"},{left:"i | interactive [options]",right:"Local REPL (chat commands; /sendto needs omnish run)"},{left:"ui [options]",right:"Browser setup UI on LAN (token-gated) \u2014 omnish ui --help"},{left:"config <add|show|edit|delete>",right:"Manage config.json (platform URL/token, tunnel, gateway) \u2014 omnish config help"},{left:"platform <subcommand>",right:"Attached mode: configure, probe, import WA \u2014 omnish help platform"},{left:"tunnel <subcommand>",right:"Expose local HTTP/TCP via omnish relay \u2014 omnish tunnel help"},{left:"search <subcommand>",right:"Search bundled guides offline \u2014 omnish search help (same index as /s in chat)"},{left:"docs <subcommand>",right:"Alias for omnish search \u2014 omnish search help"}],o=[{left:"-v, --version",right:"Print version and exit."},{left:"-h, --help",right:"Show this help (same as omnish help)."}],r=[t,Q(e,"Allowlisted inbox \u2192 your real shell. No AI."),"",K(e,"Usage:"),` ${S(e,"omnish [options] <command> [args...]")}`,"",K(e,"Options:"),..._t(e," ",o,s=>w(e,s)),"",K(e,"Commands:"),..._t(e," ",n,s=>w(e,s)),"",`${w(e,"Config:")} ${S(e,`${U} \u2014 gatewayMode: "whatsapp" | "telegram" | "both"`)}`,`${w(e,"Platform:")} ${S(e,"platform_url + platform_token \u2192 attached omnish run (omnish help platform)")}`,`${w(e,"Verbose:")} ${S(e,"omnish run --verbose (legacy: OMNISH_VERBOSE=1, WHATSVERBOSE=1)")}`,`${w(e,"Env:")} ${S(e,"TELEGRAM_BOT_TOKEN (optional override)")}`,`${w(e,"Data:")} ${S(e,"~/.omnish by default; ~/.whatslive reused if it already exists. OMNISH_HOME overrides.")}`,`${w(e,"See also:")} ${S(e,"https://omnish.dev")}`,""];console.log(r.join(`
649
- `))}function $P(e){let t=(e??"").trim().toLowerCase(),n=process.stdout;if(!t){bd();return}switch(t){case"link":bb();return;case"run":yb();return;case"start":wb();return;case"service":Sb();return;case"pull":bc();return;case"i":case"interactive":gd(n);return;case"ui":vb();return;case"config":tl(n);return;case"platform":sl(n);return;case"search":ll();return;case"docs":db();return;default:console.error(E(process.stderr,`No detailed help for "${e}". Try: omnish help`)),process.exitCode=1}}function MP(e,t){let n=t.defaultBackground,o="",r=!1,s=!1,i=`omnish ${t.command} --help`;for(let l=0;l<e.length;l++){let c=e[l]??"";if(c==="-d"||c==="--background")n=!0;else if(c==="-f"||c==="--foreground")n=!1;else if(c==="-vb"||c==="--verbose")s=!0;else if(c==="--log-file"||c==="--log"){let u=e[++l];u||(console.error(E(process.stderr,"--log-file requires a path.")),process.exit(1)),o=u}else if(c==="--help"||c==="-h")r=!0;else{let u=process.stderr;console.error(E(u,`unknown ${t.command} option: ${c}`)),console.error(E(u,`Try: ${i}`)),process.exit(1)}}let a=o.trim()!==""?wd.isAbsolute(o)?o:wd.resolve(process.cwd(),o):He;return{background:n,logFile:a,help:r,verbose:s}}async function mb(e,t){let o=MP(t,{defaultBackground:e==="start",command:e});if(o.help){e==="start"?wb():yb();return}if(o.verbose&&fl(!0),o.background){AP(o.logFile,o.verbose);return}await FP()}function AP(e,t){let n=ha(e,{verbose:t});n.ok||(console.error(E(process.stderr,n.message)),process.exit(1));let o=process.stdout;console.log(`${H(o,`gateway started in background (pid ${n.pid}).`)} ${w(o,`Log: ${e}`)}`)}function IP(){let e=ga();switch(e.outcome){case"no_pidfile":console.error(E(process.stderr,`no pidfile at ${de} \u2014 is a background gateway running?`)),process.exitCode=1;return;case"invalid_pidfile":console.error(E(process.stderr,"invalid pidfile.")),process.exitCode=1;return;case"stale_cleaned":console.log(H(process.stdout,`process ${e.pid} is not running; removing stale pidfile.`));return;case"sent_signal":console.log(H(process.stdout,`sent SIGTERM to gateway (pid ${e.pid}).`));return;case"taskkill_ok":console.log(H(process.stdout,`stopped gateway (pid ${e.pid}) using taskkill.`));return;case"failed":console.error(E(process.stderr,e.message)),process.exitCode=1;return}}function fb(){if(process.env.OMNISH_BACKGROUND_GATEWAY==="1")try{Hn.readFileSync(de,"utf8").trim()===String(process.pid)&&Hn.unlinkSync(de)}catch{}}function OP(e){return e.length<=8?"(set)":`${e.slice(0,4)}\u2026${e.slice(-4)}`}function LP(){if(!Hn.existsSync(de))return"gateway process: not running (no pid file)";let e=Hn.readFileSync(de,"utf8").trim(),t=Number(e);if(!Number.isFinite(t)||t<=0)return"gateway process: invalid pid file";try{return process.kill(t,0),`gateway process: running (pid ${t})`}catch{return`gateway process: not running (stale pid ${t} in pid file)`}}var kb=120;function Sb(){let e=process.stdout,t=[{left:"help",right:"This help."},{left:"instructions",right:"Copy-paste install steps for this machine."},{left:"status",right:"Data dir, pidfile, Node + entry script."},{left:"logs [n]",right:`Tail default gateway log (default 80 lines, max ${kb}).`},{left:"install",right:"Write user systemd / LaunchAgent unit (needs serviceInstallFromChat=true)."},{left:"uninstall",right:"Remove that unit (same gate as install)."}],n=[`${Re(e,"omnish service")} ${w(e,"<subcommand>")}`,Q(e,"Boot integration, crash restart policy, and logs (same ideas as /service in chat)."),"",K(e,"Usage:"),` ${S(e,"omnish service <subcommand>")}`,"",K(e,"Subcommands:"),..._t(e," ",t,o=>w(e,o)),"",K(e,"Restart and reload:"),` ${S(e,"Process")} ${w(e,"\u2014 Linux user unit: Restart=on-failure, RestartSec=5. macOS: KeepAlive. Full restart loads config from disk.")}`,` ${S(e,"Config live")} ${w(e,"\u2014 allowlisted chat while gateway runs: /reload or /restart")}`,"",`${w(e,"Docs:")} ${S(e,"docs/guides/background-and-boot.md")} ${Q(e,"\xB7")} https://omnish.dev`,""];console.log(n.join(`
650
- `))}function NP(e){let t=process.stdout,n=process.stderr,o=(e[0]??"help").toLowerCase();if(o==="help"||o==="--help"||o==="-h"){Sb();return}if(o==="instructions"){let r=sn();if(r.error){console.error(E(n,r.error)),process.exitCode=1;return}console.log(Zs(r));return}if(o==="status"){let r=sn(),s=(()=>{try{return Hn.existsSync(de)?`gateway.pid: ${Hn.readFileSync(de,"utf8").trim()}`:"gateway.pid: (missing)"}catch(d){return`gateway.pid: (read error: ${String(d)})`}})(),i=process.env.OMNISH_BACKGROUND_GATEWAY==="1"?"This process: background gateway (OMNISH_BACKGROUND_GATEWAY=1).":"This process: CLI (not the gateway \u2014 run omnish service status on the host where the gateway runs for live pid info).",a=typeof process.env.OMNISH_HOME=="string"&&process.env.OMNISH_HOME.trim()?`OMNISH_HOME env: ${process.env.OMNISH_HOME.trim()}`:"OMNISH_HOME env: (not set \u2014 using default data dir)",l=r.error?r.error:`Node: ${r.nodePath}
651
- Script: ${r.scriptPath}`,u=v().serviceInstallFromChat?"Install from CLI/chat: enabled (omnish service install / /service install).":"Install from CLI/chat: off \u2014 set serviceInstallFromChat true in config (same trust as shell).";console.log(Re(t,"omnish service status")),console.log(""),console.log(`${w(t,"platform:")} ${S(t,process.platform)}`),console.log(`${w(t,"session:")} ${S(t,i)}`),console.log(`${w(t,"env:")} ${S(t,a)}`),console.log(`${w(t,"data dir:")} ${S(t,W)}`),console.log(`${w(t,"pidfile:")} ${S(t,s)}`),console.log(`${w(t,"default log:")} ${S(t,He)}`),console.log(""),console.log(l),console.log(""),console.log(S(t,u));return}if(o==="logs"){let r=e.length>=2?Number.parseInt(e[1],10):80,s=Number.isFinite(r)&&r>0?Math.min(r,kb):80,i=wi(He,s);console.log(`${w(t,"file:")} ${S(t,He)}`),console.log(`${w(t,"lines:")} ${S(t,String(s))}`),console.log(""),console.log(i);return}if(o==="install"){if(!v().serviceInstallFromChat){console.error(E(n,"Install is disabled. Set serviceInstallFromChat to true in config (same trust as shell), then run again.")),process.exitCode=1;return}let s=hi();s.ok?console.log(H(t,s.detail)):(console.error(E(n,s.detail)),process.exitCode=1);return}if(o==="uninstall"){if(!v().serviceInstallFromChat){console.error(E(n,"Uninstall is disabled. Set serviceInstallFromChat to true in config or remove the unit file on the host manually.")),process.exitCode=1;return}let s=gi();s.ok?console.log(H(t,s.detail)):(console.error(E(n,s.detail)),process.exitCode=1);return}console.error(E(n,`Unknown subcommand "${o}". Try: omnish service help`)),process.exitCode=1}function _P(e){let t=e.trim();if(!t)return null;let n=t.toLowerCase();if(n.startsWith("tg:")||n.startsWith("telegram:")){let s=Fe(t);return s?`tg:${s}`:null}let o=n.startsWith("wa:")?t.slice(3):t,r=te(o);return r?`wa:${r}`:null}async function FP(){let e=null,t=Za();t.ok||(console.error(E(process.stderr,t.message)),process.exit(1));let n=ge();if(n){await Rw(n);return}let o=v(),r=o.gatewayMode,s=r==="whatsapp"||r==="both",i=r==="telegram"||r==="both",a=Ie(o),l=tn(o),c=process.stderr,u=Rp(l,"warn");if(u.length>0&&(console.warn(`${H(c,`Security (${u.length} finding(s)):`)}
652
- `),console.warn(Il(u,c))),process.env.OMNISH_BACKGROUND_GATEWAY==="1")try{Hn.writeFileSync(de,`${process.pid}
653
- `,{mode:384})}catch(I){P.warn({err:String(I)},"could not write gateway pidfile")}let d=(I,z)=>{let G=v();if(!G.clusterEnabled)return I;let Y=ht(),ae=(G.clusterLabel??"").trim()||hb.hostname(),ne=null;if(z.startsWith("tg:"))ne=z;else if(z){let we=te(z);we&&(ne=`wa:${we}`)}let Ye=ne?nn(G,ne):null;return mm(I,{nodeId:Y,label:ae,role:G.clusterRole,activeNodeId:Ye?.nodeId??""})},m=new Map,f=new Map,h=new Map,g=null,y={stop:null,sendText:null,sendMedia:null},k=null,b=!1,x=async(I,z)=>{if(I.startsWith("wa:")){let G=I.slice(3);g&&await g.sendText(G,z)}else if(I.startsWith("tg:")){let G=Number(I.slice(3));y.sendText&&Number.isFinite(G)&&await y.sendText(G,p(z))}},C=new cn({onJobExit(I){I.notifyPeerKey&&x(I.notifyPeerKey,la(I))}}),M={onPlainTextAgentDaemon(I,z){let G=v(),Y=ra(G,I,z);Y.ok||x(I,`[agent] ${Y.error}`)},onPlainTextLlmFallback(I,z){qo(v(),I,z,G=>x(I,G))},getAgentDaemonStatus(){return ia()},stopAgentDaemon(I){return Zr(v(),I)},resetAgentDaemon(I){return sa(v(),I)},sendToPeer:x},R=async(I,z)=>{if(I.startsWith("wa:")){let G=I.slice(3);g&&await g.sendMedia(G,z)}else if(I.startsWith("tg:")){let G=Number(I.slice(3));y.sendMedia&&Number.isFinite(G)&&await y.sendMedia(G,z)}},N=()=>new An(()=>v(),x),A=N();k=A;let q,ee={async reload(){try{P.info("gateway reload requested from chat"),await y.stop?.().catch(()=>{}),y.stop=null,y.sendText=null,y.sendMedia=null;let I=v(),z=I.gatewayMode==="telegram"||I.gatewayMode==="both",G=Ie(I);if(z&&G){let ne=await cu(G,()=>v(),q,{decorate:d});y.sendText=ne.sendText,y.sendMedia=ne.sendMedia,y.stop=ne.stop}let Y=["Reload complete.",`gatewayMode: ${I.gatewayMode}`,z&&G?"Telegram bot is running with the current token.":"Telegram bot is stopped (enable telegram/both + token if you want it).","Allowlists, shell, app session limits, and timeouts are read from disk on each command."].join(`
654
- `),ae=Aa(ss());return{ok:!0,summary:ae?`${Y}
655
-
656
- Updates (last check): ${ae}`:Y}}catch(I){return{ok:!1,error:String(I)}}}};if(q=async(I,z)=>{let G=v();await hs(G,C,m,f,h,I,A,ee,I.peerKey,M,Zu({sendTg:z},{surface:"telegram"}),{surface:"telegram"})},i){let I=await cu(a,()=>v(),q,{decorate:d});y.sendText=I.sendText,y.sendMedia=I.sendMedia,y.stop=I.stop}ya({getCfg:()=>v(),getWaOutbound:()=>g,getTgSendMedia:()=>y.sendMedia,getTgSendText:()=>y.sendText,sendPeerMedia:R});let ce=null;{let I=v();if(I.webhookEnabled){let z=I.webhookToken||EP.randomBytes(32).toString("hex");I.webhookToken||$({webhookToken:z}),ce=wa({port:I.webhookPort,host:I.webhookHost,token:z},{sendToPeer:x,getDefaultPeerKey:()=>{let Y=v();return Y.allowFrom.length>0?`wa:${mn(Y.allowFrom[0])}`:Y.telegramAllowFrom.length>0?`tg:${Y.telegramAllowFrom[0]}`:null}}).stop}}e=Ia({getRunningVersion:gt,getConfig:v,log:P});let Pe=na({getConfig:v,sendToPeer:x,sendMediaToPeer:R}),Ke=Yi({getConfig:v,sendToPeer:x}),F=Us({getConfig:v,sendToPeer:x}),ie=!i,B=()=>{b=!0,Pe(),Ke(),F(),e?.(),e=null,ce?.(),fb(),sr(),aa(),y.stop?.().catch(()=>{}),k?.dispose(),C.killAllRunning(),mo().stopAll().catch(()=>{}),console.error(`
657
- ${E(process.stderr,"shutting down\u2026")}`),process.exit(0)};if(process.on("SIGINT",B),process.on("SIGTERM",B),s)for(;!b;){let I=!1,z;try{z=await ua({printQr:!1,verbose:xo()}),await uo(da(z),3e5,"Gateway: timed out waiting for WhatsApp connection (5 min).")}catch(Y){console.error(E(process.stderr,`connect failed: ${String(Y)}`)),await new Promise(ae=>setTimeout(ae,5e3));continue}g=Pg(z,{decorate:d});let G=vg(z,async Y=>{let ae=v(),ne=Y.fromE164||te(Y.fromJid)||"",Ye=Em(Y),we=`wa:${ne}`;await hs(ae,C,m,f,h,Ye,A,ee,we,M,Zu({sendWaText:(re,De)=>g.sendText(re,De),sendWaMedia:(re,De)=>g.sendMedia(re,De)},{surface:"whatsapp",waJid:Y.fromJid}),{surface:"whatsapp"})});if(await new Promise(Y=>{let ae=ne=>{ne.connection==="close"&&(iu(ne.lastDisconnect)===co.loggedOut&&(I=!0),z.ev.off("connection.update",ae),Y())};z.ev.on("connection.update",ae)}),G(),ie&&(A.dispose(),A=N(),k=A),nr(z),g=null,I&&(console.error(E(process.stderr,"session logged out. Run `omnish link` again.")),Pe(),Ke(),e?.(),e=null,fb(),sr(),y.stop?.().catch(()=>{}),process.exit(1)),b)break;await new Promise(Y=>setTimeout(Y,3e3))}else for(;!b;)await new Promise(I=>setTimeout(I,500));Pe(),Ke(),e?.(),sr(),y.stop?.().catch(()=>{}),A.dispose()}function DP(e){let t=!1,n="0.0.0.0",o=3789,r;for(let s=0;s<e.length;s++){let i=e[s];if(i==="--help"||i==="-h"){t=!0;continue}if(i==="--host"||i==="-H"){let l=e[++s];l||(console.error(E(process.stderr,"--host requires an address (e.g. 127.0.0.1).")),process.exit(1)),n=l;continue}if(i==="--port"||i==="-p"){let l=e[++s],c=Number.parseInt(l??"",10);Number.isFinite(c)||(console.error(E(process.stderr,"--port requires a number.")),process.exit(1)),o=c;continue}if(i==="--token"||i==="-t"){let l=e[++s];(!l||l.startsWith("-"))&&(console.error(E(process.stderr,"--token requires a secret string.")),process.exit(1)),r=l;continue}let a=process.stderr;console.error(E(a,`unknown ui argument: ${i}`)),console.error(E(a,"Try: omnish ui --help")),process.exit(1)}return{help:t,host:n,port:o,token:r}}function vb(){let e=process.stdout,t=[`${Re(e,"omnish ui")} ${w(e,"[options]")}`,Q(e,"Serve the browser setup panel on your LAN (token-gated). Same trust as editing config on disk."),"",K(e,"Usage:"),` ${S(e,"omnish ui [options]")}`,"",K(e,"Options:"),..._t(e," ",[{left:"--host <addr>",right:"Bind address (default 0.0.0.0 \u2014 reachable on LAN). Use 127.0.0.1 for loopback only."},{left:"--port <n>",right:"TCP port (default 3789)."},{left:"--token <secret>",right:`Set or rotate setup token (saved to ${At}).`},{left:"-h, --help",right:"This help."}],n=>w(e,n)),"",`${w(e,"Warning:")} ${ke(e,"Listening on all interfaces exposes a remote control panel on your network \u2014 use a trusted LAN.")}`,""];console.log(t.join(`
658
- `))}async function WP(){let[,,e,...t]=process.argv;if(e==="--version"||e==="-v"||e==="-V"){let n=process.stdout;console.log(`${Re(n,"omnish")} ${w(n,gt())}`);return}if(e==="--help"||e==="-h"){bd();return}if(e==="help"){$P(t[0]);return}switch(se(),e){case"link":{let n=PP(t);if(n.kind==="help"){bb();return}if(n.kind==="error"){let o=process.stderr,r=n.message.replace(/^\[omnish\]\s*/,"");console.error(E(o,r)),process.exitCode=1;return}if(n.kind==="tg"){let o=n.token.trim(),r=process.stderr,s=process.stdout;if(!Ot(o)){console.error(E(r,"That does not look like a Telegram bot token (expect digits:secret from @BotFather).")),process.exitCode=1;return}fn(o);let i=St()?"both":"telegram";Is(i),console.log([`${he(s,"Telegram")} ${S(s,"bot token saved to")} ${w(s,U)}`,`${w(s,"gatewayMode:")} ${he(s,i)}`,"",S(s,"Next:"),` ${w(s,"1.")} ${S(s,"Find your numeric user id (e.g. t.me/userinfobot), then:")} ${he(s,"omnish allow tg:<id>")}`,` ${w(s,"2.")} ${he(s,"omnish run")}`,""].join(`
659
- `));return}await Ng({verbose:xo(),force:n.force});return}case"run":{await mb("run",t);return}case"start":{await mb("start",t);return}case"stop":IP();return;case"logout":{try{Hn.rmSync(le,{recursive:!0,force:!0}),console.log(H(process.stdout,"Session removed. Run `omnish link` to pair again."))}catch(n){console.error(E(process.stderr,`logout failed: ${String(n)}`)),process.exitCode=1}return}case"allow":{let n=t[0],o=process.stdout,r=process.stderr;if(!n){console.error(E(r,"Usage: omnish allow +<E164> or omnish allow tg:<user_id>")),process.exitCode=1;return}try{let s=Ms(n);console.log(`${w(o,"allowFrom:")} ${S(o,s.allowFrom.join(", ")||"(empty)")}`),console.log(`${w(o,"telegramAllowFrom:")} ${S(o,s.telegramAllowFrom.join(", ")||"(empty)")}`)}catch(s){console.error(E(r,String(s).replace(/^\[omnish\]\s*/,""))),process.exitCode=1}return}case"deny":{let n=t[0],o=process.stdout,r=process.stderr;if(!n){console.error(E(r,"Usage: omnish deny +<E164> or omnish deny tg:<user_id>")),process.exitCode=1;return}try{let s=As(n);console.log(`${w(o,"allowFrom:")} ${S(o,s.allowFrom.join(", ")||"(empty)")}`),console.log(`${w(o,"telegramAllowFrom:")} ${S(o,s.telegramAllowFrom.join(", ")||"(empty)")}`)}catch(s){console.error(E(r,String(s).replace(/^\[omnish\]\s*/,""))),process.exitCode=1}return}case"i":case"interactive":{await cb(t);return}case"status":{let n=process.stdout,o=v(),r=t.includes("--check-updates"),s=new cn().list(),i=s.filter(h=>h.status==="running").length,a=Ie(o),l=tn(o),c=o.gatewayMode==="whatsapp"||o.gatewayMode==="both",u=o.gatewayMode==="telegram"||o.gatewayMode==="both",d=St(),m=d?Ig(le):null,f=[];if(f.push(`${Re(n,"omnish")} ${w(n,gt())}`,`${w(n,"gatewayMode:")} ${S(n,o.gatewayMode)}`,`${w(n,"data dir:")} ${S(n,wd.dirname(le))}`,"",`${w(n,"gateway process:")} ${S(n,LP().replace(/^gateway process: /,""))}`,"",Sn(n),he(n,"whatsapp"),` ${w(n,"in use:")} ${c?S(n,"yes"):ke(n,"no (gatewayMode is telegram-only)")}`),c){let h=d?S(n,`linked (${le})`):ke(n,"missing \u2014 run omnish link");f.push(` ${w(n,"session:")} ${h}`),d&&m&&f.push(` ${w(n,"linked as:")} ${S(n,m)}`),d&&!m&&f.push(` ${w(n,"linked as:")} ${Q(n,"(not in creds yet \u2014 try again after omnish link completes)")}`)}if(f.push(` ${K(n,"Allowed")}`),o.allowFrom.length===0)f.push(` ${Q(n,"(none)")}`);else for(let h of o.allowFrom)f.push(` ${w(n,"whatsapp:")} ${S(n,h)}`);if(f.push("",Sn(n),he(n,"telegram")),f.push(` ${w(n,"in use:")} ${u?S(n,"yes"):ke(n,"no (gatewayMode is whatsapp-only)")}`),u){let h=a?S(n,OP(a)):ke(n,"(none) \u2014 omnish link --tg <token> or TELEGRAM_BOT_TOKEN");f.push(` ${w(n,"bot token:")} ${h}`)}if(f.push(` ${K(n,"Allowed")}`),o.telegramAllowFrom.length===0)f.push(` ${Q(n,"(none)")}`);else for(let h of o.telegramAllowFrom)f.push(` ${w(n,"telegram:")} ${S(n,h)}`);if(f.push("",Sn(n),...await Aw(n)),f.push("",Sn(n),`${he(n,"jobs")} ${w(n,`(recent): ${s.length} total, ${i} running`)}`,Pp(l,n)),console.log(f.join(`
660
- `)),r){let h=await is(gt(),o),g=Mr(h);console.log(""),console.log(Sn(n)),console.log(pe(g,"whatsapp").text)}if(o.clusterEnabled){let h=ht(),g=Te(),y=Object.keys(g.senderBindings).length,k=Object.keys(o.clusterSenderBindings??{}).length;console.log(""),console.log(Sn(n)),console.log(he(n,"cluster")),console.log(` ${w(n,"\xB7")} ${S(n,`enabled \xB7 label ${o.clusterLabel||hb.hostname()} \xB7 bindings ${y} chat / ${k} default`)}`),console.log(` ${w(n,"node:")} ${S(n,`${h.slice(0,8)}\u2026`)}`)}return}case"commands":{let n=process.stdout,o=v();console.log(vp($o(o),n)),console.log(""),console.log(Sn(n)),console.log(S(n,"Keys editable from chat via /config set (same trust as shell):")),console.log(w(n,xn.join(", "))),console.log(`${w(n,"See also:")} ${S(n,U)}`);return}case"cluster":{let n=process.stdout,o=process.stderr,r=(t[0]??"status").toLowerCase();if(r==="status"){let s=v(),i=ht();if(console.log(`${w(n,"clusterEnabled:")} ${S(n,String(s.clusterEnabled))}`),console.log(`${w(n,"clusterLabel:")} ${S(n,s.clusterLabel||"(hostname)")}`),console.log(`${w(n,"clusterRole:")} ${S(n,`${s.clusterRole} (informational; no longer used to gate traffic)`)}`),console.log(`${w(n,"node id:")} ${he(n,i)}`),s.clusterEnabled){let a=Te(),l=ec(s,null);console.log(""),console.log(S(n,l.wa.replace(/\*([^*]+)\*/g,"$1").replace(/`([^`]+)`/g,"$1"))),console.log(""),console.log(Zl(a,s,null));let c=Object.keys(a.senderBindings).length,u=Object.entries(s.clusterSenderBindings??{});if(c>0){console.log(""),console.log(he(n,"Chat bindings (cluster-local.json)"));for(let[d,m]of Object.entries(a.senderBindings))console.log(` ${w(n,d)} ${Q(n,"->")} ${S(n,`${m.nodeId} (${m.source}, since ${m.sinceIso})`)}`)}if(u.length>0){console.log(""),console.log(he(n,"Config defaults (clusterSenderBindings)"));for(let[d,m]of u)console.log(` ${w(n,d)} ${Q(n,"->")} ${S(n,m)}`)}}else console.log(""),console.log(ke(n,"(cluster disabled \u2014 /config set clusterEnabled true to enable, then /c use <label-or-id> from each sender)"));return}if(r==="use"||r==="bind"){let s=t[1],i=t.slice(2).join(" ").trim();if(!s||!i){console.error(E(o,"Usage: omnish cluster use <senderE164|tg:id> <label-or-id>")),process.exitCode=1;return}let a=_P(s);if(!a){console.error(E(o,`Could not parse sender "${s}". Use +E164 (WhatsApp) or tg:<user_id> (Telegram).`)),process.exitCode=1;return}let{state:l,resolved:c}=Tm(a,i);if(!c.ok){if(c.reason==="ambiguous-label"){let d=(c.matches??[]).map(m=>`${m.nodeId}(${m.label})`).join(", ");console.error(E(o,`Label "${i}" matches multiple machines: ${d}. Use the 8-character id.`))}else console.error(E(o,`No machine matches "${i}". Run /c status from the chat first to populate the roster, or pass an 8-character node id.`));process.exitCode=1;return}console.log(`${he(n,"cluster:")} ${S(n,`${a} -> ${c.peer.nodeId} (${c.peer.label}).`)}`);let u=v();console.log(""),console.log(Zl(l,u,a));return}if(r==="here"){console.error(E(o,"omnish cluster here is no longer available. Use: omnish cluster use <senderE164|tg:id> <label-or-id>")),console.error(E(o,"Or send /c here from the controller's chat on the machine you want to bind.")),process.exitCode=1;return}console.error(E(o,"Usage: omnish cluster [status | use <sender> <label-or-id>]")),process.exitCode=1;return}case"security":{let n=v(),o=tn(n),r=t.includes("--json");console.log(r?Tp(o):Il(o,process.stdout)),Pr(o)&&(process.exitCode=1);return}case"service":{NP(t);return}case"pull":{await xf(t);return}case"media-exec":{await Rf(t);return}case"pull-exec":{await Cf(t);return}case"config":{await Mw(t);return}case"tunnel":{await Xg(t);return}case"platform":{await Jw(t);return}case"search":{yd(t);return}case"docs":{pb(t);return}case"ui":{let n=DP(t);if(n.help){vb();return}if(!Number.isFinite(n.port)||n.port<1||n.port>65535){console.error(E(process.stderr,"port must be between 1 and 65535.")),process.exitCode=1;return}let o=Yw(n.token);await ob({host:n.host,port:n.port,meta:o});let r=process.stdout,s=rb(n.port);console.log(""),console.log(`${Re(r,"ui")} ${w(r,"listening")}`),console.log(`${w(r,"bind:")} ${S(r,`${n.host}:${n.port}`)}`),console.log(`${w(r,"setup token:")} ${S(r,o.token)}`),console.log(`${w(r,"token file:")} ${Q(r,At)}`),console.log(""),console.log(ke(r,"Anyone on your network who can reach this port needs the token \u2014 do not expose to untrusted Wi\u2011Fi.")),console.log(""),console.log(`${w(r,"Open:")}`),console.log(` ${S(r,`http://127.0.0.1:${n.port}/`)}`);for(let i of s)console.log(` ${S(r,i)}`);console.log(""),console.log(`${w(r,"Quick link (same Wi\u2011Fi):")} ${S(r,`http://127.0.0.1:${n.port}/?token=${encodeURIComponent(o.token)}`)}`),console.log("");return}default:bd(),e&&(process.exitCode=1)}}WP().catch(e=>{console.error(E(process.stderr,String(e))),process.exit(1)});
645
+ Show: omnish search show <n>`);return}if(t==="show"){let o=e.slice(1).join(" ").trim();if(!o){console.error("[omnish] Usage: omnish search show <n> | <doc-path>"),process.exitCode=1;return}if(/^\d+$/.test(o)){let s=xw(o);if(!s){console.error("[omnish] No result #"+o+". Run omnish search q first."),process.exitCode=1;return}let i=fs(s.id);if(!i){console.error("[omnish] Entry missing from index."),process.exitCode=1;return}fb(i,Number.parseInt(o,10));return}let r=Ya(o);if(!r){console.error(`[omnish] No doc at path "${o}".`),process.exitCode=1;return}fb(r);return}console.error(`[omnish] Unknown search subcommand "${t}". Try: omnish search help`),process.exitCode=1}function gb(e){kd(e)}function fb(e,t){if(!e)return;let n=qa(Bn.repoUrl,e.path),o=t!==void 0?`${t}. ${e.title}`:e.title;if(console.log(o),console.log(e.path),console.log(n),console.log(""),console.log(Va(e)),console.log(""),e.relatedCommands.length){console.log("Try:");for(let r of e.relatedCommands.slice(0,8))console.log(` ${r}`)}}LP.setDefaultResultOrder("ipv4first");function kb(e){let t=process.stdout,n=ge();n?e.push(K(t,"Attached mode (platform credentials detected):"),` ${w(t,"url:")} ${S(t,n.platformUrl)} ${Q(t,`[${ar()}]`)}`,` ${w(t,"token:")} ${S(t,Vn("platformToken",n.token))} ${Q(t,`[${ir()}]`)}`,w(t," Messengers run on the platform; allowlist is set on the dashboard. Run: omnish platform probe"),""):e.push(w(t,"Platform attached mode: omnish config add platform_url <url> platform_token <token>"),w(t," then omnish platform probe && omnish run \u2014 see omnish help platform"),"")}function Sb(){let e=process.stdout,t=[`${Re(e,"omnish run")} ${w(e,"[options]")}`,Q(e,"Listen for DMs and run shell commands from allowlisted chats."),"",K(e,"Usage:"),` ${S(e,"omnish run [options]")}`,"",K(e,"Options:"),..._t(e," ",[{left:"-d, --background",right:"Start the gateway detached; log to --log-file (default: <data>/logs/gateway.log)."},{left:"-f, --foreground",right:"Run in the foreground (default for run; same as omitting -d)."},{left:"--log-file <path>",right:`Append stdout/stderr when background (default: ${He}).`},{left:"-vb, --verbose",right:"Baileys/gateway debug logs on stderr (legacy: OMNISH_VERBOSE=1)."},{left:"-h, --help",right:"Show this help."}],n=>w(e,n)),"",w(e,"Background by default: omnish start \u2014 stop with omnish stop."),"",`${S(e,"Config reload:")} ${w(e,"while the gateway runs, edit config then send /reload or /restart from an allowlisted chat (no restart needed for many keys). /updates checks npm (and optional updateInfoUrl).")}`,""];kb(t),console.log(t.join(`
646
+ `))}function vb(){let e=process.stdout,t=[`${Re(e,"omnish start")} ${w(e,"[options]")}`,Q(e,"Start the gateway in the background (detached). Pair with omnish stop."),"",K(e,"Usage:"),` ${S(e,"omnish start [options]")}`,"",K(e,"Options:"),..._t(e," ",[{left:"-f, --foreground",right:"Run in the foreground instead of detached (same as omnish run)."},{left:"-d, --background",right:"Start detached (default for start; same as omitting -f)."},{left:"--log-file <path>",right:`Append stdout/stderr when background (default: ${He}).`},{left:"-vb, --verbose",right:"Baileys/gateway debug logs on stderr (legacy: OMNISH_VERBOSE=1)."},{left:"-h, --help",right:"Show this help."}],n=>w(e,n)),"",w(e,"Equivalent background launch: omnish run -d. Foreground default: omnish run."),"",`${S(e,"Stop:")} ${w(e,"omnish stop reads")} ${S(e,de)} ${w(e,"and signals the background process.")}`,"",`${S(e,"Config reload:")} ${w(e,"while the gateway runs, edit config then send /reload or /restart from an allowlisted chat (no restart needed for many keys). /updates checks npm (and optional updateInfoUrl).")}`,""];kb(t),console.log(t.join(`
647
+ `))}function xb(){let e=process.stdout,t=[{left:"omnish link [--force]",right:"WhatsApp: scan QR (Linked devices). --force wipes session first."},{left:"omnish link --tg <bot_token>",right:"Telegram: save token to config; gatewayMode telegram or both if WhatsApp is linked."}],n=t.map(i=>w(e,i.left)),o=Math.max(...n.map(Gs)),r=t.map((i,a)=>Pl(" ",o,n[a],S(e,i.right))),s=[`${Re(e,"omnish link")} ${w(e,"[options]")}`,Q(e,"Connect WhatsApp (QR) or save a Telegram bot token."),"",K(e,"Usage:"),` ${S(e,"omnish link [--force]")}`,` ${S(e,"omnish link --tg <bot_token>")}`,"",K(e,"Modes:"),...r,` ${Q(e,"Do not combine --tg with --force.")}`,"",K(e,"Options:"),..._t(e," ",[{left:"-f, --force",right:"WhatsApp only: delete saved session before pairing."},{left:"-vb, --verbose",right:"Baileys debug logs on stderr during pairing (legacy: OMNISH_VERBOSE=1)."},{left:"-h, --help",right:"Show this help."}],i=>w(e,i)),"",`${S(e,"Next:")} ${he(e,"omnish allow tg:<your_user_id>")} ${w(e,"then")} ${he(e,"omnish run")}`,`${w(e,"Config:")} ${S(e,U)}`,""];ge()&&s.push(K(e,"Platform attached mode:"),w(e," omnish link on this host is for standalone only. Link WhatsApp on the platform dashboard or:"),` ${S(e,"omnish platform import-whatsapp")} ${w(e,"(after a local omnish link, with omnish run stopped)")}`,w(e," Telegram: set bot token on the platform dashboard, not --tg here."),""),console.log(s.join(`
648
+ `))}function _P(e){let t=!1,n=null;for(let o=0;o<e.length;o++){let r=e[o]??"";if(r==="--help"||r==="-h")return{kind:"help"};if(r==="--force"||r==="-f"){t=!0;continue}if(r==="-vb"||r==="--verbose"){hl(!0);continue}if(r==="--tg"||r==="--telegram"){let s=e[o+1];if(!s||s.startsWith("-"))return{kind:"error",message:"[omnish] --tg requires a bot token (from @BotFather)."};n=s,o++;continue}if(r.startsWith("--tg=")||r.startsWith("--telegram=")){let s=r.indexOf("="),i=r.slice(s+1).trim();if(!i)return{kind:"error",message:"[omnish] --tg= requires a non-empty bot token."};n=i;continue}return{kind:"error",message:`[omnish] unknown link argument: ${r}
649
+ Try: omnish link --help`}}return n!==null?t?{kind:"error",message:"[omnish] --force applies to WhatsApp only; do not combine with --tg."}:{kind:"tg",token:n}:{kind:"wa",force:t}}function vd(){let e=process.stdout,t=`${Re(e,"omnish")} ${w(e,`v${gt()}`)}`,n=[{left:"link [--force] [--tg <token>]",right:"WhatsApp (QR) or Telegram bot token \u2014 omnish link --help"},{left:"run [options]",right:"Listen for DMs in foreground; -d for background \u2014 omnish run --help"},{left:"start [options]",right:"Start gateway in background (pair with stop); -f for foreground \u2014 omnish help start"},{left:"stop",right:`Stop background gateway (pidfile: ${de})`},{left:"service <subcommand>",right:"Boot install hints, logs, systemd/LaunchAgent \u2014 omnish service help"},{left:"pull <subcommand>",right:"Media URL tools (yt-dlp, ffmpeg, Whisper) \u2014 omnish pull help"},{left:"logout",right:"Delete saved WhatsApp session"},{left:"allow +<E164> | tg:<id>",right:"Add allowlist entry"},{left:"deny +<E164> | tg:<id>",right:"Remove allowlist entry"},{left:"status [--check-updates]",right:"Channels, identity, allowlists, jobs, security, cluster (if enabled)"},{left:"commands",right:"Chat commands for allowlisted users (same as /help)"},{left:"security [--json]",right:"Configuration security report (JSON for scripts)"},{left:"cluster [status | use <sender> <label-or-id>]",right:"Per-sender machine bindings"},{left:"i | interactive [options]",right:"Local REPL (chat commands; /sendto needs omnish run)"},{left:"ui [options]",right:"Browser setup UI on LAN (token-gated) \u2014 omnish ui --help"},{left:"config <add|show|edit|delete>",right:"Manage config.json (platform URL/token, tunnel, gateway) \u2014 omnish config help"},{left:"platform <subcommand>",right:"Attached mode: configure, probe, import WA \u2014 omnish help platform"},{left:"tunnel <subcommand>",right:"Expose local HTTP/TCP via omnish relay \u2014 omnish tunnel help"},{left:"search <subcommand>",right:"Search bundled guides offline \u2014 omnish search help (same index as /s in chat)"},{left:"docs <subcommand>",right:"Alias for omnish search \u2014 omnish search help"}],o=[{left:"-v, --version",right:"Print version and exit."},{left:"-h, --help",right:"Show this help (same as omnish help)."}],r=[t,Q(e,"Allowlisted inbox \u2192 your real shell. No AI."),"",K(e,"Usage:"),` ${S(e,"omnish [options] <command> [args...]")}`,"",K(e,"Options:"),..._t(e," ",o,s=>w(e,s)),"",K(e,"Commands:"),..._t(e," ",n,s=>w(e,s)),"",`${w(e,"Config:")} ${S(e,`${U} \u2014 gatewayMode: "whatsapp" | "telegram" | "both"`)}`,`${w(e,"Platform:")} ${S(e,"platform_url + platform_token \u2192 attached omnish run (omnish help platform)")}`,`${w(e,"Verbose:")} ${S(e,"omnish run --verbose (legacy: OMNISH_VERBOSE=1, WHATSVERBOSE=1)")}`,`${w(e,"Env:")} ${S(e,"TELEGRAM_BOT_TOKEN (optional override)")}`,`${w(e,"Data:")} ${S(e,"~/.omnish by default; ~/.whatslive reused if it already exists. OMNISH_HOME overrides.")}`,`${w(e,"See also:")} ${S(e,"https://omnish.dev")}`,""];console.log(r.join(`
650
+ `))}function FP(e){let t=(e??"").trim().toLowerCase(),n=process.stdout;if(!t){vd();return}switch(t){case"link":xb();return;case"run":Sb();return;case"start":vb();return;case"service":Rb();return;case"pull":kc();return;case"i":case"interactive":bd(n);return;case"ui":Tb();return;case"config":nl(n);return;case"platform":il(n);return;case"search":cl();return;case"docs":hb();return;default:console.error(P(process.stderr,`No detailed help for "${e}". Try: omnish help`)),process.exitCode=1}}function DP(e,t){let n=t.defaultBackground,o="",r=!1,s=!1,i=`omnish ${t.command} --help`;for(let l=0;l<e.length;l++){let c=e[l]??"";if(c==="-d"||c==="--background")n=!0;else if(c==="-f"||c==="--foreground")n=!1;else if(c==="-vb"||c==="--verbose")s=!0;else if(c==="--log-file"||c==="--log"){let u=e[++l];u||(console.error(P(process.stderr,"--log-file requires a path.")),process.exit(1)),o=u}else if(c==="--help"||c==="-h")r=!0;else{let u=process.stderr;console.error(P(u,`unknown ${t.command} option: ${c}`)),console.error(P(u,`Try: ${i}`)),process.exit(1)}}let a=o.trim()!==""?Sd.isAbsolute(o)?o:Sd.resolve(process.cwd(),o):He;return{background:n,logFile:a,help:r,verbose:s}}async function yb(e,t){let o=DP(t,{defaultBackground:e==="start",command:e});if(o.help){e==="start"?vb():Sb();return}if(o.verbose&&hl(!0),o.background){WP(o.logFile,o.verbose);return}await GP()}function WP(e,t){let n=ga(e,{verbose:t});n.ok||(console.error(P(process.stderr,n.message)),process.exit(1));let o=process.stdout;console.log(`${H(o,`gateway started in background (pid ${n.pid}).`)} ${w(o,`Log: ${e}`)}`)}function UP(){let e=ya();switch(e.outcome){case"no_pidfile":console.error(P(process.stderr,`no pidfile at ${de} \u2014 is a background gateway running?`)),process.exitCode=1;return;case"invalid_pidfile":console.error(P(process.stderr,"invalid pidfile.")),process.exitCode=1;return;case"stale_cleaned":console.log(H(process.stdout,`process ${e.pid} is not running; removing stale pidfile.`));return;case"sent_signal":console.log(H(process.stdout,`sent SIGTERM to gateway (pid ${e.pid}).`));return;case"taskkill_ok":console.log(H(process.stdout,`stopped gateway (pid ${e.pid}) using taskkill.`));return;case"failed":console.error(P(process.stderr,e.message)),process.exitCode=1;return}}function wb(){if(process.env.OMNISH_BACKGROUND_GATEWAY==="1")try{Hn.readFileSync(de,"utf8").trim()===String(process.pid)&&Hn.unlinkSync(de)}catch{}}function BP(e){return e.length<=8?"(set)":`${e.slice(0,4)}\u2026${e.slice(-4)}`}function jP(){if(!Hn.existsSync(de))return"gateway process: not running (no pid file)";let e=Hn.readFileSync(de,"utf8").trim(),t=Number(e);if(!Number.isFinite(t)||t<=0)return"gateway process: invalid pid file";try{return process.kill(t,0),`gateway process: running (pid ${t})`}catch{return`gateway process: not running (stale pid ${t} in pid file)`}}var Cb=120;function Rb(){let e=process.stdout,t=[{left:"help",right:"This help."},{left:"instructions",right:"Copy-paste install steps for this machine."},{left:"status",right:"Data dir, pidfile, Node + entry script."},{left:"logs [n]",right:`Tail default gateway log (default 80 lines, max ${Cb}).`},{left:"install",right:"Write user systemd / LaunchAgent unit (needs serviceInstallFromChat=true)."},{left:"uninstall",right:"Remove that unit (same gate as install)."}],n=[`${Re(e,"omnish service")} ${w(e,"<subcommand>")}`,Q(e,"Boot integration, crash restart policy, and logs (same ideas as /service in chat)."),"",K(e,"Usage:"),` ${S(e,"omnish service <subcommand>")}`,"",K(e,"Subcommands:"),..._t(e," ",t,o=>w(e,o)),"",K(e,"Restart and reload:"),` ${S(e,"Process")} ${w(e,"\u2014 Linux user unit: Restart=on-failure, RestartSec=5. macOS: KeepAlive. Full restart loads config from disk.")}`,` ${S(e,"Config live")} ${w(e,"\u2014 allowlisted chat while gateway runs: /reload or /restart")}`,"",`${w(e,"Docs:")} ${S(e,"docs/guides/background-and-boot.md")} ${Q(e,"\xB7")} https://omnish.dev`,""];console.log(n.join(`
651
+ `))}function HP(e){let t=process.stdout,n=process.stderr,o=(e[0]??"help").toLowerCase();if(o==="help"||o==="--help"||o==="-h"){Rb();return}if(o==="instructions"){let r=sn();if(r.error){console.error(P(n,r.error)),process.exitCode=1;return}console.log(Zs(r));return}if(o==="status"){let r=sn(),s=(()=>{try{return Hn.existsSync(de)?`gateway.pid: ${Hn.readFileSync(de,"utf8").trim()}`:"gateway.pid: (missing)"}catch(d){return`gateway.pid: (read error: ${String(d)})`}})(),i=process.env.OMNISH_BACKGROUND_GATEWAY==="1"?"This process: background gateway (OMNISH_BACKGROUND_GATEWAY=1).":"This process: CLI (not the gateway \u2014 run omnish service status on the host where the gateway runs for live pid info).",a=typeof process.env.OMNISH_HOME=="string"&&process.env.OMNISH_HOME.trim()?`OMNISH_HOME env: ${process.env.OMNISH_HOME.trim()}`:"OMNISH_HOME env: (not set \u2014 using default data dir)",l=r.error?r.error:`Node: ${r.nodePath}
652
+ Script: ${r.scriptPath}`,u=v().serviceInstallFromChat?"Install from CLI/chat: enabled (omnish service install / /service install).":"Install from CLI/chat: off \u2014 set serviceInstallFromChat true in config (same trust as shell).";console.log(Re(t,"omnish service status")),console.log(""),console.log(`${w(t,"platform:")} ${S(t,process.platform)}`),console.log(`${w(t,"session:")} ${S(t,i)}`),console.log(`${w(t,"env:")} ${S(t,a)}`),console.log(`${w(t,"data dir:")} ${S(t,W)}`),console.log(`${w(t,"pidfile:")} ${S(t,s)}`),console.log(`${w(t,"default log:")} ${S(t,He)}`),console.log(""),console.log(l),console.log(""),console.log(S(t,u));return}if(o==="logs"){let r=e.length>=2?Number.parseInt(e[1],10):80,s=Number.isFinite(r)&&r>0?Math.min(r,Cb):80,i=wi(He,s);console.log(`${w(t,"file:")} ${S(t,He)}`),console.log(`${w(t,"lines:")} ${S(t,String(s))}`),console.log(""),console.log(i);return}if(o==="install"){if(!v().serviceInstallFromChat){console.error(P(n,"Install is disabled. Set serviceInstallFromChat to true in config (same trust as shell), then run again.")),process.exitCode=1;return}let s=hi();s.ok?console.log(H(t,s.detail)):(console.error(P(n,s.detail)),process.exitCode=1);return}if(o==="uninstall"){if(!v().serviceInstallFromChat){console.error(P(n,"Uninstall is disabled. Set serviceInstallFromChat to true in config or remove the unit file on the host manually.")),process.exitCode=1;return}let s=gi();s.ok?console.log(H(t,s.detail)):(console.error(P(n,s.detail)),process.exitCode=1);return}console.error(P(n,`Unknown subcommand "${o}". Try: omnish service help`)),process.exitCode=1}function JP(e){let t=e.trim();if(!t)return null;let n=t.toLowerCase();if(n.startsWith("tg:")||n.startsWith("telegram:")){let s=Fe(t);return s?`tg:${s}`:null}let o=n.startsWith("wa:")?t.slice(3):t,r=te(o);return r?`wa:${r}`:null}async function GP(){let e=null,t=el();t.ok||(console.error(P(process.stderr,t.message)),process.exit(1));let n=ge();if(n){await $w(n);return}let o=v(),r=o.gatewayMode,s=r==="whatsapp"||r==="both",i=r==="telegram"||r==="both",a=Ie(o),l=tn(o),c=process.stderr,u=Pp(l,"warn");if(u.length>0&&(console.warn(`${H(c,`Security (${u.length} finding(s)):`)}
653
+ `),console.warn(Ol(u,c))),process.env.OMNISH_BACKGROUND_GATEWAY==="1")try{Hn.writeFileSync(de,`${process.pid}
654
+ `,{mode:384})}catch(I){E.warn({err:String(I)},"could not write gateway pidfile")}let d=(I,z)=>{let G=v();if(!G.clusterEnabled)return I;let Y=ht(),ae=(G.clusterLabel??"").trim()||bb.hostname(),ne=null;if(z.startsWith("tg:"))ne=z;else if(z){let be=te(z);be&&(ne=`wa:${be}`)}let Ye=ne?nn(G,ne):null;return gm(I,{nodeId:Y,label:ae,role:G.clusterRole,activeNodeId:Ye?.nodeId??""})},m=new Map,f=new Map,h=new Map,g=null,y={stop:null,sendText:null,sendMedia:null},b=null,k=!1,x=async(I,z)=>{if(I.startsWith("wa:")){let G=I.slice(3);g&&await g.sendText(G,z)}else if(I.startsWith("tg:")){let G=Number(I.slice(3));y.sendText&&Number.isFinite(G)&&await y.sendText(G,p(z))}},C=new cn({onJobExit(I){I.notifyPeerKey&&x(I.notifyPeerKey,ca(I))}}),M={onPlainTextAgentDaemon(I,z){let G=v(),Y=sa(G,I,z);Y.ok||x(I,`[agent] ${Y.error}`)},onPlainTextLlmFallback(I,z){qo(v(),I,z,G=>x(I,G))},getAgentDaemonStatus(){return aa()},stopAgentDaemon(I){return Zr(v(),I)},resetAgentDaemon(I){return ia(v(),I)},sendToPeer:x},R=async(I,z)=>{if(I.startsWith("wa:")){let G=I.slice(3);g&&await g.sendMedia(G,z)}else if(I.startsWith("tg:")){let G=Number(I.slice(3));y.sendMedia&&Number.isFinite(G)&&await y.sendMedia(G,z)}},N=()=>new An(()=>v(),x),A=N();b=A;let q,ee={async reload(){try{E.info("gateway reload requested from chat"),await y.stop?.().catch(()=>{}),y.stop=null,y.sendText=null,y.sendMedia=null;let I=v(),z=I.gatewayMode==="telegram"||I.gatewayMode==="both",G=Ie(I);if(z&&G){let ne=await pu(G,()=>v(),q,{decorate:d});y.sendText=ne.sendText,y.sendMedia=ne.sendMedia,y.stop=ne.stop}let Y=["Reload complete.",`gatewayMode: ${I.gatewayMode}`,z&&G?"Telegram bot is running with the current token.":"Telegram bot is stopped (enable telegram/both + token if you want it).","Allowlists, shell, app session limits, and timeouts are read from disk on each command."].join(`
655
+ `),ae=Ia(ss());return{ok:!0,summary:ae?`${Y}
656
+
657
+ Updates (last check): ${ae}`:Y}}catch(I){return{ok:!1,error:String(I)}}}};if(q=async(I,z)=>{let G=v();await hs(G,C,m,f,h,I,A,ee,I.peerKey,M,nd({sendTg:z},{surface:"telegram"}),{surface:"telegram"})},i){let I=await pu(a,()=>v(),q,{decorate:d});y.sendText=I.sendText,y.sendMedia=I.sendMedia,y.stop=I.stop}wa({getCfg:()=>v(),getWaOutbound:()=>g,getTgSendMedia:()=>y.sendMedia,getTgSendText:()=>y.sendText,sendPeerMedia:R});let ce=null;{let I=v();if(I.webhookEnabled){let z=I.webhookToken||NP.randomBytes(32).toString("hex");I.webhookToken||$({webhookToken:z}),ce=ba({port:I.webhookPort,host:I.webhookHost,token:z},{sendToPeer:x,getDefaultPeerKey:()=>{let Y=v();return Y.allowFrom.length>0?`wa:${mn(Y.allowFrom[0])}`:Y.telegramAllowFrom.length>0?`tg:${Y.telegramAllowFrom[0]}`:null}}).stop}}e=Oa({getRunningVersion:gt,getConfig:v,log:E});let Pe=oa({getConfig:v,sendToPeer:x,sendMediaToPeer:R}),Ke=Vi({getConfig:v,sendToPeer:x}),F=Us({getConfig:v,sendToPeer:x}),ie=!i,B=()=>{k=!0,Pe(),Ke(),F(),e?.(),e=null,ce?.(),wb(),sr(),la(),y.stop?.().catch(()=>{}),b?.dispose(),C.killAllRunning(),mo().stopAll().catch(()=>{}),console.error(`
658
+ ${P(process.stderr,"shutting down\u2026")}`),process.exit(0)};if(process.on("SIGINT",B),process.on("SIGTERM",B),s)for(;!k;){let I=!1,z;try{z=await da({printQr:!1,verbose:xo()}),await uo(pa(z),3e5,"Gateway: timed out waiting for WhatsApp connection (5 min).")}catch(Y){console.error(P(process.stderr,`connect failed: ${String(Y)}`)),await new Promise(ae=>setTimeout(ae,5e3));continue}g=Ig(z,{decorate:d});let G=Tg(z,async Y=>{let ae=v(),ne=Y.fromE164||te(Y.fromJid)||"",Ye=Mm(Y),be=`wa:${ne}`;await hs(ae,C,m,f,h,Ye,A,ee,be,M,nd({sendWaText:(re,De)=>g.sendText(re,De),sendWaMedia:(re,De)=>g.sendMedia(re,De)},{surface:"whatsapp",waJid:Y.fromJid}),{surface:"whatsapp"})});if(await new Promise(Y=>{let ae=ne=>{ne.connection==="close"&&(cu(ne.lastDisconnect)===co.loggedOut&&(I=!0),z.ev.off("connection.update",ae),Y())};z.ev.on("connection.update",ae)}),G(),ie&&(A.dispose(),A=N(),b=A),nr(z),g=null,I&&(console.error(P(process.stderr,"session logged out. Run `omnish link` again.")),Pe(),Ke(),e?.(),e=null,wb(),sr(),y.stop?.().catch(()=>{}),process.exit(1)),k)break;await new Promise(Y=>setTimeout(Y,3e3))}else for(;!k;)await new Promise(I=>setTimeout(I,500));Pe(),Ke(),e?.(),sr(),y.stop?.().catch(()=>{}),A.dispose()}function qP(e){let t=!1,n="0.0.0.0",o=3789,r;for(let s=0;s<e.length;s++){let i=e[s];if(i==="--help"||i==="-h"){t=!0;continue}if(i==="--host"||i==="-H"){let l=e[++s];l||(console.error(P(process.stderr,"--host requires an address (e.g. 127.0.0.1).")),process.exit(1)),n=l;continue}if(i==="--port"||i==="-p"){let l=e[++s],c=Number.parseInt(l??"",10);Number.isFinite(c)||(console.error(P(process.stderr,"--port requires a number.")),process.exit(1)),o=c;continue}if(i==="--token"||i==="-t"){let l=e[++s];(!l||l.startsWith("-"))&&(console.error(P(process.stderr,"--token requires a secret string.")),process.exit(1)),r=l;continue}let a=process.stderr;console.error(P(a,`unknown ui argument: ${i}`)),console.error(P(a,"Try: omnish ui --help")),process.exit(1)}return{help:t,host:n,port:o,token:r}}function Tb(){let e=process.stdout,t=[`${Re(e,"omnish ui")} ${w(e,"[options]")}`,Q(e,"Serve the browser setup panel on your LAN (token-gated). Same trust as editing config on disk."),"",K(e,"Usage:"),` ${S(e,"omnish ui [options]")}`,"",K(e,"Options:"),..._t(e," ",[{left:"--host <addr>",right:"Bind address (default 0.0.0.0 \u2014 reachable on LAN). Use 127.0.0.1 for loopback only."},{left:"--port <n>",right:"TCP port (default 3789)."},{left:"--token <secret>",right:`Set or rotate setup token (saved to ${At}).`},{left:"-h, --help",right:"This help."}],n=>w(e,n)),"",`${w(e,"Warning:")} ${ke(e,"Listening on all interfaces exposes a remote control panel on your network \u2014 use a trusted LAN.")}`,""];console.log(t.join(`
659
+ `))}async function zP(){let[,,e,...t]=process.argv;if(e==="--version"||e==="-v"||e==="-V"){let n=process.stdout;console.log(`${Re(n,"omnish")} ${w(n,gt())}`);return}if(e==="--help"||e==="-h"){vd();return}if(e==="help"){FP(t[0]);return}switch(se(),e){case"link":{let n=_P(t);if(n.kind==="help"){xb();return}if(n.kind==="error"){let o=process.stderr,r=n.message.replace(/^\[omnish\]\s*/,"");console.error(P(o,r)),process.exitCode=1;return}if(n.kind==="tg"){let o=n.token.trim(),r=process.stderr,s=process.stdout;if(!Ot(o)){console.error(P(r,"That does not look like a Telegram bot token (expect digits:secret from @BotFather).")),process.exitCode=1;return}fn(o);let i=St()?"both":"telegram";Is(i),console.log([`${he(s,"Telegram")} ${S(s,"bot token saved to")} ${w(s,U)}`,`${w(s,"gatewayMode:")} ${he(s,i)}`,"",S(s,"Next:"),` ${w(s,"1.")} ${S(s,"Find your numeric user id (e.g. t.me/userinfobot), then:")} ${he(s,"omnish allow tg:<id>")}`,` ${w(s,"2.")} ${he(s,"omnish run")}`,""].join(`
660
+ `));return}await Wg({verbose:xo(),force:n.force});return}case"run":{await yb("run",t);return}case"start":{await yb("start",t);return}case"stop":UP();return;case"logout":{try{Hn.rmSync(le,{recursive:!0,force:!0}),console.log(H(process.stdout,"Session removed. Run `omnish link` to pair again."))}catch(n){console.error(P(process.stderr,`logout failed: ${String(n)}`)),process.exitCode=1}return}case"allow":{let n=t[0],o=process.stdout,r=process.stderr;if(!n){console.error(P(r,"Usage: omnish allow +<E164> or omnish allow tg:<user_id>")),process.exitCode=1;return}try{let s=Ms(n);console.log(`${w(o,"allowFrom:")} ${S(o,s.allowFrom.join(", ")||"(empty)")}`),console.log(`${w(o,"telegramAllowFrom:")} ${S(o,s.telegramAllowFrom.join(", ")||"(empty)")}`)}catch(s){console.error(P(r,String(s).replace(/^\[omnish\]\s*/,""))),process.exitCode=1}return}case"deny":{let n=t[0],o=process.stdout,r=process.stderr;if(!n){console.error(P(r,"Usage: omnish deny +<E164> or omnish deny tg:<user_id>")),process.exitCode=1;return}try{let s=As(n);console.log(`${w(o,"allowFrom:")} ${S(o,s.allowFrom.join(", ")||"(empty)")}`),console.log(`${w(o,"telegramAllowFrom:")} ${S(o,s.telegramAllowFrom.join(", ")||"(empty)")}`)}catch(s){console.error(P(r,String(s).replace(/^\[omnish\]\s*/,""))),process.exitCode=1}return}case"i":case"interactive":{await mb(t);return}case"status":{let n=process.stdout,o=v(),r=t.includes("--check-updates"),s=new cn().list(),i=s.filter(h=>h.status==="running").length,a=Ie(o),l=tn(o),c=o.gatewayMode==="whatsapp"||o.gatewayMode==="both",u=o.gatewayMode==="telegram"||o.gatewayMode==="both",d=St(),m=d?_g(le):null,f=[];if(f.push(`${Re(n,"omnish")} ${w(n,gt())}`,`${w(n,"gatewayMode:")} ${S(n,o.gatewayMode)}`,`${w(n,"data dir:")} ${S(n,Sd.dirname(le))}`,"",`${w(n,"gateway process:")} ${S(n,jP().replace(/^gateway process: /,""))}`,"",Sn(n),he(n,"whatsapp"),` ${w(n,"in use:")} ${c?S(n,"yes"):ke(n,"no (gatewayMode is telegram-only)")}`),c){let h=d?S(n,`linked (${le})`):ke(n,"missing \u2014 run omnish link");f.push(` ${w(n,"session:")} ${h}`),d&&m&&f.push(` ${w(n,"linked as:")} ${S(n,m)}`),d&&!m&&f.push(` ${w(n,"linked as:")} ${Q(n,"(not in creds yet \u2014 try again after omnish link completes)")}`)}if(f.push(` ${K(n,"Allowed")}`),o.allowFrom.length===0)f.push(` ${Q(n,"(none)")}`);else for(let h of o.allowFrom)f.push(` ${w(n,"whatsapp:")} ${S(n,h)}`);if(f.push("",Sn(n),he(n,"telegram")),f.push(` ${w(n,"in use:")} ${u?S(n,"yes"):ke(n,"no (gatewayMode is whatsapp-only)")}`),u){let h=a?S(n,BP(a)):ke(n,"(none) \u2014 omnish link --tg <token> or TELEGRAM_BOT_TOKEN");f.push(` ${w(n,"bot token:")} ${h}`)}if(f.push(` ${K(n,"Allowed")}`),o.telegramAllowFrom.length===0)f.push(` ${Q(n,"(none)")}`);else for(let h of o.telegramAllowFrom)f.push(` ${w(n,"telegram:")} ${S(n,h)}`);if(f.push("",Sn(n),...await Nw(n)),f.push("",Sn(n),`${he(n,"jobs")} ${w(n,`(recent): ${s.length} total, ${i} running`)}`,Ap(l,n)),console.log(f.join(`
661
+ `)),r){let h=await is(gt(),o),g=Mr(h);console.log(""),console.log(Sn(n)),console.log(pe(g,"whatsapp").text)}if(o.clusterEnabled){let h=ht(),g=Te(),y=Object.keys(g.senderBindings).length,b=Object.keys(o.clusterSenderBindings??{}).length;console.log(""),console.log(Sn(n)),console.log(he(n,"cluster")),console.log(` ${w(n,"\xB7")} ${S(n,`enabled \xB7 label ${o.clusterLabel||bb.hostname()} \xB7 bindings ${y} chat / ${b} default`)}`),console.log(` ${w(n,"node:")} ${S(n,`${h.slice(0,8)}\u2026`)}`)}return}case"commands":{let n=process.stdout,o=v();console.log(Rp($o(o),n)),console.log(""),console.log(Sn(n)),console.log(S(n,"Keys editable from chat via /config set (same trust as shell):")),console.log(w(n,xn.join(", "))),console.log(`${w(n,"See also:")} ${S(n,U)}`);return}case"cluster":{let n=process.stdout,o=process.stderr,r=(t[0]??"status").toLowerCase();if(r==="status"){let s=v(),i=ht();if(console.log(`${w(n,"clusterEnabled:")} ${S(n,String(s.clusterEnabled))}`),console.log(`${w(n,"clusterLabel:")} ${S(n,s.clusterLabel||"(hostname)")}`),console.log(`${w(n,"clusterRole:")} ${S(n,`${s.clusterRole} (informational; no longer used to gate traffic)`)}`),console.log(`${w(n,"node id:")} ${he(n,i)}`),s.clusterEnabled){let a=Te(),l=tc(s,null);console.log(""),console.log(S(n,l.wa.replace(/\*([^*]+)\*/g,"$1").replace(/`([^`]+)`/g,"$1"))),console.log(""),console.log(ec(a,s,null));let c=Object.keys(a.senderBindings).length,u=Object.entries(s.clusterSenderBindings??{});if(c>0){console.log(""),console.log(he(n,"Chat bindings (cluster-local.json)"));for(let[d,m]of Object.entries(a.senderBindings))console.log(` ${w(n,d)} ${Q(n,"->")} ${S(n,`${m.nodeId} (${m.source}, since ${m.sinceIso})`)}`)}if(u.length>0){console.log(""),console.log(he(n,"Config defaults (clusterSenderBindings)"));for(let[d,m]of u)console.log(` ${w(n,d)} ${Q(n,"->")} ${S(n,m)}`)}}else console.log(""),console.log(ke(n,"(cluster disabled \u2014 /config set clusterEnabled true to enable, then /c use <label-or-id> from each sender)"));return}if(r==="use"||r==="bind"){let s=t[1],i=t.slice(2).join(" ").trim();if(!s||!i){console.error(P(o,"Usage: omnish cluster use <senderE164|tg:id> <label-or-id>")),process.exitCode=1;return}let a=JP(s);if(!a){console.error(P(o,`Could not parse sender "${s}". Use +E164 (WhatsApp) or tg:<user_id> (Telegram).`)),process.exitCode=1;return}let{state:l,resolved:c}=$m(a,i);if(!c.ok){if(c.reason==="ambiguous-label"){let d=(c.matches??[]).map(m=>`${m.nodeId}(${m.label})`).join(", ");console.error(P(o,`Label "${i}" matches multiple machines: ${d}. Use the 8-character id.`))}else console.error(P(o,`No machine matches "${i}". Run /c status from the chat first to populate the roster, or pass an 8-character node id.`));process.exitCode=1;return}console.log(`${he(n,"cluster:")} ${S(n,`${a} -> ${c.peer.nodeId} (${c.peer.label}).`)}`);let u=v();console.log(""),console.log(ec(l,u,a));return}if(r==="here"){console.error(P(o,"omnish cluster here is no longer available. Use: omnish cluster use <senderE164|tg:id> <label-or-id>")),console.error(P(o,"Or send /c here from the controller's chat on the machine you want to bind.")),process.exitCode=1;return}console.error(P(o,"Usage: omnish cluster [status | use <sender> <label-or-id>]")),process.exitCode=1;return}case"security":{let n=v(),o=tn(n),r=t.includes("--json");console.log(r?$p(o):Ol(o,process.stdout)),Pr(o)&&(process.exitCode=1);return}case"service":{HP(t);return}case"pull":{await Tf(t);return}case"media-exec":{await Pf(t);return}case"pull-exec":{await Ef(t);return}case"config":{await Lw(t);return}case"tunnel":{await ny(t);return}case"platform":{await Kw(t);return}case"search":{kd(t);return}case"docs":{gb(t);return}case"ui":{let n=qP(t);if(n.help){Tb();return}if(!Number.isFinite(n.port)||n.port<1||n.port>65535){console.error(P(process.stderr,"port must be between 1 and 65535.")),process.exitCode=1;return}let o=Zw(n.token);await ab({host:n.host,port:n.port,meta:o});let r=process.stdout,s=lb(n.port);console.log(""),console.log(`${Re(r,"ui")} ${w(r,"listening")}`),console.log(`${w(r,"bind:")} ${S(r,`${n.host}:${n.port}`)}`),console.log(`${w(r,"setup token:")} ${S(r,o.token)}`),console.log(`${w(r,"token file:")} ${Q(r,At)}`),console.log(""),console.log(ke(r,"Anyone on your network who can reach this port needs the token \u2014 do not expose to untrusted Wi\u2011Fi.")),console.log(""),console.log(`${w(r,"Open:")}`),console.log(` ${S(r,`http://127.0.0.1:${n.port}/`)}`);for(let i of s)console.log(` ${S(r,i)}`);console.log(""),console.log(`${w(r,"Quick link (same Wi\u2011Fi):")} ${S(r,`http://127.0.0.1:${n.port}/?token=${encodeURIComponent(o.token)}`)}`),console.log("");return}default:vd(),e&&(process.exitCode=1)}}zP().catch(e=>{console.error(P(process.stderr,String(e))),process.exit(1)});