stim 1.16.0 → 1.17.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.
Files changed (107) hide show
  1. package/dist/{activity-BxwdD81S.mjs → activity-IRvJY_ru.mjs} +23 -3
  2. package/dist/{agent-device-usage-output-Dpih1Tfa.mjs → agent-device-usage-output-BK41vnzN.mjs} +3 -3
  3. package/dist/{android-Ift4kVvt.mjs → android--GorydsK.mjs} +679 -317
  4. package/dist/{android-B_NumVyQ.mjs → android-B_YNZy61.mjs} +7 -43
  5. package/dist/{android-r2rBL8kV.mjs → android-DE4v59Mb.mjs} +12 -3
  6. package/dist/{android-cas-8U4DPqeR.mjs → android-cas-ZFpjJBL_.mjs} +6 -5
  7. package/dist/{app-install-CKWdbXIX.mjs → app-install-VPkI5WEg.mjs} +6 -5
  8. package/dist/{budget-DjrURfQn.mjs → budget-DM984mnd.mjs} +34 -18
  9. package/dist/{build-plan-DjeSb2Xl.mjs → build-plan-CmQCoyT-.mjs} +345 -86
  10. package/dist/{build-slots-rUk77xqh.mjs → build-slots-41v_d2x9.mjs} +35 -25
  11. package/dist/{cli-BkYZ_6hy.mjs → cli-Daxug5-K.mjs} +17 -17
  12. package/dist/cli.mjs +1 -1
  13. package/dist/collector-run.mjs +3 -3
  14. package/dist/{command-output-DnJMdEY-.mjs → command-output-CDAIFD-8.mjs} +3 -3
  15. package/dist/{created-devices-D5owjN4C.mjs → created-devices-CpvzBL25.mjs} +1 -1
  16. package/dist/{dependency-state-B6cMXZab.mjs → dependency-state-CLcBHH2M.mjs} +1 -1
  17. package/dist/{deps-urbmfXig.mjs → deps-DNWaQs4V.mjs} +1 -1
  18. package/dist/{dev-client-CroAIjjj.mjs → dev-client-B-XC4uRH.mjs} +2 -2
  19. package/dist/{device-DZQjF7H5.mjs → device-C8IOoJPv.mjs} +7 -7
  20. package/dist/device-capacity-D0GWHnTM.mjs +580 -0
  21. package/dist/device-host-worker.mjs +436 -154
  22. package/dist/{device-ios-BadmeL2V.mjs → device-ios-Bc7vbGwl.mjs} +75 -44
  23. package/dist/{device-lease-D-hLqx1b.mjs → device-lease-DpoS36_u.mjs} +2 -2
  24. package/dist/{device-lease-run-CqjShqWW.mjs → device-lease-run-B8YltIH_.mjs} +3 -3
  25. package/dist/{device-pool-CUaEWMMm.mjs → device-pool-3o7s5lo4.mjs} +2 -2
  26. package/dist/{device-remote-D5WVWqOq.mjs → device-remote-hFSkpfQu.mjs} +12 -12
  27. package/dist/{doctor-Cimf-gGB.mjs → doctor-Cnk9d7Hm.mjs} +32 -51
  28. package/dist/{error-diagnostics-B9L4Ae0x.mjs → error-diagnostics-C-NMnhrk.mjs} +7 -7
  29. package/dist/{gc-Bqy4bjEu.mjs → gc-DxCL2A46.mjs} +308 -26
  30. package/dist/{gradle-BsO1MdsH.mjs → gradle-D7-L2Th3.mjs} +3 -3
  31. package/dist/{guide-B_uyKTxW.mjs → guide-DjrTQGik.mjs} +1662 -565
  32. package/dist/{guide-status-CjEZMAD4.mjs → guide-status-Du-ZQ3FD.mjs} +1 -1
  33. package/dist/hosted-android-DwLS4Mse.mjs +32 -0
  34. package/dist/{hosted-client-a-WmqE2L.mjs → hosted-client-wk1W_CNW.mjs} +2 -3
  35. package/dist/hosted-ios-aMo7Cljf.mjs +15 -0
  36. package/dist/{hosted-logs-D5g041Q3.mjs → hosted-logs-DqYmWtXc.mjs} +16 -16
  37. package/dist/{hosted-macos-BSZyIb3y.mjs → hosted-macos-dlHXEeF5.mjs} +4 -4
  38. package/dist/{hosted-ios-CIWelIqf.mjs → hosted-native-C9xJ9rpA.mjs} +77 -42
  39. package/dist/{idle-DYVymG92.mjs → idle-B2m-Ovod.mjs} +122 -58
  40. package/dist/{idle-shutdown-CTsDnAde.mjs → idle-shutdown-_3oOV1V_.mjs} +25 -17
  41. package/dist/{in-use-DvBw0Kxo.mjs → in-use-CvGOUn-x.mjs} +11 -11
  42. package/dist/{ios-aq3QoGI3.mjs → ios-DE6duQoz.mjs} +170 -88
  43. package/dist/{ios-BaInn3sW.mjs → ios-DSfnPfse.mjs} +4 -4
  44. package/dist/{ios-device-FfCN_C8p.mjs → ios-device-pAN4t54-.mjs} +1 -1
  45. package/dist/ios-state-8L3lgum6.mjs +89 -0
  46. package/dist/{launch-verify-2DBRizjo.mjs → launch-verify-lTnWZikR.mjs} +2 -2
  47. package/dist/{logs-CHEVK8zD.mjs → logs-DUoKlDoS.mjs} +36 -23
  48. package/dist/{client-BTY686R6.mjs → machines-C2XiNwh9.mjs} +278 -20
  49. package/dist/{macos-CSYEAFmF.mjs → macos-32po3kUq.mjs} +14 -14
  50. package/dist/macos-run.mjs +1 -1
  51. package/dist/maintenance-run.mjs +2 -2
  52. package/dist/{metro-BeK1ZPz1.mjs → metro-g8NqiJp-.mjs} +1 -1
  53. package/dist/{metro-gateway-BJ-rxQAB.mjs → metro-gateway-DmslUM30.mjs} +22 -3
  54. package/dist/{named-ports-Bk_YXEmE.mjs → named-ports-C8m8EQs2.mjs} +2 -2
  55. package/dist/{native-run-NaBrRFtQ.mjs → native-run-D1Q56rAD.mjs} +17 -6
  56. package/dist/{native-runtime-DBzRnEKS.mjs → native-runtime-DhJI3IVQ.mjs} +6 -6
  57. package/dist/offload-worker.mjs +8 -8
  58. package/dist/{teardown-CL6vDs2Y.mjs → ownership-BMLqyBgJ.mjs} +603 -26
  59. package/dist/{ownership-DiA16AsN.mjs → ownership-DyRQru0q.mjs} +2 -2
  60. package/dist/{ports-R2k4Knz3.mjs → ports-4nYqxPWC.mjs} +2 -2
  61. package/dist/{prebuild-D2fsca_Z.mjs → prebuild-D7RUMkwN.mjs} +3 -3
  62. package/dist/{preview-DhVbvPAZ.mjs → preview-CxPRIEht.mjs} +7 -7
  63. package/dist/{project-Du91Ilvc.mjs → project-D47Rh0ly.mjs} +7 -2
  64. package/dist/{recordings-BIXOzVnr.mjs → recordings-BNgsD1Tr.mjs} +1 -1
  65. package/dist/{reload-rM9jV1fz.mjs → reload-qZei6iWn.mjs} +62 -35
  66. package/dist/{remote-cache-C8-V13XN.mjs → remote-cache-Ci4DQYYj.mjs} +3 -3
  67. package/dist/{run-f4vI6EnK.mjs → run-CkE9y0nc.mjs} +1 -1
  68. package/dist/{server-bare-CpPGmnmO.mjs → server-bare-BkrSxSsZ.mjs} +1 -1
  69. package/dist/{server-expo-MC0rHlOC.mjs → server-expo-CHTyXD0G.mjs} +2 -2
  70. package/dist/server-expo-CeakhI8P.mjs +2 -0
  71. package/dist/{settings-oTSkkNrM.mjs → settings-Cfd-0r2d.mjs} +10 -9
  72. package/dist/{settings-ZkDzkzvB.mjs → settings-SR9OmW9h.mjs} +3 -3
  73. package/dist/settings.schema.json +42 -13
  74. package/dist/{simslim-B8-7AoTI.mjs → simslim-BWNrc3M3.mjs} +3 -3
  75. package/dist/{slot-launch-B8VrHxn1.mjs → slot-launch-DmjBmpQV.mjs} +8 -8
  76. package/dist/{start-CX8Ig-U2.mjs → start-BopZcr4n.mjs} +1 -1
  77. package/dist/{start-ClDfwXa4.mjs → start-uee1wWjY.mjs} +11 -11
  78. package/dist/{state-DcW6vOd_.mjs → state-BKa8wNN5.mjs} +1 -1
  79. package/dist/{state-3DwSQIBK.mjs → state-DyaNTgfR.mjs} +1 -1
  80. package/dist/{state-C1jB7Q8q.mjs → state-PeQiJuIu.mjs} +2 -2
  81. package/dist/{stats-CEZ_i5je.mjs → stats-Bq4fJOdB.mjs} +7 -4
  82. package/dist/{status-BuvBFeII.mjs → status-CzS4bPO9.mjs} +4 -4
  83. package/dist/{status-Cs7VLg9D.mjs → status-dXX2JX1C.mjs} +125 -35
  84. package/dist/{stop-Dt_dM16A.mjs → stop-BCwvvs7F.mjs} +2 -2
  85. package/dist/{stop-Bffozsac.mjs → stop-BjizrQwv.mjs} +48 -27
  86. package/dist/{stop-B0Yxtmiy.mjs → stop-Cr1d8DSE.mjs} +4 -4
  87. package/dist/{stop-cause-CidxIqup.mjs → stop-cause-rhmGfwvt.mjs} +1 -1
  88. package/dist/supervisor-run.d.mts +7 -4
  89. package/dist/supervisor-run.mjs +26 -28
  90. package/dist/{support-D4aYJniO.mjs → support-BJPgYCu-.mjs} +44 -19
  91. package/dist/{support-CAP8qDUF.mjs → support-F9-sr0Oi.mjs} +6 -6
  92. package/dist/{toolchain-DkUflduf.mjs → toolchain-CNIBTIOv.mjs} +7 -7
  93. package/dist/{warm-progress-CpGv3Wb6.mjs → warm-progress-DF4JRvYI.mjs} +3 -3
  94. package/dist/watchman-APYjXSQa.mjs +48 -0
  95. package/dist/{web-DWUaMp8P.mjs → web-exQtGkoc.mjs} +12 -13
  96. package/dist/web-run.mjs +4 -4
  97. package/dist/{workspace-process-lock-BL7Q0HCO.mjs → workspace-process-lock-2mNzOlaB.mjs} +3 -2
  98. package/dist/{workspace-state-B8-aJ1_5.mjs → workspace-state-DqcCbpE7.mjs} +2 -2
  99. package/dist/{workspaces-BV4pdHFz.mjs → workspaces-Bcib97-6.mjs} +3 -3
  100. package/dist/{worktree-jnG8Iph5.mjs → worktree-Dc98cE6_.mjs} +1 -1
  101. package/dist/{worktree-K-xj9Db0.mjs → worktree-ZpZR3S1k.mjs} +141 -40
  102. package/package.json +5 -5
  103. package/dist/device-capacity-BHwxNBlu.mjs +0 -136
  104. package/dist/ios-state-DS5UpYTH.mjs +0 -48
  105. package/dist/machines-BIBUdhpj.mjs +0 -265
  106. package/dist/ownership-hCkPzGmr.mjs +0 -571
  107. package/dist/server-expo-CVqO2c35.mjs +0 -2
