@moondeck/station 0.0.1 → 1.0.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 (330) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -2
  3. package/bin/uai-host.mjs +130 -0
  4. package/db/migrations/0000_host_tasks.sql +12 -0
  5. package/db/migrations/0001_host_ui.sql +11 -0
  6. package/db/migrations/0002_host_github_tokens.sql +8 -0
  7. package/db/migrations/0003_host_ssh_keys.sql +8 -0
  8. package/db/migrations/0004_host_owner_name.sql +1 -0
  9. package/db/migrations/0005_host_env_vars.sql +5 -0
  10. package/db/migrations/0006_host_project_env.sql +9 -0
  11. package/db/migrations/0007_host_github_token_kind.sql +7 -0
  12. package/db/migrations/0008_host_mcp_connections.sql +18 -0
  13. package/db/migrations/0009_host_agent_sessions.sql +15 -0
  14. package/db/migrations/0010_host_engine_accounts.sql +13 -0
  15. package/db/migrations/0011_codex_thread_id.sql +5 -0
  16. package/db/migrations/0012_agent_session_attach_key.sql +4 -0
  17. package/db/migrations/0013_github_credential_generations.sql +11 -0
  18. package/db/migrations/0014_host_inventory_event_index.sql +1 -0
  19. package/db/migrations/0015_host_settings.sql +9 -0
  20. package/db/migrations/0016_task_environment.sql +2 -0
  21. package/db/migrations/0017_engine_account_scope.sql +7 -0
  22. package/db/migrations/0018_engine_account_position.sql +3 -0
  23. package/db/migrations/0019_engine_account_store_ref.sql +3 -0
  24. package/db/migrations/0020_user_credential_store_ref.sql +6 -0
  25. package/db/migrations/0021_project_env_store_ref.sql +3 -0
  26. package/db/migrations/0022_task_project_ids.sql +5 -0
  27. package/db/migrations/0023_project_env_org.sql +4 -0
  28. package/db/migrations/0024_task_env_mode.sql +9 -0
  29. package/db/migrations/0025_project_env_authority.sql +147 -0
  30. package/db/migrations/0026_project_env_replica.sql +150 -0
  31. package/db/migrations/0027_cloud_credential_foundation.sql +51 -0
  32. package/db/migrations/0028_leased_engine_runtimes.sql +11 -0
  33. package/db/migrations/0029_task_shared_files_mode.sql +5 -0
  34. package/db/migrations/0030_credential_cleanup_restops.sql +12 -0
  35. package/db/migrations/0031_first_turn_receipts.sql +9 -0
  36. package/db/migrations/meta/_journal.json +230 -0
  37. package/db/schema.ts +871 -0
  38. package/images/standard/Dockerfile +347 -0
  39. package/images/standard/README.md +167 -0
  40. package/images/standard/container/code-server-settings.json +63 -0
  41. package/images/standard/container/corepack-version +1 -0
  42. package/images/standard/container/uai-init +704 -0
  43. package/images/standard/container/uai-materialize-runtimes +1527 -0
  44. package/images/standard/container/uai-task-forwarder.mjs +303 -0
  45. package/images/standard/tool-versions +2 -0
  46. package/lib/__fixtures__/skill-index/SKILL.md +10 -0
  47. package/lib/__fixtures__/skill-index/skills/accessibility-checklist.md +7 -0
  48. package/lib/__fixtures__/skill-index/skills/brand-voice.md +9 -0
  49. package/lib/__fixtures__/skill-index/skills/design-tokens.md +9 -0
  50. package/lib/agent-cli.ts +1507 -0
  51. package/lib/agent-errors.ts +22 -0
  52. package/lib/agent.ts +672 -0
  53. package/lib/agents/attach-compatibility.ts +51 -0
  54. package/lib/agents/claude.ts +918 -0
  55. package/lib/agents/codex-token-usage.fixture.json +130 -0
  56. package/lib/agents/codex.ts +1392 -0
  57. package/lib/agents/cursor-period-usage.ts +253 -0
  58. package/lib/agents/cursor.ts +562 -0
  59. package/lib/agents/dispatch.ts +78 -0
  60. package/lib/agents/durable-proc.ts +334 -0
  61. package/lib/agents/factory.ts +57 -0
  62. package/lib/agents/grok.ts +381 -0
  63. package/lib/agents/kimi.ts +329 -0
  64. package/lib/agents/machine-durable.ts +604 -0
  65. package/lib/agents/mock.ts +133 -0
  66. package/lib/agents/mode.ts +64 -0
  67. package/lib/agents/opencode.ts +508 -0
  68. package/lib/agents/output-source.ts +3 -0
  69. package/lib/agents/proc.ts +221 -0
  70. package/lib/agents/rate-limit.ts +337 -0
  71. package/lib/agents/registry.ts +182 -0
  72. package/lib/agents/session-state.ts +197 -0
  73. package/lib/agents/skill-index-preamble.fixture.ts +26 -0
  74. package/lib/agents/transport.ts +741 -0
  75. package/lib/agents/types.ts +254 -0
  76. package/lib/agents/uai.ts +106 -0
  77. package/lib/agents/usage.ts +44 -0
  78. package/lib/apple-one-off.ts +277 -0
  79. package/lib/apple-runtime-recycle.ts +237 -0
  80. package/lib/apple-uninstall-teardown.ts +224 -0
  81. package/lib/assignment-evidence.ts +94 -0
  82. package/lib/attachments.ts +48 -0
  83. package/lib/browser-testing.ts +1537 -0
  84. package/lib/capture-flow.ts +332 -0
  85. package/lib/cloud-credential-cleanup-adapter.ts +123 -0
  86. package/lib/cloud-credential-host.ts +512 -0
  87. package/lib/cloud-credential-leases.ts +2587 -0
  88. package/lib/cloud-credential-runtime.ts +1357 -0
  89. package/lib/cloud-state.ts +56 -0
  90. package/lib/codex-auth.ts +456 -0
  91. package/lib/command-db.ts +310 -0
  92. package/lib/compose-network-recovery.ts +283 -0
  93. package/lib/connection-secret-redaction.ts +25 -0
  94. package/lib/container-runtime.ts +1470 -0
  95. package/lib/credential-cleanup-inventory.ts +490 -0
  96. package/lib/credential-consumers/engine-anchor.ts +1880 -0
  97. package/lib/credential-consumers/engines.ts +797 -0
  98. package/lib/credential-consumers/files.ts +934 -0
  99. package/lib/credential-consumers/github.ts +702 -0
  100. package/lib/credential-consumers/mcp.ts +389 -0
  101. package/lib/credential-consumers/task-files.ts +127 -0
  102. package/lib/credential-consumers/task-secrets.ts +10 -0
  103. package/lib/credential-store-pickup.ts +149 -0
  104. package/lib/credential-store-scope.ts +112 -0
  105. package/lib/credential-store.ts +932 -0
  106. package/lib/credentials-activation-authority.ts +709 -0
  107. package/lib/credentials-purge.ts +1006 -0
  108. package/lib/custom-connection-capture.ts +395 -0
  109. package/lib/custom-connection-launch.ts +384 -0
  110. package/lib/custom-connection-runtime.ts +141 -0
  111. package/lib/db.ts +80 -0
  112. package/lib/disk-pressure.ts +42 -0
  113. package/lib/docker-exec.ts +156 -0
  114. package/lib/engine-accounts.ts +1970 -0
  115. package/lib/engine-connect.ts +321 -0
  116. package/lib/engine-credential-env.ts +52 -0
  117. package/lib/engine-login.ts +2933 -0
  118. package/lib/engines.ts +1342 -0
  119. package/lib/enrollment-state.ts +251 -0
  120. package/lib/env-file.ts +246 -0
  121. package/lib/env-schema.ts +937 -0
  122. package/lib/env.ts +195 -0
  123. package/lib/first-turn-receipts.ts +62 -0
  124. package/lib/fs-aliases.ts +49 -0
  125. package/lib/git-clone-credentials.ts +334 -0
  126. package/lib/git-diff.ts +530 -0
  127. package/lib/git-identity.ts +715 -0
  128. package/lib/github-git-auth.ts +207 -0
  129. package/lib/github-origin.ts +28 -0
  130. package/lib/github-tokens.ts +2439 -0
  131. package/lib/host-cloud-url.ts +62 -0
  132. package/lib/host-config.ts +279 -0
  133. package/lib/host-env.ts +424 -0
  134. package/lib/host-logs.ts +965 -0
  135. package/lib/internal-connections.ts +5 -0
  136. package/lib/keyed-promise-tail.ts +23 -0
  137. package/lib/launch-mode.ts +25 -0
  138. package/lib/lease.ts +239 -0
  139. package/lib/legacy-host.ts +769 -0
  140. package/lib/legacy-runtime-retirement.ts +424 -0
  141. package/lib/legacy-runtime-v1.fixture.ts +627 -0
  142. package/lib/machine-exec.ts +238 -0
  143. package/lib/machine-gateway-tunnel.ts +344 -0
  144. package/lib/machine-keys.ts +65 -0
  145. package/lib/machine-project-env-loader.ts +152 -0
  146. package/lib/machine-provider-aws.ts +923 -0
  147. package/lib/machine-provider-local.ts +338 -0
  148. package/lib/machine-provider.ts +90 -0
  149. package/lib/managed-activation-watcher.ts +72 -0
  150. package/lib/managed-install-owner-watcher.ts +55 -0
  151. package/lib/managed-operation-drain.ts +57 -0
  152. package/lib/managed-runtime.ts +4296 -0
  153. package/lib/managed-update-scheduler.ts +125 -0
  154. package/lib/mcp-capture.ts +865 -0
  155. package/lib/mcp-config-lock.ts +2 -0
  156. package/lib/mcp-connections.ts +959 -0
  157. package/lib/mcp-gateway.ts +2594 -0
  158. package/lib/obs.ts +512 -0
  159. package/lib/openrouter-budget.ts +247 -0
  160. package/lib/orchestrator.ts +11409 -0
  161. package/lib/person-capture.ts +327 -0
  162. package/lib/preview-ports.ts +85 -0
  163. package/lib/preview-sidecar.ts +331 -0
  164. package/lib/project-env-authority.ts +1319 -0
  165. package/lib/project-env-feature.ts +74 -0
  166. package/lib/project-env-legacy-tags.ts +130 -0
  167. package/lib/project-env-loader.ts +242 -0
  168. package/lib/project-env-replica.ts +2094 -0
  169. package/lib/project-env-schema-capture.ts +342 -0
  170. package/lib/project-env-staging.ts +148 -0
  171. package/lib/project-env-task-policy.ts +176 -0
  172. package/lib/project-env-task-replica.ts +1752 -0
  173. package/lib/release-feed.ts +61 -0
  174. package/lib/release-manifest.ts +710 -0
  175. package/lib/release-trust.ts +46 -0
  176. package/lib/repo-clone.ts +38 -0
  177. package/lib/routing/shadow.ts +261 -0
  178. package/lib/runner-boot.ts +10 -0
  179. package/lib/runtime-activation-tail.ts +279 -0
  180. package/lib/runtime-archive.ts +1216 -0
  181. package/lib/runtime-authority.ts +166 -0
  182. package/lib/runtime-guard.ts +36 -0
  183. package/lib/runtime-provider-state.ts +169 -0
  184. package/lib/runtime-reconnect.ts +86 -0
  185. package/lib/runtime-state.ts +1033 -0
  186. package/lib/safe-http.ts +364 -0
  187. package/lib/secrets-proxy/address.ts +95 -0
  188. package/lib/secrets-proxy/audit.ts +30 -0
  189. package/lib/secrets-proxy/ca-store.ts +200 -0
  190. package/lib/secrets-proxy/ca.ts +470 -0
  191. package/lib/secrets-proxy/container-steps.ts +138 -0
  192. package/lib/secrets-proxy/der.ts +273 -0
  193. package/lib/secrets-proxy/engine.ts +1633 -0
  194. package/lib/secrets-proxy/host.ts +93 -0
  195. package/lib/secrets-proxy/hostname.ts +57 -0
  196. package/lib/secrets-proxy/placeholders.ts +196 -0
  197. package/lib/secrets-proxy/policy.ts +150 -0
  198. package/lib/secrets-proxy/readiness.ts +148 -0
  199. package/lib/secrets-proxy/runtime.ts +582 -0
  200. package/lib/secrets-proxy/scrub.ts +559 -0
  201. package/lib/secrets-proxy/sidecar-view.ts +139 -0
  202. package/lib/secrets-proxy/task-scripts.ts +262 -0
  203. package/lib/secrets-proxy/task-trust.ts +605 -0
  204. package/lib/secrets-proxy/trust-watch.ts +80 -0
  205. package/lib/secrets.ts +71 -0
  206. package/lib/shared-files-s3.ts +976 -0
  207. package/lib/shared-files.ts +156 -0
  208. package/lib/sidecar-assign.ts +49 -0
  209. package/lib/sidecar-guard.ts +58 -0
  210. package/lib/sidecar-resume.ts +178 -0
  211. package/lib/sidecar.ts +69 -0
  212. package/lib/signin-box-credentials.ts +465 -0
  213. package/lib/signin-box.ts +114 -0
  214. package/lib/skill-index-example.fixture.ts +56 -0
  215. package/lib/skill-layout.ts +417 -0
  216. package/lib/skills.ts +489 -0
  217. package/lib/ssh.ts +401 -0
  218. package/lib/standard-image-cleanup.ts +117 -0
  219. package/lib/standard-image-pin.ts +207 -0
  220. package/lib/standard-image.ts +1697 -0
  221. package/lib/stopped-task-status-queue.ts +120 -0
  222. package/lib/task-container-cli.ts +269 -0
  223. package/lib/task-credential-cleanup.ts +752 -0
  224. package/lib/task-diff-sandbox.ts +464 -0
  225. package/lib/task-diff.ts +91 -0
  226. package/lib/task-env-forwarder.ts +228 -0
  227. package/lib/task-env-source.ts +249 -0
  228. package/lib/task-environment/anchor.ts +31 -0
  229. package/lib/task-environment/apple-container.ts +860 -0
  230. package/lib/task-environment/docker.ts +1344 -0
  231. package/lib/task-environment/index.ts +466 -0
  232. package/lib/task-environment/legacy-adoption.ts +467 -0
  233. package/lib/task-environment/machine-environment.ts +61 -0
  234. package/lib/task-environment/machine-task-up.ts +1227 -0
  235. package/lib/task-environment/machine.ts +974 -0
  236. package/lib/task-environment/registry.ts +59 -0
  237. package/lib/task-environment/self-identity.ts +20 -0
  238. package/lib/task-environment/self.ts +566 -0
  239. package/lib/task-environment/types.ts +642 -0
  240. package/lib/task-environment/workspace-files.ts +685 -0
  241. package/lib/task-environment/workspace-runtime.fixture.ts +50 -0
  242. package/lib/task-files-machine-retirement-outbox.ts +592 -0
  243. package/lib/task-files-machine-retirement.ts +116 -0
  244. package/lib/task-files-readiness.ts +127 -0
  245. package/lib/task-gateway-port.ts +22 -0
  246. package/lib/task-gateway-protocol.ts +190 -0
  247. package/lib/task-gateway-recovery.ts +47 -0
  248. package/lib/task-gateway-state.ts +512 -0
  249. package/lib/task-identity.ts +19 -0
  250. package/lib/task-inventory.ts +682 -0
  251. package/lib/task-machine-credentials-activation.ts +95 -0
  252. package/lib/task-machine-swap-safety.ts +729 -0
  253. package/lib/task-person-credentials.ts +76 -0
  254. package/lib/task-project-ops-host.ts +147 -0
  255. package/lib/task-project-ops.ts +1062 -0
  256. package/lib/task-secret-staging.ts +16 -0
  257. package/lib/task-status.ts +46 -0
  258. package/lib/task-token.ts +46 -0
  259. package/lib/token-broker-adopt.ts +344 -0
  260. package/lib/token-broker-lambda.ts +105 -0
  261. package/lib/token-broker-local.ts +392 -0
  262. package/lib/token-broker-revision.ts +310 -0
  263. package/lib/token-broker-ssm.ts +525 -0
  264. package/lib/token-broker.ts +218 -0
  265. package/lib/token-lanes/openai-oauth.ts +123 -0
  266. package/lib/token-lanes/registry.ts +45 -0
  267. package/lib/token-lanes/types.ts +163 -0
  268. package/lib/token-lanes/xai-oauth.ts +165 -0
  269. package/lib/transcript.ts +128 -0
  270. package/lib/tunnel-registry.ts +220 -0
  271. package/lib/tunnel-runtime.ts +235 -0
  272. package/lib/ulid.ts +16 -0
  273. package/lib/user-bus-env.ts +116 -0
  274. package/lib/user-credentials-store.ts +435 -0
  275. package/lib/vnode-pressure.ts +142 -0
  276. package/package.json +115 -8
  277. package/runner/runner.mjs +442 -0
  278. package/scripts/agent/_common.sh +633 -0
  279. package/scripts/agent/env-hook.sh +626 -0
  280. package/scripts/agent/moondeck-cli.sh +5 -0
  281. package/scripts/agent/task-down.sh +291 -0
  282. package/scripts/agent/task-status.sh +70 -0
  283. package/scripts/agent/task-up.sh +3761 -0
  284. package/scripts/agent/uai-env-exec.sh +26 -0
  285. package/scripts/install/darwin.ts +1048 -0
  286. package/scripts/install/linux.ts +958 -0
  287. package/scripts/install/types.ts +78 -0
  288. package/scripts/install/util.ts +254 -0
  289. package/scripts/install/win.ts +143 -0
  290. package/src/activation-respawn.ts +155 -0
  291. package/src/apple-tunnel-route.ts +104 -0
  292. package/src/bridge-diagnostics.ts +39 -0
  293. package/src/capture-wire.ts +102 -0
  294. package/src/cli.ts +2585 -0
  295. package/src/cloud-credentials.ts +2144 -0
  296. package/src/cloud-origins.ts +23 -0
  297. package/src/credential-location.ts +248 -0
  298. package/src/credential-wire-validation.ts +25 -0
  299. package/src/custom-connections.ts +237 -0
  300. package/src/engine-account-paths.ts +11 -0
  301. package/src/engine-models.ts +39 -0
  302. package/src/engine-usage.ts +104 -0
  303. package/src/enrollment-next-steps.ts +24 -0
  304. package/src/event-outbox.ts +185 -0
  305. package/src/index.ts +2907 -0
  306. package/src/load-env.ts +97 -0
  307. package/src/machine-tunnel-route.ts +156 -0
  308. package/src/main.ts +6363 -0
  309. package/src/never-proxied-domains.ts +31 -0
  310. package/src/outbound-frame.ts +31 -0
  311. package/src/paths.ts +81 -0
  312. package/src/project-env-latest.ts +130 -0
  313. package/src/project-env-policy.ts +396 -0
  314. package/src/project-env-wording.ts +53 -0
  315. package/src/protocol.ts +5550 -0
  316. package/src/runtime-bootstrap.ts +165 -0
  317. package/src/station-identity.ts +96 -0
  318. package/src/task-env-names.ts +28 -0
  319. package/src/task-secrets.ts +75 -0
  320. package/src/tunnel-dial.ts +102 -0
  321. package/src/ui/server.ts +852 -0
  322. package/src/ui/types.ts +173 -0
  323. package/src/workspace-path-policy.ts +41 -0
  324. package/ui/app.js +1102 -0
  325. package/ui/index.html +90 -0
  326. package/ui/style.css +685 -0
  327. package/ui/uai-favicon.svg +21 -0
  328. package/ui/uai-logo-black.svg +9 -0
  329. package/ui/uai-wheel.svg +9 -0
  330. package/bin/moondeck-station.mjs +0 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Diogo Perillo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,150 @@
