diff --git a/.github/workflows/pages-preview.yml b/.github/workflows/pages-preview.yml
index 1c9e09b4de..78ec965916 100644
--- a/.github/workflows/pages-preview.yml
+++ b/.github/workflows/pages-preview.yml
@@ -12,6 +12,8 @@ on:
- "uv.lock"
- "src/ai_company/**"
- "scripts/**"
+ - "cli/scripts/install.sh"
+ - "cli/scripts/install.ps1"
- ".github/workflows/pages-preview.yml"
workflow_dispatch:
inputs:
@@ -131,6 +133,13 @@ jobs:
working-directory: site
run: npm run build
+ # --- Copy install scripts into /get/ ---
+ - name: Copy CLI install scripts into Astro output
+ run: |
+ mkdir -p site/dist/get
+ cp cli/scripts/install.sh site/dist/get/install.sh
+ cp cli/scripts/install.ps1 site/dist/get/install.ps1
+
# --- Merge outputs ---
- name: Merge Astro + Zensical into final output
run: |
diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
index e78dbcbf01..2c392f384e 100644
--- a/.github/workflows/pages.yml
+++ b/.github/workflows/pages.yml
@@ -11,6 +11,8 @@ on:
- "uv.lock"
- "src/ai_company/**"
- "scripts/**"
+ - "cli/scripts/install.sh"
+ - "cli/scripts/install.ps1"
- ".github/workflows/pages.yml"
workflow_dispatch:
@@ -67,6 +69,13 @@ jobs:
working-directory: site
run: npm run build
+ # --- Copy install scripts into /get/ ---
+ - name: Copy CLI install scripts into Astro output
+ run: |
+ mkdir -p site/dist/get
+ cp cli/scripts/install.sh site/dist/get/install.sh
+ cp cli/scripts/install.ps1 site/dist/get/install.ps1
+
# --- Merge outputs ---
- name: Merge Astro + Zensical into final output
run: |
diff --git a/CLAUDE.md b/CLAUDE.md
index 75a2e1591d..fcb0d199be 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -78,7 +78,7 @@ cd cli && golangci-lint run # lint
- **Library reference**: `docs/api/` — auto-generated from docstrings via mkdocstrings + Griffe (AST-based, no imports)
- **Custom templates**: `docs/overrides/` (`custom_dir` in `mkdocs.yml` for optional theme/template overrides)
- **Scripts**: `scripts/` — CI/build utility scripts (relaxed ruff rules: `print` and deferred imports allowed)
-- **Landing page**: `site/` (Astro, Concept C hybrid design)
+- **Landing page**: `site/` (Astro, Concept C hybrid design). Includes `/get/` CLI installation page and shared `Footer` component.
- **Config**: `mkdocs.yml` at repo root (Zensical reads this natively)
- **CI**: `.github/workflows/pages.yml` — exports OpenAPI schema (`scripts/export_openapi.py`), builds Astro landing + Zensical docs, merges, deploys to GitHub Pages
- **Architecture decisions**: `docs/architecture/decisions.md` (decision log)
@@ -153,6 +153,12 @@ cli/ # Go CLI binary (cross-platform, manages Docker lifecycle)
scripts/ # Install scripts (install.sh, install.ps1)
testdata/ # Golden files for compose generation tests
.goreleaser.yml # GoReleaser config (cross-compile, checksums)
+
+site/ # Astro landing page (synthorg.io)
+ src/
+ pages/ # Astro pages (index, get)
+ components/ # Shared components (Footer)
+ layouts/ # Base layout (Base.astro)
```
## Shell Usage
@@ -236,7 +242,7 @@ cli/ # Go CLI binary (cross-platform, manages Docker lifecycle)
- **Path filtering**: `dorny/paths-filter` detects Python/dashboard/docker changes; jobs only run when their domain is affected. CLI has its own workflow (`cli.yml`).
- **Jobs**: lint (ruff) + type-check (mypy) + test (pytest + coverage) + python-audit (pip-audit) + dockerfile-lint (hadolint) + dashboard-lint/type-check/test/build/audit (npm) run in parallel → ci-pass (gate)
-- **Pages**: `.github/workflows/pages.yml` — exports OpenAPI schema (`scripts/export_openapi.py`), builds Astro landing + Zensical docs, merges, deploys to GitHub Pages on push to main. Triggers on `docs/**`, `site/**`, `mkdocs.yml`, `pyproject.toml`, `uv.lock`, `src/ai_company/**`, `scripts/**`, workflow file changes, and `workflow_dispatch`.
+- **Pages**: `.github/workflows/pages.yml` — exports OpenAPI schema (`scripts/export_openapi.py`), builds Astro landing + Zensical docs, copies CLI install scripts into `/get/`, merges, deploys to GitHub Pages on push to main. Triggers on `docs/**`, `site/**`, `mkdocs.yml`, `pyproject.toml`, `uv.lock`, `src/ai_company/**`, `scripts/**`, `cli/scripts/install.{sh,ps1}`, workflow file changes, and `workflow_dispatch`.
- **PR Preview**: `.github/workflows/pages-preview.yml`
- Builds site on PRs (same path triggers as Pages) and on `workflow_dispatch` (with `pr_number` input, for Dependabot PRs that can't trigger `pull_request` with secrets)
- Dispatch runs resolve PR metadata (head SHA, state, same-repo check) via `gh pr view`; PR-triggered runs use event context directly
diff --git a/README.md b/README.md
index 5e9e97d162..d77425678e 100644
--- a/README.md
+++ b/README.md
@@ -80,12 +80,12 @@ Built-in tools (file system, git, sandbox, code runner) plus MCP bridge for exte
```bash
# Linux / macOS
-curl -sSfL https://raw.githubusercontent.com/Aureliolo/synthorg/main/cli/scripts/install.sh | bash
+curl -sSfL https://synthorg.io/get/install.sh | bash
```
```powershell
# Windows (PowerShell)
-irm https://raw.githubusercontent.com/Aureliolo/synthorg/main/cli/scripts/install.ps1 | iex
+irm https://synthorg.io/get/install.ps1 | iex
```
### Setup & Run
diff --git a/cli/scripts/install.ps1 b/cli/scripts/install.ps1
index 09ea514af1..62ce4e480b 100644
--- a/cli/scripts/install.ps1
+++ b/cli/scripts/install.ps1
@@ -1,5 +1,5 @@
# SynthOrg CLI installer for Windows.
-# Usage: irm https://raw.githubusercontent.com/Aureliolo/synthorg/main/cli/scripts/install.ps1 | iex
+# Usage: irm https://synthorg.io/get/install.ps1 | iex
#
# Environment variables:
# SYNTHORG_VERSION — specific version to install (default: latest)
diff --git a/cli/scripts/install.sh b/cli/scripts/install.sh
index a95b720352..bbf802e771 100644
--- a/cli/scripts/install.sh
+++ b/cli/scripts/install.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# SynthOrg CLI installer for Linux and macOS.
-# Usage: curl -sSfL https://raw.githubusercontent.com/Aureliolo/synthorg/main/cli/scripts/install.sh | bash
+# Usage: curl -sSfL https://synthorg.io/get/install.sh | bash
#
# Environment variables:
# SYNTHORG_VERSION — specific version to install (default: latest)
diff --git a/docs/user_guide.md b/docs/user_guide.md
index 3a238463ff..8d4714c296 100644
--- a/docs/user_guide.md
+++ b/docs/user_guide.md
@@ -12,7 +12,7 @@ The recommended way to run SynthOrg is via the CLI:
```bash
# Install CLI (Linux/macOS)
-curl -sSfL https://raw.githubusercontent.com/Aureliolo/synthorg/main/cli/scripts/install.sh | bash
+curl -sSfL https://synthorg.io/get/install.sh | bash
# Set up and start
synthorg init # Interactive setup wizard
@@ -35,7 +35,43 @@ cp docker/.env.example docker/.env
docker compose -f docker/compose.yml up -d
```
-Container configuration (ports, storage paths, log level) is defined in `docker/.env`. Organization setup is done via the dashboard. Custom template editing through the UI is planned for a future release.
+### Containers
+
+| Container | Image | Description |
+|-----------|-------|-------------|
+| **backend** | `ghcr.io/aureliolo/synthorg` | Python API server (Litestar). 3-stage build, Chainguard distroless runtime (no shell), runs as non-root (UID 65532). |
+| **web** | `ghcr.io/aureliolo/synthorg-web` | Nginx + Vue 3 dashboard (PrimeVue + Tailwind CSS). SPA routing, proxies API and WebSocket requests to backend. |
+
+### Environment Variables
+
+Configuration is in `docker/.env` (copy from `docker/.env.example`):
+
+| Variable | Default | Description |
+|----------|---------|-------------|
+| `AI_COMPANY_JWT_SECRET` | *(auto-generated)* | JWT signing secret. Auto-generated and persisted on first run. Set explicitly only for multi-instance deployments. Must be >= 32 characters if set. |
+| `AI_COMPANY_DB_PATH` | `/data/synthorg.db` | SQLite database path (inside container). |
+| `AI_COMPANY_MEMORY_DIR` | `/data/memory` | Agent memory storage directory (inside container). |
+| `BACKEND_PORT` | `8000` | Host port for the backend API. |
+| `WEB_PORT` | `3000` | Host port for the web dashboard. |
+| `DOCKER_HOST` | *(unset)* | Docker socket for agent code execution sandbox (optional). |
+
+### First-Run Setup
+
+After the containers are running:
+
+1. **Create an admin account** by sending a POST request to the setup endpoint:
+
+ ```bash
+ curl -X POST http://localhost:8000/api/v1/auth/setup \
+ -H "Content-Type: application/json" \
+ -d '{"username": "admin", "password": "your-secure-password"}'
+ ```
+
+2. **Access the dashboard** at [http://localhost:3000](http://localhost:3000) and log in with your admin credentials.
+
+3. **Verify health** with `curl http://localhost:8000/api/v1/health`.
+
+Organization setup (choosing templates, configuring agents) is done via the dashboard. Custom template editing through the UI is planned for a future release.
!!! info "Active Development"
SynthOrg is under active development. The web dashboard is available for monitoring and managing the organization. Templates and some features described here may evolve. Check the [GitHub repository](https://github.com/Aureliolo/synthorg) for current status.
diff --git a/site/src/components/Footer.astro b/site/src/components/Footer.astro
new file mode 100644
index 0000000000..eec63547d3
--- /dev/null
+++ b/site/src/components/Footer.astro
@@ -0,0 +1,40 @@
+
diff --git a/site/src/pages/get/index.astro b/site/src/pages/get/index.astro
new file mode 100644
index 0000000000..e583d944da
--- /dev/null
+++ b/site/src/pages/get/index.astro
@@ -0,0 +1,251 @@
+---
+import Base from "../../layouts/Base.astro";
+import Footer from "../../components/Footer.astro";
+---
+
+
+ Install the CLI to set up and manage your synthetic organization in seconds.
+ Linux / macOS Windows (PowerShell) Then set up and start your organization:
+ The web dashboard opens at
+ Review the exact scripts the install commands run. They download a binary from
+ GitHub Releases,
+ verify its SHA-256 checksum, and place it in your PATH.
+
+ Download the install scripts and run them locally instead of piping from the network.
+
+ Then run:
+ Skip the install script — grab the binary directly from GitHub Releases.
+
+ The CLI automates Docker Compose setup, image pulling, health checks, and updates.
+ If you're comfortable with Docker, run the containers directly.
+ Windows (PowerShell) install script Linux / macOS install script
+ Get
+
+ SynthOrg
+
+
+ Quick Install
+
+
+
+ $ curl -sSfL https://synthorg.io/get/install.sh | bash
+ PS> irm https://synthorg.io/get/install.ps1 | iex
+ $ synthorg init # interactive setup wizard
+$ synthorg start # pull images + start containershttp://localhost:3000 (default).
+ Other Options
+
+ Look before you pipe
+ Direct Download
+ bash install.sh or .\install.ps1
+ Manual Binary
+
+
+ sha256sum -c checksums.txt
+ PATH
+ Skip the CLI entirely
+ install.ps1
+
+ {content}install.sh
+
+ {content}
$ git clone https://github.com/Aureliolo/synthorg
-$ cd synthorg
-$ docker compose -f docker/compose.yml up -d
+ $ curl -sSfL https://synthorg.io/get/install.sh | bash
+$ synthorg init
+$ synthorg start
Get Started
@@ -433,43 +442,5 @@ import Base from "../layouts/Base.astro";
-
+
diff --git a/src/ai_company/tools/_git_base.py b/src/ai_company/tools/_git_base.py
index 27b45bf4b8..5d4de2140c 100644
--- a/src/ai_company/tools/_git_base.py
+++ b/src/ai_company/tools/_git_base.py
@@ -71,6 +71,21 @@
"PRIVATE",
)
+# Git discovery env vars that override directory-based repo detection.
+# Stripping these ensures the tool always uses the workspace ``cwd`` for
+# repo discovery instead of stale env vars (e.g. ``GIT_DIR`` inherited
+# from ``git push`` → pre-push hook → agent subprocess chains).
+_GIT_DISCOVERY_VARS: Final[frozenset[str]] = frozenset(
+ {
+ "GIT_DIR",
+ "GIT_WORK_TREE",
+ "GIT_OBJECT_DIRECTORY",
+ "GIT_ALTERNATE_OBJECT_DIRECTORIES",
+ "GIT_INDEX_FILE",
+ "GIT_COMMON_DIR",
+ }
+)
+
_CONTROL_CHAR_RE = re.compile(r"[\x00-\x1f\x7f]+")
_MAX_STDERR_FRAGMENT: Final[int] = 500
@@ -250,11 +265,14 @@ def _check_git_arg(
def _build_git_env() -> dict[str, str]:
"""Build a hardened environment for git subprocesses.
- Applies git hardening overrides and strips obvious secret
- env vars as defense-in-depth. For full environment filtering,
- use a ``SandboxBackend``.
+ Applies git hardening overrides, strips git discovery env vars
+ (so the tool uses *cwd*-based repo detection), and removes
+ obvious secret env vars as defense-in-depth. For full
+ environment filtering, use a ``SandboxBackend``.
"""
env = {**os.environ, **_GIT_HARDENING_OVERRIDES}
+ for key in _GIT_DISCOVERY_VARS:
+ env.pop(key, None)
for key in list(env):
upper = key.upper()
if any(sub in upper for sub in _SECRET_SUBSTRINGS):
diff --git a/tests/unit/tools/git/conftest.py b/tests/unit/tools/git/conftest.py
index 6ff2874392..1dceaa2f9b 100644
--- a/tests/unit/tools/git/conftest.py
+++ b/tests/unit/tools/git/conftest.py
@@ -7,6 +7,7 @@
import pytest
import ai_company.tools.git_tools as git_tools_module
+from ai_company.tools._git_base import _GIT_DISCOVERY_VARS
from ai_company.tools.git_tools import (
GitBranchTool,
GitCloneTool,
@@ -24,8 +25,13 @@
"GIT_COMMITTER_EMAIL": "test@test.local",
"GIT_TERMINAL_PROMPT": "0",
"GIT_CONFIG_NOSYSTEM": "1",
+ "GIT_CONFIG_GLOBAL": os.devnull,
"GIT_PROTOCOL_FROM_USER": "0",
}
+# Strip git discovery vars so fixtures use cwd-based repo detection,
+# not stale env vars inherited from e.g. git push → pre-push hook.
+for _key in _GIT_DISCOVERY_VARS:
+ _GIT_ENV.pop(_key, None)
def _run_git(args: list[str], cwd: Path) -> None:
@@ -52,6 +58,18 @@ def _run_git_output(args: list[str], cwd: Path) -> str:
return result.stdout.strip()
+@pytest.fixture(autouse=True)
+def _isolate_git_env(monkeypatch: pytest.MonkeyPatch) -> None:
+ """Strip git discovery env vars so tools use cwd-based repo detection.
+
+ Without this, env vars like ``GIT_DIR`` inherited from
+ ``git push`` pre-push hooks cause tools to find the parent
+ repo instead of the test fixture's repo.
+ """
+ for key in _GIT_DISCOVERY_VARS:
+ monkeypatch.delenv(key, raising=False)
+
+
@pytest.fixture
def workspace(tmp_path: Path) -> Path:
"""Bare workspace directory (no git repo)."""