hyperclaw 5.0.1 → 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 (137) hide show
  1. package/LICENSE +2 -1
  2. package/README.md +247 -32
  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-ChbThbPj.js +149 -0
  7. package/dist/audit-NPIMmOSq.js +441 -0
  8. package/dist/bounty-tools-BUqUKjt0.js +211 -0
  9. package/dist/browser-tools-CxJY6pAn.js +5 -0
  10. package/dist/browser-tools-JZ9ji6AW.js +179 -0
  11. package/dist/chat-qVuqhlPu.js +258 -0
  12. package/dist/claw-tasks-B-8RRMdq.js +80 -0
  13. package/dist/connector-1x1rCBHz.js +162 -0
  14. package/dist/connector-B4jeCULG.js +305 -0
  15. package/dist/connector-B7qngfkT.js +286 -0
  16. package/dist/connector-B8BK0GBo.js +531 -0
  17. package/dist/connector-BE9eJs8-.js +182 -0
  18. package/dist/connector-BEe-DTGQ.js +189 -0
  19. package/dist/connector-BU7p5ZgB.js +167 -0
  20. package/dist/connector-BUzzq7Ij.js +568 -0
  21. package/dist/connector-BpDqLgnW.js +419 -0
  22. package/dist/connector-BpW88ut2.js +189 -0
  23. package/dist/connector-Bxv-gy8U.js +167 -0
  24. package/dist/connector-Bz14zcJv.js +213 -0
  25. package/dist/connector-C1zP5-5q.js +85 -0
  26. package/dist/connector-CAcpcovF.js +498 -0
  27. package/dist/connector-CJgVjS58.js +181 -0
  28. package/dist/connector-Cf53D6qV.js +425 -0
  29. package/dist/connector-CyHmlbNz.js +508 -0
  30. package/dist/connector-D22mJGVu.js +340 -0
  31. package/dist/connector-D6RtMmlL.js +225 -0
  32. package/dist/connector-D9EnT8A4.js +280 -0
  33. package/dist/connector-DNDwIh37.js +239 -0
  34. package/dist/connector-Di27MeO4.js +350 -0
  35. package/dist/connector-Do0BPiHt.js +194 -0
  36. package/dist/connector-DvLwOfJy.js +192 -0
  37. package/dist/connector-DvU83NSq.js +181 -0
  38. package/dist/connector-DxskpDc_.js +173 -0
  39. package/dist/connector-byy3eISx.js +552 -0
  40. package/dist/connector-vV89hsyd.js +218 -0
  41. package/dist/cost-tracker-fnaj_6M9.js +103 -0
  42. package/dist/credentials-store-BxijEirw.js +77 -0
  43. package/dist/credentials-store-CPkVO6-z.js +4 -0
  44. package/dist/cron-tasks-L0mz1yyU.js +82 -0
  45. package/dist/daemon-CNyunwkR.js +5 -0
  46. package/dist/daemon-CindY8OK.js +318 -0
  47. package/dist/delivery-DgiZcJBp.js +4 -0
  48. package/dist/delivery-otAU4alM.js +95 -0
  49. package/dist/destructive-gate-CA0DtA5K.js +101 -0
  50. package/dist/developer-keys-Cnd1kswV.js +127 -0
  51. package/dist/developer-keys-DENo3ZA6.js +8 -0
  52. package/dist/doctor-Dgjoc3DG.js +230 -0
  53. package/dist/doctor-RwsOhtAl.js +6 -0
  54. package/dist/engine-D_VeoZHw.js +305 -0
  55. package/dist/engine-JjRnhlsE.js +7 -0
  56. package/dist/env-resolve-BFJXWl94.js +115 -0
  57. package/dist/env-resolve-bDYssfih.js +10 -0
  58. package/dist/extraction-tools-DbxnxIco.js +5 -0
  59. package/dist/extraction-tools-Dg7AHS35.js +91 -0
  60. package/dist/form_data-CGAy4HE0.js +8657 -0
  61. package/dist/gmail-watch-setup-C3uSWznp.js +40 -0
  62. package/dist/health-DUjluWHQ.js +6 -0
  63. package/dist/health-DVfkpUQW.js +152 -0
  64. package/dist/heartbeat-engine-CrgL4mrP.js +83 -0
  65. package/dist/hub-BO6bj8Yj.js +515 -0
  66. package/dist/hub-Bu52YZqW.js +6 -0
  67. package/dist/hyperclawbot-BrcoYLOp.js +505 -0
  68. package/dist/inference-DHR82Gh7.js +6 -0
  69. package/dist/inference-DhA8jpfH.js +2692 -0
  70. package/dist/knowledge-graph-BrYpSgxW.js +131 -0
  71. package/dist/loader-9JqY6Nlq.js +4 -0
  72. package/dist/loader-Cjdd1kw4.js +400 -0
  73. package/dist/logger-DCT2l9GV.js +83 -0
  74. package/dist/manager-3cq3DydI.js +4 -0
  75. package/dist/manager-BUrFrPuq.js +117 -0
  76. package/dist/manager-Bi9UYyVR.js +105 -0
  77. package/dist/manager-Biz9ixWJ.js +40 -0
  78. package/dist/manager-CBUHJiY7.js +6 -0
  79. package/dist/manager-CVLLaKmq.js +218 -0
  80. package/dist/mcp-CUoTCMw-.js +139 -0
  81. package/dist/mcp-loader-BIz-450x.js +94 -0
  82. package/dist/memory-OL77OMOr.js +270 -0
  83. package/dist/memory-auto-D-L2q21G.js +306 -0
  84. package/dist/memory-auto-DTcy5VBy.js +5 -0
  85. package/dist/memory-gUi4VaIf.js +4 -0
  86. package/dist/memory-integration-B8RSN4pr.js +91 -0
  87. package/dist/moltbook-B-40gQOL.js +81 -0
  88. package/dist/node-TWxRm84k.js +222 -0
  89. package/dist/nodes-registry-DKRtsbNg.js +52 -0
  90. package/dist/oauth-flow-JCfporKq.js +150 -0
  91. package/dist/oauth-provider-4R0EJlsT.js +110 -0
  92. package/dist/observability-CDZmeHfa.js +89 -0
  93. package/dist/onboard-BVOtKQdh.js +3641 -0
  94. package/dist/onboard-CGNIw27w.js +11 -0
  95. package/dist/orchestrator-CcKx1Ovk.js +189 -0
  96. package/dist/orchestrator-DcFfDLTX.js +6 -0
  97. package/dist/osint-B6BZKQAD.js +277 -0
  98. package/dist/pairing-B6RArWhD.js +196 -0
  99. package/dist/pairing-BsQ08DLq.js +4 -0
  100. package/dist/pc-access-B0KocJNe.js +819 -0
  101. package/dist/pc-access-DkzmugZ7.js +8 -0
  102. package/dist/pending-approval-C_HkX1QL.js +22 -0
  103. package/dist/providers-DxiamZSL.js +5 -0
  104. package/dist/providers-Dy15rDb7.js +657 -0
  105. package/dist/reminders-store-CzUY0zYx.js +58 -0
  106. package/dist/renderer-ANNfXsHn.js +225 -0
  107. package/dist/rules-BSQwwAYC.js +103 -0
  108. package/dist/run-main.js +112 -117
  109. package/dist/runner-BHRSOPEU.js +1271 -0
  110. package/dist/security--oQObeJO.js +4 -0
  111. package/dist/security-wBOg0TA8.js +73 -0
  112. package/dist/server-CbTTpB5m.js +1255 -0
  113. package/dist/server-DP_bPzvI.js +4 -0
  114. package/dist/session-store-B09r5HgB.js +5 -0
  115. package/dist/session-store-DCTQIVur.js +113 -0
  116. package/dist/sessions-tools-BdlN6Pb6.js +95 -0
  117. package/dist/sessions-tools-JVLDKSJ_.js +5 -0
  118. package/dist/skill-loader-B5oeliGu.js +7 -0
  119. package/dist/skill-loader-Wf3brNOj.js +160 -0
  120. package/dist/skill-runtime-BGlvly2s.js +102 -0
  121. package/dist/skill-runtime-DhL2T76p.js +5 -0
  122. package/dist/src-BbPa6Q8p.js +63 -0
  123. package/dist/src-BeXtfkK2.js +458 -0
  124. package/dist/src-CGQjRI4N.js +20 -0
  125. package/dist/sub-agent-tools-CmE345s_.js +39 -0
  126. package/dist/theme-D0smfC_l.js +8 -0
  127. package/dist/theme-DajRRZbA.js +180 -0
  128. package/dist/tool-policy-DgNqFWYn.js +189 -0
  129. package/dist/tts-elevenlabs-JeFaGNJU.js +61 -0
  130. package/dist/update-check-BVEqHhFY.js +83 -0
  131. package/dist/vision-fky3elEo.js +121 -0
  132. package/dist/vision-tools-C8B3776g.js +5 -0
  133. package/dist/vision-tools-dwn9p4el.js +51 -0
  134. package/dist/voice-transcription-B6RtplmN.js +138 -0
  135. package/dist/website-watch-tools-B-jRAeTe.js +139 -0
  136. package/dist/website-watch-tools-BC9xAL67.js +5 -0
  137. package/package.json +1 -1
