#!/usr/bin/env bash # # sf-from-source — run SF directly from this source checkout via node. # # Purpose: every local commit in this repo is live immediately without # rebuilding dist/. Human CLI invocations use this bash shim for better # shell integration (set -e, pipefail, etc.). # # Subagents: SF_BIN_PATH is exported as dist/loader.js (not this shim), so # all child pi processes spawned by the subagent extension use dist/loader.js # directly as their entry point. dist/loader.js is a proper Node.js shebang # entry point, avoiding the bash-script-vs-node parsing issue. # # Why node, not bun: # - bun doesn't ship node:sqlite (sf-db.ts falls back to filesystem- # derivation degraded mode under bun). # - bun's native-addon loader doesn't inherit the system library # search path under Nix (libz.so.1 not found for forge_engine.node). # - node 26.1+ has stable enough node:sqlite coverage for SF's database-first # runtime and supports # --experimental-strip-types so .ts runs directly. # - The src/resources/extensions/sf/tests/resolve-ts.mjs loader hook # already handles .js → .ts import-specifier remapping for runtime # resolution. # # Contract: # - Executable shim; human CLI entry point with full shell features. # - Exports SF_BIN_PATH=dist/loader.js so all child processes (including # subagent pi instances) use the Node.js entry point directly. # # Requirements: node >= 26.1 on PATH, # node_modules populated. set -euo pipefail SCRIPT_DIR=$(cd -- "$(dirname -- "$(readlink -f "${BASH_SOURCE[0]}")")" &>/dev/null && pwd) SF_SOURCE_ROOT=$(cd -- "$SCRIPT_DIR/.." &>/dev/null && pwd) if [[ -n "${SF_NODE_BIN:-}" ]]; then NODE_BIN="$SF_NODE_BIN" elif [[ -x "$HOME/.local/bin/mise" ]]; then NODE_BIN=$(cd -- "$SF_SOURCE_ROOT" && "$HOME/.local/bin/mise" which node 2>/dev/null || true) NODE_BIN=${NODE_BIN:-node} else NODE_BIN=node fi IS_HEADLESS=0 if [[ "${1:-}" == "headless" ]]; then IS_HEADLESS=1 echo "[forge] Preparing source runtime for headless command..." fi # SF_BIN_PATH: absolute path to dist/loader.js (not this shim). # This is what the subagent extension spawns for child pi processes. # dist/loader.js is a proper Node.js entry point — bash scripts cannot be # spawned by Node.js as executables (Node parses them as JS, causing SyntaxError). export SF_BIN_PATH="$SF_SOURCE_ROOT/dist/loader.js" export SF_CLI_PATH="${SF_CLI_PATH:-$SCRIPT_DIR/sf-from-source}" "$NODE_BIN" "$SF_SOURCE_ROOT/scripts/ensure-source-resources.cjs" if [[ "$IS_HEADLESS" == "1" ]]; then echo "[forge] Launching source CLI..." fi ORIGINAL_ARGS=("$@") NEXT_ARGS=("${ORIGINAL_ARGS[@]}") while true; do set +e "$NODE_BIN" \ --import "$SF_SOURCE_ROOT/src/resources/extensions/sf/tests/resolve-ts.mjs" \ --experimental-strip-types \ --no-warnings \ "$SF_SOURCE_ROOT/src/loader.ts" "${NEXT_ARGS[@]}" status=$? set -e if [[ "$status" == "12" && "$IS_HEADLESS" != "1" && -t 0 && -t 1 ]]; then echo "[forge] Runtime reload requested — restarting source CLI with --continue..." NEXT_ARGS=("--continue") continue fi exit "$status" done