@murphai/murph 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (301) hide show
  1. package/CHANGELOG.md +2009 -0
  2. package/LICENSE +674 -0
  3. package/README.md +97 -0
  4. package/dist/.tsbuildinfo +1 -0
  5. package/dist/assistant/automation/run-loop.d.ts +21 -0
  6. package/dist/assistant/automation/run-loop.d.ts.map +1 -0
  7. package/dist/assistant/automation/run-loop.js +31 -0
  8. package/dist/assistant/automation/run-loop.js.map +1 -0
  9. package/dist/assistant/automation.d.ts +10 -0
  10. package/dist/assistant/automation.d.ts.map +1 -0
  11. package/dist/assistant/automation.js +5 -0
  12. package/dist/assistant/automation.js.map +1 -0
  13. package/dist/assistant/cron.d.ts +19 -0
  14. package/dist/assistant/cron.d.ts.map +1 -0
  15. package/dist/assistant/cron.js +59 -0
  16. package/dist/assistant/cron.js.map +1 -0
  17. package/dist/assistant/doctor-security.d.ts +15 -0
  18. package/dist/assistant/doctor-security.d.ts.map +1 -0
  19. package/dist/assistant/doctor-security.js +172 -0
  20. package/dist/assistant/doctor-security.js.map +1 -0
  21. package/dist/assistant/doctor.d.ts +5 -0
  22. package/dist/assistant/doctor.d.ts.map +1 -0
  23. package/dist/assistant/doctor.js +527 -0
  24. package/dist/assistant/doctor.js.map +1 -0
  25. package/dist/assistant/outbox.d.ts +19 -0
  26. package/dist/assistant/outbox.d.ts.map +1 -0
  27. package/dist/assistant/outbox.js +28 -0
  28. package/dist/assistant/outbox.js.map +1 -0
  29. package/dist/assistant/provider-catalog.d.ts +61 -0
  30. package/dist/assistant/provider-catalog.d.ts.map +1 -0
  31. package/dist/assistant/provider-catalog.js +205 -0
  32. package/dist/assistant/provider-catalog.js.map +1 -0
  33. package/dist/assistant/service.d.ts +85 -0
  34. package/dist/assistant/service.d.ts.map +1 -0
  35. package/dist/assistant/service.js +26 -0
  36. package/dist/assistant/service.js.map +1 -0
  37. package/dist/assistant/status.d.ts +9 -0
  38. package/dist/assistant/status.d.ts.map +1 -0
  39. package/dist/assistant/status.js +16 -0
  40. package/dist/assistant/status.js.map +1 -0
  41. package/dist/assistant/stop.d.ts +20 -0
  42. package/dist/assistant/stop.d.ts.map +1 -0
  43. package/dist/assistant/stop.js +142 -0
  44. package/dist/assistant/stop.js.map +1 -0
  45. package/dist/assistant/store.d.ts +6 -0
  46. package/dist/assistant/store.d.ts.map +1 -0
  47. package/dist/assistant/store.js +21 -0
  48. package/dist/assistant/store.js.map +1 -0
  49. package/dist/assistant/ui/ink.d.ts +247 -0
  50. package/dist/assistant/ui/ink.d.ts.map +1 -0
  51. package/dist/assistant/ui/ink.js +2417 -0
  52. package/dist/assistant/ui/ink.js.map +1 -0
  53. package/dist/assistant/ui/theme.d.ts +64 -0
  54. package/dist/assistant/ui/theme.d.ts.map +1 -0
  55. package/dist/assistant/ui/theme.js +180 -0
  56. package/dist/assistant/ui/theme.js.map +1 -0
  57. package/dist/assistant/ui/view-model.d.ts +89 -0
  58. package/dist/assistant/ui/view-model.d.ts.map +1 -0
  59. package/dist/assistant/ui/view-model.js +298 -0
  60. package/dist/assistant/ui/view-model.js.map +1 -0
  61. package/dist/assistant-chat-ink.d.ts +2 -0
  62. package/dist/assistant-chat-ink.d.ts.map +1 -0
  63. package/dist/assistant-chat-ink.js +2 -0
  64. package/dist/assistant-chat-ink.js.map +1 -0
  65. package/dist/assistant-daemon-client.d.ts +81 -0
  66. package/dist/assistant-daemon-client.d.ts.map +1 -0
  67. package/dist/assistant-daemon-client.js +473 -0
  68. package/dist/assistant-daemon-client.js.map +1 -0
  69. package/dist/assistant-runtime.d.ts +25 -0
  70. package/dist/assistant-runtime.d.ts.map +1 -0
  71. package/dist/assistant-runtime.js +17 -0
  72. package/dist/assistant-runtime.js.map +1 -0
  73. package/dist/bin.d.ts +3 -0
  74. package/dist/bin.d.ts.map +1 -0
  75. package/dist/bin.js +7 -0
  76. package/dist/bin.js.map +1 -0
  77. package/dist/cli-entry.d.ts +10 -0
  78. package/dist/cli-entry.d.ts.map +1 -0
  79. package/dist/cli-entry.js +127 -0
  80. package/dist/cli-entry.js.map +1 -0
  81. package/dist/commands/assistant.d.ts +5 -0
  82. package/dist/commands/assistant.d.ts.map +1 -0
  83. package/dist/commands/assistant.js +1663 -0
  84. package/dist/commands/assistant.js.map +1 -0
  85. package/dist/commands/audit-command-helpers.d.ts +15 -0
  86. package/dist/commands/audit-command-helpers.d.ts.map +1 -0
  87. package/dist/commands/audit-command-helpers.js +24 -0
  88. package/dist/commands/audit-command-helpers.js.map +1 -0
  89. package/dist/commands/audit.d.ts +4 -0
  90. package/dist/commands/audit.d.ts.map +1 -0
  91. package/dist/commands/audit.js +107 -0
  92. package/dist/commands/audit.js.map +1 -0
  93. package/dist/commands/device.d.ts +4 -0
  94. package/dist/commands/device.d.ts.map +1 -0
  95. package/dist/commands/device.js +177 -0
  96. package/dist/commands/device.js.map +1 -0
  97. package/dist/commands/document.d.ts +4 -0
  98. package/dist/commands/document.d.ts.map +1 -0
  99. package/dist/commands/document.js +117 -0
  100. package/dist/commands/document.js.map +1 -0
  101. package/dist/commands/event.d.ts +4 -0
  102. package/dist/commands/event.d.ts.map +1 -0
  103. package/dist/commands/event.js +136 -0
  104. package/dist/commands/event.js.map +1 -0
  105. package/dist/commands/experiment.d.ts +4 -0
  106. package/dist/commands/experiment.d.ts.map +1 -0
  107. package/dist/commands/experiment.js +140 -0
  108. package/dist/commands/experiment.js.map +1 -0
  109. package/dist/commands/export-intake-read-helpers.d.ts +150 -0
  110. package/dist/commands/export-intake-read-helpers.d.ts.map +1 -0
  111. package/dist/commands/export-intake-read-helpers.js +328 -0
  112. package/dist/commands/export-intake-read-helpers.js.map +1 -0
  113. package/dist/commands/export.d.ts +4 -0
  114. package/dist/commands/export.d.ts.map +1 -0
  115. package/dist/commands/export.js +179 -0
  116. package/dist/commands/export.js.map +1 -0
  117. package/dist/commands/food.d.ts +4 -0
  118. package/dist/commands/food.d.ts.map +1 -0
  119. package/dist/commands/food.js +190 -0
  120. package/dist/commands/food.js.map +1 -0
  121. package/dist/commands/health-command-factory.d.ts +230 -0
  122. package/dist/commands/health-command-factory.d.ts.map +1 -0
  123. package/dist/commands/health-command-factory.js +551 -0
  124. package/dist/commands/health-command-factory.js.map +1 -0
  125. package/dist/commands/health-entity-command-registry.d.ts +27 -0
  126. package/dist/commands/health-entity-command-registry.d.ts.map +1 -0
  127. package/dist/commands/health-entity-command-registry.js +84 -0
  128. package/dist/commands/health-entity-command-registry.js.map +1 -0
  129. package/dist/commands/inbox.d.ts +5 -0
  130. package/dist/commands/inbox.d.ts.map +1 -0
  131. package/dist/commands/inbox.js +841 -0
  132. package/dist/commands/inbox.js.map +1 -0
  133. package/dist/commands/intake.d.ts +4 -0
  134. package/dist/commands/intake.d.ts.map +1 -0
  135. package/dist/commands/intake.js +175 -0
  136. package/dist/commands/intake.js.map +1 -0
  137. package/dist/commands/intervention.d.ts +4 -0
  138. package/dist/commands/intervention.d.ts.map +1 -0
  139. package/dist/commands/intervention.js +122 -0
  140. package/dist/commands/intervention.js.map +1 -0
  141. package/dist/commands/journal.d.ts +12 -0
  142. package/dist/commands/journal.d.ts.map +1 -0
  143. package/dist/commands/journal.js +186 -0
  144. package/dist/commands/journal.js.map +1 -0
  145. package/dist/commands/meal.d.ts +4 -0
  146. package/dist/commands/meal.d.ts.map +1 -0
  147. package/dist/commands/meal.js +123 -0
  148. package/dist/commands/meal.js.map +1 -0
  149. package/dist/commands/profile.d.ts +4 -0
  150. package/dist/commands/profile.d.ts.map +1 -0
  151. package/dist/commands/profile.js +62 -0
  152. package/dist/commands/profile.js.map +1 -0
  153. package/dist/commands/protocol.d.ts +4 -0
  154. package/dist/commands/protocol.d.ts.map +1 -0
  155. package/dist/commands/protocol.js +79 -0
  156. package/dist/commands/protocol.js.map +1 -0
  157. package/dist/commands/provider.d.ts +4 -0
  158. package/dist/commands/provider.d.ts.map +1 -0
  159. package/dist/commands/provider.js +115 -0
  160. package/dist/commands/provider.js.map +1 -0
  161. package/dist/commands/read.d.ts +4 -0
  162. package/dist/commands/read.d.ts.map +1 -0
  163. package/dist/commands/read.js +55 -0
  164. package/dist/commands/read.js.map +1 -0
  165. package/dist/commands/recipe.d.ts +4 -0
  166. package/dist/commands/recipe.d.ts.map +1 -0
  167. package/dist/commands/recipe.js +116 -0
  168. package/dist/commands/recipe.js.map +1 -0
  169. package/dist/commands/record-mutation-command-helpers.d.ts +196 -0
  170. package/dist/commands/record-mutation-command-helpers.d.ts.map +1 -0
  171. package/dist/commands/record-mutation-command-helpers.js +150 -0
  172. package/dist/commands/record-mutation-command-helpers.js.map +1 -0
  173. package/dist/commands/research.d.ts +3 -0
  174. package/dist/commands/research.d.ts.map +1 -0
  175. package/dist/commands/research.js +104 -0
  176. package/dist/commands/research.js.map +1 -0
  177. package/dist/commands/sample-batch-command-helpers.d.ts +24 -0
  178. package/dist/commands/sample-batch-command-helpers.d.ts.map +1 -0
  179. package/dist/commands/sample-batch-command-helpers.js +99 -0
  180. package/dist/commands/sample-batch-command-helpers.js.map +1 -0
  181. package/dist/commands/sample-import-command-helpers.d.ts +24 -0
  182. package/dist/commands/sample-import-command-helpers.d.ts.map +1 -0
  183. package/dist/commands/sample-import-command-helpers.js +49 -0
  184. package/dist/commands/sample-import-command-helpers.js.map +1 -0
  185. package/dist/commands/sample-query-command-helpers.d.ts +11 -0
  186. package/dist/commands/sample-query-command-helpers.d.ts.map +1 -0
  187. package/dist/commands/sample-query-command-helpers.js +26 -0
  188. package/dist/commands/sample-query-command-helpers.js.map +1 -0
  189. package/dist/commands/samples.d.ts +4 -0
  190. package/dist/commands/samples.d.ts.map +1 -0
  191. package/dist/commands/samples.js +261 -0
  192. package/dist/commands/samples.js.map +1 -0
  193. package/dist/commands/search.d.ts +4 -0
  194. package/dist/commands/search.d.ts.map +1 -0
  195. package/dist/commands/search.js +295 -0
  196. package/dist/commands/search.js.map +1 -0
  197. package/dist/commands/supplement.d.ts +4 -0
  198. package/dist/commands/supplement.d.ts.map +1 -0
  199. package/dist/commands/supplement.js +338 -0
  200. package/dist/commands/supplement.js.map +1 -0
  201. package/dist/commands/vault.d.ts +4 -0
  202. package/dist/commands/vault.d.ts.map +1 -0
  203. package/dist/commands/vault.js +164 -0
  204. package/dist/commands/vault.js.map +1 -0
  205. package/dist/commands/workout.d.ts +4 -0
  206. package/dist/commands/workout.d.ts.map +1 -0
  207. package/dist/commands/workout.js +284 -0
  208. package/dist/commands/workout.js.map +1 -0
  209. package/dist/incur.generated.d.ts +2164 -0
  210. package/dist/incur.generated.d.ts.map +1 -0
  211. package/dist/incur.generated.js +2 -0
  212. package/dist/incur.generated.js.map +1 -0
  213. package/dist/index.d.ts +13 -0
  214. package/dist/index.d.ts.map +1 -0
  215. package/dist/index.js +14 -0
  216. package/dist/index.js.map +1 -0
  217. package/dist/research-cli-contracts.d.ts +22 -0
  218. package/dist/research-cli-contracts.d.ts.map +1 -0
  219. package/dist/research-cli-contracts.js +18 -0
  220. package/dist/research-cli-contracts.js.map +1 -0
  221. package/dist/research-runtime.d.ts +79 -0
  222. package/dist/research-runtime.d.ts.map +1 -0
  223. package/dist/research-runtime.js +351 -0
  224. package/dist/research-runtime.js.map +1 -0
  225. package/dist/run-terminal-logging.d.ts +12 -0
  226. package/dist/run-terminal-logging.d.ts.map +1 -0
  227. package/dist/run-terminal-logging.js +323 -0
  228. package/dist/run-terminal-logging.js.map +1 -0
  229. package/dist/setup-agentmail.d.ts +30 -0
  230. package/dist/setup-agentmail.d.ts.map +1 -0
  231. package/dist/setup-agentmail.js +136 -0
  232. package/dist/setup-agentmail.js.map +1 -0
  233. package/dist/setup-assistant-account.d.ts +29 -0
  234. package/dist/setup-assistant-account.d.ts.map +1 -0
  235. package/dist/setup-assistant-account.js +443 -0
  236. package/dist/setup-assistant-account.js.map +1 -0
  237. package/dist/setup-assistant.d.ts +34 -0
  238. package/dist/setup-assistant.d.ts.map +1 -0
  239. package/dist/setup-assistant.js +355 -0
  240. package/dist/setup-assistant.js.map +1 -0
  241. package/dist/setup-cli.d.ts +72 -0
  242. package/dist/setup-cli.d.ts.map +1 -0
  243. package/dist/setup-cli.js +387 -0
  244. package/dist/setup-cli.js.map +1 -0
  245. package/dist/setup-services/channels.d.ts +19 -0
  246. package/dist/setup-services/channels.d.ts.map +1 -0
  247. package/dist/setup-services/channels.js +721 -0
  248. package/dist/setup-services/channels.js.map +1 -0
  249. package/dist/setup-services/process.d.ts +18 -0
  250. package/dist/setup-services/process.d.ts.map +1 -0
  251. package/dist/setup-services/process.js +98 -0
  252. package/dist/setup-services/process.js.map +1 -0
  253. package/dist/setup-services/scheduled-updates.d.ts +9 -0
  254. package/dist/setup-services/scheduled-updates.d.ts.map +1 -0
  255. package/dist/setup-services/scheduled-updates.js +64 -0
  256. package/dist/setup-services/scheduled-updates.js.map +1 -0
  257. package/dist/setup-services/shell.d.ts +18 -0
  258. package/dist/setup-services/shell.d.ts.map +1 -0
  259. package/dist/setup-services/shell.js +447 -0
  260. package/dist/setup-services/shell.js.map +1 -0
  261. package/dist/setup-services/steps.d.ts +39 -0
  262. package/dist/setup-services/steps.d.ts.map +1 -0
  263. package/dist/setup-services/steps.js +86 -0
  264. package/dist/setup-services/steps.js.map +1 -0
  265. package/dist/setup-services/toolchain.d.ts +46 -0
  266. package/dist/setup-services/toolchain.d.ts.map +1 -0
  267. package/dist/setup-services/toolchain.js +232 -0
  268. package/dist/setup-services/toolchain.js.map +1 -0
  269. package/dist/setup-services.d.ts +44 -0
  270. package/dist/setup-services.d.ts.map +1 -0
  271. package/dist/setup-services.js +739 -0
  272. package/dist/setup-services.js.map +1 -0
  273. package/dist/setup-wizard.d.ts +101 -0
  274. package/dist/setup-wizard.d.ts.map +1 -0
  275. package/dist/setup-wizard.js +1458 -0
  276. package/dist/setup-wizard.js.map +1 -0
  277. package/dist/usecases/intervention.d.ts +63 -0
  278. package/dist/usecases/intervention.d.ts.map +1 -0
  279. package/dist/usecases/intervention.js +205 -0
  280. package/dist/usecases/intervention.js.map +1 -0
  281. package/dist/usecases/text-duration.d.ts +4 -0
  282. package/dist/usecases/text-duration.d.ts.map +1 -0
  283. package/dist/usecases/text-duration.js +63 -0
  284. package/dist/usecases/text-duration.js.map +1 -0
  285. package/dist/usecases/workout-format.d.ts +139 -0
  286. package/dist/usecases/workout-format.d.ts.map +1 -0
  287. package/dist/usecases/workout-format.js +445 -0
  288. package/dist/usecases/workout-format.js.map +1 -0
  289. package/dist/usecases/workout.d.ts +94 -0
  290. package/dist/usecases/workout.d.ts.map +1 -0
  291. package/dist/usecases/workout.js +411 -0
  292. package/dist/usecases/workout.js.map +1 -0
  293. package/dist/vault-cli-command-manifest.d.ts +562 -0
  294. package/dist/vault-cli-command-manifest.d.ts.map +1 -0
  295. package/dist/vault-cli-command-manifest.js +759 -0
  296. package/dist/vault-cli-command-manifest.js.map +1 -0
  297. package/dist/vault-cli.d.ts +6 -0
  298. package/dist/vault-cli.d.ts.map +1 -0
  299. package/dist/vault-cli.js +38 -0
  300. package/dist/vault-cli.js.map +1 -0
  301. package/package.json +85 -0
