Skip to content
Merged
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
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@ PyPI version — not the changelog header.

### Added

- **`./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
current HEAD. Now `git clone && cd AIPass && ./setup.sh` takes you from cold
clone to a working first project in one command: on interactive terminals,
setup ends by launching the guided `aipass init run` in a sibling directory
(default `~/aipass-project`, prompt to choose — init refuses to run inside the
engine tree). New flags mirror `aipass install`'s handoff rules: `--no-init`
skips, `--with-init` forces even headless (init chains `--non-interactive`),
`--project <dir>` picks the target. CI and piped shells skip automatically
(`CI` env or no tty), so the windows/macos-test workflows that run bare
`bash setup.sh` are untouched. `install.py` now calls `setup.sh --no-init`
since install owns its own init handoff — no double-scaffold. Proven in
clean-room Docker, both runs exit 0: bare headless run skips init with a
hint; `--with-init` run chains init to `✓ Project initialized.` with the
project dir scaffolded. 39/39 install tests, seedgo 30/30. README Quick
Start updated to the one-command flow.

- **`aipass install` — one-command framework bootstrap.** The missing half of
`pip install aipass`: a single command resolves the install home (default
`~/AIPass`), git-clones the public repo, runs `setup.sh` (venv, editable
Expand Down
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,10 @@ 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 the `aipass` + `drone` commands on your PATH

cd ~ && mkdir my-project && cd my-project
aipass init run
cd AIPass && ./setup.sh # installs everything, then walks you into your first project
```

A guided setup 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.
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.

This is the base framework. It gives your agents the infrastructure to persist, communicate, and organize — everything else you build on top.

Expand Down Expand Up @@ -87,7 +84,9 @@ git clone https://github.com/AIOSAI/AIPass.git
cd AIPass && ./setup.sh # Creates venv, installs, puts `aipass` + `drone` on your PATH, bootstraps 17 agents
```

### 2. Your own project
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 <dir>` to pick where the project lands (CI and piped shells skip automatically).

### 2. Your own project (if you skipped the chain)

