diff --git a/CHANGELOG.md b/CHANGELOG.md
index 75b7312d..658d640e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,20 @@ PyPI version — not the changelog header.
### Added
+- **`./aipass` — repo-root cold-clone launcher (DPLAN-0234 Strand B).** The
+ branded entry point for the clone-first flow: `git clone`, `cd AIPass`,
+ `./aipass install` — three commands to a working AIPass. Stdlib-only bash
+ (zero deps, runs before anything is installed): pre-setup, only the `install`
+ verb exists and delegates to `setup.sh` with full flag pass-through
+ (`--no-init` / `--with-init` / `--project`); any other verb prints help
+ pointing at `./aipass install`. Post-setup the launcher turns transparent —
+ it execs the venv `aipass` binary for everything, so `./aipass doctor` just
+ works. Bare `aipass` always resolves to the PATH binary; the launcher only
+ ever runs as an explicit `./aipass`. 13 launcher tests (file properties,
+ pre-setup help, install delegation, post-setup forwarding), @aipass suite
+ 622 green, seedgo 100%. README Quick Start now leads with `./aipass install`.
+ (built by @aipass)
+
- **`./setup.sh` chains into `aipass init run` — clone-first one-command install
(DPLAN-0234 Strand A).** Distribution is git-clone, not pip: the framework
changes constantly, so a PyPI snapshot goes stale while a clone is always
diff --git a/README.md b/README.md
index 20794d93..e29f8694 100644
--- a/README.md
+++ b/README.md
@@ -32,10 +32,11 @@ AIPass is a CLI-native scaffold that adds **persistent memory, identity, and coo
```bash
git clone https://github.com/AIOSAI/AIPass.git
-cd AIPass && ./setup.sh # installs everything, then walks you into your first project
+cd AIPass
+./aipass install # installs everything, then walks you into your first project
```
-Setup builds the environment, puts `aipass` + `drone` on your PATH, then chains straight into a guided init that creates your project, your first agent, and opens a terminal where that agent is already running. Say "hi" — it knows who it is. Come back tomorrow — it remembers.
+One command does it all: builds the environment, puts `aipass` + `drone` on your PATH, then chains straight into a guided init that creates your project, your first agent, and opens a terminal where that agent is already running. Say "hi" — it knows who it is. Come back tomorrow — it remembers.
This is the base framework. It gives your agents the infrastructure to persist, communicate, and organize — everything else you build on top.
@@ -81,10 +82,11 @@ aipass init agent my-agent # Full agent: apps, mail, memory, identity
```bash
git clone https://github.com/AIOSAI/AIPass.git
-cd AIPass && ./setup.sh # Creates venv, installs, puts `aipass` + `drone` on your PATH, bootstraps 17 agents
+cd AIPass
+./aipass install # Creates venv, installs, puts `aipass` + `drone` on your PATH, bootstraps 17 agents
```
-On an interactive terminal, setup ends by chaining into `aipass init run` — one command takes you from clone to a working first project. Pass `--no-init` to skip the chain, `--project
` to pick where the project lands (CI and piped shells skip automatically).
+On an interactive terminal, install ends by chaining into `aipass init run` — one command takes you from clone to a working first project. Pass `--no-init` to skip the chain, `--project ` to pick where the project lands (CI and piped shells skip automatically). `./aipass` is a thin repo-root launcher over `setup.sh`; after setup it simply forwards to the installed `aipass` binary.
### 2. Your own project (if you skipped the chain)
diff --git a/aipass b/aipass
new file mode 100755
index 00000000..f495934a
--- /dev/null
+++ b/aipass
@@ -0,0 +1,52 @@
+#!/usr/bin/env bash
+# aipass — cold-clone entry point (pre-venv launcher)
+#
+# The first command after cloning:
+# git clone https://github.com/AIOSAI/AIPass.git
+# cd AIPass
+# ./aipass install
+#
+# Pre-setup: only `install` is available — delegates to setup.sh.
+# Post-setup: forwards everything to the venv-installed aipass binary.
+#
+# Stdlib-only, zero AIPass imports, zero third-party deps.
+
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+# --- Post-setup: forward to the real binary ---
+if [[ -x "$SCRIPT_DIR/.venv/bin/aipass" ]]; then
+ exec "$SCRIPT_DIR/.venv/bin/aipass" "$@"
+fi
+if [[ -x "$SCRIPT_DIR/.venv/Scripts/aipass.exe" ]]; then
+ exec "$SCRIPT_DIR/.venv/Scripts/aipass.exe" "$@"
+fi
+if [[ -x "$SCRIPT_DIR/.venv/Scripts/aipass" ]]; then
+ exec "$SCRIPT_DIR/.venv/Scripts/aipass" "$@"
+fi
+
+# --- Pre-setup: only 'install' works ---
+if [[ "${1:-}" == "install" ]]; then
+ shift
+ exec bash "$SCRIPT_DIR/setup.sh" "$@"
+fi
+
+# --- Help (no venv, no 'install' verb) ---
+cat <<'HELP'
+AIPass is not set up yet.
+
+ ./aipass install # set up AIPass (venv + deps + hooks)
+ ./aipass install --no-init # set up without the guided first-project setup
+ ./aipass install --project DIR # set the first-project directory
+
+After setup, all aipass commands become available:
+ ./aipass doctor # system health
+ ./aipass help # how-does-X-work Q&A
+ ./aipass init run # scaffold a new project
+
+Quick start:
+ git clone https://github.com/AIOSAI/AIPass.git
+ cd AIPass
+ ./aipass install
+HELP
diff --git a/src/aipass/aipass/.seedgo/bypass.json b/src/aipass/aipass/.seedgo/bypass.json
index f40d11af..26d8d28f 100644
--- a/src/aipass/aipass/.seedgo/bypass.json
+++ b/src/aipass/aipass/.seedgo/bypass.json
@@ -101,6 +101,11 @@
"standard": "encapsulation",
"reason": "Unit tests must import the install module directly to test home resolution, clone, and setup orchestration in isolation. Entry-point imports would defeat the purpose of unit testing."
},
+ {
+ "file": "tests/test_launcher.py",
+ "standard": "architecture",
+ "reason": "Test file lives in tests/ by convention — not in apps/. Standard 3-layer structure applies to production code only."
+ },
{
"file": "tests/test_ping_sweep.py",
"standard": "architecture",
diff --git a/src/aipass/aipass/tests/test_launcher.py b/src/aipass/aipass/tests/test_launcher.py
new file mode 100644
index 00000000..570740bd
--- /dev/null
+++ b/src/aipass/aipass/tests/test_launcher.py
@@ -0,0 +1,203 @@
+# =================== AIPass ====================
+# Name: test_launcher.py
+# Description: Tests for the repo-root ./aipass cold-clone launcher
+# Version: 1.0.0
+# Created: 2026-07-05
+# Modified: 2026-07-05
+# =============================================
+
+"""Tests for the repo-root ./aipass cold-clone launcher (bash script)."""
+
+from __future__ import annotations
+
+import os
+import shutil
+import subprocess
+import sys
+from pathlib import Path
+
+import pytest
+
+pytestmark = pytest.mark.skipif(sys.platform == "win32", reason="bash launcher requires Unix")
+
+REPO_ROOT = Path(__file__).resolve().parents[4]
+LAUNCHER = REPO_ROOT / "aipass"
+
+_EXEC_BITS = 0o111
+
+
+def _make_executable(path: Path) -> None:
+ """Set executable bits on a file."""
+ path.chmod(path.stat().st_mode | _EXEC_BITS)
+
+
+def _isolated_launcher(tmp_path: Path) -> Path:
+ """Copy the launcher to a temp dir (isolated from real .venv)."""
+ dest = tmp_path / "aipass"
+ shutil.copy2(LAUNCHER, dest)
+ _make_executable(dest)
+ return dest
+
+
+def _fake_venv_binary(tmp_path: Path, body: str) -> Path:
+ """Create a fake .venv/bin/aipass that runs the given bash body."""
+ venv_bin = tmp_path / ".venv" / "bin"
+ venv_bin.mkdir(parents=True)
+ fake = venv_bin / "aipass"
+ fake.write_text(f"#!/usr/bin/env bash\n{body}\n")
+ _make_executable(fake)
+ return fake
+
+
+class TestLauncherProperties:
+ """Verify the launcher file's basic properties."""
+
+ def test_exists(self):
+ """Launcher file exists at repo root."""
+ assert LAUNCHER.is_file()
+
+ def test_executable(self):
+ """Launcher has executable permission."""
+ assert os.access(LAUNCHER, os.X_OK)
+
+ def test_shebang(self):
+ """Launcher starts with bash shebang."""
+ first_line = LAUNCHER.read_text().splitlines()[0]
+ assert first_line == "#!/usr/bin/env bash"
+
+ def test_no_python_imports(self):
+ """Launcher contains no Python imports (stdlib-only bash)."""
+ content = LAUNCHER.read_text()
+ assert "import " not in content
+ assert "from " not in content
+
+
+class TestPreSetupHelp:
+ """Pre-setup (no venv): bare ./aipass prints help text."""
+
+ def test_no_args_shows_help(self, tmp_path):
+ """No args and no venv prints setup instructions."""
+ launcher = _isolated_launcher(tmp_path)
+ result = subprocess.run(
+ ["bash", str(launcher)],
+ capture_output=True,
+ text=True,
+ timeout=5,
+ )
+ assert "not set up yet" in result.stdout
+ assert "./aipass install" in result.stdout
+
+ def test_unknown_verb_shows_help(self, tmp_path):
+ """Unknown verb pre-setup prints help, not an error."""
+ launcher = _isolated_launcher(tmp_path)
+ result = subprocess.run(
+ ["bash", str(launcher), "doctor"],
+ capture_output=True,
+ text=True,
+ timeout=5,
+ )
+ assert "not set up yet" in result.stdout
+
+ def test_help_mentions_quick_start(self, tmp_path):
+ """Help text includes the Quick start snippet."""
+ launcher = _isolated_launcher(tmp_path)
+ result = subprocess.run(
+ ["bash", str(launcher)],
+ capture_output=True,
+ text=True,
+ timeout=5,
+ )
+ assert "Quick start" in result.stdout
+ assert "git clone" in result.stdout
+
+
+class TestPreSetupInstall:
+ """Pre-setup: ./aipass install delegates to setup.sh."""
+
+ def test_install_calls_setup_sh(self, tmp_path):
+ """./aipass install execs setup.sh in the same directory."""
+ launcher = _isolated_launcher(tmp_path)
+ setup = tmp_path / "setup.sh"
+ setup.write_text('#!/usr/bin/env bash\necho "SETUP_CALLED $@"\n')
+ _make_executable(setup)
+
+ result = subprocess.run(
+ ["bash", str(launcher), "install"],
+ capture_output=True,
+ text=True,
+ timeout=5,
+ )
+ assert "SETUP_CALLED" in result.stdout
+
+ def test_install_passes_flags(self, tmp_path):
+ """Flags after 'install' pass through to setup.sh."""
+ launcher = _isolated_launcher(tmp_path)
+ setup = tmp_path / "setup.sh"
+ setup.write_text('#!/usr/bin/env bash\necho "FLAGS:$@"\n')
+ _make_executable(setup)
+
+ result = subprocess.run(
+ ["bash", str(launcher), "install", "--no-init"],
+ capture_output=True,
+ text=True,
+ timeout=5,
+ )
+ assert "--no-init" in result.stdout
+
+ def test_install_passes_project_flag(self, tmp_path):
+ """--project and its value pass through to setup.sh."""
+ launcher = _isolated_launcher(tmp_path)
+ setup = tmp_path / "setup.sh"
+ setup.write_text('#!/usr/bin/env bash\necho "FLAGS:$@"\n')
+ _make_executable(setup)
+
+ result = subprocess.run(
+ ["bash", str(launcher), "install", "--project", str(tmp_path / "proj")],
+ capture_output=True,
+ text=True,
+ timeout=5,
+ )
+ assert "--project" in result.stdout
+
+
+class TestPostSetupForwarding:
+ """Post-setup: forwards to the venv binary."""
+
+ def test_forwards_to_venv_binary(self, tmp_path):
+ """With venv binary present, verbs forward to it."""
+ launcher = _isolated_launcher(tmp_path)
+ _fake_venv_binary(tmp_path, 'echo "VENV_AIPASS $@"')
+
+ result = subprocess.run(
+ ["bash", str(launcher), "doctor"],
+ capture_output=True,
+ text=True,
+ timeout=5,
+ )
+ assert "VENV_AIPASS doctor" in result.stdout
+
+ def test_forwards_install_post_setup(self, tmp_path):
+ """Post-setup ./aipass install goes to venv binary, not setup.sh."""
+ launcher = _isolated_launcher(tmp_path)
+ _fake_venv_binary(tmp_path, 'echo "VENV_AIPASS $@"')
+
+ result = subprocess.run(
+ ["bash", str(launcher), "install"],
+ capture_output=True,
+ text=True,
+ timeout=5,
+ )
+ assert "VENV_AIPASS install" in result.stdout
+
+ def test_no_args_forwards_post_setup(self, tmp_path):
+ """Post-setup bare ./aipass forwards to venv binary."""
+ launcher = _isolated_launcher(tmp_path)
+ _fake_venv_binary(tmp_path, 'echo "VENV_AIPASS_NOARGS"')
+
+ result = subprocess.run(
+ ["bash", str(launcher)],
+ capture_output=True,
+ text=True,
+ timeout=5,
+ )
+ assert "VENV_AIPASS_NOARGS" in result.stdout