grix-connector 2.1.4 → 2.2.0

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.
@@ -1 +1 @@
1
- import c from"node:http";import{randomUUID as d}from"node:crypto";import{log as o}from"../../core/log/index.js";function l(t){t.writeHead(401,{"content-type":"application/json"}),t.end(JSON.stringify({error:"unauthorized"}))}function u(t){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not_found"}))}function h(t,e){t.writeHead(400,{"content-type":"application/json"}),t.end(JSON.stringify({error:e}))}function p(t,e={ok:!0}){t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify(e))}async function v(t){const e=[];for await(const r of t)e.push(r);const n=Buffer.concat(e).toString("utf8").trim();return n?JSON.parse(n):{}}function k(t){const e=(t.headers.authorization??"").trim();return e.toLowerCase().startsWith("bearer ")?e.slice(7).trim():""}class w{host="127.0.0.1";port=0;token;callbacks;server=null;address=null;constructor(e){this.token=d(),this.callbacks=e}getToken(){return this.token}getURL(){return this.address?`http://${this.address.address}:${this.address.port}`:""}async start(){this.server||(this.server=c.createServer(async(e,n)=>{try{await this.handleRequest(e,n)}catch(r){h(n,r instanceof Error?r.message:String(r))}}),await new Promise((e,n)=>{this.server.once("error",n),this.server.listen(this.port,this.host,()=>{this.server.off("error",n),e()})}),this.address=this.server.address(),o.info("claude-bridge",`Bridge server listening on ${this.getURL()}`))}async stop(){if(!this.server)return;const e=this.server;this.server=null,this.address=null,e.closeIdleConnections?.(),e.closeAllConnections?.(),await new Promise((n,r)=>{e.close(s=>s?r(s):n())})}async handleRequest(e,n){if(k(e)!==this.token){l(n);return}if(e.method!=="POST"){n.writeHead(405,{"content-type":"application/json"}),n.end(JSON.stringify({error:"method_not_allowed"}));return}const r=new URL(e.url,"http://localhost").pathname,s=await v(e),i=f.get(r);if(!i){u(n);return}const a=await i(this.callbacks,s);p(n,a??{ok:!0})}}const f=new Map([["/v1/worker/register",async(t,e)=>(o.info("claude-bridge",`Worker registered: ${e.worker_id} (pid=${e.pid})`),t.onRegisterWorker(e))],["/v1/worker/status",async(t,e)=>(o.info("claude-bridge",`Worker status: ${e.status}`),t.onStatusUpdate(e))],["/v1/worker/send-text",async(t,e)=>t.onSendText(e)],["/v1/worker/send-stream-chunk",async(t,e)=>t.onSendStreamChunk(e)],["/v1/worker/send-media",async(t,e)=>t.onSendMedia(e)],["/v1/worker/delete-message",async(t,e)=>t.onDeleteMessage(e)],["/v1/worker/ack-event",async(t,e)=>t.onAckEvent(e)],["/v1/worker/event-result",async(t,e)=>t.onSendEventResult(e)],["/v1/worker/event-stop-ack",async(t,e)=>t.onSendEventStopAck(e)],["/v1/worker/event-stop-result",async(t,e)=>t.onSendEventStopResult(e)],["/v1/worker/session-composing",async(t,e)=>t.onSetSessionComposing(e)],["/v1/worker/agent-invoke",async(t,e)=>t.onAgentInvoke(e)],["/v1/worker/local-action-result",async(t,e)=>t.onLocalActionResult(e)]]);export{w as ClaudeBridgeServer};
1
+ import c from"node:http";import{randomUUID as d}from"node:crypto";import{log as o}from"../../core/log/index.js";function l(t){t.writeHead(401,{"content-type":"application/json"}),t.end(JSON.stringify({error:"unauthorized"}))}function u(t){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not_found"}))}function h(t,e){t.writeHead(400,{"content-type":"application/json"}),t.end(JSON.stringify({error:e}))}function p(t,e={ok:!0}){t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify(e))}async function v(t){const e=[];for await(const n of t)e.push(n);const r=Buffer.concat(e).toString("utf8").trim();return r?JSON.parse(r):{}}function k(t){const e=(t.headers.authorization??"").trim();return e.toLowerCase().startsWith("bearer ")?e.slice(7).trim():""}class w{host="127.0.0.1";port=0;token;callbacks;server=null;address=null;constructor(e){this.token=d(),this.callbacks=e}getToken(){return this.token}getURL(){return this.address?`http://${this.address.address}:${this.address.port}`:""}async start(){this.server||(this.server=c.createServer(async(e,r)=>{try{await this.handleRequest(e,r)}catch(n){h(r,n instanceof Error?n.message:String(n))}}),await new Promise((e,r)=>{this.server.once("error",r),this.server.listen(this.port,this.host,()=>{this.server.off("error",r),e()})}),this.address=this.server.address(),o.info("claude-bridge",`Bridge server listening on ${this.getURL()}`))}async stop(){if(!this.server)return;const e=this.server;this.server=null,this.address=null,e.closeIdleConnections?.(),e.closeAllConnections?.(),await new Promise((r,n)=>{e.close(s=>s?n(s):r())})}async handleRequest(e,r){if(k(e)!==this.token){l(r);return}if(e.method!=="POST"){r.writeHead(405,{"content-type":"application/json"}),r.end(JSON.stringify({error:"method_not_allowed"}));return}const n=new URL(e.url,"http://localhost").pathname,s=await v(e),i=f.get(n);if(!i){u(r);return}const a=await i(this.callbacks,s);p(r,a??{ok:!0})}}const f=new Map([["/v1/worker/register",async(t,e)=>(o.info("claude-bridge",`Worker registered: ${e.worker_id} (pid=${e.pid})`),t.onRegisterWorker(e))],["/v1/worker/status",async(t,e)=>(o.info("claude-bridge",`Worker status: ${e.status}`),t.onStatusUpdate(e))],["/v1/worker/send-text",async(t,e)=>t.onSendText(e)],["/v1/worker/send-stream-chunk",async(t,e)=>t.onSendStreamChunk(e)],["/v1/worker/send-media",async(t,e)=>t.onSendMedia(e)],["/v1/worker/delete-message",async(t,e)=>t.onDeleteMessage(e)],["/v1/worker/ack-event",async(t,e)=>t.onAckEvent(e)],["/v1/worker/event-result",async(t,e)=>t.onSendEventResult(e)],["/v1/worker/event-stop-ack",async(t,e)=>t.onSendEventStopAck(e)],["/v1/worker/event-stop-result",async(t,e)=>t.onSendEventStopResult(e)],["/v1/worker/session-composing",async(t,e)=>t.onSetSessionComposing(e)],["/v1/worker/agent-invoke",async(t,e)=>t.onAgentInvoke(e)],["/v1/worker/local-action-result",async(t,e)=>t.onLocalActionResult(e)]]);export{w as ClaudeBridgeServer};
@@ -1 +1 @@
1
- import{randomUUID as x}from"node:crypto";import{CallToolRequestSchema as S,ListToolsRequestSchema as w}from"@modelcontextprotocol/sdk/types.js";import{log as f}from"../../core/log/index.js";import{toolCallToInvoke as k}from"../../core/mcp/tools.js";const E=new Set(["contact_search","session_search","message_history","message_search","group_create","group_detail_read","group_leave_self","group_member_add","group_member_remove","group_member_role_update","group_all_members_muted_update","group_member_speaking_update","group_dissolve","send_msg","delete_msg","agent_api_create","agent_category_list","agent_category_create","agent_category_update","agent_category_assign","agent_api_key_rotate"]),y=3e4,I=[{name:"reply",description:"Send a visible message back to the chat for this grix-claude event.",inputSchema:{type:"object",properties:{text:{type:"string",description:"The visible reply text to send."},chat_id:{type:"string",description:"The target chat/session id from the <channel> tag."},event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},reply_to:{type:"string",description:"Optional message_id to quote instead of the inbound trigger message."},files:{type:"array",items:{type:"string"},description:"Optional absolute local file paths. Each file is uploaded through Agent API OSS presign before sending."},final:{type:"boolean",description:"Whether this is the final reply for the event. Defaults to false \u2014 the event stays open while Claude continues working, and auto-completes after inactivity. Set true only when this is definitively the last message for the event."}},required:["chat_id","event_id"]}},{name:"complete",description:"Finish an event without sending a visible reply so the backend does not time out.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},status:{type:"string",enum:["responded","canceled","failed"]},code:{type:"string"},msg:{type:"string"}},required:["event_id","status"]}},{name:"delete_message",description:"Delete a previously sent message in the same grix-claude chat.",inputSchema:{type:"object",properties:{chat_id:{type:"string"},message_id:{type:"string"}},required:["chat_id","message_id"]}},{name:"status",description:"Show grix-claude runtime status, upstream access state, bridge health, and startup hints.",inputSchema:{type:"object",properties:{}}},{name:"send",description:"Send a message to a chat session proactively, without requiring an inbound event. Use for notifications or scheduled reports.",inputSchema:{type:"object",properties:{chat_id:{type:"string",description:"The target chat/session id."},text:{type:"string",description:"The message text to send."}},required:["chat_id","text"]}},{name:"access_pair",description:"Forward a sender pairing approval code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"access_deny",description:"Forward a sender pairing denial code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"allow_sender",description:"Ask upstream access control to allow a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"remove_sender",description:"Ask upstream access control to remove a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"access_policy",description:"Ask upstream access control to update the sender access policy.",inputSchema:{type:"object",properties:{policy:{type:"string",enum:["allowlist","open","disabled"]}},required:["policy"]}},{name:"grix_query",description:"Search contacts, sessions, message history, or messages by keyword in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},keyword:{type:"string"},id:{type:"string"},sessionId:{type:"string"},limit:{type:"number"},offset:{type:"number"},beforeId:{type:"string"}},required:["action"]}},{name:"grix_group",description:"Manage groups in the Grix/AIBot platform: create, get details, leave, dissolve, manage members and permissions.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string"},memberIds:{type:"array",items:{type:"string"}},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer",description:"Member type (for update_member_role / update_member_speaking)."},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted (for update_member_speaking)."}},required:["action"]}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},content:{type:"string"},msgType:{type:"number"},quotedMessageId:{type:"string"},threadId:{type:"string"}},required:["sessionId","content"]}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},msgId:{type:"string"}},required:["sessionId","msgId"]}},{name:"grix_admin",description:"Agent and category management in the Grix/AIBot platform: create agents, manage categories, rotate API keys.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentId:{type:"string"},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"number"}},required:["action"]}}];function q(r,e){r.setRequestHandler(w,async()=>({tools:I})),r.setRequestHandler(S,async i=>{const{name:n,arguments:t}=i.params,s=t??{};try{switch(n){case"reply":return await A(s,e);case"complete":return await $(s,e);case"delete_message":return await T(s,e);case"status":return j(e);case"send":return await M(s,e);case"access_pair":case"access_deny":case"allow_sender":case"remove_sender":case"access_policy":return await R(n,s,e);case"grix_query":case"grix_group":case"grix_message_send":case"grix_message_unsend":case"grix_admin":return await O(n,s,e);default:return{content:[{type:"text",text:`Unknown tool: ${n}`}],isError:!0}}}catch(a){return f.error("claude-tools",`Tool ${n} error: ${a}`),{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:String(a)}`}],isError:!0}}})}async function A(r,e){const i=e.getActiveEvent();if(!i)return{content:[{type:"text",text:"No active event to reply to"}],isError:!0};const n=String(r.text??""),t=String(r.chat_id??""),s=String(r.event_id??i.eventId),a=r.reply_to,d=r.files,_=r.final===!0;if(!t||!s)return{content:[{type:"text",text:"reply requires chat_id and event_id"}],isError:!0};if(!n.trim()&&(!d||d.length===0))return{content:[{type:"text",text:"reply requires at least one of text or files"}],isError:!0};const{text:g,quotedMessageId:v}=e.resolveQuotedMessageId(a,n),b=[];let m=0;const h=`reply_${s}_${Date.now()}`;try{if(g){const p=e.splitText(g);for(let o=0;o<p.length;o++){if(!e.isEventActive(s))return c("ignored: event no longer active");m++,e.bridge.sendStreamChunk(s,t,p[o],++i.chunkSeq,!1,h)}}if(d&&d.length>0)for(const p of d){if(!e.isEventActive(s))return c("ignored: event no longer active");m++;const o=await e.uploadFile(p,t),l=`${x()}_${m}`;e.bridge.sendMedia(s,t,o.access_url,o.file_name,v,l,o.extra),f.info("claude-tools",`File sent: ${o.file_name}`)}e.bridge.sendStreamChunk(s,t,"",++i.chunkSeq,!0,h)}catch(p){if(g&&b.length===0)try{const o=`fallback_${s}_${Date.now()}`,l=e.splitText(g);for(let u=0;u<l.length;u++)e.bridge.sendStreamChunk("",t,l[u],u+1,!1,o);return e.bridge.sendStreamChunk("",t,"",l.length+1,!0,o),e.markReplySent(s),_&&e.finalizeEvent(s,"responded"),c("sent via fallback")}catch{}if(!e.isEventActive(s))return c("ignored: event no longer active");throw e.bridge.sendEventResult(s,"failed",String(p),"send_msg_failed"),p}return e.markReplySent(s),_?e.finalizeEvent(s,"responded"):(i.responded=!0,e.clearActiveEvent("completed")),c("Reply sent")}async function $(r,e){const i=e.getActiveEvent(),n=String(r.event_id??""),t=r.status??"",s=r.code,a=r.msg;if(!n||!t)return{content:[{type:"text",text:"complete requires event_id and status"}],isError:!0};const d=["responded","canceled","failed"];return d.includes(t)?e.isEventActive(n)?(e.bridge.sendEventResult(n,t,a,s),e.clearActiveEvent(t),c("Event completed")):c("ignored: event no longer active"):{content:[{type:"text",text:`status must be one of: ${d.join(", ")}`}],isError:!0}}async function T(r,e){const i=String(r.chat_id??""),n=String(r.message_id??"");if(!i||!n)return{content:[{type:"text",text:"chat_id and message_id are required"}],isError:!0};try{return await e.bridge.agentInvoke("grix_message_unsend",{sessionId:i,msgId:n},y),c(`deleted (${n})`)}catch(t){return{content:[{type:"text",text:`Delete failed: ${t}`}],isError:!0}}}function j(r){const e=r.getStatusInfo();return{content:[{type:"text",text:JSON.stringify({alive:e.alive,active_event:e.activeEvent,pending_approvals:e.pendingPermissions,pending_questions:e.pendingElicitations})}]}}async function M(r,e){const i=String(r.chat_id??""),n=String(r.text??"");if(!i||!n)return{content:[{type:"text",text:"chat_id and text are required"}],isError:!0};try{const t=e.splitText(n),s=`send_${i}_${Date.now()}`;for(let a=0;a<t.length;a++)e.bridge.sendStreamChunk("",i,t[a],a+1,!1,s);return e.bridge.sendStreamChunk("",i,"",t.length+1,!0,s),c("sent")}catch(t){return{content:[{type:"text",text:`Send failed: ${t}`}],isError:!0}}}const C={access_pair:{verb:"pair_approve",payloadKey:"code"},access_deny:{verb:"pair_deny",payloadKey:"code"},allow_sender:{verb:"sender_allow",payloadKey:"sender_id"},remove_sender:{verb:"sender_remove",payloadKey:"sender_id"},access_policy:{verb:"policy_set",payloadKey:"policy"}};async function R(r,e,i){try{const n=C[r];if(!n)throw new Error(`Unknown access control tool: ${r}`);const t={};e.code!=null&&(t.code=e.code),e.sender_id!=null&&(t.sender_id=e.sender_id),e.policy!=null&&(t.policy=e.policy);const s=await i.bridge.agentInvoke("claude_access_control",{verb:n.verb,payload:t},y);return{content:[{type:"text",text:typeof s=="string"?s:JSON.stringify(s)}]}}catch(n){return{content:[{type:"text",text:`${r} failed: ${n}`}],isError:!0}}}async function O(r,e,i){try{const n=k(r,e);if(!E.has(n.action))throw new Error(`Action not allowed: ${n.action}`);const t=await i.bridge.agentInvoke(n.action,n.params,y);if(t&&Number(t.code??0)!==0)throw new Error(String(t.msg??"invoke failed"));return{content:[{type:"text",text:t?.data!=null?typeof t.data=="string"?t.data:JSON.stringify(t.data):JSON.stringify(t)}]}}catch(n){return{content:[{type:"text",text:`${r} failed: ${n}`}],isError:!0}}}function c(r){return{content:[{type:"text",text:r}]}}export{q as registerClaudeTools};
1
+ import{randomUUID as x}from"node:crypto";import{CallToolRequestSchema as S,ListToolsRequestSchema as w}from"@modelcontextprotocol/sdk/types.js";import{log as f}from"../../core/log/index.js";import{toolCallToInvoke as k}from"../../core/mcp/tools.js";const E=new Set(["contact_search","session_search","message_history","message_search","group_create","group_detail_read","group_leave_self","group_member_add","group_member_remove","group_member_role_update","group_all_members_muted_update","group_member_speaking_update","group_dissolve","send_msg","delete_msg","agent_api_create","agent_category_list","agent_category_create","agent_category_update","agent_category_assign","agent_api_key_rotate"]),y=3e4,I=[{name:"reply",description:"Send a visible message back to the chat for this grix-claude event.",inputSchema:{type:"object",properties:{text:{type:"string",description:"The visible reply text to send."},chat_id:{type:"string",description:"The target chat/session id from the <channel> tag."},event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},reply_to:{type:"string",description:"Optional message_id to quote instead of the inbound trigger message."},files:{type:"array",items:{type:"string"},description:"Optional absolute local file paths. Each file is uploaded through Agent API OSS presign before sending."},final:{type:"boolean",description:"Whether this is the final reply for the event. Defaults to false \u2014 the event stays open while Claude continues working, and auto-completes after inactivity. Set true only when this is definitively the last message for the event."}},required:["chat_id","event_id"]}},{name:"complete",description:"Finish an event without sending a visible reply so the backend does not time out.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},status:{type:"string",enum:["responded","canceled","failed"]},code:{type:"string"},msg:{type:"string"}},required:["event_id","status"]}},{name:"delete_message",description:"Delete a previously sent message in the same grix-claude chat.",inputSchema:{type:"object",properties:{chat_id:{type:"string"},message_id:{type:"string"}},required:["chat_id","message_id"]}},{name:"status",description:"Show grix-claude runtime status, upstream access state, bridge health, and startup hints.",inputSchema:{type:"object",properties:{}}},{name:"send",description:"Send a message to a chat session proactively, without requiring an inbound event. Use for notifications or scheduled reports.",inputSchema:{type:"object",properties:{chat_id:{type:"string",description:"The target chat/session id."},text:{type:"string",description:"The message text to send."}},required:["chat_id","text"]}},{name:"access_pair",description:"Forward a sender pairing approval code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"access_deny",description:"Forward a sender pairing denial code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"allow_sender",description:"Ask upstream access control to allow a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"remove_sender",description:"Ask upstream access control to remove a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"access_policy",description:"Ask upstream access control to update the sender access policy.",inputSchema:{type:"object",properties:{policy:{type:"string",enum:["allowlist","open","disabled"]}},required:["policy"]}},{name:"grix_query",description:"Search contacts, sessions, message history, or messages by keyword in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},keyword:{type:"string"},id:{type:"string"},sessionId:{type:"string"},limit:{type:"number"},offset:{type:"number"},beforeId:{type:"string"}},required:["action"]}},{name:"grix_group",description:"Manage groups in the Grix/AIBot platform: create, get details, leave, dissolve, manage members and permissions.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string"},memberIds:{type:"array",items:{type:"string"}},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer",description:"Member type (for update_member_role / update_member_speaking)."},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted (for update_member_speaking)."}},required:["action"]}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},content:{type:"string"},msgType:{type:"number"},quotedMessageId:{type:"string"},threadId:{type:"string"}},required:["sessionId","content"]}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},msgId:{type:"string"}},required:["sessionId","msgId"]}},{name:"grix_admin",description:"Agent and category management in the Grix/AIBot platform: create agents, manage categories, rotate API keys.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentId:{type:"string"},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"number"}},required:["action"]}}];function q(n,e){n.setRequestHandler(w,async()=>({tools:I})),n.setRequestHandler(S,async i=>{const{name:r,arguments:t}=i.params,s=t??{};try{switch(r){case"reply":return await A(s,e);case"complete":return await $(s,e);case"delete_message":return await T(s,e);case"status":return j(e);case"send":return await M(s,e);case"access_pair":case"access_deny":case"allow_sender":case"remove_sender":case"access_policy":return await R(r,s,e);case"grix_query":case"grix_group":case"grix_message_send":case"grix_message_unsend":case"grix_admin":return await O(r,s,e);default:return{content:[{type:"text",text:`Unknown tool: ${r}`}],isError:!0}}}catch(a){return f.error("claude-tools",`Tool ${r} error: ${a}`),{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:String(a)}`}],isError:!0}}})}async function A(n,e){const i=e.getActiveEvent();if(!i)return{content:[{type:"text",text:"No active event to reply to"}],isError:!0};const r=String(n.text??""),t=String(n.chat_id??""),s=String(n.event_id??i.eventId),a=n.reply_to,d=n.files,_=n.final===!0;if(!t||!s)return{content:[{type:"text",text:"reply requires chat_id and event_id"}],isError:!0};if(!r.trim()&&(!d||d.length===0))return{content:[{type:"text",text:"reply requires at least one of text or files"}],isError:!0};const{text:g,quotedMessageId:v}=e.resolveQuotedMessageId(a,r),b=[];let m=0;const h=`reply_${s}_${Date.now()}`;try{if(g){const p=e.splitText(g);for(let o=0;o<p.length;o++){if(!e.isEventActive(s))return c("ignored: event no longer active");m++,e.bridge.sendStreamChunk(s,t,p[o],++i.chunkSeq,!1,h)}}if(d&&d.length>0)for(const p of d){if(!e.isEventActive(s))return c("ignored: event no longer active");m++;const o=await e.uploadFile(p,t),l=`${x()}_${m}`;e.bridge.sendMedia(s,t,o.access_url,o.file_name,v,l,o.extra),f.info("claude-tools",`File sent: ${o.file_name}`)}e.bridge.sendStreamChunk(s,t,"",++i.chunkSeq,!0,h)}catch(p){if(g&&b.length===0)try{const o=`fallback_${s}_${Date.now()}`,l=e.splitText(g);for(let u=0;u<l.length;u++)e.bridge.sendStreamChunk("",t,l[u],u+1,!1,o);return e.bridge.sendStreamChunk("",t,"",l.length+1,!0,o),e.markReplySent(s),_&&e.finalizeEvent(s,"responded"),c("sent via fallback")}catch{}if(!e.isEventActive(s))return c("ignored: event no longer active");throw e.bridge.sendEventResult(s,"failed",String(p),"send_msg_failed"),p}return e.markReplySent(s),_?e.finalizeEvent(s,"responded"):(i.responded=!0,e.clearActiveEvent("completed")),c("Reply sent")}async function $(n,e){const i=e.getActiveEvent(),r=String(n.event_id??""),t=n.status??"",s=n.code,a=n.msg;if(!r||!t)return{content:[{type:"text",text:"complete requires event_id and status"}],isError:!0};const d=["responded","canceled","failed"];return d.includes(t)?e.isEventActive(r)?(e.bridge.sendEventResult(r,t,a,s),e.clearActiveEvent(t),c("Event completed")):c("ignored: event no longer active"):{content:[{type:"text",text:`status must be one of: ${d.join(", ")}`}],isError:!0}}async function T(n,e){const i=String(n.chat_id??""),r=String(n.message_id??"");if(!i||!r)return{content:[{type:"text",text:"chat_id and message_id are required"}],isError:!0};try{return await e.bridge.agentInvoke("grix_message_unsend",{sessionId:i,msgId:r},y),c(`deleted (${r})`)}catch(t){return{content:[{type:"text",text:`Delete failed: ${t}`}],isError:!0}}}function j(n){const e=n.getStatusInfo();return{content:[{type:"text",text:JSON.stringify({alive:e.alive,active_event:e.activeEvent,pending_approvals:e.pendingPermissions,pending_questions:e.pendingElicitations})}]}}async function M(n,e){const i=String(n.chat_id??""),r=String(n.text??"");if(!i||!r)return{content:[{type:"text",text:"chat_id and text are required"}],isError:!0};try{const t=e.splitText(r),s=`send_${i}_${Date.now()}`;for(let a=0;a<t.length;a++)e.bridge.sendStreamChunk("",i,t[a],a+1,!1,s);return e.bridge.sendStreamChunk("",i,"",t.length+1,!0,s),c("sent")}catch(t){return{content:[{type:"text",text:`Send failed: ${t}`}],isError:!0}}}const C={access_pair:{verb:"pair_approve",payloadKey:"code"},access_deny:{verb:"pair_deny",payloadKey:"code"},allow_sender:{verb:"sender_allow",payloadKey:"sender_id"},remove_sender:{verb:"sender_remove",payloadKey:"sender_id"},access_policy:{verb:"policy_set",payloadKey:"policy"}};async function R(n,e,i){try{const r=C[n];if(!r)throw new Error(`Unknown access control tool: ${n}`);const t={};e.code!=null&&(t.code=e.code),e.sender_id!=null&&(t.sender_id=e.sender_id),e.policy!=null&&(t.policy=e.policy);const s=await i.bridge.agentInvoke("claude_access_control",{verb:r.verb,payload:t},y);return{content:[{type:"text",text:typeof s=="string"?s:JSON.stringify(s)}]}}catch(r){return{content:[{type:"text",text:`${n} failed: ${r}`}],isError:!0}}}async function O(n,e,i){try{const r=k(n,e);if(!E.has(r.action))throw new Error(`Action not allowed: ${r.action}`);const t=await i.bridge.agentInvoke(r.action,r.params,y);if(t&&Number(t.code??0)!==0)throw new Error(String(t.msg??"invoke failed"));return{content:[{type:"text",text:t?.data!=null?typeof t.data=="string"?t.data:JSON.stringify(t.data):JSON.stringify(t)}]}}catch(r){return{content:[{type:"text",text:`${n} failed: ${r}`}],isError:!0}}}function c(n){return{content:[{type:"text",text:n}]}}export{q as registerClaudeTools};
@@ -1 +1 @@
1
- import{log as l}from"../../core/log/index.js";class c{controlURL="";token="";isConfigured(){return!!(this.controlURL&&this.token)}configure(r,e){this.controlURL=r.replace(/\/+$/,""),this.token=e.trim(),l.info("claude-worker-client",`Configured with control URL: ${this.controlURL}`)}async post(r,e,s){if(!this.isConfigured())throw new Error("worker control not configured");const i=new AbortController,o=setTimeout(()=>i.abort(),s);try{const t=await fetch(`${this.controlURL}${r}`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${this.token}`},body:JSON.stringify(e),signal:i.signal}),n=await t.text(),a=n.trim()?JSON.parse(n):{};if(!t.ok)throw new Error(a.error||`worker control failed ${t.status}`);return a}finally{clearTimeout(o)}}isRetryableError(r){const e=r instanceof Error?r.message:String(r);return/fetch failed|network|ECONNRESET|ETIMEDOUT|EAI_AGAIN|socket hang up|aborted/i.test(e)}async postWithRetry(r,e,s,i=1){let o;for(let t=0;t<=i;t++)try{return t>0&&l.info("claude-worker-client",`Retrying ${r} attempt=${t+1}`),await this.post(r,e,s)}catch(n){if(o=n,t>=i||!this.isRetryableError(n))break;await new Promise(a=>setTimeout(a,150))}throw o instanceof Error?o:new Error(String(o))}async deliverEvent(r){return this.postWithRetry("/v1/worker/deliver-event",{payload:r},1e4,1)}async deliverStop(r){return this.postWithRetry("/v1/worker/deliver-stop",{payload:r},1e4,1)}async deliverLocalAction(r){return this.postWithRetry("/v1/worker/deliver-local-action",{payload:r},1e4,1)}async ping(){try{return await this.postWithRetry("/v1/worker/ping",{},5e3,1),!0}catch{return!1}}}export{c as ClaudeWorkerClient};
1
+ import{log as l}from"../../core/log/index.js";class c{controlURL="";token="";isConfigured(){return!!(this.controlURL&&this.token)}configure(t,e){this.controlURL=t.replace(/\/+$/,""),this.token=e.trim(),l.info("claude-worker-client",`Configured with control URL: ${this.controlURL}`)}async post(t,e,s){if(!this.isConfigured())throw new Error("worker control not configured");const i=new AbortController,o=setTimeout(()=>i.abort(),s);try{const r=await fetch(`${this.controlURL}${t}`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${this.token}`},body:JSON.stringify(e),signal:i.signal}),n=await r.text(),a=n.trim()?JSON.parse(n):{};if(!r.ok)throw new Error(a.error||`worker control failed ${r.status}`);return a}finally{clearTimeout(o)}}isRetryableError(t){const e=t instanceof Error?t.message:String(t);return/fetch failed|network|ECONNRESET|ETIMEDOUT|EAI_AGAIN|socket hang up|aborted/i.test(e)}async postWithRetry(t,e,s,i=1){let o;for(let r=0;r<=i;r++)try{return r>0&&l.info("claude-worker-client",`Retrying ${t} attempt=${r+1}`),await this.post(t,e,s)}catch(n){if(o=n,r>=i||!this.isRetryableError(n))break;await new Promise(a=>setTimeout(a,150))}throw o instanceof Error?o:new Error(String(o))}async deliverEvent(t){return this.postWithRetry("/v1/worker/deliver-event",{payload:t},1e4,1)}async deliverStop(t){return this.postWithRetry("/v1/worker/deliver-stop",{payload:t},1e4,1)}async deliverLocalAction(t){return this.postWithRetry("/v1/worker/deliver-local-action",{payload:t},1e4,1)}async ping(){try{return await this.postWithRetry("/v1/worker/ping",{},5e3,1),!0}catch{return!1}}}export{c as ClaudeWorkerClient};
@@ -1,2 +1,2 @@
1
- import{spawn as x,execSync as y}from"node:child_process";import{randomUUID as v}from"node:crypto";import{mkdir as S}from"node:fs/promises";import{readFileSync as C}from"node:fs";import{join as d}from"node:path";import{homedir as T,tmpdir as I}from"node:os";import{log as o}from"../../core/log/index.js";import{MCP_HTTP_CHANNEL_NAME as m}from"./protocol-contract.js";function P(t){let e=null,r=0,n=!1,i=!1;const a=v(),s=t.gatewayUrl??"http://127.0.0.1:19580/mcp";return{async start(){await F(t.command,s,t.env);const c=E(t.grix),u=[...t.args??[],"--name",`grix-mcp-${t.name}`,"--session-id",a];t.fullAuto&&u.push("--dangerously-skip-permissions"),u.push("--dangerously-load-development-channels",`server:${m}`,"--append-system-prompt",c);const f=d(I(),`grix-mcp-claude-${t.name}`);await S(f,{recursive:!0});const{expectPath:$,pidPath:g}=await M(f,t.command,u),_={...process.env,...t.env??{}};e=x("/usr/bin/expect",[$],{cwd:t.cwd,env:_,stdio:["ignore","pipe","pipe"],detached:!0}),o.info("mcp-http-launcher",`\u542F\u52A8 Claude: name=${t.name} cwd=${t.cwd} pid=${e.pid}`),r=await k(g),n=!0,o.info("mcp-http-launcher",`Claude \u5B50\u8FDB\u7A0B PID: ${r}`),e.on("exit",(l,p)=>{o.info("mcp-http-launcher",`Claude \u9000\u51FA: code=${l} signal=${p}`),n=!1,e=null,r=0,i||(o.info("mcp-http-launcher","3 \u79D2\u540E\u81EA\u52A8\u91CD\u542F..."),setTimeout(()=>{i||this.start().catch(w=>{o.error("mcp-http-launcher",`\u91CD\u542F\u5931\u8D25: ${w}`)})},3e3))}),e.stdout?.on("data",l=>{const p=l.toString().trim();p&&o.info("mcp-http-launcher",`[stdout] ${p.slice(0,300)}`)}),e.stderr?.on("data",l=>{const p=l.toString().trim();p&&o.info("mcp-http-launcher",`[stderr] ${p.slice(0,300)}`)})},async stop(){if(i=!0,n=!1,r>0)try{process.kill(r,"SIGTERM")}catch{}if(e?.pid){try{process.kill(-e.pid,"SIGTERM")}catch{}await new Promise(c=>{const u=setTimeout(()=>{if(r>0)try{process.kill(r,"SIGKILL")}catch{}if(e?.pid)try{process.kill(-e.pid,"SIGKILL")}catch{}c()},5e3);e?.once("exit",()=>{clearTimeout(u),c()})})}e=null,r=0},getStatus(){return{name:t.name,alive:n,pid:r}}}}function E(t){return["You are connected to a chat via the grix MCP server.",`On startup, immediately call grix_authorize with: agentId="${t.agentId}", apiKey="${t.apiKey}", wsUrl="${t.wsUrl}", clientType="${t.clientType}".`,"When you receive a <channel> message, you MUST respond by calling the grix_reply tool (or the grix_complete tool if no response is needed).","Never write your reply as plain text \u2014 it will NOT reach the user. Only the grix_reply tool delivers your response to the chat.","The <channel> message contains event_id and session_id \u2014 pass them to grix_reply."].join(" ")}async function F(t,e,r){const n=d(T(),".claude.json");let i=null;try{const s=C(n,"utf8");i=JSON.parse(s)?.mcpServers?.[m]??null}catch{}if(i&&String(i.type??"").trim()==="http"&&String(i.url??"").trim()===e)return;o.info("mcp-http-launcher",`\u6CE8\u518C MCP Server: ${m} -> ${e}`);const a={...process.env,...r??{}};try{y(`${t} mcp remove -s user ${m}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}catch{}y(`${t} mcp add --scope user --transport http ${m} ${e}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}async function M(t,e,r){const{writeFile:n}=await import("node:fs/promises"),i=d(t,"claude.pid"),a=d(t,"claude.expect"),s=["log_user 1","set timeout -1","set startup_prompt_armed 1",`set claude_command [list {${h(e)}}${r.map(c=>` {${h(c)}}`).join("")}]`,"spawn -noecho {*}$claude_command",`set pid_file [open {${h(i)}} w]`,"puts $pid_file [exp_pid -i $spawn_id]","close $pid_file","expect {"," -re {(?i)(Quick.*safety.*check|trust.*folder)} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)I am using this for local development} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)(Enter.*confirm|Press.*Enter|Hit.*Enter)} {",' if {$startup_prompt_armed} { send -- "\\r"; after 300 }; exp_continue'," }"," -re {Listening for channel} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," -re {bypass permissions} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," eof {}","}","expect eof",""];return await n(i,"","utf8"),await n(a,s.join(`
2
- `),"utf8"),{expectPath:a,pidPath:i}}function h(t){return t.replace(/[\\{}$\[\]"]/g,"\\$&")}async function k(t,e=1e4){const{readFile:r}=await import("node:fs/promises"),n=Math.ceil(e/100);for(let i=0;i<n;i++){try{const a=await r(t,"utf8"),s=parseInt(String(a).trim(),10);if(Number.isFinite(s)&&s>0)return s}catch{}await new Promise(a=>setTimeout(a,100))}return 0}export{P as createMcpHttpLauncher};
1
+ import{spawn as x,execSync as y}from"node:child_process";import{randomUUID as v}from"node:crypto";import{mkdir as S}from"node:fs/promises";import{readFileSync as C}from"node:fs";import{join as d}from"node:path";import{homedir as T,tmpdir as I}from"node:os";import{log as o}from"../../core/log/index.js";import{MCP_HTTP_CHANNEL_NAME as l}from"./protocol-contract.js";function P(e){let t=null,r=0,n=!1,i=!1;const a=v(),s=e.gatewayUrl??"http://127.0.0.1:19580/mcp";return{async start(){await F(e.command,s,e.env);const c=E(e.grix),u=[...e.args??[],"--name",`grix-mcp-${e.name}`,"--session-id",a];e.fullAuto&&u.push("--dangerously-skip-permissions"),u.push("--dangerously-load-development-channels",`server:${l}`,"--append-system-prompt",c);const f=d(I(),`grix-mcp-claude-${e.name}`);await S(f,{recursive:!0});const{expectPath:$,pidPath:g}=await M(f,e.command,u),_={...process.env,...e.env??{}};t=x("/usr/bin/expect",[$],{cwd:e.cwd,env:_,stdio:["ignore","pipe","pipe"],detached:!0}),o.info("mcp-http-launcher",`\u542F\u52A8 Claude: name=${e.name} cwd=${e.cwd} pid=${t.pid}`),r=await k(g),n=!0,o.info("mcp-http-launcher",`Claude \u5B50\u8FDB\u7A0B PID: ${r}`),t.on("exit",(m,p)=>{o.info("mcp-http-launcher",`Claude \u9000\u51FA: code=${m} signal=${p}`),n=!1,t=null,r=0,i||(o.info("mcp-http-launcher","3 \u79D2\u540E\u81EA\u52A8\u91CD\u542F..."),setTimeout(()=>{i||this.start().catch(w=>{o.error("mcp-http-launcher",`\u91CD\u542F\u5931\u8D25: ${w}`)})},3e3))}),t.stdout?.on("data",m=>{const p=m.toString().trim();p&&o.info("mcp-http-launcher",`[stdout] ${p.slice(0,300)}`)}),t.stderr?.on("data",m=>{const p=m.toString().trim();p&&o.info("mcp-http-launcher",`[stderr] ${p.slice(0,300)}`)})},async stop(){if(i=!0,n=!1,r>0)try{process.kill(r,"SIGTERM")}catch{}if(t?.pid){try{process.kill(-t.pid,"SIGTERM")}catch{}await new Promise(c=>{const u=setTimeout(()=>{if(r>0)try{process.kill(r,"SIGKILL")}catch{}if(t?.pid)try{process.kill(-t.pid,"SIGKILL")}catch{}c()},5e3);t?.once("exit",()=>{clearTimeout(u),c()})})}t=null,r=0},getStatus(){return{name:e.name,alive:n,pid:r}}}}function E(e){return["You are connected to a chat via the grix MCP server.",`On startup, immediately call grix_authorize with: agentId="${e.agentId}", apiKey="${e.apiKey}", wsUrl="${e.wsUrl}", clientType="${e.clientType}".`,"When you receive a <channel> message, you MUST respond by calling the grix_reply tool (or the grix_complete tool if no response is needed).","Never write your reply as plain text \u2014 it will NOT reach the user. Only the grix_reply tool delivers your response to the chat.","The <channel> message contains event_id and session_id \u2014 pass them to grix_reply."].join(" ")}async function F(e,t,r){const n=d(T(),".claude.json");let i=null;try{const s=C(n,"utf8");i=JSON.parse(s)?.mcpServers?.[l]??null}catch{}if(i&&String(i.type??"").trim()==="http"&&String(i.url??"").trim()===t)return;o.info("mcp-http-launcher",`\u6CE8\u518C MCP Server: ${l} -> ${t}`);const a={...process.env,...r??{}};try{y(`${e} mcp remove -s user ${l}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}catch{}y(`${e} mcp add --scope user --transport http ${l} ${t}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}async function M(e,t,r){const{writeFile:n}=await import("node:fs/promises"),i=d(e,"claude.pid"),a=d(e,"claude.expect"),s=["log_user 1","set timeout -1","set startup_prompt_armed 1",`set claude_command [list {${h(t)}}${r.map(c=>` {${h(c)}}`).join("")}]`,"spawn -noecho {*}$claude_command",`set pid_file [open {${h(i)}} w]`,"puts $pid_file [exp_pid -i $spawn_id]","close $pid_file","expect {"," -re {(?i)(Quick.*safety.*check|trust.*folder)} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)I am using this for local development} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)(Enter.*confirm|Press.*Enter|Hit.*Enter)} {",' if {$startup_prompt_armed} { send -- "\\r"; after 300 }; exp_continue'," }"," -re {Listening for channel} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," -re {bypass permissions} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," eof {}","}","expect eof",""];return await n(i,"","utf8"),await n(a,s.join(`
2
+ `),"utf8"),{expectPath:a,pidPath:i}}function h(e){return e.replace(/[\\{}$\[\]"]/g,"\\$&")}async function k(e,t=1e4){const{readFile:r}=await import("node:fs/promises"),n=Math.ceil(t/100);for(let i=0;i<n;i++){try{const a=await r(e,"utf8"),s=parseInt(String(a).trim(),10);if(Number.isFinite(s)&&s>0)return s}catch{}await new Promise(a=>setTimeout(a,100))}return 0}export{P as createMcpHttpLauncher};
@@ -1 +1 @@
1
- class m{defaultTimeoutMs;onTimeout;timers=new Map;constructor(e){this.defaultTimeoutMs=e.defaultTimeoutMs??9e4,this.onTimeout=e.onTimeout}arm(e,t){this.cancel(e);const s=t?.timeoutMs??this.defaultTimeoutMs,i=Date.now()+s,o=setTimeout(()=>{this.timers.delete(e),this.onTimeout(e).catch(()=>{})},s);return this.timers.set(e,o),i}cancel(e){const t=this.timers.get(e);t&&(clearTimeout(t),this.timers.delete(e))}has(e){return this.timers.has(e)}close(){for(const e of this.timers.values())clearTimeout(e);this.timers.clear()}}export{m as ResultTimeoutManager};
1
+ class m{defaultTimeoutMs;onTimeout;timers=new Map;constructor(t){this.defaultTimeoutMs=t.defaultTimeoutMs??9e4,this.onTimeout=t.onTimeout}arm(t,e){this.cancel(t);const s=e?.timeoutMs??this.defaultTimeoutMs,i=Date.now()+s,o=setTimeout(()=>{this.timers.delete(t),this.onTimeout(t).catch(()=>{})},s);return this.timers.set(t,o),i}cancel(t){const e=this.timers.get(t);e&&(clearTimeout(e),this.timers.delete(t))}has(t){return this.timers.has(t)}close(){for(const t of this.timers.values())clearTimeout(t);this.timers.clear()}}export{m as ResultTimeoutManager};
@@ -1 +1 @@
1
- import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const i of c){if(!p&&i.name.startsWith("."))continue;const t=l(a,i.name),e={id:t,name:i.name,is_directory:i.isDirectory()};try{if(i.isDirectory()){const o=await m(t);e.modified_at=o.mtime.toISOString()}else{const o=await m(t);e.size=o.size,e.modified_at=o.mtime.toISOString(),e.mime_type=n(i.name)}}catch{}s.push(e)}return s.sort((i,t)=>i.is_directory!==t.is_directory?i.is_directory?-1:1:i.name.localeCompare(t.name)),s}export{f as listFiles,n as resolveMimeType};
1
+ import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const t of c){if(!p&&t.name.startsWith("."))continue;const i=l(a,t.name),e={id:i,name:t.name,is_directory:t.isDirectory()};try{if(t.isDirectory()){const o=await m(i);e.modified_at=o.mtime.toISOString()}else{const o=await m(i);e.size=o.size,e.modified_at=o.mtime.toISOString(),e.mime_type=n(t.name)}}catch{}s.push(e)}return s.sort((t,i)=>t.is_directory!==i.is_directory?t.is_directory?-1:1:t.name.localeCompare(i.name)),s}export{f as listFiles,n as resolveMimeType};
package/dist/grix.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import g from"node:path";import{writeFileSync as x}from"node:fs";import{Manager as $}from"./manager.js";import{ensureGrixDirs as L,initLogger as N,log as n,installProcessLogRotation as b,setConsoleOutput as C}from"./core/log/index.js";import{HealthServer as U}from"./core/runtime/index.js";import{writePidFile as H,removePidFile as h}from"./core/runtime/index.js";import{resolveRuntimePaths as v}from"./core/config/index.js";import{ServiceManager as j}from"./service/service-manager.js";import{killProcessesByCommandLine as _,isWindowsElevated as G}from"./service/process-control.js";import{acquireDaemonLock as W,releaseDaemonLock as w}from"./runtime/daemon-lock.js";import{writeDaemonStatus as k,removeDaemonStatus as M}from"./runtime/service-state.js";import{AdminServer as B,generateToken as V,writeTokenFile as X}from"./core/admin/index.js";import{initSentry as q,closeSentry as P,reportFatal as E}from"./core/observability/sentry.js";const c=process.argv.slice(2),A=[],s={};for(let t=0;t<c.length;t++)c[t].startsWith("--")&&c[t+1]&&!c[t+1].startsWith("--")?(s[c[t].slice(2)]=c[t+1],t++):c[t].startsWith("--")?s[c[t].slice(2)]="true":A.push(c[t]);s.help&&(console.log(`grix-connector \u2014 Unified AI Agent Bridge
2
+ import g from"node:path";import{writeFileSync as x}from"node:fs";import{Manager as b}from"./manager.js";import{ensureGrixDirs as L,initLogger as $,log as n,installProcessLogRotation as N,setConsoleOutput as C}from"./core/log/index.js";import{HealthServer as U}from"./core/runtime/index.js";import{writePidFile as _,removePidFile as h}from"./core/runtime/index.js";import{resolveRuntimePaths as k}from"./core/config/index.js";import{ServiceManager as H}from"./service/service-manager.js";import{killProcessesByCommandLine as j,isWindowsElevated as G}from"./service/process-control.js";import{acquireDaemonLock as W,releaseDaemonLock as w}from"./runtime/daemon-lock.js";import{writeDaemonStatus as v,removeDaemonStatus as M}from"./runtime/service-state.js";import{AdminServer as B,generateToken as X,writeTokenFile as V}from"./core/admin/index.js";import{initSentry as q,closeSentry as A,reportFatal as E}from"./core/observability/sentry.js";const c=process.argv.slice(2),P=[],s={};for(let t=0;t<c.length;t++)c[t].startsWith("--")&&c[t+1]&&!c[t+1].startsWith("--")?(s[c[t].slice(2)]=c[t+1],t++):c[t].startsWith("--")?s[c[t].slice(2)]="true":P.push(c[t]);s.help&&(console.log(`grix-connector \u2014 Unified AI Agent Bridge
3
3
 
4
4
  Usage: grix-connector <command> [options]
5
5
 
@@ -18,14 +18,18 @@ Options:
18
18
 
19
19
  Platform services:
20
20
  macOS: launchd (LaunchAgent)
21
- Linux: systemd --user
21
+ Linux: systemd --user (auto-falls back to a detached daemon on systems
22
+ without a user bus \u2014 WSL, slim containers, or
23
+ GRIX_FORCE_BARE_DAEMON=1; the fallback also self-registers an
24
+ autostart snippet in ~/.profile so the daemon comes back on
25
+ every new login shell)
22
26
  Windows: Task Scheduler (hidden WScript launcher)
23
27
 
24
28
  Examples:
25
29
  grix-connector start # Start as system service
26
30
  grix-connector status # Check service status
27
31
  grix-connector restart # Restart the service
28
- `),process.exit(0));const d=A[0],I=["start","stop","restart","status"];if(d&&I.includes(d)){process.platform==="win32"&&["start","stop","restart"].includes(d)&&!G()&&console.warn(`Warning: Not running as administrator. Task Scheduler registration is skipped;
32
+ `),process.exit(0));const d=P[0],R=["start","stop","restart","status"];if(d&&R.includes(d)){process.platform==="win32"&&["start","stop","restart"].includes(d)&&!G()&&console.warn(`Warning: Not running as administrator. Task Scheduler registration is skipped;
29
33
  using Startup folder auto-start instead. For full Task Scheduler integration,
30
- right-click the terminal and select "Run as administrator".`);const t=v(),m=s["config-dir"]??(s.profile?g.join(t.configDir,s.profile):void 0),l=g.resolve(process.argv[1]||`${t.rootDir}/dist/grix.js`),r=new j({cliPath:l,nodePath:process.execPath});try{let o;switch(d){case"start":(await r.status({rootDir:t.rootDir})).installed?o=await r.start({rootDir:t.rootDir}):o=await r.install({rootDir:t.rootDir,configDir:m});break;case"stop":o=await r.stop({rootDir:t.rootDir});break;case"restart":(await r.status({rootDir:t.rootDir})).installed?o=await r.restart({rootDir:t.rootDir}):o=await r.install({rootDir:t.rootDir,configDir:m});break;case"status":o=await r.status({rootDir:t.rootDir});break}console.log(JSON.stringify(o,null,2)),process.exit(0)}catch(o){console.error(`${d} failed: ${o instanceof Error?o.message:o}`),process.exit(1)}}else d&&(console.error(`Unknown command: ${d}
31
- Valid commands: ${I.join(", ")}`),process.exit(1));const i=v(),J=s["config-dir"]??(s.profile?`${i.configDir}/${s.profile}`:void 0),a=new $,S=new U,R=V(),p=new B(R);let T=!1;async function D(t){if(T)return;T=!0,n.info("main",`Received ${t}, shutting down...`),S.markShuttingDown();const m=setTimeout(()=>{n.error("main","Shutdown timed out, forcing exit"),w(i.daemonLockFile).catch(()=>{}),h(),process.exit(2)},1e4);try{await a.stop(),await p.stop(),await S.stop(),await P(),await w(i.daemonLockFile),await M(i.daemonStatusFile).catch(()=>{}),clearTimeout(m),h(),n.info("main","Shutdown complete"),process.exit(0)}catch(l){n.error("main",`Shutdown error: ${l}`),w(i.daemonLockFile).catch(()=>{}),h(),process.exit(2)}}async function K(){L(),N(),await q(),b(i.stdoutLogFile,i.stderrLogFile),C(!1),process.platform==="win32"&&await _("GrixConnectorDaemon",{platform:"win32"});try{await W(i.daemonLockFile,i.rootDir)}catch(e){console.error(e instanceof Error?e.message:e),process.exit(1)}H(),n.info("main",`grix-connector starting (PID ${process.pid})`),await k(i.daemonStatusFile,{state:"starting",pid:process.pid,updated_at:Date.now()});const t=parseInt(s["health-port"]??process.env.GRIX_HEALTH_PORT??"19579",10);await S.start(t);const m=g.join(i.dataDir,"health-port");x(m,String(t),"utf-8"),process.on("SIGINT",()=>D("SIGINT")),process.on("SIGTERM",()=>D("SIGTERM"));let l="",r=0,o;process.on("uncaughtException",e=>{const u=e instanceof Error?e.stack??e.message:String(e);u===l?(r++,(r<=3||r%100===0)&&n.error("main",`Uncaught exception (x${r}): ${u}`)):(r>3&&n.error("main",`Previous exception repeated ${r} times total`),l=u,r=1,n.error("main",`Uncaught exception: ${e instanceof Error?e.stack:e}`),o||(o=setTimeout(()=>{r>3&&n.error("main",`Previous exception repeated ${r} times total`),l="",r=0,o=void 0},1e4).unref())),!F(e)&&(E(e,"uncaughtException"),D("uncaughtException"))}),process.on("unhandledRejection",e=>{n.error("main",`Unhandled rejection: ${e}`),!F(e)&&(E(e,"unhandledRejection"),D("unhandledRejection"))}),S.setStatusProvider(()=>a.getAgentsStatus()),await a.start(J);const f=parseInt(s["admin-port"]??process.env.GRIX_ADMIN_PORT??"19580",10);p.setAgentHandler({list:()=>a.getAgentsStatus(),add:e=>a.addAgent(e),remove:e=>a.removeAgent(e),restart:e=>a.restartAgent(e)}),p.setUpgradeHandler({check:()=>a.checkUpgrade(),trigger:()=>a.triggerUpgrade()}),p.setProbeHandler({probeAll:e=>a.probeAll(e),probeOne:(e,u)=>a.probeOne(e,u)}),p.setInstallHandler({listInstallable:()=>a.listInstallable(),installAgent:e=>a.installAgent(e),getInstallProgress:e=>a.getInstallProgress(e)}),await p.start(f);const y=g.join(i.dataDir,"admin-token"),O=g.join(i.dataDir,"admin-port");X(y,R),x(O,String(f),"utf-8"),await k(i.daemonStatusFile,{state:"running",pid:process.pid,updated_at:Date.now()}),process.send&&process.send("ready"),n.info("main","grix-connector ready")}K().catch(async t=>{n.error("main",`Fatal: ${t}`),E(t,"startup"),await P(),w(i.daemonLockFile).catch(()=>{}),h(),process.exit(1)});const z=new Set(["ECONNRESET","ECONNREFUSED","ETIMEDOUT","EPIPE","EAI_AGAIN","ENOTFOUND","EHOSTUNREACH","ENETUNREACH","EIO"]);function F(t){return t instanceof Error&&"code"in t?z.has(t.code):!1}
34
+ right-click the terminal and select "Run as administrator".`);const t=k(),m=s["config-dir"]??(s.profile?g.join(t.configDir,s.profile):void 0),l=g.resolve(process.argv[1]||`${t.rootDir}/dist/grix.js`),r=new H({cliPath:l,nodePath:process.execPath});try{let o;switch(d){case"start":(await r.status({rootDir:t.rootDir})).installed?o=await r.start({rootDir:t.rootDir}):o=await r.install({rootDir:t.rootDir,configDir:m});break;case"stop":o=await r.stop({rootDir:t.rootDir});break;case"restart":(await r.status({rootDir:t.rootDir})).installed?o=await r.restart({rootDir:t.rootDir}):o=await r.install({rootDir:t.rootDir,configDir:m});break;case"status":o=await r.status({rootDir:t.rootDir});break}console.log(JSON.stringify(o,null,2)),process.exit(0)}catch(o){console.error(`${d} failed: ${o instanceof Error?o.message:o}`),process.exit(1)}}else d&&(console.error(`Unknown command: ${d}
35
+ Valid commands: ${R.join(", ")}`),process.exit(1));const a=k(),J=s["config-dir"]??(s.profile?`${a.configDir}/${s.profile}`:void 0),i=new b,S=new U,I=X(),p=new B(I);let F=!1;async function D(t){if(F)return;F=!0,n.info("main",`Received ${t}, shutting down...`),S.markShuttingDown();const m=setTimeout(()=>{n.error("main","Shutdown timed out, forcing exit"),w(a.daemonLockFile).catch(()=>{}),h(),process.exit(2)},1e4);try{await i.stop(),await p.stop(),await S.stop(),await A(),await w(a.daemonLockFile),await M(a.daemonStatusFile).catch(()=>{}),clearTimeout(m),h(),n.info("main","Shutdown complete"),process.exit(0)}catch(l){n.error("main",`Shutdown error: ${l}`),w(a.daemonLockFile).catch(()=>{}),h(),process.exit(2)}}async function K(){L(),$(),await q(),N(a.stdoutLogFile,a.stderrLogFile),C(!1),process.platform==="win32"&&await j("GrixConnectorDaemon",{platform:"win32"});try{await W(a.daemonLockFile,a.rootDir)}catch(e){console.error(e instanceof Error?e.message:e),process.exit(1)}_(),n.info("main",`grix-connector starting (PID ${process.pid})`),await v(a.daemonStatusFile,{state:"starting",pid:process.pid,updated_at:Date.now()});const t=parseInt(s["health-port"]??process.env.GRIX_HEALTH_PORT??"19579",10);await S.start(t);const m=g.join(a.dataDir,"health-port");x(m,String(t),"utf-8"),process.on("SIGINT",()=>D("SIGINT")),process.on("SIGTERM",()=>D("SIGTERM"));let l="",r=0,o;process.on("uncaughtException",e=>{const u=e instanceof Error?e.stack??e.message:String(e);u===l?(r++,(r<=3||r%100===0)&&n.error("main",`Uncaught exception (x${r}): ${u}`)):(r>3&&n.error("main",`Previous exception repeated ${r} times total`),l=u,r=1,n.error("main",`Uncaught exception: ${e instanceof Error?e.stack:e}`),o||(o=setTimeout(()=>{r>3&&n.error("main",`Previous exception repeated ${r} times total`),l="",r=0,o=void 0},1e4).unref())),!T(e)&&(E(e,"uncaughtException"),D("uncaughtException"))}),process.on("unhandledRejection",e=>{n.error("main",`Unhandled rejection: ${e}`),!T(e)&&(E(e,"unhandledRejection"),D("unhandledRejection"))}),S.setStatusProvider(()=>i.getAgentsStatus()),await i.start(J);const f=parseInt(s["admin-port"]??process.env.GRIX_ADMIN_PORT??"19580",10);p.setAgentHandler({list:()=>i.getAgentsStatus(),add:e=>i.addAgent(e),remove:e=>i.removeAgent(e),restart:e=>i.restartAgent(e)}),p.setUpgradeHandler({check:()=>i.checkUpgrade(),trigger:()=>i.triggerUpgrade()}),p.setProbeHandler({probeAll:e=>i.probeAll(e),probeOne:(e,u)=>i.probeOne(e,u)}),p.setInstallHandler({listInstallable:()=>i.listInstallable(),installAgent:e=>i.installAgent(e),getInstallProgress:e=>i.getInstallProgress(e)}),await p.start(f);const y=g.join(a.dataDir,"admin-token"),O=g.join(a.dataDir,"admin-port");V(y,I),x(O,String(f),"utf-8"),await v(a.daemonStatusFile,{state:"running",pid:process.pid,updated_at:Date.now()}),process.send&&process.send("ready"),n.info("main","grix-connector ready")}K().catch(async t=>{n.error("main",`Fatal: ${t}`),E(t,"startup"),await A(),w(a.daemonLockFile).catch(()=>{}),h(),process.exit(1)});const z=new Set(["ECONNRESET","ECONNREFUSED","ETIMEDOUT","EPIPE","EAI_AGAIN","ENOTFOUND","EHOSTUNREACH","ENETUNREACH","EIO"]);function T(t){return t instanceof Error&&"code"in t?z.has(t.code):!1}
package/dist/log.js CHANGED
@@ -1,3 +1,3 @@
1
- import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as i}from"node:path";import{homedir as m}from"node:os";const n=i(m(),".grix"),s={base:n,config:i(n,"config"),log:i(n,"log"),data:i(n,"data")};function S(){for(const o of Object.values(s))f(o)||l(o,{recursive:!0})}let a=null;function $(){const o=new Date().toISOString().slice(0,10),r=i(s.log,`grix-acp-${o}.log`);a=g(r,{flags:"a"})}function c(){return new Date().toISOString().slice(11,19)}const u={info(o,r,...t){const e=`${c()} [${o}] ${r}${t.length?" "+t.map(String).join(" "):""}`;console.log(e),a?.write(e+`
2
- `)},error(o,r,...t){const e=`${c()} [${o}] ERROR ${r}${t.length?" "+t.map(String).join(" "):""}`;console.error(e),a?.write(e+`
1
+ import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as t}from"node:path";import{homedir as m}from"node:os";const i=t(m(),".grix"),s={base:i,config:t(i,"config"),log:t(i,"log"),data:t(i,"data")};function S(){for(const o of Object.values(s))f(o)||l(o,{recursive:!0})}let a=null;function $(){const o=new Date().toISOString().slice(0,10),r=t(s.log,`grix-acp-${o}.log`);a=g(r,{flags:"a"})}function c(){return new Date().toISOString().slice(11,19)}const u={info(o,r,...n){const e=`${c()} [${o}] ${r}${n.length?" "+n.map(String).join(" "):""}`;console.log(e),a?.write(e+`
2
+ `)},error(o,r,...n){const e=`${c()} [${o}] ERROR ${r}${n.length?" "+n.map(String).join(" "):""}`;console.error(e),a?.write(e+`
3
3
  `)}};export{s as GRIX_PATHS,S as ensureGrixDirs,$ as initLogger,u as log};
@@ -1 +1 @@
1
- import*as n from"node:net";const i={bind:"127.0.0.1",port:0,endpoint:"/mcp",sessionTimeoutMs:18e5,invokeTimeoutMs:3e4};function s(u){const e={bind:u?.bind??i.bind,port:u?.port??i.port,endpoint:u?.endpoint??i.endpoint,sessionTimeoutMs:u?.sessionTimeoutMs??i.sessionTimeoutMs,invokeTimeoutMs:u?.invokeTimeoutMs??i.invokeTimeoutMs,allowedOrigins:u?.allowedOrigins,allowedHosts:u?.allowedHosts};return t(e.bind),e.port!==0&&o(e.port),r(e.sessionTimeoutMs),e}function t(u){if(!u||!n.isIPv4(u)&&!n.isIPv6(u))throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: bind \u5730\u5740 "${u}" \u4E0D\u662F\u5408\u6CD5\u7684 IPv4 \u6216 IPv6 \u5730\u5740`)}function o(u){if(!Number.isInteger(u)||u<1||u>65535)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: port \u503C ${u} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1-65535 \u5185\u6216\u4E0D\u662F\u6574\u6570`)}function r(u){if(!Number.isInteger(u)||u<1e3||u>864e5)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: session_timeout_ms \u503C ${u} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1000-86400000 \u5185`)}export{s as createDefaultGatewayConfig};
1
+ import*as i from"node:net";const e={bind:"127.0.0.1",port:0,endpoint:"/mcp",sessionTimeoutMs:18e5,invokeTimeoutMs:3e4};function n(o){const u={bind:o?.bind??e.bind,port:o?.port??e.port,endpoint:o?.endpoint??e.endpoint,sessionTimeoutMs:o?.sessionTimeoutMs??e.sessionTimeoutMs,invokeTimeoutMs:o?.invokeTimeoutMs??e.invokeTimeoutMs,allowedOrigins:o?.allowedOrigins,allowedHosts:o?.allowedHosts};return s(u.bind),u.port!==0&&t(u.port),r(u.sessionTimeoutMs),u}function s(o){if(!o||!i.isIPv4(o)&&!i.isIPv6(o))throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: bind \u5730\u5740 "${o}" \u4E0D\u662F\u5408\u6CD5\u7684 IPv4 \u6216 IPv6 \u5730\u5740`)}function t(o){if(!Number.isInteger(o)||o<1||o>65535)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: port \u503C ${o} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1-65535 \u5185\u6216\u4E0D\u662F\u6574\u6570`)}function r(o){if(!Number.isInteger(o)||o<1e3||o>864e5)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: session_timeout_ms \u503C ${o} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1000-86400000 \u5185`)}export{n as createDefaultGatewayConfig};
@@ -1 +1 @@
1
- const a=3e4;class c{connectionManager;onDisconnected;bindings=new Map;constructor(n,i){this.connectionManager=n,this.onDisconnected=i}async bind(n,i){if(this.bindings.has(n))throw new Error(`Session ${n} is already bound to a connection`);const e=await this.connectWithTimeout(i),t=[],s=e.onDisconnected(()=>{this.removeBinding(n),this.onDisconnected(n)});return t.push(s),this.bindings.set(n,{sessionId:n,handle:e,subscriptions:t}),e}getHandle(n){return this.bindings.get(n)?.handle}unbind(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e();i.handle.disconnect()}}unbindAll(){const n=[...this.bindings.keys()];for(const i of n)this.unbind(i)}connectWithTimeout(n){return new Promise((i,e)=>{let t=!1;const s=setTimeout(()=>{t||(t=!0,e(new Error("Connection bind timeout after 30000ms")))},3e4);this.connectionManager.connect({agentId:n.agentId,apiKey:n.apiKey,url:n.wsUrl,clientType:n.clientType,capabilities:["agent_invoke"],adapterHint:`${n.clientType}/base`},{maxRetries:0}).then(o=>{t?o.disconnect():(t=!0,clearTimeout(s),i(o))}).catch(o=>{t||(t=!0,clearTimeout(s),e(o))})})}removeBinding(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e()}}}export{c as ConnectionBindingImpl};
1
+ const r=3e4;class c{connectionManager;onDisconnected;bindings=new Map;constructor(n,i){this.connectionManager=n,this.onDisconnected=i}async bind(n,i){if(this.bindings.has(n))throw new Error(`Session ${n} is already bound to a connection`);const e=await this.connectWithTimeout(i),t=[],s=e.onDisconnected(()=>{this.removeBinding(n),this.onDisconnected(n)});return t.push(s),this.bindings.set(n,{sessionId:n,handle:e,subscriptions:t}),e}getHandle(n){return this.bindings.get(n)?.handle}unbind(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e();i.handle.disconnect()}}unbindAll(){const n=[...this.bindings.keys()];for(const i of n)this.unbind(i)}connectWithTimeout(n){return new Promise((i,e)=>{let t=!1;const s=setTimeout(()=>{t||(t=!0,e(new Error("Connection bind timeout after 30000ms")))},3e4);this.connectionManager.connect({agentId:n.agentId,apiKey:n.apiKey,url:n.wsUrl,clientType:n.clientType,capabilities:["agent_invoke"],adapterHint:`${n.clientType}/base`},{maxRetries:0}).then(o=>{t?o.disconnect():(t=!0,clearTimeout(s),i(o))}).catch(o=>{t||(t=!0,clearTimeout(s),e(o))})})}removeBinding(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e()}}}export{c as ConnectionBindingImpl};
@@ -1 +1 @@
1
- function a(t){const o=new Set([`http://127.0.0.1:${t.serverPort}`,`http://localhost:${t.serverPort}`,...t.allowedOrigins]),e=new Set([`127.0.0.1:${t.serverPort}`,`localhost:${t.serverPort}`,...t.allowedHosts]);return{validateRequest(s){const r=i(s,o);if(!r.ok)return r;const n=l(s,e);return n.ok?{ok:!0}:n}}}function i(t,o){const e=t.headers.origin;return e?o.has(e)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${e}`}:{ok:!0}}function l(t,o){const e=t.headers.host;return e?o.has(e)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${e}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
1
+ function a(e){const t=new Set([`http://127.0.0.1:${e.serverPort}`,`http://localhost:${e.serverPort}`,...e.allowedOrigins]),o=new Set([`127.0.0.1:${e.serverPort}`,`localhost:${e.serverPort}`,...e.allowedHosts]);return{validateRequest(s){const r=i(s,t);if(!r.ok)return r;const n=l(s,o);return n.ok?{ok:!0}:n}}}function i(e,t){const o=e.headers.origin;return o?t.has(o)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${o}`}:{ok:!0}}function l(e,t){const o=e.headers.host;return o?t.has(o)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${o}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
@@ -1 +1 @@
1
- import{toolCallToInvoke as i}from"../../core/mcp/tools.js";import{ToolRegistryImpl as l}from"./tool-registry.js";import{validateToolArgs as a}from"./tool-schemas.js";import{isEventTool as p,executeEventTool as d}from"./event-tool-executor.js";class y{registry;constructor(){this.registry=new l}async execute(r,e,t,n){if(!this.registry.hasTool(e))return this.errorResult(`\u672A\u77E5\u5DE5\u5177: ${e}`);const s=a(e,t);if(!s.valid)return this.errorResult(`\u53C2\u6570\u6821\u9A8C\u5931\u8D25: ${s.error}`);if(r.status!=="ready")return this.errorResult(`\u8FDE\u63A5\u4E0D\u53EF\u7528: \u5F53\u524D\u72B6\u6001\u4E3A ${r.status}`);if(p(e))return this.executeEventTool(r,e,t);const o=i(e,t);try{const u=await r.agentInvoke(o.action,o.params,n);return this.normalizeResult(u)}catch(u){const c=u instanceof Error?u.message:String(u);return c.toLowerCase().includes("timeout")?this.errorResult(`\u8C03\u7528\u8D85\u65F6: ${c}`):this.errorResult(`\u8C03\u7528\u5931\u8D25: ${c}`)}}normalizeResult(r){if(r==null||typeof r!="object")return this.successResult(r??null);const e=r,t=typeof e.code=="number"?e.code:0;if(t===0){const s="data"in e?e.data:null;return this.successResult(s??null)}const n=typeof e.msg=="string"?e.msg:"\u672A\u77E5\u9519\u8BEF";return this.errorResult(`\u4E0A\u6E38\u9519\u8BEF [code=${t}]: ${n}`)}successResult(r){return{content:[{type:"text",text:JSON.stringify(r)}],isError:!1}}errorResult(r){return{content:[{type:"text",text:r}],isError:!0}}async executeEventTool(r,e,t){return e==="grix_access_control"?this.executeAccessControl(r,t):d(r,e,t)}async executeAccessControl(r,e){const t=String(e.action??""),n={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"}[t];if(!n)return this.errorResult(`\u672A\u77E5 access_control action: ${t}`);const s={};e.code!=null&&(s.code=e.code),e.sender_id!=null&&(s.sender_id=e.sender_id),e.policy!=null&&(s.policy=e.policy);try{const o=await r.agentInvoke("claude_access_control",{verb:n,payload:s},3e4);return this.successResult(o)}catch(o){const u=o instanceof Error?o.message:String(o);return this.errorResult(`access_control \u8C03\u7528\u5931\u8D25: ${u}`)}}}export{y as ToolExecutorImpl};
1
+ import{toolCallToInvoke as i}from"../../core/mcp/tools.js";import{ToolRegistryImpl as l}from"./tool-registry.js";import{validateToolArgs as a}from"./tool-schemas.js";import{isEventTool as p,executeEventTool as d}from"./event-tool-executor.js";class y{registry;constructor(){this.registry=new l}async execute(t,e,r,n){if(!this.registry.hasTool(e))return this.errorResult(`\u672A\u77E5\u5DE5\u5177: ${e}`);const s=a(e,r);if(!s.valid)return this.errorResult(`\u53C2\u6570\u6821\u9A8C\u5931\u8D25: ${s.error}`);if(t.status!=="ready")return this.errorResult(`\u8FDE\u63A5\u4E0D\u53EF\u7528: \u5F53\u524D\u72B6\u6001\u4E3A ${t.status}`);if(p(e))return this.executeEventTool(t,e,r);const o=i(e,r);try{const u=await t.agentInvoke(o.action,o.params,n);return this.normalizeResult(u)}catch(u){const c=u instanceof Error?u.message:String(u);return c.toLowerCase().includes("timeout")?this.errorResult(`\u8C03\u7528\u8D85\u65F6: ${c}`):this.errorResult(`\u8C03\u7528\u5931\u8D25: ${c}`)}}normalizeResult(t){if(t==null||typeof t!="object")return this.successResult(t??null);const e=t,r=typeof e.code=="number"?e.code:0;if(r===0){const s="data"in e?e.data:null;return this.successResult(s??null)}const n=typeof e.msg=="string"?e.msg:"\u672A\u77E5\u9519\u8BEF";return this.errorResult(`\u4E0A\u6E38\u9519\u8BEF [code=${r}]: ${n}`)}successResult(t){return{content:[{type:"text",text:JSON.stringify(t)}],isError:!1}}errorResult(t){return{content:[{type:"text",text:t}],isError:!0}}async executeEventTool(t,e,r){return e==="grix_access_control"?this.executeAccessControl(t,r):d(t,e,r)}async executeAccessControl(t,e){const r=String(e.action??""),n={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"}[r];if(!n)return this.errorResult(`\u672A\u77E5 access_control action: ${r}`);const s={};e.code!=null&&(s.code=e.code),e.sender_id!=null&&(s.sender_id=e.sender_id),e.policy!=null&&(s.policy=e.policy);try{const o=await t.agentInvoke("claude_access_control",{verb:n,payload:s},3e4);return this.successResult(o)}catch(o){const u=o instanceof Error?o.message:String(o);return this.errorResult(`access_control \u8C03\u7528\u5931\u8D25: ${u}`)}}}export{y as ToolExecutorImpl};
@@ -1 +1 @@
1
- import{TOOLS as o,EVENT_TOOLS as s}from"../../core/mcp/tools.js";const e=new Set(["grix_query","grix_group","grix_message_send","grix_message_unsend","grix_admin"]),r=new Set(["grix_reply","grix_complete","grix_event_ack","grix_composing","grix_access_control","grix_status"]);class a{tools;toolMap;constructor(){this.tools=[...o.filter(t=>e.has(t.name)),...s.filter(t=>r.has(t.name))],this.toolMap=new Map(this.tools.map(t=>[t.name,t]))}getTools(){return this.tools}getTool(t){return this.toolMap.get(t)}hasTool(t){return this.toolMap.has(t)}}export{a as ToolRegistryImpl};
1
+ import{TOOLS as s,EVENT_TOOLS as t}from"../../core/mcp/tools.js";const e=new Set(["grix_query","grix_group","grix_message_send","grix_message_unsend","grix_admin"]),r=new Set(["grix_reply","grix_complete","grix_event_ack","grix_composing","grix_access_control","grix_status"]);class a{tools;toolMap;constructor(){this.tools=[...s.filter(o=>e.has(o.name)),...t.filter(o=>r.has(o.name))],this.toolMap=new Map(this.tools.map(o=>[o.name,o]))}getTools(){return this.tools}getTool(o){return this.toolMap.get(o)}hasTool(o){return this.toolMap.has(o)}}export{a as ToolRegistryImpl};
@@ -1 +1 @@
1
- const o={required:["action"],properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},id:{type:"string"},keyword:{type:"string",maxLength:200},limit:{type:"integer",minimum:1,maximum:100},offset:{type:"integer",minimum:0},sessionId:{type:"string"},beforeId:{type:"string"}}},a={required:["action"],properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string",maxLength:128},memberIds:{type:"array",items:{type:"string"},maxItems:100},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer"},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean"}}},p={required:["sessionId","content"],properties:{sessionId:{type:"string"},content:{type:"string",maxLength:1e4},msgType:{type:"integer"},quotedMessageId:{type:"string"},threadId:{type:"string"}}},m={required:["sessionId","msgId"],properties:{sessionId:{type:"string"},msgId:{type:"string"}}},g={required:["action"],properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},agentId:{type:"string"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"integer"}}},y={required:["session_id","text"],properties:{event_id:{type:"string"},session_id:{type:"string"},text:{type:"string",maxLength:5e4},quoted_message_id:{type:"string"},is_final:{type:"boolean"}}},d={required:["event_id","status"],properties:{event_id:{type:"string"},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string",maxLength:500}}},c={required:["event_id"],properties:{event_id:{type:"string"},session_id:{type:"string"}}},l={required:["session_id","active"],properties:{session_id:{type:"string"},active:{type:"boolean"},event_id:{type:"string"}}},_={required:["action"],properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"]},code:{type:"string"},sender_id:{type:"string"},policy:{type:"string",enum:["allowlist","open","disabled"]}}},f={required:[],properties:{}},C={grix_query:o,grix_group:a,grix_message_send:p,grix_message_unsend:m,grix_admin:g,grix_reply:y,grix_complete:d,grix_event_ack:c,grix_composing:l,grix_access_control:_,grix_status:f};function B(r,t){const e=C[r];if(!e)return{valid:!1,error:`\u672A\u77E5\u5DE5\u5177: ${r}`};for(const i of e.required)if(t[i]===void 0||t[i]===null)return{valid:!1,error:`\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: ${i}`};for(const[i,u]of Object.entries(t)){if(u==null)continue;const n=e.properties[i];if(!n)continue;const s=$(i,u,n);if(s)return{valid:!1,error:s}}return{valid:!0}}function $(r,t,e){switch(e.type){case"string":if(typeof t!="string")return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxLength!==void 0&&t.length>e.maxLength)return`\u53C2\u6570 ${r} \u8D85\u8FC7\u6700\u5927\u957F\u5EA6 ${e.maxLength}\uFF0C\u5B9E\u9645 ${t.length}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${r} \u503C "${t}" \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"integer":if(typeof t!="number"||!Number.isInteger(t))return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B integer\uFF0C\u5B9E\u9645 ${typeof t=="number"?"\u6D6E\u70B9\u6570":typeof t}`;if(e.minimum!==void 0&&t<e.minimum)return`\u53C2\u6570 ${r} \u503C ${t} \u5C0F\u4E8E\u6700\u5C0F\u503C ${e.minimum}`;if(e.maximum!==void 0&&t>e.maximum)return`\u53C2\u6570 ${r} \u503C ${t} \u5927\u4E8E\u6700\u5927\u503C ${e.maximum}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${r} \u503C ${t} \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"boolean":if(typeof t!="boolean")return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B boolean\uFF0C\u5B9E\u9645 ${typeof t}`;break;case"array":if(!Array.isArray(t))return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B array\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxItems!==void 0&&t.length>e.maxItems)return`\u53C2\u6570 ${r} \u8D85\u8FC7\u6700\u5927\u5143\u7D20\u6570 ${e.maxItems}\uFF0C\u5B9E\u9645 ${t.length}`;if(e.items)for(let i=0;i<t.length;i++){const u=t[i];if(e.items.type==="string"&&typeof u!="string")return`\u53C2\u6570 ${r}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof u}`;if(e.items.type==="integer"){if(typeof u!="number"||!Number.isInteger(u))return`\u53C2\u6570 ${r}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B integer\uFF0C\u5B9E\u9645 ${typeof u}`;if(e.items.enum&&!e.items.enum.includes(u))return`\u53C2\u6570 ${r}[${i}] \u503C ${u} \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.items.enum.join(", ")}]`}}break}}export{B as validateToolArgs};
1
+ const o={required:["action"],properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},id:{type:"string"},keyword:{type:"string",maxLength:200},limit:{type:"integer",minimum:1,maximum:100},offset:{type:"integer",minimum:0},sessionId:{type:"string"},beforeId:{type:"string"}}},a={required:["action"],properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string",maxLength:128},memberIds:{type:"array",items:{type:"string"},maxItems:100},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer"},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean"}}},p={required:["sessionId","content"],properties:{sessionId:{type:"string"},content:{type:"string",maxLength:1e4},msgType:{type:"integer"},quotedMessageId:{type:"string"},threadId:{type:"string"}}},m={required:["sessionId","msgId"],properties:{sessionId:{type:"string"},msgId:{type:"string"}}},g={required:["action"],properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},agentId:{type:"string"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"integer"}}},y={required:["session_id","text"],properties:{event_id:{type:"string"},session_id:{type:"string"},text:{type:"string",maxLength:5e4},quoted_message_id:{type:"string"},is_final:{type:"boolean"}}},d={required:["event_id","status"],properties:{event_id:{type:"string"},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string",maxLength:500}}},c={required:["event_id"],properties:{event_id:{type:"string"},session_id:{type:"string"}}},l={required:["session_id","active"],properties:{session_id:{type:"string"},active:{type:"boolean"},event_id:{type:"string"}}},_={required:["action"],properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"]},code:{type:"string"},sender_id:{type:"string"},policy:{type:"string",enum:["allowlist","open","disabled"]}}},f={required:[],properties:{}},C={grix_query:o,grix_group:a,grix_message_send:p,grix_message_unsend:m,grix_admin:g,grix_reply:y,grix_complete:d,grix_event_ack:c,grix_composing:l,grix_access_control:_,grix_status:f};function B(u,t){const e=C[u];if(!e)return{valid:!1,error:`\u672A\u77E5\u5DE5\u5177: ${u}`};for(const i of e.required)if(t[i]===void 0||t[i]===null)return{valid:!1,error:`\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: ${i}`};for(const[i,r]of Object.entries(t)){if(r==null)continue;const n=e.properties[i];if(!n)continue;const s=$(i,r,n);if(s)return{valid:!1,error:s}}return{valid:!0}}function $(u,t,e){switch(e.type){case"string":if(typeof t!="string")return`\u53C2\u6570 ${u} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxLength!==void 0&&t.length>e.maxLength)return`\u53C2\u6570 ${u} \u8D85\u8FC7\u6700\u5927\u957F\u5EA6 ${e.maxLength}\uFF0C\u5B9E\u9645 ${t.length}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${u} \u503C "${t}" \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"integer":if(typeof t!="number"||!Number.isInteger(t))return`\u53C2\u6570 ${u} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B integer\uFF0C\u5B9E\u9645 ${typeof t=="number"?"\u6D6E\u70B9\u6570":typeof t}`;if(e.minimum!==void 0&&t<e.minimum)return`\u53C2\u6570 ${u} \u503C ${t} \u5C0F\u4E8E\u6700\u5C0F\u503C ${e.minimum}`;if(e.maximum!==void 0&&t>e.maximum)return`\u53C2\u6570 ${u} \u503C ${t} \u5927\u4E8E\u6700\u5927\u503C ${e.maximum}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${u} \u503C ${t} \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"boolean":if(typeof t!="boolean")return`\u53C2\u6570 ${u} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B boolean\uFF0C\u5B9E\u9645 ${typeof t}`;break;case"array":if(!Array.isArray(t))return`\u53C2\u6570 ${u} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B array\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxItems!==void 0&&t.length>e.maxItems)return`\u53C2\u6570 ${u} \u8D85\u8FC7\u6700\u5927\u5143\u7D20\u6570 ${e.maxItems}\uFF0C\u5B9E\u9645 ${t.length}`;if(e.items)for(let i=0;i<t.length;i++){const r=t[i];if(e.items.type==="string"&&typeof r!="string")return`\u53C2\u6570 ${u}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof r}`;if(e.items.type==="integer"){if(typeof r!="number"||!Number.isInteger(r))return`\u53C2\u6570 ${u}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B integer\uFF0C\u5B9E\u9645 ${typeof r}`;if(e.items.enum&&!e.items.enum.includes(r))return`\u53C2\u6570 ${u}[${i}] \u503C ${r} \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.items.enum.join(", ")}]`}}break}}export{B as validateToolArgs};
@@ -1,12 +1,12 @@
1
- import{mkdir as P,readdir as b,readFile as k,rm as g,stat as E,writeFile as S}from"node:fs/promises";import p from"node:os";import d from"node:path";import{runCommand as u,spawnDetached as W,killProcessesByCommandLine as L,isWindowsElevated as D}from"./process-control.js";import{getServicePrefix as x,parseConfigDirFromPlistXML as j,parseConfigDirFromSystemdUnit as R,resolveLinuxUserUnitPath as M,resolveMacOSLaunchAgentPath as O}from"./service-paths.js";function h(t){return String(t??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function B(t){return`'${String(t??"").replace(/'/g,"'\\''")}'`}function T(t){return[t.nodePath,t.cliPath,...t.configDir?["--config-dir",t.configDir]:[]]}function U(t){const r=T(t);return`<?xml version="1.0" encoding="UTF-8"?>
1
+ import{existsSync as R,readFileSync as j}from"node:fs";import{mkdir as P,readdir as C,readFile as f,rm as w,stat as V,writeFile as h}from"node:fs/promises";import p from"node:os";import u from"node:path";import{isProcessRunning as v,runCommand as d,spawnDetached as W,isWindowsElevated as G,killProcessesByCommandLine as T}from"./process-control.js";import{getServicePrefix as A,parseConfigDirFromPlistXML as Q,parseConfigDirFromSystemdUnit as z,resolveBareDaemonMarkerPath as g,resolveLinuxUserUnitPath as H,resolveMacOSLaunchAgentPath as J}from"./service-paths.js";function y(t){return String(t??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function F(t){return`'${String(t??"").replace(/'/g,"'\\''")}'`}function M(t){return[t.nodePath,t.cliPath,...t.configDir?["--config-dir",t.configDir]:[]]}function X(t){const r=M(t);return`<?xml version="1.0" encoding="UTF-8"?>
2
2
  <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
3
  <plist version="1.0">
4
4
  <dict>
5
5
  <key>Label</key>
6
- <string>${h(t.serviceID)}</string>
6
+ <string>${y(t.serviceID)}</string>
7
7
  <key>ProgramArguments</key>
8
8
  <array>
9
- ${r.map(e=>` <string>${h(e)}</string>`).join(`
9
+ ${r.map(e=>` <string>${y(e)}</string>`).join(`
10
10
  `)}
11
11
  </array>
12
12
  <key>RunAtLoad</key>
@@ -14,26 +14,26 @@ ${r.map(e=>` <string>${h(e)}</string>`).join(`
14
14
  <key>KeepAlive</key>
15
15
  <true/>
16
16
  <key>WorkingDirectory</key>
17
- <string>${h(d.dirname(t.cliPath))}</string>
17
+ <string>${y(u.dirname(t.cliPath))}</string>
18
18
  ${t.environmentPath?` <key>EnvironmentVariables</key>
19
19
  <dict>
20
20
  <key>PATH</key>
21
- <string>${h(t.environmentPath)}</string>
21
+ <string>${y(t.environmentPath)}</string>
22
22
  </dict>
23
23
  `:""} <key>StandardOutPath</key>
24
- <string>${h(t.stdoutPath)}</string>
24
+ <string>${y(t.stdoutPath)}</string>
25
25
  <key>StandardErrorPath</key>
26
- <string>${h(t.stderrPath)}</string>
26
+ <string>${y(t.stderrPath)}</string>
27
27
  </dict>
28
28
  </plist>
29
- `}function V(t){const r=T(t).map(e=>B(e)).join(" ");return`[Unit]
29
+ `}function Z(t){const r=M(t).map(e=>F(e)).join(" ");return`[Unit]
30
30
  Description=grix-connector daemon (${t.serviceID})
31
31
  After=network.target
32
32
 
33
33
  [Service]
34
34
  Type=simple
35
35
  ExecStart=${r}
36
- WorkingDirectory=${d.dirname(t.cliPath)}
36
+ WorkingDirectory=${u.dirname(t.cliPath)}
37
37
  Restart=always
38
38
  RestartSec=2
39
39
  StandardOutput=append:${t.stdoutPath}
@@ -41,6 +41,13 @@ StandardError=append:${t.stderrPath}
41
41
 
42
42
  [Install]
43
43
  WantedBy=default.target
44
- `}function w(t,r){const e=t.replace(/[^a-zA-Z0-9._-]/g,"_");return d.join(r,".grix",`${e}-wrapper.vbs`)}function A(t,r){const e=t.replace(/[^a-zA-Z0-9._-]/g,"_"),a=d.join(r,"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup");return d.join(a,`${e}.lnk`)}function f(t){return String(t??"").replace(/"/g,'""')}function Q(t,r){const e=d.join(d.dirname(t),d.basename(r).replace(/\.lnk$/,"-create.vbs")),a=["Option Explicit","Dim shell, shortcut",'Set shell = CreateObject("WScript.Shell")',`Set shortcut = shell.CreateShortcut("${f(r)}")`,'shortcut.TargetPath = "wscript.exe"',`shortcut.Arguments = "//B //NoLogo \\"${f(t)}\\""`,"shortcut.WindowStyle = 7","shortcut.Save",""].join(`\r
45
- `);return{path:e,content:a}}function _(t){const r=[`"${f(t.nodePath)}"`,`"${f(t.cliPath)}"`,...t.configDir?["--config-dir",`"${f(t.configDir)}"`]:[]].join(" ");return["Option Explicit","Dim shell, command, delayMs, rapidCount",`command = "${f(r)}"`,'Set shell = CreateObject("WScript.Shell")',"delayMs = 5000","rapidCount = 0","Do"," shell.Run command, 0, True"," rapidCount = rapidCount + 1"," If rapidCount >= 10 Then"," WScript.Sleep 300000"," rapidCount = 0"," Else"," WScript.Sleep delayMs"," End If","Loop",""].join(`\r
46
- `)}function y(t){return`gui/${t??0}`}function v(t){const r=String(t?.stdout??"").trim();return String(t?.stderr??"").trim()||r||`exit=${Number(t?.exitCode??-1)}`}function F(t,r){if(Number(t?.exitCode??0)!==0)throw new Error(`${r}: ${v(t)}`)}function z(){return{platform:"darwin",kind:"launchd",async install({serviceID:t,nodePath:r,cliPath:e,configDir:a,stdoutPath:n,stderrPath:i,environmentPath:c="",homeDir:o=p.homedir()}){const s=O(t,o);return await P(d.dirname(s),{recursive:!0}),await S(s,U({serviceID:t,nodePath:r,cliPath:e,configDir:a,stdoutPath:n,stderrPath:i,environmentPath:c}),{encoding:"utf8",mode:384}),{definitionPath:s}},async start({serviceID:t,definitionPath:r,runCommand:e=u,uid:a=process.getuid?.()??0}){const n=y(a);let i=await e("launchctl",["bootstrap",n,r],{allowFailure:!0});Number(i?.exitCode??0)!==0&&(await e("launchctl",["bootout",`${n}/${t}`],{allowFailure:!0}),i=await e("launchctl",["bootstrap",n,r],{allowFailure:!0}),F(i,`launchctl bootstrap ${n}`));const c=await e("launchctl",["kickstart","-k",`${n}/${t}`],{allowFailure:!0});if(Number(c?.exitCode??0)!==0){const o=Number(i?.exitCode??0)===0?"":`, bootstrap=${v(i)}`;throw new Error(`launchctl start failed for ${n}/${t}: ${v(c)}${o}`)}},async stop({serviceID:t,runCommand:r=u,uid:e=process.getuid?.()??0}){const a=y(e);await r("launchctl",["bootout",`${a}/${t}`],{allowFailure:!0})},async restart({serviceID:t,definitionPath:r,runCommand:e=u,uid:a=process.getuid?.()??0}){const n=y(a);await e("launchctl",["bootout",`${n}/${t}`],{allowFailure:!0});for(let o=0;o<20&&(await e("launchctl",["print",`${n}/${t}`],{allowFailure:!0})).exitCode===0;o+=1)await new Promise(l=>setTimeout(l,250));try{await E(r)}catch{throw new Error(`launchd plist missing: ${r}`)}const i=await e("launchctl",["bootstrap",n,r],{allowFailure:!0});F(i,`launchctl bootstrap ${n}`);const c=await e("launchctl",["kickstart","-k",`${n}/${t}`],{allowFailure:!0});F(c,`launchctl kickstart ${n}/${t}`)},async uninstall({serviceID:t,definitionPath:r,runCommand:e=u,uid:a=process.getuid?.()??0}){const n=y(a);await e("launchctl",["bootout",`${n}/${t}`],{allowFailure:!0}),await g(r,{force:!0})},async discoverServices({homeDir:t=p.homedir()}={}){const r=d.join(t,"Library","LaunchAgents"),e=await b(r).catch(()=>[]),a=x("darwin"),n=[];for(const i of e){if(!i.startsWith(a)||!i.endsWith(".plist"))continue;const c=i.slice(0,-6),o=d.join(r,i);let s=null;try{const l=await k(o,"utf8");s=j(l)}catch{}n.push({serviceID:c,definitionPath:o,configDir:s})}return n},async isServiceLoaded({serviceID:t,runCommand:r=u,uid:e=process.getuid?.()??0}){const a=y(e),n=await r("launchctl",["print",`${a}/${t}`],{allowFailure:!0});return Number(n?.exitCode??1)===0}}}function H(){return{platform:"win32",kind:"task-scheduler",async install({serviceID:t,nodePath:r,cliPath:e,configDir:a,runCommand:n=u,homeDir:i=p.homedir()}){const c=w(t,i);await P(d.dirname(c),{recursive:!0}),await S(c,_({nodePath:r,cliPath:e,configDir:a}),"utf8");let o=!1;if(D())try{await n("schtasks",["/Create","/TN",t,"/SC","ONCE","/ST","00:00","/SD","2099/12/31","/RL","LIMITED","/F","/TR",`wscript.exe //B //NoLogo "${c}"`]),o=!0}catch{}const s=A(t,i),l=Q(c,s);return await S(l.path,l.content,"utf8"),await n("wscript.exe",[l.path,"//B","//NoLogo"]).catch(()=>{}),await g(l.path,{force:!0}),{definitionPath:o?`task:${t}`:`startup:${t}`}},async start({serviceID:t,definitionPath:r,runCommand:e=u,homeDir:a=p.homedir()}){const n=w(t,a);if(r.startsWith("task:"))try{await e("schtasks",["/Run","/TN",t]);return}catch{}W("wscript.exe",["//B","//NoLogo",n])},async stop({serviceID:t,runCommand:r=u,homeDir:e=p.homedir()}){await r("schtasks",["/End","/TN",t],{allowFailure:!0});const a=`${t}-wrapper.vbs`;await L(a,{platform:"win32"})},async restart({serviceID:t,definitionPath:r,runCommand:e=u,homeDir:a=p.homedir()}){await e("schtasks",["/End","/TN",t],{allowFailure:!0});const n=`${t}-wrapper.vbs`;await L(n,{platform:"win32"});const i=w(t,a);if(r?.startsWith("task:"))try{await e("schtasks",["/Run","/TN",t]);return}catch{}W("wscript.exe",["//B","//NoLogo",i])},async uninstall({serviceID:t,runCommand:r=u,homeDir:e=p.homedir()}){await r("schtasks",["/Delete","/TN",t,"/F"],{allowFailure:!0});const a=w(t,e);await g(a,{force:!0});const n=A(t,e);await g(n,{force:!0})},async discoverServices({homeDir:t=p.homedir(),runCommand:r=u}={}){const e=x("win32"),a=await r("schtasks",["/Query","/FO","CSV","/NH"],{allowFailure:!0}),n=[];if(Number(a?.exitCode??-1)===0){const o=String(a.stdout??"").split(/\r?\n/);for(const s of o){const l=s.match(/^"([^"]+)"/);if(!l)continue;const m=l[1];if(!m.startsWith(e))continue;let C=null;try{const $=w(m,t),N=(await k($,"utf8")).match(/--config-dir\s+""([^""]+)""/);N&&(C=N[1])}catch{}n.push({serviceID:m,definitionPath:`task:${m}`,configDir:C})}}const i=d.join(t,"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup"),c=await b(i).catch(()=>[]);for(const o of c){if(!o.startsWith(e)||!o.endsWith(".lnk"))continue;const s=o.slice(0,-4);if(n.some(m=>m.serviceID===s))continue;let l=null;try{const m=w(s,t),$=(await k(m,"utf8")).match(/--config-dir\s+""([^""]+)""/);$&&(l=$[1])}catch{}n.push({serviceID:s,definitionPath:`startup:${s}`,configDir:l})}return n},async isServiceLoaded({serviceID:t,runCommand:r=u}){const e=await r("schtasks",["/Query","/TN",t,"/NH"],{allowFailure:!0});return Number(e?.exitCode??1)===0}}}function Z(){return{platform:"linux",kind:"systemd-user",async install({serviceID:t,nodePath:r,cliPath:e,configDir:a,stdoutPath:n,stderrPath:i,homeDir:c=p.homedir(),runCommand:o=u}){const s=M(t,c);return await P(d.dirname(s),{recursive:!0}),await S(s,V({serviceID:t,nodePath:r,cliPath:e,configDir:a,stdoutPath:n,stderrPath:i}),{encoding:"utf8",mode:384}),await o("systemctl",["--user","daemon-reload"]),await o("systemctl",["--user","enable",`${t}.service`]),{definitionPath:s}},async start({serviceID:t,runCommand:r=u}){await r("systemctl",["--user","start",`${t}.service`])},async stop({serviceID:t,runCommand:r=u}){await r("systemctl",["--user","stop",`${t}.service`],{allowFailure:!0})},async restart({serviceID:t,runCommand:r=u}){await r("systemctl",["--user","restart",`${t}.service`])},async uninstall({serviceID:t,definitionPath:r,runCommand:e=u}){await e("systemctl",["--user","stop",`${t}.service`],{allowFailure:!0}),await e("systemctl",["--user","disable",`${t}.service`],{allowFailure:!0}),await g(r,{force:!0}),await e("systemctl",["--user","daemon-reload"])},async discoverServices({homeDir:t=p.homedir()}={}){const r=d.join(t,".config","systemd","user"),e=await b(r).catch(()=>[]),a=x("linux"),n=[];for(const i of e){if(!i.startsWith(a)||!i.endsWith(".service"))continue;const c=i.slice(0,-8),o=d.join(r,i);let s=null;try{const l=await k(o,"utf8");s=R(l)}catch{}n.push({serviceID:c,definitionPath:o,configDir:s})}return n},async isServiceLoaded({serviceID:t,runCommand:r=u}){const e=await r("systemctl",["--user","is-active",`${t}.service`],{allowFailure:!0});return String(e?.stdout??"").trim()==="active"}}}function I(t=process.platform){return t==="darwin"?z():t==="win32"?H():Z()}export{I as getPlatformServiceAdapter};
44
+ `}function k(t,r){const e=t.replace(/[^a-zA-Z0-9._-]/g,"_");return u.join(r,".grix",`${e}-wrapper.vbs`)}function B(t,r){const e=t.replace(/[^a-zA-Z0-9._-]/g,"_"),n=u.join(r,"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup");return u.join(n,`${e}.lnk`)}function $(t){return String(t??"").replace(/"/g,'""')}function q(t,r){const e=u.join(u.dirname(t),u.basename(r).replace(/\.lnk$/,"-create.vbs")),n=["Option Explicit","Dim shell, shortcut",'Set shell = CreateObject("WScript.Shell")',`Set shortcut = shell.CreateShortcut("${$(r)}")`,'shortcut.TargetPath = "wscript.exe"',`shortcut.Arguments = "//B //NoLogo \\"${$(t)}\\""`,"shortcut.WindowStyle = 7","shortcut.Save",""].join(`\r
45
+ `);return{path:e,content:n}}function K(t){const r=[`"${$(t.nodePath)}"`,`"${$(t.cliPath)}"`,...t.configDir?["--config-dir",`"${$(t.configDir)}"`]:[]].join(" ");return["Option Explicit","Dim shell, command, delayMs, rapidCount",`command = "${$(r)}"`,'Set shell = CreateObject("WScript.Shell")',"delayMs = 5000","rapidCount = 0","Do"," shell.Run command, 0, True"," rapidCount = rapidCount + 1"," If rapidCount >= 10 Then"," WScript.Sleep 300000"," rapidCount = 0"," Else"," WScript.Sleep delayMs"," End If","Loop",""].join(`\r
46
+ `)}function b(t){return`gui/${t??0}`}function E(t){const r=String(t?.stdout??"").trim();return String(t?.stderr??"").trim()||r||`exit=${Number(t?.exitCode??-1)}`}function L(t,r){if(Number(t?.exitCode??0)!==0)throw new Error(`${r}: ${E(t)}`)}function Y(){return{platform:"darwin",kind:"launchd",async install({serviceID:t,nodePath:r,cliPath:e,configDir:n,stdoutPath:a,stderrPath:i,environmentPath:s="",homeDir:o=p.homedir()}){const c=J(t,o);return await P(u.dirname(c),{recursive:!0}),await h(c,X({serviceID:t,nodePath:r,cliPath:e,configDir:n,stdoutPath:a,stderrPath:i,environmentPath:s}),{encoding:"utf8",mode:384}),{definitionPath:c}},async start({serviceID:t,definitionPath:r,runCommand:e=d,uid:n=process.getuid?.()??0}){const a=b(n);let i=await e("launchctl",["bootstrap",a,r],{allowFailure:!0});Number(i?.exitCode??0)!==0&&(await e("launchctl",["bootout",`${a}/${t}`],{allowFailure:!0}),i=await e("launchctl",["bootstrap",a,r],{allowFailure:!0}),L(i,`launchctl bootstrap ${a}`));const s=await e("launchctl",["kickstart","-k",`${a}/${t}`],{allowFailure:!0});if(Number(s?.exitCode??0)!==0){const o=Number(i?.exitCode??0)===0?"":`, bootstrap=${E(i)}`;throw new Error(`launchctl start failed for ${a}/${t}: ${E(s)}${o}`)}},async stop({serviceID:t,runCommand:r=d,uid:e=process.getuid?.()??0}){const n=b(e);await r("launchctl",["bootout",`${n}/${t}`],{allowFailure:!0})},async restart({serviceID:t,definitionPath:r,runCommand:e=d,uid:n=process.getuid?.()??0}){const a=b(n);await e("launchctl",["bootout",`${a}/${t}`],{allowFailure:!0});for(let o=0;o<20&&(await e("launchctl",["print",`${a}/${t}`],{allowFailure:!0})).exitCode===0;o+=1)await new Promise(l=>setTimeout(l,250));try{await V(r)}catch{throw new Error(`launchd plist missing: ${r}`)}const i=await e("launchctl",["bootstrap",a,r],{allowFailure:!0});L(i,`launchctl bootstrap ${a}`);const s=await e("launchctl",["kickstart","-k",`${a}/${t}`],{allowFailure:!0});L(s,`launchctl kickstart ${a}/${t}`)},async uninstall({serviceID:t,definitionPath:r,runCommand:e=d,uid:n=process.getuid?.()??0}){const a=b(n);await e("launchctl",["bootout",`${a}/${t}`],{allowFailure:!0}),await w(r,{force:!0})},async discoverServices({homeDir:t=p.homedir()}={}){const r=u.join(t,"Library","LaunchAgents"),e=await C(r).catch(()=>[]),n=A("darwin"),a=[];for(const i of e){if(!i.startsWith(n)||!i.endsWith(".plist"))continue;const s=i.slice(0,-6),o=u.join(r,i);let c=null;try{const l=await f(o,"utf8");c=Q(l)}catch{}a.push({serviceID:s,definitionPath:o,configDir:c})}return a},async isServiceLoaded({serviceID:t,runCommand:r=d,uid:e=process.getuid?.()??0}){const n=b(e),a=await r("launchctl",["print",`${n}/${t}`],{allowFailure:!0});return Number(a?.exitCode??1)===0}}}function I(){return{platform:"win32",kind:"task-scheduler",async install({serviceID:t,nodePath:r,cliPath:e,configDir:n,runCommand:a=d,homeDir:i=p.homedir()}){const s=k(t,i);await P(u.dirname(s),{recursive:!0}),await h(s,K({nodePath:r,cliPath:e,configDir:n}),"utf8");let o=!1;if(G())try{await a("schtasks",["/Create","/TN",t,"/SC","ONCE","/ST","00:00","/SD","2099/12/31","/RL","LIMITED","/F","/TR",`wscript.exe //B //NoLogo "${s}"`]),o=!0}catch{}const c=B(t,i),l=q(s,c);return await h(l.path,l.content,"utf8"),await a("wscript.exe",[l.path,"//B","//NoLogo"]).catch(()=>{}),await w(l.path,{force:!0}),{definitionPath:o?`task:${t}`:`startup:${t}`}},async start({serviceID:t,definitionPath:r,runCommand:e=d,homeDir:n=p.homedir()}){const a=k(t,n);if(r.startsWith("task:"))try{await e("schtasks",["/Run","/TN",t]);return}catch{}W("wscript.exe",["//B","//NoLogo",a])},async stop({serviceID:t,runCommand:r=d,homeDir:e=p.homedir()}){await r("schtasks",["/End","/TN",t],{allowFailure:!0});const n=`${t}-wrapper.vbs`;await T(n,{platform:"win32"})},async restart({serviceID:t,definitionPath:r,runCommand:e=d,homeDir:n=p.homedir()}){await e("schtasks",["/End","/TN",t],{allowFailure:!0});const a=`${t}-wrapper.vbs`;await T(a,{platform:"win32"});const i=k(t,n);if(r?.startsWith("task:"))try{await e("schtasks",["/Run","/TN",t]);return}catch{}W("wscript.exe",["//B","//NoLogo",i])},async uninstall({serviceID:t,runCommand:r=d,homeDir:e=p.homedir()}){await r("schtasks",["/Delete","/TN",t,"/F"],{allowFailure:!0});const n=k(t,e);await w(n,{force:!0});const a=B(t,e);await w(a,{force:!0})},async discoverServices({homeDir:t=p.homedir(),runCommand:r=d}={}){const e=A("win32"),n=await r("schtasks",["/Query","/FO","CSV","/NH"],{allowFailure:!0}),a=[];if(Number(n?.exitCode??-1)===0){const o=String(n.stdout??"").split(/\r?\n/);for(const c of o){const l=c.match(/^"([^"]+)"/);if(!l)continue;const m=l[1];if(!m.startsWith(e))continue;let _=null;try{const x=k(m,t),D=(await f(x,"utf8")).match(/--config-dir\s+""([^""]+)""/);D&&(_=D[1])}catch{}a.push({serviceID:m,definitionPath:`task:${m}`,configDir:_})}}const i=u.join(t,"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup"),s=await C(i).catch(()=>[]);for(const o of s){if(!o.startsWith(e)||!o.endsWith(".lnk"))continue;const c=o.slice(0,-4);if(a.some(m=>m.serviceID===c))continue;let l=null;try{const m=k(c,t),x=(await f(m,"utf8")).match(/--config-dir\s+""([^""]+)""/);x&&(l=x[1])}catch{}a.push({serviceID:c,definitionPath:`startup:${c}`,configDir:l})}return a},async isServiceLoaded({serviceID:t,runCommand:r=d}){const e=await r("schtasks",["/Query","/TN",t,"/NH"],{allowFailure:!0});return Number(e?.exitCode??1)===0}}}function tt(){return{platform:"linux",kind:"systemd-user",async install({serviceID:t,nodePath:r,cliPath:e,configDir:n,stdoutPath:a,stderrPath:i,homeDir:s=p.homedir(),runCommand:o=d}){const c=H(t,s);return await P(u.dirname(c),{recursive:!0}),await h(c,Z({serviceID:t,nodePath:r,cliPath:e,configDir:n,stdoutPath:a,stderrPath:i}),{encoding:"utf8",mode:384}),await o("systemctl",["--user","daemon-reload"]),await o("systemctl",["--user","enable",`${t}.service`]),{definitionPath:c}},async start({serviceID:t,runCommand:r=d}){await r("systemctl",["--user","start",`${t}.service`])},async stop({serviceID:t,runCommand:r=d}){await r("systemctl",["--user","stop",`${t}.service`],{allowFailure:!0})},async restart({serviceID:t,runCommand:r=d}){await r("systemctl",["--user","restart",`${t}.service`])},async uninstall({serviceID:t,definitionPath:r,runCommand:e=d}){await e("systemctl",["--user","stop",`${t}.service`],{allowFailure:!0}),await e("systemctl",["--user","disable",`${t}.service`],{allowFailure:!0}),await w(r,{force:!0}),await e("systemctl",["--user","daemon-reload"])},async discoverServices({homeDir:t=p.homedir()}={}){const r=u.join(t,".config","systemd","user"),e=await C(r).catch(()=>[]),n=A("linux"),a=[];for(const i of e){if(!i.startsWith(n)||!i.endsWith(".service"))continue;const s=i.slice(0,-8),o=u.join(r,i);let c=null;try{const l=await f(o,"utf8");c=z(l)}catch{}a.push({serviceID:s,definitionPath:o,configDir:c})}return a},async isServiceLoaded({serviceID:t,runCommand:r=d}){const e=await r("systemctl",["--user","is-active",`${t}.service`],{allowFailure:!0});return String(e?.stdout??"").trim()==="active"}}}function S(t){try{const r=j(t,"utf8"),e=JSON.parse(r);if(e&&typeof e.service_id=="string"&&typeof e.node_path=="string"&&typeof e.cli_path=="string"&&typeof e.root_dir=="string")return e}catch{}return null}function rt(t){return u.join(t,"daemon.lock.json")}function N(t){const r=rt(t);try{const e=j(r,"utf8"),n=JSON.parse(e);if(typeof n?.pid=="number")return n.pid}catch{}return 0}function O(t){return u.join(t,".profile")}function et(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function nt(t){const r=[F(t.nodePath),F(t.cliPath)];t.configDir&&r.push("--config-dir",F(t.configDir));const e=r.join(" ");return`${e} status >/dev/null 2>&1 || (nohup ${e} start >/dev/null 2>&1 &)`}function at(t,r){return`# >>> grix-connector autostart [${t}] >>>
47
+ ${r}
48
+ # <<< grix-connector autostart [${t}] <<<
49
+ `}function U(t){const r=et(t);return new RegExp(`(?:^|\\n)# >>> grix-connector autostart \\[${r}\\] >>>\\n[\\s\\S]*?\\n# <<< grix-connector autostart \\[${r}\\] <<<\\n?`,"m")}async function it(t,r,e){let n="";try{n=await f(t,"utf8")}catch{}const a=at(r,e),i=U(r);let s;if(i.test(n))s=n.replace(i,`
50
+ ${a}`).replace(/^\n+/,"");else{const o=n.length===0||n.endsWith(`
51
+ `)?"":`
52
+ `;s=`${n}${o}${a}`}s!==n&&(await P(u.dirname(t),{recursive:!0}),await h(t,s,{encoding:"utf8"}))}async function ot(t,r){let e="";try{e=await f(t,"utf8")}catch{return}const n=U(r);if(!n.test(e))return;const a=e.replace(n,"").replace(/^\n+/,"");await h(t,a,{encoding:"utf8"})}function st(){return{platform:"linux",kind:"bare-daemon",async install({serviceID:t,nodePath:r,cliPath:e,configDir:n,stdoutPath:a,stderrPath:i,homeDir:s=p.homedir()}){const o=g(t,s);await P(u.dirname(o),{recursive:!0});const c=u.resolve(u.dirname(u.dirname(a||i||o))),l={schema_version:1,service_id:t,node_path:r,cli_path:e,config_dir:n??"",root_dir:c,stdout_path:a,stderr_path:i,installed_at:Date.now()};await h(o,`${JSON.stringify(l,null,2)}
53
+ `,{encoding:"utf8",mode:384});const m=nt({nodePath:r,cliPath:e,configDir:n??""});return await it(O(s),t,m),{definitionPath:o}},async start({serviceID:t,homeDir:r=p.homedir()}){const e=g(t,r),n=S(e);if(!n)throw new Error(`bare-daemon marker missing: ${e}`);const a=[n.cli_path];n.config_dir&&a.push("--config-dir",n.config_dir),W(n.node_path,a)},async stop({serviceID:t,homeDir:r=p.homedir()}){const e=g(t,r),n=S(e);if(!n)return;const a=N(n.root_dir);if(a>0&&v(a))try{process.kill(a,"SIGTERM")}catch{}},async restart(t){await this.stop(t);const r=Date.now()+5e3,e=g(t.serviceID,t.homeDir??p.homedir()),n=S(e);for(;n&&Date.now()<r;){const a=N(n.root_dir);if(a<=0||!v(a))break;await new Promise(i=>setTimeout(i,100))}await this.start(t)},async uninstall({serviceID:t,homeDir:r=p.homedir()}){const e=g(t,r),n=S(e);if(n){const a=N(n.root_dir);if(a>0&&v(a))try{process.kill(a,"SIGTERM")}catch{}}await w(e,{force:!0}),await ot(O(r),t)},async discoverServices({homeDir:t=p.homedir()}={}){const r=u.join(t,".grix","service"),e=await C(r).catch(()=>[]),n=A("linux"),a=[];for(const i of e){if(!i.startsWith(n)||!i.endsWith(".bare.json"))continue;const s=i.slice(0,-10),o=u.join(r,i),c=S(o);a.push({serviceID:s,definitionPath:o,configDir:c?.config_dir||null})}return a},async isServiceLoaded({serviceID:t,homeDir:r=p.homedir()}){const e=g(t,r),n=S(e);if(!n)return!1;const a=N(n.root_dir);return a>0&&v(a)}}}function ct(t=process.getuid?.()??0){if(process.env.GRIX_FORCE_BARE_DAEMON==="1")return!1;const r=process.env.XDG_RUNTIME_DIR;return!!(r&&R(u.join(r,"bus"))||R(`/run/user/${t}/bus`))}function wt(t=process.platform,r={}){return t==="darwin"?Y():t==="win32"?I():r.systemdUserAvailable??ct()?tt():st()}export{wt as getPlatformServiceAdapter,ct as isSystemdUserBusAvailable,ot as removeProfileAutostart,it as upsertProfileAutostart};
@@ -1,4 +1,4 @@
1
- import{createHash as w}from"node:crypto";import P from"node:fs";import S from"node:os";import c from"node:path";import m from"node:process";import{mkdir as p}from"node:fs/promises";import{setTimeout as _}from"node:timers/promises";import{resolveRuntimePaths as u}from"../core/config/paths.js";import{inspectDaemonState as l}from"../runtime/service-state.js";import{isProcessRunning as y,killOrphanedDaemonProcesses as F,runCommand as n,terminateProcessTree as L,waitForProcessExit as v}from"./process-control.js";import{readDaemonLock as g}from"../runtime/daemon-lock.js";import{ServiceInstallStore as z}from"./service-install-store.js";import{getPlatformServiceAdapter as R}from"./platform-adapter.js";import{buildServiceID as D,resolveServiceInstallRecordPath as b,resolveServiceStderrPath as I,resolveServiceStdoutPath as $}from"./service-paths.js";class G{adapter;platform;homeDir;uid;nodePath;cliPath;constructor(t){this.platform=t.platform??m.platform,this.homeDir=t.homeDir??S.homedir(),this.uid=t.uid??m.getuid?.()??0,this.nodePath=t.nodePath??m.execPath,this.cliPath=t.cliPath,this.adapter=R(this.platform)}normalizeRootDir(t){return c.resolve(String(t??"").trim())}buildDescriptor(t,i,r=""){const e=this.normalizeRootDir(t);return{schema_version:1,platform:this.platform,service_id:D(e,this.platform),node_path:this.nodePath,cli_path:this.cliPath,cli_hash:this.computeFileHash(this.cliPath)??"",definition_path:r,config_dir:i??"",installed_at:0,updated_at:0}}createStore(t){return new z(b(this.normalizeRootDir(t)))}toAdapterPayload(t){return{serviceID:t.service_id,nodePath:t.node_path,cliPath:t.cli_path,configDir:t.config_dir||void 0,environmentPath:this.buildServiceEnvironmentPath()}}async resolveServiceLogPaths(t,i){if(this.platform==="darwin"){const r=c.join(this.homeDir,"Library","Logs","grix-connector");return await p(r,{recursive:!0}),{stdoutPath:c.join(r,`${i}.out.log`),stderrPath:c.join(r,`${i}.err.log`)}}return await p(c.join(t,"service"),{recursive:!0}),{stdoutPath:$(t),stderrPath:I(t)}}buildServiceEnvironmentPath(){const t=[c.dirname(this.nodePath),"/opt/homebrew/bin","/usr/local/bin","/usr/bin","/bin","/usr/sbin","/sbin"],i=String(m.env.PATH??"").split(c.delimiter).filter(s=>s&&c.isAbsolute(s)).filter(s=>!s.includes("/node_modules/.bin")).filter(s=>!s.includes("/node-gyp-bin")),r=[],e=new Set;for(const s of[...t,...i]){if(e.has(s))continue;if(e.add(s),[...r,s].join(c.delimiter).length>2048)break;r.push(s)}return r.join(c.delimiter)}toStartParams(t){return{serviceID:t.service_id,definitionPath:t.definition_path,uid:this.uid,homeDir:this.homeDir,runCommand:n}}toStopParams(t){return{serviceID:t.service_id,uid:this.uid,homeDir:this.homeDir,runCommand:n}}toRestartParams(t){return{serviceID:t.service_id,definitionPath:t.definition_path,uid:this.uid,homeDir:this.homeDir,runCommand:n}}toUninstallParams(t){return{serviceID:t.service_id,definitionPath:t.definition_path,uid:this.uid,homeDir:this.homeDir,runCommand:n}}async loadDescriptor(t){const i=this.createStore(t),r=await i.load();if(!r)throw new Error("\u540E\u53F0\u670D\u52A1\u8FD8\u6CA1\u6709\u5B89\u88C5\uFF0C\u8BF7\u5148\u6267\u884C service install\u3002");return{store:i,descriptor:r}}isDescriptorCurrent(t){if(t.node_path!==this.nodePath||t.cli_path!==this.cliPath||!t.cli_hash)return!1;const i=this.computeFileHash(t.cli_path);return i!==null&&i===t.cli_hash}computeFileHash(t){try{const i=P.readFileSync(t);return w("sha256").update(i).digest("hex")}catch{return null}}async refreshDescriptor(t,i){const r=this.normalizeRootDir(t);await p(c.join(r,"service"),{recursive:!0});const e={...i,platform:this.platform,service_id:i.service_id||D(r,this.platform),node_path:this.nodePath,cli_path:this.cliPath,cli_hash:this.computeFileHash(this.cliPath)??"",config_dir:i.config_dir},s=await this.resolveServiceLogPaths(r,e.service_id),a=await this.adapter.install({...this.toAdapterPayload(e),stdoutPath:s.stdoutPath,stderrPath:s.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:n}),o={...e,installed_at:i.installed_at,definition_path:a?.definitionPath||e.definition_path,updated_at:Date.now()};return await this.createStore(r).save(o),o}async resolveActiveDescriptor(t){const{descriptor:i}=await this.loadDescriptor(t);return this.isDescriptorCurrent(i)?i:this.refreshDescriptor(t,i)}async discoverConflictingServices(t){const i=this.normalizeRootDir(t),r=D(i,this.platform);return(await this.adapter.discoverServices({homeDir:this.homeDir,runCommand:n})).filter(s=>s.serviceID!==r)}async cleanupConflictingServices(t){const i=await this.discoverConflictingServices(t);if(i.length>0){const e=i.map(s=>s.serviceID).join(", ");console.log(`\u53D1\u73B0 ${i.length} \u4E2A\u6B8B\u7559\u670D\u52A1\uFF0C\u6B63\u5728\u68C0\u67E5: ${e}`)}let r=0;for(const e of i){if(await this.adapter.isServiceLoaded({serviceID:e.serviceID,uid:this.uid,runCommand:n})){console.log(`\u8DF3\u8FC7\u6B63\u5728\u8FD0\u884C\u7684\u670D\u52A1: ${e.serviceID}`);continue}try{await this.adapter.stop({serviceID:e.serviceID,uid:this.uid,runCommand:n})}catch{}try{await this.adapter.uninstall({serviceID:e.serviceID,definitionPath:e.definitionPath,uid:this.uid,runCommand:n}),r++}catch{}}return r}async waitForDaemonStarted(t,i={}){const{oldPid:r=0,timeoutMs:e=2e4}=i,s=Date.now();for(;Date.now()-s<e;){const h=g(t.daemonLockFile);if(h&&h.pid>0&&y(h.pid)&&h.pid!==r)return;const f=l(t.daemonStatusFile);if(f.running&&f.pid>0&&f.pid!==r)return;await _(100)}const a=g(t.daemonLockFile),o=l(t.daemonStatusFile),d=[`daemon start timeout (${e}ms)`,`status=${JSON.stringify(o)}`,`lock=${a?JSON.stringify(a):"missing"}`,this.tailLogForError("stderr",i.stderrLogFile??t.stderrLogFile),this.tailLogForError("stdout",i.stdoutLogFile??t.stdoutLogFile)].filter(Boolean);throw new Error(d.join(`
2
- `))}tailLogForError(t,i,r=20){if(!P.existsSync(i))return`${t}=missing`;try{const e=P.readFileSync(i,"utf8").trim();if(!e)return`${t}=empty`;const s=e.split(/\r?\n/).slice(-r);return`${t}:
1
+ import{createHash as w}from"node:crypto";import D from"node:fs";import S from"node:os";import h from"node:path";import m from"node:process";import{mkdir as p}from"node:fs/promises";import{setTimeout as _}from"node:timers/promises";import{resolveRuntimePaths as u}from"../core/config/paths.js";import{inspectDaemonState as l}from"../runtime/service-state.js";import{isProcessRunning as y,killOrphanedDaemonProcesses as F,runCommand as n,terminateProcessTree as L,waitForProcessExit as v}from"./process-control.js";import{readDaemonLock as g}from"../runtime/daemon-lock.js";import{ServiceInstallStore as z}from"./service-install-store.js";import{getPlatformServiceAdapter as R}from"./platform-adapter.js";import{buildServiceID as P,resolveServiceInstallRecordPath as b,resolveServiceStderrPath as I,resolveServiceStdoutPath as $}from"./service-paths.js";class G{adapter;platform;homeDir;uid;nodePath;cliPath;constructor(t){this.platform=t.platform??m.platform,this.homeDir=t.homeDir??S.homedir(),this.uid=t.uid??m.getuid?.()??0,this.nodePath=t.nodePath??m.execPath,this.cliPath=t.cliPath,this.adapter=R(this.platform)}normalizeRootDir(t){return h.resolve(String(t??"").trim())}buildDescriptor(t,i,r=""){const e=this.normalizeRootDir(t);return{schema_version:1,platform:this.platform,service_id:P(e,this.platform),node_path:this.nodePath,cli_path:this.cliPath,cli_hash:this.computeFileHash(this.cliPath)??"",definition_path:r,config_dir:i??"",installed_at:0,updated_at:0}}createStore(t){return new z(b(this.normalizeRootDir(t)))}toAdapterPayload(t){return{serviceID:t.service_id,nodePath:t.node_path,cliPath:t.cli_path,configDir:t.config_dir||void 0,environmentPath:this.buildServiceEnvironmentPath()}}async resolveServiceLogPaths(t,i){if(this.platform==="darwin"){const r=h.join(this.homeDir,"Library","Logs","grix-connector");return await p(r,{recursive:!0}),{stdoutPath:h.join(r,`${i}.out.log`),stderrPath:h.join(r,`${i}.err.log`)}}return await p(h.join(t,"service"),{recursive:!0}),{stdoutPath:$(t),stderrPath:I(t)}}buildServiceEnvironmentPath(){const t=[h.dirname(this.nodePath),"/opt/homebrew/bin","/usr/local/bin","/usr/bin","/bin","/usr/sbin","/sbin"],i=String(m.env.PATH??"").split(h.delimiter).filter(s=>s&&h.isAbsolute(s)).filter(s=>!s.includes("/node_modules/.bin")).filter(s=>!s.includes("/node-gyp-bin")),r=[],e=new Set;for(const s of[...t,...i]){if(e.has(s))continue;if(e.add(s),[...r,s].join(h.delimiter).length>2048)break;r.push(s)}return r.join(h.delimiter)}toStartParams(t){return{serviceID:t.service_id,definitionPath:t.definition_path,uid:this.uid,homeDir:this.homeDir,runCommand:n}}toStopParams(t){return{serviceID:t.service_id,uid:this.uid,homeDir:this.homeDir,runCommand:n}}toRestartParams(t){return{serviceID:t.service_id,definitionPath:t.definition_path,uid:this.uid,homeDir:this.homeDir,runCommand:n}}toUninstallParams(t){return{serviceID:t.service_id,definitionPath:t.definition_path,uid:this.uid,homeDir:this.homeDir,runCommand:n}}async loadDescriptor(t){const i=this.createStore(t),r=await i.load();if(!r)throw new Error("\u540E\u53F0\u670D\u52A1\u8FD8\u6CA1\u6709\u5B89\u88C5\uFF0C\u8BF7\u5148\u6267\u884C service install\u3002");return{store:i,descriptor:r}}isDescriptorCurrent(t){if(t.node_path!==this.nodePath||t.cli_path!==this.cliPath||!t.cli_hash)return!1;const i=this.computeFileHash(t.cli_path);return i!==null&&i===t.cli_hash}computeFileHash(t){try{const i=D.readFileSync(t);return w("sha256").update(i).digest("hex")}catch{return null}}async refreshDescriptor(t,i){const r=this.normalizeRootDir(t);await p(h.join(r,"service"),{recursive:!0});const e={...i,platform:this.platform,service_id:i.service_id||P(r,this.platform),node_path:this.nodePath,cli_path:this.cliPath,cli_hash:this.computeFileHash(this.cliPath)??"",config_dir:i.config_dir},s=await this.resolveServiceLogPaths(r,e.service_id),a=await this.adapter.install({...this.toAdapterPayload(e),stdoutPath:s.stdoutPath,stderrPath:s.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:n}),o={...e,installed_at:i.installed_at,definition_path:a?.definitionPath||e.definition_path,updated_at:Date.now()};return await this.createStore(r).save(o),o}async resolveActiveDescriptor(t){const{descriptor:i}=await this.loadDescriptor(t);return this.isDescriptorCurrent(i)?i:this.refreshDescriptor(t,i)}async discoverConflictingServices(t){const i=this.normalizeRootDir(t),r=P(i,this.platform);return(await this.adapter.discoverServices({homeDir:this.homeDir,runCommand:n})).filter(s=>s.serviceID!==r)}async cleanupConflictingServices(t){const i=await this.discoverConflictingServices(t);if(i.length>0){const e=i.map(s=>s.serviceID).join(", ");console.log(`\u53D1\u73B0 ${i.length} \u4E2A\u6B8B\u7559\u670D\u52A1\uFF0C\u6B63\u5728\u68C0\u67E5: ${e}`)}let r=0;for(const e of i){if(await this.adapter.isServiceLoaded({serviceID:e.serviceID,uid:this.uid,homeDir:this.homeDir,runCommand:n})){console.log(`\u8DF3\u8FC7\u6B63\u5728\u8FD0\u884C\u7684\u670D\u52A1: ${e.serviceID}`);continue}try{await this.adapter.stop({serviceID:e.serviceID,uid:this.uid,homeDir:this.homeDir,runCommand:n})}catch{}try{await this.adapter.uninstall({serviceID:e.serviceID,definitionPath:e.definitionPath,uid:this.uid,homeDir:this.homeDir,runCommand:n}),r++}catch{}}return r}async waitForDaemonStarted(t,i={}){const{oldPid:r=0,timeoutMs:e=2e4}=i,s=Date.now();for(;Date.now()-s<e;){const c=g(t.daemonLockFile);if(c&&c.pid>0&&y(c.pid)&&c.pid!==r)return;const f=l(t.daemonStatusFile);if(f.running&&f.pid>0&&f.pid!==r)return;await _(100)}const a=g(t.daemonLockFile),o=l(t.daemonStatusFile),d=[`daemon start timeout (${e}ms)`,`status=${JSON.stringify(o)}`,`lock=${a?JSON.stringify(a):"missing"}`,this.tailLogForError("stderr",i.stderrLogFile??t.stderrLogFile),this.tailLogForError("stdout",i.stdoutLogFile??t.stdoutLogFile)].filter(Boolean);throw new Error(d.join(`
2
+ `))}tailLogForError(t,i,r=20){if(!D.existsSync(i))return`${t}=missing`;try{const e=D.readFileSync(i,"utf8").trim();if(!e)return`${t}=empty`;const s=e.split(/\r?\n/).slice(-r);return`${t}:
3
3
  ${s.join(`
4
- `)}`}catch(e){return`${t}=unreadable: ${e instanceof Error?e.message:String(e)}`}}async install(t){const i=this.normalizeRootDir(t.rootDir);await this.cleanupConflictingServices(i),await p(c.join(i,"service"),{recursive:!0});const r=this.buildDescriptor(i,t.configDir),e=await this.resolveServiceLogPaths(i,r.service_id),s=await this.adapter.install({...this.toAdapterPayload(r),stdoutPath:e.stdoutPath,stderrPath:e.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:n}),a={...r,definition_path:s?.definitionPath??"",installed_at:Date.now(),updated_at:Date.now()};await this.createStore(i).save(a),await this.adapter.start(this.toStartParams(a));const o=u(i);return await this.waitForDaemonStarted(o,{stdoutLogFile:e.stdoutPath,stderrLogFile:e.stderrPath}),this.status({rootDir:i})}async start(t){const i=this.normalizeRootDir(t.rootDir);await this.cleanupConflictingServices(i);const r=u(i),e=l(r.daemonStatusFile),a=await this.createStore(i).load();if(e.running&&a&&this.isDescriptorCurrent(a))return this.status({rootDir:i});const o=a?this.isDescriptorCurrent(a)&&e.running?a:await this.refreshDescriptor(i,a):await this.resolveActiveDescriptor(i),d=await this.resolveServiceLogPaths(i,o.service_id);try{e.running?await this.adapter.restart(this.toRestartParams(o)):await this.adapter.start(this.toStartParams(o))}catch(h){if(h instanceof Error&&h.message.includes("plist missing"))await this.adapter.install({...this.toAdapterPayload(o),stdoutPath:d.stdoutPath,stderrPath:d.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:n}),await this.adapter.start(this.toStartParams(o));else throw h}return await this.waitForDaemonStarted(r,{oldPid:e.pid,stdoutLogFile:d.stdoutPath,stderrLogFile:d.stderrPath}),this.status({rootDir:i})}async stop(t){const{descriptor:i}=await this.loadDescriptor(t.rootDir),r=u(i.config_dir||this.normalizeRootDir(t.rootDir)),e=l(r.daemonStatusFile);await this.adapter.stop(this.toStopParams(i)),e.running&&e.pid&&(await v(e.pid,{timeoutMs:5e3})||(await L(e.pid,{platform:this.platform,runCommandImpl:n}),await v(e.pid,{timeoutMs:5e3})));const s=await F(i.cli_path);return s>0&&console.log(`\u6E05\u7406\u4E86 ${s} \u4E2A\u6B8B\u7559 daemon \u8FDB\u7A0B`),this.status({rootDir:t.rootDir})}async restart(t){const i=this.normalizeRootDir(t.rootDir);await this.cleanupConflictingServices(i);const r=await this.resolveActiveDescriptor(i),e=u(i),s=l(e.daemonStatusFile),a=await this.resolveServiceLogPaths(i,r.service_id);try{await this.adapter.restart(this.toRestartParams(r))}catch(o){if(o instanceof Error&&o.message.includes("plist missing"))await this.adapter.install({...this.toAdapterPayload(r),stdoutPath:a.stdoutPath,stderrPath:a.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:n}),await this.adapter.start(this.toStartParams(r));else throw o}return await this.waitForDaemonStarted(e,{oldPid:s.pid,stdoutLogFile:a.stdoutPath,stderrLogFile:a.stderrPath}),this.status({rootDir:i})}async uninstall(t){const i=this.normalizeRootDir(t.rootDir),r=this.createStore(i),e=await r.load();return e&&(await this.adapter.uninstall(this.toUninstallParams(e)),await r.clear()),this.status({rootDir:i})}async status(t){const i=this.normalizeRootDir(t.rootDir),e=await this.createStore(i).load(),s=u(i),a=l(s.daemonStatusFile);return e?{installed:!0,install_state:this.isDescriptorCurrent(e)?"current":"stale",service_kind:this.adapter.kind,service_id:e.service_id,definition_path:e.definition_path,root_dir:i,daemon_state:a.running?"running":a.state,pid:a.pid,connection_state:a.connection_state,updated_at:a.updated_at}:{installed:!1,install_state:"missing",service_kind:this.adapter.kind,root_dir:i,daemon_state:a.running?"running":a.state,pid:a.pid}}}export{G as ServiceManager};
4
+ `)}`}catch(e){return`${t}=unreadable: ${e instanceof Error?e.message:String(e)}`}}async install(t){const i=this.normalizeRootDir(t.rootDir);await this.cleanupConflictingServices(i),await p(h.join(i,"service"),{recursive:!0});const r=this.buildDescriptor(i,t.configDir),e=await this.resolveServiceLogPaths(i,r.service_id),s=await this.adapter.install({...this.toAdapterPayload(r),stdoutPath:e.stdoutPath,stderrPath:e.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:n}),a={...r,definition_path:s?.definitionPath??"",installed_at:Date.now(),updated_at:Date.now()};await this.createStore(i).save(a),await this.adapter.start(this.toStartParams(a));const o=u(i);return await this.waitForDaemonStarted(o,{stdoutLogFile:e.stdoutPath,stderrLogFile:e.stderrPath}),this.status({rootDir:i})}async start(t){const i=this.normalizeRootDir(t.rootDir);await this.cleanupConflictingServices(i);const r=u(i),e=l(r.daemonStatusFile),a=await this.createStore(i).load();if(e.running&&a&&this.isDescriptorCurrent(a))return this.status({rootDir:i});const o=a?this.isDescriptorCurrent(a)&&e.running?a:await this.refreshDescriptor(i,a):await this.resolveActiveDescriptor(i),d=await this.resolveServiceLogPaths(i,o.service_id);try{e.running?await this.adapter.restart(this.toRestartParams(o)):await this.adapter.start(this.toStartParams(o))}catch(c){if(c instanceof Error&&c.message.includes("plist missing"))await this.adapter.install({...this.toAdapterPayload(o),stdoutPath:d.stdoutPath,stderrPath:d.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:n}),await this.adapter.start(this.toStartParams(o));else throw c}return await this.waitForDaemonStarted(r,{oldPid:e.pid,stdoutLogFile:d.stdoutPath,stderrLogFile:d.stderrPath}),this.status({rootDir:i})}async stop(t){const{descriptor:i}=await this.loadDescriptor(t.rootDir),r=u(i.config_dir||this.normalizeRootDir(t.rootDir)),e=l(r.daemonStatusFile);await this.adapter.stop(this.toStopParams(i)),e.running&&e.pid&&(await v(e.pid,{timeoutMs:5e3})||(await L(e.pid,{platform:this.platform,runCommandImpl:n}),await v(e.pid,{timeoutMs:5e3})));const s=await F(i.cli_path);return s>0&&console.log(`\u6E05\u7406\u4E86 ${s} \u4E2A\u6B8B\u7559 daemon \u8FDB\u7A0B`),this.status({rootDir:t.rootDir})}async restart(t){const i=this.normalizeRootDir(t.rootDir);await this.cleanupConflictingServices(i);const r=await this.resolveActiveDescriptor(i),e=u(i),s=l(e.daemonStatusFile),a=await this.resolveServiceLogPaths(i,r.service_id);try{await this.adapter.restart(this.toRestartParams(r))}catch(o){if(o instanceof Error&&o.message.includes("plist missing"))await this.adapter.install({...this.toAdapterPayload(r),stdoutPath:a.stdoutPath,stderrPath:a.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:n}),await this.adapter.start(this.toStartParams(r));else throw o}return await this.waitForDaemonStarted(e,{oldPid:s.pid,stdoutLogFile:a.stdoutPath,stderrLogFile:a.stderrPath}),this.status({rootDir:i})}async uninstall(t){const i=this.normalizeRootDir(t.rootDir),r=this.createStore(i),e=await r.load();return e&&(await this.adapter.uninstall(this.toUninstallParams(e)),await r.clear()),this.status({rootDir:i})}async status(t){const i=this.normalizeRootDir(t.rootDir),e=await this.createStore(i).load(),s=u(i),a=l(s.daemonStatusFile);return e?{installed:!0,install_state:this.isDescriptorCurrent(e)?"current":"stale",service_kind:this.adapter.kind,service_id:e.service_id,definition_path:e.definition_path,root_dir:i,daemon_state:a.running?"running":a.state,pid:a.pid,connection_state:a.connection_state,updated_at:a.updated_at}:{installed:!1,install_state:"missing",service_kind:this.adapter.kind,root_dir:i,daemon_state:a.running?"running":a.state,pid:a.pid}}}export{G as ServiceManager};
@@ -1 +1 @@
1
- import{createHash as u}from"node:crypto";import o from"node:os";import t from"node:path";import{normalizeString as a}from"../core/util/normalize-string.js";const i="com.dhfpub.grix-connector.daemon.",c="grix-connector-daemon-",s="GrixConnectorDaemon-";function f(r){return u("sha1").update(a(r)||"default").digest("hex").slice(0,12)}function x(r,e=process.platform){const n=f(r);return e==="win32"?`${s}${n}`:e==="darwin"?`${i}${n}`:`${c}${n}`}function h(r=process.platform){return r==="win32"?s:r==="darwin"?i:c}function l(r){return r.replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"')}function v(r){const e=[...r.matchAll(/<string>([^<]+)<\/string>/g)].map(n=>l(n[1]));for(let n=0;n<e.length-1;n+=1)if(e[n]==="--config-dir")return e[n+1];return null}function S(r){const e=r.match(/--config-dir\s+(?:'([^']*)'|"([^"]*)"|(\S+))/);return e?e[1]??e[2]??e[3]??null:null}function I(r){return t.join(r,"daemon-service.json")}function P(r){return t.join(r,"service","daemon.out.log")}function E(r){return t.join(r,"service","daemon.err.log")}function R(r,e){return t.join(e??o.homedir(),"Library","LaunchAgents",`${r}.plist`)}function $(r,e){return t.join(e??o.homedir(),".config","systemd","user",`${r}.service`)}export{i as SERVICE_PREFIX_DARWIN,c as SERVICE_PREFIX_LINUX,s as SERVICE_PREFIX_WIN32,x as buildServiceID,h as getServicePrefix,v as parseConfigDirFromPlistXML,S as parseConfigDirFromSystemdUnit,$ as resolveLinuxUserUnitPath,R as resolveMacOSLaunchAgentPath,I as resolveServiceInstallRecordPath,E as resolveServiceStderrPath,P as resolveServiceStdoutPath};
1
+ import{createHash as u}from"node:crypto";import o from"node:os";import t from"node:path";import{normalizeString as a}from"../core/util/normalize-string.js";const i="com.dhfpub.grix-connector.daemon.",c="grix-connector-daemon-",s="GrixConnectorDaemon-";function f(r){return u("sha1").update(a(r)||"default").digest("hex").slice(0,12)}function x(r,e=process.platform){const n=f(r);return e==="win32"?`${s}${n}`:e==="darwin"?`${i}${n}`:`${c}${n}`}function h(r=process.platform){return r==="win32"?s:r==="darwin"?i:c}function l(r){return r.replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"')}function v(r){const e=[...r.matchAll(/<string>([^<]+)<\/string>/g)].map(n=>l(n[1]));for(let n=0;n<e.length-1;n+=1)if(e[n]==="--config-dir")return e[n+1];return null}function S(r){const e=r.match(/--config-dir\s+(?:'([^']*)'|"([^"]*)"|(\S+))/);return e?e[1]??e[2]??e[3]??null:null}function I(r){return t.join(r,"daemon-service.json")}function P(r){return t.join(r,"service","daemon.out.log")}function E(r){return t.join(r,"service","daemon.err.log")}function $(r,e){return t.join(e??o.homedir(),"Library","LaunchAgents",`${r}.plist`)}function j(r,e){return t.join(e??o.homedir(),".config","systemd","user",`${r}.service`)}function R(r,e){return t.join(e??o.homedir(),".grix","service",`${r}.bare.json`)}export{i as SERVICE_PREFIX_DARWIN,c as SERVICE_PREFIX_LINUX,s as SERVICE_PREFIX_WIN32,x as buildServiceID,h as getServicePrefix,v as parseConfigDirFromPlistXML,S as parseConfigDirFromSystemdUnit,R as resolveBareDaemonMarkerPath,j as resolveLinuxUserUnitPath,$ as resolveMacOSLaunchAgentPath,I as resolveServiceInstallRecordPath,E as resolveServiceStderrPath,P as resolveServiceStdoutPath};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grix-connector",
3
- "version": "2.1.4",
3
+ "version": "2.2.0",
4
4
  "description": "Connect local AI coding agents (Claude, Codex, Gemini, Qwen, DeepSeek, Cursor, OpenCode, Pi, OpenHuman, Reasonix) to the Grix scheduling platform. Also serves as an OpenClaw plugin for Grix channel transport.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",