```bash
cd ~ && mkdir my-project && cd my-project
Expand Down
99 changes: 99 additions & 0 deletions setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
#
# AIPass setup script
# Creates a venv, installs the package in editable mode, and verifies CLI entry points.
# On interactive terminals it then chains into `aipass init run` to scaffold a first
# project (DPLAN-0234: one command does setup + init).
#
# Usage: ./setup.sh [--no-init] [--with-init] [--project <dir>]
# --no-init skip the first-project init chain
# --with-init force the init chain even headless (init runs --non-interactive)
# --project <dir> first-project directory (default: ~/aipass-project)
#

set -euo pipefail
Expand All @@ -27,6 +34,27 @@ case "${OSTYPE:-}" in
;;
esac

# --- Args ---
# Mirrors the `aipass install` handoff rules: --no-init wins, --with-init forces,
# default (auto) chains into init on interactive terminals only — CI/headless skip.
RUN_INIT="auto"
INIT_PROJECT=""
PREV_ARG=""
for arg in "$@"; do
if [ "$PREV_ARG" = "--project" ]; then
INIT_PROJECT="$arg"
PREV_ARG=""
continue
fi
case "$arg" in
--no-init) RUN_INIT="no" ;;
--with-init) RUN_INIT="yes" ;;
--project=*) INIT_PROJECT="${arg#--project=}" ;;
--project) PREV_ARG="--project" ;;
*) echo "WARN: unknown argument '$arg' (ignored)" ;;
esac
done

echo "=== AIPass Setup ==="
echo "Repo root: $SCRIPT_DIR"
echo ""
Expand Down Expand Up @@ -982,6 +1010,77 @@ if [ "$FAIL" -eq 0 ]; then
echo " Claude Code: hooks installed to ~/.claude/settings.json"
command -v codex &>/dev/null && echo " Codex CLI: hooks at .codex/hooks.json + config at ~/.codex/config.toml"
echo ""

# --- Chain into first-project init (DPLAN-0234: one command does setup + init) ---
# `aipass init` refuses to run inside the engine tree, so it always targets a
# sibling directory — never the repo itself. Decision mirrors install.py:
# --no-init wins, --with-init forces (headless chains --non-interactive),
# default = interactive terminals only (CI and piped shells skip).
AIPASS_BIN=""
if [ "$IS_WINDOWS" -eq 1 ] && [ -f "$SCRIPT_DIR/.venv/Scripts/aipass.exe" ]; then
AIPASS_BIN="$SCRIPT_DIR/.venv/Scripts/aipass.exe"
elif [ "$IS_WINDOWS" -eq 1 ] && [ -f "$SCRIPT_DIR/.venv/Scripts/aipass" ]; then
AIPASS_BIN="$SCRIPT_DIR/.venv/Scripts/aipass"
elif [ -f "$SCRIPT_DIR/.venv/bin/aipass" ]; then
AIPASS_BIN="$SCRIPT_DIR/.venv/bin/aipass"
elif command -v aipass &>/dev/null; then
AIPASS_BIN="$(command -v aipass)"
fi

LAUNCH_INIT=0
INIT_HEADLESS=0
if [ "$RUN_INIT" = "no" ]; then
echo "Skipping first-project init (--no-init). Run 'aipass init run' in a fresh directory when ready."
elif [ "$RUN_INIT" = "yes" ]; then
LAUNCH_INIT=1
if [ ! -t 0 ]; then
INIT_HEADLESS=1
fi
elif [ -t 0 ] && [ -z "${CI:-}" ]; then
LAUNCH_INIT=1
else
echo "Non-interactive shell — skipping first-project init. Run 'aipass init run' in a fresh directory when ready."
fi

if [ "$LAUNCH_INIT" -eq 1 ]; then
if [ -z "$AIPASS_BIN" ]; then
echo "WARN: aipass binary not found — open a new terminal and run 'aipass init run' in a fresh directory."
else
DEFAULT_PROJECT_DIR="$HOME/aipass-project"
PROJECT_DIR="$INIT_PROJECT"
if [ -z "$PROJECT_DIR" ] && [ "$INIT_HEADLESS" -eq 0 ] && [ -t 0 ]; then
echo "AIPass projects live in their own directory, never inside the engine repo."
read -r -p "Set up your first project now? [Y/n]: " INIT_REPLY
case "$INIT_REPLY" in
[nN]*)
PROJECT_DIR="-"
echo " Skipped. Run 'aipass init run' in a fresh directory when ready."
;;
*)
read -r -p " Project directory [$DEFAULT_PROJECT_DIR]: " INIT_INPUT
PROJECT_DIR="${INIT_INPUT:-$DEFAULT_PROJECT_DIR}"
;;
esac
elif [ -z "$PROJECT_DIR" ]; then
PROJECT_DIR="$DEFAULT_PROJECT_DIR"
fi

if [ "$PROJECT_DIR" != "-" ]; then
mkdir -p "$PROJECT_DIR"
INIT_CMD=("$AIPASS_BIN" init run)
if [ "$INIT_HEADLESS" -eq 1 ]; then
INIT_CMD+=(--non-interactive)
fi
echo ""
echo "Launching guided setup in $PROJECT_DIR ..."
if (cd "$PROJECT_DIR" && "${INIT_CMD[@]}"); then
echo "First project initialized at $PROJECT_DIR"
else
echo "Init didn't complete — run 'aipass init run' in $PROJECT_DIR when ready."
fi
fi
fi
fi
else
echo "=== Setup finished with errors ==="
echo "The venv was created and the package was installed, but one or more"
Expand Down
4 changes: 3 additions & 1 deletion src/aipass/aipass/apps/modules/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,9 @@ def _run_setup(home: Path, dry_run: bool) -> bool:
return False
console.print("[cyan]Building environment[/cyan] [dim](venv, dependencies, hook wiring)…[/dim]")
try:
proc = subprocess.run(["bash", str(setup)], cwd=str(home), timeout=_SETUP_TIMEOUT)
# --no-init: install owns the init handoff (_handoff_to_init) — without it,
# setup.sh's own init chain (DPLAN-0234) would scaffold the project twice.
proc = subprocess.run(["bash", str(setup), "--no-init"], cwd=str(home), timeout=_SETUP_TIMEOUT)
if proc.returncode == 0:
return True
logger.warning("[install] setup.sh exited %s", proc.returncode)
Expand Down
Loading