@z_ptah/agent 0.0.22 → 0.0.23

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