@@ -1,136 +0,0 @@
1
- import { H as projectDeviceSlots, _ as loadConfig, z as deviceSlotPlatforms } from "./config-D0DbpZtM.mjs";
2
- import { O as listAdbDevices } from "./android-B_NumVyQ.mjs";
3
- import { _ as parseRuntimeVersion, c as iosRuntimeMatches, u as listAllIosSims } from "./ios-BaInn3sW.mjs";
4
- //#region src/engine/device-capacity.ts
5
- function liveOwnedDeviceCount({ sims = [], adbEmulators = [], config = null } = {}) {
6
- let count = 0;
7
- for (const sim of sims) if (sim?.state === "Booted" && sim.name?.startsWith("stim-")) count++;
8
- const livePorts = new Set(adbEmulators.map((e) => e.consolePort));
9
- for (const proj of Object.values(config?.projects || {})) for (const { platforms } of projectDeviceSlots(proj)) {
10
- const android = platforms.android;
11
- if (android?.owned && android.avdName && typeof android.consolePort === "number" && livePorts.has(android.consolePort)) count++;
12
- }
13
- return count;
14
- }
15
- function workspaceHasLiveDevice({ platform, project, slot = "default", sims = [], adbEmulators = [] } = {}) {
16
- if (!platform) return false;
17
- const record = deviceSlotPlatforms(project, slot)?.[platform];
18
- if (!record) return false;
19
- if (platform === "ios") return sims.some((s) => s.udid === record.deviceUdid && s.state === "Booted");
20
- return typeof record.consolePort === "number" && adbEmulators.some((e) => e.consolePort === record.consolePort);
21
- }
22
- function deviceCapacityRefusal({ platform, project, slot = "default", max, sims = [], adb = null, config = null } = {}) {
23
- if (!max || max <= 0) return null;
24
- const adbEmulators = adb?.emulators || [];
25
- if (workspaceHasLiveDevice({
26
- platform,
27
- project,
28
- slot,
29
- sims,
30
- adbEmulators
31
- })) return null;
32
- const count = liveOwnedDeviceCount({
33
- sims,
34
- adbEmulators,
35
- config
36
- });
37
- if (count < max) return null;
38
- return {
39
- code: "STIM_AT_CAPACITY",
40
- message: `${count} Stim device(s) are already booted and concurrency.maxDevices is ${max}, so booting another would exceed the cap.`,
41
- remedy: "stop an environment (stim stop) or raise concurrency.maxDevices"
42
- };
43
- }
44
- function checkDeviceCapacity({ platform, project, slot = "default", max, sims = listAllIosSims, adb = listAdbDevices, config = loadConfig } = {}) {
45
- if (!max || max <= 0) return null;
46
- let simList = [];
47
- let adbRes = {
48
- emulators: [],
49
- physical: [],
50
- unhealthy: []
51
- };
52
- try {
53
- simList = typeof sims === "function" ? sims() || [] : sims || [];
54
- } catch {}
55
- try {
56
- adbRes = typeof adb === "function" ? adb() || adbRes : adb || adbRes;
57
- } catch {}
58
- let cfg = null;
59
- try {
60
- cfg = typeof config === "function" ? config() : config ?? null;
61
- } catch {}
62
- return deviceCapacityRefusal({
63
- platform,
64
- project,
65
- slot,
66
- max,
67
- sims: simList,
68
- adb: adbRes,
69
- config: cfg
70
- });
71
- }
72
- function deviceTypeMismatch(recordedTypeId, requestedName, deviceTypes) {
73
- if (!requestedName || !recordedTypeId) return null;
74
- const wanted = (deviceTypes || []).find((d) => d.name === requestedName);
75
- if (!wanted) return null;
76
- if (wanted.identifier === recordedTypeId) return null;
77
- const recorded = (deviceTypes || []).find((d) => d.identifier === recordedTypeId);
78
- return `this project's sim is ${recorded ? recorded.name : recordedTypeId}, but --device-type asked for ${requestedName}`;
79
- }
80
- function runtimeMismatch(recordedRuntimeId, requested, runtimes) {
81
- if (!requested || !recordedRuntimeId) return null;
82
- const wanted = runtimes.find((r) => iosRuntimeMatches(r, requested));
83
- if (!wanted || wanted.identifier === recordedRuntimeId) return null;
84
- return `this project's sim runs iOS ${parseRuntimeVersion(recordedRuntimeId)}, but --runtime asked for ${requested}`;
85
- }
86
- function installedNames(names) {
87
- const unique = [...new Set(names.filter((n) => typeof n === "string" && n !== ""))];
88
- return unique.length > 0 ? unique.join(", ") : "none";
89
- }
90
- /** Appends which settings layer supplied an unresolved device selector, so the user knows where to fix it. */
91
- function layerNote(refusal, key, flag, origin) {
92
- if (flag !== void 0 && flag !== null) return refusal;
93
- if (!origin) return refusal;
94
- return {
95
- message: `${refusal.message} ${key} is set at the ${origin} layer.`,
96
- remedy: `${refusal.remedy} Fix it with \`stim settings set ${key} <value> --scope ${origin}\` or \`stim settings unset ${key} --scope ${origin}\`.`
97
- };
98
- }
99
- function unknownIosRuntimeRefusal(requested, runtimes) {
100
- if (!requested) return null;
101
- if ((runtimes || []).some((r) => iosRuntimeMatches(r, requested))) return null;
102
- return {
103
- message: `No installed simulator runtime matches "${requested}". Installed runtimes: ${installedNames((runtimes || []).map((r) => r.version))}.`,
104
- remedy: "Pass `--runtime` (or set ios.runtime) a version printed above (\"26.5\") or a runtime's full name (\"iOS 26.5\"); nothing else matches. Install more runtimes through Xcode."
105
- };
106
- }
107
- function creatableIosDeviceTypeNames(runtimes, runtime) {
108
- return (runtime ? (runtimes || []).filter((r) => iosRuntimeMatches(r, runtime)) : runtimes || []).flatMap((r) => (r.supportedDeviceTypes || []).map((d) => d.name));
109
- }
110
- function unknownIosDeviceTypeRefusal(requested, runtimes, runtime) {
111
- if (!requested) return null;
112
- const creatable = creatableIosDeviceTypeNames(runtimes, runtime);
113
- if (creatable.includes(requested)) return null;
114
- return {
115
- message: `No device type named "${requested}" can be created on ${runtime ? `runtime ${runtime}` : "any installed simulator runtime"}. ${runtime ? `Device types runtime ${runtime} supports` : "Device types the installed runtimes support"}: ${installedNames(creatable)}.`,
116
- remedy: "Pass `--device-type` (or set ios.deviceType) one of the names printed above, exactly as `xcrun simctl list devicetypes` spells it. `xcrun simctl list devicetypes` also lists watchOS, tvOS and visionOS models, which no iOS runtime can create. Install more models through Xcode."
117
- };
118
- }
119
- function unknownAndroidSystemImageRefusal(requested, images) {
120
- if (!requested) return null;
121
- if ((images || []).some((i) => i.pkg === requested)) return null;
122
- return {
123
- message: `No installed Android system image is named "${requested}". Installed system images: ${installedNames((images || []).map((i) => i.pkg))}.`,
124
- remedy: "Pass `--system-image` (or set android.systemImage) to one of the package ids printed above. Install more with `sdkmanager \"system-images;android-36;google_apis;arm64-v8a\"`."
125
- };
126
- }
127
- function unknownAndroidDeviceProfileRefusal(requested, profiles) {
128
- if (!requested) return null;
129
- if (profiles.includes(requested)) return null;
130
- return {
131
- message: `No Android hardware profile is named "${requested}". Profiles avdmanager offers: ${installedNames(profiles)}.`,
132
- remedy: "Pass `--device-profile` (or set android.deviceProfile) to one of the ids printed above, exactly as `avdmanager list device -c` spells it, e.g. \"pixel_fold\" or \"pixel_tablet\"."
133
- };
134
- }
135
- //#endregion
136
- export { runtimeMismatch as a, unknownIosDeviceTypeRefusal as c, liveOwnedDeviceCount as i, unknownIosRuntimeRefusal as l, deviceTypeMismatch as n, unknownAndroidDeviceProfileRefusal as o, layerNote as r, unknownAndroidSystemImageRefusal as s, checkDeviceCapacity as t };
@@ -1,48 +0,0 @@
1
- import { a as updateWorkspaceState, r as readWorkspaceState$1 } from "./workspace-state-B8-aJ1_5.mjs";
2
- import { HOSTED_METRO_REQUESTS_KEY, hostedIosPlacements, hostedIosRecords, hostedMetroRequests, isJsonObject, parseHostedIosPlacement, unreadableHostedIos } from "@stim-cli/core/state";
3
- //#region src/device-host/ios-state.ts
4
- function readHostedIos(root, slot) {
5
- const state = readWorkspaceState$1(root);
6
- if (slot !== void 0) {
7
- const record = hostedIosRecords(state)[slot];
8
- if (record !== void 0 && !parseHostedIosPlacement(record)) throw Object.assign(new Error(unreadableHostedIos(slot)), {
9
- code: "STIM_HOSTING_REFUSED",
10
- remedy: `Restore that slot's recorded machine and session from the host, then run stim stop${slot === "default" ? "" : ` --slot ${slot}`} to reconcile it.`
11
- });
12
- }
13
- const placements = hostedIosPlacements(state);
14
- return slot === void 0 ? placements : placements[slot] ? { [slot]: placements[slot] } : {};
15
- }
16
- function writeHostedIos(root, slot, host) {
17
- updateWorkspaceState(root, (state) => {
18
- const slots = isJsonObject(state.deviceSlots) ? state.deviceSlots : {};
19
- const current = slot === "default" ? state.ios : isJsonObject(slots[slot]) ? slots[slot].ios : void 0;
20
- const record = isJsonObject(current) ? { ...current } : {};
21
- const previous = parseHostedIosPlacement(record.host);
22
- if (previous && previous.session !== host?.session) {
23
- const requests = { ...hostedMetroRequests(state) };
24
- delete requests[previous.session];
25
- state = {
26
- ...state,
27
- [HOSTED_METRO_REQUESTS_KEY]: requests
28
- };
29
- }
30
- if (host) record.host = host;
31
- else delete record.host;
32
- return slot === "default" ? {
33
- ...state,
34
- ios: record
35
- } : {
36
- ...state,
37
- deviceSlots: {
38
- ...slots,
39
- [slot]: {
40
- ...isJsonObject(slots[slot]) ? slots[slot] : {},
41
- ios: record
42
- }
43
- }
44
- };
45
- });
46
- }
47
- //#endregion
48
- export { writeHostedIos as n, readHostedIos as t };
@@ -1,265 +0,0 @@
1
- import { _ as loadConfig$1, d as getConfigDir } from "./config-D0DbpZtM.mjs";
2
- import { w as withDirLock } from "./workspace-state-B8-aJ1_5.mjs";
3
- import { S as readHostPermissions, _ as findPeer$1, b as realIo, g as endpoint, v as parseMachine$1, x as readAccessTicket } from "./client-BTY686R6.mjs";
4
- import { mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
5
- import { acceptsShape, deviceHostMachinesClaims, deviceHostMachinesFile, deviceHostMachinesLock, isJsonObject, readDeviceHostMachines, settingDefinition } from "@stim-cli/core/state";
6
- import { releaseClaim, tryAcquireClaim } from "@stim-cli/core/ownership-claim";
7
- import { hostname } from "node:os";
8
- //#region src/device-host/machines.ts
9
- function note(title, detail, fix = null) {
10
- return {
11
- code: "device-host-machine",
12
- level: "note",
13
- title,
14
- detail,
15
- fix
16
- };
17
- }
18
- const approval = (machine, id) => `A person on ${machine} approves it with \`stim-server devices grant ${id} --device-host\`.`;
19
- function configuredMachines() {
20
- const hosting = loadConfig$1()?.hosting;
21
- if (hosting !== void 0 && !isJsonObject(hosting)) return null;
22
- const machines = hosting?.machines;
23
- if (machines === void 0) return [];
24
- return acceptsShape(settingDefinition("hosting.machines"), machines) ? machines : null;
25
- }
26
- function store(machines) {
27
- withDirLock(deviceHostMachinesLock(), () => {
28
- const file = deviceHostMachinesFile();
29
- const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
30
- try {
31
- writeFileSync(tmp, `${JSON.stringify({
32
- version: 1,
33
- machines
34
- }, null, 2)}\n`, { mode: 384 });
35
- renameSync(tmp, file);
36
- } finally {
37
- rmSync(tmp, { force: true });
38
- }
39
- }, { ensureParent: () => mkdirSync(getConfigDir(), { recursive: true }) });
40
- }
41
- async function request(machine, peer, port, deviceName, io) {
42
- const ticket = readAccessTicket();
43
- const reply = await io.hello(endpoint(peer, port), {
44
- request: "device-host",
45
- deviceName,
46
- ...ticket ? { setupTicket: ticket.ticket } : {}
47
- });
48
- if (!("result" in reply) || reply.result?.approval?.state !== "pending" || typeof reply.result.deviceToken !== "string" || !reply.result.deviceToken || typeof reply.result.device?.id !== "string" || !reply.result.device.id || typeof reply.result.approval.expiresAt !== "string" || !reply.result.approval.expiresAt) return {
49
- report: {
50
- machine,
51
- dnsName: peer.dnsName,
52
- state: "unreachable"
53
- },
54
- finding: note(`Could not ask ${machine} for hosting access`, "The worker did not return a pending request.")
55
- };
56
- const credential = {
57
- machine,
58
- nodeId: peer.nodeId,
59
- dnsName: peer.dnsName,
60
- deviceId: reply.result.device.id,
61
- deviceToken: reply.result.deviceToken,
62
- state: "pending",
63
- requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
64
- ...ticket ? { ticketHash: ticket.ticketHash } : {}
65
- };
66
- return {
67
- report: {
68
- machine,
69
- dnsName: peer.dnsName,
70
- state: "pending",
71
- deviceId: credential.deviceId,
72
- requestedAt: credential.requestedAt
73
- },
74
- finding: note(`Asked ${machine} for hosting access`, `The request lapses at ${reply.result.approval.expiresAt}.`, approval(machine, credential.deviceId)),
75
- credential
76
- };
77
- }
78
- /** Reports separately approved hosting machines. Only --fix asks or forgets credentials; every token stays pinned. */
79
- async function inspectDeviceHostMachines({ fix }, io = realIo, entries = configuredMachines()) {
80
- if (entries === null) return {
81
- findings: [note("Invalid hosting.machines setting", "Use a hosting object with machines as an array of tailnet names. Existing hosting credentials and pinned nodes are preserved.", "Run `stim guide settings` and correct the setting before running doctor again.")],
82
- machines: []
83
- };
84
- const unavailable = (state, finding) => ({
85
- findings: [finding],
86
- machines: entries.map((machine) => ({
87
- machine,
88
- state
89
- }))
90
- });
91
- const unreadable = () => unavailable("credentials-unavailable", note("Hosting credentials are unreadable", `Stim preserves ${deviceHostMachinesFile()} and its pinned nodes.`, "Restore the existing file from a trusted backup or restore read access before running doctor again."));
92
- if (!entries.length) {
93
- if (!fix) return {
94
- findings: [],
95
- machines: []
96
- };
97
- try {
98
- if (!readDeviceHostMachines().length) return {
99
- findings: [],
100
- machines: []
101
- };
102
- } catch {
103
- return unreadable();
104
- }
105
- }
106
- const claim = tryAcquireClaim({
107
- root: deviceHostMachinesClaims(),
108
- mode: "exclusive",
109
- label: "hosting machine approval"
110
- });
111
- if (!claim.acquired) {
112
- releaseClaim(claim.pending);
113
- return unavailable("busy", note("Hosting machine approval is in use", "Another doctor run holds the hosting credential claim. Run doctor again after it finishes."));
114
- }
115
- try {
116
- let credentials;
117
- try {
118
- credentials = readDeviceHostMachines();
119
- } catch {
120
- return unreadable();
121
- }
122
- if (fix) {
123
- const kept = credentials.filter((each) => entries.includes(each.machine));
124
- if (kept.length !== credentials.length) {
125
- store(kept);
126
- credentials = kept;
127
- }
128
- }
129
- if (!entries.length) return {
130
- findings: [],
131
- machines: []
132
- };
133
- const status = io.status();
134
- if (!isJsonObject(status)) return unavailable("tailscale-off", note("Hosting machines are unreachable", "hosting.machines names hosting machines, but Tailscale is not running."));
135
- const self = isJsonObject(status.Self) ? status.Self : {};
136
- const deviceName = (typeof self.HostName === "string" && self.HostName ? self.HostName : hostname()).replace(/[\p{Cc}\p{Cf}]/gu, "").trim().slice(0, 64) || "Mac";
137
- const inspected = {
138
- findings: [],
139
- machines: []
140
- };
141
- const namedNodes = /* @__PURE__ */ new Set();
142
- for (const machine of new Set(entries)) {
143
- const parsed = parseMachine$1(machine);
144
- if (!parsed) {
145
- inspected.machines.push({
146
- machine,
147
- state: "invalid"
148
- });
149
- inspected.findings.push(note(`Hosting machine ${machine} is not a tailnet name`, "Expected `name` or `name:port`."));
150
- continue;
151
- }
152
- const current = io.status();
153
- if (!isJsonObject(current)) {
154
- inspected.machines.push({
155
- machine,
156
- state: "tailscale-off"
157
- });
158
- inspected.findings.push(note(`Hosting machine ${machine} is unreachable`, "Tailscale stopped before this connection."));
159
- continue;
160
- }
161
- const peer = findPeer$1(current, parsed.name);
162
- if (peer === "missing" || peer === "ambiguous") {
163
- inspected.machines.push({
164
- machine,
165
- state: "not-on-tailnet"
166
- });
167
- inspected.findings.push(note(`Hosting machine ${machine} is not on this tailnet`, peer === "missing" ? "No peer has that name." : "Several peers match that name."));
168
- continue;
169
- }
170
- const nodeRoute = `${peer.nodeId}:${parsed.port}`;
171
- if (namedNodes.has(nodeRoute)) {
172
- inspected.machines.push({
173
- machine,
174
- dnsName: peer.dnsName,
175
- state: "invalid"
176
- });
177
- inspected.findings.push(note(`Hosting machine ${machine} repeats a named node`, "Name each tailnet node and serve port only once in hosting.machines."));
178
- continue;
179
- }
180
- namedNodes.add(nodeRoute);
181
- const credential = credentials.find((each) => each.machine === machine);
182
- const known = {
183
- machine,
184
- dnsName: peer.dnsName,
185
- ...credential ? {
186
- deviceId: credential.deviceId,
187
- requestedAt: credential.requestedAt
188
- } : {}
189
- };
190
- if (credential && credential.nodeId !== peer.nodeId) {
191
- inspected.machines.push({
192
- ...known,
193
- state: "node-changed"
194
- });
195
- inspected.findings.push(note(`Hosting machine ${machine} is a different tailnet node`, "Stim refuses to connect or send the saved token.", `If that Mac was replaced, remove ${machine} from hosting.machines, run \`stim doctor --fix\`, then add it back and run \`stim doctor --fix\` again to request approval on the new node.`));
196
- continue;
197
- }
198
- let ask = !credential && fix;
199
- if (credential) {
200
- const reply = await io.hello(endpoint(peer, parsed.port), { deviceToken: credential.deviceToken });
201
- if ("result" in reply && reply.result?.device?.id === credential.deviceId && Array.isArray(reply.result.capabilities) && reply.result.capabilities.includes("device-host")) {
202
- if (credential.state !== "approved") {
203
- credentials = credentials.map((each) => each.machine === machine ? {
204
- ...each,
205
- state: "approved"
206
- } : each);
207
- store(credentials);
208
- }
209
- const host = readHostPermissions(reply.result.host);
210
- if (host && (host.screenRecording === false || host.accessibility === false)) {
211
- const panes = [...host.screenRecording === false ? ["Screen & System Audio Recording (Screen Recording on macOS 14)"] : [], ...host.accessibility === false ? ["Device Control and Data Access (Accessibility on macOS 26 and earlier)"] : []];
212
- const detail = [...host.screenRecording === false ? ["Viewing hosted macOS apps needs Screen Recording."] : [], ...host.accessibility === false ? ["Controlling them needs the control permission."] : []];
213
- inspected.findings.push(note(`Hosting machine ${machine} needs ${panes.join(" and ")} for ${host.name}`, detail.join(" "), `On ${machine}, approve ${host.name} in System Settings > Privacy & Security, or run \`stim-server service install\` there again to show the requests.`));
214
- }
215
- inspected.machines.push({
216
- ...known,
217
- state: "approved",
218
- ...host ? { host } : {}
219
- });
220
- continue;
221
- }
222
- const pending = "error" in reply && reply.error?.code === "approval-pending";
223
- const revoked = "error" in reply && reply.error?.code === "unauthorized";
224
- const ticket = fix && pending && credential.state !== "approved" ? readAccessTicket() : void 0;
225
- ask = fix && (revoked || !!ticket && credential.ticketHash !== ticket.ticketHash);
226
- if (pending && !ask) {
227
- inspected.machines.push({
228
- ...known,
229
- state: "pending"
230
- });
231
- inspected.findings.push(note(`Hosting machine ${machine} has not approved this Mac yet`, `Requested at ${credential.requestedAt}.`, approval(machine, credential.deviceId)));
232
- continue;
233
- }
234
- if (!ask) {
235
- inspected.machines.push({
236
- ...known,
237
- state: revoked ? "revoked" : "unreachable"
238
- });
239
- inspected.findings.push(note(`Hosting machine ${machine} ${revoked ? "no longer accepts this Mac" : "did not confirm hosting access"}`, "The saved token and pinned node are preserved.", revoked ? "Run `stim doctor --fix` to ask again." : `Check stim-server and its tailnet serve route on ${machine}, then run doctor again.`));
240
- continue;
241
- }
242
- }
243
- if (ask) {
244
- const result = await request(machine, peer, parsed.port, deviceName, io);
245
- if (result.credential) {
246
- credentials = [...credentials.filter((each) => each.machine !== machine), result.credential];
247
- store(credentials);
248
- }
249
- inspected.machines.push(result.report);
250
- inspected.findings.push(result.finding);
251
- } else {
252
- inspected.machines.push({
253
- ...known,
254
- state: "not-asked"
255
- });
256
- inspected.findings.push(note(`Hosting machine ${machine} has not approved this Mac`, "This Mac has not asked for hosting access.", "Run `stim doctor --fix` to ask."));
257
- }
258
- }
259
- return inspected;
260
- } finally {
261
- releaseClaim(claim.acquired);
262
- }
263
- }
264
- //#endregion
265
- export { inspectDeviceHostMachines as n, configuredMachines as t };