@darkrei08/setup-ai 3.2.0 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/setup-ai.mjs CHANGED
@@ -44,6 +44,7 @@ const MODULES = [
44
44
  { name: "antigravity", core: true, desc: "Google Antigravity CLI (agy)" },
45
45
  { name: "opencode", core: true, desc: "opencode agent CLI (opencode-ai)" },
46
46
  { name: "cockpit", core: false, desc: "cockpit-tools desktop GUI app (optional, CC BY-NC-SA)" },
47
+ { name: "rotator", core: false, desc: "tuxevil-rotator multi-account Gemini/Antigravity gateway (optional, opt-in)" },
47
48
  ];
48
49
 
49
50
  // dotenv is Linux-only; drop it from the Windows menu.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@darkrei08/setup-ai",
3
- "version": "3.2.0",
3
+ "version": "3.3.1",
4
4
  "description": "Cross-OS installer for an AI coding toolchain (pi, codex, antigravity, opencode, herdr, gentle-ai/gga, Engineering Excellence) with an interactive module menu and per-agent MCP registration.",
5
5
  "type": "module",
6
6
  "bin": {
package/setup-ai.ps1 CHANGED
@@ -2,7 +2,7 @@
2
2
  <#
3
3
  ==============================================================================
4
4
  AI Dev Suite — Engineering Excellence Edition (Windows)
5
- Version: 3.2.0
5
+ Version: 3.3.1
6
6
 
7
7
  Windows-native installer, sibling of setup-ai.sh. Uses each tool's official
8
8
  Windows method: winget for language runtimes, the vendor install.ps1 scripts
@@ -50,7 +50,7 @@ try {
50
50
  Write-Warning "Could not set UTF-8 console encoding: $($_.Exception.Message)"
51
51
  }
52
52
 
53
- $ScriptVersion = "3.2.0"
53
+ $ScriptVersion = "3.3.1"
54
54
  $ScriptPath = $PSCommandPath
55
55
  $ScriptDir = Split-Path -Parent $ScriptPath
56
56
  $LogDir = Join-Path $ScriptDir "logs"
@@ -94,6 +94,19 @@ $SkillAgentRoots = @{
94
94
  codex = Join-Path $HOME ".codex\skills"
95
95
  opencode = Join-Path $HOME ".config\opencode\skills"
96
96
  }
97
+ # Candidate skill roots per agent: verification passes if SKILL.md exists in any
98
+ # of them. Every agent keeps its single existing root; Codex also accepts
99
+ # ~/.agents/skills because upstream `skills add --global` writes Codex skills
100
+ # there instead of ~/.codex/skills.
101
+ $SkillAgentCandidateRoots = @{
102
+ pi = @($SkillAgentRoots['pi'])
103
+ 'claude-code' = @($SkillAgentRoots['claude-code'])
104
+ 'gemini-cli' = @($SkillAgentRoots['gemini-cli'])
105
+ cursor = @($SkillAgentRoots['cursor'])
106
+ antigravity = @($SkillAgentRoots['antigravity'])
107
+ codex = @($SkillAgentRoots['codex'], (Join-Path $HOME ".agents\skills"))
108
+ opencode = @($SkillAgentRoots['opencode'])
109
+ }
97
110
 
98
111
  # ------------------------------------------------------------------------------
99
112
  # Logging (human + JSONL)
@@ -135,18 +148,32 @@ function Update-SessionPath {
135
148
 
136
149
  # Run a step; $Optional means failures are logged as WARN and swallowed.
137
150
  function Invoke-Step {
138
- param([string]$Phase, [scriptblock]$Action, [switch]$Optional)
151
+ param(
152
+ [string]$Phase,
153
+ [scriptblock]$Action,
154
+ [switch]$Optional,
155
+ [int[]]$ExpectedExitCodes = @()
156
+ )
139
157
  Write-Log INFO $Phase "step_start" "Running step"
140
158
  try {
141
159
  $global:LASTEXITCODE = 0
142
160
  & $Action 2>&1 | Tee-Object -FilePath $HumanLog -Append | Out-Host
143
161
  $nativeExitCode = $global:LASTEXITCODE
144
162
  if ($nativeExitCode -ne 0) {
163
+ if ($ExpectedExitCodes -contains $nativeExitCode) {
164
+ Write-Log INFO $Phase "step_expected" "Step returned expected exit code $nativeExitCode" $nativeExitCode
165
+ return $null
166
+ }
145
167
  throw "Native command exited with code $nativeExitCode"
146
168
  }
147
169
  Write-Log INFO $Phase "step_ok" "Step completed"
148
170
  return $true
149
171
  } catch {
172
+ $nativeExitCode = $global:LASTEXITCODE
173
+ if ($ExpectedExitCodes -contains $nativeExitCode) {
174
+ Write-Log INFO $Phase "step_expected" "Step returned expected exit code $nativeExitCode" $nativeExitCode
175
+ return $null
176
+ }
150
177
  if ($Optional) {
151
178
  Write-Log WARN $Phase "step_failed_optional" "$($_.Exception.Message); continuing" 1
152
179
  return $false
@@ -159,13 +186,18 @@ function Invoke-Step {
159
186
  function Test-WingetInstalled {
160
187
  param([string]$Id, [string]$Phase)
161
188
  $probe = Join-Path ([IO.Path]::GetTempPath()) ("setup-ai-winget-" + [guid]::NewGuid().ToString("N") + ".log")
189
+ $wingetNoApplicationsFoundExitCode = -1978335212 # 0x8A150014: package is not installed.
162
190
  try {
163
191
  # Keep the query inside Invoke-Step so its exit status and output are
164
192
  # logged; a failed probe is treated as "not installed" and followed
165
193
  # by the mandatory install step.
166
- $listed = Invoke-Step -Phase $Phase -Optional -Action {
194
+ $listed = Invoke-Step -Phase $Phase -Optional -ExpectedExitCodes $wingetNoApplicationsFoundExitCode -Action {
167
195
  winget list --id $Id -e | Out-File -LiteralPath $probe -Encoding utf8
168
196
  }
197
+ if ($null -eq $listed) {
198
+ Write-Log INFO $Phase "not_installed" "$Id is not installed; will install"
199
+ return $false
200
+ }
169
201
  return ($listed -and [bool](Select-String -Path $probe -SimpleMatch $Id -Quiet))
170
202
  } finally {
171
203
  if (Test-Path $probe) {
@@ -263,9 +295,9 @@ function Get-TargetSkillAgents {
263
295
  function Assert-SkillInstalledForAgents {
264
296
  param([string]$Phase, [string]$Skill, [string[]]$Agents)
265
297
  foreach ($agent in $Agents) {
266
- $skillPath = Join-Path (Join-Path $SkillAgentRoots[$agent] $Skill) "SKILL.md"
267
- if (-not (Test-Path $skillPath)) {
268
- throw "$Skill SKILL.md missing for targeted agent '$agent' ($skillPath)"
298
+ $checked = @($SkillAgentCandidateRoots[$agent] | ForEach-Object { Join-Path (Join-Path $_ $Skill) "SKILL.md" })
299
+ if (-not ($checked | Where-Object { Test-Path $_ })) {
300
+ throw "$Skill SKILL.md missing for targeted agent '$agent' (checked: $($checked -join ', '))"
269
301
  }
270
302
  }
271
303
  Write-Log INFO $Phase "skill_verified" "$Skill verified for every targeted agent" 0 "agents=$($Agents -join ',')"
@@ -275,7 +307,7 @@ function Assert-SkillInstalledForAgents {
275
307
  # Module registry
276
308
  # ==============================================================================
277
309
 
278
- $ModuleOrder = @('base','node','bun','pi','go','dotenv','ee','skills','pi-workflows','herdr','gentle-ai','codex','antigravity','opencode','cockpit')
310
+ $ModuleOrder = @('base','node','bun','pi','go','dotenv','ee','skills','pi-workflows','herdr','gentle-ai','codex','antigravity','opencode','cockpit','rotator')
279
311
 
280
312
  $ModuleDesc = [ordered]@{
281
313
  'base' = 'System packages (build tools, git, gh, python, neovim, jq, imagemagick, go)'
@@ -293,8 +325,9 @@ $ModuleDesc = [ordered]@{
293
325
  'antigravity' = 'Google Antigravity CLI (agy)'
294
326
  'opencode' = 'opencode agent CLI (opencode-ai)'
295
327
  'cockpit' = 'cockpit-tools desktop GUI app (optional, CC BY-NC-SA)'
328
+ 'rotator' = 'tuxevil-rotator multi-account Gemini/Antigravity gateway (optional, opt-in)'
296
329
  }
297
- $ModuleOptional = @{ 'cockpit' = $true }
330
+ $ModuleOptional = @{ 'cockpit' = $true; 'rotator' = $true }
298
331
 
299
332
  # ==============================================================================
300
333
  # Modules
@@ -649,6 +682,78 @@ function Mod-Cockpit {
649
682
  }
650
683
  }
651
684
 
685
+ function Mod-Rotator {
686
+ Write-Log INFO "rotator" "start" "tuxevil-rotator gateway"
687
+ # Detect a desktop cockpit-tools data dir by marker file only; never read tokens.
688
+ $candidates = @(
689
+ (Join-Path $HOME ".antigravity_cockpit")
690
+ (Join-Path $HOME ".local\share\cockpit-tools")
691
+ (Join-Path $HOME ".config\cockpit-tools")
692
+ (Join-Path $HOME ".wizard-ai\cockpit-tools")
693
+ (Join-Path $HOME "Library\Application Support\cockpit-tools")
694
+ )
695
+ if ($env:APPDATA) { $candidates += Join-Path $env:APPDATA "cockpit-tools" }
696
+ if ($env:LOCALAPPDATA) { $candidates += Join-Path $env:LOCALAPPDATA "cockpit-tools" }
697
+
698
+ $cockpitDir = ""
699
+ foreach ($dir in $candidates) {
700
+ if ((Test-Path -LiteralPath (Join-Path $dir "accounts.json") -PathType Leaf) -or
701
+ (Test-Path -LiteralPath (Join-Path $dir "account-token.key") -PathType Leaf)) {
702
+ $cockpitDir = $dir
703
+ break
704
+ }
705
+ }
706
+ if ($cockpitDir) {
707
+ Write-Log INFO "rotator" "cockpit_detected" "cockpit-tools data directory detected" 0 "dir=$cockpitDir"
708
+ } else {
709
+ Write-Log INFO "rotator" "cockpit_absent" "No cockpit-tools data directory detected; the rotator can still use its own accounts" 0
710
+ }
711
+
712
+ # Non-fatal health probe.
713
+ $gw = "http://localhost:51200/v1/models"
714
+ try {
715
+ $response = Invoke-WebRequest -Uri $gw -Headers @{ Authorization = "Bearer tuxevil" } -TimeoutSec 5
716
+ $body = $response.Content
717
+ $count = if ($body) { ([regex]::Matches($body, '"id"')).Count } else { 0 }
718
+ Write-Log INFO "rotator" "gateway_up" "tuxevil-rotator gateway is reachable" 0 "url=$gw;models=$count"
719
+ } catch {
720
+ Write-Log INFO "rotator" "gateway_down" "tuxevil-rotator gateway not reachable; start it with 'tuxevil-rotator start'" 0 "url=$gw"
721
+ }
722
+
723
+ # Install the CLI idempotently. Never runs login/start or writes secrets.
724
+ if (Test-Cmd tuxevil-rotator) {
725
+ Write-Log INFO "rotator" "already_present" "tuxevil-rotator already installed"
726
+ } else {
727
+ if (-not (Test-Cmd npm)) { throw "npm not found; tuxevil-rotator cannot be installed" }
728
+ Invoke-Step -Phase "rotator" -Action { npm install -g tuxevil-rotator }
729
+ if (-not (Test-Cmd tuxevil-rotator)) {
730
+ Write-Log ERROR "rotator" "install_missing" "tuxevil-rotator not found on PATH after npm install"
731
+ throw "tuxevil-rotator not found on PATH after npm install"
732
+ }
733
+ }
734
+ if (Test-Cmd pi) {
735
+ Invoke-Step -Phase "rotator" -Action { pi install "github:darkrei08/pi-cockpit-tools-sync" }
736
+ $piSettings = Join-Path $HOME ".pi\agent\settings.json"
737
+ if (-not (Test-Path -LiteralPath $piSettings -PathType Leaf) -or
738
+ -not (Select-String -LiteralPath $piSettings -SimpleMatch "github:darkrei08/pi-cockpit-tools-sync" -Quiet)) {
739
+ Write-Log ERROR "rotator" "pi_extension_missing" "Pi did not register cockpit sync extension" 1 "expected=$piSettings"
740
+ throw "Pi did not register cockpit sync extension"
741
+ }
742
+ Write-Log INFO "rotator" "pi_extension_verified" "Cockpit sync extension registered in Pi" 0 "path=$piSettings"
743
+ } else {
744
+ Write-Log INFO "rotator" "pi_extension_skipped" "pi not found; cockpit sync extension was not installed"
745
+ }
746
+ @"
747
+ tuxevil-rotator installed. To use the multi-account Gemini/Antigravity gateway:
748
+ tuxevil-rotator login # add a Google Antigravity account (repeat to add more)
749
+ tuxevil-rotator import # or bulk-import accounts from a cockpit-tools JSON
750
+ tuxevil-rotator start # start the rotating proxy on http://localhost:51200
751
+ Pi reaches it through the 'tuxevil-rotator' provider configured in your dotenv.
752
+ The cockpit sync extension provides /cockpit-sync, /cockpit-provision, and /cockpit-proxy.
753
+ Login/start are never run by setup-ai and no tokens are read or stored.
754
+ "@ | Tee-Object -FilePath $HumanLog -Append | Out-Host
755
+ }
756
+
652
757
  $ModuleFn = @{
653
758
  'base' = ${function:Mod-Base}; 'node' = ${function:Mod-Node}; 'bun' = ${function:Mod-Bun}
654
759
  'pi' = ${function:Mod-Pi}; 'go' = ${function:Mod-Go}; 'dotenv' = ${function:Mod-Dotenv}; 'ee' = ${function:Mod-Ee}
@@ -656,7 +761,7 @@ $ModuleFn = @{
656
761
  'pi-workflows' = ${function:Mod-PiWorkflows}; 'herdr' = ${function:Mod-Herdr}
657
762
  'gentle-ai' = ${function:Mod-GentleAi}
658
763
  'codex' = ${function:Mod-Codex}; 'antigravity' = ${function:Mod-Antigravity}
659
- 'opencode' = ${function:Mod-Opencode}; 'cockpit' = ${function:Mod-Cockpit}
764
+ 'opencode' = ${function:Mod-Opencode}; 'cockpit' = ${function:Mod-Cockpit}; 'rotator' = ${function:Mod-Rotator}
660
765
  }
661
766
 
662
767
  # ==============================================================================
package/setup-ai.sh CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # ==============================================================================
4
4
  # AI Dev Suite — Engineering Excellence Edition
5
- # Version: 3.2.0
5
+ # Version: 3.3.1
6
6
  #
7
7
  # Cross-platform (macOS + all major Linux distros) installer for an AI coding
8
8
  # toolchain. Windows is handled by the sibling setup-ai.ps1; the Node launcher
@@ -26,7 +26,7 @@
26
26
  set -Eeuo pipefail
27
27
  IFS=$'\n\t'
28
28
 
29
- SCRIPT_VERSION="3.2.0"
29
+ SCRIPT_VERSION="3.3.1"
30
30
 
31
31
  SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
32
32
  LOG_DIR="${SCRIPT_DIR}/logs"
@@ -305,7 +305,7 @@ EOF
305
305
  # via --all or an explicit --only.
306
306
  # ==============================================================================
307
307
 
308
- MODULE_ORDER=(base node bun pi go dotenv ee skills pi-workflows herdr gentle-ai codex antigravity opencode cockpit)
308
+ MODULE_ORDER=(base node bun pi go dotenv ee skills pi-workflows herdr gentle-ai codex antigravity opencode cockpit rotator)
309
309
 
310
310
  module_desc() {
311
311
  case "$1" in
@@ -324,12 +324,13 @@ module_desc() {
324
324
  antigravity) printf '%s\n' "Google Antigravity CLI (agy)" ;;
325
325
  opencode) printf '%s\n' "opencode agent CLI (opencode-ai)" ;;
326
326
  cockpit) printf '%s\n' "cockpit-tools desktop GUI app (optional, CC BY-NC-SA)" ;;
327
+ rotator) printf '%s\n' "tuxevil-rotator multi-account Gemini/Antigravity gateway (optional, opt-in)" ;;
327
328
  *) return 1 ;;
328
329
  esac
329
330
  }
330
331
 
331
332
  module_is_optional() {
332
- [[ "$1" == "cockpit" ]]
333
+ [[ "$1" == "cockpit" || "$1" == "rotator" ]]
333
334
  }
334
335
 
335
336
  # ------------------------------------------------------------------------------
@@ -581,7 +582,7 @@ mod_dotenv() {
581
582
 
582
583
  # Idempotent upstream-quirk patches (no-ops once upstream merges the fixes).
583
584
  if grep_probe "dotenv" "${TMP_DIR}/monokai_hits" \
584
- -RIl --exclude-dir=.git "gthelding/monokai-pro.nvim" "${DOTENV_DIR}"; then
585
+ -rIl --exclude-dir=.git "gthelding/monokai-pro.nvim" "${DOTENV_DIR}"; then
585
586
  while IFS= read -r file; do
586
587
  run_cmd "dotenv" sed -i 's|gthelding/monokai-pro.nvim|loctvl842/monokai-pro.nvim|g' "${file}"
587
588
  log_event "INFO" "dotenv" "reference_patched" "Updated stale monokai-pro reference" 0 "file=${file}"
@@ -589,7 +590,7 @@ mod_dotenv() {
589
590
  fi
590
591
 
591
592
  if grep_probe "dotenv" "${TMP_DIR}/sudo_npm_hits" \
592
- -RIl --exclude-dir=.git 'sudo npm install -g --prefix /usr/local bun' "${DOTENV_DIR}"; then
593
+ -rIl --exclude-dir=.git 'sudo npm install -g --prefix /usr/local bun' "${DOTENV_DIR}"; then
593
594
  while IFS= read -r file; do
594
595
  run_cmd "dotenv" sed -i 's|sudo npm install -g --prefix /usr/local bun|sudo "$(command -v npm)" install -g --prefix /usr/local bun|g' "${file}"
595
596
  log_event "INFO" "dotenv" "reference_patched" "Patched sudo npm call to absolute path" 0 "file=${file}"
@@ -597,7 +598,7 @@ mod_dotenv() {
597
598
  fi
598
599
 
599
600
  if grep_probe "dotenv" "${TMP_DIR}/treesitter_hits" \
600
- -RIl --exclude-dir=.git -e 'npm install -g --prefix "\$HOME/.local" tree-sitter-cli' "${DOTENV_DIR}"; then
601
+ -rIl --exclude-dir=.git -e 'npm install -g --prefix "\$HOME/.local" tree-sitter-cli' "${DOTENV_DIR}"; then
601
602
  while IFS= read -r file; do
602
603
  local marker_rc=0
603
604
  grep -q 'AI_DEV_TS_CLI_PATCH' "${file}" 2>>"${HUMAN_LOG}" || marker_rc=$?
@@ -673,15 +674,34 @@ agent_skill_root() {
673
674
  esac
674
675
  }
675
676
 
677
+ # Candidate skill roots per agent (one per line): verification passes if
678
+ # SKILL.md exists under any of them. Every agent keeps its single existing root;
679
+ # Codex also accepts ${HOME}/.agents/skills because upstream `skills add
680
+ # --global` writes Codex skills there instead of ${HOME}/.codex/skills.
681
+ agent_skill_roots() {
682
+ agent_skill_root "$1" || return 1
683
+ if [[ "$1" == codex ]]; then
684
+ printf '%s\n' "${HOME}/.agents/skills"
685
+ fi
686
+ }
687
+
676
688
  verify_skill_for_agents() {
677
689
  local phase="$1" skill="$2"; shift 2
678
- local agent root
690
+ local agent root found checked
679
691
  for agent in "$@"; do
680
- root="$(agent_skill_root "${agent}")"
681
- if [[ ! -f "${root}/${skill}/SKILL.md" ]]; then
692
+ found=0
693
+ checked=""
694
+ while IFS= read -r root; do
695
+ checked="${checked:+${checked}, }${root}/${skill}/SKILL.md"
696
+ if [[ -f "${root}/${skill}/SKILL.md" ]]; then
697
+ found=1
698
+ break
699
+ fi
700
+ done < <(agent_skill_roots "${agent}")
701
+ if (( found == 0 )); then
682
702
  log_event "ERROR" "${phase}" "skill_missing" \
683
703
  "Skill SKILL.md missing for targeted agent" 1 \
684
- "agent=${agent};skill=${skill};expected=${root}/${skill}/SKILL.md"
704
+ "agent=${agent};skill=${skill};checked=${checked}"
685
705
  return 1
686
706
  fi
687
707
  done
@@ -1109,6 +1129,70 @@ mod_cockpit() {
1109
1129
  esac
1110
1130
  }
1111
1131
 
1132
+ # --- rotator (opt-in: tuxevil-rotator multi-account gateway) -----------------
1133
+ mod_rotator() {
1134
+ section "tuxevil-rotator gateway"
1135
+ # Detect a desktop cockpit-tools data dir by marker file only; never read tokens.
1136
+ local -a candidates=(
1137
+ "${HOME}/.antigravity_cockpit"
1138
+ "${HOME}/.local/share/cockpit-tools"
1139
+ "${HOME}/.config/cockpit-tools"
1140
+ "${HOME}/.wizard-ai/cockpit-tools"
1141
+ "${HOME}/Library/Application Support/cockpit-tools"
1142
+ )
1143
+ # Windows-only locations, appended only when set (mirrors the ps1 behavior).
1144
+ [[ -n "${APPDATA:-}" ]] && candidates+=("${APPDATA}/cockpit-tools")
1145
+ [[ -n "${LOCALAPPDATA:-}" ]] && candidates+=("${LOCALAPPDATA}/cockpit-tools")
1146
+ local dir cockpit_dir=""
1147
+ for dir in "${candidates[@]}"; do
1148
+ if [[ -f "${dir}/accounts.json" || -f "${dir}/account-token.key" ]]; then
1149
+ cockpit_dir="${dir}"; break
1150
+ fi
1151
+ done
1152
+ if [[ -n "${cockpit_dir}" ]]; then
1153
+ log_event "INFO" "rotator" "cockpit_detected" "cockpit-tools data directory detected" 0 "dir=${cockpit_dir}"
1154
+ else
1155
+ log_event "INFO" "rotator" "cockpit_absent" "No cockpit-tools data directory detected; the rotator can still use its own accounts" 0
1156
+ fi
1157
+ # Non-fatal health probe.
1158
+ local gw="http://localhost:51200/v1/models" body count
1159
+ if body="$(curl -fsS -m 5 -H 'Authorization: Bearer tuxevil' "${gw}" 2>/dev/null)"; then
1160
+ # curl -fsS already proved reachability; the count is informational only
1161
+ # (awk always exits 0, so no operational failure is masked here).
1162
+ count="$(printf '%s' "${body}" | awk '{c+=gsub(/"id"/,"&")} END{print c+0}')"
1163
+ log_event "INFO" "rotator" "gateway_up" "tuxevil-rotator gateway is reachable" 0 "url=${gw};models=${count}"
1164
+ else
1165
+ log_event "INFO" "rotator" "gateway_down" "tuxevil-rotator gateway not reachable; start it with 'tuxevil-rotator start'" 0 "url=${gw}"
1166
+ fi
1167
+ # Install the CLI idempotently. Never runs login/start or writes secrets.
1168
+ if command -v tuxevil-rotator >/dev/null 2>&1; then
1169
+ log_event "INFO" "rotator" "already_present" "tuxevil-rotator already installed" 0
1170
+ else
1171
+ run_cmd "rotator" npm install --global tuxevil-rotator
1172
+ require_command tuxevil-rotator
1173
+ fi
1174
+ if command -v pi >/dev/null 2>&1; then
1175
+ run_cmd "rotator" pi install "github:darkrei08/pi-cockpit-tools-sync"
1176
+ local pi_settings="${PI_AGENT_DIR}/settings.json"
1177
+ if [[ ! -f "${pi_settings}" ]] || ! grep -Fq 'github:darkrei08/pi-cockpit-tools-sync' "${pi_settings}"; then
1178
+ log_event "ERROR" "rotator" "pi_extension_missing" "Pi did not register cockpit sync extension" 1 "expected=${pi_settings}"
1179
+ return 1
1180
+ fi
1181
+ log_event "INFO" "rotator" "pi_extension_verified" "Cockpit sync extension registered in Pi" 0 "path=${pi_settings}"
1182
+ else
1183
+ log_event "INFO" "rotator" "pi_extension_skipped" "pi not found; cockpit sync extension was not installed" 0
1184
+ fi
1185
+ cat <<'HINT' | tee -a "${HUMAN_LOG}"
1186
+ tuxevil-rotator installed. To use the multi-account Gemini/Antigravity gateway:
1187
+ tuxevil-rotator login # add a Google Antigravity account (repeat to add more)
1188
+ tuxevil-rotator import # or bulk-import accounts from a cockpit-tools JSON
1189
+ tuxevil-rotator start # start the rotating proxy on http://localhost:51200
1190
+ Pi reaches it through the 'tuxevil-rotator' provider configured in your dotenv.
1191
+ The cockpit sync extension provides /cockpit-sync, /cockpit-provision, and /cockpit-proxy.
1192
+ Login/start are never run by setup-ai and no tokens are read or stored.
1193
+ HINT
1194
+ }
1195
+
1112
1196
  # ==============================================================================
1113
1197
  # Shell environment
1114
1198
  # ==============================================================================