esque-bridge 0.6.7 → 0.6.8
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/index.js +56 -8
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1097,6 +1097,47 @@ function listenOnFreePort(app, startPort, maxTries = 25) {
|
|
|
1097
1097
|
});
|
|
1098
1098
|
}
|
|
1099
1099
|
|
|
1100
|
+
// Open the public pairing tunnel. Prefers cloudflared (reliable, no
|
|
1101
|
+
// interstitial), and falls back to localtunnel with a HARD timeout — otherwise
|
|
1102
|
+
// a slow/down localtunnel.me hangs the whole bridge before the QR ever prints
|
|
1103
|
+
// (exactly the "nothing happens after y" failure). Resolves a uniform handle
|
|
1104
|
+
// { url, kind, close(), onClose(cb) }, or null if both paths fail.
|
|
1105
|
+
function openPairTunnel(port) {
|
|
1106
|
+
return new Promise((resolve) => {
|
|
1107
|
+
let settled = false;
|
|
1108
|
+
const done = (val) => { if (!settled) { settled = true; resolve(val); } };
|
|
1109
|
+
|
|
1110
|
+
const tryLocaltunnel = () => {
|
|
1111
|
+
const timer = setTimeout(() => done(null), 20000);
|
|
1112
|
+
localtunnel({ port, subdomain: LT_SUBDOMAIN })
|
|
1113
|
+
.then((t) => {
|
|
1114
|
+
clearTimeout(timer);
|
|
1115
|
+
if (settled) { try { t.close(); } catch { /* already gone */ } return; }
|
|
1116
|
+
done({ url: t.url, kind: 'localtunnel', close: () => { try { t.close(); } catch { /* already gone */ } }, onClose: (cb) => t.on('close', cb) });
|
|
1117
|
+
})
|
|
1118
|
+
.catch(() => { clearTimeout(timer); done(null); });
|
|
1119
|
+
};
|
|
1120
|
+
|
|
1121
|
+
if (!hasCloudflared()) { tryLocaltunnel(); return; }
|
|
1122
|
+
|
|
1123
|
+
const proc = spawn('cloudflared', ['tunnel', '--url', `http://localhost:${port}`], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
1124
|
+
let gotUrl = false, fellBack = false;
|
|
1125
|
+
const scan = (d) => {
|
|
1126
|
+
const m = String(d).match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/i);
|
|
1127
|
+
if (m && !gotUrl) {
|
|
1128
|
+
gotUrl = true;
|
|
1129
|
+
done({ url: m[0], kind: 'cloudflared', close: () => { try { proc.kill(); } catch { /* already gone */ } }, onClose: (cb) => proc.on('exit', cb) });
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
proc.stdout.on('data', scan);
|
|
1133
|
+
proc.stderr.on('data', scan);
|
|
1134
|
+
// cloudflared died before emitting a URL, or took too long → fall back once.
|
|
1135
|
+
const fallback = () => { if (!gotUrl && !fellBack) { fellBack = true; try { proc.kill(); } catch { /* already gone */ } tryLocaltunnel(); } };
|
|
1136
|
+
proc.on('exit', fallback);
|
|
1137
|
+
setTimeout(fallback, 25000);
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1100
1141
|
async function main() {
|
|
1101
1142
|
if (!fs.existsSync(WORKDIR) || !fs.statSync(WORKDIR).isDirectory()) {
|
|
1102
1143
|
console.error(`workdir does not exist: ${WORKDIR}`);
|
|
@@ -1136,15 +1177,17 @@ async function main() {
|
|
|
1136
1177
|
console.log(` ✓ Using port ${boundPort} instead (${PORT} was taken).`);
|
|
1137
1178
|
}
|
|
1138
1179
|
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
console.error('
|
|
1144
|
-
console.error('
|
|
1145
|
-
console.error(` cloudflared tunnel --url http://localhost:${boundPort}`);
|
|
1180
|
+
const tunnel = await openPairTunnel(boundPort);
|
|
1181
|
+
if (!tunnel) {
|
|
1182
|
+
console.error('\n ✗ Could not open a public tunnel.');
|
|
1183
|
+
console.error(' cloudflared and localtunnel.me both failed to respond.');
|
|
1184
|
+
console.error(' Check your internet connection and retry. For the reliable path:');
|
|
1185
|
+
console.error(' brew install cloudflared');
|
|
1146
1186
|
process.exit(1);
|
|
1147
1187
|
}
|
|
1188
|
+
if (tunnel.kind === 'localtunnel') {
|
|
1189
|
+
console.log(' (Using localtunnel — install cloudflared for a faster, steadier tunnel: brew install cloudflared)');
|
|
1190
|
+
}
|
|
1148
1191
|
|
|
1149
1192
|
const pairUrl = `esque://pair?url=${encodeURIComponent(tunnel.url)}&secret=${PAIRING_SECRET}&agent=${AGENT_TYPE}`;
|
|
1150
1193
|
|
|
@@ -1169,7 +1212,9 @@ async function main() {
|
|
|
1169
1212
|
console.log(' Press Ctrl-C to stop.');
|
|
1170
1213
|
console.log('━'.repeat(68));
|
|
1171
1214
|
|
|
1215
|
+
let shuttingDown = false;
|
|
1172
1216
|
const shutdown = (signal) => {
|
|
1217
|
+
shuttingDown = true;
|
|
1173
1218
|
console.log(`\n Received ${signal} — closing tunnel…`);
|
|
1174
1219
|
killPreview();
|
|
1175
1220
|
try {
|
|
@@ -1182,7 +1227,10 @@ async function main() {
|
|
|
1182
1227
|
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
1183
1228
|
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
1184
1229
|
|
|
1185
|
-
tunnel
|
|
1230
|
+
// Detect an unexpected tunnel drop (but stay quiet during our own shutdown,
|
|
1231
|
+
// since closing the tunnel also fires this).
|
|
1232
|
+
tunnel.onClose(() => {
|
|
1233
|
+
if (shuttingDown) return;
|
|
1186
1234
|
console.error('\nTunnel closed unexpectedly. Restart `esque-bridge`.');
|
|
1187
1235
|
process.exit(1);
|
|
1188
1236
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "esque-bridge",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.8",
|
|
4
4
|
"description": "Desktop-side receiver for the Esque Agent mobile app. Pairs your phone with a local coding-agent CLI (Claude Code, Codex, Aider, or any custom command) via a tunnel + QR code, so prompts run through your subscription instead of per-token API billing.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"esque-bridge": "index.js"
|