hyperclaw 5.0.0 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/LICENSE +2 -1
  2. package/README.md +449 -99
  3. package/dist/a2ui-protocol-Gzm29Gaw.js +75 -0
  4. package/dist/agents-routing-Biy5ew4a.js +4 -0
  5. package/dist/agents-routing-CL3HQNoM.js +327 -0
  6. package/dist/api-keys-guide-CGn5BSF7.js +149 -0
  7. package/dist/api-keys-guide-ChbThbPj.js +149 -0
  8. package/dist/audit-BJohI_vC.js +441 -0
  9. package/dist/audit-NPIMmOSq.js +441 -0
  10. package/dist/bounty-tools-BUqUKjt0.js +211 -0
  11. package/dist/bounty-tools-CY_i91DU.js +211 -0
  12. package/dist/browser-tools-CxJY6pAn.js +5 -0
  13. package/dist/browser-tools-JZ9ji6AW.js +179 -0
  14. package/dist/chat-qVuqhlPu.js +258 -0
  15. package/dist/claw-tasks-B-8RRMdq.js +80 -0
  16. package/dist/claw-tasks-Cyzdbhz_.js +80 -0
  17. package/dist/connector-1x1rCBHz.js +162 -0
  18. package/dist/connector-B4jeCULG.js +305 -0
  19. package/dist/connector-B7qngfkT.js +286 -0
  20. package/dist/connector-B8BK0GBo.js +531 -0
  21. package/dist/connector-BE9eJs8-.js +182 -0
  22. package/dist/connector-BEe-DTGQ.js +189 -0
  23. package/dist/connector-BU7p5ZgB.js +167 -0
  24. package/dist/connector-BUzzq7Ij.js +568 -0
  25. package/dist/connector-BpDqLgnW.js +419 -0
  26. package/dist/connector-BpW88ut2.js +189 -0
  27. package/dist/connector-Bxv-gy8U.js +167 -0
  28. package/dist/connector-Bz14zcJv.js +213 -0
  29. package/dist/connector-C1zP5-5q.js +85 -0
  30. package/dist/connector-CAcpcovF.js +498 -0
  31. package/dist/connector-CJgVjS58.js +181 -0
  32. package/dist/connector-Cf53D6qV.js +425 -0
  33. package/dist/connector-CyHmlbNz.js +508 -0
  34. package/dist/connector-D22mJGVu.js +340 -0
  35. package/dist/connector-D6RtMmlL.js +225 -0
  36. package/dist/connector-D9EnT8A4.js +280 -0
  37. package/dist/connector-DNDwIh37.js +239 -0
  38. package/dist/connector-Di27MeO4.js +350 -0
  39. package/dist/connector-Do0BPiHt.js +194 -0
  40. package/dist/connector-DvLwOfJy.js +192 -0
  41. package/dist/connector-DvU83NSq.js +181 -0
  42. package/dist/connector-DxskpDc_.js +173 -0
  43. package/dist/connector-byy3eISx.js +552 -0
  44. package/dist/connector-vV89hsyd.js +218 -0
  45. package/dist/cost-tracker-Ca1UPZ33.js +103 -0
  46. package/dist/cost-tracker-fnaj_6M9.js +103 -0
  47. package/dist/credentials-store-BxijEirw.js +77 -0
  48. package/dist/credentials-store-CA8UtK0T.js +77 -0
  49. package/dist/credentials-store-CPkVO6-z.js +4 -0
  50. package/dist/credentials-store-Cm7DH-kh.js +4 -0
  51. package/dist/cron-tasks-L0mz1yyU.js +82 -0
  52. package/dist/cron-tasks-_pqQCmxc.js +82 -0
  53. package/dist/daemon-7ViroziB.js +5 -0
  54. package/dist/daemon-BfyKmZhr.js +318 -0
  55. package/dist/daemon-CNyunwkR.js +5 -0
  56. package/dist/daemon-CindY8OK.js +318 -0
  57. package/dist/delivery-DVHmv1IR.js +4 -0
  58. package/dist/delivery-DgiZcJBp.js +4 -0
  59. package/dist/delivery-DpMX0Yyc.js +95 -0
  60. package/dist/delivery-otAU4alM.js +95 -0
  61. package/dist/destructive-gate-CA0DtA5K.js +101 -0
  62. package/dist/destructive-gate-DZt71UZR.js +101 -0
  63. package/dist/developer-keys-Cnd1kswV.js +127 -0
  64. package/dist/developer-keys-DENo3ZA6.js +8 -0
  65. package/dist/doctor-Dgjoc3DG.js +230 -0
  66. package/dist/doctor-RwsOhtAl.js +6 -0
  67. package/dist/engine-B0kLfRL0.js +256 -0
  68. package/dist/engine-BJUpRUOv.js +7 -0
  69. package/dist/engine-D_VeoZHw.js +305 -0
  70. package/dist/engine-JjRnhlsE.js +7 -0
  71. package/dist/env-resolve-17ekEU6p.js +10 -0
  72. package/dist/env-resolve-BFJXWl94.js +115 -0
  73. package/dist/env-resolve-Z2XF6leB.js +115 -0
  74. package/dist/env-resolve-bDYssfih.js +10 -0
  75. package/dist/extraction-tools-DbxnxIco.js +5 -0
  76. package/dist/extraction-tools-Dg7AHS35.js +91 -0
  77. package/dist/form_data-CGAy4HE0.js +8657 -0
  78. package/dist/gmail-watch-setup-C3uSWznp.js +40 -0
  79. package/dist/health-DUjluWHQ.js +6 -0
  80. package/dist/health-DVfkpUQW.js +152 -0
  81. package/dist/heartbeat-engine-CrgL4mrP.js +83 -0
  82. package/dist/heartbeat-engine-Ut6pXBD6.js +83 -0
  83. package/dist/hub-9LaKnLjY.js +6 -0
  84. package/dist/hub-BO6bj8Yj.js +515 -0
  85. package/dist/hub-Bu52YZqW.js +6 -0
  86. package/dist/hub-CfwUz9YW.js +515 -0
  87. package/dist/hyperclawbot-BrcoYLOp.js +505 -0
  88. package/dist/hyperclawbot-CBiDSKsa.js +505 -0
  89. package/dist/inference-0mlFQqIm.js +922 -0
  90. package/dist/inference-DHR82Gh7.js +6 -0
  91. package/dist/inference-DhA8jpfH.js +2692 -0
  92. package/dist/inference-SzqFe_nk.js +6 -0
  93. package/dist/knowledge-graph-BrYpSgxW.js +131 -0
  94. package/dist/knowledge-graph-DE5lSF02.js +131 -0
  95. package/dist/loader-9JqY6Nlq.js +4 -0
  96. package/dist/loader-BkDi8MD9.js +400 -0
  97. package/dist/loader-Cjdd1kw4.js +400 -0
  98. package/dist/loader-DI2qDRPC.js +4 -0
  99. package/dist/logger-Cp8wC7F8.js +83 -0
  100. package/dist/logger-DCT2l9GV.js +83 -0
  101. package/dist/manager-3cq3DydI.js +4 -0
  102. package/dist/manager-B2Gls5RG.js +218 -0
  103. package/dist/manager-BUrFrPuq.js +117 -0
  104. package/dist/manager-Bi9UYyVR.js +105 -0
  105. package/dist/manager-Biz9ixWJ.js +40 -0
  106. package/dist/manager-CBUHJiY7.js +6 -0
  107. package/dist/manager-CVLLaKmq.js +218 -0
  108. package/dist/manager-CWNSML5D.js +117 -0
  109. package/dist/manager-SJe9gt-q.js +4 -0
  110. package/dist/mcp-CUoTCMw-.js +139 -0
  111. package/dist/mcp-loader-BIz-450x.js +94 -0
  112. package/dist/mcp-loader-CvxRDtPC.js +94 -0
  113. package/dist/memory-OL77OMOr.js +270 -0
  114. package/dist/memory-auto-CpQHZlEJ.js +306 -0
  115. package/dist/memory-auto-D-L2q21G.js +306 -0
  116. package/dist/memory-auto-DTcy5VBy.js +5 -0
  117. package/dist/memory-auto-Z6LCf-iK.js +5 -0
  118. package/dist/memory-gUi4VaIf.js +4 -0
  119. package/dist/memory-integration-B8RSN4pr.js +91 -0
  120. package/dist/memory-integration-g2vxwgoE.js +91 -0
  121. package/dist/moltbook-B-40gQOL.js +81 -0
  122. package/dist/moltbook-Cl8cQfxJ.js +81 -0
  123. package/dist/node-TWxRm84k.js +222 -0
  124. package/dist/nodes-registry-C9dCFwjh.js +52 -0
  125. package/dist/nodes-registry-DKRtsbNg.js +52 -0
  126. package/dist/oauth-flow-CeaaGAz0.js +150 -0
  127. package/dist/oauth-flow-JCfporKq.js +150 -0
  128. package/dist/oauth-provider-4R0EJlsT.js +110 -0
  129. package/dist/oauth-provider-B4dzn56l.js +110 -0
  130. package/dist/observability-CDZmeHfa.js +89 -0
  131. package/dist/observability-nZ3CBIxG.js +89 -0
  132. package/dist/onboard-BBBWcfhp.js +10 -0
  133. package/dist/onboard-BVOtKQdh.js +3641 -0
  134. package/dist/onboard-Bw28IRQ3.js +4070 -0
  135. package/dist/onboard-CGNIw27w.js +11 -0
  136. package/dist/orchestrator-BovkM63z.js +6 -0
  137. package/dist/orchestrator-CcKx1Ovk.js +189 -0
  138. package/dist/orchestrator-DSbpkP1X.js +189 -0
  139. package/dist/orchestrator-DcFfDLTX.js +6 -0
  140. package/dist/osint-B4_m3VHQ.js +277 -0
  141. package/dist/osint-B6BZKQAD.js +277 -0
  142. package/dist/pairing-B6RArWhD.js +196 -0
  143. package/dist/pairing-BsQ08DLq.js +4 -0
  144. package/dist/pc-access-B0KocJNe.js +819 -0
  145. package/dist/pc-access-DkzmugZ7.js +8 -0
  146. package/dist/pending-approval-BgNjjuI2.js +22 -0
  147. package/dist/pending-approval-C_HkX1QL.js +22 -0
  148. package/dist/providers-DxiamZSL.js +5 -0
  149. package/dist/providers-Dy15rDb7.js +657 -0
  150. package/dist/reminders-store-CzUY0zYx.js +58 -0
  151. package/dist/renderer-ANNfXsHn.js +225 -0
  152. package/dist/rules-BSQwwAYC.js +103 -0
  153. package/dist/run-main.js +142 -132
  154. package/dist/runner-BHRSOPEU.js +1271 -0
  155. package/dist/runner-CJFJUtPm.js +1271 -0
  156. package/dist/sdk/index.js +2 -2
  157. package/dist/sdk/index.mjs +2 -2
  158. package/dist/security--oQObeJO.js +4 -0
  159. package/dist/security-wBOg0TA8.js +73 -0
  160. package/dist/server-Brl_HQUB.js +1255 -0
  161. package/dist/server-CbTTpB5m.js +1255 -0
  162. package/dist/server-DP_bPzvI.js +4 -0
  163. package/dist/server-DhfipkwN.js +4 -0
  164. package/dist/session-store-B09r5HgB.js +5 -0
  165. package/dist/session-store-DCTQIVur.js +113 -0
  166. package/dist/sessions-tools-BdlN6Pb6.js +95 -0
  167. package/dist/sessions-tools-JVLDKSJ_.js +5 -0
  168. package/dist/skill-loader-B5oeliGu.js +7 -0
  169. package/dist/skill-loader-Wf3brNOj.js +160 -0
  170. package/dist/skill-runtime-BGlvly2s.js +102 -0
  171. package/dist/skill-runtime-BXWd-Ktf.js +102 -0
  172. package/dist/skill-runtime-DhL2T76p.js +5 -0
  173. package/dist/skill-runtime-jgklm02e.js +5 -0
  174. package/dist/src-BbPa6Q8p.js +63 -0
  175. package/dist/src-BeXtfkK2.js +458 -0
  176. package/dist/src-Bhybpk1J.js +63 -0
  177. package/dist/src-CGQjRI4N.js +20 -0
  178. package/dist/src-DMJ4-uqk.js +458 -0
  179. package/dist/sub-agent-tools-CmE345s_.js +39 -0
  180. package/dist/sub-agent-tools-DHY-4WWM.js +39 -0
  181. package/dist/theme-D0smfC_l.js +8 -0
  182. package/dist/theme-DajRRZbA.js +180 -0
  183. package/dist/tool-policy-DZvF8xlQ.js +189 -0
  184. package/dist/tool-policy-DgNqFWYn.js +189 -0
  185. package/dist/tts-elevenlabs-C06nUxMK.js +61 -0
  186. package/dist/tts-elevenlabs-JeFaGNJU.js +61 -0
  187. package/dist/update-check-BVEqHhFY.js +83 -0
  188. package/dist/update-check-w4XuxVl7.js +81 -0
  189. package/dist/vision-JOtOS1Br.js +121 -0
  190. package/dist/vision-fky3elEo.js +121 -0
  191. package/dist/vision-tools-C8B3776g.js +5 -0
  192. package/dist/vision-tools-CB28ZCO_.js +5 -0
  193. package/dist/vision-tools-dwn9p4el.js +51 -0
  194. package/dist/vision-tools-vPPwQ-0N.js +51 -0
  195. package/dist/voice-transcription-B6RtplmN.js +138 -0
  196. package/dist/voice-transcription-DBo5hXmu.js +138 -0
  197. package/dist/website-watch-tools-B-jRAeTe.js +139 -0
  198. package/dist/website-watch-tools-BC9xAL67.js +5 -0
  199. package/package.json +1 -1
