Files
Codeman/scripts/fetch-gesture-assets.mjs
arkonandClaude Opus 4.8 cf6fabc070 fix(web): address self-review findings on #103 (master-safe defaults + hardening)
Make the branch genuinely master-mergeable and fix several review findings:

- Defaults are now prod-safe: CODEMAN_INSTANCE defaults to '' (→ ~/.codeman,
  -L codeman) and the web port back to 3000, so an existing install upgrades
  cleanly. Port also honors a new CODEMAN_PORT env var. Run the beta isolated
  alongside prod with scripts/run-beta.sh (CODEMAN_INSTANCE=beta + PORT 5000).
- .gitignore: anchor the root `public` symlink rule to `/public` (a bare
  `public` also swallowed src/web/public, silently un-staging new web assets);
  ignore the gesture wasm/model binaries explicitly instead.
- span-displays: add a macOS-only guard (400 elsewhere instead of spawning a
  bash that fails invisibly); extract resolveSpanUrl() for unit testing.
- server.ts: memoize asset-version stat() calls (~1s TTL) so each index render
  doesn't re-stat every script/link tag.
- styles.css: hide the multi-monitor button in solo (detached) windows.
- app.js: require two consecutive unanswered roll-calls before redocking, so a
  timer-throttled background popup isn't wrongly un-marked.
- index.html: make the "skip to terminal" link base-href-safe (onclick scroll)
  so it doesn't navigate to the dashboard from a /session/:id window.
- Tests: test/config/instance.test.ts, test/routes/system-span-displays.test.ts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 15:41:46 +02:00

59 lines
2.8 KiB
JavaScript

/**
* @fileoverview Fetch the gesture-overlay runtime assets (MediaPipe wasm + the
* gesture-recognizer model) into src/web/public/gesture/ so Codeman can serve
* them same-origin (a browser content-blocker otherwise blocks the public CDNs
* and the overlay fails to start). These are large binaries (~27 MB) kept OUT of
* git (ignored explicitly via `src/web/public/gesture/wasm/` + `*.task` in
* .gitignore); they are fetched here at install (postinstall) and build time.
*
* Idempotent: skips files already present. Non-fatal: the gesture overlay is
* opt-in (CODEMAN_GESTURE=1), so a fetch failure only warns — it must not break
* `npm install` / `npm run build`. The build then copies src/web/public into
* dist/ as usual, so prod gets these too.
*
* The @mediapipe/tasks-vision version MUST match the one bundled into the gesture
* overlay (Ark0N/codeman-gesture-control) so the wasm loader matches its JS API.
*/
import { mkdirSync, existsSync, statSync, writeFileSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const GESTURE = join(__dirname, '..', 'src', 'web', 'public', 'gesture');
const WASM = join(GESTURE, 'wasm');
const MP_VERSION = '0.10.21'; // keep in sync with the gesture overlay's @mediapipe/tasks-vision
const WASM_BASE = `https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@${MP_VERSION}/wasm`;
const MODEL_URL =
'https://storage.googleapis.com/mediapipe-models/gesture_recognizer/gesture_recognizer/float16/1/gesture_recognizer.task';
const ASSETS = [
{ url: `${WASM_BASE}/vision_wasm_internal.js`, path: join(WASM, 'vision_wasm_internal.js') },
{ url: `${WASM_BASE}/vision_wasm_internal.wasm`, path: join(WASM, 'vision_wasm_internal.wasm') },
{ url: `${WASM_BASE}/vision_wasm_nosimd_internal.js`, path: join(WASM, 'vision_wasm_nosimd_internal.js') },
{ url: `${WASM_BASE}/vision_wasm_nosimd_internal.wasm`, path: join(WASM, 'vision_wasm_nosimd_internal.wasm') },
{ url: MODEL_URL, path: join(GESTURE, 'gesture_recognizer.task') },
];
async function main() {
mkdirSync(WASM, { recursive: true });
let fetched = 0;
let skipped = 0;
for (const a of ASSETS) {
if (existsSync(a.path) && statSync(a.path).size > 0) {
skipped++;
continue;
}
const res = await fetch(a.url);
if (!res.ok) throw new Error(`HTTP ${res.status} for ${a.url}`);
writeFileSync(a.path, Buffer.from(await res.arrayBuffer()));
fetched++;
}
console.log(`[gesture] MediaPipe assets ready (${fetched} fetched, ${skipped} cached) → ${GESTURE}`);
}
main().catch((err) => {
// Non-fatal: opt-in feature. Warn and exit 0 so install/build still succeed.
console.warn(`[gesture] could not fetch MediaPipe assets — overlay disabled until fetched: ${err.message}`);
});