Merge remote-tracking branch 'origin/master' into cod-28-security-public-assets

This commit is contained in:
arkon
2026-06-08 18:05:10 +02:00
46 changed files with 6661 additions and 322 deletions
+3
View File
@@ -32,6 +32,9 @@ run('chmod dist/index.js', 'chmod +x dist/index.js');
// 2. Copy static assets (clean first to remove stale hashed files from previous builds)
run('clean public', 'rm -rf dist/web/public');
run('prepare dirs', 'mkdir -p dist/web dist/templates dist/web/public/vendor');
// Fetch the opt-in gesture overlay's MediaPipe wasm + model into src/ (idempotent,
// non-fatal, kept out of git) so the copy below carries them into dist/.
run('gesture assets', 'node scripts/fetch-gesture-assets.mjs');
run('copy web assets', 'cp -r src/web/public dist/web/');
run('copy template', 'cp src/templates/case-template.md dist/templates/');
+4
View File
@@ -12,6 +12,10 @@ KillMode=process
Environment=NODE_ENV=production
Environment=HOME=/home/arkon
Environment=NODE_COMPILE_CACHE=/home/arkon/.codeman/compile-cache
# Make the gesture-control overlay AVAILABLE (CSP widening + /gesture/ assets +
# window.__codemanGestureAvailable). The actual on/off stays the per-user
# `gestureControlEnabled` toggle in App Settings → Display → Input (default OFF).
Environment=CODEMAN_GESTURE=1
# Logging
StandardOutput=journal
+58
View File
@@ -0,0 +1,58 @@
/**
* @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}`);
});
+14
View File
@@ -312,6 +312,20 @@ if (isGlobalInstall) {
}
}
// ----------------------------------------------------------------------------
// 4b. Fetch gesture-overlay runtime assets (MediaPipe wasm + model) for dev mode
// (src/web/public/gesture/). Opt-in feature (CODEMAN_GESTURE=1); non-fatal.
// Large binaries kept out of git; the build copies them into dist/.
// ----------------------------------------------------------------------------
if (!isGlobalInstall) {
try {
execSync(`node "${join(import.meta.dirname, 'fetch-gesture-assets.mjs')}"`, { stdio: 'inherit' });
} catch {
// Non-fatal — the gesture overlay is opt-in.
}
}
// ----------------------------------------------------------------------------
// 5. Install git pre-commit hook (format check)
// ----------------------------------------------------------------------------
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
#
# run-beta.sh — launch a BETA Codeman isolated from a production instance.
#
# Codeman's data dir (~/.codeman) and tmux socket (-L codeman) are process-wide
# and shared by every instance on the machine. The code now DEFAULTS to that
# production layout on port 3000 (safe for master / existing installs), so a beta
# build no longer isolates itself automatically — this wrapper opts it in:
#
# CODEMAN_INSTANCE=beta → data dir ~/.codeman-beta + tmux socket codeman-beta
# CODEMAN_PORT=5000 → listen on 5000 instead of 3000
#
# Result: the beta runs side-by-side with prod and can never discover/attach to
# prod's live tmux sessions or clobber prod's state.json. Override either var to
# run additional named instances, e.g. CODEMAN_INSTANCE=foo CODEMAN_PORT=5050.
#
# Usage: ./scripts/run-beta.sh [extra `codeman web` flags]
# Build first (the beta runs the compiled dist): npm run build
set -euo pipefail
export CODEMAN_INSTANCE="${CODEMAN_INSTANCE:-beta}"
export CODEMAN_PORT="${CODEMAN_PORT:-5000}"
DIST="$(cd "$(dirname "$0")/.." && pwd)/dist/index.js"
if [ ! -f "$DIST" ]; then
echo "dist not found at $DIST — run 'npm run build' first." >&2
exit 1
fi
echo "Starting beta Codeman: instance='$CODEMAN_INSTANCE' (~/.codeman-$CODEMAN_INSTANCE, -L codeman-$CODEMAN_INSTANCE) on port $CODEMAN_PORT"
exec node "$DIST" web "$@"
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
#
# span-codeman.sh — open a Codeman window stretched across ALL displays, so that
# in-page floating session panels can be dragged from one physical monitor to
# the other. Spawned by the header "multi-monitor" button (POST
# /api/system/span-displays), or run by hand at the desk.
#
# ── PREREQUISITE (one-time, manual) ──────────────────────────────────────────
# System Settings → Desktop & Dock → turn OFF "Displays have separate Spaces",
# then LOG OUT and back in. Until you do, macOS keeps every window on a single
# display and this script's window will clamp to one monitor instead of spanning.
# (Equivalent CLI: `defaults write com.apple.spaces spans-displays -bool true`,
# still needs a re-login. Revert with `-bool false`.)
#
# Why a maximized --app window and not fullscreen: browser fullscreen is
# per-display and will NOT span. We size a windowed app to the union of all
# displays instead. macOS only.
#
set -euo pipefail
URL="${1:-http://localhost:5000}"
# Union rect of all displays in top-left-origin points — exactly what Chromium's
# --window-position/--window-size expect. Finder's desktop window bounds already
# encloses every monitor (and handles a monitor placed left/above via a negative
# origin), so no per-display math or coordinate flipping is needed.
bounds=$(osascript -e 'tell application "Finder" to get bounds of window of desktop')
X=$(echo "$bounds" | awk -F', *' '{print $1}')
Y=$(echo "$bounds" | awk -F', *' '{print $2}')
R=$(echo "$bounds" | awk -F', *' '{print $3}')
B=$(echo "$bounds" | awk -F', *' '{print $4}')
W=$((R - X))
H=$((B - Y))
echo "Display union: position ${X},${Y} size ${W}x${H}"
# Pick a Chromium-family browser. Brave leads the list — plain Google Chrome
# bounced when launched this way on the desk machine (created its profile then
# exited without a window). Force a specific one with, e.g.,
# BROWSER="Google Chrome" ./span-codeman.sh
app="${BROWSER:-}"
if [ -z "$app" ]; then
for c in "Brave Browser" "Google Chrome" "Google Chrome Beta" "Chromium" "Microsoft Edge"; do
[ -x "/Applications/$c.app/Contents/MacOS/$c" ] && app="$c" && break
done
fi
bin="/Applications/$app.app/Contents/MacOS/$app"
[ -n "$app" ] && [ -x "$bin" ] || { echo "No Chrome-family browser found (BROWSER='$app')" >&2; exit 1; }
# A dedicated, PER-BROWSER profile forces a FRESH instance — an already-running
# browser would hand the URL to itself and silently ignore the geometry flags.
# Per-browser so a Chrome-made profile can't confuse Brave (or vice-versa).
slug=$(echo "$app" | tr '[:upper:] ' '[:lower:]-')
profile="$HOME/.codeman-gesture-$slug"
echo "Browser: $bin"
echo "URL: $URL"
# Detach so the caller (terminal / web server) isn't blocked for the window's life.
nohup "$bin" \
--app="$URL" \
--user-data-dir="$profile" \
--window-position="${X},${Y}" \
--window-size="${W},${H}" \
--no-first-run \
--no-default-browser-check \
>/dev/null 2>&1 &
echo "Launched spanning window (pid $!)."
echo "If it filled only one monitor, the 'separate Spaces' prerequisite above"
echo "isn't active yet — toggle it off, log out/in, and re-run."