diff --git a/README.md b/README.md index fa66f25e..aef1d78a 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,110 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines and [AGENTS.m ## Install +### Public (binary) + +Use this for users on production machines where you don't want to expose source: + +```bash +curl -fsSL https://raw.githubusercontent.com/getcompanion-ai/co-mono/main/public-install.sh | bash +``` + +Then run: + +```bash +co-mono +``` + +The installer downloads the latest release archive, writes a launcher to +`~/.local/bin/co-mono`, and creates a private agent settings directory at +`~/.co-mono/agent/settings.json` with remote packages. + +Preinstalled package sources are: + +```json +[ + "npm:@e9n/pi-channels", + "npm:pi-memory-md", + "npm:pi-teams" +] +``` + +If `npm` is available, it also tries to install these packages during install. + +### Keep it running + +Start and keep `co-mono` alive with your process supervisor of choice (systemd, launchd, supervisor, Docker, etc). + +For public installs, a minimal systemd user service is: + +```bash +mkdir -p ~/.config/systemd/user +cat > ~/.config/systemd/user/co-mono.service <<'EOF' +[Unit] +Description=co-mono +After=network-online.target + +[Service] +Type=simple +Environment=PI_CODING_AGENT_DIR=%h/.co-mono/agent +Environment=CO_MONO_AGENT_DIR=%h/.co-mono/agent +ExecStart=%h/.local/bin/co-mono +Restart=always +RestartSec=5 + +[Install] +WantedBy=default.target +EOF + +systemctl --user daemon-reload +systemctl --user enable --now co-mono +``` + +### Local (source) + ```bash git clone https://github.com/getcompanion-ai/co-mono.git cd co-mono -npm install +./install.sh +``` + +Run: + +```bash +./co-mono +``` + +Run with built-in runtime watchdog: + +```bash +CO_MONO_RUNTIME_COMMAND="python -m http.server 8765" \ + ./co-mono --with-runtime-daemon +``` + +For a user systemd setup, create `~/.config/systemd/user/co-mono.service` with: + +```ini +[Unit] +Description=co-mono +After=network-online.target + +[Service] +Type=simple +Environment=PI_CODING_AGENT_DIR=%h/.co-mono/agent +Environment=CO_MONO_AGENT_DIR=%h/.co-mono/agent +ExecStart=/absolute/path/to/repo/co-mono --with-runtime-daemon +Restart=always +RestartSec=5 + +[Install] +WantedBy=default.target +``` + +Then enable: + +```bash +systemctl --user daemon-reload +systemctl --user enable --now co-mono ``` Optional: diff --git a/public-install.sh b/public-install.sh new file mode 100755 index 00000000..e53cac0b --- /dev/null +++ b/public-install.sh @@ -0,0 +1,257 @@ +#!/usr/bin/env bash + +set -euo pipefail + +REPO="${CO_MONO_REPO:-getcompanion-ai/co-mono}" +VERSION="${CO_MONO_VERSION:-latest}" +INSTALL_DIR="${CO_MONO_INSTALL_DIR:-$HOME/.co-mono}" +BIN_DIR="${CO_MONO_BIN_DIR:-$HOME/.local/bin}" +AGENT_DIR="${CO_MONO_AGENT_DIR:-$INSTALL_DIR/agent}" +RUN_INSTALL_PACKAGES="${CO_MONO_INSTALL_PACKAGES:-1}" +SKIP_REINSTALL="${CO_MONO_SKIP_REINSTALL:-0}" +INSTALL_RUNTIME_DAEMON="${CO_MONO_INSTALL_RUNTIME_DAEMON:-0}" + +DEFAULT_PACKAGES=( + "npm:@e9n/pi-channels" + "npm:pi-memory-md" + "npm:pi-teams" +) + +log() { + echo "==> $*" +} + +fail() { + echo "ERROR: $*" >&2 + exit 1 +} + +need() { + if ! command -v "$1" >/dev/null 2>&1; then + fail "required tool not found: $1" + fi +} + +need tar + +if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then + fail "required tool not found: curl or wget" +fi + +if ! command -v git >/dev/null 2>&1; then + log "git not found; this is fine unless package install is triggered" +fi + +if [[ -d "$INSTALL_DIR" && "${SKIP_REINSTALL}" != "1" ]]; then + rm -rf "$INSTALL_DIR" +fi + +detect_platform() { + local os + local arch + + os="$(uname -s | tr '[:upper:]' '[:lower:]')" + arch="$(uname -m)" + + case "$os" in + darwin) os="darwin" ;; + linux) os="linux" ;; + mingw*|msys*|cygwin*) + os="windows" + ;; + *) + fail "unsupported OS: $os" + ;; + esac + + case "$arch" in + x86_64|amd64) + arch="x64" + ;; + aarch64|arm64) + arch="arm64" + ;; + *) + fail "unsupported CPU architecture: $arch" + ;; + esac + + PLATFORM="${os}-${arch}" +} + +download_json() { + local url="$1" + local out="$2" + + if command -v curl >/dev/null 2>&1; then + curl -fsSL "$url" -o "$out" + elif command -v wget >/dev/null 2>&1; then + wget -qO "$out" "$url" + else + fail "neither curl nor wget is available" + fi +} + +resolve_release_tag() { + if [[ "$VERSION" != latest ]]; then + echo "$VERSION" + return + fi + + local api_json + api_json="$(mktemp)" + download_json "https://api.github.com/repos/${REPO}/releases/latest" "$api_json" + TAG="$(awk -F '"tag_name": "' 'index($0, "\"tag_name\"") { split($2, a, "\""); print a[1] }' "$api_json" | head -n 1)" + rm -f "$api_json" + + if [[ -z "$TAG" ]]; then + fail "could not determine latest tag from GitHub API" + fi + + echo "$TAG" +} + +platform_asset() { + if [[ "$PLATFORM" == windows-* ]]; then + echo "pi-${PLATFORM}.zip" + else + echo "pi-${PLATFORM}.tar.gz" + fi +} + +extract_archive() { + local archive="$1" + local out_dir="$2" + + mkdir -p "$out_dir" + if [[ "$archive" == *.zip ]]; then + if ! command -v unzip >/dev/null 2>&1; then + fail "unzip not found for windows archive" + fi + unzip -q "$archive" -d "$out_dir" + else + tar -xzf "$archive" -C "$out_dir" + fi +} + +ensure_agent_settings() { + mkdir -p "$AGENT_DIR" + + local settings_file="$AGENT_DIR/settings.json" + if [[ -f "$settings_file" ]]; then + return + fi + + cat > "$settings_file" <<'EOF' +{ + "packages": [ + "npm:@e9n/pi-channels", + "npm:pi-memory-md", + "npm:pi-teams" + ] +} +EOF +} + +maybe_install_packages() { + if [[ "$RUN_INSTALL_PACKAGES" == "0" ]]; then + return + fi + + if ! command -v npm >/dev/null 2>&1; then + log "npm not found. Skipping package installation (settings.json was still written)." + return + fi + + for pkg in "${DEFAULT_PACKAGES[@]}"; do + if [[ -n "$CO_MONO_BIN_PATH" ]]; then + log "Installing package: $pkg" + if ! PI_CODING_AGENT_DIR="$AGENT_DIR" CO_MONO_AGENT_DIR="$AGENT_DIR" "$CO_MONO_BIN_PATH" install "$pkg" >/dev/null 2>&1; then + log "Could not install $pkg now. It will be installed on first run if network/API access is available." + fi + fi + done +} + +write_launcher() { + mkdir -p "$BIN_DIR" + local launcher="$BIN_DIR/co-mono" + cat > "$launcher" <