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
33 changes: 22 additions & 11 deletions .github/workflows/installer-smoke-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
ARCHDEV_INSTALL_DIR: ${{ runner.temp }}/home/.local/bin
run: |
mkdir -p "$HOME"
./install.sh
./install.sh --skip-login --skip-setup
test "$("$HOME/.local/bin/archdev" --version)" = 0.31.0
test -f "$HOME/${{ matrix.completion_file }}"
- name: Install a release without a completion command
Expand All @@ -66,22 +66,24 @@ jobs:
# still succeed and must not leave an empty completion file.
mkdir -p "$HOME"
scripts/create-unix-fixtures.sh --output-dir "$RUNNER_TEMP/release-nocompletion" --version 0.31.0 --no-completion
./install.sh --version 0.31.0 --base-url "file://$RUNNER_TEMP/release-nocompletion" --install-dir "$HOME/.local/bin"
./install.sh --version 0.31.0 --base-url "file://$RUNNER_TEMP/release-nocompletion" --install-dir "$HOME/.local/bin" --skip-login --skip-setup
test "$("$HOME/.local/bin/archdev" --version)" = 0.31.0
test ! -e "$HOME/${{ matrix.completion_file }}"
- name: Setup installs globally without a terminal
- name: Unattended setup requires an explicit scope
env:
ARCHDEV_RELEASE_BASE_URL: http://127.0.0.1:8123
ARCHDEV_VERSION: 0.31.0
HOME: ${{ runner.temp }}/home-setup
SHELL: /bin/${{ matrix.shell_name }}
ARCHDEV_INSTALL_DIR: ${{ runner.temp }}/home-setup/.local/bin
run: |
# The documented one-liner: the script arrives on stdin and there is
# no terminal, so the installer must not prompt. The checkout is a
# Git repository, and the unasked placement question means global.
# Neither a missing terminal nor --yes grants global placement.
mkdir -p "$HOME"
cat install.sh | bash >"$RUNNER_TEMP/unattended.log" 2>&1
if cat install.sh | bash >"$RUNNER_TEMP/missing-scope.log" 2>&1; then exit 1; fi
grep -F -- '--scope user or --scope repository' "$RUNNER_TEMP/missing-scope.log"
if cat install.sh | bash -s -- --yes >"$RUNNER_TEMP/yes.log" 2>&1; then exit 1; fi
grep -F -- '--scope user or --scope repository' "$RUNNER_TEMP/yes.log"
cat install.sh | bash -s -- --scope user >"$RUNNER_TEMP/unattended.log" 2>&1
grep -F 'Skill and hooks installed for this user' "$RUNNER_TEMP/unattended.log"
grep -F 'Set up this repository with ArchDev.' "$RUNNER_TEMP/unattended.log"
cat install.sh | bash -s -- --scope repository >"$RUNNER_TEMP/repository.log" 2>&1
Expand Down Expand Up @@ -191,6 +193,9 @@ jobs:
runs-on: windows-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Placement consent contracts
shell: pwsh
run: ./scripts/test-installer-scope.ps1
- name: Build fixture release
shell: pwsh
run: |
Expand Down Expand Up @@ -230,12 +235,18 @@ jobs:
if ($fallback -notmatch "Installing the TypeScript CLI \(archdev-old\) instead") { throw "Missing the fallback notice: $fallback" }
$oldVersion = & "$env:RUNNER_TEMP\bin-old\archdev.exe" --version
if ($oldVersion.Trim() -ne "0.30.0") { throw "archdev-old fallback version mismatch: $oldVersion" }
./install.ps1 -Version 0.31.0 -BaseUrl http://127.0.0.1:8123/rust -InstallDir "$env:RUNNER_TEMP\bin" -SkipPathUpdate
./install.ps1 -Version 0.31.0 -BaseUrl http://127.0.0.1:8123/rust -InstallDir "$env:RUNNER_TEMP\bin" -SkipPathUpdate -Scope user
$cliVersion = & "$env:RUNNER_TEMP\bin\archdev.exe" --version
if ($cliVersion.Trim() -ne "0.31.0") { throw "archdev.exe version mismatch" }
# No terminal in CI, so the installer must not prompt. The checkout
# is a Git repository, and the unasked placement question means global.
$unattended = (./install.ps1 -Version 0.31.0 -BaseUrl http://127.0.0.1:8123/rust -InstallDir "$env:RUNNER_TEMP\bin" -SkipPathUpdate *>&1 | Out-String)
# Neither a missing terminal nor -Yes grants global placement.
foreach ($yesFlag in @($false, $true)) {
$missingScope = $null
try {
./install.ps1 -Version 0.31.0 -BaseUrl http://127.0.0.1:8123/rust -InstallDir "$env:RUNNER_TEMP\bin" -SkipPathUpdate -Yes:$yesFlag *>&1 | Out-Null
} catch { $missingScope = "$_" }
if ($missingScope -notmatch "explicit -Scope user or -Scope repository") { throw "Missing scope did not fail: $missingScope" }
}
$unattended = (./install.ps1 -Version 0.31.0 -BaseUrl http://127.0.0.1:8123/rust -InstallDir "$env:RUNNER_TEMP\bin" -SkipPathUpdate -Scope user *>&1 | Out-String)
if ($unattended -notmatch "Skill and hooks installed for this user") { throw "Setup did not install globally: $unattended" }
if ($unattended -notmatch "Set up this repository with ArchDev\.") { throw "Missing the agent prompt: $unattended" }
$repository = (./install.ps1 -Version 0.31.0 -BaseUrl http://127.0.0.1:8123/rust -InstallDir "$env:RUNNER_TEMP\bin" -SkipPathUpdate -Scope repository *>&1 | Out-String)
Expand Down
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ The installer does three things in order:
1. Installs the `archdev` CLI and verifies its SHA-256 against the release.
2. Signs you in with GitHub in your browser.
3. Runs `archdev setup` to install the ArchDev skill and session hooks.
Inside a Git repository it asks where they go; anywhere else they are
installed globally:
It asks where they go before installing hooks, even outside a Git repository
or when Node.js is missing:
- **Globally:** personal setup across every repository you work in.
- **This repository:** shareable setup files for your teammates.
- **This repository:** shareable setup files for your teammates. Run from
inside a Git repository with Node.js on PATH. If either is missing, setup
explains the prerequisite and stops; it never switches to global hooks.

Session hooks report activity and findings to your organization's shared
stream, visible to its members; the installer says so before setup runs. Each
Expand All @@ -43,9 +45,10 @@ When it finishes, go to a repository you work in and tell your coding agent:
Set up this repository with ArchDev.
```

Without a terminal (CI, or a coding agent running the command) the installer
never prompts: once signed in (`ARCHDEV_TOKEN`), it installs globally. Pass
`--scope repository` to install into the current repository instead:
Without a terminal (CI, or a coding agent running the command), the installer
never prompts and requires an explicit scope before setup. `--yes` does not
choose a scope. Once signed in (`ARCHDEV_TOKEN`), pass `--scope user` for global
hooks or `--scope repository` for the current repository. For example:

```sh
curl -fsSL https://archdev.ai/install.sh | bash -s -- --scope repository
Expand All @@ -55,6 +58,7 @@ curl -fsSL https://archdev.ai/install.sh | bash -s -- --scope repository
$env:ARCHDEV_INSTALL_SCOPE = "repository"; irm https://archdev.ai/install.ps1 | iex
```

Use `--skip-setup` (PowerShell: `-SkipSetup`) to install the CLI without hooks.
`bash -s -- --help` lists every option. Homebrew users can run
`brew install ArchAstro/tools/archdev` and then `archdev setup`.

Expand Down
46 changes: 27 additions & 19 deletions install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@ param(
[switch]$SkipPathUpdate,
[switch]$SkipVerify,
# Where `archdev setup` installs the skill and hooks: user (globally) or
# repository. Asked inside a Git repository; otherwise user.
# repository. Always asked interactively; required without a terminal.
[string]$Scope = $env:ARCHDEV_INSTALL_SCOPE,
# Deprecated: reporting is always enabled. `disabled` declines setup.
[string]$Reporting = $env:ARCHDEV_INSTALL_REPORTING,
[switch]$SkipLogin,
[switch]$SkipSetup,
# Never prompt. Sign-in then needs ARCHDEV_TOKEN, and setup installs
# globally unless -Scope says otherwise.
# Never prompt. Sign-in then needs ARCHDEV_TOKEN, and setup requires -Scope.
[switch]$Yes
)

Expand All @@ -27,6 +26,30 @@ $Owner = "ArchAstro"
$Repo = "archdev"
$AgentPrompt = "Set up this repository with ArchDev."
function Test-Truthy([string]$Value) { return @("1", "true", "yes", "on") -contains "$Value".ToLowerInvariant() }

# Keep placement consent independent of repository/Node availability. Validate
# project prerequisites only after selection; never fall back to global hooks.
function Resolve-SetupScope([string]$RequestedScope, [bool]$CanPrompt, [string]$RepositoryRoot, [bool]$NodeAvailable) {
if ([string]::IsNullOrWhiteSpace($RequestedScope)) {
if (-not $CanPrompt) {
throw "Setup needs an explicit -Scope user or -Scope repository. Choose where to install hooks, or use -SkipSetup to install only the CLI."
}
Write-Host "Where should ArchDev install its skill and hooks?"
Write-Host " 1) Globally (every repository you work in)"
$Hint = if ($RepositoryRoot) { $RepositoryRoot } else { "run from a Git repository" }
Write-Host " 2) This repository ($Hint)"
do {
$Choice = (Read-Host "Choose [1]").Trim()
} while (@("", "1", "2") -notcontains $Choice)
$RequestedScope = if ($Choice -eq "2") { "repository" } else { "user" }
}
if (@("user", "repository") -notcontains $RequestedScope) { throw "Unknown -Scope: $RequestedScope (use user or repository)" }
if ($RequestedScope -eq "repository") {
if (-not $RepositoryRoot) { throw "-Scope repository needs a Git repository. Run the installer from inside the repository." }
if (-not $NodeAvailable) { throw "Repository hooks need Node.js on PATH. Install Node.js, or use -Scope user." }
}
return $RequestedScope
}
if (Test-Truthy $env:ARCHDEV_INSTALL_SKIP_LOGIN) { $SkipLogin = $true }
if (Test-Truthy $env:ARCHDEV_INSTALL_SKIP_SETUP) { $SkipSetup = $true }
if (Test-Truthy $env:ARCHDEV_INSTALL_NONINTERACTIVE) { $Yes = $true }
Expand Down Expand Up @@ -236,22 +259,7 @@ if ($SkipSetup) {
$RepoRoot = Get-RepositoryRoot
$HasNode = [bool](Get-Command node -ErrorAction SilentlyContinue)

# The only question: inside a repository, the skill and hooks can live there
# instead of globally. Everywhere else, and without a terminal, they are
# global. Repository hooks need Node.js, so without it the answer is global.
if ([string]::IsNullOrWhiteSpace($Scope)) {
$Scope = "user"
if ($Interactive -and $RepoRoot -and $HasNode) {
Write-Host "Where should ArchDev install its skill and hooks?"
Write-Host " 1) Globally (every repository you work in)"
Write-Host " 2) This repository ($RepoRoot)"
if ((Read-Host "Choose [1]").Trim() -eq "2") { $Scope = "repository" }
}
}
if ($Scope -eq "repository") {
if (-not $RepoRoot) { throw "-Scope repository needs a Git repository. Run the installer from inside the repository." }
if (-not $HasNode) { throw "Repository hooks need Node.js on PATH. Install Node.js, or use -Scope user." }
}
$Scope = Resolve-SetupScope $Scope $Interactive $RepoRoot $HasNode

Write-Host "Setup posts a one-time installation announcement, and session hooks report activity and findings to your organization's shared stream, visible to its members."
if ($Scope -eq "repository") { Push-Location $RepoRoot }
Expand Down
28 changes: 12 additions & 16 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
#
# curl -fsSL https://archdev.ai/install.sh | bash
#
# Installs the archdev CLI, signs you in, and runs `archdev setup`. Inside a
# Git repository it asks whether to install the skill and hooks there or
# globally; anywhere else it installs them globally. Pass flags after
# `bash -s --`:
# Installs the archdev CLI, signs you in, and runs `archdev setup` after an
# explicit global or repository-local placement choice. Unattended setup
# requires --scope. Pass flags after `bash -s --`:
#
# curl -fsSL https://archdev.ai/install.sh | bash -s -- --help
#
Expand Down Expand Up @@ -66,14 +65,14 @@ Install options:
Sign-in and setup options:
--scope <user|repository> Where `archdev setup` installs the skill and hooks:
globally for you (user) or in this repository.
Asked inside a Git repository; otherwise user.
Always asked interactively; required without a terminal.
--reporting <enabled|disabled>
Deprecated: reporting is always enabled.
`disabled` declines setup.
--skip-login Install only; do not sign in
--skip-setup Do not run `archdev setup`
-y, --yes Never prompt. Sign-in then needs ARCHDEV_TOKEN, and
setup installs globally unless --scope says otherwise.
setup requires --scope user or --scope repository.

Other:
--dry-run Print the resolved install plan without downloading
Expand Down Expand Up @@ -1005,17 +1004,14 @@ run_setup() {

repo_root="$(repository_root)"

# The only question: inside a repository, the skill and hooks can live there
# instead of globally. Everywhere else, and without a terminal, they are
# global. Repository hooks need Node.js, so without it the answer is global.
# Missing prerequisites must never turn project-local intent into global hooks.
# --yes suppresses prompts; it is not consent to a particular placement.
if [ -z "$scope" ]; then
scope="user"
if [ "$INTERACTIVE" = true ] && [ -n "$repo_root" ] && have node; then
choose "Where should ArchDev install its skill and hooks?" 0 \
"Globally|every repository you work in" \
"This repository|$(pretty_path "$repo_root")"
if [ "$CHOICE" -eq 1 ]; then scope="repository"; fi
fi
[ "$INTERACTIVE" = true ] || die "Setup needs an explicit --scope user or --scope repository." "Choose where to install hooks, or use --skip-setup to install only the CLI."
choose "Where should ArchDev install its skill and hooks?" 0 \
"Globally|every repository you work in" \
"This repository|${repo_root:-run from a Git repository}"
if [ "$CHOICE" -eq 1 ]; then scope="repository"; else scope="user"; fi
fi

if [ "$scope" = repository ]; then
Expand Down
Loading
Loading