@@ -0,0 +1,568 @@
1
+ const require_chunk = require('./chunk-jS-bbMI5.js');
2
+ const chalk = require_chunk.__toESM(require("chalk"));
3
+ const fs_extra = require_chunk.__toESM(require("fs-extra"));
4
+ const path = require_chunk.__toESM(require("path"));
5
+ const os = require_chunk.__toESM(require("os"));
6
+ const http = require_chunk.__toESM(require("http"));
7
+ const https = require_chunk.__toESM(require("https"));
8
+ const events = require_chunk.__toESM(require("events"));
9
+
10
+ //#region extensions/tlon/src/connector.ts
11
+ const STATE_DIR = path.default.join(os.default.homedir(), ".hyperclaw");
12
+ const STATE_FILE = path.default.join(STATE_DIR, "tlon-state.json");
13
+ const PAIRING_EXPIRY_MS = 60 * 60 * 1e3;
14
+ const SSE_RECONNECT_DELAY_MS = 5e3;
15
+ const PAIRING_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
16
+ const PRIVATE_RANGES = [
17
+ /^127\./,
18
+ /^10\./,
19
+ /^192\.168\./,
20
+ /^172\.(1[6-9]|2\d|3[01])\./,
21
+ /^169\.254\./,
22
+ /^::1$/,
23
+ /^fc[0-9a-f]{2}:/i,
24
+ /^fd[0-9a-f]{2}:/i
25
+ ];
26
+ function isPrivateHost(hostname) {
27
+ return PRIVATE_RANGES.some((r) => r.test(hostname)) || hostname === "localhost";
28
+ }
29
+ function checkSsrf(shipUrl, allowPrivate) {
30
+ if (allowPrivate) return;
31
+ try {
32
+ const { hostname } = new URL(shipUrl);
33
+ if (isPrivateHost(hostname)) throw new Error(`Tlon: ship URL "${shipUrl}" resolves to a private/local address. Set channels.tlon.allowPrivateNetwork: true to allow this (SSRF opt-in).`);
34
+ } catch (e) {
35
+ if (e.message.startsWith("Tlon:")) throw e;
36
+ }
37
+ }
38
+ function markdownToInlines(text) {
39
+ const result = [];
40
+ const parts = text.split(/(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`|~[a-z-]+)/g);
41
+ for (const part of parts) if (part.startsWith("**") && part.endsWith("**")) result.push({ bold: [{ text: part.slice(2, -2) }] });
42
+ else if (part.startsWith("*") && part.endsWith("*")) result.push({ italics: [{ text: part.slice(1, -1) }] });
43
+ else if (part.startsWith("`") && part.endsWith("`")) result.push({ code: part.slice(1, -1) });
44
+ else if (/^~[a-z-]+$/.test(part)) result.push({ ship: part });
45
+ else if (part) result.push({ text: part });
46
+ return result;
47
+ }
48
+ function textToVerseBlock(text) {
49
+ const lines = text.split("\n");
50
+ const verses = [];
51
+ for (const line of lines) if (line.startsWith("# ")) verses.push({ verse: { block: [{ header: {
52
+ tag: "h1",
53
+ content: line.slice(2)
54
+ } }] } });
55
+ else if (line.startsWith("## ")) verses.push({ verse: { block: [{ header: {
56
+ tag: "h2",
57
+ content: line.slice(3)
58
+ } }] } });
59
+ else if (line.startsWith("- ") || line.startsWith("* ")) verses.push({ verse: { block: [{ listing: { item: line.slice(2) } }] } });
60
+ else if (line.startsWith("```")) verses.push({ verse: { block: [{ code: {
61
+ code: line.slice(3),
62
+ lang: ""
63
+ } }] } });
64
+ else if (line.trim() === "") verses.push({ verse: { inline: [{ break: null }] } });
65
+ else verses.push({ verse: { inline: markdownToInlines(line) } });
66
+ return { story: verses };
67
+ }
68
+ function urbitReq(shipUrl, method, pathname, cookie, body, contentType) {
69
+ return new Promise((resolve, reject) => {
70
+ const url = new URL(pathname, shipUrl);
71
+ const isHttps = url.protocol === "https:";
72
+ const mod = isHttps ? https.default : http.default;
73
+ const headers = {};
74
+ if (cookie) headers["Cookie"] = cookie;
75
+ if (body) {
76
+ headers["Content-Type"] = contentType ?? "application/json";
77
+ headers["Content-Length"] = Buffer.byteLength(body);
78
+ }
79
+ const req = mod.request({
80
+ hostname: url.hostname,
81
+ port: url.port || (isHttps ? 443 : 80),
82
+ path: url.pathname + (url.search || ""),
83
+ method,
84
+ headers
85
+ }, (res) => {
86
+ const sc = res.headers["set-cookie"]?.[0]?.split(";")[0] ?? null;
87
+ let data = "";
88
+ res.on("data", (c) => data += c.toString());
89
+ res.on("end", () => resolve({
90
+ statusCode: res.statusCode ?? 200,
91
+ setCookie: sc,
92
+ body: data
93
+ }));
94
+ });
95
+ req.on("error", reject);
96
+ if (body) req.write(body);
97
+ req.end();
98
+ });
99
+ }
100
+ var TlonConnector = class extends events.EventEmitter {
101
+ cfg;
102
+ cookie = null;
103
+ channelUid = null;
104
+ running = false;
105
+ lastEventId = 0;
106
+ constructor(rawCfg) {
107
+ super();
108
+ this.cfg = {
109
+ dmPolicy: "pairing",
110
+ allowPrivateNetwork: false,
111
+ dmAllowlist: [],
112
+ autoAcceptDmInvites: true,
113
+ autoAcceptGroupInvites: false,
114
+ autoDiscoverChannels: true,
115
+ groupChannels: [],
116
+ defaultAuthorizedShips: [],
117
+ authorization: {},
118
+ showModelSignature: false,
119
+ mediaMaxMb: 20,
120
+ requireMention: true,
121
+ ownerShip: "",
122
+ _approvedPairings: [],
123
+ _pendingPairings: {},
124
+ _knownChannels: [],
125
+ ...rawCfg
126
+ };
127
+ }
128
+ async connect() {
129
+ checkSsrf(this.cfg.url, this.cfg.allowPrivateNetwork);
130
+ await this.loadState();
131
+ await this.login();
132
+ await this.openChannel();
133
+ await this.subscribeAll();
134
+ this.running = true;
135
+ console.log(chalk.default.green(` 🦅 Tlon: connected as ${this.cfg.ship} → ${this.cfg.url}`));
136
+ this.emit("connected", {
137
+ ship: this.cfg.ship,
138
+ url: this.cfg.url
139
+ });
140
+ this.listenSSE();
141
+ }
142
+ disconnect() {
143
+ this.running = false;
144
+ }
145
+ isRunning() {
146
+ return this.running;
147
+ }
148
+ async login() {
149
+ const body = `password=${encodeURIComponent(this.cfg.code)}`;
150
+ const r = await urbitReq(this.cfg.url, "POST", "/~/login", null, body, "application/x-www-form-urlencoded");
151
+ if (!r.setCookie) throw new Error(`Tlon: login failed — check URL and code (HTTP ${r.statusCode})`);
152
+ this.cookie = r.setCookie;
153
+ }
154
+ async openChannel() {
155
+ this.channelUid = `hyperclaw-${Date.now()}`;
156
+ await this.urbitPut([]);
157
+ }
158
+ async subscribeAll() {
159
+ const actions = [];
160
+ actions.push(this.subscribeAction(this.cfg.ship, "chat", "/dm"));
161
+ const nests = [...this.cfg.groupChannels ?? []];
162
+ if (this.cfg.autoDiscoverChannels) {
163
+ const discovered = await this.discoverChannels();
164
+ for (const n of discovered) if (!nests.includes(n)) nests.push(n);
165
+ }
166
+ this.cfg._knownChannels = nests;
167
+ for (const nest of nests) {
168
+ const parts = nest.split("/");
169
+ if (parts.length >= 3) {
170
+ const [, hostShip] = parts;
171
+ actions.push(this.subscribeAction(hostShip ?? this.cfg.ship, "chat", `/${nest}`));
172
+ }
173
+ }
174
+ if (actions.length > 0) await this.urbitPut(actions);
175
+ }
176
+ subscribeAction(ship, app, subscriptionPath) {
177
+ return {
178
+ id: ++this.lastEventId,
179
+ action: "subscribe",
180
+ ship,
181
+ app,
182
+ path: subscriptionPath
183
+ };
184
+ }
185
+ /** Discover group channels by fetching the groups list from the ship. */
186
+ async discoverChannels() {
187
+ try {
188
+ const r = await urbitReq(this.cfg.url, "GET", "/~/scry/groups/groups.json", this.cookie);
189
+ if (r.statusCode !== 200) return [];
190
+ const data = JSON.parse(r.body);
191
+ const nests = [];
192
+ for (const groupData of Object.values(data)) {
193
+ const channels = groupData?.channels ?? {};
194
+ for (const nest of Object.keys(channels)) nests.push(nest);
195
+ }
196
+ return nests;
197
+ } catch {
198
+ return [];
199
+ }
200
+ }
201
+ listenSSE() {
202
+ const url = new URL(`/~/channel/${this.channelUid}`, this.cfg.url);
203
+ const isHttps = url.protocol === "https:";
204
+ const mod = isHttps ? https.default : http.default;
205
+ const doConnect = () => {
206
+ if (!this.running) return;
207
+ const req = mod.request({
208
+ hostname: url.hostname,
209
+ port: url.port || (isHttps ? 443 : 80),
210
+ path: url.pathname,
211
+ method: "GET",
212
+ headers: {
213
+ Cookie: this.cookie,
214
+ Accept: "text/event-stream",
215
+ "Cache-Control": "no-cache"
216
+ }
217
+ }, (res) => {
218
+ let buf = "";
219
+ res.on("data", (chunk) => {
220
+ buf += chunk.toString();
221
+ const lines = buf.split("\n");
222
+ buf = lines.pop() ?? "";
223
+ let dataLine = "";
224
+ for (const line of lines) if (line.startsWith("data:")) dataLine = line.slice(5).trim();
225
+ else if (line === "" && dataLine) {
226
+ try {
227
+ this.handleEvent(JSON.parse(dataLine));
228
+ } catch {}
229
+ dataLine = "";
230
+ }
231
+ });
232
+ res.on("end", () => {
233
+ if (this.running) setTimeout(doConnect, SSE_RECONNECT_DELAY_MS);
234
+ });
235
+ res.on("error", () => {
236
+ if (this.running) setTimeout(doConnect, SSE_RECONNECT_DELAY_MS);
237
+ });
238
+ });
239
+ req.on("error", () => {
240
+ if (this.running) setTimeout(doConnect, SSE_RECONNECT_DELAY_MS);
241
+ });
242
+ req.end();
243
+ };
244
+ doConnect();
245
+ }
246
+ async handleEvent(event) {
247
+ if (event.id) this.urbitDelete(event.id).catch(() => {});
248
+ const resp = event.response;
249
+ if (!resp || resp === "poke" || resp === "subscribe") return;
250
+ if (resp !== "diff") return;
251
+ const json = event.json;
252
+ if (!json) return;
253
+ const dmWrite = json["chat-dm-write"] ?? json["dm-update"]?.add?.message;
254
+ if (dmWrite) {
255
+ await this.handleDmWrite(dmWrite, json);
256
+ return;
257
+ }
258
+ const chatWrite = json["chat-write"] ?? json["chat-update"]?.post;
259
+ if (chatWrite) {
260
+ await this.handleGroupWrite(chatWrite, json);
261
+ return;
262
+ }
263
+ if (json["group-invite"] || json["dm-invite"]) await this.handleInvite(json);
264
+ }
265
+ async handleDmWrite(msg, _raw) {
266
+ const sender = msg.memo?.author ?? msg.ship ?? "unknown";
267
+ if (sender === this.cfg.ship) return;
268
+ const text = this.extractText(msg.memo?.content ?? msg.content ?? {});
269
+ if (!text) return;
270
+ const isDMAllowed = await this.checkDmPolicy(sender, text);
271
+ if (!isDMAllowed) return;
272
+ const chatId = `tlon:dm:${sender}`;
273
+ this.emit("message", {
274
+ id: `tlon-${Date.now()}`,
275
+ channelId: "tlon",
276
+ from: sender,
277
+ chatId,
278
+ text,
279
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
280
+ isDM: true,
281
+ threadId: msg.memo?.replyTo ?? void 0
282
+ });
283
+ }
284
+ async handleGroupWrite(msg, raw) {
285
+ const nest = msg.nest ?? raw.nest ?? "";
286
+ const sender = msg.memo?.author ?? msg.post?.author ?? "unknown";
287
+ if (sender === this.cfg.ship) return;
288
+ const text = this.extractText(msg.memo?.content ?? msg.post?.content ?? {});
289
+ if (!text) return;
290
+ if (this.cfg.requireMention && !text.includes(this.cfg.ship)) return;
291
+ const allowed = this.isAuthorizedForChannel(sender, nest);
292
+ if (!allowed) {
293
+ if (this.cfg.ownerShip) await this.sendDM(this.cfg.ownerShip, `🦅 Unauthorized mention in ${nest} from ${sender}`).catch(() => {});
294
+ return;
295
+ }
296
+ const chatId = `tlon:group:${nest}`;
297
+ this.emit("message", {
298
+ id: `tlon-${Date.now()}`,
299
+ channelId: "tlon",
300
+ from: sender,
301
+ chatId,
302
+ nest,
303
+ text,
304
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
305
+ isDM: false,
306
+ threadId: msg.memo?.replyTo ?? void 0
307
+ });
308
+ }
309
+ async handleInvite(json) {
310
+ if (json["dm-invite"]) {
311
+ const fromShip = json["dm-invite"].ship ?? "";
312
+ const isAllowed = this.cfg.dmAllowlist?.includes(fromShip) || fromShip === this.cfg.ownerShip;
313
+ if (isAllowed && this.cfg.autoAcceptDmInvites) {
314
+ await this.pokeChatDm(fromShip, { accept: null }).catch(() => {});
315
+ console.log(chalk.default.gray(` Tlon: auto-accepted DM invite from ${fromShip}`));
316
+ } else if (this.cfg.ownerShip) await this.sendDM(this.cfg.ownerShip, `🦅 DM invite from ${fromShip} — approve with: hyperclaw pairing approve tlon ${fromShip}`).catch(() => {});
317
+ }
318
+ if (json["group-invite"] && this.cfg.autoAcceptGroupInvites) {
319
+ const groupRef = json["group-invite"].group ?? "";
320
+ await this.pokeGroups(groupRef, { join: null }).catch(() => {});
321
+ console.log(chalk.default.gray(` Tlon: auto-accepted group invite for ${groupRef}`));
322
+ }
323
+ }
324
+ async checkDmPolicy(ship, text) {
325
+ const cfg = this.cfg;
326
+ if (cfg.ownerShip && ship === cfg.ownerShip) return true;
327
+ const policy = cfg.dmPolicy ?? "pairing";
328
+ if (policy === "disabled") return false;
329
+ if (policy === "open") return true;
330
+ if (policy === "allowlist") {
331
+ if (cfg.dmAllowlist?.includes(ship)) return true;
332
+ if (cfg.ownerShip) await this.sendDM(cfg.ownerShip, `🦅 DM request from ${ship} (not in allowlist)`).catch(() => {});
333
+ return false;
334
+ }
335
+ if (policy === "pairing") {
336
+ const now = Date.now();
337
+ for (const [code$1, entry$1] of Object.entries(cfg._pendingPairings)) if (entry$1.expiresAt < now) delete cfg._pendingPairings[code$1];
338
+ if (cfg._approvedPairings.includes(ship) || cfg.dmAllowlist?.includes(ship)) return true;
339
+ const upper = text.trim().toUpperCase();
340
+ const entry = Object.entries(cfg._pendingPairings).find(([code$1]) => code$1 === upper);
341
+ if (entry && entry[1].ship === ship) {
342
+ cfg._approvedPairings.push(ship);
343
+ delete cfg._pendingPairings[upper];
344
+ await this.saveState();
345
+ await this.sendDM(ship, "🦅 Paired! You can now chat with the assistant.");
346
+ this.emit("pairing:approved", { ship });
347
+ return false;
348
+ }
349
+ const code = Array.from({ length: 6 }, () => PAIRING_CHARS[Math.floor(Math.random() * PAIRING_CHARS.length)]).join("");
350
+ cfg._pendingPairings[code] = {
351
+ ship,
352
+ expiresAt: now + PAIRING_EXPIRY_MS
353
+ };
354
+ await this.saveState();
355
+ await this.sendDM(ship, `🦅 Pairing required. Code: **${code}**\nApprove: \`hyperclaw pairing approve tlon ${code}\`\n(Expires in 1 hour)`).catch(() => {});
356
+ if (cfg.ownerShip) await this.sendDM(cfg.ownerShip, `🦅 DM pairing request from ${ship} — code: ${code}`).catch(() => {});
357
+ return false;
358
+ }
359
+ return false;
360
+ }
361
+ isAuthorizedForChannel(ship, nest) {
362
+ if (this.cfg.ownerShip && ship === this.cfg.ownerShip) return true;
363
+ const rule = this.cfg.authorization?.channelRules?.[nest];
364
+ if (rule) {
365
+ if (rule.mode === "open") return true;
366
+ if (rule.mode === "restricted") return rule.allowedShips?.includes(ship) ?? false;
367
+ }
368
+ if (this.cfg.defaultAuthorizedShips?.includes(ship)) return true;
369
+ if (this.cfg.defaultAuthorizedShips?.includes("*")) return true;
370
+ return false;
371
+ }
372
+ extractText(content) {
373
+ if (!content) return "";
374
+ if (typeof content === "string") return content;
375
+ if (content.story && Array.isArray(content.story)) return content.story.map((verse) => {
376
+ const inline = verse?.verse?.inline;
377
+ const block = verse?.verse?.block;
378
+ if (Array.isArray(inline)) return this.inlinesToText(inline);
379
+ if (Array.isArray(block)) return this.blocksToText(block);
380
+ return "";
381
+ }).filter(Boolean).join("\n");
382
+ return "";
383
+ }
384
+ inlinesToText(inlines) {
385
+ return inlines.map((i) => {
386
+ if (typeof i === "string") return i;
387
+ if (i?.text) return i.text;
388
+ if (i?.bold) return this.inlinesToText(i.bold);
389
+ if (i?.italics) return this.inlinesToText(i.italics);
390
+ if (i?.code) return `\`${i.code}\``;
391
+ if (i?.ship) return i.ship;
392
+ if (i?.link) return i.link.content ?? i.link.href;
393
+ if (i?.break !== void 0) return "\n";
394
+ return "";
395
+ }).join("");
396
+ }
397
+ blocksToText(blocks) {
398
+ return blocks.map((b) => {
399
+ if (b?.header?.content) return b.header.content;
400
+ if (b?.listing?.item) return `- ${b.listing.item}`;
401
+ if (b?.code?.code) return `\`\`\`${b.code.code}\`\`\``;
402
+ if (b?.image?.src) return `[image: ${b.image.src}]`;
403
+ return "";
404
+ }).filter(Boolean).join("\n");
405
+ }
406
+ /** Send a DM to a Urbit ship. Converts Markdown to Tlon verse blocks. */
407
+ async sendDM(toShip, text) {
408
+ const content = textToVerseBlock(this.applySignature(text));
409
+ await this.pokeChatDm(toShip, { message: { memo: {
410
+ content,
411
+ author: this.cfg.ship,
412
+ sent: Date.now()
413
+ } } });
414
+ }
415
+ /** Send a message to a group channel nest (e.g. "chat/~host/name"). */
416
+ async sendToNest(nest, text, replyTo) {
417
+ const content = textToVerseBlock(this.applySignature(text));
418
+ const memo = {
419
+ content,
420
+ author: this.cfg.ship,
421
+ sent: Date.now()
422
+ };
423
+ if (replyTo) memo.replyTo = replyTo;
424
+ const parts = nest.split("/");
425
+ const hostShip = parts[1] ?? this.cfg.ship;
426
+ await this.urbitPut([{
427
+ id: ++this.lastEventId,
428
+ action: "poke",
429
+ ship: hostShip,
430
+ app: "chat",
431
+ mark: "chat-action-0",
432
+ json: { write: {
433
+ nest,
434
+ add: { memo }
435
+ } }
436
+ }]);
437
+ }
438
+ /** Send by delivery target string.
439
+ * Formats: ~ship, dm/~ship, chat/~host/channel, group:~host/name
440
+ */
441
+ async sendToTarget(target, text) {
442
+ const t = target.trim();
443
+ if (t.startsWith("dm/") || t.startsWith("~")) {
444
+ const ship = t.startsWith("dm/") ? t.slice(3) : t;
445
+ return this.sendDM(ship, text);
446
+ }
447
+ if (t.startsWith("chat/") || t.startsWith("group:")) {
448
+ const nest = t.startsWith("group:") ? `chat/${t.slice(6)}` : t;
449
+ return this.sendToNest(nest, text);
450
+ }
451
+ return this.sendDM(t, text);
452
+ }
453
+ async addReaction(nest, postId, emoji) {
454
+ const parts = nest.split("/");
455
+ const hostShip = parts[1] ?? this.cfg.ship;
456
+ await this.urbitPut([{
457
+ id: ++this.lastEventId,
458
+ action: "poke",
459
+ ship: hostShip,
460
+ app: "chat",
461
+ mark: "chat-action-0",
462
+ json: { react: {
463
+ nest,
464
+ postId,
465
+ react: emoji
466
+ } }
467
+ }]).catch(() => {});
468
+ }
469
+ async removeReaction(nest, postId, emoji) {
470
+ const parts = nest.split("/");
471
+ const hostShip = parts[1] ?? this.cfg.ship;
472
+ await this.urbitPut([{
473
+ id: ++this.lastEventId,
474
+ action: "poke",
475
+ ship: hostShip,
476
+ app: "chat",
477
+ mark: "chat-action-0",
478
+ json: { react: {
479
+ nest,
480
+ postId,
481
+ react: emoji === "" ? null : emoji
482
+ } }
483
+ }]).catch(() => {});
484
+ }
485
+ approvePairing(code) {
486
+ const upper = code.toUpperCase();
487
+ const entry = this.cfg._pendingPairings[upper];
488
+ if (!entry) return false;
489
+ if (Date.now() > entry.expiresAt) {
490
+ delete this.cfg._pendingPairings[upper];
491
+ this.saveState();
492
+ return false;
493
+ }
494
+ this.cfg._approvedPairings.push(entry.ship);
495
+ delete this.cfg._pendingPairings[upper];
496
+ this.saveState();
497
+ return true;
498
+ }
499
+ listPendingPairings() {
500
+ const now = Date.now();
501
+ const result = {};
502
+ for (const [code, entry] of Object.entries(this.cfg._pendingPairings)) if (entry.expiresAt > now) result[code] = entry.ship;
503
+ return result;
504
+ }
505
+ async urbitPut(actions) {
506
+ const body = JSON.stringify(actions);
507
+ await urbitReq(this.cfg.url, "PUT", `/~/channel/${this.channelUid}`, this.cookie, body);
508
+ }
509
+ async urbitDelete(eventId) {
510
+ await urbitReq(this.cfg.url, "DELETE", `/~/channel/${this.channelUid}/${eventId}`, this.cookie);
511
+ }
512
+ async pokeChatDm(toShip, dmAction) {
513
+ await this.urbitPut([{
514
+ id: ++this.lastEventId,
515
+ action: "poke",
516
+ ship: this.cfg.ship,
517
+ app: "chat",
518
+ mark: "chat-dm-action",
519
+ json: {
520
+ ship: toShip,
521
+ ...dmAction
522
+ }
523
+ }]);
524
+ }
525
+ async pokeGroups(groupRef, action) {
526
+ await this.urbitPut([{
527
+ id: ++this.lastEventId,
528
+ action: "poke",
529
+ ship: this.cfg.ship,
530
+ app: "groups",
531
+ mark: "group-action-0",
532
+ json: {
533
+ group: groupRef,
534
+ ...action
535
+ }
536
+ }]);
537
+ }
538
+ async loadState() {
539
+ try {
540
+ const s = await fs_extra.default.readJson(STATE_FILE);
541
+ if (Array.isArray(s.approved)) this.cfg._approvedPairings = s.approved;
542
+ if (s.pending && typeof s.pending === "object") this.cfg._pendingPairings = s.pending;
543
+ if (Array.isArray(s.knownChannels)) this.cfg._knownChannels = s.knownChannels;
544
+ } catch {}
545
+ }
546
+ async saveState() {
547
+ await fs_extra.default.ensureDir(STATE_DIR);
548
+ await fs_extra.default.writeJson(STATE_FILE, {
549
+ approved: this.cfg._approvedPairings,
550
+ pending: this.cfg._pendingPairings,
551
+ knownChannels: this.cfg._knownChannels
552
+ }, { spaces: 2 });
553
+ }
554
+ applySignature(text) {
555
+ if (!this.cfg.showModelSignature) return text;
556
+ return text;
557
+ }
558
+ /** Get all known channel nests (for CLI/cron delivery targets). */
559
+ getKnownChannels() {
560
+ return this.cfg._knownChannels;
561
+ }
562
+ getApprovedShips() {
563
+ return this.cfg._approvedPairings;
564
+ }
565
+ };
566
+
567
+ //#endregion
568
+ exports.TlonConnector = TlonConnector;