pubblue 0.5.0 → 0.6.1
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.
- package/dist/{chunk-YI45G6AG.js → chunk-BBJOOZHS.js} +159 -242
- package/dist/chunk-WXNNDR4T.js +1313 -0
- package/dist/index.js +236 -526
- package/dist/tunnel-daemon-BR5XKNEA.js +7 -0
- package/dist/tunnel-daemon-entry.js +12 -24
- package/package.json +3 -3
- package/dist/chunk-5GSMS3YU.js +0 -776
- package/dist/chunk-PFZT7M3E.js +0 -114
- package/dist/tunnel-bridge-entry.d.ts +0 -2
- package/dist/tunnel-bridge-entry.js +0 -703
- package/dist/tunnel-daemon-QN6TVUX6.js +0 -8
package/dist/index.js
CHANGED
|
@@ -1,54 +1,37 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
CHANNELS,
|
|
4
|
+
CONTROL_CHANNEL,
|
|
3
5
|
PubApiClient,
|
|
4
|
-
PubApiError,
|
|
5
6
|
TEXT_FILE_EXTENSIONS,
|
|
6
|
-
bridgeInfoPath,
|
|
7
|
-
bridgeLogPath,
|
|
8
7
|
buildBridgeProcessEnv,
|
|
9
8
|
buildDaemonForkStdio,
|
|
10
|
-
cleanupLiveOnStartFailure,
|
|
11
9
|
createApiClient,
|
|
12
|
-
ensureBridgeReady,
|
|
13
10
|
ensureNodeDatachannelAvailable,
|
|
14
11
|
failCli,
|
|
15
12
|
formatApiError,
|
|
13
|
+
generateMessageId,
|
|
14
|
+
getAgentSocketPath,
|
|
16
15
|
getConfig,
|
|
17
16
|
getFollowReadDelayMs,
|
|
18
17
|
getMimeType,
|
|
19
|
-
getPublicUrl,
|
|
20
18
|
getTelegramMiniAppUrl,
|
|
21
|
-
|
|
19
|
+
ipcCall,
|
|
22
20
|
isDaemonRunning,
|
|
23
21
|
liveInfoPath,
|
|
24
22
|
liveLogPath,
|
|
25
23
|
loadConfig,
|
|
26
24
|
messageContainsPong,
|
|
27
|
-
parseBridgeMode,
|
|
28
25
|
parsePositiveIntegerOption,
|
|
29
|
-
pickReusableLive,
|
|
30
|
-
readBridgeProcessInfo,
|
|
31
|
-
readDaemonProcessInfo,
|
|
32
26
|
readLogTail,
|
|
33
27
|
resolveActiveSlug,
|
|
34
|
-
|
|
28
|
+
resolveBridgeMode,
|
|
35
29
|
saveConfig,
|
|
36
|
-
shouldRestartDaemonForCliUpgrade,
|
|
37
|
-
stopBridge,
|
|
38
30
|
stopOtherDaemons,
|
|
39
31
|
toCliFailure,
|
|
40
|
-
waitForAgentOffer,
|
|
41
32
|
waitForDaemonReady,
|
|
42
|
-
waitForProcessExit,
|
|
43
33
|
writeLatestCliVersion
|
|
44
|
-
} from "./chunk-
|
|
45
|
-
import {
|
|
46
|
-
CHANNELS,
|
|
47
|
-
CONTROL_CHANNEL,
|
|
48
|
-
generateMessageId,
|
|
49
|
-
getSocketPath,
|
|
50
|
-
ipcCall
|
|
51
|
-
} from "./chunk-PFZT7M3E.js";
|
|
34
|
+
} from "./chunk-BBJOOZHS.js";
|
|
52
35
|
|
|
53
36
|
// src/program.ts
|
|
54
37
|
import { Command } from "commander";
|
|
@@ -143,11 +126,6 @@ function parseBooleanValue(raw, key) {
|
|
|
143
126
|
return false;
|
|
144
127
|
throw new Error(`Invalid boolean value for ${key}: ${raw}`);
|
|
145
128
|
}
|
|
146
|
-
function parseBridgeModeValue(raw) {
|
|
147
|
-
const normalized = raw.trim().toLowerCase();
|
|
148
|
-
if (normalized === "openclaw" || normalized === "none") return normalized;
|
|
149
|
-
throw new Error(`Invalid bridge mode: ${raw}. Use openclaw or none.`);
|
|
150
|
-
}
|
|
151
129
|
function parsePositiveInteger(raw, key) {
|
|
152
130
|
const parsed = Number.parseInt(raw, 10);
|
|
153
131
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
@@ -156,7 +134,6 @@ function parsePositiveInteger(raw, key) {
|
|
|
156
134
|
return parsed;
|
|
157
135
|
}
|
|
158
136
|
var SUPPORTED_KEYS = [
|
|
159
|
-
"bridge.mode",
|
|
160
137
|
"openclaw.path",
|
|
161
138
|
"openclaw.sessionId",
|
|
162
139
|
"openclaw.threadId",
|
|
@@ -171,9 +148,6 @@ var SUPPORTED_KEYS = [
|
|
|
171
148
|
];
|
|
172
149
|
function applyConfigSet(bridge, telegram, key, value) {
|
|
173
150
|
switch (key) {
|
|
174
|
-
case "bridge.mode":
|
|
175
|
-
bridge.mode = parseBridgeModeValue(value);
|
|
176
|
-
return;
|
|
177
151
|
case "openclaw.path":
|
|
178
152
|
bridge.openclawPath = value;
|
|
179
153
|
return;
|
|
@@ -219,9 +193,6 @@ function applyConfigSet(bridge, telegram, key, value) {
|
|
|
219
193
|
}
|
|
220
194
|
function applyConfigUnset(bridge, telegram, key) {
|
|
221
195
|
switch (key) {
|
|
222
|
-
case "bridge.mode":
|
|
223
|
-
delete bridge.mode;
|
|
224
|
-
return;
|
|
225
196
|
case "openclaw.path":
|
|
226
197
|
delete bridge.openclawPath;
|
|
227
198
|
return;
|
|
@@ -279,13 +250,11 @@ async function telegramGetMe(token) {
|
|
|
279
250
|
hasMainWebApp: data.result.has_main_web_app === true
|
|
280
251
|
};
|
|
281
252
|
}
|
|
282
|
-
async function telegramSetMenuButton(token,
|
|
253
|
+
async function telegramSetMenuButton(token, button) {
|
|
283
254
|
const resp = await fetch(`https://api.telegram.org/bot${token}/setChatMenuButton`, {
|
|
284
255
|
method: "POST",
|
|
285
256
|
headers: { "Content-Type": "application/json" },
|
|
286
|
-
body: JSON.stringify({
|
|
287
|
-
menu_button: { type: "web_app", text: "Open", web_app: { url } }
|
|
288
|
-
})
|
|
257
|
+
body: JSON.stringify({ menu_button: button })
|
|
289
258
|
});
|
|
290
259
|
const data = await resp.json();
|
|
291
260
|
if (!data.ok) {
|
|
@@ -300,7 +269,6 @@ function printConfigSummary(saved) {
|
|
|
300
269
|
console.log("Saved config:");
|
|
301
270
|
console.log(` apiKey: ${maskSecret(saved.apiKey)}`);
|
|
302
271
|
if (saved.bridge && hasValues(saved.bridge)) {
|
|
303
|
-
console.log(` bridge.mode: ${saved.bridge.mode ?? "(unset)"}`);
|
|
304
272
|
if (saved.bridge.openclawPath) console.log(` openclaw.path: ${saved.bridge.openclawPath}`);
|
|
305
273
|
if (saved.bridge.sessionId) console.log(` openclaw.sessionId: ${saved.bridge.sessionId}`);
|
|
306
274
|
if (saved.bridge.threadId) console.log(` openclaw.threadId: ${saved.bridge.threadId}`);
|
|
@@ -375,6 +343,16 @@ function registerConfigureCommand(program2) {
|
|
|
375
343
|
if (key === "telegram.botToken") telegramTokenChanged = true;
|
|
376
344
|
}
|
|
377
345
|
for (const key of opts.unset) {
|
|
346
|
+
if (key.trim() === "telegram.botToken" && nextTelegram.botToken) {
|
|
347
|
+
try {
|
|
348
|
+
await telegramSetMenuButton(nextTelegram.botToken, { type: "default" });
|
|
349
|
+
console.log("Telegram menu button reset to default.");
|
|
350
|
+
} catch (error) {
|
|
351
|
+
console.error(
|
|
352
|
+
`Warning: failed to reset Telegram menu button: ${error instanceof Error ? error.message : String(error)}`
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
378
356
|
applyConfigUnset(nextBridge, nextTelegram, key.trim());
|
|
379
357
|
}
|
|
380
358
|
if (telegramTokenChanged && nextTelegram.botToken) {
|
|
@@ -383,7 +361,11 @@ function registerConfigureCommand(program2) {
|
|
|
383
361
|
nextTelegram.botUsername = bot.username;
|
|
384
362
|
nextTelegram.hasMainWebApp = bot.hasMainWebApp;
|
|
385
363
|
console.log(` Bot: @${bot.username}`);
|
|
386
|
-
await telegramSetMenuButton(nextTelegram.botToken,
|
|
364
|
+
await telegramSetMenuButton(nextTelegram.botToken, {
|
|
365
|
+
type: "web_app",
|
|
366
|
+
text: "Open",
|
|
367
|
+
web_app: { url: "https://pub.blue" }
|
|
368
|
+
});
|
|
387
369
|
console.log(" Menu button set to https://pub.blue");
|
|
388
370
|
if (!bot.hasMainWebApp) {
|
|
389
371
|
console.log("");
|
|
@@ -413,15 +395,15 @@ import * as path2 from "path";
|
|
|
413
395
|
// package.json
|
|
414
396
|
var package_default = {
|
|
415
397
|
name: "pubblue",
|
|
416
|
-
version: "0.
|
|
398
|
+
version: "0.6.1",
|
|
417
399
|
description: "CLI tool for publishing content and running interactive sessions via pub.blue",
|
|
418
400
|
type: "module",
|
|
419
401
|
bin: {
|
|
420
402
|
pubblue: "./dist/index.js"
|
|
421
403
|
},
|
|
422
404
|
scripts: {
|
|
423
|
-
build: "tsup src/index.ts src/tunnel-daemon-entry.ts
|
|
424
|
-
dev: "tsup src/index.ts src/tunnel-daemon-entry.ts
|
|
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",
|
|
425
407
|
test: "vitest run",
|
|
426
408
|
"test:watch": "vitest",
|
|
427
409
|
lint: "tsc --noEmit"
|
|
@@ -464,529 +446,258 @@ var CLI_VERSION = version;
|
|
|
464
446
|
|
|
465
447
|
// src/commands/live.ts
|
|
466
448
|
function registerLiveCommands(program2) {
|
|
467
|
-
|
|
468
|
-
|
|
449
|
+
registerStartCommand(program2);
|
|
450
|
+
registerStopCommand(program2);
|
|
469
451
|
registerStatusCommand(program2);
|
|
470
452
|
registerWriteCommand(program2);
|
|
471
453
|
registerReadCommand(program2);
|
|
472
454
|
registerChannelsCommand(program2);
|
|
473
455
|
registerDoctorCommand(program2);
|
|
474
456
|
}
|
|
475
|
-
function
|
|
476
|
-
program2.command("
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
expiresAt: pub.live.expiresAt,
|
|
492
|
-
mode: "existing",
|
|
493
|
-
slug: pub.slug,
|
|
494
|
-
url: getPublicUrl(pub.slug)
|
|
495
|
-
};
|
|
496
|
-
console.error(`Reusing existing active live for ${pub.slug}.`);
|
|
497
|
-
}
|
|
498
|
-
} catch (error) {
|
|
499
|
-
if (!(error instanceof PubApiError && error.status === 404)) {
|
|
500
|
-
failCli(`Failed to inspect pub ${slugArg}: ${formatApiError(error)}`);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
} else if (!slugArg && !opts.new) {
|
|
504
|
-
try {
|
|
505
|
-
const pubs = await apiClient.list();
|
|
506
|
-
const reusable = pickReusableLive(pubs);
|
|
507
|
-
if (reusable) {
|
|
508
|
-
if (!reusable.live) {
|
|
509
|
-
failCli("Internal error: reusable live is missing from selected pub.");
|
|
510
|
-
}
|
|
511
|
-
target = {
|
|
512
|
-
createdNew: false,
|
|
513
|
-
expiresAt: reusable.live.expiresAt,
|
|
514
|
-
mode: "existing",
|
|
515
|
-
slug: reusable.slug,
|
|
516
|
-
url: getPublicUrl(reusable.slug)
|
|
517
|
-
};
|
|
518
|
-
const activeLives = pubs.filter(
|
|
519
|
-
(p) => p.live?.status === "active" && p.live.expiresAt > Date.now()
|
|
520
|
-
);
|
|
521
|
-
if (activeLives.length > 1) {
|
|
522
|
-
console.error(
|
|
523
|
-
[
|
|
524
|
-
`Multiple active lives found: ${activeLives.map((p) => p.slug).join(", ")}`,
|
|
525
|
-
`Reusing most recent: ${reusable.slug}.`,
|
|
526
|
-
"Use `pubblue open <slug>` to choose explicitly or --new to force creation."
|
|
527
|
-
].join("\n")
|
|
528
|
-
);
|
|
529
|
-
} else {
|
|
530
|
-
console.error(
|
|
531
|
-
`Reusing existing live for ${reusable.slug}. Use --new to force creation.`
|
|
532
|
-
);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
} catch (error) {
|
|
536
|
-
failCli(`Failed to list pubs for live reuse check: ${formatApiError(error)}`);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
if (!target) {
|
|
540
|
-
try {
|
|
541
|
-
let created;
|
|
542
|
-
if (slugArg) {
|
|
543
|
-
created = await apiClient.openLive(slugArg, {
|
|
544
|
-
expiresIn: opts.expires
|
|
545
|
-
});
|
|
546
|
-
} else {
|
|
547
|
-
const newPub = await apiClient.create({});
|
|
548
|
-
try {
|
|
549
|
-
created = await apiClient.openLive(newPub.slug, {
|
|
550
|
-
expiresIn: opts.expires
|
|
551
|
-
});
|
|
552
|
-
} catch (error) {
|
|
553
|
-
try {
|
|
554
|
-
await apiClient.remove(newPub.slug);
|
|
555
|
-
} catch (cleanupError) {
|
|
556
|
-
console.error(
|
|
557
|
-
`Warning: failed to remove pub ${newPub.slug} after open failure: ${formatApiError(cleanupError)}`
|
|
558
|
-
);
|
|
559
|
-
}
|
|
560
|
-
throw error;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
target = {
|
|
564
|
-
createdNew: true,
|
|
565
|
-
expiresAt: created.expiresAt,
|
|
566
|
-
mode: "created",
|
|
567
|
-
slug: created.slug,
|
|
568
|
-
url: created.url
|
|
569
|
-
};
|
|
570
|
-
} catch (error) {
|
|
571
|
-
failCli(`Failed to go live: ${formatApiError(error)}`);
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
if (!target) {
|
|
575
|
-
failCli("Failed to resolve live target.");
|
|
576
|
-
}
|
|
577
|
-
const socketPath = getSocketPath(target.slug);
|
|
578
|
-
const infoPath = liveInfoPath(target.slug);
|
|
579
|
-
const logPath = liveLogPath(target.slug);
|
|
457
|
+
function registerStartCommand(program2) {
|
|
458
|
+
program2.command("start").description("Start the agent daemon (registers presence, awaits live requests)").requiredOption("--agent-name <name>", "Agent display name shown to the browser user").option("--bridge <mode>", "Bridge mode: openclaw|none").option("--foreground", "Run in foreground (don't fork)").action(async (opts) => {
|
|
459
|
+
await ensureNodeDatachannelAvailable();
|
|
460
|
+
writeLatestCliVersion(CLI_VERSION);
|
|
461
|
+
const runtimeConfig = getConfig();
|
|
462
|
+
const apiClient = createApiClient(runtimeConfig);
|
|
463
|
+
const bridgeMode = resolveBridgeMode(opts);
|
|
464
|
+
const bridgeProcessEnv = buildBridgeProcessEnv(runtimeConfig.bridge);
|
|
465
|
+
const socketPath = getAgentSocketPath();
|
|
466
|
+
const infoPath = liveInfoPath("agent");
|
|
467
|
+
const logPath = liveLogPath("agent");
|
|
468
|
+
await stopOtherDaemons();
|
|
469
|
+
if (opts.foreground) {
|
|
470
|
+
const { startDaemon } = await import("./tunnel-daemon-BR5XKNEA.js");
|
|
471
|
+
console.log("Agent daemon starting in foreground...");
|
|
472
|
+
console.log("Press Ctrl+C to stop.");
|
|
580
473
|
try {
|
|
581
|
-
await
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
}
|
|
585
|
-
if (opts.foreground) {
|
|
586
|
-
if (bridgeMode !== "none") {
|
|
587
|
-
throw new Error(
|
|
588
|
-
"Foreground mode disables managed bridge process. Use background mode for --bridge openclaw."
|
|
589
|
-
);
|
|
590
|
-
}
|
|
591
|
-
const { startDaemon } = await import("./tunnel-daemon-QN6TVUX6.js");
|
|
592
|
-
console.log(`Live started: ${target.url}`);
|
|
593
|
-
const fgTma = getTelegramMiniAppUrl(target.slug);
|
|
594
|
-
if (fgTma) console.log(`Telegram: ${fgTma}`);
|
|
595
|
-
console.log(`Slug: ${target.slug}`);
|
|
596
|
-
console.log(`Expires: ${new Date(target.expiresAt).toISOString()}`);
|
|
597
|
-
if (target.mode === "existing") console.log("Mode: attached existing live");
|
|
598
|
-
console.log("Running in foreground. Press Ctrl+C to stop.");
|
|
599
|
-
try {
|
|
600
|
-
await startDaemon({
|
|
601
|
-
cliVersion: CLI_VERSION,
|
|
602
|
-
slug: target.slug,
|
|
603
|
-
apiClient,
|
|
604
|
-
socketPath,
|
|
605
|
-
infoPath
|
|
606
|
-
});
|
|
607
|
-
} catch (error) {
|
|
608
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
609
|
-
failCli(`Daemon failed: ${message}`);
|
|
610
|
-
}
|
|
611
|
-
return;
|
|
612
|
-
}
|
|
613
|
-
const runningDaemonInfo = readDaemonProcessInfo(target.slug);
|
|
614
|
-
if (runningDaemonInfo) {
|
|
615
|
-
const daemonVersion = runningDaemonInfo.cliVersion;
|
|
616
|
-
const shouldRestartForUpgrade = shouldRestartDaemonForCliUpgrade(
|
|
617
|
-
daemonVersion,
|
|
618
|
-
CLI_VERSION
|
|
619
|
-
);
|
|
620
|
-
if (shouldRestartForUpgrade) {
|
|
621
|
-
console.error(
|
|
622
|
-
`Restarting daemon for CLI version ${CLI_VERSION} (running: ${daemonVersion || "unknown"}).`
|
|
623
|
-
);
|
|
624
|
-
const bridgeError = await stopBridge(target.slug);
|
|
625
|
-
if (bridgeError) failCli(bridgeError);
|
|
626
|
-
try {
|
|
627
|
-
await ipcCall(socketPath, { method: "close", params: {} });
|
|
628
|
-
} catch (error) {
|
|
629
|
-
failCli(
|
|
630
|
-
[
|
|
631
|
-
`Failed to stop running daemon for upgrade: ${error instanceof Error ? error.message : String(error)}`,
|
|
632
|
-
"Run `pubblue close <slug>` and retry."
|
|
633
|
-
].join("\n")
|
|
634
|
-
);
|
|
635
|
-
}
|
|
636
|
-
const daemonStopped = await waitForProcessExit(runningDaemonInfo.pid, 6e3);
|
|
637
|
-
if (!daemonStopped) {
|
|
638
|
-
failCli("Daemon did not stop in time during upgrade restart.");
|
|
639
|
-
}
|
|
640
|
-
} else {
|
|
641
|
-
try {
|
|
642
|
-
const status = await ipcCall(socketPath, { method: "status", params: {} });
|
|
643
|
-
if (!status.ok) throw new Error(String(status.error || "status check failed"));
|
|
644
|
-
} catch (error) {
|
|
645
|
-
failCli(
|
|
646
|
-
[
|
|
647
|
-
`Daemon process exists but is not responding: ${error instanceof Error ? error.message : String(error)}`,
|
|
648
|
-
"Run `pubblue close <slug>` and start again."
|
|
649
|
-
].join("\n")
|
|
650
|
-
);
|
|
651
|
-
}
|
|
652
|
-
if (bridgeMode !== "none") {
|
|
653
|
-
const bridgeReady = await ensureBridgeReady({
|
|
654
|
-
bridgeMode,
|
|
655
|
-
slug: target.slug,
|
|
656
|
-
socketPath,
|
|
657
|
-
bridgeProcessEnv,
|
|
658
|
-
timeoutMs: 8e3
|
|
659
|
-
});
|
|
660
|
-
if (!bridgeReady.ok) {
|
|
661
|
-
const lines = [
|
|
662
|
-
`Bridge failed to start for running live: ${bridgeReady.reason ?? "unknown reason"}`
|
|
663
|
-
];
|
|
664
|
-
const existingBridgeLog = bridgeLogPath(target.slug);
|
|
665
|
-
if (fs2.existsSync(existingBridgeLog)) {
|
|
666
|
-
lines.push(`Bridge log: ${existingBridgeLog}`);
|
|
667
|
-
const bridgeTail = readLogTail(existingBridgeLog);
|
|
668
|
-
if (bridgeTail) {
|
|
669
|
-
lines.push("---- bridge log tail ----");
|
|
670
|
-
lines.push(bridgeTail.trimEnd());
|
|
671
|
-
lines.push("---- end bridge log tail ----");
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
failCli(lines.join("\n"));
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
console.log(`Live started: ${target.url}`);
|
|
678
|
-
const runTma = getTelegramMiniAppUrl(target.slug);
|
|
679
|
-
if (runTma) console.log(`Telegram: ${runTma}`);
|
|
680
|
-
console.log(`Slug: ${target.slug}`);
|
|
681
|
-
console.log(`Expires: ${new Date(target.expiresAt).toISOString()}`);
|
|
682
|
-
console.log("Daemon already running for this live.");
|
|
683
|
-
console.log(`Daemon log: ${logPath}`);
|
|
684
|
-
if (bridgeMode !== "none") {
|
|
685
|
-
console.log("Bridge mode: openclaw");
|
|
686
|
-
console.log(`Bridge log: ${bridgeLogPath(target.slug)}`);
|
|
687
|
-
}
|
|
688
|
-
return;
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
const { fork } = await import("child_process");
|
|
692
|
-
const daemonScript = path2.join(import.meta.dirname, "tunnel-daemon-entry.js");
|
|
693
|
-
const bridgeScript = path2.join(import.meta.dirname, "tunnel-bridge-entry.js");
|
|
694
|
-
const daemonLogFd = fs2.openSync(logPath, "a");
|
|
695
|
-
const child = fork(daemonScript, [], {
|
|
696
|
-
detached: true,
|
|
697
|
-
stdio: buildDaemonForkStdio(daemonLogFd),
|
|
698
|
-
env: {
|
|
699
|
-
...bridgeProcessEnv,
|
|
700
|
-
PUBBLUE_DAEMON_SLUG: target.slug,
|
|
701
|
-
PUBBLUE_DAEMON_BASE_URL: runtimeConfig.baseUrl,
|
|
702
|
-
PUBBLUE_DAEMON_API_KEY: runtimeConfig.apiKey,
|
|
703
|
-
PUBBLUE_DAEMON_SOCKET: socketPath,
|
|
704
|
-
PUBBLUE_DAEMON_INFO: infoPath,
|
|
705
|
-
PUBBLUE_CLI_VERSION: CLI_VERSION,
|
|
706
|
-
PUBBLUE_DAEMON_BRIDGE_MODE: bridgeMode,
|
|
707
|
-
PUBBLUE_DAEMON_BRIDGE_SCRIPT: bridgeScript,
|
|
708
|
-
PUBBLUE_DAEMON_BRIDGE_INFO: bridgeInfoPath(target.slug),
|
|
709
|
-
PUBBLUE_DAEMON_BRIDGE_LOG: bridgeLogPath(target.slug)
|
|
710
|
-
}
|
|
711
|
-
});
|
|
712
|
-
fs2.closeSync(daemonLogFd);
|
|
713
|
-
if (child.connected) {
|
|
714
|
-
child.disconnect();
|
|
715
|
-
}
|
|
716
|
-
child.unref();
|
|
717
|
-
console.log(`Starting daemon for ${target.slug}...`);
|
|
718
|
-
const ready = await waitForDaemonReady({
|
|
719
|
-
child,
|
|
720
|
-
infoPath,
|
|
721
|
-
socketPath,
|
|
722
|
-
timeoutMs: 8e3
|
|
723
|
-
});
|
|
724
|
-
if (!ready.ok) {
|
|
725
|
-
const lines = [
|
|
726
|
-
`Daemon failed to start: ${ready.reason ?? "unknown reason"}`,
|
|
727
|
-
`Daemon log: ${logPath}`
|
|
728
|
-
];
|
|
729
|
-
const tail = readLogTail(logPath);
|
|
730
|
-
if (tail) {
|
|
731
|
-
lines.push("---- daemon log tail ----");
|
|
732
|
-
lines.push(tail.trimEnd());
|
|
733
|
-
lines.push("---- end daemon log tail ----");
|
|
734
|
-
}
|
|
735
|
-
await cleanupLiveOnStartFailure(apiClient, target);
|
|
736
|
-
failCli(lines.join("\n"));
|
|
737
|
-
}
|
|
738
|
-
const offerReady = await waitForAgentOffer({
|
|
739
|
-
apiClient,
|
|
740
|
-
slug: target.slug,
|
|
741
|
-
timeoutMs: 5e3
|
|
742
|
-
});
|
|
743
|
-
if (!offerReady.ok) {
|
|
744
|
-
const lines = [
|
|
745
|
-
`Daemon started but signaling is not ready: ${offerReady.reason}`,
|
|
746
|
-
`Daemon log: ${logPath}`
|
|
747
|
-
];
|
|
748
|
-
const tail = readLogTail(logPath);
|
|
749
|
-
if (tail) {
|
|
750
|
-
lines.push("---- daemon log tail ----");
|
|
751
|
-
lines.push(tail.trimEnd());
|
|
752
|
-
lines.push("---- end daemon log tail ----");
|
|
753
|
-
}
|
|
754
|
-
await cleanupLiveOnStartFailure(apiClient, target);
|
|
755
|
-
failCli(lines.join("\n"));
|
|
756
|
-
}
|
|
757
|
-
if (bridgeMode !== "none") {
|
|
758
|
-
const bridgeReady = await ensureBridgeReady({
|
|
759
|
-
bridgeMode,
|
|
760
|
-
slug: target.slug,
|
|
474
|
+
await startDaemon({
|
|
475
|
+
cliVersion: CLI_VERSION,
|
|
476
|
+
apiClient,
|
|
761
477
|
socketPath,
|
|
762
|
-
|
|
763
|
-
|
|
478
|
+
infoPath,
|
|
479
|
+
bridgeMode,
|
|
480
|
+
agentName: opts.agentName
|
|
764
481
|
});
|
|
765
|
-
if (!bridgeReady.ok) {
|
|
766
|
-
const lines = [`Bridge failed to start: ${bridgeReady.reason ?? "unknown reason"}`];
|
|
767
|
-
const bridgeLog = bridgeLogPath(target.slug);
|
|
768
|
-
if (fs2.existsSync(bridgeLog)) {
|
|
769
|
-
lines.push(`Bridge log: ${bridgeLog}`);
|
|
770
|
-
const bridgeTail = readLogTail(bridgeLog);
|
|
771
|
-
if (bridgeTail) {
|
|
772
|
-
lines.push("---- bridge log tail ----");
|
|
773
|
-
lines.push(bridgeTail.trimEnd());
|
|
774
|
-
lines.push("---- end bridge log tail ----");
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
let daemonCloseWarning = null;
|
|
778
|
-
try {
|
|
779
|
-
await ipcCall(socketPath, { method: "close", params: {} });
|
|
780
|
-
} catch (error) {
|
|
781
|
-
daemonCloseWarning = `failed to stop daemon after bridge startup failure: ${error instanceof Error ? error.message : String(error)}`;
|
|
782
|
-
}
|
|
783
|
-
if (daemonCloseWarning) {
|
|
784
|
-
lines.push(`Warning: ${daemonCloseWarning}`);
|
|
785
|
-
}
|
|
786
|
-
await cleanupLiveOnStartFailure(apiClient, target);
|
|
787
|
-
failCli(lines.join("\n"));
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
console.log(`Live started: ${target.url}`);
|
|
791
|
-
const tma = getTelegramMiniAppUrl(target.slug);
|
|
792
|
-
if (tma) console.log(`Telegram: ${tma}`);
|
|
793
|
-
console.log(`Slug: ${target.slug}`);
|
|
794
|
-
console.log(`Expires: ${new Date(target.expiresAt).toISOString()}`);
|
|
795
|
-
if (target.mode === "existing") console.log("Mode: attached existing live");
|
|
796
|
-
console.log("Daemon health: OK");
|
|
797
|
-
console.log(`Daemon log: ${logPath}`);
|
|
798
|
-
if (bridgeMode !== "none") {
|
|
799
|
-
console.log("Bridge mode: openclaw");
|
|
800
|
-
console.log(`Bridge log: ${bridgeLogPath(target.slug)}`);
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
);
|
|
804
|
-
}
|
|
805
|
-
function registerCloseCommand(program2) {
|
|
806
|
-
program2.command("close").description("Close a live and stop its daemon").argument("<slug>", "Pub slug").action(async (slug) => {
|
|
807
|
-
const bridgeError = await stopBridge(slug);
|
|
808
|
-
if (bridgeError) console.error(bridgeError);
|
|
809
|
-
fs2.rmSync(bridgeInfoPath(slug), { force: true });
|
|
810
|
-
const socketPath = getSocketPath(slug);
|
|
811
|
-
if (isDaemonRunning(slug)) {
|
|
812
|
-
try {
|
|
813
|
-
await ipcCall(socketPath, { method: "close", params: {} });
|
|
814
482
|
} catch (error) {
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
);
|
|
483
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
484
|
+
failCli(`Daemon failed: ${message}`);
|
|
818
485
|
}
|
|
486
|
+
return;
|
|
819
487
|
}
|
|
820
|
-
const
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
488
|
+
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");
|
|
491
|
+
const child = fork(daemonScript, [], {
|
|
492
|
+
detached: true,
|
|
493
|
+
stdio: buildDaemonForkStdio(daemonLogFd),
|
|
494
|
+
env: {
|
|
495
|
+
...bridgeProcessEnv,
|
|
496
|
+
PUBBLUE_DAEMON_BASE_URL: runtimeConfig.baseUrl,
|
|
497
|
+
PUBBLUE_DAEMON_API_KEY: runtimeConfig.apiKey,
|
|
498
|
+
PUBBLUE_DAEMON_SOCKET: socketPath,
|
|
499
|
+
PUBBLUE_DAEMON_INFO: infoPath,
|
|
500
|
+
PUBBLUE_DAEMON_AGENT_NAME: opts.agentName,
|
|
501
|
+
PUBBLUE_CLI_VERSION: CLI_VERSION,
|
|
502
|
+
PUBBLUE_DAEMON_BRIDGE_MODE: bridgeMode
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
fs2.closeSync(daemonLogFd);
|
|
506
|
+
if (child.connected) {
|
|
507
|
+
child.disconnect();
|
|
828
508
|
}
|
|
829
|
-
|
|
509
|
+
child.unref();
|
|
510
|
+
console.log("Starting agent daemon...");
|
|
511
|
+
const ready = await waitForDaemonReady({
|
|
512
|
+
child,
|
|
513
|
+
infoPath,
|
|
514
|
+
socketPath,
|
|
515
|
+
timeoutMs: 8e3
|
|
516
|
+
});
|
|
517
|
+
if (!ready.ok) {
|
|
518
|
+
const lines = [
|
|
519
|
+
`Daemon failed to start: ${ready.reason ?? "unknown reason"}`,
|
|
520
|
+
`Daemon log: ${logPath}`
|
|
521
|
+
];
|
|
522
|
+
const tail = readLogTail(logPath);
|
|
523
|
+
if (tail) {
|
|
524
|
+
lines.push("---- daemon log tail ----");
|
|
525
|
+
lines.push(tail.trimEnd());
|
|
526
|
+
lines.push("---- end daemon log tail ----");
|
|
527
|
+
}
|
|
528
|
+
failCli(lines.join("\n"));
|
|
529
|
+
}
|
|
530
|
+
console.log("Agent daemon started. Waiting for browser to initiate live.");
|
|
531
|
+
console.log(`Daemon log: ${logPath}`);
|
|
532
|
+
console.log(`Bridge mode: ${bridgeMode}`);
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
function registerStopCommand(program2) {
|
|
536
|
+
program2.command("stop").description("Stop the agent daemon (deregisters presence, closes active live)").action(async () => {
|
|
537
|
+
if (!isDaemonRunning("agent")) {
|
|
538
|
+
console.log("Agent daemon is not running.");
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
await stopOtherDaemons();
|
|
542
|
+
console.log("Agent daemon stopped.");
|
|
830
543
|
});
|
|
831
544
|
}
|
|
832
545
|
function registerStatusCommand(program2) {
|
|
833
|
-
program2.command("status").description("Check live connection status").
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
|
|
546
|
+
program2.command("status").description("Check agent daemon and live connection status").action(async () => {
|
|
547
|
+
const socketPath = getAgentSocketPath();
|
|
548
|
+
let response;
|
|
549
|
+
try {
|
|
550
|
+
response = await ipcCall(socketPath, { method: "status", params: {} });
|
|
551
|
+
} catch {
|
|
552
|
+
console.log("Agent daemon is not running.");
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const activeSlug = response.activeSlug;
|
|
556
|
+
console.log(` Daemon: running`);
|
|
557
|
+
console.log(` Active slug: ${activeSlug || "(none)"}`);
|
|
837
558
|
console.log(` Status: ${response.connected ? "connected" : "waiting"}`);
|
|
838
559
|
console.log(` Uptime: ${response.uptime}s`);
|
|
839
560
|
const chNames = Array.isArray(response.channels) ? response.channels.map((c) => typeof c === "string" ? c : String(c)) : [];
|
|
840
|
-
console.log(` Channels: ${chNames.join(", ")}`);
|
|
561
|
+
console.log(` Channels: ${chNames.join(", ") || "(none)"}`);
|
|
841
562
|
console.log(` Buffered: ${response.bufferedMessages ?? 0} messages`);
|
|
842
563
|
if (typeof response.lastError === "string" && response.lastError.length > 0) {
|
|
843
564
|
console.log(` Last error: ${response.lastError}`);
|
|
844
565
|
}
|
|
845
|
-
const logPath = liveLogPath(
|
|
566
|
+
const logPath = liveLogPath("agent");
|
|
846
567
|
if (fs2.existsSync(logPath)) {
|
|
847
568
|
console.log(` Log: ${logPath}`);
|
|
848
569
|
}
|
|
849
|
-
const
|
|
850
|
-
if (
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
if (bridgeInfo.sessionId) {
|
|
855
|
-
console.log(` Bridge session: ${bridgeInfo.sessionId}`);
|
|
570
|
+
const bridge = response.bridge;
|
|
571
|
+
if (bridge) {
|
|
572
|
+
console.log(` Bridge: openclaw (${bridge.running ? "running" : "stopped"})`);
|
|
573
|
+
if (bridge.sessionId) {
|
|
574
|
+
console.log(` Bridge session: ${bridge.sessionId}`);
|
|
856
575
|
}
|
|
857
|
-
if (
|
|
858
|
-
console.log(` Bridge session source: ${
|
|
576
|
+
if (bridge.sessionSource) {
|
|
577
|
+
console.log(` Bridge session source: ${bridge.sessionSource}`);
|
|
859
578
|
}
|
|
860
|
-
if (
|
|
861
|
-
console.log(` Bridge session key: ${
|
|
579
|
+
if (bridge.sessionKey) {
|
|
580
|
+
console.log(` Bridge session key: ${bridge.sessionKey}`);
|
|
862
581
|
}
|
|
863
|
-
if (
|
|
864
|
-
console.log(` Bridge
|
|
582
|
+
if (bridge.forwardedMessages !== void 0) {
|
|
583
|
+
console.log(` Bridge forwarded: ${bridge.forwardedMessages} messages`);
|
|
584
|
+
}
|
|
585
|
+
if (bridge.lastError) {
|
|
586
|
+
console.log(` Bridge last error: ${bridge.lastError}`);
|
|
865
587
|
}
|
|
866
|
-
}
|
|
867
|
-
const bridgeLog = bridgeLogPath(slug);
|
|
868
|
-
if (fs2.existsSync(bridgeLog)) {
|
|
869
|
-
console.log(` Bridge log: ${bridgeLog}`);
|
|
870
588
|
}
|
|
871
589
|
});
|
|
872
590
|
}
|
|
873
591
|
function registerWriteCommand(program2) {
|
|
874
|
-
program2.command("write").description("Write data to a live channel").argument("[message]", "Text message (or use --file)").option("-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
} else if (TEXT_FILE_EXTENSIONS.has(ext)) {
|
|
891
|
-
msg = {
|
|
892
|
-
id: generateMessageId(),
|
|
893
|
-
type: "text",
|
|
894
|
-
data: bytes.toString("utf-8"),
|
|
895
|
-
meta: { filename, mime: getMimeType(filePath), size: bytes.length }
|
|
896
|
-
};
|
|
897
|
-
} else {
|
|
898
|
-
msg = {
|
|
899
|
-
id: generateMessageId(),
|
|
900
|
-
type: "binary",
|
|
901
|
-
meta: { filename, mime: getMimeType(filePath), size: bytes.length }
|
|
902
|
-
};
|
|
903
|
-
binaryBase64 = bytes.toString("base64");
|
|
904
|
-
}
|
|
905
|
-
} else if (messageArg) {
|
|
592
|
+
program2.command("write").description("Write data to a live channel").argument("[message]", "Text message (or use --file)").option("-c, --channel <channel>", "Channel name", "chat").option("-f, --file <file>", "Read content from file").action(async (messageArg, opts) => {
|
|
593
|
+
let msg;
|
|
594
|
+
let binaryBase64;
|
|
595
|
+
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);
|
|
600
|
+
if (ext === ".html" || ext === ".htm") {
|
|
601
|
+
msg = {
|
|
602
|
+
id: generateMessageId(),
|
|
603
|
+
type: "html",
|
|
604
|
+
data: bytes.toString("utf-8"),
|
|
605
|
+
meta: { title: filename, filename, mime: getMimeType(filePath), size: bytes.length }
|
|
606
|
+
};
|
|
607
|
+
} else if (TEXT_FILE_EXTENSIONS.has(ext)) {
|
|
906
608
|
msg = {
|
|
907
609
|
id: generateMessageId(),
|
|
908
610
|
type: "text",
|
|
909
|
-
data:
|
|
611
|
+
data: bytes.toString("utf-8"),
|
|
612
|
+
meta: { filename, mime: getMimeType(filePath), size: bytes.length }
|
|
910
613
|
};
|
|
911
614
|
} else {
|
|
912
|
-
const chunks = [];
|
|
913
|
-
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
914
615
|
msg = {
|
|
915
616
|
id: generateMessageId(),
|
|
916
|
-
type: "
|
|
917
|
-
|
|
617
|
+
type: "binary",
|
|
618
|
+
meta: { filename, mime: getMimeType(filePath), size: bytes.length }
|
|
918
619
|
};
|
|
620
|
+
binaryBase64 = bytes.toString("base64");
|
|
919
621
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
622
|
+
} else if (messageArg) {
|
|
623
|
+
msg = {
|
|
624
|
+
id: generateMessageId(),
|
|
625
|
+
type: "text",
|
|
626
|
+
data: messageArg
|
|
627
|
+
};
|
|
628
|
+
} else {
|
|
629
|
+
const chunks = [];
|
|
630
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
631
|
+
msg = {
|
|
632
|
+
id: generateMessageId(),
|
|
633
|
+
type: "text",
|
|
634
|
+
data: Buffer.concat(chunks).toString("utf-8").trim()
|
|
635
|
+
};
|
|
929
636
|
}
|
|
930
|
-
|
|
637
|
+
const socketPath = getAgentSocketPath();
|
|
638
|
+
const response = await ipcCall(socketPath, {
|
|
639
|
+
method: "write",
|
|
640
|
+
params: { channel: opts.channel, msg, binaryBase64 }
|
|
641
|
+
});
|
|
642
|
+
if (!response.ok) {
|
|
643
|
+
failCli(`Failed: ${response.error}`);
|
|
644
|
+
}
|
|
645
|
+
});
|
|
931
646
|
}
|
|
932
647
|
function registerReadCommand(program2) {
|
|
933
|
-
program2.command("read").description("Read buffered messages from live channels").
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
for (const m of response.messages) {
|
|
959
|
-
console.log(JSON.stringify(m));
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
} catch (error) {
|
|
963
|
-
consecutiveFailures += 1;
|
|
964
|
-
if (!warnedDisconnected) {
|
|
965
|
-
const detail = error instanceof Error ? ` ${error.message}` : "";
|
|
966
|
-
console.error(`Daemon disconnected. Waiting for recovery...${detail}`);
|
|
967
|
-
warnedDisconnected = true;
|
|
648
|
+
program2.command("read").description("Read buffered messages from live channels").option("-c, --channel <channel>", "Filter by channel").option("--follow", "Stream messages continuously").option("--all", "With --follow, include all channels instead of chat-only default").action(async (opts) => {
|
|
649
|
+
const socketPath = getAgentSocketPath();
|
|
650
|
+
const readChannel = opts.channel || (opts.follow && !opts.all ? CHANNELS.CHAT : void 0);
|
|
651
|
+
if (opts.follow) {
|
|
652
|
+
if (!opts.channel && !opts.all) {
|
|
653
|
+
console.error(
|
|
654
|
+
"Following chat channel by default. Use `--all` to include binary/file channels."
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
let consecutiveFailures = 0;
|
|
658
|
+
let warnedDisconnected = false;
|
|
659
|
+
while (true) {
|
|
660
|
+
try {
|
|
661
|
+
const response = await ipcCall(socketPath, {
|
|
662
|
+
method: "read",
|
|
663
|
+
params: { channel: readChannel }
|
|
664
|
+
});
|
|
665
|
+
if (warnedDisconnected) {
|
|
666
|
+
console.error("Daemon reconnected.");
|
|
667
|
+
warnedDisconnected = false;
|
|
668
|
+
}
|
|
669
|
+
consecutiveFailures = 0;
|
|
670
|
+
if (response.messages && response.messages.length > 0) {
|
|
671
|
+
for (const m of response.messages) {
|
|
672
|
+
console.log(JSON.stringify(m));
|
|
968
673
|
}
|
|
969
674
|
}
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
});
|
|
978
|
-
if (!response.ok) {
|
|
979
|
-
failCli(`Failed: ${response.error}`);
|
|
675
|
+
} catch (error) {
|
|
676
|
+
consecutiveFailures += 1;
|
|
677
|
+
if (!warnedDisconnected) {
|
|
678
|
+
const detail = error instanceof Error ? ` ${error.message}` : "";
|
|
679
|
+
console.error(`Daemon disconnected. Waiting for recovery...${detail}`);
|
|
680
|
+
warnedDisconnected = true;
|
|
681
|
+
}
|
|
980
682
|
}
|
|
981
|
-
|
|
683
|
+
const delayMs = getFollowReadDelayMs(warnedDisconnected, consecutiveFailures);
|
|
684
|
+
await new Promise((resolve3) => setTimeout(resolve3, delayMs));
|
|
982
685
|
}
|
|
686
|
+
} else {
|
|
687
|
+
const response = await ipcCall(socketPath, {
|
|
688
|
+
method: "read",
|
|
689
|
+
params: { channel: readChannel }
|
|
690
|
+
});
|
|
691
|
+
if (!response.ok) {
|
|
692
|
+
failCli(`Failed: ${response.error}`);
|
|
693
|
+
}
|
|
694
|
+
console.log(JSON.stringify(response.messages || [], null, 2));
|
|
983
695
|
}
|
|
984
|
-
);
|
|
696
|
+
});
|
|
985
697
|
}
|
|
986
698
|
function registerChannelsCommand(program2) {
|
|
987
|
-
program2.command("channels").description("List active live channels").
|
|
988
|
-
const
|
|
989
|
-
const socketPath = getSocketPath(slug);
|
|
699
|
+
program2.command("channels").description("List active live channels").action(async () => {
|
|
700
|
+
const socketPath = getAgentSocketPath();
|
|
990
701
|
const response = await ipcCall(socketPath, { method: "channels", params: {} });
|
|
991
702
|
if (response.channels) {
|
|
992
703
|
for (const ch of response.channels) {
|
|
@@ -996,12 +707,12 @@ function registerChannelsCommand(program2) {
|
|
|
996
707
|
});
|
|
997
708
|
}
|
|
998
709
|
function registerDoctorCommand(program2) {
|
|
999
|
-
program2.command("doctor").description("Run end-to-end live checks (daemon, channels, chat/canvas ping)").option("
|
|
710
|
+
program2.command("doctor").description("Run end-to-end live checks (daemon, channels, chat/canvas ping)").option("--timeout <seconds>", "Timeout for pong wait and repeated reads", "30").option("--wait-pong", "Wait for user to reply with exact text 'pong' on chat channel").option("--skip-chat", "Skip chat ping check").option("--skip-canvas", "Skip canvas ping check").action(
|
|
1000
711
|
async (opts) => {
|
|
1001
712
|
const timeoutSeconds = parsePositiveIntegerOption(opts.timeout, "--timeout");
|
|
1002
713
|
const timeoutMs = timeoutSeconds * 1e3;
|
|
1003
|
-
const
|
|
1004
|
-
const
|
|
714
|
+
const socketPath = getAgentSocketPath();
|
|
715
|
+
const slug = await resolveActiveSlug();
|
|
1005
716
|
const apiClient = createApiClient();
|
|
1006
717
|
const fail = (message) => failCli(`Doctor failed: ${message}`);
|
|
1007
718
|
console.log(`Doctor: ${slug}`);
|
|
@@ -1047,8 +758,11 @@ function registerDoctorCommand(program2) {
|
|
|
1047
758
|
if (live.expiresAt <= Date.now()) {
|
|
1048
759
|
fail("API reports live is expired.");
|
|
1049
760
|
}
|
|
1050
|
-
if (typeof live.
|
|
1051
|
-
fail("
|
|
761
|
+
if (typeof live.browserOffer !== "string" || live.browserOffer.length === 0) {
|
|
762
|
+
fail("browser offer was not published.");
|
|
763
|
+
}
|
|
764
|
+
if (typeof live.agentAnswer !== "string" || live.agentAnswer.length === 0) {
|
|
765
|
+
fail("agent answer was not published.");
|
|
1052
766
|
}
|
|
1053
767
|
console.log("API/signaling check: OK");
|
|
1054
768
|
if (!opts.skipChat) {
|
|
@@ -1117,7 +831,7 @@ function registerDoctorCommand(program2) {
|
|
|
1117
831
|
|
|
1118
832
|
// src/commands/pubs.ts
|
|
1119
833
|
function registerPubCommands(program2) {
|
|
1120
|
-
program2.command("create").description("Create a new pub").argument("[file]", "Path to the file (reads stdin if omitted)").option("--slug <slug>", "Custom slug for the URL").option("--title <title>", "Title for the pub").option("--public", "Make the pub public").option("--private", "Make the pub private (default)").option("--expires <duration>", "Auto-delete after duration (e.g. 1h, 24h, 7d)").
|
|
834
|
+
program2.command("create").description("Create a new pub").argument("[file]", "Path to the file (reads stdin if omitted)").option("--slug <slug>", "Custom slug for the URL").option("--title <title>", "Title for the pub").option("--public", "Make the pub public").option("--private", "Make the pub private (default)").option("--expires <duration>", "Auto-delete after duration (e.g. 1h, 24h, 7d)").action(
|
|
1121
835
|
async (fileArg, opts) => {
|
|
1122
836
|
const client = createClient();
|
|
1123
837
|
let content;
|
|
@@ -1126,7 +840,7 @@ function registerPubCommands(program2) {
|
|
|
1126
840
|
const file = readFile(fileArg);
|
|
1127
841
|
content = file.content;
|
|
1128
842
|
filename = file.basename;
|
|
1129
|
-
} else
|
|
843
|
+
} else {
|
|
1130
844
|
content = await readFromStdin();
|
|
1131
845
|
}
|
|
1132
846
|
const resolvedVisibility = resolveVisibilityFlags({
|
|
@@ -1148,10 +862,6 @@ function registerPubCommands(program2) {
|
|
|
1148
862
|
if (result.expiresAt) {
|
|
1149
863
|
console.log(` Expires: ${new Date(result.expiresAt).toISOString()}`);
|
|
1150
864
|
}
|
|
1151
|
-
if (opts.open) {
|
|
1152
|
-
console.log(`
|
|
1153
|
-
To open an interactive session, use: pubblue open ${result.slug}`);
|
|
1154
|
-
}
|
|
1155
865
|
}
|
|
1156
866
|
);
|
|
1157
867
|
program2.command("get").description("Get details of a pub").argument("<slug>", "Slug of the pub").option("--content", "Output raw content to stdout (no metadata, pipeable)").action(async (slug, opts) => {
|