pubblue 0.6.1 → 0.6.4

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.
@@ -11,6 +11,9 @@ var CliError = class extends Error {
11
11
  function failCli(message, exitCode = 1) {
12
12
  throw new CliError(message, exitCode);
13
13
  }
14
+ function errorMessage(error) {
15
+ return error instanceof Error ? error.message : String(error);
16
+ }
14
17
  function toCliFailure(error) {
15
18
  if (error instanceof CommanderError) {
16
19
  return {
@@ -24,15 +27,9 @@ function toCliFailure(error) {
24
27
  message: error.message
25
28
  };
26
29
  }
27
- if (error instanceof Error) {
28
- return {
29
- exitCode: 1,
30
- message: error.message
31
- };
32
- }
33
30
  return {
34
31
  exitCode: 1,
35
- message: String(error)
32
+ message: errorMessage(error)
36
33
  };
37
34
  }
38
35
 
@@ -50,8 +47,8 @@ var PubApiClient = class {
50
47
  this.baseUrl = baseUrl;
51
48
  this.apiKey = apiKey;
52
49
  }
53
- async request(path3, options = {}) {
54
- const url = new URL(path3, this.baseUrl);
50
+ async request(path2, options = {}) {
51
+ const url = new URL(path2, this.baseUrl);
55
52
  const res = await fetch(url, {
56
53
  ...options,
57
54
  headers: {
@@ -115,7 +112,7 @@ var PubApiClient = class {
115
112
  body: JSON.stringify(body)
116
113
  });
117
114
  }
118
- async remove(slug) {
115
+ async deletePub(slug) {
119
116
  await this.request(`/api/v1/pubs/${encodeURIComponent(slug)}`, {
120
117
  method: "DELETE"
121
118
  });
@@ -201,72 +198,12 @@ function shouldAcknowledgeMessage(channel, msg) {
201
198
  return channel !== CONTROL_CHANNEL && parseAckMessage(msg) === null;
202
199
  }
203
200
 
204
- // src/commands/tunnel-helpers.ts
205
- import * as fs2 from "fs";
206
- import { homedir as homedir2 } from "os";
207
- import * as path2 from "path";
208
-
209
- // src/lib/config.ts
201
+ // src/commands/live-helpers.ts
210
202
  import * as fs from "fs";
211
- import * as os from "os";
203
+ import { homedir } from "os";
212
204
  import * as path from "path";
213
- var DEFAULT_BASE_URL = "https://silent-guanaco-514.convex.site";
214
- function getConfigDir(homeDir) {
215
- const home = homeDir || os.homedir();
216
- return path.join(home, ".config", "pubblue");
217
- }
218
- function getConfigPath(homeDir) {
219
- const dir = getConfigDir(homeDir);
220
- fs.mkdirSync(dir, { recursive: true, mode: 448 });
221
- try {
222
- fs.chmodSync(dir, 448);
223
- } catch {
224
- }
225
- return path.join(dir, "config.json");
226
- }
227
- function loadConfig(homeDir) {
228
- const configPath = getConfigPath(homeDir);
229
- if (!fs.existsSync(configPath)) return null;
230
- const raw = fs.readFileSync(configPath, "utf-8");
231
- return JSON.parse(raw);
232
- }
233
- function saveConfig(config, homeDir) {
234
- const configPath = getConfigPath(homeDir);
235
- fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
236
- `, {
237
- mode: 384
238
- });
239
- try {
240
- fs.chmodSync(configPath, 384);
241
- } catch {
242
- }
243
- }
244
- function getConfig(homeDir) {
245
- const envKey = process.env.PUBBLUE_API_KEY;
246
- const envUrl = process.env.PUBBLUE_URL;
247
- const baseUrl = envUrl || DEFAULT_BASE_URL;
248
- const saved = loadConfig(homeDir);
249
- if (envKey) {
250
- return { apiKey: envKey, baseUrl, bridge: saved?.bridge };
251
- }
252
- if (!saved) {
253
- throw new Error(
254
- "Not configured. Run `pubblue configure` or set PUBBLUE_API_KEY environment variable."
255
- );
256
- }
257
- return {
258
- apiKey: saved.apiKey,
259
- baseUrl,
260
- bridge: saved.bridge
261
- };
262
- }
263
- function getTelegramMiniAppUrl(slug) {
264
- const saved = loadConfig();
265
- if (!saved?.telegram?.botUsername) return null;
266
- return `https://t.me/${saved.telegram.botUsername}?startapp=${slug}`;
267
- }
268
205
 
269
- // src/lib/tunnel-ipc.ts
206
+ // src/lib/live-ipc.ts
270
207
  import * as net from "net";
271
208
  function getAgentSocketPath() {
272
209
  return "/tmp/pubblue-agent.sock";
@@ -318,7 +255,7 @@ async function ipcCall(socketPath, request) {
318
255
  });
319
256
  }
320
257
 
321
- // src/commands/tunnel-helpers.ts
258
+ // src/commands/live-helpers.ts
322
259
  var TEXT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
323
260
  ".txt",
324
261
  ".md",
@@ -340,52 +277,54 @@ var TEXT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
340
277
  ".less",
341
278
  ".log"
342
279
  ]);
280
+ var MIME_BY_EXT = {
281
+ ".html": "text/html; charset=utf-8",
282
+ ".htm": "text/html; charset=utf-8",
283
+ ".txt": "text/plain; charset=utf-8",
284
+ ".md": "text/markdown; charset=utf-8",
285
+ ".markdown": "text/markdown; charset=utf-8",
286
+ ".json": "application/json",
287
+ ".csv": "text/csv; charset=utf-8",
288
+ ".xml": "application/xml",
289
+ ".yaml": "application/x-yaml",
290
+ ".yml": "application/x-yaml",
291
+ ".js": "text/javascript; charset=utf-8",
292
+ ".mjs": "text/javascript; charset=utf-8",
293
+ ".cjs": "text/javascript; charset=utf-8",
294
+ ".ts": "text/typescript; charset=utf-8",
295
+ ".tsx": "text/typescript; charset=utf-8",
296
+ ".jsx": "text/javascript; charset=utf-8",
297
+ ".css": "text/css; charset=utf-8",
298
+ ".scss": "text/x-scss; charset=utf-8",
299
+ ".sass": "text/x-sass; charset=utf-8",
300
+ ".less": "text/x-less; charset=utf-8",
301
+ ".log": "text/plain; charset=utf-8",
302
+ ".png": "image/png",
303
+ ".jpg": "image/jpeg",
304
+ ".jpeg": "image/jpeg",
305
+ ".gif": "image/gif",
306
+ ".webp": "image/webp",
307
+ ".svg": "image/svg+xml",
308
+ ".pdf": "application/pdf",
309
+ ".zip": "application/zip",
310
+ ".mp3": "audio/mpeg",
311
+ ".wav": "audio/wav",
312
+ ".mp4": "video/mp4"
313
+ };
343
314
  function getMimeType(filePath) {
344
- const ext = path2.extname(filePath).toLowerCase();
345
- const mimeByExt = {
346
- ".html": "text/html; charset=utf-8",
347
- ".htm": "text/html; charset=utf-8",
348
- ".txt": "text/plain; charset=utf-8",
349
- ".md": "text/markdown; charset=utf-8",
350
- ".markdown": "text/markdown; charset=utf-8",
351
- ".json": "application/json",
352
- ".csv": "text/csv; charset=utf-8",
353
- ".xml": "application/xml",
354
- ".yaml": "application/x-yaml",
355
- ".yml": "application/x-yaml",
356
- ".png": "image/png",
357
- ".jpg": "image/jpeg",
358
- ".jpeg": "image/jpeg",
359
- ".gif": "image/gif",
360
- ".webp": "image/webp",
361
- ".svg": "image/svg+xml",
362
- ".pdf": "application/pdf",
363
- ".zip": "application/zip",
364
- ".mp3": "audio/mpeg",
365
- ".wav": "audio/wav",
366
- ".mp4": "video/mp4"
367
- };
368
- return mimeByExt[ext] || "application/octet-stream";
315
+ const ext = path.extname(filePath).toLowerCase();
316
+ return MIME_BY_EXT[ext] || "application/octet-stream";
369
317
  }
370
318
  function liveInfoDir() {
371
- const dir = path2.join(
372
- process.env.HOME || process.env.USERPROFILE || "/tmp",
373
- ".config",
374
- "pubblue",
375
- "lives"
376
- );
377
- if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
319
+ const dir = path.join(homedir(), ".config", "pubblue", "lives");
320
+ fs.mkdirSync(dir, { recursive: true });
378
321
  return dir;
379
322
  }
380
323
  function liveInfoPath(slug) {
381
- return path2.join(liveInfoDir(), `${slug}.json`);
324
+ return path.join(liveInfoDir(), `${slug}.json`);
382
325
  }
383
326
  function liveLogPath(slug) {
384
- return path2.join(liveInfoDir(), `${slug}.log`);
385
- }
386
- function createApiClient(configOverride) {
387
- const config = configOverride || getConfig();
388
- return new PubApiClient(config.baseUrl, config.apiKey);
327
+ return path.join(liveInfoDir(), `${slug}.log`);
389
328
  }
390
329
  function buildBridgeProcessEnv(bridgeConfig) {
391
330
  const env = { ...process.env };
@@ -396,7 +335,7 @@ function buildBridgeProcessEnv(bridgeConfig) {
396
335
  env[key] = String(value);
397
336
  };
398
337
  setIfMissing("PUBBLUE_PROJECT_ROOT", process.cwd());
399
- setIfMissing("OPENCLAW_HOME", homedir2());
338
+ setIfMissing("OPENCLAW_HOME", homedir());
400
339
  if (!bridgeConfig) return env;
401
340
  setIfMissing("OPENCLAW_PATH", bridgeConfig.openclawPath);
402
341
  setIfMissing("OPENCLAW_SESSION_ID", bridgeConfig.sessionId);
@@ -417,12 +356,11 @@ async function ensureNodeDatachannelAvailable() {
417
356
  try {
418
357
  await import("node-datachannel");
419
358
  } catch (error) {
420
- const message = error instanceof Error ? error.message : String(error);
421
359
  failCli(
422
360
  [
423
361
  "node-datachannel native module is not available.",
424
362
  "Run `pnpm rebuild node-datachannel` in the cli package and retry.",
425
- `Details: ${message}`
363
+ `Details: ${errorMessage(error)}`
426
364
  ].join("\n")
427
365
  );
428
366
  }
@@ -432,39 +370,38 @@ function isDaemonRunning(slug) {
432
370
  }
433
371
  function readDaemonProcessInfo(slug) {
434
372
  const infoPath = liveInfoPath(slug);
435
- if (!fs2.existsSync(infoPath)) return null;
436
373
  try {
437
- const info = JSON.parse(fs2.readFileSync(infoPath, "utf-8"));
374
+ const info = JSON.parse(fs.readFileSync(infoPath, "utf-8"));
438
375
  if (!Number.isFinite(info.pid)) throw new Error("invalid daemon pid");
439
376
  if (!isProcessAlive(info.pid)) throw new Error("process not alive");
440
377
  return info;
441
378
  } catch {
442
379
  try {
443
- fs2.unlinkSync(infoPath);
380
+ fs.unlinkSync(infoPath);
444
381
  } catch {
445
382
  }
446
383
  return null;
447
384
  }
448
385
  }
449
386
  function latestCliVersionPath() {
450
- return path2.join(liveInfoDir(), "cli-version.txt");
387
+ return path.join(liveInfoDir(), "cli-version.txt");
451
388
  }
452
389
  function readLatestCliVersion(versionPath) {
453
390
  const resolved = versionPath || latestCliVersionPath();
454
- if (!fs2.existsSync(resolved)) return null;
455
391
  try {
456
- const value = fs2.readFileSync(resolved, "utf-8").trim();
392
+ const value = fs.readFileSync(resolved, "utf-8").trim();
457
393
  return value.length === 0 ? null : value;
458
394
  } catch {
459
395
  return null;
460
396
  }
461
397
  }
462
398
  function writeLatestCliVersion(version, versionPath) {
463
- if (!version || version.trim().length === 0) return;
399
+ const trimmed = version.trim();
400
+ if (trimmed.length === 0) return;
464
401
  const resolved = versionPath || latestCliVersionPath();
465
- const dir = path2.dirname(resolved);
466
- if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
467
- fs2.writeFileSync(resolved, version.trim(), "utf-8");
402
+ const dir = path.dirname(resolved);
403
+ fs.mkdirSync(dir, { recursive: true });
404
+ fs.writeFileSync(resolved, trimmed, "utf-8");
468
405
  }
469
406
  function isProcessAlive(pid) {
470
407
  try {
@@ -484,7 +421,7 @@ async function waitForProcessExit(pid, timeoutMs) {
484
421
  }
485
422
  async function stopDaemonForLive(info) {
486
423
  const pid = info.pid;
487
- if (!Number.isFinite(pid) || !isProcessAlive(pid)) return null;
424
+ if (!isProcessAlive(pid)) return null;
488
425
  const socketPath = info.socketPath;
489
426
  if (socketPath) {
490
427
  try {
@@ -493,14 +430,14 @@ async function stopDaemonForLive(info) {
493
430
  try {
494
431
  process.kill(pid, "SIGTERM");
495
432
  } catch (killError) {
496
- return `daemon ${pid}: IPC close failed (${error instanceof Error ? error.message : String(error)}); SIGTERM failed (${killError instanceof Error ? killError.message : String(killError)})`;
433
+ return `daemon ${pid}: IPC close failed (${errorMessage(error)}); SIGTERM failed (${errorMessage(killError)})`;
497
434
  }
498
435
  }
499
436
  } else {
500
437
  try {
501
438
  process.kill(pid, "SIGTERM");
502
439
  } catch (error) {
503
- return `daemon ${pid}: no socketPath and SIGTERM failed (${error instanceof Error ? error.message : String(error)})`;
440
+ return `daemon ${pid}: no socketPath and SIGTERM failed (${errorMessage(error)})`;
504
441
  }
505
442
  }
506
443
  const stopped = await waitForProcessExit(pid, 8e3);
@@ -509,7 +446,7 @@ async function stopDaemonForLive(info) {
509
446
  }
510
447
  async function stopOtherDaemons() {
511
448
  const dir = liveInfoDir();
512
- const entries = fs2.readdirSync(dir).filter((name) => name.endsWith(".json"));
449
+ const entries = fs.readdirSync(dir).filter((name) => name.endsWith(".json"));
513
450
  const failures = [];
514
451
  for (const entry of entries) {
515
452
  const slug = entry.replace(/\.json$/, "");
@@ -561,9 +498,8 @@ function messageContainsPong(payload) {
561
498
  return type === "text" && typeof data === "string" && data.trim().toLowerCase() === "pong";
562
499
  }
563
500
  function readLogTail(logPath, maxChars = 4e3) {
564
- if (!fs2.existsSync(logPath)) return null;
565
501
  try {
566
- const content = fs2.readFileSync(logPath, "utf-8");
502
+ const content = fs.readFileSync(logPath, "utf-8");
567
503
  if (content.length <= maxChars) return content;
568
504
  return content.slice(-maxChars);
569
505
  } catch {
@@ -577,7 +513,7 @@ function formatApiError(error) {
577
513
  }
578
514
  return `${error.message} (HTTP ${error.status})`;
579
515
  }
580
- return error instanceof Error ? error.message : String(error);
516
+ return errorMessage(error);
581
517
  }
582
518
  async function resolveActiveSlug() {
583
519
  const socketPath = getAgentSocketPath();
@@ -616,12 +552,12 @@ function waitForDaemonReady({
616
552
  };
617
553
  child.on("exit", onExit);
618
554
  const poll = setInterval(() => {
619
- if (pollInFlight || !fs2.existsSync(infoPath)) return;
555
+ if (pollInFlight || !fs.existsSync(infoPath)) return;
620
556
  pollInFlight = true;
621
557
  void ipcCall(socketPath, { method: "status", params: {} }).then((status) => {
622
558
  if (status.ok) done({ ok: true });
623
559
  }).catch((error) => {
624
- lastIpcError = error instanceof Error ? error.message : String(error);
560
+ lastIpcError = errorMessage(error);
625
561
  }).finally(() => {
626
562
  pollInFlight = false;
627
563
  });
@@ -635,11 +571,8 @@ function waitForDaemonReady({
635
571
 
636
572
  export {
637
573
  failCli,
574
+ errorMessage,
638
575
  toCliFailure,
639
- loadConfig,
640
- saveConfig,
641
- getConfig,
642
- getTelegramMiniAppUrl,
643
576
  PubApiError,
644
577
  PubApiClient,
645
578
  CONTROL_CHANNEL,
@@ -656,7 +589,6 @@ export {
656
589
  getMimeType,
657
590
  liveInfoPath,
658
591
  liveLogPath,
659
- createApiClient,
660
592
  buildBridgeProcessEnv,
661
593
  ensureNodeDatachannelAvailable,
662
594
  isDaemonRunning,
@@ -4,15 +4,16 @@ import {
4
4
  PubApiError,
5
5
  decodeMessage,
6
6
  encodeMessage,
7
+ errorMessage,
7
8
  generateMessageId,
8
9
  latestCliVersionPath,
9
10
  makeAckMessage,
10
11
  parseAckMessage,
11
12
  readLatestCliVersion,
12
13
  shouldAcknowledgeMessage
13
- } from "./chunk-BBJOOZHS.js";
14
+ } from "./chunk-JXEXE632.js";
14
15
 
15
- // src/lib/tunnel-daemon.ts
16
+ // src/lib/live-daemon.ts
16
17
  import * as fs from "fs";
17
18
  import * as net from "net";
18
19
  import * as path from "path";
@@ -24,7 +25,7 @@ function resolveAckChannel(input) {
24
25
  return null;
25
26
  }
26
27
 
27
- // src/lib/tunnel-bridge-openclaw.ts
28
+ // src/lib/live-bridge-openclaw.ts
28
29
  import { execFile, execFileSync } from "child_process";
29
30
  import { createHash } from "crypto";
30
31
  import {
@@ -192,6 +193,46 @@ function buildAttachmentPrompt(slug, staged, includeCanvasReminder) {
192
193
  `Canvas update: pubblue write --slug ${slug} -c canvas -f /path/to/file.html`
193
194
  ].filter(Boolean).join("\n");
194
195
  }
196
+ function parseSessionContextMeta(meta) {
197
+ if (!meta) return null;
198
+ const payload = {};
199
+ if (typeof meta.title === "string") payload.title = meta.title;
200
+ if (typeof meta.contentType === "string") payload.contentType = meta.contentType;
201
+ if (typeof meta.contentPreview === "string") payload.contentPreview = meta.contentPreview;
202
+ if (typeof meta.isPublic === "boolean") payload.isPublic = meta.isPublic;
203
+ if (meta.preferences && typeof meta.preferences === "object") {
204
+ const prefs = meta.preferences;
205
+ payload.preferences = {};
206
+ if (typeof prefs.voiceModeEnabled === "boolean") {
207
+ payload.preferences.voiceModeEnabled = prefs.voiceModeEnabled;
208
+ }
209
+ }
210
+ return payload;
211
+ }
212
+ function buildSessionBriefing(slug, ctx) {
213
+ const lines = [`[Pubblue ${slug}] Session started.`, "", "## Pub Context"];
214
+ if (ctx.title) lines.push(`- Title: ${ctx.title}`);
215
+ if (ctx.contentType) lines.push(`- Content type: ${ctx.contentType}`);
216
+ if (ctx.isPublic !== void 0)
217
+ lines.push(`- Visibility: ${ctx.isPublic ? "public" : "private"}`);
218
+ if (ctx.contentPreview) {
219
+ lines.push("- Content preview:");
220
+ lines.push(ctx.contentPreview);
221
+ }
222
+ if (ctx.preferences) {
223
+ lines.push("", "## User Preferences");
224
+ if (ctx.preferences.voiceModeEnabled !== void 0) {
225
+ lines.push(`- Voice mode: ${ctx.preferences.voiceModeEnabled ? "on" : "off"}`);
226
+ }
227
+ }
228
+ lines.push(
229
+ "",
230
+ "## Commands",
231
+ `Reply: pubblue write --slug ${slug} "<your reply>"`,
232
+ `Canvas: pubblue write --slug ${slug} -c canvas -f /path/to/file.html`
233
+ );
234
+ return lines.join("\n");
235
+ }
195
236
  function readTextChatMessage(entry) {
196
237
  if (entry.channel !== CHANNELS.CHAT) return null;
197
238
  const msg = entry.msg;
@@ -254,7 +295,7 @@ function resolveSessionFromOpenClaw(threadId) {
254
295
  const sessionsData = JSON.parse(readFileSync(sessionsPath, "utf-8"));
255
296
  return resolveSessionFromSessionsData(sessionsData, threadId);
256
297
  } catch (error) {
257
- const readError = error instanceof Error ? error.message : String(error);
298
+ const readError = errorMessage(error);
258
299
  return { attemptedKeys, readError, sessionId: null };
259
300
  }
260
301
  }
@@ -509,6 +550,7 @@ async function createOpenClawBridgeRunner(config) {
509
550
  let lastError;
510
551
  let stopping = false;
511
552
  let loopDone;
553
+ let sessionBriefingSent = false;
512
554
  const queue = [];
513
555
  let notify = null;
514
556
  function enqueue(entries) {
@@ -535,6 +577,16 @@ async function createOpenClawBridgeRunner(config) {
535
577
  seenIds.clear();
536
578
  }
537
579
  try {
580
+ if (!sessionBriefingSent && entry.channel === CONTROL_CHANNEL && entry.msg.type === "event" && entry.msg.data === "session-context") {
581
+ const ctx = parseSessionContextMeta(entry.msg.meta);
582
+ if (ctx) {
583
+ sessionBriefingSent = true;
584
+ const briefing = buildSessionBriefing(slug, ctx);
585
+ await deliverMessageToOpenClaw({ openclawPath, sessionId, text: briefing });
586
+ debugLog("session briefing delivered");
587
+ }
588
+ continue;
589
+ }
538
590
  const includeCanvasReminder = shouldIncludeCanvasPolicyReminder(
539
591
  forwardedMessageCount + 1,
540
592
  canvasReminderEvery
@@ -564,7 +616,7 @@ async function createOpenClawBridgeRunner(config) {
564
616
  forwardedMessageCount += 1;
565
617
  }
566
618
  } catch (error) {
567
- const message = error instanceof Error ? error.message : String(error);
619
+ const message = errorMessage(error);
568
620
  lastError = message;
569
621
  debugLog(`bridge entry processing failed: ${message}`, error);
570
622
  config.sendMessage(CHANNELS.CHAT, {
@@ -601,8 +653,8 @@ async function createOpenClawBridgeRunner(config) {
601
653
  };
602
654
  }
603
655
 
604
- // src/lib/tunnel-daemon-answer.ts
605
- function generateAnswer(peer, browserOffer, timeoutMs) {
656
+ // src/lib/live-daemon-answer.ts
657
+ function createAnswer(peer, browserOffer, timeoutMs) {
606
658
  return new Promise((resolve, reject) => {
607
659
  let resolved = false;
608
660
  const done = (sdp, type) => {
@@ -635,14 +687,14 @@ function generateAnswer(peer, browserOffer, timeoutMs) {
635
687
  });
636
688
  }
637
689
 
638
- // src/lib/tunnel-daemon-shared.ts
690
+ // src/lib/live-daemon-shared.ts
639
691
  var OFFER_TIMEOUT_MS = 1e4;
640
692
  var SIGNAL_POLL_WAITING_MS = 5e3;
641
693
  var SIGNAL_POLL_CONNECTED_MS = 15e3;
642
694
  var LOCAL_CANDIDATE_FLUSH_MS = 2e3;
643
695
  var WRITE_ACK_TIMEOUT_MS = 5e3;
644
696
  var NOT_CONNECTED_WRITE_ERROR = "No browser connected. Ask the user to open the pub URL first, then retry.";
645
- function getTunnelWriteReadinessError(isConnected) {
697
+ function getLiveWriteReadinessError(isConnected) {
646
698
  return isConnected ? null : NOT_CONNECTED_WRITE_ERROR;
647
699
  }
648
700
  function shouldRecoverForBrowserOfferChange(params) {
@@ -670,7 +722,7 @@ function getSignalPollDelayMs(params) {
670
722
  return Math.max(baseDelay, Math.ceil(params.retryAfterSeconds * 1e3));
671
723
  }
672
724
 
673
- // src/lib/tunnel-daemon.ts
725
+ // src/lib/live-daemon.ts
674
726
  var HEARTBEAT_INTERVAL_MS = 3e4;
675
727
  var HEALTH_CHECK_INTERVAL_MS = 60 * 60 * 1e3;
676
728
  var PERSIST_TIMEOUT_MS = 3e3;
@@ -699,7 +751,7 @@ async function startDaemon(config) {
699
751
  let localCandidateStopTimer = null;
700
752
  let healthCheckTimer = null;
701
753
  let lastError = null;
702
- const debugEnabled = process.env.PUBBLUE_TUNNEL_DEBUG === "1";
754
+ const debugEnabled = process.env.PUBBLUE_LIVE_DEBUG === "1";
703
755
  const versionFilePath = latestCliVersionPath();
704
756
  let bridgeRunner = null;
705
757
  function debugLog(message, error) {
@@ -999,7 +1051,7 @@ async function startDaemon(config) {
999
1051
  createPeer();
1000
1052
  resetNegotiationState();
1001
1053
  if (!peer) throw new Error("PeerConnection not initialized");
1002
- const answer = await generateAnswer(peer, browserOffer, OFFER_TIMEOUT_MS);
1054
+ const answer = await createAnswer(peer, browserOffer, OFFER_TIMEOUT_MS);
1003
1055
  lastAppliedBrowserOffer = browserOffer;
1004
1056
  activeSlug = slug;
1005
1057
  await apiClient.signalAnswer({ slug, answer, agentName });
@@ -1113,7 +1165,7 @@ async function startDaemon(config) {
1113
1165
  return;
1114
1166
  }
1115
1167
  handleIpcRequest(request).then((response) => conn.write(`${JSON.stringify(response)}
1116
- `)).catch((err) => conn.write(`${JSON.stringify({ ok: false, error: String(err) })}
1168
+ `)).catch((err) => conn.write(`${JSON.stringify({ ok: false, error: errorMessage(err) })}
1117
1169
  `));
1118
1170
  });
1119
1171
  });
@@ -1221,7 +1273,7 @@ async function startDaemon(config) {
1221
1273
  switch (req.method) {
1222
1274
  case "write": {
1223
1275
  const channel = req.params.channel || CHANNELS.CHAT;
1224
- const readinessError = getTunnelWriteReadinessError(connected);
1276
+ const readinessError = getLiveWriteReadinessError(connected);
1225
1277
  if (readinessError) return { ok: false, error: readinessError };
1226
1278
  const msg = req.params.msg;
1227
1279
  const binaryBase64 = typeof req.params.binaryBase64 === "string" ? req.params.binaryBase64 : void 0;
@@ -1231,9 +1283,8 @@ async function startDaemon(config) {
1231
1283
  try {
1232
1284
  await waitForChannelOpen(targetDc);
1233
1285
  } catch (error) {
1234
- const message = error instanceof Error ? error.message : String(error);
1235
1286
  markError(`channel "${channel}" failed to open`, error);
1236
- return { ok: false, error: `Channel "${channel}" not open: ${message}` };
1287
+ return { ok: false, error: `Channel "${channel}" not open: ${errorMessage(error)}` };
1237
1288
  }
1238
1289
  const waitForAck = shouldAcknowledgeMessage(channel, msg) ? waitForDeliveryAck(msg.id, WRITE_ACK_TIMEOUT_MS) : null;
1239
1290
  try {
@@ -1250,9 +1301,11 @@ async function startDaemon(config) {
1250
1301
  }
1251
1302
  } catch (error) {
1252
1303
  if (waitForAck) settlePendingAck(msg.id, false);
1253
- const message = error instanceof Error ? error.message : String(error);
1254
1304
  markError(`failed to send message on channel "${channel}"`, error);
1255
- return { ok: false, error: `Failed to send on channel "${channel}": ${message}` };
1305
+ return {
1306
+ ok: false,
1307
+ error: `Failed to send on channel "${channel}": ${errorMessage(error)}`
1308
+ };
1256
1309
  }
1257
1310
  if (waitForAck) {
1258
1311
  const acked = await waitForAck;
package/dist/index.js CHANGED
@@ -6,32 +6,28 @@ import {
6
6
  TEXT_FILE_EXTENSIONS,
7
7
  buildBridgeProcessEnv,
8
8
  buildDaemonForkStdio,
9
- createApiClient,
10
9
  ensureNodeDatachannelAvailable,
10
+ errorMessage,
11
11
  failCli,
12
12
  formatApiError,
13
13
  generateMessageId,
14
14
  getAgentSocketPath,
15
- getConfig,
16
15
  getFollowReadDelayMs,
17
16
  getMimeType,
18
- getTelegramMiniAppUrl,
19
17
  ipcCall,
20
18
  isDaemonRunning,
21
19
  liveInfoPath,
22
20
  liveLogPath,
23
- loadConfig,
24
21
  messageContainsPong,
25
22
  parsePositiveIntegerOption,
26
23
  readLogTail,
27
24
  resolveActiveSlug,
28
25
  resolveBridgeMode,
29
- saveConfig,
30
26
  stopOtherDaemons,
31
27
  toCliFailure,
32
28
  waitForDaemonReady,
33
29
  writeLatestCliVersion
34
- } from "./chunk-BBJOOZHS.js";
30
+ } from "./chunk-JXEXE632.js";
35
31
 
36
32
  // src/program.ts
37
33
  import { Command } from "commander";
@@ -39,11 +35,71 @@ import { Command } from "commander";
39
35
  // src/commands/configure.ts
40
36
  import { createInterface } from "readline/promises";
41
37
 
42
- // src/commands/shared.ts
38
+ // src/lib/config.ts
43
39
  import * as fs from "fs";
40
+ import * as os from "os";
44
41
  import * as path from "path";
45
- function createClient() {
46
- const config = getConfig();
42
+ var DEFAULT_BASE_URL = "https://silent-guanaco-514.convex.site";
43
+ function getConfigDir(homeDir) {
44
+ const home = homeDir || os.homedir();
45
+ return path.join(home, ".config", "pubblue");
46
+ }
47
+ function getConfigPath(homeDir) {
48
+ const dir = getConfigDir(homeDir);
49
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
50
+ try {
51
+ fs.chmodSync(dir, 448);
52
+ } catch {
53
+ }
54
+ return path.join(dir, "config.json");
55
+ }
56
+ function readConfig(homeDir) {
57
+ const configPath = getConfigPath(homeDir);
58
+ if (!fs.existsSync(configPath)) return null;
59
+ const raw = fs.readFileSync(configPath, "utf-8");
60
+ return JSON.parse(raw);
61
+ }
62
+ function saveConfig(config, homeDir) {
63
+ const configPath = getConfigPath(homeDir);
64
+ fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
65
+ `, {
66
+ mode: 384
67
+ });
68
+ try {
69
+ fs.chmodSync(configPath, 384);
70
+ } catch {
71
+ }
72
+ }
73
+ function getConfig(homeDir) {
74
+ const envKey = process.env.PUBBLUE_API_KEY;
75
+ const envUrl = process.env.PUBBLUE_URL;
76
+ const baseUrl = envUrl || DEFAULT_BASE_URL;
77
+ const saved = readConfig(homeDir);
78
+ if (envKey) {
79
+ return { apiKey: envKey, baseUrl, bridge: saved?.bridge };
80
+ }
81
+ if (!saved) {
82
+ throw new Error(
83
+ "Not configured. Run `pubblue configure` or set PUBBLUE_API_KEY environment variable."
84
+ );
85
+ }
86
+ return {
87
+ apiKey: saved.apiKey,
88
+ baseUrl,
89
+ bridge: saved.bridge
90
+ };
91
+ }
92
+ function getTelegramMiniAppUrl(slug) {
93
+ const saved = readConfig();
94
+ if (!saved?.telegram?.botUsername) return null;
95
+ return `https://t.me/${saved.telegram.botUsername}?startapp=${slug}`;
96
+ }
97
+
98
+ // src/commands/shared.ts
99
+ import * as fs2 from "fs";
100
+ import * as path2 from "path";
101
+ function createClient(configOverride) {
102
+ const config = configOverride || getConfig();
47
103
  return new PubApiClient(config.baseUrl, config.apiKey);
48
104
  }
49
105
  async function readFromStdin() {
@@ -65,13 +121,13 @@ function resolveVisibilityFlags(opts) {
65
121
  return void 0;
66
122
  }
67
123
  function readFile(filePath) {
68
- const resolved = path.resolve(filePath);
69
- if (!fs.existsSync(resolved)) {
124
+ const resolved = path2.resolve(filePath);
125
+ if (!fs2.existsSync(resolved)) {
70
126
  failCli(`File not found: ${resolved}`);
71
127
  }
72
128
  return {
73
- content: fs.readFileSync(resolved, "utf-8"),
74
- basename: path.basename(resolved)
129
+ content: fs2.readFileSync(resolved, "utf-8"),
130
+ basename: path2.basename(resolved)
75
131
  };
76
132
  }
77
133
 
@@ -311,7 +367,7 @@ function registerConfigureCommand(program2) {
311
367
  []
312
368
  ).option("--unset <key>", "Unset config key (repeatable)", collectValues, []).option("--show", "Show saved configuration").action(
313
369
  async (opts) => {
314
- const saved = loadConfig();
370
+ const saved = readConfig();
315
371
  const hasApiUpdate = Boolean(opts.apiKey || opts.apiKeyStdin);
316
372
  const hasSet = opts.set.length > 0;
317
373
  const hasUnset = opts.unset.length > 0;
@@ -349,7 +405,7 @@ function registerConfigureCommand(program2) {
349
405
  console.log("Telegram menu button reset to default.");
350
406
  } catch (error) {
351
407
  console.error(
352
- `Warning: failed to reset Telegram menu button: ${error instanceof Error ? error.message : String(error)}`
408
+ `Warning: failed to reset Telegram menu button: ${errorMessage(error)}`
353
409
  );
354
410
  }
355
411
  }
@@ -389,21 +445,21 @@ function registerConfigureCommand(program2) {
389
445
  }
390
446
 
391
447
  // src/commands/live.ts
392
- import * as fs2 from "fs";
393
- import * as path2 from "path";
448
+ import * as fs3 from "fs";
449
+ import * as path3 from "path";
394
450
 
395
451
  // package.json
396
452
  var package_default = {
397
453
  name: "pubblue",
398
- version: "0.6.1",
454
+ version: "0.6.4",
399
455
  description: "CLI tool for publishing content and running interactive sessions via pub.blue",
400
456
  type: "module",
401
457
  bin: {
402
458
  pubblue: "./dist/index.js"
403
459
  },
404
460
  scripts: {
405
- build: "tsup src/index.ts src/tunnel-daemon-entry.ts --format esm --dts --clean",
406
- dev: "tsup src/index.ts src/tunnel-daemon-entry.ts --format esm --watch",
461
+ build: "tsup src/index.ts src/live-daemon-entry.ts --format esm --dts --clean",
462
+ dev: "tsup src/index.ts src/live-daemon-entry.ts --format esm --watch",
407
463
  test: "vitest run",
408
464
  "test:watch": "vitest",
409
465
  lint: "tsc --noEmit"
@@ -459,7 +515,7 @@ function registerStartCommand(program2) {
459
515
  await ensureNodeDatachannelAvailable();
460
516
  writeLatestCliVersion(CLI_VERSION);
461
517
  const runtimeConfig = getConfig();
462
- const apiClient = createApiClient(runtimeConfig);
518
+ const apiClient = createClient(runtimeConfig);
463
519
  const bridgeMode = resolveBridgeMode(opts);
464
520
  const bridgeProcessEnv = buildBridgeProcessEnv(runtimeConfig.bridge);
465
521
  const socketPath = getAgentSocketPath();
@@ -467,7 +523,7 @@ function registerStartCommand(program2) {
467
523
  const logPath = liveLogPath("agent");
468
524
  await stopOtherDaemons();
469
525
  if (opts.foreground) {
470
- const { startDaemon } = await import("./tunnel-daemon-BR5XKNEA.js");
526
+ const { startDaemon } = await import("./live-daemon-EEIBVVBU.js");
471
527
  console.log("Agent daemon starting in foreground...");
472
528
  console.log("Press Ctrl+C to stop.");
473
529
  try {
@@ -480,14 +536,13 @@ function registerStartCommand(program2) {
480
536
  agentName: opts.agentName
481
537
  });
482
538
  } catch (error) {
483
- const message = error instanceof Error ? error.message : String(error);
484
- failCli(`Daemon failed: ${message}`);
539
+ failCli(`Daemon failed: ${errorMessage(error)}`);
485
540
  }
486
541
  return;
487
542
  }
488
543
  const { fork } = await import("child_process");
489
- const daemonScript = path2.join(import.meta.dirname, "tunnel-daemon-entry.js");
490
- const daemonLogFd = fs2.openSync(logPath, "a");
544
+ const daemonScript = path3.join(import.meta.dirname, "live-daemon-entry.js");
545
+ const daemonLogFd = fs3.openSync(logPath, "a");
491
546
  const child = fork(daemonScript, [], {
492
547
  detached: true,
493
548
  stdio: buildDaemonForkStdio(daemonLogFd),
@@ -502,7 +557,7 @@ function registerStartCommand(program2) {
502
557
  PUBBLUE_DAEMON_BRIDGE_MODE: bridgeMode
503
558
  }
504
559
  });
505
- fs2.closeSync(daemonLogFd);
560
+ fs3.closeSync(daemonLogFd);
506
561
  if (child.connected) {
507
562
  child.disconnect();
508
563
  }
@@ -564,7 +619,7 @@ function registerStatusCommand(program2) {
564
619
  console.log(` Last error: ${response.lastError}`);
565
620
  }
566
621
  const logPath = liveLogPath("agent");
567
- if (fs2.existsSync(logPath)) {
622
+ if (fs3.existsSync(logPath)) {
568
623
  console.log(` Log: ${logPath}`);
569
624
  }
570
625
  const bridge = response.bridge;
@@ -593,10 +648,10 @@ function registerWriteCommand(program2) {
593
648
  let msg;
594
649
  let binaryBase64;
595
650
  if (opts.file) {
596
- const filePath = path2.resolve(opts.file);
597
- const ext = path2.extname(filePath).toLowerCase();
598
- const bytes = fs2.readFileSync(filePath);
599
- const filename = path2.basename(filePath);
651
+ const filePath = path3.resolve(opts.file);
652
+ const ext = path3.extname(filePath).toLowerCase();
653
+ const bytes = fs3.readFileSync(filePath);
654
+ const filename = path3.basename(filePath);
600
655
  if (ext === ".html" || ext === ".htm") {
601
656
  msg = {
602
657
  id: generateMessageId(),
@@ -713,45 +768,29 @@ function registerDoctorCommand(program2) {
713
768
  const timeoutMs = timeoutSeconds * 1e3;
714
769
  const socketPath = getAgentSocketPath();
715
770
  const slug = await resolveActiveSlug();
716
- const apiClient = createApiClient();
771
+ const apiClient = createClient();
717
772
  const fail = (message) => failCli(`Doctor failed: ${message}`);
718
773
  console.log(`Doctor: ${slug}`);
719
- let statusResponse = null;
720
- try {
721
- statusResponse = await ipcCall(socketPath, {
722
- method: "status",
723
- params: {}
724
- });
725
- } catch (error) {
726
- fail(
727
- `daemon is unreachable (${error instanceof Error ? error.message : String(error)}).`
728
- );
729
- }
730
- if (!statusResponse) {
731
- fail("daemon status returned no response.");
732
- }
733
- const daemonStatus = statusResponse;
734
- if (!daemonStatus.ok) {
735
- fail(`daemon returned non-ok status: ${String(daemonStatus.error || "unknown error")}`);
736
- }
737
- if (!daemonStatus.connected) {
774
+ const statusResponse = await ipcCall(socketPath, {
775
+ method: "status",
776
+ params: {}
777
+ }).catch((error) => fail(`daemon is unreachable (${errorMessage(error)}).`));
778
+ if (!statusResponse.ok) {
779
+ fail(`daemon returned non-ok status: ${String(statusResponse.error || "unknown error")}`);
780
+ }
781
+ if (!statusResponse.connected) {
738
782
  fail("daemon is running but browser is not connected.");
739
783
  }
740
- const channelNames = Array.isArray(daemonStatus.channels) ? daemonStatus.channels.map((entry) => String(entry)) : [];
784
+ const channelNames = Array.isArray(statusResponse.channels) ? statusResponse.channels.map((entry) => String(entry)) : [];
741
785
  for (const required of [CONTROL_CHANNEL, CHANNELS.CHAT, CHANNELS.CANVAS]) {
742
786
  if (!channelNames.includes(required)) {
743
787
  fail(`required channel is missing: ${required}`);
744
788
  }
745
789
  }
746
790
  console.log("Daemon/channel check: OK");
747
- const live = await (async () => {
748
- try {
749
- return await apiClient.getLive(slug);
750
- } catch (error) {
751
- fail(`failed to fetch live info from API: ${formatApiError(error)}`);
752
- }
753
- throw new Error("unreachable");
754
- })();
791
+ const live = await apiClient.getLive(slug).catch(
792
+ (error) => fail(`failed to fetch live info from API: ${formatApiError(error)}`)
793
+ );
755
794
  if (live.status !== "active") {
756
795
  fail(`API reports live is not active (status: ${live.status})`);
757
796
  }
@@ -932,7 +971,7 @@ function registerPubCommands(program2) {
932
971
  });
933
972
  program2.command("delete").description("Delete a pub").argument("<slug>", "Slug of the pub to delete").action(async (slug) => {
934
973
  const client = createClient();
935
- await client.remove(slug);
974
+ await client.deletePub(slug);
936
975
  console.log(`Deleted: ${slug}`);
937
976
  });
938
977
  }
@@ -0,0 +1,7 @@
1
+ import {
2
+ startDaemon
3
+ } from "./chunk-QFJDLFK5.js";
4
+ import "./chunk-JXEXE632.js";
5
+ export {
6
+ startDaemon
7
+ };
@@ -1,11 +1,12 @@
1
1
  import {
2
2
  startDaemon
3
- } from "./chunk-WXNNDR4T.js";
3
+ } from "./chunk-QFJDLFK5.js";
4
4
  import {
5
- PubApiClient
6
- } from "./chunk-BBJOOZHS.js";
5
+ PubApiClient,
6
+ errorMessage
7
+ } from "./chunk-JXEXE632.js";
7
8
 
8
- // src/tunnel-daemon-entry.ts
9
+ // src/live-daemon-entry.ts
9
10
  var baseUrl = process.env.PUBBLUE_DAEMON_BASE_URL;
10
11
  var apiKey = process.env.PUBBLUE_DAEMON_API_KEY;
11
12
  var socketPath = process.env.PUBBLUE_DAEMON_SOCKET;
@@ -20,8 +21,7 @@ if (!baseUrl || !apiKey || !socketPath || !infoPath) {
20
21
  var apiClient = new PubApiClient(baseUrl, apiKey);
21
22
  void startDaemon({ apiClient, socketPath, infoPath, cliVersion, bridgeMode, agentName }).catch(
22
23
  (error) => {
23
- const message = error instanceof Error ? error.message : String(error);
24
- console.error(`Daemon failed to start: ${message}`);
24
+ console.error(`Daemon failed to start: ${errorMessage(error)}`);
25
25
  process.exit(1);
26
26
  }
27
27
  );
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "pubblue",
3
- "version": "0.6.1",
3
+ "version": "0.6.4",
4
4
  "description": "CLI tool for publishing content and running interactive sessions 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/live-daemon-entry.ts --format esm --dts --clean",
11
+ "dev": "tsup src/index.ts src/live-daemon-entry.ts --format esm --watch",
12
12
  "test": "vitest run",
13
13
  "test:watch": "vitest",
14
14
  "lint": "tsc --noEmit"
@@ -1,7 +0,0 @@
1
- import {
2
- startDaemon
3
- } from "./chunk-WXNNDR4T.js";
4
- import "./chunk-BBJOOZHS.js";
5
- export {
6
- startDaemon
7
- };