@touchtechclub/simbox 0.2.11 → 0.4.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.
- package/dist/cli.js +311 -8
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { Command, Option } from "commander";
|
|
4
|
+
import { Argument, Command, Option } from "commander";
|
|
5
5
|
import pc4 from "picocolors";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
|
|
@@ -12,6 +12,77 @@ import { join } from "node:path";
|
|
|
12
12
|
|
|
13
13
|
// ../../packages/shared/src/constants.ts
|
|
14
14
|
var PROD_API_URL = "https://api.simbox.touchtech.club";
|
|
15
|
+
// ../../packages/shared/src/runners.ts
|
|
16
|
+
var DEFAULT_RUNNERS = {
|
|
17
|
+
ios: { labels: ["macos-latest"] },
|
|
18
|
+
android: { labels: ["ubuntu-latest"] }
|
|
19
|
+
};
|
|
20
|
+
var RUNNER_PRESETS = [
|
|
21
|
+
{
|
|
22
|
+
name: "GitHub macOS (ARM64)",
|
|
23
|
+
platform: "ios",
|
|
24
|
+
provider: "github",
|
|
25
|
+
runner: DEFAULT_RUNNERS.ios
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "GitHub Ubuntu (x64/KVM)",
|
|
29
|
+
platform: "android",
|
|
30
|
+
provider: "github",
|
|
31
|
+
runner: DEFAULT_RUNNERS.android
|
|
32
|
+
},
|
|
33
|
+
...[2, 4, 8].map((cpu) => ({
|
|
34
|
+
name: `Blacksmith Ubuntu · ${cpu} vCPU`,
|
|
35
|
+
platform: "android",
|
|
36
|
+
provider: "blacksmith",
|
|
37
|
+
runner: { labels: [`blacksmith-${cpu}vcpu-ubuntu-2404`] }
|
|
38
|
+
})),
|
|
39
|
+
...[6, 12].map((cpu) => ({
|
|
40
|
+
name: `Blacksmith macOS · ${cpu} vCPU`,
|
|
41
|
+
platform: "ios",
|
|
42
|
+
provider: "blacksmith",
|
|
43
|
+
runner: { labels: [`blacksmith-${cpu}vcpu-macos-latest`] }
|
|
44
|
+
}))
|
|
45
|
+
];
|
|
46
|
+
function validateRunner(value, platform) {
|
|
47
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => key !== "labels")) {
|
|
48
|
+
throw new Error('Runner must be {labels: ["runner-label"]}.');
|
|
49
|
+
}
|
|
50
|
+
const labels = value.labels;
|
|
51
|
+
if (!Array.isArray(labels) || labels.length < 1 || labels.length > 8 || labels.some((label) => typeof label !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_. -]{0,99}$/.test(label) || label !== label.trim())) {
|
|
52
|
+
throw new Error("Use 1–8 runner labels (1–100 letters, numbers, spaces, dots, underscores or hyphens each).");
|
|
53
|
+
}
|
|
54
|
+
if (new Set(labels.map((label) => label.toLowerCase())).size !== labels.length)
|
|
55
|
+
throw new Error("Runner labels must be unique (case-insensitive).");
|
|
56
|
+
for (const label of labels) {
|
|
57
|
+
const lower = label.toLowerCase();
|
|
58
|
+
if (lower.includes("windows") || lower === "ubuntu-slim")
|
|
59
|
+
throw new Error("Windows and container-only ubuntu-slim runners are unsupported.");
|
|
60
|
+
if (platform === "android" && (/macos|darwin/.test(lower) || /(?:^|[-_ ])(?:arm|arm64|aarch64)(?:$|[-_ ])/.test(lower))) {
|
|
61
|
+
throw new Error("Android requires a Linux x64 runner with KVM, not macOS or ARM.");
|
|
62
|
+
}
|
|
63
|
+
if (platform === "ios" && (/ubuntu|linux/.test(lower) || /intel|x64|x86_64/.test(lower))) {
|
|
64
|
+
throw new Error("iOS requires a macOS ARM64 runner with Xcode.");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { labels: [...labels] };
|
|
68
|
+
}
|
|
69
|
+
function parseRunnerInput(input, platform) {
|
|
70
|
+
const text = input.trim();
|
|
71
|
+
let labels = [text];
|
|
72
|
+
if (text.startsWith("[")) {
|
|
73
|
+
try {
|
|
74
|
+
labels = JSON.parse(text);
|
|
75
|
+
} catch {
|
|
76
|
+
throw new Error("Custom runner label list must be valid JSON.");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return validateRunner({ labels }, platform);
|
|
80
|
+
}
|
|
81
|
+
function formatRunner(runner) {
|
|
82
|
+
return runner ? runner.labels.join(" + ") : "unknown (legacy workflow)";
|
|
83
|
+
}
|
|
84
|
+
// ../../packages/shared/src/preview.ts
|
|
85
|
+
var PREVIEW_SESSION_MS = 5 * 60000;
|
|
15
86
|
// src/config.ts
|
|
16
87
|
var DEFAULT_WEB_URL = "https://simbox.touchtech.club";
|
|
17
88
|
function stripTrailingSlash(url) {
|
|
@@ -163,7 +234,13 @@ var repoStatus = () => api("/v1/repo/status");
|
|
|
163
234
|
var repairRepo = () => api("/v1/repo/repair", {
|
|
164
235
|
method: "POST"
|
|
165
236
|
});
|
|
166
|
-
var ensureRun = (fresh, platform2) => api("/v1/runs/ensure", {
|
|
237
|
+
var ensureRun = (fresh, platform2, runner) => api("/v1/runs/ensure", {
|
|
238
|
+
method: "POST",
|
|
239
|
+
body: { new: fresh, platform: platform2, runner }
|
|
240
|
+
});
|
|
241
|
+
var runnerSettings = () => api("/v1/runners");
|
|
242
|
+
var runnerDiagnostics = () => api("/v1/runners/doctor");
|
|
243
|
+
var updateRunners = (scope, patch) => api(`/v1/runners/${scope}`, { method: "POST", body: patch });
|
|
167
244
|
async function currentRun() {
|
|
168
245
|
try {
|
|
169
246
|
const res = await api("/v1/runs/current");
|
|
@@ -361,7 +438,57 @@ class Spinner {
|
|
|
361
438
|
|
|
362
439
|
// src/remote.ts
|
|
363
440
|
import { spawn } from "node:child_process";
|
|
441
|
+
import { join as join3 } from "node:path";
|
|
442
|
+
|
|
443
|
+
// src/device-registration.ts
|
|
444
|
+
import { randomUUID } from "node:crypto";
|
|
445
|
+
import { existsSync, mkdirSync as mkdirSync2, readdirSync, statSync, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
446
|
+
import { homedir as homedir2 } from "node:os";
|
|
364
447
|
import { join as join2 } from "node:path";
|
|
448
|
+
var MAX_AGE_MS = 5 * 60000;
|
|
449
|
+
function registerDevice(info, options = {}) {
|
|
450
|
+
if (!process.env.T3CODE_HOME && !process.env.SIMBOX_DEVICE_REGISTRATION_DIR && ![".s5code", ".t3"].some((home) => existsSync(join2(homedir2(), home, "userdata", "server-runtime.json"))))
|
|
451
|
+
return;
|
|
452
|
+
try {
|
|
453
|
+
const directory = process.env.SIMBOX_DEVICE_REGISTRATION_DIR ?? join2(configDir(), "device-registrations");
|
|
454
|
+
mkdirSync2(directory, { recursive: true, mode: 448 });
|
|
455
|
+
for (const file of readdirSync(directory)) {
|
|
456
|
+
if (!/^[a-f0-9-]{36}\.json$/.test(file))
|
|
457
|
+
continue;
|
|
458
|
+
const path = join2(directory, file);
|
|
459
|
+
if (Date.now() - statSync(path).mtimeMs > MAX_AGE_MS)
|
|
460
|
+
unlinkSync(path);
|
|
461
|
+
}
|
|
462
|
+
const args = options.args ?? [];
|
|
463
|
+
const flag = (name) => {
|
|
464
|
+
const end = args.indexOf("--");
|
|
465
|
+
const flags = end < 0 ? args : args.slice(0, end);
|
|
466
|
+
const index = flags.indexOf(name);
|
|
467
|
+
return index >= 0 ? flags[index + 1] : flags.find((arg) => arg.startsWith(`${name}=`))?.slice(name.length + 1);
|
|
468
|
+
};
|
|
469
|
+
const id = randomUUID();
|
|
470
|
+
writeFileSync2(join2(directory, `${id}.json`), JSON.stringify({
|
|
471
|
+
version: 1,
|
|
472
|
+
createdAt: Date.now(),
|
|
473
|
+
...info,
|
|
474
|
+
platform: flag("--platform") ?? options.platform,
|
|
475
|
+
deviceId: flag("--udid") ?? flag("--serial"),
|
|
476
|
+
deviceName: flag("--device"),
|
|
477
|
+
agentSession: flag("--session"),
|
|
478
|
+
stop: options.stop === true,
|
|
479
|
+
autoBoot: !options.args,
|
|
480
|
+
apiUrl: apiBaseUrl(),
|
|
481
|
+
userToken: loadToken()
|
|
482
|
+
}), { mode: 384, flag: "wx" });
|
|
483
|
+
process.stderr.write(`[simbox-device:${id}]
|
|
484
|
+
`);
|
|
485
|
+
} catch {
|
|
486
|
+
process.stderr.write(`Could not notify S5 Code about this device; Simbox remains connected.
|
|
487
|
+
`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// src/remote.ts
|
|
365
492
|
var STARTUP_TIMEOUT_MS = 300000;
|
|
366
493
|
async function waitForAndroid(runId, options = {}) {
|
|
367
494
|
const deadline = Date.now() + (options.timeoutMs ?? 12 * 60000);
|
|
@@ -469,13 +596,15 @@ async function cmdExec(args) {
|
|
|
469
596
|
delete env.AGENT_DEVICE_REMOTE_CONFIG;
|
|
470
597
|
delete env.AGENT_DEVICE_DAEMON_BASE_URL;
|
|
471
598
|
const code = await new Promise((resolve, reject) => {
|
|
472
|
-
const child = spawn(bin, remoteArgs(args, info,
|
|
599
|
+
const child = spawn(bin, remoteArgs(args, info, join3(configDir(), "agent-device")), {
|
|
473
600
|
stdio: "inherit",
|
|
474
601
|
env
|
|
475
602
|
});
|
|
476
603
|
child.on("error", reject);
|
|
477
604
|
child.on("exit", (status) => resolve(status ?? 1));
|
|
478
605
|
});
|
|
606
|
+
if (code === 0 && !args.some((arg) => ["help", "--help", "-h", "--version", "version"].includes(arg)))
|
|
607
|
+
registerDevice(info, { args });
|
|
479
608
|
if (code !== 0) {
|
|
480
609
|
process.stderr.write("Simbox did not retry the device command (it may already have executed). Use `simbox exec session list` to inspect it; the next command refreshes the tunnel automatically.\n");
|
|
481
610
|
}
|
|
@@ -560,7 +689,7 @@ function printRepoConnected(repo) {
|
|
|
560
689
|
console.log(`Your default branch is protected — merge ${pc3.underline(repo.prUrl)} to activate Simbox.`);
|
|
561
690
|
}
|
|
562
691
|
if (repo.private) {
|
|
563
|
-
console.log(pc3.yellow("Note: private
|
|
692
|
+
console.log(pc3.yellow("Note: private repositories may consume paid runner minutes. Review GitHub or your selected provider's billing."));
|
|
564
693
|
}
|
|
565
694
|
console.log(`Run ${pc3.cyan("simbox sim")} to boot a simulator.`);
|
|
566
695
|
}
|
|
@@ -569,7 +698,10 @@ var SIM_POLL_MS = 3000;
|
|
|
569
698
|
async function cmdSim(opts) {
|
|
570
699
|
const json = opts.json === true;
|
|
571
700
|
const out = json ? process.stderr : process.stdout;
|
|
572
|
-
|
|
701
|
+
if (opts.runner !== undefined && !opts.platform)
|
|
702
|
+
throw new CliError("Use --platform ios or android with --runner.");
|
|
703
|
+
const runner = opts.runner === undefined ? undefined : parseRunnerInput(opts.runner, opts.platform);
|
|
704
|
+
const first = await ensureRun(opts.new === true, opts.platform, runner);
|
|
573
705
|
let info;
|
|
574
706
|
if (first.state === "live") {
|
|
575
707
|
info = {
|
|
@@ -619,6 +751,7 @@ async function cmdSim(opts) {
|
|
|
619
751
|
info = await waitForRemote(info);
|
|
620
752
|
if (opts.platform === "android")
|
|
621
753
|
await waitForAndroid(info.runId);
|
|
754
|
+
registerDevice(info, { platform: opts.platform });
|
|
622
755
|
if (json) {
|
|
623
756
|
const payload = {
|
|
624
757
|
tunnel_url: info.tunnelUrl,
|
|
@@ -645,6 +778,7 @@ async function cmdPs() {
|
|
|
645
778
|
console.log(`${pc3.bold("run")} ${run.id}`);
|
|
646
779
|
console.log(` state: ${stateColor(run.state)}`);
|
|
647
780
|
console.log(` platform: ${run.platform}`);
|
|
781
|
+
console.log(` runner: ${formatRunner(run.runner)}`);
|
|
648
782
|
console.log(` gh_run_id: ${run.ghRunId ?? pc3.dim("pending")}`);
|
|
649
783
|
console.log(` active_devices: ${run.activeDevices}`);
|
|
650
784
|
console.log(` android_ready: ${run.androidReady ? "yes" : "no"}`);
|
|
@@ -659,6 +793,13 @@ async function cmdStop() {
|
|
|
659
793
|
return;
|
|
660
794
|
}
|
|
661
795
|
await stopRun(run.id);
|
|
796
|
+
if (run.tunnelUrl && run.daemonToken)
|
|
797
|
+
registerDevice({
|
|
798
|
+
runId: run.id,
|
|
799
|
+
tunnelUrl: run.tunnelUrl,
|
|
800
|
+
daemonToken: run.daemonToken,
|
|
801
|
+
expiresAt: run.expiresAt
|
|
802
|
+
}, { stop: true });
|
|
662
803
|
console.log(`${pc3.green("✔")} Stop requested for run ${pc3.bold(run.id)}.`);
|
|
663
804
|
}
|
|
664
805
|
async function cmdRepair() {
|
|
@@ -680,6 +821,140 @@ async function cmdLogout() {
|
|
|
680
821
|
}
|
|
681
822
|
}
|
|
682
823
|
|
|
824
|
+
// src/runners.ts
|
|
825
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
826
|
+
function printRunnerSettings(settings, json = false) {
|
|
827
|
+
if (json) {
|
|
828
|
+
console.log(JSON.stringify(settings));
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
console.log(`Repository: ${settings.repoFullName ?? "not connected"}`);
|
|
832
|
+
for (const platform2 of ["ios", "android"]) {
|
|
833
|
+
console.log(`${platform2}: ${formatRunner(settings.effective[platform2].runner)} (${settings.effective[platform2].source})`);
|
|
834
|
+
console.log(` account: ${settings.account[platform2] ? formatRunner(settings.account[platform2]) : "GitHub default"}`);
|
|
835
|
+
console.log(` repository: ${settings.repository?.[platform2] ? formatRunner(settings.repository[platform2]) : "inherit account"}`);
|
|
836
|
+
}
|
|
837
|
+
console.log("Settings apply to future runs only. Run `simbox repair` once to upgrade older workflows.");
|
|
838
|
+
console.log("Blacksmith requires its GitHub App on the repository and bills separately. Custom runners need Linux x64/KVM or macOS ARM64/Xcode.");
|
|
839
|
+
}
|
|
840
|
+
async function cmdRunnersShow(json = false) {
|
|
841
|
+
printRunnerSettings(await runnerSettings(), json);
|
|
842
|
+
}
|
|
843
|
+
function cmdRunnerPresets() {
|
|
844
|
+
for (const preset of RUNNER_PRESETS)
|
|
845
|
+
console.log(`${preset.platform.padEnd(7)} ${preset.runner.labels[0]} — ${preset.name}`);
|
|
846
|
+
}
|
|
847
|
+
async function cmdRunnersSet(platform2, input, scope, json = false) {
|
|
848
|
+
const runner = input === null ? null : parseRunnerInput(input, platform2);
|
|
849
|
+
printRunnerSettings(await updateRunners(scope, { [platform2]: runner }), json);
|
|
850
|
+
}
|
|
851
|
+
async function cmdDoctor(json = false) {
|
|
852
|
+
const diagnostics = await runnerDiagnostics();
|
|
853
|
+
const tool = (name, required) => {
|
|
854
|
+
const result = spawnSync2(name, ["--version"], {
|
|
855
|
+
encoding: "utf8",
|
|
856
|
+
timeout: 1e4,
|
|
857
|
+
shell: false
|
|
858
|
+
});
|
|
859
|
+
return {
|
|
860
|
+
name,
|
|
861
|
+
required,
|
|
862
|
+
ok: !result.error && result.status === 0,
|
|
863
|
+
message: result.error ? `Missing or failed ${name}; install it locally.` : (result.stdout || result.stderr).trim().slice(0, 200)
|
|
864
|
+
};
|
|
865
|
+
};
|
|
866
|
+
const local = [tool("agent-device", true), tool("bunx", false)];
|
|
867
|
+
if (json)
|
|
868
|
+
console.log(JSON.stringify({ ...diagnostics, local }));
|
|
869
|
+
else {
|
|
870
|
+
for (const check of [...local, ...diagnostics.checks])
|
|
871
|
+
console.log(`${check.ok ? "PASS" : ("required" in check) && !check.required ? "WARN" : "FAIL"} ${check.name}: ${check.message}`);
|
|
872
|
+
printRunnerSettings(diagnostics.settings);
|
|
873
|
+
console.log("No job was started. Actual KVM/Xcode capability is checked on the runner, not this machine.");
|
|
874
|
+
}
|
|
875
|
+
if (diagnostics.checks.some((check) => !check.ok) || local.some((check) => check.required && !check.ok))
|
|
876
|
+
process.exitCode = 1;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// src/skills.ts
|
|
880
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
881
|
+
import { createInterface } from "node:readline/promises";
|
|
882
|
+
function skillInstallArgs(opts) {
|
|
883
|
+
if (opts.project && opts.global)
|
|
884
|
+
throw new CliError("Choose either --project or --global, not both.");
|
|
885
|
+
if (!opts.project && !opts.global)
|
|
886
|
+
throw new CliError("Choose --project or --global (scope is never inferred).");
|
|
887
|
+
if (opts.agent?.some((agent) => !/^[a-z][a-z0-9-]*$/.test(agent) && agent !== "*"))
|
|
888
|
+
throw new CliError("Invalid agent ID. Use skills CLI agent names, e.g. claude-code or codex.");
|
|
889
|
+
if (opts.yes && !opts.agent?.length)
|
|
890
|
+
throw new CliError("Non-interactive installs require --agent; Simbox will not choose agents for you.");
|
|
891
|
+
return [
|
|
892
|
+
"skills",
|
|
893
|
+
"add",
|
|
894
|
+
"TouchTechClub/simbox",
|
|
895
|
+
"--skill",
|
|
896
|
+
"simbox",
|
|
897
|
+
...opts.global ? ["--global"] : [],
|
|
898
|
+
...opts.agent?.length ? ["--agent", ...opts.agent] : [],
|
|
899
|
+
...opts.yes ? ["--yes"] : []
|
|
900
|
+
];
|
|
901
|
+
}
|
|
902
|
+
async function selectSkillScope(opts, interactive, ask) {
|
|
903
|
+
if (opts.project && opts.global)
|
|
904
|
+
throw new CliError("Choose either --project or --global, not both.");
|
|
905
|
+
if (opts.project || opts.global)
|
|
906
|
+
return opts;
|
|
907
|
+
if (!interactive || opts.yes)
|
|
908
|
+
throw new CliError("Specify --project or --global. For automation also provide --agent <id> --yes.");
|
|
909
|
+
while (true) {
|
|
910
|
+
const answer = (await ask(`Install the Simbox skill where?
|
|
911
|
+
1. Current project (${process.cwd()})
|
|
912
|
+
2. Global (your user account)
|
|
913
|
+
Choose 1 or 2 (q to cancel): `)).trim().toLowerCase();
|
|
914
|
+
if (answer === "1" || answer === "project")
|
|
915
|
+
return { ...opts, project: true };
|
|
916
|
+
if (answer === "2" || answer === "global")
|
|
917
|
+
return { ...opts, global: true };
|
|
918
|
+
if (answer === "q" || answer === "cancel")
|
|
919
|
+
throw new CliError("Skill installation canceled.");
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
async function cmdSkillsInstall(opts) {
|
|
923
|
+
let selected = opts;
|
|
924
|
+
if (!opts.project && !opts.global) {
|
|
925
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY || opts.yes) {
|
|
926
|
+
selected = await selectSkillScope(opts, false, async () => "");
|
|
927
|
+
} else {
|
|
928
|
+
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
929
|
+
try {
|
|
930
|
+
selected = await selectSkillScope(opts, true, (q) => prompt.question(q));
|
|
931
|
+
} finally {
|
|
932
|
+
prompt.close();
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
const args = skillInstallArgs(selected);
|
|
937
|
+
if (!opts.dryRun && !opts.yes && (!process.stdin.isTTY || !process.stdout.isTTY)) {
|
|
938
|
+
throw new CliError("Interactive skills installation needs a terminal. Use --project/--global --agent <id> --yes for automation.");
|
|
939
|
+
}
|
|
940
|
+
console.log(`Scope: ${selected.global ? "global" : `current project (${process.cwd()})`}`);
|
|
941
|
+
console.log(`Command: bunx ${args.map((arg) => arg === "*" ? "'*'" : arg).join(" ")}`);
|
|
942
|
+
console.log("This runs the third-party skills CLI. It handles agent selection and install confirmations; --yes accepts its installation/overwrite prompts.");
|
|
943
|
+
console.log("Set DISABLE_TELEMETRY=1 to opt out of skills CLI telemetry.");
|
|
944
|
+
if (opts.dryRun)
|
|
945
|
+
return;
|
|
946
|
+
await new Promise((resolve, reject) => {
|
|
947
|
+
const child = spawn2("bunx", args, {
|
|
948
|
+
cwd: process.cwd(),
|
|
949
|
+
env: process.env,
|
|
950
|
+
stdio: "inherit",
|
|
951
|
+
shell: false
|
|
952
|
+
});
|
|
953
|
+
child.once("error", (err) => reject(new CliError(`Could not start bunx. Install Bun or run the printed command manually. (${err.message})`)));
|
|
954
|
+
child.once("exit", (code, signal) => code === 0 ? resolve() : reject(new CliError(`Skills installer ${signal ? `stopped by ${signal}` : `exited with code ${code}`}.`, code ?? 1)));
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
|
|
683
958
|
// src/cli.ts
|
|
684
959
|
var program = new Command().enablePositionalOptions();
|
|
685
960
|
program.name("simbox").description("On-demand iOS simulators & Android emulators on your own GitHub Actions minutes.").version(getVersion());
|
|
@@ -691,7 +966,7 @@ program.command("init").description("Open onboarding and wait until a repo is co
|
|
|
691
966
|
program.command("sim").description("Ensure a run and its tunnel are ready for device commands").option("--new", "force a fresh run even if one is live").addOption(new Option("--platform <platform>", "ios: macOS runner; android: Linux/KVM runner").choices([
|
|
692
967
|
"ios",
|
|
693
968
|
"android"
|
|
694
|
-
])).option("--json", "print {tunnel_url, daemon_token, run_id, expires_at} as JSON only").action((opts) => {
|
|
969
|
+
])).option("--runner <label-or-json>", "override runner label or JSON label array; requires --platform").option("--json", "print {tunnel_url, daemon_token, run_id, expires_at} as JSON only").action((opts) => {
|
|
695
970
|
requireToken();
|
|
696
971
|
return run(cmdSim(opts));
|
|
697
972
|
});
|
|
@@ -712,6 +987,33 @@ program.command("repair").description("Re-commit the workflow, rotate the repo s
|
|
|
712
987
|
return run(cmdRepair());
|
|
713
988
|
});
|
|
714
989
|
program.command("logout").description("Delete the saved auth token").action(() => run(cmdLogout()));
|
|
990
|
+
var runners2 = program.command("runners").description("Account runner defaults and connected-repository overrides");
|
|
991
|
+
program.command("doctor").description("Check tools, runner settings and workflow without starting a job").option("--json", "print diagnostics as JSON").action((opts) => {
|
|
992
|
+
requireToken();
|
|
993
|
+
return run(cmdDoctor(opts.json));
|
|
994
|
+
});
|
|
995
|
+
runners2.command("show").option("--json", "print settings as JSON").action((opts) => {
|
|
996
|
+
requireToken();
|
|
997
|
+
return run(cmdRunnersShow(opts.json));
|
|
998
|
+
});
|
|
999
|
+
runners2.command("presets").description("List GitHub and Blacksmith presets").action(cmdRunnerPresets);
|
|
1000
|
+
for (const command of ["set", "reset"]) {
|
|
1001
|
+
const cmd = runners2.command(command).addArgument(new Argument("<platform>").choices(["ios", "android"]));
|
|
1002
|
+
if (command === "set")
|
|
1003
|
+
cmd.argument("<label-or-json>", "runner label or JSON label array");
|
|
1004
|
+
cmd.addOption(new Option("--scope <scope>", "account defaults or connected repository override").choices(["account", "repo"]).makeOptionMandatory()).option("--json", "print settings as JSON");
|
|
1005
|
+
if (command === "set")
|
|
1006
|
+
cmd.action((platform2, input, opts) => {
|
|
1007
|
+
requireToken();
|
|
1008
|
+
return run(cmdRunnersSet(platform2, input, opts.scope, opts.json));
|
|
1009
|
+
});
|
|
1010
|
+
else
|
|
1011
|
+
cmd.action((platform2, opts) => {
|
|
1012
|
+
requireToken();
|
|
1013
|
+
return run(cmdRunnersSet(platform2, null, opts.scope, opts.json));
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
program.command("skills").description("Set up the small Simbox agent skill").command("install").description("Install via bunx skills; asks current project or global").option("--project", "install to the current project").option("--global", "install to your user account").option("--agent <agents...>", "target skills CLI agent IDs; otherwise let the installer ask").option("--yes", "accept installer confirmations; requires explicit scope and agents").option("--dry-run", "print the command without installing").action((opts) => run(cmdSkillsInstall(opts)));
|
|
715
1017
|
function getVersion() {
|
|
716
1018
|
try {
|
|
717
1019
|
const require2 = createRequire(import.meta.url);
|
|
@@ -725,9 +1027,10 @@ async function run(p) {
|
|
|
725
1027
|
try {
|
|
726
1028
|
await p;
|
|
727
1029
|
} catch (err) {
|
|
728
|
-
|
|
1030
|
+
const error = friendlyError(err);
|
|
1031
|
+
process.stderr.write(`${pc4.red("✖")} ${error.message}
|
|
729
1032
|
`);
|
|
730
|
-
process.exitCode =
|
|
1033
|
+
process.exitCode = error.exitCode;
|
|
731
1034
|
}
|
|
732
1035
|
}
|
|
733
1036
|
program.parseAsync(process.argv).catch((err) => {
|