@@ -0,0 +1,1458 @@
1
+ import * as React from 'react';
2
+ import { Box, Text, render, useApp, useInput } from 'ink';
3
+ import { listNamedOpenAICompatibleProviderPresets, resolveOpenAICompatibleProviderPreset, resolveOpenAICompatibleProviderPresetFromId, } from '@murphai/assistant-core/assistant-provider';
4
+ import { listAssistantCronPresets } from '@murphai/assistant-core/assistant-cron';
5
+ import { DEFAULT_SETUP_OPENAI_COMPATIBLE_BASE_URL, getDefaultSetupAssistantPreset as getDefaultAssistantPreset, } from './setup-assistant.js';
6
+ import { setupChannelValues, setupWearableValues, } from '@murphai/assistant-core/setup-cli-contracts';
7
+ import { SETUP_RUNTIME_ENV_NOTICE, } from '@murphai/assistant-core/setup-runtime-env';
8
+ import { VaultCliError } from '@murphai/assistant-core/vault-cli-errors';
9
+ const DEFAULT_SETUP_DEVICE_SYNC_LOCAL_BASE_URL = 'http://localhost:8788';
10
+ const DEFAULT_SETUP_LINQ_WEBHOOK_URL = 'http://127.0.0.1:8789/linq-webhook';
11
+ const DEFAULT_SETUP_OPENAI_API_BASE_URL = 'https://api.openai.com/v1';
12
+ const setupWizardAssistantProviderOptions = [
13
+ ...listNamedOpenAICompatibleProviderPresets().map((preset) => ({
14
+ provider: preset.id,
15
+ title: preset.title,
16
+ description: buildSetupWizardAssistantProviderDescription(preset),
17
+ })),
18
+ {
19
+ provider: 'custom',
20
+ title: 'Custom endpoint',
21
+ description: 'Use any other OpenAI-style endpoint, or keep the Codex local-model path.',
22
+ },
23
+ {
24
+ provider: 'skip',
25
+ title: 'Skip for now',
26
+ description: 'Leave the current assistant settings alone.',
27
+ },
28
+ ];
29
+ const setupWizardOpenAIAssistantMethodOptions = [
30
+ {
31
+ method: 'openai-codex',
32
+ title: 'ChatGPT / Codex sign-in',
33
+ description: 'Best if you already use the Codex sign-in flow.',
34
+ detail: 'Murph will use your saved Codex / ChatGPT login and ask which default model to use next.',
35
+ badges: [{ label: 'recommended', tone: 'success' }],
36
+ },
37
+ {
38
+ method: 'openai-api-key',
39
+ title: 'OpenAI API key',
40
+ description: 'Use OPENAI_API_KEY and choose a model.',
41
+ detail: 'Good if you want direct API billing instead of the Codex sign-in path.',
42
+ },
43
+ ];
44
+ const setupWizardCompatibleAssistantMethodOptions = [
45
+ {
46
+ method: 'compatible-endpoint',
47
+ title: 'Compatible endpoint',
48
+ description: 'Use any OpenAI-style endpoint and enter the details during setup.',
49
+ detail: 'Murph will ask for the endpoint URL and then let you choose a model.',
50
+ badges: [{ label: 'manual', tone: 'accent' }],
51
+ },
52
+ {
53
+ method: 'compatible-codex-local',
54
+ title: 'Codex local model',
55
+ description: 'Keep the Codex flow, but point it at a local OSS model.',
56
+ detail: 'Good if you want the Codex tooling path with a local model by default.',
57
+ },
58
+ ];
59
+ const setupWizardChannelOptions = [
60
+ {
61
+ channel: 'imessage',
62
+ description: 'Reply from Messages on this Mac.',
63
+ title: 'iMessage',
64
+ },
65
+ {
66
+ channel: 'telegram',
67
+ description: 'Reply through a Telegram bot.',
68
+ title: 'Telegram',
69
+ },
70
+ {
71
+ channel: 'linq',
72
+ description: 'Reply by SMS, iMessage, or RCS through Linq.',
73
+ title: 'Linq',
74
+ },
75
+ {
76
+ channel: 'email',
77
+ description: 'Read and reply in email.',
78
+ title: 'Email',
79
+ },
80
+ ];
81
+ const setupWizardScheduledUpdateOptions = listAssistantCronPresets().map((preset) => ({
82
+ id: preset.id,
83
+ title: preset.title,
84
+ description: preset.description,
85
+ scheduleLabel: preset.suggestedScheduleLabel,
86
+ }));
87
+ const DEFAULT_SETUP_WIZARD_SCHEDULED_UPDATE_IDS = [
88
+ 'weekly-health-snapshot',
89
+ 'environment-health-watch',
90
+ ];
91
+ const setupWizardWearableOptions = [
92
+ {
93
+ description: 'Import sleep, readiness, and recovery from Oura.',
94
+ title: 'Oura',
95
+ wearable: 'oura',
96
+ },
97
+ {
98
+ description: 'Import sleep, strain, and recovery from WHOOP.',
99
+ title: 'WHOOP',
100
+ wearable: 'whoop',
101
+ },
102
+ ];
103
+ export function getDefaultSetupWizardAssistantPreset() {
104
+ return getDefaultAssistantPreset();
105
+ }
106
+ export function getDefaultSetupWizardChannels(platform = process.platform) {
107
+ return platform === 'darwin' ? ['imessage'] : [];
108
+ }
109
+ export function getDefaultSetupWizardWearables() {
110
+ return [];
111
+ }
112
+ export function getDefaultSetupWizardScheduledUpdates() {
113
+ const available = new Set(setupWizardScheduledUpdateOptions.map((option) => option.id));
114
+ return sortSetupWizardScheduledUpdates(DEFAULT_SETUP_WIZARD_SCHEDULED_UPDATE_IDS.filter((id) => available.has(id)));
115
+ }
116
+ export function resolveSetupWizardInitialScheduledUpdates(initialScheduledUpdates) {
117
+ return sortSetupWizardScheduledUpdates(initialScheduledUpdates === undefined
118
+ ? getDefaultSetupWizardScheduledUpdates()
119
+ : [...initialScheduledUpdates]);
120
+ }
121
+ export function wrapSetupWizardIndex(currentIndex, length, delta) {
122
+ if (length <= 0) {
123
+ return 0;
124
+ }
125
+ return (currentIndex + delta + length) % length;
126
+ }
127
+ export function toggleSetupWizardChannel(selectedChannels, channel) {
128
+ const next = new Set(selectedChannels);
129
+ if (next.has(channel)) {
130
+ next.delete(channel);
131
+ }
132
+ else {
133
+ next.add(channel);
134
+ }
135
+ return sortSetupWizardChannels([...next]);
136
+ }
137
+ export function toggleSetupWizardWearable(selectedWearables, wearable) {
138
+ const next = new Set(selectedWearables);
139
+ if (next.has(wearable)) {
140
+ next.delete(wearable);
141
+ }
142
+ else {
143
+ next.add(wearable);
144
+ }
145
+ return sortSetupWizardWearables([...next]);
146
+ }
147
+ export function toggleSetupWizardScheduledUpdate(selectedPresetIds, presetId) {
148
+ const next = new Set(selectedPresetIds);
149
+ if (next.has(presetId)) {
150
+ next.delete(presetId);
151
+ }
152
+ else {
153
+ next.add(presetId);
154
+ }
155
+ return sortSetupWizardScheduledUpdates([...next]);
156
+ }
157
+ export function createSetupWizardCompletionController() {
158
+ let settled = false;
159
+ let exited = false;
160
+ let submittedResult = null;
161
+ let resolvePromise;
162
+ let rejectPromise;
163
+ const promise = new Promise((resolve, reject) => {
164
+ resolvePromise = resolve;
165
+ rejectPromise = reject;
166
+ });
167
+ const maybeResolve = () => {
168
+ if (settled || !exited || submittedResult === null) {
169
+ return;
170
+ }
171
+ settled = true;
172
+ resolvePromise(submittedResult);
173
+ };
174
+ return {
175
+ completeExit() {
176
+ if (settled) {
177
+ return;
178
+ }
179
+ exited = true;
180
+ if (submittedResult === null) {
181
+ settled = true;
182
+ rejectPromise(new Error('Murph setup wizard exited unexpectedly.'));
183
+ return;
184
+ }
185
+ maybeResolve();
186
+ },
187
+ fail(error) {
188
+ if (settled) {
189
+ return;
190
+ }
191
+ settled = true;
192
+ rejectPromise(error);
193
+ },
194
+ submit(result) {
195
+ if (settled || submittedResult !== null) {
196
+ return;
197
+ }
198
+ submittedResult = result;
199
+ maybeResolve();
200
+ },
201
+ async waitForResult() {
202
+ return await promise;
203
+ },
204
+ };
205
+ }
206
+ export async function runSetupWizard(input) {
207
+ const initialAssistantPreset = input.initialAssistantPreset ?? getDefaultSetupWizardAssistantPreset();
208
+ const initialChannels = sortSetupWizardChannels(input.initialChannels && input.initialChannels.length > 0
209
+ ? [...input.initialChannels]
210
+ : getDefaultSetupWizardChannels(input.platform));
211
+ const initialScheduledUpdates = resolveSetupWizardInitialScheduledUpdates(input.initialScheduledUpdates);
212
+ const initialWearables = sortSetupWizardWearables(input.initialWearables && input.initialWearables.length > 0
213
+ ? [...input.initialWearables]
214
+ : getDefaultSetupWizardWearables());
215
+ const commandName = input.commandName ?? 'murph';
216
+ const completion = createSetupWizardCompletionController();
217
+ const defaultScheduledUpdateIds = new Set(getDefaultSetupWizardScheduledUpdates());
218
+ let instance = null;
219
+ const App = () => {
220
+ const createElement = React.createElement;
221
+ const { exit } = useApp();
222
+ const initialAssistantProvider = inferSetupWizardAssistantProvider({
223
+ apiKeyEnv: input.initialAssistantApiKeyEnv,
224
+ baseUrl: input.initialAssistantBaseUrl,
225
+ oss: input.initialAssistantOss,
226
+ preset: initialAssistantPreset,
227
+ providerName: input.initialAssistantProviderName,
228
+ providerPreset: input.initialAssistantProviderPreset,
229
+ });
230
+ const initialAssistantMethod = inferSetupWizardAssistantMethod({
231
+ oss: input.initialAssistantOss,
232
+ preset: initialAssistantPreset,
233
+ provider: initialAssistantProvider,
234
+ });
235
+ const [step, setStep] = React.useState('intro');
236
+ const [assistantProviderIndex, setAssistantProviderIndex] = React.useState(findSetupWizardAssistantProviderIndex(initialAssistantProvider));
237
+ const [assistantMethodIndex, setAssistantMethodIndex] = React.useState(findSetupWizardAssistantMethodIndex(initialAssistantProvider, initialAssistantMethod));
238
+ const [scheduledUpdateIndex, setScheduledUpdateIndex] = React.useState(0);
239
+ const [channelIndex, setChannelIndex] = React.useState(0);
240
+ const [wearableIndex, setWearableIndex] = React.useState(0);
241
+ const [selectedAssistantProvider, setSelectedAssistantProvider] = React.useState(initialAssistantProvider);
242
+ const [selectedAssistantMethod, setSelectedAssistantMethod] = React.useState(initialAssistantMethod);
243
+ const [selectedChannels, setSelectedChannels] = React.useState(initialChannels);
244
+ const [selectedScheduledUpdates, setSelectedScheduledUpdates] = React.useState(initialScheduledUpdates);
245
+ const [selectedWearables, setSelectedWearables] = React.useState(initialWearables);
246
+ const assistantSelection = resolveSetupWizardAssistantSelection({
247
+ initialApiKeyEnv: input.initialAssistantApiKeyEnv,
248
+ initialBaseUrl: input.initialAssistantBaseUrl,
249
+ initialProvider: initialAssistantProvider,
250
+ initialProviderName: input.initialAssistantProviderName,
251
+ method: selectedAssistantMethod,
252
+ provider: selectedAssistantProvider,
253
+ });
254
+ const latestAssistantRef = React.useRef(assistantSelection);
255
+ const latestChannelsRef = React.useRef(initialChannels);
256
+ const latestScheduledUpdatesRef = React.useRef(initialScheduledUpdates);
257
+ const latestWearablesRef = React.useRef(initialWearables);
258
+ const publicUrlReview = buildSetupWizardPublicUrlReview({
259
+ channels: selectedChannels,
260
+ wearables: selectedWearables,
261
+ publicBaseUrl: input.publicBaseUrl,
262
+ deviceSyncLocalBaseUrl: input.deviceSyncLocalBaseUrl,
263
+ linqLocalWebhookUrl: input.linqLocalWebhookUrl,
264
+ });
265
+ const includePublicUrlStep = publicUrlReview.enabled;
266
+ const includeAssistantMethodStep = doesSetupWizardAssistantProviderRequireMethod(selectedAssistantProvider);
267
+ const publicUrlGuidance = publicUrlReview.enabled
268
+ ? describeSetupWizardPublicUrlStrategyChoice({
269
+ review: publicUrlReview,
270
+ strategy: publicUrlReview.recommendedStrategy,
271
+ })
272
+ : null;
273
+ React.useEffect(() => {
274
+ latestAssistantRef.current = assistantSelection;
275
+ }, [assistantSelection]);
276
+ React.useEffect(() => {
277
+ latestChannelsRef.current = selectedChannels;
278
+ }, [selectedChannels]);
279
+ React.useEffect(() => {
280
+ latestScheduledUpdatesRef.current = selectedScheduledUpdates;
281
+ }, [selectedScheduledUpdates]);
282
+ React.useEffect(() => {
283
+ latestWearablesRef.current = selectedWearables;
284
+ }, [selectedWearables]);
285
+ React.useEffect(() => {
286
+ setAssistantMethodIndex(findSetupWizardAssistantMethodIndex(selectedAssistantProvider, selectedAssistantMethod));
287
+ }, [selectedAssistantMethod, selectedAssistantProvider]);
288
+ const assistantMethodOptions = listSetupWizardAssistantMethodOptions(selectedAssistantProvider);
289
+ const selectionSteps = {
290
+ 'assistant-provider': {
291
+ lines: setupWizardAssistantProviderOptions.map((option, index) => ({
292
+ active: index === assistantProviderIndex,
293
+ badges: buildSetupWizardAssistantProviderBadges({
294
+ currentProvider: initialAssistantProvider,
295
+ provider: option.provider,
296
+ }),
297
+ description: option.description,
298
+ key: option.provider,
299
+ selected: option.provider === selectedAssistantProvider,
300
+ title: option.title,
301
+ })),
302
+ marker: 'radio',
303
+ nextStep: includeAssistantMethodStep
304
+ ? 'assistant-method'
305
+ : 'scheduled-updates',
306
+ previousStep: 'intro',
307
+ selectCurrentOnEnter: true,
308
+ setIndex: setAssistantProviderIndex,
309
+ step: 'assistant-provider',
310
+ stepIntro: formatSetupWizardStepIntro('assistant-provider', selectedAssistantProvider),
311
+ toggleCurrent: () => {
312
+ const activeProvider = setupWizardAssistantProviderOptions[assistantProviderIndex]?.provider;
313
+ if (!activeProvider) {
314
+ return;
315
+ }
316
+ const nextMethod = resolveSetupWizardAssistantMethodForProvider({
317
+ currentMethod: selectedAssistantMethod,
318
+ provider: activeProvider,
319
+ });
320
+ setSelectedAssistantProvider(activeProvider);
321
+ setSelectedAssistantMethod(nextMethod);
322
+ setAssistantMethodIndex(findSetupWizardAssistantMethodIndex(activeProvider, nextMethod));
323
+ },
324
+ },
325
+ 'assistant-method': {
326
+ lines: assistantMethodOptions.map((option, index) => ({
327
+ active: index === assistantMethodIndex,
328
+ badges: buildSetupWizardAssistantMethodBadges({
329
+ currentMethod: initialAssistantMethod,
330
+ method: option.method,
331
+ optionBadges: option.badges,
332
+ }),
333
+ description: option.description,
334
+ detail: option.detail,
335
+ key: option.method,
336
+ selected: option.method === selectedAssistantMethod,
337
+ title: option.title,
338
+ })),
339
+ marker: 'radio',
340
+ nextStep: 'scheduled-updates',
341
+ previousStep: 'assistant-provider',
342
+ selectCurrentOnEnter: true,
343
+ setIndex: setAssistantMethodIndex,
344
+ step: 'assistant-method',
345
+ stepIntro: formatSetupWizardStepIntro('assistant-method', selectedAssistantProvider),
346
+ toggleCurrent: () => {
347
+ const activeMethod = assistantMethodOptions[assistantMethodIndex]?.method;
348
+ if (activeMethod) {
349
+ setSelectedAssistantMethod(activeMethod);
350
+ }
351
+ },
352
+ },
353
+ 'scheduled-updates': {
354
+ lines: setupWizardScheduledUpdateOptions.map((option, index) => ({
355
+ active: index === scheduledUpdateIndex,
356
+ badges: buildSetupWizardScheduledUpdateBadges({
357
+ isStarter: defaultScheduledUpdateIds.has(option.id),
358
+ }),
359
+ description: option.description,
360
+ detail: `Suggested cadence: ${option.scheduleLabel}.`,
361
+ key: option.id,
362
+ selected: selectedScheduledUpdates.includes(option.id),
363
+ title: option.title,
364
+ })),
365
+ marker: 'checkbox',
366
+ nextStep: 'channels',
367
+ previousStep: includeAssistantMethodStep
368
+ ? 'assistant-method'
369
+ : 'assistant-provider',
370
+ selectCurrentOnEnter: false,
371
+ setIndex: setScheduledUpdateIndex,
372
+ step: 'scheduled-updates',
373
+ stepIntro: formatSetupWizardStepIntro('scheduled-updates', selectedAssistantProvider),
374
+ toggleCurrent: () => {
375
+ const activePresetId = setupWizardScheduledUpdateOptions[scheduledUpdateIndex]?.id;
376
+ if (activePresetId) {
377
+ setSelectedScheduledUpdates((current) => toggleSetupWizardScheduledUpdate(current, activePresetId));
378
+ }
379
+ },
380
+ },
381
+ channels: {
382
+ lines: setupWizardChannelOptions.map((option, index) => {
383
+ const status = getChannelStatus(option.channel);
384
+ return {
385
+ active: index === channelIndex,
386
+ badges: [
387
+ {
388
+ label: status.badge,
389
+ tone: resolveSetupWizardRuntimeTone(status),
390
+ },
391
+ ],
392
+ description: option.description,
393
+ detail: formatSetupWizardRuntimeDetail(status),
394
+ key: option.channel,
395
+ selected: selectedChannels.includes(option.channel),
396
+ title: option.title,
397
+ };
398
+ }),
399
+ marker: 'checkbox',
400
+ nextStep: 'wearables',
401
+ previousStep: 'scheduled-updates',
402
+ selectCurrentOnEnter: false,
403
+ setIndex: setChannelIndex,
404
+ step: 'channels',
405
+ stepIntro: formatSetupWizardStepIntro('channels', selectedAssistantProvider),
406
+ toggleCurrent: () => {
407
+ const activeChannel = setupWizardChannelOptions[channelIndex]?.channel;
408
+ if (activeChannel) {
409
+ setSelectedChannels((current) => toggleSetupWizardChannel(current, activeChannel));
410
+ }
411
+ },
412
+ },
413
+ wearables: {
414
+ lines: setupWizardWearableOptions.map((option, index) => {
415
+ const status = getWearableStatus(option.wearable);
416
+ return {
417
+ active: index === wearableIndex,
418
+ badges: [
419
+ {
420
+ label: status.badge,
421
+ tone: resolveSetupWizardRuntimeTone(status),
422
+ },
423
+ ],
424
+ description: option.description,
425
+ detail: formatSetupWizardRuntimeDetail(status),
426
+ key: option.wearable,
427
+ selected: selectedWearables.includes(option.wearable),
428
+ title: option.title,
429
+ };
430
+ }),
431
+ marker: 'checkbox',
432
+ nextStep: includePublicUrlStep ? 'public-url' : 'confirm',
433
+ previousStep: 'channels',
434
+ selectCurrentOnEnter: false,
435
+ setIndex: setWearableIndex,
436
+ step: 'wearables',
437
+ stepIntro: formatSetupWizardStepIntro('wearables', selectedAssistantProvider),
438
+ toggleCurrent: () => {
439
+ const activeWearable = setupWizardWearableOptions[wearableIndex]?.wearable;
440
+ if (activeWearable) {
441
+ setSelectedWearables((current) => toggleSetupWizardWearable(current, activeWearable));
442
+ }
443
+ },
444
+ },
445
+ };
446
+ const selectionStep = step === 'intro' || step === 'public-url' || step === 'confirm'
447
+ ? null
448
+ : selectionSteps[step];
449
+ useInput((value, key) => {
450
+ if ((key.ctrl && value === 'c') || value.toLowerCase() === 'q') {
451
+ completion.fail(new VaultCliError('setup_cancelled', 'Murph setup was cancelled.'));
452
+ exit();
453
+ return;
454
+ }
455
+ if (step === 'intro') {
456
+ if (key.return || value === ' ') {
457
+ setStep('assistant-provider');
458
+ return;
459
+ }
460
+ if (key.escape) {
461
+ completion.fail(new VaultCliError('setup_cancelled', 'Murph setup was cancelled.'));
462
+ exit();
463
+ }
464
+ return;
465
+ }
466
+ if (selectionStep) {
467
+ if (key.upArrow) {
468
+ selectionStep.setIndex((current) => wrapSetupWizardIndex(current, selectionStep.lines.length, -1));
469
+ return;
470
+ }
471
+ if (key.downArrow) {
472
+ selectionStep.setIndex((current) => wrapSetupWizardIndex(current, selectionStep.lines.length, 1));
473
+ return;
474
+ }
475
+ if (value === ' ') {
476
+ selectionStep.toggleCurrent();
477
+ return;
478
+ }
479
+ if (key.escape) {
480
+ setStep(selectionStep.previousStep);
481
+ return;
482
+ }
483
+ if (key.return) {
484
+ if (selectionStep.step === 'assistant-provider') {
485
+ const activeProvider = setupWizardAssistantProviderOptions[assistantProviderIndex]?.provider ??
486
+ selectedAssistantProvider;
487
+ selectionStep.toggleCurrent();
488
+ setStep(doesSetupWizardAssistantProviderRequireMethod(activeProvider)
489
+ ? 'assistant-method'
490
+ : 'scheduled-updates');
491
+ return;
492
+ }
493
+ if (selectionStep.selectCurrentOnEnter) {
494
+ selectionStep.toggleCurrent();
495
+ }
496
+ setStep(selectionStep.nextStep);
497
+ return;
498
+ }
499
+ return;
500
+ }
501
+ if (step === 'public-url') {
502
+ if (key.escape) {
503
+ setStep('wearables');
504
+ return;
505
+ }
506
+ if (key.return || value === ' ') {
507
+ setStep('confirm');
508
+ }
509
+ return;
510
+ }
511
+ if (step === 'confirm') {
512
+ if (key.escape || key.leftArrow) {
513
+ setStep(includePublicUrlStep ? 'public-url' : 'wearables');
514
+ return;
515
+ }
516
+ if (key.return || value === ' ') {
517
+ completion.submit({
518
+ assistantApiKeyEnv: latestAssistantRef.current.apiKeyEnv,
519
+ assistantBaseUrl: latestAssistantRef.current.baseUrl,
520
+ assistantOss: latestAssistantRef.current.oss,
521
+ assistantPreset: latestAssistantRef.current.preset,
522
+ assistantProviderName: latestAssistantRef.current.providerName,
523
+ channels: sortSetupWizardChannels(latestChannelsRef.current),
524
+ scheduledUpdates: sortSetupWizardScheduledUpdates(latestScheduledUpdatesRef.current),
525
+ wearables: sortSetupWizardWearables(latestWearablesRef.current),
526
+ });
527
+ exit();
528
+ }
529
+ }
530
+ });
531
+ const selectedChannelNames = selectedChannels.map((channel) => formatSetupChannel(channel));
532
+ const selectedWearableNames = selectedWearables.map((wearable) => formatSetupWearable(wearable));
533
+ const selectedScheduledUpdateNames = selectedScheduledUpdates.map((presetId) => formatSetupScheduledUpdate(presetId));
534
+ const selectedChannelSummary = formatSelectionSummary(selectedChannelNames);
535
+ const selectedScheduledUpdateSummary = formatSelectionSummary(selectedScheduledUpdateNames);
536
+ const selectedWearableSummary = formatSelectionSummary(selectedWearableNames);
537
+ const selectedReadyNow = [
538
+ ...selectedChannels.flatMap((channel) => getChannelStatus(channel).ready ? [formatSetupChannel(channel)] : []),
539
+ ...selectedWearables.flatMap((wearable) => getWearableStatus(wearable).ready ? [formatSetupWearable(wearable)] : []),
540
+ ];
541
+ const selectedNeedsEnv = [
542
+ ...selectedChannels.flatMap((channel) => {
543
+ const status = getChannelStatus(channel);
544
+ return status.missingEnv.length > 0
545
+ ? [`${formatSetupChannel(channel)} (${formatMissingEnv(status.missingEnv)})`]
546
+ : [];
547
+ }),
548
+ ...selectedWearables.flatMap((wearable) => {
549
+ const status = getWearableStatus(wearable);
550
+ return status.missingEnv.length > 0
551
+ ? [`${formatSetupWearable(wearable)} (${formatMissingEnv(status.missingEnv)})`]
552
+ : [];
553
+ }),
554
+ ...(assistantSelection.apiKeyEnv &&
555
+ normalizeSetupWizardText(process.env[assistantSelection.apiKeyEnv]) === null
556
+ ? [
557
+ `Assistant (${assistantSelection.apiKeyEnv})`,
558
+ ]
559
+ : []),
560
+ ];
561
+ const hintRow = createSetupWizardHintRow(resolveSetupWizardHints({
562
+ commandName,
563
+ selectionMarker: selectionStep?.marker,
564
+ step,
565
+ }));
566
+ const confirmNextStep = describeSetupWizardReviewNextStep({
567
+ needsEnv: selectedNeedsEnv.length > 0,
568
+ hasScheduledUpdates: selectedScheduledUpdates.length > 0,
569
+ });
570
+ const completedBlocks = [];
571
+ if (hasSetupWizardStepPassed({
572
+ currentStep: step,
573
+ includeAssistantMethodStep,
574
+ includePublicUrlStep,
575
+ stepToCheck: 'assistant-provider',
576
+ })) {
577
+ completedBlocks.push(createSetupWizardAnsweredBlock({
578
+ label: formatSetupWizardPromptTitle('assistant-provider', selectedAssistantProvider),
579
+ value: assistantSelection.providerLabel,
580
+ }, 'completed-assistant-provider'));
581
+ }
582
+ if (includeAssistantMethodStep &&
583
+ hasSetupWizardStepPassed({
584
+ currentStep: step,
585
+ includeAssistantMethodStep,
586
+ includePublicUrlStep,
587
+ stepToCheck: 'assistant-method',
588
+ })) {
589
+ completedBlocks.push(createSetupWizardAnsweredBlock({
590
+ label: formatSetupWizardPromptTitle('assistant-method', selectedAssistantProvider),
591
+ value: assistantSelection.methodLabel ?? 'Skip',
592
+ detail: assistantSelection.detail,
593
+ }, 'completed-assistant-method'));
594
+ }
595
+ if (hasSetupWizardStepPassed({
596
+ currentStep: step,
597
+ includeAssistantMethodStep,
598
+ includePublicUrlStep,
599
+ stepToCheck: 'scheduled-updates',
600
+ })) {
601
+ completedBlocks.push(createSetupWizardAnsweredBlock({
602
+ label: formatSetupWizardPromptTitle('scheduled-updates', selectedAssistantProvider),
603
+ value: formatSelectionSummary(selectedScheduledUpdateNames),
604
+ }, 'completed-scheduled-updates'));
605
+ }
606
+ if (hasSetupWizardStepPassed({
607
+ currentStep: step,
608
+ includeAssistantMethodStep,
609
+ includePublicUrlStep,
610
+ stepToCheck: 'channels',
611
+ })) {
612
+ completedBlocks.push(createSetupWizardAnsweredBlock({
613
+ label: formatSetupWizardPromptTitle('channels', selectedAssistantProvider),
614
+ value: formatSelectionSummary(selectedChannelNames),
615
+ }, 'completed-channels'));
616
+ }
617
+ if (hasSetupWizardStepPassed({
618
+ currentStep: step,
619
+ includeAssistantMethodStep,
620
+ includePublicUrlStep,
621
+ stepToCheck: 'wearables',
622
+ })) {
623
+ completedBlocks.push(createSetupWizardAnsweredBlock({
624
+ label: formatSetupWizardPromptTitle('wearables', selectedAssistantProvider),
625
+ value: formatSelectionSummary(selectedWearableNames),
626
+ }, 'completed-wearables'));
627
+ }
628
+ if (includePublicUrlStep &&
629
+ hasSetupWizardStepPassed({
630
+ currentStep: step,
631
+ includeAssistantMethodStep,
632
+ includePublicUrlStep,
633
+ stepToCheck: 'public-url',
634
+ })) {
635
+ completedBlocks.push(createSetupWizardAnsweredBlock({
636
+ label: formatSetupWizardPromptTitle('public-url', selectedAssistantProvider),
637
+ value: formatSetupPublicUrlStrategy(publicUrlReview.recommendedStrategy),
638
+ detail: publicUrlGuidance ?? publicUrlReview.summary,
639
+ }, 'completed-public-url'));
640
+ }
641
+ const activePanel = step === 'intro'
642
+ ? createSetupWizardPanel({
643
+ title: 'Before you start',
644
+ tone: 'accent',
645
+ children: [
646
+ createElement(Text, null, 'We’ll help you choose how Murph should answer, which chats to turn on, and any health data you want to connect.'),
647
+ createElement(Text, null, ''),
648
+ createSetupWizardBulletRow({
649
+ body: 'You can skip anything now and change it later.',
650
+ label: 'Nothing is locked in',
651
+ tone: 'success',
652
+ }, 'intro-change-later'),
653
+ createSetupWizardBulletRow({
654
+ body: 'If something needs a key or token, you can enter it for this setup run only or leave it for later.',
655
+ label: 'Keys and tokens',
656
+ tone: 'accent',
657
+ }, 'intro-keys'),
658
+ createSetupWizardBulletRow({
659
+ body: SETUP_RUNTIME_ENV_NOTICE,
660
+ label: 'This setup run only',
661
+ tone: 'muted',
662
+ }, 'intro-runtime-env'),
663
+ ],
664
+ })
665
+ : selectionStep
666
+ ? createSetupWizardPanel({
667
+ title: formatSetupWizardPromptTitle(selectionStep.step, selectedAssistantProvider),
668
+ tone: 'accent',
669
+ children: [
670
+ selectionStep.stepIntro
671
+ ? createElement(Text, { color: resolveSetupWizardToneColor('muted') }, selectionStep.stepIntro)
672
+ : null,
673
+ selectionStep.stepIntro ? createElement(Text, null, '') : null,
674
+ ...selectionStep.lines.map((line) => createSetupWizardSelectionRow({
675
+ line,
676
+ marker: selectionStep.marker,
677
+ }, line.key)),
678
+ ],
679
+ })
680
+ : step === 'public-url'
681
+ ? createSetupWizardPanel({
682
+ title: formatSetupWizardPromptTitle('public-url', selectedAssistantProvider),
683
+ tone: 'accent',
684
+ children: [
685
+ createElement(Text, null, publicUrlReview.summary),
686
+ createElement(Text, null, ''),
687
+ createSetupWizardBulletRow({
688
+ body: publicUrlGuidance ?? '',
689
+ label: `Easiest path: ${formatSetupPublicUrlStrategy(publicUrlReview.recommendedStrategy)}`,
690
+ tone: 'accent',
691
+ }, 'public-url-recommended'),
692
+ createElement(Text, null, ''),
693
+ createElement(Text, { color: resolveSetupWizardToneColor('muted'), bold: true }, 'If you keep things local, use these URLs'),
694
+ createElement(Text, null, ''),
695
+ ...publicUrlReview.targets.map((target) => createSetupWizardPublicUrlTargetRow(target)),
696
+ createElement(Text, null, ''),
697
+ createElement(Text, { color: resolveSetupWizardToneColor('muted') }, 'This step is informational only. Murph does not save a public URL choice yet.'),
698
+ ],
699
+ })
700
+ : createSetupWizardPanel({
701
+ title: 'Review your setup',
702
+ tone: 'accent',
703
+ children: [
704
+ createSetupWizardKeyValueRow({ label: 'Assistant', value: assistantSelection.summary }, 'confirm-assistant'),
705
+ createSetupWizardKeyValueRow({ label: 'Chat channels', value: selectedChannelSummary }, 'confirm-channels'),
706
+ createSetupWizardKeyValueRow({ label: 'Health data', value: selectedWearableSummary }, 'confirm-wearables'),
707
+ createSetupWizardKeyValueRow({
708
+ label: 'Auto updates',
709
+ value: selectedScheduledUpdateSummary,
710
+ }, 'confirm-schedules'),
711
+ createElement(Text, null, ''),
712
+ createSetupWizardBulletRow({
713
+ body: formatSelectionSummary(selectedReadyNow),
714
+ label: 'Can connect now',
715
+ tone: 'success',
716
+ }, 'confirm-ready'),
717
+ createSetupWizardBulletRow({
718
+ body: formatSelectionSummary(selectedNeedsEnv),
719
+ label: 'Needs keys first',
720
+ tone: selectedNeedsEnv.length > 0 ? 'warn' : 'muted',
721
+ }, 'confirm-needs-env'),
722
+ publicUrlGuidance
723
+ ? createSetupWizardBulletRow({
724
+ body: publicUrlGuidance,
725
+ label: 'Public links',
726
+ tone: 'accent',
727
+ }, 'confirm-public-url')
728
+ : null,
729
+ createElement(Text, null, ''),
730
+ createSetupWizardBulletRow({
731
+ body: confirmNextStep,
732
+ label: 'Next',
733
+ tone: 'accent',
734
+ }, 'confirm-next-step'),
735
+ ],
736
+ });
737
+ return createElement(Box, {
738
+ flexDirection: 'column',
739
+ paddingX: 1,
740
+ paddingY: 1,
741
+ }, createElement(Text, { color: resolveSetupWizardToneColor('accent'), bold: true }, '✦ Murph setup'), createElement(Text, { color: resolveSetupWizardToneColor('muted') }, 'Choose the basics now. You can change anything later.'), createElement(Text, { color: resolveSetupWizardToneColor('muted'), dimColor: true }, `Vault: ${input.vault}`), createElement(Text, null, ''), ...completedBlocks, activePanel, createElement(Text, null, ''), hintRow);
742
+ function getChannelStatus(channel) {
743
+ return normalizeSetupWizardRuntimeStatus(input.channelStatuses?.[channel]);
744
+ }
745
+ function getWearableStatus(wearable) {
746
+ return normalizeSetupWizardRuntimeStatus(input.wearableStatuses?.[wearable]);
747
+ }
748
+ };
749
+ try {
750
+ instance = render(React.createElement(App), {
751
+ stderr: process.stderr,
752
+ stdout: process.stderr,
753
+ patchConsole: false,
754
+ });
755
+ void instance.waitUntilExit().then(() => {
756
+ completion.completeExit();
757
+ }, (error) => {
758
+ completion.fail(error);
759
+ });
760
+ }
761
+ catch (error) {
762
+ completion.fail(error);
763
+ }
764
+ if (!instance) {
765
+ completion.fail(new Error('Murph setup wizard failed to initialize.'));
766
+ }
767
+ return await completion.waitForResult();
768
+ }
769
+ function normalizeSetupWizardRuntimeStatus(status) {
770
+ return (status ?? {
771
+ badge: 'optional',
772
+ detail: '',
773
+ missingEnv: [],
774
+ ready: true,
775
+ });
776
+ }
777
+ function sortSetupWizardChannels(channels) {
778
+ const order = new Map(setupChannelValues.map((channel, index) => [channel, index]));
779
+ const unique = [...new Set(channels)];
780
+ return unique.sort((left, right) => (order.get(left) ?? Number.MAX_SAFE_INTEGER) -
781
+ (order.get(right) ?? Number.MAX_SAFE_INTEGER));
782
+ }
783
+ function sortSetupWizardWearables(wearables) {
784
+ const order = new Map(setupWearableValues.map((wearable, index) => [wearable, index]));
785
+ const unique = [...new Set(wearables)];
786
+ return unique.sort((left, right) => (order.get(left) ?? Number.MAX_SAFE_INTEGER) -
787
+ (order.get(right) ?? Number.MAX_SAFE_INTEGER));
788
+ }
789
+ function sortSetupWizardScheduledUpdates(presetIds) {
790
+ const order = new Map(setupWizardScheduledUpdateOptions.map((option, index) => [option.id, index]));
791
+ const unique = [...new Set(presetIds)];
792
+ return unique.sort((left, right) => (order.get(left) ?? Number.MAX_SAFE_INTEGER) -
793
+ (order.get(right) ?? Number.MAX_SAFE_INTEGER));
794
+ }
795
+ function findSetupWizardAssistantProviderIndex(provider) {
796
+ const index = setupWizardAssistantProviderOptions.findIndex((option) => option.provider === provider);
797
+ return index >= 0 ? index : 0;
798
+ }
799
+ function findSetupWizardAssistantMethodIndex(provider, method) {
800
+ const options = listSetupWizardAssistantMethodOptions(provider);
801
+ const index = options.findIndex((option) => option.method === method);
802
+ return index >= 0 ? index : 0;
803
+ }
804
+ export function inferSetupWizardAssistantProvider(input) {
805
+ switch (input.preset) {
806
+ case 'codex':
807
+ if (input.oss === true) {
808
+ return resolveSetupWizardCompatibleProviderPreset(input)?.id ?? 'custom';
809
+ }
810
+ return 'openai';
811
+ case 'skip':
812
+ return 'skip';
813
+ case 'openai-compatible':
814
+ if (input.providerPreset) {
815
+ return input.providerPreset;
816
+ }
817
+ if (isOpenAIAssistantSelection(input)) {
818
+ return 'openai';
819
+ }
820
+ return resolveSetupWizardCompatibleProviderPreset(input)?.id ?? 'custom';
821
+ }
822
+ }
823
+ function inferSetupWizardAssistantMethod(input) {
824
+ switch (input.preset) {
825
+ case 'codex':
826
+ return input.oss === true ? 'compatible-codex-local' : 'openai-codex';
827
+ case 'skip':
828
+ return 'skip';
829
+ case 'openai-compatible':
830
+ if (input.provider === 'openai') {
831
+ return 'openai-api-key';
832
+ }
833
+ return doesSetupWizardAssistantProviderRequireMethod(input.provider)
834
+ ? 'compatible-endpoint'
835
+ : 'compatible-provider';
836
+ }
837
+ }
838
+ function doesSetupWizardAssistantProviderRequireMethod(provider) {
839
+ return provider === 'openai' || provider === 'custom';
840
+ }
841
+ function resolveSetupWizardAssistantMethodForProvider(input) {
842
+ if (input.provider === 'skip') {
843
+ return 'skip';
844
+ }
845
+ if (input.provider === 'openai') {
846
+ return input.currentMethod === 'openai-api-key'
847
+ ? 'openai-api-key'
848
+ : 'openai-codex';
849
+ }
850
+ if (input.provider === 'custom') {
851
+ return input.currentMethod === 'compatible-codex-local'
852
+ ? 'compatible-codex-local'
853
+ : 'compatible-endpoint';
854
+ }
855
+ return 'compatible-provider';
856
+ }
857
+ function listSetupWizardAssistantMethodOptions(provider) {
858
+ switch (provider) {
859
+ case 'openai':
860
+ return setupWizardOpenAIAssistantMethodOptions;
861
+ case 'custom':
862
+ return setupWizardCompatibleAssistantMethodOptions;
863
+ case 'skip':
864
+ return [];
865
+ default:
866
+ return [];
867
+ }
868
+ }
869
+ export function resolveSetupWizardAssistantSelection(input) {
870
+ const preservedSelection = input.initialProvider === input.provider
871
+ ? {
872
+ apiKeyEnv: normalizeSetupWizardText(input.initialApiKeyEnv),
873
+ baseUrl: normalizeSetupWizardText(input.initialBaseUrl),
874
+ providerName: normalizeSetupWizardText(input.initialProviderName),
875
+ }
876
+ : {
877
+ apiKeyEnv: null,
878
+ baseUrl: null,
879
+ providerName: null,
880
+ };
881
+ if (input.provider === 'skip') {
882
+ return {
883
+ apiKeyEnv: null,
884
+ baseUrl: null,
885
+ detail: 'Murph will leave your current assistant settings alone for now.',
886
+ methodLabel: null,
887
+ oss: null,
888
+ preset: 'skip',
889
+ providerLabel: 'Skip for now',
890
+ providerName: null,
891
+ summary: 'Skip for now',
892
+ };
893
+ }
894
+ if (input.provider === 'openai') {
895
+ if (input.method === 'openai-api-key') {
896
+ const apiKeyEnv = preservedSelection.apiKeyEnv ?? 'OPENAI_API_KEY';
897
+ return {
898
+ apiKeyEnv,
899
+ baseUrl: preservedSelection.baseUrl ?? DEFAULT_SETUP_OPENAI_API_BASE_URL,
900
+ detail: `Murph will use ${apiKeyEnv} and ask which model to save next.`,
901
+ methodLabel: 'OpenAI API key',
902
+ oss: false,
903
+ preset: 'openai-compatible',
904
+ providerLabel: 'OpenAI',
905
+ providerName: preservedSelection.providerName ?? 'openai',
906
+ summary: 'OpenAI · API key',
907
+ };
908
+ }
909
+ return {
910
+ apiKeyEnv: null,
911
+ baseUrl: null,
912
+ detail: 'Murph will use your saved Codex / ChatGPT sign-in and ask which default model to use next.',
913
+ methodLabel: 'ChatGPT / Codex sign-in',
914
+ oss: false,
915
+ preset: 'codex',
916
+ providerLabel: 'OpenAI',
917
+ providerName: null,
918
+ summary: 'OpenAI · ChatGPT / Codex sign-in',
919
+ };
920
+ }
921
+ if (input.provider === 'custom') {
922
+ if (input.method === 'compatible-codex-local') {
923
+ return {
924
+ apiKeyEnv: null,
925
+ baseUrl: null,
926
+ detail: 'Murph will keep the Codex flow and ask which local model to save next.',
927
+ methodLabel: 'Codex local model',
928
+ oss: true,
929
+ preset: 'codex',
930
+ providerLabel: 'Custom endpoint',
931
+ providerName: null,
932
+ summary: 'Custom endpoint · Codex local model',
933
+ };
934
+ }
935
+ return {
936
+ apiKeyEnv: preservedSelection.apiKeyEnv,
937
+ baseUrl: preservedSelection.baseUrl ?? DEFAULT_SETUP_OPENAI_COMPATIBLE_BASE_URL,
938
+ detail: 'Murph will ask for the endpoint URL and then let you choose a model.',
939
+ methodLabel: 'Compatible endpoint',
940
+ oss: false,
941
+ preset: 'openai-compatible',
942
+ providerLabel: 'Custom endpoint',
943
+ providerName: preservedSelection.providerName,
944
+ summary: 'Custom endpoint · Compatible endpoint',
945
+ };
946
+ }
947
+ const providerPreset = resolveOpenAICompatibleProviderPresetFromId(input.provider) ??
948
+ resolveOpenAICompatibleProviderPresetFromId('custom');
949
+ return {
950
+ apiKeyEnv: preservedSelection.apiKeyEnv ?? providerPreset?.apiKeyEnv ?? null,
951
+ baseUrl: preservedSelection.baseUrl ??
952
+ providerPreset?.baseUrl ??
953
+ DEFAULT_SETUP_OPENAI_COMPATIBLE_BASE_URL,
954
+ detail: buildSetupWizardNamedProviderSelectionDetail({
955
+ apiKeyEnv: preservedSelection.apiKeyEnv ?? providerPreset?.apiKeyEnv ?? null,
956
+ preset: providerPreset,
957
+ }),
958
+ methodLabel: null,
959
+ oss: false,
960
+ preset: 'openai-compatible',
961
+ providerLabel: providerPreset?.title ?? 'OpenAI-compatible provider',
962
+ providerName: preservedSelection.providerName ?? providerPreset?.providerName ?? null,
963
+ summary: providerPreset?.title ?? 'OpenAI-compatible provider',
964
+ };
965
+ }
966
+ function resolveSetupWizardCompatibleProviderPreset(input) {
967
+ const normalizedBaseUrl = normalizeSetupWizardText(input.baseUrl);
968
+ if (normalizedBaseUrl !== null) {
969
+ const preset = resolveOpenAICompatibleProviderPreset({
970
+ baseUrl: normalizedBaseUrl,
971
+ });
972
+ return preset?.id === 'openai' ? null : preset;
973
+ }
974
+ const normalizedProviderName = normalizeSetupWizardText(input.providerName);
975
+ if (normalizedProviderName !== null) {
976
+ const preset = resolveOpenAICompatibleProviderPreset({
977
+ providerName: normalizedProviderName,
978
+ });
979
+ return preset?.id === 'openai' ? null : preset;
980
+ }
981
+ const preset = resolveOpenAICompatibleProviderPreset({
982
+ apiKeyEnv: input.apiKeyEnv,
983
+ });
984
+ return preset?.id === 'openai' ? null : preset;
985
+ }
986
+ function isOpenAIAssistantSelection(input) {
987
+ const normalizedProviderName = normalizeSetupWizardText(input.providerName);
988
+ if (normalizedProviderName !== null) {
989
+ return (resolveOpenAICompatibleProviderPreset({
990
+ providerName: normalizedProviderName,
991
+ })?.id === 'openai');
992
+ }
993
+ const normalizedBaseUrl = normalizeSetupWizardText(input.baseUrl);
994
+ if (normalizedBaseUrl !== null) {
995
+ return (resolveOpenAICompatibleProviderPreset({
996
+ baseUrl: normalizedBaseUrl,
997
+ })?.id === 'openai');
998
+ }
999
+ return (resolveOpenAICompatibleProviderPreset({
1000
+ apiKeyEnv: input.apiKeyEnv,
1001
+ })?.id === 'openai');
1002
+ }
1003
+ function buildSetupWizardAssistantProviderDescription(preset) {
1004
+ if (preset.id === 'openai') {
1005
+ return 'Use OpenAI. You can choose ChatGPT / Codex sign-in or an API key next.';
1006
+ }
1007
+ if (preset.kind === 'local') {
1008
+ return `Use ${preset.title} through its local OpenAI-compatible server.`;
1009
+ }
1010
+ if (preset.kind === 'gateway') {
1011
+ return `Use ${preset.title} as an OpenAI-compatible gateway.`;
1012
+ }
1013
+ return `Use ${preset.title} and choose a model during setup.`;
1014
+ }
1015
+ function buildSetupWizardNamedProviderSelectionDetail(input) {
1016
+ const providerTitle = input.preset?.title ?? 'this provider';
1017
+ if (input.apiKeyEnv) {
1018
+ return `Murph will use ${providerTitle} and read the key from ${input.apiKeyEnv}. It will ask which model to save next.`;
1019
+ }
1020
+ return `Murph will use ${providerTitle} and ask which model to save next.`;
1021
+ }
1022
+ function formatSetupChannel(channel) {
1023
+ switch (channel) {
1024
+ case 'imessage':
1025
+ return 'iMessage';
1026
+ case 'telegram':
1027
+ return 'Telegram';
1028
+ case 'linq':
1029
+ return 'Linq';
1030
+ case 'email':
1031
+ return 'Email';
1032
+ }
1033
+ }
1034
+ function formatSetupWearable(wearable) {
1035
+ return wearable === 'oura' ? 'Oura' : 'WHOOP';
1036
+ }
1037
+ function formatSetupScheduledUpdate(presetId) {
1038
+ return (setupWizardScheduledUpdateOptions.find((option) => option.id === presetId)?.title ??
1039
+ presetId);
1040
+ }
1041
+ function formatSelectionSummary(values) {
1042
+ return values.length > 0 ? values.join(', ') : 'None';
1043
+ }
1044
+ function formatMissingEnv(values) {
1045
+ if (values.length === 0) {
1046
+ return 'nothing else';
1047
+ }
1048
+ if (values.length === 1) {
1049
+ return values[0] ?? '';
1050
+ }
1051
+ return values.join(', ');
1052
+ }
1053
+ function formatSetupPublicUrlStrategy(strategy) {
1054
+ return strategy === 'hosted' ? 'Hosted web app' : 'Local tunnel';
1055
+ }
1056
+ function listSetupWizardSteps(input) {
1057
+ return [
1058
+ 'intro',
1059
+ 'assistant-provider',
1060
+ ...(input.includeAssistantMethodStep ? ['assistant-method'] : []),
1061
+ 'scheduled-updates',
1062
+ 'channels',
1063
+ 'wearables',
1064
+ ...(input.includePublicUrlStep ? ['public-url'] : []),
1065
+ 'confirm',
1066
+ ];
1067
+ }
1068
+ function hasSetupWizardStepPassed(input) {
1069
+ const steps = listSetupWizardSteps({
1070
+ includeAssistantMethodStep: input.includeAssistantMethodStep,
1071
+ includePublicUrlStep: input.includePublicUrlStep,
1072
+ });
1073
+ const currentIndex = steps.indexOf(input.currentStep);
1074
+ const stepIndex = steps.indexOf(input.stepToCheck);
1075
+ return currentIndex > stepIndex;
1076
+ }
1077
+ function resolveSetupWizardToneColor(tone) {
1078
+ switch (tone) {
1079
+ case 'accent':
1080
+ return 'cyan';
1081
+ case 'success':
1082
+ return 'green';
1083
+ case 'warn':
1084
+ return 'yellow';
1085
+ case 'danger':
1086
+ return 'red';
1087
+ case 'muted':
1088
+ return 'gray';
1089
+ }
1090
+ }
1091
+ function resolveSetupWizardRuntimeTone(status) {
1092
+ if (status.ready) {
1093
+ return 'success';
1094
+ }
1095
+ return status.badge.toLowerCase().includes('macos') ? 'accent' : 'warn';
1096
+ }
1097
+ function formatSetupWizardRuntimeDetail(status) {
1098
+ if (status.ready) {
1099
+ return 'Ready to connect now.';
1100
+ }
1101
+ if (!status.ready && status.missingEnv.length > 0) {
1102
+ return `Needs ${formatMissingEnv(status.missingEnv)} before this can connect.`;
1103
+ }
1104
+ return status.badge.toLowerCase().includes('macos')
1105
+ ? 'Only available on macOS.'
1106
+ : status.detail;
1107
+ }
1108
+ function buildSetupWizardAssistantProviderBadges(input) {
1109
+ const badges = [];
1110
+ if (input.provider === 'skip') {
1111
+ badges.push({ label: 'no change', tone: 'muted' });
1112
+ }
1113
+ else if (input.provider === 'custom') {
1114
+ badges.push({ label: 'manual', tone: 'accent' });
1115
+ }
1116
+ else {
1117
+ const preset = resolveOpenAICompatibleProviderPresetFromId(input.provider);
1118
+ if (preset?.id === 'openai') {
1119
+ badges.push({ label: 'recommended', tone: 'success' });
1120
+ }
1121
+ else if (preset?.kind === 'local') {
1122
+ badges.push({ label: 'local', tone: 'accent' });
1123
+ }
1124
+ else if (preset?.kind === 'gateway') {
1125
+ badges.push({ label: 'gateway', tone: 'accent' });
1126
+ }
1127
+ else {
1128
+ badges.push({ label: 'hosted', tone: 'muted' });
1129
+ }
1130
+ }
1131
+ if (input.currentProvider === input.provider) {
1132
+ badges.push({ label: 'current', tone: 'accent' });
1133
+ }
1134
+ return badges;
1135
+ }
1136
+ function buildSetupWizardAssistantMethodBadges(input) {
1137
+ return [
1138
+ ...(input.optionBadges ? [...input.optionBadges] : []),
1139
+ ...(input.currentMethod === input.method
1140
+ ? [{ label: 'current', tone: 'accent' }]
1141
+ : []),
1142
+ ];
1143
+ }
1144
+ function buildSetupWizardScheduledUpdateBadges(input) {
1145
+ return [
1146
+ ...(input.isStarter ? [{ label: 'recommended', tone: 'accent' }] : []),
1147
+ { label: 'set up later', tone: 'muted' },
1148
+ ];
1149
+ }
1150
+ function formatSetupWizardPromptTitle(step, provider) {
1151
+ switch (step) {
1152
+ case 'intro':
1153
+ return 'Before you start';
1154
+ case 'assistant-provider':
1155
+ return 'How should Murph answer?';
1156
+ case 'assistant-method':
1157
+ if (provider === 'openai') {
1158
+ return 'How should Murph connect to OpenAI?';
1159
+ }
1160
+ return 'How should Murph connect to your endpoint?';
1161
+ case 'scheduled-updates':
1162
+ return 'Auto updates';
1163
+ case 'channels':
1164
+ return 'Chat channels';
1165
+ case 'wearables':
1166
+ return 'Health data';
1167
+ case 'public-url':
1168
+ return 'Public links';
1169
+ case 'confirm':
1170
+ return 'Review';
1171
+ }
1172
+ }
1173
+ function formatSetupWizardStepIntro(step, provider) {
1174
+ switch (step) {
1175
+ case 'assistant-provider':
1176
+ return 'Choose the provider or endpoint style Murph should use by default.';
1177
+ case 'assistant-method':
1178
+ return provider === 'openai'
1179
+ ? 'Pick the OpenAI path that fits you best.'
1180
+ : 'Choose a manual endpoint or keep the Codex local-model flow.';
1181
+ case 'scheduled-updates':
1182
+ return 'These are optional check-ins Murph can send later.';
1183
+ case 'channels':
1184
+ return 'Turn on the chats you want Murph to use first.';
1185
+ case 'wearables':
1186
+ return 'Pick any health data sources you want to connect after setup.';
1187
+ default:
1188
+ return undefined;
1189
+ }
1190
+ }
1191
+ function createSetupWizardPanel(input) {
1192
+ const createElement = React.createElement;
1193
+ return createElement(Box, {
1194
+ borderColor: resolveSetupWizardToneColor(input.tone),
1195
+ borderStyle: 'round',
1196
+ flexDirection: 'column',
1197
+ paddingX: 1,
1198
+ paddingY: 0,
1199
+ }, createElement(Text, { color: resolveSetupWizardToneColor(input.tone), bold: true }, input.title), input.children.length > 0 ? createElement(Text, null, '') : null, ...input.children);
1200
+ }
1201
+ function createSetupWizardInlineBadgeElements(badges, keyPrefix) {
1202
+ const createElement = React.createElement;
1203
+ const elements = [];
1204
+ for (const [index, badge] of badges.entries()) {
1205
+ if (index > 0) {
1206
+ elements.push(createElement(Text, { key: `${keyPrefix}:space:${index}` }, ' '));
1207
+ }
1208
+ elements.push(createElement(Text, {
1209
+ bold: true,
1210
+ color: resolveSetupWizardToneColor(badge.tone),
1211
+ key: `${keyPrefix}:badge:${badge.label}:${index}`,
1212
+ }, `[${badge.label}]`));
1213
+ }
1214
+ return elements;
1215
+ }
1216
+ function createSetupWizardSelectionRow(input, key) {
1217
+ const createElement = React.createElement;
1218
+ const markerSymbol = input.marker === 'checkbox'
1219
+ ? input.line.selected
1220
+ ? '■'
1221
+ : '□'
1222
+ : input.line.selected
1223
+ ? '●'
1224
+ : '○';
1225
+ const markerTone = input.line.active
1226
+ ? 'accent'
1227
+ : input.line.selected
1228
+ ? 'success'
1229
+ : 'muted';
1230
+ const titleColor = input.line.active
1231
+ ? resolveSetupWizardToneColor('accent')
1232
+ : input.line.selected
1233
+ ? resolveSetupWizardToneColor('success')
1234
+ : undefined;
1235
+ return createElement(Box, {
1236
+ flexDirection: 'column',
1237
+ key,
1238
+ marginBottom: 1,
1239
+ }, createElement(Box, { flexDirection: 'row' }, createElement(Text, {
1240
+ color: input.line.active
1241
+ ? resolveSetupWizardToneColor('accent')
1242
+ : resolveSetupWizardToneColor('muted'),
1243
+ bold: input.line.active,
1244
+ }, `${input.line.active ? '›' : ' '} `), createElement(Text, {
1245
+ color: resolveSetupWizardToneColor(markerTone),
1246
+ bold: true,
1247
+ }, `${markerSymbol} `), createElement(Text, {
1248
+ color: titleColor,
1249
+ bold: true,
1250
+ }, input.line.title), input.line.badges.length > 0
1251
+ ? createElement(Box, {
1252
+ flexDirection: 'row',
1253
+ marginLeft: 1,
1254
+ }, createElement(Text, null, ...createSetupWizardInlineBadgeElements(input.line.badges, key)))
1255
+ : null), createElement(Text, { color: resolveSetupWizardToneColor('muted') }, ` ${input.line.description}`), input.line.detail
1256
+ ? createElement(Text, {
1257
+ color: resolveSetupWizardToneColor('muted'),
1258
+ dimColor: true,
1259
+ }, ` ${input.line.detail}`)
1260
+ : null);
1261
+ }
1262
+ function createSetupWizardAnsweredBlock(input, key) {
1263
+ const createElement = React.createElement;
1264
+ return createElement(Box, {
1265
+ flexDirection: 'column',
1266
+ key,
1267
+ marginBottom: 1,
1268
+ }, createElement(Text, { color: resolveSetupWizardToneColor('accent'), bold: true }, `◇ ${input.label}`), createElement(Text, { bold: true }, ` ${input.value}`), input.detail
1269
+ ? createElement(Text, {
1270
+ color: resolveSetupWizardToneColor('muted'),
1271
+ dimColor: true,
1272
+ }, ` ${input.detail}`)
1273
+ : null);
1274
+ }
1275
+ function createSetupWizardBulletRow(input, key) {
1276
+ const createElement = React.createElement;
1277
+ return createElement(Box, {
1278
+ flexDirection: 'column',
1279
+ key,
1280
+ marginBottom: 1,
1281
+ }, createElement(Text, null, createElement(Text, {
1282
+ color: resolveSetupWizardToneColor(input.tone),
1283
+ bold: true,
1284
+ }, `• ${input.label}: `), input.body));
1285
+ }
1286
+ function createSetupWizardKeyValueRow(input, key) {
1287
+ const createElement = React.createElement;
1288
+ return createElement(Box, {
1289
+ flexDirection: 'column',
1290
+ key,
1291
+ marginBottom: 1,
1292
+ }, createElement(Text, null, createElement(Text, {
1293
+ color: resolveSetupWizardToneColor('muted'),
1294
+ bold: true,
1295
+ }, `${input.label}: `), input.value));
1296
+ }
1297
+ function createSetupWizardPublicUrlTargetRow(target) {
1298
+ const createElement = React.createElement;
1299
+ return createElement(Box, {
1300
+ flexDirection: 'column',
1301
+ key: target.label,
1302
+ marginBottom: 1,
1303
+ }, createElement(Text, null, createElement(Text, {
1304
+ color: resolveSetupWizardToneColor('muted'),
1305
+ bold: true,
1306
+ }, `${target.label}: `), createElement(Text, { color: resolveSetupWizardToneColor('accent') }, target.url)), createElement(Text, {
1307
+ color: resolveSetupWizardToneColor('muted'),
1308
+ dimColor: true,
1309
+ }, ` ${target.detail}`));
1310
+ }
1311
+ function resolveSetupWizardHints(input) {
1312
+ switch (input.step) {
1313
+ case 'intro':
1314
+ return [
1315
+ { label: `Enter start ${input.commandName}`, tone: 'accent' },
1316
+ { label: 'q quit', tone: 'muted' },
1317
+ ];
1318
+ case 'assistant-provider':
1319
+ case 'assistant-method':
1320
+ case 'scheduled-updates':
1321
+ case 'channels':
1322
+ case 'wearables':
1323
+ return [
1324
+ { label: '↑/↓ move', tone: 'muted' },
1325
+ {
1326
+ label: input.selectionMarker === 'radio' ? 'Space choose' : 'Space toggle',
1327
+ tone: 'accent',
1328
+ },
1329
+ { label: 'Enter next', tone: 'success' },
1330
+ { label: 'Esc back', tone: 'muted' },
1331
+ { label: 'q quit', tone: 'muted' },
1332
+ ];
1333
+ case 'public-url':
1334
+ return [
1335
+ { label: 'Enter next', tone: 'success' },
1336
+ { label: 'Esc back', tone: 'muted' },
1337
+ { label: 'q quit', tone: 'muted' },
1338
+ ];
1339
+ case 'confirm':
1340
+ return [
1341
+ { label: 'Enter start setup', tone: 'success' },
1342
+ { label: 'Esc back', tone: 'muted' },
1343
+ { label: 'q quit', tone: 'muted' },
1344
+ ];
1345
+ }
1346
+ }
1347
+ function createSetupWizardHintRow(hints) {
1348
+ const createElement = React.createElement;
1349
+ return createElement(Text, null, ...createSetupWizardInlineBadgeElements(hints, 'hint'));
1350
+ }
1351
+ function describeSetupWizardReviewNextStep(input) {
1352
+ if (input.needsEnv && input.hasScheduledUpdates) {
1353
+ return 'Murph will ask for any missing keys for this setup run, finish setup, keep your update picks ready for later, and open anything that can connect right away.';
1354
+ }
1355
+ if (input.needsEnv) {
1356
+ return 'Murph will ask for any missing keys for this setup run, finish setup, and open anything that can connect right away.';
1357
+ }
1358
+ if (input.hasScheduledUpdates) {
1359
+ return 'Murph will finish setup, keep your update picks ready for later, and open anything that can connect right away.';
1360
+ }
1361
+ return 'Murph will finish setup and open anything that can connect right away.';
1362
+ }
1363
+ export function buildSetupWizardPublicUrlReview(input) {
1364
+ const publicBaseUrl = normalizeSetupWizardText(input.publicBaseUrl);
1365
+ const hasLinq = input.channels.includes('linq');
1366
+ const selectedWearables = sortSetupWizardWearables(input.wearables);
1367
+ const needsPublicStrategy = hasLinq || selectedWearables.length > 0;
1368
+ const deviceSyncLocalBaseUrl = normalizeSetupWizardText(input.deviceSyncLocalBaseUrl) ??
1369
+ DEFAULT_SETUP_DEVICE_SYNC_LOCAL_BASE_URL;
1370
+ const linqLocalWebhookUrl = normalizeSetupWizardText(input.linqLocalWebhookUrl) ??
1371
+ DEFAULT_SETUP_LINQ_WEBHOOK_URL;
1372
+ if (!needsPublicStrategy || publicBaseUrl) {
1373
+ return {
1374
+ enabled: false,
1375
+ recommendedStrategy: 'hosted',
1376
+ summary: '',
1377
+ targets: [],
1378
+ };
1379
+ }
1380
+ return {
1381
+ enabled: true,
1382
+ recommendedStrategy: selectedWearables.length > 0 ? 'hosted' : 'tunnel',
1383
+ summary: describeSetupWizardPublicUrlSummary({
1384
+ hasLinq,
1385
+ wearables: selectedWearables,
1386
+ }),
1387
+ targets: buildSetupWizardPublicUrlTargets({
1388
+ hasLinq,
1389
+ wearables: selectedWearables,
1390
+ deviceSyncLocalBaseUrl,
1391
+ linqLocalWebhookUrl,
1392
+ }),
1393
+ };
1394
+ }
1395
+ export function describeSetupWizardPublicUrlStrategyChoice(input) {
1396
+ if (!input.review.enabled) {
1397
+ return '';
1398
+ }
1399
+ if (input.strategy === 'hosted') {
1400
+ const hasLinq = input.review.targets.some((target) => target.label === 'Linq webhook');
1401
+ return hasLinq
1402
+ ? 'Use hosted `apps/web` for WHOOP/Oura, but keep Linq on the local webhook path for now.'
1403
+ : 'Use hosted `apps/web` for WHOOP/Oura so callbacks and webhooks stay on one stable public base.';
1404
+ }
1405
+ const hasWearableTargets = input.review.targets.some((target) => target.label.startsWith('WHOOP') || target.label.startsWith('Oura'));
1406
+ if (hasWearableTargets) {
1407
+ return 'Expose the local callback and webhook routes through a tunnel instead of setting up hosted `apps/web` first.';
1408
+ }
1409
+ return 'Expose the local Linq webhook through a tunnel. Murph does not have a hosted Linq webhook yet.';
1410
+ }
1411
+ function describeSetupWizardPublicUrlSummary(input) {
1412
+ if (input.wearables.length > 0 && input.hasLinq) {
1413
+ return 'WHOOP/Oura are easiest through hosted `apps/web`, while Linq still needs the local inbox webhook today.';
1414
+ }
1415
+ if (input.wearables.length > 0) {
1416
+ return 'WHOOP/Oura need a public callback URL. Hosted `apps/web` is the easiest stable base.';
1417
+ }
1418
+ return 'Linq still uses the local inbox webhook today, so a tunnel to your machine is the simplest public path.';
1419
+ }
1420
+ function buildSetupWizardPublicUrlTargets(input) {
1421
+ const targets = [];
1422
+ if (input.wearables.includes('whoop')) {
1423
+ targets.push({
1424
+ label: 'WHOOP callback',
1425
+ url: new URL('/oauth/whoop/callback', input.deviceSyncLocalBaseUrl).toString(),
1426
+ detail: 'Use this if WHOOP sends the callback directly to your machine through a tunnel.',
1427
+ });
1428
+ targets.push({
1429
+ label: 'WHOOP webhook',
1430
+ url: new URL('/webhooks/whoop', input.deviceSyncLocalBaseUrl).toString(),
1431
+ detail: 'Use this if WHOOP sends webhooks straight to your machine through a tunnel.',
1432
+ });
1433
+ }
1434
+ if (input.wearables.includes('oura')) {
1435
+ targets.push({
1436
+ label: 'Oura callback',
1437
+ url: new URL('/oauth/oura/callback', input.deviceSyncLocalBaseUrl).toString(),
1438
+ detail: 'Use this if Oura finishes sign-in on your machine through a tunnel.',
1439
+ });
1440
+ targets.push({
1441
+ label: 'Oura webhook',
1442
+ url: new URL('/webhooks/oura', input.deviceSyncLocalBaseUrl).toString(),
1443
+ detail: 'Optional today. Oura can still work without this, but this is the local webhook URL if you enable it.',
1444
+ });
1445
+ }
1446
+ if (input.hasLinq) {
1447
+ targets.push({
1448
+ label: 'Linq webhook',
1449
+ url: input.linqLocalWebhookUrl,
1450
+ detail: 'Point your tunnel here. Hosted `apps/web` does not replace this Linq webhook yet.',
1451
+ });
1452
+ }
1453
+ return targets;
1454
+ }
1455
+ function normalizeSetupWizardText(value) {
1456
+ return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
1457
+ }
1458
+ //# sourceMappingURL=setup-wizard.js.map