diff --git a/README.md b/README.md index b6d1692..6f88ae3 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,19 @@ Then run: ourocode ``` +With no arguments, `ourocode` uses the current working directory as the project +directory. Pass `--project-dir PATH` (or set `OUROCODE_PROJECT_DIR`) to point it +elsewhere. + +### Requirements + +The bundled `ourocode` is an Erlang escript, so it needs the **Erlang/OTP +runtime** (`escript`/`erl`) on your `PATH`. The installer installs it +best-effort (Homebrew on macOS, `apt`/`dnf` on Linux); if that is not possible +it stops with manual instructions. Install it yourself with `brew install +erlang`, `sudo apt-get install erlang`, or `sudo dnf install erlang`. Set +`OUROCODE_SKIP_ERLANG=1` to bypass the check. + Optional model backends: - Claude CLI @@ -96,7 +109,7 @@ curl -fsSL https://raw.githubusercontent.com/Q00/ourocode/release/bootstrap/inst ourocode ``` -`install.sh` downloads the matching GitHub Release tarball, installs `ourocode` into `~/.local/ourocode/`, and writes a launcher at `~/.local/bin/ourocode`. The launcher sets `OUROCODE_TTY` so the installed escript can find the bundled native tty helper. +`install.sh` downloads the matching GitHub Release tarball, installs `ourocode` into `~/.local/ourocode/`, and writes a launcher at `~/.local/bin/ourocode`. The launcher sets `OUROCODE_TTY` so the installed escript can find the bundled native tty helper. It also ensures the Erlang/OTP runtime is available (see [Requirements](#requirements)), since the escript cannot run without it. When run from a source checkout, the same installer uses bundled release binaries if present, or builds from source when needed. Set `OUROCODE_BUILD_FROM_SOURCE=1` to force a local build. diff --git a/install.sh b/install.sh index 237e856..86fb49d 100755 --- a/install.sh +++ b/install.sh @@ -75,6 +75,54 @@ download_release() { fi } +ensure_erlang_runtime() { + # The bundled `ourocode` is an Erlang escript and needs the Erlang/OTP runtime + # (`escript`/`erl`) on PATH to run. Release tarballs do not bundle the runtime, + # so a fresh machine ends up with a working launcher that cannot start. Make + # the runtime present here (best effort, like the Ouroboros step) so the + # documented `ourocode` quick start works after install. + if command -v escript >/dev/null 2>&1; then + return 0 + fi + + if [ "${OUROCODE_SKIP_ERLANG:-0}" = "1" ]; then + echo "==> skipping Erlang runtime check (OUROCODE_SKIP_ERLANG=1)" >&2 + return 0 + fi + + echo "==> Erlang runtime (escript) not found; ourocode needs it to run" + + local os + os="$(uname -s | tr '[:upper:]' '[:lower:]')" + + if command -v brew >/dev/null 2>&1; then + echo " installing Erlang via Homebrew (best effort)" + if brew install erlang; then + brew link --overwrite erlang >/dev/null 2>&1 || true + fi + elif [ "$os" = "linux" ] && command -v apt-get >/dev/null 2>&1; then + echo " installing Erlang via apt-get (best effort; may require sudo)" + sudo apt-get update -y >/dev/null 2>&1 || true + sudo apt-get install -y erlang >/dev/null 2>&1 || true + elif [ "$os" = "linux" ] && command -v dnf >/dev/null 2>&1; then + echo " installing Erlang via dnf (best effort; may require sudo)" + sudo dnf install -y erlang >/dev/null 2>&1 || true + fi + + if ! command -v escript >/dev/null 2>&1; then + echo "" >&2 + echo "error: Erlang/OTP runtime not found and could not be installed automatically." >&2 + echo " ourocode is an Erlang escript and needs 'escript'/'erl' on PATH." >&2 + echo " Install Erlang, then re-run this installer:" >&2 + echo " macOS: brew install erlang" >&2 + echo " Debian/Ubuntu: sudo apt-get install erlang" >&2 + echo " Fedora: sudo dnf install erlang" >&2 + echo " Other platforms: https://www.erlang.org/downloads" >&2 + echo " (set OUROCODE_SKIP_ERLANG=1 to bypass this check)" >&2 + exit 1 + fi +} + echo "==> ourocode install" need_build=0 @@ -131,6 +179,11 @@ exec "$INSTALL_DIR/ourocode" "\$@" EOF chmod +x "$BIN_DIR/ourocode" +# The launcher above runs an Erlang escript; make sure the runtime exists before +# we try to invoke it (e.g. the `--detect` call below) or hand control back to +# the user. +ensure_erlang_runtime + # Ourocode surfaces the Ouroboros capability graph. This step is best-effort # and can be skipped for lean installs or CI with OUROCODE_SKIP_OUROBOROS=1. OUROBOROS_INSTALL_URL="${OUROBOROS_INSTALL_URL:-https://raw.githubusercontent.com/Q00/ouroboros/main/scripts/install.sh}" diff --git a/lib/ourocode/cli/startup_args.ex b/lib/ourocode/cli/startup_args.ex index 518ce01..b873e62 100644 --- a/lib/ourocode/cli/startup_args.ex +++ b/lib/ourocode/cli/startup_args.ex @@ -7,7 +7,7 @@ defmodule Ourocode.CLI.StartupArgs do config/task parsers after startup-only flags are removed. """ - @default_project_dir "/Users/jaegyu.lee/Project/ourocode" + @project_dir_env "OUROCODE_PROJECT_DIR" @project_dir_flags MapSet.new(["--project-dir", "--project", "-d"]) @smoke_test_flags MapSet.new(["--smoke-test", "--smoke", "--verify"]) @prompt_flags MapSet.new(["--prompt", "-p"]) @@ -87,10 +87,30 @@ defmodule Ourocode.CLI.StartupArgs do def parse(_args, _options), do: {:error, "CLI args must be a list"} @doc """ - Returns the default implementation project directory for the launcher. + Returns the default project directory for the launcher. + + Resolution order: + + * the `OUROCODE_PROJECT_DIR` environment variable when it is set to a + non-empty value (expanded to an absolute path), otherwise + * the current working directory. + + This keeps the documented `ourocode` (no `--project-dir`) quick-start working + on any machine instead of pointing at a build-time developer path. """ @spec default_project_dir() :: String.t() - def default_project_dir, do: @default_project_dir + def default_project_dir do + case System.get_env(@project_dir_env) do + value when is_binary(value) -> + case String.trim(value) do + "" -> File.cwd!() + trimmed -> Path.expand(trimmed) + end + + _ -> + File.cwd!() + end + end defp extract_startup_args(args, project_dir), do: extract_startup_args(args, project_dir, false, false, :text, [], []) diff --git a/test/ourocode/cli_test.exs b/test/ourocode/cli_test.exs index ebcfd4a..754b480 100644 --- a/test/ourocode/cli_test.exs +++ b/test/ourocode/cli_test.exs @@ -74,9 +74,19 @@ defmodule Ourocode.CLITest do import ExUnit.CaptureIO - test "startup resolves the fixed implementation project directory" do - assert Ourocode.CLI.resolve_project_dir() == - {:ok, "/Users/jaegyu.lee/Project/ourocode"} + test "startup resolves the current working directory by default" do + assert Ourocode.CLI.resolve_project_dir() == {:ok, File.cwd!()} + end + + test "startup honors the OUROCODE_PROJECT_DIR override" do + System.put_env("OUROCODE_PROJECT_DIR", File.cwd!()) + + try do + assert Ourocode.CLI.resolve_project_dir(StartupArgs.default_project_dir()) == + {:ok, File.cwd!()} + after + System.delete_env("OUROCODE_PROJECT_DIR") + end end test "startup argument parser separates launch args, config overrides, and task text" do @@ -269,7 +279,7 @@ defmodule Ourocode.CLITest do Ourocode.CLI.main([], Ourocode.CLITest.DashboardSpy) assert_receive {:dashboard_init, ^context} - assert context.project_dir == "/Users/jaegyu.lee/Project/ourocode" + assert context.project_dir == File.cwd!() assert is_binary(context.cwd) assert context.initial_task_request == nil assert context.plugin_config.plugins |> Enum.map(& &1.id) == ["ouroboros-plugin"]