esque-bridge 0.6.4 → 0.6.6
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 +76 -3
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -476,6 +476,24 @@ app.use((req, res, next) => {
|
|
|
476
476
|
next();
|
|
477
477
|
});
|
|
478
478
|
|
|
479
|
+
// Directory identity the app binds a project to. Lets the phone detect when a
|
|
480
|
+
// project is being re-pointed at a DIFFERENT folder, or scaffolded fresh onto
|
|
481
|
+
// a folder that already has files — the two silent-mismatch foot-guns.
|
|
482
|
+
function workdirInfo() {
|
|
483
|
+
let empty = false;
|
|
484
|
+
let git = false;
|
|
485
|
+
let entries = -1;
|
|
486
|
+
try {
|
|
487
|
+
const all = fs.readdirSync(WORKDIR).filter((f) => f !== '.DS_Store');
|
|
488
|
+
entries = all.length;
|
|
489
|
+
empty = entries === 0;
|
|
490
|
+
git = all.includes('.git');
|
|
491
|
+
} catch {
|
|
492
|
+
/* unreadable dir — report unknown via -1 */
|
|
493
|
+
}
|
|
494
|
+
return { workdir: WORKDIR, workdirName: path.basename(WORKDIR), empty, git, entries };
|
|
495
|
+
}
|
|
496
|
+
|
|
479
497
|
// Public health probe.
|
|
480
498
|
app.get('/', (_req, res) => {
|
|
481
499
|
res.json({
|
|
@@ -483,8 +501,8 @@ app.get('/', (_req, res) => {
|
|
|
483
501
|
service: 'esque-bridge',
|
|
484
502
|
agent: AGENT_TYPE,
|
|
485
503
|
agentLabel: adapter.label,
|
|
486
|
-
workdir: WORKDIR,
|
|
487
504
|
sessions: Object.keys(sessionMap[AGENT_TYPE] ?? {}).length,
|
|
505
|
+
...workdirInfo(),
|
|
488
506
|
});
|
|
489
507
|
});
|
|
490
508
|
|
|
@@ -492,12 +510,13 @@ app.get('/', (_req, res) => {
|
|
|
492
510
|
// the phone includes the pair secret we validate it and report back via
|
|
493
511
|
// `paired` so the app can verify the secret BEFORE showing a green
|
|
494
512
|
// "Paired" state. `paired` is true (match), false (wrong/stale secret), or
|
|
495
|
-
// null (no secret sent — older app builds; we can't say either way).
|
|
513
|
+
// null (no secret sent — older app builds; we can't say either way). We also
|
|
514
|
+
// return workdir identity so the app can warn on a folder mismatch.
|
|
496
515
|
app.post('/', (req, res, next) => {
|
|
497
516
|
if (req.body && req.body._probe === true) {
|
|
498
517
|
const provided = req.header('x-esque-pair') || req.body?.pairSecret;
|
|
499
518
|
const paired = provided == null ? null : provided === PAIRING_SECRET;
|
|
500
|
-
return res.json({ ok: true, service: 'esque-bridge', agent: AGENT_TYPE, paired });
|
|
519
|
+
return res.json({ ok: true, service: 'esque-bridge', agent: AGENT_TYPE, paired, ...workdirInfo() });
|
|
501
520
|
}
|
|
502
521
|
return next();
|
|
503
522
|
});
|
|
@@ -668,8 +687,62 @@ async function probeBuildError(port) {
|
|
|
668
687
|
return scanOutputForError(preview && preview.output);
|
|
669
688
|
}
|
|
670
689
|
|
|
690
|
+
// Runnable previews (`expo start`, `next dev`, `vite`, …) need the project's
|
|
691
|
+
// dependencies on disk. The agent scaffolds package.json but deliberately does
|
|
692
|
+
// NOT run a slow `npm install` itself (the blueprint hands that to Esque) — so
|
|
693
|
+
// the very first preview would otherwise die with "module 'expo' is not
|
|
694
|
+
// installed" / "next: command not found". This installs them once, up front,
|
|
695
|
+
// with its own generous timeout so it never collides with the 60s port-wait.
|
|
696
|
+
// Idempotent: skipped as soon as node_modules exists.
|
|
697
|
+
function ensureDeps() {
|
|
698
|
+
return new Promise((resolve) => {
|
|
699
|
+
let needs;
|
|
700
|
+
try {
|
|
701
|
+
needs =
|
|
702
|
+
fs.existsSync(path.join(WORKDIR, 'package.json')) &&
|
|
703
|
+
!fs.existsSync(path.join(WORKDIR, 'node_modules'));
|
|
704
|
+
} catch {
|
|
705
|
+
needs = false;
|
|
706
|
+
}
|
|
707
|
+
if (!needs) return resolve(true);
|
|
708
|
+
|
|
709
|
+
// `--legacy-peer-deps`: AI-scaffolded Expo/RN projects routinely pin a
|
|
710
|
+
// React-18 app alongside test tooling that peer-wants React 19, which makes
|
|
711
|
+
// a strict `npm install` abort with ERESOLVE. The app runtime only needs
|
|
712
|
+
// the React it pins, so we tell npm to install the tree as written rather
|
|
713
|
+
// than fail the whole preview over a test-only peer mismatch.
|
|
714
|
+
console.log('[preview] installing dependencies (npm install) — first preview, this can take a few minutes…');
|
|
715
|
+
const proc = spawn('sh', ['-c', 'npm install --legacy-peer-deps --no-audit --no-fund'], {
|
|
716
|
+
cwd: WORKDIR,
|
|
717
|
+
env: process.env,
|
|
718
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
719
|
+
});
|
|
720
|
+
const onData = (d) => process.stdout.write(`[npm] ${d}`);
|
|
721
|
+
proc.stdout.on('data', onData);
|
|
722
|
+
proc.stderr.on('data', onData);
|
|
723
|
+
const timer = setTimeout(() => {
|
|
724
|
+
console.error('[preview] npm install exceeded 6 min — moving on (preview may fail)');
|
|
725
|
+
try { proc.kill('SIGKILL'); } catch { /* already gone */ }
|
|
726
|
+
resolve(false);
|
|
727
|
+
}, 360000);
|
|
728
|
+
proc.on('exit', (code) => {
|
|
729
|
+
clearTimeout(timer);
|
|
730
|
+
if (code === 0) console.log('[preview] dependencies installed');
|
|
731
|
+
else console.error(`[preview] npm install exited ${code} — preview may fail`);
|
|
732
|
+
resolve(code === 0);
|
|
733
|
+
});
|
|
734
|
+
proc.on('error', (e) => {
|
|
735
|
+
clearTimeout(timer);
|
|
736
|
+
console.error('[preview] npm install error:', e.message);
|
|
737
|
+
resolve(false);
|
|
738
|
+
});
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
|
|
671
742
|
async function startPreview(cmd, port) {
|
|
672
743
|
killPreview();
|
|
744
|
+
// Make sure node_modules exists before the dev server tries to boot.
|
|
745
|
+
await ensureDeps();
|
|
673
746
|
console.log(`[preview] starting: ${cmd} (port ${port})`);
|
|
674
747
|
const proc = spawn('sh', ['-c', cmd], { cwd: WORKDIR, env: process.env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
675
748
|
preview = { port, proc, kind: null, url: null, tunnel: null, tunnelProc: null, output: '', buildError: null };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "esque-bridge",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.6",
|
|
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"
|