@z_ptah/agent 0.0.29 → 0.0.30

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/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- var d=(e,t)=>()=>(e&&(t=e(e=0)),t);import"dotenv/config";import{execSync as Ze}from"child_process";function re(e){let t=process.env[e];if(!t)throw new Error(`Missing required environment variable: ${e}`);return t}function et(){if(process.env.CLAUDE_PATH)return process.env.CLAUDE_PATH;try{return Ze("which claude",{encoding:"utf-8"}).trim()}catch{return"/usr/bin/claude"}}var c,y=d(()=>{"use strict";c={serverUrl:re("PTAH_SERVER_URL"),token:re("PTAH_AGENT_TOKEN"),healthPort:Number(process.env.AGENT_HEALTH_PORT||9090),heartbeatIntervalMs:Number(process.env.HEARTBEAT_INTERVAL_MS||3e4),reconnectIntervalMs:Number(process.env.RECONNECT_INTERVAL_MS||5e3),projectsDir:process.env.PROJECTS_DIR||"/projects",claudePath:et()}});import{randomUUID as x}from"crypto";function se(e,t,o){let r=e.trim();if(!r)return[];let s;try{s=JSON.parse(r)}catch{return[]}let n=[],l=new Date().toISOString();if(s.type==="content_block_delta"){let a=s.delta;return a?.type==="text_delta"&&a.text&&n.push({kind:"text_delta",id:x(),sessionId:t,commandId:o,timestamp:l,role:"assistant",content:a.text}),a?.type==="thinking_delta"&&a.thinking&&n.push({kind:"thinking",id:x(),sessionId:t,commandId:o,timestamp:l,role:"assistant",content:a.thinking}),n}if(s.type==="content_block_start"){let a=s.content_block;return a?.type==="tool_use"&&n.push({kind:"tool_use",id:x(),sessionId:t,commandId:o,timestamp:l,role:"assistant",toolName:a.name||"tool",toolId:a.id||void 0,toolInput:{}}),n}if(s.type==="content_block_stop"||s.type==="message_start"||s.type==="message_delta"||s.type==="message_stop")return n;if(s.type==="assistant"&&s.message){let a=s.message;if(Array.isArray(a.content))for(let p of a.content){let h=tt(p.type);if(!h)continue;let f={kind:h,id:x(),sessionId:t,commandId:o,timestamp:l,role:"assistant"};switch(h){case"text":f.content=p.text||"";break;case"tool_use":f.toolName=p.name||"tool",f.toolId=p.id||void 0,f.toolInput=p.input??{};break;case"thinking":f.content=p.thinking||"";break}n.push(f)}}return s.type==="tool_result"&&n.push({kind:"tool_result",id:x(),sessionId:t,commandId:o,timestamp:l,role:"assistant",toolId:s.tool_use_id||void 0,content:typeof s.content=="string"?s.content:JSON.stringify(s.content??""),isError:s.is_error||!1}),s.type==="result"&&n.push({kind:"complete",id:x(),sessionId:t,commandId:o,timestamp:l,role:"assistant",content:s.result||"",metadata:{sessionId:s.session_id,cost:s.cost_usd??s.cost,totalCost:s.total_cost_usd,duration:s.duration_ms,numTurns:s.num_turns}}),n}function tt(e){switch(e){case"text":return"text";case"tool_use":return"tool_use";case"thinking":return"thinking";default:return null}}var ne=d(()=>{"use strict"});import{mkdirSync as ot,appendFileSync as rt,existsSync as st,readFileSync as nt}from"fs";import{join as at,dirname as it}from"path";function ae(e,t){return at(c.projectsDir,e,"chat-history",`${t}.jsonl`)}function _(e,t){let o=ae(e,t.sessionId);ot(it(o),{recursive:!0}),rt(o,JSON.stringify(t)+`
3
- `,"utf-8")}function ie(e,t){let o=ae(e,t);if(!st(o))return[];let r=nt(o,"utf-8"),s=[];for(let n of r.split(`
4
- `))if(n.trim())try{s.push(JSON.parse(n))}catch{}return s}var j=d(()=>{"use strict";y()});import{readFileSync as ct,existsSync as lt}from"fs";import{basename as pt}from"path";function ut(e){let t=e.substring(e.lastIndexOf(".")).toLowerCase();return dt[t]||"application/octet-stream"}async function ce(e,t){if(!lt(t))return console.warn(`[uploader] File not found: ${t}`),!1;let o=pt(t),r=ut(o),s=ct(t),n=c.serverUrl.replace(/^wss:/,"https:").replace(/^ws:/,"http:").replace(/\/ws\/agent$/,""),l=new FormData;l.append("projectId",e),l.append("file",new Blob([s],{type:r}),o);try{let a=await fetch(`${n}/agent/documents/upload`,{method:"POST",headers:{Authorization:`Bearer ${c.token}`},body:l});if(!a.ok){let p=await a.text();return console.error(`[uploader] Upload failed (${a.status}): ${p}`),!1}return console.log(`[uploader] Uploaded ${o} for project ${e}`),!0}catch(a){return console.error("[uploader] Upload error:",a),!1}}var dt,le=d(()=>{"use strict";y();dt={".xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",".xls":"application/vnd.ms-excel",".pdf":"application/pdf",".docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document",".doc":"application/msword",".csv":"text/csv",".json":"application/json",".md":"text/markdown",".txt":"text/plain",".zip":"application/zip"}});import{readdirSync as gt,statSync as mt,readFileSync as ft,writeFileSync as yt,existsSync as ht}from"fs";import{join as z}from"path";function St(e){let t=z(e,pe);if(!ht(t))return{};try{return JSON.parse(ft(t,"utf-8"))}catch{return{}}}function kt(e,t){yt(z(e,pe),JSON.stringify(t,null,2))}async function de(e,t){let o=St(t),r=!1,s;try{s=gt(t)}catch{return}for(let n of s){let l=n.substring(n.lastIndexOf(".")).toLowerCase();if(!vt.has(l))continue;let a=z(t,n);try{let p=mt(a);if(!p.isFile())continue;let h=o[n];if(h&&h>=p.mtimeMs)continue;console.log(`[auto-upload] Uploading ${n}...`),await ce(e,a)&&(o[n]=p.mtimeMs,r=!0)}catch(p){console.error(`[auto-upload] Failed to process ${n}:`,p)}}r&&kt(t,o)}var vt,pe,ue=d(()=>{"use strict";le();vt=new Set([".xlsx",".xls",".csv",".pdf",".docx",".doc"]),pe=".ptah-uploaded.json"});import{spawn as xt}from"child_process";import{mkdirSync as wt}from"fs";import{randomUUID as H}from"crypto";function ge(e){let{commandId:t,sessionId:o,projectId:r,projectDir:s,message:n,claudeSessionId:l,systemPrompt:a}=e,h=["-p",n,"--output-format","stream-json","--verbose","--allowedTools","Bash,Read,Edit,Write,Glob,Grep","--append-system-prompt",a||"Your text responses are rendered as markdown in a web UI. Use markdown formatting (tables, code blocks, lists, headings) for readability. You may freely create/edit files as needed \u2014 but always include a summary of what you did in your text response so the user can see it in the UI."];l&&h.push("--resume",l),wt(s,{recursive:!0}),console.log(`Starting Claude for session ${o} in ${s}`);let f={kind:"text",id:H(),sessionId:o,commandId:t,timestamp:new Date().toISOString(),role:"user",content:n};_(r,f),i({type:"agent:claude-message",payload:f});let P=xt(c.claudePath,h,{cwd:s,env:{...process.env},stdio:["pipe","pipe","pipe"]}),$="",Qe;function oe(u){if(!u.trim())return;try{let S=JSON.parse(u);S.session_id&&(Qe=S.session_id)}catch{return}let g=se(u,o,t);for(let S of g)S.kind!=="text_delta"&&_(r,S),i({type:"agent:claude-message",payload:S})}P.stdout.on("data",u=>{$+=u.toString();let g=$.split(`
5
- `);$=g.pop()||"";for(let S of g)oe(S)}),P.stderr.on("data",u=>{}),P.on("close",u=>{if($.trim()&&oe($),console.log(`Claude exited with code ${u} for session ${o}`),L(o),u===0&&de(r,s).catch(g=>console.error("[auto-upload] Error:",g)),u!==0){let g={kind:"error",id:H(),sessionId:o,commandId:t,timestamp:new Date().toISOString(),role:"assistant",content:`Process exited with code ${u}`};_(r,g),i({type:"agent:claude-message",payload:g})}}),P.on("error",u=>{console.error(`Claude spawn error for session ${o}:`,u),L(o);let g={kind:"error",id:H(),sessionId:o,commandId:t,timestamp:new Date().toISOString(),role:"assistant",content:`Error spawning Claude: ${u.message}`};_(r,g),i({type:"agent:claude-message",payload:g})})}var me=d(()=>{"use strict";k();F();y();ne();j();ue()});function fe(e){if(q.has(e.sessionId)){console.warn(`Session ${e.sessionId} already has an active Claude process`);return}q.set(e.sessionId,{commandId:e.commandId}),ge(e)}function L(e){q.delete(e)}var q,F=d(()=>{"use strict";me();q=new Map});import{spawn as bt}from"child_process";async function ye(e){return new Promise((t,o)=>{console.log(`Building Docker in ${e}...`);let r=bt("docker",["compose","up","-d","--build"],{cwd:e,stdio:"inherit"});r.on("close",s=>{s===0?(console.log("Docker build successful"),t()):o(new Error(`Docker build failed with code ${s}`))}),r.on("error",s=>{o(new Error(`Docker build error: ${s.message}`))})})}var he=d(()=>{"use strict"});import{writeFile as $t}from"fs/promises";import{spawn as _t}from"child_process";function T(e,t){return new Promise((o,r)=>{let s=_t(e,t,{stdio:"inherit"});s.on("close",n=>{n===0?o():r(new Error(`${e} failed with code ${n}`))}),s.on("error",r)})}async function ve(e,t,o,r){let s=/^[a-z0-9][a-z0-9-]*$/;if(!s.test(e)||!s.test(t))throw new Error("Invalid project or org code for Nginx config");if(o<1024||o>65535||r<1024||r>65535)throw new Error("Invalid port number");let n=`${e}.${t}.mybptah.com`,l=`
2
+ var d=(e,t)=>()=>(e&&(t=e(e=0)),t);import"dotenv/config";import{execSync as et}from"child_process";function re(e){let t=process.env[e];if(!t)throw new Error(`Missing required environment variable: ${e}`);return t}function tt(){if(process.env.CLAUDE_PATH)return process.env.CLAUDE_PATH;try{return et("which claude",{encoding:"utf-8"}).trim()}catch{return"/usr/bin/claude"}}var c,y=d(()=>{"use strict";c={serverUrl:re("PTAH_SERVER_URL"),token:re("PTAH_AGENT_TOKEN"),healthPort:Number(process.env.AGENT_HEALTH_PORT||9090),heartbeatIntervalMs:Number(process.env.HEARTBEAT_INTERVAL_MS||3e4),reconnectIntervalMs:Number(process.env.RECONNECT_INTERVAL_MS||5e3),projectsDir:process.env.PROJECTS_DIR||"/projects",claudePath:tt()}});import{randomUUID as x}from"crypto";function se(e,t,o){let r=e.trim();if(!r)return[];let s;try{s=JSON.parse(r)}catch{return[]}let n=[],l=new Date().toISOString();if(s.type==="content_block_delta"){let a=s.delta;return a?.type==="text_delta"&&a.text&&n.push({kind:"text_delta",id:x(),sessionId:t,commandId:o,timestamp:l,role:"assistant",content:a.text}),a?.type==="thinking_delta"&&a.thinking&&n.push({kind:"thinking",id:x(),sessionId:t,commandId:o,timestamp:l,role:"assistant",content:a.thinking}),n}if(s.type==="content_block_start"){let a=s.content_block;return a?.type==="tool_use"&&n.push({kind:"tool_use",id:x(),sessionId:t,commandId:o,timestamp:l,role:"assistant",toolName:a.name||"tool",toolId:a.id||void 0,toolInput:{}}),n}if(s.type==="content_block_stop"||s.type==="message_start"||s.type==="message_delta"||s.type==="message_stop")return n;if(s.type==="assistant"&&s.message){let a=s.message;if(Array.isArray(a.content))for(let p of a.content){let h=ot(p.type);if(!h)continue;let f={kind:h,id:x(),sessionId:t,commandId:o,timestamp:l,role:"assistant"};switch(h){case"text":f.content=p.text||"";break;case"tool_use":f.toolName=p.name||"tool",f.toolId=p.id||void 0,f.toolInput=p.input??{};break;case"thinking":f.content=p.thinking||"";break}n.push(f)}}return s.type==="tool_result"&&n.push({kind:"tool_result",id:x(),sessionId:t,commandId:o,timestamp:l,role:"assistant",toolId:s.tool_use_id||void 0,content:typeof s.content=="string"?s.content:JSON.stringify(s.content??""),isError:s.is_error||!1}),s.type==="result"&&n.push({kind:"complete",id:x(),sessionId:t,commandId:o,timestamp:l,role:"assistant",content:s.result||"",metadata:{sessionId:s.session_id,cost:s.cost_usd??s.cost,totalCost:s.total_cost_usd,duration:s.duration_ms,numTurns:s.num_turns}}),n}function ot(e){switch(e){case"text":return"text";case"tool_use":return"tool_use";case"thinking":return"thinking";default:return null}}var ne=d(()=>{"use strict"});import{mkdirSync as rt,appendFileSync as st,existsSync as nt,readFileSync as at}from"fs";import{join as it,dirname as ct}from"path";function ae(e,t){return it(c.projectsDir,e,"chat-history",`${t}.jsonl`)}function _(e,t){let o=ae(e,t.sessionId);rt(ct(o),{recursive:!0}),st(o,JSON.stringify(t)+`
3
+ `,"utf-8")}function ie(e,t){let o=ae(e,t);if(!nt(o))return[];let r=at(o,"utf-8"),s=[];for(let n of r.split(`
4
+ `))if(n.trim())try{s.push(JSON.parse(n))}catch{}return s}var z=d(()=>{"use strict";y()});import{readFileSync as lt,existsSync as pt}from"fs";import{basename as dt}from"path";function gt(e){let t=e.substring(e.lastIndexOf(".")).toLowerCase();return ut[t]||"application/octet-stream"}async function ce(e,t){if(!pt(t))return console.warn(`[uploader] File not found: ${t}`),!1;let o=dt(t),r=gt(o),s=lt(t),n=c.serverUrl.replace(/^wss:/,"https:").replace(/^ws:/,"http:").replace(/\/ws\/agent$/,""),l=new FormData;l.append("projectId",e),l.append("file",new Blob([s],{type:r}),o);try{let a=await fetch(`${n}/agent/documents/upload`,{method:"POST",headers:{Authorization:`Bearer ${c.token}`},body:l});if(!a.ok){let p=await a.text();return console.error(`[uploader] Upload failed (${a.status}): ${p}`),!1}return console.log(`[uploader] Uploaded ${o} for project ${e}`),!0}catch(a){return console.error("[uploader] Upload error:",a),!1}}var ut,le=d(()=>{"use strict";y();ut={".xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",".xls":"application/vnd.ms-excel",".pdf":"application/pdf",".docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document",".doc":"application/msword",".csv":"text/csv",".json":"application/json",".md":"text/markdown",".txt":"text/plain",".zip":"application/zip"}});import{readdirSync as mt,statSync as ft,readFileSync as yt,writeFileSync as ht,existsSync as vt}from"fs";import{join as H}from"path";function kt(e){let t=H(e,pe);if(!vt(t))return{};try{return JSON.parse(yt(t,"utf-8"))}catch{return{}}}function xt(e,t){ht(H(e,pe),JSON.stringify(t,null,2))}async function de(e,t){let o=kt(t),r=!1,s;try{s=mt(t)}catch{return}for(let n of s){let l=n.substring(n.lastIndexOf(".")).toLowerCase();if(!St.has(l))continue;let a=H(t,n);try{let p=ft(a);if(!p.isFile())continue;let h=o[n];if(h&&h>=p.mtimeMs)continue;console.log(`[auto-upload] Uploading ${n}...`),await ce(e,a)&&(o[n]=p.mtimeMs,r=!0)}catch(p){console.error(`[auto-upload] Failed to process ${n}:`,p)}}r&&xt(t,o)}var St,pe,ue=d(()=>{"use strict";le();St=new Set([".xlsx",".xls",".csv",".pdf",".docx",".doc"]),pe=".ptah-uploaded.json"});import{spawn as wt}from"child_process";import{mkdirSync as bt}from"fs";import{randomUUID as L}from"crypto";function ge(e){let{commandId:t,sessionId:o,projectId:r,projectDir:s,message:n,claudeSessionId:l,systemPrompt:a}=e,h=["-p",n,"--output-format","stream-json","--verbose","--allowedTools","Bash,Read,Edit,Write,Glob,Grep","--append-system-prompt",a||"Your text responses are rendered as markdown in a web UI. Use markdown formatting (tables, code blocks, lists, headings) for readability. You may freely create/edit files as needed \u2014 but always include a summary of what you did in your text response so the user can see it in the UI."];l&&h.push("--resume",l),bt(s,{recursive:!0}),console.log(`Starting Claude for session ${o} in ${s}`);let f={kind:"text",id:L(),sessionId:o,commandId:t,timestamp:new Date().toISOString(),role:"user",content:n};_(r,f),i({type:"agent:claude-message",payload:f});let P=wt(c.claudePath,h,{cwd:s,env:{...process.env},stdio:["pipe","pipe","pipe"]}),$="",Ze;function oe(u){if(!u.trim())return;try{let S=JSON.parse(u);S.session_id&&(Ze=S.session_id)}catch{return}let g=se(u,o,t);for(let S of g)S.kind!=="text_delta"&&_(r,S),i({type:"agent:claude-message",payload:S})}P.stdout.on("data",u=>{$+=u.toString();let g=$.split(`
5
+ `);$=g.pop()||"";for(let S of g)oe(S)}),P.stderr.on("data",u=>{}),P.on("close",u=>{if($.trim()&&oe($),console.log(`Claude exited with code ${u} for session ${o}`),F(o),u===0&&de(r,s).catch(g=>console.error("[auto-upload] Error:",g)),u!==0){let g={kind:"error",id:L(),sessionId:o,commandId:t,timestamp:new Date().toISOString(),role:"assistant",content:`Process exited with code ${u}`};_(r,g),i({type:"agent:claude-message",payload:g})}}),P.on("error",u=>{console.error(`Claude spawn error for session ${o}:`,u),F(o);let g={kind:"error",id:L(),sessionId:o,commandId:t,timestamp:new Date().toISOString(),role:"assistant",content:`Error spawning Claude: ${u.message}`};_(r,g),i({type:"agent:claude-message",payload:g})})}var me=d(()=>{"use strict";k();q();y();ne();z();ue()});function fe(e){if(J.has(e.sessionId)){console.warn(`Session ${e.sessionId} already has an active Claude process`);return}J.set(e.sessionId,{commandId:e.commandId}),ge(e)}function F(e){J.delete(e)}var J,q=d(()=>{"use strict";me();J=new Map});import{spawn as $t}from"child_process";async function ye(e){return new Promise((t,o)=>{console.log(`Building Docker in ${e}...`);let r=$t("docker",["compose","up","-d","--build"],{cwd:e,stdio:"inherit"});r.on("close",s=>{s===0?(console.log("Docker build successful"),t()):o(new Error(`Docker build failed with code ${s}`))}),r.on("error",s=>{o(new Error(`Docker build error: ${s.message}`))})})}var he=d(()=>{"use strict"});import{writeFile as _t}from"fs/promises";import{spawn as It}from"child_process";function T(e,t){return new Promise((o,r)=>{let s=It(e,t,{stdio:"inherit"});s.on("close",n=>{n===0?o():r(new Error(`${e} failed with code ${n}`))}),s.on("error",r)})}async function ve(e,t,o,r){let s=/^[a-z0-9][a-z0-9-]*$/;if(!s.test(e)||!s.test(t))throw new Error("Invalid project or org code for Nginx config");if(o<1024||o>65535||r<1024||r>65535)throw new Error("Invalid port number");let n=`${e}.${t}.mybptah.com`,l=`
6
6
  server {
7
7
  listen 80;
8
8
  server_name ${n};
@@ -27,13 +27,13 @@ server {
27
27
  proxy_cache_bypass $http_upgrade;
28
28
  }
29
29
  }
30
- `,a=`/etc/nginx/sites-available/${n}`,p=`/etc/nginx/sites-enabled/${n}`;await $t(a,l);try{await T("ln",["-sf",a,p])}catch{}return await T("nginx",["-t"]),await T("nginx",["-s","reload"]),console.log(`Nginx configured for ${n}`),n}async function Se(e){try{await T("certbot",["--nginx","-d",e,"--non-interactive","--agree-tos","--email","ssl@mybptah.com"]),console.log(`SSL configured for ${e}`)}catch(t){console.error(`SSL setup failed for ${e}:`,t)}}var ke=d(()=>{"use strict"});function E(e,t,o){let r={type:"agent:deploy-status",payload:{commandId:e.commandId,deploymentId:e.deploymentId,status:t,...o}};i(r)}async function xe(e){try{E(e,"building"),await ye(e.projectDir),E(e,"deploying");let r=await ve(e.projectCode,e.orgCode,3001,3002);await Se(r).catch(()=>{});let s=`https://${r}`;E(e,"live",{demoUrl:s}),console.log(`Deploy complete: ${s}`)}catch(t){let o=t instanceof Error?t.message:"Unknown deploy error";console.error("Deploy failed:",o),E(e,"failed",{errorMessage:o})}}var we=d(()=>{"use strict";he();ke();k()});import{execSync as J}from"child_process";function be(){try{return J(`docker ps -a --format '{"id":"{{.ID}}","name":"{{.Names}}","image":"{{.Image}}","status":"{{.Status}}","state":"{{.State}}","ports":"{{.Ports}}","createdAt":"{{.CreatedAt}}"}'`,{encoding:"utf-8",timeout:1e4}).trim().split(`
31
- `).filter(t=>t.trim()).map(t=>{let o=JSON.parse(t),r=o.name.match(/^ptah-([a-f0-9-]+)/);return{...o,projectId:r?.[1]||void 0}})}catch(e){return console.error("[docker] Failed to list containers:",e),[]}}function $e(e){try{return J(`docker stop ${e}`,{encoding:"utf-8",timeout:3e4}),{success:!0}}catch(t){return{success:!1,error:t.message}}}function _e(e){try{return J(`docker rm -f ${e}`,{encoding:"utf-8",timeout:3e4}),{success:!0}}catch(t){return{success:!1,error:t.message}}}var Ie=d(()=>{"use strict"});async function It(){if(Ce)return V;Ce=!0;try{let{createRequire:e}=await import("module");V=e(import.meta.url)("node-pty"),console.log("[terminal] node-pty loaded successfully")}catch(e){console.warn("[terminal] node-pty not available \u2014 terminal feature disabled",e.message)}return V}function Ct(e){let t=v.get(e);t&&(clearTimeout(t.idleTimer),t.idleTimer=setTimeout(()=>{console.log(`[terminal] ${e} idle timeout \u2014 killing`),N(e)},Ae))}async function Me(e,t,o){let r=await It();if(!r){i({type:"agent:terminal-exit",payload:{terminalId:e,exitCode:1}});return}if(v.has(e)){console.warn(`[terminal] Terminal ${e} already exists`);return}let s=process.env.SHELL||"/bin/bash",n=r.spawn(s,[],{name:"xterm-256color",cols:t,rows:o,cwd:process.env.HOME||"/",env:process.env}),l=setTimeout(()=>{console.log(`[terminal] ${e} idle timeout \u2014 killing`),N(e)},Ae);v.set(e,{pty:n,idleTimer:l}),n.onData(a=>{i({type:"agent:terminal-output",payload:{terminalId:e,data:a}})}),n.onExit(({exitCode:a})=>{let p=v.get(e);p&&clearTimeout(p.idleTimer),v.delete(e),i({type:"agent:terminal-exit",payload:{terminalId:e,exitCode:a}})}),console.log(`[terminal] Opened ${e} (${t}x${o})`)}function Pe(e,t){let o=v.get(e);o&&(o.pty.write(t),Ct(e))}function Te(e,t,o){let r=v.get(e);r&&r.pty.resize(t,o)}function N(e){let t=v.get(e);t&&(clearTimeout(t.idleTimer),t.pty.kill(),v.delete(e),console.log(`[terminal] Closed ${e}`))}var V,Ce,Ae,v,Ee=d(()=>{"use strict";k();V=null,Ce=!1;Ae=5*60*1e3,v=new Map});import{mkdirSync as Ne,writeFileSync as De,readdirSync as At,unlinkSync as Mt}from"fs";import{join as I}from"path";import{homedir as Pt}from"os";function Oe(e){return`${Re}${e}.md`}function Ue(e){let t=["---"];return t.push(`name: ${e.name}`),e.description&&t.push(`description: ${e.description}`),t.push("---"),t.push(""),t.push(e.content),t.join(`
32
- `)}function je(e){try{let t=At(e);for(let o of t)o.startsWith(Re)&&o.endsWith(".md")&&Mt(I(e,o))}catch{}}function ze(e){Ne(D,{recursive:!0}),je(D);for(let t of e){let o=I(D,Oe(t.code));De(o,Ue(t),"utf-8")}console.log(`[skills] Wrote ${e.length} org skills to ${D}`)}function He(e,t){let o=I(c.projectsDir,e,".claude","commands");Ne(o,{recursive:!0}),je(o);for(let r of t){let s=I(o,Oe(r.code));De(s,Ue(r),"utf-8")}console.log(`[skills] Wrote ${t.length} project skills for ${e}`)}var Re,D,Le=d(()=>{"use strict";y();Re="ptah-",D=I(Pt(),".claude","commands")});import{execSync as B}from"child_process";function Fe(e){switch(e.type){case"server:auth-result":e.payload.success||(console.error("Authentication failed:",e.payload.error),process.exit(1));break;case"server:command":console.log(`Received command: ${e.payload.action}`,e.payload);break;case"server:doc-sync":console.log(`Doc sync: ${e.payload.action} ${e.payload.fileName}`);break;case"server:claude-start":{let{commandId:t,sessionId:o,projectId:r,message:s,claudeSessionId:n,projectDir:l,systemPrompt:a}=e.payload;console.log(`Starting Claude session ${o} for project ${r}`),fe({commandId:t,sessionId:o,projectId:r,projectDir:l,message:s,claudeSessionId:n,systemPrompt:a});break}case"server:claude-stop":{console.log(`Stop requested for session ${e.payload.sessionId}`);break}case"server:chat-history-request":{let{sessionId:t,projectId:o}=e.payload;console.log(`Chat history request for session ${t}`);let r=ie(o,t);i({type:"agent:chat-history",payload:{sessionId:t,messages:r}});break}case"server:deploy-start":{let{commandId:t,deploymentId:o,projectId:r,projectCode:s,orgCode:n,projectDir:l}=e.payload;console.log(`Starting deployment for project ${s}`),xe({commandId:t,deploymentId:o,projectId:r,projectCode:s,orgCode:n,projectDir:l});break}case"server:docker-list":{let{requestId:t}=e.payload;console.log(`Docker list request: ${t}`);let o=be();i({type:"agent:docker-list",payload:{requestId:t,containers:o}});break}case"server:docker-stop":{let{requestId:t,containerId:o}=e.payload;console.log(`Docker stop: ${o}`);let r=$e(o);i({type:"agent:docker-action",payload:{requestId:t,...r}});break}case"server:docker-remove":{let{requestId:t,containerId:o}=e.payload;console.log(`Docker remove: ${o}`);let r=_e(o);i({type:"agent:docker-action",payload:{requestId:t,...r}});break}case"server:terminal-open":{let{terminalId:t,cols:o,rows:r}=e.payload;Me(t,o,r);break}case"server:terminal-input":{Pe(e.payload.terminalId,e.payload.data);break}case"server:terminal-resize":{let{terminalId:t,cols:o,rows:r}=e.payload;Te(t,o,r);break}case"server:terminal-close":{N(e.payload.terminalId);break}case"server:skills-sync":{try{ze(e.payload.skills),i({type:"agent:skills-sync-result",payload:{success:!0}})}catch(t){let o=t.message;console.error("[skills] Sync failed:",o),i({type:"agent:skills-sync-result",payload:{success:!1,error:o}})}break}case"server:project-skills-sync":{try{He(e.payload.projectId,e.payload.skills),i({type:"agent:skills-sync-result",payload:{success:!0}})}catch(t){let o=t.message;console.error("[skills] Project sync failed:",o),i({type:"agent:skills-sync-result",payload:{success:!1,error:o}})}break}case"server:agent-info":{let{requestId:t}=e.payload;i({type:"agent:info",payload:{requestId:t,version:"0.0.29",nodeVersion:process.version,uptime:Math.floor(process.uptime()),platform:process.platform,arch:process.arch,memoryUsage:Math.round(process.memoryUsage().rss/1024/1024),projectsDir:c.projectsDir}});break}case"server:agent-upgrade":{let{requestId:t}=e.payload,o="0.0.29";console.log("[agent] Upgrade requested...");try{B("npm cache clean --force 2>/dev/null; npm install -g @z_ptah/agent@latest --prefer-online",{encoding:"utf-8",timeout:12e4,shell:"/bin/bash"});let r=B("ptah version",{encoding:"utf-8"}).trim().replace("ptah-agent v","");i({type:"agent:upgrade-result",payload:{requestId:t,success:!0,previousVersion:o,newVersion:r}})}catch(r){i({type:"agent:upgrade-result",payload:{requestId:t,success:!1,previousVersion:o,error:r.message}})}break}case"server:agent-restart":{let{requestId:t}=e.payload;console.log("[agent] Restart requested..."),i({type:"agent:restart-result",payload:{requestId:t,success:!0}}),setTimeout(()=>{try{B("systemctl restart ptah-agent",{timeout:1e4})}catch{}},500);break}default:console.warn("Unknown message type:",e.type)}}var qe=d(()=>{"use strict";y();F();we();j();Ie();Ee();Le();k()});import{spawn as Tt}from"child_process";async function Je(){return new Promise(e=>{let t=Tt(c.claudePath,["auth","status"],{env:{...process.env},stdio:["pipe","pipe","pipe"]}),o="";t.stdout.on("data",r=>{o+=r.toString()}),t.stderr.on("data",r=>{o+=r.toString()}),t.on("close",()=>{try{let r=JSON.parse(o.trim());e({loggedIn:r.loggedIn===!0,email:r.email,subscriptionType:r.subscriptionType})}catch{e({loggedIn:!1})}}),t.on("error",()=>{e({loggedIn:!1})})})}function Ve(e){G({loggedIn:e.loggedIn,email:e.email}),i({type:"agent:claude-status",payload:e})}var Be=d(()=>{"use strict";k();y()});import W from"ws";function G(e){Ge=e}function i(e){m?.readyState===W.OPEN&&m.send(JSON.stringify(e))}function Nt(){K(),R=setInterval(()=>{let e={type:"agent:heartbeat",payload:{cpuUsage:0,memoryUsage:Math.round(process.memoryUsage().heapUsed/1024/1024),activeProjects:0,claude:Ge}};i(e)},c.heartbeatIntervalMs)}function K(){R&&(clearInterval(R),R=null)}function Dt(){if(w)return;let e=Math.min(c.reconnectIntervalMs*Math.pow(2,O),Et);O++,console.log(`Reconnecting in ${e/1e3}s (attempt ${O})...`),w=setTimeout(()=>{w=null,X()},e)}function X(){C||m?.readyState===W.OPEN||(C=!0,console.log(`Connecting to ${c.serverUrl}...`),m=new W(c.serverUrl),m.on("open",()=>{C=!1,console.log("Connected to Ptah Server");let e={type:"agent:auth",payload:{token:c.token}};i(e)}),m.on("message",e=>{try{let t=JSON.parse(e.toString());Fe(t),t.type==="server:auth-result"&&t.payload?.success&&(console.log(`Authenticated as VPS ${t.payload.vpsId}`),O=0,Nt(),Je().then(o=>{G({loggedIn:o.loggedIn,email:o.email}),Ve(o)}))}catch(t){console.error("Failed to parse message:",t)}}),m.on("close",()=>{C=!1,K(),console.log("Disconnected from Ptah Server"),Dt()}),m.on("error",e=>{C=!1,console.error("WebSocket error:",e.message)}))}function Y(){w&&(clearTimeout(w),w=null),K(),m&&(m.close(),m=null)}var m,R,w,C,O,Ge,Et,k=d(()=>{"use strict";y();qe();Be();m=null,R=null,w=null,C=!1,O=0,Ge={loggedIn:!1};Et=6e4});import Rt from"fastify";async function We(){await Q.listen({port:c.healthPort,host:"0.0.0.0"}),console.log(`Agent health server on http://0.0.0.0:${c.healthPort}`)}async function Z(){await Q.close()}var Q,Ke=d(()=>{"use strict";y();Q=Rt({logger:!1});Q.get("/health",async()=>({status:"ok",timestamp:new Date().toISOString(),uptime:process.uptime()}))});var Ut={};async function Ot(){console.log("Starting Ptah Agent..."),console.log(` Server: ${c.serverUrl}`),console.log(` Health port: ${c.healthPort}`),console.log(` Projects dir: ${c.projectsDir}`),await We(),X()}var Xe=d(()=>{"use strict";y();k();Ke();process.on("SIGINT",async()=>{console.log(`
30
+ `,a=`/etc/nginx/sites-available/${n}`,p=`/etc/nginx/sites-enabled/${n}`;await _t(a,l);try{await T("ln",["-sf",a,p])}catch{}return await T("nginx",["-t"]),await T("nginx",["-s","reload"]),console.log(`Nginx configured for ${n}`),n}async function Se(e){try{await T("certbot",["--nginx","-d",e,"--non-interactive","--agree-tos","--email","ssl@mybptah.com"]),console.log(`SSL configured for ${e}`)}catch(t){console.error(`SSL setup failed for ${e}:`,t)}}var ke=d(()=>{"use strict"});function E(e,t,o){let r={type:"agent:deploy-status",payload:{commandId:e.commandId,deploymentId:e.deploymentId,status:t,...o}};i(r)}async function xe(e){try{E(e,"building"),await ye(e.projectDir),E(e,"deploying");let r=await ve(e.projectCode,e.orgCode,3001,3002);await Se(r).catch(()=>{});let s=`https://${r}`;E(e,"live",{demoUrl:s}),console.log(`Deploy complete: ${s}`)}catch(t){let o=t instanceof Error?t.message:"Unknown deploy error";console.error("Deploy failed:",o),E(e,"failed",{errorMessage:o})}}var we=d(()=>{"use strict";he();ke();k()});import{execSync as N}from"child_process";function be(){try{return N(`docker ps -a --format '{"id":"{{.ID}}","name":"{{.Names}}","image":"{{.Image}}","status":"{{.Status}}","state":"{{.State}}","ports":"{{.Ports}}","createdAt":"{{.CreatedAt}}"}'`,{encoding:"utf-8",timeout:1e4}).trim().split(`
31
+ `).filter(t=>t.trim()).map(t=>{let o=JSON.parse(t),r=o.name.match(/^ptah-([a-f0-9-]+)/);return{...o,projectId:r?.[1]||void 0}})}catch(e){return console.error("[docker] Failed to list containers:",e),[]}}function $e(e){try{return N(`docker stop ${e}`,{encoding:"utf-8",timeout:3e4}),{success:!0}}catch(t){return{success:!1,error:t.message}}}function _e(e){try{return N(`docker start ${e}`,{encoding:"utf-8",timeout:3e4}),{success:!0}}catch(t){return{success:!1,error:t.message}}}function Ie(e){try{return N(`docker rm -f ${e}`,{encoding:"utf-8",timeout:3e4}),{success:!0}}catch(t){return{success:!1,error:t.message}}}var Ce=d(()=>{"use strict"});async function Ct(){if(Ae)return V;Ae=!0;try{let{createRequire:e}=await import("module");V=e(import.meta.url)("node-pty"),console.log("[terminal] node-pty loaded successfully")}catch(e){console.warn("[terminal] node-pty not available \u2014 terminal feature disabled",e.message)}return V}function At(e){let t=v.get(e);t&&(clearTimeout(t.idleTimer),t.idleTimer=setTimeout(()=>{console.log(`[terminal] ${e} idle timeout \u2014 killing`),D(e)},Me))}async function Pe(e,t,o){let r=await Ct();if(!r){i({type:"agent:terminal-exit",payload:{terminalId:e,exitCode:1}});return}if(v.has(e)){console.warn(`[terminal] Terminal ${e} already exists`);return}let s=process.env.SHELL||"/bin/bash",n=r.spawn(s,[],{name:"xterm-256color",cols:t,rows:o,cwd:process.env.HOME||"/",env:process.env}),l=setTimeout(()=>{console.log(`[terminal] ${e} idle timeout \u2014 killing`),D(e)},Me);v.set(e,{pty:n,idleTimer:l}),n.onData(a=>{i({type:"agent:terminal-output",payload:{terminalId:e,data:a}})}),n.onExit(({exitCode:a})=>{let p=v.get(e);p&&clearTimeout(p.idleTimer),v.delete(e),i({type:"agent:terminal-exit",payload:{terminalId:e,exitCode:a}})}),console.log(`[terminal] Opened ${e} (${t}x${o})`)}function Te(e,t){let o=v.get(e);o&&(o.pty.write(t),At(e))}function Ee(e,t,o){let r=v.get(e);r&&r.pty.resize(t,o)}function D(e){let t=v.get(e);t&&(clearTimeout(t.idleTimer),t.pty.kill(),v.delete(e),console.log(`[terminal] Closed ${e}`))}var V,Ae,Me,v,Ne=d(()=>{"use strict";k();V=null,Ae=!1;Me=5*60*1e3,v=new Map});import{mkdirSync as De,writeFileSync as Re,readdirSync as Mt,unlinkSync as Pt}from"fs";import{join as I}from"path";import{homedir as Tt}from"os";function Ue(e){return`${Oe}${e}.md`}function je(e){let t=["---"];return t.push(`name: ${e.name}`),e.description&&t.push(`description: ${e.description}`),t.push("---"),t.push(""),t.push(e.content),t.join(`
32
+ `)}function ze(e){try{let t=Mt(e);for(let o of t)o.startsWith(Oe)&&o.endsWith(".md")&&Pt(I(e,o))}catch{}}function He(e){De(R,{recursive:!0}),ze(R);for(let t of e){let o=I(R,Ue(t.code));Re(o,je(t),"utf-8")}console.log(`[skills] Wrote ${e.length} org skills to ${R}`)}function Le(e,t){let o=I(c.projectsDir,e,".claude","commands");De(o,{recursive:!0}),ze(o);for(let r of t){let s=I(o,Ue(r.code));Re(s,je(r),"utf-8")}console.log(`[skills] Wrote ${t.length} project skills for ${e}`)}var Oe,R,Fe=d(()=>{"use strict";y();Oe="ptah-",R=I(Tt(),".claude","commands")});import{execSync as B}from"child_process";function qe(e){switch(e.type){case"server:auth-result":e.payload.success||(console.error("Authentication failed:",e.payload.error),process.exit(1));break;case"server:command":console.log(`Received command: ${e.payload.action}`,e.payload);break;case"server:doc-sync":console.log(`Doc sync: ${e.payload.action} ${e.payload.fileName}`);break;case"server:claude-start":{let{commandId:t,sessionId:o,projectId:r,message:s,claudeSessionId:n,projectDir:l,systemPrompt:a}=e.payload;console.log(`Starting Claude session ${o} for project ${r}`),fe({commandId:t,sessionId:o,projectId:r,projectDir:l,message:s,claudeSessionId:n,systemPrompt:a});break}case"server:claude-stop":{console.log(`Stop requested for session ${e.payload.sessionId}`);break}case"server:chat-history-request":{let{sessionId:t,projectId:o}=e.payload;console.log(`Chat history request for session ${t}`);let r=ie(o,t);i({type:"agent:chat-history",payload:{sessionId:t,messages:r}});break}case"server:deploy-start":{let{commandId:t,deploymentId:o,projectId:r,projectCode:s,orgCode:n,projectDir:l}=e.payload;console.log(`Starting deployment for project ${s}`),xe({commandId:t,deploymentId:o,projectId:r,projectCode:s,orgCode:n,projectDir:l});break}case"server:docker-list":{let{requestId:t}=e.payload;console.log(`Docker list request: ${t}`);let o=be();i({type:"agent:docker-list",payload:{requestId:t,containers:o}});break}case"server:docker-start":{let{requestId:t,containerId:o}=e.payload;console.log(`Docker start: ${o}`);let r=_e(o);i({type:"agent:docker-action",payload:{requestId:t,...r}});break}case"server:docker-stop":{let{requestId:t,containerId:o}=e.payload;console.log(`Docker stop: ${o}`);let r=$e(o);i({type:"agent:docker-action",payload:{requestId:t,...r}});break}case"server:docker-remove":{let{requestId:t,containerId:o}=e.payload;console.log(`Docker remove: ${o}`);let r=Ie(o);i({type:"agent:docker-action",payload:{requestId:t,...r}});break}case"server:terminal-open":{let{terminalId:t,cols:o,rows:r}=e.payload;Pe(t,o,r);break}case"server:terminal-input":{Te(e.payload.terminalId,e.payload.data);break}case"server:terminal-resize":{let{terminalId:t,cols:o,rows:r}=e.payload;Ee(t,o,r);break}case"server:terminal-close":{D(e.payload.terminalId);break}case"server:skills-sync":{try{He(e.payload.skills),i({type:"agent:skills-sync-result",payload:{success:!0}})}catch(t){let o=t.message;console.error("[skills] Sync failed:",o),i({type:"agent:skills-sync-result",payload:{success:!1,error:o}})}break}case"server:project-skills-sync":{try{Le(e.payload.projectId,e.payload.skills),i({type:"agent:skills-sync-result",payload:{success:!0}})}catch(t){let o=t.message;console.error("[skills] Project sync failed:",o),i({type:"agent:skills-sync-result",payload:{success:!1,error:o}})}break}case"server:agent-info":{let{requestId:t}=e.payload;i({type:"agent:info",payload:{requestId:t,version:"0.0.30",nodeVersion:process.version,uptime:Math.floor(process.uptime()),platform:process.platform,arch:process.arch,memoryUsage:Math.round(process.memoryUsage().rss/1024/1024),projectsDir:c.projectsDir}});break}case"server:agent-upgrade":{let{requestId:t}=e.payload,o="0.0.30";console.log("[agent] Upgrade requested...");try{B("npm cache clean --force 2>/dev/null; npm install -g @z_ptah/agent@latest --prefer-online",{encoding:"utf-8",timeout:12e4,shell:"/bin/bash"});let r=B("ptah version",{encoding:"utf-8"}).trim().replace("ptah-agent v","");i({type:"agent:upgrade-result",payload:{requestId:t,success:!0,previousVersion:o,newVersion:r}})}catch(r){i({type:"agent:upgrade-result",payload:{requestId:t,success:!1,previousVersion:o,error:r.message}})}break}case"server:agent-restart":{let{requestId:t}=e.payload;console.log("[agent] Restart requested..."),i({type:"agent:restart-result",payload:{requestId:t,success:!0}}),setTimeout(()=>{try{B("systemctl restart ptah-agent",{timeout:1e4})}catch{}},500);break}default:console.warn("Unknown message type:",e.type)}}var Je=d(()=>{"use strict";y();q();we();z();Ce();Ne();Fe();k()});import{spawn as Et}from"child_process";async function Ve(){return new Promise(e=>{let t=Et(c.claudePath,["auth","status"],{env:{...process.env},stdio:["pipe","pipe","pipe"]}),o="";t.stdout.on("data",r=>{o+=r.toString()}),t.stderr.on("data",r=>{o+=r.toString()}),t.on("close",()=>{try{let r=JSON.parse(o.trim());e({loggedIn:r.loggedIn===!0,email:r.email,subscriptionType:r.subscriptionType})}catch{e({loggedIn:!1})}}),t.on("error",()=>{e({loggedIn:!1})})})}function Be(e){G({loggedIn:e.loggedIn,email:e.email}),i({type:"agent:claude-status",payload:e})}var Ge=d(()=>{"use strict";k();y()});import W from"ws";function G(e){We=e}function i(e){m?.readyState===W.OPEN&&m.send(JSON.stringify(e))}function Dt(){K(),O=setInterval(()=>{let e={type:"agent:heartbeat",payload:{cpuUsage:0,memoryUsage:Math.round(process.memoryUsage().heapUsed/1024/1024),activeProjects:0,claude:We}};i(e)},c.heartbeatIntervalMs)}function K(){O&&(clearInterval(O),O=null)}function Rt(){if(w)return;let e=Math.min(c.reconnectIntervalMs*Math.pow(2,U),Nt);U++,console.log(`Reconnecting in ${e/1e3}s (attempt ${U})...`),w=setTimeout(()=>{w=null,X()},e)}function X(){C||m?.readyState===W.OPEN||(C=!0,console.log(`Connecting to ${c.serverUrl}...`),m=new W(c.serverUrl),m.on("open",()=>{C=!1,console.log("Connected to Ptah Server");let e={type:"agent:auth",payload:{token:c.token}};i(e)}),m.on("message",e=>{try{let t=JSON.parse(e.toString());qe(t),t.type==="server:auth-result"&&t.payload?.success&&(console.log(`Authenticated as VPS ${t.payload.vpsId}`),U=0,Dt(),Ve().then(o=>{G({loggedIn:o.loggedIn,email:o.email}),Be(o)}))}catch(t){console.error("Failed to parse message:",t)}}),m.on("close",()=>{C=!1,K(),console.log("Disconnected from Ptah Server"),Rt()}),m.on("error",e=>{C=!1,console.error("WebSocket error:",e.message)}))}function Y(){w&&(clearTimeout(w),w=null),K(),m&&(m.close(),m=null)}var m,O,w,C,U,We,Nt,k=d(()=>{"use strict";y();Je();Ge();m=null,O=null,w=null,C=!1,U=0,We={loggedIn:!1};Nt=6e4});import Ot from"fastify";async function Ke(){await Q.listen({port:c.healthPort,host:"0.0.0.0"}),console.log(`Agent health server on http://0.0.0.0:${c.healthPort}`)}async function Z(){await Q.close()}var Q,Xe=d(()=>{"use strict";y();Q=Ot({logger:!1});Q.get("/health",async()=>({status:"ok",timestamp:new Date().toISOString(),uptime:process.uptime()}))});var jt={};async function Ut(){console.log("Starting Ptah Agent..."),console.log(` Server: ${c.serverUrl}`),console.log(` Health port: ${c.healthPort}`),console.log(` Projects dir: ${c.projectsDir}`),await Ke(),X()}var Ye=d(()=>{"use strict";y();k();Xe();process.on("SIGINT",async()=>{console.log(`
33
33
  Shutting down...`),Y(),await Z(),process.exit(0)});process.on("SIGTERM",async()=>{console.log(`
34
- Shutting down...`),Y(),await Z(),process.exit(0)});Ot().catch(e=>{console.error("Failed to start agent:",e),process.exit(1)})});import{execSync as A}from"child_process";var M="0.0.29",Ye="@z_ptah/agent",b="ptah-agent",te={start:{description:"Start the agent (via systemd)",action:()=>U(`sudo systemctl start ${b}`)},stop:{description:"Stop the agent",action:()=>U(`sudo systemctl stop ${b}`)},restart:{description:"Restart the agent",action:()=>U(`sudo systemctl restart ${b}`)},status:{description:"Show agent service status",action:()=>U(`systemctl status ${b} --no-pager`)},logs:{description:"Show agent logs (live, Ctrl+C to stop)",action:()=>{let e=process.argv[3]||"50";jt(`journalctl -u ${b} -f -n ${e}`)}},upgrade:{description:"Upgrade agent to latest version",action:zt},version:{description:"Show current version",action:()=>console.log(`ptah-agent v${M}`)},help:{description:"Show this help message",action:Ht}};function U(e){try{A(e,{stdio:"inherit"})}catch{}}function jt(e){try{A(e,{stdio:"inherit"})}catch{}}function zt(){console.log(`Current version: v${M}`),console.log("Checking for updates...");try{let e=A(`npm view ${Ye} version`,{encoding:"utf-8"}).trim();if(e===M){console.log(`Already on latest version (v${M})`);return}console.log(`New version available: v${e}`),console.log("Upgrading..."),A(`sudo npm cache clean --force 2>/dev/null; sudo npm install -g ${Ye}@latest --prefer-online`,{stdio:"inherit",shell:"/bin/bash"}),console.log(`
35
- Upgraded to v${e}`),console.log("Restarting agent...");try{A(`sudo systemctl restart ${b}`,{stdio:"inherit"}),console.log("Agent restarted successfully.")}catch{console.log("Auto-restart failed. Run `ptah restart` manually.")}}catch(e){console.error("Upgrade failed:",e.message),process.exit(1)}}function Ht(){console.log(`
34
+ Shutting down...`),Y(),await Z(),process.exit(0)});Ut().catch(e=>{console.error("Failed to start agent:",e),process.exit(1)})});import{execSync as A}from"child_process";var M="0.0.30",Qe="@z_ptah/agent",b="ptah-agent",te={start:{description:"Start the agent (via systemd)",action:()=>j(`sudo systemctl start ${b}`)},stop:{description:"Stop the agent",action:()=>j(`sudo systemctl stop ${b}`)},restart:{description:"Restart the agent",action:()=>j(`sudo systemctl restart ${b}`)},status:{description:"Show agent service status",action:()=>j(`systemctl status ${b} --no-pager`)},logs:{description:"Show agent logs (live, Ctrl+C to stop)",action:()=>{let e=process.argv[3]||"50";zt(`journalctl -u ${b} -f -n ${e}`)}},upgrade:{description:"Upgrade agent to latest version",action:Ht},version:{description:"Show current version",action:()=>console.log(`ptah-agent v${M}`)},help:{description:"Show this help message",action:Lt}};function j(e){try{A(e,{stdio:"inherit"})}catch{}}function zt(e){try{A(e,{stdio:"inherit"})}catch{}}function Ht(){console.log(`Current version: v${M}`),console.log("Checking for updates...");try{let e=A(`npm view ${Qe} version`,{encoding:"utf-8"}).trim();if(e===M){console.log(`Already on latest version (v${M})`);return}console.log(`New version available: v${e}`),console.log("Upgrading..."),A(`sudo npm cache clean --force 2>/dev/null; sudo npm install -g ${Qe}@latest --prefer-online`,{stdio:"inherit",shell:"/bin/bash"}),console.log(`
35
+ Upgraded to v${e}`),console.log("Restarting agent...");try{A(`sudo systemctl restart ${b}`,{stdio:"inherit"}),console.log("Agent restarted successfully.")}catch{console.log("Auto-restart failed. Run `ptah restart` manually.")}}catch(e){console.error("Upgrade failed:",e.message),process.exit(1)}}function Lt(){console.log(`
36
36
  ptah-agent v${M}
37
37
  `),console.log(`Usage: ptah <command>
38
38
  `),console.log("Commands:");let e=Math.max(...Object.keys(te).map(t=>t.length));for(let[t,o]of Object.entries(te))console.log(` ${t.padEnd(e+2)} ${o.description}`);console.log(`
39
- Examples:`),console.log(" ptah version Show version"),console.log(" ptah upgrade Upgrade to latest"),console.log(" ptah restart Restart agent service"),console.log(" ptah logs Follow agent logs"),console.log(" ptah logs 100 Show last 100 lines + follow"),console.log("")}var ee=process.argv[2];if(!ee)Promise.resolve().then(()=>Xe());else{let e=te[ee];e?e.action():(console.error(`Unknown command: ${ee}`),console.error("Run `ptah help` for available commands."),process.exit(1))}
39
+ Examples:`),console.log(" ptah version Show version"),console.log(" ptah upgrade Upgrade to latest"),console.log(" ptah restart Restart agent service"),console.log(" ptah logs Follow agent logs"),console.log(" ptah logs 100 Show last 100 lines + follow"),console.log("")}var ee=process.argv[2];if(!ee)Promise.resolve().then(()=>Ye());else{let e=te[ee];e?e.action():(console.error(`Unknown command: ${ee}`),console.error("Run `ptah help` for available commands."),process.exit(1))}
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import"dotenv/config";import{execSync as Pe}from"child_process";function V(e){let t=process.env[e];if(!t)throw new Error(`Missing required environment variable: ${e}`);return t}function Ce(){if(process.env.CLAUDE_PATH)return process.env.CLAUDE_PATH;try{return Pe("which claude",{encoding:"utf-8"}).trim()}catch{return"/usr/bin/claude"}}var c={serverUrl:V("PTAH_SERVER_URL"),token:V("PTAH_AGENT_TOKEN"),healthPort:Number(process.env.AGENT_HEALTH_PORT||9090),heartbeatIntervalMs:Number(process.env.HEARTBEAT_INTERVAL_MS||3e4),reconnectIntervalMs:Number(process.env.RECONNECT_INTERVAL_MS||5e3),projectsDir:process.env.PROJECTS_DIR||"/projects",claudePath:Ce()};import z from"ws";import{execSync as U}from"child_process";import{spawn as Ke}from"child_process";import{mkdirSync as Xe}from"fs";import{randomUUID as D}from"crypto";import{randomUUID as v}from"crypto";function G(e,t,o){let r=e.trim();if(!r)return[];let s;try{s=JSON.parse(r)}catch{return[]}let n=[],l=new Date().toISOString();if(s.type==="content_block_delta"){let a=s.delta;return a?.type==="text_delta"&&a.text&&n.push({kind:"text_delta",id:v(),sessionId:t,commandId:o,timestamp:l,role:"assistant",content:a.text}),a?.type==="thinking_delta"&&a.thinking&&n.push({kind:"thinking",id:v(),sessionId:t,commandId:o,timestamp:l,role:"assistant",content:a.thinking}),n}if(s.type==="content_block_start"){let a=s.content_block;return a?.type==="tool_use"&&n.push({kind:"tool_use",id:v(),sessionId:t,commandId:o,timestamp:l,role:"assistant",toolName:a.name||"tool",toolId:a.id||void 0,toolInput:{}}),n}if(s.type==="content_block_stop"||s.type==="message_start"||s.type==="message_delta"||s.type==="message_stop")return n;if(s.type==="assistant"&&s.message){let a=s.message;if(Array.isArray(a.content))for(let p of a.content){let f=Me(p.type);if(!f)continue;let g={kind:f,id:v(),sessionId:t,commandId:o,timestamp:l,role:"assistant"};switch(f){case"text":g.content=p.text||"";break;case"tool_use":g.toolName=p.name||"tool",g.toolId=p.id||void 0,g.toolInput=p.input??{};break;case"thinking":g.content=p.thinking||"";break}n.push(g)}}return s.type==="tool_result"&&n.push({kind:"tool_result",id:v(),sessionId:t,commandId:o,timestamp:l,role:"assistant",toolId:s.tool_use_id||void 0,content:typeof s.content=="string"?s.content:JSON.stringify(s.content??""),isError:s.is_error||!1}),s.type==="result"&&n.push({kind:"complete",id:v(),sessionId:t,commandId:o,timestamp:l,role:"assistant",content:s.result||"",metadata:{sessionId:s.session_id,cost:s.cost_usd??s.cost,totalCost:s.total_cost_usd,duration:s.duration_ms,numTurns:s.num_turns}}),n}function Me(e){switch(e){case"text":return"text";case"tool_use":return"tool_use";case"thinking":return"thinking";default:return null}}import{mkdirSync as Ae,appendFileSync as Te,existsSync as De,readFileSync as Ee}from"fs";import{join as Ne,dirname as Re}from"path";function W(e,t){return Ne(c.projectsDir,e,"chat-history",`${t}.jsonl`)}function x(e,t){let o=W(e,t.sessionId);Ae(Re(o),{recursive:!0}),Te(o,JSON.stringify(t)+`
3
- `,"utf-8")}function K(e,t){let o=W(e,t);if(!De(o))return[];let r=Ee(o,"utf-8"),s=[];for(let n of r.split(`
4
- `))if(n.trim())try{s.push(JSON.parse(n))}catch{}return s}import{readdirSync as Le,statSync as Fe,readFileSync as qe,writeFileSync as Je,existsSync as Be}from"fs";import{join as T}from"path";import{readFileSync as Oe,existsSync as Ue}from"fs";import{basename as je}from"path";var ze={".xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",".xls":"application/vnd.ms-excel",".pdf":"application/pdf",".docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document",".doc":"application/msword",".csv":"text/csv",".json":"application/json",".md":"text/markdown",".txt":"text/plain",".zip":"application/zip"};function He(e){let t=e.substring(e.lastIndexOf(".")).toLowerCase();return ze[t]||"application/octet-stream"}async function X(e,t){if(!Ue(t))return console.warn(`[uploader] File not found: ${t}`),!1;let o=je(t),r=He(o),s=Oe(t),n=c.serverUrl.replace(/^wss:/,"https:").replace(/^ws:/,"http:").replace(/\/ws\/agent$/,""),l=new FormData;l.append("projectId",e),l.append("file",new Blob([s],{type:r}),o);try{let a=await fetch(`${n}/agent/documents/upload`,{method:"POST",headers:{Authorization:`Bearer ${c.token}`},body:l});if(!a.ok){let p=await a.text();return console.error(`[uploader] Upload failed (${a.status}): ${p}`),!1}return console.log(`[uploader] Uploaded ${o} for project ${e}`),!0}catch(a){return console.error("[uploader] Upload error:",a),!1}}var Ve=new Set([".xlsx",".xls",".csv",".pdf",".docx",".doc"]),Y=".ptah-uploaded.json";function Ge(e){let t=T(e,Y);if(!Be(t))return{};try{return JSON.parse(qe(t,"utf-8"))}catch{return{}}}function We(e,t){Je(T(e,Y),JSON.stringify(t,null,2))}async function Q(e,t){let o=Ge(t),r=!1,s;try{s=Le(t)}catch{return}for(let n of s){let l=n.substring(n.lastIndexOf(".")).toLowerCase();if(!Ve.has(l))continue;let a=T(t,n);try{let p=Fe(a);if(!p.isFile())continue;let f=o[n];if(f&&f>=p.mtimeMs)continue;console.log(`[auto-upload] Uploading ${n}...`),await X(e,a)&&(o[n]=p.mtimeMs,r=!0)}catch(p){console.error(`[auto-upload] Failed to process ${n}:`,p)}}r&&We(t,o)}function Z(e){let{commandId:t,sessionId:o,projectId:r,projectDir:s,message:n,claudeSessionId:l,systemPrompt:a}=e,f=["-p",n,"--output-format","stream-json","--verbose","--allowedTools","Bash,Read,Edit,Write,Glob,Grep","--append-system-prompt",a||"Your text responses are rendered as markdown in a web UI. Use markdown formatting (tables, code blocks, lists, headings) for readability. You may freely create/edit files as needed \u2014 but always include a summary of what you did in your text response so the user can see it in the UI."];l&&f.push("--resume",l),Xe(s,{recursive:!0}),console.log(`Starting Claude for session ${o} in ${s}`);let g={kind:"text",id:D(),sessionId:o,commandId:t,timestamp:new Date().toISOString(),role:"user",content:n};x(r,g),i({type:"agent:claude-message",payload:g});let _=Ke(c.claudePath,f,{cwd:s,env:{...process.env},stdio:["pipe","pipe","pipe"]}),S="",$e;function B(d){if(!d.trim())return;try{let h=JSON.parse(d);h.session_id&&($e=h.session_id)}catch{return}let u=G(d,o,t);for(let h of u)h.kind!=="text_delta"&&x(r,h),i({type:"agent:claude-message",payload:h})}_.stdout.on("data",d=>{S+=d.toString();let u=S.split(`
5
- `);S=u.pop()||"";for(let h of u)B(h)}),_.stderr.on("data",d=>{}),_.on("close",d=>{if(S.trim()&&B(S),console.log(`Claude exited with code ${d} for session ${o}`),E(o),d===0&&Q(r,s).catch(u=>console.error("[auto-upload] Error:",u)),d!==0){let u={kind:"error",id:D(),sessionId:o,commandId:t,timestamp:new Date().toISOString(),role:"assistant",content:`Process exited with code ${d}`};x(r,u),i({type:"agent:claude-message",payload:u})}}),_.on("error",d=>{console.error(`Claude spawn error for session ${o}:`,d),E(o);let u={kind:"error",id:D(),sessionId:o,commandId:t,timestamp:new Date().toISOString(),role:"assistant",content:`Error spawning Claude: ${d.message}`};x(r,u),i({type:"agent:claude-message",payload:u})})}var N=new Map;function ee(e){if(N.has(e.sessionId)){console.warn(`Session ${e.sessionId} already has an active Claude process`);return}N.set(e.sessionId,{commandId:e.commandId}),Z(e)}function E(e){N.delete(e)}import{spawn as Ye}from"child_process";async function te(e){return new Promise((t,o)=>{console.log(`Building Docker in ${e}...`);let r=Ye("docker",["compose","up","-d","--build"],{cwd:e,stdio:"inherit"});r.on("close",s=>{s===0?(console.log("Docker build successful"),t()):o(new Error(`Docker build failed with code ${s}`))}),r.on("error",s=>{o(new Error(`Docker build error: ${s.message}`))})})}import{writeFile as Qe}from"fs/promises";import{spawn as Ze}from"child_process";function I(e,t){return new Promise((o,r)=>{let s=Ze(e,t,{stdio:"inherit"});s.on("close",n=>{n===0?o():r(new Error(`${e} failed with code ${n}`))}),s.on("error",r)})}async function oe(e,t,o,r){let s=/^[a-z0-9][a-z0-9-]*$/;if(!s.test(e)||!s.test(t))throw new Error("Invalid project or org code for Nginx config");if(o<1024||o>65535||r<1024||r>65535)throw new Error("Invalid port number");let n=`${e}.${t}.mybptah.com`,l=`
2
+ import"dotenv/config";import{execSync as Pe}from"child_process";function V(e){let t=process.env[e];if(!t)throw new Error(`Missing required environment variable: ${e}`);return t}function Ae(){if(process.env.CLAUDE_PATH)return process.env.CLAUDE_PATH;try{return Pe("which claude",{encoding:"utf-8"}).trim()}catch{return"/usr/bin/claude"}}var c={serverUrl:V("PTAH_SERVER_URL"),token:V("PTAH_AGENT_TOKEN"),healthPort:Number(process.env.AGENT_HEALTH_PORT||9090),heartbeatIntervalMs:Number(process.env.HEARTBEAT_INTERVAL_MS||3e4),reconnectIntervalMs:Number(process.env.RECONNECT_INTERVAL_MS||5e3),projectsDir:process.env.PROJECTS_DIR||"/projects",claudePath:Ae()};import z from"ws";import{execSync as U}from"child_process";import{spawn as Xe}from"child_process";import{mkdirSync as Ye}from"fs";import{randomUUID as E}from"crypto";import{randomUUID as v}from"crypto";function G(e,t,o){let r=e.trim();if(!r)return[];let s;try{s=JSON.parse(r)}catch{return[]}let n=[],l=new Date().toISOString();if(s.type==="content_block_delta"){let a=s.delta;return a?.type==="text_delta"&&a.text&&n.push({kind:"text_delta",id:v(),sessionId:t,commandId:o,timestamp:l,role:"assistant",content:a.text}),a?.type==="thinking_delta"&&a.thinking&&n.push({kind:"thinking",id:v(),sessionId:t,commandId:o,timestamp:l,role:"assistant",content:a.thinking}),n}if(s.type==="content_block_start"){let a=s.content_block;return a?.type==="tool_use"&&n.push({kind:"tool_use",id:v(),sessionId:t,commandId:o,timestamp:l,role:"assistant",toolName:a.name||"tool",toolId:a.id||void 0,toolInput:{}}),n}if(s.type==="content_block_stop"||s.type==="message_start"||s.type==="message_delta"||s.type==="message_stop")return n;if(s.type==="assistant"&&s.message){let a=s.message;if(Array.isArray(a.content))for(let p of a.content){let f=Me(p.type);if(!f)continue;let m={kind:f,id:v(),sessionId:t,commandId:o,timestamp:l,role:"assistant"};switch(f){case"text":m.content=p.text||"";break;case"tool_use":m.toolName=p.name||"tool",m.toolId=p.id||void 0,m.toolInput=p.input??{};break;case"thinking":m.content=p.thinking||"";break}n.push(m)}}return s.type==="tool_result"&&n.push({kind:"tool_result",id:v(),sessionId:t,commandId:o,timestamp:l,role:"assistant",toolId:s.tool_use_id||void 0,content:typeof s.content=="string"?s.content:JSON.stringify(s.content??""),isError:s.is_error||!1}),s.type==="result"&&n.push({kind:"complete",id:v(),sessionId:t,commandId:o,timestamp:l,role:"assistant",content:s.result||"",metadata:{sessionId:s.session_id,cost:s.cost_usd??s.cost,totalCost:s.total_cost_usd,duration:s.duration_ms,numTurns:s.num_turns}}),n}function Me(e){switch(e){case"text":return"text";case"tool_use":return"tool_use";case"thinking":return"thinking";default:return null}}import{mkdirSync as Te,appendFileSync as De,existsSync as Ee,readFileSync as Ne}from"fs";import{join as Re,dirname as Oe}from"path";function W(e,t){return Re(c.projectsDir,e,"chat-history",`${t}.jsonl`)}function x(e,t){let o=W(e,t.sessionId);Te(Oe(o),{recursive:!0}),De(o,JSON.stringify(t)+`
3
+ `,"utf-8")}function K(e,t){let o=W(e,t);if(!Ee(o))return[];let r=Ne(o,"utf-8"),s=[];for(let n of r.split(`
4
+ `))if(n.trim())try{s.push(JSON.parse(n))}catch{}return s}import{readdirSync as Fe,statSync as qe,readFileSync as Je,writeFileSync as Be,existsSync as Ve}from"fs";import{join as D}from"path";import{readFileSync as Ue,existsSync as je}from"fs";import{basename as ze}from"path";var He={".xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",".xls":"application/vnd.ms-excel",".pdf":"application/pdf",".docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document",".doc":"application/msword",".csv":"text/csv",".json":"application/json",".md":"text/markdown",".txt":"text/plain",".zip":"application/zip"};function Le(e){let t=e.substring(e.lastIndexOf(".")).toLowerCase();return He[t]||"application/octet-stream"}async function X(e,t){if(!je(t))return console.warn(`[uploader] File not found: ${t}`),!1;let o=ze(t),r=Le(o),s=Ue(t),n=c.serverUrl.replace(/^wss:/,"https:").replace(/^ws:/,"http:").replace(/\/ws\/agent$/,""),l=new FormData;l.append("projectId",e),l.append("file",new Blob([s],{type:r}),o);try{let a=await fetch(`${n}/agent/documents/upload`,{method:"POST",headers:{Authorization:`Bearer ${c.token}`},body:l});if(!a.ok){let p=await a.text();return console.error(`[uploader] Upload failed (${a.status}): ${p}`),!1}return console.log(`[uploader] Uploaded ${o} for project ${e}`),!0}catch(a){return console.error("[uploader] Upload error:",a),!1}}var Ge=new Set([".xlsx",".xls",".csv",".pdf",".docx",".doc"]),Y=".ptah-uploaded.json";function We(e){let t=D(e,Y);if(!Ve(t))return{};try{return JSON.parse(Je(t,"utf-8"))}catch{return{}}}function Ke(e,t){Be(D(e,Y),JSON.stringify(t,null,2))}async function Q(e,t){let o=We(t),r=!1,s;try{s=Fe(t)}catch{return}for(let n of s){let l=n.substring(n.lastIndexOf(".")).toLowerCase();if(!Ge.has(l))continue;let a=D(t,n);try{let p=qe(a);if(!p.isFile())continue;let f=o[n];if(f&&f>=p.mtimeMs)continue;console.log(`[auto-upload] Uploading ${n}...`),await X(e,a)&&(o[n]=p.mtimeMs,r=!0)}catch(p){console.error(`[auto-upload] Failed to process ${n}:`,p)}}r&&Ke(t,o)}function Z(e){let{commandId:t,sessionId:o,projectId:r,projectDir:s,message:n,claudeSessionId:l,systemPrompt:a}=e,f=["-p",n,"--output-format","stream-json","--verbose","--allowedTools","Bash,Read,Edit,Write,Glob,Grep","--append-system-prompt",a||"Your text responses are rendered as markdown in a web UI. Use markdown formatting (tables, code blocks, lists, headings) for readability. You may freely create/edit files as needed \u2014 but always include a summary of what you did in your text response so the user can see it in the UI."];l&&f.push("--resume",l),Ye(s,{recursive:!0}),console.log(`Starting Claude for session ${o} in ${s}`);let m={kind:"text",id:E(),sessionId:o,commandId:t,timestamp:new Date().toISOString(),role:"user",content:n};x(r,m),i({type:"agent:claude-message",payload:m});let _=Xe(c.claudePath,f,{cwd:s,env:{...process.env},stdio:["pipe","pipe","pipe"]}),S="",Ce;function B(d){if(!d.trim())return;try{let h=JSON.parse(d);h.session_id&&(Ce=h.session_id)}catch{return}let u=G(d,o,t);for(let h of u)h.kind!=="text_delta"&&x(r,h),i({type:"agent:claude-message",payload:h})}_.stdout.on("data",d=>{S+=d.toString();let u=S.split(`
5
+ `);S=u.pop()||"";for(let h of u)B(h)}),_.stderr.on("data",d=>{}),_.on("close",d=>{if(S.trim()&&B(S),console.log(`Claude exited with code ${d} for session ${o}`),N(o),d===0&&Q(r,s).catch(u=>console.error("[auto-upload] Error:",u)),d!==0){let u={kind:"error",id:E(),sessionId:o,commandId:t,timestamp:new Date().toISOString(),role:"assistant",content:`Process exited with code ${d}`};x(r,u),i({type:"agent:claude-message",payload:u})}}),_.on("error",d=>{console.error(`Claude spawn error for session ${o}:`,d),N(o);let u={kind:"error",id:E(),sessionId:o,commandId:t,timestamp:new Date().toISOString(),role:"assistant",content:`Error spawning Claude: ${d.message}`};x(r,u),i({type:"agent:claude-message",payload:u})})}var R=new Map;function ee(e){if(R.has(e.sessionId)){console.warn(`Session ${e.sessionId} already has an active Claude process`);return}R.set(e.sessionId,{commandId:e.commandId}),Z(e)}function N(e){R.delete(e)}import{spawn as Qe}from"child_process";async function te(e){return new Promise((t,o)=>{console.log(`Building Docker in ${e}...`);let r=Qe("docker",["compose","up","-d","--build"],{cwd:e,stdio:"inherit"});r.on("close",s=>{s===0?(console.log("Docker build successful"),t()):o(new Error(`Docker build failed with code ${s}`))}),r.on("error",s=>{o(new Error(`Docker build error: ${s.message}`))})})}import{writeFile as Ze}from"fs/promises";import{spawn as et}from"child_process";function I(e,t){return new Promise((o,r)=>{let s=et(e,t,{stdio:"inherit"});s.on("close",n=>{n===0?o():r(new Error(`${e} failed with code ${n}`))}),s.on("error",r)})}async function oe(e,t,o,r){let s=/^[a-z0-9][a-z0-9-]*$/;if(!s.test(e)||!s.test(t))throw new Error("Invalid project or org code for Nginx config");if(o<1024||o>65535||r<1024||r>65535)throw new Error("Invalid port number");let n=`${e}.${t}.mybptah.com`,l=`
6
6
  server {
7
7
  listen 80;
8
8
  server_name ${n};
@@ -27,8 +27,8 @@ server {
27
27
  proxy_cache_bypass $http_upgrade;
28
28
  }
29
29
  }
30
- `,a=`/etc/nginx/sites-available/${n}`,p=`/etc/nginx/sites-enabled/${n}`;await Qe(a,l);try{await I("ln",["-sf",a,p])}catch{}return await I("nginx",["-t"]),await I("nginx",["-s","reload"]),console.log(`Nginx configured for ${n}`),n}async function re(e){try{await I("certbot",["--nginx","-d",e,"--non-interactive","--agree-tos","--email","ssl@mybptah.com"]),console.log(`SSL configured for ${e}`)}catch(t){console.error(`SSL setup failed for ${e}:`,t)}}function $(e,t,o){let r={type:"agent:deploy-status",payload:{commandId:e.commandId,deploymentId:e.deploymentId,status:t,...o}};i(r)}async function se(e){try{$(e,"building"),await te(e.projectDir),$(e,"deploying");let r=await oe(e.projectCode,e.orgCode,3001,3002);await re(r).catch(()=>{});let s=`https://${r}`;$(e,"live",{demoUrl:s}),console.log(`Deploy complete: ${s}`)}catch(t){let o=t instanceof Error?t.message:"Unknown deploy error";console.error("Deploy failed:",o),$(e,"failed",{errorMessage:o})}}import{execSync as R}from"child_process";function ne(){try{return R(`docker ps -a --format '{"id":"{{.ID}}","name":"{{.Names}}","image":"{{.Image}}","status":"{{.Status}}","state":"{{.State}}","ports":"{{.Ports}}","createdAt":"{{.CreatedAt}}"}'`,{encoding:"utf-8",timeout:1e4}).trim().split(`
31
- `).filter(t=>t.trim()).map(t=>{let o=JSON.parse(t),r=o.name.match(/^ptah-([a-f0-9-]+)/);return{...o,projectId:r?.[1]||void 0}})}catch(e){return console.error("[docker] Failed to list containers:",e),[]}}function ae(e){try{return R(`docker stop ${e}`,{encoding:"utf-8",timeout:3e4}),{success:!0}}catch(t){return{success:!1,error:t.message}}}function ie(e){try{return R(`docker rm -f ${e}`,{encoding:"utf-8",timeout:3e4}),{success:!0}}catch(t){return{success:!1,error:t.message}}}var O=null,ce=!1;async function et(){if(ce)return O;ce=!0;try{let{createRequire:e}=await import("module");O=e(import.meta.url)("node-pty"),console.log("[terminal] node-pty loaded successfully")}catch(e){console.warn("[terminal] node-pty not available \u2014 terminal feature disabled",e.message)}return O}var le=5*60*1e3,y=new Map;function tt(e){let t=y.get(e);t&&(clearTimeout(t.idleTimer),t.idleTimer=setTimeout(()=>{console.log(`[terminal] ${e} idle timeout \u2014 killing`),P(e)},le))}async function pe(e,t,o){let r=await et();if(!r){i({type:"agent:terminal-exit",payload:{terminalId:e,exitCode:1}});return}if(y.has(e)){console.warn(`[terminal] Terminal ${e} already exists`);return}let s=process.env.SHELL||"/bin/bash",n=r.spawn(s,[],{name:"xterm-256color",cols:t,rows:o,cwd:process.env.HOME||"/",env:process.env}),l=setTimeout(()=>{console.log(`[terminal] ${e} idle timeout \u2014 killing`),P(e)},le);y.set(e,{pty:n,idleTimer:l}),n.onData(a=>{i({type:"agent:terminal-output",payload:{terminalId:e,data:a}})}),n.onExit(({exitCode:a})=>{let p=y.get(e);p&&clearTimeout(p.idleTimer),y.delete(e),i({type:"agent:terminal-exit",payload:{terminalId:e,exitCode:a}})}),console.log(`[terminal] Opened ${e} (${t}x${o})`)}function de(e,t){let o=y.get(e);o&&(o.pty.write(t),tt(e))}function ue(e,t,o){let r=y.get(e);r&&r.pty.resize(t,o)}function P(e){let t=y.get(e);t&&(clearTimeout(t.idleTimer),t.pty.kill(),y.delete(e),console.log(`[terminal] Closed ${e}`))}import{mkdirSync as me,writeFileSync as ge,readdirSync as ot,unlinkSync as rt}from"fs";import{join as w}from"path";import{homedir as st}from"os";var fe="ptah-",C=w(st(),".claude","commands");function ye(e){return`${fe}${e}.md`}function he(e){let t=["---"];return t.push(`name: ${e.name}`),e.description&&t.push(`description: ${e.description}`),t.push("---"),t.push(""),t.push(e.content),t.join(`
32
- `)}function ve(e){try{let t=ot(e);for(let o of t)o.startsWith(fe)&&o.endsWith(".md")&&rt(w(e,o))}catch{}}function ke(e){me(C,{recursive:!0}),ve(C);for(let t of e){let o=w(C,ye(t.code));ge(o,he(t),"utf-8")}console.log(`[skills] Wrote ${e.length} org skills to ${C}`)}function Se(e,t){let o=w(c.projectsDir,e,".claude","commands");me(o,{recursive:!0}),ve(o);for(let r of t){let s=w(o,ye(r.code));ge(s,he(r),"utf-8")}console.log(`[skills] Wrote ${t.length} project skills for ${e}`)}function xe(e){switch(e.type){case"server:auth-result":e.payload.success||(console.error("Authentication failed:",e.payload.error),process.exit(1));break;case"server:command":console.log(`Received command: ${e.payload.action}`,e.payload);break;case"server:doc-sync":console.log(`Doc sync: ${e.payload.action} ${e.payload.fileName}`);break;case"server:claude-start":{let{commandId:t,sessionId:o,projectId:r,message:s,claudeSessionId:n,projectDir:l,systemPrompt:a}=e.payload;console.log(`Starting Claude session ${o} for project ${r}`),ee({commandId:t,sessionId:o,projectId:r,projectDir:l,message:s,claudeSessionId:n,systemPrompt:a});break}case"server:claude-stop":{console.log(`Stop requested for session ${e.payload.sessionId}`);break}case"server:chat-history-request":{let{sessionId:t,projectId:o}=e.payload;console.log(`Chat history request for session ${t}`);let r=K(o,t);i({type:"agent:chat-history",payload:{sessionId:t,messages:r}});break}case"server:deploy-start":{let{commandId:t,deploymentId:o,projectId:r,projectCode:s,orgCode:n,projectDir:l}=e.payload;console.log(`Starting deployment for project ${s}`),se({commandId:t,deploymentId:o,projectId:r,projectCode:s,orgCode:n,projectDir:l});break}case"server:docker-list":{let{requestId:t}=e.payload;console.log(`Docker list request: ${t}`);let o=ne();i({type:"agent:docker-list",payload:{requestId:t,containers:o}});break}case"server:docker-stop":{let{requestId:t,containerId:o}=e.payload;console.log(`Docker stop: ${o}`);let r=ae(o);i({type:"agent:docker-action",payload:{requestId:t,...r}});break}case"server:docker-remove":{let{requestId:t,containerId:o}=e.payload;console.log(`Docker remove: ${o}`);let r=ie(o);i({type:"agent:docker-action",payload:{requestId:t,...r}});break}case"server:terminal-open":{let{terminalId:t,cols:o,rows:r}=e.payload;pe(t,o,r);break}case"server:terminal-input":{de(e.payload.terminalId,e.payload.data);break}case"server:terminal-resize":{let{terminalId:t,cols:o,rows:r}=e.payload;ue(t,o,r);break}case"server:terminal-close":{P(e.payload.terminalId);break}case"server:skills-sync":{try{ke(e.payload.skills),i({type:"agent:skills-sync-result",payload:{success:!0}})}catch(t){let o=t.message;console.error("[skills] Sync failed:",o),i({type:"agent:skills-sync-result",payload:{success:!1,error:o}})}break}case"server:project-skills-sync":{try{Se(e.payload.projectId,e.payload.skills),i({type:"agent:skills-sync-result",payload:{success:!0}})}catch(t){let o=t.message;console.error("[skills] Project sync failed:",o),i({type:"agent:skills-sync-result",payload:{success:!1,error:o}})}break}case"server:agent-info":{let{requestId:t}=e.payload;i({type:"agent:info",payload:{requestId:t,version:"0.0.29",nodeVersion:process.version,uptime:Math.floor(process.uptime()),platform:process.platform,arch:process.arch,memoryUsage:Math.round(process.memoryUsage().rss/1024/1024),projectsDir:c.projectsDir}});break}case"server:agent-upgrade":{let{requestId:t}=e.payload,o="0.0.29";console.log("[agent] Upgrade requested...");try{U("npm cache clean --force 2>/dev/null; npm install -g @z_ptah/agent@latest --prefer-online",{encoding:"utf-8",timeout:12e4,shell:"/bin/bash"});let r=U("ptah version",{encoding:"utf-8"}).trim().replace("ptah-agent v","");i({type:"agent:upgrade-result",payload:{requestId:t,success:!0,previousVersion:o,newVersion:r}})}catch(r){i({type:"agent:upgrade-result",payload:{requestId:t,success:!1,previousVersion:o,error:r.message}})}break}case"server:agent-restart":{let{requestId:t}=e.payload;console.log("[agent] Restart requested..."),i({type:"agent:restart-result",payload:{requestId:t,success:!0}}),setTimeout(()=>{try{U("systemctl restart ptah-agent",{timeout:1e4})}catch{}},500);break}default:console.warn("Unknown message type:",e.type)}}import{spawn as nt}from"child_process";async function we(){return new Promise(e=>{let t=nt(c.claudePath,["auth","status"],{env:{...process.env},stdio:["pipe","pipe","pipe"]}),o="";t.stdout.on("data",r=>{o+=r.toString()}),t.stderr.on("data",r=>{o+=r.toString()}),t.on("close",()=>{try{let r=JSON.parse(o.trim());e({loggedIn:r.loggedIn===!0,email:r.email,subscriptionType:r.subscriptionType})}catch{e({loggedIn:!1})}}),t.on("error",()=>{e({loggedIn:!1})})})}function be(e){j({loggedIn:e.loggedIn,email:e.email}),i({type:"agent:claude-status",payload:e})}var m=null,M=null,k=null,b=!1,A=0,_e={loggedIn:!1};function j(e){_e=e}var at=6e4;function i(e){m?.readyState===z.OPEN&&m.send(JSON.stringify(e))}function it(){H(),M=setInterval(()=>{let e={type:"agent:heartbeat",payload:{cpuUsage:0,memoryUsage:Math.round(process.memoryUsage().heapUsed/1024/1024),activeProjects:0,claude:_e}};i(e)},c.heartbeatIntervalMs)}function H(){M&&(clearInterval(M),M=null)}function ct(){if(k)return;let e=Math.min(c.reconnectIntervalMs*Math.pow(2,A),at);A++,console.log(`Reconnecting in ${e/1e3}s (attempt ${A})...`),k=setTimeout(()=>{k=null,L()},e)}function L(){b||m?.readyState===z.OPEN||(b=!0,console.log(`Connecting to ${c.serverUrl}...`),m=new z(c.serverUrl),m.on("open",()=>{b=!1,console.log("Connected to Ptah Server");let e={type:"agent:auth",payload:{token:c.token}};i(e)}),m.on("message",e=>{try{let t=JSON.parse(e.toString());xe(t),t.type==="server:auth-result"&&t.payload?.success&&(console.log(`Authenticated as VPS ${t.payload.vpsId}`),A=0,it(),we().then(o=>{j({loggedIn:o.loggedIn,email:o.email}),be(o)}))}catch(t){console.error("Failed to parse message:",t)}}),m.on("close",()=>{b=!1,H(),console.log("Disconnected from Ptah Server"),ct()}),m.on("error",e=>{b=!1,console.error("WebSocket error:",e.message)}))}function F(){k&&(clearTimeout(k),k=null),H(),m&&(m.close(),m=null)}import lt from"fastify";var q=lt({logger:!1});q.get("/health",async()=>({status:"ok",timestamp:new Date().toISOString(),uptime:process.uptime()}));async function Ie(){await q.listen({port:c.healthPort,host:"0.0.0.0"}),console.log(`Agent health server on http://0.0.0.0:${c.healthPort}`)}async function J(){await q.close()}async function pt(){console.log("Starting Ptah Agent..."),console.log(` Server: ${c.serverUrl}`),console.log(` Health port: ${c.healthPort}`),console.log(` Projects dir: ${c.projectsDir}`),await Ie(),L()}process.on("SIGINT",async()=>{console.log(`
30
+ `,a=`/etc/nginx/sites-available/${n}`,p=`/etc/nginx/sites-enabled/${n}`;await Ze(a,l);try{await I("ln",["-sf",a,p])}catch{}return await I("nginx",["-t"]),await I("nginx",["-s","reload"]),console.log(`Nginx configured for ${n}`),n}async function re(e){try{await I("certbot",["--nginx","-d",e,"--non-interactive","--agree-tos","--email","ssl@mybptah.com"]),console.log(`SSL configured for ${e}`)}catch(t){console.error(`SSL setup failed for ${e}:`,t)}}function $(e,t,o){let r={type:"agent:deploy-status",payload:{commandId:e.commandId,deploymentId:e.deploymentId,status:t,...o}};i(r)}async function se(e){try{$(e,"building"),await te(e.projectDir),$(e,"deploying");let r=await oe(e.projectCode,e.orgCode,3001,3002);await re(r).catch(()=>{});let s=`https://${r}`;$(e,"live",{demoUrl:s}),console.log(`Deploy complete: ${s}`)}catch(t){let o=t instanceof Error?t.message:"Unknown deploy error";console.error("Deploy failed:",o),$(e,"failed",{errorMessage:o})}}import{execSync as C}from"child_process";function ne(){try{return C(`docker ps -a --format '{"id":"{{.ID}}","name":"{{.Names}}","image":"{{.Image}}","status":"{{.Status}}","state":"{{.State}}","ports":"{{.Ports}}","createdAt":"{{.CreatedAt}}"}'`,{encoding:"utf-8",timeout:1e4}).trim().split(`
31
+ `).filter(t=>t.trim()).map(t=>{let o=JSON.parse(t),r=o.name.match(/^ptah-([a-f0-9-]+)/);return{...o,projectId:r?.[1]||void 0}})}catch(e){return console.error("[docker] Failed to list containers:",e),[]}}function ae(e){try{return C(`docker stop ${e}`,{encoding:"utf-8",timeout:3e4}),{success:!0}}catch(t){return{success:!1,error:t.message}}}function ie(e){try{return C(`docker start ${e}`,{encoding:"utf-8",timeout:3e4}),{success:!0}}catch(t){return{success:!1,error:t.message}}}function ce(e){try{return C(`docker rm -f ${e}`,{encoding:"utf-8",timeout:3e4}),{success:!0}}catch(t){return{success:!1,error:t.message}}}var O=null,le=!1;async function tt(){if(le)return O;le=!0;try{let{createRequire:e}=await import("module");O=e(import.meta.url)("node-pty"),console.log("[terminal] node-pty loaded successfully")}catch(e){console.warn("[terminal] node-pty not available \u2014 terminal feature disabled",e.message)}return O}var pe=5*60*1e3,y=new Map;function ot(e){let t=y.get(e);t&&(clearTimeout(t.idleTimer),t.idleTimer=setTimeout(()=>{console.log(`[terminal] ${e} idle timeout \u2014 killing`),P(e)},pe))}async function de(e,t,o){let r=await tt();if(!r){i({type:"agent:terminal-exit",payload:{terminalId:e,exitCode:1}});return}if(y.has(e)){console.warn(`[terminal] Terminal ${e} already exists`);return}let s=process.env.SHELL||"/bin/bash",n=r.spawn(s,[],{name:"xterm-256color",cols:t,rows:o,cwd:process.env.HOME||"/",env:process.env}),l=setTimeout(()=>{console.log(`[terminal] ${e} idle timeout \u2014 killing`),P(e)},pe);y.set(e,{pty:n,idleTimer:l}),n.onData(a=>{i({type:"agent:terminal-output",payload:{terminalId:e,data:a}})}),n.onExit(({exitCode:a})=>{let p=y.get(e);p&&clearTimeout(p.idleTimer),y.delete(e),i({type:"agent:terminal-exit",payload:{terminalId:e,exitCode:a}})}),console.log(`[terminal] Opened ${e} (${t}x${o})`)}function ue(e,t){let o=y.get(e);o&&(o.pty.write(t),ot(e))}function ge(e,t,o){let r=y.get(e);r&&r.pty.resize(t,o)}function P(e){let t=y.get(e);t&&(clearTimeout(t.idleTimer),t.pty.kill(),y.delete(e),console.log(`[terminal] Closed ${e}`))}import{mkdirSync as me,writeFileSync as fe,readdirSync as rt,unlinkSync as st}from"fs";import{join as w}from"path";import{homedir as nt}from"os";var ye="ptah-",A=w(nt(),".claude","commands");function he(e){return`${ye}${e}.md`}function ve(e){let t=["---"];return t.push(`name: ${e.name}`),e.description&&t.push(`description: ${e.description}`),t.push("---"),t.push(""),t.push(e.content),t.join(`
32
+ `)}function ke(e){try{let t=rt(e);for(let o of t)o.startsWith(ye)&&o.endsWith(".md")&&st(w(e,o))}catch{}}function Se(e){me(A,{recursive:!0}),ke(A);for(let t of e){let o=w(A,he(t.code));fe(o,ve(t),"utf-8")}console.log(`[skills] Wrote ${e.length} org skills to ${A}`)}function xe(e,t){let o=w(c.projectsDir,e,".claude","commands");me(o,{recursive:!0}),ke(o);for(let r of t){let s=w(o,he(r.code));fe(s,ve(r),"utf-8")}console.log(`[skills] Wrote ${t.length} project skills for ${e}`)}function we(e){switch(e.type){case"server:auth-result":e.payload.success||(console.error("Authentication failed:",e.payload.error),process.exit(1));break;case"server:command":console.log(`Received command: ${e.payload.action}`,e.payload);break;case"server:doc-sync":console.log(`Doc sync: ${e.payload.action} ${e.payload.fileName}`);break;case"server:claude-start":{let{commandId:t,sessionId:o,projectId:r,message:s,claudeSessionId:n,projectDir:l,systemPrompt:a}=e.payload;console.log(`Starting Claude session ${o} for project ${r}`),ee({commandId:t,sessionId:o,projectId:r,projectDir:l,message:s,claudeSessionId:n,systemPrompt:a});break}case"server:claude-stop":{console.log(`Stop requested for session ${e.payload.sessionId}`);break}case"server:chat-history-request":{let{sessionId:t,projectId:o}=e.payload;console.log(`Chat history request for session ${t}`);let r=K(o,t);i({type:"agent:chat-history",payload:{sessionId:t,messages:r}});break}case"server:deploy-start":{let{commandId:t,deploymentId:o,projectId:r,projectCode:s,orgCode:n,projectDir:l}=e.payload;console.log(`Starting deployment for project ${s}`),se({commandId:t,deploymentId:o,projectId:r,projectCode:s,orgCode:n,projectDir:l});break}case"server:docker-list":{let{requestId:t}=e.payload;console.log(`Docker list request: ${t}`);let o=ne();i({type:"agent:docker-list",payload:{requestId:t,containers:o}});break}case"server:docker-start":{let{requestId:t,containerId:o}=e.payload;console.log(`Docker start: ${o}`);let r=ie(o);i({type:"agent:docker-action",payload:{requestId:t,...r}});break}case"server:docker-stop":{let{requestId:t,containerId:o}=e.payload;console.log(`Docker stop: ${o}`);let r=ae(o);i({type:"agent:docker-action",payload:{requestId:t,...r}});break}case"server:docker-remove":{let{requestId:t,containerId:o}=e.payload;console.log(`Docker remove: ${o}`);let r=ce(o);i({type:"agent:docker-action",payload:{requestId:t,...r}});break}case"server:terminal-open":{let{terminalId:t,cols:o,rows:r}=e.payload;de(t,o,r);break}case"server:terminal-input":{ue(e.payload.terminalId,e.payload.data);break}case"server:terminal-resize":{let{terminalId:t,cols:o,rows:r}=e.payload;ge(t,o,r);break}case"server:terminal-close":{P(e.payload.terminalId);break}case"server:skills-sync":{try{Se(e.payload.skills),i({type:"agent:skills-sync-result",payload:{success:!0}})}catch(t){let o=t.message;console.error("[skills] Sync failed:",o),i({type:"agent:skills-sync-result",payload:{success:!1,error:o}})}break}case"server:project-skills-sync":{try{xe(e.payload.projectId,e.payload.skills),i({type:"agent:skills-sync-result",payload:{success:!0}})}catch(t){let o=t.message;console.error("[skills] Project sync failed:",o),i({type:"agent:skills-sync-result",payload:{success:!1,error:o}})}break}case"server:agent-info":{let{requestId:t}=e.payload;i({type:"agent:info",payload:{requestId:t,version:"0.0.30",nodeVersion:process.version,uptime:Math.floor(process.uptime()),platform:process.platform,arch:process.arch,memoryUsage:Math.round(process.memoryUsage().rss/1024/1024),projectsDir:c.projectsDir}});break}case"server:agent-upgrade":{let{requestId:t}=e.payload,o="0.0.30";console.log("[agent] Upgrade requested...");try{U("npm cache clean --force 2>/dev/null; npm install -g @z_ptah/agent@latest --prefer-online",{encoding:"utf-8",timeout:12e4,shell:"/bin/bash"});let r=U("ptah version",{encoding:"utf-8"}).trim().replace("ptah-agent v","");i({type:"agent:upgrade-result",payload:{requestId:t,success:!0,previousVersion:o,newVersion:r}})}catch(r){i({type:"agent:upgrade-result",payload:{requestId:t,success:!1,previousVersion:o,error:r.message}})}break}case"server:agent-restart":{let{requestId:t}=e.payload;console.log("[agent] Restart requested..."),i({type:"agent:restart-result",payload:{requestId:t,success:!0}}),setTimeout(()=>{try{U("systemctl restart ptah-agent",{timeout:1e4})}catch{}},500);break}default:console.warn("Unknown message type:",e.type)}}import{spawn as at}from"child_process";async function be(){return new Promise(e=>{let t=at(c.claudePath,["auth","status"],{env:{...process.env},stdio:["pipe","pipe","pipe"]}),o="";t.stdout.on("data",r=>{o+=r.toString()}),t.stderr.on("data",r=>{o+=r.toString()}),t.on("close",()=>{try{let r=JSON.parse(o.trim());e({loggedIn:r.loggedIn===!0,email:r.email,subscriptionType:r.subscriptionType})}catch{e({loggedIn:!1})}}),t.on("error",()=>{e({loggedIn:!1})})})}function _e(e){j({loggedIn:e.loggedIn,email:e.email}),i({type:"agent:claude-status",payload:e})}var g=null,M=null,k=null,b=!1,T=0,Ie={loggedIn:!1};function j(e){Ie=e}var it=6e4;function i(e){g?.readyState===z.OPEN&&g.send(JSON.stringify(e))}function ct(){H(),M=setInterval(()=>{let e={type:"agent:heartbeat",payload:{cpuUsage:0,memoryUsage:Math.round(process.memoryUsage().heapUsed/1024/1024),activeProjects:0,claude:Ie}};i(e)},c.heartbeatIntervalMs)}function H(){M&&(clearInterval(M),M=null)}function lt(){if(k)return;let e=Math.min(c.reconnectIntervalMs*Math.pow(2,T),it);T++,console.log(`Reconnecting in ${e/1e3}s (attempt ${T})...`),k=setTimeout(()=>{k=null,L()},e)}function L(){b||g?.readyState===z.OPEN||(b=!0,console.log(`Connecting to ${c.serverUrl}...`),g=new z(c.serverUrl),g.on("open",()=>{b=!1,console.log("Connected to Ptah Server");let e={type:"agent:auth",payload:{token:c.token}};i(e)}),g.on("message",e=>{try{let t=JSON.parse(e.toString());we(t),t.type==="server:auth-result"&&t.payload?.success&&(console.log(`Authenticated as VPS ${t.payload.vpsId}`),T=0,ct(),be().then(o=>{j({loggedIn:o.loggedIn,email:o.email}),_e(o)}))}catch(t){console.error("Failed to parse message:",t)}}),g.on("close",()=>{b=!1,H(),console.log("Disconnected from Ptah Server"),lt()}),g.on("error",e=>{b=!1,console.error("WebSocket error:",e.message)}))}function F(){k&&(clearTimeout(k),k=null),H(),g&&(g.close(),g=null)}import pt from"fastify";var q=pt({logger:!1});q.get("/health",async()=>({status:"ok",timestamp:new Date().toISOString(),uptime:process.uptime()}));async function $e(){await q.listen({port:c.healthPort,host:"0.0.0.0"}),console.log(`Agent health server on http://0.0.0.0:${c.healthPort}`)}async function J(){await q.close()}async function dt(){console.log("Starting Ptah Agent..."),console.log(` Server: ${c.serverUrl}`),console.log(` Health port: ${c.healthPort}`),console.log(` Projects dir: ${c.projectsDir}`),await $e(),L()}process.on("SIGINT",async()=>{console.log(`
33
33
  Shutting down...`),F(),await J(),process.exit(0)});process.on("SIGTERM",async()=>{console.log(`
34
- Shutting down...`),F(),await J(),process.exit(0)});pt().catch(e=>{console.error("Failed to start agent:",e),process.exit(1)});
34
+ Shutting down...`),F(),await J(),process.exit(0)});dt().catch(e=>{console.error("Failed to start agent:",e),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@z_ptah/agent",
3
- "version": "0.0.29",
3
+ "version": "0.0.30",
4
4
  "type": "module",
5
5
  "description": "Ptah VPS Agent — connects to Ptah Cloud, manages Claude sessions and deployments",
6
6
  "bin": {