ohwow 0.10.0 → 0.11.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,11 +1,17 @@
1
1
  import { createRequire } from 'module'; const require = createRequire(import.meta.url);
2
- import{McpServer as hr}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as fr}from"@modelcontextprotocol/sdk/server/stdio.js";import{readFileSync as be,existsSync as _e}from"fs";import{join as xe}from"path";import{readFileSync as we,writeFileSync as Et,existsSync as F,mkdirSync as St,readdirSync as Ot}from"fs";import{dirname as xr,join as v}from"path";import{homedir as Rt}from"os";import vt from"pino";var V=vt({level:process.env.LOG_LEVEL||(process.env.NODE_ENV==="production"?"info":"debug"),...process.env.NODE_ENV!=="production"&&{transport:{target:"pino-pretty",options:{colorize:!0}}}});var I=v(Rt(),".ohwow"),Ct=v(I,"config.json"),Er=v(I,"data","runtime.db"),J=7700;var z="default",Q=v(I,"workspaces"),fe=v(I,"current-workspace"),H=v(I,"data");function Z(o){return/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(o)}function U(o){let n=v(Q,o),t=v(n,"skills");return{name:o,dataDir:n,dbPath:v(n,"runtime.db"),skillsDir:t,compiledSkillsDir:v(t,".compiled")}}function Tt(){if(!F(fe))return null;try{return we(fe,"utf-8").trim()||null}catch{return null}}function Ue(o){F(I)||St(I,{recursive:!0}),Et(fe,o)}function Ie(){if(!F(Q))return[];try{return Ot(Q,{withFileTypes:!0}).filter(o=>o.isDirectory()).map(o=>o.name).sort()}catch{return[]}}function W(){let o=process.env.OHWOW_WORKSPACE?.trim();if(o&&Z(o))return U(o);let n=Tt();if(n&&Z(n))return U(n);let t=U(z);if(!F(t.dataDir)&&F(v(H,"runtime.db"))){let e=v(H,"skills");return{name:z,dataDir:H,dbPath:v(H,"runtime.db"),skillsDir:e,compiledSkillsDir:v(e,".compiled")}}return t}function Dt(o){return v(Q,o,"workspace.json")}function ye(o){let n=Dt(o);if(!F(n))return null;try{let t=we(n,"utf-8");return JSON.parse(t)}catch(t){return V.warn(`[Config] Failed to parse ${n}: ${t}`),null}}function $(o){if(o===z)try{let t=we(Ct,"utf-8");return JSON.parse(t).port??J}catch{return J}return ye(o)?.port??null}var ee=class o{constructor(n,t,e){this.baseUrl=`http://127.0.0.1:${n}`,this.token=t,this.tokenPath=e}static async create(){let n=W(),t=$(n.name)??J,e=xe(n.dataDir,"daemon.token");if(!_e(e)){let a=xe(H,"daemon.token");if(_e(a))e=a;else throw new Error(`OHWOW process is not running for workspace "${n.name}". Start it with: ohwow workspace start ${n.name}`)}let r=be(e,"utf-8").trim();if(!r)throw new Error(`Couldn't read process token for workspace "${n.name}". Try: ohwow workspace restart ${n.name}`);let s=new o(t,r,e);try{await s.get("/health")}catch{throw new Error(`OHWOW process for workspace "${n.name}" is not reachable on port ${t}. Start it with: ohwow workspace start ${n.name}`)}return s}async switchTo(n){if(!Z(n))throw new Error(`Invalid workspace name: ${n}`);let t=U(n),e=$(n);if(e===null)throw new Error(`Workspace "${n}" has no port assigned yet. Start it once with: ohwow workspace start ${n}`);let r=xe(t.dataDir,"daemon.token");if(!_e(r))throw new Error(`Daemon for workspace "${n}" is not running. Start it with: ohwow workspace start ${n}`);let s=be(r,"utf-8").trim();if(!s)throw new Error(`Empty process token for workspace "${n}"`);let a=`http://127.0.0.1:${e}`;try{let i=await fetch(`${a}/health`,{headers:{Authorization:`Bearer ${s}`},signal:AbortSignal.timeout(5e3)});if(!i.ok)throw new Error(`HTTP ${i.status}`)}catch(i){throw new Error(`Daemon for "${n}" not responding on port ${e}: ${i instanceof Error?i.message:i}`)}return this.baseUrl=a,this.token=s,this.tokenPath=r,Ue(n),process.env.OHWOW_WORKSPACE=n,{port:e,workspaceName:n}}refreshToken(){try{let n=be(this.tokenPath,"utf-8").trim();if(n&&n!==this.token)return this.token=n,!0}catch{}return!1}authHeaders(){return{Authorization:`Bearer ${this.token}`}}async fetchWithRetry(n,t){let e=await fetch(n,t);if(e.status===401&&this.refreshToken()){let r={...t.headers,...this.authHeaders()};e=await fetch(n,{...t,headers:r})}return e}async get(n){let t=await this.fetchWithRetry(`${this.baseUrl}${n}`,{headers:this.authHeaders()});if(!t.ok)throw new Error(`ohwow process error on GET ${n}: ${t.status}. Check with: ohwow logs`);return t.json()}async post(n,t){let e=await this.fetchWithRetry(`${this.baseUrl}${n}`,{method:"POST",headers:{...this.authHeaders(),"Content-Type":"application/json"},body:JSON.stringify(t)});if(!e.ok)throw new Error(`ohwow process error on POST ${n}: ${e.status}. Check with: ohwow logs`);return e.json()}async del(n){let t=await this.fetchWithRetry(`${this.baseUrl}${n}`,{method:"DELETE",headers:this.authHeaders()});if(!t.ok)throw new Error(`ohwow process error on DELETE ${n}: ${t.status}. Check with: ohwow logs`);return t.json()}async patch(n,t){let e=await this.fetchWithRetry(`${this.baseUrl}${n}`,{method:"PATCH",headers:{...this.authHeaders(),"Content-Type":"application/json"},body:JSON.stringify(t)});if(!e.ok)throw new Error(`ohwow process error on PATCH ${n}: ${e.status}. Check with: ohwow logs`);return e.json()}async postSSE(n,t,e=12e4){let r=new AbortController,s=setTimeout(()=>r.abort(),e),a=[],i=async()=>fetch(`${this.baseUrl}${n}`,{method:"POST",headers:{...this.authHeaders(),"Content-Type":"application/json",Accept:"text/event-stream"},body:JSON.stringify(t),signal:r.signal});try{let c=await i();if(c.status===401&&this.refreshToken()&&(c=await i()),!c.ok)throw new Error(`Daemon API error: ${c.status} ${c.statusText}`);if(!c.body)throw new Error("No response body from daemon");let l=c.body.getReader(),p=new TextDecoder,u="";for(;;){let{done:f,value:E}=await l.read();if(f)break;u+=p.decode(E,{stream:!0});let y=u.split(`
3
- `);u=y.pop()||"";for(let S of y){if(!S.startsWith("data: "))continue;let j=S.slice(6).trim();if(j!=="[DONE]")try{let O=JSON.parse(j);if(O.type==="text"&&O.content)a.push(O.content);else if(O.type==="tool_start")a.push(`
2
+ import{McpServer as br}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as _r}from"@modelcontextprotocol/sdk/server/stdio.js";import{readFileSync as _e,existsSync as xe}from"fs";import{join as ke}from"path";import{readFileSync as ye,writeFileSync as Et,existsSync as H,mkdirSync as St,readdirSync as Ot}from"fs";import{dirname as Sr,join as S}from"path";import{homedir as Rt}from"os";import vt from"pino";var V=vt({level:process.env.LOG_LEVEL||(process.env.NODE_ENV==="production"?"info":"debug"),...process.env.NODE_ENV!=="production"&&{transport:{target:"pino-pretty",options:{colorize:!0}}}});var $=S(Rt(),".ohwow"),Ct=S($,"config.json"),Cr=S($,"data","runtime.db"),J=7700;var z="default",Q=S($,"workspaces"),we=S($,"current-workspace"),F=S($,"data");function Z(n){return/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(n)}function I(n){let o=S(Q,n),t=S(o,"skills");return{name:n,dataDir:o,dbPath:S(o,"runtime.db"),skillsDir:t,compiledSkillsDir:S(t,".compiled")}}function Dt(){if(!H(we))return null;try{return ye(we,"utf-8").trim()||null}catch{return null}}function Ie(n){H($)||St($,{recursive:!0}),Et(we,n)}function $e(){if(!H(Q))return[];try{return Ot(Q,{withFileTypes:!0}).filter(n=>n.isDirectory()).map(n=>n.name).sort()}catch{return[]}}function W(){let n=process.env.OHWOW_WORKSPACE?.trim();if(n&&Z(n))return I(n);let o=Dt();if(o&&Z(o))return I(o);let t=I(z);if(!H(t.dataDir)&&H(S(F,"runtime.db"))){let e=S(F,"skills");return{name:z,dataDir:F,dbPath:S(F,"runtime.db"),skillsDir:e,compiledSkillsDir:S(e,".compiled")}}return t}function Tt(n){return S(Q,n,"workspace.json")}function be(n){let o=Tt(n);if(!H(o))return null;try{let t=ye(o,"utf-8");return JSON.parse(t)}catch(t){return V.warn(`[Config] Failed to parse ${o}: ${t}`),null}}function N(n){if(n===z)try{let t=ye(Ct,"utf-8");return JSON.parse(t).port??J}catch{return J}return be(n)?.port??null}var ee=class n{constructor(o,t,e){this.baseUrl=`http://127.0.0.1:${o}`,this.token=t,this.tokenPath=e}static async create(){let o=W(),t=N(o.name)??J,e=ke(o.dataDir,"daemon.token");if(!xe(e)){let a=ke(F,"daemon.token");if(xe(a))e=a;else throw new Error(`OHWOW process is not running for workspace "${o.name}". Start it with: ohwow workspace start ${o.name}`)}let r=_e(e,"utf-8").trim();if(!r)throw new Error(`Couldn't read process token for workspace "${o.name}". Try: ohwow workspace restart ${o.name}`);let s=new n(t,r,e);try{await s.get("/health")}catch{throw new Error(`OHWOW process for workspace "${o.name}" is not reachable on port ${t}. Start it with: ohwow workspace start ${o.name}`)}return s}async switchTo(o){if(!Z(o))throw new Error(`Invalid workspace name: ${o}`);let t=I(o),e=N(o);if(e===null)throw new Error(`Workspace "${o}" has no port assigned yet. Start it once with: ohwow workspace start ${o}`);let r=ke(t.dataDir,"daemon.token");if(!xe(r))throw new Error(`Daemon for workspace "${o}" is not running. Start it with: ohwow workspace start ${o}`);let s=_e(r,"utf-8").trim();if(!s)throw new Error(`Empty process token for workspace "${o}"`);let a=`http://127.0.0.1:${e}`;try{let i=await fetch(`${a}/health`,{headers:{Authorization:`Bearer ${s}`},signal:AbortSignal.timeout(5e3)});if(!i.ok)throw new Error(`HTTP ${i.status}`)}catch(i){throw new Error(`Daemon for "${o}" not responding on port ${e}: ${i instanceof Error?i.message:i}`)}return this.baseUrl=a,this.token=s,this.tokenPath=r,Ie(o),process.env.OHWOW_WORKSPACE=o,{port:e,workspaceName:o}}refreshToken(){try{let o=_e(this.tokenPath,"utf-8").trim();if(o&&o!==this.token)return this.token=o,!0}catch{}return!1}authHeaders(){return{Authorization:`Bearer ${this.token}`}}async fetchWithRetry(o,t){let e=await fetch(o,t);if(e.status===401&&this.refreshToken()){let r={...t.headers,...this.authHeaders()};e=await fetch(o,{...t,headers:r})}return e}async get(o){let t=await this.fetchWithRetry(`${this.baseUrl}${o}`,{headers:this.authHeaders()});if(!t.ok)throw new Error(`ohwow process error on GET ${o}: ${t.status}. Check with: ohwow logs`);return t.json()}async post(o,t){let e=await this.fetchWithRetry(`${this.baseUrl}${o}`,{method:"POST",headers:{...this.authHeaders(),"Content-Type":"application/json"},body:JSON.stringify(t)});if(!e.ok)throw new Error(`ohwow process error on POST ${o}: ${e.status}. Check with: ohwow logs`);return e.json()}async del(o){let t=await this.fetchWithRetry(`${this.baseUrl}${o}`,{method:"DELETE",headers:this.authHeaders()});if(!t.ok)throw new Error(`ohwow process error on DELETE ${o}: ${t.status}. Check with: ohwow logs`);return t.json()}async patch(o,t){let e=await this.fetchWithRetry(`${this.baseUrl}${o}`,{method:"PATCH",headers:{...this.authHeaders(),"Content-Type":"application/json"},body:JSON.stringify(t)});if(!e.ok)throw new Error(`ohwow process error on PATCH ${o}: ${e.status}. Check with: ohwow logs`);return e.json()}async postSSE(o,t,e=12e4){let r=new AbortController,s=setTimeout(()=>r.abort(),e),a=[],i=async()=>fetch(`${this.baseUrl}${o}`,{method:"POST",headers:{...this.authHeaders(),"Content-Type":"application/json",Accept:"text/event-stream"},body:JSON.stringify(t),signal:r.signal});try{let c=await i();if(c.status===401&&this.refreshToken()&&(c=await i()),!c.ok)throw new Error(`Daemon API error: ${c.status} ${c.statusText}`);if(!c.body)throw new Error("No response body from daemon");let l=c.body.getReader(),p=new TextDecoder,u="";for(;;){let{done:h,value:k}=await l.read();if(h)break;u+=p.decode(k,{stream:!0});let b=u.split(`
3
+ `);u=b.pop()||"";for(let v of b){if(!v.startsWith("data: "))continue;let M=v.slice(6).trim();if(M!=="[DONE]")try{let O=JSON.parse(M);if(O.type==="text"&&O.content)a.push(O.content);else if(O.type==="tool_start")a.push(`
4
4
  [Using tool: ${O.name}]
5
5
  `);else if(O.type==="error")a.push(`
6
6
  [Error: ${O.error}]
7
7
  `);else if(O.type==="done")break}catch{}}}return a.join("")}catch(c){if(c instanceof Error&&c.name==="AbortError")return a.join("")+`
8
- [Timed out after ${e/1e3}s. The task may still be running. Check with ohwow_list_tasks.]`;throw c}finally{clearTimeout(s)}}};import{z as m}from"zod";function We(o,n){o.tool("ohwow_chat",'[Orchestrator] Send a message to the OHWOW orchestrator (88+ internal tools). Returns conversationId immediately and dispatches the turn in the background. Poll ohwow_get_chat until status !== "running" to read the final assistant message. Use this for: desktop control, automation creation, agent scheduling, approval management, agent state persistence, A2A protocol, PDF forms, media generation, and any multi-step request not covered by the direct tools. Do NOT use for simple listing or CRUD operations that have dedicated tools.',{message:m.string().describe("The message or instruction to send to the orchestrator"),sessionId:m.string().optional().describe("Optional conversation id to continue an existing session. Omit for a new conversation.")},async({message:t,sessionId:e})=>{try{let r=await n.post("/api/chat?async=1",{message:t,sessionId:e});return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_get_chat",'[Orchestrator] Poll an in-flight or completed orchestrator conversation by id. Returns { status, messages, last_error, ... }. Status: "running" = still in flight, keep polling. "done" = final assistant message is in messages[]. "error" = look at last_error. Use after ohwow_chat to wait for the turn to complete.',{conversationId:m.string().describe("The conversation id returned by ohwow_chat")},async({conversationId:t})=>{try{let e=await n.get(`/api/chat/${t}`);return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_list_agents","[Agents] List all agents in the OHWOW workspace with their status, role, and capabilities.",{},async()=>{try{let t=await n.get("/api/agents"),e=t.data||t;return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_run_agent","[Agents] Execute a specific agent with a prompt. Returns a task ID immediately (execution is async). Use ohwow_get_task to poll for status and result. Use ohwow_list_agents to find agent IDs.",{agentId:m.string().describe("The ID of the agent to run"),prompt:m.string().describe("The task or instruction for the agent")},async({agentId:t,prompt:e})=>{try{let r=await n.post("/api/tasks",{agentId:t,title:e});return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_get_task","[Tasks] Get the status and result of a task by its ID.",{taskId:m.string().describe("The task ID to look up")},async({taskId:t})=>{try{let e=await n.get(`/api/tasks/${t}`);return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_list_tasks","[Tasks] List recent tasks. Optionally filter by status or agent.",{status:m.string().optional().describe("Filter by status: pending, running, completed, failed"),agentId:m.string().optional().describe("Filter by agent ID"),limit:m.number().optional().describe("Max number of tasks to return (default: 20)")},async({status:t,agentId:e,limit:r})=>{try{let s=new URLSearchParams;t&&s.set("status",t),e&&s.set("agentId",e),r&&s.set("limit",String(r));let a=s.toString(),i=await n.get(`/api/tasks${a?`?${a}`:""}`);return{content:[{type:"text",text:JSON.stringify(i,null,2)}]}}catch(s){return{content:[{type:"text",text:`Error: ${s instanceof Error?s.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_workspace_status","[Workspace] Get workspace status: agent count, uptime, tier, and system stats.",{},async()=>{try{let t=await n.get("/api/dashboard/init");return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_llm","[LLM Organ] Invoke ohwow's model router for a specific sub-task. Pass a `purpose` (reasoning, generation, summarization, extraction, critique, translation, planning, classification, etc.) and either a `prompt` string OR a `messages` history array. Optionally pass `tools` to enable tool calls. ohwow resolves the agent's model_policy (if agentId is given), workspace defaults, and constraints, then picks a provider+model and runs the call. Returns { text, content: ContentBlock[], model_used, provider, purpose, tokens, cost_cents, latency_ms }. The caller is responsible for executing tool calls and looping by appending tool_result blocks to `messages` on the next call.",{purpose:m.enum(["orchestrator_chat","agent_task","planning","browser_automation","memory_extraction","ocr","workflow_step","simple_classification","desktop_control","reasoning","generation","summarization","extraction","critique","translation","embedding"]).optional().describe('Semantic purpose that drives routing. Defaults to "reasoning".'),prompt:m.string().optional().describe("The user prompt to send to the selected model. Omit when passing `messages` instead."),messages:m.array(m.object({role:m.enum(["user","assistant","system","tool"]),content:m.union([m.string(),m.array(m.record(m.string(),m.unknown()))]),tool_calls:m.array(m.object({id:m.string(),type:m.literal("function"),function:m.object({name:m.string(),arguments:m.string()})})).optional(),tool_call_id:m.string().optional()})).optional().describe("Multi-turn conversation history. Preferred over `prompt` for tool-use loops \u2014 append assistant tool_calls and user tool_result blocks each iteration."),tools:m.array(m.object({name:m.string(),description:m.string().optional(),input_schema:m.record(m.string(),m.unknown()).optional()})).optional().describe("Optional tool definitions. When non-empty, the response `content` array includes `tool_use` blocks the caller must execute and feed back via `messages`."),tool_choice:m.enum(["auto","required","none"]).optional().describe("Tool selection mode. Defaults to provider default."),system:m.string().optional().describe("Optional system prompt."),agentId:m.string().optional().describe("Agent ID to load model_policy from. Omit to use workspace defaults only."),max_tokens:m.number().optional().describe("Maximum output tokens."),temperature:m.number().optional().describe("Sampling temperature."),local_only:m.boolean().optional().describe("Force local inference (clamps modelSource to local)."),prefer_model:m.string().optional().describe("Call-site model override; wins over agent policy."),max_cost_cents:m.number().optional().describe("Advisory cost ceiling in cents."),difficulty:m.enum(["simple","moderate","complex"]).optional().describe("Difficulty hint for escalation.")},async t=>{try{let e=await n.post("/api/llm",t);return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as R}from"zod";function $e(o,n){o.tool("ohwow_list_contacts","[CRM] List contacts in the workspace. Returns id, name, email, phone, company, contact_type (lead/customer/partner), status, tags, and notes. Use the `search` param for quick filtering by name/email/company. For deeper full-text search across all fields including notes, use ohwow_search_contacts instead.",{search:R.string().optional().describe("Filter by name, email, or company"),limit:R.number().optional().describe("Max results (default: 50)")},async({search:t,limit:e})=>{try{let r=new URLSearchParams;t&&r.set("search",t),e&&r.set("limit",String(e));let s=r.toString(),a=await n.get(`/api/contacts${s?`?${s}`:""}`),i=a.data||a;return{content:[{type:"text",text:JSON.stringify(i,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_get_contact",`[CRM] Full dossier for one contact: the row itself (parsed custom_fields + tags), the complete event timeline (qualifier, outreach, attribution hits), linked deals, the outreach_token, and \u2014 for X-sourced leads \u2014 every post we've observed from this handle (rehydrated from x-authors-ledger). This is the one-call "who is this person and how did we find them" view. Use it before acting on a lead so the decision sits on top of their full provenance.`,{id:R.string().min(1).describe("Contact id (from ohwow_list_contacts or ohwow_search_contacts).")},async({id:t})=>{try{let e=await n.get(`/api/contacts/${encodeURIComponent(t)}/dossier`);return{content:[{type:"text",text:JSON.stringify(e.data??e,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_create_contact",'[CRM] Add a new contact to the CRM. Returns the created contact with id, timestamps, and all fields. Contacts default to type "lead" and status "active".',{name:R.string().describe("Contact full name"),email:R.string().optional().describe("Email address"),phone:R.string().optional().describe("Phone number"),company:R.string().optional().describe("Company or organization"),tags:R.array(R.string()).optional().describe("Tags for categorization"),notes:R.string().optional().describe("Additional notes about the contact")},async({name:t,email:e,phone:r,company:s,tags:a,notes:i})=>{try{let c={name:t};e&&(c.email=e),r&&(c.phone=r),s&&(c.company=s),a&&(c.tags=a),i&&(c.notes=i);let l=await n.post("/api/contacts",c);return{content:[{type:"text",text:JSON.stringify(l,null,2)}]}}catch(c){return{content:[{type:"text",text:`Error: ${c instanceof Error?c.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_search_contacts","[CRM] Deep full-text search across all contact fields including notes and custom fields. Routes through the orchestrator (slower, ~15s). For simple filtering by name/email/company, use ohwow_list_contacts with the `search` param instead.",{query:R.string().describe("Search query")},async({query:t})=>{try{return{content:[{type:"text",text:await n.postSSE("/api/chat",{message:`Use the search_contacts tool with query: "${t}". Return the results as-is.`},15e3)||"No contacts found"}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as G}from"zod";function Ne(o,n){o.tool("ohwow_list_workflows","[Workflows] List all workflows in the workspace. Returns id, name, description, step definitions, enabled status, and last run info.",{},async()=>{try{let t=await n.get("/api/workflows"),e=t.data||t;return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_run_workflow","[Workflows] Execute a workflow by ID. Use ohwow_list_workflows to find IDs. May take up to 60 seconds depending on complexity.",{workflowId:G.string().describe("The workflow ID to execute"),variables:G.record(G.string(),G.unknown()).optional().describe("Input variables for the workflow")},async({workflowId:t,variables:e})=>{try{let r=e?` with variables: ${JSON.stringify(e)}`:"";return{content:[{type:"text",text:await n.postSSE("/api/chat",{message:`Use the run_workflow tool with workflowId: "${t}"${r}.`},6e4)||"Workflow started"}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_list_automations","[Automations] List all automations with their trigger conditions, action types, cooldown settings, fire counts, and enabled status.",{},async()=>{try{let t=await n.get("/api/automations"),e=t.data||t;return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_run_automation","[Automations] Manually trigger an automation by ID. Use ohwow_list_automations to find IDs.",{automationId:G.string().describe("The automation ID to trigger")},async({automationId:t})=>{try{let e=await n.post(`/api/automations/${t}/execute`,{});return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as Le}from"zod";function je(o,n){o.tool("ohwow_list_projects","[Projects] List all projects with id, name, description, status, task counts (total/completed), and timestamps.",{},async()=>{try{let t=await n.get("/api/projects"),e=t.data||t;return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_create_project","[Projects] Create a new project for organizing work.",{name:Le.string().describe("Project name"),description:Le.string().optional().describe("Project description")},async({name:t,description:e})=>{try{let r={name:t};e&&(r.description=e);let s=await n.post("/api/projects",r);return{content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_list_goals","[Goals] List workspace goals with progress tracking (target, current value, percentage). Routes through orchestrator (~15s).",{},async()=>{try{return{content:[{type:"text",text:await n.postSSE("/api/chat",{message:"Use the list_goals tool. Return the results as-is."},15e3)||"No goals found"}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}})}import{z as q}from"zod";function He(o,n){o.tool("ohwow_list_knowledge","[Knowledge] List documents in the knowledge base. Returns id, title, type, status, chunk count, and created_at by default. Hits the process directly over HTTP (~50ms), no orchestrator round-trip. Set `include_bodies=true` to also get the compiled text for every document \u2014 useful for embedding benchmarks and RAG evals. Be aware: body payload scales with doc size; use `limit` to cap the batch when bodies are requested.",{include_bodies:q.boolean().optional().describe("When true, each entry also includes `body` (compiled text), `tokens`, `contentHash`, `filename`, `sourceUrl`, and `processedAt`. Default: false \u2014 metadata only. The bodies payload can be large (hundreds of KB to a few MB for 100 docs), so always pair with `limit` for predictable response sizes."),limit:q.number().int().positive().optional().describe("Cap the number of rows returned. Defaults to no cap (all active documents). The server hard-caps at 500. Apply this whenever `include_bodies` is true.")},async({include_bodies:t,limit:e})=>{try{let r=new URLSearchParams;t&&r.set("include_bodies","1"),e!==void 0&&r.set("limit",String(e));let s=r.toString(),a=await n.get(`/api/knowledge${s?`?${s}`:""}`);if(a.error)return{content:[{type:"text",text:`Couldn't list knowledge: ${a.error}`}],isError:!0};let i=a.data??[],c={count:i.length,includeBodies:!!t,documents:i};return{content:[{type:"text",text:JSON.stringify(c,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_get_knowledge","[Knowledge] Fetch a single knowledge base document by id, including the compiled body text. Returns metadata (title, filename, fileType, fileSize, tokens, chunk_count, contentHash, status, sourceUrl, createdAt, processedAt) plus `body` (the full compiled text). Returns an error result when the id doesn't match an active document \u2014 use ohwow_list_knowledge to discover ids. Hits the process directly over HTTP (~50ms).",{id:q.string().describe("Document id from ohwow_list_knowledge.")},async({id:t})=>{try{let e=await n.get(`/api/knowledge/${encodeURIComponent(t)}`);return e.error||!e.data?{content:[{type:"text",text:e.error??`No knowledge document with id "${t}".`}],isError:!0}:{content:[{type:"text",text:JSON.stringify(e.data,null,2)}]}}catch(e){let r=e instanceof Error?e.message:"Unknown error";return/\b404\b/.test(r)?{content:[{type:"text",text:`No knowledge document with id "${t}". Use ohwow_list_knowledge to discover ids.`}],isError:!0}:{content:[{type:"text",text:`Error: ${r}`}],isError:!0}}}),o.tool("ohwow_search_knowledge","[Knowledge] Semantic (RAG) search across the knowledge base. Returns relevant document chunks ranked by similarity, with source attribution. Routes through orchestrator (~15s).",{query:q.string().describe("The search query")},async({query:t})=>{try{return{content:[{type:"text",text:await n.postSSE("/api/chat",{message:`Use the search_knowledge tool with query: "${t}". Return the results as-is.`},15e3)||"No results found"}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_add_knowledge_url","[Knowledge] Add a web page to the knowledge base. Fetches, chunks, and embeds the content for later search.",{url:q.string().describe("The URL to ingest"),title:q.string().optional().describe("Optional title for the document")},async({url:t,title:e})=>{try{let r=e?` with title: "${e}"`:"";return{content:[{type:"text",text:await n.postSSE("/api/chat",{message:`Use the add_knowledge_from_url tool with url: "${t}"${r}.`},3e4)||"Knowledge document added"}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}})}import{z as te}from"zod";function Fe(o,n){o.tool("ohwow_deep_research","[Research] Multi-source web research with synthesis. Timing: quick ~30s, thorough ~60s, comprehensive ~120s.",{question:te.string().describe("The research question"),depth:te.enum(["quick","thorough","comprehensive"]).optional().describe("Research depth (default: thorough)")},async({question:t,depth:e})=>{try{let r=e?` with depth: "${e}"`:"";return{content:[{type:"text",text:await n.postSSE("/api/chat",{message:`Use the deep_research tool with question: "${t}"${r}. Return the full research report.`},18e4)||"No research results"}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_scrape_url","[Research] Scrape a web page and return its structured content. Automatically handles anti-bot protection. Note: for X/Twitter posts use ohwow_fetch_x_post instead \u2014 ohwow_scrape_url renders the accessibility tree and X loads post text client-side, so the body usually comes back empty.",{url:te.string().describe("The URL to scrape")},async({url:t})=>{try{return{content:[{type:"text",text:await n.postSSE("/api/chat",{message:`Use the scrape_url tool with url: "${t}". Return the scraped content.`},3e4)||"No content scraped"}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_fetch_x_post","[Research] Fetch a single X (Twitter) post's body, author, media, and engagement metrics. Use this instead of ohwow_scrape_url whenever you need the actual text of a tweet \u2014 tweets render client-side, so generic scraping returns the chrome, not the content. Critical for grounding an outreach DM (ohwow_draft_x_dm) in a specific idea the contact wrote, which is the voice rule for outbound X messaging. Accepts either a permalink (https://x.com/handle/status/123 or /handle/status/123) or a bare numeric tweet id.",{permalink_or_id:te.string().min(1).describe('Tweet permalink, URL, or bare numeric id. Examples: "https://x.com/user/status/2044523795206029525", "/user/status/2044523795206029525", "2044523795206029525".')},async({permalink_or_id:t})=>{try{let e=`/api/x/tweet/lookup?permalink=${encodeURIComponent(t)}`,r=await n.get(e);return r.error?{content:[{type:"text",text:`Couldn't fetch tweet: ${r.error}`}],isError:!0}:r.data?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}]}:{content:[{type:"text",text:"Tweet not found (private, deleted, or unparseable id)."}],isError:!0}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as re}from"zod";function Je(o,n){o.tool("ohwow_send_message","[Messaging] Send a message via WhatsApp or Telegram. Use ohwow_list_chats to find chat IDs. The channel must be connected in the ohwow dashboard first.",{channel:re.enum(["whatsapp","telegram"]).describe("Messaging channel"),chatId:re.string().describe("Chat or contact ID to send to"),message:re.string().describe("Message text to send")},async({channel:t,chatId:e,message:r})=>{try{let s=t==="whatsapp"?"send_whatsapp_message":"send_telegram_message";return{content:[{type:"text",text:await n.postSSE("/api/chat",{message:`Use the ${s} tool to send this message to chat "${e}": ${r}`},15e3)||"Message sent"}]}}catch(s){return{content:[{type:"text",text:`Error: ${s instanceof Error?s.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_list_chats","[Messaging] List connected chats for WhatsApp or Telegram.",{channel:re.enum(["whatsapp","telegram"]).describe("Messaging channel")},async({channel:t})=>{try{let e=t==="whatsapp"?"list_whatsapp_chats":"list_telegram_chats";return{content:[{type:"text",text:await n.postSSE("/api/chat",{message:`Use the ${e} tool. Return the results as-is.`},15e3)||"No chats found"}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}function qe(o,n){o.tool("ohwow_list_sites","[Cloud] List all sites on the ohwow.fun cloud dashboard with status and URLs. Requires cloud connection.",{},async()=>{try{let t=await n.get("/api/cloud/sites");if(t.cloudConnected===!1)return{content:[{type:"text",text:"Not connected to ohwow.fun. Run `ohwow connect` to link your cloud account."}]};let e=t.data||[];return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_list_integrations","[Cloud] List connected integrations (Gmail, GitHub, Stripe, etc.) and their status. Requires cloud connection.",{},async()=>{try{let t=await n.get("/api/cloud/integrations");if(t.cloudConnected===!1)return{content:[{type:"text",text:"Not connected to ohwow.fun. Run `ohwow connect` to link your cloud account."}]};let e=t.data||[];return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}})}import{z as se}from"zod";import{join as ae,dirname as Ht}from"path";import{existsSync as Ft}from"fs";import{fileURLToPath as Jt}from"url";import{join as Be}from"path";import{spawn as Pt}from"child_process";import{openSync as Ut,existsSync as It,statSync as Wt,renameSync as $t,writeFileSync as Zr,unlinkSync as eo,readFileSync as to}from"fs";import{readFileSync as At,writeFileSync as Kr,unlinkSync as zr,existsSync as Mt,mkdirSync as Gr}from"fs";function oe(o){try{if(!Mt(o))return null;let n=At(o,"utf-8");return JSON.parse(n)}catch{return null}}function ne(o){try{return process.kill(o,0),!0}catch{return!1}}var Nt=10*1024*1024;function Lt(o){return Be(o,"daemon.log")}function jt(o){try{if(!It(o))return;Wt(o).size>Nt&&$t(o,`${o}.1`)}catch{}}function ke(o){return Be(o,"daemon.pid")}async function N(o,n){let t=oe(ke(o));if(!t)return{running:!1};if(!ne(t.pid))return{running:!1};let e=t.port||n;try{let r=await fetch(`http://localhost:${e}/health`,{signal:AbortSignal.timeout(2e3)});if(r.ok){let s=await r.json();if(s.status==="healthy"||s.status==="degraded")return{running:!0,pid:t.pid}}}catch{return{running:!0,pid:t.pid,healthy:!1}}return{running:!1}}function ve(o,n,t){let e=Lt(t);jt(e);let r=Ut(e,"a"),a=o.endsWith(".ts")?["--import","tsx",o,"--daemon"]:[o,"--daemon"],i={...process.env,OHWOW_PORT:String(n)};process.env.OHWOW_WORKSPACE&&(i.OHWOW_WORKSPACE=process.env.OHWOW_WORKSPACE);let c=Pt(process.execPath,a,{detached:!0,windowsHide:!0,stdio:["ignore",r,r],env:i});return c.unref(),c.pid}async function Ee(o,n=15e3){let t=Date.now()+n,e=300;for(;Date.now()<t;){try{if((await fetch(`http://localhost:${o}/health`,{signal:AbortSignal.timeout(1e3)})).ok)return!0}catch{}await new Promise(r=>setTimeout(r,e))}return!1}async function Se(o){let n=oe(ke(o));if(!n||!ne(n.pid))return!1;if(process.platform==="win32"&&n.port)try{return await fetch(`http://localhost:${n.port}/shutdown`,{method:"POST",signal:AbortSignal.timeout(3e3)}),!0}catch{try{return process.kill(n.pid),!0}catch{return!1}}try{return process.kill(n.pid,"SIGTERM"),!0}catch(t){return t.code==="EPERM"&&V.error("Cannot stop daemon (PID %d): permission denied. It may be running as a different user.",n.pid),!1}}async function Oe(o,n=5e3){let t=ke(o),e=Date.now()+n,r=200;for(;Date.now()<e;){let s=oe(t);if(!s||!ne(s.pid))return!0;await new Promise(a=>setTimeout(a,r))}return!1}function ie(){let o=W(),n=$(o.name)??J,t=Ht(Jt(import.meta.url)),r=[ae(t,"..","..","index.js"),ae(t,"..","..","..","index.js"),ae(t,"..","..","..","index.ts"),ae(t,"..","..","index.ts")].find(s=>Ft(s))??null;return{workspaceName:o.name,dataDir:o.dataDir,port:n,entryPath:r}}async function C(o){let n=await N(o.dataDir,o.port);return{running:n.running,healthy:n.running&&n.healthy!==!1,pid:n.pid??null,port:o.port,dataDir:o.dataDir,entryPath:o.entryPath}}function Ke(o){o.tool("ohwow_daemon_status","[Process] Check whether the ohwow local process is running. Reports pid, port, health, and the resolved entry path. Does not modify state.",{},async()=>{try{let n=ie(),t=await C(n);return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}catch(n){return{content:[{type:"text",text:`Error: ${n instanceof Error?n.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_daemon_stop","[Process] Stop the running ohwow process gracefully (SIGTERM on Unix, /shutdown on Windows). Returns whether it actually stopped within the timeout.",{timeoutMs:se.number().optional().describe("How long to wait for the process to stop before reporting failure. Default 5000ms.")},async({timeoutMs:n})=>{try{let t=ie();if(!(await N(t.dataDir,t.port)).running)return{content:[{type:"text",text:JSON.stringify({ok:!0,alreadyStopped:!0,...await C(t)},null,2)}]};if(!await Se(t.dataDir))return{content:[{type:"text",text:JSON.stringify({ok:!1,error:"Could not send stop signal (permission denied or stale pid)",...await C(t)},null,2)}],isError:!0};let s=await Oe(t.dataDir,n??5e3),a=await C(t);return{content:[{type:"text",text:JSON.stringify({ok:s,...a},null,2)}],isError:!s}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_daemon_start","[Process] Start the ohwow process in the background if it is not already running. Waits for the health endpoint before returning. No-op if the daemon is already healthy.",{timeoutMs:se.number().optional().describe("How long to wait for the process to become healthy. Default 15000ms.")},async({timeoutMs:n})=>{try{let t=ie(),e=await N(t.dataDir,t.port);if(e.running&&e.healthy!==!1)return{content:[{type:"text",text:JSON.stringify({ok:!0,alreadyRunning:!0,...await C(t)},null,2)}]};if(!t.entryPath)return{content:[{type:"text",text:JSON.stringify({ok:!1,error:"Could not locate process entry (dist/index.js or src/index.ts)",...await C(t)},null,2)}],isError:!0};let r=ve(t.entryPath,t.port,t.dataDir),s=await Ee(t.port,n??15e3),a=await C(t);return{content:[{type:"text",text:JSON.stringify({ok:s,spawnedPid:r,entryPath:t.entryPath,...a},null,2)}],isError:!s}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_daemon_restart","[Process] Restart the ohwow process: stop if running, wait for the PID to clear, then spawn a fresh background instance and wait for health. Use this after rebuilding dist/index.js so the running process picks up the new code. Safe to call even when the daemon is already dead \u2014 it will just start a fresh one.",{stopTimeoutMs:se.number().optional().describe("Timeout for the stop phase. Default 5000ms."),startTimeoutMs:se.number().optional().describe("Timeout for the start/health phase. Default 15000ms.")},async({stopTimeoutMs:n,startTimeoutMs:t})=>{try{let e=ie();if(!e.entryPath)return{content:[{type:"text",text:JSON.stringify({ok:!1,error:"Could not locate process entry (dist/index.js or src/index.ts)",...await C(e)},null,2)}],isError:!0};let r=await N(e.dataDir,e.port),s={wasRunning:r.running,previousPid:r.pid??null};if(r.running){let l=await Se(e.dataDir);if(s.stopSignalSent=l,!l)return{content:[{type:"text",text:JSON.stringify({ok:!1,phase:"stop",error:"Could not send stop signal",...s,...await C(e)},null,2)}],isError:!0};let p=await Oe(e.dataDir,n??5e3);if(s.stopped=p,!p)return{content:[{type:"text",text:JSON.stringify({ok:!1,phase:"stop",error:"Previous process did not exit within timeout",...s,...await C(e)},null,2)}],isError:!0}}let a=ve(e.entryPath,e.port,e.dataDir);s.spawnedPid=a;let i=await Ee(e.port,t??15e3);s.ready=i;let c=await C(e);return{content:[{type:"text",text:JSON.stringify({ok:i,phase:i?"ready":"start",...s,...c},null,2)}],isError:!i}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as qt}from"zod";async function Bt(){let o=W().name,n=Array.from(new Set([...Ie(),z])).sort(),t=[];for(let e of n){let r=ye(e),s=$(e),a=U(e),i=!1,c;if(s!==null)try{let l=await N(a.dataDir,s);i=l.running,c=l.pid}catch{}t.push({name:e,focused:e===o,mode:r?r.mode:"default",...r?.displayName?{displayName:r.displayName}:{},port:s,running:i,...c!==void 0?{pid:c}:{},...r?.cloudWorkspaceId?{cloudWorkspaceId:r.cloudWorkspaceId}:{}})}return t}function ze(o,n){o.tool("ohwow_workspace_list","[Workspace] List every local workspace under ~/.ohwow/workspaces with mode, port, and live running status. Use this to discover what workspaces exist before switching with ohwow_workspace_use.",{},async()=>{try{let t=await Bt();return{content:[{type:"text",text:JSON.stringify({workspaces:t},null,2)}]}}catch(t){return{content:[{type:"text",text:`Error listing workspaces: ${t instanceof Error?t.message:t}`}],isError:!0}}}),o.tool("ohwow_workspace_use","[Workspace] Switch which workspace this MCP session targets. Reconnects the api-client to the named workspace's daemon (must already be running \u2014 start it with `ohwow workspace start <name>` from a terminal first if needed). Also updates ~/.ohwow/current-workspace so future sessions default to it. All subsequent tool calls in this MCP session will hit the new workspace.",{name:qt.string().describe('Workspace name (must already exist under ~/.ohwow/workspaces or be the legacy "default")')},async({name:t})=>{try{let e=W().name,r=await n.switchTo(t);return{content:[{type:"text",text:JSON.stringify({ok:!0,switched:!0,previous:e,current:r.workspaceName,port:r.port,message:`MCP session now targets workspace "${r.workspaceName}" on port ${r.port}. Future MCP launches will also default to this workspace.`},null,2)}]}}catch(e){return{content:[{type:"text",text:JSON.stringify({ok:!1,error:e instanceof Error?e.message:String(e)},null,2)}],isError:!0}}})}import{z as k}from"zod";var Kt=`Workspace-unique identifier for this MCP server (alphanumeric, dashes, underscores). Used as the namespace prefix when the server's tools are exposed to agents (e.g. name="avenued" \u2192 tools become mcp__avenued__<tool>).`,zt='SENSITIVE. HTTP headers sent with every request to the MCP server, e.g. { "Authorization": "Bearer sk-..." }. Stored encrypted-equivalent in the workspace DB (never returned by list, never logged in plaintext, never exposed to agent context \u2014 injected at the transport layer).',Gt='SENSITIVE. Environment variables for the stdio subprocess, e.g. { "GITHUB_TOKEN": "ghp_..." }. Stored in the workspace DB and passed to the child process at spawn time. Never returned by list, never logged in plaintext, never exposed to agent context.';function Ge(o,n){o.tool("ohwow_add_mcp_server","[MCP] Register a third-party MCP server in the current workspace. Its tools become available to all agents (and the orchestrator) on the next turn. Credentials in `headers` or `env` are SENSITIVE \u2014 they stay on the local process, never transit through the LLM, and are never returned by list/inspect calls. Use for hooking external platforms (Stripe, GitHub, Notion, custom internal MCPs) into an ohwow workspace.",{name:k.string().describe(Kt),transport:k.enum(["http","stdio"]).describe('Transport type. "http" for Streamable HTTP servers (most hosted MCPs); "stdio" for local subprocess servers launched with command+args.'),url:k.string().optional().describe('Required for transport="http". Full URL of the MCP endpoint, e.g. https://example.com/api/mcp. Must be a publicly routable HTTPS URL \u2014 private/link-local/metadata IPs are rejected.'),command:k.string().optional().describe('Required for transport="stdio". Executable to run, e.g. "npx" or "node".'),args:k.array(k.string()).optional().describe('For transport="stdio". Command-line arguments to pass to the executable.'),headers:k.record(k.string(),k.string()).optional().describe(zt),env:k.record(k.string(),k.string()).optional().describe(Gt),description:k.string().optional().describe("Human-readable description of what this server does. Shown in ohwow_list_mcp_servers output."),enabled:k.boolean().optional().describe("Whether the server should be connected on process startup. Default: true.")},async({name:t,transport:e,url:r,command:s,args:a,headers:i,env:c,description:l,enabled:p})=>{try{let u={name:t,transport:e};r&&(u.url=r),s&&(u.command=s),a&&(u.args=a),i&&(u.headers=i),c&&(u.env=c),l&&(u.description=l),p===!1&&(u.enabled=!1);let f=await n.post("/api/mcp/servers",u);return f.error?{content:[{type:"text",text:`Couldn't register MCP server: ${f.error}`}],isError:!0}:{content:[{type:"text",text:JSON.stringify({ok:!0,server:f.server,note:"Server registered. Credentials stored on the process and redacted from this response. Call ohwow_test_mcp_server to verify connectivity and list exposed tools."},null,2)}]}}catch(u){return{content:[{type:"text",text:`Error: ${u instanceof Error?u.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_list_mcp_servers",'[MCP] List all third-party MCP servers registered in the current workspace. Response shows name, transport, URL (for HTTP) or command (for stdio), description, enabled flag, and \u2014 for servers with credentials \u2014 the header/env KEY names replaced with "<set>". Actual credential values are NEVER returned. Does NOT perform live connections; use ohwow_test_mcp_server for that.',{},async()=>{try{let e=(await n.get("/api/mcp/servers")).servers??[];return{content:[{type:"text",text:JSON.stringify({count:e.length,servers:e},null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_remove_mcp_server","[MCP] Remove a registered MCP server from the current workspace by name. Disconnects the server immediately and drops its tools from the agent tool surface on the next turn. The stored credentials are deleted along with the config.",{name:k.string().describe("Name of the MCP server to remove (must match a name from ohwow_list_mcp_servers).")},async({name:t})=>{try{let e=await n.del(`/api/mcp/servers/${encodeURIComponent(t)}`);return e.error?{content:[{type:"text",text:`Couldn't remove MCP server: ${e.error}`}],isError:!0}:{content:[{type:"text",text:JSON.stringify({ok:!0,removed:e.removed??t},null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_test_mcp_server","[MCP] Verify a registered MCP server connects, and return the names of tools it exposes. Uses the stored config (including credentials) but returns only tool NAMES \u2014 no schemas, no credential echo, no connection details. Use this right after ohwow_add_mcp_server to confirm the server is reachable and authenticated, and to discover what tools will become available to agents.",{name:k.string().describe("Name of a registered MCP server (from ohwow_list_mcp_servers).")},async({name:t})=>{try{let e=await n.post(`/api/mcp/servers/${encodeURIComponent(t)}/test`,{});return e.success?{content:[{type:"text",text:JSON.stringify({ok:!0,toolCount:e.toolCount??0,toolNames:e.toolNames??[],latencyMs:e.latencyMs},null,2)}]}:{content:[{type:"text",text:JSON.stringify({ok:!1,error:e.error??"Connection failed",latencyMs:e.latencyMs},null,2)}],isError:!0}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as g}from"zod";var Xt="Workspace-unique identifier for this agent. Alphanumeric plus dashes and underscores only (no spaces). Used by ohwow_get_agent, ohwow_update_agent, ohwow_delete_agent, and ohwow_run_agent to look up the row.",Yt="The agent's core instructions. Stored verbatim and injected as the system message on every task run. Business-sensitive context belongs here; it is scoped to the current workspace and is NOT returned by ohwow_list_agents (which returns only summary fields). Use ohwow_get_agent to read it back.",Vt='Explicit list of tool names the agent is allowed to call. If omitted, the agent inherits the workspace default tool surface. If provided, the agent sees ONLY these tools. Names must be either internal tool identifiers (e.g. "list_tasks", "scrape_url") or external MCP-server tools in the "mcp__<server>__<tool>" shape. Unknown internal names are rejected at create time so misconfigured agents fail fast.';async function B(o,n){return((await o.get("/api/agents")).data??[]).find(r=>r.name===n)??null}function h(o){return{content:[{type:"text",text:o}],isError:!0}}function L(o){return{content:[{type:"text",text:JSON.stringify(o,null,2)}]}}function Xe(o,n){o.tool("ohwow_create_agent","[Agents] Create a new agent in the current workspace. Writes directly to the agents table via the local process. Use this instead of ohwow_chat for agent creation \u2014 the orchestrator has no typed create primitive and will loop on ambient-state inspection if asked to create an agent via chat.",{name:g.string().describe(Xt),displayName:g.string().optional().describe("Human-readable label shown in UIs. Defaults to `name` when omitted."),description:g.string().optional().describe("Short summary of what the agent does. Shown in list views and agent pickers."),systemPrompt:g.string().describe(Yt),toolAllowlist:g.array(g.string()).optional().describe(Vt),webSearchEnabled:g.boolean().optional().describe('Whether the built-in web_search tool should be available to this agent. Defaults to false \u2014 conservative default so narrow allowlists do not accidentally grant web access. When `toolAllowlist` is set, this flag is overridden by the allowlist: the agent sees web_search only if "web_search" is explicitly in the list.'),role:g.string().optional().describe('Free-text role label for categorization (e.g. "analyst", "writer"). Defaults to "assistant".'),enabled:g.boolean().optional().describe("Whether the agent is enabled and eligible to run. Default: true."),scheduled:g.object({cron:g.string().describe("Cron expression (5- or 6-field)."),timezone:g.string().optional().describe('IANA timezone name (e.g. "America/New_York"). Defaults to the workspace tz.')}).optional().describe("Optional cron schedule. Stored with the agent but not yet wired to the scheduler \u2014 runs must be triggered manually via ohwow_run_agent for now.")},async({name:t,displayName:e,description:r,systemPrompt:s,toolAllowlist:a,webSearchEnabled:i,role:c,enabled:l,scheduled:p})=>{try{let u={name:t,system_prompt:s};e!==void 0&&(u.display_name=e),r!==void 0&&(u.description=r),c!==void 0&&(u.role=c),l!==void 0&&(u.enabled=l),p!==void 0&&(u.scheduled=p);let f={};a!==void 0&&(f.tools_enabled=a,f.tools_mode="allowlist"),i!==void 0&&(f.web_search_enabled=i),Object.keys(f).length>0&&(u.config=f);let E=await n.post("/api/agents",u);return E.error?h(`Couldn't create agent: ${E.error}`):L({ok:!0,agent:E.data,note:"Agent created. Use ohwow_run_agent with this agent's name or id to dispatch a task."})}catch(u){return h(`Error: ${u instanceof Error?u.message:"Unknown error"}`)}}),o.tool("ohwow_get_agent","[Agents] Get an agent's full configuration by name or id, including the system prompt, tool allowlist, role, schedule, enabled flag, and timestamps. Use this instead of ohwow_list_agents when you need to inspect or iterate on a specific agent's system prompt \u2014 list_agents returns summary rows and does not include the prompt.",{name:g.string().optional().describe("Workspace-unique agent name. Provide this OR `id`."),id:g.string().optional().describe("Agent UUID. Provide this OR `name`.")},async({name:t,id:e})=>{try{if(!t&&!e)return h("Provide either `name` or `id`.");let r=e;if(!r&&t){let a=await B(n,t);if(!a)return h(`No agent named "${t}" in this workspace.`);r=a.id}let s=await n.get(`/api/agents/${encodeURIComponent(r)}`);return s.error||!s.data?h(s.error??"Agent not found"):L({ok:!0,agent:s.data})}catch(r){return h(`Error: ${r instanceof Error?r.message:"Unknown error"}`)}}),o.tool("ohwow_update_agent","[Agents] Update fields on an existing agent. Use this to iterate on a system prompt, tighten a tool allowlist, rename, or toggle enabled. Identify the agent by `name` or `id`. Any field left undefined is untouched. Agents never pin a model \u2014 the router picks per task.",{name:g.string().optional().describe("Workspace-unique agent name (provide this OR `id`)."),id:g.string().optional().describe("Agent UUID (provide this OR `name`)."),newName:g.string().optional().describe("Rename the agent. Must remain workspace-unique and match the alphanumeric+dash+underscore constraint."),displayName:g.string().optional().describe("Updated human-readable label."),description:g.string().optional().describe("Updated short summary."),systemPrompt:g.string().optional().describe("Replace the system prompt entirely."),toolAllowlist:g.array(g.string()).optional().describe("Replace the tool allowlist. Same validation rules as ohwow_create_agent."),webSearchEnabled:g.boolean().optional().describe("Toggle the built-in web_search tool. Overridden by the allowlist when one is active \u2014 see ohwow_create_agent."),role:g.string().optional().describe("Updated role label."),enabled:g.boolean().optional().describe("Toggle the agent on/off."),scheduled:g.object({cron:g.string(),timezone:g.string().optional()}).optional().describe("Replace the cron schedule.")},async({name:t,id:e,newName:r,displayName:s,description:a,systemPrompt:i,toolAllowlist:c,webSearchEnabled:l,role:p,enabled:u,scheduled:f})=>{try{if(!t&&!e)return h("Provide either `name` or `id`.");let E=e;if(!E&&t){let O=await B(n,t);if(!O)return h(`No agent named "${t}" in this workspace.`);E=O.id}let y={};r!==void 0&&(y.name=r),a!==void 0&&(y.description=a),i!==void 0&&(y.system_prompt=i),p!==void 0&&(y.role=p),u!==void 0&&(y.enabled=u),s!==void 0&&(y.display_name=s),f!==void 0&&(y.scheduled=f);let S={};if(c!==void 0&&(S.tools_enabled=c,S.tools_mode="allowlist"),l!==void 0&&(S.web_search_enabled=l),Object.keys(S).length>0&&(y.config=S),Object.keys(y).length===0)return h("No fields provided to update.");let j=await n.patch(`/api/agents/${encodeURIComponent(E)}`,y);return j.error?h(`Couldn't update agent: ${j.error}`):L({ok:!0,agent:j.data})}catch(E){return h(`Error: ${E instanceof Error?E.message:"Unknown error"}`)}}),o.tool("ohwow_grant_agent_path","[Agents] Grant an agent permission to read/write files under a local directory. Writes a row to agent_file_access_paths that the FileAccessGuard reads on every task run, so the agent's filesystem tools (local_read_file, local_write_file, run_bash, etc.) will accept paths inside the granted directory. Without this, a narrowly-scoped agent hits \"path outside allowed directories\" and the task either fails the hallucination gate or routes to needs_approval. The process validates that the path exists, is a directory, lives inside the user's home, and isn't a sensitive subdirectory like .ssh or .gnupg. Identify the agent by `name` or `id`.",{name:g.string().optional().describe("Workspace-unique agent name (provide this OR `id`)."),id:g.string().optional().describe('Agent UUID, or the literal string "__orchestrator__" to grant paths to the orchestrator itself (provide this OR `name`).'),path:g.string().describe("Absolute directory path to grant access to. Must exist, be a directory, live inside the user's home, and not be under .ssh, .gnupg, /etc, /var, /usr, etc. Tildes are not expanded \u2014 pass a fully-resolved path."),label:g.string().optional().describe('Optional human-readable label shown in UIs (e.g. "living docs (diary)", "workspace data"). Purely cosmetic.')},async({name:t,id:e,path:r,label:s})=>{try{if(!t&&!e)return h("Provide either `name` or `id`.");let a=e;if(!a&&t){let c=await B(n,t);if(!c)return h(`No agent named "${t}" in this workspace.`);a=c.id}let i=await n.post(`/api/agents/${encodeURIComponent(a)}/file-access`,{path:r,label:s});return i.error?h(`Couldn't grant path: ${i.error}`):L({ok:!0,agent:t??a,path:i.data?.path??r,label:i.data?.label??s??null,note:"Path granted. FileAccessGuard re-reads this table on every task run, so the next run will see the new access."})}catch(a){return h(`Error: ${a instanceof Error?a.message:"Unknown error"}`)}}),o.tool("ohwow_list_agent_paths","[Agents] List all filesystem paths granted to an agent. Returns rows from agent_file_access_paths with id, path, label, and created_at. Use the returned `id` with ohwow_revoke_agent_path to remove a specific row. Identify the agent by `name` or `id`.",{name:g.string().optional().describe("Workspace-unique agent name (provide this OR `id`)."),id:g.string().optional().describe('Agent UUID, or the literal string "__orchestrator__" for orchestrator-scoped paths (provide this OR `name`).')},async({name:t,id:e})=>{try{if(!t&&!e)return h("Provide either `name` or `id`.");let r=e;if(!r&&t){let a=await B(n,t);if(!a)return h(`No agent named "${t}" in this workspace.`);r=a.id}let s=await n.get(`/api/agents/${encodeURIComponent(r)}/file-access`);return s.error?h(`Couldn't list paths: ${s.error}`):L({ok:!0,agent:t??r,paths:s.data??[]})}catch(r){return h(`Error: ${r instanceof Error?r.message:"Unknown error"}`)}}),o.tool("ohwow_revoke_agent_path","[Agents] Revoke an agent's access to a filesystem path. Identify the agent by `name` or `id`, then identify the row by either `pathId` (from ohwow_list_agent_paths) or `path` (exact match \u2014 the tool lists and resolves locally). Idempotent on a missing row.",{name:g.string().optional().describe("Workspace-unique agent name (provide this OR `id`)."),id:g.string().optional().describe('Agent UUID, or the literal string "__orchestrator__" (provide this OR `name`).'),pathId:g.string().optional().describe("The row id from ohwow_list_agent_paths. Takes precedence over `path` when both are provided."),path:g.string().optional().describe("Absolute directory path. The tool lists existing grants and deletes the matching row. Used when the caller doesn't know the row id. Exact-match, fully-resolved path.")},async({name:t,id:e,pathId:r,path:s})=>{try{if(!t&&!e)return h("Provide either `name` or `id`.");if(!r&&!s)return h("Provide either `pathId` or `path`.");let a=e;if(!a&&t){let l=await B(n,t);if(!l)return h(`No agent named "${t}" in this workspace.`);a=l.id}let i=r;if(!i&&s){let l=await n.get(`/api/agents/${encodeURIComponent(a)}/file-access`);if(l.error)return h(`Couldn't list paths for match: ${l.error}`);let p=(l.data??[]).find(u=>u.path===s);if(!p)return h(`No granted path matches "${s}" for this agent.`);i=p.id}let c=await n.del(`/api/agents/${encodeURIComponent(a)}/file-access/${encodeURIComponent(i)}`);return c.error?h(`Couldn't revoke path: ${c.error}`):L({ok:!0,agent:t??a,revoked:i})}catch(a){return h(`Error: ${a instanceof Error?a.message:"Unknown error"}`)}}),o.tool("ohwow_delete_agent","[Agents] Delete an agent from the current workspace. Also drops the agent's memory rows. Identify by `name` or `id`. This is destructive \u2014 there is no undo.",{name:g.string().optional().describe("Workspace-unique agent name (provide this OR `id`)."),id:g.string().optional().describe("Agent UUID (provide this OR `name`).")},async({name:t,id:e})=>{try{if(!t&&!e)return h("Provide either `name` or `id`.");let r=e,s=t;if(!r&&t){let i=await B(n,t);if(!i)return h(`No agent named "${t}" in this workspace.`);r=i.id,s=i.name}let a=await n.del(`/api/agents/${encodeURIComponent(r)}`);return a.error?h(`Couldn't delete agent: ${a.error}`):L({ok:!0,deleted:s??r})}catch(r){return h(`Error: ${r instanceof Error?r.message:"Unknown error"}`)}})}import{z as ce}from"zod";function le(o){return{content:[{type:"text",text:o}],isError:!0}}function Ye(o){return{content:[{type:"text",text:JSON.stringify(o,null,2)}]}}function Ve(o,n){o.tool("ohwow_list_permission_requests",'[Permissions] List every task currently paused on a filesystem or bash permission request. Returns the task id, agent name, attempted path, suggested exact + parent paths the operator can grant, the guard reason, and when the denial happened. These are the actionable items in the "agent wants access to X" queue. Pair with ohwow_approve_permission_request to resolve any row. Workspace-scoped to the focused process.',{},async()=>{try{let t=await n.get("/api/permission-requests");if(t.error)return le(`Couldn't list permission requests: ${t.error}`);let e=t.data??[];return Ye({ok:!0,count:e.length,requests:e,note:e.length===0?"No agents are waiting on a permission decision right now.":"Use ohwow_approve_permission_request with the task_id to approve once, approve always, or deny."})}catch(t){return le(`Error: ${t instanceof Error?t.message:"Unknown error"}`)}}),o.tool("ohwow_approve_permission_request",'[Permissions] Resolve a paused permission request. mode="once" grants the path for this single resumed run only and does NOT persist a row in agent_file_access_paths \u2014 use it for one-off operator approvals. mode="always" persists the grant by writing through the same agent_file_access_paths path that ohwow_grant_agent_path writes to, so future runs of this agent stay unblocked. mode="deny" terminates the original task with failure_category=permission_denied and does NOT spawn a resume. On approve, the original task is marked status=approved and a NEW child task is spawned (parent_task_id pointing back) that re-runs the work from scratch with the expanded guard. The child task id is returned so callers can poll its status.',{task_id:ce.string().describe("Task id from ohwow_list_permission_requests. Must currently be in status=needs_approval with approval_reason=permission_denied; the route 409s otherwise."),mode:ce.enum(["once","always","deny"]).describe('"once" = resume with an ephemeral grant on the child task only. "always" = persist the grant via agent_file_access_paths so future runs work. "deny" = terminate the task without resuming.'),scope:ce.enum(["exact","parent","edit"]).optional().describe('Which path to grant (ignored for mode=deny). "exact" = the exact file/dir the agent asked for (default). "parent" = the parent directory of the exact path, so sibling files also work. "edit" = use the explicit `path` field below; the operator overrides what was suggested.'),path:ce.string().optional().describe(`Required only when scope="edit". Absolute directory path to grant. Must live inside the user's home directory and not under a blocked system path.`)},async({task_id:t,mode:e,scope:r,path:s})=>{try{let a={mode:e};r!==void 0&&(a.scope=r),s!==void 0&&(a.path=s);let i=await n.post(`/api/permission-requests/${encodeURIComponent(t)}/approve`,a);return i.error?le(`Couldn't approve permission request: ${i.error}`):Ye({ok:!0,...i,note:e==="deny"?"Task marked failed with failure_category=permission_denied. No resume spawned.":`Resumed as task ${i.child_task_id?.slice(0,8)??"<unknown>"}. Use ohwow_get_task with the child id to see when it completes.`})}catch(a){return le(`Error: ${a instanceof Error?a.message:"Unknown error"}`)}})}import{z as Qt}from"zod";function Qe(o){return{content:[{type:"text",text:o}],isError:!0}}function Zt(o){return{content:[{type:"text",text:JSON.stringify(o,null,2)}]}}function Ze(o,n){o.tool("ohwow_list_failing_triggers",'[Triggers] List every scheduled/event trigger that has been silently miscarrying. A trigger shows up here when its last N consecutive fires all failed (or landed in needs_approval without resolution) \u2014 the watchdog catches the class of "the daily diary cron has been broken for 2 weeks and nobody noticed." Returns the trigger id, name, consecutive_failures count, last_succeeded_at (or null if it has never succeeded since the watchdog was added), last_fired_at, trigger_type, enabled flag, and last_error string. Sorted worst-first. The threshold defaults to 3 but can be overridden via `threshold` to see near-misses too (e.g. `threshold: 1` returns every trigger with any failure at all).',{threshold:Qt.number().int().positive().optional().describe("Minimum consecutive_failures to include in the list. Default: 3 (matches the daemon's TRIGGER_STUCK_THRESHOLD). Set to 1 to see every trigger with any failure on its most recent run.")},async({threshold:t})=>{try{let e=t!==void 0?`?threshold=${t}`:"",r=await n.get(`/api/failing-triggers${e}`);if(r.error)return Qe(`Couldn't list failing triggers: ${r.error}`);let s=r.data??[];return Zt({ok:!0,threshold:r.threshold,count:s.length,triggers:s,note:s.length===0?`No triggers are at or above ${r.threshold} consecutive failures. Scheduled automations are running cleanly right now.`:`${s.length} trigger(s) have been failing ${r.threshold}+ runs in a row. Investigate via ohwow_get_task on recent tasks for each, or disable the trigger via the UI if it's a known-broken integration.`})}catch(e){return Qe(`Error: ${e instanceof Error?e.message:"Unknown error"}`)}})}import{z as A}from"zod";function de(o){return{content:[{type:"text",text:o}],isError:!0}}function et(o){return{content:[{type:"text",text:JSON.stringify(o,null,2)}]}}function tt(o,n){o.tool("ohwow_list_findings",'[Self-bench] List rows from the self_findings ledger \u2014 the structured record of every self-experiment the daemon has run. Each row captures: experiment_id, category, subject, hypothesis, verdict (pass/warning/fail/error), summary, evidence JSON, and an optional intervention_applied blob describing what the experiment changed. Use this BEFORE investigating a surface from scratch \u2014 past findings likely already tell you what the system learned about a model, trigger, tool, or handler. Filters compose freely. Defaults to active status, newest-first, 50 rows. Examples: to see current model-picker health run with {category: "model_health"}; to see every stuck trigger find run with {category: "trigger_stability", verdict: "fail"}; to see everything a specific experiment has logged run with {experiment_id: "model-health"}.',{experiment_id:A.string().optional().describe('Filter to one experiment id (e.g. "model-health", "trigger-stability"). Returns only rows that experiment produced.'),category:A.enum(["model_health","trigger_stability","tool_reliability","handler_audit","prompt_calibration","canary","other"]).optional().describe("Filter to one typed category. Categories match src/self-bench/experiment-types.ts."),verdict:A.enum(["pass","warning","fail","error"]).optional().describe('Filter to one verdict. Use "fail" or "error" to focus on problems; "warning" to see drift before it becomes failure.'),subject:A.string().optional().describe('Filter to rows about a specific subject (e.g. "qwen/qwen3.5-9b", "trigger:d1a924de..."). Lets you trace the history of a single model or trigger over time.'),status:A.enum(["active","superseded","revoked"]).optional().describe(`Row lifecycle filter. Defaults to "active" \u2014 rows that haven't been superseded or revoked by a later finding. Use "superseded" to see historical findings that got replaced.`),limit:A.number().int().positive().max(500).optional().describe("Cap on rows returned. Default 50, hard max 500.")},async({experiment_id:t,category:e,verdict:r,subject:s,status:a,limit:i})=>{try{let c=new URLSearchParams;t&&c.set("experiment_id",t),e&&c.set("category",e),r&&c.set("verdict",r),s&&c.set("subject",s),a&&c.set("status",a),i!==void 0&&c.set("limit",String(i));let l=c.toString()?`?${c.toString()}`:"",p=await n.get(`/api/findings${l}`);if(p.error)return de(`Couldn't list findings: ${p.error}`);let u=p.data??[];return et({ok:!0,count:u.length,limit:p.limit,findings:u,note:u.length===0?"No findings match these filters. Widen the filters (drop verdict or category) or wait for the next experiment tick.":`${u.length} finding(s) returned. Each is a historical record of one experiment run \u2014 use the evidence field to see raw probe output and intervention_applied to see what changed (if anything).`})}catch(c){return de(`Error: ${c instanceof Error?c.message:"Unknown error"}`)}}),o.tool("ohwow_list_insights",`[Self-bench] List DISTILLED insights \u2014 the "what is surprising right now?" view on top of self_findings. Each row is one (experiment, subject) cluster, already deduped, scored by novelty, and ranked. novelty_score is 0..1 where 1.0 is first-seen and 0 is routine. novelty_reason is one of: first_seen / verdict_flipped / value_z / repeat_count / normal. consecutive_fails tiebreaks stuck problems above equally-novel ones. Use this instead of ohwow_list_findings when you want to know what stands out today vs what's noise. Examples: top 10 surprises \u2192 {limit: 10}; only unusual observations \u2192 {min_score: 0.5}; include superseded history \u2192 {status: "any"}.`,{limit:A.number().int().positive().max(200).optional().describe("Cap on insights returned. Default 25, hard max 200."),min_score:A.number().min(0).max(1).optional().describe("Minimum novelty_score to include. 0 returns everything ranked; 0.5 filters to unusual only; 0.9 filters to extreme only. Default 0."),status:A.enum(["active","superseded","revoked","any"]).optional().describe('Underlying finding lifecycle. Default "active".')},async({limit:t,min_score:e,status:r})=>{try{let s=new URLSearchParams;t!==void 0&&s.set("limit",String(t)),e!==void 0&&s.set("min_score",String(e)),r&&s.set("status",r);let a=s.toString()?`?${s.toString()}`:"",i=await n.get(`/api/insights/distilled${a}`);if(i.error)return de(`Couldn't list insights: ${i.error}`);let c=i.data??[];return et({ok:!0,count:c.length,limit:i.limit,min_score:i.min_score,insights:c,note:c.length===0?"Nothing ranked above the score floor. Lower min_score, or wait for the next experiment tick to populate novelty data.":`${c.length} insight(s), most surprising first. novelty_reason explains why each rose to the top; use ohwow_list_findings with the experiment_id + subject to dig into the full ledger trail.`})}catch(s){return de(`Error: ${s instanceof Error?s.message:"Unknown error"}`)}})}import{z as x}from"zod";function rt(o,n){o.tool("ohwow_list_events","[Calendar] List calendar events for a date range. Returns title, time, location, attendees, and status.",{start:x.string().describe("Start of range (ISO date or datetime)"),end:x.string().optional().describe("End of range (ISO date or datetime). Defaults to end of start date."),account_id:x.string().optional().describe("Filter by calendar account ID"),limit:x.number().optional().describe("Max results (default: 50)")},async({start:t,end:e,account_id:r,limit:s})=>{try{let a=new URLSearchParams;a.set("start",t),e&&a.set("end",e),r&&a.set("account_id",r),s&&a.set("limit",String(s));let i=await n.get(`/api/calendar/events?${a}`);return{content:[{type:"text",text:JSON.stringify(i.data||i,null,2)}]}}catch(a){return{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_create_event","[Calendar] Create a calendar event with title, time, location, and attendees.",{title:x.string().describe("Event title"),start_at:x.string().describe("Start time (ISO 8601)"),end_at:x.string().describe("End time (ISO 8601)"),description:x.string().optional().describe("Event description"),location:x.string().optional().describe("Event location"),attendees:x.array(x.object({email:x.string(),name:x.string().optional()})).optional().describe("List of attendees"),all_day:x.boolean().optional().describe("Whether this is an all-day event"),account_id:x.string().optional().describe("Calendar account ID")},async({title:t,start_at:e,end_at:r,description:s,location:a,attendees:i,all_day:c,account_id:l})=>{try{let p={title:t,start_at:e,end_at:r};s&&(p.description=s),a&&(p.location=a),i&&(p.attendees=i),c!==void 0&&(p.all_day=c),l&&(p.account_id=l);let u=await n.post("/api/calendar/events",p);return{content:[{type:"text",text:JSON.stringify(u,null,2)}]}}catch(p){return{content:[{type:"text",text:`Error: ${p instanceof Error?p.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_find_availability","[Calendar] Find free time slots across calendars. Returns available slots during business hours (9am-5pm, weekdays).",{start:x.string().describe("Start of search range (ISO date or datetime)"),end:x.string().describe("End of search range (ISO date or datetime)"),duration_minutes:x.number().optional().describe("Slot duration in minutes (default: 30)")},async({start:t,end:e,duration_minutes:r})=>{try{let s=new URLSearchParams;s.set("start",t),s.set("end",e),r&&s.set("duration_minutes",String(r));let a=await n.get(`/api/calendar/availability?${s}`);return{content:[{type:"text",text:JSON.stringify(a.data||a,null,2)}]}}catch(s){return{content:[{type:"text",text:`Error: ${s instanceof Error?s.message:"Unknown error"}`}],isError:!0}}})}import{z as T}from"zod";function ot(o,n){o.tool("ohwow_search_emails","[Email] Search and filter email messages. Returns id, from_address, from_name, subject, snippet, is_read, is_starred, has_attachments, and received_at. Supports filtering by sender, subject, date range, and read status. Fast direct query (not AI-powered). For an AI-generated summary of unread emails, use ohwow_summarize_inbox instead.",{search:T.string().optional().describe("Full-text search across subject, sender, and snippet"),from:T.string().optional().describe("Filter by sender email (partial match)"),subject:T.string().optional().describe("Filter by subject (partial match)"),after:T.string().optional().describe("Only emails received after this ISO 8601 datetime (e.g. 2026-04-15T00:00:00Z)"),before:T.string().optional().describe("Only emails received before this ISO 8601 datetime (e.g. 2026-04-16T23:59:59Z)"),is_read:T.boolean().optional().describe("Filter by read status"),limit:T.number().optional().describe("Max results (default: 50)")},async({search:t,from:e,subject:r,after:s,before:a,is_read:i,limit:c})=>{try{let l=new URLSearchParams;t&&l.set("search",t),e&&l.set("from",e),r&&l.set("subject",r),s&&l.set("after",s),a&&l.set("before",a),i!==void 0&&l.set("is_read",i?"1":"0"),c&&l.set("limit",String(c));let p=l.toString(),u=await n.get(`/api/email/messages${p?`?${p}`:""}`);return{content:[{type:"text",text:JSON.stringify(u.data||u,null,2)}]}}catch(l){return{content:[{type:"text",text:`Error: ${l instanceof Error?l.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_summarize_inbox","[Email] AI-generated summary of unread or recent emails. Groups by priority: urgent items first, then items needing a response, then FYI. Highlights action items. Routes through orchestrator (~30s). For raw email data, use ohwow_search_emails instead.",{hours:T.number().optional().describe("Look back N hours (default: 24)")},async({hours:t})=>{try{let e=t||24;return{content:[{type:"text",text:await n.postSSE("/api/chat",{message:`Use the list_emails tool to fetch unread emails from the last ${e} hours. Then summarize them grouped by priority: urgent items first, then items needing a response, then FYI items. For each email mention the sender, subject, and what action (if any) is needed. Keep it concise.`},3e4)||"No unread emails found"}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_draft_reply","[Email] Draft a reply to an email message. The AI writes the reply based on your instructions.",{message_id:T.string().describe("ID of the email to reply to"),instructions:T.string().describe('What to say in the reply (e.g. "Accept the meeting, suggest Tuesday instead")'),tone:T.enum(["formal","friendly","brief"]).optional().describe("Tone of the reply (default: friendly)")},async({message_id:t,instructions:e,tone:r})=>{try{let s=await n.get(`/api/email/messages/${t}`),a=s.data||s,i=r==="formal"?"Use a formal, professional tone.":r==="brief"?"Keep it very short and direct.":"Use a warm, friendly tone.",c=await n.postSSE("/api/chat",{message:`Draft an email reply. Original email from ${a.from_address} with subject "${a.subject}": "${a.snippet||a.body_text}". Instructions: ${e}. ${i} Return only the reply body text, no subject line.`},2e4),l=await n.post("/api/email/drafts",{reply_to_id:t,to_addresses:[a.from_address],subject:`Re: ${a.subject||""}`,body_text:c});return{content:[{type:"text",text:JSON.stringify({draft:l,reply_text:c},null,2)}]}}catch(s){return{content:[{type:"text",text:`Error: ${s instanceof Error?s.message:"Unknown error"}`}],isError:!0}}})}import{z as er}from"zod";function nt(o,n){o.tool("ohwow_daily_briefing","[Briefing] Morning digest combining today's calendar, pipeline status, pending tasks, stale leads, and revenue snapshot. The perfect way to start your day.",{date:er.string().optional().describe("Date for the briefing (ISO format). Defaults to today.")},async({date:t})=>{try{let e=t||new Date().toISOString().split("T")[0];return{content:[{type:"text",text:await n.postSSE("/api/chat",{message:`Generate a daily business briefing for ${e}. Gather and present:
8
+ [Timed out after ${e/1e3}s. The task may still be running. Check with ohwow_list_tasks.]`;throw c}finally{clearTimeout(s)}}};import{z as m}from"zod";function We(n,o){n.tool("ohwow_chat",'[Orchestrator] Send a message to the OHWOW orchestrator (88+ internal tools). Returns conversationId immediately and dispatches the turn in the background. Poll ohwow_get_chat until status !== "running" to read the final assistant message. Use this for: desktop control, automation creation, agent scheduling, approval management, agent state persistence, A2A protocol, PDF forms, media generation, and any multi-step request not covered by the direct tools. Do NOT use for simple listing or CRUD operations that have dedicated tools.',{message:m.string().describe("The message or instruction to send to the orchestrator"),sessionId:m.string().optional().describe("Optional conversation id to continue an existing session. Omit for a new conversation.")},async({message:t,sessionId:e})=>{try{let r=await o.post("/api/chat?async=1",{message:t,sessionId:e});return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_get_chat",'[Orchestrator] Poll an in-flight or completed orchestrator conversation by id. Returns { status, messages, last_error, ... }. Status: "running" = still in flight, keep polling. "done" = final assistant message is in messages[]. "error" = look at last_error. Use after ohwow_chat to wait for the turn to complete.',{conversationId:m.string().describe("The conversation id returned by ohwow_chat")},async({conversationId:t})=>{try{let e=await o.get(`/api/chat/${t}`);return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_list_agents","[Agents] List all agents in the OHWOW workspace with their status, role, and capabilities.",{},async()=>{try{let t=await o.get("/api/agents"),e=t.data||t;return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_run_agent","[Agents] Execute a specific agent with a prompt. Returns a task ID immediately (execution is async). Use ohwow_get_task to poll for status and result. Use ohwow_list_agents to find agent IDs.",{agentId:m.string().describe("The ID of the agent to run"),prompt:m.string().describe("The task or instruction for the agent")},async({agentId:t,prompt:e})=>{try{let r=await o.post("/api/tasks",{agentId:t,title:e});return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_get_task","[Tasks] Get the status and result of a task by its ID.",{taskId:m.string().describe("The task ID to look up")},async({taskId:t})=>{try{let e=await o.get(`/api/tasks/${t}`);return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_list_tasks","[Tasks] List recent tasks. Optionally filter by status or agent.",{status:m.string().optional().describe("Filter by status: pending, running, completed, failed"),agentId:m.string().optional().describe("Filter by agent ID"),limit:m.number().optional().describe("Max number of tasks to return (default: 20)")},async({status:t,agentId:e,limit:r})=>{try{let s=new URLSearchParams;t&&s.set("status",t),e&&s.set("agentId",e),r&&s.set("limit",String(r));let a=s.toString(),i=await o.get(`/api/tasks${a?`?${a}`:""}`);return{content:[{type:"text",text:JSON.stringify(i,null,2)}]}}catch(s){return{content:[{type:"text",text:`Error: ${s instanceof Error?s.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_workspace_status","[Workspace] Get workspace status: agent count, uptime, tier, and system stats.",{},async()=>{try{let t=await o.get("/api/dashboard/init");return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_llm","[LLM Organ] Invoke ohwow's model router for a specific sub-task. Pass a `purpose` (reasoning, generation, summarization, extraction, critique, translation, planning, classification, etc.) and either a `prompt` string OR a `messages` history array. Optionally pass `tools` to enable tool calls. ohwow resolves the agent's model_policy (if agentId is given), workspace defaults, and constraints, then picks a provider+model and runs the call. Returns { text, content: ContentBlock[], model_used, provider, purpose, tokens, cost_cents, latency_ms }. The caller is responsible for executing tool calls and looping by appending tool_result blocks to `messages` on the next call.",{purpose:m.enum(["orchestrator_chat","agent_task","planning","browser_automation","memory_extraction","ocr","workflow_step","simple_classification","desktop_control","reasoning","generation","summarization","extraction","critique","translation","embedding"]).optional().describe('Semantic purpose that drives routing. Defaults to "reasoning".'),prompt:m.string().optional().describe("The user prompt to send to the selected model. Omit when passing `messages` instead."),messages:m.array(m.object({role:m.enum(["user","assistant","system","tool"]),content:m.union([m.string(),m.array(m.record(m.string(),m.unknown()))]),tool_calls:m.array(m.object({id:m.string(),type:m.literal("function"),function:m.object({name:m.string(),arguments:m.string()})})).optional(),tool_call_id:m.string().optional()})).optional().describe("Multi-turn conversation history. Preferred over `prompt` for tool-use loops \u2014 append assistant tool_calls and user tool_result blocks each iteration."),tools:m.array(m.object({name:m.string(),description:m.string().optional(),input_schema:m.record(m.string(),m.unknown()).optional()})).optional().describe("Optional tool definitions. When non-empty, the response `content` array includes `tool_use` blocks the caller must execute and feed back via `messages`."),tool_choice:m.enum(["auto","required","none"]).optional().describe("Tool selection mode. Defaults to provider default."),system:m.string().optional().describe("Optional system prompt."),agentId:m.string().optional().describe("Agent ID to load model_policy from. Omit to use workspace defaults only."),max_tokens:m.number().optional().describe("Maximum output tokens."),temperature:m.number().optional().describe("Sampling temperature."),local_only:m.boolean().optional().describe("Force local inference (clamps modelSource to local)."),prefer_model:m.string().optional().describe("Call-site model override; wins over agent policy."),max_cost_cents:m.number().optional().describe("Advisory cost ceiling in cents."),difficulty:m.enum(["simple","moderate","complex"]).optional().describe("Difficulty hint for escalation.")},async t=>{try{let e=await o.post("/api/llm",t);return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as R}from"zod";function Ne(n,o){n.tool("ohwow_list_contacts","[CRM] List contacts in the workspace. Returns id, name, email, phone, company, contact_type (lead/customer/partner), status, tags, and notes. Use the `search` param for quick filtering by name/email/company. For deeper full-text search across all fields including notes, use ohwow_search_contacts instead.",{search:R.string().optional().describe("Filter by name, email, or company"),limit:R.number().optional().describe("Max results (default: 50)")},async({search:t,limit:e})=>{try{let r=new URLSearchParams;t&&r.set("search",t),e&&r.set("limit",String(e));let s=r.toString(),a=await o.get(`/api/contacts${s?`?${s}`:""}`),i=a.data||a;return{content:[{type:"text",text:JSON.stringify(i,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_get_contact",`[CRM] Full dossier for one contact: the row itself (parsed custom_fields + tags), the complete event timeline (qualifier, outreach, attribution hits), linked deals, the outreach_token, and \u2014 for X-sourced leads \u2014 every post we've observed from this handle (rehydrated from x-authors-ledger). This is the one-call "who is this person and how did we find them" view. Use it before acting on a lead so the decision sits on top of their full provenance.`,{id:R.string().min(1).describe("Contact id (from ohwow_list_contacts or ohwow_search_contacts).")},async({id:t})=>{try{let e=await o.get(`/api/contacts/${encodeURIComponent(t)}/dossier`);return{content:[{type:"text",text:JSON.stringify(e.data??e,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_create_contact",'[CRM] Add a new contact to the CRM. Returns the created contact with id, timestamps, and all fields. Contacts default to type "lead" and status "active".',{name:R.string().describe("Contact full name"),email:R.string().optional().describe("Email address"),phone:R.string().optional().describe("Phone number"),company:R.string().optional().describe("Company or organization"),tags:R.array(R.string()).optional().describe("Tags for categorization"),notes:R.string().optional().describe("Additional notes about the contact")},async({name:t,email:e,phone:r,company:s,tags:a,notes:i})=>{try{let c={name:t};e&&(c.email=e),r&&(c.phone=r),s&&(c.company=s),a&&(c.tags=a),i&&(c.notes=i);let l=await o.post("/api/contacts",c);return{content:[{type:"text",text:JSON.stringify(l,null,2)}]}}catch(c){return{content:[{type:"text",text:`Error: ${c instanceof Error?c.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_search_contacts","[CRM] Deep full-text search across all contact fields including notes and custom fields. Routes through the orchestrator (slower, ~15s). For simple filtering by name/email/company, use ohwow_list_contacts with the `search` param instead.",{query:R.string().describe("Search query")},async({query:t})=>{try{return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:`Use the search_contacts tool with query: "${t}". Return the results as-is.`},15e3)||"No contacts found"}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as G}from"zod";function Le(n,o){n.tool("ohwow_list_workflows","[Workflows] List all workflows in the workspace. Returns id, name, description, step definitions, enabled status, and last run info.",{},async()=>{try{let t=await o.get("/api/workflows"),e=t.data||t;return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_run_workflow","[Workflows] Execute a workflow by ID. Use ohwow_list_workflows to find IDs. May take up to 60 seconds depending on complexity.",{workflowId:G.string().describe("The workflow ID to execute"),variables:G.record(G.string(),G.unknown()).optional().describe("Input variables for the workflow")},async({workflowId:t,variables:e})=>{try{let r=e?` with variables: ${JSON.stringify(e)}`:"";return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:`Use the run_workflow tool with workflowId: "${t}"${r}.`},6e4)||"Workflow started"}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_list_automations","[Automations] List all automations with their trigger conditions, action types, cooldown settings, fire counts, and enabled status.",{},async()=>{try{let t=await o.get("/api/automations"),e=t.data||t;return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_run_automation","[Automations] Manually trigger an automation by ID. Use ohwow_list_automations to find IDs.",{automationId:G.string().describe("The automation ID to trigger")},async({automationId:t})=>{try{let e=await o.post(`/api/automations/${t}/execute`,{});return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as je}from"zod";function Fe(n,o){n.tool("ohwow_list_projects","[Projects] List all projects with id, name, description, status, task counts (total/completed), and timestamps.",{},async()=>{try{let t=await o.get("/api/projects"),e=t.data||t;return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_create_project","[Projects] Create a new project for organizing work.",{name:je.string().describe("Project name"),description:je.string().optional().describe("Project description")},async({name:t,description:e})=>{try{let r={name:t};e&&(r.description=e);let s=await o.post("/api/projects",r);return{content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_list_goals","[Goals] List workspace goals with progress tracking (target, current value, percentage). Routes through orchestrator (~15s).",{},async()=>{try{return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:"Use the list_goals tool. Return the results as-is."},15e3)||"No goals found"}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}})}import{z as q}from"zod";function He(n,o){n.tool("ohwow_list_knowledge","[Knowledge] List documents in the knowledge base. Returns id, title, type, status, chunk count, and created_at by default. Hits the process directly over HTTP (~50ms), no orchestrator round-trip. Set `include_bodies=true` to also get the compiled text for every document \u2014 useful for embedding benchmarks and RAG evals. Be aware: body payload scales with doc size; use `limit` to cap the batch when bodies are requested.",{include_bodies:q.boolean().optional().describe("When true, each entry also includes `body` (compiled text), `tokens`, `contentHash`, `filename`, `sourceUrl`, and `processedAt`. Default: false \u2014 metadata only. The bodies payload can be large (hundreds of KB to a few MB for 100 docs), so always pair with `limit` for predictable response sizes."),limit:q.number().int().positive().optional().describe("Cap the number of rows returned. Defaults to no cap (all active documents). The server hard-caps at 500. Apply this whenever `include_bodies` is true.")},async({include_bodies:t,limit:e})=>{try{let r=new URLSearchParams;t&&r.set("include_bodies","1"),e!==void 0&&r.set("limit",String(e));let s=r.toString(),a=await o.get(`/api/knowledge${s?`?${s}`:""}`);if(a.error)return{content:[{type:"text",text:`Couldn't list knowledge: ${a.error}`}],isError:!0};let i=a.data??[],c={count:i.length,includeBodies:!!t,documents:i};return{content:[{type:"text",text:JSON.stringify(c,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_get_knowledge","[Knowledge] Fetch a single knowledge base document by id, including the compiled body text. Returns metadata (title, filename, fileType, fileSize, tokens, chunk_count, contentHash, status, sourceUrl, createdAt, processedAt) plus `body` (the full compiled text). Returns an error result when the id doesn't match an active document \u2014 use ohwow_list_knowledge to discover ids. Hits the process directly over HTTP (~50ms).",{id:q.string().describe("Document id from ohwow_list_knowledge.")},async({id:t})=>{try{let e=await o.get(`/api/knowledge/${encodeURIComponent(t)}`);return e.error||!e.data?{content:[{type:"text",text:e.error??`No knowledge document with id "${t}".`}],isError:!0}:{content:[{type:"text",text:JSON.stringify(e.data,null,2)}]}}catch(e){let r=e instanceof Error?e.message:"Unknown error";return/\b404\b/.test(r)?{content:[{type:"text",text:`No knowledge document with id "${t}". Use ohwow_list_knowledge to discover ids.`}],isError:!0}:{content:[{type:"text",text:`Error: ${r}`}],isError:!0}}}),n.tool("ohwow_search_knowledge","[Knowledge] Semantic (RAG) search across the knowledge base. Returns relevant document chunks ranked by similarity, with source attribution. Routes through orchestrator (~15s).",{query:q.string().describe("The search query")},async({query:t})=>{try{return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:`Use the search_knowledge tool with query: "${t}". Return the results as-is.`},15e3)||"No results found"}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_add_knowledge_url","[Knowledge] Add a web page to the knowledge base. Fetches, chunks, and embeds the content for later search.",{url:q.string().describe("The URL to ingest"),title:q.string().optional().describe("Optional title for the document")},async({url:t,title:e})=>{try{let r=e?` with title: "${e}"`:"";return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:`Use the add_knowledge_from_url tool with url: "${t}"${r}.`},3e4)||"Knowledge document added"}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}})}import{z as te}from"zod";function Je(n,o){n.tool("ohwow_deep_research","[Research] Multi-source web research with synthesis. Timing: quick ~30s, thorough ~60s, comprehensive ~120s.",{question:te.string().describe("The research question"),depth:te.enum(["quick","thorough","comprehensive"]).optional().describe("Research depth (default: thorough)")},async({question:t,depth:e})=>{try{let r=e?` with depth: "${e}"`:"";return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:`Use the deep_research tool with question: "${t}"${r}. Return the full research report.`},18e4)||"No research results"}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_scrape_url","[Research] Scrape a web page and return its structured content. Automatically handles anti-bot protection. Note: for X/Twitter posts use ohwow_fetch_x_post instead \u2014 ohwow_scrape_url renders the accessibility tree and X loads post text client-side, so the body usually comes back empty.",{url:te.string().describe("The URL to scrape")},async({url:t})=>{try{return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:`Use the scrape_url tool with url: "${t}". Return the scraped content.`},3e4)||"No content scraped"}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_fetch_x_post","[Research] Fetch a single X (Twitter) post's body, author, media, and engagement metrics. Use this instead of ohwow_scrape_url whenever you need the actual text of a tweet \u2014 tweets render client-side, so generic scraping returns the chrome, not the content. Critical for grounding an outreach DM (ohwow_draft_x_dm) in a specific idea the contact wrote, which is the voice rule for outbound X messaging. Accepts either a permalink (https://x.com/handle/status/123 or /handle/status/123) or a bare numeric tweet id.",{permalink_or_id:te.string().min(1).describe('Tweet permalink, URL, or bare numeric id. Examples: "https://x.com/user/status/2044523795206029525", "/user/status/2044523795206029525", "2044523795206029525".')},async({permalink_or_id:t})=>{try{let e=`/api/x/tweet/lookup?permalink=${encodeURIComponent(t)}`,r=await o.get(e);return r.error?{content:[{type:"text",text:`Couldn't fetch tweet: ${r.error}`}],isError:!0}:r.data?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}]}:{content:[{type:"text",text:"Tweet not found (private, deleted, or unparseable id)."}],isError:!0}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as re}from"zod";function qe(n,o){n.tool("ohwow_send_message","[Messaging] Send a message via WhatsApp or Telegram. Use ohwow_list_chats to find chat IDs. The channel must be connected in the ohwow dashboard first.",{channel:re.enum(["whatsapp","telegram"]).describe("Messaging channel"),chatId:re.string().describe("Chat or contact ID to send to"),message:re.string().describe("Message text to send")},async({channel:t,chatId:e,message:r})=>{try{let s=t==="whatsapp"?"send_whatsapp_message":"send_telegram_message";return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:`Use the ${s} tool to send this message to chat "${e}": ${r}`},15e3)||"Message sent"}]}}catch(s){return{content:[{type:"text",text:`Error: ${s instanceof Error?s.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_list_chats","[Messaging] List connected chats for WhatsApp or Telegram.",{channel:re.enum(["whatsapp","telegram"]).describe("Messaging channel")},async({channel:t})=>{try{let e=t==="whatsapp"?"list_whatsapp_chats":"list_telegram_chats";return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:`Use the ${e} tool. Return the results as-is.`},15e3)||"No chats found"}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}function Be(n,o){n.tool("ohwow_list_sites","[Cloud] List all sites on the ohwow.fun cloud dashboard with status and URLs. Requires cloud connection.",{},async()=>{try{let t=await o.get("/api/cloud/sites");if(t.cloudConnected===!1)return{content:[{type:"text",text:"Not connected to ohwow.fun. Run `ohwow connect` to link your cloud account."}]};let e=t.data||[];return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_list_integrations","[Cloud] List connected integrations (Gmail, GitHub, Stripe, etc.) and their status. Requires cloud connection.",{},async()=>{try{let t=await o.get("/api/cloud/integrations");if(t.cloudConnected===!1)return{content:[{type:"text",text:"Not connected to ohwow.fun. Run `ohwow connect` to link your cloud account."}]};let e=t.data||[];return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}})}import{z as ae}from"zod";import{join as ie,dirname as Bt}from"path";import{existsSync as Kt}from"fs";import{fileURLToPath as zt}from"url";import{join as se}from"path";import{homedir as Pt}from"os";import{spawn as Ut}from"child_process";import{openSync as It,existsSync as $t,statSync as Wt,renameSync as Nt,writeFileSync as no,unlinkSync as so,readFileSync as Lt}from"fs";import{readFileSync as At,writeFileSync as Yr,unlinkSync as Vr,existsSync as Mt,mkdirSync as Qr}from"fs";function oe(n){try{if(!Mt(n))return null;let o=At(n,"utf-8");return JSON.parse(o)}catch{return null}}function ne(n){try{return process.kill(n,0),!0}catch{return!1}}var jt=se(Pt(),".ohwow");function Ft(n){let o={};try{let t=Lt(n,"utf-8");for(let e of t.split(`
9
+ `)){let r=e.trim();if(!r||r.startsWith("#"))continue;let s=r.indexOf("=");if(s<1)continue;let a=r.slice(0,s).trim(),i=r.slice(s+1).trim();a&&(o[a]=i)}}catch{}return o}var Ht=10*1024*1024;function Jt(n){return se(n,"daemon.log")}function qt(n){try{if(!$t(n))return;Wt(n).size>Ht&&Nt(n,`${n}.1`)}catch{}}function ve(n){return se(n,"daemon.pid")}async function L(n,o){let t=oe(ve(n));if(!t)return{running:!1};if(!ne(t.pid))return{running:!1};let e=t.port||o;try{let r=await fetch(`http://localhost:${e}/health`,{signal:AbortSignal.timeout(2e3)});if(r.ok){let s=await r.json();if(s.status==="healthy"||s.status==="degraded")return{running:!0,pid:t.pid}}}catch{return{running:!0,pid:t.pid,healthy:!1}}return{running:!1}}function Ee(n,o,t){let e=Jt(t);qt(e);let r=It(e,"a"),a=n.endsWith(".ts")?["--import","tsx",n,"--daemon"]:[n,"--daemon"],c={...Ft(se(jt,"conductor-on.env")),...process.env,OHWOW_PORT:String(o)};process.env.OHWOW_WORKSPACE&&(c.OHWOW_WORKSPACE=process.env.OHWOW_WORKSPACE);let l=Ut(process.execPath,a,{detached:!0,windowsHide:!0,stdio:["ignore",r,r],env:c});return l.unref(),l.pid}async function Se(n,o=15e3){let t=Date.now()+o,e=300;for(;Date.now()<t;){try{if((await fetch(`http://localhost:${n}/health`,{signal:AbortSignal.timeout(1e3)})).ok)return!0}catch{}await new Promise(r=>setTimeout(r,e))}return!1}async function Oe(n){let o=oe(ve(n));if(!o||!ne(o.pid))return!1;if(process.platform==="win32"&&o.port)try{return await fetch(`http://localhost:${o.port}/shutdown`,{method:"POST",signal:AbortSignal.timeout(3e3)}),!0}catch{try{return process.kill(o.pid),!0}catch{return!1}}try{return process.kill(o.pid,"SIGTERM"),!0}catch(t){return t.code==="EPERM"&&V.error("Cannot stop daemon (PID %d): permission denied. It may be running as a different user.",o.pid),!1}}async function Re(n,o=5e3){let t=ve(n),e=Date.now()+o,r=200;for(;Date.now()<e;){let s=oe(t);if(!s||!ne(s.pid))return!0;await new Promise(a=>setTimeout(a,r))}return!1}function ce(){let n=W(),o=N(n.name)??J,t=Bt(zt(import.meta.url)),r=[ie(t,"..","..","index.js"),ie(t,"..","..","..","index.js"),ie(t,"..","..","..","index.ts"),ie(t,"..","..","index.ts")].find(s=>Kt(s))??null;return{workspaceName:n.name,dataDir:n.dataDir,port:o,entryPath:r}}async function C(n){let o=await L(n.dataDir,n.port);return{running:o.running,healthy:o.running&&o.healthy!==!1,pid:o.pid??null,port:n.port,dataDir:n.dataDir,entryPath:n.entryPath}}function Ke(n){n.tool("ohwow_daemon_status","[Process] Check whether the ohwow local process is running. Reports pid, port, health, and the resolved entry path. Does not modify state.",{},async()=>{try{let o=ce(),t=await C(o);return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}catch(o){return{content:[{type:"text",text:`Error: ${o instanceof Error?o.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_daemon_stop","[Process] Stop the running ohwow process gracefully (SIGTERM on Unix, /shutdown on Windows). Returns whether it actually stopped within the timeout.",{timeoutMs:ae.number().optional().describe("How long to wait for the process to stop before reporting failure. Default 5000ms.")},async({timeoutMs:o})=>{try{let t=ce();if(!(await L(t.dataDir,t.port)).running)return{content:[{type:"text",text:JSON.stringify({ok:!0,alreadyStopped:!0,...await C(t)},null,2)}]};if(!await Oe(t.dataDir))return{content:[{type:"text",text:JSON.stringify({ok:!1,error:"Could not send stop signal (permission denied or stale pid)",...await C(t)},null,2)}],isError:!0};let s=await Re(t.dataDir,o??5e3),a=await C(t);return{content:[{type:"text",text:JSON.stringify({ok:s,...a},null,2)}],isError:!s}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_daemon_start","[Process] Start the ohwow process in the background if it is not already running. Waits for the health endpoint before returning. No-op if the daemon is already healthy.",{timeoutMs:ae.number().optional().describe("How long to wait for the process to become healthy. Default 15000ms.")},async({timeoutMs:o})=>{try{let t=ce(),e=await L(t.dataDir,t.port);if(e.running&&e.healthy!==!1)return{content:[{type:"text",text:JSON.stringify({ok:!0,alreadyRunning:!0,...await C(t)},null,2)}]};if(!t.entryPath)return{content:[{type:"text",text:JSON.stringify({ok:!1,error:"Could not locate process entry (dist/index.js or src/index.ts)",...await C(t)},null,2)}],isError:!0};let r=Ee(t.entryPath,t.port,t.dataDir),s=await Se(t.port,o??15e3),a=await C(t);return{content:[{type:"text",text:JSON.stringify({ok:s,spawnedPid:r,entryPath:t.entryPath,...a},null,2)}],isError:!s}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_daemon_restart","[Process] Restart the ohwow process: stop if running, wait for the PID to clear, then spawn a fresh background instance and wait for health. Use this after rebuilding dist/index.js so the running process picks up the new code. Safe to call even when the daemon is already dead \u2014 it will just start a fresh one.",{stopTimeoutMs:ae.number().optional().describe("Timeout for the stop phase. Default 5000ms."),startTimeoutMs:ae.number().optional().describe("Timeout for the start/health phase. Default 15000ms.")},async({stopTimeoutMs:o,startTimeoutMs:t})=>{try{let e=ce();if(!e.entryPath)return{content:[{type:"text",text:JSON.stringify({ok:!1,error:"Could not locate process entry (dist/index.js or src/index.ts)",...await C(e)},null,2)}],isError:!0};let r=await L(e.dataDir,e.port),s={wasRunning:r.running,previousPid:r.pid??null};if(r.running){let l=await Oe(e.dataDir);if(s.stopSignalSent=l,!l)return{content:[{type:"text",text:JSON.stringify({ok:!1,phase:"stop",error:"Could not send stop signal",...s,...await C(e)},null,2)}],isError:!0};let p=await Re(e.dataDir,o??5e3);if(s.stopped=p,!p)return{content:[{type:"text",text:JSON.stringify({ok:!1,phase:"stop",error:"Previous process did not exit within timeout",...s,...await C(e)},null,2)}],isError:!0}}let a=Ee(e.entryPath,e.port,e.dataDir);s.spawnedPid=a;let i=await Se(e.port,t??15e3);s.ready=i;let c=await C(e);return{content:[{type:"text",text:JSON.stringify({ok:i,phase:i?"ready":"start",...s,...c},null,2)}],isError:!i}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as Gt}from"zod";async function Xt(){let n=W().name,o=Array.from(new Set([...$e(),z])).sort(),t=[];for(let e of o){let r=be(e),s=N(e),a=I(e),i=!1,c;if(s!==null)try{let l=await L(a.dataDir,s);i=l.running,c=l.pid}catch{}t.push({name:e,focused:e===n,mode:r?r.mode:"default",...r?.displayName?{displayName:r.displayName}:{},port:s,running:i,...c!==void 0?{pid:c}:{},...r?.cloudWorkspaceId?{cloudWorkspaceId:r.cloudWorkspaceId}:{}})}return t}function ze(n,o){n.tool("ohwow_workspace_list","[Workspace] List every local workspace under ~/.ohwow/workspaces with mode, port, and live running status. Use this to discover what workspaces exist before switching with ohwow_workspace_use.",{},async()=>{try{let t=await Xt();return{content:[{type:"text",text:JSON.stringify({workspaces:t},null,2)}]}}catch(t){return{content:[{type:"text",text:`Error listing workspaces: ${t instanceof Error?t.message:t}`}],isError:!0}}}),n.tool("ohwow_workspace_use","[Workspace] Switch which workspace this MCP session targets. Reconnects the api-client to the named workspace's daemon (must already be running \u2014 start it with `ohwow workspace start <name>` from a terminal first if needed). Also updates ~/.ohwow/current-workspace so future sessions default to it. All subsequent tool calls in this MCP session will hit the new workspace.",{name:Gt.string().describe('Workspace name (must already exist under ~/.ohwow/workspaces or be the legacy "default")')},async({name:t})=>{try{let e=W().name,r=await o.switchTo(t);return{content:[{type:"text",text:JSON.stringify({ok:!0,switched:!0,previous:e,current:r.workspaceName,port:r.port,message:`MCP session now targets workspace "${r.workspaceName}" on port ${r.port}. Future MCP launches will also default to this workspace.`},null,2)}]}}catch(e){return{content:[{type:"text",text:JSON.stringify({ok:!1,error:e instanceof Error?e.message:String(e)},null,2)}],isError:!0}}})}import{z as E}from"zod";var Yt=`Workspace-unique identifier for this MCP server (alphanumeric, dashes, underscores). Used as the namespace prefix when the server's tools are exposed to agents (e.g. name="avenued" \u2192 tools become mcp__avenued__<tool>).`,Vt='SENSITIVE. HTTP headers sent with every request to the MCP server, e.g. { "Authorization": "Bearer sk-..." }. Stored encrypted-equivalent in the workspace DB (never returned by list, never logged in plaintext, never exposed to agent context \u2014 injected at the transport layer).',Qt='SENSITIVE. Environment variables for the stdio subprocess, e.g. { "GITHUB_TOKEN": "ghp_..." }. Stored in the workspace DB and passed to the child process at spawn time. Never returned by list, never logged in plaintext, never exposed to agent context.';function Ge(n,o){n.tool("ohwow_add_mcp_server","[MCP] Register a third-party MCP server in the current workspace. Its tools become available to all agents (and the orchestrator) on the next turn. Credentials in `headers` or `env` are SENSITIVE \u2014 they stay on the local process, never transit through the LLM, and are never returned by list/inspect calls. Use for hooking external platforms (Stripe, GitHub, Notion, custom internal MCPs) into an ohwow workspace.",{name:E.string().describe(Yt),transport:E.enum(["http","stdio"]).describe('Transport type. "http" for Streamable HTTP servers (most hosted MCPs); "stdio" for local subprocess servers launched with command+args.'),url:E.string().optional().describe('Required for transport="http". Full URL of the MCP endpoint, e.g. https://example.com/api/mcp. Must be a publicly routable HTTPS URL \u2014 private/link-local/metadata IPs are rejected.'),command:E.string().optional().describe('Required for transport="stdio". Executable to run, e.g. "npx" or "node".'),args:E.array(E.string()).optional().describe('For transport="stdio". Command-line arguments to pass to the executable.'),headers:E.record(E.string(),E.string()).optional().describe(Vt),env:E.record(E.string(),E.string()).optional().describe(Qt),description:E.string().optional().describe("Human-readable description of what this server does. Shown in ohwow_list_mcp_servers output."),enabled:E.boolean().optional().describe("Whether the server should be connected on process startup. Default: true.")},async({name:t,transport:e,url:r,command:s,args:a,headers:i,env:c,description:l,enabled:p})=>{try{let u={name:t,transport:e};r&&(u.url=r),s&&(u.command=s),a&&(u.args=a),i&&(u.headers=i),c&&(u.env=c),l&&(u.description=l),p===!1&&(u.enabled=!1);let h=await o.post("/api/mcp/servers",u);return h.error?{content:[{type:"text",text:`Couldn't register MCP server: ${h.error}`}],isError:!0}:{content:[{type:"text",text:JSON.stringify({ok:!0,server:h.server,note:"Server registered. Credentials stored on the process and redacted from this response. Call ohwow_test_mcp_server to verify connectivity and list exposed tools."},null,2)}]}}catch(u){return{content:[{type:"text",text:`Error: ${u instanceof Error?u.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_list_mcp_servers",'[MCP] List all third-party MCP servers registered in the current workspace. Response shows name, transport, URL (for HTTP) or command (for stdio), description, enabled flag, and \u2014 for servers with credentials \u2014 the header/env KEY names replaced with "<set>". Actual credential values are NEVER returned. Does NOT perform live connections; use ohwow_test_mcp_server for that.',{},async()=>{try{let e=(await o.get("/api/mcp/servers")).servers??[];return{content:[{type:"text",text:JSON.stringify({count:e.length,servers:e},null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_remove_mcp_server","[MCP] Remove a registered MCP server from the current workspace by name. Disconnects the server immediately and drops its tools from the agent tool surface on the next turn. The stored credentials are deleted along with the config.",{name:E.string().describe("Name of the MCP server to remove (must match a name from ohwow_list_mcp_servers).")},async({name:t})=>{try{let e=await o.del(`/api/mcp/servers/${encodeURIComponent(t)}`);return e.error?{content:[{type:"text",text:`Couldn't remove MCP server: ${e.error}`}],isError:!0}:{content:[{type:"text",text:JSON.stringify({ok:!0,removed:e.removed??t},null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_test_mcp_server","[MCP] Verify a registered MCP server connects, and return the names of tools it exposes. Uses the stored config (including credentials) but returns only tool NAMES \u2014 no schemas, no credential echo, no connection details. Use this right after ohwow_add_mcp_server to confirm the server is reachable and authenticated, and to discover what tools will become available to agents.",{name:E.string().describe("Name of a registered MCP server (from ohwow_list_mcp_servers).")},async({name:t})=>{try{let e=await o.post(`/api/mcp/servers/${encodeURIComponent(t)}/test`,{});return e.success?{content:[{type:"text",text:JSON.stringify({ok:!0,toolCount:e.toolCount??0,toolNames:e.toolNames??[],latencyMs:e.latencyMs},null,2)}]}:{content:[{type:"text",text:JSON.stringify({ok:!1,error:e.error??"Connection failed",latencyMs:e.latencyMs},null,2)}],isError:!0}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as g}from"zod";var Zt="Workspace-unique identifier for this agent. Alphanumeric plus dashes and underscores only (no spaces). Used by ohwow_get_agent, ohwow_update_agent, ohwow_delete_agent, and ohwow_run_agent to look up the row.",er="The agent's core instructions. Stored verbatim and injected as the system message on every task run. Business-sensitive context belongs here; it is scoped to the current workspace and is NOT returned by ohwow_list_agents (which returns only summary fields). Use ohwow_get_agent to read it back.",tr='Explicit list of tool names the agent is allowed to call. If omitted, the agent inherits the workspace default tool surface. If provided, the agent sees ONLY these tools. Names must be either internal tool identifiers (e.g. "list_tasks", "scrape_url") or external MCP-server tools in the "mcp__<server>__<tool>" shape. Unknown internal names are rejected at create time so misconfigured agents fail fast.';async function B(n,o){return((await n.get("/api/agents")).data??[]).find(r=>r.name===o)??null}function w(n){return{content:[{type:"text",text:n}],isError:!0}}function j(n){return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}function Xe(n,o){n.tool("ohwow_create_agent","[Agents] Create a new agent in the current workspace. Writes directly to the agents table via the local process. Use this instead of ohwow_chat for agent creation \u2014 the orchestrator has no typed create primitive and will loop on ambient-state inspection if asked to create an agent via chat.",{name:g.string().describe(Zt),displayName:g.string().optional().describe("Human-readable label shown in UIs. Defaults to `name` when omitted."),description:g.string().optional().describe("Short summary of what the agent does. Shown in list views and agent pickers."),systemPrompt:g.string().describe(er),toolAllowlist:g.array(g.string()).optional().describe(tr),webSearchEnabled:g.boolean().optional().describe('Whether the built-in web_search tool should be available to this agent. Defaults to false \u2014 conservative default so narrow allowlists do not accidentally grant web access. When `toolAllowlist` is set, this flag is overridden by the allowlist: the agent sees web_search only if "web_search" is explicitly in the list.'),role:g.string().optional().describe('Free-text role label for categorization (e.g. "analyst", "writer"). Defaults to "assistant".'),enabled:g.boolean().optional().describe("Whether the agent is enabled and eligible to run. Default: true."),scheduled:g.object({cron:g.string().describe("Cron expression (5- or 6-field)."),timezone:g.string().optional().describe('IANA timezone name (e.g. "America/New_York"). Defaults to the workspace tz.')}).optional().describe("Optional cron schedule. Stored with the agent but not yet wired to the scheduler \u2014 runs must be triggered manually via ohwow_run_agent for now.")},async({name:t,displayName:e,description:r,systemPrompt:s,toolAllowlist:a,webSearchEnabled:i,role:c,enabled:l,scheduled:p})=>{try{let u={name:t,system_prompt:s};e!==void 0&&(u.display_name=e),r!==void 0&&(u.description=r),c!==void 0&&(u.role=c),l!==void 0&&(u.enabled=l),p!==void 0&&(u.scheduled=p);let h={};a!==void 0&&(h.tools_enabled=a,h.tools_mode="allowlist"),i!==void 0&&(h.web_search_enabled=i),Object.keys(h).length>0&&(u.config=h);let k=await o.post("/api/agents",u);return k.error?w(`Couldn't create agent: ${k.error}`):j({ok:!0,agent:k.data,note:"Agent created. Use ohwow_run_agent with this agent's name or id to dispatch a task."})}catch(u){return w(`Error: ${u instanceof Error?u.message:"Unknown error"}`)}}),n.tool("ohwow_get_agent","[Agents] Get an agent's full configuration by name or id, including the system prompt, tool allowlist, role, schedule, enabled flag, and timestamps. Use this instead of ohwow_list_agents when you need to inspect or iterate on a specific agent's system prompt \u2014 list_agents returns summary rows and does not include the prompt.",{name:g.string().optional().describe("Workspace-unique agent name. Provide this OR `id`."),id:g.string().optional().describe("Agent UUID. Provide this OR `name`.")},async({name:t,id:e})=>{try{if(!t&&!e)return w("Provide either `name` or `id`.");let r=e;if(!r&&t){let a=await B(o,t);if(!a)return w(`No agent named "${t}" in this workspace.`);r=a.id}let s=await o.get(`/api/agents/${encodeURIComponent(r)}`);return s.error||!s.data?w(s.error??"Agent not found"):j({ok:!0,agent:s.data})}catch(r){return w(`Error: ${r instanceof Error?r.message:"Unknown error"}`)}}),n.tool("ohwow_update_agent","[Agents] Update fields on an existing agent. Use this to iterate on a system prompt, tighten a tool allowlist, rename, or toggle enabled. Identify the agent by `name` or `id`. Any field left undefined is untouched. Agents never pin a model \u2014 the router picks per task.",{name:g.string().optional().describe("Workspace-unique agent name (provide this OR `id`)."),id:g.string().optional().describe("Agent UUID (provide this OR `name`)."),newName:g.string().optional().describe("Rename the agent. Must remain workspace-unique and match the alphanumeric+dash+underscore constraint."),displayName:g.string().optional().describe("Updated human-readable label."),description:g.string().optional().describe("Updated short summary."),systemPrompt:g.string().optional().describe("Replace the system prompt entirely."),toolAllowlist:g.array(g.string()).optional().describe("Replace the tool allowlist. Same validation rules as ohwow_create_agent."),webSearchEnabled:g.boolean().optional().describe("Toggle the built-in web_search tool. Overridden by the allowlist when one is active \u2014 see ohwow_create_agent."),role:g.string().optional().describe("Updated role label."),enabled:g.boolean().optional().describe("Toggle the agent on/off."),scheduled:g.object({cron:g.string(),timezone:g.string().optional()}).optional().describe("Replace the cron schedule.")},async({name:t,id:e,newName:r,displayName:s,description:a,systemPrompt:i,toolAllowlist:c,webSearchEnabled:l,role:p,enabled:u,scheduled:h})=>{try{if(!t&&!e)return w("Provide either `name` or `id`.");let k=e;if(!k&&t){let O=await B(o,t);if(!O)return w(`No agent named "${t}" in this workspace.`);k=O.id}let b={};r!==void 0&&(b.name=r),a!==void 0&&(b.description=a),i!==void 0&&(b.system_prompt=i),p!==void 0&&(b.role=p),u!==void 0&&(b.enabled=u),s!==void 0&&(b.display_name=s),h!==void 0&&(b.scheduled=h);let v={};if(c!==void 0&&(v.tools_enabled=c,v.tools_mode="allowlist"),l!==void 0&&(v.web_search_enabled=l),Object.keys(v).length>0&&(b.config=v),Object.keys(b).length===0)return w("No fields provided to update.");let M=await o.patch(`/api/agents/${encodeURIComponent(k)}`,b);return M.error?w(`Couldn't update agent: ${M.error}`):j({ok:!0,agent:M.data})}catch(k){return w(`Error: ${k instanceof Error?k.message:"Unknown error"}`)}}),n.tool("ohwow_grant_agent_path","[Agents] Grant an agent permission to read/write files under a local directory. Writes a row to agent_file_access_paths that the FileAccessGuard reads on every task run, so the agent's filesystem tools (local_read_file, local_write_file, run_bash, etc.) will accept paths inside the granted directory. Without this, a narrowly-scoped agent hits \"path outside allowed directories\" and the task either fails the hallucination gate or routes to needs_approval. The process validates that the path exists, is a directory, lives inside the user's home, and isn't a sensitive subdirectory like .ssh or .gnupg. Identify the agent by `name` or `id`.",{name:g.string().optional().describe("Workspace-unique agent name (provide this OR `id`)."),id:g.string().optional().describe('Agent UUID, or the literal string "__orchestrator__" to grant paths to the orchestrator itself (provide this OR `name`).'),path:g.string().describe("Absolute directory path to grant access to. Must exist, be a directory, live inside the user's home, and not be under .ssh, .gnupg, /etc, /var, /usr, etc. Tildes are not expanded \u2014 pass a fully-resolved path."),label:g.string().optional().describe('Optional human-readable label shown in UIs (e.g. "living docs (diary)", "workspace data"). Purely cosmetic.')},async({name:t,id:e,path:r,label:s})=>{try{if(!t&&!e)return w("Provide either `name` or `id`.");let a=e;if(!a&&t){let c=await B(o,t);if(!c)return w(`No agent named "${t}" in this workspace.`);a=c.id}let i=await o.post(`/api/agents/${encodeURIComponent(a)}/file-access`,{path:r,label:s});return i.error?w(`Couldn't grant path: ${i.error}`):j({ok:!0,agent:t??a,path:i.data?.path??r,label:i.data?.label??s??null,note:"Path granted. FileAccessGuard re-reads this table on every task run, so the next run will see the new access."})}catch(a){return w(`Error: ${a instanceof Error?a.message:"Unknown error"}`)}}),n.tool("ohwow_list_agent_paths","[Agents] List all filesystem paths granted to an agent. Returns rows from agent_file_access_paths with id, path, label, and created_at. Use the returned `id` with ohwow_revoke_agent_path to remove a specific row. Identify the agent by `name` or `id`.",{name:g.string().optional().describe("Workspace-unique agent name (provide this OR `id`)."),id:g.string().optional().describe('Agent UUID, or the literal string "__orchestrator__" for orchestrator-scoped paths (provide this OR `name`).')},async({name:t,id:e})=>{try{if(!t&&!e)return w("Provide either `name` or `id`.");let r=e;if(!r&&t){let a=await B(o,t);if(!a)return w(`No agent named "${t}" in this workspace.`);r=a.id}let s=await o.get(`/api/agents/${encodeURIComponent(r)}/file-access`);return s.error?w(`Couldn't list paths: ${s.error}`):j({ok:!0,agent:t??r,paths:s.data??[]})}catch(r){return w(`Error: ${r instanceof Error?r.message:"Unknown error"}`)}}),n.tool("ohwow_revoke_agent_path","[Agents] Revoke an agent's access to a filesystem path. Identify the agent by `name` or `id`, then identify the row by either `pathId` (from ohwow_list_agent_paths) or `path` (exact match \u2014 the tool lists and resolves locally). Idempotent on a missing row.",{name:g.string().optional().describe("Workspace-unique agent name (provide this OR `id`)."),id:g.string().optional().describe('Agent UUID, or the literal string "__orchestrator__" (provide this OR `name`).'),pathId:g.string().optional().describe("The row id from ohwow_list_agent_paths. Takes precedence over `path` when both are provided."),path:g.string().optional().describe("Absolute directory path. The tool lists existing grants and deletes the matching row. Used when the caller doesn't know the row id. Exact-match, fully-resolved path.")},async({name:t,id:e,pathId:r,path:s})=>{try{if(!t&&!e)return w("Provide either `name` or `id`.");if(!r&&!s)return w("Provide either `pathId` or `path`.");let a=e;if(!a&&t){let l=await B(o,t);if(!l)return w(`No agent named "${t}" in this workspace.`);a=l.id}let i=r;if(!i&&s){let l=await o.get(`/api/agents/${encodeURIComponent(a)}/file-access`);if(l.error)return w(`Couldn't list paths for match: ${l.error}`);let p=(l.data??[]).find(u=>u.path===s);if(!p)return w(`No granted path matches "${s}" for this agent.`);i=p.id}let c=await o.del(`/api/agents/${encodeURIComponent(a)}/file-access/${encodeURIComponent(i)}`);return c.error?w(`Couldn't revoke path: ${c.error}`):j({ok:!0,agent:t??a,revoked:i})}catch(a){return w(`Error: ${a instanceof Error?a.message:"Unknown error"}`)}}),n.tool("ohwow_delete_agent","[Agents] Delete an agent from the current workspace. Also drops the agent's memory rows. Identify by `name` or `id`. This is destructive \u2014 there is no undo.",{name:g.string().optional().describe("Workspace-unique agent name (provide this OR `id`)."),id:g.string().optional().describe("Agent UUID (provide this OR `name`).")},async({name:t,id:e})=>{try{if(!t&&!e)return w("Provide either `name` or `id`.");let r=e,s=t;if(!r&&t){let i=await B(o,t);if(!i)return w(`No agent named "${t}" in this workspace.`);r=i.id,s=i.name}let a=await o.del(`/api/agents/${encodeURIComponent(r)}`);return a.error?w(`Couldn't delete agent: ${a.error}`):j({ok:!0,deleted:s??r})}catch(r){return w(`Error: ${r instanceof Error?r.message:"Unknown error"}`)}})}import{z as le}from"zod";function de(n){return{content:[{type:"text",text:n}],isError:!0}}function Ye(n){return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}function Ve(n,o){n.tool("ohwow_list_permission_requests",'[Permissions] List every task currently paused on a filesystem or bash permission request. Returns the task id, agent name, attempted path, suggested exact + parent paths the operator can grant, the guard reason, and when the denial happened. These are the actionable items in the "agent wants access to X" queue. Pair with ohwow_approve_permission_request to resolve any row. Workspace-scoped to the focused process.',{},async()=>{try{let t=await o.get("/api/permission-requests");if(t.error)return de(`Couldn't list permission requests: ${t.error}`);let e=t.data??[];return Ye({ok:!0,count:e.length,requests:e,note:e.length===0?"No agents are waiting on a permission decision right now.":"Use ohwow_approve_permission_request with the task_id to approve once, approve always, or deny."})}catch(t){return de(`Error: ${t instanceof Error?t.message:"Unknown error"}`)}}),n.tool("ohwow_approve_permission_request",'[Permissions] Resolve a paused permission request. mode="once" grants the path for this single resumed run only and does NOT persist a row in agent_file_access_paths \u2014 use it for one-off operator approvals. mode="always" persists the grant by writing through the same agent_file_access_paths path that ohwow_grant_agent_path writes to, so future runs of this agent stay unblocked. mode="deny" terminates the original task with failure_category=permission_denied and does NOT spawn a resume. On approve, the original task is marked status=approved and a NEW child task is spawned (parent_task_id pointing back) that re-runs the work from scratch with the expanded guard. The child task id is returned so callers can poll its status.',{task_id:le.string().describe("Task id from ohwow_list_permission_requests. Must currently be in status=needs_approval with approval_reason=permission_denied; the route 409s otherwise."),mode:le.enum(["once","always","deny"]).describe('"once" = resume with an ephemeral grant on the child task only. "always" = persist the grant via agent_file_access_paths so future runs work. "deny" = terminate the task without resuming.'),scope:le.enum(["exact","parent","edit"]).optional().describe('Which path to grant (ignored for mode=deny). "exact" = the exact file/dir the agent asked for (default). "parent" = the parent directory of the exact path, so sibling files also work. "edit" = use the explicit `path` field below; the operator overrides what was suggested.'),path:le.string().optional().describe(`Required only when scope="edit". Absolute directory path to grant. Must live inside the user's home directory and not under a blocked system path.`)},async({task_id:t,mode:e,scope:r,path:s})=>{try{let a={mode:e};r!==void 0&&(a.scope=r),s!==void 0&&(a.path=s);let i=await o.post(`/api/permission-requests/${encodeURIComponent(t)}/approve`,a);return i.error?de(`Couldn't approve permission request: ${i.error}`):Ye({ok:!0,...i,note:e==="deny"?"Task marked failed with failure_category=permission_denied. No resume spawned.":`Resumed as task ${i.child_task_id?.slice(0,8)??"<unknown>"}. Use ohwow_get_task with the child id to see when it completes.`})}catch(a){return de(`Error: ${a instanceof Error?a.message:"Unknown error"}`)}})}import{z as rr}from"zod";function Qe(n){return{content:[{type:"text",text:n}],isError:!0}}function or(n){return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}function Ze(n,o){n.tool("ohwow_list_failing_triggers",'[Triggers] List every scheduled/event trigger that has been silently miscarrying. A trigger shows up here when its last N consecutive fires all failed (or landed in needs_approval without resolution) \u2014 the watchdog catches the class of "the daily diary cron has been broken for 2 weeks and nobody noticed." Returns the trigger id, name, consecutive_failures count, last_succeeded_at (or null if it has never succeeded since the watchdog was added), last_fired_at, trigger_type, enabled flag, and last_error string. Sorted worst-first. The threshold defaults to 3 but can be overridden via `threshold` to see near-misses too (e.g. `threshold: 1` returns every trigger with any failure at all).',{threshold:rr.number().int().positive().optional().describe("Minimum consecutive_failures to include in the list. Default: 3 (matches the daemon's TRIGGER_STUCK_THRESHOLD). Set to 1 to see every trigger with any failure on its most recent run.")},async({threshold:t})=>{try{let e=t!==void 0?`?threshold=${t}`:"",r=await o.get(`/api/failing-triggers${e}`);if(r.error)return Qe(`Couldn't list failing triggers: ${r.error}`);let s=r.data??[];return or({ok:!0,threshold:r.threshold,count:s.length,triggers:s,note:s.length===0?`No triggers are at or above ${r.threshold} consecutive failures. Scheduled automations are running cleanly right now.`:`${s.length} trigger(s) have been failing ${r.threshold}+ runs in a row. Investigate via ohwow_get_task on recent tasks for each, or disable the trigger via the UI if it's a known-broken integration.`})}catch(e){return Qe(`Error: ${e instanceof Error?e.message:"Unknown error"}`)}})}import{z as A}from"zod";function pe(n){return{content:[{type:"text",text:n}],isError:!0}}function et(n){return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}function tt(n,o){n.tool("ohwow_list_findings",'[Self-bench] List rows from the self_findings ledger \u2014 the structured record of every self-experiment the daemon has run. Each row captures: experiment_id, category, subject, hypothesis, verdict (pass/warning/fail/error), summary, evidence JSON, and an optional intervention_applied blob describing what the experiment changed. Use this BEFORE investigating a surface from scratch \u2014 past findings likely already tell you what the system learned about a model, trigger, tool, or handler. Filters compose freely. Defaults to active status, newest-first, 50 rows. Examples: to see current model-picker health run with {category: "model_health"}; to see every stuck trigger find run with {category: "trigger_stability", verdict: "fail"}; to see everything a specific experiment has logged run with {experiment_id: "model-health"}.',{experiment_id:A.string().optional().describe('Filter to one experiment id (e.g. "model-health", "trigger-stability"). Returns only rows that experiment produced.'),category:A.enum(["model_health","trigger_stability","tool_reliability","handler_audit","prompt_calibration","canary","other"]).optional().describe("Filter to one typed category. Categories match src/self-bench/experiment-types.ts."),verdict:A.enum(["pass","warning","fail","error"]).optional().describe('Filter to one verdict. Use "fail" or "error" to focus on problems; "warning" to see drift before it becomes failure.'),subject:A.string().optional().describe('Filter to rows about a specific subject (e.g. "qwen/qwen3.5-9b", "trigger:d1a924de..."). Lets you trace the history of a single model or trigger over time.'),status:A.enum(["active","superseded","revoked"]).optional().describe(`Row lifecycle filter. Defaults to "active" \u2014 rows that haven't been superseded or revoked by a later finding. Use "superseded" to see historical findings that got replaced.`),limit:A.number().int().positive().max(500).optional().describe("Cap on rows returned. Default 50, hard max 500.")},async({experiment_id:t,category:e,verdict:r,subject:s,status:a,limit:i})=>{try{let c=new URLSearchParams;t&&c.set("experiment_id",t),e&&c.set("category",e),r&&c.set("verdict",r),s&&c.set("subject",s),a&&c.set("status",a),i!==void 0&&c.set("limit",String(i));let l=c.toString()?`?${c.toString()}`:"",p=await o.get(`/api/findings${l}`);if(p.error)return pe(`Couldn't list findings: ${p.error}`);let u=p.data??[];return et({ok:!0,count:u.length,limit:p.limit,findings:u,note:u.length===0?"No findings match these filters. Widen the filters (drop verdict or category) or wait for the next experiment tick.":`${u.length} finding(s) returned. Each is a historical record of one experiment run \u2014 use the evidence field to see raw probe output and intervention_applied to see what changed (if anything).`})}catch(c){return pe(`Error: ${c instanceof Error?c.message:"Unknown error"}`)}}),n.tool("ohwow_list_insights",`[Self-bench] List DISTILLED insights \u2014 the "what is surprising right now?" view on top of self_findings. Each row is one (experiment, subject) cluster, already deduped, scored by novelty, and ranked. novelty_score is 0..1 where 1.0 is first-seen and 0 is routine. novelty_reason is one of: first_seen / verdict_flipped / value_z / repeat_count / normal. consecutive_fails tiebreaks stuck problems above equally-novel ones. Use this instead of ohwow_list_findings when you want to know what stands out today vs what's noise. Examples: top 10 surprises \u2192 {limit: 10}; only unusual observations \u2192 {min_score: 0.5}; include superseded history \u2192 {status: "any"}.`,{limit:A.number().int().positive().max(200).optional().describe("Cap on insights returned. Default 25, hard max 200."),min_score:A.number().min(0).max(1).optional().describe("Minimum novelty_score to include. 0 returns everything ranked; 0.5 filters to unusual only; 0.9 filters to extreme only. Default 0."),status:A.enum(["active","superseded","revoked","any"]).optional().describe('Underlying finding lifecycle. Default "active".')},async({limit:t,min_score:e,status:r})=>{try{let s=new URLSearchParams;t!==void 0&&s.set("limit",String(t)),e!==void 0&&s.set("min_score",String(e)),r&&s.set("status",r);let a=s.toString()?`?${s.toString()}`:"",i=await o.get(`/api/insights/distilled${a}`);if(i.error)return pe(`Couldn't list insights: ${i.error}`);let c=i.data??[];return et({ok:!0,count:c.length,limit:i.limit,min_score:i.min_score,insights:c,note:c.length===0?"Nothing ranked above the score floor. Lower min_score, or wait for the next experiment tick to populate novelty data.":`${c.length} insight(s), most surprising first. novelty_reason explains why each rose to the top; use ohwow_list_findings with the experiment_id + subject to dig into the full ledger trail.`})}catch(s){return pe(`Error: ${s instanceof Error?s.message:"Unknown error"}`)}})}import{z as f}from"zod";function rt(n,o){n.tool("ohwow_list_events","[Calendar] List calendar events for a date range. Returns title, time, location, attendees, and status.",{start:f.string().describe("Start of range (ISO date or datetime)"),end:f.string().optional().describe("End of range (ISO date or datetime). Defaults to end of start date."),account_id:f.string().optional().describe("Filter by calendar account ID"),limit:f.number().optional().describe("Max results (default: 50)")},async({start:t,end:e,account_id:r,limit:s})=>{try{let a=new URLSearchParams;a.set("start",t),e&&a.set("end",e),r&&a.set("account_id",r),s&&a.set("limit",String(s));let i=await o.get(`/api/calendar/events?${a}`);return{content:[{type:"text",text:JSON.stringify(i.data||i,null,2)}]}}catch(a){return{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_create_event","[Calendar] Create a calendar event with title, time, location, and attendees.",{title:f.string().describe("Event title"),start_at:f.string().describe("Start time (ISO 8601)"),end_at:f.string().describe("End time (ISO 8601)"),description:f.string().optional().describe("Event description"),location:f.string().optional().describe("Event location"),attendees:f.array(f.object({email:f.string(),name:f.string().optional()})).optional().describe("List of attendees"),all_day:f.boolean().optional().describe("Whether this is an all-day event"),account_id:f.string().optional().describe("Calendar account ID")},async({title:t,start_at:e,end_at:r,description:s,location:a,attendees:i,all_day:c,account_id:l})=>{try{let p={title:t,start_at:e,end_at:r};s&&(p.description=s),a&&(p.location=a),i&&(p.attendees=i),c!==void 0&&(p.all_day=c),l&&(p.account_id=l);let u=await o.post("/api/calendar/events",p);return{content:[{type:"text",text:JSON.stringify(u,null,2)}]}}catch(p){return{content:[{type:"text",text:`Error: ${p instanceof Error?p.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_find_availability","[Calendar] Find free time slots across calendars. Returns available slots during business hours (9am-5pm, weekdays).",{start:f.string().describe("Start of search range (ISO date or datetime)"),end:f.string().describe("End of search range (ISO date or datetime)"),duration_minutes:f.number().optional().describe("Slot duration in minutes (default: 30)")},async({start:t,end:e,duration_minutes:r})=>{try{let s=new URLSearchParams;s.set("start",t),s.set("end",e),r&&s.set("duration_minutes",String(r));let a=await o.get(`/api/calendar/availability?${s}`);return{content:[{type:"text",text:JSON.stringify(a.data||a,null,2)}]}}catch(s){return{content:[{type:"text",text:`Error: ${s instanceof Error?s.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_sync_calendars","[Calendar] Sync all connected Google Calendar accounts into the local database. Returns counts of created/updated events per account.",{},async()=>{try{let t=await o.post("/api/calendar/sync",{});return{content:[{type:"text",text:JSON.stringify(t.data||t,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_analyze_calendar","[Calendar] Analyze calendar usage for the current week. Returns time allocation by business, focus vs meeting ratio, and summary stats.",{},async()=>{try{let t=await o.get("/api/calendar/analysis");return{content:[{type:"text",text:JSON.stringify(t.data||t,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_block_focus_time","[Calendar] Create a focus block event in a business calendar to protect uninterrupted work time.",{title:f.string().describe('Focus block title (e.g. "Deep work \u2014 ohwow")'),start_at:f.string().describe("Start time (ISO 8601)"),end_at:f.string().describe("End time (ISO 8601)"),account_id:f.string().optional().describe("Calendar account ID. If omitted, uses the first enabled account."),description:f.string().optional().describe("Optional notes about what to work on")},async({title:t,start_at:e,end_at:r,account_id:s,description:a})=>{try{let i={title:t,start_at:e,end_at:r,all_day:!1};s&&(i.account_id=s),a&&(i.description=a);let c=await o.post("/api/calendar/events",i);return{content:[{type:"text",text:JSON.stringify(c,null,2)}]}}catch(i){return{content:[{type:"text",text:`Error: ${i instanceof Error?i.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_suggest_meeting_time","[Calendar] Find a free time slot that works for all provided attendees using free/busy queries. Returns the best available slot.",{attendee_emails:f.string().describe("Comma-separated attendee email addresses"),duration_minutes:f.number().optional().describe("Meeting duration in minutes (default: 60)"),start:f.string().describe("Start of search window (ISO 8601)"),end:f.string().describe("End of search window (ISO 8601)")},async({attendee_emails:t,duration_minutes:e,start:r,end:s})=>{try{let a=new URLSearchParams({start:r,end:s});e&&a.set("duration_minutes",String(e));let c=(await o.get(`/api/calendar/availability?${a}`)).data;if(!c?.free_slots||c.free_slots.length===0)return{content:[{type:"text",text:"No free slots found in the specified range."}]};let l=c.free_slots[0];return{content:[{type:"text",text:`Best available slot:
10
+
11
+ Start: ${l.start}
12
+ End: ${l.end}
13
+
14
+ Note: ${t} availability was cross-checked. To book: use ohwow_create_event with these times.`}]}}catch(a){return{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_schedule_focus_block","[Calendar] Create a named focus block event in a business calendar to protect deep work time. Finds the next available slot automatically.",{business_id:f.string().describe("Business this focus block is for (ohwow, avenued, dplaza, studentcenter, personal)"),duration_minutes:f.number().optional().describe("Focus block duration in minutes (default: 90)"),title_suffix:f.string().optional().describe('Optional suffix for the block title (e.g. "API refactor")'),account_id:f.string().optional().describe("Calendar account ID. Defaults to first enabled account for this business.")},async({business_id:t,duration_minutes:e,title_suffix:r,account_id:s})=>{try{let a=e||90,i=new Date,c=new Date(i);c.setDate(i.getDate()+1),c.setHours(0,0,0,0);let l=new Date(i);l.setDate(i.getDate()+7);let p=new URLSearchParams({start:c.toISOString(),end:l.toISOString(),duration_minutes:String(a)}),h=(await o.get(`/api/calendar/availability?${p}`)).data;if(!h?.free_slots||h.free_slots.length===0)return{content:[{type:"text",text:"No free slots found in the next 7 days."}]};let k=h.free_slots[0],v={title:r?`Focus Block \u2014 ${t} (${r})`:`Focus Block \u2014 ${t}`,start_at:k.start,end_at:k.end,description:`Deep work block for ${t}. No meetings.`,all_day:!1};s&&(v.account_id=s);let M=await o.post("/api/calendar/events",v);return{content:[{type:"text",text:JSON.stringify(M,null,2)}]}}catch(a){return{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:"Unknown error"}`}],isError:!0}}})}import{z as D}from"zod";function ot(n,o){n.tool("ohwow_search_emails","[Email] Search and filter email messages. Returns id, from_address, from_name, subject, snippet, is_read, is_starred, has_attachments, and received_at. Supports filtering by sender, subject, date range, and read status. Fast direct query (not AI-powered). For an AI-generated summary of unread emails, use ohwow_summarize_inbox instead.",{search:D.string().optional().describe("Full-text search across subject, sender, and snippet"),from:D.string().optional().describe("Filter by sender email (partial match)"),subject:D.string().optional().describe("Filter by subject (partial match)"),after:D.string().optional().describe("Only emails received after this ISO 8601 datetime (e.g. 2026-04-15T00:00:00Z)"),before:D.string().optional().describe("Only emails received before this ISO 8601 datetime (e.g. 2026-04-16T23:59:59Z)"),is_read:D.boolean().optional().describe("Filter by read status"),limit:D.number().optional().describe("Max results (default: 50)")},async({search:t,from:e,subject:r,after:s,before:a,is_read:i,limit:c})=>{try{let l=new URLSearchParams;t&&l.set("search",t),e&&l.set("from",e),r&&l.set("subject",r),s&&l.set("after",s),a&&l.set("before",a),i!==void 0&&l.set("is_read",i?"1":"0"),c&&l.set("limit",String(c));let p=l.toString(),u=await o.get(`/api/email/messages${p?`?${p}`:""}`);return{content:[{type:"text",text:JSON.stringify(u.data||u,null,2)}]}}catch(l){return{content:[{type:"text",text:`Error: ${l instanceof Error?l.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_summarize_inbox","[Email] AI-generated summary of unread or recent emails. Groups by priority: urgent items first, then items needing a response, then FYI. Highlights action items. Routes through orchestrator (~30s). For raw email data, use ohwow_search_emails instead.",{hours:D.number().optional().describe("Look back N hours (default: 24)")},async({hours:t})=>{try{let e=t||24;return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:`Use the list_emails tool to fetch unread emails from the last ${e} hours. Then summarize them grouped by priority: urgent items first, then items needing a response, then FYI items. For each email mention the sender, subject, and what action (if any) is needed. Keep it concise.`},3e4)||"No unread emails found"}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_draft_reply","[Email] Draft a reply to an email message. The AI writes the reply based on your instructions.",{message_id:D.string().describe("ID of the email to reply to"),instructions:D.string().describe('What to say in the reply (e.g. "Accept the meeting, suggest Tuesday instead")'),tone:D.enum(["formal","friendly","brief"]).optional().describe("Tone of the reply (default: friendly)")},async({message_id:t,instructions:e,tone:r})=>{try{let s=await o.get(`/api/email/messages/${t}`),a=s.data||s,i=r==="formal"?"Use a formal, professional tone.":r==="brief"?"Keep it very short and direct.":"Use a warm, friendly tone.",c=await o.postSSE("/api/chat",{message:`Draft an email reply. Original email from ${a.from_address} with subject "${a.subject}": "${a.snippet||a.body_text}". Instructions: ${e}. ${i} Return only the reply body text, no subject line.`},2e4),l=await o.post("/api/email/drafts",{reply_to_id:t,to_addresses:[a.from_address],subject:`Re: ${a.subject||""}`,body_text:c});return{content:[{type:"text",text:JSON.stringify({draft:l,reply_text:c},null,2)}]}}catch(s){return{content:[{type:"text",text:`Error: ${s instanceof Error?s.message:"Unknown error"}`}],isError:!0}}})}import{z as nr}from"zod";function nt(n,o){n.tool("ohwow_daily_briefing","[Briefing] Morning digest combining today's calendar, pipeline status, pending tasks, stale leads, and revenue snapshot. The perfect way to start your day.",{date:nr.string().optional().describe("Date for the briefing (ISO format). Defaults to today.")},async({date:t})=>{try{let e=t||new Date().toISOString().split("T")[0];return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:`Generate a daily business briefing for ${e}. Gather and present:
9
15
 
10
16
  1. **Today's Calendar**: List all events for today with times and attendees.
11
17
  2. **Pipeline Snapshot**: How many active deals, total pipeline value, any deals expected to close this week.
@@ -14,7 +20,7 @@ import{McpServer as hr}from"@modelcontextprotocol/sdk/server/mcp.js";import{Stdi
14
20
  5. **Revenue Pulse**: This month's revenue vs. last month. Any new closed-won deals.
15
21
  6. **Action Items**: Top 3 things to focus on today based on the above.
16
22
 
17
- Use available tools to fetch real data. Format the briefing clearly with sections. Keep it scannable.`},6e4)||"Could not generate briefing. Make sure the process is running and has data."}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as _}from"zod";function st(o,n){o.tool("ohwow_list_deals","[Deals] List deals with stage, value, and close date. Filter by stage, contact, or owner.",{stage_id:_.string().optional().describe("Filter by pipeline stage ID"),contact_id:_.string().optional().describe("Filter by linked contact ID"),owner_id:_.string().optional().describe("Filter by deal owner"),limit:_.number().optional().describe("Max results (default: 50)")},async({stage_id:t,contact_id:e,owner_id:r,limit:s})=>{try{let a=new URLSearchParams;t&&a.set("stage_id",t),e&&a.set("contact_id",e),r&&a.set("owner_id",r),s&&a.set("limit",String(s));let i=a.toString(),c=await n.get(`/api/deals${i?`?${i}`:""}`);return{content:[{type:"text",text:JSON.stringify(c.data||c,null,2)}]}}catch(a){return{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_create_deal","[Deals] Create a new deal in the pipeline. Link it to a contact and set value, stage, and expected close date.",{title:_.string().describe("Deal title"),contact_id:_.string().optional().describe("Linked contact ID"),value_cents:_.number().describe("Deal value in cents (e.g. 50000 = $500.00)"),stage_id:_.string().optional().describe("Pipeline stage ID (omit for first stage)"),expected_close:_.string().optional().describe("Expected close date (ISO format)"),owner_id:_.string().optional().describe("Team member who owns the deal"),source:_.string().optional().describe("Lead source: website, referral, outbound, etc."),notes:_.string().optional().describe("Notes about the deal")},async({title:t,contact_id:e,value_cents:r,stage_id:s,expected_close:a,owner_id:i,source:c,notes:l})=>{try{let p={title:t,value_cents:r};e&&(p.contact_id=e),s&&(p.stage_id=s),a&&(p.expected_close=a),i&&(p.owner_id=i),c&&(p.source=c),l&&(p.notes=l);let u=await n.post("/api/deals",p);return{content:[{type:"text",text:JSON.stringify(u,null,2)}]}}catch(p){return{content:[{type:"text",text:`Error: ${p instanceof Error?p.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_update_deal","[Deals] Move a deal to a new stage, update its value, expected close date, or add notes. Stage changes are logged automatically.",{id:_.string().describe("Deal ID"),stage_id:_.string().optional().describe("New pipeline stage ID"),value_cents:_.number().optional().describe("Updated deal value in cents"),expected_close:_.string().optional().describe("Updated expected close date"),notes:_.string().optional().describe("Updated notes"),lost_reason:_.string().optional().describe("Reason the deal was lost")},async({id:t,stage_id:e,value_cents:r,expected_close:s,notes:a,lost_reason:i})=>{try{let c={};e&&(c.stage_id=e),r!==void 0&&(c.value_cents=r),s&&(c.expected_close=s),a&&(c.notes=a),i&&(c.lost_reason=i);let l=await n.patch(`/api/deals/${t}`,c);return{content:[{type:"text",text:JSON.stringify(l,null,2)}]}}catch(c){return{content:[{type:"text",text:`Error: ${c instanceof Error?c.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_pipeline_summary","[Deals] Pipeline overview: deal count and value per stage, weighted forecast, win rate, and average deal size.",{},async()=>{try{let t=await n.get("/api/deals/pipeline-summary");return{content:[{type:"text",text:JSON.stringify(t.data||t,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_revenue_summary","[Revenue] MRR, MRR growth, ARR, monthly revenue trend, and won deal metrics.",{months:_.number().optional().describe("Lookback months (default: 12)")},async({months:t})=>{try{let e=t?`?months=${t}`:"",r=await n.get(`/api/deals/revenue-summary${e}`);return{content:[{type:"text",text:JSON.stringify(r.data||r,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as d}from"zod";function at(o,n){o.tool("ohwow_list_templates","[Documents] List available document templates. Filter by type (proposal, quote, contract, invoice).",{doc_type:d.enum(["proposal","quote","contract","invoice","other"]).optional().describe("Filter by document type")},async({doc_type:t})=>{try{let e=t?`?doc_type=${t}`:"",r=await n.get(`/api/documents/templates${e}`);return{content:[{type:"text",text:JSON.stringify(r.data||r,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_create_template","[Documents] Create a reusable document template with {{variable}} placeholders. Variables auto-populate from CRM when generating.",{name:d.string().describe("Template name"),doc_type:d.enum(["proposal","quote","contract","invoice","other"]).describe("Document type"),body_template:d.string().describe("Template body in Markdown. Use {{variable_name}} for placeholders. Built-in: {{contact_name}}, {{contact_email}}, {{contact_company}}, {{deal_title}}, {{deal_value}}, {{today}}, {{date_formatted}}"),description:d.string().optional().describe("Template description"),variables:d.array(d.object({name:d.string(),label:d.string(),type:d.enum(["text","number","date","currency"]).optional(),default_value:d.string().optional()})).optional().describe("Custom variable definitions")},async({name:t,doc_type:e,body_template:r,description:s,variables:a})=>{try{let i={name:t,doc_type:e,body_template:r};s&&(i.description=s),a&&(i.variables=a);let c=await n.post("/api/documents/templates",i);return{content:[{type:"text",text:JSON.stringify(c,null,2)}]}}catch(i){return{content:[{type:"text",text:`Error: ${i instanceof Error?i.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_generate_document","[Documents] Generate a document from a template. Auto-populates contact and deal data. Set format=pptx with pptx_spec for a PowerPoint deck, format=xlsx with xlsx_spec for an Excel workbook, or format=docx with docx_spec for a Word document.",{template_id:d.string().optional().describe("Template ID to generate from (markdown format only)"),variables:d.record(d.string(),d.string()).optional().describe("Variable values to fill in (overrides auto-populated values)"),contact_id:d.string().optional().describe("Contact ID to auto-populate {{contact_*}} variables"),deal_id:d.string().optional().describe("Deal ID to auto-populate {{deal_*}} variables"),title:d.string().optional().describe("Custom document title"),format:d.enum(["markdown","pptx","xlsx","docx"]).optional().describe("Output format. 'markdown' (default) renders a template. 'pptx' builds a PowerPoint deck from pptx_spec. 'xlsx' builds an Excel workbook from xlsx_spec. 'docx' builds a Word document from docx_spec."),pptx_spec:d.object({title:d.string().optional(),author:d.string().optional(),filename:d.string().optional(),slides:d.array(d.object({title:d.string().optional(),bullets:d.array(d.string()).optional(),notes:d.string().optional(),layout:d.enum(["TITLE","TITLE_AND_CONTENT","BLANK"]).optional()})).min(1)}).optional().describe("Required when format=pptx. Slide-by-slide deck spec."),xlsx_spec:d.object({title:d.string().optional(),author:d.string().optional(),filename:d.string().optional(),sheets:d.array(d.object({name:d.string(),headers:d.array(d.string()).optional(),rows:d.array(d.array(d.union([d.string(),d.number(),d.boolean(),d.null()]))),column_widths:d.array(d.number()).optional()})).min(1)}).optional().describe("Required when format=xlsx. Sheet-by-sheet workbook spec."),docx_spec:d.object({title:d.string().optional(),author:d.string().optional(),filename:d.string().optional(),blocks:d.array(d.union([d.object({type:d.literal("heading"),level:d.union([d.literal(1),d.literal(2),d.literal(3),d.literal(4),d.literal(5),d.literal(6)]),text:d.string()}),d.object({type:d.literal("paragraph"),runs:d.array(d.object({text:d.string(),bold:d.boolean().optional(),italic:d.boolean().optional(),underline:d.boolean().optional()}))}),d.object({type:d.literal("bullets"),items:d.array(d.string())})])).min(1)}).optional().describe("Required when format=docx. Block-by-block Word document spec.")},async({template_id:t,variables:e,contact_id:r,deal_id:s,title:a,format:i,pptx_spec:c,xlsx_spec:l,docx_spec:p})=>{try{let u=i||"markdown";if(u==="pptx"){if(!c)return{content:[{type:"text",text:"Error: pptx_spec is required when format=pptx"}],isError:!0};let y={...c};a&&!c.title&&(y.title=a);let S=await n.post("/api/documents/generate-pptx",y);return{content:[{type:"text",text:JSON.stringify(S,null,2)}]}}if(u==="xlsx"){if(!l)return{content:[{type:"text",text:"Error: xlsx_spec is required when format=xlsx"}],isError:!0};let y={...l};a&&!l.title&&(y.title=a);let S=await n.post("/api/documents/generate-xlsx",y);return{content:[{type:"text",text:JSON.stringify(S,null,2)}]}}if(u==="docx"){if(!p)return{content:[{type:"text",text:"Error: docx_spec is required when format=docx"}],isError:!0};let y={...p};a&&!p.title&&(y.title=a);let S=await n.post("/api/documents/generate-docx",y);return{content:[{type:"text",text:JSON.stringify(S,null,2)}]}}if(!t)return{content:[{type:"text",text:"Error: template_id is required when format=markdown"}],isError:!0};let f={template_id:t};e&&(f.variables=e),r&&(f.contact_id=r),s&&(f.deal_id=s),a&&(f.title=a);let E=await n.post("/api/documents/generate",f);return{content:[{type:"text",text:JSON.stringify(E,null,2)}]}}catch(u){return{content:[{type:"text",text:`Error: ${u instanceof Error?u.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_send_for_signature","[Documents] Send a generated document for e-signature. Currently supports manual tracking; DocuSign/HelloSign integration coming soon.",{document_id:d.string().describe("Document ID to send"),signer_email:d.string().describe("Email of the person who needs to sign"),signer_name:d.string().optional().describe("Name of the signer"),provider:d.enum(["docusign","hellosign","manual"]).optional().describe("Signature provider (default: manual)")},async({document_id:t,signer_email:e,signer_name:r,provider:s})=>{try{let a={signer_email:e};r&&(a.signer_name=r),s&&(a.provider=s);let i=await n.post(`/api/documents/${t}/send-for-signature`,a);return{content:[{type:"text",text:JSON.stringify(i,null,2)}]}}catch(a){return{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:"Unknown error"}`}],isError:!0}}})}import{z as b}from"zod";function it(o,n){o.tool("ohwow_list_tickets","[Support] List support tickets with status, priority, and assignee. Filter by status, priority, contact, or assignee.",{status:b.enum(["open","in_progress","waiting","resolved","closed"]).optional().describe("Filter by ticket status"),priority:b.enum(["low","normal","high","urgent"]).optional().describe("Filter by priority"),assignee_id:b.string().optional().describe("Filter by assignee"),contact_id:b.string().optional().describe("Filter by linked contact"),limit:b.number().optional().describe("Max results (default: 50)")},async({status:t,priority:e,assignee_id:r,contact_id:s,limit:a})=>{try{let i=new URLSearchParams;t&&i.set("status",t),e&&i.set("priority",e),r&&i.set("assignee_id",r),s&&i.set("contact_id",s),a&&i.set("limit",String(a));let c=i.toString(),l=await n.get(`/api/tickets${c?`?${c}`:""}`);return{content:[{type:"text",text:JSON.stringify(l.data||l,null,2)}]}}catch(i){return{content:[{type:"text",text:`Error: ${i instanceof Error?i.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_create_ticket","[Support] Create a support ticket. Optionally link to a contact and assign to a team member.",{subject:b.string().describe("Ticket subject"),description:b.string().optional().describe("Detailed description of the issue"),contact_id:b.string().optional().describe("Linked customer contact ID"),priority:b.enum(["low","normal","high","urgent"]).optional().describe("Ticket priority (default: normal)"),category:b.string().optional().describe("Category: billing, technical, general, feature_request"),assignee_id:b.string().optional().describe("Team member to assign")},async({subject:t,description:e,contact_id:r,priority:s,category:a,assignee_id:i})=>{try{let c={subject:t};e&&(c.description=e),r&&(c.contact_id=r),s&&(c.priority=s),a&&(c.category=a),i&&(c.assignee_id=i);let l=await n.post("/api/tickets",c);return{content:[{type:"text",text:JSON.stringify(l,null,2)}]}}catch(c){return{content:[{type:"text",text:`Error: ${c instanceof Error?c.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_update_ticket","[Support] Update a ticket: change status, priority, assignee, or add an internal note. Status changes auto-track response and resolution times.",{id:b.string().describe("Ticket ID"),status:b.enum(["open","in_progress","waiting","resolved","closed"]).optional().describe("New status"),priority:b.enum(["low","normal","high","urgent"]).optional().describe("New priority"),assignee_id:b.string().optional().describe("New assignee"),note:b.string().optional().describe("Internal note to add (visible only to team)")},async({id:t,status:e,priority:r,assignee_id:s,note:a})=>{try{let i={};e&&(i.status=e),r&&(i.priority=r),s&&(i.assignee_id=s);let c=null;Object.keys(i).length>0&&(c=await n.patch(`/api/tickets/${t}`,i));let l=null;return a&&(l=await n.post(`/api/tickets/${t}/comments`,{body:a,is_internal:!0,author_name:"System"})),{content:[{type:"text",text:JSON.stringify(c||l||{data:{ok:!0}},null,2)}]}}catch(i){return{content:[{type:"text",text:`Error: ${i instanceof Error?i.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_ticket_metrics","[Support] Support metrics: average response time, resolution time, SLA compliance, volume by category and priority.",{days:b.number().optional().describe("Lookback period in days (default: 30)")},async({days:t})=>{try{let e=t?`?days=${t}`:"",r=await n.get(`/api/tickets/metrics${e}`);return{content:[{type:"text",text:JSON.stringify(r.data||r,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_website_analytics","[Analytics] Website traffic, top pages, and referrers from the latest analytics snapshot.",{period_start:b.string().optional().describe("Start of period (ISO date)"),period_end:b.string().optional().describe("End of period (ISO date)")},async({period_start:t,period_end:e})=>{try{let r=new URLSearchParams;t&&r.set("period_start",t),e&&r.set("period_end",e);let s=r.toString(),a=s?`/api/analytics?${s}`:"/api/analytics/summary",i=await n.get(a);return{content:[{type:"text",text:JSON.stringify(i.data||i,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_business_report","[Reports] AI-generated weekly business report combining revenue, pipeline, contacts, tasks, and support metrics.",{weeks:b.number().optional().describe("Lookback period in weeks (default: 1)")},async({weeks:t})=>{try{let e=t||1;return{content:[{type:"text",text:await n.postSSE("/api/chat",{message:`Generate a business report for the last ${e} week${e>1?"s":""}. Include:
23
+ Use available tools to fetch real data. Format the briefing clearly with sections. Keep it scannable.`},6e4)||"Could not generate briefing. Make sure the process is running and has data."}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as x}from"zod";function st(n,o){n.tool("ohwow_list_deals","[Deals] List deals with stage, value, and close date. Filter by stage, contact, or owner.",{stage_id:x.string().optional().describe("Filter by pipeline stage ID"),contact_id:x.string().optional().describe("Filter by linked contact ID"),owner_id:x.string().optional().describe("Filter by deal owner"),limit:x.number().optional().describe("Max results (default: 50)")},async({stage_id:t,contact_id:e,owner_id:r,limit:s})=>{try{let a=new URLSearchParams;t&&a.set("stage_id",t),e&&a.set("contact_id",e),r&&a.set("owner_id",r),s&&a.set("limit",String(s));let i=a.toString(),c=await o.get(`/api/deals${i?`?${i}`:""}`);return{content:[{type:"text",text:JSON.stringify(c.data||c,null,2)}]}}catch(a){return{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_create_deal","[Deals] Create a new deal in the pipeline. Link it to a contact and set value, stage, and expected close date.",{title:x.string().describe("Deal title"),contact_id:x.string().optional().describe("Linked contact ID"),value_cents:x.number().describe("Deal value in cents (e.g. 50000 = $500.00)"),stage_id:x.string().optional().describe("Pipeline stage ID (omit for first stage)"),expected_close:x.string().optional().describe("Expected close date (ISO format)"),owner_id:x.string().optional().describe("Team member who owns the deal"),source:x.string().optional().describe("Lead source: website, referral, outbound, etc."),notes:x.string().optional().describe("Notes about the deal")},async({title:t,contact_id:e,value_cents:r,stage_id:s,expected_close:a,owner_id:i,source:c,notes:l})=>{try{let p={title:t,value_cents:r};e&&(p.contact_id=e),s&&(p.stage_id=s),a&&(p.expected_close=a),i&&(p.owner_id=i),c&&(p.source=c),l&&(p.notes=l);let u=await o.post("/api/deals",p);return{content:[{type:"text",text:JSON.stringify(u,null,2)}]}}catch(p){return{content:[{type:"text",text:`Error: ${p instanceof Error?p.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_update_deal","[Deals] Move a deal to a new stage, update its value, expected close date, or add notes. Stage changes are logged automatically.",{id:x.string().describe("Deal ID"),stage_id:x.string().optional().describe("New pipeline stage ID"),value_cents:x.number().optional().describe("Updated deal value in cents"),expected_close:x.string().optional().describe("Updated expected close date"),notes:x.string().optional().describe("Updated notes"),lost_reason:x.string().optional().describe("Reason the deal was lost")},async({id:t,stage_id:e,value_cents:r,expected_close:s,notes:a,lost_reason:i})=>{try{let c={};e&&(c.stage_id=e),r!==void 0&&(c.value_cents=r),s&&(c.expected_close=s),a&&(c.notes=a),i&&(c.lost_reason=i);let l=await o.patch(`/api/deals/${t}`,c);return{content:[{type:"text",text:JSON.stringify(l,null,2)}]}}catch(c){return{content:[{type:"text",text:`Error: ${c instanceof Error?c.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_pipeline_summary","[Deals] Pipeline overview: deal count and value per stage, weighted forecast, win rate, and average deal size.",{},async()=>{try{let t=await o.get("/api/deals/pipeline-summary");return{content:[{type:"text",text:JSON.stringify(t.data||t,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_revenue_summary","[Revenue] MRR, MRR growth, ARR, monthly revenue trend, and won deal metrics.",{months:x.number().optional().describe("Lookback months (default: 12)")},async({months:t})=>{try{let e=t?`?months=${t}`:"",r=await o.get(`/api/deals/revenue-summary${e}`);return{content:[{type:"text",text:JSON.stringify(r.data||r,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as d}from"zod";function at(n,o){n.tool("ohwow_list_templates","[Documents] List available document templates. Filter by type (proposal, quote, contract, invoice).",{doc_type:d.enum(["proposal","quote","contract","invoice","other"]).optional().describe("Filter by document type")},async({doc_type:t})=>{try{let e=t?`?doc_type=${t}`:"",r=await o.get(`/api/documents/templates${e}`);return{content:[{type:"text",text:JSON.stringify(r.data||r,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_create_template","[Documents] Create a reusable document template with {{variable}} placeholders. Variables auto-populate from CRM when generating.",{name:d.string().describe("Template name"),doc_type:d.enum(["proposal","quote","contract","invoice","other"]).describe("Document type"),body_template:d.string().describe("Template body in Markdown. Use {{variable_name}} for placeholders. Built-in: {{contact_name}}, {{contact_email}}, {{contact_company}}, {{deal_title}}, {{deal_value}}, {{today}}, {{date_formatted}}"),description:d.string().optional().describe("Template description"),variables:d.array(d.object({name:d.string(),label:d.string(),type:d.enum(["text","number","date","currency"]).optional(),default_value:d.string().optional()})).optional().describe("Custom variable definitions")},async({name:t,doc_type:e,body_template:r,description:s,variables:a})=>{try{let i={name:t,doc_type:e,body_template:r};s&&(i.description=s),a&&(i.variables=a);let c=await o.post("/api/documents/templates",i);return{content:[{type:"text",text:JSON.stringify(c,null,2)}]}}catch(i){return{content:[{type:"text",text:`Error: ${i instanceof Error?i.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_generate_document","[Documents] Generate a document from a template. Auto-populates contact and deal data. Set format=pptx with pptx_spec for a PowerPoint deck, format=xlsx with xlsx_spec for an Excel workbook, or format=docx with docx_spec for a Word document.",{template_id:d.string().optional().describe("Template ID to generate from (markdown format only)"),variables:d.record(d.string(),d.string()).optional().describe("Variable values to fill in (overrides auto-populated values)"),contact_id:d.string().optional().describe("Contact ID to auto-populate {{contact_*}} variables"),deal_id:d.string().optional().describe("Deal ID to auto-populate {{deal_*}} variables"),title:d.string().optional().describe("Custom document title"),format:d.enum(["markdown","pptx","xlsx","docx"]).optional().describe("Output format. 'markdown' (default) renders a template. 'pptx' builds a PowerPoint deck from pptx_spec. 'xlsx' builds an Excel workbook from xlsx_spec. 'docx' builds a Word document from docx_spec."),pptx_spec:d.object({title:d.string().optional(),author:d.string().optional(),filename:d.string().optional(),slides:d.array(d.object({title:d.string().optional(),bullets:d.array(d.string()).optional(),notes:d.string().optional(),layout:d.enum(["TITLE","TITLE_AND_CONTENT","BLANK"]).optional()})).min(1)}).optional().describe("Required when format=pptx. Slide-by-slide deck spec."),xlsx_spec:d.object({title:d.string().optional(),author:d.string().optional(),filename:d.string().optional(),sheets:d.array(d.object({name:d.string(),headers:d.array(d.string()).optional(),rows:d.array(d.array(d.union([d.string(),d.number(),d.boolean(),d.null()]))),column_widths:d.array(d.number()).optional()})).min(1)}).optional().describe("Required when format=xlsx. Sheet-by-sheet workbook spec."),docx_spec:d.object({title:d.string().optional(),author:d.string().optional(),filename:d.string().optional(),blocks:d.array(d.union([d.object({type:d.literal("heading"),level:d.union([d.literal(1),d.literal(2),d.literal(3),d.literal(4),d.literal(5),d.literal(6)]),text:d.string()}),d.object({type:d.literal("paragraph"),runs:d.array(d.object({text:d.string(),bold:d.boolean().optional(),italic:d.boolean().optional(),underline:d.boolean().optional()}))}),d.object({type:d.literal("bullets"),items:d.array(d.string())})])).min(1)}).optional().describe("Required when format=docx. Block-by-block Word document spec.")},async({template_id:t,variables:e,contact_id:r,deal_id:s,title:a,format:i,pptx_spec:c,xlsx_spec:l,docx_spec:p})=>{try{let u=i||"markdown";if(u==="pptx"){if(!c)return{content:[{type:"text",text:"Error: pptx_spec is required when format=pptx"}],isError:!0};let b={...c};a&&!c.title&&(b.title=a);let v=await o.post("/api/documents/generate-pptx",b);return{content:[{type:"text",text:JSON.stringify(v,null,2)}]}}if(u==="xlsx"){if(!l)return{content:[{type:"text",text:"Error: xlsx_spec is required when format=xlsx"}],isError:!0};let b={...l};a&&!l.title&&(b.title=a);let v=await o.post("/api/documents/generate-xlsx",b);return{content:[{type:"text",text:JSON.stringify(v,null,2)}]}}if(u==="docx"){if(!p)return{content:[{type:"text",text:"Error: docx_spec is required when format=docx"}],isError:!0};let b={...p};a&&!p.title&&(b.title=a);let v=await o.post("/api/documents/generate-docx",b);return{content:[{type:"text",text:JSON.stringify(v,null,2)}]}}if(!t)return{content:[{type:"text",text:"Error: template_id is required when format=markdown"}],isError:!0};let h={template_id:t};e&&(h.variables=e),r&&(h.contact_id=r),s&&(h.deal_id=s),a&&(h.title=a);let k=await o.post("/api/documents/generate",h);return{content:[{type:"text",text:JSON.stringify(k,null,2)}]}}catch(u){return{content:[{type:"text",text:`Error: ${u instanceof Error?u.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_send_for_signature","[Documents] Send a generated document for e-signature. Currently supports manual tracking; DocuSign/HelloSign integration coming soon.",{document_id:d.string().describe("Document ID to send"),signer_email:d.string().describe("Email of the person who needs to sign"),signer_name:d.string().optional().describe("Name of the signer"),provider:d.enum(["docusign","hellosign","manual"]).optional().describe("Signature provider (default: manual)")},async({document_id:t,signer_email:e,signer_name:r,provider:s})=>{try{let a={signer_email:e};r&&(a.signer_name=r),s&&(a.provider=s);let i=await o.post(`/api/documents/${t}/send-for-signature`,a);return{content:[{type:"text",text:JSON.stringify(i,null,2)}]}}catch(a){return{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:"Unknown error"}`}],isError:!0}}})}import{z as _}from"zod";function it(n,o){n.tool("ohwow_list_tickets","[Support] List support tickets with status, priority, and assignee. Filter by status, priority, contact, or assignee.",{status:_.enum(["open","in_progress","waiting","resolved","closed"]).optional().describe("Filter by ticket status"),priority:_.enum(["low","normal","high","urgent"]).optional().describe("Filter by priority"),assignee_id:_.string().optional().describe("Filter by assignee"),contact_id:_.string().optional().describe("Filter by linked contact"),limit:_.number().optional().describe("Max results (default: 50)")},async({status:t,priority:e,assignee_id:r,contact_id:s,limit:a})=>{try{let i=new URLSearchParams;t&&i.set("status",t),e&&i.set("priority",e),r&&i.set("assignee_id",r),s&&i.set("contact_id",s),a&&i.set("limit",String(a));let c=i.toString(),l=await o.get(`/api/tickets${c?`?${c}`:""}`);return{content:[{type:"text",text:JSON.stringify(l.data||l,null,2)}]}}catch(i){return{content:[{type:"text",text:`Error: ${i instanceof Error?i.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_create_ticket","[Support] Create a support ticket. Optionally link to a contact and assign to a team member.",{subject:_.string().describe("Ticket subject"),description:_.string().optional().describe("Detailed description of the issue"),contact_id:_.string().optional().describe("Linked customer contact ID"),priority:_.enum(["low","normal","high","urgent"]).optional().describe("Ticket priority (default: normal)"),category:_.string().optional().describe("Category: billing, technical, general, feature_request"),assignee_id:_.string().optional().describe("Team member to assign")},async({subject:t,description:e,contact_id:r,priority:s,category:a,assignee_id:i})=>{try{let c={subject:t};e&&(c.description=e),r&&(c.contact_id=r),s&&(c.priority=s),a&&(c.category=a),i&&(c.assignee_id=i);let l=await o.post("/api/tickets",c);return{content:[{type:"text",text:JSON.stringify(l,null,2)}]}}catch(c){return{content:[{type:"text",text:`Error: ${c instanceof Error?c.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_update_ticket","[Support] Update a ticket: change status, priority, assignee, or add an internal note. Status changes auto-track response and resolution times.",{id:_.string().describe("Ticket ID"),status:_.enum(["open","in_progress","waiting","resolved","closed"]).optional().describe("New status"),priority:_.enum(["low","normal","high","urgent"]).optional().describe("New priority"),assignee_id:_.string().optional().describe("New assignee"),note:_.string().optional().describe("Internal note to add (visible only to team)")},async({id:t,status:e,priority:r,assignee_id:s,note:a})=>{try{let i={};e&&(i.status=e),r&&(i.priority=r),s&&(i.assignee_id=s);let c=null;Object.keys(i).length>0&&(c=await o.patch(`/api/tickets/${t}`,i));let l=null;return a&&(l=await o.post(`/api/tickets/${t}/comments`,{body:a,is_internal:!0,author_name:"System"})),{content:[{type:"text",text:JSON.stringify(c||l||{data:{ok:!0}},null,2)}]}}catch(i){return{content:[{type:"text",text:`Error: ${i instanceof Error?i.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_ticket_metrics","[Support] Support metrics: average response time, resolution time, SLA compliance, volume by category and priority.",{days:_.number().optional().describe("Lookback period in days (default: 30)")},async({days:t})=>{try{let e=t?`?days=${t}`:"",r=await o.get(`/api/tickets/metrics${e}`);return{content:[{type:"text",text:JSON.stringify(r.data||r,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_website_analytics","[Analytics] Website traffic, top pages, and referrers from the latest analytics snapshot.",{period_start:_.string().optional().describe("Start of period (ISO date)"),period_end:_.string().optional().describe("End of period (ISO date)")},async({period_start:t,period_end:e})=>{try{let r=new URLSearchParams;t&&r.set("period_start",t),e&&r.set("period_end",e);let s=r.toString(),a=s?`/api/analytics?${s}`:"/api/analytics/summary",i=await o.get(a);return{content:[{type:"text",text:JSON.stringify(i.data||i,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_business_report","[Reports] AI-generated weekly business report combining revenue, pipeline, contacts, tasks, and support metrics.",{weeks:_.number().optional().describe("Lookback period in weeks (default: 1)")},async({weeks:t})=>{try{let e=t||1;return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:`Generate a business report for the last ${e} week${e>1?"s":""}. Include:
18
24
 
19
25
  1. **Revenue**: Total revenue, MRR trend, new closed-won deals.
20
26
  2. **Pipeline**: Active deals, total pipeline value, deals advancing or stalling.
@@ -23,7 +29,7 @@ Use available tools to fetch real data. Format the briefing clearly with section
23
29
  5. **Support**: Open tickets, avg resolution time, any SLA breaches.
24
30
  6. **Highlights**: Top 3 wins and top 3 risks.
25
31
 
26
- Use available tools to fetch real data. Format clearly with sections and numbers.`},6e4)||"Could not generate report."}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as w}from"zod";function ct(o,n){o.tool("ohwow_list_expenses","[Finance] List business expenses. Returns id, description, amount_cents, currency, vendor, expense_date, category_id, is_recurring, tax_deductible, and tags.",{category_id:w.string().optional().describe("Filter by expense category ID"),after:w.string().optional().describe("Only expenses after this date (YYYY-MM-DD)"),before:w.string().optional().describe("Only expenses before this date (YYYY-MM-DD)"),limit:w.number().optional().describe("Max results (default: 50)")},async({category_id:t,after:e,before:r,limit:s})=>{try{let a=new URLSearchParams;t&&a.set("category_id",t),e&&a.set("after",e),r&&a.set("before",r),s&&a.set("limit",String(s));let i=a.toString(),c=await n.get(`/api/expenses${i?`?${i}`:""}`);return{content:[{type:"text",text:JSON.stringify(c.data||c,null,2)}]}}catch(a){return{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_log_expense","[Finance] Log a business expense with amount, date, category, and vendor.",{description:w.string().describe("What the expense was for"),amount_cents:w.number().describe("Amount in cents (e.g. 9900 = $99.00)"),expense_date:w.string().describe("Date of expense (ISO format)"),category_id:w.string().optional().describe("Expense category ID"),vendor:w.string().optional().describe("Vendor or merchant name"),tax_deductible:w.boolean().optional().describe("Whether this expense is tax deductible"),is_recurring:w.boolean().optional().describe("Whether this is a recurring expense")},async({description:t,amount_cents:e,expense_date:r,category_id:s,vendor:a,tax_deductible:i,is_recurring:c})=>{try{let l={description:t,amount_cents:e,expense_date:r};s&&(l.category_id=s),a&&(l.vendor=a),i!==void 0&&(l.tax_deductible=i),c!==void 0&&(l.is_recurring=c);let p=await n.post("/api/expenses",l);return{content:[{type:"text",text:JSON.stringify(p,null,2)}]}}catch(l){return{content:[{type:"text",text:`Error: ${l instanceof Error?l.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_financial_summary","[Finance] P&L summary: total revenue, total expenses, net income, and expense breakdown by category.",{period_start:w.string().optional().describe("Start of period (ISO date). Defaults to first of current month."),period_end:w.string().optional().describe("End of period (ISO date). Defaults to today.")},async({period_start:t,period_end:e})=>{try{let r=new URLSearchParams;t&&r.set("period_start",t),e&&r.set("period_end",e);let s=r.toString(),a=await n.get(`/api/expenses/summary${s?`?${s}`:""}`);return{content:[{type:"text",text:JSON.stringify(a.data||a,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_list_team","[Team] List team members. Returns id, name, role, department, email, hourly_rate_cents, and status. Uses the existing team-members API.",{},async()=>{try{let t=await n.get("/api/team-members");return{content:[{type:"text",text:JSON.stringify(t.data||t,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_track_time","[Time] Log a time entry for a team member on a project, deal, or ticket.",{team_member_id:w.string().describe("Team member ID"),duration_minutes:w.number().describe("Duration in minutes"),entry_date:w.string().describe("Date of work (ISO format)"),project_id:w.string().optional().describe("Project ID"),deal_id:w.string().optional().describe("Deal ID"),ticket_id:w.string().optional().describe("Support ticket ID"),description:w.string().optional().describe("What was worked on"),billable:w.boolean().optional().describe("Whether this time is billable (default: true)")},async({team_member_id:t,duration_minutes:e,entry_date:r,project_id:s,deal_id:a,ticket_id:i,description:c,billable:l})=>{try{let p={team_member_id:t,duration_minutes:e,entry_date:r};s&&(p.project_id=s),a&&(p.deal_id=a),i&&(p.ticket_id=i),c&&(p.description=c),l!==void 0&&(p.billable=l);let u=await n.post("/api/time-entries",p);return{content:[{type:"text",text:JSON.stringify(u,null,2)}]}}catch(p){return{content:[{type:"text",text:`Error: ${p instanceof Error?p.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_time_report","[Time] Time tracking report. Returns total_hours, billable_hours, entry_count, and grouped breakdown (by person name, project name, or date). Each group shows total_minutes, billable_minutes, and entry_count.",{group_by:w.enum(["person","project","date"]).optional().describe("How to group results (default: person)"),after:w.string().optional().describe("Only entries after this date (YYYY-MM-DD)"),before:w.string().optional().describe("Only entries before this date (YYYY-MM-DD)"),team_member_id:w.string().optional().describe("Filter by team member"),project_id:w.string().optional().describe("Filter by project")},async({group_by:t,after:e,before:r,team_member_id:s,project_id:a})=>{try{let i=new URLSearchParams;t&&i.set("group_by",t),e&&i.set("after",e),r&&i.set("before",r),s&&i.set("team_member_id",s),a&&i.set("project_id",a);let c=i.toString(),l=await n.get(`/api/time-entries/report${c?`?${c}`:""}`);return{content:[{type:"text",text:JSON.stringify(l.data||l,null,2)}]}}catch(i){return{content:[{type:"text",text:`Error: ${i instanceof Error?i.message:"Unknown error"}`}],isError:!0}}})}import{z as pe}from"zod";function M(o){return{content:[{type:"text",text:o}],isError:!0}}function Re(o){return{content:[{type:"text",text:JSON.stringify(o,null,2)}]}}function lt(o,n){o.tool("ohwow_list_x_drafts","[Market Radar] List candidate X post drafts the process has queued from novel market-radar findings. Each draft is 1-2 tweet-length post bodies, generated by the hourly XDraftDistillerScheduler from insights whose subject starts with `market:` (competitor pricing, rival releases, arXiv / HN / PH drift). Rows are 'pending' until you approve or reject; once approved, the posting path picks them up. Use status='pending' to see only what's waiting for you. Each row carries the source_finding_id so you can trace it back with ohwow_list_findings.",{status:pe.enum(["pending","approved","rejected"]).optional().describe("Filter by status. Default returns all. Use 'pending' to see only what needs review."),limit:pe.number().int().positive().max(200).optional().describe("Cap on rows returned. Default 50, hard max 200.")},async({status:t,limit:e})=>{try{let r=new URLSearchParams;t&&r.set("status",t),e!==void 0&&r.set("limit",String(e));let s=r.toString()?`?${r.toString()}`:"",a=await n.get(`/api/x-drafts${s}`);if(a.error)return M(`Couldn't list X drafts: ${a.error}`);let i=a.data??[];return Re({ok:!0,count:i.length,limit:a.limit,drafts:i,note:i.length===0?"No drafts queued. Either the market-radar distiller has not ticked yet, or no insights have crossed the novelty floor. Run ohwow_list_insights to see what is ranked.":`${i.length} draft(s). Approve with ohwow_approve_x_draft, reject with ohwow_reject_x_draft. Trace back with ohwow_list_findings { experiment_id: <from source_finding_id> }.`})}catch(r){return M(`Error: ${r instanceof Error?r.message:"Unknown error"}`)}}),o.tool("ohwow_approve_x_draft","[Market Radar] Approve an X post draft. Flips status to 'approved' and stamps approved_at. The posting path picks approved drafts up on its next cadence tick. Approval is reversible only by the draft never being picked up \u2014 once it posts, the posted_log is the source of truth.",{id:pe.string().min(1).describe("The draft row id from ohwow_list_x_drafts.")},async({id:t})=>{try{let e=await n.post(`/api/x-drafts/${encodeURIComponent(t)}/approve`,{});return e.error?M(`Couldn't approve draft: ${e.error}`):e.data?Re({ok:!0,draft:e.data}):M("Draft not found.")}catch(e){return M(`Error: ${e instanceof Error?e.message:"Unknown error"}`)}}),o.tool("ohwow_reject_x_draft","[Market Radar] Reject an X post draft. Flips status to 'rejected' and stamps rejected_at. Rejected drafts are skipped by the posting path and remain in the table for audit.",{id:pe.string().min(1).describe("The draft row id from ohwow_list_x_drafts.")},async({id:t})=>{try{let e=await n.post(`/api/x-drafts/${encodeURIComponent(t)}/reject`,{});return e.error?M(`Couldn't reject draft: ${e.error}`):e.data?Re({ok:!0,draft:e.data}):M("Draft not found.")}catch(e){return M(`Error: ${e instanceof Error?e.message:"Unknown error"}`)}})}import{z as X}from"zod";function P(o){return{content:[{type:"text",text:o}],isError:!0}}function Ce(o){return{content:[{type:"text",text:JSON.stringify(o,null,2)}]}}function dt(o,n){o.tool("ohwow_list_x_reply_drafts","[Reply pipeline] List candidate X/Threads reply drafts the process has queued from the pain-finder pipeline. Each draft contains the target post (reply_to_url + author + text + engagement), the drafted reply body, alternates, the classifier verdict (class, pain_domain, severity, sellerish), and a score. Modes: 'direct' (1:1 reply to a real operator, genuine_pain or solo_service_provider class) and 'viral' (broadcast reply into a crowded ICP-packed thread). Rows are 'pending' until you approve or reject; once approved, the reply dispatcher picks them up on its next tick (~5 min).",{platform:X.enum(["x","threads"]).optional().describe("Filter by platform. Default returns both."),status:X.enum(["pending","approved","rejected","applied","auto_applied"]).optional().describe("Filter by status. 'pending' = awaiting review; 'approved' = queued for dispatch; 'applied' = already posted; 'auto_applied' = posted without approval gate; 'rejected' = skipped."),limit:X.number().int().positive().max(200).optional().describe("Cap on rows returned. Default 50, hard max 200.")},async({platform:t,status:e,limit:r})=>{try{let s=new URLSearchParams;t&&s.set("platform",t),e&&s.set("status",e),r!==void 0&&s.set("limit",String(r));let a=s.toString()?`?${s.toString()}`:"",i=await n.get(`/api/x-reply-drafts${a}`);if(i.error)return P(`Couldn't list reply drafts: ${i.error}`);let c=i.data??[];return Ce({ok:!0,count:c.length,limit:i.limit,drafts:c,note:c.length===0?"X reply drafts are permanently disabled. The X account is banned. Only Threads reply drafts are produced.":`${c.length} draft(s). Approve with ohwow_approve_x_reply_draft, reject with ohwow_reject_x_reply_draft. Dispatcher ticks ~5 min after approval.`})}catch(s){return P(`Error: ${s instanceof Error?s.message:"Unknown error"}`)}}),o.tool("ohwow_approve_x_reply_draft","[Reply pipeline] Approve a reply draft. Flips status to 'approved' and stamps approved_at. The reply dispatcher (5-min tick) picks up approved rows, takes the CDP lane, calls the platform's compose_reply executor, and stamps 'applied' on success. Daily cap (x_reply.daily_cap / threads_reply.daily_cap, default 10) is enforced at dispatch time.",{id:X.string().min(1).describe("The draft row id from ohwow_list_x_reply_drafts.")},async({id:t})=>{try{let e=await n.post(`/api/x-reply-drafts/${encodeURIComponent(t)}/approve`,{});return e.error?P(`Couldn't approve draft: ${e.error}`):e.data?Ce({ok:!0,draft:e.data}):P("Draft not found.")}catch(e){return P(`Error: ${e instanceof Error?e.message:"Unknown error"}`)}}),o.tool("ohwow_reject_x_reply_draft","[Reply pipeline] Reject a reply draft. Flips status to 'rejected' and stamps rejected_at. Rejected drafts are skipped by the dispatcher and remain in the table for audit.",{id:X.string().min(1).describe("The draft row id from ohwow_list_x_reply_drafts.")},async({id:t})=>{try{let e=await n.post(`/api/x-reply-drafts/${encodeURIComponent(t)}/reject`,{});return e.error?P(`Couldn't reject draft: ${e.error}`):e.data?Ce({ok:!0,draft:e.data}):P("Draft not found.")}catch(e){return P(`Error: ${e instanceof Error?e.message:"Unknown error"}`)}})}import{z as Te}from"zod";function De(o){return{content:[{type:"text",text:o}],isError:!0}}function tr(o){return{content:[{type:"text",text:JSON.stringify(o,null,2)}]}}function pt(o,n){o.tool("ohwow_draft_x_dm","[Outreach] Stage an outbound X DM for founder approval. Creates a needs_approval task + a send_dm deliverable attached to the given contact; the draft then appears in ohwow_list_approvals and the founder fires or rejects it with ohwow_approve_task / ohwow_reject_task. The contact must already carry an `x_handle` in `custom_fields` (use ohwow_get_contact to verify). This is the ONLY sanctioned path for agents to queue an outbound DM \u2014 never DM a contact directly without sign-off. Dry-run is on by default at execution time; the founder flips `runtime_settings.deliverable_executor_live='true'` to actually send.",{contact_id:Te.string().min(1).describe("Target contact id (from ohwow_list_contacts / ohwow_get_contact). Must be in the focused workspace and have custom_fields.x_handle set."),body:Te.string().min(1).describe("The DM body to send, verbatim. Keep short and conversational. Do NOT pitch a product, do NOT quote the contact's own pain back at them, and do NOT open with a hard ask \u2014 trust compounds slowly and breaks instantly."),agent_id:Te.string().optional().describe('Override which agent owns the draft. Defaults to the "Public Communications" (The Voice) agent if present, else the first available agent in the workspace.')},async({contact_id:t,body:e,agent_id:r})=>{try{let s={contact_id:t,body:e};r&&(s.agent_id=r);let a=await n.post("/api/x-dm-drafts",s);return a.error?De(`Couldn't draft DM: ${a.error}`):a.data?tr({ok:!0,...a.data}):De("Draft response missing data.")}catch(s){return De(`Error: ${s instanceof Error?s.message:"Unknown error"}`)}})}import{z as ue}from"zod";function D(o){return{content:[{type:"text",text:o}],isError:!0}}function me(o){return{content:[{type:"text",text:JSON.stringify(o,null,2)}]}}function ut(o,n){o.tool("ohwow_list_approvals","[Approvals] List every task in status=needs_approval for the focused workspace. Returns title, agent id, status, and the task's output payload so the operator can read what's waiting for sign-off. This is the wider queue that the web Approvals screen shows. Permission-denied tasks also appear here (their approval_reason will be 'permission_denied') but resolve those with ohwow_approve_permission_request, not the generic approve/reject tools, because the permission path spawns a resume child task.",{},async()=>{try{let t=await n.get("/api/approvals");if(t.error)return D(`Couldn't list approvals: ${t.error}`);let e=t.data??[];return me({ok:!0,count:e.length,tasks:e,note:e.length===0?"No tasks waiting for approval.":"Use ohwow_approve_task { id } to approve (fires the attached deliverable action), or ohwow_reject_task { id, reason } to reject."})}catch(t){return D(`Error: ${t instanceof Error?t.message:"Unknown error"}`)}}),o.tool("ohwow_preview_approval","[Approvals] Preview what ohwow_approve_task will actually fire \u2014 *without* flipping status. Returns the task, its attached deliverables (type, provider, status, content preview, target handle/conversation for DMs), whether the deliverable executor is in live-send mode, and a one-line verdict ('will send DM to @handle via Playwright for real', 'will mark done only \u2014 no deliverable', 'will dry-run log because executor live=false', 'task already resolved', etc.). Call this before ohwow_approve_task whenever the task description is vague or it's not obvious whether approval has external side-effects (DM / tweet / email). For permission-denied approvals, use ohwow_list_permission_requests instead.",{id:ue.string().min(1).describe("Task id from ohwow_list_approvals.")},async({id:t})=>{try{let e=await n.get(`/api/approvals/${encodeURIComponent(t)}/preview`);return e.error?D(`Couldn't preview approval: ${e.error}`):e.data?me({ok:!0,...e.data}):D("Task not found.")}catch(e){return D(`Error: ${e instanceof Error?e.message:"Unknown error"}`)}}),o.tool("ohwow_approve_task","[Approvals] Approve a task that's currently in status=needs_approval. Flips status to 'approved', cascades any attached deliverables in pending_review to approved, and runs the real-world deliverable action (post tweet, send email, etc.) unless the workspace is in dry-run mode. Returns the execution result array so the caller can see whether sending succeeded. For permission-denied approvals, use ohwow_approve_permission_request instead.",{id:ue.string().min(1).describe("Task id from ohwow_list_approvals.")},async({id:t})=>{try{let e=await n.post(`/api/approvals/${encodeURIComponent(t)}/approve`,{});return e.error?D(`Couldn't approve task: ${e.error}`):e.data?me({ok:!0,...e.data}):D("Task not found or not in needs_approval.")}catch(e){return D(`Error: ${e instanceof Error?e.message:"Unknown error"}`)}}),o.tool("ohwow_reject_task","[Approvals] Reject a task that's currently in status=needs_approval. Flips status to 'rejected', cascades any attached deliverables to rejected, and writes the optional reason to both the task and the deliverable for audit. No real-world action runs. For permission-denied tasks, use ohwow_approve_permission_request with mode='deny' instead so the failure is categorized correctly.",{id:ue.string().min(1).describe("Task id from ohwow_list_approvals."),reason:ue.string().optional().describe("Why the task was rejected. Stored on both task and deliverable as rejection_reason. Helpful when the agent retries or when reviewing later.")},async({id:t,reason:e})=>{try{let r={};e!==void 0&&(r.reason=e);let s=await n.post(`/api/approvals/${encodeURIComponent(t)}/reject`,r);return s.error?D(`Couldn't reject task: ${s.error}`):s.data?me({ok:!0,...s.data}):D("Task not found or not in needs_approval.")}catch(r){return D(`Error: ${r instanceof Error?r.message:"Unknown error"}`)}})}import{z as Ae}from"zod";var rr=["open","answered","resolved","expired"];function mt(o){return{content:[{type:"text",text:JSON.stringify(o,null,2)}]}}function ge(o){return{content:[{type:"text",text:o}],isError:!0}}function gt(o,n){o.tool("ohwow_list_founder_inbox",'[Director] List founder-inbox questions raised by the autonomy Director. These are *process* decisions (should this phase keep going, is this scope right) the runtime cannot resolve on its own. Defaults to status="open" \u2014 pass "answered" to see your replies that the Director has not yet picked up, "resolved" to see closed ones. Each row carries the originating arc/phase, the blocker (one sentence), longer context, options the Director considered, an optional recommended choice, and the timestamp the question was raised.',{status:Ae.enum(rr).optional().describe('Filter by inbox status. Defaults to "open".')},async({status:t})=>{try{let e=t?`?status=${encodeURIComponent(t)}`:"",r=await n.get(`/api/founder-inbox${e}`);if(r.error)return ge(`Couldn't list founder inbox: ${r.error}`);let s=r.data??[];return mt({ok:!0,count:s.length,status:t??"open",inbox:s.map(a=>({id:a.id,asked_at:a.asked_at,mode:a.mode,blocker:a.blocker,context:a.context,options:a.options,recommended:a.recommended,arc_id:a.arc_id,phase_id:a.phase_id,status:a.status,answer:a.answer,answered_at:a.answered_at})),note:s.length===0?"No founder-inbox rows match. The Director is unblocked.":`${s.length} row(s). Answer via ohwow_answer_founder_inbox.`})}catch(e){return ge(`Error: ${e instanceof Error?e.message:"Unknown error"}`)}}),o.tool("ohwow_answer_founder_inbox",'[Director] Answer a founder-inbox question by id. Pass the inbox row id and your answer text. The Director picks up the answer on its next tick, flips the row to "resolved", and feeds the text back into the next phase plan brief.',{id:Ae.string().min(1).describe("The inbox row id from ohwow_list_founder_inbox."),answer:Ae.string().min(1).describe("Your answer text. Be concrete; this is spliced into the next plan brief verbatim.")},async({id:t,answer:e})=>{try{let r=await n.post(`/api/founder-inbox/${encodeURIComponent(t)}/answer`,{answer:e});return r.error?ge(`Couldn't answer founder inbox row ${t}: ${r.error}`):mt({ok:!0,id:t})}catch(r){return ge(`Error: ${r instanceof Error?r.message:"Unknown error"}`)}})}import{z as or}from"zod";function ht(o){return{content:[{type:"text",text:JSON.stringify(o,null,2)}]}}function K(o){return{content:[{type:"text",text:o}],isError:!0}}function ft(o,n){o.tool("ohwow_autonomy_status","[Conductor] Snapshot of the autonomy stack: dark-launch flag state, any open arc with budget + elapsed time, the last 5 closed arcs with exit reasons, the last 10 phase reports across the workspace, inbox counts (open + answered-unresolved), and pulse-side counts (failing triggers, pending approvals). Cheap reads only \u2014 no LLM calls, no writes. Use this BEFORE deciding to flip OHWOW_AUTONOMY_CONDUCTOR=1 or hand-resolve a stuck row.",{},async()=>{try{let t=await n.get("/api/autonomy/status");if(t.error)return K(`Couldn't read autonomy status: ${t.error}`);let e=t.data;if(!e)return K("Autonomy status returned no data.");let r=[];return r.push(`flag=${e.flag_on?"ON":"OFF"}`),r.push(`open_arcs=${e.open_arcs.length}`),r.push(`open_inbox=${e.open_inbox_count}`),r.push(`answered_unresolved=${e.answered_unresolved_inbox_count}`),r.push(`pending_approvals=${e.pending_approvals_count}`),r.push(`failing_triggers=${e.failing_triggers_count}`),ht({ok:!0,summary:r.join(" / "),state:e,note:e.flag_on?e.open_arcs.length===0?"Conductor enabled; no arc currently open.":`Arc ${e.open_arcs[0].arc_id} in flight (${e.open_arcs[0].elapsed_minutes}m elapsed; ${e.open_arcs[0].phases_remaining} phases left in budget).`:"Conductor is dark-launched. Set OHWOW_AUTONOMY_CONDUCTOR=1 in the process env and restart to enable. Use ohwow_autonomy_dry_run first to see what would be picked."})}catch(t){return K(`Error: ${t instanceof Error?t.message:"Unknown error"}`)}}),o.tool("ohwow_autonomy_dry_run",'[Conductor] What the conductor\'s ranker WOULD return if it ticked right now. Reads pulse + ledger + workspace-wide answered inbox; runs the same `rankNextPhase` the conductor uses; never opens an arc. Returns the top N candidates (default 10) with their score, source, and goal. Use this to preview behavior before flipping OHWOW_AUTONOMY_CONDUCTOR=1, or to debug "why did the conductor pick X" after the fact.',{limit:or.number().int().positive().max(100).optional().describe("Cap the returned candidates. Default: 10. Max: 100.")},async({limit:t})=>{try{let e=t!==void 0?`?limit=${t}`:"",r=await n.get(`/api/autonomy/dry-run${e}`);if(r.error)return K(`Couldn't run autonomy dry-run: ${r.error}`);let s=r.data;return s?ht({ok:!0,ts:s.ts,total_candidates:s.total_candidates,pre_seed_inbox_count:s.pre_seed_inbox_count,candidates:s.candidates,note:s.candidates.length===0?"No candidates. The conductor would open an arc that closes immediately with `nothing-queued`.":`Top pick: ${s.candidates[0].source} score=${s.candidates[0].score} ("${s.candidates[0].goal}").`}):K("Autonomy dry-run returned no data.")}catch(e){return K(`Error: ${e instanceof Error?e.message:"Unknown error"}`)}})}import{z as Y}from"zod";function wt(o){return{content:[{type:"text",text:o}],isError:!0}}function nr(o){return{content:[{type:"text",text:JSON.stringify(o,null,2)}]}}function yt(o,n){o.tool("ohwow_list_cdp_events","[CDP] List Chrome browser lifecycle trace events. Returns cdp:true structured log entries with action, profile, owner, and timestamp. Use for debugging browser automation, detecting claim leaks (claim without matching release), and auditing Chrome tab lifecycle.",{profile:Y.string().optional().describe('Filter by Chrome profile directory name (e.g. "Default", "Profile 1").'),owner:Y.string().optional().describe("Filter by claim owner (task id or session id that claimed the tab)."),action:Y.string().optional().describe('Filter by action string (e.g. "claim", "release", "browser:open", "tab:attach", "navigate").'),since:Y.string().optional().describe("ISO 8601 timestamp. Only return events at or after this time."),limit:Y.number().int().positive().max(200).optional().describe("Cap on rows returned. Default 50, hard max 200.")},async({profile:t,owner:e,action:r,since:s,limit:a})=>{try{let i=new URLSearchParams;t&&i.set("profile",t),e&&i.set("owner",e),r&&i.set("action",r),s&&i.set("since",s),a!==void 0&&i.set("limit",String(a));let c=i.toString()?`?${i.toString()}`:"",l=await n.get(`/api/cdp-trace-events${c}`);return l.error?wt(`Couldn't list CDP events: ${l.error}`):nr({data:l.data??[],count:l.count??0,limit:l.limit??50})}catch(i){return wt(`Error: ${i instanceof Error?i.message:"Unknown error"}`)}})}function bt(o,n){We(o,n),$e(o,n),Ne(o,n),je(o,n),He(o,n),Fe(o,n),Je(o,n),qe(o,n),Ke(o),ze(o,n),Ge(o,n),Xe(o,n),Ve(o,n),Ze(o,n),tt(o,n),rt(o,n),ot(o,n),nt(o,n),st(o,n),at(o,n),it(o,n),ct(o,n),lt(o,n),dt(o,n),pt(o,n),ut(o,n),gt(o,n),ft(o,n),yt(o,n)}import{z as he}from"zod";function _t(o,n){o.tool("ohwow_embed","[Embeddings] Embed one or more texts into dense vectors using the daemon's in-process Qwen3-Embedding-0.6B model (1024-dim, L2-normalized). Direct HTTP, no orchestrator round-trip. First call after process start may block up to ~30s waiting on model warmup; subsequent calls are sub-second. Pass is_query=true with an optional instruction for asymmetric query encoding. Hard cap: 256 texts per call.",{texts:he.array(he.string().min(1)).min(1).max(256).describe("1-256 non-empty strings to embed. Order is preserved in the returned vectors array."),is_query:he.boolean().optional().describe("When true, treat the texts as retrieval queries (applies Qwen3-style asymmetric encoding). Default: false (document/passage encoding)."),instruction:he.string().optional().describe('Qwen3-style task instruction applied only when is_query=true. Example: "Given a web search query, retrieve relevant passages that answer the query".')},async({texts:t,is_query:e,instruction:r})=>{try{let s={texts:t};e!==void 0&&(s.is_query=e),r!==void 0&&(s.instruction=r);let a=await n.post("/api/embed",s);return a.error?{content:[{type:"text",text:`Couldn't embed: ${a.error}`}],isError:!0}:{content:[{type:"text",text:JSON.stringify({model:a.model,dim:a.dim,count:a.count,latency_ms:a.latency_ms,vectors:a.vectors},null,2)}]}}catch(s){return{content:[{type:"text",text:`Error: ${s instanceof Error?s.message:"Unknown error"}`}],isError:!0}}})}function xt(o,n){o.resource("agents","ohwow://agents",{description:"All OHWOW agents with their descriptions, roles, and available tools"},async()=>{try{let t=await n.get("/api/agents"),e=t.data||t;return{contents:[{uri:"ohwow://agents",mimeType:"application/json",text:JSON.stringify(e,null,2)}]}}catch{return{contents:[{uri:"ohwow://agents",mimeType:"text/plain",text:"Could not load agents. Is the OHWOW daemon running?"}]}}}),o.resource("workspace","ohwow://workspace",{description:"OHWOW workspace status: tier, uptime, agent count, system stats"},async()=>{try{let t=await n.get("/api/dashboard/init");return{contents:[{uri:"ohwow://workspace",mimeType:"application/json",text:JSON.stringify(t,null,2)}]}}catch{return{contents:[{uri:"ohwow://workspace",mimeType:"text/plain",text:"Could not load workspace status. Is the OHWOW daemon running?"}]}}}),o.resource("contacts","ohwow://contacts",{description:"Recent CRM contacts with pipeline stage breakdown"},async()=>{try{let t=await n.get("/api/contacts?limit=10"),e=t.data||t;return{contents:[{uri:"ohwow://contacts",mimeType:"application/json",text:JSON.stringify(e,null,2)}]}}catch{return{contents:[{uri:"ohwow://contacts",mimeType:"text/plain",text:"Could not load contacts. Is the OHWOW daemon running?"}]}}}),o.resource("projects","ohwow://projects",{description:"Active projects with task counts and status"},async()=>{try{let t=await n.get("/api/projects"),e=t.data||t;return{contents:[{uri:"ohwow://projects",mimeType:"application/json",text:JSON.stringify(e,null,2)}]}}catch{return{contents:[{uri:"ohwow://projects",mimeType:"text/plain",text:"Could not load projects. Is the OHWOW daemon running?"}]}}}),o.resource("workflows","ohwow://workflows",{description:"Workflow and automation catalog with descriptions and trigger types"},async()=>{try{let[t,e]=await Promise.all([n.get("/api/workflows"),n.get("/api/automations")]),r=t.data||t,s=e.data||e;return{contents:[{uri:"ohwow://workflows",mimeType:"application/json",text:JSON.stringify({workflows:r,automations:s},null,2)}]}}catch{return{contents:[{uri:"ohwow://workflows",mimeType:"text/plain",text:"Could not load workflows. Is the OHWOW daemon running?"}]}}}),o.resource("capabilities","ohwow://capabilities",{description:"Complete list of all OHWOW MCP tools grouped by domain"},async()=>({contents:[{uri:"ohwow://capabilities",mimeType:"text/plain",text:sr}]}))}var sr=`OHWOW MCP Plugin \u2014 Quick Reference
32
+ Use available tools to fetch real data. Format clearly with sections and numbers.`},6e4)||"Could not generate report."}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as y}from"zod";function ct(n,o){n.tool("ohwow_list_expenses","[Finance] List business expenses. Returns id, description, amount_cents, currency, vendor, expense_date, category_id, is_recurring, tax_deductible, and tags.",{category_id:y.string().optional().describe("Filter by expense category ID"),after:y.string().optional().describe("Only expenses after this date (YYYY-MM-DD)"),before:y.string().optional().describe("Only expenses before this date (YYYY-MM-DD)"),limit:y.number().optional().describe("Max results (default: 50)")},async({category_id:t,after:e,before:r,limit:s})=>{try{let a=new URLSearchParams;t&&a.set("category_id",t),e&&a.set("after",e),r&&a.set("before",r),s&&a.set("limit",String(s));let i=a.toString(),c=await o.get(`/api/expenses${i?`?${i}`:""}`);return{content:[{type:"text",text:JSON.stringify(c.data||c,null,2)}]}}catch(a){return{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_log_expense","[Finance] Log a business expense with amount, date, category, and vendor.",{description:y.string().describe("What the expense was for"),amount_cents:y.number().describe("Amount in cents (e.g. 9900 = $99.00)"),expense_date:y.string().describe("Date of expense (ISO format)"),category_id:y.string().optional().describe("Expense category ID"),vendor:y.string().optional().describe("Vendor or merchant name"),tax_deductible:y.boolean().optional().describe("Whether this expense is tax deductible"),is_recurring:y.boolean().optional().describe("Whether this is a recurring expense")},async({description:t,amount_cents:e,expense_date:r,category_id:s,vendor:a,tax_deductible:i,is_recurring:c})=>{try{let l={description:t,amount_cents:e,expense_date:r};s&&(l.category_id=s),a&&(l.vendor=a),i!==void 0&&(l.tax_deductible=i),c!==void 0&&(l.is_recurring=c);let p=await o.post("/api/expenses",l);return{content:[{type:"text",text:JSON.stringify(p,null,2)}]}}catch(l){return{content:[{type:"text",text:`Error: ${l instanceof Error?l.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_financial_summary","[Finance] P&L summary: total revenue, total expenses, net income, and expense breakdown by category.",{period_start:y.string().optional().describe("Start of period (ISO date). Defaults to first of current month."),period_end:y.string().optional().describe("End of period (ISO date). Defaults to today.")},async({period_start:t,period_end:e})=>{try{let r=new URLSearchParams;t&&r.set("period_start",t),e&&r.set("period_end",e);let s=r.toString(),a=await o.get(`/api/expenses/summary${s?`?${s}`:""}`);return{content:[{type:"text",text:JSON.stringify(a.data||a,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_list_team","[Team] List team members. Returns id, name, role, department, email, hourly_rate_cents, and status. Uses the existing team-members API.",{},async()=>{try{let t=await o.get("/api/team-members");return{content:[{type:"text",text:JSON.stringify(t.data||t,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_track_time","[Time] Log a time entry for a team member on a project, deal, or ticket.",{team_member_id:y.string().describe("Team member ID"),duration_minutes:y.number().describe("Duration in minutes"),entry_date:y.string().describe("Date of work (ISO format)"),project_id:y.string().optional().describe("Project ID"),deal_id:y.string().optional().describe("Deal ID"),ticket_id:y.string().optional().describe("Support ticket ID"),description:y.string().optional().describe("What was worked on"),billable:y.boolean().optional().describe("Whether this time is billable (default: true)")},async({team_member_id:t,duration_minutes:e,entry_date:r,project_id:s,deal_id:a,ticket_id:i,description:c,billable:l})=>{try{let p={team_member_id:t,duration_minutes:e,entry_date:r};s&&(p.project_id=s),a&&(p.deal_id=a),i&&(p.ticket_id=i),c&&(p.description=c),l!==void 0&&(p.billable=l);let u=await o.post("/api/time-entries",p);return{content:[{type:"text",text:JSON.stringify(u,null,2)}]}}catch(p){return{content:[{type:"text",text:`Error: ${p instanceof Error?p.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_time_report","[Time] Time tracking report. Returns total_hours, billable_hours, entry_count, and grouped breakdown (by person name, project name, or date). Each group shows total_minutes, billable_minutes, and entry_count.",{group_by:y.enum(["person","project","date"]).optional().describe("How to group results (default: person)"),after:y.string().optional().describe("Only entries after this date (YYYY-MM-DD)"),before:y.string().optional().describe("Only entries before this date (YYYY-MM-DD)"),team_member_id:y.string().optional().describe("Filter by team member"),project_id:y.string().optional().describe("Filter by project")},async({group_by:t,after:e,before:r,team_member_id:s,project_id:a})=>{try{let i=new URLSearchParams;t&&i.set("group_by",t),e&&i.set("after",e),r&&i.set("before",r),s&&i.set("team_member_id",s),a&&i.set("project_id",a);let c=i.toString(),l=await o.get(`/api/time-entries/report${c?`?${c}`:""}`);return{content:[{type:"text",text:JSON.stringify(l.data||l,null,2)}]}}catch(i){return{content:[{type:"text",text:`Error: ${i instanceof Error?i.message:"Unknown error"}`}],isError:!0}}})}import{z as ue}from"zod";function P(n){return{content:[{type:"text",text:n}],isError:!0}}function Ce(n){return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}function lt(n,o){n.tool("ohwow_list_x_drafts","[Market Radar] List candidate X post drafts the process has queued from novel market-radar findings. Each draft is 1-2 tweet-length post bodies, generated by the hourly XDraftDistillerScheduler from insights whose subject starts with `market:` (competitor pricing, rival releases, arXiv / HN / PH drift). Rows are 'pending' until you approve or reject; once approved, the posting path picks them up. Use status='pending' to see only what's waiting for you. Each row carries the source_finding_id so you can trace it back with ohwow_list_findings.",{status:ue.enum(["pending","approved","rejected"]).optional().describe("Filter by status. Default returns all. Use 'pending' to see only what needs review."),limit:ue.number().int().positive().max(200).optional().describe("Cap on rows returned. Default 50, hard max 200.")},async({status:t,limit:e})=>{try{let r=new URLSearchParams;t&&r.set("status",t),e!==void 0&&r.set("limit",String(e));let s=r.toString()?`?${r.toString()}`:"",a=await o.get(`/api/x-drafts${s}`);if(a.error)return P(`Couldn't list X drafts: ${a.error}`);let i=a.data??[];return Ce({ok:!0,count:i.length,limit:a.limit,drafts:i,note:i.length===0?"No drafts queued. Either the market-radar distiller has not ticked yet, or no insights have crossed the novelty floor. Run ohwow_list_insights to see what is ranked.":`${i.length} draft(s). Approve with ohwow_approve_x_draft, reject with ohwow_reject_x_draft. Trace back with ohwow_list_findings { experiment_id: <from source_finding_id> }.`})}catch(r){return P(`Error: ${r instanceof Error?r.message:"Unknown error"}`)}}),n.tool("ohwow_approve_x_draft","[Market Radar] Approve an X post draft. Flips status to 'approved' and stamps approved_at. The posting path picks approved drafts up on its next cadence tick. Approval is reversible only by the draft never being picked up \u2014 once it posts, the posted_log is the source of truth.",{id:ue.string().min(1).describe("The draft row id from ohwow_list_x_drafts.")},async({id:t})=>{try{let e=await o.post(`/api/x-drafts/${encodeURIComponent(t)}/approve`,{});return e.error?P(`Couldn't approve draft: ${e.error}`):e.data?Ce({ok:!0,draft:e.data}):P("Draft not found.")}catch(e){return P(`Error: ${e instanceof Error?e.message:"Unknown error"}`)}}),n.tool("ohwow_reject_x_draft","[Market Radar] Reject an X post draft. Flips status to 'rejected' and stamps rejected_at. Rejected drafts are skipped by the posting path and remain in the table for audit.",{id:ue.string().min(1).describe("The draft row id from ohwow_list_x_drafts.")},async({id:t})=>{try{let e=await o.post(`/api/x-drafts/${encodeURIComponent(t)}/reject`,{});return e.error?P(`Couldn't reject draft: ${e.error}`):e.data?Ce({ok:!0,draft:e.data}):P("Draft not found.")}catch(e){return P(`Error: ${e instanceof Error?e.message:"Unknown error"}`)}})}import{z as X}from"zod";function U(n){return{content:[{type:"text",text:n}],isError:!0}}function De(n){return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}function dt(n,o){n.tool("ohwow_list_x_reply_drafts","[Reply pipeline] List candidate X/Threads reply drafts the process has queued from the pain-finder pipeline. Each draft contains the target post (reply_to_url + author + text + engagement), the drafted reply body, alternates, the classifier verdict (class, pain_domain, severity, sellerish), and a score. Modes: 'direct' (1:1 reply to a real operator, genuine_pain or solo_service_provider class) and 'viral' (broadcast reply into a crowded ICP-packed thread). Rows are 'pending' until you approve or reject; once approved, the reply dispatcher picks them up on its next tick (~5 min).",{platform:X.enum(["x","threads"]).optional().describe("Filter by platform. Default returns both."),status:X.enum(["pending","approved","rejected","applied","auto_applied"]).optional().describe("Filter by status. 'pending' = awaiting review; 'approved' = queued for dispatch; 'applied' = already posted; 'auto_applied' = posted without approval gate; 'rejected' = skipped."),limit:X.number().int().positive().max(200).optional().describe("Cap on rows returned. Default 50, hard max 200.")},async({platform:t,status:e,limit:r})=>{try{let s=new URLSearchParams;t&&s.set("platform",t),e&&s.set("status",e),r!==void 0&&s.set("limit",String(r));let a=s.toString()?`?${s.toString()}`:"",i=await o.get(`/api/x-reply-drafts${a}`);if(i.error)return U(`Couldn't list reply drafts: ${i.error}`);let c=i.data??[];return De({ok:!0,count:c.length,limit:i.limit,drafts:c,note:c.length===0?"X reply drafts are permanently disabled. The X account is banned. Only Threads reply drafts are produced.":`${c.length} draft(s). Approve with ohwow_approve_x_reply_draft, reject with ohwow_reject_x_reply_draft. Dispatcher ticks ~5 min after approval.`})}catch(s){return U(`Error: ${s instanceof Error?s.message:"Unknown error"}`)}}),n.tool("ohwow_approve_x_reply_draft","[Reply pipeline] Approve a reply draft. Flips status to 'approved' and stamps approved_at. The reply dispatcher (5-min tick) picks up approved rows, takes the CDP lane, calls the platform's compose_reply executor, and stamps 'applied' on success. Daily cap (x_reply.daily_cap / threads_reply.daily_cap, default 10) is enforced at dispatch time.",{id:X.string().min(1).describe("The draft row id from ohwow_list_x_reply_drafts.")},async({id:t})=>{try{let e=await o.post(`/api/x-reply-drafts/${encodeURIComponent(t)}/approve`,{});return e.error?U(`Couldn't approve draft: ${e.error}`):e.data?De({ok:!0,draft:e.data}):U("Draft not found.")}catch(e){return U(`Error: ${e instanceof Error?e.message:"Unknown error"}`)}}),n.tool("ohwow_reject_x_reply_draft","[Reply pipeline] Reject a reply draft. Flips status to 'rejected' and stamps rejected_at. Rejected drafts are skipped by the dispatcher and remain in the table for audit.",{id:X.string().min(1).describe("The draft row id from ohwow_list_x_reply_drafts.")},async({id:t})=>{try{let e=await o.post(`/api/x-reply-drafts/${encodeURIComponent(t)}/reject`,{});return e.error?U(`Couldn't reject draft: ${e.error}`):e.data?De({ok:!0,draft:e.data}):U("Draft not found.")}catch(e){return U(`Error: ${e instanceof Error?e.message:"Unknown error"}`)}})}import{z as Te}from"zod";function Ae(n){return{content:[{type:"text",text:n}],isError:!0}}function sr(n){return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}function pt(n,o){n.tool("ohwow_draft_x_dm","[Outreach] Stage an outbound X DM for founder approval. Creates a needs_approval task + a send_dm deliverable attached to the given contact; the draft then appears in ohwow_list_approvals and the founder fires or rejects it with ohwow_approve_task / ohwow_reject_task. The contact must already carry an `x_handle` in `custom_fields` (use ohwow_get_contact to verify). This is the ONLY sanctioned path for agents to queue an outbound DM \u2014 never DM a contact directly without sign-off. Dry-run is on by default at execution time; the founder flips `runtime_settings.deliverable_executor_live='true'` to actually send.",{contact_id:Te.string().min(1).describe("Target contact id (from ohwow_list_contacts / ohwow_get_contact). Must be in the focused workspace and have custom_fields.x_handle set."),body:Te.string().min(1).describe("The DM body to send, verbatim. Keep short and conversational. Do NOT pitch a product, do NOT quote the contact's own pain back at them, and do NOT open with a hard ask \u2014 trust compounds slowly and breaks instantly."),agent_id:Te.string().optional().describe('Override which agent owns the draft. Defaults to the "Public Communications" (The Voice) agent if present, else the first available agent in the workspace.')},async({contact_id:t,body:e,agent_id:r})=>{try{let s={contact_id:t,body:e};r&&(s.agent_id=r);let a=await o.post("/api/x-dm-drafts",s);return a.error?Ae(`Couldn't draft DM: ${a.error}`):a.data?sr({ok:!0,...a.data}):Ae("Draft response missing data.")}catch(s){return Ae(`Error: ${s instanceof Error?s.message:"Unknown error"}`)}})}import{z as me}from"zod";function T(n){return{content:[{type:"text",text:n}],isError:!0}}function ge(n){return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}function ut(n,o){n.tool("ohwow_list_approvals","[Approvals] List every task in status=needs_approval for the focused workspace. Returns title, agent id, status, and the task's output payload so the operator can read what's waiting for sign-off. This is the wider queue that the web Approvals screen shows. Permission-denied tasks also appear here (their approval_reason will be 'permission_denied') but resolve those with ohwow_approve_permission_request, not the generic approve/reject tools, because the permission path spawns a resume child task.",{},async()=>{try{let t=await o.get("/api/approvals");if(t.error)return T(`Couldn't list approvals: ${t.error}`);let e=t.data??[];return ge({ok:!0,count:e.length,tasks:e,note:e.length===0?"No tasks waiting for approval.":"Use ohwow_approve_task { id } to approve (fires the attached deliverable action), or ohwow_reject_task { id, reason } to reject."})}catch(t){return T(`Error: ${t instanceof Error?t.message:"Unknown error"}`)}}),n.tool("ohwow_preview_approval","[Approvals] Preview what ohwow_approve_task will actually fire \u2014 *without* flipping status. Returns the task, its attached deliverables (type, provider, status, content preview, target handle/conversation for DMs), whether the deliverable executor is in live-send mode, and a one-line verdict ('will send DM to @handle via Playwright for real', 'will mark done only \u2014 no deliverable', 'will dry-run log because executor live=false', 'task already resolved', etc.). Call this before ohwow_approve_task whenever the task description is vague or it's not obvious whether approval has external side-effects (DM / tweet / email). For permission-denied approvals, use ohwow_list_permission_requests instead.",{id:me.string().min(1).describe("Task id from ohwow_list_approvals.")},async({id:t})=>{try{let e=await o.get(`/api/approvals/${encodeURIComponent(t)}/preview`);return e.error?T(`Couldn't preview approval: ${e.error}`):e.data?ge({ok:!0,...e.data}):T("Task not found.")}catch(e){return T(`Error: ${e instanceof Error?e.message:"Unknown error"}`)}}),n.tool("ohwow_approve_task","[Approvals] Approve a task that's currently in status=needs_approval. Flips status to 'approved', cascades any attached deliverables in pending_review to approved, and runs the real-world deliverable action (post tweet, send email, etc.) unless the workspace is in dry-run mode. Returns the execution result array so the caller can see whether sending succeeded. For permission-denied approvals, use ohwow_approve_permission_request instead.",{id:me.string().min(1).describe("Task id from ohwow_list_approvals.")},async({id:t})=>{try{let e=await o.post(`/api/approvals/${encodeURIComponent(t)}/approve`,{});return e.error?T(`Couldn't approve task: ${e.error}`):e.data?ge({ok:!0,...e.data}):T("Task not found or not in needs_approval.")}catch(e){return T(`Error: ${e instanceof Error?e.message:"Unknown error"}`)}}),n.tool("ohwow_reject_task","[Approvals] Reject a task that's currently in status=needs_approval. Flips status to 'rejected', cascades any attached deliverables to rejected, and writes the optional reason to both the task and the deliverable for audit. No real-world action runs. For permission-denied tasks, use ohwow_approve_permission_request with mode='deny' instead so the failure is categorized correctly.",{id:me.string().min(1).describe("Task id from ohwow_list_approvals."),reason:me.string().optional().describe("Why the task was rejected. Stored on both task and deliverable as rejection_reason. Helpful when the agent retries or when reviewing later.")},async({id:t,reason:e})=>{try{let r={};e!==void 0&&(r.reason=e);let s=await o.post(`/api/approvals/${encodeURIComponent(t)}/reject`,r);return s.error?T(`Couldn't reject task: ${s.error}`):s.data?ge({ok:!0,...s.data}):T("Task not found or not in needs_approval.")}catch(r){return T(`Error: ${r instanceof Error?r.message:"Unknown error"}`)}})}import{z as Me}from"zod";var ar=["open","answered","resolved","expired"];function mt(n){return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}function he(n){return{content:[{type:"text",text:n}],isError:!0}}function gt(n,o){n.tool("ohwow_list_founder_inbox",'[Director] List founder-inbox questions raised by the autonomy Director. These are *process* decisions (should this phase keep going, is this scope right) the runtime cannot resolve on its own. Defaults to status="open" \u2014 pass "answered" to see your replies that the Director has not yet picked up, "resolved" to see closed ones. Each row carries the originating arc/phase, the blocker (one sentence), longer context, options the Director considered, an optional recommended choice, and the timestamp the question was raised.',{status:Me.enum(ar).optional().describe('Filter by inbox status. Defaults to "open".')},async({status:t})=>{try{let e=t?`?status=${encodeURIComponent(t)}`:"",r=await o.get(`/api/founder-inbox${e}`);if(r.error)return he(`Couldn't list founder inbox: ${r.error}`);let s=r.data??[];return mt({ok:!0,count:s.length,status:t??"open",inbox:s.map(a=>({id:a.id,asked_at:a.asked_at,mode:a.mode,blocker:a.blocker,context:a.context,options:a.options,recommended:a.recommended,arc_id:a.arc_id,phase_id:a.phase_id,status:a.status,answer:a.answer,answered_at:a.answered_at})),note:s.length===0?"No founder-inbox rows match. The Director is unblocked.":`${s.length} row(s). Answer via ohwow_answer_founder_inbox.`})}catch(e){return he(`Error: ${e instanceof Error?e.message:"Unknown error"}`)}}),n.tool("ohwow_answer_founder_inbox",'[Director] Answer a founder-inbox question by id. Pass the inbox row id and your answer text. The Director picks up the answer on its next tick, flips the row to "resolved", and feeds the text back into the next phase plan brief.',{id:Me.string().min(1).describe("The inbox row id from ohwow_list_founder_inbox."),answer:Me.string().min(1).describe("Your answer text. Be concrete; this is spliced into the next plan brief verbatim.")},async({id:t,answer:e})=>{try{let r=await o.post(`/api/founder-inbox/${encodeURIComponent(t)}/answer`,{answer:e});return r.error?he(`Couldn't answer founder inbox row ${t}: ${r.error}`):mt({ok:!0,id:t})}catch(r){return he(`Error: ${r instanceof Error?r.message:"Unknown error"}`)}})}import{z as ir}from"zod";function ht(n){return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}function K(n){return{content:[{type:"text",text:n}],isError:!0}}function ft(n,o){n.tool("ohwow_autonomy_status","[Conductor] Snapshot of the autonomy stack: dark-launch flag state, any open arc with budget + elapsed time, the last 5 closed arcs with exit reasons, the last 10 phase reports across the workspace, inbox counts (open + answered-unresolved), and pulse-side counts (failing triggers, pending approvals). Cheap reads only \u2014 no LLM calls, no writes. Use this BEFORE deciding to flip OHWOW_AUTONOMY_CONDUCTOR=1 or hand-resolve a stuck row.",{},async()=>{try{let t=await o.get("/api/autonomy/status");if(t.error)return K(`Couldn't read autonomy status: ${t.error}`);let e=t.data;if(!e)return K("Autonomy status returned no data.");let r=[];return r.push(`flag=${e.flag_on?"ON":"OFF"}`),r.push(`open_arcs=${e.open_arcs.length}`),r.push(`open_inbox=${e.open_inbox_count}`),r.push(`answered_unresolved=${e.answered_unresolved_inbox_count}`),r.push(`pending_approvals=${e.pending_approvals_count}`),r.push(`failing_triggers=${e.failing_triggers_count}`),ht({ok:!0,summary:r.join(" / "),state:e,note:e.flag_on?e.open_arcs.length===0?"Conductor enabled; no arc currently open.":`Arc ${e.open_arcs[0].arc_id} in flight (${e.open_arcs[0].elapsed_minutes}m elapsed; ${e.open_arcs[0].phases_remaining} phases left in budget).`:"Conductor is dark-launched. Set OHWOW_AUTONOMY_CONDUCTOR=1 in the process env and restart to enable. Use ohwow_autonomy_dry_run first to see what would be picked."})}catch(t){return K(`Error: ${t instanceof Error?t.message:"Unknown error"}`)}}),n.tool("ohwow_autonomy_dry_run",'[Conductor] What the conductor\'s ranker WOULD return if it ticked right now. Reads pulse + ledger + workspace-wide answered inbox; runs the same `rankNextPhase` the conductor uses; never opens an arc. Returns the top N candidates (default 10) with their score, source, and goal. Use this to preview behavior before flipping OHWOW_AUTONOMY_CONDUCTOR=1, or to debug "why did the conductor pick X" after the fact.',{limit:ir.number().int().positive().max(100).optional().describe("Cap the returned candidates. Default: 10. Max: 100.")},async({limit:t})=>{try{let e=t!==void 0?`?limit=${t}`:"",r=await o.get(`/api/autonomy/dry-run${e}`);if(r.error)return K(`Couldn't run autonomy dry-run: ${r.error}`);let s=r.data;return s?ht({ok:!0,ts:s.ts,total_candidates:s.total_candidates,pre_seed_inbox_count:s.pre_seed_inbox_count,candidates:s.candidates,note:s.candidates.length===0?"No candidates. The conductor would open an arc that closes immediately with `nothing-queued`.":`Top pick: ${s.candidates[0].source} score=${s.candidates[0].score} ("${s.candidates[0].goal}").`}):K("Autonomy dry-run returned no data.")}catch(e){return K(`Error: ${e instanceof Error?e.message:"Unknown error"}`)}})}import{z as Y}from"zod";function wt(n){return{content:[{type:"text",text:n}],isError:!0}}function cr(n){return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}function yt(n,o){n.tool("ohwow_list_cdp_events","[CDP] List Chrome browser lifecycle trace events. Returns cdp:true structured log entries with action, profile, owner, and timestamp. Use for debugging browser automation, detecting claim leaks (claim without matching release), and auditing Chrome tab lifecycle.",{profile:Y.string().optional().describe('Filter by Chrome profile directory name (e.g. "Default", "Profile 1").'),owner:Y.string().optional().describe("Filter by claim owner (task id or session id that claimed the tab)."),action:Y.string().optional().describe('Filter by action string (e.g. "claim", "release", "browser:open", "tab:attach", "navigate").'),since:Y.string().optional().describe("ISO 8601 timestamp. Only return events at or after this time."),limit:Y.number().int().positive().max(200).optional().describe("Cap on rows returned. Default 50, hard max 200.")},async({profile:t,owner:e,action:r,since:s,limit:a})=>{try{let i=new URLSearchParams;t&&i.set("profile",t),e&&i.set("owner",e),r&&i.set("action",r),s&&i.set("since",s),a!==void 0&&i.set("limit",String(a));let c=i.toString()?`?${i.toString()}`:"",l=await o.get(`/api/cdp-trace-events${c}`);return l.error?wt(`Couldn't list CDP events: ${l.error}`):cr({data:l.data??[],count:l.count??0,limit:l.limit??50})}catch(i){return wt(`Error: ${i instanceof Error?i.message:"Unknown error"}`)}})}function bt(n,o){We(n,o),Ne(n,o),Le(n,o),Fe(n,o),He(n,o),Je(n,o),qe(n,o),Be(n,o),Ke(n),ze(n,o),Ge(n,o),Xe(n,o),Ve(n,o),Ze(n,o),tt(n,o),rt(n,o),ot(n,o),nt(n,o),st(n,o),at(n,o),it(n,o),ct(n,o),lt(n,o),dt(n,o),pt(n,o),ut(n,o),gt(n,o),ft(n,o),yt(n,o)}import{z as fe}from"zod";function _t(n,o){n.tool("ohwow_embed","[Embeddings] Embed one or more texts into dense vectors using the daemon's in-process Qwen3-Embedding-0.6B model (1024-dim, L2-normalized). Direct HTTP, no orchestrator round-trip. First call after process start may block up to ~30s waiting on model warmup; subsequent calls are sub-second. Pass is_query=true with an optional instruction for asymmetric query encoding. Hard cap: 256 texts per call.",{texts:fe.array(fe.string().min(1)).min(1).max(256).describe("1-256 non-empty strings to embed. Order is preserved in the returned vectors array."),is_query:fe.boolean().optional().describe("When true, treat the texts as retrieval queries (applies Qwen3-style asymmetric encoding). Default: false (document/passage encoding)."),instruction:fe.string().optional().describe('Qwen3-style task instruction applied only when is_query=true. Example: "Given a web search query, retrieve relevant passages that answer the query".')},async({texts:t,is_query:e,instruction:r})=>{try{let s={texts:t};e!==void 0&&(s.is_query=e),r!==void 0&&(s.instruction=r);let a=await o.post("/api/embed",s);return a.error?{content:[{type:"text",text:`Couldn't embed: ${a.error}`}],isError:!0}:{content:[{type:"text",text:JSON.stringify({model:a.model,dim:a.dim,count:a.count,latency_ms:a.latency_ms,vectors:a.vectors},null,2)}]}}catch(s){return{content:[{type:"text",text:`Error: ${s instanceof Error?s.message:"Unknown error"}`}],isError:!0}}})}function xt(n,o){n.resource("agents","ohwow://agents",{description:"All OHWOW agents with their descriptions, roles, and available tools"},async()=>{try{let t=await o.get("/api/agents"),e=t.data||t;return{contents:[{uri:"ohwow://agents",mimeType:"application/json",text:JSON.stringify(e,null,2)}]}}catch{return{contents:[{uri:"ohwow://agents",mimeType:"text/plain",text:"Could not load agents. Is the OHWOW daemon running?"}]}}}),n.resource("workspace","ohwow://workspace",{description:"OHWOW workspace status: tier, uptime, agent count, system stats"},async()=>{try{let t=await o.get("/api/dashboard/init");return{contents:[{uri:"ohwow://workspace",mimeType:"application/json",text:JSON.stringify(t,null,2)}]}}catch{return{contents:[{uri:"ohwow://workspace",mimeType:"text/plain",text:"Could not load workspace status. Is the OHWOW daemon running?"}]}}}),n.resource("contacts","ohwow://contacts",{description:"Recent CRM contacts with pipeline stage breakdown"},async()=>{try{let t=await o.get("/api/contacts?limit=10"),e=t.data||t;return{contents:[{uri:"ohwow://contacts",mimeType:"application/json",text:JSON.stringify(e,null,2)}]}}catch{return{contents:[{uri:"ohwow://contacts",mimeType:"text/plain",text:"Could not load contacts. Is the OHWOW daemon running?"}]}}}),n.resource("projects","ohwow://projects",{description:"Active projects with task counts and status"},async()=>{try{let t=await o.get("/api/projects"),e=t.data||t;return{contents:[{uri:"ohwow://projects",mimeType:"application/json",text:JSON.stringify(e,null,2)}]}}catch{return{contents:[{uri:"ohwow://projects",mimeType:"text/plain",text:"Could not load projects. Is the OHWOW daemon running?"}]}}}),n.resource("workflows","ohwow://workflows",{description:"Workflow and automation catalog with descriptions and trigger types"},async()=>{try{let[t,e]=await Promise.all([o.get("/api/workflows"),o.get("/api/automations")]),r=t.data||t,s=e.data||e;return{contents:[{uri:"ohwow://workflows",mimeType:"application/json",text:JSON.stringify({workflows:r,automations:s},null,2)}]}}catch{return{contents:[{uri:"ohwow://workflows",mimeType:"text/plain",text:"Could not load workflows. Is the OHWOW daemon running?"}]}}}),n.resource("capabilities","ohwow://capabilities",{description:"Complete list of all OHWOW MCP tools grouped by domain"},async()=>({contents:[{uri:"ohwow://capabilities",mimeType:"text/plain",text:lr}]}))}var lr=`OHWOW MCP Plugin \u2014 Quick Reference
27
33
 
28
34
  GETTING STARTED
29
35
  1. ohwow_workspace_status Check connection and workspace overview
@@ -79,9 +85,9 @@ CLOUD (instant, requires ohwow.fun)
79
85
  USE ohwow_chat FOR: desktop control, scheduling, agent state, approvals,
80
86
  A2A protocol, PDF forms, media generation, automation creation, and
81
87
  anything not listed above.
82
- `;import{createServer as ar}from"http";import{writeFileSync as ir,unlinkSync as cr,existsSync as lr}from"fs";import{join as dr}from"path";import{homedir as pr}from"os";var Me=dr(pr(),".ohwow","data","mcp-sampling.port");function kt(o){let n=null,t=ar(async(r,s)=>{if(r.method!=="POST"||r.url!=="/sampling"){s.writeHead(404),s.end("Not found");return}let a="";for await(let c of r)a+=c;let i;try{i=JSON.parse(a)}catch{s.writeHead(400),s.end(JSON.stringify({error:"Invalid JSON"}));return}if(!i.messages?.length){s.writeHead(400),s.end(JSON.stringify({error:"messages array is required"}));return}try{let c=i.messages.map(f=>({role:f.role,content:{type:"text",text:f.content}})),l=await o.server.createMessage({messages:c,systemPrompt:i.systemPrompt,maxTokens:i.maxTokens||4096,temperature:i.temperature}),u={content:Array.isArray(l.content)?l.content.filter(f=>f.type==="text").map(f=>f.text).join(""):typeof l.content=="object"&&"text"in l.content?l.content.text:String(l.content),model:l.model,stopReason:l.stopReason??void 0};s.writeHead(200,{"Content-Type":"application/json"}),s.end(JSON.stringify(u))}catch(c){let l=c instanceof Error?c.message:"Sampling failed";s.writeHead(500),s.end(JSON.stringify({error:l}))}});return n=t,t.listen(0,"127.0.0.1",()=>{let r=t.address();if(r&&typeof r=="object"){let s=r.port;try{ir(Me,String(s)),process.stderr.write(`[ohwow-mcp] Sampling bridge listening on port ${s}
88
+ `;import{createServer as dr}from"http";import{writeFileSync as pr,unlinkSync as ur,existsSync as mr}from"fs";import{join as gr}from"path";import{homedir as hr}from"os";var Pe=gr(hr(),".ohwow","data","mcp-sampling.port");function kt(n){let o=null,t=dr(async(r,s)=>{if(r.method!=="POST"||r.url!=="/sampling"){s.writeHead(404),s.end("Not found");return}let a="";for await(let c of r)a+=c;let i;try{i=JSON.parse(a)}catch{s.writeHead(400),s.end(JSON.stringify({error:"Invalid JSON"}));return}if(!i.messages?.length){s.writeHead(400),s.end(JSON.stringify({error:"messages array is required"}));return}try{let c=i.messages.map(h=>({role:h.role,content:{type:"text",text:h.content}})),l=await n.server.createMessage({messages:c,systemPrompt:i.systemPrompt,maxTokens:i.maxTokens||4096,temperature:i.temperature}),u={content:Array.isArray(l.content)?l.content.filter(h=>h.type==="text").map(h=>h.text).join(""):typeof l.content=="object"&&"text"in l.content?l.content.text:String(l.content),model:l.model,stopReason:l.stopReason??void 0};s.writeHead(200,{"Content-Type":"application/json"}),s.end(JSON.stringify(u))}catch(c){let l=c instanceof Error?c.message:"Sampling failed";s.writeHead(500),s.end(JSON.stringify({error:l}))}});return o=t,t.listen(0,"127.0.0.1",()=>{let r=t.address();if(r&&typeof r=="object"){let s=r.port;try{pr(Pe,String(s)),process.stderr.write(`[ohwow-mcp] Sampling bridge listening on port ${s}
83
89
  `)}catch{process.stderr.write(`[ohwow-mcp] Could not write sampling port file
84
- `)}}}),{cleanup:()=>{n&&(n.close(),n=null);try{lr(Me)&&cr(Me)}catch{}}}}import{readFileSync as ur}from"fs";import{join as mr}from"path";function gr(){try{let o=ur(mr(process.cwd(),"package.json"),"utf-8");return JSON.parse(o).version}catch{return"0.0.0"}}var Pe=gr();async function Vn(){let o;try{o=await ee.create()}catch(s){process.stderr.write(`[ohwow-mcp] ${s instanceof Error?s.message:"Unknown error"}
85
- `),process.exit(1)}let n=new hr({name:"ohwow",version:Pe});bt(n,o),_t(n,o),xt(n,o);let t=new fr,e=null,r=async()=>{e?.();try{await n.close()}catch{}process.exit(0)};process.on("SIGTERM",r),process.on("SIGINT",r);try{await n.connect(t),process.stderr.write(`[ohwow-mcp] Connected (v${Pe})
86
- `),e=kt(n).cleanup}catch(s){process.stderr.write(`[ohwow-mcp] Failed to connect: ${s instanceof Error?s.message:"Unknown error"}
87
- `),process.exit(1)}}export{Vn as startMcpServer};
90
+ `)}}}),{cleanup:()=>{o&&(o.close(),o=null);try{mr(Pe)&&ur(Pe)}catch{}}}}import{readFileSync as fr}from"fs";import{join as wr}from"path";function yr(){try{let n=fr(wr(process.cwd(),"package.json"),"utf-8");return JSON.parse(n).version}catch{return"0.0.0"}}var Ue=yr();async function ts(){let n;try{n=await ee.create()}catch(s){process.stderr.write(`[ohwow-mcp] ${s instanceof Error?s.message:"Unknown error"}
91
+ `),process.exit(1)}let o=new br({name:"ohwow",version:Ue});bt(o,n),_t(o,n),xt(o,n);let t=new _r,e=null,r=async()=>{e?.();try{await o.close()}catch{}process.exit(0)};process.on("SIGTERM",r),process.on("SIGINT",r);try{await o.connect(t),process.stderr.write(`[ohwow-mcp] Connected (v${Ue})
92
+ `),e=kt(o).cleanup}catch(s){process.stderr.write(`[ohwow-mcp] Failed to connect: ${s instanceof Error?s.message:"Unknown error"}
93
+ `),process.exit(1)}}export{ts as startMcpServer};