@z_ptah/agent 0.0.27 → 0.0.29

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 ADDED
@@ -0,0 +1,39 @@
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=`
6
+ server {
7
+ listen 80;
8
+ server_name ${n};
9
+
10
+ location /api {
11
+ proxy_pass http://127.0.0.1:${o};
12
+ proxy_http_version 1.1;
13
+ proxy_set_header Upgrade $http_upgrade;
14
+ proxy_set_header Connection 'upgrade';
15
+ proxy_set_header Host $host;
16
+ proxy_set_header X-Real-IP $remote_addr;
17
+ proxy_cache_bypass $http_upgrade;
18
+ }
19
+
20
+ location / {
21
+ proxy_pass http://127.0.0.1:${r};
22
+ proxy_http_version 1.1;
23
+ proxy_set_header Upgrade $http_upgrade;
24
+ proxy_set_header Connection 'upgrade';
25
+ proxy_set_header Host $host;
26
+ proxy_set_header X-Real-IP $remote_addr;
27
+ proxy_cache_bypass $http_upgrade;
28
+ }
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(`
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(`
36
+ ptah-agent v${M}
37
+ `),console.log(`Usage: ptah <command>
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))}
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import"dotenv/config";import{execSync as fe}from"child_process";function F(e){let t=process.env[e];if(!t)throw new Error(`Missing required environment variable: ${e}`);return t}function ye(){if(process.env.CLAUDE_PATH)return process.env.CLAUDE_PATH;try{return fe("which claude",{encoding:"utf-8"}).trim()}catch{return"/usr/bin/claude"}}var c={serverUrl:F("PTAH_SERVER_URL"),token:F("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:ye()};import R from"ws";import{spawn as Ue}from"child_process";import{mkdirSync as je}from"fs";import{randomUUID as P}from"crypto";import{randomUUID as v}from"crypto";function J(e,t,o){let r=e.trim();if(!r)return[];let n;try{n=JSON.parse(r)}catch{return[]}let s=[],i=new Date().toISOString();if(n.type==="content_block_delta"){let a=n.delta;return a?.type==="text_delta"&&a.text&&s.push({kind:"text_delta",id:v(),sessionId:t,commandId:o,timestamp:i,role:"assistant",content:a.text}),a?.type==="thinking_delta"&&a.thinking&&s.push({kind:"thinking",id:v(),sessionId:t,commandId:o,timestamp:i,role:"assistant",content:a.thinking}),s}if(n.type==="content_block_start"){let a=n.content_block;return a?.type==="tool_use"&&s.push({kind:"tool_use",id:v(),sessionId:t,commandId:o,timestamp:i,role:"assistant",toolName:a.name||"tool",toolId:a.id||void 0,toolInput:{}}),s}if(n.type==="content_block_stop"||n.type==="message_start"||n.type==="message_delta"||n.type==="message_stop")return s;if(n.type==="assistant"&&n.message){let a=n.message;if(Array.isArray(a.content))for(let d of a.content){let f=he(d.type);if(!f)continue;let g={kind:f,id:v(),sessionId:t,commandId:o,timestamp:i,role:"assistant"};switch(f){case"text":g.content=d.text||"";break;case"tool_use":g.toolName=d.name||"tool",g.toolId=d.id||void 0,g.toolInput=d.input??{};break;case"thinking":g.content=d.thinking||"";break}s.push(g)}}return n.type==="tool_result"&&s.push({kind:"tool_result",id:v(),sessionId:t,commandId:o,timestamp:i,role:"assistant",toolId:n.tool_use_id||void 0,content:typeof n.content=="string"?n.content:JSON.stringify(n.content??""),isError:n.is_error||!1}),n.type==="result"&&s.push({kind:"complete",id:v(),sessionId:t,commandId:o,timestamp:i,role:"assistant",content:n.result||"",metadata:{sessionId:n.session_id,cost:n.cost_usd??n.cost,totalCost:n.total_cost_usd,duration:n.duration_ms,numTurns:n.num_turns}}),s}function he(e){switch(e){case"text":return"text";case"tool_use":return"tool_use";case"thinking":return"thinking";default:return null}}import{mkdirSync as ve,appendFileSync as xe,existsSync as Se,readFileSync as we}from"fs";import{join as be,dirname as ke}from"path";function q(e,t){return be(c.projectsDir,e,"chat-history",`${t}.jsonl`)}function w(e,t){let o=q(e,t.sessionId);ve(ke(o),{recursive:!0}),xe(o,JSON.stringify(t)+`
3
- `,"utf-8")}function B(e,t){let o=q(e,t);if(!Se(o))return[];let r=we(o,"utf-8"),n=[];for(let s of r.split(`
4
- `))if(s.trim())try{n.push(JSON.parse(s))}catch{}return n}import{readdirSync as Pe,statSync as Ae,readFileSync as Ne,writeFileSync as De,existsSync as Ee}from"fs";import{join as M}from"path";import{readFileSync as _e,existsSync as Ie}from"fs";import{basename as $e}from"path";var Ce={".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 Me(e){let t=e.substring(e.lastIndexOf(".")).toLowerCase();return Ce[t]||"application/octet-stream"}async function G(e,t){if(!Ie(t))return console.warn(`[uploader] File not found: ${t}`),!1;let o=$e(t),r=Me(o),n=_e(t),s=c.serverUrl.replace(/^wss:/,"https:").replace(/^ws:/,"http:").replace(/\/ws\/agent$/,""),i=new FormData;i.append("projectId",e),i.append("file",new Blob([n],{type:r}),o);try{let a=await fetch(`${s}/agent/documents/upload`,{method:"POST",headers:{Authorization:`Bearer ${c.token}`},body:i});if(!a.ok){let d=await a.text();return console.error(`[uploader] Upload failed (${a.status}): ${d}`),!1}return console.log(`[uploader] Uploaded ${o} for project ${e}`),!0}catch(a){return console.error("[uploader] Upload error:",a),!1}}var Te=new Set([".xlsx",".xls",".csv",".pdf",".docx",".doc"]),V=".ptah-uploaded.json";function Re(e){let t=M(e,V);if(!Ee(t))return{};try{return JSON.parse(Ne(t,"utf-8"))}catch{return{}}}function Oe(e,t){De(M(e,V),JSON.stringify(t,null,2))}async function K(e,t){let o=Re(t),r=!1,n;try{n=Pe(t)}catch{return}for(let s of n){let i=s.substring(s.lastIndexOf(".")).toLowerCase();if(!Te.has(i))continue;let a=M(t,s);try{let d=Ae(a);if(!d.isFile())continue;let f=o[s];if(f&&f>=d.mtimeMs)continue;console.log(`[auto-upload] Uploading ${s}...`),await G(e,a)&&(o[s]=d.mtimeMs,r=!0)}catch(d){console.error(`[auto-upload] Failed to process ${s}:`,d)}}r&&Oe(t,o)}function W(e){let{commandId:t,sessionId:o,projectId:r,projectDir:n,message:s,claudeSessionId:i,systemPrompt:a}=e,f=["-p",s,"--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."];i&&f.push("--resume",i),je(n,{recursive:!0}),console.log(`Starting Claude for session ${o} in ${n}`);let g={kind:"text",id:P(),sessionId:o,commandId:t,timestamp:new Date().toISOString(),role:"user",content:s};w(r,g),l({type:"agent:claude-message",payload:g});let k=Ue(c.claudePath,f,{cwd:n,env:{...process.env},stdio:["pipe","pipe","pipe"]}),S="",ge;function L(p){if(!p.trim())return;try{let y=JSON.parse(p);y.session_id&&(ge=y.session_id)}catch{return}let u=J(p,o,t);for(let y of u)y.kind!=="text_delta"&&w(r,y),l({type:"agent:claude-message",payload:y})}k.stdout.on("data",p=>{S+=p.toString();let u=S.split(`
5
- `);S=u.pop()||"";for(let y of u)L(y)}),k.stderr.on("data",p=>{}),k.on("close",p=>{if(S.trim()&&L(S),console.log(`Claude exited with code ${p} for session ${o}`),A(o),p===0&&K(r,n).catch(u=>console.error("[auto-upload] Error:",u)),p!==0){let u={kind:"error",id:P(),sessionId:o,commandId:t,timestamp:new Date().toISOString(),role:"assistant",content:`Process exited with code ${p}`};w(r,u),l({type:"agent:claude-message",payload:u})}}),k.on("error",p=>{console.error(`Claude spawn error for session ${o}:`,p),A(o);let u={kind:"error",id:P(),sessionId:o,commandId:t,timestamp:new Date().toISOString(),role:"assistant",content:`Error spawning Claude: ${p.message}`};w(r,u),l({type:"agent:claude-message",payload:u})})}var N=new Map;function X(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}),W(e)}function A(e){N.delete(e)}import{spawn as ze}from"child_process";async function Y(e){return new Promise((t,o)=>{console.log(`Building Docker in ${e}...`);let r=ze("docker",["compose","up","-d","--build"],{cwd:e,stdio:"inherit"});r.on("close",n=>{n===0?(console.log("Docker build successful"),t()):o(new Error(`Docker build failed with code ${n}`))}),r.on("error",n=>{o(new Error(`Docker build error: ${n.message}`))})})}import{writeFile as He}from"fs/promises";import{spawn as Le}from"child_process";function _(e,t){return new Promise((o,r)=>{let n=Le(e,t,{stdio:"inherit"});n.on("close",s=>{s===0?o():r(new Error(`${e} failed with code ${s}`))}),n.on("error",r)})}async function Q(e,t,o,r){let n=/^[a-z0-9][a-z0-9-]*$/;if(!n.test(e)||!n.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 s=`${e}.${t}.mybptah.com`,i=`
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=`
6
6
  server {
7
7
  listen 80;
8
- server_name ${s};
8
+ server_name ${n};
9
9
 
10
10
  location /api {
11
11
  proxy_pass http://127.0.0.1:${o};
@@ -27,7 +27,8 @@ server {
27
27
  proxy_cache_bypass $http_upgrade;
28
28
  }
29
29
  }
30
- `,a=`/etc/nginx/sites-available/${s}`,d=`/etc/nginx/sites-enabled/${s}`;await He(a,i);try{await _("ln",["-sf",a,d])}catch{}return await _("nginx",["-t"]),await _("nginx",["-s","reload"]),console.log(`Nginx configured for ${s}`),s}async function Z(e){try{await _("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 I(e,t,o){let r={type:"agent:deploy-status",payload:{commandId:e.commandId,deploymentId:e.deploymentId,status:t,...o}};l(r)}async function ee(e){try{I(e,"building"),await Y(e.projectDir),I(e,"deploying");let r=await Q(e.projectCode,e.orgCode,3001,3002);await Z(r).catch(()=>{});let n=`https://${r}`;I(e,"live",{demoUrl:n}),console.log(`Deploy complete: ${n}`)}catch(t){let o=t instanceof Error?t.message:"Unknown deploy error";console.error("Deploy failed:",o),I(e,"failed",{errorMessage:o})}}import{execSync as D}from"child_process";function te(){try{return D(`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 oe(e){try{return D(`docker stop ${e}`,{encoding:"utf-8",timeout:3e4}),{success:!0}}catch(t){return{success:!1,error:t.message}}}function re(e){try{return D(`docker rm -f ${e}`,{encoding:"utf-8",timeout:3e4}),{success:!0}}catch(t){return{success:!1,error:t.message}}}var E=null,ne=!1;async function Fe(){if(ne)return E;ne=!0;try{let{createRequire:e}=await import("module");E=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 E}var h=new Map;async function se(e,t,o){let r=await Fe();if(!r){l({type:"agent:terminal-exit",payload:{terminalId:e,exitCode:1}});return}if(h.has(e)){console.warn(`[terminal] Terminal ${e} already exists`);return}let n=process.env.SHELL||"/bin/bash",s=r.spawn(n,[],{name:"xterm-256color",cols:t,rows:o,cwd:process.env.HOME||"/",env:process.env});h.set(e,s),s.onData(i=>{l({type:"agent:terminal-output",payload:{terminalId:e,data:i}})}),s.onExit(({exitCode:i})=>{h.delete(e),l({type:"agent:terminal-exit",payload:{terminalId:e,exitCode:i}})}),console.log(`[terminal] Opened ${e} (${t}x${o})`)}function ae(e,t){let o=h.get(e);o&&o.write(t)}function ie(e,t,o){let r=h.get(e);r&&r.resize(t,o)}function ce(e){let t=h.get(e);t&&(t.kill(),h.delete(e),console.log(`[terminal] Closed ${e}`))}function le(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:n,claudeSessionId:s,projectDir:i,systemPrompt:a}=e.payload;console.log(`Starting Claude session ${o} for project ${r}`),X({commandId:t,sessionId:o,projectId:r,projectDir:i,message:n,claudeSessionId:s,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=B(o,t);l({type:"agent:chat-history",payload:{sessionId:t,messages:r}});break}case"server:deploy-start":{let{commandId:t,deploymentId:o,projectId:r,projectCode:n,orgCode:s,projectDir:i}=e.payload;console.log(`Starting deployment for project ${n}`),ee({commandId:t,deploymentId:o,projectId:r,projectCode:n,orgCode:s,projectDir:i});break}case"server:docker-list":{let{requestId:t}=e.payload;console.log(`Docker list request: ${t}`);let o=te();l({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=oe(o);l({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=re(o);l({type:"agent:docker-action",payload:{requestId:t,...r}});break}case"server:terminal-open":{let{terminalId:t,cols:o,rows:r}=e.payload;se(t,o,r);break}case"server:terminal-input":{ae(e.payload.terminalId,e.payload.data);break}case"server:terminal-resize":{let{terminalId:t,cols:o,rows:r}=e.payload;ie(t,o,r);break}case"server:terminal-close":{ce(e.payload.terminalId);break}default:console.warn("Unknown message type:",e.type)}}import{spawn as Je}from"child_process";async function de(){return new Promise(e=>{let t=Je(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 pe(e){T({loggedIn:e.loggedIn,email:e.email}),l({type:"agent:claude-status",payload:e})}var m=null,$=null,x=null,b=!1,C=0,ue={loggedIn:!1};function T(e){ue=e}var qe=6e4;function l(e){m?.readyState===R.OPEN&&m.send(JSON.stringify(e))}function Be(){O(),$=setInterval(()=>{let e={type:"agent:heartbeat",payload:{cpuUsage:0,memoryUsage:Math.round(process.memoryUsage().heapUsed/1024/1024),activeProjects:0,claude:ue}};l(e)},c.heartbeatIntervalMs)}function O(){$&&(clearInterval($),$=null)}function Ge(){if(x)return;let e=Math.min(c.reconnectIntervalMs*Math.pow(2,C),qe);C++,console.log(`Reconnecting in ${e/1e3}s (attempt ${C})...`),x=setTimeout(()=>{x=null,U()},e)}function U(){b||m?.readyState===R.OPEN||(b=!0,console.log(`Connecting to ${c.serverUrl}...`),m=new R(c.serverUrl),m.on("open",()=>{b=!1,console.log("Connected to Ptah Server");let e={type:"agent:auth",payload:{token:c.token}};l(e)}),m.on("message",e=>{try{let t=JSON.parse(e.toString());le(t),t.type==="server:auth-result"&&t.payload?.success&&(console.log(`Authenticated as VPS ${t.payload.vpsId}`),C=0,Be(),de().then(o=>{T({loggedIn:o.loggedIn,email:o.email}),pe(o)}))}catch(t){console.error("Failed to parse message:",t)}}),m.on("close",()=>{b=!1,O(),console.log("Disconnected from Ptah Server"),Ge()}),m.on("error",e=>{b=!1,console.error("WebSocket error:",e.message)}))}function j(){x&&(clearTimeout(x),x=null),O(),m&&(m.close(),m=null)}import Ve from"fastify";var z=Ve({logger:!1});z.get("/health",async()=>({status:"ok",timestamp:new Date().toISOString(),uptime:process.uptime()}));async function me(){await z.listen({port:c.healthPort,host:"0.0.0.0"}),console.log(`Agent health server on http://0.0.0.0:${c.healthPort}`)}async function H(){await z.close()}async function Ke(){console.log("Starting Ptah Agent..."),console.log(` Server: ${c.serverUrl}`),console.log(` Health port: ${c.healthPort}`),console.log(` Projects dir: ${c.projectsDir}`),await me(),U()}process.on("SIGINT",async()=>{console.log(`
32
- Shutting down...`),j(),await H(),process.exit(0)});process.on("SIGTERM",async()=>{console.log(`
33
- Shutting down...`),j(),await H(),process.exit(0)});Ke().catch(e=>{console.error("Failed to start agent:",e),process.exit(1)});
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(`
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)});
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@z_ptah/agent",
3
- "version": "0.0.27",
3
+ "version": "0.0.29",
4
4
  "type": "module",
5
5
  "description": "Ptah VPS Agent — connects to Ptah Cloud, manages Claude sessions and deployments",
6
6
  "bin": {
7
- "ptah-agent": "./dist/index.js"
7
+ "ptah": "./dist/cli.js",
8
+ "ptah-agent": "./dist/cli.js"
8
9
  },
9
10
  "files": [
10
11
  "dist"