@@ -0,0 +1,2692 @@
1
+ const require_chunk = require('./chunk-jS-bbMI5.js');
2
+ const fs_extra = require_chunk.__toESM(require("fs-extra"));
3
+ const path = require_chunk.__toESM(require("path"));
4
+ const os = require_chunk.__toESM(require("os"));
5
+ const http = require_chunk.__toESM(require("http"));
6
+ const https = require_chunk.__toESM(require("https"));
7
+
8
+ //#region packages/core/src/agent/inference.ts
9
+ function streamRequest(hostname, reqPath, headers, body, onChunk, onDone, onError, opts) {
10
+ const payload = JSON.stringify(body);
11
+ const useHttps = opts?.useHttps ?? (hostname !== "localhost" && !hostname.startsWith("127."));
12
+ const port = opts?.port ?? (useHttps ? 443 : 11434);
13
+ const mod = useHttps ? https.default : http.default;
14
+ const req = mod.request({
15
+ hostname,
16
+ port,
17
+ path: reqPath,
18
+ method: "POST",
19
+ headers: {
20
+ ...headers,
21
+ "Content-Type": "application/json",
22
+ "Content-Length": Buffer.byteLength(payload)
23
+ }
24
+ }, (res) => {
25
+ let buf = "";
26
+ res.on("data", (chunk) => {
27
+ buf += chunk.toString();
28
+ const lines = buf.split("\n");
29
+ buf = lines.pop() || "";
30
+ for (const line of lines) if (line.trim()) onChunk(line);
31
+ });
32
+ res.on("end", () => {
33
+ if (buf.trim()) onChunk(buf);
34
+ onDone();
35
+ });
36
+ res.on("error", onError);
37
+ });
38
+ req.on("error", onError);
39
+ req.write(payload);
40
+ req.end();
41
+ }
42
+ function emitMarkdownBlocks(text, onBlock) {
43
+ const codeRe = /```(\w*)\n([\s\S]*?)```/g;
44
+ let last = 0;
45
+ let m;
46
+ while ((m = codeRe.exec(text)) !== null) {
47
+ if (m.index > last) onBlock("markdown", text.slice(last, m.index));
48
+ onBlock("code", m[2], m[1] || void 0);
49
+ last = m.index + m[0].length;
50
+ }
51
+ if (last < text.length) onBlock("markdown", text.slice(last));
52
+ }
53
+ function parseSSEChunk(line) {
54
+ if (!line.startsWith("data: ")) return null;
55
+ const data = line.slice(6).trim();
56
+ if (data === "[DONE]") return null;
57
+ try {
58
+ return JSON.parse(data);
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+ function getBuiltinTools() {
64
+ return [
65
+ {
66
+ name: "get_current_time",
67
+ description: "Get the current date and time",
68
+ input_schema: {
69
+ type: "object",
70
+ properties: { timezone: {
71
+ type: "string",
72
+ description: "Timezone name, e.g. Europe/Athens"
73
+ } }
74
+ },
75
+ handler: async (input) => {
76
+ const tz = input.timezone || "UTC";
77
+ try {
78
+ return (/* @__PURE__ */ new Date()).toLocaleString("en-US", {
79
+ timeZone: tz,
80
+ dateStyle: "full",
81
+ timeStyle: "long"
82
+ });
83
+ } catch {
84
+ return (/* @__PURE__ */ new Date()).toISOString();
85
+ }
86
+ }
87
+ },
88
+ {
89
+ name: "read_memory",
90
+ description: "Read content from workspace memory files in ~/.hyperclaw/. Use MEMORY.md, AGENTS.md, SOUL.md, USER.md, or any custom .md (e.g. EPIXEIRISI.md).",
91
+ input_schema: {
92
+ type: "object",
93
+ properties: { file: {
94
+ type: "string",
95
+ description: "Filename, e.g. MEMORY.md, AGENTS.md, SOUL.md, EPIXEIRISI.md. Must end in .md and stay in workspace."
96
+ } },
97
+ required: ["file"]
98
+ },
99
+ handler: async (input) => {
100
+ const name = String(input.file || "").trim();
101
+ if (!name.endsWith(".md") || /[\\/]/.test(name)) return `Invalid filename: must be a .md file in workspace (e.g. MEMORY.md, EPIXEIRISI.md).`;
102
+ const fpath = path.default.join(HC_DIR, path.default.basename(name));
103
+ if (path.default.resolve(fpath).indexOf(path.default.resolve(HC_DIR)) !== 0) return `Invalid path.`;
104
+ if (await fs_extra.default.pathExists(fpath)) return fs_extra.default.readFile(fpath, "utf8");
105
+ return `${name} not found. Create it with create_memory_file.`;
106
+ }
107
+ },
108
+ {
109
+ name: "write_memory",
110
+ description: "Append a fact or note to MEMORY.md. Also adds to knowledge graph for cross-session context.",
111
+ input_schema: {
112
+ type: "object",
113
+ properties: {
114
+ content: {
115
+ type: "string",
116
+ description: "Text to append to MEMORY.md"
117
+ },
118
+ tags: {
119
+ type: "string",
120
+ description: "Optional comma-separated tags for knowledge graph"
121
+ }
122
+ },
123
+ required: ["content"]
124
+ },
125
+ handler: async (input) => {
126
+ const content = input.content;
127
+ const fpath = path.default.join(HC_DIR, "MEMORY.md");
128
+ const entry = `\n- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}: ${content}\n`;
129
+ await fs_extra.default.appendFile(fpath, entry);
130
+ const { addFact } = await Promise.resolve().then(() => require("./knowledge-graph-BrYpSgxW.js"));
131
+ const tags = input.tags?.split(",").map((t) => t.trim()).filter(Boolean);
132
+ await addFact(content, tags).catch(() => {});
133
+ return `Appended to MEMORY.md + knowledge graph: ${content.slice(0, 80)}${content.length > 80 ? "..." : ""}`;
134
+ }
135
+ },
136
+ {
137
+ name: "create_memory_file",
138
+ description: "Create or append to a custom .md file in ~/.hyperclaw/. Use when the user asks for a dedicated file (e.g. EPIXEIRISI.md for business context). File is loaded into context on every session.",
139
+ input_schema: {
140
+ type: "object",
141
+ properties: {
142
+ filename: {
143
+ type: "string",
144
+ description: "Filename must end in .md, e.g. EPIXEIRISI.md, PROJECTS.md"
145
+ },
146
+ content: {
147
+ type: "string",
148
+ description: "Initial content or content to append"
149
+ },
150
+ append: {
151
+ type: "string",
152
+ description: "\"true\" to append, omit to create/overwrite"
153
+ }
154
+ },
155
+ required: ["filename", "content"]
156
+ },
157
+ handler: async (input) => {
158
+ const name = String(input.filename || "").trim();
159
+ if (!/^[a-zA-Z0-9_-]+\.md$/.test(name)) return "Invalid filename: use only letters, numbers, underscore, hyphen, and .md extension (e.g. EPIXEIRISI.md).";
160
+ const content = String(input.content || "").trim();
161
+ if (!content) return "Content is required.";
162
+ await fs_extra.default.ensureDir(HC_DIR);
163
+ const fpath = path.default.join(HC_DIR, name);
164
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
165
+ if (input.append === "true" && await fs_extra.default.pathExists(fpath)) {
166
+ const entry = `\n- ${today}: ${content}\n`;
167
+ await fs_extra.default.appendFile(fpath, entry);
168
+ return `Appended to ${name}: ${content.slice(0, 80)}${content.length > 80 ? "..." : ""}`;
169
+ }
170
+ const header = `# ${name.replace(".md", "")}\n> Created: ${today}\n\n`;
171
+ await fs_extra.default.writeFile(fpath, header + content + "\n", "utf8");
172
+ return `Created ${name}. It will be loaded into context on every session.`;
173
+ }
174
+ },
175
+ {
176
+ name: "memory_graph_add",
177
+ description: "Add a structured fact to the knowledge graph. Use for preferences, projects, key facts that should persist across sessions.",
178
+ input_schema: {
179
+ type: "object",
180
+ properties: {
181
+ type: {
182
+ type: "string",
183
+ description: "fact | preference | project",
184
+ enum: [
185
+ "fact",
186
+ "preference",
187
+ "project"
188
+ ]
189
+ },
190
+ content: {
191
+ type: "string",
192
+ description: "The fact, preference (e.g. \"coffee: strong\"), or project name"
193
+ }
194
+ },
195
+ required: ["type", "content"]
196
+ },
197
+ handler: async (input) => {
198
+ const { addFact, addPreference, addProject } = await Promise.resolve().then(() => require("./knowledge-graph-BrYpSgxW.js"));
199
+ const t = input.type.toLowerCase();
200
+ const c = input.content.trim();
201
+ if (t === "preference") {
202
+ const [topic, value] = c.includes(":") ? c.split(":").map((s) => s.trim()) : [c, ""];
203
+ const id$1 = await addPreference(topic, value || "yes");
204
+ return `Added preference: ${topic}${value ? ` = ${value}` : ""}`;
205
+ }
206
+ if (t === "project") {
207
+ const id$1 = await addProject(c);
208
+ return `Added project: ${c}`;
209
+ }
210
+ const id = await addFact(c);
211
+ return `Added fact to knowledge graph: ${c.slice(0, 100)}`;
212
+ }
213
+ },
214
+ {
215
+ name: "memory_graph_query",
216
+ description: "Query the knowledge graph for relevant context. Returns facts, preferences, projects.",
217
+ input_schema: {
218
+ type: "object",
219
+ properties: {
220
+ types: {
221
+ type: "string",
222
+ description: "Optional: fact,preference,project"
223
+ },
224
+ tags: {
225
+ type: "string",
226
+ description: "Optional comma-separated tags to filter"
227
+ },
228
+ limit: {
229
+ type: "string",
230
+ description: "Max items (default 20)"
231
+ }
232
+ }
233
+ },
234
+ handler: async (input) => {
235
+ const { queryMemory } = await Promise.resolve().then(() => require("./knowledge-graph-BrYpSgxW.js"));
236
+ const types = input.types?.split(",").map((t) => t.trim()).filter(Boolean);
237
+ const tags = input.tags?.split(",").map((t) => t.trim()).filter(Boolean);
238
+ const limit = parseInt(input.limit || "20");
239
+ const result = await queryMemory({
240
+ types,
241
+ tags,
242
+ limit
243
+ });
244
+ return result || "No matching entries in knowledge graph.";
245
+ }
246
+ },
247
+ {
248
+ name: "node_command",
249
+ description: "Send a device command to a paired mobile node (iOS/Android Connect tab). Use when the user asks to take a photo, get location, read contacts, etc. on their phone. First call with no params to list connected nodes.",
250
+ input_schema: {
251
+ type: "object",
252
+ properties: {
253
+ nodeId: {
254
+ type: "string",
255
+ description: "Node ID from node_command list (e.g. \"iPhone-1\")"
256
+ },
257
+ command: {
258
+ type: "string",
259
+ description: "camera_capture | screen_record | location | contacts_list | calendar_events | photos_recent | sms_send | notify | motion",
260
+ enum: [
261
+ "camera_capture",
262
+ "screen_record",
263
+ "location",
264
+ "contacts_list",
265
+ "calendar_events",
266
+ "photos_recent",
267
+ "sms_send",
268
+ "notify",
269
+ "motion"
270
+ ]
271
+ },
272
+ params: {
273
+ type: "string",
274
+ description: "Optional JSON params, e.g. {\"message\":\"Hello\"} for notify"
275
+ }
276
+ }
277
+ },
278
+ handler: async (input) => {
279
+ const { NodeRegistry } = await Promise.resolve().then(() => require("./nodes-registry-DKRtsbNg.js"));
280
+ const nodes = NodeRegistry.getNodes();
281
+ if (!input.nodeId) {
282
+ if (nodes.length === 0) return "No mobile nodes connected. Pair the iOS/Android app via WebSocket to the gateway.";
283
+ return nodes.map((n) => `${n.nodeId} (${n.platform}): ${Object.keys(n.capabilities).filter((k) => Boolean(n.capabilities[k])).join(", ")}`).join("\n");
284
+ }
285
+ const node = NodeRegistry.getNode(String(input.nodeId ?? ""));
286
+ if (!node) return `Node ${input.nodeId} not found.`;
287
+ const cmd = input.command;
288
+ if (!cmd) return "command is required.";
289
+ const cmdId = `cmd-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
290
+ let params = {};
291
+ try {
292
+ if (input.params) params = JSON.parse(input.params);
293
+ } catch {}
294
+ const result = await node.send({
295
+ id: cmdId,
296
+ type: cmd,
297
+ params
298
+ });
299
+ if (!result.ok) return result.error || "Command failed";
300
+ return typeof result.data === "string" ? result.data : JSON.stringify(result.data);
301
+ }
302
+ },
303
+ {
304
+ name: "run_command",
305
+ description: "Run a safe shell command (read-only: ls, cat, echo, date, whoami, pwd)",
306
+ input_schema: {
307
+ type: "object",
308
+ properties: { command: {
309
+ type: "string",
310
+ description: "Shell command to run (read-only commands only)"
311
+ } },
312
+ required: ["command"]
313
+ },
314
+ handler: async (input) => {
315
+ const cmd = input.command.trim();
316
+ const safePattern = /^(ls|cat|echo|date|whoami|pwd|uname|hostname|df|free|uptime)(\s|$)/;
317
+ if (!safePattern.test(cmd)) return `Blocked: only read-only commands allowed. Got: ${cmd}`;
318
+ const { exec } = await import("child_process");
319
+ const { promisify } = await import("util");
320
+ const execAsync = promisify(exec);
321
+ const { stdout, stderr } = await execAsync(cmd, { timeout: 5e3 });
322
+ return (stdout + stderr).trim().slice(0, 2e3);
323
+ }
324
+ },
325
+ {
326
+ name: "add_reminder",
327
+ description: "Add a reminder. Use for \"remind me to X in Y\" requests.",
328
+ input_schema: {
329
+ type: "object",
330
+ properties: {
331
+ message: {
332
+ type: "string",
333
+ description: "Reminder text"
334
+ },
335
+ dueAt: {
336
+ type: "string",
337
+ description: "Optional: ISO 8601 or natural like \"in 2 hours\", \"tomorrow 9am\""
338
+ }
339
+ },
340
+ required: ["message"]
341
+ },
342
+ handler: async (input) => {
343
+ const { addReminder } = await Promise.resolve().then(() => require("./reminders-store-CzUY0zYx.js"));
344
+ const r = await addReminder(input.message, input.dueAt);
345
+ return `Reminder added (id: ${r.id}): ${r.message}${r.dueAt ? ` due ${r.dueAt}` : ""}`;
346
+ }
347
+ },
348
+ {
349
+ name: "list_reminders",
350
+ description: "List pending reminders.",
351
+ input_schema: {
352
+ type: "object",
353
+ properties: { includeCompleted: {
354
+ type: "string",
355
+ description: "\"true\" to include completed"
356
+ } },
357
+ required: []
358
+ },
359
+ handler: async (input) => {
360
+ const { listReminders } = await Promise.resolve().then(() => require("./reminders-store-CzUY0zYx.js"));
361
+ const items = await listReminders(input.includeCompleted === "true");
362
+ if (items.length === 0) return "No reminders.";
363
+ return items.map((r) => `[${r.id}] ${r.message}${r.dueAt ? ` (due ${r.dueAt})` : ""} ${r.completed ? "(done)" : ""}`).join("\n");
364
+ }
365
+ },
366
+ {
367
+ name: "complete_reminder",
368
+ description: "Mark a reminder as done.",
369
+ input_schema: {
370
+ type: "object",
371
+ properties: { id: {
372
+ type: "string",
373
+ description: "Reminder ID from list_reminders"
374
+ } },
375
+ required: ["id"]
376
+ },
377
+ handler: async (input) => {
378
+ const { completeReminder } = await Promise.resolve().then(() => require("./reminders-store-CzUY0zYx.js"));
379
+ const ok = await completeReminder(input.id);
380
+ return ok ? `Reminder ${input.id} completed.` : `Reminder ${input.id} not found.`;
381
+ }
382
+ },
383
+ {
384
+ name: "canvas_add",
385
+ description: "Add a component to the HyperClaw canvas (AI-driven UI). Use for charts, tables, forms, markdown, image, or script (in-browser JS execution). For type \"script\", pass data as JSON with key \"script\" containing the JavaScript to run in a sandboxed iframe.",
386
+ input_schema: {
387
+ type: "object",
388
+ properties: {
389
+ type: {
390
+ type: "string",
391
+ description: "Component type",
392
+ enum: [
393
+ "chart",
394
+ "table",
395
+ "form",
396
+ "markdown",
397
+ "image",
398
+ "custom",
399
+ "script"
400
+ ]
401
+ },
402
+ title: {
403
+ type: "string",
404
+ description: "Component title"
405
+ },
406
+ data: {
407
+ type: "string",
408
+ description: "Optional JSON. For type \"script\" use {\"script\": \"document.body.innerHTML = \\\"Hello\\\";\"}"
409
+ }
410
+ },
411
+ required: ["type", "title"]
412
+ },
413
+ handler: async (input) => {
414
+ const { CanvasRenderer } = await Promise.resolve().then(() => require("./renderer-ANNfXsHn.js"));
415
+ const renderer = new CanvasRenderer();
416
+ let data;
417
+ try {
418
+ data = input.data ? JSON.parse(input.data) : void 0;
419
+ } catch {}
420
+ const c = await renderer.addComponent(input.type, input.title, data);
421
+ return `Canvas component added: ${c.type}/${c.title} (id: ${c.id}). View at http://localhost:18789/canvas`;
422
+ }
423
+ },
424
+ {
425
+ name: "http_get",
426
+ description: "Make an HTTP GET request to a URL and return the response",
427
+ input_schema: {
428
+ type: "object",
429
+ properties: {
430
+ url: {
431
+ type: "string",
432
+ description: "URL to fetch"
433
+ },
434
+ headers: {
435
+ type: "string",
436
+ description: "Optional JSON headers object"
437
+ }
438
+ },
439
+ required: ["url"]
440
+ },
441
+ handler: async (input) => {
442
+ const url = input.url;
443
+ if (!url.startsWith("http://") && !url.startsWith("https://")) return "Error: URL must start with http:// or https://";
444
+ return new Promise((resolve) => {
445
+ const mod = url.startsWith("https://") ? https.default : require("http");
446
+ let extra = {};
447
+ try {
448
+ if (input.headers) extra = JSON.parse(input.headers);
449
+ } catch {}
450
+ const req = mod.get(url, {
451
+ headers: extra,
452
+ timeout: 1e4
453
+ }, (res) => {
454
+ let data = "";
455
+ res.on("data", (c) => data += c);
456
+ res.on("end", () => resolve(data.slice(0, 3e3)));
457
+ });
458
+ req.on("error", (e) => resolve(`Error: ${e.message}`));
459
+ });
460
+ }
461
+ },
462
+ {
463
+ name: "moltbook_feed",
464
+ description: "Get the latest posts from Moltbook (social feed for agents). Set MOLTBOOK_API_URL to enable.",
465
+ input_schema: {
466
+ type: "object",
467
+ properties: { limit: {
468
+ type: "string",
469
+ description: "Max posts (default 10)"
470
+ } },
471
+ required: []
472
+ },
473
+ handler: async (input) => {
474
+ const { getFeed } = await Promise.resolve().then(() => require("./moltbook-B-40gQOL.js"));
475
+ const posts = await getFeed(parseInt(input.limit || "10", 10));
476
+ if (posts.length === 0) return "Moltbook feed empty or MOLTBOOK_API_URL not set.";
477
+ return posts.map((p) => `[${p.agentId}] ${p.content.slice(0, 200)}`).join("\n---\n");
478
+ }
479
+ },
480
+ {
481
+ name: "moltbook_post",
482
+ description: "Publish a post to Moltbook. Requires MOLTBOOK_API_URL and agent auth.",
483
+ input_schema: {
484
+ type: "object",
485
+ properties: { content: {
486
+ type: "string",
487
+ description: "Post content"
488
+ } },
489
+ required: ["content"]
490
+ },
491
+ handler: async (input) => {
492
+ const { publishPost } = await Promise.resolve().then(() => require("./moltbook-B-40gQOL.js"));
493
+ const post = await publishPost(input.content);
494
+ return post ? `Published: ${post.id}` : "Moltbook not configured or publish failed. Set MOLTBOOK_API_URL.";
495
+ }
496
+ },
497
+ {
498
+ name: "claw_tasks_list",
499
+ description: "List open bounties from ClawTasks (bounty marketplace). Set CLAW_TASKS_API_URL to enable.",
500
+ input_schema: {
501
+ type: "object",
502
+ properties: { limit: {
503
+ type: "string",
504
+ description: "Max bounties (default 10)"
505
+ } },
506
+ required: []
507
+ },
508
+ handler: async (input) => {
509
+ const { listBounties } = await Promise.resolve().then(() => require("./claw-tasks-B-8RRMdq.js"));
510
+ const bounties = await listBounties(parseInt(input.limit || "10", 10), "open");
511
+ if (bounties.length === 0) return "No open bounties or CLAW_TASKS_API_URL not set.";
512
+ return bounties.map((b) => `[${b.id}] ${b.title}${b.reward ? ` — ${b.reward}` : ""}`).join("\n");
513
+ }
514
+ },
515
+ {
516
+ name: "claw_tasks_claim",
517
+ description: "Claim a ClawTasks bounty by ID. Requires CLAW_TASKS_API_URL and agent token.",
518
+ input_schema: {
519
+ type: "object",
520
+ properties: { bountyId: {
521
+ type: "string",
522
+ description: "Bounty ID from claw_tasks_list"
523
+ } },
524
+ required: ["bountyId"]
525
+ },
526
+ handler: async (input) => {
527
+ const { claimBounty } = await Promise.resolve().then(() => require("./claw-tasks-B-8RRMdq.js"));
528
+ const b = await claimBounty(input.bountyId);
529
+ return b ? `Claimed: ${b.title}` : "Claim failed or ClawTasks not configured.";
530
+ }
531
+ },
532
+ {
533
+ name: "weather",
534
+ description: "Get current weather and forecast for a location. Uses Open-Meteo (free, no key needed). Also supports Weather API key via WEATHER_API_KEY env.",
535
+ input_schema: {
536
+ type: "object",
537
+ properties: {
538
+ location: {
539
+ type: "string",
540
+ description: "City name, e.g. \"Athens\" or \"New York\""
541
+ },
542
+ days: {
543
+ type: "string",
544
+ description: "Forecast days: 1 (default) to 7"
545
+ }
546
+ },
547
+ required: ["location"]
548
+ },
549
+ handler: async (input) => {
550
+ const location = input.location;
551
+ const days = Math.min(7, Math.max(1, parseInt(input.days || "1")));
552
+ const geoUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(location)}&count=1&language=en&format=json`;
553
+ return new Promise((resolve) => {
554
+ const req = https.default.get(geoUrl, { timeout: 6e3 }, (res) => {
555
+ let d = "";
556
+ res.on("data", (c) => d += c);
557
+ res.on("end", async () => {
558
+ try {
559
+ const geo = JSON.parse(d);
560
+ const loc = geo.results?.[0];
561
+ if (!loc) {
562
+ resolve(`Location not found: ${location}`);
563
+ return;
564
+ }
565
+ const wUrl = `https://api.open-meteo.com/v1/forecast?latitude=${loc.latitude}&longitude=${loc.longitude}&daily=temperature_2m_max,temperature_2m_min,weathercode,precipitation_sum&current_weather=true&forecast_days=${days}&timezone=auto`;
566
+ https.default.get(wUrl, { timeout: 6e3 }, (wr) => {
567
+ let wd = "";
568
+ wr.on("data", (c) => wd += c);
569
+ wr.on("end", () => {
570
+ try {
571
+ const w = JSON.parse(wd);
572
+ const cur = w.current_weather;
573
+ const WMO = {
574
+ 0: "Clear",
575
+ 1: "Mainly clear",
576
+ 2: "Partly cloudy",
577
+ 3: "Overcast",
578
+ 51: "Drizzle",
579
+ 61: "Rain",
580
+ 71: "Snow",
581
+ 80: "Showers",
582
+ 95: "Thunderstorm"
583
+ };
584
+ const cond = WMO[cur?.weathercode] || `code ${cur?.weathercode}`;
585
+ let out = `📍 ${loc.name}, ${loc.country_code?.toUpperCase()}\n🌡 ${cur?.temperature}°C — ${cond} | Wind: ${cur?.windspeed} km/h\n`;
586
+ if (days > 1 && w.daily) {
587
+ out += "\n📅 Forecast:\n";
588
+ for (let i = 0; i < Math.min(days, w.daily.time.length); i++) {
589
+ const dCond = WMO[w.daily.weathercode?.[i]] || "";
590
+ out += ` ${w.daily.time[i]}: ${w.daily.temperature_2m_min?.[i]}°–${w.daily.temperature_2m_max?.[i]}° ${dCond} 💧${w.daily.precipitation_sum?.[i]}mm\n`;
591
+ }
592
+ }
593
+ resolve(out.trim());
594
+ } catch {
595
+ resolve("Weather parse error");
596
+ }
597
+ });
598
+ }).on("error", () => resolve("Weather API unreachable"));
599
+ } catch {
600
+ resolve("Geocoding parse error");
601
+ }
602
+ });
603
+ });
604
+ req.on("error", () => resolve("Geocoding API unreachable"));
605
+ });
606
+ }
607
+ },
608
+ {
609
+ name: "image_generate",
610
+ description: "Generate an image from a text prompt using DALL-E (requires OPENAI_API_KEY) or Stability AI (requires STABILITY_API_KEY). Returns a URL or base64.",
611
+ input_schema: {
612
+ type: "object",
613
+ properties: {
614
+ prompt: {
615
+ type: "string",
616
+ description: "Image description"
617
+ },
618
+ size: {
619
+ type: "string",
620
+ description: "256x256 | 512x512 | 1024x1024 (default)",
621
+ enum: [
622
+ "256x256",
623
+ "512x512",
624
+ "1024x1024"
625
+ ]
626
+ },
627
+ provider: {
628
+ type: "string",
629
+ description: "dalle (default) | stability",
630
+ enum: ["dalle", "stability"]
631
+ }
632
+ },
633
+ required: ["prompt"]
634
+ },
635
+ handler: async (input) => {
636
+ const prompt = input.prompt;
637
+ const size = input.size || "1024x1024";
638
+ const prov = input.provider || "dalle";
639
+ const openaiKey = process.env.OPENAI_API_KEY || (await fs_extra.default.readJson(path.default.join(HC_DIR, "hyperclaw.json")).catch(() => ({}))).provider?.apiKey;
640
+ if (prov === "dalle") {
641
+ if (!openaiKey) return "OPENAI_API_KEY not set. Configure an OpenAI API key.";
642
+ return new Promise((resolve) => {
643
+ const body = JSON.stringify({
644
+ model: "dall-e-3",
645
+ prompt,
646
+ n: 1,
647
+ size
648
+ });
649
+ const req = https.default.request({
650
+ hostname: "api.openai.com",
651
+ path: "/v1/images/generations",
652
+ method: "POST",
653
+ headers: {
654
+ "Authorization": `Bearer ${openaiKey}`,
655
+ "Content-Type": "application/json",
656
+ "Content-Length": Buffer.byteLength(body)
657
+ }
658
+ }, (res) => {
659
+ let d = "";
660
+ res.on("data", (c) => d += c);
661
+ res.on("end", () => {
662
+ try {
663
+ const j = JSON.parse(d);
664
+ const url = j.data?.[0]?.url;
665
+ resolve(url ? `Generated image: ${url}` : `Error: ${j.error?.message || d.slice(0, 200)}`);
666
+ } catch {
667
+ resolve("Image generation parse error");
668
+ }
669
+ });
670
+ });
671
+ req.on("error", (e) => resolve(`Error: ${e.message}`));
672
+ req.write(body);
673
+ req.end();
674
+ });
675
+ }
676
+ const stabilityKey = process.env.STABILITY_API_KEY;
677
+ if (!stabilityKey) return "STABILITY_API_KEY not set.";
678
+ return new Promise((resolve) => {
679
+ const body = JSON.stringify({
680
+ text_prompts: [{ text: prompt }],
681
+ cfg_scale: 7,
682
+ height: 512,
683
+ width: 512,
684
+ samples: 1,
685
+ steps: 30
686
+ });
687
+ const req = https.default.request({
688
+ hostname: "api.stability.ai",
689
+ path: "/v1/generation/stable-diffusion-v1-6/text-to-image",
690
+ method: "POST",
691
+ headers: {
692
+ Authorization: `Bearer ${stabilityKey}`,
693
+ "Content-Type": "application/json",
694
+ "Content-Length": Buffer.byteLength(body)
695
+ }
696
+ }, (res) => {
697
+ let d = "";
698
+ res.on("data", (c) => d += c);
699
+ res.on("end", () => {
700
+ try {
701
+ const j = JSON.parse(d);
702
+ const b64 = j.artifacts?.[0]?.base64;
703
+ resolve(b64 ? `data:image/png;base64,${b64.slice(0, 80)}... (base64 image generated)` : `Error: ${d.slice(0, 200)}`);
704
+ } catch {
705
+ resolve("Stability parse error");
706
+ }
707
+ });
708
+ });
709
+ req.on("error", (e) => resolve(`Error: ${e.message}`));
710
+ req.write(body);
711
+ req.end();
712
+ });
713
+ }
714
+ },
715
+ {
716
+ name: "gif_search",
717
+ description: "Search for a GIF on Giphy or Tenor. Returns a URL. Requires GIPHY_API_KEY or TENOR_API_KEY env var.",
718
+ input_schema: {
719
+ type: "object",
720
+ properties: {
721
+ query: {
722
+ type: "string",
723
+ description: "Search term, e.g. \"excited cat\""
724
+ },
725
+ limit: {
726
+ type: "string",
727
+ description: "Max results (default 1)"
728
+ }
729
+ },
730
+ required: ["query"]
731
+ },
732
+ handler: async (input) => {
733
+ const query = encodeURIComponent(input.query);
734
+ const limit = parseInt(input.limit || "1");
735
+ const giphyKey = process.env.GIPHY_API_KEY;
736
+ const tenorKey = process.env.TENOR_API_KEY;
737
+ if (giphyKey) return new Promise((resolve) => {
738
+ https.default.get(`https://api.giphy.com/v1/gifs/search?api_key=${giphyKey}&q=${query}&limit=${limit}&rating=g`, { timeout: 5e3 }, (res) => {
739
+ let d = "";
740
+ res.on("data", (c) => d += c);
741
+ res.on("end", () => {
742
+ try {
743
+ const j = JSON.parse(d);
744
+ const results = j.data?.map((g) => g.images?.original?.url || g.url).filter(Boolean).slice(0, limit);
745
+ resolve(results?.length ? results.join("\n") : "No GIFs found");
746
+ } catch {
747
+ resolve("Giphy parse error");
748
+ }
749
+ });
750
+ }).on("error", () => resolve("Giphy unreachable"));
751
+ });
752
+ if (tenorKey) return new Promise((resolve) => {
753
+ https.default.get(`https://tenor.googleapis.com/v2/search?q=${query}&key=${tenorKey}&limit=${limit}&media_filter=gif`, { timeout: 5e3 }, (res) => {
754
+ let d = "";
755
+ res.on("data", (c) => d += c);
756
+ res.on("end", () => {
757
+ try {
758
+ const j = JSON.parse(d);
759
+ const results = j.results?.map((g) => g.media_formats?.gif?.url).filter(Boolean).slice(0, limit);
760
+ resolve(results?.length ? results.join("\n") : "No GIFs found");
761
+ } catch {
762
+ resolve("Tenor parse error");
763
+ }
764
+ });
765
+ }).on("error", () => resolve("Tenor unreachable"));
766
+ });
767
+ return "Set GIPHY_API_KEY or TENOR_API_KEY to enable GIF search.";
768
+ }
769
+ },
770
+ {
771
+ name: "spotify",
772
+ description: "Control Spotify playback or search for tracks/playlists. Requires SPOTIFY_CLIENT_ID + SPOTIFY_CLIENT_SECRET + SPOTIFY_REFRESH_TOKEN.",
773
+ input_schema: {
774
+ type: "object",
775
+ properties: {
776
+ action: {
777
+ type: "string",
778
+ description: "play | pause | next | previous | search | current",
779
+ enum: [
780
+ "play",
781
+ "pause",
782
+ "next",
783
+ "previous",
784
+ "search",
785
+ "current"
786
+ ]
787
+ },
788
+ query: {
789
+ type: "string",
790
+ description: "Search query for action=search (e.g. \"Daft Punk\")"
791
+ },
792
+ uri: {
793
+ type: "string",
794
+ description: "Spotify URI to play (e.g. spotify:track:XXX)"
795
+ }
796
+ },
797
+ required: ["action"]
798
+ },
799
+ handler: async (input) => {
800
+ const action = input.action;
801
+ const clientId = process.env.SPOTIFY_CLIENT_ID;
802
+ const clientSecret = process.env.SPOTIFY_CLIENT_SECRET;
803
+ const refreshToken = process.env.SPOTIFY_REFRESH_TOKEN;
804
+ if (!clientId || !clientSecret || !refreshToken) return "Spotify not configured. Set SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET, and SPOTIFY_REFRESH_TOKEN.\nGet credentials at developer.spotify.com → Dashboard.";
805
+ const getToken = () => new Promise((resolve, reject) => {
806
+ const body = `grant_type=refresh_token&refresh_token=${encodeURIComponent(refreshToken)}`;
807
+ const auth = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
808
+ const req = https.default.request({
809
+ hostname: "accounts.spotify.com",
810
+ path: "/api/token",
811
+ method: "POST",
812
+ headers: {
813
+ "Authorization": `Basic ${auth}`,
814
+ "Content-Type": "application/x-www-form-urlencoded",
815
+ "Content-Length": Buffer.byteLength(body)
816
+ }
817
+ }, (res) => {
818
+ let d = "";
819
+ res.on("data", (c) => d += c);
820
+ res.on("end", () => {
821
+ try {
822
+ resolve(JSON.parse(d).access_token);
823
+ } catch {
824
+ reject(new Error("Token parse error"));
825
+ }
826
+ });
827
+ });
828
+ req.on("error", reject);
829
+ req.write(body);
830
+ req.end();
831
+ });
832
+ const spotifyApi = (method, path2, token, body) => new Promise((resolve) => {
833
+ const payload = body ? JSON.stringify(body) : void 0;
834
+ const req = https.default.request({
835
+ hostname: "api.spotify.com",
836
+ path: path2,
837
+ method,
838
+ headers: {
839
+ "Authorization": `Bearer ${token}`,
840
+ ...payload ? {
841
+ "Content-Type": "application/json",
842
+ "Content-Length": Buffer.byteLength(payload)
843
+ } : {}
844
+ }
845
+ }, (res) => {
846
+ let d = "";
847
+ res.on("data", (c) => d += c);
848
+ res.on("end", () => {
849
+ try {
850
+ resolve(d ? JSON.parse(d) : { ok: true });
851
+ } catch {
852
+ resolve({ raw: d });
853
+ }
854
+ });
855
+ });
856
+ req.on("error", (e) => resolve({ error: e.message }));
857
+ if (payload) req.write(payload);
858
+ req.end();
859
+ });
860
+ try {
861
+ const token = await getToken();
862
+ if (action === "current") {
863
+ const r = await spotifyApi("GET", "/v1/me/player/currently-playing", token);
864
+ if (!r?.item) return "Nothing playing.";
865
+ return `🎵 ${r.item.name} — ${r.item.artists?.map((a) => a.name).join(", ")} (${r.is_playing ? "▶ Playing" : "⏸ Paused"})`;
866
+ }
867
+ if (action === "search") {
868
+ if (!input.query) return "query is required for search";
869
+ const r = await spotifyApi("GET", `/v1/search?q=${encodeURIComponent(input.query)}&type=track&limit=5`, token);
870
+ const tracks = r.tracks?.items?.map((t) => `${t.name} — ${t.artists?.[0]?.name} (${t.uri})`).join("\n");
871
+ return tracks || "No results found.";
872
+ }
873
+ if (action === "play" && input.uri) {
874
+ await spotifyApi("PUT", "/v1/me/player/play", token, { uris: [input.uri] });
875
+ return `▶ Playing ${input.uri}`;
876
+ }
877
+ const ENDPOINT = {
878
+ play: ["PUT", "/v1/me/player/play"],
879
+ pause: ["PUT", "/v1/me/player/pause"],
880
+ next: ["POST", "/v1/me/player/next"],
881
+ previous: ["POST", "/v1/me/player/previous"]
882
+ };
883
+ const [method, ep] = ENDPOINT[action] || [];
884
+ if (!ep) return `Unknown action: ${action}`;
885
+ await spotifyApi(method, ep, token);
886
+ return `✅ Spotify: ${action}`;
887
+ } catch (e) {
888
+ return `Spotify error: ${e.message}`;
889
+ }
890
+ }
891
+ },
892
+ {
893
+ name: "home_assistant",
894
+ description: "Control Home Assistant devices (lights, switches, thermostats, etc.). Requires HA_URL and HA_TOKEN env vars.",
895
+ input_schema: {
896
+ type: "object",
897
+ properties: {
898
+ action: {
899
+ type: "string",
900
+ description: "list_entities | get_state | turn_on | turn_off | toggle | call_service",
901
+ enum: [
902
+ "list_entities",
903
+ "get_state",
904
+ "turn_on",
905
+ "turn_off",
906
+ "toggle",
907
+ "call_service"
908
+ ]
909
+ },
910
+ entity_id: {
911
+ type: "string",
912
+ description: "Entity ID, e.g. \"light.living_room\" or \"switch.kitchen\""
913
+ },
914
+ domain: {
915
+ type: "string",
916
+ description: "For list_entities: filter by domain (e.g. \"light\", \"switch\", \"climate\")"
917
+ },
918
+ service_data: {
919
+ type: "string",
920
+ description: "For call_service: JSON with domain, service, and entity_id"
921
+ }
922
+ },
923
+ required: ["action"]
924
+ },
925
+ handler: async (input) => {
926
+ const haUrl = (process.env.HA_URL || "").replace(/\/$/, "");
927
+ const haToken = process.env.HA_TOKEN;
928
+ if (!haUrl || !haToken) return "Home Assistant not configured. Set HA_URL (e.g. http://homeassistant.local:8123) and HA_TOKEN (long-lived access token from your HA profile).";
929
+ const haGet = (path2) => new Promise((resolve) => {
930
+ const u = new URL(haUrl + path2);
931
+ const mod = u.protocol === "https:" ? https.default : require("http");
932
+ mod.get(u.toString(), {
933
+ headers: {
934
+ Authorization: `Bearer ${haToken}`,
935
+ "Content-Type": "application/json"
936
+ },
937
+ timeout: 8e3
938
+ }, (res) => {
939
+ let d = "";
940
+ res.on("data", (c) => d += c);
941
+ res.on("end", () => {
942
+ try {
943
+ resolve(JSON.parse(d));
944
+ } catch {
945
+ resolve({ raw: d });
946
+ }
947
+ });
948
+ }).on("error", (e) => resolve({ error: e.message }));
949
+ });
950
+ const haPost = (path2, body) => new Promise((resolve) => {
951
+ const u = new URL(haUrl + path2);
952
+ const payload = JSON.stringify(body);
953
+ const mod = u.protocol === "https:" ? https.default : require("http");
954
+ const req = mod.request(u.toString(), {
955
+ method: "POST",
956
+ headers: {
957
+ Authorization: `Bearer ${haToken}`,
958
+ "Content-Type": "application/json",
959
+ "Content-Length": Buffer.byteLength(payload)
960
+ }
961
+ }, (res) => {
962
+ let d = "";
963
+ res.on("data", (c) => d += c);
964
+ res.on("end", () => {
965
+ try {
966
+ resolve(JSON.parse(d));
967
+ } catch {
968
+ resolve({ raw: d });
969
+ }
970
+ });
971
+ });
972
+ req.on("error", (e) => resolve({ error: e.message }));
973
+ req.write(payload);
974
+ req.end();
975
+ });
976
+ const action = input.action;
977
+ if (action === "list_entities") {
978
+ const states = await haGet("/api/states");
979
+ if (!Array.isArray(states)) return `HA error: ${JSON.stringify(states)}`;
980
+ const domain = input.domain || "";
981
+ const filtered = domain ? states.filter((s) => s.entity_id?.startsWith(domain + ".")) : states;
982
+ return filtered.slice(0, 30).map((s) => `${s.entity_id}: ${s.state}`).join("\n") + (filtered.length > 30 ? `\n...and ${filtered.length - 30} more` : "");
983
+ }
984
+ if (action === "get_state") {
985
+ if (!input.entity_id) return "entity_id required";
986
+ const state = await haGet(`/api/states/${input.entity_id}`);
987
+ return state.error ? `Error: ${state.error}` : `${state.entity_id}: ${state.state} — ${JSON.stringify(state.attributes).slice(0, 200)}`;
988
+ }
989
+ if (action === "call_service") {
990
+ if (!input.service_data) return "service_data required (JSON with domain, service, entity_id)";
991
+ let sd;
992
+ try {
993
+ sd = JSON.parse(input.service_data);
994
+ } catch {
995
+ return "service_data must be valid JSON";
996
+ }
997
+ const r$1 = await haPost(`/api/services/${sd.domain}/${sd.service}`, {
998
+ entity_id: sd.entity_id,
999
+ ...sd.data
1000
+ });
1001
+ return `Service called: ${sd.domain}.${sd.service}` + (r$1.error ? ` — Error: ${r$1.error}` : "");
1002
+ }
1003
+ const DOMAIN_MAP = {
1004
+ turn_on: "turn_on",
1005
+ turn_off: "turn_off",
1006
+ toggle: "toggle"
1007
+ };
1008
+ const svc = DOMAIN_MAP[action];
1009
+ if (!svc) return `Unknown action: ${action}`;
1010
+ if (!input.entity_id) return "entity_id required";
1011
+ const entityDomain = input.entity_id.split(".")[0];
1012
+ const r = await haPost(`/api/services/${entityDomain}/${svc}`, { entity_id: input.entity_id });
1013
+ return r.error ? `Error: ${r.error}` : `✅ ${action}: ${input.entity_id}`;
1014
+ }
1015
+ },
1016
+ {
1017
+ name: "github",
1018
+ description: "Interact with GitHub: list repos, issues, PRs, create issues, read file contents. Requires GITHUB_TOKEN env var.",
1019
+ input_schema: {
1020
+ type: "object",
1021
+ properties: {
1022
+ action: {
1023
+ type: "string",
1024
+ description: "list_repos | list_issues | list_prs | create_issue | get_file | search_code",
1025
+ enum: [
1026
+ "list_repos",
1027
+ "list_issues",
1028
+ "list_prs",
1029
+ "create_issue",
1030
+ "get_file",
1031
+ "search_code"
1032
+ ]
1033
+ },
1034
+ repo: {
1035
+ type: "string",
1036
+ description: "owner/repo, e.g. \"mylo-2001/hyperclaw\""
1037
+ },
1038
+ title: {
1039
+ type: "string",
1040
+ description: "For create_issue: issue title"
1041
+ },
1042
+ body: {
1043
+ type: "string",
1044
+ description: "For create_issue: issue body"
1045
+ },
1046
+ path: {
1047
+ type: "string",
1048
+ description: "For get_file: file path in repo"
1049
+ },
1050
+ query: {
1051
+ type: "string",
1052
+ description: "For search_code: search query"
1053
+ }
1054
+ },
1055
+ required: ["action"]
1056
+ },
1057
+ handler: async (input) => {
1058
+ const token = process.env.GITHUB_TOKEN;
1059
+ if (!token) return "GITHUB_TOKEN not set. Create a token at github.com/settings/tokens.";
1060
+ const ghGet = (path2) => new Promise((resolve) => {
1061
+ https.default.get(`https://api.github.com${path2}`, {
1062
+ headers: {
1063
+ Authorization: `Bearer ${token}`,
1064
+ "User-Agent": "HyperClaw",
1065
+ Accept: "application/vnd.github+json"
1066
+ },
1067
+ timeout: 8e3
1068
+ }, (res) => {
1069
+ let d = "";
1070
+ res.on("data", (c) => d += c);
1071
+ res.on("end", () => {
1072
+ try {
1073
+ resolve(JSON.parse(d));
1074
+ } catch {
1075
+ resolve({ raw: d });
1076
+ }
1077
+ });
1078
+ }).on("error", (e) => resolve({ error: e.message }));
1079
+ });
1080
+ const ghPost = (path2, body) => new Promise((resolve) => {
1081
+ const payload = JSON.stringify(body);
1082
+ const req = https.default.request({
1083
+ hostname: "api.github.com",
1084
+ path: path2,
1085
+ method: "POST",
1086
+ headers: {
1087
+ Authorization: `Bearer ${token}`,
1088
+ "User-Agent": "HyperClaw",
1089
+ Accept: "application/vnd.github+json",
1090
+ "Content-Type": "application/json",
1091
+ "Content-Length": Buffer.byteLength(payload)
1092
+ }
1093
+ }, (res) => {
1094
+ let d = "";
1095
+ res.on("data", (c) => d += c);
1096
+ res.on("end", () => {
1097
+ try {
1098
+ resolve(JSON.parse(d));
1099
+ } catch {
1100
+ resolve({ raw: d });
1101
+ }
1102
+ });
1103
+ });
1104
+ req.on("error", (e) => resolve({ error: e.message }));
1105
+ req.write(payload);
1106
+ req.end();
1107
+ });
1108
+ const action = input.action;
1109
+ if (action === "list_repos") {
1110
+ const r = await ghGet("/user/repos?per_page=20&sort=updated");
1111
+ if (!Array.isArray(r)) return `Error: ${JSON.stringify(r)}`;
1112
+ return r.map((repo) => `${repo.full_name} — ⭐${repo.stargazers_count} — ${repo.description || ""}`).join("\n");
1113
+ }
1114
+ if (action === "list_issues") {
1115
+ if (!input.repo) return "repo required";
1116
+ const r = await ghGet(`/repos/${input.repo}/issues?state=open&per_page=15`);
1117
+ if (!Array.isArray(r)) return `Error: ${JSON.stringify(r)}`;
1118
+ return r.map((i) => `#${i.number} ${i.title} [${i.state}] — ${i.html_url}`).join("\n") || "No open issues.";
1119
+ }
1120
+ if (action === "list_prs") {
1121
+ if (!input.repo) return "repo required";
1122
+ const r = await ghGet(`/repos/${input.repo}/pulls?state=open&per_page=15`);
1123
+ if (!Array.isArray(r)) return `Error: ${JSON.stringify(r)}`;
1124
+ return r.map((p) => `#${p.number} ${p.title} by ${p.user?.login} — ${p.html_url}`).join("\n") || "No open PRs.";
1125
+ }
1126
+ if (action === "create_issue") {
1127
+ if (!input.repo || !input.title) return "repo and title required";
1128
+ const r = await ghPost(`/repos/${input.repo}/issues`, {
1129
+ title: input.title,
1130
+ body: input.body || ""
1131
+ });
1132
+ return r.html_url ? `Issue created: ${r.html_url}` : `Error: ${JSON.stringify(r)}`;
1133
+ }
1134
+ if (action === "get_file") {
1135
+ if (!input.repo || !input.path) return "repo and path required";
1136
+ const r = await ghGet(`/repos/${input.repo}/contents/${input.path}`);
1137
+ if (r.content) return Buffer.from(r.content, "base64").toString("utf8").slice(0, 3e3);
1138
+ return `Error: ${JSON.stringify(r)}`;
1139
+ }
1140
+ if (action === "search_code") {
1141
+ if (!input.query) return "query required";
1142
+ const r = await ghGet(`/search/code?q=${encodeURIComponent(input.query)}&per_page=10`);
1143
+ if (!r.items) return `Error: ${JSON.stringify(r)}`;
1144
+ return r.items.map((i) => `${i.repository?.full_name}/${i.path} — ${i.html_url}`).join("\n") || "No results.";
1145
+ }
1146
+ return `Unknown action: ${action}`;
1147
+ }
1148
+ },
1149
+ {
1150
+ name: "apple_notes",
1151
+ description: "Create, list, or search Apple Notes on macOS. Actions: create, list, search.",
1152
+ input_schema: {
1153
+ type: "object",
1154
+ properties: {
1155
+ action: {
1156
+ type: "string",
1157
+ description: "create | list | search",
1158
+ enum: [
1159
+ "create",
1160
+ "list",
1161
+ "search"
1162
+ ]
1163
+ },
1164
+ title: {
1165
+ type: "string",
1166
+ description: "Note title (for create)"
1167
+ },
1168
+ body: {
1169
+ type: "string",
1170
+ description: "Note body text (for create)"
1171
+ },
1172
+ query: {
1173
+ type: "string",
1174
+ description: "Search query (for search)"
1175
+ },
1176
+ limit: {
1177
+ type: "string",
1178
+ description: "Max results for list/search (default 10)"
1179
+ }
1180
+ },
1181
+ required: ["action"]
1182
+ },
1183
+ handler: async (input) => {
1184
+ if (process.platform !== "darwin") return "Apple Notes is only available on macOS.";
1185
+ const { execFile } = await import("child_process");
1186
+ const { promisify } = await import("util");
1187
+ const exec = promisify(execFile);
1188
+ const action = input.action;
1189
+ if (action === "create") {
1190
+ const title = (input.title || "New Note").replace(/"/g, "\\\"");
1191
+ const body = (input.body || "").replace(/"/g, "\\\"");
1192
+ const script = `tell application "Notes"\nset newNote to make new note at default account with properties {name:"${title}", body:"${title}\\n${body}"}\nreturn name of newNote\nend tell`;
1193
+ try {
1194
+ const r = await exec("osascript", ["-e", script]);
1195
+ return `Created note: ${r.stdout.trim()}`;
1196
+ } catch (e) {
1197
+ return `Error: ${e.message}`;
1198
+ }
1199
+ }
1200
+ if (action === "list") {
1201
+ const lim = parseInt(input.limit || "10");
1202
+ const script = `tell application "Notes"\nset out to {}\nrepeat with n in (notes 1 thru ${lim} of default account)\nset end of out to name of n\nend repeat\nreturn out\nend tell`;
1203
+ try {
1204
+ const r = await exec("osascript", ["-e", script]);
1205
+ return r.stdout.trim() || "No notes found.";
1206
+ } catch (e) {
1207
+ return `Error: ${e.message}`;
1208
+ }
1209
+ }
1210
+ if (action === "search") {
1211
+ const q = (input.query || "").replace(/"/g, "\\\"");
1212
+ const script = `tell application "Notes"\nset out to {}\nrepeat with n in notes of default account\nif name of n contains "${q}" or body of n contains "${q}" then\nset end of out to name of n\nend if\nend repeat\nreturn out\nend tell`;
1213
+ try {
1214
+ const r = await exec("osascript", ["-e", script]);
1215
+ return r.stdout.trim() || "No matching notes.";
1216
+ } catch (e) {
1217
+ return `Error: ${e.message}`;
1218
+ }
1219
+ }
1220
+ return `Unknown action: ${action}`;
1221
+ }
1222
+ },
1223
+ {
1224
+ name: "apple_reminders",
1225
+ description: "Add or list Apple Reminders on macOS. Actions: add, list, list_lists.",
1226
+ input_schema: {
1227
+ type: "object",
1228
+ properties: {
1229
+ action: {
1230
+ type: "string",
1231
+ description: "add | list | list_lists",
1232
+ enum: [
1233
+ "add",
1234
+ "list",
1235
+ "list_lists"
1236
+ ]
1237
+ },
1238
+ title: {
1239
+ type: "string",
1240
+ description: "Reminder title (for add)"
1241
+ },
1242
+ list: {
1243
+ type: "string",
1244
+ description: "Reminder list name (default: Reminders)"
1245
+ },
1246
+ dueDate: {
1247
+ type: "string",
1248
+ description: "Due date ISO string (optional, for add)"
1249
+ },
1250
+ limit: {
1251
+ type: "string",
1252
+ description: "Max reminders to return (default 20)"
1253
+ }
1254
+ },
1255
+ required: ["action"]
1256
+ },
1257
+ handler: async (input) => {
1258
+ if (process.platform !== "darwin") return "Apple Reminders is only available on macOS.";
1259
+ const { execFile } = await import("child_process");
1260
+ const { promisify } = await import("util");
1261
+ const exec = promisify(execFile);
1262
+ const action = input.action;
1263
+ if (action === "list_lists") {
1264
+ const script = `tell application "Reminders"\nset out to {}\nrepeat with l in lists\nset end of out to name of l\nend repeat\nreturn out\nend tell`;
1265
+ try {
1266
+ const r = await exec("osascript", ["-e", script]);
1267
+ return r.stdout.trim() || "No lists found.";
1268
+ } catch (e) {
1269
+ return `Error: ${e.message}`;
1270
+ }
1271
+ }
1272
+ if (action === "add") {
1273
+ if (!input.title) return "title is required";
1274
+ const title = input.title.replace(/"/g, "\\\"");
1275
+ const listName = (input.list || "Reminders").replace(/"/g, "\\\"");
1276
+ const script = `tell application "Reminders"\ntell list "${listName}"\nmake new reminder with properties {name:"${title}"}\nend tell\nend tell`;
1277
+ try {
1278
+ await exec("osascript", ["-e", script]);
1279
+ return `Reminder "${title}" added to ${listName}.`;
1280
+ } catch (e) {
1281
+ return `Error: ${e.message}`;
1282
+ }
1283
+ }
1284
+ if (action === "list") {
1285
+ const lim = parseInt(input.limit || "20");
1286
+ const listName = input.list;
1287
+ const script = listName ? `tell application "Reminders"\nset out to {}\nrepeat with r in reminders of list "${listName.replace(/"/g, "\\\"")}"\nif completed of r is false then set end of out to name of r\nend repeat\nreturn out\nend tell` : `tell application "Reminders"\nset out to {}\nrepeat with r in (reminders whose completed is false)\nset end of out to name of r\nif (count of out) >= ${lim} then exit repeat\nend repeat\nreturn out\nend tell`;
1288
+ try {
1289
+ const r = await exec("osascript", ["-e", script]);
1290
+ return r.stdout.trim() || "No pending reminders.";
1291
+ } catch (e) {
1292
+ return `Error: ${e.message}`;
1293
+ }
1294
+ }
1295
+ return `Unknown action: ${action}`;
1296
+ }
1297
+ },
1298
+ {
1299
+ name: "things3",
1300
+ description: "Add a to-do to Things 3 on macOS via URL scheme. Actions: add.",
1301
+ input_schema: {
1302
+ type: "object",
1303
+ properties: {
1304
+ action: {
1305
+ type: "string",
1306
+ description: "add",
1307
+ enum: ["add"]
1308
+ },
1309
+ title: {
1310
+ type: "string",
1311
+ description: "Task title"
1312
+ },
1313
+ notes: {
1314
+ type: "string",
1315
+ description: "Task notes (optional)"
1316
+ },
1317
+ deadline: {
1318
+ type: "string",
1319
+ description: "Deadline date YYYY-MM-DD (optional)"
1320
+ },
1321
+ list: {
1322
+ type: "string",
1323
+ description: "Project or area name (optional)"
1324
+ },
1325
+ tags: {
1326
+ type: "string",
1327
+ description: "Comma-separated tags (optional)"
1328
+ }
1329
+ },
1330
+ required: ["action", "title"]
1331
+ },
1332
+ handler: async (input) => {
1333
+ if (process.platform !== "darwin") return "Things 3 is only available on macOS.";
1334
+ const { execFile } = await import("child_process");
1335
+ const { promisify } = await import("util");
1336
+ const exec = promisify(execFile);
1337
+ const title = encodeURIComponent(input.title);
1338
+ const params = [`title=${title}`];
1339
+ if (input.notes) params.push(`notes=${encodeURIComponent(input.notes)}`);
1340
+ if (input.deadline) params.push(`deadline=${encodeURIComponent(input.deadline)}`);
1341
+ if (input.list) params.push(`list=${encodeURIComponent(input.list)}`);
1342
+ if (input.tags) params.push(`tags=${encodeURIComponent(input.tags)}`);
1343
+ const url = `things:///add?${params.join("&")}`;
1344
+ try {
1345
+ await exec("open", [url]);
1346
+ return `Added to Things 3: "${input.title}"`;
1347
+ } catch (e) {
1348
+ return `Error: ${e.message}. Is Things 3 installed?`;
1349
+ }
1350
+ }
1351
+ },
1352
+ {
1353
+ name: "obsidian",
1354
+ description: "Read, create, or search notes in Obsidian via Local REST API plugin. Set OBSIDIAN_API_KEY (and optionally OBSIDIAN_PORT, default 27123). Actions: search, read, create.",
1355
+ input_schema: {
1356
+ type: "object",
1357
+ properties: {
1358
+ action: {
1359
+ type: "string",
1360
+ description: "search | read | create",
1361
+ enum: [
1362
+ "search",
1363
+ "read",
1364
+ "create"
1365
+ ]
1366
+ },
1367
+ query: {
1368
+ type: "string",
1369
+ description: "Search query (for search)"
1370
+ },
1371
+ path: {
1372
+ type: "string",
1373
+ description: "Note path in vault e.g. \"folder/note.md\" (for read/create)"
1374
+ },
1375
+ content: {
1376
+ type: "string",
1377
+ description: "Note content in markdown (for create)"
1378
+ }
1379
+ },
1380
+ required: ["action"]
1381
+ },
1382
+ handler: async (input) => {
1383
+ const apiKey = process.env.OBSIDIAN_API_KEY;
1384
+ if (!apiKey) return "OBSIDIAN_API_KEY not set. Install Obsidian Local REST API plugin and set the env var.";
1385
+ const port = parseInt(process.env.OBSIDIAN_PORT || "27123");
1386
+ const action = input.action;
1387
+ const doReq = (method, endpoint, body) => new Promise((resolve, reject) => {
1388
+ const opts = {
1389
+ hostname: "127.0.0.1",
1390
+ port,
1391
+ path: endpoint,
1392
+ method,
1393
+ headers: {
1394
+ Authorization: `Bearer ${apiKey}`,
1395
+ "Content-Type": "application/json"
1396
+ }
1397
+ };
1398
+ const req = http.default.request(opts, (res) => {
1399
+ let d = "";
1400
+ res.on("data", (c) => d += c);
1401
+ res.on("end", () => {
1402
+ try {
1403
+ resolve(JSON.parse(d));
1404
+ } catch {
1405
+ resolve(d);
1406
+ }
1407
+ });
1408
+ });
1409
+ req.on("error", reject);
1410
+ if (body) req.write(body);
1411
+ req.end();
1412
+ });
1413
+ if (action === "search") {
1414
+ if (!input.query) return "query required";
1415
+ try {
1416
+ const r = await doReq("POST", "/search/simple/", JSON.stringify({
1417
+ query: input.query,
1418
+ contextLength: 100
1419
+ }));
1420
+ if (!Array.isArray(r)) return `Error: ${JSON.stringify(r)}`;
1421
+ return r.map((x) => `${x.filename}: ...${x.context}...`).join("\n") || "No results.";
1422
+ } catch (e) {
1423
+ return `Error: ${e.message}`;
1424
+ }
1425
+ }
1426
+ if (action === "read") {
1427
+ if (!input.path) return "path required";
1428
+ try {
1429
+ const r = await doReq("GET", `/vault/${encodeURIComponent(input.path)}`);
1430
+ return typeof r === "string" ? r : JSON.stringify(r);
1431
+ } catch (e) {
1432
+ return `Error: ${e.message}`;
1433
+ }
1434
+ }
1435
+ if (action === "create") {
1436
+ if (!input.path || !input.content) return "path and content required";
1437
+ try {
1438
+ await doReq("PUT", `/vault/${encodeURIComponent(input.path)}`, JSON.stringify(input.content));
1439
+ return `Note created/updated at ${input.path}`;
1440
+ } catch (e) {
1441
+ return `Error: ${e.message}`;
1442
+ }
1443
+ }
1444
+ return `Unknown action: ${action}`;
1445
+ }
1446
+ },
1447
+ {
1448
+ name: "bear_notes",
1449
+ description: "Create or search notes in Bear on macOS via x-callback-url. Actions: create, search.",
1450
+ input_schema: {
1451
+ type: "object",
1452
+ properties: {
1453
+ action: {
1454
+ type: "string",
1455
+ description: "create | search",
1456
+ enum: ["create", "search"]
1457
+ },
1458
+ title: {
1459
+ type: "string",
1460
+ description: "Note title (for create)"
1461
+ },
1462
+ text: {
1463
+ type: "string",
1464
+ description: "Note body (for create)"
1465
+ },
1466
+ tags: {
1467
+ type: "string",
1468
+ description: "Comma-separated tags (for create)"
1469
+ },
1470
+ query: {
1471
+ type: "string",
1472
+ description: "Search query (for search)"
1473
+ }
1474
+ },
1475
+ required: ["action"]
1476
+ },
1477
+ handler: async (input) => {
1478
+ if (process.platform !== "darwin") return "Bear Notes is only available on macOS.";
1479
+ const { execFile } = await import("child_process");
1480
+ const { promisify } = await import("util");
1481
+ const exec = promisify(execFile);
1482
+ const action = input.action;
1483
+ if (action === "create") {
1484
+ if (!input.title && !input.text) return "title or text required";
1485
+ const params = [];
1486
+ if (input.title) params.push(`title=${encodeURIComponent(input.title)}`);
1487
+ if (input.text) params.push(`text=${encodeURIComponent(input.text)}`);
1488
+ if (input.tags) params.push(`tags=${encodeURIComponent(input.tags)}`);
1489
+ const url = `bear://x-callback-url/create?${params.join("&")}`;
1490
+ try {
1491
+ await exec("open", [url]);
1492
+ return `Note created in Bear: "${input.title || "(untitled)"}"`.trim();
1493
+ } catch (e) {
1494
+ return `Error: ${e.message}. Is Bear installed?`;
1495
+ }
1496
+ }
1497
+ if (action === "search") {
1498
+ if (!input.query) return "query required";
1499
+ const url = `bear://x-callback-url/search?term=${encodeURIComponent(input.query)}`;
1500
+ try {
1501
+ await exec("open", [url]);
1502
+ return `Bear search opened for: "${input.query}"`;
1503
+ } catch (e) {
1504
+ return `Error: ${e.message}`;
1505
+ }
1506
+ }
1507
+ return `Unknown action: ${action}`;
1508
+ }
1509
+ },
1510
+ {
1511
+ name: "trello",
1512
+ description: "Interact with Trello boards and cards. Set TRELLO_API_KEY and TRELLO_TOKEN. Actions: list_boards, list_lists, list_cards, add_card, move_card.",
1513
+ input_schema: {
1514
+ type: "object",
1515
+ properties: {
1516
+ action: {
1517
+ type: "string",
1518
+ description: "list_boards | list_lists | list_cards | add_card | move_card",
1519
+ enum: [
1520
+ "list_boards",
1521
+ "list_lists",
1522
+ "list_cards",
1523
+ "add_card",
1524
+ "move_card"
1525
+ ]
1526
+ },
1527
+ boardId: {
1528
+ type: "string",
1529
+ description: "Board ID (for list_lists, list_cards)"
1530
+ },
1531
+ listId: {
1532
+ type: "string",
1533
+ description: "List ID (for list_cards, add_card, move_card)"
1534
+ },
1535
+ cardId: {
1536
+ type: "string",
1537
+ description: "Card ID (for move_card)"
1538
+ },
1539
+ name: {
1540
+ type: "string",
1541
+ description: "Card name (for add_card)"
1542
+ },
1543
+ desc: {
1544
+ type: "string",
1545
+ description: "Card description (for add_card)"
1546
+ }
1547
+ },
1548
+ required: ["action"]
1549
+ },
1550
+ handler: async (input) => {
1551
+ const key = process.env.TRELLO_API_KEY;
1552
+ const token = process.env.TRELLO_TOKEN;
1553
+ if (!key || !token) return "TRELLO_API_KEY and TRELLO_TOKEN must be set.";
1554
+ const action = input.action;
1555
+ const auth = `key=${key}&token=${token}`;
1556
+ const trelloGet = (path$2) => new Promise((resolve, reject) => {
1557
+ const req = https.default.request({
1558
+ hostname: "api.trello.com",
1559
+ path: `${path$2}${path$2.includes("?") ? "&" : "?"}${auth}`,
1560
+ method: "GET",
1561
+ headers: { Accept: "application/json" }
1562
+ }, (res) => {
1563
+ let d = "";
1564
+ res.on("data", (c) => d += c);
1565
+ res.on("end", () => {
1566
+ try {
1567
+ resolve(JSON.parse(d));
1568
+ } catch {
1569
+ resolve(d);
1570
+ }
1571
+ });
1572
+ });
1573
+ req.on("error", reject);
1574
+ req.end();
1575
+ });
1576
+ const trelloPost = (path$2, body) => new Promise((resolve, reject) => {
1577
+ const qs = new URLSearchParams({
1578
+ ...body,
1579
+ key,
1580
+ token
1581
+ }).toString();
1582
+ const req = https.default.request({
1583
+ hostname: "api.trello.com",
1584
+ path: path$2,
1585
+ method: "POST",
1586
+ headers: {
1587
+ "Content-Type": "application/x-www-form-urlencoded",
1588
+ "Content-Length": Buffer.byteLength(qs)
1589
+ }
1590
+ }, (res) => {
1591
+ let d = "";
1592
+ res.on("data", (c) => d += c);
1593
+ res.on("end", () => {
1594
+ try {
1595
+ resolve(JSON.parse(d));
1596
+ } catch {
1597
+ resolve(d);
1598
+ }
1599
+ });
1600
+ });
1601
+ req.on("error", reject);
1602
+ req.write(qs);
1603
+ req.end();
1604
+ });
1605
+ try {
1606
+ if (action === "list_boards") {
1607
+ const r = await trelloGet("/1/members/me/boards?fields=id,name,url");
1608
+ return r.map((b) => `${b.name} (${b.id}): ${b.url}`).join("\n") || "No boards.";
1609
+ }
1610
+ if (action === "list_lists") {
1611
+ if (!input.boardId) return "boardId required";
1612
+ const r = await trelloGet(`/1/boards/${input.boardId}/lists?fields=id,name`);
1613
+ return r.map((l) => `${l.name} (${l.id})`).join("\n") || "No lists.";
1614
+ }
1615
+ if (action === "list_cards") {
1616
+ if (!input.listId) return "listId required";
1617
+ const r = await trelloGet(`/1/lists/${input.listId}/cards?fields=id,name,desc`);
1618
+ return r.map((c) => `${c.name} (${c.id})`).join("\n") || "No cards.";
1619
+ }
1620
+ if (action === "add_card") {
1621
+ if (!input.listId || !input.name) return "listId and name required";
1622
+ const r = await trelloPost("/1/cards", {
1623
+ idList: input.listId,
1624
+ name: input.name,
1625
+ desc: input.desc || ""
1626
+ });
1627
+ return `Card created: ${r.name} (${r.id}) — ${r.url}`;
1628
+ }
1629
+ if (action === "move_card") {
1630
+ if (!input.cardId || !input.listId) return "cardId and listId required";
1631
+ const qs = `key=${key}&token=${token}&idList=${input.listId}`;
1632
+ await new Promise((resolve, reject) => {
1633
+ const req = https.default.request({
1634
+ hostname: "api.trello.com",
1635
+ path: `/1/cards/${input.cardId}`,
1636
+ method: "PUT",
1637
+ headers: {
1638
+ "Content-Type": "application/x-www-form-urlencoded",
1639
+ "Content-Length": Buffer.byteLength(qs)
1640
+ }
1641
+ }, (res) => {
1642
+ res.on("data", () => {});
1643
+ res.on("end", resolve);
1644
+ });
1645
+ req.on("error", reject);
1646
+ req.write(qs);
1647
+ req.end();
1648
+ });
1649
+ return `Card ${input.cardId} moved to list ${input.listId}.`;
1650
+ }
1651
+ return `Unknown action: ${action}`;
1652
+ } catch (e) {
1653
+ return `Error: ${e.message}`;
1654
+ }
1655
+ }
1656
+ },
1657
+ {
1658
+ name: "sonos",
1659
+ description: "Control a Sonos speaker over local network. Set SONOS_IP (e.g. 192.168.1.x). Actions: play, pause, next, previous, volume, info.",
1660
+ input_schema: {
1661
+ type: "object",
1662
+ properties: {
1663
+ action: {
1664
+ type: "string",
1665
+ description: "play | pause | next | previous | volume | info",
1666
+ enum: [
1667
+ "play",
1668
+ "pause",
1669
+ "next",
1670
+ "previous",
1671
+ "volume",
1672
+ "info"
1673
+ ]
1674
+ },
1675
+ volume: {
1676
+ type: "string",
1677
+ description: "Volume 0-100 (for volume action)"
1678
+ }
1679
+ },
1680
+ required: ["action"]
1681
+ },
1682
+ handler: async (input) => {
1683
+ const ip = process.env.SONOS_IP;
1684
+ if (!ip) return "SONOS_IP not set. Set it to your Sonos speaker's local IP address.";
1685
+ const action = input.action;
1686
+ const soapPost = (path$2, service, soapAction, body) => new Promise((resolve, reject) => {
1687
+ const payload = `<?xml version="1.0"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body>${body}</s:Body></s:Envelope>`;
1688
+ const req = http.default.request({
1689
+ hostname: ip,
1690
+ port: 1400,
1691
+ path: path$2,
1692
+ method: "POST",
1693
+ headers: {
1694
+ "Content-Type": "text/xml; charset=\"utf-8\"",
1695
+ SOAPAction: `"${service}#${soapAction}"`,
1696
+ "Content-Length": Buffer.byteLength(payload)
1697
+ }
1698
+ }, (res) => {
1699
+ let d = "";
1700
+ res.on("data", (c) => d += c);
1701
+ res.on("end", () => resolve(d));
1702
+ });
1703
+ req.on("error", reject);
1704
+ req.write(payload);
1705
+ req.end();
1706
+ });
1707
+ const AVT = "urn:schemas-upnp-org:service:AVTransport:1";
1708
+ const RC = "urn:schemas-upnp-org:service:RenderingControl:1";
1709
+ try {
1710
+ if (action === "play") {
1711
+ await soapPost("/MediaRenderer/AVTransport/Control", AVT, "Play", `<u:Play xmlns:u="${AVT}"><InstanceID>0</InstanceID><Speed>1</Speed></u:Play>`);
1712
+ return "Sonos playing.";
1713
+ }
1714
+ if (action === "pause") {
1715
+ await soapPost("/MediaRenderer/AVTransport/Control", AVT, "Pause", `<u:Pause xmlns:u="${AVT}"><InstanceID>0</InstanceID></u:Pause>`);
1716
+ return "Sonos paused.";
1717
+ }
1718
+ if (action === "next") {
1719
+ await soapPost("/MediaRenderer/AVTransport/Control", AVT, "Next", `<u:Next xmlns:u="${AVT}"><InstanceID>0</InstanceID></u:Next>`);
1720
+ return "Skipped to next track.";
1721
+ }
1722
+ if (action === "previous") {
1723
+ await soapPost("/MediaRenderer/AVTransport/Control", AVT, "Previous", `<u:Previous xmlns:u="${AVT}"><InstanceID>0</InstanceID></u:Previous>`);
1724
+ return "Went to previous track.";
1725
+ }
1726
+ if (action === "volume") {
1727
+ const vol = parseInt(input.volume || "50");
1728
+ await soapPost("/MediaRenderer/RenderingControl/Control", RC, "SetVolume", `<u:SetVolume xmlns:u="${RC}"><InstanceID>0</InstanceID><Channel>Master</Channel><DesiredVolume>${vol}</DesiredVolume></u:SetVolume>`);
1729
+ return `Volume set to ${vol}.`;
1730
+ }
1731
+ if (action === "info") {
1732
+ const r = await soapPost("/MediaRenderer/AVTransport/Control", AVT, "GetTransportInfo", `<u:GetTransportInfo xmlns:u="${AVT}"><InstanceID>0</InstanceID></u:GetTransportInfo>`);
1733
+ const state = r.match(/<CurrentTransportState>(.+?)<\/CurrentTransportState>/)?.[1] ?? "Unknown";
1734
+ return `Sonos state: ${state}`;
1735
+ }
1736
+ return `Unknown action: ${action}`;
1737
+ } catch (e) {
1738
+ return `Error: ${e.message}`;
1739
+ }
1740
+ }
1741
+ },
1742
+ {
1743
+ name: "shazam",
1744
+ description: "Search for songs, albums, or artists using iTunes Search API (free, no key needed). Actions: search.",
1745
+ input_schema: {
1746
+ type: "object",
1747
+ properties: {
1748
+ action: {
1749
+ type: "string",
1750
+ description: "search",
1751
+ enum: ["search"]
1752
+ },
1753
+ query: {
1754
+ type: "string",
1755
+ description: "Song, artist, or album to search for"
1756
+ },
1757
+ type: {
1758
+ type: "string",
1759
+ description: "musicTrack | album | musicArtist (default: musicTrack)"
1760
+ },
1761
+ limit: {
1762
+ type: "string",
1763
+ description: "Max results (default 5)"
1764
+ }
1765
+ },
1766
+ required: ["action", "query"]
1767
+ },
1768
+ handler: async (input) => {
1769
+ const query = encodeURIComponent(input.query);
1770
+ const entity = input.type || "musicTrack";
1771
+ const limit = parseInt(input.limit || "5");
1772
+ const r = await new Promise((resolve, reject) => {
1773
+ const req = https.default.request({
1774
+ hostname: "itunes.apple.com",
1775
+ path: `/search?term=${query}&entity=${entity}&limit=${limit}`,
1776
+ method: "GET",
1777
+ headers: { "User-Agent": "HyperClaw/4" }
1778
+ }, (res) => {
1779
+ let d = "";
1780
+ res.on("data", (c) => d += c);
1781
+ res.on("end", () => {
1782
+ try {
1783
+ resolve(JSON.parse(d));
1784
+ } catch {
1785
+ resolve({});
1786
+ }
1787
+ });
1788
+ });
1789
+ req.on("error", reject);
1790
+ req.end();
1791
+ });
1792
+ if (!r.results?.length) return "No results found.";
1793
+ return r.results.map((t) => entity === "musicTrack" ? `"${t.trackName}" by ${t.artistName} (${t.collectionName || "Single"}) — ${t.trackViewUrl}` : entity === "album" ? `${t.collectionName} by ${t.artistName} — ${t.collectionViewUrl}` : `${t.artistName} — ${t.artistViewUrl}`).join("\n");
1794
+ }
1795
+ },
1796
+ {
1797
+ name: "philips_hue",
1798
+ description: "Control Philips Hue lights via local bridge. Set HUE_BRIDGE_IP and HUE_USERNAME (run bridge discovery once to get these). Actions: list_lights, turn_on, turn_off, brightness, color.",
1799
+ input_schema: {
1800
+ type: "object",
1801
+ properties: {
1802
+ action: {
1803
+ type: "string",
1804
+ description: "list_lights | turn_on | turn_off | brightness | color",
1805
+ enum: [
1806
+ "list_lights",
1807
+ "turn_on",
1808
+ "turn_off",
1809
+ "brightness",
1810
+ "color"
1811
+ ]
1812
+ },
1813
+ lightId: {
1814
+ type: "string",
1815
+ description: "Light ID (from list_lights). Use \"all\" to affect all lights."
1816
+ },
1817
+ brightness: {
1818
+ type: "string",
1819
+ description: "Brightness 0-254 (for brightness action)"
1820
+ },
1821
+ color: {
1822
+ type: "string",
1823
+ description: "Color as hue 0-65535 (for color action)"
1824
+ }
1825
+ },
1826
+ required: ["action"]
1827
+ },
1828
+ handler: async (input) => {
1829
+ const bridgeIp = process.env.HUE_BRIDGE_IP;
1830
+ const username = process.env.HUE_USERNAME;
1831
+ if (!bridgeIp || !username) return "HUE_BRIDGE_IP and HUE_USERNAME must be set. See Philips Hue developer portal to get your bridge username.";
1832
+ const action = input.action;
1833
+ const hueReq = (method, path$2, body) => new Promise((resolve, reject) => {
1834
+ const payload = body ? JSON.stringify(body) : void 0;
1835
+ const opts = {
1836
+ hostname: bridgeIp,
1837
+ path: `/api/${username}${path$2}`,
1838
+ method,
1839
+ headers: {
1840
+ "Content-Type": "application/json",
1841
+ ...payload ? { "Content-Length": Buffer.byteLength(payload) } : {}
1842
+ }
1843
+ };
1844
+ const req = http.default.request(opts, (res) => {
1845
+ let d = "";
1846
+ res.on("data", (c) => d += c);
1847
+ res.on("end", () => {
1848
+ try {
1849
+ resolve(JSON.parse(d));
1850
+ } catch {
1851
+ resolve(d);
1852
+ }
1853
+ });
1854
+ });
1855
+ req.on("error", reject);
1856
+ if (payload) req.write(payload);
1857
+ req.end();
1858
+ });
1859
+ try {
1860
+ if (action === "list_lights") {
1861
+ const r = await hueReq("GET", "/lights");
1862
+ if (typeof r !== "object") return `Error: ${r}`;
1863
+ return Object.entries(r).map(([id, l]) => `[${id}] ${l.name} — ${l.state?.on ? "ON" : "OFF"}, bri: ${l.state?.bri}`).join("\n") || "No lights found.";
1864
+ }
1865
+ const lid = input.lightId;
1866
+ if (!lid) return "lightId required";
1867
+ const ids = lid === "all" ? Object.keys(await hueReq("GET", "/lights")) : [lid];
1868
+ if (action === "turn_on") {
1869
+ for (const id of ids) await hueReq("PUT", `/lights/${id}/state`, { on: true });
1870
+ return `Light(s) ${ids.join(", ")} turned ON.`;
1871
+ }
1872
+ if (action === "turn_off") {
1873
+ for (const id of ids) await hueReq("PUT", `/lights/${id}/state`, { on: false });
1874
+ return `Light(s) ${ids.join(", ")} turned OFF.`;
1875
+ }
1876
+ if (action === "brightness") {
1877
+ const bri = Math.min(254, Math.max(0, parseInt(input.brightness || "128")));
1878
+ for (const id of ids) await hueReq("PUT", `/lights/${id}/state`, {
1879
+ on: true,
1880
+ bri
1881
+ });
1882
+ return `Brightness set to ${bri} for light(s) ${ids.join(", ")}.`;
1883
+ }
1884
+ if (action === "color") {
1885
+ const hue = Math.min(65535, Math.max(0, parseInt(input.color || "0")));
1886
+ for (const id of ids) await hueReq("PUT", `/lights/${id}/state`, {
1887
+ on: true,
1888
+ hue,
1889
+ sat: 254
1890
+ });
1891
+ return `Color (hue ${hue}) set for light(s) ${ids.join(", ")}.`;
1892
+ }
1893
+ return `Unknown action: ${action}`;
1894
+ } catch (e) {
1895
+ return `Error: ${e.message}`;
1896
+ }
1897
+ }
1898
+ },
1899
+ {
1900
+ name: "eightsleep",
1901
+ description: "Control your Eight Sleep Pod. Set EIGHTSLEEP_EMAIL and EIGHTSLEEP_PASSWORD. Actions: get_status, set_temperature.",
1902
+ input_schema: {
1903
+ type: "object",
1904
+ properties: {
1905
+ action: {
1906
+ type: "string",
1907
+ description: "get_status | set_temperature",
1908
+ enum: ["get_status", "set_temperature"]
1909
+ },
1910
+ side: {
1911
+ type: "string",
1912
+ description: "left | right | solo (default: solo)"
1913
+ },
1914
+ level: {
1915
+ type: "string",
1916
+ description: "Temperature level -100 to 100 (for set_temperature)"
1917
+ }
1918
+ },
1919
+ required: ["action"]
1920
+ },
1921
+ handler: async (input) => {
1922
+ const email = process.env.EIGHTSLEEP_EMAIL;
1923
+ const password = process.env.EIGHTSLEEP_PASSWORD;
1924
+ if (!email || !password) return "EIGHTSLEEP_EMAIL and EIGHTSLEEP_PASSWORD must be set.";
1925
+ const action = input.action;
1926
+ const apiPost = (path$2, body, token) => new Promise((resolve, reject) => {
1927
+ const payload = JSON.stringify(body);
1928
+ const hdrs = {
1929
+ "Content-Type": "application/json",
1930
+ "Content-Length": String(Buffer.byteLength(payload))
1931
+ };
1932
+ if (token) hdrs["authorization"] = `Bearer ${token}`;
1933
+ const req = https.default.request({
1934
+ hostname: "client-api.8slp.net",
1935
+ path: path$2,
1936
+ method: "POST",
1937
+ headers: hdrs
1938
+ }, (res) => {
1939
+ let d = "";
1940
+ res.on("data", (c) => d += c);
1941
+ res.on("end", () => {
1942
+ try {
1943
+ resolve(JSON.parse(d));
1944
+ } catch {
1945
+ resolve(d);
1946
+ }
1947
+ });
1948
+ });
1949
+ req.on("error", reject);
1950
+ req.write(payload);
1951
+ req.end();
1952
+ });
1953
+ const apiGet = (path$2, token) => new Promise((resolve, reject) => {
1954
+ const req = https.default.request({
1955
+ hostname: "client-api.8slp.net",
1956
+ path: path$2,
1957
+ method: "GET",
1958
+ headers: { authorization: `Bearer ${token}` }
1959
+ }, (res) => {
1960
+ let d = "";
1961
+ res.on("data", (c) => d += c);
1962
+ res.on("end", () => {
1963
+ try {
1964
+ resolve(JSON.parse(d));
1965
+ } catch {
1966
+ resolve(d);
1967
+ }
1968
+ });
1969
+ });
1970
+ req.on("error", reject);
1971
+ req.end();
1972
+ });
1973
+ try {
1974
+ const authR = await apiPost("/v1/login", {
1975
+ email,
1976
+ password
1977
+ });
1978
+ const token = authR?.session?.token;
1979
+ if (!token) return `Auth failed: ${JSON.stringify(authR)}`;
1980
+ const userId = authR?.session?.userId;
1981
+ const device = await apiGet(`/v1/users/${userId}/devices`, token);
1982
+ const deviceId = device?.result?.devices?.[0];
1983
+ if (!deviceId) return "No 8Sleep device found on account.";
1984
+ if (action === "get_status") {
1985
+ const status = await apiGet(`/v1/devices/${deviceId}?filter=currentState`, token);
1986
+ const s = status?.result;
1987
+ return `8Sleep device: ${deviceId}\nLeft: ${s?.leftCurrentState?.currentTemperatureRequestLevel ?? "N/A"}, Right: ${s?.rightCurrentState?.currentTemperatureRequestLevel ?? "N/A"}`;
1988
+ }
1989
+ if (action === "set_temperature") {
1990
+ const level = parseInt(input.level || "0");
1991
+ const side = input.side || "solo";
1992
+ const sideKey = side === "left" ? "leftHeatingLevel" : side === "right" ? "rightHeatingLevel" : "targetHeatingLevel";
1993
+ const r = await apiPost(`/v1/devices/${deviceId}/users/${userId}/temperature`, { [sideKey]: level }, token);
1994
+ return `8Sleep temperature set to ${level} on ${side} side.`;
1995
+ }
1996
+ return `Unknown action: ${action}`;
1997
+ } catch (e) {
1998
+ return `Error: ${e.message}`;
1999
+ }
2000
+ }
2001
+ },
2002
+ {
2003
+ name: "onepassword",
2004
+ description: "Access 1Password items via op CLI. Set OP_SERVICE_ACCOUNT_TOKEN. Actions: get_item, list_items.",
2005
+ input_schema: {
2006
+ type: "object",
2007
+ properties: {
2008
+ action: {
2009
+ type: "string",
2010
+ description: "get_item | list_items",
2011
+ enum: ["get_item", "list_items"]
2012
+ },
2013
+ item: {
2014
+ type: "string",
2015
+ description: "Item name or UUID (for get_item)"
2016
+ },
2017
+ vault: {
2018
+ type: "string",
2019
+ description: "Vault name (optional)"
2020
+ },
2021
+ fields: {
2022
+ type: "string",
2023
+ description: "Comma-separated field labels to return (for get_item, default: username,password)"
2024
+ }
2025
+ },
2026
+ required: ["action"]
2027
+ },
2028
+ handler: async (input) => {
2029
+ const token = process.env.OP_SERVICE_ACCOUNT_TOKEN;
2030
+ if (!token) return "OP_SERVICE_ACCOUNT_TOKEN not set. Create a service account at 1password.com/developer.";
2031
+ const { execFile } = await import("child_process");
2032
+ const { promisify } = await import("util");
2033
+ const exec = promisify(execFile);
2034
+ const env = {
2035
+ ...process.env,
2036
+ OP_SERVICE_ACCOUNT_TOKEN: token
2037
+ };
2038
+ const action = input.action;
2039
+ try {
2040
+ if (action === "list_items") {
2041
+ const args = [
2042
+ "item",
2043
+ "list",
2044
+ "--format=json"
2045
+ ];
2046
+ if (input.vault) args.push(`--vault=${input.vault}`);
2047
+ const r = await exec("op", args, { env });
2048
+ const items = JSON.parse(r.stdout);
2049
+ return items.map((i) => `${i.title} (${i.id}) — vault: ${i.vault?.name || "unknown"}`).join("\n") || "No items found.";
2050
+ }
2051
+ if (action === "get_item") {
2052
+ if (!input.item) return "item name or UUID required";
2053
+ const fields = (input.fields || "username,password").split(",").map((f) => f.trim());
2054
+ const args = [
2055
+ "item",
2056
+ "get",
2057
+ input.item,
2058
+ "--format=json"
2059
+ ];
2060
+ if (input.vault) args.push(`--vault=${input.vault}`);
2061
+ const r = await exec("op", args, { env });
2062
+ const item = JSON.parse(r.stdout);
2063
+ const result = {};
2064
+ for (const f of item.fields || []) if (fields.includes(f.label?.toLowerCase()) || fields.includes(f.id)) result[f.label || f.id] = f.value || "";
2065
+ return Object.entries(result).map(([k, v]) => `${k}: ${v}`).join("\n") || "No matching fields found.";
2066
+ }
2067
+ return `Unknown action: ${action}`;
2068
+ } catch (e) {
2069
+ return `Error: ${e.message}\nMake sure 'op' CLI is installed (brew install 1password-cli).`;
2070
+ }
2071
+ }
2072
+ },
2073
+ {
2074
+ name: "imessage",
2075
+ description: "Send iMessages or list recent conversations on macOS via AppleScript. Actions: send, list_conversations.",
2076
+ input_schema: {
2077
+ type: "object",
2078
+ properties: {
2079
+ action: {
2080
+ type: "string",
2081
+ description: "send | list_conversations",
2082
+ enum: ["send", "list_conversations"]
2083
+ },
2084
+ to: {
2085
+ type: "string",
2086
+ description: "Recipient phone number or email (for send)"
2087
+ },
2088
+ message: {
2089
+ type: "string",
2090
+ description: "Message text to send (for send)"
2091
+ },
2092
+ limit: {
2093
+ type: "string",
2094
+ description: "Max conversations to list (default 10)"
2095
+ }
2096
+ },
2097
+ required: ["action"]
2098
+ },
2099
+ handler: async (input) => {
2100
+ if (process.platform !== "darwin") return "iMessage is only available on macOS.";
2101
+ const { execFile } = await import("child_process");
2102
+ const { promisify } = await import("util");
2103
+ const exec = promisify(execFile);
2104
+ const action = input.action;
2105
+ if (action === "send") {
2106
+ if (!input.to || !input.message) return "to and message are required";
2107
+ const to = input.to.replace(/"/g, "\\\"");
2108
+ const msg = input.message.replace(/"/g, "\\\"");
2109
+ const script = `tell application "Messages"\nset targetBuddy to "${to}"\nset targetService to first service whose service type = iMessage\nset targetBuddy to buddy targetBuddy of targetService\nsend "${msg}" to targetBuddy\nend tell`;
2110
+ try {
2111
+ await exec("osascript", ["-e", script]);
2112
+ return `iMessage sent to ${input.to}.`;
2113
+ } catch (e) {
2114
+ return `Error: ${e.message}\nMake sure Messages.app is signed in and has accessibility permissions.`;
2115
+ }
2116
+ }
2117
+ if (action === "list_conversations") {
2118
+ const lim = parseInt(input.limit || "10");
2119
+ const script = `tell application "Messages"\nset out to {}\nrepeat with c in (chats 1 thru ${lim})\nset end of out to name of c\nend repeat\nreturn out\nend tell`;
2120
+ try {
2121
+ const r = await exec("osascript", ["-e", script]);
2122
+ return r.stdout.trim() || "No conversations found.";
2123
+ } catch (e) {
2124
+ return `Error: ${e.message}`;
2125
+ }
2126
+ }
2127
+ return `Unknown action: ${action}`;
2128
+ }
2129
+ },
2130
+ {
2131
+ name: "install_skill_from_hub",
2132
+ description: "Install a skill by URL from clawhub.ai or any SKILL.md URL. Fetches the skill, extracts SKILL.md and files, installs npm deps if needed. Use when user gives a clawhub.ai link.",
2133
+ input_schema: {
2134
+ type: "object",
2135
+ properties: {
2136
+ url: {
2137
+ type: "string",
2138
+ description: "clawhub.ai skill URL, e.g. https://clawhub.ai/owner/skill-slug"
2139
+ },
2140
+ npmInstall: {
2141
+ type: "string",
2142
+ description: "Set to \"true\" to run npm install if the skill has a package.json"
2143
+ }
2144
+ },
2145
+ required: ["url"]
2146
+ },
2147
+ handler: async (input) => {
2148
+ const rawUrl = input.url.trim().replace(/\/$/, "");
2149
+ const fetchText = (url) => new Promise((resolve, reject) => {
2150
+ const mod = url.startsWith("https") ? https.default : http.default;
2151
+ const doReq = (u, redirects = 0) => {
2152
+ const req = mod.request(u, {
2153
+ method: "GET",
2154
+ headers: {
2155
+ "User-Agent": "HyperClaw/4",
2156
+ Accept: "text/html,application/json,text/plain"
2157
+ }
2158
+ }, (res) => {
2159
+ if ((res.statusCode === 301 || res.statusCode === 302) && res.headers.location && redirects < 5) {
2160
+ doReq(res.headers.location, redirects + 1);
2161
+ return;
2162
+ }
2163
+ let d = "";
2164
+ res.setEncoding("utf8");
2165
+ res.on("data", (c) => d += c);
2166
+ res.on("end", () => resolve({
2167
+ status: res.statusCode ?? 0,
2168
+ body: d
2169
+ }));
2170
+ });
2171
+ req.on("error", reject);
2172
+ req.end();
2173
+ };
2174
+ doReq(url);
2175
+ });
2176
+ const hubMatch = rawUrl.match(/https?:\/\/clawhub\.ai\/([^/]+)\/([^/?#]+)/);
2177
+ if (hubMatch) {
2178
+ const [, owner, slug] = hubMatch;
2179
+ const apiUrls = [`https://clawhub.ai/api/skills/${owner}/${slug}`, `https://clawhub.ai/api/v1/skills/${owner}/${slug}`];
2180
+ for (const apiUrl of apiUrls) try {
2181
+ const { status, body } = await fetchText(apiUrl);
2182
+ if (status === 200) {
2183
+ const data = JSON.parse(body);
2184
+ const s = data?.skill || data;
2185
+ if (s?.skillMd || s?.content || s?.skill_md) {
2186
+ const skillId = s.slug || s.skillId || slug;
2187
+ const skillMd = s.skillMd || s.content || s.skill_md || "";
2188
+ const extraFiles = s.files || {};
2189
+ const { writeSkill } = await Promise.resolve().then(() => require("./skill-loader-B5oeliGu.js"));
2190
+ const { dir, id } = await writeSkill(skillId, {
2191
+ name: s.name,
2192
+ description: s.description,
2193
+ content: skillMd
2194
+ });
2195
+ const results = [`SKILL.md → written`];
2196
+ for (const [rel, content] of Object.entries(extraFiles)) {
2197
+ const norm = path.default.normalize(rel).replace(/^(\.\.(\/|\\|$))+/, "");
2198
+ const fp = path.default.join(dir, norm);
2199
+ await fs_extra.default.ensureDir(path.default.dirname(fp));
2200
+ await fs_extra.default.writeFile(fp, content, "utf8");
2201
+ results.push(`${norm} → written`);
2202
+ }
2203
+ if (input.npmInstall === "true" && await fs_extra.default.pathExists(path.default.join(dir, "package.json"))) {
2204
+ const { execFile } = await import("child_process");
2205
+ const { promisify } = await import("util");
2206
+ await promisify(execFile)("npm", ["install", "--omit=dev"], {
2207
+ cwd: dir,
2208
+ timeout: 12e4
2209
+ }).catch((e) => results.push(`npm install failed: ${e.message}`));
2210
+ results.push("npm install → done");
2211
+ }
2212
+ return `Skill "${id}" installed from ${rawUrl}\n${results.join("\n")}\nLoads on next turn.`;
2213
+ }
2214
+ }
2215
+ } catch {}
2216
+ const rawSkillUrls = [
2217
+ `https://clawhub.ai/${owner}/${slug}/raw/SKILL.md`,
2218
+ `https://clawhub.ai/skills/${owner}/${slug}/SKILL.md`,
2219
+ `https://raw.clawhub.ai/${owner}/${slug}/SKILL.md`
2220
+ ];
2221
+ for (const rawSkillUrl of rawSkillUrls) try {
2222
+ const { status, body } = await fetchText(rawSkillUrl);
2223
+ if (status === 200 && body.trim().length > 20) {
2224
+ const { writeSkill } = await Promise.resolve().then(() => require("./skill-loader-B5oeliGu.js"));
2225
+ const { id } = await writeSkill(slug, { content: body });
2226
+ return `Skill "${id}" installed from ${rawSkillUrl}\nLoads on next turn.`;
2227
+ }
2228
+ } catch {}
2229
+ }
2230
+ if (rawUrl.endsWith("SKILL.md") || rawUrl.includes("/raw/")) try {
2231
+ const { status, body } = await fetchText(rawUrl);
2232
+ if (status === 200 && body.trim().length > 20) {
2233
+ const slugFromUrl = rawUrl.split("/").slice(-3, -1).join("-") || "custom-skill";
2234
+ const { writeSkill } = await Promise.resolve().then(() => require("./skill-loader-B5oeliGu.js"));
2235
+ const { id } = await writeSkill(slugFromUrl, { content: body });
2236
+ return `Skill "${id}" installed from ${rawUrl}\nLoads on next turn.`;
2237
+ }
2238
+ } catch (e) {
2239
+ return `Failed to fetch SKILL.md: ${e.message}`;
2240
+ }
2241
+ try {
2242
+ const { status, body } = await fetchText(rawUrl);
2243
+ if (status !== 200) return `Could not fetch ${rawUrl} (HTTP ${status}). Try pasting the SKILL.md content directly.`;
2244
+ const fmMatch = body.match(/---\s*\n[\s\S]*?name:\s*([^\n]+)[\s\S]*?---\s*\n([\s\S]+?)(?=<|```|$)/);
2245
+ if (fmMatch) {
2246
+ const [fullMatch] = fmMatch;
2247
+ const slugFromUrl = rawUrl.split("/").pop() || "custom-skill";
2248
+ const { writeSkill } = await Promise.resolve().then(() => require("./skill-loader-B5oeliGu.js"));
2249
+ const { id } = await writeSkill(slugFromUrl, { content: fullMatch });
2250
+ return `Skill "${id}" extracted from page and installed.\nLoads on next turn.`;
2251
+ }
2252
+ const preview = body.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 800);
2253
+ return `Could not auto-extract SKILL.md from ${rawUrl}.\nPage preview:\n${preview}\n\nPaste the SKILL.md content directly and I'll install it with create_skill.`;
2254
+ } catch (e) {
2255
+ return `Error fetching ${rawUrl}: ${e.message}\n\nPaste the SKILL.md content directly and I'll install it with create_skill.`;
2256
+ }
2257
+ }
2258
+ },
2259
+ {
2260
+ name: "create_skill",
2261
+ description: "Create or overwrite a custom skill (self-writing skills). Supports multi-file skills with npm dependencies. The skill will be loaded on next message.",
2262
+ input_schema: {
2263
+ type: "object",
2264
+ properties: {
2265
+ skillId: {
2266
+ type: "string",
2267
+ description: "Unique slug, e.g. \"stealth-browser\" or \"remind-weekly\""
2268
+ },
2269
+ name: {
2270
+ type: "string",
2271
+ description: "Human-readable name"
2272
+ },
2273
+ description: {
2274
+ type: "string",
2275
+ description: "Short description of when to use"
2276
+ },
2277
+ content: {
2278
+ type: "string",
2279
+ description: "Full SKILL.md body: instructions, when to use, steps. Use markdown."
2280
+ },
2281
+ files: {
2282
+ type: "string",
2283
+ description: "JSON object of extra files to write alongside SKILL.md. Keys are relative paths (e.g. \"scripts/browser.js\"), values are file contents."
2284
+ },
2285
+ npmInstall: {
2286
+ type: "string",
2287
+ description: "Set to \"true\" to run npm install in the skill directory after writing files (requires package.json in files)."
2288
+ }
2289
+ },
2290
+ required: ["skillId", "content"]
2291
+ },
2292
+ handler: async (input) => {
2293
+ const { writeSkill } = await Promise.resolve().then(() => require("./skill-loader-B5oeliGu.js"));
2294
+ const { path: p, id, dir } = await writeSkill(input.skillId, {
2295
+ name: input.name,
2296
+ description: input.description,
2297
+ content: input.content
2298
+ });
2299
+ const results = [`SKILL.md → ${p}`];
2300
+ if (input.files) {
2301
+ let extraFiles;
2302
+ try {
2303
+ extraFiles = JSON.parse(input.files);
2304
+ } catch {
2305
+ return `Error: "files" must be a valid JSON object mapping relative paths to file contents.`;
2306
+ }
2307
+ for (const [relPath, content] of Object.entries(extraFiles)) {
2308
+ const norm = path.default.normalize(relPath).replace(/^(\.\.(\/|\\|$))+/, "");
2309
+ if (path.default.isAbsolute(norm) || norm.startsWith("..")) {
2310
+ results.push(`Skipped ${relPath}: path traversal not allowed`);
2311
+ continue;
2312
+ }
2313
+ const filePath = path.default.join(dir, norm);
2314
+ await fs_extra.default.ensureDir(path.default.dirname(filePath));
2315
+ await fs_extra.default.writeFile(filePath, content, "utf8");
2316
+ results.push(`${norm} → written`);
2317
+ }
2318
+ }
2319
+ if (input.npmInstall === "true") {
2320
+ const pkgJson = path.default.join(dir, "package.json");
2321
+ if (await fs_extra.default.pathExists(pkgJson)) try {
2322
+ const { execFile } = await import("child_process");
2323
+ const { promisify } = await import("util");
2324
+ await promisify(execFile)("npm", ["install", "--omit=dev"], {
2325
+ cwd: dir,
2326
+ timeout: 12e4
2327
+ });
2328
+ results.push("npm install → done");
2329
+ } catch (e) {
2330
+ results.push(`npm install → failed: ${e.message}`);
2331
+ }
2332
+ else results.push("npm install → skipped (no package.json found)");
2333
+ }
2334
+ return `Skill "${id}" installed at ${dir}\n${results.join("\n")}\n\nIt will be loaded on the next turn.`;
2335
+ }
2336
+ }
2337
+ ];
2338
+ }
2339
+ var HC_DIR, InferenceEngine;
2340
+ var init_inference = require_chunk.__esm({ "packages/core/src/agent/inference.ts"() {
2341
+ HC_DIR = path.default.join(os.default.homedir(), ".hyperclaw");
2342
+ InferenceEngine = class {
2343
+ opts;
2344
+ constructor(opts) {
2345
+ this.opts = opts;
2346
+ }
2347
+ async run(messages) {
2348
+ const result = {
2349
+ text: "",
2350
+ thinking: void 0,
2351
+ toolCalls: [],
2352
+ usage: {
2353
+ input: 0,
2354
+ output: 0
2355
+ },
2356
+ stopReason: "end_turn"
2357
+ };
2358
+ const history = [...messages];
2359
+ const maxRounds = this.opts.maxToolRounds ?? 10;
2360
+ let rounds = 0;
2361
+ while (rounds < maxRounds) {
2362
+ rounds++;
2363
+ const response = await this.callAPI(history, result);
2364
+ const toolUses = response.content?.filter((b) => b.type === "tool_use") || [];
2365
+ if (toolUses.length === 0 || !this.opts.tools?.length) {
2366
+ const textBlocks = (response.content || []).filter((b) => b.type === "text");
2367
+ result.text = textBlocks.map((b) => b.text).join("");
2368
+ result.stopReason = response.stop_reason || "end_turn";
2369
+ if (response.usage) result.usage = {
2370
+ input: response.usage.input_tokens || 0,
2371
+ output: response.usage.output_tokens || 0,
2372
+ cacheRead: response.usage.cache_read_input_tokens
2373
+ };
2374
+ break;
2375
+ }
2376
+ history.push({
2377
+ role: "assistant",
2378
+ content: response.content
2379
+ });
2380
+ const toolResults = [];
2381
+ for (const toolUse of toolUses) {
2382
+ const tool = this.opts.tools?.find((t) => t.name === toolUse.name);
2383
+ this.opts.onToolCall?.(toolUse.name, toolUse.input);
2384
+ let toolResult = "";
2385
+ if (tool) try {
2386
+ toolResult = await tool.handler(toolUse.input);
2387
+ } catch (err) {
2388
+ toolResult = `Error: ${err.message}`;
2389
+ }
2390
+ else toolResult = `Error: Tool "${toolUse.name}" not found`;
2391
+ this.opts.onToolResult?.(toolUse.name, toolResult);
2392
+ result.toolCalls.push({
2393
+ name: toolUse.name,
2394
+ input: toolUse.input,
2395
+ result: toolResult
2396
+ });
2397
+ toolResults.push({
2398
+ type: "tool_result",
2399
+ tool_use_id: toolUse.id,
2400
+ content: toolResult
2401
+ });
2402
+ }
2403
+ history.push({
2404
+ role: "user",
2405
+ content: toolResults
2406
+ });
2407
+ }
2408
+ return result;
2409
+ }
2410
+ callAPI(messages, result) {
2411
+ const { provider } = this.opts;
2412
+ if (provider === "anthropic") return this.callAnthropic(messages, result);
2413
+ if (provider === "custom" && this.opts.baseUrl) return this.callCustom(messages, result);
2414
+ return this.callOpenRouter(messages, result);
2415
+ }
2416
+ callAnthropic(messages, result) {
2417
+ return new Promise((resolve, reject) => {
2418
+ const body = {
2419
+ model: this.opts.model,
2420
+ max_tokens: this.opts.maxTokens || 4096,
2421
+ messages,
2422
+ stream: true
2423
+ };
2424
+ if (this.opts.system) body.system = this.opts.system;
2425
+ if (this.opts.thinking) body.thinking = {
2426
+ type: "enabled",
2427
+ budget_tokens: this.opts.thinking.budget_tokens
2428
+ };
2429
+ if (this.opts.tools?.length) {
2430
+ body.tools = this.opts.tools.map((t) => ({
2431
+ name: t.name,
2432
+ description: t.description,
2433
+ input_schema: t.input_schema
2434
+ }));
2435
+ body.tool_choice = { type: "auto" };
2436
+ }
2437
+ const responseContent = [];
2438
+ let currentBlock = null;
2439
+ let inputJson = "";
2440
+ let stopReason = "end_turn";
2441
+ let usage = {
2442
+ input_tokens: 0,
2443
+ output_tokens: 0
2444
+ };
2445
+ streamRequest("api.anthropic.com", "/v1/messages", {
2446
+ "Authorization": `Bearer ${this.opts.apiKey}`,
2447
+ "anthropic-version": "2023-06-01",
2448
+ "anthropic-beta": "interleaved-thinking-2025-05-14"
2449
+ }, body, (line) => {
2450
+ const event = parseSSEChunk(line);
2451
+ if (!event) return;
2452
+ switch (event.type) {
2453
+ case "content_block_start":
2454
+ currentBlock = { ...event.content_block };
2455
+ inputJson = "";
2456
+ break;
2457
+ case "content_block_delta":
2458
+ if (!currentBlock) break;
2459
+ if (event.delta?.type === "text_delta") {
2460
+ currentBlock.text = (currentBlock.text || "") + event.delta.text;
2461
+ this.opts.onToken?.(event.delta.text);
2462
+ if (currentBlock.type !== "thinking") result.text += event.delta.text;
2463
+ }
2464
+ if (event.delta?.type === "thinking_delta") {
2465
+ currentBlock.thinking = (currentBlock.thinking || "") + event.delta.thinking;
2466
+ this.opts.onThinking?.(event.delta.thinking);
2467
+ }
2468
+ if (event.delta?.type === "input_json_delta") inputJson += event.delta.partial_json;
2469
+ break;
2470
+ case "content_block_stop":
2471
+ if (currentBlock) {
2472
+ if (currentBlock.type === "tool_use" && inputJson) try {
2473
+ currentBlock.input = JSON.parse(inputJson);
2474
+ } catch {}
2475
+ if (currentBlock.type === "text" && currentBlock.text && this.opts.onBlock) emitMarkdownBlocks(currentBlock.text, (type, content, lang) => this.opts.onBlock(type, content, lang));
2476
+ responseContent.push(currentBlock);
2477
+ currentBlock = null;
2478
+ }
2479
+ break;
2480
+ case "message_delta":
2481
+ if (event.delta?.stop_reason) stopReason = event.delta.stop_reason;
2482
+ if (event.usage) usage.output_tokens = event.usage.output_tokens || 0;
2483
+ break;
2484
+ case "message_start":
2485
+ if (event.message?.usage) usage = event.message.usage;
2486
+ break;
2487
+ }
2488
+ }, () => resolve({
2489
+ content: responseContent,
2490
+ stop_reason: stopReason,
2491
+ usage
2492
+ }), reject);
2493
+ });
2494
+ }
2495
+ callOpenRouter(messages, _result) {
2496
+ return new Promise((resolve, reject) => {
2497
+ const oaiMessages = messages.map((m) => {
2498
+ if (m.role === "user" && Array.isArray(m.content)) {
2499
+ const toolResults = m.content.filter((b) => b.type === "tool_result");
2500
+ if (toolResults.length > 0) return toolResults.map((b) => ({
2501
+ role: "tool",
2502
+ tool_call_id: b.tool_use_id,
2503
+ content: b.content
2504
+ }));
2505
+ }
2506
+ if (m.role === "assistant" && Array.isArray(m.content)) {
2507
+ const textBlocks = m.content.filter((b) => b.type === "text");
2508
+ const toolUseBlocks = m.content.filter((b) => b.type === "tool_use");
2509
+ const msg = {
2510
+ role: "assistant",
2511
+ content: textBlocks.map((b) => b.text).join("") || null
2512
+ };
2513
+ if (toolUseBlocks.length > 0) msg.tool_calls = toolUseBlocks.map((b) => ({
2514
+ id: b.id,
2515
+ type: "function",
2516
+ function: {
2517
+ name: b.name,
2518
+ arguments: JSON.stringify(b.input)
2519
+ }
2520
+ }));
2521
+ return msg;
2522
+ }
2523
+ return {
2524
+ role: m.role === "tool" ? "tool" : m.role,
2525
+ content: typeof m.content === "string" ? m.content : m.content.filter((b) => b.type === "text").map((b) => b.text).join("")
2526
+ };
2527
+ }).flat();
2528
+ const body = {
2529
+ model: this.opts.model,
2530
+ max_tokens: this.opts.maxTokens || 4096,
2531
+ messages: this.opts.system ? [{
2532
+ role: "system",
2533
+ content: this.opts.system
2534
+ }, ...oaiMessages] : oaiMessages,
2535
+ stream: true
2536
+ };
2537
+ if (this.opts.tools?.length) {
2538
+ body.tools = this.opts.tools.map((t) => ({
2539
+ type: "function",
2540
+ function: {
2541
+ name: t.name,
2542
+ description: t.description,
2543
+ parameters: t.input_schema
2544
+ }
2545
+ }));
2546
+ body.tool_choice = "auto";
2547
+ }
2548
+ let fullText = "";
2549
+ let stopReason = "stop";
2550
+ const toolCallsAcc = {};
2551
+ streamRequest("openrouter.ai", "/api/v1/chat/completions", {
2552
+ "Authorization": `Bearer ${this.opts.apiKey}`,
2553
+ "HTTP-Referer": "https://hyperclaw.ai",
2554
+ "X-Title": "HyperClaw"
2555
+ }, body, (line) => {
2556
+ const event = parseSSEChunk(line);
2557
+ if (!event?.choices?.[0]) return;
2558
+ const choice = event.choices[0];
2559
+ const delta = choice.delta;
2560
+ if (delta?.content) {
2561
+ fullText += delta.content;
2562
+ this.opts.onToken?.(delta.content);
2563
+ }
2564
+ if (delta?.tool_calls) for (const tc of delta.tool_calls) {
2565
+ const idx = tc.index ?? 0;
2566
+ if (!toolCallsAcc[idx]) toolCallsAcc[idx] = {
2567
+ id: tc.id || `call_${idx}`,
2568
+ name: "",
2569
+ arguments: ""
2570
+ };
2571
+ if (tc.id) toolCallsAcc[idx].id = tc.id;
2572
+ if (tc.function?.name) toolCallsAcc[idx].name += tc.function.name;
2573
+ if (tc.function?.arguments) toolCallsAcc[idx].arguments += tc.function.arguments;
2574
+ }
2575
+ if (choice.finish_reason) stopReason = choice.finish_reason;
2576
+ }, () => {
2577
+ const responseContent = [];
2578
+ if (fullText) responseContent.push({
2579
+ type: "text",
2580
+ text: fullText
2581
+ });
2582
+ for (const tc of Object.values(toolCallsAcc)) {
2583
+ let input = {};
2584
+ try {
2585
+ input = JSON.parse(tc.arguments || "{}");
2586
+ } catch {}
2587
+ this.opts.onToolCall?.(tc.name, input);
2588
+ responseContent.push({
2589
+ type: "tool_use",
2590
+ id: tc.id,
2591
+ name: tc.name,
2592
+ input
2593
+ });
2594
+ }
2595
+ const mappedStop = stopReason === "tool_calls" ? "tool_use" : stopReason;
2596
+ resolve({
2597
+ content: responseContent,
2598
+ stop_reason: mappedStop,
2599
+ usage: {}
2600
+ });
2601
+ }, reject);
2602
+ });
2603
+ }
2604
+ callCustom(messages, _result) {
2605
+ const baseUrl = (this.opts.baseUrl || "").trim().replace(/\/$/, "");
2606
+ if (!baseUrl) return Promise.reject(new Error("Custom provider requires baseUrl"));
2607
+ let hostname;
2608
+ let reqPath;
2609
+ let port;
2610
+ let useHttps;
2611
+ try {
2612
+ const u = new URL(baseUrl.startsWith("http") ? baseUrl : "https://" + baseUrl);
2613
+ hostname = u.hostname;
2614
+ reqPath = (u.pathname || "/").replace(/\/$/, "") + "/chat/completions";
2615
+ if (!reqPath.startsWith("/")) reqPath = "/" + reqPath;
2616
+ useHttps = u.protocol === "https:";
2617
+ port = u.port ? parseInt(u.port, 10) : useHttps ? 443 : 80;
2618
+ } catch {
2619
+ return Promise.reject(new Error("Invalid custom baseUrl"));
2620
+ }
2621
+ return new Promise((resolve, reject) => {
2622
+ const oaiMessages = messages.map((m) => ({
2623
+ role: m.role === "tool" ? "tool" : m.role,
2624
+ content: typeof m.content === "string" ? m.content : m.content.filter((b) => b.type === "text").map((b) => b.text).join("")
2625
+ }));
2626
+ const body = {
2627
+ model: this.opts.model,
2628
+ max_tokens: this.opts.maxTokens || 4096,
2629
+ messages: this.opts.system ? [{
2630
+ role: "system",
2631
+ content: this.opts.system
2632
+ }, ...oaiMessages] : oaiMessages,
2633
+ stream: true
2634
+ };
2635
+ if (this.opts.tools?.length) body.tools = this.opts.tools.map((t) => ({
2636
+ type: "function",
2637
+ function: {
2638
+ name: t.name,
2639
+ description: t.description,
2640
+ parameters: t.input_schema
2641
+ }
2642
+ }));
2643
+ let fullText = "";
2644
+ let stopReason = "stop";
2645
+ streamRequest(hostname, reqPath, {
2646
+ "Authorization": `Bearer ${this.opts.apiKey}`,
2647
+ "HTTP-Referer": "https://hyperclaw.ai",
2648
+ "X-Title": "HyperClaw"
2649
+ }, body, (line) => {
2650
+ const event = parseSSEChunk(line);
2651
+ if (!event?.choices?.[0]) return;
2652
+ const delta = event.choices[0].delta;
2653
+ if (delta?.content) {
2654
+ fullText += delta.content;
2655
+ this.opts.onToken?.(delta.content);
2656
+ }
2657
+ if (event.choices[0].finish_reason) stopReason = event.choices[0].finish_reason;
2658
+ }, () => resolve({
2659
+ content: [{
2660
+ type: "text",
2661
+ text: fullText
2662
+ }],
2663
+ stop_reason: stopReason,
2664
+ usage: {}
2665
+ }), reject, {
2666
+ port,
2667
+ useHttps
2668
+ });
2669
+ });
2670
+ }
2671
+ };
2672
+ } });
2673
+
2674
+ //#endregion
2675
+ Object.defineProperty(exports, 'InferenceEngine', {
2676
+ enumerable: true,
2677
+ get: function () {
2678
+ return InferenceEngine;
2679
+ }
2680
+ });
2681
+ Object.defineProperty(exports, 'getBuiltinTools', {
2682
+ enumerable: true,
2683
+ get: function () {
2684
+ return getBuiltinTools;
2685
+ }
2686
+ });
2687
+ Object.defineProperty(exports, 'init_inference', {
2688
+ enumerable: true,
2689
+ get: function () {
2690
+ return init_inference;
2691
+ }
2692
+ });