1
1
  # @moondeck/station
2
- This is a placeholder package that reserves the name for Moondeck Station.
3
- The first release is coming soon; see https://moondeck.io.
2
+
3
+ **Moondeck Station**, the host for [Moondeck](https://moondeck.io) — a self-hosted service
4
+ that runs parallel AI coding tasks in ephemeral Docker containers on a machine
5
+ **you** control. This package is the long-running per-user daemon that connects
6
+ your machine to the Moondeck cloud and runs the task containers locally. It also
7
+ serves a small read-only monitor UI on `127.0.0.1` (ADR-028).
8
+
9
+ One process does three things:
10
+
11
+ - **Cloud bridge** — a host-initiated *outbound* WSS to
12
+ `wss://app.moondeck.io/host` carrying every command/tunnel. The cloud never
13
+ connects in; nothing inbound is exposed.
14
+ - **Local UI** — an HTTP server on `127.0.0.1:5876` (next free port if taken):
15
+ connection badge, service info, active tasks, recent events. Localhost only,
16
+ no auth, read-only — mutations come from the cloud, not here.
17
+ - **MCP gateway** — a credential-injecting HTTP proxy for task containers,
18
+ bound to `127.0.0.1:5877` by default. Listener failures retry with capped
19
+ backoff and are advertised to the cloud as bounded health state.
20
+
21
+ ## Requirements
22
+
23
+ - **Docker** (running) — tasks are containers.
24
+ - **Node 22, 24, 25 or 26; Node 24 LTS recommended.** Those are the majors the
25
+ pinned SQLite dependency ships a prebuilt binary for. Tested release majors
26
+ are 22, 24 and 26; Node 25 warns and continues when the binding loads.
27
+ Anything else — including Node 20, 21 and 23 — has to compile from source
28
+ and fails on a machine without a C++ toolchain.
29
+
30
+ ## Install & enroll
31
+
32
+ ```bash
33
+ npm i -g @moondeck/station
34
+ ```
35
+
36
+ Then, in the Moondeck web app, open an organization and choose **“Add a host.”** It
37
+ mints a one-time enrollment token and shows the exact command to run on this
38
+ machine (ADR-031):
39
+
40
+ ```bash
41
+ moondeck-station setup --cloud wss://app.moondeck.io/host --enroll uaienroll_…
42
+ moondeck-station install # per-user service: launchd (macOS) / systemd --user (Linux)
43
+ moondeck-station start
44
+ ```
45
+
46
+ The host generates its **own** bridge credential locally. The cloud persists
47
+ only its hash; the raw credential transits authenticated TLS for bridge auth
48
+ and existing-host attachment but is not stored there (ADR-015). Once started
49
+ it appears in your org and can run tasks. Data + config live under `~/.moondeck`
50
+ (`~/.moondeck/.env.local`, `~/.moondeck/data/`); from a repo checkout they stay under the
51
+ repo instead. The monitor UI is at `http://127.0.0.1:5876` (exact port in
52
+ `moondeck-station status`).
53
+
54
+ ## CLI (`moondeck-station`)
55
+
56
+ ```
57
+ setup --cloud <wss-url> --enroll <token>
58
+ claim this machine, or enroll its existing identity in
59
+ the token's workspace
60
+ enroll <token> [--cloud <wss-url>]
61
+ first-time setup, or attach this machine to another
62
+ workspace; uses the stored cloud URL when available
63
+ install [--dry-run] install as a per-user service for this OS
64
+ uninstall remove the service
65
+ start | stop control the installed service
66
+ restart stop + start
67
+ status connection, service info, active tasks (same data as the UI)
68
+ logs [--follow] tail the service log
69
+ run run in the foreground (debug)
70
+ pair <token> store a host token directly (manual fallback for setup)
71
+ open open the local UI in your browser
72
+ ```
73
+
74
+ `enroll` is available in host version 0.9.15 and newer. Its compatible cloud
75
+ endpoint is intentionally deployed before that host release.
76
+
77
+ `--dry-run` on `install` (and start/stop/…) prints the unit file + load
78
+ commands without touching launchd/systemd.
79
+
80
+ ## Headless
81
+
82
+ Runs identically with no display: install via the same flow, skip `moondeck-station
83
+ open`, and use `moondeck-station status` for everything the UI shows. The UI stays
84
+ reachable over an SSH port-forward to `127.0.0.1:5876` if you want it.
85
+
86
+ At boot, task and channel starts wait for the initial agent-CLI maintenance
87
+ pass to settle before any in-container writer or station executes. The cloud
88
+ host page shows this short preparation state. It also shows MCP gateway
89
+ listener failures while the host retries them; gateway health is independent
90
+ of ordinary task admission.
91
+
92
+ ## Security
93
+
94
+ Tasks run in ephemeral containers and **do not** mount your live AI session
95
+ dirs. Per-task credentials (GitHub tokens, SSH keys) are decrypted only on the
96
+ host, using a host-resident master key; the cloud stores only hashes and public
97
+ keys. See [ADR-015](https://github.com/moondeckio/uai/blob/main/docs/decisions.md)
98
+ for the secret-blind model.
99
+
100
+ ## Dev (from a repo checkout)
101
+
102
+ `pnpm host-agent` (from the repo root) = `moondeck-station run` — the service in the
103
+ foreground, no install, logs to stdout. The packaged binary runs the same
104
+ TypeScript source through [tsx](https://github.com/privatenumber/tsx); there is
105
+ no build step (ADR-032).
106
+
107
+ ## More
108
+
109
+ - [Moondeck on GitHub](https://github.com/moondeckio/uai)
110
+ - [docs/host-ui.md](https://github.com/moondeckio/uai/blob/main/docs/host-ui.md) — the local UI surface.
111
+ - [docs/host-packaging.md](https://github.com/moondeckio/uai/blob/main/docs/host-packaging.md) — how this package is built and published.
112
+ </content>
113
+
114
+ ## Telemetry
115
+
116
+ Installed hosts report **crashes only** to Sentry by default. Sent on a
117
+ crash: the error and stack trace (paths and secret-shaped strings
118
+ redacted), recent bridge-connection breadcrumbs, OS/runtime versions, the
119
+ package version, and a pseudonymous host id. No sessions, no usage pings,
120
+ and no request data — nothing is sent until something actually crashes.
121
+ Telemetry is designed to exclude chat, prompts, env values, and repo
122
+ content; error text is scrubbed for secret shapes, but redaction is
123
+ pattern-based, not perfect. Repo checkouts and dev/test runs never report,
124
+ and reporting only activates after this notice has been shown once (the
125
+ `install`/`pair`/`setup`/`start`/`restart` commands print it — a silently
126
+ upgraded service stays off until one of them runs).
127
+
128
+ Opt out any time — add to `$UAI_HOME/.env.local` (default
129
+ `~/.moondeck/.env.local`), then `moondeck-station restart`:
130
+
131
+ ```
132
+ UAI_TELEMETRY_DISABLED=1
133
+ ```
134
+
135
+ `UAI_SENTRY_DSN=<dsn>` redirects reports to your own Sentry instead.
136
+
137
+ ## Moving from the old Uai host (`@runuai/host`)
138
+
139
+ A station never runs beside the old host. Remove the old one first: its
140
+ service, runtime, `uai-host` commands and leftover task containers. `~/.uai`
141
+ is kept unless you pass `--purge`; it refuses while the old host runs tasks
142
+ unless you pass `--force` (their work is lost):
143
+
144
+ ```sh
145
+ npx @moondeck/station uninstall-legacy # or: moondeck-station uninstall-legacy
146
+ npx @moondeck/station install
147
+ moondeck-station enroll <token from app.moondeck.io>
148
+ ```
149
+
150
+ With the one-line installer: `curl -fsSL https://app.moondeck.io/install | sh -s -- --uninstall-legacy --enroll <token>`.
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Published entrypoint for `@moondeck/station`, its `moondeck-station` bin (ADR-032). The file keeps its
4
+ * pre-rename name: the runtime archive layout pins `agent/bin/uai-host.mjs`.
5
+ *
6
+ * The host ships TypeScript source and runs it through tsx — no build step —
7
+ * so every runtime path (drizzle migrations, scripts/agent/*.sh, the
8
+ * images/standard build context) resolves off its source file exactly as in
9
+ * dev. This shim verifies that the native SQLite dependency can load,
10
+ * registers tsx's ESM loader, then hands off to src/cli.ts. argv passes
11
+ * straight through: src/cli.ts reads process.argv.slice(2).
12
+ */
13
+
14
+ // A builtin — importing it here loads nothing of the app and no tsx.
15
+ import { stripVTControlCharacters } from "node:util";
16
+
17
+ const minimumNodeMajor = 20;
18
+ const testedNodeMajors = new Set([22, 24, 26]);
19
+ const nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "", 10);
20
+
21
+ /**
22
+ * The commands that BOOT the agent, and so genuinely need the native module.
23
+ *
24
+ * Everything else — `logs`, `status`, `open`, `stop`, `uninstall`, `setup`,
25
+ * `--help` — only inspects or dismantles an install, and all of them were
26
+ * verified to run with the module unloadable. Failing them on a broken probe
27
+ * takes away the diagnostic path (`logs`) and the recovery path (`stop`,
28
+ * `uninstall`) from precisely the user the probe exists to help: they are told
29
+ * to reinstall, and cannot remove the wedged service first.
30
+ *
31
+ * `start`/`restart`/`install` don't open the database in THIS process — they
32
+ * hand off to launchd/systemd — but they all end in a running agent, so
33
+ * failing fast here beats a crash-looping service the user then has to chase
34
+ * through the log.
35
+ */
36
+ const commandsNeedingNativeSqlite = new Set([
37
+ "run",
38
+ "start",
39
+ "restart",
40
+ "install",
41
+ ]);
42
+
43
+ /**
44
+ * Of those, the ones whose `--dry-run` prints a plan and boots nothing.
45
+ *
46
+ * `run` is deliberately absent: it ignores the flag and boots regardless, so
47
+ * exempting on the flag alone would wave through the one command that really
48
+ * does need the module.
49
+ */
50
+ const dryRunnableCommands = new Set(["start", "restart", "install"]);
51
+
52
+ /** Whether this invocation will actually end in a running agent. */
53
+ function needsNativeSqlite(argv) {
54
+ const command = argv[2] ?? "";
55
+ if (!commandsNeedingNativeSqlite.has(command)) return false;
56
+ return !(dryRunnableCommands.has(command) && argv.includes("--dry-run"));
57
+ }
58
+
59
+ function errorSummary(error) {
60
+ // Native loader errors can carry ANSI colour, which lands as escape noise in
61
+ // a log file or a non-TTY pipe. Strip it before picking the line to show.
62
+ const message = stripVTControlCharacters(
63
+ error instanceof Error ? error.message : String(error),
64
+ );
65
+ const firstLine = message
66
+ .split(/\r?\n/)
67
+ .map((line) => line.trim())
68
+ .find(Boolean);
69
+ if (!firstLine) return "unknown error";
70
+ return firstLine.length > 240 ? `${firstLine.slice(0, 237)}...` : firstLine;
71
+ }
72
+
73
+ function nativeRecoveryAdvice() {
74
+ return process.versions.electron
75
+ ? "Update or reinstall the Uai Host desktop app and retry."
76
+ : "Reinstall @moondeck/station; if that still fails, switch to Node.js 24 LTS and reinstall.";
77
+ }
78
+
79
+ async function probeNativeSqlite() {
80
+ try {
81
+ const { default: Database } = await import("better-sqlite3");
82
+ const database = new Database(":memory:");
83
+ database.close();
84
+ return { ok: true };
85
+ } catch (error) {
86
+ return { ok: false, error };
87
+ }
88
+ }
89
+
90
+ async function main() {
91
+ if (!Number.isInteger(nodeMajor) || nodeMajor < minimumNodeMajor) {
92
+ console.error(
93
+ `moondeck-station requires Node.js 20 or newer; found v${process.versions.node}. Install Node.js 24 LTS and retry.`,
94
+ );
95
+ process.exitCode = 1;
96
+ return;
97
+ }
98
+
99
+ const sqliteProbe = await probeNativeSqlite();
100
+ if (!sqliteProbe.ok) {
101
+ const detail =
102
+ `moondeck-station could not load its native SQLite module on Node.js v${process.versions.node}. ` +
103
+ `${nativeRecoveryAdvice()} Cause: ${errorSummary(sqliteProbe.error)}`;
104
+ if (needsNativeSqlite(process.argv)) {
105
+ console.error(detail);
106
+ process.exitCode = 1;
107
+ return;
108
+ }
109
+ // Still say it — a broken module IS why their service is down — but let
110
+ // the command through so they can read the log and uninstall.
111
+ console.warn(`${detail} Continuing — this command does not need it.`);
112
+ }
113
+
114
+ // Only when the probe actually succeeded — otherwise this contradicts the
115
+ // warning just printed, telling the user SQLite loaded right after saying it
116
+ // could not.
117
+ if (sqliteProbe.ok && !testedNodeMajors.has(nodeMajor)) {
118
+ console.warn(
119
+ `moondeck-station has not been tested on Node.js v${process.versions.node}; continuing because native SQLite loaded. Node.js 24 LTS is recommended.`,
120
+ );
121
+ }
122
+
123
+ // Keep this import behind the native probe: the preflight must run in plain
124
+ // Node before tsx (and the TypeScript application graph) is loaded.
125
+ const { register } = await import("tsx/esm/api");
126
+ register();
127
+ await import(new URL("../src/cli.ts", import.meta.url).href);
128
+ }
129
+
130
+ await main();
@@ -0,0 +1,12 @@
1
+ CREATE TABLE `uai_host_tasks` (
2
+ `task_id` text PRIMARY KEY NOT NULL,
3
+ `code_server_port` integer,
4
+ `preview_ports` text DEFAULT '[]' NOT NULL,
5
+ `compose_project` text,
6
+ `worktree_path` text,
7
+ `status_mirror` text,
8
+ `locked_at` integer,
9
+ `started_at` integer,
10
+ `ended_at` integer,
11
+ `updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL
12
+ );
@@ -0,0 +1,11 @@
1
+ ALTER TABLE `uai_host_tasks` ADD `owner_user_id` text;--> statement-breakpoint
2
+ ALTER TABLE `uai_host_tasks` ADD `owner_email` text;--> statement-breakpoint
3
+ ALTER TABLE `uai_host_tasks` ADD `project_slugs` text DEFAULT '[]' NOT NULL;--> statement-breakpoint
4
+ CREATE TABLE `uai_host_events` (
5
+ `id` text PRIMARY KEY NOT NULL,
6
+ `task_id` text NOT NULL,
7
+ `kind` text NOT NULL,
8
+ `ts` integer DEFAULT (unixepoch() * 1000) NOT NULL,
9
+ `detail` text
10
+ );--> statement-breakpoint
11
+ CREATE INDEX `uai_host_events_ts_idx` ON `uai_host_events` (`ts`);
@@ -0,0 +1,8 @@
1
+ CREATE TABLE `host_github_tokens` (
2
+ `user_id` text PRIMARY KEY NOT NULL,
3
+ `installation_id` integer NOT NULL,
4
+ `refresh_token_ct` blob NOT NULL,
5
+ `refresh_token_nonce` blob NOT NULL,
6
+ `refresh_token_expires_at` integer,
7
+ `updated_at` integer NOT NULL
8
+ );
@@ -0,0 +1,8 @@
1
+ CREATE TABLE `host_ssh_keys` (
2
+ `user_id` text PRIMARY KEY NOT NULL,
3
+ `public_key` text NOT NULL,
4
+ `private_key_ct` blob NOT NULL,
5
+ `private_key_nonce` blob NOT NULL,
6
+ `created_at` integer NOT NULL,
7
+ `updated_at` integer NOT NULL
8
+ );
@@ -0,0 +1 @@
1
+ ALTER TABLE `uai_host_tasks` ADD `owner_name` text;
@@ -0,0 +1,5 @@
1
+ CREATE TABLE `host_env_vars` (
2
+ `key` text PRIMARY KEY NOT NULL,
3
+ `value_enc` text NOT NULL,
4
+ `updated_at` integer NOT NULL DEFAULT (unixepoch() * 1000)
5
+ );
@@ -0,0 +1,9 @@
1
+ DROP TABLE IF EXISTS `host_env_vars`;
2
+ --> statement-breakpoint
3
+ CREATE TABLE `host_project_env` (
4
+ `project_id` text NOT NULL,
5
+ `key` text NOT NULL,
6
+ `value_enc` text NOT NULL,
7
+ `updated_at` integer NOT NULL DEFAULT (unixepoch() * 1000),
8
+ PRIMARY KEY (`project_id`, `key`)
9
+ );
@@ -0,0 +1,7 @@
1
+ -- ADR-033: non-expiring per-host tokens. `kind` distinguishes the stored token:
2
+ -- 'refresh' (legacy, ADR-027) → refresh_token_ct holds a refresh token; the
3
+ -- host exchanges it for short-lived access tokens.
4
+ -- 'access' (ADR-033) → refresh_token_ct holds a long-lived, non-
5
+ -- expiring access token; injected directly, no exchange/refresh.
6
+ -- Existing rows default to 'refresh' (their current semantics).
7
+ ALTER TABLE `host_github_tokens` ADD `kind` text DEFAULT 'refresh' NOT NULL;
@@ -0,0 +1,18 @@
1
+ -- ADR-057: user-authed MCP connections. Secrets sealed with the host master
2
+ -- key (ct.nonce base64, like host_project_env.value_enc); the cloud holds only
3
+ -- non-secret metadata.
4
+ CREATE TABLE `host_mcp_connections` (
5
+ `id` text PRIMARY KEY NOT NULL,
6
+ `user_id` text NOT NULL,
7
+ `url` text NOT NULL,
8
+ `auth_kind` text NOT NULL,
9
+ `token_endpoint` text,
10
+ `redirect_uri` text,
11
+ `client_id` text,
12
+ `client_secret_enc` text,
13
+ `pkce_verifier_enc` text,
14
+ `secret_enc` text,
15
+ `status` text DEFAULT 'pending' NOT NULL,
16
+ `scopes` text,
17
+ `updated_at` integer NOT NULL
18
+ );
@@ -0,0 +1,15 @@
1
+ -- ADR-061: durable agent sessions. One row per (task, agent) tracking the
2
+ -- live in-container runner and how far the host has consumed its outbox —
3
+ -- the byte offset is what makes a host restart resume instead of respawn.
4
+ CREATE TABLE `host_agent_sessions` (
5
+ `task_id` text NOT NULL,
6
+ `agent_id` text NOT NULL,
7
+ `session_dir` text NOT NULL,
8
+ `container_name` text NOT NULL,
9
+ `kind` text NOT NULL,
10
+ `outbox_offset` integer DEFAULT 0 NOT NULL,
11
+ `status` text DEFAULT 'running' NOT NULL,
12
+ `created_at` integer NOT NULL,
13
+ `updated_at` integer NOT NULL,
14
+ PRIMARY KEY(`task_id`, `agent_id`)
15
+ );
@@ -0,0 +1,13 @@
1
+ -- ADR-076: multiple accounts per engine. One row per EXTRA (non-default)
2
+ -- credential for an engine kind; the legacy single slot is synthesized as the
3
+ -- "default" account and is NOT stored here. Secret material is sealed with the
4
+ -- host master key (env kind) or lives in an isolated host dir (config-dir kind).
5
+ CREATE TABLE `host_engine_accounts` (
6
+ `id` text PRIMARY KEY NOT NULL,
7
+ `kind` text NOT NULL,
8
+ `label` text NOT NULL,
9
+ `auth_kind` text NOT NULL,
10
+ `secret_enc` text,
11
+ `config_dir` text,
12
+ `created_at` integer NOT NULL
13
+ );
@@ -0,0 +1,5 @@
1
+ -- ADR-078: Codex restart-survival. Persist the app-server thread id so a
2
+ -- respawned Codex (allowAttach:false → kill+respawn on host restart) can
3
+ -- `thread/resume` the SAME durable thread from its on-disk rollout instead of
4
+ -- starting a fresh one and losing the conversation. Null for non-codex kinds.
5
+ ALTER TABLE `host_agent_sessions` ADD `codex_thread_id` text;
@@ -0,0 +1,4 @@
1
+ -- Durable runners otherwise attach by task + agent alone. Persist the
2
+ -- host-owned launch-policy generation so a rolling policy/tool transition can
3
+ -- replace an incompatible live runner instead of silently inheriting it.
4
+ ALTER TABLE `host_agent_sessions` ADD `attach_compatibility_key` text;
@@ -0,0 +1,11 @@
1
+ ALTER TABLE `host_github_tokens` ADD `generation` integer DEFAULT 0 NOT NULL;
2
+ --> statement-breakpoint
3
+ CREATE TABLE `host_github_credential_generations` (
4
+ `user_id` text PRIMARY KEY NOT NULL,
5
+ `generation` integer DEFAULT 0 NOT NULL,
6
+ `mutation` text DEFAULT 'set' NOT NULL,
7
+ `updated_at` integer NOT NULL
8
+ );
9
+ --> statement-breakpoint
10
+ INSERT INTO `host_github_credential_generations` (`user_id`, `generation`, `mutation`, `updated_at`)
11
+ SELECT `user_id`, 0, 'set', `updated_at` FROM `host_github_tokens`;
@@ -0,0 +1 @@
1
+ CREATE INDEX `uai_host_events_task_id_idx` ON `uai_host_events` (`task_id`);
@@ -0,0 +1,9 @@
1
+ CREATE TABLE `host_settings` (
2
+ `id` text PRIMARY KEY DEFAULT 'host' NOT NULL,
3
+ `retain_terminal_history` integer DEFAULT true NOT NULL,
4
+ `updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL,
5
+ CONSTRAINT "host_settings_singleton" CHECK (`host_settings`.`id` = 'host'),
6
+ CONSTRAINT "host_settings_retention_boolean" CHECK (`host_settings`.`retain_terminal_history` IN (0, 1))
7
+ );
8
+ --> statement-breakpoint
9
+ INSERT INTO `host_settings` (`id`, `retain_terminal_history`) VALUES ('host', true);
@@ -0,0 +1,2 @@
1
+ ALTER TABLE `uai_host_tasks` ADD `environment_provider` text;--> statement-breakpoint
2
+ ALTER TABLE `uai_host_tasks` ADD `environment_locator` text CONSTRAINT `uai_host_tasks_environment_pair` CHECK ((`environment_provider` IS NULL) = (`environment_locator` IS NULL));
@@ -0,0 +1,7 @@
1
+ -- ADR-131 Decision 3: engine accounts carry a scope — 'host' (the owner's,
2
+ -- today's rows) or 'org:<orgId>' (a workspace's account on a shared host).
3
+ -- Tasks resolve accounts by their scope; org scopes never see the default
4
+ -- slot or the host's rows.
5
+ ALTER TABLE `host_engine_accounts` ADD `scope` text NOT NULL DEFAULT 'host';--> statement-breakpoint
6
+ CREATE INDEX `host_engine_accounts_scope_kind_idx` ON `host_engine_accounts` (`scope`,`kind`);--> statement-breakpoint
7
+ ALTER TABLE `uai_host_tasks` ADD `engine_account_scope` text;
@@ -0,0 +1,3 @@
1
+ -- Manage-v1 (host 0.9.104): explicit priority order for a kind's extra
2
+ -- accounts within a scope. Existing rows keep 0 and fall back to created_at.
3
+ ALTER TABLE `host_engine_accounts` ADD `position` integer NOT NULL DEFAULT 0;
@@ -0,0 +1,3 @@
1
+ -- ADR-134 (host 0.9.111): the credential-store parameter an engine account
2
+ -- mirrors to. Null on owned hosts and before the first successful mirror.
3
+ ALTER TABLE `host_engine_accounts` ADD `store_ref` text;
@@ -0,0 +1,6 @@
1
+ -- ADR-134 slice 2 (host 0.9.113): per-user credentials mirror to the
2
+ -- credential store on Uai-operated hosts. Null on owned hosts and before the
3
+ -- first successful mirror.
4
+ ALTER TABLE `host_github_tokens` ADD `store_ref` text;--> statement-breakpoint
5
+ ALTER TABLE `host_ssh_keys` ADD `store_ref` text;--> statement-breakpoint
6
+ ALTER TABLE `host_mcp_connections` ADD `store_ref` text;
@@ -0,0 +1,3 @@
1
+ -- ADR-134 slice 3 (host 0.9.115): project env values mirror to the credential
2
+ -- store on Uai-operated hosts. Null on owned hosts and before the first mirror.
3
+ ALTER TABLE `host_project_env` ADD `store_ref` text;
@@ -0,0 +1,5 @@
1
+ -- Machine tasks (ADR-121) have no compose env, so per-project env values
2
+ -- (ADR-015) must ride each agent session's env instead — and the host only
3
+ -- knew a task's project SLUGS while env is keyed by project id. Lead project
4
+ -- first. Older rows stay '[]' (Docker tasks never read it).
5
+ ALTER TABLE `uai_host_tasks` ADD `project_ids` text NOT NULL DEFAULT '[]';
@@ -0,0 +1,4 @@
1
+ -- ADR-133: the project's org, so the mirrored parameter can carry the `org`
2
+ -- tag the task-machine lease policy conditions on. Null until the cloud sends
3
+ -- it (env.var.set / env.var.list with orgId) or a stored value carries it.
4
+ ALTER TABLE `host_project_env` ADD `org_id` text;
@@ -0,0 +1,9 @@
1
+ -- ADR-153: durable rollout fence for per-command project env delivery.
2
+ -- Existing/null rows are interpreted as `baked`; new task-up writes `loader`
3
+ -- before creating runtime resources, so recovery never launches a binary that
4
+ -- is absent from an older container image.
5
+ ALTER TABLE `uai_host_tasks` ADD `env_mode` text;
6
+ --> statement-breakpoint
7
+ -- Value-free declaration/literal metadata needed to rebuild the resolver
8
+ -- after runner restart, before the cloud re-sends task-up.
9
+ ALTER TABLE `uai_host_tasks` ADD `project_env_spec` text;