Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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/<version>`, 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/<version>`, 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.

Expand Down
53 changes: 53 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}"
Expand Down
26 changes: 23 additions & 3 deletions lib/ourocode/cli/startup_args.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
Expand Down Expand Up @@ -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, [], [])
Expand Down
18 changes: 14 additions & 4 deletions test/ourocode/cli_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"]
Expand Down