@z_ptah/agent 0.0.28 → 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 +39 -0
- package/dist/index.js +10 -9
- package/package.json +3 -2
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 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
|
+
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 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
|
+
Shutting down...`),Y(),await Z(),process.exit(0)});process.on("SIGTERM",async()=>{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
|
+
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(()=>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,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import"dotenv/config";import{execSync as
|
|
3
|
-
`,"utf-8")}function
|
|
4
|
-
`))if(
|
|
5
|
-
`);S=u.pop()||"";for(let h of u)
|
|
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
|
-
server_name ${
|
|
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/${
|
|
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
|
|
32
|
-
|
|
33
|
-
Shutting down...`),
|
|
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
|
+
Shutting down...`),F(),await J(),process.exit(0)});process.on("SIGTERM",async()=>{console.log(`
|
|
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,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@z_ptah/agent",
|
|
3
|
-
"version": "0.0.
|
|
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": {
|
|
7
|
-
"ptah
|
|
7
|
+
"ptah": "./dist/cli.js",
|
|
8
|
+
"ptah-agent": "./dist/cli.js"
|
|
8
9
|
},
|
|
9
10
|
"files": [
|
|
10
11
|
"dist"
|