trooper-cli 0.1.6 → 0.1.7
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/README.md +4 -0
- package/bin/trooper.js +95 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,6 +27,8 @@ On macOS, `npx -y trooper-cli` prepares:
|
|
|
27
27
|
|
|
28
28
|
The local desktop plan does not require a Trooper cloud computer or Stripe checkout.
|
|
29
29
|
|
|
30
|
+
One computer can host one local Trooper workspace at a time. Re-running the CLI for the same workspace is safe. If the computer is already paired to another workspace, Trooper leaves that installation unchanged and asks you to use another computer, choose Trooper Cloud, or uninstall the existing local host first.
|
|
31
|
+
|
|
30
32
|
## Commands
|
|
31
33
|
|
|
32
34
|
### Install local runtime
|
|
@@ -72,6 +74,8 @@ npx -y --prefer-online trooper-cli@latest uninstall --yes
|
|
|
72
74
|
|
|
73
75
|
This removes Trooper's local runtime, LaunchAgents, installer command file, and Trooper local Docker container. It leaves Docker, Colima, Homebrew, Node, and npm installed because those may be used by other projects.
|
|
74
76
|
|
|
77
|
+
When online, uninstall also releases this computer's workspace binding so it can be paired elsewhere.
|
|
78
|
+
|
|
75
79
|
Keep local workspace/config data:
|
|
76
80
|
|
|
77
81
|
```bash
|
package/bin/trooper.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
4
|
import { randomBytes } from 'node:crypto';
|
|
5
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
7
|
+
import { join } from 'node:path';
|
|
5
8
|
|
|
6
9
|
const DEFAULT_API_URL = 'https://trooper-production.up.railway.app';
|
|
7
10
|
const LOCAL_MAC_SETUP_SCRIPT_URL = 'https://raw.githubusercontent.com/absurdfounder/trooper-bridge/main/setup-local-mac-host.sh';
|
|
@@ -88,6 +91,49 @@ function localToken(prefix) {
|
|
|
88
91
|
return `${prefix}_${randomBytes(24).toString('base64url')}`;
|
|
89
92
|
}
|
|
90
93
|
|
|
94
|
+
function decodeShellEnvValue(value = '') {
|
|
95
|
+
const normalized = String(value || '').trim();
|
|
96
|
+
if (normalized.startsWith("'") && normalized.endsWith("'")) return normalized.slice(1, -1);
|
|
97
|
+
return normalized.replace(/\\([\\ :/?&=])/g, '$1');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function readExistingLocalHostBinding() {
|
|
101
|
+
if (process.platform !== 'darwin') return null;
|
|
102
|
+
const installIdPath = join(homedir(), 'Library', 'Application Support', 'Trooper', 'install-id');
|
|
103
|
+
const envPath = join(homedir(), 'Library', 'Application Support', 'Trooper', 'runtime', 'trooper-local-host.env');
|
|
104
|
+
const persistedHostDeviceId = existsSync(installIdPath)
|
|
105
|
+
? String(readFileSync(installIdPath, 'utf8') || '').trim()
|
|
106
|
+
: '';
|
|
107
|
+
if (!existsSync(envPath)) {
|
|
108
|
+
return persistedHostDeviceId ? {
|
|
109
|
+
envPath,
|
|
110
|
+
orgId: '',
|
|
111
|
+
hostDeviceId: persistedHostDeviceId,
|
|
112
|
+
apiUrl: DEFAULT_API_URL,
|
|
113
|
+
token: '',
|
|
114
|
+
paired: false,
|
|
115
|
+
} : null;
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
const values = {};
|
|
119
|
+
for (const line of readFileSync(envPath, 'utf8').split(/\r?\n/)) {
|
|
120
|
+
const match = line.match(/^([A-Z0-9_]+)=(.*)$/);
|
|
121
|
+
if (match) values[match[1]] = decodeShellEnvValue(match[2]);
|
|
122
|
+
}
|
|
123
|
+
if (!values.ORG_ID) return null;
|
|
124
|
+
return {
|
|
125
|
+
envPath,
|
|
126
|
+
orgId: values.ORG_ID,
|
|
127
|
+
hostDeviceId: persistedHostDeviceId || values.TROOPER_INSTALLATION_ID || values.HOST_DEVICE_ID || '',
|
|
128
|
+
apiUrl: values.API_URL || DEFAULT_API_URL,
|
|
129
|
+
token: values.BRIDGE_AUTH_TOKEN || values.GATEWAY_TOKEN || '',
|
|
130
|
+
paired: values.ORG_ID !== 'local-unpaired',
|
|
131
|
+
};
|
|
132
|
+
} catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
91
137
|
function buildLocalMacAppInstallCommand() {
|
|
92
138
|
return `install_or_open_trooper_app() {
|
|
93
139
|
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
|
|
@@ -186,6 +232,26 @@ GROUP_ID="\$(/usr/bin/id -g)"
|
|
|
186
232
|
|
|
187
233
|
echo "Uninstalling Trooper local host from this Mac..."
|
|
188
234
|
|
|
235
|
+
if [[ -f "$TROOPER_HOME/trooper-local-host.env" ]]; then
|
|
236
|
+
set -a
|
|
237
|
+
source "$TROOPER_HOME/trooper-local-host.env"
|
|
238
|
+
set +a
|
|
239
|
+
if [[ -n "\${ORG_ID:-}" && "\${ORG_ID:-}" != "local-unpaired" && -n "\${BRIDGE_AUTH_TOKEN:-}" ]]; then
|
|
240
|
+
RELEASE_API="\${API_URL:-${DEFAULT_API_URL}}"
|
|
241
|
+
RELEASE_BODY="$(ORG_ID="$ORG_ID" HOST_DEVICE_ID="\${TROOPER_INSTALLATION_ID:-\${HOST_DEVICE_ID:-}}" BRIDGE_AUTH_TOKEN="$BRIDGE_AUTH_TOKEN" /usr/bin/python3 - <<'PY'
|
|
242
|
+
import json, os
|
|
243
|
+
print(json.dumps({
|
|
244
|
+
"token": os.environ.get("BRIDGE_AUTH_TOKEN", ""),
|
|
245
|
+
"hostDeviceId": os.environ.get("HOST_DEVICE_ID", ""),
|
|
246
|
+
}, separators=(",", ":")))
|
|
247
|
+
PY
|
|
248
|
+
)"
|
|
249
|
+
/usr/bin/curl -fsS -X POST "$RELEASE_API/api/organizations/$ORG_ID/local-host/release" \
|
|
250
|
+
-H 'Content-Type: application/json' --data "$RELEASE_BODY" >/dev/null \
|
|
251
|
+
|| echo "Warning: Trooper could not release the online workspace binding. Local removal will continue."
|
|
252
|
+
fi
|
|
253
|
+
fi
|
|
254
|
+
|
|
189
255
|
ROOT_OWNED_PATHS=()
|
|
190
256
|
for path in "$TROOPER_HOME" "$TROOPER_PARENT_DIR/install-local-host.command" "$PLIST_DIR"/so.trooper.local-*.plist; do
|
|
191
257
|
if [[ -e "$path" && ! -O "$path" ]]; then
|
|
@@ -238,14 +304,14 @@ fi
|
|
|
238
304
|
echo "Docker, Colima, Homebrew, Node, and npm were left installed."`;
|
|
239
305
|
}
|
|
240
306
|
|
|
241
|
-
async function fetchSetupGuide({ apiUrl, token, platform }) {
|
|
307
|
+
async function fetchSetupGuide({ apiUrl, token, platform, hostDeviceId = '' }) {
|
|
242
308
|
const res = await fetch(`${apiUrl}/api/local-host/setup`, {
|
|
243
309
|
method: 'POST',
|
|
244
310
|
headers: {
|
|
245
311
|
'Content-Type': 'application/json',
|
|
246
312
|
'x-trooper-setup-token': token,
|
|
247
313
|
},
|
|
248
|
-
body: JSON.stringify({ token, platform }),
|
|
314
|
+
body: JSON.stringify({ token, platform, hostDeviceId }),
|
|
249
315
|
});
|
|
250
316
|
const data = await res.json().catch(async () => ({ message: await res.text() }));
|
|
251
317
|
if (!res.ok) {
|
|
@@ -325,6 +391,20 @@ async function main() {
|
|
|
325
391
|
console.error('Tokenless local install is currently available for macOS. Open Trooper to prepare a paired installer for this platform.');
|
|
326
392
|
process.exit(1);
|
|
327
393
|
}
|
|
394
|
+
const existingBinding = readExistingLocalHostBinding();
|
|
395
|
+
if (existingBinding?.paired) {
|
|
396
|
+
console.log(`This Mac already hosts Trooper workspace ${existingBinding.orgId}.`);
|
|
397
|
+
console.log('The existing local workspace was left unchanged. Opening Trooper...');
|
|
398
|
+
if (args['print-command'] || args['dry-run']) {
|
|
399
|
+
console.log('\n' + buildLocalMacAppInstallCommand());
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
runShellCommand(buildLocalMacAppInstallCommand(), platform, {
|
|
403
|
+
failureLabel: 'Trooper desktop app',
|
|
404
|
+
successMessage: '\nTrooper desktop app is ready.',
|
|
405
|
+
});
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
328
408
|
const installCommand = buildLocalMacInstallCommand({ apiUrl, installApp: !args['skip-app'] });
|
|
329
409
|
console.log('Starting Trooper local host setup on this Mac...');
|
|
330
410
|
if (args['print-command'] || args['dry-run']) {
|
|
@@ -340,8 +420,20 @@ async function main() {
|
|
|
340
420
|
return;
|
|
341
421
|
}
|
|
342
422
|
|
|
423
|
+
const existingBinding = readExistingLocalHostBinding();
|
|
343
424
|
console.log(`Preparing Trooper local setup from ${apiUrl}...`);
|
|
344
|
-
const guide = await fetchSetupGuide({
|
|
425
|
+
const guide = await fetchSetupGuide({
|
|
426
|
+
apiUrl,
|
|
427
|
+
token,
|
|
428
|
+
platform,
|
|
429
|
+
hostDeviceId: existingBinding?.hostDeviceId || '',
|
|
430
|
+
});
|
|
431
|
+
if (existingBinding?.paired && existingBinding.orgId !== guide.orgId) {
|
|
432
|
+
console.error(`This Mac already hosts Trooper workspace ${existingBinding.orgId}.`);
|
|
433
|
+
console.error(`Trooper will not replace it with workspace ${guide.orgId}.`);
|
|
434
|
+
console.error('Open the existing workspace, use another computer, or run `npx -y trooper-cli uninstall --yes` first.');
|
|
435
|
+
process.exit(73);
|
|
436
|
+
}
|
|
345
437
|
const label = guide.organizationName ? ` for ${guide.organizationName}` : '';
|
|
346
438
|
console.log(`Starting Trooper ${guide.platform || platform} local host setup${label}.`);
|
|
347
439
|
|