@runtypelabs/cli 2.59.1 → 2.59.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.
- package/dist/chunk-0-T9NWjH.js +3899 -0
- package/dist/chunk-0x7UKC1U.js +17 -0
- package/dist/chunk-2ySxXtcg.js +418 -0
- package/dist/chunk-3An_BRHv.js +501 -0
- package/dist/chunk-6uTxgdiY.js +16218 -0
- package/dist/chunk-87nRnNRG.js +1363 -0
- package/dist/chunk-B3wo8uRx.js +322 -0
- package/dist/chunk-BGp3ZdBy.js +560 -0
- package/dist/chunk-BJQv9Elp.js +107 -0
- package/dist/chunk-BOpO6xE-.js +6053 -0
- package/dist/chunk-BQXXCL8B.js +102 -0
- package/dist/chunk-BV-8isOd.js +34 -0
- package/dist/chunk-BVMhjGVe.js +826 -0
- package/dist/chunk-BW2fg46T.js +141 -0
- package/dist/chunk-BWJdY5Ag.js +36 -0
- package/dist/chunk-BYJOrLhh.js +302 -0
- package/dist/chunk-BYab8EVL.js +127 -0
- package/dist/chunk-BfEKvp7h.js +82 -0
- package/dist/chunk-BhgLSfH6.js +1123 -0
- package/dist/chunk-BirUxTtH.js +296 -0
- package/dist/chunk-BmB6UVZ9.js +1559 -0
- package/dist/chunk-BnWhIqW1.js +109 -0
- package/dist/chunk-Bpfnfk0t.js +123 -0
- package/dist/chunk-BrhgdnM3.js +108 -0
- package/dist/chunk-Bt0XAgX5.js +327 -0
- package/dist/chunk-Bu0FJr6S.js +84 -0
- package/dist/chunk-BvEoKpN3.js +255 -0
- package/dist/chunk-BxNJgYbE.js +28 -0
- package/dist/chunk-C1OFhd7F.js +6131 -0
- package/dist/chunk-C7e7WwbU.js +331 -0
- package/dist/chunk-CDTYP6bN.js +558 -0
- package/dist/chunk-CGDQoqDC.js +24 -0
- package/dist/chunk-CJraEBVd.js +127 -0
- package/dist/chunk-CQrNhOtv.js +40 -0
- package/dist/chunk-CW6AzBH6.js +128 -0
- package/dist/chunk-CXgL4w-9.js +317 -0
- package/dist/chunk-CZrqoPyf.js +233 -0
- package/dist/chunk-CcFCyqbB.js +83 -0
- package/dist/chunk-CdaIvO-y.js +485 -0
- package/dist/chunk-CehSXtgK.js +624 -0
- package/dist/chunk-Ciqkop65.js +218 -0
- package/dist/chunk-CoAWdSiE.js +43 -0
- package/dist/chunk-Crb4y7t4.js +830 -0
- package/dist/chunk-CrlqIfmb.js +337 -0
- package/dist/chunk-Cs4pwCQ-.js +324 -0
- package/dist/chunk-CwM_9y01.js +519 -0
- package/dist/chunk-CyRbIJg8.js +440 -0
- package/dist/chunk-D0OHsyMy.js +212 -0
- package/dist/chunk-D3y2bURs.js +1347 -0
- package/dist/chunk-D4_mJk03.js +2338 -0
- package/dist/chunk-D6C8rned.js +578 -0
- package/dist/chunk-D9ZbUnks.js +11 -0
- package/dist/chunk-DEPmLshX.js +834 -0
- package/dist/chunk-DFj1UPB2.js +330 -0
- package/dist/chunk-DGdFdZVf.js +197 -0
- package/dist/chunk-DGv7X9S_.js +63 -0
- package/dist/chunk-DJQ47WYZ.js +664 -0
- package/dist/chunk-DJw3O61j.js +1974 -0
- package/dist/chunk-DK4FbWVK.js +100 -0
- package/dist/chunk-DSRYhUus.js +993 -0
- package/dist/chunk-DajZ9Eg4.js +299 -0
- package/dist/chunk-Df4dunvU.js +195 -0
- package/dist/chunk-DjNTPhfO.js +481 -0
- package/dist/chunk-Dn8eaJuu.js +1042 -0
- package/dist/chunk-DwDWqSxM.js +637 -0
- package/dist/chunk-Dz7cEtxl.js +84 -0
- package/dist/chunk-E_0bgjrS.js +359 -0
- package/dist/chunk-KPYN2vUp.js +776 -0
- package/dist/chunk-KcqPQJBi.js +86 -0
- package/dist/chunk-N9H0JnoG.js +349 -0
- package/dist/chunk-NONFcXkn.js +728 -0
- package/dist/chunk-PrFjcpmH.js +256 -0
- package/dist/chunk-QSfNYiw9.js +11 -0
- package/dist/chunk-R265bzj9.js +226 -0
- package/dist/chunk-X9V_r6jM.js +166 -0
- package/dist/chunk-ZBtPUtyA.js +55 -0
- package/dist/chunk-ZCTfmOs7.js +4939 -0
- package/dist/chunk-h6oavj0t.js +97 -0
- package/dist/chunk-i7DwkvvD.js +42 -0
- package/dist/chunk-mG-4eZ6S.js +203 -0
- package/dist/chunk-o0ygUbCf.js +313 -0
- package/dist/chunk-ucu5lrDZ.js +5715 -0
- package/dist/index.js +11367 -73789
- package/package.json +4 -4
|
@@ -0,0 +1,728 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { t as isRecord } from "./chunk-QSfNYiw9.js";
|
|
3
|
+
import { i as getApiUrl, r as createCliClient } from "./chunk-BW2fg46T.js";
|
|
4
|
+
import { n as ensureAuth } from "./chunk-CJraEBVd.js";
|
|
5
|
+
import { a as printJson, i as printDetail } from "./chunk-CcFCyqbB.js";
|
|
6
|
+
import { i as createDeviceListenIdentityStore, n as assertListenForwardTarget, r as runDeviceListen } from "./chunk-Dn8eaJuu.js";
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import { constants } from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { spawn } from "node:child_process";
|
|
12
|
+
import os from "node:os";
|
|
13
|
+
import { randomUUID } from "node:crypto";
|
|
14
|
+
import { access, chmod, copyFile, mkdir, readFile, realpath, rename, rm, stat } from "node:fs/promises";
|
|
15
|
+
// src/lib/device-provider.ts
|
|
16
|
+
var DeviceProviderError = class extends Error {
|
|
17
|
+
code;
|
|
18
|
+
retryable;
|
|
19
|
+
constructor(message, code, retryable = false, options) {
|
|
20
|
+
super(message, options);
|
|
21
|
+
this.code = code;
|
|
22
|
+
this.retryable = retryable;
|
|
23
|
+
this.name = "DeviceProviderError";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
// src/lib/macos-device-provider.ts
|
|
27
|
+
const MAX_SIDECAR_OUTPUT_BYTES = 1048576;
|
|
28
|
+
const MAX_HELPER_BYTES = 104857600;
|
|
29
|
+
const DEFAULT_SIDECAR_TIMEOUT_MS = 3e4;
|
|
30
|
+
function parseHello(value) {
|
|
31
|
+
if (!isRecord(value) || typeof value.providerId !== "string" || typeof value.protocolVersion !== "number" || typeof value.helperVersion !== "string" || typeof value.platform !== "string" || !Array.isArray(value.capabilities) || !value.capabilities.every((capability) => typeof capability === "string")) throw new DeviceProviderError("The native helper returned an invalid hello response", "INVALID_RESPONSE");
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
async function isExecutable(filePath) {
|
|
35
|
+
try {
|
|
36
|
+
await access(filePath, constants.X_OK);
|
|
37
|
+
return (await stat(filePath)).isFile();
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
var MacOSDeviceProvider = class {
|
|
43
|
+
id = "com.runtype.device.macos";
|
|
44
|
+
platform;
|
|
45
|
+
architecture;
|
|
46
|
+
operatingSystemRelease;
|
|
47
|
+
homeDirectory;
|
|
48
|
+
workingDirectory;
|
|
49
|
+
environment;
|
|
50
|
+
sidecarTimeoutMs;
|
|
51
|
+
constructor(options = {}) {
|
|
52
|
+
this.platform = options.platform ?? process.platform;
|
|
53
|
+
this.architecture = options.architecture ?? process.arch;
|
|
54
|
+
this.operatingSystemRelease = options.operatingSystemRelease ?? os.release();
|
|
55
|
+
this.homeDirectory = options.homeDirectory ?? os.homedir();
|
|
56
|
+
this.workingDirectory = options.workingDirectory ?? process.cwd();
|
|
57
|
+
this.environment = options.environment ?? process.env;
|
|
58
|
+
this.sidecarTimeoutMs = options.sidecarTimeoutMs ?? DEFAULT_SIDECAR_TIMEOUT_MS;
|
|
59
|
+
}
|
|
60
|
+
async probe() {
|
|
61
|
+
const unsupportedReason = this.unsupportedReason();
|
|
62
|
+
if (unsupportedReason) return {
|
|
63
|
+
providerId: this.id,
|
|
64
|
+
supported: false,
|
|
65
|
+
installed: false,
|
|
66
|
+
reason: unsupportedReason
|
|
67
|
+
};
|
|
68
|
+
const binaryPath = await this.resolveBinary();
|
|
69
|
+
if (!binaryPath) return {
|
|
70
|
+
providerId: this.id,
|
|
71
|
+
supported: true,
|
|
72
|
+
installed: false,
|
|
73
|
+
reason: "The Runtype macOS device helper is not installed"
|
|
74
|
+
};
|
|
75
|
+
try {
|
|
76
|
+
const hello = parseHello(await this.invokeAt(binaryPath, "hello"));
|
|
77
|
+
this.assertCompatible(hello);
|
|
78
|
+
return {
|
|
79
|
+
providerId: this.id,
|
|
80
|
+
supported: true,
|
|
81
|
+
installed: true,
|
|
82
|
+
binaryPath,
|
|
83
|
+
helperVersion: hello.helperVersion,
|
|
84
|
+
protocolVersion: hello.protocolVersion
|
|
85
|
+
};
|
|
86
|
+
} catch (error) {
|
|
87
|
+
return {
|
|
88
|
+
providerId: this.id,
|
|
89
|
+
supported: true,
|
|
90
|
+
installed: true,
|
|
91
|
+
binaryPath,
|
|
92
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async ensureInstalled(options = {}) {
|
|
97
|
+
this.assertPlatform();
|
|
98
|
+
if (!options.helperPath) {
|
|
99
|
+
const installedPath = await this.resolveInstalledBinary();
|
|
100
|
+
if (installedPath) return this.describeInstallation(installedPath, false);
|
|
101
|
+
}
|
|
102
|
+
const sourcePath = options.helperPath ? path.resolve(options.helperPath) : await this.resolveDevelopmentSource();
|
|
103
|
+
if (!sourcePath) throw new DeviceProviderError([
|
|
104
|
+
"The Runtype macOS device helper is not installed.",
|
|
105
|
+
"For this internal-alpha build, compile it from the core repository and pass:",
|
|
106
|
+
" runtype devices setup --helper-path apps/device-daemon-macos/.build/release/runtype-device"
|
|
107
|
+
].join("\n"), "HELPER_NOT_INSTALLED");
|
|
108
|
+
const resolvedSource = await realpath(sourcePath).catch(() => sourcePath);
|
|
109
|
+
const sourceDetails = await stat(resolvedSource).catch(() => null);
|
|
110
|
+
if (!sourceDetails?.isFile() || sourceDetails.size > MAX_HELPER_BYTES) throw new DeviceProviderError(`Device helper is not a valid executable file: ${sourcePath}`, "INVALID_HELPER");
|
|
111
|
+
await access(resolvedSource, constants.X_OK).catch((error) => {
|
|
112
|
+
throw new DeviceProviderError(`Device helper is not executable: ${sourcePath}`, "INVALID_HELPER", false, { cause: error });
|
|
113
|
+
});
|
|
114
|
+
const sourceHello = parseHello(await this.invokeAt(resolvedSource, "hello"));
|
|
115
|
+
this.assertCompatible(sourceHello);
|
|
116
|
+
const targetPath = this.stableBinaryPath;
|
|
117
|
+
if (resolvedSource !== await realpath(targetPath).catch(() => targetPath)) {
|
|
118
|
+
const targetDirectory = path.dirname(targetPath);
|
|
119
|
+
await mkdir(targetDirectory, {
|
|
120
|
+
recursive: true,
|
|
121
|
+
mode: 448
|
|
122
|
+
});
|
|
123
|
+
await chmod(targetDirectory, 448);
|
|
124
|
+
const temporaryPath = path.join(targetDirectory, `.runtype-device-${randomUUID()}.tmp`);
|
|
125
|
+
try {
|
|
126
|
+
await copyFile(resolvedSource, temporaryPath, constants.COPYFILE_EXCL);
|
|
127
|
+
await chmod(temporaryPath, 448);
|
|
128
|
+
await rename(temporaryPath, targetPath);
|
|
129
|
+
} finally {
|
|
130
|
+
await rm(temporaryPath, { force: true });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return this.describeInstallation(targetPath, true);
|
|
134
|
+
}
|
|
135
|
+
async invoke(method, params = {}) {
|
|
136
|
+
this.assertPlatform();
|
|
137
|
+
const binaryPath = await this.resolveBinary();
|
|
138
|
+
if (!binaryPath) throw new DeviceProviderError("The Runtype macOS device helper is not installed. Run `runtype devices setup` first.", "HELPER_NOT_INSTALLED");
|
|
139
|
+
const result = await this.invokeAt(binaryPath, method, params);
|
|
140
|
+
if (!isRecord(result)) throw new DeviceProviderError("The native helper returned a non-object result", "INVALID_RESPONSE");
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
async removeInstalledBinary() {
|
|
144
|
+
if (!await isExecutable(this.stableBinaryPath)) return false;
|
|
145
|
+
await rm(this.stableBinaryPath, { force: true });
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
get stableBinaryPath() {
|
|
149
|
+
return path.join(this.homeDirectory, ".runtype", "device", "bin", "runtype-device");
|
|
150
|
+
}
|
|
151
|
+
installedCandidates() {
|
|
152
|
+
return [
|
|
153
|
+
this.stableBinaryPath,
|
|
154
|
+
path.join(this.homeDirectory, "Applications", "Runtype Device.app", "Contents", "MacOS", "runtype-device"),
|
|
155
|
+
"/Applications/Runtype Device.app/Contents/MacOS/runtype-device"
|
|
156
|
+
];
|
|
157
|
+
}
|
|
158
|
+
developmentCandidates() {
|
|
159
|
+
return [
|
|
160
|
+
...this.environment.RUNTYPE_DEVICE_HELPER_PATH ? [path.resolve(this.environment.RUNTYPE_DEVICE_HELPER_PATH)] : [],
|
|
161
|
+
path.join(this.workingDirectory, "apps", "device-daemon-macos", ".build", "release", "runtype-device"),
|
|
162
|
+
path.join(this.workingDirectory, "apps", "device-daemon-macos", ".build", "debug", "runtype-device")
|
|
163
|
+
];
|
|
164
|
+
}
|
|
165
|
+
async firstExecutable(candidates) {
|
|
166
|
+
for (const candidate of candidates) if (await isExecutable(candidate)) return candidate;
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
resolveInstalledBinary() {
|
|
170
|
+
return this.firstExecutable(this.installedCandidates());
|
|
171
|
+
}
|
|
172
|
+
resolveDevelopmentSource() {
|
|
173
|
+
return this.firstExecutable(this.developmentCandidates());
|
|
174
|
+
}
|
|
175
|
+
async resolveBinary() {
|
|
176
|
+
return await this.resolveInstalledBinary() ?? await this.firstExecutable(this.developmentCandidates());
|
|
177
|
+
}
|
|
178
|
+
assertPlatform() {
|
|
179
|
+
const reason = this.unsupportedReason();
|
|
180
|
+
if (!reason) return;
|
|
181
|
+
throw new DeviceProviderError(reason, this.platform === "darwin" ? "UNSUPPORTED_OS_VERSION" : "UNSUPPORTED_PLATFORM");
|
|
182
|
+
}
|
|
183
|
+
unsupportedReason() {
|
|
184
|
+
if (this.platform !== "darwin") return `Runtype Device currently supports macOS only (detected ${this.platform}/${this.architecture})`;
|
|
185
|
+
const darwinMajor = Number.parseInt(this.operatingSystemRelease.split(".")[0] ?? "", 10);
|
|
186
|
+
if (!Number.isInteger(darwinMajor) || darwinMajor < 23) return `Runtype Device requires macOS 14 or newer (detected Darwin ${this.operatingSystemRelease})`;
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
assertCompatible(hello) {
|
|
190
|
+
if (hello.providerId !== this.id || hello.platform !== "macos") throw new DeviceProviderError(`Unexpected native provider identity: ${hello.providerId}/${hello.platform}`, "PROVIDER_MISMATCH");
|
|
191
|
+
if (hello.protocolVersion !== 1) throw new DeviceProviderError(`Native helper protocol ${hello.protocolVersion} is incompatible with CLI protocol 1`, "PROTOCOL_MISMATCH");
|
|
192
|
+
}
|
|
193
|
+
async describeInstallation(binaryPath, installed) {
|
|
194
|
+
const hello = parseHello(await this.invokeAt(binaryPath, "hello"));
|
|
195
|
+
this.assertCompatible(hello);
|
|
196
|
+
return {
|
|
197
|
+
providerId: this.id,
|
|
198
|
+
binaryPath,
|
|
199
|
+
helperVersion: hello.helperVersion,
|
|
200
|
+
protocolVersion: hello.protocolVersion,
|
|
201
|
+
installed
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
async invokeAt(binaryPath, method, params = {}) {
|
|
205
|
+
const id = randomUUID();
|
|
206
|
+
const request = JSON.stringify({
|
|
207
|
+
protocolVersion: 1,
|
|
208
|
+
id,
|
|
209
|
+
method,
|
|
210
|
+
params
|
|
211
|
+
});
|
|
212
|
+
const child = spawn(binaryPath, ["control"], {
|
|
213
|
+
env: this.environment,
|
|
214
|
+
stdio: [
|
|
215
|
+
"pipe",
|
|
216
|
+
"pipe",
|
|
217
|
+
"pipe"
|
|
218
|
+
],
|
|
219
|
+
shell: false
|
|
220
|
+
});
|
|
221
|
+
const stdout = [];
|
|
222
|
+
const stderr = [];
|
|
223
|
+
let stdoutBytes = 0;
|
|
224
|
+
let stderrBytes = 0;
|
|
225
|
+
let outputExceeded = false;
|
|
226
|
+
let timedOut = false;
|
|
227
|
+
child.stdout.on("data", (chunk) => {
|
|
228
|
+
stdoutBytes += chunk.length;
|
|
229
|
+
if (stdoutBytes > MAX_SIDECAR_OUTPUT_BYTES) {
|
|
230
|
+
outputExceeded = true;
|
|
231
|
+
child.kill("SIGKILL");
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
stdout.push(chunk);
|
|
235
|
+
});
|
|
236
|
+
child.stderr.on("data", (chunk) => {
|
|
237
|
+
if (stderrBytes >= MAX_SIDECAR_OUTPUT_BYTES) return;
|
|
238
|
+
stderrBytes += chunk.length;
|
|
239
|
+
stderr.push(chunk.subarray(0, MAX_SIDECAR_OUTPUT_BYTES - (stderrBytes - chunk.length)));
|
|
240
|
+
});
|
|
241
|
+
const completion = new Promise((resolve, reject) => {
|
|
242
|
+
child.once("error", reject);
|
|
243
|
+
child.once("close", (code, signal) => resolve({
|
|
244
|
+
code,
|
|
245
|
+
signal
|
|
246
|
+
}));
|
|
247
|
+
});
|
|
248
|
+
const timeout = setTimeout(() => {
|
|
249
|
+
timedOut = true;
|
|
250
|
+
child.kill("SIGKILL");
|
|
251
|
+
}, this.sidecarTimeoutMs);
|
|
252
|
+
child.stdin.on("error", () => {});
|
|
253
|
+
child.stdin.end(`${request}\n`);
|
|
254
|
+
let completionResult;
|
|
255
|
+
try {
|
|
256
|
+
completionResult = await completion;
|
|
257
|
+
} catch (error) {
|
|
258
|
+
throw new DeviceProviderError(`Unable to start the native device helper at ${binaryPath}`, "HELPER_START_FAILED", false, { cause: error });
|
|
259
|
+
} finally {
|
|
260
|
+
clearTimeout(timeout);
|
|
261
|
+
}
|
|
262
|
+
if (timedOut) throw new DeviceProviderError(`Native device helper timed out after ${this.sidecarTimeoutMs}ms`, "HELPER_TIMEOUT", true);
|
|
263
|
+
if (outputExceeded) throw new DeviceProviderError(`Native device helper output exceeded ${MAX_SIDECAR_OUTPUT_BYTES} bytes`, "OUTPUT_TOO_LARGE");
|
|
264
|
+
const output = Buffer.concat(stdout).toString("utf8").trim();
|
|
265
|
+
let response;
|
|
266
|
+
try {
|
|
267
|
+
response = JSON.parse(output);
|
|
268
|
+
} catch (error) {
|
|
269
|
+
const detail = Buffer.concat(stderr).toString("utf8").trim();
|
|
270
|
+
throw new DeviceProviderError(`Native device helper returned invalid JSON${detail ? `: ${detail}` : ""}`, "INVALID_RESPONSE", false, { cause: error });
|
|
271
|
+
}
|
|
272
|
+
if (response.protocolVersion !== 1 || response.id !== id || typeof response.ok !== "boolean") throw new DeviceProviderError("Native device helper returned a mismatched response envelope", "INVALID_RESPONSE");
|
|
273
|
+
if (!response.ok) throw new DeviceProviderError(response.error?.message || "Native device operation failed", response.error?.code || "OPERATION_FAILED", response.error?.retryable === true);
|
|
274
|
+
if (completionResult.code !== 0) throw new DeviceProviderError(`Native device helper exited with status ${completionResult.code ?? completionResult.signal ?? "unknown"}`, "HELPER_EXITED");
|
|
275
|
+
return response.result;
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
// src/commands/devices.ts
|
|
279
|
+
const defaultDependencies = {
|
|
280
|
+
ensureAuth,
|
|
281
|
+
createClient: (apiKey) => createCliClient(apiKey),
|
|
282
|
+
createProvider: () => new MacOSDeviceProvider(),
|
|
283
|
+
getApiUrl,
|
|
284
|
+
sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
|
|
285
|
+
setExitCode: (code) => {
|
|
286
|
+
process.exitCode = code;
|
|
287
|
+
},
|
|
288
|
+
createIdentityStore: (workspace) => createDeviceListenIdentityStore({ workspace }),
|
|
289
|
+
runListen: (options) => runDeviceListen(options)
|
|
290
|
+
};
|
|
291
|
+
function unwrapData(response) {
|
|
292
|
+
if (!isRecord(response) || !("data" in response)) throw new Error("Runtype API returned an invalid device response");
|
|
293
|
+
return response.data;
|
|
294
|
+
}
|
|
295
|
+
function errorMessage(error) {
|
|
296
|
+
return error instanceof Error ? error.message : String(error);
|
|
297
|
+
}
|
|
298
|
+
function apiStatusCode(error) {
|
|
299
|
+
return isRecord(error) && typeof error.statusCode === "number" ? error.statusCode : null;
|
|
300
|
+
}
|
|
301
|
+
function isRecordedRevocationError(error) {
|
|
302
|
+
if (apiStatusCode(error) !== 503) return false;
|
|
303
|
+
const data = isRecord(error) && isRecord(error.data) ? error.data : null;
|
|
304
|
+
return (typeof data?.error === "string" ? data.error : errorMessage(error)).toLowerCase().includes("revocation was recorded");
|
|
305
|
+
}
|
|
306
|
+
function printCommandError(label, error) {
|
|
307
|
+
console.error(chalk.red(label));
|
|
308
|
+
console.error(chalk.red(errorMessage(error)));
|
|
309
|
+
}
|
|
310
|
+
function printDevice(device) {
|
|
311
|
+
printDetail("Runtype Device", [
|
|
312
|
+
{
|
|
313
|
+
label: "ID",
|
|
314
|
+
value: device.id
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
label: "Name",
|
|
318
|
+
value: device.name
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
label: "Platform",
|
|
322
|
+
value: device.platform
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
label: "Online",
|
|
326
|
+
value: device.status?.online
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
label: "Last seen",
|
|
330
|
+
value: device.status?.lastSeenAt
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
label: "Available tools",
|
|
334
|
+
value: device.status?.availableTools.join(", ")
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
label: "MCP URL",
|
|
338
|
+
value: device.mcpUrl
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
label: "Revoked",
|
|
342
|
+
value: device.revokedAt
|
|
343
|
+
}
|
|
344
|
+
]);
|
|
345
|
+
}
|
|
346
|
+
function localDeviceId(status) {
|
|
347
|
+
return typeof status.deviceId === "string" && status.deviceId.length > 0 ? status.deviceId : null;
|
|
348
|
+
}
|
|
349
|
+
async function authenticatedClient(dependencies) {
|
|
350
|
+
const apiKey = await dependencies.ensureAuth({ exitOnFailure: false });
|
|
351
|
+
if (!apiKey) {
|
|
352
|
+
dependencies.setExitCode(1);
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
return dependencies.createClient(apiKey);
|
|
356
|
+
}
|
|
357
|
+
async function waitForDevice(client, deviceId, dependencies) {
|
|
358
|
+
let latest = null;
|
|
359
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
360
|
+
try {
|
|
361
|
+
latest = unwrapData(await client.get(`/devices/${encodeURIComponent(deviceId)}`));
|
|
362
|
+
if (latest.status?.online) return latest;
|
|
363
|
+
} catch {}
|
|
364
|
+
if (attempt < 7) await dependencies.sleep(750);
|
|
365
|
+
}
|
|
366
|
+
return latest;
|
|
367
|
+
}
|
|
368
|
+
async function runTail(logPath, lines) {
|
|
369
|
+
const child = spawn("/usr/bin/tail", [
|
|
370
|
+
"-n",
|
|
371
|
+
String(lines),
|
|
372
|
+
"-f",
|
|
373
|
+
logPath
|
|
374
|
+
], {
|
|
375
|
+
stdio: "inherit",
|
|
376
|
+
shell: false
|
|
377
|
+
});
|
|
378
|
+
return new Promise((resolve, reject) => {
|
|
379
|
+
child.once("error", reject);
|
|
380
|
+
child.once("close", (code) => resolve(code ?? 1));
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
function createDevicesCommand(overrides = {}) {
|
|
384
|
+
const dependencies = {
|
|
385
|
+
...defaultDependencies,
|
|
386
|
+
...overrides
|
|
387
|
+
};
|
|
388
|
+
const command = new Command("devices").description("Manage local computers connected to Runtype agents");
|
|
389
|
+
command.command("setup").description("Install, pair, and start Runtype Device on this Mac").option("--name <name>", "Device name shown in Runtype").option("--helper-path <path>", "Install a locally built native helper (internal alpha)").option("--json", "Output as JSON").action(async (options) => {
|
|
390
|
+
const provider = dependencies.createProvider();
|
|
391
|
+
let pairedDeviceId = null;
|
|
392
|
+
try {
|
|
393
|
+
const installation = await provider.ensureInstalled({ helperPath: options.helperPath });
|
|
394
|
+
const existingDeviceId = localDeviceId(await provider.invoke("status"));
|
|
395
|
+
if (existingDeviceId) throw new DeviceProviderError(`This Mac is already paired as ${existingDeviceId}. Run \`runtype devices status\` instead.`, "ALREADY_PAIRED");
|
|
396
|
+
const client = await authenticatedClient(dependencies);
|
|
397
|
+
if (!client) return;
|
|
398
|
+
const pairParams = {
|
|
399
|
+
pairingUri: unwrapData(await client.post("/devices/pairing-sessions", {})).pairingUri,
|
|
400
|
+
apiUrl: dependencies.getApiUrl()
|
|
401
|
+
};
|
|
402
|
+
if (options.name) pairParams.name = options.name;
|
|
403
|
+
const paired = await provider.invoke("pair", pairParams);
|
|
404
|
+
pairedDeviceId = paired.deviceId;
|
|
405
|
+
const service = await provider.invoke("install");
|
|
406
|
+
const local = await provider.invoke("status");
|
|
407
|
+
const device = await waitForDevice(client, paired.deviceId, dependencies);
|
|
408
|
+
const result = {
|
|
409
|
+
deviceId: paired.deviceId,
|
|
410
|
+
name: paired.name,
|
|
411
|
+
online: device?.status?.online ?? false,
|
|
412
|
+
mcpUrl: device?.mcpUrl ?? null,
|
|
413
|
+
installation,
|
|
414
|
+
service,
|
|
415
|
+
local
|
|
416
|
+
};
|
|
417
|
+
if (options.json) {
|
|
418
|
+
printJson(result);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
console.log(chalk.green(`Paired and started ${paired.name}`));
|
|
422
|
+
console.log(` Device ID: ${chalk.green(paired.deviceId)}`);
|
|
423
|
+
console.log(` Relay: ${device?.status?.online ? chalk.green("online") : chalk.yellow("connecting")}`);
|
|
424
|
+
if (device?.mcpUrl) console.log(` MCP URL: ${device.mcpUrl}`);
|
|
425
|
+
if (local.messagesDatabaseReadable !== true) console.log(chalk.yellow(`\nGrant Full Disk Access to ${String(local.executablePath)} to enable iMessage reads.`));
|
|
426
|
+
console.log(chalk.gray("iMessage sending remains disabled by local policy."));
|
|
427
|
+
} catch (error) {
|
|
428
|
+
printCommandError("Failed to set up Runtype Device", error);
|
|
429
|
+
if (pairedDeviceId) console.error(chalk.yellow(`Pairing completed as ${pairedDeviceId}, but startup did not finish. Run \`runtype devices install\` after correcting the error.`));
|
|
430
|
+
dependencies.setExitCode(1);
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
command.command("install").description("Install or restart the local Runtype Device background service").option("--helper-path <path>", "Install a locally built native helper (internal alpha)").option("--json", "Output as JSON").action(async (options) => {
|
|
434
|
+
const provider = dependencies.createProvider();
|
|
435
|
+
try {
|
|
436
|
+
const installation = await provider.ensureInstalled({ helperPath: options.helperPath });
|
|
437
|
+
const service = await provider.invoke("install");
|
|
438
|
+
if (options.json) printJson({
|
|
439
|
+
installation,
|
|
440
|
+
service
|
|
441
|
+
});
|
|
442
|
+
else {
|
|
443
|
+
console.log(chalk.green("Runtype Device is installed and running"));
|
|
444
|
+
console.log(` Helper: ${installation.binaryPath}`);
|
|
445
|
+
if (typeof service.logPath === "string") console.log(` Logs: ${service.logPath}`);
|
|
446
|
+
}
|
|
447
|
+
} catch (error) {
|
|
448
|
+
printCommandError("Failed to install Runtype Device", error);
|
|
449
|
+
dependencies.setExitCode(1);
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
command.command("listen").description("Forward this Runtype workspace's calls to a localhost agent. Does not publish a URL. Does not bind that port.").option("--forward-to <url>", "Loopback HTTP URL of the local agent").option("--name <name>", "Device name shown in Runtype").option("--json", "Output the connected session as JSON").action(async (options) => {
|
|
453
|
+
let forwardTo;
|
|
454
|
+
try {
|
|
455
|
+
forwardTo = assertListenForwardTarget(options.forwardTo);
|
|
456
|
+
} catch (error) {
|
|
457
|
+
printCommandError("Cannot forward to that URL", error);
|
|
458
|
+
dependencies.setExitCode(1);
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
const client = await authenticatedClient(dependencies);
|
|
462
|
+
if (!client) return;
|
|
463
|
+
try {
|
|
464
|
+
const workspace = await client.get("/auth/me");
|
|
465
|
+
if (typeof workspace.userId !== "string" || !workspace.userId || workspace.orgId !== null && typeof workspace.orgId !== "string") throw new Error("Cannot verify the authenticated workspace");
|
|
466
|
+
await dependencies.runListen({
|
|
467
|
+
client,
|
|
468
|
+
apiUrl: dependencies.getApiUrl(),
|
|
469
|
+
forwardTo,
|
|
470
|
+
name: options.name,
|
|
471
|
+
json: Boolean(options.json),
|
|
472
|
+
identityStore: dependencies.createIdentityStore(workspace),
|
|
473
|
+
sleep: dependencies.sleep,
|
|
474
|
+
setExitCode: dependencies.setExitCode
|
|
475
|
+
});
|
|
476
|
+
} catch (error) {
|
|
477
|
+
printCommandError("Failed to listen for workspace calls", error);
|
|
478
|
+
dependencies.setExitCode(1);
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
command.command("list").description("List paired devices and relay status").option("--json", "Output as JSON").action(async (options) => {
|
|
482
|
+
try {
|
|
483
|
+
const client = await authenticatedClient(dependencies);
|
|
484
|
+
if (!client) return;
|
|
485
|
+
const devices = unwrapData(await client.get("/devices"));
|
|
486
|
+
if (options.json) {
|
|
487
|
+
printJson({ data: devices });
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (devices.length === 0) {
|
|
491
|
+
console.log(chalk.gray("No paired devices"));
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
console.log(chalk.cyan("Runtype Devices:"));
|
|
495
|
+
for (const device of devices) {
|
|
496
|
+
const state = device.revokedAt ? "revoked" : device.status?.online ? "online" : "offline";
|
|
497
|
+
console.log(` ${chalk.green(device.id)} ${device.name} ${state}`);
|
|
498
|
+
}
|
|
499
|
+
} catch (error) {
|
|
500
|
+
printCommandError("Failed to list Runtype devices", error);
|
|
501
|
+
dependencies.setExitCode(1);
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
command.command("status [id]").description("Show local status, or cloud relay status for a device ID").option("--json", "Output as JSON").action(async (id, options) => {
|
|
505
|
+
try {
|
|
506
|
+
if (id) {
|
|
507
|
+
const client = await authenticatedClient(dependencies);
|
|
508
|
+
if (!client) return;
|
|
509
|
+
const device = unwrapData(await client.get(`/devices/${encodeURIComponent(id)}`));
|
|
510
|
+
if (options.json) printJson(device);
|
|
511
|
+
else printDevice(device);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
const local = await dependencies.createProvider().invoke("status");
|
|
515
|
+
if (options.json) printJson(local);
|
|
516
|
+
else printDetail("Local Runtype Device", [
|
|
517
|
+
{
|
|
518
|
+
label: "Device ID",
|
|
519
|
+
value: local.deviceId ?? "not paired"
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
label: "API URL",
|
|
523
|
+
value: local.apiUrl
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
label: "Helper version",
|
|
527
|
+
value: local.helperVersion
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
label: "Service installed",
|
|
531
|
+
value: local.serviceInstalled
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
label: "Service running",
|
|
535
|
+
value: local.serviceRunning
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
label: "Messages readable",
|
|
539
|
+
value: local.messagesDatabaseReadable
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
label: "Sending enabled",
|
|
543
|
+
value: local.allowSend
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
label: "Executable",
|
|
547
|
+
value: local.executablePath
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
label: "Logs",
|
|
551
|
+
value: local.logPath
|
|
552
|
+
}
|
|
553
|
+
]);
|
|
554
|
+
} catch (error) {
|
|
555
|
+
printCommandError("Failed to read Runtype Device status", error);
|
|
556
|
+
dependencies.setExitCode(1);
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
command.command("doctor").description("Check native helper, background service, and Messages permissions").option("--json", "Output as JSON").action(async (options) => {
|
|
560
|
+
const provider = dependencies.createProvider();
|
|
561
|
+
try {
|
|
562
|
+
const probe = await provider.probe();
|
|
563
|
+
const local = probe.installed && !probe.reason ? await provider.invoke("doctor") : null;
|
|
564
|
+
const healthy = probe.supported && probe.installed && local?.serviceRunning === true && local.messagesDatabaseReadable === true;
|
|
565
|
+
const result = {
|
|
566
|
+
healthy,
|
|
567
|
+
probe,
|
|
568
|
+
local
|
|
569
|
+
};
|
|
570
|
+
if (options.json) printJson(result);
|
|
571
|
+
else {
|
|
572
|
+
console.log(healthy ? chalk.green("Runtype Device is healthy") : chalk.yellow("Runtype Device needs attention"));
|
|
573
|
+
console.log(` Platform supported: ${probe.supported ? "yes" : "no"}`);
|
|
574
|
+
console.log(` Helper installed: ${probe.installed ? "yes" : "no"}`);
|
|
575
|
+
if (probe.reason) console.log(` Helper: ${probe.reason}`);
|
|
576
|
+
if (local) {
|
|
577
|
+
console.log(` Service running: ${local.serviceRunning === true ? "yes" : "no"}`);
|
|
578
|
+
console.log(` Messages readable: ${local.messagesDatabaseReadable === true ? "yes" : "no"}`);
|
|
579
|
+
if (local.messagesDatabaseReadable !== true) console.log(chalk.yellow(` Grant Full Disk Access to ${String(local.executablePath)}`));
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
if (!healthy) dependencies.setExitCode(1);
|
|
583
|
+
} catch (error) {
|
|
584
|
+
printCommandError("Runtype Device diagnostics failed", error);
|
|
585
|
+
dependencies.setExitCode(1);
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
command.command("policy <mode>").description("Set local iMessage send policy (allow-send or deny-send)").option("--json", "Output as JSON").action(async (mode, options) => {
|
|
589
|
+
if (mode !== "allow-send" && mode !== "deny-send") {
|
|
590
|
+
console.error(chalk.red("Policy must be allow-send or deny-send"));
|
|
591
|
+
dependencies.setExitCode(1);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
const provider = dependencies.createProvider();
|
|
595
|
+
let policyUpdated = false;
|
|
596
|
+
try {
|
|
597
|
+
const policy = await provider.invoke("policy", { allowSend: mode === "allow-send" });
|
|
598
|
+
policyUpdated = true;
|
|
599
|
+
const service = (await provider.invoke("status")).serviceInstalled === true ? await provider.invoke("install") : null;
|
|
600
|
+
const result = {
|
|
601
|
+
...policy,
|
|
602
|
+
capabilitiesRepublished: service !== null,
|
|
603
|
+
service
|
|
604
|
+
};
|
|
605
|
+
if (options.json) printJson(result);
|
|
606
|
+
else {
|
|
607
|
+
console.log(chalk.green(`Local iMessage sending is ${mode === "allow-send" ? "enabled" : "disabled"}`));
|
|
608
|
+
if (service) console.log(chalk.gray("Restarted the device service to publish capabilities."));
|
|
609
|
+
else console.log(chalk.yellow("The device service is not installed; run `runtype devices install` to publish this policy."));
|
|
610
|
+
}
|
|
611
|
+
} catch (error) {
|
|
612
|
+
printCommandError(policyUpdated ? "Local policy was updated, but the device service could not publish it" : "Failed to update local device policy", error);
|
|
613
|
+
dependencies.setExitCode(1);
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
command.command("mcp-url <id>").description("Print the managed MCP endpoint for a paired device").option("--json", "Output as JSON").action(async (id, options) => {
|
|
617
|
+
try {
|
|
618
|
+
const client = await authenticatedClient(dependencies);
|
|
619
|
+
if (!client) return;
|
|
620
|
+
const device = unwrapData(await client.get(`/devices/${encodeURIComponent(id)}`));
|
|
621
|
+
if (options.json) printJson({
|
|
622
|
+
deviceId: id,
|
|
623
|
+
mcpUrl: device.mcpUrl ?? null
|
|
624
|
+
});
|
|
625
|
+
else if (device.mcpUrl) console.log(device.mcpUrl);
|
|
626
|
+
else throw new Error("The API did not return an MCP URL for this device");
|
|
627
|
+
} catch (error) {
|
|
628
|
+
printCommandError("Failed to get device MCP URL", error);
|
|
629
|
+
dependencies.setExitCode(1);
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
command.command("revoke <id>").description("Revoke a paired device in Runtype").option("--yes", "Confirm irreversible device revocation").option("--json", "Output as JSON").action(async (id, options) => {
|
|
633
|
+
if (!options.yes) {
|
|
634
|
+
console.error(chalk.red("Pass --yes to confirm irreversible device revocation"));
|
|
635
|
+
dependencies.setExitCode(1);
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
try {
|
|
639
|
+
const client = await authenticatedClient(dependencies);
|
|
640
|
+
if (!client) return;
|
|
641
|
+
const device = unwrapData(await client.post(`/devices/${encodeURIComponent(id)}/revoke`, {}));
|
|
642
|
+
if (options.json) printJson(device);
|
|
643
|
+
else {
|
|
644
|
+
console.log(chalk.green(`Revoked ${device.name} (${device.id})`));
|
|
645
|
+
console.log(chalk.gray("If this is the local Mac, run `runtype devices reset --yes`."));
|
|
646
|
+
}
|
|
647
|
+
} catch (error) {
|
|
648
|
+
if (isRecordedRevocationError(error)) {
|
|
649
|
+
const result = {
|
|
650
|
+
deviceId: id,
|
|
651
|
+
revoked: true,
|
|
652
|
+
relayDisconnectConfirmed: false,
|
|
653
|
+
message: errorMessage(error)
|
|
654
|
+
};
|
|
655
|
+
if (options.json) printJson(result);
|
|
656
|
+
else {
|
|
657
|
+
console.error(chalk.yellow(`Revocation was recorded for ${id}, but relay disconnection was not confirmed.`));
|
|
658
|
+
console.error(chalk.yellow("Retry this command, and reset the local Mac if applicable."));
|
|
659
|
+
}
|
|
660
|
+
dependencies.setExitCode(1);
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
printCommandError("Failed to revoke Runtype device", error);
|
|
664
|
+
dependencies.setExitCode(1);
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
command.command("logs").description("Read local Runtype Device logs").option("-n, --lines <count>", "Number of lines", "100").option("-f, --follow", "Follow the log file").action(async (options) => {
|
|
668
|
+
try {
|
|
669
|
+
const lines = Number.parseInt(options.lines, 10);
|
|
670
|
+
if (!Number.isInteger(lines) || lines < 1 || lines > 1e4) throw new Error("--lines must be an integer between 1 and 10000");
|
|
671
|
+
const status = await dependencies.createProvider().invoke("status");
|
|
672
|
+
if (typeof status.logPath !== "string") throw new Error("The helper did not return a log path");
|
|
673
|
+
if (options.follow) {
|
|
674
|
+
const code = await runTail(status.logPath, lines);
|
|
675
|
+
if (code !== 0) dependencies.setExitCode(code);
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
const entries = (await readFile(status.logPath, "utf8").catch((error) => {
|
|
679
|
+
if ((isRecord(error) ? error.code : void 0) === "ENOENT") return "";
|
|
680
|
+
throw error;
|
|
681
|
+
})).split(/\r?\n/u);
|
|
682
|
+
if (entries.at(-1) === "") entries.pop();
|
|
683
|
+
const selected = entries.slice(-lines).join("\n");
|
|
684
|
+
if (selected) console.log(selected);
|
|
685
|
+
else console.log(chalk.gray("No local device logs yet"));
|
|
686
|
+
} catch (error) {
|
|
687
|
+
printCommandError("Failed to read Runtype Device logs", error);
|
|
688
|
+
dependencies.setExitCode(1);
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
command.command("uninstall").description("Stop the local background service while preserving pairing state").option("--remove-binary", "Also remove the CLI-managed helper binary").option("--json", "Output as JSON").action(async (options) => {
|
|
692
|
+
const provider = dependencies.createProvider();
|
|
693
|
+
try {
|
|
694
|
+
const service = await provider.invoke("uninstall");
|
|
695
|
+
const removedBinary = options.removeBinary ? await provider.removeInstalledBinary() : false;
|
|
696
|
+
if (options.json) printJson({
|
|
697
|
+
service,
|
|
698
|
+
removedBinary
|
|
699
|
+
});
|
|
700
|
+
else console.log(chalk.green("Stopped and uninstalled the local Runtype Device service"));
|
|
701
|
+
} catch (error) {
|
|
702
|
+
printCommandError("Failed to uninstall Runtype Device", error);
|
|
703
|
+
dependencies.setExitCode(1);
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
command.command("reset").description("Clear local pairing state and rotate the native signing identity").option("--yes", "Confirm destructive local reset").option("--json", "Output as JSON").action(async (options) => {
|
|
707
|
+
if (!options.yes) {
|
|
708
|
+
console.error(chalk.red("Pass --yes to confirm destructive local reset"));
|
|
709
|
+
dependencies.setExitCode(1);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
try {
|
|
713
|
+
const result = await dependencies.createProvider().invoke("reset", { rotateIdentity: true });
|
|
714
|
+
if (options.json) printJson(result);
|
|
715
|
+
else {
|
|
716
|
+
console.log(chalk.green("Cleared local device state and rotated the signing identity"));
|
|
717
|
+
console.log(chalk.gray("Revoke the old device in Runtype if it has not already been revoked."));
|
|
718
|
+
}
|
|
719
|
+
} catch (error) {
|
|
720
|
+
printCommandError("Failed to reset local Runtype Device state", error);
|
|
721
|
+
dependencies.setExitCode(1);
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
return command;
|
|
725
|
+
}
|
|
726
|
+
const devicesCommand = createDevicesCommand();
|
|
727
|
+
export { devicesCommand };
|
|
728
|
+
|