trooper-cli 0.1.0 → 0.1.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.
Files changed (2) hide show
  1. package/bin/trooper.js +95 -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,62 @@ 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
+ TROOPER_HOME="\${TROOPER_HOME:-$HOME/Library/Application Support/Trooper/runtime}"
113
+ TROOPER_PARENT_DIR="\$(dirname "$TROOPER_HOME")"
114
+ PLIST_DIR="$HOME/Library/LaunchAgents"
115
+ OPENCLAW_GATEWAY_CONTAINER="\${OPENCLAW_GATEWAY_CONTAINER:-openclaw-openclaw-gateway-1}"
116
+ LABELS=(${labels})
117
+
118
+ echo "Uninstalling Trooper local host from this Mac..."
119
+
120
+ ROOT_OWNED_PATHS=()
121
+ for path in "$TROOPER_HOME" "$TROOPER_PARENT_DIR/install-local-host.command" "$PLIST_DIR"/so.trooper.local-*.plist; do
122
+ if [[ -e "$path" && ! -O "$path" ]]; then
123
+ ROOT_OWNED_PATHS+=("$path")
124
+ fi
125
+ done
126
+ if (( \${#ROOT_OWNED_PATHS[@]} > 0 )); then
127
+ echo "Repairing ownership from an earlier Trooper local-host installation..."
128
+ sudo chown -R "\$(id -u):\$(id -g)" "\${ROOT_OWNED_PATHS[@]}"
129
+ fi
130
+
131
+ for label in "\${LABELS[@]}"; do
132
+ launchctl bootout "gui/\$(id -u)/$label" >/dev/null 2>&1 || true
133
+ launchctl bootout "gui/\$(id -u)" "$PLIST_DIR/$label.plist" >/dev/null 2>&1 || true
134
+ rm -f "$PLIST_DIR/$label.plist"
135
+ done
136
+
137
+ if command -v docker >/dev/null 2>&1; then
138
+ docker rm -f "$OPENCLAW_GATEWAY_CONTAINER" trooper-local-gateway >/dev/null 2>&1 || true
139
+ fi
140
+
141
+ rm -f "$TROOPER_PARENT_DIR/install-local-host.command" /tmp/trooper-local-mac-host.sh
142
+
143
+ if [[ ${keepData ? '1' : '0'} == "1" ]]; then
144
+ rm -rf "$TROOPER_HOME/bridge" "$TROOPER_HOME/bin" "$TROOPER_HOME/logs"
145
+ rm -f "$TROOPER_HOME/trooper-local-host.env"
146
+ echo "Preserved local Trooper data at: $TROOPER_HOME/openclaw-data"
147
+ else
148
+ rm -rf "$TROOPER_HOME"
149
+ fi
150
+
151
+ if [[ ${removeApp ? '1' : '0'} == "1" ]]; then
152
+ rm -rf "$HOME/Applications/Trooper.app" 2>/dev/null || true
153
+ if [[ -d "/Applications/Trooper.app" ]]; then
154
+ rm -rf "/Applications/Trooper.app" 2>/dev/null || echo "Trooper.app in /Applications still exists; remove it from Finder or rerun with permission."
155
+ fi
156
+ fi
157
+
158
+ echo "Trooper local host has been uninstalled."
159
+ if [[ ${keepData ? '1' : '0'} != "1" ]]; then
160
+ echo "Local runtime data was removed from: $TROOPER_HOME"
161
+ fi
162
+ echo "Docker, Colima, Homebrew, Node, and npm were left installed."`;
163
+ }
164
+
99
165
  async function fetchSetupGuide({ apiUrl, token, platform }) {
100
166
  const res = await fetch(`${apiUrl}/api/local-host/setup`, {
101
167
  method: 'POST',
@@ -118,19 +184,19 @@ async function fetchSetupGuide({ apiUrl, token, platform }) {
118
184
  return data;
119
185
  }
120
186
 
121
- function runInstallCommand(command, platform, { successMessage = '' } = {}) {
187
+ function runShellCommand(command, platform, { failureLabel = 'Command', successMessage = '' } = {}) {
122
188
  const isWindows = platform === 'windows' || process.platform === 'win32';
123
189
  const child = isWindows
124
190
  ? spawn('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', command], { stdio: 'inherit' })
125
191
  : spawn(process.platform === 'darwin' ? '/bin/zsh' : '/bin/bash', ['-lc', command], { stdio: 'inherit' });
126
192
 
127
193
  child.on('error', (error) => {
128
- console.error(`Could not start installer: ${error.message}`);
194
+ console.error(`Could not start ${failureLabel.toLowerCase()}: ${error.message}`);
129
195
  process.exit(1);
130
196
  });
131
197
  child.on('exit', (code, signal) => {
132
198
  if (signal) {
133
- console.error(`Installer stopped by signal ${signal}`);
199
+ console.error(`${failureLabel} stopped by signal ${signal}`);
134
200
  process.exit(1);
135
201
  }
136
202
  if ((code ?? 0) === 0 && successMessage) {
@@ -147,7 +213,7 @@ async function main() {
147
213
  printHelp();
148
214
  return;
149
215
  }
150
- if (!['setup', 'onboard'].includes(command)) {
216
+ if (!['setup', 'onboard', 'uninstall', 'remove'].includes(command)) {
151
217
  console.error(`Unknown command: ${command}`);
152
218
  printHelp();
153
219
  process.exit(1);
@@ -157,6 +223,27 @@ async function main() {
157
223
  const platform = String(args.platform || process.env.TROOPER_SETUP_PLATFORM || detectPlatform()).trim().toLowerCase();
158
224
  const token = String(args.token || args['setup-token'] || process.env.TROOPER_SETUP_TOKEN || '').trim();
159
225
 
226
+ if (command === 'uninstall' || command === 'remove') {
227
+ if (platform !== 'macos') {
228
+ console.error('Trooper local-host uninstall is currently available for macOS.');
229
+ process.exit(1);
230
+ }
231
+ if (!args.yes && !args.y && !args['print-command'] && !args['dry-run']) {
232
+ console.error('Refusing to uninstall without --yes. Run: npx -y trooper-cli uninstall --yes');
233
+ process.exit(1);
234
+ }
235
+ const uninstallCommand = buildLocalMacUninstallCommand({
236
+ keepData: Boolean(args['keep-data']),
237
+ removeApp: Boolean(args['remove-app']),
238
+ });
239
+ if (args['print-command'] || args['dry-run']) {
240
+ console.log(uninstallCommand);
241
+ return;
242
+ }
243
+ runShellCommand(uninstallCommand, platform, { failureLabel: 'Uninstall' });
244
+ return;
245
+ }
246
+
160
247
  if (!token) {
161
248
  if (platform !== 'macos') {
162
249
  console.error('Tokenless local install is currently available for macOS. Open Trooper to prepare a paired installer for this platform.');
@@ -168,7 +255,8 @@ async function main() {
168
255
  console.log('\n' + installCommand);
169
256
  return;
170
257
  }
171
- runInstallCommand(installCommand, platform, {
258
+ runShellCommand(installCommand, platform, {
259
+ failureLabel: 'Installer',
172
260
  successMessage: '\nTrooper local host is installed. Open Trooper to connect this Mac to your workspace.',
173
261
  });
174
262
  return;
@@ -184,7 +272,7 @@ async function main() {
184
272
  return;
185
273
  }
186
274
 
187
- runInstallCommand(guide.installCommand, platform);
275
+ runShellCommand(guide.installCommand, platform, { failureLabel: 'Installer' });
188
276
  }
189
277
 
190
278
  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.1",
4
4
  "description": "Trooper local host setup CLI",
5
5
  "type": "module",
6
6
  "bin": {