auvezy-terminal-remote 0.7.1 → 0.7.3
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/README.md +44 -30
- package/dist/cli.js +191 -69
- package/frontend-dist/assets/{eruda-Di8R7tKR.js → eruda-D1P5Ouj7.js} +1 -1
- package/frontend-dist/assets/index-C4eh00Gk.js +359 -0
- package/frontend-dist/assets/{index-jLMclWkI.css → index-D1Dp0iBu.css} +1 -1
- package/frontend-dist/assets/zxing_reader-9hFayGnH.wasm +0 -0
- package/frontend-dist/index.html +2 -2
- package/frontend-dist/sw.js +1 -1
- package/package.json +1 -1
- package/frontend-dist/assets/index-DLn07Svp.js +0 -359
package/README.md
CHANGED
|
@@ -15,22 +15,36 @@ Remote-control any terminal program on your PC from a phone or tablet
|
|
|
15
15
|
browser over LAN. Start a broker once at boot — open the browser any
|
|
16
16
|
time to log in, create instances, and run Claude / your shell / any TUI.
|
|
17
17
|
|
|
18
|
-
<img src="./frontend/public/screenshots/desktop.png" alt="Webapp running Claude Code in a browser tab" width="
|
|
18
|
+
<img src="./frontend/public/screenshots/desktop.png" alt="Webapp running Claude Code in a browser tab" width="960">
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
<img src="./frontend/public/screenshots/mobile.png" alt="Webapp on a phone screen" width="400">
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
</div>
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
- **
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
-
|
|
30
|
-
- **
|
|
31
|
-
|
|
32
|
-
-
|
|
33
|
-
- **
|
|
24
|
+
## ✨ Highlights
|
|
25
|
+
|
|
26
|
+
- **Mobile browser as a first-class terminal client** — full-fidelity PTY
|
|
27
|
+
for any program (`claude`, `vim`, `htop`, your shell). The mobile UI ships
|
|
28
|
+
with on-screen shortcut keys, IME-safe input handling, swipe-to-scroll,
|
|
29
|
+
viewport-aware sizing, and an installable PWA manifest.
|
|
30
|
+
- **TUI / Claude Code adaptation** — handles Ink/Yoga reflow on resize so
|
|
31
|
+
Claude does not blank on device rotation; an alt-screen blocklist keeps
|
|
32
|
+
full-screen TUIs (`claude`, `tmux`, `lazygit`, …) clean across reconnects.
|
|
33
|
+
- **Reconnect with replay** — scrollback is rehydrated on every reconnect,
|
|
34
|
+
so transient network drops, lock-screen, or sleeping the device do not
|
|
35
|
+
lose context.
|
|
36
|
+
- **Multi-instance with unified tab bar** — each `atr <program>` runs as an
|
|
37
|
+
independent subprocess at its own URL (`/i/<id>/`); the webapp surfaces
|
|
38
|
+
every active instance in a single tab strip.
|
|
39
|
+
- **Configurable from the settings panel** — on-screen shortcut keys,
|
|
40
|
+
saved command snippets, per-device font size, terminal theme, scrollback
|
|
41
|
+
size, and hook integrations are user-configurable. All preferences are
|
|
42
|
+
persisted to `~/.atrrc`.
|
|
43
|
+
- **LAN-only architecture** — a single shared token (timing-safe comparison),
|
|
44
|
+
workers bound to `127.0.0.1`, and the broker as the sole outward-facing
|
|
45
|
+
process. No public server, no third-party relay.
|
|
46
|
+
- **One-step boot autostart** — `atr install` generates the systemd /
|
|
47
|
+
launchd unit; the service comes up automatically on reboot.
|
|
34
48
|
|
|
35
49
|
Full list: [`docs/FEATURES.md`](./docs/FEATURES.md).
|
|
36
50
|
|
|
@@ -40,23 +54,23 @@ Full list: [`docs/FEATURES.md`](./docs/FEATURES.md).
|
|
|
40
54
|
**worker** is a single PTY instance.
|
|
41
55
|
|
|
42
56
|
```
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
Browser / phone Your PC
|
|
58
|
+
┌──────────────┐ ┌──────────────────────────────────┐
|
|
59
|
+
│ │ ws://host │ broker (LAN: 0.0.0.0:3000) │
|
|
60
|
+
│ webapp PWA │ ──────────────►│ ├─ /api/* (auth / instances / │
|
|
61
|
+
│ │ │ │ push / config / …) │
|
|
62
|
+
│ │ │ ├─ /i/<id>/ → SPA + base href │
|
|
63
|
+
│ │ │ ├─ /i/<id>/api/* → proxy worker │
|
|
64
|
+
│ │ │ └─ /i/<id>/ws → proxy worker │
|
|
65
|
+
└──────────────┘ │ │ │
|
|
66
|
+
│ ▼ │
|
|
67
|
+
│ worker A worker B … │
|
|
68
|
+
│ 127.0.0.1: 127.0.0.1: │
|
|
69
|
+
│ 3001 3002 … │
|
|
70
|
+
│ ├─ PTY (claude / shell / TUI) │
|
|
71
|
+
│ ├─ /api/health /api/hook │
|
|
72
|
+
│ └─ /ws (PTY IO) │
|
|
73
|
+
└──────────────────────────────────┘
|
|
60
74
|
```
|
|
61
75
|
|
|
62
76
|
- **broker** (LAN entry point): the only outward-facing process, listens on
|
package/dist/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ var DEFAULT_PORT, DEFAULT_SESSION_TTL_MS, DEFAULT_AUTH_RATE_LIMIT, DEFAULT_MAX_B
|
|
|
14
14
|
var init_constants = __esm({
|
|
15
15
|
"shared/dist/constants.js"() {
|
|
16
16
|
"use strict";
|
|
17
|
-
DEFAULT_PORT =
|
|
17
|
+
DEFAULT_PORT = 3737;
|
|
18
18
|
DEFAULT_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
19
19
|
DEFAULT_AUTH_RATE_LIMIT = 20;
|
|
20
20
|
DEFAULT_MAX_BUFFER_LINES = 1e4;
|
|
@@ -608,6 +608,9 @@ function assignFlag(out, key, value) {
|
|
|
608
608
|
case "--strict-port":
|
|
609
609
|
out.strictPort = value === true || value === "true";
|
|
610
610
|
return;
|
|
611
|
+
case "--foreground":
|
|
612
|
+
out.foreground = value === true || value === "true";
|
|
613
|
+
return;
|
|
611
614
|
case "--help":
|
|
612
615
|
out.help = true;
|
|
613
616
|
return;
|
|
@@ -719,7 +722,8 @@ var init_cli_utils = __esm({
|
|
|
719
722
|
"--version",
|
|
720
723
|
"--no-open",
|
|
721
724
|
"--wait-confirm",
|
|
722
|
-
"--strict-port"
|
|
725
|
+
"--strict-port",
|
|
726
|
+
"--foreground"
|
|
723
727
|
]);
|
|
724
728
|
KNOWN_FLAGS_VALUE = /* @__PURE__ */ new Set([
|
|
725
729
|
"--port",
|
|
@@ -774,8 +778,9 @@ Usage:
|
|
|
774
778
|
atr <subcommand> [...] manage the broker / instances (see below)
|
|
775
779
|
|
|
776
780
|
Subcommands:
|
|
777
|
-
start [--port n] [--host ip] start the background service (broker)
|
|
778
|
-
|
|
781
|
+
start [--port n] [--host ip] start the background service (broker); the command
|
|
782
|
+
returns immediately once the broker is healthy.
|
|
783
|
+
add --foreground to keep it attached (systemd / Docker).
|
|
779
784
|
stop stop the background service
|
|
780
785
|
status one-shot view: process, token, entry URLs, instances
|
|
781
786
|
list list all live instances
|
|
@@ -804,7 +809,7 @@ Strict argument order:
|
|
|
804
809
|
atr -- --weird '--' forces split; default shell with '--weird'
|
|
805
810
|
|
|
806
811
|
Run options (for atr [program]):
|
|
807
|
-
-p, --port <n> Background service (broker) port (default
|
|
812
|
+
-p, --port <n> Background service (broker) port (default 3737). If broker is
|
|
808
813
|
already running and on a different port, atr will refuse to
|
|
809
814
|
start \u2014 run 'atr stop' first if you want to switch.
|
|
810
815
|
Worker ports are internal and auto-assigned; you don't set them.
|
|
@@ -842,7 +847,7 @@ Run options (for atr [program]):
|
|
|
842
847
|
passes through automatically)
|
|
843
848
|
|
|
844
849
|
Multi-instance:
|
|
845
|
-
The background service (broker) runs once on port
|
|
850
|
+
The background service (broker) runs once on port 3737 and is shared by all
|
|
846
851
|
instances. Running atr [program] in different terminals all connect to the
|
|
847
852
|
same service; PTY children are independent. Click the tab bar in the browser
|
|
848
853
|
to switch between them. If the service isn't running, the first atr will
|
|
@@ -4257,9 +4262,9 @@ async function bindAvailablePort(opts) {
|
|
|
4257
4262
|
return { port: actualPort };
|
|
4258
4263
|
}
|
|
4259
4264
|
if (strict) {
|
|
4260
|
-
throw new InstanceError(ErrorCode.PORT_UNAVAILABLE,
|
|
4265
|
+
throw new InstanceError(ErrorCode.PORT_UNAVAILABLE, `port ${preferred} is in use (--strict-port set; not auto-incrementing)`, 503);
|
|
4261
4266
|
}
|
|
4262
|
-
throw new InstanceError(ErrorCode.PORT_UNAVAILABLE,
|
|
4267
|
+
throw new InstanceError(ErrorCode.PORT_UNAVAILABLE, `none of ports ${preferred}..${preferred + maxAttempts - 1} are available (last EADDRINUSE: ${lastEaddrPort ?? preferred})`, 503);
|
|
4263
4268
|
}
|
|
4264
4269
|
function readListenedPort(server) {
|
|
4265
4270
|
try {
|
|
@@ -4437,7 +4442,7 @@ async function renderQrCode(url, opts = {}) {
|
|
|
4437
4442
|
try {
|
|
4438
4443
|
return await QRCode.toString(url, {
|
|
4439
4444
|
type: "utf8",
|
|
4440
|
-
errorCorrectionLevel: opts.errorCorrectionLevel ?? "
|
|
4445
|
+
errorCorrectionLevel: opts.errorCorrectionLevel ?? "M",
|
|
4441
4446
|
// utf8 模式本身就是半字符垂直压缩,体积约 qrcode-terminal small=true 的 1/2。
|
|
4442
4447
|
// margin: utf8 渲染器在 margin=1 时有"Invalid array length" bug,避开它;
|
|
4443
4448
|
// margin=2 视觉上仍然紧凑,且扫码识别率更高
|
|
@@ -5444,21 +5449,13 @@ async function startBrokerServer(opts) {
|
|
|
5444
5449
|
socket.destroy();
|
|
5445
5450
|
});
|
|
5446
5451
|
}
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
const onListening = () => {
|
|
5453
|
-
httpServer.removeListener("error", onError);
|
|
5454
|
-
resolveListen();
|
|
5455
|
-
};
|
|
5456
|
-
httpServer.once("error", onError);
|
|
5457
|
-
httpServer.once("listening", onListening);
|
|
5458
|
-
httpServer.listen(port, host);
|
|
5452
|
+
const bound = await bindAvailablePort({
|
|
5453
|
+
preferred: port,
|
|
5454
|
+
host,
|
|
5455
|
+
server: httpServer,
|
|
5456
|
+
strict: opts.strictPort ?? false
|
|
5459
5457
|
});
|
|
5460
|
-
const
|
|
5461
|
-
const actualPort = typeof addr === "object" && addr ? addr.port : port;
|
|
5458
|
+
const actualPort = bound.port;
|
|
5462
5459
|
writeBrokerState({
|
|
5463
5460
|
pid: process.pid,
|
|
5464
5461
|
port: actualPort,
|
|
@@ -5519,7 +5516,8 @@ var init_broker_server = __esm({
|
|
|
5519
5516
|
init_broker_state();
|
|
5520
5517
|
init_instance_router();
|
|
5521
5518
|
init_router();
|
|
5522
|
-
|
|
5519
|
+
init_port_finder();
|
|
5520
|
+
DEFAULT_BROKER_PORT = 3737;
|
|
5523
5521
|
DEFAULT_BROKER_HOST = "0.0.0.0";
|
|
5524
5522
|
}
|
|
5525
5523
|
});
|
|
@@ -5546,7 +5544,9 @@ async function ensureBroker(opts) {
|
|
|
5546
5544
|
if (isBrokerAlive(existing)) {
|
|
5547
5545
|
if (existing && await probeHealth(existing, fetchImpl, probeTimeoutMs)) {
|
|
5548
5546
|
if (opts.brokerPort !== void 0 && existing.port !== opts.brokerPort) {
|
|
5549
|
-
throw new AppError(ErrorCode.INTERNAL_ERROR, `broker is already running on port ${existing.port}; cannot honor requested port ${opts.brokerPort}.
|
|
5547
|
+
throw new AppError(ErrorCode.INTERNAL_ERROR, `broker is already running on port ${existing.port}; cannot honor requested port ${opts.brokerPort}.
|
|
5548
|
+
- to use the running broker: drop -p ${opts.brokerPort}
|
|
5549
|
+
- to switch broker: 'atr stop' then 'atr -p ${opts.brokerPort} ...' again`);
|
|
5550
5550
|
}
|
|
5551
5551
|
return { state: existing, forked: false };
|
|
5552
5552
|
}
|
|
@@ -5696,6 +5696,41 @@ var init_broker = __esm({
|
|
|
5696
5696
|
}
|
|
5697
5697
|
});
|
|
5698
5698
|
|
|
5699
|
+
// backend/dist/utils/wsl-detect.js
|
|
5700
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
5701
|
+
function isWsl(deps) {
|
|
5702
|
+
if (cached !== void 0 && deps === void 0)
|
|
5703
|
+
return cached;
|
|
5704
|
+
const platform2 = deps?.platform ?? process.platform;
|
|
5705
|
+
if (platform2 !== "linux") {
|
|
5706
|
+
if (deps === void 0)
|
|
5707
|
+
cached = false;
|
|
5708
|
+
return false;
|
|
5709
|
+
}
|
|
5710
|
+
let content;
|
|
5711
|
+
try {
|
|
5712
|
+
content = (deps?.readProcVersion ?? defaultReadProcVersion)();
|
|
5713
|
+
} catch {
|
|
5714
|
+
if (deps === void 0)
|
|
5715
|
+
cached = false;
|
|
5716
|
+
return false;
|
|
5717
|
+
}
|
|
5718
|
+
const lower = content.toLowerCase();
|
|
5719
|
+
const result = lower.includes("microsoft") || lower.includes("wsl");
|
|
5720
|
+
if (deps === void 0)
|
|
5721
|
+
cached = result;
|
|
5722
|
+
return result;
|
|
5723
|
+
}
|
|
5724
|
+
function defaultReadProcVersion() {
|
|
5725
|
+
return readFileSync9("/proc/version", "utf-8");
|
|
5726
|
+
}
|
|
5727
|
+
var cached;
|
|
5728
|
+
var init_wsl_detect = __esm({
|
|
5729
|
+
"backend/dist/utils/wsl-detect.js"() {
|
|
5730
|
+
"use strict";
|
|
5731
|
+
}
|
|
5732
|
+
});
|
|
5733
|
+
|
|
5699
5734
|
// backend/dist/broker/entry-discovery.js
|
|
5700
5735
|
var entry_discovery_exports = {};
|
|
5701
5736
|
__export(entry_discovery_exports, {
|
|
@@ -5742,13 +5777,8 @@ function discoverEntries(opts) {
|
|
|
5742
5777
|
url: buildEntryUrl("127.0.0.1", brokerPort, instanceId, urlOpts)
|
|
5743
5778
|
});
|
|
5744
5779
|
}
|
|
5745
|
-
const
|
|
5746
|
-
|
|
5747
|
-
lan: 1,
|
|
5748
|
-
ipv6: 2,
|
|
5749
|
-
other: 3,
|
|
5750
|
-
loopback: 4
|
|
5751
|
-
};
|
|
5780
|
+
const wsl = isWsl();
|
|
5781
|
+
const order = wsl ? { lan: 0, ipv6: 1, tailscale: 2, other: 3, loopback: 4 } : { tailscale: 0, lan: 1, ipv6: 2, other: 3, loopback: 4 };
|
|
5752
5782
|
out.sort((a, b) => {
|
|
5753
5783
|
if (a.kind !== b.kind)
|
|
5754
5784
|
return order[a.kind] - order[b.kind];
|
|
@@ -5809,6 +5839,7 @@ var init_entry_discovery = __esm({
|
|
|
5809
5839
|
"backend/dist/broker/entry-discovery.js"() {
|
|
5810
5840
|
"use strict";
|
|
5811
5841
|
init_network();
|
|
5842
|
+
init_wsl_detect();
|
|
5812
5843
|
}
|
|
5813
5844
|
});
|
|
5814
5845
|
|
|
@@ -6306,17 +6337,17 @@ hint: check whether ~/.atr/broker.json is held by a stale process; set ATR_DEBUG
|
|
|
6306
6337
|
setTimeout(() => triggerSpawn("timeout"), cfg.spawnTimeoutSec * 1e3).unref();
|
|
6307
6338
|
}
|
|
6308
6339
|
}
|
|
6340
|
+
const brokerHost = brokerState.host === "0.0.0.0" || brokerState.host === "::" ? displayIp : brokerState.host;
|
|
6309
6341
|
void registry.register({
|
|
6310
6342
|
instanceId,
|
|
6311
6343
|
name: cfg.instanceName,
|
|
6312
|
-
// 0.7.0:host 改为 worker 实际监听地址(loopback)。broker 阶段 3 反代时
|
|
6313
|
-
// 直接用这个 host:port 连 worker。displayIp 已不参与 worker 注册
|
|
6314
6344
|
host: "127.0.0.1",
|
|
6315
6345
|
port: cfg.port,
|
|
6316
6346
|
pid: process.pid,
|
|
6317
6347
|
cwd: cfg.claudeCwd,
|
|
6318
6348
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6319
|
-
headless: cfg.noTerminal
|
|
6349
|
+
headless: cfg.noTerminal,
|
|
6350
|
+
...brokerHost ? { brokerHost } : {}
|
|
6320
6351
|
}).catch((err) => logger.warn({ err }, "\u6CE8\u518C\u5B9E\u4F8B\u5931\u8D25"));
|
|
6321
6352
|
logger.info({
|
|
6322
6353
|
port: cfg.port,
|
|
@@ -7391,7 +7422,7 @@ __export(service_installer_exports, {
|
|
|
7391
7422
|
renderSystemdUnit: () => renderSystemdUnit,
|
|
7392
7423
|
uninstall: () => uninstall
|
|
7393
7424
|
});
|
|
7394
|
-
import { existsSync as existsSync15, mkdirSync as mkdirSync12, readFileSync as
|
|
7425
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync12, readFileSync as readFileSync10, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "node:fs";
|
|
7395
7426
|
import { resolve as resolve15, dirname as dirname8 } from "node:path";
|
|
7396
7427
|
import { homedir as homedir9, platform } from "node:os";
|
|
7397
7428
|
function detectPlatform(env = process.env, plat = platform()) {
|
|
@@ -7413,7 +7444,7 @@ After=network.target
|
|
|
7413
7444
|
|
|
7414
7445
|
[Service]
|
|
7415
7446
|
Type=simple
|
|
7416
|
-
ExecStart=${opts.nodeBin} ${opts.cliPath} start
|
|
7447
|
+
ExecStart=${opts.nodeBin} ${opts.cliPath} start --foreground
|
|
7417
7448
|
Restart=on-failure
|
|
7418
7449
|
RestartSec=5s
|
|
7419
7450
|
${portEnv}[Install]
|
|
@@ -7438,6 +7469,7 @@ function renderLaunchdPlist(opts) {
|
|
|
7438
7469
|
<string>${opts.nodeBin}</string>
|
|
7439
7470
|
<string>${opts.cliPath}</string>
|
|
7440
7471
|
<string>start</string>
|
|
7472
|
+
<string>--foreground</string>
|
|
7441
7473
|
</array>
|
|
7442
7474
|
<key>RunAtLoad</key><true/>
|
|
7443
7475
|
<key>KeepAlive</key><true/>
|
|
@@ -7553,7 +7585,7 @@ var init_service_installer = __esm({
|
|
|
7553
7585
|
mkdirSync12(p, o);
|
|
7554
7586
|
},
|
|
7555
7587
|
writeFileSync: (p, d, o) => writeFileSync5(p, d, { encoding: "utf-8", ...o ?? {} }),
|
|
7556
|
-
readFileSync: (p) =>
|
|
7588
|
+
readFileSync: (p) => readFileSync10(p, "utf-8"),
|
|
7557
7589
|
rmSync: rmSync3
|
|
7558
7590
|
};
|
|
7559
7591
|
ServicePlatformUnsupportedError = class extends AppError {
|
|
@@ -7610,8 +7642,8 @@ var cli_exports = {};
|
|
|
7610
7642
|
__export(cli_exports, {
|
|
7611
7643
|
runServiceCli: () => runServiceCli
|
|
7612
7644
|
});
|
|
7613
|
-
import { execSync as execSync2 } from "node:child_process";
|
|
7614
|
-
import { readFileSync as
|
|
7645
|
+
import { execSync as execSync2, spawn as spawn4 } from "node:child_process";
|
|
7646
|
+
import { existsSync as existsSync16, openSync as openSync3, readFileSync as readFileSync11 } from "node:fs";
|
|
7615
7647
|
import { resolve as resolve16, dirname as dirname9 } from "node:path";
|
|
7616
7648
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7617
7649
|
import { homedir as homedir10 } from "node:os";
|
|
@@ -7624,7 +7656,7 @@ function getBrokerVersion() {
|
|
|
7624
7656
|
];
|
|
7625
7657
|
for (const pkgPath of candidates) {
|
|
7626
7658
|
try {
|
|
7627
|
-
const pkg = JSON.parse(
|
|
7659
|
+
const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
|
|
7628
7660
|
if (pkg.name === "auvezy-terminal-remote" && typeof pkg.version === "string") {
|
|
7629
7661
|
return pkg.version;
|
|
7630
7662
|
}
|
|
@@ -7636,18 +7668,19 @@ function getBrokerVersion() {
|
|
|
7636
7668
|
}
|
|
7637
7669
|
function getCliPath() {
|
|
7638
7670
|
const __dirname2 = dirname9(fileURLToPath3(import.meta.url));
|
|
7639
|
-
const
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7671
|
+
const parentDir = resolve16(__dirname2, "..", "cli.js");
|
|
7672
|
+
const sameDir = resolve16(__dirname2, "cli.js");
|
|
7673
|
+
try {
|
|
7674
|
+
readFileSync11(parentDir);
|
|
7675
|
+
return parentDir;
|
|
7676
|
+
} catch {
|
|
7677
|
+
}
|
|
7678
|
+
try {
|
|
7679
|
+
readFileSync11(sameDir);
|
|
7680
|
+
return sameDir;
|
|
7681
|
+
} catch {
|
|
7649
7682
|
}
|
|
7650
|
-
return
|
|
7683
|
+
return parentDir;
|
|
7651
7684
|
}
|
|
7652
7685
|
async function runServiceCli(action, cli) {
|
|
7653
7686
|
switch (action) {
|
|
@@ -7668,6 +7701,10 @@ async function runServiceCli(action, cli) {
|
|
|
7668
7701
|
}
|
|
7669
7702
|
}
|
|
7670
7703
|
async function runBrokerStart(cli) {
|
|
7704
|
+
const wantForeground = cli.foreground === true || process.env["ATR_BROKER_FOREGROUND"] === "1";
|
|
7705
|
+
if (!wantForeground) {
|
|
7706
|
+
return runBrokerStartDaemonize(cli);
|
|
7707
|
+
}
|
|
7671
7708
|
const port = cli.port ?? parseEnvPort(process.env["ATR_BROKER_PORT"]) ?? DEFAULT_BROKER_PORT;
|
|
7672
7709
|
const host = cli.host ?? process.env["ATR_BROKER_HOST"] ?? DEFAULT_BROKER_HOST;
|
|
7673
7710
|
const brokerVersion = getBrokerVersion();
|
|
@@ -7710,7 +7747,7 @@ async function runBrokerStart(cli) {
|
|
|
7710
7747
|
const pushService = new PushService();
|
|
7711
7748
|
await pushService.init();
|
|
7712
7749
|
startInstanceWatcher(registry.filePath);
|
|
7713
|
-
const cliJsPath =
|
|
7750
|
+
const cliJsPath = getCliPath();
|
|
7714
7751
|
const workdirAllow = currentUserConfig.workdirAllow;
|
|
7715
7752
|
const workdirDeny = currentUserConfig.workdirDeny;
|
|
7716
7753
|
const spawner = new DefaultInstanceSpawner({
|
|
@@ -7738,14 +7775,20 @@ async function runBrokerStart(cli) {
|
|
|
7738
7775
|
brokerVersion,
|
|
7739
7776
|
registry,
|
|
7740
7777
|
frontendDist,
|
|
7741
|
-
brokerApi
|
|
7778
|
+
brokerApi,
|
|
7779
|
+
strictPort: cli.strictPort ?? false
|
|
7742
7780
|
});
|
|
7743
7781
|
} catch (err) {
|
|
7744
|
-
process.stderr.write(
|
|
7782
|
+
process.stderr.write(`${c.red("[atr]")} startup failed: ${err instanceof Error ? err.message : String(err)}
|
|
7745
7783
|
`);
|
|
7746
7784
|
return 1;
|
|
7747
7785
|
}
|
|
7748
|
-
|
|
7786
|
+
if (handle.port !== port) {
|
|
7787
|
+
process.stderr.write(`${c.yellow("[atr]")} preferred port ${port} was busy; bound to ${handle.port} instead
|
|
7788
|
+
` + c.dim(` pass --strict-port if you want atr to refuse to start when ${port} is taken
|
|
7789
|
+
`));
|
|
7790
|
+
}
|
|
7791
|
+
process.stderr.write(`${c.cyan("[atr]")} listening on http://${host}:${handle.port}
|
|
7749
7792
|
`);
|
|
7750
7793
|
let stopping = false;
|
|
7751
7794
|
const stop = async (signal) => {
|
|
@@ -7779,6 +7822,83 @@ function installBrokerLogRotator() {
|
|
|
7779
7822
|
});
|
|
7780
7823
|
return rotator;
|
|
7781
7824
|
}
|
|
7825
|
+
async function runBrokerStartDaemonize(cli) {
|
|
7826
|
+
const tag = c.cyan("[atr]");
|
|
7827
|
+
const port = cli.port ?? parseEnvPort(process.env["ATR_BROKER_PORT"]) ?? DEFAULT_BROKER_PORT;
|
|
7828
|
+
const existing = readBrokerState();
|
|
7829
|
+
if (existing && isBrokerAlive(existing)) {
|
|
7830
|
+
process.stderr.write(`${tag} broker already running on ${existing.host}:${existing.port} (pid=${existing.pid})
|
|
7831
|
+
`);
|
|
7832
|
+
return 0;
|
|
7833
|
+
}
|
|
7834
|
+
const cliJsPath = getCliPath();
|
|
7835
|
+
const entry = resolveDaemonEntry(cliJsPath);
|
|
7836
|
+
const logFd = process.env["ATR_DEBUG_SPAWN"] ? openSync3(`/tmp/atr-broker-${Date.now()}.log`, "a") : "ignore";
|
|
7837
|
+
const childEnv = {
|
|
7838
|
+
...process.env,
|
|
7839
|
+
ATR_BROKER_FOREGROUND: "1"
|
|
7840
|
+
};
|
|
7841
|
+
if (cli.port !== void 0)
|
|
7842
|
+
childEnv["ATR_BROKER_PORT"] = String(cli.port);
|
|
7843
|
+
if (cli.host !== void 0)
|
|
7844
|
+
childEnv["ATR_BROKER_HOST"] = cli.host;
|
|
7845
|
+
if (cli.strictPort)
|
|
7846
|
+
childEnv["ATR_BROKER_STRICT_PORT"] = "1";
|
|
7847
|
+
const child = spawn4(entry.execPath, [...entry.args, "start", "--foreground"], {
|
|
7848
|
+
env: childEnv,
|
|
7849
|
+
detached: true,
|
|
7850
|
+
stdio: ["ignore", logFd, logFd]
|
|
7851
|
+
});
|
|
7852
|
+
if (typeof child.pid !== "number") {
|
|
7853
|
+
process.stderr.write(`${tag} failed to spawn broker subprocess (no pid)
|
|
7854
|
+
`);
|
|
7855
|
+
return 1;
|
|
7856
|
+
}
|
|
7857
|
+
const earlyExit = [];
|
|
7858
|
+
const earlyError = [];
|
|
7859
|
+
child.once("error", (e) => {
|
|
7860
|
+
earlyError.push(e);
|
|
7861
|
+
});
|
|
7862
|
+
child.once("exit", (code, signal) => {
|
|
7863
|
+
earlyExit.push({ code, signal });
|
|
7864
|
+
});
|
|
7865
|
+
child.unref();
|
|
7866
|
+
const t0 = Date.now();
|
|
7867
|
+
const statePath = defaultBrokerStatePath();
|
|
7868
|
+
while (Date.now() - t0 < DAEMONIZE_TIMEOUT_MS) {
|
|
7869
|
+
const st = readBrokerState(statePath);
|
|
7870
|
+
if (st && isBrokerAlive(st) && st.pid === child.pid) {
|
|
7871
|
+
process.stdout.write(`${tag} broker started on ${c.green(`${st.host}:${st.port}`)} (pid=${st.pid})
|
|
7872
|
+
`);
|
|
7873
|
+
return 0;
|
|
7874
|
+
}
|
|
7875
|
+
await sleep4(DAEMONIZE_POLL_INTERVAL_MS);
|
|
7876
|
+
}
|
|
7877
|
+
let detail = "";
|
|
7878
|
+
if (earlyError[0]) {
|
|
7879
|
+
detail = `
|
|
7880
|
+
- spawn error: ${earlyError[0].message}`;
|
|
7881
|
+
} else if (earlyExit[0]) {
|
|
7882
|
+
detail = `
|
|
7883
|
+
- child exited early (code=${earlyExit[0].code}, signal=${earlyExit[0].signal})`;
|
|
7884
|
+
}
|
|
7885
|
+
process.stderr.write(`${tag} broker did not become ready within ${DAEMONIZE_TIMEOUT_MS}ms.${detail}
|
|
7886
|
+
- check ~/.auvezy/terminal-remote/broker-*.log for errors
|
|
7887
|
+
- or set ATR_DEBUG_SPAWN=1 and retry to capture /tmp/atr-broker-*.log
|
|
7888
|
+
- port ${port} may be busy; try '--port <other>' or '--strict-port' to fail fast
|
|
7889
|
+
`);
|
|
7890
|
+
return 1;
|
|
7891
|
+
}
|
|
7892
|
+
function resolveDaemonEntry(cliJsPath) {
|
|
7893
|
+
if (existsSync16(cliJsPath)) {
|
|
7894
|
+
return { execPath: process.execPath, args: [cliJsPath] };
|
|
7895
|
+
}
|
|
7896
|
+
const tsPath = cliJsPath.replace(/\.js$/, ".ts");
|
|
7897
|
+
if (existsSync16(tsPath)) {
|
|
7898
|
+
return { execPath: process.execPath, args: ["--import", "tsx", tsPath] };
|
|
7899
|
+
}
|
|
7900
|
+
return { execPath: process.execPath, args: [cliJsPath] };
|
|
7901
|
+
}
|
|
7782
7902
|
async function runBrokerStop() {
|
|
7783
7903
|
const tag = c.cyan("[atr]");
|
|
7784
7904
|
const state = readBrokerState();
|
|
@@ -7936,12 +8056,12 @@ function writeServiceInstallSection() {
|
|
|
7936
8056
|
async function writeTokenSection() {
|
|
7937
8057
|
process.stdout.write(c.bold("=== Token ===\n"));
|
|
7938
8058
|
try {
|
|
7939
|
-
const { readFileSync:
|
|
8059
|
+
const { readFileSync: readFileSync12, statSync: statSync6 } = await import("node:fs");
|
|
7940
8060
|
const { resolve: pathResolve2 } = await import("node:path");
|
|
7941
8061
|
const { homedir: homedir11 } = await import("node:os");
|
|
7942
8062
|
const path = pathResolve2(homedir11(), ".atrrc");
|
|
7943
8063
|
const stat = statSync6(path);
|
|
7944
|
-
const cfg = JSON.parse(
|
|
8064
|
+
const cfg = JSON.parse(readFileSync12(path, "utf-8"));
|
|
7945
8065
|
if (typeof cfg.token === "string" && cfg.token.length > 0) {
|
|
7946
8066
|
process.stdout.write(` token: ${cfg.token}
|
|
7947
8067
|
`);
|
|
@@ -7963,10 +8083,10 @@ async function writeEntriesSection(brokerPort) {
|
|
|
7963
8083
|
const { discoverEntries: discoverEntries2, kindLabel: kindLabel2 } = await Promise.resolve().then(() => (init_entry_discovery(), entry_discovery_exports));
|
|
7964
8084
|
let token;
|
|
7965
8085
|
try {
|
|
7966
|
-
const { readFileSync:
|
|
8086
|
+
const { readFileSync: readFileSync12 } = await import("node:fs");
|
|
7967
8087
|
const { resolve: pathResolve2 } = await import("node:path");
|
|
7968
8088
|
const { homedir: homedir11 } = await import("node:os");
|
|
7969
|
-
const cfg = JSON.parse(
|
|
8089
|
+
const cfg = JSON.parse(readFileSync12(pathResolve2(homedir11(), ".atrrc"), "utf-8"));
|
|
7970
8090
|
if (typeof cfg.token === "string" && cfg.token.length > 0)
|
|
7971
8091
|
token = cfg.token;
|
|
7972
8092
|
} catch {
|
|
@@ -8025,12 +8145,12 @@ async function runListInstances() {
|
|
|
8025
8145
|
async function runShowLogs() {
|
|
8026
8146
|
const { homedir: homedir11 } = await import("node:os");
|
|
8027
8147
|
const { resolve: pathResolve2 } = await import("node:path");
|
|
8028
|
-
const { existsSync:
|
|
8029
|
-
const { spawn:
|
|
8148
|
+
const { existsSync: existsSync17 } = await import("node:fs");
|
|
8149
|
+
const { spawn: spawn5 } = await import("node:child_process");
|
|
8030
8150
|
const today = /* @__PURE__ */ new Date();
|
|
8031
8151
|
const day = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}-${String(today.getDate()).padStart(2, "0")}`;
|
|
8032
8152
|
const logPath = pathResolve2(homedir11(), ".atr", `broker-${day}.log`);
|
|
8033
|
-
if (!
|
|
8153
|
+
if (!existsSync17(logPath)) {
|
|
8034
8154
|
process.stderr.write(`[atr] today's log not found: ${logPath}
|
|
8035
8155
|
hint: is the service running? try atr status, or atr start to launch.
|
|
8036
8156
|
`);
|
|
@@ -8038,14 +8158,14 @@ hint: is the service running? try atr status, or atr start to launch.
|
|
|
8038
8158
|
}
|
|
8039
8159
|
process.stderr.write(`[atr] tail -F ${logPath} (Ctrl+C to quit)
|
|
8040
8160
|
`);
|
|
8041
|
-
const tail =
|
|
8161
|
+
const tail = spawn5("tail", ["-F", logPath], { stdio: "inherit" });
|
|
8042
8162
|
return new Promise((resolveExit) => {
|
|
8043
8163
|
tail.on("error", (err) => {
|
|
8044
8164
|
process.stderr.write(`[atr] cannot spawn tail (${err.message}); falling back to one-shot output:
|
|
8045
8165
|
`);
|
|
8046
|
-
void import("node:fs").then(({ readFileSync:
|
|
8166
|
+
void import("node:fs").then(({ readFileSync: readFileSync12 }) => {
|
|
8047
8167
|
try {
|
|
8048
|
-
process.stdout.write(
|
|
8168
|
+
process.stdout.write(readFileSync12(logPath, "utf-8"));
|
|
8049
8169
|
resolveExit(0);
|
|
8050
8170
|
} catch (e) {
|
|
8051
8171
|
process.stderr.write(`[atr] failed to read log: ${e.message}
|
|
@@ -8131,7 +8251,7 @@ function parseEnvPort(raw) {
|
|
|
8131
8251
|
function sleep4(ms) {
|
|
8132
8252
|
return new Promise((r) => setTimeout(r, ms));
|
|
8133
8253
|
}
|
|
8134
|
-
var STOP_GRACE_MS, STOP_POLL_INTERVAL_MS;
|
|
8254
|
+
var DAEMONIZE_TIMEOUT_MS, DAEMONIZE_POLL_INTERVAL_MS, STOP_GRACE_MS, STOP_POLL_INTERVAL_MS;
|
|
8135
8255
|
var init_cli = __esm({
|
|
8136
8256
|
"backend/dist/broker/cli.js"() {
|
|
8137
8257
|
"use strict";
|
|
@@ -8153,6 +8273,8 @@ var init_cli = __esm({
|
|
|
8153
8273
|
init_broker_state();
|
|
8154
8274
|
init_service_installer();
|
|
8155
8275
|
init_colors();
|
|
8276
|
+
DAEMONIZE_TIMEOUT_MS = 8e3;
|
|
8277
|
+
DAEMONIZE_POLL_INTERVAL_MS = 100;
|
|
8156
8278
|
STOP_GRACE_MS = 5e3;
|
|
8157
8279
|
STOP_POLL_INTERVAL_MS = 100;
|
|
8158
8280
|
}
|
|
@@ -8221,11 +8343,11 @@ void (async () => {
|
|
|
8221
8343
|
process.exit(0);
|
|
8222
8344
|
}
|
|
8223
8345
|
if (cli.version) {
|
|
8224
|
-
const { readFileSync:
|
|
8346
|
+
const { readFileSync: readFileSync12 } = await import("node:fs");
|
|
8225
8347
|
const { resolve: resolve17, dirname: dirname10 } = await import("node:path");
|
|
8226
8348
|
const { fileURLToPath: fileURLToPath4 } = await import("node:url");
|
|
8227
8349
|
const __dirname2 = dirname10(fileURLToPath4(import.meta.url));
|
|
8228
|
-
const pkg = JSON.parse(
|
|
8350
|
+
const pkg = JSON.parse(readFileSync12(resolve17(__dirname2, "..", "package.json"), "utf-8"));
|
|
8229
8351
|
process.stdout.write(`${pkg.version}
|
|
8230
8352
|
`);
|
|
8231
8353
|
process.exit(0);
|