trooper-cli 0.1.0 → 0.1.2

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.
Files changed (2) hide show
  1. package/bin/trooper.js +96 -7
  2. package/package.json +1 -1
package/bin/trooper.js CHANGED
@@ -5,6 +5,12 @@ import { randomBytes } from 'node:crypto';
5
5
 
6
6
  const DEFAULT_API_URL = 'https://trooper-production.up.railway.app';
7
7
  const LOCAL_MAC_SETUP_SCRIPT_URL = 'https://raw.githubusercontent.com/absurdfounder/trooper-bridge/main/setup-local-mac-host.sh';
8
+ const LOCAL_MAC_LABELS = [
9
+ 'so.trooper.local-gateway',
10
+ 'so.trooper.local-bridge',
11
+ 'so.trooper.local-tunnel',
12
+ 'so.trooper.local-heartbeat',
13
+ ];
8
14
 
9
15
  function printHelp() {
10
16
  console.log(`Trooper CLI
@@ -12,15 +18,19 @@ function printHelp() {
12
18
  Usage:
13
19
  npx -y trooper-cli onboard --yes
14
20
  npx -y trooper-cli onboard --yes --token <setup-token>
21
+ npx -y trooper-cli uninstall --yes
15
22
 
16
23
  Aliases:
17
24
  setup Same as onboard
25
+ remove Same as uninstall
18
26
 
19
27
  Options:
20
28
  --token <token> Optional short-lived setup token from Trooper for workspace pairing
21
29
  --api <url> Trooper API URL (defaults to production)
22
30
  --platform <name> macos, windows, or linux (defaults to this computer)
23
31
  --yes Run non-interactively with sensible defaults
32
+ --keep-data Preserve local workspace/config data during uninstall
33
+ --remove-app Also remove Trooper.app when uninstalling, if it is writable
24
34
  --print-command Print the installer command without running it
25
35
  --dry-run Same as --print-command
26
36
  -h, --help Show this help
@@ -37,7 +47,7 @@ function parseArgs(argv) {
37
47
  }
38
48
  const [key, inlineValue] = arg.split('=', 2);
39
49
  const name = key.replace(/^-+/, '');
40
- if (['help', 'h', 'dry-run', 'print-command', 'yes', 'y'].includes(name)) {
50
+ if (['help', 'h', 'dry-run', 'print-command', 'yes', 'y', 'keep-data', 'remove-app'].includes(name)) {
41
51
  args[name] = true;
42
52
  continue;
43
53
  }
@@ -96,6 +106,63 @@ function buildLocalMacInstallCommand({ apiUrl }) {
96
106
  ].join('\n');
97
107
  }
98
108
 
109
+ function buildLocalMacUninstallCommand({ keepData = false, removeApp = false } = {}) {
110
+ const labels = LOCAL_MAC_LABELS.map(shellSingleQuote).join(' ');
111
+ return `set -euo pipefail
112
+ export PATH="/opt/homebrew/bin:/usr/local/bin:/Applications/Docker.app/Contents/Resources/bin:$HOME/Applications/Docker.app/Contents/Resources/bin:/usr/bin:/bin:/usr/sbin:/sbin"
113
+ TROOPER_HOME="\${TROOPER_HOME:-$HOME/Library/Application Support/Trooper/runtime}"
114
+ TROOPER_PARENT_DIR="\$(dirname "$TROOPER_HOME")"
115
+ PLIST_DIR="$HOME/Library/LaunchAgents"
116
+ OPENCLAW_GATEWAY_CONTAINER="\${OPENCLAW_GATEWAY_CONTAINER:-openclaw-openclaw-gateway-1}"
117
+ LABELS=(${labels})
118
+
119
+ echo "Uninstalling Trooper local host from this Mac..."
120
+
121
+ ROOT_OWNED_PATHS=()
122
+ for path in "$TROOPER_HOME" "$TROOPER_PARENT_DIR/install-local-host.command" "$PLIST_DIR"/so.trooper.local-*.plist; do
123
+ if [[ -e "$path" && ! -O "$path" ]]; then
124
+ ROOT_OWNED_PATHS+=("$path")
125
+ fi
126
+ done
127
+ if (( \${#ROOT_OWNED_PATHS[@]} > 0 )); then
128
+ echo "Repairing ownership from an earlier Trooper local-host installation..."
129
+ sudo chown -R "\$(id -u):\$(id -g)" "\${ROOT_OWNED_PATHS[@]}"
130
+ fi
131
+
132
+ for label in "\${LABELS[@]}"; do
133
+ launchctl bootout "gui/\$(id -u)/$label" >/dev/null 2>&1 || true
134
+ launchctl bootout "gui/\$(id -u)" "$PLIST_DIR/$label.plist" >/dev/null 2>&1 || true
135
+ rm -f "$PLIST_DIR/$label.plist"
136
+ done
137
+
138
+ if command -v docker >/dev/null 2>&1; then
139
+ docker rm -f "$OPENCLAW_GATEWAY_CONTAINER" trooper-local-gateway >/dev/null 2>&1 || true
140
+ fi
141
+
142
+ rm -f "$TROOPER_PARENT_DIR/install-local-host.command" /tmp/trooper-local-mac-host.sh
143
+
144
+ if [[ ${keepData ? '1' : '0'} == "1" ]]; then
145
+ rm -rf "$TROOPER_HOME/bridge" "$TROOPER_HOME/bin" "$TROOPER_HOME/logs"
146
+ rm -f "$TROOPER_HOME/trooper-local-host.env"
147
+ echo "Preserved local Trooper data at: $TROOPER_HOME/openclaw-data"
148
+ else
149
+ rm -rf "$TROOPER_HOME"
150
+ fi
151
+
152
+ if [[ ${removeApp ? '1' : '0'} == "1" ]]; then
153
+ rm -rf "$HOME/Applications/Trooper.app" 2>/dev/null || true
154
+ if [[ -d "/Applications/Trooper.app" ]]; then
155
+ rm -rf "/Applications/Trooper.app" 2>/dev/null || echo "Trooper.app in /Applications still exists; remove it from Finder or rerun with permission."
156
+ fi
157
+ fi
158
+
159
+ echo "Trooper local host has been uninstalled."
160
+ if [[ ${keepData ? '1' : '0'} != "1" ]]; then
161
+ echo "Local runtime data was removed from: $TROOPER_HOME"
162
+ fi
163
+ echo "Docker, Colima, Homebrew, Node, and npm were left installed."`;
164
+ }
165
+
99
166
  async function fetchSetupGuide({ apiUrl, token, platform }) {
100
167
  const res = await fetch(`${apiUrl}/api/local-host/setup`, {
101
168
  method: 'POST',
@@ -118,19 +185,19 @@ async function fetchSetupGuide({ apiUrl, token, platform }) {
118
185
  return data;
119
186
  }
120
187
 
121
- function runInstallCommand(command, platform, { successMessage = '' } = {}) {
188
+ function runShellCommand(command, platform, { failureLabel = 'Command', successMessage = '' } = {}) {
122
189
  const isWindows = platform === 'windows' || process.platform === 'win32';
123
190
  const child = isWindows
124
191
  ? spawn('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', command], { stdio: 'inherit' })
125
192
  : spawn(process.platform === 'darwin' ? '/bin/zsh' : '/bin/bash', ['-lc', command], { stdio: 'inherit' });
126
193
 
127
194
  child.on('error', (error) => {
128
- console.error(`Could not start installer: ${error.message}`);
195
+ console.error(`Could not start ${failureLabel.toLowerCase()}: ${error.message}`);
129
196
  process.exit(1);
130
197
  });
131
198
  child.on('exit', (code, signal) => {
132
199
  if (signal) {
133
- console.error(`Installer stopped by signal ${signal}`);
200
+ console.error(`${failureLabel} stopped by signal ${signal}`);
134
201
  process.exit(1);
135
202
  }
136
203
  if ((code ?? 0) === 0 && successMessage) {
@@ -147,7 +214,7 @@ async function main() {
147
214
  printHelp();
148
215
  return;
149
216
  }
150
- if (!['setup', 'onboard'].includes(command)) {
217
+ if (!['setup', 'onboard', 'uninstall', 'remove'].includes(command)) {
151
218
  console.error(`Unknown command: ${command}`);
152
219
  printHelp();
153
220
  process.exit(1);
@@ -157,6 +224,27 @@ async function main() {
157
224
  const platform = String(args.platform || process.env.TROOPER_SETUP_PLATFORM || detectPlatform()).trim().toLowerCase();
158
225
  const token = String(args.token || args['setup-token'] || process.env.TROOPER_SETUP_TOKEN || '').trim();
159
226
 
227
+ if (command === 'uninstall' || command === 'remove') {
228
+ if (platform !== 'macos') {
229
+ console.error('Trooper local-host uninstall is currently available for macOS.');
230
+ process.exit(1);
231
+ }
232
+ if (!args.yes && !args.y && !args['print-command'] && !args['dry-run']) {
233
+ console.error('Refusing to uninstall without --yes. Run: npx -y trooper-cli uninstall --yes');
234
+ process.exit(1);
235
+ }
236
+ const uninstallCommand = buildLocalMacUninstallCommand({
237
+ keepData: Boolean(args['keep-data']),
238
+ removeApp: Boolean(args['remove-app']),
239
+ });
240
+ if (args['print-command'] || args['dry-run']) {
241
+ console.log(uninstallCommand);
242
+ return;
243
+ }
244
+ runShellCommand(uninstallCommand, platform, { failureLabel: 'Uninstall' });
245
+ return;
246
+ }
247
+
160
248
  if (!token) {
161
249
  if (platform !== 'macos') {
162
250
  console.error('Tokenless local install is currently available for macOS. Open Trooper to prepare a paired installer for this platform.');
@@ -168,7 +256,8 @@ async function main() {
168
256
  console.log('\n' + installCommand);
169
257
  return;
170
258
  }
171
- runInstallCommand(installCommand, platform, {
259
+ runShellCommand(installCommand, platform, {
260
+ failureLabel: 'Installer',
172
261
  successMessage: '\nTrooper local host is installed. Open Trooper to connect this Mac to your workspace.',
173
262
  });
174
263
  return;
@@ -184,7 +273,7 @@ async function main() {
184
273
  return;
185
274
  }
186
275
 
187
- runInstallCommand(guide.installCommand, platform);
276
+ runShellCommand(guide.installCommand, platform, { failureLabel: 'Installer' });
188
277
  }
189
278
 
190
279
  main().catch((error) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trooper-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Trooper local host setup CLI",
5
5
  "type": "module",
6
6
  "bin": {