@geekbeer/minion 2.42.3 → 2.43.0

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.
@@ -6,6 +6,7 @@
6
6
  # Usage:
7
7
  # sudo minion-cli setup [options] # Set up minion agent service (root)
8
8
  # sudo minion-cli reconfigure [options] # Re-register with new HQ credentials (root)
9
+ # sudo minion-cli uninstall [--keep-data] # Remove agent and services (root)
9
10
  # sudo minion-cli start # Start agent service (root)
10
11
  # sudo minion-cli stop # Stop agent service (root)
11
12
  # sudo minion-cli restart # Restart agent service (root)
@@ -807,6 +808,167 @@ CFEOF
807
808
  fi
808
809
  }
809
810
 
811
+ # ============================================================
812
+ # uninstall subcommand
813
+ # ============================================================
814
+ do_uninstall() {
815
+ local KEEP_DATA=false
816
+
817
+ # Parse arguments
818
+ while [[ $# -gt 0 ]]; do
819
+ case "$1" in
820
+ --keep-data)
821
+ KEEP_DATA=true
822
+ shift
823
+ ;;
824
+ *)
825
+ echo "Unknown option: $1"
826
+ echo "Usage: sudo minion-cli uninstall [--keep-data]"
827
+ exit 1
828
+ ;;
829
+ esac
830
+ done
831
+
832
+ echo "========================================="
833
+ echo " @geekbeer/minion Uninstall"
834
+ echo "========================================="
835
+ echo ""
836
+ echo "This will remove the minion agent and all related services."
837
+ if [ "$KEEP_DATA" = true ]; then
838
+ echo " --keep-data: /opt/minion-agent/.env will be preserved."
839
+ else
840
+ echo " Config directory /opt/minion-agent/ will be deleted."
841
+ fi
842
+ echo ""
843
+ echo -n "Type 'yes' to continue: "
844
+ read -r CONFIRM
845
+ if [ "$CONFIRM" != "yes" ]; then
846
+ echo "Uninstall cancelled."
847
+ exit 0
848
+ fi
849
+ echo ""
850
+
851
+ local TOTAL_STEPS=5
852
+
853
+ # Step 1: Stop and disable services
854
+ echo "[1/${TOTAL_STEPS}] Stopping and disabling services..."
855
+ case "$PROC_MGR" in
856
+ systemd)
857
+ for svc in minion-agent tmux-init novnc vnc autocutsel fluxbox xvfb; do
858
+ if [ -f "/etc/systemd/system/${svc}.service" ]; then
859
+ $SUDO systemctl stop "$svc" 2>/dev/null || true
860
+ $SUDO systemctl disable "$svc" 2>/dev/null || true
861
+ $SUDO rm -f "/etc/systemd/system/${svc}.service"
862
+ echo " -> Removed ${svc}.service"
863
+ fi
864
+ done
865
+ $SUDO systemctl daemon-reload
866
+ ;;
867
+ supervisord)
868
+ for conf in minion-agent cloudflared; do
869
+ local CONF_FILE="/etc/supervisor/conf.d/${conf}.conf"
870
+ if [ -f "$CONF_FILE" ]; then
871
+ $SUDO supervisorctl stop "$conf" 2>/dev/null || true
872
+ $SUDO rm -f "$CONF_FILE"
873
+ echo " -> Removed ${conf}.conf"
874
+ fi
875
+ done
876
+ if $SUDO supervisorctl status &>/dev/null; then
877
+ $SUDO supervisorctl reread 2>/dev/null || true
878
+ $SUDO supervisorctl update 2>/dev/null || true
879
+ fi
880
+ ;;
881
+ *)
882
+ echo " -> No supported process manager found, skipping"
883
+ ;;
884
+ esac
885
+
886
+ # Step 2: Remove sudoers file
887
+ echo "[2/${TOTAL_STEPS}] Removing sudoers configuration..."
888
+ local SUDOERS_FILE="/etc/sudoers.d/minion-agent"
889
+ if [ -f "$SUDOERS_FILE" ]; then
890
+ $SUDO rm -f "$SUDOERS_FILE"
891
+ echo " -> Removed $SUDOERS_FILE"
892
+ else
893
+ echo " -> Not found, skipping"
894
+ fi
895
+
896
+ # Step 3: Remove config directory
897
+ echo "[3/${TOTAL_STEPS}] Removing config directory..."
898
+ if [ "$KEEP_DATA" = true ]; then
899
+ echo " -> Skipped (--keep-data)"
900
+ else
901
+ if [ -d /opt/minion-agent ]; then
902
+ $SUDO rm -rf /opt/minion-agent
903
+ echo " -> Removed /opt/minion-agent/"
904
+ else
905
+ echo " -> Not found, skipping"
906
+ fi
907
+ fi
908
+
909
+ # Step 4: Remove deployed skills and rules
910
+ echo "[4/${TOTAL_STEPS}] Removing deployed skills and rules..."
911
+
912
+ # Detect target user home (best-effort from existing service config)
913
+ local UNINSTALL_HOME="$TARGET_HOME"
914
+
915
+ local CLAUDE_SKILLS_DIR="${UNINSTALL_HOME}/.claude/skills"
916
+ local CLAUDE_RULES_DIR="${UNINSTALL_HOME}/.claude/rules"
917
+
918
+ # Remove bundled skills (only skills that ship with the package)
919
+ local NPM_ROOT
920
+ NPM_ROOT="$(npm root -g 2>/dev/null)" || true
921
+ local BUNDLED_SKILLS_DIR="${NPM_ROOT}/@geekbeer/minion/skills"
922
+
923
+ if [ -d "$BUNDLED_SKILLS_DIR" ] && [ -d "$CLAUDE_SKILLS_DIR" ]; then
924
+ for skill_dir in "$BUNDLED_SKILLS_DIR"/*/; do
925
+ if [ -d "$skill_dir" ]; then
926
+ local skill_name
927
+ skill_name=$(basename "$skill_dir")
928
+ if [ -d "${CLAUDE_SKILLS_DIR}/${skill_name}" ]; then
929
+ rm -rf "${CLAUDE_SKILLS_DIR}/${skill_name}"
930
+ echo " -> Removed skill: $skill_name"
931
+ fi
932
+ fi
933
+ done
934
+ else
935
+ echo " -> No bundled skills to remove"
936
+ fi
937
+
938
+ # Remove deployed rules
939
+ if [ -f "${CLAUDE_RULES_DIR}/core.md" ]; then
940
+ rm -f "${CLAUDE_RULES_DIR}/core.md"
941
+ echo " -> Removed rules: core.md"
942
+ fi
943
+
944
+ # Step 5: Remove Cloudflare Tunnel config
945
+ echo "[5/${TOTAL_STEPS}] Removing Cloudflare Tunnel configuration..."
946
+ if [ -d /etc/cloudflared ]; then
947
+ # Stop cloudflared service if running via systemd
948
+ if [ "$PROC_MGR" = "systemd" ]; then
949
+ $SUDO systemctl stop cloudflared 2>/dev/null || true
950
+ $SUDO systemctl disable cloudflared 2>/dev/null || true
951
+ fi
952
+ $SUDO rm -rf /etc/cloudflared
953
+ echo " -> Removed /etc/cloudflared/"
954
+ else
955
+ echo " -> Not found, skipping"
956
+ fi
957
+
958
+ echo ""
959
+ echo "========================================="
960
+ echo " Uninstall Complete!"
961
+ echo "========================================="
962
+ echo ""
963
+ echo "The minion agent services have been removed."
964
+ echo ""
965
+ echo "To also remove the npm package:"
966
+ echo " npm uninstall -g @geekbeer/minion"
967
+ echo ""
968
+ echo "Software installed by setup (Claude Code, Gemini CLI, VNC, etc.)"
969
+ echo "was NOT removed. Uninstall them manually if needed."
970
+ }
971
+
810
972
  # ============================================================
811
973
  # reconfigure subcommand
812
974
  # ============================================================
@@ -1007,6 +1169,12 @@ case "${1:-}" in
1007
1169
  do_setup "$@"
1008
1170
  ;;
1009
1171
 
1172
+ uninstall)
1173
+ require_root uninstall
1174
+ shift
1175
+ do_uninstall "$@"
1176
+ ;;
1177
+
1010
1178
  reconfigure)
1011
1179
  require_root reconfigure
1012
1180
  shift
@@ -1142,6 +1310,7 @@ case "${1:-}" in
1142
1310
  echo "Usage:"
1143
1311
  echo " sudo minion-cli setup [options] # Set up agent service (root)"
1144
1312
  echo " sudo minion-cli reconfigure [options] # Re-register with new HQ credentials (root)"
1313
+ echo " sudo minion-cli uninstall [options] # Remove agent and services (root)"
1145
1314
  echo " sudo minion-cli start # Start agent service (root)"
1146
1315
  echo " sudo minion-cli stop # Stop agent service (root)"
1147
1316
  echo " sudo minion-cli restart # Restart agent service (root)"
@@ -1164,6 +1333,9 @@ case "${1:-}" in
1164
1333
  echo " --minion-id <UUID> Minion ID (required)"
1165
1334
  echo " --api-token <TOKEN> API token (required)"
1166
1335
  echo ""
1336
+ echo "Uninstall options:"
1337
+ echo " --keep-data Keep /opt/minion-agent/.env (preserve credentials)"
1338
+ echo ""
1167
1339
  echo "Status values: online, offline, busy"
1168
1340
  echo ""
1169
1341
  echo "Environment:"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekbeer/minion",
3
- "version": "2.42.3",
3
+ "version": "2.43.0",
4
4
  "description": "AI Agent runtime for Minion - manages status and skill deployment on VPS",
5
5
  "main": "linux/server.js",
6
6
  "bin": {
@@ -3,6 +3,7 @@
3
3
  # Usage:
4
4
  # minion-cli-win setup --hq-url https://... --minion-id <UUID> --api-token <TOKEN>
5
5
  # minion-cli-win setup --setup-tunnel
6
+ # minion-cli-win uninstall [--keep-data]
6
7
  # minion-cli-win start | stop | restart | status | health | diagnose | version | help
7
8
 
8
9
  # Parse arguments manually to avoid issues with npm wrapper passing $args as array
@@ -11,17 +12,19 @@ $HqUrl = ''
11
12
  $MinionId = ''
12
13
  $ApiToken = ''
13
14
  $SetupTunnel = $false
15
+ $KeepData = $false
14
16
 
15
17
  $i = 0
16
18
  while ($i -lt $args.Count) {
17
19
  $arg = [string]$args[$i]
18
20
  switch -Regex ($arg) {
19
- '^(setup|reconfigure|start|stop|restart|status|health|diagnose|version|help)$' { $Command = $arg }
21
+ '^(setup|reconfigure|uninstall|start|stop|restart|status|health|diagnose|version|help)$' { $Command = $arg }
20
22
  '^(-v|--version)$' { $Command = 'version' }
21
23
  '^--hq-url$' { $i++; if ($i -lt $args.Count) { $HqUrl = [string]$args[$i] } }
22
24
  '^--minion-id$' { $i++; if ($i -lt $args.Count) { $MinionId = [string]$args[$i] } }
23
25
  '^--api-token$' { $i++; if ($i -lt $args.Count) { $ApiToken = [string]$args[$i] } }
24
26
  '^--setup-tunnel$' { $SetupTunnel = $true }
27
+ '^--keep-data$' { $KeepData = $true }
25
28
  '^(-h|--help)$' { $Command = 'help' }
26
29
  }
27
30
  $i++
@@ -68,6 +71,28 @@ function Test-CommandExists {
68
71
  $null -ne (Get-Command $Name -ErrorAction SilentlyContinue)
69
72
  }
70
73
 
74
+ function Get-WebsockifyCommand {
75
+ # Returns @(executable, args-prefix) for launching websockify.
76
+ # 1) websockify.exe on PATH
77
+ if (Get-Command websockify -ErrorAction SilentlyContinue) {
78
+ return @((Get-Command websockify).Source)
79
+ }
80
+ # 2) Look in Python Scripts directories
81
+ if (Test-CommandExists 'python') {
82
+ $scriptsDir = & python -c "import sysconfig; print(sysconfig.get_path('scripts'))" 2>$null
83
+ if ($scriptsDir) {
84
+ $wsExe = Join-Path $scriptsDir 'websockify.exe'
85
+ if (Test-Path $wsExe) { return @($wsExe) }
86
+ }
87
+ }
88
+ # 3) Fallback: python -m websockify
89
+ if (Test-CommandExists 'python') {
90
+ $check = & python -c "import websockify" 2>&1
91
+ if ($LASTEXITCODE -eq 0) { return @('python', '-m', 'websockify') }
92
+ }
93
+ return $null
94
+ }
95
+
71
96
  function Get-LanIPAddress {
72
97
  try {
73
98
  $ip = (Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue |
@@ -329,17 +354,21 @@ function Invoke-Setup {
329
354
 
330
355
  # Try prebuilt version first (no Build Tools required)
331
356
  try {
332
- & npm install node-pty-prebuilt-multiarch 2>$null
333
- Write-Detail "node-pty-prebuilt-multiarch installed (no Build Tools needed)"
334
- $ptyInstalled = $true
357
+ $npmResult = cmd /c "npm install node-pty-prebuilt-multiarch 2>&1"
358
+ if ($LASTEXITCODE -eq 0) {
359
+ Write-Detail "node-pty-prebuilt-multiarch installed (no Build Tools needed)"
360
+ $ptyInstalled = $true
361
+ }
335
362
  } catch {}
336
363
 
337
364
  # Fallback: source-compiled version (requires Visual Studio Build Tools)
338
365
  if (-not $ptyInstalled) {
339
366
  try {
340
- & npm install node-pty 2>$null
341
- Write-Detail "node-pty installed (compiled from source)"
342
- $ptyInstalled = $true
367
+ $npmResult = cmd /c "npm install node-pty 2>&1"
368
+ if ($LASTEXITCODE -eq 0) {
369
+ Write-Detail "node-pty installed (compiled from source)"
370
+ $ptyInstalled = $true
371
+ }
343
372
  } catch {}
344
373
  }
345
374
 
@@ -412,10 +441,17 @@ if (`$vncExe) {
412
441
  }
413
442
  # Reload registry config (ensures no-auth settings are applied)
414
443
  & `$vncExe -controlapp -reload 2>`$null
415
- if (Get-Command websockify -ErrorAction SilentlyContinue) {
444
+ `$wsCmd = Get-WebsockifyCommand
445
+ if (`$wsCmd) {
416
446
  `$wsProc = Get-Process -Name websockify -ErrorAction SilentlyContinue
417
447
  if (-not `$wsProc) {
418
- Start-Process -FilePath (Get-Command websockify).Source -ArgumentList '6080', 'localhost:5900' -WindowStyle Hidden
448
+ if (`$wsCmd.Count -eq 1) {
449
+ Start-Process -FilePath `$wsCmd[0] -ArgumentList '6080', 'localhost:5900' -WindowStyle Hidden
450
+ } else {
451
+ # python -m websockify 6080 localhost:5900
452
+ `$wsArgs = (`$wsCmd[1..(`$wsCmd.Count-1)] + @('6080', 'localhost:5900')) -join ' '
453
+ Start-Process -FilePath `$wsCmd[0] -ArgumentList `$wsArgs -WindowStyle Hidden
454
+ }
419
455
  }
420
456
  }
421
457
  }
@@ -547,7 +583,7 @@ Remove-Item `$PidFile -Force -ErrorAction SilentlyContinue
547
583
 
548
584
  # Step 8: Setup websockify (WebSocket proxy for VNC)
549
585
  Write-Step 8 $totalSteps "Setting up websockify..."
550
- if (Test-CommandExists 'websockify') {
586
+ if (Get-WebsockifyCommand) {
551
587
  Write-Detail "websockify already installed"
552
588
  }
553
589
  else {
@@ -561,9 +597,10 @@ Remove-Item `$PidFile -Force -ErrorAction SilentlyContinue
561
597
  if (-not $pythonUsable) {
562
598
  Write-Host " Python not found. Installing via winget..."
563
599
  try {
564
- & winget install --id Python.Python.3.12 --accept-package-agreements --accept-source-agreements
600
+ $wingetResult = cmd /c "winget install --id Python.Python.3.12 --accept-package-agreements --accept-source-agreements 2>&1"
565
601
  $env:PATH = [System.Environment]::GetEnvironmentVariable('PATH', 'Machine') + ';' + [System.Environment]::GetEnvironmentVariable('PATH', 'User')
566
- Write-Detail "Python installed"
602
+ if ($LASTEXITCODE -eq 0) { Write-Detail "Python installed" }
603
+ else { Write-Warn "winget install may have failed (exit code $LASTEXITCODE): $wingetResult" }
567
604
  }
568
605
  catch {
569
606
  Write-Warn "Failed to install Python: $_"
@@ -573,21 +610,28 @@ Remove-Item `$PidFile -Force -ErrorAction SilentlyContinue
573
610
 
574
611
  Write-Host " Installing websockify via pip..."
575
612
  try {
613
+ # Use cmd /c to prevent pip's stderr (progress bars, warnings) from
614
+ # becoming RemoteException errors in PowerShell remoting sessions.
576
615
  if (Test-CommandExists 'pip') {
577
- & pip install websockify 2>$null
578
- Write-Detail "websockify installed"
616
+ $pipResult = cmd /c "pip install websockify 2>&1"
617
+ if ($LASTEXITCODE -eq 0) { Write-Detail "websockify installed" }
618
+ else { Write-Warn "pip install failed (exit code $LASTEXITCODE): $pipResult" }
579
619
  }
580
620
  elseif (Test-CommandExists 'pip3') {
581
- & pip3 install websockify 2>$null
582
- Write-Detail "websockify installed"
621
+ $pipResult = cmd /c "pip3 install websockify 2>&1"
622
+ if ($LASTEXITCODE -eq 0) { Write-Detail "websockify installed" }
623
+ else { Write-Warn "pip3 install failed (exit code $LASTEXITCODE): $pipResult" }
583
624
  }
584
625
  elseif (Test-CommandExists 'python') {
585
- & python -m pip install websockify 2>$null
586
- Write-Detail "websockify installed (via python -m pip)"
626
+ $pipResult = cmd /c "python -m pip install websockify 2>&1"
627
+ if ($LASTEXITCODE -eq 0) { Write-Detail "websockify installed (via python -m pip)" }
628
+ else { Write-Warn "python -m pip install failed (exit code $LASTEXITCODE): $pipResult" }
587
629
  }
588
630
  else {
589
631
  Write-Warn "pip not found. Install Python first, then: pip install websockify"
590
632
  }
633
+ # Refresh PATH so websockify.exe in Python Scripts dir is discoverable
634
+ $env:PATH = [System.Environment]::GetEnvironmentVariable('PATH', 'Machine') + ';' + [System.Environment]::GetEnvironmentVariable('PATH', 'User')
591
635
  }
592
636
  catch {
593
637
  Write-Warn "Failed to install websockify: $_"
@@ -743,6 +787,152 @@ Remove-Item `$PidFile -Force -ErrorAction SilentlyContinue
743
787
  Write-Host " Get-Content $(Join-Path $LogDir 'service-stdout.log') -Tail 50 # View logs"
744
788
  }
745
789
 
790
+ # ============================================================
791
+ # Uninstall
792
+ # ============================================================
793
+
794
+ function Invoke-Uninstall {
795
+ Write-Host ""
796
+ Write-Host "=========================================" -ForegroundColor Red
797
+ Write-Host " @geekbeer/minion Uninstall" -ForegroundColor Red
798
+ Write-Host "=========================================" -ForegroundColor Red
799
+ Write-Host ""
800
+ Write-Host "This will remove the minion agent and all related configuration."
801
+ if ($KeepData) {
802
+ Write-Host " --keep-data: $EnvFile will be preserved."
803
+ }
804
+ else {
805
+ Write-Host " Data directory $DataDir will be deleted."
806
+ }
807
+ Write-Host ""
808
+ $confirm = Read-Host " Type 'yes' to continue"
809
+ if ($confirm -ne 'yes') {
810
+ Write-Host "Uninstall cancelled."
811
+ exit 0
812
+ }
813
+ Write-Host ""
814
+
815
+ $totalSteps = 5
816
+
817
+ # Step 1: Stop agent and child processes
818
+ Write-Step 1 $totalSteps "Stopping agent and related processes..."
819
+ Stop-MinionProcess
820
+
821
+ # Stop VNC server
822
+ $vncProc = Get-Process -Name tvnserver -ErrorAction SilentlyContinue
823
+ if ($vncProc) {
824
+ Stop-Process -Name tvnserver -Force -ErrorAction SilentlyContinue
825
+ Write-Detail "TightVNC server stopped"
826
+ }
827
+
828
+ # Stop websockify
829
+ $wsProc = Get-Process -Name websockify -ErrorAction SilentlyContinue
830
+ if ($wsProc) {
831
+ Stop-Process -Name websockify -Force -ErrorAction SilentlyContinue
832
+ Write-Detail "websockify stopped"
833
+ }
834
+
835
+ # Stop cloudflared
836
+ $cfProc = Get-Process -Name cloudflared -ErrorAction SilentlyContinue
837
+ if ($cfProc) {
838
+ Stop-Process -Name cloudflared -Force -ErrorAction SilentlyContinue
839
+ Write-Detail "cloudflared stopped"
840
+ }
841
+
842
+ Write-Detail "All processes stopped"
843
+
844
+ # Step 2: Remove startup shortcut
845
+ Write-Step 2 $totalSteps "Removing auto-start registration..."
846
+ $startupDir = [Environment]::GetFolderPath('Startup')
847
+ $shortcutPath = Join-Path $startupDir 'MinionAgent.lnk'
848
+ if (Test-Path $shortcutPath) {
849
+ Remove-Item $shortcutPath -Force
850
+ Write-Detail "Removed $shortcutPath"
851
+ }
852
+ else {
853
+ Write-Detail "Startup shortcut not found, skipping"
854
+ }
855
+
856
+ # Step 3: Remove data directory (or keep .env)
857
+ Write-Step 3 $totalSteps "Removing data directory..."
858
+ if ($KeepData) {
859
+ # Remove everything except .env
860
+ if (Test-Path $DataDir) {
861
+ Get-ChildItem -Path $DataDir -Recurse -File | Where-Object { $_.FullName -ne $EnvFile } | Remove-Item -Force -ErrorAction SilentlyContinue
862
+ Get-ChildItem -Path $DataDir -Recurse -Directory | Sort-Object { $_.FullName.Length } -Descending | ForEach-Object {
863
+ if ((Get-ChildItem $_.FullName -Force | Measure-Object).Count -eq 0) {
864
+ Remove-Item $_.FullName -Force -ErrorAction SilentlyContinue
865
+ }
866
+ }
867
+ Write-Detail "Data directory cleaned (kept .env)"
868
+ }
869
+ else {
870
+ Write-Detail "Data directory not found, skipping"
871
+ }
872
+ }
873
+ else {
874
+ if (Test-Path $DataDir) {
875
+ Remove-Item $DataDir -Recurse -Force
876
+ Write-Detail "Removed $DataDir"
877
+ }
878
+ else {
879
+ Write-Detail "Data directory not found, skipping"
880
+ }
881
+ }
882
+
883
+ # Step 4: Remove deployed skills and rules
884
+ Write-Step 4 $totalSteps "Removing deployed skills and rules..."
885
+ $claudeSkillsDir = Join-Path $env:USERPROFILE '.claude\skills'
886
+ $claudeRulesDir = Join-Path $env:USERPROFILE '.claude\rules'
887
+
888
+ # Remove bundled skills (only skills that ship with the package)
889
+ $npmRoot = & npm root -g 2>$null
890
+ $bundledSkillsDir = Join-Path $npmRoot '@geekbeer\minion\skills'
891
+ if ((Test-Path $bundledSkillsDir) -and (Test-Path $claudeSkillsDir)) {
892
+ Get-ChildItem -Path $bundledSkillsDir -Directory | ForEach-Object {
893
+ $targetSkill = Join-Path $claudeSkillsDir $_.Name
894
+ if (Test-Path $targetSkill) {
895
+ Remove-Item $targetSkill -Recurse -Force
896
+ Write-Detail "Removed skill: $($_.Name)"
897
+ }
898
+ }
899
+ }
900
+ else {
901
+ Write-Detail "No bundled skills to remove"
902
+ }
903
+
904
+ # Remove deployed rules
905
+ $coreRule = Join-Path $claudeRulesDir 'core.md'
906
+ if (Test-Path $coreRule) {
907
+ Remove-Item $coreRule -Force
908
+ Write-Detail "Removed rules: core.md"
909
+ }
910
+
911
+ # Step 5: Remove Cloudflare Tunnel configuration
912
+ Write-Step 5 $totalSteps "Removing Cloudflare Tunnel configuration..."
913
+ $cfConfigDir = Join-Path $env:USERPROFILE '.cloudflared'
914
+ if (Test-Path $cfConfigDir) {
915
+ Remove-Item $cfConfigDir -Recurse -Force
916
+ Write-Detail "Removed $cfConfigDir"
917
+ }
918
+ else {
919
+ Write-Detail "Not found, skipping"
920
+ }
921
+
922
+ Write-Host ""
923
+ Write-Host "=========================================" -ForegroundColor Green
924
+ Write-Host " Uninstall Complete!" -ForegroundColor Green
925
+ Write-Host "=========================================" -ForegroundColor Green
926
+ Write-Host ""
927
+ Write-Host "The minion agent has been removed."
928
+ Write-Host ""
929
+ Write-Host "To also remove the npm package:"
930
+ Write-Host " npm uninstall -g @geekbeer/minion"
931
+ Write-Host ""
932
+ Write-Host "Software installed by setup (Node.js, Claude Code, TightVNC, etc.)"
933
+ Write-Host "was NOT removed. Uninstall them manually if needed."
934
+ }
935
+
746
936
  # ============================================================
747
937
  # Reconfigure
748
938
  # ============================================================
@@ -828,6 +1018,9 @@ switch ($Command) {
828
1018
  'setup' {
829
1019
  Invoke-Setup
830
1020
  }
1021
+ 'uninstall' {
1022
+ Invoke-Uninstall
1023
+ }
831
1024
  'reconfigure' {
832
1025
  Invoke-Reconfigure
833
1026
  }
@@ -904,6 +1097,7 @@ switch ($Command) {
904
1097
  Write-Host "Usage (no admin required):"
905
1098
  Write-Host " minion-cli-win setup [options] # Set up agent (auto-start on login)"
906
1099
  Write-Host " minion-cli-win reconfigure [options] # Re-register with new HQ credentials"
1100
+ Write-Host " minion-cli-win uninstall [options] # Remove agent and configuration"
907
1101
  Write-Host " minion-cli-win start # Start agent process"
908
1102
  Write-Host " minion-cli-win stop # Stop agent process"
909
1103
  Write-Host " minion-cli-win restart # Restart agent process"
@@ -923,6 +1117,9 @@ switch ($Command) {
923
1117
  Write-Host " --minion-id <UUID> Minion ID (required)"
924
1118
  Write-Host " --api-token <TOKEN> API token (required)"
925
1119
  Write-Host ""
1120
+ Write-Host "Uninstall options:"
1121
+ Write-Host " --keep-data Keep .env file (preserve credentials)"
1122
+ Write-Host ""
926
1123
  Write-Host "Data directory: $DataDir"
927
1124
  Write-Host ""
928
1125
  Write-Host "Environment:"