pubblue 0.4.5 → 0.4.10

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.
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,305 @@
1
+ import {
2
+ ipcCall
3
+ } from "./chunk-HJ5LTUHS.js";
4
+ import {
5
+ CHANNELS,
6
+ generateMessageId
7
+ } from "./chunk-MW35LBNH.js";
8
+
9
+ // src/lib/tunnel-bridge-openclaw.ts
10
+ import { execFile, execFileSync } from "child_process";
11
+ import { existsSync, readFileSync, writeFileSync } from "fs";
12
+ import { homedir } from "os";
13
+ import { join } from "path";
14
+ import { promisify } from "util";
15
+ var execFileAsync = promisify(execFile);
16
+ var OPENCLAW_DISCOVERY_PATHS = [
17
+ "/app/dist/index.js",
18
+ join(homedir(), "openclaw", "dist", "index.js"),
19
+ join(homedir(), ".openclaw", "openclaw"),
20
+ "/usr/local/bin/openclaw",
21
+ "/opt/homebrew/bin/openclaw"
22
+ ];
23
+ function sleep(ms) {
24
+ return new Promise((resolve) => {
25
+ setTimeout(resolve, ms);
26
+ });
27
+ }
28
+ function readSessionIdFromOpenClaw(threadId) {
29
+ try {
30
+ const sessionsPath = join(
31
+ homedir(),
32
+ ".openclaw",
33
+ "agents",
34
+ "main",
35
+ "sessions",
36
+ "sessions.json"
37
+ );
38
+ const sessions = JSON.parse(readFileSync(sessionsPath, "utf-8"));
39
+ if (threadId && threadId.length > 0) {
40
+ const byThread = sessions[`agent:main:main:thread:${threadId}`];
41
+ if (typeof byThread?.sessionId === "string" && byThread.sessionId.length > 0) {
42
+ return byThread.sessionId;
43
+ }
44
+ }
45
+ const main = sessions["agent:main:main"];
46
+ if (typeof main?.sessionId === "string" && main.sessionId.length > 0) {
47
+ return main.sessionId;
48
+ }
49
+ return null;
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+ function resolveOpenClawPath() {
55
+ const configuredPath = process.env.OPENCLAW_PATH;
56
+ if (configuredPath) {
57
+ if (!existsSync(configuredPath)) {
58
+ throw new Error(`OPENCLAW_PATH does not exist: ${configuredPath}`);
59
+ }
60
+ return configuredPath;
61
+ }
62
+ try {
63
+ const which = execFileSync("which", ["openclaw"], { timeout: 5e3 }).toString().trim();
64
+ if (which.length > 0 && existsSync(which)) {
65
+ return which;
66
+ }
67
+ } catch {
68
+ }
69
+ for (const candidate of OPENCLAW_DISCOVERY_PATHS) {
70
+ if (existsSync(candidate)) return candidate;
71
+ }
72
+ throw new Error(
73
+ [
74
+ "OpenClaw executable was not found.",
75
+ "Configure it with: pubblue configure --set openclaw.path=/absolute/path/to/openclaw",
76
+ "Or set OPENCLAW_PATH in environment.",
77
+ `Checked: ${OPENCLAW_DISCOVERY_PATHS.join(", ")}`
78
+ ].join(" ")
79
+ );
80
+ }
81
+ function getOpenClawInvocation(openclawPath, args) {
82
+ if (openclawPath.endsWith(".js")) {
83
+ return { cmd: process.execPath, args: [openclawPath, ...args] };
84
+ }
85
+ return { cmd: openclawPath, args };
86
+ }
87
+ function formatExecFailure(prefix, error) {
88
+ if (!(error instanceof Error)) {
89
+ return new Error(`${prefix}: ${String(error)}`);
90
+ }
91
+ const withOutput = error;
92
+ const stderr = typeof withOutput.stderr === "string" ? withOutput.stderr.trim() : Buffer.isBuffer(withOutput.stderr) ? withOutput.stderr.toString("utf-8").trim() : "";
93
+ const stdout = typeof withOutput.stdout === "string" ? withOutput.stdout.trim() : Buffer.isBuffer(withOutput.stdout) ? withOutput.stdout.toString("utf-8").trim() : "";
94
+ const detail = stderr || stdout || error.message;
95
+ return new Error(`${prefix}: ${detail}`);
96
+ }
97
+ function buildInboundPrompt(tunnelId2, userText) {
98
+ return [
99
+ `[Pubblue Tunnel ${tunnelId2}] Incoming user message:`,
100
+ "",
101
+ userText,
102
+ "",
103
+ "---",
104
+ `Reply with: pubblue tunnel write --tunnel ${tunnelId2} "<your reply>"`,
105
+ `Canvas update: pubblue tunnel write --tunnel ${tunnelId2} -c canvas -f /path/to/file.html`
106
+ ].join("\n");
107
+ }
108
+ function readTextChatMessage(entry) {
109
+ if (!entry || typeof entry !== "object") return null;
110
+ const outer = entry;
111
+ if (outer.channel !== CHANNELS.CHAT || !outer.msg || typeof outer.msg !== "object") return null;
112
+ const msg = outer.msg;
113
+ if (msg.type !== "text" || typeof msg.data !== "string" || typeof msg.id !== "string")
114
+ return null;
115
+ return { id: msg.id, text: msg.data };
116
+ }
117
+ function writeBridgeInfo(infoPath2, patch) {
118
+ const payload = {
119
+ ...patch,
120
+ updatedAt: patch.updatedAt ?? Date.now()
121
+ };
122
+ writeFileSync(infoPath2, JSON.stringify(payload));
123
+ }
124
+ async function runOpenClawPreflight(openclawPath) {
125
+ const invocation = getOpenClawInvocation(openclawPath, ["agent", "--help"]);
126
+ try {
127
+ await execFileAsync(invocation.cmd, invocation.args, {
128
+ timeout: 1e4
129
+ });
130
+ } catch (error) {
131
+ throw formatExecFailure("OpenClaw preflight failed", error);
132
+ }
133
+ }
134
+ async function deliverMessageToOpenClaw(params) {
135
+ const deliverText = buildInboundPrompt(params.tunnelId, params.text);
136
+ const timeoutMs = Number.parseInt(process.env.OPENCLAW_DELIVER_TIMEOUT_MS || "120000", 10);
137
+ const effectiveTimeoutMs = Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 12e4;
138
+ const args = ["agent", "--local", "--session-id", params.sessionId, "-m", deliverText];
139
+ const shouldDeliver = process.env.OPENCLAW_DELIVER === "1" || Boolean(process.env.OPENCLAW_DELIVER_CHANNEL) || Boolean(process.env.OPENCLAW_REPLY_TO);
140
+ if (shouldDeliver) args.push("--deliver");
141
+ if (process.env.OPENCLAW_DELIVER_CHANNEL) {
142
+ args.push("--channel", process.env.OPENCLAW_DELIVER_CHANNEL);
143
+ }
144
+ if (process.env.OPENCLAW_REPLY_TO) {
145
+ args.push("--reply-to", process.env.OPENCLAW_REPLY_TO);
146
+ }
147
+ const invocation = getOpenClawInvocation(params.openclawPath, args);
148
+ try {
149
+ await execFileAsync(invocation.cmd, invocation.args, {
150
+ timeout: effectiveTimeoutMs
151
+ });
152
+ } catch (error) {
153
+ throw formatExecFailure("OpenClaw delivery failed", error);
154
+ }
155
+ }
156
+ async function startOpenClawBridge(params) {
157
+ const startedAt = Date.now();
158
+ const baseInfo = {
159
+ pid: process.pid,
160
+ tunnelId: params.tunnelId,
161
+ mode: "openclaw",
162
+ startedAt
163
+ };
164
+ let shuttingDown = false;
165
+ const shutdown = () => {
166
+ shuttingDown = true;
167
+ };
168
+ process.on("SIGINT", shutdown);
169
+ process.on("SIGTERM", shutdown);
170
+ writeBridgeInfo(params.infoPath, {
171
+ ...baseInfo,
172
+ status: "starting"
173
+ });
174
+ try {
175
+ const openclawPath = resolveOpenClawPath();
176
+ const sessionId = process.env.OPENCLAW_SESSION_ID || readSessionIdFromOpenClaw(process.env.OPENCLAW_THREAD_ID) || "";
177
+ if (sessionId.length === 0) {
178
+ throw new Error(
179
+ [
180
+ "OpenClaw session could not be resolved.",
181
+ "Configure one of:",
182
+ " pubblue configure --set openclaw.sessionId=<session-id>",
183
+ " pubblue configure --set openclaw.threadId=<thread-id>",
184
+ "Or set OPENCLAW_SESSION_ID / OPENCLAW_THREAD_ID in environment."
185
+ ].join("\n")
186
+ );
187
+ }
188
+ await runOpenClawPreflight(openclawPath);
189
+ try {
190
+ const daemonStatus = await ipcCall(params.socketPath, { method: "status", params: {} });
191
+ if (!daemonStatus.ok) {
192
+ throw new Error(String(daemonStatus.error || "daemon status request failed"));
193
+ }
194
+ } catch (error) {
195
+ throw new Error(
196
+ `Failed to connect to local tunnel daemon socket (${params.socketPath}): ${error instanceof Error ? error.message : String(error)}`
197
+ );
198
+ }
199
+ writeBridgeInfo(params.infoPath, {
200
+ ...baseInfo,
201
+ sessionId,
202
+ status: "ready"
203
+ });
204
+ const seenIds = /* @__PURE__ */ new Set();
205
+ let consecutiveReadFailures = 0;
206
+ while (!shuttingDown) {
207
+ let messages = [];
208
+ try {
209
+ const response = await ipcCall(params.socketPath, {
210
+ method: "read",
211
+ params: { channel: CHANNELS.CHAT }
212
+ });
213
+ if (!response.ok) {
214
+ throw new Error(String(response.error || "daemon read failed"));
215
+ }
216
+ messages = Array.isArray(response.messages) ? response.messages : [];
217
+ consecutiveReadFailures = 0;
218
+ } catch (error) {
219
+ consecutiveReadFailures += 1;
220
+ writeBridgeInfo(params.infoPath, {
221
+ ...baseInfo,
222
+ sessionId,
223
+ status: "waiting-daemon",
224
+ lastError: error instanceof Error ? error.message : String(error)
225
+ });
226
+ const delayMs = Math.min(5e3, 500 * 2 ** Math.min(consecutiveReadFailures, 4));
227
+ await sleep(delayMs);
228
+ continue;
229
+ }
230
+ if (messages.length === 0) {
231
+ await sleep(400);
232
+ continue;
233
+ }
234
+ for (const entry of messages) {
235
+ const chat = readTextChatMessage(entry);
236
+ if (!chat || seenIds.has(chat.id)) continue;
237
+ await deliverMessageToOpenClaw({
238
+ openclawPath,
239
+ sessionId,
240
+ text: chat.text,
241
+ tunnelId: params.tunnelId
242
+ });
243
+ seenIds.add(chat.id);
244
+ }
245
+ writeBridgeInfo(params.infoPath, {
246
+ ...baseInfo,
247
+ sessionId,
248
+ status: "ready"
249
+ });
250
+ }
251
+ writeBridgeInfo(params.infoPath, {
252
+ ...baseInfo,
253
+ sessionId,
254
+ status: "stopped"
255
+ });
256
+ } catch (error) {
257
+ const message = error instanceof Error ? error.message : String(error);
258
+ writeBridgeInfo(params.infoPath, {
259
+ ...baseInfo,
260
+ status: "error",
261
+ lastError: message
262
+ });
263
+ try {
264
+ await ipcCall(params.socketPath, {
265
+ method: "write",
266
+ params: {
267
+ channel: CHANNELS.CHAT,
268
+ msg: {
269
+ id: generateMessageId(),
270
+ type: "text",
271
+ data: `Bridge error: ${message}`
272
+ }
273
+ }
274
+ });
275
+ } catch {
276
+ }
277
+ throw error;
278
+ } finally {
279
+ process.off("SIGINT", shutdown);
280
+ process.off("SIGTERM", shutdown);
281
+ }
282
+ }
283
+
284
+ // src/tunnel-bridge-entry.ts
285
+ var mode = process.env.PUBBLUE_BRIDGE_MODE;
286
+ var tunnelId = process.env.PUBBLUE_BRIDGE_TUNNEL_ID;
287
+ var socketPath = process.env.PUBBLUE_BRIDGE_SOCKET;
288
+ var infoPath = process.env.PUBBLUE_BRIDGE_INFO;
289
+ if (!mode || !tunnelId || !socketPath || !infoPath) {
290
+ console.error("Missing required env vars for bridge process.");
291
+ process.exit(1);
292
+ }
293
+ if (mode !== "openclaw") {
294
+ console.error(`Unsupported bridge mode: ${mode}. Supported values: openclaw`);
295
+ process.exit(1);
296
+ }
297
+ void startOpenClawBridge({
298
+ tunnelId,
299
+ socketPath,
300
+ infoPath
301
+ }).catch((error) => {
302
+ const message = error instanceof Error ? error.message : String(error);
303
+ console.error(`Tunnel bridge failed: ${message}`);
304
+ process.exit(1);
305
+ });
@@ -2,7 +2,7 @@ import {
2
2
  getTunnelWriteReadinessError,
3
3
  shouldRecoverForBrowserAnswerChange,
4
4
  startDaemon
5
- } from "./chunk-AIEPM67G.js";
5
+ } from "./chunk-HOHLQGQT.js";
6
6
  import "./chunk-MW35LBNH.js";
7
7
  export {
8
8
  getTunnelWriteReadinessError,
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  TunnelApiClient
3
- } from "./chunk-BV423NLA.js";
3
+ } from "./chunk-7NFHPJ76.js";
4
4
  import {
5
5
  startDaemon
6
- } from "./chunk-AIEPM67G.js";
6
+ } from "./chunk-HOHLQGQT.js";
7
7
  import "./chunk-MW35LBNH.js";
8
8
 
9
9
  // src/tunnel-daemon-entry.ts
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "pubblue",
3
- "version": "0.4.5",
3
+ "version": "0.4.10",
4
4
  "description": "CLI tool for publishing static content via pub.blue",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "pubblue": "./dist/index.js"
8
8
  },
9
9
  "scripts": {
10
- "build": "tsup src/index.ts src/tunnel-daemon-entry.ts --format esm --dts --clean",
11
- "dev": "tsup src/index.ts src/tunnel-daemon-entry.ts --format esm --watch",
10
+ "build": "tsup src/index.ts src/tunnel-daemon-entry.ts src/tunnel-bridge-entry.ts --format esm --dts --clean",
11
+ "dev": "tsup src/index.ts src/tunnel-daemon-entry.ts src/tunnel-bridge-entry.ts --format esm --watch",
12
12
  "test": "vitest run",
13
13
  "test:watch": "vitest",
14
14
  "lint": "tsc --noEmit"